llm-ingestion-okf/tests/test_okf_v0_2_characterization.py
Kjell Tore Guttormsen 8318605e34 feat(profiles): DEFAULT stamps commons' O2 generated, V1 executed
Commons ratified V1 2026-08-02 and executed it at `54e0ec7`; verified
against their tree rather than taken on report. ingest-spec.md:217 now
defines `generated` as `{ by: process:okf-ingest, at: <ingested_at> }`,
unquoted, `at` repeating `ingested_at` verbatim. `generated: true` no
longer appears in the spec.

`DEFAULT` states commons' §5 layer, so its stamp is theirs to decide.
`DEFAULT.ownership` gains the actor; the four goldens this repo's plan
named in advance were regenerated by RUNNING the materializer, each on
its own case's `ingested-at.txt`. The v0.2 golden was untouched, as
predicted -- it has carried the O2 form since D5.

Not a migration onto OKF v0.2: `DEFAULT` stays v0.1 on every axis
upstream owns and still emits no `sources`. Commons' spec and the Google
version are independent axes, and comments that narrated them as one
were rewritten rather than left to mislead. README and CLAUDE.md said
the additive rule without that boundary, which would have told a
consumer their DEFAULT bytes can never move; both now state it.

V-A3 is amended, not dropped. `DEFAULT` must OWN the mapping it now
writes -- a profile refusing its own output fires the collision gate on
files its own previous run wrote -- while a mapping naming a foreign
actor, or §7's `human:` actor on curated content, stays unowned. That
half is what carried the safety and it is asserted directly.

§11's stamp-integrity condition moved with the value: the forgeable
stamp was `true` and is now the mapping naming the ingest actor. The
defence was never the value -- the §3 scan globs `ingest-*.md`, so a
Door C import is unreachable however well it forges. Second spoof test
added; both were hand-mutated (glob widened to `*.md`) to confirm they
can fail.

The characterization test derived its foreign-stamp fixture from the
literal `generated: true`, which V1 leaves without a referent -- a
silent no-op waiting to happen. It now derives the needle from the
profile and asserts the substitution occurred.

Door B is deliberately untouched: not the ingest-spec's, marker is
`generated` + `source_file`, disjoint from Door A's `ingest_manifest`,
and the divergence predates V1.

Nothing released or notified. The pilot set pins `v0.5.0a2`, not `main`,
so this is invisible to portfolio-optimiser's freeze and demo; the
consumer exposure report is owed at the release that carries this.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VwcjUXbKySLbEG5WqTNkta
2026-08-09 12:29:05 +02:00

351 lines
15 KiB
Python

"""The safety net under OKF v0.2 support (plan steps 1-3).
Every test here characterizes behavior this library ALREADY has, before any
v0.2 code exists. Nothing is changed by them; that is the point. They are
written first because the one part of the v0.2 work we called risky —
`_is_ingest_owned`, the pre-mutation collision gate — is only safe to touch
with its current guarantees pinned.
Three groups, one per plan step:
1. **Characterization.** An inline flow mapping survives the scalar
`parse_frontmatter` verbatim (V-A2), a block list does not, and a v0.2
`generated` mapping can never make a file ingest-owned (V-A3). The last one
converts today's fail-safe from an accident into a documented guarantee:
a v0.2 file at a target name is refused, never overwritten.
2. **Door C consumer tolerance** (D3). OKF §14 forbids a consumer to reject on
five things; Door C is a consumer in that sense. One case per MUST NOT, plus
the §5.2 bare-`verified` mapping.
3. **No profile hard-codes an upstream version** (V4/V-A5). The `okf_version`
VALUE belongs to catalog (decision E1); a profile names the key and expresses
any value, which is what keeps "always the latest OKF version" from turning
into a constant this repo has to chase.
"""
from __future__ import annotations
import dataclasses
import json
import re
from collections.abc import Iterator
from pathlib import Path
from typing import Any
import pytest
from llm_ingestion_okf.errors import MaterializationError
from llm_ingestion_okf.materialize import (
_is_ingest_owned,
materialize_bundle,
parse_frontmatter,
)
from llm_ingestion_okf.profiles import DEFAULT, STRICT_V1, BundleProfile, FrontmatterSchema
from test_import_flow import StubImportGate, place, run
INGESTED_AT = "2026-07-25T12:00:00Z"
# The v0.2 `generated` shape, as an inline flow mapping — the form A-E3 pins
# for the coming profile, and the form the §5 single-line MUST admits.
V0_2_GENERATED = "{ by: llm-ingestion-okf/0.4.0, at: 2026-07-25T12:00:00Z }"
# --- step 1: what the scalar parser already does --------------------------
def test_an_inline_flow_mapping_survives_the_scalar_parser_verbatim(tmp_path: Path) -> None:
"""V-A2. `parse_frontmatter` splits on the FIRST colon, so a flow mapping —
colons and all — comes back as one opaque string. Byte-exact both ways:
what the emitter wrote is what the parser returns.
"""
values = {"type": "dataset", "title": "Orders", "generated": V0_2_GENERATED}
path = tmp_path / "concept.md"
path.write_bytes(f"---\n{DEFAULT.frontmatter.emit(values)}\n---\n\nBody.\n".encode())
assert path.read_bytes().decode("utf-8").splitlines()[3] == f"generated: {V0_2_GENERATED}"
assert parse_frontmatter(path) == values
def test_a_block_list_pollutes_the_scalar_parsers_key_space(tmp_path: Path) -> None:
"""The measured reason `sources` is emitted as an inline flow sequence.
Upstream's canonical `sources` is a block list of multi-key mappings. Read
through this line-oriented parser, each item line becomes a KEY: the list
disappears and `- id` / `resource` appear as frontmatter keys that no
document declared. `_is_ingest_owned` reads through this same parser, so
emitting the block form would have forced the gate and the parser to be
hardened in one step.
"""
path = tmp_path / "concept.md"
path.write_bytes(
b"---\ntype: dataset\nsources:\n - id: margin-standard\n"
b" resource: policies/margin-standard.md\n---\n\nBody.\n"
)
parsed = parse_frontmatter(path)
assert parsed["sources"] == ""
assert parsed["- id"] == "margin-standard"
assert parsed["resource"] == "policies/margin-standard.md"
def _stamped_concept(generated: str) -> bytes:
return (
"---\ntype: dataset\ntitle: Orders\n"
"ingest_manifest: manifest@0123456789abcdef\n"
f"generated: {generated}\n---\n\nBody.\n"
).encode()
def test_a_v0_1_generated_true_is_ingest_owned(tmp_path: Path) -> None:
"""The control for the test below: the stamp as v0.1 writes it DOES own."""
path = tmp_path / "ingest-orders.md"
path.write_bytes(_stamped_concept("true"))
assert _is_ingest_owned(path, "manifest") is True
def test_a_v0_2_generated_mapping_is_never_ingest_owned(tmp_path: Path) -> None:
"""V-A3. `generated` as a v0.2 mapping is not the string `true`, so the
predicate returns False and the file is not ours to replace.
The manifest reference is readable and matches — the refusal comes from
`generated` alone. This is also why `generated` can never become the
ownership predicate on its own (U3): upstream writes it for HAND-AUTHORED
files, so presence proves nothing about who generated what.
"""
path = tmp_path / "ingest-orders.md"
path.write_bytes(_stamped_concept(V0_2_GENERATED))
assert parse_frontmatter(path)["ingest_manifest"] == "manifest@0123456789abcdef"
assert _is_ingest_owned(path, "manifest") is False
def test_a_v0_2_file_at_a_target_name_is_refused_not_overwritten(tmp_path: Path) -> None:
"""The fail-safe, end to end: not-owned means the §3 collision gate refuses
the run before any mutation. A v0.2 shape arriving in a bundle Door A also
writes into can cost a run; it can never cost data.
"""
src = tmp_path / "src"
src.mkdir()
manifest_path = src / "manifest.json"
manifest_path.write_text(
json.dumps(
{
"manifest_version": 1,
"source": {"type": "file", "id": "catalogue-1", "root": "data"},
"bundle_summary": "A test bundle.",
"extractions": [
{
"id": "orders",
"title": "Orders",
"query": "orders.csv",
"okf_type": "dataset",
"max_rows": 100,
}
],
}
),
encoding="utf-8",
)
(src / "data").mkdir()
(src / "data" / "orders.csv").write_text("a,b\n1,x\n", encoding="utf-8", newline="")
bundle = tmp_path / "bundle"
materialize_bundle(manifest_path, bundle, INGESTED_AT)
concept = bundle / "ingest-orders.md"
# Substitute the stamp THIS profile wrote for a foreign one. The needle is
# derived from the profile rather than spelled out because it moved once
# already (V1 took `DEFAULT` off `generated: true`), and a stale literal
# here would make `replace` a silent no-op — leaving the file carrying our
# own stamp and the test passing for a reason it does not describe. The
# assertion below is the guard against that happening again.
own_stamp = f"generated: {DEFAULT.ownership.stamp(INGESTED_AT)}".encode()
original = concept.read_bytes()
assert own_stamp in original
v0_2_bytes = original.replace(own_stamp, f"generated: {V0_2_GENERATED}".encode())
assert v0_2_bytes != original
concept.write_bytes(v0_2_bytes)
with pytest.raises(MaterializationError) as excinfo:
materialize_bundle(manifest_path, bundle, INGESTED_AT)
assert excinfo.value.code == "collision_unstamped"
assert concept.read_bytes() == v0_2_bytes
# --- step 2: Door C against the §14 consumer tolerance rules --------------
TOLERANCE_CASES = [
pytest.param(
"---\ntype: Concept\n---\n\nOnly a type.\n",
id="missing-optional-frontmatter",
),
pytest.param(
"---\ntype: Attested Computation\ntitle: Gross margin\n---\n\nA v0.2 type.\n",
id="unknown-type-value",
),
pytest.param(
"---\ntype: dataset\nstale_after: 2027-01-01\nnot_a_key_we_know: x\n---\n\nBody.\n",
id="unknown-additional-keys",
),
pytest.param(
"---\ntype: dataset\n---\n\nSee [the other one](does-not-exist.md).\n",
id="broken-cross-link",
),
pytest.param(
"---\ntype: dataset\nverified: { by: human:jsmith@acme, at: 2026-07-01T09:00:00Z }\n"
"---\n\nBody.\n",
id="bare-verified-mapping",
),
]
@pytest.mark.parametrize("document", TOLERANCE_CASES)
def test_door_c_merges_what_a_consumer_must_not_reject(tmp_path: Path, document: str) -> None:
"""D3/§14. Each case is one thing a conformant consumer MUST NOT reject.
Door C's tolerance is structural rather than lenient: it writes the guard's
bytes verbatim and never parses the sender's frontmatter, so there is no
place for a shape judgement to be made. That is what these cases pin — a
future reader (D1b) that starts judging frontmatter at this door would
break them, which is exactly when we want to hear about it.
The bare-`verified` case is the §5.2 MUST in its Door C form: the mapping is
persisted unmodified. Coercing it to a one-element list is the READER's
obligation and lands with D1b; nothing here reads it.
"""
place(tmp_path / "source", "notes/tolerated.md", document)
result, bundle = run(tmp_path, StubImportGate(), ingested_at=INGESTED_AT)
assert result.failed == ()
assert [entry.concept_path for entry in result.merged] == ["notes/tolerated.md"]
assert (bundle / "import-notes-tolerated.md").read_bytes() == document.encode("utf-8")
def test_door_c_does_not_require_the_source_bundle_to_carry_an_index(tmp_path: Path) -> None:
"""The fifth MUST NOT: a missing `index.md` is not grounds for rejection.
Door C generates the target index itself, so the sender's bundle need not
carry one — asserted explicitly here because every other test in the suite
omits the source index incidentally rather than as a stated rule.
"""
document = "---\ntype: dataset\n---\n\nNo index anywhere in the source.\n"
place(tmp_path / "source", "tables/users.md", document)
assert not (tmp_path / "source" / "index.md").exists()
result, bundle = run(tmp_path, StubImportGate(), ingested_at=INGESTED_AT)
assert result.failed == ()
assert [entry.concept_path for entry in result.merged] == ["tables/users.md"]
assert (bundle / "index.md").read_text(encoding="utf-8") == (
"- [tables/users](import-tables-users.md)\n"
)
# --- step 3: no profile hard-codes an upstream version --------------------
# A version LITERAL — `0.1`, `0.2`, `2026.7.3`. Compiled patterns are walked
# past deliberately: a regex is a shape, and a form gate for version values is
# the opposite of hard-coding one.
_VERSION_LITERAL_RE = re.compile(r"^\d+(\.\d+)+$")
def _strings_in(value: Any) -> Iterator[str]:
"""Every string reachable from a profile, through dataclasses and
collections alike. Recursive so that a version added to a nested policy —
the only place it could plausibly be added — is still found."""
if isinstance(value, str):
yield value
elif dataclasses.is_dataclass(value) and not isinstance(value, type):
for field in dataclasses.fields(value):
yield from _strings_in(getattr(value, field.name))
elif isinstance(value, (tuple, list, set, frozenset)):
for item in value:
yield from _strings_in(item)
@pytest.mark.parametrize("profile", [DEFAULT, STRICT_V1], ids=["DEFAULT", "STRICT_V1"])
def test_no_profile_hard_codes_an_upstream_version(profile: BundleProfile) -> None:
"""V4/V-A5. A profile names `okf_version` as a key and never carries its
value. The value tracks the upstream Google version and belongs to catalog
(decision E1), so a constant here would be this repo claiming a decision it
does not own — and the one that would have to be chased on every upstream
release.
"""
hard_coded = sorted({s for s in _strings_in(profile) if _VERSION_LITERAL_RE.match(s)})
assert hard_coded == []
def test_the_key_name_is_what_a_profile_pins(tmp_path: Path) -> None:
"""The other half of V4: naming the key is allowed and is what STRICT_V1
does. Without this, the test above would also pass on a profile that had
stopped requiring the declaration at all."""
assert "okf_version" in STRICT_V1.index.root_frontmatter
@pytest.mark.parametrize(
"schema_kwargs",
[
pytest.param({"order": ("timestamp", "generated")}, id="both-emitted"),
pytest.param(
{"order": ("timestamp",), "allowed": frozenset({"timestamp", "generated"})},
id="one-emitted-one-admitted",
),
pytest.param(
{"order": (), "required": frozenset({"timestamp", "generated"})},
id="both-required",
),
pytest.param(
{"order": ("timestamp", "title"), "nullable": frozenset({"generated"})},
id="one-emitted-one-nullable",
),
],
)
def test_a_schema_naming_both_timestamp_and_generated_cannot_be_built(
schema_kwargs: dict[str, Any],
) -> None:
"""V-A7. OKF §13.1 grants the `timestamp` fallback only while `generated` is
ABSENT, so a schema able to name both can describe a document with neither a
valid `generated.at` nor an eligible fallback. Refused at construction, the
same shape as the reserved `verdict` layer: a profile that could reach the
combination cannot be built, let alone handed to a door.
Every judging field names keys, `nullable` included. An open namespace
already admits `generated`, so a schema that also gives it a null rule has
named it exactly as surely as one that emits it — the last case is the
construction the proving consumer found the day the gate was written.
"""
with pytest.raises(ValueError, match="timestamp"):
FrontmatterSchema(**schema_kwargs)
def test_the_two_shipped_profiles_sit_on_opposite_sides_of_the_fallback() -> None:
"""Why the gate above costs nothing: neither shipped profile is near the
combination, and they are not near it in opposite directions. `DEFAULT`
emits `generated` and no `timestamp`; `STRICT_V1` emits `timestamp` and no
`generated`, which is precisely what puts the wiki on the §13.1 legacy path
rather than in a defect.
"""
assert "generated" in DEFAULT.frontmatter.order
assert "timestamp" not in DEFAULT.frontmatter.order
assert "timestamp" in STRICT_V1.frontmatter.order
assert "generated" not in STRICT_V1.frontmatter.order
assert STRICT_V1.frontmatter.allowed is not None
assert "generated" not in STRICT_V1.frontmatter.allowed
@pytest.mark.parametrize("declared", ["0.1", "0.2", "1.0", "2026.7.3"])
def test_the_root_frontmatter_policy_expresses_any_version_value(declared: str) -> None:
"""V4. The policy judges presence and ORDER, never the value — so the day
upstream ships v0.3, no index policy in this library has to change.
"""
text = (
f"---\nokf_version: {declared}\nbundle_profile: strict-v1\n"
"okf_spec_commit: 0123456789abcdef\n---\n\n# Bundle\n\n* [A](a.md) - a\n"
)
violations = STRICT_V1.index.violations(text, is_root=True, expected_targets={"a.md"})
assert violations == ()