llm-ingestion-okf/tests/test_segmented_identity.py

338 lines
13 KiB
Python

"""Bundle identity at Door B: the caller names the bundle, the door mirrors it.
Order `…2527032751` settled the form: **(c) a root-frontmatter bundle
identifier that consumers join on**. Two facts made this new wiring rather than
a value change. First, `root_frontmatter` had ZERO references in `inbox.py`:
Door B seeded its index with an empty file and no frontmatter block at all,
where Door A has carried the machinery since D5. Second, identity had to be
CALLER-owned — a bundle is a collection the caller delimits, so hashing its
contents would hand it a new identity every time a document was added.
That is also why there are two identity mechanisms in this library, stated
rather than hidden: WITHIN a bundle, Door C keys on a content hash; ACROSS
bundles, identity is this caller-assigned `bundle_id`. A consumer meets both.
S4b is resolved as ONE branch, not two: the root index is the SOURCE (the
caller supplies the value exactly once, D5 intact) and each concept MIRRORS
it. Two bundles built from identical inputs therefore hold concepts whose
paths COLLIDE by construction and whose identity values are disjoint — the
collision is the expected behaviour under form (c), not a defect.
"""
from __future__ import annotations
from pathlib import Path
import pytest
from llm_ingestion_okf.errors import MaterializationError
from llm_ingestion_okf.inbox import GateDecision, process_inbox, render_inbox_concept
from llm_ingestion_okf.profiles import DEFAULT, SEGMENTED_V1, STRUCTURED_V1
from llm_ingestion_okf.segmentation import SegmentEntry, parse_segmentation_plan
INGESTED_AT = "2026-07-25T12:00:00Z"
DOCUMENT = "# Brannkonsept\n\nSeksjonering etter N500.\n"
def gate(text: str) -> GateDecision:
return GateDecision(sanitized_text=text, disposition="warn")
def drop(inbox: Path, name: str, text: str = DOCUMENT) -> None:
inbox.mkdir(parents=True, exist_ok=True)
(inbox / name).write_text(text, encoding="utf-8", newline="")
def run(tmp: Path, *, profile=SEGMENTED_V1, values=None, round_name: str = "round"):
return process_inbox(
tmp / round_name,
tmp / "bundle",
INGESTED_AT,
okf_type="reference",
gate=gate,
profile=profile,
root_frontmatter_values=values,
)
def tree(bundle: Path) -> dict[str, bytes]:
"""Every file under the bundle, keyed by bundle-relative path."""
if not bundle.is_dir():
return {}
return {
str(path.relative_to(bundle)): path.read_bytes()
for path in sorted(bundle.rglob("*"))
if path.is_file()
}
# --- additivity: the four shipped profiles do not move --------------------
def test_default_without_values_writes_what_it_wrote_before(tmp_path: Path) -> None:
drop(tmp_path / "a", "n500-vegbygging.md")
process_inbox(
tmp_path / "a",
tmp_path / "old",
INGESTED_AT,
okf_type="reference",
gate=gate,
profile=DEFAULT,
)
drop(tmp_path / "b", "n500-vegbygging.md")
process_inbox(
tmp_path / "b",
tmp_path / "new",
INGESTED_AT,
okf_type="reference",
gate=gate,
profile=DEFAULT,
root_frontmatter_values=None,
)
assert tree(tmp_path / "new") == tree(tmp_path / "old")
assert not (tmp_path / "new" / "index.md").read_text(encoding="utf-8").startswith("---")
def test_structured_v1_without_values_still_writes_no_frontmatter(tmp_path: Path) -> None:
drop(tmp_path / "round", "n500-vegbygging.md")
run(tmp_path, profile=STRUCTURED_V1)
assert not (tmp_path / "bundle" / "index.md").read_text(encoding="utf-8").startswith("---")
# --- emission -------------------------------------------------------------
def test_the_root_index_opens_with_the_declared_bundle_id(tmp_path: Path) -> None:
drop(tmp_path / "round", "n500-vegbygging.md")
run(tmp_path, values={"bundle_id": "b-1"})
body = (tmp_path / "bundle" / "index.md").read_text(encoding="utf-8")
assert body.startswith("---\nbundle_id: b-1\n---\n\n")
def test_the_value_is_written_verbatim_and_unquoted(tmp_path: Path) -> None:
# Read as RAW BYTES: a parsed assertion returns the same string whether or
# not the emitter added quotes, so it would mask exactly the defect a
# consumer's shape gate trips on.
drop(tmp_path / "round", "n500-vegbygging.md")
run(tmp_path, values={"bundle_id": "svv-n500-2026"})
assert b"bundle_id: svv-n500-2026\n" in (tmp_path / "bundle" / "index.md").read_bytes()
def test_a_second_round_does_not_double_the_block(tmp_path: Path) -> None:
drop(tmp_path / "one", "n500-vegbygging.md")
run(tmp_path, values={"bundle_id": "b-1"}, round_name="one")
drop(tmp_path / "two", "v720-tunnel.md")
run(tmp_path, values={"bundle_id": "b-1"}, round_name="two")
body = (tmp_path / "bundle" / "index.md").read_text(encoding="utf-8")
assert body.startswith("---\nbundle_id: b-1\n---\n\n")
assert body.count("bundle_id:") == 1
def test_the_index_still_carries_its_entries_below_the_block(tmp_path: Path) -> None:
drop(tmp_path / "round", "n500-vegbygging.md")
run(tmp_path, values={"bundle_id": "b-1"})
body = (tmp_path / "bundle" / "index.md").read_text(encoding="utf-8")
assert "](inbox-n500-vegbygging.md)" in body
assert body.index("bundle_id") < body.index("](inbox-n500-vegbygging.md)")
# --- fail-fast, before any disk mutation ----------------------------------
def test_a_key_the_policy_does_not_name_is_refused_before_any_write(tmp_path: Path) -> None:
drop(tmp_path / "round", "n500-vegbygging.md")
before = tree(tmp_path / "bundle")
with pytest.raises(MaterializationError) as excinfo:
run(tmp_path, values={"okf_version": "0.2"})
assert excinfo.value.code == "index_root_frontmatter_unexpected"
# The whole point of hoisting the call above the write: a refused run must
# leave nothing behind, not a half-built bundle whose index is missing.
assert tree(tmp_path / "bundle") == before
assert before == {}
def test_a_refusal_leaves_an_existing_bundle_untouched(tmp_path: Path) -> None:
drop(tmp_path / "one", "n500-vegbygging.md")
run(tmp_path, values={"bundle_id": "b-1"}, round_name="one")
before = tree(tmp_path / "bundle")
assert before != {}
drop(tmp_path / "two", "v720-tunnel.md")
with pytest.raises(MaterializationError) as excinfo:
run(tmp_path, values={"okf_version": "0.2"}, round_name="two")
assert excinfo.value.code == "index_root_frontmatter_unexpected"
assert tree(tmp_path / "bundle") == before
def test_values_are_refused_against_a_profile_naming_no_root_keys(tmp_path: Path) -> None:
drop(tmp_path / "round", "n500-vegbygging.md")
with pytest.raises(MaterializationError) as excinfo:
run(tmp_path, profile=DEFAULT, values={"bundle_id": "b-1"})
assert excinfo.value.code == "index_root_frontmatter_unexpected"
assert tree(tmp_path / "bundle") == {}
# --- the mirror: identity, per concept ------------------------------------
#
# S4b resolved as ONE branch. The root index is the SOURCE of `bundle_id` --
# the caller supplies it exactly once, so D5 stays intact -- and every concept
# MIRRORS it. That satisfies S4b's "recorded per concept" literally, without a
# second place a caller could set it differently.
def segment_entry(**overrides: object) -> SegmentEntry:
payload = {
"segment_id": "s1",
"path": "krav/3-1/brannkonsept.md",
"title": "Brannkonsept",
"okf_type": "requirement",
"span": [12, 48],
"ingested_at": "2026-08-30T09:00:00Z",
}
payload.update(overrides) # type: ignore[arg-type]
return parse_segmentation_plan(
{
"version": "1",
"source_sha256": "a" * 64,
"extractor_id": "text",
"extractor_version": "1.0.0",
"adjudicated_at": "2026-08-30T08:00:00Z",
"entries": [payload],
}
).entries[0]
def render(**overrides: object) -> str:
arguments: dict[str, object] = {
"okf_type": "reference",
"title": "Brannkonsept",
"source_file": "n500.md",
"source_bytes": b"raw",
"ingested_at": INGESTED_AT,
"profile": SEGMENTED_V1,
}
arguments.update(overrides)
return render_inbox_concept(DOCUMENT, **arguments) # type: ignore[arg-type]
def frontmatter_of(document: str) -> dict[str, str]:
head = document.split("---\n")[1]
return dict(
line.split(": ", 1) for line in head.splitlines() if ": " in line and line[:1] != " "
)
def test_a_segmented_concept_carries_the_identity_and_segment_keys() -> None:
keys = frontmatter_of(render(segment=segment_entry(), bundle_id="b-1"))
assert keys["bundle_id"] == "b-1"
assert keys["segment_id"] == "s1"
assert keys["source_offset"] == "[12, 48]"
def test_the_offset_is_flow_form_never_block() -> None:
# Flow, never block: this library's parser round-trips a flow value as an
# opaque string and cannot read a block one at all, so emitting block would
# produce bundles we cannot read back.
document = render(segment=segment_entry(), bundle_id="b-1")
assert "source_offset: [12, 48]\n" in document
assert "source_offset:\n" not in document
assert "\n - " not in document
def test_a_declared_parent_is_mirrored_and_a_flat_segment_carries_none() -> None:
plan_with_parent = parse_segmentation_plan(
{
"version": "1",
"source_sha256": "a" * 64,
"extractor_id": "text",
"extractor_version": "1.0.0",
"adjudicated_at": "2026-08-30T08:00:00Z",
"entries": [
{
"segment_id": "s1",
"path": "krav/3-1.md",
"title": "Krav",
"okf_type": "requirement",
"span": [0, 12],
"ingested_at": "2026-08-30T09:00:00Z",
},
{
"segment_id": "s2",
"path": "krav/3-1/brannkonsept.md",
"title": "Brannkonsept",
"okf_type": "requirement",
"span": [12, 48],
"ingested_at": "2026-08-30T09:00:00Z",
"parent_id": "s1",
},
],
}
)
parent, child = plan_with_parent.entries
assert "parent" not in frontmatter_of(render(segment=parent, bundle_id="b-1"))
assert frontmatter_of(render(segment=child, bundle_id="b-1"))["parent"] == "s1"
def test_the_plan_entry_supplies_ingested_at_not_the_call_argument() -> None:
# A plan-covered concept must never see the call-level timestamp: the plan
# replays an adjudication, and a rebuild months later has to reproduce the
# same bytes as the round that first wrote it.
keys = frontmatter_of(render(segment=segment_entry(), bundle_id="b-1"))
assert keys["ingested_at"] == "2026-08-30T09:00:00Z"
assert keys["ingested_at"] != INGESTED_AT
# --- additivity at the renderer -------------------------------------------
def test_default_without_the_new_parameters_is_byte_identical() -> None:
without = render_inbox_concept(
DOCUMENT,
okf_type="reference",
title="Brannkonsept",
source_file="n500.md",
source_bytes=b"raw",
ingested_at=INGESTED_AT,
profile=DEFAULT,
)
with_defaults = render(profile=DEFAULT, segment=None, bundle_id=None)
assert with_defaults == without
assert "bundle_id" not in without
def test_a_profile_without_the_capability_ignores_a_segment() -> None:
# The keys are added ONLY behind `profile.segmentation is not None`. Without
# that guard, `emit` would sort them into the tail of all four shipped
# profiles and churn every golden.
document = render(profile=STRUCTURED_V1, segment=segment_entry(), bundle_id="b-1")
assert "bundle_id" not in document
assert "segment_id" not in document
assert "source_offset" not in document
def test_a_concept_the_plan_does_not_cover_keeps_todays_rule(tmp_path: Path) -> None:
document = render(segment=None, bundle_id="b-1")
assert "bundle_id" not in document
assert "segment_id" not in document
assert frontmatter_of(document)["ingested_at"] == INGESTED_AT
# --- two bundles, same paths, disjoint identity ---------------------------
def test_two_bundles_hold_colliding_paths_and_disjoint_identity_values() -> None:
entries = (
segment_entry(),
segment_entry(segment_id="s2", path="krav/3-2/roemning.md", span=[48, 90]),
)
one = [render(segment=item, bundle_id="b-1") for item in entries]
other = [render(segment=item, bundle_id="b-2") for item in entries]
# The paths COLLIDE by construction. Under form (c) that is the expected
# behaviour, not a defect: the path is the concept ID, the bundle is what
# disambiguates, and a consumer joins on `bundle_id`.
assert {item.path for item in entries} == {item.path for item in entries}
assert [frontmatter_of(document)["bundle_id"] for document in one] == ["b-1", "b-1"]
assert [frontmatter_of(document)["bundle_id"] for document in other] == ["b-2", "b-2"]
assert set(one).isdisjoint(set(other))