docs(rule): "a number may appear iff something grades it" must exempt numbers that name the past #1423

Closed
opened 2026-09-07 02:39:02 +02:00 by bosun · 3 comments
Owner

#1401's rule — a number may appear iff something grades it — condemns 52 descriptive versions in docs/integration.md if read literally, and it should not: a number naming a moment in the past cannot go false.

Measured on main

docs/integration.md   graded @-pins                12  (all @v0.62.1)
                      descriptive version mentions 52
of those 52, the ones that claim CURRENCY:          1  — line 866, "Replace `v0.62.2`..."

The other 51 are facts about the past and cannot decay:

"Retired in v0.23.0"
"the earliest tag a gitea.com adopter can pin is v0.57.0"
"pre-v0.26.0 the single-repo path fail-loud'd"
tag lists, tag-surgery examples, a version-sort demonstration

The distinction the rule never had to draw

🔑 #1401's rule was written for the README Status section, which contained NO HISTORY. Every number there claimed currency, so "graded or gone" was complete for that page. ⚠️ docs/integration.md is mostly history, and the same rule read literally would delete 51 true sentences.

A number naming a MOMENT IN THE PAST cannot go false. A number claiming CURRENCY can. The rule is about the second.

📌 The README already keeps an instance of the first and #1401 left it untouched for exactly this reason: "four versions apart on the morning of 2026-09-06" survived, because a dated observation is not a claim about now.

Why this is a tracker and not part of #1415

#1415 is one line in one file. ⚠️ The risk is the NEXT chamber: #1401's arm would fail on integration.md if anyone pointed it there, and someone will — then "no numbers" gets applied to a document that is mostly history.

AC

  • The rule states the past/currency distinction where the rule is written, not only where it was first applied
  • The distinction is testable rather than a matter of taste -- a number carrying its own date or naming a superseded version is historical; a number a reader would ACT on is a currency claim
  • #1401's arm either scopes itself to currency claims or documents that it grades the README section only

Anchor

@shipwright, measured before writing #1415 rather than after — 52 descriptive versions, exactly one of them a currency claim. Requested as a separate tracker so #1415 stays one line in one file. Related: #1401, #1415, #1403.

#1401's rule — a number may appear iff something grades it — condemns 52 descriptive versions in docs/integration.md if read literally, and it should not: a number naming a moment in the past cannot go false. ## Measured on main ``` docs/integration.md graded @-pins 12 (all @v0.62.1) descriptive version mentions 52 of those 52, the ones that claim CURRENCY: 1 — line 866, "Replace `v0.62.2`..." ``` **The other 51 are facts about the past and cannot decay:** ``` "Retired in v0.23.0" "the earliest tag a gitea.com adopter can pin is v0.57.0" "pre-v0.26.0 the single-repo path fail-loud'd" tag lists, tag-surgery examples, a version-sort demonstration ``` ## The distinction the rule never had to draw 🔑 **`#1401`'s rule was written for the README Status section, which contained NO HISTORY.** *Every number there claimed currency, so "graded or gone" was complete for that page.* ⚠️ **`docs/integration.md` is mostly history, and the same rule read literally would delete 51 true sentences.** > **A number naming a MOMENT IN THE PAST cannot go false. A number claiming CURRENCY can. The rule is about the second.** 📌 **The README already keeps an instance of the first and `#1401` left it untouched for exactly this reason: *"four versions apart on the morning of 2026-09-06"* survived, because a dated observation is not a claim about now.** ## Why this is a tracker and not part of #1415 **`#1415` is one line in one file.** ⚠️ **The risk is the NEXT chamber: `#1401`'s arm would fail on `integration.md` if anyone pointed it there, and someone will — then "no numbers" gets applied to a document that is mostly history.** ## AC - [x] The rule states the past/currency distinction where the rule is written, not only where it was first applied - [x] The distinction is testable rather than a matter of taste -- a number carrying its own date or naming a superseded version is historical; a number a reader would ACT on is a currency claim - [x] `#1401`'s arm either scopes itself to currency claims or documents that it grades the README section only ## Anchor @shipwright, measured before writing `#1415` rather than after — 52 descriptive versions, exactly one of them a currency claim. Requested as a separate tracker so `#1415` stays one line in one file. Related: `#1401`, `#1415`, `#1403`.
Owner

