llm-ingestion-okf/tests/test_propose_segments.py
Kjell Tore Guttormsen dc67f86511 feat(propose): name the grid-rule grammar Arm E measures
Two constants, no behaviour. `RULE_TABLE_GRID = "rule:table-grid"` joins
`RULE_NAMES`, and `_GRID_RULE` sits beside `_TABLE_ROW` as the grammar of a
pandoc grid-table rule line.

The rule is the author's, not upstream's, and the constant says so: `grep -c -i
"arm" docs/2026-09-02-k3-k4-k5-metode.md` is 0, so the definition was written
for order 20260907T075834Z-18584396-from-.claude. Its axis is a third one --
Arm C names SIZE, Arm D names what the DOCUMENT declared, and this names what
the CONVERTER emitted.

The character class `[-=:+]` is measured, not guessed. Across the three
grid-bearing documents of the K2 corpus, 38 of 38 lines whose stripped form
starts with `+` match this pattern, and those four characters are the complete
set occurring on them. The `:` is pandoc's column-alignment marker and is load
bearing: a first pass with `[-=+]` matched 37 of 38, and through that single
miss read one document as having two tables where it has one. The `\s*` on
both ends mirrors `_TABLE_ROW` because the loop iterates
`splitlines(keepends=True)` -- every line carries its `\n`, and an indented
rule line is a real shape.

Tests first: 2 red, then green. 1233 -> 1235.
ruff check: exit 0. ruff format --check: exit 0. mypy --strict src/: 17 files,
Success. pytest -q: exit 0.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-07 10:47:36 +02:00

1280 lines
49 KiB
Python

