Skip to content

Fix lockfile metadata serialization order#29

Merged
laulauland merged 4 commits intomainfrom
lau/lockfile-merge-resistant
May 5, 2026
Merged

Fix lockfile metadata serialization order#29
laulauland merged 4 commits intomainfrom
lau/lockfile-merge-resistant

Conversation

@laulauland
Copy link
Copy Markdown
Member

@laulauland laulauland commented Apr 21, 2026

Summary

Fixes a lockfile serialization bug where equivalent metadata could be written in different field orders depending on how the binding was built. That made semantically identical lockfile states produce different bytes, which in turn created avoidable textual merge conflicts.

The fix canonicalizes metadata field order during serialization and adds a property test that reproduces the class of bug: two lockfiles with the same bindings and fields, but different binding/field iteration order, must serialize to byte-identical output.

What changed

  1. Adopt minish for property-based tests — test-only dependency, with deterministic seed support via -Dminish-seed=<u64>.
  2. Canonicalize lockfile metadata on writeserialize now emits metadata fields sorted by key.
  3. Add regression propertyserialize is invariant under binding and field reorder.

The broader merge-resistance measurements and format experiments have been split out of this PR and kept for follow-up work.

Test plan

  • zig build test
  • Reverted the serialization fix locally and confirmed the new property catches the bug

Wire minish v0.3.0 into the build as a test-only module. Seed defaults to
the first 16 hex chars of the current git HEAD so each commit explores a
fresh slice of the state space; override with -Dminish-seed=<u64> to
reproduce a specific failing run. Plumbed through helpers.minish_seed so
property tests share one source of truth.

Smoke test runs 25 iterations of a trivial int property to catch
regressions in the wiring (import, module shape, seed plumbing).

DRIFT-mshkigwb
Bindings were serialized with metadata fields in insertion order (per
`Binding.setField` at lockfile.zig:33-45), which meant two branches that
converged on the same semantic state through different `setField` sequences
produced different byte strings for the same binding — and git/jj flagged
those as spurious textual merge conflicts.

Sort metadata by key in `renderLineToWriter` so the on-disk form is a
function of semantic state only. The sort buffer reuses the `scratch`
allocator that `serializeToWriter` already threads through, so no new
lifetime surface.

Unblocks the upcoming serialization-under-reorder property test.

DRIFT-hoocxemw
…ld reorder

Property: semantic_eq(L1, L2) ⟹ serialize(L1) == serialize(L2). The
generator builds a lockfile state with up to 6 bindings, each with 0–6
metadata fields drawn uniquely from a fixed key pool; the property builds
two `[]Binding` slices from that state (forward order, forward fields
vs. reverse order, reverse fields) and asserts byte-equal serialize
outputs. Runs 200 iterations, seeded from helpers.minish_seed (git HEAD
by default; -Dminish-seed=<u64> to reproduce).

Verified that the test catches the insertion-order bug by temporarily
reverting the canonicalization fix — property fails with a counterexample
showing differing serializations within the first few iterations.

DRIFT-zvfkxuyr
@laulauland laulauland force-pushed the lau/lockfile-merge-resistant branch from 28c5aee to db2eca1 Compare May 5, 2026 17:53
@laulauland laulauland changed the title Make drift.lock format merge-conflict resistant (canonicalize + property tests) Fix lockfile metadata serialization order May 5, 2026
@laulauland laulauland merged commit b203586 into main May 5, 2026
5 checks passed
@laulauland laulauland deleted the lau/lockfile-merge-resistant branch May 5, 2026 18:06
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.

1 participant