add refinement phases
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
# Refinement Phase 6 — Money-path correctness completion
|
||||
|
||||
> **Mission:** close the one genuine money-correctness hole and the referential-integrity gaps left around the
|
||||
> (verified-clean) ledger. The load-bearing invariants — balanced ledger groups, the four money DB CHECKs,
|
||||
> webhook idempotency, forward-only status machines — are all verified in code. This phase fixes the holes
|
||||
> *around* them.
|
||||
>
|
||||
> **Track:** backend (money path) · **Depends on:** nothing hard (do **before** any real BNPL/manual refund —
|
||||
> i.e. before [Phase 8](refinement-phase-8-external-rails.md) money rails) · **Unlocks:** ledger⇄bank
|
||||
> reconciliation
|
||||
> **Before you start, read [../../phases/_shared/agent-operating-rules.md](../../phases/_shared/agent-operating-rules.md).**
|
||||
|
||||
## 1. Context
|
||||
|
||||
This phase **is** the server audit's **post-phase-2**. Full file/line evidence and fixes:
|
||||
**[../server/post-phase-backend-plan.md](../server/post-phase-backend-plan.md) § post-phase-2**. Read it — it is
|
||||
the spec.
|
||||
|
||||
The headline is one real defect: **the BNPL/manual refund settlement path is unreachable.** A card refund posts
|
||||
its `refund_payable ↔ escrow_held` clearing immediately, but a BNPL-revert / manual-bank refund is left in
|
||||
`processing` with the clearing "deferred to reconciliation" — and **no reconciliation path exists anywhere**:
|
||||
`Refund.MarkSucceededAsync` has **zero callers**, no endpoint/webhook/job performs `processing → succeeded`.
|
||||
So every BNPL/manual refund permanently overstates `escrow_held` and strands `refund_payable`; the ledger will
|
||||
never reconcile with the bank. This must be fixed before real BNPL/manual refunds exist.
|
||||
|
||||
## 2. Required reading
|
||||
|
||||
- **[../server/post-phase-backend-plan.md](../server/post-phase-backend-plan.md)** § post-phase-2 (items
|
||||
2.1–2.6) — the spec, with every file/line.
|
||||
- `server/CLAUDE.md` "Refunds, clawbacks & invoices" + "Payments core" — the invariants you must preserve.
|
||||
- The [Phase 3](refinement-phase-3-contract-batch.md) refund REQs (019/020/021/035) — coordinate: the customer
|
||||
cancel command + admin preview/approve you may build there interact with the settlement fix here.
|
||||
|
||||
## 3. Scope — deliver plan items 2.1–2.6
|
||||
|
||||
- **6.1 (top fix) — wire the unreachable refund settlement.** Add a `ConfirmRefundSettlementCommand` (admin
|
||||
`POST admin_refunds/{id}/confirm_settlement` + a BNPL-callback branch when the provider confirms customer
|
||||
cash-back) that transitions `processing → succeeded`, stamps `settled_at`, and posts
|
||||
`LedgerPosting.RefundPayableClearing` in the same commit; plus a `mark_failed` counterpart. Tests for both
|
||||
channels.
|
||||
- **6.2 — add the promised forward-dep FKs** (one additive migration + an index): `refunds.ticket_id`,
|
||||
`nurse_clawbacks.original_payout_id` / `recovered_in_payout_id`, `invoices.partner_center_id` (+ index). The
|
||||
target tables all shipped and the values are wired; only the constraints are missing. Fix the now-false config
|
||||
comments too.
|
||||
- **6.3 — extend `IAuditable`** to the admin-decided money & trust entities (`Refund`, `NurseClawback`,
|
||||
`NursePayout`, `NursePayoutBatch`, `NurseVerification`) so approve/reject/process and the `is_verified` flip
|
||||
leave an audit-diff row. Confirm `[AuditRedacted]` covers `iban_snapshot` first.
|
||||
- **6.4 — close the refund channel-execute-before-commit crash window** (persist a `pending` refund row before
|
||||
the external channel call, then execute and update — the two-phase intent/confirm shape the webhook handler
|
||||
already uses).
|
||||
- **6.5 — test the untested admin money paths** (`WriteOffClawbackCommand` / bad-debt group; a true racing
|
||||
webhook-insert; messaging `is_internal` handler-level tests).
|
||||
- **6.6 — retire the orphaned `refund_ticket_required` config key** (b15 superseded it) or repurpose it; fix the
|
||||
false description.
|
||||
|
||||
## 4. Mocks & seams
|
||||
|
||||
None new. This is domain/ledger code over existing seams; the BNPL/PSP mocks stay until
|
||||
[Phase 8](refinement-phase-8-external-rails.md).
|
||||
|
||||
## 5. Critical rules
|
||||
|
||||
- **The ledger stays append-only and balanced.** Every new posting is a balanced group under one
|
||||
`transaction_group_id`; the settlement clearing must reconcile (`Σdebit = Σcredit`).
|
||||
- **Idempotency preserved.** The settlement confirm and the crash-window fix must be safe to replay (the
|
||||
idempotency key + status machine are the backstop).
|
||||
- **Do 6.1 before real BNPL/manual refunds** ([Phase 8](refinement-phase-8-external-rails.md)) — otherwise real
|
||||
money strands ledger state.
|
||||
- Don't regress the four DB CHECKs (`CK_Bookings_AmountSplit`, `CK_NursePayouts_NetSplit`,
|
||||
`CK_Refunds_LegSplit`, `CK_BnplTransactions_SettleSplit`).
|
||||
|
||||
## 6. Definition of Done
|
||||
|
||||
- [ ] A BNPL/manual refund can reach `succeeded` (via admin confirm + BNPL callback) and posts the
|
||||
`refund_payable ↔ escrow_held` clearing — proven by a test that the ledger reconciles.
|
||||
- [ ] The three forward-dep FK sets + the invoice index exist (additive migration); config comments corrected.
|
||||
- [ ] The five listed entities are `IAuditable` and produce audit-diff rows on admin decisions.
|
||||
- [ ] The refund crash-window is closed; the previously-untested admin money paths have tests.
|
||||
- [ ] `dotnet build` zero new warnings; `dotnet test` green (incl. the new tests).
|
||||
|
||||
## 7. How to test
|
||||
|
||||
- Create a BNPL refund → it lands `processing` → admin `confirm_settlement` → status `succeeded`, `settled_at`
|
||||
set, and `GetNursePayableBalance`/escrow reconcile (no stranded `refund_payable`).
|
||||
- `WriteOffClawback` posts a balanced `bad_debt` group; a replayed settlement confirm is an idempotent no-op.
|
||||
- Inspect `audit_logs` after an admin refund approve / a verification `is_verified` flip → a diff row exists.
|
||||
|
||||
## 8. Hand off & document
|
||||
|
||||
- Update `server/CLAUDE.md` refunds/payments sections + the contract docs for the new admin refund route. Flip
|
||||
the mocks-registry / audit notes for the settlement seam. Save a memory note that the unreachable BNPL/manual
|
||||
refund clearing is now wired (the audit's top code fix).
|
||||
Reference in New Issue
Block a user