From 5e094f347e58806d24b4b9110e667b609e1c7afc Mon Sep 17 00:00:00 2001 From: root Date: Sun, 31 May 2026 10:35:56 +1000 Subject: [PATCH] =?UTF-8?q?feat(schema):=20004=20=E2=80=94=20agents,=20age?= =?UTF-8?q?nt=5Ftokens,=20conversations,=20messages?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/db/migrations/004_agents.sql | 50 ++++++++++++++++++++++++++++++++ tests/db/migration_004.test.js | 24 +++++++++++++++ 2 files changed, 74 insertions(+) create mode 100644 lib/db/migrations/004_agents.sql create mode 100644 tests/db/migration_004.test.js diff --git a/lib/db/migrations/004_agents.sql b/lib/db/migrations/004_agents.sql new file mode 100644 index 0000000..28ccb4c --- /dev/null +++ b/lib/db/migrations/004_agents.sql @@ -0,0 +1,50 @@ +CREATE TABLE agents ( + id uuid PRIMARY KEY DEFAULT gen_random_uuid(), + slug text NOT NULL UNIQUE, + name text NOT NULL, + kind text NOT NULL + CHECK (kind IN ('claude','ollama','mastra','mcp-client','external')), + model text, + persona_path text, + capabilities jsonb NOT NULL DEFAULT '{"read":true,"suggest":true,"write":false}'::jsonb, + scopes jsonb NOT NULL DEFAULT '{}'::jsonb, + created_at timestamptz NOT NULL DEFAULT now() +); + +CREATE TABLE agent_tokens ( + id uuid PRIMARY KEY DEFAULT gen_random_uuid(), + agent_id uuid NOT NULL REFERENCES agents(id) ON DELETE CASCADE, + label text, + token_hash text NOT NULL, + last_used timestamptz, + created_at timestamptz NOT NULL DEFAULT now(), + revoked_at timestamptz +); + +CREATE TABLE conversations ( + id uuid PRIMARY KEY DEFAULT gen_random_uuid(), + title text, + agent_id uuid REFERENCES agents(id) ON DELETE SET NULL, + participants text[] NOT NULL DEFAULT '{}', + status text NOT NULL DEFAULT 'open' + CHECK (status IN ('open','summarized','archived')), + summary text, + metadata jsonb NOT NULL DEFAULT '{}'::jsonb, + embedding vector(1024), + started_at timestamptz NOT NULL DEFAULT now(), + ended_at timestamptz +); + +CREATE TABLE messages ( + id uuid PRIMARY KEY DEFAULT gen_random_uuid(), + conversation_id uuid NOT NULL REFERENCES conversations(id) ON DELETE CASCADE, + role text NOT NULL CHECK (role IN ('user','assistant','system','tool')), + agent_id uuid REFERENCES agents(id) ON DELETE SET NULL, + body text NOT NULL, + metadata jsonb NOT NULL DEFAULT '{}'::jsonb, + created_at timestamptz NOT NULL DEFAULT now() +); + +CREATE INDEX idx_messages_conv ON messages(conversation_id, created_at); +CREATE INDEX idx_messages_fts ON messages USING GIN (to_tsvector('english', body)); +CREATE INDEX idx_agent_tokens_hash ON agent_tokens(token_hash) WHERE revoked_at IS NULL; diff --git a/tests/db/migration_004.test.js b/tests/db/migration_004.test.js new file mode 100644 index 0000000..12d562e --- /dev/null +++ b/tests/db/migration_004.test.js @@ -0,0 +1,24 @@ +import { describe, it, expect, beforeEach } from 'vitest'; +import { resetDb, withClient } from '../helpers/db.js'; +import { migrateUp } from '../../lib/db/migrate.js'; + +describe('migration 004 — agents', () => { + beforeEach(async () => { await resetDb(); await migrateUp(); }); + + it('creates agents, agent_tokens, conversations, messages', async () => { + await withClient(async (c) => { + for (const t of ['agents','agent_tokens','conversations','messages']) { + const { rows } = await c.query(`SELECT to_regclass('public.' || $1) AS t;`, [t]); + expect(rows[0].t).toBe(t); + } + }); + }); + + it('agents.kind enum is enforced', async () => { + await withClient(async (c) => { + await expect(c.query( + `INSERT INTO agents(slug, name, kind) VALUES('x','X','invalid');` + )).rejects.toThrow(/check/i); + }); + }); +});