Skip to main content

RFC Review: One CID Multiple WABA — Billing V3 Shared Balance Pool (Phase 1)

Companion review for rfc-one-cid-multiple-waba-phase-1.md, produced by the rfc-reviewer skill. Lives beside the RFC; valid only for the RFC revision in reviewed_rfc_last_updated (2026-07-01).

Executive Summary

  • Overall Score: 7.0/10 (was 6.5 at first pass; +0.5 after REV-1 closed as already-implemented and Decision 5 resolved)
  • Rating: Strong
  • RFC Type: full-stack
  • Sub-Type: enhancement (FE + BE)
  • Assessment Confidence: High (all five repos were grounded with file:line evidence; contracts are traceable)
  • Applied Caps/Gates: No numeric cap triggered (no category < 5.0). Overall is held below the backend half's strength by the weakest-layer merge rule — the frontend contract layer (now consolidated in Detail 2.F) would force an agent to guess until filled.
  • Implementation Readiness Verdict: Backend half → PROCEED (chunks 1–6 executable now); Frontend half → HOLD — gated solely on the FE-owner task (Detail 2.F: frames + component contracts + UI-states + a11y + analytics). No backend or product blockers remain (Decision 1 confirmed; threshold already implemented).
  • Report Path: bifrost/one-cid-multiple-waba/rfcs/rfc-one-cid-multiple-waba-phase-1-review.md
  • RFC Author: Grehasta Mahardika / Syafrizal M. | Reviewed: 2026-07-01

The backend is genuinely agent-ready: every decision is grounded in real code (e.g. the deduction hierarchy at qontak-billing/.../quota_management/deduction.go:169-241, the optimistic-lock UPDATE at organization_package_component_initials.sql:42-51), Decision 1's cardinality is now confirmed, and the DDL is a single, reversible index change. The biggest strength is that the RFC correctly reframes the work as aggregation + surfacing over an already-complete engine rather than greenfield billing. The biggest gap is the frontend contract layer (now consolidated in Detail 2.F): the three net-new components (SharedBalanceTooltip, LowBalanceBanner, and the waba_id filter dropdown) are named and pattern-anchored but carry no prop types, event payloads, or UI-state matrix, and no accessibility or FE-analytics spec — and Design frames are still pending. The one thing that must change before full agentic execution: the FE owner fills Detail 2.F. All backend/product blockers are cleared (Decision 1 confirmed; low-balance threshold already implemented).


Quick Verdict

Why this RFC can be implemented agentically (backend):

  • Every backend decision cites a real file+line and a reuse/extend tag; the deduction engine already exists, so the diff surface is small and grounded.
  • DDL is one index change with an explicit rollback; migration sequence, backfill, and rollback recipe are concrete and ordered.
  • Concurrency, idempotency (unique_code), and failure paths are specified with real mechanisms, not prose.

Why this RFC will cause agent guessing or rework (frontend only):

  • Net-new FE components have no Props interfaces, event names, or 5-state UI matrix — an agent would invent them (now consolidated in Detail 2.F).
  • Figma frames are pending, so pixel-faithful FE is blocked until Design supplies them (Detail 2.F(a)).
  • FE accessibility + analytics event are unspecified (Detail 2.F(d)(e)).

Findings Ledger (carry-forward)

