Skip to main content

CRM Custom Fields — Completion Alignment Check (delivered vs PRD/RFC)

Generated: 2026-07-27 15:12 WIB (UTC+7) Initiative: chatbot/qontak-action → CRM Deals Custom Fields (child Epic BOT-4662) Contract: prds/crm-actions-custom-fields.md v1.5 (status shipped, 2026-07-27) Gate:NOT-ALIGNED


Baseline

Contract pinned. PRD v1.5 dated 2026-07-27 is current. v1.5 changed only frontmatter, header and Rollout (changelog l.411) — it recorded GA. It did not amend §15 D-1/D-2 (pipeline scoping) or D-6 (create-only) or D-3 (array control), all of which the implementation contradicts. There is no ADR amending PRD scope; delivery/decisions/0001-… covers duplicate Jira Stories only.

Design. Backend RFC rfcs/crm-actions-custom-fields.md (status draft) and FE RFC crm-actions-custom-fields-fe.md. The BE RFC's Decision 3.7 (l.294–307) rules that CRM additional fields are org-level, not pipeline-scoped, and removes the pipeline gate. That decision is explicitly not ratified: RFC §8 A-4 is Open — PM to amend PRD (l.1104, l.1146). The built product follows the RFC; the contract still says pipeline-scoped.

Pivot timeline (delivery/status/): 2026-07-16 board-registration escalation → 2026-07-20 BE 4 md corroborated, BOT-4668 (feature flag) Won't Fix "0.5 md descoped — DRI to confirm" → 2026-07-23 F-26 escalated: BOT-4669 + BOT-4673 board-Done with no commit trace → 2026-07-27 GA declared, health on-track, F-26 still open. No status entry records a decision to drop arrays (QACF-S02) or pruning (QACF-S04).

Board (JQL sweep of BOT-4662 + children, 2026-07-27). jira_epic_model: umbrella; BOT-4662 is the executable child Epic. Granularity is one Story per PRD user story (per ADR 0001), so composite ids do not appear in Jira and a git key-join resolves only to Story/Task level — corroboration, never proof. All AC verdicts below rest on an artifact-level read of current source.

StoryKeyBoard status
QACF-S01BOT-4663To Do
QACF-S02BOT-4664To Do
QACF-S03BOT-4665To Do
QACF-S04BOT-4666To Do
QACF-S01-NEGBOT-4667In Progress

All implementation Tasks are Done (BOT-4669/4670/4671/4672/4673, FE BOT-4680–4683/4699/4722/4724, QA BOT-4688) except BOT-4668 (feature flag) Won't Fix. No Story is Done, yet the initiative was declared GA. BOT-4727 is excluded — re-parented to BOT-4790 (Get Ticket & Get Deals) on 2026-07-27; its ActionFieldArrayField.vue / "field array" work is not evidence for QACF-S02.

Code searched. chatbot @ e1b769b31 (master, clean) and chatbot-fe @ c99c7410 (main, clean), both 2026-07-27. notification-service not searched — no artifact in this PRD touches it. qontak-designer skipped: code-repos.json marks it evidence: "none".

Test-case sources consulted. Repo suite: none existschatbot/qontak-action/testcases/ is absent, and no testcases/ directory exists under any chatbot/ initiative. Pokayoke: reached and queried, project Qontak = projectId 17 (repo titan), confirmed from list_projects at run time. Details and per-key results in §Verification coverage.

Unverified dimension (not scored). The node_registry settings.custom_fields_enabled seed (Task 6 / BOT-4673) is database state, absent from db/ and lib/. Whether the picker is actually exposed on any given action in a live org therefore cannot be confirmed from the repos. Every verdict below is scored on the render/merge code, which is verifiable; runtime exposure is not.


Gate

⛔ NOT-ALIGNED — 18 of 24 composite ids fail, and 4 Direction-B findings need a product decision. Separately, 6 of 6 DELIVERED ids are UNCOVERED — everything confirmed built is untested in both the repo suite and Pokayoke. Coverage does not feed the gate; it is reported beside it.

