resource-control/tools/basis.py
tegwick b8081f6c2d fix: correct the P-row misuse and the basis tier ordering
Both from info-tech-canon's review of the restatement.

STRAIN 1 — credential custody was recorded as one unit of class P consumption.
P is purchased platform capacity, and using security.secrets buys none. Removed
the row; the relationship now sits in provisions[].uses_provisions alongside the
object-store dependency, marked explicitly as a proposed extension because
ITC-CAP declares no provision-to-provision relation. Filed as
info-tech-canon/demand/ProvisionRelationships.md (their commit ce17dc4).

BASIS TIERS — their point about invoiced being a fin-hub fact we name rather
than originate exposed a real bug: a strict list order made weakest(["invoiced",
"measured"]) return "measured", implying an invoice outranks a measurement. It
does not outside its own domain. Strength is now a tier — invoiced and measured
are peers, quoted below both — with ties broken deterministically by catalog
order without implying a difference that does not exist.

Also filed info-tech-canon/demand/EvidenceBasis.md at their request, proposing
ITC-GOV as owner rather than ITC-CAP.

187 tests pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-15 19:02:58 +02:00

183 lines
7 KiB
Python

#!/usr/bin/env python3
"""Evidence basis: how a value was obtained, and how far it can be trusted.
Every quantity in this repository is one of a small number of epistemic kinds.
A counted object and an assumed hourly rate are both numbers; they are not both
knowledge. This module names the difference and propagates it.
The central rule is that a derived value is only as strong as its weakest
input. Without it, a precise-looking figure launders weak assumptions: EUR 30.00
of monthly labour reads like a measurement, when it is another repository's
estimate of hours multiplied by a rate we chose.
"""
from __future__ import annotations
import json
import sys
from pathlib import Path
# Ordered strongest to weakest. The order is the whole point: it is what makes
# "weakest input wins" computable.
BASIS_ORDER = (
"invoiced", # a booked financial fact, authoritative from fin-hub
"measured", # directly observed from the authoritative system
"quoted", # stated by a provider or counterparty in a citable source
"derived", # computed from other values by a stated rule
"projected", # interpolated between, or extrapolated beyond, observations
"estimated", # human judgement, neither observed nor computed
"assumed", # a modelling constant we chose
"unknown", # no value exists
)
BASES = frozenset(BASIS_ORDER)
# Strength is a tier, not a total order. `invoiced` and `measured` are peers:
# an invoice is the authoritative record of a payment, a measurement is the
# authoritative record of a quantity, and neither outranks the other outside
# its own domain. Asserting an order between them would make the weakest-input
# rule claim something it cannot know.
_TIER = {
"invoiced": 0, "measured": 0,
"quoted": 1,
"derived": 2,
"projected": 3,
"estimated": 4,
"assumed": 5,
"unknown": 6,
}
_ORDER = {name: index for index, name in enumerate(BASIS_ORDER)}
# Bases that assert an observed or contracted fact about the world.
EVIDENCED = frozenset({"invoiced", "measured", "quoted"})
def rank(basis: str) -> int:
"""Strength tier; lower is stronger. Peers share a tier."""
if basis not in _TIER:
raise ValueError(f"unknown evidence basis {basis!r}")
return _TIER[basis]
def weakest(bases) -> str:
"""The weakest basis in a collection. Empty means nothing is known.
Ties within a tier resolve by catalog order so the result is deterministic
without implying a strength difference that does not exist.
"""
bases = list(bases)
if not bases:
return "unknown"
return min(bases, key=lambda b: (-rank(b), _ORDER[b]))
def strongest(bases) -> str:
bases = list(bases)
if not bases:
return "unknown"
return min(bases, key=lambda b: (rank(b), _ORDER[b]))
def is_evidenced(basis: str) -> bool:
"""True when the value asserts an observed or contracted fact."""
return basis in EVIDENCED
def resolve(value: dict) -> str:
"""Effective basis of a value, propagating through derivation.
A `derived` value resolves to the weakest basis among its inputs: deriving
GB from measured bytes stays measured, while deriving euros from estimated
hours and an assumed rate is no better than assumed.
"""
basis = value.get("basis", "unknown")
if basis not in BASES:
raise ValueError(f"unknown evidence basis {basis!r}")
if basis != "derived":
return basis
inputs = value.get("derived_from") or []
if not inputs:
raise ValueError("a derived value must record derived_from")
return weakest(resolve(item) for item in inputs)
def validate_value(value: dict) -> None:
basis = value.get("basis")
if basis not in BASES:
raise ValueError(f"unknown evidence basis {basis!r}")
if basis == "derived" and not value.get("derived_from"):
raise ValueError("a derived value must record derived_from")
if basis != "derived" and value.get("derived_from"):
raise ValueError("only a derived value may record derived_from")
if basis == "unknown":
if value.get("value") is not None:
raise ValueError("a value with basis unknown must not carry a quantity")
if not value.get("gap"):
raise ValueError("an unknown value must name the gap and its owner")
elif value.get("value") is None:
raise ValueError("a known basis must carry a quantity; use basis unknown instead")
# A proxy measures a different quantity than the one named. That does not
# weaken the measurement, but it does weaken the inference drawn from it.
proxy_for = value.get("proxy_for")
if proxy_for is not None and not str(proxy_for).strip():
raise ValueError("proxy_for must name the quantity actually wanted")
for item in value.get("derived_from") or []:
validate_value(item)
def profile(values) -> dict:
"""Summarize a set of values for decision review."""
resolved = []
proxies = []
for value in values:
validate_value(value)
effective = resolve(value)
resolved.append(effective)
if value.get("proxy_for"):
proxies.append({"name": value.get("name"), "proxy_for": value["proxy_for"]})
counts: dict[str, int] = {}
for basis in resolved:
counts[basis] = counts.get(basis, 0) + 1
evidenced = [b for b in resolved if is_evidenced(b)]
return {
"count": len(resolved),
"by_basis": {k: counts[k] for k in BASIS_ORDER if k in counts},
"weakest": weakest(resolved),
"evidenced": len(evidenced),
"evidenced_ratio": round(len(evidenced) / len(resolved), 4) if resolved else None,
"proxies": proxies,
}
def decision_grade(values) -> dict:
"""Grade a decision by the weakest evidence it actually rests on.
A conclusion is not stronger than its weakest load-bearing input, however
precise the arithmetic between them looks.
"""
summary = profile(values)
basis = summary["weakest"]
if basis == "unknown":
grade, note = "insufficient", "at least one load-bearing value is unknown"
elif is_evidenced(basis):
grade, note = "evidenced", "every load-bearing value is observed, invoiced, or quoted"
elif basis == "projected":
grade, note = "projected", "the conclusion rests on values projected from observations"
else:
grade, note = "indicative", f"the conclusion is no stronger than an {basis} value" if basis[0] in "aeiou" else f"the conclusion is no stronger than a {basis} value"
if summary["proxies"]:
note += f"; {len(summary['proxies'])} value(s) measure a proxy rather than the quantity named"
return {**summary, "grade": grade, "note": note}
def main() -> int:
if len(sys.argv) != 2:
print(f"usage: {sys.argv[0]} VALUES.json", file=sys.stderr)
return 2
payload = json.loads(Path(sys.argv[1]).read_text())
values = payload["values"] if isinstance(payload, dict) else payload
print(json.dumps(decision_grade(values), indent=2))
return 0
if __name__ == "__main__":
raise SystemExit(main())