fix(retrieval-gate): the judge reads a passage as its exact reconstruction — M15 felled, row 7 15 of 15

Row 8's judge required every delivered text to be the whole concept's bytes
(the row-6 identity), which is older than v1.1 C1: an excerpt `as_passage` cut
to its answering passage was class e even when its span carried the citation.
PM's re-measurement of e503f6a found that the dominant miss class of row 8 on
the real set.

`passage_span` accepts a passage only when the delivered text IS what the
bundle's bytes rebuild: the span is body[start:end] byte for byte in the
DELIVERED body (the offsets land a frontmatter's length off in the file), `of`
is that body's length, `[...]` stands exactly where text is left out, and the
one other line allowed is a heading line of the body above the span, or a
prefix of one. The citation is read in the span alone. A passage that is not
its reconstruction is class e with its own detail. A quote only in the heading
or the markers is class d.

Chose to check that the heading is one of the body's own heading lines above
the span, and not to re-derive WHICH heading `as_passage` picks, because the
guarantee is "the bundle's bytes"; the nearest-heading rule is the product's
presentation and a judge that copies it agrees by construction.

M15 (a passage carrying one sentence the file does not) is row 7's fifteenth
mutant. No synthetic concept was long enough to be cut, so a new fixture
delivers one (DELIVERY, ~6 600 characters, set set-passage.json); the corpus
pin moved with it. MUTANT_BAR unchanged, src/ untouched.

Gate, synthetic: rows 1 and 6 13/13 -> 14/14, row 7 14/14 -> 15 of 15 GREEN
(M15 -> row 1, 6), rows 2/3/4 unchanged, GATE RED: rows 5, 8. Row 8 rerun on
the real set: no class e miss remains (its numbers are kept out of this public
history, per the wiki directive). Suite after git add,
FORCE_COLOR unset: 2450 passed, 1 skipped (+6). ruff, format, mypy --strict
clean.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Kjell Tore Guttormsen 2026-09-22 23:53:09 +02:00
commit cb0c10b421
5 changed files with 199 additions and 29 deletions

View file

@ -0,0 +1,13 @@
{
"set_id": "synthetic-passage",
"bundle": "delivery",
"note": "One question whose answer lies in a concept over consume.PASSAGE_CHARS, so it is delivered as a passage: heading, [...], span, [...]. The span carries the citation. Rows 1 and 6 count it only when the judge accepts a passage that is its exact reconstruction from the bundle's bytes, and M15 (one sentence added to the passage) fells both.",
"questions": [
{
"id": "PD1",
"question": "Hvem gir varmepumpa i kjelleren service?",
"fasit": [{"by": "concept", "value": "husbok/drift", "quote": "service av roerleggeren"}]
}
],
"controls": []
}

View file

@ -637,13 +637,16 @@ def test_the_gate_is_red_today_and_says_which_rows(tmp_path: Path) -> None:
# Rows 2 and 3 are green again since the synthetic corpus was re-measured
# for BM25 (2026-09-21). Row 7 fells all 14 since 2026-09-22, when the three
# v1.1 survivors each got a fixture that makes their mechanism decide a
# delivery (`set-mechanisms.json`, three more row-1 units).
# delivery (`set-mechanisms.json`, three more row-1 units), and all 15
# since the judge reads a passage as its exact reconstruction and one
# fixture delivers a passage (`set-passage.json`, one more unit), so M15
# has something to fell.
assert [row.number for row in rows if row.fails] == [5, 8]
assert (by_number[1].k, by_number[1].m) == (13, 13)
assert (by_number[1].k, by_number[1].m) == (14, 14)
assert (by_number[2].k, by_number[2].m) == (7, 7)
assert (by_number[3].k, by_number[3].m) == (5, 5)
assert (by_number[6].k, by_number[6].m) == (13, 13)
assert (by_number[7].k, by_number[7].m) == (14, 14)
assert (by_number[6].k, by_number[6].m) == (14, 14)
assert (by_number[7].k, by_number[7].m) == (15, 15)
def test_the_same_tree_measures_the_same_twice(tmp_path: Path) -> None: