Skip to content

PluginConfigValidator can never run: PluginLoader calls its own validatePluginConfig(metadata) with no config, and a plugin factory closes over its config so the kernel never receives it #11982

Description

@os-zhuang

Found while implementing #11637 (the REST server config seam). Filed, not fixed — #11637's declared surface is packages/rest/src/rest-server.ts, and this is a packages/core mechanism with its own gate family.

#11637 offered "declare configSchema on the REST plugin and let the kernel's plugin-config-validator do it" as one of its two candidate fixes. Measuring that candidate is what turned this up: the mechanism cannot run at all, for any plugin, so the candidate was structurally unavailable and #11637 landed the seam-side parse instead.

What was measured

On origin/main @ 7899f5745.

1. The one call site passes no config. packages/core/src/plugin-loader.ts:157-159:

if (metadata.configSchema) {
    this.validatePluginConfig(metadata);   // <- no second argument
}

and :406-419:

private validatePluginConfig(plugin: PluginMetadata, config?: any): void {
    if (!plugin.configSchema) return;
    if (config === undefined) {
         // In loadPlugin, we often don't have the config yet.
         // We skip validation here or valid against empty object if schema allows?
         // For now, let's keep the logging behavior but note it's delegating
         this.logger.debug(`Plugin ${plugin.name} has configuration schema (config validation postponed)`);
         return;
    }
    this.configValidator.validatePluginConfig(plugin, config);
}

config is always undefined here, so the early return always fires. "Postponed" to nothing: git grep validatePluginConfig finds no other caller outside PluginConfigValidator's own unit test.

2. There is nothing for it to postpone to. kernel.use(plugin) (packages/core/src/kernel.ts:198) hands pluginLoader.loadPlugin(plugin) the Plugin object only. A plugin factory captures its config in a closure — createRestApiPlugin(config) is the model — so no config value ever crosses into the kernel, and PluginMetadata carries no field for one. Even a correct validatePluginConfig(metadata, config) call would have nothing to pass.

3. No kernel plugin in the repo declares configSchema. git grep -n "configSchema:" packages -- ':!**/dist/**' ':!**/CHANGELOG.md' returns 20 hits and none is a kernel Plugin: they are automation node-executor JSON schemas (service-automation/src/builtin/*), the datasource driver catalog, plugin-approvals' approval-node descriptor, and spec/test declarations. So PluginConfigValidator — ~200 lines with its own unit test, its own formatZodErrors, validatePartialConfig and getDefaultConfig — has zero live consumers.

Why it matters

PluginMetadata.configSchema reads, at every author's call site, as the declared way to validate a plugin's configuration. It is documented as such (packages/core/ADVANCED_FEATURES.md:304, ADR-0025's "Config" section: "PluginConfigValidator validates plugin config against the plugin's schema"). A plugin author who declares one gets a debug line and no validation — the declared-not-enforced shape, one layer up from the one #11637 closes.

Not prejudged

Three shapes, and the choice is a real call:

Worth measuring before choosing: whether ADR-0025's manifest-driven load path ("configuration" in the plugin manifest) has a live call site that would have a config in hand, since that is the one caller for which the mechanism was designed.


Generated by Claude Code

Metadata

Metadata

Assignees

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions