Files
rune/tests/test_behavior_port.py
T
Nick 4e1eee7d9e feat(behavior): port to FULL Rust — dichotomy wave
RUST verdict (dichotomy law, #2479 tranche): behavior.py's FULL decision
surface is ported to src/rust/behavior — the reinforce decision math
(habit_reinforce: activation/success/failure counting, confidence
min(1.0, 0.5 + rate*0.5), candidate→active promotion at min_activations;
correction_reinforce: success +0.1 / failure −0.3 floor/ceiling, evidence
counting, active/archived threshold at 0.3), the match_context per-row
predicates (scope-pattern regex IGNORECASE with invalid-regex skip,
domain case-insensitive substring, confidence > 0.3 gates), the
scope_tags parse fallback, the PLUGIN descriptor (frozen data), and the
db-path resolution. IO stays Python: SQLite I/O + row ordering (the
crate links no sqlite), wall-clock now, argparse CLI.

Wiring: bh_loader.py ctypes (doc-envelope, cs_free_string — no free
symbol re-export), facade delegation in behavior.py with
RUNE_BEHAVIOR_CORE=python fallback pin (honored in the loader probe);
the evidence_provenance ep_loader path remains as the pin oracle.

Truth: scripts/gen_behavior_truth.py freezes 30 live-oracle cases (add
shapes, match branch matrix, reinforce matrix with promotion/archival/
floor/ceiling, list filters, stats, PLUGIN) embedded verbatim as
bh_truth.json; battery 10 tests replay it (clippy 0).

Parity: 100% live (14/14 check groups over the frozen shapes + list/
stats leaves, rust facade vs pinned-python, 0 divergences); 13/13 port
+ shim wiring tests; full suite failure set IDENTICAL to the clean tree
(95 pre-existing, 0 new).

Contract BUILDER-2026-10-11T16:27:58Z-92bb54-factory-wave (building).
2026-10-12 00:05:38 +01:00

131 lines
5.1 KiB
Python

"""Tests: behavior.py Rust core port (dichotomy wave, #2479).
Delegation + fallback-pin discipline: the reinforce decision math and
the match_context per-row predicates come from the Rust core when
available; RUNE_BEHAVIOR_CORE=python pins the Python oracle (the
evidence_provenance shim path for reinforce, inline predicates for
match).
"""
from __future__ import annotations
import os
import sys
from pathlib import Path
import pytest
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT / "src"))
from rune import behavior as bh # noqa: E402
from rune import bh_loader # noqa: E402
@pytest.fixture()
def isolated_env(tmp_path):
home = tmp_path / "home"
(home / "memory").mkdir(parents=True)
saved = {k: os.environ.get(k) for k in ("RUNE_HOME", "RUNE_MEMORY_DIR")}
os.environ["RUNE_HOME"] = str(home)
os.environ["RUNE_MEMORY_DIR"] = str(home / "memory")
# bh resolves DB_PATH at import; re-point it at THIS test's home and
# clear any prior db so ids align per test.
bh.DB_PATH = os.path.join(str(home / "memory"), "behavior.db")
if os.path.exists(bh.DB_PATH):
os.remove(bh.DB_PATH)
yield home
for k, v in saved.items():
if v is None:
os.environ.pop(k, None)
else:
os.environ[k] = v
@pytest.fixture()
def core_built():
if not bh_loader.available():
pytest.skip("rust behavior core not built")
return True
def test_loader_probe_and_pin(core_built, monkeypatch):
monkeypatch.delenv("RUNE_BEHAVIOR_CORE", raising=False)
assert bh_loader.core() is not None
assert bh._bh_core() is not None
monkeypatch.setenv("RUNE_BEHAVIOR_CORE", "python")
assert bh_loader.core() is None
assert bh._bh_core() is None
def test_reinforce_delegates(core_built, monkeypatch, isolated_env):
monkeypatch.delenv("RUNE_BEHAVIOR_CORE", raising=False)
cid = bh.add_correction("say X", "say Y", domain="test")["added"]
r = bh.reinforce(cid, "success", item_type="correction")
assert r == {"reinforced": cid, "confidence": 0.6, "state": "active"}
hid = bh.add_habit("trigger t", "action a")["added"]
for _ in range(3):
r = bh.reinforce(hid, "success")
assert r["state"] == "active" and r["confidence"] == 1.0
assert bh.reinforce(99999, "success") == {"error": "Habit 99999 not found"}
assert bh.reinforce(99998, "success", item_type="correction") == {
"error": "Correction 99998 not found"}
def test_reinforce_pin_matches_shim_oracle(core_built, monkeypatch, isolated_env):
monkeypatch.setenv("RUNE_BEHAVIOR_CORE", "python")
cid = bh.add_correction("pin X", "pin Y")["added"]
pinned = bh.reinforce(cid, "failure", item_type="correction")
assert pinned == {"reinforced": cid, "confidence": 0.2, "state": "archived"}
def test_match_delegates(core_built, monkeypatch, isolated_env):
monkeypatch.delenv("RUNE_BEHAVIOR_CORE", raising=False)
bh.add_correction("o1", "scoped corr", domain="shell", pattern="deploy")
bh.add_correction("o3", "plain corr")
bh.add_correction("o4", "bad regex corr", pattern="([unclosed")
bh.add_habit("deploy", "run deploy script")
got = bh.match_context("deploy to shell now")
assert [c["corrected_behavior"] for c in got["corrections"]] == ["scoped corr", "plain corr"]
assert [h["action_template"] for h in got["habits"]] == ["run deploy script"]
# invalid regex rows are skipped by the core, not errors
got = bh.match_context("anything at all")
assert [c["corrected_behavior"] for c in got["corrections"]] == ["plain corr"]
assert isinstance(got["corrections"][0]["scope_tags"], dict)
def test_match_pin_matches_inline_oracle(core_built, monkeypatch, isolated_env):
monkeypatch.setenv("RUNE_BEHAVIOR_CORE", "python")
bh.add_correction("o2", "domain corr", domain="editor")
got = bh.match_context("deploy the editor")
assert [c["corrected_behavior"] for c in got["corrections"]] == ["domain corr"]
assert bh.match_context("deploy the editor", match_type="habit") == {
"corrections": [], "habits": []}
def test_kernel_failure_falls_back(core_built, monkeypatch, isolated_env):
"""A broken core must degrade to the shim oracle, never crash."""
monkeypatch.delenv("RUNE_BEHAVIOR_CORE", raising=False)
class Boom:
def habit_reinforce(self, row, outcome):
raise RuntimeError("simulated kernel failure")
def correction_reinforce(self, row, outcome):
raise RuntimeError("simulated kernel failure")
def match_row(self, kind, row, context):
raise RuntimeError("simulated kernel failure")
monkeypatch.setattr(bh, "_bh_core", lambda: Boom())
cid = bh.add_correction("fb X", "fb Y")["added"]
assert bh.reinforce(cid, "success", item_type="correction")["confidence"] == 0.6
bh.add_correction("fbm", "fallback corr", domain="editor")
got = bh.match_context("deploy the editor")
assert "fallback corr" in [c["corrected_behavior"] for c in got["corrections"]]
def test_plugin_shape():
assert bh.PLUGIN["name"] == "behavior"
assert bh_loader.plugin() == bh.PLUGIN