Files
hack-house/cmd_chat/sor/churn.py
T
leetcrypt 12fd2537e3 R7 (live halves): seeded churn + rebuild selector + metrics.json
churn.py emits a seed-deterministic kill/spawn schedule (pure data — no real VM
spin/kill; the live fabric half stays gated by the containment law). selector.py
consumes the schedule and rebuilds a circuit whenever a kill drops one of its
hops, across static | random | agent strategies; the paid frontier-model agent
arm (GOAL envelope (c)) is human-gated and NOT wired — the agent strategy here is
a local stability heuristic that spends nothing. analysis/metrics.py aggregates
the four DV families (RQ1 correlation AUC, RQ2 entropy bits, RQ3 throughput
retention + rebuild-classifier AUC) into a schema-valid, write-once metrics.json.
Acceptance check green: under a fixed churn seed the selector rebuilds every
dropped circuit (every_drop_rebuilt, all strategies) and metrics.json is
produced. Python R7 selector suite 11 passed; full SOR suite 92 passed.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-07-19 17:18:44 -07:00

72 lines
2.6 KiB
Python

"""R7 — Seeded churn schedule (deterministic node kill/spawn stream).
The churn generator produces a **schedule** — a reproducible list of kill/spawn
events drawn from the R1 ``Domain.CHURN`` stream — that models nodes dropping out
of and rejoining the grid over time. It is pure data: this module spins and kills
no real VM (that live half runs against the isolated hackhouse VM fabric and is
gated by the same containment law as the R4 forwarder). Producing the schedule
here, deterministically from the seed, is what lets the R7 acceptance check assert
that a fixed churn seed drives the selector to rebuild *every* dropped circuit —
verifiable entirely offline.
"""
from __future__ import annotations
from dataclasses import dataclass
from typing import List
from cmd_chat.sor.config import Domain, SorRng
@dataclass(frozen=True)
class ChurnEvent:
"""One scheduled grid event at logical step ``t``. ``kind`` is ``"kill"`` or
``"spawn"``; ``node`` is the affected node id."""
t: int
kind: str # "kill" | "spawn"
node: str
def churn_schedule(
seed: int,
nodes: List[str],
steps: int,
kill_prob_pct: int = 30,
) -> List[ChurnEvent]:
"""Deterministically build a churn schedule over ``nodes`` for ``steps`` logical
steps, drawing from the seed's CHURN stream alone (so the same seed yields the
same schedule — the R7 determinism the selector check relies on).
At each step every currently-live node may be killed with probability
``kill_prob_pct``%, and every currently-dead node is respawned with the same
probability. Events are emitted in a stable (step, node) order."""
if not nodes or steps <= 0:
return []
s = SorRng(seed).stream(Domain.CHURN)
live = {n: True for n in nodes}
events: List[ChurnEvent] = []
for t in range(steps):
for n in nodes: # stable order -> stable schedule
roll = s.next_below(100)
if live[n]:
if roll < kill_prob_pct:
live[n] = False
events.append(ChurnEvent(t, "kill", n))
else:
if roll < kill_prob_pct:
live[n] = True
events.append(ChurnEvent(t, "spawn", n))
return events
def live_nodes_at(nodes: List[str], schedule: List[ChurnEvent], t: int) -> List[str]:
"""The set of live nodes at (through the end of) step ``t``, replaying the
schedule from the all-live initial state. Deterministic."""
live = {n: True for n in nodes}
for ev in schedule:
if ev.t > t:
break
live[ev.node] = ev.kind == "spawn"
return [n for n in nodes if live[n]]