portfolio-optimiser/tests/test_falsification_skill_loadbearing.py
Kjell Tore Guttormsen fed69790ac fix(okf): close the inert ingest-stamp guard, then land okf 0.8.5 -- and read the block sources form all four bases actually write
P13 measured this lift and REFUSED it, because okf >=0.8.5 emits the ownership
stamp as the V1 flow mapping `generated: { by: process:okf-ingest, at: ... }`
where 0.3.2 emitted `true`, and `_carries_complete_ingest_stamp` read the new
form as NOT a stamp -- write_concept_file's forgery refusal would have shipped
DISARMED with the whole fail-closed suite green. That blocker is closed first,
red-first, and then the pin moves.

ROW 1, THE SECURITY HALF. `_claims_ingest_ownership` widens the predicate from
"reads as boolean True" to "claims ingest ownership", of which the boolean is
the pre-V1 spelling. The recogniser for the new half is `decode_flow_value` --
the module's ONE flow decoder, the same argument write_concept_file already
makes for `verified`: the writer refuses exactly what the reader can read. A
value the decoder REFUSES is therefore not an ownership claim and writes
through, which is what keeps this from collapsing into "any non-empty
generated". Two arms red before the fix; no YAML library introduced.

THE PIN. okf v0.3.2 -> v0.8.5, guard v0.3.4 -> v1.4.0 spelled `tag =`, not
`rev =`, and not the declared floor 1.2.0 -- both P13 premises hold and the
reason now lives next to the pin in pyproject.toml. The ":40" comment is
corrected: okf has ONE runtime dependency, the guard, and that is what binds
the two lines together. 27/27 imported names resolve across five modules.

