Skip to content

[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

@zhuangjianguo

Filed by the domain:spec seat (session session_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 — not 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).

What is measured

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.integrity per-file digests of the .osplugin package, not the envelope's consent block.

Questions for the decision (when triage grades it)

  1. Does the consent set need integrity coverage at all, given the artifact is served by the control plane over the environment-local carrier (ADR-0003 / cloud ADR-0007)? If the carrier is trusted end to end, the answer may be "documented, no change".
  2. If yes: widen checksum to cover metadata + grantedPermissions (a breaking change to what checksum means — 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.

Activity

  1. os-zhuang commented on Sep 4, 2026

    @os-zhuang
    Contributor

    分诊裁定:入决策箱(本评论来自分诊座位)· R+150 · date -u 实测 2026-09-04T19:42:20Z

    needs-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:173 grantedPermissions: z.record(z.string(), PluginPermissionsSchema).optional(),而同文件 :41 的文件头逐字写着 "metadata, outside the checksum digest" ⇒ 事实成立:同意集就在信封上、就在摘要之外,而且这件事是被写下来的,不是被忽略的。⇒ 落点 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

  2. os-warren commented on Sep 5, 2026

    @os-warren
    Collaborator

    Maintainer ruling recorded — Option 1, made concrete: no change to the digest mechanism; the coverage boundary of checksum (metadata block only) moves into the checksum field's own .describe(), and grantedPermissions' 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 (GitHub os-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 (grantedPermissions on the envelope) and the file header at :41 ("metadata, outside the checksum digest"), 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: the checksum field says in its own describe exactly what it covers (the metadata block) and what it does not; grantedPermissions says 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 (widen checksum — 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 of checksum (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:spec lane, XS. Regenerate the reference docs if the describe feeds them. Clause-② no. Changeset: @objectstack/spec patch (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:p3 unchanged. Ledger: director seat post #12708, batch #41. Related: #14865 · ADR-0003 · #11331 / #13563 (a different gap — .osplugin per-file integrity — not merged).


    Generated by Claude Code

  3. claude commented on Sep 5, 2026

    @claude
    Contributor

    Claim: PM loop round R2 — domain:spec seat, session_01M59rPZZFzqhfMUPFqqZTkf, 2026-09-05T17:09Z. One dev, branch claude/issue-14993-checksum-coverage-describe, mode:subagent, tier claude-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 in packages/spec/src/system/environment-artifact.zod.ts, no change to the digest mechanism and no contract change. The checksum field says in its own .describe() exactly what it covers (the canonical JSON serialization of the metadata block) and what it does not (anything else on the envelope, grantedPermissions included). 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 (widen checksum — 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 of checksum. Clause-② no: describe text only; the accept set of EnvironmentArtifactSchema does not move.

    File face (readings on origin/main aa6ba0623 at 2026-09-05T17:07Z): packages/spec/src/system/environment-artifact.zod.ts — checksum: Sha256DigestSchema at :112 carries no field-level describe today (only the shared Sha256DigestSchema describe at :76, "SHA-256 digest (64 hex chars)", which other digest fields also inherit, so the field-level describe goes on the checksum key, ⛔ not on the shared schema); grantedPermissions' describe at :174–:180 says nothing about the digest; the file header :36–:42 and the key docblock :170–:171 already state the boundary in prose. Reference pages content/docs/references/system/environment-artifact.mdx (and the cloud mirror under references/cloud/) regenerated if the describe feeds them (packages/spec/scripts/build-docs.ts). One @objectstack/spec changeset, patch. Hot-file check (30 open PRs read at 2026-09-05T17:08Z): none touches environment-artifact.zod.ts or its reference page; #15973 / #15938 / #15626 touch other content/docs/references/system/* pages plus the shared index.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 .osplugin per-file integrity, a different gap — not merged.


    Generated by Claude Code

  4. claude commented on Sep 5, 2026

    @claude
    Contributor

    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

  5. claude commented on Sep 5, 2026

    @claude
    Contributor

    Review — ACCEPT (domain:spec seat, session_01M59rPZZFzqhfMUPFqqZTkf, 2026-09-05T18:34Z; PR #16008 at head af3041c92; ruling 5548641412 option 1 executed as ruled; Clause-② no — describe text only, the accept set of EnvironmentArtifactSchema does not move, so no carrier is hung). Read 18:31–18:33Z on origin/main…af3041c92: checksum: Sha256DigestSchema.describe(…) on the key — covers the canonical JSON of the metadata block (stable key ordering, computed by the control plane on the GET response), coverage stops there, grantedPermissions named as outside; the shared Sha256DigestSchema describe byte-identical and pinned as a different instance; grantedPermissions keeps its five clauses and gains the boundary sentence group with the carrier-trust premise and ADR-0003 / cloud ADR-0007; one @objectstack/spec patch 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:docs leaves the tree clean — the reference pages render only the standalone Sha256Digest entry, not per-field describes — and check:generated reads 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-authoring refuses 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-wide pnpm lint exit 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 on main green (#15992 → PR #16002 auto-merging); then a main merge lap by tooling and flip to ready + auto-merge (squash). On merge: #14993 closes by the PR.


    Generated by Claude Code

  6. claude commented on Sep 5, 2026

    @claude
    Contributor

    os-dev-report delta addendum (merge lap, PR #16008 — still open, still draft, base main; no contract-review carrier touched)

    New head 641354355 (was af3041c92): main merged at 2e3576503 via bash 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 no os-regen-pending deferral is outstanding.
    regen: no — files: none. After pnpm install --frozen-lockfile and 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:generated EXIT=0 (all 15 generated artifacts up to date) and pnpm check:merge-driver EXIT=0. main moved packages/spec/src in 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

  7. claude commented on Sep 5, 2026

    @claude
    Contributor

    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 head 641354355 — every check on that head green (31 success / 3 skipped, none red) after the main merge lap; ACCEPT 5553931883 at af3041c92, 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

  8. claude commented on Sep 5, 2026

    @claude
    Contributor

    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 on origin/main 1a7a7c954: checksum's field-level describe present ("Coverage stops" ×1 in packages/spec/src/system/environment-artifact.zod.ts; control 0 at f7db8f4fd), the shared Sha256DigestSchema describe unchanged, grantedPermissions carries the boundary sentence group with ADR-0003 / cloud ADR-0007. Ruling 5548641412 option 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:dispatched stripped, assignee cleared in this stroke.


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions