Uh oh!
There was an error while loading. Please reload this page.
fix(pm): make check:i18n derivable from a metadata form module edit - #9143
Merged
Conversation
A `.form.ts` change in packages/spec moves platform-objects' committed metadata-form bundles, but no derived gate list could name check:i18n for it: the gate walks packages/ for extract configs, packages/spec owns none, and the gate's own path literals are its CLI/stale-dist prerequisites. The family scored neither matched nor undetermined — printed nowhere. PR #9113 paid one CI round trip plus a patch commit for that edge. Two changes, one contract: - scripts/i18n-bundle-surface.mjs — the config walk and the docstring-flag parse move here and BOTH readers import them. dispatch-gates used to carry a hand-written mirror of the gate's walk, described in its own comment as mirroring it "exactly": a second contract that agrees until one side moves, with nothing to report the day it stops. - a second convention entry in CHANGE_KIND_GATES for a metadata form module, with the population walked at runtime and the applicability read from the configs' own documented flags — the day every config passes --no-metadata-forms, no form module can move a committed bundle and the entry stops firing by itself. Both consumers declare the shared module as a bare module-body constant, the shape check-type-check-coverage.mjs already uses: an import specifier is not a discoverable watch hint, so without it a card editing the shared enumeration would move two gates while deriving neither — the same blind spot one layer down. Pinned live in the tool's self-test, both directions. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011RB4waLuNbdruCo6X9oobm
…9116) Reverse-verifying the new entry by making every extract config opt out emptied the rendered lines, and the bare `formHit[0]` crashed the whole self-test — one stack in place of 183 named verdicts, hiding every other finding behind it. Same fallback discipline check-i18n-bundles.mjs states for its own classifiers. Ablated again after the fix: 5 named failures, all of them the new claims, nothing else. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011RB4waLuNbdruCo6X9oobm
…ge (#9116) Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011RB4waLuNbdruCo6X9oobm
os-project-manager
marked this pull request as ready for review
August 16, 2026 14:39
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#9116
A
.form.tschange inpackages/specmoves platform-objects' committed metadata-form bundles, but no derived gate list could namecheck:i18nfor it. PR #9113 paid one CI round trip plus a patch commit for that edge.Why the derivation could not see it
A bundle has two producers, and only one lives in the package that owns the bundle:
objects— enumerated by the config's own package, so the owning package IS the trigger surface. This is what the existing convention entry covers.metadataForms— registry-driven and identical for every stack, so exactly one package commits that baseline (platform-objects; every other config passes--no-metadata-forms) while its source sits inpackages/spec, which owns no extract config and which the gate's walk never reaches.So the family scored neither
matchednorundetermined— printed nowhere at all.What changed
scripts/i18n-bundle-surface.mjs(new) — the config walk and the docstring-flag parse move here and BOTH readers import them.dispatch-gates.mjsused to carry a hand-written mirror of the gate's walk, described in its own comment as mirroringfindConfigs"exactly": a second contract that agrees until one side moves, with nothing to report the day it stops. Same shapescripts/cli-build-prerequisite.mjsalready has for the classifiers this gate shares withcheck-i18n-coverage.mjs.A second
CHANGE_KIND_GATESentry for a metadata form module. The KIND is written down; the POPULATION is walked at runtime; and applicability is read from the configs' own documented flags (anyConfigExtractsMetadataForms) rather than assumed — the day every config passes--no-metadata-forms, no form module can move a committed bundle and the entry stops firing by itself. That is its stated deletion criterion.The one convention written down is the suffix
.form.ts, which is the producer's own — stated by the registry it feeds (packages/spec/src/system/metadata-form-registry.ts: "the FormView produced bydefineForm({ schemaId })in the corresponding*.form.ts"). Measured: 17 files in the repo carry that suffix, all underpackages/spec/src, and the registry has exactly 17 form entries — the convention and the population coincide with nothing left over on either side.A declared coupling in both consumers. An import specifier is not a discoverable watch hint (
../i18n-bundle-surface.mjsstrips to a bare filename the extractor rejects), so without it a card editing the shared enumeration would move two gates while deriving neither — the same blind spot one layer down. Both declare it as a bare module-body constant, the shapecheck-type-check-coverage.mjsalready uses, pinned live in the tool's self-test.The before/after that is the deliverable
Reproducing PR #9113's shape (
object.form.ts+field.form.ts), counting mentions ofcheck:i18nin the whole output:origin/main@24206416a)d1945d278)object.form.ts+field.form.tspackages/spec/src/data/filter.zod.ts(a schema next door to a real form module)packages/rest/src/rest-server.tsAfter:
A card editing the new shared module now derives both gates it can move (
check:i18n,check:pm-dispatch-gates).Verification at
d1945d278pnpm check:i18n— green, full run on a built closure: 9 packages, all bundles in sync (platform-objects in sync (8 bundle(s)), the other eight 4 each). The refactored walk returns the same 9 configs in the same sorted order as the pre-change one, and each resolves the same documented--outdirectory.pnpm check:pm-dispatch-gates— green, 183 self-test cases (up from 179; the new cases are the metadata-form edge in both directions plus the two live coupling pins).node scripts/check-nul-bytes.mjs— green.check:i18nandcheck:pm-dispatch-gates, both run above.Reverse verification (direction decided first: ablating the applicability read, not the entry, should stop the entry firing). Adding
--no-metadata-formsto platform-objects' config docstring madeanyConfigExtractsMetadataFormsgo false, the incident paths derive 0 mentions ofcheck:i18n, and exactly 5 named self-test cases went red — all of them the new claims, nothing else. Restored byte-identical (cmpagainst a pre-ablation copy), green again.That ablation also caught a real defect in the first draft of the self-test: an empty rendering crashed it on a
TypeError, replacing 183 named verdicts with one stack. Fixed in its own commit — a case that stops holding must fail by name, the disciplinecheck-i18n-bundles.mjsstates for its own classifiers.Scope note — an edge this deliberately does NOT close
Measured while enumerating the trigger surface:
walkMetadataForms(packages/cli/src/utils/i18n-extract.ts) emitsmetadataForms.TYPE.label/.descriptionfor every entry ofDEFAULT_METADATA_TYPE_REGISTRY(packages/spec/src/kernel/metadata-plugin.zod.ts), andmetadata-form-registry.tsdecides which forms are walked at all. Editing either moves the same four bundles and matches no convention here, because neither carries a filename that distinguishes it. Closing it needs an anchor this change does not have, and the candidates trade off against each other rather than being one obvious shape, so it is filed rather than guessed at: #9144 (which stays open — it is not addressed here). The shared module's header states the same gap at the source, so the omission is visible there and not only in this description.Tooling-only (
scripts/), no user-visible change —skip-changeset.Generated by Claude Code