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

@ -122,6 +122,35 @@ def validate_provision(provision: dict, canon: dict) -> None:
if row.get("supply") and row["supply"] not in {"internal", "external"}:
raise ValueError(f"supply must be internal or external, got {row['supply']!r}")
validate_uses_provisions(provision, canon)
def validate_uses_provisions(provision: dict, canon: dict) -> None:
"""CAP-R11: relying on another provision is a relationship, not consumption.
A `depends_on` entry MUST correspond to a `depends_on` declared between the
two capabilities in the catalog, so the provision graph stays checkable
against the capability graph rather than free-form.
"""
capability = canon["capabilities"][provision["capability"]]
for entry in provision.get("uses_provisions") or []:
target = entry["capability"]
if target not in canon["capabilities"]:
raise ValueError(f"unknown capability {target} in uses_provisions")
relation = entry["relation"]
if relation not in {"depends_on", "may_use"}:
raise ValueError(f"relation must be depends_on or may_use, got {relation!r}")
if not entry.get("provider"):
raise ValueError(f"uses_provisions entry for {target} must name a provider")
declared = set(capability.get(relation) or [])
if relation == "depends_on" and target not in declared:
raise ValueError(
f"{provision['capability']} does not declare depends_on {target} in the catalog"
)
if relation == "may_use" and target not in declared:
# SHOULD, not MUST — surfaced rather than fatal.
entry.setdefault("_note", f"catalog does not declare may_use {target}")
def evidence_coverage(provision: dict, canon: dict) -> dict:
"""Which of the capability's declared evidence hooks this provision satisfies."""
@ -185,6 +214,10 @@ def review(record: dict, canon: dict) -> dict:
"maturity": provision["maturity"],
"evidence": evidence_coverage(provision, canon),
"consumption": consumption_profile(provision),
"uses_provisions": [
{"capability": u["capability"], "relation": u["relation"], "provider": u["provider"]}
for u in provision.get("uses_provisions") or []
],
}
for provision in record.get("provisions") or []
],