IDSeverityFinding (one line)RFC locationStatusFirst seenResolved inEvidence / fix
REV-1blockerLow-balance threshold value undecided§4.A Config; Decision 5wontfixR1R1 (already implemented)Closed 2026-07-01 — not a concern. Already implemented: billing_components.threshold_running_out DECIMAL(5,2) (qontak-billing migration 20260126000001_*), 40% default when NULL (deduction.go:299-301, quota_management_alert.go:97-98). Per-org override out of Phase 1 scope.
REV-2blockerFigma frame-level links missing§1 Design References; Detail 2.F (a)openR1Consolidated into the FE-owner task (Detail 2.F). Design to supply per-surface frames; FE chunks 10–11 blocked until then.
REV-3majorNet-new FE components lack prop-type/event contractsDetail 2.F (b); §4.C chunks 9–11openR1Consolidated into Detail 2.F. Props+events for SharedBalanceTooltip, LowBalanceBanner, filter dropdown.
REV-4majorNo UI State Matrix (loading/empty/error/partial/success)Detail 2.F (c)openR1Consolidated into Detail 2.F. Port PRD WABA-S05 UI states into a 5-state matrix.
REV-5majorNo FE accessibility spec for new componentsDetail 2.F (d)openR1Consolidated into Detail 2.F. Keyboard/focus/ARIA for tooltip + banner + filter.
REV-6majorNo FE analytics eventsDetail 2.F (e)openR1Consolidated into Detail 2.F. Define tooltip-interaction event (PRD §11).
REV-7majorcredited_to value vocabulary mismatch (PRD vs qontak-billing)§5 non-blocking #8; Detail 2.4wontfixR1R1 (owner decision)Dropped 2026-07-01 by owner — reconciled at implementation time against the live credited_to/quota_type values, not tracked as a spec task.
REV-8minor/billings/info aggregated response fields not fully typedDetail 2.4 outbound row 1openR1Pin field names + types for aggregated wabi/additional/postpaid + billing_version.
REV-9minorWaConversationLog.status referenced by PRD but absent in schema§5 non-blocking #7openR1Confirm billable/failed decided upstream (no column needed) or add column.
REV-10minorCross-Layer Rollout Compatibility Matrix described in prose, not tabulated§4 RolloutopenR1Add the 6-scenario old/new matrix; deploy order IS specified so no cap.
REV-11minorFE performance budget (LCP/INP/CLS, browser matrix) absent§3 PerformanceopenR1Add Core Web Vitals budget for the Package Usage page delta.
REV-12minorGateway path mapping unconfirmed (/internal/qontak/billing/v1/iag/v1)§5 non-blocking #4; Detail 2.4 noteopenR1Confirm Mekari gateway rewrite; api_spec.yaml authoritative.

Ledger summary: 10 open (1 blocker [REV-2], 4 major [REV-3/4/5/6], 5 minor), 2 wontfix (REV-1 already-implemented, REV-7 owner-dropped), 0 fixed. REV-2/3/4/5/6 are all consolidated into the single FE-owner task, Detail 2.F. Open findings promoted to the RFC Open-Questions surface by id.


PRD → RFC Traceability Matrix

PRD ElementRFC SectionCoverage
§1–4 Problem/Persona/Non-Goals/Constraints§1 Overview, Out of Scope, §3 PerformanceFull
§5 CHG-001/002/003 (report columns, aggregated display)Detail 2.4; Decision 6; Detail 2.AFull (BE) / Partial (FE contract — REV-3)
§6 Shared Balance Tooltip§4.C chunk 10Partial — component contract thin (REV-3), frame pending (REV-2)
§7.1–7.7 API & Webhook behaviorDetail 2.4; Decisions 1–5; Detail 2.CFull
§8 Stories WABA-S01..S07Detail 1.C (all 7 mapped)Full
§9/§9.1 Rollout/Migration window§4 RolloutFull
§10/§10.1 Observability§3 MonitoringPartial — FE analytics missing (REV-6)
§11 Success Metrics§1 Success CriteriaFull
§12 Launch Plan§4 stagesFull
§13 Dependencies§1 DependenciesFull
§14 Key Decisions§2 Technical DecisionsFull
§15 Open Questions (Q1 threshold, Q2 log granularity, Q4 FK assumption)§5; Assumption 1/3; REV-1Q2/Q4 resolved by grounding; Q1 open (REV-1)

Summary: 15 of 15 PRD sections covered; all 7 user stories mapped. 2 partials trace to FE-contract thinness (REV-3/4/6). PRD Open Q2 (log granularity) and Q4 (FK assumption) were answered by grounding — a strength. No RFC decision lacks a PRD driver.


Scorecard (Full-Stack, 18 categories)

#CategorySourceScoreEvidence-Based Rationale
1PRT — PRD TraceabilityMerged9.0FE+BE forward/reverse matrices (Detail 1.A), all 7 stories in Detail 1.C, PRD §-coverage table; even answers PRD Open Q2/Q4.
2TDC — Technical DecisionsMerged8.06 ADR blocks w/ options+reversibility; BE strong. FE decisions limited to Decision 6; tooltip/banner not ADR'd → weaker layer drags.
3CNT — Contract SpecificityFE5.0Net-new components (SharedBalanceTooltip/LowBalanceBanner/filter) have no prop types or event payloads (Detail 2.A not filled for FE). REV-3.
4SCB — Scope BoundariesFE7.5§4.C chunks 7–11 name exact files to create/modify; non-goals explicit.
5DEP — DependenciesFE8.0§1 Dependencies table tags availability (exists/needs-building/blocked) incl. BE endpoints.
6NFS — Non-Functional SpecificityFE5.0Page/query latency targets present; no LCP/INP/CLS, a11y, or browser matrix. REV-5/REV-11.
7TPS — Test Plan SpecificityFE7.0vitest commands sourced from package.json:15; per-chunk AC. No explicit cross-API integration test.
8DMS — Data Model & SchemaBE8.5Index DDL + rollback; existing tables cited w/ columns + migration files; ER diagram; retention/PII.
9ACV — API Contract & VersioningBE7.0Outbound+inbound tables, reuse tags, unique_code idempotency. /billings/info aggregated fields untyped (REV-8); credited_to mismatch (REV-7).
10DIC — Data Integrity & ConsistencyBE8.5Data Integrity Matrix + Concurrency Collision Map; optimistic lock cited at line level; tx scope explicit.
11FMC — Failure Mode CoverageMerged7.0BE strong (retry/requeue/timeout/DLQ catalogs, error catalog). FE lacks 5-state UI matrix (REV-4) → weakest link.
12CSS — Concurrency & ScalingBE7.5Collision map + optimistic lock + bounded retry; QPS/pool numbers absent (advisory).
13SAS — Security & AuthorizationBE8.0Role × Endpoint matrix, company_id tenancy, sqlc parameterization, secrets, static analysis tools named.
14ROL — Rollout & RollbackMerged7.5Deploy order specified (5 ordered steps), rollback recipe, flags, backfill. Compat matrix in prose not tabulated (REV-10). Deploy order present → no 6.0 cap.
15OBS — ObservabilityMerged7.0BE events/metrics/alerts named (PRD §10 mirror). FE analytics missing → merge caps at 7.0 (REV-6).
16SBC — Service Boundary & CouplingBE8.5Responsibility Boundary Matrix; in-process gem vs HTTP-gateway boundary explicit; sync/async labeled.
17CPA — Consistency & Pattern AlignmentMerged8.0Patterns-to-Follow table per layer w/ reference files (Go handler/sqlc, Grape, Pinia, pixel3).
18CDG — Compliance & Data GovernanceBE8.0Triggered (PII: customer_name/phone_number Lockbox-encrypted; financial). Detail 3.C: classification, legal basis, retention, encryption.

Resource & Cost Advisory

  • Detail 4.E present and reasonable (no new pods; +1 Redis key/company; log growth unchanged). Advisory only — does not affect score.

Decision Closure Assessment

Decision Index

