Skip to content

Govern pushes by the content Git actually receives - #23

Merged
Deep-Sixed merged 3 commits into
mainfrom
claude/vigilant-darwin-1ebqfk
Sep 29, 2026
Merged

Deep-Sixed merged 3 commits into
mainfrom
claude/vigilant-darwin-1ebqfk

Conversation

@Deep-Sixed

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

Copy link
Copy Markdown
Owner

This brings back the most useful Codex_Kernel idea: governance should be based on what Git actually receives, not on what an agent said it planned to change.

What changes

  • git_objects.py: works out which paths a push changes, using only Git objects. It inspects only commits that are new to the hub (reachable from the pushed revision and from no existing ref). Merges use Git's combined diff, and root commits are compared with the empty tree. So merging main into a feature branch doesn't count main's changes against the pusher. The hook and the offline planner share this code, so they produce the same operation hashes.
  • Claimed scopes are checked when you push. If a claimed branch receives changes to paths outside the claim's scopes, the push needs an out_of_scope_push approval. That approval is tied to the claim ID and to exactly those paths. Branches with no claim get no scope check.
  • Protected paths. New commands repo protect-path and repo add --protected-path. Any push that changes a matching path needs a protected_path_update approval, on any ref.
  • Allowed ref namespaces. Repositories accept pushes to refs/heads/ only. An operator can allow more with repo allow-ref <repo> refs/tags/. Migration 005 applies this to existing repositories too.
  • approve prepare. Plans a push from a clone. It lists the changed paths, the claim, anything that would refuse the push outright, and every approval needed, with the exact hash the hook will ask for. --request opens those approvals and --json prints the plan as JSON.
  • If one push needs several approvals, it's all-or-nothing. The hook lists every missing approval at once and uses none of them unless all are usable.
  • docs/agent-guide.md: how agents should work day to day, plus guidance on keeping secrets out of the permanent audit, handoff and approval records.
  • The approval policy item (roles, threshold, no self-approval) was already on main via migration 004. It is not changed here.

Upgrade impact

  • Tags and custom refs are refused until an operator allows their namespace.
  • A claim with scopes that are too narrow will now trigger out_of_scope_push approvals.

Both are documented in the README.

Testing

  • New tests/test_content_governance.py (26 tests) uses real pushes through the hook. It covers:
    • glob matching
    • changed-path derivation, including merges
    • scope enforcement and approval
    • merging mainline into a claimed branch without being flagged
    • protected paths
    • ref namespaces
    • the prepare hash matching the hook's hash
    • all-or-nothing handling of multiple approvals
    • refusals in prepare
    • regressions for the review fixes: clones missing hub tips, SHA-256 repositories, and non-UTF-8 paths
  • Updated the existing migration-list assertions for 005.
  • pytest on the final head (75f73ee, after merging main with Normalize claim scopes and enforce them at push time #19): 180 passed. ruff check and pyright are clean.

🤖 Generated with Claude Code

https://claude.ai/code/session_0142xh1CDDPxBvB1EHu6PN3m

Recover the Codex_Kernel idea that governance should be derived from the
pushed objects, not from what an agent said it would modify:

- git_objects: derive changed paths from commits new to the repository
  (combined diff for merges, empty tree for roots), shared by the hook and
  the offline planner so both produce identical operation hashes.
- Pre-receive enforces claimed scopes: changes outside the claim's scopes
  need an out_of_scope_push approval bound to the claim and paths.
- Protected paths (repo protect-path / repo add --protected-path) need a
  protected_path_update approval on any ref.
- Allowed ref namespaces: repositories accept refs/heads/ only until an
  operator opts other namespaces in with repo allow-ref.
- approve prepare: plan a push from a clone, listing changed paths and
  every required operation with the hook's exact hash; --request opens them.
- Multiple approvals for one push are all-or-nothing.
- docs/agent-guide.md: agent operating and secret-handling guidance.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0142xh1CDDPxBvB1EHu6PN3m
@Deep-Sixed
Deep-Sixed marked this pull request as ready for review September 29, 2026 22:34

@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: c261e064f0

ℹ️ 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
Comment thread src/quorumgit/git_objects.py Resolved
Comment thread src/quorumgit/git_objects.py Resolved
…rned scopes

main now carries #19, which rejects pushes that touch paths outside the
claim's scopes. This branch makes those pushes approval-governed instead.
Resolution keeps both:

- #19's scope normalization/validation, case-insensitive overlap, and
  path_in_scopes matcher replace this branch's duplicate glob matcher.
- This branch's git_objects path derivation (shared with approve prepare)
  replaces #19's _pushed_paths, so the hook and the planner agree on hashes.
- Out-of-scope pushes are still rejected by default, with #19's wording, but
  can now proceed with an out_of_scope_push approval.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0142xh1CDDPxBvB1EHu6PN3m
- Refuse to plan from a clone that lacks a commit any hub ref points at;
  otherwise commits already governed in the hub look new locally and the
  derived paths and hashes differ from the hook's.
- Derive the null OID from the other revision's length, so planned new
  refs and deletions match the hook in SHA-256 repositories.
- Render non-UTF-8 path bytes as \xNN escapes so operations containing
  them can be hashed and stored instead of crashing the hook.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0142xh1CDDPxBvB1EHu6PN3m
@Deep-Sixed
Deep-Sixed merged commit 1553327 into main Sep 29, 2026
7 checks passed
Deep-Sixed pushed a commit that referenced this pull request Sep 29, 2026
#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
Deep-Sixed pushed a commit that referenced this pull request Sep 29, 2026
…e ref-transaction binding

main's content governance (#23) can require several approvals for one
ref update, where this branch recorded one operation and one approval.
Resolved by keeping main's derivation (governed_operations over
git_objects.changed_paths) and binding all of it to the transaction:

- pre-receive derives the update's paths from the received objects,
  records them with the update, and consumes nothing.
- The reference-transaction `prepared` stage re-derives the operations
  from those recorded paths against current governance state and
  consumes one approval per operation. Recording the paths matters in a
  non-atomic multi-ref push: by `prepared`, earlier refs have moved,
  which would change which commits look new and so the hashes.
- Every consumed approval is linked in ref_update_approvals, so an abort
  (or `doctor --repair` of an aborted update) restores all of them.

This branch's own scope enforcement and path helpers are dropped in
favour of main's (#19, #23). The ref_updates migration is renumbered
from 003 to 006: main already ships 004 and 005, and a gap-filling 003
would apply after them on existing stores but before them on new ones.
The README now requires Git 2.28 (for the reference-transaction hook)
instead of #18's 2.17.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014R4wfmENrMnt36KN1Kj6Fx
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