Коннекторы и креденш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:purge (и credential:maintain) — в SYSTEM_ONLY_PERMISSIONS, людям не выдаются (I-9). Сама команда purge_credential (и delete_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) |
— |
Пишет CredentialType — modules/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_key — runtime-ключ реестра конкретного инстанса (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/admin → complete_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 credential → Add instance → Allowlist ON → Bindings → operator: Run. Если в проекте по одному инстансу на тип — шаг Bindings автоматизируется.
Флоу 2 — Подключить Gmail за ~90 секунд (manager+)¶
- Console → Credentials →
New credential. - Категория «Email» → «Gmail (OAuth 2.0)».
- Display name:
Sales Inbox. Connect with Google→ Google consent screen → разрешить → callback в app-api → код уходит в worker, обменивается на токены, токены шифруются (envelope) → строкаapp.connector_credentials(cred_<ulid>,status=active).- Авто
test_credential(worker-side probe) →last_test_result=success→ ✅. - Готово. Креденшiал доступен для создания Gmail connector instances в этом проекте.
Если у
CredentialTyperequires_approval=true— после шага 4 создаётсяcredential_proposal; креденшiал появится только послеapproveот reviewer/manager+ (нужно для production CRM, payment-провайдеров).
Флоу 3 — Создать connector instance (Add instance, manager+)¶
- Console → Connectors →
Add instance. Adapter— выбрать конкретный адаптер (gmail).Credential— выбрать креденшiал проекта (Sales Inbox). Валидируется: тот же проект, usable (status='active', не soft-deleted, не purged, encrypted payload/DEK на месте — условие D42);adapter_keyподдерживается этимCredentialType.Display name(Sales Inbox Gmail); формируетсяconnector_key(напримерgmail_sales) — уникальный в проекте.Config— только non-secret параметры:label_ids(Gmail),allowed_chat_ids(Telegram),polling_interval_seconds,webhook_url. Секреты сюда класть нельзя —webhook_secret, API-ключи, HMAC-секреты живут в зашифрованном payload креденшiала; для верификации вебхуков система отдельно проецируетconnector_ingress_verifiers.- Сохранить → команда
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 рассылает письма и с админского ящика, и с ящика саппорта.
- Инженер в
WorkflowDefinitionобъявляет два именованных требования одного типа:ConnectorRequirement(name="admin_mailbox", connector_type="email")иConnectorRequirement(name="support_mailbox", connector_type="email"). - Шаги workflow ссылаются на нужный слот (
connector_requirement/connector_instance_idвstep.config). manager+: создать два креденшiала (Admin Inbox,Support Inbox) и два инстанса (gmail_admin,gmail_support— обаadapter_key=gmail, обаconnector_type=email, разныеconnector_key, каждый со своимcredential_id).manager+: в Bindings workflow'а замаппитьadmin_mailbox → gmail_admin,support_mailbox → gmail_support. (Можно даже разные адаптеры:admin_mailbox → Gmail,support_mailbox → IMAP, лишь бы оба удовлетворяли типуemailи публиковали нужные действия — D45.)- На запуске каждый шаг резолвит свой инстанс → свой креденшiал.
Ограничение: нельзя привязать два разных креденшiала к одному и тому же слоту в одном run'е — слот → ровно один
connector_instance→ ровно один креденшial (Mode XOR, D27). Нужно два ящика → два слота.
Флоу 5 — Reauthorize протухшего OAuth (manager / admin)¶
- Worker при провале refresh шлёт deeplink-алерт в Telegram владельцу/админу.
- Console → Credentials → найти креденшiал в статусе
error/distressed →Reauthorize. - Повторный Google consent → callback → команда
complete_oauth_credential(режим переавторизации;worker_apply_then_signal, permissioncredential: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, permissioncredential: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_health → connector_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_credential (с deletion_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_version ≠ payload_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_signal—handler.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 + runtimeValueError. - 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.md —
ConnectorRequirement[], 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).