Operatørbeslutning 2026-08-18: vei A. Ressurs-, prosjekt- og vertsnavnene i måleprotokollen fra første levende kjøring byttes mot repoets eksisterende plassholder-form (<resource-group> / <resource> / <project>, samme som DEPLOY.md og auth-oppskriften). Vei B (omskrive historikken) er avvist: koordinatene nådde alt en tredjepart via 14:24-bygget, så B ville kostet en force-push for en gevinst vi ikke kan bekrefte. MÅLT, ikke antatt: - 5 linjer bar koordinatene (10-12, 14, 172), ikke de 10 STATE påsto. Linje 164-166 bærer kun `gpt-4-1-mini` - offentlig Azure-nomenklatur, står. - Ingen gate pinner strengene: 0 treff i tests/ + src/ + scripts/, kjent-positiv kontroll samme kommandoform ga 5. En prosa-redaksjon kan ikke brekke en guard. - Klasse 2 etter redaksjonen: 0 treff over hele treet (nevner 327 sporede filer), tre spørringsklasser hver med kjent-positiv kontroll som fyrte. De gjenværende AI-Services-vertene er testdummies (x./platform./injected), og GUID-en 53ca6127-db72-4b80-b1b0-d745d6d5456d er Azures OFFENTLIGE built-in role definition id for Foundry User (verifisert mot Microsoft Learn), lik i hver tenant - ikke en koordinat. - Regresjonsgaten: 869 passed / 5 skipped, likt referansen. Dokumentets verdi står: dette er en måleprotokoll, og poenget er hva som ble målt - ikke hvilken ressursgruppe det skjedde i. En linje under tabellen sier høyt at navnene er plassholdert, så ingen leser tror <resource> var det literale navnet. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WVTZgMF94VZtNa7nGVtupF
247 lines
13 KiB
Markdown
247 lines
13 KiB
Markdown
# 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 | `<resource-group>` (eastus) |
|
||
| Foundry-ressurs | `<resource>` — `kind: AIServices`, `--allow-project-management` |
|
||
| Prosjekt | `<project>` |
|
||
| Deployment | `gpt-4-1-mini` (modell `gpt-4.1-mini`, versjon `2025-04-14`, GlobalStandard) |
|
||
| Prosjekt-endepunkt | `https://<resource>.services.ai.azure.com/api/projects/<project>` |
|
||
| Rolle | `Foundry User` (`53ca6127-…`) på **prosjekt**-scope |
|
||
|
||
Ressurs-, prosjekt- og vertsnavnene er byttet mot plassholdere (`<resource-group>` / `<resource>` /
|
||
`<project>`, samme form som `DEPLOY.md` og auth-oppskriften). Modellnavn, versjon og region er
|
||
offentlig Azure-nomenklatur og står som målt.
|
||
|
||
**`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
|
||
|
||
### Oppsettet, reproduserbart
|
||
|
||
Model-mappet ligger **out-of-tree** med vilje: `data/model_map.json` i treet bærer
|
||
`REPLACE-WITH-FOUNDRY-DEPLOYMENT`-plassholdere, og tenant-spesifikke deployment-navn skal aldri
|
||
committes (B12). `PORTFOLIO_MODEL_MAP` peker på en fil operatøren eier:
|
||
|
||
```json
|
||
{
|
||
"local": {"default": "qwen3:4b", "proposer": "qwen3:4b", "checker": "qwen3:4b"},
|
||
"azure": {
|
||
"default": "gpt-4-1-mini",
|
||
"proposer": "gpt-4-1-mini",
|
||
"checker": "gpt-4-1-mini"
|
||
}
|
||
}
|
||
```
|
||
|
||
```bash
|
||
export PORTFOLIO_FOUNDRY_PROJECT_ENDPOINT="https://<resource>.services.ai.azure.com/api/projects/<project>"
|
||
export PORTFOLIO_FOUNDRY_DEPLOYMENT="gpt-4-1-mini"
|
||
export PORTFOLIO_MODEL_MAP="/absolutt/sti/til/model_map.live.json"
|
||
export PORTFOLIO_LIVE_FULL_RUN=1 # BETALER — utelat den for alt annet enn trinn 4
|
||
```
|
||
|
||
Merk at `PORTFOLIO_LIVE_FULL_RUN` bevisst settes SIST og alene for det dyre trinnet: uten den er
|
||
trinn 2 og 3 gratis-nok til å kjøres fritt, og det er hele stigens poeng.
|
||
|
||
### Stigen
|
||
|
||
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-mini`s egnethet for proposer-rollen er ikke avgjort av den.
|