feat: adopt canon 0.4.0-0.6.0 — EvidenceBasis is canon, uses_provisions is canon

Both demands were accepted. Adopting what landed.

EVIDENCE BASIS IS NOW ITC-GOV CANON (0.4.0)
tools/basis.py reads infospace/models/governance/evidence-basis.yaml instead of
defining its own vocabulary — same discipline we already applied to the
capability catalog. Two semantic changes came back that we did not have:

- estimated and assumed are peers in tier "judgement". We had them separately
  ranked, which asserted a difference the canon does not.
- derived belongs to no tier at all; asking for its tier before resolving it is
  now an error rather than a silent rank.

Tier membership is read from tiers[].members, not bases[].tier: the latter
labels invoiced/measured/quoted all as "evidenced" while the tier list splits
them across "observed" and "quoted". tiers[] is authoritative; reported upstream.

USES_PROVISIONS IS NOW CANON (0.5.0, CAP-R11)
Dropped the proposed_extensions marker. Renamed relation "uses" to "may_use" per
their migration note. tools/capability.py now enforces CAP-R11: relation must be
depends_on or may_use, a provider must be named, and a depends_on entry MUST be
declared between those capabilities in the catalog. data.backup gained catalog
may_use: security.secrets from our restatement, so our entry now checks out.

Also in 0.4.0: §10.3 changed so a joinable consumer record counts as promotion
proof, met by our restatement; ITC-CAP is now 0.4.0 / canon 0.6.0, status draft.
Record and tests updated to those versions.

196 tests pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
tegwick 2026-08-15 19:57:46 +02:00
parent b8081f6c2d
commit 7a196b6265
6 changed files with 292 additions and 131 deletions

View file

@ -29,8 +29,10 @@ class CanonBindingTest(unittest.TestCase):
self.canon = canon()
def test_catalog_is_the_version_we_restated_against(self):
self.assertEqual("0.2.0", self.canon["version"])
self.assertEqual("0.3.0", self.canon["canon_version"])
self.assertEqual("0.4.0", self.canon["version"])
self.assertEqual("0.6.0", self.canon["canon_version"])
self.assertEqual(self.canon["version"], RECORD["canon"]["model_version"])
self.assertEqual(self.canon["canon_version"], RECORD["canon"]["canon_version"])
def test_human_effort_and_intelligence_classes_exist_with_native_units(self):
classes = self.canon["resource_classes"]
@ -133,6 +135,34 @@ class ProvisionTest(unittest.TestCase):
self.assertFalse(coverage["complete"])
self.assertIn("object_integrity_tests", coverage["missing"])
def test_using_another_capability_is_a_relation_not_a_p_row(self):
"""CAP-R11. The credential-custody P row was the wrong kind."""
backup = self.provisions["data.backup"]
classes = {r["class"] for r in backup["consumes"]}
self.assertNotIn("P", classes)
relations = {u["capability"]: u["relation"] for u in backup["uses_provisions"]}
self.assertEqual("may_use", relations["security.secrets"])
self.assertEqual("depends_on", relations["data.object"])
def test_depends_on_must_be_declared_between_the_capabilities(self):
backup = self.provisions["data.backup"]
entry = next(u for u in backup["uses_provisions"] if u["capability"] == "security.secrets")
entry["relation"] = "depends_on"
with self.assertRaisesRegex(ValueError, "does not declare depends_on"):
validate_provision(backup, self.canon)
def test_relation_outside_the_canon_vocabulary_is_rejected(self):
backup = self.provisions["data.backup"]
backup["uses_provisions"][0]["relation"] = "uses"
with self.assertRaisesRegex(ValueError, "depends_on or may_use"):
validate_provision(backup, self.canon)
def test_a_used_provision_must_name_its_provider(self):
backup = self.provisions["data.backup"]
backup["uses_provisions"][0]["provider"] = ""
with self.assertRaisesRegex(ValueError, "must name a provider"):
validate_provision(backup, self.canon)
def test_effort_and_tokens_are_recorded_in_native_units(self):
rows = {r["class"]: r for r in self.provisions["data.object"]["consumes"]}
self.assertEqual("hour", rows["H"]["quantity"]["unit"])