Remediate the 21 session-narrative sites #784's sweep found — 21 judgements, not a regex #812
Closed
opened 2026-08-21 22:49:00 +02:00 by timothy
·
3 comments
No Branch/Tag Specified
main
renovate/meziantou.analyzer-3.x
release/v26.15.0-notes
fix/830-add-items-error-surface
renovate/lucene.net
renovate/cliwrap-3.x
issue-806-guard-populations
renovate/dotnet-monorepo
scratch/767b-poisoned
scratch/767b-control
release/v26.14.0-notes
release/v26.14.0
renovate/sqlitepclraw.bundle_e_sqlite3-3.x
docs/510-skill-logo-bug-policy
fix/510-watermark-resolution-policy
fix/629-verdict-classifier-falseopens
fix/609-decisions-edit-token-scope
issue-135-clear-to-none
release/v26.12.0-notes
fix/409b-lastscan-api-parity
fix/401-updatechannel-mirror-422
fix/327-playlist-rename-validation
fix/410-scancancel-log-level
fix/409-447-librariesscreen-neverscanned
fix/338-zap-exit-code
fix/367-plex-budget-message
fix/310-debom-legacy-cs
ci/604-lane-rebalance
feat/388-design-mirror
feat/247-test-ownership
feat/247-primary-action
feat/357-player-owned-playback
feat/357-jellyfin-plugin-poc
fix/289-mcp-hardening
issue58-mcp
feat/244-channels-extract
ci/auto-bump-prod-compose
feat/multi-rerun-collections-api
feat/collections-api
feat/quick-wins
feat/185-docs-part2
feat/140-collections-screen
feat/146-channel-edit
feat/147-classic-ui-link
issue22-renovate-dashboard
feat/91-cutover
feat/63-composite-create
feat/65-library-browse
feat/85-epg
feat/86-schedule-editor
feat/109-dashboard-data
feat/99-session-tracking
fix/dockerfile-node-tag
feat/59-spa-foundation
docs/59-ui-redesign-brief
feat/102-json-guide
feat/111-schedule-durations
feat/104-artwork-upload
feat/103-media-sources-api
feat/playouts-read-api
feat/108-health-api
feat/105-picker-list-endpoints
issue-97-channel-state-api
issue42-jellyfin-musicvideos
issue46-rest-api-error-contract
dependabot/nuget/ErsatzTV.FFmpeg.Tests/multi-d307a2e06f
qsv-improvements
hdr-vulkan-cuda-test
v26.15.0
v26.14.0
v26.13.0
v26.12.0
v26.11.0
v26.10.0
v26.9.0
v26.8.0
v26.7.0
blazor-final
v26.6.0
v26.5.0
v26.4.0
v26.3.1
v26.3.0
v26.2.0
v26.1.1
v26.1.0
v25.9.0
v25.8.0
v25.7.1
v25.7.0
v25.6.0
v25.5.0
v25.4.0
v25.3.1
v25.3.0
v25.2.0
v25.1.0
v0.8.8-beta
v0.8.7-beta
v0.8.6-beta
v0.8.5-beta
v0.8.4-beta
v0.8.3-beta
v0.8.2-beta
v0.8.1-beta
v0.8.0-beta
v0.7.9-beta
v0.7.8-beta
v0.7.7-beta
v0.7.6-beta
v0.7.5-beta
v0.7.4-beta
v0.7.3-beta
v0.7.2-beta
v0.7.1-beta
v0.7.0-beta
v0.6.9-beta
v0.6.8-beta
v0.6.7-beta
v0.6.6-beta
v0.6.5-beta
v0.6.4-beta
v0.6.3-beta
v0.6.2-beta
v0.6.1-beta
v0.6.0-beta
v0.5.8-beta
v0.5.7-beta
v0.5.6-beta
v0.5.5-beta
v0.5.4-beta
v0.5.3-beta
v0.5.2-beta
v0.5.1-beta
v0.5.0-beta
v0.4.5-alpha
v0.4.4-alpha
v0.4.3-alpha
v0.4.2-alpha
v0.4.1-alpha
v0.4.0-alpha
v0.3.8-alpha
v0.3.7-alpha
develop
v0.3.6-alpha
v0.3.5-alpha
v0.3.4-alpha
v0.3.3-alpha
v0.3.2-alpha
v0.3.1-alpha
v0.3.0-alpha
v0.2.5-alpha
v0.2.4-alpha
v0.2.3-alpha
v0.2.2-alpha
v0.2.1-alpha
v0.2.0-alpha
v0.1.5-alpha
v0.1.4-alpha
v0.1.3-alpha
v0.1.2-alpha
v0.1.1-alpha
v0.1.0-alpha
v0.0.62-alpha
v0.0.61-alpha
v0.0.60-alpha
v0.0.59-alpha
v0.0.58-alpha
v0.0.57-alpha
v0.0.56-alpha
v0.0.55-alpha
v0.0.54-alpha
v0.0.53-alpha
v0.0.52-alpha
v0.0.51-alpha
v0.0.50-alpha
v0.0.49-prealpha
v0.0.48-prealpha
v0.0.47-prealpha
v0.0.46-prealpha
v0.0.45-prealpha
v0.0.44-prealpha
v0.0.43-prealpha
v0.0.42-prealpha
v0.0.41-prealpha
v0.0.40-prealpha
v0.0.39-prealpha
v0.0.38-prealpha
v0.0.37-prealpha
v0.0.36-prealpha
v0.0.35-prealpha
v0.0.34-prealpha
v0.0.33-prealpha
v0.0.32-prealpha
v0.0.31-prealpha
v0.0.30-prealpha
v0.0.29-prealpha
v0.0.28-prealpha
v0.0.27-prealpha
v0.0.26-prealpha
v0.0.25-prealpha
v0.0.24-prealpha
v0.0.23-prealpha
v0.0.22-prealpha
v0.0.21-prealpha
v0.0.20-prealpha
v0.0.19-prealpha
v0.0.18-prealpha
v0.0.17-prealpha
v0.0.16-prealpha
v0.0.15-prealpha
v0.0.14-prealpha
v0.0.13-prealpha
v0.0.12-prealpha
v0.0.11-prealpha
v0.0.10-prealpha
v0.0.9-prealpha
v0.0.8-prealpha
v0.0.7-prealpha
v0.0.6-prealpha
v0.0.5-prealpha
v0.0.4-prealpha
v0.0.3-prealpha
v0.0.2-prealpha
v0.0.1-prealpha
Labels
Clear labels
ad-hoc
api
bug
ci-cd
content
dependencies
enhancement
frontend
in-progress
jellyfin
parked
priority: high
priority: low
priority: medium
review
security
One-off / ad-hoc work not tracked by a dedicated issue
REST API / HTTP endpoints
Something isn't working
Build, test, deploy pipeline
Channel content / schedules / playlists
Dependency updates (Renovate)
New feature or improvement
ChicoryTV React SPA frontend
Claimed by an active session — do not pick up
Jellyfin tuner / IPTV integration
Excluded from automatic queue pickup; work only when explicitly selected
Adversarial review finding
Security / vulnerability fix
No labels
priority: medium
Milestone
No items
No Milestone
Projects
Clear projects
No projects
No Assignees
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: timothy/ersatztv#812
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Spawned by #784, which generalized
docs.no-session-narrativeand reported the existing leak rather than remediating it. This issue is the remediation decision.What the sweep found (2026-08-21, at
706674272)Two independent passes over the tracked docs corpus, excluding
docs/decisions/**(exempt by the rule):scripts/check-doc-narrative.py --all, the advisory string predicate: 10 sitesUnion 21 sites across 7 files; each pass missed sites the other caught, which is the measured case for why the detector is advisory. Every line below was re-read in the file, not taken from a summary.
docs/ci-cd.mddocs/remote-state-inventory.mddocs/guard-inventory.mddocs/blazor-route-parity.mddocs/defect-shapes-773.mddocs/superpowers/plans/2026-07-20-unified-logo-bug.mddocs/superpowers/specs/2026-07-20-unified-logo-bug-design.mdWhy this is not a mechanical sweep
Several of these are load-bearing and must stay. The
docs/remote-state-inventory.mdcluster is mostly of the form "this row was graded down by cold review, and the earlier wording was circular" — that tells the next reader not to regrade it back, so it passes the who-benefits test and is a finding, not narrative. Thedocs/guard-inventory.mdcluster is similar: the summary counts were wrong in the first draft because a hand-maintained summary of a table is a second copy of it, which is the argument the table exists to make.Others are plainly narrative: an earlier draft of a paragraph having been wrong, where only the corrected claim matters to a reader.
So this is 21 individual judgements, not a regex. And this repo has the inverse failure on record — #542, where a pruned narrative turned out to be the only copy — which is why #784 deliberately did not batch them behind the rule that authorized them.
The two
docs/superpowers/**hits are a separate question: plans and specs are session artifacts by nature, and it is arguable the rule should not reach them at all. Decide that before touching them.Done-when
docs/superpowers/**is in scope fordocs.no-session-narrative, recorded in the record if it changes the rulescripts/check-doc-narrative.py --allre-run and its remaining output explained rather than driven to zero — the surviving hits should be the carved-out onesClaiming this (Claude Code session, worktree
main-2).Pre-claim checks per
process.parallel-session-claim, all clear: no open PR references #812,git ls-remote --heads origin '*812*'returns nothing, no prior comments, andorigin/mainre-fetched atb6b3520bd.Bundle scan (all three axes): no milestone, no cross-references, and the only label is
priority: medium. The nearest sibling considered was #869 (the other open docs-prose remediation sweep, also spawned by a predecessor that deliberately deferred). Rejecting that bundle: #869 needs live Gitea-source re-probes atv1.27.1and edits.gitea/workflows/review-verdict.yml+scripts/, which is a different verification story and would cost this issue its docs-only exemption from the review-verdict gate. #812 goes alone.Working it as the issue frames it — 21 individual classifications, each re-read in the file, not a regex sweep, with the
docs/superpowers/**scope question decided before those two sites are touched.Claim RETRACTED — duplicate pickup. This claim collided with a parallel session already working #812 on branch
fix/812-session-narrative-remediation; that session owns the issue and thein-progresslabel. Disregard this comment as a claim.My findings were handed to that session directly rather than duplicated here. The one point worth recording publicly, because it changes the shape of the task: the issue's 21 sites are dated at
706674272(2026-08-21) and the docs have churned ~4,000 lines since, so the line numbers have drifted, one of the 21 (docs/guard-inventory.md:45) no longer exists — its section was rewritten by #872 when the guard population became derived — and a re-derived reading pass at HEAD finds sites the list does not contain. The population has to be re-derived rather than inherited from the table.Closing record
Outcome: Shipped in PR #882, merged as
736649b3b. 64 dispositions across the tracked docs corpus — 50 sites edited (53 clauses), 13 kept as carve-outs, 1 already remediated upstream by #872. The per-site manifest is in the PR body.scripts/check-doc-narrative.py --allgoes 9 → 1; the survivor is a carve-out (a rejected watermark-lookup design kept with the concrete harm that killed it). Deferred: the same class outside the docs corpus (#876), and the mechanism that let four false claims into this PR (#881).Root cause: #784 generalized the rule and reported the leak rather than remediating it, so the leak was the backlog. The interesting cause is the second one: the issue's list was not the population. Its table was dated at
706674272; 1924 lines had landed in the corpus since, every line number had drifted, one of the 21 sites no longer existed (#872 had rewritten it, keeping the why and dropping the autobiography — the correct outcome, reached independently), four sites postdated the sweep, and more were missed by both of #784's passes. Auditing exactly the 21 named lines would have repeated the defect the issue is about.Decisions/conventions changed:
docs.no-session-narrative—rule:,mechanics:,signals:and body (Decisions-Edit: yes). Two substantive additions:docs/superpowers/**is reached by the rule and the detector's population is unchanged, with the rejected exemption recorded so it is not re-proposed; and the exemption test's limits are stated rather than overstated. Alsorelease.verdict-vocabulary-shared— a scope claim that said #812 was still undecided.Reusable knowledge:
--diff-mode check never sees a frozen file. The case for exemptingdocs/superpowers/**was that its 35 files are frozen — but--diffscans lines a PR adds, and a frozen file adds none. The exemption would have bought nothing where the check runs, while suppressing exactly the case where the remedy (move it to the commit message) is still available. Exempting a path costs a detector its only reach."an earlier \n draft"wrapped across a newline is invisible to both. Re-running the sweep over whitespace-joined text found sites six rounds of grepping had missed.hits(out) == set()passes when the scan examined nothing. A scanned-zero run is indistinguishable from a working exemption. A non-exempt control asserted HIT in the same invocation is what separates them — and three successive finite controls (depth-1, depth-3, minimal twins) were each defeated by the next round's mutant. What closed it was a closed-form proof rather than a fourth fixture: compare the predicate against an independent restatement of the rule across every tracked path, plus a cross product of the dimensions a population rule keys on. The real corpus alone is insufficient, measured: no scanned path carries more than three slashes, so acount("/") <= 3cap is a no-op against it and would ship latent.continueat either call site re-exempts a path with the predicate untouched — measured to drop--allfrom 66 files to 31 with the oracle still green. Named as an open residual, with one fixture witnessing the specific re-exemption this change argues against.titleI passed and dropped the entire body — rationale andDecisions-Edit: yestrailer both. On a change whose own rule says the path to the end state goes in the commit message, the merge deleted the commit message. No CI consequence (pr-checks.ymltriggers onpull_request:only, so the decisions guard never re-ran on the push), but the rationale survives only because it is also in the PR body and here. Passmessageexplicitly on a squash, or merge-commit.fixes #Non its own line voids the entire trailer block.Decisions-Edit: yesparsed as nothing;decisions_validateexited 1 on two records. Put the closing keyword in its own paragraph.Verification:
scripts/tests/1230 passed / 2 skipped / 0 failed.--all66 files, 1 warning;--diff origin/main10 files, 0 warnings.decisions_validate --base origin/mainOK; catalog in sync; frontmatter parses under PyYAML.ruff check+format --checkclean. Ten independent cold review rounds across two model families, every one worktree-isolated and review-only; the final round on the merging head found no BLOCKER/HIGH/MEDIUM. Two separate rounds audited every deleted line for over-stripping and found no loss existing nowhere else — the risk that mattered most here, given #542.Deferred: #876 (the rule reaches
.claude/hooks and code comments; nothing has ever swept them) and #881 (mutation claims written as prose sit outsidetesting.mutation-claims-are-executed, whose population isguard-inventory.mdrows — the #773 shape, and the mechanism behind four false claims in this PR, each caught by a separate round).Docs updated:
docs/decisions/records/docs/no-session-narrative.md,docs/decisions/README.md(regenerated),docs/decisions/records/release/verdict-vocabulary-shared.md, plus the remediated corpus:docs/ci-cd.md,docs/remote-state-inventory.md,docs/guard-inventory.md,docs/blazor-route-parity.md,docs/defect-shapes-773.md, and five files underdocs/superpowers/.