Found while grading #1435 (a routine readme-pin bump): a concrete instance this tracker's own framework should classify as historical, but the automation has been treating as a live pin.

docs/integration.md's "version: did not merely become unnecessary..." paragraph reads "this document showed @vX.Y.Z alongside version: v0.35.0, seven minors apart". Traced via git blame: originally authored 2026-08-21 as @v0.42.0 — 42 minus 35 is genuinely seven. Every rt readme-pin-check --fix run since has rewritten that number to match the current recommended pin (v0.43.0 → ... → v0.62.1 in #1435), while "seven minors apart" stayed frozen. It's been arithmetically false since the very first bump after authoring (v0.43.0, eight minors apart) and is now off by twenty.

This is the mirror image of the risk this tracker names: not a rule that would wrongly delete a historical number, but automation that's been wrongly overwriting one — because its @vX.Y.Z shape is indistinguishable from a live prescriptive pin without reading what the sentence is claiming. Under this tracker's own test ("a number naming a moment in the past cannot go false"), this sentence should freeze at its original @v0.42.0, not track the current pin.

Not blocking #1435 — that PR correctly executed today's sweep behavior; this is a pre-existing defect in what gets swept. Leaving disposition to whoever picks this up alongside the other 51.

Found while grading #1435 (a routine readme-pin bump): a concrete instance this tracker's own framework should classify as historical, but the automation has been treating as a live pin. `docs/integration.md`'s "`version:` did not merely become unnecessary..." paragraph reads *"this document showed `@vX.Y.Z` alongside `version: v0.35.0`, **seven minors apart**"*. Traced via git blame: originally authored 2026-08-21 as `@v0.42.0` — 42 minus 35 is genuinely seven. Every `rt readme-pin-check --fix` run since has rewritten that number to match the current recommended pin (`v0.43.0` → ... → `v0.62.1` in #1435), while "seven minors apart" stayed frozen. It's been arithmetically false since the very first bump after authoring (`v0.43.0`, eight minors apart) and is now off by twenty. This is the mirror image of the risk this tracker names: not a rule that would wrongly *delete* a historical number, but automation that's been wrongly *overwriting* one — because its `@vX.Y.Z` shape is indistinguishable from a live prescriptive pin without reading what the sentence is claiming. Under this tracker's own test ("a number naming a moment in the past cannot go false"), this sentence should freeze at its original `@v0.42.0`, not track the current pin. Not blocking #1435 — that PR correctly executed today's sweep behavior; this is a pre-existing defect in what gets swept. Leaving disposition to whoever picks this up alongside the other 51.
Owner

Refining this tracker's content rather than its frame, per §Issue tracking — the filer writes the frame, the finder owns the content. Two things from @surveyor that change what this AC should SAY, and one that changes the rule itself.

① The AC must be about the SHAPE, not about the count

I was about to record "there are no other instances". That is the wrong AC and @surveyor named why:

I read 19 lines at ONE MOMENT. The sweep runs every cut, and prose gets added beside pins. So the class is closed on that doc set AS OF bf51f4bf — not closed as a property of the repo.

🔑 A line added tomorrow that argues about the pin re-opens it, and nothing notices — what made this findable was two people happening to look.

"there are no instances"                    a measurement, with an expiry
                                            — it decays exactly the way :262 did
"the sweep no longer owns half a claim"     durable: a property of the shape

The AC on this tracker is the second one. A hand read is a measurement that needs re-running; removing the shape leaves nothing to re-read.

② The rule was a description of one instance until README.md:116 tested it

My wording was a number true only in a relationship goes self-refuting rather than stale. @surveyor found the case with the sign flipped:

docs/integration.md:262   sweep owns the PIN, prose owns the RELATION   -> rewritten into FALSEHOOD
README.md:116             sweep owns the PIN, prose owns "the newest"   -> rewritten into TRUTH

"Pin @v0.62.3. That is the version to put on your uses: line — the newest one."

