Related #14122 · same shape as #15210 (the by-shape sweep) and #14512 (the emitter half). Found while implementing #15006; deliberately not folded into it, because the answer is a decision rather than a read fix.
The sites
file:line |
Reads |
packages/cli/src/commands/serve.ts:2622 |
let plugins = config.plugins || []; |
packages/cli/src/commands/serve.ts (a few lines below) |
if (flags.dev && config.devPlugins) plugins = [...plugins, ...config.devPlugins]; |
packages/cli/src/utils/schema-migration-plugins.ts:1082 |
const hostPlugins = Array.isArray(config?.plugins) ? config.plugins : []; |
plugins and devPlugins are both package-owned collections by the derivation every reader in this program uses — COMPOSE_KEY_DISPOSITIONS gives them 'concat', so they are members of AssembledPackageBodySchema, so ObjectStackDefinitionSchema ∩ AssembledPackageBodySchema contains them. (Both appear in the 37-key list #15210 prints.) An option-B-shaped stack therefore carries them under packages[i].manifest.plugins and not at the top level, and all three reads above see nothing.
Why the one-line fix that every other site in the program takes is WRONG here
Everywhere else in the reader program the repair is "resolve packages[] when the top level is absent". Applying that here would hand kernel.use() whatever sits in packages[i].manifest.plugins — and on the artifact path that is inert JSON, not a live plugin instance. A JSON artifact cannot carry a constructed plugin. So the mechanical repair converts a silent skip into a boot that registers garbage.
That points at a question upstream of the CLI, which is why this is filed rather than fixed:
- Should
plugins / devPlugins be in the assembled-package-body key set at all? They are the only members of it whose values are live objects rather than serializable metadata. If they should not be, this is a packages/spec disposition change (COMPOSE_KEY_DISPOSITIONS), not a CLI change, and every reader in the program inherits the answer.
- If they stay, what does a package's
plugins mean inside an artifact — a dropped key, or a declaration the loader is supposed to resolve to a real module?
Not measured, and why
The #15004 acceptance zoo declares no plugins, so no probe row covers this today and the loss above is derived from the key set rather than observed. Adding plugins to that fixture is fixture surgery three parallel cards are reading, so it was left alone. A row belongs here once question 1 is answered — before that, a row would pin a shape that may be about to change.
Reached in practice by any multi-package host that composes plugins per package: composeStacks concatenates them to the top level today, which is exactly the copy option B removes.
Related #14122 · same shape as #15210 (the by-shape sweep) and #14512 (the emitter half). Found while implementing #15006; deliberately not folded into it, because the answer is a decision rather than a read fix.
The sites
file:linepackages/cli/src/commands/serve.ts:2622let plugins = config.plugins || [];packages/cli/src/commands/serve.ts(a few lines below)if (flags.dev && config.devPlugins) plugins = [...plugins, ...config.devPlugins];packages/cli/src/utils/schema-migration-plugins.ts:1082const hostPlugins = Array.isArray(config?.plugins) ? config.plugins : [];pluginsanddevPluginsare both package-owned collections by the derivation every reader in this program uses —COMPOSE_KEY_DISPOSITIONSgives them'concat', so they are members ofAssembledPackageBodySchema, soObjectStackDefinitionSchema∩AssembledPackageBodySchemacontains them. (Both appear in the 37-key list #15210 prints.) An option-B-shaped stack therefore carries them underpackages[i].manifest.pluginsand not at the top level, and all three reads above see nothing.Why the one-line fix that every other site in the program takes is WRONG here
Everywhere else in the reader program the repair is "resolve
packages[]when the top level is absent". Applying that here would handkernel.use()whatever sits inpackages[i].manifest.plugins— and on the artifact path that is inert JSON, not a live plugin instance. A JSON artifact cannot carry a constructed plugin. So the mechanical repair converts a silent skip into a boot that registers garbage.That points at a question upstream of the CLI, which is why this is filed rather than fixed:
plugins/devPluginsbe in the assembled-package-body key set at all? They are the only members of it whose values are live objects rather than serializable metadata. If they should not be, this is apackages/specdisposition change (COMPOSE_KEY_DISPOSITIONS), not a CLI change, and every reader in the program inherits the answer.pluginsmean inside an artifact — a dropped key, or a declaration the loader is supposed to resolve to a real module?Not measured, and why
The #15004 acceptance zoo declares no
plugins, so no probe row covers this today and the loss above is derived from the key set rather than observed. Addingpluginsto that fixture is fixture surgery three parallel cards are reading, so it was left alone. A row belongs here once question 1 is answered — before that, a row would pin a shape that may be about to change.Reached in practice by any multi-package host that composes plugins per package:
composeStacksconcatenates them to the top level today, which is exactly the copy option B removes.