fix(b-gate): a heading is not a section -- the rest of N3, and one rewrapped ledger line [skip-docs]
The measured escape: a runbook that was the five contract-named headings plus "x" (125 characters in all), with a correctly checksummed attestation, read `2 av 2 GRØNN`. Only a file whose whole content was "x" had been refused -- the rule asked whether the section NAME appeared in the text, never whether anything stood under it. A table of contents is not a runbook anyone can follow. Section names are now bound to a HEADING line, and what counts is the body beneath it (`section_bodies`, 80 non-whitespace characters as a floor). The row says so itself, and says what the floor is not: a length is never a measure of whether the runbook is true. That stays the operator's, which is why row 6 is still `IKKE MÅLT`. Two rc-0 controls in the suite carried section bodies of "noe" and "steg 1: naviger pakken" -- both would now fail on the new rule rather than on the rule they were written for, so both got a real body. A control that falls on the wrong rule has stopped controlling. Also: the one very long line in the ledger (the arm name mid-paragraph) rewrapped. Cosmetic, named in the 2026-09-20 checkpoint's leftovers. NOT taken from that same list: freezing the outbox shape so the round-builder's denominator arm stops skipping outside a tree with `scratchpad/`. It needs a checked-in fixture of the measured form, which is not the "only if cheap" the order allowed, and the checkpoint proposed no round for it either. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
6ca4c7eb9f
commit
f430b790a3
3 changed files with 79 additions and 10 deletions
|
|
@ -3155,8 +3155,9 @@
|
|||
dessuten på glob, ikke på denne lista, så en type utenfor den bæres uansett). **Setningen er
|
||||
skrevet om TRE ganger og var usann hver gang; den er nå pinnet** av en arm som teller utboksene
|
||||
og krever at raden sier det tellingen sier
|
||||
(`test_the_ledgers_claim_about_the_five_other_types_is_what_the_repo_measures`). Talt over de fire arkiverte kjøringene sjekkpunktet
|
||||
leste (`tunnel-hauglia-2027` -04/-06/-07/-08; hver `<run_id>-<rest>.json` typet som
|
||||
(`test_the_ledgers_claim_about_the_five_other_types_is_what_the_repo_measures`). Talt over de
|
||||
fire arkiverte kjøringene sjekkpunktet leste (`tunnel-hauglia-2027` -04/-06/-07/-08; hver
|
||||
`<run_id>-<rest>.json` typet som
|
||||
`proposal`/`outcome` når `<rest>` ender der, ellers `<rest>` selv): `-06`, `-07` og `-08` har
|
||||
alle sju (15 filer hver), `-04` har seks (12 filer, ingen `parse-failures` — den skrives bare når
|
||||
noe ikke parset, så FRAVÆRET er signalet). Union = 7, skrevet av TO kommandoer: `run.py` seks,
|
||||
|
|
|
|||
|
|
@ -137,9 +137,16 @@ BUDGET_IN_B = (
|
|||
RUNBOOK_RULE = (
|
||||
"rad 6 regner gaten aldri ut: en kjørebok kan være komplett på papiret og likevel ikke ha "
|
||||
"drevet én analyse. Skrittet til grønt er operatørens egen bekreftelse på at han kjørte den "
|
||||
"og at den virket — en fil gaten ALDRI skriver selv, like lite som attesteringen på v1-gaten"
|
||||
"og at den virket — en fil gaten ALDRI skriver selv, like lite som attesteringen på v1-gaten. "
|
||||
"Det gaten kan se er FORMEN: hver seksjon kontrakten navngir må stå som en overskrift MED "
|
||||
"tekst under seg. En overskrift alene er en innholdsfortegnelse, ikke en kjørebok noen kan "
|
||||
"følge — målt 19.09: de fem overskriftene pluss «x» ga 2 av 2 GRØNN. Lengdekravet er et gulv "
|
||||
"mot den tomme overskriften, aldri et mål på om kjøreboka er sann"
|
||||
)
|
||||
|
||||
#: Hvor mange tegn (blanktegn ikke talt) som må stå under en overskrift før seksjonen er skrevet.
|
||||
RUNBOOK_MIN_BODY = 80
|
||||
|
||||
CONTRACT = """\
|
||||
Radene, og hva som flytter hver av dem:
|
||||
|
||||
|
|
@ -1406,6 +1413,26 @@ def read_runbook_attestation(
|
|||
return RunbookAttestation(True, True, "")
|
||||
|
||||
|
||||
def section_bodies(text: str, sections: Sequence[str]) -> dict[str, str]:
|
||||
"""``seksjon -> tegnene under overskriften``, blanktegn ikke talt, fram til neste overskrift.
|
||||
|
||||
Målt 19.09 (resten av N3): kravet var at seksjonsnavnet fantes I TEKSTEN. En kjørebok som var
|
||||
de fem navnene som overskrifter pluss «x» ga `2 av 2 GRØNN`. Navnet bindes derfor til en
|
||||
OVERSKRIFTSLINJE, og det som teller er hva som står under den."""
|
||||
found: dict[str, str] = {}
|
||||
current: str | None = None
|
||||
for line in text.splitlines():
|
||||
if line.lstrip().startswith("#"):
|
||||
heading = line.lstrip("# ").strip()
|
||||
current = next((s for s in sections if s in heading), None)
|
||||
if current is not None:
|
||||
found.setdefault(current, "")
|
||||
continue
|
||||
if current is not None:
|
||||
found[current] += "".join(line.split())
|
||||
return found
|
||||
|
||||
|
||||
def score_runbook(
|
||||
config: Mapping[str, Any],
|
||||
repo_root: Path,
|
||||
|
|
@ -1421,8 +1448,10 @@ def score_runbook(
|
|||
#: attestering, ga «2 av 2 GRØNN». Tom fil var alt som var avvist. Kontrakten navngir derfor
|
||||
#: seksjonene en kjørebok må bære — det gjør den ikke sann, men det gjør «x» umulig.
|
||||
sections = [str(s) for s in config.get("sections", ())]
|
||||
missing_sections = [s for s in sections if s not in text]
|
||||
has_runbook = bool(text.strip()) and not missing_sections
|
||||
bodies = section_bodies(text, sections)
|
||||
missing_sections = [s for s in sections if s not in bodies]
|
||||
thin_sections = [s for s in sections if s in bodies and len(bodies[s]) < RUNBOOK_MIN_BODY]
|
||||
has_runbook = bool(text.strip()) and not missing_sections and not thin_sections
|
||||
attest_path = attest if attest is not None else repo_root / str(config["attestation"])
|
||||
attestation = read_runbook_attestation(
|
||||
attest_path, list(config["keys"]), runbook, str(config["path"]), now
|
||||
|
|
@ -1437,6 +1466,11 @@ def score_runbook(
|
|||
f"kjørebok: {config['path']} mangler seksjonene kontrakten navngir "
|
||||
f"({', '.join(missing_sections)})"
|
||||
)
|
||||
elif thin_sections:
|
||||
exceptions.append(
|
||||
f"kjørebok: {config['path']} har seksjoner det står ingenting under "
|
||||
f"({', '.join(thin_sections)}) — en overskrift er ikke en seksjon"
|
||||
)
|
||||
if not attestation.ok:
|
||||
exceptions.append(f"attestering: {attestation.why}")
|
||||
return Row(
|
||||
|
|
|
|||
|
|
@ -996,11 +996,18 @@ def test_row6_denominator_comes_from_the_artefacts_the_contract_names(tmp_path:
|
|||
# --- rad 6: attesteringen kan felles ----------------------------------------------------------
|
||||
|
||||
|
||||
#: Én seksjons kropp i rc-0-kontrollen. Lengden er ikke pynt: kontrakten krever at det STÅR noe
|
||||
#: under hver overskrift, og en fixtur med to ord ville gjort kontrollen rød sammen med juksen.
|
||||
_SECTION_BODY = (
|
||||
"steg 1: naviger pakken med toolbox-doeren og les filene den rapporterer.\n"
|
||||
"steg 2: hent kostnadsgrunnlaget fra det prisede skjemaet i samme base.\n"
|
||||
)
|
||||
|
||||
|
||||
def _runbook(root: Path) -> str:
|
||||
"""En kjørebok som bærer seksjonene kontrakten navngir — ellers er «x» en kjørebok (N3)."""
|
||||
sections = "\n".join(
|
||||
f"## {s}\n\nsteg 1: naviger pakken\n" for s in _CONFIG["runbook"]["sections"]
|
||||
)
|
||||
"""En kjørebok som bærer seksjonene kontrakten navngir, med tekst under hver — ellers er «x»
|
||||
en kjørebok (N3), og de fem overskriftene alene er det også (resten av N3, målt 19.09)."""
|
||||
sections = "\n".join(f"## {s}\n\n{_SECTION_BODY}" for s in _CONFIG["runbook"]["sections"])
|
||||
text = f"# kjørebok\n\n{sections}"
|
||||
_write(root / _CONFIG["runbook"]["path"], text)
|
||||
return hashlib.sha256(text.encode("utf-8")).hexdigest()
|
||||
|
|
@ -1463,7 +1470,10 @@ def test_a_runbook_without_the_sections_the_contract_names_is_not_green(tmp_path
|
|||
cfg = _CONFIG["runbook"]
|
||||
sections = list(cfg.get("sections", ()))
|
||||
assert sections, "kontrakten navngir ingen seksjoner — da er «x» en kjørebok"
|
||||
text = "# kjørebok\n\n" + "\n".join(f"## {s}\n\nnoe\n" for s in sections)
|
||||
# Kroppen er _SECTION_BODY og ikke «noe»: fra 20.09 er en overskrift uten tekst under seg
|
||||
# ikke en seksjon (se test_a_runbook_of_headings_alone_is_not_a_runbook), og en rc-0-kontroll
|
||||
# som faller på DEN regelen måler ikke lenger den den er skrevet for.
|
||||
text = "# kjørebok\n\n" + "\n".join(f"## {s}\n\n{_SECTION_BODY}" for s in sections)
|
||||
_write(tmp_path / cfg["path"], text)
|
||||
digest = hashlib.sha256(text.encode("utf-8")).hexdigest()
|
||||
_attestation_lines(tmp_path, digest)
|
||||
|
|
@ -1951,3 +1961,27 @@ def test_a_door_call_that_can_never_run_is_not_an_execution(
|
|||
row = gate.score_toolbox(_one_step(), src, _fake_run_path(), _FAKE_PASSED, tmp_path)
|
||||
assert (row.k, row.n) == (0, 1), (form, row.exceptions)
|
||||
assert any("rører ikke døren" in x for x in row.exceptions), (form, row.exceptions)
|
||||
|
||||
|
||||
def test_a_runbook_of_headings_alone_is_not_a_runbook(tmp_path: Path) -> None:
|
||||
"""Resten av N3: «x» var avvist, men de fem overskriftene PLUSS «x» (125 tegn i alt) ga
|
||||
`2 av 2 GRØNN`. Kontrakten krevde at navnet STO der, ikke at det sto noe under det — og en
|
||||
innholdsfortegnelse er ikke en kjørebok noen kan følge."""
|
||||
digest = _runbook(tmp_path)
|
||||
_attestation_lines(tmp_path, digest)
|
||||
kontroll = gate.score_runbook(_CONFIG["runbook"], tmp_path, now=_NOW)
|
||||
assert (kontroll.k, kontroll.n, kontroll.status) == (2, 2, gate.GREEN), kontroll.exceptions
|
||||
|
||||
tynn = "\n".join(f"## {s}" for s in _CONFIG["runbook"]["sections"]) + "\nx\n"
|
||||
assert len(tynn) < 200, "armen skal måle den formen sjekkpunktet målte"
|
||||
_write(tmp_path / _CONFIG["runbook"]["path"], tynn)
|
||||
_attestation_lines(tmp_path, hashlib.sha256(tynn.encode("utf-8")).hexdigest())
|
||||
row = gate.score_runbook(_CONFIG["runbook"], tmp_path, now=_NOW)
|
||||
assert row.status != gate.GREEN
|
||||
assert row.k == 1, row.exceptions
|
||||
assert any("står ingenting under" in x for x in row.exceptions), row.exceptions
|
||||
|
||||
|
||||
def test_the_runbook_rule_says_that_a_heading_alone_is_not_a_section() -> None:
|
||||
assert "overskrift" in gate.RUNBOOK_RULE
|
||||
assert gate.RUNBOOK_MIN_BODY > 0
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue