fix(validator): stage 0 navngir ALLE baseline-overtredelser, ikke bare den foerste
K2-funn (b), maalt live i oekt 94 (docs/2026-09-06-major2-levende-k2.md § 4,
kjoering 3, max_attempts=3):
1000/500 -> "quantity 1000 ... baseline 1250" -> 1188/350
-> "unit_cost 350 ... baseline 850" -> 1000/850 -> forsoekene brukt opp
Steg 5 mater avvisningsgrunnen ORDRETT inn i neste forsoeks prompt, saa en
melding som navngir ETT felt leses som en instruks om aa rette det feltet.
Modellen fant hver riktig verdi og aldri begge samtidig: den rettet feltet
avvisningen navnga og brakk det andre. Loekka oscillerte i stedet for aa
konvergere.
Endringen gjelder KUN meldingens fullstendighet. D6 er uendret: en hvilken som
helst overtredelse avviser fortsatt, i samme stage, foer loeseren.
max_attempts heves IKKE og eksponeres IKKE.
Hver overtredelse beholder dagens setning ORDRETT, sammenfoeyd med "; ", saa
NOEYAKTIG EN overtredelse rendres byte-identisk med foer - det er dette som
holder de eksisterende delstreng-assertene i S4.0-, reserve- og
levert-bundle-gatene staaende. Skilletegnet er valgt framfor linjeskift fordi
Rejection.reason ogsaa lander i outbox-JSON, de hostede payloadene og
terminal-notisene.
Rekkefoelgen er FORSLAGETS egen (linjer i oppgitt rekkefoelge, quantity foer
unit_cost i en linje), saa to identiske forsoek gir to identiske prompter. En
ukjent kostkode bidrar med sin ENE setning og ingen magnitude-setninger: det
finnes ingen baseline-linje aa avvike fra, og en sammenligning mot ingenting er
nettopp den fabrikasjonen dette steget finnes for.
Load-bearing MAALT (tests/test_stage0_all_violations_loadbearing.py, 6 armer),
seks mutasjoner ALLE ROEDE mot HELE suiten + groenn kontroll 1387/5 (fra
1381/5; +6, 0 fjernet - strengt supersett) og golden demo-transcript.stdout
BYTE-UENDRET (shasum -a 1 av INNHOLDET = ea8c534773acdbe41ae68f2c55724d69aaf8be4f):
returner ved foerste overtredelse (4 roede) - rapporter kun den siste (4) -
ustabil rekkefoelge i linja (1) - over-rapporter et felt som er INNENFOR
toleransen (28) - drift enkelt-overtredelsens form (1) - la en ukjent kode
ogsaa emittere magnitude-setninger (19).
Co-Authored-By: Claude <claude-opus-5>
This commit is contained in:
parent
ab747bc7e8
commit
78e8e39147
2 changed files with 187 additions and 16 deletions
|
|
@ -163,32 +163,49 @@ def _reconcile_against_baseline(
|
|||
than ``tolerance`` (relative to the BASELINE value, which is the ground truth) — a real code
|
||||
carrying a fabricated magnitude.
|
||||
|
||||
Returns the first ``Rejection`` (validator's own type — never a new gate), or ``None`` when the
|
||||
proposal reconciles. Items are checked in their stated order so the reason is deterministic.
|
||||
A validation, never a repair: the proposal is rejected, not silently corrected to the baseline."""
|
||||
Returns ONE ``Rejection`` (validator's own type — never a new gate) naming EVERY violation the
|
||||
attempt carries, or ``None`` when the proposal reconciles. A validation, never a repair: the
|
||||
proposal is rejected, not silently corrected to the baseline.
|
||||
|
||||
**Completeness is load-bearing (K2 finding (b), measured live in økt 94).** Step 5 feeds this
|
||||
reason VERBATIM into the next attempt's prompt, so a message naming only the FIRST violation
|
||||
reads as an instruction to fix that one field. Measured on a line broken in both fields
|
||||
(``docs/2026-09-06-major2-levende-k2.md`` § 4, run 3): the proposer fixed the quantity and
|
||||
rebroke the unit cost, then fixed the unit cost and rebroke the quantity, and ran out of
|
||||
attempts — it found each correct value and never both at once. Reporting them together is what
|
||||
lets the loop converge under the EXISTING cap (``max_attempts`` is not raised, and stays
|
||||
unexposed). The verdict itself (D6) is unchanged: any violation still rejects, in this stage,
|
||||
before the solver.
|
||||
|
||||
Each violation keeps its sentence VERBATIM, joined with ``"; "``, so exactly one violation
|
||||
renders byte-identically to before. The joiner is a separator no single-line renderer can
|
||||
break — chosen over a newline because ``Rejection.reason`` also lands in outbox JSON, the
|
||||
hosted payloads and terminal notices. Order is the PROPOSAL's own — items in stated order,
|
||||
``quantity`` before ``unit_cost`` within an item — so two identical attempts get two identical
|
||||
prompts. An unknown code contributes its one sentence and NO magnitude sentences: there is no
|
||||
baseline line for its figures to deviate from, and a comparison against nothing is exactly the
|
||||
fabrication this stage exists to catch."""
|
||||
violations: list[str] = []
|
||||
for item in proposal.affected_items:
|
||||
line = baseline.items.get(item.code)
|
||||
if line is None:
|
||||
return Rejection(
|
||||
proposal=proposal,
|
||||
reason=(
|
||||
f"unknown cost code {item.code!r}: not in project {baseline.project_id}'s "
|
||||
f"cost baseline ({len(baseline.items)} known codes)"
|
||||
),
|
||||
violations.append(
|
||||
f"unknown cost code {item.code!r}: not in project {baseline.project_id}'s "
|
||||
f"cost baseline ({len(baseline.items)} known codes)"
|
||||
)
|
||||
continue
|
||||
for field, claimed, actual in (
|
||||
("quantity", item.quantity, line.quantity),
|
||||
("unit_cost", item.unit_cost, line.unit_cost),
|
||||
):
|
||||
if abs(claimed - actual) > tolerance * actual:
|
||||
return Rejection(
|
||||
proposal=proposal,
|
||||
reason=(
|
||||
f"{field} {claimed:g} for cost code {item.code!r} is outside the "
|
||||
f"{tolerance:.1%} tolerance around the baseline {field} {actual:g}"
|
||||
),
|
||||
violations.append(
|
||||
f"{field} {claimed:g} for cost code {item.code!r} is outside the "
|
||||
f"{tolerance:.1%} tolerance around the baseline {field} {actual:g}"
|
||||
)
|
||||
return None
|
||||
if not violations:
|
||||
return None
|
||||
return Rejection(proposal=proposal, reason="; ".join(violations))
|
||||
|
||||
|
||||
def validate_proposal(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue