v0.5.0a1 shipped the one actor value the spec owner had already excluded. Caught before any pilot was notified, so it costs a tag, not a migration. Commons decided <fast id> = process:okf-ingest on 2026-07-31, on this repo's own proposal, superseding option (d) (process:llm-ingestion-okf) chosen here on 07-27. The exclusion is ingest-spec.md:7-8, frozen on the spec being framework-neutral: normalising OUR repo name into the normative id would force every other conformant implementation to write it into its own output. Verified against three independent sources before touching anything — commons' coord message 20260731T154140Z, their plan :215-216/:244, and their STATE :37. Why this had to land before the pilot notifications rather than after: actor is both the stamp written and the value owned back (OwnershipPolicy), and recognition is one-way. A pilot that had run Test A against the excluded id would hold bundles this library stops recognising the moment the id is corrected — collision_unstamped on their OWN files. That is the A-E5 failure mode, and we would have inflicted it. Worse, it would not have shown up as a failure: the plan's A-E3 expectation (:854) named the same excluded value as the code, so Test A would have PASSED and confirmed the error. Expectation and implementation agreeing is not evidence when both predate the decision. Nothing in the wild carried the old value: OKF_V0_2 did not exist at v0.4.0, so the profile has never been released. v0.5.0a1 is abandoned, not moved — a tag already on a public remote does not get force-pushed, and the history should say plainly that a1 was wrong. A-E3 now records both corrections with dates. The V1 paragraph at :1169 is superseded in place rather than rewritten: its reasoning still holds, only its outcome moved. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CVTup4v7tW9QiVyBENk2LV
341 lines
13 KiB
Python
341 lines
13 KiB
Python
"""D2 — the `OKF_V0_2` profile, profile-aware ownership, `sources` derivation.
|
|
|
|
Three things, in the order the risk runs:
|
|
|
|
1. **The profile constant.** Names the §5 families, requires only `type` (§4:
|
|
"the only always-required key"), and closes nothing — §14 forbids a consumer
|
|
to reject on unknown types or unknown keys, so an allowlist here would put
|
|
the profile in violation of the spec it is named after.
|
|
2. **Ownership.** `_is_ingest_owned` is the pre-mutation collision gate, so it
|
|
is the one piece of this work whose defects are expensive and quiet. The v0.2
|
|
stamp is a flow mapping carrying `ingested_at`, so equality is impossible by
|
|
construction and the predicate is a prefix test. The operator decided the
|
|
direction: `OKF_V0_2` accepts BOTH stamps, so a bundle written under `DEFAULT`
|
|
re-runs IN PLACE; `DEFAULT` does not accept the v0.2 form, so the recognition
|
|
is one-way and the fail-safe from V-A3 is preserved.
|
|
3. **`sources`.** Derived from the manifest's source as an inline flow sequence
|
|
(requirement 1's measured form), with the locator taken verbatim per source
|
|
type: `root`, `connection_ref`, `base_url`. `credential_ref` is never a
|
|
locator and never reaches frontmatter.
|
|
|
|
`generated.by` is `process:okf-ingest` — decided by commons 2026-07-31 on this
|
|
repo's own proposal, superseding option (d) (`process:llm-ingestion-okf`), which
|
|
was chosen here 2026-07-27 and is now explicitly excluded. The exclusion is
|
|
`ingest-spec.md:7-8`, frozen on the spec being framework-neutral: normalising
|
|
*our* repo name would force every other conformant implementation to write it
|
|
into its own output. The value carries no version either way, which is what
|
|
keeps a byte-compared fixture stable across releases.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import re
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
from llm_ingestion_okf.errors import MaterializationError
|
|
from llm_ingestion_okf.manifest import (
|
|
Extraction,
|
|
FileSource,
|
|
HttpSource,
|
|
Manifest,
|
|
Source,
|
|
SqlSource,
|
|
)
|
|
from llm_ingestion_okf.materialize import _is_ingest_owned, _render_concept_file
|
|
from llm_ingestion_okf.profiles import DEFAULT, OKF_LATEST, OKF_V0_2, STRICT_V1, BundleProfile
|
|
|
|
INGESTED_AT = "2026-07-25T12:00:00Z"
|
|
STAMP = "manifest@0123456789abcdef"
|
|
V0_2_STAMP = f"{{ by: process:okf-ingest, at: {INGESTED_AT} }}"
|
|
|
|
|
|
def _manifest(source: Source) -> Manifest:
|
|
return Manifest(
|
|
manifest_version=1,
|
|
source=source,
|
|
bundle_summary="A test bundle.",
|
|
extractions=(
|
|
Extraction(
|
|
id="orders", title="Orders", query="orders.csv", okf_type="dataset", max_rows=10
|
|
),
|
|
),
|
|
)
|
|
|
|
|
|
def _concept(source: Source, *, profile: BundleProfile = OKF_V0_2) -> str:
|
|
manifest = _manifest(source)
|
|
return _render_concept_file(
|
|
manifest,
|
|
manifest.extractions[0],
|
|
"Body.\n",
|
|
ingested_at=INGESTED_AT,
|
|
stamp=STAMP,
|
|
profile=profile,
|
|
)
|
|
|
|
|
|
def _stamped_concept(generated: str) -> bytes:
|
|
return (
|
|
"---\ntype: dataset\ntitle: Orders\n"
|
|
f"ingest_manifest: {STAMP}\n"
|
|
f"generated: {generated}\n---\n\nBody.\n"
|
|
).encode()
|
|
|
|
|
|
# --- the profile constant -------------------------------------------------
|
|
|
|
|
|
def test_the_v0_2_profile_names_the_five_families_it_must_express() -> None:
|
|
"""C-E3's list, as emission order rather than as prose. Naming a key is what
|
|
lets a caller emit it in canonical order; judging a structured value is D1's
|
|
reader and deliberately absent here.
|
|
"""
|
|
order = OKF_V0_2.frontmatter.order
|
|
|
|
for key in ("generated", "sources", "verified", "status", "stale_after"):
|
|
assert key in order
|
|
assert "timestamp" not in order # §13.1: the fallback is for v0.1 documents
|
|
assert order.index("generated") < order.index("sources")
|
|
|
|
|
|
def test_the_v0_2_profile_requires_only_type() -> None:
|
|
"""§4/§11: `type` is the only always-required key, and a concept carrying
|
|
just `type` is fully conformant."""
|
|
assert OKF_V0_2.frontmatter.required == frozenset({"type"})
|
|
|
|
|
|
def test_the_v0_2_profile_closes_neither_the_type_set_nor_the_key_namespace() -> None:
|
|
"""§14's MUST NOTs, expressed as configuration: a consumer must not reject
|
|
on an unknown `type` value or on unknown additional keys. A profile that
|
|
closed either would be non-conformant against the version it is named for.
|
|
"""
|
|
assert OKF_V0_2.types.allowed is None
|
|
assert OKF_V0_2.frontmatter.allowed is None
|
|
assert OKF_V0_2.frontmatter.key_pattern is None
|
|
|
|
|
|
def test_the_v0_2_profile_keeps_the_source_query_collapse_and_the_default_paths() -> None:
|
|
"""The parts that are commons' §5 rather than upstream's: the doors write
|
|
the same filenames whatever the OKF version, and a multi-line SELECT still
|
|
renders on one line."""
|
|
assert OKF_V0_2.frontmatter.collapsed_keys == frozenset({"source_query"})
|
|
assert OKF_V0_2.paths == DEFAULT.paths
|
|
|
|
|
|
def test_okf_latest_still_means_v0_1_until_ga() -> None:
|
|
"""`OKF_LATEST` is "the latest version supported as STABLE", not the latest
|
|
present in the tree. Flipping it is the GA event — a single auditable
|
|
action rather than a side effect of this merge — so during the pilot a
|
|
consumer bound to the alias keeps v0.1 semantics.
|
|
"""
|
|
assert OKF_LATEST is DEFAULT
|
|
|
|
|
|
# --- ownership ------------------------------------------------------------
|
|
|
|
|
|
def test_the_default_profile_still_stamps_the_v0_1_literal() -> None:
|
|
assert DEFAULT.ownership.stamp(INGESTED_AT) == "true"
|
|
|
|
|
|
def test_the_v0_2_stamp_is_a_flow_mapping_whose_at_is_the_argument_exactly() -> None:
|
|
"""A-E3, corrected: the actor is the §7 `process:<id>` form and carries no
|
|
version. `at` binds to the already-validated `ingested_at` argument, so no
|
|
wall-clock is introduced and two runs with the same argument agree.
|
|
"""
|
|
assert OKF_V0_2.ownership.stamp(INGESTED_AT) == V0_2_STAMP
|
|
assert "llm-ingestion-okf/" not in OKF_V0_2.ownership.stamp(INGESTED_AT)
|
|
|
|
|
|
def test_the_v0_2_predicate_accepts_the_stamp_its_own_profile_writes(tmp_path: Path) -> None:
|
|
"""A-E5's second run: a v0.2 bundle re-runs into the directory it wrote."""
|
|
path = tmp_path / "ingest-orders.md"
|
|
path.write_bytes(_stamped_concept(V0_2_STAMP))
|
|
|
|
assert _is_ingest_owned(path, "manifest", profile=OKF_V0_2) is True
|
|
|
|
|
|
def test_the_v0_2_predicate_also_accepts_the_v0_1_stamp(tmp_path: Path) -> None:
|
|
"""The operator's decision (2026-07-27): a `DEFAULT`-written bundle is
|
|
re-runnable IN PLACE under `OKF_V0_2`. Without this the profile switch would
|
|
fire `collision_unstamped` on the library's own previous output, and the
|
|
black-box promise — an upstream release costs a consumer a re-run, nothing
|
|
more — would not hold across the profile change.
|
|
"""
|
|
path = tmp_path / "ingest-orders.md"
|
|
path.write_bytes(_stamped_concept("true"))
|
|
|
|
assert _is_ingest_owned(path, "manifest", profile=OKF_V0_2) is True
|
|
|
|
|
|
def test_the_default_predicate_still_refuses_the_v0_2_stamp(tmp_path: Path) -> None:
|
|
"""Recognition is one-way, which is what preserves V-A3. `DEFAULT` meeting a
|
|
v0.2 file refuses the run rather than replacing the file."""
|
|
path = tmp_path / "ingest-orders.md"
|
|
path.write_bytes(_stamped_concept(V0_2_STAMP))
|
|
|
|
assert _is_ingest_owned(path, "manifest") is False
|
|
assert _is_ingest_owned(path, "manifest", profile=DEFAULT) is False
|
|
|
|
|
|
def test_a_foreign_v0_2_actor_is_never_ingest_owned(tmp_path: Path) -> None:
|
|
"""U3 as a test: `generated` is attribution, and upstream writes it for
|
|
HAND-AUTHORED files. Presence proves nothing — the actor has to be ours, or
|
|
the collision gate would claim a human's file that merely carries the key.
|
|
"""
|
|
path = tmp_path / "ingest-orders.md"
|
|
path.write_bytes(_stamped_concept(f"{{ by: human:jsmith@acme, at: {INGESTED_AT} }}"))
|
|
|
|
assert _is_ingest_owned(path, "manifest", profile=OKF_V0_2) is False
|
|
|
|
|
|
def test_the_v0_2_stamp_still_has_to_name_this_manifest(tmp_path: Path) -> None:
|
|
"""§10.2 per-manifest ownership is unchanged by the profile: a sibling
|
|
manifest sharing the bundle keeps its own files."""
|
|
path = tmp_path / "ingest-orders.md"
|
|
path.write_bytes(_stamped_concept(V0_2_STAMP))
|
|
|
|
assert _is_ingest_owned(path, "other-manifest", profile=OKF_V0_2) is False
|
|
|
|
|
|
# --- `sources` derivation -------------------------------------------------
|
|
|
|
|
|
SOURCE_CASES = [
|
|
pytest.param(FileSource(id="golden-catalogue", root="fixture"), "fixture", id="file"),
|
|
pytest.param(
|
|
SqlSource(id="golden-db", connection_ref="OKF_GOLDEN_SQL_DB"),
|
|
"OKF_GOLDEN_SQL_DB",
|
|
id="sql",
|
|
),
|
|
pytest.param(
|
|
HttpSource(id="golden-api", base_url="https://golden.example.test"),
|
|
"https://golden.example.test",
|
|
id="http",
|
|
),
|
|
]
|
|
|
|
|
|
@pytest.mark.parametrize("source,resource", SOURCE_CASES)
|
|
def test_sources_is_an_inline_flow_sequence_carrying_the_locator(
|
|
source: Source, resource: str
|
|
) -> None:
|
|
"""A-E4. One entry, `id` from the manifest source and `resource` its locator
|
|
verbatim — the filesystem root, the env-var NAME holding the DSN, or the base
|
|
URL. Upstream's `resource` is a bundle-internal path; ours is not, and
|
|
inventing the `author`/`last_modified` upstream also carries would be writing
|
|
fields with no reader.
|
|
"""
|
|
line = f"sources: [{{ id: {source.id}, resource: {resource} }}]"
|
|
|
|
assert line in _concept(source).splitlines()
|
|
|
|
|
|
def test_sources_follows_generated_in_the_emitted_block() -> None:
|
|
"""The order the schema declares is the order that reaches disk."""
|
|
lines = _concept(FileSource(id="golden-catalogue", root="fixture")).splitlines()
|
|
|
|
assert [line.split(":")[0] for line in lines[1:9]] == [
|
|
"type",
|
|
"title",
|
|
"source_system",
|
|
"source_query",
|
|
"ingested_at",
|
|
"ingest_manifest",
|
|
"generated",
|
|
"sources",
|
|
]
|
|
|
|
|
|
def test_the_default_profile_emits_neither_sources_nor_a_v0_2_generated() -> None:
|
|
"""V-A6 at the unit level: the seven-key v0.1 block, byte for byte. The
|
|
golden suite proves the same thing end to end; this one localizes a failure
|
|
to the renderer rather than to a fixture diff.
|
|
"""
|
|
rendered = _concept(FileSource(id="golden-catalogue", root="fixture"), profile=DEFAULT)
|
|
|
|
assert rendered == (
|
|
"---\n"
|
|
"type: dataset\n"
|
|
"title: Orders\n"
|
|
"source_system: golden-catalogue\n"
|
|
"source_query: orders.csv\n"
|
|
f"ingested_at: {INGESTED_AT}\n"
|
|
f"ingest_manifest: {STAMP}\n"
|
|
"generated: true\n"
|
|
"---\n"
|
|
"\n"
|
|
"Body.\n"
|
|
)
|
|
|
|
|
|
def test_the_http_credential_reference_never_reaches_the_frontmatter() -> None:
|
|
"""A credential reference is not a locator. The convention forbids the
|
|
credential itself in frontmatter; the env-var NAME holding it has no reader
|
|
in a bundle either, so it is not written.
|
|
"""
|
|
source = HttpSource(
|
|
id="golden-api",
|
|
base_url="https://golden.example.test",
|
|
credential_ref="OKF_GOLDEN_API_TOKEN",
|
|
)
|
|
|
|
assert "OKF_GOLDEN_API_TOKEN" not in _concept(source)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"root",
|
|
["data, backup", "data{1}", "data[1]", "key: value"],
|
|
ids=["comma", "braces", "brackets", "colon-space"],
|
|
)
|
|
def test_a_locator_that_would_restructure_the_flow_mapping_is_refused(root: str) -> None:
|
|
"""Fail-fast rather than emit a silently WRONG provenance record.
|
|
|
|
These characters terminate or restructure a YAML flow mapping, and the
|
|
failure is quiet rather than loud. Measured with PyYAML 2026-07-27 rather
|
|
than argued: `[{ id: a, resource: data, backup }]` raises nothing and yields
|
|
`[{'id': 'a', 'resource': 'data', 'backup': None}]` — a clean parse into a
|
|
record no one wrote. Refusing is the same posture as the filename-length
|
|
gate: validation, not repair.
|
|
|
|
The same run confirms what IS emitted parses as intended — `generated` to a
|
|
two-key mapping, `sources` to a one-element list of one — including a base
|
|
URL, whose colons stay inside a plain scalar in flow context.
|
|
"""
|
|
with pytest.raises(MaterializationError) as excinfo:
|
|
_concept(FileSource(id="golden-catalogue", root=root))
|
|
|
|
assert excinfo.value.code == "source_reference_unquotable"
|
|
|
|
|
|
def test_the_default_profile_is_unaffected_by_an_unquotable_locator() -> None:
|
|
"""The refusal is a property of the v0.2 emission, not a new gate on v0.1:
|
|
`DEFAULT` never writes the locator, so it has nothing to refuse. A shared
|
|
refusal would have broken additivity — a manifest that ran yesterday must
|
|
still run."""
|
|
rendered = _concept(FileSource(id="golden-catalogue", root="data, backup"), profile=DEFAULT)
|
|
|
|
assert "data, backup" not in rendered
|
|
|
|
|
|
# --- V-A5 extended to the new profile -------------------------------------
|
|
|
|
_VERSION_LITERAL_RE = re.compile(r"^\d+(\.\d+)+$")
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"profile", [DEFAULT, STRICT_V1, OKF_V0_2], ids=["DEFAULT", "STRICT_V1", "OKF_V0_2"]
|
|
)
|
|
def test_no_profile_hard_codes_an_upstream_version(profile: BundleProfile) -> None:
|
|
"""V-A5, restated over the profile the name of which is the one place a
|
|
version literal would look natural. `OKF_V0_2` is a NAME; the value belongs
|
|
to catalog (E1) and is declared at D5, in the fixture, once.
|
|
|
|
Kept alongside the characterization suite's copy rather than replacing it:
|
|
that one pins the two shipped profiles at the point they were measured.
|
|
"""
|
|
from test_okf_v0_2_characterization import _strings_in
|
|
|
|
assert sorted({s for s in _strings_in(profile) if _VERSION_LITERAL_RE.match(s)}) == []
|