# P14 — kontekstsettet for stresstesten **Ordre `20260912T202210Z-7590723260-from-.claude`, økt 118, 2026-09-12.** Operatørens valg 12.09, ORDRETT fra `~/.claude/docs/2026-09-12-ukesgrunnlag-uke39.md § Operatørens valg 12.09`: «**Tredje lesning:** D-1 står; mål, bundle-valg og all kontekst gis til po per prosjektkjøring (se § 1). Rad 2 + 3, ikke 3′». Ordren leser dette som **P14-valg (a)** — én bundle per kjøring, fire kjøringer — og fører **(c)**, CLI-flate for `run_mandate_across_bundles`, som en EGEN bygge-ordre etter første stressrunde. (c) er IKKE bygget her. Ingen modellkall, ingen Azure. Alt under er målt mot disken, ikke antatt. --- ## 1. Formen (DEL 1) Ett kontekstsett = én katalog under `contexts//`: | Fil | Rolle | Leses av | |---|---|---| | `mandate.json` | kommisjonen — `Mandate`-skjemaet ORDRETT (`mandate.py`) | `load_mandate` (fail-fast) | | `bundle.txt` | hvilken kunnskapsbase settet hører til, og hvilken id den erklærer | testen + operatøren | | `fasit.json` | fasiten: hva et riktig svar MÅ peke på, hva som IKKE kan besvares, og ærlighetsfeltet | testen + operatøren etter kjøringen | | `docs/` | 0–n syntetiske prosjektdokumenter | *ingen i dag* — se § 1.4 | ### 1.1 `mandate.json` — ingen ny form Filen er `Mandate` slik `mandate.py` allerede definerer den. Ingen felt er funnet på: `objective`, `success_criteria`, `approaches[]` med `id`/`label`/`description`/`affected_codes`/ `claimed_saving_nok`/`bundle_id`. `allow_own_proposals` står på sin default (`true`), fordi kravet er «disse **og/eller** dine egne» og den permissive halvdelen er dagens oppførsel. `bundle_id` settes EKSPLISITT på hver approach, også når settet bare har én base. Med én base ville `route_by_bundle` løst et tomt felt til den ene basen uansett (`sole`-regelen), så feltet er strengt tatt valgfritt her — men det er nøyaktig det feltet **(c)** kommer til å rute på, og et sett som allerede bærer det er dispatchbart uendret den dagen (c) finnes. Å la det stå tomt nå ville betydd fire redigeringer da. ### 1.2 `bundle.txt` — symbolsk navn, aldri en absolutt sti To linjer, `nøkkel: verdi`: ``` name: n500-2024 bundle_id: vegnormal-n500-2024 ``` **Symbolsk navn, ikke absolutt sti** — ordren tillater begge. En absolutt sti ville pinnet settet til én maskins hjemmekatalog i et repo som publiseres på `open/`, og `git archive HEAD`-pakka bærer tracked filer (Fase 5-invarianten), altså ville stien fulgt med ut. Navnet resolveres mot `PORTFOLIO_VEGNORMAL_ROOT`, som defaulter til `~/repos/vegnormal-okf/build/ferdig`. `bundle_id` er DEKLARERT i fila fordi den gjør DEL 3(d) målbar **uten** basen til stede: testen kan sammenligne mandatets `bundle_id` mot settets erklæring i enhver checkout. Når basen ER til stede, verifiseres erklæringen i tillegg mot basens egen `okf.reconcile_bundle_id` — så erklæringen kan ikke drifte fra basen i stillhet. ### 1.3 `fasit.json` — ÉN maskinlesbar kilde, ikke `fasit.md` Ordren foreslår `fasit.md` («f.eks.»). **Avvik, uttalt:** fasiten er `fasit.json`. Grunnen er kø-(p): testen må lese fasit-id-ene, og en prosa-fil ved siden av en maskinlesbar ville vært to kopier av ett faktum, fri til å drifte. Prosaen bor derfor INNI JSON-en (`rationale`, `honesty`, `question`), så mennesket og testen leser den samme fila. ```jsonc { "project_id": "...", "bundle": "n500-2024", "must_cite": [ // per approach: hva et riktig svar MÅ peke på {"approach_id": "...", "rationale": "...", "concepts": [{"path": "krav/N500/id-….md", "title": "…", "ref": "Krav 10.4.3—2"}]} ], "unanswerable": [ // falsifiseringsgaten {"question": "…", "anchors": ["enhetspris"], "expected": "ubesvart — riktig svar er å si det"} ], "honesty": "…" // DEL 2(iii) } ``` `path` er **bundle-relativ**, altså nøyaktig den identifikatoren `read_file(bundle_id, path)` tar. Det er den eneste formen som er både grep-bar mot basen og brukbar ORDRETT som neste kalls argument (S7a-3s regel om at en sti aldri skal måtte komponeres av en modell). `title`/`ref` er **målt** ut av konseptets egen frontmatter ved forfatting og re-verifiseres av testen — de er et opptak, ikke en andre kilde. ### 1.4 `docs/` er TOM, og det er en beslutning Scenarioet er MS Office + PDF. Ingen av dem kan mates inn her: `--docs-dir`-omveien er **frarådet** (P13 — den omgår stigen `list_bundles → read_bundle → read_dir → read_file` og de to gatene §4.1a-dimensjonen og verdict-laget), og ordren forbyr å bygge den. Veien et ekte prosjektdokument skal ta er gjennom okf-ingest inn i en base, altså en base til, ikke en katalog ved siden av. Katalogen finnes derfor med en `README.md` som sier nøyaktig det, og `0` dokumenter — som ordrens «0–n» tillater. --- ## 2. De fire settene (DEL 2) Basene er MÅLT, ikke lest av ordren (`find -name '*.md' | wc -l`): | Sett | Base | `.md`-filer | konsepter (`type: Krav`/`Prosess`) | erklært `bundle_id` | |---|---|---|---|---| | `gate-nordvik-2027` | `n100-2023` | 450 | 445 Krav | `vegnormal-n100-2023` | | `fv412-dekkefornyelse-2027` | `n200-2024` | 1 137 | 1 132 Krav | `vegnormal-n200-2024` | | `tunnel-hauglia-2027` | `n500-2024` | 274 | 269 Krav | `vegnormal-n500-2024` | | `kontrakt-sorasen-2027` | `r761-2025` | 5 514 | 2 727 Prosess + 28 Kapittel | `vegnormal-r761-2025` | Ordrens tall (450 / 1 137 / 274 / 5 514) reproduseres eksakt. Differansen mellom `.md`-filer og konsepter er `index.md`-ene, som er navigasjon og ikke innhold. ### 2.1 `affected_codes` — hvor de kommer fra, og hvor de IKKE gjør det **Bare `kontrakt-sorasen-2027` har ekte koder.** R761 bærer `prosessnr` i frontmatter (`12.1`, `22.1`, `51.1`, `52.1` …), altså en kodeserie som finnes i basen og er grep-bar. De tre vegnormal-settene bærer INGEN kostkoder — N100/N200/N500 er krav, ikke prissatte poster — så kodene der er **syntetiske prosjekt-kostlinjer jeg har funnet på**, navngitt i ærlighetsfeltet i hvert sett. Dette er ikke pynt: `candidate_from_approach` (S7b-døra, `--proposals-from-mandate`) avviser en kode basens baseline ikke bærer, så de tre settene er BEVISST ikke kjørbare på den døra. Den døra er ikke stresstestens sti — stresstesten går `--mandate` over den ordinære løkka. ### 2.2 Falsifiseringsgaten — og hvorfor den er så skarp her **RETTET 2026-09-13 (P15, ordre `20260912T220951Z`).** Den opprinnelige setningen her — «samtlige 22 er FRAVÆRENDE fra n100/n200/n500» — var USANN og er fjernet. Kun 8 av de 22 opprinnelig prøvde ordene ble noensinne navngitt (listen over, med et avsluttende «…»), og selve 22-ordslisten ble ALDRI persistert noe sted i repoet — den kan derfor ikke re-måles verbatim. Det er en nevner-svikt av Verifiseringslovens ansikt 4-type: et «fraværende»-utsagn uten en gjenfinnbar liste er ikke et mål, det er en påstand om et mål som fant sted. **Ny, navngitt og persistert 22-ordsliste** (2026-09-13, denne rettelsen — velges for domene-treffsikkerhet, ikke for å oppnå et bestemt utfall), re-målt med gatens egen substring-regel (`anchors_are_absent`, `tests/test_context_sets_loadbearing.py`) mot alle fire basene: `enhetspris`, `kroner`, `budsjett`, `kostnadsestimat`, `prisskjema`, `timepris`, `nåverdi`, `driftskostnad`, `anleggskostnad`, `materialkostnad`, `arbeidskostnad`, `investeringskostnad`, `vedlikeholdskostnad`, `kapitalkostnad`, `finansieringskostnad`, `livssykluskostnad`, `kontraktssum`, `anbudspris`, `tilbudspris`, `merverdiavgift`, `avskrivning`, `besparelsespotensial`. **Faktisk telling per base (446/1133/270/2756 konsepter):** - **n100-2023: 22 av 22 fraværende.** - **n200-2024: 22 av 22 fraværende.** - **n500-2024: 21 av 22 fraværende — IKKE 22.** Basen bærer `kroner`, men som en FALSK POSITIV: ordet forekommer kun inni `borkroner` (boreutstyr, ikke penger) i `krav/N500/id-41a2f459-e362-42d8-d725-aaee8290c7c1.md` — «(styrestenger, borkroner), nøyaktighet ved a…». Delstreng-regelen i Rule U treffer bevisst uten ordgrenser (§ 2.3), og dette er den konkrete prisen for det: en ekte nulltreff-base kan likevel «bære» et pengeord gjennom et urelatert sammensatt ord. Funnet endrer INGEN gate — ingen fasit-anchor er `kroner` — men det gjør STATE-påstanden usann og er derfor rettet her. - **r761-2025: 18 av 22 fraværende** (uendret konklusjon fra tidligere, men re-målt mot den nye listen): basen bærer `enhetspris`, `kroner`, `budsjett` og `kapitalkostnad` — alle fire er ekte kostnadsspråk (f.eks. «kapitalkostnader», «mengder i kroner avviker fra summen»), konsistent med at prosesskoden ER kontraktsspråk. Konklusjonen står: tre av fire baser er reelt uten kostnadsspråk (n500s ene treff er en falsk positiv, ikke et pengeord), og det er hele grunnen til at gaten er verdt å teste. En kjøring som svarer med et kronebeløp mot N100 har ikke lest basen — den har funnet på. ### 2.3 Regelen for «kan ikke besvares» (DEL 3c) — skrevet ned > **Regel U.** Hvert ubesvarbart spørsmål erklærer ≥ 1 `anchor`: et ord på ≥ 4 tegn, små bokstaver. > Spørsmålet er admittert **iff hver anchor er fraværende — case-insensitivt, som delstreng — fra > HELE teksten (frontmatter + kropp) i HVERT konseptdokument i basen.** Tre valg i den regelen er bevisste: 1. **Anchor, ikke «deler ingen nøkkelord».** Ordrens bokstav ville krevd at spørsmålet ikke deler ett eneste ord med noen konsepttittel. I en domenebase er det ikke oppnåelig — et tunnelspørsmål deler «tunnel» med hundrevis av titler, og det beviser ingenting. Det som gjør et spørsmål ubesvarbart er at basen mangler **saken**, ikke vokabularet. Anchoren ER den saken. 2. **Hele teksten, ikke bare tittelen.** En tittel-regel er en proxy: et ord kan mangle i hver tittel og stå i hver kropp. Fullteksten koster ingenting ekstra (filene leses uansett i sin helhet — målt 0,77 s for r761, den største basen), så den svakere regelen hadde ingen pris å forsvare seg med. 3. **Nevner, alltid.** Skanningen rapporterer hvor mange konsepter den så, og en skanning som ser **null** konsepter er RØD. Uten den ville «anchoren ble ikke funnet» vært like sant om en base som ikke ble lest (Verifiseringsloven ansikt 4). **Ærlighets-grense, uttalt:** regelen beviser at ORDET ikke står i basen, ikke at SPØRSMÅLET er ubesvarbart. Et spørsmål kan omskrives til synonymer basen bærer og da være besvarbart uten at anchoren dukker opp. Anchoren er valgt til å være spørsmålets bærende begrep nettopp for å gjøre det gapet lite, men det er en proxy, og det står her i stedet for å bli bortforklart. --- ## 3. Gaten (DEL 3) `tests/test_context_sets_loadbearing.py`. Fire krav, og **hvert av dem har sin egen kjent-positiv**: et bevisst ødelagt sett bygget i `tmp_path` som gjør nøyaktig den armen rød. | Arm | Krever basen? | Hva den nekter | |---|---|---| | (a) hvert `mandate.json` lastes gjennom `load_mandate` | nei | et sett som ikke laster | | (d) `bundle_id` i mandatet == settets erklærte id | nei | et mandat som peker på feil base | | (b) hver fasit-sti finnes i basen, og `title`/`ref` er basens egne | **ja** | en fasit-id som ikke finnes, eller en tittel som har driftet | | (c) Regel U over hele basen | **ja** | et «ubesvarbart» spørsmål basen faktisk bærer ordet for | | (e) den erklærte `bundle_id` == basens egen `reconcile_bundle_id` | **ja** | en erklæring som har driftet fra basen | **Basene er IKKE en repo-avhengighet, og de bundle-krevende armene SKIPPER når roten mangler.** Det er MAJOR-3-gatens egen begrensning (K2 kunne heller ikke være en testavhengighet) og samme klasse som de fem betalte skippene suiten alt har: basene ligger utenfor repoet, og en hard feil ville brutt `uv run pytest` i overleveringspakka for enhver ekstern mottaker. Skippet navngir roten, og armen som kjører bærer nevner-kontrollen fra § 2.3, så et stille tomt skann kan ikke bli grønt. De to offline-armene er ubetingede og kan aldri være fraværende. --- ## 4. Målepunktet: kan én kjøring bære flere bundler? (DEL 4 — ikke bygget) ### 4.1 Hva (a) koster i dag Fire kjøringer, fire `run_id`, fire utbokser. Per sett (`` og `` fra tabellen i § 2): ```sh uv run python -m portfolio_optimiser.run \ --bundle-dir "$PORTFOLIO_VEGNORMAL_ROOT/" \ --mandate contexts//mandate.json \ --run-id -01 \ --outbox-dir scratchpad/p14-stress/ \ --profile azure --max-rounds 8 --max-tokens 120000 ``` Kostnaden ved (a) er altså **ikke** fire ganger modellprisen mot én — det er fire uavhengige kjøringer som hver bærer sin egen base, og det er dét som gjør dem sammenlignbare. Det den koster er **operatørarbeid og sammenstilling**: fire kommandoer, fire utbokser, fire dom-nøkler, og ingen felles `MultiBaseResult` som sier hva kommisjonen samlet ble til. En kryss-base-kollisjon (D2) kan ikke oppstå, fordi hver kjøring har sin egen `VerdictStore` — læring fra base k når aldri base k+1. Dét er hovedtapet ved (a), og det er nøyaktig det (c) kjøper. ### 4.2 Nøyaktig hva (c) ville trenge **Motoren finnes ferdig.** `run.run_mandate_across_bundles` (`run.py:2124`) tar allerede `mandate` + `bundle_dirs: Sequence[str]`, partisjonerer via `mandate.route_by_bundle`, tråder ÉN `VerdictStore` på tvers, og returnerer `MultiBaseResult` med `unreached` og `collisions`. Den tar bevisst **ingen** `project_id` (hver bases prosjekt leses av `_project_from_bundle`). Det som mangler er utelukkende kallstedet, og det er tre ting — MÅLT, ikke anslått: 1. **Argparse:** en repeterbar `--bundle-dir` finnes ikke (`run.py:2444` — `parser.add_argument("--bundle-dir", default=None, …)`, altså ÉN katalog), og STATE fører «repeterbart `--bundle-dir` = NEI» som stående beslutning. (c) trenger derfor et **nytt, eget flagg** — f.eks. `--bundle-dirs` (`action="append"`) eller `--mandate-across` — aldri en utvidelse av det eksisterende, som ville endret en flate fire eksisterende gater pinner. 2. **`run_id`-mynting:** `run_mandate_across_bundles` wirer **ikke** utboksen. Funksjonens egen docstring sier hvorfor: *«The outbox is NOT wired: N runs need N `run_id`s, and minting them here would default a key this repo requires a caller to supply»* (`run.py:2178-2180`), og kroppens ene `await run_project(` (`run.py:2260`) sender verken `outbox_dir` eller `run_id`. (c) må altså avgjøre myntingsregelen på KALLER-siden — f.eks. `-` — og den regelen er en operatørbeslutning, ikke en default dette laget får ta. 3. **Partisjonen i `main()`:** hvert nytt flagg må inn i de tre nekt-settene som allerede finnes — `--portfolio`-partisjonen (`run.py:2771 ff.`), `report_forbidden` (`run.py:2859 ff.`) og dry-run-partisjonen — hver med sin rc-0-kontroll. Det er repoets egen regel om at en utelatelse i `report_forbidden` er et **stille dropp**, ikke en nekt (F4-gapet). Rekkefølgen er dermed gitt: (c) er én ordre med ett nytt flagg, én myntingsregel operatøren bestemmer, og tre partisjons-rader. Den skrives etter første stressrunde — når vi vet om (a)s manglende kryss-base-læring faktisk kostet noe. --- ## 5. FUNN — konsept-titlene kollapser på vei gjennom navigasjonsstigen **Målt, ikke antatt, og funnet fordi P14 leste basene i stedet for å anta dem.** `okf.parse_frontmatter` er linjeorientert og **last-write-wins** — det står ordrett i dens egen docstring («every frontmatter line that carries a colon becomes one `key: value` pair, last write winning»). Hver eneste vegnormal-konseptfil avslutter frontmatteren med en `sources:`-blokk: ```yaml sources: - resource: https://viewers.vegnorm.vegvesen.no/api/nisosts/859990?languageCode=nb title: N500:2024 ``` Den **innrykkede** `title` overskriver dermed konseptets egen. Målt på `n500-2024`: | Målt | Resultat | |---|---| | `okf.navigate_bundle(...)` → `context_files` | **270 konseptfiler, 1 distinkt tittel** (`N500:2024`, 270 ganger) | | `okf.directory_listing(bundle, path="krav/N500")` | **269 dokumenter, alle med `"title": "N500:2024"`** | | konseptenes EGNE toppnivå-titler | **269 distinkte** (`Krav 1.1—2 Generelle bestemmelser`, …) | Over de fire basene: **445/445 · 1 132/1 132 · 269/269 · 2 378/2 727** distinkte egne titler, mot **1** distinkt gjennom `parse_frontmatter` på hver av dem. **Hvorfor det betyr noe for stresstesten.** S7a-3 bygde stigen `list_bundles → read_bundle → read_dir → read_file` nettopp for at navigatøren skal kunne VELGE hvilket dokument den åpner. På disse basene er rung 2 og rung 3 informasjonsløse: navigatøren ser 269 oppføringer som skiller seg fra hverandre med et ugjennomsiktig UUID-filnavn og et tegnantall. Den kan ikke velge på annet enn tilfeldighet — og MAJOR-3s egen ærlighets-grense («at en LEVENDE modell velger BEDRE med en liste enn med hele konteksten er IKKE bevist») blir her målbart usann i den ene retningen ingen hadde målt: det er ingenting å velge PÅ. **IKKE fikset her, og det er en scope-grense, ikke en forglemmelse.** Ordren forbyr å bygge utover P14, og en fiks er dessuten to ulike beslutninger med hver sin gate: enten endres `parse_frontmatter`s pinnede last-write-wins-regel (som mange tester pinner), eller så bytter `directory_listing` tittelkilde til en toppnivå-leser. Begge er en egen ordre. **Gaten har derfor en TRIPWIRE i stedet.** `own_frontmatter` i `tests/test_context_sets_loadbearing.py` leser konseptets egen erklæring (toppnivå-nøkler, første forekomst vinner) — uten den ville fasit-asserten sammenlignet hvert konsept mot den samme konstanten og vært VAKUØS, repoets egen vakuøs-gate-klasse. Arm `test_the_fasit_titles_are_distinct_not_the_collapsed_sources_title` asserterer BEGGE halvdeler: at de registrerte titlene skiller konseptene fra hverandre, **og** at `parse_frontmatter` fortsatt kollapser dem. Den dagen den andre halvdelen blir rød, er funnet borte og armen skal SLETTES, ikke svekkes — det står i armens docstring. --- ## 6. Leveransen (DEL 5) - **Ingen produksjonskode er rørt.** Leveransen er fire datasett + én gate + dette dokumentet. - **Suite:** basislinje etter P13b **1606 passed / 5 skipped** → **1642 passed / 5 skipped** (+36, 0 fjernet — strengt supersett). `demo-transcript.stdout` BYTE-UENDRET, `shasum -a 1` av INNHOLDET = `ea8c534773acdbe41ae68f2c55724d69aaf8be4f` (aldri git-blob-id-en). `ruff check` + `ruff format` + `mypy src` grønne. - **Seks mutasjoner, alle røde på sin egen arm** (mot gate-fila; `grep -rln contexts tests/` viser at INGEN annen test leser `contexts/`, så hele-suite-kjøringer per mutasjon ville ikke kunnet legge til informasjon — uttalt, ikke utelatt). Settene ble sikkerhetskopiert til `scratchpad/p14-backup/` og gjenopprettet derfra, aldri med `git checkout`; de 16 filene er verifisert byte-identiske etterpå. | # | Mutasjon | Rød arm | |---|---|---| | M1 | fasit peker på en sti basen ikke bærer | (b) + tittel-tripwiren | | M2 | en approach rutet mot en annen base | (d) | | M3 | en «ubesvarbar» anchor basen FAKTISK bærer (`asfalt` i n200) | (c) | | M4 | duplisert approach-id | (a) + (d) + fasit-dekningen | | M5 | erklært `bundle_id` driftet fra basens egen | (d) + (e) | | M6 | en registrert tittel driftet fra basens egen | (b) | Den aller første kjøringen av gaten var **rød før settene fantes** (`expected four context sets … found []`), som er Iron Law-rekkefølgen. ### 6.1 Ærlighets-grenser, uttalt 1. **De fire prosjektene er oppdiktet.** Hvert sett bærer sitt eget `honesty`-felt som sier nøyaktig hva jeg har konstruert. Kort: navn, lengder, ÅDT og alle tolv beløp er satt av meg; `affected_codes` er syntetiske i tre av fire sett og EKTE `prosessnr` i `kontrakt-sorasen-2027`. Alt i `must_cite` er derimot lest ordrett ut av basenes egen frontmatter. 2. **Regel U beviser at ORDET mangler, ikke at spørsmålet er ubesvarbart** (§ 2.3). 3. **De bundle-krevende armene skipper uten basene.** På denne maskinen kjørte alle fire; i en ren klon uten `~/repos/vegnormal-okf` skipper tre armer per sett med roten navngitt. 4. **Ingen kjøring er gjort.** Ordren forbyr modellkall og Azure. At po faktisk KLARER å peke på fasit-konseptene, og at den faktisk SIER «dette kan ikke besvares», er ikke bevist her — det er nøyaktig det stresstesten 18.09 skal måle. Dette er måleoppsettet, ikke målingen.