Review of
phase-1-per-conversation-split.md(full-stack, sub-type new-feature/new-feature) against therfc-reviewerfull-stack rubric. Cycle R2 (cumulative re-review). This pass re-grounded everyfile:linecitation against the currentchatbot(Rails, HEADfa6dd8b79) andchatbot-fe(Nuxt, HEADf5b80d5a) checkouts. The RFC'slast_updated(2026-06-20) is unchanged since R1, so this is a refresh/confirm cycle: every priorfixed (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 Readiness | PROCEED 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 type | full-stack · FE new-feature · BE new-feature |
| Decision closure | 9 of 9 ADRs Resolved · 0 Partial · 0 Dangling (ADR-3 and ADR-8, Partial at R1, are now Resolved — re-verified) |
| PRD traceability | 15 of 15 PRD sections mapped · 7 of 7 stories mapped · 0 internal contradictions (REV-3 fix holds) |
| Citation integrity | High — 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
| # | Code | Category | Score | One-line evidence |
|---|---|---|---|---|
| 1 | PRT | PRD Traceability | 8.5 | Forward+reverse matrices, per-story change map, full § section-coverage; REV-3 contradiction resolved (variant stamped only under active split) |
| 2 | TDC | Technical Decision Completeness | 8.5 | 9 ADRs with options/consequences/reversibility; ADR-3/ADR-9 close the R1 decision edges; all grounded |
| 3 | CNT | Contract Specificity (FE) | 8.0 | §2.A UI contract + §2.C state matrix pin components, props, events; Pixel 3 verified package.json:74 |
| 4 | SCB | Scope Boundaries (FE) | 8.5 | §2.I lists FE create/modify/NOT-touched explicitly |
| 5 | DEP | Dependency Explicitness | 8.0 | §1 dependency table + endpoint availability; Data-squad seam named (§2.F.1) |
| 6 | NFS | Non-Functional (FE) | 7.0 | perf budget (§3) + a11y (§3.E) present but FE a11y/browser-support remain thin |
| 7 | TPS | Test Plan Specificity | 8.0 | commands sourced from repo (verified package.json:17/22, AGENTS.md); REV-7 spec marked new |
| 8 | DMS | Data Model & Schema | 8.5 | DDL + indexes + CHECK + per-status lifecycle; reversible migrations; schema gaps confirmed (no variant/bot_percent yet) |
| 9 | ACV | API Contract & Versioning | 8.0 | 3 endpoints tagged reuse/extend/new; comparison schema pinned (REV-4/REV-8 fix holds) |
| 10 | DIC | Data Integrity & Consistency | 8.5 | decide-once atomic update + route-by-persisted-variant (ADR-3, REV-1 fix holds) |
| 11 | FMC | Failure Mode Coverage | 8.5 | merged failure catalog (§3.A) + fail-safe ADR-4 + FE error catalog, codes aligned |
| 12 | CSS | Concurrency & Scaling | 7.5 | collision map §2.E + ≤5ms target; QPS not quantified (acceptable, not a capacity RFC) |
| 13 | SAS | Security & Authorization | 8.5 | role×endpoint matrix, server-side gate, edge+DB validation, tenant isolation; REV-9 SPV decided |
| 14 | ROL | Rollout & Rollback | 9.0 | deploy order (BE-first) + full compat matrix + flag-flip rollback |
| 15 | OBS | Observability | 8.0 | event catalog + alerts; handover emit (REV-5) + save_failed emitter (REV-6) defined BE-side |
| 16 | SBC | Service Boundary & Coupling | 8.0 | responsibility-boundary matrix, sync/async clear, reuse-vs-new explicit |
| 17 | CPA | Consistency & Pattern Alignment | 8.5 | every pattern cited to a real reference file; snake_case FE↔BE verified |
| 18 | CDG | Compliance & Data Governance | 8.0 | ids-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).
| ID | Sev | Status | First seen | Resolved in | Finding (one line) | R2 re-confirmation evidence |
|---|---|---|---|---|---|---|
| REV-1 | major | fixed (R1) | R1 | R1 | Decide-once protected the tag, not the routing under race | Holds. 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-2 | major | fixed (R1) | R1 | R1 | Split scope vs non-bot (CRM/agent/division) paths undefined | Holds. 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-3 | major | fixed (R1) | R1 | R1 | Variant polluted comparison when split disabled | Holds. 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-4 | major | fixed (R1) | R1 | R1 | Comparison endpoint schema underspecified | Holds. §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-5 | major | fixed (R1) | R1 | R1 | bot_arm_handover_to_human had no emission point | Holds. §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-6 | minor | fixed (R1) | R1 | R1 | bot_traffic_split_save_failed emitter ambiguous | Holds. §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-7 | minor | fixed (R1) | R1 | R1 | Test command cited a non-existent spec | Holds. 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-8 | minor | fixed (R1) | R1 | R1 | Date-range cap not in the contract | Holds. §2.4 comparison row enforces ≤90-day range; §3.B Error Catalog returns 422 on over-long range. |
| REV-9 | minor | fixed (R1) | R1 | R1 | SPV write-restriction left open | Holds. 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-10 | minor | fixed (R1) | R1 | R1 | Disable semantics unspecified | Holds. §2.4 + §4.D #3 state disabling preserves bot_percent (only the boolean flips). |
| REV-11 | minor | fixed (R1) | R1 | R1 | Plan-eligibility enforcement path not named | Holds. §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-12 | minor | open | R2 | — | ADR-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) | Status | Evidence at current HEAD |
|---|---|---|
process_incoming…rb:84 crm_intent_id | ✓ valid | if @path.present? && @path.crm_assignment at :83; self.crm_intent_id = set_crm_data at :84 |
…rb:120 global rollout flag | ✓ valid | Repositories::SystemPreferences::FindBy.new({ code: 'ai_assist_image_processing', group_code: 'rollout', enabled: true }) at :120 |
…rb:261 Rooms::FindOrCreateBy | ✓ valid | inside save_room (:259); FindOrCreateBy.new(channel_integration_id:…).call at :261 |
…rb:488 find_default_path | ✓ valid | def find_default_path exactly at :488 |
…rb:499 intent_id | ✓ valid | self.intent_id = @path.try(:intent_id) at :499 |
…rb:501/:502 agent/division | ✓ valid | self.agent_id at :501, self.division_id at :502 |
…rb:503 is_auto_assign_agent | ✓ valid | self.is_auto_assign_agent = @path.try(:is_auto_assign_agent) at :503 |
…rb:534 send_message_assign_agent | ✓ valid | def send_message_assign_agent at :534 |
branch order crm:536 / agent:539 / div:543 / aaa:545 / intent:548 | ⚠ drift (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 | ✓ valid | elsif is_auto_assign_agent → SendMessageAutoAssignAgentWorker.perform_async at :545 |
…rb:1234 Mixpanel emit | ✓ valid | SendMixpanelEventWorker.perform_async(channel_integration.organization_id, 'Process Message', …) at :1234 |
…rb:54 UseMekariBilling | ✓ valid | Repositories::Orders::UseMekariBilling.new({ … }).call at :54 |
paths schema (no JSON config) | ✓ valid | is_auto_assign_agent, intent_id null:false, is_default, channel_integration_id; no JSON column |
rooms schema (no variant yet) | ✓ valid | resolved_at,assigned_at,is_closed,path_id,deleted_at present; variant absent (correctly NEW) |
channel_integrations.settings JSON; no split cols | ✓ valid | t.json "settings" present; no traffic_split_enabled/bot_percent yet (correctly NEW) |
channel_integration.rb:88/89 patch ':id' + set_role | ✓ valid | patch ':id' at :88; set_role(%w[owner supervisor admin]) at :89 |
channel_integration.rb:153 patch ':id/publish' | ✓ valid | patch ':id/publish' at :153; set_role at :154 |
room.rb:3-4 acts_as_paranoid | ✓ valid | class Room < ApplicationRecord :3, acts_as_paranoid :4 |
SendMessageAutoAssignAgentWorker shape | ✓ valid | class :3, include Sidekiq::Worker :4, def perform(channel_type_id, history_id, raw_params) :14 |
SendMixpanelEventWorker shape | ✓ valid | class :3, sidekiq_options queue: :event_tracker :5, def perform(distinct_id, event_name, params) :7 |
active_subscription_status.rb:23 OrganizationFeature | ✓ valid | OrganizationFeature.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) | ✓ valid | confirmed absent |
process_incoming_message_with_resolve_spec.rb (must exist) | ✓ valid | exists |
spec/app/worker/send_message_auto_assign_agent_worker_spec.rb (must exist) | ✓ valid | exists at exact cited path |
UseMekariBilling repo file | ✓ valid | app/core/repositories/orders/use_mekari_billing.rb |
| AGENTS.md test runners | ✓ valid | rspec, 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 | ✓ valid | present; feature .code/.enabled shape confirmed |
FE channel-integration.ts update→PATCH | ✓ valid | endpoint.v1.channel_integrations.update.replace(":id", payload.id), method: "PATCH" |
FE endpoint.ts channel_integrations.update | ✓ valid | update: "/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 | ✓ valid | useVuelidate, 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 | ✓ valid | rootPiniaStore.pushToast(opts) |
| FE i18n absence | ✓ valid | no 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)
| ADR | R1 Verdict | R2 Verdict | Note |
|---|---|---|---|
| ADR-1 config storage | Resolved | Resolved | channel_integrations columns; alternatives rejected with code-grounded reasons; Q-1 PM-confirm is courtesy, not a blocker |
| ADR-2 random bucketing | Resolved | Resolved | rand(100) < bot_percent, not persisted; no prior rand bucketing in repo (re-confirmed grep) |
| ADR-3 decide-once + route-by-variant | Partial | Resolved | re-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-safe | Resolved | Resolved | Rescue → bot arm, variant stays NULL + decision_fallback (REV-3-consistent) |
| ADR-5 human-arm hook | Resolved | Resolved | clears the right precedence fields; grounded :84,:501-502,:545. REV-12: cites :539/:548, actual :538/:547 — cosmetic only |
| ADR-6 config API | Resolved | Resolved | dedicated sub-action mirrors patch ':id/publish' (:153); SPV write decided (REV-9) |
| ADR-7 org gate | Resolved | Resolved | OrganizationFeature + SystemPreference, real patterns; plan-check path named (REV-11) |
| ADR-8 comparison source | Partial | Resolved | field schema/formula/handover-emit pinned (REV-4/REV-5); proxies self-labelled |
| ADR-9 split scope | Resolved | Resolved | split 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 againstchannel-integration.ts(updateuses raw snake_case body;endpoint.tsconfirms 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)
- Fix REV-12 (cosmetic) — update ADR-5's branch line numbers from
:539/:548to:538/:547so the citation matches current HEAD. One-line edit; not a blocker. - 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.
- Confirm Q-1 (config scope, PM) — the one true pre-build blocker (sets the migration target). Decision is implementable as-is on
channel_integrations. - 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
| Cycle | Date | Reviewed RFC revision (last_updated / commit) | Score | Verdict | Findings open → fixed | Notes |
|---|---|---|---|---|---|---|
| R1 (assess) | 2026-06-20 | last_updated 2026-06-20 (R0 draft) | 7.5 (Strong) | PROCEED | 11 open, 0 fixed | 11 findings raised (REV-1..11) |
| R1 (post-fix) | 2026-06-20 | author revision (same day) | ~8.5 (Agentic-Ready) | PROCEED | 0 open, 11 fixed | All 11 addressed in-RFC |
| R2 (re-ground) | 2026-06-20 | last_updated 2026-06-20 · chatbot fa6dd8b79 · chatbot-fe f5b80d5a | 8.5 (Agentic-Ready) | PROCEED | 1 open (minor), 0 newly fixed, 1 newly found | Re-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
| # | Question | Category | Severity |
|---|---|---|---|
| 1 | REV-12: ADR-5 branch line numbers stale (:539/:548 → :538/:547) — refresh to match current HEAD | TDC / citation integrity | Nice-to-have |
| 2 | Q-1 config scope (per-channel on channel_integrations) — PM confirmation before migration | TDC | Important (pre-build) |
| 3 | Q-2/Q-3/L-1 — canonical resolved definition, CSAT source, handover signal | OBS / 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/:548drifted by one (actual:538/:547) — REV-12.db/schema.rb— confirmedrooms.variant,channel_integrations.traffic_split_enabled/bot_percentare 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.rbstill absent (correctly "new — create"); the two extend-targets exist at the exact cited paths. - FE anchors —
package.json:74/17/22, service/endpoint/composable/page/form/table/toast references all resolved; i18n still absent (Q-6 stands).