Repository navigation
fix(turn-contract): hash canonical LF bytes so the provenance guard is checkout-independent - #6200
Conversation
A Windows checkout with core.autocrlf=true keeps CRLF in the contract JSON, so the generator recorded a checkout-specific digest and the freshness guard reported the committed artifacts as stale; the suggested regeneration then wrote that CRLF digest into the artifacts, which fails on an LF checkout and would break the required Linux checks if committed. Hash the contract's canonical LF bytes, matching the existing pattern in loopx/presentation/chat_bundle.py::source_digest, and pin the behavior with a regression test that regenerates from a CRLF copy of the contract. Signed-off-by: JasonBuildAI <jasonbuildai@gmail.com>
…ject#6200 for agent-x7m3 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; xhigh
结论:APPROVE,精确 head e604dd12ac05ddb66ecb2770430afdd638a7c357。未发现阻断项。原生 Windows/Git 安装环境及长期耗时未在本轮测量;本轮在 macOS 用实际 CRLF checkout 文件运行完整生产检查路径。
动机
受影响的是 Windows/Git CRLF contributors and maintainers reviewing generated-artifact changes。同一版本的 JSON 被 Git 检出为 CRLF 后,干净 checkout 被报为 stale;照提示运行生成器又重写两份 committed provenance,产生跨平台往返修复。 本轮实际结果:同一 LF/CRLF 输入经真实 generator --check 和 semantic smoke:base 在 CRLF 失败,head 通过;照打印提示修复时 base 改两份 artifact,head 不改文件;真实内容编辑或 artifact 损坏仍拒绝。 仅修复文本 provenance 的跨 checkout 稳定性;不修改 Turn 决策、运行时 quota/scheduler、权限或现有生成文件。
这不是 LF 单元测试失败,而是干净 CRLF checkout 被误判为 stale:贡献者照打印提示修复后,两份生成文件又写入平台特有 digest,随后 LF checkout 再报错。验收来源:https://github.com/loopx-project/loopx/issues/6198,`spec_revision: 2026-10-11T07:10:27Z;body-sha256:fc85ea6a6300624c066084bffdf1a71bda9391e8bf175846f8ed81c55c6e197c,criterion_id Expected behavior`。要求是同一文本 revision 在 LF/CRLF 下使用同一 provenance,并且无需修复或改动 committed artifact;真实契约改动仍由现有 freshness guard 识别。
改动思路
在既有 Turn artifact 生成 owner 内把文本 CRLF 归一化为 LF;无需新的 capability、模式、平台目录或共享哈希框架。 仅修复文本 provenance 的跨 checkout 稳定性;不修改 Turn 决策、运行时 quota/scheduler、权限或现有生成文件。 检索了既有 generator、完整 artifact 验证调用、chat_bundle.source_digest 和 .gitattributes。bundle 与 Turn 的资源/消费者不同,复用同一归一化原则即可;把两者合成平台或哈希框架会增加本次无须承担的依赖。未来变更成本检查:现有 owner 足够,未发现需要追加的同域重构。
具体改动
完整两文件 +17/-1 都已核验。build_artifacts() 在 SHA256 前只把物理 CRLF 转 LF;read_contract() 的解析/校验、Python/TypeScript 生成结构、verified_generated_paths() 的全文 freshness 判断及 main() 的 check/显式修复分支保持。一个聚焦测试比较实际 CRLF 输入与 committed artifact,没有新增运行时 vocabulary、状态、CLI 模式或 capability。
独立实测:三组架构/控制器 suite,head 321 passed、不可变 base 8d5a6fc170422ff4ff78818898b172064b1e1591 320 passed。同一个真实文件 harness 经实际 generator 和 semantic smoke:LF 两边成功;未修改 CRLF 在 base 都退出1、head 都退出0。执行错误消息提示的真实修复命令,base 改两份 artifact,head 改零份。实际有效 reason 字符串编辑,两边 generator/semantic 都退出1;污染生成文件,两边 generator 都退出1,最后原始字节恢复、两棵 tracked checkout 干净。该旧版本反例经过公共 caller,避免仅证明新的哈希表达式自洽。
语义 advisory 先运行(无 supported 新集合候选,不把零报告当语义证明),随后当前原生 premerge 4 direct + 2 selected 全通过、0失败/警告/manual hold,完整 semantic 和 maintainability guard 都执行;覆盖两个改动路径。未查询或等待 GitHub CI。
对主干的风险
有意行为变化只有 CRLF 文本不再触发假 stale;LF artifact 与 SHA256 格式保持。JSON 中转义的换行字符不是物理 CRLF,真实内容和 schema 校验没有被绕开。--check 仍不写文件,显式生成器仍只修复真正 stale 的 artifact;不赋予运行、调度、权限或自动修复权。无 opt-in/default-off 声明,无新 frontend/Lark 配置旅程。未修改可影响 Agent 判断的义务文本,错误和恢复指令保持原 owner。
我的整体评价
这个有界变更对体验和维护效率是正向的:实际消除了错误修复及两份 artifact 的跨平台往返,同时保留对真实 stale 的拒绝。依据是同输入 base/head 完整 CLI 和副作用对照,不能由测试数量推断跨 Goal、不同领域 Agent 的长期净收益百分比。在既有 Turn artifact 生成 owner 内把文本 CRLF 归一化为 LF;无需新的 capability、模式、平台目录或共享哈希框架。 仅修复文本 provenance 的跨 checkout 稳定性;不修改 Turn 决策、运行时 quota/scheduler、权限或现有生成文件。 没有必要为它追加状态、provider 或新能力。无阻断项;合并资格与仓库维护者的合并操作独立。
English review
English verdict: APPROVE
APPROVE at e604dd12ac05ddb66ecb2770430afdd638a7c357; no blocking finding. Specification: #6198, revision 2026-10-11T07:10:27Z;body-sha256:fc85ea6a6300624c066084bffdf1a71bda9391e8bf175846f8ed81c55c6e197c, criterion Expected behavior.
The old clean CRLF checkout falsely fails the real generator/semantic guard, and the suggested repair dirties both generated provenance files. This two-file fix canonicalizes physical CRLF to LF inside the existing artifact-generation owner, retaining schema validation, complete generated-text verification, interfaces and LF artifact bytes. The existing chat-bundle digest uses the same principle for different resources; no shared platform framework or new capability is needed.
Independent source suites: head321/base320 passed. Identical real-filesystem probes at immutable base 8d5a6fc170422ff4ff78818898b172064b1e1591 and this head show LF success at both; unchanged CRLF generator/semantic exits1 at base and0 at head; explicit repair changes2 artifacts at base and0 at head. A real contract reason edit still makes both public checks fail, and corrupted generated content still fails verification. All original bytes were restored. Current vocabulary advisory precedes native premerge:4direct+2selected passed, zero failures/warnings/manual holds, including complete semantic and maintainability guards. No GitHub CI consulted.
The bounded experience and maintenance effect is positive because false repair and cross-platform artifact churn disappear without hiding real changes. No runtime Goal/lease/quota authority or optional activation contract changes. Native Windows OS/Git installation and long-run saved time are unmeasured; actual CRLF bytes and real production checks ran on macOS. A broader refactor would add cost without a required consumer. Merge readiness remains a separate maintainer decision.
Goal And Delivered Outcome
core.autocrlf=true, the Turn contract freshness guard fails on an unmodified revision, and the repair it prints writes a checkout-specific digest into the committed artifacts.scripts/generate_turn_contract.pyhashed the physical checkout bytes ofloopx/control_plane/turn_loop_controller_contract_v0.json, while Git stores that file with LF and checks it out with CRLF on Windows, so the recorded provenance digest differed per checkout.verified_generated_paths(), the semantic drift smoke, andloopx canary premerge --from-git-diffthen reportedstale generated Turn contracton a clean revision.examples/semantic-vocabulary-drift-smoke.pyprintedFAIL / stale generated Turn contract ...before andsemantic-vocabulary-drift-smoke: okafter (rowreal_entrypoint);tests/architecture/test_turn_contract_generation.pywent from 2 failed / 20 passed to 23 passed (rowunit); running the generator on a CRLF checkout now reportsup to dateand leaves the committed artifacts untouched.main.Author Declaration
AI disclosure: this change was drafted with AI assistance at my request, under my direction and review.
Implemented against
8d5a6fc17:scripts/generate_turn_contract.pymust detect stale generated Turn artifacts from the contract's content;loopx/presentation/chat_bundle.py::source_digeststates the repository's canonical-bytes rule for tracked text inputs ("Git may check text out with CRLF on Windows; binary assets remain exact");.gitattributestreatscore.autocrlf=trueas a supported checkout mode.build_artifacts()inscripts/generate_turn_contract.pytests/architecture/test_turn_contract_generation.py::test_contract_digest_ignores_checkout_line_endingsuv run --extra test python scripts/generate_turn_contract.py→up to date;git diffcleanreasonstring → digestbb2a296c...≠ committedab92e388...turn_contract_generated.py/.tsgit diff(no artifact change); required Linux checks on this PRchat_bundleprecedent before changing anything; reproduced the failure on two native Windows checkouts (base5fb256a4d, rebased head on8d5a6fc17), then re-ran the same commands at the rebased head. Deliberately not fixed here:tests/architecture/test_semantic_production.pyreadsloopx/semantics/vocabulary_v0.jsonwith the locale codec (UnicodeDecodeErroron this host), andpython -m mypyreports a Windows-stubos.WNOHANGerror inloopx/control_plane/effect_runtime.py; neither file is in this diff.Scope And Continuation
Validation
e604dd12a(rebased onto8d5a6fc17); the before/after comparison also ran at52040f653on base5fb256a4d.unitpasseduv run --extra test python -m pytest -q tests/architecture/test_turn_contract_generation.py→ 23 passed. Before the change on the same checkout: 2 failed, 20 passed, both withstale generated Turn contract.unitpassed... tests/architecture/test_semantic_vocabulary_drift.py→ 126 passed; before: 1 failed with the same message.real_entrypointpasseduv run --extra test python examples/semantic-vocabulary-drift-smoke.py→semantic-vocabulary-drift-smoke: ok; before:semantic-vocabulary-drift-smoke: FAIL+stale generated Turn contract: loopx\control_plane\turn_driver\turn_contract_generated.py; run scripts/generate_turn_contract.py.regression_paritypassedtest_contract_digest_ignores_checkout_line_endingsfails against the pre-fix generator (regenerated artifact differs) and passes with the fix; the mutation probe shows a content edit still moves the digest fromab92e388...tobb2a296c....manualpasseduv run --extra test loopx canary premerge --from-git-diff --git-diff-base upstream/main→merge_gate_passed: true,manual_holds: 0; catalog checkscontrol-plane-maintainability-ratchet-smokeandsemantic-vocabulary-drift-smokepassed; diff hygiene andpy_compilepassed.unitpassed... tests/test_loop_turn_controller_contract.py→ 172 passed (controller behavior unchanged).staticpasseduv run --extra test python -m ruff check scripts/generate_turn_contract.py tests/architecture/test_turn_contract_generation.py→ All checks passed.manualpasseduv run --extra test loopx check --scan-path scripts/generate_turn_contract.py --scan-path tests/architecture/test_turn_contract_generation.py→public boundary scan clean: 2 files, exit 0 (isolated state dir).staticfaileduv run --extra test python -m mypy→ 1 error,loopx/control_plane/effect_runtime.py:432: Module has no attribute "WNOHANG", a pre-existing Windows-stub issue. That file is not in this diff, and mypy's configuredfileslist contains neither changed file.windows-powershelllane does not run these architecture files. Effect-runtime-backed rows ran with Node.js 24.19.0 (declared minimum 22.22.3). Pre-existing, untouched Windows-only failures:tests/architecture/test_semantic_production.py(locale-codec read) and the mypy row above.Frontend / Visual Evidence
Type of Change
LoopX Area
Technical Direction
Shared-authority RFC fixture impact
uv run python scripts/generate_semantic_inventory.py --changed-from upstream/main→No supported new vocabulary carriers were detected.Boundary Checklist
.loopx/,.codex/goals/, and liveACTIVE_GOAL_STATE.md).none.Signed-off-bytrailer (git commit -s).