refactor(spec)!: remove the plugin sandboxing / integrity / approval config that never existed (#3896 follow-up) - #3950
Merged
Conversation
…ig that never existed (#3896) DynamicLoadingConfigSchema, PluginDiscoveryConfigSchema and PluginDiscoverySourceSchema declared a plugin security control set — defaultSandbox, requireIntegrity, allowedSources, and discovery's requireApproval ("require admin approval before loading discovered plugins"). None of it was ever wired to anything. The three schemas were an island: not composed into any parent schema, not read by any runtime, referenced only by their own round-trip tests. They were nonetheless published into json-schema/ and the authorable key surface, where an author — very often an AI (ADR-0033) — reads them as capabilities this platform has. Someone reading the spec could reasonably conclude ObjectStack sandboxes dynamically loaded plugins. It does not. That is the ADR-0049 false-compliance shape, and the precedent for a SAFETY-shaped instance is to remove rather than mark dead: tool.requiresConfirmation was pruned in #3715 because it was "unenforced on every path, so it was false compliance, not merely dead". Same case, one layer up. Found while building the empty-state gate (#3945): allowedSources documented [] as admitting every source, and checking who enforced that turned up nobody. No retiredKey() tombstones, deliberately. A tombstone earns its keep by making a removal audible at a parse the author actually reaches — and nothing parses these schemas, so the prescription could never be delivered. The silent-strip the key-vanish guard exists to prevent was already these keys' permanent condition: writing one has always been a no-op, because no parent schema ever accepted them. The baseline entries in json-schema.manifest.json and authorable-surface.json are dropped here as the deliberate removal both files document as the legitimate path, rather than tombstoning 15 keys nobody could have successfully authored. Breaking in the narrow sense: kernel/index.ts re-exports this module with `export *` and ./kernel is a published subpath, so the types were importable. Nothing in this repo imported them; it folds into the unreleased 17.0.0. The rest of the file is untouched, including ActivationEventSchema — the one export in it with real consumers. The remainder (DynamicLoadRequest, DynamicUnloadRequest, DynamicPluginResult, PluginSource, DynamicPluginOperation) also has no runtime consumer, but those are operation contracts rather than security promises, so the enforce-or-remove call on them is a design decision, not a correction. spec: 263 files / 6856 tests green, tsc clean, all six generated-artifact and liveness gates pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QuViRSR1j6GJjf9qGbnqFX
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Contributor
📓 Docs Drift CheckThis PR changes 1 package(s): 105 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
…in-loading exports The third guard on a deliberate removal, after json-schema.manifest.json and authorable-surface.json: check:api-surface diffs the public export list and correctly reported 7 exports gone from ./kernel. Same exemption rationale as the other two baselines — the removal is the point of the PR, and the major changeset is already in place. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QuViRSR1j6GJjf9qGbnqFX
…napshot came from stale dist build-api-surface.ts reads the built dist (its header says to run it after a build). The previous regeneration ran against a dist compiled before this branch's removal — so the snapshot KEPT the 7 removed plugin-loading exports and DELETED six exports that had landed on main since (UpdateManyRecord*, ReportSort*, reportSelectionOrder). CI read that as 7 breaking + 6 added. Rebuilt, regenerated: the diff is now exactly the intended shape — the seven plugin-loading exports gone, the six main-side exports restored. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QuViRSR1j6GJjf9qGbnqFX
os-zhuang
marked this pull request as ready for review
July 29, 2026 23:35
This was referenced Jul 30, 2026
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Follow-up to #3945 (merged). Found while building the empty-state gate:
allowedSourcesdocumented[]as admitting every source, and checking who enforced that turned up nobody.A security control set that was never wired to anything
DynamicLoadingConfigSchema,PluginDiscoveryConfigSchemaandPluginDiscoverySourceSchemadeclared:defaultSandboxrequireIntegrityallowedSourcesdiscovery.requireApprovalNone of it was ever wired to anything. The three schemas were an island — not composed into any parent schema, not read by any runtime, referenced only by their own round-trip tests. They were nonetheless published into
json-schema/and the authorable key surface, where an author (very often an AI, ADR-0033) reads them as capabilities this platform has.Someone reading the spec could reasonably conclude ObjectStack sandboxes dynamically loaded plugins. It does not.
That is the ADR-0049 false-compliance shape, and the precedent for a SAFETY-shaped instance is to remove rather than mark dead:
tool.requiresConfirmationwas pruned in #3715 because it was "unenforced on every path, so it was false compliance, not merely dead". Same case, one layer up.No tombstones, deliberately — and how the guard was cleared
build-schemas.tsguards removals in two stages, and both fired:json-schema.manifest.jsonin the same PR. Done.retiredKey()tombstones + a D2 conversion + a major changeset.Stage 2's remedy is not applicable here, and the exemption is the interesting part of this PR. A tombstone earns its keep by making a removal audible at a parse the author actually reaches — and nothing parses these schemas. The prescription could never be delivered. Meanwhile the silent-strip the guard exists to prevent was already these keys' permanent condition: writing one has always been a no-op, because no parent schema ever accepted them. Removing them does not create that failure mode; it removes the documentation of a thing that never worked.
So the baseline entries in
json-schema.manifest.jsonandauthorable-surface.jsonare dropped here as the deliberate removal both files document as legitimate, rather than tombstoning 15 keys nobody could have successfully authored. The file itself carries a block comment recording this reasoning where the schemas used to be, so the next reader does not have to reconstruct it.Breaking, in the narrow sense
packages/spec/src/kernel/index.tsre-exports the module withexport *, and./kernelis a published subpath — soDynamicLoadingConfig,PluginDiscoveryConfig,PluginDiscoverySourceand their schemas were importable as types. Nothing in this repo imported them. Markedmajorbecause removing a public export is breaking regardless of use; in practice it folds into the unreleased17.0.0.Scope — what is deliberately left alone
ActivationEventSchemastays: it is the one export in this file with real consumers (7).The remainder —
DynamicLoadRequest,DynamicUnloadRequest,DynamicPluginResult,PluginSource,DynamicPluginOperation— also has no runtime consumer today, so the whole file exceptActivationEventSchemais dead surface. It is left in place because those are operation contracts, not security promises: the enforce-or-remove call on them is a design decision, not a correction, and therequiresConfirmationprecedent draws its line at exactly that distinction.Rebuilding this surface is a design job, not a schema job: write the runtime first, then declare only what it enforces.
Verification
packages/spec: 263 files, 6856 tests, all passing (−2: the removed schemas' round-trip tests).tsc --noEmitclean.check:docs,check:liveness,check:empty-state,check:spec-changes,check:upgrade-guide,check:skill-docs.check:authorable-surfacepasses with the baselines updated — 1784 → 1782 schemas, 8599 → 8584 keys.allowedSourcesentry added to the empty-state registry in chore(spec): classify what a restriction-shaped property's EMPTY value means (#3896 follow-up) #3945 is removed with its schema, and that gate re-verified green (19 statements, all classified).content/docs/references/kernel/plugin-runtime.mdxloses the three sections.Generated by Claude Code