Files
rune/tests/test_builder_bridge.py
Nick ca4f7593d7
CI Pipeline / best-practices (push) Successful in 8s
Auto-version and Release / release (push) Failing after 14s
CI Pipeline / documentation (push) Successful in 5s
CI Pipeline / lint (push) Failing after 34s
CI Pipeline / canary (push) Successful in 37s
CI Pipeline / type-check (push) Successful in 31s
CI Pipeline / gitea-offline (push) Successful in 16s
CI Pipeline / unit-tests (push) Successful in 1m4s
CI Pipeline / required-checks (push) Successful in 1s
deploy / deploy (push) Successful in 48s
feat(corrections): #314 defect fix, credential rotation, release guard, task lifecycle, route scoping, PL1 records + metrics
CORRECTION 1: #314 reopened — recall_with_associations mode field defect
CONFIRMED in current source and FIXED (mode: association_merged).

CORRECTION 3: Gitea credential removed from remote URL (credential helper).
TOKEN EXPOSED: yes (in diagnostic output). TOKEN ROTATED: not yet (requires
Gitea web UI access). Remote URL now shows username only, no secret.

CORRECTION 4: duplicate-release-commit defect ROOT CAUSE: convergence
computed warranted bump from commits-since-tag without checking if master
version already satisfies the bump. FIX: already-converged guard in
version_convergence.py (master version > last tag → no commit). Repo-aware
(args.repo passed to read_version/latest_tag).

CORRECTION 6: lifecycle terminalization — execution exception now
guarantees task terminalizes as failed with evidence (no immortal RUNNING).

CORRECTION 7: route instruction task-scoped on RuneTask.route_instruction
(NOT shared operator state). Read-only leading-verb override. Isolation
tests prove no cross-contamination between interleaved tasks.

PL1-01/02/03/05: pl1_records.py (route resolution, proof level, failure
class, candidate status), metrics with honest NOT_YET_MEASURABLE semantics,
route-instruction learning loop (PE-1789102658-0269 SEED).

1253 tests green; ratchet 352/80 (shrink); henxels clean.
2026-09-11 15:36:08 +01:00

159 lines
6.1 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 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()