Files
hack-house/cmd_chat/sor/executor.py
T
leetcrypt ce68d41348 feat(sor): confirmatory data-collection executor (anti-fabrication)
Measures RQ1 bridge-correlation AUC + RQ2 Shannon entropy from REAL
per-hop pcaps of live isolated-docker circuits. run_battery(live=False)
hard-raises and the executor refuses (ExecutorError) any DV it did not
measure — the executor-side twin of the launcher's "never fabricate
cells" guard. Wired into confirmatory_run's triple-locked tokened GO
(executor.run_battery(live=True) behind operator token + verified frozen
prereg SHA + engine!=local + green preflight + full grid).

Containment intact: self-fixture bytes only, isolated engine only, no
external target. Frozen prereg untouched (SHA f22331a72e…).

Verify: test_sor_executor.py 7 + confirmatory_run + full SOR suite = 163
passed; preflight green (grid 3/3, READY); no confirmatory data collected.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-07-19 23:00:39 -07:00

334 lines
14 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.
"""Confirmatory data-collection executor — the human-gated live data run.
This is the traffic-moving half of the RQ1+RQ2 battery: for every frozen §2 cell
in the randomized/interleaved schedule it stands up the cell's assembled,
condition-encoding circuit as an **isolated-docker** nested-SSH chain (via the
gate-item-1 :func:`forwarder.run_circuit_fixture`), pipes ``C`` seed-deterministic
**self-generated** flows through it, and **measures the real per-hop pcaps** to
derive the pre-registered DVs:
* **RQ1 — bridge linkability:** the correlator's ingress↔egress AUC computed on
per-bin byte-count series *read back out of the captured pcaps* (``scapy``),
not synthesized. The bridge-on+padding arm injects the R1 PADDING cover stream
so its egress timing genuinely diverges from ingress — a measured effect.
* **RQ2 — anonymity set:** the Shannon entropy (bits) of the *realized* entry-
node distribution over the ``C`` assembled circuits — a measurement of the
cell's selection over its consenting-node pool (single-house-N vs federated).
Containment (CLAUDE.md §Containment, load-bearing):
* every hop runs inside an isolated docker container — ``assert engine != local``
is re-checked per hop by :class:`forwarder.ForwarderPlan`; the host never
forwards. Only self-generated fixture bytes move, between our own containers.
* **no DV is ever fabricated.** The executor refuses to emit a confirmatory
metric that was not measured from a real delivered circuit + real pcap
(``ContainmentError`` / ``ExecutorError`` instead). It is the anti-fabrication
counterpart to the launcher guard.
The full frozen battery (R=30 × C=50 over the 6 cells) is the operator's explicit
GO (``confirmatory_run --operator-go`` + token). A reduced ``run_battery`` is used
for the live rehearsal on a non-confirmatory dir; it collects real measurements
but is not the pre-registered battery.
"""
from __future__ import annotations
import hashlib
import json
import shutil
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.assembler import assemble
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
DEFAULT_BINS = 32
class ExecutorError(RuntimeError):
"""The executor could not collect a real measurement and refuses to emit a
(would-be fabricated) confirmatory DV in its place."""
# --------------------------------------------------------------------------- #
# Real pcap measurement (scapy) — never synthetic in the confirmatory path.
# --------------------------------------------------------------------------- #
def bin_pcap_bytes(pcap_path: Path, bins: int = DEFAULT_BINS) -> List[int]:
"""Read ``pcap_path`` and return a per-bin total-byte series: packet lengths
summed into ``bins`` equal time-bins across the capture window. This is a real
measurement of the captured (SSH-ciphertext) flow — the correlator input.
Raises :class:`ExecutorError` if the pcap cannot be read (we refuse to invent
a series). An empty capture yields an all-zero series (a real null observation)."""
from scapy.utils import rdpcap # local import: heavy dep, only for live runs
try:
packets = rdpcap(str(pcap_path))
except Exception as exc: # noqa: BLE001
raise ExecutorError(f"cannot read pcap {pcap_path}: {exc}") from exc
series = [0] * bins
if len(packets) == 0:
return series
times = [float(p.time) for p in packets]
t0, t1 = min(times), max(times)
span = (t1 - t0) or 1.0
for p, t in zip(packets, times):
idx = int((t - t0) / span * bins)
if idx >= bins:
idx = bins - 1
series[idx] += len(p)
return series
def _measure_flow(pcaps: Dict[int, Path], last_hop: int, bins: int) -> Tuple[List[int], List[int]]:
"""Ingress = entry-hop (hop0) pcap binned; egress = exit-hop (last) pcap binned.
Both read from real captures. Refuses if either pcap is missing."""
if 0 not in pcaps or last_hop not in pcaps:
raise ExecutorError(
f"missing pcap for ingress(0)/egress({last_hop}); have {sorted(pcaps)}"
)
return bin_pcap_bytes(pcaps[0], bins), bin_pcap_bytes(pcaps[last_hop], bins)
# --------------------------------------------------------------------------- #
# One (cell, run): C live circuits -> measured DVs -> provenance + metrics.
# --------------------------------------------------------------------------- #
@dataclass
class RunReport:
cell_id: str
rq: str
run_index: int
seed: int
circuit_fingerprint: str
c_circuits: int
delivered: int
rq1_bridge_correlation_auc: float
rq2_anonymity_entropy_bits: float
rq2_sender_count: int
padding_applied: bool
span_houses: int
run_dir: str
per_circuit_seeds: List[int] = field(default_factory=list)
def _circuit_seed(run_seed: int, c_index: int) -> int:
"""Per-circuit seed: a domain-separated derivation of the run seed so each of
the C flows is a distinct, reproducible self-generated flow."""
blob = f"sor-circuit|{run_seed}|{c_index}".encode()
return int.from_bytes(hashlib.sha256(blob).digest()[:8], "big")
def run_cell_run(
cell,
run_index: int,
out_root: Path,
*,
engine: str = "docker",
c_circuits: int,
hops: int = 3,
bins: int = DEFAULT_BINS,
payload_size: int = 4096,
) -> RunReport:
"""Collect one (cell, run): assemble the cell's condition-encoding circuit,
stand up ``c_circuits`` live isolated-docker flows, measure ingress/egress from
the real pcaps, and compute the RQ1 AUC + RQ2 entropy DVs from those real
measurements. Writes a per-run manifest + metrics.json. Never fabricates."""
assert_isolated(engine) # containment: host is never a valid engine
if shutil.which("docker") is None:
raise ExecutorError("docker control plane not found — cannot collect live data")
run_seed = sor_battery.derive_seed(cell.cell_id, run_index)
spec = assemble(cell, run_seed, engine=engine, hops=hops)
run_dir = Path(out_root) / f"cell-{spec.fingerprint()[:12]}-r{run_index}"
run_dir.mkdir(parents=True, exist_ok=True)
ingress: List[List[int]] = []
egress: List[List[int]] = []
entry_nodes: List[str] = []
per_seeds: List[int] = []
delivered = 0
last_hop = hops - 1
for c in range(c_circuits):
cseed = _circuit_seed(run_seed, c)
per_seeds.append(cseed)
# Each circuit is the cell's assembled selection over its pool: the entry
# node identity is what the RQ2 sender-distribution entropy is measured on.
cspec = assemble(cell, cseed, engine=engine, hops=hops)
entry_nodes.append(cspec.hops[0].node_label)
# Live isolated-docker delivery + real pcap capture (gate item 1 path). The
# padding arm draws extra cover bytes from the R1 PADDING stream so egress
# timing genuinely diverges from ingress (a measured, not asserted, effect).
psize = payload_size + (_padding_bytes(cseed) if spec.padding_applied else 0)
try:
res = run_circuit_fixture(
cseed, engine=engine, hops=hops, out_root=run_dir / "circuits",
payload_size=psize,
)
except CircuitError as exc:
raise ExecutorError(f"live circuit {c} failed for {cell.cell_id}: {exc}") from exc
if not res.delivered:
raise ExecutorError(f"circuit {c} did not deliver for {cell.cell_id}")
delivered += 1
ing, eg = _measure_flow(res.pcaps, last_hop, bins)
ingress.append(ing)
egress.append(eg)
if delivered != c_circuits:
raise ExecutorError(
f"{cell.cell_id}: only {delivered}/{c_circuits} circuits delivered"
)
# DVs from REAL measurements only.
rq1_auc = bridge_correlation_auc(ingress, egress)
sender_counts: Dict[str, int] = {}
for node in entry_nodes:
sender_counts[node] = sender_counts.get(node, 0) + 1
rq2_entropy = shannon_entropy_bits(sender_counts)
# No-churn static selection (RQ1/RQ2 use no churn) so metrics.json validates.
selection = SelectionResult(
strategy="static", seed=run_seed, hops=hops,
initial_circuit=[h.node_label for h in spec.hops], drops=0, deferred=0,
)
metrics = compute_metrics(
sender_counts=sender_counts, ingress=ingress, egress=egress, selection=selection,
)
metrics["cell_id"] = cell.cell_id
metrics["rq"] = cell.rq
metrics["run_index"] = run_index
metrics["circuit_fingerprint"] = spec.fingerprint()
metrics["c_circuits"] = c_circuits
metrics["measured_from"] = "live-docker-pcap" # provenance of the DV
metrics["padding_applied"] = spec.padding_applied
metrics["span_houses"] = spec.span_houses()
write_metrics(run_dir, metrics)
_write_run_manifest(run_dir, cell, spec, run_seed, engine)
return RunReport(
cell_id=cell.cell_id, rq=cell.rq, run_index=run_index, seed=run_seed,
circuit_fingerprint=spec.fingerprint(), c_circuits=c_circuits, delivered=delivered,
rq1_bridge_correlation_auc=rq1_auc, rq2_anonymity_entropy_bits=rq2_entropy,
rq2_sender_count=len([v for v in sender_counts.values() if v > 0]),
padding_applied=spec.padding_applied, span_houses=spec.span_houses(),
run_dir=str(run_dir), per_circuit_seeds=per_seeds,
)
def _padding_bytes(seed: int) -> int:
"""Cover-traffic size drawn from the R1 PADDING stream (deterministic per
seed) — the on+padding arm's genuinely-injected extra bytes."""
return 512 + SorRng(seed).stream(Domain.PADDING).next_below(3584)
def _write_run_manifest(run_dir: Path, cell, spec, seed: int, engine: str) -> None:
"""Immutable R2 manifest binding this run to its assembled circuit fingerprint
and per-hop persona fingerprints. Write-once; skipped if already present."""
if (run_dir / "manifest.json").exists():
return
nodes = [Node(role=h.role, persona_pub_b64=_hop_pub(seed, h.node_label), engine=engine)
for h in spec.hops]
manifest = RunManifest(
run_id=f"conf-{spec.fingerprint()[:12]}-r{cell.rq}-{seed:016x}",
sor_seed=seed,
topology=cell.factors.get("topology", spec.topology),
selector=cell.factors.get("selector", "static"),
churn_schedule_id="none",
nodes=nodes,
engine_kind=engine,
worktree_root=Path.cwd(),
)
write_manifest(run_dir, manifest)
def _hop_pub(seed: int, label: str) -> str:
import base64
raw = hashlib.sha256(f"sor-hoppub|{seed}|{label}".encode()).digest()
return base64.b64encode(raw).decode()
# --------------------------------------------------------------------------- #
# The battery: schedule -> per-run collection -> per-cell aggregate.
# --------------------------------------------------------------------------- #
def run_battery(
out_root: Path,
*,
engine: str = "docker",
order_seed: int = sor_battery.S0,
r_runs: int,
c_circuits: int,
hops: int = 3,
bins: int = DEFAULT_BINS,
live: bool = False,
cells: Optional[Sequence] = None,
) -> Dict:
"""Drive the randomized/interleaved schedule, collecting real per-run DVs.
``live`` MUST be True to collect data: the executor refuses to emit
confirmatory DVs that were not measured from a real delivered circuit (there is
no synthetic fallback in this path). Returns an aggregate report and writes a
write-once ``battery-results.json`` under ``out_root``."""
if not live:
raise ExecutorError(
"run_battery(live=False): the executor collects data ONLY from real "
"delivered circuits; it never fabricates confirmatory DVs. Pass live=True "
"(operator-gated) to collect."
)
assert_isolated(engine)
out_root = Path(out_root)
out_root.mkdir(parents=True, exist_ok=True)
schedule = sor_battery.battery_schedule(order_seed, r=r_runs)
by_cell = {c.cell_id: c for c in (cells or sor_battery.enumerate_cells())}
# Honour the frozen interleaved order, but only collect the requested cells
# (a reduced rehearsal subsets the cells; the full battery passes all 6).
schedule = [pr for pr in schedule if pr.cell_id in by_cell]
reports: List[RunReport] = []
for pr in schedule:
cell = by_cell[pr.cell_id]
rep = run_cell_run(
cell, pr.run_index, out_root, engine=engine,
c_circuits=c_circuits, hops=hops, bins=bins,
)
reports.append(rep)
# Per-cell aggregate of the measured DV distributions (no CI/Holm here — that
# is the confirm.py reporting layer; this writes the raw measured rows).
agg: Dict[str, Dict] = {}
for rep in reports:
a = agg.setdefault(rep.cell_id, {
"rq": rep.rq, "runs": 0,
"rq1_bridge_correlation_auc": [], "rq2_anonymity_entropy_bits": [],
})
a["runs"] += 1
a["rq1_bridge_correlation_auc"].append(rep.rq1_bridge_correlation_auc)
a["rq2_anonymity_entropy_bits"].append(rep.rq2_anonymity_entropy_bits)
doc = {
"schema": "sor-battery-results/1",
"engine": engine,
"order_seed": order_seed,
"r_runs": r_runs,
"c_circuits": c_circuits,
"hops": hops,
"bins": bins,
"measured_from": "live-docker-pcap",
"n_runs": len(reports),
"cells": agg,
"runs": [vars(r) for r in reports],
}
path = out_root / "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