RFC: Consistent Deal & Ticket Creation Experience — Phase 1: Bot & AI Creation Parity
Document Conventions (do not remove)
This RFC follows the Qontak RFC Template format for governance — the metadata table, Confluence sections 1–6, and Comment logs are mandatory. It is also agent-execution-ready: §1 PRD-to-Schema Derivation, §2 Repo Reading Guide (Detail 2.0), mermaid diagrams, and §4 Agent Execution Plan + Verification & Rollback Recipe must be complete before §7 flips to yes.
The YAML frontmatter is the machine-readable index. The metadata table below is the human-readable governance record. Status vocab mapping (documents-repo linter): table
IDEA⇄ frontmatterdraft;RFC⇄in-review;AGREED⇄approved.
Metadata
| Field | Value | Notes |
|---|---|---|
| Status | IDEA | frontmatter draft; not yet review-ready (see §7) |
| Type | backend | sub_type: enhancement (extends an existing webhook contract) |
| Title | Consistent Deal & Ticket Creation Experience — Phase 1: Bot & AI Creation Parity | — |
| Owner (DRI) | Alma Syafira (PM) | CRM Squad owns BE; crm-fe-v3 owns CRM UI embed |
| Authors | rfc-starter (agent draft) | from PRD v1.1 |
| Reviewers | CRM Eng lead, Omnichannel Eng lead, Chatbot Squad, Agentic AI Squad | cross-squad |
| Approvers | CRM Tech Lead, InfoSec approver [REQUIRED] | infosec sign-off mandatory |
| Submitted date | 2026-06-30 | ISO-8601 |
| Last updated | 2026-06-30 | refinement r2 — added Chatbot BE (chatbot) producer repo |
| Target release | 2026-Q3 | per PRD |
| Related documents | PRD: Phase 1 Bot & AI Creation Parity (see frontmatter related) · ANCHOR: Create Deal in CRM via Chatbot UI · ANCHOR: AI Agent Commercialization | — |
| Discussion | [REQUIRED: Slack channel/thread] | — |
Sections at a Glance
- Overview — Problem, PRD-to-Schema Derivation, Decisions Closed index (1.B), Per-Story Change Map (1.C), coverage matrices (1.A)
- Technical Design — Infra Topology, Repo Reading Guide (2.0), ADRs, Data Model (2.3), APIs (2.4), Sequence diagrams, State diagrams, Branch catalog
- High-Availability & Security — auth (company token), authorization matrix, multi-tenancy, failure/idempotency
- Backwards Compatibility & Rollout — flag contract, Agent Execution Plan (4.C), Verification & Rollback Recipe
- Concerns / Open Questions — severity-tagged blockers
- Comment logs
- Ready for agent execution — gate marker
1. Overview
1.0 Problem & Scope
Deals and tickets created by Chatbot or Agentic AI today do not reach parity with manually-created records: the Omnichannel infobar keeps showing the "Create Deal" CTA, the CRM record has no conversation contact / origin context, and there is no timeline entry explaining how the record originated. This RFC makes the qontak.com CRM backend emit the create/update/delete signals and build the origin context so the existing Omnichannel + CRM UIs render parity.
In scope (BE, qontak.com — MAIN): persist creator_flag; emit create + update + delete events to Omnichannel for bot/AI records that carry a channel_integration_room_id; auto-associate the conversation contact (reuse); auto-create a chat-history timeline note; generate a room deeplink for that note.
Cross-service (read-only context, NOT written here): chatbot (Chatbot BE) is the upstream producer — its CRM node executor already calls POST /api/v3.1/deals|tickets and must add channel_integration_room_id, contact_id/lead-ids, and creator_flag to that request body (ADR-7); hub-core (Omnichannel-be) must accept the new update (deal) and create/update (ticket) receiver branches; hub-chat (Omnichannel-fe) renders the preview; crm-fe-v3 renders the timeline entry + "Bot"/"AI" creator label (UI embed is pre-baked to crm-fe-v3 — ADR-1).
Non-goals: multiple deals/tickets per room, manual/auto-create flow changes, historical backfill, source granularity beyond "Bot"/"AI", chatbot qualification logic, mobile (PRD §4).
1.A Coverage Matrices
PRD Section Coverage
| PRD § | Title | Covered in RFC |
|---|---|---|
| 1 | One-liner + Problem | §1.0 |
| 2 | If we don't ship | §1.0 (motivation) |
| 3 | Personas | §3 authz matrix (Sales Agent / Manager) |
| 4 | Non-Goals | §1.0 Non-goals · §2 Branch Catalog (guard rails) |
| 5 | Constraints | §3 (auth, perf, retention) · §4 (flag) |
| 5.1 | Data Lifecycle | §2.3 Per-Status Lifecycle (note 1y retention, room overwrite) |
| 6 | Feature Changes (CHG-001/002) | §2.4 APIs (preview events) · ADR-4 (creator label) |
| 7 | New Feature (Timeline log) | §2.3 (Crm::Note reuse) · §2.4 row 8 · ADR-5 |
| 8 | API & Webhook Behavior (#1–9) | §2.4 Outbound/Inbound tables |
| 9 | System Flow + Stories + ACs | §2 sequence diagrams · 1.C Per-Story Change Map |
| 10 | Rollout | §4 Rollout |
| 11 | Observability | §4 Observability |
| 12 | Success Metrics | §4 Observability (metric sources) |
| 13 | Launch Plan & Stage Gates | §4 Rollout |
| 14 | Dependencies | §3 Responsibility Boundary Matrix |
| 15 | Key Decisions | §2 ADRs (mapped) |
| 16 | Open Questions | §5 (severity-tagged) |
UI / Consumer Surface Coverage
| Surface | Repo | Read endpoint / data source | Notes |
|---|---|---|---|
| Conversation infobar deal preview | hub-chat DealDetails.vue | hub-core QontakCrmObject (external_id,external_url) | display fields source = OQ-1 |
| Conversation infobar ticket preview | hub-chat TicketDetails.vue | hub-core RoomTicket (name,external_status,external_url) | RoomTicket already stores display fields |
| "Create Deal" CTA (empty state) | hub-chat TicketingCrmDeal.vue:220 | unchanged | Non-Goal #2 guard rail |
| CRM deal timeline entry | crm-fe-v3 Deals/Detail/DealDetailTimeline.vue | qontak.com Crm::Note (HubChannel types) | new note auto-created on bot/AI create |
| CRM ticket timeline entry | crm-fe-v3 Tickets/Detail/ActivityTimeline.vue | qontak.com Crm::Note | same |
| Creator field "Bot"/"AI" | crm-fe-v3 types/deals/api-types.ts:61 (creator_name) | qontak.com creator_flag (new) | label mapping FE-side |
Role Coverage
| Role | Capability | Enforcement |
|---|---|---|
| Sales/CS Agent | view preview (Omnichannel inbox access, even w/o CRM perm); click → CRM 403 if no CRM perm | hub-core room access + CRM authz on detail |
| Sales Manager | view creator label + timeline; audit | CRM deal/ticket view authz |
| System (Chatbot / Agentic AI) | create record w/ room_id,contact_id,creator_flag | company-token authenticated create API |
1.B Decisions Closed (index → §2 ADRs)
| # | Decision | Status | ADR |
|---|---|---|---|
| 1 | CRM UI embed (timeline/creator label) handled by crm-fe-v3 | chosen — pre-baked by author — see input | ADR-1 |
| 2 | Reuse existing CRM→hub-core webhook pipeline rather than build a net-new "dedicated Omnichannel API" | chosen | ADR-2 |
| 3 | Emit update events (not just create/delete) for preview freshness | chosen (PRD 15a) | ADR-3 |
| 4 | Persist bot/AI actor via new creator_flag column (not creator_id/data_source) | chosen (pending author confirm — OQ-3) | ADR-4 |
| 5 | Chat-history timeline = lightweight reference + room deeplink via existing Crm::Note HubChannel types + ChatHistory::GetChatHistory, async | chosen (PRD 15a) | ADR-5 |
| 6 | Latest-wins, ordered by CRM creation timestamp | chosen (PRD 15a; E1) | ADR-6 |
| 7 | Chatbot BE extends its existing CRM node executor body (not a new node) to carry room_id/contact_id/creator_flag | chosen (cross-service — Chatbot Squad owns) | ADR-7 |
1.C Per-Story Change Map
| Story | Layer scope | Changes | Acceptance criteria (verbatim AC IDs) | RFC anchors |
|---|---|---|---|---|
| CDTC-S01 Preview in chat room | Cross-squad | chatbot: add room_id/creator_flag to create body · qontak.com: emit create/update/delete webhook for bot/AI deal+ticket w/ room_id · hub-core: add deal update + ticket create/update receiver branches · hub-chat: render | AC-1, AC-2, AC-3, AC-4, ERR-1, ERR-2 | §2.4 Inbound(chatbot)/Outbound/Inbound · ADR-2,3,6,7 · §4.C C2–C4 |
| CDTC-S02 Navigate room→detail (new tab) | FE-only (hub-chat) | uses external_url from payload | AC-1, AC-2, ERR-1 | §2.4 (external_url) · n/a BE — covered in hub-chat |
| CDTC-S03 Auto-associate contact | Cross-squad (chatbot + qontak.com) | chatbot: sends contact via crm_lead_ids phone lookup (OQ-10 vs contact_id) · qontak.com: reuse associate_hub_contact | AC-1, AC-2, ERR-1, ERR-2 | chatbot execute.rb:261 · create_service.rb:378 · §4.C C1 verify · OQ-10 |
| CDTC-S04 Timeline log + chat preview | BE + FE | qontak.com: auto-create Crm::Note HubChannel + room deeplink async · crm-fe-v3: render | AC-1, AC-2, ERR-1, ERR-2 | ADR-5 · §2.3 · §4.C C5 |
| CDTC-S05 Navigate timeline→room | BE + FE | qontak.com: generate room_deeplink (OQ-4) · crm-fe-v3: render link | AC-1, AC-2, ERR-1 | §2.4 · §5 OQ-4 |
| CDTC-S06 Creator label "Bot"/"AI" | Cross-squad (chatbot + qontak.com + FE) | chatbot/AI: send creator_flag in create body · qontak.com: store+expose creator_flag · crm-fe-v3: map to "Bot"/"AI" | AC-1, AC-2, AC-3 | ADR-4, ADR-7 · chatbot execute.rb · §2.3 |
| CDTC-NEG-01 Single per room (latest wins) | Runtime / behavior | enforce at hub-core unique index + CRM ts ordering | NEG-1 | ADR-6 · §2.3 |
| CDTC-NEG-02 Manual/auto flow unchanged | Config / behavior | no change to manual create path | NEG-2 | §2 Branch Catalog |
| CDTC-NEG-03 Source value preserved | BE-only | do not override existing crm_source | NEG-3 | §2 Branch Catalog |
2. Technical Design
2.1 Infrastructure Topology
flowchart TB
subgraph clients[Actors]
AGENT[Sales/CS Agent browser]
end
subgraph bot[chatbot — Chatbot BE]
BFLOW[Flow / node executor<br/>mekari_qontak_crm/execute.rb]
BHTTP[CrmHttpClient<br/>company-token auth + 401 refresh]
end
AI[Agentic AI / Airene<br/>producer — repo TBD]
LB[(ALB / nginx ingress)]
subgraph crm[qontak.com — CRM BE - MAIN]
WEB[Rails web pods]
SK[Sidekiq workers<br/>queues: integrations, room_interaction]
REDIS[(Redis<br/>webhook config cache)]
DBP[(Postgres primary)]
DBR[(Postgres replica)]
end
subgraph hub[hub-core — Omnichannel BE]
HWEB[Rails web pods]
HSK[Sidekiq: write_log_crm_webhook]
HDB[(Postgres: qontak_crm_objects / room_tickets / rooms)]
ES[(Elasticsearch<br/>message history)]
end
subgraph fe[Frontends]
HUBFE[hub-chat - Nuxt 4]
CRMFE[crm-fe-v3 - Nuxt 4]
end
BFLOW --> BHTTP
BHTTP -->|POST /api/v3.1/deals,/tickets| LB
AI -.->|POST create deal/ticket| LB
LB --> WEB
WEB --> DBP
WEB --> SK
SK -->|webhook config| REDIS
SK -->|HTTPS webhook eventType=qontak.crm.deal.*| HWEB
SK -->|GET chat history /api/open/v1/messages/rooms/:id| HWEB
HWEB --> HSK --> HDB
HWEB --> ES
AGENT --> HUBFE -->|read preview| HWEB
AGENT --> CRMFE -->|read timeline/creator| WEB
WEB -->|reads| DBR
Per-service responsibility & third-party connections
| Service | Use cases (this RFC) | Internal calls (owner) | External / third-party |
|---|---|---|---|
| chatbot (Chatbot BE) | build create body w/ room_id/contact_id/creator_flag; call CRM deal/ticket create API; phone→lead lookup for contact | qontak.com CRM REST /api/v3.1/* (CRM); SSO company-token refresh (CRM) | qontak.com CRM API, Postgres, Sidekiq, Rollbar |
| qontak.com (CRM BE) | persist creator_flag, associate contact, emit webhook events, build timeline note + deeplink | hub-core webhook receive (Omnichannel), hub-core message API (Omnichannel) | Postgres, Redis, Sidekiq |
| hub-core (Omnichannel BE) | receive create/update/delete → upsert QontakCrmObject/RoomTicket; serve message history | — | Postgres, Elasticsearch, Sidekiq |
| hub-chat (Omnichannel FE) | render infobar preview, new-tab nav | hub-core read APIs | — |
| crm-fe-v3 (CRM FE) | render timeline entry, "Bot"/"AI" creator label, room deeplink | qontak.com /api/mobile/v2.8 | — |
2.0 Repo Reading Guide (repo as documentation)
Repo Map (slice this RFC touches)
flowchart LR
subgraph CB[BE: chatbot READ-ONLY]
CEX[node_executions/.../mekari_qontak_crm/execute.rb<br/>add room_id/contact_id/creator_flag to body]
CHC[crm_http_client.rb<br/>company-token]
end
subgraph CRM[BE: qontak.com]
DS[crm/deals/create_service.rb<br/>modified]
TC[ticket/create.rb<br/>modified]
SSW[crm/webhook_sender/should_send_webhook_service.rb<br/>read]
PB[webhook/entity/deal,ticket base+update<br/>modified]
NOTE[crm/note.rb<br/>read]
GCH[chat_history/get_chat_history.rb<br/>read/extend]
NW[NEW: timeline-note builder worker<br/>new]
MIG[db/migrate add creator_flag<br/>new]
end
subgraph HUB[BE: hub-core READ-ONLY]
DR[crm/.../webhook/deal_receiver.rb<br/>add update branch]
TR[crm/.../webhook/ticket_receiver.rb<br/>add create/update]
QCO[models/qontak_crm_object.rb]
NR[interactors/.../notification_receiver.rb]
end
subgraph FE[FE READ-ONLY]
HD[hub-chat DealDetails.vue/TicketDetails.vue]
CT[crm-fe-v3 DealDetailTimeline.vue]
end
CEX --> CHC
CHC -.HTTPS POST /api/v3.1/deals.-> DS
DS --> MIG
DS --> NW --> NOTE
NW --> GCH
DS --> PB --> SSW
SSW -.HTTPS.-> NR --> DR --> QCO
NR --> TR
QCO --> HD
NOTE --> CT
Existing Code Anchors (read before writing)
| Concern | Repo:path:line | What to learn |
|---|---|---|
| Chatbot CRM node executor (producer) | chatbot app/core/repositories/node_executions/nodes/mekari_qontak_crm/execute.rb:18,74,261 | ACTION_TYPE_MAPPING → POST /api/v3.1/deals / /api/v3.1/tickets (:18-25); body built from configured/AI args (process_body); enrich_body_for_deal_create adds crm_lead_ids via phone lookup (:261-275) — no channel_integration_room_id/creator_flag set today |
| Chatbot → CRM HTTP client (producer auth) | chatbot app/core/repositories/node_resources/mekari_qontak_crm/crm_http_client.rb:107,143,150 | company-token credential __qontak_crm_company_token (:107-113); 401 → refresh via POST /api/v3.1/teams/company_token + X-Crm-Api-Key (:143-151) |
| Deal create service | qontak.com app/services/crm/deals/create_service.rb:6,28,97,378 | #call shape; channel_integration_room_id already set (:97); associate_hub_contact already creates crm_people_deals (:378) |
| Ticket create service | qontak.com app/services/ticket/create.rb:1,16,107 | hash result; associate_hub_contact (:107) |
| Deal model | qontak.com app/models/crm/deal.rb:150,157,2257 | creator is a User FK (:150); resolve_room_chat after_commit hub hook pattern (:2257) |
| Schema (deals) | qontak.com db/schema.rb:1363,1367,1392,1394 | creator_id, crm_source_id, channel_integration_room_id, data_source default "web" |
| Schema (tickets) | qontak.com db/schema.rb:4941,4956,4961 | creator_id, channel_integration_room_id, data_source |
| Webhook payload entity | qontak.com app/services/webhook/entity/deal/base.rb:18,20,21 | exposes slug, channel_integration_room_id, creator_id/creator_name, stage/pipeline already |
| Webhook config gate | qontak.com app/services/crm/webhook_sender/should_send_webhook_service.rb:33-44 | per-team { 'Crm::Deal' => {create,update,delete} } cache + DB gate |
| Omnichannel ticket sender | qontak.com app/services/crm/webhook_sender/omnichannel/webhook_sender_service.rb:12,34,62-63 | ENV['OMNICHANNEL_TICKET_WEBHOOK']; hardcodes event_type:'ticket', action:'delete' |
| Event type format | qontak.com app/services/webhook/payload_builder.rb:52 | "qontak.crm.#{type}.#{action}" |
| Chat history retrieval | qontak.com app/services/chat_history/get_chat_history.rb:11,64 | NOTE_TYPES HubChannel; Hub::ChatService::Messages::Message#load_message(token,room,1,100) |
| Timeline model | qontak.com app/models/crm/note.rb | Crm::Note STI; HubChannel subtypes back timeline |
| Hub deal receiver | hub-core app/apps/crm/repositories/webhook/deal_receiver.rb:20,27,50 | only create/delete; requires room; one-per-room guard |
| Hub ticket receiver | hub-core app/apps/crm/repositories/webhook/ticket_receiver.rb:14 | only delete |
| Hub dispatch | hub-core app/apps/crm/interactors/webhook/notification_receiver.rb:19,32,42 | routes by eventType (deal) vs event_type:'ticket' |
| Hub CRM object model | hub-core app/core/domains/models/qontak_crm_object.rb | stores external_id,external_url,object_type,room_id — no name/stage/pipeline |
| Hub room ticket model | hub-core app/core/domains/models/room_ticket.rb | stores name,description,external_status,external_url |
| Migration dialect | qontak.com db/migrate/20260619000001_add_live_location_config_to_teams.rb | Rails 5.2 Postgres add_column style |
| Worker pattern | qontak.com app/workers/crm/integrations/deal/resolve_room_worker.rb | thin Sidekiq wrapper → service; queue: :integrations |
Patterns to Follow
| Concern | Reference file (opened) | Rule |
|---|---|---|
| Chatbot CRM call (producer) | chatbot app/core/repositories/node_executions/nodes/mekari_qontak_crm/execute.rb | extend process_body/enrich_body_for_deal_create to add fields; keep destination-based arg extraction |
| Service object | app/services/crm/deals/create_service.rb | keyword-arg init, #call, hash result |
| Background worker | app/workers/crm/integrations/deal/resolve_room_worker.rb | look up by id, delegate to service, idempotent |
| Webhook emission gate | app/services/crm/webhook_sender/should_send_webhook_service.rb | per-team config + Redis cache |
| Webhook payload | app/services/webhook/entity/deal/base.rb | Grape::Entity expose; add fields here |
| Timeline note | app/services/chat_history/get_chat_history.rb + Crm::Note HubChannel types | reuse existing note STI types |
| Migration | db/migrate/20260619000001_add_live_location_config_to_teams.rb | ActiveRecord::Migration[5.2], Postgres |
Source Verification
| Item | Evidence (verified) |
|---|---|
| chatbot calls CRM deal/ticket create API | chatbot execute.rb:19,21 ACTION_TYPE_MAPPING → POST /api/v3.1/deals, POST /api/v3.1/tickets |
| chatbot omits room_id/creator_flag today | chatbot execute.rb:74-79,261-275 — deal body built from configured/AI args + crm_lead_ids only; no channel_integration_room_id/creator_flag written → confirms Chatbot Squad payload work (Risk #7) is real |
| chatbot contact assoc via lead ids | chatbot execute.rb:269-273 GET /api/v3.1/contacts?phone= → body['crm_lead_ids'] (array) — field differs from PRD contact_id → OQ-10 |
| company token (producer side) | chatbot crm_http_client.rb:107-113 credential __qontak_crm_company_token; :143-151 refresh POST /api/v3.1/teams/company_token + X-Crm-Api-Key — PRD assumption #5 verified from producer side |
creator is User FK | crm/deal.rb:150 belongs_to :creator, class_name: 'User' — confirms no string actor today (drives ADR-4) |
| room id column exists | db/schema.rb:1392 t.string "channel_integration_room_id" |
| room id set at create | create_service.rb:97 deal.channel_integration_room_id = params[:channel_integration_room_id] |
| contact auto-assoc exists | create_service.rb:378 deal.crm_people_deals.create!(crm_person_id: lead_id) |
| webhook entity exposes room+stage+pipeline | webhook/entity/deal/base.rb:20,28-29,56-61 |
| deal receiver rejects update | hub-core deal_receiver.rb:20 unless %w[create delete].include?(@action) |
| ticket receiver delete-only | hub-core ticket_receiver.rb:14 if @webhook_attr.action == 'delete' |
| QontakCrmObject lacks display fields | hub-core qontak_crm_object.rb exposes external_id/external_url/object_type/room_id only |
| company-token mapping exists | omnichannel sender uses @team.external_company_id (webhook_sender_service.rb:64,69); hub-core finds org by company_id (deal_receiver.rb:35) — PRD assumption #5 verified |
| message history API | chat_history/get_chat_history.rb:64 → Hub::ChatService::Messages::Message → /api/open/v1/messages/rooms/:id — PRD assumption #6 verified |
| deeplink helper | NOT FOUND — no web room deeplink builder located → OQ-4 |
Reading Order for the Agent
Cross-service producer (read first for context, do not edit here): chatbot
app/core/repositories/node_executions/nodes/mekari_qontak_crm/execute.rb+crm_http_client.rb— how the bot/AI create request is assembled and authenticated.
app/services/crm/deals/create_service.rbapp/services/ticket/create.rbapp/services/crm/webhook_sender/should_send_webhook_service.rbapp/services/crm/webhook_sender/omnichannel/webhook_sender_service.rbapp/services/webhook/entity/deal/base.rbapp/services/chat_history/get_chat_history.rb+app/models/crm/note.rb- hub-core
app/apps/crm/interactors/webhook/notification_receiver.rb - hub-core
app/apps/crm/repositories/webhook/deal_receiver.rb+ticket_receiver.rb - hub-core
app/core/domains/models/qontak_crm_object.rb+room_ticket.rb db/migrate/20260619000001_add_live_location_config_to_teams.rb
2.2 Architecture Decision Records
ADR-1 — CRM UI embed handled by crm-fe-v3
- Context: Timeline entry, creator label, and room deeplink must render in the CRM detail page.
- Options: (a) crm-fe-v3 (pre-baked); (b) legacy Nuxt-2
crmrepo. - Decision: crm-fe-v3. Rationale: pre-baked by author — see input. (crm-fe-v3 already ports Deals/Tickets per AGENTS.md rollout matrix; timeline component exists
Deals/Detail/DealDetailTimeline.vue.) - Consequences: BE must expose
creator_flag+ timeline note fields on the/api/mobile/v2.8CRM read endpoints crm-fe-v3 consumes. - Reversibility: High (FE-only swap).
ADR-2 — Reuse existing CRM→hub-core webhook pipeline (not a net-new API)
- Context: PRD §8 describes "dedicated Omnichannel deal/ticket creation API". A working pipeline already exists:
ShouldSendWebhookService→SendWebhookWorker→Webhook::Sender(HTTPS,MAX_RETRIES=3) → hub-coreNotificationReceiver→DealReceiver/TicketReceiver→QontakCrmObject/RoomTicket. - Options: (a) reuse/extend the existing webhook contract; (b) build a parallel dedicated endpoint.
- Decision: (a) reuse/extend. Rationale: create+delete already function end-to-end (
deal_receiver.rb:49); the pipeline already carrieschannel_integration_room_idandslug(entity/deal/base.rb:20), has retry + logging, and is company-token authenticated. A parallel API would duplicate auth, retry, and logging for no benefit. - Consequences: the "new API" in PRD §8 is realized as new event actions on the existing contract, plus new hub-core receiver branches. Per-team webhook config (
Crm::Webhook) gating must be made automatic for bot/AI records (see OQ-5). - Reversibility: Medium (contract additions are additive/back-compat).
ADR-3 — Emit update events for preview freshness
- Context: PRD AC-4 (CDTC-S01) requires the preview to reflect latest Name/Stage/Pipeline/Status.
VALID_WEBHOOK_ACTIONSalready includesupdate(crm/webhook.rb:13) andWebhook::Entity::Deal::Update/Ticket::Updateentities exist, but hub-coreDealReceiverrejectsupdate(:20) andTicketReceiverhandles onlydelete. - Options: (a) emit update + add hub-core update branch that upserts; (b) FE live-fetches from CRM on render (no update event).
- Decision: (a) — conditional on OQ-1. Rationale: PRD explicitly chose update sync (15a). If OQ-1 shows hub-chat already live-fetches display fields from CRM, (b) may suffice for deals and update events become a cache-invalidation signal only.
- Consequences: hub-core
DealReceivergains anupdatebranch;QontakCrmObjectmay need display columns iff not live-fetched (OQ-1).RoomTicketalready stores display fields. - Reversibility: Medium.
ADR-4 — Persist bot/AI actor via new creator_flag column
- Context: PRD payload carries
creator_flag("bot"|"agentic_ai"); CHG-002 wants the Creator field to read "Bot"/"AI".creator_idis a User FK (deal.rb:150,ticket.rb:60);data_sourceis transport-level (web/mobile/open-api,schema.rb:1394) and bot creates already set it toopen-api. - Options: (a) new
creator_flagstring column oncrm_deals+tickets; (b) overloaddata_source; (c) a dedicatedCrm::Sourcerow. - Decision: (a) new column, pending author confirm (OQ-3). Rationale: (b) collides with the existing
open-apitransport value; (c)crm_sourceis team-scoped sales attribution, not actor. A nullablecreator_flagis additive and back-compat (absent ⇒ existing creator behavior, PRD CDTC-S06 AC-3). - Consequences: migration on two large tables (
add_column, nullable, no default — no table rewrite on Postgres 11+). Exposed via webhook entity + mobile read entity. - Reversibility: High (drop column; absent ⇒ default behavior).
ADR-5 — Timeline chat-history note: lightweight reference + deeplink, async, reuse Crm::Note
- Context: PRD §7 + 15a want a timeline entry with a truncated chat history preview + room link, not a full transcript embed.
Crm::NoteSTI HubChannel subtypes already back the timeline, andChatHistory::GetChatHistoryalready pulls history by room via the hub message API. - Options: (a) reuse
Crm::NoteHubChannel type, populated async by a worker at creation; (b) new timeline table; (c) full transcript embed. - Decision: (a). Rationale: zero new timeline schema; FE already renders these notes (
DealDetailTimeline.vue). PRD rejected (c) (15b). - Consequences: new thin worker enqueued from
CreateServicewhenchannel_integration_room_idpresent; on hub message-API failure, note is still created with deeplink + creator label, preview omitted (PRD CDTC-S04 ERR-2). Retention: existing 1-year CRM log purge (PRD 5.1) — verify a purge job covers HubChannel notes (OQ). - Reversibility: High.
ADR-6 — Latest-wins ordered by CRM creation timestamp
- Context: One room = one deal + one ticket preview (Non-Goal #1; E1).
- Options: (a) order by CRM
created_at; (b) by hub-core receipt time. - Decision: (a). Rationale: PRD 15a; receipt time is non-deterministic under retry. hub-core enforces one active object per
[room_id, organization_id, object_type](unique index,create_qontak_crm_object.rb); the create-receiver currently rejects a second create (deal_receiver.rb:50) — for latest-wins it must replace instead. - Consequences: hub-core create branch changes from "fail if exists" to "soft-delete prior + insert" (or upsert) keyed on newer CRM
created_at. - Reversibility: Medium.
ADR-7 — Chatbot BE extends its existing CRM node executor (not a new node)
- Context: chatbot already creates CRM records via its generic flow node executor
Repositories::NodeExecutions::Nodes::MekariQontakCrm::Execute(execute.rb:18-25mapsqontak_crm_deal_create→POST /api/v3.1/deals,qontak_crm_ticket_create→POST /api/v3.1/tickets). The body is assembled from configured/AI args plus acrm_lead_idsenrichment (:261-275); it does not currently setchannel_integration_room_idorcreator_flag. To reach parity, those fields must be added to the create body. - Options: (a) extend the existing executor's body building (
process_body/enrich_body_for_deal_create) + the node_registry property set; (b) build a new dedicated CRM-deal/ticket node; (c) inject fields server-side in qontak.com from auth/session context. - Decision: (a), owned by Chatbot Squad. Rationale: the executor, company-token auth, 401-refresh and retry already exist (
crm_http_client.rb); adding 3 additive body fields reuses all of it. (b) duplicates the executor; (c) is brittle —room_idandcreator_flagare only known to the producer, not derivable from the company token.@room_idis already in scope in the executor (execute.rb:75), so wiringchannel_integration_room_idis local. The Agentic AI producer path must mirror the same contract (repo TBD — OQ-11). - Consequences: chatbot
node_registryproperties for the CRM-create actions must exposechannel_integration_room_id,creator_flag(and aligncontact_idvscrm_lead_ids— OQ-10) withdestination: 'body'; this is a cross-service change tracked in the chatbot repo, not executed against qontak.com. Blocking dependency (PRD §14 / Risk #7). - Reversibility: High (additive body fields; absent ⇒ existing behavior).
Minimum coverage checklist
- Storage:
creator_flagoncrm_deals/tickets; timeline viaCrm::Note(ADR-4, ADR-5). - Sync vs async: webhook emission + timeline-note build are async Sidekiq (existing
SendWebhookWorker; new note worker) — external hub calls > 100ms. - Caching: reuse
ShouldSendWebhookServiceRedis cache (webhook_<class>_<team>); no new cache. - Third-party: hub-core via HTTPS webhook + message API (existing
Webhook::Sender,Hub::ChatService); chatbot→CRM via REST/api/v3.1/*(existingCrmHttpClient). - Consistency: eventual (async webhook + retry); preview may be briefly stale (PRD E2 accepted).
- Multi-tenancy: company token →
external_company_id/company_idorg scoping (verified both directions). - Reuse vs new: see §2.4 endpoint tags.
2.3 Data Model
erDiagram
crm_deals ||--o{ crm_people_deals : has
crm_people_deals }o--|| crm_people : links
crm_deals ||--o{ crm_notes : timeline
tickets ||--o{ people_tickets : has
tickets ||--o{ crm_notes : timeline
crm_deals {
bigint id
int creator_id "User FK (existing)"
int crm_source_id "existing"
string channel_integration_room_id "existing"
string data_source "existing default web"
string creator_flag "NEW nullable bot|agentic_ai"
}
tickets {
bigint id
int creator_id "User FK (existing)"
string channel_integration_room_id "existing"
string data_source "existing"
string creator_flag "NEW nullable bot|agentic_ai"
}
crm_notes {
bigint id
string type "STI Crm::HubChannel*"
bigint crm_deal_id
bigint ticket_id
string channel_room_id
string channel_organization_id
}
DDL (matches Rails 5.2 / Postgres migrator):
class AddCreatorFlagToCrmDealsAndTickets < ActiveRecord::Migration[5.2]
def change
add_column :crm_deals, :creator_flag, :string # nullable, no default (back-compat)
add_column :tickets, :creator_flag, :string
end
end
Per-Status Lifecycle (State Surface Contract)
| Entity | State | Visibility | Retention | Restore | Transitions |
|---|---|---|---|---|---|
QontakCrmObject (deal preview, hub-core) | active | infobar preview | until replaced (latest-wins) or deleted | none (soft deleted_at) | create→update→delete; create(newer)→replace |
RoomTicket (ticket preview) | active | infobar preview | until delete | none | create→update→delete |
Crm::Note (HubChannel timeline) | created | CRM timeline | 1 year (system purge, PRD 5.1) | none | created→purged |
creator_flag | set-once at create | Creator field | record lifetime | n/a | immutable post-create |
2.4 APIs
Inbound (Chatbot BE / Agentic AI → qontak.com) — existing CRM create REST API, body extended
| # | Endpoint | Method | Tag | Caller must add | Notes |
|---|---|---|---|---|---|
| IB1 | /api/v3.1/deals | POST | extended | channel_integration_room_id, contact_id/crm_lead_ids (OQ-10), creator_flag | chatbot already calls this (execute.rb:19); body assembled in process_body/enrich_body_for_deal_create — add 3 fields |
| IB2 | /api/v3.1/tickets | POST | extended | channel_integration_room_id, creator_flag, ticket fields | chatbot already calls this (execute.rb:21) |
| IB3 | /api/v3.1/contacts?phone= | GET | reused | — | chatbot phone→lead lookup that yields crm_lead_ids (execute.rb:277-291) |
| IB4 | /api/v3.1/teams/company_token | POST | reused | sso_id + X-Crm-Api-Key | chatbot token refresh (crm_http_client.rb:143-151) — company-token auth |
Outbound (qontak.com → hub-core) — extends existing webhook contract
| # | Event | Method/transport | Tag | Payload key fields | Timeout / retry / failure |
|---|---|---|---|---|---|
| O1 | qontak.crm.deal.create | HTTPS POST (Webhook::Sender) | reused | id,slug,channel_integration_room_id,creator_flag(new),stage,pipeline (entity/deal/base.rb) | retry MAX_RETRIES=3; on fail → no preview, logged (Crm::LogApi) |
| O2 | qontak.crm.deal.update | HTTPS POST | extended (entity exists; receiver branch new) | latest name/stage/pipeline | retry 3; on fail → stale preview, logged |
| O3 | qontak.crm.deal.delete | HTTPS POST | reused | id | retry 3; on fail → stale preview |
| O4 | ticket create | HTTPS POST | new-with-justification | room_id,crm_ticket_name,crm_ticket_id,crm_unique_ticket_id,creator_flag | retry; justification: omnichannel sender hardcodes action:'delete' (webhook_sender_service.rb:62-63) — no create path exists |
| O5 | ticket update | HTTPS POST | new-with-justification | latest name/status | same |
| O6 | ticket delete | HTTPS POST | reused | unique_ticket_id | ticket_receiver.rb:30 |
| O7 | GET chat history | GET /api/open/v1/messages/rooms/:room_id?offset=1&limit=100 | reused | — | on fail → timeline note w/o preview (ERR-2); no retry this phase |
Inbound (hub-core receiver branches — implemented in hub-core, READ-ONLY here)
| # | Receiver | Current | Change needed |
|---|---|---|---|
| I1 | DealReceiver#call (:20) | create, delete | add update; change create from reject-if-exists to replace (ADR-6) |
| I2 | TicketReceiver#call (:14) | delete | add create + update |
| I3 | NotificationReceiver#ticket_webhook_receiver (:42) | rejects non-delete | allow create/update routing |
Role × Endpoint Authorization
| Endpoint | System (bot/AI) | Agent (Omni only) | Agent (CRM perm) | Manager |
|---|---|---|---|---|
| Create deal/ticket (company token) | allow | n/a | n/a | n/a |
| View infobar preview | n/a | allow | allow | allow |
| Click preview → CRM detail | n/a | 403 (CRM-managed) | allow | allow |
| View timeline note + creator | n/a | n/a | allow | allow |
2.5 Sequence Diagrams
Happy path — bot/AI creates deal → preview + timeline:
sequenceDiagram
participant BOT as chatbot BE (execute.rb)
participant LB
participant WEB as CRM web
participant DBP as PG primary
participant SK as Sidekiq
participant REDIS as Redis
participant HUB as hub-core
participant HDB as hub PG
BOT->>BOT: build body (room_id, contact_id/crm_lead_ids, creator_flag)
BOT->>LB: POST /api/v3.1/deals (company token)
LB->>WEB: route
WEB->>DBP: INSERT deal (channel_integration_room_id, creator_flag)
WEB->>DBP: INSERT crm_people_deals (contact assoc)
WEB-->>BOT: 201 Created
WEB->>SK: enqueue SendWebhookWorker + TimelineNoteWorker
SK->>REDIS: webhook config (Crm::Deal.create?)
SK->>HUB: POST eventType=qontak.crm.deal.create (~80-200ms)
HUB->>HDB: upsert QontakCrmObject (latest-wins)
SK->>HUB: GET /messages/rooms/:room_id (chat history)
HUB-->>SK: messages
SK->>DBP: INSERT Crm::Note (HubChannel: preview + deeplink)
Failure path — hub message API down at timeline build:
sequenceDiagram
participant SK as Sidekiq
participant HUB as hub-core
participant DBP as PG primary
SK->>HUB: GET /messages/rooms/:room_id
HUB-->>SK: timeout / 5xx
Note over SK: rescue (per get_chat_history.rb:64 `rescue nil`)
SK->>DBP: INSERT Crm::Note (deeplink + creator label only, NO preview)
Note over SK: log timeline_log_creation_failed; no agent-facing error (CDTC-S04 ERR-2)
Failure path — webhook delivery fails (deal create):
sequenceDiagram
participant SK as Sidekiq
participant HUB as hub-core
SK->>HUB: POST qontak.crm.deal.create
HUB-->>SK: 5xx / timeout
Note over SK: Webhook::Sender retries (MAX_RETRIES=3, +30s)
SK->>HUB: retry x3
Note over SK: exhausted → Crm::LogApi failure; infobar keeps CTA (no preview)
Failure path — chatbot CRM create token expired (producer side):
sequenceDiagram
participant BOT as chatbot CrmHttpClient
participant WEB as CRM web
BOT->>WEB: POST /api/v3.1/deals (Bearer access_token)
WEB-->>BOT: 401 Unauthorized
Note over BOT: request_with_auth retries once (crm_http_client.rb:51)
BOT->>WEB: POST /api/v3.1/teams/company_token (sso_id + X-Crm-Api-Key)
WEB-->>BOT: new token
BOT->>WEB: POST /api/v3.1/deals (new token)
Note over BOT: on refresh fail → { code: nil, data: 'Failed refresh token' }; Rollbar; no deal created
2.6 State Diagram — infobar preview (re-derived from PRD §7)
stateDiagram-v2
[*] --> CTA: no record
CTA --> Preview: create event received
Preview --> Preview: update event (latest fields) / newer record (latest-wins)
Preview --> CTA: delete event
Preview --> Error: hub cannot reach CRM (CDTC-S01 ERR-2)
Error --> Preview: retry/reload
2.7 Branch & Skip Catalog
| Branch | Condition | Behavior | Owner |
|---|---|---|---|
| Skip preview/timeline | channel_integration_room_id absent | record created; no webhook, no note (PRD E3, ERR-1) | qontak.com CreateService |
| Skip contact assoc | contact_id/crm_lead_ids invalid/absent | record created, contact empty, log internal (CDTC-S03 ERR-1/2) | chatbot execute.rb (lookup) · qontak.com associate_hub_contact |
| Default creator | creator_flag absent/unrecognised | existing creator behavior (CDTC-S06 AC-3) | qontak.com |
| Preserve source | payload already has source | do not override w/ channel source (NEG-3) | qontak.com |
| Manual flow untouched | manual create | no change (NEG-2) | n/a |
| Latest-wins | 2nd record for room | replace prior preview (NEG-1) | hub-core receiver |
3. High-Availability & Security
- AuthN: company token →
external_company_id(qontak.com) maps tocompany_id/Organization (hub-core) — verified existing. Chatbot producer authenticates with the same company token (__qontak_crm_company_token) and refreshes via/api/v3.1/teams/company_token(crm_http_client.rb) — verified. - AuthZ: preview viewable with Omnichannel inbox access; CRM detail gated by CRM permission (CRM-managed 403 on click — OQ-1 confirms UX). Creator field & timeline read-only, not editable (PRD §5 read/write).
- Multi-tenancy: all records team/org-scoped; webhook config is per-team (
webhook_<class>_<team_id>); chatbot scopes every CRM call byorganization_id→ company token (crm_http_client.rb:15,90). - Idempotency: deal create uses idempotency key (
deals.rb:1374); timeline-note worker must guard against duplicate notes per[deal_id, room_id](E1 concurrency — see OQ). - Failure/integrity: every external call has retry+log (O1–O6 retry 3; O7 no retry, graceful degrade; chatbot create has single 401-refresh retry). Webhook delivery failure → stale preview accepted (PRD E2).
- InfoSec approval:
[REQUIRED]before §7 yes.
Responsibility Boundary Matrix (PRD §14)
| Capability | Owner | Status |
|---|---|---|
Bot payload (room_id,contact_id,creator_flag) | Chatbot Squad — chatbot node_executions/.../mekari_qontak_crm/execute.rb (extend body) | dependency (blocking) — ADR-7 |
| AI payload (auto-identified) | Agentic AI Squad — producer repo TBD (mirror ADR-7 contract) | dependency (blocking) |
| Persist room/contact/creator_flag; emit events; build timeline note + deeplink | CRM (qontak.com) | this RFC |
| Receiver branches (deal update, ticket create/update, latest-wins) | Omnichannel (hub-core) | this RFC (cross-service) |
| Infobar preview + new-tab nav | Omnichannel (hub-chat) | this RFC (FE) |
| Timeline + creator label render | CRM (crm-fe-v3) | this RFC (FE, pre-baked) |
4. Backwards Compatibility & Rollout Plan
Backwards compat: all changes additive — creator_flag nullable (absent ⇒ unchanged behavior); new event actions ignored by old receivers; new chatbot body fields are additive on the existing /api/v3.1/deals|tickets calls; manual/auto flows untouched (NEG-2).
Feature flag: [REQUIRED: name] (PRD OQ#4) — default OFF, per-account (reuse Feature.enabled_globally/per-team gate pattern seen in webhook_sender_service.rb:114).
4.C Agent Execution Plan (qontak.com only — cross-service work tracked separately)
C1 — Verify existing reuse (no code). Read create_service.rb:97,378 + ticket/create.rb:107; cross-check producer side chatbot execute.rb:261-275. Accept: confirm room-id + contact assoc fire for the bot create path and reconcile the contact_id vs crm_lead_ids contract (OQ-10); write findings into §5 if not.
C2 — Migration: creator_flag. Create db/migrate/*_add_creator_flag_to_crm_deals_and_tickets.rb (ADR-4 DDL). Cmds: bundle exec rails db:migrate; Accept: Crm::Deal.column_names.include?('creator_flag') true; schema.rb diff shows both tables.
C3 — Persist creator_flag in services. Modify crm/deals/create_service.rb + ticket/create.rb to set creator_flag from params (preserve absent ⇒ nil). Accept: bundle exec rspec spec/services/crm/deals/create_service_spec.rb — new examples for bot/agentic_ai/absent pass.
C4 — Expose + emit. Add creator_flag to webhook/entity/deal/base.rb + ticket entity; ensure update + ticket create/update events emit via ShouldSendWebhookService/omnichannel sender (resolve OQ-5 gating). Update api_spec.yaml for any changed mobile read entity; Cmds: bundle exec rspec spec/contract/. Accept: contract test green; payload includes creator_flag.
C5 — Timeline note worker. New thin worker (pattern: resolve_room_worker.rb) → service that builds a Crm::Note HubChannel entry using ChatHistory::GetChatHistory; enqueue from CreateService when room present; graceful degrade on hub failure (ERR-2); idempotency guard. Accept: bundle exec rspec spec/services/... covering happy + hub-failure + no-room; bundle exec rspec spec/workers/....
C6 — Static + security. bundle exec rubocop <files>; bundle exec brakeman --no-pager; bundle exec bundler-audit check --update. Accept: 0 offenses / 0 new warnings.
Cross-service chunks are owned by other squads and tracked in their repos — not executed against qontak.com:
- chatbot: extend
mekari_qontak_crm/execute.rbbody building +node_registryproperties to sendchannel_integration_room_id/creator_flagand align contact field (ADR-7, OQ-10).- hub-core: receiver branches (deal
update, ticketcreate/update, latest-wins replace).- hub-chat / crm-fe-v3: preview render + timeline/creator-label render.
4.B / Verification & Rollback Recipe
Pre-merge (in order): bundle exec rubocop <changed> → bundle exec brakeman --no-pager → bundle exec rspec spec/services spec/workers → bundle exec rspec spec/contract/.
Post-deploy signals (PRD §11): bot_deal_created/ai_deal_created non-zero; deal_preview_displayed ratio ≥95% (alert if 0 while creates >0 for 15min); timeline_log_creation_failed < 5%/1h; P95 room load ≤ baseline+200ms (rollback trigger).
Rollback: (1) toggle feature flag [name] OFF (per-account) → bot/AI events stop emitting; (2) creator_flag column is inert when unread — no down-migration required for rollback; (3) confirm deal_preview_displayed returns to pre-rollout and P95 latency recovers; (4) revert PR if needed (column drop only as cleanup, last).
5. Concerns / Open Questions
| # | Severity | Question | Owner | Blocks |
|---|---|---|---|---|
| OQ-1 | [critical] | How does hub-chat DealDetails.vue source Name/Stage/Pipeline — live CRM fetch via external_id, or stored? QontakCrmObject stores none. Determines whether deal update-sync (AC-4) needs new hub-core columns or is just a cache-bust. | Omnichannel Eng | ADR-3, O2, I1 |
| OQ-2 | [critical] | Which sender carries bot/AI ticket create+update? Omnichannel sender hardcodes action:'delete' (webhook_sender_service.rb:62-63); generic webhook supports Ticket (webhook.rb:10) but needs a create/update receiver in hub-core. | CRM + Omnichannel Eng | O4,O5,I2,I3 |
| OQ-3 | [important] | Confirm new creator_flag column vs overloading data_source (ADR-4). | CRM Eng + PM | C2,C3,ADR-4 |
| OQ-4 | [critical] | Exact hub-chat room deeplink URL pattern (host + route) for the timeline link (CDTC-S05). No web deeplink helper found in repo. | Omnichannel Eng | C5, CDTC-S05 |
| OQ-5 | [important] | For bot/AI parity, deal-create webhook must fire automatically — but emission is gated by per-team Crm::Webhook config (should_send_webhook_service.rb). Is there an always-on path for bot/AI, or must each account configure a Crm::Deal.create webhook? | CRM Eng | O1 reliability |
| OQ-6 | [important] | E1 concurrency: two creates for one room within ms — confirm CRM created_at tie-break + timeline-note dedup key. | CRM Eng | C5, ADR-6 |
| OQ-7 | [important] | Confirm a purge job actually deletes HubChannel Crm::Note after 1y (PRD 5.1) — not verified in repo. | CRM Eng | retention claim |
| OQ-8 | [important] | Full list of deal/ticket fields that trigger an update event (PRD OQ#8). | CRM Eng | O2,O5 |
| OQ-9 | [nice-to-have] | Latency threshold for "no noticeable degradation" (PRD OQ#3). | Omnichannel Eng | rollout gate |
| OQ-10 | [critical] | Contact-association contract mismatch (verified across repos): chatbot sends crm_lead_ids (array, derived via /api/v3.1/contacts?phone= lookup — execute.rb:269-291), but PRD/CDTC-S03 + CRM associate_hub_contact (create_service.rb:378) describe a singular contact_id. Confirm the canonical field name and which side performs association — otherwise contact is associated twice or not at all. | Chatbot + CRM Eng | C1, CDTC-S03, ADR-7, IB1 |
| OQ-11 | [important] | Does the Agentic AI producer reuse the chatbot CRM node executor (mekari_qontak_crm/execute.rb) or a separate service? Determines whether ADR-7's body-field work is implemented once or mirrored in a second repo (currently TBD). | Agentic AI Squad | ADR-7, AI payload dep |
Fragile assumptions: PRD assumptions #5 (company token) and #6 (chat-history API) are verified in-repo (see Source Verification) — and #5 is now also verified from the producer side (chatbot crm_http_client.rb uses __qontak_crm_company_token + /api/v3.1/teams/company_token). Verified (producer side): chatbot's CRM node executor (execute.rb) already calls /api/v3.1/deals|tickets but does not populate channel_integration_room_id/creator_flag today — confirming the Chatbot Squad payload work (Risk #7) is real and scoped to execute.rb body building. Risk #7 (5 of 9 deps owned by Chatbot/Agentic AI squads) remains the top program risk; mitigation = align payload delivery in sprint planning.
6. Comment logs
| Date | Author | Note |
|---|---|---|
| 2026-06-30 | rfc-starter | Initial draft from PRD v1.1. Key finding: CRM→hub-core webhook pipeline + QontakCrmObject/RoomTicket receivers already exist; PRD "dedicated Omnichannel API" reframed as additive event actions (ADR-2). 4 critical OQs gate readiness. |
| 2026-06-30 | rfc-starter (refinement r2) | Added Chatbot BE (chatbot) as the upstream producer repo. Grounded against mekari_qontak_crm/execute.rb (`POST /api/v3.1/deals |
7. Ready for agent execution
Ready for agent execution: no
Blocking gates (must resolve before yes):
- OQ-1 [critical] — deal preview field source unknown → cannot finalize update-sync design (ADR-3 / hub-core
QontakCrmObjectshape). - OQ-2 [critical] — bot/AI ticket create+update has no sender/receiver path today.
- OQ-4 [critical] — room deeplink URL pattern unverified (left as
[REQUIRED], not invented). - OQ-10 [critical] — chatbot↔CRM contact contract mismatch (
crm_lead_idsvscontact_id) must be reconciled before C1/CDTC-S03 is safe. - InfoSec approver + feature flag name + discussion link are
[REQUIRED]metadata.
The qontak.com-only chunks (C1–C6) are executable now for the storage + emission + timeline half; the cross-service contract (chatbot producer body per ADR-7, hub-core I1–I3, FE renders) and OQ-1/2/4/10 must close before the end-to-end flow is agent-runnable without a clarification meeting.