portfolio-optimiser/docs/2026-08-14-fase1b-forste-levende-kjoring.md
Kjell Tore Guttormsen 2d1264088e feat(1b): skjemaet ER akseptert av det levende endepunktet - malt, ikke resonnert [skip-docs]
Fase 1b siste trinn: forste bundne levende kjoring over HELE run_project-stien
mot Foundry (gpt-4-1-mini). Okt 37s uttalte aerlighets-grense er lukket ved
maling: ingen -parse-failures.json i outboksen, altsa parset hvert eneste
genererings-svar. assumptions-normaliseringen virket ende-til-ende.

Utfall: rejected pa P90 (claimed 34500 > feasible 11488), checker approve.
Kjoringen KONKLUDERTE - per pre-registreringen det bestatte utfallet. De to
falsifisererne skilte lag for forste gang mot en levende modell.

FUNN storre enn den gronne testen: modellen fant opp kostkoden
EL-LIGHTING-OP-HR (null treff i kunnskapsbasen). Avvisningen var riktig men
skjedde pa 30%-cap-en, ikke stage 0 - bundelen shipper ingen cost-baseline.json,
sa S4.0-forankringen var inaktiv. Ko-fort, ikke rettet her.

Gate-designet er ovis beslutning, tatt for koding:
- TREDJE distinkt opt-in PORTFOLIO_LIVE_FULL_RUN (truthiness, 4b-invarianten).
  Begge eksisterende live-tester gater pa SAMME to Foundry-variabler, sa
  gjenbruk ville latt den billige proben fyre den dyre kjoringen - stigen i
  maleprotokollen ville kollapset til ett trinn. MALT: den dyre SKIPPET med
  begge Foundry-variablene satt.
- Asserten i EN kopi (conftest.assert_full_run_contract, ko-(p)), smal med
  vilje: fravaer av parse-failures-artefaktet + at validatoren avgjorde. En
  rejected BESTAR - pastanden er schema-aksept, ikke modell-dommekraft.
- Iron Law uten a betale to ganger: diskrimineringen bevist OFFLINE av
  test_live_full_run_contract.py. To mutasjoner, hver sin signatur: detach
  artefakt-sjekken (T1 rod ALENE) - raise ubetinget (T2 rod ALENE).

STATE-premiss korrigert: "test_foundry_profile_live dekker KUN klient-nivaet"
var upresist - test_portfolio_live.py dekket allerede fan-outen, men dens
len(runs)==1 kan ikke skille validert fra avvist og bar derfor ikke pastanden.

869 passed / 5 skipped (fra 867/4), ruff+format+mypy rene.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GF7va4cpRiuf79kTzAi3vW
2026-08-14 20:02:26 +02:00

12 KiB
Raw Blame History

Fase 1b — den første levende kjøringen (14. august 2026)

Dette dokumentet er et måleprotokoll, ikke en konklusjon. Alt under er kjørt; ingenting er utledet. Der noe ikke er bevist, står det.

0. Hva som ble opprettet

Ressurs Verdi
Ressursgruppe portfolio-optimiser-rg (eastus)
Foundry-ressurs po-foundry-ktgkind: AIServices, --allow-project-management
Prosjekt po-project
Deployment gpt-4-1-mini (modell gpt-4.1-mini, versjon 2025-04-14, GlobalStandard)
Prosjekt-endepunkt https://po-foundry-ktg.services.ai.azure.com/api/projects/po-project
Rolle Foundry User (53ca6127-…) på prosjekt-scope

gpt-4o-mini ble avvist av plattformen med ServiceModelDeprecating — modellnavnet måtte måles med az cognitiveservices model list, ikke hentes fra hukommelsen. Kommandoene er hentet fra Microsoft Learn (az cognitiveservices account create / account project create), ikke formulert fritt.

Rotårsaken til at dette ikke fantes før var prosedural, ikke teknisk: den påloggede identiteten har vært Owner på abonnementet hele tiden, mens repoets egen state-fil hadde ført opprettelsen opp som operatørens oppgave og samtidig sagt at operatøren aldri kjører kommandoer selv. Arbeidet tilhørte ingen, og hver økt målte lydig på nytt at det ikke fantes.

