Skip to content

Four of the five declared plugin lifecycle hooks are never invoked — and the metadata-type registry docs tell plugins to use one of them #4212

Description

@os-zhuang

Found while exempting onEnable from the new top-level authoring-key lint (#4167 / #4189). Checking which lifecycle hooks the runtime actually honours turned up that only one of the five does.

The finding

PluginLifecycleSchema (packages/spec/src/kernel/plugin.zod.ts:80-90) declares five hooks, each with descriptive TSDoc, and PluginLifecycleHooks (packages/spec/src/system/types/plugin.ts) mirrors them:

hook declared invoked anywhere
onInstall 0 call sites
onEnable 3 (packages/runtime/src/app-plugin.ts)
onDisable 0 call sites
onUninstall 0 call sites
onUpgrade 0 call sites

Method: repo-wide grep over packages/, apps/, examples/ excluding dist/ and tests, for both direct (.onInstall() and indexed ([hook]() invocation, plus a check for any dynamic lifecycle dispatch table. Nothing. onEnable is the only hook with a runtime that runs it.

onUpgrade has an entire supporting type built for it — UpgradeContextSchema with previousVersion / newVersion / isMajorUpgrade (plugin.zod.ts:59-76), exported from the package root — for a callback nothing ever calls.

The part that actively misleads

packages/spec/src/kernel/metadata-type-schemas.ts documents onInstall as the place to call two registration functions:

:134  * Plugins that introduce custom metadata types — declared through
      * `additionalTypes` on `MetadataPluginConfig` — should call this from
      * their `onInstall` hook so the engine's `/meta/types/:type` endpoint
      * starts emitting a real JSON Schema for them.

:167  * Plugins call this from `onInstall` to layer actions onto any type —
      * built-in or custom — without forking the registry.

A plugin author who follows that instruction exactly registers nothing. registerMetadataTypeSchema / registerMetadataTypeActions never run, /api/v1/meta/types/:type keeps emitting no schema and no type-level buttons, and there is no error — the hook simply never fires. That is the same silent-drop shape as #3786/#4148/#4167, one layer up: the guidance is the thing that's dead, not just the property.

Why it matters

  • declared ≠ enforced (Prime Directive chore: version packages #10) on the plugin system's own contract. packages/spec is the constitution; five hooks are written into it and four have no implementation.
  • It is load-bearing advice, not just an unused field. The metadata-type registry is the documented extension point for custom metadata types, and its documented entry point does not exist.
  • It propagates: packages/cli/src/utils/config.ts:93 and packages/cli/src/commands/serve.ts:676 both name onDisable alongside onEnable when preserving module-level named exports, so the CLI carefully carries a hook to a runtime that will not call it.

Suggested resolution — the spec-property-retirement playbook

Per ADR-0049 enforce-or-remove, each of the four needs a decision, and they may not all get the same one:

  1. onInstall — highest priority, because it has documented consumers. Either implement it (the metadata-type registry needs some install-time seam) or rewrite both TSDoc blocks to name a hook that runs. Rewriting the docs to say onEnable is probably the cheap correct answer, but only if onEnable's timing is actually right for registry population — worth checking rather than assuming.
  2. onDisable / onUninstall — no consumers found, no callers. Retire, or implement as part of a real enable/disable lifecycle if one is planned.
  3. onUpgrade — retire together with UpgradeContextSchema, or wire it into the package-upgrade path that already exists (packages/spec/src/kernel/package-upgrade.ts), which looks like where it was meant to go.

Whichever way each goes, the lint family this came out of would like a note: onEnable is now in STACK_RUNTIME_MEMBERS precisely because the runtime honours it, and onDisable is deliberately excluded because it does not. If onDisable ever becomes real, that exclusion has to move with it — a test in packages/spec/src/kernel/metadata-authoring-lint.test.ts pins the current distinction and will fail loudly, which is the intended alarm.

I have not implemented anything here; #4189 only documents the current state where it had to.

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions