Skip to main content

Review of phase-1-per-conversation-split.md (full-stack, sub-type new-feature/new-feature) against the rfc-reviewer full-stack rubric. Cycle R2 (cumulative re-review). This pass re-grounded every file:line citation against the current chatbot (Rails, HEAD fa6dd8b79) and chatbot-fe (Nuxt, HEAD f5b80d5a) checkouts. The RFC's last_updated (2026-06-20) is unchanged since R1, so this is a refresh/confirm cycle: every prior fixed (R1) finding was re-verified to still hold in both the RFC text and the live code.

RFC Review — Bot-vs-Human Traffic Split, Phase 1

Verdict

Overall (R1 carry)~8.5 / 10 — Agentic-Ready (11 findings fixed at R1)
Overall (R2, this cycle)8.5 / 10 — Agentic-Ready (all 11 fixes re-confirmed against live code; 1 new minor citation-drift finding REV-12)
Agentic ReadinessPROCEED for routing + config (BTS-S01/S02/S03 + NEG guards); comparison view (BTS-S04) PROCEED with labelled proxies — KPI accuracy still pending Data (Q-2/Q-3/L-1)
RFC typefull-stack · FE new-feature · BE new-feature
Decision closure9 of 9 ADRs Resolved · 0 Partial · 0 Dangling (ADR-3 and ADR-8, Partial at R1, are now Resolved — re-verified)
PRD traceability15 of 15 PRD sections mapped · 7 of 7 stories mapped · 0 internal contradictions (REV-3 fix holds)
Citation integrityHigh — every sampled file:line claim re-verified true against current HEADs; one intra-ADR drift (REV-12, ADR-5 :539/:548 → actual :538/:547) — cosmetic, does not affect implementability

The RFC remains unusually well-grounded. On this re-ground pass, every architectural anchor resolved to real code at the cited line at the current HEAD: process_incoming_message_with_resolve.rb :84 (crm), :120 (rollout flag), :261 (Rooms::FindOrCreateBy), :488 (find_default_path), :499 (intent_id), :501/:502 (agent_id/division_id), :503 (is_auto_assign_agent), :534 (send_message_assign_agent), :545 (is_auto_assign_agent branch), :1234 (Mixpanel emit), :54 (UseMekariBilling); the paths/rooms/channel_integrations schema; channel_integration.rb:88/89/153/154; both reused workers; active_subscription_status.rb:23; FE package.json:74/17/22, useSubscription.ts, channel-integration.ts, endpoint.ts, ai-assist.vue, qontak-crm-list.vue, toast.ts. The single non-existent spec (REV-7) is still correctly labelled "new — create"; the two "exists" specs still exist at the cited paths. No new architecture risk surfaced.

Scorecard

#CodeCategoryScoreOne-line evidence
1PRTPRD Traceability8.5Forward+reverse matrices, per-story change map, full § section-coverage; REV-3 contradiction resolved (variant stamped only under active split)
2TDCTechnical Decision Completeness8.59 ADRs with options/consequences/reversibility; ADR-3/ADR-9 close the R1 decision edges; all grounded
3CNTContract Specificity (FE)8.0§2.A UI contract + §2.C state matrix pin components, props, events; Pixel 3 verified package.json:74
4SCBScope Boundaries (FE)8.5§2.I lists FE create/modify/NOT-touched explicitly
5DEPDependency Explicitness8.0§1 dependency table + endpoint availability; Data-squad seam named (§2.F.1)
6NFSNon-Functional (FE)7.0perf budget (§3) + a11y (§3.E) present but FE a11y/browser-support remain thin
7TPSTest Plan Specificity8.0commands sourced from repo (verified package.json:17/22, AGENTS.md); REV-7 spec marked new
8DMSData Model & Schema8.5DDL + indexes + CHECK + per-status lifecycle; reversible migrations; schema gaps confirmed (no variant/bot_percent yet)
9ACVAPI Contract & Versioning8.03 endpoints tagged reuse/extend/new; comparison schema pinned (REV-4/REV-8 fix holds)
10DICData Integrity & Consistency8.5decide-once atomic update + route-by-persisted-variant (ADR-3, REV-1 fix holds)
11FMCFailure Mode Coverage8.5merged failure catalog (§3.A) + fail-safe ADR-4 + FE error catalog, codes aligned
12CSSConcurrency & Scaling7.5collision map §2.E + ≤5ms target; QPS not quantified (acceptable, not a capacity RFC)
13SASSecurity & Authorization8.5role×endpoint matrix, server-side gate, edge+DB validation, tenant isolation; REV-9 SPV decided
14ROLRollout & Rollback9.0deploy order (BE-first) + full compat matrix + flag-flip rollback
15OBSObservability8.0event catalog + alerts; handover emit (REV-5) + save_failed emitter (REV-6) defined BE-side
16SBCService Boundary & Coupling8.0responsibility-boundary matrix, sync/async clear, reuse-vs-new explicit
17CPAConsistency & Pattern Alignment8.5every pattern cited to a real reference file; snake_case FE↔BE verified
18CDGCompliance & Data Governance8.0ids-only events, no PII, retention inherits acts_as_paranoid

No category < 5.0; no score cap triggered. ACV and DIC both cleared the 6.5 R1 caps and now sit at 8.0/8.5 — the RFC sits in the Agentic-Ready band. The ADR-5 line-number drift (REV-12) is cosmetic and does not move any score.

Findings Ledger (carry-forward)

Stable, never-renumbered ids. REV-1..REV-11 carried from R1 (all fixed); each re-confirmed against the current RFC text AND live code this cycle. REV-12 is the only new id (minor citation drift found during re-grounding).

IDSevStatusFirst seenResolved inFinding (one line)R2 re-confirmation evidence
REV-1majorfixed (R1)R1R1Decide-once protected the tag, not the routing under raceHolds. ADR-3 (RFC :631) specifies atomic UPDATE … WHERE variant IS NULL + re-read + apply_arm(canonical); §2.E collision map + §2.2 sequence both route the race loser per persisted variant. Grounded: Rooms::FindOrCreateBy at :261, branch at :534.
REV-2majorfixed (R1)R1R1Split scope vs non-bot (CRM/agent/division) paths undefinedHolds. ADR-9 (RFC :725) scopes the split to plain bot-intent paths only. Grounded: crm_assignment at :83/crm_intent_id at :84, agent_id at :501, division_id at :502 — all confirmed in code.
REV-3majorfixed (R1)R1R1Variant polluted comparison when split disabledHolds. State machine + §2.3 lifecycle + ADR-3/ADR-4 all leave variant=NULL when split disabled / non-bot / fail-safe; comparison aggregates WHERE variant IS NOT NULL (§2.4, ADR-8). Consistent across all sections.
REV-4majorfixed (R1)R1R1Comparison endpoint schema underspecifiedHolds. §2.4 pins a typed v1 JSON schema (resolution_rate float 0..1, resolution_parity float|null, handover_rate, csat_avg float|null, no_data) + resolution_basis/handover_basis proxy labels.
REV-5majorfixed (R1)R1R1bot_arm_handover_to_human had no emission pointHolds. §3 Monitoring table + ADR-8 name the existing bot→agent handover path, gated on room.variant == 'bot', with assigned_at proxy + L-1 until Data ingests.
REV-6minorfixed (R1)R1R1bot_traffic_split_save_failed emitter ambiguousHolds. §2.2 config-save sequence + §3 table emit it BE-side (UpdateTrafficSplit use case on 5xx). No FE Mixpanel client assumed (FE i18n/analytics absence re-confirmed by grep).
REV-7minorfixed (R1)R1R1Test command cited a non-existent specHolds. spec/api/frontend_service/v1/channel_integration_spec.rb re-checked — still absent; §4.C/§4.D mark it "new — create". process_incoming…_spec.rb and spec/app/worker/send_message_auto_assign_agent_worker_spec.rb both still exist at cited paths.
REV-8minorfixed (R1)R1R1Date-range cap not in the contractHolds. §2.4 comparison row enforces ≤90-day range; §3.B Error Catalog returns 422 on over-long range.
REV-9minorfixed (R1)R1R1SPV write-restriction left openHolds. ADR-6 + §3 + role matrix use set_role(%w[owner admin]) on write, owner/supervisor/admin on read. Grounded against the combined gate at channel_integration.rb:89 (verified — divergence is deliberate).
REV-10minorfixed (R1)R1R1Disable semantics unspecifiedHolds. §2.4 + §4.D #3 state disabling preserves bot_percent (only the boolean flips).
REV-11minorfixed (R1)R1R1Plan-eligibility enforcement path not namedHolds. §3 Security names Repositories::Orders::UseMekariBilling + active-subscription check (pattern at :54). Repo path app/core/repositories/orders/use_mekari_billing.rb confirmed; eligible-plan list stays Q-9 (list only).
REV-12minoropenR2ADR-5 cites stale intra-branch line numbers. ADR-5 (RFC :659-660) lists the branch order as agent_id (:539) and intent_id (:548); current code has elsif agent_id.present? at :538 and elsif intent_id.present? at :547 (off by one). The anchor lines ADR-5 actually depends on (crm_intent_id :536, division_id :543, is_auto_assign_agent :545) are all exact.Cosmetic drift; the agent would still find the right branch from the surrounding method. Fix: update ADR-5 to :538/:547.

