From fd71b19f8666c0f1d3f5b734c4d3176e935d3343 Mon Sep 17 00:00:00 2001 From: Grant Date: Fri, 19 Jun 2026 11:55:26 -0500 Subject: [PATCH] Release 0.2.0:61 --- 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 b137a19..dd1ac29 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:61 — **Security hardening for self-license tier enforcement.** The daemon now re-verifies its own self-license against the signed key on every hourly tier refresh, not only at boot. Issuer-applied tier changes — downgrade, suspension, revocation, and the license's own expiry — now take effect on a running daemon within the hour instead of waiting for the next restart. Master and honest downstream instances behave exactly as before. No schema change, no SDK change — straight drop-in over :60.", + '', '0.2.0:60 — **Fix a Zaprite auto-charge silent-lapse on the recurring-renewal money path.** `charge_order_with_profile` already errors on a non-2xx response (those route correctly to WARN + `auto_charge_failed` audit + manual-pay fallback), but on a 2xx `try_auto_charge_zaprite` returned `Ok(true)` regardless of the order\'s actual status — it read `status` only for a log line. So a 200 carrying a non-settled status (a declined or expired charge, or an in-flight PENDING/PROCESSING) suppressed the manual-pay renewal notification and left the worker waiting for an `order.paid` webhook that never arrives: the subscription silently lapsed, the buyer got no pay link, and the operator saw no error. Fixed by classifying the charge response — the auto-charge is treated as successful (and manual-pay suppressed) only when the order is in a recognized settled state (`PAID`/`COMPLETE`/`OVERPAID`, mirroring `get_invoice_status`\'s `Settled` mapping); any other or unrecognized status logs a WARN and falls through to the existing manual-pay `subscription.renewal_pending` path so the buyer can always recover the cycle. Allowlist by design — Zaprite has no documented terminal-failure status string, so an unknown or missing status is treated as not-settled rather than optimistically assumed paid. Adds a unit test on the new `zaprite_charge_settled` helper covering settled / in-flight / failed / unknown statuses. BTCPay subscriptions and any sub without a saved Zaprite profile are unaffected. Also docs-only: flagged the dormant `merchant_profiles.smtp_*` columns (the buyer-email / SMTP plan was dropped — Keysat does not send email). No schema change, no SDK change — straight drop-in over :59.', '', '0.2.0:59 — **Admin UI: drop the gold button-fill design-contract violations.** Two admin-SPA controls filled with gold, which the brand contract (`design/DESIGN.md`) and the admin-UI pill convention forbid (gold is a marketing accent, never a button fill): the "Featured" tier toggle\'s on-state and the sidebar tier-upgrade CTA. Both now follow the convention — the Featured toggle is navy-filled with a cream pip when on; the upgrade CTA is cream-filled with navy text (the on-brand high-contrast treatment for a primary action on the navy sidebar), and its corner radius is aligned to the 8px button spec. CSS / inline-style only in the embedded `web/index.html` — no schema, no SDK, no behavior change. Straight drop-in over :58. (The matching public-landing fix — the Buy button\'s pill radius set to 8px — ships in the keysat-xyz-landing repo, deployed separately.)', @@ -536,7 +538,7 @@ const ROUTINE_NOTES = [ ].join('\n\n') export const v0_2_0 = VersionInfo.of({ - version: '0.2.0:60', + version: '0.2.0:61', releaseNotes: { en_US: ROUTINE_NOTES }, // No on-disk transformation needed — v0.2.0:0 is a label change. // SQLite-level migrations live separately under