Skip to content

Add a 2/3+1 quorum mode to repository approval policy - #21

Merged
Deep-Sixed merged 8 commits into
mainfrom
claude/eloquent-galileo-xu0ah7-approvers
Sep 30, 2026
Merged

Deep-Sixed merged 8 commits into
mainfrom
claude/eloquent-galileo-xu0ah7-approvers

Conversation

@Deep-Sixed

@Deep-Sixed Deep-Sixed commented Sep 29, 2026 •

Copy link
Copy Markdown
Owner

Restores Codex_Kernel's quorum that grows with the number of approvers, built on the repository approval policy from #12 and the per-operation overrides from #27.

Rebuilt. This PR originally added its own approver roster and separation-of-duties rules. #12 merged in the meantime and already covers those (roles, requester can't vote, an approver can't use its own approval, re-check at use time). As the repository owner decided, this PR is now only the piece #12 lacks.

Problem

In Codex_Kernel, a proposal needed ⌊2N/3⌋+1 votes from its N authorized nodes, so the bar rose as the approver set grew. #12's approval_threshold is a fixed number and defaults to 1. With ten operators, a single vote still approves a push to main.

Change

  • quorumgit repo policy <repo> --quorum | --no-quorum (operator-only and audited, like the rest of the policy). With quorum on, an operation needs 2/3+1 of the agents eligible to vote on it. "Eligible" means:

    • the agent holds one of the policy's approving roles;
    • minus the requester, unless --requester-may-vote is set;
    • minus a takeover's beneficiary.

    --threshold stays as a floor.

  • Per-operation overrides can set quorum mode (Add per-operation approval policy overrides #27): repo policy <repo> --operation force_update --quorum turns it on for one operation type, --no-quorum turns it off for one type while the default has it on, and an override that doesn't mention it follows the default. --inherit can't be combined with --quorum. The repo policy listing shows the quorum rule on each override.

  • Counting only eligible agents means these exclusions never make approval impossible while other approvers exist. For example, with three operators, one operator's own request needs the other two, not three of three.

  • gate.required_approvals() is the single requirement, used when votes are tallied, when an approval is used, and when Add per-operation approval policy overrides #27 decides whether an approved instance is stale and should reopen for votes. As a result:

    • An approval's status always matches whether it can be used.
    • Because eligibility is worked out again at use time, adding approvers raises the bar for approvals not yet used, just as raising --threshold already does; the next eligible vote reopens such an approval so it can collect the missing votes.
    • Who may carry out an approval is still Move approval authority to the repository and continue retained worktrees #12's separate rule: an approver never uses its own approval.
  • The approval instance records the requirement that applied when it was requested, for display.

  • repo policy shows the quorum rule and how many agents currently hold an approving role.

  • Migration 009_approval_quorum.sql adds repositories.approval_quorum (default off) and a nullable operation_approval_policies.approval_quorum (NULL inherits). Renumbered from 006 after Bind push authorization to Git's reference transaction #30, Measure claim scopes against the mainline, not every ref #29 and Add per-operation approval policy overrides #27 took 006–008.

With quorum mode off, behaviour is unchanged. Approvals created by #23's content governance (protected paths, ref namespaces) go through the same required_approvals(), so quorum mode applies to them too.

Tests

tests/test_approval_quorum.py adds 21 tests, run against an isolated store because roles are global:

  • the quorum table (9 cases);
  • a worker's request needs 3 of 4 operators;
  • the requester exclusion shrinks the eligible set (2 of 2);
  • status and usability use the same requirement (5 operators, 3 votes stays pending even for a non-voting operator);
  • --threshold acts as a floor;
  • adding operators invalidates an unused approval;
  • approving roles decide N;
  • quorum off keeps the fixed threshold;
  • changes are operator-only and audited;
  • the CLI;
  • an override turns quorum on for one operation type only;
  • an override turns it off while the default has it on, an unset override field inherits, and --inherit --quorum is refused;
  • a quorum raised by new approvers reopens an approved instance for the missing vote (fails if the stale check uses the fixed threshold);
  • the CLI shows quorum on an override.

The existing migration-list and policy-dict assertions include 009 and quorum.

After merging main (through #27): pytest 262 passed, ruff (pinned 0.16.9) clean, pyright 0 errors.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Mc44WsAZx9hzZdWEihDk5a
https://claude.ai/code/session_01Q8BpCkArA34hB1aSyVcnZp

The predecessor Codex_Kernel gate derived its threshold from membership:
a proposal needed floor(2N/3) + 1 votes from the N authorized nodes, so the
bar grew with the approver set. Repository policy (#12) uses a fixed
approval_threshold, which stays at 1 no matter how many operators exist.

`quorumgit repo policy <repo> --quorum` makes an operation require 2/3 + 1
of the agents eligible to approve it: agents holding an approving role,
minus the requester (unless the policy lets it vote) and a takeover's
beneficiary, and at use time minus the consumer. The fixed threshold
remains a floor. Counting only eligible agents keeps those exclusions from
making approval impossible. Because eligibility is re-derived when an
approval is used, adding approvers raises the bar for unused approvals the
same way raising --threshold does. Migration 005 adds the flag; policy
changes remain operator-only and audited.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Mc44WsAZx9hzZdWEihDk5a
@Deep-Sixed
Deep-Sixed force-pushed the claude/eloquent-galileo-xu0ah7-approvers branch from cd1b083 to 5a1146f Compare September 29, 2026 22:29
@Deep-Sixed Deep-Sixed changed the title Add approver rosters, 2/3+1 quorum, and separation of duties Add a 2/3+1 quorum mode to repository approval policy Sep 29, 2026
@Deep-Sixed
Deep-Sixed changed the base branch from claude/eloquent-galileo-xu0ah7 to main September 29, 2026 22:29
@Deep-Sixed
Deep-Sixed marked this pull request as ready for review September 29, 2026 22:36

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5a1146fe15

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/quorumgit/gate.py Resolved
Excluding the consuming agent from the eligible set at use time could only
lower the requirement, but a still-pending approval is refused before that
check runs, so an approval's status and whether it could be used disagreed
(review feedback). The quorum is now always 2/3 + 1 of the agents eligible
to vote on the approval; whether a given agent may carry it out remains the
separate rule that an approver never uses its own approval.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Mc44WsAZx9hzZdWEihDk5a
#23 added 005_content_governance.sql, so the quorum migration moves to
006_approval_quorum.sql. Conflicts were only in the migration lists (tests
and README) and the repo policy command reference, resolved by keeping both
sides.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Mc44WsAZx9hzZdWEihDk5a
Renumber the quorum migration to 009 and let a per-operation override
set quorum mode as well as threshold, roles and requester voting: a NULL
approval_quorum on an override inherits the repository default. The
repo policy CLI accepts --quorum with --operation and shows quorum on
overrides. Reopening a stale approval (#27) uses the same requirement
as voting and consumption, so a quorum raised by new approvers reopens
it for the missing votes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q8BpCkArA34hB1aSyVcnZp
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q8BpCkArA34hB1aSyVcnZp
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q8BpCkArA34hB1aSyVcnZp
@Deep-Sixed
Deep-Sixed merged commit dbf4da2 into main Sep 30, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants