gates: the two required contexts name their own subject, not the wrong one - #3062
Conversation
…g one Closes #3061 Both are in docs/BRANCH-PROTECTION.md's required list, so each wrong message lands on a check nobody can merge past. NOW Sync Gate. `${PR_BASE_SHA:?}` catches unset and empty, not a non-empty SHA naming an object this clone does not have -- a shallow fetch, a force-push that orphaned the base, a rerun after the branch moved. `git diff` exits 128, and the `|| true` that exists to absorb grep's no-match absorbs that identically, so the gate printed "SYNC REQUIRED: this PR/push adds no docs/now/ entry" about a comparison it never made. Reproduced with a bad base SHA. It now verifies both revisions with `git cat-file -e <sha>^{commit}` and exits 2 when one is absent, naming the variable and the value; resolvable revisions produce exactly the verdict they did before. The push arm is fixed the same way. Issue Gate. workflow_dispatch carries no `pull_request` object, so PR_TITLE and PR_BODY render as empty strings, grep matches nothing, and the step printed "L1 TRACEABILITY violation: No issue reference found in PR title/body" against a pull request that does not exist. Not hypothetical: tools/check_now_entry_shape.py:185 already records the dispatch run that produced it. It now reads PR_NUMBER and, when there is none, says it examined nothing and exits 0 -- POSIX 1003.3 UNTESTED, no subject, which is different from an instrument that failed, and the message says it is not a pass for any PR. Found by a read-only fan-out over workflows, hooks, python gates and the Rust --gate commands: 41 candidates, 15 confirmed, 9 refuted, 0 agent deaths. These two are the only ones marked blocks-a-merge; the other thirteen are in #3061's closing note. Four mutants, four kills, and two are caught only by the controls: refusing every revision kills the resolvable-pair control, and taking the no-PR branch unconditionally kills both real-PR controls. The test extracts the Issue Gate's `run:` body from the workflow rather than restating it, so editing the YAML and not the test is caught -- and it asserts the extraction matched, because an extractor that stops matching would assert nothing at all. The control for the NOW gate compares HEAD against itself rather than HEAD~1: it needs no history, so it survives a fetch-depth 1 checkout instead of silently skipping there. A control that vanishes with the runner's clone depth is not one.
|
📓 NotebookLM Notebook linked to this PR
This notebook contains session context, decisions, and artifacts for this work. |
|
📓 NotebookLM Notebook linked to this PR
This notebook contains session context, decisions, and artifacts for this work. |
PR DashboardGenerated at: 2026-09-03 22:00:32 UTC
Summary
Seal Status
|
PR DashboardGenerated at: 2026-09-03 22:10:02 UTC
Summary
Seal Status
|
|
📓 NotebookLM Notebook linked to this PR
This notebook contains session context, decisions, and artifacts for this work. |
PR DashboardGenerated at: 2026-09-03 22:20:47 UTC
Summary
Seal Status
|
|
📓 NotebookLM Notebook linked to this PR
This notebook contains session context, decisions, and artifacts for this work. |
|
📓 NotebookLM Notebook linked to this PR
This notebook contains session context, decisions, and artifacts for this work. |
PR DashboardGenerated at: 2026-09-03 22:34:38 UTC
Summary
Seal Status
|
Both sides append a step at the same anchor in untrusted-input-gate.yml, so the conflict is structural rather than semantic and both steps are always right. This is the fourth time; the cheap fix is an ordering rule for the step list, noted rather than done here.
…b.com/gHashTag/t27 into w54-required-gates-name-their-subject
PR DashboardGenerated at: 2026-09-03 23:06:10 UTC
Summary
Seal Status
|
|
📓 NotebookLM Notebook linked to this PR
This notebook contains session context, decisions, and artifacts for this work. |
This branch owns no skill section; master's copy carried the three conflict markers from #3072, so the repaired copy is used rather than propagating them.
|
📓 NotebookLM Notebook linked to this PR
This notebook contains session context, decisions, and artifacts for this work. |
PR DashboardGenerated at: 2026-09-03 23:15:14 UTC
Summary
Seal Status
|
|
📓 NotebookLM Notebook linked to this PR
This notebook contains session context, decisions, and artifacts for this work. |
PR DashboardGenerated at: 2026-09-03 23:30:26 UTC
Summary
Seal Status
|
|
📓 NotebookLM Notebook linked to this PR
This notebook contains session context, decisions, and artifacts for this work. |
PR DashboardGenerated at: 2026-09-03 23:55:59 UTC
Summary
Seal Status
|
…b.com/gHashTag/t27 into w54-required-gates-name-their-subject
PR DashboardGenerated at: 2026-09-03 23:59:22 UTC
Summary
Seal Status
|
|
📓 NotebookLM Notebook linked to this PR
This notebook contains session context, decisions, and artifacts for this work. |
Closes #3061
Two of the five required contexts in
docs/BRANCH-PROTECTION.mdprinted a verdict about something they never examined. A wrong message on a required check is the worst place for one: it is the check a contributor cannot merge past, and it sends them to fix the wrong thing.1. NOW Sync Gate — a revision it cannot resolve read as a missing entry
${...:?}catches unset and empty, not a non-empty SHA naming an object this clone does not have — a shallow fetch, a force-push that orphaned the base, a rerun after the branch moved.git diffexits 128, and the|| truethat exists to absorb grep's no-match absorbs that identically.After: both revisions are verified with
git cat-file -e <sha>^{commit}; an absent one exits 2 and names the variable and the value. Resolvable revisions produce exactly the verdict they did before. Thepusharm is fixed the same way.2. Issue Gate — no pull request read as an unreferenced pull request
workflow_dispatchcarries nopull_requestobject, soPR_TITLEandPR_BODYrender as empty strings, grep matches nothing, and the step printedagainst a pull request that does not exist.
IS_BOTdoes not rescue it — a human dispatcher makes every disjunct false. Not hypothetical:tools/check_now_entry_shape.py:185already records the dispatch run that produced it.After: the step reads
PR_NUMBER, and with none it says it examined nothing and exits 0. POSIX 1003.3 calls that UNTESTED — no subject, which is different from an instrument that failed — and the message says it is not a pass for any PR.Evidence
PR_BASE_SHAand the value, no "SYNC REQUIRED"pushwith an absentPUSH_AFTERpull_requestCloses #7✅ Issue gate passedThe test extracts the Issue Gate's
run:body from the workflow rather than restating it, so editing the YAML and not the test is caught — and it asserts the extraction matched, because an extractor that stops matching would assert nothing at all.The NOW gate's control compares HEAD against itself rather than
HEAD~1: it needs no history, so it survives afetch-depth: 1checkout instead of silently skipping there. A control that vanishes with the runner's clone depth is not one.The test runs in
untrusted-input-gate.ymlrather than in either gate's own workflow, so a break in the test cannot turn a required context red for a reason unrelated to the change under test.Where these came from
A read-only fan-out over workflows, hooks, python gates and the Rust
--gatecommands: 41 candidates, 15 confirmed, 9 refuted, 0 agent deaths. These two are the only ones marked blocks-a-merge.Among the thirteen left is a fourth quiet shape that
tri gates quiet(#3049) does not classify —if grep -n PATTERN A B 2>/dev/null; then … else echo OK, where grep's exit 2 (cannot open) merges with exit 1 (no match) and theelseprints success.coq-kernel.yml:121is its only instance in 49 workflow files, and the reproduction ran GNU grep 3.11 with one operand absent and gotOK: no Admitted, exit 0. Listed in #3061 rather than fixed here.