VerdictCount
DELIVERED6
PARTIAL5
DIVERGED6
MISSING7
DESCOPED-RECORDED0
UNVERIFIABLE0

Aligned = (DELIVERED 6 + DESCOPED-RECORDED 0) ÷ 24 = 25%, with 0 UNVERIFIABLE. Nothing was scored DESCOPED-RECORDED: not one gap has an ADR, a confirmed status decision, or a "skipped story" row with a stated rationale behind it.

Verification coverage (separate axis — does not change the gate).

CoverageCount
COVERED-BOTH0
REPO-ONLY0
POKAYOKE-ONLY0
DIVERGENT0
UNCOVERED24
UNVERIFIABLE-COVERAGE0

Shipped but unverified: DELIVERED + UNCOVERED = 6 of 6. Every AC this audit could confirm was built has no test case in either system. This initiative is qa_lane: B ("money-critical: deal/ticket creation", owner Tito Amanda / Izzul) — a Lane-B initiative reached GA with zero executable QA coverage of its own scope.


Alignment matrix

Composite idStory / JiraVerdictCoverageEvidence
QACF-S01/AC-1S01 · BOT-4663 To DoPARTIALUNCOVEREDPicker lists org additional fields (ActionIntegrationForm.vue:371-376) but is a flat list of display names — no Standard/Custom grouping, no type badge, no {pipeline} header (AdditionalFieldSelector.vue:35-42). The AC's ai_agent_action_custom_fields-ON precondition is unimplementable (no flag exists).
QACF-S01/AC-2S01 · BOT-4663MISSINGUNCOVEREDNo array-type badge anywhere; and no array descriptor can reach the FE — ADDITIONAL_FIELD_CONFIG whitelists 6 scalar types only (lookup_resources.rb:33-40). No ADR or status entry drops it.
QACF-S01/AC-3S01 · BOT-4663DELIVEREDUNCOVEREDAdded field defaults to AI mode — updateChoosenResult(field.name, AI_CHOICE_AI) (ActionIntegrationForm.vue:552-560); removed from picker via availableOptions filter (AdditionalFieldSelector.vue:104-106). Corroborated by BOT-4680 Done.
QACF-S01/AC-4S01 · BOT-4663DIVERGEDUNCOVEREDSpec: Pipeline on Let AI decide → "Choose a pipeline first" hint. Built: no pipeline gate at all; fields load on mount with no pipeline parameter (useAdditionalFieldLookup.ts:36-59), per RFC Decision 3.7 (rfcs/crm-actions-custom-fields.md:294-307). PRD D-1/D-2 unamended; A-4 Open — PM to amend PRD (l.1104).
QACF-S01/AC-5S01 · BOT-4663PARTIALUNCOVEREDUnsupported CRM types are excluded — next unless config (lookup_resources.rb:187-189). The required ai_agent_action_custom_field_type_unsupported log is absent: zero hits for that event across chatbot and chatbot-fe.
QACF-S01/ERR-1S01 · BOT-4663PARTIALUNCOVEREDBE logs the failure (lookup_resources.rb:197-199, under message "Failed to fetch … additional fields", not the specified event) and returns []. FE has no error state and no retry: the catch only console.errors (useAdditionalFieldLookup.ts:77-81) and the whole picker is hidden when the list is empty (AdditionalFieldSelector.vue:3).
QACF-S02/AC-1S02 · BOT-4664 To DoMISSINGUNCOVEREDNo array custom field can exist: the BE whitelist emits only input/select/textarea, never select multiple or is_array (lookup_resources.rb:33-40); the FE arg builder types the value string | number | null (ActionIntegrationForm.vue:729).
QACF-S02/AC-2S02 · BOT-4664MISSINGUNCOVEREDNo "AI will select one or more values" disabled placeholder exists in chatbot-fe.
QACF-S02/AC-3S02 · BOT-4664MISSINGUNCOVEREDRuntime rejects non-scalars: next unless value.is_a?(String) || value.is_a?(Numeric) || value == true || value == false (execute.rb:267); asserted by spec (execute_spec.rb:401-412, nested value → no additional_fields key).
QACF-S02/AC-4S02 · BOT-4664DELIVEREDUNCOVEREDNon-array custom fields render per type through the existing adaptive renderer with the AI/manual toggle (ActionIntegrationForm.vue:318-328), fed by the normalized descriptors (lookup_resources.rb:202-213). Corroborated by BOT-4680 / BOT-4682 Done.
QACF-S02/ERR-1S02 · BOT-4664MISSINGUNCOVEREDVacuous — there is no constrained array control to reject an out-of-options value. Not a pass.
QACF-S03/AC-1S03 · BOT-4665 To DoPARTIALUNCOVEREDScalar merge works (execute.rb:249-277), with unit coverage for static, AI, symbol-keyed and cached_result_name cases (execute_spec.rb:260-458). Two AC clauses fail: it is not gated by ai_agent_action_custom_fields (no such check anywhere), and array fields are not merged.
QACF-S03/AC-2S03 · BOT-4665MISSINGUNCOVEREDArray values are dropped, not sent as a list (execute.rb:266-267).
QACF-S03/AC-3S03 · BOT-4665MISSINGUNCOVERED"Flag OFF → parent behavior" cannot hold: the flag does not exist. No AiAgentActionCustomFields rollout class in app/core/repositories/system_preferences/rollout/; zero hits for ai_agent_action_custom_fields / custom_fields_enabled across app db spec lib config. BOT-4668 is Won't Fix, recorded in status 2026-07-20/23 as "0.5 md descoped — DRI to confirm" — noticed, never decided, no rationale. Not DESCOPED-RECORDED.
QACF-S03/AC-4S03 · BOT-4665DELIVEREDUNCOVEREDenrich_body_for_deal_create still runs, and runs before the merge (execute.rb:83-90); lead-lookup path intact (execute.rb:302+).
QACF-S03/ERR-1S03 · BOT-4665DELIVEREDUNCOVERED401 refresh + retry unchanged (crm_http_client.rb:45-62).
QACF-S03/ERR-2S03 · BOT-4665DELIVEREDUNCOVEREDNon-2xx returns {status: 'error', code:, body:} with the CRM payload surfaced, single POST so no partial write (execute.rb:395-412).
QACF-S04/AC-1S04 · BOT-4666 To DoDIVERGEDUNCOVEREDNo pruning code path exists. FE RFC calls the AC "met trivially" because fields are org-level (…-fe.md:188-189, 261) — but that rests on the same unratified Decision 3.7. Under the PRD's contract this is a behavior change, not a satisfied AC.
QACF-S04/AC-2S04 · BOT-4666DIVERGEDUNCOVEREDCustom group header carries no pipeline label and does not re-scope on pipeline change (AdditionalFieldSelector.vue:35-42).
QACF-S04/ERR-1S04 · BOT-4666DIVERGEDUNCOVEREDNo prune-before-save path; the premise (stale pipeline-scoped mapping) does not exist in the built model.
QACF-S01-NEG/NEG-1NEG · BOT-4667 In ProgressDIVERGEDUNCOVEREDThe merge is applied to all four write actions including qontak_crm_deal_update and qontak_crm_ticket_updateWRITE_ACTION_TYPES (execute.rb:33-39) used at execute.rb:90. Non-Goal 1 and D-6 scope this to create only. FE exposure on update forms is registry-seed-dependent and unverifiable; the BE acceptance alone breaks the guard rail.
QACF-S01-NEG/NEG-2NEG · BOT-4667DIVERGEDUNCOVEREDSame as S01/AC-4 — the pipeline gate was removed, so nothing hints; all org fields are offered regardless of pipeline mode.
QACF-S01-NEG/NEG-3NEG · BOT-4667PARTIALUNCOVEREDThe "no cross-pipeline union" clause holds — the endpoint is team_id-scoped and takes no pipeline parameter (lookup_resources.rb:18-19), so no merged per-pipeline list is ever constructed. The "scoped to the one selected pipeline" clause does not hold: the picker shows the org's whole additional-field set.
QACF-S01-NEG/NEG-4NEG · BOT-4667DELIVEREDUNCOVEREDPicker is read-only — search + select only, no create/edit affordance (AdditionalFieldSelector.vue:1-149).

Direction A — findings by verdict

MISSING (7). The whole of QACF-S02 (arrays, 5 ids) plus S01/AC-2 and S03/AC-2 — array support is absent end-to-end: whitelist, descriptor, FE state, and CRM payload. And S03/AC-3 — the feature flag was never built, so there is no OFF path and no per-org rollout control.

DIVERGED (6). One root cause for five of them: org-level scoping replaced pipeline scoping (BE RFC Decision 3.7), taking out AC-4, NEG-2 and all of S04. Plus NEG-1 — the merge was extended to the two update actions the PRD explicitly excluded.

PARTIAL (5). The scalar happy path is genuinely built but each of these is missing a named clause: grouping/badges (S01/AC-1), unsupported-type telemetry (S01/AC-5), FE error+retry (S01/ERR-1), flag gating + arrays on the merge (S03/AC-1), and pipeline scoping on the picker (NEG-3).

DELIVERED (6). Scalar custom fields work: add-to-config defaults and picker removal, per-type rendering, the merge itself preserving lead enrichment, 401 refresh/retry, error surfacing on CRM rejection, and the read-only picker guard.


Direction B — shipped behavior the contract never asked for

Needs a decision

  1. Custom-field merge applied to update deal / update ticket. execute.rb:90 gates on WRITE_ACTION_TYPES (:33-39), which includes both update actions. PRD Non-Goal 1 and D-6 scope this to create only, and NEG-1 forbids it. This is live scope expansion with no PRD line and no ADR — and the update actions have no picker specification, so whatever a builder configures there is unspecified behavior.
  2. Org-level scoping shipped against a pipeline-scoped contract. RFC Decision 3.7 is a sound engineering finding (the CRM endpoint is team_id-scoped), but A-4 is still Open — PM to amend PRD (l.1104). The product shipped and went GA on the unratified reading. Either amend the PRD (D-1, D-2, S01/AC-4, S04, NEG-2, NEG-3) or restore the gate.
  3. No feature flag exists, and the GA record says one does. The PRD Rollout section (l.303-305) and the GA capture note assert "the feature flag remains the operational control for emergency rollback". There is no ai_agent_action_custom_fields anywhere in chatbot. Rollback today requires a code revert and deploy, not a console toggle. This is an operational claim that will fail when it is needed.
  4. ActionMultiSelectField rewritten to MpInputTag (BOT-4724). PRD D-3 and RFC FE-D4 both explicitly rejected MpInputTag and mandated ActionMultiSelectField / select multiple; commit 56a08651 [BOT-4724] feat: replace ActionMultiSelectField MpAutocomplete+MpTag with MpInputTag did the opposite. The component is shared with the existing shipped deal tags field — blast radius extends beyond this PRD, and three follow-up fix commits (353c6692, eedbc7d4, 5af88261) suggest the swap was not clean.

