Skip to main content

RFC Review: AI Agent Impact Report — Phase 1: Live Impact Report

Companion review for phase-1-live-impact-report.md, produced by the rfc-reviewer skill. Lives beside the RFC; valid only for the RFC revision in reviewed_rfc_last_updated. Source claims in the RFC were verified against the real repos chatbot (52383bea1), chatbot-fe (4ab5028a), and qontak-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, CNT all ≥ 8.5 and DIC = 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 FILTER idiom, rooms columns, closed_reason literals, 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_count is unconfirmed (OQ-3); the plausible histories table joins on bigint 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.md existed. Stable, never-renumbered ids.

IDSeverityFinding (one line)RFC locationStatusFirst seenResolved inEvidence / fix
REV-1major48h 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 matrixopenR1Job 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-2majorForecast 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-2openR1forecast:{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-3majorTurns-to-resolve source unconfirmed; stored turns_sum/turns_count have no grounded population path§2.3 DDL, §1 PRD-to-Schema row 7, OQ-3open (author-flagged OQ-3)R1Verified: 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-4minorSupporting indexes for the aggregation/self-join are absent on rooms and not planned§2.0 Source Verification ("indexes …"), §2.2 nightly, §3 PerformanceopenR1Verified 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-5minorExact AI add-on feature code unknown (gates entitlement gate + FE flag key)OQ-1, §2.0, Detail 4.Bopen (author-flagged OQ-1)R1Confirmed: no hardcoded feature code in chatbot source; it is DB data (ChatbotGpt::Feature.all). Gates chunk 5/8; not a design blocker.
REV-6minorCanonical FE role strings for the net-new role gate unconfirmedOQ-2, §2.0 role sourceopen (author-flagged OQ-2)R1Confirmed 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-7minorPer-org business-hours config for after-hours tile not foundOQ-4, D6, §1 PRD-to-Schema (after-hours row)accepted-risk (author-mitigated)R1Mitigation: ship P1 without the after-hours tile if config absent; non-blocking to the rest. Sound.
REV-8minorsnake_case → camelCase mapping assumption (OQ-5)OQ-5, §2.G, §2.0 patternsaccepted-risk (verified low)R1Verified: FE report module consumes $apiMain ._data raw (no global camelCase mapper); the assumption holds. Keep OQ-5 as a confirm-only item.
REV-9minorFigma frames pending — pixel/contrast QA gatedOQ-6, §1 Design References, §2.0 Design↔Codeaccepted-risk (author-flagged)R1Layout may proceed against wireframe; not a code-execution blocker. Reasonable.
REV-10minorThree source-accuracy nits (conclusions unaffected)§2.0 Source VerificationopenR1(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 ElementRFC SectionCoverage
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-9Full
IMPACT-S01/AC-6 (baseline-forming)baseline_forming flag §2.4, §2.C, §3.A.1Full
IMPACT-S02/AC-1..3 (blended journey)journey_* §2.3/§2.4, BlendedJourneyBar §2.AFull
IMPACT-S03/AC-1 (reopen rate)reopened_48h §2.3, aggregator self-joinPartial — 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-1Full
IMPACT-S03/AC-3 (turns-to-resolve)turns_sum/turns_count §2.3Partial — 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.AFull
IMPACT-S05/AC-1 (trend vs baseline)trend[] derived §2.4Full
IMPACT-S05/AC-2..3 (forecast, flag-gated)forecast §2.4, Decision 5, ForecastPanelPartial — derivation method absent (REV-2)
IMPACT-S01-NEG..S04-NEG (no add-on / role / CSAT / mobile)§3 auth matrix, §1 Out of Scope, D9Full
PRD §6.7 Data Lifecycle§2.3 retention/lifecycle tableFull
PRD §10/§10.5/§11/§13 (rollout, semantic rollback, obs, gates)§4 Rollout, §4.E, §3 MonitoringFull
PRD §14 datamart dependencyDecision 1 (chatbot-owned now; Hologres Phase 2) — honest divergence, surfacedFull (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)

#CategorySourceScoreEvidence-Based Rationale
1PRT — PRD TraceabilityMerged9.5FE: 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.
2TDC — Technical DecisionsMerged9.012 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).
3CNT — Contract SpecificityFE9.0§2.A props fully typed for every component; event payloads enumerated; state shapes named.
4SCB — Scope BoundariesFE9.5§2.I exhaustive create/modify/NOT-touched for both layers; verified against real file paths.
5DEP — DependenciesFE8.5D1-D8 table with blocking flags + availability; backend-endpoint availability tracked. OQ-1 flag code open but flagged.
6NFS — Non-FunctionalFE8.5CWV budgets (LCP≤2.5s/INP≤200ms/CLS≤0.1), WCAG AA specifics (§3.E), browser matrix, bundle-delta reasoning. Contrast gated on OQ-6.
7TPS — Test PlanFE8.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).
8DMS — Data Model & SchemaBE8.0Full 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).
9ACV — API Contract & VersioningBE9.03 endpoints, full request/response/error/auth/idempotency/versioning + example payloads; §2.G confirms FE consumes what BE emits (verified snake_case raw consumption).
10DIC — Data Integrity & ConsistencyBE7.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.
11FMC — Failure Mode CoverageMerged9.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.
12CSS — Concurrency & ScalingBE8.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).
13SAS — Security & AuthorizationBE9.0Two-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.
14ROL — Rollout & RollbackMerged9.0Deploy 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.
15OBS — ObservabilityMerged9.0FE Mixpanel events + RUM + Rollbar + CWV; BE RED + APM + Sidekiq + Lograge; thresholded alerts + channels; SLO; cross-layer request-id trace correlation.
16SBC — Service Boundary & CouplingBE8.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).
17CPA — Pattern AlignmentMerged9.0Extensive patterns-to-follow + reuse-vs-new tables, both layers, verified accurate; deviations (error+retry, custom viz, role middleware) explicitly flagged; snake_case consistency noted.
18CDG — Compliance & Data GovernanceBEN/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 rooms table without a supporting closed_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

#DecisionStatusCritical Gaps
D1Aggregate ownership & source (chatbot nightly → ai_activity_logs)Resolvednone — alternatives (Hologres now / live read) rejected on shippability + p95; reversibility via repository swap (verified precedent)
D2/D12Async nightly aggregation, sync thin read/writeResolvednone
D3Caching — none for MVP, Redis fast-followResolvedtrigger (p95 miss under load) + bust-on-cost-edit stated
D4Money model — admin-set assumption, no defaultResolvednone
D5Forecast gating — own flag, OFF defaultPartialgating resolved; derivation method not specified (REV-2) — output field + stop-condition given, algorithm absent
D6Read spine — pre-computed aggregate over Metabase iframeResolvednone
D7Visualization — custom SVG/CSS, no chart libResolvedverified: no chart dep; qontak-designer has a real hand-SVG trend-line reference
D8Copy — hardcoded Indonesian-first, no i18nResolvedverified: no i18n framework; OQ-7 confirms language only
D9Route /reports/ai-agent-impactResolvednone
D10Reuse-vs-new (new report endpoints, reuse gates/entities)Resolvednone
D11Per-status lifecycle — TTL only, no status enumResolvednone
Decision 6Multi-tenancy isolation (two layers)Resolvedsecurity 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 HologresRecord model; 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

ComponentLoadingEmptyErrorPartialSuccessAssessment
Report pagedefineddefined (baseline-forming)defined (error+retry)defined (per-tile degrade)defined5/5
CostAssumptionModaldefineddefined (blank+helper)defined (inline 422)n/adefined5/5 (partial n/a by nature)
ForecastPaneldefineddefined (baseline note)defined (chart hidden)defined (historical-only)defined5/5
BlendedJourneyBar(inherits page)defined (placeholder)defined (placeholder)defineddefined5/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

MetricTargetBaselineSourceAssessment
LCP≤ 2.5snot stated§3 Performanceadequate target; no baseline (acceptable — new surface)
INP≤ 200ms§3adequate
CLS≤ 0.1§3adequate
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, §3adequate; 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

