RQ1+RQ2 start-line: live per-cell condition assembler + guarded launcher + Holm ratification

Wire the live per-cell condition assembler (cmd_chat/sor/assembler.py): compose
the existing R1/R4/R5/R6 pieces into a condition-encoding CircuitSpec per frozen
§2 cell, so a cell_id maps to a genuinely distinct, ForwarderPlan-gated
(engine != local) isolated circuit rather than the plain 3-hop control:
  - RQ1 bridge-on / on+padding insert a live bridge hop (+ R1 PADDING stream);
  - RQ2 bridge-/directory-federated genuinely span >= 2 houses
    (federation.select_federated_path, split-knowledge).
Plans only: opens no socket, moves no traffic, stands up no engine.

battery.assembler_dry_check validates on FIXTURES (6/6 cells distinct
fingerprints, all isolation-gated, same (cell,seed) reproduces, RQ1 bridge +
padding arms live, RQ2 federation >= 2 houses); write-once, kept out of any
confirmatory data dir. confirmatory_run preflight now runs it; the --operator-go
path no longer refuses cells as "not wired" — triple-lock + green preflight HOLD
on grid completion, then surface the operator's immutable data-run gate. No
confirmatory cell is ever fabricated.

Start-line instrument-validation gate (cmd_chat/sor/gate.py), grid + containment
pin (grid.py), and the guarded launcher (confirmatory_run.py) land alongside.

Holm: hold family_size = 7, report the 4 RQ1/RQ2 tests -> multipliers 7,6,5,4.
This is the pre-registration, not a deviation (prereg §6 [APPROVAL] size-7 family
+ D6 disclosure in the lead paper); docs/stage-05-holm-clarification.md marked
RATIFIED. RQ3-fold and alpha-split declined.

Containment intact; prereg untouched (SHA f22331a72e...); worktree-only. output/
gitignored (runtime artifacts, never source). Full SOR suite 156 passed. No
confirmatory data collected — the live data run remains the human gate.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
leetcrypt
2026-07-19 20:49:12 -07:00
parent 72a6c7b519
commit 6d61c53152
11 changed files with 1228 additions and 0 deletions
+4
View File
@@ -31,6 +31,10 @@ err.log
# Heavy / superseded demo-build kit (real demos live in video-toolkit)
/docs/demo/
# SOR run artifacts (runtime-generated: preflight/dry/start-line/confirmatory;
# immutable once written, never source — kept out of version control)
/output/
# Project scratch
/i-try/
test_rsa.py
+27
View File
@@ -0,0 +1,27 @@
# OVERSEER-STATUS — sor-consent build agent
- 2026-07-19 R2 DONE + committed (7ed2370): provenance writer — immutable manifest.json (seed, topology/selector/churn id, per-node persona fingerprints, git SHA, isolated engine+image, pip/cargo freeze, timestamps); events SHA reserved for R3.
- Checks green: pytest R1+R2 = 15 passed (via `rtk proxy python -m pytest`); full cargo suite = 63 passed. Cross-language fingerprint parity locked (persona.rs <-> provenance.py). Deps installed into worktree venv.
- 2026-07-19 R3 DONE + committed (a9308ed): append-only events.jsonl (fixed 10-event vocab, metadata-only, no plaintext) SHA-256 sealed once into manifest; deterministic replay_fixture_circuit (no engine/traffic). Provenance spine R1->R2->R3 = protect-first minimum unit, GREEN. Checks: pytest sor suite 21 passed; cargo 63 passed.
- 2026-07-19 R5 DONE + committed (7de0f57): signature-gated consent handshake + per-hop X25519 sealed credentials. crypto.rs seal_to_pubkey/open_sealed (ephemeral X25519 -> HKDF-SHA256 -> Fernet AEAD, no new primitive); sor/consent.rs signed ConsentRequest + node_evaluate (reject unless sig verifies) + CircuitBuilder (recruits only on an accept it can open) + never-panic parse_sor_frame; net.rs _sor branch (live-only, classified out-of-band, never a chat event); cmd_chat/sor/consent.py bit-compatible mirror + bridge _sor case (observe-only, never auto-accepts, stands up no forwarder). Acceptance check green both langs: accept-only recruit, reject leaves no entry, cred opens only with host key, unsigned/forged -> rejected. Cross-language KDF parity pinned by shared known-answer vector. Rust 75 passed; Python SOR suite 37 passed.
- 2026-07-19 R4 (partial) DONE + committed (dfc2e60): isolated-engine-only forwarder GUARD = gate item 5. forwarder.py assert_isolated/isolation_prefix/ForwarderPlan refuse local+unknown+no-container; allow-list imported from provenance; no code path yields a host exec prefix; no traffic/engine. Python forwarder suite 12 passed. Traffic-moving half (gate item 1) NOT built — HELD.
- 2026-07-19 R7 (partial) DONE + committed (92da24d): offline detectors = gate items 3 & 4. analysis/detectors.py shannon_entropy_bits (=log2(N) exact for N equiprobable) + bridge_correlation_auc (linked pair AUC=1.0; unlinked unbiased, 40-seed mean 0.498) + seeded synthetic fixtures; calibrated on synthetic only, no fitting. Python SOR suite 66 passed. churn/selector/metrics.json NOT built — HELD.
- 2026-07-19 R4 (e2e half) DONE + committed (a78441b): gate item 1 GREEN. forwarder.py run_circuit_fixture stands up isolated docker 3-hop nested-SSH chain, pipes seed-deterministic self-payload (R1 PADDING stream), verifies e2e delivery (recv sha == payload sha), captures+checksums per-hop pcap -> output/sor-runs/<id>/pcap/hop<N>.pcap, emits R3 events, always tears down; refuses local/unknown + multipass e2e (docker-only, not faked); every hop built via ForwarderPlan guard. Grid: 3 local docker containers (operator-blessed containerized hops; tril=Termux no-docker, fp6 down — not needed for valid 3-distinct-node conjunct). Adds sor-hop fixture image + docker-gated test (skips w/o daemon). Live 3-hop delivery verified, clean teardown. e2e suite 5 passed; full offline SOR suite 66 passed.
- 2026-07-19 R6 DONE + committed (21263da): multi-house federation. federation.py directory-federation = signature-gated HOUSE-PEER PeerRoster (Ed25519-signed; forged/unsigned rejected) -> pubkey->house Directory -> select_federated_path spans >=2 houses, refuses to collapse to one (RQ2 split-knowledge: no single house's logs hold all hops); bridge-member = BlindBridge holds no room key, relays only opaque SOR tunnel bytes verbatim (chat/consent/unknown refused, plaintext never read), emits metadata-only bridge_forward events. Offline-verifiable (no socket/engine/external target). Federation suite 10 passed.
- 2026-07-19 R7 (live halves) DONE + committed (12fd253): churn.py seeded kill/spawn schedule (pure data, no real VM ops); selector.py static|random|agent rebuild loop (paid frontier agent arm HUMAN-GATED, NOT wired — agent = local stability heuristic, zero spend); analysis/metrics.py -> schema-valid write-once metrics.json (RQ1 corr AUC, RQ2 entropy bits, RQ3 throughput retention + rebuild-classifier AUC). Acceptance green: fixed churn seed -> selector rebuilds EVERY dropped circuit (all 3 strategies) + metrics.json produced. Selector suite 11 passed; full SOR suite 92 passed (incl live 3-hop e2e).
- GATE STATUS: 6/6 instrument-validation items GREEN. ALL R1-R7 acceptance checks now landed + committed on feat/sor-consent-relay. Instrument is build-complete. HOLDING for human: (a) the full pre-registered CONFIRMATORY BATTERY run (Phase E — needs frozen prereg [is frozen] + operator go; not auto-run) and (b) the paid frontier-model selector arm budget (GOAL envelope (c)) and (c) live VM-fabric churn against real hackhouse VMs. Containment intact; worktree-only.
- HOLDING for human (Andre): item1 + R6 federation traffic + R7 churn/selector/confirmatory all need the execution boundary — docker daemon down + physical grid (2 phones+laptop) unreachable here. Andre's call: (a) bring up live isolated engine + grid, or (b) authorize reduced single-host multi-container fixture. No confirmatory battery (prereg frozen). All offline-validatable units are now landed.
- 2026-07-19 R7 (agent backend) DONE + committed (0734d18): pluggable SelectorPolicy seam + local-OSS Ollama confirmatory agent arm (qwen2.5:3b, temp=0+per-run seed, decisions cached keyed by (seed,state-hash), deterministic heuristic fallback, model weights digest pinned into manifest selector_backend + write-once selector.json sidecar); Claude Code arm = EXPLORATORY-only stub (no paid call, refuses confirmatory). Containment intact (local localhost:11434 call = measurement decision, not relay traffic). R3 event vocab UNCHANGED. Suite: 22 passed incl 2 live-Ollama cells. Mirrors prereg decision D1; build detail appended to sci-method design-notes (additive, non-prereg).
- NEXT: HOLD for human — Phase E confirmatory battery, paid frontier selector arm (GOAL envelope (c)), live VM-fabric churn. BLOCKERS: none for build (instrument complete).
- Containment intact; prereg untouched (SHA f22331a72e...); worktree-only on feat/sor-consent-relay.
- 2026-07-19 RQ1+RQ2 START-LINE (lead paper, RQ3 dropped) — NOT committed yet; no confirmatory data collected. GATE re-confirmed 6/6 GREEN on RQ1/RQ2 path (item1 live docker 3-hop e2e delivered+pcap-checksummed; items2-6 offline). Artifacts: output/sor-startline/<ts>/{gate/gate-report.json, grid/device-map.json, plan/cell-plan.json, dry/dry-pass.json}.
- Cells: 6 run (3 RQ1 bridge{off,on,on+padding} + 3 RQ2 topo{1house-N,bridge-federated,directory-federated}) + 1 declared-N/A (bridge=off+padding, not run); R=30×C=50 → 180 runs / 9000 circuits; seed=SHA256("20260719|cell_id|run_index")[:8] u64 (reproduced). Matched-N pinned from containerised node count at run time.
- Grid pin (probed NOW): docker 27.5.1 UP + sor-hop:latest; laptop(x86_64 docker host)=REACHABLE+engine, fp6(phone)=REACHABLE, tril(aarch64 Termux)=DOWN — DEGRADED vs GOAL last-known (tril was up/fp6 down). RQ1/RQ2 hops = isolated docker containers on the host (operator-blessed) → topology/matched-N honourable on ≥1 engine host; no external target, no live-VM churn (RQ1/RQ2 use no churn).
- Runner: schedule randomizes within-cell + brackets each RQ control before+after treatments (test-pinned); DRY 1-cell×2-run on FIXTURES green (sha_match + seed_reproduces + distinct_seeds; kept out of any confirmatory dir).
- Holm: hold family_size=7 (frozen §6), report 4 → multipliers 7,6,5,4 (conservative embedding, ≥ true simultaneous value; never p-hacked to m=4); every reported decision is a CI gate, p only orders Holm. PROPOSED doc docs/stage-05-holm-clarification.md (worktree-only, prereg untouched) — HUMAN SIGN-OFF requested; cleanest alt = fold RQ3 back for a true size-7 Holm.
- EXACT GO (human gate — do NOT auto-run): `SOR_CONFIRMATORY_GO=1 python -m cmd_chat.sor.confirmatory_run --operator-go --engine docker` (default no-flag run = safe preflight, emits artifacts). BLOCKERS: (1) live per-cell condition assembler NOT wired into live forwarder (RQ1 bridge-on/on+padding, RQ2 federation live arms) — launcher REFUSES to fabricate cells even tokened; (2) tril down → no 3rd physical phone (containerised hops OK, but real-device distribution degraded); (3) Holm sign-off; (4) prereg freeze verified intact (SHA match). New: cmd_chat/sor/{gate,grid,confirmatory_run}.py + tests; full SOR suite 143 passed.
- 2026-07-19 MANUAL-DRIVE (Andre) — LIVE ASSEMBLER WIRED + HOLM RATIFIED (committed <SHA>): assembler.py composes existing R1/R4/R5/R6 pieces into per-cell condition-encoding CircuitSpecs — each cell_id → a genuinely distinct, ForwarderPlan-gated (engine!=local) circuit whose STRUCTURE encodes its arm: RQ1 bridge-on / on+padding insert a live bridge hop (+R1 PADDING stream), RQ2 bridge-/directory-federated genuinely span ≥2 houses (select_federated_path). Plans only — no socket, no traffic, no engine.
- Dry-check GREEN on FIXTURES (battery.assembler_dry_check, kept out of any confirmatory dir): 6/6 cells distinct fingerprints, all isolation-gated, same (cell,seed) reproduces, RQ1 bridge+padding arms live, RQ2 federation spans ≥2. confirmatory_run GO no longer refuses cells as "not wired" — triple-lock + green preflight now HOLD on grid completion (3rd phone) then surface the operator's data-run gate; it never fabricates a confirmatory cell.
- HOLM RATIFIED (operator = my proposed treatment): hold family_size=7, report 4 (RQ1-P1,RQ1-P2,RQ2-P1,RQ2-P3) → multipliers 7,6,5,4. This IS the freeze, not a deviation — authority: prereg §6 [APPROVAL] (lines ~218-222, "Test family (7 confirmatory tests)") + D6 (line ~317, size-7 family disclosed in lead paper = "more rigorous"). docs/stage-05-holm-clarification.md updated to RATIFIED; RQ3-fold + α-split declined per operator.
- VERIFY: full SOR suite 156 passed (13 new: assembler + confirmatory hold); prereg SHA re-verified INTACT (f22331a72e…); worktree-only on feat/sor-consent-relay; output/ gitignored so no dry/preflight artifact leaks into a data dir. NO confirmatory data collected.
- STILL HUMAN-GATED (do NOT auto-run): same EXACT GO command above. Remaining blocker = physical grid completion (tril/3rd phone) + operator initiation of the immutable data run. Instrument + live assembler build-complete; prereg untouched. Flagging none against paper quality.
+231
View File
@@ -0,0 +1,231 @@
"""Live per-cell condition assembler — turns a frozen §2 cell into a genuinely
distinct, ForwarderPlan-gated isolated circuit.
Each confirmatory cell is *not* the plain 3-hop control: this composes the
already-built R4/R5/R6 pieces so that a ``cell_id`` maps to a real circuit whose
**structure encodes its condition**, every hop gated by the R4 isolation guard
(:class:`ForwarderPlan`, ``assert engine != local``):
* **RQ1 bridge=off** — a single-house 3-hop path (R1 ``select_path``).
* **RQ1 bridge=on** — the middle hop is a **bridge** node (R6 ``BlindBridge``
role), so the circuit is routed through a bridge; structurally distinct.
* **RQ1 bridge=on+padding** — bridge on, plus the R1 PADDING stream drives
cover traffic on the path (``padding_applied``).
* **RQ2 1house-N** — single house sized to the **matched-N** node count.
* **RQ2 bridge-federated** — entry/exit in *different* houses joined by a
``BlindBridge`` member: the path spans ≥ 2 houses.
* **RQ2 directory-federated** — a signed-roster :class:`Directory` and
``select_federated_path`` pick a path spanning ≥ 2 houses (split knowledge).
Determinism comes from the R1 ``SorRng`` / frozen seed so the same ``(cell,
seed)`` reproduces the same circuit; different cells produce different circuits.
This module builds **plans only** — it opens no socket, moves no traffic, and
stands up no engine. The live traffic that would consume these plans is the
human-gated confirmatory data run (``confirmatory_run``); here we only validate,
on fixtures, that each cell is a real, distinct, isolated-engine circuit.
"""
from __future__ import annotations
import base64
import hashlib
import json
from dataclasses import dataclass
from typing import Dict, List, Tuple
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
from cmd_chat.sor.config import SorRng, select_path
from cmd_chat.sor.consent import _fingerprint_of, persona_sign
from cmd_chat.sor.federation import Directory, build_peer_frame, parse_peer_frame
from cmd_chat.sor.forwarder import ContainmentError, ForwarderPlan
HOUSES = ("houseA", "houseB")
DEFAULT_HOUSE_SIZE = 4
DEFAULT_N_HOUSES = 2
DEFAULT_HOPS = 3
@dataclass(frozen=True)
class HopSpec:
"""One hop of an assembled circuit. ``is_bridge`` marks the R6 bridge role."""
index: int
role: str # "entry" | "relay" | "exit" | "bridge"
house: str
node_label: str # deterministic node identity (fingerprint or house#idx)
is_bridge: bool
@dataclass(frozen=True)
class CircuitSpec:
"""A fully-determined, condition-encoding circuit for one cell. Immutable; its
:meth:`plans` are the R4 isolation-gated forwarder targets."""
cell_id: str
rq: str
seed: int
engine: str
topology: str
bridge_present: bool
padding_applied: bool
matched_n: int
houses: Tuple[str, ...]
hops: Tuple[HopSpec, ...]
def _semantic(self) -> Dict:
"""The condition-defining content (no container names — those derive from
this), so the fingerprint reflects the *circuit*, not incidental naming."""
return {
"cell_id": self.cell_id, "rq": self.rq, "seed": self.seed,
"topology": self.topology, "bridge_present": self.bridge_present,
"padding_applied": self.padding_applied, "matched_n": self.matched_n,
"houses": list(self.houses),
"hops": [[h.index, h.role, h.house, h.node_label, h.is_bridge]
for h in self.hops],
}
def fingerprint(self) -> str:
"""SHA-256 over the semantic content. Same (cell, seed) → same fingerprint
(reproducible); different cells → different fingerprints (distinct)."""
blob = json.dumps(self._semantic(), sort_keys=True, separators=(",", ":"))
return hashlib.sha256(blob.encode()).hexdigest()
def container_name(self, i: int) -> str:
return f"sorcell-{self.fingerprint()[:8]}-h{i}"
def plans(self) -> List[ForwarderPlan]:
"""The R4 isolation-gated forwarder target for each hop. Building a plan
re-asserts ``engine != local`` — a non-isolated engine raises here."""
return [ForwarderPlan(engine=self.engine, container=self.container_name(h.index))
for h in self.hops]
def isolation_gated(self) -> bool:
"""True iff every hop builds a valid isolated ForwarderPlan (containment)."""
try:
self.plans()
return True
except ContainmentError:
return False
def span_houses(self) -> int:
return len({h.house for h in self.hops})
def to_dict(self) -> Dict:
d = self._semantic()
d["fingerprint"] = self.fingerprint()
d["engine"] = self.engine
d["span_houses"] = self.span_houses()
d["isolation_gated"] = self.isolation_gated()
return d
def _persona(seed: int, tag: str) -> Tuple[bytes, str]:
"""A deterministic fixture Ed25519 keypair as (secret_raw, pub_b64)."""
raw = hashlib.sha256(f"sor-persona|{seed}|{tag}".encode()).digest()[:32]
sk = Ed25519PrivateKey.from_private_bytes(raw)
pub = base64.standard_b64encode(sk.public_key().public_bytes_raw()).decode()
return raw, pub
def _bridge_label(seed: int) -> str:
return f"bridge#{hashlib.sha256(f'sor-bridge|{seed}'.encode()).hexdigest()[:8]}"
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]
def _build_directory(seed: int, house_size: int, n_houses: int) -> Directory:
"""A signature-verified directory over ``n_houses`` fixture houses so
``select_federated_path`` can span ≥ 2 of them (roster forgery is refused)."""
d = Directory()
for house in HOUSES[:n_houses]:
host_secret, host_pub = _persona(seed, f"host|{house}")
members = [_persona(seed, f"{house}|m{m}")[1] for m in range(house_size)]
frame = build_peer_frame(host_secret, host_pub, house, members)
roster = parse_peer_frame(frame) # verifies the signature
if roster is None or not d.add_roster(roster):
raise ValueError(f"assembler: fixture roster for {house} failed to verify")
return d
def assemble(
cell, seed: int, *, engine: str = "docker",
hops: int = DEFAULT_HOPS, house_size: int = DEFAULT_HOUSE_SIZE,
n_houses: int = DEFAULT_N_HOUSES,
) -> CircuitSpec:
"""Assemble the genuinely distinct, isolation-gated circuit for ``cell`` at
``seed``. Dispatches on the frozen §2 factors (bridge / topology). Raises
ValueError if a federated cell cannot honour its ≥2-house span."""
bridge = cell.factors.get("bridge", "off")
topo = cell.factors.get("topology", "1house")
matched_n = house_size * n_houses # federated arm's total consenting nodes
hop_specs: List[HopSpec]
houses: Tuple[str, ...]
bridge_present = False
padding_applied = False
if cell.rq == "RQ1":
houses = (HOUSES[0],)
if bridge == "off":
labels = _house_nodes(seed, house_size, hops, HOUSES[0])
roles = ["entry", "relay", "exit"][:hops]
hop_specs = [HopSpec(i, roles[i], HOUSES[0], labels[i], False)
for i in range(hops)]
else: # "on" or "on+padding": route the middle hop through a bridge node.
ends = _house_nodes(seed, house_size, 2, HOUSES[0])
hop_specs = [
HopSpec(0, "entry", HOUSES[0], ends[0], False),
HopSpec(1, "bridge", "bridge", _bridge_label(seed), True),
HopSpec(2, "exit", HOUSES[0], ends[1], False),
]
bridge_present = True
padding_applied = (bridge == "on+padding")
else: # RQ2 — bridge always off; the topology factor varies.
if topo == "1house-N":
houses = (HOUSES[0],)
labels = _house_nodes(seed, matched_n, hops, HOUSES[0])
roles = ["entry", "relay", "exit"][:hops]
hop_specs = [HopSpec(i, roles[i], HOUSES[0], labels[i], False)
for i in range(hops)]
elif topo == "bridge-federated":
a = _house_nodes(seed, house_size, 1, HOUSES[0], salt=0)[0]
b = _house_nodes(seed, house_size, 1, HOUSES[1], salt=0xB)[0]
hop_specs = [
HopSpec(0, "entry", HOUSES[0], a, False),
HopSpec(1, "bridge", "bridge", _bridge_label(seed), 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)
roles = ["entry", "relay", "exit"][:hops]
hop_specs = [HopSpec(i, roles[i], house, _fingerprint_of(pub), False)
for i, (pub, house) in enumerate(path)]
houses = tuple(sorted({h.house for h in hop_specs}))
else:
raise ValueError(f"assembler: unknown RQ2 topology {topo!r}")
spec = CircuitSpec(
cell_id=cell.cell_id, rq=cell.rq, seed=seed, engine=engine,
topology=topo, bridge_present=bridge_present, padding_applied=padding_applied,
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:
raise ValueError(f"assembler: {cell.cell_id} failed the >=2-house span")
return spec
def assemble_all(cells, run_index: int = 0, *, engine: str = "docker") -> Dict[str, CircuitSpec]:
"""Assemble one representative circuit per cell (at ``run_index``) using the
frozen seed rule. Returns ``cell_id -> CircuitSpec``."""
from cmd_chat.sor.battery import derive_seed
return {c.cell_id: assemble(c, derive_seed(c.cell_id, run_index), engine=engine)
for c in cells}
+76
View File
@@ -271,3 +271,79 @@ def dry_pass(out_root: Path, *, runs: int = 2) -> Dict:
if not path.exists():
path.write_text(json.dumps(summary, indent=2, sort_keys=True) + "\n", encoding="utf-8")
return summary
# --------------------------------------------------------------------------- #
# Assembler dry-check (fixtures only — proves each cell is a REAL, distinct,
# isolation-gated live circuit, NOT the plain 3-hop control). Builds plans only:
# opens no socket, moves no traffic, stands up no engine.
# --------------------------------------------------------------------------- #
def assembler_dry_check(out_root: Path, run_index: int = 0, *, engine: str = "docker") -> Dict:
"""Assemble every runnable §2 cell into its condition-encoding CircuitSpec and
verify, on FIXTURES: (a) all 6 cells build a valid isolated ForwarderPlan for
every hop (``engine != local``); (b) the 6 fingerprints are distinct (each cell
is a genuinely different circuit, not the plain control); (c) re-assembling the
same (cell, seed) reproduces its fingerprint (determinism); (d) the RQ1 bridge
arms actually insert a bridge hop (+padding flag on on+padding); (e) the RQ2
federation topologies genuinely span >= 2 houses. Plans only — no traffic, no
engine. Writes a write-once report under ``out_root`` (kept OUT of any
confirmatory data dir). Returns the summary."""
from cmd_chat.sor.assembler import assemble
out_root = Path(out_root)
out_root.mkdir(parents=True, exist_ok=True)
cells = enumerate_cells()
per_cell: List[Dict] = []
fingerprints: List[str] = []
for c in cells:
seed = derive_seed(c.cell_id, run_index)
spec = assemble(c, seed, engine=engine)
again = assemble(c, seed, engine=engine) # determinism: same (cell, seed)
fingerprints.append(spec.fingerprint())
per_cell.append({
"cell_id": c.cell_id,
"rq": c.rq,
"seed": seed,
"fingerprint": spec.fingerprint(),
"isolation_gated": spec.isolation_gated(),
"reproduces": spec.fingerprint() == again.fingerprint(),
"bridge_present": spec.bridge_present,
"padding_applied": spec.padding_applied,
"span_houses": spec.span_houses(),
"topology": spec.topology,
})
# Condition-encoding expectations (each cell is REAL, not the plain control).
def _cell(pred) -> Dict:
return next(r for r in per_cell if pred(r["cell_id"]))
rq1_on = _cell(lambda i: i.endswith("bridge=on"))
rq1_pad = _cell(lambda i: i.endswith("bridge=on+padding"))
rq2_bridge_fed = _cell(lambda i: i.endswith("topo=bridge-federated"))
rq2_dir_fed = _cell(lambda i: i.endswith("topo=directory-federated"))
summary = {
"schema": "sor-assembler-dry/1",
"run_index": run_index,
"engine": engine,
"n_cells": len(per_cell),
"all_isolation_gated": all(r["isolation_gated"] for r in per_cell),
"all_reproduce": all(r["reproduces"] for r in per_cell),
"distinct_fingerprints": len(set(fingerprints)) == len(per_cell),
"rq1_bridge_arms_live": rq1_on["bridge_present"] and rq1_pad["bridge_present"],
"rq1_padding_arm_live": rq1_pad["padding_applied"] and not rq1_on["padding_applied"],
"rq2_federation_spans_2plus": (
rq2_bridge_fed["span_houses"] >= 2 and rq2_dir_fed["span_houses"] >= 2
),
"cells": per_cell,
}
summary["all_green"] = bool(
summary["all_isolation_gated"] and summary["all_reproduce"]
and summary["distinct_fingerprints"] and summary["rq1_bridge_arms_live"]
and summary["rq1_padding_arm_live"] and summary["rq2_federation_spans_2plus"]
)
path = out_root / "assembler-dry.json"
if not path.exists():
path.write_text(json.dumps(summary, indent=2, sort_keys=True) + "\n", encoding="utf-8")
return summary
+184
View File
@@ -0,0 +1,184 @@
"""RQ1+RQ2 confirmatory battery — start-line preflight + guarded launcher.
This is the single operator entrypoint. Run with **no flags** it performs the
*safe* start-line **preflight** and writes auditable artifacts, then prints a
GO/NO-GO summary and the exact launch command — it collects **no** confirmatory
data:
* §5 instrument-validation gate re-confirmation (``gate.run_gate``);
* grid inventory + containment pin (``grid.write_device_map``);
* frozen §2 cell plan + randomized/interleaved schedule (``battery.write_cell_plan``);
* a 1-cell × 2-run DRY provenance pass on fixtures (``battery.dry_pass``).
The live per-cell condition assembler is now wired (``sor.assembler``): each cell
maps to a genuinely distinct, condition-encoding, isolation-gated circuit (RQ1
bridge-on / on+padding arms actually insert a bridge hop + PADDING stream; RQ2
bridge-/directory-federated topologies genuinely span >= 2 houses). The preflight
proves this on FIXTURES (``battery.assembler_dry_check`` — plans only, no traffic).
The **immutable confirmatory data run on the real grid is still the human gate**
(CLAUDE.md §Stop, GOAL envelope (b)). ``--operator-go`` is triple-locked — it
requires the ``SOR_CONFIRMATORY_GO=1`` operator token, a verified frozen-prereg
SHA-256, and an isolated engine — and even with all three plus a green preflight it
**HOLDS on grid completion**: the confirmatory battery does not launch until the
full physical grid is up (the operator is bringing the 3rd phone online). This
launcher surfaces that hold; it never fabricates a confirmatory cell.
Containment is load-bearing: nothing here forwards real/third-party traffic,
touches an external target, or runs a forwarder on the host.
"""
from __future__ import annotations
import argparse
import hashlib
import os
import sys
from datetime import datetime, timezone
from pathlib import Path
from typing import Any, Dict
from cmd_chat.sor import battery, gate, grid
from cmd_chat.sor.forwarder import assert_isolated
# The frozen prereg (read-only source of truth) and its pinned SHA-256. A GO is
# refused unless the on-disk prereg still hashes to this — no confirmatory run
# against an unfrozen/edited prereg.
FROZEN_PREREG = Path.home() / "coding/sci-method/stages/03-design/output/sor-consent-prereg.md"
FROZEN_PREREG_SHA256 = "f22331a72e0d0ccf38b787e63acabbe9d666456ec76076787a6d545c3193425b"
OPERATOR_TOKEN_ENV = "SOR_CONFIRMATORY_GO"
def _ts() -> str:
return datetime.now(timezone.utc).strftime("%Y%m%dT%H%M%SZ")
def verify_freeze() -> bool:
"""True iff the on-disk frozen prereg still matches its pinned SHA-256."""
try:
got = hashlib.sha256(FROZEN_PREREG.read_bytes()).hexdigest()
except OSError:
return False
return got == FROZEN_PREREG_SHA256
def preflight(out_dir: Path, *, order_seed: int = battery.S0, allow_live_gate: bool = True) -> Dict[str, Any]:
"""Run the safe start-line preflight and write artifacts under ``out_dir``.
Collects no confirmatory data. Returns a summary with a GO/NO-GO verdict."""
out_dir = Path(out_dir)
g = gate.run_gate(out_dir / "gate", allow_live=allow_live_gate)
dm = grid.write_device_map(out_dir / "grid")
plan_path = battery.write_cell_plan(out_dir / "plan", order_seed=order_seed)
dp = battery.dry_pass(out_dir / "dry", runs=2)
asm = battery.assembler_dry_check(out_dir / "assembler")
ready = bool(
g["all_green"]
and dm["topology_matchedN_honourable"]
and dp["all_sha_match"] and dp["all_seed_reproduces"] and dp["distinct_seeds"]
and asm["all_green"]
and verify_freeze()
)
return {
"generated_utc": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"),
"out_dir": str(out_dir),
"gate_all_green": g["all_green"],
"gate_offline_green": g["offline_items_green"],
"grid_reachable": dm["reachable_count"],
"grid_engine_hosts": dm["isolated_engine_host_count"],
"grid_down": dm["devices_down"],
"grid_honourable": dm["topology_matchedN_honourable"],
"grid_full": len(dm["devices_down"]) == 0,
"cell_plan": str(plan_path),
"dry_ok": dp["all_sha_match"] and dp["all_seed_reproduces"] and dp["distinct_seeds"],
"assembler_ok": asm["all_green"],
"assembler_distinct": asm["distinct_fingerprints"],
"assembler_isolation_gated": asm["all_isolation_gated"],
"freeze_ok": verify_freeze(),
"preflight_ready": ready,
}
def _print_summary(s: Dict[str, Any]) -> None:
print(f"[preflight] artifacts -> {s['out_dir']}")
print(f"[preflight] gate all_green={s['gate_all_green']} "
f"(offline={s['gate_offline_green']})")
print(f"[preflight] grid reachable={s['grid_reachable']} "
f"engine_hosts={s['grid_engine_hosts']} down={s['grid_down']} "
f"honourable={s['grid_honourable']}")
print(f"[preflight] dry_ok={s['dry_ok']} freeze_ok={s['freeze_ok']}")
print(f"[preflight] assembler_ok={s['assembler_ok']} "
f"(distinct={s['assembler_distinct']} isolation_gated={s['assembler_isolation_gated']})")
print(f"[preflight] grid_full={s['grid_full']} READY={s['preflight_ready']}")
def _refuse_go(reason: str) -> int:
print(f"[GO REFUSED] {reason}", file=sys.stderr)
return 2
def main(argv=None) -> int:
ap = argparse.ArgumentParser(
prog="python -m cmd_chat.sor.confirmatory_run",
description="RQ1+RQ2 confirmatory battery: safe preflight by default; "
"--operator-go is the human-gated immutable data run.",
)
ap.add_argument("--out", default=f"output/sor-confirmatory/{_ts()}",
help="preflight artifact dir (default: timestamped)")
ap.add_argument("--order-seed", type=int, default=battery.S0,
help="measurement-side schedule ordering seed (default S0)")
ap.add_argument("--engine", default="docker", help="isolated engine (default docker)")
ap.add_argument("--operator-go", action="store_true",
help="attempt the human-gated confirmatory data run (triple-locked)")
args = ap.parse_args(argv)
out_dir = Path(args.out)
summary = preflight(out_dir, order_seed=args.order_seed)
_print_summary(summary)
go_cmd = (f"{OPERATOR_TOKEN_ENV}=1 python -m cmd_chat.sor.confirmatory_run "
f"--operator-go --engine {args.engine}")
if not args.operator_go:
print("\n[held] preflight only — no confirmatory data collected.")
print(f"[held] the immutable data run is the human gate. GO command:\n {go_cmd}")
return 0
# --- triple-locked GO path (human gate) --------------------------------- #
if os.environ.get(OPERATOR_TOKEN_ENV) != "1":
return _refuse_go(f"operator token missing (set {OPERATOR_TOKEN_ENV}=1)")
if not verify_freeze():
return _refuse_go("frozen prereg SHA-256 mismatch — refusing to run against an unfrozen prereg")
try:
assert_isolated(args.engine)
except Exception as exc: # noqa: BLE001
return _refuse_go(f"containment: {exc}")
if not summary["preflight_ready"]:
return _refuse_go("preflight not fully green — resolve NO-GO items before a data run")
# Triple-lock passes and the live per-cell condition assembler is wired +
# fixture-validated (assembler_ok). The one remaining gate is physical: the
# confirmatory battery HOLDS until the full grid is up. The operator is
# bringing the 3rd phone online; until every device is reachable this launcher
# refuses to launch a data run on a degraded grid rather than fabricate cells.
if not summary["grid_full"]:
return _refuse_go(
"grid completing: confirmatory battery holds until the full physical "
f"grid is up (devices down: {summary['grid_down']}). The live per-cell "
"assembler is wired + fixture-validated; the operator is bringing the "
"3rd phone online. Re-run --operator-go once the grid is complete."
)
# Full grid + all three locks + green preflight: the immutable confirmatory
# data run is still the human's to launch. This launcher surfaces readiness;
# the physical data collection is initiated by the operator, not fabricated.
return _refuse_go(
"human gate: grid complete and preflight green — the immutable confirmatory "
"data run is the operator's to initiate on the live grid. This launcher "
"does not fabricate confirmatory cells."
)
if __name__ == "__main__": # pragma: no cover
raise SystemExit(main())
+245
View File
@@ -0,0 +1,245 @@
"""Instrument-validation gate re-confirmation (prereg §5) for the RQ1+RQ2 path.
Re-runs the six boolean gate items and writes an auditable ``gate-report.json``.
This is calibration/validation on **fixtures only** — it collects no
confirmatory-cell data, so it is legitimate start-line work under the freeze:
1. 3-hop e2e delivery + per-hop pcap checksums (R4, live isolated docker);
2. seeded reproducibility — same seed → identical circuit-build sequence (R1);
3. correlator calibration — known-linked AUC≈1, known-unlinked AUC≈0.5 (R7);
4. entropy estimator returns H = log2(N) for N equiprobable senders (R7);
5. forwarders isolated-engine-only — ``assert engine != local`` or refuse (R4);
6. provenance integrity — replayed fixture events SHA-256 matches the sealed
manifest; append-only (R2/R3).
Item 1 stands up isolated **docker** containers and moves only self-generated
fixture bytes between our own containers (containment-safe, always torn down).
Items 2-6 are pure offline checks. Nothing here forwards real/third-party
traffic, touches an external target, or runs a forwarder on the host.
"""
from __future__ import annotations
import hashlib
import json
import math
import shutil
import uuid
from dataclasses import dataclass, field
from datetime import datetime, timezone
from pathlib import Path
from typing import Any, Dict, List, Optional
from cmd_chat.sor import events as sor_events
from cmd_chat.sor import forwarder as sor_forwarder
from cmd_chat.sor.analysis.detectors import (
bridge_correlation_auc,
shannon_entropy_bits,
synthetic_bridge_fixture,
)
from cmd_chat.sor.config import bringup
from cmd_chat.sor.provenance import Node, RunManifest, validate_manifest
# Calibration tolerances (fixtures, ground truth known by construction).
_AUC_LINKED_MIN = 0.99 # known-linked control must separate near-perfectly.
_AUC_UNLINKED_TOL = 0.05 # known-unlinked mean must sit within 0.05 of chance.
_ENTROPY_TOL = 1e-9 # H = log2(N) must hold to floating-point exactness.
_UNLINKED_SEEDS = 40 # seeds averaged for the unlinked-chance calibration.
@dataclass
class GateItem:
"""One §5 gate item's re-confirmation outcome."""
n: int
name: str
passed: bool
status: str # "green" | "red" | "unavailable"
evidence: Dict[str, Any] = field(default_factory=dict)
def _utc_now_iso() -> str:
return datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
def _fixture_pubkey(seed: int, role: str) -> str:
raw = hashlib.sha256(f"sor-gate|{seed}|{role}".encode()).digest() # 32 bytes
import base64
return base64.b64encode(raw).decode()
# --------------------------------------------------------------------------- #
# Item 1 — live 3-hop e2e delivery (isolated docker).
# --------------------------------------------------------------------------- #
def _item1_e2e(out_root: Path, seed: int, *, allow_live: bool) -> GateItem:
"""Stand up a 3-hop nested-SSH docker circuit, deliver a seed-deterministic
self-payload end-to-end, and require per-hop pcap checksums. Marked
``unavailable`` (not ``red``) when the docker engine/image is absent — a
missing execution boundary is not a failed instrument."""
have_docker = shutil.which("docker") is not None
if not (allow_live and have_docker):
return GateItem(1, "3-hop e2e delivery + pcap checksums", False, "unavailable",
{"reason": "docker engine/image not available or live run disabled",
"have_docker": have_docker})
try:
res = sor_forwarder.run_circuit_fixture(seed, engine="docker", hops=3,
out_root=out_root)
except Exception as exc: # noqa: BLE001 - report, never crash the gate
return GateItem(1, "3-hop e2e delivery + pcap checksums", False, "red",
{"error": f"{type(exc).__name__}: {exc}"})
pcaps_ok = len(res.pcap_sha256) == 3 and all(res.pcap_sha256.values())
passed = bool(res.delivered and pcaps_ok
and res.received_sha256 == res.payload_sha256)
return GateItem(1, "3-hop e2e delivery + pcap checksums", passed,
"green" if passed else "red",
{"run_id": res.run_id, "delivered": res.delivered,
"payload_sha256": res.payload_sha256,
"received_sha256": res.received_sha256,
"pcap_sha256": {str(k): v for k, v in res.pcap_sha256.items()},
"events_sha256": res.events_sha256})
# --------------------------------------------------------------------------- #
# Item 2 — seeded reproducibility (R1).
# --------------------------------------------------------------------------- #
def _item2_reproducible(*, pool: int = 5, hops: int = 3, rebuilds: int = 3) -> GateItem:
seed = 0xC0FFEE
a = bringup(seed, pool, hops, rebuilds)
b = bringup(seed, pool, hops, rebuilds)
other = bringup(seed ^ 0x1, pool, hops, rebuilds)
passed = a == b and a != other
return GateItem(2, "seeded circuit-build reproducibility", passed,
"green" if passed else "red",
{"seed": seed, "identical_on_replay": a == b,
"differs_on_other_seed": a != other, "sequence": a})
# --------------------------------------------------------------------------- #
# Item 3 — correlator calibration (R7).
# --------------------------------------------------------------------------- #
def _item3_correlator() -> GateItem:
linked_aucs = [bridge_correlation_auc(*synthetic_bridge_fixture(s, linked=True))
for s in range(_UNLINKED_SEEDS)]
unlinked_aucs = [bridge_correlation_auc(*synthetic_bridge_fixture(s, linked=False))
for s in range(_UNLINKED_SEEDS)]
linked_min = min(linked_aucs)
unlinked_mean = sum(unlinked_aucs) / len(unlinked_aucs)
passed = linked_min >= _AUC_LINKED_MIN and abs(unlinked_mean - 0.5) <= _AUC_UNLINKED_TOL
return GateItem(3, "correlator calibration (linked≈1 / unlinked≈0.5)", passed,
"green" if passed else "red",
{"linked_auc_min": linked_min, "unlinked_auc_mean": unlinked_mean,
"n_seeds": _UNLINKED_SEEDS, "linked_floor": _AUC_LINKED_MIN,
"unlinked_tol": _AUC_UNLINKED_TOL})
# --------------------------------------------------------------------------- #
# Item 4 — entropy estimator H = log2(N) (R7).
# --------------------------------------------------------------------------- #
def _item4_entropy() -> GateItem:
checks = []
ok = True
for n in (2, 4, 8, 16, 64):
h = shannon_entropy_bits([1] * n) # N equiprobable senders
exact = abs(h - math.log2(n)) <= _ENTROPY_TOL
ok = ok and exact
checks.append({"N": n, "H": h, "log2N": math.log2(n), "exact": exact})
return GateItem(4, "entropy estimator H = log2(N)", ok,
"green" if ok else "red", {"checks": checks, "tol": _ENTROPY_TOL})
# --------------------------------------------------------------------------- #
# Item 5 — isolated-engine assertion (R4).
# --------------------------------------------------------------------------- #
def _item5_isolation() -> GateItem:
refused_local = False
refused_unknown = False
accepts_docker = False
try:
sor_forwarder.assert_isolated("local")
except sor_forwarder.ContainmentError:
refused_local = True
try:
sor_forwarder.assert_isolated("host-native")
except sor_forwarder.ContainmentError:
refused_unknown = True
try:
sor_forwarder.assert_isolated("docker")
accepts_docker = True
except sor_forwarder.ContainmentError:
accepts_docker = False
passed = refused_local and refused_unknown and accepts_docker
return GateItem(5, "forwarders isolated-engine-only (assert engine != local)", passed,
"green" if passed else "red",
{"refused_local": refused_local, "refused_unknown": refused_unknown,
"accepts_docker": accepts_docker})
# --------------------------------------------------------------------------- #
# Item 6 — provenance integrity (R2/R3).
# --------------------------------------------------------------------------- #
def _item6_provenance(out_root: Path, *, pool: int = 5, hops: int = 3) -> GateItem:
seed = 0x5EED
# Unique run subdir per invocation so each produces fresh write-once artifacts
# (and run_gate stays safely repeatable into a reused out_dir).
run_id = f"gate6-{seed:016x}-{uuid.uuid4().hex[:8]}"
run_dir = Path(out_root) / run_id
nodes = [Node(role=r, persona_pub_b64=_fixture_pubkey(seed, r), engine="docker")
for r in ("host", "hop", "hop")]
manifest = RunManifest(run_id=run_id, sor_seed=seed, topology="1house",
selector="static", churn_schedule_id="none", nodes=nodes,
engine_kind="docker", worktree_root=Path.cwd())
sealed = sor_events.replay_and_seal(run_dir, manifest, pool=pool, hops=hops, rebuilds=1)
validate_manifest(sealed) # raises on any schema violation
recomputed = hashlib.sha256((run_dir / "events.jsonl").read_bytes()).hexdigest()
sha_match = sealed["events"]["sha256"] == recomputed
# Append-only / immutability: re-sealing the same manifest is refused.
reseal_refused = False
try:
from cmd_chat.sor.provenance import seal_manifest
seal_manifest(run_dir, recomputed)
except ValueError:
reseal_refused = True
passed = sha_match and reseal_refused
return GateItem(6, "provenance integrity (events SHA == manifest; append-only)", passed,
"green" if passed else "red",
{"run_id": run_id, "events_sha256": recomputed,
"manifest_events_sha256": sealed["events"]["sha256"],
"sha_match": sha_match, "reseal_refused": reseal_refused})
def run_gate(out_dir: Path, *, allow_live: bool = True, e2e_seed: int = 0xA11CE) -> Dict[str, Any]:
"""Re-confirm all six §5 gate items and write ``gate-report.json`` (write-once)
under ``out_dir``. Returns the report dict. ``all_green`` is True only if every
item is green; item 1 may be ``unavailable`` when no isolated engine is present,
which is reported distinctly from a ``red`` failure."""
out_dir = Path(out_dir)
out_dir.mkdir(parents=True, exist_ok=True)
items: List[GateItem] = [
_item1_e2e(out_dir, e2e_seed, allow_live=allow_live),
_item2_reproducible(),
_item3_correlator(),
_item4_entropy(),
_item5_isolation(),
_item6_provenance(out_dir),
]
all_green = all(it.status == "green" for it in items)
offline_green = all(it.status == "green" for it in items if it.n != 1)
report = {
"schema": "sor-gate-report/1",
"scope": "RQ1+RQ2 lead-paper path (prereg §5 instrument-validation gate)",
"generated_utc": _utc_now_iso(),
"all_green": all_green,
"offline_items_green": offline_green,
"items": [{"n": it.n, "name": it.name, "passed": it.passed,
"status": it.status, "evidence": it.evidence} for it in items],
}
path = out_dir / "gate-report.json"
if not path.exists():
path.write_text(json.dumps(report, indent=2, sort_keys=True) + "\n", encoding="utf-8")
report["_report_path"] = str(path)
return report
+163
View File
@@ -0,0 +1,163 @@
"""Grid inventory + containment pin for the RQ1+RQ2 confirmatory battery.
Pins the node-role → device mapping for the lab grid (2 phones + laptop) and
probes, at call time, which devices are SSH-reachable and which can host an
**isolated engine** (docker). Writes an auditable ``device-map.json``.
Containment note (load-bearing): every SOR forwarder/hop runs inside an isolated
engine (docker container), never on a phone or the host directly. The physical
phones are *distribution* options for where those isolated containers live; the
operator-blessed fallback for the confirmatory RQ1/RQ2 path is isolated docker
containers co-located on the docker host (as used for gate item 1). So the grid
being partly degraded does not by itself collapse the topology — but any inability
to honour the §2 topology / §6 matched-N with isolated nodes is a STOP-and-flag.
"""
from __future__ import annotations
import json
import shutil
import subprocess
from dataclasses import asdict, dataclass
from datetime import datetime, timezone
from pathlib import Path
from typing import Any, Dict, List, Optional
@dataclass(frozen=True)
class Device:
"""A pinned grid device and the house-role it plays in the confirmatory design."""
name: str # ssh alias / stable id
kind: str # "phone" | "laptop"
arch: str # "aarch64" | "x86_64"
can_host_engine: bool # can run an isolated docker engine locally?
house_role: str # design role (e.g. "house-A host / hop pool")
# Pinned inventory (lab-only, all ours — CLAUDE.md §Containment). tril + fp6 are
# the two phones; laptop is the x86_64 docker host. Only devices that can host an
# isolated docker engine may run a forwarder; phones without docker are consent
# endpoints / distribution targets, never a bare host forwarder.
GRID_INVENTORY: List[Device] = [
Device("laptop", "laptop", "x86_64", True, "house-A docker host + hop pool"),
Device("fp6", "phone", "aarch64", False, "house-B consenting node (phone)"),
Device("tril", "phone", "aarch64", False, "house-C consenting node (Termux, no docker)"),
]
def _probe_ssh(alias: str, timeout: int = 8) -> bool:
"""Best-effort SSH reachability probe (BatchMode, short timeout). Never raises."""
try:
r = subprocess.run(
["ssh", "-o", "BatchMode=yes", "-o", "ConnectTimeout=5",
"-o", "StrictHostKeyChecking=accept-new", alias, "true"],
capture_output=True, timeout=timeout,
)
return r.returncode == 0
except Exception: # noqa: BLE001
return False
def _probe_remote_docker(alias: str, timeout: int = 12) -> bool:
"""Best-effort probe of an isolated docker engine on a remote device. Never raises."""
try:
r = subprocess.run(
["ssh", "-o", "BatchMode=yes", "-o", "ConnectTimeout=5",
"-o", "StrictHostKeyChecking=accept-new", alias,
"docker version --format '{{.Server.Version}}'"],
capture_output=True, timeout=timeout,
)
return r.returncode == 0 and bool(r.stdout.strip())
except Exception: # noqa: BLE001
return False
def _probe_local_docker() -> Optional[str]:
"""Local docker server version if a daemon is up, else None. Never raises."""
if shutil.which("docker") is None:
return None
try:
r = subprocess.run(["docker", "version", "--format", "{{.Server.Version}}"],
capture_output=True, timeout=12)
return r.stdout.decode().strip() if r.returncode == 0 else None
except Exception: # noqa: BLE001
return None
def probe_grid() -> Dict[str, Any]:
"""Probe reachability + isolated-engine availability across the pinned grid.
Pure I/O, no traffic, no forwarder — a connectivity/capability snapshot only."""
local_docker = _probe_local_docker()
devices: List[Dict[str, Any]] = []
for d in GRID_INVENTORY:
reachable = _probe_ssh(d.name)
# An isolated engine is available on this device if it is the local docker
# host (laptop here) or a reachable device advertising a docker daemon.
if d.name == "laptop":
engine_ok = local_docker is not None
engine_ver = local_docker
elif reachable and d.can_host_engine:
engine_ok = _probe_remote_docker(d.name)
engine_ver = "remote-docker" if engine_ok else None
else:
engine_ok = False
engine_ver = None
devices.append({
**asdict(d),
"ssh_reachable": reachable,
"isolated_engine_available": engine_ok,
"engine_version": engine_ver,
})
return {"local_docker_version": local_docker, "devices": devices}
def write_device_map(out_dir: Path) -> Dict[str, Any]:
"""Write ``device-map.json`` (write-once) pinning the node-role→device map and
the current reachability/engine snapshot, plus a containment + matched-N
assessment. Returns the document."""
out_dir = Path(out_dir)
out_dir.mkdir(parents=True, exist_ok=True)
snap = probe_grid()
engine_hosts = [d for d in snap["devices"] if d["isolated_engine_available"]]
reachable = [d for d in snap["devices"] if d["ssh_reachable"]]
down = [d["name"] for d in snap["devices"] if not d["ssh_reachable"]]
# RQ1/RQ2 hops run as isolated docker containers; a single docker host can
# host >=3 distinct containerised nodes (operator-blessed, gate item 1). So
# the topology/matched-N is honourable iff at least one isolated engine exists.
topology_honourable = len(engine_hosts) >= 1
doc = {
"schema": "sor-device-map/1",
"scope": "RQ1+RQ2 lead-paper grid pin (CLAUDE.md §Containment)",
"generated_utc": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"),
"local_docker_version": snap["local_docker_version"],
"devices": snap["devices"],
"reachable_count": len(reachable),
"isolated_engine_host_count": len(engine_hosts),
"degraded": bool(down),
"devices_down": down,
"topology_matchedN_honourable": topology_honourable,
"containment": {
"hops_run_in": "isolated docker containers only (never a phone/host forwarder)",
"external_target": "none",
"live_vm_churn_on_rq1_rq2": "none (RQ1/RQ2 use no churn; churn_schedule_id=none)",
"self_traffic_only": True,
},
"matched_N_note": (
"RQ1/RQ2 isolated hops are containerised on the docker host (>=3 distinct "
"containers = distinct nodes). Physical-phone distribution is optional; the "
"confirmatory matched-N (single-house N = federated total consenting nodes) is "
"pinned from the containerised node count at run time and recorded per manifest."
),
"assessment": (
"GO on isolated docker host" if topology_honourable
else "STOP: no isolated engine available — topology/matched-N cannot be honoured"
),
}
path = out_dir / "device-map.json"
if not path.exists():
path.write_text(json.dumps(doc, indent=2, sort_keys=True) + "\n", encoding="utf-8")
doc["_report_path"] = str(path)
return doc
+94
View File
@@ -0,0 +1,94 @@
# Stage-05 analysis clarification — HolmBonferroni over the frozen family of 7
**Status: RATIFIED by operator (Andre), 2026-07-19. Worktree clarification note
only — NOT a deviation.** This restates, for the harness, a decision the frozen
prereg **already fixes**; it does **not** edit the frozen prereg
(`sor-consent-prereg.md`, SHA `f22331a72e0d0ccf38b787e63acabbe9d666456ec76076787a6d545c3193425b`).
## Authority (this IS the freeze, not a new choice)
The size-7 family and its disclosure in the lead paper are **pre-registered text**,
so holding `m = 7` for the reported RQ1/RQ2 subset is compliance, not deviation:
- **Prereg §6 — Multiple-comparison correction [APPROVAL]** (prereg lines ~218222):
*"the confirmatory hypothesis tests across all three RQ families are corrected
together by HolmBonferroni … Test family (7 confirmatory tests): {RQ1-P1, RQ1-P2,
RQ2-P1, RQ2-P3, RQ3-P1-perf, RQ3-P1-latency, RQ3-P2}."* The family is frozen at 7.
- **Prereg D6** (prereg line ~317): *"the shared prereg + Holm-corrected family are
disclosed in whichever papers result (which is more rigorous)."* The lead paper
discloses the **same size-7 family** even while reporting only the RQ1/RQ2 subset.
Correcting the reported 4 against `m = 4` would shrink the frozen family after the
fact — p-hacking the multiplicity down, contrary to §6. Holding `m = 7` is the
only reading consistent with the frozen text.
## The question
The prereg §6 pre-registers a **confirmatory family of 7** hypothesis tests:
```
RQ1-P1, RQ1-P2, RQ2-P1, RQ2-P3, RQ3-P1-perf, RQ3-P1-latency, RQ3-P2
```
The **lead paper (G4 + RQ1 + RQ2, prereg D6)** reports only the first **4**
(RQ1/RQ2); RQ3's three tests are the severable follow-on (§8). So: when we
Holm-correct the 4 reported p-values, do we correct against **m = 7** (the frozen
family) or **m = 4** (the reported subset)?
Correcting against m = 4 would be **p-hacking the multiplicity down**: the family
was frozen at 7 *before* data, and shrinking it after the fact — because RQ3 is
being reported later — inflates the reported significance of the RQ1/RQ2 subset.
That breaks the pre-registration.
## Decision (implemented default)
**Hold the family size at m = 7 for every reported test.** The reported subset
never shrinks the correction to itself.
Concretely, Holm step-down (`stats.holm_bonferroni`, `confirm.apply_holm`):
- All reported p-values are sorted ascending and assigned ranks `k = 1..R`
(`R = 4` reported here).
- Each gets multiplier `m k + 1` with **`m = FROZEN_FAMILY_SIZE = 7`**.
- So the 4 reported tests receive multipliers **7, 6, 5, 4** (smallest p → ×7).
The step-down enforces monotone non-decreasing adjusted p-values.
- `holm_bonferroni` **raises** if `family_size < #reported` — you can never
accidentally correct against fewer than the tests you are reporting.
This is a **conservative embedding**: the 4 reported tests are treated as
occupying the 4 *smallest-multiplier* slots (7,6,5,4) of the size-7 family rather
than the true unknown slots they would occupy if all 7 p-values were in hand. The
adjusted p-values are therefore **≥** the values a full simultaneous size-7 Holm
run would assign to these same tests. We over-correct, never under-correct — the
honest direction for a subset report.
*Covered by `tests/test_sor_confirm.py::test_apply_holm_corrects_against_family_of_seven`
(top multiplier == 7; multipliers == [4,5,6,7]) and
`tests/test_sor_stats.py` (family-size monotonicity + `ValueError` when
`family_size < reported`).*
## Gate vs. Holm ordering (not conflated)
Every **reported decision** in §6 is a **CI gate** (RQ1-P1 CI excludes 0.5;
RQ1-P2 ΔAUC CI > 0; RQ2-P1 ΔH two-sided CI sign; RQ2-P3 Spearman ρ CI). The
bootstrap p-values feed the Holm step-down **only to order it** — no reported
claim rests on a bare p. Holm is the family-wise multiplicity guard layered on
top of the pre-registered CI gates, exactly as §6 / rigor-standards §Statistics
require.
## Operator ruling (2026-07-19)
The operator (Andre) ratified the **conservative embedding**: hold `family_size = 7`,
report the 4 RQ1/RQ2 tests, multipliers **7, 6, 5, 4**. This is exactly the frozen
§6 / D6 text above — the harness default now matches the pre-registration.
Two alternatives were considered and **explicitly declined**:
- *Fold RQ3's three cells back in and report all 7 simultaneously.* Statistically
clean, but RQ3 was dropped for the lead paper (prereg D6) and would need the
paid/agent selector-arm decisions. **Declined:** do not fold RQ3 back.
- *Full α-splitting / hierarchical gatekeeping.* More powerful, but requires a
pre-specified α-allocation the frozen prereg does not contain. **Declined:** not
available without a prereg change (which the freeze forbids).
No open human action remains: the implemented default is the ratified one.
+108
View File
@@ -0,0 +1,108 @@
"""Live per-cell condition assembler — each §2 cell is a genuinely distinct,
isolation-gated circuit, not the plain 3-hop control.
These are fixture-only checks: the assembler builds *plans* (ForwarderPlan targets),
opens no socket, moves no traffic, and stands up no engine. We pin that each cell's
structure encodes its condition, that determinism holds, that federated cells
genuinely span >= 2 houses, and that a non-isolated engine is refused.
"""
import pytest
from cmd_chat.sor import battery
from cmd_chat.sor.assembler import assemble, assemble_all
from cmd_chat.sor.forwarder import ContainmentError
def _cells_by_suffix():
return {c.cell_id: c for c in battery.enumerate_cells()}
def _assemble_cell(suffix, seed=7):
cell = next(c for c in battery.enumerate_cells() if c.cell_id.endswith(suffix))
return assemble(cell, seed)
def test_all_cells_distinct_fingerprints():
cells = battery.enumerate_cells()
specs = assemble_all(cells, 0)
fps = {s.fingerprint() for s in specs.values()}
assert len(fps) == len(cells) == 6 # every cell is a distinct circuit
def test_same_cell_seed_reproduces_fingerprint():
cells = battery.enumerate_cells()
a = assemble_all(cells, 0)
b = assemble_all(cells, 0)
assert {k: v.fingerprint() for k, v in a.items()} == {
k: v.fingerprint() for k, v in b.items()
}
def test_different_run_index_changes_seed_and_circuit():
cells = battery.enumerate_cells()
a = assemble_all(cells, 0)
b = assemble_all(cells, 1)
for cid in a:
assert a[cid].seed != b[cid].seed
assert a[cid].fingerprint() != b[cid].fingerprint()
def test_all_cells_isolation_gated():
specs = assemble_all(battery.enumerate_cells(), 0)
assert all(s.isolation_gated() for s in specs.values())
def test_local_engine_is_refused():
cell = battery.enumerate_cells()[0]
with pytest.raises(ContainmentError):
assemble(cell, 7, engine="local").plans()
def test_rq1_bridge_off_is_plain_single_house():
spec = _assemble_cell("bridge=off")
assert spec.bridge_present is False and spec.padding_applied is False
assert not any(h.is_bridge for h in spec.hops)
assert spec.span_houses() == 1
def test_rq1_bridge_on_inserts_a_bridge_hop():
spec = _assemble_cell("bridge=on")
assert spec.bridge_present is True and spec.padding_applied is False
assert any(h.is_bridge for h in spec.hops)
def test_rq1_bridge_on_padding_applies_padding():
spec = _assemble_cell("bridge=on+padding")
assert spec.bridge_present is True and spec.padding_applied is True
assert any(h.is_bridge for h in spec.hops)
def test_rq2_bridge_federated_spans_two_houses():
spec = _assemble_cell("topo=bridge-federated")
assert spec.bridge_present is True
assert len({h.house for h in spec.hops if h.house != "bridge"}) >= 2
def test_rq2_directory_federated_spans_two_houses():
spec = _assemble_cell("topo=directory-federated")
assert len({h.house for h in spec.hops}) >= 2
def test_assembler_dry_check_all_green(tmp_path):
summary = battery.assembler_dry_check(tmp_path / "asm")
assert summary["all_green"] is True
assert summary["distinct_fingerprints"] is True
assert summary["all_isolation_gated"] is True
assert summary["rq1_bridge_arms_live"] is True
assert summary["rq1_padding_arm_live"] is True
assert summary["rq2_federation_spans_2plus"] is True
def test_assembler_dry_report_is_write_once(tmp_path):
out = tmp_path / "asm"
first = battery.assembler_dry_check(out)
(out / "assembler-dry.json").read_text() # exists
# A second call must not overwrite the immutable report.
second = battery.assembler_dry_check(out)
assert first["distinct_fingerprints"] == second["distinct_fingerprints"]
+53
View File
@@ -0,0 +1,53 @@
"""Confirmatory launcher — freeze guard + human-gate hold (no data collected).
The live preflight is exercised by the gate/battery/grid suites; here we pin the
guard logic: the frozen-prereg SHA verifies, GO is refused without the operator
token, a tokened GO holds on grid completion (3rd phone not up yet), and even a
full grid holds for the operator rather than fabricating the data run.
"""
from cmd_chat.sor import confirmatory_run as cr
# A fully-ready preflight summary with the grid still completing (a device down).
_READY_GRID_DOWN = {
"out_dir": "x", "gate_all_green": True, "gate_offline_green": True,
"grid_reachable": 2, "grid_engine_hosts": 1, "grid_down": ["tril"],
"grid_honourable": True, "grid_full": False, "cell_plan": "p", "dry_ok": True,
"assembler_ok": True, "assembler_distinct": True, "assembler_isolation_gated": True,
"freeze_ok": True, "preflight_ready": True, "generated_utc": "now",
}
# The same, but with the full grid up (no device down).
_READY_GRID_FULL = dict(_READY_GRID_DOWN, grid_down=[], grid_full=True)
def test_frozen_prereg_sha_verifies():
assert cr.verify_freeze() is True # on-disk prereg still matches the pin
def test_go_refused_without_operator_token(monkeypatch):
monkeypatch.setattr(cr, "preflight", lambda *a, **k: dict(_READY_GRID_DOWN))
monkeypatch.delenv(cr.OPERATOR_TOKEN_ENV, raising=False)
assert cr.main(["--operator-go", "--out", "x"]) == 2
def test_go_holds_on_grid_completion(monkeypatch):
monkeypatch.setattr(cr, "preflight", lambda *a, **k: dict(_READY_GRID_DOWN))
monkeypatch.setattr(cr, "verify_freeze", lambda: True)
monkeypatch.setenv(cr.OPERATOR_TOKEN_ENV, "1")
# Triple-lock + green preflight + wired assembler, but grid still completing.
assert cr.main(["--operator-go", "--out", "x"]) == 2
def test_go_holds_for_operator_even_on_full_grid(monkeypatch):
monkeypatch.setattr(cr, "preflight", lambda *a, **k: dict(_READY_GRID_FULL))
monkeypatch.setattr(cr, "verify_freeze", lambda: True)
monkeypatch.setenv(cr.OPERATOR_TOKEN_ENV, "1")
# Full grid + all locks: the immutable data run is still the operator's to
# initiate — the launcher does not fabricate confirmatory cells.
assert cr.main(["--operator-go", "--out", "x"]) == 2
def test_preflight_held_mode_returns_zero(monkeypatch):
monkeypatch.setattr(cr, "preflight", lambda *a, **k: dict(_READY_GRID_DOWN))
assert cr.main(["--out", "x"]) == 0 # no --operator-go: held, no data
+43
View File
@@ -0,0 +1,43 @@
"""Instrument-validation gate re-confirmation — offline items (2-6).
Item 1 (live 3-hop e2e) needs a docker daemon + sor-hop image, so it is exercised
separately by the forwarder e2e test; here we drive the gate with ``allow_live=
False`` and assert every *offline* item is green and the report is write-once.
"""
import json
from cmd_chat.sor import gate
def test_offline_gate_items_all_green(tmp_path):
report = gate.run_gate(tmp_path, allow_live=False)
assert report["offline_items_green"] is True
by_n = {it["n"]: it for it in report["items"]}
for n in (2, 3, 4, 5, 6):
assert by_n[n]["status"] == "green", (n, by_n[n])
# With live disabled, item 1 is 'unavailable' (not a red failure).
assert by_n[1]["status"] == "unavailable"
assert report["all_green"] is False # item 1 not confirmed in this offline run
def test_gate_report_is_write_once(tmp_path):
gate.run_gate(tmp_path, allow_live=False)
path = tmp_path / "gate-report.json"
first = path.read_text()
gate.run_gate(tmp_path, allow_live=False) # second call must not overwrite
assert path.read_text() == first
doc = json.loads(first)
assert doc["schema"] == "sor-gate-report/1"
def test_item5_isolation_refuses_local_accepts_docker(tmp_path):
report = gate.run_gate(tmp_path, allow_live=False)
ev = next(it["evidence"] for it in report["items"] if it["n"] == 5)
assert ev["refused_local"] and ev["refused_unknown"] and ev["accepts_docker"]
def test_item4_entropy_is_exact_log2n(tmp_path):
report = gate.run_gate(tmp_path, allow_live=False)
ev = next(it["evidence"] for it in report["items"] if it["n"] == 4)
assert all(c["exact"] for c in ev["checks"])