Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions scripts/check-i18n-bundles.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,37 @@ import { findExtractConfigs, flagsFromDocstring } from './i18n-bundle-surface.mj
*/
const SURFACE_MODULE = 'scripts/i18n-bundle-surface.mjs';

/**
* The two producers of the `metadataForms` TYPE-LEVEL surface, declared as
* bare module-body path literals for the same reason as SURFACE_MODULE above
* (#9144). `walkMetadataForms` (packages/cli/src/utils/i18n-extract.ts) emits
* `metadataForms.<type>.label` / `.description` for every entry of
* `DEFAULT_METADATA_TYPE_REGISTRY` — including form-less types like
* `datasource`/`job`/`translation` — and separately reads
* `METADATA_FORM_REGISTRY` itself (the map, not the `*.form.ts` leaves it
* points at) to decide which types also get section/field labels. Editing
* either moves the same `platform-objects` bundles PR #9113 had to
* regenerate — and, unlike the `*.form.ts` leaves, neither carries a filename
* SURFACE_MODULE's own convention (`isMetadataFormModulePath`) can see.
*
* Not folded into SURFACE_MODULE: that module documents the ONE convention it
* enumerates at runtime (the `.form.ts` suffix), and these two files
* deliberately do not carry it — inventing a second filename convention for
* exactly two files would be the guess #9144 declined to make. A bare
* coupling constant was the option the card measured to have no downside
* beyond upkeep; see i18n-bundle-surface.mjs's header for the two rejected
* alternatives and why.
*
* This is per-coupling manual upkeep, deliberately, and it does not go quiet:
* dispatch-gates' own self-test pins that a card editing either path derives
* check:i18n, against the real files (existsSync) — delete either constant
* and that self-test reddens instead of the silence coming back. If either
* module is renamed or the registries merge, update the self-test's pins in
* the same change: the evidence goes with the claim, never ahead of it.
*/
const METADATA_TYPE_REGISTRY_MODULE = 'packages/spec/src/kernel/metadata-plugin.zod.ts';
const METADATA_FORM_REGISTRY_MODULE = 'packages/spec/src/system/metadata-form-registry.ts';

/** The one command this gate invokes per package, as oclif topic/command parts. */
const EXTRACT_COMMAND_ID = ['i18n', 'extract'];
const write = process.argv.includes('--write');
Expand Down
24 changes: 15 additions & 9 deletions scripts/i18n-bundle-surface.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -50,17 +50,23 @@
* form — the convention and the population coincide, with nothing left over on
* either side.
*
* What it deliberately does NOT cover, measured and stated so the next reader
* does not mistake silence for coverage: the type-level half of the same
* surface. `walkMetadataForms` in packages/cli/src/utils/i18n-extract.ts emits
* What it still deliberately does NOT cover, stated so the next reader does
* not mistake silence for coverage: the type-level half of the same surface.
* `walkMetadataForms` in packages/cli/src/utils/i18n-extract.ts emits
* `metadataForms.TYPE.label`/`.description` for every entry of
* DEFAULT_METADATA_TYPE_REGISTRY (packages/spec/src/kernel/metadata-plugin.zod.ts),
* and the registry module itself decides which forms are walked. Editing either
* moves the same four bundles and matches no convention here, because neither
* carries a filename that distinguishes it. Closing that edge needs an anchor
* this module 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:
* issue 9144.
* and the registry module itself (packages/spec/src/system/metadata-form-
* registry.ts) decides which forms are walked. Editing either moves the same
* four bundles, and neither carries a filename this module's ONE convention
* (the `.form.ts` suffix) can see — inventing a second convention for exactly
* two files would be a guess, not a discovery. #9144 closed that edge WITHOUT
* extending this module: the two module paths are declared as bare coupling
* constants directly in check-i18n-bundles.mjs (METADATA_TYPE_REGISTRY_MODULE
* / METADATA_FORM_REGISTRY_MODULE, next to SURFACE_MODULE), which the
* derivation reads straight off that gate's own module body — no population
* to enumerate, since it is exactly two known files. See that pair's doc
* comment for why here was the wrong home and dispatch-gates.mjs's self-test
* for how the constants are kept honest.
*/

