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).
131 lines
5.1 KiB
Python
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
|