Skip to main content

Qontak | Chatbot | AI Agent — Qontak CRM Actions — Adj: Custom fields on create ticket / create deal

ADJUSTMENT to Qontak CRM Actions (as-built). Extends the already-shipped create deal (QACT-S01) and create ticket (QACT-S02) actions so the builder can map the tenant's CRM custom fieldsorg-level (shared by every pipeline) — for the AI Agent to fill. Everything else about those two actions (auth, {{customer_name}} interpolation, 401 refresh/retry, the fixed standard fields) stays exactly as shipped. Design grounded in the qontak-designer prototype — wireframes committed in-repo at documentations/crm-actions-custom-fields-wireframes.md; feasibility grounded in chatbot BE + chatbot-fe FE (2026-07-13).

v1.6 — as-built reconciliation (2026-07-27). v1.5 recorded GA (frontmatter, header, Rollout) but left the contract itself untouched; the completion-alignment audit then gated the initiative ⛔ NOT-ALIGNED — 6 of 24 composite ACs delivered, 0 test cases in either the repo suite or Pokayoke. v1.6 answers that audit's open questions 1–4 in this contract. It is aligned to the shipped implementation and to BE TECH-RFC Decision 3.7. Two things changed materially since v1.4: custom fields turned out to be org-level (team_id-scoped), not pipeline-scoped — the pipeline gate (old D-1/D-2) is gone (ratified, closes RFC A-4) — and three drafted capabilities did not ship: array-typed fields, the ai_agent_action_custom_fields flag, and the custom-field telemetry. They are recorded honestly as deferrals in Section 15 (D-11 → D-15) with follow-up items in Section 16, not rewritten as if never intended. Naming: the product term here is custom field; the CRM API, the BE code, and the FE code all call the same thing an additional field (additional_field_deal / additional_fields[]) — they are the same object.

HEADER BLOCK

FieldValue
PMDimas Fauzi Hidayat
PRD Version1.7
StatusSHIPPED — GA to all clients/users 2026-07-27, partial against this contract. The scalar path is live (BE 1.324.0, FE 3.8.3); array types, the feature flag and the telemetry events did not ship (D-11 → D-13), and the merge reaches the update actions (D-15). Audit: completion-alignment check — ⛔ NOT-ALIGNED, 6 of 24 ACs delivered
PRD TypeADJUSTMENT
EpicBOT-4662 — 26Q3 Chatbot - CRM Actions: Custom Fields (child Epic; initiative umbrella is BOT-4597).
SquadBOT — Chatbot & AI Squad
RFC LinkBE TECH-RFC — CRM actions custom fields (+ FE RFC) — merged; Decision 3.7 is the org-level scoping authority
Figma MasterPending — production Figma frames TBD. Interim design reference: Wireframes (in-repo) + qontak-designer prototype
AnchorYes — Qontak Action — ANCHOR
Parent PRDQontak CRM Actions — Released Actions (as-built)
Labelsepic:qontak-chatbot | module:ai-agent | feature:qontak-action
Last Updated2026-07-27

Status values: DRAFTREADYBUILDSHIPPED.

Table of Contents

2. CONDITIONAL BLOCK: ADJUSTMENT CONTEXT

FieldValue
CB-A.1 — Parent AnchorQontak Action — ANCHOR. Parent PRD: Qontak CRM Actions — Released Actions (as-built), child Epic BOT-4662.
CB-A.2 — Adjustment scope (as-built)The create deal and create ticket action-config surfaces gain an org-level custom-field picker — a dedicated Add field popover listing the org's CRM additional fields (searchable), shipped as AdditionalFieldSelector.vue. The list is not filtered by pipeline: CRM defines additional fields per team_id, so every pipeline in the org shares one list (D-1). Each added field, like the existing fixed fields, carries a Let AI decide / Set manually mode. The BE executor merges the configured values into the CRM write body as an additional_fields[] array (D-14). Config surface is create deal + create ticket; note the BE merge itself is wired for all four write actions incl. update deal / update ticket (D-15). Array-typed fields (Checklist / Multi-select / Tags) are NOT in the shipped scope — see D-11.
CB-A.3 — Parent scope preserved (unchanged)Unchanged: company-token (Bearer) auth + 401 refresh/retry (node_resources/mekari_qontak_crm/crm_http_client.rb:45-77 (401 refresh/retry + Bearer)); {{customer_name}} interpolation; customer_contact_association lead lookup → crm_lead_ids (execute.rb:262-276 (enrich_body_for_deal_create)); the existing fixed fields (Pipeline, Stage, name, creator/owner, customer_contact_association, deal tags); all other actions (update ticket, update deal, assign, resolve, tag, attachment); the ai_agent_action gating and per-action billing components (Section 6 of the parent PRD). This adjustment adds only the custom-field layer on top of the two create actions.
CB-A.4 — Reason for adjustment (why now)Unblocks a live configuration gap. Tenants configuring create deal / create ticket today can only map the hard-coded standard properties — the FE picker offers no path to their org's custom fields (chatbot-fe/modules/ai-agent/constants/ai-agent-actions-constants.ts:75-432) and the BE builds the CRM body only from node-registry properties matched by destination, with no custom-field merge (chatbot/app/core/repositories/node_executions/nodes/mekari_qontak_crm/execute.rb:186-208). Result: every deal/ticket the agent creates lands with the org's custom fields empty, so CS ops must hand-back-fill each record in CRM — and pipelines with required custom fields cannot be automated at all. This is the top blocker for adopting the create actions among orgs whose CRM depends on custom fields.

3. One-liner + Problem

One-liner: Let the Qontak AI Agent fill a tenant's CRM custom fields — the org's own additional-field definitions, shared across pipelines — when it creates a deal or ticket, so automated records land complete instead of half-empty.

Problem: The shipped create deal / create ticket actions only let the builder map Qontak's built-in standard properties. Every org that has customized its CRM — the common case for Sales/Service pipelines — configures the action, watches the agent create records, and then finds its own custom fields (ticket type, affected modules, competitor products, region tags, SLA tier, …) blank on every record. CS ops back-fills by hand, defeating the automation; and an org with a required custom field can't automate the create at all because CRM rejects it. The capability gap is purely in the config surface + payload merge — the array primitives the agent needs already exist in the codebase (see Section 14).

Evidence grounding. No Product Intelligence Hub research doc is cited (sources: []) — this adjustment is driven by a code-grounded configuration gap and the qontak-designer design prototype, not by a user-research study. The problem is evidenced by the FE/BE code paths above and confirmed live in the prototype.

4. Target Users + Persona Context

PersonaRoleGoalPainWorkaround
Primary — CS Ops Lead / Bot ManagerCustomer-side bot owner who configures the AI Agent's create deal / create ticket actionsHave the agent create CRM records that are complete — populated with the org's own custom fields, not just the standard onesThe Add-field picker offers only standard properties; the agent can't touch custom fields, so records are created incompleteManually edit every agent-created deal/ticket in Qontak CRM to fill custom fields; or avoid the action for pipelines that require custom fields
Secondary — CRM AdminOwns the org's custom-field (additional-field) definitions in Qontak CRM (settings → customization) — org-level, shared by every pipelineTrust that fields they define for the org are honoured by automationCustom fields they define are invisible to the AI Agent configTell CS ops which fields to back-fill by hand
Runtime actor — AI Agent (fills the values)The function-calling agent during a live conversationFill each mapped custom field from the conversation, choosing valid values (one or more, for array fields)

(Full persona background + auth/gating: parent PRD Sections 4 & 6, and the ANCHOR.)

5. Non-Goals

  1. update deal / update ticket config surface — this adjustment's builder-facing scope is the two create actions. (As-built caveat: the BE merge is wired for all four write actions — whether the picker is exposed on the update actions depends only on the per-action node_registries seed, not on code. See D-15 and OQ A-6.)
  2. Creating or editing custom-field definitions — the picker reads the org's existing custom fields; defining them stays in Qontak CRM (settings → customization). Out of scope.
  3. Custom fields for chat actions (assign, resolve, tag, attachment) — they don't write CRM records; not applicable.
  4. Custom-field types outside the shipped whitelist — the as-built normalizer supports Single-line text, Text Area, Number, Percentage, URL, Dropdown select (see Section 7.1). Any other CRM type — including Date picker, Checkbox, Qontak user, and the array family (Checklist / Multi-select / Tags) — is silently skipped by the lookup and never reaches the picker. Extending the whitelist is a follow-up (D-11), not this PRD.
  5. Pipeline-scoped or per-pipeline custom-field lists — custom fields are org-level in CRM, so there is nothing to scope: one list serves every pipeline (D-1). The picker deliberately does not filter by the selected pipeline.
  6. Array (multi-value) custom fields — did not ship and are explicitly out of the as-built scope: values are constrained to scalars (string / number / boolean) at the merge point. Multi-value support is a follow-up (D-11).
  7. Mekari-product / third-party action custom fields — owned by Mekari Action (HMAC) and Native Integration (OAuth), not this initiative.

