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:
glass beadian 2026-10-14 08:04:56 +00:00
parent f2eb14956e
commit b89dea5c93
26 changed files with 1637 additions and 31 deletions

View file

@ -85,6 +85,14 @@ describe("E3 gate in the dispatch path", () => {
expect(h.publish).not.toHaveBeenCalled();
});
it("publish gate pending (admin poll not passed): known no-send with zero HTTP requests", async () => {
const gate = gateReturning({ outcome: "blocked", reasonCodes: ["INSUFFICIENT_QUORUM", "PENDING_POLL"], policyVersion: "pv1", mode: "immediate", approvedHash: "abc", pollState: "open" });
const h = harness({ kind: "claimed", job }, gate);
await expect(h.activity.publishScheduled(ref)).resolves.toEqual({ kind: "failed", code: "POLICY_INSUFFICIENT_QUORUM" });
expect(h.fetchImpl).not.toHaveBeenCalled();
expect(h.publish).not.toHaveBeenCalled();
});
it("gate evaluated per destination with the lease's idempotency key", async () => {
const gate = gateReturning({ outcome: "fired", reasonCodes: [], policyVersion: "pv1", mode: "immediate", approvedHash: "abc", pollState: null });
const h = harness({ kind: "claimed", job }, gate);