portfolio-optimiser/docs/2026-08-25-syretest-vei-ab.md
Kjell Tore Guttormsen a1f8522bdf feat(explore): katalogkallet koster O(baser), ikke O(korpus) (ORDRE 20260825T213645Z)
list_bundles returnerte hele rot-indeksens body for HVER konfigurert base samtidig,
pluss ett JSON-objekt per ufulgt kryss-lenke. Begge vokser med korpuset, saa prisen
paa aa finne ut HVILKE baser som finnes ble satt av hvor mye de INNEHOLDER - progressiv
disclosure snudd paa hodet.

Maalt med o200k_base, instrumentet foerst validert mot commons' egne fasittall:
  tre flate Vegnormal-baser   112 116 -> 362 tokens   (-99,7 %)
  171 grenbaser               124 942 -> 21 448       (-82,8 %)
Grenformen (vegnormal-okf 8145c23) lukket bundle-siden og gjorde katalogsiden verre,
noeyaktig som det repoet forutsa.

Et premiss ble felt FOER noe ble bygget paa det: "indeksbodyen forteller hva basen
handler om" er usant for maskin-importerte baser - grenbasenes index.md er en ren
lenkeliste uten frontmatter og prosa, saa feltet var dyrt OG innholdsloest der.

Fast vindu (200 tegn), aldri en andel av basen. Avkorting annonseres som FELT
(index_truncated), og en base som passer blir ikke merket avkortet. En ufulgt lenke
overlever som ANTALL; per-lenke-detaljen blir liggende der den er handlingsbar.
Hele indeksen er fortsatt ett read_file(id, "index.md") unna.

Taket (500 tegn/base) bor i TESTEN, ikke i explore.py.

Load-bearing MAALT: tests/test_catalogue_cost_loadbearing.py, 7 armer, ni mutasjoner
alle roede mot HELE suiten + groenn kontroll 1066/5 og golden demo-transcript.stdout
byte-uendret (ea8c534773acdbe41ae68f2c55724d69aaf8be4f).

Ogsaa: MINOR-1 i syretest-rapporten rettet - flatheten er Doer C sin
(llm-ingestion-okf importer.py, §6-index-blokka), ikke vegnormals emitterform.
Verifisert mot kilden, ikke mot meldingen.

Maaling: docs/2026-08-26-katalogkostnaden.md
2026-08-26 14:45:16 +02:00

283 lines
18 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.

