From 82c4c3061fddd14c1744443e943dcafdbecf9541 Mon Sep 17 00:00:00 2001 From: lab Date: Mon, 7 Sep 2026 00:58:58 +0700 Subject: [PATCH] ci(now-gate): the frozen archive is declared, not detected (Refs #3368) `docs/NOW.md` carries "FROZEN ARCHIVE -- do not add entries here." on its first line, and nothing enforced it. Measured: every mention of that path under `.github/workflows/`, `scripts/` and `.githooks/` is a comment -- zero lines reject an edit -- so an author following one of the stale instructions that pointed there could reopen the archive and pass all four required checks. BOTH detection rules were measured, and both fail on the same commit. "Refuse any diff touching it" blocks `458ec0bd6`, which REPAIRS destroyed entries: of the 600 commits since the freeze, 2 touch the file and that is one of them. So does the narrower "refuse an ADDED `## ` heading" I proposed as the replacement -- that repair adds three, because it restored headings whose bodies had been destroyed. My recommendation was wrong in the same direction twice, and the second time I had not run the replacement past the exception that killed the first. No textual rule separates adding an entry from repairing one: the only difference is POSITION, which is the coupling the one-file-per-entry layout exists to remove. So the exception is DECLARED, where the tool looks -- the shape `# tri:no-dispatch` and `# tri:cause-removed` already use here. A commit that edits the archive on purpose says so: Archive-Repair: The gate does not judge the reason. It requires one to exist, so reopening a frozen file is a decision somebody signed rather than an accident. Six controls, run against a scratch repository rather than reasoned about: PR and push, each with and without the trailer; a trailer carrying no reason (refused); and a range that does not touch the file (unchanged). Historical control: `458ec0bd6` carries no trailer and would be refused -- correctly; the point is not to permit it retroactively. My own probe was the broken instrument first: `git reset --hard` removed `docs/now/`, the entry file could not be written, and two cases failed on the presence check instead of on the guard. Fixed the harness, not the reading. R12 states the rule; LOOP-RULES.sha256 moves with it in this commit. Co-Authored-By: Claude Opus 5 --- docs/loop/LOOP-RULES.md | 10 ++++ docs/loop/LOOP-RULES.sha256 | 2 +- ...rozen-file-that-nothing-refused-to-open.md | 8 +++ scripts/ci/now-sync-gate-diff.sh | 58 +++++++++++++++++++ 4 files changed, 77 insertions(+), 1 deletion(-) create mode 100644 docs/now/2026-09-07-a-frozen-file-that-nothing-refused-to-open.md diff --git a/docs/loop/LOOP-RULES.md b/docs/loop/LOOP-RULES.md index 105a6e5f7d..061121e903 100644 --- a/docs/loop/LOOP-RULES.md +++ b/docs/loop/LOOP-RULES.md @@ -232,6 +232,16 @@ in `f5be7dc1c` (#2298) precisely because one file per PR is what stops every concurrent PR colliding on its first line, and this rule went on naming it for sixteen days. +`docs/NOW.md` is now ENFORCED, not merely labelled: a range that edits it is +refused unless a commit in it carries + + Archive-Repair: + +Repairing the archive is legitimate -- one of the two post-freeze edits is a +repair of destroyed bodies -- and no textual rule separates a repair from a new +entry, since the repair adds headings too. So the exception is DECLARED rather +than detected. The gate does not judge the reason; it requires one to exist. + Reference the issue in the PR body. `Refs #N` DOES satisfy `check-linked-issue`: the matcher is `(Closes?|Fixes?|Resolves?|Refs?|Updates?)\s*#[0-9]+` at `.github/workflows/issue-gate.yml:69`, and seven readers in this tree carry that diff --git a/docs/loop/LOOP-RULES.sha256 b/docs/loop/LOOP-RULES.sha256 index 34798256a0..14f7a65167 100644 --- a/docs/loop/LOOP-RULES.sha256 +++ b/docs/loop/LOOP-RULES.sha256 @@ -1 +1 @@ -f4c3c52eb779142edd011d3ce39faf71f12a4a69a677ecb5b42ca8b8e7b6fbd9 docs/loop/LOOP-RULES.md +6d68cc1592b9078f888081b8d8cb8bc0f4ea1b9ce1705d38adbff8e130c6a466 docs/loop/LOOP-RULES.md diff --git a/docs/now/2026-09-07-a-frozen-file-that-nothing-refused-to-open.md b/docs/now/2026-09-07-a-frozen-file-that-nothing-refused-to-open.md new file mode 100644 index 0000000000..42016c54d5 --- /dev/null +++ b/docs/now/2026-09-07-a-frozen-file-that-nothing-refused-to-open.md @@ -0,0 +1,8 @@ +# NOW -- A frozen file that nothing refused to open (2026-09-07) + +## A frozen file that nothing refused to open (Closes #3368) + +- `docs/NOW.md` says FROZEN ARCHIVE on line 1, and every mention of it under `.github/workflows/`, `scripts/` and `.githooks/` was a comment -- measured, zero lines rejected an edit. An author following a stale instruction could reopen it and pass all four required checks. +- Both detection rules were measured and BOTH fail: 'refuse any diff touching it' blocks the one legitimate repair in the population, and so does 'refuse an added `## ` heading' -- that repair adds three, because it restored headings whose bodies were destroyed. +- So the exception is DECLARED rather than detected: `Archive-Repair: ` in the commit message, the same shape as `# tri:no-dispatch` and `# tri:cause-removed`. The gate does not judge the reason; it requires one to exist. +- Six controls on a scratch repository -- PR and push, with and without the trailer, a bare trailer, and an untouched file. Historical control: `458ec0bd6` carries no trailer and would be refused, correctly. Blast radius: 2 commits in 600. diff --git a/scripts/ci/now-sync-gate-diff.sh b/scripts/ci/now-sync-gate-diff.sh index 675c4c38b4..30d06c1a76 100755 --- a/scripts/ci/now-sync-gate-diff.sh +++ b/scripts/ci/now-sync-gate-diff.sh @@ -79,21 +79,79 @@ if [ "$event" = "pull_request" ]; then require_rev "$BASE" PR_BASE_SHA require_rev "$HEAD" PR_HEAD_SHA ADDED=$(git diff --diff-filter=A --name-only "$BASE" "$HEAD" | grep -E "$ENTRY_RE" || true) + RANGE_FROM="$BASE"; RANGE_TO="$HEAD" elif [ "$event" = "push" ]; then BEFORE="${PUSH_BEFORE:?}" AFTER="${PUSH_AFTER:?}" require_rev "$AFTER" PUSH_AFTER if [ "$BEFORE" = "0000000000000000000000000000000000000000" ]; then ADDED=$(git show --diff-filter=A --name-only --pretty=format: "$AFTER" | grep -E "$ENTRY_RE" || true) + RANGE_FROM=""; RANGE_TO="$AFTER" else require_rev "$BEFORE" PUSH_BEFORE ADDED=$(git diff --diff-filter=A --name-only "$BEFORE" "$AFTER" | grep -E "$ENTRY_RE" || true) + RANGE_FROM="$BEFORE"; RANGE_TO="$AFTER" fi else echo "::error::now-sync-gate-diff.sh: unsupported GITHUB_EVENT_NAME=$event" exit 1 fi +# --- THE FROZEN ARCHIVE --------------------------------------------------- +# docs/NOW.md carries "FROZEN ARCHIVE -- do not add entries here." on its first +# line, and until now NOTHING enforced it: every mention of that path under +# .github/workflows/, scripts/ and .githooks/ is a comment -- measured, zero +# lines reject an edit. An author following one of the stale instructions that +# still pointed there could reopen the archive and pass all four required checks. +# +# A blanket "refuse any diff touching it" is WRONG, and that was measured too: +# of the 600 commits since the freeze, 2 touched the file and one (458ec0bd6) +# REPAIRS damaged entries. So is the narrower "refuse an ADDED `## ` heading" -- +# that same repair adds three of them, because it restored headings whose bodies +# had been destroyed. No textual rule separates adding an entry from repairing +# one: the only difference is POSITION, which is the coupling the one-file-per- +# entry layout exists to remove. +# +# So the exception is DECLARED, where the tool looks, the way `# tri:no-dispatch` +# and `# tri:cause-removed` are. A commit that edits the archive on purpose says +# so in its own message: +# +# Archive-Repair: +# +# The gate does not judge the reason. It requires one to exist, so that reopening +# a frozen file is a decision somebody signed rather than an accident. +if [ -n "$RANGE_TO" ]; then + if [ -n "$RANGE_FROM" ]; then + TOUCHED=$(git diff --name-only "$RANGE_FROM" "$RANGE_TO" -- docs/NOW.md || true) + MSGS=$(git log --format=%B "$RANGE_FROM".."$RANGE_TO" || true) + else + TOUCHED=$(git show --name-only --pretty=format: "$RANGE_TO" -- docs/NOW.md || true) + MSGS=$(git log -1 --format=%B "$RANGE_TO" || true) + fi + if [ -n "$TOUCHED" ]; then + if printf '%s\n' "$MSGS" | grep -qE '^Archive-Repair:[[:space:]]*[^[:space:]]'; then + echo "docs/NOW.md edited under an Archive-Repair trailer -- allowed." + else + echo "::error::docs/NOW.md is a FROZEN ARCHIVE and this range edits it." + echo "" + echo "Its first line says so. Entries go in one file each:" + echo " docs/now/-.md" + echo "" + echo "If you are REPAIRING the archive rather than adding to it -- restoring a" + echo "body the tooling destroyed, say -- that is allowed, and it has to be said" + echo "out loud. Put a trailer in the commit message:" + echo "" + echo " Archive-Repair: " + echo "" + echo "The gate does not judge the reason; it requires one to exist. A blanket" + echo "refusal was measured and rejected: 1 of the 2 post-freeze edits is a" + echo "legitimate repair, and it adds headings, so no textual rule tells the two" + echo "apart." + exit 1 + fi + fi +fi + if [ -z "$ADDED" ]; then echo "::error::SYNC REQUIRED: this PR/push adds no docs/now/ entry." echo ""