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.
This commit is contained in:
parent
f2eb14956e
commit
b89dea5c93
26 changed files with 1637 additions and 31 deletions
40
apps/api/migrations/033_publish_governance.sql
Normal file
40
apps/api/migrations/033_publish_governance.sql
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
-- 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';
|
||||
Loading…
Add table
Add a link
Reference in a new issue