feat(propose,cli): the sheet's rows, the wrapped sentence, and a default that moved
K3 round 3, order 20260908T170037Z-3622420612-from-.claude, plus the PM injection of 17:55Z carrying the operator's D3 answer. TWO NEW RULES, both OFF by default. `--sheet-section-rows` cuts an open table block at the rows that label its sections: a RUN of at least three rows whose first cell is a bare numeric label. It is the first rule here that opens a span INSIDE a table, and the opposite direction from Arm E -- that arm decides how far a block extends, this decides where it is cut inside. The spreadsheet is the one file type no arm had ever moved: 1 concept -> 12 on the priced sheet, of which 11 are the reference's 11 cost groups. The run guard buys exactly three candidates on the corpus (the rows stating a computation basis), and each section carries its own `source_sheet` + `source_rows`, verified on the artifact. `--drop-wrapped-outline` declines an outline candidate whose line continues onto the next one. Round 2 falsified the LENGTH axis on this case; this one is not about size. Over the 12-document sample it separates 8 of 34 candidates -- the quoted regulation paragraphs and the risk-table rows -- and none of the 26 the operator kept. Position 4: 4 concepts -> 1, the reference. The cell is `treffer` by count and lossy by bytes, and the report says so. Whole corpus, one cached extraction: 1 of 39 documents changes under the first rule (0 of 32 pdf, 0 of 5 docx), 5 of 39 under the second (all pdf). Arm B and F2 are byte-identical against a frozenc3b645b, both halves by `diff -r`. THE `okf build` DEFAULT MOVED, and this is the operator's decision executed, not a side effect: no flag now means `--outline-run 3 --unit-fold`. Each arm keeps an explicit opt-out (`--outline-run 0`, `--no-unit-fold`) and the pair reproduces the pre-move bytes exactly. The proposer's own defaults do NOT move: they are what the goldens and every published reproduction block are pinned to, so the two layers now disagree on purpose and `cli.py` says where. Two shipped expectations moved with it and both are stated in place. MEASURED AND REPORTED BACK: the configuration made default scores 2 of 12 on the unit worksheet, `docx` 0 of 3 -- Arm F's published 5 of 12 was measured with `--table-grid` ON, and without it the fold has no joined table to fold. Position 1 is declined for the second round on the axis the order named: 3 of 3 recovered chapters have body under them, so "heading-on-heading" separates 0 of 3. The K2 ranking control round 2 could not measure: two bundles from frozenc3b645bdiffering only in `--keep-table-heading`, 2 of 1108 files apart. The priced concept ranks 96 of 629 in BOTH and is delivered at rank 10 in both, so the flag buys 35 payload bytes and zero rank positions. Round 2's prediction is falsified: the concept's NAME was already restored by the orphan-name inheritance; the flag adds the heading LINE. The stale S7 literal is restated where it stood (`c759a657...`, 171 614 B, re-measured on this HEAD), so the next order cannot inherit the superseded one. 1397 -> 1414 tests. Report: docs/2026-09-08-k3-runde3-per-filtype.md. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
c3b645bccf
commit
47f6619e6f
11 changed files with 1381 additions and 36 deletions
|
|
@ -29,6 +29,7 @@ from __future__ import annotations
|
|||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
from collections.abc import Sequence
|
||||
from dataclasses import replace
|
||||
from pathlib import Path
|
||||
|
||||
|
|
@ -82,12 +83,16 @@ def tree(root: Path) -> dict[str, bytes]:
|
|||
}
|
||||
|
||||
|
||||
def two_script_bundle(inbox: Path, out: Path) -> Path:
|
||||
def two_script_bundle(inbox: Path, out: Path, proposer_flags: Sequence[str] = ()) -> Path:
|
||||
"""The path this command replaces, run as it is documented, as subprocesses.
|
||||
|
||||
Re-implementing the loop here would compare the CLI against a copy of
|
||||
itself. Driving the actual scripts is what makes the byte comparison mean
|
||||
"the old path and the new path agree".
|
||||
|
||||
`proposer_flags` is passed through verbatim. It exists because the BUILD
|
||||
default moved and the proposer's did not: the two paths still agree, and
|
||||
saying which flags make them agree is the honest form of that claim.
|
||||
"""
|
||||
plans = out / "plans"
|
||||
plans.mkdir(parents=True, exist_ok=True)
|
||||
|
|
@ -109,6 +114,7 @@ def two_script_bundle(inbox: Path, out: Path) -> Path:
|
|||
relative.with_suffix("").as_posix(),
|
||||
"--proposed-at",
|
||||
PROPOSED_AT,
|
||||
*proposer_flags,
|
||||
],
|
||||
capture_output=True,
|
||||
check=True,
|
||||
|
|
@ -545,11 +551,18 @@ def test_the_arms_reach_the_proposer_from_the_build_command(tmp_path: Path) -> N
|
|||
|
||||
|
||||
def test_the_build_default_is_unchanged_by_the_flags_existing(tmp_path: Path) -> None:
|
||||
"""The control the test above rests on: same inbox, no flags, Arm B.
|
||||
"""The control the test above rests on: same inbox, no flags, the DEFAULT.
|
||||
|
||||
Both halves over the same fixture. Without this, a change that turned an
|
||||
arm ON by default would leave the assertion above green and move every
|
||||
bundle every consumer builds.
|
||||
**This expectation MOVED on 2026-09-08 and the move is stated rather than
|
||||
quietly rewritten.** It was written in round 2 to pin the flagless build to
|
||||
Arm B, so that a change turning an arm on by default could not hide behind
|
||||
the assertion above. The operator then turned two arms on, deliberately,
|
||||
and the titles below are Arm D plus Arm F on this fixture.
|
||||
|
||||
What the test still measures is unchanged and is the reason it stays: the
|
||||
flagless build has ONE declared behaviour and a drifting default moves it.
|
||||
`test_each_arm_in_the_default_has_an_explicit_opt_out` holds the other end,
|
||||
where Arm B's titles are still asserted, from the opt-out.
|
||||
"""
|
||||
inbox = inbox_for_arms(tmp_path)
|
||||
plans = tmp_path / "plans-plain"
|
||||
|
|
@ -564,6 +577,236 @@ def test_the_build_default_is_unchanged_by_the_flags_existing(tmp_path: Path) ->
|
|||
)
|
||||
== 0
|
||||
)
|
||||
# Arm F folds `4.1 Loesmasser` into `4 Grunnforhold`; the sheet heading is
|
||||
# still carried by the table block, because `--keep-table-heading` did NOT
|
||||
# move and is still a flag.
|
||||
assert plan_titles(plans) == ["4 Grunnforhold", "5 Prissammenstilling"]
|
||||
|
||||
|
||||
# A sheet whose units are ROWS: one heading, one continuous table block, a
|
||||
# preamble and a run of three numbered rows. Kept apart from ARM_DOCUMENTS so
|
||||
# the round-2 assertions above keep measuring what they were written for --
|
||||
# `epsilon.md`'s table has ONE numbered row, which is a quantity and not a run.
|
||||
SHEET_DOCUMENT = {
|
||||
"zeta.md": (
|
||||
"## 6 Kostnadsoversikt\n\n"
|
||||
"| Skjema | | |\n|----|----|----|\n"
|
||||
"| Skjemaet fylles ut i sin helhet. | | |\n"
|
||||
"| 01 | Felleskostnader | |\n"
|
||||
"| 02 | Bygning | |\n"
|
||||
"| 07 | Utendoers | |\n"
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
def inbox_for_sheet_sections(root: Path) -> Path:
|
||||
inbox = root / "sheet-inbox"
|
||||
inbox.mkdir(parents=True, exist_ok=True)
|
||||
for name, body in SHEET_DOCUMENT.items():
|
||||
(inbox / name).write_text(body, encoding="utf-8", newline="")
|
||||
return inbox
|
||||
|
||||
|
||||
def test_sheet_section_rows_reaches_the_proposer_from_the_build_command(tmp_path: Path) -> None:
|
||||
"""The red test for round 3: D3 exists here and it changes the plan.
|
||||
|
||||
Asserted on the plan's TITLES, for the reason round 2's is: a rule that
|
||||
produced the right number of concepts by cutting in the wrong places would
|
||||
pass a count assertion. Each title carries the row's own label, which is
|
||||
what makes the concept findable by the number a reader is holding.
|
||||
"""
|
||||
inbox = inbox_for_sheet_sections(tmp_path)
|
||||
plans = tmp_path / "plans-sheet"
|
||||
assert (
|
||||
build(
|
||||
inbox,
|
||||
tmp_path / "bundle-sheet",
|
||||
"--plans-dir",
|
||||
str(plans),
|
||||
"--proposed-at",
|
||||
PROPOSED_AT,
|
||||
"--sheet-section-rows",
|
||||
)
|
||||
== 0
|
||||
)
|
||||
assert plan_titles(plans) == [
|
||||
"6 Kostnadsoversikt",
|
||||
"01 Felleskostnader",
|
||||
"02 Bygning",
|
||||
"07 Utendoers",
|
||||
]
|
||||
|
||||
|
||||
def test_the_build_default_leaves_a_sheet_as_one_concept(tmp_path: Path) -> None:
|
||||
"""The control the test above rests on: same inbox, no flag, one concept.
|
||||
|
||||
Without it, a change that turned D3 on by default would leave the assertion
|
||||
above green while cutting every table a consumer has ever ingested.
|
||||
"""
|
||||
inbox = inbox_for_sheet_sections(tmp_path)
|
||||
plans = tmp_path / "plans-sheet-plain"
|
||||
assert (
|
||||
build(
|
||||
inbox,
|
||||
tmp_path / "bundle-sheet-plain",
|
||||
"--plans-dir",
|
||||
str(plans),
|
||||
"--proposed-at",
|
||||
PROPOSED_AT,
|
||||
)
|
||||
== 0
|
||||
)
|
||||
assert plan_titles(plans) == ["6 Kostnadsoversikt"]
|
||||
|
||||
|
||||
# A document whose numbering is RECOVERED and whose first numbered line is a
|
||||
# wrapped sentence: the shape of quoted regulation text.
|
||||
WRAPPED_DOCUMENT = {
|
||||
"eta.md": (
|
||||
"Innledning uten nummer.\n\n"
|
||||
"1 Krav og kriterier etter denne bestemmelsen skal ha som maal aa\n"
|
||||
"redusere anskaffelsens samlede klimaavtrykk.\n\n"
|
||||
"2 Ordinaert kapittel\n\nDette kapittelet har sin egen kropp.\n\n"
|
||||
"3 Tredje kapittel\n\nOgsaa dette er en kropp.\n"
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
def inbox_for_wrapped_outline(root: Path) -> Path:
|
||||
inbox = root / "wrapped-inbox"
|
||||
inbox.mkdir(parents=True, exist_ok=True)
|
||||
for name, body in WRAPPED_DOCUMENT.items():
|
||||
(inbox / name).write_text(body, encoding="utf-8", newline="")
|
||||
return inbox
|
||||
|
||||
|
||||
def test_drop_wrapped_outline_reaches_the_proposer_from_the_build_command(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
"""The second red test for round 3, and its control is the pair below.
|
||||
|
||||
The build path had four flags and the proposer now has six. A rule that
|
||||
only the proposer can run is a rule no bundle is ever built with, which is
|
||||
the defect round 2 fixed for the arms.
|
||||
"""
|
||||
inbox = inbox_for_wrapped_outline(tmp_path)
|
||||
plans = tmp_path / "plans-wrapped"
|
||||
assert (
|
||||
build(
|
||||
inbox,
|
||||
tmp_path / "bundle-wrapped",
|
||||
"--plans-dir",
|
||||
str(plans),
|
||||
"--proposed-at",
|
||||
PROPOSED_AT,
|
||||
"--outline-run",
|
||||
"3",
|
||||
"--drop-wrapped-outline",
|
||||
)
|
||||
== 0
|
||||
)
|
||||
assert plan_titles(plans) == ["Ordinaert kapittel", "Tredje kapittel"]
|
||||
|
||||
|
||||
def test_the_build_default_keeps_a_wrapped_outline_candidate(tmp_path: Path) -> None:
|
||||
"""The control: Arm D alone recovers all three, including the sentence."""
|
||||
inbox = inbox_for_wrapped_outline(tmp_path)
|
||||
plans = tmp_path / "plans-wrapped-plain"
|
||||
assert (
|
||||
build(
|
||||
inbox,
|
||||
tmp_path / "bundle-wrapped-plain",
|
||||
"--plans-dir",
|
||||
str(plans),
|
||||
"--proposed-at",
|
||||
PROPOSED_AT,
|
||||
"--outline-run",
|
||||
"3",
|
||||
)
|
||||
== 0
|
||||
)
|
||||
assert plan_titles(plans) == [
|
||||
"Krav og kriterier etter denne bestemmelsen skal ha som maal aa",
|
||||
"Ordinaert kapittel",
|
||||
"Tredje kapittel",
|
||||
]
|
||||
|
||||
|
||||
# --- the default moved (operator, 2026-09-08) ------------------------------
|
||||
#
|
||||
# Round 3. The operator answered the standing question with alternative (b):
|
||||
# `okf build` with no flag is now Arm D plus Arm F, `--outline-run 3
|
||||
# --unit-fold`. Two arms and not four -- `--table-grid` and
|
||||
# `--keep-table-heading` stay flags until the K2 ranking control is measured.
|
||||
#
|
||||
# Every arm keeps an EXPLICIT opt-out, because a default nobody can turn off is
|
||||
# not a default, it is a behaviour: `--outline-run 0` for Arm D (the number was
|
||||
# always its own switch) and `--no-unit-fold` for Arm F.
|
||||
|
||||
|
||||
def test_the_build_default_is_now_arm_d_plus_arm_f(tmp_path: Path) -> None:
|
||||
"""The red test for the move: no flags must EQUAL the two flags, byte for byte.
|
||||
|
||||
Asserted on the bundle rather than on the parser, and byte for byte rather
|
||||
than on a count: a default that reached the proposer as a different number
|
||||
would still produce a plausible plan, and only the bytes can tell.
|
||||
"""
|
||||
inbox = inbox_for_arms(tmp_path)
|
||||
default = tmp_path / "default-bundle"
|
||||
explicit = tmp_path / "explicit-bundle"
|
||||
assert build(inbox, default, "--proposed-at", PROPOSED_AT) == 0
|
||||
assert (
|
||||
build(
|
||||
inbox,
|
||||
explicit,
|
||||
"--proposed-at",
|
||||
PROPOSED_AT,
|
||||
"--outline-run",
|
||||
"3",
|
||||
"--unit-fold",
|
||||
)
|
||||
== 0
|
||||
)
|
||||
assert tree(default) == tree(explicit)
|
||||
|
||||
|
||||
def test_each_arm_in_the_default_has_an_explicit_opt_out(tmp_path: Path) -> None:
|
||||
"""The other half: a caller can still get the pre-move behaviour, and say so.
|
||||
|
||||
`--outline-run 0 --no-unit-fold` must reproduce Arm B exactly. Without
|
||||
this, the move would be irreversible for every consumer who needs the old
|
||||
bytes, and "off by default" would have become "unreachable".
|
||||
"""
|
||||
inbox = inbox_for_arms(tmp_path)
|
||||
opted_out = tmp_path / "opt-out-bundle"
|
||||
assert (
|
||||
build(
|
||||
inbox,
|
||||
opted_out,
|
||||
"--proposed-at",
|
||||
PROPOSED_AT,
|
||||
"--outline-run",
|
||||
"0",
|
||||
"--no-unit-fold",
|
||||
)
|
||||
== 0
|
||||
)
|
||||
plans = tmp_path / "plans-opt-out"
|
||||
assert (
|
||||
build(
|
||||
inbox,
|
||||
tmp_path / "opt-out-plans-bundle",
|
||||
"--plans-dir",
|
||||
str(plans),
|
||||
"--proposed-at",
|
||||
PROPOSED_AT,
|
||||
"--outline-run",
|
||||
"0",
|
||||
"--no-unit-fold",
|
||||
)
|
||||
== 0
|
||||
)
|
||||
# Arm B's own titles, the ones round 2 pinned for the flagless build.
|
||||
assert plan_titles(plans) == ["4 Grunnforhold", "4.1 Loesmasser", "5 Prissammenstilling"]
|
||||
|
||||
|
||||
|
|
@ -572,12 +815,42 @@ def test_a_bundle_built_with_no_flags_is_byte_identical_to_the_shipped_one(
|
|||
) -> None:
|
||||
"""The byte control, on the tree the other build tests use.
|
||||
|
||||
The proposer gained a parameter and the CLI gained four. A default that
|
||||
moved by a byte would break rebuild-equals-incremental for every consumer
|
||||
who never passes a flag, and a title assertion cannot see that.
|
||||
**This expectation MOVED on 2026-09-08 and the move is stated rather than
|
||||
quietly rewritten.** Until then `okf build` with no flag was Arm B, so it
|
||||
equalled the two-script path run with no flag either. The operator moved
|
||||
the default to Arm D plus Arm F, so the equivalence now has to name the
|
||||
arms on one side or the other, and both halves are asserted:
|
||||
|
||||
- the two-script path with the two arms equals the new default;
|
||||
- the two-script path with no arm equals `--outline-run 0 --no-unit-fold`,
|
||||
which is what makes the move reversible for a consumer who needs the old
|
||||
bytes.
|
||||
|
||||
A single half would leave the other unmeasured, and the second is the one
|
||||
that goes red if an opt-out stops opting out.
|
||||
"""
|
||||
inbox = inbox_with_subdirectories(tmp_path)
|
||||
reference = two_script_bundle(inbox, tmp_path / "reference-2")
|
||||
armed = two_script_bundle(
|
||||
inbox, tmp_path / "reference-armed", proposer_flags=("--outline-run", "3", "--unit-fold")
|
||||
)
|
||||
bundle = tmp_path / "cli-bundle-2"
|
||||
assert build(inbox, bundle, "--ingested-at", INGESTED_AT, "--proposed-at", PROPOSED_AT) == 0
|
||||
assert tree(bundle) == tree(reference)
|
||||
assert tree(bundle) == tree(armed)
|
||||
|
||||
plain = two_script_bundle(inbox, tmp_path / "reference-2")
|
||||
opted_out = tmp_path / "cli-bundle-opt-out"
|
||||
assert (
|
||||
build(
|
||||
inbox,
|
||||
opted_out,
|
||||
"--ingested-at",
|
||||
INGESTED_AT,
|
||||
"--proposed-at",
|
||||
PROPOSED_AT,
|
||||
"--outline-run",
|
||||
"0",
|
||||
"--no-unit-fold",
|
||||
)
|
||||
== 0
|
||||
)
|
||||
assert tree(opted_out) == tree(plain)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue