docs: apply the unapplied extract-ssot deduplication lane, and close its declared recall gap - #3460
Conversation
…its declared recall gap The repo-wide docs-hygiene sweep (#3362) ran eight lanes and graduated its remainder into docs/specs/docs-hygiene-sweep-unapplied-remediations.md. #3380 closed out L2, L4, L5, L6 and L7 and left L3, the extract-ssot deduplication lane, untouched. An adversarial verifier re-tested that premise cluster by cluster before anything was edited: all 13 remediated clusters were still open, no later commit had applied them, and no open pull request covered them. That lane also declared its own recall limit and named the fix. This carries it out: a shingled n-gram pass over whitespace-normalized markdown with line breaks removed, plus the reading-driven semantic pass it had no subagent tool to attempt. Against a control paragraph re-wrapped at three widths, the old line-anchored method shares zero lines and the new pass recovers 100%. Four of the findings are factual defects rather than prose drift: - A fallback that could never fire, at 26 sites. Piping a probe into head before || makes the fallback unreachable, so a failed git status rendered an empty string under a label that reads as a clean tree. Proven by execution. - An inline floor the loop-lane convention requires byte-identical, which was three distinct texts. The drift traces to two de-slop shards, one of which also introduced a comma splice. - A dropped-allow-rule roster missing the Monitor class upstream added in v2.1.236, so a dropped Monitor grant was reported under none of the classes. - A title taxonomy credited to a living author that the owner file records as unaudited and not his, including in a shipped prompt template. docs/PLUGIN-PHILOSOPHY.md gains the runtime-grounded clause 20 setup skills were asserting with nothing to point at, and four Convention registry rows: three owner docs that declare themselves owners and were absent from the registry that indexes them, plus the precompute convention. The full roster, the twelve contradictions the semantic pass surfaced, and the measured recall limits of the detector itself are recorded in docs/specs/extract-ssot-sweep-2026-08-28.md so the remainder is resumable. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UmrZGdp1dgbZuPCy7tcRJo
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 723559280c
ℹ️ 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 encountered an error after 1s —— View job I'll analyze this and get back to you. |
|
Warning Automated review did not complete — this is an infrastructure failure, not a review verdict. Treat any Claude comment on this PR (including a placeholder like "I'll analyze this and get back to you") as incomplete, not "no findings."
Re-run the job, or workflow_dispatch this workflow with the PR number, to retry the review. A new push re-triggers this lane only if the caller's |
|
Claude encountered an error after 0s —— View job I'll analyze this and get back to you. |
|
Warning Automated security review did not complete — this is an infrastructure failure, not a review verdict. Treat any Claude comment on this PR (including a placeholder like "I'll analyze this and get back to you") as incomplete, not "no findings."
The check is green on purpose, and it is not evidence. It certifies that a security pass ran, and this one did not complete — but the cause is outside this PR's control, so merging is deliberately left unblocked rather than locking every merge for the length of the outage. Nothing was reviewed at this head. Where this check is required, it is satisfied without that evidence; a human should review security-sensitive changes here before merging. Re-run the job, or workflow_dispatch this workflow with the PR number, to retry the review (ci-workflows#227). A new push also retries it only if the caller's Re-running does NOT help for every class:
|
…t introduced it Codex review P2 on #3460. The new Monitor branch classified every Monitor allow rule as dropped unconditionally, but the category arrived in Claude Code v2.1.236 and the reference's own source note records that earlier versions leave Monitor rules in effect. The plugin declares no minimum Claude Code version, so on an older install the verdict was inverted in the direction that matters: it told an operator a live grant was already suspended. The script cannot read the running version, so it now emits a DIFF-NOTE naming the v2.1.236 bound whenever it classifies a Monitor rule, following the same idiom the classifyAllShell branch already uses for its own version requirement. The reference documents the note, and two regression tests pin it present when a Monitor rule is classified and absent when none is. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UmrZGdp1dgbZuPCy7tcRJo
Standing down on the automated review lanes, with the evidence they would have producedBoth Claude lanes (
Per the workflow's own notice this is the class that "needs an operator; retrying will not clear it," so I have spent my one re-run and am not retrying further. This is an account-wide budget outage, not a property of this change. Nothing was reviewed by those lanes at either head. Because the notice also says a human should review security-sensitive changes before merging, here is the verification I ran in their place, so that review starts from evidence rather than from scratch. Executable surface of this PRThree files, all in The 26 precompute injections. Every added injection, deduplicated, is one of exactly two shapes: Audited across all added injection lines:
The one
Tests. Net +7 assertions in What a human should still look atThe lanes never ran, so this is my verification and not an independent one. The narrowest thing worth a second pair of eyes is the Generated by Claude Code |
|
Claude encountered an error after 0s —— View job I'll analyze this and get back to you. |
|
Claude encountered an error after 0s —— View job I'll analyze this and get back to you. |
An adversarial review of the change set found the mechanical work sound and the record wrong in eight places. A sweep whose subject is false claims in versioned surfaces cannot ship false claims in its own versioned surfaces. Changelog entries that described a different change than the diff made: - ai-slop, mutation-testing and testing said all five producer preambles are now byte-identical. Four are; testing:audit keeps a numbered-step form by design and regained the three clauses it had dropped. Each entry now says which. - claude-ops, knowledge and rate-limit-guard each carried the probe-don't-recite entry, which belongs to the twenty setup skills that actually contained that directive. These three did not. Each now reports the defect its own diff repaired: a comma splice, a broken list item, an orphaned clause. - verification 0.5.8 called its fallback unreachable. That site is the fleet's one uncapped injection, so its fallback ran and emitted an empty string, which under the label is still indistinguishable from a clean tree. Twenty-five sites had the unreachable shape; this one had a reachable but uninformative one. - songwriting 1.4.15 reported the skill-wide clusters and omitted the suno files and ledger row S20 that the same commit moved, which the ledger's own rule requires be reported here. detector-findings, the convention this sweep also edited: - The new audit-noise adopter row called itself the third producer to join the crosswalk; the changelog entry directly above it correctly says fifth. It said the detector marks six shapes with five declined; lib/noise-shapes.sh marks eight and detect.sh classifies negation as a ninth, so eight are declined. - The section heading "Three emitters" outlived its count at six. A heading that names a number decays every time the table it heads grows. Anti-regrowth lines restored in songwriting/suno context files, whose inline "deliberately retired" note an earlier release records as the defense against the advice returning. Both prompts/loops rate-limit floor carriers brought to the canonical wording; both are blockquoted restatements outside the byte-audited block, and the spec now says so rather than implying seven carriers are under the identity contract. The resumable spec gains the four filtered-probe sites deliberately left unnormalized and why, and declares that every figure in its recall-limits section is unreproducible because the Pass A detector was a session tool that was deliberately not committed. loop-lane 9.0.1 records the §4 citation-form change, which had edited a versioned contract with no entry in its own changelog. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UmrZGdp1dgbZuPCy7tcRJo
…source A verifier read the detector-findings adopter row for docs-hygiene:audit-noise claim by claim against the scripts. Nine of eleven claims held with direct code evidence. Two did not, and chasing one of them found the defect underneath. The row said "selection is a mechanical per-sentence scan with no withholding verdict". True of detect.sh, false of the skill: a model judgment lane sits between the scan and the writer and may dismiss a candidate on the grounds SKILL.md enumerates. Every dismissal is counted in ## Surfaces as reason=judgment-lane-dismissal, so nothing vanishes silently, but the contract's "no evidence, no decline" rule requires a declining rule to state its decline evidence in its crosswalk row, and this one stated only two of its three classes. The sibling row for claude-config:audit-instructions gets this right. Both the adopter row and the crosswalk row now name the lane. rule-negation-hard-guardrail's Auto-applicable cell led with `n/a`, which is not one of the four lead forms this contract permits; its three sibling non-emitting rows all use "Not applicable — no row". Corrected, argument unchanged. The shape count was wrong at its source, not just in the row. SKILL.md, context/persist-findings.md and emit-findings.sh's header each said the scanner marks six shapes and declines five, naming citation, ghost-ref, preamble, enum-list and scope-meta. lib/noise-shapes.sh appends eight -- those five plus plan-reference, conversational-antecedent and ticket-pr-residue -- and detect.sh drives a ninth, negation, over an accumulated paragraph. Three shapes were added without the prose following, so a reader of ## Surfaces would find declined counts for shapes the skill's own docs say do not exist. All three now say nine and eight, name all eight, and point at audit_noise_detect_shapes_into as the count's source rather than restating it as a fact. No behavior changes; emit-findings 35/35 and detect 199/199 still pass. The adopter row's lead also said nothing distinguishing -- three other adopters are equally read-only and reach the relay. Its real novelty is that it is the first producer selecting per sentence across soft-wrapped lines rather than per line, which is what the lead now says. Separately: a fifth site was checked against the unreachable-fallback shape and cleared by execution. docs-hygiene:audit-derivability captures the probe into a variable first, so the probe's own exit status heads an && list and the || fires on failure. That is the form the four deliberately-skipped filtered probes should adopt, and the spec now records it as a candidate rather than a decision. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UmrZGdp1dgbZuPCy7tcRJo
…sot-extraction-jft8sf # Conflicts: # docs/conventions/detector-findings/CHANGELOG.md # plugins/ai-slop/CHANGELOG.md # plugins/claude-config/.claude-plugin/plugin.json # plugins/claude-config/CHANGELOG.md # plugins/claude-ops/CHANGELOG.md # plugins/code-tidying/CHANGELOG.md # plugins/codebase-health/CHANGELOG.md # plugins/discovery/CHANGELOG.md # plugins/disk-hygiene/.claude-plugin/plugin.json # plugins/disk-hygiene/CHANGELOG.md # plugins/docs-hygiene/CHANGELOG.md # plugins/firecrawl/CHANGELOG.md # plugins/implementation/CHANGELOG.md # plugins/improvement/CHANGELOG.md # plugins/knowledge/CHANGELOG.md # plugins/mutation-testing/.claude-plugin/plugin.json # plugins/mutation-testing/CHANGELOG.md # plugins/planning/.claude-plugin/plugin.json # plugins/planning/CHANGELOG.md # plugins/playbooks/CHANGELOG.md # plugins/playwright/CHANGELOG.md # plugins/rate-limit-guard/CHANGELOG.md # plugins/repo-hygiene/CHANGELOG.md # plugins/review/.claude-plugin/plugin.json # plugins/review/CHANGELOG.md # plugins/session-flow/.claude-plugin/plugin.json # plugins/session-flow/CHANGELOG.md # plugins/songwriting/CHANGELOG.md # plugins/source-control/.claude-plugin/plugin.json # plugins/source-control/CHANGELOG.md # plugins/testing/.claude-plugin/plugin.json # plugins/testing/CHANGELOG.md # plugins/toolchain/CHANGELOG.md # plugins/verification/.claude-plugin/plugin.json # plugins/verification/CHANGELOG.md # plugins/work-items/CHANGELOG.md
|
Claude encountered an error after 0s —— View job I'll analyze this and get back to you. |
|
Claude encountered an error after 0s —— View job I'll analyze this and get back to you. |
|
Both automated review lanes returned Standing in for the lanes, here is the verification, re-derived against Security surfaceDynamic-context injections. The diff adds exactly four distinct Every one invokes only
The absence of The one The grant did not widen. The old rule was Executable changesFour scripts are touched. Three are comment-only:
The one substantive change is
Repo gates on this headWhat this does not coverThis is the author verifying their own change, which is weaker than an independent lane and should Generated by Claude Code |
A third adversarial review re-derived the change set's own claims against the repository. Every finding is record-keeping; none is a code defect. All eight were re-verified here before fixing. "Three distinct texts" was two. Hashing the floor block across the five carriers at origin/main returns exactly two md5 values, not three. Both de-slop shards made the same two substitutions, so between them they produced one drifted form rather than two. Corrected in the spec and in the four plugin changelogs that repeated it, with "one of the two edits" disambiguated to name the substitution rather than the commit. The spec also overstated what loop-lane §6 requires. §6 binds the three consuming lane bodies, on the values, and names the reader contract as their provenance rather than as a carrier; it says nothing about extract-ssot's orchestrated-mode consumer. Under that literal scope the three lanes were already identical and the values never drifted at all -- what drifted was the surrounding prose. Extending identity to the two cited files is a choice this record makes, and now says so instead of claiming the convention compelled it. The check-skill.sh:414 cluster was declared closed while audit-noise's own emit-findings.sh still carried the citation. The earlier sweep grepped only *.md and missed the .sh site. Fixed there too, so the claim is now true. "Every site carries one wording" was 18 of 20. context-guard and rate-limit-guard state the same rule about their own scripts in their own words and were not touched. Corrected in all eight changelogs that carried the claim, naming the two holdouts and the separate pass they need. The four-preambles claim went stale when provenance landed with the merge. Its persist-findings.md is a fifth file and is not byte-identical -- "this plugin's read" rather than "this skill's", plus a resolution list the four do not carry. Re-worded in ai-slop, mutation-testing and testing. The docs-hygiene shape-count entry misdescribed SKILL.md on both sides. That file was internally inconsistent, not wrong: it says nine throughout and documents all three newer shapes, but one sentence said "the other five". The entry now says that, and SKILL.md's sentence points at audit_noise_detect_shapes_into instead of restating the number. Two smaller ones: the spec's Pass B row said 15 recorded contradictions where the file records 12, and one roster row quoted "This doc never restates it" against severity when that sentence is about the findings-file shape. The underlying observation held; the quote did not. Folded in from the same review: the encapsulation roster is a floor, with three more path-citations found on no roster; a fifth filtered probe in docs-hygiene:compress reaches the same ambiguity from the other direction; the 1,536 cap count moved from 14 to 16 during review, which is the decay rule rather than an exception to it; and review 0.26.13 now says its diff rewrote an allowed-tools grant, not only an injection. emit-findings 35/35, detect 199/199, shellcheck clean, all repo gates green. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UmrZGdp1dgbZuPCy7tcRJo
|
Claude encountered an error after 0s —— View job I'll analyze this and get back to you. |
|
Claude encountered an error after 0s —— View job I'll analyze this and get back to you. |
|
Addendum — the head moved from The verification above was performed at
So the security surface at Still the same caveat: this is the author checking their own work, and no independent lane has run Generated by Claude Code |
…closed gate that failed open (#3468) #3460 merged a whole-repo extract-ssot sweep and recorded, in its own resumable spec, three things it deliberately left open. This closes them, and closes a fourth the sweep did not know it had. A post-merge security audit found the defect #3460 fixed for git-status probes still live in the rest of its class. video-digest/SKILL.md states "STOP if the pre-computed context above shows MISSING for yt-dlp, ffmpeg, or ImageMagick. Cloud agents without the media toolchain fail closed." Its probes were `yt-dlp --version 2>/dev/null | head -1 || echo "MISSING ..."`. Because || binds to the pipeline and head exits 0 regardless, an absent yt-dlp rendered an empty string, never MISSING. The gate could not fire: a skill documenting itself as failing closed failed open. Fixed there and at course-digest, ai-slop:audit, provenance:audit, firecrawl:update and claude-ops:observability, using the guard-first shape firecrawl:firecrawl already shipped, so the fleet gains no new idiom. Proven by execution both ways. Also fixes a grant #3460 left behind: it rewrote the two prototype skills' injections to begin with { while their grant stayed the prefix rule Bash(git status:*), whose leading token no longer matches. The five filtered probes #3460 deferred are bound to their probes, each keeping its own filter, cap and label noun so "the filter matched nothing" and "git did not run" stay distinguishable. The ADR 0018 encapsulation floor is closed. The spec recorded two sites and declared the roster a floor; re-derivation across all of docs/** with five independent search shapes found 16 citations at 12 sites in 8 files, among them V-review-13 and V-review-14, open on the predecessor's roster since 2026-08-26 (34 to 32). The sharpest: docs/PLUGIN-PHILOSOPHY.md's Convention registry row, which #3460 itself added, was written in the exact bare unresolvable form ADR 0018 names as its real defect class. 23 citations are kept on a stated test and recorded as a judgment set so a later pass can overturn them. A fourth review round found five more false claims in the merged record, each re-derived before fixing. Two are structural: a cross-reference pointing at a section that names none of its four clusters, and a quote attributed to two files that neither makes -- the identical wrong-subject defect round three fixed, recurring one paragraph away. Refs #3460, refs #3362, refs #3380.
…l-proof (#3469) #3468 fixed five filtered-probe injections so a failed probe could be told apart from a filter that matched nothing. The form it shipped is wrong under set -o pipefail, and wrong in the more damaging direction: it asserts a failure that did not happen. probe >/dev/null 2>&1 && probe | filter | head -10 || echo "(git status unavailable)" The && list takes the pipeline's exit status. Without pipefail that is head's, which is 0, and the guard works. With pipefail it is the last non-zero status in the pipeline, which two ordinary situations produce: grep matching nothing, and git taking SIGPIPE when head closes the pipe at the cap. Either fires the failure token on a healthy probe. That is worse than the defect #3468 removed: the shape it replaced only ever said none, while this one positively asserts git status was unavailable when it ran fine. Both modes reproduced against the shipped lines, then shown fixed. On a healthy repository whose dirty files do not match, and again on one with 3,000 dirty files, the 0.21.23 form prints (git status unavailable) and this one does not. Outside a repository this one still prints it, which is the case the token is for. The filter pipeline now sits in a brace group closed by :, a command that cannot fail, so the || is reachable only by the guard short-circuiting. No $ expansion is introduced; the composed pre-compute block must stay verifiable to the worktree-isolation guard. audit-comment-residue's parity test anchors its porcelain capture past '&& {' so it evaluates the data run rather than an unterminated brace group, and its capture was checked non-empty so the assertion still bites. Open and undocumented: whether Claude Code executes ! injections under pipefail. Neither the precompute convention nor the permissions docs say. The brace-group form is correct under both settings and the shipped form under only one, so this is the fail-safe direction regardless. Refs #3468, refs #3460.
…idiom Two things land here: the merge resolution, and the exemption rework that had to happen on the merged tree. ## Merge resolution, per conflict Main reconciled the floor block independently while this branch was in review, so seven files conflicted. - reader-contract.md, orchestrated-mode.md, loop-lane-prompts.md and loop-lane-profile-claude-code-plugins.md: all four conflicts are the same one line, this branch's comma against main's colon in the staleness bullet. Main's side taken in all four. Main's #3460 hashed the carriers, found the same divergence this branch found, and repaired it first; its form is grammatical where the comma left a splice. Running this branch's gate against origin/main unmodified reports 6 consumers matching and no unregistered copy, so main is already consistent across the owner and all six registered consumers. There is no live drift event to reconcile here, and re-imposing a second correct answer would be a third uncoordinated edit to the block. - rate-limit-guard/CHANGELOG.md: main claimed 0.7.18. This branch's entry moves to 0.7.19 and drops its reconciliation bullet, which main's 0.7.18 now owns; what remains is the gate, the discovery scan, and the setup-probe change. That also removes the "other three copies matched byte for byte" line, which was wrong: two of those three are blockquoted templates matching under values-mode normalization, not byte for byte. - docs-hygiene/CHANGELOG.md: same, 0.21.22 taken, this branch's entry moves to 0.21.23 and now records only that the copy is gated rather than that it was reconciled here. - loop-lane/CHANGELOG.md: main added 9.0.1, this branch 9.1.0. Both kept, in order; no content overlap. Manifests follow: rate-limit-guard 0.7.19, docs-hygiene 0.21.23. ## The exemption idiom could silence a genuine consumer The in-file `loop-lane-floor-carrier-ok:` annotation matched anywhere on any line, in any context, so a real lane body could inline the floor, apply it, mention the token in a sentence, and never be compared again. Demonstrated end to end by review: an annotated lane passed at rc=0, then had its 90 changed to 80 and still passed. That is materially weaker than the two idioms it was modelled on, for a structural reason rather than an implementation one: `# lane-coverage-ok:` and `# silent-skip-ok:` anchor to a syntactic site, a job key or a guard line, which bounds what they excuse, and a file has no such site. The exception list moves into the gate as DATA_CARRIERS, path plus reason. No file can now exempt itself by writing anything into itself; a new data carrier costs an edit to the gate, which is the review the exception needs. Entries are stale-guarded the way the consumer registry is, failing when the path is gone or no longer carries the floor, and they are printed on the PASSING path of --check, not only under --list: CI runs --check and nothing else, so an exemption visible only under a listing flag would be invisible in the log of the run that went green over it. Corrects the claim this defect falsified. The gate header and section 6 said the checked set was "provably equal" to the set of files carrying the floor, untrue while an unbounded self-declared exemption existed two sentences away. Both now state the guarantee exactly: the carried set minus a finite, enumerated, reviewed exception list. In a change whose premise is that an unbacked doc claim is a defect, that had to be fixed rather than narrowed. Tests, now 28: a file naming the old token cannot excuse its own copy; the same file still fails once drifted; --check names every excused carrier on the passing path; every listed data carrier exists in the checkout. The fixture tree seeds the data-carrier paths it reads back out of the gate, so both registries are stale-guarded against the same tree. Adds the missing test for the discovery self-proof. Gutting that guard previously left the suite green while the gate reported a clean corpus over a scan that never ran, the exact property the liveness-assertion Adopters row advertises by name. Staging every fixture file EXCEPT the source reproduces a search that returns nothing, and the gate must exit 2. Verified as a mutation: with the self-proof condition forced false the suite reports PASS=27 FAIL=1, and the one failure is that test. Documents the shared anchored-marker limit: a copy indented under an outer list or nested two blockquote levels deep is invisible to discovery AND to comparison. The second half bounds the first, since such a path registered as a consumer fails as MISSING FLOOR rather than passing silently. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QkMx972GYEQ3vCb98pLw8m
…a spaced skill dir (#3474) #3469 fixed five filtered probes whose || fallback fires spuriously under set -o pipefail, and stopped there. Two independent verifiers found the same shape still live at eleven more sites across six plugins, four of them introduced by #3468 -- the commit immediately before the one that diagnosed the bug class. The mechanism is unchanged. guard >/dev/null 2>&1 && probe | cap || echo TOKEN takes the pipeline's exit status. Without pipefail that is the cap's, always 0. With pipefail it is the last non-zero status, which two ordinary situations produce: a filter matching nothing, and the writer taking SIGPIPE when the cap closes the pipe. Either fires the failure token on a healthy probe. ai-slop:audit was live and unconditional. detect.sh --show-config emits 10 lines against a head -8 cap, writing them one at a time, so head closes the pipe mid-run every time; PIPESTATUS reads 141 0. The probe rendered the full correct config and then appended 'detector unavailable' beneath it, reproducible from any directory with no configuration at all. knowledge had a consequence beyond a wrong string. video-digest states 'STOP if the pre-computed context above shows MISSING for yt-dlp, ffmpeg, or ImageMagick. Cloud agents without the media toolchain fail closed.' #3468 fixed that gate's fail-open defect and shipped this inversion in its place, so the gate would refuse to run on a machine that HAS the toolchain. Measured over 30 runs each with the tool installed and working: ffmpeg 29/30 (41 lines), magick 19/30 (5 lines, a race), yt-dlp 0/30 (one line, never reaches the cap). After the fix, 0/30 at every size. Also fixed: course-digest's two probes, firecrawl:firecrawl (pre-existing, and the idiom the fleet copied), provenance:audit's two detector probes (latent by shape only, and its changelog says so rather than claiming a live fix), docs-hygiene:audit-derivability whose capture-first form protects only the git call, and code-tidying:tidy where a bare third 'unknown' was appended to well-formed output. One grant moved, and an earlier draft of this message denied it. Quoting CLAUDE_SKILL_DIR changes the literal command string, and Bash permission rules are globs over that string, so the unquoted rule stops matching the quoted command. Three companion quoted rules were added with the unquoted ones retained; same script, same anchor, same :* scope, nothing widened. The quoting itself fixes a real defect: installed under a path containing a space, the unquoted expansion made a working detector report itself unavailable. Roster completeness was re-derived by a method unrelated to the first grep, a parser tracking brace depth and quote state, which found zero of these eleven and nine survivors, all correctly-drawn declines or structurally immune. Open and undocumented: whether Claude Code runs ! injections under pipefail. The brace-group form is correct under both settings, so the exposure is avoidable regardless. Refs #3469, refs #3468, refs #3460.
…five wrong keeps (#3475) #3468 closed 16 cross-plugin path citations under ADR 0018 and kept 23 on a test it never wrote down. Its own verifier reported '3 keeps refuted; 4 cannot verify' and that reasoning was unreadable, because the verifier ran as a remote session the agent could not read back. An independent verifier settled it: five are wrong, not three, and it found why this recurred. The dividing test #3468 applied -- fix a citation that is an ADDRESS for an obligation, keep one that is EVIDENCE about this tree at a moment in time -- appears nowhere in ADR 0018. Clause 2 reads unconditionally. But ADR 0018 is itself a docs/** file and path-cites plugin skill privates five times, quoting their contents inline. Either it violates itself on its own first page, or clause 2 is scoped to citations that function as an address. Only the second reading is coherent, and an unwritten rule is what let one commit rule two ways in two files. This writes it down, as an Amendment following ADR 0011's convention: the evidence/address test, scripts/lib being outside the entry-surface carve-out, and how plugin-level non-skill trees generalize. The five wrong keeps. shell-test-helpers cited a private scripts/lib/ path -- and that is a self-contradiction inside c66f26c itself, which added detector-findings 2.8.1 fixing the byte-identical shape and stating the ground, then kept it one file over. Three line and step pins are dropped as the same class 2.7.1 removed a :414 pin for; each citation already quotes its content inline, so the drop is lossless. records.json:3's keep-ground was false: the sweep kept it because generation overwrites hand-edits, but that store's own note says 'Hand-editable and human-gated'. Recorded as open remainder rather than swept: twelve citations c66f26c created itself, every fix having written a changelog entry quoting the path it removed, so the population is 16 fixed and 35 kept rather than 16 and 23; three trees excluded by fiat with no carve-out authorizing it; 51 citations into plugin-level non-skill trees; and a stale dometrain security record whose own version-bump re-trigger has fired unnoticed. A fifth review round found two more false claims, both stale statuses. '34 to 32' was wrong: re-deriving all 34 rows against the roster's own text test finds 22 already closed, twelve of them by #3380, the same commit that last wrote the roster and left its own 'all 34 re-verified' line standing over twelve citations it had just deleted. This change set republished that status in a file whose line 18 reads 'the check is the text, never the status and never the line number.' And the spec still shipped the probe shape #3469 removed, presented as verified in three states, two of which print the failure token under pipefail. Refs #3468, refs #3469, refs #3460. Sibling #3474.
…test, and record the pipefail question (#3476) The last five findings from an independent security review of #3460/#3468/ #3469. #3474 and #3475 closed the probe-shape and citation halves; this closes the filter half and writes the open question into the convention. Four probes silently under-reported any filename git status --porcelain C-quotes. A plain space is enough to defeat grep '\.md$' and awk '{print $NF}'. Executed against a repo holding seven files per extension (plain, space, single quote, double quote, semicolon, pipe, embedded newline), each of the four went from catching 4 of 7 to 7 of 7. Two correct forms already shipped in the same two plugins, so this was drift rather than a missing idea: the .md sites took audit-noise's grep form, which keeps their leading token and so keeps their grant valid, and dissolve-comments took audit-comment-residue's -z NUL parse. The residual is stated in the changelogs rather than hidden. audit-comment-residue's parity test could not see an over-reporting preview. Its loop only checked one direction, so mutating the SKILL.md awk to drop its rename skip made the preview emit a phantom path while detect.sh audited five real ones, and the suite still passed 53/53. A reverse loop now asserts every previewed path is one detect.sh audited: the same mutation fails 1/54 naming the phantom, and reverted it passes 54/54. The forward loop is byte-identical. claude-ops:observability rendered 'unknown' both outside a repo and inside one whose toplevel directory is literally named unknown. The failure case now renders a distinct token. Six labels asserted 'empty = none' in two states the probe does not establish: the brace group's : makes the outer || unreachable, which also masks a filter-stage failure and a guard/data double invocation. The labels now say 'empty = none matched or the probe returned nothing'. No plumbing restructured; the ~25 unfiltered probes were left alone because their claim is already true. The durable fix is in precompute-context.md: whether Claude Code runs ! injections under pipefail is undocumented, the brace group is correct under both settings while guard && pipeline || token is correct under only one, and the brace group's own price is that an internal failure renders empty, which is why the label must carry it. The recheck trigger now covers shell options. Two derivations unrelated to the review's table both return the same six filtered probes: the true set is six, four defective, not five and three. #3474 fixed audit-derivability's SIGPIPE shape hours earlier and left its filter untouched, so both defects landed on one line; this branch was rebuilt onto current main and carries both, since committing either alone would have reverted the other. 70 pipefail cells all exit 0; affected-tests real exit 0 with 17/17 suites; changelog parity including --check-preserved across 306 headings; 3061 plugin files validated; markdownlint clean over 173 files. No grant widened or broken. Refs #3474, refs #3475, refs #3469, refs #3468, refs #3460.
…, and close the dometrain staleness #3468 closed the ADR 0018 encapsulation floor by writing a convention CHANGELOG entry for each fix, and every entry quotes the path it removed. That manufactured twelve new instances of the shape the pass was sweeping. The sweep record listed them and stated they were fine; nothing ruled on them one at a time, so the arithmetic (16 fixed and 35 kept, not 16 and 23) rested on an assumption. Ruled individually against the test #3475 wrote into ADR 0018's amendment. All twelve are keep-correct and none is edited. Each sits in a dated entry whose claim is what a named file contained on that date, quoted as the string the entry removed, with the replacing invocation named in the same sentence, so no reader is sent to any of them for a rule. Three sub-rulings the class needed are recorded, because a bare "all twelve are evidence" is the assumption this was meant to replace. The three config-cascade rows quote plugin-relative forms that resolve against nothing, and the entry says so: clause 3 does not fire where the entry asserts non-resolution, since making them resolve would delete the finding. The other nine resolve on disk from their own implied base, checked one at a time. None carries a line or step pin, the part the amendment says rots first. Re-derived with a second expression over the fix commit's added lines rather than a scan of the files at rest. Same twelve, plus one the roster excludes on purpose: plugins/review/reference/topic-docs.md, a plugin-level non-skill tree that belongs to the pass the amendment routes it to, and a declared keep rather than a quoted removal. Separately, MIGRATION-PLAYBOOK's dometrain record was reviewed at 0.1.0 and the manifest reads 0.2.7, eleven releases later. Whether any of them added a trust surface is what the re-trigger clause turns on and no record answers it, which takes a review rather than a reading. The staleness is now stated in place with the re-review logged as owed. The review itself is deliberately not performed here. Refs #3468, refs #3475, refs #3460. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UmrZGdp1dgbZuPCy7tcRJo
…, and flag a fired security re-trigger (#3477) Every one of #3468's ADR-0018 citation fixes wrote a CHANGELOG entry quoting the path it removed, creating twelve new instances of the shape it was closing. The population at main is 16 fixed and 35 kept, not 16 and 23. #3475 wrote the governing test into ADR 0018; this applies it to those twelve so a later re-derivation does not re-open them. All twelve are keep-correct, established row by row and anchored on text rather than line numbers. A dated changelog entry quoting a citation it removed is the evidence case exactly. No CHANGELOG was edited, so no convention bump is owed. Three sub-rulings the class needed, now written into the spec rather than left implicit. config-cascade's three rows are the only clause-3 question in the set: they are plugin-relative and resolve against nothing, and the entry says so in the same sentence. Clause 3 governs addresses a doc offers, not strings it quotes in order to report them broken, and forcing resolution would delete the finding. The other nine resolve on disk today, verified one path at a time across eleven distinct targets. No row carries a line or step pin, the part the amendment says rots first. One excluded on purpose and recorded so it is not re-opened: plugins/review/reference/topic-docs.md, a plugin-level non-skill tree the amendment routes to its own pass, which the entry declares a keep rather than quoting as removed. Separately, MIGRATION-PLAYBOOK.md records a dometrain security review performed at 0.1.0 and states that a version bump adding a new trust surface re-triggers it. The manifest now reads 0.2.7, eleven releases later. The note added states the reviewed version, the shipping version, that the ACCEPT below it describes 0.1.0 only, and that the re-review is owed and deliberately not performed here. It stops short of asserting a trust surface was added: that is the condition the clause turns on and cannot be settled by reading the page. Two checks were run to avoid claiming otherwise and neither settles it, which the note says. The spec's dometrain entry pinned MIGRATION-PLAYBOOK.md:943, a line this change's own edit would have invalidated. Re-anchored on the record's heading text. That decay rule has been violated three times across this sweep; this is the first time it was caught before landing. Recorded because two earlier claims about it were wrong in opposite directions: this worker's definition declares the Agent tool and the harness withheld it at spawn, so nesting is gated by depth realized as pre-launch tool omission. There is no refusal string because there is no call to refuse. Refs #3476, refs #3475, refs #3474, refs #3469, refs #3468, refs #3460.
No linked issue
Summary
Applies the one lane of the repo-wide
docs-hygienesweep that was never applied. #3362 ran eight lanes and graduated its remainder intodocs/specs/docs-hygiene-sweep-unapplied-remediations.md; #3380 then closed out L2, L4, L5, L6 and L7 and left L3, theextract-ssotdeduplication lane, untouched. An adversarial verifier re-tested that premise cluster by cluster before anything was edited: all 13 remediated clusters were still open, no later commit had applied them, and no open pull request covered them.That lane also declared its own recall limit precisely, and named the fix. This change carries the fix out: a shingled n-gram pass over whitespace-normalized markdown with line breaks removed, plus the reading-driven semantic pass the earlier lane had no subagent tool to attempt. Against a control paragraph re-wrapped at three widths, the old line-anchored method shares zero lines and the new pass recovers 100%.
Four of the findings are factual defects rather than prose drift.
Fix
A fallback that could never fire, at 25 sites — and a 26th that fired and said nothing.
git status --porcelain 2>/dev/null | head -N || echo "clean"never runs its fallback:||binds to the pipeline, a pipeline's status is its last command's, andheadexits 0 on empty input. A failed probe rendered an empty string under a label readingWorking tree status:, which a reader takes for a clean tree. Five different fallback strings had drifted across the sites without one of them ever being reachable.plugins/verification/skills/confirm/SKILL.mdis the fleet's one uncapped site, so its||already bound to the probe and its fallback did run — and emitted an empty string, which under that label renders exactly the same as a clean tree. Same misreading, different mechanism. The 25 capped sites now put the fallback in a brace group with the probe and apply the cap outside it;verification:confirmkeeps its uncapped form and gains a fallback string that says something. All 26 labels now say what empty means. Proven by execution rather than argument: in a non-repository the old form prints nothing and the new one prints the fallback. The reasoning is recorded once, at theplaybooksconvention that owns precompute context. One site'sallowed-toolsgrant was a byte copy of the injection it authorizes; normalizing only the injection would have orphaned the grant.Four further sites carry the identical unreachable shape and are deliberately not normalized:
docs-hygiene:audit-noise,docs-hygiene:audit-progressive-disclosure,code-tidying:dissolve-comments, andcode-tidying:audit-comment-residue. Those are filtered probes whose fallback readsnone, which for a filtered list is the correct answer when the filter matches nothing — so an empty render there is not the misreading it is for a working-tree status. Making the fallback reachable would collapse "no matching files" and "git did not run" into one string, which is the ambiguity this fix exists to remove. Each needs a distinct failure token and a re-decided label, per skill. Recorded open in the resumable spec rather than done badly.An inline floor whose carriers had drifted apart.
docs/conventions/loop-lane/§6 requires each of the three consuming lane bodies to inline the floor, and "records the inline-floor rule so the values stay byte-identical across lanes". It binds three lanes, on the values, and names the reader contract as their provenance rather than as a fourth carrier — an earlier draft of this PR overstated that as "byte-identical across its carriers", which round three caught. Under §6's literal scope the three lanes were already mutually identical and the values never drifted; what drifted was the surrounding prose, between the lanes and the contract they cite. Hashing the three lanes plus the contract plusextract-ssot's orchestrated-mode consumer found two distinct texts, not three:git log -Sreturns two de-slop shards, and both made the same two substitutions, so between them they produced one drifted form rather than two. One of those substitutions replaced a clause-joining dash with a comma and left a splice in a sentence whose whole job is to state a causal link. All five now hash identically on a form that is both em-dash-free and grammatical. Extending identity to the two cited files is a choice this change makes, not one §6 compels, and the spec says so.A third em-dash-purge regression in the same class, in the three telemetry-upsert references: one purged copy had lost a clause explaining what the creation-race reconcile does to a sibling instance's comment, and the two unpurged copies disagreed about it. Only one of the two wordings is true, and it is now in all three. No executable block was edited; the worker refuted the brief's claim that the bash blocks were identical and verified each against its own HEAD rather than editing code to fit a false premise.
A dropped-allow-rule roster missing a class.
claude-config:audit-permission-staterecapped upstream's auto-mode drop classes withMonitorelided, then counted "four documented classes" from the elision. Upstream added the category in v2.1.236. A droppedMonitorallow rule was reported under none of the audit's classes. The prose, the diff driver and its test suite now carry the fifth class, and the skill states plainly that the siblingaudit-permission-grantscheck has noMonitorequivalent, so a clean run there is not evidence that such a grant survives.Owner clauses that did not exist.
docs/PLUGIN-PHILOSOPHY.mdgains theruntime-groundedclause 20 setup skills were asserting with nothing to point at, and four Convention registry rows: three owner docs that declare themselves owners and were missing from the registry that indexes them, plus the precompute convention. The registry's own text makes that load-bearing, since fleet audits check conformance per row.Detector-findings preamble, five sites not four. The four
persist-findings.mdpreambles this sweep touched are byte-identical apart from their run-name slot.testing:auditstates the same contract as a numberedapplystep instead, keeps that form on purpose, and had silently dropped three load-bearing clauses from it — the shape's authority, the self-ignore guard, and "where the contract and this file disagree, the contract wins". It carries all three again in its own form.provenance:audit's preamble arrived with themainmerge and is a sixth surface outside that set, not a fifth copy: it opens on "this plugin's read" rather than "this skill's" and carries a resolution list the four do not. On the owner side the contract still said "both current adopters" with four rows in a table its own rule says is added to by the commit that makes it true; a fifth producer had conformed since 2.5.0.A line-number citation that asserts the opposite of the line it names. Four surfaces cited
plugins/skill-quality/scripts/check-skill.sh:414as the hard FAIL for a dropped trigger phrase — the fence that keeps a detector's remediation out of frontmatter. Line 414 sits inside a comment stating that a trigger move WARNs and never blocks; theerris at 462. This was written up as an open cluster in the resumable spec, and between that writing and this mergemainfixed one of the four indetector-findings2.7.1, independently and by the same reasoning, leaving the roster wrong within hours. The other three now carrymain's remedy: name the trigger-phrase drop check, never a line. The spec keeps the episode as a worked example of its own decay rule rather than deleting it.A taxonomy attributed to a living author who did not write it. Four songwriting sites credit a seven-title-type taxonomy to Pat Pattison; the owner file's own heading says it is unaudited and that "the count 'seven' is ours, not Pat's". One site carried that warning and contradicted it five lines later. A shipped prompt template asserted it with no caveat. Alongside it, three skills stated a filter's mandatory load list as two files where the filter names seven, and nine skills had dropped the canonical's
first matchqualifier from the template-override rule.Verification
Every execute worker re-derived its site roster before editing rather than trusting the verdict it was handed, and three reported a delta as a result: one found five sites where the record said four, one found the record's "34 edited" was down to one because 33 had landed three days before the audit that recorded them, and one refuted the brief it was given. Independent post-hoc greps confirm all 26 sites on the canonical probe form, the only remaining instances of the broken shape being the four filtered probes deliberately left out of scope above, and the five byte-audited floor carriers hashing identically. The two
prompts/loops/files carry the floor as a blockquoted restatement, re-wrapped and explicitly outside the byte-audited block; their wording was brought to the same text but they are not among the five under the identity contract.The detector itself was audited adversarially. It found three real defects, all absorbed by the triage contract's requirement that every row be resolved against the real files before judgment: a prose mention of
BEGIN GENERATEDopened a region that never closed, the known-refused pre-filter suppressed four recorded-open remediations that the re-verification lane owned separately, and union-find chains blocks transitively. Measured precision is 65% false-positive on a uniform sample and 12% on the in-scope remainder; K=12 was tested against K=8 and K=16 and held. Every figure in that paragraph and in the spec's recall-limits section is unreproducible from this repository: the detector was a session tool, deliberately not committed, because a one-run measurement instrument is not a marketplace artifact. The spec now says so in place rather than letting the numbers read as citable facts.The change set's own record was then reviewed adversarially and was wrong in eight places — the mechanical work verified sound, the record-keeping not. Six changelog entries described a change their diff did not make: three claimed the probe-don't-recite normalization for plugins whose setup skills never contained that directive (their diffs repaired a comma splice, a broken list item and an orphaned clause, and now say so); three claimed all five producer preambles are byte-identical when four are. The new
detector-findingsadopter row called itself the third producer to join the crosswalk while the changelog entry directly above it correctly says fifth, and said the detector marks six shapes whenlib/noise-shapes.shmarks eight anddetect.shclassifies a ninth. The section heading "Three emitters" had outlived its count at six. All are corrected in3f0f0ed2, anddocs/conventions/loop-lane/CHANGELOG.mdgains the 9.0.1 entry the §4 citation-form change should have carried.A second verifier then read that adopter row claim by claim against the scripts and found two more. The row said "selection is a mechanical per-sentence scan with no withholding verdict", which is true of
detect.shand false of the skill: a model judgment lane sits between the scan and the writer and may dismiss a candidate on groundsSKILL.mdenumerates. The sibling row forclaude-config:audit-instructionsstates its model-lane carve-outs; this one did not, and this contract's "no evidence, no decline" rule requires a declining rule to state its decline evidence in its crosswalk row. Both are now stated, andrule-negation-hard-guardrail'sAuto-applicablecell led withn/a, which is not one of the four lead forms the contract permits. Chasing the shape count to its source found the real defect underneath:audit-noise's ownSKILL.md,context/persist-findings.mdandemit-findings.shheader all said the scanner marks six shapes and declines five, naming five by name.lib/noise-shapes.shappends eight anddetect.shdrives a ninth. Three shapes had been added without the prose following, so## Surfacesreports declined counts for shapes the skill's own docs say do not exist. Fixed in all three, each now pointing at the function as the count's source instead of restating it.A third round re-derived the whole change set's claims and found eight more, all record-keeping, none a code defect. "Three distinct texts" was two — both de-slop shards made the same substitutions. The spec overstated what loop-lane §6 binds. The
check-skill.sh:414cluster was declared closed whileaudit-noise's ownemit-findings.shstill carried it, because the earlier sweep grepped only*.md. "Every site carries one wording" was 18 of 20;context-guardandrate-limit-guardare named as the holdouts. The four-preambles claim went stale whenprovenancelanded. Thedocs-hygieneshape-count entry misdescribedSKILL.md, which was internally inconsistent rather than wrong. The Pass B row said 15 contradictions where the file records 12. And one roster row quoted "This doc never restates it" against severity when that sentence is about the findings-file shape. Each was re-verified against the repository before being fixed, and this PR body was corrected alongside them.A sweep whose subject is false claims in versioned surfaces does not get to ship false claims in its own — including in this description.
mainmoved under this branch during the review rounds (#3461, #3456, #3457), conflicting 36 files: every plugin this change bumps had also been bumped onmain, andmain's version had shipped. Resolved by takingmain's side of every manifest and changelog and re-landing this branch's entry one patch above it, so no shipped version number is reused and nomainentry is lost. All 26 verified centrally: manifest version equals topmost changelog version, no duplicates, no conflict markers,--check-orderclean at 85 changelogs.main'sdaa9e148was an authoring-doctrine and em-dash-grammar pass over the same surfaces; it does not overlap the three grammar repairs here, checked one by one againstorigin/mainrather than assumed.The sweep's full roster, including everything verified and deliberately not applied, the twelve contradictions the semantic pass surfaced, and those measured recall limits, is recorded in
docs/specs/extract-ssot-sweep-2026-08-28.mdso the remainder is resumable without re-auditing.Related
Refs #3362, refs #3380, refs #3178. Extends
docs/specs/docs-hygiene-sweep-unapplied-remediations.mdL3.Generated by Claude Code
Generated by Claude Code