Skip to content

fix(coordination): tolerate Windows ctime skew in source lease reads - #8761

Merged
loopx-agent merged 2 commits into
loopx-project:mainfrom
BigDataDZ:codex/source-lease-ctime-policy-20261114
Nov 14, 2026
Merged

loopx-agent merged 2 commits into
loopx-project:mainfrom
BigDataDZ:codex/source-lease-ctime-policy-20261114

Conversation

@BigDataDZ

Copy link
Copy Markdown
Contributor

Motivation / 动机

Issue #8604 的审计把 authority_source_capture.py 的两处 ctime 比较列为 SUSCEPTIBLE(Deferred):_source_lease_bytes 在 path lstat → descriptor fstat → final path lstat 三次观察之间无条件比较 st_ctime_ns,Windows 上未变更 lease 文件的纯 ctime 偏差会误抛 source_changed_retry。本 PR 把 #8599 已确立并合并的 Windows ctime 政策应用到 source lease 读取围栏。

Change / 变更

  • loopx/control_plane/coordination/authority_source_capture.py:POSIX 保留 ctime 参与 lease 围栏;Windows 仅当三次观察都带有完整卷/文件索引(st_dev/st_ino 非零)时容忍 ctime-only 偏差,索引不完整时 fail-closed 保留 ctime 比较。
  • dev/ino、size、mtime、常规文件与 O_NOFOLLOW 守卫全部保留,比较语义与原先逐字段等价(仅 ctime 的参与按平台政策条件化)。

Regression / 回归

tests/control_plane/test_shadow_registry_source.py::test_lease_read_identity_change_policy——真实 capture() 路径上、stat 边界注入单一字段偏移,2 平台 × 5 字段 × 3 身份可用性共 30 例:

  • Windows + 仅 ctime + 完整卷/文件身份 → 接受,lease inventory 与 sha256 精确;
  • POSIX ctime、任何 st_dev/st_ino/st_size/st_mtime_ns 变化、或卷/文件索引缺失 → 双平台均拒绝 source_changed_retry。

Validation / 验证

基于 main 1cdd9bbef,真实 Windows 运行:

  • uv run --extra test python -m pytest -q tests/control_plane/test_shadow_registry_source.py -k lease_read_identity_change_policy → 30 passed
  • uv run --extra test python -m pytest -q tests/control_plane/test_shadow_registry_source.py → 55 passed, 1 failed(test_source_snapshot_preserves_inventory_without_projecting_orphan_leases:Windows Path 比较大小写折叠导致的既有排序失败,已验证在未修改 main 上同样失败,与本 PR 无关,不在此处理)
  • uv run --extra test python -m pytest -q tests/architecture/test_settlement_receipt_source_boundaries.py tests/control_plane/test_canonical_lease_inspection.py → 95 passed

Addresses the authority_source_capture sites deferred by #8604; the remaining seven deferred modules are untouched.

@BigDataDZ

Copy link
Copy Markdown
Contributor Author

windows-powershell 失败与本 PR 无关(跨 PR 既有故障)

本 PR 的 windows-powershell 检查失败于步骤 "Qualify the native Windows CLI output differential":base 侧探针在 read_pinned_cli_body 抛 FileNotFoundError: [WinError 2] 后 AssertionError: agent-facing CLI base/head differential failed。

