2026-08-11-branch-protection
2026-08-11 · project workspace · path prompts/2026-08-11-branch-protection
Prompt prompts/2026-08-11-branch-protection/PROMPT.md
BRANCH-PROTECTION ROUND — payload audit + flip runbook
*Dispatched 2026-08-11. Verbatim as sent, per prompts/README.md.*
Prompt author: Claude Fable 5 (Anthropic), 2026-08-11, in a maintainer-directed session. Dispatched by the maintainer.
Extends the governance architecture; AGENTS.md rules apply. This round prepares and (where possible) applies branch protection. The visibility flip itself remains reserved to the maintainer — never change visibility.
A. Audit the committed payload against the decided semantics
Verify, and correct where wrong (each correction is a normal commit with reasons):
- Required approvals = 0. This is deliberate and load-bearing: GitHub forbids self-approval, so "require 1 approval" would mechanically reinstate a two-human gate on every maintainer PR — the exact thing the constitution decided against (DECISIONS, 2026-08-11). Enforcement lives in required checks, not required reviews. If the payload says 1 (or more), set it to 0 and note why in the commit.
- Required status checks: exactly the eight gate names, matching the CI job names string-for-string (path-gate, checkers, conservativity, python, frozen-integrity, foundations-verification, lean, dco — use the true job-name strings from ci.yml, not this prose).
- Force-pushes blocked and branch deletion blocked on main — this is what makes git history immutable in fact rather than by convention, and the frozen discipline presumes it.
- Enforce for administrators: ON. Recorded understanding (put it in the DECISIONS entry): the repo owner can always disable protection in settings, so this is a latch, not a lock — it does not stop deliberate bypass; it converts accidental or lazy bypass into a visible, deliberate settings change. That is the intended amount of self-binding.
- PRs required for all changes to main (no direct pushes).
B. Check names become spec
Add to AGENTS.md (spec layer): CI job names are spec-layer values. Required status checks match job names by exact string, so renaming a CI job silently breaks enforcement in one of two directions (blocks everything, or stops requiring the real gate). Any job rename must update the protection payload in the same PR, and the payload file is the source of truth for the required-check list.
C. Apply now if the plan allows; otherwise stage
Attempt to apply the audited payload to main via the API while the repo is private. If GitHub rejects it (branch protection on private repos requires a paid plan), do not treat this as failure: commit the audited payload plus a one-command application script (gh api, no third-party dependencies), and write FLIP_RUNBOOK.md at repo root containing the flip-minute procedure below. If it applies while private, the runbook shrinks accordingly (protection is already live; the flip is just the flip) — write it anyway.
D. FLIP_RUNBOOK.md — the flip minute, exactly
For the maintainer to execute, in order, at a moment they are at the keyboard:
- Flip visibility to public (Settings → General → Danger Zone, or gh api).
- Immediately run the application script; confirm via API that main reports: required checks = the eight names, approvals 0, force-push and deletion blocked, admins included.
- Only then link, announce, or reference the repo anywhere. Exposure begins at attention, not at the visibility bit; the unprotected-public window binds only accounts with write access (the two maintainers), so executed in one sitting its risk is a maintainer's accidental direct push during that minute and nothing else.
- DECISIONS.md entry: went public, protection applied, date.
Note in the runbook what the flip does NOT discharge, so it can't be executed by momentum: the maintainer's read-through sign-off of the note-dump conversations must already be in DECISIONS.md before step 1 (release gate), and the checker harness remains llm-unreviewed until the maintainer's pass — public flip does not change its provenance class.
Report
ROUND_REPORT.md per convention: payload deltas found in the audit (list each, before/after), whether protection applied while private or was staged, the exact eight check-name strings as committed, and confirmation that the runbook's preconditions section names the release gate.
Report prompts/2026-08-11-branch-protection/REPORT.md
Round report — branch-protection audit and flip runbook
Attribution. Prompt author: Claude Fable 5 (Anthropic), 2026-08-11, in a maintainer-directed session. Executor: Claude Opus 5 (Anthropic), 2026-08-11.
Audit deltas
Four corrections. Three were the brief's; one was not, and it defeats the same decision by a different route.
| field | before | after | why |
|---|---|---|---|
required_approving_review_count | 1 | **0** | GitHub forbids self-approval, so requiring one approval mechanically reinstates a two-human gate on every maintainer pull request — the thing decided against on 2026-08-11. Enforcement lives in the required checks. |
require_code_owner_reviews | true | **false** | Not in the brief. With both maintainers listed as code owners, a code-owner requirement reinstates exactly the same two-human gate. Setting approvals to zero while leaving this true would have looked correct and behaved wrongly. |
dismiss_stale_reviews | true | **false** | Dead configuration at zero approvals. Removed so the file states only what it means; dead config in a spec-layer file invites misreading. |
enforce_admins | false | **true** | The latch, per below. |
Already correct, unchanged: allow_force_pushes: false, allow_deletions: false, strict: true, restrictions: null, and pull requests required for all changes to main.
Required status checks: verified by string comparison, not by eye. A script parsed the name: values out of ci.yml, sorted both lists, and compared: 8 job names, 8 contexts, exact match including the em-dashes.
checkers — house harness self-test and the claims registries
conservativity — no new axioms, specification shape unchanged
dco — every commit carries a sign-off
foundations-verification — the frozen consolidation re-proves itself
frozen-integrity — digests and the manifest rule
lean — build, sorry-free, axiom audit
path-gate — proof-layer PRs may not touch the specification layer
python — project test runners
Applied, or staged?
Staged. Application was attempted against the live repository and refused:
HTTP 403 — Upgrade to GitHub Pro or make this repository public
to enable this feature.
Per the brief this is not a failure. Committed instead: .github/apply-branch-protection.sh — one command, gh only, no third-party dependencies — which applies the payload, reads back what GitHub actually stored, verifies six properties, and exits non-zero unless all hold. Verifying by read-back rather than by trusting the write is the point: a payload that was accepted is not the same as a protection that is correct.
Check names are now spec
Added to the trust chain in AGENTS.md as item 6: required checks match job names by exact string, so a rename breaks enforcement silently in one of two directions — the branch demands a check that no longer reports and blocks everything, or it stops requiring a gate that still runs and nothing announces it. The second is the dangerous one, because everything looks green. Any job rename updates the payload in the same pull request, and the payload file is the source of truth for the list.
The runbook
FLIP_RUNBOOK.md at repository root. Its preconditions section comes first, before step 1, and names the release gate: the maintainer's read-through sign-off of the note-dump conversations must already be recorded in DECISIONS.md, and only the maintainer can discharge it. Two further preconditions are named — the checker harness remains llm-unreviewed and a public flip does not change that, and the one unverified citation.
The flip minute is four steps: flip, apply-and-verify, then announce, then record. Step 3 carries the reasoning rather than just the instruction — exposure begins at attention, not at the visibility bit, and the unprotected-public window can only be written to by accounts with push access, which is the two maintainers. So its real risk is a maintainer's own accidental direct push during that minute. In one sitting that is close to nil; left open overnight it is a habit forming.
What this round does not establish
Protection is not live. Nothing about main changed: direct pushes still work, and all eight gates remain advisory until the payload is applied after the flip. This round moved the work from "unavailable" to "one command, verified on application" — it did not enforce anything.
And the honest limit of what will be enforced, recorded in the ledger rather than implied away: enforce_admins is a latch, not a lock. The owner can disable protection in settings at any time. It does not stop deliberate bypass; it converts accidental or lazy bypass into a visible, deliberate act. Against someone holding admin rights that is the most a self-binding rule can honestly claim.
Claims it registered
None.
Items it filed or answered
None recorded in the index.