Skip to content

docs: accept tool identity baseline - #740

Merged
jinjunnn merged 7 commits into
alphafrom
design/731-tool-identity-baseline
Aug 8, 2026
Merged

jinjunnn merged 7 commits into
alphafrom
design/731-tool-identity-baseline

Conversation

@jinjunnn

@jinjunnn jinjunnn commented Jul 31, 2026 •

Copy link
Copy Markdown
Owner

Tracks #731

Refs #722 · Refs #724 · Refs #726

DECIDE 票的产出:一份 commit-pinned 的方案基线,现为 status: accepted;owner 已批准三项裁决。
零生产代码改动。落点 docs/design/2026-07-31-tool-identity-baseline.md,并加进
docs/design/README.md 的家族索引。

这份文件在解决什么(大白话)

今天一个工具在系统内部没有名字,只有"模型看到的那个字符串"。内置工具、MCP 工具、
插件工具共用同一个字符串空间,而 MCP 那条的拼接(sanitize(server) + "_" + sanitize(name))不可逆 —— 拿到 cloud_web_search 无法确定它是 cloud 的
web_search 还是 cloud_web 的 search。后果:工具卡不知道自己在展示谁;权限规则
可能打到另一个工具身上;"总是允许"存下来的那条规则会被当成通配模式使用。

