Перейти к содержанию

Коннекторы и креденшiалы

Как Axon подключается к внешним сервисам (Gmail, Telegram, CRM, диски, вебхуки) и где хранятся учётные данные. Этот мануал отвечает на вопросы: что такое «коннектор» и «креденшiал» и почему это не одно и то же; кто что может делать; что за кнопки Allowlist и Add instance в разделе Connectors; как пройти весь путь от нового проекта до работающей интеграции.


1. Что это и зачем

В Axon одна интеграция с внешним сервисом раскладывается на две ортогональные оси:

  • Коннектор — это код («как разговаривать с сервисом»): класс-адаптер (GmailConnector, TelegramConnector…), который умеет вызывать API сервиса, нормализовать входящие события, проверять подписи вебхуков. Плюс декларативное описание формата учётных данных (CredentialType). Коннекторы одинаковы для всех проектов, в БД их нет — они деплоятся вместе с платформой.
  • Креденшiал — это данные конкретной учётной записи («с чьими полномочиями»): зашифрованный секрет (OAuth-токен, API-ключ) + его жизненный цикл (активен / протух / отозван, когда последний раз обновлялся, когда истекает). Креденшiал — project-scoped, лежит в БД (app.connector_credentials), создаётся и управляется через Console.

Между ними есть промежуточная сущность — connector instance («сконфигурированное подключение») = адаптер + креденшiал + non-secret настройки. Workflow ссылается именно на инстансы (через именованные слоты), а не на коннектор-код и не на креденшiал напрямую.

Зачем разделено (а не «одна сущность “интеграция”»): - у кода адаптера и у токена разные жизненные циклы — код меняется релизом, токен протухает/отзывается/обновляется постоянно; - граница безопасности: app-api свободно работает с адаптером, но никогда не видит plaintext-секрет — расшифровка возможна только в worker (инвариант I-1); - 1:N: один креденшiал (один Gmail-аккаунт) может питать несколько инстансов с разными настройками; один адаптер — много инстансов; - project-isolation: креденшiал строго привязан к проекту, нельзя шарить между проектами; адаптер — общий; - аудит и governance: каждое касание секрета — отдельная запись в audit log, отдельные permissions, отдельный approval-gate.

Аналогия за 60 секунд: CredentialType = бланк удостоверения (какие поля бывают). Credential = твоё конкретное удостоверение в сейфе (номер зашифрован, на корешке видно «действительно до», «проверка ОК»). Connector (адаптер) = умение водить машину этого сервиса. ConnectorInstance = «эта машина, на которой я езжу с этим удостоверением, с настройками под себя». BindingProfile = инструкция бригаде «на этот рейс берём вот эту машину». Сейф (worker) открывается своим ключом; диспетчер (app-api) видит «удостоверение есть, действительно, привязано к машине №3» — самих цифр не видит.


2. Роли и доступ

Источник: core/models/auth.py. owner / admin — instance-scoped (действуют во всех проектах). manager / operator / reviewer / read_only — project-scoped (только в проектах из своих project_scopes). system — служебные акторы (auto-refresh, health, retention), людям эти права не выдаются (инвариант I-9: HUMAN ∩ SYSTEM_ONLY = ∅).

Действие Permission owner admin manager operator reviewer read_only system
Видеть коннекторы / инстансы / здоровье read*
Видеть креденшiалы (redacted) credential:read
Allowlist toggle (вкл/выкл коннектор для проекта) project_connector:manage
Add instance / update / delete connector instance connector:manage
Настроить workflow binding profile (слот → инстанс) workflow:configure_bindings
New credential / OAuth connect credential:create
Update креденшiал (метаданные/payload) credential:update
Revoke креденшiал credential:revoke
Test connection credential:test
Использовать креденшiал в run'е credential:use
Approve high-stakes credential proposal approve
Запустить workflow (использует привязанные инстансы) start_workflow
Reauthorize OAuth / refresh-rotate credential:rotate ✅† ✅† ✅†
Auto-healing / maintain credential:maintain
Hard-purge / GDPR-erasure credential:purge
Webhook ingress verifier upsert/disable ingress_verifier:manage

* отдельного connector:read permission в core/models/auth.py нет — просмотр коннекторов/инстансов/здоровья гейтится read + наличие project access (канонические доки query-роутов иногда пишут connector:read как сокращение; RBAC-слой это покрывает через read). † credential:rotate есть у owner/admin (через HUMAN_PERMISSIONS), у manager (через _CREDENTIAL_MANAGER_PERMISSIONS) и у system. Автоматический OAuth refresh (refresh_oauth_credential) выполняется только system (handler-level allowlist (command_type, actor_id, source)); ручной reauthorize (complete_oauth_credential в режиме переавторизации) — manager+. Отдельной команды rotate_credential в текущей версии нет. ‡ credential:purgecredential:maintain) — в SYSTEM_ONLY_PERMISSIONS, людям не выдаются (I-9). Сама команда purge_credentialdelete_credential с deletion_reason) пока не реализована — это beyond-Stage-3 backlog (CONCEPT-CONNECTORS.md §0.1); DDL-колонки под GDPR (deleted_at/deletion_reason/purge_after/purged_at) уже есть.

