improved a lot of things

This commit is contained in:
SinachPat
2026-04-26 09:09:55 +01:00
parent 2911f22df8
commit 97315846be
70 changed files with 4967 additions and 10 deletions
@@ -0,0 +1,174 @@
-- Origin Graph — Initial Schema
-- Migration: 001
-- All tables use UUIDs as primary keys and include created_at / updated_at.
-- Enable pgcrypto for gen_random_uuid() if not already enabled.
CREATE EXTENSION IF NOT EXISTS "pgcrypto";
-- ── Enum types ────────────────────────────────────────────────────────────────
CREATE TYPE origin_type AS ENUM (
'GIT_COMMIT',
'LINEAR_ISSUE',
'SLACK_MESSAGE',
'URL',
'FORK'
);
CREATE TYPE diff_status AS ENUM (
'DRAFT',
'EXPORTED',
'IMPLEMENTED',
'BLOCKED'
);
CREATE TYPE team_role AS ENUM (
'OWNER',
'DESIGNER',
'ENGINEER',
'PM',
'VIEWER'
);
CREATE TYPE agent_type AS ENUM (
'CURSOR',
'CLAUDE_CODE',
'GENERIC'
);
CREATE TYPE workspace_plan AS ENUM (
'FREE',
'TEAM',
'ENTERPRISE'
);
CREATE TYPE agent_session_status AS ENUM (
'ACTIVE',
'COMPLETED',
'FAILED'
);
-- ── workspaces ────────────────────────────────────────────────────────────────
CREATE TABLE workspaces (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name TEXT NOT NULL,
owner_id TEXT NOT NULL, -- Clerk user ID
plan workspace_plan NOT NULL DEFAULT 'FREE',
settings_jsonb JSONB NOT NULL DEFAULT '{}',
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
-- ── origins ───────────────────────────────────────────────────────────────────
CREATE TABLE origins (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
type origin_type NOT NULL,
source_ref TEXT NOT NULL,
source_metadata_jsonb JSONB NOT NULL DEFAULT '{}',
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
-- ── artboards ─────────────────────────────────────────────────────────────────
CREATE TABLE artboards (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
workspace_id UUID NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,
name TEXT NOT NULL,
origin_id UUID REFERENCES origins(id) ON DELETE SET NULL,
parent_artboard_id UUID REFERENCES artboards(id) ON DELETE SET NULL,
metadata_jsonb JSONB NOT NULL DEFAULT '{}',
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX artboards_workspace_idx ON artboards(workspace_id);
CREATE INDEX artboards_parent_idx ON artboards(parent_artboard_id);
-- ── intent_diffs ──────────────────────────────────────────────────────────────
CREATE TABLE intent_diffs (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
artboard_id UUID NOT NULL REFERENCES artboards(id) ON DELETE CASCADE,
author_id TEXT NOT NULL, -- Clerk user ID
changes_jsonb JSONB NOT NULL DEFAULT '{}',
summary TEXT NOT NULL DEFAULT '',
status diff_status NOT NULL DEFAULT 'DRAFT',
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX intent_diffs_artboard_idx ON intent_diffs(artboard_id);
CREATE INDEX intent_diffs_status_idx ON intent_diffs(status);
-- ── agent_sessions ────────────────────────────────────────────────────────────
CREATE TABLE agent_sessions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
artboard_id UUID NOT NULL REFERENCES artboards(id) ON DELETE CASCADE,
diff_id UUID REFERENCES intent_diffs(id) ON DELETE SET NULL,
agent_type agent_type NOT NULL,
messages_jsonb JSONB NOT NULL DEFAULT '[]',
status agent_session_status NOT NULL DEFAULT 'ACTIVE',
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX agent_sessions_artboard_idx ON agent_sessions(artboard_id);
CREATE INDEX agent_sessions_diff_idx ON agent_sessions(diff_id);
-- ── design_language_files ─────────────────────────────────────────────────────
CREATE TABLE design_language_files (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
workspace_id UUID NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,
name TEXT NOT NULL,
schema_jsonb JSONB NOT NULL,
version INTEGER NOT NULL DEFAULT 1,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX dlf_workspace_idx ON design_language_files(workspace_id);
-- ── team_members ──────────────────────────────────────────────────────────────
CREATE TABLE team_members (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
workspace_id UUID NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,
user_id TEXT NOT NULL, -- Clerk user ID
role team_role NOT NULL DEFAULT 'VIEWER',
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
UNIQUE (workspace_id, user_id)
);
CREATE INDEX team_members_workspace_idx ON team_members(workspace_id);
CREATE INDEX team_members_user_idx ON team_members(user_id);
-- ── updated_at trigger ────────────────────────────────────────────────────────
CREATE OR REPLACE FUNCTION set_updated_at()
RETURNS TRIGGER LANGUAGE plpgsql AS $$
BEGIN
NEW.updated_at = now();
RETURN NEW;
END;
$$;
DO $$
DECLARE t TEXT;
BEGIN
FOREACH t IN ARRAY ARRAY['workspaces','origins','artboards','intent_diffs',
'agent_sessions','design_language_files','team_members']
LOOP
EXECUTE format(
'CREATE TRIGGER trg_%I_updated_at
BEFORE UPDATE ON %I
FOR EACH ROW EXECUTE FUNCTION set_updated_at()',
t, t
);
END LOOP;
END;
$$;
@@ -0,0 +1,46 @@
-- Origin Graph — Artboard Ancestry Materialized View
-- Migration: 002
-- Pre-computes all ancestor/descendant relationships so the app never needs
-- recursive CTEs at query time. Updated automatically on artboards INSERT.
CREATE MATERIALIZED VIEW artboard_ancestry AS
WITH RECURSIVE ancestry(artboard_id, ancestor_id, depth) AS (
-- Base: each artboard is at depth 0 relative to itself
SELECT id AS artboard_id, id AS ancestor_id, 0 AS depth
FROM artboards
UNION ALL
-- Recurse: walk up the parent chain
SELECT a.id AS artboard_id, anc.ancestor_id, anc.depth + 1
FROM artboards a
JOIN ancestry anc ON a.parent_artboard_id = anc.artboard_id
)
SELECT artboard_id, ancestor_id, depth
FROM ancestry
WHERE artboard_id <> ancestor_id -- exclude self-reference
ORDER BY artboard_id, depth;
CREATE UNIQUE INDEX artboard_ancestry_pk
ON artboard_ancestry(artboard_id, ancestor_id);
CREATE INDEX artboard_ancestry_ancestor_idx
ON artboard_ancestry(ancestor_id);
-- ── Refresh trigger ───────────────────────────────────────────────────────────
-- Refreshes the materialized view concurrently whenever an artboard is
-- inserted. CONCURRENTLY requires the unique index above — it allows reads
-- to continue during refresh.
CREATE OR REPLACE FUNCTION refresh_artboard_ancestry()
RETURNS TRIGGER LANGUAGE plpgsql AS $$
BEGIN
REFRESH MATERIALIZED VIEW CONCURRENTLY artboard_ancestry;
RETURN NULL;
END;
$$;
CREATE TRIGGER trg_artboard_ancestry_refresh
AFTER INSERT OR UPDATE OF parent_artboard_id ON artboards
FOR EACH STATEMENT
EXECUTE FUNCTION refresh_artboard_ancestry();
@@ -0,0 +1,111 @@
-- Origin Graph — Row-Level Security Policies
-- Migration: 003
-- All tables are workspace-scoped. A user may only read or write rows in
-- workspaces where they have a team_members record. The Clerk JWT is verified
-- server-side; auth.uid() maps to the Clerk user_id column.
-- Enable RLS on every table
ALTER TABLE workspaces ENABLE ROW LEVEL SECURITY;
ALTER TABLE artboards ENABLE ROW LEVEL SECURITY;
ALTER TABLE origins ENABLE ROW LEVEL SECURITY;
ALTER TABLE intent_diffs ENABLE ROW LEVEL SECURITY;
ALTER TABLE agent_sessions ENABLE ROW LEVEL SECURITY;
ALTER TABLE design_language_files ENABLE ROW LEVEL SECURITY;
ALTER TABLE team_members ENABLE ROW LEVEL SECURITY;
-- ── Helper: is the current user a member of the given workspace? ──────────────
CREATE OR REPLACE FUNCTION is_workspace_member(ws_id UUID)
RETURNS BOOLEAN LANGUAGE sql SECURITY DEFINER AS $$
SELECT EXISTS (
SELECT 1 FROM team_members
WHERE workspace_id = ws_id
AND user_id = auth.uid()::TEXT
);
$$;
-- ── workspaces ────────────────────────────────────────────────────────────────
CREATE POLICY workspaces_select ON workspaces
FOR SELECT USING (is_workspace_member(id));
CREATE POLICY workspaces_insert ON workspaces
FOR INSERT WITH CHECK (owner_id = auth.uid()::TEXT);
CREATE POLICY workspaces_update ON workspaces
FOR UPDATE USING (owner_id = auth.uid()::TEXT);
-- ── artboards ─────────────────────────────────────────────────────────────────
CREATE POLICY artboards_select ON artboards
FOR SELECT USING (is_workspace_member(workspace_id));
CREATE POLICY artboards_insert ON artboards
FOR INSERT WITH CHECK (is_workspace_member(workspace_id));
CREATE POLICY artboards_update ON artboards
FOR UPDATE USING (is_workspace_member(workspace_id));
CREATE POLICY artboards_delete ON artboards
FOR DELETE USING (is_workspace_member(workspace_id));
-- ── intent_diffs ──────────────────────────────────────────────────────────────
-- Derived from artboard's workspace membership
CREATE POLICY intent_diffs_select ON intent_diffs
FOR SELECT USING (
is_workspace_member((SELECT workspace_id FROM artboards WHERE id = artboard_id))
);
CREATE POLICY intent_diffs_insert ON intent_diffs
FOR INSERT WITH CHECK (
is_workspace_member((SELECT workspace_id FROM artboards WHERE id = artboard_id))
);
CREATE POLICY intent_diffs_update ON intent_diffs
FOR UPDATE USING (
is_workspace_member((SELECT workspace_id FROM artboards WHERE id = artboard_id))
);
-- ── agent_sessions ────────────────────────────────────────────────────────────
CREATE POLICY agent_sessions_select ON agent_sessions
FOR SELECT USING (
is_workspace_member((SELECT workspace_id FROM artboards WHERE id = artboard_id))
);
CREATE POLICY agent_sessions_insert ON agent_sessions
FOR INSERT WITH CHECK (
is_workspace_member((SELECT workspace_id FROM artboards WHERE id = artboard_id))
);
CREATE POLICY agent_sessions_update ON agent_sessions
FOR UPDATE USING (
is_workspace_member((SELECT workspace_id FROM artboards WHERE id = artboard_id))
);
-- ── design_language_files ─────────────────────────────────────────────────────
CREATE POLICY dlf_select ON design_language_files
FOR SELECT USING (is_workspace_member(workspace_id));
CREATE POLICY dlf_insert ON design_language_files
FOR INSERT WITH CHECK (is_workspace_member(workspace_id));
CREATE POLICY dlf_update ON design_language_files
FOR UPDATE USING (is_workspace_member(workspace_id));
-- ── team_members ──────────────────────────────────────────────────────────────
CREATE POLICY team_members_select ON team_members
FOR SELECT USING (is_workspace_member(workspace_id));
-- Only workspace owners can add/remove members
CREATE POLICY team_members_insert ON team_members
FOR INSERT WITH CHECK (
EXISTS (SELECT 1 FROM workspaces WHERE id = workspace_id AND owner_id = auth.uid()::TEXT)
);
CREATE POLICY team_members_delete ON team_members
FOR DELETE USING (
EXISTS (SELECT 1 FROM workspaces WHERE id = workspace_id AND owner_id = auth.uid()::TEXT)
);
@@ -0,0 +1,6 @@
-- ── Migration 004: Add notes column to intent_diffs ──────────────────────────
-- Allows the coding agent to record why a diff was blocked or any implementation
-- notes when updating status via the Agent Bridge MCP tool.
ALTER TABLE intent_diffs
ADD COLUMN IF NOT EXISTS notes TEXT;