- build_basis.py: durable versioned research basis — depth decision derived from adaptive_router routes (no second router; deviations recorded with justification), OUTWARD prior-art contract, legal dispositions (ADOPT/ADAPT/COMBINE/REJECT/INNOVATE), qualification gate, attachable evidence_ref, structured summary (no private reasoning). - 18D CORRECTION (Nick): THE RESEARCH PLAN ITSELF IS CONTEXTUALLY GENERATED — generate_research_plan synthesizes topics from THIS intent's material unknowns/decisions/risks; NO permanent topic list. Held-outs prove unrelated intents produce materially different plans; the structural-model topics never leak into unrelated research. Principle persisted in SCAFFOLDING_V1_DEFINITION.md. - 18D qualified Build Basis persisted from REAL prior-art research (Backstage/Kythe/SCIP/Nx/Bazel/C4/Glean surveyed; adopt/adapt/ combine/reject/innovate decisions recorded with refs). - structural_model.py: first-class project structural Scaffold over the EXISTING structural_store core (no second graph store, no competing Spine). Backstage relation vocabulary + Kythe edge kinds adopted; stable deterministic IDs (Kythe-adapted: stability PROMISED where Kythe declines); every edge carries evidence_refs+authority+ status (the genuinely-absent innovation); provides-ripple in impact closure (Nx-affected semantics); dynamic consumers stay explicit uncertainty. 7 held-outs. Suite 1938 green; ratchets PASS; Henxels rc=0.
139 lines
6.2 KiB
Python
139 lines
6.2 KiB
Python
"""test_structural_model.py — T18 §18E/§18F: structural Scaffold
|
|
held-outs. Modeled on Scaffolding ITSELF (the 18O self-scaffold shape)."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
REPO = Path(__file__).resolve().parent.parent
|
|
sys.path.insert(0, str(REPO / "src"))
|
|
|
|
from rune import structural_model as sm # noqa: E402
|
|
|
|
|
|
@pytest.fixture()
|
|
def model(tmp_path):
|
|
m = sm.StructuralModel(tmp_path / "store", "scaffolding")
|
|
# shape: project → domain → subsystem → component (Scaffolding itself)
|
|
m.add("domain", "construction", parent="project:scaffolding")
|
|
m.add("subsystem", "kernel", parent="domain:construction")
|
|
m.add("component", "scaffold", parent="subsystem:kernel")
|
|
m.add("component", "construction_interface", parent="subsystem:kernel")
|
|
m.add("component", "construction_mode", parent="subsystem:kernel")
|
|
m.add("component", "programme_continuation", parent="subsystem:kernel")
|
|
m.add("component", "event_wait", parent="subsystem:kernel")
|
|
m.add("interface", "construction-api", parent="subsystem:kernel")
|
|
m.add("state_store", "scaffold-store", parent="project:scaffolding")
|
|
m.add("test_suite", "kernel-tests", parent="project:scaffolding")
|
|
m.add("deployment_surface", "rune-code-current",
|
|
parent="project:scaffolding")
|
|
m.add("environment", "production", parent="project:scaffolding")
|
|
# relationships (Backstage + Kythe vocabulary)
|
|
m.relate("component:construction_interface", "provides",
|
|
"interface:construction-api")
|
|
m.relate("component:scaffold", "consumes", "interface:construction-api")
|
|
m.relate("component:programme_continuation", "consumes",
|
|
"interface:construction-api")
|
|
m.relate("component:scaffold", "depends_on",
|
|
"component:construction_mode")
|
|
m.relate("component:scaffold", "owns", "state_store:scaffold-store")
|
|
m.relate("component:construction_interface", "reads",
|
|
"state_store:scaffold-store")
|
|
m.relate("component:construction_interface", "writes",
|
|
"state_store:scaffold-store")
|
|
m.relate("component:event_wait", "calls", "component:event_wait") if False else None
|
|
m.relate("component:scaffold", "verified_by", "test_suite:kernel-tests")
|
|
m.relate("component:programme_continuation", "verified_by",
|
|
"test_suite:kernel-tests")
|
|
m.relate("deployment_surface:rune-code-current", "connects_to",
|
|
"environment:production")
|
|
m.relate("component:scaffold", "deployed_by",
|
|
"deployment_surface:rune-code-current")
|
|
m.save()
|
|
return m
|
|
|
|
|
|
def test_18e_stable_deterministic_ids(model):
|
|
assert model.get("component:scaffold") is not None
|
|
# same name → same id, no duplicates (Backstage entity-ref discipline)
|
|
model.add("component", "scaffold", evidence_ref="re-add")
|
|
ids = [e["id"] for e in model.doc["entities"]
|
|
if e["id"] == "component:scaffold"]
|
|
assert len(ids) == 1
|
|
|
|
|
|
def test_18e_every_edge_carries_authority_status_evidence(model):
|
|
for r in model.doc["relationships"]:
|
|
assert r.get("evidence_refs"), r
|
|
assert r.get("authority") in ("model-author", "importer", "user"), r
|
|
assert "status" in r, r
|
|
|
|
|
|
def test_18e_illegal_kind_and_rel_refused(model):
|
|
with pytest.raises(ValueError):
|
|
model.add("vibe", "x")
|
|
with pytest.raises(ValueError):
|
|
model.relate("component:scaffold", "vibes_with", "domain:construction")
|
|
|
|
|
|
def test_18f_queries(model):
|
|
assert model.children("domain:construction") == ["subsystem:kernel"]
|
|
beneath = model.beneath("domain:construction")
|
|
assert "component:scaffold" in beneath and "component:event_wait" in beneath
|
|
assert model.owning_domain("component:scaffold") == "domain:construction"
|
|
assert model.dependencies("component:scaffold") == [
|
|
"component:construction_mode"]
|
|
assert set(model.consumers("interface:construction-api")) == {
|
|
"component:scaffold", "component:programme_continuation"}
|
|
assert model.state_owned("component:scaffold") == [
|
|
"state_store:scaffold-store"]
|
|
assert model.canonical_state_owner("component:scaffold") == \
|
|
"state_store:scaffold-store"
|
|
assert model.canonical_state_owner("state_store:scaffold-store") == \
|
|
"state_store:scaffold-store"
|
|
sup = model.supersession("component:scaffold")
|
|
assert sup == {"supersedes": [], "superseded_by": [], "legacy_of": []}
|
|
|
|
|
|
def test_18f_impact_closure_shape(model):
|
|
c = model.impact_closure("component:scaffold")
|
|
assert c["target"] == "component:scaffold"
|
|
assert c["owning_domain"] == "domain:construction"
|
|
assert "component:scaffold" in c["mutation_scope"]
|
|
assert "component:construction_mode" in c["observation_scope"][
|
|
"direct_dependencies"]
|
|
assert "interface:construction-api" in c["observation_scope"][
|
|
"interfaces_consumed"]
|
|
# sibling consumers of a shared interface are NOT in a component's
|
|
# closure (changing scaffold doesn't change the interface) — the
|
|
# ripple happens at the INTERFACE level (transitive test below)
|
|
assert "component:programme_continuation" not in c["mutation_scope"]
|
|
assert "test_suite:kernel-tests" in c["verification_scope"]
|
|
assert "deployment_surface:rune-code-current" in c["deployment_scope"]
|
|
assert c["invariants"] and isinstance(c["uncertainty"], list)
|
|
# dynamic-consumer uncertainty is EXPLICIT, never silent (rule 18F)
|
|
assert any(u["kind"] == "dynamic-consumer" for u in c["uncertainty"])
|
|
|
|
|
|
def test_18f_impact_closure_transitive_and_bounded(model):
|
|
# chain: pc consumes iface; scaffold consumes iface → both in scope
|
|
c = model.impact_closure("interface:construction-api")
|
|
assert "component:scaffold" in c["mutation_scope"]
|
|
assert "component:programme_continuation" in c["mutation_scope"]
|
|
assert c["closure_depth"] <= 8
|
|
assert model.impact_closure("component:ghost").get("error")
|
|
|
|
|
|
def test_18e_persistence_round_trip(tmp_path):
|
|
m = sm.StructuralModel(tmp_path / "store", "scaffolding")
|
|
m.add("domain", "d1", parent="project:scaffolding")
|
|
m.save()
|
|
m2 = sm.StructuralModel(tmp_path / "store", "scaffolding")
|
|
assert m2.get("domain:d1") is not None
|
|
assert m2.get("project:scaffolding") is not None
|
|
# schema version stamped
|
|
assert m2.doc.get("schema_version") == sm.SCHEMA_VERSION
|