Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 14 additions & 16 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -330,30 +330,28 @@ git push origin v9.9.0 # moves a published release tag

The glob was never what made those safe — it was a blunt instrument
compensating for having no guard at the only layer that can see a *ref update*
rather than a command string. That layer now exists. Four **GitHub rulesets**,
all `enforcement=active` with an **empty `bypass_actors` list**, refuse three
of the four lines above — see the gap called out immediately after the table:
rather than a command string. That layer now exists. Five **GitHub rulesets**,
all `enforcement=active` with an **empty `bypass_actors` list**. They refuse
every *rewrite* above; the one thing they still permit is deleting a spent
`release-*` branch, which is explained under the table:

| Repo | Ruleset | Applies to | Rules |
|---|---|---|---|
| `bess-manager` | Protect Main Branch | `~DEFAULT_BRANCH` | deletion, non_fast_forward, pull_request |
| `bess-manager` | Protect beta release branches | `beta-release-*` | deletion, non_fast_forward |
| `bess-manager` | Protect release tags | `~ALL` tags | deletion, non_fast_forward |
| `bess-manager` | Protect stable hotfix branches | `release-*` | non_fast_forward |
| `bess-manager-beta` | Protect beta main (fast-forward only) | `~DEFAULT_BRANCH` | deletion, non_fast_forward |

⚠️ **`release-X.Y` is not in that table, and `--delete release-X.Y` is
therefore unguarded at BOTH layers.** That is the one line of the four above
which nothing currently refuses. It is the short-lived stable hotfix branch the
`release` skill creates (steps 2–6), and it *is* pushed and tagged, so it is a
shared ref by the standard used everywhere else here. The protected tag
preserves the released commit, which makes the branch recoverable after
tagging but not before.

Left open knowingly rather than by oversight — closing it means adding a
`release-*` ruleset (`non_fast_forward`, and deliberately **not** `deletion`,
since the branch is meant to be cleaned up after the release). Do **not** close
it by restoring a `Bash(git push*)` ask: that guards every push to fix one
branch pattern, and `quality-check.sh` fails on it.
**`release-*` carries `non_fast_forward` and deliberately NOT `deletion`**,
which is the one asymmetry in the table and the one line of the four above that
is still permitted. It is the short-lived stable hotfix branch the `release`
skill creates (steps 2–6): it is pushed and tagged, so *rewriting* it must
fail, while deleting it once the release is out is ordinary cleanup and must
not. What makes dropping the deletion guard safe is the tag ruleset one row up
— the published tag pins the released commit, so a deleted `release-*` branch
costs nothing after tagging. Before tagging it is still recoverable only from
a local reflog, so delete it after, not during.

The empty bypass list is the load-bearing part: **local pushes authenticate as
the repo owner**, not as `bess-agent` (the credential helper is osxkeychain and
Expand Down
27 changes: 14 additions & 13 deletions scripts/quality-check.sh
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,7 @@ import json, re, sys
#
# git push origin main --force -> non_fast_forward on ~DEFAULT_BRANCH
# git push origin +beta-release-9.9 -> non_fast_forward on beta-release-*
# git push origin +release-9.9 -> non_fast_forward on release-*
# git push origin --delete <ref> -> deletion on both of the above
# git push origin v9.9.0 (force/move) -> non_fast_forward on ~ALL tags
#
Expand All @@ -201,11 +202,11 @@ import json, re, sys
# gh api repos/johanzander/bess-manager-beta/rulesets
#
# What this deliberately does NOT cover: force-pushing or deleting a FEATURE
# branch (fix/**, feat/**) on origin, nor `release-X.Y` (see MUST_NOT_BE_GUARDED
# below). Accepted residuals -- but NOT because "the damage is bounded to your
# own unmerged branch". ~20 worktrees push in parallel as the same identity, so
# a misaimed --force destroys another agent's commits and closes its PR, with
# the recovering reflog sitting in a different worktree.
# branch (fix/**, feat/**) on origin. An accepted residual -- but NOT because
# "the damage is bounded to your own unmerged branch". ~20 worktrees push in
# parallel as the same identity, so a misaimed --force destroys another agent's
# commits and closes its PR, with the recovering reflog sitting in a different
# worktree.
#
# Every entry below is a rule whose deletion is the exact regression this gate
# was written for -- the GitHub-reaching and history-destroying guards. Keep
Expand Down Expand Up @@ -339,15 +340,15 @@ MUST_NOT_BE_GUARDED = [
# "all of them" -- do not read this list as a protection matrix:
#
# main --force, +beta-release-*, v9.9.0 (tag) -> refused by a ruleset
# --delete release-X.Y -> NOT refused; no ruleset
# covers `release-*`
# +release-X.Y (rewrite) -> refused by a ruleset
# --delete release-X.Y -> ALLOWED, deliberately
#
# That last line is a real residual, not an oversight to "fix" by putting
# the prompt back: `release-X.Y` is the short-lived hotfix branch created
# by the release skill (steps 2-6), it is pushed and tagged, and nothing
# currently protects it at either layer. The protected TAG preserves the
# released commit, so the branch is recoverable after tagging but not
# before. Closing it means adding a `release-*` ruleset, not an ask rule.
# That last line is a deliberate asymmetry, not a residual and not an
# oversight to "fix" by putting the prompt back. `release-X.Y` is the
# short-lived hotfix branch created by the release skill (steps 2-6);
# rewriting it is refused by the `release-*` ruleset, while deleting it
# once the release is out is ordinary cleanup. The protected TAG pins the
# released commit, so a spent branch costs nothing to lose.
"git push", "git push -u origin main",
"git push origin main --force",
"git push origin +beta-release-9.9",
Expand Down
Loading