feat(run,generate): a run says what its delivered input can ground, before it spends an attempt [skip-docs]

P7 is right and landed, but re-measuring it exposed a consequence no row stated: with the gate
live, 29 of 29 cost codes in 13 of 13 delivered proposals fall across the three free recordings
(PM's denominator; 14 of 14 in 8 proposals on the PARSEABLE one -- the five blobs that separate
the numbers are refused by pydantic's `claimed <= total` and never reach stage 0b). All 29 were
invented, so the gate is right; but a gate that always refuses is as useless as one that never
does.

The cause is that the PROMPT asks for something the input cannot supply. `_build_messages`
requires each affected_item to "restate a cost line as the project's price schedule already
carries it", while K2's delivered input carries 9 occurrences / 2 distinct code-shaped tokens --
`SHA-01`/`SHA-10`, both document numbers off a page footer -- and `derive_cost_baseline` refuses
the base outright. There is no cost line in it to restate.

`GroundingOffer(chars, identifiers, cost_lines)` reports it. The PAIR is the diagnosis: "50
identifiers, 0 cost lines" says what neither number says alone. A REPORT, never a gate -- it
blocks nothing, because a blocking requirement IS `--require-cost-baseline` (F4/D-3, opt-in,
untouched), and `_ground_against_input` is untouched.

The callsite is MEASURED, not chosen: `generate.py` composes the grounding per attempt, after
`await _fetch_parsed`, so a report there could only speak once an attempt had been paid for;
`run.py` binds both halves above the `--live-dry-run` cut and before the first `debate.run`, so
the FREE trip says it. `delivered` is bound ONCE and the same variable feeds the report and
`_evaluate`; the report composes THROUGH `_grounding_text`, the gate's own composer.

A pattern is admissible here and not in the gate, and that is the difference between a report and
a falsifier: an unknown form is a token left uncounted -- an under-count, never a false rejection.
The forms are transcribed from the measurement; bare numbers are excluded with the number
(46 394 / 2 117 in K2). `grounding_offer_notice` is the ONE renderer and is silent when the run
CAN anchor -- omission, never an empty row.

Load-bearing MEASURED (tests/test_grounding_offer_loadbearing.py, 12 arms), nine mutations all
red against the WHOLE suite + green control 1570/5 (from 1558/5, strict superset, 0 removed) and
the golden byte-unchanged (shasum -a 1 of the CONTENT = ea8c534773acdbe41ae68f2c55724d69aaf8be4f).

Measurement: docs/2026-09-09-p8-forankringstilbudet.md

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Kjell Tore Guttormsen 2026-09-09 17:50:02 +02:00
commit 455d611660
5 changed files with 767 additions and 2 deletions

View file

@ -22,6 +22,7 @@ Two entry points, because the LLM call is async while ``validator.self_repair``
from __future__ import annotations
import json
import re
from collections.abc import Callable, Mapping
from dataclasses import dataclass, field, replace
from typing import Any
@ -456,6 +457,79 @@ def _grounding_text(project: Project, baseline: CostBaseline | None, delivered:
)
#: The identifier forms the DELIVERED corpora actually carry, TRANSCRIBED from the measurement
#: (``docs/2026-09-09-p8-forankringstilbudet.md`` § 2) rather than chosen: over K2's two delivered
#: bundles the first form finds 50 distinct tokens and the second none, while over the three
#: N payloads the second finds 269-981 distinct and the first at most 3.
#:
#: **Why a pattern is admissible HERE and not in ``_ground_against_input``.** That is a GATE, and a
#: pattern there would be a rule about shapes that can wrongly REFUSE a real code. This is a
#: REPORT: a form it does not know is a token it fails to count, so it errs toward saying the input
#: offers LESS than it does — an under-count is a quiet report, never a false rejection.
#:
#: Bare numbers are deliberately EXCLUDED, with the number: K2 carries 46 394 occurrences over
#: 2 117 distinct values (P7 § 2), so counting them would make every report positive and the
#: measurement inert — the repo's cardinal class, a gate that can only come out green.
_IDENTIFIER_FORMS = (
# ``SHA-01``, ``RIM-02``, ``B-20-00-00``, ``FOR-2011-12-06-1357`` (K2's 50).
re.compile(r"\b[A-ZÆØÅ]{1,8}[-_]\d{1,4}(?:[-_]\d{1,4})*\b"),
# ``Krav 3.3.1—13`` (the N corpora's dominant form). EM-DASH U+2014 AND the hyphen, because the
# binding known positive is the em-dash spelling and only the em-dash spelling scores 6 of 6.
re.compile(r"Krav\s+\d+(?:\.\d+)*\s*[\u2014-]\s*\d+"),
)
@dataclass(frozen=True)
class GroundingOffer:
"""P8: what the DELIVERED input of one run can lawfully ground an ``affected_item`` code in.
A REPORT, never a gate. It blocks nothing a run whose offer is null still runs because a
blocking requirement is exactly ``--require-cost-baseline``, which F4 settled as opt-in.
The PAIR is the whole diagnosis, and neither number says it alone. MEASURED on K2: the
delivered text offers 50 citable identifiers and ZERO cost lines, while the proposer prompt
asks each ``affected_item`` to "restate a cost line as the project's price schedule already
carries it". An operator reading "identifiers: 50, cost lines: 0" learns that no attempt could
have succeeded; reading either number by itself, they learn nothing of the sort.
``chars`` is the size of the exact text P7's gate will measure against — carried so the report
and the gate can be seen to be talking about the same input, which is the only defence against
a second rendering free to disagree with the one that was sent.
"""
#: Length of ``_grounding_text``'s output — the text the gate itself will search.
chars: int
#: Distinct tokens of any ``_IDENTIFIER_FORMS`` shape the text carries.
identifiers: int
#: Cost lines this run can anchor one of them AS. 0 when the run is un-anchored, which is the
#: state every free K2 arm measured. Read off the SAME ``baseline`` binding ``_grounding_text``
#: takes as its third source, so the count and the anchoring can never disagree.
cost_lines: int
def grounding_offer(
project: Project, baseline: CostBaseline | None, delivered: str
) -> GroundingOffer:
"""Measure what ``delivered`` can ground, on the EXACT text the gate will see.
Composed THROUGH ``_grounding_text`` the one composer ``generate_via_llm`` passes to
``validate_proposal`` never re-assembled here. A second composition would be free to drift
from the one that was actually sent, and a report about a text nobody was given is worse than
no report: it reads as evidence.
Deterministic and free: no model call, no network, and no second walk of the bundle.
"""
text = _grounding_text(project, baseline, delivered)
found: set[str] = set()
for form in _IDENTIFIER_FORMS:
found |= set(form.findall(text))
return GroundingOffer(
chars=len(text),
identifiers=len(found),
cost_lines=0 if baseline is None else len(baseline.items),
)
async def generate_via_llm(
chat_client: BaseChatClient,
project: Project,

View file

@ -80,7 +80,12 @@ from portfolio_optimiser.explore import (
tool_call_payload,
trace_payload,
)
from portfolio_optimiser.generate import ParseFailure, generate_via_llm
from portfolio_optimiser.generate import (
GroundingOffer,
ParseFailure,
generate_via_llm,
grounding_offer,
)
from portfolio_optimiser.ir import SavingsProposal
from portfolio_optimiser.mandate import (
OWN_PROPOSAL_ID,
@ -248,6 +253,15 @@ class RunResult:
#: Carried HERE and not on ``ProvenanceStamp``: the stamp describes the gate that judged ONE
#: candidate, while this is a RUN-level fact about what the run was allowed to read at all.
prepass: prepass.PrepassDeclaration | None = None
#: What this run's DELIVERED input could ground an ``affected_item`` code in (P8): the size of
#: the text P7's gate searched, how many distinct identifiers of a measured form it carries,
#: and how many cost lines the run can anchor one of them AS.
#:
#: A RUN-level fact settled ONCE, before any candidate exists, which is why it is here and not
#: on ``ProvenanceStamp`` (that describes the gate that judged ONE candidate) — ``skipped_links``'
#: own placement rule. DEFAULTED, ``prepass``' half of the rule: ``None`` is the true statement
#: "no measurement was made", and there is exactly one place that makes it.
grounding_offer: GroundingOffer | None = None
@property
def verdict_key(self) -> str:
@ -313,6 +327,12 @@ class DryRunReport:
#: resolves a payload, so a dry run can honestly report what it would have read — whereas a
#: field resolved above that cut could only ever report zero.
prepass: prepass.PrepassDeclaration | None = None
#: What the delivered input of a REAL run of this configuration could ground an
#: ``affected_item`` code in (P8). Carried here for ``cost_baseline_anchored``'s reason and,
#: more sharply, because this is the surface on which "before it spends its three attempts"
#: is provable at all: the dry-run cut returns before the first model call, so a dry run that
#: reports a null offer has said the run cannot succeed WITHOUT paying to find out.
grounding_offer: GroundingOffer | None = None
@dataclass(frozen=True)
@ -781,6 +801,37 @@ def cost_baseline_notice(anchored: bool) -> str | None:
return None if anchored else _UNANCHORED_NOTICE
def grounding_offer_notice(offer: GroundingOffer | None) -> str | None:
"""Render the one line that says what this run's delivered input can ground, or ``None`` when
there is nothing to warn about (P8).
ONE renderer with N callsites, never N copies of the wording (-(p)), taking the
ALREADY-MEASURED value rather than a text: a renderer that re-composed the grounding would be
a second resolution of the same rule, free to drift from the run it describes
(``cost_baseline_notice``'s rule).
``None`` when the run CAN anchor a cost line omission, never an empty row
(``mandate.announce``'s rule) — and ``None`` on ``None``, which is the honest reading of "no
measurement was made". This is deliberately NOT ``proposal_review_notice``'s deviation: there,
silence on zero was ambiguous; here, a run that can anchor its lines has nothing to report that
the outcome does not already say.
BOTH numbers, because the pair is the diagnosis. "0 cost lines" alone reads as a restatement of
``cost_baseline_notice``; "50 identifiers" alone reads as good news. Together they say the
thing P8 measured: the input offers plenty to cite and nothing to cost, while the proposer
prompt asks for a cost line.
English, like every other line this CLI prints."""
if offer is None or offer.cost_lines > 0:
return None
return (
f" Grounding offer: {offer.identifiers} distinct identifier(s) and "
f"{offer.cost_lines} cost line(s) in the {offer.chars} characters this run was given — "
"the proposer is asked to restate a cost line this input does not carry, so every "
"candidate it invents will be refused as ungrounded"
)
def bundle_id_notice(resolved: okf.ResolvedBundleId | None) -> str | None:
"""Render the one line that says a base was mounted under a name it does not answer to, or
``None`` when there is nothing to say.
@ -1166,6 +1217,17 @@ async def run_project(
"--derive-cost-baseline when the base carries a priced schedule"
)
# P8: what this run was GIVEN, composed ONCE. Bound HERE and not inside ``_evaluate`` below,
# and that placement is the measurement this seam rests on: this is the first point at which
# both halves exist AND it is above the ``--live-dry-run`` cut, so the offer can be reported
# on the FREE trip — before the first model call at ``debate.run``, let alone the three
# generation attempts. ``generate.py``'s own composition happens per attempt, AFTER
# ``_fetch_parsed`` has returned, so a report from there could only ever speak once an attempt
# had been paid for. ONE binding feeding both the report and the gate: two compositions of one
# text are free to disagree, which is exactly what a report must not be able to do (kø-(p)).
delivered = "\n".join([context, bundle_grounding])
offer = grounding_offer(project, baseline, delivered)
# Trekk B2 (krav 3): configured MCP servers become tools the AGENTS can call during the debate.
# Appended to BOTH paths — on the bundle path they are the first tools that path has ever had.
# Constructed here but NOT connected: an ``MCPTool`` is an async context manager, so the run
@ -1239,6 +1301,9 @@ async def run_project(
bundle_id_source=resolved_bundle_id,
skipped_links=skipped_links,
prepass=prepass_declaration,
# P8, and this surface is the point: the offer is measured ABOVE this cut, so a dry
# run reports it having made no model call at all.
grounding_offer=offer,
)
# The MCP lifecycle (Trekk B2): entered HERE, after the dry-run cut above, so a dry run never
# opens a connection — its promise to stop before the first call covers egress too. Constructed
@ -1378,7 +1443,7 @@ async def run_project(
# ``context`` is the DELIVERED rendering (pre-pass cut / bundle pointer / retrieved
# chunks) — never ``gen_context``, which on the debate path is the model's own
# summary and would let a code the debate invented ground the proposal repeating it.
grounding="\n".join([context, bundle_grounding]),
grounding=delivered,
)
refinements.extend(generated.refinements)
return generated.outcome
@ -1587,6 +1652,9 @@ async def run_project(
debate_tool_calls=tuple(debate_tool_calls),
expert_revisions=tuple(expert_reviews),
prepass=prepass_declaration,
# P8: read off the SAME single measurement the gate's own grounding descends from, so the
# record and the refusals cannot describe different inputs.
grounding_offer=offer,
)
@ -3836,6 +3904,12 @@ def main(argv: list[str] | None = None) -> int:
notice = cost_baseline_notice(report.cost_baseline_anchored)
if notice is not None:
print(notice)
# P8, printed next to the line it qualifies: "stage 0 is skipped" says the gate lost a
# falsifier; this says what the input could have offered it instead. On the FREE trip, so
# an operator learns a run cannot be grounded without paying three attempts to find out.
offer_notice = grounding_offer_notice(report.grounding_offer)
if offer_notice is not None:
print(offer_notice)
# The second measured silence on this surface: a bundle with an unfollowable cross-link
# dry-ran to rc 0 with nothing said, so a half-read base looked exactly like a small one.
nav_notice = skipped_links_notice(report.skipped_links)
@ -3925,6 +3999,11 @@ def main(argv: list[str] | None = None) -> int:
notice = cost_baseline_notice(result.provenance.cost_baseline_anchored)
if notice is not None:
print(notice)
# Same renderer on the full run, read off the run's OWN measurement: a run that spent every
# attempt being refused as ungrounded is exactly where the input-side fact costs the most.
offer_notice = grounding_offer_notice(result.grounding_offer)
if offer_notice is not None:
print(offer_notice)
# Same renderer on the full run, and deliberately so: a run that PRODUCED a proposal from a
# half-read base is where the silence cost the most — the dry run at least produced nothing.
nav_notice = skipped_links_notice(result.skipped_links)