Scope Changes

Engineering surfaces this PRD touches (controlled vocab). Kept in sync with the scope_changes frontmatter above.

As-built references verified against chatbot@master + chatbot-fe@main on 2026-07-27.

  • Backendchatbot: node_executions/nodes/mekari_qontak_crm/execute.rb merges configured custom-field arguments into the CRM write body as an additional_fields[] array of {id, name, value, value_name} (merge_additional_field_arguments, execute.rb:249-277), called for WRITE_ACTION_TYPES (execute.rb:33-39); node_resources/mekari_qontak_crm/lookup_resources.rb adds the org-level definitions lookup (additional_field_deal / additional_field_ticketGET /api/mobile/v2.8/crm/additional_fields?object=…&created_by=user, :18-19, fetch_additional_fields :183-200) plus the type whitelist + descriptor normalizer (ADDITIONAL_FIELD_CONFIG :33-41, build_field_property :202-224); the per-action exposure is a node_registries settings.option.additional_field entry (data seed, not code).
  • Frontendchatbot-fe: modules/shared/ai-agent-actions/composables/useAdditionalFieldLookup.ts fetches the definitions with company_id only — no pipeline_id (:49-59); components/forms/AdditionalFieldSelector.vue is the shipped Add field popover (search + "All fields added" empty state, one flat list, no groups and no type badges); components/forms/ActionIntegrationForm.vue renders each added field through its existing html.element/html.type switch and the per-row remove control (:330-338, selector mounted :371-375); PropertiesItem.is_additional_field carries the descriptor (constants/action-constants.ts:28).
  • Design — Prototyped in qontak-designer (AddFieldPicker.vue — see design-wireframes/ai-agent-custom-fields/). The prototype's pipeline scoping and array rows were not built (D-1, D-11). Production Figma frames still TBD.

6. Constraints

FieldValue
Feature flagNone — descoped (D-12). ai_agent_action_custom_fields was specified (RFC Decision 3.4 + Chunk 1) but never implemented: there is no rollout/ai_agent_action_custom_fields.rb and no flag check anywhere in chatbot or chatbot-fe. The effective gate is the node_registries settings.option.additional_field seed per action — where the seed exists, every org on that action sees the picker. The base ai_agent_action gate (parent PRD Section 6) still applies.
Plan / billing componentInherits the create actions' existing components (parent PRD Section 6): Create Deal → CP-QONTAKCRM-2025-0005 (CRM Deal) → Sales Suite; Create Ticket → CP-QONTAKCRM-2025-0002 (CRM Ticket) → Service Suite. No new billing component — custom fields ride the same entitlement as the action they extend.
PlatformConfiguration — Qontak web app (AI Agent config drawer). Runtime — the AI Agent function-calls the action during a conversation on any supported channel.
AuthUnchanged — Qontak CRM company token (Bearer), 401 refresh/retry (node_resources/mekari_qontak_crm/crm_http_client.rb:45-77 (401 refresh/retry + Bearer)). Custom fields ride the same authenticated POST.
Field scopeCustom fields are org-level in Qontak CRM — the definitions endpoint returns fields scoped to team_id, with no pipeline parameter, so all pipelines in an org share one list (D-1, RFC Decision 3.7). The picker therefore has no pipeline dependency: it loads on mount from company_id alone and works whether Pipeline is Set manually or Let AI decide.
Value constraintValues are scalar only — the merge accepts String, Numeric, or boolean and skips anything else (execute.rb:267), so multi-value/array fields cannot be carried (D-11). For Dropdown select fields the allowed options come from the CRM dropdown[] payload as resource_types: [{type: 'fixed', items: […]}], so both manual selection and AI fill are constrained to real options and the agent cannot invent a value.
Performance / data limitsInherit Qontak CRM API limits. The definitions lookup is a single uncached org-level call (RFC Decision 3.5) expected within the existing pipeline/stage lookup budget (target ≤ 1.5 s to populate the picker); no Qontak-Action-specific quota is introduced. Max custom fields addable per action follows the org's own field count (no separate cap).
Data lifecycleNo new persisted artifact beyond the action config itself — the custom-field mapping is stored in the action's node_registry config alongside the existing field mappings; no temp files, jobs, or queues. (4.7 auto-passes.)
Read/writeConfigure — builder roles that can edit AI Agent config (Admin / Bot Manager). Trigger — the AI Agent at runtime. Customers cannot configure or trigger.

7. Feature Changes

The delta is entirely additive on the create deal / create ticket config surface + their CRM POST bodies. Standard fields and all other actions are untouched.

Change ID: CHG-001 — A dedicated org-level "Add field" picker for custom fields (as-built)

FieldDetail
Change TypeNew component (AdditionalFieldSelector.vue) on the action config
SurfaceAI Agent config → create deal / create ticketAdd field
FigmaPending (prototyped in qontak-designerAddFieldPicker.vue; shipped UI is simpler — no groups, no badges)
ElementBeforeAfter (shipped)
Custom-field entry pointNone — only the hard-coded standard properties are configurableA separate Add field popover listing the org's custom fields, with a search box; already-added fields drop out of the list
Picker scopeN/AOrg-level — one list for the whole org, loaded once on mount from company_id; independent of the Pipeline field's value or mode
Picker visibilityN/ARendered only when the action's node_registries entry carries settings.option.enabled; hidden entirely once every field has been added (empty state "All fields added")
Added field rowN/ARenders per its normalized descriptor through the existing ActionIntegrationForm.vue switch, with the same Let AI decide / Set manually control as standard fields plus a per-row remove button

Not shipped from the v1.4 draft: the two-group Standard vs Custom picker with type badges, the "Choose a pipeline first" hint, and stale-field pruning on pipeline change — all three were consequences of the pipeline model that Decision 3.7 retired (D-1).

Change ID: CHG-002 — Scalar custom-field rendering (AI + manual modes) (as-built; array support deferred — D-11)

FieldDetail
Change TypeReuse of the existing field-rendering switch for normalized custom fields
SurfaceA custom field added to the create deal / create ticket config
ElementBeforeAfter (shipped)
Text / Text Area / Number / Percentage / URL fieldN/ARenders per its normalized descriptor (input+text, textarea, input+number) with the standard Let AI decide / Set manually toggle
Dropdown select fieldN/ARenders as a select over the CRM dropdown[] options (resource_types: [{type:'fixed', items}]); manual pick or AI fill, both constrained to those options
Array (Checklist / Multi-select / Tags) fieldN/ANot shipped — absent from the type whitelist, so never offered in the picker; the merge would also drop a non-scalar value (execute.rb:267). Deferred to the follow-up in D-11

7.1 Custom-field type → FE component mapping (normalization contract)

Principle: descriptor-driven, not per-type FE code. ActionIntegrationForm.vue is already an adaptive renderer branching on html.element/html.type. The BE lookup normalizes each supported CRM custom-field type into the existing PropertiesItem descriptor shape (build_field_property, lookup_resources.rb:202-224), and the FE renders it with the switch it already has — no new FE variant per CRM type. A new CRM field type = one row in the BE whitelist, not an FE release.

As-built whitelistADDITIONAL_FIELD_CONFIG (chatbot/app/core/repositories/node_resources/mekari_qontak_crm/lookup_resources.rb:33-41). Presence in this map is the type whitelist; the CRM type strings below are the exact keys.

CRM custom-field typeNormalized descriptor (type / html.element+html.type / extras)FE control (existing)
Single-line textstring / input + textMpInput
Text Areastring / textareaMpTextarea
Numbernumber / input + numberMpInput (number)
Percentagenumber / input + numberMpInput (number)
URLstring / input + textMpInput
Dropdown selectstring / select + is_rl: true, resource_types: [{type:'fixed', items}] from CRM dropdown[]select over fixed items
Any other type — incl. Date picker, Checkbox, Qontak user, Checklist / Multi-select / TagsNot renderedfilter_map skips the field (:187-190), so it never reaches the picker— (safe degradation)

Two v1.4 intentions did not survive to as-built: (a) the type coverage is narrower — date, checkbox, user-lookup and the whole array family are not whitelisted (D-11); (b) the skip is silent — no ai_agent_action_custom_field_type_unsupported telemetry was implemented, so an unsupported field is invisible to both the builder and the squad (D-13, OQ A-7).

Change ID: CHG-003 — CRM create body carries mapped custom fields

FieldDetail
Change TypeModified payload (BE)
SurfacePOST /api/v3.1/deals, POST /api/v3.1/tickets (and the update counterparts — D-15)
BeforeBody built only from node-registry standard properties matched by destination (execute.rb:186-208); custom-field args, if any, are dropped
After (shipped)Configured custom fields are collected by merge_additional_field_arguments (execute.rb:249-277) and appended as a single additional_fields[] array, each entry {id, name, value, value_name} where id is the CRM field id from the definitions lookup, name is the config key, and value_name falls back to cached_result_name for dropdowns. use_ai: true reads the agent's argument, otherwise the configured literal is used. Fields with a nil id or a non-scalar value are skipped; an empty result leaves the body untouched. No feature-flag gate (D-12, D-14)

8. API & Webhook Behavior

Plain-language behavior; exact request/response schema is for the RFC. All calls reuse the existing company-token (Bearer) auth + 401 refresh/retry.

#BehaviorEntity AffectedTriggered ByExpected BehaviorFailure Behavior
1Load the org's custom fieldsCRM additional-field definitions (read)The create deal / create ticket config mounts (no pipeline interaction needed)One org-level GET …/crm/additional_fields?object=deal|ticket&created_by=user via the resource-lookup endpoint with company_id only; whitelisted types are normalized into field descriptors, the rest skippedLookup fails/times out → the request is caught and the picker simply has no options (the Add field button is hidden). No retry affordance and no user-facing error shipped — see D-13 / OQ A-7
2Create deal with custom fieldsQontak CRM dealAgent function-calls qontak_crm_deal_create with configured custom fieldsExisting body (pipeline/stage/name/owner/tags + customer_contact_association lead lookup) plus an additional_fields[] array of {id, name, value, value_name}; dropdown values constrained to the CRM options401 → refresh + retry (unchanged); CRM rejects an invalid/missing-required custom field → error surfaces to runtime, no partial silent drop
3Create ticket with custom fieldsQontak CRM ticketAgent function-calls qontak_crm_ticket_create with configured custom fieldsExisting ticket body plus the same additional_fields[] array401 → refresh + retry (unchanged); CRM rejects invalid/required-missing custom field → error surfaces to runtime
4Fill a custom field (runtime)The custom field's value on the write bodyAgent evaluating the conversation for a custom field in Let AI decideAgent returns a single scalar value; for a dropdown field the value is one of the CRM optionsValue absent or non-scalar → the field is dropped from additional_fields[] (silently, execute.rb:266-267); if CRM requires it, the create fails and the validation error surfaces to runtime
5Update deal / ticket with custom fieldsQontak CRM deal / ticketAgent function-calls qontak_crm_deal_update / qontak_crm_ticket_updateAs-built: the same merge runs for the update actions (WRITE_ACTION_TYPES), so any custom fields present in that action's config are sentSame as create; whether a builder can configure them there depends on the action's node_registries seed (D-15, OQ A-6)

As-built evidence (verified 2026-07-27 against chatbot@master + chatbot-fe@main): definitions lookup node_resources/mekari_qontak_crm/lookup_resources.rb:18-19 (paths), :33-41 (type whitelist), :183-200 (fetch_additional_fields — note it logs Rollbar.error "Failed to fetch <object> additional fields", not the ai_agent_action_custom_field_lookup_failed event this PRD specified), :202-224 (descriptor + fixed-items normalizer). Merge node_executions/nodes/mekari_qontak_crm/execute.rb:249-277, invoked at :90 for WRITE_ACTION_TYPES (:33-39). Unchanged parent behaviour: lead lookup execute.rb:262-276 (enrich_body_for_deal_create), auth node_resources/mekari_qontak_crm/crm_http_client.rb:45-77 (401 refresh/retry + Bearer). FE: modules/shared/ai-agent-actions/composables/useAdditionalFieldLookup.ts:49-59 (company_id-only payload), components/forms/AdditionalFieldSelector.vue (picker), components/forms/ActionIntegrationForm.vue:330-338,371-375. The array primitives the v1.4 draft planned to reuse (PropertiesItem.type:'array', html.element:'select multiple'ActionMultiSelectField) are NOT wired to custom fields — no array type reaches the picker (D-11) — and ActionMultiSelectField itself now renders via MpInputTag, the control D-3 rejected (D-16).

9. System Flow + User Stories + ACs

9.1 System Flow

Flow: Builder maps the org's custom fields → agent fills them at runtime → BE appends them to the CRM write body · Type: Config journey + runtime fill + API merge

  1. Builder opens the create deal / create ticket action config. No pipeline prerequisite.
  2. On mount the config loads the org's custom-field definitions in one call (company_id only); whitelisted types become field descriptors, others are skipped silently.
  3. Builder clicks Add field → a searchable popover lists the org's custom fields not yet added; builder adds one or more.
  4. For each added field the builder chooses Let AI decide or Set manually; the row renders per the field's descriptor (dropdown → select over CRM options, number → number input, text → input/textarea).
  5. Builder saves; the mapping is stored in the action's node_registry config. No feature flag is consulted (D-12).
  6. At runtime the agent function-calls the action; for AI-mode fields it returns a scalar value (dropdowns: one of the CRM options).
  7. BE builds the standard body, then appends additional_fields[] ({id, name, value, value_name} per mapped field) and sends the authenticated write to Qontak CRM.
  8. Decision — value missing or non-scalar: the field is dropped from additional_fields[]; if every field drops out, the body is sent exactly as it would be without this feature.
  9. Decision — unsupported CRM type: the field never appears in the picker (whitelist miss), with no telemetry and no builder-facing notice (D-13).
  10. Failure branch: definitions lookup fails → the error is caught, the picker has no options and its Add field button is hidden (no retry UI shipped). CRM rejects an invalid or required-missing custom field → error surfaces to runtime, no silent partial write.

📊 System Flow — Custom fields on create deal / ticket

sequenceDiagram
participant B as Builder
participant Cfg as AI Agent Config (FE)
participant CF as CRM Additional-field Source (org-level)
participant AG as AI Agent (runtime)
participant EX as Action Executor (mekari_qontak_crm)
participant CRM as Qontak CRM API
B->>Cfg: Open create deal / create ticket config
Cfg->>CF: Load org custom fields (company_id only)
alt lookup succeeds
CF-->>Cfg: whitelisted fields (+descriptor, dropdown options)
B->>Cfg: Add field → pick from searchable list, choose AI / manual
else lookup fails
CF-->>Cfg: error (caught)
Cfg-->>B: No options — "Add field" hidden (no retry UI)
end
B->>Cfg: Save (no feature flag)
AG->>EX: function-call create/update deal|ticket (runtime)
EX->>CRM: POST body + additional_fields[] {id, name, value, value_name}
CRM-->>EX: 200 OK
alt 401 Unauthorized
EX->>CRM: refresh company_token + retry
else CRM validation error (invalid / required-missing custom field)
CRM-->>EX: 4xx
EX-->>AG: error surfaced to runtime (no partial write)
end

9.2 User Stories

User StoryImportanceMockupTechnical NotesAcceptance Criteria
[QACF-S01] — Org-level custom-field picker (as-built)

As a Bot Manager, I want to add my org's CRM custom fields to the create deal/ticket action, so that the agent creates complete CRM records.
Must Have — ✅ shippedFigma: Pending — shipped as AdditionalFieldSelector.vue (simpler than the qontak-designer AddFieldPicker.vue prototype)Data Fields:
company_id (number, required) — the only lookup parameter; no pipeline_id (D-1)
custom_fields[] (list) — each { id, name, display_name, type, html, resource_types? } normalized by the BE from the CRM additional-fields response