AspectSpecified?DetailsAssessment
Keyboard navigationyesRetry, date-range, modal focusableadequate
Focus managementyesMpModal traps + restores focusadequate (verified compound modal pattern)
ARIA labelsyesSVG bar/line role="img" + aria-label value summaryadequate
Heading hierarchypartialnot explicitminor — confirm at design QA (OQ-6)
Color contrastgatedverified vs pixel3 tokens at design QAgated on OQ-6
Motion sensitivityyesprefers-reduced-motion on skeleton shimmeradequate
Screen readeryesverdict is text not color-onlyadequate

Strong for an RFC at this stage; pixel-level items correctly gated on Figma (OQ-6).


Pattern Alignment Check

PatternRFC ApproachAssessment
Pinia 6-file store + extractStorefollows (store/report/)verified exact — 6 files, nested defineStore, persist:true
Service {fetch,controller} + fetchStatus machinefollowsverified (report.ts, bot-peformance.vue idle/pending/resolved/rejected)
Grape endpoint → use-case → repositoryfollowsverified (report.rb, custom_report/generate.rb)
Sidekiq worker + sidekiq-cronfollowsverified (assign_agent_worker.rb, schedule.yml)
Error+retry componentcreates new (justified)verified none reusable exists
Chartscreates new custom SVG (justified)verified no chart lib
Role-gate middlewarecreates new (justified)verified existing middleware is flag-only, no role check
snake_case API → FE consumptionfollows (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 PathTransaction ScopePartial FailureIdempotency KeyConsistencyDuplicate Handling
Nightly aggregation upsertone tx per org-day batchfailed org-day retried next run(organization_id, activity_date) uniqueeventual ≤24hupsert overwrites the day's row
PUT cost_assumptionsingle-row upsert (primary)422 before write → nothing persisted(organization_id) uniquestrong (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 ResourceWritersCollisionResolutionBehaviorAssessment
1ai_cost_assumptions (org row)Owner/Admin (rare)two admins save near-simultaneouslyupsert on unique (organization_id)last write wins; next read latestadequate
2ai_activity_logs (org-day)nightly job (+ manual backfill)re-run / overlapidempotent upsert + Sidekiq single-run/tickrecompute identical; no dupadequate

Both real collision points identified and resolved (§2.E). No unaddressed multi-writer scenario.


API Contract Completeness Check

EndpointRequestResponseError TaxonomyAuthIdempotencyExamplesAssessment
GET /v1/reports/ai_agent_impactcomplete (start_date,end_date, ≤12mo)complete (full nested schema, nullability marked)complete (200/400/403/5xx)specific (Bearer+Ownership+set_role+entitlement)safe GETyes6/6
GET .../cost_assumptioncomplete (none)complete (`datanull`)complete (200/403)specificsafe GETyes
PUT .../cost_assumptioncomplete (agent_hour_rate≥0,minutes_per_conversation≥0)complete (saved record)complete (200/403/422)specific (owner/admin)upsert (idempotent)yes6/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

JobTriggerInputRetryDLQConcurrencyIdempotencyTimeoutAssessment
AiActivityLogAggregatorWorkersidekiq-cron 0 1 * * * Asia/Jakartaperform(activity_date=yesterday)retry: 3Sidekiq dead set + Rollbarqueue application_maintenance, 1 run/day(org, activity_date) upsertjob-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

TriggerFound?LocationAssessment
PIInoaggregate counts onlyhandled (none)
Payment/banknocost assumption is a rate, not an accounthandled (none)
Healthnon/a
User content w/ retentionnono message content storedhandled
Auth/session datanoorg from session, not stored in these tableshandled
Cross-border transfernosingle-regionn/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

EndpointBackend ResponseFrontend ExpectedMatch?Gaps
GET /v1/reports/ai_agent_impactsuccess envelope, snake_case, work_absorbed/after_hours/sentiment/forecast nullableFE reads res.data raw (snake_case), per-tile null degradeYesnone — verified FE consumes _data raw, no camelCase mapper (OQ-5)
GET .../cost_assumption`data:{...}null`FE branches on null → "set assumption" promptYes
PUT .../cost_assumption200 / 403 / 422FE hides editor on 403, inline on 422YesFE 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

ScenarioFEBEWorks?Notes
Pre-deployOldOldYesbaseline (no report)
Backend firstOldNewYesnew endpoints unused (flag OFF)
Frontend firstNewOldNoforbidden by deploy order (FE would call non-existent endpoints)
Both deployedNewNewYestarget
Backend rollbackNewOldNomitigated: roll FE flag OFF first, then BE
Frontend rollbackOldNewYesBE 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/PhraseLocationImpactConcrete Replacement
1"next-period forecast" (no method)§2.4 / Decision 5agent guesses the modelspecify algorithm + min points (REV-2)
2"message-count source" (histories/summaries)OQ-3agent guesses the table + join keyconfirm source + key (REV-3)
3"sentiment coverage adequate?"§2.1 branch flowthreshold undefinedstate 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

#AlternativesLocationImpact
noneevery "Option A/B/C" resolves to a named choice with rejection rationale

Total dangling alternatives: 0.

Task Decomposition Assessment

ChunkAcceptance CriteriaAssessment
1 Migrationstables + unique indexes; db:rollback revertsverifiable
2 Models + aggregation reponet/gross correct; excludes bot_preview/SPAM/bare RESOLVE; journey sums 100%; reopen self-join within 48hverifiable — but must add: trailing-day recompute for the 48h window (REV-1) + confirmed turns source (REV-3)
3 Aggregator worker + cronupserts one row/org-day; idempotent; sentiment coverage recordedverifiable — extend to trailing-day recompute (REV-1)
4-5 Cost + report endpoints403/422 matrix; baseline_forming; work_absorbed null when unsetverifiable
6 RuboCop + suitelint clean; specs greenverifiable
7-11 FE store/mw/page/tiles/viz/E2EfetchStatus machine; verdict-not-number; flag-gated forecast; owner-only editorverifiable — 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 FILTER idiom (custom_report/generate.rb:108-117), rooms columns + indexes (db/schema.rb:1761+), closed_reason literals (en.yml:346-350 + write sites), sentiment-is-Postgres (with the PRD's MongoDB error honestly corrected), cron/worker, migration house-style, bitbucket-pipelines.yml commands. 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 (mmdc v11.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_count are stored, but the population source is unconfirmed; the plausible histories table joins on bigint room_id while sentiment joins on string 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

  1. 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_date rows on each nightly run (the (org, activity_date) idempotent upsert already supports this), or finalize reopened_48h on 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. §2.3 DDL row 7 + OQ-3 (REV-3) — Confirm the message-count source and its join key before chunk 2. If histories (bigint room_id → rooms.id), document the second join strategy alongside the string-keyed sentiment join; add any needed index.
  3. Decision 5 / §2.4 forecast row (REV-2) — Name the forecast algorithm, minimum trend length, and projected_net bounds; add to chunk-10 acceptance criteria.
  4. §2.0 Source Verification wording (REV-4, REV-10) — Either plan a covering index on rooms for the nightly scan/self-join (none of contact_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; entitlement valid_until window not single order_id; application_maintenance queue lives on other workers) so the evidence table stays trustworthy.

Backend Contract Addendum

Endpoint Contract Details

EndpointMethod/PathAuthZRequestResponseErrorIdempotency/VerStatus
ReportGET /v1/reports/ai_agent_impactBearer+Ownership+set_role(owner/supervisor/admin)+entitlementstart_date,end_date ≤12monested data{containment,journey,quality,trend,work_absorbed?,forecast?}400/403/5xxsafe GET, /v1/Complete
Cost readGET .../cost_assumption+set_role(owner/supervisor/admin)`data{...}null`403safe GET
Cost writePUT .../cost_assumption+set_role(owner/admin){agent_hour_rate≥0,minutes_per_conversation≥0}saved record403/422upsert on (org)Complete (add explicit upper bounds, OQ-3)

Database Changes Details

ChangeTableDDL / ShapeMigrationRollbackCompat WindowStatus
new tableai_activity_logs16 cols, NOT NULL DEFAULT 0 counts, UNIQUE(org, activity_date)additive; disable_ddl_transaction! + concurrent indexdb:rollback (recomputable from rooms)n/a (new)Complete — consider adding source-side rooms index (REV-4)
new tableai_cost_assumptionsUNIQUE(org), CHECK(≥0)additivedb:rollbackn/aComplete

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 ⚠.

OrderChunkFilesAcceptance CriteriaDependencies
1BE migrationsdb/migrate/*_create_ai_activity_logs.rb, *_create_ai_cost_assumptions.rbtables + unique indexes; db:rollback cleanNone
2BE models + aggregation repoapp/models/ai_activity_log.rb, ai_cost_assumption.rb, app/core/repositories/ai_agent_impact/aggregate.rbnet/gross correct; excludes bot_preview/SPAM/bare RESOLVE; journey=100% · ⚠ turns source confirmed (REV-3)1
3BE aggregator worker + cronapp/workers/ai_activity_log_aggregator_worker.rb, config/schedule.ymlidempotent per org-day; sentiment coverage · ⚠ trailing-day 48h reopen recompute (REV-1)2
4-5BE cost + report endpointsapp/api/frontend_service/v1/report.rb + entities + use-cases403/422 matrix; baseline_forming; work_absorbed null when unset2
6BE lint + suiteRuboCop clean; specs green4-5
7-8FE store/service/endpoint + middlewarestore/ai-agent-impact/*, common/services/main/v1/ai-agent-impact.ts, middleware/ai-agent-impact-feature.tsfetchStatus machine; flag+role gate → 404 abort5 (live contract)
9FE page + tiles + statespages/reports/ai-agent-impact/index.vue, `modules/report/viewscomponents/ai-agent-impact/*`, error+retry componentverdict-not-number; baseline-forming; error+retry; view event
10FE journey + forecast + cost modalBlendedJourneyBar.vue, ForecastPanel.vue, CostAssumptionModal.vuesegments=100%; ⚠ forecast method defined (REV-2); owner-only editor9
11FE lint + E2Enav link, tests/e2e/ai-agent-impact.spec.tsowner sees tiles; agent no entry; cost save recomputes10

Dangling Decisions Log

#DecisionLocationOwnerDeadline
D5 (Partial)Forecast derivation method (not the gating)Decision 5, §2.4 forecast rowDimas + BEbefore chunk 10

No fully-dangling decisions. D5 is Partial (gating resolved, method open) — tracked as REV-2.


Open Questions

#QuestionCategorySeverity
1How is reopened_48h finalized across the daily-batch boundary (trailing-day recompute vs 48h lag)? (REV-1)DICBlocking (for chunk 2/3)
2Confirmed source + join key for turns-to-resolve message count? (REV-3 / OQ-3)DMSBlocking (for chunk 2)
3Forecast derivation algorithm + min trend length + bounds? (REV-2)TDCImportant (chunk 10)
4Should 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/DMSImportant
5Exact AI add-on feature code (OQ-1); canonical FE role strings (OQ-2)SAS/DEPImportant (chunk 5/8)
6Business-hours config existence for after-hours tile (OQ-4)DEPNice-to-have (mitigated)

Evidence Notes

  • §2.0 Source Verification — spot-checked against all three repos; ~95% exact. Confirmed: role gate, Ownership, COUNT FILTER, rooms columns/indexes, closed_reason literals + 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.
  • rooms indexes (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 mmdc v11.16.0 (repo map, deployment/service topology, end-to-end component, ER, UI state machine, branch/skip, 4 sequences). No diagram defects.

Review History

CycleDateReviewed RFC revisionScoreVerdictFindings open → fixedNotes
R12026-07-08last_updated 2026-07-08 / working tree8.0PROCEED with notes10 open (3 major, 7 minor; 3 accepted-risk), 0 fixedFirst 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.