Acceptable but undocumented

  1. CRM body shape. Fields are sent as a top-level additional_fields[] array of {id, name, value, value_name} (execute.rb:271-276), not "merged under each field's CRM key" as PRD CHG-003 and S03/AC-1 word it. Correct per CRM, resolved in the RFC (A-5, 2026-07-15) — never carried back into the PRD.
  2. Type coverage differs from §7.1. Shipped: Percentage and URL (not in the PRD table); shipped for tickets as well as deals. Not shipped: Date picker, Checkbox, Qontak user, and the whole array family — all listed in §7.1 as in-contract.

Ghost / unbacked tickets — Done tickets with no artifact behind them:

TicketClaimWhat the repos show
BOT-4669 DoneAdditionalFieldNormalizerNo such file — find . -iname "*additional_field*" in chatbot returns nothing. The normalization lives inline in lookup_resources.rb. Open as finding F-26 since 2026-07-20.
BOT-4673 Donenode registry custom_fields_enabled seedNo trace in db/ or lib/; the key appears nowhere in the backend.
BOT-4681 Done"[FE] i18n Keys + Analytics Events"Zero custom-field i18n keys and zero ai_agent_action_custom_field* analytics events in chatbot-fe.
BOT-4683 Done"[FE] E2E Spec — Custom Fields Config Flow"No feature-specific E2E spec found; unit tests for useAdditionalFieldLookup and AdditionalFieldSelector do exist.
BOT-4688 Done"[QACF] QA — Custom Fields (Create Deal / Create Ticket) coverage" (Tito Amanda)Zero test cases exist for this scope in either system — no repo testcases/, no Pokayoke case referencing any in-scope key. See §Verification coverage.

