Files
momentum-system/research/experiments/registry.py
T
momentum devandClaude Fable 5 16f880d372 experiment-os: 'run' self-registers the declared cohort (fresh-DB bootstrap)
The scheduler ran 'experiments run/adjudicate' but nothing registered the five
experiments/*.json into the experiments table, so on a fresh DB the harness
replayed over an empty set and the ledger stayed empty forever (owner hit this
on first deploy). registry.register_cohort() registers every cohort JSON
idempotently (skips already-registered, tolerates a malformed file); 'run'
calls it at startup so a fresh deployment self-bootstraps with no manual
register step. +test.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 10:14:51 -04:00

133 lines
5.0 KiB
Python

"""Experiment registry — schema apply + immutable register/list/get.
The registry owns the ``experiments`` contract table. Registration is
one-shot and immutable: an id can never be overwritten (a pre-registered bar
that could be re-tuned after seeing the data is not pre-registered). The
required-keys check refuses any experiment missing its fidelity floors — the
single guard that keeps a zero-slippage artifact (vr1/liq1 class) out.
"""
from __future__ import annotations
import json
import os
from datetime import datetime, timezone
from pathlib import Path
_SCHEMA_SQL = Path(__file__).resolve().parents[2] / "storage" / "experiments_schema.sql"
_COHORT_DIR = Path(__file__).resolve().parents[2] / "experiments"
# Every experiment JSON must carry these; fidelity must carry these floors.
_REQUIRED = ("id", "hypothesis", "population", "intervention", "bars", "fidelity")
_REQUIRED_FIDELITY = ("atr_frac", "spread_mult", "adverse_ticks")
_TERMINAL = {"passed", "failed", "killed"}
def apply_schema(con) -> None:
"""Create the experiment tables idempotently on ``con``.
Honors ``PG_SKIP_SCHEMA`` exactly like storage/db_pg.get_connection: set it
when the owner provisions the DDL out-of-band and the harness should not
touch DDL. On an isolated test schema the connection's search_path is
already pinned, so the CREATEs land in that schema.
"""
if os.environ.get("PG_SKIP_SCHEMA", "").strip().lower() in {"1", "true", "yes"}:
return
con.execute(_SCHEMA_SQL.read_text())
def _validate(spec: dict) -> None:
missing = [k for k in _REQUIRED if k not in spec]
if missing:
raise ValueError(f"experiment missing required keys: {missing}")
fid = spec["fidelity"]
if not isinstance(fid, dict):
raise ValueError("fidelity must be an object with slippage floors")
fmissing = [k for k in _REQUIRED_FIDELITY if k not in fid]
if fmissing:
raise ValueError(f"fidelity missing required floors: {fmissing}")
def register(con, json_path: str | Path) -> str:
"""Register one experiment from a JSON file. Immutable: refuses to overwrite
an existing id. Returns the registered id."""
spec = json.loads(Path(json_path).read_text())
_validate(spec)
exp_id = spec["id"]
if get(con, exp_id) is not None:
raise ValueError(f"experiment {exp_id!r} already registered (immutable)")
con.execute(
"INSERT INTO experiments "
"(id, registered_at, hypothesis, population, intervention, bars, futility, "
" fidelity, status) "
"VALUES (?, ?, ?, ?::jsonb, ?::jsonb, ?::jsonb, ?::jsonb, ?::jsonb, 'running')",
[
exp_id,
spec.get("registered_at") or datetime.now(timezone.utc).isoformat(),
spec["hypothesis"],
json.dumps(spec["population"]),
json.dumps(spec["intervention"]),
json.dumps(spec["bars"]),
json.dumps(spec.get("futility")) if spec.get("futility") is not None else None,
json.dumps(spec["fidelity"]),
],
)
return exp_id
def register_cohort(con, cohort_dir: str | Path | None = None) -> list[str]:
"""Register every ``experiments/*.json`` not already registered (idempotent).
The declared cohort must be in the ``experiments`` table before ``run`` can
replay anything — otherwise the harness silently does nothing on a fresh DB.
``run`` calls this at startup so a fresh deployment self-bootstraps with no
manual ``register`` step. Already-registered ids are skipped (immutable);
a malformed file is logged and skipped, never fatal to the run."""
apply_schema(con)
d = Path(cohort_dir) if cohort_dir else _COHORT_DIR
newly: list[str] = []
for f in sorted(d.glob("*.json")):
try:
newly.append(register(con, f))
except ValueError as exc:
# already registered (the common nightly case) or malformed — skip it
if "already registered" not in str(exc):
import logging
logging.getLogger(__name__).warning("skip cohort file %s: %s", f.name, exc)
return newly
def _row_to_dict(row) -> dict:
return {
"id": row[0],
"registered_at": row[1],
"hypothesis": row[2],
"population": row[3],
"intervention": row[4],
"bars": row[5],
"futility": row[6],
"fidelity": row[7],
"status": row[8],
"verdict_at": row[9],
"verdict_note": row[10],
}
_COLS = ("id, registered_at, hypothesis, population, intervention, bars, futility, "
"fidelity, status, verdict_at, verdict_note")
def get(con, exp_id: str) -> dict | None:
row = con.execute(
f"SELECT {_COLS} FROM experiments WHERE id = ?", [exp_id]).fetchone()
return _row_to_dict(row) if row else None
def list_experiments(con, running_only: bool = False) -> list[dict]:
sql = f"SELECT {_COLS} FROM experiments"
if running_only:
sql += " WHERE status = 'running'"
sql += " ORDER BY registered_at"
return [_row_to_dict(r) for r in con.execute(sql).fetchall()]