The snapshot and manifest schemas, both example dataspaces, and the shared text renderer now pass the fixture validator and the negative contract tests. JSON, NDJSON, and Markdown frontmatter publish a snapshot only when rights, identity, and references allow it. CONTENT-WP-0001 and CONTENT-WP-0003 are marked finished against those artifacts. CONTENT-WP-0000-T04 stays open. SCOPE.md and the 19:05 scope-against-intent note record the stop. Assistant: grok Assistant-Session: 01a130f1-abfb-7d71-9cd6-a8b75ec7f95a
147 lines
5.9 KiB
Python
147 lines
5.9 KiB
Python
"""Contract checks against the shipped validator, not a copy of it."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import copy
|
|
import json
|
|
import unittest
|
|
from pathlib import Path
|
|
|
|
ROOT = Path(__file__).resolve().parent.parent
|
|
import sys
|
|
|
|
sys.path.insert(0, str(ROOT))
|
|
|
|
from content_explorer.contract import ( # noqa: E402
|
|
resolve_claims,
|
|
validate_dataspace,
|
|
validate_documents,
|
|
)
|
|
from content_explorer.render import render_explorer # noqa: E402
|
|
|
|
EXAMPLES = ROOT / "examples" / "dataspaces"
|
|
RENDERER = ROOT / "content_explorer" / "render.py"
|
|
|
|
|
|
def load_pair(name: str) -> tuple[dict, dict]:
|
|
directory = EXAMPLES / name
|
|
snapshot = json.loads((directory / "snapshot.json").read_text(encoding="utf-8"))
|
|
manifest = json.loads((directory / "manifest.json").read_text(encoding="utf-8"))
|
|
return snapshot, manifest
|
|
|
|
|
|
class PublishedFixtures(unittest.TestCase):
|
|
def test_each_example_passes_the_file_entry_point(self) -> None:
|
|
names = sorted(path.name for path in EXAMPLES.iterdir() if (path / "snapshot.json").is_file())
|
|
self.assertGreaterEqual(len(names), 2)
|
|
for name in names:
|
|
errors = validate_dataspace(EXAMPLES / name, require_size=True)
|
|
self.assertEqual(errors, [], name)
|
|
|
|
def test_renderer_switches_with_the_manifest(self) -> None:
|
|
systems, systems_manifest = load_pair("systems-thinking")
|
|
software, software_manifest = load_pair("software-architecture")
|
|
systems_view = render_explorer(systems, systems_manifest)
|
|
software_view = render_explorer(software, software_manifest)
|
|
switched = render_explorer(systems, software_manifest)
|
|
self.assertIn(systems_manifest["home"]["heading"], systems_view)
|
|
self.assertIn(software_manifest["home"]["heading"], software_view)
|
|
self.assertIn(software_manifest["home"]["heading"], switched)
|
|
self.assertNotIn(systems_manifest["home"]["heading"], switched)
|
|
self.assertNotIn(software_manifest["home"]["heading"], systems_view)
|
|
|
|
def test_renderer_source_has_no_domain_vocabulary(self) -> None:
|
|
source = RENDERER.read_text(encoding="utf-8").lower()
|
|
for word in (
|
|
"cancer",
|
|
"oncolog",
|
|
"feedback",
|
|
"coupling",
|
|
"ashby",
|
|
"meadows",
|
|
"alexander",
|
|
"systems-thinking",
|
|
"software-architecture",
|
|
"causal",
|
|
"cohesion",
|
|
):
|
|
self.assertNotIn(word, source, word)
|
|
|
|
|
|
class NegativeGraph(unittest.TestCase):
|
|
def setUp(self) -> None:
|
|
self.snapshot, self.manifest = load_pair("systems-thinking")
|
|
|
|
def test_duplicate_entity_id(self) -> None:
|
|
snapshot = copy.deepcopy(self.snapshot)
|
|
snapshot["entities"].append(copy.deepcopy(snapshot["entities"][0]))
|
|
errors = validate_documents(snapshot, self.manifest)
|
|
self.assertTrue(any("duplicate entity id" in error for error in errors))
|
|
|
|
def test_dangling_relation(self) -> None:
|
|
snapshot = copy.deepcopy(self.snapshot)
|
|
snapshot["relations"][0]["to"] = "urn:ce:systems-thinking:concept:missing"
|
|
errors = validate_documents(snapshot, self.manifest)
|
|
self.assertTrue(any("dangling to" in error for error in errors))
|
|
|
|
def test_missing_source(self) -> None:
|
|
snapshot = copy.deepcopy(self.snapshot)
|
|
snapshot["entities"][0]["sourceRefs"] = ["src:missing"]
|
|
errors = validate_documents(snapshot, self.manifest)
|
|
self.assertTrue(any("missing source src:missing" in error for error in errors))
|
|
|
|
def test_taxonomic_cycle_fails_and_other_cycles_do_not(self) -> None:
|
|
snapshot = copy.deepcopy(self.snapshot)
|
|
left = snapshot["entities"][0]["id"]
|
|
right = snapshot["entities"][1]["id"]
|
|
source = snapshot["sources"][0]["id"]
|
|
snapshot["relations"].extend([
|
|
{
|
|
"id": "cycle-a",
|
|
"predicate": "related-to",
|
|
"from": left,
|
|
"to": right,
|
|
"sourceRefs": [source],
|
|
},
|
|
{
|
|
"id": "cycle-b",
|
|
"predicate": "related-to",
|
|
"from": right,
|
|
"to": left,
|
|
"sourceRefs": [source],
|
|
},
|
|
])
|
|
manifest = copy.deepcopy(self.manifest)
|
|
manifest["relationLabels"]["related-to"] = {"forward": "Related to", "inverse": "Related to"}
|
|
manifest["acyclicPredicates"] = []
|
|
open_cycle = validate_documents(snapshot, manifest)
|
|
self.assertFalse(any("taxonomic cycle" in error for error in open_cycle))
|
|
manifest["acyclicPredicates"] = ["related-to"]
|
|
closed = validate_documents(snapshot, manifest)
|
|
self.assertTrue(any("taxonomic cycle" in error for error in closed))
|
|
|
|
|
|
class FactPolicy(unittest.TestCase):
|
|
def test_missing_unknown_and_conflict(self) -> None:
|
|
claims = [
|
|
{"key": "measure", "status": "unknown", "value": None},
|
|
{"key": "name", "status": "asserted", "value": "one"},
|
|
{"key": "name", "status": "asserted", "value": "two"},
|
|
]
|
|
self.assertEqual(resolve_claims(claims, "absent", "show-all")["state"], "missing")
|
|
self.assertEqual(resolve_claims(claims, "measure", "show-all")["state"], "unknown")
|
|
shown = resolve_claims(claims, "name", "show-all")
|
|
self.assertEqual(shown["state"], "contradicted")
|
|
self.assertEqual(shown["values"], ["one", "two"])
|
|
withheld = resolve_claims(claims, "name", "withhold")
|
|
self.assertEqual(withheld["values"], [])
|
|
preferred_claims = [
|
|
{"key": "name", "status": "asserted", "value": "one", "preferred": True},
|
|
{"key": "name", "status": "asserted", "value": "two"},
|
|
]
|
|
chosen = resolve_claims(preferred_claims, "name", "preferred")
|
|
self.assertEqual(chosen, {"state": "present", "values": ["one"]})
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|