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:
parent
54a0bc26ef
commit
3fa772e917
3 changed files with 267 additions and 0 deletions
22
tests/fixtures/README.md
vendored
22
tests/fixtures/README.md
vendored
|
|
@ -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.
|
||||
|
|
|
|||
92
tests/fixtures/propose-golden-grid-default.json
vendored
Normal file
92
tests/fixtures/propose-golden-grid-default.json
vendored
Normal 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"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -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"
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue