Handoff: phase-2 foundation + payment-connect spec/roadmap

This commit is contained in:
Keysat
2026-06-16 21:18:57 -05:00
parent 6dc5c8a740
commit 1788c9b082
2 changed files with 46 additions and 42 deletions
+36 -42
View File
@@ -114,50 +114,44 @@ Operator-specific memories at `~/.claude/projects/-Users-macpro-Projects-keysat/
publish **`0.2.0:57`** — universal multi-arch (x86_64 + aarch64), GitHub release
`v0.2.0-57`. Migrations 00200023; four SDKs published; `keysat.xyz` +
`docs.keysat.xyz` deployed.
- **Live server `immense-voyage.local` still runs `:56`** — `:57` shipped to the registry
this session, but the box wasn't redeployed (that publish run predated this session's
change adding `make install` as `publish.sh` step 5). Run `make install` from
`licensing-service-startos/` once to catch the box up to `:57`; future `publish.sh` runs
now deploy to the live host automatically (best-effort, non-fatal).
- **Shipped this session as `:57`** (feature `d5885d1`, bump `069cf1e`; pushed
origin+gitea) — **`merchant-onboard` scoped-key role** for least-privilege self-serve
onboarding: read + `products:write` + `policies:write` + `licenses:write` (create
product → define policies/tiers → issue licenses, no master key). New `Role` variant
only — the catalog write scopes already existed and were enforced since `:55`;
`grants()` matches scope strings explicitly (never `:write` suffix) so it can't widen
into settings/payment/profile/webhook writes, and every master-only op stays behind
`require_admin`. Migration 0023 widens the `scoped_api_keys` role CHECK (no FKs → plain
rebuild). **Caveat**: covers catalog + manual issuance fully, but payment-provider
connect stays master-only, so buyer-paid purchase still needs a one-time operator step.
See `src/api/api_keys.rs`.
`:57` shipped the **`merchant-onboard`** scoped role (catalog + license self-serve, no
master key; `src/api/api_keys.rs`) — see git log for detail.
- **Live box `immense-voyage.local`**: `:57` was deployed this session via `make install`
(`start-cli package install` returned clean; StartOS applies the swap async — **not
independently confirmed**; verify the StartOS UI shows `0.2.0:57`). `publish.sh` now runs
`make install` as step 5, so future ships auto-deploy (best-effort, non-fatal).
- **In progress — agent-payment-connect (phase 2)**. Approved spec:
`plans/agent-payment-connect-scope.md`. Lets a scoped key connect a BTCPay provider, but
ONLY on a sandbox daemon and ONLY for a non-mainnet network — never folded into a role
(a key that can repoint settlement is a fund-redirection key).
- **Foundation committed this session (`3afac07`, origin+gitea; NOT version-bumped)** —
slices 12 of 5: `Config.sandbox_mode` (env `KEYSAT_SANDBOX_MODE`, never API-settable;
surfaced in `/v1/admin/tier`); migration 0024 `scoped_api_keys.extra_scopes`; per-key
à-la-carte scopes (`GRANTABLE_EXTRA_SCOPES=["payment_providers:write"]`, granted via
`extra_scopes`, in NO role — `grants()` carves it out of full-admin's wildcard;
fail-closed parsing). Reviewer pass clean after fixing the full-admin-wildcard P1.
- **Pending — slices 35**: `require_provider_connect` gate (master→any; scoped+
`payment_providers:write`→only if `sandbox_mode` AND non-mainnet); BTCPay OAuth wiring
(record `scoped_initiator` in `btcpay_authorize_state` at `start_connect`, network-check
at `finish_connect`, migration 0025); Zaprite stays master-only. **The BTCPay on-chain
address network detection MUST be validated against a live regtest box** before
shipping (classify address prefix `bc1`/`tb1`/`bcrt1`, fail-closed to mainnet; the
payment-method id is `BTC-CHAIN` vs `BTC` by version).
- **Work queue (next, in order)**:
1. Deploy `:57` to `immense-voyage.local` (`make install`) when ready.
2. Operator data action (needs master key): grant `unlimited_merchant_profiles` to
Pro/Patron on the live master (see Open TODOs).
3. 3 remaining multi-profile UIs (rail picker, per-profile SMTP, rail-pref editor) —
see ROADMAP.
4. Split `audit:read` out of the blanket `:read` scope into its own tier (Open TODOs).
1. Build gate slices 35 (above) — validate the BTCPay address fetch on regtest.
2. Confirm `:57` is live on `immense-voyage.local` (StartOS UI).
3. Operator data action (master key): grant `unlimited_merchant_profiles` to Pro/Patron.
4. 3 multi-profile UIs + split `audit:read` (ROADMAP / Open TODOs).
- **P2 (unfixed, deferred pending user's call)**: `set_product_entitlements_catalog`
has no `rows_affected` guard — a bad product-id silently 200s with stale data (latent
since migration 0014; the new profile writer guards this correctly); no rate-limit on
`/v1/purchase`+`/v1/redeem` (bucket keys on spoofable `X-Forwarded-For`); `422`/`415`
return plain-text not JSON (breaks SDK `JSON.parse`); product `slug` unvalidated;
`GET /v1/admin/products` 405 vs OpenAPI; dep advisories (`sqlx`→≥0.8.1
RUSTSEC-2024-0363, `rustls-webpki`→≥0.103.12); no CI, fmt/clippy/prettier unenforced.
- **P2/P3 debt (unchanged)**: `set_product_entitlements_catalog` missing `rows_affected`
guard; no rate-limit on purchase/redeem (spoofable XFF); `422`/`415` plain-text not JSON;
`slug` unvalidated; `GET /v1/admin/products` 405 vs OpenAPI; dep advisories (`sqlx`→≥0.8.1,
`rustls-webpki`→≥0.103.12); no CI / fmt-clippy unenforced; field-naming drift; outbound
webhook SSRF; design-contract conformance (see ROADMAP).
- **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
`_sqlx_migrations` allowlist foot-gun; 2 KB unauth Zaprite payload WARN-log;
outbound-webhook SSRF (operator-only); re-register master Zaprite webhook at the
path-keyed URL; registry icon non-render (platform limit); design-contract conformance
(see ROADMAP); optional fmt/prettier standalone commit.
- **Tests/build**: `cargo check` + `npm run check` (tsc) clean (1 intentional
deprecation warning); full suite green — unit 10, api **57** (incl. the
merchant-onboard onboard-chain + master-only-denial test), subscriptions 7, upgrades 9,
worker 3, crosscheck 4, migrations 9 (through 0023). No new clippy warnings. FK
- **Tests/build**: `cargo check` + `npm run check` clean (1 intentional deprecation
warning); full suite green — lib unit **13**, api **59**, subscriptions 7, upgrades 9,
worker 3, crosscheck 4, migrations 9 (through 0024). No new clippy warnings. FK
enforcement confirmed — sqlx pool sets `foreign_keys(true)` per connection.