Spaces:
Sleeping
Sleeping
File size: 3,309 Bytes
6952bcb | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 | -- 006_licensing (sqlite) β Phase 4 billing (docs/HARDENING.md): org-scoped
-- agent licenses + append-only usage metering (docs/TENANCY.md: both are
-- T1 org-internal data).
--
-- Conventions (002/004/005):
-- * ts columns are ISO-8601 UTC strings ("%Y-%m-%dT%H:%M:%SZ").
-- * org_id defaults to 'org-demo', the bootstrap org (005 bridge), so dev
-- mode (BU_AUTH_DISABLED=1) keeps working unchanged.
-- * SQLite has NO row-level security: dev/demo isolation relies on layers
-- 1-2 of docs/TENANCY.md (route deps + org-scoped SQL via
-- atp/tenant_db.py). The Postgres twin (006_licensing.pg.sql) adds RLS.
--
-- Mutability model (deliberate asymmetry):
-- * `licenses` is MUTABLE β revocation and expiry are DB state transitions
-- (status active -> revoked | expired). A license key alone NEVER
-- suffices: atp/licensing.py check_license() re-reads the row on every
-- call, so a revoked/expired row fails instantly regardless of a valid
-- key signature.
-- * `usage_events` is APPEND-ONLY at the DB level (BEFORE UPDATE / BEFORE
-- DELETE triggers abort) β usage rows are billing evidence and must be
-- as tamper-resistant as the atp_evidence chain.
--
-- Key material: only `key_id` (the public identifier half of the license
-- key) is stored. The secret half is an HMAC over key_id with
-- LICENSE_SIGNING_KEY, shown once at issue time and never persisted.
--
-- Statement separator convention: a line containing only `--;;` splits this
-- file into statements (see atp/db.py) β required for the trigger bodies.
CREATE TABLE IF NOT EXISTS licenses (
id TEXT PRIMARY KEY,
org_id TEXT NOT NULL DEFAULT 'org-demo',
agent_id TEXT NOT NULL,
kind TEXT NOT NULL CHECK (kind IN ('evaluation', 'production')),
status TEXT NOT NULL DEFAULT 'active'
CHECK (status IN ('active', 'revoked', 'expired')),
key_id TEXT NOT NULL UNIQUE,
created_at TEXT NOT NULL,
expires_at TEXT, -- NULL = never expires
revoked_at TEXT,
revoked_reason TEXT,
stripe_customer TEXT, -- NULL for 'manual' (invoice) licenses
stripe_subscription TEXT, -- NULL for 'manual' (invoice) licenses
seats INTEGER NOT NULL DEFAULT 1
);
--;;
-- (org_id, <pk>) per docs/TENANCY.md invariants; key_id lookup is covered by
-- the UNIQUE constraint's implicit index.
CREATE INDEX IF NOT EXISTS idx_licenses_org ON licenses(org_id, id);
--;;
CREATE TABLE IF NOT EXISTS usage_events (
id INTEGER PRIMARY KEY AUTOINCREMENT,
ts TEXT NOT NULL,
org_id TEXT NOT NULL DEFAULT 'org-demo',
license_id TEXT,
agent_id TEXT,
endpoint TEXT,
tokens_in INTEGER,
tokens_out INTEGER,
status TEXT
);
--;;
CREATE INDEX IF NOT EXISTS idx_usage_events_org ON usage_events(org_id, id);
--;;
-- ββ Append-only enforcement (same pattern as 002 atp_hitl/atp_evidence) ββββ
CREATE TRIGGER IF NOT EXISTS trg_usage_events_no_update
BEFORE UPDATE ON usage_events
BEGIN
SELECT RAISE(ABORT, 'append-only');
END;
--;;
CREATE TRIGGER IF NOT EXISTS trg_usage_events_no_delete
BEFORE DELETE ON usage_events
BEGIN
SELECT RAISE(ABORT, 'append-only');
END;
|