Repository navigation
fix(leases): treat case aliases as overlapping write scopes - #7252
JasonBuildAI wants to merge 2 commits into
Conversation
Hard-lease scope comparison was case-sensitive even though the contract requires host/path case aliases to remain overlapping. Two agents could therefore acquire active exclusive leases for one physical file on default case-insensitive Windows and macOS filesystems. Case-fold only admission matching, including literals, prefixes and glob character classes, while leaving retained scopes and request identity in their original spelling. Signed-off-by: JasonBuildAI <jasonbuildai@gmail.com>
Signed-off-by: JasonBuildAI <jasonbuildai@gmail.com>
Folding mixed-case range endpoints could still miss case aliases such as C inside [A-b], so two exclusive leases could remain active. Test the original character and its single-code-unit case variants against the unmodified class instead. Signed-off-by: JasonBuildAI <jasonbuildai@gmail.com>
Independent AI reviewThis is an independent AI review of the exact head Verdict: APPROVE WITH NOTES Earlier in this review, the implementation was found to miss mixed-case character-class ranges:
|
Signed-off-by: JasonBuildAI <jasonbuildai@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
动机
在大小写不敏感文件系统上并行修改同一仓库的开发者和 Agent。
例如两个执行者分别申请 src/Σ.ts 与 src/ς.ts;本机文件系统确认它们指向同一文件,但修复后仍会同时准入。ASCII 文件名与字符类反例已修复,Unicode 别名的核心风险仍在。
实际 File、SQLite 的普通 acquire 和原子 Todo claim 中仍有6个 Unicode 反例同时保有两份 active 租约;ASCII、混合大小写字符类及拒绝不写状态的检查通过。
本次只审查既有同 Goal/仓库的写范围互斥,不扩大为符号链接、跨 Goal 的物理锁,也不修改原范围拼写、请求身份、owner、TTL、CAS 或独立 worktree 的 advisory 模式。
改动思路
范围冲突应由既有 TypeScript acquire decision 统一决定,File、SQLite、共享服务与原子 claim 复用它。原样保留字符类后测试候选大小写变体,能修复 [A-b] 和 [!A-b];但 literal/prefix 仍以 toLowerCase 当作等价关系,不能覆盖已约定的实际主机大小写别名。
classMatch 保留原区间,对原字符及单字符大小写变体先求并集再取反;fnmatchcase、literal、目录前缀与双 glob 前缀增加大小写比较。比较副本改变,持久化及请求身份不转换。 保留唯一 decision owner,补全比较专用的大小写等价处理并覆盖 literal、目录和 glob 前缀;继续保留原区间、否定顺序和原始范围/请求拼写。仅把 lower 换成 upper 或在 CLI 再补一套判断均不足以建立一致契约。
本次只审查既有同 Goal/仓库的写范围互斥,不扩大为符号链接、跨 Goal 的物理锁,也不修改原范围拼写、请求身份、owner、TTL、CAS 或独立 worktree 的 advisory 模式。
具体改动
[P1] Unicode 大小写别名仍可取得两份互斥租约。 同一 macOS 文件的 src/Σ.ts/src/ς.ts(以及 src/ß.ts/src/SS.ts)在这里仍不相等,目录和 glob 前缀也用同一 lower 假设。独立实际 File/SQLite CLI 复现:普通 acquire 两种 provider 各有这2例,原子 Todo claim 各有 Σ/ς 1例,第二次都 exit 0、两份租约都 active、provider 状态改变;实际文件系统已确认两种写法对应同一文件。ASCII 和新字符类测试通过仍不能满足既有 “Host/path case aliases remain overlapping.” 契约。这是该修复仍遗漏的原缺陷。请在现有比较 owner 中统一覆盖 Unicode 大小写别名,保持原范围/请求身份和字符类区间/否定语义,并补齐两种 provider 的 acquire 与 atomic claim 回归。
不可变已接受规格:docs/reference/canonical-lease-renew.md,版本 28fa39bd8a2f172c8a1cfde5b144dbd249f165a8。先按原规格审查;下列是本审计映射标签,非伪造原编号:
host-case-alias-overlap(not_met):Host/path case aliases remain overlapping.preserve-scope-identity(implemented):Keep stored scope spelling and request identity unchanged.existing-canonical-lease-boundary(implemented):Use the shared canonical acquire path and retain owner, TTL, CAS, replay and independent-worktree advisory boundaries.
3 files +97/-21; production +34/-21。全差异、原调用者、完整讨论、DCO与公开边界已读。字符类之外的全部literal/目录/glob分支一起评估,不能只看最后一笔commit。
classMatch(loopx/control_plane/work_items/task_lease_acquire_decision.ts:143):对原区间逐项判断候选集合;否定发生于全部候选求并集之后,保留 [A-z] 的原标点和 [!A-b] 排除语义。
fnmatchcase(loopx/control_plane/work_items/task_lease_acquire_decision.ts:175):memoized glob 匹配把 foldCase 传给字符类;literal 分支只做逐字符 lower 比较,Σ/ς仍不等。
scopePairOverlaps(loopx/control_plane/work_items/task_lease_acquire_decision.ts:214):literal、目录、单 glob 和双 glob 路由到同一比较;lower 并不是实际主机全部大小写别名的等价关系。
decideTaskLeaseAcquire(loopx/control_plane/work_items/task_lease_acquire_decision.ts:410):原 owner/replay/版本检查先执行,完整其他有效租约通过范围比较决定 conflict 或 advisory,再由原执行层提交。
对主干的风险
独立 Python 28通过;native workspace 30通过/1项环境跳过;独立 PostgreSQL 16 三套隔离数据库集成合计375通过;标准 premerge 5项直接/编译检查和17项选中检查通过。16组不可变基线/当前实际 CLI 配对共160个进程,另外读取当前主机真实文件别名与纯 API 的 literal/目录/glob 分支。
macOS 实际同 inode 的 Σ/ς 与 ß/SS 路径都被当前 literal 比较漏掉;普通 acquire 在两种 provider 各漏2例,原子 claim 各漏1例。两份租约确实 active,provider 字节确实改变,不是只看纯函数。
Unicode 互斥缺口阻止批准。这是修复范围内仍未解决的原缺陷,不称为新引入回归。PostgreSQL集成通过不证明该主机别名反例已在 PostgreSQL 执行,native workspace 的1项 PostgreSQL 环境跳过仍单独披露;没有原生 Windows、已安装产品或 live 编辑资格。
实际CLI使用不可变基线产品源码;seed和物理路径在两臂间完全还原。原始完整退出/输出/receipt/字节摘要私有保留;公开只给合成反例,不发布本地路径、活跃Goal或原始运行日志。
CI按当前契约未查询、轮询或等待。未执行安装、模型调用或合并。
我的整体评价
REQUEST_CHANGES,对应 7252@7815a91fdd1b850fbcdebdf839ecac159b6d729f。核心host-case-alias验收尚未满足;请求在现有owner补齐,不能以部分ASCII修复宣称全部解决。
范围冲突应由既有 TypeScript acquire decision 统一决定,File、SQLite、共享服务与原子 claim 复用它。原样保留字符类后测试候选大小写变体,能修复 [A-b] 和 [!A-b];但 literal/prefix 仍以 toLowerCase 当作等价关系,不能覆盖已约定的实际主机大小写别名。
本次只审查既有同 Goal/仓库的写范围互斥,不扩大为符号链接、跨 Goal 的物理锁,也不修改原范围拼写、请求身份、owner、TTL、CAS 或独立 worktree 的 advisory 模式。
长期工作:preserved,保留原重放、拒绝与后续读回,未测多轮净收益;使用体验:not_yet_proven,实际 File、SQLite 的普通 acquire 和原子 Todo claim 中仍有6个 Unicode 反例同时保有两份 active 租约;ASCII、混合大小写字符类及拒绝不写状态的检查通过。
已检查既有 CLI 完整输出、错误/恢复正文及原调用者;没有页面、布局、组件或视觉素材变更,截图与构图检查不适用。
Unicode 互斥缺口阻止批准。这是修复范围内仍未解决的原缺陷,不称为新引入回归。PostgreSQL集成通过不证明该主机别名反例已在 PostgreSQL 执行,native workspace 的1项 PostgreSQL 环境跳过仍单独披露;没有原生 Windows、已安装产品或 live 编辑资格。
有界重构:保留唯一 decision owner,补全比较专用的大小写等价处理并覆盖 literal、目录和 glob 前缀;继续保留原区间、否定顺序和原始范围/请求拼写。仅把 lower 换成 upper 或在 CLI 再补一套判断均不足以建立一致契约。 审批、approval-closeout、merge-readiness和维护者合并权限各自核验;本评审不合并。
English verdict: REQUEST_CHANGES. REQUEST_CHANGES. The ASCII/class fixes work, but same-file Unicode case aliases still receive two active exclusive leases through real File/SQLite acquire and atomic claim. This is an unresolved defect in the stated repair scope, not a newly introduced regression. Complete the comparison-only equivalence without rewriting stored scopes or request identity.
| function scopePairOverlaps(left: string, right: string): boolean { | ||
| if (left === right) return true; | ||
| const equal = (first: string, second: string) => first === second || | ||
| first.toLowerCase() === second.toLowerCase(); |
There was a problem hiding this comment.
[P1] 同一 macOS 文件的 src/Σ.ts/src/ς.ts(以及 src/ß.ts/src/SS.ts)在这里仍不相等,目录和 glob 前缀也用同一 lower 假设。独立实际 File/SQLite CLI 复现:普通 acquire 两种 provider 各有这2例,原子 Todo claim 各有 Σ/ς 1例,第二次都 exit 0、两份租约都 active、provider 状态改变;实际文件系统已确认两种写法对应同一文件。ASCII 和新字符类测试通过仍不能满足既有 “Host/path case aliases remain overlapping.” 契约。这是该修复仍遗漏的原缺陷。请在现有比较 owner 中统一覆盖 Unicode 大小写别名,保持原范围/请求身份和字符类区间/否定语义,并补齐两种 provider 的 acquire 与 atomic claim 回归。
Goal And Delivered Outcome
This prevents a silent exclusive-write bypass for default hard-lease multi-agent goals on case-insensitive Windows and macOS: two agents can otherwise hold active leases for
src/Main.tsandsrc/main.ts, which name one physical file.status: "applied",acquired: true, and mutated the provider; after the change it exits 1 withwrite_scope_conflictand leaves provider state unchanged.Closes #7243; baseloopx-project/loopx:mainat28fa39bd8a2f172c8a1cfde5b144dbd249f165a8.Author Declaration
Implemented against
docs/reference/canonical-lease-renew.md,Repository-relative scope identity, revision28fa39bd8a2f172c8a1cfde5b144dbd249f165a8. The linked issue and default provider contract inlocal-authority-provider-selection.mddefine the affected default path.Host/path case aliases remain overlappingscopePairOverlapsinloopx/control_plane/work_items/task_lease_acquire_decision.tstests/control_plane_ts/task_lease_workspace.test.ts;tests/control_plane/test_canonical_lease_scopes.pycomparisonWriteScopes,normalizeWriteScopesscope comparison folds case aliases without rewriting retained or request spelling; operation-identity replay assertionclassMatch,fnmatchcase[A-Z]and preserved original-range coverage for[A-z]evaluateTaskLeaseAcquireDecisionFileandSQLitereal-CLI test[A-b]still missedC/c; the positive and negated cases were reproduced as failures and are fixed at this head. Existing repository-wide suite gaps on native Windows are listed under Coverage and gaps rather than claimed as green.Scope And Continuation
Validation
7815a91fdd1b850fbcdebdf839ecac159b6d729fstaticpassednpm run -s typecheck:control-plane; Ruff overtestsand the changed control-plane scope; semantic-vocabulary advisory found no supported new vocabulary carriers.unitpassednode --no-warnings --experimental-sqlite --experimental-strip-types --test tests/control_plane_ts/task_lease_workspace.test.ts: 26 tests, 24 passed, 0 failed, 2 environment-gated skips.regression_paritypassedpytest tests/control_plane/test_canonical_lease_scopes.py -q: 2 failed before the initial implementation; mixed-range cases failed on the first implementation; 18 passed at this head. The failing paths acquired both exclusive leases withstatus: "applied"; the head paths returnwrite_scope_conflictor correctly permit a negated-class non-overlap.real_entrypointpassedsrc/Main.ts; replay keeps request identity; agent-b'ssrc/main.tsis rejected and provider state is byte-unchanged.integrationpassedmanualpassedloopx canary premerge --from-git-diff --git-diff-base upstream/main --timeout-seconds 300: 17 selected, 17 passed, 0 failed, 0 manual holds. Public-boundary scan: 0 errors; 43 pre-existing credential-reference warnings.fcntlimports. Those host failures were not treated as passing evidence.Frontend / Visual Evidence
Type of Change
LoopX Area
Technical Direction
Shared-authority RFC fixture impact
Boundary Checklist
.loopx/,.codex/goals/, and liveACTIVE_GOAL_STATE.md).python3 scripts/check_rfc_bilingual.py --base origin/main --head HEADpasses, or no paired RFC changed.none.Signed-off-bytrailer (git commit -s).