Skip to content

Commit dce5cd4

Browse files
claude[bot]claude
andauthored
feat(spec)!: retire the plugin manifest's three dead top-level containers (capabilities, configuration, extensions) (#13287)
ADR-0049 enforce-or-remove. Zero reads of each container itself measured across objectstack, objectui and cloud with positive controls; the census settles all eight keys beneath them at once. configuration.properties.secret promised encryption/masking that nothing implemented — the false-compliance shape. retiredKey() tombstones (ManifestSchema is not .strict(), so a plain deletion would silently strip), RETIRED_KEYS_BY_MAJOR[18] entries, one D3 semantic entry, ledger rows to the tombstone disposition, minor changeset per the launch-window convention, docs corrections, and the two in-repo authors cleaned. Claude-Session: https://claude.ai/code/session_01KX8wnyjStaZcuMyAMNsy3N Co-authored-by: Claude <noreply@anthropic.com>
1 parent 0783d7b commit dce5cd4

20 files changed

Lines changed: 520 additions & 658 deletions
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
---
2+
"@objectstack/spec": minor
3+
---
4+
5+
feat(spec)!: retire the plugin manifest's three dead top-level containers — `capabilities`, `configuration`, `extensions` (#11332, ADR-0049)
6+
7+
<!-- adr-0087: registered plugin-manifest-dead-containers-retired -->
8+
9+
**BREAKING** accept-set narrowing, landing after the v17.0.0 cut (the lockstep
10+
launch-window convention ships it as `minor`; the prescriptions are registered
11+
under protocol major 18 — `RETIRED_KEYS_BY_MAJOR[18]` + the D3 semantic entry
12+
`plugin-manifest-dead-containers-retired` — where `os migrate meta` users will
13+
look).
14+
15+
The census (#11332, cloud leg #12400) measured ZERO reads of each container
16+
itself across objectstack, objectui and cloud, with positive controls — which
17+
settles every key beneath them at once, because a key cannot be read if the
18+
object holding it never is. `configuration.properties.secret` is why this is
19+
false compliance rather than tidying: its describe() promised "value is
20+
encrypted/masked (e.g. API Keys)" while nothing encrypted, masked or even
21+
parsed the flag — `secret: true` next to an API key got exactly the same
22+
handling as `secret: false`. `capabilities`' describe() sold "interoperability
23+
and automatic discovery" no discovery path ever performed, and `extensions`
24+
was an untyped `z.record(z.string(), z.unknown())` catch-all nothing
25+
consulted.
26+
27+
FROM → TO:
28+
29+
- `manifest.capabilities` (`implements` / `provides` / `requires` /
30+
`extensionPoints` / `extensions`) → *(removed — no replacement block)*.
31+
Real dependency resolution runs off top-level `manifest.dependencies`,
32+
which stays unchanged. `PluginCapabilityManifestSchema` itself stays
33+
exported: the plugin-registry surface (`plugin-registry.zod.ts`) still
34+
declares it.
35+
- `manifest.configuration` (`{ title, properties }`) → pass options to the
36+
plugin's constructor in `defineStack({ plugins: [new MyPlugin({ … })] })`
37+
the channel hosts already use, and the only one anything reads.
38+
- `manifest.extensions` → the enforced extension channels:
39+
`contributes.kinds` registers metadata kinds, `navigationContributions`
40+
(ADR-0029 D7) injects navigation into other packages' apps, and code-level
41+
extension lives in the plugin itself (`init`/`start`).
42+
43+
One-line fix: delete the keys (they configured nothing); if you passed
44+
settings via `configuration`, move them to the constructor options your host
45+
already hands the plugin.
46+
47+
The retirement kit:
48+
49+
- `retiredKey()` tombstones on all three keys (`ManifestSchema` is not
50+
`.strict()`, so a plain deletion would silently strip the key — the
51+
`manifest.loading` precedent): authoring one is a `tsc` error (input typed
52+
`never`) and a parse error carrying the prescription
53+
- `kernel/Manifest:capabilities` / `kernel/Manifest:configuration` /
54+
`kernel/Manifest:extensions` in `RETIRED_KEYS_BY_MAJOR[18]`, plus the D3
55+
semantic entry `plugin-manifest-dead-containers-retired`; deliberately NO
56+
D2 conversion — a package manifest is not a stack collection member
57+
(`PLURAL_TO_SINGULAR` has no `packages` / `plugins` entry), so a conversion
58+
would be a transform with no seam that ever runs (the
59+
`kernel/Manifest:loading` reasoning)
60+
- pin tests (`kernel/manifest.test.ts`): per-container rejection carrying the
61+
prescription as the specific zod issue; live neighbours
62+
(`dependencies`, `contributes.kinds`, `navigationContributions`) pinned
63+
green
64+
- the two in-repo authors (driver-memory and plugin-hono-server, each
65+
writing `configuration` + `capabilities` blocks nothing read) stop
66+
authoring the keys; liveness ledger rows move to the tombstone
67+
disposition

content/docs/protocol/kernel/plugin-spec.mdx

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ A **plugin** is the unit of distribution in ObjectStack. It packages ObjectQL sc
2020

2121
## Plugin Manifest
2222

23-
Every plugin **must** have a manifest file that declares its identity, dependencies, and capabilities.
23+
Every plugin **must** have a manifest file that declares its identity, dependencies, and contributions.
2424

2525
### Manifest Location
2626

@@ -170,17 +170,21 @@ export default definePlugin({
170170
(`packages/spec/src/kernel/manifest.zod.ts`): `id`, `version`, `type` and `name` are
171171
required; the optional fields are `namespace`, `defaultDatasource`, `scope`,
172172
`description`, `permissions`, `objects`, `datasources`, `dependencies`,
173-
`configuration`, `contributes`, `data`, `capabilities`, `extensions`,
174-
`navigationContributions`, `loading`, `engine`, `engines`, `runtime`, `packaging`
175-
and `integrity`.
173+
`contributes`, `data`, `navigationContributions`, `engine`, `engines`, `runtime`,
174+
`packaging` and `integrity`. The `configuration`, `capabilities`, `extensions` and
175+
`loading` keys are **retired tombstones** (ADR-0049 enforce-or-remove): nothing ever
176+
read any of them, so authoring one is now a compile error and a parse error carrying
177+
the migration prescription.
176178

177179
The `displayName` / `author` / `license` / `homepage` / `optionalDependencies` /
178180
`peerDependencies` / `metadata` / `config` / `marketplace` keys above are
179181
**proposal-only** — the schema declares none of them. Notably: compatibility ranges
180182
live in `engines: { platform, protocol }` (or the legacy `engine: { objectstack }`),
181-
not `marketplace.compatibility`; config defaults live in
182-
`configuration: { title, properties }` (a simplified JSON-Schema map with a per-key
183-
`secret` flag), not `config.defaults` / `config.secrets`; and metadata globs are the
183+
not `marketplace.compatibility`; plugin settings are **not** declared in the manifest
184+
at all — the retired `configuration` block (whose per-key `secret` flag promised
185+
encryption/masking that nothing implemented) configured nothing, and a plugin is
186+
configured by the host that composes it, via the options object passed to its
187+
constructor in `defineStack({ plugins: [...] })`; and metadata globs are the
184188
top-level `objects` / `datasources` arrays, not a `metadata` block.
185189
</Callout>
186190

content/docs/references/api/package-api.mdx

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -199,11 +199,11 @@ Install package request
199199
| **objects** | `string[]` | optional | Glob patterns for ObjectQL schemas files |
200200
| **datasources** | `string[]` | optional | Glob patterns for Datasource definitions |
201201
| **dependencies** | `Record<string, string>` | optional | Package dependencies |
202-
| **configuration** | `{ title?: string; properties: Record<string, object> }` | optional | Plugin configuration settings |
202+
| **configuration** | `never` | optional | [REMOVED] `manifest.configuration` was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — nothing ever read the block: no settings UI rendered it and no loader resolved a setting from it, so authoring it configured nothing. Worse, `properties.*.secret` promised "value is encrypted/masked (e.g. API Keys)" while nothing encrypted, masked or even parsed the flag — a false assurance about credential handling. Delete the key. A plugin is configured by the host that composes it: pass options to its constructor in `defineStack({ plugins: [new MyPlugin({ … })] })`, which is the enforced channel. A declarative settings surface must be designed with an enforcing reader first, not revived here. |
203203
| **contributes** | `{ kinds?: object[] }` | optional | Platform contributions |
204204
| **data** | `{ object: string; externalId?: string \| string[]; mode?: Enum<'insert' \| 'update' \| 'upsert' \| 'replace' \| 'ignore'>; env?: Enum<'prod' \| 'dev' \| 'test'>[]; … }[]` | optional | Initial seed data (prefer top-level data field) |
205-
| **capabilities** | `{ implements?: object[]; provides?: object[]; requires?: object[]; extensionPoints?: object[]; … }` | optional | Plugin capability declarations for interoperability |
206-
| **extensions** | `Record<string, any>` | optional | Extension points and contributions |
205+
| **capabilities** | `never` | optional | [REMOVED] `manifest.capabilities` was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — no discovery path ever consulted the block: nothing read `implements`, `provides`, `requires`, `extensionPoints` or `extensions`, so the declared "interoperability and automatic discovery" never happened. Delete the key. Real dependency resolution runs off top-level `manifest.dependencies`, which stays. Capability-based discovery must be designed with an enforcing reader first, not revived here. |
206+
| **extensions** | `never` | optional | [REMOVED] `manifest.extensions` was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — an untyped map with zero readers: whatever was parked here was stored and never consulted. Delete the key. Extend the platform through the enforced channels instead: `contributes.kinds` registers metadata kinds, `navigationContributions` injects navigation into other packages' apps, and code-level extension happens in the plugin itself (`init`/`start`). |
207207
| **navigationContributions** | `{ app: string; group?: string; priority?: integer; items: (object \| … +8 more)[] }[]` | optional | Navigation items this package contributes into apps owned by other packages |
208208
| **loading** | `never` | optional | [REMOVED] `manifest.loading` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — the entire block (`strategy`, `preload`, `codeSplitting`, `dynamicImport`, `initialization`, `dependencyResolution`, `hotReload`, `caching`, `sandboxing`, `monitoring`) had no runtime reader in any repo, so authoring it configured nothing. Delete the key. Plugins are composed at boot — `defineStack` registers them and the kernel runs `init` then `start` in an order topologically resolved from each composed plugin's own `dependencies` / `optionalDependencies` (`resolvePluginOrder`); the set is fixed until the process restarts. ⚠️ `loading.sandboxing` in particular never isolated anything: it did not run plugins in a process, vm, iframe or web-worker, and `allowedServices` gated no call. If you were relying on it for isolation, you had none — use the plugin trust tier (`manifest.runtime`) and the permission declarations, which are enforced. |
209209
| **engine** | `{ objectstack: string }` | optional | Platform compatibility requirements (legacy; superseded by `engines`) |
@@ -321,11 +321,11 @@ Upgrade package request
321321
| **objects** | `string[]` | optional | Glob patterns for ObjectQL schemas files |
322322
| **datasources** | `string[]` | optional | Glob patterns for Datasource definitions |
323323
| **dependencies** | `Record<string, string>` | optional | Package dependencies |
324-
| **configuration** | `{ title?: string; properties: Record<string, object> }` | optional | Plugin configuration settings |
324+
| **configuration** | `never` | optional | [REMOVED] `manifest.configuration` was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — nothing ever read the block: no settings UI rendered it and no loader resolved a setting from it, so authoring it configured nothing. Worse, `properties.*.secret` promised "value is encrypted/masked (e.g. API Keys)" while nothing encrypted, masked or even parsed the flag — a false assurance about credential handling. Delete the key. A plugin is configured by the host that composes it: pass options to its constructor in `defineStack({ plugins: [new MyPlugin({ … })] })`, which is the enforced channel. A declarative settings surface must be designed with an enforcing reader first, not revived here. |
325325
| **contributes** | `{ kinds?: object[] }` | optional | Platform contributions |
326326
| **data** | `{ object: string; externalId?: string \| string[]; mode?: Enum<'insert' \| 'update' \| 'upsert' \| 'replace' \| 'ignore'>; env?: Enum<'prod' \| 'dev' \| 'test'>[]; … }[]` | optional | Initial seed data (prefer top-level data field) |
327-
| **capabilities** | `{ implements?: object[]; provides?: object[]; requires?: object[]; extensionPoints?: object[]; … }` | optional | Plugin capability declarations for interoperability |
328-
| **extensions** | `Record<string, any>` | optional | Extension points and contributions |
327+
| **capabilities** | `never` | optional | [REMOVED] `manifest.capabilities` was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — no discovery path ever consulted the block: nothing read `implements`, `provides`, `requires`, `extensionPoints` or `extensions`, so the declared "interoperability and automatic discovery" never happened. Delete the key. Real dependency resolution runs off top-level `manifest.dependencies`, which stays. Capability-based discovery must be designed with an enforcing reader first, not revived here. |
328+
| **extensions** | `never` | optional | [REMOVED] `manifest.extensions` was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — an untyped map with zero readers: whatever was parked here was stored and never consulted. Delete the key. Extend the platform through the enforced channels instead: `contributes.kinds` registers metadata kinds, `navigationContributions` injects navigation into other packages' apps, and code-level extension happens in the plugin itself (`init`/`start`). |
329329
| **navigationContributions** | `{ app: string; group?: string; priority?: integer; items: (object \| … +8 more)[] }[]` | optional | Navigation items this package contributes into apps owned by other packages |
330330
| **loading** | `never` | optional | [REMOVED] `manifest.loading` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — the entire block (`strategy`, `preload`, `codeSplitting`, `dynamicImport`, `initialization`, `dependencyResolution`, `hotReload`, `caching`, `sandboxing`, `monitoring`) had no runtime reader in any repo, so authoring it configured nothing. Delete the key. Plugins are composed at boot — `defineStack` registers them and the kernel runs `init` then `start` in an order topologically resolved from each composed plugin's own `dependencies` / `optionalDependencies` (`resolvePluginOrder`); the set is fixed until the process restarts. ⚠️ `loading.sandboxing` in particular never isolated anything: it did not run plugins in a process, vm, iframe or web-worker, and `allowedServices` gated no call. If you were relying on it for isolation, you had none — use the plugin trust tier (`manifest.runtime`) and the permission declarations, which are enforced. |
331331
| **engine** | `{ objectstack: string }` | optional | Platform compatibility requirements (legacy; superseded by `engines`) |
@@ -405,11 +405,11 @@ Resolve dependencies request
405405
| **objects** | `string[]` | optional | Glob patterns for ObjectQL schemas files |
406406
| **datasources** | `string[]` | optional | Glob patterns for Datasource definitions |
407407
| **dependencies** | `Record<string, string>` | optional | Package dependencies |
408-
| **configuration** | `{ title?: string; properties: Record<string, object> }` | optional | Plugin configuration settings |
408+
| **configuration** | `never` | optional | [REMOVED] `manifest.configuration` was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — nothing ever read the block: no settings UI rendered it and no loader resolved a setting from it, so authoring it configured nothing. Worse, `properties.*.secret` promised "value is encrypted/masked (e.g. API Keys)" while nothing encrypted, masked or even parsed the flag — a false assurance about credential handling. Delete the key. A plugin is configured by the host that composes it: pass options to its constructor in `defineStack({ plugins: [new MyPlugin({ … })] })`, which is the enforced channel. A declarative settings surface must be designed with an enforcing reader first, not revived here. |
409409
| **contributes** | `{ kinds?: object[] }` | optional | Platform contributions |
410410
| **data** | `{ object: string; externalId?: string \| string[]; mode?: Enum<'insert' \| 'update' \| 'upsert' \| 'replace' \| 'ignore'>; env?: Enum<'prod' \| 'dev' \| 'test'>[]; … }[]` | optional | Initial seed data (prefer top-level data field) |
411-
| **capabilities** | `{ implements?: object[]; provides?: object[]; requires?: object[]; extensionPoints?: object[]; … }` | optional | Plugin capability declarations for interoperability |
412-
| **extensions** | `Record<string, any>` | optional | Extension points and contributions |
411+
| **capabilities** | `never` | optional | [REMOVED] `manifest.capabilities` was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — no discovery path ever consulted the block: nothing read `implements`, `provides`, `requires`, `extensionPoints` or `extensions`, so the declared "interoperability and automatic discovery" never happened. Delete the key. Real dependency resolution runs off top-level `manifest.dependencies`, which stays. Capability-based discovery must be designed with an enforcing reader first, not revived here. |
412+
| **extensions** | `never` | optional | [REMOVED] `manifest.extensions` was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — an untyped map with zero readers: whatever was parked here was stored and never consulted. Delete the key. Extend the platform through the enforced channels instead: `contributes.kinds` registers metadata kinds, `navigationContributions` injects navigation into other packages' apps, and code-level extension happens in the plugin itself (`init`/`start`). |
413413
| **navigationContributions** | `{ app: string; group?: string; priority?: integer; items: (object \| … +8 more)[] }[]` | optional | Navigation items this package contributes into apps owned by other packages |
414414
| **loading** | `never` | optional | [REMOVED] `manifest.loading` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — the entire block (`strategy`, `preload`, `codeSplitting`, `dynamicImport`, `initialization`, `dependencyResolution`, `hotReload`, `caching`, `sandboxing`, `monitoring`) had no runtime reader in any repo, so authoring it configured nothing. Delete the key. Plugins are composed at boot — `defineStack` registers them and the kernel runs `init` then `start` in an order topologically resolved from each composed plugin's own `dependencies` / `optionalDependencies` (`resolvePluginOrder`); the set is fixed until the process restarts. ⚠️ `loading.sandboxing` in particular never isolated anything: it did not run plugins in a process, vm, iframe or web-worker, and `allowedServices` gated no call. If you were relying on it for isolation, you had none — use the plugin trust tier (`manifest.runtime`) and the permission declarations, which are enforced. |
415415
| **engine** | `{ objectstack: string }` | optional | Platform compatibility requirements (legacy; superseded by `engines`) |

0 commit comments

Comments
 (0)