Skip to content

Three plugin-manifest blocks — capabilities, configuration, extensions — have zero reads of the container itself, so all 8 keys beneath them are inert (configuration.properties.secret promises encryption/masking) #11332

Description

@os-sam

Measured while seeding the plugin-manifest liveness ledger for #10728 (all eight verdicts recorded as dead in packages/spec/liveness/manifest.json). Filed, not fixed — this is the enforce-or-remove unit the ledger's dead rows produce.

Measured on origin/main (b9e9227e3). Sibling of #10724, which is the same shape for the contributes block; these three were outside that card's scope because nothing had measured the manifest's non-contributes surface until now.

What is declared, and what is measured

For each block the probe finds zero reads of the container, which settles every key beneath it at once — a key cannot be read if the object holding it never is.

BlockLineKeys beneathReads of the container
capabilities (PluginCapabilityManifestSchema)manifest.zod.ts:484implements, provides, requires, extensionPoints, extensions0
configurationmanifest.zod.ts:305title, properties0
extensions (z.record(z.string(), z.unknown()))manifest.zod.ts:491— (untyped)0

Probe: (manifest|pkg|package|entry|artifact)?.<key> over *.ts,*.tsx,*.mts,*.mjs,*.js across packages, apps, examples, skills, excluding node_modules/dist, test files and comment lines. objectui likewise zero.

⚠️.capabilities is the one that looks non-zero and is not. A bare .capabilities probe returns hits, and every one belongs to a different surface: driver loader contracts (packages/metadata/src/metadata-manager.ts:798), the QuickJS sandbox argument set (packages/runtime/src/sandbox/quickjs-runner.ts:120), REST discovery (packages/rest/src/rest-server.ts:3333). None is reached from a manifest. Worth stating because the next person to re-measure this will hit the same false positive.

Also note the live analogue one level up: real dependency resolution runs off top-level manifest.dependencies (packages/metadata-protocol/src/protocol.ts:4290-4300), not off capabilities.requires.

Controls — an uncontrolled zero is not a reading

  • The same probe finds real reads of sibling manifest keys: .packaging (cli/src/commands/plugin/build.ts:126), .data (runtime/src/app-plugin.ts:946), .contributes (objectql/src/engine.ts:4504).
  • objectui control: manifest.(id|name|namespace|version) → 46 hits, so manifest reads are findable in that repo.

The one key worth calling out separately

configuration.properties declares a per-setting shape with secret: z.boolean().optional().describe('If true, value is encrypted/masked (e.g. API Keys)').

Nothing encrypts, masks, resolves, or even parses it. A plugin author reading that describe() writes secret: true next to an API key and gets exactly the same handling as secret: false — which is a stronger claim than "unused", because the key's own text is an assurance about credential handling. capabilities' describe() has the milder version of the same problem (it sells "plugin interoperability and automatic discovery"; no discovery path consults it).

Note on extensions specifically

Its value type is z.unknown(), so this key is where anything the platform does not yet model would get parked. Its emptiness is therefore evidence of something slightly different from the other two: authors are not parking things here either. Worth separating when a removal is cut — an untyped escape hatch with no users is a cheaper removal than one with unknown users.

⚠️ Scope limit — measure cloud before removing

cloud is unmeasured, carried unchanged from #10627 (add_repo → "you don't have access to objectstack-ai/cloud"), and manifest.zod.ts:10-18 says the control plane mirrors these shapes when it validates a published .osplugin and persists declared metadata onto sys_package_version. capabilities is the most plausible of the three to have a cloud-side consumer (marketplace discovery). Run the cloud leg with its own control probe first — the same precondition #10724 carries.

Route, if removal is chosen

Tombstone, not deletion, on the #4914 / #10724 precedent: neither ManifestSchema nor these blocks are .strict() (manifest.zod.ts:132 is a plain z.object; the only .strict() in the file, at :44, belongs to PluginPermissionsSchema), so a plain deletion silently strips the key — replacing an inert declaration with an invisible one. retiredKey() per block, retired-key entries, one ADR-0087 D3 semantic entry, upgrade-guide row, changeset. Sequencing with #10724 (also a manifest tombstone card, currently pm:blocked on the same cloud census) is a triage call — the two touch the same file and the same D3 neighbourhood.

Filed unassigned for triage.

Blocked-by: #10812


Generated by Claude Code

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions