Repository navigation
tooling(pm): stop reading a gate's exclusion constant as a watch hint - #15801
Conversation
extractWatchHints scans a gate's whole module body, so a path declared inside that gate's own exclusion list was admitted as a surface the gate watches -- the exact inverse of the declaration. A literal whose offset falls inside a top-level value declaration named for exclusion (NOISE / SKIP / EXCLUDE / IGNORE, segment-anchored) is now skipped at admission, read from the scanned gate's own declaration rather than from any list kept here. Census over scripts/**: 64 of 3,513 top-level value declarations match; 14 hints move across 6 files; 8 of those change no derivation because the gate also declares the containing root. The 6 that do were all false leads, and the pending-changeset projection every card without a changeset carries drops from 16 families to 12. Two consequences are recorded rather than hidden: check:pm-half-states joins ROOT_WALK_RESIDUE_LEDGER (it was placed by path only through the bogus hint), and the two self-test blocks that probed with a changeset path now name the gate script too, with the removal itself pinned. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019RfFHiRCSs3JXLK4cwcfox
|
ACCEPT — in-seat review of PR #15801, head Implemented-by: Verified in a detached compare worktree at the PR head, three-dot against The dev's evidence beyond the seat's runs: red first on Landing regime: pure code ⇒ ready + auto-merge in this same act (the queue's SQUASH governs whatever method the tool reports); Generated by Claude Code |
|
Measurement follow-up to the ACCEPT above (skills seat, 2026-09-05T09:1xZ): the seat's own Generated by Claude Code |
Fixes #15753
extractWatchHintsscans a gate's whole module body for path-shaped stringliterals, so a path declared inside that gate's own exclusion constant was
admitted as a surface the gate watches. The card's own sentence — the one
triage marked as the whole value of the card and asked to travel into the PR
unchanged — is carried here as it stands, in both the wordings it stands in:
The fix
One file,
scripts/pm/dispatch-gates.mjs. A literal whose offset falls inside atop-level value declaration whose identifier carries
NOISE/SKIP/EXCLUDE/IGNORE(segment-anchored) is skipped at admission. The exclusion isread from the scanned gate's own declaration, never from a list kept here:
dispatch-gates— a copy answerstoday's spelling and drifts the moment either side moves;
maskedModuleBodyblanks comments before this scanruns, so a marker on the constant is invisible from here while declaration
text survives the mask (verified on the masked output);
check:pm-half-states— that leaves the class inplace.
scripts/pm/check-half-states.mjsis not edited (in flight under check-half-states: H45's second half — apm:epiccard whose parent lackspm:epic— needs a new fetch class (the sub-issues parent read) and apm:epicpopulation kept out ofseen#15702).Spans, not lines: a noise floor is usually written multi-line, and a per-line
check would admit every entry but the first.
One correction to the card's framing, verified rather than assumed: the
"precedent" it points at is not one.
pnpm-lock.yaml— the other spelling in thesame noise floor — was never extracted because it fails
looksPathy(noseparator, not one of the four dotted names), which is an incidental refusal
rather than noise awareness. After this change both spellings of that floor are
refused for the SAME declared reason, which is what makes the pair symmetric for
the first time.
The census, measured over
scripts/**3,513 top-level value declarations across the 230 tracked JS/TS files under
scripts/. 64 identifiers match the predicate, 58 carry at least one stringliteral, and the whole set moves 14 hints across 6 files:
check-doc-authoring.mjsSKIP_PATHS,SKIP_FILES,PACKAGES_PROSE_EXCLUDEDcheck-refd-timer-probe.mjsEXCLUDED_DIRScheck-corpus-claim-drift.mjsSKIP_SUBTREEScheck-role-word.mjsSKIP_SUBTREEScheck-keyed-text-bounds.mjsSKIP_DIRScheck-half-states.mjsH36_SHARED_PREFIX_NOISEEach was read against the gate that declares it, and each is an exclusion in
that gate's own words ("whole subtrees skipped by path", "generated subtrees,
excluded by PATH under ROOTS", "directories
git ls-filescan still name thathold no authored source", and — for the one that is a bare string rather than a
list — the
continuein the gate's owndescendthat skips it).8 of the 14 change no derivation at all: the gate also declares the
containing root as an inclusion population, so
hintCoversstill reaches thepath through that (measured per hint by probing under each dropped hint against
the surviving set).
check-doc-authoring's own self-test already said so fromthe other side — every
SKIP_PATHSentry must sit under a declared root.6 really leave a derivation, and every one was a false lead. The user-visible
half is the changeset pair. A card that has not written its changeset yet is told
which families it will owe once it does, and that projection carried four
fabricated rows, 16 -> 12 — including
check-half-states.mjs --format=markdown --provenance="$PROVENANCE", thenetworked half-state-patrol sweep, advertised to every card in the tree as a gate
its changeset would trigger.
The predicate was narrowed by the census.
DENY/DENIEDmatched exactly onedeclaration and that one is a false positive (an HTTP fixture, not an exclusion
list); "deny" in this tree names authorization vocabulary, never a path skip
list, so that arm was removed. One false positive survives and costs nothing:
SHOW_EXCLUDEDinscripts/objectui-range.mjsis a CLI flag, and its onlyliteral is
'--all', refused by the flag rule regardless. No matched identifierturned out to be an inclusion list — the reading that would narrow the
predicate further;
check-watch-hint-literal.mjs's roster of the inclusion idiomshares no word with it.
Blind spots are stated in the docblock rather than left to be discovered: only
top-level declarations (one function-local instance on this tree, costing
nothing today) and only SCREAMING_SNAKE segments. Both drop less, which is the
direction this extractor errs in everywhere.
Two consequences that are not cosmetic, and are not hidden
Removing the false hint exposed two things it had been masking. Neither is
papered over:
check:pm-half-statesjoinsROOT_WALK_RESIDUE_LEDGER. It sweeps therepo root, declares neither marker, and was "derivable by path" only through
the bogus
.changesethint. Its new row says what it reads instead: lint.ymlruns the self-test half and never the sweep, and the
git ls-filesoraclebelongs to the networked patrol no lint job schedules. The ablation below
shows the row is measuring something — it reds in the opposite direction
("listed but no longer a member") when the fix is reverted.
extra#15115, [finding] dispatch-gates offerscheck-adr-0087-registration --self-testas the runnable member and files the REAL check as NOT RUNNABLE LOCALLY — but the script's own usage line defaults--basetoorigin/main#15441) were probing with a changeset pathbecause that is what reached a value-bearing family — through this very
defect. The probe card now names the gate script too, every original
assertion is unchanged, and a new case pins the removal itself: a changeset
path alone reaches no value-bearing family any more. The derivation this
change removes is recorded as an assertion rather than lost with the probe.
A third case,
check-doc-authoring's bounded over-claim, was restated ratherthan deleted:
docs/**still claims the exemptdocs/plans, but that is now thedeclaration's over-claim alone instead of being doubled by the skip list.
Verification
dispatch-gates.mjs --self-test: 1478 -> 1493 cases, 0 failures (thebrief's 1445 was stale; 1478 is the count at
66e68adc6, re-measured).throwaway worktree at this head — mutation confirmed on disk by blob hash
before and after, never by the editor's exit code — reds 11 of 1493: the
fixture case, the multi-line case, all five named-spelling cases, the live
case, the doc-authoring provenance case, the ledger membership case (in the
opposite direction: "listed but no longer a member", which is what makes the
new row a measurement rather than a placeholder) and the changeset control.
Every positive control stays green through the ablation, which is what makes
the negatives a measurement. Restored and the worktree removed, with
git diff HEADempty and the marker gone.dispatch-gates.mjs .changeset/foo.md --repo objectstack-ai/objectstackderives no
check:pm-half-states; the same tool onscripts/pm/check-half-states.mjsstill does, via gate script.--commands --repo objectstack-ai/objectstackon thefinal file list — 28 commands — and run in full at this head: 28 pass, 0 fail at
a7ddb020ac.--ranreconciles clean: "28 derived famil(ies) accounted for — 28 run,0 NOT-MEASURED", exit 0. Whole-repo
pnpm lintthroughscripts/pm/os-verify-lock.sh(slotissue-15753):eslint . --no-inline-configover the whole repo, exit 0, no output. Every exitcode captured by redirect, never read through a pipe.
scanSourceper declaring file. Measured--commandsover thisfile, two runs each: 17.2s baseline, 19.2s unconditional, 18.1s with the
declaration prefilter that ships — about 5%, paid only by the ~50 files that
carry an exclusion constant.
skip-changeset:scripts/pm/publishes nothing from any released package.Generated by Claude Code
Generated by Claude Code