Files
rune/tests/test_surface_completeness.py
T
Nick 535c290917 feat(scaffolding): affected-surface propagation + requirement completeness gate + impact-aware contract generation (SCAFFOLD-TRANCHE-005)
Origin: maintenance:contract-gen-affected-surface (opened by
DEPLOY-MONOTONIC-001's honest closure — planned work appeared as
genuinely_unexpected because generate_contract dropped the Need's
affected surface).

- construction_decision.py:
  * classify_surface/resolve_surface/resolve_affected_surface:
    open-world surface classification (structural entity / source path /
    workflow config / ui component / layout region / unresolved — never
    dropped) with deterministic resolution against authorities via
    identity, recorded path attributes, and Layout Contract bindings
  * check_completeness: bounded structure-grounded gate BEFORE fit —
    affected-surface entities contribute their IMPLEMENTED contracts to
    the material scope; unrepresented ones = MATERIAL_GAP with full
    provenance (derived_from/rule/evidence) + concrete probe questions.
    Verdicts COMPLETE_ENOUGH/MATERIAL_GAP/UNKNOWN; UNKNOWN never
    silently COMPLETE. PREMATURE-FIT GUARD: a concrete verdict
    (REUSE/SATISFIED/COMPOSE/ADAPT_EXISTING) on a materially incomplete
    requirement is downgraded to INSUFFICIENT_EVIDENCE with the
    would-have-been verdict retained — the fit engine was right GIVEN
    its requirement; the requirement was too narrow
  * record_proposed_requirement: model-proposed requirements are
    PENDING evidence, never structural authority by themselves
  * iterative decision trace: prior_iterations retained with why-it-
    changed provenance; final resolution stays singular
  * test-surface derivation: tests/test_<name>.py in an affected
    surface derives the test:<name> expected-new entity (Rune path
    convention; structural consequence, provenance recorded)
  * impact-aware generate_contract: distinct affected_paths /
    affected_ui_surfaces / affected_regions / workflow_config_surfaces /
    unresolved_surfaces / expected_runtime_effects surfaces; file scope
    GENERATED from affected paths + selected impl paths + expected-new
    paths, caller additions provenance-tracked; file scope != structural
    impact != expected new != runtime effect
- scaffold.py: closure classifies expected RUNTIME effects (an observed
  entity matching a predicted effect phrase = expected, not unexpected;
  prediction still cannot self-certify — independent evidence required);
  legacy contracts unaffected (empty lists, no crash)

REAL PROOFS: §23 read-only replay of the deploy incident — ONE pass
identifies MATERIAL_GAP (contract:monotonic_promotion on
module:deploy_stage), blocks the premature REUSE automatically, emits a
concrete probe question; §22 live proof CONTRACT-GEN-REPAIR-001
generated through the improved path with all surfaces propagated,
regeneration proving the planned test surface is now expected-new.

Tests: 28 new (surface/completeness/trace/runtime/compat plans §24-§28);
suite 1525 green; henxels rc=0; pyright clean.
2026-09-13 13:36:50 +01:00

465 lines
21 KiB
Python

