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:
@@ -55,7 +55,9 @@
|
||||
|
||||
pub mod admin;
|
||||
pub mod admin_ui;
|
||||
pub mod api_keys;
|
||||
pub mod auth;
|
||||
pub mod openapi;
|
||||
pub mod btcpay_authorize;
|
||||
pub mod discount_codes;
|
||||
pub mod machines;
|
||||
@@ -325,6 +327,10 @@ pub fn router(state: AppState) -> Router {
|
||||
"/v1/admin/policies/:id/public",
|
||||
patch(policies::set_public),
|
||||
)
|
||||
.route(
|
||||
"/v1/admin/policies/:id/archived",
|
||||
patch(policies::set_archived),
|
||||
)
|
||||
.route(
|
||||
"/v1/admin/policies/:id/tip",
|
||||
patch(policies::set_tip),
|
||||
@@ -335,6 +341,14 @@ pub fn router(state: AppState) -> Router {
|
||||
get(policies::list_public_policies),
|
||||
)
|
||||
.route("/v1/admin/tips", get(policies::list_tips))
|
||||
// Scoped API keys — additional credentials with bounded permissions.
|
||||
// Master admin_api_key gates the management endpoints; the scoped
|
||||
// keys themselves are accepted on endpoints that call require_scope.
|
||||
.route(
|
||||
"/v1/admin/api-keys",
|
||||
get(api_keys::list).post(api_keys::create),
|
||||
)
|
||||
.route("/v1/admin/api-keys/:id", axum::routing::delete(api_keys::revoke))
|
||||
// Subscriptions (recurring billing) — admin list + cancel.
|
||||
.route(
|
||||
"/v1/admin/subscriptions",
|
||||
@@ -457,6 +471,10 @@ pub fn router(state: AppState) -> Router {
|
||||
// Public read of the issuer's signing public key — used by the
|
||||
// admin Overview "Embed your public key" tip and by SDK consumers.
|
||||
.route("/v1/issuer/public-key", get(issuer_key::public))
|
||||
// OpenAPI 3.1 spec — public, no auth. Drives agent discovery and
|
||||
// SDK code generation. Curated subset of the full route surface;
|
||||
// see crate::api::openapi for the inline definition.
|
||||
.route("/v1/openapi.json", get(openapi::spec))
|
||||
// Tier model — drives the admin sidebar's persistent upgrade banner.
|
||||
.route("/v1/admin/tier", get(tier::admin_status))
|
||||
// Web-UI password auth (v0.1.0:28+).
|
||||
@@ -741,7 +759,7 @@ footer.kfooter a:hover {{ color:var(--navy-900); }}
|
||||
</div>
|
||||
|
||||
<footer class="kfooter">
|
||||
<span>Powered by <a href="https://keysat.xyz" target="_blank" rel="noopener">Keysat</a> · Bitcoin-paid software licensing</span>
|
||||
<span>Powered by <a href="https://keysat.xyz" target="_blank" rel="noopener">Keysat</a> · Bitcoin-native self-hosted software licensing</span>
|
||||
</footer>
|
||||
|
||||
<script>
|
||||
@@ -817,8 +835,8 @@ footer.kfooter a:hover {{ color:var(--navy-900); }}
|
||||
function waitingCopy(status) {{
|
||||
const min = Math.floor(elapsedMs / 60000);
|
||||
if (status === 'pending' || status === 'processing') {{
|
||||
if (min < 2) return 'invoice ' + status + ' — should settle within a block (~10 min).';
|
||||
if (min < 10) return 'invoice ' + status + ' — waiting for block confirmation. Safe to leave this tab open or bookmark this URL and come back.';
|
||||
if (min < 2) return 'invoice ' + status + ' — Lightning settles in seconds; on-chain takes a block (~10 min).';
|
||||
if (min < 10) return 'invoice ' + status + ' — looks like an on-chain payment, waiting for block confirmation. Safe to leave this tab open or bookmark this URL.';
|
||||
return 'invoice ' + status + ' — slow block. Still polling. Bookmark this URL and refresh later if you close the tab.';
|
||||
}}
|
||||
return 'invoice status: ' + (status || 'pending');
|
||||
|
||||
Reference in New Issue
Block a user