fix(gates): scan the published catalog's references/ in role-word and corpus-claim-drift - #15073
Conversation
… corpus-claim-drift Both gates walk ['content/docs', 'skills'] and carried `references` in a directory-NAME skip set. The reason for that entry — the spec source is the fix site for generated pages, so a finding in one names the wrong file — is true of `content/docs/references/` and of nothing under `skills/`, where the same directory name holds published catalog content: two hand-authored companions that ship verbatim to third-party projects via `npx skills add`, plus ten generator-owned index pages whose fix site is their generator. A name cannot say which tree it means, so the correct exclusion for one root was applied to the other and hid 12 of the 46 markdown files under `skills/` (26%). Both gates ran, passed, and reported nothing about the files they never opened. The exclusion moves from SKIP_DIRS to a new SKIP_SUBTREES set matched by path under the roots, so it names the generated tree it was always about. SKIP_DIRS keeps only the installed-or-generated names (node_modules, .git, dist). No carve-out follows for the generator-owned pages under `skills/` — that is the refusal check-doc-authoring.mjs already records beside those same files. Each self-test pins the population FROM THE WALK, in both directions: the walk reaches the published reference pages under `skills/`, reaches nothing under `content/docs/references/`, reaches more than those pages, every SKIP_DIRS entry is declared installed-or-generated, and every SKIP_SUBTREES entry lies under a configured root and exists. Never a typed file count — a count cannot state "the scan reaches no reference page at all". Populations, per each gate's own verdict line: check-role-word 224 -> 236 (content/docs 190 -> 190, skills 34 -> 46) check-corpus-claim-drift 224 -> 236 (content/docs 190 -> 190, skills 34 -> 46) check-corpus-claim-drift stays GREEN: none of the four VOCABULARY spellings occurs in the 12 newly visible files, so its baseline does not move. check-role-word goes RED on skills/objectstack-data/references/data-hooks.md (12 occurrences of the reserved word). That page is a hand-authored companion and is deliberately NOT edited here, and the baseline path is marked MAINTAINER-ONLY by the gate itself. The red is the dormant coverage becoming visible; the remedy is the maintainer's call. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019RfFHiRCSs3JXLK4cwcfox
|
Maintainer ruling (director seat, summon 13): B — the red is resolved by rewriting the page, not by admitting twelve occurrences. Card #15177 (skills lane) rewrites Generated by Claude Code |
Brings in PR #15186 (#15177), which rewrote skills/objectstack-data/references/data-hooks.md into ADR-0090 D3 vocabulary, leaving 3 quoted-history occurrences of the reserved word where this branch's widened scan first met 12. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019RfFHiRCSs3JXLK4cwcfox
The maintainer authorised the MAINTAINER-ONLY `--update` for this page's residue and for this PR only, after PR #15186 rewrote the page into ADR-0090 D3 vocabulary and left 3 occurrences that cannot be reworded without falsifying what they document. Both are quoted pre-rename history — D3's own named legitimate KIND: line 350 **No role list** - `session.roles` was retired in 17.0.0: it was declared but never produced, so every read was undefined line 837 Never gate this on a session claim: ctx.session?.roles?.… Naming the retired identifier is the sentence's entire content; renaming it makes the sentence false. `--update` rewrites the whole baseline from the current tree, so the result was inspected key by key rather than skimmed. Exactly one entry was added and nothing else moved: entries 43 -> 44, occurrences 120 -> 123 (delta 3) ADDED skills/objectstack-data/references/data-hooks.md: 3 REMOVED (none) CHANGED (none) check-role-word: green, 236 files across 2 roots, ledger 44 file(s) / 123 occurrence(s). check-corpus-claim-drift: green, ledger unchanged at 2 file(s). Both population pins hold: 236 walked, 12 published reference pages, 0 under the generated content/docs/references/ tree. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019RfFHiRCSs3JXLK4cwcfox
⛔ merge queue 构建失败 — 先分诊,再决定要不要重排队列构建 33848437820 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集), 失败的 job(日志抽取,best effort):
跨 PR 相同签名(24h,按失败测试文件聚合):
历史信号:
分诊清单:
Generated by Claude Code · merge-queue-triage workflow (#4859) |
Fixes #15061
Both
scripts/check-role-word.mjsandscripts/check-corpus-claim-drift.mjswalk['content/docs', 'skills']and carriedreferencesin a directory-NAME skip set. Thereason for that entry is real, and
check-corpus-claim-drift.mjsstates it outright — "thespec source is the fix site there, so a finding in a generated file names the wrong file" —
but it is true of exactly one of the two roots. A name cannot say which one it means, so the
correct exclusion for
content/docs/references/was applied toskills/too, where the samedirectory name holds published catalog content.
Two files changed, both gate scripts. No markdown file is edited by this PR.
The shape of the fix
The exclusion moves out of the name set and into a new path set, in both gates identically:
SKIP_DIRSnow holds only entries whose exclusion is a property of the name itself — true atevery depth and under every root, i.e. INSTALLED or GENERATED trees.
SKIP_SUBTREESis matchedagainst the walker's joined path (normalised to forward slashes) and names the generated tree
the exclusion was always about. Measured, not assumed: the walker is seeded with the ROOTS
entries verbatim, so the joined path for that directory is exactly
content/docs/references.No carve-out follows for the ten generator-owned pages under
skills/— that is the refusalcheck-doc-authoring.mjsalready records beside those same files ("an exemption over a surfacethat no longer needs one is where the next regeneration would smuggle one back in. A red here
is fixed AT THE SPEC SOURCE, never by hand-editing the artifact"), and the fix-site argument
that justifies the
SKIP_SUBTREESentry is what decides it rather than weighing against it: afinding emitted into a page by its generator is a real finding whose remedy is that generator.
Population, before and after — each gate's own verdict line
check:role-wordScanned: 224 .md/.mdx file(s) read across 2 root(s) — content/docs 190, skills 34.Scanned: 236 .md/.mdx file(s) read across 2 root(s) — content/docs 190, skills 46.check:corpus-claim-driftScanned: 224 .md/.mdx file(s) read across 2 root(s) — content/docs 190, skills 34.Scanned: 236 .md/.mdx file(s) read across 2 root(s) — content/docs 190, skills 46.The 12 catalog files appear (
skills34 to 46, the whole markdown population underskills/),and the generated tree stays absent (
content/docs190 to 190, with 214 markdown files undercontent/docs/references/never walked). Both halves are required, and both are asserted.The population pin, in each self-test
Pinned FROM THE WALK — the same function the scan calls, on the real tree — and never as a
typed file count. A count has to be re-typed whenever a page is added, and it cannot state
either thing that actually goes wrong. Five cases per gate:
skills/;content/docs/references/(this half is why the exclusion is apath; deleting it outright would pass case 1);
SKIP_DIRSentry is DECLARED installed-or-generated;SKIP_SUBTREESentry lies under a configured ROOT and EXISTS (a path excluding atree no root reaches, or one that has since moved, is dead configuration that reads as
coverage).
Each gate's self-test verdict now publishes the three numbers it pinned, so a future run states
its own population rather than having it rediscovered:
Both gates use the battery-roster floor (#13489), so the new section is a declared battery with
its own floor:
check-role-wordroster 11 to 12 batteries (SELF_TEST_BATTERY_FLOOR11 to 12),check-corpus-claim-drift16 to 17 (floor 16 to 17).Baseline-corpus measurement
check-corpus-claim-driftcarries a baselined claim corpus, so widening the walk could move it.Measured: none of the four VOCABULARY spellings (
$exists, its portability gloss, the retired$regex, sectionvisibleWhen) occurs anywhere in the 12 newly visible files — a per-filecount over all twelve returns 0. The baseline does not move, and the gate's own ledger line is
unchanged before and after:
Ledger: 2 baselined file(s) in scripts/corpus-claim-drift-baseline.json.STOP CONDITION HIT —
check:role-wordis RED, deliberatelyThe widened scan reds on a hand-authored companion, which is the stop condition the card's
triage names. The page is not edited and the baseline is not touched. The gate's finding,
quoted (its remedy sentence names a marker whose literal spelling is an HTML-comment shape, so
that clause is elided here rather than pasted):
Both remedies an author can take on their own are wrong for this page, on the evidence:
session.rolesbeing documented — "No role list —session.roleswas retired in 17.0.0:it was declared but never produced, so every read was
undefined", and a hook comment warning"Do NOT gate this on a role name". These are D3's own named legitimate KIND, quoted pre-rename
history. Renaming the retired identifier in the sentence that retires it makes the sentence
false.
third party's literal wire payload, and the one that is closest (
where: { role: 'admin' }querying a user field) sits in the
better-authsys_member.roleterritory D3 already carvesout — but as an example query, not a wire body.
So the remaining path is the third, and it is marked MAINTAINER-ONLY by the gate itself. That
decision is not this flight's to take. The red is not a regression: it is dormant coverage
becoming visible, which is exactly what this card asserts. Ablation below proves that.
check:corpus-claim-driftis GREEN.Ablation — the dormancy, proved per gate
For each gate, the blanket
referencesname-skip was restored, the mutation was proved ON DISK(the deleted text grepped to 0 occurrences and the injected text to 1, anchored at the target
line — never the editor's exit code), the gate and its self-test were run, and the tree was
restored with
git checkout HEAD -- ABSOLUTE_PATHunder anEXIT INT TERMtrap and proved backby state (
git diff HEADempty for the path AND the blob hash equal to the HEAD blob hash; anempty hash would have been read as failure, not as "nothing to compare"). No rebuild leg
applies: both gates are plain
.mjsrun directly from source, resolved by path and not throughany package
exports, so there is nodist/for a stale artifact to hide in.check-role-word+referencesrestoredScanned: 224 ... skills 34check-corpus-claim-drift+referencesrestoredScanned: 224 ... skills 34That is the dormancy this card is about, stated in one line per gate: the gate stays green while
its population silently drops by 12 files. The self-test names itself instead:
Blob hashes, both legs, both files: mutated blob differed from HEAD, restored blob equal to HEAD
(
0f3c2c64...forcheck-role-word.mjs,1a81bdc8...forcheck-corpus-claim-drift.mjs), andgit status --porcelainfor each path came back empty.Note the second ablation direction is covered by the pin too but was not run as a separate leg:
case 2 (
generatedRefs.length === 0) is what would red ifSKIP_SUBTREESwere dropped insteadof
SKIP_DIRSrestored — it is asserted on every run against the real tree, wherecontent/docs/references/holds 214 markdown files.Governance
skip-changeset: this PR publishes nothing from any released package — two files underscripts/, repo tooling only.Gates run — head
7c660c57All of the below were run at
7c660c57(git rev-parse --short HEADfrom the verification run;working tree clean, both ablations restored first). Family list re-derived from the commit with
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack(no paths, so the tooltakes its own change set) — 23 families, identical to the dispatch's list, no additions. Every
exit code captured by redirecting to a file BEFORE any pipe.
pnpm check:role-wordskills/objectstack-data/references/data-hooks.md, quoted above — the stop condition, self-test greenpnpm check:corpus-claim-driftOK, no new claim sites beside a pinned spelling. Scanned: 236 ... content/docs 190, skills 46. Ledger: 2 baselined file(s)node scripts/check-self-test-wired.mjsevery one of the 166 script(s) CI runs that ship a --self-test has that self-test run by CIpnpm check:ratchet-remedy-authority186 scripts swept; 12 mark the expanding remedy MAINTAINER-ONLY, 6 turn it down outrightpnpm check:watch-hint-literal48 declaration(s) across 4 rostered name(s) ... no unrostered spelling of the idiom in the treenode scripts/pm/bare-root-worklist.mjs --self-test57 live row(s), 49 unreachable as spelled, 49 recorded verdict(s) — none stale, none missing, none contradictedpnpm check:pm-dispatch-gatesdispatch-gates self-test: 1291 cases pass.pnpm check:nul-bytesscanned 8195 text file(s) ... no raw ASCII control bytespnpm check:entry-guard206 scripts/ file(s) — every entry guard goes through invoked-as.mjspnpm check:parse-guardpnpm check:agent-test-spellingpnpm check:bash32-floor26 tracked shell file(s) ... name no bash 4+ constructpnpm check:cli-command-ids324 command-id literal(s) across 114 file(s) ... all resolvepnpm check:cross-package-test-inputsAll 117 self-test cases passed. OK: 25 package(s) read outside themselves, all declaredpnpm check:pnpm-filter-targets142/181 --filter occurrence(s) across 33 file(s) resolvepnpm check:refd-timer-probe5839 source file(s) sweptnode scripts/check-ci-filter-parity.mjsall 135 declared cross-package glob(s) (95 unique) are coverednode scripts/check-closing-keyword-parity.mjs3 parsers agree on all 9 keywords ... 8202 tracked filesnode scripts/check-comment-mask-corpus.mjs5844 files, 0 disagree, 0 unparseablenode scripts/check-cross-package-test-inputs.mjsOK: 25 package(s) read outside themselves, all declarednode scripts/check-shard-attestation.mjs2 aggregate gate(s) count 3 declared leg(s) across 3 attesting job(s)node scripts/check-whole-set-label-write.mjs160 pin(s) were judged and cleared on the rulenode scripts/check-test-completeness.mjsPREREQUISITE NOT MET — this gate grades a saved turbo run test log, and no log was named...the local reading for this gate is NOT MEASURED. ⛔ It is not a redRepo-wide ESLint, run whole and not narrowed, through the shared verification lock:
Neither gate ships a
*.test.tssuite of its own (git grepover test files for either scriptname returns nothing), so the gate-script clause adds nothing beyond the 23 families above.
Patch round — the residue baselined
Everything above stands as reviewed. The gate change itself is untouched by this round:
SKIP_SUBTREES = content/docs/references, the two five-case population pins, the batteryrosters. What is added is one line in
scripts/role-word-baseline.json.Authorisation
The maintainer ruled option B on decision card #15075 — rewrite the page first, and let the
baseline admit only what survives. Director record
5535828546on #15075, provenance recordedthere as "maintainer, live chat with the director seat, 2026-09-04 ~04:5xZ". The maintainer's
words, quoted verbatim and untranslated, exactly as that record carries them:
The same record states the second half:
And on this PR, comment
5535832006, the sentence that authorises the act here:The authorisation is for this page's residue and this PR only. It is not a standing licence.
The merge
origin/mainmerged into the branch as a merge commit — no rebase, no force-push. The mergebrings in PR #15186 (card #15177),
5f5511f0, which rewrote the page into ADR-0090 D3vocabulary. Merge commit
750b0655, merging9c1bcda3; clean, no conflicts, and themerge driver recorded no deferred regeneration (no
os-regen-pendingentry).5f5511f0wasconfirmed an ancestor of the merged main before merging.
Red before, green after — the gate's own lines
At the merged head, before
--update(exit 1):Three, inside the ruling's predicted 3-to-4 band, and both sites are quoted history — the
retirement of
session.rolesbeing documented, which is the one thing that cannot be rewordedwithout falsifying it:
After
node scripts/check-role-word.mjs --update(exit 0):The FULL baseline diff, for line-by-line review
--updaterewrites the whole baseline from the current tree, so this is the entire diff ofscripts/role-word-baseline.json, pasted rather than summarised:One insertion, no deletions, no other file touched —
git diff --statreadsscripts/role-word-baseline.json | 1 +, andscripts/role-word-baseline.jsonis the onlypath in the commit. Because a textual diff can hide a reordering, the two JSON objects were
also compared key by key:
The stop-and-report condition (any OTHER file moving in the rewrite) did not trigger.
Re-verification at the final head,
085cd82dFamilies re-derived from the merged commit with
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands(no paths).Still 23 families, but not the same 23 —
mainmoved under the branch: three--self-testspellings joined (
check-closing-keyword-parity,check-self-test-wired,check-whole-set-label-write) and three left (check-cross-package-test-inputs.mjs,check-shard-attestation.mjs,check-test-completeness.mjs). All were re-run at the finalcommit; every exit code captured by redirecting to a file before any pipe.
23 of 23 green, no reds, nothing NOT MEASURED this round. The two gates this PR edits:
pnpm check:role-wordOK, no new occurrences of the reserved word. Scanned: 236 ... content/docs 190, skills 46. Ledger: 44 baselined file(s) still carrying it (123 occurrence(s))pnpm check:corpus-claim-driftOK, no new claim sites beside a pinned spelling. Scanned: 236 ... content/docs 190, skills 46. Ledger: 2 baselined file(s)check:corpus-claim-drift's ledger is unchanged —git diff --statonscripts/corpus-claim-drift-baseline.jsonis empty. Both self-tests still print theirpopulation pins from the walk, unchanged by the merge:
The other 21, all exit 0:
check-ci-filter-parity(all 143 declared cross-package glob(s) (99 unique) are covered),check-closing-keyword-parity(3 parsers agree on all 9 keywords ... 8276 tracked files) and its self-test (24 assertions, 5 mutations of the shipped parsers each driven to red),check-comment-mask-corpus(5874 files, 0 disagree, 0 unparseable),check-self-test-wired(168 of those are run by 30 workflow(s)) and its self-test (7 declared batteries, 50 cases registered, every battery at or above its pinned floor),check-whole-set-label-writeand its self-test (24 fixture trees + 5 refusals + 1 allowlist hatch),bare-root-worklist --self-test(54 live row(s), 46 recorded verdict(s) — none stale, none missing, none contradicted),check:agent-test-spelling,check:bash32-floor(
26 tracked shell file(s) ... name no bash 4+ construct),check:cli-command-ids(329 command-id literal(s) across 116 file(s) ... all resolve),check:cross-package-test-inputs(
26 package(s) read outside themselves, all declared),check:entry-guard(209 scripts/ file(s) — every entry guard goes through invoked-as.mjs),check:nul-bytes(scanned 8269 text file(s) ... no raw ASCII control bytes),check:parse-guard,check:pm-dispatch-gates(
dispatch-gates self-test: 1353 cases pass),check:pnpm-filter-targets,check:ratchet-remedy-authority(188 scripts swept; 13 mark the expanding remedy MAINTAINER-ONLY),check:refd-timer-probe,check:watch-hint-literal(49 declaration(s) across 4 rostered name(s)).Repo-wide ESLint, run whole and not narrowed, through the shared verification lock:
Dependencies were reinstalled at the merged head (
pnpm install --frozen-lockfile, exit 0)before any of it — the worktree is a fresh checkout at the merged commit, so no artefact was
carried across the merge.
This PR stays a DRAFT for the seat's review of the baseline diff above;
skip-changesetstays applied. Patch round authored by Claude Code, session
session_019RfFHiRCSs3JXLK4cwcfox(recorded here in prose because an edit to a PR body does not preserve the session-URL footer).
Generated by Claude Code
Generated by Claude Code