Files
hack-house/cmd_chat/sor/analysis/rq2p3_confirm.py
T
leetcrypt f920516c69 RQ2-P3 STEP 2: run confirmatory battery OFFLINE + DETERMINISTIC — RESOLVED MIX
Operator-authorized (D1: freeze + run). New harness analysis/rq2p3_confirm.py
runs the frozen §4 battery entirely offline: all 9 sweep cells (B∈{2,4,8} ×
alpha∈{0,1,2}; B=50 anchor excluded) × R=30 × C=50 = 13,500 bridged circuits.
RQ2 entropy is analytic and pool bridge assignment is deterministic from the
seed, so no engine, no traffic, no grid, $0 — the same offline reconstruction
path confirm_load_rq2 uses for the lead RQ2. Seeds per frozen §6: run =
derive_seed(S0=20260719 ‖ cell_id ‖ run_index), per-circuit = the live
executor's real _circuit_seed rule (byte-identical). The frozen instruments
(stats.py, confirm.py, confirm_load_rq2.py, assembler.py) are UNCHANGED — the
only new code is seed enumeration, run-as-unit grouping, and an OLS-slope helper.

Frozen §8 analysis (effect + BCa 95% CI, never a bare p; 10,000 resamples;
α=0.05; run-level cluster bootstrap):
  H1 pooled Spearman ρ(c_i,H_i) = +0.6244, CI [+0.5941, +0.6545] -> mix
  H2 OLS dose-response slope    = +0.7052, CI [+0.6195, +0.7903] -> mix (n=270)
  H3 joint -> RESOLVED = mix (both exclude 0, agree in sign)
  Holm over own family {H1-pooled, H2-slope}, size 2: both reject.

Honest disclosure (as commanded): the §7 dry pass already previewed this mix
(ρ 0→+0.838); the battery quantifies an effect already visible at calibration;
the two-sided pre-commitment stood. The +ρ MIX qualifies/corrects the lead
RQ2-P1 "shrink" headline as a unique-bridge artifact — a shared finite bridge
pool RAISES the per-circuit anonymity set, the opposite of the naive funnel.
That correction is the finding, reported openly. The lead RQ2-P1 result is NOT
re-litigated; any pool ΔH is exploratory (prereg §1).

Sealed immutably under output/ (gitignored → anchors force-added):
rq2p3-confirmatory-results.json (SHA-256 5fdcb379d8a2…) + SHA256SUMS;
byte-identical on re-run. Both prereg SHAs verified intact (lead f22331a72e…,
RQ2-P3 8db4e8a7ac60…). Tests: tests/test_sor_rq2p3_confirm.py 7 passed; full
SOR suite 201 passed, no regression. Defensive-measurement instrument only;
worktree-only on feat/sor-consent-relay.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-07-21 21:03:41 -07:00

366 lines
16 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""RQ2-P3 confirmatory battery — OFFLINE + DETERMINISTIC (frozen prereg
`docs/rq2p3-mechanism-prereg.md`, FROZEN 2026-07-21, SHA in the sidecar
`rq2p3-mechanism-prereg.sha256`).
Why this can run offline. RQ2-P3 measures **no live-only DV** — the per-circuit
anonymity-set entropy ``H_i`` is analytic (MillerMadow over the observation-
consistent posterior) and the willing-bridge assignment is a **deterministic**
function of the circuit seed (``assembler.assemble`` under the
``bridge-federated-pool`` branch). Every circuit is recomputable from its seed with
no engine, no traffic, no grid — the SAME offline reconstruction path
``confirm_load_rq2`` uses for the lead RQ2. There is nothing to fabricate: unlike
RQ1's pcap-timing AUC or RQ3's added-latency, the RQ2-P3 DVs are not measured from a
running circuit. Containment is therefore satisfied by construction (no forwarder
ever runs); budget is $0.
Seeds (frozen §6). Per-run seed = ``derive_seed(cell_id, run_index)`` =
``SHA256(S0=20260719 ‖ cell_id ‖ run_index)``; per-circuit seed = the confirmatory
executor's real rule ``executor._circuit_seed(run_seed, c)`` =
``SHA256("sor-circuit|{run_seed}|{c}")`` — reused verbatim so these offline circuits
are byte-identical to the ones a live executor would persist as ``per_circuit_seeds``.
Design (frozen §4). The 9 sweep cells ``B ∈ {2,4,8} × alpha ∈ {0, 1.0, 2.0}`` at
``R = 30`` runs × ``C = 50`` circuits. The ``B=50, alpha=0`` calibration anchor from
``enumerate_rq2p3_cells()`` is NOT a confirmatory cell (§4: exactly 9) and is excluded
here.
Analysis (frozen §8, two-sided / direction-agnostic; effect + BCa 95% CI, never a bare
p; 10,000 resamples; α = 0.05):
* **H1 (pooled Spearman ρ, run-level cluster bootstrap).** ρ between per-circuit
top-3 concentration ``c_i`` and entropy ``H_i`` over all bridged circuits, with the
**run** as the resampling unit (resample whole runs, pool their circuits) — circuits
sharing a bridge are pseudo-replicates, so per-circuit resampling would falsely
narrow the CI (§8). Funnel iff CI < 0, mix iff CI > 0, inconclusive iff it spans 0.
* **H2 (dose-response OLS slope, run-level cluster bootstrap).** Slope β of per-run
mean-H on per-run mean top-3 concentration over the 270 per-run points, resampling
whole runs. Funnel iff slope CI < 0, mix iff > 0.
* **H3 (joint).** RESOLVED iff H1 and H2 agree in sign AND both exclude 0.
* **Multiplicity.** HolmBonferroni over THIS study's own family {H1-pooled,
H2-slope} (family_size = 2). The lead study's family-of-7 is closed and not reopened.
Frozen instruments reused UNCHANGED (§3): ``stats.bootstrap_ci`` (BCa),
``stats.spearman``, ``stats.holm_bonferroni``, ``confirm_load_rq2.bridge_concentration``
/``per_circuit_entropy``/``top_k_bridge_concentration``, ``assembler.assemble``,
``executor._circuit_seed``. The only new code is this harness (seed enumeration, the
run-as-unit grouping, and an OLS-slope helper) — no detector is re-fit.
Honest-disclosure (mandatory, §7 scope note). The §7 dry calibration pass already
PREVIEWED a mix (ρ 0→+0.838 across the sweep); this confirmatory battery **quantifies**
an effect already visible at calibration. The pre-commitment stays **two-sided**; if the
data show a mix (ρ > 0) that plainly **qualifies/corrects** the lead RQ2-P1 "shrink"
headline as a unique-bridge artifact — that correction IS the finding, reported openly.
"""
from __future__ import annotations
import hashlib
import json
import statistics
import sys
from dataclasses import dataclass
from pathlib import Path
from typing import Dict, List, Sequence, Tuple
from cmd_chat.sor.analysis.confirm_load_rq2 import (
bridge_concentration,
per_circuit_entropy,
top_k_bridge_concentration,
)
from cmd_chat.sor.analysis.stats import (
DEFAULT_ALPHA,
DEFAULT_RESAMPLES,
bootstrap_ci,
holm_bonferroni,
spearman,
two_sided_bootstrap_p,
)
from cmd_chat.sor.assembler import assemble
from cmd_chat.sor.battery import (
C_CIRCUITS,
R_RUNS,
S0,
derive_seed,
enumerate_rq2p3_cells,
)
from cmd_chat.sor.executor import _circuit_seed
# A run's pooled per-circuit measurements: parallel (c_i, H_i) over its bridged
# circuits — the cluster unit for the H1 run-level bootstrap.
RunPairs = List[Tuple[float, float]]
# A single per-run dose-response point (mean top-3 concentration, mean H) — the unit
# for the H2 slope bootstrap.
RunPoint = Tuple[float, float]
def confirmatory_cells():
"""The 9 frozen §4 sweep cells (B ∈ {2,4,8} × alpha ∈ {0,1,2}). The B=50 anchor
in ``enumerate_rq2p3_cells()`` is a calibration anchor (§7 gate item 1), NOT a
confirmatory cell, and is excluded."""
return [c for c in enumerate_rq2p3_cells() if int(c.factors["pool_B"]) in (2, 4, 8)]
def _assemble_run(cell, run_index: int, c: int = C_CIRCUITS,
*, engine: str = "docker", hops: int = 3):
"""Reconstruct one run's C circuit specs OFFLINE from the frozen seeds. Persists
nothing itself; the caller records ``per_circuit_seeds`` for the immutable seal."""
run_seed = derive_seed(cell.cell_id, run_index)
seeds = [_circuit_seed(run_seed, c_ix) for c_ix in range(c)]
specs = [assemble(cell, s, engine=engine, hops=hops) for s in seeds]
return run_seed, seeds, specs
def _run_pairs(specs) -> RunPairs:
"""Parallel (c_i, H_i) over the run's bridged circuits (all circuits are bridged
in the pool topology; a None-concentration circuit — none here — is dropped)."""
conc = bridge_concentration(specs)
ent = per_circuit_entropy(specs)
return [(c, h) for c, h in zip(conc, ent) if c is not None]
def _pooled_spearman(runs: Sequence[RunPairs]) -> float:
"""Spearman ρ over the circuits of ALL runs in the (possibly resampled) cluster
sample — the run-level cluster-bootstrap statistic for H1."""
xs: List[float] = []
ys: List[float] = []
for run in runs:
for c, h in run:
xs.append(c)
ys.append(h)
return spearman(xs, ys)
def _ols_slope(points: Sequence[RunPoint]) -> float:
"""OLS slope β of y on x = cov(x,y)/var(x) over the per-run points (H2). Harness
grouping only — the frozen instruments are untouched. Zero-variance x → 0.0."""
n = len(points)
if n < 2:
return 0.0
mx = sum(p[0] for p in points) / n
my = sum(p[1] for p in points) / n
sxx = sum((p[0] - mx) ** 2 for p in points)
if sxx == 0.0:
return 0.0
sxy = sum((p[0] - mx) * (p[1] - my) for p in points)
return sxy / sxx
@dataclass(frozen=True)
class CellRecord:
cell_id: str
B: int
alpha: float
n_runs: int
n_bridged_circuits: int
mean_top3_concentration: float
mean_entropy_bits: float
exploratory_pooled_spearman: float # EXPLORATORY per-cell ρ (§8)
def collect(r: int = R_RUNS, c: int = C_CIRCUITS) -> Dict:
"""Reconstruct the full 9×R×C battery offline and return the immutable record:
per-cell summaries, the pooled (run-clustered) H1 pairs, the 270 H2 per-run
points, and the per-run seed provenance (for the seal)."""
cells = confirmatory_cells()
per_cell: List[CellRecord] = []
h1_runs: List[RunPairs] = [] # one RunPairs per (cell, run) — H1 cluster units
h2_points: List[RunPoint] = [] # 9*R per-run (mean_conc, mean_H) points — H2 units
seed_manifest: List[Dict] = []
for cell in cells:
b = int(cell.factors["pool_B"])
alpha = float(cell.factors["pool_alpha"])
cell_runs: List[RunPairs] = []
cell_top3: List[float] = []
cell_meanh: List[float] = []
cell_bridged = 0
for ri in range(r):
run_seed, seeds, specs = _assemble_run(cell, ri, c)
pairs = _run_pairs(specs)
cell_runs.append(pairs)
cell_bridged += len(pairs)
top3 = top_k_bridge_concentration(specs, k=3)
mean_h = statistics.fmean(h for _, h in pairs) if pairs else 0.0
cell_top3.append(top3)
cell_meanh.append(mean_h)
h2_points.append((top3, mean_h))
seed_manifest.append({
"cell_id": cell.cell_id, "run_index": ri, "run_seed": run_seed,
"per_circuit_seeds": seeds,
})
h1_runs.extend(cell_runs)
per_cell.append(CellRecord(
cell_id=cell.cell_id, B=b, alpha=alpha, n_runs=r,
n_bridged_circuits=cell_bridged,
mean_top3_concentration=statistics.fmean(cell_top3),
mean_entropy_bits=statistics.fmean(cell_meanh),
exploratory_pooled_spearman=_pooled_spearman(cell_runs),
))
return {
"cells": per_cell,
"h1_runs": h1_runs,
"h2_points": h2_points,
"seed_manifest": seed_manifest,
"n_pooled_bridged_circuits": sum(len(run) for run in h1_runs),
}
def _boot_seed(tag: str) -> int:
"""Deterministic bootstrap seed derived from the frozen base seed S0 so the CIs
are reproducible (the §8 seed spot-check is mechanical)."""
return int.from_bytes(hashlib.sha256(f"rq2p3-confirm|{tag}|{S0}".encode()).digest()[:8], "big")
def analyze(collected: Dict, *, n_resamples: int = DEFAULT_RESAMPLES,
alpha: float = DEFAULT_ALPHA) -> Dict:
"""Run the frozen §8 H1/H2/H3 analysis on the collected battery. Effect + BCa
95% CI for both; run-level cluster bootstrap; Holm over {H1-pooled, H2-slope}."""
h1_runs: List[RunPairs] = collected["h1_runs"]
h2_points: List[RunPoint] = collected["h2_points"]
# H1 — pooled Spearman ρ, resampling whole RUNS (cluster unit), BCa CI.
h1_ci, h1_dist = bootstrap_ci(
h1_runs, _pooled_spearman, n_resamples=n_resamples, alpha=alpha,
seed=_boot_seed("H1-pooled"), method="bca", return_dist=True,
)
h1_p = two_sided_bootstrap_p(h1_dist, 0.0)
h1_decision = ("funnel" if h1_ci.strictly_less(0.0)
else "mix" if h1_ci.strictly_greater(0.0)
else "inconclusive")
# H2 — OLS dose-response slope over the 270 per-run points, resampling whole RUNS.
h2_ci, h2_dist = bootstrap_ci(
h2_points, _ols_slope, n_resamples=n_resamples, alpha=alpha,
seed=_boot_seed("H2-slope"), method="bca", return_dist=True,
)
h2_p = two_sided_bootstrap_p(h2_dist, 0.0)
h2_decision = ("funnel" if h2_ci.strictly_less(0.0)
else "mix" if h2_ci.strictly_greater(0.0)
else "inconclusive")
# H3 — joint: RESOLVED iff H1 and H2 agree in sign AND both exclude 0.
both_exclude = h1_ci.excludes(0.0) and h2_ci.excludes(0.0)
same_sign = (h1_ci.point > 0) == (h2_ci.point > 0)
h3_resolved = bool(both_exclude and same_sign)
h3_finding = (h1_decision if h3_resolved else "unresolved")
# Multiplicity — Holm over THIS study's own family {H1-pooled, H2-slope}, size 2.
holm = holm_bonferroni({"H1-pooled": h1_p, "H2-slope": h2_p},
alpha=alpha, family_size=2)
return {
"H1_pooled_spearman": {
"effect": "spearman_rho", "decision": h1_decision,
"p_for_holm": h1_p, **h1_ci.as_dict(),
},
"H2_dose_response_slope": {
"effect": "ols_slope_H_on_conc", "decision": h2_decision,
"n_points": len(h2_points), "p_for_holm": h2_p, **h2_ci.as_dict(),
},
"H3_joint": {
"resolved": h3_resolved, "finding": h3_finding,
"both_exclude_zero": both_exclude, "agree_in_sign": same_sign,
},
"holm_own_family": [
{"name": h.name, "p": h.p, "p_adjusted": h.p_adjusted,
"reject": h.reject, "rank": h.rank, "multiplier": h.multiplier}
for h in holm
],
}
def run_confirmatory(r: int = R_RUNS, c: int = C_CIRCUITS,
*, n_resamples: int = DEFAULT_RESAMPLES,
alpha: float = DEFAULT_ALPHA) -> Dict:
"""The full offline RQ2-P3 confirmatory report: collect → analyze → assemble the
sealed record (with per-run seed provenance and the honest-disclosure note)."""
collected = collect(r, c)
results = analyze(collected, n_resamples=n_resamples, alpha=alpha)
cells = collected["cells"]
return {
"schema": "sor-rq2p3-confirmatory/1",
"prereg": "docs/rq2p3-mechanism-prereg.md",
"prereg_frozen": "2026-07-21",
"prereg_sha256_sidecar": "docs/rq2p3-mechanism-prereg.sha256",
"offline_deterministic": True,
"no_engine_no_traffic_no_grid": True,
"base_seed_S0": S0,
"R": r, "C": c, "n_cells": len(cells),
"n_pooled_bridged_circuits": collected["n_pooled_bridged_circuits"],
"n_resamples": n_resamples, "alpha": alpha,
"sweep": [
{"cell_id": cr.cell_id, "B": cr.B, "alpha": cr.alpha,
"n_runs": cr.n_runs, "n_bridged_circuits": cr.n_bridged_circuits,
"mean_top3_concentration": cr.mean_top3_concentration,
"mean_entropy_bits": cr.mean_entropy_bits,
"exploratory_pooled_spearman": cr.exploratory_pooled_spearman}
for cr in cells
],
"results": results,
"seed_manifest": collected["seed_manifest"],
"honest_disclosure": (
"The §7 dry calibration pass already PREVIEWED a mix (rho 0->+0.838 across "
"the sweep); this confirmatory battery QUANTIFIES an effect already visible "
"at calibration. The pre-commitment stays two-sided. A mix (rho>0) qualifies/"
"corrects the lead RQ2-P1 'shrink' headline as a unique-bridge artifact — "
"that correction IS the finding, reported openly. The lead RQ2-P1 result is "
"NOT re-litigated; any pool DeltaH is EXPLORATORY (prereg §1)."
),
}
def _seal(out_dir: Path, report: Dict) -> Dict[str, str]:
"""Write the immutable results + a SHA256SUMS over them. ``output/`` is gitignored;
the caller force-adds the anchors so the sealed raw record enters the commit."""
out_dir.mkdir(parents=True, exist_ok=True)
results_path = out_dir / "rq2p3-confirmatory-results.json"
results_path.write_text(json.dumps(report, indent=2, sort_keys=True), encoding="utf-8")
sums: Dict[str, str] = {}
for p in sorted(out_dir.glob("*.json")):
sums[p.name] = hashlib.sha256(p.read_bytes()).hexdigest()
sums_path = out_dir / "SHA256SUMS"
sums_path.write_text(
"".join(f"{h} {name}\n" for name, h in sorted(sums.items())), encoding="utf-8")
return {"results": str(results_path), "sha256sums": str(sums_path), **sums}
def main(argv=None) -> int:
import argparse
ap = argparse.ArgumentParser(
prog="python -m cmd_chat.sor.analysis.rq2p3_confirm",
description="RQ2-P3' confirmatory battery — OFFLINE + DETERMINISTIC (frozen prereg).",
)
ap.add_argument("--out", default="output/sor-rq2p3-confirmatory",
help="immutable seal dir (gitignored; force-add the anchors)")
ap.add_argument("--r-runs", type=int, default=R_RUNS)
ap.add_argument("--c-circuits", type=int, default=C_CIRCUITS)
ap.add_argument("--resamples", type=int, default=DEFAULT_RESAMPLES)
args = ap.parse_args(argv)
report = run_confirmatory(args.r_runs, args.c_circuits, n_resamples=args.resamples)
sealed = _seal(Path(args.out), report)
h1 = report["results"]["H1_pooled_spearman"]
h2 = report["results"]["H2_dose_response_slope"]
h3 = report["results"]["H3_joint"]
print(f"[RQ2-P3'] OFFLINE confirmatory — {report['n_cells']} cells x R={report['R']} "
f"x C={report['C']} = {report['n_pooled_bridged_circuits']} bridged circuits")
print(f"[H1] pooled Spearman rho = {h1['point']:+.4f} "
f"95% BCa CI [{h1['ci_lo']:+.4f}, {h1['ci_hi']:+.4f}] "
f"({h1['method']}) -> {h1['decision']}")
print(f"[H2] OLS slope H~conc = {h2['point']:+.4f} "
f"95% BCa CI [{h2['ci_lo']:+.4f}, {h2['ci_hi']:+.4f}] "
f"({h2['method']}, n={h2['n_points']}) -> {h2['decision']}")
print(f"[H3] joint -> resolved={h3['resolved']} finding={h3['finding']}")
print("[Holm own-family {H1-pooled,H2-slope}] " + ", ".join(
f"{h['name']}:p_adj={h['p_adjusted']:.4g}({'reject' if h['reject'] else 'retain'})"
for h in report["results"]["holm_own_family"]))
print(f"[seal] {sealed['results']}")
print(f"[seal] {sealed['sha256sums']}")
print("[disclosure] " + report["honest_disclosure"])
return 0
if __name__ == "__main__":
raise SystemExit(main())