Found while writing the repository.directory gate (PR #16446). Filed rather than fixed there: the repair is a change to scripts/pm/dispatch-gates.mjs's admission rule and belongs to a card that owns that file.
The measurement
A gate whose population is a FILE KIND rather than a subtree wants to declare it in one line. That line is judged CORRECTLY by the consumer and is never SEEN by the producer:
hintCovers('**/package.json', p) answers true for package.json, packages/triggers/trigger-schedule/package.json, apps/docs/package.json and examples/crm/package.json, and false for packages/spec/src/index.ts. It is judged as a pattern (globInNonFinalSegment is true), so it goes through triggerCovers and is exactly right.
extractWatchHints on a module body containing const ROOT_DIR_WATCH_HINTS = ['**/package.json']; returns [].
The reason is one character position in the admission regex, which requires the literal to START with a word character, a dot or an @:
if (!/^[\w.@][\w.@\/*-]*$/.test(raw)) continue;
A leading * fails that test, so the literal never reaches the resolve step, never becomes a hint, and the gate is placed on NO card.
Why this is the silent-drop class rather than a spelling preference
The symptom is the one check:watch-hint-literal exists to prevent, arrived at through the VALUE instead of through the spelling. That gate asks whether the declaration is a literal array inside the declaration statement — which this one is, so it passes. Nothing anywhere asks whether the literals inside it are ADMISSIBLE. The gate then reads as declared, its self-test passes, check:declared-population-live is satisfied by nothing (a family that declares zero literals "declares nothing", which is a legitimate state), and the family scores undetermined or silent for every card in the tree.
Measured end to end on the branch of PR #16446: with the one-line declaration, check:pm-dispatch-gates reddened on the ROOT_WALK_RESIDUE case ("unlisted: check:manifest-repository-directory") — which is the correct outcome and is the only reason the drop was noticed at all. A gate whose source carries no repo-root walk would have gone unnoticed entirely: no gate reds, and the family simply appears on no card.
The workaround that shipped, and why it is not the fix
PR #16446 declares four literals instead, all admissible:
const ROOT_DIR_WATCH_HINTS = ['packages/**/package.json', 'apps/**/package.json', 'examples/**/package.json'];
const ROOT_FILE_WATCH_HINTS = ['package.json/**'];
Those extract all four and place the gate exactly (MATCHED on a manifest card, silent on packages/spec/src/index.ts). But the enumeration has to name every root that holds a manifest, so it goes stale the day a new top-level root appears — that gate pins a set-equality against its own scan to catch it, which is per-gate work every future file-kind gate would have to repeat.
The decision this needs, not guessed here
Two shapes, and they are not equivalent:
- Widen admission to accept a leading glob. Cheap, but admission is a load-bearing refusal with measurements behind it (the docblock records +139084 fabricated pairs for bare top-level words and a re-measured refusal of the resolved-form widening), so widening it is a change to a rule that was narrowed deliberately.
- Leave admission alone and make the DROP loud: a literal that fails admission inside a recognised
*_WATCH_HINTS declaration is a finding in check:watch-hint-literal, which already owns "the declaration must be visible to the extractor" and already reads exactly those declarations.
Option 2 looks like the smaller change and the one that matches the existing division of labour, but the choice is a maintainer's — the first is a change to the derivation's own admission semantics for every gate in the farm.
Unassigned and untriaged.
Found while writing the
repository.directorygate (PR #16446). Filed rather than fixed there: the repair is a change toscripts/pm/dispatch-gates.mjs's admission rule and belongs to a card that owns that file.The measurement
A gate whose population is a FILE KIND rather than a subtree wants to declare it in one line. That line is judged CORRECTLY by the consumer and is never SEEN by the producer:
hintCovers('**/package.json', p)answerstrueforpackage.json,packages/triggers/trigger-schedule/package.json,apps/docs/package.jsonandexamples/crm/package.json, andfalseforpackages/spec/src/index.ts. It is judged as a pattern (globInNonFinalSegmentis true), so it goes throughtriggerCoversand is exactly right.extractWatchHintson a module body containingconst ROOT_DIR_WATCH_HINTS = ['**/package.json'];returns[].The reason is one character position in the admission regex, which requires the literal to START with a word character, a dot or an
@:A leading
*fails that test, so the literal never reaches the resolve step, never becomes a hint, and the gate is placed on NO card.Why this is the silent-drop class rather than a spelling preference
The symptom is the one
check:watch-hint-literalexists to prevent, arrived at through the VALUE instead of through the spelling. That gate asks whether the declaration is a literal array inside the declaration statement — which this one is, so it passes. Nothing anywhere asks whether the literals inside it are ADMISSIBLE. The gate then reads as declared, its self-test passes,check:declared-population-liveis satisfied by nothing (a family that declares zero literals "declares nothing", which is a legitimate state), and the family scoresundeterminedorsilentfor every card in the tree.Measured end to end on the branch of PR #16446: with the one-line declaration,
check:pm-dispatch-gatesreddened on the ROOT_WALK_RESIDUE case ("unlisted: check:manifest-repository-directory") — which is the correct outcome and is the only reason the drop was noticed at all. A gate whose source carries no repo-root walk would have gone unnoticed entirely: no gate reds, and the family simply appears on no card.The workaround that shipped, and why it is not the fix
PR #16446 declares four literals instead, all admissible:
Those extract all four and place the gate exactly (MATCHED on a manifest card, silent on
packages/spec/src/index.ts). But the enumeration has to name every root that holds a manifest, so it goes stale the day a new top-level root appears — that gate pins a set-equality against its own scan to catch it, which is per-gate work every future file-kind gate would have to repeat.The decision this needs, not guessed here
Two shapes, and they are not equivalent:
*_WATCH_HINTSdeclaration is a finding incheck:watch-hint-literal, which already owns "the declaration must be visible to the extractor" and already reads exactly those declarations.Option 2 looks like the smaller change and the one that matches the existing division of labour, but the choice is a maintainer's — the first is a change to the derivation's own admission semantics for every gate in the farm.
Unassigned and untriaged.