Skip to content

feat(policy): host baseline push policy judged with every repository's own - #98

Open
jonasgrosch wants to merge 1 commit into
tobi:mainfrom
conxai-technologies:conxai/default-policy
Open

jonasgrosch wants to merge 1 commit into
tobi:mainfrom
conxai-technologies:conxai/default-policy

Conversation

@jonasgrosch

Copy link
Copy Markdown

A repository has no policy.json until an admin writes one, so a new repository is allow-all until then — and a per-repo write races the first push. Deployments that want every repository to protect a ref from the first push had no way to say so.

What

[policy] baseline = "<path>" names one more document in the policy.json language (docs/POLICY.md, "Host baseline"), read once at startup with the same parser and validator; unreadable or invalid stops the process (and fails walgit config check). Held in memory: 0 extra store requests on a push.

Combination law: both apply

A push is judged against the baseline, then the repository's own policy.json, and must pass both — protect's AND carried across documents. Order only decides which rule a rejection names (rejected by baseline rule '<name>' first). Each document resolves group: against its own roster.

Why not "own replaces baseline": repositories get a policy.json for local reasons; under replacement that write — or a PUT of an empty document — would silently drop the host's protection. The language has no admitting effect, so a repository can add restrictions and never lift one; exceptions are the operator's.

The overlapping-bypass lockout check stays per document (across documents it would refuse most repository rules under a baseline over refs/heads/**). POST …/policy/dry-run judges after the baseline exactly like receive-pack, so a cross-document lockout is visible before it bites.

API

GET …/policy unchanged (the Settings editor round-trips it through PUT); new GET …/policy/effective → {layers:[{source:"baseline"|"repository", policy}]}; dry-run evaluates after the baseline.

Notes

Host-only config; every host that runs receive-pack must carry the same file. docs/POLICY.md carries a worked baseline (members in draft/**/explore/**, production for one deploy principal, the rest of refs/heads + refs/tags for automation); a test pins it verbatim. Follow-ups: the Settings page's "(empty = allow-all)" hint and an SDK repo.policy.effective().

Tests

Unit: composition (baseline alone, own adds never lifts, empty own document, rule naming, roster isolation), the documented example's full principal × ref × op matrix, every policy.json refusal also refuses the baseline. Integration: receive-pack with and without an own policy.json, …/policy vs …/policy/effective, dry-run with/without a baseline, startup refusing a missing/unparsable/lockout baseline. Config: key parses, empty path and unknown keys refused, repo settings can't set [policy].

Part of #94. Each of these PRs claims the next decision number in AGENTS.md (D50/D5x); renumber on merge as you prefer.

🤖 Generated with Claude Code

…s own

A repository has no policy.json until an admin writes one, so a new repository
is allow-all until then, and a per-repo write races the first push.

`[policy] baseline = "<path>"` names one more document in the policy.json
language. It is read at startup with the same parser and validator; an
unreadable or invalid file stops the process and fails `walgit config check`.
It is held in memory.

Combination: both apply. Receive-pack judges a push against the baseline, then
against the repository's own policy.json, and the push must pass both. This is
protect's AND carried across documents, so order only decides which rule the ng
line names (`rejected by baseline rule '<name>'` comes first). Each document
resolves `group:` against its own roster. The language has no admitting effect,
so a repository can add restrictions and never lift one: neither a policy.json
written for a local reason nor a PUT of an empty document can drop the host's
protection. The settings dry-run judges after the baseline the same way.

GET …/policy still returns the repository's own document, which the editor
round-trips through PUT. The new GET …/policy/effective lists the layers in
evaluation order, `{layers: [{source: baseline|repository, policy}]}`.

Round trips: unchanged. Receive-pack still does one policy.json GET (a 404 costs
nothing) and the baseline adds 0 store requests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
nibzard added a commit to nibzard/walgit that referenced this pull request Oct 14, 2026
… every repository's own

Co-Authored-By: Claude Code <noreply@anthropic.com>

This branch has not been deployed

No deployments
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