portfolio-optimiser-claude/docs/2026-07-16-review-paritet.md
Kjell Tore Guttormsen d746891f06 docs(review): full-paritet review + sesjonsplanverk + beslutningsbrief (Fable 5 + 6 Opus-agenter)
Operatør-mandat 2026-07-16: full MAF-paritet (D-G pkt. 2). Review-økt, ingen kildekode.

- review-paritet.md: C-serien re-verifisert (alle åpne), 10 nye R-funn. Kjørt-bevist av
  orkestrator: R-2 (Infinity → validates=True, NaN avvist), R-3 (tom CSV krasjer ingest →
  delvis bundle). R-1 = §11-raden «Closed loop» ubundet. 4/4 detach røde i throwaway-kopi;
  SDK-premisser holder mot 0.2.110→0.2.120; model-id gyldig+billigst; docs §1-ærlige.
- sesjonsplan-paritet.md: ERSTATTER 2026-07-10-planen. 35-raders paritetsmatrise (opphevet
  «Overføres IKKE»-lista, re-klassifisert rad for rad). 16 ugatede sesjoner + gatede med
  eksakte utløsere + avhengighetsgraf + binær leveringsklar-sjekkliste.
- beslutningsbrief.md: operatør-køen i klarspråk (D-A+C-P1, amendment, toolkit-§8, D-B,
  + nytt foreslått delt punkt).
- 2026-07-10-sesjonsplan-d7.md: superseded-merket med peker.

Steg 0 verifisert: commons-amendment IKKE landet, okf-toolkit-repo finnes IKKE.
STATE.md er local-only (gitignored) — ikke committet.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 08:36:07 +02:00

