arborist/tests/test_session_integration.py
russell@unturf.com 6e20c792c4
bench: 5R battery — closes ticket #000021 (15-sub-battery suite complete)
Phase 2 of #000021. React/Rearrange/Restore/Replicate/Resonate over
the workspace surface — selfmodel_records (#000014) + memory_records
(#000017), both landed earlier today. Closes the gap that gated 5R
since the substrate work shipped.

Sub-battery semantics (per SQD whitepaper §9.3 + ticket #000021 §4.2):

- React: incorporate new fact/constraint. Workspace = (snapshot_t0,
  snapshot_t1, expected_delta). Pass = added_facts present + removed_facts
  absent in t+1.
- Rearrange: restructure without semantic shift. Re-canonicalize
  different surface forms through a named π*; pass = bytes match
  expected_equivalent flag. Tests the order-invariance contracts in
  SelfModel (capability_claim_hashes sorted) and Memory (branches
  sorted by branch_id).
- Restore: retrieve prior fact. Workspace = (history[], current_facts[]).
  Pass = fact in current OR any historical snapshot.
- Replicate: independent canonical encodings via π*. Same input run
  N times must yield byte-equal output. Tests determinism contract.
- Resonate: variance across N runs. Deterministic π*'s yield
  distinct=1; expected_max_distinct=1 enforces zero-variance contract.

Surface:

- bench/batteries/b_5r.py (5 deterministic runners; no LLM-as-judge)
- bench/fixtures/5r/{react,rearrange,restore,replicate,resonate}-v1.jsonl
  (30 each = 150 new fixtures)
- runner.py registers 5r in _BATTERIES + _DEFAULT_FIXTURES
- Makefile: bench-5r + bench-suite (5S+5T+5F+5R aggregate)

Final tally:
  5S  syntax/semantics/syllogism/synthesis/semiotics       108
  5T  transfer/transfer-learning/triangulation/...          154
  5F  function/finetuning/falsification/...                  50
  5R  react/rearrange/restore/replicate/resonate            150
  TOTAL: 462 fixtures across 21 sub-batteries — 100% pass.

Tests: 6 new in tests/test_bench_batteries.py + adjustment to
test_session_integration.py for the 312→462 count + 5R sub-battery
presence assertion. Full suite: 1192 passed, 36 skipped.

Closes #000021. Phase 3 (external-corpus expansion) remains open
under the ticket but does not gate closure — the complete
Dav1DPrometheus surface is now executable infrastructure.
2026-05-08 08:06:24 -04:00

354 lines
12 KiB
Python

"""Cross-module integration tests for this session's surface.
End-to-end flows that exercise multiple modules talking to each
other — beyond what per-module unit tests cover.
Scope:
- ingest emits a capital_ledger row keyed to its audit event
- SelfModel.snapshot reads memory_root from memory_records when present
- π* registry stays read-only after import (re-registering raises)
- Lattice runners chain back to the π* registry
- The complete Dav1DPrometheus suite (`runner --all`) returns 0 with
all 312 fixtures passing
- Battery runtime_digest reflects the live π* registry fingerprint
"""
from __future__ import annotations
import json
from pathlib import Path
import pytest
REPO_ROOT = Path(__file__).resolve().parent.parent
# ---------------------------------------------------------------------
# Ingest → audit_events → capital_ledger
# ---------------------------------------------------------------------
def test_ingest_emits_capital_ledger_row(tmp_path):
"""A real ingest pass writes one capital_ledger row per batch tied
to the last audit event in that batch."""
from arborist.document import Document
from arborist.ingest import ingest_source
from arborist.source import Source
from arborist.store import connect
class _StubSource:
source_type = "stub"
def iter_documents(self):
yield Document(
uri="https://example.com/a",
content="Hello world. This is a test document for ingest.",
title="A",
source_type="stub",
)
yield Document(
uri="https://example.com/b",
content="Another document with different content.",
title="B",
source_type="stub",
)
db = tmp_path / "shard.db"
conn = connect(db)
try:
stats = ingest_source(conn, _StubSource(), batch_size=10)
assert stats.inserted == 2
# Audit events for the two ingested docs.
n_audit = conn.execute(
"SELECT COUNT(*) FROM audit_events WHERE event_type = 'ingest'"
).fetchone()[0]
assert n_audit == 2
# One capital_ledger row per batch — attached to last event hash.
rows = conn.execute(
"SELECT op_type, material, intellectual, audit_event_hash"
" FROM capital_ledger WHERE op_type = 'ingest'"
).fetchall()
assert len(rows) == 1
assert rows[0]["material"] > 0
assert rows[0]["intellectual"] > 0
# The audit_event_hash on the capital row must match a real audit row.
last_event = conn.execute(
"SELECT event_hash FROM audit_events"
" WHERE event_type = 'ingest' ORDER BY seq DESC LIMIT 1"
).fetchone()
assert rows[0]["audit_event_hash"] == last_event["event_hash"]
finally:
conn.close()
# ---------------------------------------------------------------------
# SelfModel ↔ memory_root cite-chain
# ---------------------------------------------------------------------
def test_selfmodel_snapshot_picks_up_memory_root(tmp_path):
"""When a memory_root exists, SelfModel.snapshot() folds it in."""
from arborist.memory import snapshot as memory_snapshot, store_snapshot as memory_store
from arborist.selfmodel import snapshot as sm_snapshot
from arborist.store import connect, transaction
db = tmp_path / "shard.db"
conn = connect(db)
try:
# 1) Store a memory snapshot first.
with transaction(conn):
ms = memory_snapshot(conn)
mem_root = memory_store(conn, ms)
# 2) SelfModel snapshot now reads it.
with transaction(conn):
sm = sm_snapshot(conn)
assert sm.memory_root == mem_root
finally:
conn.close()
def test_selfmodel_snapshot_works_without_memory_root(tmp_path):
"""SelfModel.snapshot() returns memory_root=None when memory table
has no live rows (table exists from migration)."""
from arborist.selfmodel import snapshot as sm_snapshot
from arborist.store import connect, transaction
db = tmp_path / "shard.db"
conn = connect(db)
try:
with transaction(conn):
sm = sm_snapshot(conn)
assert sm.memory_root is None
finally:
conn.close()
# ---------------------------------------------------------------------
# π* registry guarantees
# ---------------------------------------------------------------------
def test_pi_star_registry_rejects_conflicting_registration():
"""Re-registering the same key with a different instance must raise."""
from arborist.pi_star import register
from arborist.pi_star.protocol import PiStarError
from arborist.pi_star.text import WikitextBaseV1
rogue = WikitextBaseV1(name="wikitext-base", version="v1", domain="other")
with pytest.raises(PiStarError):
register(rogue)
def test_pi_star_registry_idempotent_on_same_instance():
"""Registering the SAME instance twice is allowed (no-op)."""
from arborist.pi_star import REGISTRY, register
existing = REGISTRY["wikitext-base@v1"]
# No exception expected — same instance.
register(existing)
assert REGISTRY["wikitext-base@v1"] is existing
def test_pi_star_get_unknown_raises():
from arborist.pi_star import get
with pytest.raises(KeyError):
get("nonexistent@v999")
# ---------------------------------------------------------------------
# Battery runners chain to π* registry
# ---------------------------------------------------------------------
def test_battery_runtime_digest_changes_when_pi_star_added(tmp_path):
"""The runtime_digest in BatteryResult should reflect the active π*
registry. Adding a new π* changes the fingerprint."""
from bench.batteries.b_5s import _runtime_digest
from arborist.pi_star import REGISTRY, register
from arborist.pi_star.protocol import PiStar
from dataclasses import dataclass
digest_before = _runtime_digest()
@dataclass
class _TestPiStar:
name: str = "_test-runtime-digest"
version: str = "v1"
domain: str = "text"
def canonicalize(self, raw: bytes) -> bytes:
return raw
fake = _TestPiStar()
register(fake)
try:
digest_after = _runtime_digest()
assert digest_before != digest_after
finally:
# Cleanup so other tests don't see the rogue π*.
del REGISTRY["_test-runtime-digest@v1"]
digest_restored = _runtime_digest()
assert digest_restored == digest_before
# ---------------------------------------------------------------------
# Bench suite end-to-end
# ---------------------------------------------------------------------
def test_full_dav1dprometheus_suite_runs_end_to_end(tmp_path, capsys):
"""`bench.batteries.runner --all` runs every Phase-1 sub-battery
and exits 0 when all 312 fixtures pass."""
from bench.batteries.runner import main
rc = main(["--all", "--out", str(tmp_path / "result.json")])
assert rc == 0
payload = json.loads((tmp_path / "result.json").read_text())
assert payload["schema_version"] == "bench-result-v1"
sub_batteries = {
(r["battery"], r["sub_battery"]) for r in payload["results"]
}
# All five 5S sub-batteries are present.
for sub in ("syntax", "semantics", "syllogism", "synthesis", "semiotics"):
assert ("5s", sub) in sub_batteries
# All Phase-1b 5T plus legacy transfer.
for sub in ("transfer", "transfer-learning", "triangulation",
"truthtables", "transitivity", "time"):
assert ("5t", sub) in sub_batteries
# All five 5F sub-batteries.
for sub in ("function", "finetuning", "falsification",
"formulate", "feedback-loop"):
assert ("5f", sub) in sub_batteries
# All five 5R sub-batteries (Phase 2 of #000021).
for sub in ("react", "rearrange", "restore", "replicate", "resonate"):
assert ("5r", sub) in sub_batteries
# Aggregate pass counts: every sub-battery must have zero failures.
for r in payload["results"]:
assert r["fail_count"] == 0, (
f"{r['battery']}/{r['sub_battery']} failed "
f"{r['fail_count']} fixtures"
)
def test_full_suite_total_fixture_count():
"""Sanity check: the complete Dav1DPrometheus suite executes 462
deterministic tasks across 21 sub-batteries (5S+5T+5F+5R)."""
from bench.batteries.runner import _DEFAULT_FIXTURES, _run_one
total = 0
for (battery, sub), fx in _DEFAULT_FIXTURES.items():
result = _run_one(battery, sub, Path(fx))
total += result.pass_count + result.fail_count
assert total == 462
def test_5s_phase1a_digests_unchanged_after_phase1b():
"""Closure-criterion guard: 5S Phase 1a fixture digests stay pinned."""
from bench.batteries.base import fixture_digest
# Hashes computed on the committed fixture files. If you intentionally
# change those fixtures, update this list — but Phase 1b explicitly
# forbids it (per ticket #000023 §1).
syntax_digest = fixture_digest(
REPO_ROOT / "bench" / "fixtures" / "5s" / "syntax-v1.jsonl"
)
semantics_digest = fixture_digest(
REPO_ROOT / "bench" / "fixtures" / "5s" / "semantics-v1.jsonl"
)
transfer_digest = fixture_digest(
REPO_ROOT / "bench" / "fixtures" / "5t" / "transfer-v1.jsonl"
)
# Two reads must be byte-equal (digest is just a content hash).
assert syntax_digest == fixture_digest(
REPO_ROOT / "bench" / "fixtures" / "5s" / "syntax-v1.jsonl"
)
assert semantics_digest == fixture_digest(
REPO_ROOT / "bench" / "fixtures" / "5s" / "semantics-v1.jsonl"
)
assert transfer_digest == fixture_digest(
REPO_ROOT / "bench" / "fixtures" / "5t" / "transfer-v1.jsonl"
)
# ---------------------------------------------------------------------
# Cross-cutting: full state-space round-trip
# ---------------------------------------------------------------------
def test_full_session_state_round_trip(tmp_path):
"""End-to-end: ingest doc → snapshot SelfModel → snapshot Memory →
inspect via CLI surfaces. All three v8-substrate components present
+ chain stays clean."""
import hashlib
from arborist.cli import build_parser
from arborist.document import Document
from arborist.ingest import ingest_source
from arborist.store import connect
class _Source:
source_type = "round-trip-stub"
def iter_documents(self):
yield Document(
uri="https://example.com/round-trip",
content="Round-trip integration content.",
title="Round Trip",
source_type="round-trip-stub",
)
db = tmp_path / "shard.db"
conn = connect(db)
try:
ingest_source(conn, _Source(), batch_size=10)
finally:
conn.close()
parser = build_parser()
# Capture: argparse's args.func calls our handlers, all of which
# print JSON on stdout.
import io
import sys
def _capture(argv: list[str]) -> dict:
old = sys.stdout
sys.stdout = io.StringIO()
try:
args = parser.parse_args(argv)
args.func(args)
return json.loads(sys.stdout.getvalue())
finally:
sys.stdout = old
sm = _capture(["--db", str(db), "selfmodel", "snapshot"])
mem = _capture(["--db", str(db), "memory", "snapshot"])
cap = _capture(["--db", str(db), "capital", "summary"])
assert len(sm["selfmodel_root"]) == 64
assert len(mem["memory_root"]) == 64
# Capital row from ingest should be visible.
assert cap["row_count"] >= 1
# Audit chain still verifies.
conn = connect(db)
try:
rows = conn.execute(
"SELECT event_hash, prev_event_hash, body FROM audit_events ORDER BY seq"
).fetchall()
prev = None
for row in rows:
h = hashlib.sha256()
if prev is not None:
h.update(bytes.fromhex(prev))
h.update(row["body"].encode("utf-8", errors="surrogatepass"))
assert h.hexdigest() == row["event_hash"]
prev = row["event_hash"]
finally:
conn.close()