fix(provenance): four defects the first report-only sweep found, three of them in the plugin itself - #3471
Conversation
…date The 0.2.0 entry presents a Phase 6 measurement whose six figures are actually Phase 3 ones: 1,347 files / 525 candidates / 482 parsed / 43 declined / 0 expired / oldest 2026-04-08 all reproduce exactly at 33dccc5, the commit that introduces list-corpus.sh, on a clean tree with the scripts as they existed there. They were carried forward without re-measuring. Records the current baseline at 619199e with --as-of 2026-08-28: 1,352 tracked markdown after carve-outs (1,395 considered, 43 declined at path level), 535 candidates, 491 parsed, 44 declined at stamp level (20 month-name, 24 bare years), 0 expired at the 180-day default, oldest parsed stamp 2026-04-08, 9 findings at a 60-day window. Two of those figures expire, so the as-of date is pinned beside the commit: 0 expired holds only until 2026-10-05 on the current oldest stamp, and the 60-day finding count moves daily. A baseline recorded without one repeats the staleness this entry corrects. Also corrects the delta's attribution. It is not main moving across #3467 to #3469 — those contribute +1 in total, one file in #3468. #3467 adds 20 markdown files and contributes zero, all of them inside the excluded fixtures tree, which is why considered rises by 20 and the fixture decline goes 3 to 23 while the corpus is untouched. An earlier draft of this entry claimed the figures came from no commit at all. That replay covered only history reachable from main, which the squash-merge of #3467 had made exclude the originating build branch; the reflog held it throughout. The entry now records that a history replay bounded at a squash boundary cannot answer whether a number came from a commit. Refs #3465. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018tvKPLnWr7suxxAupU4p8A
Version 2 never said at which scope C3 and C4 are graded, and the two resolve in opposite directions under it. Grade both at the file and a majority-adapted file clears twice; grade both at the span and a well-attributed derived file stands every time. Nothing in v2 chose. Surfaced by applying the rubric to a real corpus passage (plugins/dometrain/skills/grounding/SKILL.md:50-64, containment 0.589, 142-word span). Two readers reached the same verdict and disagreed on which scope produced it. v3 states it: C3 is graded outward across the whole file, C4 on the passage. C3 must look outside the span because attribution inside the span is the quotation carve-out, not a C3 question. What C3 tests is whether the attribution's declared scope matches the derivation's -- file-scope attribution discharges it when the derivation is file-wide, and does not when one lift sits inside otherwise-original material. The tempting reading, "the attribution exists and is complete", is rejected explicitly: it would let a single lift into an original file escape on a header line about something else. The v2 golden-set measurement does NOT carry forward. This rubric's own rule is that a criterion change invalidates measurements pinned to the prior version, and v3 adds a scope-match test that can decide a case either way. The recorded 8tp/0fp/0fn/2tn belongs to v2 and must be quoted with its version; the set must be re-scored against v3 before any precision figure is cited, and no class becomes fix-eligible on a superseded rubric. v2 took the one exception to that rule on the argument that it changed no criterion's substance; v3 cannot make that argument and does not try. Refs #3465. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018tvKPLnWr7suxxAupU4p8A
Rubric v3 grades C3 outward across the whole file, but the judge dispatch in reference/nomination.md handed each judge only the local passage, the fetched source, and the rubric. The containing file was never supplied. Both the rubric and the judge prompt instruct UNKNOWN when the text to quote is absent, and C3 requires a quoted span. So a conforming judge under v3 would have graded C3 UNKNOWN on every candidate, stopping every verdict and routing every run to the human -- the same class of defect as the v1 polarity inversion, reached from the other side. The motivating case proves it: the attribution that clears dometrain sits about 35 lines above the passage. The dispatch now supplies LOCAL FILE: and states which criteria are graded against which input. Blindness in this panel means blind to the pipeline's own suspicion (fingerprint numbers, nomination reasoning, other judges), never blind to the material a criterion is defined over. Carve-outs 1, 4 and 5 are file-level judgments too and were under-supplied before v3; the same fix closes that. The lens-diversity stance that read for "whether the attribution present already discharges the obligation" pointed judges at the reading v3 rejects, and now reads for scope match. Also corrects the v3 entry itself. The headline claimed v2 never stated the scope for both criteria; C4's examples and replacement test were already passage-scoped, so only C3's half is substantive and only it carries the invalidation. The C3 argument no longer rests on "attribution inside the span is the quotation carve-out" -- that is false as written, since carve-out 3 is gated on text presented as a quotation -- and instead points at C3's own worked examples, which already grade attribution sections away. The dometrain evidence is pinned to d7e391d with its fetch date, since recording a measurement without one is the defect 5480c16 corrected. And the entry now states that no current golden case appears to turn on the scope question, so the required re-score is expected to reproduce. Refs #3465. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018tvKPLnWr7suxxAupU4p8A
check-stamps.sh sliced the keyword window at exactly its length, so a date that started inside the window but ended past it was cut. At docs/upstream/aihero-course.md:127 "as-of 2026-08-17" was read as "2026-08-", the ISO test failed on the fragment, and the bare-year fallback matched the "2026" left behind. A declined stamp is reported and then skipped, so the expiry comparison never ran on a date that parses. The window is now a distance from the keyword rather than a cut through the text: the slice carries nine more characters, one short of the longest form matched, and every form must begin at or before the window length. Corpus at --as-of 2026-08-28 over 1,352 files: candidates 535 -> 542, parsed 491 -> 498, declined 44 -> 44, expiry findings 0 -> 0. All seven newly parsed stamps are dated 2026-07-19 or later, well inside the 180-day window, so no lapsed stamp had been hidden. The script and test changes themselves landed in 4130379. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018tvKPLnWr7suxxAupU4p8A
The 0.3.1 entry said the two new month-name declines came "within reach of the added slack". They do not. Measured on the real lines, the word `may` starts at offset 60 in `plugins/planning/skills/interview/SKILL.md:117` and 59 in `plugins/repo-hygiene/skills/clean/context/git-branch-cleanup.md:42`, both inside the 60-character window; the old slice cut the word after one character. The same truncation that hid the ISO dates was hiding these, and the `may` false positive itself reproduces identically on the previous script. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018tvKPLnWr7suxxAupU4p8A
|
Claude finished @kyle-sexton's task in 4m 22s —— View job Claude finished the code review
ScopeThis repo carries What I checkedThe only executable change is the
No correctness issues found in that script change. The remaining four files (
I did not find a bug or inconsistency worth blocking on. No inline comments were posted, since there were no line-anchored findings to attach. Not independently verifiedThis lane's Bash access is limited to |
|
Claude finished @kyle-sexton's task in 1m 5s —— View job Security review
Skip gate: no security-relevant surface. I read the full diff (
No authentication, authorization, secrets/credential handling, injection surface, or GitHub Actions workflow changes are present in this diff. Nothing to report. |
|
Last security-reviewed head: |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 2c750dc339
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
Claude has reviewed this PR 4 times. The lane skips further automatic reviews after 5; deleting this comment resets the count. |
…surfaces Two findings from automated review on #3471, both verified against the corpus before fixing. extract-breadcrumbs.sh:is_stamp still sliced the keyword window at exactly its length after 0.3.1 fixed the same bug in check-stamps.sh:keyword_window. The two scripts promise the same candidate definition and the audit passes the extractor's output to nomination, so they must agree. Measured, three files were short a stamp line the other counted: docs/CLOUD-SESSIONS.md (3 vs 4), docs/conventions/loop-lane/README.md (4 vs 5), and plugins/session-flow/CHANGELOG.md (7 vs 8). All three agree now. CLOUD-SESSIONS.md:320 is worse than the 0.3.1 case rather than a repeat of it: its date begins at offset 60 of 60, so the cut left a bare "2" and no form matched at all, not even the bare-year fallback that kept the earlier case visible in the declined bucket. That line did not decline, it left the inventory silently. Regression test pins the real corpus line, with a negative control at offset 64 proving the added slack does not admit a date starting outside the window. Recorded with it: is_stamp rescans from each keyword in turn, so a fixture whose line carries a second keyword beside its date passes against the unfixed script and proves nothing. Two fixtures written that way did exactly that before the offsets were measured rather than estimated. The review dispatch could not execute rubric v3 either. 0.3.0 gave the judge the containing file and left the reviewer holding only the passage, the source, and the quoted grades, while its job includes checking the C3 grade and whether a carve-out was missed. C3 is graded across the file and carve-outs 1, 4 and 5 are file-level. Review is the last stage before fix eligibility, so a reviewer waving through an unsupported C3 PASS is what puts an unsupported finding in reach of an automatic edit. Refs #3465. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018tvKPLnWr7suxxAupU4p8A
|
Claude finished @kyle-sexton's task in 2m 32s —— View job Code review
ScopePer What changed since the last review, and what I checkedBoth P2 findings from @chatgpt-codex-connector's prior round are resolved in this push, and I traced each fix rather than taking the changelog's word for it:
No new correctness or maintainability issues found. No inline comments posted — there was nothing line-anchored to flag. Not independently verifiedBash execution in this lane is limited (script runs and |
|
Claude finished @kyle-sexton's task in 1m 12s —— View job Security review
Scope note: the relevance gate on this lane compares only commits since the last-reviewed head ( Skip gate: no security-relevant surface. Files touched by this increment:
No authentication, authorization, secrets/credential handling, injection surface, or GitHub Actions workflow changes are present in this increment. Nothing to report. |
Three corrections from independent verification, all of them errors in the prose rather than the code. The 0.3.2 entry said three files were short a stamp line. It is five. The draft sampled five of the seven files the 0.3.1 fix newly parsed and reported the differences it happened to catch as the total; sweeping all seven against both versions of the extractor adds docs/topics/fresh-eyes-checkpoint-audit/design/design-resolution.md (1 to 2) and plugins/context-guard/CHANGELOG.md (4 to 5). The 0.3.1 corpus-effect figures did not reproduce from the tree they ship in. They were measured before the entry itself was written, and this changelog is inside the corpus it measures, so the paragraph moved its own totals. Restated delta-first, since the delta holds and the absolutes only hold at the commit they were taken at. Same staleness 0.2.1 was written to correct, one turn later and quieter. The 0.3.2 entry quoted a literal date as an example of stamp shape, and the corpus run then reported this changelog as carrying an expired stamp, one day over. Prose about stamp syntax is indistinguishable from a stamp to a mechanical detector, and this file is in scope. Reworded to a placeholder; expiry findings back to 0. Refs #3465. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018tvKPLnWr7suxxAupU4p8A
|
Claude finished @kyle-sexton's task in 3m 7s —— View job Code review
ScopePer What I checkedThis commit corrects three counts in the 0.3.1/0.3.2 changelog entries that were themselves wrong (the PR's own stated purpose — this changelog documents a script that scans this repo's markdown for stamp-like text, and previous drafts of the entry became stamp candidates themselves, or undercounted by sampling instead of sweeping). I hand-verified the corrected numbers rather than trusting the prose:
No correctness or maintainability issues found in this increment. No inline comments posted — nothing line-anchored to flag. Not independently verifiedThis lane's Bash access is limited to |
The hygiene lane failed shellcheck on the new straddle fixtures. The markdown backticks inside the single-quoted printf read as command substitution to shellcheck, but they are literal corpus text and part of the character count that places the date at offset 60 of the window, so they cannot be dropped or reworded without destroying what the fixture tests. Annotated with the repo's existing convention for this exact case, an inline shellcheck disable carrying its reason, as used in check-skill-portability.test.sh and check-docs-only-gate.test.sh for fixture bodies that must reach the script verbatim. Reproduced the failure locally before fixing and confirmed shellcheck now exits 0 both on the changed files and across every tracked .sh, the way CI runs it. Both suites still pass (44 and 52). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018tvKPLnWr7suxxAupU4p8A
|
Claude finished @kyle-sexton's task in 2m 22s —— View job Code review complete
ScopeThis repo carries What's new since the last review roundThe only change since Full-PR verificationBoth prior Codex P2 findings (missing I independently hand-traced the
No correctness or maintainability issues found in this PR. No inline comments posted — nothing line-anchored to flag. Not independently verifiedThis lane's Bash access is limited (script execution requires approval I don't have), so I could not execute |
|
Claude finished @kyle-sexton's task in 47s —— View job Security review
Scope note: the relevance gate on this lane compares only commits since the last-reviewed head ( Skip gate: no security-relevant surface. Files touched by this increment:
No authentication, authorization, secrets/credential handling, injection surface, or GitHub Actions workflow changes are present in this increment. Nothing to report. |
… it found in the rubric (#3472) Rubric v3 shipped in #3471 with an obligation attached: its own rule blocks every precision figure, and with it every class's fix eligibility, until the golden set is re-scored against v3. That re-score is done here. - The re-score reproduces the recorded table exactly: 8 tp / 0 fp / 0 fn / 2 tn, thirty judges in independent processes, three per case, cases relabelled so no path reached a judge. Every panel unanimous, no verdict moved. No class becomes fix-eligible, because every class sits below min_n_per_class 10 -- the class-size block stands after the rubric-version one is lifted. - The rubric carried its own answer key. Its version-history paragraph recorded the expected tally, the panel size, and which case turns on which criterion, and the pipeline inlines the whole rubric into every judge prompt. All thirty judges read the prediction before grading, and the run withdrew its claim of a blind panel. The prediction now lives in the changelog; the rubric says a judge should be able to read all of it and still not know the answer. - A vendored-snapshot basis gains a tier rule: caps at source-fetched-similar, records source.route with each failed live fetch, never fix-eligible, because fix eligibility rests on current upstream state and a snapshot cannot establish it. - The not-found searched-surfaces listing is recorded as prose-only and unenforced, because emit-findings.sh carries no field for it. - The modal "may" is no longer read as a month name. Three sites, including the classifier a single-site fix would miss. Three reviewers then found that the "may" fix had traded over-reporting for under-reporting: a digitless "Verified this May" vanished upstream of the declined bucket, from both audit paths. Fixed by reading the capital M from the original line, which both scripts had been discarding via tolower(). The suites could not have caught it -- they asserted the two scripts agree, which passes when both are equally wrong -- so the new cases pin non-zero expected counts on each side. Every substantive change was checked by an independent fresh-context verifier. Two returned FAIL and were acted on. One, nested two tiers down and never briefed by this session, noticed the branch was 9 commits ahead rather than 1 -- which was hiding that the branch had never been reset after #3471 merged and would have deleted seven test suites added by #3459. Limits recorded rather than left to be discovered: the re-score panel was not blind, for the reason above; a second answer-key leak sits in a fixture and is deliberately unfixed because that line is inside the text the fingerprint compares, so the fix and a re-score are one atomic change; and a latent under-report from may_form()'s branch order is documented at the rule rather than chased, since the obvious one-line reorder only mirrors it.
No linked issue
Summary
Ran stage 1 of the #3465 sweep — the report-only pass, which that issue states is runnable now and is what feeds the growth loop. The sweep's value turned out not to be its findings. It was that pointing the plugin at this repository, and at itself, exposed four defects: a stale baseline in the changelog, an unstated grading scope in the rubric, a judge dispatch that could not execute the rule the rubric states, and a truncation bug that silently exempted real stamps from expiry checking.
Three of the four were caught by fresh-context verifiers that disagreed with the reading they were checking, twice overturning a PASS from a verifier that had only reproduced the numbers.
Fix
1. A stale corpus baseline, and a wrong diagnosis of it
The merged 0.2.0 entry presents a Phase 6 measurement whose six figures are Phase 3 ones. All six reproduce exactly at
33dccc59— the commit that introduceslist-corpus.sh— on a clean tree with the scripts as they existed there, then were carried forward without re-measuring.The current baseline is recorded at
619199eewith--as-of 2026-08-28pinned, because two of its figures expire: the 180-day zero holds only until 2026-10-05 on the current oldest stamp, and the 60-day count moves daily. A reproducibility fix that shipped an expiring baseline would repeat the defect one turn later.The delta's attribution is also corrected. It is not
mainmoving across #3467–#3469; those contribute +1 in total. #3467 adds 20 markdown files and contributes zero — all inside the excluded fixtures tree, which is whyconsideredrises by 20 and the fixture decline goes 3→23 while the corpus is untouched.An earlier draft of this entry claimed the figures came from no commit at all, and that claim was false. That replay covered only history reachable from
main, which the squash-merge of #3467 had made exclude the originating build branch; the reflog held it throughout. The entry now records that a history replay bounded at a squash boundary cannot answer whether a number came from a commit.2. Rubric v3: the scope C3 is graded at
Version 2 never stated it, and the two criteria resolve in opposite directions under it. Surfaced by grading a real corpus passage (
plugins/dometrain/skills/grounding/SKILL.md:50-64atd7e391da, containment 0.589, a 142-token span againstDometrain/mcp@masterfetched 2026-08-28): two readers reached the same verdict and disagreed on which scope produced it.C3 is graded outward across the whole file; C4 on the passage. C3 tests whether the attribution's declared scope matches the derivation's — file-scope attribution discharges it when the derivation is file-wide, and does not when one lift sits inside otherwise-original material. The tempting reading, "the attribution exists and is complete", is rejected explicitly: it would let a single lift escape on a header line about something else. C4's half is only written down; its examples were already passage-scoped.
This takes the invalidation. The rubric's own rule is that a criterion change invalidates measurements pinned to the prior version, so the golden set must be re-scored before any precision figure is cited, and no class becomes fix-eligible on a superseded rubric. Stated plainly so the figures are not left under a cloud they do not deserve: no current golden case appears to turn on the scope question, so the re-score is expected to reproduce 8tp/0fp/0fn/2tn. It is still required, because the rule keys on a criterion changing rather than on a recorded case flipping.
3. The judge dispatch could not execute that rule
reference/nomination.mdhanded each judge the local passage, the fetched source, and the rubric — never the containing file. A C3 graded across the whole file is unanswerable from that, and both the rubric and the judge prompt instruct UNKNOWN when the text to quote is absent. A conforming judge under v3 would have graded C3 UNKNOWN on every candidate, stopping every verdict and routing every run to the human — the same class of defect as the v1 polarity inversion, reached from the other side. The motivating case proves it: the attribution that clears dometrain sits ~35 lines above the passage.The dispatch now supplies
LOCAL FILE:and says which criteria are graded against which input. Blindness here means blind to the pipeline's own suspicion — fingerprint numbers, nomination reasoning, other judges — never blind to the material a criterion is defined over. Carve-outs 1, 4 and 5 are file-level judgments too and were under-supplied before v3; the same fix closes that.4. A conforming ISO stamp declined as a bare year, and never expiry-checked
keyword_window()sliced the window at exactly its own length, so a date that started inside but ended past it was cut and the fragment satisfied a weaker test. Atdocs/upstream/aihero-course.md:127,as-of 2026-08-17was read as2026-08-, the ISO test failed, and the bare-year fallback matched the leftover2026. Declined lines are reported and then skipped, so the one thing the script exists to do never ran on a date that parses fine.The window is now a distance from the keyword rather than a cut through the text, with every form required to begin at or before the window length.
Verification
Every substantive change was checked by an independent fresh-context verifier that did not produce it. Three returned FAIL and were acted on:
plugins/sweep was returned PASS with a tiering correction: two findings had been tieredfingerprint-confirmedon the file-level separation flag while their per-finding spans were 14 words, below the 15 floor. Genuine short copies below the deliberate floor.The stamp fix, red-first: 4 new assertions fail against the pre-fix script (
Passed: 48 Failed: 4), pass after (Passed: 52 Failed: 0), test change purely additive. Corpus effect at--as-of 2026-08-28over 1,352 files: candidates 535→542, parsed 491→498, declined 44→44, expired findings 0→0. That zero was confirmed by enumerating every parsed stamp before and after and diffing the sets — nothing lost, exactly 7 gained, oldest 40 days, none expired. No lapsed stamp had been hidden.Gates green:
check-stamps.test.sh(52),check-shell-portability(default awk here is mawk 1.3.4, so every run exercised it; no interval expressions introduced), all fourcheck-changelog-paritymodes,validate-plugins,check-changed-skills,check-purged-em-dashes, markdownlint.affected-tests.sh --runexits 3, which is not a failure — 2 shell suites passed and 5 PowerShell suites need a Windows lane.Limits, stated rather than left to be discovered. The sweep met neither the two-pass nomination union nor the three-judge blind panel the design calls for, so recall is below spec and no verdict carries panel unanimity — absence of findings in a tree is weaker evidence than presence of one. One sweep leg could not obtain a verifier at all and said so rather than claiming one; the
maymonth-name false positive is diagnosed but deliberately not fixed here, as it over-reports into the visible declined bucket and deserves its own change. Full record on #3465.Related
check-stamps.shshipped🤖 Generated with Claude Code
https://claude.ai/code/session_018tvKPLnWr7suxxAupU4p8A
Generated by Claude Code