-- Publish approval gate for research topics. Distinct from the existing -- `approvals` table (0001), which is tightly coupled to gated tool calls -- inside an agent run (session_key + run_id + action_type + category + -- payload + preview + requested_by_agent, all NOT NULL). None of those apply -- to a publish-of-a-static-topic gate; forcing them nullable would ripple -- through cm_safety, so we give publish approvals their own small table. -- -- Lifecycle: -- 1. Topic reaches `reviewing` (either the orchestrator flipped it after -- the last agent run completed, or the user hit /submit-review). -- 2. A workspace member calls /api/research/:id/request-publish, which -- writes a row here with status='pending'. -- 3. Another workspace member (any user in the workspace; per-role gating -- can layer on later) approves or rejects. -- 4. On the first approval, the topic transitions `reviewing → publishing` -- and (after any artifact assembly) `publishing → published`. -- 5. On a reject, the topic stays in `reviewing` and a new request is -- allowed. Old rejected rows are audit history. CREATE TABLE research_publish_approvals ( id UUID PRIMARY KEY, workspace_id UUID NOT NULL REFERENCES workspaces (id) ON DELETE CASCADE, topic_id UUID NOT NULL REFERENCES research_topics (id) ON DELETE CASCADE, requested_by UUID NOT NULL REFERENCES users (id) ON DELETE RESTRICT, status TEXT NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'approved', 'rejected')), decided_by UUID REFERENCES users (id) ON DELETE RESTRICT, decided_at TIMESTAMPTZ, created_at TIMESTAMPTZ NOT NULL DEFAULT now() ); -- Sidebar "pending approvals" list query. CREATE INDEX research_publish_approvals_pending_idx ON research_publish_approvals (workspace_id, created_at DESC) WHERE status = 'pending'; -- Look up "does this topic already have a pending approval?" before letting -- a caller create a second one. CREATE INDEX research_publish_approvals_topic_pending_idx ON research_publish_approvals (topic_id) WHERE status = 'pending';