Skip to content

fix(gates): scan the published catalog's references/ in role-word and corpus-claim-drift - #15073

Merged
os-steve merged 3 commits into
mainfrom
claude/issue-15061-catalog-gates-scan-references
Sep 4, 2026
Merged

fix(gates): scan the published catalog's references/ in role-word and corpus-claim-drift#15073
os-steve merged 3 commits into
mainfrom
claude/issue-15061-catalog-gates-scan-references

Conversation

@claude

@claude claude Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Fixes #15061

Both scripts/check-role-word.mjs and scripts/check-corpus-claim-drift.mjs walk
['content/docs', 'skills'] and carried references in a directory-NAME skip set. The
reason for that entry is real, and check-corpus-claim-drift.mjs states it outright — "the
spec 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 to skills/ too, where the same
directory 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:

const SKIP_DIRS     = new Set(['node_modules', '.git', 'dist']);   // was: + 'references'
const SKIP_SUBTREES = new Set(['content/docs/references']);         // new

SKIP_DIRS now holds only entries whose exclusion is a property of the name itself — true at
every depth and under every root, i.e. INSTALLED or GENERATED trees. SKIP_SUBTREES is matched
against 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 refusal
check-doc-authoring.mjs already records beside those same files ("an exemption over a surface
that 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_SUBTREES entry is what decides it rather than weighing against it: a
finding 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

gate before after
check:role-word Scanned: 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-drift Scanned: 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 (skills 34 to 46, the whole markdown population under skills/),
and the generated tree stays absent (content/docs 190 to 190, with 214 markdown files under
content/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:

  1. the walk REACHES the published reference pages under skills/;
  2. the walk DOES NOT reach content/docs/references/ (this half is why the exclusion is a
    path; deleting it outright would pass case 1);
  3. the walk reaches more than those pages, so 1 and 2 judge a real population;
  4. every SKIP_DIRS entry is DECLARED installed-or-generated;
  5. every SKIP_SUBTREES entry lies under a configured ROOT and EXISTS (a path excluding a
    tree 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:

OK  self-test: the walk reaches 236 markdown file(s) across the roots, 12 of them published
reference pages under skills/, and 0 under the generated content/docs/references/ tree ...

Both gates use the battery-roster floor (#13489), so the new section is a declared battery with
its own floor: check-role-word roster 11 to 12 batteries (SELF_TEST_BATTERY_FLOOR 11 to 12),
check-corpus-claim-drift 16 to 17 (floor 16 to 17).

Baseline-corpus measurement

check-corpus-claim-drift carries 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, section visibleWhen) occurs anywhere in the 12 newly visible files — a per-file
count 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-word is RED, deliberately

The 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):

skills/objectstack-data/references/data-hooks.md: NEW use of the reserved word "role" (12
occurrence(s)). ADR-0090 D3: use permission_set / position / business_unit. That is the fix.
[... the vendor-wire fence remedy, declared boundaries: better-auth ...] ⛔ MAINTAINER-ONLY,
NOT a co-equal third: for a genuine boundary that is NOT a fenced wire payload (ARIA, quoted
history), add it to scripts/role-word-baseline.json by running
node scripts/check-role-word.mjs --update. ... The baseline is shrink-only, so this weakens
a ratchet and needs a maintainer to agree the boundary is genuine first — do not take this
path to get CI green.

Both remedies an author can take on their own are wrong for this page, on the evidence:

  • Rewording would falsify what the page teaches. All 12 occurrences are the retirement of
    session.roles being documented — "No role listsession.roles was 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.
  • The vendor-wire fence does not fit: most occurrences are prose and code comments, not a
    third party's literal wire payload, and the one that is closest (where: { role: 'admin' }
    querying a user field) sits in the better-auth sys_member.role territory D3 already carves
    out — 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-drift is GREEN.

Ablation — the dormancy, proved per gate

For each gate, the blanket references name-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_PATH under an EXIT INT TERM trap and proved back
by state (git diff HEAD empty for the path AND the blob hash equal to the HEAD blob hash; an
empty hash would have been read as failure, not as "nothing to compare"). No rebuild leg
applies: both gates are plain .mjs run directly from source, resolved by path and not through
any package exports, so there is no dist/ for a stale artifact to hide in.

ablation gate itself its self-test
check-role-word + references restored exit 0, GREENScanned: 224 ... skills 34 exit 1, 2 cases fail, both naming themselves
check-corpus-claim-drift + references restored exit 0, GREENScanned: 224 ... skills 34 exit 1, 2 cases fail, both naming themselves

That 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:

x self-test: #15061 — the walk REACHES the published catalog's reference pages under skills/: hand-authored companions that ship verbatim to third parties via npx skills add, plus generator-owned index pages whose fix site is their generator. Restoring the name-keyed skip empties this while the gate itself stays green, which is the dormancy this case exists for

Blob hashes, both legs, both files: mutated blob differed from HEAD, restored blob equal to HEAD
(0f3c2c64... for check-role-word.mjs, 1a81bdc8... for check-corpus-claim-drift.mjs), and
git status --porcelain for 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 if SKIP_SUBTREES were dropped instead
of SKIP_DIRS restored — it is asserted on every run against the real tree, where
content/docs/references/ holds 214 markdown files.

Governance

$ node scripts/pm/check-governed-merges.mjs --test scripts/check-role-word.mjs scripts/check-corpus-claim-drift.mjs
governed-surface predicate: 0 of 2 path(s) hit the register (5 surfaces, repo-agnostic).
  ✅  NOT governed — ordinary queue landing applies to a PR with exactly this file list.

skip-changeset: this PR publishes nothing from any released package — two files under
scripts/, repo tooling only.

Gates run — head 7c660c57

All of the below were run at 7c660c57 (git rev-parse --short HEAD from 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 tool
takes 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.

family exit verdict line
pnpm check:role-word 1 RED on skills/objectstack-data/references/data-hooks.md, quoted above — the stop condition, self-test green
pnpm check:corpus-claim-drift 0 OK, 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.mjs 0 every one of the 166 script(s) CI runs that ship a --self-test has that self-test run by CI
pnpm check:ratchet-remedy-authority 0 186 scripts swept; 12 mark the expanding remedy MAINTAINER-ONLY, 6 turn it down outright
pnpm check:watch-hint-literal 0 48 declaration(s) across 4 rostered name(s) ... no unrostered spelling of the idiom in the tree
node scripts/pm/bare-root-worklist.mjs --self-test 0 57 live row(s), 49 unreachable as spelled, 49 recorded verdict(s) — none stale, none missing, none contradicted
pnpm check:pm-dispatch-gates 0 dispatch-gates self-test: 1291 cases pass.
pnpm check:nul-bytes 0 scanned 8195 text file(s) ... no raw ASCII control bytes
pnpm check:entry-guard 0 206 scripts/ file(s) — every entry guard goes through invoked-as.mjs
pnpm check:parse-guard 0 green
pnpm check:agent-test-spelling 0 green
pnpm check:bash32-floor 0 26 tracked shell file(s) ... name no bash 4+ construct
pnpm check:cli-command-ids 0 324 command-id literal(s) across 114 file(s) ... all resolve
pnpm check:cross-package-test-inputs 0 All 117 self-test cases passed. OK: 25 package(s) read outside themselves, all declared
pnpm check:pnpm-filter-targets 0 142/181 --filter occurrence(s) across 33 file(s) resolve
pnpm check:refd-timer-probe 0 5839 source file(s) swept
node scripts/check-ci-filter-parity.mjs 0 all 135 declared cross-package glob(s) (95 unique) are covered
node scripts/check-closing-keyword-parity.mjs 0 3 parsers agree on all 9 keywords ... 8202 tracked files
node scripts/check-comment-mask-corpus.mjs 0 5844 files, 0 disagree, 0 unparseable
node scripts/check-cross-package-test-inputs.mjs 0 OK: 25 package(s) read outside themselves, all declared
node scripts/check-shard-attestation.mjs 0 2 aggregate gate(s) count 3 declared leg(s) across 3 attesting job(s)
node scripts/check-whole-set-label-write.mjs 0 green, 160 pin(s) were judged and cleared on the rule
node scripts/check-test-completeness.mjs 3 NOT MEASURED, in the gate's own words: PREREQUISITE 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 red

Repo-wide ESLint, run whole and not narrowed, through the shared verification lock:

$ OS_VERIFY_LOCK_SLOT=issue-15061-lint bash scripts/pm/os-verify-lock.sh -c 'pnpm lint'
> node --stack-size=4000 node_modules/eslint/bin/eslint.js . --no-inline-config
os-verify-lock: VERDICT command-exit 0 · held the lock 61s (1m01s) · waited 0s

Neither gate ships a *.test.ts suite of its own (git grep over test files for either script
name 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 battery
rosters. 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 5535828546 on #15075, provenance recorded
there 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:

按照你的建议,跟进并处理完这两个pr。

The same record states the second half:

PR #15073 waits on that page (card #15061pm:blocked, Blocked-by: #15151), then adds
the baseline entry for the residue (expected 3 to 4, all quoted history) via
node scripts/check-role-word.mjs --update — the ⛔ MAINTAINER-ONLY act, authorized by the
maintainer for the residue only — and lands.

And on this PR, comment 5535832006, the sentence that authorises the act here:

Once that page is on main, this PR rebases, runs node scripts/check-role-word.mjs --update
so the baseline admits only the residue (expected 3 to 4, quoted history) — the
MAINTAINER-ONLY act, authorized by the maintainer for the residue in this PR (record on
#15075, comment 5535828546; quote it in the body) — and lands through the normal path.
Nothing else in this PR changes; the gate fix and its pins stand as reviewed by the seat.

The authorisation is for this page's residue and this PR only. It is not a standing licence.

The merge

origin/main merged into the branch as a merge commit — no rebase, no force-push. The merge
brings in PR #15186 (card #15177), 5f5511f0, which rewrote the page into ADR-0090 D3
vocabulary. Merge commit 750b0655, merging 9c1bcda3; clean, no conflicts, and the
merge driver recorded no deferred regeneration (no os-regen-pending entry). 5f5511f0 was
confirmed 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):

skills/objectstack-data/references/data-hooks.md: NEW use of the reserved word "role"
(3 occurrence(s)). ADR-0090 D3: use permission_set / position / business_unit. ...

Three, inside the ruling's predicted 3-to-4 band, and both sites are quoted history — the
retirement of session.roles being documented, which is the one thing that cannot be reworded
without falsifying it:

line 350  | `ctx.session` | ... **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?.includes(…)`

After node scripts/check-role-word.mjs --update (exit 0):

role-word baseline updated: 44 file(s) baselined from 236 .md/.mdx file(s) read across 2 root(s)
  — content/docs 190, skills 46, with 0 os:vendor-wire block(s) suppressed 0 occurrence(s)
  and 3 generated region(s) suppressed 0 occurrence(s).

check-role-word: OK, no new occurrences of the reserved word.
  Scanned: 236 .md/.mdx file(s) read across 2 root(s) — content/docs 190, skills 46.
  Exempt: 0 os:vendor-wire block(s) suppressed 0 occurrence(s); 3 generated region(s) suppressed 0 occurrence(s).
  Ledger: 44 baselined file(s) still carrying it (123 occurrence(s)) in scripts/role-word-baseline.json.

The FULL baseline diff, for line-by-line review

--update rewrites the whole baseline from the current tree, so this is the entire diff of
scripts/role-word-baseline.json, pasted rather than summarised:

diff --git a/scripts/role-word-baseline.json b/scripts/role-word-baseline.json
index 78519c3d..101e085d 100644
--- a/scripts/role-word-baseline.json
+++ b/scripts/role-word-baseline.json
@@ -36,6 +36,7 @@
   "skills/objectstack-ai/SKILL.md": 1,
   "skills/objectstack-automation/SKILL.md": 1,
   "skills/objectstack-data/SKILL.md": 1,
+  "skills/objectstack-data/references/data-hooks.md": 3,
   "skills/objectstack-data/rules/relationships.md": 1,
   "skills/objectstack-platform/SKILL.md": 2,
   "skills/objectstack-query/rules/filters.md": 8,

One insertion, no deletions, no other file touched — git diff --stat reads
scripts/role-word-baseline.json | 1 +, and scripts/role-word-baseline.json is the only
path in the commit. Because a textual diff can hide a reordering, the two JSON objects were
also compared key by key:

entries before: 43   after: 44
ADDED  : ["skills/objectstack-data/references/data-hooks.md"]
REMOVED: []
CHANGED (value moved): []
occurrence total before: 120   after: 123   delta: 3
VERDICT: CLEAN — only the data-hooks.md entry was added, value 3; no other file moved.

The stop-and-report condition (any OTHER file moving in the rewrite) did not trigger.

Re-verification at the final head, 085cd82d

Families 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 23main moved under the branch: three --self-test
spellings 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 final
commit; 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:

family exit verdict line
pnpm check:role-word 0 OK, 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-drift 0 OK, 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 --stat on
scripts/corpus-claim-drift-baseline.json is empty. Both self-tests still print their
population pins from the walk, unchanged by the merge:

OK  self-test: the walk reaches 236 markdown file(s) across the roots, 12 of them published
reference pages under skills/, and 0 under the generated content/docs/references/ tree ...

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-write and 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:

$ OS_VERIFY_LOCK_SLOT=issue-15061-lint bash scripts/pm/os-verify-lock.sh -c 'pnpm lint'
> node --stack-size=4000 node_modules/eslint/bin/eslint.js . --no-inline-config
os-verify-lock: VERDICT command-exit 0 · held the lock 84s (1m24s) · waited 0s

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-changeset
stays 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

… 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

hotlong commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

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 data-hooks.md to ADR-0090 D3 vocabulary keeping one retirement note; this PR waits on it (card #15061 pm:blocked, Blocked-by: #15177). Once that page is on main, this PR rebases, runs node scripts/check-role-word.mjs --update so the baseline admits only the residue (expected 3 to 4, quoted history) — the MAINTAINER-ONLY act, authorized by the maintainer for the residue in this PR (record on #15075, comment 5535828546; quote it in the body) — and lands through the normal path. Nothing else in this PR changes; the gate fix and its pins stand as reviewed by the seat.


Generated by Claude Code

os-steve and others added 2 commits September 4, 2026 06:33
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
@github-actions

github-actions Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

⛔ merge queue 构建失败 — 先分诊,再决定要不要重排

队列构建 33848437820 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集),
所以失败的测试可能在本 PR 没碰过的包里 —— 那不是重排能修的。每次盲目重排都会让排在后面的所有 PR 重建一轮。

失败的 job(日志抽取,best effort):

  • Test Core (1/6) — 失败步骤: Run this shard's tests

    @objectstack/cli:test:  FAIL   integration  test/serve-publishes-bound-port.e2e.test.ts > #13062 the non-zero half — nothing an ordinary boot publishes may move > publishes exactly the port it was ask
      ↳ 失败原因: @objectstack/cli:test: AssertionError: expected 40734 to be 40733 // Object.is equality
    

↳ 失败原因 是判读的关键:超时Test timed out in … / Hook timed out in …)多半是负载/时序,不是本 PR 的回归;
断言AssertionError: …)才指向真实的行为改变。两者的 FAIL 行长得一模一样,只有这一行能区分。

⚠️ 断言这一侧有一类例外,判据是断言在测什么,不是它是不是 AssertionError 断言的对象是产品行为(一个值、一个形状、一次拒收)⇒ 照上面读:真实的行为改变,去查,⛔ 不要重排掉;
断言的对象是这次实验自身的有效性前提(跑完的耗时、负载下的先后、任何只在时间预算内才成立的条件)⇒ 它跟超时是同一类,同样对负载敏感,重排一次是合法的判别手段。
识别是机械的:断言的消息或它比较的值本身点名了一段时长、一个时间戳、一个耗时计数。实测过的一对 —— AssertionError: SecurityPlugin.init() ran: expected false to be true 测的是产品行为(真回归);
AssertionError: this run took over a second, so second-precision stamps could have differed too: expected 1006 to be less than 1000 测的是实验前提:它守护的那条不变式当时是绿的,同一个 head 原样重排一次即成功。
穿着 AssertionError 外衣的时间测量,仍然是时间测量。(⛔ 这只改「怎么读一次红」,不改「哪些测试可以重排」——后者由别处管。)

跨 PR 相同签名(24h,按失败测试文件聚合):

  • test/serve-publishes-bound-port.e2e.test.ts — 24h 窗口内只有本 PR 撞到过,暂不汇总(再有一个不同 PR 撞到就会自动开汇总 issue)。
  • ⚠️ 24h 评论账本没读完(超过 5 页仍未读到窗口尽头),所以上面的「不同 PR 数」是下界,不是全量。

历史信号:

  • 本 PR 过去 24h 无队列失败记录(首次)。
  • 过去 24h 队列共有 3 个失败构建(不含本次)。

分诊清单:

  1. 失败测试在本 PR 改动的包里 → 真回归,修 PR。
  2. 失败测试与本 PR 无关 → 看上面的「跨 PR 相同签名」;已有汇总 issue ⇒ flaky/环境问题实锤,去那张 issue 上谈,修好前重排只会再烧一轮全队列。
  3. 两者都不是 → 可能与同组 PR 语义冲突;等前面的 PR 落地或失败出队后再重排一次即可,不要连续重排。

Generated by Claude Code · merge-queue-triage workflow (#4859)

@os-steve
os-steve added this pull request to the merge queue Sep 4, 2026
Merged via the queue into main with commit c0d32cd Sep 4, 2026
35 checks passed
@os-steve
os-steve deleted the claude/issue-15061-catalog-gates-scan-references branch September 4, 2026 08:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/m skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants