R1: deterministic --sor-seed core for reproducible measurement
Adds the single stochasticity source for the SOR measurement instrument so every later stochastic decision (path selection R4, churn/selector R7, padding jitter R4) is driven by one --sor-seed and is byte-reproducible run-to-run — the R1 acceptance check and instrument-validation gate item 2. - hh/src/sor/mod.rs: SplitMix64 + SHA-256 domain-separated sub-streams + unbiased Lemire bounded sampling -> deterministic select_path/bringup. Pure bookkeeping: no forwarder, no socket, no engine. Isolated-engine containment assertions land with the R4 forwarder. - hh/src/main.rs: `sor-bringup --sor-seed` emits the deterministic circuit-build sequence as JSON (observable reproducibility). - cmd_chat/sor/config.py: bit-for-bit Python mirror so the seed means the same thing on both sides. - Tests (Rust proptest+unit, Python pytest): SplitMix64 known-answer vector, same-seed determinism, seed divergence, bounded/no-panic, and a shared cross-language parity vector asserted identically in both suites. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,58 @@
|
||||
"""R1 — deterministic seed core, and cross-language parity with the Rust mirror.
|
||||
|
||||
The parity vectors below are asserted identically in
|
||||
hh/src/sor/mod.rs::tests::parity_vector. If either language changes the
|
||||
algorithm, one of the two test suites fails — that is the reproducibility
|
||||
contract for `--sor-seed`.
|
||||
"""
|
||||
|
||||
from cmd_chat.sor.config import Domain, SorRng, Stream, bringup, select_path
|
||||
|
||||
|
||||
def test_splitmix64_known_answer():
|
||||
# Canonical SplitMix64 from initial state 0 (published first outputs).
|
||||
s = Stream(0)
|
||||
assert s.next_u64() == 0xE220A8397B1DCDAF
|
||||
assert s.next_u64() == 0x6E789E6AA1B965F4
|
||||
assert s.next_u64() == 0x06C45D188009454F
|
||||
|
||||
|
||||
def test_same_seed_identical_bringup():
|
||||
# R1 acceptance check, primitive form.
|
||||
assert bringup(0xDEADBEEF, 8, 3, 5) == bringup(0xDEADBEEF, 8, 3, 5)
|
||||
|
||||
|
||||
def test_different_seed_diverges():
|
||||
assert bringup(1, 12, 3, 4) != bringup(2, 12, 3, 4)
|
||||
|
||||
|
||||
def test_domains_decorrelated():
|
||||
rng = SorRng(42)
|
||||
assert rng.stream(Domain.PATH).next_u64() != rng.stream(Domain.CHURN).next_u64()
|
||||
|
||||
|
||||
def test_select_path_distinct_in_range():
|
||||
path = select_path(SorRng(7), 6, 3)
|
||||
assert len(path) == 3
|
||||
assert all(0 <= h < 6 for h in path)
|
||||
assert len(set(path)) == len(path)
|
||||
|
||||
|
||||
def test_next_below_in_range():
|
||||
for seed in (0, 1, 42, 0xFFFFFFFF):
|
||||
s = SorRng(seed).stream(Domain.CHURN)
|
||||
for n in (1, 2, 3, 5, 8, 1000, 999983):
|
||||
for _ in range(32):
|
||||
assert 0 <= s.next_below(n) < n
|
||||
|
||||
|
||||
def test_select_path_degenerate_hops_ge_pool():
|
||||
# hops >= pool returns a full deterministic permutation, no panic.
|
||||
path = select_path(SorRng(3), 4, 10)
|
||||
assert sorted(path) == [0, 1, 2, 3]
|
||||
|
||||
|
||||
def test_parity_vector_matches_rust():
|
||||
# These EXACT values are baked into hh/src/sor/mod.rs::tests::parity_vector.
|
||||
assert select_path(SorRng(42), 8, 3) == [5, 0, 6]
|
||||
assert bringup(123456789, 5, 3, 3) == [[4, 1, 3], [3, 2, 0], [3, 0, 4]]
|
||||
Reference in New Issue
Block a user