Severity tally: 0 open blocker · 0 open major · 1 open minor (REV-12) · 11 fixed (re-confirmed). Ledger summary: 1 open (minor), 0 fixed this cycle (all prior fixes re-confirmed, none regressed), 0 accepted-risk. REV-12 promoted to the RFC Open-Questions table.

Citation Re-Grounding Log (R2 — the core of this cycle)

Every anchor the prior review/RFC leans on, re-checked at current HEADs.

Anchor (as cited)StatusEvidence at current HEAD
process_incoming…rb:84 crm_intent_id✓ validif @path.present? && @path.crm_assignment at :83; self.crm_intent_id = set_crm_data at :84
…rb:120 global rollout flag✓ validRepositories::SystemPreferences::FindBy.new({ code: 'ai_assist_image_processing', group_code: 'rollout', enabled: true }) at :120
…rb:261 Rooms::FindOrCreateBy✓ validinside save_room (:259); FindOrCreateBy.new(channel_integration_id:…).call at :261
…rb:488 find_default_path✓ validdef find_default_path exactly at :488
…rb:499 intent_id✓ validself.intent_id = @path.try(:intent_id) at :499
…rb:501/:502 agent/division✓ validself.agent_id at :501, self.division_id at :502
…rb:503 is_auto_assign_agent✓ validself.is_auto_assign_agent = @path.try(:is_auto_assign_agent) at :503
…rb:534 send_message_assign_agent✓ validdef send_message_assign_agent at :534
branch order crm:536 / agent:539 / div:543 / aaa:545 / intent:548drift (REV-12)actual: crm :536 ✓, agent :538 (cited 539), div :543 ✓, is_auto_assign_agent :545 ✓, intent :547 (cited 548)
…rb:545 human-arm branch✓ validelsif is_auto_assign_agentSendMessageAutoAssignAgentWorker.perform_async at :545
…rb:1234 Mixpanel emit✓ validSendMixpanelEventWorker.perform_async(channel_integration.organization_id, 'Process Message', …) at :1234
…rb:54 UseMekariBilling✓ validRepositories::Orders::UseMekariBilling.new({ … }).call at :54
paths schema (no JSON config)✓ validis_auto_assign_agent, intent_id null:false, is_default, channel_integration_id; no JSON column
rooms schema (no variant yet)✓ validresolved_at,assigned_at,is_closed,path_id,deleted_at present; variant absent (correctly NEW)
channel_integrations.settings JSON; no split cols✓ validt.json "settings" present; no traffic_split_enabled/bot_percent yet (correctly NEW)
channel_integration.rb:88/89 patch ':id' + set_role✓ validpatch ':id' at :88; set_role(%w[owner supervisor admin]) at :89
channel_integration.rb:153 patch ':id/publish'✓ validpatch ':id/publish' at :153; set_role at :154
room.rb:3-4 acts_as_paranoid✓ validclass Room < ApplicationRecord :3, acts_as_paranoid :4
SendMessageAutoAssignAgentWorker shape✓ validclass :3, include Sidekiq::Worker :4, def perform(channel_type_id, history_id, raw_params) :14
SendMixpanelEventWorker shape✓ validclass :3, sidekiq_options queue: :event_tracker :5, def perform(distinct_id, event_name, params) :7
active_subscription_status.rb:23 OrganizationFeature✓ validOrganizationFeature.exists?(feature_id: feature.id, organization_id: @organization_id, enabled: true) at :23
spec/api/frontend_service/v1/channel_integration_spec.rb (REV-7: must NOT exist)✓ validconfirmed absent
process_incoming_message_with_resolve_spec.rb (must exist)✓ validexists
spec/app/worker/send_message_auto_assign_agent_worker_spec.rb (must exist)✓ validexists at exact cited path
UseMekariBilling repo file✓ validapp/core/repositories/orders/use_mekari_billing.rb
AGENTS.md test runners✓ validrspec, rspec spec/path/to/file_spec.rb:42, rubocop, bundle exec reek, ActiveRecord::Migration
FE package.json:74 pixel3✓ valid"@mekari/pixel3": "^1.0.12" at :74
FE package.json:17/:22 test scripts✓ valid"test": "vitest run" :17, "test:e2e": "playwright test" :22
FE useSubscription.ts checkSubscription✓ validpresent; feature .code/.enabled shape confirmed
FE channel-integration.ts update→PATCH✓ validendpoint.v1.channel_integrations.update.replace(":id", payload.id), method: "PATCH"
FE endpoint.ts channel_integrations.update✓ validupdate: "/v1/channel_integrations/:id"
FE pages/chat/settings/index.vue MpTabs✓ valid<MpTabs id="chat-settings-tab-list" …>
FE ai-assist.vue Vuelidate numeric form✓ validuseVuelidate, integer, minValue, MpInputRightAddon, type="number" all present
FE qontak-crm-list.vue pixel-table + empty-state✓ valid<pixel-table … :empty-state=…>, MpBadge
FE plugins/toast.ts pushToast✓ validrootPiniaStore.pushToast(opts)
FE i18n absence✓ validno vue-i18n/useI18n (re-confirmed) — Q-6 stands

