Files
rune/tests/test_t214_material_closure.py
T
Nick f3ef4672f3
CI Pipeline / canary (push) Successful in 42s
CI Pipeline / type-check (push) Successful in 31s
deploy / deploy (push) Skipped
CI Pipeline / lint (push) Successful in 56s
CI Pipeline / best-practices (push) Successful in 4s
CI Pipeline / gitea-offline (push) Successful in 15s
CI Pipeline / documentation (push) Successful in 4s
CI Pipeline / unit-tests (push) Successful in 3m2s
CI Pipeline / required-checks (push) Successful in 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 6s
CI Pipeline / best-practices (pull_request) Successful in 8s
CI Pipeline / documentation (pull_request) Successful in 5s
CI Pipeline / unit-tests (pull_request) Failing after 33s
CI Pipeline / canary (pull_request) Successful in 37s
CI Pipeline / lint (pull_request) Successful in 47s
CI Pipeline / type-check (pull_request) Successful in 27s
CI Pipeline / gitea-offline (pull_request) Successful in 18s
Spec gate (headless) / spec-gate (pull_request) Successful in 13s
CI Pipeline / required-checks (pull_request) Failing after 2s
CI Pipeline / CI Pipeline / notify-event-bridge (pull_request) Successful in 4s
Auto-label / priority-gate (pull_request) Skipped
Auto-label / auto-label (pull_request) Successful in 2s
test(t21.4): coverage for auto material-change closure + spine entry-point seeding (CI coverage ratchet)
2026-09-17 02:07:15 +01:00

169 lines
7.8 KiB
Python

