Follow-on from #10627, which measured reader liveness for the whole plugin-manifest contributes block. Filed unassigned. That card carries the full verdict table; this one is the removal unit it cut.
The verdict being acted on
Measured on origin/main (299b85e9d), monorepo-wide, non-test: the entire monorepo contains exactly one read of manifest.contributes, and it reads one member.
packages/objectql/src/engine.ts:4499: if (manifest.contributes?.kinds) {
packages/objectql/src/engine.ts:4500: this.logger.debug('Registering kinds from manifest', { id, kindCount: manifest.contributes.kinds.length });
packages/objectql/src/engine.ts:4501: for (const kind of manifest.contributes.kinds) {
grep: \.contributes\b|contributes\?\.|contributes\[ over *.ts,*.tsx,*.mts,*.mjs,*.js, excluding node_modules/dist, test files and comment lines. So kinds is LIVE and every other member of the block has zero readers.
Controls — an uncontrolled zero is not a reading
- A. The pattern finds a real read of this exact block. The identical regex returns the three
kinds lines above. The probe is not blind to contributes. - B. Manifest reads are findable at all.
manifest.(namespace|navigationContributions|plugins|id) → 133 non-test hits. - C. The member words exist in the corpus. Bare-word non-test hit counts:
events 568, menus 3, themes 29, translations 497, actions 1175, drivers 395, fieldTypes 31, functions 338, commands 223. The zero is about the path, not the word. - D. objectui. 65 hits of the word
contributes, 0 property reads (\.contributes\b|contributes\?\.) — the same asymmetry.
⚠️Measurement gap to close before executing this card. The cloud repo could not be attached to the measuring session (add_repo returned "you don't have access to objectstack-ai/cloud"). The #4914 precedent measured all three repos; this census covers objectstack + objectui only. Re-run the cloud leg first — with its own control probe — and only then remove.
Scope — nine members, one carrier
| Member | line | What the author should use instead |
|---|
events | 337 | Imperative subscription in the plugin's start(). Its only in-repo author — packages/plugins/plugin-hono-server/objectstack.config.ts:233, declaring kernel:ready / kernel:listening — already subscribes in code; the declaration drives nothing. |
menus | 342 | App navigation / manifest.navigationContributions (read at packages/objectql/src/engine.ts:4472-4474 → registerAppNavContribution). Two alias maps already redirect this spelling: packages/spec/src/ui/app.zod.ts:1242 (menus: 'navigation') and packages/spec/src/system/translation.zod.ts:428. |
themes | 351 | None — see #10485 (stack themes carrier). Note the two surfaces are unrelated shapes: this one is {id,label,path}, the stack carrier is ThemeSchema, and the stack-level hits reach the registry through top-level manifest.themes (METADATA_ARRAY_KEYS, packages/objectql/src/engine.ts:1884), never through contributes.themes. |
translations | 361 | The translation metadata type / stack translations collection (governed, packages/spec/liveness/translation.json). |
actions | 370 | Stack actions collection (METADATA_ARRAY_KEYS, packages/objectql/src/engine.ts:1884) + engine.registerAction. |
drivers | 382 | The driver.* kernel service. Its only in-repo author — packages/drivers/driver-memory/objectstack.config.ts:250 — is registered by packages/objectql/src/plugin.ts:592 (this.ql.registerDriver(service) for services named driver.*), not by its declaration. |
fieldTypes | 392 | Nothing declared. There is no registerFieldType seam anywhere — zero hits monorepo-wide. |
functions | 402 | defineStack({ functions }) → packages/objectql/src/hook-binder.ts:128-143 (engine.registerFunction(...)). |
commands | 448 | oclif auto-discovery. Already stated in-tree at packages/spec/src/kernel/cli-extension.zod.ts:58-63: "The previous plugin model required contributes.commands in the manifest … The objectstack.config.ts plugins array no longer determines CLI commands." |
Deliberately not in this card:contributes.routes (line 419) is an enforce-or-remove fork, not a mechanical removal — filed separately. contributes.kinds (line 327) survives; it has a reader.
Route: tombstone, not deletion
Neither ManifestSchema nor the contributes object is .strict() — packages/spec/src/kernel/manifest.zod.ts:321 is a plain z.object, and the only .strict() in the file (line 44) belongs to PluginPermissionsSchema. That is exactly the condition recorded for the sibling retirement at lines 510-514: a plain deletion silently strips the key, replacing an inert declaration with an invisible one. So the route is retiredKey() per member, mirroring loading at line 518.
Record shape to mirror (#4914 precedent, ADR-0087)
retiredKey() tombstone per member in manifest.zod.ts (see loading, line 518) carrying the migrate sentence.- Retired-key entries under
packages/spec/src/migrations/entries/retired-keys/ — cf. 17.kernel__Manifest__loading.ts (export const entry = 'kernel/Manifest:loading'). - One ADR-0087 D3
SemanticMigration for the block — cf. packages/spec/src/migrations/entries/semantic/17.plugin-manifest-loading-retired.ts, registered in packages/spec/src/migrations/registry.ts. D3 and not a D2 conversion, for the reason recorded verbatim in the retired-key entry: the conversion chain walks a normalized STACK and PLURAL_TO_SINGULAR has no packages / plugins entry, so a manifest is not a stack collection member and a conversion would be a transform with no seam that ever runs. - Generated upgrade-guide row (
docs/protocol-upgrade-guide.md) + changeset.
Author-facing corrections this removal owes
These are the reason the block is not merely tidying — published material actively teaches these keys:
content/docs/protocol/kernel/i18n-standard.mdx:599-612 — an os:check-marked example stating "Translation files are registered under contributes.translations". It parses and configures nothing.content/docs/plugins/development.mdx:334 and :388 — manifest examples with contributes blocks.docs/adr/0088-metadata-kind-admission-and-retirement.md:41 credits "plugin contributes.functions (QL query functions)" as a delivered form of the function kind. That row is wrong and should be corrected with this removal.packages/spec/src/kernel/manifest.zod.ts:429-447 documents Commander.js runtime resolution for commands as current behaviour, contradicting cli-extension.zod.ts:58-63.
Acceptance
- The
cloud leg is measured (with control) and still zero, or this card is re-scoped. - Nine
retiredKey() tombstones; kinds and routes untouched. - ADR-0087 D3 + retired-key entries + upgrade-guide row + changeset.
- Runtime behaviour unchanged and verified as such — nothing read the members, so removing them removes no behaviour.
- The four author-facing corrections above land with it, so no published page keeps teaching a no-op key.
Generated by Claude Code
Follow-on from #10627, which measured reader liveness for the whole plugin-manifest
contributesblock. Filed unassigned. That card carries the full verdict table; this one is the removal unit it cut.The verdict being acted on
Measured on
origin/main(299b85e9d), monorepo-wide, non-test: the entire monorepo contains exactly one read ofmanifest.contributes, and it reads one member.grep:
\.contributes\b|contributes\?\.|contributes\[over*.ts,*.tsx,*.mts,*.mjs,*.js, excludingnode_modules/dist, test files and comment lines. Sokindsis LIVE and every other member of the block has zero readers.Controls — an uncontrolled zero is not a reading
kindslines above. The probe is not blind tocontributes.manifest.(namespace|navigationContributions|plugins|id)→ 133 non-test hits.events568,menus3,themes29,translations497,actions1175,drivers395,fieldTypes31,functions338,commands223. The zero is about the path, not the word.contributes, 0 property reads (\.contributes\b|contributes\?\.) — the same asymmetry.cloudrepo could not be attached to the measuring session (add_reporeturned "you don't have access to objectstack-ai/cloud"). The #4914 precedent measured all three repos; this census coversobjectstack+objectuionly. Re-run thecloudleg first — with its own control probe — and only then remove.Scope — nine members, one carrier
eventsstart(). Its only in-repo author —packages/plugins/plugin-hono-server/objectstack.config.ts:233, declaringkernel:ready/kernel:listening— already subscribes in code; the declaration drives nothing.menusnavigation/manifest.navigationContributions(read atpackages/objectql/src/engine.ts:4472-4474→registerAppNavContribution). Two alias maps already redirect this spelling:packages/spec/src/ui/app.zod.ts:1242(menus: 'navigation') andpackages/spec/src/system/translation.zod.ts:428.themesthemescarrier). Note the two surfaces are unrelated shapes: this one is{id,label,path}, the stack carrier isThemeSchema, and the stack-level hits reach the registry through top-levelmanifest.themes(METADATA_ARRAY_KEYS,packages/objectql/src/engine.ts:1884), never throughcontributes.themes.translationstranslationmetadata type / stacktranslationscollection (governed,packages/spec/liveness/translation.json).actionsactionscollection (METADATA_ARRAY_KEYS,packages/objectql/src/engine.ts:1884) +engine.registerAction.driversdriver.*kernel service. Its only in-repo author —packages/drivers/driver-memory/objectstack.config.ts:250— is registered bypackages/objectql/src/plugin.ts:592(this.ql.registerDriver(service)for services nameddriver.*), not by its declaration.fieldTypesregisterFieldTypeseam anywhere — zero hits monorepo-wide.functionsdefineStack({ functions })→packages/objectql/src/hook-binder.ts:128-143(engine.registerFunction(...)).commandspackages/spec/src/kernel/cli-extension.zod.ts:58-63: "The previous plugin model requiredcontributes.commandsin the manifest … Theobjectstack.config.tsplugins array no longer determines CLI commands."Deliberately not in this card:
contributes.routes(line 419) is an enforce-or-remove fork, not a mechanical removal — filed separately.contributes.kinds(line 327) survives; it has a reader.Route: tombstone, not deletion
Neither
ManifestSchemanor thecontributesobject is.strict()—packages/spec/src/kernel/manifest.zod.ts:321is a plainz.object, and the only.strict()in the file (line 44) belongs toPluginPermissionsSchema. That is exactly the condition recorded for the sibling retirement at lines 510-514: a plain deletion silently strips the key, replacing an inert declaration with an invisible one. So the route isretiredKey()per member, mirroringloadingat line 518.Record shape to mirror (#4914 precedent, ADR-0087)
retiredKey()tombstone per member inmanifest.zod.ts(seeloading, line 518) carrying the migrate sentence.packages/spec/src/migrations/entries/retired-keys/— cf.17.kernel__Manifest__loading.ts(export const entry = 'kernel/Manifest:loading').SemanticMigrationfor the block — cf.packages/spec/src/migrations/entries/semantic/17.plugin-manifest-loading-retired.ts, registered inpackages/spec/src/migrations/registry.ts. D3 and not a D2 conversion, for the reason recorded verbatim in the retired-key entry: the conversion chain walks a normalized STACK andPLURAL_TO_SINGULARhas nopackages/pluginsentry, so a manifest is not a stack collection member and a conversion would be a transform with no seam that ever runs.docs/protocol-upgrade-guide.md) + changeset.Author-facing corrections this removal owes
These are the reason the block is not merely tidying — published material actively teaches these keys:
content/docs/protocol/kernel/i18n-standard.mdx:599-612— anos:check-marked example stating "Translation files are registered undercontributes.translations". It parses and configures nothing.content/docs/plugins/development.mdx:334and:388— manifest examples withcontributesblocks.docs/adr/0088-metadata-kind-admission-and-retirement.md:41credits "plugincontributes.functions(QL query functions)" as a delivered form of thefunctionkind. That row is wrong and should be corrected with this removal.packages/spec/src/kernel/manifest.zod.ts:429-447documents Commander.js runtime resolution forcommandsas current behaviour, contradictingcli-extension.zod.ts:58-63.Acceptance
cloudleg is measured (with control) and still zero, or this card is re-scoped.retiredKey()tombstones;kindsandroutesuntouched.Generated by Claude Code