Document P0 webhook fix + test seam; ship :54; track EVALUATION.md

This commit is contained in:
Keysat
2026-06-12 22:42:29 -05:00
parent be7dfa5d8c
commit ffdb59aa8f
4 changed files with 131 additions and 39 deletions
+25 -23
View File
@@ -58,7 +58,8 @@ tests/crosscheck/ cross-language LIC1 verifier → guides/crypto
Note: the daemon (`licensing-service-startos`, repo `keysat`), each SDK, and
`plans/` are **separate git repos** — commit code/plan changes in their own repo.
The root `Licensing` repo (`keysat-root`) tracks only `AGENTS.md` + `docs/guides/`
+ `.claude/rules/`. **Remotes differ per repo**: the daemon's `main` tracks
+ `.claude/rules/` + `EVALUATION.md` (the latest full-eval report; overwritten
each run, history in git log). **Remotes differ per repo**: the daemon's `main` tracks
**GitHub** (`origin`, the public upstream) with a `gitea` backup — plain `git push`
goes to GitHub, so also `git push gitea main`; root + plans are **Gitea-only**.
Run `git remote -v` (full) and check what the branch tracks before pushing.
@@ -97,17 +98,19 @@ Operator-specific memories at `~/.claude/projects/-Users-macpro-Projects-licensi
## Current state (2026-06-13)
- **Live**: server `immense-voyage.local` runs daemon `0.2.0:53` (migrations
00200022 applied). Registry `registry.keysat.xyz` now publishes `:53` too
(GitHub release `v0.2.0-53` cut; `files.keysat.xyz` serves the s9pk). Four SDKs
- **Live**: server `immense-voyage.local` runs daemon `0.2.0:54` (migrations
00200022 applied). Registry `registry.keysat.xyz` publishes `:54` too
(GitHub release `v0.2.0-54` cut; `files.keysat.xyz` serves the s9pk). Four SDKs
published; `keysat.xyz` + `docs.keysat.xyz` deployed.
- **`:52`/`:53` = multi-provider/merchant-profile model**: data model + backend
resolution shipped and audited sound; the resolution/CRUD query surface now has
test coverage. See `docs/guides/payments.md`.
- **Purchase-path bug fixed and shipped**: the `:52` ambiguous-column bug (broke
*every* paid purchase) was fixed in daemon `31f4670`; `:53` (version bump
`8c4bacc`) built, installed to prod, and published to the registry on
2026-06-13. The live purchase path works again.
- **Two payment-path fixes shipped 2026-06-13**: (a) `:53` fixed the `:52`
ambiguous-column bug that broke *every* paid purchase (daemon `31f4670`); (b)
`:54` fixed the **P0 Zaprite webhook-forgery** — settle now re-confirms against
the provider API before issuing (daemon `783372c`, bump `495fbbf`). Both built,
installed to prod, and published to the registry. Live purchase + settle paths
are sound.
- **GAP — multi-profile is non-functional end-to-end**: nothing in the shipped
app writes `products.merchant_profile_id` (the INSERT in
`create_product_with_currency` omits it; `update_product_with_currency` has no
@@ -118,23 +121,22 @@ Operator-specific memories at `~/.claude/projects/-Users-macpro-Projects-licensi
product→profile **write path** is missing. **This is the gating piece for
multi-profile** — see the scoped slice below.
- **Triage from `EVALUATION.md` (full-eval, 2026-06-13)** — P0/P1 = work queue,
P2 = known debt, P3+ = deferred. The report at repo root has file:line evidence.
P2 = known debt, P3+ = deferred. The report at repo root has file:line evidence;
it's tracked, so re-running full-eval overwrites it and `git log -- EVALUATION.md`
preserves prior runs.
- **Work queue (P0/P1 — do first, in this order)**:
1. **[P1] Provider-injection test seam — BEFORE #2's fix.** The prod purchase/
settle path has no mock seam (the mock injects into the dead `state.payment`
singleton, not `resolve_provider_for_profile_rail` — this is how the `:52`
500 shipped). Add an always-compiled `Option<Arc<dyn PaymentProvider>>`
override on `AppState`, checked first in `resolve_provider_for_profile_rail`.
Greens the two `paid_purchase_*` red tests and gives #2 a regression harness.
**Delete** the dead `payment_provider_preference_round_trip` in the same pass.
2. **[P0] Zaprite webhook forgery.** The settle-webhook is unauthenticated — a
forged `order.change`/`status=PAID` with a buyer-visible order id mints a
signed license (the `externalUniqId` "trust anchor" in the comments is never
read in the webhook path). Fix: on settle, re-fetch `get_invoice_status` from
the provider and require local `pending` state + matching amount/currency
before issuing (mirror the reconciler's safe re-fetch). Closes the P1 below
too. `payment/zaprite/provider.rs:234-362`, `api/webhook.rs:62-196,121-194`.
1. **SHIPPED in `:54` — Provider-injection test seam.** Added the
always-compiled `AppState::provider_override` + `provider_from_row` helper at
every resolution site; greened the two `paid_purchase_*` tests; deleted the
dead `payment_provider_preference_round_trip`. See `docs/guides/testing.md`.
2.**SHIPPED in `:54` — Zaprite webhook forgery fix.** `webhook.rs::handle_inner`
re-fetches `provider.get_invoice_status` and requires `Settled` before any
settle-derived action; acks 200 (no issue) when the provider is unreachable.
Two regression tests (forged-settle, provider-unreachable). api 47/47.
**Still open (auditor P1):** a literal paid-amount/currency check — needs a
trait change (`get_invoice_status` returns only a status enum). See
`docs/guides/payments.md`. **This is now the top remaining security item.**
3. **[P1] Scoped API keys (`ks_…`) are non-functional** — issuable but 403 on
every admin endpoint; the `require_admin``require_scope` migration was never
done. Finish it, or stop advertising/issuing them. `api/api_keys.rs:14`.