Главное: managerне «только смотрит». Менеджер в своём проекте может создавать креденшiалы, создавать connector instances, переключать allowlist, настраивать binding profiles и использовать всё это в run'ах. Чисто «смотрят» — reviewer и read_only. operator — смотрит + test + use + запускает workflow, но не создаёт креды/инстансы и не трогает allowlist. Создавать проекты и пользователей (create_project, manage_users) могут только owner / admin.

credential:use ≠ право расшифровать. Это «capability членства в проекте» — нужно, чтобы runtime использовал креденшiал от имени актора. Plaintext всё равно расшифровывается только в worker; Console показывает редактированные данные (без токенов).

2.1 Architect / Engineer — роль вне RBAC

«Architect» (программист) — это не одна из ролей core/models/auth.py; это персона, которая работает на уровне git + деплоя, а не Console. Граница «код ↔ Console» в этом домене:

Делает в коде (modules/, IDE, git) Делает в Console (как manager+)
Пишет connector-адаптеры — modules/connectors/{gmail,telegram,...}/ (наследники ConnectorBase: API + MCP tools + normalize() + verify_signature() + action manifest)
Пишет CredentialTypemodules/credential_types/*.py (формат полей, auth_inject, OAuth2-метаданные, test_request, extends) → CredentialTypeRegistry
Пишет WorkflowDefinition с ConnectorRequirement[] — именованные слоты + логические типы + required_actions Публикует определение (publish_definition) — если у него есть Console-роль manager+; иначе публикует отдельный manager
Подключает креденшiалы, создаёт connector instances, настраивает allowlist и binding profiles

То есть «коннектор как возможность» и «формат креденшiала» появляются в коде (architect + деплой), а «конкретный креденшiал», «инстанс», «allowlist», «привязка к workflow» — в Console (manager+). Все адаптеры и CredentialType общие для всех проектов и в БД не хранятся.


3. Где это в Console

Два смежных раздела в сайдбаре под группой Administration:

3.1 Раздел «Connectors» — /{project_id}/connectors

Состоит из трёх блоков.

Блок «Project connector allowlist» — таблица «какие connector keys разрешены в этом проекте».

Колонка / элемент Что это
Connector connector_key — конкретный ключ коннектора (например gmail_oauth2_test2). Подпись блока: «Concrete connector keys only» — здесь именно конкретные ключи, не логические типы.
Credential Какой креденшiал ассоциирован с этим ключом напрямую (или No active credential, если привязка живёт только в connector instance ниже).
Allowlist (тоггл) Включить/выключить коннектор для проекта. ON = коннектор разрешён (входящие вебхуки на этот ключ принимаются, binding resolver видит коннектор). OFF = fail-closed: вебхуки отвергаются (generic 401, событие не создаётся), коннектор недоступен для привязок. Подпись: «Credentials stay intact when a connector is disabled» — выключение не трогает креды и инстансы, это «рубильник», не «удаление». Доступно manager+ (project_connector:manage); под капотом — команда set_project_connector_enabled, пишет строку app.project_connector_allowlist.
Updated Когда последний раз менялся тоггл.
Кнопка New credential (верх блока) Перебрасывает в флоу создания креденшiала (см. §3.2 / §5).

Блок «Connector instances» — таблица сконфигурированных подключений (app.connector_instances).

Колонка / элемент Что это
Instance Display name + connector_key подзаголовком (например gmail_oauth2 (migrated) / gmail_oauth2_test2). Пометка (migrated) — инстанс перенесён из старого file-mount flow.
Credential На какой креденшiал ссылается инстанс (credential_id обязателен — инстанс без креденшiала не имеет смысла).
Adapter adapter_key — конкретная фабрика адаптера (gmail, telegram, bitrix24…).
Status Lifecycle-статус инстанса: active / paused / error. Ниже мелким — health_status: healthy / degraded / down / unknown (оперативное здоровье; unknown, пока runtime не отрепортил статус).
Actions Action manifest адаптера (D45): какие конкретные операции этот инстанс публикует (search_email, send_email…). Валидатор workflow проверяет требуемые шагом действия против этого списка.
Manage (...) Меню: изменить display_name / config; поставить на паузу / возобновить (смена status через update_connector_instance); удалить. credential_id / connector_type / adapter_key / connector_key после создания неизменяемы (update_connector_instance мутирует только display_name/config/status) — чтобы сменить креденшiал или адаптер, создаётся новый инстанс. Delete = soft-delete/tombstone (D41): строка остаётся для аудита/replay; блокируется, если на инстанс ссылается binding profile или активный/pending run snapshot. UI предлагает «find-and-replace» перед удалением.
Кнопка Add instance (верх блока) Создать новый connector instance — см. §5, флоу 3.

Блок «Connector health» — таблица оперативного здоровья per connector. Появляется после того, как коннектор отрепортил runtime-статус (probe / реальное использование). Пока пусто — «No connector health rows / Health appears after a connector reports runtime status». Пишется системой через внутреннюю команду update_connector_health (internal, 403 from external) → app.connector_health_snapshots + connector_instances.health_status. Auto-pause инстанса при устойчивом down — функция auto-healing monitor (спроектирована, см. CONCEPT-CONNECTORS.md §0.1 backlog); в текущей версии паузу ставит человек через update_connector_instance (status='paused').

3.2 Раздел «Credentials» — /{project_id}/credentials

Список креденшiалов проекта (redacted — без секретов) + кнопка создания.

Колонка / элемент Что это
Display name Человекочитаемое имя (Sales Inbox). Уникально в пределах (project, credential_type_key) среди не-удалённых.
Type CredentialType (Gmail (OAuth 2.0), Generic HMAC Webhook, …) с иконкой.
Status active / expired / revoked / error.
Last test Когда и с каким результатом был последний test_credential (success / failure). При failure — компактное сообщение об ошибке (≤500 символов, без утечки токена).
Last used / Last refresh Когда креденшiал последний раз использовался / последний успешный OAuth refresh. При накоплении refresh_failure_count > 0 креденшiал помечается «distressed».
Expires Если известно (refresh-токены часто не истекают).
Actions Test connection; Reauthorize (для OAuth — повторный consent); Revoke (soft tombstone — выводит креденшiал из обращения; зависимые connector instances при этом становятся unusable по D42; блокируется, если на креденшiал ссылается активный/приостановленный workflow или активный/pending run, использующий зависимый инстанс). Полноценный «delete»/GDPR-purge — beyond Stage 3, ещё не реализован.
Кнопка New credential Мастер создания — см. §5, флоу 2.

4. Концепции (mental model)

Слоёная модель из пяти сущностей:

СЛОЙ КОДА (общий для всех проектов, в БД нет)
  ├─ Connector adapter        modules/connectors/{gmail,telegram,...}/
  │     ConnectorBase: API + MCP tools + normalize() + verify_signature()
  │     identity: connector_key (legacy) / adapter_key (concrete factory)
  └─ CredentialType           modules/credential_types/*.py → CredentialTypeRegistry
        формат полей, auth_inject, OAuth2-метаданные, test_request
        наследование: extends: ["generic_oauth2"]   (новый OAuth-провайдер ~50-100 строк)

СЛОЙ ДАННЫХ (project-scoped, app.*, мутации только через CommandEnvelope)
  ├─ Credential               app.connector_credentials   id: cred_<ulid>
  │     зашифрованный секрет (envelope: DEK под KEK, AES-256-GCM)
  │     + lifecycle: status, last_test_*, last_used_at, last_refresh_at,
  │       refresh_failure_count, expires_at, soft-delete + GDPR (purge_after/purged_at)
  ├─ ConnectorInstance        app.connector_instances     id: ci_<ulid>
  │     = adapter_key + credential_id (NOT NULL) + connector_type (логич.)
  │       + connector_key (runtime registry key, уникален per project)
  │       + config (ТОЛЬКО non-secret: polling_interval_seconds, webhook_url,
  │         label_ids, allowed_chat_ids) + status + health_status
  ├─ project_connector_allowlist   (project_id, connector_key) → enabled   (SoT-тоггл)
  └─ connector_ingress_verifiers   проекция секрета для верификации вебхуков (D54)

СЛОЙ ПРИВЯЗКИ К WORKFLOW
  ConnectorRequirement[]  (в WorkflowDefinition, пишет инженер: «нужен email + crm»,
        именованные слоты + логический тип + required_actions)
    → WorkflowBindingProfile  (admin/manager один раз: main_email → ci_gmail_sales)
      → workflow_run_bindings_snapshot  (пиннится на каждый run, D17/D36)

Три разных «имени» коннектора (важно не путать): - connector_typeлогическая способность: email, crm, storage. Это то, что декларирует workflow («мне нужен email»). - adapter_keyконкретный провайдер/фабрика: gmail, imap, smtp, bitrix24. Это то, что выбирает админ при создании инстанса. - connector_keyruntime-ключ реестра конкретного инстанса (gmail_sales, gmail_support). Уникален в пределах проекта среди не-удалённых (connector_instances_unique_active_key_idx). Allowlist оперирует именно connector_key.

Развязка connector_type ≠ adapter_key (решение D35) означает: workflow не привязан к Gmail — он просит «email», а админ может удовлетворить это Gmail'ом, или IMAP, или SMTP. Совместимость по действиям при этом не выводится из типа: адаптер обязан публиковать action manifest, и валидатор проверяет required_actions шага против него (D45) — email-коннектор без send_email не закроет требование шага send_email.

1:N. Один креденшiал → много инстансов: один Gmail-аккаунт → инстанс gmail_sales (метки «Продажи») и инстанс gmail_support (метки «Поддержка»), у каждого свой connector_key и свой config, но один credential_id. Поэтому connector_instances — отдельная таблица с FK на credential_id (composite FK (project_id, credential_id) запрещает cross-project leak на уровне БД).

Три слоя override при запуске (D17): profile.bindings ← case.connector_overrides ← run.binding_overrides. Profile — baseline (90% случаев); case override — редкое исключение (multi-bot Telegram, BYOK SaaS); run override — тестовые/одноразовые запуски.


5. Флоу: пошаговые сценарии

Флоу 1 — Полный путь: от нового проекта до настроенной интеграции

Фаза Что делается Кто (роль / permission)
0. Платформенный код Написать connector-адаптер (modules/connectors/...) и CredentialType (modules/credential_types/...); задеплоить. Это общее для всех проектов, в Console не делается. Инженер платформы (git + деплой)
1. Создать проект app.projects row, базовый ProjectConfig. owner / admin (create_project)
2. Завести людей Создать пользователей, выдать роли и project_scopes (например manager на этот проект — он дальше всё настроит). owner / admin (manage_users)
3. Опубликовать workflow-определения Инженер пишет WorkflowDefinition в коде с ConnectorRequirement[] (именованные слоты + логические типы + required_actions). Публикация через API. На первом publish, если в проекте уже есть ровно один usable инстанс под каждый требуемый тип → авто-создаётся binding profile (status=complete, auto_configured=true, баннер «Review»). Инженер (код) → publish: manager+ (publish_definition)
4. Подключить креденшiалы Console → Credentials → New credential → выбрать CredentialType → display name → «Connect with Google» (OAuth consent → callback) → авто test_credential → креденшiал сохранён (status=active). Повторить для каждого аккаунта. Для high-stakes типов (requires_approval=true) — создаётся credential_proposal, креденшiал материализуется только после approve. manager+ (credential:create); approval — reviewer/manager+ (approve)
5. Создать connector instances Console → Connectors → Add instance → adapter (gmail) + credential (Sales Inbox) + non-secret config (label_ids, polling_interval_seconds, webhook_url) + display name → команда create_connector_instance → строка app.connector_instances + outbox-события (connector_instance.created, при необходимости проекция ingress verifier и регистрация вебхука у провайдера — асинхронно в worker). Повторить для каждого инстанса. manager+ (connector:manage)
6. Включить коннекторы в allowlist Console → Connectors → Allowlist toggle ON для каждого connector_key → команда set_project_connector_enabled. Без этого вебхуки fail-closed и коннектор недоступен для привязок. manager+ (project_connector:manage)
7. Настроить binding profile Console → Workflows → <definition> → Bindings → «Auto-map» (ConnectorMapper предлагает инстансы по типу) или вручную выбрать инстанс под каждый слот → команда set_workflow_bindings → profile status=complete. Set-once-use-many. manager+ (workflow:configure_bindings)
8. Запускать workflow Console → Run Wizard → выбрать case → bindings подтягиваются из profile автоматически; опционально override на этот запуск. Запуск пиннит workflow_run_bindings_snapshot (D36, system). На connector-step worker резолвит инстанс → берёт креденшiал → расшифровывает в памяти worker → policy gate / approval / audit / idempotency → side effect. operator+ (start_workflow + credential:use)
9. Эксплуатация Автоматический OAuth refresh (system, refresh_oauth_credential); reauthorize при провале refresh (deeplink-алерт в Telegram → manager/admincomplete_oauth_credential); health-мониторинг (system → update_connector_health); revoke креденшiала / pause-resume-delete инстанса (manager+). GDPR-инструменты (delete_credential с deletion_reason, hard-purge sweeper по purge_after) — beyond Stage 3, ещё не реализованы. system + manager+

Минимальный «happy path»: owner создаёт проект и заводит manager'а → manager: New credentialAdd instanceAllowlist ON → Bindings → operator: Run. Если в проекте по одному инстансу на тип — шаг Bindings автоматизируется.

Флоу 2 — Подключить Gmail за ~90 секунд (manager+)

  1. Console → Credentials → New credential.
  2. Категория «Email» → «Gmail (OAuth 2.0)».
  3. Display name: Sales Inbox.
  4. Connect with Google → Google consent screen → разрешить → callback в app-api → код уходит в worker, обменивается на токены, токены шифруются (envelope) → строка app.connector_credentials (cred_<ulid>, status=active).
  5. Авто test_credential (worker-side probe) → last_test_result=success → ✅.
  6. Готово. Креденшiал доступен для создания Gmail connector instances в этом проекте.

Если у CredentialType requires_approval=true — после шага 4 создаётся credential_proposal; креденшiал появится только после approve от reviewer/manager+ (нужно для production CRM, payment-провайдеров).

Флоу 3 — Создать connector instance (Add instance, manager+)

  1. Console → Connectors → Add instance.
  2. Adapter — выбрать конкретный адаптер (gmail).
  3. Credential — выбрать креденшiал проекта (Sales Inbox). Валидируется: тот же проект, usable (status='active', не soft-deleted, не purged, encrypted payload/DEK на месте — условие D42); adapter_key поддерживается этим CredentialType.
  4. Display name (Sales Inbox Gmail); формируется connector_key (например gmail_sales) — уникальный в проекте.
  5. Config — только non-secret параметры: label_ids (Gmail), allowed_chat_ids (Telegram), polling_interval_seconds, webhook_url. Секреты сюда класть нельзяwebhook_secret, API-ключи, HMAC-секреты живут в зашифрованном payload креденшiала; для верификации вебхуков система отдельно проецирует connector_ingress_verifiers.
  6. Сохранить → команда create_connector_instance → строка app.connector_instances + outbox: connector_instance.created, опционально …ingress_verifier_projection_requested (worker спроецирует verifier подписи), опционально …webhook_registration_requested (worker дёрнет setWebhook у провайдера — для Telegram-подобных). Провайдерские API inline не вызываются.

Флоу 4 — Один коннектор + два разных креденшiала в одном workflow

Сценарий: workflow рассылает письма и с админского ящика, и с ящика саппорта.

  1. Инженер в WorkflowDefinition объявляет два именованных требования одного типа: ConnectorRequirement(name="admin_mailbox", connector_type="email") и ConnectorRequirement(name="support_mailbox", connector_type="email").
  2. Шаги workflow ссылаются на нужный слот (connector_requirement / connector_instance_id в step.config).
  3. manager+: создать два креденшiала (Admin Inbox, Support Inbox) и два инстанса (gmail_admin, gmail_support — оба adapter_key=gmail, оба connector_type=email, разные connector_key, каждый со своим credential_id).
  4. manager+: в Bindings workflow'а замаппить admin_mailbox → gmail_admin, support_mailbox → gmail_support. (Можно даже разные адаптеры: admin_mailbox → Gmail, support_mailbox → IMAP, лишь бы оба удовлетворяли типу email и публиковали нужные действия — D45.)
  5. На запуске каждый шаг резолвит свой инстанс → свой креденшiал.

Ограничение: нельзя привязать два разных креденшiала к одному и тому же слоту в одном run'е — слот → ровно один connector_instance → ровно один креденшial (Mode XOR, D27). Нужно два ящика → два слота.

Флоу 5 — Reauthorize протухшего OAuth (manager / admin)

  1. Worker при провале refresh шлёт deeplink-алерт в Telegram владельцу/админу.
  2. Console → Credentials → найти креденшiал в статусе error/distressed → Reauthorize.
  3. Повторный Google consent → callback → команда complete_oauth_credential (режим переавторизации; worker_apply_then_signal, permission credential:rotate) → новый payload (новая payload_version) → зависимые ingress verifiers и кэши инвалидируются автоматически → status=active, refresh_failure_count=0.

Флоу 6 — Временно отключить коннектор (manager+)

Console → Connectors → блок allowlist → тоггл Allowlist OFF для нужного connector_key. Эффект: входящие вебхуки на этот ключ отвергаются (fail-closed), коннектор недоступен для новых привязок. Креденшiалы и инстансы остаются нетронутыми — включил тоггл обратно, всё снова работает.


6. Справочник опций

6.1 Креденшiал (app.connector_credentials)

Поле Что делает Дефолт / ограничения
display_name Человекочитаемое имя обязательно; уникально в (project, credential_type_key) среди не-удалённых
credential_type_key Какой CredentialType (gmail_oauth2, …) обязательно; должен существовать в CredentialTypeRegistry
encrypted_data / payload_nonce / payload_auth_tag / encrypted_dek / dek_nonce / dek_auth_tag / kek_version Зашифрованный секрет (envelope: payload под DEK, DEK под KEK, AES-256-GCM) заполняется только в worker; в Console не виден; до purge — NOT NULL, после purge — NULL (tombstone)
status active / expired / revoked / error меняется только командами; RAW UPDATE запрещён
last_test_at / last_test_result / last_test_error Результат test_credential last_test_error ≤ 500 символов, без утечки токена
last_used_at Последнее использование обновляется только coalesced-командой record_credential_usage (не чаще ~1/60с)
last_refresh_at / refresh_failure_count / expires_at OAuth refresh state refresh_failure_count сбрасывается на успехе; > 0 = distressed
deleted_at / deletion_reason / purge_after / purged_at Soft-delete + GDPR (схема готова, команды частично) revoke_credential ставит deleted_at (soft tombstone); deletion_reason ∈ {user_request, project_archived, gdpr_erasure, incident}; delete_credential-с-deletion_reason и hard-purge sweeper по purge_after (purged_at зануляет payload/DEK) — beyond Stage 3, ещё не реализованы
version Optimistic concurrency все мутирующие команды требуют expected_version

6.2 Connector instance (app.connector_instances)

Поле Что делает Дефолт / ограничения
connector_type Логическая способность (email/crm/storage) — матчит ConnectorRequirement.connector_type обязательно; email ≠ gmail
adapter_key Конкретная фабрика адаптера (gmail/telegram/bitrix24) обязательно; должен поддерживаться CredentialType креденшiала
connector_key Runtime-ключ реестра (gmail_sales) уникален в проекте среди не-удалённых
credential_id Какой креденшiал использовать NOT NULL; composite FK (project_id, credential_id)
display_name Имя в UI обязательно
config (JSONB) Только non-secret параметры polling_interval_seconds, webhook_url, label_ids (Gmail), allowed_chat_ids (Telegram), … Секреты сюда запрещены (handler прогоняет display_name/config через credential-scan guards)
status active / paused / error меняется через update_connector_instance (connector:manage). credential_id/connector_type/adapter_key/connector_key — неизменяемы после создания
health_status healthy / degraded / down / unknown пишется системой через внутреннюю команду update_connector_health
deleted_at / deletion_reason Soft-delete/tombstone (D41) deletion_reason ∈ {user_request, project_archived, migration}; delete_connector_instance блокируется при ссылке из binding profile или активного/pending run snapshot; нет restore-команды

Когда инстанс считается «usable» (D42): не tombstoned (deleted_at IS NULL) + status='active' + backing credential того же проекта + credential.status='active' + не soft-deleted + не purged + encrypted payload/DEK на месте. Любой валидатор, принимающий connector_instance_id, проверяет это составное условие, а не только ci.status.

6.3 Project connector allowlist (app.project_connector_allowlist)

Поле Что делает
(project_id, connector_key) Natural key — на какой connector key распространяется тоггл
enabled (bool) SoT-флаг «коннектор разрешён в проекте». DB-строка бьёт legacy-YAML enabled_connectors. Нет строки + LEGACY_YAML_CONNECTORS=false → коннектор считается выключенным (fail-closed)

Меняется командой set_project_connector_enabled (sync_apply, публичная, project_connector:manage); читается через проекцию read.project_connector_allowlist (Console никогда не читает app.* напрямую).

6.4 Связанные команды (CommandEnvelope)

Значения колонок взяты из core/api/commands/execution_paths.py (путь), core/api/commands/policy.py (side-effect class), _COMMAND_PERMISSION_MAP там же (permission). «secret-bearing» — отдельная пометка о том, что хендлер расшифровывает/обрабатывает секретный материал (поэтому worker_apply_then_signal).

Команда Путь исполнения Side-effect class Permission Доступ Назначение
create_credential worker_apply_then_signal (secret-bearing) internal credential:create manager+ Создать креденшiал; шифрование/провайдер — только в worker
update_credential dual-path: sync_apply (metadata-only) / worker_apply_then_signal (если в payload credential_payload_ref — secret-bearing) internal credential:update manager+ Обновить метаданные / payload; connector_key неизменяем
revoke_credential sync_apply internal credential:revoke manager+ Soft tombstone (ставит deleted_at + retention purge_after), encrypted payload retained; зависимые CI становятся unusable (D42); блокируется при активном/приостановленном workflow или активном/pending run на зависимом CI
test_credential worker_apply_then_signal (secret-bearing) external_low credential:test operator+ Provider probe (adapter.health_check()) — только в worker
complete_oauth_credential worker_apply_then_signal (secret-bearing) external_low credential:create (purpose=create) / credential:rotate (purpose=reauthorize) internal-only (диспатчит OAuth-callback; grant пользователя перепроверяется в worker) Завершение OAuth callback / переавторизация
refresh_oauth_credential worker_apply_then_signal (secret-bearing) external_low credential:rotate internal-only (system actor allowlist) Авто-обновление OAuth-токена
mark_credential_distressed / mark_credential_expired / record_credential_usage sync_apply internal credential:maintain internal-only (system actor allowlist) Lifecycle/телеметрия (D40); record_credential_usage coalesced ≈1/60с
create_connector_instance / update_connector_instance / delete_connector_instance sync_apply internal connector:manage manager+ CRUD инстансов; update мутирует только display_name/config/status; delete = soft-tombstone (блокируется при ссылке из binding profile или активного/pending run snapshot); create эмитит outbox-события (провайдера inline не вызывает)
update_connector_health sync_apply — (не в credential side-effect-реестре) edit_project internal-only (403 from external) Health snapshot (D40)
set_project_connector_enabled sync_apply internal project_connector:manage manager+ (public) Allowlist toggle
set_workflow_bindings / clear_workflow_bindings / auto_map_workflow_bindings sync_apply internal workflow:configure_bindings manager+ Binding profile
upsert_connector_ingress_verifier worker_apply_then_signal (secret-bearing) internal ingress_verifier:manage internal-only (system actor allowlist) Проекция verifier'а вебхука (D54) — decrypt'ит durable credential payload
disable_connector_ingress_verifier sync_apply internal ingress_verifier:manage internal-only (system actor allowlist) Отключить verifier (D54)
pin_workflow_run_bindings sync_apply internal workflow_run:pin_bindings internal-only (sub-step start_workflow dispatch) Пиннит binding snapshot run'а (D36)

Не реализованы в текущей версии (beyond Stage 3 / зарезервированы как permission): delete_credential (soft-delete с deletion_reason), purge_credential (hard-purge sweeper, permission credential:purge), отдельная rotate_credential, отдельная pause_connector_instance (пауза — через update_connector_instance). См. CONCEPT-CONNECTORS.md §0.1 backlog.


7. Жизненный цикл и обслуживание

Статусный автомат креденшiала: active → (expired | revoked | error). revoked/expired немедленно делает все зависимые connector instances unusable (D42), даже если на них ссылаются по явному ID или run-override. CI при этом сам не мутируется — паузу инстанса (если нужна) ставят отдельной командой update_connector_instance (status='paused').

OAuth refresh (автоматический): только system (handler-level actor allowlist; permission credential:rotate), команда refresh_oauth_credential. Успех → last_refresh_at, refresh_failure_count=0. Провал → refresh_failure_count++; на пороге (см. credential_refresh_dispatcher) → mark_credential_distressed + deeplink-алерт в Telegram → ручной Reauthorize (Флоу 5, complete_oauth_credential).

Health-мониторинг: scheduled probe → update_connector_healthconnector_health_snapshots + connector_instances.health_status (audit пишется только на смене статуса). Авто-pause инстанса при устойчивом down — функция auto-healing monitor (спроектирована, см. CONCEPT-CONNECTORS.md §0.1 backlog); в текущей версии паузу ставит человек.

Вывод из обращения: - креденшiал — revoke_credential (soft tombstone, статус→revoked, deleted_at set, encrypted payload retained для аудита/возможного re-enable; зависимые CI становятся unusable по D42). Блокируется, если на креденшiал ссылается активный/приостановленный workflow либо активный/pending run, использующий зависимый инстанс — сначала заверши/отмени их; - connector instance — delete_connector_instance (soft/tombstone, D41); строка остаётся для аудита/replay; блокируется при ссылке из binding profile или активного/pending run snapshot; перед удалением — отключение D54 verifier; UI предлагает find-and-replace; - полноценный delete_credentialdeletion_reason, cascade-disable D54 verifiers) и hard-purge sweeper по purge_after (purged_at зануляет payload/DEK, tombstone-строка остаётся) — beyond Stage 3, ещё не реализованы (DDL-колонки уже есть).

Ротация ключей шифрования: versioned (kek_version); re-encrypt — maintenance CLI (worker-side), runbook операторский. (Полноценная rotation orchestration — beyond-scope, см. CONCEPT-CONNECTORS.md §0.1 / §26.)


8. Траблшутинг

Симптом Причина Что делать
В блоке allowlist у коннектора No active credential К самому connector_key в таблице allowlist не привязан креденшiал напрямую — привязка живёт в connector instance Это нормально, если есть connector instance с нужным credential_id. Проверь блок «Connector instances».
Входящий вебхук возвращает 401 (generic) Коннектор disabled в allowlist / нет ingress verifier / verifier устарел (credential_secret_versionpayload_version) / подпись не сошлась Включи Allowlist ON; проверь, что credential active и не distressed; при необходимости Reauthorize (это пересоздаёт verifier через outbox). Конкретная причина — в audit (в тело ответа не утекает).
Add instance отвергает выбранный креденшiал Креденшiал другого проекта / не usable (expired/revoked/soft-deleted/purged) / adapter_key не поддерживается этим CredentialType Используй активный креденшiал того же проекта; проверь, что adapter_key есть в supported_adapter_keys типа.
Workflow не запускается — «binding profile incomplete» Не все ConnectorRequirement слоты замаплены на usable инстансы Console → Workflows → <definition> → Bindings → «Auto-map» или вручную выбери инстанс под каждый слот; убедись, что инстансы usable (D42) и коннекторы в allowlist ON.
Revoke креденшiала не проходит («credential_in_use») На него ссылается активный/приостановленный workflow либо активный/pending run, использующий зависимый инстанс Заверши/отмени эти workflow/run'ы, затем Revoke. Idle-инстансы revoke не блокируют — они просто становятся unusable по D42. (Полноценный «delete»/GDPR-purge ещё не реализован — для вывода из обращения используется именно Revoke.)
Не получается удалить connector instance На него ссылается binding profile или активный/pending run snapshot Перепривяжи слот в binding profile на другой инстанс (UI предлагает find-and-replace); дождись завершения активных run'ов.
Креденшiал в статусе error, OAuth не обновляется refresh_failure_count перевалил порог (distressed) Reauthorize (повторный consent). См. Флоу 5. Должен прийти deeplink-алерт в Telegram.
Шаг send_email падает «action not available» Инстанс под слотом не публикует это действие (action manifest, D45) Замапь слот на инстанс адаптера, который публикует send_email (видно в колонке Actions).

9. Ограничения и инварианты

  • App-api никогда не видит plaintext-секрет (I-1). Durable KEK AXON_CREDENTIAL_ENCRYPTION_KEY монтируется только в app-worker / maintenance CLI; в app-api эта переменная запрещена. Secret-bearing credential-команды идут путём worker_apply_then_signalhandler.apply() (расшифровка/провайдер) выполняется только в worker. App-api получает лишь transient secret-ref ключ (AXON_TRANSIENT_SECRET_REF_KEY) и public OAuth-дескриптор.
  • OAuth client_secret — тоже worker-side (I-20, Strategy A). В Console и в логах его нет.
  • Webhook ingress verification key — отдельный root (AXON_INGRESS_VERIFICATION_KEY), монтируется в app-api + app-worker, используется только для проекции connector_ingress_verifiers (HKDF SHA-256 subkeys per RFC 5869). Не пересекается с durable KEK и не расшифровывает credential payload (инвариант I-DOMAIN-CONNECTORS-STAGE2-INGRESS-KEY).
  • Секреты никогда не в connector_instance.config и не в step.config. Только в зашифрованном payload креденшiала / credential proposal.
  • Project-scope hard isolation. Креденшiал нельзя шарить между проектами. Нужен один Gmail в двух проектах → два credential row. Composite FK (project_id, credential_id) запрещает cross-project leak на уровне БД.
  • Mode XOR (D27). В connector-backed step.config ровно один из connector_requirement / connector_instance_id / connector_key. Несколько указано → publish-time 422 + runtime ValueError.
  • Fail-closed. Коннектор disabled в allowlist / отсутствует verifier / verifier устарел / подпись не сошлась → вебхук отвергается, строка inbound_events не создаётся, generic 401, причина — только в audit.
  • maintain / purge / ingress_verifier:manage — system-only (I-9). Auto-healing и hard-purge — поведения системы по дизайну; людям эти permissions не выдаются (HUMAN ∩ SYSTEM_ONLY = ∅).
  • Все мутации — через CommandEnvelope. RAW UPDATE по app.connector_* запрещён; операторские YAML-правки коннекторов (legacy file-mount) — обходят инвариант и удаляются по плану (V6.4 GA window).
  • Ambiguous ingress binding → reject, не «случайный проект». Если один inbox/bot привязан к нескольким проектам и hints не разрешают — событие пишется с project_id=NULL, resolution_status='ambiguous'; оператор разрешает вручную. Cross-project data leakage запрещена.
  • Connector-as-Workflow (концептуально «всё workflow») в V6.2 не делается — ConnectorInstance остаётся отдельной сущностью; см. CONCEPT-CONNECTORS.md §26.

10. Связанные мануалы и каноны

  • Roles-And-Permissions.md — полная матрица ролей и permissions.
  • Workflows.mdConnectorRequirement[], binding profiles, Run Wizard, override.
  • Security-And-Audit.md — обращение с секретами, audit log, объяснимость.
  • First-Project-Walkthrough.md — сквозной сценарий нового проекта (включает Флоу 1 этого мануала).
  • Каноны: ARCHITECTURE-V6.md §11 (Коннекторы и Tools); CONCEPT-CONNECTORS.md §1-§5/§10/§22 (long-form архитектура); CONCEPT-CONNECTORS-3-STAGES.md (этапы); CONCEPT-CONNECTORS-SECOND.md (Tier 2+, расширение coverage); OWNERSHIP-MAP.md (зоны 1/4/5); core/models/auth.py (RBAC).