Uh oh!
There was an error while loading. Please reload this page.
feat(core): retire the inert PluginMetadata surfaces — configSchema with PluginConfigValidator, and hotReloadable (ADR-0049) - #12689
Conversation
…tor under ADR-0049 (#11982) The kernel-owned plugin-config-validation surface could never run: the loader's one call site passed no config, plugin factories close over their config so the kernel never receives it, and zero plugins declared a configSchema (measured with positive controls; maintainer ruled Option B, 2026-08-27, decision-inbox batch 5). - remove PluginMetadata.configSchema and the always-early-returning validatePluginConfig path from PluginLoader - delete PluginConfigValidator / createPluginConfigValidator and their unit test; unpublish them from the security barrel - record the retirement in ADR-0025 section 3.7: re-declaring a kernel-owned config-validation surface is a fresh decision for the day the distribution layer lands, with the zero-caller measurement as starting evidence - drop the ADVANCED_FEATURES.md section whose example promised 'Config is validated before init is called' — false on this ref - pin the retirement: barrel no longer exports the validator (runtime), a declared configSchema no longer type-checks (compile-time, via the type-check DEBT ratchet), startupTimeout as the live-sibling positive control Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LZbWd2jNV1FErXTPSS4Dry
Declared 'Whether plugin supports hot reload' and documented, with zero reads and zero declarations: HotReloadManager.reloadPlugin gates only on its own registered reload configs, so hotReloadable: false was hot-reloaded identically to true — a reload-safety assurance the runtime never honoured (maintainer ruled Option B, 2026-08-27, decision-inbox batch 5, same batch as the configSchema retirement). - remove PluginMetadata.hotReloadable and its ADVANCED_FEATURES.md line - drop the field from ADR-0025's present-capability inventory (the section 3.7 record already names this sibling retirement; the ADR's distribution-layer design prose keeps its forward-looking mentions) - pin: a declared hotReloadable no longer type-checks (compile-time, via the type-check DEBT ratchet) - add the family changeset covering both retirements (@objectstack/core minor under the lockstep launch-window convention, with the ADR-0087 runtime-interface-only disposition) Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LZbWd2jNV1FErXTPSS4Dry
…am (#11982, #12587) check:type-check-coverage correctly refused the first shape: @objectstack/core has no typecheck script (type-check DEBT ledger entry), so a @ts-expect-error in core is a phantom pin no tsc program a typecheck script runs would evaluate, and PHANTOM_PIN_DEBT is closed to new entries. - core keeps the RUNTIME pins (security barrel no longer publishes PluginConfigValidator / createPluginConfigValidator, live-sibling positive control on the namespace) - the COMPILE-TIME pins move to packages/rest, whose tsconfig.test.json program is run by its typecheck script (check:test-typecheck, EXACT per-file ratchet) and resolves @objectstack/core to the BUILT dist .d.ts — so the directives pin the published contract consumers actually see, in the package that carries the retirement's worked replacement (#11637 seam parse) Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LZbWd2jNV1FErXTPSS4Dry
…tire-plugin-config-schema
📓 Docs Drift CheckThis PR changes 1 package(s): 6 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 1 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 23 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 7358479711b239f5838b06d052aeeef63776aca5 && git checkout 7358479711b239f5838b06d052aeeef63776aca5
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 284fc22d8834f2c9a3530592d6614e0eb6a28590 973e858bfcc4e5dbc96b3a7cfaec1145de4a5b93 && git checkout -B drift-repro 284fc22d8834f2c9a3530592d6614e0eb6a28590 && git merge --no-ff 973e858bfcc4e5dbc96b3a7cfaec1145de4a5b93
node scripts/docs-audit/affected-docs.mjs --json 284fc22d8834f2c9a3530592d6614e0eb6a28590
|
#11982, #12587) Docs-drift pass over the six pages the drift bot anchored to this diff: - plugins/anatomy.mdx: the Plugin-class example carried the retired configSchema field with a comment claiming the loader validates it; the block now shows constructor-owned config and names the retirement - plugins/index.mdx: the Configuration Validation bullet and the configSchema half of the securePlugin example removed (signature kept - live surface); replacement paragraph states the plugin-owned self-parse pattern - protocol/kernel/index.mdx: the Configuration Management snippet and the fail-fast callout no longer document the retired field as merely 'postponed' - both now state the retirement and the self-parse seam No change, with the reason measured per page: automation/flows.mdx names the ADR-0018 node-executor configSchema (different surface); getting-started/quick-reference.mdx names spec's plugin-validator.zod.ts PluginMetadata (locally-declared homonym, live); protocol/kernel/plugin-spec.mdx teaches the surviving self-parse pattern - its phantom manifest file-map row predates this diff and is filed as #12690. content/docs/releases/v17.mdx is release-owned and untouched; its four configSchema mentions are all the ADR-0018 / driver surfaces, none the kernel field. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LZbWd2jNV1FErXTPSS4Dry
…tire-plugin-config-schema
os-elon
commented
Aug 28, 2026
Marking ready and merging directly on the maintainer's explicit order — provenance: maintainer, 2026-08-28, live director session (session This PR touches Generated by Claude Code |
Uh oh!
There was an error while loading. Please reload this page.
Fixes#11982
Fixes#12587
🚨 Governed surface — this PR touches
docs/adr/0025-plugin-package-distribution.md, so it stays a DRAFT for the maintainer to hand-merge. Never flip it ready, never enqueue it, never arm auto-merge — hand-merge IS the review record.The ruling (2026-08-27, decision-inbox batch 5 — not re-adjudicable)
Maintainer, verbatim, untranslated: 「同意」 — accepting recommendation B, retire under ADR-0049 enforce-or-remove, presented paired across both cards with the ADR-0025 lever stated explicitly. Option C stayed measured-out (no caller holds a config); Option A was declined (zero pull; the ~40 production
kernel.use()compositions pass config via constructors and work).What lands, one commit per card
Commit 1 — #11982 (
configSchema/PluginConfigValidator)PluginMetadata.configSchemaremoved; the always-early-returningvalidatePluginConfigpath, theconfigValidatorfield and its construction removed fromPluginLoader(packages/core/src/plugin-loader.ts, with an ADR-0049/ADR-0025 retirement note left in the interface).packages/core/src/security/plugin-config-validator.tsand its unit test deleted; the two exports removed frompackages/core/src/security/index.tswith a retirement note.PluginConfigValidatorcan never run:PluginLoadercalls its ownvalidatePluginConfig(metadata)with no config, and a plugin factory closes over its config so the kernel never receives it #11982's zero-caller measurement as its starting evidence. Three factual inventory mentions of the validator in the same ADR were aligned with the record (the "can already load" list, the manifest example's comment, the reference-file row). The forward-looking design prose (§3.5 step 7, the open question on hot updates) deliberately keeps its mentions — it describes the to-be-built layer, which §3.7 now marks as a fresh decision.packages/core/ADVANCED_FEATURES.mdsection 10 deleted — its example carriedconfigSchemabeside the comment claiming config is validated before init runs, which is false on the current ref — plus theconfigSchemarow in the API reference list.Commit 2 — #12587 (
hotReloadable)PluginMetadata.hotReloadableremoved (retirement note in the interface); theADVANCED_FEATURES.mdreference row removed; the field dropped from ADR-0025's present-capability inventory line.HotReloadManagerneeded no change — it never consulted the field, which is the defect.Commit 3 — pin relocation (both cards)
pnpm check:type-check-coveragecorrectly refused the first pin shape:@objectstack/corehas notypecheckscript (type-check DEBT ledger entry), so a@ts-expect-errorthere is a phantom pin, andPHANTOM_PIN_DEBTis closed to new entries. Final architecture:packages/core/src/plugin-loader.retired-fields.pin.test.ts— RUNTIME pins: the security barrel no longer publishesPluginConfigValidator/createPluginConfigValidator, with a live-sibling positive control on the namespace.packages/rest/src/plugin-metadata-retired-fields.pin.test.ts— COMPILE-TIME pins for both retired fields against the builtdist/index.d.tsof@objectstack/core(rest'stsconfig.test.jsonprogram, run by itstypecheckscript viacheck:test-typecheck, EXACT per-file ratchet). Positive control: astartupTimeoutliteral keeps compiling with no directive. Rest is also the retirement's worked replacement —RestApiConfigSchemaconstrainsapi.versionwith a regex the REST server never runs — the seam casts instead of parsing, soapi.version: ''is accepted and mounts the whole API at/api//#11637 landed the seam-side parse there.Commit 4 — docs-drift pass (PM-directed, both cards)
The docs-drift bot returned SUBSTANTIVE for this diff: six hand-written pages anchored to
configSchema/PluginMetadata. Per-page dispositions below in "Docs-drift pass".Changeset (
.changeset/retire-plugin-metadata-inert-fields.md):@objectstack/coreminor with a BREAKING declaration — the launch-window lockstep convention ships breaking changes as minor andcheck:changeset-no-majorrefuses majors repo-wide (precedent: the two recent spec retirements). ADR-0087 disposition:not-required (runtime-interface-only packages/core/src/plugin-loader.ts#PluginMetadata)— no Zod schema, no spec declaration, no stored representation; the compiler is the notification channel. Verified green bycheck:adr-0087-registration.Premises re-verified on the current ref before any edit
Every PM brief premise was re-measured at base
4bd6faa2:validatePluginConfighad callers only in the loader (3), the validator itself (2) and its own test (6) — thepackages/resthits are prose comments, not calls;hotReloadablehad only its declaration and doc line in core (ADR/design-doc prose aside); positive controlstartupTimeoutfired (kernel.ts 4, kernel.test.ts 8); the barrel published the validator at lines 39–40; the ADR filename0025-plugin-package-distribution.mdis correct. The pinned objectui sibling (.objectui-sha190fbd01) was fetched at its pin and greps ZERO forPluginConfigValidator,hotReloadable, and does not depend on@objectstack/coreat all (control fired on its own tree).premise_still_valid: true.Ablation (predicted in writing before the mutation — prediction files in the run log)
Shipping-channel leg, with the dist preflight both ways: re-adding
hotReloadableto the interface and REBUILDING core's dist (marker confirmed absent indist/index.d.tsbefore, present after: 0 then 1) turnedcheck:test-typecheckRED with exactly 1 error insrc/plugin-metadata-retired-fields.pin.test.ts(TS2578, unused directive) — direction and count exactly as predicted; theconfigSchemadirective stayed satisfied and thestartupTimeoutcontrol stayed clean and present in dist (count 3, both legs). Restore leg proven on disk:git diff HEADempty, src anchor 0, dist anchor back to 0 after the restore rebuild, gate back to its own OK line. An earlier same-package ablation of the first pin shape measured the DEBT-ratchet channel too (98 to 99, one TS2578) before the coverage gate ruled that residence out.Docs-drift pass (PM-directed) — what was read and what changed, per page
Edited (3):
content/docs/plugins/anatomy.mdx— the Plugin-class example declared the retiredconfigSchemawith a comment saying the loader reads it to validate config. Replaced with constructor-owned config naming the ADR-0049 retirement; unused zod import dropped.content/docs/plugins/index.mdx— the "Configuration Validation" bullet and theconfigSchemahalf of thesecurePluginexample removed (thesignaturehalf kept — live surface, exclusion list). A replacement paragraph states the plugin-owned self-parse pattern and the retirement.content/docs/protocol/kernel/index.mdx— the "Configuration Management" snippet documented the field as recorded-but-postponed; rewritten to state the retirement and the self-parse seam (the settings-service half of the example kept). The fail-fast Callout likewise rewritten: config is not part of the fail-fast path because the field is retired, not because the check is postponed.Read, no change needed — with the reason measured (3):
content/docs/automation/flows.mdx— itsconfigSchemais the ADR-0018 node-executor schema ("the registered executor'sconfigSchema"), a different surface entirely.content/docs/getting-started/quick-reference.mdx— itsPluginMetadatais the export of spec'splugin-validator.zod.ts, a locally-declared homonym that is live and untouched.content/docs/protocol/kernel/plugin-spec.mdx— its "Configuration Schema" section and best-practice 4 teach the plugin-owned self-parse pattern (configSchema.parse(this.options)ininit) — the surviving replacement, not the retired kernel field. Its manifest example'sconfigSchema: 'src/config.schema.ts'file-map row names a keyManifestSchemanever declared — a pre-existing inaccuracy NOT falsified by this diff, filed as content/docs/protocol/kernel/plugin-spec.mdx documents a manifestconfigSchemafile-map key that ManifestSchema never declared #12690 rather than edited here.Release-owned page, read only (⛔ not edited):
content/docs/releases/v17.mdxcarries fourconfigSchemamentions and every one is a different surface: line ~532 "A node'sconfigstays an open record: it is per-node-type, owned by the executor'sconfigSchemaand the conversion layer" (ADR-0018); line ~582 "An earlier version of this note said the driver's ownconfigSchemadid that, which was wrong for two releases: the field existed, nothing read it" (driver surface, historical); lines ~1949 and ~2242 are flow-designer/ADR-0018 again. None advertisesPluginMetadata.configSchemaor kernel plugin-config validation as a v17 capability, so this retirement falsifies nothing on that page — it is a historical record of other surfaces and needs no routing.Blind-spot hand sweep (the bot cannot see behavioural descriptions): grepped all hand-written docs for
PluginConfigValidator/createPluginConfigValidator/ADVANCED_FEATURES/hotReloadable— zero hits outsidereleases/; behavioural phrasings ("config validated", "validates plugin config", hot-reload wording) — the only hits are connectorproviderConfig(a live, unrelated boot validation) and the auto-generatedreferences/tree describing spec'sHotReloadConfig/manifest.loading(live or already-tombstoned surfaces, regenerated — not hand-edited). The two changed files that yielded no anchors (ADVANCED_FEATURES.md,security/index.ts) got the same sweep by identifier: no hand-written page documents the security barrel's export list or mirrors ADVANCED_FEATURES content.Verification — three heads, each quoted from the run that produced it
node scripts/pm/dispatch-gates.mjs, no paths — answer taken from objectstack-ai/objectstack at this branch): 27 path-derived + 6 convention-triggered families all run at heada6c83156; every one exited 0 by its own verdict line, exceptscripts/pm/check-half-states.mjswhich is NOT MEASURED on this seat (exit 3, PREREQUISITE NOT MET: the container token is a proxy placeholder and the tool refuses to sweep anonymously — its own text says the result "says NOTHING about whether the board carries half-states"; CI runs it with real credentials).67da0f09— doc-frontmatter, doc-route-spelling, docs-section-name, doc-security-posture, spec check:docs / empty-state / liveness / skill-examples / strictness-ledger / variant-docs, dispatcher-error-vocabulary, doc-anchors, docs-audit-scope, docs-redirects, docs-single-h1, published-readme-links, react-page-adapter-contract, role-word, doc-authoring, doc-formula-expressions, docs-affected, docs-drift-comment, ci-filter-parity, cross-package-test-inputs, nul-bytes. The final merge (origin/mainat0d4a6a85) brought no content/docs changes, so those readings carry to the final head unchanged.973e858b:check:type-check-coverage0 ·check:type-check-debt0 (its own line: 31 ledger entries re-measured, 1687 raw errors total, none above recorded) · restcheck:test-typecheck0 (my pin file at zero errors outside the ledger) ·check:engine-double-contract0 ·check:where-matcher0 ·check:query-options-erasure0 ·pnpm --filter @objectstack/core test0 (40 files / 999 tests passed).check:nul-bytes: OK (its own line: 7042 text files, no raw control bytes).Serial constraint and scope notes
authz.invalidatedchannel, boot-time posture statement (#11968) #12652 (the other editor ofpackages/core/src/security/index.ts) landed on main before this branch was cut; the branch is based on it andorigin/mainwas merged before opening this PR (that merge brought only ADR-0127) and again at final push time (0d4a6a85— 26 files, none in packages/core, packages/rest, packages/spec/src, and no lockfile move). Barrel re-verified after each merge.startupTimeout,signature,healthCheckuntouched.packages/specuntouched (spec seat's territory).packages/core/REFACTORING_SUMMARY.mddeliberately untouched — a historical record outside both cards' ruled scope; its section 5 claims are now recorded as packages/core/REFACTORING_SUMMARY.md section 5 claims config validation that never ran, and now describes a retired mechanism #12688 (filed unassigned,finding).packages/rest/src/rest-server.tsandrest-config-parse-not-cast.test.tsthat mention the validator historically are kept — they explain why the seam parse exists, in the past tense.Generated by Claude Code
Generated by Claude Code