feat(okf): read_provenance names WHY a signal is unreadable, on the SkippedLink shape
Co-Authored-By: Claude <claude-opus-5>
This commit is contained in:
parent
940796d9ed
commit
7552be239b
2 changed files with 216 additions and 9 deletions
|
|
@ -29,6 +29,7 @@ never written.
|
|||
|
||||
from __future__ import annotations
|
||||
|
||||
import itertools
|
||||
import json
|
||||
import posixpath
|
||||
import re
|
||||
|
|
@ -370,6 +371,96 @@ class SkippedLink:
|
|||
reason: SkipReason
|
||||
|
||||
|
||||
#: 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
|
||||
#: mapping are both CONFORMANT OKF (SPEC §5.2 writes ``verified`` in exactly those forms); they are
|
||||
#: simply outside the accepted single-line subset, and this decoder is not entitled to an opinion
|
||||
#: about whether the author erred. ``unsupported-flow`` is the one token that does denote a
|
||||
#: malformation, and it says so by naming the flow FORM rather than the author.
|
||||
ProvenanceReason = Literal["block-sequence", "block-mapping", "unsupported-flow"]
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class UnreadableProvenance:
|
||||
"""A provenance key that IS present and could NOT be read, and why — never silence.
|
||||
|
||||
Mirrors ``SkippedLink``: structured rather than a rendered string, for the reason
|
||||
``BudgetExceeded`` carries ``kind``/``limit``/``observed`` as fields (kø-(y)). "Which shape is
|
||||
this written in" and "how many entries were there" are two separate operative questions, and a
|
||||
caller forced to re-parse a token to tell them apart has been handed a diagnostic it cannot act
|
||||
on. That is why the COUNT is its own field and is never folded into the token.
|
||||
|
||||
``value`` is the offending text VERBATIM as written in the file, indentation included — the
|
||||
operator fixing the document edits that text, and a normalised form would send them looking for
|
||||
a string their file does not contain."""
|
||||
|
||||
#: Path of the document the key was read from.
|
||||
file: str
|
||||
#: The frontmatter key that could not be read.
|
||||
key: str
|
||||
#: The offending text exactly as it appears in that file.
|
||||
value: str
|
||||
reason: ProvenanceReason
|
||||
#: Entries seen. ``0`` when nothing countable was there — an empty key, or a flow value the
|
||||
#: decoder refused and therefore never enumerated.
|
||||
items_seen: int
|
||||
|
||||
|
||||
def read_provenance(
|
||||
path: str | Path, key: str
|
||||
) -> tuple[dict[str, str], ...] | UnreadableProvenance | None:
|
||||
"""Read one provenance key into entries, or say why it could not be read.
|
||||
|
||||
Three outcomes, and the three-way split is the whole point:
|
||||
|
||||
* ``None`` — the document does not carry the key. **Absence is ``None``, never a default**:
|
||||
the F2 principle one layer down, where an unread signal must not become an asserted absent
|
||||
one.
|
||||
* a tuple of entries — the value was in the accepted flow subset and decoded.
|
||||
* ``UnreadableProvenance`` — the key is THERE and could not be read, with the shape and the
|
||||
count that say what was seen.
|
||||
|
||||
Driven by ``_split_frontmatter``: this is a second READER of the one parse, never a second
|
||||
parser. Only top-level (unindented) frontmatter lines are considered as key sites, so an
|
||||
indented ``by:`` inside a block entry can never be mistaken for a document-level key.
|
||||
|
||||
Gated by ``tests/test_provenance_decoder_loadbearing.py``."""
|
||||
lines = _split_frontmatter(Path(path).read_text(encoding="utf-8"))[0]
|
||||
for i, line in enumerate(lines):
|
||||
if line[:1] in (" ", "\t"):
|
||||
continue
|
||||
name, sep, raw = line.partition(":")
|
||||
if not sep or name.strip() != key:
|
||||
continue
|
||||
value = raw.strip()
|
||||
if value:
|
||||
try:
|
||||
return decode_flow_value(value, key=key)
|
||||
except FlowDecodeError:
|
||||
return UnreadableProvenance(
|
||||
file=str(path), key=key, value=value, reason="unsupported-flow", items_seen=0
|
||||
)
|
||||
continuation = list(itertools.takewhile(lambda ln: ln[:1] in (" ", "\t"), lines[i + 1 :]))
|
||||
if not continuation:
|
||||
return UnreadableProvenance(
|
||||
file=str(path), key=key, value="", reason="block-mapping", items_seen=0
|
||||
)
|
||||
# An ITEM is a continuation line whose STRIPPED form opens with ``- ``. A ``- `` occurring
|
||||
# inside a value is text, not an item, and counting it would inflate the number the caller
|
||||
# acts on.
|
||||
items = sum(1 for ln in continuation if ln.strip().startswith("- "))
|
||||
return UnreadableProvenance(
|
||||
file=str(path),
|
||||
key=key,
|
||||
value="\n".join(continuation),
|
||||
# SPEC §5.2's one-element MUST: a bare mapping IS one entry, so a mapping that is
|
||||
# present counts 1 rather than 0 — 0 is reserved for "nothing was there at all".
|
||||
reason="block-sequence" if items else "block-mapping",
|
||||
items_seen=items or 1,
|
||||
)
|
||||
return None
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Bundle:
|
||||
"""A navigated OKF bundle: ``index.md`` plus every cross-linked file that resolves — and, in
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue