tools/git has no branch-protection wrapper, so the setting that enforces the trunk-based gate has no owned path #1231

Open
opened 2026-08-15 21:26:48 +00:00 by Ghost · 0 comments

The gap

tools/git/ carries wrappers for issues, pull requests, milestones, reviews, merges, assignment
and the CI queue. It carries none for branch protection.

That matters more than an ordinary missing verb, because branch protection is the setting that
enforces the framework's own trunk-based gate — no direct pushes to the default branch, a required
approving review, stale approvals dismissed on push. It is the mechanical form of a rule the
constitution states in prose, and there is no wrapper to set it.

Why the absence bites rather than merely inconveniencing

wrapper-guard.sh blocks raw provider API writes to endpoints a wrapper owns, and correctly
lets through endpoints no wrapper owns. Branch protection is in the second category today, so the
raw call is allowed — but the moment a wrapper is added and the guard's endpoint map grows an arm
for it, every existing raw caller starts being refused. So the current state is not stable: it is
either a permanent hole in the wrapper-first gate, or a future breakage for anyone who wrote the
raw call in the meantime.

Two further reasons a wrapper earns its place here rather than a documented curl:

  • The write is not the evidence. Gitea accepts unknown fields in the protection object and
    returns HTTP 200, so a mistyped key is a silent no-op. Getting this right requires a mandatory
    read-back that compares every field it just sent — exactly the shape issue-comment.sh already
    implements for comments, and exactly the shape an operator will skip when they are typing curl.
  • One field is a foot-gun. Enabling the status-check requirement with an empty context list
    blocks every merge, because the forge waits for a check that will never report. A wrapper can
    refuse that combination; a curl example in a runbook cannot.

Suggested shape

branch-protect.sh alongside the others:

  • --branch, --required-approvals, --dismiss-stale, --block-on-rejected, --merge-whitelist
  • --login for identity pinning, like issue-comment.sh
  • create-or-update semantics: POST, and on conflict PATCH the existing rule, so re-running is
    idempotent rather than an error
  • a --show mode that prints the current protection, so a pre-change inventory and a post-change
    read-back are the same command
  • mandatory read-back of every field written, failing closed on any mismatch
  • refuse --status-check with no contexts

Then add the endpoint to the guard's map, with the span stated honestly if the wrapper covers less
than the whole endpoint.

## The gap `tools/git/` carries wrappers for issues, pull requests, milestones, reviews, merges, assignment and the CI queue. It carries none for **branch protection**. That matters more than an ordinary missing verb, because branch protection is the setting that enforces the framework's own trunk-based gate — no direct pushes to the default branch, a required approving review, stale approvals dismissed on push. It is the mechanical form of a rule the constitution states in prose, and there is no wrapper to set it. ## Why the absence bites rather than merely inconveniencing `wrapper-guard.sh` blocks raw provider API **writes** to endpoints a wrapper owns, and correctly lets through endpoints no wrapper owns. Branch protection is in the second category today, so the raw call is allowed — but the moment a wrapper is added and the guard's endpoint map grows an arm for it, every existing raw caller starts being refused. So the current state is not stable: it is either a permanent hole in the wrapper-first gate, or a future breakage for anyone who wrote the raw call in the meantime. Two further reasons a wrapper earns its place here rather than a documented curl: - **The write is not the evidence.** Gitea accepts unknown fields in the protection object and returns HTTP 200, so a mistyped key is a silent no-op. Getting this right requires a mandatory read-back that compares every field it just sent — exactly the shape `issue-comment.sh` already implements for comments, and exactly the shape an operator will skip when they are typing curl. - **One field is a foot-gun.** Enabling the status-check requirement with an empty context list blocks every merge, because the forge waits for a check that will never report. A wrapper can refuse that combination; a curl example in a runbook cannot. ## Suggested shape `branch-protect.sh` alongside the others: - `--branch`, `--required-approvals`, `--dismiss-stale`, `--block-on-rejected`, `--merge-whitelist` - `--login` for identity pinning, like `issue-comment.sh` - create-or-update semantics: POST, and on conflict PATCH the existing rule, so re-running is idempotent rather than an error - a `--show` mode that prints the current protection, so a pre-change inventory and a post-change read-back are the same command - mandatory read-back of every field written, failing closed on any mismatch - refuse `--status-check` with no contexts Then add the endpoint to the guard's map, with the span stated honestly if the wrapper covers less than the whole endpoint.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: mosaicstack/stack#1231