本文给出:由来源事实派生、可逆反解、跨全部执行路径一致的 identity,让 REQ-125 展示票
(#722)与 REQ-131 策略票(#724)消费同一份定义,而不是各自再定义一次。

勘破对票面事实的三处修正(都带 file:line,实读 alpha@7799a5e6)

  1. 注册来源实为 6 个,不是票面的 4 个。"插件动态"是两个机制不同的来源
    (目录扫描 tool/registry.ts:178-192 有命名空间;Plugin 钩子 :194-199
    导出名原样、零命名空间),另有三个 MCP-resource 伪工具与 V2 registry。
  2. 执行咽喉实测 7 处,票面列了 5 处。漏掉的两处都在 session/prompt.ts,
    都不经 SessionTools.resolve:
    • E6 prompt.ts:255-345 — subtask 直呼 task 工具,走 registry.named()
      绕过 registry.tools() 的可见性过滤,并写出一条用户可见的 ToolPart;
    • E7 prompt.ts:808-828 — 附件摄取直呼 read,ask: () => Effect.void
      把权限整个短路,且 bypassCwdCheck: true。
      另有第 8 个把别名当权限键的地方:session/llm.ts:150-153 的 workflow 预批名单。
      用「plugin.trigger("tool.execute.before") 符号轴」+「直接 execute( 调用轴」两条
      互相独立的检索交叉枚举才查全,单跑一条正则会漏。
  3. Wildcard 的文法会吃掉工具名里的字符(core/util/wildcard.ts:3-14):pattern 侧
    *→.*、?→.、\→/,Windows 上还是大小写不敏感("si")。而"总是允许"
    存下来的 permission 字段(permission/index.ts:228-232)就落在 pattern 轴。
    ⇒ canonical 编码必须转义 % : * ? \ / 这六个字符,否则等于把一个远端
    服务器可控的通配符送进权限模式轴。

决定

  • identity = 结构化三元组 {source, origin, name},只在元组仍完整的铸造点产生
    (mcp/index.ts:674-685 等 6 处),此后只传递。
  • canonical 字符串 = 固定三段 + 六字符百分号转义,单射可逆;反解按未转义 : 切成
    恰好 3 段,段数不对就拒绝,不猜。
  • 别名↔身份的双射由装载期账本校验保证([REQ-131][CODE] MCP 工具别名碰撞时禁止静默覆盖 #726 由"补丁"升格为不变量 I1),
    任何地方不得从别名反推来源 —— 现有的 code-mode.ts:39-56 反解由本方案删除。
  • 不新建三态 / wildcard / 合并优先级 / 目录过滤钩子,只改"键是什么"。作用域
    ("mcp:github:*": "deny")由既有 wildcard 引擎免费得到,零新代码。
  • 内置工具的硬编码能力串保留为独立一轴,与身份轴取"与"关系。理由三条(能力串里
    有三个根本没有对应工具的审批点;能力轴对未来新增的写工具默认拒绝而身份轴默认放行;
    edit|write|apply_patch 的合并是刻意的产品语义),已知限制 L1–L3 逐条列明。

表面边界:实测,不是推断

在本分支放四个探针跑 bash scripts/alpha-check.sh(worktree 已用
scripts/worktree-link-deps.sh 装真依赖,不是软链假红环境):

探针 守卫结果
修改 packages/opencode/src/mcp/catalog.ts 红,逐名列出
修改 packages/plugin/src/tool.ts 绿(不在 UPSTREAM_PATHS)
修改 packages/ext/src/register.ts 绿(alpha 自有包)
新增 packages/opencode/src/alpha-probe-new-file.ts(已 git add) 绿

第四条是关键:守卫谓词是 --diff-filter=DMR(不含 A),alpha-check.sh:66-68 的
ADR-035 注释与 5 个既有先例证实这是既定设计。⇒ 机制部分(类型/编码/账本/校验/测试)
可落 L0 新增文件;接线部分(6 个铸造点 + 7 处咽喉 + ToolPart schema)必须走一条
L3 收编 ADR
,并且已逐条给出「L0 插件钩子 / L0 新增文件 / L1 变换层 / HTTP 面」
四条低级别路径为何不可行的勘探证据(ADR-029 §3 要求)。该 ADR 是实现票的硬前置。

判据(identity 怎么被测试守住)

文档 §7 列了 10 条不变量,每条都写明反向判据:碰撞(I1)、编码单射可逆(I2)、
转义集覆盖 wildcard 文法(I3)、大小写(I4)、7 条咽喉解析到同一对象(I5)、
E7 边界显式(I6)、rename 不继承旧授权(I7)、动态增删(I8)、插件遮蔽内置不静默(I9)、
仓内不存在别名反解(I10)。I1–I4、I10 可落 L0 新增测试文件、无需 ADR。

跑过的门与结果

在 worktree .worktrees/design-731(scripts/worktree-link-deps.sh 已装真依赖):

  • bash scripts/alpha-check.sh — 全绿(连跑两次结果一致):
    • [1/3] north-star guard ✓ zero upstream package edits
    • [2/3] typecheck ✓(contracts-consumer + ext + ui-mac)
    • [3/3] contract lock + unit tests ✓ — 36 pass / 0 fail、132 pass / 0 fail、
      3315 pass / 0 fail(14668 expect)
  • python3 scripts/check-doc-links.py <两个改动的 md> — ✓ 3 relative link(s) resolve across 2 file(s)(docs-only PR 在 CI 里正是走这道门)
  • check_docs_contract.py --profile alpha --strict — alpha-code 侧唯一 ERROR 是既有的
    docs/design/2026-07-21-req053-bootstrap-loop-hardening.md 断链(P1),与本 PR 无关;
    另两条 ERROR 在 alpha-web,同样是 base fail-set。

与 base fail-set 的差:0。 本 PR 未引入任何新红。

为什么用了 --no-verify(必须记录)

pre-push 钩子恒红在 check:vendor 的跨仓 provenance 一步,与本 PR 无关,已定位到
根因并复现:

packages/alpha-contracts-consumer/scripts/vendor-alpha-contracts.ts:170
  Bun.spawnSync(["git", "-C", sourceRoot, "rev-parse", `${upstream.commit}^{commit}`])

git 钩子会在环境里设 GIT_DIR,而这次 spawn 没有清掉它 ⇒ git -C /Users/tide/app/alpha-web
实际解析到的是 alpha-code 的 git dir,那个 alpha-web commit 当然不存在。实测对照:

$ git -C /Users/tide/app/alpha-web rev-parse b71748103…^{commit}
b71748103ce65f97e3e5c8ac03f08152a0a1456f            # 独立跑:通过
$ GIT_DIR=$(git rev-parse --absolute-git-dir) git -C /Users/tide/app/alpha-web rev-parse b71748103…^{commit}
fatal: ambiguous argument …                          # 钩子环境:必红

⇒ 同一个检查独立跑必绿、经钩子跑必红。上面列的三道门全部是我在钩子外亲自跑的真实
输出。这条缺陷超出本票边界,未顺手修,单独回报给主 session 决定是否开 Issue。

文档影响

  • 新增:docs/design/2026-07-31-tool-identity-baseline.md(kind: design,status: accepted)
  • 索引:docs/design/README.md 家族表新增一行 + last_reviewed 顺带更新
  • ADR:本 PR 不新增 ADR;§6.3 只是提出需要一条收编 ADR(建议 ADR-040)并给出
    其必须载明的接管面清单,owner 已批准该收编方向;ADR 落地仍是接线实现的硬前置。

Owner 裁决已完成(2026-08-06)

  1. 批准 §6.3 的 L3 收编 ADR;ADR 落地仍是接线实现硬前置。
  2. E5 workflow executor 纳入本轮,必须改用 canonical identity。
  3. I9 纳入本轮且不拆票;插件遮蔽内置工具必须 loud 失败。

文档已转为 status: accepted,不再保留 owner 开放点。

在 alpha@7799a5e6 上实读勘破,落一份 commit-pinned 的 identity 方案基线
(status: draft,待 owner 批准),供 #722(展示)与 #724(策略)共同消费。

勘破对票面事实的三处修正:
- 注册来源实为 6 个(插件是两个机制不同的来源;三个 MCP-resource 伪工具是
  引擎自造的第四类),不是 4 个;
- 执行咽喉实测 7 处,票面列了 5 处 —— 漏掉 session/prompt.ts 的 subtask 直呼
  (E6,写真实 ToolPart)与附件摄取直呼 read(E7,ask 被短路为 no-op);
- Wildcard 的文法会吃掉工具名里的 * ? \ /,而"总是允许"存下来的 permission
  字段就落在 pattern 轴 —— canonical 编码必须转义这六个字符,否则等于把远端
  可控的通配符送进权限模式轴。

决定:identity = 结构化三元组(source/origin/name),只在元组仍完整的铸造点
产生;canonical 用固定三段 + 六字符百分号转义,单射可逆;别名↔身份的双射由
装载期账本校验保证(#726 升格为不变量),任何地方不得从别名反推来源。
权限引擎/wildcard/合并优先级/目录过滤钩子一律不新建,只改"键是什么"。
内置能力串保留为独立一轴,与身份轴取"与"关系,理由与已知限制在 §5 逐条列明。

表面边界经 scripts/alpha-check.sh 四探针实测(非推断):机制部分可落 L0 新增
文件;接线部分(6 个铸造点 + 7 处咽喉 + ToolPart schema)必须走一条 L3 收编
ADR,该 ADR 是实现票的硬前置。

Refs #722
Refs #724
Refs #726
Fixes #731

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

jinjunnn commented Aug 4, 2026

Copy link
Copy Markdown
Owner Author

Codex 按当前 origin/alpha@10c08ccf 审计:合并试演无冲突,check-doc-links 3/3、git diff --check 通过;但文档末尾真实混入了两行工具转录垃圾:</content> 与 </invoke>,当前不能批准。另文档自身仍留 3 个 owner 开放点,不能把 draft 当作已裁决。最小下一步:只删除这两行垃圾;保持 draft,不扩设计。等 owner 单独裁“是否收编 ADR / E5 是否纳入 / I9 是否拆票”后再改 accepted。

docs/design/2026-07-31-tool-identity-baseline.md 末尾混入了两行
不属于文档内容的残片(</content> / </invoke>),纯删除,无其他改动。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017nwznzUDX5onDqymbU5FFz
@jinjunnn
jinjunnn marked this pull request as draft August 6, 2026 04:03
@jinjunnn jinjunnn changed the title [#731][DECIDE] 全来源工具 inventory 与稳定 identity 方案基线 docs: accept tool identity baseline Aug 6, 2026
@jinjunnn

jinjunnn commented Aug 6, 2026

Copy link
Copy Markdown
Owner Author

Codex second-pass at exact head 7f88ec36e446b22601818d135a7bcb65918d08a1: no blocking finding.

The three owner decisions are implemented in the accepted baseline, not merely appended:

  1. §6.3 makes the L3 adoption ADR a hard prerequisite for all wiring changes and enumerates the exact upstream surfaces.
  2. E5 (workflow executor) is included in the permission-key transition and in the shared rebind invariant.
  3. I9 is in the same delivery scope, with a reverse test that forces plugin shadowing of builtin bash to fail loudly.

Additional checks:

Per DECIDE closure semantics, #731 may close after this PR merges. The accepted ADR prerequisite remains open work and is not falsely claimed complete here.

@jinjunnn
jinjunnn marked this pull request as ready for review August 8, 2026 07:21
@jinjunnn
jinjunnn merged commit 487b0b4 into alpha Aug 8, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants