test(accounting): row 3 has to SAY what a whole refusal cost

P11 and P12, PM 2026-09-19: the `refused={u.refused}` column on row 3's
detail line and the clause "N element(s) lost with R of D document(s) refused
whole" in its reason could each be deleted with this file green at 106
passed. The LOSS is held -- `Unit.refused` keeps the unit unclean and the
note names the source and its code -- but what the ROW says about it was
decoration nothing pinned, and the row is what a reader of the gate's output
sees first.

R, D and the element total are counted over the units the test builds, never
read back off the row. The known-negative is the same units with no refusal:
the sentence must move with them, or it is a constant that happens to read
true.

Red proven in a scratch copy of HEAD (`/tmp/shy-mut`), control green at 109
passed:

  P11 detail line drops `refused={u.refused}`        -> 1 failed (line 666)
  P12 reason drops the whole "... refused whole" clause -> 1 failed (line 661)

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Kjell Tore Guttormsen 2026-09-19 08:40:27 +02:00
commit ee827b9f47
Signed by: ktg
SSH key fingerprint: SHA256:JakMjO6FTBBzN0Bhfj9saOoEjaFxlSdYuZQQpM/lF9Q

View file

@ -632,6 +632,48 @@ def test_row3_is_red_when_no_fate_is_declared(tmp_path: Path) -> None:
assert gate.row3(units, door=False).status == gate.RED
def test_row3_says_how_many_documents_were_refused_whole_and_what_they_cost() -> None:
"""P11 and P12, PM 2026-09-19: row 3's own summary of a refusal, and the
`refused=` column on its detail line, could each be deleted with this file
staying green at 106 passed. The LOSS is held -- `Unit.refused` keeps the
unit unclean and the note names the source and its code -- but what the
ROW says about it was decoration nothing pinned, and the row is what a
reader of the gate's output actually sees.
R, D and the element total are counted HERE, over the units this test
built, never read back off the row. The known-negative is the same units
without a refusal: the sentence has to change with them, or it is a
constant that happens to read true."""
refused_note = "refused whole: 17 element(s) declared rejected `fail_secure`"
units = [
gate.Unit("a.xml", "document", 0, 0, refused=17, notes=[refused_note]),
gate.Unit("b.json", "document", 0, 0, refused=4, notes=["refused whole: 4 element(s)"]),
gate.Unit("c.md", "document", 0, 0, verified=9),
gate.Unit("d.png", "file", 0, 0),
]
documents = [u for u in units if u.kind == "document"]
refused_docs = [u for u in documents if u.refused]
elements = sum(u.refused for u in units)
assert (len(refused_docs), len(documents), elements) == (2, 3, 21)
row = gate.row3(units, door=True)
assert row.status == gate.RED
assert (
f"{elements} element(s) lost with "
f"{len(refused_docs)} of {len(documents)} document(s) refused whole"
) in row.reason
for unit in refused_docs:
assert any(
unit.name in detail and f"refused={unit.refused}" in detail for detail in row.details
), unit.name
clean = [gate.Unit(u.name, u.kind, 0, 0, verified=9) for u in units]
assert (
"0 element(s) lost with 0 of 3 document(s) refused whole"
in gate.row3(clean, door=True).reason
)
def test_a_file_carried_through_a_document_and_rejected_is_double_booked(tmp_path: Path) -> None:
corpus = _corpus(tmp_path)
carried = _assets(corpus / "graphics" / "x.png")