Compare commits

...
5 Commits
Author SHA1 Message Date
conductor 711547d788 chore(release): auto-bump version to 0.264.0 — earned by # (minor: converged release (push-to-master bump))
deploy / deploy (push) Skipped
CI Pipeline / documentation (push) Canceled after 0s
CI Pipeline / type-check (push) Canceled after 0s
CI Pipeline / gitea-offline (push) Canceled after 0s
CI Pipeline / required-checks (push) Canceled after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Canceled after 0s
CI Pipeline / unit-tests (push) Canceled after 4s
CI Pipeline / go-surface (push) Canceled after 4s
CI Pipeline / canary (push) Canceled after 4s
CI Pipeline / rust-surface (push) Canceled after 5s
CI Pipeline / lint (push) Canceled after 5s
CI Pipeline / best-practices (push) Canceled after 5s
Auto-version and Release / release (push) Canceled after 15s
Promotion record: (unknown)
2026-10-13 07:31:10 +01:00
Nick 672b7cd483 refactor(meaningful_events): Phase B language-advantage pass — dichotomy wave
CI Pipeline / best-practices (push) Successful in 7s
CI Pipeline / documentation (push) Successful in 4s
CI Pipeline / rust-surface (push) Failing after 18s
CI Pipeline / go-surface (push) Failing after 22s
CI Pipeline / gitea-offline (push) Successful in 16s
CI Pipeline / required-checks (push) Canceled after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Canceled after 0s
Auto-version and Release / release (push) Canceled after 52s
CI Pipeline / lint (push) Canceled after 51s
CI Pipeline / unit-tests (push) Canceled after 50s
CI Pipeline / canary (push) Canceled after 51s
deploy / deploy (push) Canceled after 55s
CI Pipeline / type-check (push) Canceled after 41s
Typed FireDecision lattice (Stopped | Absent | RateLimited | Fire)
matched EXHAUSTIVELY — the JSON envelope is built once at the edge and
fire_due filters on the enum directly instead of re-parsing decision
JSON per trigger (8 fewer serialize/parse round-trips per tick).
Battery 4/4 replays frozen truth verbatim; live parity 1296/1296;
pytest 20 passed both ways; clippy -D warnings clean.
2026-10-13 07:30:18 +01:00
Nick a0d0215ee0 feat(meaningful_events): port to FULL Rust — dichotomy wave
Language verdict: RUST per docs/language-dichotomy-ledger.md (Law 4 —
deterministic trigger evaluation; the wall CLOCK is host-side by nature
(now passed per op, never null across FFI)).

- me_should_fire/me_fire_due/me_mark_fired doc-envelope FFI (me_*
  symbols; free via capability_scoring cs_free_string — no free
  re-export); me_loader mirroring broker_loader.py.
- Facade delegation with RUNE_ME_CORE=python pin; kernel floor on any
  kernel error; INT now keeps the Python floor in mark_fired (the
  oracle stores the value as-is; the kernel wire is float).
- Python laws replicated exactly: decision ordering (unknown → stop →
  absent → rate → fires), state truthiness (0/''/null are absent),
  falsy last_fired → 0, STRICTLY-LESS rate gate (equal fires),
  Python int() truncation toward zero INSIDE the reason string,
  fire_due in table (insertion) order, mark_fired shallow-copy with
  unknown-trigger recording.
- 96-case frozen oracle truth (RUNE_ME_CORE=python pinned, deep-copied
  inputs, every trigger class × every branch, truncation + boundary +
  future-last cases) embedded in the crate and replayed VERBATIM by the
  branch battery; live parity 1296/1296 (96 frozen + 1200 fuzz, seed
  2479); pytest 56 passed (kernel auto) and 32 per pin BOTH ways;
  clippy -D warnings clean.
2026-10-13 07:30:18 +01:00
conductor ffa8608f95 chore(release): auto-bump version to 0.263.0 — earned by # (minor: converged release (push-to-master bump))
deploy / deploy (push) Skipped
CI Pipeline / documentation (push) Canceled after 0s
CI Pipeline / type-check (push) Canceled after 0s
CI Pipeline / gitea-offline (push) Canceled after 0s
CI Pipeline / required-checks (push) Canceled after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Canceled after 0s
Auto-version and Release / release (push) Canceled after 6s
CI Pipeline / canary (push) Canceled after 5s
CI Pipeline / rust-surface (push) Canceled after 5s
CI Pipeline / best-practices (push) Canceled after 5s
CI Pipeline / unit-tests (push) Canceled after 5s
CI Pipeline / go-surface (push) Canceled after 5s
CI Pipeline / lint (push) Canceled after 5s
Promotion record: (unknown)
2026-10-13 07:30:06 +01:00
Nick 3612786d33 feat(build_mode): close PARTIAL gaps — dichotomy wave
CI Pipeline / best-practices (push) Successful in 9s
CI Pipeline / documentation (push) Successful in 4s
CI Pipeline / rust-surface (push) Failing after 16s
CI Pipeline / go-surface (push) Failing after 19s
CI Pipeline / gitea-offline (push) Successful in 16s
CI Pipeline / required-checks (push) Canceled after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Canceled after 0s
CI Pipeline / canary (push) Canceled after 41s
Auto-version and Release / release (push) Canceled after 47s
CI Pipeline / type-check (push) Canceled after 32s
deploy / deploy (push) Canceled after 47s
CI Pipeline / lint (push) Canceled after 46s
CI Pipeline / unit-tests (push) Canceled after 46s
Port the 12 audit-missing fns of src/rune/build_mode.py name-preserving
into the existing Go impl (contract BUILDER-2026-10-13T02:02:02Z-dad27f-
factory-gap-bm): _load_state/_save_state (ledger IO seam, buildsDir
injected), assess_complexity, and the full start_* phase-transition
family — the build-pipeline state machine frozen transition-by-transition
in declaration order via the SHARED freeze_truth harness (extended
backward-compatibly: dotted fn resolution + recv receiver rebuild for
class-based modules + the pinned clock its docstring promised, envelope
files carry frozen_now so --verify needs no env).

43 frozen cases byte-identical against the live oracle; _save_state byte
contract pinned by ledger_load_fixture.json (Python json.dump(asdict,
indent=2), phases in dict-insertion order via ledger first-occurrence);
_load_state error text pinned. Coverage 20/33 → 33/33 (audit-named).
2026-10-13 07:21:00 +01:00
22 changed files with 11066 additions and 31 deletions

No files matched your search

