# P19 — kravet som binder, ordet som ikke er en kode, sporet som sier hvordan **Økt 122, 15.09.2026.** Ordre `20260914T221206Z-3427310140-from-.claude`. Commits `c84e8bf` (A) · `d74f32d` (B) · `4c6084e` (C+D) · denne rapporten (E). Kontroll **1744 passed / 5 skipped** (fra 1699/5 ved P18, strengt supersett, 0 fjernet). Golden `tests/golden/demo-transcript.stdout` BYTE-UENDRET gjennom hele arbeidet: `shasum -a 1` av INNHOLDET = `ea8c534773acdbe41ae68f2c55724d69aaf8be4f` (aldri git-blob-id-en). --- ## 0. Et premiss i ordren ble felt FØR noe ble bygget på det Ordrens **A1** legger kravet i `_INSTRUCTIONS[HYPOTHESISER_ROLE]` ALENE, og **E1** gjentar P18s stresskommando ordrett. De to kan ikke begge være sanne: | målt 15.09 | resultat | | --- | --- | | stresskommandoen i E1 | `--mandate`, **ingen `--explore`** | | `--explore` + `--mandate` | NEKTES ved navn (økt 57) | | `{run_id}-exploration.json` i de ni runde-1/2-utboksene | **0 av 9** | Hypotesisereren kjører altså **aldri** i en stressrunde. En forpliktelse bare den kan bære ville vært strukturelt inert i nøyaktig de betalte kjøringene ordren bestiller — og **A3** («kravet når forslaget») ville vært unåbar sammen med den. **A2s egen setning løser det:** nekten skal gå til modellen «som en tur den kan rette (samme mekanisme som `quick_validate`s nekt), ikke som en `raise`». `quick_validate` ER et verktøy. `declare_requirement` bor derfor i `navigator_tools`, altså hos BEGGE roller som navigerer: utforskningen, og siden S2c debatten. Én instruksjon, én nekt, én record, to dører. **Dette er ikke en omtolkning som ble bekreftet i etterkant — det er dét som gjorde runde 3 målbar:** en levende modell kalte verktøyet i **5 av 5** kjøringer. --- ## 1. DEL A — kravet som binder `declare_requirement(bundle_id, path, ref)` finnes **kun når kalleren gir begge sinkene** (`opened` + `requirements`), og dét er hva som holder hvert pre-P19-kallsted byte-identisk. Én sink uten den andre NEKTES ved konstruksjon: en logg som ikke ser hva som ble åpnet ville akseptert enhver erklæring. `opened` er den SAMME lista `ExplorationToolRecorder` fyller — en alias, aldri en kopi — så nekten leser kjøringens EGEN lesetrace. Merket hypotese bærer `requirement` som PÅKREVD nøkkel: utelatt er en hard feil, eksplisitt `null` er lovlig og krever `why_none`, halvnavngitt nektes. Et MYNTET forslag bærer feltet; et FRØ får det aldri (§ C.6 dør 1). `_build_messages` skriver linja kun når feltet finnes. **Mutasjoner (4, alle røde mot HELE suiten, grønn kontroll 1711/5):** | # | mutasjon | røde | | --- | --- | --- | | A-i | `requirement` valgfri igjen | 1 | | A-ii | nekten sjekker ikke mot åpnede stier | 1 | | A-iii | A3-linja fyrer ubetinget | 1 | | A-iv | dommeren teller mot hele basen | 1 | **ORDRENS SPÅDDE SIGNATUR FOR A-iii BLE FALSIFISERT.** A5 sier golden må bli rød. Den er byte-uendret, og grunnen er strukturell: demoen kjører UTEN mandat, så `_build_messages`' approach- gren tas aldri på golden-stien. Vitnet er byte-identitets-halvdelen av arm (g), som asserterer at en prompt uten krav er tegn for tegn den samme som før. **A-iv STO GRØNN FØRST — repoets vakuøs-gate-klasse, TJUEFJERDE gang.** Armen drev `_attributable` mens treffet regnes ut på KALLSTEDET i `score_context_set`. Den driver nå hele dommeren mot en erklæring som ER i basen men IKKE er fasitens, med en positiv kontroll. --- ## 2. DEL B — ordet som ikke er en kode **Kjent-positiv, MÅLT offline mot basene kjøringene faktisk fikk:** | artefakt | kode | før | etter | | --- | --- | --- | --- | | `tunnel-hauglia-2027-02-a4` | `impulsventilator` | validated | **rejected** — «offers 391 identifiers of its own» | | `fv412-…-02-a1` | `bituminøst bærelag` | validated | **rejected** — «offers 1359» | **B1 — tilbudet per base, før/etter de to nye formene:** | base | dok | tegn | før | etter | | --- | ---: | ---: | ---: | ---: | | n100-2023 | 446 | 436 799 | 435 | 578 | | n200-2024 | 1 133 | 1 441 170 | 982 | 1 512 | | n500-2024 | 270 | 388 773 | 272 | 391 | | **r761-2025** | 2 756 | 6 561 263 | **3** | **2 332** | **Den FØRSTE formen ble utvidet i samme slengen, og dét er en måling.** B2 gjorde de samme formene til `prose`/`identifier`-avgjørelsen, og repoets EGEN `ENERGI-TOTAL-EL` matchet ingen av dem (form 1 krevde siffer etter separatoren) — klassifisereren kalte altså en ekte kostkode prosa, og den nye gaten nektet den. **En gate får bare ta feil i retningen som slipper for mye inn.** Form 2 fikk et valgfritt `_`-suffiks fordi ÉN av de 26 fasit-referansene er `Krav 3.3.2—1_1`. **Kjent-negativ:** 26 av 26 fasit-referanser klassifiseres som identifikatorer. **Ærlighets-grense, målt og gitt sin EGEN arm:** `42.5` og `12.1` er typografisk identiske og ingen regel skiller dem, så formen teller begge. P8s eksisterende «bare tall telles ikke»-arm er derfor SNEVRET til heltall (K2s målte klasse, 46 394 forekomster), og desimal-tvetydigheten står i en navngitt arm i stedet for i en docstring. **Re-dom av alle ni runde-1+2-utbokser: 26 av 36 koder er prosa** (runde 1: 11 av 15 · runde 2: 15 av 21). **Mutasjoner (4, alle røde, grønn kontroll 1734/5):** B-i gaten uten tilbuds-vilkåret (**16**, spredt over syv eldre testfiler) · B-ii prosessnummer-formen droppet (10) · B-iii `prose` rapporteres aldri (1) · B-iv gaten detachet (2). --- ## 3. DEL C + DEL D — sporet, forbruket og stoppgrunnen `ToolCall` bærer nå `filter`/`offset`/`limit`. `_number_argument` er en SØSKEN av `_string_argument`: en modell kan sende `limit` som `10` eller `"10"`, og en leser som kjente én form ville rapportert et paginert kall som upaginert. **P18s FUNN 4 VAR FEIL SOM FORMULERT.** `provenance.token_usage` har stått på hvert `-proposal.json` siden S3.4; det som manglet var en LESER. Rettelsen er lagt inn som datert tilføyelse i `docs/2026-09-14-p18-stressrunde-2.md` § 7, UNDER det opprinnelige avsnittet — en rapport som retter seg selv i stillhet er ikke en rapport. **Det som genuint manglet er `{run_id}-coverage.json`.** `stop_reason` kommer fra en KALLER-EID SINK, ikke fra `in_flight`, og dét er en måling: `_evaluate_mandate` SVELGER `BudgetExceeded` så snart noe er produsert, så `run_project`s egen `in_flight` ser den aldri. **Mutasjoner (4, alle røde, grønn kontroll 1744/5):** C-i vindus-argumentene registreres ikke (2) · D-i coverage skrives med tom grunn (1) · D-ii kjøringen skriver den aldri (2) · D-iii dommeren slutter å lese forbruket (1). **D-i STO GRØNN FØRST — vakuøs-gate-klassen, TJUEFEMTE gang.** Armen kalte `write_coverage` selv og VALGTE dermed grunnen den så asserterte på. Bare en kjøring et tak faktisk kappet kan skille de to; armen driver nå `run_project` med `max_rounds=1` (én runde betaler første approach, den andre er den taket kutter). --- ## 4. DEL E — stressrunde 3 (fem betalte kjøringer) Miljø: `az cognitiveservices account show --name po-foundry-ktg --resource-group portfolio-optimiser-rg` INLINE → PROSJEKT-formen `…/api/projects/po-project`; `PORTFOLIO_FOUNDRY_DEPLOYMENT=gpt-4-1-mini`; `PORTFOLIO_MODEL_MAP=scratchpad/major2-live/model_map.json`; `PACE_SECONDS=2`. **Klientprobe FØR armene: `tests/test_foundry_profile_live.py` → 1 passed** (ikke skippet). Gratis `--live-dry-run` på alle fire først, alle rc 0. Parametrene er P18s (`--max-rounds 3 --max-tokens 600000`), uendret for sammenlignbarhet. ### 4.1 Rådata, alle tre runder gjennom SAMME dommer | kjøring | grunnet | req_hit | named | halluc | prosa | filter | paged | tokens | stopp | a4 | | --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | --- | --- | | fv412-01 | 0 | 0 | 0 | 3 | 3 | 0 | 0 | 523 633 | absent | ✅ | | gate-01 | 0 | 0 | 0 | 4 | 4 | 0 | 0 | 509 310 | absent | ✅ | | sorasen-01 | 0 | 0 | 1 | 5 | 3 | 0 | 0 | 104 905 | absent | ❌ | | tunnel-01 | 0 | 0 | 0 | 3 | 1 | 0 | 0 | 255 418 | absent | ✅ | | fv412-02 | 0 | 0 | 0 | 5 | 5 | 0 | 0 | 44 468 | absent | ✅ | | gate-02 | 0 | 0 | 0 | 3 | 2 | 0 | 0 | 35 406 | absent | ✅ | | gate-03 | 0 | 0 | 0 | 4 | 2 | 0 | 0 | 89 911 | absent | ✅ | | sorasen-02 | 0 | 0 | 1 | 4 | 4 | 0 | 0 | 73 627 | absent | ✅ | | tunnel-02 | 0 | 0 | 0 | 5 | 2 | 0 | 0 | 45 642 | absent | ❌ | | **fv412-04** | 0 | 0 | 0 | 4 | **1** | **38** | 5 | 287 883 | rounds | ✅ | | **gate-04** | 0 | 0 | 0 | 4 | **1** | **13** | 2 | 140 695 | rounds | ✅ | | **gate-05** | 0 | 0 | 0 | 4 | **0** | **10** | 4 | 51 257 | rounds | ✅ | | **tunnel-04** | **1** | 0 | 0 | 4 | **0** | **4** | 4 | 59 372 | rounds | ❌ | | **sorasen-04** | — | — | — | — | — | — | — | — | rounds | — | `sorasen-04` ble **REFUSERT av dommeren** (`EmptyMeasurement`): kjøringen døde på rundetaket (`rounds limit=12 observed=13`) etter 11 parse-feil — modellen foreslo `claimed_saving_nok: 0` elleve ganger — og etterlot intet forslags-artefakt. Dommeren nekter å rapportere «0 hallusinasjoner» om en tom utboks. **`{run_id}-coverage.json` ble likevel skrevet, med `stop_reason: "rounds"`** — det er nøyaktig kjøringen DEL D2 finnes for. ### 4.2 Hovedtallet: fasit-konsepter ÅPNET | runde | åpnet | nevner | | --- | ---: | ---: | | 1 | **0** | 26 | | 2 | **0** | 26 | | **3** | **1** | **32** (gate kjørte to ganger) | Treffet er `krav/N500/id-bfb0edb4-…` i `tunnel-hauglia-2027-04`, som gjorde a1 `grounded=True` for første gang i tre runder. **Det er bevegelse, og det er lite.** 1 av 32 er ikke et resultat noen skal bygge en påstand på. ### 4.3 Det verktøyet FAKTISK gjorde **En LEVENDE modell kalte `declare_requirement` i 5 av 5 kjøringer** (1–2 ganger hver). Eksempel fra r761: `{"bundle_id": "vegnormal-r761-2025", "path": "R761/kapittel/4-3/id-7c6d5921-….md", "ref": "4.3"}`. Den brukte også filteret tungt (`filter='krav'`, `'bindende'`, `'bind'`) — 4 til 38 filtrerte kall per kjøring, mot **0 i runde 1 og 2**, der sporet ikke kunne se det. **Men `requirement_hit` er 0 i 5 av 5:** ikke én erklæring traff fasitens konsepter. Modellen navngir et krav, leser det først (nekten tvinger det), og velger likevel feil dokument. `declare_requirement` flyttet altså **hvorvidt** et krav navngis, ikke **hvilket**. ### 4.4 Varians: 04 mot 05 på samme sett `gate-nordvik-2027-04` og `-05` er samme sett, samme base, samme parametre: | | 04 | 05 | | --- | ---: | ---: | | tokens | 140 695 | 51 257 (**2,7×**) | | verktøykall | 30 | 18 | | filtrerte kall | 13 | 10 | | åpnede dokumenter | 6 | 2 | | erklærte krav | 2 | 1 | | utfall | 4 rejected | 4 rejected | **Utfallet er identisk, forbruket 2,7×.** Én kjøring per sett er ikke et utvalg, og det er den viktigste enkeltsetningen i denne seksjonen. --- ## 5. Hva hver del kjøpte — målt, ikke tilskrevet | del | målbar effekt | | --- | --- | | A | `declare_requirement` kalt av en levende modell i **5/5**; `requirement_hit` **0/5** | | B | r761s tilbud **3 → 2 332**; to runde-2-artefakter `validated → rejected` offline; prosa-koder per kjøring **1–5 → 0–1** | | C | filtrerte kall lesbare for første gang: **0 → 4…38** | | D1 | forbruket lesbart per kjøring for første gang (rettelse av P18 funn 4) | | D2 | `sorasen-04` er den eneste kjøringen i tre runder som SIER hvorfor den stoppet | **Ikke tilskrevet:** at prosa-kodene faller fra 1–5 til 0–1 er IKKE bevist å være B3s fortjeneste. B3 fyrte **aldri** i runde 3 — hver avvisning kom fra P7 («appears nowhere in the input»), som fyrer FØRST. Modellene produserte koder som ikke står i basen i det hele tatt. At de samtidig sluttet å produsere prosa-koder som STÅR der, er en observasjon om fem kjøringer, ikke en effekt som er isolert. --- ## 6. Gjenstående stygt — hvert funn med en navngitt løsning ### F1. Et KRAVNUMMER blir akseptert som en kostkode `tunnel-hauglia-2027-04` sitt `a4-enhetspris-ventilator` VALIDERTE på koden **`10.4`** — et kravnummer fra N500, ikke en kostlinje. Det er grunnet (står i basen), det har en identifikator-form, og B3 slipper det gjennom. Formene kan ikke skille «identifikator for et KRAV» fra «identifikator for en KOSTLINJE», fordi en base uten prisskjema ikke har noen av de siste. `must_refuse`-armen faller derfor for andre runde på rad på nøyaktig dette settet. **LØSNING (finnes allerede, ikke aktivert): `--require-cost-baseline`.** MÅLT 15.09: med flagget nekter nøyaktig denne kjøringen **før første modellkall**, med rc 1 og NOK 0. F4 gjorde det opt-in med vilje (hver commons-eid golden er uforankret), og **valget om å gjøre det til stressrundens default er operatørens**. Anslag: 0 kodelinjer, én rad i kjørekommandoen. **ALTERNATIV LØSNING (bygges, ~1 økt):** la `code_forms` skille en tredje verdi `requirement` — en kode som matcher form 2 eller 3 OG står som `req_number` i basens frontmatter er et krav, ikke en kostlinje — og la 0b nekte den når kjøringen er uforankret. Risiko: r761s prosessnumre ER både krav og oppgjørsposter, så regelen ville nektet nøyaktig det korpuset den er mest relevant for. **Anbefaling: `--require-cost-baseline` først, mål så om alternativet fortsatt trengs.** ### F2. Modellen navngir et krav, men ikke det riktige `requirement_hit` 0 av 5. Nekten tvinger fram en LESNING, ikke en RELEVANS. **LØSNING (~1 økt):** la `declare_requirement` returnere kravets egen `title`/`req_number` fra frontmatter i svaret, og la mandatets `success_criteria` nå hypotese-prompten — i dag når den bare annonseringen. Anslag: to sømmer, én ny gate, ingen ny flate. **Ikke bygget her: det er en ny beslutning om hva som skal inn i prompten, ikke en fiks av noe målt ødelagt.** ### F3. Rundetaket kapper hver kjøring `stop_reason: rounds` i **5 av 5**. `own-proposal` ble aldri evaluert i noen kjøring i noen runde. Taket er `max_rounds * 4 = 12` genererings-runder, og fire bestilte approaches bruker minst fire av dem — flere når en parse-feil brenner en. **LØSNING (0 kodelinjer):** `--max-rounds 5` gir 20 runder. Kostnaden er lineær i antall approaches, ikke i korpuset. **Operatørbeslutning, fordi den øker regningen.** ### F4. `claimed_saving_nok: 0` brenner et helt budsjett r761 brant 11 av 12 runder på at modellen foreslo null besparelse. Pydantic nekter `> 0`, og `_fetch_parsed` prøver på nytt med samme prompt. **LØSNING (~0,5 økt):** mat `ValidationError`-grunnen inn i neste forsøks prompt — Steg 5s mekanisme finnes allerede for validator-avvisninger (`prior_rejection`), men en PARSE-feil går ikke den veien. Anslag: én ny parameter på `_build_messages`, én gate. --- ## 7. Kostnad — anslag med uttalt antakelse Målt forbruk, runde 3: **539 207 tokens** over de fire kjøringene som etterlot et artefakt. `sorasen-04` er **ikke målt** (den døde før noe forslag ble skrevet, så ingen `token_usage` finnes) — og dét er en ærlig luke, ikke en null. | runde | kjøringer | målte tokens | | --- | ---: | ---: | | 1 | 4 | 1 393 266 | | 2 | 5 | 289 054 | | 3 | 4 (+1 umålt) | **539 207** | **Runde 3 er dyrere enn runde 2**, og det er forventet: modellen navigerer nå mye mer (4–38 filtrerte kall mot 0). **ANTAKELSE, ikke måling:** ved samme listepris som P18s anslag (289 k ≈ NOK 2) er runde 3 ≈ **NOK 4**. Ingen faktura er lest. --- ## 8. Verifisering | sjekk | resultat | | --- | --- | | `uv run pytest -q` | **1744 passed / 5 skipped** | | `shasum -a 1 tests/golden/demo-transcript.stdout` (INNHOLD) | `ea8c534773acdbe41ae68f2c55724d69aaf8be4f` | | `uv run ruff check src tests` | All checks passed | | `uv run ruff format --check src tests` | 210 files already formatted | | `uv run mypy src` | Success: no issues found in 38 source files | | mutasjoner A/B/C+D | 4 + 4 + 4 = **12, alle røde mot HELE suiten** | | klientprobe før betalte armer | `test_foundry_profile_live.py` 1 passed | | gratis dry-run, fire sett | rc 0 alle fire | --- ## 9. Ærlighets-grenser * **1 av 32 fasit-konsepter er ikke et resultat.** Det er bevegelse fra 0, og fem kjøringer. * **Varians ikke målt utover ett par.** 04/05 på gate viser 2,7× forbruksforskjell ved identisk utfall; ett par er ikke et utvalg. * **B3 fyrte aldri levende.** Gaten er bevist på to REPLAYEDE artefakter, ikke på en kjøring der den faktisk avgjorde utfallet. * **`prose_codes` falt uten at årsaken er isolert.** Se § 5. * **`sorasen-04`s forbruk er ukjent** — artefaktet som bærer tallet ble aldri skrevet. * **Ingen faktura lest.** Alle kronebeløp er anslag fra listepris. * **Formene er transkribert fra FIRE korpus.** Et femte kan bære en femte form; en ukjent form klassifiseres som prosa, og feilretningen er derfor nekt — dét er hva generalitetsvernet (tilbud ≥ 1) og baseline-unntaket finnes for. * **`token_usage` er kjøringens ENE teller** — den skiller ikke debatt fra generering.