feat(okf): evidence_for distinguishes present, absent and unreadable with a reason
Co-Authored-By: Claude <claude-opus-5>
This commit is contained in:
parent
3d76a46d76
commit
d62e89935c
2 changed files with 232 additions and 1 deletions
|
|
@ -455,6 +455,101 @@ def trust_tier(entries: tuple[dict[str, str], ...] | None) -> TrustTier:
|
||||||
return "machine-confirmed"
|
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`` kø-(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 (kø-(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
|
#: 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
|
#: 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
|
#: mapping are both CONFORMANT OKF (SPEC §5.2 writes ``verified`` in exactly those forms); they are
|
||||||
|
|
|
||||||
|
|
@ -17,9 +17,10 @@ reusing it would mean editing a frozen module to reach a repo-owned fixture.
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import shutil
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from portfolio_optimiser import okf
|
from portfolio_optimiser import okf, verdicts
|
||||||
|
|
||||||
_GOLDEN_DIR = Path(__file__).resolve().parents[1] / "tests" / "golden" / "block-form-provenance"
|
_GOLDEN_DIR = Path(__file__).resolve().parents[1] / "tests" / "golden" / "block-form-provenance"
|
||||||
|
|
||||||
|
|
@ -52,3 +53,138 @@ def test_block_form_bundle_renders_the_captured_bytes() -> None:
|
||||||
assert [f.name for f in bundle.files] == ["index.md", "attested.md", "multi-verified.md"]
|
assert [f.name for f in bundle.files] == ["index.md", "attested.md", "multi-verified.md"]
|
||||||
|
|
||||||
assert okf.bundle_context(bundle) == expected
|
assert okf.bundle_context(bundle) == expected
|
||||||
|
|
||||||
|
|
||||||
|
# --- Step 7: the falsification verdict, with the third state carrying its reason ---------------
|
||||||
|
|
||||||
|
_ENERGI_BUNDLE = Path(__file__).resolve().parents[1] / "shared" / "examples" / "bygg-energi-mikro"
|
||||||
|
|
||||||
|
|
||||||
|
def _present_document(tmp_path: Path) -> Path:
|
||||||
|
"""A REAL flow-form document, produced by our own writer — never a mock.
|
||||||
|
|
||||||
|
All three arms below read committed or freshly-written files, because the property under test
|
||||||
|
is what the reader does with bytes on disk, and a stubbed reader would test the stub.
|
||||||
|
"""
|
||||||
|
dst = tmp_path / "bundle"
|
||||||
|
shutil.copytree(_ENERGI_BUNDLE, dst)
|
||||||
|
return verdicts.promote_verdict(
|
||||||
|
str(dst),
|
||||||
|
verdicts.Verdict(
|
||||||
|
id="EVIDENCE-PRESENT",
|
||||||
|
proposal_features=verdicts.bundle_candidate_features(str(dst)),
|
||||||
|
decision="approved",
|
||||||
|
rationale="godkjent",
|
||||||
|
),
|
||||||
|
approver="process:fixture-check",
|
||||||
|
experiment="exp-B",
|
||||||
|
timestamp="2026-06-30T09:00:00Z",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_a_flow_form_document_is_present_and_tiered(tmp_path: Path) -> None:
|
||||||
|
"""CONTROL — without it, every arm below is satisfied by a reader that always says "absent"."""
|
||||||
|
evidence = okf.evidence_for(_present_document(tmp_path))
|
||||||
|
assert evidence.state == "present"
|
||||||
|
assert evidence.tier == "machine-confirmed"
|
||||||
|
assert evidence.reason is None, (
|
||||||
|
"a present state has no reason to carry, and must not invent one"
|
||||||
|
)
|
||||||
|
assert evidence.items_seen == 1
|
||||||
|
|
||||||
|
|
||||||
|
def test_a_document_without_the_key_is_absent_and_carries_no_tier() -> None:
|
||||||
|
"""``absent`` is what a document that never claimed verification says — and it is NOT the same
|
||||||
|
fact as ``unreadable``."""
|
||||||
|
evidence = okf.evidence_for(_GOLDEN_DIR / "bundle" / "index.md")
|
||||||
|
assert evidence.state == "absent"
|
||||||
|
assert evidence.tier is None
|
||||||
|
assert evidence.reason is None
|
||||||
|
assert evidence.items_seen == 0
|
||||||
|
|
||||||
|
|
||||||
|
def test_a_block_form_document_is_unreadable_and_says_WHY() -> None:
|
||||||
|
"""The third state, and the arm asserts the REASON, not merely the state.
|
||||||
|
|
||||||
|
A collapsed ``unreadable`` → ``absent`` is a verdict on missing evidence presented as evidence
|
||||||
|
of absence. Asserting only ``state != "present"`` would stay green against exactly that
|
||||||
|
collapse, so the reason token is what this arm pins.
|
||||||
|
"""
|
||||||
|
evidence = okf.evidence_for(_GOLDEN_DIR / "bundle" / "attested.md")
|
||||||
|
assert evidence.state == "unreadable"
|
||||||
|
assert evidence.reason == "block-sequence"
|
||||||
|
assert evidence.items_seen == 1
|
||||||
|
|
||||||
|
|
||||||
|
def test_a_two_entry_block_document_yields_NO_tier() -> None:
|
||||||
|
"""A tier here would be the measured second-entry-wins defect surfacing.
|
||||||
|
|
||||||
|
``multi-verified.md`` carries a human sign-off FIRST and a process entry SECOND. A reader that
|
||||||
|
limped past the block form and kept the last entry it saw would report ``machine-confirmed``
|
||||||
|
for a concept a human signed — downgrading the tier with nothing failing. The honest answer to
|
||||||
|
an unreadable value is no tier at all.
|
||||||
|
"""
|
||||||
|
evidence = okf.evidence_for(_GOLDEN_DIR / "bundle" / "multi-verified.md")
|
||||||
|
assert evidence.state == "unreadable"
|
||||||
|
assert evidence.tier is None
|
||||||
|
assert evidence.items_seen == 2
|
||||||
|
|
||||||
|
|
||||||
|
def test_evidence_notice_is_None_when_there_is_nothing_to_say(tmp_path: Path) -> None:
|
||||||
|
"""Omission, never an empty row — the ``cost_baseline_notice`` precedent."""
|
||||||
|
assert okf.evidence_notice(okf.evidence_for(_present_document(tmp_path))) is None
|
||||||
|
|
||||||
|
|
||||||
|
def test_evidence_notice_prints_the_reason_TOKEN_itself() -> None:
|
||||||
|
"""No second display vocabulary. A prose translation here would be free to drift from
|
||||||
|
``ProvenanceReason``, and the drifted copy is the one the operator would read."""
|
||||||
|
notice = okf.evidence_notice(okf.evidence_for(_GOLDEN_DIR / "bundle" / "attested.md"))
|
||||||
|
assert notice is not None
|
||||||
|
assert "block-sequence" in notice
|
||||||
|
assert "items_seen=1" in notice
|
||||||
|
|
||||||
|
|
||||||
|
# --- Amendment A: the K5 threshold ------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
def test_admits_falsification_refuses_a_state_that_is_not_present() -> None:
|
||||||
|
"""AMENDMENT A, first conjunct — a verdict may not rest on evidence that was never read."""
|
||||||
|
assert not okf.admits_falsification(okf.evidence_for(_GOLDEN_DIR / "bundle" / "attested.md"))
|
||||||
|
assert not okf.admits_falsification(okf.evidence_for(_GOLDEN_DIR / "bundle" / "index.md"))
|
||||||
|
|
||||||
|
|
||||||
|
def test_admits_falsification_refuses_an_unverified_tier() -> None:
|
||||||
|
"""AMENDMENT A, second conjunct — its OWN arm, because one arm cannot separate two conjuncts.
|
||||||
|
|
||||||
|
**This state is not reachable through ``evidence_for`` today, and that is measured, not
|
||||||
|
assumed:** ``decode_flow_value`` refuses an empty flow sequence, so a ``present`` value always
|
||||||
|
carries at least one entry naming an actor, and ``trust_tier`` therefore never answers
|
||||||
|
``unverified`` for it. The conjunct is DEFENSIVE, which is precisely why it needs an arm of its
|
||||||
|
own — a single arm over a reachable document would leave it untested, and an untested conjunct
|
||||||
|
is one a later simplification deletes without anything going red.
|
||||||
|
"""
|
||||||
|
unverified = okf.FalsificationEvidence(
|
||||||
|
file="crafted.md", state="present", tier="unverified", reason=None, entries=(), items_seen=0
|
||||||
|
)
|
||||||
|
assert not okf.admits_falsification(unverified)
|
||||||
|
|
||||||
|
|
||||||
|
def test_admits_falsification_ADMITS_the_positive_case(tmp_path: Path) -> None:
|
||||||
|
"""CONTROL — an always-refusing threshold passes both negative arms and is worthless."""
|
||||||
|
assert okf.admits_falsification(okf.evidence_for(_present_document(tmp_path)))
|
||||||
|
|
||||||
|
|
||||||
|
def test_a_discounted_concept_reports_the_TRIPLE_not_merely_the_refusal() -> None:
|
||||||
|
"""AMENDMENT A — "why it was discounted" is the operative fact.
|
||||||
|
|
||||||
|
A dropped concept and a discounted one are different facts, and only the second is honest about
|
||||||
|
what was read. Asserting that admission was denied says nothing about which of them happened;
|
||||||
|
the triple ``(state, reason, items_seen)`` is what makes the difference legible.
|
||||||
|
"""
|
||||||
|
evidence = okf.evidence_for(_GOLDEN_DIR / "bundle" / "multi-verified.md")
|
||||||
|
assert not okf.admits_falsification(evidence)
|
||||||
|
assert (evidence.state, evidence.reason, evidence.items_seen) == (
|
||||||
|
"unreadable",
|
||||||
|
"block-sequence",
|
||||||
|
2,
|
||||||
|
)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue