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.
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
-- Migration 0015: policies.archived_at
|
||||
--
|
||||
-- Adds a soft-archive flag to policies. An archived policy is hidden
|
||||
-- from the admin grid (unless the operator opts to show archived) and
|
||||
-- from the public /buy/<slug> page. Existing licenses keep validating
|
||||
-- because their entitlements are signed into the LIC1 payload; the
|
||||
-- policy row is not consulted at validate time. Active recurring
|
||||
-- subscriptions tied to an archived policy stop renewing — the renewal
|
||||
-- worker treats archived as a hard stop and surfaces a clear event.
|
||||
--
|
||||
-- Why a column instead of a status TEXT enum? Policies already have
|
||||
-- two boolean toggles (active, public). A nullable timestamp is the
|
||||
-- minimum-information shape: NULL = live, timestamp = when archived.
|
||||
-- Useful for sorting "Archived (most recent first)" without an extra
|
||||
-- column.
|
||||
|
||||
ALTER TABLE policies ADD COLUMN archived_at TEXT NULL;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_policies_archived_at ON policies(archived_at);
|
||||
@@ -0,0 +1,37 @@
|
||||
-- Migration 0016: scoped API keys
|
||||
--
|
||||
-- The master `admin_api_key` (set in config) is a full-access credential;
|
||||
-- it's the right thing for the operator to hold but the wrong thing to
|
||||
-- hand to an agent or a third-party tool. This table stores additional
|
||||
-- API keys with operator-chosen roles that bound what they can do.
|
||||
--
|
||||
-- Storage model:
|
||||
-- - `token_hash` is the sha256 of the raw token. We NEVER store the
|
||||
-- raw token after generation — the create endpoint returns it once
|
||||
-- in the response body and that's the operator's only chance to
|
||||
-- copy it.
|
||||
-- - `role` is a string tag (read-only | license-issuer | support |
|
||||
-- full-admin). Scope sets per role are computed at auth time, not
|
||||
-- stored here — that way we can extend the scope mapping without a
|
||||
-- migration.
|
||||
-- - `revoked_at` flips this row from valid → permanently rejected.
|
||||
-- Soft-revoke rather than DELETE so the audit log keeps a stable
|
||||
-- reference and the operator can see "this key existed but is
|
||||
-- gone now" in the admin UI.
|
||||
-- - `last_used_at` is touched best-effort on every successful auth.
|
||||
-- Bounded write rate (one update per minute per key) is the next
|
||||
-- iteration; for v1 we update every call.
|
||||
|
||||
CREATE TABLE IF NOT EXISTS scoped_api_keys (
|
||||
id TEXT PRIMARY KEY NOT NULL,
|
||||
label TEXT NOT NULL,
|
||||
token_hash TEXT NOT NULL UNIQUE,
|
||||
role TEXT NOT NULL,
|
||||
created_at TEXT NOT NULL,
|
||||
last_used_at TEXT,
|
||||
revoked_at TEXT,
|
||||
CHECK (role IN ('read-only', 'license-issuer', 'support', 'full-admin'))
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_scoped_api_keys_token ON scoped_api_keys(token_hash);
|
||||
CREATE INDEX IF NOT EXISTS idx_scoped_api_keys_active ON scoped_api_keys(revoked_at);
|
||||
Reference in New Issue
Block a user