Measured while seeding the plugin-manifest liveness ledger for #10728 (verdict recorded as dead in packages/spec/liveness/manifest.json). Filed, not fixed.
Measured on origin/main (b9e9227e3).
What is declared
packages/spec/src/kernel/manifest.zod.ts:107-109, 576 — integrity: PluginIntegritySchema.optional(), a Record<string, string> of artifact-relative path → digest ("sha256-<base64>"), whose own describe/TSDoc says:
Per-file content digests of the packaged artifact (ADR-0025 §3.2), re-verified by the runtime when it unpacks the .osplugin (ADR §3.5 step 5).
What is measured
Zero reads, in either direction of the lifecycle:
- Nothing computes the digests —
os plugin build (packages/cli/src/commands/plugin/build.ts) bundles with esbuild and never hashes per-file content into a manifest. - Nothing verifies them — repo-wide probe for a manifest-shaped
.integrity read ((manifest|pkg|package|entry|artifact)?.integrity, non-test, non-comment) returns no hits at all in objectstack, and objectui is likewise zero.
Controls
- 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) — so it is not blind to this object. objectui control: manifest.(id|name|namespace|version) → 46 hits there. The zero is about the path, not the probe.
Adjacent machinery that exists and is NOT this
packages/core/src/security/plugin-artifact-signature.ts verifies an artifact signature, and deliberately returns verified=false rather than throwing when one is absent (its header says so at :144, leaving the trust-tier decision to the caller). That is a different mechanism on a different field. Its presence is part of why this gap is easy to miss: the artifact-security story looks staffed.
Why it matters
A declared integrity check that never runs is the false-compliance shape in its most literal form: to any reader — human, AI, or an auditor reading a published manifest — the presence of an integrity block is indistinguishable from the digests having been checked. Unlike a dead display key, this one is about whether the bytes on disk are the bytes that were published.
⚠️ Scope limit — measure cloud before acting
cloud is unmeasured (carried from #10627: add_repo → "you don't have access to objectstack-ai/cloud"). The install-time unpack that ADR-0025 §3.5 describes is a control-plane path, and manifest.zod.ts:10-18 states the cloud control plane mirrors these shapes when validating a published .osplugin. Measure the cloud leg, with a control probe, before either fork. Same precondition #10724 carries.
The fork (not prejudged)
- A — Enforce. Emit digests at
os plugin build and verify them at unpack. This is what the spec already tells readers happens, so A is the option that makes the shipped documentation true. - B — Retire. Tombstone under ADR-0049 (
ManifestSchema is not .strict(), so a plain deletion would silently strip the key — the loading precedent at manifest.zod.ts:510-514) and remove the "re-verified by the runtime" sentence with it.
A is the more defensible default here than on a typical dead key: the cost of B is deleting a supply-chain control the project has already published an intent to have. But intent is not delivery, and this is not a decision for an implementation seat.
Filed unassigned for triage.
Blocked-by: #10812
Generated by Claude Code
Measured while seeding the plugin-manifest liveness ledger for #10728 (verdict recorded as
deadinpackages/spec/liveness/manifest.json). Filed, not fixed.Measured on
origin/main(b9e9227e3).What is declared
packages/spec/src/kernel/manifest.zod.ts:107-109, 576—integrity: PluginIntegritySchema.optional(), aRecord<string, string>of artifact-relative path → digest ("sha256-<base64>"), whose own describe/TSDoc says:What is measured
Zero reads, in either direction of the lifecycle:
os plugin build(packages/cli/src/commands/plugin/build.ts) bundles with esbuild and never hashes per-file content into a manifest..integrityread ((manifest|pkg|package|entry|artifact)?.integrity, non-test, non-comment) returns no hits at all inobjectstack, andobjectuiis likewise zero.Controls
.packaging(cli/src/commands/plugin/build.ts:126),.data(runtime/src/app-plugin.ts:946),.contributes(objectql/src/engine.ts:4504) — so it is not blind to this object.objectuicontrol:manifest.(id|name|namespace|version)→ 46 hits there. The zero is about the path, not the probe.Adjacent machinery that exists and is NOT this
packages/core/src/security/plugin-artifact-signature.tsverifies an artifact signature, and deliberately returnsverified=falserather than throwing when one is absent (its header says so at :144, leaving the trust-tier decision to the caller). That is a different mechanism on a different field. Its presence is part of why this gap is easy to miss: the artifact-security story looks staffed.Why it matters
A declared integrity check that never runs is the false-compliance shape in its most literal form: to any reader — human, AI, or an auditor reading a published manifest — the presence of an
integrityblock is indistinguishable from the digests having been checked. Unlike a dead display key, this one is about whether the bytes on disk are the bytes that were published.cloudbefore actingcloudis unmeasured (carried from #10627:add_repo→ "you don't have access to objectstack-ai/cloud"). The install-time unpack that ADR-0025 §3.5 describes is a control-plane path, andmanifest.zod.ts:10-18states the cloud control plane mirrors these shapes when validating a published.osplugin. Measure the cloud leg, with a control probe, before either fork. Same precondition #10724 carries.The fork (not prejudged)
os plugin buildand verify them at unpack. This is what the spec already tells readers happens, so A is the option that makes the shipped documentation true.ManifestSchemais not.strict(), so a plain deletion would silently strip the key — theloadingprecedent atmanifest.zod.ts:510-514) and remove the "re-verified by the runtime" sentence with it.A is the more defensible default here than on a typical dead key: the cost of B is deleting a supply-chain control the project has already published an intent to have. But intent is not delivery, and this is not a decision for an implementation seat.
Filed unassigned for triage.
Blocked-by: #10812
Generated by Claude Code