zz_

app/worker/migrations/0002_unify_agents.sql

zzmq.dev@gmail.com/zz / app / worker / migrations / 0002_unify_agents.sql @ 5e5720fb 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
);