import { readFileSync } from 'node:fs';
Expand Down
71 changes: 62 additions & 9 deletions scripts/pm/dispatch-gates.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -1159,15 +1159,16 @@ export function reachesMetadataFormModule(path, modulePaths) {
* output for every card in the tree;
* - `check:i18n` walks `packages/` at runtime for files NAMED
* `i18n-extract.config.ts` and re-extracts each owning package's bundles.
* Its source is worse than silent: the path-ish literals it does carry are
* its CLI prerequisite and stale-dist checks (`packages/cli/dist/commands/
* i18n/extract.js`, `packages/spec/dist`, measured — eleven hints, none of
* them the population). So it matches nothing AND, having hints, never
* reaches the "undetermined" bucket either: before this entry existed, an
* edit to `packages/services/service-messaging/src/objects/` — which
* regenerates that package's four bundles — printed the gate in NEITHER
* half of the output. A gate the derivation cannot mention at all is the
* one shape this script must not produce; it cost a PR a CI round.
* Its source names only three hints (measured, post-#9144): the shared
* walk module (SURFACE_MODULE) and the two metadata-registry coupling
* constants below — none of them the OWNING-PACKAGE population this entry
* answers for. So it still matches nothing on an ordinary object/field
* edit AND, having hints, never reaches the "undetermined" bucket either:
* before this entry existed, an edit to
* `packages/services/service-messaging/src/objects/` — which regenerates
* that package's four bundles — printed the gate in NEITHER half of the
* output. A gate the derivation cannot mention at all is the one shape
* this script must not produce; it cost a PR a CI round.
*
* No per-card gate list derived from paths can ever name these, however the
* derivation improves.
Expand Down Expand Up @@ -1251,6 +1252,32 @@ export function reachesMetadataFormModule(path, modulePaths) {
* baseline. The day the last one opts out, no form module can move a committed
* bundle and this entry stops firing on its own.
*
* ## Why there is no THIRD i18n entry, for the type-registry edge (#9144)
*
* `walkMetadataForms` has a second edge the SECOND entry above does not reach:
* `DEFAULT_METADATA_TYPE_REGISTRY` (packages/spec/src/kernel/metadata-plugin.
* zod.ts) supplies `metadataForms.<type>.label`/`.description` for EVERY
* registry entry, including form-less types, and `METADATA_FORM_REGISTRY`
* itself (packages/spec/src/system/metadata-form-registry.ts, the map, not
* the `*.form.ts` leaves it points at) decides which types get section/field
* labels at all. Editing either moves the same bundles PR #9113 paid for —
* but unlike the `.form.ts` leaves, neither file carries a filename the
* `.form.ts` convention (or any convention) distinguishes, so a KIND entry
* here would need to invent one for exactly two files.
*
* That is not the same shape as the two entries above: this is not a
* runtime-enumerated population at all, it is two SPECIFIC, KNOWN files —
* the shape `SURFACE_MODULE` and `check-type-check-coverage.mjs`'s
* `ROOT_PROGRAM_COUPLED_SCRIPT` already use. So it is closed there instead:
* `check-i18n-bundles.mjs` declares both paths as bare module-body coupling
* constants (`METADATA_TYPE_REGISTRY_MODULE` / `METADATA_FORM_REGISTRY_
* MODULE`), which the ORDINARY path-literal derivation now reads directly off
* that gate's own source — no `CHANGE_KIND_GATES` entry, no `matches`
* function, nothing here to keep in sync. See that pair's doc comment in
* check-i18n-bundles.mjs for the full reasoning, and this file's own
* self-test for the live pins that keep the constants honest as the coupling
* they are: manual, per-file, and silently rottable if nothing watched it.
*
* ## How these entries stay honest
*
* - Every `name` here is resolved against the families actually discovered in
Expand Down Expand Up @@ -2359,6 +2386,32 @@ function selfTest() {
// than a pair of matching strings.
t('the declared shared module exists', existsSync(join(ROOT, SHARED)));

// The same shape again, for the TYPE-registry edge of walkMetadataForms
// (#9144) — two specific, known files rather than a runtime-enumerated
// population, so they are closed as coupling constants in
// check-i18n-bundles.mjs rather than a third CHANGE_KIND_GATES entry. Both
// directions pinned LIVE: delete either constant and this reddens instead
// of the derivation going silently blind on that edge again.
const TYPE_REGISTRY = 'packages/spec/src/kernel/metadata-plugin.zod.ts';
const FORM_REGISTRY = 'packages/spec/src/system/metadata-form-registry.ts';
const i18nGateHints = readHints('scripts/check-i18n-bundles.mjs');
t('the i18n gate declares the type-level metadata registry module', covers(i18nGateHints, TYPE_REGISTRY));
t('the i18n gate declares the form registry module too (not just its *.form.ts leaves)', covers(i18nGateHints, FORM_REGISTRY));
const typeRegistryVerdict = classifyEntry({ files: ['scripts/check-i18n-bundles.mjs'], hints: i18nGateHints }, [TYPE_REGISTRY]);
const formRegistryVerdict = classifyEntry({ files: ['scripts/check-i18n-bundles.mjs'], hints: i18nGateHints }, [FORM_REGISTRY]);
t(
'so a card editing the type registry is MATCHED through that constant, not dropped as silent',
typeRegistryVerdict.verdict === 'matched' && typeRegistryVerdict.hits[0]?.hint === TYPE_REGISTRY,
);
t(
'and a card editing the form registry module is MATCHED through its own constant',
formRegistryVerdict.verdict === 'matched' && formRegistryVerdict.hits[0]?.hint === FORM_REGISTRY,
);
// Both declared paths are real files, so the four claims above are live
// rather than a pair of matching strings.
t('the declared type registry module exists', existsSync(join(ROOT, TYPE_REGISTRY)));
t('the declared form registry module exists', existsSync(join(ROOT, FORM_REGISTRY)));

// ── A family's OWN script files as match keys (#8509) ─────────────────────
//
// Both directions are the product, and both are pinned: a card editing a
Expand Down
Loading