1. Stigen — billigste trinn først

Disiplinen fra fase 1a: bevis så mye som mulig før det dyre trinnet, så en feil er attribuerbar.

Trinn Kommando Utfall
1 az account get-access-token --resource https://ai.azure.com exit 0
2 preflight --profile azure (fra utpakket overleveringspakke) preflight OK (azure)
3 pytest tests/test_foundry_profile_live.py (gatet triviell probe) 1 passed, 5,02 s
4 Full run_project mot levende modell RC=1 — BudgetExceeded

Trinn 3 er prosjektets første levende modellkall noensinne. Det beviser at auth, RBAC, endepunkt-form og deployment-navn komponerer — og at en rød trinn 4 derfor ikke kan skyldes noen av dem.

2. Trinn 4 — hva som faktisk skjedde

uv run python -m portfolio_optimiser.run BYGG-KONTOR-NORD \
  --profile azure \
  --docs-dir shared/examples/bygg-energi-mikro \
  --bundle-dir shared/examples/bygg-energi-mikro \
  --outbox-dir <tmp>/outbox --run-id live-001

stdout var tom. stderr bar to linjer som betyr noe:

GroupChatOrchestrator reached max_rounds=3; forcing completion.
portfolio_optimiser.budget.BudgetExceeded: budget exceeded: rounds limit=12 observed=13

Den første er forventet — maker/checker-debatten kjører til taket også i den offline demoen. Den andre er funnet.

Diagnosen, utledet av kode og ikke av flere betalte kjøringer

generate._fetch_parsed er en while True som kaller meter.tick_round() for hvert forsøk og continue-er ved parse-feil. Rund-budsjettet i genereringsfasen er max(max_rounds * 4, 4) = 12 (run.py:546). Hadde modellsvarene parset til IR-formen, ville tre validerings-forsøk kostet tre runder. At alle tolv gikk med betyr at de fleste svarene fra gpt-4.1-mini ikke lot seg parse — taket ble brent på formatfeil, ikke på validator-avslag.

Dette er et FUNN, ikke en diagnose som er ferdig. Det som mangler for å lukke den, er den råe svarteksten, og den finnes ikke i noen artefakt i dag (--outbox-dir skrev kun live-001-runconfig.json). Å skaffe den krever en endring i koden, altså Iron-Law-arbeid — ikke en rask omkjøring med høyere tak, som ville kostet penger og fortsatt ikke sagt hvorfor.

En bieffekt som er verdt å uttale

BudgetExceeded forlot kjøringen som en uhåndtert exception med traceback, ikke som et strukturert utfall. På den hostede flaten ville nøyaktig dette blitt HTTP 500 — altså ville ressurs-utmattelse (en normal, forventet tilstand) presentert seg for en ekstern kaller som en serverfeil. Det er ikke rettet her; det er notert.

3. Hva som ER bevist, og hva som IKKE er det

Bevist, målt:

  • Rammeverket når en levende Foundry-modell: auth, RBAC på prosjekt-scope, endepunkt-form, deployment-oppslag og klientbygging virker.
  • Kunnskapsbase-navigasjon, debatt-orkestrering og genererings-løkka kjører mot ekte modellsvar — kjøringen døde inne i løkka, ikke før den.
  • Preflight fra den utpakkede overleveringspakka er grønn mot et ekte prosjekt, både med vårt eget endepunkt-variabelnavn og med plattformens injiserte.

Ikke bevist:

  • At systemet produserer et validert forslag mot en levende modell. Det har det aldri gjort. Kjøringen nådde aldri fram til validatoren med en parsebar kandidat.
  • At gpt-4.1-mini er en egnet modell for proposer-rollen. Målingen peker mot at den ikke er det uten endret prompting eller structured output — men én kjøring er én kjøring.
  • Noe som helst om kostnad i drift. Denne kjøringen kostet noen få øre; det sier ingenting om en reell portefølje.

