Placement settled by catalog's own reading of upstream at the pinned commit
3fcbb9f: SS8:509-510 and SS12:773-775 both put `okf_version` in a bundle-root
`index.md` frontmatter block, and SS12 calls it the only place frontmatter is
permitted in an index. Catalog's spec says the opposite about the same file;
that divergence is theirs against upstream, and we conform to upstream.
The value never touches a profile. `OKF_V0_2.index.root_frontmatter` names the
key; the caller supplies the value through a new keyword-only
`root_frontmatter_values` mapping. That keeps V4/V-A5 intact - `okf_version`'s
value tracks the upstream Google version and belongs to catalog (E1), so a
constant here would claim a decision we do not own and would have to be chased
on every upstream release. In the fixture the value is fixture DATA
(`okf-version.txt`), not a literal in our source.
Ordering comes from the policy, not the caller's mapping: a dict preserves
insertion order, so two callers passing the same keys would otherwise emit
different bytes. A key the policy does not name is refused fail-fast, before
any disk mutation. Omitting the argument emits no block at all - SS12 is a MAY
and none of upstream's four reference bundles declares the key.
The block is written only when the index is CREATED, so a re-run into an
existing bundle stays byte-identical (A-E5).
Raw-byte assertions rather than parsed ones, on the committed fixture as well
as on fresh runs: catalog measured that a quoted value fails their shape regex
with exit 1 and that a BOM hides the marker while still exiting 0.
`yaml.safe_load` returns "0.2" either way and strips a BOM first, so a parsed
assertion masks exactly those two defects. Asserting the frozen fixture catches
what a self-comparison cannot - regenerating from a broken emitter moves both
sides together.
A-E6 is now placement-explicit (promised catalog in 99cf987), and separates the
two byte properties: BOM-free is a property of the file, unquoted is a property
of CATALOG'S GATE and not of OKF v0.2 - upstream's own SS12 example is quoted,
so their gate rejects the spec's canonical form.
README gains the upstream-version section it was missing; CLAUDE.md gains the
mechanism behind "no profile hard-codes an upstream version": a profile names a
key, a caller owns its value.
550 -> 559 tests. test_profile_threading's `OKF_V0_2.index is DEFAULT.index`
assertion is replaced rather than deleted: object identity was a proxy for "the
shipped profiles differ in no NAME-bearing field", which is what makes the
synthetic test profile necessary, so the guard now asserts that directly.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013dgkSPjkLpACjMayd9R5jx
215 lines
7.9 KiB
Python
215 lines
7.9 KiB
Python
"""D5's emission half: the root `index.md` frontmatter block.
|
|
|
|
Upstream §8 binds `index.md` to "no frontmatter, with one exception: a
|
|
bundle-root `index.md` MAY carry an `okf_version` key (§12)", and §12 puts the
|
|
declaration "in a bundle-root `index.md` frontmatter block (the only place
|
|
frontmatter is permitted in an `index.md`)". Catalog verified both quotations
|
|
against the pinned commit `3fcbb9f` on 2026-07-31 and reported the same
|
|
reading, so placement is settled: frontmatter, not a body line.
|
|
|
|
The value never comes from a profile. V4/V-A5 gives `okf_version`'s *value* to
|
|
catalog (decision E1) and leaves this library the narrower obligation — name
|
|
the key, express any value. So the profile pins `root_frontmatter` and the
|
|
caller supplies the mapping.
|
|
|
|
Two assertions here read RAW BYTES rather than a parsed value, deliberately.
|
|
Catalog measured that a quoted value fails their shape regex with `exit 1`, and
|
|
that a UTF-8 BOM makes the marker invisible to them while still exiting `0`. A
|
|
parsed assertion masks exactly those two defects: `yaml.safe_load` returns the
|
|
string `"0.2"` whether or not it was quoted, and strips a BOM before the
|
|
caller ever sees it.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
from dataclasses import replace
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
from llm_ingestion_okf.errors import MaterializationError
|
|
from llm_ingestion_okf.materialize import materialize_bundle
|
|
from llm_ingestion_okf.profiles import DEFAULT, OKF_V0_2
|
|
|
|
INGESTED_AT = "2026-07-16T12:00:00Z"
|
|
|
|
|
|
def build_case(tmp_path: Path) -> Path:
|
|
"""A minimal file-source manifest; the frontmatter block is what is under
|
|
test, so the extraction is kept as small as it can be."""
|
|
case = tmp_path / "case"
|
|
fixture = case / "fixture"
|
|
fixture.mkdir(parents=True)
|
|
(fixture / "rows.csv").write_text("id,label\n1,alpha\n", encoding="utf-8")
|
|
manifest = {
|
|
"manifest_version": 1,
|
|
"source": {"type": "file", "id": "root-fm", "root": "fixture"},
|
|
"bundle_summary": "Root frontmatter case.",
|
|
"extractions": [
|
|
{
|
|
"id": "rows",
|
|
"title": "Rows",
|
|
"query": "rows.csv",
|
|
"okf_type": "dataset",
|
|
"max_rows": 10,
|
|
}
|
|
],
|
|
}
|
|
(case / "manifest.json").write_text(json.dumps(manifest), encoding="utf-8")
|
|
return case
|
|
|
|
|
|
def test_root_index_carries_the_declared_key(tmp_path: Path) -> None:
|
|
"""The block opens the file and the body follows it."""
|
|
case = build_case(tmp_path)
|
|
bundle = tmp_path / "bundle"
|
|
|
|
materialize_bundle(
|
|
case / "manifest.json",
|
|
bundle,
|
|
INGESTED_AT,
|
|
profile=OKF_V0_2,
|
|
root_frontmatter_values={"okf_version": "0.2"},
|
|
)
|
|
|
|
assert (bundle / "index.md").read_bytes() == (
|
|
b"---\nokf_version: 0.2\n---\n\nRoot frontmatter case.\n- [Rows](ingest-rows.md)\n"
|
|
)
|
|
|
|
|
|
def test_the_value_is_unquoted_in_the_raw_bytes(tmp_path: Path) -> None:
|
|
"""Catalog's shape regex `/^\\d+(\\.\\d+)*$/` sees the quotes as part of the
|
|
value and exits 1. Asserted on bytes: a parsed check passes either way."""
|
|
case = build_case(tmp_path)
|
|
bundle = tmp_path / "bundle"
|
|
|
|
materialize_bundle(
|
|
case / "manifest.json",
|
|
bundle,
|
|
INGESTED_AT,
|
|
profile=OKF_V0_2,
|
|
root_frontmatter_values={"okf_version": "0.2"},
|
|
)
|
|
|
|
raw = (bundle / "index.md").read_bytes()
|
|
assert b"okf_version: 0.2\n" in raw
|
|
assert b'okf_version: "0.2"' not in raw
|
|
assert b"okf_version: '0.2'" not in raw
|
|
|
|
|
|
def test_the_file_carries_no_utf8_bom(tmp_path: Path) -> None:
|
|
"""A BOM leaves the marker invisible to catalog's gate while still exiting
|
|
0 — the failure that reports success, so it gets its own byte assertion."""
|
|
case = build_case(tmp_path)
|
|
bundle = tmp_path / "bundle"
|
|
|
|
materialize_bundle(
|
|
case / "manifest.json",
|
|
bundle,
|
|
INGESTED_AT,
|
|
profile=OKF_V0_2,
|
|
root_frontmatter_values={"okf_version": "0.2"},
|
|
)
|
|
|
|
raw = (bundle / "index.md").read_bytes()
|
|
assert not raw.startswith(b"\xef\xbb\xbf")
|
|
assert raw.startswith(b"---\n")
|
|
|
|
|
|
def test_a_key_the_policy_does_not_name_is_refused(tmp_path: Path) -> None:
|
|
"""`DEFAULT` names no root-frontmatter key, so offering one is a caller
|
|
error rather than something to write. Fail-fast: refused BEFORE any disk
|
|
mutation, or a rejected run would still leave a half-written bundle."""
|
|
case = build_case(tmp_path)
|
|
bundle = tmp_path / "bundle"
|
|
|
|
with pytest.raises(MaterializationError) as excinfo:
|
|
materialize_bundle(
|
|
case / "manifest.json",
|
|
bundle,
|
|
INGESTED_AT,
|
|
profile=DEFAULT,
|
|
root_frontmatter_values={"okf_version": "0.2"},
|
|
)
|
|
|
|
assert excinfo.value.code == "index_root_frontmatter_unexpected"
|
|
assert not bundle.exists()
|
|
|
|
|
|
def test_omitting_the_values_emits_no_frontmatter(tmp_path: Path) -> None:
|
|
"""§12 is a MAY, and none of upstream's four reference bundles declares the
|
|
key at all (catalog grepped `okf/bundles` and `okf/samples`: zero hits). A
|
|
profile that names the key must therefore still emit a bundle without it."""
|
|
case = build_case(tmp_path)
|
|
bundle = tmp_path / "bundle"
|
|
|
|
materialize_bundle(case / "manifest.json", bundle, INGESTED_AT, profile=OKF_V0_2)
|
|
|
|
raw = (bundle / "index.md").read_bytes()
|
|
assert not raw.startswith(b"---")
|
|
assert b"okf_version" not in raw
|
|
|
|
|
|
def test_key_order_follows_the_policy_not_the_mapping(tmp_path: Path) -> None:
|
|
"""The policy pins the order; a caller's dict ordering must not reach the
|
|
file, or two callers passing the same keys would emit different bytes."""
|
|
case = build_case(tmp_path)
|
|
bundle = tmp_path / "bundle"
|
|
profile = replace(
|
|
OKF_V0_2,
|
|
index=replace(OKF_V0_2.index, root_frontmatter=("okf_version", "bundle_profile")),
|
|
)
|
|
|
|
materialize_bundle(
|
|
case / "manifest.json",
|
|
bundle,
|
|
INGESTED_AT,
|
|
profile=profile,
|
|
root_frontmatter_values={"bundle_profile": "okf-v0-2", "okf_version": "0.2"},
|
|
)
|
|
|
|
raw = (bundle / "index.md").read_bytes()
|
|
assert raw.startswith(b"---\nokf_version: 0.2\nbundle_profile: okf-v0-2\n---\n\n")
|
|
|
|
|
|
def test_the_frozen_golden_declares_the_version_gate_safely() -> None:
|
|
"""D5's raw-byte guard, asserted on the COMMITTED fixture rather than on a
|
|
fresh run.
|
|
|
|
`test_golden.py` already compares a run against these bytes, but that only
|
|
proves the code agrees with itself: regenerate the fixture from a quoted or
|
|
BOM-carrying emitter and both sides move together, silently. This asserts
|
|
the shape of what we froze and hand to catalog's gate — the one thing a
|
|
self-comparison cannot catch.
|
|
"""
|
|
raw = (
|
|
Path(__file__).parent.parent
|
|
/ "examples"
|
|
/ "ingest-golden-okf-v0-2"
|
|
/ "expected-bundle"
|
|
/ "index.md"
|
|
).read_bytes()
|
|
|
|
assert raw.startswith(b"---\nokf_version: 0.2\n---\n\n")
|
|
assert not raw.startswith(b"\xef\xbb\xbf") # a BOM exits 0 while hiding the marker
|
|
assert b'"' not in raw.split(b"---\n", 2)[1] # a quoted value exits 1
|
|
assert b"\r\n" not in raw # LF-only, like every other emitted file
|
|
|
|
|
|
def test_reingest_in_place_is_byte_identical(tmp_path: Path) -> None:
|
|
"""A-E5. The second run goes into the FIRST run's directory — the shape the
|
|
pilot runs — so the frontmatter block must survive §6 index maintenance
|
|
rather than being appended a second time or dropped."""
|
|
case = build_case(tmp_path)
|
|
bundle = tmp_path / "bundle"
|
|
call = dict(
|
|
profile=OKF_V0_2,
|
|
root_frontmatter_values={"okf_version": "0.2"},
|
|
)
|
|
|
|
materialize_bundle(case / "manifest.json", bundle, INGESTED_AT, **call) # type: ignore[arg-type]
|
|
first = (bundle / "index.md").read_bytes()
|
|
materialize_bundle(case / "manifest.json", bundle, INGESTED_AT, **call) # type: ignore[arg-type]
|
|
|
|
assert (bundle / "index.md").read_bytes() == first
|