Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3497d2b7a9 | ||
|
|
60a21dc7da |
No files matched your search
@@ -770,6 +770,7 @@ class ConstructionInterface:
|
||||
material_closure = self._auto_material_change(
|
||||
scope_files or [],
|
||||
note=f"contract {contract_id} closed (material change)")
|
||||
self._record_impact_delta(contract_id, scope_files or [])
|
||||
continuation = self._programme_continuation(contract_id)
|
||||
state = {
|
||||
"phase": "closed",
|
||||
@@ -789,6 +790,30 @@ class ConstructionInterface:
|
||||
state=state,
|
||||
)
|
||||
|
||||
def _record_impact_delta(self, contract_id: str, scope_files: list) -> None:
|
||||
"""T5.15 slice 4 (the iteration wire): a material closure records a
|
||||
predicted-vs-observed impact delta (rule 19H — deltas compound into
|
||||
better future prediction). Governance-only closes (no scope files)
|
||||
record nothing. Failure degrades: the ledger never fails a close."""
|
||||
if not scope_files:
|
||||
return
|
||||
try:
|
||||
from rune import iteration as it
|
||||
|
||||
it.record_impact_delta(
|
||||
Path(self.scaffold.current).parent,
|
||||
{
|
||||
"contract_id": contract_id,
|
||||
"scope_files": sorted(scope_files),
|
||||
"predicted_frontier": "from-contract-scope",
|
||||
"recorded_at": ss.now(),
|
||||
},
|
||||
)
|
||||
except Exception as exc: # noqa: BLE001 — delta failure never fails a closure
|
||||
self._record_session(
|
||||
self.scaffold.load(), "impact_delta_degraded",
|
||||
f"{contract_id}: {str(exc)[:120]}")
|
||||
|
||||
def _programme_continuation(self, contract_id: str) -> dict | None:
|
||||
"""T5.15 slice 3 (the programme_continuation wire): after a legal
|
||||
close, any persisted Programme stage bound to this contract is
|
||||
|
||||
@@ -2525,6 +2525,53 @@ I have tools to read, write, edit, search, lint, test, format, and commit code.
|
||||
_operator = None
|
||||
|
||||
|
||||
def _forge_client_for_projection():
|
||||
"""The client seam for the forge projection (test-overridable)."""
|
||||
try:
|
||||
from .forge_client import create_forge_client
|
||||
except ImportError: # TUI top-level deploy (script mode)
|
||||
from forge_client import create_forge_client
|
||||
return create_forge_client()
|
||||
|
||||
|
||||
def _project_failure_to_forge(*, behaviour_id: str, observation: dict,
|
||||
repo: str) -> dict:
|
||||
"""T5.15 slice 5 (the forge projection wire): a recorded failure
|
||||
projects to the human-visible forge (rule 19P — the projection is a
|
||||
VIEW; canonical truth stays in the ledger). Uses the ALREADY-WIRED
|
||||
forge client (create_forge_client → NullAdapter when unconfigured, so
|
||||
the projection is dormant by construction). A thin shim adapts the
|
||||
client's create_issue to the testing-mode projection's
|
||||
create_or_patch_release contract. Forge failure NEVER breaks the
|
||||
failure path — it degrades to a recorded marker."""
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
try:
|
||||
client = _forge_client_for_projection()
|
||||
|
||||
class _Shim:
|
||||
"""Adapts create_issue → the projection's release contract."""
|
||||
|
||||
@staticmethod
|
||||
def create_or_patch_release(repo_: str, tag: str,
|
||||
title: str, body: str) -> dict:
|
||||
return client.create_issue(title, body)
|
||||
|
||||
try:
|
||||
from .testing_mode import project_failure_to_forge
|
||||
except ImportError:
|
||||
from testing_mode import project_failure_to_forge
|
||||
|
||||
return project_failure_to_forge(
|
||||
_Shim(), repo, behaviour_id=behaviour_id,
|
||||
observation=observation)
|
||||
except Exception as exc: # noqa: BLE001 — projection failure never rewrites truth
|
||||
log.debug("forge projection degraded: %s", exc)
|
||||
return {"projected": False,
|
||||
"error": str(exc)[:200]}
|
||||
|
||||
|
||||
def _wire_auto_filer() -> None:
|
||||
"""Composition-root wiring for the failure auto-filer (#122).
|
||||
|
||||
|
||||
@@ -755,3 +755,42 @@ def test_two_submits_same_second_get_distinct_contract_ids(tmp_path):
|
||||
e2 = _submit(iface, objective="wave stage two")
|
||||
assert e1["success"] and e2["success"], (e1["errors"], e2["errors"])
|
||||
assert e1["ids"]["contract_id"] != e2["ids"]["contract_id"]
|
||||
|
||||
|
||||
# ── T5.15 slice 4: iteration impact-delta recorded at close ─────────────────
|
||||
|
||||
|
||||
def test_close_records_impact_delta_when_scope_changed(tmp_path):
|
||||
"""A closed contract with scope_files records a predicted-vs-observed
|
||||
impact delta into the project store's impact_deltas.jsonl (rule 19H:
|
||||
deltas compound into better future prediction)."""
|
||||
from rune import iteration as it
|
||||
|
||||
iface = _iface(tmp_path)
|
||||
env = _submit(iface)
|
||||
cid = env["ids"]["contract_id"]
|
||||
for ph in ("approved", "building", "verifying"):
|
||||
iface.transition(cid, ph)
|
||||
iface.close(cid, observed_evidence=[{"entity": "capability:exporter", "detail": "done"}],
|
||||
scope_files=["src/export_module.py"])
|
||||
delta_file = tmp_path / ".rune" / "scaffold" / "impact_deltas.jsonl"
|
||||
assert delta_file.exists(), "close must persist the impact delta"
|
||||
import json as _json
|
||||
deltas = [_json.loads(l) for l in delta_file.read_text().splitlines() if l.strip()]
|
||||
assert any(d.get("contract_id") == cid for d in deltas)
|
||||
d = next(d for d in deltas if d.get("contract_id") == cid)
|
||||
assert d.get("scope_files") == ["src/export_module.py"]
|
||||
assert "recorded_at" in d
|
||||
|
||||
|
||||
def test_close_without_scope_records_no_delta(tmp_path):
|
||||
"""A governance-only close (no scope files) records nothing — the
|
||||
delta ledger only carries material changes."""
|
||||
iface = _iface(tmp_path)
|
||||
env = _submit(iface)
|
||||
cid = env["ids"]["contract_id"]
|
||||
for ph in ("approved", "building", "verifying"):
|
||||
iface.transition(cid, ph)
|
||||
iface.close(cid, observed_evidence=[{"entity": "capability:exporter", "detail": "done"}])
|
||||
delta_file = tmp_path / ".rune" / "scaffold" / "impact_deltas.jsonl"
|
||||
assert not delta_file.exists()
|
||||
@@ -0,0 +1,84 @@
|
||||
"""T5.15 slice 5: the forge projection wired into the operator failure path.
|
||||
|
||||
The projection (testing_mode.project_failure_to_forge) was TEST_PROVEN with
|
||||
zero runtime callers. This wire invokes it from the operator's recorded-
|
||||
failure sink using the ALREADY-WIRED forge client (create_forge_client —
|
||||
NullAdapter when unconfigured → the projection is dormant by construction,
|
||||
never an error source).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
sys.path.insert(0, str(ROOT / "src"))
|
||||
|
||||
from rune import rune_operator as ro # noqa: E402
|
||||
from rune import testing_mode as tm # noqa: E402
|
||||
|
||||
|
||||
class RecordingAdapter:
|
||||
"""Records create_issue calls — proves the projection fired."""
|
||||
|
||||
def __init__(self):
|
||||
self.calls: list[tuple[str, str]] = []
|
||||
|
||||
def create_issue(self, title: str, body: str) -> dict:
|
||||
self.calls.append((title, body))
|
||||
return {"created": True, "number": len(self.calls)}
|
||||
|
||||
|
||||
def test_recorded_failure_projects_to_forge(monkeypatch):
|
||||
adapter = RecordingAdapter()
|
||||
monkeypatch.setattr(ro, "_forge_client_for_projection", lambda: adapter)
|
||||
result = ro._project_failure_to_forge(
|
||||
behaviour_id="behaviour-test-1",
|
||||
observation={"expected": "tests pass", "observed": "2 failed",
|
||||
"observation_id": "obs-1", "reproduction": "pytest -k x"},
|
||||
repo="conductor/rune",
|
||||
)
|
||||
# the projection returns the adapter's own dict (create_issue result) —
|
||||
# the wire is proven by the adapter having been CALLED
|
||||
assert len(adapter.calls) == 1
|
||||
assert result.get("created") is True
|
||||
title, _body = adapter.calls[0]
|
||||
assert "behaviour-test-1" in title
|
||||
|
||||
|
||||
def test_null_adapter_projection_is_dormant_not_error(monkeypatch):
|
||||
"""Unconfigured forge → the projection returns a dormant marker; the
|
||||
failure path is untouched."""
|
||||
monkeypatch.setattr(ro, "_forge_client_for_projection", lambda: _NullAdapterProbe())
|
||||
result = ro._project_failure_to_forge(
|
||||
behaviour_id="b-2",
|
||||
observation={"expected": "x", "observed": "y"},
|
||||
repo="r",
|
||||
)
|
||||
assert result.get("created") is False
|
||||
assert result.get("reason") == "no forge configured"
|
||||
|
||||
|
||||
def test_forge_unavailable_never_breaks_the_failure_path(monkeypatch):
|
||||
from rune import forge as _forge
|
||||
|
||||
def boom():
|
||||
raise _forge.ForgeUnavailable("forge down")
|
||||
monkeypatch.setattr(ro, "_forge_client_for_projection", lambda: boom())
|
||||
result = ro._project_failure_to_forge(
|
||||
behaviour_id="b-3",
|
||||
observation={"expected": "x", "observed": "y"},
|
||||
repo="r",
|
||||
)
|
||||
assert result["projected"] is False
|
||||
assert "forge down" in result.get("error", "")
|
||||
|
||||
|
||||
class _NullAdapterProbe:
|
||||
"""Mimics forge_client.NullAdapter's shape for the dormant test."""
|
||||
|
||||
def create_issue(self, title: str, body: str) -> dict:
|
||||
return {"created": False, "reason": "no forge configured"}
|
||||
Reference in new issue
Block a user