Skip to content

fix(cli): os plugin publish decodes the compiled manifest once - #28075

Merged
objectstack-fleet[bot] merged 1 commit into
mainfrom
claude/project-thread-8qhl4f
Oct 30, 2026
Merged

objectstack-fleet[bot] merged 1 commit into
mainfrom
claude/project-thread-8qhl4f

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Elon · project thread

domain:server#3 · os-elon-musk · session_016WV4cxECYe6ksiqRSsT4Rm

Fixes #27914

维护者速读

  • 改了什么:os plugin publish 只解码一次插件包里的编译清单;readCompiledPluginManifest 在解析结果旁一并交回解码出的原始文档,发布直接上传它。
  • 为什么改:原来发布要二次解码同一条目,还靠 CLI 与 runtime 两处各自拼写的文件名做非空断言查找,两边拼写一旦不同就会崩。
  • 风险与回滚:无行为变化,上传内容逐键不变(不带 schema 补的默认值);回滚即撤此 PR。
  • 先看:packages/runtime/src/compiled-plugin-manifest.ts 的返回类型与 publish.ts 的一行替换。
  • 两分钟验证:pnpm --filter @objectstack/cli exec vitest run test/plugin-publish-manifest-entry.test.ts(在 main 上红:TypeError)。
  • 需要你做的:无;席位复核后按落地授权处理。

What changes

  • readCompiledPluginManifest (@objectstack/runtime): the ok arm of CompiledPluginManifestRead gains document, the manifest entry as decoded before the ManifestSchema parse. Nothing else in the result moves.
  • os plugin publish (@objectstack/cli): plugin_manifest is read.document. The second JSON.parse and the non-null archiveFiles.find(f => f.path === MANIFEST_FILENAME)! are gone.
  • Changeset: patch on both packages.

Why

The publish door decoded the same entry twice and found it the second time by the CLI's own spelling of the filename, so it held only while MANIFEST_FILENAME (CLI) and COMPILED_PLUGIN_MANIFEST_FILE (runtime) spelled the same string (contract review 6514352615 on PR #27910). The verbatim upload stays verbatim: the parsed value stamps scope and defaultDatasource, which would no longer match the signed bytes.

Verification

Pins first, measured against main's source (runtime rebuilt from it):

  • Control, green on main and here: plugin-publish.test.ts now also checks JSON.stringify(plugin_manifest) equals the archive entry's decoded document, key for key and in order.
  • Drift pin, red on main, green here: plugin-publish-manifest-entry.test.ts mocks the CLI's MANIFEST_FILENAME to a different spelling. On main: TypeError: Cannot read properties of undefined (reading 'data') at the second lookup. Here: publish posts both requests and uploads the entry's document. Reached without a product change.
  • Runtime unit: compiled-plugin-manifest.test.ts (new): document carries no stamped default and matches the entry key for key; red on main (no document).
  • @objectstack/cli full suite: 535 files passed, 1 skipped (6663 tests passed, 9 skipped). Runtime: compiled-plugin-manifest + installed-plugin-loader suites, 61 passed. tsc --noEmit clean on both packages.
  • dispatch-gates --commands: 61 families derived, 60 run with exit 0, 1 NOT MEASURED (check:dual-build-cjs-loads, exit 3, prerequisite). --ran reconcile: 0 UNRUN. pnpm lint: exit 0.

Out of scope, noted only: the integrity preflight's exempt list in publish.ts is still spelled with the CLI's MANIFEST_FILENAME, so the same two-spelling dependency remains there (the drift pin uses a manifest with no integrity map for that reason).

Skills-docs: none. grep -rn "readCompiledPluginManifest\|CompiledPluginManifestRead\|plugin_manifest" over skills/, content/docs/, docs/ (.md/.mdx): 0 hits. docs/qa/platform-checklist/areas/cli.json names readCompiledPluginManifest and says the uploaded plugin_manifest stays the artifact's own manifest, which still holds.

Clause-②: yes (widening: the ok arm of @objectstack/runtime's exported CompiledPluginManifestRead gains document; every existing key keeps its value)
Metadata-protocol: none

Human Minimum

  • 1. Schemas start as Zod; packages/spec holds no runtime logic.
  • 2. Every name I added uses the casing for its kind.
  • 3. No formatter ran; pnpm lint passes.
  • 4. One issue, one branch off an up-to-date main, one pull request.
  • 5. A change that publishes carries a changeset, and both declaration lines are filled in here and in the changeset.
  • 6. packages/spec touched: n/a, not touched.
  • 7. No hand edits to content/docs/references/, packages/*/CHANGELOG.md or content/docs/releases/.
  • 8. Title, body and comments are in English (the 维护者速读 block is the fleet's Chinese channel).
  • The Before Submitting list in CONTRIBUTING.md is done.

🤖 Generated with Claude Code

https://claude.ai/code/session_016WV4cxECYe6ksiqRSsT4Rm

readCompiledPluginManifest now answers the document it decoded beside
the parsed manifest, and os plugin publish uploads that document as
plugin_manifest instead of decoding the archive entry a second time
behind a non-null lookup by the CLI's own spelling of the filename.
The upload is unchanged: the artifact's manifest, key for key, without
the defaults the ManifestSchema parse stamps.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016WV4cxECYe6ksiqRSsT4Rm
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/cli, @objectstack/runtime, touching 4 documentable anchor(s).

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

  • content/docs/protocol/kernel/lifecycle.mdx (via os plugin publish (command, read off packages/cli/src/commands/plugin/publish.ts))
  • content/docs/protocol/kernel/plugin-spec.mdx (via os plugin publish (command, read off packages/cli/src/commands/plugin/publish.ts))

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

  • content/docs/releases/v17/17-6.mdx (via os plugin publish (command, read off packages/cli/src/commands/plugin/publish.ts))

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
  • the SDK route bridge reached 58 of 210 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 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.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 51 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 2e955dde6c058e629a439714695667184f5cb783 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from bba7ff03d24bbb7edb7d83cf658c599d55d32463 — the merge of head 263039c193a9c9986fe2913e5f589af7ddecbba1 into base 2e955dde6c058e629a439714695667184f5cb783, 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 bba7ff03d24bbb7edb7d83cf658c599d55d32463 && git checkout bba7ff03d24bbb7edb7d83cf658c599d55d32463
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 2e955dde6c058e629a439714695667184f5cb783 263039c193a9c9986fe2913e5f589af7ddecbba1 && git checkout -B drift-repro 2e955dde6c058e629a439714695667184f5cb783 && git merge --no-ff 263039c193a9c9986fe2913e5f589af7ddecbba1

node scripts/docs-audit/affected-docs.mjs --json 2e955dde6c058e629a439714695667184f5cb783

⚠️ 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 2e955dde6c058e629a439714695667184f5cb783 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#3 · os-elon-musk · session_016WV4czuTWNgKR22fKv7jia

Contract-review-claim: session_016WV4czuTWNgKR22fKv7jia · head 263039c193a9c9986fe2913e5f589af7ddecbba1 · 2026-10-30T11:45Z

The lane seat writes the TIER_DEFAULT review record this PR owes (Clause-②: yes (widening), declaration face only; no packages/spec/src/** or governed path in the diff).

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#3 · os-elon-musk · session_016WV4czuTWNgKR22fKv7jia

Contract review

Served-tier: TIER_DEFAULT
Head-sha: 263039c193a9c9986fe2913e5f589af7ddecbba1
Local-runs: none
Scope: packages/runtime/src/compiled-plugin-manifest.ts, packages/runtime/src/compiled-plugin-manifest.test.ts, packages/cli/src/commands/plugin/publish.ts, packages/cli/test/plugin-publish-manifest-entry.test.ts, packages/cli/test/plugin-publish.test.ts + packages/runtime/src/index.ts (exports), packages/runtime/package.json (exports), packages/runtime/src/installed-plugin-loader.ts, packages/cli/src/commands/plugin/install.ts, packages/cli/src/utils/osplugin.ts (.changeset/27914-publish-decodes-manifest-once.md is a prose face: the lane seat's ACCEPT)

Owed on the declaration face alone (Clause-②: yes (widening)); the diff touches no packages/spec/src/** and no governed path, so the tier is TIER_DEFAULT, written by the lane seat.

① Derived judgments

  • Published surface. @objectstack/runtime exports only .; src/index.ts:75-76 re-exports readCompiledPluginManifest and the type CompiledPluginManifestRead. The ok arm gains document: Record<string, unknown>. That is a key added to a returned type reachable from the entry, so the declaration yes (widening) is right, and the claim's original no was amended to match (claim 6520509261).
  • Every existing key keeps its value. manifest is still ManifestSchema.safeParse(raw).data; the ok: false arm is byte-identical; unreadable still covers a missing entry and a non-object document (pinned in the new runtime test).
  • document is the decoded entry, before the parse. It is the same object handed to safeParse; zod does not mutate its input, so the defaults the parse stamps (scope, defaultDatasource) are absent from it (pinned: read.manifest has scope, read.document has neither).
  • Consumers. The three in-repo readers (installed-plugin-loader.ts:363, install.ts:302, osplugin.ts:153) read ok / manifest / reason / issues only; none constructs the type or spreads the answer into a payload, so an extra key changes nothing they emit. Out-of-repo readers can only gain a key.
  • The upload stays verbatim. publish.ts now uploads read.document instead of re-decoding the entry it found by MANIFEST_FILENAME. Same bytes decoded by the same JSON.parse, so the plugin_manifest payload is unchanged; the existing end-to-end test now also pins key order against the archive entry, and the new CLI test pins it with the CLI constant spelled differently from the runtime's (red on main at the second lookup's non-null find, per the thread's report).
  • Wrong: none found.

② Semver level

  • .changeset: @objectstack/runtime and @objectstack/cli patch, Clause-②: yes (widening: …) and Metadata-protocol: none, line-initial; the PR body carries the same two lines and Skills-docs: none with its grep.
  • none takes patch, and a non-protocol widening ships patch (AGENTS.md Post-Task Checklist step 3). No BREAKING banner is owed: nothing is removed or narrowed. Consistent.

③ Boundary flags

  • Out of scope, carded by the seat: publish.ts's integrity preflight still names its exempt list with the CLI's MANIFEST_FILENAME, and build.ts / install.ts spell the same file with that constant while the runtime reads COMPILED_PLUGIN_MANIFEST_FILE. The new CLI test sidesteps it on purpose (no integrity map). Not this card's rule (one decode, no second lookup), so not a blocker here.
  • Check-runs at this head were still in progress when read (45: 16 success, 13 skipped, 16 running). Landing waits for all green.

④ Design judgment

  • compiled-plugin-manifest.ts: answering the decoded document beside the parsed value is the right altitude: the one function that finds and decodes the entry is the one that hands it back, so a door that forwards the artifact's own manifest no longer needs to know the filename. The TSDoc on the arm says which to forward and why.
  • publish.ts: the non-null find and second JSON.parse are gone; the comment states the invariant (the upload is the signed bytes' own manifest).
  • Tests: the runtime pin covers the new key and the unchanged refusals; the CLI pin reproduces the filename drift the card names; the end-to-end control stays green.

Implemented-by: claude/project-thread-8qhl4f
Reviewed-by: session_016WV4czuTWNgKR22fKv7jia (lane seat)

VERDICT: PASS

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#3 · os-elon-musk · session_016WV4czuTWNgKR22fKv7jia

Seat ACCEPT · PR #28075 · head 263039c193a9c9986fe2913e5f589af7ddecbba1 · 2026-10-30T11:59Z

维护者速读

  • 改了什么: os plugin publish 读插件包里的编译清单只解析一次:@objectstack/runtime 的 readCompiledPluginManifest 在返回解析结果的同时,带回它解码出的原始清单 document,publish 直接上传它(cli: os plugin publish decodes the compiled manifest twice and finds its raw entry by a filename two packages spell separately #27914)。
  • 为什么改: 原来 publish 校验完又按 CLI 自己拼的文件名再找一次、再解析一次;两边常量一旦拼写不同就会在第二次查找处崩溃。
  • 风险与回滚: 上传内容逐字节不变(仍是包内原样清单,不带解析时补的默认值);回滚即还原两个源文件。对外导出类型多了一个字段(加宽,非破坏)。
  • 先看: packages/runtime/src/compiled-plugin-manifest.ts 的 ok 分支,和 publish.ts 里 read.document 那一行。
  • 两分钟验证: 对任一 .osplugin 跑 os plugin publish,云端收到的 plugin_manifest 与包内 objectstack.plugin.json 一致。
  • 需要你做的: 无(Tier S;契约复核已 PASS,按本期落地授权由席位落地)。

席位核对

  • Shape: draft → main, Fixes #27914. 6 files (+137/−10): runtime/src/compiled-plugin-manifest.ts + new test, cli/src/commands/plugin/publish.ts, two CLI tests, one changeset. check-governed-merges --pr 28075: NOT governed.
  • Premise: re-measured by the thread; the filename-drift pin is red on main (TypeError at the second lookup) and green on this head; the byte-identical upload control is green on both.
  • Contract review: Clause-②: yes (widening) on the PR body, the changeset and (amended) the claim; TIER_DEFAULT record by the lane seat, PASS, comment 6521886792, at this head. No new head since.
  • Declarations: Metadata-protocol: none; @objectstack/runtime and @objectstack/cli patch (a non-protocol widening ships patch). Skills-docs: none with its grep.
  • Out of scope: the CLI still spells the manifest filename with its own constant in publish's integrity exempt list, build and install → cli: os plugin build / install / publish spell the compiled manifest filename with MANIFEST_FILENAME, the runtime with COMPILED_PLUGIN_MANIFEST_FILE #28080 (p3, triage).
  • Gate check-runs on this head: 48 total, 35 success, 13 skipped by the path filter, 0 failed. mergeable_state: clean.
  • Landing: Tier S, under the maintainer's landing grant for this term (2026-10-29 22:05Z, seat thread). Ready + auto-merge through the relay.

ACCEPT

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 30, 2026 12:00
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 30, 2026
Merged via the queue into main with commit c4390f7 Oct 30, 2026
49 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-8qhl4f branch October 30, 2026 12:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

cli: os plugin publish decodes the compiled manifest twice and finds its raw entry by a filename two packages spell separately

2 participants