One grammar, four call sites. `read_block_mappings` moves out of
`consume.read_sources` -- where it was written and measured -- into
`profiles`, the module both the flat readers and `consume` already import,
and the three copies of the line-oriented frontmatter grammar now decode a
block sequence for the keys `STRUCTURED_BLOCK_KEYS` names. Two copies of a
block grammar would be two answers to one question.
The value TYPE was the real choice and it was measured, not argued.
`parse_frontmatter` is public API (`okf.parse_frontmatter`) returning
`dict[str, str]`, and a list of mappings is not a `str`. Widening the return
type to `str | list[dict[str, str]]` costs 15 `mypy --strict` errors across
four of the five modules that touch the reader, plus a signature every
caller outside this repository would have to follow. Rendering the entries
back into the flow form those same readers already round-trip costs 0. The
rendering is a READING projection and says so: it is not a claim that the
value is writable -- `yaml_flow_plain` still refuses a `?` and the guard
still refuses a quote inside a flow mapping, which is why the producer
writes block in the first place.
`STRUCTURED_BLOCK_KEYS` is one key wide. `sources` is the key `read_sources`
already knows how to read; a fixture in this tree carries a block
`verified:` that still reads as an empty value, and a test pins that state
so the next widening is a decision rather than a side effect.
Nothing nested reaches the document's namespace: the entries land inside
their own value, and the K3-20 substitution guarantee is asserted per reader
copy.
Three tests that pinned the old behaviour are rewritten to what is now true,
none weakened on its other half: the block round trip in
`test_multi_source_provenance` (the evidence behind `_render_sources`'
reason 1), the v0.2 characterization (whose key-space assertion is the half
that must never weaken), and K3-22's shipped-file known-positive, where the
one difference is counted and pinned at 1.
Suite 1807 passed / 1 skipped, rc 0, 94 s -- 1782/1 before plus 25 new.
ruff clean, `mypy --strict` clean over 21 files, `uv.lock` untouched.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`consume.read_sources` reads both YAML forms. This library's three copies of
the line-oriented frontmatter grammar read only the flow one, and on a block
sequence they return the key with an EMPTY value -- not a `KeyError` a
consumer can catch, an address that disappears.
Measured 2026-09-12 over four bundles a producer ships, denominator = files
carrying a frontmatter block: 2 756 of 2 757, 446 of 447, 1 133 of 1 134 and
270 of 271 concept files lose the address through the flat readers, while
PyYAML 6.0.3 and the pinned guard 1.4.0 both read it on 100 % of the same
files.
The bar is dict EQUALITY against two independent readers rather than "it
parses". The two disagree on one axis and it is named rather than averaged:
the guard keeps a leaf's quotes verbatim, PyYAML decodes them, and this
library follows `read_sources`' K3-22 rule.
Red: 13 failed, 12 passed. The 12 are the known-positive controls and the
known-negatives the fix may not move -- the nested-`title:` substitution trap
above all, plus the flow form, an absent key, and a shape `read_sources`
refuses.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>