同一签名在其他 PR 上同样出现(例:#8758,失败步骤、两条异常完全一致),且失败点位于 pinned base 基线读取,不在本 PR 的 diff(authority_source_capture.py 的 ctime 政策与测试)覆盖范围内。本 PR 在真实 Windows 上的相关验证见 PR 正文(30 例回归矩阵 + 56/56 组合验证)。

@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

评审 625459784c6186b587f4e40df16b25c5bdb72037,请求修改。目标分支头 45cfd0c86d8f24d630ab66ad7f76784faa21c3f6。基线 1cdd9bbefd1d849ac8aa733bd3d80bc13ad0b722,实时主干 4604f938b612c5da0d8a3485ce563bea922fc14e。 当前主干的合并树 55b17147eb8caa89074b9bb320b75c2c3ce32328 无冲突。

[P2] Windows ctime 容错须通过最终的 lease 重读。 在真实 bootstrap 的前置采集及最终同源重读中,用同一稳定文件,只让路径和描述符的 ctime 视图不同。 在真实 CLI 上,前置 Python 读取与 TS 重读均只注入 ctime 差异时,当前提交仍返回 source_changed_retry;没有成功的 bootstrap 或权威快照。只注入 TS,同样失败;只注入 Python,当前提交能写入原租约的快照。 对同一条生产路径的 TS sourceLeaseBytes 使用对应平台策略;仅在 Windows 的卷/文件身份全程已证实时放宽 ctime。保持身份、文件类型、大小、mtime、字节摘要和锁,并用真实 CLI 验证两层均只有 ctime 偏差时能成功,以及实际来源变化时拒绝。 源代码

动机 / Motivation

在 Windows 上创建或复验本地 coordination shadow 的维护者。 运维人员用 coordination-shadow bootstrap 建立本地影子时,Windows 文件在路径查询和描述符查询中可能给出不同 ctime,即使租约字节、身份、mtime 和大小都没变。 主干在 Python 捕获阶段可能因纯 ctime 差异拒绝 unchanged 文件。 反复重试仍不能建立影子,也不能从当前状态判断是文件真变了还是平台读数不同。 稳定租约应通过前置读取与最终同源校验;真实内容或身份变化仍应在任何权威写入前拒绝。 只修复同一次 lease 读取的稳定性;保持已有同源 bytes digest、锁、身份、常规文件、禁链接和启用边界。

改动思路 / Approach

保留 Python 采集和 TypeScript 持锁校验的职责;让两层对同一档平台不变量保持一致。 只修复同一次 lease 读取的稳定性;保持已有同源 bytes digest、锁、身份、常规文件、禁链接和启用边界。 主干两层均依赖混用的时间戳。Python 的最小局部修补可以减少一处误判;但对用户报告为修复时,还需要覆盖其实际 TS 读回。用现有读取器处理相同原则,不加新设置或 provider。 Python 采集提供原租约字节和摘要。现有 TypeScript writer 持锁对源文件、目录清单和摘要重新验证,且最后决定是否可提交。

新增输入观察只发生于已启用的 本地 shadow 引导;不会启动 provider 或迁移现有数据。两个语言的检查不能各自放宽到不同范围。正常 bootstrap、旧损坏来源和关闭时的拒绝未新增权限。

具体改动 / Changes

2 个文件(+84/−2)。生产是一个已有 Python 采集器;测试新增 30 个 stat 边界场景。TS 校验行在主干和 head 中相同。 现有同源校验协议 与问题清单 #8604 规定源复验;Windows ctime 政策对照 #8599。 采用规范版本 4604f938b612c5da0d8a3485ce563bea922fc14e,来源 docs/reference/reviewed-coordination-promotion.md。

检查点 可观察要求 结果
windows_full_identity 稳定 lease 在两层都有 ctime 差异 未满足
genuine_change_remains_denied 身份或内容变化要保留拒绝 通过
untouched_other_platform 无偏差与 POSIX 路径保持 通过

关键代码讲解

  • _source_lease_bytes:采集未变租约与摘要的输入。Windows 完整身份允许 ctime-only;mtime、身份和长度继续约束。
  • _build_runtime_shadow_source_snapshot:把 lease bytes 绑定到清单。每个成员使用实际 SHA-256,不发布未经采集的数据。
  • sourceLeaseBytes:持锁校验再次读源文件。无条件比较 ctime,复现与 Python 新策略不一致。
  • verifyShadowSourceSnapshot:核对清单及投影。只有当前清单和字节摘要相同才到写入。

对主干的风险 / Risks

同一条未变 lease 仅在 Python 观察中出现 ctime 偏差时,主干拒绝,候选的真实 bootstrap 提交成功;正常租约两边均成功。POSIX ctime 改变和 Windows 策略下 mtime 改变仍拒绝;缺卷/文件标识、真实元数据变化和不安全路径另有本地回归覆盖。 在真实 CLI 上,前置 Python 读取与 TS 重读均只注入 ctime 差异时,当前提交仍返回 source_changed_retry;没有成功的 bootstrap 或权威快照。只注入 TS,同样失败;只注入 Python,当前提交能写入原租约的快照。

候选 151 项、当前主干 121 项通过;Ruff、diff、DCO 与双语检查通过,当前主干合并树无冲突。

语义与 CI 对齐

真实命令使用隔离源和存储;模拟条件只改变时间读数。没有把实验结果说成原生 Windows 已验证。 在 macOS 真实 CLI 与真实文件 I/O 上定点注入两层元数据偏差;未独立运行原生 Windows。作者报告的 Windows 30 项测试覆盖 Python read policy;其排序用例失败单独归于 #8762,不能证明当前端到端通过。未查询托管 CI。

我的整体评价 / Verdict

REQUEST_CHANGES。对同一条生产路径的 TS sourceLeaseBytes 使用对应平台策略;仅在 Windows 的卷/文件身份全程已证实时放宽 ctime。保持身份、文件类型、大小、mtime、字节摘要和锁,并用真实 CLI 验证两层均只有 ctime 偏差时能成功,以及实际来源变化时拒绝。 当前评审的交付边界是 CLI、宿主文本入口和持久读回。无变化的产品页面不需要首屏截图。

English verdict: REQUEST_CHANGES at 6254597. The Python lease read now tolerates Windows-style ctime skew with complete identity, but the same CLI transaction still rejects that stable lease during the mandatory TypeScript source recheck.

# Tolerate Windows ctime-only skew only with a volume and file index
# in every observation; POSIX keeps ctime in the lease fence.
stability_fields: tuple[str, ...] = ("st_mtime_ns",)
if os.name != "nt" or not all(

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.

[P2] Windows ctime 容错须通过最终的 lease 重读。 在真实 bootstrap 的前置采集及最终同源重读中,用同一稳定文件,只让路径和描述符的 ctime 视图不同。 在真实 CLI 上,前置 Python 读取与 TS 重读均只注入 ctime 差异时,当前提交仍返回 source_changed_retry;没有成功的 bootstrap 或权威快照。只注入 TS,同样失败;只注入 Python,当前提交能写入原租约的快照。 对同一条生产路径的 TS sourceLeaseBytes 使用对应平台策略;仅在 Windows 的卷/文件身份全程已证实时放宽 ctime。保持身份、文件类型、大小、mtime、字节摘要和锁,并用真实 CLI 验证两层均只有 ctime 偏差时能成功,以及实际来源变化时拒绝。

_source_lease_bytes compared st_ctime_ns unconditionally across its
path lstat / descriptor fstat / final path lstat observations, so a
Windows ctime-only skew on an unchanged lease file raised
source_changed_retry without any byte change. Apply the policy already
established for materials and cold-source backup: keep POSIX ctime in
the lease fence, and tolerate Windows ctime skew only when every
observation carries a complete volume/file identity.

The regression matrix changes exactly one metadata field at one stat
boundary: Windows ctime alone is accepted, POSIX ctime and any
dev/ino/size/mtime change is rejected on both policies, and missing
volume/file indexes fail closed to the ctime fence.

Signed-off-by: BigDataDZ <[email protected]>
The Python capture layer tolerates Windows ctime-only skew with a
complete volume/file identity, but the same CLI transaction still
rejected that stable lease in the mandatory TypeScript recheck:
sourceLeaseBytes compared ctimeMs unconditionally across its path and
descriptor observations. Apply the identical platform policy to the TS
fence so both layers of one bootstrap agree.

The regression drives a real bootstrapCoordinationRuntimeShadow in an
isolated child with node:fs/promises wrapped at the stat boundary: a
ctime-only skew on an applied bootstrap passes both the admission and
final recheck, an incomplete volume identity keeps ctime and fails
closed, and an mtime skew still rejects.

Signed-off-by: BigDataDZ <[email protected]>
@BigDataDZ
BigDataDZ force-pushed the codex/source-lease-ctime-policy-20261114 branch from 6254597 to 8b201db Compare November 14, 2026 14:59
@BigDataDZ

Copy link
Copy Markdown
Contributor Author

已修复:TS 复验层与 Python 采集层采用同一 ctime 政策

感谢评审指出端到端缺口——确认属实:Python 采集层放宽后,同一 bootstrap 的 TS sourceLeaseBytes(runtime_shadow.ts)仍无条件比较 ctimeMs,两层不一致。

新提交 8b201db8e(已 rebase 到 4604f938b):

  1. 生产:sourceLeaseBytes 的同读围栏采用与 Python 完全一致的平台政策——POSIX 保留 ctime;Windows 仅当 before/after/current 三次观察都带有非零卷/文件身份时才将 ctime 排除。dev/ino、size、mtime、字节摘要、常规文件/禁链接与锁全部保留。
  2. 回归:coordination_runtime_shadow.test.ts 在隔离子进程中用 mock.module 包装 node:fs/promises 的 stat 边界,驱动真实 bootstrapCoordinationRuntimeShadow(沿用本仓库 effect_runtime_io.test.ts 的注入惯例):
    • Windows + 仅 ctime + 完整身份 → applied,且断言两轮复验(准入+最终写入前复验)都读到同样的注入偏差(每轮 lstat×2 + handle.stat×2);
    • 卷/文件身份不完整 → ctime 保留,source_changed_retry(该分支即 POSIX 政策分支,任意真实平台都覆盖;反向平台伪造在真 Windows 上会让无关持锁方泄漏 EPERM,故不采用);
    • mtime 偏差 → 拒绝;
    • 无偏差对照 → applied。

验证(真实 Windows):TS 新场景 4/4、全文件 23 pass / 0 fail(1 个既有平台跳过)、Python 30 例矩阵 rebase 后复跑全过。

说明:评审要求的"两层同时只注入 ctime 差异"在真实 CLI 单进程里无法从外部各自注入(Python 层在宿主进程、TS 层在 Node 子进程),故以两层各自的同政策注入 + 同一真实 bootstrap 端到端(TS 层)组合覆盖;两层政策字节级一致(同一条件、同一身份守卫),不再存在单侧放宽的窗口。

@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

评审提交 8b201db8eeb6f04bbb8f15ce6064d7b493c0230d,分叉基线 4604f938b612c5da0d8a3485ce563bea922fc14e。

批准当前提交。 上一轮发现的最终重读缺口已修复。用真实 CLI 同时让 Python 和 TypeScript 读到 ctime 差异,候选成功建立影子副本,重放沿用原游标。上一轮评审

动机 / Motivation

在 Windows 上建立本地协调状态影子副本的维护者会遇到这个问题。 同一份租约内容没变时,文件路径查询和已打开文件查询的 ctime 仍可能不同。原实现把这类差异当作源已变化,要求操作者重试。 用同一份稳定租约模拟两层 ctime 差异,候选版本能建立影子副本,读回结果匹配,同一个操作重放沿用已有结果。 这次修复覆盖已有的本地影子引导及来源复核,权限与写入继续由原有配置和事务决定。

改动思路 / Approach

两个现有读取点采用同一平台条件,省去额外协议、设置和兼容状态。 这次修复覆盖已有的本地影子引导及来源复核,权限与写入继续由原有配置和事务决定。 改动落在现有 Python 采集和 TypeScript 持锁重读。两处都参与一次引导,单改单侧仍会误拒。 Python 先采集原文件和摘要,TypeScript 在既有锁下按同一规则重新读取文件并比较实际字节,复核通过才提交。

具体改动 / Changes

以 4604f938b612c5da0d8a3485ce563bea922fc14e 上的 docs/reference/reviewed-coordination-promotion.md 为准。四个文件共 +188/−5,其中生产代码 +15/−4。Windows 需要路径和描述符的观察都提供卷标识和文件索引。身份不完整时继续比较 ctime,POSIX 维持原比较。同源复核协议 · 问题 #8604

检查点 独立观察
windows_full_identity 基线拒绝。候选写下游标 1,读回 matched。重复同一请求返回 replayed。
genuine_change_remains_denied 同长度字节变化、mtime 变化、有 ctime 偏差但缺少卷号,以及真实路径被目录替换均被拒。
untouched_other_platform 普通来源两版均成功,POSIX ctime 偏差和关闭配置仍被拒。

对主干的风险 / Risks

ctime 有偏差且身份不完整时仍返回 source_changed_retry。mtime 差异和实际同长度字节变化也返回此错误。实际把目录换到租约路径也被拒绝。各拒绝样本的隔离运行目录只有原来源文件。 无偏差时基线和候选都通过。POSIX 的 ctime 差异仍被拒绝。未启用时两版在来源读取前返回 coordination_shadow_not_enabled。 Python 三组回归基线 121 项、候选 151 项通过。TypeScript shadow suite 基线 20 项、候选 24 项通过。两版经独立 CLI 对照,Ruff、DCO、diff check、RFC 双语 diff 和本地主干合并树检查通过。

语义与 CI 对齐

Windows 平台选择与 ctime 差异在 macOS 上定点注入,文件 I/O、CLI、产品代码和 File provider 为实际执行。原生 Windows 结果只参考作者自测。本轮未独立运行原生 Windows 或查询托管 CI。

我的整体评价 / Verdict

批准。完整来源恢复了原有引导与状态读回,变更来源仍会被原事务拒绝。当前影子写入后源仍是主权威。原生 Windows 的实测范围如上,合并资格走仓库现有检查。

English verdict: APPROVE at 8b201db. The real CLI now completes and replays a stable bootstrap with Windows-style ctime skew in both readers. The CLI still rejects ctime skew when volume identity is missing. It also rejects modified bytes and actual path replacement.

@loopx-agent
loopx-agent merged commit e6198ba into loopx-project:main Nov 14, 2026
29 of 44 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