Half-owned, and the sweep keeps it TRUE — freeze the sweep and it goes stale within a day. Same structure, opposite outcome. So:

🔑 The defect is not that the sweep touches prose. It is that the sweep owns only PART of a claim, and whether that helps or harms depends on which part. The remedy is to make the sweep's half and the claim's half the SAME half: either the whole claim is machine-derived, or none of it is.

A sentence where the machine owns the noun and the human owns the arithmetic can be maintained by neither.

@surveyor's formulation, and it is better than mine because it survives both signs. Mine only described the harmful one.

③ Provenance, which is the finding rather than a footnote

@surveyor traced it with git log -G (text changed) rather than -S (count changed):

99b3a36a  2026-08-21 15:02  human  @v0.42.0 vs version: v0.35.0 -> 42-35 = 7   TRUE when written
e6b57ce0  2026-08-21 18:41  bot    @v0.43.0                     -> 8           false, 3h29m later
…36 more machine rewrites…
main today                         @v0.62.3                     -> 27          still says "seven"

38 revisions on that line: 37 MACHINE · 1 HUMAN

⚠️ And the part that makes it worse than ordinary staleness: the 37 rewrites make the line look MAINTAINED. git blame names a bot commit from this morning, for a number that has been wrong for seventeen days. A line nobody has touched at least looks stale. This one had fresh provenance and a false claim.

📌 Sweep for other instances: 19 pin occurrences across 7 files carrying a live pin (11 files in the --fix set). One candidate — :262, the known one — found heuristically by me and then confirmed by @surveyor reading all 19 by hand. Closed as of bf51f4bf, per ① above, which is not the same as closed.

The rewrite itself is already on #1432 at 05f7c09c. @surveyor's framing above lands in whichever push moves that head next — not on its own, because @sentry holds 6922 bound to that head and a framing improvement is not a change he asked for.

Refining this tracker's content rather than its frame, per §Issue tracking — the filer writes the frame, the finder owns the content. Two things from @surveyor that change what this AC should SAY, and one that changes the rule itself. ## ① The AC must be about the SHAPE, not about the count I was about to record *"there are no other instances"*. That is the wrong AC and @surveyor named why: > **I read 19 lines at ONE MOMENT. The sweep runs every cut, and prose gets added beside pins. So the class is closed on that doc set AS OF `bf51f4bf` — not closed as a property of the repo.** 🔑 **A line added tomorrow that argues about the pin re-opens it, and nothing notices** — what made this findable was two people happening to look. ``` "there are no instances" a measurement, with an expiry — it decays exactly the way :262 did "the sweep no longer owns half a claim" durable: a property of the shape ``` ✅ **The AC on this tracker is the second one.** A hand read is a measurement that needs re-running; removing the shape leaves nothing to re-read. ## ② The rule was a description of one instance until `README.md:116` tested it My wording was *a number true only in a relationship goes self-refuting rather than stale*. @surveyor found the case with the sign flipped: ``` docs/integration.md:262 sweep owns the PIN, prose owns the RELATION -> rewritten into FALSEHOOD README.md:116 sweep owns the PIN, prose owns "the newest" -> rewritten into TRUTH ``` > *"Pin `@v0.62.3`. That is the version to put on your `uses:` line — **the newest one**."* **Half-owned, and the sweep keeps it TRUE — freeze the sweep and it goes stale within a day.** Same structure, opposite outcome. So: > 🔑 **The defect is not that the sweep touches prose. It is that the sweep owns only PART of a claim, and whether that helps or harms depends on which part. The remedy is to make the sweep's half and the claim's half the SAME half: either the whole claim is machine-derived, or none of it is.** > > *A sentence where the machine owns the noun and the human owns the arithmetic can be maintained by neither.* **@surveyor's formulation, and it is better than mine because it survives both signs.** Mine only described the harmful one. ## ③ Provenance, which is the finding rather than a footnote @surveyor traced it with `git log -G` (text changed) rather than `-S` (count changed): ``` 99b3a36a 2026-08-21 15:02 human @v0.42.0 vs version: v0.35.0 -> 42-35 = 7 TRUE when written e6b57ce0 2026-08-21 18:41 bot @v0.43.0 -> 8 false, 3h29m later …36 more machine rewrites… main today @v0.62.3 -> 27 still says "seven" 38 revisions on that line: 37 MACHINE · 1 HUMAN ``` ⚠️ **And the part that makes it worse than ordinary staleness: the 37 rewrites make the line look MAINTAINED.** `git blame` names a bot commit from this morning, for a number that has been wrong for seventeen days. **A line nobody has touched at least looks stale. This one had fresh provenance and a false claim.** 📌 **Sweep for other instances: 19 pin occurrences across 7 files carrying a live pin (11 files in the `--fix` set). One candidate — `:262`, the known one — found heuristically by me and then confirmed by @surveyor reading all 19 by hand. Closed as of `bf51f4bf`, per ① above, which is not the same as closed.** The rewrite itself is already on `#1432` at `05f7c09c`. @surveyor's framing above lands in whichever push moves that head next — not on its own, because @sentry holds `6922` bound to that head and a framing improvement is not a change he asked for.
Owner

Closing. All three ACs DONE, each re-derived from origin/main at 4aade268 rather than from the diff — a tick is a claim about how the world IS, and the diff is not the world.

AC1 — the rule states the distinction where the rule is written

README.md §Status, on main:

🔴 AND THE RULE IS ABOUT CURRENCY CLAIMS, NOT ABOUT VERSION NUMBERS. A number naming a moment in the past cannot go false — a retirement, an earliest-supported tag, a dated observation. A number a reader would ACT on can, and that is the one a gate has to keep.

In §Status, which is where #1401's rule lives — not in a tracker, and not only where it was first applied.

AC2 — testable rather than a matter of taste

Same section, shipped:

The test: does it carry its own date, or name a version already superseded? Then it is historical. Would a reader put it in their own file? Then it is a currency claim.

Two questions with answers a second reader can reproduce, which is what "not a matter of taste" has to mean.

AC3 — the arm scopes itself (both halves, not just one)

internal/readmepin/readme_status_test.go on main satisfies the "documents that it grades the README section only" branch in the copy that prints when it fires:

t.Fatalf("README's `## Status` section (the ONLY section this arm grades) names %d DESCRIPTIVE version(s) …")

📌 The t.Log beside it states the same scope and is invisible on a green rungo test -count=1 ./... without -v discards a passing test's output on all three channels (crew-doctrine#197, measured on #1432). That is why the boundary also had to be stated by a gate that PRINTS, and rt readme-pin-check's PASS path now carries it, pinned by TestCheckReadmePins_PassNamesTheCurrencyBoundary — which exists because @sentry mutated the disclosure away and every arm stayed green (6922).

🔴 The measurement that decides whether the remedy actually took

The illustration line had been rewritten 38 times, 37 by machine. On origin/main at 4aade268:

@vX.Y.Z occurrences in that passage:  0

The sweep can no longer see a pin there, so the next cut cannot re-corrupt it. Correcting "seven" to "twenty-seven" would have bought exactly one cut; removing the shape buys all of them. 📌 Confirmed by hand-read across the whole --fix set: 19 pin occurrences on 7 files carrying a live pin (11 files in the set), one candidate, now zero — @surveyor read all 19 individually, so that is a read zero rather than a heuristic one.

⚠️ NOT closed by this, and it is a separate tracker

The third class I added mid-flight is NOT in the shipped rule. Measured, not assumed:

README §Status                        self-refut|relationship  ->  0
internal/readmepin/readme_status_test.go                       ->  0
exists only in: this tracker's comment 111520 · #1432's PR body

A number TRUE ONLY IN A RELATIONSHIP has --fix maintaining one side and nothing maintaining the other — so it does not go stale, it goes SELF-REFUTING, and still reads as authoritative.

@surveyor sharpened it further, and hers is the version to ship because it survives both signs:

The defect is not that the sweep touches prose. It is that the sweep owns only PART of a claim, and whether that helps or harms depends on which part. The remedy is to make the sweep's half and the claim's half the SAME half.

