Skip to content

fix(leases): treat case aliases as overlapping write scopes - #7252

Open
JasonBuildAI wants to merge 2 commits into
loopx-project:mainfrom
JasonBuildAI:codex/agent-z6k2
Open

JasonBuildAI wants to merge 2 commits into
loopx-project:mainfrom
JasonBuildAI:codex/agent-z6k2

Conversation

@JasonBuildAI

@JasonBuildAI JasonBuildAI commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

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.ts and src/main.ts, which name one physical file.

  • Outcome basis / optional anchor: #7243, reproduced through the real CLI with both File and SQLite canonical authority.
  • Goal/source and gap: The accepted lease contract requires host/path case aliases to remain overlapping, but the shared acquire decision compared literals, path prefixes and globs case-sensitively. The adjacent lexical-alias fix in fix(leases): canonicalize equivalent write scopes #7204 left this case-alias member of the same bypass.
  • Observable before -> after, with the validation row that proves it: Before the change, a second exclusive acquire for a case-only alias returned status: "applied", acquired: true, and mutated the provider; after the change it exits 1 with write_scope_conflict and leaves provider state unchanged.
  • Issue/task and intended base: Closes #7243; base loopx-project/loopx:main at 28fa39bd8a2f172c8a1cfde5b144dbd249f165a8.

Author Declaration

  • Written by: model_agent, OpenAI Codex
  • AI disclosure: this change was drafted with AI assistance at my request, under my direction and review.

Implemented against

Criterion (spec clause) Disposition Symbol / path Test or command
Host/path case aliases remain overlapping implemented scopePairOverlaps in loopx/control_plane/work_items/task_lease_acquire_decision.ts tests/control_plane_ts/task_lease_workspace.test.ts; tests/control_plane/test_canonical_lease_scopes.py
Case folding is limited to conflict comparison; retained and request spelling stays unchanged implemented comparisonWriteScopes, normalizeWriteScopes scope comparison folds case aliases without rewriting retained or request spelling; operation-identity replay assertion
Glob character-class behavior is not weakened implemented classMatch, fnmatchcase Case-folded [A-Z] and preserved original-range coverage for [A-z]
The shared decision serves both File and SQLite hard-lease authority implemented evaluateTaskLeaseAcquireDecision Parameterized File and SQLite real-CLI test
  • Self-check before submission: Rebased onto the tested upstream revision, read the contract clause and shared callers, ran the changed and adjacent suites plus premerge, and intentionally left persistence schemas, provider code, request identity and UI unchanged. Independent review found that mixed-case character-class ranges such as [A-b] still missed C/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

  • Completed scope and remaining work: Complete within this scope. The fix covers literal, directory-prefix and glob comparisons at the shared decision owner; no configuration, storage or provider migration is needed.
  • Slice boundary / successor: N/A. This closes the remaining case-alias member of the exclusive-scope bypass addressed by fix(leases): canonicalize equivalent write scopes #7204; no successor is required for this defect.

Validation

  • Tested revision: 7815a91fdd1b850fbcdebdf839ecac159b6d729f
  • Run state: finished
  • Input classes: synthetic
Check kind Result Public-safe evidence / limitation
static passed npm run -s typecheck:control-plane; Ruff over tests and the changed control-plane scope; semantic-vocabulary advisory found no supported new vocabulary carriers.
unit passed node --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_parity passed pytest 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 with status: "applied"; the head paths return write_scope_conflict or correctly permit a negated-class non-overlap.
real_entrypoint passed Parameterized real-CLI parity with File and SQLite canonical authority: agent-a acquires src/Main.ts; replay keeps request identity; agent-b's src/main.ts is rejected and provider state is byte-unchanged.
integration passed Adjacent lease acquire/renew/lifecycle/CLI suites: 47 passed, 2 skipped.
manual passed loopx 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.
  • Coverage and gaps: Focused TypeScript, Python, adjacent lease and real-CLI paths are covered. PostgreSQL was not run because this change is in the shared provider-independent decision and does not alter schema, storage or provider behavior. The repository-wide TypeScript suite remains host-blocked on this native Windows checkout by unrelated CRLF fixture parsing and process-cleanup failures; full pytest collection remains blocked by POSIX-only fcntl imports. Those host failures were not treated as passing evidence.

Frontend / Visual Evidence

  • UI impact: none
  • Before: N/A
  • After: N/A
  • First viewport and preview: N/A
  • States and viewports shown: N/A
  • Source data: none
  • Attention review: N/A; no user-visible surface changed.
  • Craft review: N/A; no user-visible surface changed.

Type of Change

  • Bug fix
  • New feature
  • Breaking change
  • Refactoring (no functional changes)
  • Documentation update
  • Test update

