Skip to content

feat(break-glass): the escape hatch as a script that REFUSES to exit 0 unless the read-back matches β€” verified end-to-end on real main - #1194

Open
ZacxDev wants to merge 3 commits into
mainfrom
feat/break-glass-merge
Open

feat(break-glass): the escape hatch as a script that REFUSES to exit 0 unless the read-back matches β€” verified end-to-end on real main#1194
ZacxDev wants to merge 3 commits into
mainfrom
feat/break-glass-merge

Conversation

@ZacxDev

@ZacxDev ZacxDev commented Sep 1, 2026

Copy link
Copy Markdown
Member

Rank 6 of the clipboard-research handoff. It was deliberately not built before: shipping an untested command into a break-glass path is the exact failure rank 2 corrects, and it cannot be proven end-to-end without opening the protection window on main. Operator decision this session was to open it.

πŸ”΄ Verified end-to-end against the real main

2026-09-01T01:50Z β€” real DELETE, real PUT, script exit 0.

The script's own read-back is a control that shares the step under doubt, so the verdict comes from a capture taken independently before any of this existed:

INDEPENDENT key-by-key diff (pre-session capture vs now):
  ok    allow_deletions ... allow_force_pushes ... enforce_admins
  ok    required_status_checks ... restrictions        (all 11)
VERDICT: IDENTICAL to the capture taken before any of this ran

Nothing merged through the open window. It was open a few seconds; afterwards #1191 and #1169 were both still open and blocked, and #1192 had merged at 01:47:57Z with both checks SUCCESS β€” over two minutes before the window opened.

What it encodes β€” three measured failures

  • Never PATCH. PATCH .../required_status_checks 404s (Required status checks not enabled) once the sub-resource is deleted; it updates checks that exist and cannot recreate one. Only a full PUT of the whole object closes the window.
  • A PARTIAL PUT returns 200 and silently drops every key it omits β€” enforce_admins, force-push and deletion settings included. So the read-back is not optional, and a mismatch is reported key-by-key.
  • restore_and_verify is ONE function, called on the normal path and from the EXIT trap. A restore failed before inside a trap that fired exactly as designed, because the untested command was in the safety net. That shape is now unrepresentable.

Nothing is redirected to /dev/null: the 2026-08-30 restore only looked silent because >/dev/null 2>&1 discarded the message naming the cause.

Self-test mode (no --pr) opens the window, merges nothing, closes it β€” deliberately the same code path as a real run, only the payload differs. The dangerous half is not a mode that has never executed.

Exit codes

0 ok Β· 2 usage Β· 3 bad capture (nothing touched) Β· 4 open failed (nothing touched) Β· 5 merge failed (window still closed + verified) Β· 6 RESTORE FAILED β€” main may be unprotected Β· 7 read-back mismatch

Coverage

13 tests. The gh stub shells out to real jq for every --jq, and the filter it runs is extracted from the script β€” a stubbed binary whose --jq never runs would leave the capture projection, the thing deciding which keys survive a restore, exercised by nothing.

Mutation sweep is self-checking (control GREEN + positive control KILLED, else SWEEP-INVALID):

mutant outcome
put-becomes-patch KILLED (5)
readback-skipped KILLED (3)
bool-type-loop-removed KILLED (1)
zero-checks-guard-removed KILLED (1)
appid-guard-removed KILLED (1)
restore-failure-swallowed KILLED (1)
positive control delete-removed KILLED (2)

survivors=none.

πŸ”΄ Two guards the sweep found VACUOUS β€” fixed, not annotated

  • The REQUIRED_KEYS has() loop could never fire: the projection names all 11 keys unconditionally, so jq emits null for a missing source key and has() is always true. Replaced with a type check on the 8 booleans, which is the real hazard β€” restoring a null lets GitHub pick its own default.
  • The NULLABLE_KEYS has() loop was vacuous for the same reason and is deleted; that invariant belongs to the filter and is pinned by test_the_capture_projection_runs_under_real_jq.