"""test_surface_completeness.py — SCAFFOLD-TRANCHE-005 test plans
§24 (affected surfaces), §25 (requirement completeness), §26 (iterative
trace), §27 (expected runtime effects), §28 (backward compat)."""
import json
import os
import subprocess
import sys
import pytest
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "src"))
from rune import construction_decision as cd # noqa: E402
from rune import layout_contract as lc # noqa: E402
from rune import scaffold as sc # noqa: E402
from rune import structural_store as ss # noqa: E402
def _authorities():
"""Spine-like authority: deploy module with a promotion contract the
requirement does NOT represent (the deploy-incident shape), plus a
path-mapped module, plus a UI component with a Layout Contract."""
spine = ss.new_doc()
ss.add_entity(
spine,
id="module:deploy_stage",
kind="module",
identities={"path": "scripts/deploy_stage.py"},
maturity="WIRED",
evidence_refs=["f"],
)
ss.add_entity(spine, id="contract:monotonic_promotion", kind="contract", identities={}, evidence_refs=["f"])
ss.add_relationship(
spine,
type="implements",
from_id="module:deploy_stage",
to_id="contract:monotonic_promotion",
evidence_refs=["f"],
)
ss.add_entity(spine, id="contract:fail_closed_deployment", kind="contract", identities={}, evidence_refs=["f"])
ss.add_entity(
spine,
id="module:deploy_qualify",
kind="module",
identities={"path": "scripts/deploy_qualify.py"},
maturity="WIRED",
evidence_refs=["f"],
)
ss.add_relationship(
spine,
type="implements",
from_id="module:deploy_qualify",
to_id="contract:fail_closed_deployment",
evidence_refs=["f"],
)
scaffold = ss.new_doc()
lc.define_region(scaffold, "footer-bar", provenance="scaffold")
lc.bind_component(
scaffold, "version_display", "footer-bar", provenance="scaffold", order=0, implementation="src/rune/rune_tui.py"
)
return {"rune_spine": spine, "project_scaffold": scaffold}
def _need(**kw):
d = dict(
need_id="n5",
objective="prevent deploy regression",
origin="user",
constraints={"contracts": ["contract:fail_closed_deployment"]},
affected_surface=["scripts/deploy_qualify.py"],
evidence_refs=["x"],
approval_context="covered",
)
d.update(kw)
return cd.Need(**d)
# ── §24 affected surfaces ───────────────────────────────────────────────────
def test_24a_source_path_propagates_to_file_scope(tmp_path):
s, doc = _gen(tmp_path)
c = s.contract(doc, "G5")
assert "scripts/deploy_qualify.py" in c["ext"]["affected_paths"]
assert "scripts/deploy_qualify.py" in c["identities"]["scope"]
def test_24b_mapped_path_resolves_to_module():
res = cd.resolve_affected_surface(_need(), _authorities())
assert "module:deploy_qualify" in res["affected_existing_entities"]
r = next(r for r in res["resolutions"] if r["surface"] == "scripts/deploy_qualify.py")
assert r["resolved"][0]["via"] == "path_attribute"
def test_24c_unresolved_path_retained_not_dropped():
need = _need(affected_surface=["scripts/brand_new_thing.py"])
res = cd.resolve_affected_surface(need, _authorities())
assert any(u["surface"] == "scripts/brand_new_thing.py" for u in res["unresolved_surfaces"])
assert "scripts/brand_new_thing.py" in res["affected_paths"] # still scoped
def test_24d_ui_component_pulls_layout_contract():
need = _need(affected_surface=["ui:version_display"])
res = cd.resolve_affected_surface(need, _authorities())
assert "ui:version_display" in res["affected_ui_surfaces"]
r = res["resolutions"][0]
assert r["resolved"][0]["via"] == "layout_contract"
trace = cd.make_decision(need, authorities=_authorities())
assert any(o["component"] == "ui:version_display" for o in trace["layout_obligations"])
def test_24e_region_surface_pulls_layout_obligations():
need = _need(affected_surface=["region:footer-bar"])
trace = cd.make_decision(need, authorities=_authorities())
assert any(o["region"] == "footer-bar" for o in trace["layout_obligations"])
def test_24f_workflow_config_mechanically_scoped(tmp_path):
s, doc = _gen(tmp_path, affected_surface=["scripts/deploy_qualify.py", ".gitea/workflows/deploy.yml"])
c = s.contract(doc, "G5")
assert ".gitea/workflows/deploy.yml" in c["ext"]["workflow_config_surfaces"]
assert ".gitea/workflows/deploy.yml" in c["identities"]["scope"]
def test_24g_affected_surface_not_auto_expected_new(tmp_path):
s, doc = _gen(tmp_path)
c = s.contract(doc, "G5")
# affected existing entities never leak into expected-new
assert c["identities"]["expected_new_entities"] == []
assert c["identities"]["affected_entities"] # but affected is populated
def test_24h_expected_new_path_entity_distinct(tmp_path):
s = sc.Scaffold(tmp_path / ".rune" / "scaffold", "p5")
doc = s.init(purpose="t")
need = _need(
constraints={
"contracts": ["contract:fail_closed_deployment"],
"expected_new_entities": ["module:fresh_thing"],
"expected_new_source_paths": ["scripts/fresh_thing.py"],
},
affected_surface=["scripts/deploy_qualify.py"],
evidence_refs=["x"],
)
trace = cd.make_decision(need, authorities=_authorities())
cd.generate_contract(s, doc, trace, contract_id="G5H", scope=None)
c = s.contract(doc, "G5H")
assert c["identities"]["expected_new_entities"] == ["module:fresh_thing"]
assert "scripts/fresh_thing.py" in c["identities"]["scope"] # expected-new path scoped
assert "scripts/fresh_thing.py" not in c["ext"]["affected_paths"]
def test_24i_contract_roundtrip_preserves_surfaces(tmp_path):
s, doc = _gen(tmp_path, affected_surface=["scripts/deploy_qualify.py", "ui:version_display", "scripts/unmapped.py"])
s.save(doc)
doc2 = sc.Scaffold(tmp_path / ".rune" / "scaffold", "p5").load()
c = doc2 and sc.Scaffold(tmp_path / ".rune" / "scaffold", "p5").contract(doc2, "G5")
assert c["ext"]["affected_paths"] == ["scripts/deploy_qualify.py", "scripts/unmapped.py"]
assert c["ext"]["affected_ui_surfaces"] == ["ui:version_display"]
assert any(u["surface"] == "scripts/unmapped.py" for u in c["ext"]["unresolved_surfaces"])
# ── §25 requirement completeness ────────────────────────────────────────────
def _gen(tmp_path, **need_kw):
s = sc.Scaffold(tmp_path / ".rune" / "scaffold", "p5")
doc = s.init(purpose="t5")
need = _need(**need_kw)
trace = cd.make_decision(need, authorities=_authorities(), project_toolchain={"existing_language": "python"})
cd.generate_contract(s, doc, trace, contract_id="G5", scope=None)
return s, doc
def test_25a_complete_requirement_complete_enough():
need = _need(
constraints={"contracts": ["contract:fail_closed_deployment"]}, affected_surface=["scripts/deploy_qualify.py"]
)
trace = cd.make_decision(need, authorities=_authorities())
assert trace["completeness"]["verdict"] == cd.COMPLETE_ENOUGH
assert trace["resolution"] == "REUSE" # fit proceeds
def test_25b_affected_stage_missing_material_gap():
"""THE deploy-incident shape: requirement names qualification only, but
the affected surface includes the promotion implementation carrying an
unrepresented ordering contract."""
need = _need(affected_surface=["scripts/deploy_qualify.py", "scripts/deploy_stage.py"])
trace = cd.make_decision(need, authorities=_authorities())
assert trace["completeness"]["verdict"] == cd.MATERIAL_GAP
gaps = {g["missing_contract"] for g in trace["completeness"]["gaps"]}
assert "contract:monotonic_promotion" in gaps
# PREMATURE-FIT GUARD: without the guard this resolves REUSE/SATISFIED
assert trace["resolution"] == "INSUFFICIENT_EVIDENCE"
assert trace["_merged"].get("premature_fit_blocked", {}).get("gaps")
# gap provenance: derived_from the entity, with rule + evidence
g = next(g for g in trace["completeness"]["gaps"] if g["missing_contract"] == "contract:monotonic_promotion")
assert g["derived_from"] == "module:deploy_stage"
assert g["rule"] and g["evidence"]
def test_25c_no_resolution_unknown():
need = _need(affected_surface=["totally/unknown.xyz"])
trace = cd.make_decision(need, authorities=_authorities())
assert trace["completeness"]["verdict"] == cd.COMPLETENESS_UNKNOWN
def test_25d_unknown_never_becomes_complete():
need = _need(affected_surface=["totally/unknown.xyz"])
trace = cd.make_decision(need, authorities=_authorities())
assert trace["completeness"]["verdict"] != cd.COMPLETE_ENOUGH
assert "undeterminable" in trace["completeness"]["note"]
def test_25e_derived_requirement_provenance():
need = _need(affected_surface=["scripts/deploy_qualify.py", "scripts/deploy_stage.py"])
trace = cd.make_decision(need, authorities=_authorities())
g = trace["completeness"]["gaps"][0]
assert g["derived_from"] and g["evidence"] and g["rule"]
def test_25f_model_proposal_not_structural_authority():
"""A model-proposed contract is recorded with provider/confidence and
verification state PENDING — it does not enter the requirement's
contract set as authority."""
proposed = cd.record_proposed_requirement(
"contract:model_claimed", provider="local-model", model="m", evidence=["hunch"], confidence=0.4
)
assert proposed["verification_state"] == "PENDING"
assert proposed["provider"] == "local-model"
# normalization does not accept it as a contract
need = _need(constraints={"contracts": ["contract:fail_closed_deployment"], "model_proposed": [proposed]})
req, _ = cd.normalize_need(need)
assert "contract:model_claimed" not in req.contracts
def test_25g_consumer_contracts_derive_regression():
spine = _authorities()["rune_spine"]
ss.add_entity(spine, id="module:consumer", kind="module", identities={}, evidence_refs=["f"])
ss.add_relationship(
spine, type="consumes", from_id="module:consumer", to_id="module:deploy_qualify", evidence_refs=["f"]
)
need = _need(affected_surface=["scripts/deploy_qualify.py"])
trace = cd.make_decision(need, authorities={"rune_spine": spine})
assert trace["resolution"] == "REUSE"
reg = " ".join(trace["obligations"]["regression"])
assert "module:consumer" in reg
def test_25h_adapt_existing_includes_old_plus_new():
spine = _authorities()["rune_spine"]
ss.add_entity(spine, id="contract:extra_obligation", kind="contract", identities={}, evidence_refs=["f"])
ss.add_relationship(
spine,
type="implements",
from_id="module:deploy_qualify",
to_id="contract:extra_obligation",
evidence_refs=["f"],
)
need = _need(affected_surface=["scripts/deploy_qualify.py"])
need.constraints["contracts"] = ["contract:fail_closed_deployment", "contract:extra_obligation"]
trace = cd.make_decision(need, authorities={"rune_spine": spine})
assert trace["completeness"]["verdict"] == cd.COMPLETE_ENOUGH
assert trace["resolution"] == "REUSE" # exact fit on both contracts
assert "new behavior proven by tests" in " ".join(trace["obligations"]["verification"])
def test_25i_compose_derives_boundary_verification():
spine = _authorities()["rune_spine"]
need = _need(affected_surface=["scripts/deploy_qualify.py", "scripts/deploy_stage.py"])
need.constraints["contracts"] = ["contract:fail_closed_deployment", "contract:monotonic_promotion"]
trace = cd.make_decision(need, authorities={"rune_spine": spine})
assert trace["resolution"] == "COMPOSE"
assert "composition boundary behavior verified end-to-end" in " ".join(trace["obligations"]["verification"])
comp = trace["obligations"]["composition"][0]
assert set(comp["components"]) == {"module:deploy_qualify", "module:deploy_stage"}
def test_25j_layout_lock_derives_preservation():
scaffold = _authorities()["project_scaffold"]
lc.bind_component(
scaffold, "status_bar", "footer-bar", lock=True, provenance="user", order=1, evidence_ref="user-2026"
)
need = _need(affected_surface=["ui:status_bar"])
trace = cd.make_decision(need, authorities=_authorities() | {"project_scaffold": scaffold})
lo = [o for o in trace["layout_obligations"] if o["component"] == "ui:status_bar"]
assert lo and lo[0]["lock"] is True
assert "explicit user authorization" in lo[0]["obligation"]
# and the approval gate fires for the locked surface
assert any("USER-LOCKED" in r for r in trace["approval"]["reasons"]) or trace["approval"]["covered_by"]
def test_25k_completeness_prevents_premature_reuse():
"""§23 core test, fixture form: the deploy-like Need must NOT resolve
REUSE while the affected promotion surface carries an unrepresented
contract."""
need = _need(affected_surface=["scripts/deploy_qualify.py", "scripts/deploy_stage.py"])
trace = cd.make_decision(need, authorities=_authorities())
assert trace["resolution"] != "REUSE"
assert trace["completeness"]["verdict"] == cd.MATERIAL_GAP
blocked = trace["_merged"]["premature_fit_blocked"]
assert blocked["would_have_been"] in ("REUSE", "SATISFIED")
# ── §26 iterative trace ──────────────────────────────────────────────────────
def test_26_iterative_trace_durable(tmp_path):
"""initial normalization → gap → refinement → revised → final decision;
all retained; fresh process reconstructs without transcript."""
s = sc.Scaffold(tmp_path / ".rune" / "scaffold", "p26")
doc = s.init(purpose="t26")
# iteration 1: narrow requirement → premature fit blocked
need1 = _need(affected_surface=["scripts/deploy_qualify.py", "scripts/deploy_stage.py"])
t1 = cd.make_decision(need1, authorities=_authorities())
assert t1["resolution"] == "INSUFFICIENT_EVIDENCE"
assert t1["_merged"]["premature_fit_blocked"]["would_have_been"] == "REUSE"
# evidence/refinement: the promotion contract is added to the requirement
need2 = _need(
constraints={"contracts": ["contract:fail_closed_deployment", "contract:monotonic_promotion"]},
affected_surface=["scripts/deploy_qualify.py", "scripts/deploy_stage.py"],
evidence_refs=["x", "refinement:completeness-gap-iter1"],
)
t2 = cd.make_decision(
need2,
authorities=_authorities(),
prior_iterations=[
{
"iteration": 1,
"normalized_contracts": need1.constraints["contracts"],
"completeness": t1["completeness"]["verdict"],
"gaps": [g["missing_contract"] for g in t1["completeness"]["gaps"]],
"resolution": t1["resolution"],
"refined_by": "completeness gap evidence",
}
],
)
assert t2["resolution"] == "COMPOSE"
assert t2["iterations"][0]["gaps"] == ["contract:monotonic_promotion"]
cd.generate_contract(s, doc, t2, contract_id="IT1", scope=None)
s.transition_contract(doc, "IT1", "approved")
s.save(doc)
# fresh process
script = (
"import sys, json;"
"sys.path.insert(0, %r);"
"from rune import scaffold;"
"s = scaffold.Scaffold(%r, 'p26');"
"doc = s.load();"
"c = s.contract(doc, 'IT1');"
"t = c['ext']['decision_trace'];"
"print(json.dumps({'iterations': len(t['iterations']),"
"'iter1_gaps': t['iterations'][0]['gaps'],"
"'iter1_resolution': t['iterations'][0]['resolution'],"
"'refined_by': t['iterations'][0]['refined_by'],"
"'final_resolution': t['resolution'],"
"'final_contracts': t['requirement']['contracts']}))"
) % (os.path.join(os.path.dirname(__file__), "..", "src"), str(tmp_path / ".rune" / "scaffold"))
r = subprocess.run([sys.executable, "-c", script], capture_output=True, text=True, timeout=60)
assert r.returncode == 0, r.stderr
rec = json.loads(r.stdout)
assert rec["iterations"] == 1
assert rec["iter1_gaps"] == ["contract:monotonic_promotion"]
assert rec["iter1_resolution"] == "INSUFFICIENT_EVIDENCE"
assert rec["refined_by"] == "completeness gap evidence"
assert rec["final_resolution"] == "COMPOSE"
assert "contract:monotonic_promotion" in rec["final_contracts"]
# ── §27 expected runtime effects ─────────────────────────────────────────────
def _close_with(tmp_path, expected_runtime, observed):
s = sc.Scaffold(tmp_path / ".rune" / "scaffold", "p27")
doc = s.init(purpose="t27")
s.new_contract(
doc, contract_id="R1", need="n", scope=["src/x"], affected_entities=["module:a"], expected_new_entities=[]
)
c = s.contract(doc, "R1")
c.setdefault("ext", {})["expected_runtime_effects"] = expected_runtime
s.transition_contract(doc, "R1", "approved")
s.transition_contract(doc, "R1", "building")
s.transition_contract(doc, "R1", "verifying")
s.close_contract(doc, "R1", observed_evidence=observed)
return s.contract(doc, "R1")["ext"]["contract_quality"]
def test_27a_expected_runtime_observed_not_unexpected(tmp_path):
q = _close_with(
tmp_path, ["deployment:current advances"], [{"entity": "deployment:current", "source": "deployed-identity"}]
)
assert q["expected_runtime_observed"] == ["deployment:current advances"]
assert "deployment:current" not in q["genuinely_unexpected_new"]
def test_27b_expected_runtime_missing(tmp_path):
q = _close_with(tmp_path, ["cache invalidated"], [])
assert q["expected_runtime_missing"] == ["cache invalidated"]
assert q["expected_runtime_observed"] == []
def test_27c_unpredicted_runtime_genuinely_unexpected(tmp_path):
q = _close_with(tmp_path, [], [{"entity": "deployment:current", "source": "deployed-identity"}])
assert "deployment:current" in q["genuinely_unexpected_new"]
def test_27d_no_self_certification(tmp_path):
"""Predicting the runtime effect does not observe it: no evidence item →
missing, and correctly_predicted unaffected."""
q = _close_with(tmp_path, ["deployment:current advances"], [])
assert q["expected_runtime_missing"] == ["deployment:current advances"]
assert q["expected_runtime_observed"] == []
def test_27e_deployment_current_fixture(tmp_path):
"""The DEPLOY-MONOTONIC-001 defect shape, now correct: a release-class
contract that predicts the deployment advance closes clean."""
q = _close_with(
tmp_path,
["deployment:current advances"],
[{"entity": "module:a", "source": "git-diff"}, {"entity": "deployment:current", "source": "deployed-identity"}],
)
assert q["correctly_predicted"] == ["module:a"]
assert q["expected_runtime_observed"] == ["deployment:current advances"]
assert q["genuinely_unexpected_new"] == []
# ── §28 backward compatibility ───────────────────────────────────────────────
def test_28_old_contract_closes_without_new_fields(tmp_path):
s = sc.Scaffold(tmp_path / ".rune" / "scaffold", "p28")
doc = s.init(purpose="t28")
# old-shape: no expected_runtime_effects, no expected_new_entities
s.new_contract(doc, contract_id="OLD", need="old", scope=["src/x"], affected_entities=["module:a"])
c = s.contract(doc, "OLD")
assert not c.get("ext", {}).get("expected_runtime_effects") # empty, not crash
s.transition_contract(doc, "OLD", "approved")
s.transition_contract(doc, "OLD", "building")
s.transition_contract(doc, "OLD", "verifying")
s.close_contract(doc, "OLD", observed_evidence=[{"entity": "module:a", "source": "git-diff"}])
q = s.contract(doc, "OLD")["ext"]["contract_quality"]
assert q["correctly_predicted"] == ["module:a"]
assert q["expected_runtime_effects"] == [] # empty, not crash
def test_28_tranche4_contract_data_still_readable(tmp_path):
s = sc.Scaffold(tmp_path / ".rune" / "scaffold", "p28b")
doc = s.init(purpose="t28b")
s.new_contract(
doc,
contract_id="T4SHAPE",
need="n",
scope=["src/x"],
affected_entities=["module:a"],
expected_new_entities=["module:b"],
)
s.transition_contract(doc, "T4SHAPE", "approved")
s.transition_contract(doc, "T4SHAPE", "building")
s.transition_contract(doc, "T4SHAPE", "verifying")
s.close_contract(doc, "T4SHAPE", observed_evidence=[{"entity": "module:b", "source": "file-added"}])
q = s.contract(doc, "T4SHAPE")["ext"]["contract_quality"]
assert q["expected_new_observed"] == ["module:b"]