# Syretesten vei A/B — de tre Vegnormal-basene gjennom portfolio-optimiser
**Ordre:** `20260825T111038Z-1174613178-from-.claude` (programplanens spor 3, gap G12).
**Dato:** 2026-08-25 (økt 60). **Mandat: MÅL, IKKE BYGG.** Ingen fil under `src/` er endret;
`uv run pytest -q`**1021 passed / 5 skipped (166 s)**, identisk med tallet før økten.
**Eksponerings-grense (ordrens harde krav, holdt):** dette repoet pusher til `open/`. Rapporten
bærer derfor kun **tall, stier, kommandoer og egne observasjoner**. Ingen bundle-fil er kopiert,
og ikke én linje kravtekst fra et konsept er lest inn eller gjengitt — alle konsept-tall under er
lengdemålinger, ikke innhold.
**Stopp-betingelsen var oppfylt:** multi-base-ordren `20260825T080753Z-103813595` lå i
`orders/archive/` (commit `18af86e` + `785261f`) da økten startet. Multi-base-formen er lest slik
den **faktisk landet** (`run.py`, `explore.py`, `mandate.py`, README), ikke slik planens § C.7
omtalte den.
---
## 1. Sammendrag — én setning per målepunkt
| # | Punkt | Status | Kjernetall |
|---|---|---|---|
| 1 | Katalogen (`index_summary`, konsepter, cost-baseline, verdicts) | **MÅLT** | 446 / 1017 / 270 konsepter; **0 av 3** har `cost-baseline.json`; **0 av 3** har `validator-input.json`; **0** `type: verdict`-filer i alle tre |
| 2 | Kontekstkostnad (`bundle_context(navigate_bundle(...))`) | **MÅLT** | 93 422 / 250 785 / 85 937 o200k_base-tokens — **sum 430 144** mot 3861/12595/10406 for de tre eksempelbundlene (instrumentet reproduserte commons' tall eksakt) |
| 3 | Dry-run med tre baser i multi-base-formen | **MÅLT — og formen finnes ikke fra CLI-en** | Fire `--bundle-dir` gir **exit 0** og en kjøring mot ÉN base (siste vinner, stille); bibliotekdøra `run_mandate_across_bundles` **ruter korrekt** men feiler i dispatch: `FileNotFoundError @ okf.py:433` |
| 4 | Offline-simuleringen med samme oppsett | **MÅLT** | Golden `demo-transcript.stdout` **BYTE-UENDRET** (`ea8c534773acdbe41ae68f2c55724d69aaf8be4f`), stderr 4 linjer; demoen **kan ikke** peke på en Vegnormal-base (samme `okf.py:433`) |
| 5 | `--explore` med full seks-felts `--explore-config` | **MÅLT — delvis vakuøst, som ordren forutså** | Sløyfa **fullfører** offline mot tre baser og returnerer et **rutet** mandat (`bundle_id='B-n100-…'`, `stop=None`); men **0 verktøykall** og **0 `quick_validate`** — navigatoren åpnet aldri en base. Fra CLI-en er utforskningen **ikke kjørbar offline i det hele tatt** (`KeyError: 'navigator'`) |
| 6 | G14: navigasjon inn i nestede `index.md` | **IKKE PRØVBAR HER — nevner oppgitt** | **0 nestede `index.md` av 1733 konsepter** i alle tre basene (0 underkataloger); kjent-positiv kontroll `nav-golden-hierarchy/bundle` finner 2 nestede index og 2 dypt-nådde kontekstfiler, så instrumentet **kan** se dem |
---
## 2. Tabellen (punkt 1 + 2)
Kommando bak hver rad: `okf.navigate_bundle(d)``okf.bundle_context(bundle)`, med
`tiktoken.get_encoding("o200k_base")` (kjørt via `uv run --with tiktoken`; `tiktoken` er **ikke**
lagt til som prosjekt-avhengighet).
| Base | Konsepter | Kontekstfiler | `index_summary` (tegn / tokens) | `bundle_context` tegn | bytes | **o200k_base-tokens** | verdicts | cost-baseline | validator-input | skipped links |
|---|---:|---:|---|---:|---:|---:|---:|:--:|:--:|---:|
| `B-n100-2023-uten-sources-importert` | 446 | 446 | 51 214 / 28 289 | 221 916 | 226 430 | **93 422** | 0 | nei | **nei** | 0 |
| `B-n200-2024-uten-sources-importert` | 1017 | 1017 | 116 879 / 64 764 | 616 179 | 626 034 | **250 785** | 0 | nei | **nei** | 0 |
| `B-n500-2024-uten-sources-importert` | 270 | 270 | 30 974 / 17 197 | 240 714 | 245 251 | **85 937** | 0 | nei | **nei** | 0 |
| **Sum, tre baser** | **1733** | **1733** | 199 067 / 110 250 | 1 078 809 | 1 097 715 | **430 144** | 0 | — | — | 0 |
| *kontroll:* `veglys-fv-soer` | 6 | 5 | 3 646 / — | 32 201 | 32 884 | **10 406** | 1 | ja | ja | 1 |
| *kontroll:* `tunnel-hauglia` | 6 | 5 | 4 763 / — | 39 583 | 40 475 | **12 595** | 1 | ja | ja | 1 |
| *kontroll:* `bygg-energi-mikro` | 5 | 4 | 1 884 / — | 12 005 | 12 270 | **3 861** | 1 | nei | ja | 1 |
**Instrumentet er validert mot kjent fasit** (Verifiseringsloven ansikt 4): de tre
kontrollradene reproduserer commons' egne tall — 3861 / 12 595 / 10 406 — eksakt. Uten den
kontrollen ville Vegnormal-tallene vært en måling ingen visste kunne treffe.
**Ordrens tall bekreftet mot ground truth** før noe ble bygget på dem: `447 / 1018 / 271` `.md`-filer
på disk = `446 / 1017 / 270` konsepter + `index.md` i hver. Hver `index.md` har nøyaktig like mange
lenker som det er konsepter (446 / 1017 / 270), alle unike, alle fulgt — `skipped = 0`.
**Det manageren faktisk ser.** Ett `list_bundles()`-kall (`explore.py:419-438`) returnerer hele
`index_summary` for **alle** baser samtidig:
| | tegn | bytes | **o200k_base-tokens** |
|---|---:|---:|---:|
| `list_bundles()` over de tre basene | 201 196 | 201 196 | **112 116** |
Dette er ett verktøykall, og det er katalogverktøyets **eneste** form.
### Hva tallene sier om `.claude`s § 9.1-analyse
`.claude` sin `docs/okf-bundle-prosessen.md § 9.1` («en fil uten lenke finnes ikke; alt som lenkes
leses helt») er **bekreftet mot et ekte korpus, og den er kostbar her**: alle 1733 konsepter er
lenket fra rot-`index.md`, `skipped = 0`, og «leses helt» betyr 430 144 tokens for de tre basene.
Progressiv disclosure gir ingen lettelse på denne bundle-formen, fordi importformen legger *alt*
på ett nivå — se funn **MINOR-1**.
---
## 3. Funn
### BLOCKER-1 — kontekstkostnaden gjør en live utforskning mot disse basene ugjennomførbar som de står
`list_bundles()` = **112 116 tokens** i ett kall; `read_bundle("B-n200-…")` = **250 785 tokens**.
`ExplorationContract.max_tokens` (`explore.py:70`) er ledgeren `BudgetMiddleware` håndhever, og et
enkelt katalogkall bruker mer enn et normalt tak. En 128k-modell kan ikke ta N200 i det hele tatt.
**Dette er ikke en defekt i rammeverket** — det er korpusets form møtt av § 9.1-kontrakten. Men det
er den harde grensen for vei A/B live, og den var ikke målt før i dag.
**Fil:linje:** `src/portfolio_optimiser/explore.py:419-438` (`list_bundles`), `:444-445` (`read_bundle`).
> **Oppdatert 2026-08-26 (økt 65, ordre `20260825T213645Z-9019120455`):** katalog-halvdelen er
> **lukket**. `list_bundles()` over de samme tre basene koster nå **362 tokens** (fra 112 116), og
> over alle 171 grenbaser **21 448** (fra 124 942). `read_bundle`-halvdelen ble lukket på korpussiden
> av `vegnormal-okf` `8145c23` (grener som egne baser). Måling og gate:
> [docs/2026-08-26-katalogkostnaden.md](2026-08-26-katalogkostnaden.md). Setningen over står som
> den ble målt 25.08 — den er historikk, ikke en gjeldende tilstand.
### MAJOR-1 — gjentatt `--bundle-dir` forkastes STILLE; kjøringen ser ut som multi-base og er det ikke
Ordrens pkt. 3 forutsatte at CLI-en tar tre baser. Målt:
```
uv run python -m portfolio_optimiser.run VEGLYS-FV-SOER \
--docs-dir shared/examples/veglys-fv-soer \
--bundle-dir <N100> --bundle-dir <N200> --bundle-dir <N500> \
--bundle-dir shared/examples/veglys-fv-soer --live-dry-run
→ EXIT 0, "VEGLYS-FV-SOER: LIVE-DRY-RUN OK (…)"
```
**Exit 0.** De tre Vegnormal-basene ble droppet uten ett ord. Bytter man rekkefølgen slik at en
Vegnormal-base står sist, feiler samme kommando i stedet (`live-dry-run refused: IR projection not
found in bundle: 'validator-input.json'`) — altså **siste `--bundle-dir` vinner**, som er argparse
sin default når `action="append"` mangler.
Dette er repoets egen defektklasse, anvendt på operatørflaten: hosting-whitelisten nekter ukjente
felt **ved navn** nettopp fordi stille dropping er uleselig utenfra, og multi-base-invarianten
avviser en andre `bundle_dir``run_project`-signaturen fordi den ville tvunget «et stille
velg-en». Her *er* det et stille velg-en — bare i argv i stedet for i signaturen.
**Fil:linje:** `src/portfolio_optimiser/run.py:1581-1583` (`add_argument("--bundle-dir", default=None…)`,
ingen `action="append"`), konsumert `run.py:2070` (`bundle_dirs=(args.bundle_dir,)`) og `run.py:607-610`.
**Minste ærlige rettelse (ikke bygget — ordren er MÅL, IKKE BYGG):** nekt et gjentatt `--bundle-dir`
ved navn, på linje med de åtte eksisterende utforskningsnektene. STATE fører allerede
«repeterbart `--bundle-dir`» som en **åpen operatørbeslutning** fra økt 58; denne målingen sier at
inntil den er tatt, er *stillheten* selv problemet — ikke fraværet av funksjonen.
### MAJOR-2 — `--explore --scripted-replies` krasjer med rå traceback: `KeyError: 'navigator'`
Den ene offline-døra CLI-en har til `--explore` er ubrukelig. `_SCRIPTED_ROLES = ("proposer",
"checker")` er debattens to roller; utforskningen trenger i tillegg `navigator`, `hypothesiser` og
`manager`. `_load_scripted_replies` er eksplisitt fail-fast **for de to den kjenner** («a missing
role would otherwise surface as a `KeyError` deep inside `scripted_factory`'s lookup, mid-run») —
og så inntreffer nøyaktig det den advarer mot, for de tre den ikke kjenner:
```
File ".../explore.py", line 576, in fresh_exploration_workflow
client_factory(role),
File ".../simulation.py", line 470, in factory
reply = replies[role]
KeyError: 'navigator'
```
Ingen `run refused:`-linje, ingen rc-1 med forklaring — en traceback, som er den kanalen
økt 57 betalte for å holde konfigurasjonsfeil UTE av.
**Positivt målt i samme kjøring:** `finally`-blokka holdt. `{run_id}-exploration.json` ble skrevet
selv om kjøringen krasjet, med `"completed": false` og `"stop": null` — nøyaktig det
`completed`-feltet finnes for.
**Fil:linje:** `src/portfolio_optimiser/run.py:1531` (`_SCRIPTED_ROLES`), `:1559-1564`
(fail-fast-listen), krasjer i `src/portfolio_optimiser/simulation.py:470`.
**Merk:** `simulation.scripted_exploration_factory` (`simulation.py:713-736`) dekker allerede alle
fem rollene. Sømmen finnes; CLI-en når den bare ikke.
### MAJOR-3 — regelverksbaser kan ikke være baser i multi-base-dispatchen (arkitektonisk, ikke en bug)
Bibliotekdøra `run_mandate_across_bundles` ble målt direkte med de tre basene og et mandat med én
approach per base:
```
STEG 1 route_by_bundle → B-n100…: ['a0'] B-n200…: ['a1'] B-n500…: ['a2'] ✅ korrekt partisjon
STEG 2 run_mandate_across_bundles → FileNotFoundError @ okf.py:433
"IR projection not found in bundle: 'validator-input.json'"
```
**Partisjonen virker perfekt.** Dispatchen gjør det ikke, fordi `run.py:1491` leser hver bases
prosjekt fra **den basens egen** IR-projeksjon — som er selve multi-base-invariantens designvalg
(«en kaller-oppgitt konstant kunne uansett bare vært riktig for én base av N»).
Konsekvensen er den viktigste innsikten i hele syretesten: **multi-base betyr N prosjekter, ikke
1 prosjekt × N referansebaser.** Vegnormal-basene er *regelverk* — de har verken prosjekt eller
kostbaseline, og skal ikke ha det. Ordrens mentale modell («ett veglysprosjekt + tre normalbaser
som kontekst») er en **annen form**, og den finnes allerede — bare ikke i dispatchen:
| Dør | `bundle_dirs` betyr | Passer regelverk? |
|---|---|---|
| `run.run_mandate_across_bundles` (`run.py:1388`) | N **prosjektbaser** → N kjøringer | **Nei** — krever `validator-input.json` per base |
| `explore.explore` (`explore.py:~840`) | N **lesekilder** for navigator/hypothesiser | **Ja** — målt, se under |
**Fil:linje:** `src/portfolio_optimiser/okf.py:431-433`, kalt fra `run.py:1491` (dispatchen),
`run.py:609` (enkeltkjøringen) og `simulation.py` (demoen) — alle tre feiler på samme sted.
### MINOR-1 — importformen legger alt på ett nivå, så progressiv disclosure gir null lettelse
0 underkataloger, 0 nestede `index.md`, 1733 av 1733 konsepter lenket direkte fra rot. Navigasjonen
har ingenting å utsette; hele korpuset er ett flatt nivå. Dette er en egenskap ved **kilden**, ikke
ved `okf.py`.
> **Rettet 2026-08-26 (økt 65): tilskrivelsen var feil, og `vegnormal-okf` har rett.** Flatheten er
> **Dør C** sin, ikke vegnormals emitterform. Verifisert mot kilden, ikke mot deres melding:
> `llm-ingestion-okf` `src/llm_ingestion_okf/importer.py` (§6-index-blokka) kaller
> `link_in_index(bundle, entry.path.name, _index_label(entry.concept_path))` per merget oppføring —
> altså én flat lenke i rot-`index.md` for hvert konsept, uansett hvor nestet konseptstien er.
> Vegnormals emitter skriver allerede et tonivåtre. Setningen over sto uendret som «vegnormal-okf
> sin importform» til dette punktet.
Dette var også hele grunnen til BLOCKER-1: med nestede indekser kunne manageren åpnet én gren om
gangen. Løsningen ble en annen — grener som **egne baser** (`vegnormal-okf` `8145c23`), med den
målte begrunnelsen at basegrensen er der OKF-navigasjonen stopper, så ingen indeksstruktur INNE i en
base senker prisen på å åpne den.
### NICE-1 — `read_bundle` nekter ukjent base ved navn, som lovet
```
read_bundle("finnes-ikke") → ExplorationError: unknown knowledge base 'finnes-ikke';
configured: B-n100-2023-…, B-n200-2024-…, B-n500-2024-…
```
Nekten navngir det konfigurerte settet. **Fil:linje:** `explore.py:390-393`.
---
## 4. Hva punkt 5 faktisk viste — og hvor grensen for offline går
Ordren ba om at vakuiteten skulle måles, ikke antas. Målt, med `scripted_exploration_factory`
(alle fem roller) mot de tre basene:
| Arm | Utfall |
|---|---|
| **B** — hypotese **uten** `bundle_id`, tre baser | `HypothesisParseError @ explore.py:732` — «a marked hypothesis must name its knowledge base when several are configured». **Multi-base-nekten fyrer korrekt mot et ekte korpus.** |
| **C** — hypotese **med** `bundle_id`, tre baser | `stop=None`, **1 approach**, `bundle_id='B-n100-2023-uten-sources-importert'`, 2 ledger-runder, 6 modell-prompts. **Sløyfa fullfører og produserer et rutet mandat.** |
Og så det ærlige forbeholdet, som er poenget:
- `quick_validate`-kall: **0**
- Prompt-strøm-forekomster av basenavnene: `N100` **2**, `N200` **0**, `N500` **0** — begge fra
instruksjonene, ingen fra et verktøyresultat.
**Navigatoren åpnet aldri en base.** Den scriptede klienten returnerer tekst og emitterer ingen
verktøykall — nøyaktig den grensen økt 56s måling allerede slo fast, nå bekreftet mot et eksternt
korpus. Lesesømmen **selv** er derimot bevist mot disse basene, ved direkte kall (samme kontroll
økt 56 måtte innføre da den oppdaget at armen lå utenfor gaten): `list_bundles()` → 3 baser,
`read_bundle()` → 221 916 / 616 179 / 240 714 tegn.
**Konklusjon for punkt 5, uten pynt:** *plumbingen* er bevist ende-til-ende mot ekte eksterne
baser — ruting, nekt, mandatform, artefaktskriving. *Verdien* er ikke bevist, og kan ikke bli det
offline. **Syretesten trenger en levende modell.** Det er et funn, ikke en feil.
---
## 5. Hva som MÅ til for en live-kjøring
Målt i denne økten, ikke antatt:
| # | Mangler | Målt tilstand | Konkret |
|---|---|---|---|
| 1 | **Kontekstbudsjettet** | `list_bundles()` = 112 116 tokens; `read_bundle(N200)` = 250 785 | **Den harde blokkeringen.** Enten en modell med svært stort vindu, eller — mer realistisk — en bundle-form med nestede indekser slik at manageren kan åpne én gren. Kilden eies av `vegnormal-okf`. |
| 2 | **Foundry-endepunkt** | `PORTFOLIO_FOUNDRY_PROJECT_ENDPOINT` **ikke satt**, `FOUNDRY_PROJECT_ENDPOINT` **ikke satt** | Én av de to må eksporteres. Låst av operatørbeslutningen om `DisableLocalAuth` (ordre `20260821T094949Z`, `docs/2026-08-18-vurdering-azure-omdoeping.md`). **Ikke rørt her.** |
| 3 | **Modell-map** | `PORTFOLIO_MODEL_MAP` ikke satt; `src/portfolio_optimiser/data/model_map.json` bærer `REPLACE-WITH-FOUNDRY-DEPLOYMENT` for alle azure-roller | `resolve_model("azure", r)` **nekter for alle fem roller**, inkl. `manager`/`navigator`/`hypothesiser`. Under `local` faller alle fem til `qwen3:4b` via `default` — utforskningsrollene er fortsatt ikke eksplisitt mappet (kjent ærlighets-grense fra økt 56). |
| 4 | **En prosjektbase** | 0 av 3 Vegnormal-baser har `validator-input.json` eller `cost-baseline.json` | Kjøringen trenger et **prosjekt** å optimere. Vegnormal-basene er regelverket det optimeres *innenfor*. Riktig oppsett: `--bundle-dir <prosjektbase>` for pipelinen + de tre normalbasene som `explore(bundle_dirs=…)`-lesekilder — men det krever MAJOR-1 løst, siden CLI-en i dag sender **én** base til begge. |
| 5 | **Offline-generalprøve** | `KeyError: 'navigator'` | MAJOR-2 må lukkes før en betalt kjøring, ellers er første live-kjøring også første gjennomkjøring. Repoets egen måleprotokoll: bevis så mye som mulig gratis, så en feil er attribuerbar. |
**Rekkefølge, uten å foregripe operatørens valg:** 5 → 1 → 2/3 → 4. Punkt 5 er gratis, punkt 1
avgjør om vei A/B i det hele tatt er mulig med denne bundle-formen, og punktene 23 koster penger
og er Azure-gatet.
---
## 6. Kommandologg
Hver tabellverdi over stammer fra én av disse, kjørt i denne økten:
```bash
# pkt 1: konsepter, index-lenker, verdicts, cost-baseline
find <base> -name '*.md' | wc -l ; grep -oE '\]\([^)]+\)' <base>/index.md | wc -l
grep -lE '^type: *verdict' <base>/*.md | wc -l
# pkt 1+2: navigasjon, kontekst, tokens (instrument validert mot commons' tre fasittall)
uv run --with tiktoken python # okf.navigate_bundle / okf.bundle_context / o200k_base
# pkt 3: CLI, fire --bundle-dir, begge rekkefølger
uv run python -m portfolio_optimiser.run VEGLYS-FV-SOER --docs-dir … --bundle-dir … --live-dry-run
# pkt 3: bibliotekdøra
python # mandate.route_by_bundle + run.run_mandate_across_bundles
# pkt 4: golden-regresjon
uv run python -m portfolio_optimiser.simulation | shasum # ea8c534773acdbe41ae68f2c55724d69aaf8be4f
# pkt 5: CLI-en, og deretter explore() direkte med alle fem roller scriptet
uv run python -m portfolio_optimiser.run … --explore … --explore-config … --scripted-replies …
# pkt 6: nestede index, med nav-golden-hierarchy som kjent-positiv kontroll
# regresjon
uv run pytest -q # 1021 passed, 5 skipped, 166.00s
```