From 8ddfb85599881e19ed47bf4c92492dc54e055bec Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 25 Aug 2026 11:05:00 +0000 Subject: [PATCH] docs(cli): state the precondition the filePatterns glob-discovery rationale carries MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `metadata-file-name.ts`'s docblock derived the generated filename from the registry's `filePatterns` and justified it with `MetadataPlugin._loadFromFileSystem` globbing every registered type. Every clause was true, but the precondition was missing, so the rationale read as a universal property of the platform. Re-measured on origin/main: `_loadFromFileSystem` is reached on exactly one branch — `bootstrap: 'eager'` (the default) AND no `artifactSource` — and both non-test `new MetadataPlugin(...)` sites in this repo configure an `artifactSource: { mode: 'local-file' }`, so nothing this repo boots takes it. All nine tracked `objectstack.config.ts` files declare their metadata in code or declare none; `os compile` builds the artifact from `loadConfig` alone and globs nothing. The derivation still stands, as a consistency property rather than the discoverability property the old wording framed. Also records the disposition on the second question the finding raised: no end-to-end dogfood is minted for the eager path. Stated on the CLI's own read of `filePatterns` rather than beside the registry declaration, which lives in `packages/spec` and is not this package's to annotate. Comment-only: no behaviour, no public surface, no schema. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01HbG3rGVLjZStHQxHDtzJdJ --- packages/cli/src/utils/metadata-file-name.ts | 59 +++++++++++++++++-- ...generate-file-name-registry-parity.test.ts | 26 ++++++-- 2 files changed, 73 insertions(+), 12 deletions(-) diff --git a/packages/cli/src/utils/metadata-file-name.ts b/packages/cli/src/utils/metadata-file-name.ts index 3e4e99076b..14766d198f 100644 --- a/packages/cli/src/utils/metadata-file-name.ts +++ b/packages/cli/src/utils/metadata-file-name.ts @@ -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 diff --git a/packages/cli/test/generate-file-name-registry-parity.test.ts b/packages/cli/test/generate-file-name-registry-parity.test.ts index cf59e64c36..3e027795fb 100644 --- a/packages/cli/test/generate-file-name-registry-parity.test.ts +++ b/packages/cli/test/generate-file-name-registry-parity.test.ts @@ -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 @@ -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); @@ -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); }, );