-- Dealspace initial schema -- All content fields (search_key, search_key2, summary, data) are encrypted blobs. -- Plain fields (entry_id, project_id, parent_id, type, depth, stage, timestamps, IDs) are indexed. PRAGMA journal_mode = WAL; PRAGMA foreign_keys = ON; -- Users CREATE TABLE IF NOT EXISTS users ( user_id TEXT PRIMARY KEY, email TEXT NOT NULL UNIQUE, name TEXT NOT NULL, password TEXT NOT NULL, -- bcrypt hash org_id TEXT NOT NULL DEFAULT '', org_name TEXT NOT NULL DEFAULT '', mfa_secret TEXT NOT NULL DEFAULT '', -- TOTP secret, encrypted active INTEGER NOT NULL DEFAULT 1, created_at INTEGER NOT NULL, updated_at INTEGER NOT NULL ); CREATE INDEX IF NOT EXISTS idx_users_email ON users(email); CREATE INDEX IF NOT EXISTS idx_users_org ON users(org_id); -- Entries: the single tree CREATE TABLE IF NOT EXISTS entries ( entry_id TEXT PRIMARY KEY, project_id TEXT NOT NULL, parent_id TEXT NOT NULL DEFAULT '', type TEXT NOT NULL, depth INTEGER NOT NULL DEFAULT 0, search_key BLOB, -- blind index (HMAC-SHA256) search_key2 BLOB, -- blind index (HMAC-SHA256) summary BLOB, -- packed: zstd + AES-256-GCM data BLOB, -- packed: zstd + AES-256-GCM stage TEXT NOT NULL DEFAULT 'pre_dataroom', assignee_id TEXT NOT NULL DEFAULT '', return_to_id TEXT NOT NULL DEFAULT '', origin_id TEXT NOT NULL DEFAULT '', version INTEGER NOT NULL DEFAULT 1, deleted_at INTEGER, deleted_by TEXT, key_version INTEGER NOT NULL DEFAULT 1, created_at INTEGER NOT NULL, updated_at INTEGER NOT NULL, created_by TEXT NOT NULL ); CREATE INDEX IF NOT EXISTS idx_entries_project ON entries(project_id); CREATE INDEX IF NOT EXISTS idx_entries_parent ON entries(parent_id); CREATE INDEX IF NOT EXISTS idx_entries_type ON entries(type); CREATE INDEX IF NOT EXISTS idx_entries_stage ON entries(stage); CREATE INDEX IF NOT EXISTS idx_entries_assignee ON entries(assignee_id); CREATE INDEX IF NOT EXISTS idx_entries_return ON entries(return_to_id); CREATE INDEX IF NOT EXISTS idx_entries_origin ON entries(origin_id); CREATE INDEX IF NOT EXISTS idx_entries_search_key ON entries(search_key); CREATE INDEX IF NOT EXISTS idx_entries_deleted ON entries(deleted_at); -- Answer ↔ Request links (many-to-many) CREATE TABLE IF NOT EXISTS answer_links ( answer_id TEXT NOT NULL REFERENCES entries(entry_id), request_id TEXT NOT NULL REFERENCES entries(entry_id), linked_by TEXT NOT NULL, linked_at INTEGER NOT NULL, confirmed INTEGER NOT NULL DEFAULT 0, ai_score REAL, status TEXT NOT NULL DEFAULT 'pending', -- pending | confirmed | rejected reviewed_by TEXT, reviewed_at INTEGER, reject_reason TEXT, PRIMARY KEY (answer_id, request_id) ); -- Access / RBAC CREATE TABLE IF NOT EXISTS access ( id TEXT PRIMARY KEY, project_id TEXT NOT NULL, workstream_id TEXT, user_id TEXT NOT NULL, role TEXT NOT NULL, ops TEXT NOT NULL, can_grant INTEGER NOT NULL DEFAULT 0, granted_by TEXT NOT NULL, granted_at INTEGER NOT NULL, revoked_at INTEGER, revoked_by TEXT ); CREATE INDEX IF NOT EXISTS idx_access_user ON access(user_id); CREATE INDEX IF NOT EXISTS idx_access_project ON access(project_id); -- Entry events (workflow thread) CREATE TABLE IF NOT EXISTS entry_events ( id TEXT PRIMARY KEY, entry_id TEXT NOT NULL REFERENCES entries(entry_id), actor_id TEXT NOT NULL, channel TEXT NOT NULL, action BLOB NOT NULL, -- packed data BLOB NOT NULL, -- packed ts INTEGER NOT NULL ); CREATE INDEX IF NOT EXISTS idx_events_entry ON entry_events(entry_id); CREATE INDEX IF NOT EXISTS idx_events_actor ON entry_events(actor_id); -- Channel threads (email/Slack/Teams → entry mapping) CREATE TABLE IF NOT EXISTS channel_threads ( id TEXT PRIMARY KEY, entry_id TEXT NOT NULL REFERENCES entries(entry_id), channel TEXT NOT NULL, thread_id TEXT NOT NULL, project_id TEXT NOT NULL, UNIQUE(channel, thread_id) ); -- Embeddings (AI matching) CREATE TABLE IF NOT EXISTS embeddings ( entry_id TEXT PRIMARY KEY REFERENCES entries(entry_id), vector BLOB NOT NULL ); -- Audit log CREATE TABLE IF NOT EXISTS audit ( id TEXT PRIMARY KEY, project_id TEXT NOT NULL, actor_id TEXT NOT NULL, action BLOB NOT NULL, -- packed target_id TEXT, details BLOB, -- packed ip TEXT, ts INTEGER NOT NULL ); CREATE INDEX IF NOT EXISTS idx_audit_project ON audit(project_id); CREATE INDEX IF NOT EXISTS idx_audit_actor ON audit(actor_id); CREATE INDEX IF NOT EXISTS idx_audit_ts ON audit(ts); -- Sessions CREATE TABLE IF NOT EXISTS sessions ( id TEXT PRIMARY KEY, user_id TEXT NOT NULL REFERENCES users(user_id), fingerprint TEXT NOT NULL DEFAULT '', created_at INTEGER NOT NULL, expires_at INTEGER NOT NULL, revoked INTEGER NOT NULL DEFAULT 0 ); CREATE INDEX IF NOT EXISTS idx_sessions_user ON sessions(user_id); -- Broadcasts (idempotency) CREATE TABLE IF NOT EXISTS broadcasts ( id TEXT PRIMARY KEY, answer_id TEXT NOT NULL, request_id TEXT NOT NULL, recipient_id TEXT NOT NULL, sent_at INTEGER NOT NULL, UNIQUE(answer_id, request_id, recipient_id) );