Record product→merchant-profile write path; refresh Current state
Document the now-functional product→profile write path in the payments guide (set_product_merchant_profile, post-write pattern, picker gating, double-Option clear). Mark the multi-profile GAP closed, drop the done work-queue item, and note the discovered set_product_entitlements_catalog rows_affected gap.
This commit is contained in:
@@ -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,
|
- Build the admin SPA "API keys" management panel (create w/ role picker, list,
|
||||||
revoke) — backend is wired; UI deferred to a design-focused session.
|
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
|
- **Live**: registry `registry.keysat.xyz` publishes **`0.2.0:55`** — universal
|
||||||
multi-arch (x86_64 + aarch64, verified via the registry index + s9pk header),
|
multi-arch, GitHub release `v0.2.0-55`, canonical s9pk at
|
||||||
GitHub release `v0.2.0-55`, canonical s9pk at `files.keysat.xyz/keysat.s9pk`.
|
`files.keysat.xyz/keysat.s9pk`. Migrations 0020–0022; four SDKs published;
|
||||||
Migrations 0020–0022; four SDKs published; `keysat.xyz` + `docs.keysat.xyz`
|
`keysat.xyz` + `docs.keysat.xyz` deployed. Live server `immense-voyage.local`
|
||||||
deployed (docs redeployed this session). **The live server `immense-voyage.local`
|
still runs `:54` until updated from the registry. **This session's work is
|
||||||
still runs `:54` until updated from the registry.** `:55` ships the two
|
committed + pushed to `main` but UNRELEASED — no `:56` cut, not built.** (`:55`
|
||||||
prior-session P1s (scoped keys, settle-amount tripwire) + this session's packaging
|
shipped scoped keys, the settle-amount tripwire, and all 4 StartOS submission
|
||||||
work.
|
blockers — see git log.)
|
||||||
- **Shipped this session (`:55` published; all committed + pushed)** — doc-auditor +
|
- **Shipped this session (daemon `b088bfc`, pushed origin+gitea; unreleased)** —
|
||||||
start9-spec-checker + reviewer passes, all approved/no blockers; `tsc`/`bash -n`
|
**product→merchant-profile write path, closing the multi-profile GAP.**
|
||||||
clean. Landed: **all 4 StartOS submission blockers** (new `instructions.md`;
|
`Product.merchant_profile_id` + all 4 product SELECTs + `row_to_product`; new
|
||||||
manifest `packageRepo`/`docsUrls` dead-link fixes; **universal multi-arch publish**
|
`repo::set_product_merchant_profile` (validates profile exists → 404, not FK 500);
|
||||||
— `publish.sh` → `make universal`, registry entry carries both arches with no arch
|
threaded through `CreateProductReq` (post-write) + `UpdateProductReq`
|
||||||
restriction); enforce-mode drift cleaned from `activateLicense.ts`/`showCredentials.ts`
|
(double-Option, `Some(None)` clears to default); admin SPA profile `<select>`
|
||||||
(enforce retired, `self_license.rs:15`); 5 doc-drift fixes (api 47→54; FK confirmed
|
shown only when >1 profile. Mirrors the entitlements-catalog post-write pattern.
|
||||||
`db/mod.rs:29`; `integrate.html` phantom `GET /v1/licenses/{id}/status` → `POST
|
Reviewer pass: approved, no blockers. **Multi-profile is now functional
|
||||||
/v1/validate`; `v0.2.0.ts` stale header; go README v0.2); **refunds scrubbed from
|
end-to-end** (resolver was already complete; only the write path was missing).
|
||||||
public docs** (Keysat has no refund feature — out-of-band only, v0.3 revoke-on-refund
|
See `docs/guides/payments.md`.
|
||||||
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.**
|
|
||||||
|
|
||||||
- **Work queue (next, in order)**:
|
- **Work queue (next, in order)**:
|
||||||
1. **product→merchant-profile picker** (the GAP — add `merchant_profile_id` to
|
1. 3 remaining multi-profile UIs (rail picker, per-profile SMTP, rail-pref
|
||||||
`Product` + `repo.rs` SELECT; `set_product_merchant_profile` writer mirroring
|
editor); `unlimited_merchant_profiles` on master Pro/Patron policies.
|
||||||
`set_product_entitlements_catalog`; field on `CreateProductReq`/
|
2. Cut `:56` to ship this session's write path to the registry (bump manifest →
|
||||||
`UpdateProductReq` applied post-write; profile `<select>` from
|
`make universal` → `publish.sh`) — bundle with the UIs above if landing soon.
|
||||||
`GET /v1/admin/merchant-profiles`, shown only when >1 profile; no migration).
|
3. Deferred (now in Open TODOs): split `audit:read` out of the blanket `:read`
|
||||||
2. 3 other deferred UIs (rail picker, per-profile SMTP, rail-pref editor);
|
scope; build the admin "API keys" management SPA panel.
|
||||||
`unlimited_merchant_profiles` on master Pro/Patron policies.
|
|
||||||
3. Deferred this session (now in Open TODOs): split `audit:read` out of the
|
- **Discovered this session (P2, unfixed)**: `set_product_entitlements_catalog`
|
||||||
blanket `:read` scope; build the admin "API keys" management SPA panel.
|
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` +
|
- **Known debt (P2 — schedule, not urgent)**: no rate-limit on `/v1/purchase` +
|
||||||
`/v1/redeem`; rate-limit bucket keys on spoofable `X-Forwarded-For` (bypass
|
`/v1/redeem`; rate-limit bucket keys on spoofable `X-Forwarded-For` (bypass
|
||||||
|
|||||||
@@ -39,6 +39,14 @@ merchant_profiles (1) ──< (N) payment_providers
|
|||||||
- **merchant_profile_rail_preferences** — tie-breaker `(profile, rail) → provider`
|
- **merchant_profile_rail_preferences** — tie-breaker `(profile, rail) → provider`
|
||||||
when a profile has two providers serving the same rail.
|
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 `<select>` only when >1 profile exists; a NULL
|
||||||
|
`merchant_profile_id` resolves to the default profile (`merchant_profiles::for_product`,
|
||||||
|
NOT the bare repo query, applies that fallback). On `UpdateProductReq` the field is a
|
||||||
|
double-Option: `Some(None)` clears back to default-resolution.
|
||||||
|
|
||||||
**Rails** are buyer-facing methods (`lightning`, `onchain`, `card`), declared per
|
**Rails** are buyer-facing methods (`lightning`, `onchain`, `card`), declared per
|
||||||
provider kind via `rails_for_kind` (BTCPay → lightning+onchain; Zaprite →
|
provider kind via `rails_for_kind` (BTCPay → lightning+onchain; Zaprite →
|
||||||
card+lightning+onchain), NOT stored per row.
|
card+lightning+onchain), NOT stored per row.
|
||||||
|
|||||||
Reference in New Issue
Block a user