feat(prepass,explore): padding dies at the prompt; a refusal the model can act on is a return value [skip-docs]
Order 20260908T195801Z. Findings 4 and 5 from the S7 acid test, then the two things finding 99 measured and deliberately did not fix (D3, D2). No user-facing surface changes: no new flag, no new command, no changed output contract. Both seams are internal (the pre-pass rendering, and the shape a tool answers a model with), so [skip-docs] rather than a README edit that would describe nothing an operator can do differently. FINDING 4 -- MEASURED, NOTHING BUILT. K2's price schedule IS readable without guessing (8 column spans, 71 of 91 non-blank rows give >= 2 cells, the split stable for K = 2..64). But 0 of 92 rows name all three of code/quantity/unit_cost -- also under a looser substring match -- and 0 of 91 data rows carry code + quantity + amount. The triple is not formatted away; it is not in the document. It is a price SUMMARY plus nine rate cards whose unit-price columns are empty (pre-award). The order's binding decision rule therefore falls against building: --derive-cost-baseline keeps refusing, and MAJOR-4's own honesty limit holds. FINDING 5 -- BUILT. Measured on the actual rendering path (concept_text, not the raw file): the delivered excerpt is 104 lines / 67 245 chars, carrying 208 interior whitespace runs, 117 of them >= 100 and the longest 887 -- 56 806 of 67 245 characters = 84.5 %, over 72 of 104 lines. collapse_padding, called from _data_blocks (the one renderer both arms share, and therefore AFTER verify_against_bundle -- collapsing in concept_text would break every payload's own digest), gives -72.4 %: line count invariant, non-whitespace byte-identical, leading indentation untouched, no number changed. F99-D3 -- read_file / read_dir / read_bundle now RETURN their refusal. MAF turns a tool raise into "Error: Function failed." (_tools.py:1410-1432, :1427) and counts it against DEFAULT_MAX_CONSECUTIVE_ERRORS_PER_REQUEST = 3, so everything the refusing arm knows is destroyed on the way out. The gates are unchanged; the property they exist for -- the reason travels, the bytes never do -- is now asserted explicitly on the returned value. The arm is keyed on named classes, never bare Exception, because ExplorationError is itself a RuntimeError subclass. F99-D2 -- the invariant row, plus one for finding 5 (a stated deviation from "one row only": finding 5 is a separately built seam and the ledger's standing rule requires its own row). 19 existing arms rewritten, never deleted and never weakened: where the class carried a distinction, the refusal KIND carries it now. 13 mutations, all red against the whole suite (W1-W5, M1-M8), each restored from scratchpad with shasum -c. Control 1543 passed / 5 skipped (from 1529/5, a strict superset, 0 removed). Golden demo-transcript.stdout unchanged (shasum -a 1 of the CONTENT = ea8c534773acdbe41ae68f2c55724d69aaf8be4f). Measurement: docs/2026-09-08-funn-4-5-og-read-nekt.md Co-Authored-By: Claude <Opus 5>
This commit is contained in:
parent
078a099898
commit
eb4137415c
13 changed files with 975 additions and 69 deletions
|
|
@ -31,10 +31,12 @@ from __future__ import annotations
|
|||
|
||||
import hashlib
|
||||
import json
|
||||
import re
|
||||
import unicodedata
|
||||
from collections.abc import Mapping
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import Final
|
||||
|
||||
from pydantic import BaseModel, ConfigDict
|
||||
|
||||
|
|
@ -574,14 +576,61 @@ def _excerpt_header(excerpt: PrepassExcerpt) -> str:
|
|||
return f"--- BEGIN DATA {excerpt.concept_id} ({', '.join(fields)}) ---"
|
||||
|
||||
|
||||
#: Two or more. There is deliberately no threshold to tune: "a run of horizontal whitespace inside
|
||||
#: a line is a column gap" is the whole rule, so no K can drift and no K has to be defended.
|
||||
_PADDING_RUN_RE: Final = re.compile(r"(?<=\S)[ \t]{2,}")
|
||||
|
||||
|
||||
def collapse_padding(text: str) -> str:
|
||||
r"""Drop a spreadsheet render's column PADDING on the way into the prompt, and nothing else.
|
||||
|
||||
**Measured (finding 5, order 20260908T195801Z)** on K2's price schedule as
|
||||
``concept_text`` delivers it -- 104 lines / 67 245 characters: 208 interior whitespace runs of
|
||||
two or more, **117 of them >= 100 characters and the longest 887**, together **56 806 of
|
||||
67 245 characters = 84.5 %** of the excerpt, over 72 of 104 lines. So a label and its amount
|
||||
reached the model hundreds of characters apart (``82`` / 593 spaces / a description / 250
|
||||
spaces / ``5647500.0``), and most of what the run paid for was a column width pandoc chose.
|
||||
|
||||
**No judgement anywhere**, which is four separate promises and each is gated:
|
||||
|
||||
* interior runs of spaces and tabs collapse to ONE space;
|
||||
* **LEADING whitespace is untouched** -- indentation is markdown structure (nested lists,
|
||||
indented code), not padding, and flattening it would rewrite documents rather than unpad
|
||||
them. That is what the ``(?<=\S)`` lookbehind buys;
|
||||
* **no newline is touched**, so the line count is invariant and no row is merged with its
|
||||
neighbour -- a naive ``re.sub(r"\s+", " ", text)`` turns a table into a paragraph;
|
||||
* **no non-whitespace character is touched**, so no number can change. The gate asserts the
|
||||
per-line sequence of non-whitespace characters is identical, not merely that the digits are
|
||||
still somewhere.
|
||||
|
||||
**It runs in the RENDERING, never in :func:`concept_text`.** That function is the local
|
||||
re-derivation ``verify_against_bundle`` binds a payload's bytes to; collapsing there would make
|
||||
every payload ever written fail its own digest and would defeat the gate that stops a payload
|
||||
delivering bytes the base does not hold. This is a display transform applied AFTER that gate.
|
||||
|
||||
**Honesty limit, stated.** Collapsing to a single space loses the CELL BOUNDARY: ``Post SUM``
|
||||
becomes indistinguishable from prose containing those two words. Inventing a delimiter to carry
|
||||
the boundary would be exactly the judgement this rule avoids, and no measurement says which
|
||||
delimiter a model reads better -- so the loss is recorded rather than papered over. That a model
|
||||
then USES the numbers is likewise not shown: it would take a paid run, which is a separate
|
||||
order.
|
||||
"""
|
||||
return "\n".join(_PADDING_RUN_RE.sub(" ", line) for line in text.split("\n"))
|
||||
|
||||
|
||||
def _data_blocks(payload: PrepassPayload) -> list[str]:
|
||||
"""The delimited DATA blocks, one per delivered excerpt (SS 9.3), shared by both arms."""
|
||||
"""The delimited DATA blocks, one per delivered excerpt (SS 9.3), shared by both arms.
|
||||
|
||||
:func:`collapse_padding` runs HERE, in the one renderer both arms share (kø-(p)): a fix in
|
||||
``render_context`` alone would leave the exploration reading padding, and two copies of a
|
||||
display rule drift into two answers about one excerpt.
|
||||
"""
|
||||
lines: list[str] = []
|
||||
for excerpt in payload.excerpts:
|
||||
lines += [
|
||||
"",
|
||||
_excerpt_header(excerpt),
|
||||
excerpt.text,
|
||||
collapse_padding(excerpt.text),
|
||||
f"--- END DATA {excerpt.concept_id} ---",
|
||||
]
|
||||
return lines
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue