-- 001-initial-schema.sql -- MNote control-plane v1 schema -- -- All tables use TEXT for datetime columns (ISO-8601) so the database -- remains readable without language-specific timestamp types. CREATE TABLE IF NOT EXISTS users ( id TEXT PRIMARY KEY, email TEXT UNIQUE, username TEXT UNIQUE NOT NULL, display_name TEXT NOT NULL, role TEXT NOT NULL DEFAULT 'user', status TEXT NOT NULL DEFAULT 'active', created_at TEXT NOT NULL, updated_at TEXT NOT NULL, revision INTEGER NOT NULL DEFAULT 1 ); CREATE TABLE IF NOT EXISTS auth_identities ( id TEXT PRIMARY KEY, user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE, provider TEXT NOT NULL, provider_subject TEXT NOT NULL, password_hash TEXT, password_version INTEGER NOT NULL DEFAULT 1, created_at TEXT NOT NULL, updated_at TEXT NOT NULL, UNIQUE(provider, provider_subject) ); CREATE TABLE IF NOT EXISTS auth_sessions ( id TEXT PRIMARY KEY, user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE, token_hash TEXT NOT NULL UNIQUE, user_agent TEXT, ip_hash TEXT, created_at TEXT NOT NULL, expires_at TEXT NOT NULL, revoked_at TEXT, last_seen_at TEXT NOT NULL ); CREATE INDEX IF NOT EXISTS idx_auth_sessions_user ON auth_sessions(user_id); CREATE INDEX IF NOT EXISTS idx_auth_sessions_token ON auth_sessions(token_hash); CREATE TABLE IF NOT EXISTS workspaces ( id TEXT PRIMARY KEY, owner_user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE, name TEXT NOT NULL, kind TEXT NOT NULL DEFAULT 'personal', root_uri TEXT NOT NULL UNIQUE, root_path TEXT NOT NULL, source_kind TEXT NOT NULL DEFAULT 'local_folder', status TEXT NOT NULL DEFAULT 'active', created_at TEXT NOT NULL, updated_at TEXT NOT NULL, revision INTEGER NOT NULL DEFAULT 1 ); CREATE INDEX IF NOT EXISTS idx_workspaces_owner ON workspaces(owner_user_id); CREATE TABLE IF NOT EXISTS workspace_members ( id TEXT PRIMARY KEY, workspace_id TEXT NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE, user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE, role TEXT NOT NULL, status TEXT NOT NULL DEFAULT 'active', created_at TEXT NOT NULL, updated_at TEXT NOT NULL, revision INTEGER NOT NULL DEFAULT 1, UNIQUE(workspace_id, user_id) ); CREATE TABLE IF NOT EXISTS directory_grants ( id TEXT PRIMARY KEY, user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE, workspace_id TEXT REFERENCES workspaces(id) ON DELETE SET NULL, root_uri TEXT NOT NULL, root_path TEXT NOT NULL, permission TEXT NOT NULL, recursive INTEGER NOT NULL DEFAULT 1, capabilities_json TEXT NOT NULL DEFAULT '[]', source TEXT NOT NULL DEFAULT 'admin', status TEXT NOT NULL DEFAULT 'active', created_by TEXT REFERENCES users(id), created_at TEXT NOT NULL, updated_at TEXT NOT NULL, revision INTEGER NOT NULL DEFAULT 1 ); CREATE INDEX IF NOT EXISTS idx_directory_grants_user ON directory_grants(user_id); CREATE INDEX IF NOT EXISTS idx_directory_grants_root_uri ON directory_grants(root_uri); CREATE TABLE IF NOT EXISTS share_links ( id TEXT PRIMARY KEY, workspace_id TEXT NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE, resource_kind TEXT NOT NULL, resource_id TEXT NOT NULL, token_hash TEXT NOT NULL UNIQUE, permission TEXT NOT NULL, created_by TEXT NOT NULL REFERENCES users(id), expires_at TEXT, revoked_at TEXT, created_at TEXT NOT NULL, updated_at TEXT NOT NULL, revision INTEGER NOT NULL DEFAULT 1 ); CREATE INDEX IF NOT EXISTS idx_share_links_resource ON share_links(workspace_id, resource_kind, resource_id); CREATE TABLE IF NOT EXISTS sync_state ( id TEXT PRIMARY KEY, workspace_id TEXT NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE, remote_kind TEXT NOT NULL, remote_id TEXT, cursor TEXT, last_synced_at TEXT, status TEXT NOT NULL DEFAULT 'idle', error_json TEXT, updated_at TEXT NOT NULL, revision INTEGER NOT NULL DEFAULT 1, UNIQUE(workspace_id, remote_kind) ); CREATE TABLE IF NOT EXISTS ai_policies ( id TEXT PRIMARY KEY, user_id TEXT REFERENCES users(id) ON DELETE CASCADE, workspace_id TEXT REFERENCES workspaces(id) ON DELETE CASCADE, allowed_roots_json TEXT NOT NULL DEFAULT '[]', model_policy_json TEXT NOT NULL DEFAULT '{}', quota_json TEXT NOT NULL DEFAULT '{}', created_at TEXT NOT NULL, updated_at TEXT NOT NULL, revision INTEGER NOT NULL DEFAULT 1 ); CREATE TABLE IF NOT EXISTS audit_log ( id TEXT PRIMARY KEY, actor_user_id TEXT REFERENCES users(id), action TEXT NOT NULL, target_kind TEXT NOT NULL, target_id TEXT, metadata_json TEXT NOT NULL DEFAULT '{}', created_at TEXT NOT NULL ); CREATE INDEX IF NOT EXISTS idx_audit_actor ON audit_log(actor_user_id, created_at); CREATE INDEX IF NOT EXISTS idx_audit_target ON audit_log(target_kind, target_id, created_at); CREATE TABLE IF NOT EXISTS outbox_events ( id TEXT PRIMARY KEY, topic TEXT NOT NULL, event_type TEXT NOT NULL, payload_json TEXT NOT NULL, created_at TEXT NOT NULL, delivered_at TEXT, attempts INTEGER NOT NULL DEFAULT 0 ); CREATE INDEX IF NOT EXISTS idx_outbox_pending ON outbox_events(delivered_at, created_at); CREATE TABLE IF NOT EXISTS legacy_id_map ( id TEXT PRIMARY KEY, legacy_system TEXT NOT NULL, legacy_kind TEXT NOT NULL, legacy_id TEXT NOT NULL, new_kind TEXT NOT NULL, new_id TEXT NOT NULL, metadata_json TEXT NOT NULL DEFAULT '{}', created_at TEXT NOT NULL, UNIQUE(legacy_system, legacy_kind, legacy_id) );