oclaw/svc/persistence/ddl/postgresql_bootstrap.sql
oliver d14e9d3596 feat(persistence): PostgreSQL assistant store, chat persist fixes, gateway scripts
- Add SQLAlchemy Core repos, pg adapter/compat, assistant_store factory, Alembic bootstrap and migration/cutover scripts.

- Harden chat_message writes (NUL scrub for PG), turn_uuid on attempt failure, WS turn_runner fallbacks and gateway executed_turn_uuid init.

- start_gateway: log paths, PS7 stderr handling via cmd, background stdout/stderr redirect; runtime assistant_runtime_log_dir export.

- Ops: clear_all_chat_sessions with PG-only --postgresql and env-gated wipe; clear_postgres_chat_sessions.ps1.

- Tests: SA repos, pg compat, persist fallback, smoke env isolation; CI and docs touch-ups.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-14 09:33:50 +08:00

486 lines
22 KiB
SQL

-- Generated for PostgreSQL assistant store (from SQLite schema)
SET client_min_messages TO WARNING;
CREATE TABLE IF NOT EXISTS chat_session (
id TEXT PRIMARY KEY,
title TEXT NOT NULL,
created_at TEXT NOT NULL,
last_message_at TEXT
);
CREATE TABLE IF NOT EXISTS tenant (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
created_at TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS app_user (
id TEXT PRIMARY KEY,
tenant_id TEXT NOT NULL,
username TEXT,
display_name TEXT NOT NULL,
role TEXT NOT NULL,
password_hash TEXT,
is_active INTEGER NOT NULL DEFAULT 1,
created_at TEXT NOT NULL, avatar_attachment_id TEXT,
FOREIGN KEY(tenant_id) REFERENCES tenant(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS channel_identity (
tenant_id TEXT NOT NULL,
channel TEXT NOT NULL,
external_user_id TEXT NOT NULL,
user_id TEXT NOT NULL,
created_at TEXT NOT NULL,
PRIMARY KEY (tenant_id, channel, external_user_id),
FOREIGN KEY(tenant_id) REFERENCES tenant(id) ON DELETE CASCADE,
FOREIGN KEY(user_id) REFERENCES app_user(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS channel_identity_v2 (
tenant_id TEXT NOT NULL,
channel TEXT NOT NULL,
account_id TEXT NOT NULL,
external_user_id TEXT NOT NULL,
user_id TEXT NOT NULL,
created_at TEXT NOT NULL,
PRIMARY KEY (tenant_id, channel, account_id, external_user_id),
FOREIGN KEY(tenant_id) REFERENCES tenant(id) ON DELETE CASCADE,
FOREIGN KEY(user_id) REFERENCES app_user(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS bind_code (
code TEXT PRIMARY KEY,
tenant_id TEXT NOT NULL,
role TEXT NOT NULL,
created_at TEXT NOT NULL,
used_at TEXT,
used_by_external_user_id TEXT,
FOREIGN KEY(tenant_id) REFERENCES tenant(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS channel_session (
tenant_id TEXT NOT NULL,
channel TEXT NOT NULL,
external_chat_id TEXT NOT NULL,
external_user_id TEXT NOT NULL,
session_id TEXT NOT NULL,
created_at TEXT NOT NULL,
PRIMARY KEY (tenant_id, channel, external_chat_id, external_user_id),
FOREIGN KEY(tenant_id) REFERENCES tenant(id) ON DELETE CASCADE,
FOREIGN KEY(session_id) REFERENCES chat_session(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS channel_session_v2 (
tenant_id TEXT NOT NULL,
channel TEXT NOT NULL,
account_id TEXT NOT NULL,
external_chat_id TEXT NOT NULL,
external_user_id TEXT NOT NULL,
session_id TEXT NOT NULL,
created_at TEXT NOT NULL,
PRIMARY KEY (tenant_id, channel, account_id, external_chat_id, external_user_id),
FOREIGN KEY(tenant_id) REFERENCES tenant(id) ON DELETE CASCADE,
FOREIGN KEY(session_id) REFERENCES chat_session(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS user_channel_account (
tenant_id TEXT NOT NULL,
user_id TEXT NOT NULL,
channel TEXT NOT NULL,
account_id TEXT NOT NULL,
name TEXT NOT NULL DEFAULT '',
config TEXT NOT NULL DEFAULT '{}',
is_active INTEGER NOT NULL DEFAULT 1,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
PRIMARY KEY (tenant_id, user_id, channel, account_id),
FOREIGN KEY(tenant_id) REFERENCES tenant(id) ON DELETE CASCADE,
FOREIGN KEY(user_id) REFERENCES app_user(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS todo_item (
id TEXT PRIMARY KEY,
tenant_id TEXT NOT NULL,
owner_user_id TEXT NOT NULL,
assignee_user_id TEXT,
title TEXT NOT NULL,
due_at TEXT,
status TEXT NOT NULL,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
FOREIGN KEY(tenant_id) REFERENCES tenant(id) ON DELETE CASCADE,
FOREIGN KEY(owner_user_id) REFERENCES app_user(id) ON DELETE CASCADE,
FOREIGN KEY(assignee_user_id) REFERENCES app_user(id) ON DELETE SET NULL
);
CREATE TABLE IF NOT EXISTS chat_message (
id BIGSERIAL PRIMARY KEY,
session_id TEXT NOT NULL,
role TEXT NOT NULL,
content TEXT NOT NULL,
tool_calls TEXT,
attachments TEXT,
turn_uuid TEXT,
event_type TEXT,
event_payload TEXT,
timestamp TEXT NOT NULL,
FOREIGN KEY(session_id) REFERENCES chat_session(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS ui_session_owner (
session_id TEXT PRIMARY KEY,
tenant_id TEXT NOT NULL,
user_id TEXT NOT NULL,
created_at TEXT NOT NULL,
FOREIGN KEY(session_id) REFERENCES chat_session(id) ON DELETE CASCADE,
FOREIGN KEY(tenant_id) REFERENCES tenant(id) ON DELETE CASCADE,
FOREIGN KEY(user_id) REFERENCES app_user(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS user_workspace_path_allowlist (
tenant_id TEXT NOT NULL,
user_id TEXT NOT NULL,
extra_roots TEXT NOT NULL DEFAULT '',
allow_any_path INTEGER NOT NULL DEFAULT 0,
allow_high_risk_public_tools INTEGER NOT NULL DEFAULT 0,
updated_at TEXT NOT NULL,
PRIMARY KEY (tenant_id, user_id),
FOREIGN KEY(tenant_id) REFERENCES tenant(id) ON DELETE CASCADE,
FOREIGN KEY(user_id) REFERENCES app_user(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS auth_session (
session_token_hash TEXT PRIMARY KEY,
tenant_id TEXT NOT NULL,
user_id TEXT NOT NULL,
role TEXT NOT NULL,
created_at TEXT NOT NULL,
expires_at TEXT NOT NULL,
last_seen_at TEXT NOT NULL,
revoked_at TEXT,
FOREIGN KEY(tenant_id) REFERENCES tenant(id) ON DELETE CASCADE,
FOREIGN KEY(user_id) REFERENCES app_user(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS role_permission (
role TEXT NOT NULL,
permission TEXT NOT NULL,
created_at TEXT NOT NULL,
PRIMARY KEY (role, permission)
);
CREATE TABLE IF NOT EXISTS user_permission (
tenant_id TEXT NOT NULL,
user_id TEXT NOT NULL,
permission TEXT NOT NULL,
created_at TEXT NOT NULL,
PRIMARY KEY (tenant_id, user_id, permission),
FOREIGN KEY(tenant_id) REFERENCES tenant(id) ON DELETE CASCADE,
FOREIGN KEY(user_id) REFERENCES app_user(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS admin_audit_log (
id BIGSERIAL PRIMARY KEY,
actor_tenant_id TEXT NOT NULL,
actor_user_id TEXT NOT NULL,
action TEXT NOT NULL,
target_type TEXT NOT NULL,
target_id TEXT NOT NULL,
status TEXT NOT NULL,
detail TEXT NOT NULL DEFAULT '{}',
timestamp TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS attachment_acl (
attachment_id TEXT NOT NULL,
tenant_id TEXT NOT NULL,
user_id TEXT NOT NULL,
session_id TEXT NOT NULL,
source TEXT NOT NULL,
created_at TEXT NOT NULL,
PRIMARY KEY (attachment_id, tenant_id, user_id, session_id, source),
FOREIGN KEY(session_id) REFERENCES chat_session(id) ON DELETE CASCADE,
FOREIGN KEY(tenant_id) REFERENCES tenant(id) ON DELETE CASCADE,
FOREIGN KEY(user_id) REFERENCES app_user(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS tool_log (
id BIGSERIAL PRIMARY KEY,
session_id TEXT NOT NULL,
tool_name TEXT NOT NULL,
specialist TEXT NOT NULL DEFAULT '',
args TEXT NOT NULL,
result TEXT NOT NULL,
timestamp TEXT NOT NULL,
duration_ms INTEGER,
FOREIGN KEY(session_id) REFERENCES chat_session(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS tool_plugin (
plugin_name TEXT NOT NULL,
plugin_version TEXT NOT NULL,
entry_point TEXT NOT NULL,
enabled INTEGER NOT NULL DEFAULT 1,
updated_at TEXT NOT NULL,
PRIMARY KEY (plugin_name, entry_point)
);
CREATE TABLE IF NOT EXISTS mcp_server_registry (
server_id TEXT PRIMARY KEY,
source_type TEXT NOT NULL,
source_ref TEXT NOT NULL,
version TEXT NOT NULL DEFAULT '',
entry_command TEXT NOT NULL DEFAULT '',
entry_args TEXT NOT NULL DEFAULT '[]',
env_schema TEXT NOT NULL DEFAULT '{}',
required_permissions TEXT NOT NULL DEFAULT '[]',
risk_level TEXT NOT NULL DEFAULT 'high',
timeout_s DOUBLE PRECISION NOT NULL DEFAULT 30,
enabled INTEGER NOT NULL DEFAULT 0,
updated_at TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS mcp_server_installation (
id BIGSERIAL PRIMARY KEY,
server_id TEXT NOT NULL,
status TEXT NOT NULL,
error_code TEXT NOT NULL DEFAULT '',
detail TEXT NOT NULL DEFAULT '{}',
install_command TEXT NOT NULL DEFAULT '',
timestamp TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS mcp_server_health (
server_id TEXT PRIMARY KEY,
status TEXT NOT NULL,
detail TEXT NOT NULL DEFAULT '{}',
checked_at TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS mcp_server_tool (
server_id TEXT NOT NULL,
tool_name TEXT NOT NULL,
description TEXT NOT NULL DEFAULT '',
parameters TEXT NOT NULL DEFAULT '{}',
updated_at TEXT NOT NULL,
PRIMARY KEY (server_id, tool_name)
);
CREATE TABLE IF NOT EXISTS app_setting (
key TEXT PRIMARY KEY,
value TEXT NOT NULL,
is_secret INTEGER NOT NULL DEFAULT 0,
updated_at TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS knowledge_chunk (
chunk_id TEXT PRIMARY KEY,
source TEXT NOT NULL,
content TEXT NOT NULL,
metadata TEXT NOT NULL DEFAULT '{}',
updated_at TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS knowledge_embedding (
chunk_id TEXT NOT NULL,
model TEXT NOT NULL,
dim INTEGER NOT NULL,
vector_json TEXT NOT NULL,
updated_at TEXT NOT NULL,
PRIMARY KEY (chunk_id, model),
FOREIGN KEY(chunk_id) REFERENCES knowledge_chunk(chunk_id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS memory_item (
memory_id TEXT PRIMARY KEY,
tenant_id TEXT NOT NULL,
user_id TEXT NOT NULL,
session_id TEXT NOT NULL,
memory_type TEXT NOT NULL,
content TEXT NOT NULL,
confidence DOUBLE PRECISION NOT NULL DEFAULT 0,
source TEXT NOT NULL DEFAULT 'memory',
metadata TEXT NOT NULL DEFAULT '{}',
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
expires_at TEXT
);
CREATE TABLE IF NOT EXISTS memory_vector (
memory_id TEXT NOT NULL,
model TEXT NOT NULL,
dim INTEGER NOT NULL,
vector_json TEXT NOT NULL,
updated_at TEXT NOT NULL,
PRIMARY KEY (memory_id, model),
FOREIGN KEY(memory_id) REFERENCES memory_item(memory_id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS memory_hit_log (
id BIGSERIAL PRIMARY KEY,
tenant_id TEXT NOT NULL,
user_id TEXT NOT NULL,
session_id TEXT,
memory_id TEXT,
query_text TEXT NOT NULL,
score DOUBLE PRECISION NOT NULL DEFAULT 0,
source TEXT NOT NULL DEFAULT 'memory',
timestamp TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS oclaw_task (
id TEXT PRIMARY KEY,
tenant_id TEXT NOT NULL,
session_id TEXT NOT NULL,
task_type TEXT NOT NULL DEFAULT 'async_turn',
status TEXT NOT NULL,
payload TEXT NOT NULL DEFAULT '{}',
result TEXT NOT NULL DEFAULT '{}',
attempt_count INTEGER NOT NULL DEFAULT 0,
claimed_by TEXT,
lease_expires_at TEXT,
last_error TEXT NOT NULL DEFAULT '',
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
finished_at TEXT
);
CREATE TABLE IF NOT EXISTS oclaw_run (
run_id TEXT PRIMARY KEY,
tenant_id TEXT NOT NULL,
session_id TEXT NOT NULL,
status TEXT NOT NULL,
payload TEXT NOT NULL DEFAULT '{}',
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS oclaw_attempt (
id BIGSERIAL PRIMARY KEY,
run_id TEXT NOT NULL,
tenant_id TEXT NOT NULL,
session_id TEXT NOT NULL,
attempt_no INTEGER NOT NULL,
status TEXT NOT NULL,
reason TEXT NOT NULL DEFAULT '',
payload TEXT NOT NULL DEFAULT '{}',
created_at TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS agent_audit_log (
id BIGSERIAL PRIMARY KEY,
session_id TEXT NOT NULL,
specialist TEXT NOT NULL,
task_kind TEXT NOT NULL,
action TEXT NOT NULL,
payload TEXT NOT NULL,
status TEXT NOT NULL,
reason TEXT NOT NULL,
duration_ms INTEGER NOT NULL DEFAULT 0,
timestamp TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS agent_eval_log (
id BIGSERIAL PRIMARY KEY,
session_id TEXT NOT NULL,
specialist TEXT NOT NULL,
task_kind TEXT NOT NULL,
success INTEGER NOT NULL,
latency_ms INTEGER NOT NULL,
cost_hint DOUBLE PRECISION NOT NULL DEFAULT 0,
notes TEXT NOT NULL DEFAULT '',
timestamp TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS trace_event (
id BIGSERIAL PRIMARY KEY,
session_id TEXT NOT NULL,
trace_id TEXT NOT NULL,
span_id TEXT NOT NULL,
parent_span_id TEXT,
event_type TEXT NOT NULL,
payload TEXT NOT NULL DEFAULT '{}',
timestamp TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS llm_profile (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
mode TEXT NOT NULL,
model TEXT,
base_url TEXT,
api_key TEXT,
updated_at TEXT NOT NULL
, is_builtin INTEGER NOT NULL DEFAULT 0, hide_in_ui INTEGER NOT NULL DEFAULT 0, owner_user_id TEXT, thinking_mode_enabled INTEGER NOT NULL DEFAULT 0, reasoning_effort TEXT);
CREATE TABLE IF NOT EXISTS llm_profile_user_grant (
id TEXT PRIMARY KEY,
tenant_id TEXT NOT NULL,
profile_id TEXT NOT NULL,
user_id TEXT NOT NULL,
created_at TEXT NOT NULL,
created_by_user_id TEXT,
UNIQUE(tenant_id, profile_id, user_id),
FOREIGN KEY(profile_id) REFERENCES llm_profile(id) ON DELETE CASCADE,
FOREIGN KEY(user_id) REFERENCES app_user(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS llm_profile_tenant_grant (
id TEXT PRIMARY KEY,
tenant_id TEXT NOT NULL,
profile_id TEXT NOT NULL,
created_at TEXT NOT NULL,
created_by_user_id TEXT,
UNIQUE(tenant_id, profile_id),
FOREIGN KEY(profile_id) REFERENCES llm_profile(id) ON DELETE CASCADE
);
CREATE INDEX IF NOT EXISTS idx_chat_session_activity ON chat_session(COALESCE(last_message_at, created_at) DESC, created_at DESC);
CREATE UNIQUE INDEX IF NOT EXISTS idx_app_user_tenant_username ON app_user(tenant_id, username);
CREATE INDEX IF NOT EXISTS idx_user_channel_account_channel_account ON user_channel_account(channel, account_id, is_active);
CREATE INDEX IF NOT EXISTS idx_chat_message_session_turn_uuid ON chat_message(session_id, turn_uuid);
CREATE INDEX IF NOT EXISTS idx_ui_session_owner_tenant_user ON ui_session_owner(tenant_id, user_id, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_ui_session_owner_tenant_user_session ON ui_session_owner(tenant_id, user_id, session_id);
CREATE INDEX IF NOT EXISTS idx_ui_session_owner_tenant_session ON ui_session_owner(tenant_id, session_id);
CREATE INDEX IF NOT EXISTS idx_auth_session_user_expires ON auth_session(user_id, expires_at);
CREATE INDEX IF NOT EXISTS idx_admin_audit_actor_ts ON admin_audit_log(actor_user_id, timestamp DESC);
CREATE INDEX IF NOT EXISTS idx_attachment_acl_tenant_attachment ON attachment_acl(tenant_id, attachment_id, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_attachment_acl_user_attachment ON attachment_acl(tenant_id, user_id, attachment_id, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_attachment_acl_session_attachment ON attachment_acl(session_id, attachment_id, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_memory_item_tenant_user_updated ON memory_item(tenant_id, user_id, updated_at DESC);
CREATE INDEX IF NOT EXISTS idx_memory_hit_log_tenant_user_ts ON memory_hit_log(tenant_id, user_id, timestamp DESC);
CREATE INDEX IF NOT EXISTS idx_memory_item_session_updated ON memory_item(session_id, updated_at DESC);
CREATE INDEX IF NOT EXISTS idx_oclaw_task_status_updated ON oclaw_task(status, updated_at);
CREATE INDEX IF NOT EXISTS idx_oclaw_task_tenant_session ON oclaw_task(tenant_id, session_id, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_oclaw_run_tenant_session ON oclaw_run(tenant_id, session_id, updated_at DESC);
CREATE INDEX IF NOT EXISTS idx_oclaw_attempt_run_no ON oclaw_attempt(run_id, attempt_no);
CREATE INDEX IF NOT EXISTS idx_knowledge_chunk_source_updated ON knowledge_chunk(source, updated_at);
CREATE INDEX IF NOT EXISTS idx_trace_event_session_id_id ON trace_event(session_id, id);
CREATE INDEX IF NOT EXISTS idx_llm_profile_grant_user ON llm_profile_user_grant(tenant_id, user_id);
CREATE INDEX IF NOT EXISTS idx_llm_profile_grant_profile ON llm_profile_user_grant(tenant_id, profile_id);
CREATE INDEX IF NOT EXISTS idx_llm_profile_tenant_grant ON llm_profile_tenant_grant(tenant_id, profile_id);
CREATE INDEX IF NOT EXISTS idx_chat_message_session_id_id ON chat_message(session_id, id);