Repository navigation
ADR-0025 M1 — local T0 install loop for first-party, self-signed plugins: os plugin install (signature + integrity, materialize, record), boot-time loader with the tier dispatch point, disable/uninstall, Stripe sample as acceptance #27123
Description
Ruled: 6467932167 · letter B′ · 2026-10-28T11:35Z
Part of #27108 (M1). Filing gate: ③ maintainer-directed — ruling 6461953053 on objectstack#26553 (「插件包分发 同意,应该立专题卡吧?」) and ADR-0025 v2 §3.5 / §4 (PR #27118, merged). Filed by the director seat (session_01KrKXQVvgYk2AJ4VkqMobyb, ledger objectstack#12708). ⛔ Not a claim. ⛔ No domain:*: the work spans packages/cli and packages/core / packages/runtime; triage grades and sets the lane(s) and may split this card by lane. Dedupe: #17757 (closed design card), #13457 (closed, F4 registration); no open card carries the install loop.
维护者速读
本地安装闭环,只限首方/自签插件(T0,node):os plugin install ./x.osplugin → 校验整包 Ed25519 签名与逐文件 integrity → 落盘 <OS_HOME>/plugins/<env>/<id>/<version>/ → 写安装记录 → 下次启动加载器扫描、dynamic import dist/index.mjs、走现有 plugin-loader 的依赖排序,带 tier 分派点(本期只接 node)→ 禁用/卸载复用包的 disable-state store。验收:cloud 的 plugins/connector-stripe 样例装得上、跑得起、禁得掉。⛔ 不上任何市场面;T0 = 宿主进程内跑作者代码。
What lands (ADR-0025 v2 §3.5 M1, §3.3, §3.6)
os plugin install ./x.osplugin(packages/cli/src/commands/plugin/install.ts): read the archive (readTarGz, today publish-only); verify the whole-artifact signature from the.sigsidecar against the host's trusted keys viaverifyPluginArtifact(packages/core/src/security/plugin-artifact-signature.ts, first production caller); re-verify every file againstintegrityat unpack; refuse on any mismatch;--trust-unsignedas the explicit dev override, logged loudly. Materialize to<OS_HOME>/plugins/<env>/<id>/<version>/. Write the installation record (sys_package_installationshape, v2 §3.8) to the local store (ADR-0016 §9.7package-state-store.tspattern).- Loader (
packages/core/packages/runtime): at boot, scan installed plugins, dynamic-importdist/index.mjs, reuseplugin-loader.tsdependency ordering and health checks; a tier dispatch point that M1 wires forruntime:'node'only and that refusessandbox/workerwith a clear message until M3 (no silent in-process fallback). Load-time signature check becomes a real verification (today format-only,plugin-loader.ts:512–535). - Disable / uninstall through the package disable-state store; uninstall removes the materialized dir on next boot.
- Retire the build-side
manifest-depshalf (build.ts:159–207; Q6) and add the native-addon refusal toos plugin build(v2 §3.3). - Delete the self-descriptions as they stop being true:
manifest.zod.tsintegrity "NOT RE-VERIFIED AT UNPACK… there is noos plugin install",PluginRuntimeSchema"nothing dispatches on the tier" (replace with the M1 wording:nodedispatched, others refused until M3). - Acceptance: an e2e that builds, signs (test key), installs and boots the cloud
plugins/connector-stripesample on a standalone stack, exercises one of its tools, disables it, and asserts the tamper cases (bad signature, bad integrity,sandboxtier) are refused.
Not in M1
- Cloud download route,
os plugin install <id>@<version>— M2. - Per-plugin
SecurePluginContextenforcement — M2 (M1 loads first-party code with the enforcer registered, as today). - T1 / T2 tiers, publish scans, secret-refs — M3.
- Any marketplace listing of installed plugins.
Acceptance
os plugin install+ loader + disable work end to end on the Stripe sample (e2e in CI).- Tamper cases refused with one diagnostic each;
--trust-unsignedlogs the override. - The listed self-descriptions are deleted in the same PRs; no new "NOT ENFORCED" prose is added.
- ADR-0025 v2 §3.5 M1 bullet becomes a ✅ line in a one-line docs follow-up (Tier H).
Size: L (two or three PRs; triage may split by lane: cli install command · core/runtime loader · build retirements). Clause-②: no (no authorable key changes; PluginPackagingSchema narrows to bundled — if that is judged a spec narrowing, the lane declares the arm). Metadata-protocol: none.
Related
#27108 (epic, Q1–Q3, Q6), ADR-0025 v2 §3.3 / §3.5 / §3.6, ADR-0016 §9, #26553 ruling 6461953053, cloud plugins/connector-stripe.
Activity
objectstack-fleet commentedon Oct 28, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Triage: p1 kept · lane
domain:server·area:devpath· 2026-10-28T07:59Z⛔ Not a claim.
os plugin install, signature and integrity, the boot loader with the tier dispatch point, disable and uninstall).packages/cli,packages/coreand the runtime loader, all indomain:server. No cloud step: M1 is local-only, so the cloud routing rule (pm-dispatch(triage + lanes): any card that involves cloud is routed to the cloud PM (domain:cloud+status:parked), never to an objectstack lane #26762) does not apply. M2 and M3 do.verifyPluginArtifact. A failed or absent signature refuses the install; there is no "unsigned with warning" arm.domain:server,area:devpath.Generated by Claude Code
objectstack-fleet commentedon Oct 28, 2026
Claim: PM loop round 5 · card thread · slice 1 of 3 · lane refill under the shared cap of 4
Session:
session_0186vfjHHMsRXC9EUT9hTFMo(the seat); the card thread issession_01FEkQ5nwY7dd2EmDhaAqt2jAccount:
os-project-manager(the seat's linked user; the card's assignee)Branch:
claude/project-thread-7vyd1w(the card thread's harness-pinned branch; it names no issue, so this claim is the identity record)Worktree: the card thread's own container checkout
Domain:
domain:serverSeat:
domain:server#2Ruling-ref: triage
6463804227(p1 kept,area:devpath: "⛔ The signature check is the first production caller ofverifyPluginArtifact. A failed or absent signature refuses the install; there is no 'unsigned with warning' arm"), under ruling6461953053on #26553 (M1 local T0 install loop, Q2 local first, Q3 T0 only). Seat slicing, under the card's "triage may split" line: ①os plugin installverify, materialize, record (this claim); ② boot loader, tier dispatch point, real load-time signature check, disable and uninstall, Stripe e2e; ③ build-sidemanifest-depsretirement and native-addon refusal. Slices ② and ③ get their own claims.File surface:
packages/cli/src/commands/plugin/install.ts(new) and its command registration,packages/cli/src/utils/osplugin.ts(reader reuse only), a local installation-record store beside the ADR-0016 §9.7package-state-store.tspattern, themanifest.zod.tsintegrity prose that this slice falsifies, tests, and changesets per package the diff publishes from. ⛔ No loader, no tier dispatch, noos plugin buildedit, no cloud download routeContainer & model:
L,mode:thread, default model and effort (set at thread open)Clause-②: no (expected; the dev measures and declares the arm, incl. a first-reader spec shape for the record if one is needed)
Metadata-protocol: none
Responsibility: the local T0 plugin install command (slice 1 of #27123)
Thread-read: 6463804227
Premise: holds (read 2026-10-28T08:59Z at
origin/mainb15b2fcd: card open, unassigned,pm:queuewith its triage line;packages/cli/src/commands/plugin/holds onlybuild.ts,publish.ts,sign.ts;verifyPluginArtifacthas no production caller); the dev re-measures it first, and a refutation is apremise_still_valid: falsehand-backResume-from: none — fresh from
origin/mainSerial constraints cleared: yes — open PRs touching
packages/cli/src/commands/plugin/**orplugin-artifact-signature.tsat this read: none known; the dev re-measures before the first edit and beforepr_createState:
pm:queue→pm:dispatched, assigned toos-project-manager, in this act. The card stays open across the three slices; each PR saysPart of #27123.Generated by Claude Code
objectstack-fleet commentedon Oct 28, 2026
os-dev-report
{
"issue": 27123,
"status": "needs_decision",
"branch": "claude/project-thread-7vyd1w",
"pr": null,
"session": "session_01FEkQ5nwY7dd2EmDhaAqt2j",
"premise_still_valid": true,
"summary": "Slice 1 of 3 (os plugin install) stopped before the first edit on the order's first stop-and-report line: the host has no trusted-key source a local install can read, and ADR-0025 v2 section 7 defers designing one to M3 ('the host's trusted root set and its rotation / revocation are designed with the T1 tier'). The rest of the premise holds at origin/main 38490db: packages/cli/src/commands/plugin/ holds only build.ts, publish.ts, sign.ts; verifyPluginArtifact has no production caller (git grep finds only its definition outside tests); no open PR touches the claimed surface. The second stop line does not fire: the record is writable locally (see open question note). No code, no commits, no PR.",
"tests": "none run: stopped before any edit (order stop line 1). Measurements only, all on origin/main 38490db in the card-thread worktree.",
"gates": "none run: no diff.",
"dogfood_battery": "n/a: no diff",
"mcp_calls": "0",
"api_writes": "1: POST repos//issues/27123/comments (this report, via scripts/pm/post-stamped.mjs)",
"open_questions": [
{
"status": "needs_decision",
"question": "Where does os plugin install read the trusted publisher keys that verifyPluginArtifact checks the .sig against? MEASURED, what exists: (1) the SIGN key: os plugin sign --key takes the publisher's Ed25519 PKCS#8 private PEM from a path and --key-id (default 'default') is embedded in the signature as ed25519:KEYID:B64; the public half is never written anywhere. (2) The CLOUD keys: cloud verifies the publisher signature at publish and counter-signs with the platform key at approval (publish.ts header, ADR section 3.4); both the publisher-key registry and the platform key live in objectstack-ai/cloud, unreachable from this session and not readable by a local host. (3) CONFIG: none that a host reads. verifyPluginArtifact takes keys only from its caller (platformPublicKey, getPublisherPublicKey) and has no caller; PluginSignatureVerifier.trustedPublicKeys (core, RS256/ES256, a different contract) has no production construction site; spec PluginSecurityPolicy codeSigning.allowedSigners is identities, not keys, with zero readers; no env var, stack-config key or OS_HOME file names a trusted key. ADR-0025 says 'Host ships trusted root keys' (section 3.7) and 'publisher key or an explicit --trust-unsigned dev override' for local artifacts (section 3.5 local-first parity), but section 7 defers the host root set to M3.",
"options": [
"A. Operator names the key on the command: os plugin install ./x.osplugin --public-key ./publisher.pub.pem --key-id ID (key-id defaults to 'default', mirroring os plugin sign). The trusted set for this install is exactly that one key. Diagnostics stay distinct: no .sig = no signature; signature keyId differs from --key-id = untrusted key; bytes fail = bad signature. The record stores keyId plus the SPKI fingerprint, and the materialized dir keeps the original .osplugin and .sig so slice 2 can re-verify at load. Cost: about the same as the order's slice (one flag pair). Invents no persistent host store; M3 designs the root set freely.",
"B. A local trusted-key ring under OS_HOME (e.g. OS_HOME/trusted-keys/KEYID.pem, populated by hand or a new os plugin trust add command), read by install now and by the slice-2 loader at boot. Cost: plus a store, its file format and maybe a command; it pre-empts the M3 root-set and rotation design that section 7 defers, and M3 must migrate or retire it.",
"C. A stack-config key (defineStack plugins trustedKeys map). Cost: a new authorable spec surface (Clause-2 and Metadata-protocol both move, first reader D5), keys inside project metadata, and the same M3 pre-emption as B.",
"D. M1 ships only --trust-unsigned (no verification arm). Contradicts triage 6463804227 ('the first production caller of verifyPluginArtifact'); listed only to rule it out."
],
"recommendation": "A. Four axes. Real business need: the only M1 consumer is a first-party developer installing their own self-signed Stripe sample on their own machine (ruling Q1, Q3); they hold the key pair in hand, and nobody yet needs a persistent multi-key ring (that pull arrives with M2 cloud fetch and the platform key). Long-term soundness: A adds no host store, so M3 designs the root set and rotation without a legacy store to migrate; B and C create exactly the surface section 7 deferred. Preventing AI mistakes: A is explicit per install and refuses loudly (missing key = refusal naming --public-key or --trust-unsigned), with no default or fallback source an author could misconfigure silently; C would put trust material into metadata an AI writes. Startup focus: A is the smallest surface that makes verifyPluginArtifact's first caller real; B/C expand capability without pull. Trade-off stated honestly: with A the slice-2 boot check re-verifies against the key recorded at install, so it proves the materialized files are the signed bytes, not that the key is still trusted; a revocation story is M3's either way."
}
],
"out_of_scope_findings": [
"class: none, noted only, for the seat's slice planning: the record shape is recordable locally without a new schema. sys_package_installation is a cloud-owned row (spec retired cloud/PackageInstallation in v18, #16325); its v2 section 3.8 fields are env_id, version_id, granted_permissions, status. Locally there is no sys_package_version row, so version_id has to be a derived local id (e.g. ID@VERSION), and granted_permissions equals the declared set per v2 section 3.5 ('there is no partial grant'). The writer (cli) and the slice-2 reader (runtime) share the shape, so it should live beside package-state-store.ts in runtime (internal, like that store) rather than in spec. carrier: none",
"class: none, noted only: integrity re-verification can reuse core's existing verifyIntegrity / formatIntegrityViolation (already called by os plugin publish), and OS_HOME resolves through runtime's resolveObjectStackHome (OS_HOME env, else ~/.objectstack), which the cli already imports in serve.ts. carrier: none"
]
}
Generated by Claude Code
objectstack-fleet commentedon Oct 28, 2026
待裁:本地安装插件时,用哪把公钥验签
domain:server席位 2 ·session_0186vfjHHMsRXC9EUT9hTFMo· 2026-10-28T09:06Z · 来源:切片 1 的 os-dev-report6464930591(needs_decision,未写代码、无 PR)。一句话问题: M1 要求
os plugin install ./x.osplugin先验签再安装,但宿主今天没有任何地方存放"信任哪把公钥",ADR 又把这个信任根的设计推到了 M3。所以安装命令拿不到可以验签的公钥。背景与 Governing text
ADR-0025 v2(
docs/adr/0025-plugin-package-distribution.md)有两处相关规定:--trust-unsigneddev override)"。--trust-unsignedas the explicit dev override"。但 §7(L629–631)写着:"Key management & rotation — deferred to M3 (publisher keys stay with the publisher; the host's trusted root set and its rotation / revocation are designed with the T1 tier)"。
另有两条在先裁决:
6463804227: "⛔ The signature check is the first production caller ofverifyPluginArtifact. A failed or absent signature refuses the install; there is no 'unsigned with warning' arm"。6461953053: Q1 首个消费者是首方 Stripe 样例,Q3 规定 M1 只做 T0。协议声明: 选 A、B 都不改 spec。选 C 会新增可编写的 spec 面,
Clause-②与Metadata-protocol都会变。前提(每条带 re-check 命令)
verifyPluginArtifact的公钥只由调用方传入,且仓内没有生产调用方。re-check:
git grep -n "verifyPluginArtifact(" origin/main -- packages ':!*.test.ts',预期只有定义处 1 行。os plugin sign只读私钥 PEM 与--key-id,公钥不落盘。re-check:
git grep -n "key-id\|publicKey" origin/main -- packages/cli/src/commands/plugin/sign.ts。OS_HOME文件都没有),spec 里的codeSigning.allowedSigners读者为 0。re-check:
git grep -n "allowedSigners" origin/main -- packages ':!packages/spec',预期 0 行。选项 × 真实代价 × 工作量
--public-key ./pub.pem --key-id ID(与os plugin sign对称)。本次安装只信这一把。安装记录存 keyId 与 SPKI 指纹,落盘目录保留原.osplugin与.sig,供切片 2 在启动时复验--public-key或--trust-unsignedOS_HOME/trusted-keys/下建本地钥匙环,手放或新增os plugin trust add命令,安装与启动都读它plugins.trustedKeys--trust-unsigned,不验签6463804227,只为排除而列业务含义直译
gpg --verify时手里拿着对方给的公钥,当场核对。known_hosts,先登记,后免问。四维分析(业务立场)
verifyPluginArtifact首个调用真实成立的最小面。B、C 在没有拉动时扩张能力面。只看①选 A;②③④ 是否翻转:否。
os-decision-facets
Prior rulings read:
trusted key|trust-unsigned|root set|allowedSignersin AGENTS.md, docs/adr, packages/spec/src → ADR-0025 L281–283 / L297–302 / L347 / L629–631, speccodeSigning.allowedSigners; ruling6461953053, triage6463804227; thread: 1 (6464930591)推荐:A
置信缺口: 选 A 后,切片 2 在启动时用安装时记录的钥匙复验,证明的是"落盘文件就是当初签名的字节",不证明"这把钥匙至今仍可信"。吊销属于 M3,四个选项在这点上都一样。
不取其余选项的理由:
裁后执行
裁 A 后,席位按原切片计划重派切片 1,在令中写入这几条:
--public-key与--key-id(缺省default)。--trust-unsigned时拒绝。.osplugin与.sig。dev 先验证上面的前提 1–3,任何一条不成立就报 fork,⛔ 不悄悄改走 B 或 C。切片 2、3 不变。
Generated by Claude Code
22 remaining items
objectstack-fleet commentedon Oct 29, 2026
domain:server#3 · os-bill · session_01Utp6CXc5hequf76uJfzrdK
Release:
session_01Utp6CXc5hequf76uJfzrdK· partial landing: slices 1–2 are merged (#27335, #27374), and the seat is winding down by maintainer order ·pm:queue, so anydomain:serverseat can claim slice 3Resume-from: none (both slice branches merged; slice 3 starts from
main)Slice-3 handover
The maintainer ordered this seat to wind down at 2026-10-29T02:22Z, so it claims no further slice.
What landed:
os plugin installverifies a signed .osplugin against the environment's trusted keys and installs it locally (#27123, slice 1 of 3) #27335:os plugin install, the trusted-key store, and the installation record.os serveloads the pluginsos plugin installinstalled, re-verified and dispatched byruntimetier;os plugin disable | enable | uninstall(#27123, slice 2 of 3) #27374, the boot-time loader:readTarGz, and a byte-equal tree.nodeis imported,sandboxandworkerare refused by name, and unset is refused (default-deny).kernel.usenow requires a recorded host verdict for asignature. This is a BREAKING narrowing,adr-0087: not-required.os plugin disable,enableanduninstall, and a lock on the installations file.PluginRuntimeSchemaself-description is replaced with the M1 wording, and the livenessruntimerow is nowlive.6493264083and delta PASS6495004904.Owed by slice 3 (the card's remainder):
manifest-depshalf (build.ts, Q6) and add the native-addon refusal toos plugin build(v2 §3.3). The card's caveat applies here: ifPluginPackagingSchemanarrows tobundled, the lane declares the Clause-② arm.manifest.zod.tsintegrity text "NOT RE-VERIFIED AT UNPACK… there is noos plugin install". Check it against slices 1 and 2 and delete what is now false.plugins/connector-stripe. The test builds, signs with a test key, installs, boots, exercises one tool, disables, and runs the tamper cases. The sample lives in objectstack-ai/cloud, so the cloud lane rule applies: any cloud-side change goes through that repo's owner, and none is made from here.packages/lint/src/lint-liveness-properties.ts:217(comment) and:295(thelive-elsewheredefaultHint). Both still usemanifest.runtimeas the worked example, but that row is nowlive. Swap in a neutral example. This was should-fix 7 of6493264083.packages/lint/CHANGELOG.mdis release-owned; do not edit it.6484915007item 3):SysTrustedKeyplus the four sibling deployment-level objects are missing frompackages/platform-objects/scripts/i18n-extract.config.ts. Fix all five at once.Tier H docs follow-up (separate, maintainer-landed): four ADR-0025 edits, all still on
main:6467932167ordered;os plugin install";Cross-lane, not slice 3: #27114
json-stdout-puritybelongs to the devx holder.plugin install's onlybootSchemaStack(call is inplugin-trust-read.ts, which that discovery does not see (comment 6490929251).Generated by Claude Code
objectstack-fleet commentedon Oct 29, 2026
domain:server#2 · os-sales · session_01L6bhgUCizoYWQVJWjhdR6F
pm:retriage · slice 3 · 2026-10-29T13:28Z · ⛔ not a claim
Premise read on
main@72b08eecagainst the slice-3 handover (Release6496079064). This seat split what is not this lane's, and asks triage one routing question.manifest.zod.tsintegrity text): already done. Slice 1's changeset.changeset/27123-integrity-reverified-at-unpack.mdrewrote it; no "NOT RE-VERIFIED" remains in code. Only ADR-0025 lines remain, and those are the Tier H docs follow-up. Dropped.manifest.runtime, which is now aliverow (#27123 follow-up) #27569; the cloudconnector-stripeacceptance e2e (card item 6) → plugin install acceptance e2e on cloudplugins/connector-stripe: build, sign, install, boot, one tool, disable, tamper cases (#27123 item 6) #27570. Both filed withoutdomain:*.SysTrustedKeyplus four sibling deployment-level objects missing frompackages/platform-objects/scripts/i18n-extract.config.ts; serial behind open PR feat(platform-objects): sys_exchange_rate — an organization's dated exchange rates, admin-written and audited, with a Setup list under Localization #27246 on that file).manifest-depsfork inpackages/cli/src/commands/plugin/build.ts(twopackaging === 'manifest-deps'forks) and adds the native-addon refusal. The same change makes thepackagingrow ofpackages/spec/liveness/manifest.jsonfalse (its evidence cites those forks), and the card's caveat names narrowingPluginPackagingSchema(packages/spec/src/kernel/manifest.zod.ts,enum(['bundled', 'manifest-deps'])) tobundled. These cannot land apart without a red liveness check. Does triage grant a cross-domain exception for one PR (this lane claims; the claim declares the spec files), or route the spec half todomain:specwith this cardBlocked-by:it?The card stays
pm:queue; this seat dispatches the server half on the answer (p1, card thread).objectstack-fleet commentedon Oct 29, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Triage: retriage answer · cross-domain exception granted, one PR · 2026-10-29T14:01Z
⛔ Not a claim. The R743 grade named a remedy without checking its lane boundary, and that is triage's miss. Answered here:
manifest-depsforks and add the native-addon refusal) cannot land apart from thepackagingliveness row and narrowingPluginPackagingSchematobundledwithout a red liveness check. This lane claims the one PR, and the claim declarespackages/spec/src/kernel/manifest.zod.ts(the enum) andpackages/spec/liveness/manifest.json(the row).Clause-②: no (narrowing),Metadata-protocol: narrowed, the ADR-0087 disposition and aSkills-docs:line. A contract review is owed before approval (pm-dispatch(contract-review): a seat sends a PR to the maintainer for approval only after a contract PASS on the current head, and a PR that landed still owing one is re-reviewed after landing, with each finding filed as a new card #27345).manifest.runtime, which is now aliverow (#27123 follow-up) #27569 (lint example) and plugin install acceptance e2e on cloudplugins/connector-stripe: build, sign, install, boot, one tool, disable, tamper cases (#27123 item 6) #27570 (the cloud e2e, routed to the cloud PM this round). Item 5 stays serial behind PR feat(platform-objects): sys_exchange_rate — an organization's dated exchange rates, admin-written and audited, with a Setup list under Localization #27246.pm:retriageremoved.Generated by Claude Code
objectstack-fleet commentedon Oct 29, 2026
domain:server#2 · os-sales · session_01L6bhgUCizoYWQVJWjhdR6F
Claim: PM loop round 2 · card thread · slice 3 (server half) under ruling B′
Session:
session_01L6bhgUCizoYWQVJWjhdR6F(the seat); the card thread issession_01L6bhgWLCWkh3MaLj1qTLCeAccount:
os-salesBranch:
claude/project-thread-c3umeu(the card thread's harness-pinned branch, fromorigin/main4a00b2b7; it names no issue, so this claim is the identity record)Worktree:
objectstack-issue-27123in the card thread's own containerDomain:
domain:serverSeat:
domain:server#2Ruling-ref:
6467932167(letter B′); cross-domain exception6498767357(triage)File surface:
packages/cli/src/commands/plugin/build.ts(themanifest-depsforks; the native-addon refusal),packages/cli/src/utils/osplugin.ts(wording); cross-domain, declared under6498767357:packages/spec/src/kernel/manifest.zod.ts(PluginPackagingSchema→bundled) andpackages/spec/liveness/manifest.json(thepackagingrow); generated reference pages regenerated, never hand-edited (stop on breach; explain in the report)Container & model:
L,mode:thread,model: default (dispatch-gates --tier: no path-derived mandate),effort: xhigh(p1)Clause-②: no (narrowing — owes one contract-review-tier review before the queue)
Metadata-protocol: narrowed
Responsibility:
n/a — not a defect cardThread-read: 6498767357
Premise: corrected
6498767357(owed item 2 already done by slice 1; #27569 and #27570 split out; owed item 5 serial behind PR #27246 and not in this slice)Serial constraints cleared: none on
packages/cli/src/commands/pluginormanifest.zod.ts(no open PR);i18n-extract.config.tsleft alone (PR #27246)Resume-from: none (Release
6496079064: slice 3 starts frommain)objectstack-fleet commentedon Oct 29, 2026
domain:server#2 · os-sales · session_01L6bhgUCizoYWQVJWjhdR6F
Claim amendment to
6499525158(slice 3) · 2026-10-29T14:36Z · File surface increment, declaredThe ADR-0087 disposition triage
6498767357requires for this narrowing isregistered(the repo's precedents for narrowing a published authorable enum value, including the same manifest'smanifest-permissions-string-list-removed;not-required (no-migration-prescription)cannot hold because the BREAKING changeset names a FROM → TO). That disposition needs files outside the declared surface, so the claim's File surface adds, under the same cross-domain exception:packages/spec/src/conversions/registry.ts(one D2 conversion strippingpackaging: 'manifest-deps'frommanifestandpackages[].manifest)packages/spec/src/migrations/entries/semantic/(one D3 semantic entry, the current 18.x file)packages/spec/src/migrations/registry.ts.template(oneSTEP18_RATIONALEfragment)packages/spec/src/shared/retired-key-removal-version.pin.test.ts(one JOIN row)packages/spec/src/kernel/docs/qa/platform-checklist/areas/cli.json(the checklist item that exercises themanifest-depsfork; it turns false with this change)Triage may object here; the thread builds the
build.tshalf meanwhile. Everything else in the claim is unchanged.objectstack-fleet commentedon Oct 29, 2026
domain:server#2 · os-sales · session_01L6bhgUCizoYWQVJWjhdR6F
Claim amendment to
6499525158(slice 3) · 2026-10-29T16:56Z · File surface increment, declaredThe new kernel retirement pin
packages/spec/src/kernel/manifest-packaging-retirement.test.ts(declared in amendment6499637007) carries a tree-wide absence scan that reads files outsidepackages/spec, so the derived gatecheck:cross-package-test-inputsrequires it on the spec package's repo-test list. The claim's File surface adds, under the same cross-domain exception:packages/spec/vitest.repo-tests.json(one alphabetically placed entry for that test; the scan's glob is already declared inscripts/cross-package-test-inputs.mjsandturbo.json, as for the precedentpackage-upgrade-family-retirement.test.ts)Everything else in the claim is unchanged.
objectstack-fleet commentedon Oct 29, 2026
domain:server#2 · os-sales · session_01L6bhgUCizoYWQVJWjhdR6F
os-dev-report
{ "issue": 27123, "slice": "3 of 3, server half (claim 6499525158; increments 6499637007, 6503048893)", "status": "done", "branch": "claude/project-thread-c3umeu", "pr": "https://github.com/objectstack-ai/objectstack/pull/27679", "head": "b0bfccdd74d06f63a04a92bfc6350d26c8e42218", "base": "origin/main efa2915e (one merge, b3b4af13)", "session": "session_01L6bhgWLCWkh3MaLj1qTLCe", "premise_still_valid": true, "summary": "spec: PluginPackagingSchema narrowed to bundled; manifest-deps is a retired value refused with its prescription at every manifest door (ManifestSchema, defineStack manifest, ArtifactPackageEntrySchema, the install body) and out of the TS input type. D2 conversion manifest-packaging-manifest-deps-removed (toMajor 18, retiredAfter 18.2.0, order 80), D3 entry manifest-packaging-manifest-deps-retired with its STEP18 fragment (order 114), the removal-version JOIN row; liveness packaging row live to planned (one-member precedent), the manifest count shard regenerated. cli: os plugin build loses both manifest-deps forks and refuses a native addon before writing the artifact (a .node module in the esbuild graph with its importers, a bundled package with binding.gyp / gypfile true / a shipped .node, a .node under assets/), every hit named, exit 1, no artifact. Checklist item cli.plugin-manifest-build-contract revision 4; five reference pages regenerated by gen:docs; one changeset (spec minor, cli patch, registered disposition).", "tests": "Red on df778ab4 with the fix reverted: spec pin 8 failed / 7 passed (15); cli pin 7 failed / 2 passed (9), the two bundled controls green. Green: spec pin 15/15 (repo project, final head), cli pin 9/9 after a spec rebuild. spec --project local full on the fix: 751 files passed, the 24 failures all in check-liveness.test.ts (stale manifest shard), then gen:liveness-counts and scripts/liveness 12/12 files 359/359; spec test:repo on the final head 87/87 files 1448/1448; merged-head spec pins 81/81 files 2321/2321. cli --project unit 350/350 files 5063/5063; cli integration serve-installed-plugins + plugin/install + schema-migrate.one-shot-family 3/3 files 153/153 (a whole-package cli run hit the 30-minute background limit and was stopped, not counted). spec and cli typecheck with check:test-typecheck exit 0. Ablation A (native-addon refusal disabled, build.ts): cli pin 6 failed / 3 passed, exactly the six addon cases; restored, blob f5cb8ec5 == HEAD, git diff HEAD empty. Ablation B (manifest-deps put back as a live enum member, manifest.zod.ts): spec pins 6 failed / 68 passed; spec rebuilt, ablation-dist-preflight marker present in 2 built files; cli pin 1 failed / 8 passed, exactly the manifest-deps case; restored, blob ed95910f == HEAD, git diff HEAD empty; rebuilt, --absent marker gone from all 580 built files, tree clean. Heavy runs under os-verify-lock slot issue-27123, every cited VERDICT command-exit 0 except the ablation legs (expected 1).", "gates": "dispatch-gates --commands: 111 derived, the same list on b3b4af13 and on the final head. First run: 108 exit 0; check:cross-package-test-inputs exit 1 (new pin missing from vitest.repo-tests.json, fixed in b0bfccdd under increment 6503048893), check-plugin-teardown-shape --self-test exit 3 (pinned fixture commit absent from the shallow clone, fetched), check:dual-build-cjs-loads exit 3 (six packages unbuilt, built); all three rerun exit 0 on the final head, with check-changeset-no-major, check-adr-0087-registration and check-empty-changeset. --ran: 111 derived, 111 run, 0 NOT-MEASURED, 0 UNRUN. check-changeset-no-major with the PR body as the event payload: LEVEL AXIS ok (spec minor). pnpm lint repo-wide exit 0; eslint packages/cli packages/spec exit 0; check-nul-bytes OK; check-platform-checklist full OK; check:generated: only the references were stale, regenerated. PR check-runs not read yet: the PR was just opened.", "dogfood_battery": "merged tree b3b4af13 (the final head differs by one line in packages/spec/vitest.repo-tests.json) · os-verify-lock VERDICT command-exit 0, held 37m38s · Test Files 351 passed, 3 skipped (354) · Tests 2822 passed, 20 skipped (2842) · no failure; closure built with turbo --filter=@objectstack/dogfood^... (68/68 tasks), no OS_TEST_SHARD", "metadata_protocol": "narrowed: Clause-② no (narrowing); the changeset carries the BREAKING banner and the ADR-0087 registered disposition naming both new ids", "size": "19 files, +929/-77 (generated included): under 3000, not Tier H by size", "merge_tree": "no open PR changes a file this PR changes; 41 of 43 open heads merge clean, 27246 and 27623 conflict with origin/main itself in files this PR does not touch", "mcp_calls": "10 GitHub MCP read calls: issue_read x3, pull_request_read x4, list_pull_requests x1, search_pull_requests x2; no write tool (subscribe_pr_activity on 27679 is a harness call)", "api_writes": "2 relay dispatches (POST /repos/objectstack-ai/objectstack/dispatches): pr_create 27679 (draft, no assignees, no labels, no review request; read back 17702/17702 bytes identical, assignees none) and this os-dev-report comment. Two branch pushes, not counted.", "open_questions": [], "out_of_scope_findings": [ "ledger drift, not a/b/c: docs/qa/platform-checklist/areas/cli.json item cli.plugin-manifest-build-contract still carries the two negatives saying nothing re-verifies integrity at unpack and nothing dispatches on runtime, both made false by slices 1-2; left untouched in revision 4 and named in its history row · carrier: server seat 2 (os-sales) · noted, not filed", "observation: ui/packages/app-shell/src/console/marketplace/marketplaceApi.ts types cloud listing packaging as bundled or manifest-deps in a comment; docs/design/plugin-distribution.md and ADR-0025 still mention manifest-deps (the ADR edit is the Tier H half) · carrier: server seat 2 (os-sales) · noted, not filed", "observation: packages/spec/liveness/README.md manifest notes cell says Dead 19 while the generated shard says dead 13; pre-existing, the cell names no packaging set, left alone (outside the claim) · carrier: none · noted, not filed" ] }Generated by Claude Code
objectstack-fleet commentedon Oct 29, 2026
domain:server#2 · os-sales · session_01L6bhgUCizoYWQVJWjhdR6F
Claim amendment to
6499525158(slice 3) · 2026-10-29T17:48Z · File surface increment, declaredThe contract review of PR #27679 (
6504145955, should-fix 1) names three files in the diff that the claim and its amendments6499637007/6503048893do not list by name. Each is a mechanical companion of a declared file. The claim's File surface adds them:packages/cli/test/plugin-build-bundled-only.test.ts(the pin for the declaredpackages/cli/src/commands/plugin/build.ts).changeset/27123-plugin-packaging-bundled-only.md(the changeset the change owes)packages/spec/liveness/state-counts/manifest.md(the generated shard of the declared liveness row)Everything else in the claim is unchanged.
objectstack-fleet commentedon Oct 29, 2026
domain:server#2 · os-sales · session_01L6bhgUCizoYWQVJWjhdR6F
ACCEPT → PR #27679 (slice 3) comment 6504287105 · head
b0bfccdd· 2026-10-29T17:54Z · landing sent (contract review PASS6504145955). Follow-ups filed: #27684 (ADR-0025 edits, Tier H; this card's owed docs follow-up), #27685 (prose leftovers), #27686 (os plugin installmanifest validation). This card stays open for #27684 and the split cards #27569 / #27570.