Found on PR #8348 (#8118), where it cost the PR a CI round: the dispatch prompt's gate list and a dispatch-gates.mjs re-derivation against the changed paths both omitted check:i18n, and the PR went red in the TypeScript Type Check job on services/service-messaging DRIFTED (4) — the object-definition edit (internal: true + a new field description on sys_http_delivery.headers_json in packages/services/service-messaging/src/objects/http-delivery.object.ts) regenerates that package's four translation bundles, and nothing in the derivation could say so.
Why the derivation structurally misses it — measured, both halves
- The path half cannot match.
dispatch-gates.mjs matches a check to input paths by scanning the check script's own source for path literals ("watch hints"). scripts/check-i18n-bundles.mjs names none for the population it guards: it discovers its targets at runtime by walking packages/ for files namedi18n-extract.config.ts (its findExtractConfigs, ~line 106-112 — a readdirSync walk matching on the filename with p.includes('/scripts/')). The only package-path strings in its source are self-test fixture assertions (error-message literals about packages/platform-objects/...), which are not hints about what the gate watches — and would point at the wrong package if they were. So an edit under packages/services/service-messaging/src/objects/** has nothing to match against. - The convention half does not know this kind.
CHANGE_KIND_GATES carries exactly one kind today (adds or edits a test file). "Edits a file in a package that owns an i18n-extract.config.ts" is not a kind it knows, so the convention derivation cannot name check:i18n either. The script's own output honestly labels this residue a PM judgment call — which is exactly what failed here: two careful readers (the dispatching PM and the dev's re-derivation) both missed it.
The mechanical fix that respects the script's no-embedded-list philosophy
The trigger IS derivable at dispatch time without embedding any list: walk packages/ for i18n-extract.config.tsexactly the way the checker itself does, and match any input path that sits inside a package owning one → pnpm check:i18n. That is the same runtime-discovery approach the script already takes for workflows (extractCheckInvocations re-reads .github/workflows/*.yml every run) and for package.json script resolution — when a tenth package grows a bundle, the next derivation run sees it, no list to rot. It could land either as a second CHANGE_KIND_GATES entry whose matches does the walk, or as a small "runtime-discovered watch roots" extension to the path half; the former is smaller.
A narrower variant (match only src/objects/** inside such packages) would under-cover: the extraction reads whatever each package's config enumerates, and the config file itself is also part of the trigger surface.
Scope
scripts/pm/dispatch-gates.mjs only. Not the gate itself — check-i18n-bundles.mjs is correct and caught the drift; this card is about the derivation that is supposed to predict it. Sibling but distinct: #8162 (the derivation's self-tests run in no CI job).
Generated by Claude Code
Found on PR #8348 (#8118), where it cost the PR a CI round: the dispatch prompt's gate list and a
dispatch-gates.mjsre-derivation against the changed paths both omittedcheck:i18n, and the PR went red in theTypeScript Type Checkjob onservices/service-messaging DRIFTED (4)— the object-definition edit (internal: true+ a new fielddescriptiononsys_http_delivery.headers_jsoninpackages/services/service-messaging/src/objects/http-delivery.object.ts) regenerates that package's four translation bundles, and nothing in the derivation could say so.Why the derivation structurally misses it — measured, both halves
dispatch-gates.mjsmatches a check to input paths by scanning the check script's own source for path literals ("watch hints").scripts/check-i18n-bundles.mjsnames none for the population it guards: it discovers its targets at runtime by walkingpackages/for files namedi18n-extract.config.ts(itsfindExtractConfigs, ~line 106-112 — areaddirSyncwalk matching on the filename withp.includes('/scripts/')). The only package-path strings in its source are self-test fixture assertions (error-message literals aboutpackages/platform-objects/...), which are not hints about what the gate watches — and would point at the wrong package if they were. So an edit underpackages/services/service-messaging/src/objects/**has nothing to match against.CHANGE_KIND_GATEScarries exactly one kind today (adds or edits a test file). "Edits a file in a package that owns ani18n-extract.config.ts" is not a kind it knows, so the convention derivation cannot namecheck:i18neither. The script's own output honestly labels this residue a PM judgment call — which is exactly what failed here: two careful readers (the dispatching PM and the dev's re-derivation) both missed it.The mechanical fix that respects the script's no-embedded-list philosophy
The trigger IS derivable at dispatch time without embedding any list: walk
packages/fori18n-extract.config.tsexactly the way the checker itself does, and match any input path that sits inside a package owning one →pnpm check:i18n. That is the same runtime-discovery approach the script already takes for workflows (extractCheckInvocationsre-reads.github/workflows/*.ymlevery run) and forpackage.jsonscript resolution — when a tenth package grows a bundle, the next derivation run sees it, no list to rot. It could land either as a secondCHANGE_KIND_GATESentry whosematchesdoes the walk, or as a small "runtime-discovered watch roots" extension to the path half; the former is smaller.A narrower variant (match only
src/objects/**inside such packages) would under-cover: the extraction reads whatever each package's config enumerates, and the config file itself is also part of the trigger surface.Scope
scripts/pm/dispatch-gates.mjsonly. Not the gate itself —check-i18n-bundles.mjsis correct and caught the drift; this card is about the derivation that is supposed to predict it. Sibling but distinct: #8162 (the derivation's self-tests run in no CI job).Generated by Claude Code