Before-After Behavior: Before, only hard-coded standard properties were configurable (action-constants.ts); after, a separate Add field popover lists the org's custom fields, independent of the Pipeline field.
— Happy Path —
• AC-1: Given the action's node_registries entry has settings.option.enabled, when the builder opens the config, then the org's custom fields load in one company_id-scoped call and the Add field control is offered — regardless of whether Pipeline is set manually or left on Let AI decide.
• AC-2: ⛔ NOT SHIPPED (D-11)(drafted: array-typed fields carry a distinct type badge). No array type reaches the picker and the shipped list carries no type badges.
• AC-3: Given the builder picks a custom field, when it is added, then it appears as a config row with the standard Let AI decide / Set manually control and a remove button, and it drops out of the picker's remaining options.
• AC-4: REPLACED (D-1) — Given Pipeline is on Let AI decide or unset, when the builder opens Add field, then the full org custom-field list is still shown (no gate, no "choose a pipeline first" hint) — custom fields do not depend on pipeline selection.
• AC-5: Given the org has a custom field whose CRM type is outside the whitelist (Section 7.1), when the definitions load, then that field is excluded from the picker and never mis-rendered. (⚠️ The ai_agent_action_custom_field_type_unsupported log this AC also required was not implemented — D-13 / OQ A-7.)
• AC-6: Given every available custom field has already been added, when the builder looks for Add field, then the control is hidden and the picker's empty state reads "All fields added".

— Error / Unhappy Path —
• ERR-1: ⚠️ PARTIALLY SHIPPED (D-13) — Given the definitions lookup fails or times out, when the config loads, then the error is caught, the standard fields stay fully usable, and no custom fields are offered. (The drafted retry affordance and the ai_agent_action_custom_field_lookup_failed event were not built; the BE logs Rollbar.error "Failed to fetch <object> additional fields" instead — OQ A-7.)

— Permission Model —
• CAN: Admin / Bot Manager · CANNOT: roles without AI Agent edit rights · Unauthorized: picker not shown.
• Reversibility: adding/removing a field is reversible in config before save; no CRM record is touched at config time.

— UI States —
• Loading: definitions loading (picker not yet populated) · Empty: no custom fields returned → Add field hidden · All added: "All fields added" · Search miss: "{query}" not found · Success: searchable list rendered
[QACF-S02] — Scalar custom field: AI-fill and manual (as-built; multi-value withdrawn to a follow-up — D-11)

As a Bot Manager, I want to map a custom field so the agent (or I) can set its value, so that fields like SLA tier, region or account URL are populated on agent-created records.
Must Have — ✅ shipped for scalarsFigma: Pending — shipped via the existing ActionIntegrationForm.vue field switchData Fields:
use_ai (boolean) — true = Let AI decide, false = Set manually (execute.rb:259)
value (string | number | boolean) — manual literal or the agent's argument
cached_result_name (string, optional) — the human-readable label sent as value_name for dropdown fields
id (number, required) — CRM field id from the definitions lookup; a field without it is dropped

Before-After Behavior: Before, custom fields could not be mapped at all; after, a mapped custom field renders per its normalized descriptor with the same Let AI decide / Set manually control as every standard field.
— Happy Path —
• AC-1: REPLACED (D-11) — Given a Dropdown select custom field in Set manually, when the builder opens its value control, then it offers exactly the CRM dropdown[] options and stores the chosen option (single value).
• AC-2: REPLACED (D-11) — Given a custom field in Let AI decide, when configured, then no manual value control is required and the value is taken from the agent's argument at runtime.
• AC-3: REPLACED (D-11) — Given a Dropdown select field in Let AI decide, when the agent runs, then the value sent is a single option value and value_name carries its label.
• AC-4: Given a text / textarea / number / percentage / URL custom field, when added, then it renders per its type (input+text, textarea, input+number) with the standard Let AI decide / Set manually control.
• AC-5: Given any mapped custom field, when the agent supplies a non-scalar value (array/object), then that field is omitted from additional_fields[] rather than sent malformed (execute.rb:267).

— Error / Unhappy Path —
• ERR-1: Given a mapped custom field whose resolved value is nil, when the action runs, then the field is skipped and the rest of the body is unaffected; if all fields are skipped the body is byte-identical to the no-custom-field case.

— Permission Model —
• CAN: Admin / Bot Manager · CANNOT: roles without AI Agent edit rights · Unauthorized: control not shown.

— UI States —
• Loading: dropdown options loading · Empty: dropdown with no options → no fixed items · Success: control rendered per descriptor
[QACF-S03] — Merge mapped custom fields into the CRM write (as-built)

As the AI Agent runtime, I want the configured custom fields sent with the deal/ticket write call, so that the created record carries the org's custom-field values.
Must Have — ✅ shippedFigma: N/A — backendData Fields:
additional_fields[] — array of {id, name, value, value_name} appended to the body (execute.rb:271)
• existing standard body fields (unchanged)

Before-After Behavior: Before, extract_arguments_by_destination (execute.rb:186-208) builds the body only from node-registry standard properties and drops custom-field args; after, merge_additional_field_arguments (:249-277) appends the additional_fields[] array for every action in WRITE_ACTION_TYPES.
— Happy Path —
• AC-1: REPLACED (D-12, D-14) — Given custom fields are mapped, when the agent calls qontak_crm_deal_create / qontak_crm_ticket_create, then the body carries an additional_fields[] array with one {id, name, value, value_name} entry per mapped field, alongside the unchanged standard fields — with no feature-flag check (the flag was never implemented).
• AC-2: REPLACED (D-11) — Given a mapped field's value is a scalar (string / number / boolean), when merged, then it is sent as-is; a non-scalar value is skipped instead of being serialized.
• AC-3: REPLACED (D-12) — Given an action config with no custom fields mapped (every existing pre-feature config), when the agent calls the action, then additional_fields is absent and the body is byte-identical to the parent behaviour.
• AC-4: Given the existing customer_contact_association and {{customer_name}} behavior, when a create runs with custom fields, then that existing behavior still applies unchanged (execute.rb:262-276 (enrich_body_for_deal_create)).
• AC-5: NEW (D-15) — Given qontak_crm_deal_update / qontak_crm_ticket_update, when the agent calls them with custom fields present in the config, then the same additional_fields[] merge applies (the executor gates on WRITE_ACTION_TYPES, not on create-only).

— Error / Unhappy Path —
• ERR-1: Given CRM returns 401, when the create runs, then the company token is refreshed and retried (unchanged).
• ERR-2: Given CRM rejects the create because a custom-field value is invalid or a required custom field is missing, when it runs, then no partial/silent write occurs and the validation error surfaces to the runtime.

— Permission Model —
• CAN: the AI Agent at runtime · CANNOT: customers cannot trigger · Unauthorized: not exposed where the base action is off.
• Reversibility: a created record cannot be undone by the agent — editable/deletable only in Qontak CRM (unchanged from parent).

— UI States —
• N/A — backend/runtime behavior
[QACF-S04] — Prune stale custom fields on pipeline change

WITHDRAWN 2026-07-27 (D-1) — custom fields are org-level, so a pipeline change can never make a mapped field stale. Nothing was built and nothing needs to be. Its Jira Story should be closed Won't Do, not left open.
Withdrawn— Withdrawn —
• AC-1: ⛔ withdrawn — no pipeline-owned field set exists to diff.
• AC-2: ⛔ withdrawn — the picker header carries no pipeline label.
• ERR-1: ⛔ withdrawn — no pruning path exists, so no orphaned mapping can be produced this way.
[QACF-S01-NEG] — Scope guard rails (Guard Rail — from Non-Goals)

As a builder, I should not find this custom-field surface where it isn't in scope.
Guard Rail— Negative Scenarios —
• NEG-1: ⚠️ BE-CONTRADICTED (D-15) — Given the update deal / update ticket config, when the builder looks for the custom-field picker, then it is offered only if that action's node_registries entry carries settings.option.additional_field; the BE merge itself already accepts custom fields on both update actions, so this guard rail is a seed/config guarantee, not a code guarantee. QA must verify against the live node_registries rows (OQ A-6).
• NEG-2: REPLACED (D-1) — Given Pipeline is on Let AI decide, when the builder opens Add field, then the org's custom fields are listed (no gate) — the previous "choose a pipeline first" expectation is void.
• NEG-3: REPLACED (D-1) — Given the picker, when the builder compares two pipelines, then both show the same org-level custom-field list — there is no per-pipeline subset to withhold.
• NEG-4: Given the picker, when the builder wants to create/edit a custom-field definition, then it is not possible here — definitions live in Qontak CRM settings → customization (Non-Goal 2).
• NEG-5: NEW (D-11) — Given an org with Checklist / Multi-select / Tags / Date / Checkbox custom fields in CRM, when the builder opens Add field, then those fields are not listed (whitelist miss) and no error explains their absence.

Dependencies: [QACF-S02] depends on [QACF-S01] (the picker). [QACF-S03] (BE merge) depends on the config produced by S01/S02 and on the BE TECH-RFC. [QACF-S04] is withdrawn (D-1) — close its Jira Story as Won't Do. Story→Jira: one Story per QACF-S0x under child Epic BOT-4662 (see ADR 0001 for the Story-family remediation).

