Skip to content

[Bug]: the Turn contract digest covers checkout bytes, so Windows CRLF checkouts fail the guard and the suggested regeneration breaks LF checkouts #6198

Closed

Description

@JasonBuildAI

Preflight

  • I searched existing issues and discussions for this behavior.

  • This report is not an unpatched security vulnerability.

  • The report and attachments contain no credentials or private/internal material.

  • Issue origin: Observed or reproduced in a real environment

  • LoopX version or commit: 5fb256a4d (latest main at report time, LoopX 1.4.0)

  • Host or runtime surface: Not host-specific; reproduced on native Windows 11 with Git for Windows' default core.autocrlf=true

  • LoopX area: Control plane (goals, todos, quota, scheduler, registry, runtime)

Problem

scripts/generate_turn_contract.py records the contract provenance digest by hashing the physical checkout bytes of loopx/control_plane/turn_loop_controller_contract_v0.json:

digest = hashlib.sha256(CONTRACT_PATH.read_bytes()).hexdigest()

Git for Windows checks that JSON out with CRLF by default (core.autocrlf=true), while the committed artifacts carry the digest of the LF bytes Git stores. On a Windows checkout the guard therefore reports a stale artifact even though the revision is unmodified:

  • examples/semantic-vocabulary-drift-smoke.py fails with stale generated Turn contract: loopx\control_plane\turn_driver\turn_contract_generated.py; run scripts/generate_turn_contract.py;
  • tests/architecture/test_turn_contract_generation.py (2 rows) and tests/architecture/test_semantic_vocabulary_drift.py fail on the same message;
  • loopx canary premerge --from-git-diff selects that smoke as a default architecture check for any diff under loopx/, so the pre-push gate documented in CONTRIBUTING.md fails on Windows.

The printed repair makes the damage worse: running uv run --extra test python scripts/generate_turn_contract.py on Windows rewrites both committed artifacts with the CRLF digest. That content then fails the same freshness guard on an LF checkout, so a Windows contributor who follows the error message commits a generated artifact that breaks the required Linux checks.

Minimal reproduction

git clone https://github.com/loopx-project/loopx
cd loopx
git config --get core.autocrlf   # true on a default Git for Windows install
uv sync --extra test
uv run --extra test python examples/semantic-vocabulary-drift-smoke.py
uv run --extra test python -m pytest -q tests/architecture/test_turn_contract_generation.py

Expected behavior

The provenance digest covers the canonical LF content of the contract, so one revision records the same digest on every checkout: the smoke and the architecture tests pass on a CRLF checkout, and running the generator there reports up to date without touching the committed artifacts.

Actual behavior

The smoke fails with the stale-contract message, and the suggested regeneration changes the recorded digest from the LF value to the CRLF value:

-SHA256: ab92e3888d83977b2b4548d924a7ff800f7fd1f6388f347772a8c3d6211fa3d8"""
+SHA256: 20bfb14e763ed0f4f3ce4183c35362fe9727ac436c6be7bad8b126dece5e9010"""

Sanitized diagnostics

$ uv run --extra test python examples/semantic-vocabulary-drift-smoke.py
semantic-vocabulary-drift-smoke: FAIL
  stale generated Turn contract: loopx\control_plane\turn_driver\turn_contract_generated.py; run scripts/generate_turn_contract.py

$ uv run --extra test python -m pytest -q tests/architecture/test_turn_contract_generation.py
FAILED tests/architecture/test_turn_contract_generation.py::test_controller_and_projection_witnesses_cover_live_dispositions
FAILED tests/architecture/test_turn_contract_generation.py::test_new_independent_twin_cannot_hide_behind_generated_pair
2 failed, 20 passed

Additional context

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions