RFC Review: AI Agent Impact Report — Phase 1: Live Impact Report
Companion review for
phase-1-live-impact-report.md, produced by therfc-reviewerskill. Lives beside the RFC; valid only for the RFC revision inreviewed_rfc_last_updated. Source claims in the RFC were verified against the real reposchatbot(52383bea1),chatbot-fe(4ab5028a), andqontak-designer(bceccf9).
Executive Summary
- Overall Score:
8.0/10 - Rating:
Strong - RFC Type:
full-stack - Sub-Type:
new-feature (FE) + new-feature (BE) - Assessment Confidence:
High - Applied Caps/Gates: No hard numeric cap triggered (no category < 5.0; ACV ≥ 5.0; ROL ≥ 5.0 with deploy order specified; no cross-layer contract mismatch). The 9.0+ band is gated out because the rubric requires
DIC,ACV,FMC,CNTall ≥ 8.5 andDIC = 7.5(see REV-1). - Implementation Readiness Verdict:
PROCEED with notes— begin at chunk 1 (migrations) immediately; resolve REV-1 (reopen windowing) and REV-3 (turns source, OQ-3) before finalizing the aggregator acceptance criteria (chunk 2), and REV-2 (forecast method) before chunk 10. - Report Path:
chatbot/ai-agent-impact-report/rfcs/phase-1-live-impact-report-review.md - RFC Author: Dimas Fauzi Hidayat | Reviewed: 2026-07-08
This is an unusually complete, source-grounded full-stack RFC — an AI agent can implement the large majority of it (migrations, models, endpoints, auth, FE store/service/middleware/tiles, rollout) directly, without a clarification meeting. Its biggest strength is verified grounding: nearly every anchor, contract, column, and CI command was confirmed against the real repos, and all 11 Mermaid diagrams parse. Its biggest gap is in the aggregation correctness of the headline metric: the nightly per-day job model does not reconcile the 48-hour reopen window (a day's reopened_48h is not final until 48h later), so recent-window "honest containment" — the hero and Success Criteria #1/#5 — is systematically over-stated unless trailing days are recomputed. The one thing that must change before the aggregator is written: specify how the 48h reopen adjustment is finalized across the daily-batch boundary (and confirm the turns-to-resolve source, OQ-3, which is currently ungrounded).
Quick Verdict
Why this RFC can be implemented agentically:
- Full DDL + 3 fully-specified endpoints (request/response/error/auth/idempotency) + an 11-chunk ordered execution plan with real, repo-sourced commands and verifiable acceptance criteria (§2.3, §2.4, §4.D).
- Every source anchor was verified against the real code (role gate, Ownership middleware,
COUNT FILTERidiom,roomscolumns,closed_reasonliterals, sentiment DB, cron/worker, migration house-style, CI commands) — the agent is building on confirmed reality, not assumptions. - Cross-layer contract, rollout compatibility matrix (deploy order = BE-first, forbidden scenario mitigated), and failure/error catalogs are all present and internally consistent (§2.G, §4.A, §3.A–C).
Why this RFC will cause agent guessing or rework:
- The 48h reopen adjustment is not reconciled with the per-day nightly job — the aggregator's acceptance criteria will produce a subtly-wrong recent-window hero unless trailing-day recomputation is specified (REV-1).
- The forecast derivation method (IMPACT-S05/AC-2) is never stated — only its gating, its output field, and its stop-condition — so an agent must invent the algorithm (REV-2).
- The turns-to-resolve source for the stored
turns_sum/turns_countis unconfirmed (OQ-3); the plausiblehistoriestable joins onbigint room_id, a different key than the sentiment join, and no counter column exists (REV-3).
Findings Ledger (carry-forward)
First review cycle (R1) — no prior
-review.mdexisted. Stable, never-renumbered ids.
| ID | Severity | Finding (one line) | RFC location | Status | First seen | Resolved in | Evidence / fix |
|---|---|---|---|---|---|---|---|
REV-1 | major | 48h reopen adjustment not reconciled with the per-day job: recent-window reopened_48h (hence net containment hero) is under-adjusted/over-stated until 48h elapse | §2.2 nightly sequence, §2.F job spec, §2.D integrity matrix | open | R1 | — | Job runs perform(yesterday) and stores reopened_48h per activity_date; a reopen can land up to 2 days later. Fix: recompute trailing 2 days each night (idempotent upsert already supports it) or finalize reopened_48h on a 48h lag; state it in chunk-2/3 acceptance criteria. |
REV-2 | major | Forecast derivation method unspecified — only gating, output field, and MAE stop-condition given | §1 PRD-to-Schema (forecast row), §2.A ForecastPanel, Decision 5, IMPACT-S05/AC-2 | open | R1 | — | forecast:{projected_net} is a contract but the algorithm (moving average? linear fit over trend[]? seasonal?) is absent. Contained by flag-OFF default, but chunk 10 needs the method. Specify the exact computation + the minimum trend length. |
REV-3 | major | Turns-to-resolve source unconfirmed; stored turns_sum/turns_count have no grounded population path | §2.3 DDL, §1 PRD-to-Schema row 7, OQ-3 | open (author-flagged OQ-3) | R1 | — | Verified: no message_count column; plausible source histories (db/schema.rb:567) joins on bigint room_id → rooms.id, different key than the sentiment app-level join (channel_room_id string). Aggregator would need two join strategies. Confirm source + key before chunk 2. |
REV-4 | minor | Supporting indexes for the aggregation/self-join are absent on rooms and not planned | §2.0 Source Verification ("indexes …"), §2.2 nightly, §3 Performance | open | R1 | — | Verified absent: no contact_id index, no closed_at index at all, no composite (organization_id, closed_at/created_at). Nightly scan cost over large rooms unaddressed; the "indexed" evidence overstates coverage for the metric's actual filters. Consider a covering index migration (or document the batch runs off-peak and is acceptable). |
REV-5 | minor | Exact AI add-on feature code unknown (gates entitlement gate + FE flag key) | OQ-1, §2.0, Detail 4.B | open (author-flagged OQ-1) | R1 | — | Confirmed: no hardcoded feature code in chatbot source; it is DB data (ChatbotGpt::Feature.all). Gates chunk 5/8; not a design blocker. |
REV-6 | minor | Canonical FE role strings for the net-new role gate unconfirmed | OQ-2, §2.0 role source | open (author-flagged OQ-2) | R1 | — | Confirmed net-new: existing middleware checks only feature flag, not role; role is a plain string used for analytics (profile.data.role). Gates chunk 8. |
REV-7 | minor | Per-org business-hours config for after-hours tile not found | OQ-4, D6, §1 PRD-to-Schema (after-hours row) | accepted-risk (author-mitigated) | R1 | — | Mitigation: ship P1 without the after-hours tile if config absent; non-blocking to the rest. Sound. |
REV-8 | minor | snake_case → camelCase mapping assumption (OQ-5) | OQ-5, §2.G, §2.0 patterns | accepted-risk (verified low) | R1 | — | Verified: FE report module consumes $apiMain ._data raw (no global camelCase mapper); the assumption holds. Keep OQ-5 as a confirm-only item. |
REV-9 | minor | Figma frames pending — pixel/contrast QA gated | OQ-6, §1 Design References, §2.0 Design↔Code | accepted-risk (author-flagged) | R1 | — | Layout may proceed against wireframe; not a code-execution blocker. Reasonable. |
REV-10 | minor | Three source-accuracy nits (conclusions unaffected) | §2.0 Source Verification | open | R1 | — | (a) grep -ric mongoid = 1 (a Devise comment), not 0; (b) entitlement uses a valid_until >= now-7d active-order window + orders.pluck(:id), not a single order_id:<active>; (c) assign_agent_worker queue is :agent_routing (the application_maintenance queue exists, but on other workers). Fix the wording so the evidence stays trustworthy. |
Ledger summary: 10 findings — 3 major (REV-1/2/3), 7 minor; 0 fixed (first cycle), 3 accepted-risk (REV-7/8/9). Still-open material findings promoted to the RFC §5 Open-Questions table by id (REV-1, REV-2 are net-new; REV-3/5/6 map to existing OQ-3/1/2).
PRD → RFC Traceability Matrix
PRD exists (
../prds/phase-1-live-impact-report.md, READY 9.8). Standard format.
| PRD Element | RFC Section | Coverage |
|---|---|---|
| IMPACT-S01/AC-1..5 (honest containment + value) | §1 Detail 1.A/1.C, §2.4 report GET, §2.A tiles, §4.D chunks 2-3,6-9 | Full |
| IMPACT-S01/AC-6 (baseline-forming) | baseline_forming flag §2.4, §2.C, §3.A.1 | Full |
| IMPACT-S02/AC-1..3 (blended journey) | journey_* §2.3/§2.4, BlendedJourneyBar §2.A | Full |
| IMPACT-S03/AC-1 (reopen rate) | reopened_48h §2.3, aggregator self-join | Partial — correctness of the 48h window across the daily batch boundary unspecified (REV-1) |
| IMPACT-S03/AC-2,4 (sentiment delta + coverage) | app-level join §2.3/§2.F.1, L-1 | Full |
| IMPACT-S03/AC-3 (turns-to-resolve) | turns_sum/turns_count §2.3 | Partial — data source unconfirmed (REV-3 / OQ-3) |
| IMPACT-S04/AC-1..3 (cost assumption → money) | ai_cost_assumptions §2.3, GET/PUT §2.4, CostAssumptionModal §2.A | Full |
| IMPACT-S05/AC-1 (trend vs baseline) | trend[] derived §2.4 | Full |
| IMPACT-S05/AC-2..3 (forecast, flag-gated) | forecast §2.4, Decision 5, ForecastPanel | Partial — derivation method absent (REV-2) |
| IMPACT-S01-NEG..S04-NEG (no add-on / role / CSAT / mobile) | §3 auth matrix, §1 Out of Scope, D9 | Full |
| PRD §6.7 Data Lifecycle | §2.3 retention/lifecycle table | Full |
| PRD §10/§10.5/§11/§13 (rollout, semantic rollback, obs, gates) | §4 Rollout, §4.E, §3 Monitoring | Full |
| PRD §14 datamart dependency | Decision 1 (chatbot-owned now; Hologres Phase 2) — honest divergence, surfaced | Full (with documented divergence) |
Summary: 10 of 13 PRD element-groups fully covered, 3 partial (reopen-window correctness, turns source, forecast method), 0 missing. 0 RFC decisions without a PRD driver — no scope creep; the one deliberate divergence (Decision 1/D3 datamart ownership, and the MongoDB→Postgres correction) is explicitly surfaced, not silent.
Scorecard
Full-Stack Scorecard (18 categories)
| # | Category | Source | Score | Evidence-Based Rationale |
|---|---|---|---|---|
| 1 | PRT — PRD Traceability | Merged | 9.5 | FE: Detail 1.A forward+reverse, UI/role/section coverage tables · BE: PRD-to-Schema derivation maps every entity→table.column→endpoint→enforcement. Per-story change map (1.C) fills FE+BE per story. |
| 2 | TDC — Technical Decisions | Merged | 9.0 | 12 decisions (Detail 1.B) + §2 Decisions 1-8, each with alternatives/rejection/rationale/consequences/reversibility, both layers. Docked for the forecast-method gap that hides behind Decision 5 (REV-2). |
| 3 | CNT — Contract Specificity | FE | 9.0 | §2.A props fully typed for every component; event payloads enumerated; state shapes named. |
| 4 | SCB — Scope Boundaries | FE | 9.5 | §2.I exhaustive create/modify/NOT-touched for both layers; verified against real file paths. |
| 5 | DEP — Dependencies | FE | 8.5 | D1-D8 table with blocking flags + availability; backend-endpoint availability tracked. OQ-1 flag code open but flagged. |
| 6 | NFS — Non-Functional | FE | 8.5 | CWV budgets (LCP≤2.5s/INP≤200ms/CLS≤0.1), WCAG AA specifics (§3.E), browser matrix, bundle-delta reasoning. Contrast gated on OQ-6. |
| 7 | TPS — Test Plan | FE | 8.5 | §4.C repo-sourced commands (vitest/playwright/eslint), cross-layer test, per-chunk acceptance criteria; correctly avoids inventing pnpm typecheck (verified: no such script). |
| 8 | DMS — Data Model & Schema | BE | 8.0 | Full DDL w/ unique indexes, retention, lifecycle, cardinality, example rows, PII class. Docked: (a) source rooms indexes for the aggregation/self-join are absent and unplanned (REV-4); (b) turns_* population path ungrounded (REV-3). |
| 9 | ACV — API Contract & Versioning | BE | 9.0 | 3 endpoints, full request/response/error/auth/idempotency/versioning + example payloads; §2.G confirms FE consumes what BE emits (verified snake_case raw consumption). |
| 10 | DIC — Data Integrity & Consistency | BE | 7.5 | §2.D matrix, idempotent upsert on unique indexes, strong cost write / eventual aggregate. Held below 8.5 by REV-1: the headline metric's 48h reopen adjustment is not reconciled with the daily-batch boundary. |
| 11 | FMC — Failure Mode Coverage | Merged | 9.0 | §3.A merged catalog + §3.B BE + §3.C FE error catalogs + §3.A.1 branch/skip; per-tile graceful degrade; §2.F poison handling. FE↔BE codes aligned. |
| 12 | CSS — Concurrency & Scaling | BE | 8.0 | §2.E collision map (both writers), Sidekiq single-run/tick, upsert conflict resolution, load test defined. Nightly scan cost over un-indexed rooms at scale unaddressed (ties to REV-4). |
| 13 | SAS — Security & Authorization | BE | 9.0 | Two-layer tenancy isolation (Decision 6, verified Ownership middleware), role×endpoint matrix, input validation + bounds, parameterized AR (no injection), Brakeman/RuboCop, audit logging, no new secrets. OQ-2 minor. |
| 14 | ROL — Rollout & Rollback | Merged | 9.0 | Deploy order BE-first w/ reasoning; §4.A compat matrix with the one "No" scenario forbidden by deploy order; two independent flags + coupling; staged go/no-go; §4.E deploy-order-aware rollback. Meets both mandatory full-stack ROL checks → no cap. |
| 15 | OBS — Observability | Merged | 9.0 | FE Mixpanel events + RUM + Rollbar + CWV; BE RED + APM + Sidekiq + Lograge; thresholded alerts + channels; SLO; cross-layer request-id trace correlation. |
| 16 | SBC — Service Boundary & Coupling | BE | 8.5 | §2.F.1 responsibility-boundary matrix; sync/async split (D2/D12); repository indirection so the Phase-2 Hologres swap is a one-file change (verified precedent exists). |
| 17 | CPA — Pattern Alignment | Merged | 9.0 | Extensive patterns-to-follow + reuse-vs-new tables, both layers, verified accurate; deviations (error+retry, custom viz, role middleware) explicitly flagged; snake_case consistency noted. |
| 18 | CDG — Compliance & Data Governance | BE | N/A | §3.D: aggregate counts + org cost assumption only; no PII/payment/health/cross-border. Verified against §2.3 DDL. No compliance trigger. |
Resource & Cost Advisory (non-blocking)
- Compute/storage are trivial as described (~2M-row 13-month aggregate; one nightly batch; low-RPS admin reads on the replica). Advisory, does not affect scores: the one real cost risk is the nightly aggregator's scan of the large
roomstable without a supportingclosed_at/composite index (REV-4) — at high room volume the off-peak batch could lengthen. Worth a covering-index migration or an explicit "acceptable off-peak" note; not a capacity/cost-scoped RFC, so non-blocking.
Decision Closure Assessment
Decision Index
| # | Decision | Status | Critical Gaps |
|---|---|---|---|
| D1 | Aggregate ownership & source (chatbot nightly → ai_activity_logs) | Resolved | none — alternatives (Hologres now / live read) rejected on shippability + p95; reversibility via repository swap (verified precedent) |
| D2/D12 | Async nightly aggregation, sync thin read/write | Resolved | none |
| D3 | Caching — none for MVP, Redis fast-follow | Resolved | trigger (p95 miss under load) + bust-on-cost-edit stated |
| D4 | Money model — admin-set assumption, no default | Resolved | none |
| D5 | Forecast gating — own flag, OFF default | Partial | gating resolved; derivation method not specified (REV-2) — output field + stop-condition given, algorithm absent |
| D6 | Read spine — pre-computed aggregate over Metabase iframe | Resolved | none |
| D7 | Visualization — custom SVG/CSS, no chart lib | Resolved | verified: no chart dep; qontak-designer has a real hand-SVG trend-line reference |
| D8 | Copy — hardcoded Indonesian-first, no i18n | Resolved | verified: no i18n framework; OQ-7 confirms language only |
| D9 | Route /reports/ai-agent-impact | Resolved | none |
| D10 | Reuse-vs-new (new report endpoints, reuse gates/entities) | Resolved | none |
| D11 | Per-status lifecycle — TTL only, no status enum | Resolved | none |
| Decision 6 | Multi-tenancy isolation (two layers) | Resolved | security invariant; verified against Ownership middleware |
Aggregate: 11 of 12 Resolved, 1 Partial (D5 forecast derivation), 0 Dangling.
Decision: D1 — Aggregate ownership & source
Status: Resolved
What was decided: "Option A — Chatbot-owned nightly sidekiq-cron job → ai_activity_logs table in primary Postgres."
Alternatives considered: (B) BI/Data-owned Hologres datamart now — rejected: hard cross-squad blocking dep, MVP unshippable, not single-agent-executable. (C) Live rooms aggregation at request time — rejected: cross-DB + reopen self-join per request blows ≤2s p95. Both rejections tied to concrete constraints.
Grounding in existing code: Verified — custom_report/generate.rb:108-117 (COUNT FILTER + group-by-date + org scope), config/schedule.yml:27 cron, hologres/unanswered_question.rb:4 (Phase-2 swap precedent). Agent can find the code.
Interface specification: Full — DDL (§2.3), repository indirection, upsert key.
Failure handling: §2.F — retry 3 → dead set + Rollbar; next night re-computes (idempotent).
Challenge results:
- Scale: aggregate read O(days) is fine; the write side (nightly scan of
rooms) is the real 10× concern — no supporting index exists (REV-4). - Reversibility: low cost — repository points at a
HologresRecordmodel; verified precedent. - Consistency: consistent with D2/D6/D12.
- Agent implementability: yes for the table/job scaffold; the aggregation logic is where REV-1/REV-3 force guesses.
Gaps and suggestions: Add a covering index (or an explicit off-peak-acceptable note) for the nightly scan; specify the reopen-window finalization (REV-1) and the turns source (REV-3) in the aggregator acceptance criteria.
Decision: D5 — Forecast gating & derivation
Status: Partial
What was decided: Gate the forecast behind ai_agent_impact_report_forecast (OFF default); output forecast:{projected_net}; stop-condition MAE ≥ 10pts over 2 periods → toggle OFF.
Alternatives considered: Ship forecast unconditionally — rejected (projection can mislead; §10.5 needs a kill-switch). Sound.
Interface specification: Output field + flag + stop-condition specified. Missing: the derivation algorithm itself.
Failure handling: flag OFF + MAE stop-condition; historical-only fallback when insufficient trend (§2.1 branch flow). Good.
Challenge results — Agent implementability: An agent cannot implement IMPACT-S05/AC-2 without guessing the method (linear regression over trend[]? trailing moving average? period-over-period growth?). The MAE stop-condition implies a point forecast but pins no model.
Gaps and suggestions: State the exact computation (e.g., "least-squares linear fit over the last N daily net points, projected one period; require ≥ 14 points else omit"), the minimum trend length, and how projected_net is bounded to [0,1]. Contained by flag-OFF default, so this can land at chunk 10 rather than blocking chunk 1.
UI State Audit
| Component | Loading | Empty | Error | Partial | Success | Assessment |
|---|---|---|---|---|---|---|
| Report page | defined | defined (baseline-forming) | defined (error+retry) | defined (per-tile degrade) | defined | 5/5 |
| CostAssumptionModal | defined | defined (blank+helper) | defined (inline 422) | n/a | defined | 5/5 (partial n/a by nature) |
| ForecastPanel | defined | defined (baseline note) | defined (chart hidden) | defined (historical-only) | defined | 5/5 |
| BlendedJourneyBar | (inherits page) | defined (placeholder) | defined (placeholder) | defined | defined | 5/5 |
Summary: 4 of 4 components have all applicable states defined (§2.C UI State Matrix). Exceptional — the error+retry component is correctly flagged as net-new to build (verified: no reusable error+retry exists in chatbot-fe).
Performance Budget Check
| Metric | Target | Baseline | Source | Assessment |
|---|---|---|---|---|
| LCP | ≤ 2.5s | not stated | §3 Performance | adequate target; no baseline (acceptable — new surface) |
| INP | ≤ 200ms | — | §3 | adequate |
| CLS | ≤ 0.1 | — | §3 | adequate |
| Bundle delta | "small (no chart lib)" | — | Decision 7 (verified no chart dep) | reasonable; not quantified but justified |
| Report GET p95 | ≤ 3s (aggregate read ≤ 2s) | — | §1 SC-2, §3 | adequate; load test defined (50 concurrent × 90-day, 5 min) |
Perf budget present and adequate for a new-feature RFC. Not a performance sub-type, so no NFS cap risk.
Accessibility Review
| Aspect | Specified? | Details | Assessment |
|---|---|---|---|
| Keyboard navigation | yes | Retry, date-range, modal focusable | adequate |
| Focus management | yes | MpModal traps + restores focus | adequate (verified compound modal pattern) |
| ARIA labels | yes | SVG bar/line role="img" + aria-label value summary | adequate |
| Heading hierarchy | partial | not explicit | minor — confirm at design QA (OQ-6) |
| Color contrast | gated | verified vs pixel3 tokens at design QA | gated on OQ-6 |
| Motion sensitivity | yes | prefers-reduced-motion on skeleton shimmer | adequate |
| Screen reader | yes | verdict is text not color-only | adequate |
Strong for an RFC at this stage; pixel-level items correctly gated on Figma (OQ-6).
Pattern Alignment Check
| Pattern | RFC Approach | Assessment |
|---|---|---|
Pinia 6-file store + extractStore | follows (store/report/) | verified exact — 6 files, nested defineStore, persist:true |
Service {fetch,controller} + fetchStatus machine | follows | verified (report.ts, bot-peformance.vue idle/pending/resolved/rejected) |
| Grape endpoint → use-case → repository | follows | verified (report.rb, custom_report/generate.rb) |
| Sidekiq worker + sidekiq-cron | follows | verified (assign_agent_worker.rb, schedule.yml) |
| Error+retry component | creates new (justified) | verified none reusable exists |
| Charts | creates new custom SVG (justified) | verified no chart lib |
| Role-gate middleware | creates new (justified) | verified existing middleware is flag-only, no role check |
| snake_case API → FE consumption | follows (raw _data) | verified no camelCase mapper (OQ-5 low-risk) |
No silent new patterns; every deviation is declared and justified. CPA is well-earned.
Data Integrity Deep-Dive
| Write Path | Transaction Scope | Partial Failure | Idempotency Key | Consistency | Duplicate Handling |
|---|---|---|---|---|---|
| Nightly aggregation upsert | one tx per org-day batch | failed org-day retried next run | (organization_id, activity_date) unique | eventual ≤24h | upsert overwrites the day's row |
PUT cost_assumption | single-row upsert (primary) | 422 before write → nothing persisted | (organization_id) unique | strong (read-after-write) | last write wins |
Deep-dive verdict: Both write paths are well-specified. The gap is not in a write path but in the read-model correctness: reopened_48h is written per activity_date, but the reopen event that adjusts day D's containment can occur on D+1/D+2. With the job computing only yesterday, day D's value is finalized ~24h into a 48h window (REV-1). The idempotent upsert permits the fix (recompute trailing days) but the RFC's job spec does not require it. This is the single most important correction before the aggregator acceptance criteria are frozen.
Concurrency Collision Map
| # | Shared Resource | Writers | Collision | Resolution | Behavior | Assessment |
|---|---|---|---|---|---|---|
| 1 | ai_cost_assumptions (org row) | Owner/Admin (rare) | two admins save near-simultaneously | upsert on unique (organization_id) | last write wins; next read latest | adequate |
| 2 | ai_activity_logs (org-day) | nightly job (+ manual backfill) | re-run / overlap | idempotent upsert + Sidekiq single-run/tick | recompute identical; no dup | adequate |
Both real collision points identified and resolved (§2.E). No unaddressed multi-writer scenario.
API Contract Completeness Check
| Endpoint | Request | Response | Error Taxonomy | Auth | Idempotency | Examples | Assessment |
|---|---|---|---|---|---|---|---|
GET /v1/reports/ai_agent_impact | complete (start_date,end_date, ≤12mo) | complete (full nested schema, nullability marked) | complete (200/400/403/5xx) | specific (Bearer+Ownership+set_role+entitlement) | safe GET | yes | 6/6 |
GET .../cost_assumption | complete (none) | complete (`data | null`) | complete (200/403) | specific | safe GET | yes |
PUT .../cost_assumption | complete (agent_hour_rate≥0,minutes_per_conversation≥0) | complete (saved record) | complete (200/403/422) | specific (owner/admin) | upsert (idempotent) | yes | 6/6 |
All three endpoints contract-complete with example payloads and error catalog (§2.4, §3.B). The one refinement: FE must map the exact 422 error.messages to field-level inline text (already noted in §2.G).
Async Job / Event Consumer Spec
| Job | Trigger | Input | Retry | DLQ | Concurrency | Idempotency | Timeout | Assessment |
|---|---|---|---|---|---|---|---|---|
AiActivityLogAggregatorWorker | sidekiq-cron 0 1 * * * Asia/Jakarta | perform(activity_date=yesterday) | retry: 3 | Sidekiq dead set + Rollbar | queue application_maintenance, 1 run/day | (org, activity_date) upsert | job-level (day batch) | 7/7 specified |
Spec is complete (§2.F). Two content caveats, not spec-completeness caveats: (1) perform(yesterday) alone cannot finalize the 48h reopen window (REV-1) — the input shape should be a date range or the job should recompute trailing days; (2) queue application_maintenance verified to exist (on other maintenance workers), though the cited assign_agent_worker itself uses :agent_routing (REV-10).
Compliance Trigger Check
| Trigger | Found? | Location | Assessment |
|---|---|---|---|
| PII | no | aggregate counts only | handled (none) |
| Payment/bank | no | cost assumption is a rate, not an account | handled (none) |
| Health | no | — | n/a |
| User content w/ retention | no | no message content stored | handled |
| Auth/session data | no | org from session, not stored in these tables | handled |
| Cross-border transfer | no | single-region | n/a |
CDG Status: N/A — no compliance triggers. updated_by (internal user id) correctly flagged as scrub-from-external-logs. Verified against the DDL.
Cross-Layer Contract Verification
| Endpoint | Backend Response | Frontend Expected | Match? | Gaps |
|---|---|---|---|---|
GET /v1/reports/ai_agent_impact | success envelope, snake_case, work_absorbed/after_hours/sentiment/forecast nullable | FE reads res.data raw (snake_case), per-tile null degrade | Yes | none — verified FE consumes _data raw, no camelCase mapper (OQ-5) |
GET .../cost_assumption | `data:{...} | null` | FE branches on null → "set assumption" prompt | Yes |
PUT .../cost_assumption | 200 / 403 / 422 | FE hides editor on 403, inline on 422 | Yes | FE must map exact 422 error.messages to field text (noted) |
Checks: casing consistent (snake_case both ends, verified) ✓ · nullability aligned (UI State Matrix handles nulls) ✓ · error shape matched (§3.A codes aligned) ✓ · pagination n/a (bounded series) ✓ · auth token format consistent (Bearer, verified apiMain) ✓.
Mismatches found: 0 — no cross-layer contract cap applies.
Cross-Layer Rollout Compatibility Matrix
| Scenario | FE | BE | Works? | Notes |
|---|---|---|---|---|
| Pre-deploy | Old | Old | Yes | baseline (no report) |
| Backend first | Old | New | Yes | new endpoints unused (flag OFF) |
| Frontend first | New | Old | No | forbidden by deploy order (FE would call non-existent endpoints) |
| Both deployed | New | New | Yes | target |
| Backend rollback | New | Old | No | mitigated: roll FE flag OFF first, then BE |
| Frontend rollback | Old | New | Yes | BE endpoints unused, harmless |
Deploy order: Backend first (stated + reasoned). Incompatible scenarios: 2, both explicitly forbidden/mitigated by the deploy-order-aware plan (§4.A, §4.E) → ROL not capped.
End-to-End Data Flow
Flow: View report
Admin opens /reports/ai-agent-impact
→ FE: page → ai-agent-impact-feature middleware (flag + role) → Pinia store action
→ service → $apiMain GET /v1/reports/ai_agent_impact (Bearer, start/end)
→ BE: Grape report.rb → Ownership + set_role + OrganizationFeature entitlement
→ AiAgentImpact use-case → repository
→ DB: SELECT ai_activity_logs WHERE (org, date range) + SELECT ai_cost_assumptions WHERE org
→ Side effects: Mixpanel impact_report_viewed; on failure impact_report_load_failed + Rollbar
→ Response: success envelope {containment, journey, quality, trend, work_absorbed|null, forecast|null}
→ FE: store fetchStatus=resolved → tiles render verdicts + plain headline
Gaps in flow: none in the view path — fully traceable end-to-end (§2.H). The upstream write path that feeds the aggregate (the nightly job) is where REV-1/REV-3 live.
Flow: Set cost assumption
Owner/Admin saves modal → PUT .../cost_assumption → set_role(owner/admin) → validate (≥0 + bounds)
→ upsert ai_cost_assumptions → 200 → FE refetch report → work_absorbed recomputes
→ Side effect: cost_assumption_updated
Gaps in flow: none — traceable; strong (read-after-write) consistency correctly claimed.
Agentic Readiness Deep-Dive
Vague Word Audit
| # | Word/Phrase | Location | Impact | Concrete Replacement |
|---|---|---|---|---|
| 1 | "next-period forecast" (no method) | §2.4 / Decision 5 | agent guesses the model | specify algorithm + min points (REV-2) |
| 2 | "message-count source" (histories/summaries) | OQ-3 | agent guesses the table + join key | confirm source + key (REV-3) |
| 3 | "sentiment coverage adequate?" | §2.1 branch flow | threshold undefined | state the min coverage % that flips the degrade note |
Total vague words in spec sections: 3 (all tied to REV-2/REV-3 or a minor threshold). Remarkably low for an RFC this size.
Dangling Alternatives
| # | Alternatives | Location | Impact |
|---|---|---|---|
| — | none | — | every "Option A/B/C" resolves to a named choice with rejection rationale |
Total dangling alternatives: 0.
Task Decomposition Assessment
| Chunk | Acceptance Criteria | Assessment |
|---|---|---|
| 1 Migrations | tables + unique indexes; db:rollback reverts | verifiable |
| 2 Models + aggregation repo | net/gross correct; excludes bot_preview/SPAM/bare RESOLVE; journey sums 100%; reopen self-join within 48h | verifiable — but must add: trailing-day recompute for the 48h window (REV-1) + confirmed turns source (REV-3) |
| 3 Aggregator worker + cron | upserts one row/org-day; idempotent; sentiment coverage recorded | verifiable — extend to trailing-day recompute (REV-1) |
| 4-5 Cost + report endpoints | 403/422 matrix; baseline_forming; work_absorbed null when unset | verifiable |
| 6 RuboCop + suite | lint clean; specs green | verifiable |
| 7-11 FE store/mw/page/tiles/viz/E2E | fetchStatus machine; verdict-not-number; flag-gated forecast; owner-only editor | verifiable — chunk 10 needs the forecast method (REV-2) |
Assessment: 11-chunk plan (§4.D) is ordered, file-specific, command-backed, and criteria-verifiable — best-in-class. The two edits above (chunk-2/3 reopen-window + turns source; chunk-10 forecast method) are the only additions needed.
Strengths
- Verified source grounding (§2.0 Source Verification, Reading Guide). Nearly every anchor, contract, column, index, literal, and CI command was confirmed against the real repos — role gate (
report.rb:16), Ownership middleware,COUNT FILTERidiom (custom_report/generate.rb:108-117),roomscolumns + indexes (db/schema.rb:1761+),closed_reasonliterals (en.yml:346-350+ write sites), sentiment-is-Postgres (with the PRD's MongoDB error honestly corrected), cron/worker, migration house-style,bitbucket-pipelines.ymlcommands. The agent builds on reality. - Complete, internally-consistent contracts across layers (§2.3, §2.4, §2.G, §4.A). DDL → endpoints → cross-layer verification → rollout compatibility all trace to each other and to PRD ACs; deploy order + rollback are deploy-order-aware; all 11 Mermaid diagrams parse (
mmdcv11.16.0). - Honest gap-surfacing. 8 OQs + 3 limitations are enumerated with owners and gating chunks, and the RFC corrects the PRD (MongoDB→Postgres) rather than silently papering over it. A reviewer's job is easier because the author already flagged most of the soft spots.
Biggest Gaps
- REV-1 — 48h reopen window vs daily-batch model (DIC, §2.2/§2.F/§2.D). The hero metric (Success Criteria #1/#5) is over-stated for recent windows unless the nightly job recomputes trailing days. This is a correctness gap in the exact number the report exists to make trustworthy, and it is not among the author's OQs — it must be added to the aggregator's acceptance criteria before chunk 2.
- REV-3 — turns-to-resolve source ungrounded (DMS, OQ-3, §2.3 row).
turns_sum/turns_countare stored, but the population source is unconfirmed; the plausiblehistoriestable joins onbigint room_idwhile sentiment joins onstring channel_room_id— a real join-key asymmetry the aggregator must resolve. - REV-2 — forecast derivation method absent (TDC/D5, IMPACT-S05/AC-2). Only the gating, output field, and MAE stop-condition are specified; the forecasting algorithm is left to the agent. Contained by flag-OFF default, so it can land at chunk 10, but it is a genuine spec hole.
Priority Actions
- Aggregator §2.F/§2.2 + chunk-2/3 acceptance criteria (REV-1) — Specify how the 48h reopen adjustment is finalized across the daily batch: recompute the trailing 2
activity_daterows on each nightly run (the(org, activity_date)idempotent upsert already supports this), or finalizereopened_48hon a 48h lag. State it as a testable criterion ("a reopen occurring on D+2 decrements day D's net containment on the next run"). Unblocks the hero's correctness. - §2.3 DDL row 7 + OQ-3 (REV-3) — Confirm the message-count source and its join key before chunk 2. If
histories(bigintroom_id → rooms.id), document the second join strategy alongside the string-keyed sentiment join; add any needed index. - Decision 5 / §2.4 forecast row (REV-2) — Name the forecast algorithm, minimum trend length, and
projected_netbounds; add to chunk-10 acceptance criteria. - §2.0 Source Verification wording (REV-4, REV-10) — Either plan a covering index on
roomsfor the nightly scan/self-join (none ofcontact_id,closed_at,(org, closed_at)exists) or add an explicit "off-peak batch, un-indexed scan acceptable" note; and fix the three accuracy nits (mongoid=1 Devise comment not 0; entitlementvalid_untilwindow not singleorder_id;application_maintenancequeue lives on other workers) so the evidence table stays trustworthy.
Backend Contract Addendum
Endpoint Contract Details
| Endpoint | Method/Path | AuthZ | Request | Response | Error | Idempotency/Ver | Status |
|---|---|---|---|---|---|---|---|
| Report | GET /v1/reports/ai_agent_impact | Bearer+Ownership+set_role(owner/supervisor/admin)+entitlement | start_date,end_date ≤12mo | nested data{containment,journey,quality,trend,work_absorbed?,forecast?} | 400/403/5xx | safe GET, /v1/ | Complete |
| Cost read | GET .../cost_assumption | +set_role(owner/supervisor/admin) | — | `data{...} | null` | 403 | safe GET |
| Cost write | PUT .../cost_assumption | +set_role(owner/admin) | {agent_hour_rate≥0,minutes_per_conversation≥0} | saved record | 403/422 | upsert on (org) | Complete (add explicit upper bounds, OQ-3) |
Database Changes Details
| Change | Table | DDL / Shape | Migration | Rollback | Compat Window | Status |
|---|---|---|---|---|---|---|
| new table | ai_activity_logs | 16 cols, NOT NULL DEFAULT 0 counts, UNIQUE(org, activity_date) | additive; disable_ddl_transaction! + concurrent index | db:rollback (recomputable from rooms) | n/a (new) | Complete — consider adding source-side rooms index (REV-4) |
| new table | ai_cost_assumptions | UNIQUE(org), CHECK(≥0) | additive | db:rollback | n/a | Complete |
Implementation Readiness Checklist
Unblocked (agent can proceed)
- PRD → RFC traceability matrix complete (forward + reverse + per-story)
- Technical decisions resolved with alternatives rejected (11/12; D5 partial)
- Failure modes handled per interaction with error catalogs (FE + BE)
- Configuration contract: 2 flags + cron with defaults (§4.B)
- Pattern alignment verified (both layers, against real code)
- Rollout plan with flags + deploy-order-aware rollback
- Observability metrics + alerts defined
- Task decomposition with acceptance criteria per chunk (§4.D)
- All UI states defined; all component prop types specified
- Cross-layer contract verified (0 mismatches); deploy order specified; compat matrix clean
- End-to-end data flow documented (view + cost save)
- Schema at DDL precision; API contracts complete with examples + error taxonomy
- Tenancy isolation, input validation, injection surface addressed
- Migration plan additive + reversible
Blocked (must refine before the dependent chunk)
- REV-1 — reopen-window finalization in aggregator acceptance criteria (before chunk 2/3)
- REV-3 — confirmed turns-to-resolve source + join key (before chunk 2)
- REV-2 — forecast derivation method (before chunk 10)
Verdict: Ready to implement chunks 1 and most of 2–9 now; refine 3 items before freezing the aggregator (chunk 2/3) and the forecast (chunk 10).
Task Manifest
The RFC's §4.D already specifies an 11-chunk plan with files, commands, and acceptance criteria — verified sound. Reproduced in condensed form with the review's required edits marked ⚠.
| Order | Chunk | Files | Acceptance Criteria | Dependencies |
|---|---|---|---|---|
| 1 | BE migrations | db/migrate/*_create_ai_activity_logs.rb, *_create_ai_cost_assumptions.rb | tables + unique indexes; db:rollback clean | None |
| 2 | BE models + aggregation repo | app/models/ai_activity_log.rb, ai_cost_assumption.rb, app/core/repositories/ai_agent_impact/aggregate.rb | net/gross correct; excludes bot_preview/SPAM/bare RESOLVE; journey=100% · ⚠ turns source confirmed (REV-3) | 1 |
| 3 | BE aggregator worker + cron | app/workers/ai_activity_log_aggregator_worker.rb, config/schedule.yml | idempotent per org-day; sentiment coverage · ⚠ trailing-day 48h reopen recompute (REV-1) | 2 |
| 4-5 | BE cost + report endpoints | app/api/frontend_service/v1/report.rb + entities + use-cases | 403/422 matrix; baseline_forming; work_absorbed null when unset | 2 |
| 6 | BE lint + suite | — | RuboCop clean; specs green | 4-5 |
| 7-8 | FE store/service/endpoint + middleware | store/ai-agent-impact/*, common/services/main/v1/ai-agent-impact.ts, middleware/ai-agent-impact-feature.ts | fetchStatus machine; flag+role gate → 404 abort | 5 (live contract) |
| 9 | FE page + tiles + states | pages/reports/ai-agent-impact/index.vue, `modules/report/views | components/ai-agent-impact/*`, error+retry component | verdict-not-number; baseline-forming; error+retry; view event |
| 10 | FE journey + forecast + cost modal | BlendedJourneyBar.vue, ForecastPanel.vue, CostAssumptionModal.vue | segments=100%; ⚠ forecast method defined (REV-2); owner-only editor | 9 |
| 11 | FE lint + E2E | nav link, tests/e2e/ai-agent-impact.spec.ts | owner sees tiles; agent no entry; cost save recomputes | 10 |
Dangling Decisions Log
| # | Decision | Location | Owner | Deadline |
|---|---|---|---|---|
| D5 (Partial) | Forecast derivation method (not the gating) | Decision 5, §2.4 forecast row | Dimas + BE | before chunk 10 |
No fully-dangling decisions. D5 is Partial (gating resolved, method open) — tracked as REV-2.
Open Questions
| # | Question | Category | Severity |
|---|---|---|---|
| 1 | How is reopened_48h finalized across the daily-batch boundary (trailing-day recompute vs 48h lag)? (REV-1) | DIC | Blocking (for chunk 2/3) |
| 2 | Confirmed source + join key for turns-to-resolve message count? (REV-3 / OQ-3) | DMS | Blocking (for chunk 2) |
| 3 | Forecast derivation algorithm + min trend length + bounds? (REV-2) | TDC | Important (chunk 10) |
| 4 | Should a covering index be added to rooms for the nightly scan/self-join, or is the off-peak un-indexed batch acceptable? (REV-4) | CSS/DMS | Important |
| 5 | Exact AI add-on feature code (OQ-1); canonical FE role strings (OQ-2) | SAS/DEP | Important (chunk 5/8) |
| 6 | Business-hours config existence for after-hours tile (OQ-4) | DEP | Nice-to-have (mitigated) |
Evidence Notes
- §2.0 Source Verification — spot-checked against all three repos; ~95% exact. Confirmed: role gate, Ownership,
COUNT FILTER,roomscolumns/indexes,closed_reasonliterals + write sites, sentiment DB + join key, Hologres precedent, rollout flag, entitlement mechanism, cron/worker, migration style, CI commands, no-chart-lib/no-i18n/no-typecheck. This grounded the high PRT/CPA/ACV/SAS scores. roomsindexes (db/schema.rb) — the metric's actual filter/self-join columns (contact_id,closed_at,(org, closed_at)) are not indexed; drove REV-4 and the DMS/CSS docking.- Nightly job spec (§2.F) vs 48h window — the
perform(yesterday)model finalizes day D at ~24h into a 48h window; drove REV-1 and held DIC to 7.5 (and thus the overall out of the 9.0+ band). - Forecast (Decision 5 / §2.4) — output + gating present, method absent; drove REV-2.
- Mermaid — 11/11 blocks parse under
mmdcv11.16.0 (repo map, deployment/service topology, end-to-end component, ER, UI state machine, branch/skip, 4 sequences). No diagram defects.
Review History
| Cycle | Date | Reviewed RFC revision | Score | Verdict | Findings open → fixed | Notes |
|---|---|---|---|---|---|---|
| R1 | 2026-07-08 | last_updated 2026-07-08 / working tree | 8.0 | PROCEED with notes | 10 open (3 major, 7 minor; 3 accepted-risk), 0 fixed | First cycle. Source claims verified across chatbot/chatbot-fe/qontak-designer; all Mermaid valid. Net-new findings REV-1 (reopen windowing) + REV-2 (forecast method); REV-3/5/6 map to OQ-3/1/2. |