`uv sync --frozen` resolved ruff 0.15.22 and the tree read clean. A loose install resolves 0.16.6, under which the SAME untouched code reports 148 findings -- 4 more than round 9 counted, because this round added four files. All of them are new rules rather than new defects: 0.16 widened the default rule set to whole families (YTT, ASYNC, PL, ISC, C4, UP, B, SIM, FURB, ...). (`[skip-docs]` is for CLAUDE.md, which a lint-configuration change does not reach. README's developer section IS updated in this commit.) THE DEFECT IS NOT THE 148, IT IS THAT NOBODY CHOSE THEM. `[tool.ruff]` set only `line-length` and `target-version`, so the acceptance was ruff's default, and the tree stayed green only as long as the lockfile froze an old ruff. `select` is now written down: `E4`, `E7`, `E9`, `F` (the historical default), `I` because this tree already keeps imports sorted, and `RUF100` so a `noqa` that has stopped meaning anything is caught rather than left as decoration. Pin `ruff>=0.9` -> `ruff>=0.16.6,<0.17`. Per rule, before -> after: RUF100 50 -> 0, I001 20 -> 0, ISC004 19, PLW1510 8, C408 8, EXE001 6, RUF007 5, PLE2515 4, UP031 3, B017 3, and fourteen more with 2 or fewer -- the families out of the declared set are 0 by selection, and 148 is the number to start from if they are adopted, which is a separate decision and not one to take inside a version-pin commit. 57 were auto-fixed; one E402 was reintroduced by the import-sorting fix merging a block away from its `noqa`, and got the directive back rather than a bare one. `S` IS MEASURED OUT, NOT ASSUMED OUT: it reports 2657 `S101` on a suite whose every assertion is an `assert`, and `S603` flags 19 subprocess calls of which one was ever marked -- selecting it buys 18 suppressions and no defect. Two `noqa` directives naming non-selected rules were dropped with that reason recorded in the configuration instead. THE TWO FILES 0.16 WOULD REFORMAT ARE MARKDOWN, NOT PYTHON: `README.md` and `docs/2026-09-08-blindsone-below-k-k2.md`. 0.16 formats fenced Python inside markdown, and both blocks are RECORDS -- the second is a quotation of `COST_VOCABULARY` as it stood when that measurement was taken. Reformatting a quotation makes it stop being one, so markdown is excluded from the formatter and `ruff format --check .` stays in the acceptance over `.py`. `tools/okf_consume_measure.py` is fenced by the order as run-not-edited, so its three findings are exempted by path with the reason and the debt named, and its bytes are untouched. THE LOCKFILE TRAP IS CLOSED, NOT AVOIDED. `uv.lock` predated the `[ocr]` extra, so any unlocked resolve wrote that extra's transitive tree back into it -- 681 insertions over 4 deletions, twice now, and round 9 recorded the cause as `uv run` OUTSIDE the project when it is `uv run` without `--frozen` INSIDE it. The relock is complete for every declared extra (703 insertions, 26 deletions), and measured after it, an unfrozen `uv run` leaves the file alone. `ruff check src tests tools`, `ruff format --check .` (0.16.6), `mypy src` over 21 files and 1535 tests, all green. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
153 lines
6.3 KiB
Python
153 lines
6.3 KiB
Python
"""The outline-reach instrument: phase 2 of order 20260906T213322Z.
|
|
|
|
Measures how far Arm D's outline rule reaches into a corpus, before and after
|
|
the orphan check that deletes a third of what it proposes. The instrument
|
|
exists because `docs/2026-09-04-k3-arm-c.md` published figures a reader could
|
|
not re-derive: a number without a committed script cannot be reproduced, and a
|
|
K3 row resting on one is an assertion rather than a measurement.
|
|
|
|
The identity test below is the one that matters most. An instrument that
|
|
re-implements the grammar it measures is measuring a SECOND definition, free to
|
|
drift from the shipped one without a single test going red -- so the instrument
|
|
imports `outline_lines`, `outline_runs` and `find_candidates` from the tool,
|
|
and this file pins that they are the same objects.
|
|
|
|
The negative control matters as much as the positive one, for the reason
|
|
`test_cid_measure.py` states: an instrument reporting reach on a corpus that
|
|
has none would inflate every number it ever produces. Here the control is
|
|
sharper than "zero" -- it is zero WITH a nonzero denominator, because "found
|
|
nothing" and "measured nothing" are different results and only one of them is
|
|
evidence.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "tools"))
|
|
|
|
import okf_outline_measure
|
|
|
|
from llm_ingestion_okf import propose as okf_propose_segments
|
|
|
|
OUTLINE_DOC = """1 Innledning
|
|
|
|
Bakgrunn for prosjektet og omfanget.
|
|
|
|
2 Krav
|
|
|
|
Krav til seksjonering av bygget.
|
|
|
|
3 Gjennomfoering
|
|
|
|
Framdrift, faser og overlevering.
|
|
"""
|
|
|
|
NO_OUTLINE_DOC = """# Teknisk grunnlag
|
|
|
|
Innledende tekst uten nummerering i det hele tatt.
|
|
|
|
## 3.1 Brannkonsept
|
|
|
|
To uavhengige roemningsveier fra hver branncelle.
|
|
"""
|
|
|
|
|
|
def test_the_instrument_and_the_tool_are_one_definition() -> None:
|
|
"""Identity, not equality: the cheapest proof there is no second grammar."""
|
|
assert okf_outline_measure.outline_runs is okf_propose_segments.outline_runs
|
|
assert okf_outline_measure.outline_lines is okf_propose_segments.outline_lines
|
|
assert okf_outline_measure.find_candidates is okf_propose_segments.find_candidates
|
|
|
|
|
|
def test_a_hand_computed_document_reports_its_boundaries_both_sides_of_the_gate() -> None:
|
|
"""Three chapters, each with a body, so the gate deletes none of them."""
|
|
result = okf_outline_measure.measure_document(OUTLINE_DOC, "doc", run_length=3)
|
|
assert result.pre_gate_boundaries == 3
|
|
assert result.post_gate_boundaries == 3
|
|
assert result.arm_b_entries == 0
|
|
assert result.arm_d_entries == 3
|
|
assert result.deleted_arm_b == 0
|
|
|
|
|
|
def test_the_gate_deletes_an_arm_b_heading_the_outline_immediately_follows() -> None:
|
|
"""The 34 %-deletion mechanism, measured on a document small enough to check.
|
|
|
|
`# Teknisk grunnlag` is followed immediately by `1 Innledning`, so its body
|
|
is empty and the orphan check drops it. Arm B had one entry; Arm D has
|
|
three, and the one Arm B had is gone.
|
|
"""
|
|
text = "# Teknisk grunnlag\n\n1 Innledning\n\nA.\n\n2 Krav\n\nB.\n\n3 Slutt\n\nC.\n"
|
|
result = okf_outline_measure.measure_document(text, "doc", run_length=3)
|
|
assert result.arm_b_entries == 1
|
|
assert result.deleted_arm_b == 1
|
|
assert result.post_gate_boundaries == 3
|
|
|
|
|
|
def test_a_corpus_with_no_outline_reports_zero_with_a_nonzero_denominator() -> None:
|
|
"""The negative control. Zero reach is only evidence if something was measured."""
|
|
result = okf_outline_measure.measure_document(NO_OUTLINE_DOC, "doc", run_length=3)
|
|
assert result.pre_gate_boundaries == 0
|
|
assert result.post_gate_boundaries == 0
|
|
# The denominator: the document WAS measured, and Arm B did find boundaries
|
|
# in it, so a zero here is the rule declining rather than the probe failing.
|
|
assert result.arm_b_entries > 0
|
|
assert result.arm_d_entries == result.arm_b_entries
|
|
|
|
|
|
def test_an_empty_document_does_not_crash() -> None:
|
|
result = okf_outline_measure.measure_document("", "doc", run_length=3)
|
|
assert result.pre_gate_boundaries == 0
|
|
assert result.arm_b_entries == 0
|
|
assert result.arm_d_entries == 0
|
|
assert result.unique_paths == 0
|
|
|
|
|
|
def test_the_sample_draw_reproduces_a_known_ordering() -> None:
|
|
"""The draw is the method's, re-derived: hex SHA-256 of the NFC filename.
|
|
|
|
The expected list is computed by hand from the digests, not by calling the
|
|
function under test -- otherwise the assertion would only prove the code
|
|
agrees with itself.
|
|
"""
|
|
names = [
|
|
"alfa.pdf",
|
|
"beta.pdf",
|
|
"gamma.pdf",
|
|
"delta.docx",
|
|
"epsilon.docx",
|
|
"zeta.xlsx",
|
|
"eta.xlsx",
|
|
"theta.pdf",
|
|
]
|
|
drawn = okf_outline_measure.draw_sample(names, {"pdf": 2, "docx": 1, "xlsx": 1})
|
|
assert drawn == ["theta.pdf", "gamma.pdf", "eta.xlsx", "delta.docx"]
|
|
|
|
|
|
def test_the_draw_normalises_to_nfc_before_hashing() -> None:
|
|
"""macOS hands filenames over decomposed; the two forms hash differently.
|
|
|
|
Without this the same visual corpus would draw a different sample depending
|
|
on which normalisation the filenames arrived in -- the same defect the
|
|
library already fixed in `reduce_to_id_grammar`.
|
|
"""
|
|
# Escapes, not literals: a source file is stored in ONE normalisation, so
|
|
# writing both forms as literals would silently make them the same string
|
|
# and the control below would be green for the wrong reason. `oe` is used
|
|
# because U+00F8 has no canonical decomposition at all -- picking it would
|
|
# make this test vacuous in a second, quieter way.
|
|
composed = "caf\u00e9.pdf" # NFC: e-acute as one code point
|
|
decomposed = "cafe\u0301.pdf" # NFD: `e` plus combining acute
|
|
assert composed != decomposed # the control: they really are different strings
|
|
assert okf_outline_measure._draw_key(composed) == okf_outline_measure._draw_key(decomposed)
|
|
# And a second control: two names that are genuinely different still differ.
|
|
assert okf_outline_measure._draw_key("a.pdf") != okf_outline_measure._draw_key("b.pdf")
|
|
|
|
|
|
def test_a_title_with_no_alphabetic_word_is_counted_as_junk() -> None:
|
|
"""11 of the 95 surviving outline titles are junk; the report needs the count."""
|
|
text = "1 477 3 025\n\nA.\n\n2 Krav\n\nB.\n\n3 D L\n\nC.\n"
|
|
result = okf_outline_measure.measure_document(text, "doc", run_length=3)
|
|
assert result.post_gate_boundaries == 3
|
|
assert result.junk_titles == 2 # `477 3 025` and `D L` -- `Krav` is a word
|