#DecisionStatusCritical Gaps
1Shared-pool cardinality (single OrganizationPackage; N WABAs → 1)Resolvednone — cardinality confirmed 2026-07-01; grounded; reversibility stated
2Concurrency on shared pool (optimistic lock + requeue)Resolvedminor — "align to 3 retries" not pinned to a concrete max_fails number
3Aggregated balance read + cachePartialcache TTL value not specified ("short TTL"); invalidation trigger set
4WABI monthly reset (reuse)Resolvednone
5Company-level low-balance + below-zero notificationResolvedthreshold already implemented (40% default, billing_components.threshold_running_out); below-zero branch + dedup key specified
6waba_id column gating data-driven → preference-drivenResolvedFE contract detail thin (ties to REV-3) but decision itself closed

Aggregate: 5 Resolved, 1 Partial (Decision 3 cache TTL), 0 Dangling.

Decision 1 — Shared-pool cardinality

Status: Resolved. Decided: single OrganizationPackage per company; drop the unique index on whatsapp_packages.organization_package_id so N WABAs share it. Alternatives: Option B (many packages + SUM aggregation) documented and rejected for concentrating risk on the money-critical hot path. Grounding: hub_core/.../20260113000000_*.rb, wa_package.rb:213-240, organization_package_components.sql:87-137. Failure/reversibility: gated behind existing waba_save_organization_id; reversibility rated Medium with mitigation. Challenge: holds at scale (single-row optimistic lock); consistent with company-scoped deduction. No gap.

Decision 3 — Aggregated balance read + cache

Status: Partial. Missing: the Redis TTL value (RFC says "short TTL"), and the exact cache-key invalidation ordering vs invalidate-cache. Suggested resolution: pin TTL (e.g. 30–60s given the ≤500ms budget and refresh-based display) and state that deduction/top-up call PUT .../invalidate-cache before returning. Open question: what staleness is acceptable for balance display between a deduction and the next read?

Decision 5 — Low-balance + below-zero notification

Status: Resolved (2026-07-01). The threshold is already implementedbilling_components.threshold_running_out with a 40% default (deduction.go:299-301, quota_management_alert.go:97-98); Phase 1 reuses it (REV-1 closed). The only net-new parts (distinct balance_below_zero event + per-org/per-cycle dedup key) are specified. Minor follow-up: document the dedup window length. A per-org threshold override is explicitly out of Phase 1 scope.


Cross-Layer Contract Verification

EndpointBackend Response SchemaFrontend Expected SchemaMatch?Gaps
GET /billings/info+ billing_version, aggregated wabi/additional/postpaid (untyped in RFC)PackageInfo.billing_version; BillingInfo (needs billing_version)Partialfield names/types not pinned (REV-8); FE BillingStore must add billing_version
GET /mcc_logs?waba_id=rows incl. waba_id (pref ON)WhatsAppBalanceLog.waba_id? (exists)Yesnone — param + optional field already present both sides
GET /download_broadcast_deductionCSV WABA ID col pref-gatedblob download (exists)Yesnone
POST /iag/.../deductioncredited_to = component_code + quota_typePRD/report expect wa_balance_initial/...Partialvocabulary mismatch (REV-7)

