RFC Review: Deduction V2.0 — Authorize-at-Send + Settle-Against-Meta
Companion review for
deduction-v2.md, produced by therfc-reviewerskill. Lives beside the RFC; valid only for the RFC revision inreviewed_rfc_last_updated.Note on continuity: the prior
deduction-v2-review.md(R0, 2026-06-23, 7.5/10) reviewed the now-replaced debit-at-webhook design. Because the design changed wholesale (different tables, send-gating, settlement model), its findings (DED-S07 Figma, Modpanelheld_deducted, chunk 9) do not carry forward — this is a fresh ledger (R1) for the authorize-at-send design.
Executive Summary
- Overall Score:
6.5/10 - Rating:
Needs Work - RFC Type:
backend - Sub-Type:
new-feature - Assessment Confidence:
High - Applied Caps/Gates: none triggered (no category below 5.0; PRT/TDC/DMS/DIC/SAS ≥ 5.0; FMC ≥ 4.0). 6.5 is a judgment score, not a cap.
- Implementation Readiness Verdict:
HOLD— the backend ledger (T1, T2, T4, T6) is executable today, but four things would force an agent to guess or a reviewer to reject: the PRD contradicts the headline decision (OQ-1), the one consumer-facing endpoint contract is unspecified (REV-2),customer_refmay store a customer phone in plaintext (REV-3), and settlement worker-level failure/alerting is unpinned (REV-4). - Report Path:
bifrost/deduction-v2/rfcs/deduction-v2-review.md - RFC Author: addo.hernando@mekari.com, hafriz.damarsidi@mekari.com | Reviewed: 2026-06-29
This RFC is unusually well-grounded on the ledger mechanics: six full ADRs, DDL-precise tables,
a Source Verification table where every anchor is a real quoted file in hub_core, and a
greenfield grep proving none of it is half-built. An agent can implement the hold table, CreateHold,
the webhook transition, and the sweeper directly. The score is held down by the edges of the
system, not its core: the get usage balance change (the single client-visible contract) is
described as "return available_wa_balance" with no path/schema/error taxonomy; settlement workers
are retry: 0 with no DLQ/alert/timeout spec; observability names events but not metrics/SLOs; and
customer_ref introduces a plaintext-PII question the existing wa_conversation_log answers with
Lockbox. The one thing that must change before approval is OQ-1: the source PRD still forbids
send-blocking (Non-Goal #4), so the RFC's central feature has no PRD mandate — that is a governance
blocker, not a code one.
Quick Verdict
Why this RFC can be implemented agentically:
- The backend ledger spine (tables, models,
CreateHold, webhook transition, FIFO settlement, sweeper) is DDL- and file-precise, grounded against verifiedhub_coreanchors with a 7-chunk execution plan. - Decisions are closed as ADRs with reversibility (flag-off restores current behavior), and the greenfield grep removes "is this already built?" ambiguity.
Why this RFC will cause agent guessing or rework:
- The Available-only balance endpoint has no contract (path, request/response schema, error codes) — an agent would invent the response shape (REV-2).
customer_refstorage (customer phone or bsuid) has no PII classification or encryption decision while the siblingwa_conversation_logLockbox-encryptsphone_number— an agent would guess plaintext (REV-3).- Settlement workers are
retry: 0with no DLQ/timeout/alert thresholds — an agent would ship a job that silently dies (REV-4).
Findings Ledger (carry-forward)
| ID | Severity | Finding (one line) | RFC location | Status | First seen | Resolved in | Evidence / fix |
|---|---|---|---|---|---|---|---|
REV-1 | blocker | Source PRD contradicts the RFC's headline decision — PRD §5 Non-Goal #4 forbids send-blocking, which the send-gate does; no PRD mandate for the core feature | §1 banner, §1.A, OQ-1 | open | R1 | — | Refresh the PRD (or author a new one) to the authorize-at-send design before approved. RFC flags it honestly but cannot self-resolve. |
REV-2 | major | Balance endpoint (get usage balance) contract unspecified — no path, request/response schema, field shape, or error taxonomy for the Available-only change | §2.4 | open | R1 | — | Add an endpoint contract row: exact path, the response field that changes, before/after example payloads, error codes. |
REV-3 | major | customer_ref (customer phone or bsuid) stored on wa_balance_holds with no PII classification/encryption/retention, while wa_conversation_log Lockbox-encrypts phone_number | §2.3 DDL, §3 | open | R1 | — | Classify customer_ref; decide Lockbox vs plaintext + retention; if only bsuid is ever stored, state that constraint and enforce it. |
REV-4 | major | Settlement/sweeper workers are retry: 0 with no DLQ, per-message timeout, or alert thresholds; recovery leans solely on the pending-reopen window | §2.F, §3 | open | R1 | — | Specify what catches a crashed worker run (re-dispatch? alert?), a timeout, and concrete alert thresholds for missing_hold_on_delivered / reconciliation_shortfall. |
REV-5 | minor | Observability names events but not exact metric identifiers/tags, SLO targets, or dashboard location; no "debug at 3am" note | §3 | open | R1 | — | Pin metric names + tags, an SLO (settlement success %, tally-exactness), and a dashboard pointer. |
REV-6 | minor | DMS lacks quantified volume/growth projection and an example row for either new table | §2.3 | open | R1 | — | Add rows/day + steady-state estimate and one example row per table. |
REV-7 | minor | Settlement money-movement atomic write-set described in prose, not pinned — which writes are in the one locked txn (batch upsert + hold stamp + N WaConversationLog rows + pool deduction)? | Decision 4, §2.G | open | R1 | — | Enumerate the exact transaction boundary and ordering for the per-bucket settlement commit. |
REV-8 | minor | OQ-2 (Services::Preference entry point in hub_core) and OQ-3 (Meta currency conversion source/rate) unresolved | §2.0 note, OQ-2/3 | open | R1 | — | Confirm the hub_core flag-read API; define the currency source + conversion point. |
Ledger summary: 8 open (1 blocker / 3 major / 4 minor), 0 fixed this cycle (fresh ledger for the replaced design), 0 accepted-risk. All 8 promoted to the RFC Open-Questions surface below (REV-1/2/4/8 already partly tracked as OQ-1/2/3).
PRD → RFC Traceability Matrix
The source PRD (v1.4) documents the earlier debit-at-webhook design. The RFC maps the correspondence and flags divergence (good practice), but the underlying truth is that the PRD does not justify — and in one case forbids — this design. That is the dominant PRT finding (REV-1).
| PRD Element | RFC Section | Coverage |
|---|---|---|
| DED-S01 real-time hold | §2.2 send seq, CreateHold | Partial — re-scoped from webhook-time to send-time |
| DED-S01/AC-2 balance nets holds | §2.4, Decision 5 | Full (mechanism differs: reserve vs debit) |
| DED-S02 daily reconcile to settled | §2.2 settle seq, SettleDaily | Full (reverse→re-credit replaced by settle→capture) |
| DED-S02/AC-2 history shows settled | report rows only | Partial — Meta-actual rows, but no aggregate-row UI (dropped) |
| DED-S03 date-scoped re-trigger | SettleDaily target_date | Full |
| DED-S04 WABA TZ stamping | WaTimezoneResolver in settle worker | Full |
| DED-S05/S06 delayed local-midnight toggle | — | Missing (intentionally dropped; plain flag instead) |
| DED-S07 daily-aggregate UI | — | Missing (intentionally dropped; Out of Scope #5) |
| PRD §5 Non-Goal #4 "no send-blocking" | send-gate (Decision 5, T3) | Contradicted — RFC builds the opposite (REV-1) |
| Send-gate / over-spend block (RFC core) | Decision 5, T3 | No PRD driver — net-new, contradicts a Non-Goal |
wa_balance_holds/wa_reconciliation_batches | §2.3 | No direct PRD driver (PRD specified held_deducted/wa_held_deduction_logs) |
Summary: ~5 of 9 PRD stories fully covered, 2 partial, 2 intentionally dropped; 1 direct contradiction (Non-Goal #4) and ≥2 core RFC decisions without PRD justification. The divergence is documented, not silent — which keeps PRT off the floor — but the contradiction means an agent reading PRD+RFC together gets conflicting direction on the headline feature.
Scorecard
Backend Scorecard (12 core + 1 conditional)
| Category | Score | Evidence-Based Rationale |
|---|---|---|
| PRT — PRD Traceability | 6.0 | Bidirectional matrix present and honest (§1.A), but the PRD contradicts the core decision (Non-Goal #4) and ≥2 decisions have no PRD driver (REV-1). Documented divergence ≠ justified scope. |
| TDC — Technical Decisions | 8.0 | Six full ADRs (Decisions 1–6) with options/rationale/consequences/reversibility. Residuals are scoped to OQs, not left dangling in the decision bodies. |
| DMS — Data Model & Schema | 7.5 | Full DDL for both tables, every index justified, partial-unique double-charge guard, uuid tenant ids, lifecycle table. Missing: volume/growth projection, example rows (REV-6). |
| ACV — API Contract & Versioning | 5.0 | §2.4 extends get usage balance to "return available_wa_balance" with no path, request/response schema, field, or error taxonomy. Webhook reused (no contract change). The one client-facing contract is underspecified (REV-2). |
| DIC — Data Integrity & Consistency | 7.5 | §2.G collision map; idempotency via partial-unique (external_id,state) + batch unique key + reconciliation_batch_id IS NULL; Meta-fail → batch pending; reset interplay via is_auto_deduct=false. Atomic write-set for settlement is prose, not enumerated (REV-7). |
| FMC — Failure Mode & Retry Coverage | 6.0 | Good design-level recovery (pending-reopen, 30d shortfall, missing-hold metric, reuses Meta 3× retry). But workers are retry: 0 with no DLQ/timeout, and no error-response catalog for the balance endpoint (rubric gate for ≥7.0). (REV-4) |
| CSS — Concurrency & Scaling | 7.0 | §2.G covers package with_lock, hold dedup, batch idempotency, bounded gate↔hold over-commit (A-5), FIFO delivered_at ASC, sweeper in_batches(500). New-worker concurrency caps not restated (inherits existing throttle by reference). |
| SAS — Security & Authorization | 7.0 | §3 threat model (double-charge = top threat, mitigated by flag + partial-unique), tenancy by organization_id, parameterized AR, rake arg validation, Brakeman, no name/phone on holds — except the customer_ref plaintext-PII gap (REV-3) and balance-read authz left as "existing oauth2". |
| MRP — Migration & Rollout Plan | 7.5 | Flag wa_hold_settlement default-off; deploy-all-off → register → enable per org; stages; stop conditions; rollback = flag off + target_date backfill; additive migrations. No data backfill needed (holds accrue forward). |
| OBS — Observability Definition | 6.0 | Events named conceptually (missing_hold_on_delivered, shortfall, expired) + one alert family, but no exact metric names/tags, no SLO, no dashboard, no debug runbook (REV-5). |
| SBC — Service Boundary & Coupling | 8.0 | Clear: all logic in hub_core; hub_service ingress unchanged; Meta external; moderator-be only for flag registration. Per-service responsibility diagram present. |
| CPA — Pattern Alignment | 8.5 | Patterns-to-Follow + Source Verification cite real files (AbstractModelBilling, Dry::Monads, dispatcher/per-org worker pair, find_or_create_by+with_lock, AbstractHttp, colocated specs). Strongest category. |
| CDG — Compliance & Data Governance | 6.0 | Active (stores customer_ref = customer phone/bsuid; tenant ids; money path). Holds deliberately avoid name/phone, but customer_ref is uncharacterized and unencrypted vs the Lockbox precedent (REV-3). No retention/right-to-delete for the hold rows. |
Resource & Cost Advisory
- No advisory blocker. Net-new: one INSERT + indexed
Σ(holds)read per billable send; a nightly settlement batch reusing the existingpricing_analyticsfetch (no new external calls); two new tables on the:billingshard growing with billable PMP volume (bounded only if a hold-purge is added later — not in scope). Route table-growth to infra planning if PMP volume is high.
Decision Closure Assessment
Decision Index
| # | Decision | Status | Critical Gaps |
|---|---|---|---|
| 1 | Authorize-at-send + capture-at-EOD model | Resolved | none — alternatives + reversibility documented |
| 2 | Meta pricing_analytics as report source-of-truth | Resolved | none |
| 3 | Two new tables, uuid tenant ids | Resolved | volume projection, example rows (REV-6) |
| 4 | FIFO allocation, residual-to-last, pending-reopen | Resolved* | atomic write-set not enumerated (REV-7) |
| 5 | Available-only balance via one helper | Partial | endpoint contract unspecified (REV-2) |
| 6 | Single flag wa_hold_settlement (nested in wa_new_pricing_v2) | Partial | hub_core flag-read entry point unconfirmed (REV-8/OQ-2); "nested" semantics not defined |
| — | Meta currency conversion | Dangling | source/rate/conversion point undefined (OQ-3) |
Aggregate: 4 Resolved, 2 Partial, 1 Dangling.
Decision 5 — Available-only balance display (Partial)
- What was decided: show
Available = Pooled − Reserved; theget usage balanceendpoint and the send-gate both callHelpers#available_wa_balance(Decision 5, §2.4). - Interface specification: incomplete. The helper signature is implied (
available_wa_balance(wa_package)), but the endpoint contract is not: no path, no response field name/type, no before/after JSON, no error codes. An agent can write the helper but will guess the API response shape. - Failure handling: not specified for the endpoint (what does it return if the hold-sum query fails?).
- Agent implementability: helper yes; endpoint no.
- Suggested resolution: add an ACV row — path, the exact field that flips to Available, an example payload pre/post-flag, and the error code on read failure.
Decision 6 — Single flag wa_hold_settlement (Partial)
- What was decided: gate every new branch on
Services::Preferenceflagwa_hold_settlement, nested inwa_new_pricing_v2(Decision 6). - Grounding: the flag is read in
hub_core(e.g.wa_new_pricing_v2in specs) but theServices::Preferencedefinition was found inmoderator-be— the exact hub_core entry point is an Open Question (OQ-2). "Nested insidewa_new_pricing_v2" is asserted but the operational meaning (doeswa_new_pricing_v2gatewa_hold_settlement? both must be on?) is not defined. - Suggested resolution: confirm the hub_core
enabled?call site and state the nesting rule explicitly (e.g. "wa_hold_settlementis only honored whenwa_new_pricing_v2is also on").
Data Integrity Deep-Dive
| Write Path | Transaction Scope | Partial Failure Behavior | Idempotency Key | Consistency | Duplicate Handling |
|---|---|---|---|---|---|
| Create hold at send | single INSERT, no pool mutation | nothing to roll back (insert-only); on dup → Success | partial-unique (external_id, state) (rescue RecordNotUnique → Success) | strong (single row) | idempotent insert |
Webhook transition (held→delivered/→refunded) | single-row state update | idempotent (re-delivery = no-op); missing hold → Success+metric, no deduction | hold external_id | strong | idempotent transition |
| EOD settlement (per bucket) | "one locked txn per bucket" — exact write-set not enumerated (REV-7) | Meta-fail → batch stays pending, retried; mid-write crash behavior unstated | wa_reconciliation_batches unique key + reconciliation_batch_id IS NULL candidate filter | strong per bucket | re-run consumes only unsettled holds → idempotent |
| Monthly reset interplay | shared package with_lock; settlement rows is_auto_deduct=false | reset gap queries (L222-238) ignore settlement rows | n/a | strong | n/a |
Strong overall; the single gap is REV-7 — name the writes inside the per-bucket commit and their ordering.
Concurrency Collision Map
| # | Shared Resource | Writers | Collision | Resolution | Lock-Failure Behavior | Assessment |
|---|---|---|---|---|---|---|
| 1 | package pools | settlement vs monthly reset vs (flag-off) live deduction | concurrent debit | package with_lock + is_auto_deduct=false for settlement; settlement re-enqueues on StaleObjectError | second waits / re-enqueue | adequate |
| 2 | wa_balance_holds (same wamid) | dup send, out-of-order webhook | two holds / two transitions | partial-unique (external_id, state) | second insert rejected → Success | adequate |
| 3 | settlement bucket | concurrent settle runs | double-settle | batch unique key + reconciliation_batch_id IS NULL | reload on RecordNotUnique | adequate, but candidate-select→stamp window not shown atomic (relates REV-7) |
| 4 | gate ↔ hold | concurrent sends | over-commit | bounded over-commit accepted (A-5), not serialized | n/a | adequate (documented trade-off) |
API Contract Completeness Check
| Endpoint | Request | Response | Error Taxonomy | Auth | Idempotency | Examples | Assessment |
|---|---|---|---|---|---|---|---|
get usage balance (extended) | n/a (read) | missing (field/shape unspecified) | missing | "existing oauth2" (vague) | n/a (read) | no | 1/6 — underspecified (REV-2) |
POST /webhook/wa/:code | reused | reused | reused | channel-code (existing) | partial-unique on hold | n/a | reuse, no contract change |
Async Job / Event Consumer Spec
| Job | Trigger | Input | Retry | DLQ | Concurrency | Idempotency | Timeout | Assessment |
|---|---|---|---|---|---|---|---|---|
CreateHold | send success (inline) | message/channel ctx | n/a | n/a | n/a | partial-unique (external_id,state) | n/a | 6/7 (inline, fine) |
SettleWaHoldsDispatcherWorker | cron ~01:00 ICT | — | retry: 0 | none | fan-out | — | none | 3/7 (REV-4) |
SettleWaHoldsWorker | dispatcher | (org) | retry: 0 + re-enqueue on StaleObjectError | none | per [waba,tz] | batch key | none | 4/7 (REV-4) |
SweepStaleWaHoldsWorker | cron ~02:00 ICT | — | retry: 0 | none | in_batches(500) | state='held' only | none | 4/7 |
retry: 0 is consistent with the existing daily workers (a deliberate pattern), but with no DLQ/alert a crashed run is silent until the next night. REV-4: pin the alert + re-dispatch behavior.
Compliance Trigger Check
| Trigger | Found? | Data Location | Classification | Assessment |
|---|---|---|---|---|
| PII (phone) | yes | wa_balance_holds.customer_ref (customer phone or bsuid) | uncharacterized | unhandled — plaintext vs Lockbox undecided (REV-3) |
| PII (business number) | yes | wa_balance_holds.phone_recipient | business identifier (not customer PII) | acceptable (it's the Meta match key) |
| Payment/financial | yes | pool deduction, settlement amounts | financial | handled (no card data) |
| Cross-border | partial | Meta pricing_analytics (external) | — | out of scope of this change |
CDG Status: Active — scored 6.0. The deliberate "no name/phone on holds" stance is undercut by customer_ref.
Strengths
- Decision discipline (TDC 8.0, CPA 8.5). Six ADRs with reversibility, every pattern tied to a quoted
hub_corefile, and a Source Verification table + greenfield grep — the agent never has to ask "does this exist?" or "what pattern?". - Ledger correctness model (DIC 7.5). The "report is written only by settlement, only with Meta's number" invariant (Decision 2) makes the tally guarantee structural, and idempotency is layered (partial-unique + batch key +
reconciliation_batch_id IS NULL). - DDL precision (DMS 7.5). Both tables are migration-ready: types, defaults, the partial-unique
(external_id, state)double-charge guard, and uuid tenant ids corrected from the plan's bigint.
Biggest Gaps
- REV-1 (governance blocker): the PRD forbids the headline feature (send-blocking, Non-Goal #4). Until the PRD is refreshed, the RFC's central decision has no product mandate — §1/§1.A flag it but cannot resolve it.
- REV-2 (ACV 5.0): the only client-facing contract — the Available-only balance read — has no schema/path/error spec (§2.4). An agent ships a guessed response shape.
- REV-3 (CDG/SAS):
customer_refstores a customer phone in plaintext on a new table whilewa_conversation_logLockbox-encrypts the same datum (§2.3). An agent picks the wrong (insecure) default.
Priority Actions
- OQ-1 / REV-1 — Refresh the PRD to the authorize-at-send design (reverse Non-Goal #4, restate stories around hold→deliver→settle). This is the approval gate; the code is implementable but unmandated until then.
- §2.4 / REV-2 — Specify the balance endpoint contract: path, the exact response field that becomes
Available, before/after example payloads (flag off vs on), and the read-failure error code. Without it ACV cannot clear 7.0. - §2.3 / REV-3 — Decide
customer_refhandling: classify it, choose Lockbox-encrypt vs store-bsuid-only, and set retention. Mirror thewa_conversation_logprecedent unless there's a stated reason not to. - §2.F + §3 / REV-4 — Pin worker failure + alerting: what catches a crashed settlement/sweep run, a per-run timeout, and numeric alert thresholds for
missing_hold_on_deliveredandreconciliation_shortfall.
Backend Contract Addendum
Endpoint Contract Details
| Endpoint | Method/Path | AuthZ | Request | Response | Error | Idempotency/Versioning | Status |
|---|---|---|---|---|---|---|---|
| Usage balance | GET <path — UNSPECIFIED> | "existing oauth2, own org" (under-specified) | n/a | MISSING — which field carries Available? type? | MISSING | additive behind flag | Missing fields (REV-2) |
Database Changes Details
| Change | Table | DDL / Shape | Migration | Rollback | Compat Window | Status |
|---|---|---|---|---|---|---|
| new table | wa_balance_holds | complete (§2.3, all cols + 6 indexes + partial-unique) | additive create | flag-off (behavioral); drop if abandoned | n/a (greenfield) | Complete |
| new table | wa_reconciliation_batches | complete (§2.3, unique bucket key) | additive create | flag-off; drop if abandoned | n/a | Complete |
customer_ref PII | wa_balance_holds | column present; classification/encryption undecided | — | — | — | Missing (REV-3) |
Implementation Readiness Checklist
Unblocked (agent can proceed)
- Technical decisions resolved with alternatives (6 ADRs)
- Schema at DDL precision (both tables)
- Transaction boundaries/idempotency per write path (except settlement atomic-set, REV-7)
- Concurrency collision points listed with resolutions
- Pattern alignment verified (Source Verification table)
- Rollout plan + flag + rollback
- Task decomposition with acceptance criteria (task breakdown, 7 chunks)
- Mermaid diagrams valid (9/9 parse)
Blocked (must fix first)
- REV-1 — PRD refreshed to mandate send-blocking (governance)
- REV-2 — balance endpoint contract specified
- REV-3 —
customer_refPII classification + encryption decided - REV-4 — settlement worker DLQ/timeout/alert thresholds
Verdict: Fix 4 blockers first (1 governance, 3 spec). The BE ledger chunks (T1, T2, T4, T6) are safe to start in parallel; T3 (balance display) waits on REV-2, and approval/T5-rollout waits on REV-1/REV-3/REV-4.
Task Manifest
The RFC specifies decomposition in
deduction-v2.task-breakdown.md(T1–T7). Verified — chunks map to §4.C and carry files + commands + acceptance criteria. No re-derivation needed; the manifest is sound. The only manifest-level note: T3 should not start until REV-2 (endpoint contract) is closed.
Dangling Decisions Log
| # | Decision | Location | Owner | Deadline |
|---|---|---|---|---|
| 1 | Meta currency conversion source/rate/point | OQ-3, Decision 4 consequences | Bifrost Eng | before T5 |
| 2 | wa_hold_settlement nesting semantics vs wa_new_pricing_v2 | Decision 6 | Bifrost Eng | before T1 flag wiring |
Open Questions
| # | Question | Category | Severity |
|---|---|---|---|
| 1 | Will the PRD be refreshed to mandate send-blocking, or is this RFC the new source of truth (PRD deprecated)? | PRT | Blocking |
| 2 | Exact response contract of the Available-only balance endpoint? | ACV | Blocking |
| 3 | customer_ref: Lockbox-encrypt, or constrain to bsuid-only? Retention? | CDG/SAS | Blocking |
| 4 | Crashed settlement/sweep run: re-dispatch, alert, timeout? | FMC/OBS | Important |
| 5 | Exact metric names/tags + SLO for settlement tally-exactness? | OBS | Important |
| 6 | hub_core Services::Preference entry point (OQ-2) + currency (OQ-3) | TDC | Important |
Evidence Notes
- §2.3 DDL — both tables migration-ready (drove DMS 7.5);
customer_refline drove CDG to 6.0 and REV-3. - §2.4 APIs — "return
available_wa_balance" with no schema drove ACV to 5.0 (REV-2). - §2.F async spec + §3 —
retry: 0, no DLQ/timeout, events-not-metrics drove FMC 6.0 / OBS 6.0 (REV-4/5). - §1 banner + §1.A traceability — honest PRD-divergence flagging kept PRT at 6.0 rather than lower, but the Non-Goal #4 contradiction is REV-1.
- Decisions 1–6 + Source Verification — drove TDC 8.0 / CPA 8.5; the strongest part of the RFC.
- Mermaid — 9/9 blocks validated with
mmdc(topology, per-service, balance, repo map, ER, state, 3 sequences); no parse failures.
Review History
| Cycle | Date | Reviewed RFC revision | Score | Verdict | Findings open → fixed | Notes |
|---|---|---|---|---|---|---|
| R0 | 2026-06-23 | last_updated 2026-06-23 (debit-at-webhook design) | 7.5 | PROCEED w/ notes | n/a | Reviewed the now-replaced design; superseded by the design pivot. |
| R1 | 2026-06-29 | last_updated 2026-06-29 / 19dd270 (authorize-at-send) | 6.5 | HOLD | 0 fixed, 8 open (fresh ledger) | First review of the authorize-at-send design. Strong ledger core; blocked by PRD contradiction + endpoint/PII/worker-failure gaps. |