Gap 4 (count=1) is closed. scan_lexicon still gives one finding per pattern id at the first offset, and its count is now the number of matches: the most matches any one view of the text shows (raw, decoded, folded, rot13). Views are not summed, so count is a lower bound; the joiner row counts only the splits it reports. The matrix case moved from gap to caught (lexicon:count), and the gap register records it closed. Measured before and after on the same trees: the FP populations are identical line for line at 106/198, 196/2918, 108/389; all 3583 documents (with the 78 pinned OKF concepts) keep their disposition under both presets and the same labels and severities; OKF import stays 47/31. 53 documents now carry count > 1 (23 + 23 + 7 + 0), as measured before building. scan_lexicon over 3505 documents: 96.0 s before, 97.1 s after. Mutating count back to 1 turns 5 tests red. docs/LIMITATIONS.md: gaps 2, 3, 5 and 7 now state the rule tried and its cost (session 1 numbers): 2 fires on the true twin, 3 costs 48 on the trusted gate, 5 costs 23 of 569 READMEs, 7 catches 2 of 5 rewordings. Matrix 145/145 + 6/6 gaps, suite 1260 passed.
60 lines
2.4 KiB
Python
60 lines
2.4 KiB
Python
"""The seven documented gaps are a frozen denominator: row 2 of the 2026-11 done-criterion.
|
|
|
|
Row 2 (`docs/PLAN.md` § V.2) counts, for each documented gap, whether it is closed
|
|
or stands with a measured reason in `docs/LIMITATIONS.md`. The denominator is the
|
|
seven frozen in session 1 (2026-11-01) as `docs/threat-2026/gaps-frozen.tsv`,
|
|
whose sha256 is pinned here and in the plan.
|
|
|
|
This file fails when the frozen list is edited, when a gap disappears from the
|
|
coverage matrix without being recorded as closed below, or when a new gap case
|
|
appears without being frozen. Closing a gap is therefore a deliberate edit of
|
|
``CLOSED`` in the same commit that moves the matrix case from gap to caught.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import csv
|
|
import hashlib
|
|
from pathlib import Path
|
|
|
|
from llm_ingestion_guard.coverage import CORE_CASES
|
|
|
|
ROOT = Path(__file__).resolve().parent.parent
|
|
FROZEN = ROOT / "docs" / "threat-2026" / "gaps-frozen.tsv"
|
|
FROZEN_SHA256 = "b1a6d5be5fbfc81263e55135abf11bc6fe349eac82db0d1e84f9e44949b9d22a"
|
|
|
|
# tag -> the `expect` of the caught case that now holds it: a finding label, or
|
|
# the name of a predicate case when the closing rule adds no label (gap 4).
|
|
CLOSED: dict[str, str] = {"dedup": "count"}
|
|
|
|
|
|
def _rows() -> list[dict[str, str]]:
|
|
with FROZEN.open(encoding="utf-8", newline="") as fh:
|
|
return list(csv.DictReader(fh, delimiter="\t"))
|
|
|
|
|
|
def test_frozen_list_is_unchanged():
|
|
assert hashlib.sha256(FROZEN.read_bytes()).hexdigest() == FROZEN_SHA256
|
|
|
|
|
|
def test_denominator_is_seven():
|
|
rows = _rows()
|
|
assert [r["n"] for r in rows] == [str(i) for i in range(1, 8)]
|
|
assert len({r["tag"] for r in rows}) == 7
|
|
|
|
|
|
def test_every_frozen_gap_is_open_in_the_matrix_or_recorded_closed():
|
|
gaps = {c.expect for c in CORE_CASES if c.status == "gap"}
|
|
caught = {c.expect for c in CORE_CASES if c.status == "caught"}
|
|
for row in _rows():
|
|
tag = row["tag"]
|
|
if tag in CLOSED:
|
|
assert tag not in gaps, f"{tag} recorded closed but still a gap case"
|
|
assert CLOSED[tag] in caught, f"{tag} closed by {CLOSED[tag]}, which no caught case asserts"
|
|
else:
|
|
assert tag in gaps, f"{tag} left the matrix without being recorded in CLOSED"
|
|
|
|
|
|
def test_no_gap_case_outside_the_frozen_list():
|
|
frozen = {r["tag"] for r in _rows()}
|
|
extra = {c.expect for c in CORE_CASES if c.status == "gap"} - frozen
|
|
assert not extra, f"gap cases not in the frozen denominator: {sorted(extra)}"
|