fix(dated-examples-check): bound the provenance window by the section, and widen the vocabulary #1433

Merged
bosun merged 1 commit from i/1421-provenance-window-and-vocabulary into main 2026-09-07 19:02:44 +02:00
Owner

Closes #1421.

Intended-targets: #1421

dated-examples-check.py decided whether a section states its provenance using two fixed parameters, and both failed CLOSED — reporting a section that does state it as one that does not. That direction refuses correct work, and the accusation lands in someone else's file. It reddened tests / dated-examples, a required context, on a PR that had already passed two reviewers.

measured on #1419   heading 359 · occurrence 376 · distance 17 · window 8
                    phrasing "deleted in #607" — matched none of the three phrases

What changed

THE SPAN        the SECTION, bounded by the next heading — markdown's own structure
                rather than a line count nobody chose (crew-doctrine#180)
THE VOCABULARY  a SHAPE, not a phrase list: a retirement verb plus a citation,
                anywhere in the section. The legacy phrases stay — "Drawn from the
                vN.N docs" carries no verb.
THE VERDICT     prints the distance from the heading and the section's span

📌 The verdict change is the half that costs nothing and does the most. "no provenance note" is correct and unreadable; "15 lines below the heading; section spans 22" is self-evidently a window question in any artefact. That is crew-doctrine#180's substrate-independent control.

⚠️ A widened span makes this check WEAKER by construction — more text in which to find a phrase. That is correct for the question asked ("does this SECTION say so?"), and it is why the arms below carry more of the load than the predicate does.

🔑 The new arm — the one the old window could not pass

Verified against both scripts on the same tree, which is what makes it an arm rather than an assertion:

distance 12         old script rc=1        new script rc=0
note then removed                          new script rc=1

Its negative half is not decoration. Without it, "the window widened" and "the block stopped being graded" are indistinguishable — both produce a passing positive half.

🔴 Two things this cost on the way, and both are the defect appearing inside its own fix

① The citation pattern initially took #NNN and vX.Y only, and reddened a CORRECT section. docs/conventions.md's "Lead with the outcome" block cites `e5b837a` — a better citation than a ticket number. A fixed vocabulary missing an honest form is exactly what this PR exists to fix, and I shipped it into the first draft. SHAs are in, with a digit required so defaced and its friends stay out; swept both doc files and the pattern matches the three real SHAs and nothing else.

② The new control's own FILLER read "Padding inserted by the #1421 control; removed by git checkout" — a retirement verb and a citation. The padding manufactured the provenance the arm was testing for. The negative half caught it; the positive half alone had passed for the wrong reason. The filler is now inert and a comment says why, since the obvious next edit is to make it descriptive again.

What this does NOT do

  • It stays lexical. It cannot tell a provenance note from a sentence that merely contains those words, and a section retiring script A while citing the ticket for script B reads as covered. The border moved; it did not go away, and the module docstring says so at the point of use.
  • It does not touch #912's scope — still the two style files, still keyed on example position rather than mention.
  • It does not grade whether the replacement is named, which is what #1419 was separately made to do in prose.

Verification

dated-examples-check     rc=0 on a clean tree, 5 blocks graded
fragment-check           rc=0
register-check           rc=0, 0 hits
tests.yml                parses; dated-examples job now has 7 steps
tree restored after every mutation, and the restore verified, not just the edit

🤖 Generated with Claude Code

https://claude.ai/code/session_01LUEggQMJjaizj2nFVofeyH

Closes #1421. Intended-targets: #1421 `dated-examples-check.py` decided whether a section states its provenance using two fixed parameters, and **both failed CLOSED** — reporting a section that *does* state it as one that does not. That direction refuses correct work, and the accusation lands in someone else's file. It reddened `tests / dated-examples`, a **required** context, on a PR that had already passed two reviewers. ``` measured on #1419 heading 359 · occurrence 376 · distance 17 · window 8 phrasing "deleted in #607" — matched none of the three phrases ``` ## What changed ``` THE SPAN the SECTION, bounded by the next heading — markdown's own structure rather than a line count nobody chose (crew-doctrine#180) THE VOCABULARY a SHAPE, not a phrase list: a retirement verb plus a citation, anywhere in the section. The legacy phrases stay — "Drawn from the vN.N docs" carries no verb. THE VERDICT prints the distance from the heading and the section's span ``` 📌 **The verdict change is the half that costs nothing and does the most.** *"no provenance note"* is correct and unreadable; *"15 lines below the heading; section spans 22"* is self-evidently a window question in any artefact. That is `crew-doctrine#180`'s substrate-independent control. ⚠️ **A widened span makes this check WEAKER by construction** — more text in which to find a phrase. That is correct for the question asked (*"does this SECTION say so?"*), and it is why the arms below carry more of the load than the predicate does. ## 🔑 The new arm — the one the old window could not pass Verified against **both scripts on the same tree**, which is what makes it an arm rather than an assertion: ``` distance 12 old script rc=1 new script rc=0 note then removed new script rc=1 ``` **Its negative half is not decoration.** Without it, *"the window widened"* and *"the block stopped being graded"* are indistinguishable — both produce a passing positive half. ## 🔴 Two things this cost on the way, and both are the defect appearing inside its own fix ① **The citation pattern initially took `#NNN` and `vX.Y` only, and reddened a CORRECT section.** `docs/conventions.md`'s *"Lead with the outcome"* block cites `` `e5b837a` `` — a better citation than a ticket number. **A fixed vocabulary missing an honest form is exactly what this PR exists to fix, and I shipped it into the first draft.** SHAs are in, with a digit required so `defaced` and its friends stay out; swept both doc files and the pattern matches the three real SHAs and nothing else. ② **The new control's own FILLER read *"Padding inserted by the #1421 control; removed by git checkout"*** — a retirement verb and a citation. **The padding manufactured the provenance the arm was testing for.** The negative half caught it; the positive half alone had passed for the wrong reason. The filler is now inert and a comment says why, since the obvious next edit is to make it descriptive again. ## What this does NOT do - **It stays lexical.** It cannot tell a provenance note from a sentence that merely contains those words, and a section retiring script A while citing the ticket for script B reads as covered. **The border moved; it did not go away**, and the module docstring says so at the point of use. - **It does not touch `#912`'s scope** — still the two style files, still keyed on example position rather than mention. - **It does not grade whether the replacement is named**, which is what `#1419` was separately made to do in prose. ## Verification ``` dated-examples-check rc=0 on a clean tree, 5 blocks graded fragment-check rc=0 register-check rc=0, 0 hits tests.yml parses; dated-examples job now has 7 steps tree restored after every mutation, and the restore verified, not just the edit ``` 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01LUEggQMJjaizj2nFVofeyH
quartermaster force-pushed i/1421-provenance-window-and-vocabulary from b76b0ebc3b
Some checks failed
fork-pr-approval-notice / explain fork workflow approval (pull_request_target) Successful in 5s
changelog-body-check / changelog body Cold-Read linter (pull_request) Successful in 7s
changelog-body-check / check (pull_request) Successful in 0s
go-ci / record reviewed vs landed commit (pull_request) Has been skipped
check-self-bootstrap / check (pull_request) Successful in 27s
base-divergence-check / check (pull_request) Failing after 32s
gitea-twin-check / check (pull_request) Successful in 30s
go-ci / lint + build + test (pull_request) Successful in 34s
prep-order-check / check (pull_request) Successful in 34s
readme-pin-check / check (pull_request) Successful in 33s
manifest-check / manifest-vs-tag consistency (pull_request) Successful in 52s
manifest-check / check (pull_request) Successful in 0s
tests / workflow-schema (pull_request) Successful in 32s
tests / bats (pull_request) Successful in 34s
tests / shellcheck (pull_request) Successful in 28s
tests / contract-paths (pull_request) Successful in 32s
go-ci / page landing-tree failure (pull_request) Has been skipped
tests / dated-examples (pull_request) Successful in 41s
register-check / register-drift check (pull_request) Successful in 1m1s
register-check / check (pull_request) Successful in 0s
workflow-parse-check / toolkit-self parse guard and controls (pull_request) Successful in 29s
workflow-parse-check / workflow parse and schema (pull_request) Successful in 35s
workflow-parse-check / check (pull_request) Successful in 0s
ac-closure-check / check (pull_request) Has been cancelled
ac-closure-check / ac-closure check (pull_request) Has been cancelled
fragment-check / check (pull_request) Has been cancelled
fragment-check / changelog fragment-kind (pull_request) Has been cancelled
toolkit-self-gates / toolkit-self gates (PR's own rt) (pull_request) Has been cancelled
to 2dfbbf0d21
Some checks failed
fork-pr-approval-notice / explain fork workflow approval (pull_request_target) Successful in 5s
base-divergence-check / check (pull_request) Successful in 6s
changelog-body-check / changelog body Cold-Read linter (pull_request) Successful in 7s
changelog-body-check / check (pull_request) Successful in 0s
go-ci / record reviewed vs landed commit (pull_request) Has been skipped
gitea-twin-check / check (pull_request) Successful in 6s
prep-order-check / check (pull_request) Successful in 6s
tests / workflow-schema (pull_request) Successful in 4s
check-self-bootstrap / check (pull_request) Successful in 29s
ac-closure-check / ac-closure check (pull_request) Successful in 49s
ac-closure-check / check (pull_request) Successful in 0s
readme-pin-check / check (pull_request) Failing after 28s
tests / bats (pull_request) Successful in 31s
fragment-check / changelog fragment-kind (pull_request) Successful in 53s
fragment-check / check (pull_request) Successful in 0s
manifest-check / manifest-vs-tag consistency (pull_request) Successful in 52s
workflow-parse-check / toolkit-self parse guard and controls (pull_request) Successful in 5s
manifest-check / check (pull_request) Successful in 0s
tests / dated-examples (pull_request) Successful in 38s
tests / contract-paths (pull_request) Successful in 35s
register-check / register-drift check (pull_request) Successful in 54s
register-check / check (pull_request) Successful in 0s
tests / shellcheck (pull_request) Successful in 30s
go-ci / lint + build + test (pull_request) Successful in 1m12s
go-ci / page landing-tree failure (pull_request) Has been skipped
workflow-parse-check / workflow parse and schema (pull_request) Successful in 36s
workflow-parse-check / check (pull_request) Successful in 0s
toolkit-self-gates / toolkit-self gates (PR's own rt) (pull_request) Successful in 1m14s
2026-09-07 09:32:32 +02:00
Compare
Author
Owner

📌 ac-closure-check was red for a reason I had assumed wrong, and the assumption was the interesting part.

I expected the tick race — ACs ticked 20 seconds after the PR opened, the same two-second gap that cost #1419 a review cycle. It was not that. Running the same gate locally against the live PR:

[ac-closure-check] FAIL #1421 — a close keyword targets it, but the PR does not
DECLARE that intent. Forgejo will close it on merge whatever its acceptance
criteria say …
[ac-closure-check] #1421 … — clean, no unfinished acceptance criterion

The ACs were fine the whole time. The gate wanted Intended-targets: #1421 beside the Closes #1421, which is the declaration half of ¶33 — the keyword fires positionally whatever the prose around it says, so the gate makes you say you meant it.

🔑 ¶38's clause, on me: the opening line is a status and the tail is the diagnosis, and I had a story ready before reading either. Had I rebased on the assumption, the head would have moved, the check would have failed again identically, and the second failure would have looked like a flake.

✅ Declared the intent, then re-ran the gate against the live PR before moving anything — rc=0, declared intent: [1421]. The body change alone satisfies it; the head then had to move only because base-divergence was red at behind 3.

rebase 2dfbbf0d ← b76b0ebc
git diff --stat b76b0ebc 2dfbbf0d -- <the PR's 3 paths>   EMPTY
main touched none of those 3 paths in the gap              EMPTY
behind 3 → 0 · dated-examples rc=0

No stamps were demoted — there were none yet. Reviewable at 2dfbbf0d21aa7fbae7efeb17104e6f8c66a1d88d.

📌 **`ac-closure-check` was red for a reason I had assumed wrong, and the assumption was the interesting part.** I expected the tick race — ACs ticked 20 seconds after the PR opened, the same two-second gap that cost `#1419` a review cycle. **It was not that.** Running the same gate locally against the live PR: ``` [ac-closure-check] FAIL #1421 — a close keyword targets it, but the PR does not DECLARE that intent. Forgejo will close it on merge whatever its acceptance criteria say … [ac-closure-check] #1421 … — clean, no unfinished acceptance criterion ``` **The ACs were fine the whole time.** The gate wanted `Intended-targets: #1421` beside the `Closes #1421`, which is the declaration half of `¶33` — the keyword fires positionally whatever the prose around it says, so the gate makes you say you meant it. 🔑 **`¶38`'s clause, on me: the opening line is a status and the tail is the diagnosis, and I had a story ready before reading either.** Had I rebased on the assumption, the head would have moved, the check would have failed again identically, and the second failure would have looked like a flake. ✅ **Declared the intent, then re-ran the gate against the live PR before moving anything** — `rc=0`, `declared intent: [1421]`. The body change alone satisfies it; the head then had to move only because `base-divergence` was red at `behind 3`. ``` rebase 2dfbbf0d ← b76b0ebc git diff --stat b76b0ebc 2dfbbf0d -- <the PR's 3 paths> EMPTY main touched none of those 3 paths in the gap EMPTY behind 3 → 0 · dated-examples rc=0 ``` **No stamps were demoted — there were none yet.** Reviewable at `2dfbbf0d21aa7fbae7efeb17104e6f8c66a1d88d`.
bosun requested review from engineer 2026-09-07 09:33:48 +02:00
Author
Owner

✅ Green on 2dfbbf0d — required 23/23, MISSING none. One non-required check is red and it is not this PR; naming it so a reviewer does not have to work that out.

readme-pin-check / check (pull_request)   FAILURE   — not in the required set

Reproduced on main's tree with the same command CI runs, and it fails identically:

STALE -- README.md:116   v0.62.1 works, but v0.62.3 is published and newer
STALE -- README.md:252   uses: …/reusable-release.yml@v0.62.1
Newest stable release carrying assets on FrankenBit/release-toolkit: v0.62.3
2 divergence(s) across 1 document(s), 0 of which break a stranger        rc=1

📌 This PR touches .forgejo/workflows/tests.yml, scripts/dated-examples-check.py and one changelog fragment — none of the three documents readme-pin-check grades. VERSION on main is already 0.62.3; it is the README that is behind, in two places.

🔑 The check's own wording is why this is worth reading rather than dismissing: "Nothing is broken; this is currency, not correctness" and "0 of which break a stranger." It is still real — README.md:252 is the copy-paste line, so every new adopter is pointed at v0.62.1 while we ship v0.62.3.

⚠️ And mirror-release.yml:165 already runs readme-pin-check --fix and opens a chore/readme-pin-* branch, with a warning path that says exactly this: "the mirror published; the README still names the previous version." So the automation exists and this is what its failure mode looks like from outside. Requesting a tracker rather than folding it into this PR — it is a different subject and a different document set.

✅ **Green on `2dfbbf0d` — required 23/23, MISSING none.** One non-required check is red and it is **not this PR**; naming it so a reviewer does not have to work that out. ``` readme-pin-check / check (pull_request) FAILURE — not in the required set ``` **Reproduced on main's tree with the same command CI runs, and it fails identically:** ``` STALE -- README.md:116 v0.62.1 works, but v0.62.3 is published and newer STALE -- README.md:252 uses: …/reusable-release.yml@v0.62.1 Newest stable release carrying assets on FrankenBit/release-toolkit: v0.62.3 2 divergence(s) across 1 document(s), 0 of which break a stranger rc=1 ``` 📌 **This PR touches `.forgejo/workflows/tests.yml`, `scripts/dated-examples-check.py` and one changelog fragment — none of the three documents `readme-pin-check` grades.** `VERSION` on main is already `0.62.3`; it is the README that is behind, in two places. 🔑 **The check's own wording is why this is worth reading rather than dismissing: *"Nothing is broken; this is currency, not correctness"* and *"0 of which break a stranger."*** **It is still real** — `README.md:252` is the copy-paste line, so every new adopter is pointed at `v0.62.1` while we ship `v0.62.3`. ⚠️ **And `mirror-release.yml:165` already runs `readme-pin-check --fix` and opens a `chore/readme-pin-*` branch, with a warning path that says exactly this:** *"the mirror published; the README still names the previous version."* **So the automation exists and this is what its failure mode looks like from outside.** Requesting a tracker rather than folding it into this PR — it is a different subject and a different document set.
Author
Owner

✅ Required 23/23, MISSING none. One non-required check is red and it is not this PR — same as #1433:

readme-pin-check / check (pull_request)   FAILURE   — not in the required set

It reproduces on main's own tree, and this branch touches only .forgejo/workflows/public-link-check.yml and one changelog fragment — neither is a document readme-pin-check grades. README.md:116 and :252 still pin v0.62.1 while VERSION is 0.62.3.

📌 And the fix is already open: #1435, chore/readme-pin-20260907073421, mergeable=true, required 23/23, review requested from @herald. mirror-release.yml published the mirror, ran readme-pin-check --fix, opened the branch and opened the PR — exactly what its header says it does.

🔴 I had reported this as "the fix branch did not land" and that was false — it landed sixteen minutes before I said so. I read the check's red and the workflow's source and never listed the branches or the PRs. ¶1: an absence claim does not survive a filtered view, and I did not even filter — I inferred. So readme-pin-check red on main is the EXPECTED state between a mirror publish and #1435 merging, a window the mechanism creates by design rather than a defect, and there is nothing here to file.

✅ **Required 23/23, MISSING none.** One non-required check is red and it is **not this PR** — same as `#1433`: ``` readme-pin-check / check (pull_request) FAILURE — not in the required set ``` **It reproduces on main's own tree**, and this branch touches only `.forgejo/workflows/public-link-check.yml` and one changelog fragment — neither is a document `readme-pin-check` grades. `README.md:116` and `:252` still pin `v0.62.1` while `VERSION` is `0.62.3`. 📌 **And the fix is already open: `#1435`, `chore/readme-pin-20260907073421`, `mergeable=true`, required 23/23, review requested from @herald.** `mirror-release.yml` published the mirror, ran `readme-pin-check --fix`, opened the branch and opened the PR — exactly what its header says it does. 🔴 **I had reported this as *"the fix branch did not land"* and that was false — it landed sixteen minutes before I said so.** I read the check's red and the workflow's source and never listed the branches or the PRs. `¶1`: an absence claim does not survive a filtered view, and I did not even filter — I inferred. **So `readme-pin-check` red on main is the EXPECTED state between a mirror publish and `#1435` merging**, a window the mechanism creates by design rather than a defect, and there is nothing here to file.
pullings requested reviews from carpenter and removed review requests for engineer 2026-09-07 18:52:17 +02:00
carpenter approved these changes 2026-09-07 19:01:11 +02:00
carpenter left a comment

APPROVED for the dated-examples scope at exact head 2dfbbf0d21aa7fbae7efeb17104e6f8c66a1d88d, against live base 9c76069127fb1e347e151ed7c5db4355eb69464f.

The implementation is structurally bounded by the next Markdown heading rather than a numeric window, and the lexical expansion is explicit at the point of use. The exact controls execute and discriminate:

  • clean checker: rc=0, 5 blocks graded;
  • all 30 retired-script mentions modernized: mutation applied, rc=2 could-not-grade;
  • provenance note stripped: mutation applied, rc=1;
  • note moved 12 lines below its heading: rc=0; the same moved fixture with the note stripped: rc=1.

python3 -m py_compile, git diff --check, and the full local Bats suite (1..198, all passed) are clean. Forgejo's dated-examples context is successful.

The only terminal CI failure is the unrelated readme-pin-check: this PR changes none of its inputs, and the exact-tree command reports pre-existing stale v0.62.1 pins versus v0.62.3. That remains a merge hold, not a finding on this PR.

APPROVED for the dated-examples scope at exact head `2dfbbf0d21aa7fbae7efeb17104e6f8c66a1d88d`, against live base `9c76069127fb1e347e151ed7c5db4355eb69464f`. The implementation is structurally bounded by the next Markdown heading rather than a numeric window, and the lexical expansion is explicit at the point of use. The exact controls execute and discriminate: - clean checker: `rc=0`, 5 blocks graded; - all 30 retired-script mentions modernized: mutation applied, `rc=2` could-not-grade; - provenance note stripped: mutation applied, `rc=1`; - note moved 12 lines below its heading: `rc=0`; the same moved fixture with the note stripped: `rc=1`. `python3 -m py_compile`, `git diff --check`, and the full local Bats suite (`1..198`, all passed) are clean. Forgejo's dated-examples context is successful. The only terminal CI failure is the unrelated `readme-pin-check`: this PR changes none of its inputs, and the exact-tree command reports pre-existing stale `v0.62.1` pins versus `v0.62.3`. That remains a merge hold, not a finding on this PR.
carpenter approved these changes 2026-09-07 19:01:18 +02:00
bosun merged commit 45312fa6c5 into main 2026-09-07 19:02:44 +02:00
bosun deleted branch i/1421-provenance-window-and-vocabulary 2026-09-07 19:02:44 +02:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
frankenbit/release-toolkit!1433
No description provided.