Commit Graph

7 Commits

Author SHA1 Message Date
Grant 257669092b v0.2.0:11 + v0.2.0:12 — Archive, Settings, agent surface, machines redesign
Two release cycles prepared together: v0.2.0:11 (policy archive + safe-
delete cleanup + brand-consistent confirm modals) and v0.2.0:12 (Settings
tab + agent-friendly operator API + machines tab redesign + buyer-facing
copy alignment).

Highlights:

- Migration 0015: policies.archived_at column. Archive button on tier
  cards; safe-delete relaxed to ignore revoked-license tombstones;
  renewal worker refuses archived policies.
- Migration 0016: scoped_api_keys table. Four roles (read-only,
  license-issuer, support, full-admin) with bounded scopes. Master
  admin_api_key still works on every endpoint; scoped keys gated on
  endpoints wired through require_scope().
- New /v1/openapi.json — public, no auth. Curated OpenAPI 3.1 spec
  for agent / SDK discovery.
- New Settings tab: Operator name + Payment providers panel + API
  keys management. Replaces 8 StartOS Actions (Zaprite all, BTCPay
  all, operator name, switch-provider). StartOS Actions pruned to 4
  install-time essentials.
- Machines tab rewritten: global default view grouped by product,
  filter pills with counts, quick-stats row, drill-down via new
  "Machines" button on each Licenses-tab row. New repo helper
  list_machines_admin joins machines x licenses x products
  server-side.
- Branded confirmModal replaces every native window.confirm() call
  in the admin UI (7 callsites).
- Enforce mode killed: KEYSAT_LICENSE_ENFORCE compile-time flag
  retired; daemon always boots; missing self-license -> Creator
  (free) tier. "Unlicensed" label gone from admin UI.
- Zaprite gated on the new zaprite_payments entitlement (renamed
  from card_payments to reflect the broader gateway).
- Creator code cap 5 -> 10.
- KEYSAT_AGENT_GUIDE.md: auth, role-to-scope mapping, error envelope,
  webhook events, worked recipes.
- Buyer-facing copy aligned with new positioning: "Bitcoin-native
  self-hosted software licensing" everywhere on production surfaces.
- Cross-product safety section (Section 9a) added to KEYSAT_INTEGRATION.md.
- 5 new API integration smoke tests covering OpenAPI, scoped API
  keys CRUD, role-elevation guard, and Zaprite-tier gating.

Test count: 83 passing (was 78). All migration tests pass against
0015 and 0016 applied to populated DBs.
2026-05-11 08:45:25 -05:00
Grant 4b9ef0ea8c v0.2.0:8 release notes + integration doc section 8 update
Notes cover the entitlements catalog feature shipped in 68dfe7f
plus the four SDK 0.3.0 cuts (TS / Rust / Python / Go) that
surface the catalog on listPublicPolicies. Phase 2 (side-by-side
card-grid policy authoring UI) is queued for v0.2.0:9.

KEYSAT_INTEGRATION.md section 8 grows a subsection explaining the
catalog mechanics: bubble picker, buy page rendering, SDK surface,
catalog-stability rule.

Test count: 78 (unchanged from :7 except for migration_0014 already
counted in the prior commit).
2026-05-10 08:01:43 -05:00
Grant 89d505b9de KEYSAT_INTEGRATION.md: section 0a "How enforcement actually works"
Captures the offline-vs-online enforcement framing that every
operator hits when they realize they want to revoke / downgrade /
lapse a license. Previously this answer was scattered across
sections; consolidating into a dedicated section 0a so both LLMs
and humans following the integration doc see it before they make
the SDK call-pattern decision.

Covers:
- What the buyer's app can enforce offline (baked-in expiry,
  entitlement set, trial flag, fingerprint binding)
- What the operator can change ONLY online (revocation, tier
  changes, sub lapses, seat enforcement)
- The two design dials operators pick (baked-expiry length,
  whether the app calls validate())
- The two patterns: A = "true perpetual, offline-only"; B =
  "perpetual price, online-enforced entitlements"
- Side-by-side TS code samples for each pattern
- Operator-side implications for each product type (perpetual,
  recurring, trial-converting)
- Cross-reference to section 11a (tier upgrades only have teeth
  with Pattern B) so the LLM following that section's flow back
  to here gets the right framing
- Note that Keysat itself dogfoods Pattern B (with reference
  to the new license_self::refresh_self_tier_from_db helper)

The framing is the same one that came out of Grant's testing
session — the integration doc is now the canonical place to
point any future operator who asks "wait, why doesn't downgrading
take effect?"
2026-05-09 14:03:32 -05:00
Grant 735461b3ef KEYSAT_INTEGRATION.md: section 11a — tier-aware purchases + in-app picker
Documents the multi-policy in-app purchase flow that the Recap dev
hit a dead-end on (no obvious tier discriminator on startPurchase).
Adds:

- New section 11a "Tier-aware purchases — in-app tier picker
  (multi-tier products)" walking the full pattern: listPublicPolicies
  → render tier UI → startPurchase with policySlug → open checkout →
  poll/webhook → write key. Same shape in TS / Python / Rust / Go.
- Architecture diagram showing buyer → SDK → daemon → BTCPay → key.
- "When you'd use this" guidance + "Common mistakes" section
  including the four traps the Recap dev guessed at: hardcoding
  slugs, splitting products, abusing discount codes as tier
  selectors, omitting policySlug.
- Cross-reference from question 7 in section 0 (the operator-
  questionnaire) so the LLM nudges toward the picker pattern when
  there are 2+ tiers, and back to single-tier section 11 otherwise.
- Cross-reference from section 7f (frontend integration for
  hard-gate Flavor 2) so the activation-screen pattern surfaces
  the picker as an inline option.
- Cross-reference from section 11 → 11a so single-policy readers
  who later add tiers find the upgrade path.

This is the pattern Recap implements in its activation screen, and
becomes the canonical example for any future multi-tier integration.
SDKs (TS, Rust, Python, Go) all support it as of their 0.2.0
releases (commits c3a57a0 / 5dd301c / 94654f6 / 970f95a in their
respective repos).
2026-05-09 09:11:34 -05:00
Grant 4ac856bb10 Restore KEYSAT_INTEGRATION.md (mistakenly deleted in v0.1.0:41)
The previous commit removed the canonical 1378-line integration guide
based on a misread of intent — the file's "moved to startos folder"
note referred to *this* (licensing-service-startos) repo. The 12-line
stub at the parent licensing/ folder is the forwarder, not the canonical.

No version bump: doc-only restore, no on-disk or daemon behaviour
change. v0.1.0:41 release notes contain an incidental line stating
"KEYSAT_INTEGRATION.md is removed from this repo" — left as-is for
now since the .s9pk hasn't been re-published since :41. If we
re-publish :41 and the line bothers us, a separate commit can correct
it before the next .s9pk build.
2026-05-08 08:17:33 -05:00
Grant 116ed0d1f8 v0.1.0:41 — second hotfix to migration 0009; migration regression tests
The v0.1.0:40 migration was correct on clean installs but crashed at
COMMIT on any database with rows in discount_redemptions: SQLite's
deferred FK check saw the dropped parent's bookkeeping as unsatisfied
even after the rename. Fix is to rebuild discount_redemptions in the
same transaction (stash → drop → rebuild → restore) plus orphan
cleanup. Migration is idempotent; operators on :40 with a checksum
mismatch recover by deleting the version=9 row from _sqlx_migrations
and restarting.

Lands the missing migration test scaffolding too. The four tests in
licensing-service/tests/migrations.rs apply migrations against a
realistic populated database (products, policies, invoices, licenses,
machines, discount codes, redemptions, webhooks, tip attempts). The
regression test fails with the exact 787 error against the v40
migration — would have caught the bug pre-release.

KEYSAT_INTEGRATION.md is removed from this repo; it now lives in the
parent licensing/ folder.
2026-05-08 08:05:19 -05:00
Grant beedd07f07 v0.1.0:25–40 — tier model, edit forms, force-delete, license counts, migration 0009 (and hotfix); KEYSAT_INTEGRATION.md merged with downstream-LLM revisions 2026-05-07 23:35:22 -05:00