clavitor/clavitor.ai/schema.sql

167 lines
8.6 KiB
SQL

-- Clavitor Corporate Service Database Schema
-- Uses 8-byte int64 IDs (positive only, via lib.NewID())
-- Separate from vault databases - this is the central metadata/operational service
-- Tenants: Multi-tenant hierarchy (MSP → Customer → Dept)
CREATE TABLE tenants (
id INTEGER PRIMARY KEY, -- 8-byte int64, use lib.NewID()
parent_id INTEGER REFERENCES tenants(id), -- NULL for root tenants
level INTEGER NOT NULL, -- Hierarchy depth: 1=MSP, 2=Customer, 3=Dept
name TEXT NOT NULL,
status TEXT NOT NULL DEFAULT 'active', -- active, suspended, archived
billing_email TEXT, -- Where invoices go
created_at INTEGER NOT NULL,
updated_at INTEGER
);
CREATE INDEX idx_tenants_parent ON tenants(parent_id);
CREATE INDEX idx_tenants_status ON tenants(status);
-- Humans: People who can access the system
CREATE TABLE humans (
id INTEGER PRIMARY KEY, -- 8-byte int64
tenant_id INTEGER NOT NULL REFERENCES tenants(id),
email TEXT NOT NULL UNIQUE,
name TEXT NOT NULL,
status TEXT NOT NULL DEFAULT 'active', -- active, inactive, suspended
created_at INTEGER NOT NULL,
updated_at INTEGER
);
CREATE INDEX idx_humans_tenant ON humans(tenant_id);
CREATE INDEX idx_humans_email ON humans(email);
-- Roles: Global role definitions (we define these, customers assign them)
CREATE TABLE roles (
id INTEGER PRIMARY KEY, -- 8-byte int64
name TEXT NOT NULL UNIQUE, -- SuperAdmin, MSPAdmin, TenantAdmin, VaultUser, BillingAdmin, ReadOnly
permissions TEXT NOT NULL, -- JSON: {"vault_create": true, "agent_create": true, ...}
description TEXT,
created_at INTEGER NOT NULL
);
-- Pre-populate standard roles
INSERT INTO roles (id, name, permissions, description, created_at) VALUES
(lib.NewID(), 'SuperAdmin', '{"all": true}', 'Full access to all tenants and systems', strftime('%s', 'now')),
(lib.NewID(), 'MSPAdmin', '{"tenant_admin": true, "billing_view": true, "vault_read": true}', 'Admin for MSP and all child tenants', strftime('%s', 'now')),
(lib.NewID(), 'TenantAdmin', '{"tenant_admin": true, "vault_create": true, "agent_create": true, "device_enroll": true}', 'Admin for specific tenant', strftime('%s', 'now')),
(lib.NewID(), 'VaultUser', '{"vault_read": true, "vault_write": true}', 'Can use vaults, cannot admin', strftime('%s', 'now')),
(lib.NewID(), 'BillingAdmin', '{"billing_view": true, "invoices": true}', 'Can view and pay invoices', strftime('%s', 'now')),
(lib.NewID(), 'ReadOnly', '{"vault_read": true}', 'View only, no changes', strftime('%s', 'now'));
-- RoleAssignments: Humans can have different roles in different tenants
CREATE TABLE role_assignments (
id INTEGER PRIMARY KEY, -- 8-byte int64
human_id INTEGER NOT NULL REFERENCES humans(id),
tenant_id INTEGER NOT NULL REFERENCES tenants(id),
role_id INTEGER NOT NULL REFERENCES roles(id),
granted_by INTEGER REFERENCES humans(id), -- Who gave this permission
created_at INTEGER NOT NULL,
UNIQUE(human_id, tenant_id, role_id) -- One role per human per tenant
);
CREATE INDEX idx_role_assignments_human ON role_assignments(human_id);
CREATE INDEX idx_role_assignments_tenant ON role_assignments(tenant_id);
-- Subscriptions: Flat-rate license plans (not metered usage)
CREATE TABLE subscriptions (
id INTEGER PRIMARY KEY, -- 8-byte int64
tenant_id INTEGER NOT NULL REFERENCES tenants(id),
plan TEXT NOT NULL, -- Personal, Family, Pro, Team10, Team25, Team100, Team250, Team500, MME, Enterprise
status TEXT NOT NULL DEFAULT 'active', -- active, past_due, cancelled, suspended, archived
started_at INTEGER NOT NULL,
ends_at INTEGER, -- NULL = perpetual
billing_cycle TEXT, -- monthly, annual
next_invoice_at INTEGER,
max_vaults INTEGER NOT NULL,
max_devices INTEGER NOT NULL, -- Per vault
max_tokens INTEGER NOT NULL, -- Per vault
price_per_year_cents INTEGER NOT NULL, -- For reference/display
paddle_subscription_id TEXT, -- External billing reference (optional for direct invoice)
invoice_terms TEXT, -- net_60, immediate - NULL defaults by tier
created_at INTEGER NOT NULL,
updated_at INTEGER
);
CREATE INDEX idx_subscriptions_tenant ON subscriptions(tenant_id);
CREATE INDEX idx_subscriptions_status ON subscriptions(status);
CREATE INDEX idx_subscriptions_ends ON subscriptions(ends_at);
-- Vaults: Registry linking L0 vaults to tenants and humans
CREATE TABLE vaults (
l0 TEXT PRIMARY KEY, -- Vault identifier (first 4 bytes of L3), hex encoded
tenant_id INTEGER NOT NULL REFERENCES tenants(id),
owner_human_id INTEGER REFERENCES humans(id), -- Who created/owns this vault
subscription_id INTEGER REFERENCES subscriptions(id),
created_at INTEGER NOT NULL,
last_seen_at INTEGER, -- For activity tracking
status TEXT NOT NULL DEFAULT 'active', -- active, suspended, archived
plan_override TEXT -- NULL = use subscription plan, or override for this specific vault
);
CREATE INDEX idx_vaults_tenant ON vaults(tenant_id);
CREATE INDEX idx_vaults_owner ON vaults(owner_human_id);
CREATE INDEX idx_vaults_status ON vaults(status);
-- Credentials (WL3 Registry): Which humans have which wrapped L3s
-- This is the "journal" for POPs to pull from - no vault data, just metadata
CREATE TABLE credentials (
id INTEGER PRIMARY KEY, -- 8-byte int64
human_id INTEGER NOT NULL REFERENCES humans(id),
p0 TEXT NOT NULL, -- First 4 bytes of PRF, hex encoded (lookup key)
l0 TEXT NOT NULL REFERENCES vaults(l0), -- Which vault this opens
credential_id BLOB NOT NULL, -- WebAuthn credential ID (for display/admin)
wl3_path TEXT NOT NULL, -- Path in storage: a1/a1b2c3d4/20260405_abc.cla
enrolled_by INTEGER REFERENCES humans(id), -- Device that authorized this enrollment
created_at INTEGER NOT NULL,
last_used_at INTEGER -- For "active device" tracking
);
CREATE INDEX idx_credentials_human ON credentials(human_id);
CREATE INDEX idx_credentials_l0 ON credentials(l0);
CREATE INDEX idx_credentials_p0 ON credentials(p0);
CREATE INDEX idx_credentials_created ON credentials(created_at); -- For incremental sync to POPs
-- BillingEvents: Minimal audit trail
CREATE TABLE billing_events (
id INTEGER PRIMARY KEY, -- 8-byte int64
occurred_at INTEGER NOT NULL,
tenant_id INTEGER NOT NULL REFERENCES tenants(id),
subscription_id INTEGER REFERENCES subscriptions(id),
human_id INTEGER REFERENCES humans(id),
vault_l0 TEXT REFERENCES vaults(l0),
event_type TEXT NOT NULL, -- subscription_created, upgraded, payment_received, vault_created, device_enrolled, etc.
amount_cents INTEGER, -- For payment events
details TEXT -- JSON blob for extensibility
);
CREATE INDEX idx_billing_events_tenant ON billing_events(tenant_id, occurred_at);
CREATE INDEX idx_billing_events_time ON billing_events(occurred_at);
-- SyncState: Track which POPs have which credentials (for incremental pull API)
CREATE TABLE pop_sync_state (
pop_id TEXT PRIMARY KEY, -- e.g., "pop-zurich-01", "pop-singapore-03"
last_sync_at INTEGER NOT NULL, -- Timestamp of last successful sync
last_sync_cursor INTEGER, -- Optional: last credential.id synced
status TEXT NOT NULL DEFAULT 'active', -- active, paused, error
updated_at INTEGER NOT NULL
);
-- AgentTokens: For CLI/extension authentication (separate from WebAuthn)
CREATE TABLE agent_tokens (
id INTEGER PRIMARY KEY, -- 8-byte int64
vault_l0 TEXT NOT NULL REFERENCES vaults(l0),
name TEXT NOT NULL, -- e.g., "Claude Code", "Deploy CI"
token_hash TEXT NOT NULL UNIQUE, -- SHA256 of the actual token
scopes TEXT NOT NULL DEFAULT '', -- Comma-separated scope IDs: "0001,0002"
all_access INTEGER NOT NULL DEFAULT 0, -- 1 = reads everything
created_by INTEGER REFERENCES humans(id),
created_at INTEGER NOT NULL,
expires_at INTEGER, -- NULL = no expiry
last_used_at INTEGER,
status TEXT NOT NULL DEFAULT 'active' -- active, revoked, expired
);
CREATE INDEX idx_agent_tokens_vault ON agent_tokens(vault_l0);
CREATE INDEX idx_agent_tokens_hash ON agent_tokens(token_hash);