# Merkle-Batched Journal Commitment — frozen local wire

25 October 2026. Parent approved the opaque-byte boundary and two APIs before implementation. This file fixes exact fields and outcomes. Applies D30-F05-A05 and its downstream adapter.

## Mathematical and trust boundary

Use SHA-256 with RFC 9162 sections 2.1.1/2.1.3 construction: empty tree H(empty), leaf H(00 || entry bytes), branch H(01 || left32 || right32), split at largest power of two strictly less than n. No duplicate-last-leaf rule. Paths list bottom-up sibling digests; orientation comes from index and exact tree size, never a caller-provided left/right flag.

The public core accepts **opaque byte strings**, represented as canonical lowercase even-length hex. It does not parse G framing, validate journals, authenticate scope, infer source completeness or prove correct economic recognition. Empty byte entries are mathematically allowed and deliberately distinguished from an empty tree; an empty entry is not a valid D30 committed group. The Hash adapter must supply validated canonical whole-group G bytes under INTEGRITY-BYTE-CONTRACT.md and bind their semantic metadata. One correction group is one leaf, despite containing two journals.

The root alone commits byte order, not the metadata supplied beside these opaque bytes. Trusted metadata matching is verifier policy; its semantic relationship to G is a host adapter obligation. Repeated identical byte entries remain distinct positions and are allowed; only the source validator decides whether a repeated financial group is invalid.

No signatures, hash-chain proof, consistency proof, external anchor, current-tail completeness or regulatory opinion is implemented here. A full-prefix checkpoint requires start_group_index=1 and actual complete source coverage from the Hash adapter. A caller's label cannot establish that coverage. A batch may instead declare any bounded contiguous group-index interval.

## Portable types and resource limits

All objects are plain exact-field JSON objects; missing/unknown fields reject. IDs use Stage 0 ASCII [A-Za-z0-9][A-Za-z0-9._:-]{0,63}. All integers cross the wire as canonical strings (0 or nonzero digit followed by digits), never signs, leading zeros, booleans, JSON numbers, exponent or decimal notation. Digests are exactly 64 lowercase hex characters.

0..4096 ordered entries; each 0..16384 bytes. Hex is bounded before decoding. This accommodates the selected full two-journal correction framing with 32 legs per journal and 240-scalar reasons. The tree is bounded to the declared source reference interval 1..4096: start>=1 and start+n-1<=4096 when n>0. These are portable reference resource caps, not a ledger or throughput limit. Proof paths have 0..12 hashes and must have the exact shape-derived length. Input values and nested arrays are never mutated or returned by alias.

## Build API

Python: build_merkle_commitment(request).
TypeScript: await buildMerkleCommitment(request), using native crypto.subtle.digest; Python uses hashlib.sha256. No custom SHA implementation or sibling imports.

Request exact fields:

schema_version="d30-merkle-build-v1", batch_id, stream_id, ledger_id, entity_id, start_group_index, entries_hex, proof_leaf_index.

proof_leaf_index is null or a canonical zero-based string 0<=index<n. Empty batch requires null. start_group_index is canonical 1..4096 even when n=0.

Always exact output fields: status,code,commitment,proof,trace.

Valid status ok / MERKLE_COMMITMENT_BUILT. This is local calculation, not an independent assurance pass. commitment exact:

schema_version="d30-merkle-root-v1", batch_id,stream_id,ledger_id,entity_id,start_group_index,tree_size,root_hex.

proof is null when none requested, otherwise exact:

schema_version="d30-merkle-inclusion-v1", batch_id,stream_id,ledger_id,entity_id,start_group_index,tree_size,leaf_index,entry_hex,path.

tree_size n, paths as above. Global group index = start_group_index + zero-based leaf_index. No journal-sequence value is invented. trace is deterministic postorder (left subtree, right subtree, parent), each exact start_leaf_index,leaf_count,kind,hash_hex,left_count,right_count. kind leaf/branch/empty. Counts strings; left_count/right_count null on leaf and empty; empty trace has one empty node at start0/count0. A 3-leaf tree has 3 leaves and 2 branches.

Rejected / INVALID_BUILD_SCHEMA, INVALID_SCOPE, INVALID_GROUP_RANGE, INVALID_ENTRIES, INVALID_ENTRY_HEX, INVALID_PROOF_INDEX. Validate in that fixed order, before hashing. Empty inclusion request rejects INVALID_PROOF_INDEX. Rejected output commitment/proof null, trace[].

Unavailable native digest yields indeterminate / CRYPTO_UNAVAILABLE, commitment/proof null, trace[]. Never claim pass on unavailable crypto.

## Inclusion verification API

Python verify_merkle_inclusion(proof,trusted_commitment).
TypeScript await verifyMerkleInclusion(proof,trustedCommitment).

Proof exact shape above. 1<=tree_size<=4096, index in range, interval in range, entry hex bounded, digest path strict, exact expected path length. trusted_commitment is null or the exact root object shape above (size0 permitted as a root object, though it cannot match a valid inclusion). No embedded proof root is trusted.

Always exact output status,code,scope,proof,verification,trace.

For rejected inputs scope/proof/verification null, trace[]. Fixed precedence: INVALID_PROOF_SCHEMA, INVALID_SCOPE, INVALID_GROUP_RANGE, INVALID_PROOF_INDEX, INVALID_ENTRY_HEX, INVALID_PROOF_PATH, INVALID_PROOF_LENGTH, INVALID_TRUSTED_COMMITMENT. Surplus/short paths reject even without trust. Invalid trusted object rejects rather than being ignored.

For valid inputs scope exact batch_id,stream_id,ledger_id,entity_id,start_group_index. proof is a detached exact copy.

verification exact computed_root_hex,expected_root_hex,leaf_hash_hex,tree_size,leaf_index,group_index,path_length,byte_scope_only,mismatched_fields. All counts strings; byte_scope_only true. expected_root_hex null without a supplied trusted root. mismatched_fields is ASCII-sorted list of differing metadata keys, excluding schema_version and root_hex.

trace exact step,sibling_side,sibling_hash_hex,input_hash_hex,output_hash_hex. step string starting1, sibling_side left/right.

With complete valid inputs:

1. Any metadata mismatch (batch_id,stream_id,ledger_id,entity_id,start_group_index,tree_size) => fail / TRUSTED_COMMITMENT_MISMATCH, preserving computed path diagnostics.
2. Otherwise computed root unequal trusted root => fail / MERKLE_ROOT_MISMATCH.
3. Otherwise pass / MERKLE_INCLUSION_VERIFIED.
4. Null trusted root => indeterminate / MISSING_TRUSTED_COMMITMENT, preserving locally computed root, never a pass.

Known inclusion failure is not downgraded because separate source, signer or external-anchor assurance is unavailable; those are independent downstream layers. Native hash unavailability yields indeterminate / CRYPTO_UNAVAILABLE with no partial output. Empty tree has no inclusion proof; a one-leaf tree has an empty path.

## Independent expectations before core

Pin the parent-authored INDEPENDENT-BYTE-HASH-VECTORS.json byte-for-byte. The 3-leaf full-G root is 34ad17bffd657e7227adb7083eb1be5d0a7f9bcd5ba6b404313dce2242393192. Leaf index2 has one sibling: the root of the first two leaves, f7166ff638fdfea0e5b73405e6f4bf58b7d76a0c7199aa84a453d18eeb7d3cd6. Parent vectors import no Stage6 core.

Additional authored fixtures use an independent iterative stack root and bottom-up range decomposition to materialize roots, paths, node traces and full verification expectations before the production recursion. Include empty versus one empty entry, 1/2/3/4/5/7/8/9 leaves, every position, repeated bytes, bounded non-prefix range, canonical G leaf byte mutations, normalization/leg-order differences, wrong index/size/root/scope, reversed/modified/short/surplus paths, missing trusted root and strict shape/type/hex limits. Actual financial adapter integration remains parent-owned.