THE GOLDENS, REGENERATED AS A DECISION. Seven concept files across four
examples/ingest-golden-* bundles, one line each. Two were regenerated by the
REAL materializer; the other five are derived (http/sql/mcp cannot materialize
outside the tests' stubs) and then MEASURED -- all four golden suites compare
byte for byte against what the stubs produce, and all four are green. The four
`generated == "true"` asserts now read ONE source, conftest.
expected_generated_stamp: four literals for one emitter fact are four places a
later release can leave half-corrected, which is exactly how the pre-V1 form
survived until P13 measured it. tests/test_okf.py keeps its literal on purpose
-- that one round-trips a CURATED half-stamp through our own writer.

THE BLOCK READER. Measured with the full denominator: all four delivered
knowledge bases write `sources` as a BLOCK sequence and none in flow form
(n100 446/446, n200 1133/1133, n500 270/270, r761 2756/2756 = 4605/4605), and
`evidence_for` reported `unreadable` on 4605 of 4605 -- the falsification layer
had no address for any document in any base. `okf.decode_block_mappings` is the
second CARRIER of one grammar, never a second grammar: colon-SPACE separator,
unquote_scalar, duplicate keys refused, SPEC 5.2's actor rule applied. okf's
consume.read_sources was READ for the form and not called; po calls no okf
reader, which is measured and deliberate. After: 4605 present / 4605 entries.
Reading is not a licence to WRITE -- the emitter is untouched and both writers
still refuse what decode_flow_value refuses.

THREE FINDINGS. (1) The first block reader INVENTED data on `- { k: v }` items
-- SPEC-canonical, and the shape tests/golden/block-form-provenance writes for
`verified` -- decoding it as `{'{ id': '...'}`. No arm caught it: the 5.2 actor
rule shielded the fixture by accident. Closed with a flow-decoder branch and
four new arms. (2) One of my own arms was VACUOUS, found by my own mutation M5:
it claimed to prove the colon-SPACE rule and stayed green under first-colon,
because the two rules agree on every delivered value. Renamed, labelled, and
the claim moved to the arm that actually witnesses it. (3) OPEN, and it needs
the operator: the commons-owned worked example declares its second concept
`unreadable`/`block-sequence`, which is now false for po. `shared/` is
pull-only, so closing it needs a commons amendment; the test asserts the
divergence instead of skipping it, keeping the discriminating half (the example
says two entries were seen and the reader returns exactly two).

NINE EXISTING ARMS REWRITTEN, NONE WEAKENED. All nine pinned "the block form is
unreadable" -- the behaviour this order changes. Each keeps its claim on a
specimen that is still unreadable for a reason of its own (5.2: an entry naming
no actor), or pins the REVERSED direction where the old arm stood so the change
cannot be silent. Two got STRONGER: multi-verified.md was authored for "a reader
keeping the last entry reports machine-confirmed for a concept a human signed",
and that could not be tested while the form was unreadable. Three node ids were
renamed; nothing was removed in substance.

Suite 1582 -> 1606 passed / 5 skipped. Both demo goldens byte-unchanged
(ea8c534... / ede3e2f..., shasum -a 1 of the CONTENT, never the git blob id).
ruff check / ruff format / mypy green. shared/ untouched.

Six mutations, all red against the WHOLE suite, each with its own signature:
row 1 detached (2) / block reader detached (17) / flow-item branch detached (7)
/ a stray indented line folds into an INVENTED entry (4) / separator becomes the
first colon (1 -- and that is finding 2) / the stamp expectation reverts to
"true" (4).

Order: 20260912T195112Z-995611104-from-.claude
Record: docs/2026-09-12-p13b-okf-bump.md

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-12 23:02:38 +02:00

171 lines
9.5 KiB
Python

"""Step 12 (Session 5) - the falsification method as a SHARED Agent Skill, authored in commons.
**Operator decision D4, and why the skill is not written here.** ``shared/`` is a PULL-ONLY git
subtree of ``portfolio-optimiser-commons``. Authoring the skill locally would produce a wheel the
sibling never inherits, and **no test in this suite detects that divergence** --
``tests/test_shared_packaged_data_loadbearing.py`` compares the working tree against the wheel,
never against commons. The natural "fix" is ``git subtree push``, which leaked consumer history
once already, on 2026-07-03. So the two files under ``shared/skills/falsification-reviewer/``
arrive by ``git subtree pull``, and nothing in this file writes there.
**What is guarded HERE, and what deliberately is not.** The framework-neutrality rule has ONE copy
and it is NOT in this file: ``test_method_spec_loadbearing.test_method_spec_is_framework_neutral``
sweeps every shared spec and every skill tree, and Amendment 2 extended it to cover this skill.
Two copies of one rule is the ko-(p) drift class, and the plan concedes the framework guard is
close to trivially green on pure prose anyway. **The TERMINOLOGY guard is the one that bites**, and
it lives here: the customer-facing wording is "knowledge base" / "knowledge bundle", never the
internal format name, so the literal string ``OKF bundle`` appearing in the skill's prose turns
this file red.
**Amendment 3 decides the round trip's input, and it is not a preference.** ``frontmatter_verbatim``
is AUTHORITATIVE: it is the bytes a test materialises into a throwaway concept file before reading
them back. The sibling ``frontmatter`` object is a line-oriented PROJECTION and informative only --
by construction it cannot carry a block form, which is why concept 2 has no ``sources`` key there.
Materialising from ``frontmatter`` would derive ``state: absent`` and silently lose the
``unreadable`` case the example exists to demonstrate: the arm would be GREEN while proving the
opposite of its docstring. The example's own judgement is ``undecided`` -- not ``survived`` -- for
the same reason, because a claim whose one possible refuter was unreadable was never attacked.
"""
from __future__ import annotations
import json
import re
from pathlib import Path
import pytest
from portfolio_optimiser import okf, persona
REPO_ROOT = Path(__file__).resolve().parents[1]
SKILL_DIR = REPO_ROOT / "shared" / "skills" / "falsification-reviewer"
EXAMPLE = SKILL_DIR / "references" / "example-evidence.json"
#: The internal format name. Customer-facing prose says "knowledge base" / "knowledge bundle".
_INTERNAL_NAME = re.compile(r"OKF bundle")
def test_the_skill_ships_its_two_files_with_usable_frontmatter() -> None:
"""(a) Structure. RED before the subtree pull has landed -- which is exactly Step 12's
On-failure clause: if the files are absent, STOP; do not author under ``shared/`` locally."""
skill_md = SKILL_DIR / "SKILL.md"
assert skill_md.is_file(), "shared/skills/falsification-reviewer/SKILL.md missing"
assert EXAMPLE.is_file(), "the skill's worked example is missing"
fm = okf.parse_frontmatter(skill_md)
assert fm.get("name") == "falsification-reviewer"
assert fm.get("description", "").strip(), "SKILL.md description must be non-empty"
json.loads(EXAMPLE.read_text(encoding="utf-8"))
def test_the_skill_never_uses_the_internal_format_name() -> None:
"""(b) The DISCRIMINATING guard. Paired with a KNOWN-POSITIVE control: a guard that searched
for something no file could ever contain would be green by construction, so the same regex is
first shown to fire on text that does carry the string."""
assert _INTERNAL_NAME.search("read the OKF bundle at that path") is not None, (
"the guard cannot find the string it is supposed to forbid -- it proves nothing"
)
for f in sorted(p for p in SKILL_DIR.rglob("*") if p.is_file()):
hit = _INTERNAL_NAME.search(f.read_text(encoding="utf-8"))
assert hit is None, f"internal format name {hit.group(0)!r} in customer-facing prose: {f}"
def test_the_example_resolves_at_call_time_not_at_import(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
"""(c) The loader seam, mirroring ``persona.load_persona_example``: resolved INSIDE the call
(never frozen into a default argument), so both the test seam and ``PORTFOLIO_SHARED_ROOT``
stay live. A loader that bound its path at import would answer from the tree that existed when
the module was first imported."""
real = persona.load_falsification_example()
assert real.judgement == "undecided"
stand_in = tmp_path / "example-evidence.json"
stand_in.write_text(
json.dumps(
{
"claim": "c",
"judgement": "survived",
"refuter": None,
"concepts": [],
}
),
encoding="utf-8",
)
monkeypatch.setattr(persona, "_FALSIFICATION_EXAMPLE_PATH", stand_in)
assert persona.load_falsification_example().judgement == "survived"
def test_a_missing_example_fails_fast_rather_than_degrading(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
"""(d) The example is REQUIRED input (contrast the tolerant Step-7 inbox). A broken shared
artefact must surface, not pass silently."""
monkeypatch.setattr(persona, "_FALSIFICATION_EXAMPLE_PATH", tmp_path / "nope.json")
with pytest.raises(FileNotFoundError):
persona.load_falsification_example()
def test_the_worked_example_round_trips_through_the_real_readers(tmp_path: Path) -> None:
"""(e) The example is not decoration: every concept's declared ``evidence`` and ``derived``
values are re-derived by the SHIPPED readers from the SHIPPED bytes.
``read_provenance`` takes a MARKDOWN path, not JSON, so the fields are materialised into a
throwaway concept file first (Amendment 3) -- ``frontmatter_verbatim`` VERBATIM, never
reassembled from the ``frontmatter`` projection. Reassembling would emit concept 2's
``sources`` as nothing at all, and the ``unreadable``/``block-sequence`` arm would pass as
``absent`` while claiming to prove the opposite.
Nothing git-tracked is touched: each concept is written under ``tmp_path``.
"""
example = persona.load_falsification_example()
assert len(example.concepts) == 2, "the example must keep both the readable and unreadable case"
seen_states = set()
for concept in example.concepts:
path = tmp_path / f"{concept.concept_id}.md"
path.write_text(concept.frontmatter_verbatim, encoding="utf-8")
# The plan's letter: ``read_provenance`` + ``trust_tier``, never ``evidence_for`` with a
# non-default key. That is not a stylistic choice — the example itself splits the two,
# putting state/reason/items_seen under ``evidence`` and the tier under ``derived``,
# because ``FalsificationEvidence.tier`` is only meaningful for the ONE key SPEC §5.3
# tiers. Branching on the primitive's own documented three-way return is test-level
# dispatch, not a second copy of production logic.
# P13b: the example's UNREADABLE concept is now READ, and that is a live divergence
# between this repo and the commons-owned artefact rather than a defect on either side.
# The example declares `state: unreadable, reason: block-sequence, items_seen: 2` for a
# SPEC §5.1 block sequence; po widened its reader to that carrier because all four
# delivered knowledge bases write it and nothing else (measured 2026-09-12: n100 446/446,
# n200 1133/1133, n500 270/270, r761 2756/2756 = 4605/4605, 0 in flow form). `shared/` is a
# PULL-ONLY subtree, so the declaration cannot be corrected from here: closing this needs a
# commons amendment, and the divergence is asserted rather than skipped so it cannot sit
# unnoticed until someone reads the prose.
#
# What still round-trips is the COUNT, and that is the discriminating half: the example
# says two entries were seen and po's reader returns exactly two, so a reader that lost an
# entry, kept one, or invented a key that is not a key still fails here.
result = okf.read_provenance(path, "sources")
assert isinstance(result, tuple), concept.concept_id
assert len(result) == concept.items_seen, concept.concept_id
if concept.state == "present":
assert concept.reason is None, concept.concept_id
else:
assert concept.reason == "block-sequence", (
"the only DECLARED-unreadable shape po now reads is the block sequence; any other "
f"reason is a real regression, got {concept.reason!r} for {concept.concept_id}"
)
# The verified half goes through the SHIPPED ``evidence_for`` on its DEFAULT key — the
# path where the tier derivation is the one §5.3 defines.
verified = okf.evidence_for(path)
assert okf.trust_tier(verified.entries) == concept.trust_tier, concept.concept_id
assert okf.adjudication_for(path) == concept.adjudication, concept.concept_id
seen_states.add(concept.state)
assert seen_states == {"present", "unreadable"}, (
"the example must DECLARE both a readable and an unreadable concept -- one of each is what "
f"makes the round trip discriminating, got {sorted(seen_states)}. Since P13b po READS both "
"(the declared-unreadable one is a SPEC 5.1 block sequence); the declaration is the "
"commons artefact's and can only be corrected there, see the comment above"
)