Skip to content

Verify manifest.integrity at the os plugin publish preflight (framework leg of #11331) - #13502

Draft
os-project-manager wants to merge 2 commits into
mainfrom
claude/issue-13464-publish-integrity-preflight
Draft

Verify manifest.integrity at the os plugin publish preflight (framework leg of #11331)#13502
os-project-manager wants to merge 2 commits into
mainfrom
claude/issue-13464-publish-integrity-preflight

Conversation

@os-project-manager

Copy link
Copy Markdown
Collaborator

Fixes #13464
Part-of #11331

Scope framing (verbatim obligation from the card)

This PR does NOT discharge the #11331 unpack ruling. The ruled enforce leg — re-verification of manifest.integrity at install/load-time unpack (ADR-0025 §3.5 step 5) — has no landing surface in this repo and remains the cloud control plane's obligation, tracked on #11331. This PR lands only the framework leg: the publisher now self-checks its own artifact before upload. #11331 remains open and its spec promise remains outstanding.

What landed

  1. verifyIntegrity(files, integrity, options) in packages/core/src/security/plugin-artifact-integrity.ts, beside the artifact-signature contract and with the same portability property (pure, node:crypto only, byte-mirrorable to cloud). Returns a structured envelope: ok / skipped / checked count / one violation per finding, with kinds digest_mismatch, missing_file, extra_file. SRI-style digests (sha256 by declared algorithm; sha384/sha512 honoured; an unrecognized digest shape compares as sha256 and therefore fails loudly, never silently passes). formatIntegrityViolation renders the actionable line the CLI prints. Exported from @objectstack/core.
  2. One call siteos plugin publish preflight (packages/cli/src/commands/plugin/publish.ts, step 2b): the already-in-memory unpacked tree is checked against the manifest's own declared digests before any network call; refusal exits 1 pre-upload with per-file findings and the remedy (rebuild with os plugin build, re-sign, publish fresh). The compiled manifest and SIGNATURE placeholder are exempt paths, mirroring what computeIntegrity excludes at build.
  3. TSDoc prose corrections in packages/spec/src/kernel/manifest.zod.ts (regions 100-109 and 611-616 only — measured disjoint from in-flight [text correction · ruled B] manifest.runtime tombstone/describe reads truthfully: publish-gate enforced (cloud), load-side NOT enforced #11330's regions; no shape change, no describe-text change): the comments no longer claim the runtime re-verifies at unpack; they now state the truth — computed at build, self-checked at publish preflight, unpack-time verification is the cloud control plane's unimplemented obligation (manifest.integrity declares per-file artifact digests the spec says the runtime re-verifies at unpack — nothing computes them and nothing checks them #11331).
  4. Liveness ledger note prose (packages/spec/liveness/manifest.json, integrity row): corrected census — the map IS computed at build and now has one non-test in-repo reader (the publish preflight). status left dead per the card's mandate; check:liveness passes on the row as written, and the note records explicitly that any flip to live belongs to the manifest.integrity declares per-file artifact digests the spec says the runtime re-verifies at unpack — nothing computes them and nothing checks them #11331 resolution / review chain, not to a rider here.
  5. Tests: core unit suite for match / single-file mismatch / missing declared entry / extra file / absent map / exempt paths / non-sha256 SRI / deterministic ordering; CLI e2e suites proving the refusal happens with zero fetch calls (nothing reaches the cloud) for all three violation kinds, and that an absent map still publishes with a skip notice.
  6. Changeset (.changeset/plugin-publish-integrity-preflight.md): patch for @objectstack/cli and @objectstack/core.

Decision mandate (pre-answered on the card, carried here)

  • Absent integrity map = permissive: publish proceeds with a one-line notice. The field is .optional(); strict-by-default would be a contract change needing its own ruling.
  • Extra files not in the map = refuse: a stale map is exactly the drift this check exists to catch. If review finds this controversial, the sanctioned downgrade is to warn — in review, not here.
  • No error-code-ledger registration: this is a pre-server-bind CLI refusal (precedent: packages/runtime/src/artifact-reference.ts, lines 68-73 at base). packages/spec/src/api/error-code-ledger.zod.ts is untouched.

Declared bounded in-place fix (same defect class, same files)

packages/cli/src/utils/osplugin.ts carried the identical false claim the card corrects in the spec ("re-verifies … when the runtime unpacks", header and the sriDigest docblock). Comment-only correction to the same truthful wording, in a file this card already reads at its call site; no behavior change. Evidence: the card's own ruling text pins the correct prose; the PM claim measured no other in-flight card touching osplugin.ts.

Clause ② parking

Both limbs fire (diff touches packages/spec/src/kernel/manifest.zod.ts; publish gains an accept/reject change). This PR is parked as DRAFT with needs:contract-review on both carriers (PR + card #13464). The review chain owns enqueue; #13464 stays open until then and #11331 remains open regardless.

Gates

All measured at branch head f26fef2a (base c38b7eff = origin/main at dispatch; upstream moved since, but touched none of this PR's files — in particular manifest.zod.ts is unchanged upstream, so in-flight #11330 has not merged and no base merge was owed).

Gate Result
pnpm --filter @objectstack/cli test PASS — 218 files / 2510 tests (verify-lock VERDICT command-exit 0)
vitest run test/plugin-publish.test.ts (direct) PASS — 7/7
pnpm --filter @objectstack/core test PASS — 44 files / 1070 tests
vitest run src/security/plugin-artifact-integrity.test.ts (direct) PASS — 10/10
pnpm --filter @objectstack/spec check:liveness PASS — the gate accepts the integrity row as written, status: dead kept per mandate
pnpm --filter @objectstack/spec check:authorable-surface PASS — baseline unmoved ("1237 default(s) unchanged")
pnpm --filter @objectstack/spec check:generated PASS — "All 14 generated artifacts are up to date" (includes check:docs — the TSDoc edits moved no generated artifact, so no spec changeset owed)
pnpm --filter @objectstack/spec typecheck PASS (tsc + scripts + test-typecheck)
pnpm --filter @objectstack/cli typecheck PASS — note: cli tsconfig includes src only, so the edited test file is exercised by vitest, not tsc (pre-existing repo shape)
pnpm lint (repo eslint, --no-inline-config) PASS — exit 0 captured before any pipe
check:type-check-coverage + check:type-check-debt --re-measure (full package closure built first) PASS — "none above its recorded number"
check:i18n / check:i18n-coverage PASS — "602 baselined untranslated string(s), none new"
Derived batch (scripts/pm/dispatch-gates.mjs, derived at f26fef2a) all PASS: adr-0087-registration · changeset-no-major · empty-changeset · nul-bytes · cross-package-test-inputs · test-source-alias · cli-test-child-env · undeclared-dep-imports · kernel-hook-pairs · objectql-double-limit · where-matcher · keyed-text-bounds · doc-authoring · merge-driver · spec-parsed-alias · logger-receiver-detach · published-files · page-declaration-shape · slot-lookup · type-source-resolution · changeset-gate-self-tests · objectui-changeset · pm-half-states · comment-mask-adoption · plugin-teardown-shape · dev-prereqs · ci-filter-parity · empty-state · variant-docs · yaml-examples · llms-txt · skill-refs · doc-formula-expressions

Generated by Claude Code


Generated by Claude Code

@github-actions

github-actions Bot commented Aug 30, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 3 package(s): @objectstack/cli, @objectstack/core, @objectstack/spec, touching 15 documentable anchor(s). ⚠️ 2 changed file(s) yielded no anchor (packages/cli/src/utils/osplugin.ts, packages/spec/liveness/manifest.json), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

3 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/getting-started/quick-reference.mdx (via ManifestSchema (symbol))
  • content/docs/plugins/development.mdx (via ManifestSchema (symbol))
  • content/docs/protocol/kernel/plugin-spec.mdx (via ManifestSchema (symbol), os plugin publish (command))

1 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v15.mdx (via ManifestSchema (symbol))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • 2 changed file(s) yielded no anchor (packages/cli/src/utils/osplugin.ts, packages/spec/liveness/manifest.json) — pages documenting those are invisible to this run
  • 5 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 47 of 219 client-bound route-ledger rows — the other 172 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 172: 14 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 56 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 102 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 138 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json e7191ce71da1a8cc25c05e3d372eb4f63be5517apackageMentionDocs.

Which tree this was computed on

This run read content/docs from e25b7cf3076f40a212b1e9eb2dbb1ac4db5198cd — the merge of head 4f3eda771ce43129d0208f90e1f53c7df5b6009e into base e7191ce71da1a8cc25c05e3d372eb4f63be5517a, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin e25b7cf3076f40a212b1e9eb2dbb1ac4db5198cd && git checkout e25b7cf3076f40a212b1e9eb2dbb1ac4db5198cd
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin e7191ce71da1a8cc25c05e3d372eb4f63be5517a 4f3eda771ce43129d0208f90e1f53c7df5b6009e && git checkout -B drift-repro e7191ce71da1a8cc25c05e3d372eb4f63be5517a && git merge --no-ff 4f3eda771ce43129d0208f90e1f53c7df5b6009e

node scripts/docs-audit/affected-docs.mjs --json e7191ce71da1a8cc25c05e3d372eb4f63be5517a

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs e7191ce71da1a8cc25c05e3d372eb4f63be5517a → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@os-project-manager
os-project-manager force-pushed the claude/issue-13464-publish-integrity-preflight branch from f26fef2 to 5ead860 Compare August 30, 2026 15:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Verify manifest.integrity at publish preflight — the framework leg of #11331 (does not discharge the unpack ruling)

2 participants