Files
mnote/rust/crates/control-plane/migrations/001-initial-schema.sql
T
lix-2026 47e224d419 chore: align sqlite control plane architecture
- replace default Convex control-plane wording with Rust SQLite control-plane across architecture, AGENTS, Reasonix, and design docs

- retire root Convex functions source and deploy script into recycle while keeping explicit cloud/compat/sync-replica boundaries

- add control-plane migration guard/docs and keep CodeGraph refreshed after the SQLite control-plane cutover
2026-05-22 17:45:22 +08:00

173 lines
5.3 KiB
SQL

-- 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)
);