Triage full-eval findings into Current state: work queue / known debt / deferred

This commit is contained in:
Keysat
2026-06-12 21:58:43 -05:00
parent ac2aa85b7e
commit be7dfa5d8c
+55 -24
View File
@@ -117,28 +117,59 @@ Operator-specific memories at `~/.claude/projects/-Users-macpro-Projects-licensi
product's sales to them. The data model + resolver fully support it; only the
product→profile **write path** is missing. **This is the gating piece for
multi-profile** — see the scoped slice below.
- **Next, in priority order**:
(1) **Product→merchant-profile picker** (gating piece above). Slice:
add `merchant_profile_id: Option<String>` to the `Product` model + its SELECT
column mapping in `repo.rs`; add a `set_product_merchant_profile` follow-up
writer mirroring `set_product_entitlements_catalog`; add the field to
`CreateProductReq`/`UpdateProductReq` (`api/admin.rs`) applied as a post-create/
update follow-up; add a profile `<select>` (populated from
`GET /v1/admin/merchant-profiles`) to the create + edit product forms in
`web/index.html`, rendered only when >1 profile exists. No migration (column
exists since 0020). Default/None → stays on default profile.
(2) resolve the 3 red tests — **delete** the dead
`payment_provider_preference_round_trip`; for the two `paid_purchase_*`, add a
provider-injection seam (recommended: an always-compiled
`Option<Arc<dyn PaymentProvider>>` override on `AppState`, checked first in
`resolve_provider_for_profile_rail`; alt: gate a mock behind a `test-mocks`
cargo feature). See `docs/guides/testing.md`.
(3) build the other 3 deferred UIs (rail picker, per-profile SMTP, rail-pref
editor) + add `unlimited_merchant_profiles` to master Pro/Patron policies;
(4) re-register the master Zaprite webhook; (5) optional: run formatters as a
standalone commit.
- **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.
- **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`.
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`.
- **Then resume feature work**: the **product→merchant-profile picker** (the GAP
above — slice: add `merchant_profile_id` to the `Product` model + `repo.rs`
SELECT mapping; a `set_product_merchant_profile` follow-up writer mirroring
`set_product_entitlements_catalog`; the field on `CreateProductReq`/
`UpdateProductReq` applied post-write; a profile `<select>` from
`GET /v1/admin/merchant-profiles` in the create+edit product forms, shown only
when >1 profile; no migration), the 3 other deferred UIs (rail picker,
per-profile SMTP, rail-pref editor), and `unlimited_merchant_profiles` on
master Pro/Patron policies.
- **Known debt (P2 — schedule, not urgent)**: no rate-limit on `/v1/purchase` +
`/v1/redeem`; rate-limit bucket keys on spoofable `X-Forwarded-For` (bypass
conditional on whether the StartOS proxy rewrites XFF — unverified); `422`/`415`
errors return plain-text not JSON (breaks SDK `JSON.parse`); product `slug` has
no validation (empty/300-char/meta chars stored); `GET /v1/admin/products`
returns 405 though OpenAPI documents it; dep advisories (`sqlx`→≥0.8.1
RUSTSEC-2024-0363, `rustls-webpki`→≥0.103.12); **4 StartOS submission blockers**
missing `instructions.md`, dead `packageRepo` (`…/keysat-startos``…/keysat`) +
`docsUrls` (`/docs/``/licensing-service/docs/`) manifest links, aarch64
declared-but-not-shipped; no CI + fmt/clippy/prettier unenforced.
- **Deferred (P3+ — bulk or later decision)**: `/v1/purchase` 400 vs
`/v1/btcpay/webhook` 503 for the same no-provider cause; undocumented required
`kind` on discount-codes; field-naming drift (`license_id`/`id`, machines `key`
vs `license_key`, `redeem`/`purchase` `product` vs `validate` `product_slug`);
migration self-heal `_sqlx_migrations` allowlist foot-gun; 2 KB unauth Zaprite
payload WARN-log; outbound-webhook SSRF (operator-only); stale
`versions/v0.2.0.ts:3-4` "NOT YET WIRED" comment; re-register the master Zaprite
webhook at the path-keyed URL; registry icon non-render (known platform limit);
optional fmt/prettier standalone commit.
- **Tests/build**: `cargo check` clean (1 intentional deprecation warning); api
43 pass / 3 known-fail (test-debt), other suites green. No CI; fmt/prettier not
enforced or clean. FK enforcement **confirmed** the sqlx pool sets
`foreign_keys(true)` per connection (`db/mod.rs`); the old "latent/unchecked"
caveat is resolved.
43 pass / 3 known-fail (now tracked in the work queue above), other suites
green. FK enforcement **confirmed** — sqlx pool sets `foreign_keys(true)` per
connection (`db/mod.rs`). CI/fmt status is in Known debt.