fix(segmentation): hash the extracted text and let the plan key fire
This commit is contained in:
parent
6dce4355be
commit
9e9bb8645d
13 changed files with 272 additions and 40 deletions
|
|
@ -35,7 +35,12 @@ from llm_ingestion_okf.errors import SegmentationError
|
|||
from llm_ingestion_okf.extract import extract_text
|
||||
from llm_ingestion_okf.inbox import GateDecision, process_inbox
|
||||
from llm_ingestion_okf.profiles import DEFAULT, SEGMENTED_V1
|
||||
from llm_ingestion_okf.segmentation import SegmentationPlan, parse_segmentation_plan
|
||||
from llm_ingestion_okf.segmentation import (
|
||||
STDLIB_EXTRACTOR_VERSION,
|
||||
SegmentationPlan,
|
||||
observed_extractor_version,
|
||||
parse_segmentation_plan,
|
||||
)
|
||||
|
||||
INGESTED_AT = "2026-07-25T12:00:00Z"
|
||||
PLAN_AT = "2026-08-30T09:00:00Z"
|
||||
|
|
@ -90,8 +95,9 @@ def build_plan(
|
|||
payload: dict[str, Any] = {
|
||||
"version": "1",
|
||||
"source_sha256": hashlib.sha256(source_bytes).hexdigest(),
|
||||
"text_sha256": hashlib.sha256(text.encode("utf-8")).hexdigest(),
|
||||
"extractor_id": extractor_id,
|
||||
"extractor_version": "1.0.0",
|
||||
"extractor_version": observed_extractor_version(extractor_id),
|
||||
"adjudicated_at": "2026-08-30T08:00:00Z",
|
||||
"entries": [
|
||||
{
|
||||
|
|
@ -441,3 +447,55 @@ def test_without_the_capability_an_unmatched_plan_is_still_the_earlier_refusal(
|
|||
with pytest.raises(SegmentationError) as excinfo:
|
||||
run(tmp_path, plan=plan, profile=DEFAULT, values={})
|
||||
assert excinfo.value.code == "segmentation_unsupported_profile"
|
||||
|
||||
|
||||
# --- S5b: the cache key can actually fail ----------------------------------
|
||||
#
|
||||
# Both halves below were decorative before Step 11. The proposer hashed SOURCE
|
||||
# BYTES only, so a converter that reshaped the extracted text left the hash
|
||||
# identical and every offset moved under a key that still matched; and the run
|
||||
# path passed `plan.extractor_version` straight back into `assert_plan_applies`,
|
||||
# comparing the plan's value with itself. Two guards that could never fire, in
|
||||
# the one place where a false pass produces a bundle nobody adjudicated and no
|
||||
# downstream test can catch -- every span still lands on real text.
|
||||
|
||||
|
||||
def test_a_plan_whose_extracted_text_hash_moved_is_refused(tmp_path: Path) -> None:
|
||||
"""The signal a source-bytes hash cannot carry.
|
||||
|
||||
Same bytes on disk, same extractor id, same extractor version -- and a
|
||||
different canonical text, which is what the offsets index. Simulated by
|
||||
moving the hash rather than the converter, because the property under test
|
||||
is that the component is COMPARED at all.
|
||||
"""
|
||||
source = drop(tmp_path / "round", "n500.md", DOCUMENT)
|
||||
plan = build_plan(source.read_bytes(), DOCUMENT, text_sha256="0" * 64)
|
||||
result = run(tmp_path, plan=plan)
|
||||
assert {entry.error.code for entry in result.failed} == {"segmentation_extractor_mismatch"}
|
||||
assert "text_sha256" in str(result.failed[0].error)
|
||||
assert tree(tmp_path / "bundle") == {}
|
||||
|
||||
|
||||
def test_a_plan_whose_extractor_version_moved_is_refused(tmp_path: Path) -> None:
|
||||
"""The half of S5b that compared a value with itself."""
|
||||
source = drop(tmp_path / "round", "n500.md", DOCUMENT)
|
||||
plan = build_plan(source.read_bytes(), DOCUMENT, extractor_version="not-the-one-that-ran")
|
||||
result = run(tmp_path, plan=plan)
|
||||
assert {entry.error.code for entry in result.failed} == {"segmentation_extractor_mismatch"}
|
||||
assert "extractor_version" in str(result.failed[0].error)
|
||||
assert tree(tmp_path / "bundle") == {}
|
||||
|
||||
|
||||
def test_the_observed_extractor_version_is_not_the_proposers_own(tmp_path: Path) -> None:
|
||||
"""Defect (b): the proposer wrote ITS version into the extractor's field.
|
||||
|
||||
A stdlib row names this package's own literal because there is no third
|
||||
party to name; a converter row names the pinned converter. What matters is
|
||||
that the two are DIFFERENT values from different sources -- one tool
|
||||
version standing in for both is exactly what made the field unable to move.
|
||||
"""
|
||||
assert observed_extractor_version("md") == STDLIB_EXTRACTOR_VERSION
|
||||
assert observed_extractor_version("docx") != STDLIB_EXTRACTOR_VERSION
|
||||
with pytest.raises(SegmentationError) as excinfo:
|
||||
observed_extractor_version("nothing-registers-this")
|
||||
assert excinfo.value.code == "segmentation_extractor_mismatch"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue