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.
| Story | Key | Board status |
|---|---|---|
| QACF-S01 | BOT-4663 | To Do |
| QACF-S02 | BOT-4664 | To Do |
| QACF-S03 | BOT-4665 | To Do |
| QACF-S04 | BOT-4666 | To Do |
| QACF-S01-NEG | BOT-4667 | In 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 exists — chatbot/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.
| Verdict | Count |
|---|---|
| DELIVERED | 6 |
| PARTIAL | 5 |
| DIVERGED | 6 |
| MISSING | 7 |
| DESCOPED-RECORDED | 0 |
| UNVERIFIABLE | 0 |
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).
| Coverage | Count |
|---|---|
| COVERED-BOTH | 0 |
| REPO-ONLY | 0 |
| POKAYOKE-ONLY | 0 |
| DIVERGENT | 0 |
| UNCOVERED | 24 |
| UNVERIFIABLE-COVERAGE | 0 |
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 isqa_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 id | Story / Jira | Verdict | Coverage | Evidence |
|---|---|---|---|---|
| QACF-S01/AC-1 | S01 · BOT-4663 To Do | PARTIAL | UNCOVERED | Picker 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-2 | S01 · BOT-4663 | MISSING | UNCOVERED | No 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-3 | S01 · BOT-4663 | DELIVERED | UNCOVERED | Added 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-4 | S01 · BOT-4663 | DIVERGED | UNCOVERED | Spec: 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-5 | S01 · BOT-4663 | PARTIAL | UNCOVERED | Unsupported 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-1 | S01 · BOT-4663 | PARTIAL | UNCOVERED | BE 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-1 | S02 · BOT-4664 To Do | MISSING | UNCOVERED | No 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-2 | S02 · BOT-4664 | MISSING | UNCOVERED | No "AI will select one or more values" disabled placeholder exists in chatbot-fe. |
| QACF-S02/AC-3 | S02 · BOT-4664 | MISSING | UNCOVERED | Runtime 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-4 | S02 · BOT-4664 | DELIVERED | UNCOVERED | Non-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-1 | S02 · BOT-4664 | MISSING | UNCOVERED | Vacuous — there is no constrained array control to reject an out-of-options value. Not a pass. |
| QACF-S03/AC-1 | S03 · BOT-4665 To Do | PARTIAL | UNCOVERED | Scalar 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-2 | S03 · BOT-4665 | MISSING | UNCOVERED | Array values are dropped, not sent as a list (execute.rb:266-267). |
| QACF-S03/AC-3 | S03 · BOT-4665 | MISSING | UNCOVERED | "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-4 | S03 · BOT-4665 | DELIVERED | UNCOVERED | enrich_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-1 | S03 · BOT-4665 | DELIVERED | UNCOVERED | 401 refresh + retry unchanged (crm_http_client.rb:45-62). |
| QACF-S03/ERR-2 | S03 · BOT-4665 | DELIVERED | UNCOVERED | Non-2xx returns {status: 'error', code:, body:} with the CRM payload surfaced, single POST so no partial write (execute.rb:395-412). |
| QACF-S04/AC-1 | S04 · BOT-4666 To Do | DIVERGED | UNCOVERED | No 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-2 | S04 · BOT-4666 | DIVERGED | UNCOVERED | Custom group header carries no pipeline label and does not re-scope on pipeline change (AdditionalFieldSelector.vue:35-42). |
| QACF-S04/ERR-1 | S04 · BOT-4666 | DIVERGED | UNCOVERED | No prune-before-save path; the premise (stale pipeline-scoped mapping) does not exist in the built model. |
| QACF-S01-NEG/NEG-1 | NEG · BOT-4667 In Progress | DIVERGED | UNCOVERED | The merge is applied to all four write actions including qontak_crm_deal_update and qontak_crm_ticket_update — WRITE_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-2 | NEG · BOT-4667 | DIVERGED | UNCOVERED | Same 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-3 | NEG · BOT-4667 | PARTIAL | UNCOVERED | The "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-4 | NEG · BOT-4667 | DELIVERED | UNCOVERED | Picker 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
- Custom-field merge applied to
update deal/update ticket.execute.rb:90gates onWRITE_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. - 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 stillOpen — 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. - 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_fieldsanywhere inchatbot. Rollback today requires a code revert and deploy, not a console toggle. This is an operational claim that will fail when it is needed. ActionMultiSelectFieldrewritten toMpInputTag(BOT-4724). PRD D-3 and RFC FE-D4 both explicitly rejectedMpInputTagand mandatedActionMultiSelectField/select multiple; commit56a08651 [BOT-4724] feat: replace ActionMultiSelectField MpAutocomplete+MpTag with MpInputTagdid the opposite. The component is shared with the existing shipped dealtagsfield — blast radius extends beyond this PRD, and three follow-up fix commits (353c6692,eedbc7d4,5af88261) suggest the swap was not clean.
Acceptable but undocumented
- 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. - 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:
| Ticket | Claim | What the repos show |
|---|---|---|
| BOT-4669 Done | AdditionalFieldNormalizer | No 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 Done | node registry custom_fields_enabled seed | No 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 searched | Result |
|---|---|
| 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
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.- 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. - Stale coverage: none. No test case asserts a mechanism the
DIVERGEDverdicts 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. - 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. 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
- Board vs release. All five Stories BOT-4663–4667 are
To Do/In Progresswhile the PRD isshipped, the README says "Shipped — GA", and every implementation Task isDone. Under this repo's own two-source rule the initiative has no Story-level Done evidence at all. - GA vs progress. Initiative README carries
progress_pct: 0/progress_done_days: 0andeffort_total_days: TBDalongside 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. - 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: draftwhile its design is in GA. - Prior review, same day. An earlier
2026-07-27-crm-custom-fields-follow-up-report.mdreached "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 currentmaster/mainand all hold; this document supersedes and replaces it (the prior text is in git history).
Open questions for the DRI
- Pipeline-scoped or org-scoped? Ratify or reject RFC A-4 and amend the PRD accordingly — six verdicts flip on this one answer.
- 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.
- Was extending the merge to
update deal/update ticketintended? If yes it needs a PRD; if no it is a scope leak on a live path. - Are arrays (QACF-S02, entire story) deferred or dropped? Nothing in the delivery record decides
it, and the story remains
To Doon the board post-GA. - Why are BOT-4669 / 4673 / 4681 / 4683
Donewith no artifact? F-26 has been open since 2026-07-20 and GA did not close it. - Should the five Stories be transitioned to reflect reality, or the GA claim be qualified?
- 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); noAdditionalFieldNormalizerfile. - 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-S03was checked per-id rather than failed as a block. - Added beyond the existing follow-up report: the
MpInputTagreversal of D-3 (BOT-4724), theadditional_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 forBOT-467was 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.