# Uavhengig kryssmodell-review — portfolio-optimiser-claude (2026-07-10) > **Reviewer:** Fable 5 (xhigh), uavhengig av Opus 4.8 som skrev koden. **Mandat:** speil av > MAF-søskenets kryssmodell-review (2026-07-09) — verifiser hvert MAF-funn mot DENNE kodebasen > (premisser, ikke fakta) + uavhengig sveip etter D7-egne blindsoner. Ingen live-filer ble > endret under selve reviewen; fletting (dette dokumentet + sesjonsplan + STATE) er > operatør-forhåndsgodkjent. > > **Metode:** all kildekode lest (16 moduler), alle nøkkel-tester lest, full lokal suite + > ruff + mypy kjørt, F2/F3/F5/F7-moteksempler KJØRT mot live-modulene (read-only), og > **11 detach-eksperimenter utført i en throwaway-kopi** (scratchpad — live-repoet urørt). > Hver påstand har `fil:linje`- eller kommando-belegg; verifiseringslogg nederst. ## 0. Baseline (verifisert, ikke sitert fra STATE) | Påstand | Resultat | Belegg | |---|---|---| | Testsuite | **265 passed** (1.6s) — STATE/CHANGELOG-tallet stemmer | `uv run pytest -q` | | Lint / typer | ruff clean (40 filer formatert); mypy clean (16 filer, strict) | `uv run ruff check .`; `uv run mypy src` | | Push-status | HEAD `42f320b` == `origin/main` == `open/main` — STATEs påstand stemmer | `git rev-parse` alle tre | | Offline-invariant | Suiten kjører uten nøkkel/nettverk; ingen egress-stier i `src/` utenfor SDK-klienten | suite-kjøring + grep `urllib\|socket\|requests` | | S10-artefakter | `runs/s10/` urørt (fasit, protokoll §4.2/§4.5) — IKKE re-kjørt | kun lest | **Detach-bevis (load-bearing-stikkprøver, ALLE 11 RØDE i kopien — MAF-paritet 11/11):** (1) Steg-1-fold detached → `test_step1` + `test_step7` røde (4 failed); (2) checker-override detached → `test_checker_gate` rød (3); (3) inbox-merge detached → `test_step7` rød (2); (4) promotion-index-lenke detached → `test_step8` rød (5); (5) verdict-eksklusjon fjernet fra `bundle_context` → `test_okf` + `test_step8` røde (2); (6) `setting_sources=[]` → `None` → `test_sdk_isolation` rød (2); (7) informert refinement detached → `test_step5` rød (2); (8) verbatim-id-seeding re-mintet → `test_step8` rød (2); (9) re-ingest-lagsikkerhet detached (alle .md «stamped») → `test_ingest_loadbearing` rød (1); (10) `import claude_agent_sdk` i `okf.py` → purity-testene røde (2); (11) usage-fail-closed detached → `test_budget` rød (1). Kopien verifisert isolert (pakken importerte fra kopiens `src/`) og revertert ren etterpå. **Metoden holder på alle stikkprøvde sømmer.** --- ## 1. Funn (rangert, mest alvorlig først) Ingen BLOCKER i dagens påstands-omfang: single-run-claims er sanne, detach-beviste, og README/extending.md er gjennomgående ærlige (§1). Funn 1–2 og 4–5 er spec-nivå-defekter som finnes her *by construction* (samme frosne spec) og deler beslutning D-A med MAF-siden. ### C-F2 [MAJOR · spec-nivå] Validator: nominal-grensen gater aldri — selv-forfattede bands blåser P90 forbi «maximum feasible» (F2-analog: BEKREFTET) - **Belegg:** eneste numeriske blokk er P90 (`validator.py:68`); `nominal_feasible` beregnes (`validator.py:53`) men brukes aldri som gate. `assumptions` forfattes av modellen selv (`loop.py:66-73` parser fritt; `ir.py:30-49` har ingen invariant om at bandet omslutter `unit_cost`). **Kjørt bevis (live-modul, read-only):** golden-bundelens egne tall — total 300 000, nominal 90 000, band [0.70, 1.40] — validerer claim **100 000 > 90 000** (p90=121 057.09). Band helt over unit_cost ([1.8, 2.2]) validerer claim **55 000 mot nominal 30 000** (p90=65 058.49). Nøyaktig samme tall som MAF-moteksemplene — bekrefter at defekten er spec-båren, ikke implementasjonsbåren. - **Feilscenario:** en ekte proposer setter et optimistisk band → claim over den deterministiske feasibility-grensen stemples `validated` og persisteres som «validator-godkjent» i run-artefaktene. - **Fiks:** speiling av MAF S2.7 — (a) strukturell blokk `claimed > nominal_feasible`; (b) IR-invariant `low ≤ unit_cost ≤ high`. **Golden- OG fixture-kompatibel her, verifisert:** golden-claim 30 000 ≤ 90 000; alle ikke-tomme bands i dette repoets fixtures omslutter unit_cost ([0.8,1.2]∋1.0 `test_ir.py:26`; [0.70,1.40]∋1.0 golden; (0.70,1.50)∋1.0 `test_bygg_energi_mikro.py:83`; resten tomme). Gated på **D-A pkt. 1 + commons-pull** → sesjon **C2.7**. ### C-F3 [MAJOR · spec-nivå] `affected_items` forankres ikke mot noen kostbaseline (F3-analog: BEKREFTET) - **Belegg:** `_parse_candidate` aksepterer vilkårlige modell-forfattede kostlinjer (`loop.py:66-73`); ingenting i bundle-formatet bærer en kostbaseline å avstemme mot. **Kjørt bevis:** diktet linje `{code:"XX-DIKTET", quantity:1e6, unit_cost:10}` → total 10 MNOK → claim **2 900 000 stemples `validated`** (degenererte percentiler 3.0 MNOK uten band). IR-invarianten (claim ≤ items-total, `ir.py:39-49`) regner kun på selv-deklarerte tall. - **Feilscenario:** mot en ekte modell er «validatoren avgjør tallene» tom mot hallusinerte kostlinjer; kun checker-LLM-en (fail-open, `loop.py:200-212`) står imellom. - **Fiks:** speiling av MAF S4.0 — fail-closed avstemmings-stage mot en baseline-projeksjon i bundelen (commons-amendment for `cost-baseline.json`). Gated på **D-A pkt. 2** → sesjon **C4.0**. MÅ være grønn før enhver eventuell fremtidig live-kjøring i dette repoet (D6: ny spend krever uansett ny operatør-godkjenning). ### C-N1 [MAJOR · konformitet · D7-EGEN] Method-spec-integritetsvakten mangler — §11-raden «Spec integrity» er uimplementert - **Belegg:** ingen test i repoet refererer `method-spec.md` (`grep -rn "method-spec\|method_spec" tests/` → null treff i testkode). Spec §11 krever: «this spec goes missing, names a framework, or stops documenting a consumed contract field» → rød (referansetest `test_method_spec_loadbearing.py`); §1 pkt. 3 gjør §11-tabellen til konformitetskrav. Ingest-spec-en HAR sin analog (`test_ingest_spec_loadbearing.py` — forbidden-toolkits + kontraktsfelt-dekning), så mønsteret finnes i repoet; method-spec-vakten ble aldri speilet. Spec-intro-ens «a guard test keeps it that way» er dermed sann kun via MAF-søskenets test — dette repoet håndhever den ikke selv. - **Feilscenario:** en commons-pull som (utilsiktet) endrer method-spec-en — fjerner et kontraktsfelt fra §12-tabellen eller introduserer rammeverksnavn — passerer denne suiten grønt; D7 mister sin eneste lokale detektor for spec-drift i subtree-synken. - **Fiks:** ny `tests/test_method_spec_loadbearing.py` etter ingest-spec-mønsteret (fil-eksistens, forbidden-toolkits, §12-kontraktsfelt-dekning for feltene DENNE implementasjonen konsumerer). Ingen beslutnings-avhengighet → sesjon **C1.1** (byggbar NÅ). ### C-F5 [MAJOR · spec-nivå] ExpeL-seeding feilnøkler features på bundelens ENE kandidat (F5-analog: BEKREFTET, med nyanse — id-halvdelen er allerede løst her) - **Belegg:** `seed_store_from_bundle` leser features fra `load_validator_input(bundle_dir)` — bundelens ENE IR-projeksjon — og gir ALLE `type: verdict`-filer de samme features (`experience.py:132`, `experience.py:145-152`). **Nyansen:** `verdict_id` leses VERBATIM fra frontmatter (`experience.py:147`), så id-kollisjonen MAF-funnet beskrev er avverget for promoterte filer — load-bearing-bevist (`test_step8:test_two_promoted_candidates_both_seed_verbatim_ids`). **Kjørt bevis for rest-defekten:** to promoterte dommer om ulike kandidater (E01/led-retrofit vs E99/ventilation) seedes begge med bundle-features (ENERGI-TOTAL-EL/LED) — retrieval kan ikke skille dem; en spørring med kandidat Bs egne features rangerer på feil grunnlag. - **Feilscenario:** flerkandidat-bundle: dom om tiltak B foldes inn i hypoteser om tiltak A — feil læringssignal, presist det klynge C-skalering forutsetter at ikke skjer. - **Fiks:** speiling av MAF S3.2 — seed leser features fra verdict-filas egen frontmatter (promote skriver dem), fallback dagens nøkling. Gated på **D-A pkt. 4** → sesjon **C3.2**. ### C-F4 [MAJOR · frossen-ramme-utfordring] `/`-prefiksede OKF-lenker hoppes over — spec-tro implementasjon av en spec-feil (F4-analog: BEKREFTET) - **Belegg:** `navigate_bundle` skipper ethvert mål med `/` eller `\` (`okf.py:112`) — method-spec §3 Steg 1 pinner regelen. OKF-spec-en anbefaler bundle-rot-relativ `/`-form (MAF-review U-1, WebFetch-verifisert 2026-07-09; ikke re-hentet her — nettverksregel). - **Feilscenario (D7-spesifikk nyanse):** en ekstern bundle med `](/tiltak.md)`-lenker navigerer til KUN index. Har indexen citerbar body, feiler kjøringen IKKE fast — `build_citations` siterer index-bodyen (`artifacts.py:31-59`) og kjøringen fortsetter med stille kontekst-tap; kun en body-løs index gir fail-fast («no citable content»). - **Fiks:** kun via **D-A pkt. 3** (spec-amendment: ledende `/` mappes til bundle-rot, fortsatt fail-closed boundary) → sesjon **C2.8**. Merk: MAF-planen har i dag INGEN bygg-sesjon for F4-implementasjonen på sin side — bør legges til der når D-A lander. (Relatert MINOR, samme som MAF: en index-lenket `log.md` — reservert i OKF — ville rendres som konseptfil; `okf.py:128-134` filtrerer kun verdict + tom body.) ### C-F7 [MINOR nå → MAJOR ved flerbruker/klynge E] Verdict-inboxen er en uvalidert prompt-injeksjonsflate (F7-analog: BEKREFTET) - **Belegg:** `decision` valideres kun som ikke-tom streng (`inbox.py:47`) — modulens docstring UTSETTER eksplisitt vokabular-politiet til §4.1/§6 (`inbox.py:10-13`), men på merge-stien (`inbox.py:122-132`) fins ingen sjekk; `rationale` flyter verbatim inn i hypotese-prompten via folden (`experience.py:157-171`). Ingen størrelses-/antall-tak. **Kjørt bevis:** `{"decision": "hva-som-helst", "rationale": "IGNORE ALL PREVIOUS INSTRUCTIONS…"}` → merged=1, decision-strengen OG injeksjonsteksten når prompten. - **Fiks (fortsatt tolerant — skip, aldri raise):** vokabular-sjekk mot {approved, rejected, approved_with_adjustment}-settet ved merge, tak på rationale-lengde + antall filer. Ingen beslutnings-avhengighet → sesjon **C2.5**. ### C-N2 [MINOR · D7-EGEN] Steg-7-inboxen har ingen kjørbar kjøresti-inngang - **Belegg:** `merge_inbox_into_store` kalles fra INGEN kjøresti — `run_s10.py:44-54` har ingen `--inbox`-arg og kaller kun `seed_store_from_bundle` + `fold_experience` (`run_s10.py:72-79`). Steg 7 (§3/§5: «a run ingests the inbox INTO its store BEFORE the Step-1 fold») er bevist som primitiv + test-komposisjon (`test_step7`, detach-bevist rød), men ingen skipbar inngang komponerer den. README sier «a later run ingests it tolerantly and merges it before the fold» (`README.md:66-68`) — sant om kontrakten, men ingen leverbar kjøresti gjør det. (Til forskjell fra MAF-F1: ingen død docstring-påstand — folden ER wiret på den ene kjørestien som finnes; F1 i MAF-formen er AVKREFTET her.) - **Feilscenario:** en operatør som dropper en dom i en inbox-mappe og re-kjører har ingen kommando som plukker den opp — læringssløyfas lange tidsskala er i praksis kun promoterings-veien (som ER wiret via bundle-seeding). - **Fiks:** generalisert kjøresti-inngang (offline-testbar komposisjonsfunksjon + `--inbox`/`--verdict-dir`-arg; `run_s10.py` forblir urørt fasit-sti). Ingen beslutnings-avhengighet → sesjon **C2.0**. ### C-N3 [MINOR · D7-EGEN] SDK-avhengigheten er løst pinnet og run-stien leser 0.2.110-attributter uten versjonsvakt (F11-analog: AVKREFTET for testdobbelen, rest-risiko på run-stien) - **Belegg:** testdobbelen er REN — `ScriptedClient` implementerer repoets egen `ModelClient`-protokoll og importerer ingenting fra SDK-en (`tests/_scripted.py`), så MAF-F11s privat-API-skjørhet i suiten er AVKREFTET her. Men run-stien leser `AssistantMessage.error/.model`, `ResultMessage.usage/.total_cost_usd/.is_error/.subtype/.errors` («verifisert mot 0.2.110», `sdk_client.py:12-17,108-121`) med pin `claude-agent-sdk>=0.2` (`pyproject.toml:8`) — enhver 0.x-oppgradering kan endre attributt-semantikken stille, og `_total_tokens` summerer da et KRYMPET felt-subsett uten feil (`sdk_client.py:66-71`). - **Fiks:** pin `>=0.2,<0.3` + versjonsvakt-test med handlingsrettet melding (tvinger bevisst re-verifisering av attributt-premissene ved oppgradering) → inn i sesjon **C2.5**. ### C-N4 [MINOR] Token-taket lades ETTER fullført kall; USD-taket er kun per-kall (F10-analog: DELVIS BEKREFTET) - **Belegg:** `meter.charge_tokens` kalles etter `client.complete` returnerer (`loop.py:91`, `budget.py:43-51`) — kallet som krysser taket fullføres (og koster) før raise, som i MAF. USD: per-kall-tak finnes førsteklasses (`sdk_client.py:87`, `ClaudeAgentOptions.max_budget_usd` — bedre enn MAF), men ingen run-total USD-grense; totalen er kun indirekte bundet (antall kall × per-kall-tak, antall kall bundet av meteren). **Ærlighets-nyansen fra MAF-F10 er AVKREFTET her:** ingen docstring lover «short-circuits» — `budget.py:1-9` beskriver mekanikken presist. - **Fiks (lav prioritet):** pre-call-guard + eksplisitt run-total USD-tak → valgfri sesjon **C3.5** (uavhengig; mest verd før en eventuell ny live-kjøring). ### C-S1 [SUGGESTION] Checker-parse: `VERDICT: REJECT`-substring-presedens hvor som helst (F13-analog, spec-pinnet) - `_REJECT_PATTERN.search` over hele teksten (`loop.py:42,186-194`): en checker som SITERER formatet feiltolkes som reject. Spec-konformt (§3 Steg 4 pinner presedensen) — flagges som robusthet; evt. siste-linje-parse er en D-A-nyansering (allerede i MAF-planens D-A pkt. 5-nabolag). ### C-S2 [SUGGESTION] `max_rows`-cap håndheves etter full innlesing - `_read_csv` leser hele fila før cap-sjekk (`ingest.py:190-197`); `_read_sql` gjør `fetchall()` før sjekk (`ingest.py:244-247`). Fail-fast i UTFALL (aldri stille trunkering, §8-konformt), men en enorm lokal kilde leses helt inn i minnet først. Akseptabel MVP-grense for lokale kilder; noteres for en evt. streaming-variant. ### C-S3 [SUGGESTION] Døde/små ting - `promotion._link_from_index` er ikke-atomisk RMW — dokumentert MVP-grense (`promotion.py:92-95`), samme som MAF-F6s index-del; blir først reell ved concurrency (D-D), som D7 ikke har noen sti for i dag. - `model_map.json` har kun `anthropic`-profil (Bedrock/Vertex/Foundry er env-switch-konfig per design) — ingen placeholder-problem à la MAF-P6 her. ### C-P1 [MAJOR · plan-nivå · DELT — flagges til hovedrepoet] MAF-planens S3.1 (semantisk henting) kolliderer med frossen method-spec §3 Steg 1 - **Belegg:** method-spec §3 Steg 1 (frossen): «Ranking is **structural, never textual** — surface text MUST NOT contribute to similarity.» MAF-planens S3.1 bygger «HybridRanker = dagens strukturelle score + cosine over lagrede vektorer» gated KUN på D-C — men cosine over embeddings av rationale-/overflatetekst ER tekstlig similarity og bryter spec-en uten amendment. D7 håndhever regelen i dag (`experience.py:83-95` + load-bearing `test_surface_text_never_contributes`). - **Konsekvens:** D-C-beslutningen må enten (a) kobles til et D-A-punkt som amender ranking-regelen bevisst, eller (b) scope S3.1 til embeddings over strukturelle features. Gjelder BEGGE repo — dette er en delt beslutnings-forutsetning MAF-reviewen ikke fanget. --- ## 2. MAF-funnene — status i dette repoet | MAF-funn | Status her | Belegg / hvor | |---|---|---| | F1 (portefølje-læringssløyfe død) | **Avkreftet i MAF-formen** — ingen portefølje-sti finnes; folden ER wiret på eneste kjøresti (`run_s10.py:72-79`); ingen død docstring. Rest-analog: C-N2 (ingen kjørbar Steg-7-inngang) | §1 C-N2 | | F2 (nominal gater aldri) | **Bekreftet** (kjørt bevis, identiske moteksempel-tall) | §1 C-F2 | | F3 (ingen kostbaseline) | **Bekreftet** (kjørt bevis, 2.9 MNOK validert) | §1 C-F3 | | F4 (`/`-lenker skippes) | **Bekreftet** (`okf.py:112`; spec-tro; stille-tap-nyanse) | §1 C-F4 | | F5 (én-kandidat-seeding) | **Bekreftet med nyanse** — id-halvdelen ALLEREDE løst (verbatim `verdict_id` + load-bearing test); feature-halvdelen kjørt-bevist | §1 C-F5 | | F6 (concurrency/RMW) | **Ikke anvendbar** — ingen portefølje-/concurrent-sti; RMW-grensen dokumentert (`promotion.py:95`). Relevant først ved D-D-speiling | §1 C-S3 | | F7 (inbox-injeksjon) | **Bekreftet** (kjørt bevis) | §1 C-F7 | | F8 (metode-cap fail-open) | **Ikke anvendbar** — D7-validatoren har ingen metoderegler (kun §3-Steg-4-kjernen); golden krever dem ikke | `validator.py` (hele) | | F9 (prosent-mål/baseline) | **Ikke anvendbar** — ingen goals/ledger-lag i D7 | filkatalog | | F10 (budsjett post-charge) | **Delvis bekreftet** — post-charge ja; docstring ærlig; per-kall-USD-tak finnes (bedre enn MAF); run-total USD-tak mangler | §1 C-N4 | | F11 (privat-API i doubles) | **Avkreftet for testdobbelen** (egen protokoll); **rest-risiko på run-stien** (løs pin, ingen versjonsvakt) | §1 C-N3 | | F12 (CHANGELOG stale) | **Avkreftet** — `CHANGELOG.md:16` sier 265 == suiten | baseline | | F13 (småting) | **Delvis** — checker-parse-presedensen gjelder (C-S1); `_authored_texts`-dødkoden og retrieval-substring er MAF-spesifikke (D7-similarity er ren strukturell) | §1 C-S1 | | F14 (Magentic-begrunnelse) | **Ikke anvendbar** — ingen MAF/Magentic-referanser i D7 | grep | | P1/P2 (roadmap vs ingest-målbilde / MCP-wiring) | **Ikke anvendbar** — D7 har ingen Fase-2-roadmap; `extending.md` er ærlig om HTTP/MCP ubygd (peker-only) | `docs/extending.md:128-160` | | P4 (output-lag ubygget) | **I hovedsak avkreftet her** — `artifacts.py` persisterer proposal/result/provenance/usage + stop-artefakter deterministisk (`runs/s10/`). Rest: run_id-basert outbox + pending-sporing KUN hvis D7 skal speile Fase 5 (ikke planlagt) | `artifacts.py:83-152` | | P5 (verdict_dir i portefølje) | **Ikke anvendbar** (ingen `run_portfolio`) | filkatalog | | P6 (stubs: notify/MCP-demo/Azure-placeholders/main-paritet) | **Ikke anvendbar** i MAF-formen — ingen slike stubs her; CLI-paritets-resten dekkes av C-N2/C2.0 | §1 C-N2 | | U-1–U-4 (frossen-ramme-utfordringer) | Samme status som F4/F2+F3/P1/F5 over — D7 slutter seg til U-1/U-2/U-4; U-3 ikke anvendbar | §1 | ## 3. Utfordringer mot frosne beslutninger (KLART MERKET — kun operatøren kan endre) D7-siden SLUTTER SEG TIL MAF-reviewens U-1 (`/`-lenker), U-2 (validator-semantikk) og U-4 (seeding-regelen) med egne kjørte bevis (§1), og legger til: | # | Frossen ramme | Utfordring | Belegg | |---|---|---|---| | U-5 (ny) | method-spec §3 Steg 1 («structural, never textual») | Delt D-C-plan (semantisk henting) er uimplementerbar uten amendment eller re-scoping — koblingen D-C↔D-A mangler i den delte køen | §1 C-P1 | Ingen utfordring rettes mot: golden-suiten (reprodusert eksakt), maker-checker-gaten (detach-bevist), fail-closed promotering (detach-bevist), verdict-eksklusjonen (detach-bevist), SDK-isolasjonsvalget `setting_sources=[]` (detach-bevist; S10-post-mortem- begrunnelsen står), eller load-bearing-regelen (11/11 stikkprøver røde). ## 4. Verifiseringslogg | Påstand | Kilde/kommando | |---|---| | 265 passed / ruff clean / mypy clean (16 filer) | `uv run pytest -q` (1.60s); `uv run ruff check .`; `uv run ruff format --check .`; `uv run mypy src` (2026-07-10) | | HEAD == origin/main == open/main | `git rev-parse HEAD origin/main open/main` → alle `42f320b` | | 11 detach-eksperimenter røde | throwaway-kopi i scratchpad; driver-script med kirurgisk patch + `uv run pytest ` per søm + git-revert; kopi-isolasjon verifisert (`portfolio_optimiser_claude.__file__` peker i kopien); 11/11 RED, 0 anomalier | | F2-moteksempler validerer | `uv run python` mot live-modul (read-only): claim 100k>nominal 90k → ValidatedProposal p90=121 057.09; claim 55k>nominal 30k (band [1.8,2.2]) → ValidatedProposal p90=65 058.49 | | F3-moteksempel validerer | samme script: diktet linje 1e6×10 → claim 2.9 MNOK → ValidatedProposal (percentiler 3.0 MNOK) | | F5-feilnøkling | kjørt i kopien: to promoterte dommer (E01 vs E99) → begge store-records bærer bundle-features (ENERGI-TOTAL-EL/LED); distinkte ids (verbatim) | | F7-injeksjon | kjørt i kopien: `decision:"hva-som-helst"` + injeksjons-rationale → merged=1, begge når prompten via folden | | Method-spec-vakt mangler | `grep -rn "method-spec\|method_spec" tests/` → 0 treff; `test_ingest_spec_loadbearing.py` finnes (mønsteret) | | C2.7-antakelsen (alle bands omslutter unit_cost) | grep over tests/ + examples/ + shared/examples/: eneste ikke-tomme bands [0.8,1.2]∋1.0, [0.70,1.40]∋1.0, (0.70,1.50)∋1.0 — null brudd | | Ingen egress i src utenfor SDK-klient | `grep -rn "urllib\|socket\|requests\.\|http" src/…/*.py` → kun docstring-treff i ingest.py | | S10-artefakter urørt | kun lest; ingen re-kjøring (protokoll §4.2/§4.5) | | OKF-spec-ens `/`-anbefaling | IKKE re-verifisert her (nettverksregel denne sesjonen) — arvet fra MAF-review §6 (WebFetch 2026-07-09), merket som arvet premiss | | SDK-attributt-premisser («verifisert mot 0.2.110») | arvet fra kodens egne verifikasjonsnotater (`sdk_client.py:12-17`, CLAUDE.md) — ikke re-verifisert mot nyere SDK; nettopp derfor C-N3 krever versjonsvakt |