RQ2-P3: bridge-federated-pool mechanism instrument + synthetic tests + calibration gate

Build the funnelling-mechanism instrument for the RQ2-P3 follow-up: a new
`bridge-federated-pool` topology drawing each circuit's bridge from a finite
willing-bridge pool (size B) under Zipf willingness skew (alpha), so bridge
concentration genuinely varies as a manipulated IV. Defensive measurement only:
synthetic seeds, no engine, no traffic, no confirmatory record read.

- assembler.py: add `bridge-federated-pool` branch + zipf_weights/weighted_draw
  helpers; existing lead `bridge-federated` branch left untouched and
  bit-reproducible (fresh bridge per seed).
- battery.py: enumerate_rq2p3_cells() = B{2,4,8} x alpha{0,1,2} + anchor B=50,
  alpha=0 (separate fn; frozen 6-cell lead lattice unmutated).
- analysis/rq2p3_calibration.py: DRY synthetic-only §7 calibration gate; no
  confirmatory data read. confirm_load_rq2.py/confirm.py/stats.py unchanged.
- tests/test_sor_rq2p3_pool.py: 9 synthetic tests (helpers + pool reuse/variance
  + lead branch reproducibility).

Calibration gate RAN: items 3 (monotonicity) & 4 (entropy) PASS; items 1 & 2 do
NOT match their naive-funnel wording because the ratified posterior yields a MIX
(rho>0 across the whole sweep incl anchor rho=+0.838; B=1 -> H=2.54 bits HIGH) --
the note-unique-bridge-artifact.md prediction, surfaced early. HARD HOLD: no
confirmatory battery, no prereg freeze; NEEDS-OPERATOR banner raised for the §7
item-1/2 re-word decision. Lead prereg SHA f22331a72e... untouched; worktree-only.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
leetcrypt
2026-07-21 18:38:55 -07:00
parent 1c6063248f
commit 1d99e7f9a9
5 changed files with 384 additions and 1 deletions
+52 -1
View File
@@ -132,6 +132,36 @@ def _bridge_label(seed: int) -> str:
return f"bridge#{hashlib.sha256(f'sor-bridge|{seed}'.encode()).hexdigest()[:8]}"
def zipf_weights(pool_size: int, alpha: float) -> List[float]:
"""Deterministic Zipf willingness weights over a finite willing-bridge POOL of
``pool_size`` bridges: rank ``r`` (1..B) gets mass ∝ ``1/r**alpha``, normalized to
sum 1. ``alpha = 0`` → uniform willingness; larger ``alpha`` → heavier skew toward
the most-willing bridges (higher realized concentration). Pure function of
``(pool_size, alpha)`` — both cell-level factors — so the willingness profile is
fixed within a run (RQ2-P3 mechanism prereg §3)."""
if pool_size < 1:
raise ValueError(f"pool_size must be >= 1, got {pool_size}")
raw = [1.0 / (r ** alpha) for r in range(1, pool_size + 1)]
total = sum(raw)
return [w / total for w in raw]
def weighted_draw(digest_hex: str, weights: List[float]) -> int:
"""Deterministic index in ``[0, len(weights))`` by CDF inversion of a SHA-256
hex digest: the first 8 bytes map to ``u ∈ [0, 1)`` and we return the first bin
whose cumulative weight exceeds ``u``. Deterministic from ``digest_hex`` (a
per-circuit hash), so a circuit's willing bridge is reproducible from its seed."""
if not weights:
raise ValueError("weights must be non-empty")
u = int(digest_hex[:16], 16) / float(1 << 64)
cum = 0.0
for i, w in enumerate(weights):
cum += w
if u < cum:
return i
return len(weights) - 1
def _house_nodes(seed: int, pool: int, take: int, house: str, salt: int = 0) -> List[str]:
idx = select_path(SorRng(seed ^ salt), pool, take)
return [f"{house}#{i}" for i in idx]
@@ -201,6 +231,27 @@ def assemble(
]
houses = (HOUSES[0], HOUSES[1])
bridge_present = True
elif topo == "bridge-federated-pool":
# RQ2-P3 mechanism instrument (rq2p3-mechanism-prereg.md §3): identical
# to bridge-federated EXCEPT the willing bridge is drawn from a FINITE
# shared pool of size B under a fixed Zipf willingness (skew alpha), so
# circuits genuinely REUSE bridges and top-3 concentration varies. The
# existing bridge-federated branch above is left untouched (lead cells
# stay bit-reproducible).
pool_b = int(cell.factors["pool_B"])
pool_alpha = float(cell.factors["pool_alpha"])
a = _house_nodes(seed, house_size, 1, HOUSES[0], salt=0)[0]
b = _house_nodes(seed, house_size, 1, HOUSES[1], salt=0xB)[0]
weights = zipf_weights(pool_b, pool_alpha)
digest = hashlib.sha256(f"sor-bridge-pool|{seed}".encode()).hexdigest()
idx = weighted_draw(digest, weights)
hop_specs = [
HopSpec(0, "entry", HOUSES[0], a, False),
HopSpec(1, "bridge", "bridge", f"bridge#{idx:02d}", True),
HopSpec(2, "exit", HOUSES[1], b, False),
]
houses = (HOUSES[0], HOUSES[1])
bridge_present = True
elif topo == "directory-federated":
directory = _build_directory(seed, house_size, n_houses)
path = directory.select_federated_path(seed, hops=hops, min_houses=2)
@@ -217,7 +268,7 @@ def assemble(
matched_n=matched_n, houses=houses, hops=tuple(hop_specs),
)
# Federated cells must genuinely span ≥ 2 houses (split-knowledge, RQ2).
if topo in ("bridge-federated", "directory-federated") and spec.span_houses() < 2:
if topo in ("bridge-federated", "bridge-federated-pool", "directory-federated") and spec.span_houses() < 2:
raise ValueError(f"assembler: {cell.cell_id} failed the >=2-house span")
return spec