# MAJOR-2 — en dør for menneskelig tilbakemelding på forslaget som ligger på bordet > **Måledato:** 2026-09-05 (stien bærer ordredatoen, headeren måledatoen — S7b/S2c-formen) > **Ordre:** `20260904T173146Z-8102814273-from-portfolio-optimiser` > **Kode målt ved:** `ee7da3b` (steg 1–8: `ce4c15e` · `d4c8691` · `bbf4d3b` · `59f3fce` · > `c391fb5` · `fedf989` · `bfc634d` · `ee7da3b`); mutasjonene M1–M40 og K2-omkjøringen er > målt mot nøyaktig det treet (`shasum -c` grønn før og etter hver mutasjon) > **Grønn kontroll:** 1363 / 5 under mutasjonsmålingen, 1364 / 5 etter README-armen i steg 10 --- ## 0. Hva døra er | Spørsmål | Før | Etter | |---|---|---| | Kan et menneske svare på forslaget mens det ligger på bordet? | nei | ja — `--proposal-review` | | Hvor mange menneske-sømmer finnes inne i løkka? | 1 (`plan_reviewer`, som fyrer FØR enhver hypotese finnes) | 2 | | Hva skjer med svaret? | — | `revise` kjøper ETT forsøk til under det EKSISTERENDE taket; ordene når neste prompt ORDRETT | | Hvem avgjør utfallet? | validatoren | validatoren — dens SISTE dom (D6) | | Mynter en `approve` en dom? | — | nei; F2 står | | En kjøring uten reviewer | — | byte-identisk: samme prompter, samme utboks-filer, golden uendret | Målt før arbeidet startet (`docs/2026-09-02-misjonsreview-v2.md` § 3, re-målt mot HEAD): `grep -n "prior_" generate.py` finner nøyaktig ÉN søm — `prior_rejection`, maskinens egen falsifisering matet tilbake inne i samme kjøring. Et menneskes dom lander i NESTE kjøring (Steg-7-innboksen). «Improved proposals given feedback» var altså, for en person, en to-kjørings løkke med dager imellom. --- ## 1. Løkkas exit-kontrakt — og et premiss målingen felte **FØR** (`generate.py` ved `bcf3337`): ```python if isinstance(result, ValidatedProposal): return GenerationResult(outcome=result, refinements=tuple(fed_back)) last = result assert last is not None # max_attempts >= 1, so at least one validation ran return GenerationResult(outcome=last, refinements=tuple(fed_back)) ``` `last` settes KUN av en validator-avvisning. Planen (§ Risks, «Critical» #1) sa at «validert → revise» på hvert forsøk derfor ville nå slutten av løkka med `last is None` og dø på asserten. **ETTER**: en eksplisitt bærer `last_ruling: ValidatedProposal | Rejection | None`, satt etter HVER `validate_proposal`; D6 leses rett av den. **⚠️ PLANENS PREMISS ER FALSIFISERT AV MÅLINGEN — M29 forble GRØNN (0 røde av 1363).** Mutasjonen reverterer bæreren til `assert last is not None`, og HELE suiten står grønn. Grunnen er strukturell og ble først synlig da mutasjonen ble kjørt: D1(a) gjør at en `revise` med `attempts_remaining == 0` **returnerer** den validerte dommen inne i løkka, og på siste forsøk er `remaining = max_attempts - i - 1` alltid 0. Løkka kan derfor bare falle GJENNOM til halen etter en validator-**avvisning**, som setter `last` også. `last_ruling` og `last` er dermed identiske i det ene punktet halen leser dem. Konsekvens, uttalt heller enn skjult: **bæreren er UVITNET** (`budget_stop`-presedensen). Den står fordi den gjør D6 eksplisitt og fjerner en assert som hviler på en ikke-lokal invariant — ikke fordi en test holder den. Kommentaren i `generate.py` som påsto at asserten ville fyrt er en påstand flaten gjør om seg selv (Fase-3-klassen) og **må rettes i neste økt**, sammen med commit-teksten for `bbf4d3b` som bærer samme påstand (historikken skrives ikke om; rettelsen står her og i invariantraden). --- ## 2. Mutasjonstabellen — M1–M40, ÉN liste **Grønn kontroll:** `uv run pytest -q` → **1363 passed / 5 skipped**. Node-ID-settet er et **strengt supersett** av pre-MAJOR-2-baselinen: 1319 → 1368, **0 fjernet**, 49 lagt til. `ruff check src tests`, `ruff format --check src tests` og `mypy src` rene. Golden `tests/golden/demo-transcript.stdout` **BYTE-UENDRET**, `shasum -a 1` av INNHOLDET = `ea8c534773acdbe41ae68f2c55724d69aaf8be4f` (ikke git-blob-id-en). Protokoll per mutasjon: hele suiten, maks to mutasjoner per Bash-kall, restaurert fra `scratchpad/major2/pristine/` og verifisert med `shasum -c` — aldri `git checkout`. Alle 41 kjøringer rapporterte `restore=OK`. **Nevneren, med enhet:** M1–M40 er **40 mutasjons-ID-er**, men M26 er sjekket i TO lengder (M26a/M26b, planens funn-1-skjerping), så måleprotokollen er **41 KJØRINGER**. Av dem er **40 røde og 1 grønn** (M29). Begge tellemåtene er sanne; dokumentet teller i KJØRINGER hele veien, og `coord-send`-rapporten gjør det samme. | M | hva mutasjonen gjør | røde | første navngitte armer | uavhengige vitner | |---|---|---|---|---| | M1 | detach reviewer-kallet (returner på validert som før) | 16 | T1, T2, T3a … | — | | M2 | ignorer beslutningen (alltid approve) | 13 | T1, T2, T3a … | — | | M3 | drop `prior_feedback`-injeksjonen (print-and-discard) | 6 | ordrett-armen, komposisjons-armen, T1 … | — | | M4 | tøm feedback etter ett forsøk (ikke-klebrig) | 1 | T6 alene | — | | M5 | akkumuler avvisninger i prompten | 1 | T6 alene | — | | M6 | vis også en AVVIST kandidat til revieweren | 4 | T4, T6, T10 … | — | | M7 | la en revise slippe å tikke rundeboka | 1 | T3a alene | — | | M8 | la revisjoner løkke UTENFOR `max_attempts` | 36 | A5, mandat, portefølje … | **ja** — 18 testfiler, alle eldre enn dette arbeidet | | M9 | `honoured` konstant `True` (og ingen forfremmelse) | 6 | T2, T3a, T3b, T7 … | — | | M10 | `approach_id` alltid `None` | 3 | T5, T8, T5-run | — | | M11 | skriv artefaktet etter returen, ikke fra `finally` | 2 | T9, T16 | — | | M12 | skriv artefaktet kun når lista er ikke-tom | 2 | T10, T16 | — | | M13 | skriv artefaktet også uten reviewer | 3 | T11 | **ja** — `test_a5_per_approach_artifacts`, `test_parse_failure_capture` | | M14 | la en `approve` mynte en `Verdict` | 1 | T12 alene | — | | M15 | les EOF som approve | 2 | T16-unit, T16-CLI | — | | M16 | fang ikke `ProposalReviewInputError` ved navn | 1 | T16-CLI alene | — | | M17 | enhver ikke-revise-linje er approve | 1 | T15 alene | — | | M18 | drop `single_only`-raden | 1 | T17 alene | — | | M19 | drop `report_forbidden`-raden | 1 | T18 alene | — | | M20 | bibliotekssømmen wiret, argparse-flagget borte | 9 | T13, T16, T17 … | — | | M21 | strømmene bundet ved konstruksjon | 1 | T20 alene | — | | M22 | slett den hostede navngitte nekten | 1 | T21 alene | — | | M23 | videresend det hostede feltet i stedet for å nekte | 4 | T21, partisjons-armen | **ja** — Fase-4e-armene i `test_explore_callsites` + `test_hosting` | | M24 | fersk reviewer per base i dispatcheren | 1 | T22 alene | — | | M25 | render kandidaten som repr | 2 | T14, T13 | — | | M26a | trunker feedback til 20 tegn | 5 | T1, T3b, T8 … | — | | M26b | trunker feedback til 40 tegn (sentinelen OVERLEVER) | 3 | T1, T8, T13 | — | | M27 | fall tilbake til siste validerte forslag (D6-alternativ b) | 1 | T13-loop alene | — | | M28 | drop `verdict_key` fra posten | 2 | payload-armen, run-payload-armen | — | | **M29** | **reverter exit-kontrakten til `assert last is not None`** | **0** | **— (se § 1)** | — | | M30 | drop `--live-dry-run`-nekten | 1 | dry-run-armen alene | — | | M31 | drop `--proposals-from-mandate`-nekten | 1 | pfm-armen alene | — | | M32 | notice-rendereren skriver linja uten reviewer | 2 | notice-armen, CLI-kontrollen | — | | M33 | notice-rendereren returnerer `None` på null | 3 | notice-armen, null-armen, CLI-null-armen | — | | M34 | feedback løftet ut av per-approach-kallet (krysser tilnærminger) | 5 | T1, T7, T5-run … | — | | M35 | bygg posten fra en RETURNERT liste i stedet for kaller-eid sink | 1 | T9 alene | — | | M36 | dispatcheren slutter å tråde revieweren | 1 | T22 alene | — | | M37 | detach CLI-utskriften mens rendereren fortsatt returnerer linja | 2 | T13, CLI-null-armen | — | | M38 | `attempts_remaining` regnet fra `max_attempts` alene | 2 | T3a, T5 | — | | M39 | drop `--checkpoint-dir`-nekten | 1 | park-armen alene | — | | M40 | `honoured=True` satt ved revise-tid i stedet for etter hentingen | 2 | T3b, T9 | — | **Sum: 41 kjøringer (40 mutasjons-ID-er), 40 røde, 1 grønn (M29).** ### Tre signaturer verdt å lese * **M13 og M23 er røde i tester som er ELDRE enn dette arbeidet.** «Skriv artefaktet iff en reviewer ble gitt» (D4) holdes av A5-ens eksakte fire-navns-listing og av parse-fangstens kontroll; den hostede partisjonen holdes av Fase-4es to asserts. Sømmene er altså gatet av uavhengige vitner, ikke bare av sine egne armer. * **M38 er rød i TO armer, og begge NAVNGIR den.** T3a («no exception, exactly two calls» under `max_rounds=2`/`max_attempts=10`) og T5 (terminalen viser `attempts_remaining == 0` under `max_rounds=1`/`max_attempts=3`) er de to sidene av den samme ledger-bevisste `min(...)`: den ene måler at løkka STOPPER der boka slutter, den andre at TALLET operatøren leser sier det samme. Det ble VERIFISERT i docstringene, ikke antatt ut fra at armene lå i samme fil. * **M26a og M26b har ULIKE signaturer, og det var ikke gitt.** Ved 40 tegn overlever `PROPOSAL-REVIEW-SENTINEL-9c41ae` (31 tegn) i de armene hvis feedback er kortere enn taket, så T3b og T9 blir grønne igjen mens T1, T8 og T13 — de med lengre feedback — forblir røde. Planens to-lengde-skjerping virker altså etter hensikten her. --- ## 3. K2-omkjøringen — kriterium 8 Samme base, samme syntetiske prisfikstur og samme instrument som S7b § 3 og S2c § 3: `scratchpad/s7b-syretest/K2-priset-SYNTETISK` (630 konseptfiler, erklært id `k2-trinn1-20260903` mot monteringsnavnet — S7a-3-slakken fyrer, og kjøringen sier det). K2-basen er **utracket og finnes kun på denne maskinen**, så kriteriet er verifiserbart her og rapporteres; det asserteres aldri av suiten. **Instrumentet er validert mot en KJENT POSITIV FØR bruk:** rotnivå-listingen på LEVERT K2 måles til **3 954 tegn / 1 495 o200k-tokens over 629 konsepter**, som reproduserer S7a-3s publiserte tall eksakt. Uten den kontrollen ville et lavt ETTER-tall like gjerne betydd at sonden var i stykker (MAJOR-3s falske null). ### 3.1 De to kjøringene Manuset (`scratchpad/major2/scripted-replies-major2.json`) gir proposeren FIRE steg — to tilnærminger × to forsøk, der annethvert oppgir **150 000** i stedet for **200 000**. Manus- posisjonen er delt over kjøringen, ikke per tilnærming (målt), så kontrollen og den reviderte kjøringen leser den samme lista fra hver sin ende. ```bash # KONTROLL — samme flagg, samme manus, bare et annet SVAR printf 'approve\napprove\n' | uv run portfolio-optimiser K2 \ --docs-dir scratchpad/s7b-syretest/K2-priset-SYNTETISK \ --bundle-dir scratchpad/s7b-syretest/K2-priset-SYNTETISK \ --derive-cost-baseline \ --explore "Finn kostnadsbesparelser i Stange skole-anbudet" \ --explore-config scratchpad/s7b-syretest/explore-config.json \ --scripted-replies scratchpad/major2/scripted-replies-major2.json \ --outbox-dir scratchpad/major2/outbox-control --run-id major2-control \ --proposal-review # REVIDERT — to revise, to approve printf 'revise Bruk 150 000, ikke 200 000\napprove\nrevise Bruk 150 000, ikke 200 000\napprove\n' | ... ``` **Kontrollen er approve-only, ikke «uten flagget».** Det isolerer SVARET: begge kjøringene har døra, samme manus og samme budsjett, så alt som skiller dem er hva mennesket sa. ### 3.2 Utfallet flytter seg | | KONTROLL (approve ×2) | REVIDERT (revise/approve ×2) | |---|---|---| | review-spørsmål | 2 over 2 kandidater | **4 over 2 kandidater** | | `hypothesis-1` | VALIDERT 200 000 NOK | **VALIDERT 150 000 NOK** | | `own-proposal` | VALIDERT 150 000 NOK | VALIDERT 150 000 NOK | | kjøringens utfall | best **200 000** NOK | best **150 000** NOK | | dom-nøkkel | `be8535e204cdc4c6` | **`f23ecff85f4188b5`** | | `attempts remaining` vist | 2 | 2, så **1** på forsøk to | Begge kjøringene ender rc 0 med `ValidatedProposal`. Validatorens dom er ikke overstyrt av noen — det er en ANNEN kandidat som ble dømt, fordi mennesket kjøpte ett forsøk til. ### 3.3 Prisen: én ekstra genererings-prompt per tilnærming | fase | prompter (kontroll) | tokens | prompter (revidert) | tokens | |---|---|---|---|---| | 1 — utforskning (navigatør 5, manager 6, hypotesiser 1) | 12 | 18 355 | 12 | **18 355** | | 2a — debatt (proposer 2, checker 1) | 3 | 513 | 3 | **513** | | 2b — generering | 2 | 406 | **4** | 908 | | **sum** | **17** | **19 274** | **19** | **19 776** | **Utforskningen er uendret til tokenet**, og debatt-promptene er uendret både i antall og i størrelse (proposer 314, checker 199 i begge). Hele forskjellen er de **to ekstra genererings-promptene** — én per tilnærming, nøyaktig dét en `revise` kjøper. **+502 tokens, +2,6 %** for en kjøring der et menneske faktisk endret utfallet. Klassifiseringen debatt/generering er MÅLT, ikke antatt: genererings-prompten bærer `Project: {id} - {name}` (`generate._build_messages`), debatt-turen bærer basepekeren. ### 3.4 Ordene når fram — ORDRETT `Bruk 150 000, ikke 200 000` står i **2 av 6** proposer-prompter i den reviderte kjøringen og i **0 av 4** i kontrollen — altså nøyaktig i de to andre-forsøks-promptene, og ingen andre steder. `scratchpad/major2/outbox-revise/major2-revise-proposal-reviews.json`: ```json {"approach_id": "hypothesis-1", "attempt": 0, "decision": "revise", "feedback": "Bruk 150 000, ikke 200 000", "honoured": true, "p50": 319311.8986090132, "verdict_key": "be8535e204cdc4c6"} {"approach_id": "hypothesis-1", "attempt": 1, "decision": "approve", "feedback": "", "honoured": true, "p50": 319311.8986090132, "verdict_key": "f23ecff85f4188b5"} ``` Posten er nøklet per `(approach_id, attempt)` og bærer `verdict_key` for den kandidaten mennesket faktisk SÅ — attempt 0 navngir 200 000-kandidaten, attempt 1 den kjøringen endte på. Kontrollens artefakt har to rader, begge `approve` med tom feedback: **fila skrives uansett, fordi en reviewer ble gitt** (D4). **Ærlighets-grense på nettopp dette manuset:** den skriptede proposeren LESER ikke feedbacken — den returnerer steg 2 uansett. Det som er målt her er derfor (a) at ordene når prompten ordrett, (b) at et forsøk til faktisk ble kjøpt og hentet (`honoured: true`), og (c) at kjøringen bærer validatorens dom på DET forsøket. At en levende modell ville korrigert seg etter ordene er ikke vist, og kan ikke vises av et manus. ## 4. Ærlighets-grenser * **`last_ruling`-bæreren er uvitnet** — se § 1. Planens «Critical risk #1» er falsifisert. Kommentaren i `generate.py` som påsto at asserten ville fyrt er RETTET i samme commit som dette dokumentet; commit-teksten for `bbf4d3b` bærer fortsatt den gamle påstanden, og historikken skrives ikke om. * **Steg 5 under-rapporterer på en revidert kjøring:** en `revise` bruker ett av de SAMME `max_attempts`-forsøkene en validator-avvisning ville brukt, så en kjøring kan bruke opp forsøkene på ekspert-revisjoner og aldri nå en andre validator-falsifisering. `refinements` under-rapporterer da ved konstruksjon. Uttalt, ikke reparert. * **`coverage`-radene navngir ikke en revise som årsak.** Hver revise tikker den DELTE rundeboka, så en eksperts egne revisjoner kan la halen av en kommisjon stå `not_evaluated`. Detaljen forblir «budget exhausted before this approach was evaluated» — sann, og `coverage` er et non-goal. Terminalen viser `attempts remaining` FØR hvert svar, og review-posten er der årsaken kan leses. * **Checker-gaten (6b) og dimensjons-gaten (6c) overstyrer ETTER at genereringen har returnert.** En `approve` registrert ordrett i artefaktet kan derfor sitte på en kjøring hvis utfall er en checker-kildet `Rejection`. Posten sier hva mennesket SÅ og SVARTE, aldri hva kjøringen konkluderte; at de to er uenige er tilsiktet, og å blande dem er nøyaktig det to-falsifiserer-raden forbyr. * **Feedback-teksten er UBEGRENSET**, som plan-review-feedback — belastet `max_tokens`, som er bunden. At den når en proposer som holder navigatør-verktøyene er uttalt, ikke voktet. * **F4s traceback-asymmetri er urørt:** `PlanReviewInputError` forlater fortsatt CLI-en som en traceback, mens `ProposalReviewInputError` fanges ved navn. Uttalt, ikke fikset her. * **`run_portfolio` tar ingen reviewer**, og fraværet er assertert. * **Ingen levende modell har svart på en review.** Alt her er skriptede stand-ins (structured-output-grensens klasse). * **K2-prisene er SYNTETISKE**, og manuset er skriptet: null modellkall, banneret sier det på hver kjøring. Det som er vist er at maskineriet kommer HELT fram med et menneske i løkka, ikke hva et tilbud koster. Multiplikatorene i § 3.3 gjelder dette manuset. * **README-armen har intet M-nummer** — M-lista lukket ved M40. Den ble drevet rød to ganger (manglende blokk, og blokk uten `--checkpoint-dir` navngitt), og kontrollen på at uttrekkeren ikke stille finner ingenting er `--plan-review`-blokka.