Verification coverage (Step 3b — repo suite ⇄ Pokayoke ⇄ AC set)

Coverage is an axis of its own: a test case is never delivery evidence and none of the verdicts above moved because of this section.

Repo suite — does not exist. chatbot/qontak-action/testcases/ is absent, and find chatbot -maxdepth 2 -type d -name testcases returns nothing across the whole team folder. So testcase-drift-check has nothing to audit here either; there is no committed, reviewable QA record for this initiative.

Pokayoke — reached, queried, empty for this scope. Project resolved at run time from list_projects: Qontak = projectId 17, git@bitbucket.org:mid-kelola-indonesia/titan.git. Each in-scope key was searched individually:

Keys searchedResult
BOT-4662 (Epic), BOT-4663, BOT-4664, BOT-4665, BOT-4666, BOT-4667 (all 5 Stories)0 cases each
BOT-4669, BOT-4671 (BE Tasks), BOT-4680, BOT-4683 (FE Tasks), BOT-4688 (QA Task)0 cases each

The empty result is a real absence, not a broken query. The same mechanism returns 51 cases for BOT-4036 (AI Agent, module "AI Agent Tree Diagram"), so the search path and project id are sound.

Loose-substring caveat honoured. search: "BOT-467" returned 12 cases, but parsing their references on commas gives BOT-388, BOT-389, BOT-467, BOT-551 — no exact equality with any in-scope key, so all 12 are recorded as non-matches, not coverage.

Keyword sweep. search: "additional field" returns 398 cases; the sampled page is entirely CRM web/mobile deal/task/contact form coverage referencing QON-* keys (e.g. QON-8235, QON-12325, QON-16269) in modules like "Layout For Deal" and "Filtering from Sidebar". None concern the AI Agent action-config surface this PRD changes. Consequence: the pre-existing CRM-side custom-field coverage does not transfer — nothing exercises the agent writing additional_fields on create.

No automatedStatus / priority / platform recorded — those columns exist only on a matched case, and there are none, so the qa-lane-advisor entry-bar input for this initiative is empty. This independently confirms, from the QA system rather than the repo, the initiative README's own Lane-B basis: "No E2E test specs exist for this initiative yet, so the Lane-A entry bar … is unmet regardless." It is the one doc claim checked in this audit that holds exactly as written.

Consolidation findings

  1. DELIVERED + UNCOVERED = 6 of 6 — shipped and unverified. Every AC confirmed built has no test case anywhere: the AI-mode default (S01/AC-3), non-array rendering (S02/AC-4), lead-enrichment preservation (S03/AC-4), 401 refresh/retry (S03/ERR-1), CRM-rejection surfacing (S03/ERR-2), and the read-only picker guard (NEG-4). Two of these — ERR-1 and ERR-2 — are the money-critical error paths on deal/ticket creation.
  2. A Lane-B initiative went GA with no QA artifact. README declares qa_lane: B, qa_lane_basis: "money-critical: deal/ticket creation", owners Tito Amanda / Izzul. The Lane-B human gate has no test cases behind it, and its QA task (BOT-4688) is Done.
  3. Stale coverage: none. No test case asserts a mechanism the DIVERGED verdicts replaced — because no test case exists. If the pipeline-scoping question is ratified either way, coverage will have to be authored from scratch, not corrected.
  4. Orphan tests: none detected, within a stated bound. Detection ran key-inward over the in-scope key set only. A project-wide sweep was not run — project 17 carries ~1589 modules and a bare BOT- search returns ~1955 cases, so enumerating it would be neither token-safe nor meaningful. A case covering this feature under some unrelated key would not be visible to this method.
  5. DESCOPED-RECORDED + active test case: N/A — nothing was descoped-recorded.

