resource-control/tools/basis.py
tegwick 13c2b82281 feat: restate the backup case in ITC-CAP terms; add evidence basis; publish consumption-mode signal
Three things.

1. CANON RESTATEMENT (info-tech-canon's ask after accepting our demand)
data/capability/platform-audit-storage.json restates the backup case against
ITC-CAP 0.2.0: requirement with profile, targets and the failure-domain
constraint that decided the procurement; two provisions (data.object and
data.backup); all four data.backup evidence hooks satisfied and measured; and
consumption in native units — GB, hours, tokens — with unknown never zero.

tools/capability.py reads their capabilities.yaml directly rather than copying
it, so drift in either repo fails here. The requirement asks D5, the provision
is D4, and the review reports below_requirement rather than inflating maturity.

2. EVIDENCE BASIS (tools/basis.py, docs/evidence-basis.md)
Every value declares how it was obtained on an ordered scale: invoiced,
measured, quoted, derived, projected, estimated, assumed, unknown. A derived
value resolves to the weakest basis among its inputs, so precise arithmetic
cannot launder weak assumptions.

First application is a finding about our own biggest decision: the Scaleway vs
Hetzner comparison, EUR 29.14/month stated to the cent, grades "indicative" —
1 of 4 load-bearing values evidenced, weakest "assumed". The direction is
robust; the magnitude is a model output. The cheapest fix is recording real
operator hours, not better arithmetic.

3. CONSUMPTION-MODE SIGNAL (railiance-platform RAILIANCE-WP-0017)
settlement.py gains a consumption-mode command projecting statements into the
signal they consume; make consumption-mode PERIOD=YYYY-MM publishes
data/consumption-mode/current.json. Currently an empty list: no live charges for
2026-09, so no entity is restricted. Publishing the empty list makes that an
assertion rather than an absence, which their contract distinguishes. The
validator fails if the published signal is stale.

185 tests pass.

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

163 lines
6.2 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)
_RANK = {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:
if basis not in _RANK:
raise ValueError(f"unknown evidence basis {basis!r}")
return _RANK[basis]
def weakest(bases) -> str:
"""The weakest basis in a collection. Empty means nothing is known."""
bases = list(bases)
if not bases:
return "unknown"
return max(bases, key=rank)
def strongest(bases) -> str:
bases = list(bases)
if not bases:
return "unknown"
return min(bases, key=rank)
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())