Skip to content

feat(core): retire the inert PluginMetadata surfaces — configSchema with PluginConfigValidator, and hotReloadable (ADR-0049) - #12689

Merged
os-elon merged 6 commits into
mainfrom
claude/issue-11982-retire-plugin-config-schema
Aug 28, 2026
Merged

feat(core): retire the inert PluginMetadata surfaces — configSchema with PluginConfigValidator, and hotReloadable (ADR-0049)#12689
os-elon merged 6 commits into
mainfrom
claude/issue-11982-retire-plugin-config-schema

Conversation

@claude

@claudeclaudeBot commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

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.configSchema removed; the always-early-returning validatePluginConfig path, the configValidator field and its construction removed from PluginLoader (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.ts and its unit test deleted; the two exports removed from packages/core/src/security/index.ts with a retirement note.
  • ADR-0025 §3.7 Config bullet rewritten to record the retirement: re-declaring a kernel-owned config-validation surface is a fresh decision for the day the distribution layer lands, with 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'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.
  • Either-way item (binding): packages/core/ADVANCED_FEATURES.md section 10 deleted — its example carried configSchema beside the comment claiming config is validated before init runs, which is false on the current ref — plus the configSchema row in the API reference list.

Commit 2 — #12587 (hotReloadable)

  • PluginMetadata.hotReloadable removed (retirement note in the interface); the ADVANCED_FEATURES.md reference row removed; the field dropped from ADR-0025's present-capability inventory line. HotReloadManager needed no change — it never consulted the field, which is the defect.

Commit 3 — pin relocation (both cards)

pnpm check:type-check-coverage correctly refused the first pin shape: @objectstack/core has no typecheck script (type-check DEBT ledger entry), so a @ts-expect-error there is a phantom pin, and PHANTOM_PIN_DEBT is closed to new entries. Final architecture:

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 and check:changeset-no-major refuses 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 by check:adr-0087-registration.

Premises re-verified on the current ref before any edit

Every PM brief premise was re-measured at base 4bd6faa2: validatePluginConfig had callers only in the loader (3), the validator itself (2) and its own test (6) — the packages/rest hits are prose comments, not calls; hotReloadable had only its declaration and doc line in core (ADR/design-doc prose aside); positive control startupTimeout fired (kernel.ts 4, kernel.test.ts 8); the barrel published the validator at lines 39–40; the ADR filename 0025-plugin-package-distribution.md is correct. The pinned objectui sibling (.objectui-sha 190fbd01) was fetched at its pin and greps ZERO for PluginConfigValidator, hotReloadable, and does not depend on @objectstack/core at 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 hotReloadable to the interface and REBUILDING core's dist (marker confirmed absent in dist/index.d.ts before, present after: 0 then 1) turned check:test-typecheck RED with exactly 1 error in src/plugin-metadata-retired-fields.pin.test.ts (TS2578, unused directive) — direction and count exactly as predicted; the configSchema directive stayed satisfied and the startupTimeout control stayed clean and present in dist (count 3, both legs). Restore leg proven on disk: git diff HEAD empty, 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 retired configSchema with 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 the configSchema half of the securePlugin example removed (the signature half 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 — its configSchema is the ADR-0018 node-executor schema ("the registered executor's configSchema"), a different surface entirely.
  • content/docs/getting-started/quick-reference.mdx — its PluginMetadata is the export of spec's plugin-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) in init) — the surviving replacement, not the retired kernel field. Its manifest example's configSchema: 'src/config.schema.ts' file-map row names a key ManifestSchema never declared — a pre-existing inaccuracy NOT falsified by this diff, filed as content/docs/protocol/kernel/plugin-spec.mdx documents a manifest configSchema file-map key that ManifestSchema never declared #12690 rather than edited here.

Release-owned page, read only (⛔ not edited):content/docs/releases/v17.mdx carries four configSchema mentions and every one is a different surface: line ~532 "A node's config stays an open record: it is per-node-type, owned by the executor's configSchema and the conversion layer" (ADR-0018); line ~582 "An earlier version of this note said the driver's own configSchema did 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 advertises PluginMetadata.configSchema or 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 outside releases/; behavioural phrasings ("config validated", "validates plugin config", hot-reload wording) — the only hits are connector providerConfig (a live, unrelated boot validation) and the auto-generated references/ tree describing spec's HotReloadConfig / 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

  • Full derived gate union (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 head a6c83156; every one exited 0 by its own verdict line, except scripts/pm/check-half-states.mjs which 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).
  • After the docs pass, the union was RE-DERIVED (the content/docs paths add ~18 doc families): all 25 gates of that batch exited 0 at head 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/main at 0d4a6a85) brought no content/docs changes, so those readings carry to the final head unchanged.
  • Ratchet families and tests re-run on the FINAL head 973e858b: check:type-check-coverage 0 · check:type-check-debt 0 (its own line: 31 ledger entries re-measured, 1687 raw errors total, none above recorded) · rest check:test-typecheck 0 (my pin file at zero errors outside the ledger) · check:engine-double-contract 0 · check:where-matcher 0 · check:query-options-erasure 0 · pnpm --filter @objectstack/core test 0 (40 files / 999 tests passed).
  • check:nul-bytes: OK (its own line: 7042 text files, no raw control bytes).

Serial constraint and scope notes


Generated by Claude Code


Generated by Claude Code

…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
@github-actions

github-actionsBot commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/core, touching 14 documentable anchor(s). ⚠️2 changed file(s) yielded no anchor (packages/core/ADVANCED_FEATURES.md, packages/core/src/security/index.ts), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

6 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/automation/flows.mdx(via configSchema (symbol))
  • content/docs/getting-started/quick-reference.mdx(via PluginMetadata (symbol))
  • content/docs/plugins/anatomy.mdx(via configSchema (symbol))
  • content/docs/plugins/index.mdx(via PluginMetadata (symbol), configSchema (symbol))
  • content/docs/protocol/kernel/index.mdx(via configSchema (symbol))
  • content/docs/protocol/kernel/plugin-spec.mdx(via configSchema (symbol))

1 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v17.mdx(via configSchema (symbol))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • 2 changed file(s) yielded no anchor (packages/core/ADVANCED_FEATURES.md, packages/core/src/security/index.ts) — pages documenting those are invisible to this run
  • 2 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 47 of 219 client-bound route-ledger rows — the other 172 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 172: 14 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 56 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 102 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 23 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 284fc22d8834f2c9a3530592d6614e0eb6a28590packageMentionDocs.

Which tree this was computed on

This run read content/docs from 7358479711b239f5838b06d052aeeef63776aca5 — the merge of head 973e858bfcc4e5dbc96b3a7cfaec1145de4a5b93 into base 284fc22d8834f2c9a3530592d6614e0eb6a28590, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# 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

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 284fc22d8834f2c9a3530592d6614e0eb6a28590 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

#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
@os-elonClaude

Copy link
Copy Markdown
Collaborator

Marking ready and merging directly on the maintainer's explicit order — provenance: maintainer, 2026-08-28, live director session (session session_016SG9S6V15MqeAgkehDcTwk), verbatim: 「12718 12736 12689 我都批准了,为什么合并队列会踢出,你帮我合并」.

This PR touches docs/adr/0025-plugin-package-distribution.md (governed surface) and was correctly held draft; the maintainer's approval + this recorded order is the review record, and the ready-flip exists solely to execute the ordered merge — ⛔ not a queue entry. mergeable_state: clean at merge time.


Generated by Claude Code

@os-elon
os-elon marked this pull request as ready for review August 28, 2026 07:03
@os-elon
os-elon requested a review from hotlong as a code ownerAugust 28, 2026 07:03
@os-elon
os-elon added this pull request to the merge queueAug 28, 2026
Merged via the queue into main with commit 49f0dcfAug 28, 2026
38 checks passed
@os-elon
os-elon deleted the claude/issue-11982-retire-plugin-config-schema branch August 28, 2026 07:26
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/lteststooling

Projects

None yet

3 participants

@os-elon@os-zhuang@claude