Uh oh!
There was an error while loading. Please reload this page.
fix(pm): derive check:i18n by owning-package walk in dispatch-gates - #8363
Merged
Conversation
`check:i18n` was invisible to the derivation in BOTH halves of the output. The path half cannot reach it: check-i18n-bundles.mjs discovers its targets at runtime by walking packages/ for files named i18n-extract.config.ts, so its source names no population path to match. Worse than silent — it does carry eleven path-ish literals (CLI prerequisite, stale-dist checks), so it also never lands in the "repo-wide / undetermined" bucket. The convention half did not know the kind. Measured on PR #8348: an object-definition edit under packages/services/service-messaging/src/objects/ regenerates that package's four bundles, and the derivation named the gate nowhere at all. Adds a second CHANGE_KIND_GATES entry that repeats the gate's own walk (same skip set, same filename-plus-/scripts/ test) and matches any input path inside an owning package. The whole package counts, config file included — narrowing to src/objects/** would under-cover, since extraction reads whatever each package's config enumerates. Lists nothing: the KIND is written down, never its population, so a tenth package growing a bundle is matched by the next run. Owner population verified at parity with the gate's own nine. Self-test 28 -> 47 cases, pinning both directions and the new entry's STALE branch. Fixes#8352 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01139NJ9Wg5pFeZi1Zh8WLg6
Found by reverse-verifying the entry above: renaming its gate to
`check:i18n-renamed-probe` made the live run print the STALE line exactly as
designed, and the self-test stayed green at 47/47 — `includes('pnpm
check:i18n')` is satisfied by every name that merely STARTS with it, so a
prefix-preserving rename is invisible to a substring pin. That is precisely
the rot the STALE branch exists to report.
Anchors both pins on the rendered delimiters (`- pnpm x —`, `⚠ x: STALE`)
so the name must match exactly. Re-verified: the same rename now fails 2
cases instead of 0.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01139NJ9Wg5pFeZi1Zh8WLg6The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
This was referenced Aug 13, 2026
hotlong
commented
Aug 13, 2026
ContributorAuthor
PM review: ACCEPT — skills seat (#7623), session
Merge path unchanged: stays draft, awaiting a human merge (conservative pending the maintainer's ruling on Generated by Claude Code |
hotlong
marked this pull request as ready for review
August 13, 2026 12:47
Uh oh!
There was an error while loading. Please reload this page.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes#8352
check:i18nwas invisible todispatch-gates.mjsin both halves of its output. Measured on PR #8348: an object-definition edit underpackages/services/service-messaging/src/objects/regenerates that package's four translation bundles, and the derivation named the gate nowhere at all — costing that PR a CI round.Why both halves missed it
scripts/check-i18n-bundles.mjsdiscovers its targets at runtime, walkingpackages/for files namedi18n-extract.config.ts(findConfigs, ~line 107 — the card cites it asfindExtractConfigs; the mechanism it describes is exact), so its source names no population path to match against.packages/cli/dist/commands/i18n/extract.js,packages/spec/dist), none of which describes the population it guards. Becausehints.lengthis non-zero, the gate is also filtered out of the "repo-wide / undetermined" bucket. Matching nothing and being excluded from the fallback bucket is what made it invisible rather than merely unmatched — a gate the derivation cannot mention at all.CHANGE_KIND_GATEScarried exactly one kind ("adds or edits a test file") and did not know this one.The fix
A second
CHANGE_KIND_GATESentry — the smaller of the two shapes the card offered — whosematchesrepeats the gate's own walk: same skip set (node_modules,dist, dotted entries), same filename-plus-/scripts/test. Any input path inside a package owning a config qualifies.It lists nothing. What is written down is the KIND; the population is discovered per run, so a tenth package growing a bundle is matched by the next run with nothing to update here — the same runtime-discovery contract the workflow and
package.jsonreads already keep. Verified at parity with the gate's own population: 9 owners derived, 9 found, identical sets.Whole owning package, config file included — the narrowing to
src/objectsthat the card rejects would under-cover, since extraction reads whatever each package's config enumerates and the config itself is part of the trigger surface. Containment is one-directional on purpose: a parent directory must not drag in the owners below it.Three supporting pure helpers are factored out so the judgments are pinnable offline:
isExtractConfigPath,owningPackageOfExtractConfig(which refuses an owner that would collapse to a bare top-level directory), andisInI18nBundlePackage. The walk is memoised per process; an unreadablepackages/throws rather than degrading to "no owners", per this script's rule that unreadable input must never look like an empty answer.Acceptance demo (live runs)
The measured incident path now names the gate:
A non-owning package still does not:
The config file itself qualifies (
packages/plugins/plugin-audit/scripts/i18n-extract.config.ts→pnpm check:i18n).Reverse verification — direction predicted first, and one prediction was uncomfortable
Predicted: renaming the entry's gate to a name no workflow runs leaves the KIND matching (the walk is untouched) and rots only name resolution, so the live run must print the
STALEline in place of the runnable invocation.Observed, both halves:
⚠ check:i18n-renamed-probe: STALE — no workflow runs a gate under this name.— as designed.includes('pnpm check:i18n')is satisfied by any name that merely starts with it, so a prefix-preserving rename was invisible to exactly the pins meant to catch it.That hole was in coverage this PR itself added, so it is fixed here (second commit): both pins now anchor on the rendered delimiters (
- pnpm x —,⚠ x: STALE). Re-running the identical break now fails 2 of 47 cases, and fails the right two — the kind-match cases stay green, confirming the walk still matches and only resolution rotted. Restored byte-identical from the commit each time (git restore; blob hash re-checked againstHEAD,git statusclean — nevergit checkout ref -- path).Self-test
28 → 47 cases, all passing. Both directions of the new kind (a path inside an owning package qualifies; one in a package without a config does not), the config file itself, a sibling sharing a name prefix, a parent directory, the live walk against the real tree, and the new entry's STALE branch. The self-test section comment is updated to say honestly that the i18n cases touch the filesystem on purpose: that entry's whole content is a walk of the real tree, and a fixture-only test passes just as happily when the walk is rooted at the wrong directory.
Gates
node scripts/pm/dispatch-gates.mjs --self-testnpx eslint scripts/pm/dispatch-gates.mjspnpm check:nul-bytesLabels
skip-changeset: internal PM dispatch tooling underscripts/pm/, nothing user-visible ships and no package changes. No changeset file.Draft on purpose — awaiting a human merge, per the claim comment. No auto-merge, no merge queue, no ready-flip.
Generated by Claude Code