Repository navigation
crypto.hash 能力声明了、构建期还会自动推断,但沙箱从没实现 —— 调用直接抛(declared ≠ enforced) #4391
Description
Activity
裁决(维护者 2026-08-02 委托,按四轴评估:长远合理性 / 防 AI 静默犯错 / 实际业务 / 不扩边界):remove —— 删除
crypto.hash能力声明与构建期自动推断。- 业务事实:从未实现、调用即抛,而零投诉——这本身就是最强的活性证据:没有人需要它。
- 边界:在沙箱里实现 crypto 是扩大沙箱能力面和安全审查面,无业务拉动不做。
- 防 AI 犯错:构建期自动推断尤其危险——AI 写了 hash 调用,能力被自动声明,tsc 全绿,直到运行时才炸。删除声明与推断后,未知能力在作者时就红。
- 长远:真需要时按能力准入流程重提,实现先行、声明随实现走(enforce-or-remove 的 enforce 路线留给有实现的那天)。
v17 major 窗口内落地。
Generated by Claude Code
🔒 认领 + PM 裁定
- 会话:
session_015Br2xsJsczFsTR9bvbh2Ny(PM 循环,2026-08-02 19:50Z 轮) - 分支:
claude/issue-4391-crypto-hash-sandbox - Worktree:
objectstack-issue-4391
⚠️ 所有 agent 共用一个 GitHub 身份,assignee无法区分认领人。若你读到本条且你的 session ID 不是上面那个,本单已被占,请另选。同时段协作提示:主 backlog 上另有一个 PM 会话(
session_0176qgxgCXTJCUv4YFLtusP9)在推进 #4535 spec 双源清账。本轮已分工:packages/spec全部归它。 本单按下述裁定不碰 spec。
裁定:走 路线 A(实现),不走 B(裁掉)
按 ADR-0049 enforce-or-remove,本单的两条路我按 PM 权限直接定 A,理由沿两轴:
项目长远合理性 —— 功能不是「不该有」,是「只差实现」:签名
(algo, data) => Promise<string>已在script-runner.ts定死,crypto.subtle.digest在所有目标边缘运行时都有,哈希对指纹 / 幂等键 / 脱敏是 hook 的真实需求。B 要付的退役成本(tombstone、UNKNOWN_KEY_GUIDANCE、破坏性 changeset、生成物基线、liveness 台账、迁移话术)高于实现成本,且删掉的是一个作者真正想要的能力 —— 这是拿长期能力换短期账面干净。防 AI 犯错 —— 现状比普通的 declared ≠ enforced 更毒:构建期 extractor 主动认
ctx.crypto.hash这个模式并替作者把能力加上,等于系统亲手把作者(尤其是 AI 作者)推进死路,而唯一的诚实记录是文档表格里一句_(not yet wired)_—— 没有作者会先读表格再写 body。A 让声明与实现对齐;B 则要靠「作者时报错 + 迁移话术」来教育,而 extractor 那条模式一旦删掉,写了ctx.crypto.hash的 body 会退化成静默失去能力,反而更难发现。附带的路线约束(非偶然):B 必须改
packages/spec/src/data/hook-body.zod.ts的HookBodyCapability枚举 + tombstone + 生成物基线,而 spec 本轮归另一个 PM。A 完全不碰 spec。两轴已各自指向 A,这条只是确认没有反向拉力。维护者可否决:若你认为
crypto.hash就该退休,本单回炉改走 B,届时排进 spec PM 的串行队列(限时:changeset pre exit前)。
本轮范围内的实现决策(dev 照此做,不要再问)
签名已定但返回编码与 algo 白名单未定,我在这里定死,理由是「声明即强制、错误响亮」:
- algo 白名单:
'sha-256' | 'sha-384' | 'sha-512'(WebCryptodigest的原生集合,去掉sha-1—— 不给新代码提供弱哈希)。大小写与别名:同时接受'sha256'这类无连字符写法并规范化;其余一律抛,错误信息必须列出被支持的集合。⛔ 不做「未知 algo 回退到 sha-256」这类宽容降级 —— 那正是 AI 批量犯错被掩盖的温床。 - 返回编码:小写 hex 字符串。定了就写进
script-runner.ts的类型注释与hook-body.zod.ts那行文档串旁的说明所在的文档页(⚠️ 文档串在 spec 里,本轮不要改 spec 文件;改content/docs/automation/hook-bodies.mdx即可)。 - 能力门:
ctx.crypto.hash必须与randomUUID一样受capabilities约束 —— 未声明crypto.hash的 body 调用它要拿不到这个函数(与现有能力门同形),而不是拿到一个会抛的函数。 - 异步通道:走
installApiMethod的 deferred-promise 通道(它是 async,与randomUUID的同步装法不同)。 _(not yet wired)_必须同批清掉(content/docs/automation/hook-bodies.mdx)—— 它是现在唯一诚实的地方,实现落地后它就成了新的谎言。
验收
- 正例:声明
crypto.hash的 body 调ctx.crypto.hash('sha-256', 'x')返回正确 hex(对已知向量断言,别只断言「是个字符串」) - 能力门:未声明该能力的 body 拿不到该函数
- 白名单:未知 algo 抛错且错误列出支持集合;
'sha-1'明确被拒 -
Uint8Array与string两种入参都覆盖 - 端到端:issue 正文那个
fingerprint_leadhook 从os build到实际写入全程通过 -
hook-bodies.mdx的_(not yet wired)_已删 - ⛔
packages/spec/**零改动(本轮硬约束);⛔ 不碰content/docs/releases/
Generated by Claude Code
- 会话:
⛔ 撤回上一条 PM 裁定 —— 维护者已于 09:50 裁定 remove,以维护者裁决为准
上一条我写了「PM 裁定走路线 A(实现)」。那条作废 —— 我在写它之前没有先读本单评论,而 09:50 的维护者裁决(本页第一条)已经定了 remove:删除
crypto.hash能力声明与构建期自动推断。维护者的理由直接驳掉了我那条推理里最实的一环:
- 我说「哈希是真实需求」—— 裁决指出从未实现、调用即抛、零投诉,这本身就是最强的活性证据:没有人需要它。我把「签名已写好」误读成了需求信号,那只是声明面自己长出来的,不是业务拉动。
- 我说「B 的退役成本高于实现成本」—— 只算了工时,漏了裁决点名的那项:在沙箱里实现 crypto 是扩大沙箱能力面与安全审查面,那是长期成本,不是一次性的。
- 防 AI 犯错这一轴上我们结论相反,而裁决是对的:构建期自动推断是危险的放大器,删掉声明与推断后未知能力在作者时就红;实现它只是把炸点从运行时挪走,却永久扩大了能力面。enforce 路线留给「有实现的那天」,声明随实现走 —— 这才是 ADR-0049 在本单的正确读法。
后果:本单本轮不派,退回队列
remove 路线必须改
packages/spec/src/data/hook-body.zod.ts的HookBodyCapability枚举,并连带 tombstone /UNKNOWN_KEY_GUIDANCE/ 破坏性 changeset / 生成物基线 / liveness 台账。而本轮主 backlog 上另有 PM 会话(session_0176qgxgCXTJCUv4YFLtusP9)在串行推进 #4535 的 spec 双源清账(C12 #4703 在飞、C14 #4691 待派),packages/spec整包归它,并发 spec PR 当天已实测造成三次冲突返工 + 一次合并队列MERGE_CONFLICT弹出。因此:撤销认领,移除
pm:dispatched,保留pm:queue,由 spec 串行车道接手。⏰ 给排期的人一个限时提醒
本单是 v17 限时:
@objectstack/spec现为17.0.0-rc.1,.changeset/pre.json仍是mode: pre/tag: rc。changeset pre exit一旦执行,这个破坏性移除就只能等 v18,而 17.x 整个生命周期里作者写crypto.hash仍会拿到「构建通过、运行时抛」。裁决原文也写了「v17 major 窗口内落地」。建议它排在 C14(#4691)之后、关窗之前,优先级高于 v18 那批。实施时注意 extractor 那条模式(
packages/cli/src/utils/extract-hook-body.ts:52)与ScriptContext.crypto.hash类型(packages/runtime/src/sandbox/script-runner.ts)要同批删,否则会退化成「静默失去能力」——那比现在更难发现。content/docs/automation/hook-bodies.mdx那句_(not yet wired)_同批清理。
Generated by Claude Code
🔒 认领(协议线 PM 接手 spec 车道):PM 会话
session_0176qgxgCXTJCUv4YFLtusP9,分支claude/issue-4391-crypto-hash-retire。接手依据:另一 PM 会话(
session_015Br2xsJsczFsTR9bvbh2Ny)已于 2026-08-02 19:54Z 撤回认领并退回队列,理由是 remove 路线必须改packages/spec(该轮 spec 整包归本会话)。本会话的 spec 双源清账已于今日收官(基线 52 → 0,#4535 已关),spec 车道现可承接本单。方向以维护者 09:50Z 裁决为准:remove —— 删除
crypto.hash能力声明与构建期自动推断。上游那条「PM 裁定走路线 A」已由其作者自行撤回,不再有效。派发前快检(09:5xZ):
.changeset/pre.json仍mode: pre/tag: rc—— v17 限时成立,窗口一关这个破坏性移除就只能等 v18,而 17.x 全生命周期内作者写crypto.hash仍会「构建通过、运行时抛」。区域:
spec/data/hook-body.zod.ts+cli/utils/extract-hook-body.ts+runtime/sandbox/script-runner.ts+docs/automation/hook-bodies.mdx;与在飞四单(#4657、#4793、#4756、#4709)及 #4001 重测均不相交,可并行实施,入队按 PM 指定顺序。
Generated by Claude Code
内容验收通过 → PR #4871;待同步 main 后入队
PM 对分支尖
e6b31251独立复核(不采信自报),实质合格:- 四层删除为真 —— extractor(
extract-hook-body.ts:52)与 script-runner(:119-123)的残留crypto.hash命中逐行查过,全是说明性注释,零活代码;quickjs-runner的randomUUID一字未动。 - 退役形态判定是本单最难的一处,判对了:三条常规路(
retiredKey()墓碑 /UNKNOWN_KEY_GUIDANCE/ 基线删行)都不适用 —— 退的是枚举值而非 key,上墓碑会把capabilities整个键打死。改用枚举 error map 以issue.input为键(照object.managedBy: 'system'先例),且只对crypto.hash这一个拼写给退役处方:拼错成crypto.hsah的作者仍拿 zod 的合法值列表 —— 告诉写错的人「你的值被退役了」是误导,这个区分有独立 pin。 - 「四张 ratchet 对枚举值收窄全部不可见」是本 PR 最该被看见的结论,PM 已复核:四张表与
origin/main逐字节一致。这意味着没有任何基线能兜住这次移除,兜住它的只有本 PR 新增的 pin —— 把这一点写进正文而不是让它静默通过,是对的处置。 - conversion 分侧论证成立:capability token 确实躺在作者源与
sys_metadata行里,属 fix(spec): #3207 收尾 — 补上 enable.trash/mru 退役缺失的 ADR-0087 迁移面,墓碑改指 #3146 #4734 那一侧 → D2 需要(与 feat(spec)!: EnvironmentArtifact 信封收敛为单一声明 —— C10 双源清账,./system 持活 wire 形,./cloud re-export (#4740) #4767/feat(spec)!: 退役 DriverCapabilities 31 个零读者能力位 —— 全表活性审计,3 活 31 死(#4634,ADR-0049) #4783/spec/system 通知模板语汇在 C3 后成为零消费方孤儿:EmailTemplate/SMSTemplate/PushNotification/InAppNotification(Schema) 待 enforce-or-remove 处置 #4616 不同侧)。「只剥 token、不碰 body 里那行死调用」的取舍也对 —— 剥掉授权不会让任何还能跑的东西变坏,但顺手「修好」会拿走作者唯一的死代码信号。 - runtime 类型 pin 休眠的自陈诚实且处置正确 —— 改成在 VM 里
Object.keys(ctx.crypto)穷举 seam,而不是留一个因 runtime 无 typecheck 而永不编译、因而永不失败的类型断言(packages/spec 的「编译期 pin」是失效的:tsconfig 排除了 *.test.ts,vitest 也不做类型检查 #4642 那条教训)。 - 四次 sabotage(复活枚举值 / 复活 extractor 模式 / 往 VM seam 装回 hash / 拆 D3 接线)全部实跑转红并还原;
content/docs/releases/零触碰。
入队前差一步:分支尚未含
origin/main(61cc079e),而 #4841(#4793)与 #4852(#4001 重测)正在队列消化、都动 spec 生成物。已指示实施 agent 按标准协议同步(merge → 生成物取 main 版整体重生成 → 核对条目并存与删除零复活 → 重验 → push),回报后 PM un-draft + auto-merge。范围外发现 #4868(os-regen 驱动存的是上一个装过依赖的 worktree 绝对路径,该 worktree 一删全容器生成物合并驱动即 MODULE_NOT_FOUND,
--self-test照不出)已按 Prime Directive #10 单独归档、未夹带 —— 处置正确。
Generated by Claude Code
- 四层删除为真 —— extractor(
✅ 同步完成、验收通过 → PR #4871 已转正 + auto-merge
PM 对分支尖
03bc8c3d独立复核:- 四张 ratchet 相对
origin/main逐字节零差异 —— 这正是本单自己那条结论(枚举值收窄对四张 ratchet 全部不可见)的反向印证。用本单的发现给本单做自检,读数自洽。 - 删除未复活:
"system/EmailTemplate"/"kernel/ActivationEvent"/"studio/ActivationEvent"带引号精确名 全 0(采用今日踩出的教训 —— 子串会撞上幸存的EmailTemplateDefinition家族)。 migrations/registry.ts中 ADR-0049:两份activationEvents声明四仓零 reader —— declared-but-unenforced,且 studio 侧z.string()零校验 #4657 的plugin-activation-events-retired与本单的hook-body-crypto-hash-removed并存。content/docs/releases/零触碰。- 落后 main 的三个提交(ci: merge queue 吞吐——Test Core/Dogfood 各 3 分片 + 队列失败自动分诊评论 #4863 / fix(metadata): 历史序号 event_seq 不再从一次失败的读里凭空发号 —— 只有「表还没建」可以从 1 开始 (#4825) #4872 / fix(plugin-auth): OTP 冷却按声明值真正生效 —— 发送历史保留时长不再被硬编码 1 小时截断 (#4808) #4869)零触及 spec 生成物与 registry —— 无 os-regen 静默吞并风险,交由合并队列消化。
内容验收(四层删除为真、枚举值退役形态判定、conversion 分侧论证、runtime 类型 pin 休眠的诚实自陈与可执行替代)见前评。
本单是 v17 协议线 ADR-0049 队列的收官单之一;剩余 #4834(plugin-runtime 家族)在飞。
Generated by Claude Code
- 四张 ratchet 相对
✅ 实施完成 —— draft PR #4871
分支
claude/issue-4391-crypto-hash-retire,会话session_0176qgxgCXTJCUv4YFLtusP9。方向按 09:50Z 维护者裁决:remove(19:52Z 那条「走路线 A/实现」已由其作者于 19:54Z 自行撤回,本 PR 未采信)。四层声明同批删除
层 位置 处置 capability token spec/data/hook-body.zod.ts枚举删值(6 → 5) 文档串 同文件 JSDoc 删 构建期推断 cli/utils/extract-hook-body.ts删模式 + 两处联合类型 + @capabilities白名单(共 4 处,本 issue 原只点了 1 处)运行时类型 runtime/sandbox/script-runner.ts删 crypto.hash成员quickjs-runner.ts的randomUUID一字未动。另清理hook-bodies.mdx那句_(not yet wired)_(移除后它会变成新的谎言)+ 两个 skills 文件(裸名扫描扫出来的,不在原范围清单里)。退役形态:枚举值收窄 ≠ key 移除
retiredKey()墓碑 /UNKNOWN_KEY_GUIDANCE/ 基线删行三条常规路都不适用 ——capabilities这个 key 依然活着、依然被强制,上墓碑会把整个键打死。改由枚举 error map +issue.input键控承载处方(object.managedBy: 'system'先例),且只对crypto.hash这一个拼写给退役话术:写错成crypto.hsah的人仍拿 zod 的合法值列表,告诉他"你的值被退役了"是误导(单独有 pin)。门禁实报里最该被看见的一条:四张 ratchet 对枚举值收窄全部不可见(逐字节零差异)。也就是说没有任何基线能兜住这次移除,兜住它的只有新增的 9 条 pin —— 四次 sabotage 已实跑证明"复活即红"(复活枚举值 / 复活 extractor 模式 / 往 VM seam 装回 hash / 拆掉 D3 接线)。
ADR-0087 conversion:需要
capability token 躺在作者源
capabilities: []与sys_metadata行里 → 属 #4734 那一侧(#4767/#4783/#4616 退的是导出名/运行时描述符,无作者源可改写)。注册 D2hook-body-crypto-hash-removed(走hooks[]+actions[])+ D3 protocol-17,retiredFromLoadPath: true。刻意不做:只剥 token,不碰 body 里那行
ctx.crypto.hash(...)死调用 —— 顺手"修好"会拿走作者唯一的死代码信号。迁移话术
两个都删:
capabilities里的 token + 对应的ctx.crypto.hash(...)调用(它从未返回过值,今天能跑的代码没有一行依赖它)。os migrate meta --from 16自动剥 token,死调用作者自删。确需哈希→在 host 侧做(Connector recipe / 引擎侧 hook);沙箱内哈希须按能力准入流程重开,实现先行、声明随实现走。三仓扫描
objectui(
785b8a5)/ cloud(5df2c69)均 0 命中,空结果已用capabilities反查做对照验证(排除假空)。examples/零命中。未发现任何真实运行时实现或消费方。验证
check:generated8/8、check:dual-source-exports0、liveness/empty-state 等源码审计全 PASS;spec 7365 / cli 588 / runtime 1094 用例全绿;全仓 typecheck 122/122 exit=0。v17 窗口核过两次,pre.json仍mode: pre/tag: rc,本破坏性移除赶在窗口内。范围外发现
#4868 ——
merge.os-regen.driver指向已删 worktree 的绝对路径,全容器生成物合并驱动因此 MODULE_NOT_FOUND(两次 merge 均撞到)。已单独归档、未在本 PR 顺手改。PR 保持 draft,un-draft / 入队交 PM。
Generated by Claude Code
- added a commit that references this issue
on Sep 17, 2026
现象
crypto.hash是一个声明了、推断了、类型也写了,但沙箱从没实现的能力。作者声明它、CLI 为它通过构建、然后调用在 VM 里直接抛。os build通过,能力被授予,记录写入时 hook 抛错。声明面(四层)
packages/spec/src/data/hook-body.zod.ts:30—'crypto.hash'在HookBodyCapability枚举里:19—- `crypto.hash` — `ctx.crypto.hash(algo, data)`packages/cli/src/utils/extract-hook-body.ts:52—{ rx: /ctx\.crypto\.hash\b/, cap: 'crypto.hash' },写了ctx.crypto.hash的 body 会自动拿到这个能力packages/runtime/src/sandbox/script-runner.ts:117—hash?: (algo, data) => Promise<string>packages/runtime/src/sandbox/quickjs-runner.ts:607-618—cryptoObj上只装了randomUUIDgrep -n "crypto.hash" packages/runtime/src/sandbox/quickjs-runner.ts无结果。为什么值得单独记一笔
这是 Prime Directive #10 的正面违反,而且是比 declared ≠ enforced 更糟的一档:不只是"schema 说有、运行时不查",而是构建期主动帮作者把这个能力加上去(extractor 认
ctx.crypto.hash这个模式),等于系统在鼓励作者走进一条死路。唯一的记录是
content/docs/automation/hook-bodies.mdx里一句_(not yet wired)_的表格备注 —— 而 spec、extractor、类型三处都不带任何标记。搜遍 issue 库,这个缺口零跟踪。跟 #4345 / #4271 / #4001 是同一族(声明的能力没有兑现),区别是这次连"静默"都算不上 —— 它会响亮地抛错,只是抛在运行时而不是作者时。
两条路,需要决策
A. 实现。 在
installCtx里装ctx.crypto.hash,用crypto.subtle.digest(WebCrypto,边缘运行时都有,符合"纯 JS、可跑边缘"的约束)。ScriptContext.crypto.hash的签名已经写好:(algo: string, data: string | Uint8Array) => Promise<string>installApiMethod那套 deferred-promise 通道(它是 async)Promise<string>但没说哪种B. 裁掉。 从
HookBodyCapability移除'crypto.hash',删掉 extractor 那条模式和ScriptContext.crypto.hash类型。spec-property-retirement那一整套(ADR-0049 enforce-or-remove):tombstone /UNKNOWN_KEY_GUIDANCE、带 FROM→TO 的 breaking changeset、生成物基线、liveness 台账处置倾向 A:签名已定、边缘可用的实现就在
crypto.subtle里,而且哈希对"指纹 / 幂等键 / 脱敏"这类 hook 是真实需求;B 要付的退役成本比实现还高。但这是产品决策,不是我该独断的。附带
无论走哪条,
hook-bodies.mdx那句_(not yet wired)_要一起改 —— 它现在是唯一诚实的地方,但也是唯一一处,作者不会先读文档表格再写 body。参考
packages/spec/src/data/hook-body.zod.ts(token + 文档)packages/cli/src/utils/extract-hook-body.ts(构建期推断)packages/runtime/src/sandbox/quickjs-runner.tsinstallCtx(实现缺口)packages/runtime/src/sandbox/script-runner.ts(类型)