"""The proposer: it proposes, a human adjudicates, and it never pretends otherwise.
The research is explicit about why this tool exists and about what it may not
claim. Topic 2 measured that the OKF reference agent splits on **what a thing
is** -- four semantic gates that need a model this run path does not have --
while a handful of MECHANICAL rules port today. Topic 1b measured that heading
derivation is inert on most of the K2 corpus: 23 of 33 PDFs carry no outline at
all and 95 % of the outline entries that exist are CAD export metadata, so the
scoreable denominator is ONE document. A rule validated on n=1 is not validated.
So the tool marks every decision `PROPOSED` and nothing else. The distinction
is the whole design: a proposal a human has not looked at must never be
replayable as an adjudication, because replay is exactly what the run path does
with a plan, deterministically and forever.
It lives outside `src/`, so it never enters a wheel (`tests/test_packaging.py`
enforces that) and no consumer's install surface changes because it exists.
"""
from __future__ import annotations
import json
import socket
from pathlib import Path
import pytest
from llm_ingestion_okf.segmentation import parse_segmentation_plan
from llm_ingestion_okf import propose as okf_propose_segments
DOCUMENT = """# N500 Vegbygging
Innledende tekst om vegbygging og dens omfang.
## 3.1 Brannkonsept
Krav til seksjonering av bygget over flere etasjer.
## 3.2 Roemning
To uavhengige roemningsveier fra hver branncelle.
## Og
## 3.3 Baereevne
Hovedbaeresystemet skal ha R60 dokumentert baereevne.
"""
#: The golden fixture's document. It carries a BARE-INTEGER ascending run of
#: three (`1`, `2`, `3`), which today's rules do not match at all -- measured,
#: bare `1` / `1.` / `1)` yield 0 candidates. That is precisely why the golden
#: is taken over THIS text and not over `DOCUMENT`: `DOCUMENT` has no
#: bare-integer line, so a golden over it would stay byte-identical even if the
#: outline rule were accidentally defaulted ON, and the guard would be a trap
#: written down but never able to fire. The ATX heading and the dotted heading
#: are here so the shipping rules fire too, and the golden pins their output as
#: well as the outline rule's absence.
OUTLINE_DOCUMENT = """# Stange skole -- teknisk grunnlag
Innledende tekst som gir dokumentet en kropp foer kapitlene begynner.
1 Innledning
Bakgrunnen for prosjektet og omfanget av arbeidet.
2 Krav
Krav til seksjonering av bygget over flere etasjer.
3 Gjennomfoering
Framdrift, faser og overlevering av bygget.
1.1 Brannkonsept
To uavhengige roemningsveier fra hver branncelle.
"""
#: Frozen in the golden's bytes. An explicit argument because the artifact
#: carries it verbatim, and a wall-clock default would make the golden
#: unreproducible by construction.
GOLDEN_PROPOSED_AT = "2026-09-03T00:00:00Z"
def write(tmp_path: Path, text: str = DOCUMENT, name: str = "n500.md") -> Path:
path = tmp_path / name
path.write_text(text, encoding="utf-8", newline="")
return path
def propose(tmp_path: Path, **kwargs: object) -> dict:
source = kwargs.pop("source", None) or write(tmp_path)
out = tmp_path / "plan.json"
code = okf_propose_segments.main([str(source), "--out", str(out)])
assert code == 0, f"proposer exited {code}"
return json.loads(out.read_text(encoding="utf-8"))
# --- the artifact is a valid plan -----------------------------------------
def test_the_emitted_artifact_parses_as_a_segmentation_plan(tmp_path: Path) -> None:
plan = parse_segmentation_plan(propose(tmp_path))
assert plan.entries
def test_it_carries_its_provenance(tmp_path: Path) -> None:
payload = propose(tmp_path)
assert payload["source_sha256"]
assert payload["extractor_id"]
assert payload["extractor_version"]
assert payload["proposed_by"].startswith("okf-propose-segments")
def test_the_source_hash_is_the_hash_of_the_bytes_it_read(tmp_path: Path) -> None:
import hashlib
source = write(tmp_path)
payload = propose(tmp_path, source=source)
assert payload["source_sha256"] == hashlib.sha256(source.read_bytes()).hexdigest()
# --- PROPOSED, never adjudicated ------------------------------------------
def test_every_entry_is_marked_proposed(tmp_path: Path) -> None:
payload = propose(tmp_path)
assert payload["entries"]
for entry in payload["entries"]:
assert okf_propose_segments.PROPOSED_MARKER in entry["derived"]
def test_no_entry_claims_to_be_adjudicated(tmp_path: Path) -> None:
payload = propose(tmp_path)
assert payload["adjudicated"] is False
for entry in payload["entries"]:
assert "adjudicated" not in entry
def test_every_entry_names_the_rule_that_proposed_it(tmp_path: Path) -> None:
# Per-decision provenance. A proposal an operator cannot trace to a rule is
# one they can only accept or reject wholesale.
for entry in propose(tmp_path)["entries"]:
rules = [name for name in entry["derived"] if name.startswith("rule:")]
assert len(rules) == 1
assert rules[0] in okf_propose_segments.RULE_NAMES
# --- the mechanical rules that actually ported -----------------------------
def test_a_heading_of_only_stop_words_is_not_a_boundary(tmp_path: Path) -> None:
# "Og" is a stop word. Without the gate it would open a segment of its own.
titles = [entry["title"] for entry in propose(tmp_path)["entries"]]
assert "Og" not in titles
assert any("Brannkonsept" in title for title in titles)
def test_a_heading_with_no_body_proposes_nothing(tmp_path: Path) -> None:
"""The orphan check, observed through the status now that a zero-entry plan
is no longer written: proposing nothing and writing nothing are the same
outcome, and it is distinct from failing."""
out = tmp_path / "orphans.json"
source = write(tmp_path, "# Tom\n\n## Ogsaa tom\n")
assert okf_propose_segments.main([str(source), "--out", str(out)]) == 1
assert not out.exists()
def test_the_spans_it_proposes_are_slices_of_the_text_it_read(tmp_path: Path) -> None:
from llm_ingestion_okf.extract import extract_text
source = write(tmp_path)
payload = propose(tmp_path, source=source)
text = extract_text(source.name, source.read_bytes())
for entry in payload["entries"]:
start, end = entry["span"]
assert text[start:end].strip()
def test_every_entry_carries_an_anchor_quoting_its_own_span(tmp_path: Path) -> None:
"""Written at proposal time, when text and offsets are known to agree.
Reconstructed later it would quote whatever the extraction had already
become, which is precisely the drift the anchor exists to survive.
"""
from llm_ingestion_okf.extract import extract_text
source = write(tmp_path)
payload = propose(tmp_path, source=source)
text = extract_text(source.name, source.read_bytes())
assert payload["entries"]
for entry in payload["entries"]:
start, end = entry["span"]
anchor = entry["anchor"]
assert anchor["quote"] == text[start:end]
assert text[start - len(anchor["prefix"]) : start] == anchor["prefix"]
assert text[end : end + len(anchor["suffix"])] == anchor["suffix"]
def test_a_proposed_plan_survives_a_shift_of_the_text_it_was_made_against(
tmp_path: Path,
) -> None:
"""The round trip the anchor exists for, measured end to end.
Prepend a paragraph -- exactly the benign edit that leaves every offset
short -- and every body must still be the text the proposer quoted. Before
anchors this cut each segment early and landed on real prose, with nothing
anywhere able to tell.
"""
from llm_ingestion_okf.extract import extract_text
from llm_ingestion_okf.segmentation import slice_segments
source = write(tmp_path)
plan = parse_segmentation_plan(propose(tmp_path, source=source))
text = extract_text(source.name, source.read_bytes())
quoted = [text[start:end] for start, end in (item.span for item in plan.entries)]
shifted = "Et nytt avsnitt foran alt annet.\n\n" + text
assert [body for _, body in slice_segments(shifted, plan)] == quoted
def test_the_paths_it_proposes_are_unique_and_hierarchical(tmp_path: Path) -> None:
plan = parse_segmentation_plan(propose(tmp_path))
paths = [entry.path for entry in plan.entries]
assert len(paths) == len(set(paths))
assert any("/" in path for path in paths)
# --- it does not reach the network ----------------------------------------
def test_it_makes_no_network_call(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
def refuse(*args: object, **kwargs: object) -> None:
raise AssertionError("the proposer opened a socket")
monkeypatch.setattr(socket, "socket", refuse)
monkeypatch.setattr(socket, "create_connection", refuse)
parse_segmentation_plan(propose(tmp_path))
def test_the_socket_guard_can_actually_fire(monkeypatch: pytest.MonkeyPatch) -> None:
# The known-positive control. Without it, a guard that patched the wrong
# name would make "no network call" a statement about nothing.
def refuse(*args: object, **kwargs: object) -> None:
raise AssertionError("the proposer opened a socket")
monkeypatch.setattr(socket, "socket", refuse)
with pytest.raises(AssertionError, match="opened a socket"):
socket.socket()
# --- failure is loud ------------------------------------------------------
def test_a_missing_source_exits_two_and_says_so(
tmp_path: Path, capsys: pytest.CaptureFixture[str]
) -> None:
code = okf_propose_segments.main([str(tmp_path / "absent.md"), "--out", str(tmp_path / "o")])
assert code == 2
assert "okf-propose-segments" in capsys.readouterr().err
def test_an_unknown_file_type_exits_two(tmp_path: Path, capsys: pytest.CaptureFixture[str]) -> None:
source = write(tmp_path, "body\n", name="thing.unknown")
code = okf_propose_segments.main([str(source), "--out", str(tmp_path / "o")])
assert code == 2
assert "okf-propose-segments" in capsys.readouterr().err
# --- the per-document scope a multi-document corpus needs ------------------
def test_a_path_prefix_scopes_every_entry_under_one_directory(tmp_path: Path) -> None:
"""Measured on the K2 corpus, 2026-09-03: 39 documents proposed 618 entries
under 601 distinct paths -- **17 paths were claimed by two documents each**.
Every one of them would hit Door B's collision gate, and the gate refuses
per DOCUMENT, so those documents would land as coded rejections instead of
concepts. Section numbering is document-local (`1 Innledning` is in most of
them), so the collision is structural rather than unlucky.
The scope is an argument and not something this tool invents: the proposer
sees one document and has no way of knowing what else is in the bundle, so
the caller who does supplies the prefix.
"""
out = tmp_path / "plan.json"
assert (
okf_propose_segments.main(
[str(write(tmp_path)), "--out", str(out), "--path-prefix", "Del II Bilag 3.1"]
)
== 0
)
payload = json.loads(out.read_text(encoding="utf-8"))
assert payload["entries"]
for entry in payload["entries"]:
assert entry["path"].startswith("del-ii-bilag-3-1/")
# Still a valid plan: the prefix passes the same path grammar as any other
# component, rather than being spliced in behind the validator's back.
assert parse_segmentation_plan(payload).entries
def test_a_multi_component_prefix_carries_the_relative_directory(tmp_path: Path) -> None:
"""Door B now walks the inbox recursively and records a `source_file`
relative to its root, so the caller driving a nested corpus has a
DIRECTORY, not a name, to scope by. Each component is reduced on its own
and rejoined with `/`: reducing the whole string would fold the separator
into a `-` and flatten `sub/sub2` into one component named `sub-sub2`,
which is a different bundle shape than the inbox it came from.
"""
out = tmp_path / "plan.json"
assert (
okf_propose_segments.main(
[str(write(tmp_path)), "--out", str(out), "--path-prefix", "Bilag 3/Del II"]
)
== 0
)
payload = json.loads(out.read_text(encoding="utf-8"))
assert payload["entries"]
for entry in payload["entries"]:
assert entry["path"].startswith("bilag-3/del-ii/")
assert parse_segmentation_plan(payload).entries
def test_a_prefix_with_one_empty_component_is_refused(tmp_path: Path) -> None:
"""`a//b` and `a/###/b` are the multi-component form of the same defect the
single-component gate already refuses: an empty component would collapse
the path silently rather than scope it.
"""
for prefix in ("sub//two", "sub/###/two", "/sub", "sub/"):
code = okf_propose_segments.main(
[str(write(tmp_path)), "--out", str(tmp_path / "p.json"), "--path-prefix", prefix]
)
assert code == 2, prefix
assert not (tmp_path / "p.json").exists(), prefix
def test_without_a_prefix_the_artifact_is_byte_identical(tmp_path: Path) -> None:
"""Additive. Every plan already produced stays exactly what it was."""
first = tmp_path / "a.json"
second = tmp_path / "b.json"
source = write(tmp_path)
okf_propose_segments.main([str(source), "--out", str(first)])
okf_propose_segments.main([str(source), "--out", str(second), "--path-prefix", ""])
assert first.read_bytes() == second.read_bytes()
def test_a_prefix_that_reduces_to_nothing_is_refused(tmp_path: Path) -> None:
"""A prefix of punctuation would otherwise become an empty component and
silently produce the unscoped paths the caller asked to avoid."""
code = okf_propose_segments.main(
[str(write(tmp_path)), "--out", str(tmp_path / "p.json"), "--path-prefix", "###"]
)
assert code == 2
assert not (tmp_path / "p.json").exists()
# --- nothing to propose is an outcome, and it is not an artifact -----------
def test_a_document_with_no_proposable_structure_writes_no_artifact(tmp_path: Path) -> None:
"""Measured on the K2 corpus 2026-09-03: 11 of 39 documents proposed zero
segments -- overwhelmingly PDFs with no declared structure, which Topic 1b
already measured at 23 of 33.
An empty artifact is not a plan a run can replay: `process_inbox` refuses
one by design, because an empty plan would persist nothing for a document
that was dropped. So the only use a zero-entry file has is to fail a run
later, and writing it converts "this document proposes no split" into "this
corpus cannot be built". It gets its own exit status instead: distinct from
2, which means the tool could not do its job at all.
"""
flat = write(tmp_path, "Loepende tekst uten overskrifter i det hele tatt.\n", name="flat.md")
out = tmp_path / "flat.json"
assert okf_propose_segments.main([str(flat), "--out", str(out)]) == 1
assert not out.exists()
def test_nothing_to_propose_is_not_reported_as_a_failure(
tmp_path: Path, capsys: pytest.CaptureFixture[str]
) -> None:
"""The two outcomes must be distinguishable by a driver reading the status:
one means "this document lands as one flat concept", the other means "stop"."""
flat = write(tmp_path, "Loepende tekst uten overskrifter i det hele tatt.\n", name="flat.md")
assert okf_propose_segments.main([str(flat), "--out", str(tmp_path / "f.json")]) == 1
nothing = capsys.readouterr().err
assert "FAILED" not in nothing
assert "nothing to propose" in nothing.lower()
assert (
okf_propose_segments.main([str(tmp_path / "absent.md"), "--out", str(tmp_path / "a.json")])
== 2
)
assert "FAILED" in capsys.readouterr().err
# --- Arm C: the size-driven subdivision -----------------------------------
#
# Arm C is NOT defined in `docs/2026-09-02-k3-k4-k5-metode.md` -- that file
# contains no occurrence of the word at all -- so the definition these tests
# pin was written for order 20260904T145630Z and is marked as such wherever it
# is reported: Arm B's mechanical rules, plus ONE deterministic rule that
# subdivides any proposed span longer than a declared cap at the nearest
# paragraph boundary at or before it, the whole document counting as one span
# when the rules find no boundary at all.
#
# One rule and not two, on purpose. The two failure modes the K2 rebuild
# measured -- a document with no outline (Bilag 9.1, 217 472 characters) and a
# document whose headings are a table of contents so the trailing segment
# absorbs the body (Bilag 3.1, Bilag 1) -- are the same failure of SIZE, and a
# second rule aimed at each would confound which one moved the number.
#
# What Arm C deliberately does NOT change: the region before the first
# candidate is still covered by no segment. That is a COVERAGE defect the
# baseline's blind rater named, it is real, and fixing it here would put two
# changes behind one measurement.
LONG_PARAGRAPH = ("Krav til seksjonering av bygget over flere etasjer. " * 40).strip()
UNSTRUCTURED = "\n\n".join(f"{LONG_PARAGRAPH} Avsnitt {i}." for i in range(12)) + "\n"
STRUCTURED_WITH_A_LONG_TAIL = (
"# N500 Vegbygging\n\nInnledende tekst om vegbygging.\n\n"
"## 3.1 Brannkonsept\n\nKort avsnitt om seksjonering.\n\n"
"## 3.2 Roemning\n\n" + UNSTRUCTURED
)
def propose_arm_c(tmp_path: Path, text: str, cap: int, name: str = "arm-c.md") -> dict:
source = write(tmp_path, text, name)
out = tmp_path / "arm-c-plan.json"
code = okf_propose_segments.main(
[str(source), "--out", str(out), "--max-segment-chars", str(cap)]
)
assert code == 0, f"proposer exited {code}"
return json.loads(out.read_text(encoding="utf-8"))
def test_arm_c_off_is_byte_identical_to_the_default(tmp_path: Path) -> None:
"""The cap defaults to 0 = off. The standard profile must not move."""
source = write(tmp_path, STRUCTURED_WITH_A_LONG_TAIL, "same.md")
without = tmp_path / "without.json"
with_zero = tmp_path / "with-zero.json"
assert okf_propose_segments.main([str(source), "--out", str(without)]) == 0
assert (
okf_propose_segments.main(
[str(source), "--out", str(with_zero), "--max-segment-chars", "0"]
)
== 0
)
assert without.read_bytes() == with_zero.read_bytes()
def test_a_span_longer_than_the_cap_is_split_into_contiguous_parts(
tmp_path: Path,
) -> None:
"""The parts must TILE the span they replace: no overlap, no gap.
Overlap would emit the same unit of knowledge twice, which is the
`duplicate` category the K3 method counts. A gap would drop text silently.
"""
plan = propose_arm_c(tmp_path, STRUCTURED_WITH_A_LONG_TAIL, 2000)
spans = [tuple(entry["span"]) for entry in plan["entries"]]
assert spans == sorted(spans)
for (_, first_end), (second_start, _) in zip(spans, spans[1:]):
assert first_end == second_start, f"{first_end} != {second_start}"
def test_no_proposed_span_exceeds_the_cap(tmp_path: Path) -> None:
plan = propose_arm_c(tmp_path, STRUCTURED_WITH_A_LONG_TAIL, 2000)
for entry in plan["entries"]:
start, end = entry["span"]
assert end - start <= 2000, f"{entry['path']} is {end - start} chars"
def test_it_cuts_at_a_paragraph_boundary_when_one_is_available(
tmp_path: Path,
) -> None:
"""A cut mid-sentence splits one unit of knowledge for no reason."""
plan = propose_arm_c(tmp_path, UNSTRUCTURED, 3000)
text = UNSTRUCTURED
for entry in plan["entries"][1:]:
start = entry["span"][0]
assert text[start - 2 : start] == "\n\n", repr(text[start - 8 : start + 8])
def test_a_document_with_no_proposable_structure_is_segmented_by_size(
tmp_path: Path,
) -> None:
"""Bilag 9.1's case: no outline at all, so Arm B proposes nothing."""
out = tmp_path / "arm-b.json"
assert okf_propose_segments.main([str(write(tmp_path, UNSTRUCTURED)), "--out", str(out)]) == 1
assert not out.exists()
plan = propose_arm_c(tmp_path, UNSTRUCTURED, 3000)
assert len(plan["entries"]) > 1
assert plan["entries"][0]["span"][0] == 0
assert plan["entries"][-1]["span"][1] == len(UNSTRUCTURED)
def test_a_short_unstructured_document_still_proposes_nothing(
tmp_path: Path, capsys: pytest.CaptureFixture[str]
) -> None:
"""Arm C fires on SIZE. Under the cap there is nothing for it to do, and a
one-entry plan would only dress the same single concept in a plan file."""
source = write(tmp_path, "Bare en kort tekst uten noen overskrift.\n", "kort.md")
out = tmp_path / "kort.json"
assert (
okf_propose_segments.main([str(source), "--out", str(out), "--max-segment-chars", "3000"])
== 1
)
assert not out.exists()
def test_every_split_part_names_the_size_rule_and_stays_proposed(
tmp_path: Path,
) -> None:
plan = propose_arm_c(tmp_path, UNSTRUCTURED, 3000)
for entry in plan["entries"]:
assert entry["derived"][0] == okf_propose_segments.PROPOSED_MARKER
assert okf_propose_segments.RULE_SIZE_SPLIT in entry["derived"]
def test_a_split_part_keeps_the_rule_that_proposed_its_origin_span(
tmp_path: Path,
) -> None:
"""Traceability: a part must still name the heading rule it came from."""
plan = propose_arm_c(tmp_path, STRUCTURED_WITH_A_LONG_TAIL, 2000)
split = [e for e in plan["entries"] if okf_propose_segments.RULE_SIZE_SPLIT in e["derived"]]
assert split
assert all(okf_propose_segments.RULE_HEADING in e["derived"] for e in split)
def test_a_span_under_the_cap_is_left_exactly_as_arm_b_proposed_it(
tmp_path: Path,
) -> None:
plan = propose_arm_c(tmp_path, STRUCTURED_WITH_A_LONG_TAIL, 2000)
untouched = [
e for e in plan["entries"] if okf_propose_segments.RULE_SIZE_SPLIT not in e["derived"]
]
assert untouched
for entry in untouched:
assert entry["derived"] == [
okf_propose_segments.PROPOSED_MARKER,
okf_propose_segments.RULE_HEADING,
]
def test_split_parts_get_unique_paths(tmp_path: Path) -> None:
plan = propose_arm_c(tmp_path, STRUCTURED_WITH_A_LONG_TAIL, 2000)
paths = [entry["path"] for entry in plan["entries"]]
assert len(paths) == len(set(paths))
def test_the_anchor_quotes_the_part_and_not_the_origin_span(tmp_path: Path) -> None:
plan = propose_arm_c(tmp_path, UNSTRUCTURED, 3000)
for entry in plan["entries"]:
start, end = entry["span"]
assert entry["anchor"]["quote"] == UNSTRUCTURED[start:end]
def test_a_negative_cap_is_refused(tmp_path: Path, capsys: pytest.CaptureFixture[str]) -> None:
source = write(tmp_path, UNSTRUCTURED)
code = okf_propose_segments.main(
[str(source), "--out", str(tmp_path / "x.json"), "--max-segment-chars", "-1"]
)
assert code == 2
assert "max-segment-chars" in capsys.readouterr().err
def test_an_unbreakable_line_longer_than_the_cap_is_still_cut(
tmp_path: Path,
) -> None:
"""No paragraph boundary to cut at. The cap still binds -- a part that
silently exceeded it would make the cap unenforceable exactly where the
document is hardest."""
plan = propose_arm_c(tmp_path, "x" * 9000 + "\n", 3000, "unbreakable.md")
for entry in plan["entries"]:
start, end = entry["span"]
assert end - start <= 3000
# --- the default profile, pinned byte for byte ----------------------------
#
# The one guard that can go red on an accidental default-on regression of any
# rule added later. It compares the WHOLE artifact, so a new candidate, a moved
# span, a changed `derived` list or a renamed path all break it.
def test_the_default_artifact_matches_its_committed_golden(tmp_path: Path) -> None:
"""The default profile is a promise to consumers; this is what keeps it.
The golden transitively pins `observed_extractor_version` too, so a
converter bump turns this red. That is a legitimate red, not a defect:
read the diff and decide, per `tests/fixtures/README.md`.
"""
source = write(tmp_path, OUTLINE_DOCUMENT, "outline.md")
out = tmp_path / "outline-plan.json"
assert (
okf_propose_segments.main(
[str(source), "--out", str(out), "--proposed-at", GOLDEN_PROPOSED_AT]
)
== 0
)
golden = Path(__file__).parent / "fixtures" / "propose-golden-default.json"
assert out.read_bytes() == golden.read_bytes(), (
"the default artifact diverges from its committed golden bytes"
)
# --- Arm D: the outline rule ----------------------------------------------
#
# Arm D is NOT defined in `docs/2026-09-02-k3-k4-k5-metode.md` -- that file
# contains no occurrence of the word -- so the definition these tests pin was
# written for the brief of order 20260906T213322Z and is reported as the
# author's, not as a ratified one: Arm B's mechanical rules, plus ONE rule that
# reads the document's OWN numbered outline -- the bare integers `N`, `N.`,
# `N)` that the shipping grammar cannot match because `_NUMBERED` requires a
# dot -- admitted only where the numbers form a maximal ascending run of at
# least a declared length, taking the LAST such run when the outline repeats,
# because a contents listing precedes the body it lists.
#
# EVERY fixture below uses BARE lines with no `#` prefix, and that is load
# bearing rather than stylistic. Measured on today's code: `# 1 Innledning`
# already yields 2 candidates via `_ATX`, and `1.1 Brannkonsept` already yields
# 2 via `_NUMBERED` -- so a fixture using either form would be green before the
# rule exists and would prove nothing. Bare `1` / `1.` / `1)` yield 0.
RUN_OF_THREE_THEN_RUN_OF_TWO = """Forord uten nummerering.
1 Innledning
Bakgrunn for prosjektet og omfanget.
2 Krav
Krav til seksjonering av bygget.
3 Gjennomfoering
Framdrift, faser og overlevering.
1 Vedlegg A
Foerste vedlegg til dokumentet.
2 Vedlegg B
Andre vedlegg til dokumentet.
"""
TWO_MAXIMAL_RUNS = """1 Innledning
Foerste forekomst, i innholdslista.
2 Krav
Andre forekomst, i innholdslista.
3 Gjennomfoering
Tredje forekomst, i innholdslista.
1 Innledning i kroppen
Her begynner selve teksten.
2 Krav i kroppen
Kravene slik de er skrevet ut.
3 Gjennomfoering i kroppen
Gjennomfoeringen slik den er skrevet ut.
"""
ALL_THREE_INTEGER_FORMS = """1 Innledning
Bakgrunn for prosjektet.
2. Krav
Krav til seksjonering.
3) Gjennomfoering
Framdrift og faser.
"""
NUMBER_WITHOUT_A_TITLE = """1 Innledning
Bakgrunn for prosjektet.
2
3 Gjennomfoering
Framdrift og faser.
"""
RUN_NOT_STARTING_AT_ONE = """2 Krav
Krav til seksjonering.
3 Gjennomfoering
Framdrift og faser.
4 Overlevering
Overlevering av bygget.
"""
def outline_titles(runs: list[list[tuple[int, int, str]]]) -> list[list[str]]:
return [[title for _, _, title in run] for run in runs]
def test_a_run_of_three_fires_and_a_run_of_two_in_the_same_document_does_not() -> None:
"""The paired form, and the pairing is the point.
A lone "a 2-run must not fire" assertion is true by absence before the rule
exists and true forever after, so it can never go red. Asserting both in ONE
document makes the negative half depend on the rule actually running.
"""
entries = okf_propose_segments.outline_lines(RUN_OF_THREE_THEN_RUN_OF_TWO)
runs = okf_propose_segments.outline_runs(entries, 3)
assert outline_titles(runs) == [["Innledning", "Krav", "Gjennomfoering"]]
# The known-positive control for the same fixture: at a minimum of 2 the
# second run IS admitted, so its absence above is the gate, not the text.
relaxed = okf_propose_segments.outline_runs(entries, 2)
assert outline_titles(relaxed) == [
["Innledning", "Krav", "Gjennomfoering"],
["Vedlegg A", "Vedlegg B"],
]
def test_runs_restart_at_every_new_one_and_the_last_maximal_run_is_the_last() -> None:
"""Two identical runs; the LAST is the body, the first is the contents."""
entries = okf_propose_segments.outline_lines(TWO_MAXIMAL_RUNS)
runs = okf_propose_segments.outline_runs(entries, 3)
assert len(runs) == 2
assert outline_titles(runs)[-1] == [
"Innledning i kroppen",
"Krav i kroppen",
"Gjennomfoering i kroppen",
]
# And the last run sits later in the document than the first.
assert runs[-1][0][0] > runs[0][-1][0]
def test_all_three_integer_forms_are_admitted() -> None:
"""`N`, `N.` and `N)` are the same declaration in three typographies."""
entries = okf_propose_segments.outline_lines(ALL_THREE_INTEGER_FORMS)
runs = okf_propose_segments.outline_runs(entries, 3)
assert outline_titles(runs) == [["Innledning", "Krav", "Gjennomfoering"]]
def test_a_number_with_no_title_is_not_a_candidate() -> None:
"""A bare `2` on its own line names no unit of knowledge.
It also breaks the run, which is the honest outcome: the document did not
declare a chapter there, so the rule may not invent one.
"""
entries = okf_propose_segments.outline_lines(NUMBER_WITHOUT_A_TITLE)
assert [number for _, number, _ in entries] == [1, 3]
assert okf_propose_segments.outline_runs(entries, 3) == []
def test_a_sequence_not_starting_at_one_is_not_a_run() -> None:
"""A run is anchored at `1`. `2, 3, 4` is page furniture until proven otherwise."""
entries = okf_propose_segments.outline_lines(RUN_NOT_STARTING_AT_ONE)
assert [number for _, number, _ in entries] == [2, 3, 4]
assert okf_propose_segments.outline_runs(entries, 3) == []
def test_a_trailing_page_number_is_stripped_from_an_outline_title() -> None:
"""`Innledning 6` is a contents line; the page number is not part of the title.
Load-bearing rather than cosmetic: titles become concept paths through
`_segment_path`, so a page number left on would become part of a filename.
"""
assert okf_propose_segments._strip_page_number("Innledning 6") == "Innledning"
assert okf_propose_segments._strip_page_number("Vurdering av restrisiko 25") == (
"Vurdering av restrisiko"
)
def test_a_title_that_is_only_digits_is_left_alone_rather_than_emptied() -> None:
"""The negative control for the stripper: it must not eat the whole title.
Emptying it here would hide junk from the stop-word/junk path that is
supposed to see it, and an empty stem falls back to `seksjon`, which would
make junk look like a named section.
"""
assert okf_propose_segments._strip_page_number("477") == "477"
# --- Arm D: wiring the rule into find_candidates ---------------------------
DOTTED_AND_INTEGER_SIBLINGS = """1 Innledning
Bakgrunn for prosjektet og omfanget.
2 Krav
Krav til seksjonering av bygget.
1.2 Brannkonsept
To uavhengige roemningsveier fra hver branncelle.
3 Gjennomfoering
Framdrift, faser og overlevering.
"""
ARM_B_HEADING_THEN_OUTLINE = """# Teknisk grunnlag
1 Innledning
Bakgrunn for prosjektet.
2 Krav
Krav til seksjonering.
3 Gjennomfoering
Framdrift og faser.
"""
def arm_d_plan(tmp_path: Path, text: str, run_length: int, name: str = "arm-d.md") -> dict:
"""Arm D at the `build_plan` level -- no CLI, so step 5 stands alone."""
source = write(tmp_path, text, name)
return okf_propose_segments.build_plan(
source,
text,
source.read_bytes(),
okf_type="reference",
proposed_at="2026-09-07T00:00:00Z",
outline_run=run_length,
)
def test_an_outline_entry_names_the_outline_rule_and_nothing_else() -> None:
"""Exhaustive `==`, not `in`: a second rule name would be a second claim."""
candidates = okf_propose_segments.find_candidates(ALL_THREE_INTEGER_FORMS, outline_run=3)
assert [c.rule for c in candidates] == [okf_propose_segments.RULE_OUTLINE] * 3
assert [c.title for c in candidates] == ["Innledning", "Krav", "Gjennomfoering"]
def test_a_dotted_heading_and_its_integer_sibling_carry_different_rules() -> None:
"""The paired form, and the pairing is what makes it non-vacuous.
A lone "no outline rule fired on `1.2`" assertion is green TODAY for the
opposite reason: measured, `1.2 Brannkonsept` already yields a candidate via
`_NUMBERED` with `rule:heading`. Asserting both siblings in ONE document is
what makes the integer-only claim testable.
"""
candidates = okf_propose_segments.find_candidates(DOTTED_AND_INTEGER_SIBLINGS, outline_run=3)
by_title = {c.title: c.rule for c in candidates}
assert by_title["Brannkonsept"] == okf_propose_segments.RULE_HEADING
assert by_title["Innledning"] == okf_propose_segments.RULE_OUTLINE
assert by_title["Krav"] == okf_propose_segments.RULE_OUTLINE
def test_an_outline_candidate_can_orphan_the_arm_b_candidate_it_follows() -> None:
"""Characterization, with the mechanism named rather than implied.
The orphan check at the emit loop measures a heading's body up to the NEXT
candidate of ANY rule. So an outline candidate landing immediately after an
Arm B heading empties that heading's body and deletes it. This is the
measured 34 %-deletion behaviour, pinned here so a later change to the span
bound has to confront it.
"""
without = okf_propose_segments.find_candidates(ARM_B_HEADING_THEN_OUTLINE)
assert [c.title for c in without] == ["Teknisk grunnlag"]
with_arm_d = okf_propose_segments.find_candidates(ARM_B_HEADING_THEN_OUTLINE, outline_run=3)
# The ATX heading is gone: `1 Innledning` follows it immediately, so its
# body is empty and the orphan check drops it.
assert "Teknisk grunnlag" not in [c.title for c in with_arm_d]
assert [c.rule for c in with_arm_d] == [okf_propose_segments.RULE_OUTLINE] * 3
def test_arm_d_spans_tile_and_each_anchor_quotes_its_own_span(tmp_path: Path) -> None:
plan = arm_d_plan(tmp_path, DOTTED_AND_INTEGER_SIBLINGS, 3)
spans = [tuple(entry["span"]) for entry in plan["entries"]]
assert spans == sorted(spans)
for (_, first_end), (second_start, _) in zip(spans, spans[1:]):
assert first_end == second_start, f"gap or overlap: {first_end} != {second_start}"
for entry in plan["entries"]:
start, end = entry["span"]
assert entry["anchor"]["quote"] == DOTTED_AND_INTEGER_SIBLINGS[start:end]
def test_outline_entries_get_unique_paths(tmp_path: Path) -> None:
plan = arm_d_plan(tmp_path, DOTTED_AND_INTEGER_SIBLINGS, 3)
paths = [entry["path"] for entry in plan["entries"]]
assert len(paths) == len(set(paths))
assert any(
entry["derived"]
== [
okf_propose_segments.PROPOSED_MARKER,
okf_propose_segments.RULE_OUTLINE,
]
for entry in plan["entries"]
)
def test_no_line_can_match_the_outline_grammar_and_an_arm_b_rule() -> None:
"""Why this repo ships NO coincident-boundary dedupe, pinned as a test.
The plan called for a dedupe branch guarding two candidates on one line.
Measured over 2400 synthetic lines spanning every shape the four grammars
accept: **zero** lines match `_OUTLINE` and any of `_ATX`, `_NUMBERED` or
`_TABLE_ROW`. `_ATX` needs `#`, `_TABLE_ROW` needs `|`, `_NUMBERED` needs a
dot inside the number, and `_OUTLINE` needs whitespace right after an
integer -- so the collision is impossible BY CONSTRUCTION, not merely
absent from this corpus, and a dedupe branch would be dead code with a test
that could never go red for a real reason.
This test is what keeps that argument honest: widen `_OUTLINE` until it can
overlap and this goes red, at which point the dedupe branch is owed.
"""
import itertools
collisions = []
tried = 0
for pre, num, mark, title, sep in itertools.product(
["", " ", " ", " ", " "],
["1", "9", "12", "99", "1.", "1)", "1.2", "12.3.4"],
["", "#", "##", "######", "|"],
["Innledning", "A |", "| A |", "Krav 6"],
[" ", " ", "\t"],
):
line = f"{pre}{mark}{num}{sep}{title}"
tried += 1
if okf_propose_segments._OUTLINE.match(line) and (
okf_propose_segments._ATX.match(line)
or okf_propose_segments._NUMBERED.match(line)
or okf_propose_segments._TABLE_ROW.match(line)
):
collisions.append(line)
assert tried == 2400
assert collisions == []
# Known-positive controls: the probe CAN match, so the empty result above
# is a measurement and not a broken query.
assert okf_propose_segments._OUTLINE.match("1 Innledning")
assert okf_propose_segments._ATX.match("# 1.2 Krav")
assert okf_propose_segments._NUMBERED.match("1.2 Krav")
assert okf_propose_segments._TABLE_ROW.match("| a | b |")
def test_the_outline_rule_is_off_at_the_function_level_by_default() -> None:
"""`outline_run` defaults to 0 and 0 means Arm B, exactly."""
default = okf_propose_segments.find_candidates(ALL_THREE_INTEGER_FORMS)
explicit_zero = okf_propose_segments.find_candidates(ALL_THREE_INTEGER_FORMS, outline_run=0)
assert default == explicit_zero == []
# The known-positive control: the same text DOES produce candidates when asked.
assert len(okf_propose_segments.find_candidates(ALL_THREE_INTEGER_FORMS, outline_run=3)) == 3
# --- Arm D: the CLI flag ---------------------------------------------------
def propose_arm_d(tmp_path: Path, text: str, run_length: int, name: str = "arm-d.md") -> dict:
source = write(tmp_path, text, name)
out = tmp_path / "arm-d-cli.json"
code = okf_propose_segments.main(
[str(source), "--out", str(out), "--outline-run", str(run_length)]
)
assert code == 0, f"proposer exited {code}"
return json.loads(out.read_text(encoding="utf-8"))
def test_outline_run_zero_is_byte_identical_to_the_flag_being_absent(tmp_path: Path) -> None:
"""The default profile is a promise; 0 must not be a different code path."""
source = write(tmp_path, ALL_THREE_INTEGER_FORMS, "same.md")
without = tmp_path / "without.json"
with_zero = tmp_path / "with-zero.json"
assert okf_propose_segments.main([str(source), "--out", str(without)]) == 1
assert (
okf_propose_segments.main([str(source), "--out", str(with_zero), "--outline-run", "0"]) == 1
)
# Exit 1 both times: this text has no Arm B boundary at all, so neither run
# writes an artifact. The known-positive control that the text is not inert:
assert (
okf_propose_segments.main(
[str(source), "--out", str(tmp_path / "on.json"), "--outline-run", "3"]
)
== 0
)
def test_the_two_arms_are_independent(tmp_path: Path) -> None:
"""`--outline-run 0` leaves Arm C's artifact untouched, and vice versa."""
source = write(tmp_path, STRUCTURED_WITH_A_LONG_TAIL, "both.md")
arm_c_only = tmp_path / "c.json"
arm_c_and_zero = tmp_path / "cz.json"
assert (
okf_propose_segments.main(
[str(source), "--out", str(arm_c_only), "--max-segment-chars", "2000"]
)
== 0
)
assert (
okf_propose_segments.main(
[
str(source),
"--out",
str(arm_c_and_zero),
"--max-segment-chars",
"2000",
"--outline-run",
"0",
]
)
== 0
)
assert arm_c_only.read_bytes() == arm_c_and_zero.read_bytes()
def test_a_negative_outline_run_is_refused_by_the_tool_not_by_argparse(
tmp_path: Path, capsys: pytest.CaptureFixture[str]
) -> None:
"""Exit 2 alone cannot separate the two refusals -- argparse exits 2 too.
`FAILED` is the tool's own two-line failure form, so asserting it is what
proves the refusal came from `run` and not from the parser.
"""
source = write(tmp_path, ALL_THREE_INTEGER_FORMS)
code = okf_propose_segments.main(
[str(source), "--out", str(tmp_path / "x.json"), "--outline-run", "-1"]
)
assert code == 2
err = capsys.readouterr().err
assert "FAILED" in err
assert "outline-run" in err
def test_a_non_integer_outline_run_is_refused_by_argparse(
tmp_path: Path, capsys: pytest.CaptureFixture[str]
) -> None:
"""The other exit-2 path, so the two cannot be satisfied by one branch."""
source = write(tmp_path, ALL_THREE_INTEGER_FORMS)
with pytest.raises(SystemExit) as exit_info:
okf_propose_segments.main(
[str(source), "--out", str(tmp_path / "x.json"), "--outline-run", "three"]
)
assert exit_info.value.code == 2
assert "usage:" in capsys.readouterr().err
#: Which arm flags carry the literal "not defined upstream" in their own help
#: chunk, and which must NOT. Data rather than a hand-counted assertion: the
#: whole-output count below is derived from this table, so adding an arm is one
#: line here instead of an edit to a number nobody can trace back to a rule.
#: Arm C is False on purpose -- its help says "not defined in the K3 method
#: file" instead, and the point of the check is that each arm's attribution
#: sits in its OWN chunk.
ARM_ATTRIBUTION = {"outline-run": True, "max-segment-chars": False}
def test_each_arm_flag_carries_its_attribution_inside_its_own_option_chunk(
capsys: pytest.CaptureFixture[str],
) -> None:
"""Each attribution must sit in its own option's help, not merely in the file.
Sliced between option headers on purpose: a whole-output grep would be
satisfied by adding the literal to another arm's block, which would
attribute the wrong rule. And the squeeze is `tr -s '[:space:]'`, not a
newline swap -- argparse wraps its help, so a newline-only normalisation
leaves the run of spaces behind and the literal stays unfindable.
The whole-output count is DERIVED from `ARM_ATTRIBUTION` rather than
written down. A hard-coded `== 1` is correct only until a second arm is
attributed the same way, and it then goes red on an axis that has nothing
to do with whether the new arm is right.
"""
with pytest.raises(SystemExit) as exit_info:
okf_propose_segments.main(["--help"])
assert exit_info.value.code == 0
squeezed = " ".join(capsys.readouterr().out.split())
# Drop the usage line: it repeats every option name, so slicing the whole
# output would find two chunks per flag and neither would be the help.
assert "options:" in squeezed
options = squeezed.split("options:", 1)[1]
chunks = options.split(" --")
for flag, attributed in ARM_ATTRIBUTION.items():
matching = [c for c in chunks if c.startswith(flag)]
assert len(matching) == 1, f"expected exactly one {flag} chunk, got {len(matching)}"
if attributed:
assert "not defined upstream" in matching[0], flag
else:
assert "not defined upstream" not in matching[0], flag
expected = sum(1 for attributed in ARM_ATTRIBUTION.values() if attributed)
assert squeezed.count("not defined upstream") == expected
# --- Arm E: the grid-rule join --------------------------------------------
#
# Arm E is not defined in `docs/2026-09-02-k3-k4-k5-metode.md` -- that file
# contains no occurrence of the word "arm" at all. The definition below was
# written for order 20260907T075834Z-18584396-from-.claude and is the author's.
#
# Two things about the fixtures are load bearing rather than stylistic.
#
# FIRST: every count asserted below was MEASURED against today's code before
# the test was written, so a fixture cannot be green by accident. `GRID_TABLE`
# yields 3 candidates today and `TWO_TABLES_BLANK_SEPARATED` yields 3, not
# because those numbers look right but because that is what `find_candidates`
# returns on this text right now.
#
# SECOND: the second table in `TWO_TABLES_BLANK_SEPARATED` has exactly ONE row
# group, so it CANNOT be joined. That is the only shape that can catch a join
# whose "a rule line is pending" state is never cleared -- a table's trailing
# bottom rule sets it, and if the blank line after the table does not clear it,
# the next table's first row is marked joined with no join having occurred. A
# fixture whose second table also joins would stay green through that defect.
#
# The `propose()` helper above is deliberately NOT used anywhere in this band:
# it pops `source` and silently discards every other kwarg, so a test written
# as `propose(tmp_path, table_grid=True)` would measure the DEFAULT and pass.
GRID_TABLE = """+-------+-------+
| Navn | Verdi |
+=======+=======+
| Areal | 120 |
+-------+-------+
| Hoyde | 3 |
+-------+-------+
"""
TWO_TABLES_BLANK_SEPARATED = """+-------+-------+
| Navn | Verdi |
+=======+=======+
| Areal | 120 |
+-------+-------+
Mellomtekst som skiller de to tabellene.
+-------+-------+
| Rom | Antall|
+-------+-------+
Avsluttende avsnitt.
"""
TWO_TABLES_RULE_SEPARATED = """+-------+-------+
| Navn | Verdi |
+-------+-------+
| Rom | Antall|
+-------+-------+
"""
PIPE_TABLE = """| Navn | Verdi |
|-------|-------|
| Areal | 120 |
| Hoyde | 3 |
Etterfoelgende avsnitt.
"""
PROSE_WITH_A_STRAY_RULE = """# Notat
Et avsnitt med tekst.
+-------+-------+
Enda et avsnitt med tekst.
"""
GRID_GOLDEN_DOCUMENT = """# Romskjema
Innledende avsnitt om romskjemaet.
+-------+-------+
| Navn | Verdi |
+=======+=======+
| Areal | 120 |
+-------+-------+
| Hoyde | 3 |
+-------+-------+
"""
def test_todays_grid_table_cuts_one_table_into_one_concept_per_row_group() -> None:
"""Characterization. ONE table, three concepts -- the defect Arm E measures.
Committed BEFORE the rule exists, so the later claim that the default did
not move rests on a pinned artifact rather than on reading a diff.
"""
candidates = okf_propose_segments.find_candidates(GRID_TABLE)
assert len(candidates) == 3
assert [c.rule for c in candidates] == [okf_propose_segments.RULE_TABLE_BLOCK] * 3
assert [c.title for c in candidates] == [
"Tabell linje 2",
"Tabell linje 4",
"Tabell linje 6",
]
# The spans tile: the document is cut into three, not sampled at three points.
assert candidates[0].start == 18
assert [(c.start, c.end) for c in candidates] == [(18, 54), (54, 90), (90, 126)]
assert candidates[-1].end == len(GRID_TABLE)
def test_a_grid_rule_line_matches_no_shipping_grammar() -> None:
"""The four rules that ship today all decline a grid rule line.
Fed with the trailing newline every one of them actually sees, because
`find_candidates` iterates `splitlines(keepends=True)`. A stripped literal
would pass here while the shipped loop never met the same string.
"""
rules = ("+---+---+\n", "+===+===+\n", "+:--+--:+\n", " +---+---+\n")
for line in rules:
assert okf_propose_segments._TABLE_ROW.match(line) is None, line
assert okf_propose_segments._ATX.match(line) is None, line
assert okf_propose_segments._NUMBERED.match(line) is None, line
assert okf_propose_segments._OUTLINE.match(line) is None, line
# Known-positive controls: each grammar CAN match something, so the four
# `is None` assertions above are evidence rather than four dead regexes.
assert okf_propose_segments._TABLE_ROW.match("| a | b |\n") is not None
assert okf_propose_segments._ATX.match("# Tittel\n") is not None
assert okf_propose_segments._NUMBERED.match("3.1 Brannkonsept\n") is not None
assert okf_propose_segments._OUTLINE.match("3 Brannkonsept\n") is not None
def test_the_default_artifact_over_a_grid_table_matches_its_committed_golden(
tmp_path: Path,
) -> None:
"""The second golden, and the reason there has to be a second one.
`propose-golden-default.json` is taken over `OUTLINE_DOCUMENT`, which
contains no `|` row and no `+` rule line. It is therefore STRUCTURALLY
incapable of going red if a table rule were ever defaulted on -- a trap
written down but unable to fire. This one is taken over a document that
has a grid table, so it can.
"""
source = write(tmp_path, GRID_GOLDEN_DOCUMENT, "grid.md")
out = tmp_path / "grid-plan.json"
assert (
okf_propose_segments.main(
[str(source), "--out", str(out), "--proposed-at", GOLDEN_PROPOSED_AT]
)
== 0
)
golden = Path(__file__).parent / "fixtures" / "propose-golden-grid-default.json"
assert out.read_bytes() == golden.read_bytes(), (
"the default artifact over a grid table diverges from its committed golden bytes"
)
def test_the_grid_rule_name_is_registered() -> None:
"""A rule an operator cannot find in `RULE_NAMES` is an unnameable rule."""
assert okf_propose_segments.RULE_TABLE_GRID == "rule:table-grid"
assert okf_propose_segments.RULE_TABLE_GRID in okf_propose_segments.RULE_NAMES
def test_the_grid_rule_grammar_is_the_class_the_corpus_declared() -> None:
"""`[-=:+]`, and the `\\s*` tolerance, are both measured rather than guessed.
Measured on the three grid-bearing documents of the K2 corpus: 38 of 38
lines whose stripped form starts with `+` match this pattern, and the
complete character set on those lines is `+`, `-`, `:`, `=`. The `:` is
pandoc's column-alignment marker; a first pass with the class `[-=+]`
returned 37 and mis-read one document as having two tables instead of one.
Every input carries its trailing newline, because `find_candidates`
iterates `splitlines(keepends=True)` and that is the string the shipped
loop actually sees. A stripped-literal test would be green while the rule
never fired on an indented or trailing-space rule line.
"""
grid = okf_propose_segments._GRID_RULE
for line in ("+---+---+\n", "+===+===+\n", "+:--+--:+\n", "+---+\n", " +---+---+\n"):
assert grid.match(line) is not None, line
for line in ("+\n", "++\n", "|---|---|\n", "+--- +---+\n", "---+---\n", "+abc+\n"):
assert grid.match(line) is None, line