Skip to content

feat(merkle): freeze MerkleNode bytes with cross-language conformance vectors - #113

Merged
hartsock merged 15 commits into
mainfrom
feat/merkle-frozen-bytes
Oct 15, 2026
Merged

hartsock merged 15 commits into
mainfrom
feat/merkle-frozen-bytes

Conversation

@hartsock

Copy link
Copy Markdown
Owner

Summary

Freezes MerkleNode's serialized bytes.

  • Five conformance vectors (tests/merkle_vectors.json) pin the exact dag-cbor bytes and CID: no parents, one parent, several parents in shuffled order, duplicate parents, a nested payload.
  • Rust (tests/merkle_conformance.rs) and Python (tests/test_content_addressable.py) both reproduce every vector. Python can't encode links, so it builds the node independently and checks byte parity, which makes it a second implementation rather than a copy.
  • MerkleNode is now part of the default surface. unstable-merkle stays as a documented no-op alias, so existing manifests still resolve. unstable-store is unchanged.
  • STABILITY, README, CHANGELOG (Unreleased) and the module docs say the bytes are frozen. The pre-push hook, CI and justfile comments no longer say the feature stays off.

No change to ContentId, the canonical encoder or the dir-tree code. No version bump.

Fixes #112

Test plan

  • A layout change (renaming payload) fails the Rust gate, and fails the Python gate even when the vectors are regenerated from the altered Rust. Reverted after.
  • Pre-push hook: tests, clippy --all-features -D warnings, rustdoc, fmt.
  • CI green, including the Python job.

🤖 Generated with Claude Code

hartsock and others added 5 commits October 15, 2026 16:40
WHAT: five vectors (no parents, one, shuffled, duplicate, nested payload), each
pinning dag-cbor hex and CID string.
WHY: freezes the node layout (#112).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
WHAT: Rust rebuilds each vector via MerkleNode; Python rebuilds the bytes by
hand (it cannot encode links) and checks bytes, CID and the checked decoder.
WHY: a layout change must fail CI in both languages (#112).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
WHAT: ungate merkle module and re-export; unstable-merkle stays as a
deprecated no-op alias; module docs say frozen instead of non-frozen.
WHY: bytes are now pinned by cross-language vectors (#112).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
WHAT: pre-push, ci.yml and justfile name what each pass proves now that
MerkleNode is default; python parity gate named CI-only in the hook.
WHY: hook-parity rule after the feature flag moved (#112).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@hartsock hartsock added enhancement New feature or request risk:high High-risk change per CLAUDE.md PR workflow (byte/wire/CI/build) merkle-catalog Merkle structure catalog epic work labels Oct 15, 2026
hartsock and others added 10 commits October 15, 2026 16:46
…erkle

MerkleNode is on by default now, so "same posture as unstable-merkle"
wrongly suggested it was still opt-in.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
WHAT: exact node map, key order, parent sort/dedup rule, tag-42 link form, CID
derivation, payload rule, frozen scope and a who-rejects-what table.
WHY: an implementer should not need src/merkle.rs (#112 hardening).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
WHAT: proptest permutation/duplication invariance against a spec-derived
encoder, decode/re-encode identity, blake3 pin of merkle_vectors.json, and
adversarial inputs through the checked decoder with decoder-vs-verify split.
WHY: golden vectors alone cannot show order independence or refusals.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
WHY: regenerating expected output must not be a quiet way to accept a layout
change; MERKLE_VECTORS_REGENERATE=1 is required.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… the spec

WHAT: envelope frozen, payload frozen only inside the canonical data model;
STABILITY and module docs link the wire spec; Python gate cites it, not Rust.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… parents)

WHAT: append two vectors; the first five are byte-identical. The spec encoder
and the Python gate now implement the minimal array-header rule; the file's
BLAKE3 pin is updated consciously in this commit.
WHY: MERKLE_WIRE.md §2 specified those headers but no vector exercised them.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…ctor count

WHAT: MERKLE_WIRE.md §2 states 0x80+n/0x98/0x99/0x9a/0x9b; Rust spec encoder
and Python helper implement them with boundary tests (23..2^32) and a real
65536-parent node compared to production. §4/§6 scope the 36-byte profile to
parents and add a payload-link (raw/SHA-256) counterexample. 'five' -> 'seven'.
WHY: PR review round 4 P2s. No vector or production encoding changed.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…in the payload

Records why plain decoding may normalize parent order and repeats: a node's
identity is always the CID of its canonical form. Parents cannot cycle, so a
repeated parent carries nothing; back-references belong in the payload as
links traversal does not follow.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Existence, node type and acyclicity of parents are application invariants;
traversals keep a visited set. A payload CID is hashed like a parent, so
cycle-closing references are stored as non-content links (e.g. a symlink's
target path) that parent-edge traversal does not follow.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Removing a Cargo feature breaks manifests that list it, so the no-op alias
may only go in a breaking release, and that promise is separate from the
frozen MerkleNode API and bytes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@hartsock
hartsock merged commit 44732f0 into main Oct 15, 2026
8 checks passed
@hartsock
hartsock deleted the feat/merkle-frozen-bytes branch October 15, 2026 21:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request merkle-catalog Merkle structure catalog epic work risk:high High-risk change per CLAUDE.md PR workflow (byte/wire/CI/build)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(merkle): freeze MerkleNode bytes with cross-language conformance vectors

1 participant