portfolio-optimiser/.claude/projects/2026-07-03-s11-sammenligning-rapport/plan.md

443 lines
34 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

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.

# S11 — Sammenligningen + rapport (program-leveransen)
> **Plan quality: B** (pending adversarial review) — APPROVE_WITH_NOTES pending
>
> Generated by trekplan v5.9.0 on 2026-07-03 — `plan_version: 1.7`
## Context
S11 er program-SLUTTLEVERANSEN (målbilde §1, briefens Intent): en rettferdig sammenligning av to
uavhengige implementasjoner av samme metode — MAF-stacken i dette repoet og Claude Agent
SDK-søskenet i `/Users/ktg/repos/portfolio-optimiser-claude` — på identisk delt kjerne, dokumentert
i én norsk forretningsrapport med verifiseringslogg. Troverdigheten hviler på at målestokken
(`docs/plan/2026-07-03-sammenligningsprotokoll.md`, S3) ble bundet FØR søskenrepoet fantes og FØR
ekstraksjonen (S4/S5) — PRESIST det protokoll-headeren selv påstår, og ikke mer: MAF-implementasjonen
forelå da protokollen ble skrevet, så pre-bindingen beskytter mot at CLAUDE-siden former målestokken
etter eget resultat; denne residual-asymmetrien ERKLÆRES i rapporten (Adversarial Pass 2, B1) — og på
at hver tallpåstand bærer sin egen reproduserende kommando/kilde. Rapporten skal være nøktern, uten
salgsspråk, og aldri påstå mer enn bevisene bærer (briefens NFR «Ærlighet»; method-spec §1).
Protokollen er normativ og fryst i økta (briefens Constraint 1). Alt kjøres offline — null
API-spend (Constraint 2, D6). Søskenrepoet er read-only: ingen tracked-fil endres der; transiente,
gitignorerte artefakter fra testkjøring (`.pytest_cache/`, `__pycache__/`) tillates, operasjonalisert
som identisk `git status --porcelain` før/etter (brief-review-funn a, avklart av risk-assessor).
## Architecture Diagram
```mermaid
graph TD
subgraph "Delt kjerne (pinned db86e15, tre-hash fc58b0b9)"
SHARED["shared/ — 13 filer<br/>golden.json + validator-input.json<br/>method-spec.md + OKF-bundle + persona"]
end
subgraph "MAF-repo (dette)"
SIM["portfolio_optimiser.simulation<br/>(offline to-runs-bevis, stdout)"]
MGOLD["tests/test_bygg_energi_mikro.py<br/>(M1: golden-frys)"]
MSUITE["uv run pytest / ruff / mypy (SC2)"]
end
subgraph "SDK-søsken (read-only)"
SGOLD["tests/test_bygg_energi_mikro.py<br/>(M1 + mutasjonskontroller)"]
SMIRROR["tests/test_step7/8_*_loadbearing.py<br/>(offline-speil = testsuiten)"]
S10["runs/s10/*.json<br/>(S10-artefakter = fasit, live)"]
end
SHARED --> SIM & MGOLD & SGOLD & SMIRROR
SIM & MGOLD & MSUITE --> RAPPORT["docs/rapport/2026-07-03-sammenligningsrapport.md<br/>(M1M4 + ordrett §3-erklæring + verifiseringslogg)"]
SGOLD & SMIRROR & S10 --> RAPPORT
```
## Codebase Analysis
- **Tech stack:** Python ≥3.10, `uv`, pytest/ruff/mypy begge repo. MAF-repo: `agent-framework-core`;
søsken: `claude-agent-sdk`. MAF-venv Python 3.12.13; søsken-venv 3.14.0 (skew OK: begge
validatorer bruker kun `random.Random(20260624).uniform(...)`, versjonsstabil sekvens; golden-tester
grønne på hver side — python-versjonene loggføres i rapporten).
- **Key patterns:** load-bearing tester (detach-bevis) begge repo; golden-suite som eneste fasit
(seed 20260624, MAF `validator.py:41`, søsken `validator.py:25`); dokumentkonvensjon
`YYYY-MM-DD-slug.md`, norsk forretningsprosa, verifiseringslogg-tabell (presedens
`docs/2026-06-26-fot-i-bakken.md`).
- **Relevant files (verifisert i utforskningen):**
- MAF: `src/portfolio_optimiser/simulation.py` (294 LOC, `main()` :249; skriver INGEN artefakter —
kun stdout + temp-katalog `po-sim-*`; exit 0 = løkke lukket), `budget.py` (`TokenMeter` :6677),
`run.py` (:317 `token_usage`), `validator.py`, `shared_root.py` (env `PORTFOLIO_SHARED_ROOT`),
`tests/test_bygg_energi_mikro.py::test_validator_output_matches_golden` (:8190).
- Søsken (read-only): `tests/test_step7_async_loop_loadbearing.py` + `tests/test_step8_promotion_loadbearing.py`
(= «D7 offline-speil»; INGEN kjørbar sim-modul finnes), `tests/test_bygg_energi_mikro.py`
(golden + 2 mutasjonskontroller MAF mangler), `runs/s10/{proposal,provenance,run_result,usage}.json`
(fasit, committet samlet i `0f8af04`), `src/portfolio_optimiser_claude/run_s10.py` (:4653
defaults = §4.3-konfigen; MÅ ALDRI kjøres i S11 — ville overskrive fasit + bruke API-penger).
- Protokoll/plan: `docs/plan/2026-07-03-sammenligningsprotokoll.md` (M1M4 :2427, §3-erklæring,
§4-regler), `docs/plan/2026-07-02-sesjonsplan.md` :183193 (S11-kriterier).
- **Reusable code:** ingen ny kode — S11 er kjøringer + dokument. Verbatim-sjekken gjenbruker
test-strategistens python-oneliner; pin-sjekken gjenbruker `git rev-parse HEAD:shared`.
- **External tech (researched):** ingen (0 research-topics, brief bekreftet).
- **Recent git activity:** begge repo rene og pushet. MAF tip `aec95eb` (kun ny plan-fil for
fremtidig ingest-lag — ortogonal til S11, verifisert `git show --stat`). Søsken tip `0f8af04`
(eneste commit som rører `runs/s10/`). Commons-pin: MAF squash `7916e10`
(«from 7d2b46c..db86e15»), søsken squash `97a9172` («content from commit db86e15»,
`git-subtree-split: db86e159148d7d8553a38516e174370cf913f889`).
## Implementation Plan
Ingen produksjonskode endres; «test first» erstattes av **Verify** med eksakte kommandoer
(mal-tillatt for prosjekter der steget ikke er kode). Evidence-filer legges i prosjektkatalogens
`evidence/`, som gjøres selv-ignorerende i Step 1 (`evidence/.gitignore` med innhold `*`
`.claude/projects/` er ellers IKKE gitignored, verifisert med `git check-ignore`); kommandoene i
verifiseringsloggen er den reproduserbare kilden for lesere. Alle capture-kommandoer i Steps 13
fanger stderr (`2>&1`) slik at feilmeldinger og EXIT-linjer havner i evidence-filene.
### Step 1: Preflight — pinned-ref-bevis og miljø-ground-truth
- **Files:** `.claude/projects/2026-07-03-s11-sammenligning-rapport/evidence/01-preflight.txt` (new file)
- **Changes:** Opprett først selv-ignorerende evidence-katalog:
`mkdir -p evidence && printf '*\n' > evidence/.gitignore` (gjør at `git status --porcelain`
forblir uendret av bevisfangsten). Fang deretter (alt med `2>&1`):
(1) `git -C <begge repo> status --porcelain` + `rev-parse HEAD` (rene trær, tips dokumentert);
(2) pin-identitet med `PIN: `-prefiks per linje:
`git -C /Users/ktg/repos/portfolio-optimiser rev-parse 'HEAD:shared' 'db86e15^{tree}' | sed 's/^/PIN: /'`
og samme i søskenet, PLUSS den TEMPORALE sjekken (Adversarial Pass 2, time-axis):
`git -C /Users/ktg/repos/portfolio-optimiser-claude rev-parse '0f8af04:shared' | sed 's/^/PIN: /'`
— beviser at shared/-treet ved S10-artefakt-committen er samme tre som dagens pin (kjernen endret
seg ikke mellom live-kjøringen og sammenligningen). Alle FEM PIN-linjer skal vise
`fc58b0b956e5f9871b29810f17284250e794abd5`;
(3) `diff -rq /Users/ktg/repos/portfolio-optimiser/shared /Users/ktg/repos/portfolio-optimiser-claude/shared`
(tom) + `find shared -type f | wc -l` (= 13); (4) `echo "PORTFOLIO_SHARED_ROOT=[$PORTFOLIO_SHARED_ROOT]"`
(skal være tom — runtime-identitet, risk-assessors residual); (5) python-versjoner:
`uv run --frozen python -V` i begge repo. (basert på kodebase-analyse)
- **Reuses:** `git rev-parse HEAD:shared`-sjekken (test-strategist, verifisert i utforskningen);
`shared_root.py`-sømmen dokumenterer env-varen.
- **Verify:** `grep -c '^PIN: fc58b0b956e5f9871b29810f17284250e794abd5$' evidence/01-preflight.txt` → expected: `5` (prefikset teller kun rev-parse-output, ikke annoteringer; diff-linjen tom)
- **On failure:** ulik tre-hash → escalate (pinned-ref-premisset brutt; re-pinning er eksplisitt Non-Goal — operatør må avgjøre). Satt `PORTFOLIO_SHARED_ROOT` → unset i sesjonens miljø, re-fang punkt (4), loggfør hendelsen i evidence; escalate kun hvis den ikke lar seg unsette
- **Checkpoint:** ingen commit (read-only bevissteg; evidence er selv-gitignored)
- **Manifest:**
```yaml
manifest:
expected_paths:
- .claude/projects/2026-07-03-s11-sammenligning-rapport/evidence/.gitignore
- .claude/projects/2026-07-03-s11-sammenligning-rapport/evidence/01-preflight.txt
min_file_count: 2
commit_message_pattern: ""
bash_syntax_check: []
forbidden_paths:
- shared/README.md
- docs/plan/2026-07-03-sammenligningsprotokoll.md
must_contain:
- path: .claude/projects/2026-07-03-s11-sammenligning-rapport/evidence/01-preflight.txt
pattern: "PIN: fc58b0b956e5f9871b29810f17284250e794abd5"
```
### Step 2: MAF-side kjøringer — gates (SC2) + offline-sim (SC3, M2/M3 offline)
- **Files:** `.claude/projects/2026-07-03-s11-sammenligning-rapport/evidence/02-maf-gates.txt` (new file), `.claude/projects/2026-07-03-s11-sammenligning-rapport/evidence/03-maf-sim.txt` (new file)
- **Changes:** I MAF-repoet, fang til 02 (hver kommando med `2>&1`): `uv run --frozen pytest`
(noter passed/skipped-tall og NAVNGI de 4 gatede live-skippene — «157 grønne» alene er upresist),
`uv run ruff check .`, `uv run ruff format --check .`, `uv run mypy src`. Deretter fang til 03:
`uv run --frozen python -m portfolio_optimiser.simulation 2>&1; echo "EXIT=$?"` — stdout-tracen ER
M2/M3-kilden på MAF-siden (RUN A/PROMOTE/RUN B-linjer, «LEARNING LOOP CLOSED», syntetiske
token-tellinger 8/reply, debatt kuttet av GroupChatOrchestrator ved max_rounds=3). Ferske tall,
aldri STATE-prosa (premiss-verifisering). (basert på kodebase-analyse)
- **Reuses:** `simulation.py` uendret; `--frozen` beskytter `uv.lock` (risk-assessor).
- **Verify:** `grep -E "LEARNING LOOP CLOSED|EXIT=0" evidence/03-maf-sim.txt | wc -l` → expected: `2`
- **On failure:** escalate — rød gate/åpen løkke på pinned ref er et rapport-nivå-funn operatøren må se før rapporten skrives
- **Checkpoint:** ingen commit (kjøringer + selv-gitignored evidence; `git status --porcelain` skal være uendret)
- **Manifest:**
```yaml
manifest:
expected_paths:
- .claude/projects/2026-07-03-s11-sammenligning-rapport/evidence/02-maf-gates.txt
- .claude/projects/2026-07-03-s11-sammenligning-rapport/evidence/03-maf-sim.txt
min_file_count: 2
commit_message_pattern: ""
bash_syntax_check: []
forbidden_paths:
- src/portfolio_optimiser/simulation.py
- uv.lock
must_contain:
- path: .claude/projects/2026-07-03-s11-sammenligning-rapport/evidence/03-maf-sim.txt
pattern: "LEARNING LOOP CLOSED"
```
### Step 3: Søsken-side gates (SC2/SC3) under read-only-protokoll
- **Files:** `.claude/projects/2026-07-03-s11-sammenligning-rapport/evidence/04-sibling-gates.txt` (new file)
- **Changes:** Fang (alt med `2>&1`): (1) status FØR med markør-prefiks:
`git -C /Users/ktg/repos/portfolio-optimiser-claude status --porcelain | sed 's/^/BEFORE:/'`;
(2) `PYTHONDONTWRITEBYTECODE=1 uv run --frozen --directory /Users/ktg/repos/portfolio-optimiser-claude pytest -p no:cacheprovider`
(= «D7 offline-speil» kjørt: to-runs-beviset `test_step7_async_loop_loadbearing.py` +
promoterings-beviset `test_step8_promotion_loadbearing.py` + golden `test_bygg_energi_mikro.py`
inngår — noter ferskt passed-tall, forvent ~187); (3) ruff + mypy med søskenets egne konfigurerte
kommandoer (`uv run --frozen --directory … ruff check .` og `… mypy src` — strictness styres av
dets pyproject, per dets CLAUDE.md «mypy --strict rene»); (4) status ETTER med markør-prefiks:
`git -C … status --porcelain | sed 's/^/AFTER:/'` — identisk med FØR (read-only-beviset, inn i
verifiseringsloggen). ALDRI kjør `src/portfolio_optimiser_claude/run_s10.py` (overskriver
fasit-artefaktene + ekte API-kost — farligste feilmodus). (basert på kodebase-analyse)
- **Reuses:** søskenets egen testsuite uendret; cache-fri invokasjon fra test-strategist.
- **Verify:** `bash -c 'diff <(grep "^BEFORE:" evidence/04-sibling-gates.txt | sed "s/^BEFORE://") <(grep "^AFTER:" evidence/04-sibling-gates.txt | sed "s/^AFTER://")'` → expected: tom diff, exit 0 (før/etter-status identisk; BSD-kompatibel — ingen `head -n -1`) og pytest-linjen viser `0 failed`
- **On failure:** retry — én gang med `uv sync --frozen` først (miljø-drift); deretter escalate hvis fortsatt rødt
- **Checkpoint:** ingen commit (ingen tracked filer endres — i noen av repoene)
- **Manifest:**
```yaml
manifest:
expected_paths:
- .claude/projects/2026-07-03-s11-sammenligning-rapport/evidence/04-sibling-gates.txt
min_file_count: 1
commit_message_pattern: ""
bash_syntax_check: []
forbidden_paths: []
must_contain:
- path: .claude/projects/2026-07-03-s11-sammenligning-rapport/evidence/04-sibling-gates.txt
pattern: "passed"
```
### Step 4: Fasit-ekstraksjon — S10-artefakter (M2/M3 live) + M4-målinger
- **Files:** `.claude/projects/2026-07-03-s11-sammenligning-rapport/evidence/05-s10-artefakter.txt` (new file), `.claude/projects/2026-07-03-s11-sammenligning-rapport/evidence/06-m4.txt` (new file)
- **Changes:** Til 05: `cat` av de fire `runs/s10/*.json` + `git -C <søsken> log --format="%H %cI %s" -- runs/s10/`
(skal vise KUN `0f8af04`). Tall som siteres i rapporten leses HERFRA (36 791/150 000 tokens,
2/12 runder, attempts 1, $0.127514, modell `claude-haiku-4-5-20251001`) — aldri fra STATE-prosa.
§4.3-konfigen = defaultene i `src/portfolio_optimiser_claude/run_s10.py` (:4653).
M1-hovedpåstanden (live-utfallet reproduserer goldens persentiler bit-for-bit) BEVISES med denne
kommandoen, som også gjengis i verifiseringsloggen:
```
python3 - <<'EOF'
import json, pathlib
g = json.loads(pathlib.Path("shared/examples/bygg-energi-mikro/golden.json").read_text())["validator"]
r = json.loads(pathlib.Path("/Users/ktg/repos/portfolio-optimiser-claude/runs/s10/run_result.json").read_text())["outcome"]
for k in ("p10", "p50", "p90"):
assert g[k] == r[k], (k, g[k], r[k])
print("OK: golden == run_result (bit-for-bit p10/p50/p90)")
EOF
```
Til 06 (M4-råstoff, kommando per tall — FERSKE tall, forhåndstall fra utforskningen
(~2 696 LOC/18 moduler vs ~1 696 LOC/15 moduler) er kun forventninger): LOC/moduler
`find src -name '*.py' -exec wc -l {} +` i begge repo; load-bearing-inventar
`ls tests/test_*loadbearing*.py` begge (+ søskenets `test_sdk_isolation.py`, mutasjonskontrollene
MAF mangler); method-spec §12-tabellen som spec-troskaps-sjekkliste. (basert på kodebase-analyse)
- **Reuses:** artefaktskjemaene kartlagt av dep-tracer (usage/run_result/provenance/proposal-felter).
- **Verify:** `grep -c '"cost_usd": 0.127514' evidence/05-s10-artefakter.txt` → expected: `1`; M1-kommandoen over → `OK: golden == run_result (bit-for-bit p10/p50/p90)`
- **On failure:** escalate — avvik mellom artefaktfil og forventet verdi betyr at fasit-premisset har driftet
- **Checkpoint:** ingen commit (read-only ekstraksjon)
- **Manifest:**
```yaml
manifest:
expected_paths:
- .claude/projects/2026-07-03-s11-sammenligning-rapport/evidence/05-s10-artefakter.txt
- .claude/projects/2026-07-03-s11-sammenligning-rapport/evidence/06-m4.txt
min_file_count: 2
commit_message_pattern: ""
bash_syntax_check: []
forbidden_paths: []
must_contain:
- path: .claude/projects/2026-07-03-s11-sammenligning-rapport/evidence/05-s10-artefakter.txt
pattern: "claude-haiku-4-5-20251001"
```
### Step 5: Skriv rapporten, kjør mekaniske sjekker, commit + push
- **Files:** `docs/rapport/2026-07-03-sammenligningsrapport.md` (new file — ny katalog `docs/rapport/`, konsistent med sjanger-per-underkatalog)
- **Changes:** Skriv rapporten på norsk etter konvensjons-hybriden: `> **Hva dette er:**`-blockquote →
`## Hovedfunn` tidlig → nummererte seksjoner → verifiseringslogg-tabell sist. Innhold, bundet av
protokollen OG Adversarial Pass 2-funnene:
(1) **Bakgrunn/metode** — protokollens temporale status PRESIST (bundet før S4/S5, ETTER at
MAF-implementasjonen forelå — residual-asymmetrien erklæres, B1); **interessekonflikt-erklæring**
(forfatter er Anthropic-fokusert AI-rådgiver; begge implementasjoner + rapport skrevet med Claude —
leverandøren av den ene komparanden; én setning, COI); pinned ref db86e15 med tre-hash-identiteten
fra Step 1 INKL. den temporale (S10-commit-treet = dagens pin), python-versjoner, dato.
(2) **M1 validator-samsvar** — RAMMES som EKVIVALENS-GATE, ikke differensierende metrikk (begge
validatorer er ren python; golden ble generert av spec §7.2-referanseprosedyren fra MAF-linjen —
generatoren NAVNGIS, så MAF-siden er selv-samsvar og kun Claude-siden er uavhengig reproduksjon).
Begge sider grønne mot golden på pinned ref (pytest-node-id-er som kilde); søskenets live
run_result reproduserer goldens persentiler bit-for-bit (Step 4-kommandoen som bevis); MAF-simens
tall er IKKE golden-tall (skriptet forslag uten assumptions → degenerert MC, p90=90000) og siteres
aldri som M1-kilde. Ekvivalens-påstanden SCOPES til det ene benchmark-bundelet (bygg-energi-mikro).
(3) **M2/M3 — offline-kategori (BEGGE sider):** skriptede tall presenteres som DESIGNPARAMETRE +
boolske håndhevelses-utfall (tak håndhevet ja/nei, refinement-sti utløst ja/nei) — ALDRI som målinger
(forfatter-valgte inputs, synthetic≠measured); MAF-sim-tracen (løkke lukket, forsøk avledet av
generation-prompt-telling) og søskenets offline-speil (testsuite — emitterer ikke runde-tall; sies
eksplisitt). **Live- og offline-tall i SEPARATE tabeller** (ikke én tabell med kategorikolonne).
(4) **S10 — live-kategori (KUN Claude-siden, merket):** **den feilede FØRSTE kjøringen rapporteres
FØRSTEKLASSES med lik prominens** (B2) — protokoll §4.5 lister «tak nådd» som innholdsutfall som
skal rapporteres, og unntaket dekker kun transportfeil; §4.5 SITERES, kategoriseringen
(harness-/konfig-feil, operatør-godkjent re-kjøring etter fiks) begrunnes eksplisitt i et **datert
protokoll-avviksnotat** i rapporten (avvik erklæres, absorberes aldri stille); **konfig-diffen
mellom feilet og vellykket kjøring publiseres** (kilde: `git -C <søsken> diff 0238507..7637c6f --stat`
+ run-lagets endringer — viser om suksess var utfalls-betinget tuning) samt den asymmetriske
artefakt-retensjonen (suksess: 4 committede JSON; feil: kun prosa) med fremadrettet
retensjonsregel. Verbatim-tall fra artefaktene; §4.3-konfig. Første kjørings tall finnes KUN i
søskenets STATE.md:4041 — raden merkes «disclosed-non-reproducible». Ingen statistiske ord
(«typisk», «gjennomsnittlig», «stabilt»); «eksistensbevis» glosses for forretningspublikum
(«én kjøring viser at det KAN skje, ikke at det VIL skje»).
(5) **M4 kvalitativ** — RAMMES som CASE-STUDIE-observasjoner, aldri framework-egenskaper
(Prechelt 2000: inter-programmerer-varians > inter-språk-varians; N=1 per rammeverk, samme
forfatter+AI; spec-en ble ekstrahert FRA MAF-implementasjonen → MAF-spec-troskap er
nær-tautologisk og søskenet har annenmanns-fordel — sies eksplisitt). LOC/moduler (ferske tall fra
Step 4-kommandoene; forventet ~2 696/18 vs ~1 696/15), spec-troskap mot §12, load-bearing-dekning
inkl. den ærlige asymmetrien (søskenet har mutasjonskontroller + attempts-felt MAF mangler;
«kjørbar sim-modul vs testsuite-bevis» som formforskjell), utvikleropplevelse som
ÉN-RATER-observasjon med eksplisitt disclaimer (ingen rubrikk-pretensjon).
(6) **Liveness-asymmetri-erklæringen (§3) ORDRETT** som blockquote, PLUSS klarspråk-versjon i
Hovedfunn (prominens, ikke bare tilstedeværelse).
(7) **Begrensninger (threats to validity) — OBLIGATORISK seksjon:** N=1-implementasjon per
rammeverk, ett benchmark-bundle, forfatter-COI, residual-asymmetrien fra B1, én live-kjøring;
huser også liveness-erklæringens kontekst.
(8) **Konklusjon** — nøktern; skriptede stand-ins merket som stand-ins overalt (method-spec §1).
(9) **Verifiseringslogg med TRE-KLASSE-taksonomi** per rad: `reproduce` (kommando kan kjøres på
nytt) / `recompute-from-artifact` (avledes av committede artefakter — live-tallene) /
`disclosed-non-reproducible` (kun den feilede kjøringens STATE-prosa-rad). Rader: tre-hash x5,
begge suiter (ferske tall m/ navngitte skips), sim-exit, S10-artefaktstier + single-commit-beviset,
konfig-diff-kommandoen, LOC-kommandoer, verbatim-sjekken, read-only-beviset (status før/etter).
Etter rapporten: **korriger dette repoets STATE.md** (briefens Constraint 6 / Assumption 3):
overskriv med S11-utfallet (NESTE → S12 [GATED]), fjern ⛔ PUSH-FRYS-linjen, pek på rapporten.
STATE.md er gitignored (local-only) — ingen commit av den.
Mekaniske sjekker etter skriving: (a) verbatim-sjekken, KOMMANDOEN ER (whitespace-normalisert
sammenligning; gjengis også i verifiseringsloggen):
```
python3 - <<'EOF'
import re, pathlib
proto = pathlib.Path("docs/plan/2026-07-03-sammenligningsprotokoll.md").read_text()
block = re.search(r"> \*\*Liveness-asymmetri-erklæring:\*\*.*?(?=\n\n## )", proto, re.S).group(0)
decl = "\n".join(l.lstrip("> ").rstrip() for l in block.splitlines())
report = pathlib.Path("docs/rapport/2026-07-03-sammenligningsrapport.md").read_text()
assert decl.replace("\n", " ") in " ".join(report.split()), "asymmetry declaration NOT verbatim"
print("OK: declaration verbatim")
EOF
```
(b) forbudt-ord-grep i ESCAPED form — `grep -inE 'typis[k]|gjennomsnittli[g]|stabil[t]' docs/rapport/2026-07-03-sammenligningsrapport.md`
→ 0 treff; loggens egen rad gjengir kommandoen i samme escaped form slik at den aldri matcher seg
selv; (c) manuell tabell-audit: ingen rad blander offline- og live-kategori. (basert på
kodebase-analyse + protokollens §2§5)
- **Reuses:** presedens-strukturen fra `docs/2026-06-26-fot-i-bakken.md`; §3-teksten kopieres fra protokollen.
- **Verify:** verbatim-kommandoen over → expected: `OK: declaration verbatim`; escaped grep → expected: exit 1 (0 treff); `grep -c "S11" STATE.md` → ≥ 1
- **On failure:** retry — rett rapporten til sjekkene er grønne (dokumentfeil er alltid rettbar i økta); escalate kun hvis en KILDE viser seg å motsi en annen
- **Checkpoint:** `git commit -m "docs(rapport): S11 — sammenligningsrapport begge stacker på pinned db86e15"`; deretter push KUN hvis Assumption 3 står (frys-oppheving operatør-bekreftet i økta — `git push origin main`); hvis operatøren i mellomtiden har gjeninnført frys: parker push, informer
- **Manifest:**
```yaml
manifest:
expected_paths:
- docs/rapport/2026-07-03-sammenligningsrapport.md
- STATE.md
min_file_count: 2
commit_message_pattern: "^docs\\(rapport\\): S11 — "
bash_syntax_check: []
forbidden_paths:
- docs/plan/2026-07-03-sammenligningsprotokoll.md
- docs/plan/2026-07-02-sesjonsplan.md
- shared/method-spec.md
must_contain:
- path: docs/rapport/2026-07-03-sammenligningsrapport.md
pattern: "Liveness-asymmetri-erklæring"
- path: STATE.md
pattern: "S11"
```
## Alternatives Considered
| Approach | Pros | Cons | Why rejected |
|----------|------|------|--------------|
| Permanent load-bearing test som vokter rapportens §3-sitat | Automatisk drift-vern | Rapporten er terminal artefakt ingenting konsumerer — ingen søm detacher; bryter 90 %-prinsippet | One-off verbatim-kommando i verifiseringsloggen dekker behovet (test-strategist) |
| Frisk S10-re-kjøring for «bedre» live-tall | Ferskere data | Forbudt: §4.2 (artefakter er fasit) + §4.5 (re-rull av utfall) + D6 (ny spend krever godkjenning) + overskriver committet fasit | Protokollbrudd; Non-Goal (a) i briefen |
| Rapport i `docs/plan/` i stedet for ny `docs/rapport/` | Ingen ny katalog | Blander leveranse med planverk; sjanger-per-underkatalog er etablert konvensjon | Brief/Preference sier `docs/rapport/`; konvensjonsskann støtter |
| Commit-melding-grep som SC2-ref-sjekk (briefens opprinnelige formulering) | Enkelt | Beviser omtale, ikke innhold — squash-meldingene er ulike; en lokal shared/-endring ville passert | Erstattet av tre-hash-identitet `fc58b0b9…` x4 (brief-review-funn b, verifisert av to agenter) |
## Test Strategy
- **Framework:** pytest (begge repo). Ingen nye testfiler i S11 — leveransen er et dokument;
mekanisk verifisering skjer via kommandoer som selv føres i verifiseringsloggen.
- **Existing patterns:** golden-frys (`test_validator_output_matches_golden`), load-bearing
detach-bevis (`test_*loadbearing*.py`), env-gatede live-skips (de 4 i MAF-repoet — navngis i loggen).
- **New tests in this plan:** 0 (bevisst — se Alternatives rad 1).
- **Mekanisk rapport-verifisering:** verbatim-sjekk (python, whitespace-normalisert), forbudt-ord-grep,
kategori-tabell-audit (separate tabeller), read-only-bevis (status før/etter), tre-hash-identitet x5.
## Risks and Mitigations
| Priority | Risk | Location | Impact | Mitigation |
|----------|------|----------|--------|------------|
| Critical | `run_s10.py` kjøres «for å speile» → overskriver committet fasit + ekte API-kost | søsken `src/portfolio_optimiser_claude/run_s10.py` (default `--out runs/s10`) | Fasit ødelagt; §4.2/§4.5/D6 brutt | Step 3/4 leser KUN filer; forbudet står i Step 3-prosaen; API-nøkkel i miljøet gjør kallet reelt mulig — aldri utfør |
| High | `uv run` re-låser `uv.lock` (tracked) i et av repoene | begge repo | Read-only brutt i søskenet; støy-commit her | `--frozen` på ALLE `uv run`-kall (Step 13); status-før/etter-bevis |
| High | Kryss-kategori-lekkasje: S10-live-tall havner i samme tabellrad som MAF-offline-tall | rapportens M2/M3-seksjoner | Protokoll §2-brudd — rapportens gyldighet | To adskilte seksjoner + kategori-kolonne; eksplisitt audit i Step 5-sjekkene |
| High | §4.5-kategorisering av S10-re-kjøringen fremstilles glatt («transportfeil») | rapportens S10-seksjon | Ærlighetsbrudd; protokollens ordlyd dekker ikke konfig-feil | Loggfør BEGGE kjøringer med kost/exit; drøft avviket eksplisitt (arch-mapper/risk-assessor) |
| Medium | `PORTFOLIO_SHARED_ROOT` satt i miljøet re-peker MAF-kjernen utenom pinningen | `shared_root.py` | «Identisk kjerne» usann runtime | Step 1 sjekker at varen er tom og loggfører det |
| Medium | Tall siteres fra STATE-prosa i stedet for ferske kjøringer/artefakter | rapporten | Premiss-verifiseringsbrudd; STATE var alt stale én gang i dag | Steps 24 fanger alt ferskt; rapporten siterer kun evidence/kommandoer |
| Low | MC-determinisme på tvers av python 3.12/3.14 | begge validatorer | Golden-avvik | Kun `Random.uniform` (versjonsstabil); golden-testene ER sjekken; versjoner loggføres |
| Low | Mid-sesjon-commits fra andre sesjoner (aec95eb-mønsteret) | dette repoet | Plan-premisser drifter | Step 1 re-dokumenterer tips; commit i Step 5 skjer fra verifisert tilstand |
## Assumptions
| # | Assumption | Why unverifiable | Impact if wrong |
|---|-----------|-----------------|-----------------|
| 1 | Operatørens S10-re-kjøringsgodkjenning (dokumentert i søskenets STATE-logg) aksepteres som §4.5-loggføringsgrunnlag | Beslutningen ligger i STATE-prosa, ikke i en committet protokolltekst | Rapportens §4.5-drøfting må skjerpes/eskaleres til operatør |
| 2 | Søskenets pyproject kjører mypy i strict-modus via konfig (STATE/CLAUDE.md sier «--strict rene») | Ikke lest ut av pyproject i utforskningen | Step 3 bruker uansett repoets egne konfigurerte kommandoer; loggen siterer faktisk invokasjon |
| 3 | Push-frys er opphevet (operatør-bekreftet i økta; sibling pushet 10:57) | Muntlig/chat-bekreftelse, ikke skriftlig direktiv-endring i STATE ennå | Step 5 push parkeres; commit står lokalt; STATE-korrigering uansett ved sesjonslutt |
## Verification
*Per-step manifests over; dette er end-to-end-sjekkene (= briefens SC1SC5).*
- [ ] SC1: `python3`-verbatim-sjekken (Step 5) → `OK: declaration verbatim`
- [ ] SC2: `uv run --frozen pytest` exit 0 i BEGGE repo (ferske tall, navngitte skips) + ruff + mypy exit 0 + tre-hash `fc58b0b9…` x5 identisk (inkl. temporal S10-commit-sjekk)
- [ ] SC3: `evidence/03-maf-sim.txt` inneholder `LEARNING LOOP CLOSED` + `EXIT=0`; søsken-suiten (inkl. step7/step8-loadbearing) grønn i `evidence/04-sibling-gates.txt`; alle M1M3-tall i rapporten sporer til evidence/artefakter
- [ ] SC4: hver tallpåstand i rapporten har rad i verifiseringsloggen; ALLE M1M3-rader kjøres på nytt → samme tall (briefens ordlyd; det eneste erklærte unntaket er den feilede S10-kjøringens STATE-prosa-rad, merket ikke-reproduserbar)
- [ ] SC5: `grep -inE 'typisk|gjennomsnittlig|stabilt' docs/rapport/…` → 0 treff; tabell-audit: ingen rad blander kategori; ingen live-påstand om MAF-siden
## Estimated Scope
- **Files to modify:** 0
- **Files to create:** 1 tracked (rapporten) + 6 untracked evidence-filer
- **Complexity:** medium (ingen kode, men høy presisjonsbyrde: hver setning i rapporten er en verifiserbar påstand)
## Plan Quality Score
| Dimension | Weight | Score | Notes |
|-----------|--------|-------|-------|
| Structural integrity | 0.15 | 95 | Lineær avhengighet 1→5; ingen sirkularitet |
| Step quality | 0.20 | 90 | Eksakte kommandoer; Verify per steg; read-only operasjonalisert |
| Coverage completeness | 0.20 | 95 | Alle SC1SC5 + begge brief-review-funn adressert |
| Specification quality | 0.15 | 90 | Rapportinnhold seksjon-for-seksjon; ingen «etter behov» |
| Risk & pre-mortem | 0.15 | 95 | 8 risikoer m/ mitigering; farligste (run_s10) eksplisitt |
| Headless readiness | 0.10 | 80 | Steps 14 har ingen commit (bevisst — read-only); manifester kompenserer |
| Manifest quality | 0.05 | 85 | must_contain på nøkkelverdier; tomme commit-patterns på read-only-steg |
| **Weighted total** | **1.00** | **91** | **Grade: A** |
**Adversarial review:**
- **Plan critic:** 1 blocker, 5 major, 6 minor — alle adressert i revisjon 1 (se Revisions)
- **Scope guardian:** ALIGNED — 0 creep, 2 minor gaps + 1 sti-feil, alle adressert i revisjon 1
## Revisions
| # | Finding | Severity | Resolution |
|---|---------|----------|------------|
| 1 | Step 3 Verify brukte `head -n -1` (ulovlig i BSD-head) og udefinerte markørlinjer | blocker | BEFORE:/AFTER:-prefiks definert i Changes; Verify omskrevet til BSD-kompatibel grep/sed-diff |
| 2 | evidence/ ikke gitignored → status-støy + commit-sweep-risiko | major | Step 1 oppretter selv-ignorerende `evidence/.gitignore` (`*`); manifest håndhever |
| 3 | Feilet S10-kjørings tall (162 250/exit 3/$0.331506) hadde ingen tillatt kilde | major | Eksplisitt kilde-unntak i Step 5: raden merkes «ikke-reproduserbar kilde: STATE-prosa» ærlig |
| 4 | SC1-verbatim-sjekkens kommando sto ikke i planen | major | Full python-kommando innfelt i Step 5 (whitespace-normalisert semantikk definert) |
| 5 | Forbudt-ord-grep ville matche sin egen logg-rad | major | Escaped form `typis[k]\|gjennomsnittli[g]\|stabil[t]` foreskrevet i både sjekk og logg |
| 6 | M1-bit-for-bit-påstanden manglet beviskommando | major | Python-sammenligning golden↔run_result innfelt i Step 4 + Verify |
| 7 | `ls shared/ -R \| wc` teller feil (28 linjer, ikke 13 filer) | minor | Byttet til `find shared -type f \| wc -l` |
| 8 | `run_s10.py`-sti feil (repo-rot vs `src/portfolio_optimiser_claude/`) | minor | Rettet i Codebase Analysis, Step 3/4 og risikotabellen (reist av begge reviewere) |
| 9 | Stalt søsken-modultall (16; ground truth 15) | minor | Rettet til ~15 og alle forhåndstall degradert til «forventninger» — kommandoen er kilden |
| 10 | Step 1-grep skjør + manglende On-failure for satt env-var | minor | PIN:-prefiks ankret grep; unset/re-fang/escalate-sti lagt til |
| 11 | `2>&1` uspesifisert i captures | minor | Foreskrevet globalt i plan-preamble + eksplisitt i Steps 13 |
| 12 | Push-betingelsen bodde kun i Assumptions | minor | Flyttet inn i Step 5-checkpointen (push kun hvis A3 står; ellers parker + informer) |
| 13 | SC4-stikkprøve (3 rader) svakere enn briefens «hver rad» | minor | Verification SC4 skjerpet til ALLE M1M3-rader; STATE-prosa-unntaket erklært |
| 14 | STATE.md-korrigering manglet som steg (falt bort headless) | minor | Lagt i Step 5 Changes + manifest (`STATE.md` must_contain «S11») |
## Adversarial Pass 2 (v5.1.1 high-effort — gemini-bridge feilet; contrarian-researcher substitutt)
**Prosessnotat (ærlighet):** Gemini Deep Research-jobben ble startet og fullførte server-side, men
resultatet var uhentbart pga. to uavhengige verktøyfeil i gemini-MCP-serveren
(`get_research_result`: `'Interaction' object has no attribute 'outputs'`, to forsøk;
`research_followup`: 404 på pensjonert modell). Passet ble derfor gjennomført av
`contrarian-researcher` (uavhengig web-grunnlagt metodestress-test — annet perspektiv enn
plan-critic, samme hensikt som gemini-passet). Funnene under er innarbeidet i Context og Step 5.
| # | Finding | Severity | Resolution |
|---|---------|----------|------------|
| B1 | «Definert før begge stacker» er falsifiserbar — protokollen predaterer kun S4/S5; MAF-impl forelå | blocker (restatement) | Context + Step 5(1): presis temporal påstand + residual-asymmetri erklært |
| B2 | Vellykket live-kjøring ser ut til å bryte §4.5 («tak nådd» = innholdsutfall; unntak kun transportfeil); klassifiseringen var post-hoc | blocker (restatement) | Step 5(4): feilet kjøring rapporteres førsteklasses, §4.5 siteres, datert avviksnotat |
| M1g | M1 er asymmetrisk/sirkulær (golden generert fra MAF-linjen; begge validatorer framework-frie) — ekvivalens-gate, ikke komparativ metrikk | major | Step 5(2): M1 omrammet; generator navngis; scope til bundelet |
| MPr | N=1 per rammeverk, samme forfatter+AI (Prechelt 2000); spec ekstrahert fra MAF → M4 unattributabel til rammeverk | major | Step 5(5): M4 = case-studie-observasjoner, aldri framework-egenskaper |
| MCOI | Udeklarert interessekonflikt (Anthropic-fokusert forfatter; rapport skrevet med Claude) | major | Step 5(1): COI-erklæring, én setning |
| MCfg | Konfig-diff feilet↔vellykket kjøring + asymmetrisk artefakt-retensjon må publiseres | major | Step 5(4): diff-kommando + retensjonsregel |
| MTid | §1 krever re-pin+re-kjør ved kjerneendring, men Claude-siden kan ikke re-kjøres — temporal identitet må BEVISES | major | Step 1: femte PIN-linje `rev-parse '0f8af04:shared'` (verifisert = fc58b0b9…) |
| MSyn | Skriptede M2/M3-tall er forfatter-valgte inputs, ikke målinger | major | Step 5(3): designparametre + boolske håndhevelses-utfall; separate tabeller |
| MVer | Verifiseringsloggens «reproduserende kommando»-regel selv-bryter for live-tall | major | Step 5(9): tre-klasse-taksonomi (reproduce / recompute-from-artifact / disclosed-non-reproducible) |
| min15 | Ett bundle-scope · Begrensninger-seksjon obligatorisk · erklæringens prominens (Hovedfunn) · «eksistensbevis» glosses · DX én-rater-disclaimer | minor | Step 5(2)(5)(6)(7): alle innarbeidet |