associativeJoinLawsHoldOverLowerSeeds

Both bracketing laws over the lower half of the seed budget, seeds 0..7.

piece must be associative over reachable, causally related states, and the two bracketings of an equal join must additionally encode to identical bytes. This is the pair convergesAcrossSeeds cannot check. That test folds a fixed set of operands in every order and asks whether they all land in the same place; a join that loses a contribution depending on bracketing still passes it, because every fold there absorbs the same operands and the loss heals as soon as the missing one arrives. Associativity is the stronger statement that b ⊔ c delivered as one digest is interchangeable with b then c — the statement anti-entropy, delta batching, and state hashing all rest on. The byte law is the one #1955's root-hash gate rests on.

Which law broke is named by the failure, not by the test. The two messages LatticeLawHarness.runAssociativeLaws raises are deliberately different: NOT EQUAL for an associativity defect, EQUAL but encode to DIFFERENT bytes for a canonicality one. They stopped being separate test methods when the two passes were folded into one — running them apart meant computing every join twice, for 18% of this module's Kotlin/Native budget. What the two entry points still buy is a bounded seed window: a red here has its cause in 0..7, and one in associativeJoinLawsHoldOverUpperSeeds in 8..15, which halves the range a repro has to sweep.

See LatticeLawHarness.runAssociativeLaws.