R7/RQ3: executor-wiring stone — offline selector DVs + calibration gate

Wire the RQ3 churn-resilience companion (severable follow-on; RQ3 frozen in its
own prereg) as SYNTHETIC/OFFLINE build + calibration only — no confirmatory
battery run.

- battery: enumerate_rq3_cells()/rq3_schedule() add selector in {static(control),
  random, agent} at 1house/bridge-off under pinned churn kp=30/steps=20, kept
  SEPARATE so the frozen 6-cell lead lattice stays byte-identical.
- executor: run_rq3_cell_run collects offline selector DVs (throughput-retention,
  drops/rebuilds, rebuild-interval gaps); added-latency is live-only (offline path
  records None, never fabricated); run_rq3_battery(live=False) hard-raises.
- analysis/rq3_calibration: DRY offline gate. Churn-bites (1589 drops/1576
  rebuilds) + rebuild-classifier calibration green with real teeth — churned(kp30)
  vs baseline(kp5) AUC 0.926 separable, baseline-vs-baseline null 0.518 blind.
  Classifier scored on the PER-RUN mean inter-rebuild gap (the confirmatory
  grouping unit); the frozen instrument (rebuild_interval_gaps,
  rebuild_classifier_auc) is untouched, no fit to confirmatory data.

HARD HOLD: no RQ3 confirmatory battery (live added-latency is operator+grid-gated).
Lead prereg SHA f22331a72e… untouched; containment intact (synthetic/offline, $0
local Ollama, frontier arm inert); worktree-only.

