Repository navigation
Story 3.4:裁定并落地 sourceRef 的跨机器可移植性语义 - #177
Merged
Merged
Conversation
AD-22 退役第 (1) 步把「一并裁定 sourceRef 的跨机器可移植性语义」列为显式
开放项,并把「两个实现者各自发明」判为 critical:一方产出的修订在另一方
手里会按 AD-10 静默全量降级,且没有任何门能指出根因。本 Story 裁定该语义
并落地解析侧。
裁定(负责人 2026-08-25):sourceRef 只有一种合法形态——库内相对 POSIX
路径。绝对路径一律拒绝。根是本机配置,不进修订,机器差异只由根承担。
五条判定规则(validateSupplyRelativeRef,全仓唯一实现):非空 → 不含
反斜杠 → 不带盘符前缀 → 非绝对 → 解析后严格落在根内。盘符那条刻意排在
isAbsolute 之前,使判定与诊断都平台无关。
规则③④之所以必须存在,是实测出来的,不是防御性编程:
- known('') 与 known('.') 会让 path.resolve 返回根本身,cp --recursive
随后把整个供给库当作一个 Skill 拷入并报告成功——把 AD-10 的 fail-closed
翻成了 fail-open。
- 同盘符的 C:x/y 在 win32 被 path.resolve 折进根内而被接受,且此时收敛
判定依赖 process.cwd();C:/x/y 在 POSIX 上 isAbsolute 为假,被当普通
相对名接受——同一条修订两个平台含义不同。
根在 materializeClaudeContent 顶部快照一次后向下传,不在引用处重读:三组
是 Promise.all 并发,重读会让同一条修订劈成两个根。
cap-fs 的隐含根取 repoRoot 而非 capRoot(spec 的 Code Map 原写 capRoot,
是它写错了):lock.json 的 project_skill_imports[].source 为
plugins/grilling/skills/grilling,位于 capRoot 之外,相对 capRoot 会得到
../plugins/... 而被本合同拒绝。产出侧先校验后 join,不合法即降级为既有的
Unknown,不产出一个稍后必被拒的形态。
三轮审查(blind-hunter/edge-case-hunter/verification-gap 各两轮)促成
一次 intent_gap 回退与 10 条 patch。其中两条由真变异证明此前是空洞:把根
改回每引用重读 env、把失败信息缩成只有名字,两种变异下 502 全绿——AC3 与
「诊断必须抵达用户」这两条都没有测试兜住。现已分别用「同组两引用 +
queueMicrotask 翻转」和 CLI 边界断言钉住,并引入 SUPPLY_REF_REJECTION_MARKER
使反斜杠规则在 POSIX 腿上也真正被验证(此前那边靠 ENOENT 也能通过)。
真实库实测:20 个 skill 条目的 sourceRef 全为 Unknown、Known 零条,因此
「拒绝绝对路径」不作废任何真实数据。
bunx tsc --noEmit 干净;bun test 518 pass / 0 fail(基线 482),连跑两次。
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Epic 3 剩余「供应」半边的第一个 Story。关联 #173。
为什么先做这一条
AD-22 退役第 (1) 步把「必须一并裁定
sourceRef的跨机器可移植性语义」列为显式开放项,并把「两个实现者各自发明」判为 critical——一方产出的修订在另一方手里会按 AD-10 静默全量降级,且没有任何门能指出根因。这条 Story 只做裁定与解析侧;供给命令本体是 Story 3.5。裁定
sourceRef只有一种合法形态:库内相对 POSIX 路径。绝对路径一律拒绝。根是本机配置,不进修订,机器差异只由根承担。五条判定规则(
validateSupplyRelativeRef,全仓唯一实现,产出侧与解析侧共用):非空→不含反斜杠→不带盘符前缀→非绝对→解析后严格落在根内盘符那条刻意排在
isAbsolute之前,使判定与诊断都平台无关。规则③④是实测逼出来的,不是防御性编程
known('')/known('.')path.resolve返回根本身cp --recursive把整个供给库当作一个 Skill 拷入并报告启动成功——AD-10 的 fail-closed 被翻成 fail-openC:x/y(与根同盘)process.cwd()C:/x/yisAbsolute为假第一条由 verification-gap 层实测复现完整因果链(
failures: []、写出plugin.json、--plugin-dir照常交给 spawn 出去的进程)。其他关键决定
根按调用快照一次,在
materializeClaudeContent顶部解析后向下传。三组是Promise.all并发,在引用处重读会让同一条修订劈成两个根。cap-fs 的隐含根取
repoRoot而非capRoot——spec 的 Code Map 原写capRoot,是 Code Map 错了:lock.json的project_skill_imports[].source为plugins/grilling/skills/grilling,位于capRoot之外,相对capRoot会得到../plugins/...而被本合同拒绝。产出侧先校验后 join(先 join 会把/abs/x吞掉),不合法即降级为既有的Unknown。审查:三层 × 两轮,一次 intent_gap 回退
第一轮三层独立收敛到同一批问题,其中最要命的根因在 spec 冻结区——我写的「POSIX
/x/y原样返回」在 win32 上是事实错误(实测path.resolve('D:\lib','/x/y')→D:\x\y,盘符取自根)。那种错误改实现改不掉,按 workflow 判为 intent_gap,回滚代码、重新谈定合同后再推导。第二轮由真变异证明了两处「有测试但是空的」:
三者已分别用「同组两引用 +
queueMicrotask翻转」、CLI 边界断言、以及SUPPLY_REF_REJECTION_MARKER(任何ENOENT/EISDIR消息都不可能包含它)钉住。兼容性
真实库实测:20 个 skill 条目的
sourceRef全为Unknown、Known 零条,因此「拒绝绝对路径」不作废任何真实数据。验证
bunx tsc --noEmit零错误bun test518 pass / 0 fail(基线 482),连跑两次未做(已记入
deferred-work.md,6 条)符号链接可绕过纯词法收敛(注意本次是收窄:改动前任意绝对路径都放行);写路径
establish.ts不校验(冻结区 Never 明确排除,归 Story 3.5);claude-launch.ts在 optional/degraded 分支丢弃诊断;render.ts只显示裸相对路径、无根上下文;无规范形式;supply-root.ts的位置与 adapter 里的隐式 env 全局态。Story 3.5 已进
epics.md与sprint-status.yaml,generate --dry-run报in_sync: true、零 orphan。spec 内附 Suggested Review Order,入口是
packages/control-plane/src/cli/supply-root.ts:91。🤖 Generated with Claude Code