Handoff: these gaps are qa-test-cases-creation work, and this skill made no Pokayoke writes.


Conflicts between sources

  1. Board vs release. All five Stories BOT-4663–4667 are To Do / In Progress while the PRD is shipped, the README says "Shipped — GA", and every implementation Task is Done. Under this repo's own two-source rule the initiative has no Story-level Done evidence at all.
  2. GA vs progress. Initiative README carries progress_pct: 0 / progress_done_days: 0 and effort_total_days: TBD alongside a GA declaration; status 2026-07-27 records GA "as a milestone, not a percentage". The delivery layer cannot express how much of this shipped.
  3. PRD vs RFC. PRD v1.5 (2026-07-27) still states pipeline scoping; the RFC that the code follows overturned it on 2026-07-15. The RFC itself remains status: draft while its design is in GA.
  4. Prior review, same day. An earlier 2026-07-27-crm-custom-fields-follow-up-report.md reached "partially implemented; not ready for requirements sign-off" on 2026-07-27, hours before GA was declared. Its P1/P2 claims were re-verified independently here against current master/main and all hold; this document supersedes and replaces it (the prior text is in git history).

Open questions for the DRI

  1. Pipeline-scoped or org-scoped? Ratify or reject RFC A-4 and amend the PRD accordingly — six verdicts flip on this one answer.
  2. Is BOT-4668 (feature flag) a real descope? If yes, record it with a rationale and fix the PRD Rollout section + GA note, which currently promise a rollback control that does not exist. If no, the flag is a GA blocker that shipped anyway.
  3. Was extending the merge to update deal / update ticket intended? If yes it needs a PRD; if no it is a scope leak on a live path.
  4. Are arrays (QACF-S02, entire story) deferred or dropped? Nothing in the delivery record decides it, and the story remains To Do on the board post-GA.
  5. Why are BOT-4669 / 4673 / 4681 / 4683 Done with no artifact? F-26 has been open since 2026-07-20 and GA did not close it.
  6. Should the five Stories be transitioned to reflect reality, or the GA claim be qualified?
  7. QA: BOT-4688 is Done but no test case for this scope exists in the repo suite or Pokayoke (project 17). Was coverage executed and never recorded, or not executed? A Lane-B, money-critical initiative is live either way.

Verification note (Step 5 adversarial pass)

Every finding was re-checked against primary text before publication.

  • Confirmed by independent read: no feature flag (grep over app db spec lib config); merge on update actions (execute.rb:33-39, :90); arrays rejected (execute.rb:267 + spec :401-412) and absent from the BE whitelist (lookup_resources.rb:33-40); no FE error/retry (useAdditionalFieldLookup.ts:77-81); flat picker without grouping or badges (AdditionalFieldSelector.vue:35-42); unsupported types skipped without telemetry (lookup_resources.rb:187-189); no AdditionalFieldNormalizer file.
  • Corrected / not overstated: the scalar path is genuinely working and specced — S02/AC-4, S03/AC-1's merge core, AC-4, ERR-1, ERR-2 and S01/AC-3 were each re-read and not downgraded despite the surrounding failures. QACF-S03 was checked per-id rather than failed as a block.
  • Added beyond the existing follow-up report: the MpInputTag reversal of D-3 (BOT-4724), the additional_fields[] body-shape drift from PRD CHG-003, the false rollback claim in the GA record, and the Story-status-vs-GA conflict.
  • Coverage pass (Step 3b), added on re-run: the empty Pokayoke result was itself adversarially checked before being reported — a known-good key (BOT-4036 → 51 cases) proves the query path, and the loose-substring hit set for BOT-467 was parsed for exact key equality and rejected rather than counted. Coverage was kept strictly off the verdict axis; no Step-2 verdict moved.
  • Excluded: BOT-4727 / ActionFieldArrayField.vue — array-shaped code belonging to BOT-4790 (Get Ticket & Get Deals), not to this Epic. Accepting it would have wrongly marked QACF-S02 delivered.
  • Not claimed: no test suite was executed (no Bundler/pnpm in this environment); all code verdicts are static reads of current master/main.