"""test_t214_material_closure.py — T21.4 §21.4H/§21.4G coverage.
Covers:
- construction_interface.close() AUTO material-change closure
(rule 19Q.2: governed construction cannot bypass the revision flow)
- spine.build() entry-point seeding (__main__ is a runtime path)
"""
from __future__ import annotations
import json
import sys
from pathlib import Path
import pytest
_ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(_ROOT / "src"))
from rune.construction_interface import ConstructionInterface
from rune.capability_ledger import CapabilityLedger
from rune import spine
@pytest.fixture
def governed_store(tmp_path):
"""A store shaped like the Rune store: scaffold + rune_model ledgers."""
scaffold_dir = tmp_path / "store" / "scaffold"
scaffold_dir.mkdir(parents=True)
model_dir = tmp_path / "store" / "rune_model"
model_dir.mkdir(parents=True)
ledger = CapabilityLedger(model_dir)
ledger.define(capability_id="closure-target",
name="Closure target",
purpose="capability whose source file is in contract scope",
component="component:x", owning_project="t",
source_locations=["scripts/rollback_generation.py"])
ledger.save()
return tmp_path, scaffold_dir, model_dir, ledger
def test_close_auto_material_change(governed_store):
tmp_path, scaffold_dir, model_dir, ledger = governed_store
from rune import scaffold as sc
from rune import structural_store as ss
scaffold = sc.Scaffold(scaffold_dir, "t21.4-test")
doc = scaffold.init(purpose="t21.4 closure test")
ss.add_entity(doc, id="component:closure-target", kind="component",
identities={"source": "scripts/rollback_generation.py"},
maturity="EXERCISED")
ss.add_relationship(doc, type="provides", from_id="component:closure-target",
to_id="contract:closure-target")
scaffold.commit(doc, what="seed")
ci = ConstructionInterface("t21.4-test", project_store=str(scaffold_dir))
r = ci.submit_need(
objective="bounded change touching closure_target",
origin="operator",
affected_surface=["scripts/rollback_generation.py"],
constraints={"environment": {"existing_language": "python"},
"contracts": ["contract:closure-target"]},
evidence_refs=["tests/test_t214_material_closure.py"])
assert r["success"], r.get("errors")
cid = r["ids"]["contract_id"]
for phase in ("approved", "building", "verifying"):
t = ci.transition(cid, phase, evidence_ref=f"t21.4 test {phase}")
assert t["success"], t.get("errors")
c = ci.close(cid, observed_evidence=[{"kind": "test", "detail": "x"}],
scope_files=["scripts/rollback_generation.py"])
assert c["success"], c.get("errors")
closure = c["state"]["material_change_closure"]
assert closure["changed"] == ["capability:closure-target"]
assert closure["unmodeled"] == []
# revision incremented and durable
rec = CapabilityLedger(model_dir).get("closure-target")
assert rec["material_revision"] == 1
assert any(h["event"] == "material_change"
for h in json.loads(
(model_dir / "capability_ledger.json").read_text()
)["history"])
def test_close_unmodeled_files_reported(governed_store):
tmp_path, scaffold_dir, _, _ = governed_store
from rune import scaffold as sc
from rune import structural_store as ss
scaffold = sc.Scaffold(scaffold_dir, "t21.4-test")
doc = scaffold.init(purpose="t21.4 closure test")
ss.add_entity(doc, id="component:closure-target", kind="component",
identities={"source": "scripts/rollback_generation.py"},
maturity="EXERCISED")
ss.add_relationship(doc, type="provides", from_id="component:closure-target",
to_id="contract:closure-target")
scaffold.commit(doc, what="seed")
ci = ConstructionInterface("t21.4-test", project_store=str(scaffold_dir))
r = ci.submit_need(objective="scope with an unmodeled file",
origin="operator",
affected_surface=["scripts/rollback_generation.py",
"tests/test_t214_material_closure.py"],
constraints={"environment": {"existing_language": "python"},
"contracts": ["contract:closure-target"]},
evidence_refs=["tests/test_t214_material_closure.py"])
cid = r["ids"]["contract_id"]
for phase in ("approved", "building", "verifying"):
ci.transition(cid, phase, evidence_ref="t")
c = ci.close(cid, observed_evidence=[{"kind": "test", "detail": "x"}],
scope_files=["tests/test_t214_material_closure.py"])
closure = c["state"]["material_change_closure"]
assert closure["changed"] == []
assert closure["unmodeled"] == ["tests/test_t214_material_closure.py"]
def test_closure_error_does_not_break_close(governed_store, monkeypatch):
"""OSError in the ledger write degrades to a note — close() succeeds."""
tmp_path, scaffold_dir, model_dir, ledger = governed_store
from rune import scaffold as sc
from rune import structural_store as ss
scaffold = sc.Scaffold(scaffold_dir, "t21.4-test")
doc = scaffold.init(purpose="t21.4 closure test")
ss.add_entity(doc, id="component:closure-target", kind="component",
identities={"source": "scripts/rollback_generation.py"},
maturity="EXERCISED")
ss.add_relationship(doc, type="provides", from_id="component:closure-target",
to_id="contract:closure-target")
scaffold.commit(doc, what="seed")
monkeypatch.setattr(
"rune.capability_ledger.CapabilityLedger.save",
lambda self: (_ for _ in ()).throw(OSError("disk full")),
)
ci = ConstructionInterface("t21.4-test", project_store=str(scaffold_dir))
r = ci.submit_need(
objective="bounded change touching closure_target",
origin="operator",
affected_surface=["scripts/rollback_generation.py"],
constraints={"environment": {"existing_language": "python"},
"contracts": ["contract:closure-target"]},
evidence_refs=["tests/test_t214_material_closure.py"])
cid = r["ids"]["contract_id"]
for phase in ("approved", "building", "verifying"):
ci.transition(cid, phase, evidence_ref="t")
c = ci.close(cid, observed_evidence=[{"kind": "test", "detail": "x"}],
scope_files=["scripts/rollback_generation.py"])
assert c["success"], c.get("errors")
closure = c["state"]["material_change_closure"]
assert "closure error" in closure.get("note", "")
assert closure["changed"] == ["capability:closure-target"]
def test_spine_seeds_entry_point(tmp_path, monkeypatch):
"""__main__ is invisible to the import graph but is a real runtime
path — build() must seed it deterministically."""
src_dir = tmp_path / "src" / "rune"
src_dir.mkdir(parents=True)
(src_dir / "__main__.py").write_text("# entry point\n")
(src_dir / "seeded_mod.py").write_text("x = 1\n")
empty_store = tmp_path / "spine.json"
monkeypatch.setattr(spine, "SRC_DIR", src_dir)
monkeypatch.setattr(spine, "load", lambda path=None: None)
monkeypatch.setattr(spine, "save",
lambda doc, path=None: empty_store)
monkeypatch.setattr(spine, "_canary_registry", lambda: [])
monkeypatch.setattr(spine, "ingest_umbrellas", lambda doc, mp=None: 0)
monkeypatch.setattr(spine, "_test_backed_modules", lambda: set())
monkeypatch.setattr(spine, "DEPLOYED_LINK", tmp_path / "no-deploy")
doc = spine.build()
entry = spine.entity(doc, "module:__main__")
assert entry is not None
assert entry["identities"]["path"] == "src/rune/__main__.py"