Files
rune/tests/test_builder_bridge.py
conductor 873d0025ad fix(scaffolding): T22 — cover the new paths; two defects the coverage tests found
Self-review against the CI gates surfaced two genuine defects in the first
commit. Changed-lines coverage is now 85% (floor 70):

1. The bridge projection read the WRONG STORE. `_active_construction_lines`
   built `ConstructionInterface` without `project_store`, so it fell back to
   the interface default instead of the configured construction store — the
   `RUNE_CONSTRUCTION_STORE` override was ignored, so a redirected
   deployment would have projected another project's construction state.
   Fixed: pass `_construction_store()` + the project id.

2. LOST UPDATE between the intent-layer note and the main activation path.
   The degrade path called `_construction_note()`, which does its own
   load/save; the normal path then saved its own stale `doc`, silently
   clobbering the note. Fixed: append the note to the SAME in-memory doc
   (one doc, one save), so the failure is recorded and survives.

New coverage: active-contract projection + capsule rendering + read-only
guarantee; store-resolution override; intent-layer-unavailable degrade path.

Note: the stale TUI banner (`Rune v0.87.6` vs `__version__` 0.88.0) that
this commit also fixed on the old base was fixed independently on master by
the 0.88.2 release bump, so the rebase takes master's banner — nothing to
carry here.
2026-09-23 18:10:07 +01:00

236 lines
9.6 KiB
Python