Tests: tests/test_sor_rq3_wiring.py 9 passed; full SOR suite 194 passed.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
leetcrypt
2026-07-21 20:07:30 -07:00
parent 7ab3466878
commit c6e882abd6
5 changed files with 623 additions and 2 deletions
+211 -2
View File
@@ -35,18 +35,21 @@ from __future__ import annotations
import hashlib
import json
import shutil
import statistics
import time
from dataclasses import dataclass, field
from pathlib import Path
from typing import Dict, List, Optional, Sequence, Tuple
from cmd_chat.sor import battery as sor_battery
from cmd_chat.sor.analysis.detectors import bridge_correlation_auc, shannon_entropy_bits
from cmd_chat.sor.analysis.metrics import compute_metrics, write_metrics
from cmd_chat.sor.analysis.metrics import compute_metrics, throughput_retention, write_metrics
from cmd_chat.sor.assembler import assemble
from cmd_chat.sor.churn import churn_schedule
from cmd_chat.sor.config import Domain, SorRng
from cmd_chat.sor.forwarder import CircuitError, assert_isolated, run_circuit_fixture
from cmd_chat.sor.provenance import Node, RunManifest, write_manifest
from cmd_chat.sor.selector import SelectionResult
from cmd_chat.sor.selector import SelectionResult, run_selection
DEFAULT_BINS = 32
@@ -331,3 +334,209 @@ def run_battery(
path.write_text(json.dumps(doc, indent=2, sort_keys=True) + "\n", encoding="utf-8")
doc["_results_path"] = str(path)
return doc
# --------------------------------------------------------------------------- #
# RQ3 — churn-resilient selector collection.
#
# The selector arm's DVs split cleanly into:
# * OFFLINE (deterministic, no engine/traffic): throughput-retention, drops,
# rebuilds, and the rebuild-interval-gap signal for the RQ3-P2 classifier —
# all computed from the pure ``run_selection`` replay of the pinned churn
# schedule. These need no live circuit and are collected here directly.
# * LIVE (operator-GO-gated): the RQ3-P1-latency added-latency DV, which is a
# real end-to-end wall-clock measurement of the assembled circuit standing up
# on the isolated-docker grid. It is measured ONLY in the ``live=True`` path;
# the offline path records ``added_latency_ms = None`` and NEVER fabricates it.
# --------------------------------------------------------------------------- #
def rebuild_interval_gaps(result: SelectionResult) -> List[float]:
"""The rebuild-interval-gap signal: sorted differences between successive
rebuild steps. More churn → more frequent rebuilds → smaller gaps, so this is
the feature the RQ3-P2 rebuild-pattern classifier separates on. Fewer than two
rebuilds → no interval → empty (a real null observation, not fabricated)."""
ts = sorted(rb.t for rb in result.rebuilds)
return [float(b - a) for a, b in zip(ts, ts[1:])]
def _rq3_pool(cell, size: int = 8) -> List[str]:
"""The 1-house consenting-node pool the selector rebuilds over. Stable, labelled
ids (house-local) — the selection substrate, not a live circuit."""
house = cell.factors.get("topology", "1house")
return [f"{house}/node{ix:02d}" for ix in range(size)]
@dataclass
class RQ3RunReport:
cell_id: str
rq: str
run_index: int
seed: int
strategy: str
hops: int
kill_prob_pct: int
steps: int
drops: int
rebuilds: int
deferred: int
every_drop_rebuilt: bool
throughput_retention: float
rebuild_gaps: List[float]
added_latency_ms: Optional[float] # measured only in the live path; else None
run_dir: str
def run_rq3_cell_run(
cell,
run_index: int,
out_root: Path,
*,
engine: str = "docker",
pool_size: int = 8,
hops: int = 3,
c_circuits: int = 0,
payload_size: int = 4096,
live: bool = False,
) -> RQ3RunReport:
"""Collect one RQ3 (cell, run): replay the pinned churn schedule under the cell's
selector strategy and record the OFFLINE selector DVs (retention, drops, rebuilds,
rebuild-interval gaps). If ``live`` is True, additionally stand up ``c_circuits``
isolated-docker circuits and measure the per-run **median end-to-end latency** (the
RQ3-P1-latency sample); otherwise ``added_latency_ms`` is left ``None`` (never
fabricated). Writes a write-once ``rq3-run.json`` sidecar. Deterministic offline."""
kp = int(cell.factors["churn_kill_prob_pct"])
steps = int(cell.factors["churn_steps"])
strategy = cell.factors.get("selector", "static")
seed = sor_battery.derive_seed(cell.cell_id, run_index)
run_dir = Path(out_root) / f"rq3-{cell.cell_id.replace('/', '_')}-r{run_index}"
run_dir.mkdir(parents=True, exist_ok=True)
nodes = _rq3_pool(cell, pool_size)
schedule = churn_schedule(seed, nodes, steps, kill_prob_pct=kp)
result = run_selection(seed, nodes, hops, schedule, strategy=strategy)
gaps = rebuild_interval_gaps(result)
added_latency_ms: Optional[float] = None
if live:
# RQ3-P1-latency: a REAL end-to-end measurement, isolated-docker only. Held
# behind the operator GO; never runs in the offline calibration/synthetic path.
assert_isolated(engine)
if shutil.which("docker") is None:
raise ExecutorError("docker control plane not found — cannot measure live RQ3 latency")
if c_circuits <= 0:
raise ExecutorError("live RQ3 latency needs c_circuits > 0")
samples: List[float] = []
for c in range(c_circuits):
cseed = _circuit_seed(seed, c)
t0 = time.perf_counter()
res = run_circuit_fixture(
cseed, engine=engine, hops=hops, out_root=run_dir / "circuits",
payload_size=payload_size,
)
dt_ms = (time.perf_counter() - t0) * 1000.0
if not res.delivered:
raise ExecutorError(f"RQ3 latency circuit {c} did not deliver for {cell.cell_id}")
samples.append(dt_ms)
added_latency_ms = statistics.median(samples)
doc = {
"schema": "sor-rq3-run/1",
"cell_id": cell.cell_id,
"rq": cell.rq,
"run_index": run_index,
"seed": seed,
"selector_strategy": result.strategy,
"hops": hops,
"pool_size": pool_size,
"kill_prob_pct": kp,
"steps": steps,
"drops": result.drops,
"rebuilds": len(result.rebuilds),
"deferred": result.deferred,
"every_drop_rebuilt": result.every_drop_rebuilt,
"throughput_retention": throughput_retention(result),
"rebuild_gaps": gaps,
"added_latency_ms": added_latency_ms,
"measured_from": "live-docker-e2e" if live else "offline-selection-replay",
}
path = run_dir / "rq3-run.json"
if not path.exists():
path.write_text(json.dumps(doc, indent=2, sort_keys=True) + "\n", encoding="utf-8")
return RQ3RunReport(
cell_id=cell.cell_id, rq=cell.rq, run_index=run_index, seed=seed,
strategy=result.strategy, hops=hops, kill_prob_pct=kp, steps=steps,
drops=result.drops, rebuilds=len(result.rebuilds), deferred=result.deferred,
every_drop_rebuilt=result.every_drop_rebuilt,
throughput_retention=throughput_retention(result), rebuild_gaps=gaps,
added_latency_ms=added_latency_ms, run_dir=str(run_dir),
)
def run_rq3_battery(
out_root: Path,
*,
engine: str = "docker",
order_seed: int = sor_battery.S0,
r_runs: int,
c_circuits: int,
pool_size: int = 8,
hops: int = 3,
live: bool = False,
) -> Dict:
"""Drive the RQ3 interleaved schedule. Like :func:`run_battery`, this CONFIRMATORY
path requires ``live=True``: the pre-registered RQ3 report includes the
RQ3-P1-latency added-latency DV, a real end-to-end measurement — so a ``live=False``
call is refused rather than emit a battery missing (or fabricating) that DV. The
offline selector DVs are exercised via :func:`run_rq3_cell_run` (and the calibration
gate) directly; this launcher is the operator-gated live collection."""
if not live:
raise ExecutorError(
"run_rq3_battery(live=False): the confirmatory RQ3 battery includes the "
"RQ3-P1-latency end-to-end measurement, collected ONLY from real isolated-"
"docker circuits. Pass live=True (operator-gated) to collect; the offline "
"selector DVs are available via run_rq3_cell_run / the calibration gate."
)
assert_isolated(engine)
out_root = Path(out_root)
out_root.mkdir(parents=True, exist_ok=True)
schedule = sor_battery.rq3_schedule(order_seed, r=r_runs)
by_cell = {c.cell_id: c for c in sor_battery.enumerate_rq3_cells()}
schedule = [pr for pr in schedule if pr.cell_id in by_cell]
reports: List[RQ3RunReport] = []
for pr in schedule:
reports.append(run_rq3_cell_run(
by_cell[pr.cell_id], pr.run_index, out_root, engine=engine,
pool_size=pool_size, hops=hops, c_circuits=c_circuits, live=True,
))
agg: Dict[str, Dict] = {}
for rep in reports:
a = agg.setdefault(rep.cell_id, {
"strategy": rep.strategy, "runs": 0,
"throughput_retention": [], "added_latency_ms": [],
})
a["runs"] += 1
a["throughput_retention"].append(rep.throughput_retention)
if rep.added_latency_ms is not None:
a["added_latency_ms"].append(rep.added_latency_ms)
doc = {
"schema": "sor-rq3-battery-results/1",
"engine": engine,
"order_seed": order_seed,
"r_runs": r_runs,
"c_circuits": c_circuits,
"pool_size": pool_size,
"hops": hops,
"measured_from": "live-docker-e2e",
"n_runs": len(reports),
"cells": agg,
"runs": [vars(r) for r in reports],
}
path = out_root / "rq3-battery-results.json"
if not path.exists():
path.write_text(json.dumps(doc, indent=2, sort_keys=True) + "\n", encoding="utf-8")
doc["_results_path"] = str(path)
return doc