Red first (K3-19 c). SPEC SS 4.1 makes `description` RECOMMENDED -- "A single
sentence summarizing the concept" -- and sets no length limit anywhere, so
the limit is ours: the FIRST <p> of the FIRST direct-child
<sec sec-type="spec"> of a titled <sec>, whole. Measured on R761, 2 026 of
2 761 titled sections carry such a point.
8 of 11 red on 912b850: OutlineMark has no description, the plan carries
none, the loader validates none, and the door writes none. The 3 that pass
pin what must not move: the extracted text, a type that declares no spec
point getting no key, and a stated `--frontmatter description=...` replacing
the derived one (landed with b).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
197 lines
7 KiB
Python
197 lines
7 KiB
Python
"""An STS section's `description` is its own first spec point (K3-19 c).
|
|
|
|
SPEC SS 4.1: `description` is RECOMMENDED -- "A single sentence summarizing the
|
|
concept. Used by `index.md` generators, search snippets, and previews." The
|
|
spec sets NO length limit, in SS 4.1, SS 8 or SS 11. The limit here is this
|
|
package's own and it is structural: the FIRST `<p>` of the FIRST direct-child
|
|
`<sec sec-type="spec">` of a titled `<sec>`, whole. Measured on the one STS
|
|
document this row has: 2 026 of 2 761 titled sections carry such a point.
|
|
|
|
WHAT THESE TESTS PIN:
|
|
|
|
- the description is the source's own words, never derived from the title and
|
|
never invented where the source has none -- a section without a spec point
|
|
of its own gets NO `description` key;
|
|
- a second spec point, and a second paragraph inside the first, never join it;
|
|
- a spec point belongs to the section it is a DIRECT child of, so a container
|
|
does not borrow its child's;
|
|
- the plan carries it, because a plan is the record a rebuild replays and the
|
|
title already travels there the same way;
|
|
- the gate sees it: it is document text persisted OUTSIDE the screened body,
|
|
so it is kept only on the gate's non-blocking floor, as the sanitized text;
|
|
- a value stated for the run replaces it (`--frontmatter description=...`).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
from llm_ingestion_okf import cli, extract, propose
|
|
from llm_ingestion_okf.errors import IngestError
|
|
from llm_ingestion_okf.inbox import GateDecision, process_inbox
|
|
from llm_ingestion_okf.materialize import parse_frontmatter
|
|
from llm_ingestion_okf.profiles import SEGMENTED_OKF_V0_2
|
|
from llm_ingestion_okf.segmentation import parse_segmentation_plan
|
|
|
|
FIXTURES = Path(__file__).parent / "fixtures"
|
|
IDENTITY = FIXTURES / "sts-identity.xml"
|
|
|
|
FIRST = "Omfatter utskifting av skadde enkeltkomponenter i rekkverk."
|
|
SECOND = "Omfatter maling av rekkverk."
|
|
EXPECTED = {
|
|
"Rekkverk": None,
|
|
"Utskifting av enkeltkomponenter": FIRST,
|
|
"Utskifting av handlist": None,
|
|
"Maling av rekkverk": SECOND,
|
|
}
|
|
STAMP = "2026-09-08T12:00:00Z"
|
|
|
|
|
|
def _by_title(bundle: Path) -> dict[str, dict[str, str]]:
|
|
return {
|
|
values["title"]: values
|
|
for values in (
|
|
parse_frontmatter(path)
|
|
for path in sorted(bundle.rglob("*.md"))
|
|
if path.name not in ("index.md", "log.md")
|
|
)
|
|
}
|
|
|
|
|
|
def _build(tmp_path: Path, name: str, data: bytes, *extra: str) -> Path:
|
|
inbox = tmp_path / "docs"
|
|
inbox.mkdir()
|
|
(inbox / name).write_bytes(data)
|
|
bundle = tmp_path / "bundle"
|
|
args = ["build", str(inbox), "--bundle", str(bundle), "--bundle-id", "d"]
|
|
assert cli.main([*args, "--okf-version", "0.2", *extra]) == 0
|
|
return bundle
|
|
|
|
|
|
# --- the reader --------------------------------------------------------------
|
|
|
|
|
|
def test_each_titled_section_carries_its_own_first_spec_paragraph() -> None:
|
|
marks = extract.xml_outline(IDENTITY.name, IDENTITY.read_bytes())
|
|
|
|
assert [(mark.title, mark.description) for mark in marks] == [
|
|
("88 Rekkverk", None),
|
|
("88.612 Utskifting av enkeltkomponenter", FIRST),
|
|
("88.6121 Utskifting av handlist", None),
|
|
("88.613 Maling av rekkverk", SECOND),
|
|
]
|
|
|
|
|
|
def test_the_extracted_text_does_not_move() -> None:
|
|
"""The description is read BESIDE the text, never out of it."""
|
|
text = extract.extract_text(IDENTITY.name, IDENTITY.read_bytes())
|
|
|
|
assert f"a) {FIRST}" in text.split("\n")
|
|
assert "b) Et andre punkt blir aldri beskrivelsen." in text.split("\n")
|
|
|
|
|
|
# --- through the plan and the door -------------------------------------------
|
|
|
|
|
|
def test_the_description_reaches_the_concept_only_where_the_source_has_one(
|
|
tmp_path: Path,
|
|
) -> None:
|
|
concepts = _by_title(_build(tmp_path, IDENTITY.name, IDENTITY.read_bytes()))
|
|
|
|
assert {title: values.get("description") for title, values in concepts.items()} == EXPECTED
|
|
|
|
|
|
def test_a_type_that_declares_no_spec_point_gets_no_description(tmp_path: Path) -> None:
|
|
markdown = b"# Innledning\n\na) Omfatter noe.\n\n# Omfang\n\nMer tekst her.\n"
|
|
concepts = _by_title(_build(tmp_path, "notat.md", markdown))
|
|
|
|
assert concepts
|
|
assert not any("description" in values for values in concepts.values())
|
|
|
|
|
|
def test_a_stated_description_replaces_the_derived_one(tmp_path: Path) -> None:
|
|
bundle = _build(
|
|
tmp_path,
|
|
IDENTITY.name,
|
|
IDENTITY.read_bytes(),
|
|
"--frontmatter",
|
|
"description=Standard beskrivelsestekst",
|
|
)
|
|
|
|
assert {values["description"] for values in _by_title(bundle).values()} == {
|
|
"Standard beskrivelsestekst"
|
|
}
|
|
|
|
|
|
def _plan() -> dict[str, object]:
|
|
data = IDENTITY.read_bytes()
|
|
text = extract.extract_text(IDENTITY.name, data)
|
|
return propose.build_plan(
|
|
IDENTITY, text, data, okf_type="reference", proposed_at=STAMP, path_prefix="r900"
|
|
)
|
|
|
|
|
|
def test_the_plan_carries_the_description_as_the_record_a_rebuild_replays() -> None:
|
|
entries = _plan()["entries"]
|
|
assert isinstance(entries, list)
|
|
|
|
assert {entry["title"]: entry.get("description") for entry in entries} == EXPECTED
|
|
plan = parse_segmentation_plan(_plan())
|
|
assert {entry.title: entry.description for entry in plan.entries} == EXPECTED
|
|
|
|
|
|
@pytest.mark.parametrize("bad", ["", "to\nlinjer", 5], ids=["empty", "two-lines", "not-a-string"])
|
|
def test_the_plan_loader_refuses_a_description_it_could_not_write(bad: object) -> None:
|
|
payload = _plan()
|
|
entries = payload["entries"]
|
|
assert isinstance(entries, list)
|
|
entries[1]["description"] = bad
|
|
|
|
with pytest.raises(IngestError) as caught:
|
|
parse_segmentation_plan(payload)
|
|
assert caught.value.code == "segmentation_plan_invalid"
|
|
|
|
|
|
def _door(tmp_path: Path, gate: object) -> dict[str, dict[str, str]]:
|
|
inbox = tmp_path / "docs"
|
|
inbox.mkdir()
|
|
(inbox / IDENTITY.name).write_bytes(IDENTITY.read_bytes())
|
|
bundle = tmp_path / "bundle"
|
|
result = process_inbox(
|
|
inbox,
|
|
bundle,
|
|
STAMP,
|
|
okf_type="reference",
|
|
gate=gate, # type: ignore[arg-type]
|
|
profile=SEGMENTED_OKF_V0_2,
|
|
root_frontmatter_values={"okf_version": "0.2", "bundle_id": "d"},
|
|
segmentation=parse_segmentation_plan(_plan()),
|
|
)
|
|
assert result.failed == ()
|
|
return _by_title(bundle)
|
|
|
|
|
|
def test_a_description_the_gate_refuses_is_dropped_and_the_concept_is_kept(
|
|
tmp_path: Path,
|
|
) -> None:
|
|
def gate(text: str) -> GateDecision:
|
|
refused = text == FIRST
|
|
return GateDecision(sanitized_text=text, disposition="block" if refused else "warn")
|
|
|
|
concepts = _door(tmp_path, gate)
|
|
|
|
assert "description" not in concepts["Utskifting av enkeltkomponenter"]
|
|
assert concepts["Maling av rekkverk"]["description"] == SECOND
|
|
|
|
|
|
def test_the_description_written_is_the_gates_sanitized_text(tmp_path: Path) -> None:
|
|
def gate(text: str) -> GateDecision:
|
|
return GateDecision(sanitized_text=text.replace("skadde", "[fjernet]"), disposition="warn")
|
|
|
|
concepts = _door(tmp_path, gate)
|
|
|
|
assert concepts["Utskifting av enkeltkomponenter"]["description"] == FIRST.replace(
|
|
"skadde", "[fjernet]"
|
|
)
|