Skip to content

Commit 30f8fdd

Browse files
docs(adr): ADR-0025 v2 says what M1 shipped — the local install loop, the trusted-key store, the native-addon refusal, manifest-deps retired (#27684) (#27815)
<!-- claude-projects-attribution: {"github_login":"os-elon-musk"} --> _Requested by **Elon** · [project thread](https://claude.ai/code/project/chan_01WedDzV26nniwxv7jC88B9D?thread=cmsg_01WedDzV26nniwxv7jC88B9DEsWMgMazMEzGNW4yuNJbZb)_ domain:skills#2 · os-elon-musk · session_016WV4d1j1VPnYjVUvuUMHjc Fixes #27684 Ruling-ref: 6467932167 Clause-②: no Metadata-protocol: none ## 维护者速读 - **改了什么:** 只改 `docs/adr/0025-plugin-package-distribution.md` 一个文件,把 ADR-0025 v2 里被 M1 三片(#27335、#27374、#27679)变成假话的说法改成代码现在的样子。卡上点名的 8 处:§3.5 的 M1 条改成 ✅ 已落地行;§7 按裁决 6467932167(B′)加一条带日期的说明(M1 已有最小形态的可信公钥库);"Steps 4–7 不存在、没有 `os plugin install`" 那句;§3.2 示例注释;§3.3 两句(原生插件拒绝、`manifest-deps` 构建半边已删);§3.5 第 5 步的 pnpm install;§5 风险一条。另有同类 3 处(席位已同意就地修):状态行、§3.4 第 2 步"验签没有生产调用方"、§3.6"没有任何东西按 tier 分派"。Q6 等正确的历史记录行一字未动。 - **为什么改:** ADR 是后续卡片和 AI 都要读的约束文本。现在它还说"没有 `os plugin install`"、"构建还不拒绝原生插件"、"可选用 manifest-deps 缓解",会把人和 AI 引向已经不存在或已退役的东西。这是 #27123 欠下的 Tier H 文档收尾。 - **风险与回滚:** 纯文档,无代码、无行为变化。风险只在措辞是否与代码一致:每一句替换文字都在 `origin/main` f5dd057 上实测过,证据见下方 What changes。M1 的验收(cloud 车道 Stripe 样例端到端,#27570)还没做,✅ 行如实写了"仍未完成"。回滚 = revert 本 PR。 - **先看:** §3.5 的 ✅ M1 行和它上面改写的 "Steps 4–7" 句;§7 的日期说明;第 3 行状态行。 - **两分钟验证:** 运行 `grep -n "M1 adds\|nothing dispatches\|Steps 4–7 do not\|no production caller\|mitigated by the opt-in" docs/adr/0025-plugin-package-distribution.md`,预期零命中(同一条命令在 main 上命中 5 行,都是被更正的假话);再运行 `grep -n "manifest-deps" docs/adr/0025-plugin-package-distribution.md`,预期剩下的每一行都在说它已退役。 - **需要你做的:** 现在无需操作。契约审查(fable)尚欠,由席位进行,尚未请求批准;审查通过后席位再请你批准,批准后由席位落地。 ## What changes One file, `docs/adr/0025-plugin-package-distribution.md` (+45 / −33). Every replacement sentence was measured on `origin/main` `f5dd0575`; line numbers below are on that commit unless marked. The eight edits the card lists: | # | Where | Was | Now | Evidence | |---|---|---|---|---| | 1 | §3.5, the M1 bullet | the plan | ✅ landed 2026-10-29 (#27335, #27374, #27679), what each step does, and the acceptance run on the cloud Stripe sample still open (#27570) | `install.ts` header steps 1–6; `installed-plugin-loader.ts` header checks 0–5; `plugin-installation-store.ts` header (local stand-in for the `sys_package_installation` row); `disable.ts`, `uninstall.ts` headers; #27570 open, `status:parked`, `domain:cloud` | | 2 | §7, key management | deferred to M3 | + a dated note, as ruling `6467932167` ordered: the trusted-key store landed in M1 in its minimal form; rotation and the `platform` / `cloud` sources stay M3 | `packages/platform-objects/src/system/sys-trusted-key.object.ts` (`source` select `local` / `platform` / `cloud`, `status` `active` / `revoked`); `packages/cli/src/commands/plugin/keygen.ts`; `trust/{add,list,revoke}.ts`; install refuses an unknown or revoked key id (`install.ts` header step 1), the loader too (`signature_key_untrusted`, `signature_key_revoked`) | | 3 | §3.5, "Steps 4–7 do not exist … there is no `os plugin install`" | false | steps 4–7 exist for a local artifact only (step 7's per-plugin enforcer is M2); in the cloud nothing serves the `.osplugin` back | `install.ts` exists and takes a file path only (`Args.string`, "Path to the .osplugin artifact"); §3.4's download-route sentence is unchanged and still true | | 4 | §3.2, `// bundled \| manifest-deps` | stale | `// bundled, the one strategy` | `manifest.zod.ts:258` `enumWithRetiredValues(['bundled'], PACKAGING_RETIRED)` | | 5 | §3.3, "the build does not yet refuse them mechanically (M1 adds the refusal)" | false | `os plugin build` refuses a native addon before writing anything | `build.ts:303` `findNativeAddons` and the refusal, before `computeIntegrity`; the only writes are `build.ts:331–332`, after it; esbuild runs with `write: false` (`build.ts:271`) | | 6 | §3.3, "the build-side half is removed in M1" | future tense | done: the schema refuses `manifest-deps` by name with its prescription; M1 (#27679) removed the build half | `manifest.zod.ts:238–258` (`PACKAGING_RETIRED`); `build.ts` header step 2 (everything but `@objectstack/*` inlined) | | 7 | §3.5 step 5, "For manifest-deps, run `pnpm install …` here" | stale | *v2:* nothing is installed there; install runs no package manager | `install.ts` header: verify, read, re-verify integrity, materialize, record; no package-manager call in `install.ts` | | 8 | §5 Negative, "mitigated by the opt-in manifest-deps strategy in Phase 5" | false | the build refuses native addons; with manifest-deps retired a plugin that needs one waits for M3 (§3.3) | §3.3's own retirement paragraph; `build.ts:303` | Three more in the same file, same defect class (made false by slices 1–3), fixed in place under os-dev's bounded in-place rule and agreed by the seat before the edit (claim 6511498060's file-surface line names them): | # | Where | Was | Now | Evidence | |---|---|---|---|---| | 9 | §3.6 v2 paragraph | "**nothing dispatches on the tier at load**"; "Until M3, the publish-time refusal is the only tier gate" | the boot-time loader dispatches (`node` imported in process; `sandbox` / `worker` and an undeclared tier refused by name); until M3 nothing loads isolated | `installed-plugin-loader.ts:180–195` `dispatchPluginRuntime` (`runtime_undeclared`, `runtime_not_dispatched`); `manifest.zod.ts:214–222` describe; listed as owed in the slice-2 hand-over 6496079064 | | 10 | §3.4 step 2 | "`verifyPluginArtifact` … has **no production caller until M1** (the loader checks only that a signature is well-formed)" | install and every boot verify against the environment's trusted keys; `plugin-loader.ts` refuses a signature no host verified | `install.ts:254` `verifyPublisherSignature`; `installed-plugin-loader.ts:341` `verifyPluginArtifact`; `plugin-loader.ts:519–552` | | 11 | Status line | M1 listed under "Not yet built" | M1 listed under what landed (acceptance run still open); the cloud fetch stays M2, tier dispatch beyond `node` stays M3; "amended 2026-10-30 to what M1 shipped" | the three merged PRs above; `docs/adr/README.md` index cell unchanged (`adr-index --check` OK) | Kept as written, because they correctly record the retirement: the v2 note (L15), the artifact tree (L141–142), the §3.3 RETIRED bullet head, the Q6 row, the §4 M1 phasing bullet and the Phase 5 mapping line. ## Tests None: an ADR text change, no code and no tested behaviour, so no ablation. ## Acceptance notes - **Line budget:** net +12 against the order's "≈ 0". Where it went: the §7 note the ruling ordered (+5, new content), §3.6 (+3, item 9), §3.3 (+2), the §3.5 M1 line (+2); the status line and every other hunk 0. Nothing was re-wrapped to buy lines. - **Deviation, declared:** items 9–11 are outside the card's eight. The order's mechanism assumption ("only fix the eight") was measured false: those three sentences would contradict the new ✅ line. Reported to the seat before the edit; the seat amended the claim. - **The ✅ line is not a full acceptance:** #27123's acceptance e2e on the cloud `plugins/connector-stripe` sample is #27570 (cloud lane, parked). The line says so. - **§7 note wording:** the ruling's order reads "rotation policy and platform / cloud sources stay M3"; the note uses that phrasing. The ruling's own body places the `cloud` rows with M2's chain; the note does not restate that schedule. - Observation, not edited (original design text, not one of the eleven and not contradicted by M1): §3.7 "Supply chain. Lockfile + per-file integrity …" still names a lockfile, which a bundled-only artifact does not carry. - Observation, not filed (code comment drift, outside this card's surface): `sys-trusted-key.object.ts`'s docblock says install verifies with `verifyPluginArtifact` (it calls `verifyPublisherSignature`, which `verifyPluginArtifact` wraps) and names the boot-time loader as not yet built. - No changeset: `docs/adr/**` publishes nothing; `skip-changeset` is set with this PR's creation. ## Verification Head `c49a117f`. Local subset = every family `node scripts/pm/dispatch-gates.mjs --commands` derives for this diff (21), re-run on the final head; the lint closure was built first for `check:doc-formula-expressions`. <details><summary>dispatch-gates: 21 derived, 21 run, all exit 0</summary> ``` node scripts/check-adr-links.mjs :: exit 0 node scripts/check-adr-links.mjs --self-test :: exit 0 node scripts/check-closing-keyword-parity.mjs :: exit 0 node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0 node scripts/check-comment-mask-corpus.mjs :: exit 0 node scripts/ci/plan-test-core-shards.mjs --self-test :: exit 0 pnpm --filter @objectstack/lint run check:doc-formula-expressions :: exit 0 pnpm check:adr-anchors :: exit 0 pnpm check:adr-index :: exit 0 pnpm check:adr-symbol-anchors :: exit 0 pnpm check:ci-filter-parity :: exit 0 pnpm check:closed-ceremony-text :: exit 0 pnpm check:cross-package-test-inputs :: exit 0 pnpm check:doc-authoring :: exit 0 pnpm check:driver-memory-census :: exit 0 pnpm check:gitlink-declared :: exit 0 pnpm check:nul-bytes :: exit 0 pnpm check:pm-governed-merges :: exit 0 pnpm check:pm-prior-rulings :: exit 0 pnpm check:refd-timer-probe :: exit 0 pnpm check:watch-hint-literal :: exit 0 ``` `dispatch-gates --ran`: "21 derived famil(ies) accounted for — 21 run, 0 NOT-MEASURED (a DERIVED zero — all 21 recorded an exit code and none of them is 3)." </details> <details><summary>Other readings</summary> - `node scripts/adr-index.mjs --check`: "OK — docs/adr/README.md is current and every record parsed." (the index cell is cut before the status-line text this PR changes). - `node scripts/check-commit-card-trailers.mjs --range origin/main..HEAD`: both commits clean. - `dispatch-gates --tier`: MANDATORY `docs/adr/**` ceiling tier; this draft was written at the default tier on the seat's recorded downgrade, and the contract review at the ceiling tier is owed before any approval is asked. - Changed lines 78 (+45 / −33), under the 3000 human-merge threshold. - Card re-check on the head: `grep -n "manifest-deps\|Steps 4–7\|M1 adds" docs/adr/0025-plugin-package-distribution.md` hits L15, L141–142, L188, L192, L295, L560, L567, L582, L603 — each one either records the retirement or is a corrected line. </details> --- _Generated by [Claude Code](https://claude.ai/code/session_016WV4d1j1VPnYjVUvuUMHjc)_ 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_016WV4d1j1VPnYjVUvuUMHjc --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent d5151a7 commit 30f8fdd

1 file changed

Lines changed: 61 additions & 38 deletions

File tree

‎docs/adr/0025-plugin-package-distribution.md‎

Lines changed: 61 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# ADR-0025: Plugin Package Distribution (Code + Dependencies)
22

3-
**Status**: Accepted — 2026-10-28 (ruled on objectstack#26553, ADR batch 4; proposed 2026-05, revised 2026-06-01, audited 2026-07-16 and 2026-09-12). Narrowed to what landed: Phase 1 (the `.osplugin` artifact, the compiled-manifest schema, `os plugin build` / `sign` / `publish`, cloud review and counter-signing) and install-time permission consent for package installs (console disclosure, `sys_package_installation.granted_permissions`, re-consent on a widening upgrade, the artifact → enforcer binding at load). Not yet built, carried by the M0–M3 programme on objectstack#27108: the code-plugin half of the install flow (§3.5 steps 4–7: `os plugin install`, download / verify / materialize / load — M1 local T0, M2 cloud fetch), runtime enforcement of the granted set (registered only today, §3.7 — M2), tier dispatch (T1 sandbox — M3), publish-side scans and secret-refs (M3). This revision (v2, 2026-10-28) corrects §3.3, §3.4, §3.8 and §3.10 to the code and records the maintainer's decisions Q1–Q8.
3+
**Status**: Accepted — 2026-10-28 (ruled on objectstack#26553, ADR batch 4; proposed 2026-05, revised 2026-06-01, audited 2026-07-16 and 2026-09-12). Narrowed to what landed: Phase 1 (the `.osplugin` artifact, the compiled-manifest schema, `os plugin build` / `sign` / `publish`, cloud review and counter-signing), install-time permission consent for package installs (console disclosure, `sys_package_installation.granted_permissions`, re-consent on a widening upgrade, the artifact → enforcer binding at load), and M1, the local T0 install loop (2026-10-29, §3.5: `os plugin install ./x.osplugin` against the environment's trusted keys, the boot-time loader with the tier dispatch point, disable / enable / uninstall; its cloud-sample acceptance run is still open). Not yet built, carried by the M0–M3 programme on objectstack#27108: the cloud fetch of a code plugin (§3.5 — M2), runtime enforcement of the granted set (registered only today, §3.7 — M2), tier dispatch beyond `node` (T1 sandbox — M3), publish-side scans and secret-refs (M3). This revision (v2, 2026-10-28) corrects §3.3, §3.4, §3.8 and §3.10 to the code and records the maintainer's decisions Q1–Q8; amended 2026-10-30 to what M1 shipped (§3.2, §3.3, §3.4, §3.5, §3.6, §3.7, §5, §7, §8).
44
**Deciders**: ObjectStack Protocol Architects
55
**Builds on**: [ADR-0003](./0003-package-as-first-class-citizen.md) (package + versioned releases), [ADR-0004](./0004-cloud-multi-kernel.md) (cloud multi-kernel), [ADR-0010](./0010-metadata-protection-model.md) (L1/L2/L3 protection), [ADR-0016](./0016-studio-package-authoring-and-publish.md) (package authoring & publish, local export/import)
66
**Consumers**: `@objectstack/core` (kernel, plugin-loader, security), `@objectstack/runtime` (sandbox, marketplace install), `@objectstack/cli`, `@objectstack/spec/system` (ObjectStackManifest), `@objectstack/spec/cloud`, `../objectui` (Studio)
@@ -156,7 +156,7 @@ Extends the existing `ObjectStackManifest` with three new blocks
156156
"type": "plugin",
157157
"engines": { "platform": ">=4.0 <5", "protocol": ">=1.0" },
158158
"runtime": "node", // node | sandbox | worker (see §3.6)
159-
"packaging": "bundled", // bundled | manifest-deps (see §3.3)
159+
"packaging": "bundled", // bundled, the one strategy (see §3.3)
160160
"permissions": { // explicitly requested capabilities
161161
"services": ["object", "http"],
162162
"hooks": ["record.beforeInsert"],
@@ -182,13 +182,15 @@ checks the existing `PluginPermissionEnforcer` already implements
182182
the manifest against `ManifestSchema` and writing per-file `integrity` into the
183183
compiled manifest. Install = unpack + verify; **no npm/toolchain at install
184184
time**. This is the closest analog to the JSON flow and keeps the security
185-
surface minimal. Native (`.node`) addons are disallowed by rule; the build does
186-
not yet refuse them mechanically (M1 adds the refusal).
185+
surface minimal. Native (`.node`) addons are disallowed by rule, and since M1
186+
`os plugin build` refuses one before writing anything (a `.node` module or a
187+
native package the bundle reaches, or a `.node` file under `assets/`).
187188
- **manifest-deps — RETIRED (v2, maintainer decision Q6, 2026-10-28; ADR-0049
188189
enforce-or-remove).** The strategy existed on the build side only (it shipped
189190
`package.json` + `pnpm-lock.yaml` and nothing ever installed them); it had zero
190-
consumers and no install path. `PluginPackagingSchema` keeps `bundled` as its
191-
only live value; the build-side half is removed in M1. Plugins that need native
191+
consumers and no install path. `PluginPackagingSchema` accepts `bundled` only
192+
and refuses `manifest-deps` by name with its migration prescription; the
193+
build-side half was removed in M1 (#27679). Plugins that need native
192194
addons or very large dependencies are a fresh decision for M3, with this
193195
retirement as the starting evidence.
194196

@@ -214,9 +216,9 @@ os plugin build → os plugin sign → os plugin publish
214216
`<id>-<version>.osplugin` (ustar + gzip) with a placeholder `SIGNATURE` entry.
215217
2. **sign** — Ed25519 over the raw artifact bytes, written as
216218
`ed25519:<keyId>:<base64url>` to a `.sig` sidecar (`sign.ts`); the publisher
217-
key stays with the publisher. Verification (`verifyPluginArtifact`) exists in
218-
core and has **no production caller until M1** (the loader checks only that a
219-
signature is well-formed, `plugin-loader.ts`).
219+
key stays with the publisher. Since M1 the host verifies it against the
220+
environment's trusted keys (§7) at `os plugin install` and again at every boot
221+
(`verifyPluginArtifact`); `plugin-loader.ts` refuses a signature no host verified.
220222
3. **publish** — extends `os package publish` and **reuses the package registry**
221223
(the `/cloud/plugins` endpoints of the original text were never built):
222224
- `POST /cloud/packages` → ensure the `sys_package` row (reverse-domain `id`,
@@ -264,13 +266,13 @@ resolve → compat check → permission consent → download+verify → material
264266
trusted publisher + marketplace keys; verify each file against `integrity`.
265267
Reject on any mismatch.
266268
5. **Materialize.** Unpack to a per-environment, per-plugin dir
267-
`<OS_HOME>/plugins/<env>/<id>/<version>/`. For manifest-deps, run
268-
`pnpm install --frozen-lockfile --ignore-scripts` here.
269+
`<OS_HOME>/plugins/<env>/<id>/<version>/`. *v2:* nothing is installed there;
270+
`bundled` is the one strategy (§3.3), so install runs no package manager.
269271
6. **Register.** Write the installation record — `sys_package_installation`
270272
(`env_id`, `version_id`, `granted_permissions`, `status`; v2 §3.8, the
271273
`sys_plugin_installation` of the original text was never created). Persist to a local store so installs survive
272274
restarts (reuse the ADR-0016 §9.7 `package-state-store.ts` pattern).
273-
7. **Load + activate.** On next boot (or hot, when `hotReloadable`), the loader
275+
7. **Load + activate.** On next boot (*v2:* boot only, `hotReloadable` retired), the loader
274276
scans installed plugins, dynamic-imports the entry, wraps `PluginContext`
275277
with the enforcer scoped to the granted set, and runs `init → start` in
276278
dependency order. If the plugin also has `contributes` metadata, that JSON is
@@ -280,7 +282,9 @@ resolve → compat check → permission consent → download+verify → material
280282
plugin" upload both hit a `marketplace/install-local`-style endpoint (inline
281283
artifact, register-before-persist per ADR-0016 §9.3). Local artifacts need no
282284
cloud account; signature verification is still performed (publisher key or an
283-
explicit `--trust-unsigned` dev override).
285+
explicit `--trust-unsigned` dev override). *v2 — as built in M1:* no endpoint and
286+
no Studio upload; `os plugin install` is a CLI act at the host that persists at
287+
install (under `OS_HOME`) and registers the plugin at the next boot.
284288

285289
*v2 — where the flow stands and how it lands (maintainer decisions Q1–Q2, 2026-10-28):*
286290

@@ -290,21 +294,23 @@ explicit `--trust-unsigned` dev override).
290294
code-bearing package, `sys_package_installation.granted_permissions`, and a
291295
409 `PERMISSION_RECONSENT_REQUIRED` on a widening upgrade. The granted set
292296
always equals the declared set — there is no partial grant.
293-
- **Steps 4–7 do not exist** for code plugins: there is no `os plugin install`,
294-
nothing reads a `.osplugin` back except `publish`, no `<OS_HOME>/plugins/…`
295-
path, no `--trust-unsigned`, and an environment artifact carries
297+
- **Steps 4–7 exist for a local artifact only** (M1, below; step 7's per-plugin
298+
enforcer is M2, §3.7). For a code plugin in the cloud they do not: nothing
299+
serves the `.osplugin` back (§3.4), and an environment artifact carries
296300
`sys_package_version.manifest_json` — metadata only, never the blob.
297-
- **M1 — local T0 loop, first-party / self-signed only** (`os plugin install
298-
./x.osplugin`): verify the whole-artifact Ed25519 signature against the host's
299-
trusted keys (`verifyPluginArtifact` gets its first production caller) and
300-
re-verify every file against `integrity` at unpack; materialize to
301-
`<OS_HOME>/plugins/<env>/<id>/<version>/`; write the installation record
302-
(§3.8); `--trust-unsigned` as the explicit dev override. The loader scans
303-
installed plugins **at boot** (hot reload was retired with #12587),
304-
dynamic-imports `dist/index.mjs`, reuses `plugin-loader.ts` dependency
305-
ordering, and carries a tier dispatch point that M1 wires for `node` only.
306-
Disable / uninstall reuse the package disable-state store. Acceptance: the
307-
cloud `plugins/connector-stripe` sample installs, loads and disables cleanly.
301+
- ✅ **M1 — local T0 loop, first-party / self-signed only — landed 2026-10-29**
302+
(objectstack#27123: #27335, #27374, #27679). `os plugin install ./x.osplugin`
303+
verifies the `.sig` over the raw bytes against the environment's trusted keys
304+
(§7) before inflating anything, re-verifies every file against `integrity`,
305+
materializes to `<OS_HOME>/plugins/<env>/<id>/<version>/` and records the
306+
install in a local store (the stand-in for the §3.8 row); `--trust-unsigned`
307+
admits only an unsigned artifact, loudly. At **boot** (hot reload was retired
308+
with #12587) `os serve` re-verifies each install (artifact sha, signature,
309+
byte-equal tree), dispatches on the tier (§3.6) and hands what passed to
310+
`kernel.use`, which orders dependencies. `os plugin disable` / `enable` use the
311+
package disable-state store; `uninstall` takes effect at the next boot. Still
312+
open: the acceptance run on the cloud `plugins/connector-stripe` sample
313+
(objectstack#27570).
308314
⛔ **M1 is first-party / self-signed only and is listed on no marketplace
309315
surface**: T0 is author code in the host process (§3.6).
310316
- **M2 — cloud fetch**: the artifact download route serves the `.osplugin` with
@@ -333,13 +339,16 @@ enforces this at publish time (an unverified publisher cannot ship `runtime:
333339

334340
*v2 — as built and as phased (maintainer decision Q3):* `PluginRuntimeSchema`
335341
holds the three values, the cloud refuses `node` from an unverified publisher at
336-
publish, and **nothing dispatches on the tier at load** — every plugin that
337-
loads today loads in-process. The QuickJS runner serves hook / action script
342+
publish, and since M1 the boot-time loader dispatches on an installed plugin's
343+
tier (`dispatchPluginRuntime`): `node` is imported in process, `sandbox` /
344+
`worker` and an undeclared tier are refused by name — every plugin that loads
345+
today loads in-process. The QuickJS runner serves hook / action script
338346
bodies (§3.10 L1) and is never reached from a plugin's declared tier;
339347
`PluginSandboxRuntime` has no production construction site; T2 has no code.
340-
M1 wires the dispatch point for T0 only; **M3** routes `runtime: 'sandbox'` to
341-
QuickJS and decides the T2 RPC surface (§7). Until M3, the publish-time refusal
342-
is the only tier gate, which is why M1 stays first-party / self-signed.
348+
M1 wired the dispatch point for T0 only; **M3** routes `runtime: 'sandbox'` to
349+
QuickJS and decides the T2 RPC surface (§7). Until M3 nothing loads isolated
350+
(those tiers are refused, not confined), which is why M1 stays first-party /
351+
self-signed.
343352

344353
### 3.7 Security model (reuses existing components)
345354

@@ -387,9 +396,12 @@ is the only tier gate, which is why M1 stays first-party / self-signed.
387396
lands, with #11982's zero-caller measurement as its starting evidence — the
388397
manifest `configuration` block below records the design intent, not a live
389398
validator.
390-
- **Supply chain.** Lockfile + per-file `integrity`; server-side scan for
391-
secrets and known-vuln deps; SBOM stored on the version row; **always**
392-
`--ignore-scripts` (no `postinstall`).
399+
- **Supply chain.** Per-file `integrity`; server-side scan for secrets and
400+
known-vuln deps; SBOM stored on the version row; **always** `--ignore-scripts`
401+
(no `postinstall`). *v2:* no lockfile ships (bundled only, §3.3), so install
402+
runs no package manager at all; `integrity` is re-verified at install, and at
403+
every boot the install directory is held byte-equal to the signed artifact
404+
(M1); the scans are M3 (§3.4).
393405
- **Reversible disable.** Reuse the package enable/disable + disable-state-store
394406
(ADR-0016 §9.5/§9.7): disabling a plugin stops loading its code and unregisters
395407
its contributions on next boot.
@@ -592,8 +604,8 @@ its download / scan half is M2 / M3; Phase 4 (sandbox tier) = M3; Phase 5
592604
JSON-only flow never needed.
593605
- Permission consent + tiering add UX and lifecycle surface (re-consent on
594606
permission widening, tier enforcement).
595-
- Bundled packaging excludes native addons by default (mitigated by the opt-in
596-
manifest-deps strategy in Phase 5).
607+
- Bundled packaging excludes native addons, and `os plugin build` refuses them;
608+
with manifest-deps retired (Q6), a plugin that needs one waits for M3 (§3.3).
597609

598610
### Neutral
599611

@@ -628,7 +640,14 @@ its download / scan half is M2 / M3; Phase 4 (sandbox tier) = M3; Phase 5
628640
on the next boot.
629641
- **Key management & rotation** — deferred to **M3** (publisher keys stay with the
630642
publisher; the host's trusted root set and its rotation / revocation are
631-
designed with the T1 tier).
643+
designed with the T1 tier). *Note (2026-10-30, ruling `6467932167` on
644+
objectstack#27123, letter B′):* the host's trusted-key **store** landed in M1
645+
in its minimal form — `sys_trusted_key` (public keys only, `source: 'local'`),
646+
`os plugin keygen` and `os plugin trust add | list | revoke`; install and boot
647+
refuse an unknown or revoked key id by name. Rotation policy stays M3; the
648+
`platform` / `cloud` values of `source` are reserved for the keys M2 / M3 cache
649+
from the platform chain (M2's `cloud` rows: root key → platform
650+
counter-signature → publisher key).
632651
- **T2 RPC surface** — deferred to **M3** (T2 stays reserved; the minimal
633652
host-service RPC contract is designed when the sandbox tier lands).
634653
- **Cross-plugin dependency conflicts** — deferred to **M3** (M1 and M2 install
@@ -654,6 +673,10 @@ its download / scan half is M2 / M3; Phase 4 (sandbox tier) = M3; Phase 5
654673
- `packages/core/src/security/granted-permissions-not-enforced.pin.test.ts` — the registered-not-enforced measurement pin (deleted by M2)
655674
- `packages/core/src/security/plugin-artifact-signature.ts` — Ed25519 sign / verify / counter-sign (v2 §3.4)
656675
- `packages/cli/src/commands/plugin/{build,sign,publish}.ts`, `packages/cli/src/utils/osplugin.ts` — the Phase 1 pipeline as built (v2 §3.3–3.4)
676+
- `packages/cli/src/commands/plugin/{install,keygen,disable,enable,uninstall}.ts`, `packages/cli/src/commands/plugin/trust/{add,list,revoke}.ts` — the M1 install loop and the trusted-key commands (v2 §3.5, §7)
677+
- `packages/runtime/src/installed-plugin-loader.ts`, `packages/runtime/src/plugin-installation-store.ts`, `packages/cli/src/utils/installed-plugins-boot.ts` — the boot-time loader with the tier dispatch point, the local installation records, and `os serve`'s registration of what passed (v2 §3.5, §3.6)
678+
- `packages/core/src/security/plugin-artifact-verdict.ts` — the host's verdict `plugin-loader.ts` reads before it accepts a `signature` (v2 §3.4)
679+
- `packages/platform-objects/src/system/sys-trusted-key.object.ts` — `sys_trusted_key`, the environment's trusted publisher public keys (v2 §7)
657680
- `packages/runtime/src/security/artifact-granted-permissions.ts` — the artifact → enforcer binding at load (v2 §3.7)
658681
- `packages/spec/src/kernel/manifest.zod.ts` — `PluginPermissionsSchema`, `PluginEnginesSchema`, `PluginRuntimeSchema`, `PluginPackagingSchema`, `PluginIntegritySchema` (v2 §3.2)
659682
- objectstack#27108 — the M0–M3 programme (v2 §4)

0 commit comments

Comments
 (0)