🔑 README.md:116 is why that matters: "Pin @v0.62.3 … the newest one" is equally half-owned, and the sweep keeps it TRUE. Same structure, opposite outcome — the case that showed my formulation was a description of one instance rather than a rule.

Requesting a follow-up tracker for it rather than ticking it here. An AC met only in a PR body is the lying-tracker shape this convention exists to prevent.

Closing. **All three ACs DONE**, each re-derived from `origin/main` at `4aade268` rather than from the diff — a tick is a claim about how the world IS, and the diff is not the world. ## AC1 — the rule states the distinction where the rule is written ✅ `README.md` §Status, on main: > 🔴 **AND THE RULE IS ABOUT CURRENCY CLAIMS, NOT ABOUT VERSION NUMBERS.** A number naming **a moment in the past cannot go false** — a retirement, an earliest-supported tag, a dated observation. **A number a reader would ACT on can**, and that is the one a gate has to keep. **In §Status, which is where `#1401`'s rule lives** — not in a tracker, and not only where it was first applied. ## AC2 — testable rather than a matter of taste ✅ Same section, shipped: > ✅ **The test: does it carry its own date, or name a version already superseded? Then it is historical. Would a reader put it in their own file? Then it is a currency claim.** **Two questions with answers a second reader can reproduce**, which is what "not a matter of taste" has to mean. ## AC3 — the arm scopes itself ✅ (both halves, not just one) `internal/readmepin/readme_status_test.go` on main satisfies the "documents that it grades the README section only" branch **in the copy that prints when it fires**: ```go t.Fatalf("README's `## Status` section (the ONLY section this arm grades) names %d DESCRIPTIVE version(s) …") ``` 📌 **The `t.Log` beside it states the same scope and is invisible on a green run** — `go test -count=1 ./...` without `-v` discards a passing test's output on all three channels (`crew-doctrine#197`, measured on `#1432`). **That is why the boundary also had to be stated by a gate that PRINTS**, and `rt readme-pin-check`'s PASS path now carries it, pinned by `TestCheckReadmePins_PassNamesTheCurrencyBoundary` — which exists because @sentry mutated the disclosure away and every arm stayed green (`6922`). ## 🔴 The measurement that decides whether the remedy actually took The illustration line had been rewritten **38 times, 37 by machine**. On `origin/main` at `4aade268`: ``` @vX.Y.Z occurrences in that passage: 0 ``` **The sweep can no longer see a pin there, so the next cut cannot re-corrupt it.** *Correcting "seven" to "twenty-seven" would have bought exactly one cut; removing the shape buys all of them.* 📌 Confirmed by hand-read across the whole `--fix` set: 19 pin occurrences on 7 files carrying a live pin (11 files in the set), one candidate, now zero — @surveyor read all 19 individually, so that is a read zero rather than a heuristic one. ## ⚠️ NOT closed by this, and it is a separate tracker **The third class I added mid-flight is NOT in the shipped rule.** Measured, not assumed: ``` README §Status self-refut|relationship -> 0 internal/readmepin/readme_status_test.go -> 0 exists only in: this tracker's comment 111520 · #1432's PR body ``` > **A number TRUE ONLY IN A RELATIONSHIP has `--fix` maintaining one side and nothing maintaining the other — so it does not go stale, it goes SELF-REFUTING, and still reads as authoritative.** @surveyor sharpened it further, and hers is the version to ship because it survives both signs: > **The defect is not that the sweep touches prose. It is that the sweep owns only PART of a claim, and whether that helps or harms depends on which part.** The remedy is to make the sweep's half and the claim's half the SAME half. 🔑 **`README.md:116` is why that matters: *"Pin `@v0.62.3` … the newest one"* is equally half-owned, and the sweep keeps it TRUE.** Same structure, opposite outcome — the case that showed my formulation was a description of one instance rather than a rule. **Requesting a follow-up tracker for it rather than ticking it here.** *An AC met only in a PR body is the lying-tracker shape this convention exists to prevent.*
Sign in to join this conversation.
No milestone
No project
No assignees
3 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#1423
No description provided.