test(accounting): the witnesses see what the formats actually hold (M-1..M-3)
Rows 2 and 3 require the build's inventory to EQUAL the witness's, so what the witness does not count, nothing can lose visibly. An independent review put a header and a comment in a docx, measured 0 of either in the bundle, and the accounting still read "2 of 2 carried". Thirteen classes are now counted, each with a red test written first: docx header/footer, comment, endnote and text box (a box's paragraphs are its own, or the text is booked twice) - pptx speaker note and hidden slide (`show="0"`, no longer counted as an ordinary slide) - xlsx formula and hidden sheet (the state lives in `workbook.xml` and is reached through the relationship id, so the sheet part itself says nothing about it) - odt header/footer from `styles.xml` and annotation (counted as prose, it made the accounting demand a reader carry a note the author wrote to themselves) - STS `mixed-citation`, `mml:math`, `fig` and its caption, measured by the review at 4.1 % of N200's source text and 3.9 % of N100's. M-2: the two STS witnesses had ONE role map between them, so row 5 -- "two witnesses agree" -- could not see a hole in it. `_sts_role_xml` and `_sts_role_json` are written apart, each for its own delivery, and a test holds them apart. M-3: 20 of 63 element types had a count of ZERO in their only fixture. Seven hand-built documents close it, every element type now occurs at least once (a test asserts it), and ALL TWENTY documents carry a hand count read off the fixture's own bytes (four did before). `.xlsx image` -- the operator's own proposed exception -- could not be exercised at all until now. Every witness also states WHAT IT STILL DOES NOT COUNT, per file type, and the gate prints that list on every run. THE FIXTURE ROWS ARE RED NOW, AND THAT IS THE POINT. Row 2 red on .docx, .odt, .pptx, .xlsx and .xml; row 3 at u = 25, d = 2 over the new classes, including a footnote and four spreadsheet cells the build genuinely drops. `0 claimed and not found` on the same run: nothing the build DOES book as carried failed the bundle check, so the red is the build's and not the instrument's. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
656cbe5d02
commit
e5dc21ec2f
14 changed files with 1542 additions and 59 deletions
|
|
@ -102,7 +102,10 @@ def test_the_fixture_corpus_covers_every_readme_file_type() -> None:
|
|||
assert {entry["suffix"] for entry in inventory["documents"].values()} == set(TABLE)
|
||||
|
||||
|
||||
# Counted by hand from the fixture bytes, not by the witness.
|
||||
# Counted by hand from the fixture bytes, not by the witness. Seven of the
|
||||
# thirteen documents were added 2026-09-18 because 20 of 63 element types had
|
||||
# a count of ZERO in their only fixture, and a type that cannot appear cannot
|
||||
# be lost visibly either (independent review, M-3).
|
||||
HAND_COUNTS = {
|
||||
"notat.md": {
|
||||
"code_block": 1,
|
||||
|
|
@ -112,13 +115,26 @@ HAND_COUNTS = {
|
|||
"table": 1,
|
||||
"table_row": 2,
|
||||
},
|
||||
"side.htm": {"cell": 4, "heading": 2, "image": 0, "list_item": 2, "paragraph": 1, "table": 1},
|
||||
"side.htm": {"cell": 4, "heading": 2, "image": 1, "list_item": 2, "paragraph": 1, "table": 1},
|
||||
"figur.html": {
|
||||
"cell": 4,
|
||||
"heading": 1,
|
||||
"image": 1,
|
||||
"list_item": 2,
|
||||
"paragraph": 1,
|
||||
"table": 1,
|
||||
},
|
||||
"krav-rikt-tekstformat.rtf": {"cell": 56, "image": 0, "paragraph": 3, "table_row": 24},
|
||||
"bilde.rtf": {"cell": 0, "image": 1, "paragraph": 2, "table_row": 0},
|
||||
"prosess-84-sts.xml": {
|
||||
"cell": 0,
|
||||
"citation": 0,
|
||||
"figure": 0,
|
||||
"figure_caption": 0,
|
||||
"footnote": 0,
|
||||
"image": 2,
|
||||
"list_item": 0,
|
||||
"math": 0,
|
||||
"paragraph": 2,
|
||||
"section": 2,
|
||||
"section_label": 2,
|
||||
|
|
@ -126,9 +142,140 @@ HAND_COUNTS = {
|
|||
"table_label": 0,
|
||||
"title": 2,
|
||||
},
|
||||
"sts-rikt.xml": {
|
||||
"cell": 4,
|
||||
"citation": 1,
|
||||
"figure": 1,
|
||||
"figure_caption": 1,
|
||||
"footnote": 1,
|
||||
"image": 1,
|
||||
"list_item": 1,
|
||||
"math": 1,
|
||||
"paragraph": 5,
|
||||
"section": 1,
|
||||
"section_label": 1,
|
||||
"table": 1,
|
||||
"table_label": 1,
|
||||
"title": 1,
|
||||
},
|
||||
"topptekst-og-kommentar.docx": {
|
||||
"cell": 2,
|
||||
"comment": 1,
|
||||
"endnote": 1,
|
||||
"footnote": 1,
|
||||
"header_footer": 2,
|
||||
"heading": 1,
|
||||
"image": 0,
|
||||
"paragraph": 3,
|
||||
"table": 1,
|
||||
"text_box": 1,
|
||||
},
|
||||
"notater-og-skjult.pptx": {
|
||||
"cell": 4,
|
||||
"hidden_slide": 1,
|
||||
"image": 0,
|
||||
"note": 1,
|
||||
"paragraph": 2,
|
||||
"slide": 1,
|
||||
"table": 2,
|
||||
"title": 2,
|
||||
},
|
||||
"skjult-ark-og-formel.xlsx": {
|
||||
"cell": 7,
|
||||
"formula": 1,
|
||||
"hidden_sheet": 1,
|
||||
"image": 1,
|
||||
"row": 3,
|
||||
"sheet": 1,
|
||||
},
|
||||
"logg.txt": {"line": 4, "paragraph": 3},
|
||||
"mengder.csv": {"cell": 6, "header_cell": 3, "row": 2},
|
||||
"parametre.json": {"key": 6, "value": 6},
|
||||
"prosess-84-tabell.pdf": {"image": 2, "page": 1},
|
||||
"prosess-84-web.html": {
|
||||
"cell": 0,
|
||||
"heading": 1,
|
||||
"image": 3,
|
||||
"list_item": 0,
|
||||
"paragraph": 3,
|
||||
"table": 0,
|
||||
},
|
||||
"prosess-84-notat.docx": {
|
||||
"cell": 0,
|
||||
"comment": 0,
|
||||
"endnote": 0,
|
||||
"footnote": 0,
|
||||
"header_footer": 0,
|
||||
"heading": 0,
|
||||
"image": 1,
|
||||
"paragraph": 2,
|
||||
"table": 0,
|
||||
"text_box": 0,
|
||||
},
|
||||
"prosess-84-presentasjon.pptx": {
|
||||
"cell": 0,
|
||||
"hidden_slide": 0,
|
||||
"image": 1,
|
||||
"note": 0,
|
||||
"paragraph": 0,
|
||||
"slide": 1,
|
||||
"table": 0,
|
||||
"title": 1,
|
||||
},
|
||||
"prisark.xlsx": {
|
||||
"cell": 18,
|
||||
"formula": 0,
|
||||
"hidden_sheet": 0,
|
||||
"image": 0,
|
||||
"row": 9,
|
||||
"sheet": 2,
|
||||
},
|
||||
"krav-tekstdokument.odt": {
|
||||
"annotation": 0,
|
||||
"cell": 56,
|
||||
"header_footer": 0,
|
||||
"heading": 1,
|
||||
"image": 0,
|
||||
"list_item": 0,
|
||||
"paragraph": 2,
|
||||
"table": 2,
|
||||
},
|
||||
"liste-og-bilde.odt": {
|
||||
"annotation": 1,
|
||||
"cell": 2,
|
||||
"header_footer": 2,
|
||||
"heading": 1,
|
||||
"image": 1,
|
||||
"list_item": 2,
|
||||
"paragraph": 4,
|
||||
"table": 1,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def test_every_element_type_the_witness_counts_occurs_in_a_fixture() -> None:
|
||||
"""M-3: 20 of 63 element types had a count of 0 in their only fixture, so
|
||||
six of seven witness mutants survived -- a witness cannot be wrong about
|
||||
something it never sees."""
|
||||
pytest.importorskip("pdfplumber")
|
||||
inventory = gate.load_inventory(gate.INVENTORY)
|
||||
seen: dict[str, int] = {}
|
||||
for entry in inventory["documents"].values():
|
||||
for role, number in entry["elements"].items():
|
||||
key = f"{entry['suffix']} {role}"
|
||||
seen[key] = seen.get(key, 0) + number
|
||||
absent = sorted(key for key, number in seen.items() if number == 0)
|
||||
assert absent == [], f"element types with no occurrence in any fixture: {absent}"
|
||||
|
||||
|
||||
def test_the_hand_counts_cover_every_document_of_the_corpus() -> None:
|
||||
"""Row 1's fasit is the witness's own output, so a hand count is the only
|
||||
thing in the loop that the witness did not produce. Four of thirteen
|
||||
documents had one; every document has one now."""
|
||||
inventory = gate.load_inventory(gate.INVENTORY)
|
||||
assert sorted(HAND_COUNTS) == sorted(inventory["documents"])
|
||||
|
||||
|
||||
@pytest.mark.parametrize("name", sorted(HAND_COUNTS))
|
||||
def test_the_witness_matches_a_hand_count(name: str) -> None:
|
||||
inventory = witness.witness_file(gate.CORPUS, gate.CORPUS / name)
|
||||
|
|
@ -162,6 +309,67 @@ def test_the_witness_refuses_a_doctype() -> None:
|
|||
witness.count_sts_xml(b'<!DOCTYPE x [<!ENTITY a "b">]><standard/>')
|
||||
|
||||
|
||||
# --- M-1 / M-2: what the witness could not see -------------------------------
|
||||
#
|
||||
# Written RED 2026-09-18. Rows 2 and 3 require the build's inventory to EQUAL
|
||||
# the witness's, so what the witness does not count, nothing can account for:
|
||||
# an independent review put a header and a comment in a docx, measured 0 of
|
||||
# either in the bundle, and the accounting still read "2 of 2 carried".
|
||||
|
||||
|
||||
def test_the_docx_witness_counts_a_header_a_comment_and_a_text_box() -> None:
|
||||
inventory = witness.witness_file(gate.CORPUS, gate.CORPUS / "topptekst-og-kommentar.docx")
|
||||
for role in ("header_footer", "comment", "endnote", "text_box"):
|
||||
assert inventory.elements[role] > 0, role
|
||||
assert "Utkast" in " ".join(
|
||||
piece for pieces in inventory.texts["header_footer"] for piece in pieces
|
||||
)
|
||||
|
||||
|
||||
def test_the_pptx_witness_counts_notes_and_does_not_call_a_hidden_slide_a_slide() -> None:
|
||||
inventory = witness.witness_file(gate.CORPUS, gate.CORPUS / "notater-og-skjult.pptx")
|
||||
assert inventory.elements["note"] > 0
|
||||
assert inventory.elements["hidden_slide"] == 1
|
||||
|
||||
|
||||
def test_the_xlsx_witness_counts_a_formula_and_a_hidden_sheet() -> None:
|
||||
inventory = witness.witness_file(gate.CORPUS, gate.CORPUS / "skjult-ark-og-formel.xlsx")
|
||||
assert inventory.elements["formula"] > 0
|
||||
assert inventory.elements["hidden_sheet"] == 1
|
||||
|
||||
|
||||
def test_the_odt_witness_counts_a_header_and_does_not_call_an_annotation_prose() -> None:
|
||||
inventory = witness.witness_file(gate.CORPUS, gate.CORPUS / "liste-og-bilde.odt")
|
||||
assert inventory.elements["header_footer"] > 0
|
||||
assert inventory.elements["annotation"] == 1
|
||||
|
||||
|
||||
def test_the_sts_witness_counts_citations_formulas_and_figure_captions() -> None:
|
||||
inventory = witness.witness_file(gate.CORPUS, gate.CORPUS / "sts-rikt.xml")
|
||||
for role in ("citation", "math", "figure", "figure_caption"):
|
||||
assert inventory.elements[role] > 0, role
|
||||
|
||||
|
||||
def test_the_two_sts_role_maps_are_written_twice_and_not_shared() -> None:
|
||||
"""M-2: both STS witnesses went through ONE `_sts_role`, so row 5 could
|
||||
never see a hole in it. Two maps, each written for its own delivery."""
|
||||
assert witness._sts_role_xml is not witness._sts_role_json
|
||||
assert "_sts_role_json" not in witness._sts_role_xml.__code__.co_names
|
||||
assert "_sts_role_xml" not in witness._sts_role_json.__code__.co_names
|
||||
|
||||
|
||||
def test_every_witnessed_type_says_what_it_does_not_count() -> None:
|
||||
assert set(witness.NOT_COUNTED) == set(witness.WITNESSED_SUFFIXES)
|
||||
assert all(witness.NOT_COUNTED[suffix] for suffix in witness.WITNESSED_SUFFIXES)
|
||||
|
||||
|
||||
def test_the_gate_prints_what_each_witness_does_not_count() -> None:
|
||||
rendered = gate.render([])
|
||||
assert "not counted" in rendered
|
||||
for suffix in witness.WITNESSED_SUFFIXES:
|
||||
assert suffix in rendered
|
||||
|
||||
|
||||
# --- 2. every row can go both ways -------------------------------------------
|
||||
|
||||
|
||||
|
|
@ -628,18 +836,43 @@ def test_the_door_exists() -> None:
|
|||
assert gate.door_available()
|
||||
|
||||
|
||||
def test_the_real_gate_is_green_on_every_fixture_row(real_rows: list[gate.Row]) -> None:
|
||||
def test_the_real_gate_names_what_the_build_does_not_account_for(
|
||||
real_rows: list[gate.Row],
|
||||
) -> None:
|
||||
"""Rows 1-5 against the real `okf build`; row 6 needs R761 and is skipped
|
||||
here. Written red at 0b00de4 (rows 2, 3, 4 red), green once the build
|
||||
accounts for every element."""
|
||||
here.
|
||||
|
||||
Rows 2 and 3 were GREEN at `864570b` and are RED now, and that is the
|
||||
hardening working rather than a regression: the witness counts thirteen
|
||||
classes of content it could not see before, the build accounts for none
|
||||
of them, and a number nobody counts is a loss nobody can report. The
|
||||
named classes are the raw material for the next capability order.
|
||||
"""
|
||||
assert [(r.number, r.status) for r in real_rows] == [
|
||||
(1, gate.GREEN),
|
||||
(2, gate.GREEN),
|
||||
(3, gate.GREEN),
|
||||
(2, gate.RED),
|
||||
(3, gate.RED),
|
||||
(4, gate.GREEN),
|
||||
(5, gate.GREEN),
|
||||
(6, gate.SKIPPED),
|
||||
]
|
||||
assert real_rows[2].reason.startswith(
|
||||
"u = 0 unaccounted, d = 0 double-booked, 0 booked carried and not in the bundle"
|
||||
)
|
||||
unaccounted = " ".join(real_rows[2].details)
|
||||
for element in (
|
||||
"annotation",
|
||||
"citation",
|
||||
"comment",
|
||||
"endnote",
|
||||
"figure",
|
||||
"figure_caption",
|
||||
"formula",
|
||||
"header_footer",
|
||||
"hidden_sheet",
|
||||
"hidden_slide",
|
||||
"math",
|
||||
"note",
|
||||
"text_box",
|
||||
):
|
||||
assert f"{element}: 0 booked of" in unaccounted, element
|
||||
# And no false red from the gate's own reading: every element the build
|
||||
# DOES book as carried was found in the bundle.
|
||||
assert "0 claimed and not found" in real_rows[2].details[0]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue