ADR 0144: Intercom — team transport, reference service и CIDE sync¶
Статус: Accepted
Дата: 2026-05-24
Связанные ADR¶
| ADR | Роль |
|---|---|
| 0142 | Открытый wire, pluggable transport — политика |
| 0132 | Multi-client (Web, MCC), паритет ролей — видение; transport-реализация → этот ADR |
| 0045 | Локальный append-only log — кэш + offline; канон событий |
| 0134 | Prepare/commit до transport |
| 0128 | AttachmentAnchor, SenderWorkspaceContext |
| 0143 | Participant lens — UI; transport несёт полный log |
| 0080 | Intercom как канал; не «чат-виджет» |
| 0028 | Секреты transport (token) — LocalAppData / secrets, не в git |
| 0101 | Лицензия reference server и зависимостей |
| 0146 | Канон wire в intercom-wire/ — независимо от сервера |
| 0147 | Per-team roles, agent УЗ, CIDE admin — расширение §2.2, §8, §10; teams + projects §2.1–2.3 |
Вне ADR¶
| Документ | Роль |
|---|---|
| intercom-ux-reference-slack-mattermost-v1.md | UX-паттерны; не диктует transport |
| iop-manifest-v1.md | IOP: transport не подменяет git/kb |
| wire/intercom-wire (см. §7) | JSON Schema wire v1 + HTTP profile |
| docs/intercom-wire/README.md | Указатель из docs |
Резюме¶
Принято для командного Intercom с несколькими CIDE (и последующими Web/MCC):
- Intercom Team — стабильный идентификатор общей ленты (
team_id), не абсолютный путь workspace на диске. - Reference Intercom Service — централизованный append-only store + realtime (SSE v1, WebSocket v1.1); каталог
host/intercom-service/в репозитории cascade-ide (CascadeIDE.slnx). - CIDE — реализация
IntercomTransport(FederatedSync): локальный 0045 остаётся; исходящие human-сообщения → transport; входящие → append в log + проекция UI. - Wire v1 — JSON,
schema_version, mapping событий 0045; attach — structured JSON 0128. - Фаза sync v1 — только human (
sender_role: human); agent/system остаются локальными до отдельного RFC. - Auth в пилоте — OAuth 2.0 / OIDC (GitHub первым, другие провайдеры конфигом) → JWT Intercom service; без password store; shared team token — только
DEVbootstrap (§8).
0132 остаётся картой Web/MCC/паритета; выбор «client–server vs mesh» для пилота команды — client–server (вариант A) — зафиксирован здесь.
Контекст¶
Сегодня Intercom в CIDE силён локально (0045, prepare 0134, attach 0128), но второй оператор с CIDE не видит ленту: нет общего store и realtime.
Обсуждение с командой зафиксировало целевой пилот:
| Требование | Решение |
|---|---|
| Второй клиент — ещё один CIDE | FederatedSync transport |
| Realtime желателен | SSE (primary) + optional WS |
| Сначала только люди | Fan-out только sender_role: human |
| Workspace «непонятен» | Разделить team (transport) и workspace root (CIDE) — §1 |
| Вход сразу нормальный | Sign in with GitHub + generic OIDC в пилоте (§8), не «потом» |
0142 принял открытый wire и pluggable transport; этот ADR — нормативная реализация reference path для команды из 2+ CIDE.
Проблема¶
- Две правды: локальный NDJSON у каждого разработчика без merge → «что у тебя в чате?».
- Путаница workspace: путь
D:\…vs/home/…нельзя использовать какteam_id. - Смешение ролей: без политики sync агентские
message_stream_deltaзадуют шум и гонки на сервере. - Вечный Proposed 0132 без критериев «готово для двух CIDE».
Решение¶
1. Термины (нормативно)¶
| Термин | Значение |
|---|---|
| Intercom Team | Логическая команда, общий message store на transport. Идентификатор: team_id (строка, стабильная, сравнимая). |
| Workspace root (CIDE) | Каталог IOP/CIDE для attach, .cascade-ide/, git — как сегодня ResolveAttachWorkspaceRoot(). Не ключ transport. |
| Member | Участник team: member_id + display_name + optional client_kind (cide | web | mcc). |
| Topic | Линия обсуждения в team (аналог channel/thread hybrid 0132). |
| Transport event | Запись на сервере, маппинг из 0045 + envelope metadata. |
| Local-only event | Событие только в CIDE log (agent stream, system, SelfOnly) — не fan-out в v1. |
flowchart TB
subgraph cideA["CIDE (оператор A)"]
LA["Local event log\n.cascade-ide/chat-sessions"]
PA["Prepare pipeline"]
TA["IntercomTransport\nFederatedSync"]
end
subgraph cideB["CIDE (оператор B)"]
LB["Local event log"]
TB["IntercomTransport\nFederatedSync"]
end
S["Intercom Service\nappend-only + SSE"]
PA --> LA
PA -->|human only v1| TA
TB --> LB
TA <-->|REST + SSE| S
TB <-->|REST + SSE| S
2. Intercom Team — идентичность team_id¶
Нормативно: все клиенты одной логической команды используют один team_id. Канон team — на сервере (intercom-service), не в git/TOML репозитория и не в monorepo/submodule layout.
| Слой | Источник истины |
|---|---|
| Team exists, metadata, policy | Сервер — таблица Teams + admin API / CIDE admin (0147) |
| Membership, roles | Сервер — TeamMembers (0147 §2) |
| Активный team в CIDE | Пользователь выбирает из GET /auth/me → teams[]; кэш last selected в user settings (0028) — не определение team |
| OAuth / Connect | team_id в state — из выбора пользователя или invite; сервер валидирует membership / join policy |
team_id не вычисляется из абсолютного пути workspace, не из monorepo vs submodule, не из git remote.
2.1 Server-managed teams (normative v1.1)¶
Принято: создание team, display_name, join_policy, invites, members/roles — только на сервере (и CIDE admin как клиент API). Layout репозитория на диске не создаёт team; связь workspace ↔ team — через project (§2.3).
| Операция | Где |
|---|---|
| Создать team | POST /api/v1/teams (owner bootstrap) или admin CIDE |
| Join policy, GitHub org allowlist | Поля/config team на сервере (0147 §2.1) |
| Пригласить | Invite token через admin API |
| Выбрать team в CIDE | UI picker ← /auth/me; [intercom.transport].team_id = last selection |
CIDE settings (LocalAppData): base_url, oauth_provider, optional team_id (last selected team), workspace_hints (§2.3.1). Не committed TOML с определением team.
2.2 Repo manifest — не SSOT (strangler)¶
Ранний черновик: .cascade-ide/intercom-team.toml в git. Amend: файл не является источником team для transport v1.1+.
| Было (MVP / strangler) | Сейчас (normative) |
|---|---|
team_id в committed TOML |
Сервер; TOML не обязателен |
| Monorepo root vs submodule = разные teams | Не по пути на диске; monorepo/submodule → repos проекта §2.3 |
git: + remote URL inference |
Убрать из normative path |
Код IntercomTeamManifestResolver — strangler до §2.3.1 (resolve + local hint); после v1.1 — только fallback до первого успешного resolve, не SSOT.
wire/intercom-wire/schemas/v1/team-manifest.schema.json — deprecated для SSOT; удаление из wire v1.2.
2.3 Team → Project → Repositories¶
Принято: логическая цепочка не «team = git root», а:
Team ──works on──▶ Project ──links──▶ Repository (git remote URL, normalized)
| Сущность | Смысл | Где живёт |
|---|---|---|
| Team | Люди, roles, Intercom transport (team_id), topics |
Сервер Teams |
| Project | Продукт / IOP-единица, над которой работает team | Сервер Projects |
| Repository | Привязка к git (origin URL, optional path hint); не абсолютный путь диска | Сервер ProjectRepos |
Правила:
- Team не равен repo. Один team может вести несколько projects; один project может иметь несколько repos (monorepo + submodule remotes, polyrepo product).
- Monorepo vs submodule — это сколько repo-ссылок у project, не «какой
.cascade-ideroot = team_id». - CIDE workspace (solution/git root локально) → клиент шлёт normalized remote URL(s) → сервер:
repos → projects → teams(фильтр по membership) → предложить default team для compose; пользователь может переключить. - Transport по-прежнему keyed by
team_id; project — контекст маршрутизации и admin, не второй message store. - Связи team↔project↔repo — server-managed (admin API / CIDE), как §2.1.
flowchart LR
T["Team: financial-open"]
P["Project: Cascade IDE"]
R1["repo: github.com/…/cascade-ide"]
R2["repo: github.com/…/intercom-web"]
T --> P
P --> R1
P --> R2
API sketch — project admin (v1.2):
| Метод | Назначение |
|---|---|
POST /projects, PATCH /projects/{id} |
CRUD project (admin) |
PUT /projects/{id}/repos |
Список normalized git URLs |
PUT /teams/{team_id}/projects |
Привязка team ↔ project |
2.3.1 Workspace resolve + local hint (v1.1, DX)¶
Принято: чтобы не откатить DX «открыл clone → понятен team», без repo TOML как SSOT:
| Механизм | Роль |
|---|---|
GET /api/v1/resolve/workspace-context |
Сервер: repo URL(s) → projects → teams (фильтр membership) |
| Local workspace hint | CIDE LocalAppData: кэш последнего успешного resolve / ручного выбора per normalized repo — не SSOT, offline fallback |
IntercomTeamManifestResolver |
Strangler: если нет hint и нет JWT — одноразовый bootstrap; после resolve не перезаписывает server hint |
Flow (CIDE):
sequenceDiagram
participant C as CIDE
participant L as LocalAppData hint
participant S as intercom-service
C->>C: normalize git remote(s) workspace
C->>L: read hint[repo_key]
alt JWT + online
C->>S: GET /resolve/workspace-context
S-->>C: teams[], suggested_team_id
C->>L: write hint (not SSOT)
else offline / no JWT
C->>C: hint or manifest strangler or manual picker
end
GET /api/v1/resolve/workspace-context (normative sketch v1.1):
| Param | Содержание |
|---|---|
repo_url |
Повторяемый query param; 1..N remotes (root origin; опционально submodule remotes v1.2) |
| Auth | Authorization: Bearer — только teams, где caller ∈ membership |
Response (orientир):
{
"normalized_repo_urls": ["github.com/ai-guiders/cascade-ide"],
"projects": [
{ "project_id": "cascade-ide", "display_name": "Cascade IDE" }
],
"teams": [
{
"team_id": "financial-open",
"display_name": "Financial Open",
"team_role": "member",
"project_id": "cascade-ide"
}
],
"suggested_team_id": "financial-open"
}
suggested_team_id — пересечение repo→project→team и /auth/me; при неоднозначности — null, CIDE показывает picker.
Repo URL normalization (normative):
| Правило | Пример |
|---|---|
| Lowercase host | GitHub.com → github.com |
Без .git suffix |
repo.git → repo |
| SSH → HTTPS host/path | git@github.com:Org/Repo → github.com/org/repo |
| Без credentials / userinfo | — |
| Несколько remotes | v1.1: origin only; v1.2: все remotes workspace + union match |
Local hint (CIDE, 0028):
# [intercom.transport.workspace_hints] — LocalAppData only, NOT in git
[intercom.transport.workspace_hints."github.com/ai-guiders/cascade-ide"]
team_id = "financial-open"
project_id = "cascade-ide" # optional, from last resolve
updated_at_utc = "2026-05-24T12:00:00Z"
source = "resolve" # resolve | manual | manifest_strangler
| Поле | Правило |
|---|---|
| Key | Normalized repo URL (§ выше) |
team_id |
Last used / suggested; invalidate если /auth/me не содержит team |
source = manifest_strangler |
Допустимо до первого resolve; затем перезаписать |
Не SSOT: hint может устареть; server policy (join, roles) всегда wins at Connect/post.
Не v1.1: автосоздание project/team из clone без admin; IOP manifest в git как SSOT project (может подсказать project_id после регистрации на сервере).
2.3.2 Risks и v1 scope (не «всё сразу в production»)¶
Целевая модель §2.1–2.3 полная; реализация и ops — волнами. ADR описывает конечное состояние; ниже — что обязательно в ближайших волнах и где сознательные дыры.
| Волна | Deliver | Не блокирует transport |
|---|---|---|
| v1 / фаза 1 (0147) | Server teams, roles, join policy, invite; human fan-out; DEV open / first_owner |
Project CRUD UI, agent accounts, multi-IdP registry |
| v1.1 | GET /resolve/workspace-context, workspace hints, strangler manifest → hint; manual team picker |
Полный project admin, submodule remotes в resolve |
| v1.2 | Projects / ProjectRepos / TeamProject на сервере; admin link repos |
Account linking (Google+Microsoft → один member) |
| v2+ | Agent transport fan-out, idp_domain, native WebAuthn на service |
— |
Риски и mitigations:
| Риск | Mitigation |
|---|---|
| Resolve хрупкий (нет origin, SSH/https drift, fork) | Нормализация URL §2.3.1; v1.1 origin only; manual picker всегда; offline → local hint |
| Bootstrap chicken-egg (нет server / owner) | CIDE local server host; DEV join policy; LocalOnly без transport |
| DX без repo TOML | §2.3.1 hints + resolve; manifest strangler временно |
| Project дублирует IOP / solution | project_id стыкуется с IOP manifest (0121) — server shell для transport/admin, IOP остаётся продуктовым описанием в git; один канонический id при регистрации project |
| Solo-dev overkill | LocalOnly + один WitDB файл — valid; federated — когда 2+ CIDE |
| Agent УЗ до fan-out | Модель в 0147 §3; код — фаза 3–4 |
| Local hint устарел | Invalidate по /auth/me; server policy wins |
| Passwordless friction в LAN | Development open / invite bootstrap — не «стыдный» режим |
Production vs pilot: Production-like = invite_required, TLS, per-member JWT, без committed team TOML, без shared team token. Pilot LAN может ослабить join policy; не ослабляет passwordless и anti-spoof member_id.
Порядок кода (рекомендация): roles + invite + resolve/hints раньше, чем полный project admin UI; transport MVP не ждёт v1.2 project layer, если есть hints + picker + minimal repo seed (фаза 1b 0147).
2.4 Member identity¶
| Поле | Пилот (норма) |
|---|---|
member_id |
Стабильный ключ с сервера после OIDC (iss + sub → canonical id) |
display_name |
Claims IdP (GitHub login, name) → fallback member_id |
git config / локальный UUID — не источник истины для transport после Connect; только до первого входа или offline-only.
Два человека в одной ленте различаются по member_id, не по пути диска.
3. Reference Intercom Service¶
| Аспект | Решение |
|---|---|
| Репозиторий | cascade-ide — host/intercom-service/ (тот же git, что IDE) |
| Стек | ASP.NET Minimal API + EF Core (IntercomDbContext); v1 default: WitDatabase (intercom.witdb, UseWitDb); v1.1+ ops: shared server RDBMS через тот же EF — провайдер из конфига (§3.1) |
| Store | Append-only transport_events (LSM-friendly append на Wit); проекции topic/message — в сервисе |
| Realtime | GET /teams/{team_id}/stream — SSE (primary v1) |
| Альтернатива | WS — v1.1, тот же payload |
| Идемпотентность | client_event_id (UUID) unique per team — повтор POST не дублирует |
Не цель v1: HA, sharding, E2EE, Matrix federation.
3.1 Database provider (EF Core, конфигурируемо)¶
Принято: persistence — IntercomDbContext + EF Core; выбор движка — deployment, не переписывание домена. Postgres в ранних черновиках — пример shared RDBMS, не единственный вариант.
| Режим | Когда | Провайдер EF |
|---|---|---|
| Пилот v1 (default) | Один процесс, LAN/Tailscale/VPS | WitDatabase — файл intercom.witdb (UseWitDb) |
| Ops v1.1+ | Несколько инстансов intercom-service, managed backup |
Shared server RDBMS — Npgsql (Postgres), SQL Server, MariaDB, … — из конфига |
Смена провайдера (ориентир): connection string + UseNpgsql / UseSqlServer / … в Program.cs (или Database:Provider + factory); EF migrations вместо EnsureCreated(); секреты — env/vault.
Почему Wit в v1: append-heavy transport_events, один writer, zero ops — см. WitDatabase. Почему shared RDBMS позже: несколько writer'ов не делят один witdb-файл по сети; нужен client–server store (не «потому что EF требует Postgres»).
Anti-pattern: NFS/SMB-шара одного intercom.witdb между репликами — вместо этого shared RDBMS или один инстанс.
4. HTTP API v1 (нормативный sketch)¶
Базовый путь: /api/v1. Защищённые маршруты: Authorization: Bearer <intercom_jwt> (после OIDC).
| Метод | Путь | Назначение |
|---|---|---|
GET |
/auth/login |
Redirect / metadata для OAuth (provider, team_id, PKCE) |
GET |
/auth/callback/{provider} |
OIDC callback → выдача JWT + refresh |
POST |
/auth/token |
Refresh access token |
POST |
/auth/logout |
Revoke refresh (опционально в MVP) |
GET |
/auth/me |
Текущий member + teams |
GET |
/teams/{team_id} |
Meta team (опционально health) |
GET |
/teams/{team_id}/topics |
Список topics |
POST |
/teams/{team_id}/topics |
Создать topic (title, optional spine_key) |
GET |
/topics/{topic_id}/events |
История (after_seq, limit) |
POST |
/topics/{topic_id}/events |
Append (idempotent client_event_id) |
GET |
/teams/{team_id}/stream |
SSE: новые events по team (filter topic_id query optional) |
Topic v0 пилота: сервис при первом подключении team создаёт topic general если нет topics.
5. Wire envelope и mapping 0045¶
Каждая запись transport:
{
"schema_version": 1,
"seq": 1204,
"team_id": "financial-open",
"topic_id": "…",
"client_event_id": "…",
"occurred_at_utc": "2026-05-24T12:00:00Z",
"event_kind": "message_added",
"sender": {
"member_id": "…",
"display_name": "Dmitry",
"sender_role": "human",
"client_kind": "cide"
},
"payload": { }
}
payload — тот же JSON, что в локальном NDJSON 0045 для поддерживаемых kind.
Local event_kind |
Sync v1 | Примечание |
|---|---|---|
message_added |
да, если human | role: user → sender_role: human |
message_completed |
да, если human | дедуп с message_added по message_id на ingest |
message_edited |
v1.1 | |
message_stream_delta |
нет | agent local |
thread_forked |
v1.1 | |
message_range_related |
v1.1 | |
clarification_* |
нет | local / agent |
Local role (ChatHistoryMessagePayload) |
Wire sender_role |
|---|---|
user |
human |
assistant |
agent |
| (system payloads) | system |
Attachment в payload.attachments[] — AttachmentAnchor. Prose bracket на wire не уходит (0128).
sender_workspace_context — опционально на каждом human message (SenderWorkspaceContext); у каждого отправителя свой snapshot (ветка/commit/solution).
6. CIDE: IntercomTransport и FederatedSync¶
| Реализация | Когда |
|---|---|
LocalOnlyTransport |
Нет team_id / нет base_url |
FederatedSyncTransport |
Настроены team_id, base_url, JWT (после Connect Intercom / OIDC) |
Исходящий путь (после 0134 commit):
- Append в локальный log (как сегодня).
- Если
sender_role→ human и audienceChannel(IntercomMessageAudience) —POSTtransport (async, retry queue offline). - Ошибка transport не откатывает локальный log; UI — статус доставки (degraded).
Входящий путь:
- SSE receiver → parse envelope → idempotent append в local log (
client_event_id/seq). ChatHistoryMessageProjector/ Skia refresh — как для локальных событий.- Чужие attach: re-resolve на локальном workspace root получателя (0128).
Конфиг (settings + secrets):
[intercom.transport]
enabled = true
base_url = "https://intercom.example:5080"
# oauth: Connect Intercom в UI → refresh/access в secrets ([0028](0028-user-settings-toml-localappdata-and-secrets.md))
sse_reconnect_backoff_ms = 1000
default_topic_id = "" # empty → follow "general" from server
7. Артефакты wire (wire/intercom-wire/)¶
Канон в репозитории cascade-ide (0146).
| Путь | Содержание |
|---|---|
wire/intercom-wire/schemas/v1/transport-envelope.schema.json |
Envelope + payload refs |
wire/intercom-wire/schemas/v1/event-kinds.json |
Реестр event_kind |
wire/intercom-wire/schemas/v1/extension-registry.json |
relates_to, code_doc_link, … |
wire/intercom-wire/schemas/v1/team-manifest.schema.json |
~~intercom-team.toml~~ deprecated SSOT; team на сервере §2.1 |
wire/intercom-wire/profiles/reference-http-v1/openapi.yaml |
HTTP+SSE profile (не wire) |
Генерация C# DTO (опционально): tools/IntercomWireCodegen или NSwag в CI — не блокирует v1; PR в wire и PR в service раздельно.
8. Auth и deployment¶
Принцип: не свой login/password store; OAuth 2.0 / OpenID Connect (внешние IdP) + короткоживущие JWT Intercom service — с первого пилота (intercom-service MVP + CIDE Connect), не откладывать.
| Режим | Модель | Когда |
|---|---|---|
| Пилот (норма) | OIDC → JWT per member | 2+ CIDE, LAN, VPS, будущий Web |
| DEV bootstrap | Shared team Bearer |
Только ASPNETCORE_ENVIRONMENT=Development + явный env INTERCOM_DEV_TEAM_TOKEN; запрещён в Production |
8.1 OAuth / OIDC (пилот — норма)¶
| Решение | Содержание |
|---|---|
| Протокол | OAuth 2.0 Authorization Code + PKCE (Web, desktop CIDE через system browser) |
| Первый провайдер | GitHub (Sign in with GitHub) — естественно для dev-команды, sub + login/display name в claims |
| Расширение | Любой OIDC-совместимый IdP — конфиг deployment, не форк wire. Ориентир registry: Google, Microsoft Entra, Yandex ID, LinkedIn, GitLab, Keycloak, Authentik, … (0147 §2.2) |
| Member identity | member_id = стабильный ключ из claims (iss + sub или provider-specific id); display_name / avatar из claims |
| API после входа | Authorization: Bearer <intercom_jwt>; SSE с тем же JWT (query access_token или заголовок — зафиксировать в OpenAPI) |
| Logout | «Disconnect Intercom» в CIDE: удалить refresh/access; POST /auth/logout в MVP |
| Team membership (пилот) | После OIDC: join по server join_policy + invite (0147 §2.1); не auto-join из repo TOML |
Не делаем:
- Собственные пароли в SQLite как основной способ входа.
- Production с единым shared secret на всю команду.
CIDE (desktop) — в пилоте:
- Connect Intercom (настройки / первый send в Channel) → system browser → loopback
http://127.0.0.1:<port>/callback+ PKCE. - Refresh token в secrets (0028); access — авто-обновление перед POST/SSE.
member_id/display_nameна wire = mapping сервера из OIDC (§2.2).- Disconnect = logout + transport
LocalOnlyдо повторного Connect.
Intercom Web:
- Тот же OIDC; BFF или SPA + PKCE — выбор при реализации
intercom-web(0132 фаза 3).
Реализация reference server (ориентир):
- ASP.NET Core
AddAuthentication().AddOpenIdConnect(...)/ multiple schemes; - выдача своих JWT после успешного external login (не подмена GitHub token на каждый API-вызов);
- секреты OAuth client — env / vault, не в git.
sequenceDiagram
participant U as Operator
participant C as CIDE / Web
participant I as Intercom service
participant G as GitHub OIDC
U->>C: Connect team
C->>I: GET /auth/login?provider=github
I->>G: Authorization Code + PKCE
G->>U: Consent
G->>I: callback + code
I->>C: intercom JWT (+ refresh)
C->>I: POST event Bearer JWT
Deployment пилота:
| Режим | Когда |
|---|---|
| LAN | http://192.168.x.x:5080 — два CIDE в одной сети |
| VPS | Удалённая команда; TLS reverse proxy |
| localhost tunnel | Временный ngrok/cloudflared — только dev |
9. Offline, merge, порядок¶
| Правило | Содержание |
|---|---|
| Источник порядка на сервере | Монотонный seq per team |
| Источник истины для UI | Локальный log после merge |
| Конфликт | Серверный append wins для transport events; локальные-only never overwritten |
| Offline queue | Исходящие POST в SQLite/file queue; flush при reconnect |
| Дубликаты | client_event_id + message_id dedup на ingest |
10. Связь с 0143 и агентом¶
| Слой | Поведение |
|---|---|
| Transport v1 | Только human |
| Participant lens Humans | Совпадает с тем, что ушло в team |
| Lens Agents / System | Локальные события; у другого CIDE не появятся в v1 |
| Agent v2 | Отдельное решение: server-side BFF или fan-out sender_role: agent с policy |
11. Фазы и критерии готовности¶
| Фаза | Содержание | Критерий «готово» |
|---|---|---|
| 0 | Этот ADR Accepted | README + cross-links |
| 1 | wire/intercom-wire/schemas/v1/* + profile OpenAPI + server team API sketch |
Schema ↔ AttachmentAnchor; extensions в registry |
| 2 | intercom-service MVP: events + SSE + OIDC (GitHub + 1 generic OIDC) + JWT |
Два аккаунта GitHub → оба POST в general; SSE у обоих |
| 3 | CIDE: FederatedSyncTransport + Connect / Disconnect |
Два CIDE без ручного копирования secret |
| 3.1 | message_edited, multi-topic, team invite |
Changelog wire |
| 4 | Agent fan-out (optional) | RFC + ADR amend |
| 5 | Intercom Web (0132 фаза 3) — отдельный org-репо 0145 | Тот же OIDC + CORS |
Не блокирует: chips/prose 0128, lens UI 0143.
12. Отношение к 0142 и мостам¶
| 0142 | Этот ADR |
|---|---|
| Pluggable transport | IntercomService = reference реализация |
| LocalOnly | Остаётся без team |
| MatrixBridge / MM | Поверх того же envelope; после фазы 3 |
| Protobuf | Не v1; по 0041 при метриках |
Последствия¶
Положительные¶
- Два+ CIDE делят одну ленту с realtime без клонирования Slack.
- Явное разделение team vs workspace root снимает путаницу путей.
- Локальный log 0045 сохраняется — offline и агент не ломаются.
Отрицательные / риски¶
- Операционка сервиса (uptime, backup SQLite).
- OAuth client secrets / redirect URI — дисциплина env и TLS; DEV bootstrap token не в Production.
- Attach на разных machine paths — re-resolve может fail → excerpt + deep link остаются fallback (0132).
Не цели¶
- Intercom Web / MCC UI (фазы 5+ 0132; OIDC уже в пилоте).
- Собственная password-БД как основной login (только OIDC + DEV bootstrap).
- Shared team token в Production.
- Mesh/CRDT без сервера (0132 вариант C).
- E2EE, org-wide ACL, read receipts.
- Protobuf на wire v1.
- Fan-out агента в team v1.
Anti-patterns¶
| Anti-pattern | Почему |
|---|---|
team_id = абсолютный путь диска |
Ломает второй CIDE |
| Team definition / join policy в repo TOML | SSOT — сервер §2.1; monorepo/submodule не задают team |
| Local workspace hint как SSOT | Hint — кэш §2.3.1; server + membership wins |
| Два message store (MCC + service) | 0142 |
Sync всех message_stream_delta в v1 |
Шум, гонки, трафик |
| Transport без idempotency | Дубликаты при retry |
| Секрет OAuth/JWT в committed manifest | 0028 |
| Shared team token в Production | Только per-member JWT после OIDC |
Открытые вопросы¶
- ~~Postgres vs WitDatabase file~~ → §3.1: v1 — Wit file + один инстанс; multi-instance — shared RDBMS через EF (провайдер из конфига, Postgres — пример).
- ~~Единый team на monorepo vs submodule~~ → §2.1: teams server-managed; repo layout irrelevant; выбор team из
/auth/me. - Порог fan-out agent — метрики + amend §10 после фазы 3.
- ~~Team invite после OIDC~~ → 0147 §2.1 (
join_policy, invite token,github_org). - SSE + JWT — заголовок vs
?access_token=(some proxies); в OpenAPI auth v1.
История¶
| Дата | Изменение |
|---|---|
| 2026-05-24 | Accepted: team transport, reference service, CIDE FederatedSync, SSE v1, human-only sync phase, team_id ≠ workspace path. |
| 2026-05-24 | §8.1: OAuth/OIDC (GitHub + generic), JWT. |
| 2026-05-24 | Auth в пилоте (фазы 2–3): OIDC обязателен; shared token — только DEV bootstrap. |
| 2026-05-24 | Store: WitDatabase (intercom.witdb), не generic SQLite; WitDatabase. |
| 2026-05-24 | §3.1: EF provider из конфига; shared RDBMS (Postgres и др.) — v1.1 ops, не привязка к Npgsql. |
| 2026-05-24 | §2.1–2.2: teams server-managed; repo intercom-team.toml не SSOT; monorepo/submodule не задают team. |
| 2026-05-24 | §2.3: Team → Project → Repositories; workspace resolve через git remote, не disk path. |
| 2026-05-24 | §2.3.1: GET /resolve/workspace-context + local workspace hints (v1.1 DX). |
| 2026-05-24 | §2.3.2: Risks и v1 scope — волны реализации, не big-bang production. |