The fixture that kills the type check is the only case that reaches it β€” every other bad-capture case is also caught by the later zero-checks guard, which is why deleting the loop first left the suite green. That is the "an earlier/later check always wins" trap, caught by isolating the mutation.

Scope

CLAUDE.md gains a pointer to the script beside the four-step recipe (kept β€” the script does not replace knowing what it does). test_break_glass_note.py and test_ci_claim_matches_reality.py stay green; the <!-- merge-gate: other --> marker is untouched.

⚠ It still merges through an open window, so anything with auto-merge armed can land ungated while it runs. The header and the CLAUDE.md note both say so.

πŸ€– Generated with Claude Code

https://claude.ai/code/session_01DXp5UYUoZxPVUS6qejBUf7

ZacxDev and others added 2 commits August 31, 2026 20:52
…0 unless the read-back matches

Rank 6 of the clipboard-research handoff: the deterministic version of the
prose break-glass recipe. It was deliberately not built before, because
shipping an UNTESTED command into a break-glass path is the exact failure
rank 2 corrects, and it cannot be proven end-to-end without opening the
protection window on `main`. Operator decision: open it.

πŸ”΄ VERIFIED END-TO-END AGAINST REAL `main`, 2026-09-01T01:50Z β€” real DELETE,
real PUT, exit 0. All 11 keys then read back IDENTICAL to a capture taken
independently before any of this existed (the script's own read-back would
be a control sharing the step under doubt). The window was open a few
seconds; nothing merged through it β€” #1191 and #1169 were both still open
and blocked afterwards, and #1192 had merged at 01:47:57Z with both checks
SUCCESS, over two minutes before the window opened.

What it encodes, all three from measured failures:
  - never PATCH. `PATCH .../required_status_checks` 404s once the
    sub-resource is deleted; only a full PUT of the whole object closes it.
  - a PARTIAL PUT returns 200 and silently drops every key it omits, so the
    read-back is not optional and the diff is reported key-by-key.
  - `restore_and_verify` is ONE function called on the normal path AND from
    the EXIT trap, so the trap can never hold a command that has not just
    been exercised. A restore failed inside a trap that fired exactly as
    designed, because the untested command was in the safety net.
Nothing is redirected to /dev/null: the 2026-08-30 restore only looked
silent because `>/dev/null 2>&1` discarded the message naming the cause.

Self-test mode (no --pr) opens the window, merges nothing, closes it β€” the
SAME code path as a real run, so the dangerous half is not a mode that has
never executed.

Coverage: 13 tests driving a `gh` stub that shells out to REAL jq for every
--jq, because a stubbed binary whose --jq never runs would leave the capture
projection exercised by nothing. Mutation sweep is self-checking (control
GREEN + positive control KILLED, else SWEEP-INVALID): 6/6 mutants killed,
survivors none.

Two guards were found VACUOUS by that sweep and fixed rather than annotated:
  - the REQUIRED_KEYS `has()` loop could never fire β€” the projection names
    all 11 keys unconditionally, so jq emits `null` for a missing source key
    and has() is always true. Replaced by a TYPE check on the 8 booleans,
    which is the real hazard (a null PUT lets GitHub pick its own default).
  - the NULLABLE_KEYS `has()` loop was vacuous for the same reason and is
    deleted; that invariant belongs to the filter and is pinned there.
The fixture that kills the type check is the one case reaching it β€” every
other bad-capture case is also caught by the later zero-checks guard, which
is why deleting the loop first left the suite green.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DXp5UYUoZxPVUS6qejBUf7
Claude-Session-Id: baaf1e44-2565-400b-884b-6dc557ad1faf
Claude-Session-Id: baaf1e44-2565-400b-884b-6dc557ad1faf
…erged tree

Claude-Session-Id: baaf1e44-2565-400b-884b-6dc557ad1faf
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.

1 participant