"""P13b: a BLOCK sequence of mappings is provenance po can read, not provenance po cannot. RED-FIRST, measured 2026-09-12 with the full denominator: every concept file in all four delivered knowledge bases writes ``sources`` as a BLOCK sequence — n100 446/446, n200 1133/1133, n500 270/270, r761 2756/2756, **4605 of 4605, and 0 in flow form**. ``read_provenance`` answered ``UnreadableProvenance(reason="block-sequence")`` for every one of them, so ``evidence_for`` reported ``state="unreadable"`` on 4605 of 4605 documents: the falsification layer had no address for any document in any base po is about to be stress-tested against. **This widens the READER and nothing else.** The producer still EMITS flow (measured: okf's ``materialize._render_sources`` is unchanged in 0.8.5), so no bundle bytes move, and ``write_concept_file``/``verified_field`` still refuse what the flow decoder refuses — the writer keeps refusing exactly what the reader could not read, which is the property the round-trip gate exists for. What changes is that a form the producer's own SPEC §5.1 documents, and that four delivered bases actually use, stops being reported as unreadable. **Two refusals are KEPT, and they are this file's discriminators.** ``block-mapping`` (an indented continuation with no ``- `` item) and ``unsupported-flow`` (a single-line value the flow decoder refuses) must still come back as ``UnreadableProvenance``. Without those arms a reader widened to "anything indented is entries" would pass every positive arm here while inventing entries the document does not have — the repo's vacuous-gate class. The grammar is po's OWN, never okf's imported: the pair separator is colon-SPACE (``_find_pair_separator``, the rule ``decode_flow_value`` already uses), because two separator rules in one module are two answers to one question. okf's ``consume.read_sources`` was read for the FORM and not called — po calls no okf reader, which is measured and deliberate (P13 § 1a). """ from __future__ import annotations from pathlib import Path import pytest from portfolio_optimiser import okf _FIXTURES = Path(__file__).resolve().parent / "fixtures" / "p13b-block-sources" #: A byte-identical copy of a REAL delivered concept file (n100-2023, `krav/N100/ #: id-2b69893f-…md`, sha1 8c2952d1…). Read from the producer's tree, never written there. A #: hand-written approximation would prove the reader handles what I imagined the form to be. _REAL = _FIXTURES / "n100-krav-4-2-5-1-3.md" def _write(tmp_path: Path, frontmatter: str) -> Path: path = tmp_path / "c.md" path.write_text(f"---\n{frontmatter}\n---\n\nbody\n", encoding="utf-8") return path def test_the_real_delivered_concept_file_yields_its_address() -> None: """The measurement this whole row exists for, against the actual bytes on disk.""" entries = okf.read_provenance(_REAL, "sources") assert isinstance(entries, tuple), f"still unreadable: {entries!r}" assert entries == ( { "resource": "https://viewers.vegnorm.vegvesen.no/api/nisosts/859984?languageCode=nb", "title": "N100:2023", }, ) def test_evidence_for_reports_present_on_the_real_file() -> None: """The three-state answer a falsification verdict acts on, end to end. ``tier`` stays ``None`` because ``sources`` is not the key SPEC §5.3 tiers — the B4 rule, unchanged by this widening.""" evidence = okf.evidence_for(_REAL, "sources") assert evidence.state == "present" assert evidence.items_seen == 1 assert evidence.tier is None assert okf.evidence_notice(evidence) is None def test_a_two_entry_block_sequence_keeps_both_entries(tmp_path: Path) -> None: """More than one address, so the positive arm cannot pass on a reader that returns the first.""" path = _write( tmp_path, "type: concept\n" "sources:\n" " - resource: https://a.example/doc?q=1\n" " title: A\n" " - resource: https://b.example/doc\n" " id: b\n", ) assert okf.read_provenance(path, "sources") == ( {"resource": "https://a.example/doc?q=1", "title": "A"}, {"resource": "https://b.example/doc", "id": "b"}, ) def test_the_key_after_a_block_sequence_is_still_read(tmp_path: Path) -> None: """The continuation must stop at the first unindented line, or the reader swallows the rest of the frontmatter into the last entry.""" path = _write( tmp_path, "type: concept\nsources:\n - resource: https://a.example/d\nstatus: stable\n", ) assert okf.read_provenance(path, "sources") == ({"resource": "https://a.example/d"},) assert okf.parse_frontmatter(path)["status"] == "stable" def test_a_value_carrying_a_colon_survives_whole(tmp_path: Path) -> None: """``title: N100:2023`` is one pair and the value keeps its own colon, as does a URL's ``://``. **This arm is a REGRESSION GUARD, not a discriminator, and the distinction was measured rather than assumed.** It was written claiming to prove the colon-SPACE rule, and mutation M5 (separator changed to the FIRST colon) left it GREEN: the first colon in ``resource: https://…`` and in ``title: N100:2023`` is the same one colon-SPACE finds, so the two rules agree on every value shaped like a delivered one. The arm that actually tells them apart is ``test_an_item_with_no_pair_separator_is_refused`` — under first-colon, ``- https://a.example/d`` decodes to ``{'https': '//a.example/d'}``, a key invented out of a URL scheme. Kept because it pins the values four delivered bases actually carry; labelled honestly because a test that cannot separate two implementations proves nothing about them.""" path = _write( tmp_path, "type: concept\nsources:\n - resource: https://x.example/a\n title: N100:2023\n", ) assert okf.read_provenance(path, "sources") == ( {"resource": "https://x.example/a", "title": "N100:2023"}, ) # --- the KEPT refusals: the discriminators ---------------------------------------------------- def test_a_block_mapping_is_still_unreadable(tmp_path: Path) -> None: """No ``- `` item, so no entry was opened. Folding it into one would invent an entry the document does not carry.""" path = _write(tmp_path, "type: concept\nverified:\n by: someone\n at: 2026-01-01\n") result = okf.read_provenance(path, "verified") assert isinstance(result, okf.UnreadableProvenance) assert result.reason == "block-mapping" def test_an_indented_line_before_any_item_is_refused(tmp_path: Path) -> None: """The sharp case: a continuation that LOOKS like a sequence but opens with a bare pair. Refused rather than folded, which is where a naive block reader invents an entry. **A predicted reason was FALSIFIED by the measurement and is written down as measured.** This arm expected ``block-mapping``; the answer is ``block-sequence``, because the caller's reason is chosen by whether any ``- `` item is PRESENT, and one is — it simply does not decode. That is the honest label: ``block-mapping`` is reserved for a continuation carrying no item at all (the arm above), and relabelling this one would make the two indistinguishable.""" path = _write( tmp_path, "type: concept\nsources:\n title: stray\n - resource: https://a.example/d\n", ) result = okf.read_provenance(path, "sources") assert isinstance(result, okf.UnreadableProvenance) assert result.reason == "block-sequence" assert result.items_seen == 1 def test_an_item_with_no_pair_separator_is_refused(tmp_path: Path) -> None: """``- just-a-scalar`` names a resource without saying so — the same refusal the flow decoder makes for a bare scalar entry, so the two carriers cannot disagree about what an entry is. **This is the ONE arm that witnesses the colon-SPACE rule** (measured by mutation M5): a reader splitting on the FIRST colon reads this item as ``{'https': '//a.example/d'}``, inventing a key out of a URL scheme instead of refusing. Every delivered ``resource`` is a URL, so that reader would mint 4605 such keys rather than fail once.""" path = _write(tmp_path, "type: concept\nsources:\n - https://a.example/d\n") result = okf.read_provenance(path, "sources") assert isinstance(result, okf.UnreadableProvenance) assert result.reason == "block-sequence" def test_an_unsupported_flow_value_is_still_unreadable(tmp_path: Path) -> None: """The other kept refusal: a single-line value the flow decoder rejects never reaches the block path at all.""" path = _write(tmp_path, "type: concept\nsources: [not-a-mapping]\n") result = okf.read_provenance(path, "sources") assert isinstance(result, okf.UnreadableProvenance) assert result.reason == "unsupported-flow" def test_an_absent_key_is_still_none(tmp_path: Path) -> None: """Absence is ``None``, never an empty tuple — the F2 principle one layer down, and the control that keeps "unreadable" and "absent" from collapsing into each other.""" path = _write(tmp_path, "type: concept\ntitle: t\n") assert okf.read_provenance(path, "sources") is None def test_the_flow_form_still_decodes(tmp_path: Path) -> None: """Control: widening the reader to the block form must not cost the flow form, which is what every commons-owned fixture and the producer's own emitter still write.""" path = _write(tmp_path, "type: concept\nsources: [{ id: a, resource: https://a.example/d }]\n") assert okf.read_provenance(path, "sources") == ({"id": "a", "resource": "https://a.example/d"},) @pytest.mark.parametrize("key", ["verified", "sources", "attester"]) def test_the_block_reader_is_key_agnostic(tmp_path: Path, key: str) -> None: """Whatever key carries the sequence, the same grammar reads it — never a hard-coded ``{id, resource}``, which would refuse the very shapes this seam exists for (the B4 rule).""" path = _write(tmp_path, f"type: concept\n{key}:\n - by: a\n at: 2026-01-01\n") assert okf.read_provenance(path, key) == ({"by": "a", "at": "2026-01-01"},) # --- the FLOW-mapping item: found by measuring my own reader, not by a failing arm ------------- # # A block sequence whose ITEMS are flow mappings (``- { id: a, resource: x }``) is SPEC-canonical # and is exactly what ``tests/golden/block-form-provenance`` writes for ``verified``. The first # block reader decoded it as ``{'{ id': 'a, resource: https://x/y }'}`` — a key that is not a key — # which is the "invent an entry the document does not carry" failure its own docstring forbids. No # arm caught it: on ``verified`` the §5.2 "must name an actor" rule refused it for an unrelated # reason, so the fixture that carries the shape was shielded by accident. Measured, then fixed. def test_a_block_item_that_is_a_flow_mapping_is_decoded_by_the_flow_decoder(tmp_path: Path) -> None: """One decoder, two carriers: the item goes through ``decode_flow_value``, so the block and flow spellings of the same entry cannot come back different.""" path = _write( tmp_path, "type: concept\nsources:\n - { id: a, resource: https://x.example/y }\n" ) assert okf.read_provenance(path, "sources") == ({"id": "a", "resource": "https://x.example/y"},) def test_two_flow_mapping_items_keep_their_order(tmp_path: Path) -> None: """The order-sensitive case the fixture's own prose calls the whole point: a reader that kept the last entry it saw would report the wrong actor first.""" path = _write( tmp_path, "type: concept\nverified:\n" " - { by: human:a, at: 2026-09-02T09:00:00Z }\n" " - { by: process:b, at: 2026-09-02T10:00:00Z }\n", ) assert okf.read_provenance(path, "verified") == ( {"by": "human:a", "at": "2026-09-02T09:00:00Z"}, {"by": "process:b", "at": "2026-09-02T10:00:00Z"}, ) def test_a_flow_item_with_a_bare_continuation_is_refused(tmp_path: Path) -> None: """Mixing the two spellings within one entry is refused rather than merged — merging would decide, silently, which carrier wins for a document that used both.""" path = _write( tmp_path, "type: concept\nsources:\n - { id: a, resource: https://x.example/y }\n title: T\n", ) result = okf.read_provenance(path, "sources") assert isinstance(result, okf.UnreadableProvenance) assert result.reason == "block-sequence" def test_a_malformed_flow_item_is_refused_not_guessed(tmp_path: Path) -> None: """The discriminator against the defect this block exists for: an item the flow decoder refuses must come back unreadable, never as a key that is not a key.""" path = _write(tmp_path, "type: concept\nsources:\n - { id a, resource x }\n") result = okf.read_provenance(path, "sources") assert isinstance(result, okf.UnreadableProvenance) assert result.reason == "block-sequence"