Repository navigation
fix(leases): canonicalize equivalent write scopes - #7204
Conversation
Prevent lexical path aliases from bypassing same-repository task-lease conflicts. Preserve retained lease bytes while using a conservative comparison view for legacy malformed scopes. Signed-off-by: duang777 <duangjl007@gmail.com>
loopx-agent
left a comment
There was a problem hiding this comment.
动机
REQUEST_CHANGES:当前精确提交 a7365711bc3be3dda22a92988a6bb57fff1da9dc 有三处可复现回归,详见后面的 F1–F3。原别名漏洞确实修复,以下问题不能由这个正例或常规测试通过抵消。
通过 CLI 为仓库任务领取独占写租约、升级后恢复原执行,或从 Todo 声明直接领取工作的调用者。 例如两个任务分别写 src//main.ts 与 src/./main.ts,原实现会发出两笔租约;本提交能拦住这对别名,却同时让旧请求重放和正常 Todo 领取失败,并新增一类 glob 漏拦。 独立真实入口既验证了预期冲突修复,也验证了三个新回归,因此当前提交仍需修改后再验收。 本评审不要求重做整个路径权限系统、引入新 scope 协议或完成父级迁移;不授予合并、安装、跨 Goal 锁或实际文件访问权限。
作者写“无现成规格”,但仓库已有已接受的租约契约:spec_ref docs/reference/canonical-lease-renew.md,spec_revision 09e47b0e2ed990681b007c031efae385b73df1a3(固定版本)。本次使用原文真实节名作为 criterion_id:Repository-relative scope identity 规定普通同仓库重叠排他,Commit, retry and readback 规定原请求身份/回执及当前执行证明,Revising declared work requirements 区分实际声明修订与不变工作。它们分别受 F3、F1、F2 影响,未引入新的 roadmap 门槛。
改动思路
TS 现有租约 owner 是正确放置点,但共享 normalizeWriteScopes 同时改变了比较值、历史请求 digest 和 Todo 数量校验;这三个消费边界不能被当成同一种规范化。新增字符类扫描还与同文件 classMatch 的语法分叉。 当前 PR 应保留同仓库别名冲突修复,并在同一 owner 内修复旧回执重放、add→claim 声明一致性和 glob 字符类边界;不需要新协议、存储迁移或另建权限层。
保留现状会继续让相同文件的别名逃过排他检查,另加一套权限系统或改写历史也没有必要。需要收敛的是派生比较、不可变身份和声明有效性各自的职责。比较时规范化不代表可以改已有请求的 hash;输入数组规范化去重也不等于历史声明损坏;解析路径分隔符必须先正确识别完整 glob 字符类。这个修复仍可在原有几个 owner 内完成。
下面是源码导出的比较表,用于说明消费边界,并非执行证据;未另作 Mermaid 渲染验收。
| 输入/消费边界 | base | 当前 head |
|---|---|---|
| 普通相对路径别名冲突 | 错误发出第二笔租约 | 正确拒绝第二笔 |
| base 已提交的原参数重试 | 返回原回执与有效执行 | 错报请求身份不同 |
| 普通 Todo 的两种等价 scope 声明 | 可直接领取 | add 成功,claim 报声明损坏 |
具有字面量 ] 的字符类 |
保留原匹配集并拒绝冲突 | 改变匹配集并允许冲突 |
具体改动
关键代码讲解
canonicalWriteScope 去掉路径中的空段和独立 .,保留末尾目录斜杠;comparisonWriteScopes 为保留记录构造比较视图,无法解析的历史 scope 以 ** 保守比较,原记录不回写。现有同仓库/生效 holder/worktree 分支仍由原决策器负责。这个放置本身合理,字符类的结束位置却与下方已有 classMatch 不一致。
normalizeWriteScopes 改为规范化后再去重,requireWriteScopes 增加新输入严格拒绝。它既被独立 acquire 使用,也被原 coordinationTodoWriteScopes 消费。后者继续用数组长度变化判断损坏,导致正确的词法归并被误当成无效声明。独立 acquire 又在建立不可变请求身份前先规范化,触发不同的持久兼容性问题。
F1 [P1] 保留旧 acquire 请求的原始回执身份 — 入口 L36。File/SQLite:base 用 src//main.ts 首次 acquire,再按同一 key/expected-version=0/TTL/scope 重放成功;同一物理 provider 不变交给 head,完全原参数却 exit1/coordination_operation_identity_mismatch,原租约仍 active。 这里新调用先把 scope 改为 src/main.ts,随后未改的 request_sha256 计算自然与旧回执不同。不是调用者改了意图,也不是执行已退休;inspect 仍返回完全原样的 active lease。需要把比较/新记录规范化与既有请求身份分开,保证旧生产者→新消费者的原参数重试仍能恢复不可变原回执及当前有效证明,不能靠删除回执或要求换新执行绕过。
F2 [P2] 声明生产者与领取使用一致的等价规则 — 共享规范化 L254。File/SQLite:原 required_write_scopes=[src//main.ts, src/./main.ts] 在 base 可原子 claim/acquire,在 head 返回 invalid_coordination_task_lease。head 自己的普通 todo add 也能合法保存这两个别名,紧接着 claim 即失败,拒绝前后 canonical 状态不变。 两个合法字符串在这里归并成一个,而 unchanged coordinationTodoWriteScopes 看到长度从2变1就抛出损坏错误。本轮还通过 head 自己的普通 todo add --required-write-scope src//main.ts --required-write-scope src/./main.ts 实际创建记录,紧接着正常 claim 就失败,因此不只是人工构造的历史情况。修复应贯通声明生产与消费,保留既有有效 Todo;不应让用户为同一工作额外修订一次 scope 才能执行。
F3 [P2] 使用匹配器的完整字符类边界 — 扫描 L88。File/SQLite:合法 glob src/[]/./]x 匹配 src/.x,base 拒绝第二笔独占租约;head 把原 glob 存成 src/[]/]x 后准许第二笔,两个同仓库 active lease 同时存在。 已有 classMatch 支持字符类开头的字面量 ],新扫描却把它当成闭合括号。于是字符类里面的 /./ 被当成路径段折叠,. 成员丢失。这不是等价路径规范化,而是改变 glob 覆盖文件集合;只测 [./]、[//] 的字符串输出抓不住它。需要复用匹配语法的括号边界,补字面量右括号、否定类及类内 slash/dot 的真实排他回归。
真实 File/SQLite CLI 证实原漏洞:base 可先后给 src/./main.ts 和 src//main.ts 两个同仓库任务发租约,head 在第二次领取时返回 write_scope_conflict,源状态不变。 独立 33 个 Python 单测、56 个既有真实入口测试通过;12 个相关 TS 文件 181 passed/6 PostgreSQL optional skipped;typecheck 通过;标准 premerge 5 direct、9 catalog/8 risk 共17 selected 通过。另启动隔离真实 PostgreSQL16.15,完整 authority-store integration 369 passed/0 skipped,容器已 finally 清理;该套件不替代本轮新 File/SQLite 反例。 这说明通用存储和原来的测试场景仍能运行,不能证明新增边界正确;三个阻断问题均经真实 CLI→原生 owner→独立 provider 读回复现,没有用 mock 预置结论。
对主干的风险
F1 使丢响应/升级后的合法继续被当成另一请求;F2 让正常声明到执行的既有旅程增加无必要的 owner 重录;F3 则在本应排他的同文件工作上发出第二笔租约。范围限定于对应输入与共享租约语义,不称为文件系统越权或跨 Goal 锁问题。拒绝场景的 canonical 源、显示和 registry 均独立验证未变;glob 漏拦则实际观察到两笔 active lease。
既有 owner 可以 inspect、按原 proof release 后重新获取;把声明显式修订成单个规范 scope 后也能领取。这些真实 File/SQLite 恢复均已执行,但属于额外操作,不能冒充原重放保证或原有 add→claim 路径恢复正常。回滚整个 PR 又会恢复原别名漏拦,因此建议保留原正向修复,在同一 owner 内修三个消费边界。
语义与 CI 对齐
当前义务来自上述固定契约,触发点、观察、最小修复与重跑范围已在 F1–F3 说明。修复后应重跑原生公开 File/SQLite 原生产者/新消费者、普通 add→claim、glob 持有者→目标的对照,再跑相关 lease suites、实际 PostgreSQL integration 和标准 premerge;不以只改码或放宽断言结清。digest-owner 定向检查在不可变 base 和 exact head 同为16过1失败,完整对象同为 trajectory_input_usage.ts:24 /^[a-f0-9]{64}$/;因果文件和检查器逐字节相同,属于 pre_existing_unrelated,不要求本 PR 修复或放宽。 它与本 PR 新回归分开,不能拿旧全库失败否定新问题,也不能把本轮局部全绿写成全库全绿。
初私有评审 runner 漏建临时目录、误给 agent Todo add 加 --agent-id、按未加优先级的文字查新 Todo,被原有契约正确拒绝;纠正为正常 authoring 参数和真实返回 ID 后才用于结论。初 PostgreSQL runner 在镜像初始化临时 socket 就绪时取版本,撞到临时服务关闭;改为最终 TCP 服务就绪后正常跑完并清理,原失败保留,没有跳过检查。未执行本轮全库套件、实际生产升级、真实模型、Windows/最低Node版本或长期成本实验;新增三个回归直接在 File/SQLite 复现,未声称对 PostgreSQL逐条重演。未读取或等待远端 CI。
我的整体评价
原漏洞值得修,生产四文件净增79行和现有 typed owner 的选址并非本轮阻断理由。必要的同域收敛是把比较语义、持久身份与声明校验分清,并复用已有字符类语法;无需借机加版本协议、改外部权限或扩成另一套路径系统。当前同一进程版本内的单测通过,不等于跨版本原回执和普通生产者/消费者已兼容。
检查既有 CLI add/claim/acquire/readback 及完整七文件 diff,未改变渲染页面、控件或可见布局;问题在现有旅程的后端准入和恢复。 未做 GUI 截图或页面可读性验收;未把后端修复等同完整协作 UI 资格。 原来已有意图和授权时,用户只需原请求重试或声明后领取;当前额外 release/new execution、重录 scope 并不缺用户新决定,应由实现消除。建议修复三项并用同样反例重新核验后再批准;本次不合并或安装。
English verdict: REQUEST_CHANGES a7365711bc3be3dda22a92988a6bb57fff1da9dc. The motivating alias conflict is fixed, but real File/SQLite CLI checks reproduce three regressions: old exact acquisition retries fail after normalization, ordinary accepted Todo declarations cannot be claimed, and a valid character class is changed so a conflicting second exclusive lease is admitted. Preserve original receipt identity, align producer/admission validation, and share the existing glob grammar. Passing local/premerge and real PostgreSQL tests do not cover these counterexamples.
Reviewer: model_agent; gpt-6.1-sol; OpenAI; runtime_reported; reasoning_effort=max.
| owner: normalizeOwner(raw.owner), idempotency_key: normalizeIdempotencyKey(raw.idempotency_key), | ||
| write_workspace: leaseWorkspace(raw.write_workspace), | ||
| write_scopes: normalizeWriteScopes(raw.write_scopes), ttl_seconds: normalizeTtl(raw.ttl_seconds), | ||
| write_scopes: requireWriteScopes(raw.write_scopes), ttl_seconds: normalizeTtl(raw.ttl_seconds), |
There was a problem hiding this comment.
[P1] 保留旧租约 acquire 请求的原始回执身份
File/SQLite:base 用 src//main.ts 首次 acquire,再按同一 key/expected-version=0/TTL/scope 重放成功;同一物理 provider 不变交给 head,完全原参数却 exit1/coordination_operation_identity_mismatch,原租约仍 active。 Separate comparison/new-record canonicalization from frozen historical request/receipt identity. Recover the existing original request and current proof without changing its immutable receipt or requiring a new execution. Create the lease with immutable base through real CLI on File/SQLite, then replay exact same key, expected0, TTL and original scope on head, also after renewal; require original_receipt unchanged/current generation returned and no extra event or lease.
| const seen = new Set<string>(); | ||
| for (const item of raw) { | ||
| const scope = compact(item); | ||
| const scope = canonicalWriteScope(compact(item)); |
There was a problem hiding this comment.
[P2] 让 Todo 声明与领取对等价 scope 使用一致规则
File/SQLite:原 required_write_scopes=[src//main.ts, src/./main.ts] 在 base 可原子 claim/acquire,在 head 返回 invalid_coordination_task_lease。head 自己的普通 todo add 也能合法保存这两个别名,紧接着 claim 即失败,拒绝前后 canonical 状态不变。 Align producer and admission validation without treating lexical deduplication as corruption of a valid declaration. Preserve legacy declarations and ordinary add->claim; do not require the owner to rewrite unchanged work simply to acquire it. Exercise ordinary todo add with both aliases, then hard-lease claim on File/SQLite, plus a base-produced retained Todo; assert useful claim succeeds with semantic scope coverage and no extra manual declaration revision.
| let start = 0; | ||
| for (let index = 0; index < scope.length; index += 1) { | ||
| if (scope[index] === "[") { | ||
| const closing = scope.indexOf("]", index + 1); |
There was a problem hiding this comment.
[P2] 识别字符类开头的字面量右括号
File/SQLite:合法 glob src/[]/./]x 匹配 src/.x,base 拒绝第二笔独占租约;head 把原 glob 存成 src/[]/]x 后准许第二笔,两个同仓库 active lease 同时存在。 Reuse the matching grammar for character-class boundaries, including an initial literal ] and optional !. Do not collapse path separators or dot tokens inside the class; retain its matching set. Real File/SQLite public acquire with the original glob and literal target must reject the second lease; add parser/overlap cases for literal ], negation and slash/dot membership.
…-aliases Signed-off-by: duang777 <duangjl007@gmail.com>
Keep original scope spellings in acquisition request identity while using canonical comparison and new-record views. Preserve accepted Todo alias declarations, share character-class parsing, and reject malformed scope evidence in historical receipts. Cover public File/SQLite acquisition, cross-version renewal replay, ordinary add-to-claim and exclusive glob conflicts. Signed-off-by: duang777 <duangjl007@gmail.com>
loopx-agent
left a comment
There was a problem hiding this comment.
Reviewer: model_agent · gpt-6.1-sol · OpenAI · runtime_reported · reasoning_effort=max
APPROVE — 当前精确 head b61bc6a7e0aadc16d3801560e79448e3ee09f27e,集成基线 659c1fd441abf5bd9171f22d9a84981497856a63。原 F1–F3 已通过独立真实入口复验;旧错误与旧评审保留为历史,不继承其结论。
动机
通过 CLI 领取同仓库独占租约、从普通 Todo 声明领取工作,以及升级/续租后重试原请求的调用者。
src//main.ts 与 src/./main.ts 原来逃过同仓库排他;前一 head 修正比较却破坏原回执、合法声明及带字面量 ] 的 glob。本 head 分开不可变原拼写与派生比较,并共用完整字符类边界。 原别名漏洞确实需要修复,但修复不能让调用者为同一项工作重录声明、删除回执或重新开始执行。本次用原合同与原三个反例分别判断,未把作者测试或新 head 本身当作解决证明。
独立真实入口确认:原请求在升级、续租后恢复原回执,普通合法声明直接领取,完整字符类覆盖和原别名互斥同时正确;调用者不必重录已知要求或重启原执行。
范围仍是既有租约准入和恢复,不新增文件访问权限、跨 Goal 锁、provider、协议、UI、模型执行或生产升级,也不授合并权。
实际 Windows、安装 App、真实模型采用与长期完整成本未在本轮认证;六个可选 TS PostgreSQL case 保留跳过,另行通过的369项真实 PostgreSQL integration 不冒充这些 case 的执行。
修改前先读 canonical lease contract和原评审全部正文、三个 inline;规范固定为 09e47b0e2ed990681b007c031efae385b73df1a3。三个真实节名 Repository-relative scope identity、Commit, retry and readback、Revising declared work requirements 分别要求有效覆盖互斥、原请求与当前证明兼容、合法声明不被当成实际工作修订。本 head 对三项均已实现;不是靠本次新增文档定义自己的验收。
改动思路
复用既有 TS lease decision/state/receipt owner:原请求 normalize 保留字符串身份;canonicalWriteScope/comparisonWriteScopes只派生新记录和比较;requireWriteScopes在新输入入口严格验证;characterClassEnd由规范化和匹配共用。 原有 typed owner 足够,无需新协议、存储迁移、全局锁或物理路径权限层。同一个字符串在请求身份、工作声明和冲突比较中的责任不同;只让派生比较和新记录使用规范集合,才能同时修复漏拦与过度拒绝。
当前新增的两语合同文字准确说明原拼写与比较值的区别。新输入中的反斜杠、越级或非相对 token 会严格拒绝,不再静默过滤;损坏的历史 scope 仅在冲突比较中保守视为 **,不会被改写成真实根权限,也不能作为有效历史回执复用。独立工作树提示、同 checkout 排他、仓库未知时保守互斥等原边界保持。
具体改动
关键代码讲解
canonicalWriteScope/comparisonWriteScopes在原决策 owner 中折叠空段和独立.;characterClassEnd与原 matcher 共用初始字面量]和可选!的解析,字符类中的 slash/dot 不再被误当路径段。新记录规范化,保留记录只生成比较视图。normalizeWriteScopes/requireWriteScopes保留合法原拼写,避免两个等价声明归并后被旧长度校验误判损坏;严格入口先拒绝非法新输入。executeCanonicalTaskLeaseAcquire用原始合法字符串计算冻结 digest,解码回执时先拒绝坏 scope,再比较语义覆盖;原 receipt/current-proof owner 继续核验 key、版本、epoch、资格和退役。它不会用比较等价性接受同一 key 下改过的原参数。canonicalTaskLeaseAcquireFacts从完整 canonical head 提取比较 scopes,不重写旧 lease,也不依赖 Todo 列表或展示页。
完整 10 文件 +408/-31:产品四个 TS 文件 +119/-30,合同12行,五个测试文件 +277/-1。后续测试针对三个实际消费边界、原路径及损坏回执拒绝,并非增加另一套产品 owner。新共享解析函数是适用的同域收敛;task-orchestration 的另一套语义仍保持原范围,不扩成本 PR 的迁移工程。
对主干的风险
独立脚本使用原 09e47b0、前一坏 head a736571 和当前 head,通过 94 次真实 CLI 调用记录40行比较;全部使用隔离真实 File/SQLite、普通生产 owner 和独立源读回,没有 mock 预置成功。
| 原反例 | 当前独立结果 |
|---|---|
| F1 原生产者的原 acquire 请求 | 同一物理 provider 上,旧坏 head 仍报 identity mismatch;当前升级前后均恢复完整原回执。真实续租后返回当前 version2/epoch1,原 receipt 不变、无额外 lease/event;改拼写同 key 和已释放执行仍拒绝。 |
| F2 原声明与普通 add→claim | 原 base 与当前普通 add 都保存两个合法别名;坏 head 仍拒绝 claim,当前正常领取,声明原样保留而新 lease 存一个规范 scope,无额外 owner 修订。 |
| F3 完整字符类 | 字面量 ]、否定类、类内 slash/dot 的原覆盖恢复;应冲突的目标均拒绝,否定类排除的无关目标仍可领取。原词法别名漏拦也继续被修复。 |
另经真实 CLI 复验:同 Goal/仓库的无关 docs scope 可以执行;伪造或删除旧展示不会改变 canonical 冲突,完整拒绝结果相同;由真正持有者按 proof 释放后,原先被拒的目标获得新 active lease 并独立 inspect。十个非法新 scope 输入拒绝且源状态不变。这些结果同时检查“漏拦”和“过度拒绝”,读回或拒绝本身不被当作恢复完成。
独立 87 Python passed;12个相关 TS 文件 184 passed / 6 optional PostgreSQL skipped;隔离真实 PostgreSQL16.15 integration 369 passed / 0 skipped,容器已清理。PG suite 不冒充上述六个可选 case 或逐条服务端 CLI 反例的执行。Typecheck、Ruff、标准 native premerge 5 direct / 18 selected(9 catalog、8 risk、1 public boundary)全部通过。最新 target main 7a03792090abb44850109cb6f6d93219c63e555e 在四个产品 owner 上与本 PR base 相同。未查询或等待远端 CI;旧 head 的其它 CI/全树失败不被本次局部通过改写。
我的整体评价
当前完整提案可批准:原漏洞与三个公开回归在同一 owner 内一起修复,原请求、合法工作与真实错误都能沿原路径继续,无需额外用户决定或确认。原评审中要求的修复条件已逐条取得当前 exact-head 证据,仍需分别完成原生审批收尾与合并就绪检查;批准本身不授合并权。
检查既有 CLI、完整 diff 与消费者,未修改渲染页面、布局或控件,视觉证据不适用。本次是既有准入/恢复旅程的修复,不是完整协作 UI、物理文件权限、跨 Goal 锁、生产升级或长期模型收益认证。经验提示只用于普通工作→失败→恢复→读回的判断,没有据此增加无关 rollout 门或声称 memory 效益。
English verdict: APPROVE — b61bc6a7e0aadc16d3801560e79448e3ee09f27e. All three prior regressions are repaired through real File/SQLite CLI: immutable old requests/receipts survive upgrade and renewal, ordinary declarations claim without revision, and complete glob-class coverage is preserved. The motivating alias exclusion and opposite nonoverlap/recovery cases pass.87 Python and184 TypeScript tests pass;6 optional TS PostgreSQL cases remain skipped, while the separate real PostgreSQL16.15 integration passes369 with zero skips. Native premerge and type/lint pass; no CI, production upgrade or merge is claimed.
Summary
Malformed retained scope strings compare as ** without rewriting the stored lease. Acquisition identity retains the original request spellings; new lease contents and conflict checks use the canonical scope set. Glob character classes share the matcher's boundary parser.
Evidence
Merge Danger
Door: two-way
The change only normalizes lexical equivalents for lease admission and comparison. Reverting restores the former matching behavior.
Blast Radius: coordination
Goal And Delivered Outcome
Author Declaration
Implemented against
Scope And Continuation
Review follow-up
This head repairs the three reported regressions while retaining the motivating alias-conflict fix:
Historical receipt decoding remains strict: malformed scope evidence cannot become a root wildcard through the conservative conflict-comparison fallback. The prior CHANGES_REQUESTED review is historical; the new head requires independent review.
Validation
b61bc6a7e0aadc16d3801560e79448e3ee09f27e.mainat659c1fd; merged normally into the feature branch, preserving published history.Frontend / Visual Evidence
Type of Change
LoopX Area
Technical Direction
Shared-authority RFC fixture impact
Boundary Checklist