Repository navigation
[finding] The environment artifact's checksum digests the metadata block only — the new grantedPermissions consent set (#14865) rides the envelope outside any integrity coverage #14993
Description
Activity
分诊裁定:入决策箱(本评论来自分诊座位)· R+150 ·
date -u实测 2026-09-04T19:42:20Zneeds-user-decision·domain:spec·security·finding·priority:p3。⛔ 不打pm:queue—— 卡面自陈「whether the granted set needs integrity coverage is a decision, not a mechanical fix, and it touches the security boundary (human floor for any change)」,本席同意:安全边界上的契约变更是人工地板的明列项。落点现验(
origin/main,同一次调用):packages/spec/src/system/environment-artifact.zod.ts:173grantedPermissions: z.record(z.string(), PluginPermissionsSchema).optional(),而同文件:41的文件头逐字写着 "metadata, outside thechecksumdigest" ⇒ 事实成立:同意集就在信封上、就在摘要之外,而且这件事是被写下来的,不是被忽略的。⇒ 落点packages/spec⇒domain:spec。四棱分析(分诊席出具,供裁决;⛔ 本会话档位 opus,不代裁)
- ① 项目长远合理性(权重 ≥50%,领起)—— 指向「先把语义钉死,再谈要不要加覆盖」。 今天的形状不是「忘了覆盖」,而是
checksum这个名字承诺的范围大于它实际覆盖的范围:它是metadata块的摘要,却坐在信封上叫「checksum」。长期健全的终态只有两个:要么名字收窄到它实际覆盖的东西(metadataChecksum一类),要么覆盖面扩到名字暗示的东西。⚠️ 而这两件事都比「加不加一个摘要」更根本,且第一件几乎零成本、不动任何验证者。⇒ ① 认为无论 ②怎么答,命名/文档这一步都该做。 - ② 实际业务拉动 —— ⛔ 未测量,且本席读不到决定它的事实。 是否需要完整性覆盖,取决于载体是否端到端可信(ADR-0003 / cloud ADR-0007)。cloud 仓是私有的,本席读不到那份记录,因此无法判断威胁模型是否已经把这条路径覆盖掉。⇒ 这不是「拉动弱」,是「拉动未知」,而未知的方向是双向的:若载体可信,答案就是「记录下来、不改」;若不可信,那么一个能改这个块的载体可以静默地放宽插件权限。
- ③ 防 AI 犯错 —— 中性偏「记录下来」。 陷阱确实存在(读到
checksum会以为整个信封被覆盖),但本仓已经把反例写在同一个文件的文件头里(:41与:36)。⇒ 会读 schema 的 agent 读得到;会踩的是只看键名的读者。这一棱支持把这句话搬到更显眼处(例如checksum字段自己的 describe),而不是支持改摘要。 - ④ 创业阶段不扩散 —— 明确反对两个改造选项,尤其是选项 2a。 把
checksum扩到metadata + grantedPermissions是破坏性变更:每一个生产者与验证者都要同步移动,而这条链跨开源仓与控制面两侧。加一个兄弟摘要是加性的、验证者可选,但它新增一个长期要维护的概念,而 ② 还没给出需要它的证据。⇒ ④ 支持「先文档、后机制」。
推荐:选项 1 的「documented, no change」作为现在的落点,但把它做实而不是做虚。 具体两步,都不动契约:① 把
:41文件头那句话搬进checksum字段自己的.describe(),让只读字段的人也看得到覆盖边界;② 在grantedPermissions的 describe 里点明它不在摘要内以及为什么这是可以接受的(载体信任模型 + 引 ADR)。
⛔ 但这个推荐带一个硬条件:它只有在载体端到端可信时成立。⇒ 请维护者在裁定时直接回答 ②:若载体不可信,推荐立即翻转为「兄弟摘要(加性,验证者可选)」,⛔ 而不是加宽checksum(④ 反对,且它会把破坏性变更压到每个验证者身上)。置信缺口(必录):⛔ 本席未读也读不到 cloud ADR-0007 与 ADR-0003 对载体信任的原文(cloud 仓私有)—— 而那正是决定本卡答案的事实。⛔ 本席也未测量控制面是否已在别处对该块签名(若已签,本卡整个消失)。
⚠️ 这两条缺口的方向是相反的,所以不能靠猜取中值。p3 判据:今天没有已知的可利用路径(载体是环境本地、控制面服务),且卡面自陈无变更被提出;它值得被裁,但⛔ 不应排在决策箱里那张 p0 安全卡(#15409)之前。
⚠️ 若维护者回答 ② 时说「载体不可信」,严重度立刻重估。邻卡辨析(卡面已做,本席复核采纳):#11331 / #13563 是
.osplugin包的manifest.integrity逐文件摘要,不是信封上同意块的覆盖问题 —— 不同缺口,⛔ 不合并。
Generated by Claude Code
- ① 项目长远合理性(权重 ≥50%,领起)—— 指向「先把语义钉死,再谈要不要加覆盖」。 今天的形状不是「忘了覆盖」,而是
Maintainer ruling recorded — Option 1, made concrete: no change to the digest mechanism; the coverage boundary of
checksum(metadata block only) moves into thechecksumfield's own.describe(), andgrantedPermissions' describe states that it sits outside the digest and why that is acceptable (carrier trust model, citing ADR-0003 / cloud ADR-0007)Director seat, summon #14, session
session_01LsEjuNMPitCHwEfYftZ1um(GitHubos-warren), 2026-09-05. Provenance: maintainer, live PM chat, decision batch #41 (item 3, presented with the recommendation 1 and the explicit request to answer "carrier trusted / not trusted" in the reply), verbatim reply 「同意」.⚠️ The reply adopted the recommendation as presented and did not separately answer the trust question; the recommendation was presented on the premise that the carrier is trusted end-to-end, so option 1 is recorded on that premise. If the maintainer states the carrier is not trusted, this ruling flips to 2b (an additive sibling digest over the granted set, validator-optional) and the card returns at p1 — recorded here so the flip is a one-line reply, not a re-litigation. Premise:environment-artifact.zod.ts:173(grantedPermissionson the envelope) and the file header at:41("metadata, outside thechecksumdigest"), triage facets 5545641358; no known exploitable path today.Ruled: 1. Two describe edits in
packages/spec/src/system/environment-artifact.zod.ts, no contract change: thechecksumfield says in its own describe exactly what it covers (themetadatablock) and what it does not;grantedPermissionssays it is outside the digest, that integrity of the granted set rests on the carrier (environment-local, control-plane served) per ADR-0003 / cloud ADR-0007, and points at this card. Not taken: 2a (widenchecksum— a breaking change on every producer and validator across the open repo and the control plane, refused by ④), 2b (held as the flip above), a rename ofchecksum(a breaking envelope key change for a documentation problem).Why (① ≥50%): the name promises more than it covers; the fix that costs nothing is to make the field say what it covers where a reader of the field sees it. ③ an agent reading the schema already meets the header; moving the sentence to the field closes the "key-name-only reader" gap. ④ no new mechanism without evidence it is needed.
Execution:
domain:speclane, XS. Regenerate the reference docs if the describe feeds them. Clause-② no. Changeset:@objectstack/specpatch (describe text). The PR closes this card.⚠️ Not to be sequenced ahead of #15409's PR in the services lane; unrelated files, no conflict expected.State transition, same stroke:
needs-user-decision→pm:queue.security·domain:spec·finding·priority:p3unchanged. Ledger: director seat post #12708, batch #41. Related: #14865 · ADR-0003 · #11331 / #13563 (a different gap —.ospluginper-file integrity — not merged).
Generated by Claude Code
Claim: PM loop round R2 —
domain:specseat,session_01M59rPZZFzqhfMUPFqqZTkf, 2026-09-05T17:09Z. One dev, branchclaude/issue-14993-checksum-coverage-describe,mode:subagent, tierclaude-opus-5(docs-only describe edit, no contract change). Size XS.Clause-②: no
Ruled card, executed as ruled: director ruling
5548641412(2026-09-05T02:09Z; maintainer verbatim 「同意」 to decision batch #41 item 3, adopted on the stated premise that the carrier is trusted end-to-end) — Option 1, made concrete: two describe edits inpackages/spec/src/system/environment-artifact.zod.ts, no change to the digest mechanism and no contract change. Thechecksumfield says in its own.describe()exactly what it covers (the canonical JSON serialization of themetadatablock) and what it does not (anything else on the envelope,grantedPermissionsincluded).grantedPermissions' describe adds that it sits outside the digest, that integrity of the granted set rests on the carrier (environment-local, control-plane served) per ADR-0003 / cloud ADR-0007, and points at this card. Not taken: 2a (widenchecksum— breaking for every producer and validator across both repos), 2b (held as the recorded flip: if the maintainer states the carrier is not trusted, this card returns at p1 with an additive sibling digest), a rename ofchecksum. Clause-② no: describe text only; the accept set ofEnvironmentArtifactSchemadoes not move.File face (readings on
origin/mainaa6ba0623at 2026-09-05T17:07Z):packages/spec/src/system/environment-artifact.zod.ts—checksum: Sha256DigestSchemaat:112carries no field-level describe today (only the sharedSha256DigestSchemadescribe at:76, "SHA-256 digest (64 hex chars)", which other digest fields also inherit, so the field-level describe goes on thechecksumkey, ⛔ not on the shared schema);grantedPermissions' describe at:174–:180says nothing about the digest; the file header:36–:42and the key docblock:170–:171already state the boundary in prose. Reference pagescontent/docs/references/system/environment-artifact.mdx(and the cloud mirror underreferences/cloud/) regenerated if the describe feeds them (packages/spec/scripts/build-docs.ts). One@objectstack/specchangeset,patch. Hot-file check (30 open PRs read at 2026-09-05T17:08Z): none touchesenvironment-artifact.zod.tsor its reference page; #15973 / #15938 / #15626 touch othercontent/docs/references/system/*pages plus the sharedindex.mdx/meta.json— regen-only overlap, no conflict expected. Sequencing note from the ruling honored: not ahead of #15409's services-lane PR; unrelated files. Dedup: #11331 / #13563 are.ospluginper-file integrity, a different gap — not merged.
Generated by Claude Code
os-dev-report
{ "issue": 14993, "status": "done", "branch": "claude/issue-14993-checksum-coverage-describe", "pr": "https://github.com/objectstack-ai/objectstack/pull/16008", "premise_still_valid": true, "summary": "Ruling option 1 executed as ruled: two describe edits in packages/spec/src/system/environment-artifact.zod.ts, no contract change. `checksum` now carries its own field-level .describe() stating what it covers (the SHA-256 digest of the canonical JSON serialization of the `metadata` block, stable key ordering, computed by the control plane when assembling the GET response) and what it does not (nothing else on the envelope, `grantedPermissions` included); the shared Sha256DigestSchema describe is untouched and still inherited by every other digest field (probed: its description is byte-identical after the edit, and shape.checksum is a different instance). `grantedPermissions` gained one appended sentence group: it sits beside `metadata`, outside the digest, and integrity of the granted set rests on the carrier (environment-local, control-plane served) per ADR-0003 / cloud ADR-0007; its five existing clauses are unchanged. ONE DECLARED DEVIATION from the ruling's letter: the ruling asked that sentence group to point at this card, but `pnpm check:doc-authoring` reds on an internal issue id inside .describe() prose (measured: it named this exact string), on the maintainer ruling that describe text is printed at the customer where a citation-shaped id points at nothing. Its own prescribed route is an adjacent // comment, so the card anchor lives in a two-line comment beside the key and the describe keeps only the customer-resolvable ADR references. The PR body carries the ruling verbatim including the trust premise, both landed describe texts, and this deviation. Stop condition checked and NOT triggered: grep of grantedPermissions across packages/, examples/, apps/, content/ finds no in-repo producer or validator that signs or digests the granted set, so the card's premise holds. Assignee was already set by the dispatch and was never written by me; the newest Claim: on the card (comment 5553417839) names this branch.", "tests": "All figures below are from the final commit af3041c9 (this branch merged with origin/main at f7db8f4fd); heavy runs went through scripts/pm/os-verify-lock.sh with OS_VERIFY_LOCK_SLOT=issue-14993 and every verdict is the wrapper's own 'VERDICT command-exit' line, never a bare $?. (1) pnpm --filter @objectstack/spec test -- 'Test Files 478 passed (478) / Tests 12844 passed (12844)', VERDICT command-exit 0. (2) pnpm --filter @objectstack/spec typecheck -- VERDICT command-exit 0; check:test-typecheck OK, ledger unmoved at 54 files / 261 errors / 145 pinned signatures. (3) Targeted file run: 33 passed (27 before this PR, 6 added). (4) Repo-wide lint RAN, not narrowed: pnpm lint = eslint . --no-inline-config over the whole repo, EXIT=0 captured before any pipe. (5) Gate families derived mechanically by node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack at the merged head (change set: 3 paths, no stale-tree warning): 72 commands, 70 exit 0. The two exceptions are exit 3 = PREREQUISITE NOT MET, read as NOT MEASURED and not as failures: pnpm check:dual-build-cjs-loads (78+ packages have no dist) and pnpm check:type-check-debt (29 workspace deps unbuilt); both want a whole-workspace build, which is CI's run. check:type-check-coverage itself passed at exit 0. An earlier lap had two more exit-3 results (check:doc-formula-expressions, needing @objectstack/formula + @objectstack/lint built) and one genuine exit 1 (check:doc-authoring); the prerequisite was built and both are green in the final sweep. (6) Regeneration measured, not assumed: pnpm --filter @objectstack/spec gen:docs generated 230 files and left the working tree clean, so the field-level describe does not project into content/docs/references/** (system page or cloud mirror); check:generated reports all 15 generated artifacts up to date, check:docs and check:authorable-surface among them. Nothing under content/docs/ was hand-edited; content/docs/releases/ untouched. (7) Ablation, predicted direction RED and observed RED. No rebuild leg is owed: the test names its subject by the relative same-package import './environment-artifact.zod', so it resolves to src, not through any package exports to dist -- the condition ablation-dist-preflight.mjs exists for is absent here. Implementation committed FIRST (blob cea5caa47d6cae84f708155e815c30ce4bb08fb1). Mutation: the whole declaration restored from the pinned base sha aa6ba0623 (both describes gone), written tree-only. On-disk proof, anchored on the text actually being removed: marker counts went 1 -> 0 for 'nothing else on the envelope' and for 'rests on the carrier', and the mutated blob equals the base blob exactly. Run: Tests 4 failed | 29 passed (33) -- the 4 are the coverage-sentence pins and the shared-instance pin. The other 2 new assertions stay green by design (the base declaration validates identically and carries no issue id), reported as observed rather than claimed red. Restore leg: git checkout HEAD -- ABSOLUTE_PATH under a trap on EXIT/INT/TERM, verified by hash (on-disk hash equals the HEAD blob) and by an empty git diff HEAD, not by an exit code. (8) Byte hygiene: pnpm check:nul-bytes exit 0, plus the wider self-scan grep -naP over all three changed files -- no match (grep exit 1, zero bytes of output).", "mcp_calls": "0 - no MCP GitHub calls this run. The session's repo-scoped REST probe returned 200, so the card read, the claim verification, the PR creation, the title fix and the report comment all went over REST; everything else was git and local tooling.", "open_questions": [ { "question": "The ruling's letter asked the grantedPermissions describe to point at this card, but pnpm check:doc-authoring refuses an internal issue id inside .describe() prose and named that exact string. I took the gate's own prescribed route (an adjacent // comment) and declared it in the PR body. Should the ruling text be treated as satisfied, and should future dispatch rulings stop asking for a card id inside customer-facing spec text?", "options": [ "A - treat it as satisfied: the gate is a maintainer ruling of its own ('保留 issue id没有意义'), the anchor survives for internal readers in the // comment, and the describe keeps the customer-resolvable ADR references.", "B - route the card reference back into the describe with a gate exemption. The gate says there is no per-string exemption to reach for, by design, so this needs a maintainer decision on the gate itself, not on this PR.", "C - land as-is and file a separate note asking the director seat to stop writing 'points at this card' into rulings that land in .describe() prose." ], "recommendation": "A, because the gate and the ruling rest on the same premise - the surface must be readable by whoever sees it - and they only disagree about which reader. Nothing in the ruling's intent is lost: the boundary sentence is on the surface, the trust premise is in the PR body verbatim, and the card is one grep away in the code. C is worth doing as a PM-side note, but it is not this card's work and I filed nothing for it." } ], "out_of_scope_findings": [] }
Generated by Claude Code
Review — ACCEPT (domain:spec seat,
session_01M59rPZZFzqhfMUPFqqZTkf, 2026-09-05T18:34Z; PR #16008 at headaf3041c92; ruling5548641412option 1 executed as ruled; Clause-② no — describe text only, the accept set ofEnvironmentArtifactSchemadoes not move, so no carrier is hung). Read 18:31–18:33Z onorigin/main…af3041c92:checksum: Sha256DigestSchema.describe(…)on the key — covers the canonical JSON of themetadatablock (stable key ordering, computed by the control plane on the GET response), coverage stops there,grantedPermissionsnamed as outside; the sharedSha256DigestSchemadescribe byte-identical and pinned as a different instance;grantedPermissionskeeps its five clauses and gains the boundary sentence group with the carrier-trust premise and ADR-0003 / cloud ADR-0007; one@objectstack/specpatch changeset; six describe-text pins (ablation reddens the four coverage/shared-instance pins as predicted, restore proven by blob hash). Governed predicate 0 of 3. Regeneration measured, not assumed:gen:docsleaves the tree clean — the reference pages render only the standaloneSha256Digestentry, not per-field describes — andcheck:generatedreads 15/15. Stop condition checked: no in-repo producer or validator signs or digests the granted set (premise holds). Open question ruled A: the card id lives in an adjacent//comment, not in the describe —check:doc-authoringrefuses an internal issue id in customer-facing.describe()prose by the maintainer's own ruling, the ADR references stay in the describe, and the trust premise is in the PR body verbatim; the seat records for the shift report that rulings landing in.describe()prose should not ask for a card id there (option C's note, PM-side). Verification accepted: full spec suite 478 files / 12 844 tests, spec typecheck, repo-widepnpm lintexit 0, gates 70 of 72 green with two whole-workspace prerequisites NOT MEASURED (CI's). Seat note: the PR title was re-spelled by the seat at 18:32Z (no backticks, the ruling's two facets named) — the squash commit takes it. Landing: waits onmaingreen (#15992 → PR #16002 auto-merging); then amainmerge lap by tooling and flip to ready + auto-merge (squash). On merge: #14993 closes by the PR.
Generated by Claude Code
os-dev-report delta addendum (merge lap, PR #16008 — still open, still draft, base
main; no contract-review carrier touched)New head
641354355(wasaf3041c92):mainmerged at2e3576503viabash scripts/pm/os-regen-merge.sh— step 2 took no side of any generated artifact (no per-path notice), step 3 committed the merge before any regeneration, and noos-regen-pendingdeferral is outstanding.
regen: no — files: none. Afterpnpm install --frozen-lockfileand a rebuild of the spec closure (turbo run build --filter='@objectstack/spec...', VERDICT command-exit 0) the working tree is clean, so nothing needed regenerating:pnpm --filter @objectstack/spec check:generatedEXIT=0 (all 15 generated artifacts up to date) andpnpm check:merge-driverEXIT=0.mainmovedpackages/spec/srcin this window, so the suite was re-run rather than assumed:pnpm --filter @objectstack/spec test— 478 files, 12844 passed, 0 failed, VERDICT command-exit 0.
Generated by Claude Code
Landing provenance (domain:spec seat,
session_01M59rPZZFzqhfMUPFqqZTkf, 2026-09-05T22:16Z): PR #16008 flipped to ready and auto-merge (squash) enabled at 22:15Z on head641354355— every check on that head green (31 success / 3 skipped, none red) after themainmerge lap; ACCEPT5553931883ataf3041c92, the lap added one merge commit only. Governed predicate: 0 of 3 paths. The queue's enqueue reading and the landing note follow on MERGED (landing watch armed).
Generated by Claude Code
Release:
session_01M59rPZZFzqhfMUPFqqZTkf(domain:spec seat) — reason: landed; destination: closed by PR #16008 (Fixes #14993), no successor.Landing note (2026-09-05T22:48Z): PR #16008 MERGED at 2026-09-05T22:47:52Z as
1a7a7c954(squash; enqueued 2026-09-05T22:16:09Z, ~32 min in the queue behind other lanes' entries). Probe onorigin/main1a7a7c954:checksum's field-level describe present ("Coverage stops" ×1 inpackages/spec/src/system/environment-artifact.zod.ts; control 0 atf7db8f4fd), the sharedSha256DigestSchemadescribe unchanged,grantedPermissionscarries the boundary sentence group with ADR-0003 / cloud ADR-0007. Ruling5548641412option 1 executed as ruled; the flip to 2b stays a one-line reply on this card if the maintainer states the carrier is not trusted. Lane inventory: this card closes; nothing else moves.pm:dispatchedstripped, assignee cleared in this stroke.
Generated by Claude Code
- added a commit that references this issue
on Sep 9, 2026
Filed by the
domain:specseat (sessionsession_0174WZTU6XcFcS7g2kykC53i, seat post #6017) from the contract review of PR #14992 (#14865), where the dev raised it as an observation with no change proposed. Observation for triage — notpm:queue; whether the granted set needs integrity coverage is a decision, not a mechanical fix, and it touches the security boundary (human floor for any change).What is measured
packages/spec/src/system/environment-artifact.zod.ts(file header,origin/main44ffa210): the envelope'schecksum(Sha256DigestSchema, line 104) is documented as covering themetadatablock only — "Deployment Config … NOT in this schema"; the compiled metadata is what the digest identifies alongsidecommitId.grantedPermissionsonEnvironmentArtifactSchema— install-time granted set per plugin manifest id (#14865) #14992 declaresgrantedPermissions: z.record(z.string(), PluginPermissionsSchema).optional()as a top-level envelope key besidemetadata— exactly as ruled (manifest.permissionsis live on its LEGACYstring[]arm only — the structuredPluginPermissionsSchema(services / hooks / network / fs) has zero readers, and new code is told to prefer it #11333 option A, Phase 1 of #11333: wire granted_permissions into PluginPermissionEnforcer (F4) as the load-time gate #13457 batch) and as filed on spec: declaregrantedPermissionsonEnvironmentArtifactSchema(record of manifest id → PluginPermissions) — the artifact-contract half of #14034 / #11333 Phase 1, which must land BEFORE cloud can write it (plain z.object strips undeclared keys at the artifact door) #14865. The docblock states it plainly: "Sits besidemetadata, outside thechecksumdigest (which covers themetadatablock only)."{ services, hooks, network, fs }— the value the materialize-time loader hands toPluginPermissionEnforcer.registerGrantedPermissions— is control-plane state re-emitted per assembly and is NOT under the envelope's integrity digest. A carrier that altered that block would not fail the checksum.Why it is a card and not a rider
The ruling placed the granted set on the artifact contract; it did not specify integrity coverage, and the envelope's digest was designed around compiled metadata. Extending the digest (or adding a second digest over the consent block) changes what the control plane signs and what the runtime verifies — a contract change on a security boundary, which is the maintainer's decision. The neighbouring cards are different gaps: #11331 / #13563 are
manifest.integrityper-file digests of the.ospluginpackage, not the envelope's consent block.Questions for the decision (when triage grades it)
checksumto covermetadata+grantedPermissions(a breaking change to whatchecksummeans — every producer and verifier moves), or add a sibling digest for the consent block (additive, verifier opt-in).Dedupe:
search_issues"environment artifact checksum digest covers metadata only grantedPermissions consent state outside integrity" → nearest #11331 (manifest.integrity per-file digests) and #13563 (its cloud enforce leg); neither is this gap.