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]>
270 lines
10 KiB
PL/PgSQL
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()
|
|
);
|