Skip to content

fix(docs): write down the test ADR 0018 was applied under, and close five wrong keeps - #3475

Merged
claude[bot] merged 2 commits into
mainfrom
claude/adr-0018-keeps-and-clauses
Aug 28, 2026
Merged

fix(docs): write down the test ADR 0018 was applied under, and close five wrong keeps#3475
claude[bot] merged 2 commits into
mainfrom
claude/adr-0018-keeps-and-clauses

Conversation

@claude

@claude claude Bot commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

No linked issue

Summary

#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. So three keeps were probably wrong and nobody knew which.

An independent verifier settled it. Five are wrong, not three. More usefully, it found why this recurred.

Fix

The root cause: the test was real but unwritten

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's last sentence is unconditional: "docs/** and .claude/rules/** cite skills by slash invocation, never by path."

Read literally, all 23 keeps are violations. But ADR 0018 is itself a docs/** file and path-cites plugin skill privates five times, quoting their contents inline. Either the ADR 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.

An unwritten rule is exactly what let one commit rule two ways in two files. This PR writes it down.

The five wrong keeps

Citation Fix Deciding clause
shell-test-helpers/README.md:27clean/scripts/lib/test-helpers.sh named, not linked: "/repo-hygiene:clean's bundled test-helper copy" new amendment clause 2
plugin-data-report-keying/README.md:98bugs/write/SKILL.md:97 pin dropped, path kept 2.7.1's pin-rot class
:108unhobble/SKILL.md:53-62 pin dropped same
:123 → "steps 6 and 7" replaced with what those steps do same
native-surfaces/records.json:3audit-native-overlap/reference/canonical-pairs.json named by invocation amendment clause 1

The first is a self-contradiction inside c66f26ce. That commit added detector-findings 2.8.1, which fixed the byte-identical shape and stated the ground: a reach into a private scripts/lib/ directory "the entry-surface carve-out does not cover". One commit ruled scripts/lib/ uncovered, fixed it in one file, and kept it in another. Both dated the same day, in the same commit.

The three pins all resolve correctly today — verified — but detector-findings 2.7.1, dated the same day, removed a :414 pin as a class because "the pin had already rotted". Each citation already quotes its content inline, so dropping the pin is lossless.

records.json:3's keep-ground was simply false: the sweep kept it because hand-edits get overwritten by generation, but that store's own note says "Hand-editable and human-gated". It is exactly where an edit lands.

Lines 50/55/57 of shell-test-helpers stay — those are scripts/ entry surface, not skill privates.

The ADR amendment

Added as ## Amendment (2026-08-28), following ADR 0011's amendment convention, three clauses:

  1. The evidence/address test, with the ADR's own five citations recorded as the evidence kind — so the document stops contradicting itself.
  2. scripts/lib/ and any private subdirectory are outside the entry-surface carve-out. Already ruled in detector-findings 2.8.1; it belongs here.
  3. Plugin-level non-skill trees generalize on privacy but not into permission — clause 3 and the fetched-contract problem still bind them.

Recorded as open remainder, not swept

  • Twelve citations c66f26ce created itself. Every fix wrote a convention CHANGELOG entry quoting the path it removed. The population at main is 16 fixed + 35 kept, not 16 + 23. All twelve are keep-correct on the test above, but nobody adjudicated them. Confirmed genuinely new by differencing c66f26ce^: exactly three such citations existed before, precisely the sweep's "three dated changelog entries".
  • Three trees excluded by fiat with no ADR carve-out authorizing it: docs/specs/ (~265), docs/topics/ (~43), docs/adr/ (25). The cheat sheet (162, generated and CI drift-checked) and docs/upstream/ (27, vendored) are defensible.
  • 51 citations into plugin-level non-skill trees — the largest structural gap, ruled on only in a changelog until now.
  • A stale record found in passing: MIGRATION-PLAYBOOK.md:943 says the dometrain security review was done at 0.1.0 and "a version bump adding a new trust surface re-triggers this review". The manifest reads 0.2.7. The trigger fired; nobody re-ran it.

Round five: two more false claims, both stale statuses

"34 to 32" was wrong. Closing V-review-13/V-review-14 was said to take the predecessor's L4 roster from 34 open rows to 32. Re-deriving all 34 against the roster's own text test finds 22 of the other 32 already closed before this change set began — twelve of them by #3380, the same commit that last wrote the roster, which left its own line "All 34 path:line citations were re-verified on 2026-08-26 and every one resolves" standing over twelve citations it had just deleted. This change set read that status as fact and republished it, in a file whose line 18 reads "the check is the text, never the status and never the line number." What remains of the 34 is Group 2's eight intra-plugin defects.

The spec still shipped the probe shape #3469 removed, presented as verified in three states — two of which print the failure token once pipefail is on. The plugin changelogs superseded it correctly; the spec is the resumable record a later pass reads, and the word pipefail appeared nowhere in docs/. Corrected, with the qualifier stated as the lesson: a verification that fixes the shell's options and does not say so proves less than it appears to.

c66f26ce's commit message carries the same "34 to 32" error and cannot be edited; the changelog entry here is the correction of record.

Verification

All 16 fixed citations resolve: each /plugin:skill exists as plugins/<p>/skills/<s>/ and <p> matches that plugin.json's name. All 24 kept targets exist on disk.

Roster re-derived three ways — absolute plugins/<p>/skills/…; the bare skills/<name>/ token with no prefix; and all forms over convention changelogs including directory-only and plugin-relative shapes. Delta on the twelve: zero; derivation 3 recovered all twelve including three config-cascade plugin-relative forms and a permission-rule-hygiene directory form that derivation 1's regex missed.

scripts/affected-tests.sh --run                    exit 3 (documented non-shell-lane pass)
scripts/check-changelog-parity.sh --check-order    86 changelogs, no duplicates
node scripts/validate-plugin-contracts.mjs         clean
markdownlint-cli2 / typos                          0 issues
jq . docs/native-surfaces/records.json             parses

Two things the verifier could not settle, stated rather than guessed: which three changelog citations the sweep meant by "three dated changelog entries" (arithmetic inference, not evidence), and whether the entry-surface carve-out reaches a prose convention doc at all — a stricter consumer-class reading would flip three more keeps.

Related

Refs #3468, refs #3469, refs #3460. Amends docs/adr/0018.

Sibling: #3474 (merged as bd77698a) closes the probe-shape half of the same verification pass — eleven pipefail inversions, including the video-digest gate that would have refused to run on a machine that has the media toolchain. This PR is the citation half. They share no files.


Generated by Claude Code

claude added 2 commits August 28, 2026 14:29
…five wrong keeps

The sweep in c66f26c closed 16 cross-plugin path citations under ADR 0018 and
kept 23 on a dividing test it stated only in its pull request. The test is not
in the ADR. That omission is not academic: the same commit ruled a reach into a
skill's private scripts/lib/ out of bounds in detector-findings 2.8.1 and left
the byte-identical shape standing in shell-test-helpers, three files apart, on
the same day.

Five keeps re-derived and corrected, each verified against the tree first:

- shell-test-helpers/README.md dropped the link to repo-hygiene:clean's
  scripts/lib/test-helpers.sh and names the copy instead, on 2.8.1's own remedy.
  The scripts/ entry-surface pointers later in that file resolve and stay.
- plugin-data-report-keying/README.md drops three location pins (:97, :53-62,
  "steps 6 and 7"). All three resolved when checked, so none was broken yet; the
  pin is the part that rots, and detector-findings 2.7.1 already dropped a :414
  pin as a class after it drifted. Each citation quotes its content inline, so
  the pin carried nothing. Patch bump to 1.0.1 per that contract's own rule.
- docs/native-surfaces/records.json's note names
  /claude-ops:audit-native-overlap instead of addressing its private
  reference/canonical-pairs.json. The store is hand-editable by its own note, so
  the generation argument the sweep kept it on does not apply. overlap.py
  generate re-run: the rendered view is unchanged, as the note is store-only.

ADR 0018 gains an amendment with three clauses it never carried:

1. The evidence/address test, stated. FIX a citation that is the doc's address
   for an obligation, since the binding fails exactly where it binds. KEEP one
   that is evidence about this tree at a moment in time. The ADR path-cites
   plugin skill privates five times on its own first page, so either it violates
   itself while stating the rule or clause 2 was always scoped to function
   rather than form. Its own five are recorded as evidence.
2. scripts/lib/ and any private subdirectory sit outside the entry-surface
   carve-out. Already ruled in detector-findings 2.8.1; a ruling that lives only
   in one convention's changelog is invisible to the next doc that needs it.
3. Plugin-level non-skill trees are outside the privacy question and inside
   clause 3. The population is recorded as measured, not swept.

The sweep spec gains an open-remainder section: twelve citations c66f26c
created and never rostered (each fix wrote a changelog entry quoting the path it
removed), proven disjoint from the three already counted by differencing the
tree at the fix commit's parent, so the population is 16 fixed and 35 kept
rather than 16 and 23. Also three trees excluded by fiat with no carve-out
authorizing it, and the dometrain security review whose re-trigger fired at
0.1.0 and has gone unrun to 0.2.7.

One roster correction: the loop-lane citation was reported at :14 and is at :69
today, release 9.1.0 having landed above it. Re-derive by text, not line.

Refs #3468, refs #3460.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UmrZGdp1dgbZuPCy7tcRJo
Round five of an adversarial claims audit. Both findings are the same defect
family as rounds three and four: a status trusted instead of re-derived, in a
document whose own decay rule forbids exactly that.

"34 to 32" was wrong, and wrong in an instructive way. The claim appeared in the
sweep record and in native-references' changelog: that closing V-review-13 and
V-review-14 took the predecessor's L4 roster from 34 open rows to 32.
Re-deriving all 34 against the roster's own text test -- is the cited private
surface still named at the citing path? -- finds 22 of the other 32 already
closed before this change set began. Twelve of them were closed by #3380, the
same commit that last wrote the roster, which left its own line "All 34
path:line citations were re-verified on 2026-08-26 and every one resolves"
standing over twelve citations it had just deleted. This change set read that
status as fact and republished it, in a file that says at line 18 "the check is
the text, never the status and never the line number". What actually remains of
the 34 is Group 2's eight intra-plugin path-form defects, untouched here.

The spec also still shipped the probe shape #3469 removed, presented as
verified. It showed

  probe >/dev/null 2>&1 && probe | filter | head -N || echo TOKEN

and stated it was verified by execution in three states: token outside a
repository, nothing when the filter matches nothing, and the cap holding above
it. Under set -o pipefail two of those three states print the token instead,
which is why docs-hygiene 0.21.24 and code-tidying 0.14.13 replaced the form
with the pipeline in a brace group closed by `:`. The plugin changelogs
recorded that correctly; the spec is the resumable record a later pass reads,
and it carried the superseded shape with no pipefail qualifier at all -- the
word appears nowhere in docs/. Both the shape and the verification claim are
corrected, and the qualifier is now stated as the lesson: a verification that
fixes the shell's options and does not say so proves less than it appears to.

The commit message of c66f26c carries the same "34 to 32" error and cannot be
edited; this entry is the correction of record.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UmrZGdp1dgbZuPCy7tcRJo
@claude
claude Bot merged commit 02e1d8b into main Aug 28, 2026
63 of 64 checks passed
@claude
claude Bot deleted the claude/adr-0018-keeps-and-clauses branch August 28, 2026 14:49
claude Bot added a commit that referenced this pull request Aug 28, 2026
…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.
kyle-sexton pushed a commit that referenced this pull request Aug 28, 2026
…, 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
claude Bot added a commit that referenced this pull request Aug 28, 2026
…, 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.
claude Bot added a commit that referenced this pull request Aug 28, 2026
…reopened it three times (#3478)

The L4 roster's Group 2, eight intra-plugin path-form citations, was the last
block anyone believed open. All eight were already closed, by 6c7a103 (#3380)
on 2026-08-26 -- the same commit that wrote the roster. Its own message names
what it did: eight citations written with an implied base of the plugin root
while the real base was reference/, none of which resolved for any reader.

Verified one at a time against the roster's own text test. All eight now read
${CLAUDE_PLUGIN_ROOT}/skills/<skill>/<path> and all seven distinct targets
exist on disk. Zero plugins/** files edited, so no bump and no plugin changelog
entry: nothing needed changing.

The reason four separate passes each re-derived this roster from scratch is one
sentence. #3380 fixed 32 of 34 rows and left its summary asserting that all 34
path:line citations were re-verified on 2026-08-26 and every one resolves --
standing over citations it had just deleted. A fifth-round audit caught that for
Group 1, where 22 of 32 were already closed and twelve of those by #3380 itself.
This closes the same hole for Group 2. The generalizable rule is now in the
sweep spec: a record that fixes findings and updates its own summary in the same
commit must update the summary, or the summary outranks the fix for every later
reader.

The roster's inventory rows are left verbatim, per its own decay rule that the
inventory is the part that cannot be re-derived, and per the precedent that
#3474 and #3475 both closed rows without touching the file. Only an additive
closure stamp is appended.

On whether ADR 0018 reaches this class, the honest answer is that its
encapsulation half does not. Clause 1 legalises intra-plugin citations and names
this exact citing surface. Clause 2 cannot apply, because both files ship inside
one plugin so the runtime absence motivating it cannot occur, and the 2026-08-28
amendment's fix-an-address / keep-evidence test divides clause 2 applications
only. Only clause 3 reaches Group 2, and clause 3 is a resolvability rule rather
than an encapsulation one: had these been open, the remedy would have been path
form and nothing else. The clause that earned its keep is the ADR's own
observation that proximity did not prevent them.

A second derivation making no reference to the roster resolved every citation
token in every plugin-level reference/, context/ and agents/ tree plus every
plugin README against the base its own form implies: 52 tokens, 0 clause-3
failures. Group 3's two anchors were also closed by #3380. 34 closed, 0 open.

No fresh-context verifier reviewed this diff. Nested spawning is unavailable at
this depth: the Agent tool is withheld pre-launch, so there is no call to refuse
and no refusal string. A second mechanical derivation was substituted and every
line reference re-checked against the live tree, which is weaker than a fresh
context and is recorded as such.

Refs #3477, refs #3476, refs #3475, refs #3474, refs #3469, refs #3468.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant