feat(okf): evidence_for distinguishes present, absent and unreadable with a reason

Co-Authored-By: Claude <claude-opus-5>
This commit is contained in:
Kjell Tore Guttormsen 2026-09-02 20:31:18 +02:00
commit d62e89935c
2 changed files with 232 additions and 1 deletions

View file

@ -455,6 +455,101 @@ def trust_tier(entries: tuple[dict[str, str], ...] | None) -> TrustTier:
return "machine-confirmed"
#: What a document says about ONE provenance key. Three states, and the third is the whole point:
#: collapsing ``unreadable`` into ``absent`` turns a verdict on missing evidence into evidence of
#: absence.
EvidenceState = Literal["present", "absent", "unreadable"]
@dataclass(frozen=True)
class FalsificationEvidence:
"""What one document offers a falsification verdict, and how much of it could be read.
Each field asserts only what its state can honestly carry: ``tier`` is ``None`` unless the state
is ``present`` (an unread value tiers nothing), and ``reason`` is ``None`` when it IS present
(there is nothing to explain). ``items_seen`` is its own field rather than folded into a token,
the ``BudgetExceeded`` -(y) rule: "which shape" and "how many" are two operative questions."""
#: Path of the document the evidence was read from.
file: str
state: EvidenceState
#: The derived trust tier — ``None`` unless ``state`` is ``present``.
tier: TrustTier | None
#: WHY the value could not be read — ``None`` unless ``state`` is ``unreadable``.
reason: ProvenanceReason | None
#: The decoded entries, empty unless ``state`` is ``present``.
entries: tuple[dict[str, str], ...]
#: How many entries were seen, INCLUDING ones that could not be decoded. Part of the
#: ``(state, reason, items_seen)`` triple a discounted concept is reported with.
items_seen: int
def evidence_for(path: str | Path, key: str = "verified") -> FalsificationEvidence:
"""Read one document's provenance into the three-state answer a falsification verdict needs.
**A library primitive, deliberately NOT wired into ``run_project`` or ``explore``**, mirroring
``promote_verdict`` and ``write_verdict``: the system reads, the caller decides. Wiring it into
the run surface would move the byte-pinned demo transcript, which nothing asks for.
Gated by ``tests/test_falsification_verdict_loadbearing.py``."""
result = read_provenance(path, key)
if result is None:
return FalsificationEvidence(
file=str(path), state="absent", tier=None, reason=None, entries=(), items_seen=0
)
if isinstance(result, UnreadableProvenance):
return FalsificationEvidence(
file=str(path),
state="unreadable",
tier=None,
reason=result.reason,
entries=(),
items_seen=result.items_seen,
)
return FalsificationEvidence(
file=str(path),
state="present",
tier=trust_tier(result),
reason=None,
entries=result,
items_seen=len(result),
)
def evidence_notice(evidence: FalsificationEvidence) -> str | None:
"""One line about evidence that could not be used, or ``None`` when there is nothing to say.
Omission, never an empty row (the ``cost_baseline_notice`` precedent). The reason TOKEN is
printed raw rather than translated into prose, so no second display vocabulary exists to drift
from ``ProvenanceReason``."""
if evidence.state == "present":
return None
detail = "" if evidence.reason is None else f"; reason {evidence.reason}"
return (
f"provenance {evidence.state} in {evidence.file}{detail}; items_seen={evidence.items_seen}"
)
def admits_falsification(evidence: FalsificationEvidence) -> bool:
"""The K5 threshold, expressed in ONE place: ``present`` AND a tier above ``unverified``.
An operator decision, not a default. A second copy of a threshold drifts (-(p)), and a
threshold spelled inline at each caller is a threshold nobody can find.
Everything it refuses is meant to be REPORTED with ``(state, reason, items_seen)`` and
explicitly discounted never silently excluded, because a dropped concept and a discounted one
are different facts and only one of them is honest about what was read.
**``author`` / ``usage_count`` / ``last_modified`` are deliberately NOT required, and the
denominator is written down rather than implied:** SPEC §5.1 names SIX entry keys and the
producer writes TWO of them. Requiring keys the producer does not emit would make the threshold
unreachable in practice while looking strict on paper. The threshold names only what is
actually written. See ``docs/okf-konsum-kontrakter.md`` § 1, which is the source for this rule.
Gated by ``tests/test_falsification_verdict_loadbearing.py``."""
return evidence.state == "present" and evidence.tier != "unverified"
#: WHY a provenance value could not be read. The tokens name the SHAPE the value is written in
#: and NOTHING else — the same discipline ``SkipReason`` carries. A block sequence and a block
#: mapping are both CONFORMANT OKF (SPEC §5.2 writes ``verified`` in exactly those forms); they are