LoopX Area

  • Control plane (goals, todos, quota, scheduler, registry, runtime)
  • Benchmark boundary (adapters, runners, verifiers, scoring, evidence)
  • Capability or extension (providers, adapters, skills)
  • Public docs or presentation surface (README, protocols, dashboard)
  • Build, packaging, installer, or CI
  • Host or runtime integration

Technical Direction

  • Direction / acceptance reference, when applicable: Core control-plane hardening; canonical task-lease write-scope authority.

Shared-authority RFC fixture impact

  • Production-scale fixture schema: N/A; this PR does not claim shared-authority RFC promotion progress.
  • Semantic dimensions changed, or reviewed no-impact rationale: N/A; only conflict comparison changes, not persisted identity.
  • Provider conformance arms run: File and SQLite through the canonical CLI; PostgreSQL is covered by the same shared decision but was not run locally.
  • Read-only legacy/file/PostgreSQL three-arm rehearsal (required for promotion, runtime-routing, or compatibility-projection changes): N/A; no promotion, routing or compatibility projection changes.

Boundary Checklist

  • Neither the diff nor this PR body/comments/attachments disclose private state, credentials, raw traces or verifier output, internal links, or local machine paths (including .loopx/, .codex/goals/, and live ACTIVE_GOAL_STATE.md).
  • I did not duplicate maintainer-owned benchmark work unless a maintainer split out a public issue for it.
  • I kept the change scoped to the linked issue/task.
  • Existing English/Chinese RFC pairs (including ledger entries) both have a diff, and the local premerge check python3 scripts/check_rfc_bilingual.py --base origin/main --head HEAD passes, or no paired RFC changed.
  • I completed the visual evidence section for UI changes, or marked UI impact none.
  • Every commit includes a DCO Signed-off-by trailer (git commit -s).

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>
JasonBuildAI added a commit to JasonBuildAI/loopx that referenced this pull request Oct 24, 2026
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>
@JasonBuildAI

Copy link
Copy Markdown
Contributor Author

Independent AI review

This is an independent AI review of the exact head 7815a91fdd1b850fbcdebdf839ecac159b6d729f. It is not a human approval or a formal GitHub approval.

Verdict: APPROVE WITH NOTES

Earlier in this review, the implementation was found to miss mixed-case character-class ranges: [A-b] folded only its endpoints and therefore missed C/c, allowing a second exclusive lease through the real CLI. The incremental commit at this head resolves that finding. Positive and negated range cases now behave correctly, and the provider state remains unchanged on the conflict path.

  1. Importance: Yes. This protects the exclusive-write concurrency boundary for default SQLite hard_lease and File-authority Goals on case-insensitive Windows/macOS hosts. A failure silently permits concurrent writes to one physical file under two apparently exclusive leases.
  2. Single problem: Yes. The cumulative diff stays within the shared lease decision and its two focused test files; the incremental commit only repairs character-class membership and adds boundary coverage.
  3. Logic and boundaries: No remaining implementation defect found. Mixed-case positive and negated classes behave correctly, including the original [A-z] coverage. The reviewer also checked all 1,378 ASCII letter ranges in positive and negated form without a mismatch.
  4. Abstraction: No unnecessary abstraction. The membership helper and candidate set remain local to the existing matcher; no new owner, module, configuration or persisted contract is introduced.
  5. Test sufficiency: The Python suite covers the mixed-range conflict through both File and SQLite, checks provider readback, and covers the negated-class non-overlap. The TypeScript test covers a case alias, mixed-case ranges, preserved spelling, directory prefixes, glob-versus-glob overlap and an unrelated-path negative case. The reviewer also reproduced the real-CLI conflict response and verified regular provider files, including SQLite sidecars, remained byte-identical.
  6. Checks: Focused Python 18 passed; TypeScript workspace 24 passed, 2 environment-gated skips; control-plane typecheck, Ruff and git diff --check passed; the real-server PostgreSQL authority CI check passed. The exact-head Release Artifacts build fails on the pre-existing browser assertion Goal drawer header is still pinned too close to the top edge in examples/personal-workspace-browser/typed-actions.mjs; the same assertion failed on merged PR fix(chat): withdraw misplaced Goal-level task selector #7248, and this diff changes no UI files. Broader CI jobs were still pending at the review snapshot.

JasonBuildAI added a commit to JasonBuildAI/loopx that referenced this pull request Oct 24, 2026
Signed-off-by: JasonBuildAI <jasonbuildai@gmail.com>

@loopx-agent loopx-agent left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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 回归。

This branch has not been deployed

No deployments
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