zz_

app/worker/migrations/0002_unify_agents.sql

zzmq.dev@gmail.com/zz / app / worker / migrations / 0002_unify_agents.sql @ ebdb40fe Download
Ladder Summary
  • file This migration drops the legacy `accounts`, `agents`, `account_identities`, `agent_tokens`, `sessions`, `repos`, and related tables, then recreates a unified identity model where a single **agents** table represents both human and synthetic identities, with associated **agent_identities**, **agent_tokens**, **sessions**, **repos**, and a temporary **cli_login_sessions** table for CLI‑OAuth login flow. No JavaScript/TypeScript functions or classes are exported from this file (it is pure SQL).
 -- Collapses the accounts/agents two-tier hierarchy into one peer
 -- table, per governance.md: "One noun covers every participant: an
 -- Agent is an authenticatable identity, human or synthetic... There's
 -- no capability a synthetic Agent has that a human Agent couldn't
 -- exercise too." The previous schema special-cased "accounts" (OAuth-
 -- login humans, implicitly superior, able to override) vs "agents"
 -- (bot sub-identities, owned by an account) -- a real distinction in
 -- kind that shouldn't exist. Now there is exactly one identity table;
 -- "logged in via OAuth" and "authenticates with a bearer token" are
 -- just two kinds of credential the SAME kind of entity can hold, not
 -- two different entity types. `created_by` is provenance only (who
 -- spun up this identity, for /profile display) -- never an authority
 -- relationship; all real authority flows through repo-level Role
 -- grants (roles.json on each repo's zz-meta branch), uniformly, for
 -- every Agent. Dropping and recreating rather than migrating data:
 -- every row here is this session's own test/verification data.
 
 DROP TABLE IF EXISTS sessions;
 DROP TABLE IF EXISTS agent_tokens;
 DROP TABLE IF EXISTS account_identities;
 DROP TABLE IF EXISTS repos;
 DROP TABLE IF EXISTS agents;
 DROP TABLE IF EXISTS accounts;
 
 CREATE TABLE agents (
  id TEXT PRIMARY KEY,
  display_name TEXT NOT NULL,
  email TEXT,
  created_at TEXT NOT NULL,
  created_by TEXT REFERENCES agents(id) -- provenance only, nullable (a human's first login has no creator)
 );
 
 CREATE TABLE agent_identities (
  id TEXT PRIMARY KEY,
  agent_id TEXT NOT NULL REFERENCES agents(id),
  provider TEXT NOT NULL CHECK (provider IN ('cloudflare', 'google')),
  provider_subject TEXT NOT NULL,
  access_token_enc TEXT,
  refresh_token_enc TEXT,
  token_expires_at TEXT,
  created_at TEXT NOT NULL,
  UNIQUE (provider, provider_subject)
 );
 CREATE INDEX idx_agent_identities_agent ON agent_identities(agent_id);
 
 CREATE TABLE agent_tokens (
  id TEXT PRIMARY KEY,
  agent_id TEXT NOT NULL REFERENCES agents(id),
  token_hash TEXT NOT NULL UNIQUE,
  created_at TEXT NOT NULL,
  revoked_at TEXT,
  last_used_at TEXT
 );
 CREATE INDEX idx_agent_tokens_agent ON agent_tokens(agent_id);
 
 CREATE TABLE sessions (
  id TEXT PRIMARY KEY,
  agent_id TEXT NOT NULL REFERENCES agents(id),
  created_at TEXT NOT NULL,
  expires_at TEXT NOT NULL
 );
 CREATE INDEX idx_sessions_agent ON sessions(agent_id);
 
 CREATE TABLE repos (
  id TEXT PRIMARY KEY,
  slug TEXT NOT NULL UNIQUE, -- "<owner-segment>/<repo-name>"
  artifacts_name TEXT NOT NULL UNIQUE,
  description TEXT,
  owner_id TEXT NOT NULL REFERENCES agents(id), -- single FK; no more owner_type split
  created_at TEXT NOT NULL,
  updated_at TEXT NOT NULL
 );
 CREATE INDEX idx_repos_updated ON repos(updated_at DESC);
 CREATE INDEX idx_repos_owner ON repos(owner_id);
 
 -- `zz login`'s browser<->CLI bridge: the CLI generates `nonce`, opens
 -- a browser to it, polls until the OAuth callback fills in agent_id/
 -- token, then the row is consumed (never reused). See accountsDb.ts.
 DROP TABLE IF EXISTS cli_login_sessions;
 CREATE TABLE cli_login_sessions (
  nonce TEXT PRIMARY KEY,
  agent_id TEXT REFERENCES agents(id),
  token TEXT,
  created_at TEXT NOT NULL,
  consumed INTEGER NOT NULL DEFAULT 0
 );