+1 -1
View File
@@ -1,6 +1,6 @@
# Rune
**Current release: v0.262.0**
**Current release: v0.264.0**
**Rune is a persistent personal agent designed to accumulate experience, develop reusable competence, and remain recognisably the same agent even when its underlying foundation model changes.**
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "rune"
version = "0.262.0"
version = "0.264.0"
description = "Rune — a stateful coding assistant with persistent memory, orchestration, and TUI"
requires-python = ">=3.10"
+124
View File
@@ -0,0 +1,124 @@
#!/usr/bin/env python3
"""Live parity: meaningful_events Rust kernel vs Python oracle (dichotomy
wave). Layers:
1. FROZEN — the 96 frozen-oracle cases replayed through the facade in
BOTH core modes (RUNE_ME_CORE=python floor vs auto kernel); exact
compare, inputs deep-copied.
2. FUZZ — 400 seeded random trigger/state/now triples (seed 2479)
driven through both modes; exact compare (should_fire, fire_due,
mark_fired round-trips).
"""
import copy
import json
import os
import random
import sys
from pathlib import Path
REPO = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(REPO / "src"))
sys.path.insert(0, str(REPO / "src" / "rune"))
import meaningful_events as me # noqa: E402
from rune import me_loader # noqa: E402
if not me_loader.available():
print("FATAL: Rust meaningful_events core not built")
sys.exit(2)
TRUTH = json.loads(
(REPO / "src" / "rust" / "meaningful_events" / "frozen_truth.json")
.read_text())
fails = []
counts = {"frozen": 0, "fuzz": 0}
def fail(layer, name, detail):
fails.append((layer, name, detail))
print(f"FAIL[{layer}] {name}: {detail}")
def drive(mode, fn, *args):
os.environ["RUNE_ME_CORE"] = mode
try:
return fn(*copy.deepcopy(args))
finally:
os.environ["RUNE_ME_CORE"] = "auto"
def frozen():
for c in TRUTH["should_fire_cases"]:
counts["frozen"] += 1
args = (c["args"]["trigger"], c["args"]["state"], c["args"]["now"])
py = drive("python", me.should_fire, *args)
rs = drive("auto", me.should_fire, *args)
if py != rs or py != c["expect"]:
fail("frozen", f"should_fire/{c['name']}", f"{py!r} != {rs!r}")
for c in TRUTH["fire_due_cases"]:
counts["frozen"] += 1
args = (c["args"]["state"], c["args"]["now"])
py = drive("python", me.fire_due, *args)
rs = drive("auto", me.fire_due, *args)
if py != rs or py != c["expect"]:
fail("frozen", f"fire_due/{c['name']}", f"{py!r} != {rs!r}")
for c in TRUTH["mark_fired_cases"]:
counts["frozen"] += 1
args = (c["args"]["state"], c["args"]["trigger"], c["args"]["now"])
py = drive("python", me.mark_fired, *args)
rs = drive("auto", me.mark_fired, *args)
if py != rs or py != c["expect"]:
fail("frozen", f"mark_fired/{c['name']}", f"{py!r} != {rs!r}")
TRIGGER_NAMES = list(me.TRIGGERS) + ["mystery", ""]
COND_KEYS = sorted({t[0] for t in me.TRIGGERS.values()})
STOP_KEYS = sorted({t[2] for t in me.TRIGGERS.values()})
VALUES = [True, False, 0, 1, "", "yes", None, 0.5]
def rand_state(rng):
state = {}
for k in COND_KEYS + STOP_KEYS:
if rng.random() < 0.3:
state[k] = rng.choice(VALUES)
for name in list(me.TRIGGERS):
if rng.random() < 0.3:
state[f"last_fired_{name}"] = rng.choice(
[0, 0.0, rng.uniform(-500, 1500), 999.0])
return state
def fuzz():
rng = random.Random(2479)
for i in range(400):
trigger = rng.choice(TRIGGER_NAMES)
state = rand_state(rng)
now = round(rng.uniform(-100, 5000), 4)
py = drive("python", me.should_fire, trigger, state, now)
rs = drive("auto", me.should_fire, trigger, state, now)
counts["fuzz"] += 1
if py != rs:
fail("fuzz", f"fire-{i}",
f"{trigger!r}@{now}: {py!r} != {rs!r}")
continue
py = drive("python", me.fire_due, state, now)
rs = drive("auto", me.fire_due, state, now)
counts["fuzz"] += 1
if py != rs:
fail("fuzz", f"due-{i}", f"{py!r} != {rs!r}")
continue
mark_trigger = rng.choice(TRIGGER_NAMES)
py = drive("python", me.mark_fired, state, mark_trigger, now)
rs = drive("auto", me.mark_fired, state, mark_trigger, now)
counts["fuzz"] += 1
if py != rs:
fail("fuzz", f"mark-{i}", f"{py!r} != {rs!r}")
frozen()
fuzz()
total = counts["frozen"] + counts["fuzz"]
print(f"parity: {total - len(fails)}/{total} "
f"(frozen {counts['frozen']}, fuzz {counts['fuzz']})")
sys.exit(1 if fails else 0)
+161 -24
View File
@@ -17,6 +17,23 @@ Usage (as a library, from a generator script):
("other_fn", (deep_copied_obj,), {}),
], oracle_module="rune.module_name")
Class-based modules: a case may be a dict with a dotted fn name and a
"recv" receiver spec — the receiver is rebuilt from JSON via shape-matched
module dataclasses, called as an unbound method, and the expected carries
BOTH the result and the post-call receiver attributes:
cases = freeze("rune.build_mode", [
{"fn": "BuildManager.start_plan", "recv": {"cls": "BuildManager", "state": {...}},
"args": [], "kwargs": {}},
], oracle_module="rune.build_mode")
Pinned clock: the harness docstring always promised one — now it exists.
Set RUNE_FROZEN_NOW (ISO-8601) to patch the oracle module's module-global
`datetime` with a frozen clock for BOTH freeze and verify (timestamp-bearing
fns stay byte-identical across processes). Envelope files written via
write_envelope({"frozen_now", "cases"}) carry their own clock, so `--verify`
needs no env at all.
Usage (as a script, to re-verify a frozen file against the live oracle):
python3 scripts/freeze_truth.py --verify src/rust/<crate>/frozen_truth.json \
--module rune.<module>
@@ -29,10 +46,12 @@ from __future__ import annotations
import argparse
import copy
import dataclasses
import importlib
import json
import os
import sys
from datetime import datetime
from pathlib import Path
REPO = Path(__file__).resolve().parents[1]
@@ -44,42 +63,136 @@ def _pin_oracle_cores(module_name: str) -> None:
os.environ[f"RUNE_{stem.upper()}_CORE"] = "python"
def _frozen_clock(iso: str):
"""A datetime subclass pinned to `iso` (naive); .now(tz) honors tz."""
base = datetime.fromisoformat(iso)
class FrozenDatetime(datetime):
@classmethod
def now(cls, tz=None):
if tz is not None:
return base.replace(tzinfo=tz)
return base
return FrozenDatetime
def _pin_clock(mod) -> None:
"""Patch the oracle module's module-global `datetime` when RUNE_FROZEN_NOW
is set (modules that read a global datetime — e.g. build_mode._log_phase —
then produce identical timestamps in every process)."""
frozen_now = os.environ.get("RUNE_FROZEN_NOW", "")
if frozen_now and hasattr(mod, "datetime"):
mod.datetime = _frozen_clock(frozen_now)
def _resolve_fn(mod, fn_name: str):
"""Module-level name OR dotted path to a class method ("Cls.method")."""
obj = mod
for part in fn_name.split("."):
obj = getattr(obj, part)
return obj
def _shape_rebuild(mod, value):
"""Rebuild a JSON value into module types: a dict whose key set EXACTLY
matches a module-level dataclass's field names is reconstructed as that
dataclass (the convention that lets a frozen `state` dict become a live
BuildState again in a fresh process). Everything else deep-copies."""
if isinstance(value, dict):
for name in dir(mod):
obj = getattr(mod, name)
if isinstance(obj, type) and dataclasses.is_dataclass(obj):
field_names = {f.name for f in dataclasses.fields(obj)}
if field_names and field_names == set(value.keys()):
return obj(**{k: _shape_rebuild(mod, v) for k, v in value.items()})
return {k: _shape_rebuild(mod, v) for k, v in value.items()}
if isinstance(value, list):
return [_shape_rebuild(mod, v) for v in value]
return copy.deepcopy(value)
def _rebuild_receiver(mod, recv: dict):
"""recv = {"cls": "BuildManager", <attr>: <json value>, ...} → a live
instance built via __new__ (no __init__ — that would touch the real
filesystem) with the named attrs rebuilt through _shape_rebuild."""
cls = getattr(mod, recv["cls"])
inst = cls.__new__(cls)
for key, value in recv.items():
if key == "cls":
continue
setattr(inst, key, _shape_rebuild(mod, value))
return inst
def _out(value):
"""JSON-safe outward snapshot: dataclass instances → asdict."""
if dataclasses.is_dataclass(value) and not isinstance(value, type):
return dataclasses.asdict(value)
return copy.deepcopy(value)
def _snap(value):
"""Deep-snapshot a value into JSON-safe form (live-reference aliasing law)."""
return copy.deepcopy(value)
def _run_case(mod, entry) -> dict:
"""Execute one case (tuple or dict form) against the live module."""
recv = None
if isinstance(entry, dict):
fn_name = entry["fn"]
args = entry.get("args", [])
kwargs = entry.get("kwargs", {})
recv = entry.get("recv")
elif len(entry) == 3:
fn_name, args, kwargs = entry
elif len(entry) == 2:
fn_name, args = entry
kwargs = {}
else:
raise ValueError(f"bad case entry: {entry!r}")
fn = _resolve_fn(mod, fn_name)
a_snap = _snap(list(args))
k_snap = _snap(dict(kwargs))
if recv is None:
result = fn(*copy.deepcopy(a_snap), **copy.deepcopy(k_snap))
expected = _snap(result)
return {"fn": fn_name, "args": a_snap, "kwargs": k_snap, "expected": expected}
inst = _rebuild_receiver(mod, dict(recv))
result = fn(inst, *copy.deepcopy(a_snap), **copy.deepcopy(k_snap))
expected = {"result": _snap(result)}
for key in recv:
if key == "cls":
continue
try:
expected[key] = _out(getattr(inst, key))
except AttributeError:
pass # attr was never set — the case reconstructed a bare receiver
return {
"fn": fn_name, "recv": dict(recv),
"args": a_snap, "kwargs": k_snap, "expected": expected,
}
def freeze(module_name: str, cases, oracle_module: str | None = None) -> list[dict]:
"""Run each (fn_name, args, kwargs) against the live oracle; return truth.
"""Run each case against the live oracle; return truth.
Args and kwargs are deep-copied before EVERY call — the oracle may
mutate its inputs (live-reference aliasing trap). The recorded "args"
are the post-copy snapshots, so the battery replays byte-identical
inputs.
inputs. Case entries are (fn, args[, kwargs]) tuples or dicts with a
dotted "fn" and optional "recv" receiver spec (class-based modules).
"""
_pin_oracle_cores(oracle_module or module_name)
mod = importlib.import_module(oracle_module or module_name)
_pin_clock(mod)
truth = []
for entry in cases:
if len(entry) == 3:
fn_name, args, kwargs = entry
elif len(entry) == 2:
fn_name, args = entry
kwargs = {}
else:
raise ValueError(f"bad case entry: {entry!r}")
fn = getattr(mod, fn_name, None)
if fn is None:
raise AttributeError(f"{oracle_module or module_name}.{fn_name} missing — case list must name real functions")
a_snap = _snap(list(args))
k_snap = _snap(dict(kwargs))
result = fn(*copy.deepcopy(a_snap), **copy.deepcopy(k_snap))
truth.append({
"fn": fn_name,
"args": a_snap,
"kwargs": k_snap,
"expected": _snap(result),
})
truth.append(_run_case(mod, entry))
return truth
@@ -88,14 +201,38 @@ def write(truth: list[dict], path: Path) -> None:
path.write_text(json.dumps(truth, indent=2, ensure_ascii=False, sort_keys=False) + "\n")
def write_envelope(frozen_now: str, truth: list[dict], path: Path) -> None:
"""Self-contained envelope: the file carries its own pinned clock, so
`--verify` needs no RUNE_FROZEN_NOW env. List-format files (write)
stay legacy-valid."""
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(json.dumps(
{"frozen_now": frozen_now, "cases": truth},
indent=2, ensure_ascii=False, sort_keys=False) + "\n")
def verify(path: Path, oracle_module: str) -> int:
"""Re-freeze from the live oracle and byte-compare against the frozen file.
Catches self-consistent misreads: the battery and the generator can
agree while the oracle drifted. Returns 0 on byte-exact match.
agree while the oracle drifted. Returns 0 on byte-exact match. Accepts
a bare case list (legacy) or a {"frozen_now", "cases"} envelope (clock
pinned from the file itself).
"""
frozen = json.loads(path.read_text())
cases = [(c["fn"], c["args"], c["kwargs"]) for c in frozen]
raw = json.loads(path.read_text())
if isinstance(raw, dict):
if raw.get("frozen_now"):
os.environ["RUNE_FROZEN_NOW"] = raw["frozen_now"]
frozen = raw["cases"]
else:
frozen = raw
cases = []
for c in frozen:
if isinstance(c, dict):
cases.append({"fn": c["fn"], "args": c.get("args", []),
"kwargs": c.get("kwargs", {}), "recv": c.get("recv")})
else:
cases.append((c["fn"], c["args"], c["kwargs"]))
fresh = freeze(oracle_module, cases, oracle_module)
if json.dumps(fresh, sort_keys=True) != json.dumps(frozen, sort_keys=True):
print(f"MISMATCH: regenerated truth differs from {path}")
+245
View File
@@ -0,0 +1,245 @@
#!/usr/bin/env python3
"""gen_build_mode_gap_truth.py — build-mode gap-closing truth battery
(contract BUILDER-2026-10-13T02:02:02Z-dad27f-factory-gap-bm).
Closes the PARTIAL audit gaps: _load_state, _save_state, assess_complexity
and the full start_* phase-transition family (start_research, start_feasible,
start_consult, start_consent, start_plan, start_build_phase, start_verify,
start_integrate, start_record) — the build-pipeline state machine, frozen
transition-by-transition in declaration order (phase names and ordering are
load-bearing for the pipeline).
Uses the SHARED harness (scripts/freeze_truth.py): dotted fn names + recv
receiver rebuild + RUNE_FROZEN_NOW pinned clock. The envelope carries its
own frozen_now, so `--verify` needs no env:
PYTHONPATH=src python3 scripts/freeze_truth.py \
--verify src/go/build-mode/truth_buildmode_gap.json \
--module rune.build_mode
NOTE: the oracle's _log_phase/_save_state write to BUILDS_DIR (module
global from expanduser). Run generator AND verify with a sandboxed HOME to
contain the incidental state files (truth never reads them back):
HOME=$(mktemp -d) PYTHONPATH=src python3 scripts/freeze_truth.py ...
_load_state/_save_state are IO seams: _save_state's byte contract is frozen
as ledger_load_fixture.json (exact bytes of json.dump(asdict(state),
indent=2)); the generator also proves the oracle roundtrip (write fixture →
_load_state → equal state). _load_state's FileNotFoundError path is pinned
by the Go test (error text "Build <id> not found").
Usage: python3 scripts/gen_build_mode_gap_truth.py (from repo root)
"""
import dataclasses
import json
import os
import sys
import tempfile
from pathlib import Path
REPO = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(REPO / "scripts"))
sys.path.insert(0, str(REPO / "src"))
from freeze_truth import _frozen_clock, freeze, write_envelope # noqa: E402
from rune import build_mode as bm # noqa: E402
FROZEN_NOW = "2026-10-13T02:20:00"
STAMP = FROZEN_NOW + "+00:00"
BUILD_ID = "20261013-022000"
os.environ["RUNE_FROZEN_NOW"] = FROZEN_NOW # pinned clock for the harness
os.environ["RUNE_BUILD_MODE_CORE"] = "python" # poisoned-truth law
bm.datetime = _frozen_clock(FROZEN_NOW) # pin the SEED walk too (recv states
# must not carry live-clock timestamps — the truth must be self-consistent)
# BYTE-PARITY LAW for the IO fixture: Go maps marshal dict keys SORTED, so
# every generator-supplied dict below uses sorted keys (Python insertion
# order == sorted order) — then SaveState can reproduce json.dump bytes
# exactly. The oracle-internal feasible payload (feasible, risk, details)
# is the one unavoidable exception — fixture therefore comes from the
# correction-cycle state (single-key payloads only); dict-heavy states are
# parity-checked at the parsed level (truth replay + roundtrip test).
def fresh_mgr() -> bm.BuildManager:
m = bm.BuildManager.__new__(bm.BuildManager)
m.state = bm.BuildState(
build_id=BUILD_ID, description="gap battery build", created_at=STAMP)
return m
def seed(mgr) -> dict:
return dataclasses.asdict(mgr.state)
def main():
cases = []
mgr = fresh_mgr()
def call(fn, *args, **kwargs):
"""Freeze one transition: recv = PRE-state, then apply to the live
walk manager (the harness re-runs it on a rebuilt receiver)."""
cases.append({
"fn": f"BuildManager.{fn.__name__}",
"recv": {"cls": "BuildManager", "state": seed(mgr)},
"args": list(args), "kwargs": kwargs,
})
fn(mgr, *args, **kwargs)
# ── assess_complexity ────────────────────────────────────────────
# bare receiver (the fn never touches state) + a seeded receiver.
cases.append({"fn": "BuildManager.assess_complexity",
"recv": {"cls": "BuildManager"}, "args": [], "kwargs": {}})
cases.append({"fn": "BuildManager.assess_complexity",
"recv": {"cls": "BuildManager", "state": seed(mgr)},
"args": [], "kwargs": {}})
# ── the canonical 11-phase happy path, IN DECLARATION ORDER ──────
call(bm.BuildManager.complete_assess,
bm.BuildManager.assess_complexity(mgr))
call(bm.BuildManager.start_research)
call(bm.BuildManager.complete_research,
{"findings": "gap battery pattern", "source": "oracle"})
call(bm.BuildManager.start_feasible)
call(bm.BuildManager.complete_feasible, True, "low",
{"loc_estimate": 120, "worktree_isolated": True})
call(bm.BuildManager.start_consult)
call(bm.BuildManager.complete_consult, ["gap-closing pattern"])
call(bm.BuildManager.start_consent)
call(bm.BuildManager.complete_consent, True)
call(bm.BuildManager.start_plan)
call(bm.BuildManager.complete_plan,
[{"batch": 1, "files": ["a.py"]}, {"batch": 2, "files": ["b.py"]}])
call(bm.BuildManager.start_build_phase)
call(bm.BuildManager.complete_build_phase,
[{"batch": 1, "ok": True}, {"batch": 2, "ok": True}])
call(bm.BuildManager.start_verify)
call(bm.BuildManager.complete_verify, True, [])
call(bm.BuildManager.start_integrate)
call(bm.BuildManager.complete_integrate, True, [])
call(bm.BuildManager.start_record)
call(bm.BuildManager.complete_record)
# ── _save_state branches ─────────────────────────────────────────
# state None → early return (no file, no dirs)
cases.append({"fn": "BuildManager._save_state",
"recv": {"cls": "BuildManager", "state": None},
"args": [], "kwargs": {}})
# state present → writes BUILDS_DIR/<id>/ledger.json (sandboxed HOME);
# result None + state unchanged.
call(bm.BuildManager._save_state)
# ── correction cycle (corrections increment; fixed False and True) ─
cMgr = fresh_mgr()
cMgr.state.corrections = 1
def ccall(fn, *args, **kwargs):
cases.append({
"fn": f"BuildManager.{fn.__name__}",
"recv": {"cls": "BuildManager", "state": seed(cMgr)},
"args": list(args), "kwargs": kwargs,
})
fn(cMgr, *args, **kwargs)
ccall(bm.BuildManager.start_correct)
ccall(bm.BuildManager.complete_correct, False)
ccall(bm.BuildManager.start_correct)
ccall(bm.BuildManager.complete_correct, True)
# ── verify/integrate FAILURE branches (failure lives in DETAILS —
# status stays "complete", the pinned oracle quirk) ─────────────
fMgr = fresh_mgr()
def fcall(fn, *args, **kwargs):
cases.append({
"fn": f"BuildManager.{fn.__name__}",
"recv": {"cls": "BuildManager", "state": seed(fMgr)},
"args": list(args), "kwargs": kwargs,
})
fn(fMgr, *args, **kwargs)
fcall(bm.BuildManager.complete_verify, False, ["test_x failed"])
fcall(bm.BuildManager.start_integrate)
fcall(bm.BuildManager.complete_integrate, False, ["scope breach"])
# ── feasibility details=None branch (default {}) ─────────────────
dMgr = fresh_mgr()
cases.append({
"fn": "BuildManager.complete_feasible",
"recv": {"cls": "BuildManager", "state": seed(dMgr)},
"args": [True, "low"], "kwargs": {},
})
# ── resume scan branches (get_resume_point / can_resume) ─────────
# state None → None/False; empty phases → None; in_progress → found;
# failed → found; all complete → None.
def scan_case(state_dict):
cases.append({"fn": "BuildManager.get_resume_point",
"recv": {"cls": "BuildManager", "state": state_dict},
"args": [], "kwargs": {}})
cases.append({"fn": "BuildManager.can_resume",
"recv": {"cls": "BuildManager", "state": state_dict},
"args": [], "kwargs": {}})
scan_case(None)
scan_case(seed(fresh_mgr()))
p = seed(fresh_mgr())
p["phases"]["plan"] = "in_progress"
scan_case(p)
f = seed(fresh_mgr())
f["phases"]["verify"] = "failed"
scan_case(f)
scan_case(seed(mgr)) # fully walked: every phase complete
# ── get_status branches ──────────────────────────────────────────
cases.append({"fn": "BuildManager.get_status",
"recv": {"cls": "BuildManager", "state": None},
"args": [], "kwargs": {}})
cases.append({"fn": "BuildManager.get_status",
"recv": {"cls": "BuildManager", "state": seed(mgr)},
"args": [], "kwargs": {}})
# ── _load_state IO roundtrip + byte fixture ──────────────────────
# The fixture is the CORRECTION-CYCLE state: every dict in it is
# single-key or sorted-key (see BYTE-PARITY LAW) — the one state whose
# json.dump bytes Go can reproduce exactly. The full-walk state (with
# the oracle-internal feasible payload) is roundtrip-proven at the
# parsed level instead.
full_state = dataclasses.asdict(mgr.state)
fixture_state = dataclasses.asdict(cMgr.state)
with tempfile.TemporaryDirectory() as tmp:
bm.BUILDS_DIR = tmp # oracle reads the module global at call time
build_dir = Path(tmp) / BUILD_ID
build_dir.mkdir(parents=True)
ledger_bytes = json.dumps(fixture_state, indent=2,
ensure_ascii=False).encode("utf-8")
(build_dir / "ledger.json").write_bytes(ledger_bytes)
full_bytes = json.dumps(full_state, indent=2,
ensure_ascii=False).encode("utf-8")
(build_dir.parent / "full-walk-build").mkdir()
(build_dir.parent / "full-walk-build" / "ledger.json").write_bytes(
full_bytes)
m2 = bm.BuildManager.__new__(bm.BuildManager)
loaded = bm.BuildManager._load_state(m2, BUILD_ID)
assert dataclasses.asdict(loaded) == fixture_state, "roundtrip broke"
full_id = "full-walk-build"
m3 = bm.BuildManager.__new__(bm.BuildManager)
loaded_full = bm.BuildManager._load_state(m3, full_id)
assert dataclasses.asdict(loaded_full) == full_state, "full broke"
# missing build → FileNotFoundError("Build <id> not found")
try:
bm.BuildManager._load_state(m2, "no-such-build")
raise SystemExit("_load_state missing-path did NOT raise")
except FileNotFoundError as e:
assert str(e) == f"Build no-such-build not found", str(e)
fixture = REPO / "src/go/build-mode" / "ledger_load_fixture.json"
fixture.write_bytes(ledger_bytes)
print(f"wrote {fixture} ({len(ledger_bytes)} bytes, oracle roundtrip proven)")
truth = freeze("rune.build_mode", cases, "rune.build_mode")
out = REPO / "src/go/build-mode" / "truth_buildmode_gap.json"
write_envelope(FROZEN_NOW, truth, out)
print(f"wrote {out} ({len(truth)} frozen cases)")
if __name__ == "__main__":
main()
+133
View File
@@ -0,0 +1,133 @@
#!/usr/bin/env python3
"""Freeze the meaningful_events oracle truth (dichotomy wave, #2479).
Generator law: RUNE_ME_CORE=python pinned, every input deep-copied,
every public function exercised across ALL branches:
* should_fire — unknown trigger; each of the 8 stop conditions; each
of the 8 absent conditions; rate limiting incl. Python int()
truncation (fractional remaining), future last_fired, last_fired 0;
falsy condition/stop values (0, "", None) vs truthy
* fire_due — table-order listing, subsets, none due, stop-blocked
* mark_fired — key set on copy (original untouched), unknown trigger
still records, explicit now
"""
import copy
import json
import os
import sys
from pathlib import Path
REPO = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(REPO / "src"))
sys.path.insert(0, str(REPO / "src" / "rune"))
os.environ["RUNE_ME_CORE"] = "python"
import meaningful_events as me # noqa: E402
cases_should_fire = []
cases_fire_due = []
cases_mark_fired = []
def case_fire(name, trigger, state, now):
trigger = copy.deepcopy(trigger)
state = copy.deepcopy(state)
now = copy.deepcopy(now)
cases_should_fire.append({
"name": name,
"args": {"trigger": trigger, "state": state, "now": now},
"expect": me.should_fire(copy.deepcopy(trigger),
copy.deepcopy(state),
copy.deepcopy(now)),
})
def case_due(name, state, now):
state = copy.deepcopy(state)
now = copy.deepcopy(now)
cases_fire_due.append({
"name": name,
"args": {"state": state, "now": now},
"expect": me.fire_due(copy.deepcopy(state), copy.deepcopy(now)),
})
def case_mark(name, state, trigger, now):
state = copy.deepcopy(state)
trigger = copy.deepcopy(trigger)
now = copy.deepcopy(now)
st = copy.deepcopy(state)
expect = me.mark_fired(st, copy.deepcopy(trigger), copy.deepcopy(now))
cases_mark_fired.append({
"name": name,
"args": {"state": state, "trigger": trigger, "now": now},
"expect": expect,
"input_unchanged": st == state,
})
# ── should_fire: every branch ───────────────────────────────────────────────
case_fire("unknown-trigger", "nope", {}, 1000.0)
for trig in me.TRIGGERS:
cond, limit, stop = me.TRIGGERS[trig]
case_fire(f"fires-{trig}", trig, {cond: True}, 1000.0)
case_fire(f"fires-with-old-last-{trig}", trig,
{cond: True, f"last_fired_{trig}": 0.0}, 1000.0)
case_fire(f"stop-met-{trig}", trig, {cond: True, stop: True}, 1000.0)
case_fire(f"stop-truthy-string-{trig}", trig, {cond: True, stop: "yes"}, 1000.0)
case_fire(f"stop-falsy-zero-not-met-{trig}", trig,
{cond: True, stop: 0}, 1000.0)
case_fire(f"condition-absent-{trig}", trig, {}, 1000.0)
case_fire(f"condition-falsy-empty-str-{trig}", trig, {cond: ""}, 1000.0)
case_fire(f"condition-falsy-null-{trig}", trig, {cond: None}, 1000.0)
case_fire(f"rate-limited-{trig}", trig,
{cond: True, f"last_fired_{trig}": 1000.0 - (limit - 100.0)}, 1000.0)
case_fire(f"rate-limited-exact-boundary-{trig}", trig,
{cond: True, f"last_fired_{trig}": 1000.0 - limit}, 1000.0)
case_fire("rate-truncation-fractional", "task_blockers",
{"task_blocked": True, "last_fired_task_blockers": 400.5}, 1000.0)
case_fire("rate-truncation-tiny-remaining", "owner_corrections",
{"correction_received": True, "last_fired_owner_corrections": 700.0001},
1000.0)
case_fire("future-last-rate-limited", "task_blockers",
{"task_blocked": True, "last_fired_task_blockers": 5000.0}, 1000.0)
case_fire("negative-now-last-fractional", "curiosity_candidates",
{"novel_material_present": True, "last_fired_curiosity_candidates": -100.25},
1000.0)
case_fire("float-now-and-last", "repeated_failure",
{"same_failure_3x": True, "last_fired_repeated_failure": 123.456},
789.012)
# ── fire_due: ordering + subsets ────────────────────────────────────────────
ALL_DUE = {cond: True for cond, _, _ in me.TRIGGERS.values()}
case_due("all-eight-due-table-order", ALL_DUE, 1000.0)
case_due("none-due", {}, 1000.0)
case_due("subset-due", {"task_blocked": True, "correction_received": 1}, 1000.0)
case_due("stop-blocks-some", {**ALL_DUE, "task_unblocked": True}, 1000.0)
case_due("rate-limits-block-some",
{cond: True for cond, _, _ in me.TRIGGERS.values()}
| {"last_fired_owner_corrections": 999.0}, 1000.0)
case_due("falsy-conditions-only",
{cond: 0 for cond, _, _ in me.TRIGGERS.values()}, 1000.0)
# ── mark_fired: copy semantics ──────────────────────────────────────────────
case_mark("mark-explicit-now", {"task_blocked": True}, "task_blockers", 1000.0)
case_mark("mark-unknown-trigger-still-records", {}, "mystery", 55.5)
case_mark("mark-overwrites-existing", {"last_fired_task_blockers": 1.0},
"task_blockers", 2000.0)
case_mark("mark-empty-state", {}, "owner_corrections", 0.25)
truth = {
"module": "meaningful_events",
"core_pin": "RUNE_ME_CORE=python",
"should_fire_cases": cases_should_fire,
"fire_due_cases": cases_fire_due,
"mark_fired_cases": cases_mark_fired,
}
out = REPO / "src" / "rust" / "meaningful_events" / "frozen_truth.json"
out.parent.mkdir(parents=True, exist_ok=True)
out.write_text(json.dumps(truth, indent=1, sort_keys=True) + "\n",
encoding="utf-8")
n = len(cases_should_fire) + len(cases_fire_due) + len(cases_mark_fired)
print(f"frozen {n} cases -> {out}")
+5
View File
@@ -223,7 +223,12 @@ func StartBuildID(nowUTC string) string {
// ResumePoint — reversed declaration-order scan: the first phase whose
// status is in_progress or failed; "" when all complete (Python None).
// Nil state → "" (the Python's `if not self.state: return None` guard,
// pinned by truth_buildmode_gap.json).
func ResumePoint(state *BuildState) string {
if state == nil {
return ""
}
for i := len(Phases) - 1; i >= 0; i-- {
status, has := state.Phases[Phases[i]]
if !has {
+185
View File
@@ -0,0 +1,185 @@
package main
// build_mode_gap.go — gap-closing batch: the PARTIAL audit's missing fns of
// src/rune/build_mode.py (contract BUILDER-2026-10-13T02:02:02Z-dad27f-
// factory-gap-bm). _load_state/_save_state (the ledger IO seam, buildsDir
// INJECTED — the Python reads a module global), assess_complexity, and the
// full start_* phase-transition family. Phase names and ordering are
// load-bearing for the pipeline: every transition here is frozen
// transition-by-transition in declaration order in
// truth_buildmode_gap.json (live oracle, clock pinned via the shared
// freeze_truth harness).
import (
"bytes"
"encoding/json"
"fmt"
"os"
"path/filepath"
"sort"
)
// LoadState — _load_state: read BUILDS_DIR/<build_id>/ledger.json into a
// BuildState. Missing file → the Python FileNotFoundError text.
func LoadState(buildsDir, buildID string) (*BuildState, error) {
ledgerPath := filepath.Join(buildsDir, buildID, "ledger.json")
if _, err := os.Stat(ledgerPath); err != nil {
return nil, fmt.Errorf("Build %s not found", buildID)
}
data, err := os.ReadFile(ledgerPath)
if err != nil {
return nil, err
}
var state BuildState
if err := json.Unmarshal(data, &state); err != nil {
return nil, err
}
return &state, nil
}
// SaveState — _save_state: write the state as asdict-shape JSON (indent 2,
// no HTML escaping, no trailing newline — Python json.dump byte contract,
// pinned by ledger_load_fixture.json). Nil state → no-op (the Python's
// `if not self.state: return`).
//
// BYTE PARITY TRAP: Python dict preserves INSERTION order and json.dump
// emits it; Go maps marshal sorted. The live manager creates the phases
// keys via _log_phase, so the ledger's first-occurrence order IS the dict
// order — the phases object is spliced in that order (keys missing from
// the ledger fall back to pipeline declaration order). The nil sentinel
// replaces exactly the one top-level "phases" key.
func SaveState(state *BuildState, buildsDir string) error {
if state == nil {
return nil
}
buildDir := filepath.Join(buildsDir, state.BuildID)
if err := os.MkdirAll(buildDir, 0o755); err != nil {
return err
}
data, err := stateJSON(state)
if err != nil {
return err
}
return os.WriteFile(filepath.Join(buildDir, "ledger.json"), data, 0o644)
}
// stateJSON — the Python json.dump byte contract (indent 2, no HTML
// escaping, no trailing newline, phases in dict-insertion order).
func stateJSON(state *BuildState) ([]byte, error) {
var buf bytes.Buffer
enc := json.NewEncoder(&buf)
enc.SetEscapeHTML(false)
enc.SetIndent("", " ")
saved := state.Phases
state.Phases = nil
err := enc.Encode(state)
state.Phases = saved
if err != nil {
return nil, err
}
out := bytes.Replace(buf.Bytes(), []byte(`"phases": null`),
orderedPhasesJSON(state), 1)
return bytes.TrimRight(out, "\n"), nil
}
// orderedPhasesJSON — the phases object in Python-dict insertion order
// (ledger first-occurrence, then declaration order, then sorted rest).
func orderedPhasesJSON(state *BuildState) []byte {
order := make([]string, 0, len(state.Phases))
seen := make(map[string]bool, len(state.Phases))
for _, e := range state.Ledger {
if _, has := state.Phases[e.Phase]; has && !seen[e.Phase] {
seen[e.Phase] = true
order = append(order, e.Phase)
}
}
for _, p := range Phases {
if _, has := state.Phases[p]; has && !seen[p] {
seen[p] = true
order = append(order, p)
}
}
rest := make([]string, 0)
for p := range state.Phases {
if !seen[p] {
rest = append(rest, p)
}
}
sort.Strings(rest)
order = append(order, rest...)
var b bytes.Buffer
// Python json.dump(indent=2) nests the phases entries one level deeper
// (4-space entries, 2-space closing brace).
b.WriteString(`"phases": {`)
for i, p := range order {
if i > 0 {
b.WriteByte(',')
}
b.WriteString("\n ")
k, _ := json.Marshal(p)
v, _ := json.Marshal(state.Phases[p])
b.Write(k)
b.WriteString(": ")
b.Write(v)
}
if len(order) > 0 {
b.WriteString("\n ")
}
b.WriteString("}")
return b.Bytes()
}
// AssessComplexity — assess_complexity: the placeholder report, verbatim.
func AssessComplexity() map[string]interface{} {
return map[string]interface{}{
"complexity": "complex", // simple/medium/complex
"estimated_steps": 10,
"requires_build_mode": true,
}
}
// StartResearch — start_research.
func StartResearch(state *BuildState, nowISO string) {
LogPhase(state, "research", StatusInProgress, nil, nil, nowISO)
}
// StartFeasible — start_feasible.
func StartFeasible(state *BuildState, nowISO string) {
LogPhase(state, "feasible", StatusInProgress, nil, nil, nowISO)
}
// StartConsult — start_consult.
func StartConsult(state *BuildState, nowISO string) {
LogPhase(state, "consult", StatusInProgress, nil, nil, nowISO)
}
// StartConsent — start_consent.
func StartConsent(state *BuildState, nowISO string) {
LogPhase(state, "consent", StatusInProgress, nil, nil, nowISO)
}
// StartPlan — start_plan.
func StartPlan(state *BuildState, nowISO string) {
LogPhase(state, "plan", StatusInProgress, nil, nil, nowISO)
}
// StartBuildPhase — start_build_phase.
func StartBuildPhase(state *BuildState, nowISO string) {
LogPhase(state, "build", StatusInProgress, nil, nil, nowISO)
}
// StartVerify — start_verify.
func StartVerify(state *BuildState, nowISO string) {
LogPhase(state, "verify", StatusInProgress, nil, nil, nowISO)
}
// StartIntegrate — start_integrate.
func StartIntegrate(state *BuildState, nowISO string) {
LogPhase(state, "integrate", StatusInProgress, nil, nil, nowISO)
}
// StartRecord — start_record.
func StartRecord(state *BuildState, nowISO string) {
LogPhase(state, "record", StatusInProgress, nil, nil, nowISO)
}
@@ -0,0 +1,55 @@
{
"build_id": "20261013-022000",
"description": "gap battery build",
"created_at": "2026-10-13T02:20:00+00:00",
"worktree_path": null,
"current_phase": "assess",
"phases": {
"correct": "complete"
},
"ledger": [
{
"phase": "correct",
"status": "in_progress",
"timestamp": "2026-10-13T02:20:00+00:00",
"details": {},
"errors": []
},
{
"phase": "correct",
"status": "complete",
"timestamp": "2026-10-13T02:20:00+00:00",
"details": {
"fixed": false
},
"errors": []
},
{
"phase": "correct",
"status": "in_progress",
"timestamp": "2026-10-13T02:20:00+00:00",
"details": {},
"errors": []
},
{
"phase": "correct",
"status": "complete",
"timestamp": "2026-10-13T02:20:00+00:00",
"details": {
"fixed": true
},
"errors": []
}
],
"batches": [],
"corrections": 3,
"max_corrections": 3,
"current_build_phase": "foundation",
"trajectory": [],
"failure_patterns": [],
"complexity_score": 0,
"estimated_steps": 0,
"actual_steps": 0,
"files_changed": [],
"contracts": []
}
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,257 @@
package main
// truth_buildmode_gap_test.go — gap-closing parity gate: replays
// truth_buildmode_gap.json VERBATIM (go:embed — the shared freeze_truth
// harness output, live oracle, clock pinned) through the ported functions.
// Every case carries a recv receiver (the PRE state), the call args, and
// the expected post-call state + result; the Go replay must match
// byte-for-byte after canonical JSON. Contract
// BUILDER-2026-10-13T02:02:02Z-dad27f-factory-gap-bm.
import (
_ "embed"
"encoding/json"
"os"
"path/filepath"
"strings"
"testing"
)
//go:embed truth_buildmode_gap.json
var gapTruth []byte
//go:embed ledger_load_fixture.json
var ledgerFixture []byte
type gapEnvelope struct {
FrozenNow string `json:"frozen_now"`
Cases []struct {
Fn string `json:"fn"`
Recv struct {
Cls string `json:"cls"`
State json.RawMessage `json:"state"`
} `json:"recv"`
Args []interface{} `json:"args"`
Expected struct {
Result json.RawMessage `json:"result"`
State json.RawMessage `json:"state"`
} `json:"expected"`
} `json:"cases"`
}
func TestBuildModeGapParity(t *testing.T) {
var doc gapEnvelope
if err := json.Unmarshal(gapTruth, &doc); err != nil {
t.Fatalf("unparseable: %v", err)
}
stamp := doc.FrozenNow + "+00:00"
for i, c := range doc.Cases {
name := c.Fn
t.Run(name, func(t *testing.T) {
// Rebuild the receiver: recv.state null → nil state.
var state *BuildState
if string(c.Recv.State) != "null" && len(c.Recv.State) > 0 {
state = &BuildState{}
if err := json.Unmarshal(c.Recv.State, state); err != nil {
t.Fatalf("recv state unparseable: %v", err)
}
}
got, gotState := dispatchGap(t, state, c.Fn, c.Args, stamp)
canonEq(t, "result", got, c.Expected.Result)
// state: absent/null expected → no state attr or nil state;
// else canonical compare.
if len(c.Expected.State) == 0 || string(c.Expected.State) == "null" {
if len(c.Expected.State) == 0 {
return // receiver never had the attr — nothing to compare
}
if gotState != nil {
t.Fatalf("expected nil state, got one")
}
} else {
canonEq(t, "state", gotState, c.Expected.State)
}
})
_ = i
}
}
// dispatchGap — replay one frozen call through the ported function; return
// the JSON-comparable result and the post-call state pointer.
func dispatchGap(t *testing.T, state *BuildState, fn string, args []interface{}, stamp string) (result interface{}, outState *BuildState) {
t.Helper()
name := strings.TrimPrefix(fn, "BuildManager.")
arg := func(i int) interface{} {
if i < len(args) {
return args[i]
}
return nil
}
switch name {
case "assess_complexity":
return AssessComplexity(), state
case "complete_assess":
CompleteAssess(state, arg(0).(map[string]interface{}), stamp)
case "start_research":
StartResearch(state, stamp)
case "complete_research":
CompleteResearch(state, arg(0).(map[string]interface{}), stamp)
case "start_feasible":
StartFeasible(state, stamp)
case "complete_feasible":
CompleteFeasible(state, arg(0) == true, arg(1).(string),
argMap(arg(2)), stamp)
case "start_consult":
StartConsult(state, stamp)
case "complete_consult":
CompleteConsult(state, strList(arg(0)), stamp)
case "start_consent":
StartConsent(state, stamp)
case "complete_consent":
CompleteConsent(state, arg(0) == true, stamp)
case "start_plan":
StartPlan(state, stamp)
case "complete_plan":
CompletePlan(state, objList(arg(0)), stamp)
case "start_build_phase":
StartBuildPhase(state, stamp)
case "complete_build_phase":
CompleteBuildPhase(state, objList(arg(0)), stamp)
case "start_verify":
StartVerify(state, stamp)
case "complete_verify":
CompleteVerify(state, arg(0) == true, strList(arg(1)), stamp)
case "start_correct":
StartCorrect(state, stamp)
case "complete_correct":
CompleteCorrect(state, arg(0) == true, stamp)
case "start_integrate":
StartIntegrate(state, stamp)
case "complete_integrate":
CompleteIntegrate(state, arg(0) == true, strList(arg(1)), stamp)
case "start_record":
StartRecord(state, stamp)
case "complete_record":
CompleteRecord(state, stamp)
case "_save_state":
dir := t.TempDir()
if err := SaveState(state, dir); err != nil {
t.Fatalf("save: %v", err)
}
return nil, state
case "get_resume_point":
rp := ResumePoint(state)
if rp == "" {
return nil, state // Python None
}
return rp, state
case "can_resume":
return CanResume(state), state
case "get_status":
return BuildStatus(state), state
default:
t.Fatalf("unknown frozen fn %q", fn)
}
return nil, state
}
func argMap(v interface{}) map[string]interface{} {
m, _ := v.(map[string]interface{})
return m
}
// canonEq — canonical-JSON comparison (Go-built values vs the frozen
// oracle snapshot).
func canonEq(t *testing.T, what string, got interface{}, wantRaw json.RawMessage) {
t.Helper()
gb, err := json.Marshal(got)
if err != nil {
t.Fatalf("marshal got %s: %v", what, err)
}
var gotV, wantV interface{}
if err := json.Unmarshal(gb, &gotV); err != nil {
t.Fatalf("reparse got %s: %v", what, err)
}
if err := json.Unmarshal(wantRaw, &wantV); err != nil {
t.Fatalf("reparse want %s: %v", what, err)
}
gc, _ := json.Marshal(gotV)
wc, _ := json.Marshal(wantV)
if string(gc) != string(wc) {
t.Fatalf("%s divergence:\n got %s\nwant %s", what, gc, wc)
}
}
// TestLoadStateFixture — the IO seam: a build whose ledger.json holds the
// oracle's exact save bytes must load into the identical state.
func TestLoadStateFixture(t *testing.T) {
dir := t.TempDir()
buildDir := filepath.Join(dir, "20261013-022000")
if err := os.MkdirAll(buildDir, 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(buildDir, "ledger.json"), ledgerFixture, 0o644); err != nil {
t.Fatal(err)
}
state, err := LoadState(dir, "20261013-022000")
if err != nil {
t.Fatalf("load: %v", err)
}
var want BuildState
if err := json.Unmarshal(ledgerFixture, &want); err != nil {
t.Fatal(err)
}
canonEq(t, "loadstate", state, ledgerFixture)
if _, err := LoadState(dir, "no-such-build"); err == nil ||
err.Error() != "Build no-such-build not found" {
t.Fatalf("missing-build error: got %v", err)
}
}
// TestSaveStateBytes — the byte contract: Python json.dump(asdict(state),
// indent=2) — the fixture IS those bytes; SaveState must reproduce them.
func TestSaveStateBytes(t *testing.T) {
var state BuildState
if err := json.Unmarshal(ledgerFixture, &state); err != nil {
t.Fatal(err)
}
dir := t.TempDir()
if err := SaveState(&state, dir); err != nil {
t.Fatal(err)
}
got, err := os.ReadFile(filepath.Join(dir, "20261013-022000", "ledger.json"))
if err != nil {
t.Fatal(err)
}
if string(got) != string(ledgerFixture) {
t.Fatalf("save bytes diverge from oracle fixture:\n got %d bytes\nwant %d bytes",
len(got), len(ledgerFixture))
}
// nil state → no-op, nothing written (fresh dir: nothing created).
nilDir := t.TempDir()
if err := SaveState(nil, nilDir); err != nil {
t.Fatalf("nil save: %v", err)
}
entries, _ := os.ReadDir(nilDir)
if len(entries) != 0 {
t.Fatalf("nil state must not create the build dir, found %d entries", len(entries))
}
}
// TestSaveStateRoundTrip — the phases splice must never corrupt content:
// save the fixture state, load it back, require canonical equality.
func TestSaveStateRoundTrip(t *testing.T) {
var state BuildState
if err := json.Unmarshal(ledgerFixture, &state); err != nil {
t.Fatal(err)
}
dir := t.TempDir()
if err := SaveState(&state, dir); err != nil {
t.Fatal(err)
}
back, err := LoadState(dir, state.BuildID)
if err != nil {
t.Fatal(err)
}
canonEq(t, "roundtrip", back, ledgerFixture)
}
+1 -1
View File
@@ -6,7 +6,7 @@ This is the main entry point for the Rune program.
import json
from pathlib import Path
__version__ = "0.262.0"
__version__ = "0.264.0"
# One build identity (handoff 2026-09-10 §4): the semantic version above is
# the repo-level truth (auto-bumped at merge); the DEPLOYED revision (exact
+1 -1
View File
@@ -233,7 +233,7 @@ def main() -> int:
)
check(
"Rune minor version advertises provisioning and restore verification",
'__version__ = "0.262.0"' in rune_version,
'__version__ = "0.264.0"' in rune_version,
failures,
)
# #169 policy update: auto-bump on merge is sanctioned (Nick directive:
+98
View File
@@ -0,0 +1,98 @@
#!/usr/bin/env python3
"""ctypes loader for the Rust meaningful_events core (dichotomy wave,
#2479 tranche). Mirrors broker_loader.py's contract:
FFI contract (doc-envelope law — every call returns {"ok", "value"|"err"}):
* me_should_fire({trigger, state, now}) -> {fires, reason}
* me_fire_due({state, now}) -> [trigger, ...]
* me_mark_fired({state, trigger, now}) -> state dict
The wall CLOCK is host-side by nature (now resolved by the facade —
null never crosses); the kernel owns the trigger table and the laws.
Free: capability_scoring's cs_free_string (shared process). Override the
.so with ME_RUST_LIB (a FILE path).
"""
from __future__ import annotations
import ctypes
import json
import os
from pathlib import Path
_REPO = Path(__file__).resolve().parents[2]
_CRATE_DIR = _REPO / "src" / "rust" / "meaningful_events"
_LIB_PATH = Path(
os.environ.get("ME_RUST_LIB", _CRATE_DIR / "target" / "release" / "libmeaningful_events.so")
)
_ERR_TYPES = {
"ValueError": ValueError,
"KeyError": KeyError,
"AttributeError": AttributeError,
"TypeError": TypeError,
}
_lib = None
class EventsCoreUnavailable(FileNotFoundError):
"""The Rust meaningful-events core is not built. Callers fall back
to Python."""
def available() -> bool:
return _LIB_PATH.is_file()
def _load() -> ctypes.CDLL:
global _lib
if _lib is not None:
return _lib
if not _LIB_PATH.is_file():
raise EventsCoreUnavailable(str(_LIB_PATH))
lib = ctypes.CDLL(str(_LIB_PATH))
for sym in ("me_should_fire", "me_fire_due", "me_mark_fired"):
getattr(lib, sym).argtypes = [ctypes.c_void_p]
getattr(lib, sym).restype = ctypes.c_void_p
try:
lib.cs_free_string
except AttributeError:
scoring = ctypes.CDLL(str(
_REPO / "src" / "rust" / "capability_scoring" / "target" / "release" / "libcapability_scoring.so"
))
scoring.cs_free_string.argtypes = [ctypes.c_void_p]
lib._free = scoring.cs_free_string
_lib = lib
return lib
def _call(sym: str, payload: dict):
lib = _load()
data = json.dumps(payload).encode()
buf = ctypes.create_string_buffer(data)
ptr = getattr(lib, sym)(ctypes.cast(buf, ctypes.c_void_p))
if not ptr:
raise RuntimeError(f"{sym} returned null")
out = ctypes.string_at(ptr).decode()
free = getattr(lib, "_free", None)
if free is not None:
free(ptr)
doc = json.loads(out)
if not doc.get("ok"):
etype = doc.get("type", "RuntimeError")
raise _ERR_TYPES.get(etype, RuntimeError)(doc.get("err", f"{sym} failed"))
return doc["value"]
def should_fire(trigger: str, state: dict, now: float) -> dict:
return _call("me_should_fire", {"trigger": trigger, "state": state,
"now": now})
def fire_due(state: dict, now: float) -> list:
return _call("me_fire_due", {"state": state, "now": now})
def mark_fired(state: dict, trigger: str, now: float) -> dict:
return _call("me_mark_fired", {"state": state, "trigger": trigger,
"now": now})
+42 -1
View File
@@ -15,6 +15,7 @@ approaching, not because a timer fired.
from __future__ import annotations
import os
import time
# Trigger classes with (condition_key, default_rate_limit_s, stop_key)
@@ -30,10 +31,36 @@ TRIGGERS = {
}
def _kernel():
"""The Rust meaningful-events core (me_loader) when RUNE_ME_CORE
allows it, else None — the trigger table, the should_fire law, the
fire_due ordering and the mark_fired copy law delegate there first
and fall back to the Python body below (same semantics). The wall
CLOCK is host-side by nature: `now` is resolved here (never null
across FFI), and an INT now keeps the Python floor in mark_fired
(the oracle stores the value as-is; the kernel wire is float)."""
if os.environ.get("RUNE_ME_CORE", "auto") == "python":
return None
try:
from rune import me_loader
if me_loader.available():
return me_loader
except Exception: # noqa: BLE001 — kernel failure → Python floor
return None
return None
def should_fire(trigger: str, state: dict, now: float | None = None) -> dict:
"""Evaluate one trigger against current state. Returns
{fires, reason}. Rate-limited, stop-conditioned."""
now = now if now is not None else time.time()
k = _kernel()
if k is not None:
try:
return k.should_fire(trigger, state, now)
except Exception: # noqa: BLE001 — kernel floor
pass
if trigger not in TRIGGERS:
return {"fires": False, "reason": f"unknown trigger: {trigger}"}
cond_key, rate_limit, stop_key = TRIGGERS[trigger]
@@ -50,12 +77,26 @@ def should_fire(trigger: str, state: dict, now: float | None = None) -> dict:
def fire_due(state: dict, now: float | None = None) -> list:
"""All triggers due right now (the endogenous cognition tick)."""
resolved = now if now is not None else time.time()
k = _kernel()
if k is not None:
try:
return k.fire_due(state, resolved)
except Exception: # noqa: BLE001 — kernel floor
pass
return [t for t in TRIGGERS
if should_fire(t, state, now)["fires"]]
if should_fire(t, state, resolved)["fires"]]
def mark_fired(state: dict, trigger: str, now: float | None = None) -> dict:
"""Record a firing (for rate limiting). Returns the updated state."""
k = _kernel()
if k is not None and (now is None or isinstance(now, float)):
try:
return k.mark_fired(state, trigger,
now if now is not None else time.time())
except Exception: # noqa: BLE001 — kernel floor
pass
state = dict(state)
state[f"last_fired_{trigger}"] = now if now is not None else time.time()
return state
+2 -2
View File
@@ -8,7 +8,7 @@ mode system, subject header, shared memory and identity.
This is not an interface. This is how I see, speak, and interact with the world.
Version: v0.262.0
Version: v0.264.0
Features: 41 (24 original + 17 new native commands)
Usage:
@@ -2037,7 +2037,7 @@ class RuneApp(App):
# Show version info
self.chat_channel.add_message(
"system",
f"[dim]Rune v0.262.0 | Textual TUI | {len(loaded)} native modules[/dim]",
f"[dim]Rune v0.264.0 | Textual TUI | {len(loaded)} native modules[/dim]",
)
# Auto-show night mode suggestion if evening/night hours
+14
View File
@@ -0,0 +1,14 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "evidence_provenance"
version = "0.1.0"
[[package]]
name = "meaningful_events"
version = "0.1.0"
dependencies = [
"evidence_provenance",
]
+11
View File
@@ -0,0 +1,11 @@
[package]
name = "meaningful_events"
version = "0.1.0"
edition = "2021"
description = "FULL parity port of src/rune/meaningful_events.py (dichotomy wave, #2479 tranche)"
[lib]
crate-type = ["rlib", "cdylib", "staticlib"]
[dependencies]
evidence_provenance = { path = "../evidence_provenance" }
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,153 @@
//! branch_battery — replay the FROZEN oracle truth VERBATIM
//! (frozen_truth.json, generated by scripts/gen_meaningful_events_truth.py
//! against the LIVE Python oracle with RUNE_ME_CORE=python pinned and
//! deep-copied inputs).
use crate::{fire_due, mark_fired, should_fire};
use evidence_provenance::pyjson::{self, Json};
const TRUTH: &str = include_str!("../frozen_truth.json");
fn truth() -> Json {
pyjson::parse(TRUTH).expect("frozen truth parses")
}
fn cases(group: &str) -> Vec<Json> {
match truth().get(group) {
Some(Json::Arr(items)) => items.clone(),
_ => panic!("truth group {group} missing"),
}
}
fn at<'a>(v: &'a Json, path: &[&str]) -> Option<&'a Json> {
let mut cur = v;
for k in path {
cur = match cur {
Json::Obj(pairs) => pairs
.iter()
.find(|(k2, _)| k2 == k)
.map(|(_, x)| x)?,
_ => return None,
};
}
Some(cur)
}
fn jstr(v: Option<&Json>) -> String {
match v {
Some(Json::Str(s)) => s.clone(),
_ => String::new(),
}
}
fn now_of(v: Option<&Json>) -> Option<f64> {
match v {
Some(Json::Num(n)) | Some(Json::Float(n)) => Some(*n),
_ => None,
}
}
/// Python-value equality on Json (order-insensitive objects).
fn peq(a: &Json, b: &Json) -> bool {
match (a, b) {
(Json::Null, Json::Null) => true,
(Json::Bool(x), Json::Bool(y)) => x == y,
(Json::Num(x), Json::Num(y)) | (Json::Float(x), Json::Float(y)) => x == y,
(Json::Float(x), Json::Num(y)) | (Json::Num(x), Json::Float(y)) => x == y,
(Json::Str(x), Json::Str(y)) => x == y,
(Json::Arr(x), Json::Arr(y)) => {
x.len() == y.len() && x.iter().zip(y).all(|(a, b)| peq(a, b))
}
(Json::Obj(x), Json::Obj(y)) => {
x.len() == y.len()
&& x.iter().all(|(k, v)| {
y.iter()
.find(|(k2, _)| k2 == k)
.map(|(_, v2)| peq(v, v2))
.unwrap_or(false)
})
}
_ => false,
}
}
fn assert_case(name: &str, want: &Json, got: &Json) {
assert!(
peq(want, got),
"case {name} diverged:\n want={}\n got={}",
pyjson::dumps(want),
pyjson::dumps(got),
);
}
#[test]
fn replay_should_fire() {
for c in cases("should_fire_cases") {
let got = should_fire(
&jstr(at(&c, &["args", "trigger"])),
at(&c, &["args", "state"]).expect("state"),
now_of(at(&c, &["args", "now"])),
);
assert_case(
&jstr(at(&c, &["name"])),
at(&c, &["expect"]).expect("expect"),
&got,
);
}
}
#[test]
fn replay_fire_due() {
for c in cases("fire_due_cases") {
let got = fire_due(
at(&c, &["args", "state"]).expect("state"),
now_of(at(&c, &["args", "now"])),
);
assert_case(
&jstr(at(&c, &["name"])),
at(&c, &["expect"]).expect("expect"),
&got,
);
}
}
#[test]
fn replay_mark_fired() {
for c in cases("mark_fired_cases") {
let got = mark_fired(
at(&c, &["args", "state"]).expect("state"),
&jstr(at(&c, &["args", "trigger"])),
now_of(at(&c, &["args", "now"])),
);
assert_case(
&jstr(at(&c, &["name"])),
at(&c, &["expect"]).expect("expect"),
&got,
);
// the copy law: the kernel takes &Json and cannot mutate, but
// the truth ALSO records the oracle's input-unchanged invariant
assert!(
matches!(at(&c, &["input_unchanged"]), Some(Json::Bool(true))),
"case {} oracle input was mutated",
jstr(at(&c, &["name"])),
);
}
}
#[test]
fn all_eight_triggers_in_table_order() {
let names: Vec<&str> = crate::TRIGGERS.iter().map(|t| t.name).collect();
assert_eq!(
names,
vec![
"unresolved_commitments",
"approaching_deadlines",
"repeated_failure",
"contradictions",
"curiosity_candidates",
"prediction_mismatches",
"task_blockers",
"owner_corrections",
]
);
}
+268
View File
@@ -0,0 +1,268 @@
//! meaningful_events — endogenous meaningful-event triggers (#48).
//!
//! FULL parity port of src/rune/meaningful_events.py (dichotomy wave,
//! #2479 tranche). The wall CLOCK is host-side by nature — `now` is
//! passed per op (null never reaches the kernel; the facade resolves
//! time.time() first). The kernel owns the eight-trigger table, the
//! should_fire law, the fire_due ordering and the mark_fired copy law.
//!
//! FFI (doc-envelope law): every call takes one JSON doc and returns
//! {"ok": true, "value": ...} | {"ok": false, "err": ..., "type": ...}.
//! * me_should_fire({trigger, state, now}) -> {fires, reason}
//! * me_fire_due({state, now}) -> [trigger, ...]
//! * me_mark_fired({state, trigger, now}) -> state dict
//!
//! Python laws replicated exactly:
//! * should_fire ordering: unknown → stop → absent → rate → fires
//! * truthiness on state values (0, "", null are ABSENT/NOT-MET)
//! * last = state["last_fired_{trigger}"] or 0 (falsy → 0)
//! * rate gate is STRICTLY `now - last < rate_limit` (equal fires)
//! * remaining = int(rate_limit - (now - last)) — Python int()
//! truncation TOWARD ZERO (matches `as i64` for this domain)
//! * fire_due lists TABLE ORDER (insertion order, not sorted)
//! * mark_fired: dict(state) shallow copy + key set (unknown
//! triggers still record)
//!
//! NOTE: no me_free_string export — callers free through capability_
//! scoring's cs_free_string (shared process allocator).
use evidence_provenance::pyjson::{self, Json};
use std::ffi::{CStr, CString};
use std::os::raw::c_char;
fn obj(pairs: Vec<(&str, Json)>) -> Json {
Json::Obj(pairs.into_iter().map(|(k, v)| (k.to_string(), v)).collect())
}
fn s(v: &str) -> Json {
Json::Str(v.to_string())
}
/// Phase B groundwork for the language-advantage pass: the trigger
/// table is typed records, not tuples — the compiler names every field.
pub struct Trigger {
pub name: &'static str,
pub condition_key: &'static str,
pub rate_limit_s: f64,
pub stop_key: &'static str,
}
/// The eight trigger classes, TABLE ORDER (the fire_due listing law).
pub const TRIGGERS: [Trigger; 8] = [
Trigger { name: "unresolved_commitments", condition_key: "has_open_commitments", rate_limit_s: 3600.0, stop_key: "commitments_resolved" },
Trigger { name: "approaching_deadlines", condition_key: "deadline_within_24h", rate_limit_s: 1800.0, stop_key: "deadline_passed_or_met" },
Trigger { name: "repeated_failure", condition_key: "same_failure_3x", rate_limit_s: 900.0, stop_key: "failure_pattern_broken" },
Trigger { name: "contradictions", condition_key: "contradiction_detected", rate_limit_s: 1800.0, stop_key: "contradiction_resolved" },
Trigger { name: "curiosity_candidates", condition_key: "novel_material_present", rate_limit_s: 7200.0, stop_key: "material_absorbed" },
Trigger { name: "prediction_mismatches", condition_key: "prediction_wrong", rate_limit_s: 1800.0, stop_key: "model_updated" },
Trigger { name: "task_blockers", condition_key: "task_blocked", rate_limit_s: 600.0, stop_key: "task_unblocked" },
Trigger { name: "owner_corrections", condition_key: "correction_received", rate_limit_s: 300.0, stop_key: "correction_absorbed" },
];
fn find(trigger: &str) -> Option<&'static Trigger> {
TRIGGERS.iter().find(|t| t.name == trigger)
}
/// Python truthiness on a JSON value (state.get() laws).
fn truthy(v: Option<&Json>) -> bool {
match v {
None | Some(Json::Null) => false,
Some(Json::Bool(b)) => *b,
Some(Json::Num(n)) => *n != 0.0,
Some(Json::Float(n)) => *n != 0.0,
Some(Json::Str(x)) => !x.is_empty(),
Some(Json::Arr(a)) => !a.is_empty(),
Some(Json::Obj(o)) => !o.is_empty(),
}
}
/// as_f64 over state/now values (Num and Float alike).
fn f64_of(v: Option<&Json>) -> Option<f64> {
match v {
Some(Json::Num(n)) | Some(Json::Float(n)) => Some(*n),
_ => None,
}
}
/// Phase B (language advantage): the decision is a typed verdict
/// matched EXHAUSTIVELY — the JSON envelope is built once at the edge,
/// and fire_due filters on the enum instead of re-parsing decision
/// JSON per trigger.
#[derive(Debug, Clone, Copy, PartialEq)]
enum FireDecision {
/// unreachable through should_fire (find() rules it out) but kept
/// so the match stays exhaustive over the decision lattice
#[allow(dead_code)]
Unknown,
Stopped(&'static str),
Absent(&'static str),
RateLimited(i64),
Fire(&'static str),
}
fn decide(t: &'static Trigger, state: &Json, now: f64) -> FireDecision {
if truthy(state.get(t.stop_key)) {
return FireDecision::Stopped(t.stop_key);
}
if !truthy(state.get(t.condition_key)) {
return FireDecision::Absent(t.condition_key);
}
// last = state.get(f"last_fired_{trigger}", 0) — falsy → 0
let last = f64_of(state.get(&format!("last_fired_{}", t.name))).unwrap_or(0.0);
if now - last < t.rate_limit_s {
return FireDecision::RateLimited((t.rate_limit_s - (now - last)) as i64);
}
FireDecision::Fire(t.condition_key)
}
/// should_fire law. `now` is always Some (facade-resolved clock).
pub fn should_fire(trigger: &str, state: &Json, now: Option<f64>) -> Json {
let now = now.unwrap_or(0.0);
let Some(t) = find(trigger) else {
return obj(vec![
("fires", Json::Bool(false)),
("reason", s(&format!("unknown trigger: {trigger}"))),
]);
};
match decide(t, state, now) {
FireDecision::Stopped(stop_key) => obj(vec![
("fires", Json::Bool(false)),
("reason", s(&format!("stop condition met: {stop_key}"))),
]),
FireDecision::Absent(cond_key) => obj(vec![
("fires", Json::Bool(false)),
("reason", s(&format!("condition absent: {cond_key}"))),
]),
FireDecision::RateLimited(remaining) => obj(vec![
("fires", Json::Bool(false)),
("reason", s(&format!("rate-limited ({remaining}s left)"))),
]),
FireDecision::Fire(cond_key) => obj(vec![
("fires", Json::Bool(true)),
("reason", s(&format!("condition holds: {cond_key}"))),
]),
FireDecision::Unknown => unreachable!("find() already ruled this out"),
}
}
/// fire_due law: TABLE ORDER listing of due triggers.
pub fn fire_due(state: &Json, now: Option<f64>) -> Json {
let now = now.unwrap_or(0.0);
let due: Vec<Json> = TRIGGERS
.iter()
.filter(|t| matches!(decide(t, state, now), FireDecision::Fire(_)))
.map(|t| s(t.name))
.collect();
Json::Arr(due)
}
/// mark_fired law: dict(state) shallow copy + `last_fired_{trigger}`.
pub fn mark_fired(state: &Json, trigger: &str, now: Option<f64>) -> Json {
let mut pairs: Vec<(String, Json)> = match state {
Json::Obj(pairs) => pairs.clone(),
_ => Vec::new(),
};
let stamp = Json::Float(now.unwrap_or(0.0));
let key = format!("last_fired_{trigger}");
if let Some(slot) = pairs.iter_mut().find(|(k, _)| *k == key) {
slot.1 = stamp;
} else {
pairs.push((key, stamp));
}
Json::Obj(pairs)
}
// ── FFI (doc-envelope) ───────────────────────────────────────────────────
fn envelope(result: Result<Json, (String, String)>) -> *mut c_char {
let body = match result {
Ok(value) => obj(vec![("ok", Json::Bool(true)), ("value", value)]),
Err((err, ty)) => obj(vec![
("ok", Json::Bool(false)),
("err", Json::Str(err)),
("type", Json::Str(ty)),
]),
};
let out = pyjson::dumps(&body);
match CString::new(out) {
Ok(c) => c.into_raw(),
Err(_) => CString::new(
r#"{"ok":false,"err":"nul in output","type":"RuntimeError"}"#,
)
.unwrap()
.into_raw(),
}
}
fn parse_doc(raw: &str) -> Result<Json, (String, String)> {
pyjson::parse(raw).map_err(|e| (e, "ValueError".to_string()))
}
fn read_arg(ptr: *const c_char) -> Result<String, (String, String)> {
if ptr.is_null() {
return Ok(String::new());
}
unsafe { CStr::from_ptr(ptr) }
.to_str()
.map(|s| s.to_string())
.map_err(|e| (e.to_string(), "ValueError".to_string()))
}
fn now_of(doc: &Json) -> Result<Option<f64>, (String, String)> {
match doc.get("now") {
None | Some(Json::Null) => Ok(None),
Some(v) => f64_of(Some(v))
.map(Some)
.ok_or_else(|| ("now must be a number or null".into(), "TypeError".into())),
}
}
fn state_of(doc: &Json) -> Result<&Json, (String, String)> {
match doc.get("state") {
Some(st @ Json::Obj(_)) => Ok(st),
_ => Err(("state must be an object".into(), "TypeError".into())),
}
}
macro_rules! ffi {
($name:ident, $body:expr) => {
#[no_mangle]
pub extern "C" fn $name(arg: *const c_char) -> *mut c_char {
let out: Result<Json, (String, String)> =
read_arg(arg).and_then(|raw| $body(&raw));
envelope(out)
}
};
}
ffi!(me_should_fire, |raw: &str| -> Result<Json, (String, String)> {
let doc = parse_doc(raw)?;
let trigger = match doc.get("trigger") {
Some(Json::Str(t)) => t.clone(),
_ => return Err(("trigger must be a string".into(), "TypeError".into())),
};
let state = state_of(&doc)?.clone();
let now = now_of(&doc)?;
Ok(should_fire(&trigger, &state, now))
});
ffi!(me_fire_due, |raw: &str| -> Result<Json, (String, String)> {
let doc = parse_doc(raw)?;
let state = state_of(&doc)?.clone();
let now = now_of(&doc)?;
Ok(fire_due(&state, now))
});
ffi!(me_mark_fired, |raw: &str| -> Result<Json, (String, String)> {
let doc = parse_doc(raw)?;
let state = state_of(&doc)?.clone();
let trigger = match doc.get("trigger") {
Some(Json::Str(t)) => t.clone(),
_ => return Err(("trigger must be a string".into(), "TypeError".into())),
};
let now = now_of(&doc)?;
Ok(mark_fired(&state, &trigger, now))
});
#[cfg(test)]
mod branch_battery;
+93
View File
@@ -0,0 +1,93 @@
"""Tests: meaningful_events.py Rust core port (dichotomy wave, #2479).
Frozen-truth battery: the 96 frozen-oracle cases replayed VERBATIM
through the facade in BOTH core modes (RUNE_ME_CORE=python floor vs the
Rust kernel), plus loader pin discipline, the fire→mark→quiet loop and
input-copy semantics.
"""
from __future__ import annotations
import copy
import json
import os
import sys
from pathlib import Path
import pytest
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT / "src"))
sys.path.insert(0, str(ROOT / "src" / "rune"))
import meaningful_events as me # noqa: E402
from rune import me_loader # noqa: E402
TRUTH = json.loads(
(ROOT / "src" / "rust" / "meaningful_events" / "frozen_truth.json")
.read_text())
@pytest.fixture()
def core_built():
if not me_loader.available():
pytest.skip("rust meaningful_events core not built")
return True
def drive(mode, fn, *args):
os.environ["RUNE_ME_CORE"] = mode
try:
return fn(*copy.deepcopy(args))
finally:
os.environ["RUNE_ME_CORE"] = "auto"
def test_loader_probe_and_pin(core_built, monkeypatch):
monkeypatch.delenv("RUNE_ME_CORE", raising=False)
assert me_loader.available()
assert me._kernel() is not None
monkeypatch.setenv("RUNE_ME_CORE", "python")
assert me._kernel() is None
def test_frozen_should_fire_both_ways(core_built):
for c in TRUTH["should_fire_cases"]:
args = (c["args"]["trigger"], c["args"]["state"], c["args"]["now"])
py = drive("python", me.should_fire, *args)
rs = drive("auto", me.should_fire, *args)
assert py == rs == c["expect"], c["name"]
def test_frozen_fire_due_both_ways(core_built):
for c in TRUTH["fire_due_cases"]:
args = (c["args"]["state"], c["args"]["now"])
py = drive("python", me.fire_due, *args)
rs = drive("auto", me.fire_due, *args)
assert py == rs == c["expect"], c["name"]
def test_frozen_mark_fired_both_ways(core_built):
for c in TRUTH["mark_fired_cases"]:
args = (c["args"]["state"], c["args"]["trigger"], c["args"]["now"])
py = drive("python", me.mark_fired, *args)
rs = drive("auto", me.mark_fired, *args)
assert py == rs == c["expect"], c["name"]
assert c["input_unchanged"] is True, c["name"]
def test_fire_mark_due_loop_both_ways(core_built):
for mode in ("python", "auto"):
state = {"task_blocked": True}
due = drive(mode, me.fire_due, state, 1000.0)
assert "task_blockers" in due
state = drive(mode, me.mark_fired, state, "task_blockers", 1000.0)
assert drive(mode, me.fire_due, state, 1000.0) == []
def test_rate_limit_string_law(core_built):
# int() truncation surfaces IN the reason string — verbatim law
r = drive("auto", me.should_fire, "task_blockers",
{"task_blocked": True, "last_fired_task_blockers": 400.5},
1000.0)
assert r == {"fires": False, "reason": "rate-limited (0s left)"}