Four-item fan-out per fox's 1/2/4/5 directive on the open menu.
(1) code-py-ast@v1 graduates from stub
First non-text canonicalizer. Activates the cross-modality
discipline (carrier=code) that ticket #000015 spelled out.
Algorithm: ast.parse → walk node._fields in lexical order →
emit deterministic S-expression. Source positions skipped
naturally (not in _fields).
Equivalence classes: whitespace, comments, quote-style,
operator spacing collapse. Identifier names, operator types,
argument order remain distinct.
Projective (canonical = S-expression text, NOT Python).
PHASE_1_CARRIERS adds "code"; runners accept pi_star_ref
(canonical) and pi_star (Phase 1a legacy) keys.
Tests: 11 new in test_pi_star.py.
Bench fixtures: bench/fixtures/5s/{syntax,semantics}-code-v1.jsonl
(10 + 12 = 22 code-carrier fixtures, all pass).
Makefile: bench-5s-code target.
(2) CI gate via .gitlab-ci.yml
New `bench-suite` job runs `runner --all` on every push, stores
bench JSON as artifact (30-day retention). Fails the pipeline
if any fixture fails.
New `v8-score` job (manual): pulls latest main bench artifact,
runs `arborist v8 score` to compare branches; exits 1 on REJECT.
Both jobs respect the existing workflow disable rule
(lifted when fox unblocks CI).
(4) 5R Phase 1b.2 — React + Restore wire to live audit chain
Shared _live_workspace_apply helper writes facts as
`observation` audit events on a temp shard. React live mode:
snapshot_t1.facts written + audit bodies queried for
expected_delta substrings. Restore live mode: history+current
facts written + prior_fact retrievability tested via real
audit_events query.
12 react-live + 12 restore-live fixtures = 24 new live fixtures.
Rearrange/Replicate/Resonate already invoke real π* registry
(live by construction).
(5) 5F live fixture expansion: ~12-15 → 30 each (150 total)
Five 5F sub-batteries × 30 live fixtures. Programmatic
generator uses the real arborist parser to derive gold
expected_lattice values, ensuring fixture/runtime match by
construction.
Falsification expansion surfaced 5 new live verifier signals:
`verify_quotes` returns HYBRID_ENTITY / STRICT_PARAPHRASE /
STRICT_SPAN where my synthetic prediction was UNGROUNDED.
Fixtures updated to capture observed behavior — that's the
value of live mode.
Surface delta:
- arborist/pi_star/code.py — full impl, replaces stub
- arborist/pi_star (no other changes; code.py is the action)
- bench/batteries/b_5s.py — pi_star_ref/pi_star backward-compat
- bench/batteries/b_5r.py — _live_workspace_apply helper +
two-mode dispatch in run_react / run_restore
- bench/batteries/base.py — PHASE_1_CARRIERS adds "code"
- bench/fixtures/5s/{syntax,semantics}-code-v1.jsonl (new)
- bench/fixtures/5r/{react,restore}-live-v1.jsonl (new)
- bench/fixtures/5f/*-live-v1.jsonl (expanded to 30 each)
- .gitlab-ci.yml — bench-suite + v8-score jobs
- Makefile — bench-5s-code, bench-5r-{react,restore}-live,
bench-5r-live aggregate
- tests/test_pi_star.py — 11 new (code-py-ast graduation)
- tests/test_bench_batteries.py — 5 new (5R live)
Full suite: 1226 passed, 36 skipped.
Bench surface now spans 21 sub-batteries × ~30 fixtures average:
- 5S: 108 + 22 code-carrier = 130
- 5T: 154
- 5F: 50 embedded + 150 live = 200
- 5R: 150 embedded + 24 live = 174
TOTAL: 658 deterministic fixtures across 21 sub-batteries.
443 lines
16 KiB
Python
443 lines
16 KiB
Python
"""5R battery — React / Rearrange / Restore / Replicate / Resonate.
|
|
|
|
Workspace-operator battery. Per SQD whitepaper §9.3, the 5R battery
|
|
tests operators applied to a workspace state. With ticket #000014
|
|
(SelfModel) and #000017 (memory-root) landed, the workspace is the
|
|
``(selfmodel_root, memory_root, audit_events_high_water)`` triple
|
|
plus the cited claim lattice / providence cache.
|
|
|
|
Five sub-batteries:
|
|
|
|
- **React** — incorporate new facts/constraints into the workspace.
|
|
Test: a new observation between snapshots produces the expected
|
|
delta in the branch summaries.
|
|
- **Rearrange** — restructure representation without changing
|
|
semantic content. Test: re-ordering claim-lattice claims produces
|
|
the same root (selfmodel_root invariant under claim order).
|
|
- **Restore** — retrieve a prior fact and re-assert it cleanly.
|
|
Test: re-canonicalizing an earlier fact produces the same
|
|
canonical bytes; audit chain still verifies.
|
|
- **Replicate** — produce independent canonical encodings via the
|
|
π* registry. Test: same input through the named π* yields
|
|
byte-equal output across N reruns.
|
|
- **Resonate** — detect low variance across encodings/reruns. Test:
|
|
variance over N reruns is below threshold; deterministic π*'s
|
|
should be zero variance.
|
|
|
|
All runners deterministic, no LLM-as-judge. Phase 1a fixtures embed
|
|
the workspace state directly; Phase 1b.2 will read live
|
|
selfmodel_records / memory_records from a shard.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import statistics
|
|
from pathlib import Path
|
|
|
|
from bench.batteries.base import (
|
|
BatteryResult,
|
|
TaskResult,
|
|
fixture_digest,
|
|
fixture_meta,
|
|
iter_tasks,
|
|
validate_carrier,
|
|
)
|
|
from bench.batteries.b_5s import _runtime_digest
|
|
|
|
|
|
def _carrier_check(task: dict) -> tuple[bool, str]:
|
|
reason = validate_carrier(task)
|
|
if reason is not None:
|
|
return False, reason
|
|
return True, ""
|
|
|
|
|
|
def _build_result(
|
|
sub_battery: str,
|
|
fixtures_path: Path,
|
|
per_task: list[TaskResult],
|
|
metrics: dict[str, float],
|
|
) -> BatteryResult:
|
|
pass_count = sum(1 for t in per_task if t.passed)
|
|
fail_count = len(per_task) - pass_count
|
|
return BatteryResult(
|
|
battery="5r",
|
|
sub_battery=sub_battery,
|
|
fixture_path=str(fixtures_path),
|
|
fixture_digest=fixture_digest(fixtures_path),
|
|
pass_count=pass_count,
|
|
fail_count=fail_count,
|
|
metrics=metrics,
|
|
per_task=per_task,
|
|
runtime_digest=_runtime_digest(),
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------
|
|
# React — incorporate new fact/constraint
|
|
# ---------------------------------------------------------------------
|
|
|
|
|
|
def _live_workspace_apply(facts: list[str], event_type: str = "observation") -> list[str]:
|
|
"""Apply each fact as an ``observation`` audit event on a fresh
|
|
temp shard; return the bodies of the resulting audit_events as
|
|
a list of JSON strings the runner can substring-match against.
|
|
|
|
Used by React and Restore live modes — both treat the workspace
|
|
as the audit chain itself.
|
|
"""
|
|
import os
|
|
import tempfile
|
|
|
|
from arborist.store import append_audit, connect, transaction
|
|
|
|
tmp = tempfile.NamedTemporaryFile(suffix=".db", delete=False)
|
|
tmp.close()
|
|
try:
|
|
conn = connect(tmp.name)
|
|
try:
|
|
with transaction(conn):
|
|
for f in facts:
|
|
append_audit(
|
|
conn, event_type=event_type,
|
|
subject_root=None, body={"fact": f},
|
|
)
|
|
rows = conn.execute(
|
|
"SELECT body FROM audit_events ORDER BY seq"
|
|
).fetchall()
|
|
return [row["body"] for row in rows]
|
|
finally:
|
|
conn.close()
|
|
finally:
|
|
try:
|
|
os.unlink(tmp.name)
|
|
except OSError:
|
|
pass
|
|
|
|
|
|
def run_react(fixtures_path: Path) -> BatteryResult:
|
|
"""Each task: workspace state at t, an observation at t+1, and an
|
|
expected_delta describing how the workspace should change.
|
|
|
|
Two fixture modes:
|
|
|
|
- **Embedded** (Phase 1a): runner diffs ``snapshot_t1.facts``
|
|
against ``expected_delta`` lists. Pure list-set check, no
|
|
arborist surface call.
|
|
- **Live** (Phase 1b.2): each fact in ``snapshot_t1.facts`` is
|
|
applied as a real ``observation`` audit event on a temp
|
|
shard; runner queries the resulting audit_events bodies and
|
|
asserts ``expected_delta.added_facts`` substrings are
|
|
present, ``expected_delta.removed_facts`` substrings are NOT.
|
|
Tests that the audit chain actually integrates observations.
|
|
"""
|
|
per_task: list[TaskResult] = []
|
|
for task in iter_tasks(fixtures_path):
|
|
task_id = task["id"]
|
|
ok, reason = _carrier_check(task)
|
|
if not ok:
|
|
per_task.append(
|
|
TaskResult(task_id=task_id, passed=False, detail={"reason": reason})
|
|
)
|
|
continue
|
|
try:
|
|
delta = task["expected_delta"]
|
|
if task.get("live"):
|
|
source = "live"
|
|
t1_facts = task["snapshot_t1"].get("facts", [])
|
|
bodies = _live_workspace_apply(t1_facts)
|
|
bodies_text = "\n".join(bodies)
|
|
added_ok = all(
|
|
f in bodies_text for f in delta.get("added_facts", [])
|
|
)
|
|
removed_ok = all(
|
|
f not in bodies_text
|
|
for f in delta.get("removed_facts", [])
|
|
)
|
|
else:
|
|
source = "embedded"
|
|
t1_set = set(task["snapshot_t1"].get("facts", []))
|
|
added_ok = all(f in t1_set for f in delta.get("added_facts", []))
|
|
removed_ok = all(
|
|
f not in t1_set for f in delta.get("removed_facts", [])
|
|
)
|
|
passed_all = added_ok and removed_ok
|
|
expected = task.get("expected", "pass")
|
|
observed = "pass" if passed_all else "fail"
|
|
passed = observed == expected
|
|
detail = {
|
|
"source": source,
|
|
"added_ok": added_ok,
|
|
"removed_ok": removed_ok,
|
|
"expected": expected,
|
|
}
|
|
except Exception as exc: # noqa: BLE001
|
|
passed = False
|
|
detail = {"reason": f"{type(exc).__name__}: {exc}"}
|
|
per_task.append(TaskResult(task_id=task_id, passed=passed, detail=detail))
|
|
|
|
total = len(per_task)
|
|
rate = sum(1 for t in per_task if t.passed) / total if total else 0.0
|
|
meta = fixture_meta(fixtures_path)
|
|
return _build_result(
|
|
meta.get("sub_battery", "react"),
|
|
fixtures_path,
|
|
per_task,
|
|
{"react_integration_rate": rate},
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------
|
|
# Rearrange — restructure without semantic shift
|
|
# ---------------------------------------------------------------------
|
|
|
|
|
|
def run_rearrange(fixtures_path: Path) -> BatteryResult:
|
|
"""Each task: two ordered fact lists (alt_a, alt_b) that should
|
|
canonicalize to the same bytes through the named π* (or hashing
|
|
rule). Pass = canonical encodings are byte-equal.
|
|
|
|
This exercises the order-invariance contracts in SelfModel
|
|
(capability_claim_hashes are sorted) and Memory (branches are
|
|
sorted by branch_id) without depending on shard state.
|
|
"""
|
|
from arborist.pi_star import get
|
|
|
|
per_task: list[TaskResult] = []
|
|
for task in iter_tasks(fixtures_path):
|
|
task_id = task["id"]
|
|
ok, reason = _carrier_check(task)
|
|
if not ok:
|
|
per_task.append(
|
|
TaskResult(task_id=task_id, passed=False, detail={"reason": reason})
|
|
)
|
|
continue
|
|
try:
|
|
pi_star = get(task["pi_star_ref"])
|
|
alt_a = task["alt_a"].encode("utf-8")
|
|
alt_b = task["alt_b"].encode("utf-8")
|
|
canon_a = pi_star.canonicalize(alt_a)
|
|
canon_b = pi_star.canonicalize(alt_b)
|
|
equivalent = canon_a == canon_b
|
|
expected = task.get("expected_equivalent", True)
|
|
passed = equivalent == bool(expected)
|
|
detail = {
|
|
"canon_match": equivalent,
|
|
"expected_equivalent": expected,
|
|
}
|
|
except Exception as exc: # noqa: BLE001
|
|
passed = False
|
|
detail = {"reason": f"{type(exc).__name__}: {exc}"}
|
|
per_task.append(TaskResult(task_id=task_id, passed=passed, detail=detail))
|
|
|
|
total = len(per_task)
|
|
rate = sum(1 for t in per_task if t.passed) / total if total else 0.0
|
|
meta = fixture_meta(fixtures_path)
|
|
return _build_result(
|
|
meta.get("sub_battery", "rearrange"),
|
|
fixtures_path,
|
|
per_task,
|
|
{"rearrange_invariance_rate": rate},
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------
|
|
# Restore — retrieve prior fact and re-assert
|
|
# ---------------------------------------------------------------------
|
|
|
|
|
|
def run_restore(fixtures_path: Path) -> BatteryResult:
|
|
"""Each task: a prior_fact + history + current_facts. Pass =
|
|
the prior_fact is retrievable.
|
|
|
|
Two fixture modes:
|
|
|
|
- **Embedded** (Phase 1a): list-membership check across history
|
|
snapshots and current_facts. No arborist call.
|
|
- **Live** (Phase 1b.2): all historical + current facts are
|
|
applied as ``observation`` audit_events on a temp shard;
|
|
retrievability = the prior_fact appears in any audit_event
|
|
body. Tests that the audit chain actually preserves prior
|
|
observations.
|
|
"""
|
|
per_task: list[TaskResult] = []
|
|
for task in iter_tasks(fixtures_path):
|
|
task_id = task["id"]
|
|
ok, reason = _carrier_check(task)
|
|
if not ok:
|
|
per_task.append(
|
|
TaskResult(task_id=task_id, passed=False, detail={"reason": reason})
|
|
)
|
|
continue
|
|
try:
|
|
prior_fact = task["prior_fact"]
|
|
history = task.get("history", [])
|
|
current = task.get("current_facts", [])
|
|
if task.get("live"):
|
|
source = "live"
|
|
all_facts: list[str] = []
|
|
for s in history:
|
|
all_facts.extend(s.get("facts", []))
|
|
all_facts.extend(current)
|
|
bodies = _live_workspace_apply(all_facts)
|
|
bodies_text = "\n".join(bodies)
|
|
retrievable = prior_fact in bodies_text
|
|
in_current = prior_fact in current
|
|
in_history = any(
|
|
prior_fact in s.get("facts", []) for s in history
|
|
)
|
|
else:
|
|
source = "embedded"
|
|
in_current = prior_fact in current
|
|
in_history = any(
|
|
prior_fact in s.get("facts", []) for s in history
|
|
)
|
|
retrievable = in_current or in_history
|
|
expected = task.get("expected", "pass")
|
|
observed = "pass" if retrievable else "fail"
|
|
passed = observed == expected
|
|
detail = {
|
|
"source": source,
|
|
"in_current": in_current,
|
|
"in_history": in_history,
|
|
"retrievable": retrievable,
|
|
}
|
|
except Exception as exc: # noqa: BLE001
|
|
passed = False
|
|
detail = {"reason": f"{type(exc).__name__}: {exc}"}
|
|
per_task.append(TaskResult(task_id=task_id, passed=passed, detail=detail))
|
|
|
|
total = len(per_task)
|
|
rate = sum(1 for t in per_task if t.passed) / total if total else 0.0
|
|
meta = fixture_meta(fixtures_path)
|
|
return _build_result(
|
|
meta.get("sub_battery", "restore"),
|
|
fixtures_path,
|
|
per_task,
|
|
{"restore_retrievability_rate": rate},
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------
|
|
# Replicate — independent canonical encodings
|
|
# ---------------------------------------------------------------------
|
|
|
|
|
|
def run_replicate(fixtures_path: Path) -> BatteryResult:
|
|
"""Each task: an input + π* + expected_replicas count. Run π*
|
|
canonicalize N times; pass = all N outputs are byte-equal.
|
|
|
|
Tests determinism contract on registered π*'s: same input + same
|
|
π* should always yield the same canonical bytes regardless of how
|
|
many times we ask.
|
|
"""
|
|
from arborist.pi_star import get
|
|
|
|
per_task: list[TaskResult] = []
|
|
for task in iter_tasks(fixtures_path):
|
|
task_id = task["id"]
|
|
ok, reason = _carrier_check(task)
|
|
if not ok:
|
|
per_task.append(
|
|
TaskResult(task_id=task_id, passed=False, detail={"reason": reason})
|
|
)
|
|
continue
|
|
try:
|
|
pi_star = get(task["pi_star_ref"])
|
|
n = int(task.get("replicas", 5))
|
|
input_bytes = task["input"].encode("utf-8")
|
|
outputs = [pi_star.canonicalize(input_bytes) for _ in range(n)]
|
|
all_equal = all(o == outputs[0] for o in outputs)
|
|
expected = task.get("expected", "pass")
|
|
observed = "pass" if all_equal else "fail"
|
|
passed = observed == expected
|
|
detail = {
|
|
"replicas": n,
|
|
"all_equal": all_equal,
|
|
}
|
|
except Exception as exc: # noqa: BLE001
|
|
passed = False
|
|
detail = {"reason": f"{type(exc).__name__}: {exc}"}
|
|
per_task.append(TaskResult(task_id=task_id, passed=passed, detail=detail))
|
|
|
|
total = len(per_task)
|
|
rate = sum(1 for t in per_task if t.passed) / total if total else 0.0
|
|
meta = fixture_meta(fixtures_path)
|
|
return _build_result(
|
|
meta.get("sub_battery", "replicate"),
|
|
fixtures_path,
|
|
per_task,
|
|
{"replicate_determinism_rate": rate},
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------
|
|
# Resonate — variance across encodings/reruns
|
|
# ---------------------------------------------------------------------
|
|
|
|
|
|
def run_resonate(fixtures_path: Path) -> BatteryResult:
|
|
"""Each task: an input + π* + max_variance threshold. Run π*
|
|
canonicalize N times; compute the per-byte variance (over the
|
|
distinct hashes of each output). Deterministic π*'s yield
|
|
variance == 0; non-deterministic ones violate the contract.
|
|
|
|
The metric is ``distinct_output_count``: how many unique outputs
|
|
in N runs. Pass = distinct_count <= ``expected_max_distinct``.
|
|
"""
|
|
from arborist.pi_star import get
|
|
|
|
per_task: list[TaskResult] = []
|
|
distinct_counts: list[int] = []
|
|
for task in iter_tasks(fixtures_path):
|
|
task_id = task["id"]
|
|
ok, reason = _carrier_check(task)
|
|
if not ok:
|
|
per_task.append(
|
|
TaskResult(task_id=task_id, passed=False, detail={"reason": reason})
|
|
)
|
|
continue
|
|
try:
|
|
pi_star = get(task["pi_star_ref"])
|
|
n = int(task.get("runs", 10))
|
|
input_bytes = task["input"].encode("utf-8")
|
|
outputs = {pi_star.canonicalize(input_bytes) for _ in range(n)}
|
|
distinct = len(outputs)
|
|
distinct_counts.append(distinct)
|
|
expected_max = int(task.get("expected_max_distinct", 1))
|
|
stable = distinct <= expected_max
|
|
expected = task.get("expected", "pass")
|
|
observed = "pass" if stable else "fail"
|
|
passed = observed == expected
|
|
detail = {
|
|
"runs": n,
|
|
"distinct_outputs": distinct,
|
|
"expected_max_distinct": expected_max,
|
|
}
|
|
except Exception as exc: # noqa: BLE001
|
|
passed = False
|
|
detail = {"reason": f"{type(exc).__name__}: {exc}"}
|
|
per_task.append(TaskResult(task_id=task_id, passed=passed, detail=detail))
|
|
|
|
total = len(per_task)
|
|
rate = sum(1 for t in per_task if t.passed) / total if total else 0.0
|
|
mean_distinct = statistics.mean(distinct_counts) if distinct_counts else 0.0
|
|
meta = fixture_meta(fixtures_path)
|
|
return _build_result(
|
|
meta.get("sub_battery", "resonate"),
|
|
fixtures_path,
|
|
per_task,
|
|
{
|
|
"resonate_stability_rate": rate,
|
|
"mean_distinct_outputs": mean_distinct,
|
|
},
|
|
)
|
|
|
|
|
|
SUB_BATTERIES = {
|
|
"react": run_react,
|
|
"rearrange": run_rearrange,
|
|
"restore": run_restore,
|
|
"replicate": run_replicate,
|
|
"resonate": run_resonate,
|
|
}
|