test(propose): pin today's grid-table split with a golden that can fire

The K3 Arm E round (order 20260907T075834Z-18584396-from-.claude) measures a
rule that has not been written yet. This commit pins what the code does TODAY,
so the later claim "the default did not move" rests on a committed artifact
rather than on reading a diff.

Three characterization tests and one new golden fixture. Every count they
assert was measured against unmodified code before the test was written:
GRID_TABLE yields 3 candidates, TWO_TABLES_BLANK_SEPARATED yields 3,
TWO_TABLES_RULE_SEPARATED 2, PIPE_TABLE 1, PROSE_WITH_A_STRAY_RULE 1.

The second golden is not duplication. `propose-golden-default.json` is taken
over `OUTLINE_DOCUMENT`, which contains no `|` row and no `+` rule line, so no
table rule -- present or future -- can move its bytes. It is a guard that
cannot fire. `propose-golden-grid-default.json` is taken over a document that
has a pandoc grid table, and it is generated here from PRE-Arm-E code: a
fixture recorded after the change would pin post-change bytes and prove
nothing about the default.

Two fixture shapes are load bearing and say so in the band's comment. The
second table in TWO_TABLES_BLANK_SEPARATED has exactly ONE row group, so it
cannot be joined -- that is the only shape that catches a join whose pending
state is never cleared. And `propose()` is excluded from this band: it pops
`source` and discards every other kwarg, so an Arm E test written through it
would silently measure the default and pass.

Tests first: 1 red (the golden did not exist), then green. 1230 -> 1233.
ruff check: exit 0. ruff format --check: exit 0. pytest -q: exit 0.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Kjell Tore Guttormsen 2026-09-07 10:45:05 +02:00
commit 3fa772e917
3 changed files with 267 additions and 0 deletions

View file

@ -162,3 +162,25 @@ two independent parsers return the same wrong shape, because the breakage is in
the documents' ruling geometry rather than in either library. PDFs enter this
library as **prose**, and structured tables are out of scope until that is
decided separately.
## `propose-golden-grid-default.json`
Pins the DEFAULT proposer artifact over a document containing a pandoc **grid
table**. Generated on unmodified code, before Arm E's rule existed, with
exactly the command the test runs:
```
write(tmp_path, GRID_GOLDEN_DOCUMENT, "grid.md")
okf_propose_segments.main([source, "--out", out, "--proposed-at", "2026-09-03T00:00:00Z"])
```
It exists because `propose-golden-default.json` **cannot** pin this. That
golden is taken over `OUTLINE_DOCUMENT`, which contains no `|` row and no `+`
rule line, so no table rule -- present or future -- can move its bytes. A guard
that is structurally incapable of firing is a trap written down but never
armed. This one is taken over a document that has a grid table, so an
accidentally default-on table rule turns it red.
Both goldens transitively pin `observed_extractor_version` and `PROPOSER_VERSION`.
A red here after a dependency change is a legitimate red: read the diff and
decide, do not re-record the expectation.

View file

@ -0,0 +1,92 @@
{
"version": "1",
"source_sha256": "4a50a27785e57b8d12319e3aad1118529251287b6ae8ecd3060bbac9fe04aee4",
"text_sha256": "4a50a27785e57b8d12319e3aad1118529251287b6ae8ecd3060bbac9fe04aee4",
"extractor_id": "md",
"extractor_version": "stdlib-1",
"adjudicated_at": "2026-09-03T00:00:00Z",
"adjudicated": false,
"proposed_by": "okf-propose-segments/1",
"entries": [
{
"segment_id": "p1",
"path": "romskjema.md",
"title": "Romskjema",
"okf_type": "reference",
"span": [
0,
67
],
"ingested_at": "2026-09-03T00:00:00Z",
"anchor": {
"quote": "# Romskjema\n\nInnledende avsnitt om romskjemaet.\n\n+-------+-------+\n",
"prefix": "",
"suffix": "| Navn | Verdi |\n+=======+=======+\n| Areal | 12"
},
"derived": [
"PROPOSED",
"rule:heading"
]
},
{
"segment_id": "p2",
"path": "tabell-linje-6.md",
"title": "Tabell linje 6",
"okf_type": "reference",
"span": [
67,
103
],
"ingested_at": "2026-09-03T00:00:00Z",
"anchor": {
"quote": "| Navn | Verdi |\n+=======+=======+\n",
"prefix": "ende avsnitt om romskjemaet.\n\n+-------+-------+\n",
"suffix": "| Areal | 120 |\n+-------+-------+\n| Hoyde | 3 "
},
"derived": [
"PROPOSED",
"rule:table-block"
]
},
{
"segment_id": "p3",
"path": "tabell-linje-8.md",
"title": "Tabell linje 8",
"okf_type": "reference",
"span": [
103,
139
],
"ingested_at": "2026-09-03T00:00:00Z",
"anchor": {
"quote": "| Areal | 120 |\n+-------+-------+\n",
"prefix": "--+-------+\n| Navn | Verdi |\n+=======+=======+\n",
"suffix": "| Hoyde | 3 |\n+-------+-------+\n"
},
"derived": [
"PROPOSED",
"rule:table-block"
]
},
{
"segment_id": "p4",
"path": "tabell-linje-10.md",
"title": "Tabell linje 10",
"okf_type": "reference",
"span": [
139,
175
],
"ingested_at": "2026-09-03T00:00:00Z",
"anchor": {
"quote": "| Hoyde | 3 |\n+-------+-------+\n",
"prefix": "==+=======+\n| Areal | 120 |\n+-------+-------+\n",
"suffix": ""
},
"derived": [
"PROPOSED",
"rule:table-block"
]
}
]
}

View file

@ -1084,3 +1084,156 @@ def test_the_help_attributes_arm_d_inside_its_own_option_chunk(
assert "not defined upstream" not in arm_c_chunk[0]
# The whole-output count must also be 1: the literal belongs to Arm D alone.
assert squeezed.count("not defined upstream") == 1
# --- 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"
)