Checks: casing (snake_case BE ↔ camelCase FE) — FE customFetch transform not explicitly stated for new fields; nullability ok; error shapes differ by service (Grape vs qontak-billing envelope) — acceptable across the gateway. Mismatches found: 1 material (credited_to vocabulary) → does not cap ROL (it's a data-vocabulary, not an endpoint-shape, mismatch) but is a major (REV-7).

Cross-Layer Rollout Compatibility Matrix

ScenarioFrontendBackendWorks?Notes
Pre-deployOldOldYesbaseline
Backend firstOldNewYesnew fields additive; old FE ignores billing_version; deduction pool-correct
Frontend firstNewOldPartialFE gates on billingM1Version/pref; if BE lacks billing_version in payload, tooltip stays hidden (fail-safe) — acceptable
BothNewNewYestarget
Backend rollbackNewOldPartialsame as "frontend first" — FE degrades safely
Frontend rollbackOldNewYesadditive BE, old FE unaffected

Deploy order: specified — backend first (§4 Rollout steps 1→5). No unaddressed "No". Tabulating this (REV-10) would make it explicit in the RFC.

End-to-End Data Flow — outbound message deduction

Agent sends billable msg
→ hub_service Grape → hub_core Broadcasts::BillingDeduction (stamp waba_id)
→ QuotaManagement HTTP client → Mekari gateway → qontak-billing POST /iag/v1/.../deduction (company_id)
→ GetQuotaForUpdate (pool) → OPC*DeductionQuota (lock_version) → CreateBillingLog (credited_to, quota_type, waba_id)
→ Side effects: enqueue alert if remaining ≤ threshold
→ Response value_before/after/credited_to → hub_core Success → delivery not blocked

Gaps: the credited_to value returned (component_code) vs what reports render (REV-7). Otherwise fully traceable across all five services — a strength.


Strengths

  • Grounded to the line, across 5 repos. Every anchor in Detail 2.0 has file:line evidence (e.g. deduction.go:169-241, billings.rb:489/559, useTopupStore.ts:115-117), and the Source Verification table is complete — an agent can navigate directly.
  • Correct problem reframing. The RFC proves most of the PRD is already built and scopes the true delta (one aggregation change + FE surfacing), and it answers two of the PRD's own Open Questions (Q2 log granularity, Q4 FK assumption) from code.
  • Backend rigor. Decision 1's cardinality is confirmed; DDL is a single reversible index change; concurrency/idempotency/failure paths use real mechanisms (optimistic lock + requeue, unique_code), and the rollback recipe is ordered and flag-gated.

Biggest Gaps

  • FE component contracts absent (REV-3/4/5). SharedBalanceTooltip, LowBalanceBanner, and the waba_id filter dropdown have no prop types, event payloads, UI-state matrix, or a11y — an agent building the FE would guess all of it. This is what drags the merged score to Needs Work.
  • Two blocking Open Questions (REV-1/REV-2). Threshold value blocks WABA-S06's warning branch; Figma frames block pixel-faithful FE. Both are external decisions the RFC correctly flags but cannot self-resolve.
  • credited_to vocabulary unreconciled (REV-7). The report/FE key on strings that differ between the PRD and the actual engine output; unresolved, the waba_id/bucket columns risk showing wrong or empty values.

Priority Actions

  1. Detail 2.F — FE-owner task (REV-2/3/4/5/6) — the single remaining blocker, all frontend. Design supplies frame-level Figma links per surface; the FE owner then pins Props+events for the three net-new components, a 5-state UI matrix per report surface, a11y (keyboard/focus/ARIA), and the tooltip-interaction analytics event. Unblocks FE chunks 9–11.
  2. Detail 2.4 (REV-8) — pin the /billings/info aggregated field names/types (the exact JSON the FE BillingStore will read).
  3. Decision 3 — pin the balance-cache TTL value + invalidation ordering.
  4. Backend chunks 1–6 — no blocker; can start now in parallel with the FE-owner task above.

Backend Contract Addendum

Endpoint Contract Details

EndpointMethod/PathAuthZRequestResponseErrorIdempotency/VersioningStatus
Billing infoGET /api/core/v1/billings/infoOAuth2 :admin,:owner,:supervisor,:agent,:membernone (org from token)+ billing_version, aggregated bucketstimeout → cached + logn/a (read)Missing typed aggregated fields (REV-8)
DeductionPOST /iag/v1/quota-managements/deductioninternal SSO + X-Api-Keycompany_id,billing_code,deduction_code,unique_code,quantity,extra_attrs{waba_id}value_before/after,credited_toquota_exceeded; 400 envelopeunique_codeComplete (vocabulary note REV-7)

Database Changes Details

ChangeTableDDL / Shape DiffMigration PlanRollbackCompatibilityStatus
Drop unique, add non-unique indexwhatsapp_packagesorganization_package_id index unique→non-uniquebackfill FK on V3 rows; verify 100%re-add unique only after re-split (documented)non-unique is a superset; additiveComplete

Implementation Readiness Checklist

Unblocked (backend)

  • PRD→RFC traceability complete
  • Backend decisions resolved with alternatives
  • Backend failure modes + error catalog
  • Schema at DDL precision + rollback
  • Transaction boundaries + idempotency (unique_code)
  • Concurrency collision points + resolution
  • Security: tenancy (company_id), validation, injection, secrets
  • Migration/backfill/rollback recipe
  • Observability (BE metrics/alerts)
  • Task decomposition with per-chunk AC

Blocked (must fix first) — all in the single FE-owner task (Detail 2.F)

  • Figma frames per surface — REV-2 (Design)
  • FE component contracts (props/events) — REV-3
  • 5-state UI matrix — REV-4
  • FE a11y — REV-5
  • FE analytics event — REV-6

Closed since first pass: REV-1 (threshold already implemented), REV-7 (vocabulary, owner-dropped).

Verdict: One consolidated FE-owner task (Detail 2.F) blocks the frontend half. Backend half: Ready to implement now.

Task Manifest

The RFC's Detail 4.C already provides an 11-chunk ordered plan with files + commands + verifiable AC — verified as sound for the backend chunks (1–6) and directionally correct for FE chunks (7–11), which become executable once REV-3/REV-1/REV-2 close. No replacement manifest needed; chunks 1–6 can start now, 7–11 after the FE-contract gaps close.

Dangling Decisions Log

#DecisionLocationOwnerDeadline
none dangling — 2 Partial (Decision 3 TTL, Decision 5 threshold) tracked as REV-1 / Decision-3 note§2Bifrost Eng / PM+Financebefore Ready:yes

Open Questions

#QuestionCategorySeverity
1Low-balance threshold valueclosed 2026-07-01; already implemented (threshold_running_out, 40% default)FMC (REV-1, wontfix)
2FE-owner task (Detail 2.F): frames + component contracts + UI-states + a11y + analyticsCNT/NFS (REV-2/3/4/5/6)Blocking (FE only)
4credited_to string vocabularydropped 2026-07-01 (owner); reconciled at implementation timeACV (REV-7, wontfix)
5Typed /billings/info aggregated response shape?ACV (REV-8)Important
6Cache TTL + invalidation ordering for aggregated balance?TDC (Decision 3)Important
7Is WaConversationLog.status needed, or is billable decided upstream?DMS (REV-9)Nice-to-have
8Gateway path rewrite /internal/qontak/billing/v1/iag/v1 confirmed?SBC (REV-12)Nice-to-have

Evidence Notes

  • Detail 2.0 Source Verification — complete, per-line evidence across 5 repos; drove PRT/CPA/DMS high and confidence to High.
  • Detail 2.A (FE) — the full-stack template's FE UI Contract / UI-State matrix was not filled for the net-new components; drove CNT/FMC down (REV-3/4).
  • §3 (FE) — no a11y/analytics/CWV for FE; drove NFS/OBS down (REV-5/6/11).
  • Decision blocks — 6 ADRs with reversibility; 2 Partial on undecided TTL/threshold.
  • Mermaid — 10/10 blocks parse (validated with mmdc); one ;-in-sequenceDiagram issue was found and fixed pre-review.

Review History

CycleDateReviewed RFC revisionScoreVerdictFindings open → fixedNotes
R12026-07-01last_updated: 2026-07-01 / working tree (uncommitted)7.0BE PROCEED / FE HOLD10 open (1B/4M/5m), 2 wontfixInitial review (6.5), then owner refinements same day: REV-1 closed (threshold already implemented), REV-7 dropped (vocabulary), REV-2/3/4/5/6 consolidated into FE-owner task (Detail 2.F) → 7.0. BE chunks 1–6 executable now. Mermaid 10/10.