RFC Summary: Phase 1 — Bot & AI Creation Parity: Backend (qontak.com)
Full RFC: Phase 1 — Bot & AI Creation Parity: Backend (qontak.com)
Status: IDEA | Type: backend / enhancement
Author: Ardian Pradipta | Target: 2026-Q3
PRD: crm/consistent-deal-ticket-creation/prds/phase-1-bot-ai-creation-parity.md
Problem
Deals and tickets created by Chatbot or Agentic AI show no creator attribution — data_source is always 'open-api' and the timeline displays "<deleted user>" instead of "Bot" or "AI". There is no way for CRM users to distinguish bot/AI-created records from regular API creates, and the timeline lacks the chat-context note that manual creates get.
What's Being Built
creator_flagparam — Chatbot/Agentic AI callers sendcreator_flag: 'bot'or'agentic_ai'on POST create;data_sourcerecords the channel origin.- Timeline creator label —
audits.usernamestores thecreator_flagvalue;audit.rb#mapping_whorenders "Bot" or "Agentic AI" in the timeline. - Timeline chat note — When
channel_integration_room_idis also sent, a note is created:Crm::HubChannelTicketfor deals,Crm::HubChannelTicketV2for tickets. - Contact association —
customer_idson ticket create dispatches a newLinkTicketByCustomerIdWorkerto associate contacts viaPeopleTicketjoin model.
Component Architecture
flowchart TB
caller([API caller]) --> api[/v4 · v3.1 deals · tickets API/]
api --> svc[create handler]
svc -->|as_user creator_flag| audit_write[(audits table\nusername=creator_flag)]
svc -->|data_source=creator_flag| db[(Postgres)]
svc -->|room_id present - deals| hct[Crm::HubChannelTicket]
svc -->|room_id present - tickets| hctv2[Crm::HubChannelTicketV2]
hct --> audit_write
hctv2 --> audit_write
hct --> db
hctv2 --> db
svc -->|customer_ids async - tickets| wrk[LinkTicketByCustomerIdWorker]
wrk --> svc2[LinkTicketByCustomerIdService]
svc2 --> db
Repo Map (files touched)
flowchart LR
subgraph crm[BE: qontak.com]
subgraph controllers[Controllers]
v4d["api/v4/deals.rb"]
v4t["api/v4/tickets.rb"]
v31d["api/v3dot1/deals.rb"]
v31t["api/v3dot1/tickets.rb"]
end
subgraph notes[Timeline Notes]
hct["models/crm/hub_channel_ticket.rb\n(Crm::DealNote — deals)"]
hctv2["models/crm/hub_channel_ticket_v2.rb\n(TicketNote — tickets)"]
note_tmpl["services/hub/ticket/new_ticket_service.rb\n(create_note_ticket — V2 template)"]
end
subgraph contact_assoc[Contact Association]
svc_d["services/contacts/link_deal_by_customer_id_service.rb"]
wrk_d["workers/contacts/link_deal_by_customer_id_worker.rb"]
svc_t["services/contacts/link_ticket_by_customer_id_service.rb [NEW]"]
wrk_t["workers/contacts/link_ticket_by_customer_id_worker.rb [NEW]"]
pt["models/people_ticket.rb"]
end
subgraph models[Models / Schema]
deal_m["models/crm/deal.rb"]
schema["db/schema.rb"]
audit["models/audit.rb\n(mapping_who — extended)"]
end
end
v4d -->|create_notes| hct
v4t -->|create_ticket_notes NEW| hctv2
note_tmpl -.->|template for| hctv2
v4d -->|customer_ids async| wrk_d
v4t -->|customer_ids async| wrk_t
wrk_d --> svc_d
wrk_t --> svc_t
svc_d --> pt
svc_t --> pt
Sequence Diagrams
Happy path — bot deal creation with timeline note
sequenceDiagram
actor Bot as Chatbot API
participant LB as Load Balancer
participant API as CRM API pod
participant DB as Postgres primary
Bot->>LB: POST /v4/deals (creator_flag:bot, room_id)
LB->>API: HTTP
API->>API: permit params, validate creator_flag
API->>API: set audited_actor = creator_flag (bot)
note over API: Audited.as_user(bot) block begins
API->>DB: INSERT deal (data_source=bot, channel_integration_room_id)<br/>audits.username=bot
DB-->>API: commit
API->>DB: INSERT Crm::HubChannelTicket note<br/>audits.username=bot
DB-->>API: commit
note over API: Audited.as_user block ends
API-->>Bot: 201 Created
Failure path — invalid creator_flag
sequenceDiagram
actor Bot as Chatbot API
participant API as CRM API pod
Bot->>API: POST /v4/deals (creator_flag:invalid)
API->>API: validate creator_flag
alt value not bot or agentic_ai
API-->>Bot: 422 INVALID_CREATOR_FLAG
else absent
API->>API: skip mapping, default behavior
API-->>Bot: 201 (data_source unchanged)
end
Key Technical Decisions
| # | Decision | Chosen | Rationale |
|---|---|---|---|
| 1 | Creator label mechanism | audits.username via as_user(creator_flag) + mapping_who extension | data_source is for channel-origin tracking, not creator labels. audits.username already handles system actors (hub, Qontak system). No schema change. |
| 2 | Timeline note model | Crm::HubChannelTicket for deals, Crm::HubChannelTicketV2 for tickets (not one shared model) | Two distinct STI subclasses with different parent associations (crm_deal vs tickets). Using the wrong type would break timeline rendering. |
| 3 | Feature flag | None — param is self-gating | creator_flag absent → existing behavior. Producers control when they start sending it. |
| 4 | customer_ids for tickets | New LinkTicketByCustomerIdService + Worker | Deal counterpart exists. Generalising it adds polymorphic complexity not worth the risk. Two ~50-line files following an established template. |
APIs at a Glance
| Method | Endpoint | Status | Owner |
|---|---|---|---|
POST | /api/v4/deals | extended — add optional creator_flag, customer_ids, async_lead_assoc | CRM BE |
POST | /api/v4/tickets | extended — same params | CRM BE |
POST | /api/v3.1/deals | extended — same params | CRM BE |
POST | /api/v3.1/tickets | extended — same params | CRM BE |
Request params: creator_flag (optional, "bot"|"agentic_ai", case-sensitive), customer_ids (optional, string[]), async_lead_assoc (optional, boolean, default false).
201 response: Unchanged shape — data_source is NOT exposed in the entity. creator_flag is not echoed back.
Open Blockers
| # | Blocker | Owner | Blocks |
|---|---|---|---|
| 1 | Chatbot/AI squads must send creator_flag + room_id in create payload | Chatbot + AI squads | Feature inactive until payload delivered |
| 2 | InfoSec approver | CRM Tech Lead + InfoSec | §7 Ready marker cannot flip to yes |
Rollout
| Stage | Audience | Go/No-go evidence |
|---|---|---|
| 1 — Producers adopt | Chatbot/AI teams | creator_flag present in production API calls; zero unexpected creator_flag.invalid WARN logs over 7 days |
| 2 — FE reads timeline | CRM FE team | Creator label displays correctly in timeline |
Stop conditions: Revert PR if creator_flag.invalid error rate spikes in production or timeline labels regress for existing manual creates.
Rollback: Revert the PR. No DB changes to undo — the data_source column accepts existing values natively. Any LinkTicketByCustomerIdWorker jobs in the queue become no-ops (the service guard ticket_not_found handles orphaned jobs gracefully).
Generated from RFC: Phase 1 — Bot & AI Creation Parity: Backend (qontak.com) · 2026-07-03