Cut 0.2.0:56 — product→merchant-profile write path
This commit is contained in:
@@ -39,6 +39,8 @@ const RELEASE_NOTES = [
|
|||||||
// in RELEASE_NOTES above (the milestone). Subsequent revisions
|
// in RELEASE_NOTES above (the milestone). Subsequent revisions
|
||||||
// append here.
|
// append here.
|
||||||
const ROUTINE_NOTES = [
|
const ROUTINE_NOTES = [
|
||||||
|
'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 `<select>` on the product form only when more than one profile exists, so single-profile operators see no change. No schema migration (highest is still 0022) — straight drop-in over :55. No SDK change.',
|
||||||
|
'',
|
||||||
'0.2.0:55 — **Scoped API keys, an advisory settle-amount tripwire, and multi-arch packaging.** Three things land over :54, with no schema migration (highest is still 0022) — straight drop-in. **(1) Scoped admin API keys.** 58 admin endpoints move from the blanket `require_admin` gate to role-scoped `require_scope` checks, so an operator can mint reduced-privilege keys (for example, read-only access to dashboards and licenses) instead of handing out the master key; 12 sensitive endpoints stay master-only (issuer key, provider connect/disconnect, set-password, API-key CRUD, db-info, operator-name, per-license tier change). The master admin key keeps full access, so existing automation is unaffected. **(2) Advisory settle-amount tripwire** — the follow-up flagged in :54. On settle, `audit_settle_amount` (shared by the webhook and reconcile issue paths) compares the provider-reported paid amount against what was invoiced; on drift it WARN-logs and writes an `invoice.amount_mismatch` audit row, then issues anyway. It is an advisory signal, not a payment gate (a hard gate would fight BTCPay payment tolerance). SAT-denominated invoices only; fiat-subscription renewals and amount-less snapshots are skipped so there are no false positives. **(3) StartOS packaging and multi-arch.** The package now ships as a single universal s9pk built for both `x86_64` and `aarch64` (previously x86-only), so it installs on ARM StartOS hardware. Adds the required `instructions.md`, fixes two dead manifest links (`packageRepo`, `docsUrls`), and clears stale references to the long-retired license enforce mode from the Activate-License and Show-Credentials actions (the daemon always boots at the free Creator tier; activating a license lifts the caps). Daemon test suite is at 54 api tests, up from 47. No SDK change.',
|
'0.2.0:55 — **Scoped API keys, an advisory settle-amount tripwire, and multi-arch packaging.** Three things land over :54, with no schema migration (highest is still 0022) — straight drop-in. **(1) Scoped admin API keys.** 58 admin endpoints move from the blanket `require_admin` gate to role-scoped `require_scope` checks, so an operator can mint reduced-privilege keys (for example, read-only access to dashboards and licenses) instead of handing out the master key; 12 sensitive endpoints stay master-only (issuer key, provider connect/disconnect, set-password, API-key CRUD, db-info, operator-name, per-license tier change). The master admin key keeps full access, so existing automation is unaffected. **(2) Advisory settle-amount tripwire** — the follow-up flagged in :54. On settle, `audit_settle_amount` (shared by the webhook and reconcile issue paths) compares the provider-reported paid amount against what was invoiced; on drift it WARN-logs and writes an `invoice.amount_mismatch` audit row, then issues anyway. It is an advisory signal, not a payment gate (a hard gate would fight BTCPay payment tolerance). SAT-denominated invoices only; fiat-subscription renewals and amount-less snapshots are skipped so there are no false positives. **(3) StartOS packaging and multi-arch.** The package now ships as a single universal s9pk built for both `x86_64` and `aarch64` (previously x86-only), so it installs on ARM StartOS hardware. Adds the required `instructions.md`, fixes two dead manifest links (`packageRepo`, `docsUrls`), and clears stale references to the long-retired license enforce mode from the Activate-License and Show-Credentials actions (the daemon always boots at the free Creator tier; activating a license lifts the caps). Daemon test suite is at 54 api tests, up from 47. No SDK change.',
|
||||||
'',
|
'',
|
||||||
'0.2.0:54 — **Security: settle webhooks are now confirmed against the provider before a license is issued.** Previously the settle handler trusted the webhook body\'s claim alone. BTCPay webhooks are HMAC-signed so a forgery there is infeasible, but **Zaprite webhooks carry no signature** — so a forged `order.change`/`status=PAID` POST containing a buyer-visible Zaprite order id could mint a fully-signed license without any payment (the `externalUniqId` "trust anchor" the code comments described was never actually checked on the inbound path). Fixed in `api/webhook.rs::handle_inner`: on any settle event the daemon now re-fetches the authoritative status from the provider\'s own API (`get_invoice_status`) and requires it to actually be `Settled` before persisting the paid status or taking ANY settle-derived action — license issuance, tier-change application, or subscription renewal (the confirmation gate sits ahead of all three). If the provider\'s API is unreachable the handler acks `200` WITHOUT issuing rather than erroring, so a transient provider outage can\'t turn every in-flight webhook into a retry storm; the existing 60-second reconcile loop re-confirms and issues on its next tick (fail-closed on issuance). This only affects operators who enabled the optional Zaprite provider; BTCPay-only operators were never exposed. No schema change, no SDK change — straight drop-in over :53. **Known follow-up**: the confirmation is a binary settled/not-settled check; a literal paid-amount/currency comparison (to reject a provider-reported underpayment) is not yet wired and is tracked separately. Internally this release also adds the first integration-test seam for the real purchase/settle path (`AppState::provider_override`), bringing the daemon test suite to 47 passing with the prior 3 known-failing payment tests resolved.',
|
'0.2.0:54 — **Security: settle webhooks are now confirmed against the provider before a license is issued.** Previously the settle handler trusted the webhook body\'s claim alone. BTCPay webhooks are HMAC-signed so a forgery there is infeasible, but **Zaprite webhooks carry no signature** — so a forged `order.change`/`status=PAID` POST containing a buyer-visible Zaprite order id could mint a fully-signed license without any payment (the `externalUniqId` "trust anchor" the code comments described was never actually checked on the inbound path). Fixed in `api/webhook.rs::handle_inner`: on any settle event the daemon now re-fetches the authoritative status from the provider\'s own API (`get_invoice_status`) and requires it to actually be `Settled` before persisting the paid status or taking ANY settle-derived action — license issuance, tier-change application, or subscription renewal (the confirmation gate sits ahead of all three). If the provider\'s API is unreachable the handler acks `200` WITHOUT issuing rather than erroring, so a transient provider outage can\'t turn every in-flight webhook into a retry storm; the existing 60-second reconcile loop re-confirms and issues on its next tick (fail-closed on issuance). This only affects operators who enabled the optional Zaprite provider; BTCPay-only operators were never exposed. No schema change, no SDK change — straight drop-in over :53. **Known follow-up**: the confirmation is a binary settled/not-settled check; a literal paid-amount/currency comparison (to reject a provider-reported underpayment) is not yet wired and is tracked separately. Internally this release also adds the first integration-test seam for the real purchase/settle path (`AppState::provider_override`), bringing the daemon test suite to 47 passing with the prior 3 known-failing payment tests resolved.',
|
||||||
@@ -526,7 +528,7 @@ const ROUTINE_NOTES = [
|
|||||||
].join('\n\n')
|
].join('\n\n')
|
||||||
|
|
||||||
export const v0_2_0 = VersionInfo.of({
|
export const v0_2_0 = VersionInfo.of({
|
||||||
version: '0.2.0:55',
|
version: '0.2.0:56',
|
||||||
releaseNotes: { en_US: ROUTINE_NOTES },
|
releaseNotes: { en_US: ROUTINE_NOTES },
|
||||||
// No on-disk transformation needed — v0.2.0:0 is a label change.
|
// No on-disk transformation needed — v0.2.0:0 is a label change.
|
||||||
// SQLite-level migrations live separately under
|
// SQLite-level migrations live separately under
|
||||||
|
|||||||
Reference in New Issue
Block a user