4. Neste steg, i rekkefølge

  1. Fang den råe svarteksten ved parse-feil (i dag forsvinner den i except: continue). Test først — dette er en søm, ikke en logg-linje.
  2. Vurder structured output mot Foundry for proposer-rollen, framfor å prompte fram JSON.
  3. Vurder om BudgetExceeded skal bli et strukturert utfall i stedet for en traceback, særlig for den hostede flaten.
  4. Først når 1 og 2 er på plass: en ny bundet kjøring, og en gatet test som dekker hele run_project-stien (den eksisterende gatede testen dekker kun klient-nivået).

Rettelse til punkt 4, målt 14.08 (økt 40): parentesen er upresis. tests/test_portfolio_live.py fantes allerede og dekker run_portfolio-utfoldingen — altså hele run_project-stien — med samme env-gate. Men den asserterer len(result.runs) == 1, og fordi runs og failures partisjonerer, kan den kun skille «kjøringen fullførte» fra «kjøringen raiste». Den kan ikke skille et validert forslag fra et avvist, og heller ikke en kjøring der svarene parset fra en der de ikke gjorde det. Den bærer derfor ikke påstanden 1b skal felle. Punkt 4 står, men grunnen er en annen enn skrevet.

5. Pre-registrerte utfall (skrevet FØR kjøringen)

Denne kjøringen har ett formål: å felle den ene gjenstående ærlighets-grensen fra økt 37 — at det emitterte response_format-skjemaet ER akseptert av det LEVENDE endepunktet er uverifisert; testene beviser konformitet med Azures dokumenterte subset, ikke aksept.

Instrumentet er conftest.assert_full_run_contract, og diskriminatoren er et artefakt repoet allerede eier: {run_id}-parse-failures.json skrives hvis og bare hvis et svar ikke lot seg parse (økt 35). Artefaktets fravær ved siden av et RunResult beviser at hvert genererings-svar kom tilbake i den bestilte formen. Kontraktens evne til å skille er bevist offline og gratis (tests/test_live_full_run_contract.py, to mutasjoner med hver sin signatur: detach artefakt-sjekken → T1 rød alene; raise ubetinget → T2 rød alene). Det betalte kallet er målingen, ikke beviset på at måleinstrumentet virker.

Hva hvert utfall betyr — avgjort på forhånd:

Utfall Betydning
Ingen parse-failure-artefakt + validatoren avgjorde (validated eller rejected) Ærlighets-grensen er felt. Skjemaet ble akseptert av det levende endepunktet. En P90-avvisning er et bestått utfall — kjøringen KONKLUDERTE.
Parse-failure-artefaktet finnes Skjemaet ble ikke honorert. Et FUNN, ikke et bevis — og denne gangen finnes den råe teksten (økt 35), så neste steg kan begrunnes i stedet for gjettes.
BudgetExceeded Fortsatt ubevist, men artefaktet forklarer hvorfor. Samme form som 1a-kjøringen.

Taket heves IKKE. max_rounds/max_tokens står på defaultene den første levende kjøringen døde på: fyrer ledgeren igjen, er dét informasjon, og å heve taket ville brukt mer penger på en sti som kanskje fortsatt er brukket.

Gate-variabelen er en TREDJE, distinkt opt-in (PORTFOLIO_LIVE_FULL_RUN, lest på truthiness). Begge de eksisterende live-testene gater på nøyaktig PORTFOLIO_FOUNDRY_PROJECT_ENDPOINT + PORTFOLIO_FOUNDRY_DEPLOYMENT; å gjenbruke det paret ville betydd at en operatør som eksporterer de to for å kjøre den billige ett-ords-proben også fyrer den dyre fullkjøringen — altså at stigen i §1 kollapser til ett trinn. PORTFOLIO_MODEL_MAP er med i skip-betingelsen av en annen grunn: uten den feiler kjøringen av en konfigurasjons-årsak som ser ut som en modell-feil.

6. Den bundne fullkjøringen — målt 14.08 kl. 19:54

