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
59 changes: 53 additions & 6 deletions packages/cli/src/utils/metadata-file-name.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,59 @@ export function metadataFileInfix(type: string): string | null {
* ## Why this is derived rather than tabulated
*
* `MetadataPlugin._loadFromFileSystem` primes every registered type by
* globbing that type's `filePatterns`, and that is the default (`eager`)
* bootstrap whenever no compiled artifact is configured. A scaffold whose
* name matches none of those patterns still type-checks, still passes
* `os validate` and still publishes, with nothing anywhere reporting that it
* was skipped — the silent-strip shape ADR-0063's retirement of `os g agent`
* closed (#10359).
* globbing that type's `filePatterns`. A scaffold whose name matches none of
* those patterns is never primed, and nothing anywhere reports it: it still
* type-checks, still passes `os validate` and still publishes — the
* silent-strip shape ADR-0063's retirement of `os g agent` closed (#10359).
*
* ### That mechanism has a precondition, and this tree never meets it (#12075)
*
* The paragraph above used to carry the whole argument, which made it read as
* a universal property of the platform. It is not one. `MetadataPlugin.start`
* reaches `_loadFromFileSystem` on exactly ONE branch: `bootstrap` is `eager`
* (the default) AND `options.artifactSource` is unset. An artifact source
* routes eager to `_loadFromLocalFile` instead, and `lazy` / `artifact-only`
* never glob at all. Re-measured on `origin/main` while writing this note:
*
* - Both non-test `new MetadataPlugin(...)` sites in this repo pass
* `artifactSource: { mode: 'local-file', path }` unconditionally —
* `packages/runtime/src/standalone-stack.ts` and the dev-HMR site in
* `packages/cli/src/commands/serve.ts`. `os dev` compiles and spawns
* `serve`; `os serve` and `os start` boot from the compiled artifact; and
* `os init` writes a `package.json` whose every script runs one of those.
* - All nine tracked `objectstack.config.ts` files declare their metadata in
* code — a barrel (`import * as objects from './src/objects'`) or a named
* import — or declare none at all. ZERO rely on `filePatterns` to find
* anything.
*
* The discriminating measurement, because "the glob might not be consulted"
* and "the glob is not consulted here" are different claims: a file spelled
* exactly the way the registry declares, sitting in the scaffold's own object
* directory, does NOT reach the compiled artifact when the barrel omits it,
* while a file matching no pattern at all DOES reach it as soon as the barrel
* names it. `os compile` builds `dist/objectstack.json` out of `loadConfig`
* alone and globs nothing. For everything this CLI scaffolds, the barrel's
* module specifier is the whole load path.
*
* The derivation below still stands, on narrower ground than it used to
* claim: it is a CONSISTENCY property — one CLI teaching one spelling that
* the registry and every example already agree on — not the DISCOVERABILITY
* property the first paragraph reads as on its own. Do not argue "matches no
* pattern therefore silently skipped" from this docblock alone; check the
* project's bootstrap first, and for anything `os init` writes the answer is
* no.
*
* ### Why `filePatterns` gets no end-to-end dogfood (#12075)
*
* It is a declared discovery surface with no measured consumer in this tree,
* and the recorded disposition is that it stays one: no end-to-end dogfood is
* being minted for the eager `_loadFromFileSystem` path — startup focus, and
* a surface with zero measured consumers does not earn one. Recorded here, on
* the CLI's own read of `filePatterns`, rather than beside the registry
* declaration itself — that lives in
* `packages/spec/src/kernel/metadata-plugin.zod.ts`, which this package only
* reads. Unexercised is NOT broken: nothing above reports the eager path as
* defective, only as unmeasured.
*
* The harness used to write `NAME.ts` for every type, and that name matches
* no pattern the registry declares for ANY type. Measured rather than
Expand Down
26 changes: 20 additions & 6 deletions packages/cli/test/generate-file-name-registry-parity.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,22 @@
* The defect this closes was six generators writing `NAME.ts` while
* `DEFAULT_METADATA_TYPE_REGISTRY` declared `NAME.TYPE.ts` for their types,
* so `MetadataPlugin._loadFromFileSystem` — which globs EVERY registered type
* by that type's own `filePatterns`, and is the default `eager` bootstrap
* whenever no compiled artifact is configured — never saw the scaffolds the
* CLI had just written. They type-check, they pass `os validate`, they
* publish, and nothing at any step says they were skipped.
* by that type's own `filePatterns` — never saw the scaffolds the CLI had
* just written. They type-check, they pass `os validate`, they publish, and
* nothing at any step says they were skipped.
*
* ⚠️ THAT LOAD PATH HAS A PRECONDITION, and nothing this repo boots meets it
* (#12075). `_loadFromFileSystem` is reached only when `bootstrap` is `eager`
* (the default) AND no `artifactSource` is configured; every non-test
* `MetadataPlugin` construction site here configures one, so `os dev` /
* `os serve` / `os start` all load the compiled artifact and the barrel's
* module specifier is the whole load path. `metadata-file-name.ts` carries
* the measurement, including the discriminator: a file spelled exactly as the
* registry declares does not reach the artifact when the barrel omits it.
* So what this file pins is a CONSISTENCY property — generator spelling
* equals registry spelling — and NOT the discoverability property the
* paragraph above reads as on its own. The narrower rationale does not weaken
* the pin: both halves stay derived, for the reason below.
*
* A pin written as `expect(name).toBe('customer.object.ts')` six times is
* green the day a SEVENTH generator is added with no registry entry and no
Expand Down Expand Up @@ -69,7 +81,8 @@ describe('[#11071] every generator writes a name its own type declares', () => {
expect(
entry,
`\`os g ${type}\` scaffolds a type absent from DEFAULT_METADATA_TYPE_REGISTRY — `
+ 'nothing globs it, so whatever it writes is invisible to the loader',
+ 'no entry declares a pattern for it, so whatever it writes matches no contract '
+ 'anything can check',
).toBeDefined();

const written = metadataFileName(type, STEM);
Expand All @@ -85,7 +98,8 @@ describe('[#11071] every generator writes a name its own type declares', () => {
expect(
matched.length,
`generated "${relPath}" matches none of ${JSON.stringify(entry!.filePatterns)} — `
+ 'it would validate, publish and never load',
+ 'the CLI would be teaching a name its own registry entry does not declare '
+ '(and under an eager, artifact-less bootstrap it would never load)',
).toBeGreaterThan(0);
},
);
Expand Down
Loading