feat(prepass): the excerpt's new fields reach the prompt, po stops dropping them twice

P2 measured that the producer's payload now carries title/req_number/sources/source_* on
every excerpt (14 members, was 9), but PrepassExcerpt ignored them (extra="ignore") and
_data_blocks rendered only concept_id/adjudication/trust_tier -- so (b') was a po verdict,
never a model verdict. title/req_number/sources are now named fields; source_* locators are
read via model_extra and a prefix scan (measured: the producer treats source_* as an
open-ended family, not a fixed allowlist), so a future producer's new source_foo key reaches
the prompt without a code change here. A P1-form payload renders byte-identical to before.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Kjell Tore Guttormsen 2026-09-08 16:42:17 +02:00
commit 2453246d4b
5 changed files with 406 additions and 3 deletions

View file

@ -474,3 +474,126 @@ avgrensning operatøren har tatt stilling til.
| 22 | Parse-feilen på N200 | `p2-n200-free-parse-failures.json` finnes; feilteksten sitert ordrett |
| 23 | Prisene | Azure Retail Prices API hentet på nytt 08.09 |
| 24 | 0 × 429 | `retries`-telleren i alle 18 poster |
---
## 10. P3 (ordre `20260908T141941Z`) — po bærer utdragets nye felt gjennom til prompten
PM-valg 08.09 14:20Z, på denne målingens egen anbefaling (§ 6): **ja, to linjer, rød test først.**
### 10.1 Reproduksjon (steg 1, uendret fra § 4)
```python
>>> sorted(json.load(open("scratchpad/nbundler-p2/payload-n500.json"))["excerpts"][0].keys())
['adjudication', 'bundle_id', 'bundle_id_inherited', 'concept_id', 'rank', 'req_number',
'sha256', 'source_element_id', 'source_sha256', 'sources', 'text', 'text_sha256', 'title',
'trust_tier'] # 14 medlemmer, i payloaden
>>> sorted(prepass.load_prepass_payload("scratchpad/nbundler-p2/payload-n500.json").excerpts[0]
... .model_dump().keys())
['adjudication', 'bundle_id', 'concept_id', 'sha256', 'text', 'text_sha256', 'trust_tier']
# 7 medlemmer, etter parsing
>>> "req_number" in prepass.render_context(...)
False # aldri i prompten (§ 4)
```
### 10.2 Feltform, valgt med måling
Spørsmålet ordren stiller: hvilken form overlever en ukjent `source_foo`-nøkkel fra en FRAMTIDIG
produsent, uten kodeendring her? okfs egen melding (§ 4, sitert) kaller `source_*` en **prefiks-
regel, ikke en allowlist** — K2 bærer fem slike lokatorer der N-bundlene bærer to. To former ble
sammenliknet mot nøyaktig det spørsmålet:
- **Navngitte felt** (`source_element_id: str | None`, `source_sha256: str | None`, …): en tredje
`source_*`-nøkkel krever et NYTT felt på `PrepassExcerpt` og en ny rendringslinje — en
kodeendring, nøyaktig det prefiksregelen sier man IKKE skal måtte gjøre.
- **`model_extra`** (`extra="allow"``PrepassExcerpt` ALENE, lest tilbake via et
`source_`-prefikssøk i `source_locators()`): en ny `source_*`-nøkkel havner unavngitt i
`model_extra` og plukkes opp av SAMME søk, null kodeendring.
**Valgt: `model_extra` for `source_*`-familien.** `title`, `req_number` og `sources` er derimot
navngitte felt — de er SS-8-deklarerte, entallige medlemmer (aldri en voksende familie), og en
`model_extra`-lesning av dem ville kastet bort pydantics egen validering for ingen gevinst.
Testen `test_a_future_source_star_key_survives_without_a_code_change`
(`tests/test_prepass_excerpt_fields_loadbearing.py`) legger til en TREDJE, aldri navngitt
`source_page_number`-nøkkel og viser at den når `source_locators()` uendret.
### 10.3 Fiksen: to steder, ikke to linjer
Anbefalingen i § 6 anslo "to linjer"; målt var det to STEDER (parsing + rendering), hver med mer
enn én linje fordi `sources` trengte en egen typet klasse (`PrepassSource`, med `resource` og en
valgfri `title`) og `source_locators()` trengte en egen metode:
1. **Parsing** (`PrepassExcerpt`): `title: str | None`, `req_number: str | None`,
`sources: tuple[PrepassSource, ...] | None`, alle default `None` (de fleste payloader i dette
repoet er fra FØR P2s produsent-endring og bærer ingen av dem — et påkrevd felt ville refusert
hver payload skrevet før i dag). `model_config = ConfigDict(extra="allow")` — kun på DENNE
klassen, `_Permissive`s `extra="ignore"` står urørt for `PrepassBundle`/`PrepassBudget`/osv.
2. **Rendering** (`_excerpt_header`, ny funksjon `_data_blocks` nå kaller): `req_number` og
`title` legges til i parentesen ved siden av `adjudication`/`trust_tier` NÅR de finnes;
`sources[0].resource` (adressen) og hver `source_*`-lokator (generisk, via
`source_locators()`) legges til deretter. **Kjent-negativ:** når ingen av de nye feltene
finnes, er sløyfa en no-op og headeren er BYTE-IDENTISK med før P3
(`test_a_p1_form_payload_renders_the_header_exactly_as_before`,
`test_a_p1_form_payload_render_seed_also_unchanged`) — pinnet LITERALT, ikke med en
delstreng-assert, fordi en lekkende tom-klausul (f.eks. en etterlatt `, `) ikke ville vist seg
i et substring-søk.
`render_seed` deler `_data_blocks` med `render_context` (ko-(p)); en fiks som bare traff den ene
armen ville latt utforsknings-døra ligge stille bak debatt-døra —
`test_render_seed_also_carries_the_new_fields` gater begge.
### 10.4 Rødt → grønt → mutasjon rød
Ny fil: `tests/test_prepass_excerpt_fields_loadbearing.py`, 10 arm. FØR fiksen: **8 røde, 2
grønne** (de to kjent-negative armene er trivielt grønne før fiksen òg — de er kontroller, ikke
mål). ETTER fiksen: **10 av 10 grønne.**
**Mutasjon** (revert `_excerpt_header`/`_data_blocks` til § 4s form, midlertidig, aldri committet):
3 av 10 røde — nøyaktig de tre rendrings-armene
(`test_the_data_block_renders_req_number_title_and_the_address`,
`test_the_data_block_renders_a_future_locator_too`, `test_render_seed_also_carries_the_new_fields`)
— parsing-armene forblir grønne (mutasjonen rørte ikke `PrepassExcerpt`), som beviser at de to
halvdelene av fiksen har HVER SIN uavhengige gate. Fiksen restaurert fra en kopi (`shasum -a 1`
verifisert identisk før/etter), aldri `git checkout`.
### 10.5 Suite, golden, lint
**1521 passed, 5 skipped** (var 1511 — 10 nye), `pytest -q`, 272 s. `ruff check src tests` og
`ruff format --check src tests`: rene (94 funn i `scratchpad/` er FØR-eksisterende, utracket,
utenfor scope — samme avgrensning som K3-ordrenes `ruff check src tests tools`). `mypy src`: 0
funn, 37 filer. Golden `demo-transcript.stdout` UENDRET: `shasum -a 1` = `ea8c534…` — forventet,
`simulation.py` importerer ikke `prepass` i det hele tatt.
### 10.6 Én betalt N100-kjøring med `--require-cost-baseline` — og (b) er STRUKTURELT ikke dekket
Samme oppsett som § 3 (`live_nbundler_p2.py n100 --require-cost-baseline`), med den FIKSEDE koden.
```
KJENT-POSITIV n100: navigate_bundle=446 (fasit 446) OK
run refused: this run was required to be anchored, but the knowledge base offers no cost baseline: …
===== ARM n100 [req] (rc=1) =====
MODELLKALL: 0 (gaten MAA fyre foer foerste kall)
```
**rc=1, 0 modellkall, NOK 0,00.** Dette REPRODUSERER § 2s funn ordrett — `--require-cost-baseline`
nekter N100 FØR første modellkall, fordi en vegnormal ikke bærer kostlinjer (strukturelt, uendret
av denne fiksen). **Konsekvens: (b) kan IKKE måles i denne armen** — det oppstår aldri en
hypotese å navngi et konsept for, uansett hvor godt utdraget bærer `req_number`/`title`. Dette er
IKKE en regresjon fra fiksen; det er § 2s "en N-bundle kan ikke samtidig være forankret og
produsere en hypotese" gjentatt på den fiksede koden.
**(b) er derfor "ikke dekket" i DENNE armen — strukturelt, ikke som en feil ved fiksen.**
Fiksens EGEN korrekthet er verifisert der den kan verifiseres gratis: offline, mot payloadens
faktiske felt (§ 10.110.4). En live gjenmåling av (b) på den FRIE armen (uten flagget, slik § 3
kjørte N100) ville kostet et nytt betalt kall utenfor denne ordrens scope (ordren navngir
eksplisitt `--require-cost-baseline`-armen som den ENE betalte kjøringen); det er ikke gjort her.
### 10.7 Funn — rapportert, ikke fikset
7. **(b) er umålbar under `--require-cost-baseline` for enhver N-bundle** (§ 10.6). Samme
struktur som funn 2 i § 7, nå bekreftet på den fiksede koden. Ingen handling — F4 gjør nøyaktig
det den ble bygget for.
8. **En live gjenmåling av (b) på den frie N100-armen (uten flagget) er ikke gjort** her — ordren
navngir `--require-cost-baseline` som den ene betalte kjøringen, og en fri kjøring ville vært
en ny, ubedt betalt handling.