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
169 lines
7.8 KiB
Python
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"
|