T-HUB/apps/api/migrations/033_publish_governance.sql
glass beadian b89dea5c93 feat: per-connection publish governance — auto, 1-admin, majority, consensus
Per the owner's spec: each social connection gets publishGovernance in its
fields — unset = manual publish (today), auto = dispatch on approval,
admin1/admin_majority/admin_consensus = a publish-phase chatgov poll
(subject draft_publish, its own open-poll slot, no collision with review
gates). Dispatch holds until the publish poll passes (E3 pre-dispatch
check); auto dispatches on approval. Includes migration 033, the PATCH
/connections/:id/publish-governance route (chatgov.manage), and 19+31 new
tests. Implementation by the branding-panel process (claude/opus), lint
and integration fixes here.
2026-10-14 08:04:56 +00:00

40 lines
2.2 KiB
SQL

-- Per-connection publish governance (publish-phase gate).
--
-- connections.fields.publishGovernance selects what happens once a draft is
-- approved: unset = manual publish (today), 'auto' = dispatch on approval,
-- 'admin1' | 'admin_majority' | 'admin_consensus' = a SECOND chatgov poll
-- (subject_kind 'draft_publish', kind yes_no) must pass before the publish
-- is scheduled. The setting itself lives in the established fields jsonb;
-- this migration only teaches `polls` about the publish-phase subject.
--
-- publish_connection_id the destination the publish poll releases to
-- publish_request {publishAt, requiredPermission} captured at hold
-- time; the requester is polls.created_by and stays
-- the scheduling principal (re-checked at claim).
ALTER TABLE polls DROP CONSTRAINT polls_subject_kind_check;
ALTER TABLE polls ADD CONSTRAINT polls_subject_kind_check CHECK
(subject_kind IN ('draft', 'chat_post', 'none', 'draft_publish'));
ALTER TABLE polls
ADD COLUMN publish_connection_id uuid,
ADD COLUMN publish_request jsonb CHECK (publish_request IS NULL OR jsonb_typeof(publish_request) = 'object');
ALTER TABLE polls ADD CONSTRAINT polls_publish_connection_fk
FOREIGN KEY (publish_connection_id, workspace_id) REFERENCES connections (id, workspace_id)
ON DELETE NO ACTION ON UPDATE RESTRICT;
ALTER TABLE polls ADD CONSTRAINT polls_publish_subject_check CHECK (
(subject_kind = 'draft_publish') = (publish_connection_id IS NOT NULL)
AND (subject_kind <> 'draft_publish' OR (draft_id IS NOT NULL AND publish_request IS NOT NULL))
);
-- One open REVIEW gate per draft stays index-enforced; publish gates get
-- their own slot per (draft, destination) so they never collide with it.
DROP INDEX polls_open_one_per_draft;
CREATE UNIQUE INDEX polls_open_one_per_draft ON polls (workspace_id, draft_id)
WHERE state = 'open' AND draft_id IS NOT NULL AND subject_kind <> 'draft_publish';
CREATE UNIQUE INDEX polls_open_one_per_publish ON polls (workspace_id, draft_id, publish_connection_id)
WHERE state = 'open' AND subject_kind = 'draft_publish';
CREATE INDEX polls_ws_publish ON polls (workspace_id, draft_id, publish_connection_id, created_at DESC)
WHERE subject_kind = 'draft_publish';