Update Current state: 0.2.0:57 shipped (merchant-onboard role)
This commit is contained in:
@@ -110,77 +110,53 @@ Operator-specific memories at `~/.claude/projects/-Users-macpro-Projects-keysat/
|
|||||||
|
|
||||||
## Current state (2026-06-16)
|
## Current state (2026-06-16)
|
||||||
|
|
||||||
- **Live**: registry `registry.keysat.xyz` publishes **`0.2.0:56`** — universal
|
- **Live (registry/canonical)**: `registry.keysat.xyz` + `files.keysat.xyz/keysat.s9pk`
|
||||||
multi-arch, GitHub release `v0.2.0-56`, canonical s9pk at
|
publish **`0.2.0:57`** — universal multi-arch (x86_64 + aarch64), GitHub release
|
||||||
`files.keysat.xyz/keysat.s9pk`. Migrations 0020–0022; four SDKs published;
|
`v0.2.0-57`. Migrations 0020–0023; four SDKs published; `keysat.xyz` +
|
||||||
`keysat.xyz` + `docs.keysat.xyz` deployed. **Live server `immense-voyage.local`
|
`docs.keysat.xyz` deployed.
|
||||||
now runs `:56`** — deployed via `make install` this session and verified: public
|
- **Live server `immense-voyage.local` still runs `:56`** — `:57` was published to the
|
||||||
`/v1/products` 200, and `/v1/admin/tier` with a read-only scoped key flipped
|
registry this session but NOT `make install`-deployed to the box (`publish.sh` doesn't
|
||||||
403→200, confirming the `:55` `require_scope` rollout is now active on the box.
|
touch the live host). Run `make install` from `licensing-service-startos/` to put the
|
||||||
(`:55` shipped scoped keys, the settle-amount tripwire, and all 4 StartOS
|
new role on the live server when ready.
|
||||||
submission blockers; `:56` adds the product→merchant-profile write path — see git log.)
|
- **Shipped this session as `:57`** (feature `d5885d1`, bump `069cf1e`; pushed
|
||||||
- **Shipped + RELEASED this session as `:56` (daemon feature `b088bfc`,
|
origin+gitea) — **`merchant-onboard` scoped-key role** for least-privilege self-serve
|
||||||
version-bump `6b02992`, doc-fix `d2846ac`; pushed origin+gitea)** —
|
onboarding: read + `products:write` + `policies:write` + `licenses:write` (create
|
||||||
**product→merchant-profile write path, closing the multi-profile GAP.**
|
product → define policies/tiers → issue licenses, no master key). New `Role` variant
|
||||||
`Product.merchant_profile_id` + all 4 product SELECTs + `row_to_product`; new
|
only — the catalog write scopes already existed and were enforced since `:55`;
|
||||||
`repo::set_product_merchant_profile` (validates profile exists → 404, not FK 500);
|
`grants()` matches scope strings explicitly (never `:write` suffix) so it can't widen
|
||||||
threaded through `CreateProductReq` (post-write) + `UpdateProductReq`
|
into settings/payment/profile/webhook writes, and every master-only op stays behind
|
||||||
(double-Option, `Some(None)` clears to default); admin SPA profile `<select>`
|
`require_admin`. Migration 0023 widens the `scoped_api_keys` role CHECK (no FKs → plain
|
||||||
shown only when >1 profile. Mirrors the entitlements-catalog post-write pattern.
|
rebuild). **Caveat**: covers catalog + manual issuance fully, but payment-provider
|
||||||
Reviewer pass: approved, no blockers. **Multi-profile is now functional
|
connect stays master-only, so buyer-paid purchase still needs a one-time operator step.
|
||||||
end-to-end** (resolver was already complete; only the write path was missing).
|
See `src/api/api_keys.rs`.
|
||||||
See `docs/guides/payments.md`.
|
|
||||||
|
|
||||||
- **Committed this session (`d5885d1`, pushed origin+gitea; NOT yet
|
|
||||||
version-bumped/released)** — **new `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) without the master key. The catalog write scopes already
|
|
||||||
existed and were enforced; only the `Role::grants` expansion was missing, so
|
|
||||||
this is a new `Role` variant, not a scope-model change. `grants()` matches
|
|
||||||
scope strings **explicitly** (never by `:write` suffix) so it can't widen into
|
|
||||||
settings/payment/merchant-profile/webhook writes; every master-only op stays
|
|
||||||
behind `require_admin` and is unreachable; tier caps still bound it. Migration
|
|
||||||
0023 rebuilds `scoped_api_keys` to widen the role CHECK (no FKs → plain
|
|
||||||
copy/drop/rename). **Caveat for the doc-onboarding harness/marketing**: this
|
|
||||||
credential covers catalog + manual license issuance fully, but **cannot connect
|
|
||||||
a payment provider** (master-only by design), so the buyer-paid purchase flow
|
|
||||||
still needs a one-time operator step. See `src/api/api_keys.rs`.
|
|
||||||
|
|
||||||
- **Work queue (next, in order)**:
|
- **Work queue (next, in order)**:
|
||||||
1. 3 remaining multi-profile UIs (rail picker, per-profile SMTP, rail-pref
|
1. Deploy `:57` to `immense-voyage.local` (`make install`) when ready.
|
||||||
editor).
|
2. Operator data action (needs master key): grant `unlimited_merchant_profiles` to
|
||||||
2. Operator data action (now unblocked — `:56` is live): grant
|
Pro/Patron on the live master (see Open TODOs).
|
||||||
`unlimited_merchant_profiles` to Pro/Patron on the master (see Open TODOs).
|
3. 3 remaining multi-profile UIs (rail picker, per-profile SMTP, rail-pref editor) —
|
||||||
3. Deferred (now in Open TODOs): split `audit:read` out of the blanket `:read`
|
see ROADMAP.
|
||||||
scope into its own tier.
|
4. Split `audit:read` out of the blanket `:read` scope into its own tier (Open TODOs).
|
||||||
|
|
||||||
- **Discovered this session (P2, unfixed)**: `set_product_entitlements_catalog`
|
- **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
|
has no `rows_affected` guard — a bad product-id silently 200s with stale data (latent
|
||||||
(latent since migration 0014; the new profile writer guards this correctly).
|
since migration 0014; the new profile writer guards this correctly); no rate-limit on
|
||||||
One-line fix, deferred pending the user's call.
|
`/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.
|
||||||
|
|
||||||
- **Known debt (P2 — schedule, not urgent)**: no rate-limit on `/v1/purchase` +
|
- **P3+ (bulk or later decision)**: `/v1/purchase` 400 vs `/v1/btcpay/webhook` 503 for
|
||||||
`/v1/redeem`; rate-limit bucket keys on spoofable `X-Forwarded-For` (bypass
|
the same no-provider cause; undocumented required `kind` on discount-codes;
|
||||||
conditional on whether the StartOS proxy rewrites XFF — unverified); `422`/`415`
|
field-naming drift (`license_id`/`id`, machines `key` vs `license_key`,
|
||||||
errors return plain-text not JSON (breaks SDK `JSON.parse`); product `slug` has
|
`redeem`/`purchase` `product` vs `validate` `product_slug`); migration
|
||||||
no validation (empty/300-char/meta chars stored); `GET /v1/admin/products`
|
`_sqlx_migrations` allowlist foot-gun; 2 KB unauth Zaprite payload WARN-log;
|
||||||
returns 405 though OpenAPI documents it; dep advisories (`sqlx`→≥0.8.1
|
outbound-webhook SSRF (operator-only); re-register master Zaprite webhook at the
|
||||||
RUSTSEC-2024-0363, `rustls-webpki`→≥0.103.12); no CI + fmt/clippy/prettier
|
path-keyed URL; registry icon non-render (platform limit); design-contract conformance
|
||||||
unenforced. (The 4 StartOS submission blockers are resolved and shipped in `:55`.)
|
(see ROADMAP); optional fmt/prettier standalone commit.
|
||||||
|
|
||||||
- **Deferred (P3+ — bulk or later decision)**: `/v1/purchase` 400 vs
|
- **Tests/build**: `cargo check` + `npm run check` (tsc) clean (1 intentional
|
||||||
`/v1/btcpay/webhook` 503 for the same no-provider cause; undocumented required
|
deprecation warning); full suite green — unit 10, api **57** (incl. the
|
||||||
`kind` on discount-codes; field-naming drift (`license_id`/`id`, machines `key`
|
merchant-onboard onboard-chain + master-only-denial test), subscriptions 7, upgrades 9,
|
||||||
vs `license_key`, `redeem`/`purchase` `product` vs `validate` `product_slug`);
|
worker 3, crosscheck 4, migrations 9 (through 0023). No new clippy warnings. FK
|
||||||
migration self-heal `_sqlx_migrations` allowlist foot-gun; 2 KB unauth Zaprite
|
enforcement confirmed — sqlx pool sets `foreign_keys(true)` per connection.
|
||||||
payload WARN-log; outbound-webhook SSRF (operator-only); 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); full
|
|
||||||
suite green with the merchant-onboard role — unit 10, api **57** (incl. the
|
|
||||||
merchant-onboard onboard-chain + master-only-denial test), subscriptions 7,
|
|
||||||
upgrades 9, worker 3, crosscheck 4, migrations 9 (now through 0023). No new
|
|
||||||
clippy warnings. FK enforcement **confirmed** — sqlx pool sets
|
|
||||||
`foreign_keys(true)` per connection (`db/mod.rs`). CI/fmt status is in Known debt.
|
|
||||||
|
|||||||
Reference in New Issue
Block a user