#!/usr/bin/env python3
"""tests/test_builder_bridge.py — MiMo builder bridge canaries (Option C).
The bridge is a thin adapter over Rune Core: zero model calls, bounded
capsule, same authority the operator consumes. These tests prove the
contract, not the cognition (the cognition is the core's, tested there).
"""
import json
import os
import sys
import unittest
PKG_ROOT = os.path.join(os.path.dirname(__file__), "..")
sys.path.insert(0, os.path.join(PKG_ROOT, "src"))
from rune import builder_bridge # noqa: E402
from rune.builder_bridge import ( # noqa: E402
BuilderCapsule,
MAX_LESSON_LINES,
MAX_MEMORY_LINES,
preflight,
)
class ConstructionProjectionActive(unittest.TestCase):
"""T22: with a LIVE contract the projection surfaces canonical state
(contract/phase/scope/obligations/next-action) and renders it in the
capsule markdown; the read-only projection never mutates the store."""
def _active_store(self, td):
from pathlib import Path
from rune.scaffold import Scaffold
store = Path(td) / "scaffold"
sc = Scaffold(store, "fixture-proj")
sc.init(purpose="fixture")
doc = sc.load()
sc.new_contract(doc, contract_id="c-t22", need="add the retry backoff",
origin="user", scope=["src/rune/retry.py"],
requirement="prove the projection")
sc.save(doc)
return str(store)
def test_active_contract_projects_and_renders(self):
import tempfile
from pathlib import Path
with tempfile.TemporaryDirectory() as td:
store = self._active_store(td)
old = os.environ.get("RUNE_CONSTRUCTION_STORE")
os.environ["RUNE_CONSTRUCTION_STORE"] = store
try:
lines = builder_bridge._active_construction_lines()
self.assertTrue(lines, "an open non-terminal contract must project")
self.assertIn("c-t22", lines[0])
self.assertTrue(any("scope" in ln for ln in lines), lines)
cap = builder_bridge.preflight("add the retry backoff")
self.assertTrue(cap.construction_lines)
md = cap.to_markdown()
self.assertIn("Active Scaffolding construction", md)
# read-only: the store is unchanged by projection
before = (Path(store) / "current.json").read_text()
builder_bridge._active_construction_lines()
self.assertEqual(before, (Path(store) / "current.json").read_text())
finally:
if old is None:
os.environ.pop("RUNE_CONSTRUCTION_STORE", None)
else:
os.environ["RUNE_CONSTRUCTION_STORE"] = old
def test_store_resolution_honours_env_override(self):
self.assertEqual(builder_bridge._construction_store(),
os.environ.get("RUNE_CONSTRUCTION_STORE"))
class ConstructionProjection(unittest.TestCase):
"""T22: the bridge surfaces the ACTIVE Scaffolding construction (the
shared Construction Context) to the builder — read-only projection,
inject-nothing preserved when no contract is active."""
def test_no_active_contract_projects_nothing(self):
import tempfile
from pathlib import Path
with tempfile.TemporaryDirectory() as td:
old = os.environ.get("RUNE_CONSTRUCTION_STORE")
os.environ["RUNE_CONSTRUCTION_STORE"] = str(Path(td) / "scaffold")
try:
self.assertEqual(builder_bridge._active_construction_lines(), [])
finally:
if old is None:
os.environ.pop("RUNE_CONSTRUCTION_STORE", None)
else:
os.environ["RUNE_CONSTRUCTION_STORE"] = old
def test_terminal_phase_is_not_active_construction(self):
"""A contract left status='open' with a TERMINAL phase must not be
reported as active (live store had 8 such records)."""
from rune.scaffold import TERMINAL_PHASES
self.assertIn("abandoned", TERMINAL_PHASES)
self.assertIn("closed", TERMINAL_PHASES)
self.assertNotIn("building", TERMINAL_PHASES)
class BridgeContract(unittest.TestCase):
def test_preflight_local_task_is_cheap_and_bounded(self):
cap = preflight("Fix the telemetry summary log level constant in stage_telemetry.py")
self.assertIn(cap.task_route, ("LOCAL_FIX", "STANDARD_CHANGE"))
self.assertFalse(cap.floor)
self.assertLessEqual(len(cap.memory_lines), MAX_MEMORY_LINES)
self.assertLessEqual(len(cap.lesson_lines), MAX_LESSON_LINES)
d = cap.to_dict()
self.assertIn("generated_at", d)
self.assertEqual(d["core_version"], "rune-core/operator-path")
def test_preflight_research_task_carries_floor(self):
cap = preflight("New subsystem: evaluate and adopt an external vector database dependency")
self.assertEqual(cap.task_route, "RESEARCHED_CHANGE")
self.assertTrue(cap.floor)
self.assertTrue(any("floor" in c.lower() for c in cap.constraints))
def test_lesson_lines_empty_until_pl1(self):
# Handoff §9: raw SEED records are outcome telemetry, not lessons.
# The bridge must not inject unvalidated process pressure.
cap = preflight("Fix typo in CLI help text")
self.assertEqual(cap.lesson_lines, [])
def test_markdown_renders_without_empty_sections(self):
cap = BuilderCapsule(task_route="LOCAL_FIX", unknowns=["verifiability"])
md = cap.to_markdown()
self.assertIn("LOCAL_FIX", md)
self.assertIn("verifiability", md)
self.assertNotIn("Relevant Rune memory", md) # empty section omitted
self.assertNotIn("Validated process lessons", md)
def test_memory_selector_plumbs_through(self):
class FakeSelector:
def select_for_task(self, text):
class Block:
content = "memory line one\nmemory line two"
class Result:
blocks = [Block()]
return Result()
import rune.builder_bridge as bb
saved = bb._SELECTOR
bb._SELECTOR = FakeSelector()
try:
lines = bb._retrieve_memory("Fix typo")
self.assertTrue(any("memory line" in m for m in lines))
finally:
bb._SELECTOR = saved
def test_selector_failure_degrades_not_blocks(self):
class BrokenSelector:
def select_for_task(self, text):
raise RuntimeError("boom")
cap = preflight("Fix typo", memory_selector=BrokenSelector())
self.assertEqual(cap.memory_lines, [])
self.assertIn(cap.task_route, ("LOCAL_FIX", "STANDARD_CHANGE", "RESEARCHED_CHANGE"))
class ResearchRequiredLifecycle(unittest.TestCase):
"""research_required is a nonterminal disposition, NOT failure (§2)."""
def test_state_machine_allows_lifecycle(self):
import sys as _sys
_sys.path.insert(0, os.path.join(PKG_ROOT, "src"))
from rune.orchestration.conversation_state import TASK_STATUSES, TASK_TRANSITIONS, OPEN_TASK_STATUSES
self.assertIn("research_required", TASK_STATUSES)
self.assertIn("research_required", OPEN_TASK_STATUSES)
# running -> research_required -> running (resume) and -> completed
self.assertIn("research_required", TASK_TRANSITIONS["running"])
self.assertIn("running", TASK_TRANSITIONS["research_required"])
self.assertIn("completed", TASK_TRANSITIONS["research_required"])
self.assertIn("failed", TASK_TRANSITIONS["research_required"])
def test_experience_consumer_skips_research_dispatch(self):
import tempfile
import json as _json
sys.path.insert(0, os.path.join(PKG_ROOT, "scripts"))
from process_experience import record_operator_outcomes
with tempfile.TemporaryDirectory() as tmp:
state = {
"tasks": [
{
"task_id": "t1",
"title": "research task",
"status": "research_required",
"outcome_ref": "research_dispatch",
"assigned_model": "m",
"result": "RESEARCH_REQUIRED",
},
{
"task_id": "t2",
"title": "real failure",
"status": "failed",
"outcome_ref": "",
"assigned_model": "m",
},
]
}
sp = os.path.join(tmp, "task_state.json")
with open(sp, "w") as f:
_json.dump(state, f)
lp = os.path.join(tmp, "led.jsonl")
recs = record_operator_outcomes(sp, lp)
self.assertEqual(len(recs), 1) # only the real failure
self.assertEqual(recs[0]["result_type"], "FAILURE")
self.assertNotIn("research", recs[0]["symptom"].lower())
class RouteRiskFlagsContract(unittest.TestCase):
def test_detect_flags_vocabulary(self):
from rune.route_risk_flags import RISK_FLAGS, UNCERTAINTY_FLAGS, detect_flags
risk, unc = detect_flags("this adds a new dependency and changes memory architecture")
self.assertIn("new_dependency", risk)
self.assertIn("memory_architecture", risk)
risk2, unc2 = detect_flags("competing designs with expensive reversal")
self.assertIn("competing_designs", unc2)
self.assertIn("expensive_reversal", unc2)
risk3, unc3 = detect_flags("fix typo in help text")
self.assertEqual(risk3, [])
self.assertEqual(unc3, [])
if __name__ == "__main__":
unittest.main()