Drift/breakage summary: 1 drift (REV-12, ADR-5 :539/:548:538/:547), 0 broken. All other anchors exact at the current HEADs.

Decision Closure (ADR-by-ADR)

ADRR1 VerdictR2 VerdictNote
ADR-1 config storageResolvedResolvedchannel_integrations columns; alternatives rejected with code-grounded reasons; Q-1 PM-confirm is courtesy, not a blocker
ADR-2 random bucketingResolvedResolvedrand(100) < bot_percent, not persisted; no prior rand bucketing in repo (re-confirmed grep)
ADR-3 decide-once + route-by-variantPartialResolvedre-read + apply_arm(canonical) now specified; race loser routes per persisted variant — REV-1 closed and re-confirmed in §2.E/§2.2
ADR-4 fail-safeResolvedResolvedRescue → bot arm, variant stays NULL + decision_fallback (REV-3-consistent)
ADR-5 human-arm hookResolvedResolvedclears the right precedence fields; grounded :84,:501-502,:545. REV-12: cites :539/:548, actual :538/:547 — cosmetic only
ADR-6 config APIResolvedResolveddedicated sub-action mirrors patch ':id/publish' (:153); SPV write decided (REV-9)
ADR-7 org gateResolvedResolvedOrganizationFeature + SystemPreference, real patterns; plan-check path named (REV-11)
ADR-8 comparison sourcePartialResolvedfield schema/formula/handover-emit pinned (REV-4/REV-5); proxies self-labelled
ADR-9 split scopeResolvedResolvedsplit applies only to plain bot-intent paths; grounded :84/:501-502

Aggregate: 9 of 9 Resolved, 0 Partial, 0 Dangling. (R1 → R2: ADR-3 and ADR-8 promoted Partial → Resolved on re-verification.)

