M38 (attempts_remaining fra max_attempts alene) 2 roede - T3a og T5, som BEGGE navngir M38 i sin docstring; M39 (drop --checkpoint-dir-nekten) 1; M40 (honoured=True ved revise-tid) 2. Sum: 41 kjoeringer, 40 roede, 1 groenn (M29). K2-omkjoeringen (kriterium 8), instrumentet validert mot en kjent positiv foerst (3 954 tegn / 1 495 tok, S7a-3s tall reprodusert eksakt): utforskningen 12 prompter / 18 355 tokens UENDRET til tokenet, debatt-promptene uendret i antall OG stoerrelse, genererings-promptene 2 -> 4. Totalt 17 -> 19 prompter, 19 274 -> 19 776 tokens (+2,6 %). Utfallet flytter seg 200 000 -> 150 000 NOK og dom-noekkelen be8535e2 -> f23ecff8; ekspertens ord staar ordrett i 2 av 6 proposer-prompter (0 av 4 i kontrollen). generate.py: kommentaren paasto at `assert last is not None` ville fyrt uten baereren. M29 maalte at den ikke KAN - D1(a) returnerer inne i loekka naar remaining == 0, og paa siste forsoek er max_attempts - i - 1 alltid 0, saa halen er naabar kun etter en validator-avvisning, som setter `last` ogsaa. Baereren er uvitnet (budget_stop-presedensen), og det staar naa i kilden. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
274 lines
17 KiB
Markdown
274 lines
17 KiB
Markdown
# 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.
|