Record scoped-keys + settle-tripwire work; document boundary and TODOs

Update Current state for the two P1 fixes done this session (source-only,
awaiting :55). Document the advisory settle-amount tripwire in payments.md. Add
Open TODOs: split audit:read into its own scope tier, and build the admin API-keys
management panel (both deferred to later sessions).
This commit is contained in:
Keysat
2026-06-13 00:10:53 -05:00
committed by Keysat
parent ffdb59aa8f
commit 6d4efc8a33
2 changed files with 69 additions and 56 deletions
+22 -4
View File
@@ -65,10 +65,28 @@ webhook body's claim. `api/webhook.rs::handle_inner` re-fetches
taking ANY settle-derived action (license issuance, tier-change, subscription
renewal — the guard sits ahead of all of them). On a provider-API error it acks
`200` without issuing — the reconcile loop re-confirms and issues on its next tick
(fail-closed on issuance, and a 2xx avoids a provider retry-storm). **Not yet
done**: a literal paid-amount/currency check (the trait exposes only a status
enum); trusting the provider's own `Settled` determination is the current
boundary — see the auditor's open P1.
(fail-closed on issuance, and a 2xx avoids a provider retry-storm).
**Settle-amount tripwire (advisory, not a gate).** `get_invoice_status` returns a
`ProviderInvoiceSnapshot { status, amount }`; on a confirmed settle,
`audit_settle_amount` (shared by the webhook + reconcile issue paths) compares the
provider's reported sat amount against the invoice's `amount_sats` and, on
mismatch, logs a WARN + writes an `invoice.amount_mismatch` audit row — then
**issues anyway**. It is deliberately NOT a hard gate: a literal "amount actually
paid" check is redundant with the `Settled` requirement (both providers only
report `Settled` for a paid-in-full invoice — Zaprite maps `UNDERPAID`
`Pending`), and a strict `paid >= owed` gate would false-reject operators running
a BTCPay payment tolerance (a deliberate config we must not second-guess). The
tripwire catches *drift* — settling a SAT invoice for an amount other than the
`amount_sats` we recorded. It applies ONLY to SAT-denominated settles: one-shot
purchases and SAT subscriptions always charge `Money::sats` (= `amount_sats`), but
fiat-priced subscription RENEWALS (`subscriptions.rs`) create the order in the
listed fiat currency, where `amount_sats` is not the charged amount — those report
a non-SAT currency and are **skipped** (no clean comparison basis; `Settled`
already covers them). `amount` is likewise `None`/skipped when the provider
response carries no parseable positive amount. Regression tests in `tests/api.rs`:
`settled_amount_mismatch_issues_license_but_audits`,
`settled_non_sat_settle_skips_amount_tripwire`.
## Provider connect