feat(inbox): accept one segmentation plan per document

This commit is contained in:
Kjell Tore Guttormsen 2026-09-02 14:51:53 +02:00
commit 81c6a01c86
2 changed files with 175 additions and 17 deletions

View file

@ -119,6 +119,7 @@ def run(
tmp: Path,
*,
plan: SegmentationPlan | None,
plans: dict[str, SegmentationPlan] | None = None,
profile=SEGMENTED_V1,
values: dict[str, str] | None = None,
round_name: str = "round",
@ -133,6 +134,7 @@ def run(
profile=profile,
root_frontmatter_values={"bundle_id": "b-1"} if values is None else values,
segmentation=plan,
segmentations=plans,
)
@ -499,3 +501,109 @@ def test_the_observed_extractor_version_is_not_the_proposers_own(tmp_path: Path)
with pytest.raises(SegmentationError) as excinfo:
observed_extractor_version("nothing-registers-this")
assert excinfo.value.code == "segmentation_extractor_mismatch"
# --- one plan per document, so a corpus can be run at all ------------------
#
# `process_inbox` took ONE plan, so a round over a heterogeneous corpus with
# several segmented documents was not expressible: arm B cannot execute
# without this. The single-plan form stays exactly as it was -- the mapping
# arrives as a new keyword-only parameter with a default, so every existing
# call site is source-compatible and no golden moves.
#
# The mapping is keyed by SOURCE FILENAME because that is what an operator
# reads, but selection stays by CONTENT IDENTITY: `_plan_covering` matches on
# `source_sha256`, so a renamed file still finds its plan and a plan pointed at
# the wrong name still refuses rather than segmenting the wrong document.
OTHER = "A Innledning: hva dette dokumentet dekker.\nB Virkeomraade: hvilke anlegg det gjelder.\n"
OTHER_PATHS = ("annen/innledning.md", "annen/virkeomraade.md")
def test_two_documents_with_two_plans_both_segment_in_one_run(tmp_path: Path) -> None:
first = drop(tmp_path / "round", "n500.md", DOCUMENT)
second = drop(tmp_path / "round", "n200.md", OTHER)
run(
tmp_path,
plan=None,
plans={
"n500.md": build_plan(first.read_bytes(), DOCUMENT),
"n200.md": build_plan(second.read_bytes(), OTHER, paths=OTHER_PATHS),
},
)
found = concepts(tmp_path / "bundle")
assert set(found) == set(PATHS) | set(OTHER_PATHS)
def test_a_mapping_selects_by_content_not_by_the_name_it_is_keyed_under(
tmp_path: Path,
) -> None:
"""The key is for the operator; the hash is what decides.
Keyed under a name no dropped file carries, the plan still finds the bytes
it was adjudicated over. Anything else would make a rename silently produce
a flat bundle.
"""
source = drop(tmp_path / "round", "n500.md", DOCUMENT)
run(
tmp_path,
plan=None,
plans={"whatever-i-called-it.md": build_plan(source.read_bytes(), DOCUMENT)},
)
assert set(concepts(tmp_path / "bundle")) == set(PATHS)
def test_a_plan_in_the_mapping_that_matches_nothing_is_still_refused(
tmp_path: Path,
) -> None:
"""The silent-skip refusal has to survive the wider input.
One plan matching is not enough: a corpus run where four of five plans
matched would otherwise report success over four segmented documents and
one flat one, which is exactly the silent zero this refusal exists for.
"""
source = drop(tmp_path / "round", "n500.md", DOCUMENT)
with pytest.raises(SegmentationError) as excinfo:
run(
tmp_path,
plan=None,
plans={
"n500.md": build_plan(source.read_bytes(), DOCUMENT),
"ghost.md": build_plan(source.read_bytes(), DOCUMENT, source_sha256="0" * 64),
},
)
assert excinfo.value.code == "segmentation_plan_unmatched"
assert tree(tmp_path / "bundle") == {}
def test_passing_both_forms_at_once_is_refused(tmp_path: Path) -> None:
"""Two ways to say the same thing invite a silent disagreement."""
source = drop(tmp_path / "round", "n500.md", DOCUMENT)
plan = build_plan(source.read_bytes(), DOCUMENT)
with pytest.raises(SegmentationError) as excinfo:
run(tmp_path, plan=plan, plans={"n500.md": plan})
assert excinfo.value.code == "segmentation_plan_invalid"
assert tree(tmp_path / "bundle") == {}
def test_two_plans_claiming_the_same_bytes_are_refused(tmp_path: Path) -> None:
"""Which one would have segmented the document is not a coin toss."""
source = drop(tmp_path / "round", "n500.md", DOCUMENT)
with pytest.raises(SegmentationError) as excinfo:
run(
tmp_path,
plan=None,
plans={
"a.md": build_plan(source.read_bytes(), DOCUMENT),
"b.md": build_plan(source.read_bytes(), DOCUMENT, paths=OTHER_PATHS + PATHS[2:]),
},
)
assert excinfo.value.code == "segmentation_plan_invalid"
assert tree(tmp_path / "bundle") == {}
def test_the_single_plan_form_is_unchanged(tmp_path: Path) -> None:
source = drop(tmp_path / "round", "n500.md", DOCUMENT)
run(tmp_path, plan=build_plan(source.read_bytes(), DOCUMENT))
assert set(concepts(tmp_path / "bundle")) == set(PATHS)