Repository navigation
docs(spec): manifest.integrity TSDoc stops asserting an unpack verification nobody performs - #16532
Conversation
The `integrity` TSDoc asserted that per-file re-verification at unpack is the cloud control plane's obligation. The cloud repo's own docs assert it is the runtime's. Neither side unpacks anything, so two published texts pointed at each other and a reader of either learned that a verification exists. Rewrite both `integrity` docblocks in `manifest.zod.ts` to state what is true: the map is computed by `os plugin build` and self-checked by the `os plugin publish` preflight; unpack-time re-verification is NOT implemented and is owned by the future runtime loader (ADR-0025 §3.5 steps 4-7), not by the control plane. The enforce leg points at #11331. Correct the same attribution in the liveness ledger row and its README note so the ledger text agrees with the TSDoc. The row's `status` (dead) and `verifiedAt` are untouched: this is a prose correction, not a re-measurement. ADR-0025 needs no edit — its status line already records the install flow (§3.5) as unimplemented, and §3.5 assigns steps 4-7 to the installing host. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T6HeZvT9wdSJD1ZxJb5Eno
…rection Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T6HeZvT9wdSJD1ZxJb5Eno
📓 Docs Drift CheckThis PR changes 1 package(s): 3 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 1 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 130 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 57af7ef2e261e9386ec628543fcd953e2811b465 && git checkout 57af7ef2e261e9386ec628543fcd953e2811b465
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 2a2f39b72a4354faf9dc4003da4337e7e25409e3 9fd63b161dc831a0ca15f73f5d6d945d41140480 && git checkout -B drift-repro 2a2f39b72a4354faf9dc4003da4337e7e25409e3 && git merge --no-ff 9fd63b161dc831a0ca15f73f5d6d945d41140480
node scripts/docs-audit/affected-docs.mjs --json 2a2f39b72a4354faf9dc4003da4337e7e25409e3
|
Landing path: ORDINARY
packages/specPR — this diff touches NO governed surface, so it is armable in the usual way and does not need a human merge.docs/adr/0025-plugin-package-distribution.mdis deliberately not edited; the reasoning is in "Why the ADR needed no edit" below. Checked mechanically against the register rather than recalled: today'sGOVERNED_SURFACESisdocs/adr/**·.claude/**·skills/**·AGENTS.md·CLAUDE.md, and the four changed paths hit none of them.Fixes #16333
Clause-②: no— prose only. No export added, removed or narrowed; no authorable key moved; no accept-set movement. Proven with a two-direction ablation below rather than read off a bare green.What was wrong
The
integrityTSDoc said per-file re-verification at unpack is the cloud control plane's obligation. The cloud repo's documentary half says it is the runtime's. Neither side unpacks anything, so two published texts each pointed at the other and a reader of either learned that a verification exists when none does. Because this text ships inside@objectstack/spec's.d.ts, the wrong claim reached every consumer that hovered the field.Measured in this checkout at
3335bd35eaf47a5b2aed9120e217dafd242ff7e1:computeIntegrityis defined atpackages/cli/src/utils/osplugin.tsand has exactly one production caller,packages/cli/src/commands/plugin/build.ts.verifyIntegrityhas exactly one production caller, theos plugin publishpreflight (packages/cli/src/commands/plugin/publish.ts).os plugin install—packages/cli/src/commands/plugin/holdsbuild.ts,publish.ts,sign.tsand nothing else.readTarGz's only production caller is that same publish path, reading back its own output. Nothing in this repo unpacks an installed artifact.Cloud's half is not re-measured here; this session has no access to that repo. The cloud-side reading is inherited from #13563 and labelled as inherited, and cloud's documentary half stays with #13563 by the same ruling.
The line anchor had drifted
The card cites
manifest.zod.ts:129-131. At the anchor sha that range isPluginRuntimeSchema's describe andPluginPackagingSchema. Re-located by symbol, the docblock is at:139-145, and there is a second one the card's line cite does not reach: theintegrityfield's own TSDoc insideManifestSchema, at:748-753, carrying the same false attribution. Both are rewritten — acceptance asks thatmanifest.zod.tsno longer assert a cloud-side unpack verification, and one of the two would have left the claim shipping.What the TSDoc says now
os plugin buildcomputes the map; theos plugin publishpreflight re-hashes the artifact bytes against it and refuses on digest mismatch, a declared entry with no file, or a packaged file the map does not declare (Verifymanifest.integrityat publish preflight — the framework leg of #11331 (does not discharge the unpack ruling) #13464). An absent map is a permissive pass — the field is.optional().manifest.integritydeclares per-file artifact digests the spec says the runtime re-verifies at unpack — nothing computes them and nothing checks them #11331 so the enforce leg is findable when the loader is built.Why the ADR needed no edit
The card makes ADR correction conditional on its prose stating step 5 as done. It does not — it states it as designed:
:118is the §2 Decision statement,:171describes the bundled packaging strategy,:236is step 5 of the §3.5 numbered design list, and:448is a §5 projected consequence. None claims the step is built.So the ADR is left alone rather than touched for thoroughness, and this PR stays off the governed path.
Liveness ledger
The card names the ledger alongside the ADR. No row states step 5 as done — but two carried the same wrong owner, which acceptance ("ADR / ledger text agrees with the TSDoc") does not allow to stand:
packages/spec/liveness/manifest.json— theintegrityrow's note said the leg "remains the cloud control plane's obligation". Corrected to the future runtime loader.packages/spec/liveness/README.md— its note said "nothing computes or checks them", stale since Verifymanifest.integrityat publish preflight — the framework leg of #11331 (does not discharge the unpack ruling) #13464 and contradicting the corrected TSDoc. Corrected. This cell is hand-written by design:readme-table.mtsgenerates the row set and the counts, and states that the Notes content is deliberately never generated.The row's
status(dead) andverifiedAt(2026-08-30) are deliberately unchanged. This is a prose correction, not a re-measurement, and movingverifiedAtwould falsely claim a fresh liveness reading.Verification
Base
3335bd35eaf47a5b2aed9120e217dafd242ff7e1; all readings below taken at head9fd63b161d.Generated artifacts — zero movement.
pnpm --filter @objectstack/spec check:generated(after a real build) reports "All 15 generated artifacts are up to date".git diff --name-only BASE..HEADlists exactly the four changed files, none of them generated. Nothing was hand-edited and nothing needed regenerating: TSDoc feeds no artifact, only.describe()does, and no.describe()was touched.Measured generated-artifact footprint, since the card asks for it tracked vs gitignored:
api-surface/export-origins/authorable-surface/declaration-map/authorable-defaults/authorable-surface.base.json+docs/protocol-upgrade-guide.mdcontent/docs/references/packages/spec/json-schema/.gitignore:63)Tracked total is 309, not "~14" — that figure is exactly the
authorable-surface/shard count, which misses the other four sharded directories, the singletons, the 228-file references tree, and the whole 1576-file gitignoredjson-schema/tree.Clause ② — two-direction ablation, no rebuild between mutation and check. The confound is real:
pnpm --filter @objectstack/spec buildregenerates the gates' own baselines, so a mutate-build-check leg comes back falsely green. Avoided by mutating the checked-in baseline instead of the source, which needs no rebuild at all. Each leg proved its mutation reached disk bygit hash-objectdiffering from the HEAD blob, restored withgit checkout HEAD -- PATH, and proved restoration by the hash returning to the HEAD blob.check:api-surface0 breaking (removed/narrowed), 1 addedcheck:api-surface1 breaking (removed/narrowed), 0 addedcheck:authorable-surfacekernel/ValidationWarning:messageauthorable-surface/ is out of date (1 key(s) not recorded)check:authorable-surfacekernel/OsAblationPhantomDef:phantomProp1 authorable key(s) disappeared from the contractThe two legs of each gate produce opposite diagnostics, so this is a real two-direction proof and not merely two reds.
One leg was discarded as vacuous rather than banked: a first attempt at the authorable-surface add-direction used the key
OsAblationPhantom.key, which the gate rejected as malformed ("cannot shard ... has no category segment"). That red graded my fixture's shape, not the ratchet, so the leg was re-run with a well-formed key and only the re-run is reported above.Clean verdicts on the unmutated tree, exit codes captured before any pipe:
check:api-surface— exit 0, "public API surface + factory signatures unchanged"check:authorable-surface— exit 0, "authorable-defaults/ verified against upstream 3335bd3 — 1211 default(s) unchanged"Tests, typecheck, gates.
pnpm --filter @objectstack/spec test— 483 files / 13136 tests passed, lock verdictcommand-exit 0pnpm --filter @objectstack/spec typecheck— exit 0node scripts/pm/dispatch-gates.mjs --ran— 69 derived families, 69 run, 0 UNRUNpnpm lint(repo-wideeslint . --no-inline-config) — exit 0, so the whole-repo scan is a real reading here, not a narrowingpnpm check:nul-bytesgreen, plus a direct control-character scan over the three edited files: no matchesOne family is NOT MEASURED, not passed.
pnpm check:dual-build-cjs-loadsexits 3 —PREREQUISITE NOT MET, because it reads built output for ~87 packages and only the@objectstack/specclosure was built here. Its own text says "This is NOT a pass: nothing was measured." CI builds everything and will measure it. (check:doc-formula-expressionsalso exited 3 at first; that one was cheap to satisfy, so@objectstack/formulaand@objectstack/lintwere built and it was re-run to a real exit 0.)Out of scope, filed separately
The same false attribution lives at five more sites outside this card's scope, in
packages/cliandpackages/core. They are deliberately not touched here: editing them would pull two more packages into the verification surface for a prose card. Filed for triage rather than ridden along.Generated by Claude Code