Files
clawmates/migrations/0001_init.sql
T
Omar SobhandClaude Fable 5 add4f79fed Rebrand: TeamClaw -> Clawmates (clawmates.work)
Full-depth rename per the approved plan; the 'claw' product vocabulary
(claws, /claws routes, clawId, Claw Chat) stays — it is now the brand.

- Display brand: Clawmates (manifest, titles, hero, login/rail logo
  'clawmates'); default host app.clawmates.work; registry
  ghcr.io/clawmates
- Crates tc-* -> cm-* (16 crates + all imports); binaries
  clawmates-server/broker/bundler; images clawmates/*; env prefix
  CLAWMATES_* (+ CM_TEST_DATABASE_URL / CM_LIVE_LLM); config
  clawmates.toml; helm chart deploy/helm/clawmates with clawmates-*
  resources; db names clawmates*; sockets /run/clawmates; cookie
  cm_session; kind cluster clawmates-test; seccomp node profile
  clawmates-agent-profile.json
- All 9 Playwright brand assertions updated in lockstep; historical
  spec document left untouched as the only remaining 'TeamClaw'
- Local env migrated: dev pg clawmates-dev-pg/clawmates_dev, shared
  test server clawmates-test-pg, kind cluster recreated with image +
  profile, compose images rebuilt under clawmates/*

Verified end to end: 161 Rust + 68 frontend tests, 29 Playwright
journeys, 4 live kind tests, helm/install/LOC/placeholder gates, and
the clean-room install rehearsal serving the clawmates login page from
a signed bundle of the rebuilt images.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-06-10 12:31:25 -05:00

270 lines
10 KiB
PL/PgSQL

-- Clawmates schema, spec §14. Single source of truth for the data model.
-- Ids are UUIDv7 generated by the application (cm-domain).
CREATE TABLE workspaces (
id UUID PRIMARY KEY,
name TEXT NOT NULL,
plan TEXT NOT NULL DEFAULT 'free',
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE TABLE users (
id UUID PRIMARY KEY,
workspace_id UUID NOT NULL REFERENCES workspaces (id),
email TEXT NOT NULL UNIQUE,
role TEXT NOT NULL CHECK (role IN ('owner', 'member')),
display_name TEXT NOT NULL,
-- Subject claim for OIDC users; NULL for local-auth users.
auth_subject TEXT,
password_hash TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX users_workspace_idx ON users (workspace_id);
CREATE TABLE agents (
id UUID PRIMARY KEY,
workspace_id UUID NOT NULL REFERENCES workspaces (id),
name TEXT NOT NULL,
job_title TEXT NOT NULL,
-- The Settings "Job Description" textarea IS the system prompt (§7.7).
system_prompt TEXT NOT NULL DEFAULT '',
avatar TEXT NOT NULL DEFAULT '',
accent TEXT NOT NULL DEFAULT '',
wallpaper TEXT NOT NULL DEFAULT '',
managed_by UUID NOT NULL REFERENCES users (id),
status TEXT NOT NULL CHECK (status IN ('provisioning', 'online', 'offline')),
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
deleted_at TIMESTAMPTZ
);
CREATE INDEX agents_workspace_idx ON agents (workspace_id) WHERE deleted_at IS NULL;
CREATE TABLE access_policies (
agent_id UUID PRIMARY KEY REFERENCES agents (id) ON DELETE CASCADE,
humans_mode TEXT NOT NULL CHECK (humans_mode IN ('entire_team', 'specific')),
human_ids UUID[] NOT NULL DEFAULT '{}',
agents_mode TEXT NOT NULL CHECK (agents_mode IN ('any', 'specific')),
agent_ids UUID[] NOT NULL DEFAULT '{}'
);
CREATE TABLE sessions (
id UUID PRIMARY KEY,
agent_id UUID NOT NULL REFERENCES agents (id),
workspace_id UUID NOT NULL REFERENCES workspaces (id),
title TEXT NOT NULL DEFAULT '',
shard SMALLINT NOT NULL DEFAULT 0,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
last_active_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX sessions_agent_idx ON sessions (agent_id, last_active_at DESC);
CREATE TABLE messages (
id UUID PRIMARY KEY,
session_id UUID NOT NULL REFERENCES sessions (id),
seq BIGINT NOT NULL,
role TEXT NOT NULL CHECK (role IN ('user', 'agent', 'system')),
content JSONB NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
UNIQUE (session_id, seq)
);
CREATE TABLE steps (
id UUID PRIMARY KEY,
message_id UUID NOT NULL REFERENCES messages (id),
seq INT NOT NULL,
kind TEXT NOT NULL,
tool_name TEXT,
input JSONB,
output JSONB,
-- Taint sources: 'web', 'email', 'inter_agent', 'tool_result' (§15).
taint TEXT[] NOT NULL DEFAULT '{}',
status TEXT NOT NULL,
started_at TIMESTAMPTZ NOT NULL DEFAULT now(),
finished_at TIMESTAMPTZ,
UNIQUE (message_id, seq)
);
CREATE TABLE agent_runs (
id UUID PRIMARY KEY,
session_id UUID NOT NULL REFERENCES sessions (id),
state TEXT NOT NULL CHECK
(state IN ('running', 'awaiting_approval', 'completed', 'failed', 'cancelled')),
checkpoint JSONB,
last_event_id BIGINT NOT NULL DEFAULT 0,
error TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE TABLE approvals (
id UUID PRIMARY KEY,
workspace_id UUID NOT NULL REFERENCES workspaces (id),
run_id UUID NOT NULL REFERENCES agent_runs (id),
session_key TEXT NOT NULL,
action_type TEXT NOT NULL,
category TEXT NOT NULL CHECK (category IN (
'outbound_message', 'secret_sharing', 'access_change',
'financial_transaction', 'file_deletion', 'infra_access_grant')),
payload JSONB NOT NULL,
-- The exact rendered preview shown to the human (§10 approval card).
preview JSONB NOT NULL,
requested_by_agent UUID NOT NULL REFERENCES agents (id),
taint_sources TEXT[] NOT NULL DEFAULT '{}',
status TEXT NOT NULL CHECK
(status IN ('pending', 'approved', 'rejected', 'expired')),
decided_by UUID REFERENCES users (id),
decided_at TIMESTAMPTZ,
expires_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX approvals_pending_idx ON approvals (workspace_id, created_at)
WHERE status = 'pending';
CREATE TABLE execution_grants (
id UUID PRIMARY KEY,
approval_id UUID NOT NULL UNIQUE REFERENCES approvals (id),
nonce TEXT NOT NULL,
consumed BOOLEAN NOT NULL DEFAULT false,
consumed_at TIMESTAMPTZ
);
CREATE TABLE audit_log (
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
workspace_id UUID NOT NULL REFERENCES workspaces (id),
actor_kind TEXT NOT NULL CHECK (actor_kind IN ('user', 'agent', 'system')),
actor_id UUID,
event_type TEXT NOT NULL,
subject_type TEXT NOT NULL,
subject_id TEXT NOT NULL,
detail JSONB NOT NULL DEFAULT '{}',
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
-- The audit log is append-only at the database level, not by convention.
CREATE FUNCTION audit_log_immutable() RETURNS trigger AS $$
BEGIN
RAISE EXCEPTION 'audit_log is append-only';
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER audit_log_no_update
BEFORE UPDATE OR DELETE ON audit_log
FOR EACH ROW EXECUTE FUNCTION audit_log_immutable();
CREATE TABLE skills (
id UUID PRIMARY KEY,
-- NULL workspace = catalog skill visible to every workspace (§8.1).
workspace_id UUID REFERENCES workspaces (id),
title TEXT NOT NULL,
author TEXT NOT NULL,
description TEXT NOT NULL DEFAULT '',
body TEXT NOT NULL DEFAULT '',
installs INT NOT NULL DEFAULT 0,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE TABLE installed_skills (
agent_id UUID NOT NULL REFERENCES agents (id) ON DELETE CASCADE,
skill_id UUID NOT NULL REFERENCES skills (id),
installed_by UUID NOT NULL REFERENCES users (id),
installed_at TIMESTAMPTZ NOT NULL DEFAULT now(),
PRIMARY KEY (agent_id, skill_id)
);
CREATE TABLE secrets (
id UUID PRIMARY KEY,
workspace_id UUID NOT NULL REFERENCES workspaces (id),
kind TEXT NOT NULL,
ciphertext BYTEA NOT NULL,
nonce BYTEA NOT NULL,
key_version INT NOT NULL DEFAULT 1,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE TABLE app_connections (
id UUID PRIMARY KEY,
workspace_id UUID NOT NULL REFERENCES workspaces (id),
agent_id UUID REFERENCES agents (id),
provider TEXT NOT NULL,
auth_type TEXT NOT NULL CHECK
(auth_type IN ('oauth', 'keys', 'basic', 'mcp_oauth')),
scopes TEXT[] NOT NULL DEFAULT '{}',
status TEXT NOT NULL,
secret_ref UUID REFERENCES secrets (id),
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE TABLE threads (
id UUID PRIMARY KEY,
workspace_id UUID NOT NULL REFERENCES workspaces (id),
subject TEXT NOT NULL,
sensitivity TEXT NOT NULL DEFAULT 'normal' CHECK
(sensitivity IN ('normal', 'sensitive')),
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE TABLE thread_participants (
thread_id UUID NOT NULL REFERENCES threads (id) ON DELETE CASCADE,
agent_id UUID NOT NULL REFERENCES agents (id),
PRIMARY KEY (thread_id, agent_id)
);
CREATE TABLE thread_messages (
id UUID PRIMARY KEY,
thread_id UUID NOT NULL REFERENCES threads (id),
from_agent UUID NOT NULL REFERENCES agents (id),
content JSONB NOT NULL,
taint TEXT[] NOT NULL DEFAULT '{inter_agent}',
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE TABLE routines (
id UUID PRIMARY KEY,
agent_id UUID NOT NULL REFERENCES agents (id) ON DELETE CASCADE,
name TEXT NOT NULL,
schedule_cron TEXT NOT NULL,
action JSONB NOT NULL,
status TEXT NOT NULL DEFAULT 'active' CHECK
(status IN ('active', 'paused')),
next_run_at TIMESTAMPTZ,
last_run_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE TABLE file_nodes (
id UUID PRIMARY KEY,
workspace_id UUID NOT NULL REFERENCES workspaces (id),
-- NULL for the team-wide shared drive; set for per-agent drives (§7.4).
agent_id UUID REFERENCES agents (id),
drive TEXT NOT NULL CHECK (drive IN ('documents', 'received', 'shared')),
path TEXT NOT NULL,
kind TEXT NOT NULL CHECK (kind IN ('file', 'folder')),
size BIGINT NOT NULL DEFAULT 0,
blob_ref TEXT,
owner_kind TEXT NOT NULL CHECK (owner_kind IN ('user', 'agent')),
owner_id UUID NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE UNIQUE INDEX file_nodes_path_idx
ON file_nodes (workspace_id, drive, COALESCE(agent_id, '00000000-0000-0000-0000-000000000000'::uuid), path);
CREATE TABLE credit_lots (
id UUID PRIMARY KEY,
workspace_id UUID NOT NULL REFERENCES workspaces (id),
amount NUMERIC NOT NULL CHECK (amount >= 0),
remaining NUMERIC NOT NULL CHECK (remaining >= 0),
source TEXT NOT NULL,
purchased_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE TABLE usage_events (
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
workspace_id UUID NOT NULL REFERENCES workspaces (id),
agent_id UUID REFERENCES agents (id),
run_id UUID REFERENCES agent_runs (id),
kind TEXT NOT NULL,
tokens_in BIGINT NOT NULL DEFAULT 0,
tokens_out BIGINT NOT NULL DEFAULT 0,
credits NUMERIC NOT NULL DEFAULT 0,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);