fix(okf): evidence_for derives a tier only for the key SPEC 5.3 tiers

[skip-docs]

PM addendum 5 to order 20260902T151931Z-250257273, reported at the close of B4
and deliberately left unfixed there as outside that order.

evidence_for(path, key="sources") raised ValueError. The tier was derived
unconditionally through trust_tier, which refuses an entry that names no `by`
actor -- correctly, because a trust level derived from an entry identifying
nobody mints the provenance it claims to read. But `by` is required of a
VERIFICATION entry (SPEC 5.2), not of every provenance key: the agreed
segmented form carries segment_id/source_offset and a sources list carries
id/resource. The guard belonging to one key was being applied to all of them.

trust_tier is UNCHANGED. evidence_for stops applying it to keys it was never
about: _TIERED_KEY names the one key SPEC 5.3 tiers, and every other key comes
back with state / reason / items_seen / entries and tier=None.

admits_falsification moved WITH it, and that is the same fact rather than
scope: it read `tier != "unverified"`, and None != "unverified" is TRUE, so a
present `sources` list would have cleared a threshold about verification. It
now names the tiers that clear it. For every value reachable before this change
both spellings are identical, which is why the existing K5 arms stay green.

Measured, and it corrected the test: an actorless `verified` value never
reaches trust_tier through evidence_for at all -- the decoder refuses that shape
first as unreadable / unsupported-flow. Both guards are now asserted where each
actually lives instead of one asserted where it is not.

Load-bearing measured, four mutations, all red against the WHOLE suite: derive
the tier unconditionally (3 red) - revert the K5 threshold (1, the consequence
arm alone) - "fix" it by never tiering at all (3, including two pre-existing
falsification arms) - loosen trust_tier instead (2, including its own
pre-existing gate). Green control 1208 passed / 5 skipped; golden
demo-transcript.stdout unchanged (ea8c534773acdbe41ae68f2c55724d69aaf8be4f).
This commit is contained in:
Kjell Tore Guttormsen 2026-09-03 01:28:12 +02:00
commit d296cffcd5
2 changed files with 167 additions and 5 deletions

View file

@ -35,7 +35,7 @@ import posixpath
import re
from dataclasses import dataclass
from pathlib import Path
from typing import Any, Literal
from typing import Any, Final, Literal
from portfolio_optimiser.ir import CostBaseline
from portfolio_optimiser.retrieval import PathSecurityError, safe_resolve
@ -538,14 +538,28 @@ class FalsificationEvidence:
items_seen: int
def evidence_for(path: str | Path, key: str = "verified") -> FalsificationEvidence:
#: The ONE provenance key SPEC §5.3 derives a trust tier from. Named rather than spelled inline at
#: the two places that care, so "which key is tiered" cannot answer differently in each of them.
_TIERED_KEY: Final = "verified"
def evidence_for(path: str | Path, key: str = _TIERED_KEY) -> 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``."""
**A tier is derived ONLY for the key SPEC §5.3 tiers.** ``tier`` used to be derived
unconditionally, so ``key="sources"`` or the agreed segmented form's
``segment_id``/``source_offset`` crashed in ``trust_tier``, which refuses an entry naming no
``by`` actor. That refusal is right, and it belongs to ``verified`` alone: ``by`` is required of
a verification entry (SPEC §5.2), not of every provenance key. So the guard stays exactly where
it is and this function stops applying it to keys it was never about. Measured at the close of
B4: all 11 call sites in the repo use the default, so the parameter had never been exercised.
Gated by ``tests/test_falsification_verdict_loadbearing.py`` and
``tests/test_evidence_key_parameter_loadbearing.py``."""
result = read_provenance(path, key)
if result is None:
return FalsificationEvidence(
@ -563,7 +577,7 @@ def evidence_for(path: str | Path, key: str = "verified") -> FalsificationEviden
return FalsificationEvidence(
file=str(path),
state="present",
tier=trust_tier(result),
tier=trust_tier(result) if key == _TIERED_KEY else None,
reason=None,
entries=result,
items_seen=len(result),
@ -601,7 +615,12 @@ def admits_falsification(evidence: FalsificationEvidence) -> bool:
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"
# NAMES the tiers that clear it, rather than excluding the one that does not. ``tier`` is now
# ``None`` for any key other than ``verified`` (SPEC §5.3 tiers that key alone), and
# ``None != "unverified"`` is TRUE — so the exclusion spelling would let a present ``sources``
# list clear a threshold about verification. For every value reachable before that change the
# two spellings are identical, which is why this is a tightening and not a new rule.
return evidence.state == "present" and evidence.tier in ("human-reviewed", "machine-confirmed")
#: WHY a provenance value could not be read. The tokens name the SHAPE the value is written in