374 lines
27 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Review-paritet — portfolio-optimiser-claude (2026-07-16)
> **Reviewer:** Fable 5 (xhigh) som orkestrator + seks Opus 4.8 xhigh-subagenter (spec-
> konformitet, sikkerhet/injeksjon, SDK+model-map, testkvalitet/detach, ærlighet §1,
> korrekthet). **Mandat:** full review + paritetsgap-kartlegging under operatør-mandatet FULL
> MAF-PARITET (2026-07-16). Ingen live-fil endret; alle kjørbare eksperimenter i throwaway-
> kopier (scratchpad) med import-isolasjon verifisert, eller rene in-memory-kall mot
> live-moduler (read-only). Ingen API-kall, ingen nettverk (unntatt SDK-agentens
> dokumentasjons-oppslag mot PyPI/Anthropic, eksplisitt tillatt).
>
> **Ny funn-serie R (kolliderer ikke med C-serien i [review 2026-07-10](review-2026-07.md)).**
> Paritetsmatrisen + sesjonsplanen ligger i
> [`2026-07-16-sesjonsplan-paritet.md`](2026-07-16-sesjonsplan-paritet.md); operatør-køen i
> [`2026-07-16-beslutningsbrief.md`](2026-07-16-beslutningsbrief.md).
## 0. Baseline (verifisert 2026-07-16, ikke sitert fra STATE)
| Påstand | Resultat | Belegg |
|---|---|---|
| Testsuite | **265 passed** (1.68s) — STATE-tallet stemmer | `uv run pytest -q` (kjørt) |
| Lint / format / typer | ruff clean (40 filer) · ruff format clean · mypy clean (16 filer, strict) | kjørt |
| Push-status | HEAD `b46f32c` == `origin/main`; `open/main` @ `42f320b` (to review/guard-commits foran open — bevisst ikke speilet); `commons/main` @ `84191c6` | `git rev-parse` alle |
| Subtree-status | `shared/` 2 commons-commits bak (docs-only: `.gitignore` + guard-adopsjonsdoc — INGEN spec-endring) | subtree-diff `HEAD:shared``commons/main` |
| Commons-amendment (D-F/D-G) | **IKKE landet** i commons | samme diff |
| okf-toolkit-repo | **finnes ikke** (lokalt + Forgejo) | `ls` + `git ls-remote` → not found |
| Detach-stikkprøver (4/4) | ALLE RØDE i kopi, revert→grønn, arbeidstre urørt | testkvalitetsagent, §3 |
| SDK-premisser | alle attributter holder mot installert 0.2.110 + t.o.m. nyeste 0.2.120 | SDK-agent, §2 |
| S10-artefakter | `runs/s10/` urørt (fasit) — kun lest | ikke re-kjørt |
**Konklusjon baseline:** ingen BLOCKER. STATE-ens tall er korrekte. De nye funnene under er
robusthets-/korrekthetsdefekter (mest alvorlige R-2/R-3, begge MEDIUM) + paritetsgap +
konformitetsgapet R-1 (§11-raden «Closed loop» ubundet).
---
## 1. Status på eksisterende funn (C-serien, review 2026-07-10)
Alle re-verifisert; ingen har endret premiss. Stikkprøvene under er kjørt på nytt, ikke sitert.
| C-funn | Status 2026-07-16 | Belegg (re-verifisert) |
|---|---|---|
| C-F2 (nominal gater aldri) | **ÅPEN, bekreftet** | `validator.py:53` beregner `nominal_feasible`, `:73` bruker den kun i avvisnings-*meldingen*; eneste numeriske gate er p90 (`:68`) |
| C-F3 (ingen kostbaseline) | **ÅPEN, bekreftet** | `ir.py:43-48` sjekker kun claim ≤ Σ quantity·unit_cost; diktede kostlinjer validerer |
| C-F4 (`/`-lenker skippes) | **ÅPEN, spec-tro** | `okf.py:112` skip; spec §3 Steg 1 pinner regelen |
| C-F5 (seeding-nøkling) | **ÅPEN med nyanse** | features fra `load_validator_input(bundle_dir)` (`experience.py:132`), ikke per verdict-fil; id-halvdel løst (verbatim `verdict_id` `:147`). Ny konkret drop/count-effekt: **R-8** |
| C-F7 (inbox-injeksjon) | **ÅPEN, kjørt-bevist på nytt** | injeksjons-`rationale` OG `decision` når prompt verbatim; `decision` tillater newline som bryter linjeformatet (sikkerhetsagent §a). Ny run-path-nyanse: **R-9** |
| C-N1 (method-spec-vakt mangler) | **ÅPEN, bekreftet** | ingen `test_method_spec_loadbearing.py`; §12-kompletthet dermed ikke håndhevet (spec §12 lover det) |
| C-N2 (Steg-7 kjøresti-inngang) | **ÅPEN, bekreftet** | `merge_inbox_into_store` refereres kun fra tester (grep); `run_s10.py` har ingen `--inbox` |
| C-N3 (løs SDK-pin) | **ÅPEN, bekreftet** | `pyproject.toml:8` `>=0.2` uten øvre grense; «verifisert mot 0.2.110» kun i docstrings, ingen kjørbar versjonssjekk. Anbefalt pin nå: `>=0.2.111,<0.3` (§2) |
| C-N4 (budsjett post-charge / USD per-kall) | **ÅPEN, bekreftet** | `charge_tokens` etter `client.complete` (`loop.py:90-91`); per-kall-USD via `max_budget_usd`, ingen run-total |
| C-S1 (REJECT-substring-presedens) | **ÅPEN, spec-pinnet** | `_REJECT_PATTERN` først med presedens (`loop.py:188-191`) |
| C-S2 (max_rows etter innlesing) | **ÅPEN, bekreftet + utvidet** | `_read_csv` (`ingest.py:191-195`) OG `_read_sql` `fetchall()` (`:244-248`) — se **R-7** |
| C-S3 (promotion RMW ikke-atomisk) | **ÅPEN, dokumentert MVP-grense** | `promotion.py:92-103` |
| C-P1 (D-C ↔ frossen §3 Steg 1) | **ÅPEN, delt** | står til D-A-bolken (beslutningsbrief pkt. 1); `test_surface_text_never_contributes` håndhever regelen i dag |
---
## 2. Nye funn (R-serien, rangert)
### R-1 [MEDIUM · konformitet] §11-raden «Closed loop» er ubundet — `test_simulation_loadbearing.py` mangler
- **Belegg:** method-spec §11 (`method-spec.md:408`) navngir sømmen «the two-run simulation's
marker crosses runs [via] promotion (or fails to cross with it)». Filen finnes ikke; ingen
test importerer både `promote` og en run-komposisjon (grep, spec-konformitetsagent). Den
nærmeste — `test_step8_promotion_loadbearing.py:143` — binder `promotion→seed→fold` (markør i
prompt), IKKE en to-kjørings-simulering med negativ kontroll. `test_step7` binder
to-kjørings-flippen, men via **inbox**-stien, ikke promotering.
- **Feilscenario:** hvis promoterings-stien slutter å lukke lærings-loopen på run-nivå
(promotert verdict seedes ikke inn i en senere kjøring), går INGEN test rød. Dette er §11s
eneste udekkede rad utover C-N1s spec-vakt.
- **Distinkt fra C-N1** (spec-integritet, §11 rad 11) og **C-N2** (Steg-7 kjøresti-inngang,
D7-egen kjørbarhet). R-1 er §11 rad 8.
- **Fiks:** ny `tests/test_simulation_loadbearing.py` + tynn deterministisk to-run-driver
(`simulation.py`, ScriptedClient) → sesjon **K4** (byggbar nå, forutsetter C2.0-inngangen).
### R-2 [MEDIUM · korrekthet] `Infinity` beseirer den deterministiske validatoren — KJØRT-BEVIST (også av orkestrator)
- **Belegg:** Pydantic v2 har `allow_inf_nan=True` som default, så `float('inf')` tilfredsstiller
`unit_cost: Field(gt=0)` (`ir.py:27`) og de ubegrensede `assumptions`-båndene (`ir.py:37`);
`json.loads` godtar det ikke-standard JSON-tokenet `Infinity`.
- **Kjørt bevis (orkestrator, mot live-modul, read-only):**
`unit_cost: Infinity``SavingsProposal` konstrueres, `validate_proposal`
`ValidatedProposal(validates=True, nominal_feasible=inf, p90=inf)`. Samme via
`assumptions: {"A":[1.0, Infinity]}``validates=True, p90=inf`. **NaN avvises korrekt**
(kontroll: `ValidationError`) — kun `inf` slipper gjennom.
- **Feilscenario:** validatoren er spec-ens obligatoriske, blokkerende anker mot
swarm-selvbekreftelse (§4). Ett `Infinity`-token fra proposer-modellen — nåbart på kjørestien
via `loop._parse_candidate` — får ethvert claim til å klarere porten vakuøst (`claimed > inf`
er alltid False).
- **Fiks:** `allow_inf_nan=False` på de numeriske IR-feltene (eller eksplisitt finitthetssjekk
av `quantity`/`unit_cost`/`claimed_saving_nok` + bånd-endepunkter). Ingen beslutnings-
avhengighet, golden uendret (golden-tallene er finite) → sesjon **C2.6** (byggbar nå).
### R-3 [MEDIUM · korrekthet/robusthet] Tom kilde-CSV krasjer ingest MIDT i materialisering → delvis bundle
- **Belegg (kjørt av korrekthetsagent; mekanisme re-verifisert av orkestrator):**
`_render_table([])` kaster `ValueError: not enough values to unpack` (`ingest.py:176`,
`header, *data = rows`). `_read_csv` returnerer `[]` for 0-byte fil (`ingest.py:188-197`;
`data_rows=0` → ingen max_rows-brudd fanger det).
- **Feilscenario:** en ekstraksjon med tom CSV → krasj INNE i `materialize`s skrive-løkke,
ETTER at stemplede filer er fjernet og tidligere ekstraksjoner skrevet, FØR `_update_index`
→ bundle etterlatt delvis materialisert (bekreftet: `ingest-first.md` skrevet, ingen
`index.md`, gamle stemplede filer allerede slettet). SQL-stien er immun (header alltid til
stede).
- **Fiks:** vokt tom `rows` med klar fail-fast FØR disk-mutasjon (eller behandle som tom
tabell) → sesjon **K2.9** (byggbar nå).
### R-4 [LOWMEDIUM · korrekthet] UTF-8 BOM korrumperer stille første CSV-header
- **Belegg (kjørt av korrekthetsagent; encoding re-verifisert av orkestrator: 5×
`encoding="utf-8"`, ingen `utf-8-sig` i `ingest.py`):** Excel/Windows-CSV med ledende BOM →
første header-celle blir `'code'` i stedet for `'code'`; korrupt header flyter verbatim
inn i markdown-tabell + concept-fil. Ingen krasj — stille datakorrupsjon.
- **Fiks:** les CSV med `utf-8-sig` → sesjon **K2.9**.
### R-5 [LOWMEDIUM · robusthet] Malformert men EKSISTERENDE lenket concept-fil avbryter ALL bundle-navigasjon
- **Belegg (kjørt av korrekthetsagent):** `parse_concept_file(resolved)` i `navigate_bundle`
(`okf.py:118`) kaster `ValueError` for en lenket fil som eksisterer men mangler
frontmatter/`type` — fanges ikke. Robusthetsregelen (skip broken/escaping links) dekker kun
ikke-eksisterende / utenfor-bundle mål.
- **Feilscenario:** én dårlig generert/kuratert fil tar ned hele lese-konteksten: `bundle_context`,
`build_citations` OG `seed_store_from_bundle` (seeding avbrytes helt i stedet for å hoppe over
den ene fila). Kan være tiltenkt fail-fast — **asymmetrien mot skip-broken-links fortjener en
eksplisitt beslutning** (tolerér-og-hopp vs fail-fast), ikke stille divergens.
- **Fiks:** beslutt semantikk + bind med test → sesjon **K2.9** (med eksplisitt valg dokumentert).
### R-6 [LOW · sikkerhet, latent] `inbox.write_verdict` mangler id-grammatikk → path-traversal ved skriving
- **Belegg (kjørt av sikkerhetsagent; konstruksjon re-verifisert av orkestrator):**
`VerdictDocument.id` er kun `Field(min_length=1)` (`inbox.py:46`) — ingen grammatikk, i
motsetning til `source.id`/`extraction.id` (`_ID_RE`, `ingest.py:63-68,100-105`).
`write_verdict` skriver `inbox_dir / f"{verdict.id}.json"` (`inbox.py:96`). Konstruksjon med
`id="../../escaped"` aksepteres verbatim (orkestrator-kjørt) → skrivemål utenfor inbox.
- **Feilscenario:** i dag ALLTID mintet id (`from_candidate``mint_verdict_id`, 16 hex) → ikke
nåbar med fiendtlig input nå. Latent hvis en fremtidig sti skriver en verdict med untrusted id.
Defensiv asymmetri beviser glippen: `promotion._filename_token` behandler samme id som
fiendtlig (saniterer + hash-fallback, `promotion.py:34-40`).
- **Fiks:** samme grammatikk/sanering før `write_verdict` eller på `VerdictDocument.id` → foldes
inn i **C2.5** (inbox-herding).
### R-7 [LOWMODERAT · robusthet] `_read_sql` materialiserer hele resultatsettet før `max_rows` (nytt tilfelle av C-S2)
- **Belegg (kjørt av sikkerhetsagent):** `cursor.fetchall()` (`ingest.py:244`) FØR
`len(data) > max_rows` (`ingest.py:247`); 1000-rads tabell med `max_rows=10` → alle 1000 i
minne før cap-en hever feil. Samme feilmodus som C-S2 i `_read_csv`.
- **Feilscenario:** robusthet/DoS-mot-seg-selv (begge kilder førstepart-scopet), ikke
angriper-drevet i dag. Fail-fast i UTFALL (aldri stille trunkering) — akseptabel MVP-grense.
- **Fiks (valgfri):** `LIMIT max_rows+1` / `fetchmany` for SQL, streaming-tell for CSV → **K2.9**
eller notér som akseptert MVP-grense.
### R-8 [LOW · korrekthet, knyttet til C-F5] `seed_store_from_bundle` over-rapporterer antall; seeds uten `verdict_id` kollapser stille
- **Belegg (kjørt av korrekthetsagent):** alle seedede verdicts deler SAMME features
(bundle-IR-projeksjonen), så flere seed-filer UTEN `verdict_id` minter samme id →
first-write-wins dropper alle utenom første, men funksjonen returnerer FIL-antallet
(`experience.py:132,145-153`). To `type: verdict`-filer, ulik decision/rationale, ingen
`verdict_id``seeded=2` returnert, `len(store)==1`; det andre læringssignalet tapes.
`run_s10.py:86` printer det inflaterte tallet.
- **Feilscenario:** konkret manifestasjon av C-F5s feature-halvdel + en count-ærlighetsfeil
(rapportert antall ≠ faktisk lagret). Løses av per-verdict-nøkling (C3.2); count-ærligheten
bør fikses uansett.
- **Fiks:** C3.2 løser roten; legg en count-korreksjon (returner faktisk lagret antall, eller
logg drops) i C3.2s scope.
### R-9 [MERKNAD · sikkerhet — presiserer C-F7/guard-plan, ikke ny defekt] Fold-som-injeksjonsbærer NÅR kjørestien via bundle-seeding
- **Belegg (kjørt av sikkerhetsagent):** mens inbox-injeksjonen (C-F7) ikke er wiret til noen
kommando (C-N2), ER samme injeksjonsbærer wiret via den ANDRE folde-kilden:
`run_s10.py:73``seed_store_from_bundle` leser `type: verdict`-frontmatter og folder den
inn. En forgiftet `promoted-verdict-*.md` i bundelen nådde folded prompt på run-stien
(kjørt: `- aaaa [approved]: IGNORE PRIOR INSTRUCTIONS ... INJECT-MARKER-SEED`). Seed-stien er
single-line-begrenset (frontmatter-parser), så inbox er strengt verre — men *fold-som-bærer*
treffer den faktiske kjørestien.
- **Konsekvens for guard-planen:** dette er nøyaktig trust-grensen guard-planens §2 rad 5/6
beskriver (mottatt/delt bundle med forgiftet promotert verdict). Bekrefter at G3
(`okf.import_bundle` ved delt/mottatt bundle) er riktig plassert, og at promoterings-
gaten (§6, fail-closed, kun ekspert-godkjent) er den nåværende beskyttelsen. **Ingen ny
kodeendring kreves nå** — men guard-planens markørlinje bør nevne bundle-seed-stien
eksplisitt som den wirede fold-bæreren (i dag nevner den inbox). Notert i STATE-oppdatering.
### R-10 [LOW · testdekning] Run-path-orkestrering og SDK-meldingshåndtering er offline-ubundet
- **Belegg (testkvalitetsagent, coverage i kopi, 90% total):** `run_s10.py` 0% (`main()`-
orkestreringen, inkl. `BudgetExceeded`-stopp-grenen `:99-114` som docstringen selv kaller en
tidligere regresjon «stopped here with NO record»); `sdk_client.py` 63% (meldingshåndteringen
i `_complete_async` — tekst-aggregering, error-fail-paths, usage/kost-ekstraksjon,
`_total_tokens` fail-closed, `max_budget_usd_per_call<=0`-guard — kjøres aldri fordi
`test_sdk_isolation`s `fake_query` yield-er tom strøm).
- **Feilscenario:** en regresjon i stopp-artefakt-persisteringen eller usage-ekstraksjonen går
ubemerket. `run_s10.py` er fasit-sti (skal ikke endres), men logikken KAN bindes offline bak
en klient-seam på den NYE kjøresti-inngangen (C2.0) uten å røre fasit-stien.
- **Fiks:** budsjett-stopp-binding på C2.0-stien (R-10a); syntetisk-strøm-test for
`sdk_client._complete_async` (R-10b) → foldet inn i **C2.0** og **C2.5** (se sesjonsplan).
### Lavere / kant (korrekthetsagent + spec-agent, kort — hver notert, ikke egen sesjon)
- **assumptions-bånd uten grense** (`ir.py:37`): negative bånd-verdier godtas → skjev MC
(negative p10/p50, kjørt). Løses av samme finitthets-/gyldighets-hardening som R-2 (C2.6).
- **tom-streng modell-id passerer `ModelMapContract`** (`contracts.py:43`, ingen `min_length`)
`resolve_model` kan returnere `""`. PLAUSIBEL, lav. Foldes inn i C2.6/contracts-hardening.
- **promotion filnavn-token-kollisjon** (`promotion.py:34-40`): to distinkte usikre id-er →
samme token → overskriv/dedup. Kun nåbar med ikke-mintede id-er. PLAUSIBEL, kant.
- **anker/query-krysslenker droppes stille** (`okf.py:24` `_CROSSLINK_PATTERN` krever `.md)`
umiddelbart) → `(x.md#sec)` navigeres aldri. LAV, spec-tro (regelen er `\]\(([^)]+\.md)\)`).
- **ikke-SELECT SQL → `cursor.description is None` → TypeError** (`ingest.py:243`). Konfig-feil,
kryptisk melding. PLAUSIBEL, lav.
- **`check_turn_safety_net` er en død vokter** (`loop.py:111`): turns ≤ 2·max_rounds <
2·max_rounds+2, så den kan aldri utløses gitt løkkegrensene. Ikke en defekt — men §8s
«turn-count termination safety net above [the round cap]» er dermed en no-op i praksis;
notér i C2.6 eller egen liten opprydding.
- **OBS-2 (spec-agent):** `DataSourceContract.docs_dir` valideres (`contracts.py:36`) men
`run_s10` navigerer `args.bundle` direkte (`run_s10.py:63,69`) — konsistent i dag, men et
fremtidig avvik passerer stille. LAV.
---
## 3. Testkvalitet + §11 load-bearing (detach-stikkprøver)
**4/4 detach-eksperimenter RØDE i throwaway-kopi** (testkvalitetsagent), hver revert→grønn,
arbeidstre urørt (`git status` = kun `?? .claude/`, HEAD `b46f32c`):
| # | Detach (i kopi) | Forventet | Faktisk |
|---|---|---|---|
| A | checker-override kortsluttet (`loop.py apply_checker_gate`) | `test_checker_gate_loadbearing` rød | **RØD, 3 failed** |
| B | inbox-merge → `pass` (`inbox.py`) | `test_step7_async_loop_loadbearing` rød | **RØD, 2 failed** |
| C | verbatim-id re-mintet (`experience.py seed`) | `test_step8_promotion_loadbearing` rød | **RØD, 2 failed** |
| D | `okf_type` fjernet fra ingest-spec-KOPI | `test_ingest_spec_loadbearing` rød | **RØD, 1 failed** |
Baseline i kopi 265 passed; etter alle reverts 265 passed re-bekreftet. Metoden holder på de
stikkprøvde sømmene (paritet med 2026-07-10s 11/11 på disse fire).
**Presisjons-lærdom (detach D):** ingest-spec-vaktens assertion er substring-hvor-som-helst,
ikke tabellspesifikk — å fjerne KUN §12-tabellraden ville ikke detache (feltet står også i
felt-definisjonen). Dette må C1.1s method-spec-vakt ta høyde for (dokumentér begrensningen
eller scope assertion til §12-blokken).
**Testkvalitetssveip:** ingen vakuøse assertions (ingen `assert True`/`xfail`/`skip`/TODO);
ScriptedClient (`tests/_scripted.py`) bekreftet SDK-import-ren (importerer kun
`typing.Callable` + `loop.ModelReply`; `loop.py` drar ingen SDK); suiten deterministisk uten
nøkkel/nett (ingen ambient `os.environ`/socket/api_key; eneste `setenv` er lokal
sqlite-fixture-sti). Dekningshull: **R-10** (run_s10 0%, sdk_client 63%).
---
## 4. SDK-bruk + modell-map-gyldighet (SDK-agent, med kilder)
- **Alle SDK-attributt-premisser i `sdk_client.py` holder mot installert 0.2.110** — verifisert
mot pakkekilden (`.venv/.../claude_agent_sdk/types.py`): `AssistantMessage.error/.model`,
`ResultMessage.usage/.total_cost_usd/.is_error/.subtype/.errors`,
`ClaudeAgentOptions.max_budget_usd/.setting_sources/.system_prompt/.max_turns/.model/.tools`,
`TextBlock.text`, `query`. `setting_sources=[]`-isolasjonspåstanden og
`system_prompt=None``--system-prompt ""`-serialiseringen er ordrett dokumentert/verifisert
ned til CLI-argumentet (`subprocess_cli.py:227-228`).
- **Nyeste SDK: 0.2.120 (15. juli 2026)**; installert 0.2.110. **Ingen breaking changes** på de
brukte attributtene 0.2.110→0.2.120 (hovedsakelig bundlede CLI-bumps). **0.2.111 (6. juli)
fikser feil som treffer nettopp denne klientens leseløkke** (NDJSON-parser mistet whitespace
på linjer >64 KiB; TypeError-krasj når `content` er ren streng; zombie-subprosess ved
cancellation). Kilder: [pypi.org/project/claude-agent-sdk](https://pypi.org/project/claude-agent-sdk/),
[GitHub releases](https://github.com/anthropics/claude-agent-sdk-python/releases), hentet
2026-07-16.
- **Anbefalt pin: `claude-agent-sdk>=0.2.111,<0.3`** (C-N3) + versjonsvakt-test (C2.5).
- **Modell-map:** `claude-haiku-4-5-20251001` (default/proposer/checker) er **gjeldende** i
offisiell modell-oversikt, IKKE deprecated, og **fortsatt billigst egnet** ($1/$5 MTok;
eneste rimeligere er retired Haiku 3.5; ingen Haiku nyere enn 4.5). Kostnadsdisiplinen
(Haiku 4.5 for en eventuell live-kjøring) holder. Kilder: [platform.claude.com models](https://platform.claude.com/docs/en/about-claude/models/overview),
[pricing](https://platform.claude.com/docs/en/about-claude/pricing), hentet 2026-07-16.
`ModelMapContract` matcher fil-strukturen; ingen manglende/ubrukte roller.
- **Presisjonsnoter:** `test_sdk_isolation.test_the_system_prompt_is_empty` hevder i
docstringen `--system-prompt ""`-serialisering men asserterer bare `system_prompt is None`
(grønn-men-død for akkurat den claimen — påstanden er korrekt, testen dekker den ikke);
runtime-feltnavnene i `usage`-dicten (`input_tokens` etc.) kan ikke bekreftes uten et
forbudt `query()`-kall, men koden degraderer fail-closed til `None` ved avvik.
---
## 5. Spec-konformitet (spec-agent)
- **~70 normative krav KONFORME** (implementert + bundet av test). Nøkkelbelegg: §3 Steg 1
navigasjon `okf.py:93-119` + verdict-eksklusjon `:131`; fold/seeding `experience.py:60-171`;
Steg 4 validator `validator.py:50-83` + checker-gate `loop.py:186-212`; §6 promotion
`promotion.py:48-103`; §8 budsjett `budget.py:35-57`; §10 startup `contracts.py:98-116`;
ingest verdict-reservasjon `ingest.py:114-122`, provenance-lag `:279-297`.
- **Konformitetsgap:** §1 konformans-pkt 3 (hver load-bearing søm har detach-test) er 9/11 —
C-N1 (spec-integritet) + R-1 (closed loop) mangler; §12-kompletthet er derfor ikke håndhevet
(spec §12 lover at spec-integritetstesten gjør det).
- **Konformt-men-uvoktet (folder inn i C-N1):** §1 ærlighetsregelen, §4.3 persona-prosa-
nøytralitet og §11 spec-integritet er alle konforme NÅ, men uten automatisk vakt — drift
ville være usynlig. Nettopp lasten C-N1 skal bære.
- **ingest §8 Network gate:** N/A i D7-scope (http uimplementert/nektet ved manifest-validering;
file+sql lokale → ingen egress-sti). Bekreftet av sikkerhetsagentens egress-grep (0 treff).
**§12-felt konsumert av src-logikk** (for C1.1s vaktdesign): `decision`, `rationale`,
`id`/`verdict_id`, `proposal_features`, `affected_codes`, `measure_type`, `claimed_saving_nok`,
`description`, `marker`, `project_id`, `measure`, `affected_items`, `code`, `quantity`,
`unit_cost`, `assumptions`, `validates`, `nominal_feasible`, `p10`, `p50`, `p90`,
`realization_rate`, `expected_actual_saving_nok`, `type`, `approved`, `rejected`,
`approved_with_adjustment`. **Kun golden/test-konsumert (ikke lest av src):** `outcome`,
`modelled_saving_nok`, `gap_source`, `context_key`. Anbefaling: vakten dekker ALLE 25 (mirror
av ingest-vakten), med bevissthet om at de fire siste er fasit-/test-konsumert.
---
## 6. Ærlighetsregel §1 (docs)
Verifisert av orkestrator mot faktiske artefakter, OG uavhengig bekreftet av ærlighets-agenten
(samme konklusjon: én myk overclaim, ingen harde, ingen umerkede stand-ins, ingen utdaterte
tall; agenten rekonsilierte S10-tallene byte for byte og la til p10 68 543 / p90 121 057).
**Ingen OVERCLAIM funnet** (utover den ene myke F1/C-N2 under). Docs er gjennomgående ærlige (§1). Verifiserte claims:
| Claim | Verifisert mot | Status |
|---|---|---|
| «265 tests, offline uten API-nøkkel» (`README.md:16-18,153`; `CHANGELOG`) | `uv run pytest` (265 passed uten nøkkel/nett) | ÆRLIG |
| S10-tall: «$0.127514 · 36 791/150 000 tokens · 2 of 12 rounds» (`README.md:132-138`) | `runs/s10/usage.json` (`cost_usd 0.127514`, `tokens_used 36791`, `max_tokens 150000`, `rounds_used 2`, `max_rounds 12`) — matcher byte for byte | ÆRLIG |
| «everything else is deterministic and offline; nothing here claims more live behaviour than that one documented run» (`README.md:140-141`) | S10 er eneste live-kjøring; resten offline | ÆRLIG (eksplisitt §1-selvbegrensning) |
| «http/MCP … D7 does not implement it … no network egress path» (`README.md:118-123`, `extending.md:128-155`) | egress-grep 0 treff (sikkerhetsagent); http avvist fail-fast | ÆRLIG (gjentatt, presist hedget) |
| «`create_sdk_mcp_server`/`@tool` … not used anywhere in this repo» (`extending.md:152-155`) | `ingest.py` ren stdlib | ÆRLIG |
| ScriptedClient som stand-in | `_scripted.py:1-7` merket §1 | ÆRLIG |
| ingest «zero model calls, imports nothing from SDK» (`README.md:100-102`) | `ingest.py:5`; import-grep | ÆRLIG |
**Én honesty-nær sak (ikke overclaim, men upresis):** README-ens inbox-avsnitt
(`README.md:66-68`) «a later run ingests it tolerantly and merges it before the fold» er sant
om KONTRAKTEN, men ingen leverbar kjøresti komponerer den i dag (C-N2 — `merge_inbox_into_store`
kalles kun fra tester). Formuleringen impliserer en kjørbar «later run» som ikke finnes som
kommando. Lukkes av C2.0 (kjøresti-inngangen gjør claimet sant om en skipbar sti). Merk
tilliggende nyanse fra R-9: fold-bæreren ER wiret via bundle-seeding på run-stien — så «merges
it before the fold» er delvis sant (seed-folden kjører; inbox-folden gjør ikke). **Presis fiks
(ærlighets-agentens anbefaling):** ordet «run» i `README.md:67` er det eneste som bærer
overclaimet — kvalifisér til f.eks. «the async-loop seam — proven by `test_step7` in a two-run
simulation — ingests … before the fold», ELLER (bedre) la C2.0 gjøre setningen bokstavelig sann
ved å levere kjøresti-inngangen. C2.0-scopet oppdaterer allerede README-inbox-avsnittet.
**Ærlighetsregelens egen svakhet (konformitet):** ingen automatisk vakt håndhever §1 på
docs/spec (C-N1 mangler) — dagens ærlighet er ekte, men drift ville være usynlig. C1.1 lukker
spec-siden; en docs-ærlighets-test (K12s README-claims-mot-CLI-sjekk) lukker README-siden.
---
## 7. Verifiseringslogg
| Påstand | Kilde/kommando |
|---|---|
| 265 passed / ruff / mypy clean | `uv run pytest -q` (1.68s); `ruff check .`; `ruff format --check .`; `mypy src` (2026-07-16) |
| HEAD/remote-sync | `git rev-parse HEAD origin/main open/main commons/main`; `git fetch` alle |
| commons-amendment ikke landet / toolkit finnes ikke | subtree-diff `HEAD:shared``commons/main` (docs-only); `ls ~/repos/okf-toolkit` + `git ls-remote` → not found |
| R-2 (Infinity) | orkestrator kjørte mot live `ir`+`validator`: `unit_cost:Infinity` → ValidatedProposal(True, inf, p90=inf); `assumptions:[1.0,Infinity]` → True, p90=inf; NaN → ValidationError |
| R-3 (tom CSV) | orkestrator: `ingest._render_table([])``ValueError: not enough values to unpack`; korrekthetsagent: delvis bundle bekreftet i kopi |
| R-4 (BOM) | orkestrator: `ingest.py` har 5× `encoding="utf-8"`, 0× `utf-8-sig`; korrekthetsagent kjørte korrupt header i kopi |
| R-6 (id-traversal) | orkestrator: `VerdictDocument(id="../../escaped")` aksepteres verbatim; sikkerhetsagent skrev fil utenfor inbox i kopi |
| 4/4 detach røde | testkvalitetsagent, throwaway-kopi, import-isolasjon verifisert, revert→265 passed, arbeidstre urørt |
| SDK-premisser + modell-id | SDK-agent mot `.venv/.../types.py` (0.2.110) + PyPI/GitHub/platform.claude.com (2026-07-16) |
| C-F7 / R-9 fold-injeksjon | sikkerhetsagent, kjørt i kopi: inbox verbatim; bundle-seed på run-stien (`run_s10.py:73`) verbatim |
| Guard-plan §2-tabell rad 16 | sikkerhetsagent, kjørt: path-escape avvist, sql read-only, http fail-fast, 0 egress-call-sites |
| S10-artefakter urørt | kun lest; ingen re-kjøring |
---
## 8. Oppsummering for sesjonsplanen
Nye ugatede hardening-sesjoner (byggbare nå, injisert i
[sesjonsplanen](2026-07-16-sesjonsplan-paritet.md)):
- **C2.6** — validator/IR finitthets- og gyldighets-hardening (**R-2** primær; +
negative bånd, tom-streng modell-id, død turn-safety-net-vokter). Høyest verdi: lukker en
faktisk validator-omgåelse på kjørestien. Golden uendret (finite tall).
- **K2.9** — ingest-robusthet (**R-3** tom CSV fail-fast før disk-mutasjon; **R-4** `utf-8-sig`;
**R-5** malformert-lenket-fil-semantikk med eksplisitt beslutning; **R-7** valgfri max_rows-
strømming). Golden uendret.
- **R-1/K4** — closed-loop to-kjørings-simulering (§11-raden), allerede i planen.
- **R-6** → foldet inn i **C2.5** (id-grammatikk på `VerdictDocument`).
- **R-8** → foldet inn i **C3.2** (count-ærlighet ved seeding).
- **R-10** → bindinger foldet inn i **C2.0** (budsjett-stopp på ny sti) + **C2.5**
(sdk_client syntetisk-strøm-test).