🧪 Test Coverage Matrix — [QACF-S01]

DimensionCoverageNotes
Boundary values✅ definedAC-6 all-added empty state; ⚠️ QA: org with 0 custom fields (Add field hidden) and very large custom-field count (popover scroll)
State transitions✅ definedAC-1/AC-3 picker → added row; AC-4 pipeline-independent load
Data validation⚠️ TBD⚠️ QA: custom-field label/name with special chars; two CRM fields sharing a name (the picker de-dupes by name, so a collision would hide one)
Concurrency⚠️ TBD⚠️ QA: action saved while the definitions lookup is still in flight (fields not yet loaded)
Network/timeout⚠️ partialERR-1 lookup failure degrades silently — QA must assert the standard fields stay usable and that no retry/error UI is expected (D-13)

🧪 Test Coverage Matrix — [QACF-S02]

DimensionCoverageNotes
Boundary values⚠️ partialAC-5 non-scalar dropped; ⚠️ QA: dropdown with a single option; very long text value; numeric field given a non-numeric AI value
State transitions✅ definedAC-2 AI ↔ AC-1 manual mode toggle
Data validation✅ definedAC-1/AC-3 dropdown constrained to CRM options; AC-5 non-scalar skipped; ERR-1 nil skipped
Concurrency⚠️ TBD⚠️ QA: CRM dropdown options edited between config-save and runtime (stale value/value_name)
Network/timeout⚠️ TBD⚠️ QA: definitions load timeout on a config that already has fields mapped

🧪 Test Coverage Matrix — [QACF-S03]

DimensionCoverageNotes
Boundary values⚠️ partialERR-1 nil skipped; AC-5 update actions; ⚠️ QA: max additional_fields[] length CRM accepts; field id present but deleted in CRM
State transitions✅ definedAC-3 no fields mapped → parent body; AC-1 fields mapped → additional_fields[] appended
Data validation✅ definedAC-2 non-scalar skipped; ERR-2 CRM rejects invalid/required-missing custom field
Concurrency⚠️ TBD⚠️ QA: agent fires create twice in rapid turns (inherits parent QACT-S01 concern)
Network/timeout✅ definedERR-1 401 refresh + retry (unchanged)

10. Rollout

As-built: there is no staged flag rollout. The ai_agent_action_custom_fields flag was descoped (D-12), so the effective release control is the node_registries settings.option.additional_field seed per action — once seeded, the picker is live for every org already entitled to that action. The three stages below are recorded as the drafted plan and what actually applied:

  1. Internal QAenable the flag on an internal test org → verified on an internal org by seeding the action's node_registries option. Config → runtime fill → CRM merge exercised for scalar + dropdown fields only (no array types exist to test — D-11).
  2. Closed Betaskipped in practice: with no per-org flag there was no mechanism to limit exposure to 3–5 design partners. Recorded as a deliberate consequence of D-12, not an oversight to repeat.
  3. GAcompleted 2026-07-27: enabled for all clients/users with the create-deal/ticket entitlement, effective at seed time.

GA record (from v1.5, corrected). CRM Custom Fields reached 100% rollout to all clients/users on 2026-07-27; the implementation is recorded in the Chatbot BE 1.324.0 and Hadiningbot FE 3.8.3 release records. ⚠️ v1.5 also stated "the feature flag remains the operational control for emergency rollback" — that is not true and is corrected here: no such flag exists in chatbot or chatbot-fe (D-12). Emergency rollback today requires a code revert and deploy, not a console toggle — anyone relying on the flag during an incident would lose time discovering this. Fixing the capture note (2026-07-27-crm-custom-fields-general-availability.md) is part of OQ A-8.

Migration transition window: the only stored artifact is the action config. Existing create deal/create ticket actions configured before this shipped carry no custom-field mapping, so merge_additional_field_arguments returns the body untouched and they behave exactly as before (AC-3 of [QACF-S03]). No data backfill and no migration of existing records is required; old and new configs coexist safely.

Rollback: with no flag, rollback means un-seeding the action's node_registries option (hides the picker; already-saved mappings would then be sent by the executor but no longer be editable) or reverting the BE merge. Worth deciding deliberately before the next action gains custom fields — see OQ A-8.

11. Observability

As-built: none of the five events below shipped (D-13). The only telemetry in the shipped path is a generic Rollbar.error(e, message: "Failed to fetch <deal|ticket> additional fields", organization_id:) in the lookup's rescue (lookup_resources.rb:198). There is no adoption signal, no merge signal, no rejection signal, and unsupported field types are dropped silently. Consequence: every metric in Section 12 is currently unmeasurable and the alert below does not exist. Tracked as OQ A-7.

EventTriggerKey propertiesShipped?
ai_agent_action_custom_field_addedBuilder adds a custom field in configaction_type (deal/ticket), field_type, mode (ai/manual) — (pipeline_id/is_array dropped: D-1, D-11)⛔ No
ai_agent_action_custom_field_lookup_failedOrg custom-field lookup errors/times outorganization_id, object (deal/ticket), latency_ms, error⚠️ Partial — generic Rollbar error only, no event name, no latency
ai_agent_action_custom_field_mergedCustom fields sent with a CRM write at runtimeaction_type, field_count⛔ No
ai_agent_action_custom_field_create_rejectedCRM rejects a write due to a custom-field valueaction_type, reason (invalid / required-missing)⛔ No
ai_agent_action_custom_field_type_unsupportedLookup returns a CRM field type outside the whitelist (S7.1) — field excluded from pickerorganization_id, raw_type, field_name⛔ No — silent filter_map skip

Dashboard owner: BOT — Chatbot & AI Squad. Alert (not yet live): if ai_agent_action_custom_field_create_rejected / total custom-field writes > 5% over any rolling day, page the squad (signals an options or required-field contract problem).

11.1 Post-Launch Monitoring Cadence

Weekly review for the first 4 weeks post-GA. If the custom-field create-rejection rate exceeds 5% week-over-week, or lookup-failure rate exceeds 2%, investigate immediately before widening rollout. Blocked today — the events this cadence reads do not exist (D-13); until OQ A-7 is closed, post-launch monitoring is limited to Rollbar noise on the lookup and manual spot-checks of created records.

12. Success Metrics

Baselines are N/A — and targets remain unmeasurable post-GA because the instrumentation in Section 11 did not ship (D-13). Closing OQ A-7 is the precondition for reporting any row below.

CategoryMetricBaselineTargetMeasurable today?
Adoption% of active create deal/create ticket actions that map ≥1 custom fieldN/A (0 — not possible before this shipped)≥ 40% within 60 days of GA⚠️ Only by querying node_registry action configs directly (no event)
QualityCustom-field write-rejection rate (rejected ÷ custom-field writes)N/A< 2% steady-state (alert at 5%)⛔ No
EfficiencyReduction in manual custom-field back-fill on agent-created records (design-partner self-report + CRM edit-after-create rate)Manual back-fill on ~100% of custom-field records≥ 70% reduction for beta orgs within 30 days⚠️ Self-report only (no closed beta ran — D-12)
ReliabilityCustom-field definitions lookup success rateN/A≥ 99% (≤ 1.5 s p95)⚠️ Failure count only, via Rollbar; no latency

13. Launch Plan & Stage Gates

StageAudienceDurationSuccess gate
Internal QA1 internal org with org-level custom fields~1 week✅ Met for the shipped surface — config → runtime → CRM merge verified for scalar + dropdown fields; no-mapping path byte-identical to parent. (Array coverage void — D-11.)
Closed Beta3–5 design-partner orgs2–3 weeksNot run — no per-org gate existed after the flag was descoped (D-12)
GAAll orgs w/ create-deal/ticket entitlement⚠️ Reached with two gates unmet: monitoring + alert are not live (D-13) and the beta gate above was skipped. Recorded rather than back-dated; OQ A-7 is the remediation

14. Dependencies

DependencyOwnerWhat it providesBlocking
BE payload merge + custom-field source (TECH-RFC)BOT — Chatbot & AI Squad (chatbot)Delivered — (a) merge_additional_field_arguments appends additional_fields[] (execute.rb:249-277); (b) per-action exposure via the node_registries settings.option.additional_field seed; (c) the org-level definitions lookup + type whitelist + descriptor normalizer (lookup_resources.rb:18-19,33-41,183-224). (Not delivered from the RFC: the flag rollout class — Chunk 1 / D-12.)YES — was blocking; now closed
FE picker + field rendering (chatbot-fe)BOT — Chatbot & AI SquadDeliveredAdditionalFieldSelector.vue (searchable Add-field popover) + useAdditionalFieldLookup.ts + per-row rendering/removal in ActionIntegrationForm.vue. (Not delivered: Standard/Custom grouping, type badges, array controls.)YES — was blocking; now closed
Qontak CRM API accepts custom fields on writeQontak CRM (platform)Confirmed 2026-07-15 (RFC A-1): definitions via GET /api/mobile/v2.8/crm/additional_fields?object=deal|ticket&created_by=user (org/team_id-scoped); values sent as additional_fields[] entries keyed by field id (+ name, value, value_name)YES — closed
Design — production Figma framesDesignFrame-level specs for the shipped Add-field popover + field rowsNO — shipped against the prototype; Figma still outstanding as as-built documentation
qontak-designer prototype (reference)PM / DesignWorking reference for the picker — design-wireframes/ai-agent-custom-fields/. Stale in two ways: it models pipeline scoping and array rows, neither of which shipped (D-1, D-11)NO — reference only; flag as stale for the designer

📊 Dependency Graph — Custom fields on create deal / ticket

graph LR
F[Custom fields on create deal/ticket]
F -->|BLOCKING - closed| BE[BE merge + org-level lookup TECH-RFC]
F -->|BLOCKING - closed| FE[FE Add-field picker + field rows]
F -->|BLOCKING - closed| CRMAPI[CRM API - additional_fields by field id]
F -->|non-blocking| DES[Production Figma frames - outstanding]
F -->|non-blocking| PROTO[qontak-designer prototype - stale]

15. Key Decisions + Alternatives Rejected

Decisions Made

#DecisionRationaleDate
D-1AMENDED 2026-07-27 — custom fields are org-level, not pipeline-scoped. The picker lists the org's whole additional-field set and is independent of the Pipeline field. (Superseded: "custom fields are scoped to the selected pipeline".)The CRM contract, verified 2026-07-15, settles it: GET /api/mobile/v2.8/crm/additional_fields?object=deal&created_by=user returns fields scoped to team_id with no pipeline parameter, so every pipeline in an org shares one definition set. A pipeline filter would have withheld data the builder is always entitled to see, and CRM offers no server-side way to apply one. PM ratified RFC Decision 3.7 Option A — closes RFC open question A-4.2026-07-27
D-2WITHDRAWN 2026-07-27 — no "Pipeline set manually" prerequisite. The definitions load on mount from company_id; Let AI decide on Pipeline no longer gates anything. (Superseded: "custom fields require Pipeline set manually, gated with a hint otherwise".)Follows directly from D-1 — with org-level fields there is nothing a concrete pipeline would resolve. Removing the gate deletes a whole class of UX state (hint, prune, re-scope) rather than moving it.2026-07-27
D-3VOID 2026-07-27 (see D-11, D-16) — array fields were to reuse the existing prod multi-select primitive (ActionMultiSelectField, html.element:'select multiple', type:'array'), and MpInputTag was explicitly rejectedDoubly overtaken: no array type was whitelisted so nothing was wired to it (D-11), and the primitive itself was re-implemented on the rejected MpInputTag (D-16). Do not treat this row as the design of record for the follow-up without re-deciding the control.2026-07-13
D-4NARROWED 2026-07-27 — AI-mode values are constrained to allowed options for Dropdown select fields, via resource_types: [{type:'fixed', items}] built from the CRM dropdown[] payloadThe constraint principle shipped, but only where CRM supplies an option list. Free-text/number/URL custom fields accept whatever the agent produces, bounded only by the scalar check at merge time.2026-07-27
D-5SUPERSEDED 2026-07-27 (see D-12) — gate behind a new flag ai_agent_action_custom_fields (default OFF)The flag was specified in the PRD and in RFC Decision 3.4 + Chunk 1, then descoped during delivery.2026-07-13
D-6Scope to create actions onlyHighest-value, unblocks the current gap; update actions are a clean fast-follow reusing the same picker + merge.2026-07-13
D-7Type→component mapping is a BE normalization contract, not per-type FE codeTenants define custom fields in CRM first, so the variant space is open-ended. The BE lookup normalizes every CRM type into the existing PropertiesItem descriptor shape (Section 7.1) and the FE renders it with the adaptive switch it already has (ActionIntegrationForm.vue branches on html.element/html.type). A new CRM type = one normalizer row, not an FE release. Unknown types degrade safely: excluded from the picker + telemetry, never mis-rendered.2026-07-13
D-8R-1 mitigation confirmed by PM: required custom fields must be surfaced/flagged in the picker so the builder is forced to map them; runtime rejection (S03/ERR-2) surfaces cleanly rather than silently droppingAligns with the drafted mitigation — resolves R-1. Exact required-field detection mechanism still confirmed in the RFC (depends on A-1's CRM API answer).2026-07-13
D-9R-2 confirmed: no artificial cap on AI array cardinality — the agent returns as many valid allowed-option values as the conversation supportsPM confirmed the default; constrained-to-allowed-options (D-4) already bounds the risk. Revisit only if Beta shows real over-population.2026-07-13
D-10PARTLY OVERTAKEN 2026-07-27 (see D-15)update deal / update ticket custom fields were declared a separate future PRDStill the right split for the builder-facing surface and its ACs, but the shipped BE merge already covers both update actions, so the "future PRD" now inherits working plumbing and only needs the config surface + ACs.2026-07-13
D-11Array-typed custom fields (Checklist / Multi-select / Tags) did NOT ship — deferred to a follow-up. The as-built whitelist is scalar-only (Single-line text, Text Area, Number, Percentage, URL, Dropdown select); Date picker, Checkbox and Qontak user are also absent. The merge additionally hard-rejects non-scalar values (execute.rb:267).Recorded, not rationalised: array support was a Must Have in v1.4 ([QACF-S02]) and one of the two headline capabilities of this adjustment. Delivery whitelisted the six types CRM returned in the confirmed payload and left the array family out; the FE primitives to render it exist and are unused. This is a real scope reduction the PRD must show, and it needs a PM decision (OQ A-5) — either a follow-up PRD or an explicit permanent non-goal.2026-07-27
D-12The ai_agent_action_custom_fields flag did NOT ship — the feature is ungated. No rollout/ai_agent_action_custom_fields.rb, no flag check in chatbot or chatbot-fe. Release control is the per-action node_registries settings.option.additional_field seed.RFC Chunk 1 specified the rollout class; the corresponding task (BOT-4668) was closed Won't Fix (0.5 md descoped). Consequences accepted here: no closed beta was possible, and rollback is un-seeding or a revert (OQ A-8). Low blast radius because a config with no mapped fields is byte-identical to the parent behaviour.2026-07-27
D-13No custom-field telemetry shipped. None of the five Section 11 events exist; unsupported types are skipped silently; the lookup's only signal is a generic Rollbar.error.Recorded as a gap, not a choice: it makes every Section 12 metric unreportable and leaves the "unsupported CRM type" path invisible to both builder and squad — exactly the failure mode AC-5 was written to prevent. Remediation is OQ A-7.2026-07-27
D-14Payload shape: one additional_fields[] array of {id, name, value, value_name}, keyed by the CRM field id, appended to the write body — not individual top-level keys as v1.4 assumedMatches what the CRM write API accepts (RFC A-1, confirmed 2026-07-15). value_name carries the human-readable label for dropdown values (cached_result_name), which keeps the payload self-describing for CRM-side display.2026-07-27
D-17The custom-field picker is not AI-Agent-only — it also renders on the bot-automation action drawers. CreateActionForm.vue calls the same useAdditionalFieldLookup(settings) and is mounted by modules/bot-automation/components/drawers/NewActionDrawer.vue and EditCapabilityActionDrawer.vue, each passing tool.node_registry.settings unchanged.Verified 2026-07-27. Consequence: exposure is governed per node_type only — one seed lights the field up on every surface that renders that action, so "AI Agent only" is not expressible in the current design. This PRD scoped itself to the AI Agent config drawer and never specified the bot-automation surface (no ACs, no QA, no design). Tracked as OQ A-11.2026-07-27
D-16ActionMultiSelectField was re-implemented on MpInputTag (BOT-4724) — reversing D-3's explicit rejection of it. The component file name survives, but its internals are now MpInputTag (ActionMultiSelectField.vue:2,25), and three follow-up fix commits (353c6692, eedbc7d4, 5af88261) followed the swap.Verified 2026-07-27. Matters for two reasons beyond bookkeeping: (a) D-3 and RFC FE-D4 both named MpInputTag as the rejected option, so a rejected alternative shipped without either doc being amended; (b) the component is shared with the already-shipped deal tags field, so the blast radius is wider than this PRD. It does not affect the custom-field path today only because no array type reaches it (D-11). Raised by the completion-alignment audit (Direction B #4) and re-verified independently here.2026-07-27
D-15The BE merge is wired for all four write actions (qontak_crm_deal_create, qontak_crm_deal_update, qontak_crm_ticket_create, qontak_crm_ticket_updateWRITE_ACTION_TYPES, execute.rb:33-39), not create-onlyEngineering chose the general gate, which is cheap and forward-compatible with the update-actions follow-up. The product consequence must be understood: the create-only boundary (Non-Goal 1 / NEG-1) is now enforced only by which actions get the node_registries seed. PM to confirm the seed state per action (OQ A-6).2026-07-27

Alternatives Rejected

AlternativeWhy rejectedDate
Show a union of custom fields across all pipelinesrejection reversed 2026-07-27 (D-1)The original rejection assumed per-pipeline definitions. There is no union to build: CRM defines one org-level set, so the "union" is the correct list and the pipeline-scoped alternative was the wrong model.2026-07-27
Infer custom fields when Pipeline is AI-decidedmoot 2026-07-27 (D-1/D-2)Nothing is inferred because nothing is pipeline-dependent: the same list loads whatever the Pipeline field's mode.2026-07-27
Keep the pipeline gate anyway (RFC Decision 3.7 Option B)Would have kept the PRD unamended at the cost of shipping a knowingly wrong model — withholding fields the builder can always use, with no CRM-side filter to implement it honestly (client-side only). Rejected in favour of correcting the PRD.2026-07-27
Back-date the PRD to pretend arrays, the flag and the telemetry were never in scopeThey were: arrays and the flag were Must-Have/committed in v1.4, and the telemetry was the basis of every success metric. Silently rewriting scope would erase the delivery signal (a Must-Have dropped without a decision) and leave the metrics section quietly unmeasurable. Recorded as D-11 → D-13 with owners instead.2026-07-27
Free-text values for array fieldsWould let the agent produce values CRM rejects (invalid options), reintroducing the failure this PRD removes. Constrained-to-allowed-options is required.2026-07-13
Build a bespoke multi-value componentUnnecessary — the array primitives already exist in chatbot-fe; a new component adds maintenance and inconsistency.2026-07-13
Fold this into the parent as-built PRDThe parent records shipped behavior; this is net-new capability with its own ACs, RFC, and rollout — it belongs in its own ADJUSTMENT PRD under the same Epic.2026-07-13
Cap AI array cardinality with a per-field maxPM confirmed (D-9) no artificial cap is needed at launch — constrained-to-allowed-options already bounds the risk; a cap would need arbitrary tuning with no evidence yet.2026-07-13
Silently expand this PRD's scope to cover update actionsPM confirmed (D-10) update actions stay a separate future PRD — keeps this ADJUSTMENT's delta precise and its regression surface narrow (CB-A.2).2026-07-13

16. Open Questions

TypeItemOwnerDeadlineMitigation (required for Risk)
Open QuestionRESOLVED 2026-07-15 (RFC A-1; see D-1, D-14)A-1 — CRM custom-field API contract: (a) definitions come from GET /api/mobile/v2.8/crm/additional_fields?object=deal|ticket&created_by=user and are org-level (team_id), with no per-pipeline endpoint; (b) values are sent by field id inside an additional_fields[] array. The array-payload half of the question is moot for now — no array type shipped (D-11).BE + Qontak CRMBefore RFC
RiskRESOLVED 2026-07-13 (see D-8)R-1 — Required custom fields: if a pipeline has a required custom field and neither AI nor manual supplies it, CRM rejects the create at runtime.PM + BEBefore BetaSurface a pipeline's required custom fields in the picker (e.g. auto-added / flagged) so the builder must map them; runtime error (S03/ERR-2) surfaces cleanly rather than silently dropping. PM confirmed this mitigation (D-8); exact required-field detection mechanism still confirmed in the RFC (depends on A-1).
RiskRESOLVED 2026-07-13 (see D-9)R-2 — AI array cardinality: unbounded AI value selection could over-populate an array field.PMBefore BetaConfirmed: AI returns as many valid allowed-option values as the conversation supports, no artificial cap. Constrained-to-allowed-options (D-4) bounds the blast radius.
AssumptionA-2 — Lookup performance: the org-level custom-field lookup returns within the existing pipeline/stage lookup budget (≤ 1.5 s p95). Still unverified — no latency telemetry shipped (D-13), and the lookup is uncached by design (RFC Decision 3.5).BEPost-GAIf it exceeds budget, add the short-TTL Redis cache the RFC pre-approved. Cannot be assessed until A-7 lands.
Open QuestionRESOLVED 2026-07-13 (see D-10, D-15)A-3 — Update actions fast-follow: confirm update deal / update ticket custom fields are a follow-up PRD (reusing this picker + merge), not this one.PMPost-GAConfirmed — separate future PRD. Note the BE half already ships (D-15), so the follow-up is config-surface + ACs only.
Open QuestionRESOLVED 2026-07-27 (see D-1, D-2)A-4 (raised by the BE RFC) — org-level vs pipeline-scoped: should the picker show custom fields without requiring a pipeline selection, and is PRD D-1/D-2 to be amended?PMBefore FE sprintRatified: org-level, no pipeline gate. D-1 amended, D-2 withdrawn, [QACF-S04] withdrawn, AC-4 / NEG-2 / NEG-3 replaced, Non-Goals 5–6 rewritten. Mirrored back into the RFC (A-4 closed).
Decision neededA-5 — Array custom fields (D-11): arrays were a v1.4 Must Have and did not ship. Choose: (a) follow-up PRD to extend the whitelist + wire the existing array primitives (also picks up Date / Checkbox / Qontak user), or (b) declare arrays a permanent non-goal for the AI Agent and tell CRM-heavy orgs so explicitly.PMNext planning cycleInterim expectation-setting: the picker silently omits those fields today, so a builder cannot tell they are unsupported (NEG-5). At minimum, document the supported-type list in the customer-facing feature guide.
Decision neededA-6 — Exposure surface (D-15, D-17): the exposure question is wider than create-vs-update. It is 4 node types × 3 UI surfaces, all driven by the same per-node-type seed with no per-surface control: node types qontak_crm_deal_create / qontak_crm_deal_update / qontak_crm_ticket_create / qontak_crm_ticket_update; surfaces = AI Agent action drawer, and the two bot-automation drawers (D-17). Confirm which node types carry settings.option today. This is checkable without DB access — see the runbook below.PM + BEImmediateIf a node type is seeded beyond this PRD's scope, decide: un-seed it, or fast-track the follow-up PRD's config-surface ACs. Note un-seeding hides the picker but does not stop the executor from sending mappings already saved (A-8).
RiskA-11 — Custom fields reach the bot-automation surface with no spec (D-17): the picker is not AI-Agent-only. CreateActionForm.vue calls the same useAdditionalFieldLookup and is rendered by modules/bot-automation/components/drawers/NewActionDrawer.vue (:194) and EditCapabilityActionDrawer.vue (:165,:194), both passing tool.node_registry.settings straight through. So a bot-tree/capability action on a seeded node type gets custom fields too — a surface this PRD never mentions, with no ACs, no QA and no design.PM + BEImmediateConfirm on staging whether the bot-automation drawers currently show the picker for the CRM actions. If yes, it needs either its own ACs or a surface-level guard; a per-node-type seed cannot express "AI Agent only".
RiskA-7 — Zero telemetry (D-13): none of the five specified events exist, so adoption, rejection rate, lookup latency and unsupported-type occurrences are all unobservable, and the 5% rejection alert does not exist.BOT squad + PMBefore widening to more actionsLand the five events (a small BE/FE task each) or explicitly downgrade Section 12 to "not measured". Until then post-GA monitoring is Rollbar noise + manual record spot-checks.
RiskA-9 — Lane-B initiative live with zero QA coverage: the completion-alignment audit found 0 test cases for this scope in either system — no chatbot/qontak-action/testcases/ suite and no Pokayoke case referencing any in-scope Jira key (verified against a known-good control key). All 6 delivered ACs are uncovered, including the two money-critical error paths (S03/ERR-1 401 retry, S03/ERR-2 CRM rejection), while its QA task BOT-4688 is Done.PM + QA (Tito Amanda / Izzul)ImmediateAuthor the missing cases (qa-test-cases-creation) against the amended ACs in this version — the audit notes no existing case needs correcting because none exists. Also decide whether the five Stories BOT-4663–4667 (all still To Do / In Progress post-GA) are transitioned or the GA claim qualified.
RiskA-10 — Rejected control shipped (D-16): ActionMultiSelectField now renders via MpInputTag, which D-3 and RFC FE-D4 explicitly rejected, and the component is shared with the already-shipped deal tags field.BOT squadBefore the array follow-upConfirm the deal tags field is unaffected in production, then either amend D-3/FE-D4 to accept MpInputTag deliberately or restore the mandated control. Do not start the array follow-up on an undecided control.
RiskA-8 — No kill switch (D-12): with the flag descoped, disabling the feature means un-seeding node_registries (which hides the picker but leaves already-saved mappings being sent) or reverting the BE.BOT squadBefore the next action gains custom fieldsEither restore the RFC Chunk-1 rollout class, or document un-seed-plus-config-cleanup as the accepted rollback runbook.

16.1 A-6 / A-11 verification runbook (no DB access needed)

The node_registries seed is database state with no trace in db/ or lib/, which is why the audit scored exposure unverifiable from the repos. It is still checkable from outside: the same rows the FE reads are served by GET https://chatbot-staging.qontak.com/api/v1/node_registries (production: https://chatbot.qontak.com/api/…), role-gated to owner / supervisor / admin and scoped to the caller's organization. The response's settings is the raw row hash — exactly what the drawer branches on.

# 1. Exposure per node type (answers A-6)
curl -s -H "Authorization: Bearer $TOKEN" \
"https://chatbot-staging.qontak.com/api/v1/node_registries?limit=200" \
| jq '.data[]
| select(.node_type | test("^qontak_crm_(deal|ticket)_(create|update)$"))
| {node_type,
picker_shown: (.settings.option.enabled == true),
has_field_source: (.settings.option.additional_field != null),
resource_key: .settings.option.additional_field.resource_types[0].resource_key}'
Result per node typeReading
picker_shown: true on a create actionExpected — this PRD's scope
picker_shown: true on an update actionA-6 fires: live beyond scope, no ACs/QA. Contradicts Non-Goal 1 / NEG-1
picker_shown: false but fields already mapped in a saved configPicker hidden, yet the executor still sends them (A-8) — un-seeding is not a kill switch
has_field_source: false while enabled: truePicker mounts with no options and self-hides — indistinguishable from a failed lookup (D-13)

FE branch points this mirrors: ActionIntegrationForm.vue:371 (requiredItem?.settings?.option?.enabled) and useAdditionalFieldLookup.ts:38-45 (option.enabledadditional_field.is_rlresource_types[0].provider/resource_key).

For A-11 (bot-automation surface) the API cannot answer it — the seed is per node_type, not per surface, so any picker_shown: true above implies the picker on every surface rendering that action. Confirm by clicking: open bot automation → add/edit a capability action → pick the CRM action (NewActionDrawer / EditCapabilityActionDrawer) and check whether Add field appears. If it does, the feature is live on a surface this PRD never specified.

PRD CHANGELOG

VersionDateAuthorSection(s)TypeSummary
1.72026-07-27Dimas Fauzi Hidayat15 (D-17), 16 (A-6, A-11, new 16.1)ADDEDExposure surface investigated (A-6). Two findings. (1) D-17 / A-11 — the picker is not AI-Agent-only: CreateActionForm.vue calls the same useAdditionalFieldLookup and is mounted by the bot-automation drawers NewActionDrawer.vue:194 and EditCapabilityActionDrawer.vue:165,194, both passing tool.node_registry.settings through, so a seeded node type lights the field up on the bot-tree/capability surface too — no ACs, no QA, no design. Exposure is expressible per node_type only; "AI Agent only" cannot be configured. (2) A-6 is now checkable without DB access — new §16.1 runbook: GET /api/v1/node_registries serves the same settings rows the drawer branches on, with a jq filter over the four qontak_crm_* node types, a reading table per outcome, and the click-path for the surface question the API cannot answer.
1.62026-07-27Dimas Fauzi HidayatHeader, 2, 3, 4, 5, Scope Changes, 6, 7, 7.1, 8, 9, 10, 11, 12, 13, 14, 15, 16AS-BUILT + RESOLVEDReconciled to the shipped implementation and the merged BE RFC, answering open questions 1–4 of the completion-alignment audit (⛔ NOT-ALIGNED, 6/24 ACs delivered). Builds on v1.5's GA record, which is preserved. Additionally: corrected v1.5's Rollout claim that "the feature flag remains the operational control for emergency rollback" — no flag exists, so rollback needs a revert + deploy (D-12, A-8); recorded the MpInputTag reversal of D-3 (D-16, A-10, independently re-verified at ActionMultiSelectField.vue:2,25); and raised the zero-QA-coverage risk on this Lane-B, money-critical initiative (A-9). (1) Ratified RFC Decision 3.7: custom fields are org-level (team_id), not pipeline-scoped — D-1 amended, D-2 withdrawn, [QACF-S04] withdrawn (close its Jira Story Won't Do), S01/AC-4 + NEG-2 + NEG-3 replaced, Non-Goals 5–6 rewritten, CHG-001 + the flow, mermaid, constraints and telemetry properties de-pipelined. Closes RFC open question A-4. (2) Recorded three capabilities that did not ship, with owners rather than silent rewrites: array-typed fields (D-11 → A-5; was a Must Have), the ai_agent_action_custom_fields flag (D-12 → A-8; RFC Chunk 1 / BOT-4668 Won't Fix), and all custom-field telemetry (D-13 → A-7, which makes every Section 12 metric unreportable). (3) Corrected the payload contract to the as-built additional_fields[] array keyed by CRM field id (D-14) and the narrowed type whitelist (Section 7.1). (4) Surfaced that the BE merge covers all four write actions, so the create-only boundary is now a node_registries seed guarantee, not a code guarantee (D-15 → A-6). (5) Status → SHIPPED (partial); all code refs re-verified against chatbot@master + chatbot-fe@main.
1.52026-07-27Dimas Fauzi HidayatFrontmatter, Header, RolloutSHIPPEDCRM Custom Fields rolled out to General Availability — all clients/users as of 2026-07-27. PRD lifecycle draft → shipped; canonical Epic corrected to BOT-4662; rollout recorded against BE 1.324.0 and FE 3.8.3. Capture note: documentations/2026-07-27-crm-custom-fields-general-availability.md. (v1.6 note: this version recorded the release but did not amend the contract — its Rollout flag-rollback claim is corrected in v1.6.)
1.42026-07-13Dimas Fauzi Hidayat15, 16RESOLVEDPM confirmed all three pending items: R-1 mitigation accepted (D-8), R-2 no-cap confirmed (D-9), A-3 update-actions-is-separate-PRD confirmed (D-10). Dated every row in Decisions Made (14a.2) and Alternatives Rejected (14b.3); added 2 new Alternatives Rejected entries (cap on AI cardinality; scope-creep into update actions). A-1 (CRM API contract) remains the sole open item before the RFC.
1.32026-07-13Dimas Fauzi HidayatHeader, introADDEDWireframes committed in-repo (documentations/crm-actions-custom-fields-wireframes.{md,html}); PRD links updated to the stable in-repo design reference.
1.22026-07-13Dimas Fauzi Hidayat7.1 (new), 9.2 (S01/AC-5), 11, 15 (D-7)ADDEDType→component normalization contract: CRM custom-field types map to descriptors the existing ActionIntegrationForm.vue renderer already handles (one BE normalizer table, no per-type FE code); unknown types excluded from picker + ai_agent_action_custom_field_type_unsupported telemetry (new AC-5, new event).
1.12026-07-13Dimas Fauzi Hidayat2, 6, 7, 8, 9, 14, 16 + Scope ChangesGROUNDEDApplied ground-prd findings vs chatbot BE + chatbot-fe FE: corrected stale line refs (extract_arguments_by_destination →186-208; lead lookup →enrich_body_for_deal_create 262-276; crm_http_client.rbnode_resources/…:45-77; ACTION_LIST →75-432); fixed array-control fact (input-tag is prototype-only — prod uses select multiple/ActionMultiSelectField); confirmed the custom-field definitions lookup is net-new (lookup_resources.rb) and sharpened A-1.
1.02026-07-13Dimas Fauzi HidayatAllCREATEDInitial ADJUSTMENT PRD — custom fields (dynamic, pipeline-scoped, array-typed) on create deal / create ticket, grounded in the qontak-designer prototype + chatbot/chatbot-fe code.