167 lines
8.6 KiB
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);
|