From 069cf1eb40a05c373320cafe2f21dbbc113fe73e Mon Sep 17 00:00:00 2001 From: Grant Date: Tue, 16 Jun 2026 19:17:02 -0500 Subject: [PATCH] Bump version to 0.2.0:57 (merchant-onboard scoped-key role) --- startos/versions/v0.2.0.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/startos/versions/v0.2.0.ts b/startos/versions/v0.2.0.ts index d288ccc..d62b07d 100644 --- a/startos/versions/v0.2.0.ts +++ b/startos/versions/v0.2.0.ts @@ -39,6 +39,8 @@ const RELEASE_NOTES = [ // in RELEASE_NOTES above (the milestone). Subsequent revisions // append here. const ROUTINE_NOTES = [ + '0.2.0:57 — **New `merchant-onboard` scoped-API-key role for least-privilege self-serve onboarding.** A fifth scoped-key role sits between `license-issuer` and `full-admin`, granting read access plus `products:write` + `policies:write` + `licenses:write` — the minimum a merchant (or an integrating agent) needs to stand up a catalog end-to-end over the API: create a product, define its policies/tiers, and issue licenses against them, all without holding the master key. The catalog write *scopes* already existed and were enforced on the endpoints since :55; only a role that expands to them was missing, so this is a `Role`-variant addition, not a scope-model change. `Role::grants` matches the write scopes explicitly (never by `:write` suffix), so the role can never widen into settings / payment-provider / merchant-profile / webhook writes, and every master-only operation (signing-key rotation, payment connect, web-admin password, API-key management, server settings, per-license tier change, DB introspection) stays behind `require_admin` and is structurally unreachable from any scoped key. Existing Creator-tier caps still bound it (5 products / 5 policies per product / 10 active codes). **Caveat:** the role covers catalog setup + manual license issuance fully, but connecting a BTCPay/Zaprite payment provider stays master-only by design, so the buyer-paid purchase flow still needs a one-time operator step. Migration 0023 rebuilds `scoped_api_keys` to widen the role CHECK constraint (SQLite can\'t alter a CHECK in place; the table has no foreign keys, so it\'s a plain copy/drop/rename) — additive, a straight drop-in over :56. Daemon api test suite is at 57, up from 56. No SDK change.', + '', '0.2.0:56 — **Product→merchant-profile write path — multi-profile is now functional end-to-end.** The multi-profile *resolver* has been complete since :52, but products had no way to be *assigned* to a profile, so every product stuck to the auto-created default profile. This cut wires the missing write half. `Product.merchant_profile_id` now threads through all four product SELECTs + `row_to_product`; a new `repo::set_product_merchant_profile` validates the target profile exists first (returns a clean 404 rather than a raw FK 500); it is threaded through `CreateProductReq` (applied as a post-write step) and `UpdateProductReq` (double-`Option` semantics, where `Some(None)` clears a product back to the default profile). The admin SPA shows a merchant-profile `