Stigen på nytt, samme disiplin som §1. Alt under er kjørt.

Trinn Kommando Utfall
1a az account get-access-token --resource https://ai.azure.com (uten rør) exit 0, expiresOn 21:07:40
1b az ad signed-in-user show (ekte Graph-kall, ikke lokal cache) exit 0 — gmail-kontoen, ikke jobbkontoen
2 preflight --profile azure preflight OK (azure)
3 pytest tests/test_foundry_profile_live.py 1 passed, 5,48 s
3b samme kall, med test_full_run_live.py samlet SKIPPED — det tredje flagget holder stigen
4 pytest tests/test_full_run_live.py (PORTFOLIO_LIVE_FULL_RUN=1) 1 passed, 22,87 s

Trinn 3b er verdt å uttale: den dyre testen hoppet over selv med begge Foundry-variablene satt. Det er den empiriske bekreftelsen på at det tredje opt-in-flagget gjør jobben sitt design lover.

Ærlighets-grensen ER felt

Outboksen inneholder -proposal.json, -outcome.json, -runconfig.json — og ingen -parse-failures.json. Hvert eneste genererings-svar fra gpt-4.1-mini kom tilbake som et parsebart objekt i den bestilte formen. Økt 37s uttalte grense — «at det emitterte skjemaet ER akseptert av det LEVENDE endepunktet er IKKE verifisert» — er dermed lukket ved måling, ikke ved resonnement. Kontrasten til den første levende kjøringen er hele funnet: der brant tolv runder på formatfeil og etterlot null tegn; her feilet ingen.

assumptions kom tilbake som forventet ({"EL-LIGHTING-OP-HR": [10.0, 13.0]}), altså virker økt 37s additive wire-form → IR-map-normalisering ende-til-ende mot et levende endepunkt. Det var beslutningen som holdt den stokastiske falsifisereren fra å gå inert, og den er nå prøvd i felt.

Utfallet: rejected — og det er et bestått utfall

outcome_type:     rejected
reason:           claimed saving 34500 exceeds P90 feasible 11488
checker_verdict:  approve
validator_decision: rejected
token_usage:      15 306

Kjøringen KONKLUDERTE. Per §5s pre-registrering er dette det positive utfallet: validatoren ble nådd med en parsebar kandidat og avgjorde. Og de to falsifisererne skilte lag for første gang mot en levende modell — checkeren godkjente resonnementet, validatoren avviste tallene. Nøyaktig den uavhengigheten checker_verdict holdes atskilt fra provenance.validator_decision for.

FUNN som er viktigere enn den grønne testen: modellen fant opp en kostkode

Forslaget bar code: "EL-LIGHTING-OP-HR" (3 000 × 11,5). Den koden finnes ikke noe sted i kunnskapsbasen (grep over hele bundelen: null treff). Kunnskapsbasen instruerer eksplisitt mappingen ENERGI-TOTAL-EL, 300 000 kWh × 1,00 NOK — modellen konstruerte i stedet sin egen kostlinje med en egen enhet.

Avvisningen var derfor riktig, men skjedde på feil gate: 30 %-cap-en fanget den på magnitude (34 500 > P90 11 488), ikke stage 0 på eksistens. Grunnen er en kjent og uttalt egenskap, ikke en defekt: S4.0-forankringen aktiveres på bundle-stien KUN når bundelen shipper cost-baseline.json, og bygg-energi-mikro gjør ikke det (målt) — «en pre-amendment-bundle er legitimt uforankret».

Dette er akkurat den hallusinasjons-klassen S4.0 ble bygget for, observert i felt for første gang. At den uforankrede gaten fanget den likevel er betryggende; at den fanget den på den dyre gaten i stedet for den billige er en kø-post, ikke noe som endres her.

Fortsatt ikke bevist: at systemet produserer et validert forslag mot en levende modell. Denne kjøringen avviste — korrekt, og med en begrunnelse som kan leses. Én kjøring er én kjøring, og gpt-4.1-minis egnethet for proposer-rollen er ikke avgjort av den.