docs.no-session-narrative reaches hooks and code comments, but nothing has ever swept them — 4 known sites #876

Closed
opened 2026-08-29 18:54:40 +02:00 by timothy · 3 comments
Owner

Spawned by #812, which swept the docs corpus and deliberately stopped at its boundary.

The gap

docs.no-session-narrative states its scope as "every durable artifact — an in-repo docs/ page, a
skill, a README, a code comment, an Obsidian vault page"
. The DETECTOR's population is much narrower
and says so: docs/**/*.md minus docs/decisions/**, plus root-level *.md. Everything else —
.claude/ hooks and skills, web/, C# comments — is in scope for the RULE and has never been swept
by anything, mechanical or human.

#812 closed one class inside the docs corpus (a bare attribution of who found a finding, where the
reasons always follow in the next clause and the attribution carries nothing a reader acts on). The
same grep run outside that corpus finds the identical class, untouched:

Site Text
.claude/hooks/pretooluse-merge-consent.sh:178 "review found the first draft collapsing it into the latter: …"
.claude/hooks/pretooluse-merge-consent.sh:448 "It was used first, with the list consulted only on a 404, and cold review found what that left"
.claude/hooks/pretooluse-merge-consent.sh:681 "…, and cold review found scheduled+retarget denied while"
ErsatzTV.Tests/Application/Channels/CreateChannelFromLineupHandlerTests.cs:467 "regression (#70, found by adversarial review of PR #402): …"

Found by cold review of #812 (measured 2026-08-29 at 37d86cda0). Four is what ONE class's grep
returns — it is a floor, not the population.

Why it was not folded into #812

#812's scope is the docs sweep, and its verification story is the detector plus a reading pass over
that corpus. These sites are a different corpus with a different verification story, and expanding
mid-PR would have made the change unreviewable.

#812 does NOT claim the class is closed even inside the docs corpus, and neither should this
issue.
Six review rounds each surfaced another survivor there, every round's grep being a
locally-correct word-list over a phrasing space that never converged — the last one found
(revised after Fable review) in a file whose sibling had already been edited, which is the
"fix one path, then check its twin" shape. What #812 claims is bounded: its manifest's sites were
remediated. Treat the table above the same way — a floor from one grep, not a population.

The judgement this needs — do not treat it as mechanical

The carve-out matters more here than in docs, not less:

  • A code comment's provenance pointer is usually a finding, not narrative. CreateChannelFromLineupHandlerTests.cs:467 cites the issue AND the PR that found it, and the sentence after it names the exact defect (a third writer of PlaylistItem.PlaybackOrder missed when the other two were gated). A reader deleting that test would want all of it. It is genuinely arguable that "found by adversarial review of PR #402" should stay as a citation.
  • A hook comment is read by someone about to change a security gate. The merge-consent hook's comments record rejected designs. Severing the attribution must not sever the rejection.

So this is per-site judgement, the same as #812 — the three dispositions #812 used (CUT / sever the
phrase and keep the claim / KEEP) transfer directly.

Also worth deciding

Whether the detector's population should grow to cover .claude/**, or whether these corpora stay a
human judgement. #812 rejected a population change in the other direction (removing
docs/superpowers/**) partly because --diff only ever sees ADDED lines — the same argument applies
to any addition here, so a population change would help only for newly written comments.

Done-when

  • The population stated and derived from source, not from the table above (which is one class's grep, not the corpus)
  • Each site classified with its reasoning stated, using #812's three dispositions
  • A decision on whether the detector's population grows to .claude/**, recorded in the record if it changes
  • Adversarial review passed
Spawned by #812, which swept the **docs corpus** and deliberately stopped at its boundary. ## The gap `docs.no-session-narrative` states its scope as *"every durable artifact — an in-repo `docs/` page, a skill, a README, a code comment, an Obsidian vault page"*. The DETECTOR's population is much narrower and says so: `docs/**/*.md` minus `docs/decisions/**`, plus root-level `*.md`. Everything else — `.claude/` hooks and skills, `web/`, C# comments — is in scope for the RULE and has never been swept by anything, mechanical or human. #812 closed one class inside the docs corpus (a bare attribution of *who* found a finding, where the reasons always follow in the next clause and the attribution carries nothing a reader acts on). The same grep run outside that corpus finds the identical class, untouched: | Site | Text | |---|---| | `.claude/hooks/pretooluse-merge-consent.sh:178` | "review found the first draft collapsing it into the latter: …" | | `.claude/hooks/pretooluse-merge-consent.sh:448` | "It was used first, with the list consulted only on a 404, and cold review found what that left" | | `.claude/hooks/pretooluse-merge-consent.sh:681` | "…, and cold review found scheduled+retarget denied while" | | `ErsatzTV.Tests/Application/Channels/CreateChannelFromLineupHandlerTests.cs:467` | "regression (#70, found by adversarial review of PR #402): …" | Found by cold review of #812 (measured 2026-08-29 at `37d86cda0`). Four is what ONE class's grep returns — it is a floor, not the population. ## Why it was not folded into #812 #812's scope is the docs sweep, and its verification story is the detector plus a reading pass over that corpus. These sites are a different corpus with a different verification story, and expanding mid-PR would have made the change unreviewable. **#812 does NOT claim the class is closed even inside the docs corpus, and neither should this issue.** Six review rounds each surfaced another survivor there, every round's grep being a locally-correct word-list over a phrasing space that never converged — the last one found `(revised after Fable review)` in a file whose sibling had already been edited, which is the "fix one path, then check its twin" shape. What #812 claims is bounded: its manifest's sites were remediated. Treat the table above the same way — a floor from one grep, not a population. ## The judgement this needs — do not treat it as mechanical The carve-out matters more here than in docs, not less: - **A code comment's provenance pointer is usually a finding, not narrative.** `CreateChannelFromLineupHandlerTests.cs:467` cites the issue AND the PR that found it, and the sentence after it names the exact defect (a third writer of `PlaylistItem.PlaybackOrder` missed when the other two were gated). A reader deleting that test would want all of it. It is genuinely arguable that "found by adversarial review of PR #402" should stay as a citation. - **A hook comment is read by someone about to change a security gate.** The merge-consent hook's comments record rejected designs. Severing the attribution must not sever the rejection. So this is per-site judgement, the same as #812 — the three dispositions #812 used (CUT / sever the phrase and keep the claim / KEEP) transfer directly. ## Also worth deciding Whether the detector's population should grow to cover `.claude/**`, or whether these corpora stay a human judgement. #812 rejected a population change in the other direction (removing `docs/superpowers/**`) partly because `--diff` only ever sees ADDED lines — the same argument applies to any addition here, so a population change would help only for newly written comments. ## Done-when - [x] The population stated and derived from source, not from the table above (which is one class's grep, not the corpus) - [x] Each site classified with its reasoning stated, using #812's three dispositions - [x] A decision on whether the detector's population grows to `.claude/**`, recorded in the record if it changes - [x] Adversarial review passed
timothy added the priority: medium label 2026-08-29 18:55:08 +02:00
Author
Owner

Not claiming — but flagging a file collision for whoever does.

Three of this issue's four known sites are in .claude/hooks/pretooluse-merge-consent.sh (lines 178 / 448 / 681), and I am editing that file right now for #858+#859 (branch fix/858-859-merge-consent-hook-residuals). My diff lands in the branch-protection fetch block around line 448 and the guard-scope freshness block near 681 — both of which move line numbers and rewrite adjacent prose. So the table's line numbers will be stale once my PR merges, and a sweep started before then will conflict.

Suggested sequencing: take this after #858/#859 lands, and re-derive the sites by grep rather than by the line numbers above — which the issue body already tells you to do ("the population stated and derived from source, not from the table above").

Why I am not folding it into my bundle, since it is tempting on a shared-file basis: this issue was split out of #812 precisely because a comment sweep is a different corpus with a different verification story, and its own Done-when asks for a derived population, per-site dispositions, and a decision on whether the detector grows to .claude/**. Attaching that to a hook-behaviour PR would make both halves unreviewable — which is the reason #812 gave for not expanding mid-PR, and it applies unchanged in this direction. Shared file is not shared verification.

One substantive note for the eventual sweep, from having just read all three sites closely for #858/#859: line 448's attribution is load-bearing and I would grade it KEEP, or at most sever-and-keep. The sentence is "It was used first, with the list consulted only on a 404, and cold review found what that left behind: the precedence argument below guarded the 404 path while the 200 path — the one this repo actually takes — granted without it." The clause after the colon is a rejected design and the exact false-open it produced (a by-name branch_protections/{name} lookup that knows nothing about Priority). Cutting back to "cold review found" would be safe; cutting the whole sentence would delete the only record of why the by-name endpoint must never come back, and that is precisely the "severing the attribution must not sever the rejection" case this issue's own judgement section warns about. Line 178 and line 681 read to me as ordinary sever-the-phrase candidates by comparison.

**Not claiming — but flagging a file collision for whoever does.** Three of this issue's four known sites are in `.claude/hooks/pretooluse-merge-consent.sh` (lines 178 / 448 / 681), and I am editing that file right now for #858+#859 (branch `fix/858-859-merge-consent-hook-residuals`). My diff lands in the branch-protection fetch block **around line 448** and the guard-scope freshness block **near 681** — both of which move line numbers and rewrite adjacent prose. So the table's line numbers will be stale once my PR merges, and a sweep started before then will conflict. Suggested sequencing: take this **after** #858/#859 lands, and re-derive the sites by grep rather than by the line numbers above — which the issue body already tells you to do ("the population stated and derived from source, not from the table above"). Why I am not folding it into my bundle, since it is tempting on a shared-file basis: this issue was split out of #812 precisely because a comment sweep is a **different corpus with a different verification story**, and its own Done-when asks for a derived population, per-site dispositions, and a decision on whether the detector grows to `.claude/**`. Attaching that to a hook-*behaviour* PR would make both halves unreviewable — which is the reason #812 gave for not expanding mid-PR, and it applies unchanged in this direction. Shared file is not shared verification. One substantive note for the eventual sweep, from having just read all three sites closely for #858/#859: **line 448's attribution is load-bearing and I would grade it KEEP, or at most sever-and-keep.** The sentence is "It was used first, with the list consulted only on a 404, and cold review found what that left behind: the precedence argument below guarded the 404 path while the 200 path — the one this repo actually takes — granted without it." The clause after the colon is a *rejected design and the exact false-open it produced* (a by-name `branch_protections/{name}` lookup that knows nothing about Priority). Cutting back to "cold review found" would be safe; cutting the whole sentence would delete the only record of why the by-name endpoint must never come back, and that is precisely the "severing the attribution must not sever the rejection" case this issue's own judgement section warns about. Line 178 and line 681 read to me as ordinary sever-the-phrase candidates by comparison.
timothy added the in-progress label 2026-09-03 18:58:02 +02:00
Author
Owner

Claiming (session 2026-09-03). #858/#859 landed as #897, so the file-collision flagged above is cleared. Plan: derive the population by grep over every corpus the rule names outside the detector (.claude/**, scripts/**, web/**, C#), classify each site with #812's three dispositions, decide the detector-population question, one PR.

**Claiming** (session 2026-09-03). #858/#859 landed as #897, so the file-collision flagged above is cleared. Plan: derive the population by grep over every corpus the rule names outside the detector (`.claude/**`, `scripts/**`, `web/**`, C#), classify each site with #812's three dispositions, decide the detector-population question, one PR.
Author
Owner

Closing record

Outcome: Shipped in PR #906, merged as efadbec297. The population outside the detector was derived by a whitespace-joined sweep (453 sites in 108 files at fb5592971, plus a second phrasing pass), every site classified with #812's CUT / SEVER / KEEP dispositions (per-corpus manifests on the PR: SEVER 467 · CUT 49 · KEEP 208 over 724 manifest rows), and the detector's population grown to .claude/, .gitea/, .husky/ and scripts/ regardless of extension, minus the detector, its test and scripts/tests/fixtures/. Three latent detector defects fixed on the way: a tracked symlink or gitlink was opened by --all, an unmerged path was scanned once per index stage, and a +++ filename containing a space was dropped silently in --diff.
Root cause: the rule was established for the docs corpus and its detector never extended to the class it names (#773 shape); the corpus it did not reach is the one where narrative is actively written — 287 of 453 sites were under 30 days old.
Decisions/conventions changed: docs.no-session-narrativemechanics: (population, exemptions, regular-files rule, re-dated depth measurement, tab arm on the proven list), signals:, body (the #876 paragraph). Decisions-Edit: yes.
Reusable knowledge:

  • Growing a guard's population silently falsifies every dated measurement the record states ABOUT that population (a depth histogram, a file count, "no path deeper than N"). They live in prose outside the diff; re-measure and date each one in the same commit — here one was caught by a cold reviewer running the mutant.
  • Test data under a process prefix (decision-record copies in scripts/tests/fixtures/) is not a durable artifact; exempting it is what keeps the depth claim true, and the oracle now asserts it so it cannot go stale silently.
  • A range edit anchored on two markers deletes whatever sits between them: a test was lost with the def test_ count unchanged (one removed, one added). A by-function-name diff against origin/main is the check; a count is not.
  • A title count by regex missed an it.each whose array spans lines with parentheses; walk each changed title line back to its anchor instead.
  • Git renames one side of a symlink/regular add/add conflict (h.sh~HEAD), so that index shape needs update-index --index-info to witness.
  • Across six review rounds every defect was in a claim ABOUT the change; the sweep itself lost no rejected design, harm, measurement or trap over ~1,100 deleted prose lines.
    Verification: scripts/tests 1569 passed / 3 skipped on the final tree; web lint + typecheck + 1319 vitest green; check-doc-narrative.py --all 179 files / 1 warning (the #812 carve-out), --diff origin/main 0 warnings; decisions_validate --base origin/main OK; ten detector mutants each red; BOM unchanged on every touched .cs. Reviews: Codex rounds 1–5 (cross-family, read-only) and Opus rounds 1–6 (worktree-isolated, every deleted line read); final Opus verdict MERGEABLE with 0 new findings; CI green on a7d91bf15.
    Deferred: none. web/ and C# stay outside the detector on a stated measurement (3 of 74 PATTERNS-matching sites); the predicate did not grow, by the record's own argument.
    Docs updated: docs/decisions/records/docs/no-session-narrative.md, docs/decisions/README.md (regenerated), docs/ci-cd.md.
## Closing record **Outcome:** Shipped in PR #906, merged as `efadbec297`. The population outside the detector was derived by a whitespace-joined sweep (453 sites in 108 files at `fb5592971`, plus a second phrasing pass), every site classified with #812's CUT / SEVER / KEEP dispositions (per-corpus manifests on the PR: SEVER 467 · CUT 49 · KEEP 208 over 724 manifest rows), and the detector's population grown to `.claude/`, `.gitea/`, `.husky/` and `scripts/` regardless of extension, minus the detector, its test and `scripts/tests/fixtures/`. Three latent detector defects fixed on the way: a tracked symlink or gitlink was opened by `--all`, an unmerged path was scanned once per index stage, and a `+++` filename containing a space was dropped silently in `--diff`. **Root cause:** the rule was established for the docs corpus and its detector never extended to the class it names (`#773` shape); the corpus it did not reach is the one where narrative is actively written — 287 of 453 sites were under 30 days old. **Decisions/conventions changed:** `docs.no-session-narrative` — `mechanics:` (population, exemptions, regular-files rule, re-dated depth measurement, tab arm on the proven list), `signals:`, body (the #876 paragraph). `Decisions-Edit: yes`. **Reusable knowledge:** - Growing a guard's population silently falsifies every dated measurement the record states ABOUT that population (a depth histogram, a file count, "no path deeper than N"). They live in prose outside the diff; re-measure and date each one in the same commit — here one was caught by a cold reviewer running the mutant. - Test data under a process prefix (decision-record copies in `scripts/tests/fixtures/`) is not a durable artifact; exempting it is what keeps the depth claim true, and the oracle now asserts it so it cannot go stale silently. - A range edit anchored on two markers deletes whatever sits between them: a test was lost with the `def test_` count unchanged (one removed, one added). A by-function-name diff against `origin/main` is the check; a count is not. - A title count by regex missed an `it.each` whose array spans lines with parentheses; walk each changed title line back to its anchor instead. - Git renames one side of a symlink/regular add/add conflict (`h.sh~HEAD`), so that index shape needs `update-index --index-info` to witness. - Across six review rounds every defect was in a claim ABOUT the change; the sweep itself lost no rejected design, harm, measurement or trap over ~1,100 deleted prose lines. **Verification:** `scripts/tests` 1569 passed / 3 skipped on the final tree; web lint + typecheck + 1319 vitest green; `check-doc-narrative.py --all` 179 files / 1 warning (the #812 carve-out), `--diff origin/main` 0 warnings; `decisions_validate --base origin/main` OK; ten detector mutants each red; BOM unchanged on every touched `.cs`. Reviews: Codex rounds 1–5 (cross-family, read-only) and Opus rounds 1–6 (worktree-isolated, every deleted line read); final Opus verdict MERGEABLE with 0 new findings; CI green on `a7d91bf15`. **Deferred:** none. `web/` and C# stay outside the detector on a stated measurement (3 of 74 PATTERNS-matching sites); the predicate did not grow, by the record's own argument. **Docs updated:** `docs/decisions/records/docs/no-session-narrative.md`, `docs/decisions/README.md` (regenerated), `docs/ci-cd.md`.
timothy removed the in-progress label 2026-09-03 21:26:25 +02:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: timothy/ersatztv#876