6d61c53152
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>
185 lines
8.5 KiB
Python
185 lines
8.5 KiB
Python
"""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())
|