diff --git a/AGENTS.md b/AGENTS.md index 3cc217d..3a9d95a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -101,63 +101,39 @@ Operator-specific memories at `~/.claude/projects/-Users-macpro-Projects-keysat/ - Build the admin SPA "API keys" management panel (create w/ role picker, list, revoke) — backend is wired; UI deferred to a design-focused session. -## Current state (2026-06-13) +## Current state (2026-06-16) -- **Live**: registry `registry.keysat.xyz` now publishes **`0.2.0:55`** — universal - multi-arch (x86_64 + aarch64, verified via the registry index + s9pk header), - GitHub release `v0.2.0-55`, canonical s9pk at `files.keysat.xyz/keysat.s9pk`. - Migrations 0020–0022; four SDKs published; `keysat.xyz` + `docs.keysat.xyz` - deployed (docs redeployed this session). **The live server `immense-voyage.local` - still runs `:54` until updated from the registry.** `:55` ships the two - prior-session P1s (scoped keys, settle-amount tripwire) + this session's packaging - work. -- **Shipped this session (`:55` published; all committed + pushed)** — doc-auditor + - start9-spec-checker + reviewer passes, all approved/no blockers; `tsc`/`bash -n` - clean. Landed: **all 4 StartOS submission blockers** (new `instructions.md`; - manifest `packageRepo`/`docsUrls` dead-link fixes; **universal multi-arch publish** - — `publish.sh` → `make universal`, registry entry carries both arches with no arch - restriction); enforce-mode drift cleaned from `activateLicense.ts`/`showCredentials.ts` - (enforce retired, `self_license.rs:15`); 5 doc-drift fixes (api 47→54; FK confirmed - `db/mod.rs:29`; `integrate.html` phantom `GET /v1/licenses/{id}/status` → `POST - /v1/validate`; `v0.2.0.ts` stale header; go README v0.2); **refunds scrubbed from - public docs** (Keysat has no refund feature — out-of-band only, v0.3 revoke-on-refund - hook is a no-op; admin-UI "handle out of band" disclaimers kept). Remotes: keysat-root - → gitea-only; keysat-docs, daemon, go SDK → GitHub `origin` + gitea backup. -- **`:52`/`:53` = multi-provider/merchant-profile model**: data model + backend - resolution shipped and audited sound; resolution/CRUD query surface has tests. - Both `:54` P0s (provider-injection test seam; Zaprite webhook-forgery re-confirm) - remain fixed; live purchase + settle paths sound. - -- **Shipped in `:55` (was the prior session's unshipped P1s)**: - 1. **Settle-amount tripwire.** `get_invoice_status` now returns - `ProviderInvoiceSnapshot { status, amount }`; `audit_settle_amount` (shared by - webhook + reconcile issue paths) WARNs + writes an `invoice.amount_mismatch` - audit row on drift, then **issues anyway** (advisory, not a gate — a hard gate - would fight BTCPay payment tolerance). SAT-only: skips non-SAT (fiat sub - renewals) and `None`. Reviewed (caught + fixed a fiat-renewal false-positive). - See `docs/guides/payments.md`. - 2. **Scoped API keys wired.** 58 admin endpoints migrated `require_admin`→ - `require_scope`; 12 sensitive ones stay master-only (issuer key, provider - connect/disconnect, set-password, api-key CRUD, db-info, operator-name, - per-license tier change). `require_scope` re-exported from `api::admin`. Role - boundary tests added. Boundary documented in `api/api_keys.rs` module doc. - -- **GAP — multi-profile still non-functional end-to-end**: nothing writes - `products.merchant_profile_id` (INSERT in `create_product_with_currency` omits - it; `update_product_with_currency` has no field; `Product` in `models.rs` lacks - it). Resolver fully supports it; only the product→profile **write path** is - missing. **Gating piece for multi-profile.** +- **Live**: registry `registry.keysat.xyz` publishes **`0.2.0:55`** — universal + multi-arch, GitHub release `v0.2.0-55`, canonical s9pk at + `files.keysat.xyz/keysat.s9pk`. Migrations 0020–0022; four SDKs published; + `keysat.xyz` + `docs.keysat.xyz` deployed. Live server `immense-voyage.local` + still runs `:54` until updated from the registry. **This session's work is + committed + pushed to `main` but UNRELEASED — no `:56` cut, not built.** (`:55` + shipped scoped keys, the settle-amount tripwire, and all 4 StartOS submission + blockers — see git log.) +- **Shipped this session (daemon `b088bfc`, pushed origin+gitea; unreleased)** — + **product→merchant-profile write path, closing the multi-profile GAP.** + `Product.merchant_profile_id` + all 4 product SELECTs + `row_to_product`; new + `repo::set_product_merchant_profile` (validates profile exists → 404, not FK 500); + threaded through `CreateProductReq` (post-write) + `UpdateProductReq` + (double-Option, `Some(None)` clears to default); admin SPA profile `` from - `GET /v1/admin/merchant-profiles`, shown only when >1 profile; no migration). - 2. 3 other deferred UIs (rail picker, per-profile SMTP, rail-pref editor); - `unlimited_merchant_profiles` on master Pro/Patron policies. - 3. Deferred this session (now in Open TODOs): split `audit:read` out of the - blanket `:read` scope; build the admin "API keys" management SPA panel. + 1. 3 remaining multi-profile UIs (rail picker, per-profile SMTP, rail-pref + editor); `unlimited_merchant_profiles` on master Pro/Patron policies. + 2. Cut `:56` to ship this session's write path to the registry (bump manifest → + `make universal` → `publish.sh`) — bundle with the UIs above if landing soon. + 3. Deferred (now in Open TODOs): split `audit:read` out of the blanket `:read` + scope; build the admin "API keys" management SPA panel. + +- **Discovered this session (P2, unfixed)**: `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). + One-line fix, deferred pending the user's call. - **Known debt (P2 — schedule, not urgent)**: no rate-limit on `/v1/purchase` + `/v1/redeem`; rate-limit bucket keys on spoofable `X-Forwarded-For` (bypass diff --git a/docs/guides/payments.md b/docs/guides/payments.md index 70b9d74..c878b53 100644 --- a/docs/guides/payments.md +++ b/docs/guides/payments.md @@ -39,6 +39,14 @@ merchant_profiles (1) ──< (N) payment_providers - **merchant_profile_rail_preferences** — tie-breaker `(profile, rail) → provider` when a profile has two providers serving the same rail. +Products attach to a profile via `repo::set_product_merchant_profile` (validates +the profile exists → 404, else UPDATE), called **post-write** from the create / +update handlers — same pattern as the entitlements catalog. The admin product form +renders the profile `