Cross-Layer Deep-Dives

  • Contract verification (§2.G): All three rows yes. PATCH/GET snake_case consumed as-is — re-verified against channel-integration.ts (update uses raw snake_case body; endpoint.ts confirms path). Comparison schema now pinned tightly (REV-4) so the "yes" is now backed by a typed contract, not asserted. No mismatch → no cap.
  • Rollout matrix (§4.A): Complete, all cells yes, deploy order BE-first with explicit "enable flag only after BE" rule. Strongest section (ROL 9.0).
  • End-to-end flow (§2.H): All three actions (save, route, view) traceable without piecing together. The routing race branch (where REV-1 hid) is now resolved by ADR-3 re-read.

Top Improvement Actions (sequenced)

  1. Fix REV-12 (cosmetic) — update ADR-5's branch line numbers from :539/:548 to :538/:547 so the citation matches current HEAD. One-line edit; not a blocker.
  2. Resolve external Data dependencies (Q-2 / Q-3 / L-1) — canonical "resolved" definition, CSAT source, handover signal. These firm up BTS-S04 accuracy; the endpoint already ships with labelled proxies.
  3. Confirm Q-1 (config scope, PM) — the one true pre-build blocker (sets the migration target). Decision is implementable as-is on channel_integrations.
  4. Tighten NFS (FE a11y / browser-support) — §3.E targets "existing baseline"; an explicit browser matrix + keyboard/focus spec would lift NFS above 7.0. Non-blocking.

Review History

CycleDateReviewed RFC revision (last_updated / commit)ScoreVerdictFindings open → fixedNotes
R1 (assess)2026-06-20last_updated 2026-06-20 (R0 draft)7.5 (Strong)PROCEED11 open, 0 fixed11 findings raised (REV-1..11)
R1 (post-fix)2026-06-20author revision (same day)~8.5 (Agentic-Ready)PROCEED0 open, 11 fixedAll 11 addressed in-RFC
R2 (re-ground)2026-06-20last_updated 2026-06-20 · chatbot fa6dd8b79 · chatbot-fe f5b80d5a8.5 (Agentic-Ready)PROCEED1 open (minor), 0 newly fixed, 1 newly foundRe-grounded all citations vs live code; 11 R1 fixes re-confirmed (none regressed); ADR-3/ADR-8 promoted Partial→Resolved; minted REV-12 (ADR-5 line drift :539/:548:538/:547)

Delta R1 → R2: 11 fixed/confirmed (re-verified against current HEADs, none regressed), 1 still open (REV-12, new minor), 1 newly found (REV-12). Score steady at 8.5 (Agentic-Ready). The only citation movement vs current code is ADR-5's intra-branch line numbers (agent_id :539→:538, intent_id :548→:547); every other anchor — including all the cap-driving REV-1/REV-3/REV-4 anchors — resolved exactly at the cited line. ADR-3 and ADR-8 (Partial at R1) are now Resolved on re-verification. Remaining non-blocking items stay external (Q-1 PM confirm; Q-2/Q-3/Q-4/Q-7/Q-9 + L-1 Data & design), tracked in RFC §5.

Open Questions

#QuestionCategorySeverity
1REV-12: ADR-5 branch line numbers stale (:539/:548:538/:547) — refresh to match current HEADTDC / citation integrityNice-to-have
2Q-1 config scope (per-channel on channel_integrations) — PM confirmation before migrationTDCImportant (pre-build)
3Q-2/Q-3/L-1 — canonical resolved definition, CSAT source, handover signalOBS / ACV (BTS-S04 accuracy)Important (not blocking routing)

Evidence Notes

  • process_incoming_message_with_resolve.rb (1239 lines at HEAD) — the routing brain; every cited line (:84, :120, :261, :488, :499, :501-503, :534, :545, :1234) verified exact. Only the ADR-5 prose line numbers :539/:548 drifted by one (actual :538/:547) — REV-12.
  • db/schema.rb — confirmed rooms.variant, channel_integrations.traffic_split_enabled/bot_percent are all still absent (the RFC correctly treats them as NEW); reused columns (resolved_at, assigned_at, is_closed, path_id) present.
  • Spec existence (REV-7)channel_integration_spec.rb still absent (correctly "new — create"); the two extend-targets exist at the exact cited paths.
  • FE anchorspackage.json:74/17/22, service/endpoint/composable/page/form/table/toast references all resolved; i18n still absent (Q-6 stands).