Fase 3 (AAA+ på publisert flate). Tre av planens premisser falt på måling og er rettet FØR handling, ikke etterpå: * GOVERNANCE-raden hadde feil tiltak. Planen sa «skriv den»; org-ops D11 sier én kanonisk fil som hvert repo LENKER, og filen er nå publisert (målt: HTTP 200 på open/repo-standard). Å skrive vår egen ville gjort oss til kopi nr. 12 av en fil D11-bølgen holder på å rydde vekk. README lenker den, i samme form som repo-mailbox bruker, og bus-faktor 1 står uttalt i den kanoniske teksten. * Release-objektet for v1.0.0 FINNES allerede på open/ (id 155, CHANGELOG-kropp, siden rendrer) — det som mangler er vedlegg, ikke objektet. * WARN RELEASE-STALE fyrer ikke, og kan ikke: regelen sammenligner utgivelse mot tagg og er strukturelt blind for repo med null utgivelser (org-ops hovedbok #18). Gaten var OK/20 sjekker FØR arbeidet startet, så den kan ikke tjene som verifikasjon for denne fasen. Bevisene er Forgejo-APIet, filinnholdet og ren-klon-kjøringen. A5-defekten rettet: env.template:21 sa at credential resolves via DefaultAzureCredential. Den har aldri gjort det — backends.py:149 konstruerer ManagedIdentityCredential eller AzureCliCredential, og Learns MAF-veiledning navngir den spesifikke credentialen NETTOPP for å unngå probing. En operatør som kopierte templaten ble fortalt at feil identitet ville bli brukt. To load-bearing gater (Iron Law: begge røde før fiksen, 2 failed / 7 passed): 1. env.template navngir de credentials backends.py faktisk konstruerer, og ingen linje utgir DefaultAzureCredential for å være mekanismen. LINJEFORANKRET, ikke delstreng: backends.py NAVNGIR klassen fire ganger i kommentarene som begrunner hvorfor den ikke brukes, så en fil-bred substring-gate ville vært rød på nøyaktig den prosaen den beskytter (repoets 08-09-klasse, fjerde gang). 2. README-ens wheel-filnavn bærer versjonen bygget stempler på fila. Uten den ville en versjonsbump stille etterlatt en publisert install-kommando som peker på en fil som ikke finnes. Hver positiv assert er paret med en KONTROLL på at det søkes etter noe som finnes — en ekstraktor som stille finner null lager en gate som bare kan bli grønn. MUTASJONER MÅLT MOT HELE SUITEN, begge røde på riktig test og på INGEN annen: gjeninnfør den usanne credential-påstanden (2 røde, 844 grønne) · la wheel-filnavnet drifte til 1.0.0 (1 rød, 845 grønne). Restaurert fra scratchpad + shasum -c mellom hver. Bumpen selv var den andre mutasjonen: pyproject 1.0.0 → 1.1.0 gjorde README-gaten rød alene, før README ble rettet. SECURITY.md: varslingsfrist (minst én minor-release og aldri under 30 dager mellom kunngjøring og fjerning, med sikkerhetskritisk fjerning som uttalt unntak). Støttetabellen er bevisst VERSJONSFRI — et release-nummer skrevet der ville drevet ved neste tagg, altså samme defektklasse som gate 2 fanger. CLAUDE.md beholdt på flaten med en engelsk innramming øverst (operatørvalg): den sier hva fila er for en fremmed. Innholdet er repoets sterkeste bevis på at hver beslutning er målt; å fjerne det ville fjernet bevis, ikke friksjon. Versjon 1.1.0 — synket i pyproject, __init__, test_smoke og README-kommandoen. 1.0.0-treet kan ikke produsere en kjørbar wheel (force-include kom etter taggen, målt: git show v1.0.0:pyproject.toml har den ikke), så en wheel hengt på den utgivelsen ville vært nøyaktig den usanne påstanden denne fasen finnes for å fjerne. Operatøren valgte bumpen framfor et vedlegg som ikke virker. 846 passed / 4 skipped (fra 837). ruff + format + mypy rene. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011ckyg3Pc6k7FRuR6fDGQLJ
50 KiB
portfolio-optimiser
Note for visitors — what this file is. This is the working agreement between the repository and the AI coding agent that builds it (the Claude Code convention), and it is written in Norwegian because that is the maintainer's working language. It doubles as the repository's invariant ledger: each block below records a design decision, the measurement that forced it, and the test that turns red when the decision is undone.
You need none of it to use the framework — start with the README. It is published anyway, because the reasoning behind a decision is worth more than the decision, and because a rule kept out of sight is a rule that drifts without anyone noticing.
Kontekst
Generisk, åpent Python-rammeverk på Microsoft Agent Framework (MAF) som finner kostnadsbesparelser INNI hvert prosjekt i en portefølje av uavhengige prosjekter. Multi-agent samarbeid genererer kandidat-tiltak; en obligatorisk deterministisk validator avgjør verdiene; fageksperter vurderer via HITL, og systemet lærer av dommene. Publiseres på Forgejo for alle som har MAF.
Bakgrunn og beslutninger: research (§15 implementeringsregister), plan. Løpende state: STATE.md (local-only).
Stack
Python ≥3.10. MAF (agent-framework-core 1.9.0). Pakkehåndtering: uv. To backend-profiler: Azure/Foundry (full) + lokal (fallback).
Konvensjoner
- Type hints overalt (
mypyder mulig). Pydantic for validering/IR. rufffor lint+format.pytestfor test.- Modell-valg som konfig (modell-map rolle→Foundry-deployment), ikke spredt i kode.
- Metode kodifiseres som Agent Skill (
agentskills.io:SKILL.md+scripts/+references/). - Datatilgang: in-process
FunctionTooler default-sømmen i kjørestien. MCP er wiret som opt-in i kjørestien (mcp_tools.py+--mcp-config, Trekk B 2026-08-05): konkrete eksterne servere blir verktøy agentene kan kalle UNDER debatten. Uten konfig gjøres null nettverkskall og verktøylista er uendret. Tre regler er load-bearing: allowlist er påkrevd (tom liste ville latt motparten bestemme hva agentene får kalle), hver server og hvert tillatte verktøy navngis i kunngjøringen før første kall (også uten--mandate— ingen udeklarert egress), og--live-dry-runåpner ingenting. Egen søm fraingest_mcp.py(kildedokumenter FØR kjøring, null-argument-tools) — samme protokoll, ulik jobb.build_mcp_server(datasource.py) er fortsatt kun demo. Data-source-konfig JSON-Schema-validert, fail-fast. shared/er en git subtree avportfolio-optimiser-commons(source of truth, R1 realisert 2026-07-03; publisert iopen/2026-08-04 —commons-remoten peker fortsatt påktg/og virker uendret). Synk er pull-only: endringer committes i commons og hentes medgit subtree pull --prefix=shared commons main --squash. ALDRIgit subtree pushfra konsument — re-split lekker hele konsument-historikken inn i commons (observert + opprydd 2026-07-03). Seshared/README.md. Wheelen bærer treet som pakkede data siden Fase 4a — se invarianten under.
Kommandoer
- Sync:
uv sync— installerer to konsoll-kommandoer:portfolio-optimiser(CLI,run:main) ogportfolio-optimiser-demo(offline-beviset,simulation:main).python -m-formene virker uendret og er byte-identiske på stdout (målt). Bevisst KUN to av femmain()—costsim/hitl/preflighter operatørverktøy, ikke produktets inngang, og hvert navn her er et navn frysen må bære. Pinnet avtests/test_console_entry_points.pymot den INSTALLERTE distribusjonens metadata, ikke mot TOML-en: en[project.scripts]-linje som aldri eruv sync-et er en påstand, ikke en kommando. - Test:
uv run pytest - Lint:
uv run ruff check .+uv run ruff format . - Type:
uv run mypy src
Arbeidsflyt (invarianter)
- Rent teknisk rammeverk: deployer eier DPIA/ROS/behandlingsformål. Bygg IKKE compliance-funksjoner — kun tekniske forutsetninger (lokal-only, provenance, ingen stille egress) + disclaimer.
- 90%-prinsipp: bygg den generiske kjernen + tydelige extension points; jakt IKKE de siste 10 %.
- Deterministisk validator er obligatorisk og blokkerende — aldri valgfri plugin.
- Framework-nøytral kontekst-søm: OKF-bundle-navigasjon (
okf.py) og den delteshared/-kjernen er ren stdlib — nullagent_framework/mcp-import, så samme bundles konsumeres uendret av begge stacker (D7-portabel). Håndhevet avtests/test_okf.py::test_okf_is_maf_free; importér aldri MAF inn i kontekst-laget. - OKF-navigert bundle-kontekst (ikke stuffing): på bundle-stien bygges agent-lese-konteksten
ved å NAVIGERE bundelen (
okf.bundle_context: index + frontmatter + cross-links, progressiv disclosure) — aldri keyword-chunk-stuffing (målbilde §2/§4).type: verdict-laget ekskluderes fra denne konteksten: tidligere dommer når hypotese-prompten KUN via den gatede ExpeL-folden. Load-bearing:test_bundle_context_excludes_verdict_layer+ empty-store-kontrollen itest_step1_expel_loadbearing.py(realiseringssignalet lekker aldri inn via kontekst). - Navigasjons-kontrakten er hierarkisk, og escape — ikke dybde — er forbudt (
method-spec§3 Steg 1):navigate_bundlefølger cross-links REKURSIVT, dybde-først i først-sett-rekkefølge; ledende/betyr bundle-rot (aldri filsystem-absolutt), alt annet er relativt til den LENKENDE filas katalog; dedup skjer på resolvert sti (så./a.md==a.md, og sykler termineres).safe_resolveer den ENESTE inn-/ut-av-bundle-testen (fail-closed) — den erstattet den pensjonerte «separator = utenfor bundelen»-heuristikken, som forvekslet dybde med escape. Manglendeindex.mder feil KUN i bundle-rota (navigasjon følger lenker, aldri katalog-enumerering). Rendering er FLAT uansett dybde; nestedeindex.mder navigasjon, ikke innhold. Gaten er commons-eide nav-goldens (shared/examples/nav-golden-*/expected-read-context.md, byte-nivå fasit):test_nav_golden_hierarchy_*(positiv) +test_nav_golden_escape_*(negativ — en gate som bare kan bli grønn beviser ingenting). - Kuraterte skrivere kan ikke forfalske ingest-stempelet (
ingest-spec§3):write_concept_fileer repoets ene authoring-primitiv som materialiserer en konseptfil fra CALLER-oppgitt frontmatter, og avviser derfor det KOMPLETTE eierskaps-stempelet (generated: true+ingest_manifest) medIngestStampError— mens hver halvdel alene er lovlig (kuratert innhold kan bære ett provenance-felt). Validering, ALDRI reparasjon: ingenting skrives. Uten dette kunne en kuratert fil bli stille slettet av en senere re-materialisering, som fjerner nøyaktig det som bærer stempelet. IngestErrormå overleve anyio-task-gruppene (kø-(x), 2026-08-03):stdio_clientogClientSessioner hver sin task group, og anyio pakker ALT som forlater en av dem i enBaseExceptionGroup. Derfor nåddestdio_call_tools egne feil (mcp_tool_error,mcp_non_text_content) kalleren som en exception group — aldri somIngestError, som er typen hele Door A fanger og switcher på viacode._unwrap_ingest_errorpakker ut og re-raiser den eide feilen; alt annet re-raises URØRT (innsnevring, aldri blanket re-raise). Duck-typet på.exceptions, fordiexcept*/ExceptionGrouper 3.11+ og repoet er>=3.10. Ingen canned-tool-test kunne fanget dette — de går aldri inn i en task group; defekten dukket opp første gang koden faktisk ble kjørt. En MCP-server på ingest-stien må eksponere en null-argument-tool (URL-en bærer begge koordinatene, tool kalles med{}), sådatasource.build_mcp_serverkan IKKE serve den —retrieve_cost_docs(query)har et påkrevd argument og returnerer et error-result. De to er separate sømmer med vilje. Load-bearing MÅLT (tests/test_ingest_golden_mcp.py), fem mutasjoner alle røde: detach unwrappingen · detachinitialize()· gjør feilkoden generisk · detachisError-grenen · endre ett byte av bodyen.- MCP-timeouten må komponeres MED anyios eget cancel scope, ikke
asyncio.wait_forutenfra (kø-(z), 2026-08-03): STATE-premisset ("en utypetTimeoutErrorre-raises urørt") var FEIL — målt mot en EKTE hengende server gaasyncio.wait_for(run(), timeout=...)aldri enTimeoutErrori det hele tatt; den kansellererrun()UTENFRA strukturen anyio selv eier (stdio_client/ClientSession), og de to kansellerings-mekanismene komponerer ikke — målt utfall var enanyio.BrokenResourceErrorinni enBaseExceptionGroup(en bakgrunns-reader-task mistet skrive-enden midt i nedrigging). Fiksen eranyio.fail_after(timeout_seconds)NESTET INNI begge task-gruppene, der anyio rigger ned sin egen struktur rent og raiser en renTimeoutError. Innsnevringen dekker IKKE bare typen: builtinTimeoutErrorer ogsåsocket.timeout(≥3.10) ogasyncio.TimeoutError(≥3.11), så et ubetingetexcept TimeoutErrorville mislabelt en HVILKEN SOM HELSTTimeoutErrorsommcp_timeout— reviewet FØR commit (advisor) fant nøyaktig dette. Retteslen eranyio.CancelScope.cancelled_caught: kun scopet som faktisk traff SIN EGEN deadline tjenermcp_timeout-koden, ellers re-raises urørt (speiler_unwrap_ingest_errors eierskaps-regel). Ingen levende utløser finnes i dag for en "fremmed"TimeoutErrorpå denne stien — MÅLT: MCPs eget per-request read-timeout (ClientSession.send_request) konverterer sinanyio.fail_aftertilMcpErrorFØR den når oss, og en tool som raiserTimeoutErrorserver-side blir et ordinærtisError-resultat (samme som enhver annen tool-exception) — begge verifisert empirisk, ikke antatt. Diskriminatoren er likevel pinned med en syntetisk test (raiser fraStdioServerParameters-konstruksjon, inni fail_after-scopet men FØR noen task group), fordi defektklassen ellers ikke har en nåbar sti å bevise den mot. Load-bearing MÅLT (tests/test_ingest_golden_mcp.py) mot HELE 625-suiten, fire mutasjoner alle røde: detach hele oversettelsen · revert tilasyncio.wait_for· relabel koden · detachcancelled_caught-gaten (behold kun scope-presence).anyiopromotert fra transitiv (viamcp) til deklarert direkte dep (pyproject.toml) — modulen importerer den nå direkte. - Door A-innholdsgaten kan IKKE bo i
materialize— den bor rundt den (P2/S1.b, 2026-08-09):materializeer en REN delegasjon til det pinnedellm_ingestion_okfv0.3.2smaterialize_bundle, som stager i minnet og utfører sin EGEN disk-fase; det finnes ingen callback mellom de to, så en gate plassert «i skrivepunktet» kunne bare kjørt ETTER at bytene hadde landet — en opprydding, ikke en gate (planens premiss, felt ved måling FØR bygging).materialize_gateder derfor: kopier bundelen → materialiser inn i kopien → skann det som ble generert → publiser eller forkast. Kopien er BÆRENDE: bibliotekets §3 eierskaps-skann, kollisjons-gaten mot kuratert innhold og §6 index-merge leser alle den EKSISTERENDE bundelen — staging i en tom katalog mister alle tre og publiserer en bundle uten kuraterte naboer og deres index-lenker (datatap forkledd som sikkerhetsfiks; MÅLT av kun ÉN test, 809 andre merket ingenting).materializeforblir UGATET med vilje — fire golden-suiter pinner bytene, og en kaller som vil ha gaten ber om den ved navn. Utfall per BUNDLE, diagnostikk per DOKUMENT: delvis publisering ville etterlatt bundle + index som svarer til INTET manifest, menimport_bundleitererer forbi første avvisning så hvert funn rapporteres. Trust følger ORIGIN, aldri channel (Origin.EXTERNAL/Channel.AUTOMATIC= UNTRUSTED) — ikke et av guardens toPolicy-preset:PRESET_USER_UPLOADbærerquarantine_default=Truesom Door A ikke har. Den laveste dispositionen erwarn, ikkeallow(warn < quarantine_review < fail_secure;allowfinnes ikke) — en gate skrevet mot== allowville avvist hvert dokument noensinne. Funnene tillog.md(OKF §7), ALDRI konsept-frontmatter — der ville de brutt fire goldener. Guarden shipper ingenpy.typed: mypy-override ALENE gjør sømmen type-BLIND, så_stamp_line+ koersering stopperAnyved grensen. Load-bearing MÅLT (tests/test_ingest_content_gate_loadbearing.py), fem mutasjoner alle røde + grønn kontroll: detach gaten · la den fyre ETTER publisering ·Origin.INTERNAL· tom staging-katalog · rapporter kun første avvisning. Målingen felte en VAKUØS test først: en hard injeksjon scorerfail_secureunder BEGGE tierene, såOrigin.INTERNAL-mutasjonen lot alle tre avvisningstestene stå grønne — beslutningen så dekket ut uten å være testet. Båndet der tieren faktisk avgjør er høy-entropi-innhold (quarantine_reviewvswarn), og testen ble skrevet mot nøyaktig det. shared/leses som PAKKEDE DATA, med arbeidstreet som overstyring (Fase 4a): wheelen bærer en byte-identisk speiling av heleshared/-treet underportfolio_optimiser/_shared/(hatchling force-include ipyproject.toml), ogshared_root()løser ved KALL-tid i fast rekkefølge:PORTFOLIO_SHARED_ROOT→ arbeidstreetsshared/når det finnes → pakket kopi. Arbeidstreet er autoritativt i en checkout — det er dét som holder pull-only-subtree- kontrakten og de byte-eksakte goldenene urørt (målt: goldens shasum-identiske før/etter, ogshared/selv urørt). Den pakkede kopien er dét som gjør wheel og container mulig uten klone (målt før: 1.0.0-wheelen bar 58 filer, null undershared/; etter: 122, hvorav 64 under_shared/, og sdist→wheel-kjeden bærer treet). Speilingen er ALDRI en redigert derivat — byte-identitet er egenskapen som lar commons-goldenene fortsatt gate den pakkede kopien. Load-bearing MÅLT (tests/test_shared_packaged_data_loadbearing.py, ekteuv buildi fixturen — pakkekonfigen er selv en søm), tre mutasjoner alle røde mot hele suiten: detach fallbacken (1 rød) · detach force-include (3 røde) · snu rekkefølgen (1 rød — ordnings-testen var grønn før fiksen; dens kontroll på at pakket kopi FINNES er det som gjør flippen målbar).- AZURE-profilen leser MILJØET sitt ved kall-tid, ikke operatørens laptop (Fase 4b): endepunktet
løses som første IKKE-TOMME av
_ENDPOINT_ENVS— vårt egetPORTFOLIO_FOUNDRY_PROJECT_ENDPOINTFØRST, deretter Foundrys injiserteFOUNDRY_PROJECT_ENDPOINT. Vårt vinner (det er dét enhver doc, recipe og test setter, så en eksport av det er en bevisst handling; en plattformverdi som stille overstyrte den ville vært uforklarlig utenfra), og fallbacken er dét som lar samme image kjøre hostet uten ekstra wiring. Presedensen gjelder VERDIER, ikke deklarasjoner — et eksportert-men-tomt eget navn faller igjennom i stedet for å skygge et ekte injisert inn i en fail-fast. Feilmeldingen navngir BEGGE: operatøren i en container og operatøren på en laptop leter etter hver sin variabel. Credential velges av samme miljø:AzureCliCredentiallokalt (konstruksjon henter INGEN token —az loginer operatørens manuelle steg),ManagedIdentityCredentialnårFOUNDRY_HOSTING_ENVIRONMENTer satt, fordi containeren ikke har noen Azure CLI og plattformen mynter den en egen Entra-identitet ved deploy. IkkeDefaultAzureCredential: Learns egen MAF-veiledning sier «prefer a specific credential such asManagedIdentityCredentialto avoid unintended credential probing» — probing ville vandret en kjede som ikke KAN lykkes der, og gjort en konfigfeil om til en treg en. Markøren leses på truthiness, ikke presence: en eksportert tom verdi er et shell-uhell, ikke et hosting-signal. Klienten eksponerer INGEN credential-attributt (målt), så testene observerer via enFoundryChatClient-recorder — med én UPATCHET arm, ellers ville de kun bevist at vi sender noe som hetercredential. Load-bearing MÅLT (tests/test_hosted_backend_loadbearing.py), fire mutasjoner alle røde mot hele suiten + grønn kontroll: detach credential-valget · presence i stedet for truthiness · detach fallbacken · snu presedensen. Fail-fast-testen ble skrevet VAKUØS først (repoets 08-09-klasse):PORTFOLIO_FOUNDRY_PROJECT_ENDPOINTINNEHOLDERFOUNDRY_PROJECT_ENDPOINT, så asserten på det injiserte navnet var oppfylt av vårt eget; den fjerner nå vårt navn før den sjekker. - Hostet inngang er en WRAPPER rundt
run_projectpå ÉN asyncio-løkke (Fase 4d):main.py→hosting.pyserverer hosting-kontrakten (port 8088/PORTpå truthiness,GET /readiness,POST /invocations, SIGTERM → exit 0) med stdlib asyncio — ALDRIas_agent()(validator, baseline-forankring, checker-gate og ledger ligger UTENFOR grafen, spike §5) og ALDRI tråder (NG1-guarden:http.servers trådvariant ville lagt samtidige kjøringer på OS-tråder der S3.3-resonnementet ikke holder; samtidige invocations interleaver som koroutiner — samme modell somrun_portfolios bølger, og/readinesssvarer mens en kjøring venter på modell-I/O, målt). Formen er MÅLT, ikke valgt: hosting-pakkasInvocationsHostServerfinnes kun i bygg som krever core>=1.13.0 (treet låser 1.9.0; eneste 1.9-kompatible bygg er en forlatt alfa med defekt metadata — importerermcpudeklarert), og et gjenbrukt bygget workflow er SINGLE-USE på 1.9.0 (målt kall-serie [2, 0, 0] — rundetaket persisterer i objektet, så gjenbruk gir TOMME kjøringer; ferskt objekt per kall er ren kontroll). Payloaden whitelistes pårun_projects signatur — ukjente felt NEKTES ved navn (400), aldri stille droppet (valg-doc §0-fella anvendt på vår egen flate);profiledefaulter tilazureKUN her (containeren har ingen lokal endpoint;run_projects egen default forblir LOCAL). Feilmapping ærlig:ValueError(pydantic-kontrakter subklasser den) → 400, alt annet → 500{error_type, error}(speilerRunFailure), og enRejectioner en VELLYKKET kjøring → 200 — det negative utfallet tilhører payloaden, aldri transporten.outbox.outcome_payloader den ENE kopien av validated/rejected-forgreningen (delt av fil-skriveren og HTTP-responsen — to kopier drifter, kø-(p)-regelen).azure.yamlvalidert GRØNN mot begge autoritative skjemaer; ingenenv:(redeklarer aldriFOUNDRY_PROJECT_ENDPOINT), ingenstartupCommand(imagetsCMDer den ene kopien av startkommandoen).git archive <tree> | docker build --platform linux/amd64 -grønn på indeks-treet. Load-bearing MÅLT (tests/test_hosting_loadbearing.py), seks mutasjoner alle røde mot hele suiten på riktig test: detach felt-mappingen · dropp ukjente felt stille · flipp 400/500 · detach azure-defaulten · detach SIGTERM-handleren · detach main.py-shimen (de to siste fanges KUN av subprosess-testen — P4-presedensen). Deploy er IKKE utført (azd-steget er operatørens); chunked request-bodies støttes ikke, og under CPU-bundne strekk (CBC-solven) står readiness — uttalt, ikke skjult. - Whitelisten må komponere med den EKTE
run_project, og artefaktene gates som RÅ TEKST (Fase 4e): alle 4d-testene gainvokeen stand-in som sluker**kwargs, så whitelisten kunne navngi et feltrun_projectikke tar — eller sende samme argument to ganger — uten at én test merket det, mens en levende container svarte 500. Sømmen errun._default_factory, ikke payloaden:client_factoryNEKTES av whitelisten med vilje (en kaller av en hostet agent skal aldri velge serverens modellklient), så å patche factory-defaulten er eneste injeksjonspunkt flaten etterlater (samme argumenttest_run_cli_loadbearinggjør formain()). Testen sender HVERT whitelistet felt og asserterer dekningen mot_ALLOWED_FIELDS, så et felt lagt til senere ikke kan gli forbi uøvet. Profilen er LOCAL, ikke den hostede defaulten: AZURE-armen slår opp et Foundry-deployment-navn i modell-mappet FØR noen klient bygges (run.pystempler provenance med det), så den kan ikke fullføre offline — containeren trenger altsåPORTFOLIO_MODEL_MAPeller et utfyltdata/model_map.json, ikke bare et endepunkt (målt her, ikke antatt).Dockerfile/azure.yamlKJØRES av ingen test (docker build/azd deployer operatør-gatet), så rå-tekst er eneste tilgjengelige gate:--platform linux/amd64(målt påkrevd, spike §1.4 — uten det arver imaget byggerens arkitektur og bygger grønt lokalt mens det ikke kan starte i skyen) + ÉN kopi av startkommandoen (imagetsCMDnavngirmain.py,azure.yamlhar ingenstartupCommand). Nøkkel-sjekkene er LINJEFORANKRET, ikke delstreng:azure.yamls egen kommentar NAVNGIRstartupCommandogenvfor å begrunne fraværet, så en substring-gate ville vært rød på prosaen den beskytter. Load-bearing MÅLT (tests/test_hosting_loadbearing.py), fem mutasjoner alle røde på riktig test og på INGEN annen (836 øvrige grønne hver gang): sendproject_idto ganger · whitelist et feltrun_projectikke tar · fjernbundle_dirfra whitelisten · fjern--platform linux/amd64· giazure.yamlenstartupCommand-nøkkel. - Stoppkriterier + budsjett-tak påkrevd ved oppstart (fail-fast, aldri ubegrenset loop).
- Group Chat maker-checker som debatt-default (IKKE Magentic, som er eksperimentell).
- To falsifiserere, samme kandidat (Steg 3/4, målbilde §2/§6): den deterministiske validatoren
gater tallene (blokkerende), checkeren gater resonnementet. Checkeren avslutter turen med en
VERDICT: APPROVE/VERDICT: REJECT — <grunn>-linje; et eksplisitt avslag blokkerer et ellers validert forslag (run_projectoverflater begge debatt-deltakere viaoutput_from=agentsog overstyrer utfallet til en checker-kilde-Rejection). Gaten er opt-in-reject (fail-open ved manglende markør), ogprovenance.validator_decisionforblir ærlig — den speiler KUN validatoren, aldri checkeren (de to falsifisererne blandes aldri). Load-bearing:tests/test_checker_gate_loadbearing.pyblir rød ved BEGGE detach-punkt (revertoutput_from, eller fjern override). Checkeren «må faktisk gate, ELLER vi slutter å kalle det maker-checker». - Informert forbedring, bundet (Steg 5, målbilde §5/§7):
generate_via_llms ytremax_attempts-løkke er ikke lenger blind — validatorens forrigeRejection.reasonmates inn i neste forsøks prompt (_build_messages(prior_rejection=...)), så proposeren korrigerer i stedet for å gjenta. Kun den mest-nylige falsifiseringen (last, ikke akkumulert), kun grunnen (aldri forrige proposal-JSON), under EKSISTERENDE tak (meter.tick_round+max_attempts— ingen ny løkke; «forbedre til god nok» uten tak er forbudt). Eneste per-forsøk-falsifiserer her er validatoren; å seede generering med checker-kritikken er run-nivå og separat scoped (IKKE bygget her) — så koden påstår ikke mer enn den gjør. Load-bearing:tests/test_step5_refine_loadbearing.pyblir rød når reason-injeksjonen detaches (utfallet flipper aldri + reason-verbatim-asserten faller); kontrollen beviser at løkka forblir bundet. - Falsifiserings-historikken FORLATER generate-løkka som typet returverdi (Steg 5, del 2):
generate_via_llmreturnererGenerationResult(outcome, refinements)— ikke lenger bareValidatedProposal | Rejection. Før dette forbrukte løkka hverRejectioninternt (last) og DROPPET den, så Steg 5 var det ene av åtte steg uten observerbart utfall. Returverdi, ikke out-parameter/callback: en returnert verdi kan ikke bli stille tapt av en kaller som glemmer å sende en samler, og mypy tvinger hvert kallsted til å ta stilling.refinementsbærer KUN avvisninger som faktisk ble matet tilbake i et senere forsøks prompt — ved uttømt budsjett ER den siste avvisningenoutcome, den informerte ingenting, og å telle den med ville vært dobbeltføring (en «samle alt»-implementasjon består den positive testen og faller på kontrollen). Taket er URØRT:max_attempts+meter.tick_roundstår, oglastdriver fortsatt prompten alene (prompt-veksten er uendret).run.pyakkumulerer på tvers av_evaluate-kallene, så_evaluate_mandateer urørt;RunResult.refinementser defaultet (coverage-presedensen), og med mandat er den KONKATENERT på tvers av tiltak, ikke nøklet per tiltak (uttalt ærlighets-grense).scripted_factorytar nåstr | reply_selectorper rolle, så simuleringens proposer korrigerer seg innholds-nøklet uten en andre scriptet kropp. Load-bearing MÅLT (tests/test_step5_history_loadbearing.py), fire mutasjoner: detach returneringen · samle-alt · detach run-wiringen · reverter simuleringens proposer til konstant svar. - Lang/async fil-løkke (Steg 7, målbilde §3/§7):
run_project(verdict_dir=...)er den lange tilbakemeldings-tidsskalaen — en ekspert/persona dropper en verdict-fil (vanlig JSON, RAW-laget per §10 R2) i en inbox-mappe ETTER en kjøring, og en separat, senere kjøringload_verdicts_from_dir→store.addmerger den inn FØR Steg-1-folden (ingen endring i folden), så dommen når neste hypotese. Rolledeling (§3, ufravikelig): systemet LESER mappa; eksperten/personaen SKRIVER den —run_projectpersisterer ALDRI sin egen fangede dom tilbake (det er outbox/Steg 8). Merge, aldri erstatt (run_portfolio-tråding intakt); tolerant last (manglende mappe / fremmede / halvskrevne filer hoppes over, ikke raises — RAW-lag, kontrastokf.load_ir_projections fail-fast);idleses verbatim, re-mintes aldri.write_verdicter den offentlige authoring-primitiven (persona/test + framtidig Steg 8), men wires IKKE inn irun_project. Load-bearing:tests/test_step7_async_loop_loadbearing.py— en dom droppet etter Run A MÅ nå Run B's prompt (Run B bruker FERSK store → overføringen er fil-løkka, ikke in-memory-carryover); tom-inbox-kontroll beviser kausalitet. Markør = realiseringsverdi som finnes ingen steder i bundelen (ikke frøets 0.82). - Gated wiki-promotering (Steg 8, målbilde §3/§6/§7): når en ekspert/persona GODKJENNER et
utfall, løfter
verdicts.promote_verdictdet fra RAW output-laget inn i kontekst-laget (OKF-bundelen) som entype: verdict-konseptfil, navigerbar av neste kjøringsseed_store_from_bundle. Gaten er fail-closed: en ikke-godkjent dom (decision ∉ {approved, approved_with_adjustment}) raiserPromotionRefusedog skriver/linker INGENTING — kun menneske/persona-godkjent kunnskap når wikien, aldri rå agent-output (selv-forurensning). Provenance-stemplet (hvem/eksperiment/når;timestamper påkrevd keyword, ingen wall-clock-default → deterministisk). OKF-skriveren bor iokf.pyog er ren stdlib (D7-portabel, MAF-fri — håndhevet avtest_okf_is_maf_free); navigasjon følger KUN index-cross-links, såpromote_verdictlinker filen iindex.mdvia en NØYTRAL label (ellers lekker signalet inn iindex_summary→bundle_contextutenom gaten). R4 = valgfri+gated:promote_verdicter en offentlig opt-in-primitiv, wires IKKE inn irun_project(speilerwrite_verdict— systemet leser; gaten/personaen promoterer). Ærlighets-grenser: promotert fil er MINIMAL (læringssignal kun somdescription/body-prosa, reproduserer ikke seedens strukturerterealization_rateo.l.); id = læringsnøkkel, så to godkjenninger om samme kandidat deler filnavn (last-write-wins, somwrite_verdict) — wikien vokser én kuratert fil per distinkt kandidat, ikke per dom-hendelse. Load-bearing-trio (tests/test_step8_promotion_loadbearing.py): gaten avviser ikke-godkjent dom (RØD uten gate); godkjent dom er navigerbar (RØD nårlink_in_indexdetaches); promotert signal holdes ute avbundle_context(RØD når en beskrivende index-label lekker det inn). Index-RMW er ikke-atomisk (enprosess-MVP). - En dom nøkles på SIN kandidat, ikke bundelens ene IR-projeksjon (S3.2):
seed_store_from_bundleleser hvertype: verdict-fils EGNE strukturelle felt fra frontmatter (affected_codes/measure_type/claimed_saving_nok); mangler de, faller nøklingen tilbake tilbundle_candidate_features— så hver pre-S3.2-seed står uendret (fallbacken er BÆRENDE: fjernes den, brekker step1-suiten ved collection). Uten dette kollapset en bundle med dommer om flere kandidater dem på ÉN nøkkel, og en dom om kandidat B scoret perfekt strukturell match mot kandidat As query. ALLE TRE felt eller ingen: en delvis erklæring raiserVerdictFrontmatterErrori stedet for å slås sammen med bundle-kandidaten — sammenslåingen ville myntet en nøkkel som tilhører INGEN av kandidatene. Validering, ALDRI reparasjon (speilerwrite_concept_file); den tolerante hopp-over-regelen hører til RAW-innboks-laget.claimed_saving_nokparses medjson.loads— SAMME literal-regel IR-projeksjonen gikk gjennom — og skrives tilbake somstr()av råverdien, fordi_mint_idhasher den (30000≠30000.0; en normaliserende skriver ville splittet én kandidats signal på to id-er).promote_verdictskriver de tre feltene, så en promotert dom ikke utgir seg for målbundelens kandidat; nøkkelen er signal-fri, så Steg-8s no-leak-egenskap står. Semantikken er bestemt HER — commons' seeding-regel (method-spec§3 Steg 1) hadde ikke kommet; D7-speiling forblir ÅPEN. Load-bearing MÅLT (tests/test_step32_multicandidate_loadbearing.py+test_step8_promotion_loadbearing.py), fem mutasjoner alle røde: detach per-dom-nøklingen · detach feltenepromote_verdictskriver · gjør en delvis/uparsebar nøkkel tolerant · normaliser magnituden ved skriving · fjern fallbacken (kontroll). - Den deterministiske gaten er FORANKRET i prosjektets faktiske kostbaseline (S4.0, F3/F8): før
S4.0 resonnerte HVER stage kun om tall forslaget selv oppga, så en internt konsistent hallusinasjon
klarerte hele gaten.
validate_proposal(..., baseline=...)avstemmer nå hvertaffected_itemmotCostBaseline(ir.py) i en stage 0 — FØR løseren (billigst, og den eneste som skiller en oppdiktet linje fra en ekte; å bruke en CBC-solve på tall som ikke tilhører prosjektet er arbeid på et krav som uansett ikke kan valideres). To uavhengige avvisninger: ukjent kostkode, og ekte kode medquantity/unit_costutenfortolerance(default 5 %, konfig) relativt til BASELINE-verdien. Validering, ALDRI reparasjon — forslaget avvises, aldri stilltiende korrigert til baselinen. Argumentet er VALGFRITT (None= pre-S4.0-oppførsel), men begge run-stier SETTER det: road-stien fraproject.cost_items(alltid — estimatet ER prosjektet), bundle-stien KUN når bundelen shippercost-baseline.json(load_optional_cost_baseline) — en pre-amendment-bundle er legitimt uforankret, og det er dét som holder commons-goldenene byte-identiske. Toleransen stopper ved fravær: en baseline som FINNES men er malformed raiser på BEGGE loaderne (å lese korrupt som «ingen baseline» ville gitt en uforankret gate i forkledning — samme resonnement somread_spend). F8: metode-cap-en slås opp iMETHOD_CAPS-registeret (måletype→brøk, injiserbart), ikke motenergy_efficiency-literalen — en andre metode er nå data, ikke en redigering av validatoren. Format- og toleranse-semantikken er bestemt LOKALT (commons-amendmentet D-A pkt. 2 kom aldri, som i S3.2); D7-speiling ÅPEN. Load-bearing MÅLT (tests/test_s40_cost_baseline_loadbearing.py), seks mutasjoner alle røde: detach avstemmings-stagen · detach magnitude-toleransen · detach road-wiringen · detach bundle-wiringen · ignorer det injiserte cap-registeret · gjør den valgfrie loaderen tolerant. - Globalt token-tak håndheves FØR kall, aldri bare etterpå (S3.4, F10):
PortfolioBudget+PortfolioMeterer ÉN ledger over hele porteføljepasset (og — seedet avread_spend— på tvers av pass), mens per-runBudget/TokenMeterer uendret. Taket har tre tenner, med hver sin jobb: (1) oppstartsnekt — en rest som ikke kan finansiere én kjøring raiserBudgetRefusedFØR noe lastes (et pass som har råd til null prosjekter er en caller-feil, ikke et resultat); (2) wave-assembly — et prosjekt som ikke kan finansieres blir ALDRI STARTET, og passet stopper strukturert (budget_stop+stopped_early, fullførte runs bevart). Aldri-startet er poenget: et ufinansiert prosjekt som bare avbrytes har allerede kostet kall. Fordi hele bølgen sjekkes mot SAMME før-bølge-rest, reserverer admission hver members krav — ellers overforplikter en bølge av k taket med inntil k kjøringer; (3) pre-call-guard iBudgetMiddleware— et kall resten ikke kan betale for NEKTES i stedet for å gjøres (post-charge-sjekken består: ekte usage kjennes først etterpå, så guarden stopper NESTE kall, aldri det som er i lufta).budget_stoper et EGET felt, aldristop_reason: et mål-stopp er suksess, dette er ressurs-utmattelse — å slå dem sammen ville gjort «vi stoppet» uleselig.record/checker SPLITTET iPortfolioMeterfordi tokens leverandøren allerede har fakturert må nå ledgeren selv når samme charge bryter run-taket. Spend-fila er vår EGEN regnskapstilstand:read_spendraiser på korrupt innhold (kontrast det tolerante RAW-inbox-laget — å lese korrupt som null ville gitt tilbake et allerede brukt budsjett), ogwrite_spendtar et PÅKREVDstamputen wall-clock-default (byte-determinisme, speilerpromote_verdict).portfolio_meterogmeter_factoryer gjensidig utelukkende — en factory-meter er ubundet, så begge sammen ville gitt et pass som SER capped ut uten å være det. Load-bearing MÅLT (tests/test_portfolio_budget_loadbearing.py+tests/test_budget.py), seks mutasjoner alle røde: detach wave-sjekken · detach pre-call-guarden · detach bølge-reservasjonen · sjekk run-taket før global kreditering · detach oppstartsnekten · gjørread_spendtolerant. BudgetExceededs tre felt beskriver ÉN og samme ledger (kø-(y)):kind/limit/observeder ett strukturert stopp-event, ogobservedvar det udefenderte tredje feltet — MÅLT: fire av fem raise-steder (TokenMeter.charge,tick_round, og BEGGE armene iexhausted()) kunne rapportere hvilken som helst verdi uten at 621 tester merket det; barePortfolioMeter.checkvar dekket. Fellen som skjulte det:spikes/_harness.py:41har sin EGEN kopi avBudgetExceeded/TokenMeter, såspikes/test_harness.pysobserved-assert dekker IKKE den shippede modulen — produksjonenstick_roundhadde null direkte test.exhausted()er det ENESTE stedet som VELGER ledger (S3.4-guarden), så en refusal som sierportfolio_tokensmens den rapporterer runets eget forbruk ville villedet enhver leser. Testene bygges medobserved != limit: ved nøyaktig-uttømt sammenfaller de to, og en test skrevet der kan ikke skille dem — den ville passert på en implementasjon som ekkoet taket tilbake som forbruket. Ingen defekt funnet i verdiene selv (kontrast (x)/(p)): trippelen var koherent alle fem steder, gapet var rent dekning. Load-bearing MÅLT (tests/test_budget.py), ni mutasjoner alle røde: femobserved-mutasjoner (inkl. kontrollen) + fire ekko-mutasjoner.- Pengetall kvantiseres i ÉN orden, fra ÉN kilde (kø-(p)):
ledger.to_oreer rammeverkets ENE NOK→øre-konvertering (Decimal, ROUND_HALF_UP), og den brukes per pengebeløp — deretter summeres HELTALL.run.pyimporterer den; aldri en egen kopi (to kopier av en penge-konvertering drifter, og en driftet kopi setter de to sidene av en mål-sammenligning på hver sin skala — S4.0sREPLIES-presedens). Før dette summerterun.pys mål-baselineProject.total_cost-FLOATS og kvantiserte totalen ÉN gang, mensSavingsLedgersummerte per-kandidat-heltall — og de to ordenene møttes i nøyaktig ETT punkt:_goal_limit_if_reached, der et prosent-mål avgjør om et porteføljepass stopper tidlig. Målt divergens: tre linjer à60000.005NOK er18000003øre kvantisert først, men18000001summert først (float-drift til180000.01499999998) — nok til å vippe et mål. Kvantiser-først valgt fordi hverCostItemER et beløp (S4.0 gjorde per-linjequantity/unit_costtil validatorens grunnsannhet), og fordi heltallsaddisjon er assosiativ → rekkefølge-uavhengig under D-D-bølgemodellen, som float-folden ikke er. To kallsteder, ikke ett: portefølje- og per-prosjekt-baselinen er separate, og en fiks på bare den ene OVERLEVDE hele suiten (målt). Load-bearing MÅLT (tests/test_money_quantization_loadbearing.py), fem mutasjoner alle røde: detach portefølje-baselinen · detach per-prosjekt-baselinen · gjeninnfør en privat kopi irun.py· endre avrundingsmodus · larealizegå utenomto_ore. Ærlighets-grense:sum_claimed_saving_nok(run.py:_aggregate) er BEVISST urørt — et float-NOK-rapportfelt som aldri kvantiseres og aldri sammenlignes mot ledgeren, altså utenfor ordens-defekten. - Kostnadsdisiplin: utvikle primært på lokal profil (gratis); Foundry/Azure (privat tenant finnes) kun til målrettet, minimal verifisering; billigste modeller + små syntetiske data + harde token-tak. Ingen tunge test-kjøringer.
- Offline simulering = primært metode-bevis (kostnadsdrevet, erstatter §11.8): operatøren kjører
IKKE MAF mot ekte modell (verken Azure/Foundry eller Ollama — API for begge repoene er for kostbart
privat).
portfolio_optimiser.simulationdriverrun_projectmed en SKRIPTET syntetisk chat-klient (ScriptedChatClientpåOpenAIChatCompletionClient— IKKE bareBaseChatClient, ellers no-op-erBudgetMiddleware) over to kjøringer adskilt av en promotering, og viser at læringssløyfa lukkes: Run A's godkjente persona-dom (markør fraværende fra bundelen) →promote_verdict→ re-seed → Run B's hypotese-prompt bærer markøren (tom-wiki-kontroll på Run A beviser kausalitet). Ærlighet (§1, ufravikelig): beviser plumbing + deterministisk ryggrad + at dataflyten lukkes — IKKE at en levende LLM ville produsert forslaget/dommen (skriptede stand-ins). Den genuine modell-atferd-sammenligningen lever på Claude-SDK-siden (minimal API-kjøring). Skriptet klient = MAF-side stillas, IKKE delt (shared/forblir framework-nøytralt). Kjøresuv run python -m portfolio_optimiser.simulation. Load-bearing:tests/test_simulation_loadbearing.pyblir RØD når promoteringen detaches. - Demoen KJØRER begge tidsskalaer, og de bæres av HVER SIN markør (P1/S1.a): Steg 7-linja sa
«lang fil-løkke», men
simulate_learning_loopkalterun_projectUTENverdict_dir— dommen kom som funksjonsargument (verdict_input, den KORTE i-kjøring-fangsten). Nå skriver en ekspert en faktisk fil (write_verdict) i en innboks MELLOM kjøringene, og Run B fårverdict_dir=. Hvorfor en ANDRE markør og ikke persona-dommen gjennom innboksen: Steg 7 (innboks) og Steg 8 (promotering) er to ULIKE mekanismer som begge ender i Run B's hypotese-prompt — med én delt markør kunne hver av dem båret den alene, ogtest_simulation_loadbearing.pys promoterings-assert ville stått GRØNN med promoteringen detached, altså blitt vakuøs.simulate_learning_loopraiser derforValueErrornårinbox_marker == marker. Innboksen ligger VED SIDEN AV bundle-kopien, aldri inni: en dom-fil inne i bundelen når neste kjøring som navigerbar kontekst, som er en annen mekanisme i denne sin forkledning. Sentinel-id(aldri myntet) —_mint_idhasher kandidat- featurene, så en myntet id kolliderer med den promoterte dommens, ogVerdictStore.adder first-write-wins per id. Load-bearing MÅLT (tests/test_step7_demo_inbox_loadbearing.py), tre mutasjoner røde + grønn kontroll: detachverdict_dir=· la Run B lese en TOM mappe · sett innboks-markøren til en verdi som FINNES i bundelen. - Det skriptede manuset nøkles på PROSJEKT-ID-en, og det er MÅLT:
scripted_proposer(candidates)bygger simuleringens proposer fra etScriptedCandidate-register, så et nytt prosjekt er en data-oppføring (demo-uke-plan §4 risiko 2) — ikke et andre håndskrevet manus. Hvorfor ikke kostkode/tiltaksnavn: to prompt-former når selectoren — debatt-prompten bærer hele bundle-konteksten, mens genererings-prompten (generate._build_messages) bærerProject: {id} - {name}pluss debatt-outputen som kontekst, altså selectorens EGET tidligere svar. Kostkode og tiltaksnavn står derfor i genererings-prompten kun fordi manuset selv la dem der; å nøkle på dem ville nøklet manuset på sin egen output. Prosjekt-ID-en er den ene identifikatoren BEGGE former bærer og som RAMMEVERKET stempler. Validering, ALDRI reparasjon: null treff — eller mer enn ett — raiserScriptedCandidateError; et default-svar ville besvart et uregistrert prosjekt med et ANNET prosjekts tall, som på skjermen er umulig å skille fra en riktig kjøring, og en tvetydig blob er et DATA-problem som skal falle på generalprøven, ikke avgjøres av register-rekkefølgen.flip_keyMÅ være fraværende fra bundelen (ellers bærer forsøk 1s prompt den allerede).simulate_learning_looptarproject_idved siden avbundle_dir. Load-bearing MÅLT (tests/test_content_keyed_script_loadbearing.py), fem mutasjoner alle røde + grønn kontroll: detach nøklingen · én global flip-key · fallback ved ukjent prosjekt · første-treff ved tvetydighet · detachproject_id-argumentet. Flip-key-testen ble skrevet om under målingen — første form asserterte på FØRSTE register-oppføring, der «den matchede kandidatens nøkkel» og «candidates[0]s nøkkel» sammenfaller; den kunne ikke skille de to implementasjonene. - Demoen kjører FORANKRET, og baselinen DERIVERES fra manuset (P4 pkt. 0): før dette regnet
validatoren i demoen kun på tall forslaget selv oppga — S4.0-forankringen aktiveres bare når
kunnskapsbasen shipper
cost-baseline.json, og ingen bundle undershared/har den. Reserven kan aldri få fila DER (pull-only subtree + kriterium 8 krever goldenene byte-uendret), men det er en plasserings-begrensning:materialize_anchored_bundleKOPIERER bundelen og legger fila til utenforshared/, og kjørestien (run.py→load_optional_cost_baseline) er da NØYAKTIG samme søm en levert bundle ville brukt. Retningen på avledningen er bærende: reservens tall er syntetiske, så manus-registeret er eneste grunnsannhet —baseline_from_scripted_candidateavleder i KODE, aldri en andre håndskrevet kopi av de samme tallene (to kilder drifter, og drift er nøyaktig det 10 %-prøven modellerer). På GO-dagen snus retningen (plan P3 b: registeret skrives FRA levert fil). Begge skriptede svar må oppgi SAMME kostlinjer (ValueErrorellers): var de ulike, ville hypotese #1 blitt avvist av stage 0 istedenfor av P90 — samme REJECTED-linje på skjermen, annen mekanisme. Forankringen er usynlig i alt annet stdout (målt: eneste diff mot uforankret er KUNNSKAPSBASE-blokka), derfor printes den erklærte baselinen, og derfor erprovenanceet PÅKREVD argument til_baseline_lines— kallstedet som velger bundelen er det eneste som vet hvor tallene kom fra. Load-bearing MÅLT (tests/test_anchored_reserve_loadbearing.py), fem mutasjoner røde + grønn kontroll: detach main-wiringen · detach fil-skrivingen · la filnavnet drifte · detach to-svars-enigheten · returner et literal i stedet for det avledede. Målingen felte TESTEN først (samme klasse som 08-06): «ingen kostbaseline erklært» INNEHOLDER «kostbaseline erklært», ogENERGI-TOTAL-ELstår allerede i Steg 2-linja — begge assertene overlevde detach-mutasjonen. De to grenene deler nå ingen ordlyd. - Demoens stderr: rund-taket dempes,
ExperimentalWarning-paret PINNES (P4 pkt. 2): målt 08-09 var stderr seks linjer.quiet_expected_round_cap_notice()dropper KUN «reached max_rounds=…; forcing completion» — en hendelse demoen selv provoserer (maker/checker kjører til taket) — via et filter på den EMITTERENDE loggeren (ROUND_CAP_LOGGER, lest ut av MAFs kilde). Logger-filtre gjelder kun loggeren posten ble logget GJENNOM; en forfars filtre konsulteres aldri. Filteret installeres imain(), ALDRI ved import — en bibliotek-modul skal ikke omkonfigurere loggingen til en konsument. De toExperimentalWarning-linjene dempes IKKE: de fyrer mensportfolio_optimiser/__init__.pyimportererrun→agent_framework, altså alltid FØRsimulationsin egen importblokk, under BEGGE kjøreformer — så å dempe dem ville krevd et warnings-filter inne i bibliotekpakken, dvs. at rammeverket bestemmer hva MAF får si til enhver konsument. En wrapper bak konsoll-kommandoen ble avvist av en andre grunn: da ville de to kjøreformene skrevet ULIK stderr, og en byte-fasit ville pinnet kommandoen i stedet for programmet. Dempingen er smal ved konstruksjon — nøklet på meldingen, ikke loggeren — nettopp så pkt. 3-pinnen fortsatt kan felles av en NY advarsel. Load-bearing MÅLT (tests/test_demo_stderr_quiet_loadbearing.py), fem mutasjoner røde + grønn kontroll: detachmain()-kallet (subprosess-testen er ENESTE som fanger det — de tre filter-testene installerer filteret selv) · la filteret droppe alt · installer ved import · pluss de to entry-point-mutasjonene. - Demo-transkriptet er sjekket inn som fasit, og masken er SPANN-avgrenset (P4 pkt. 3):
kriterium 6 er selv-identitet — to kjøringer av en REGREDERT demo er like enige som to kjøringer av
en riktig, så fasiten må forlate prosessen.
tests/golden/demo-transcript.stdouter stdout ORDRETT (målt byte-identisk over kjøringer OG i fersk klon), og er derfor også demoens abortsti: feiler live-kjøringen, ER fila transkriptet.….stderrer normalisert på nøyaktig to MÅLTE miljø-spann —site-packages-prefikset og temp-katalogen bak(arbeidskopi: …), derpo-sim--prefikset holdes SYNLIG fordi det er en egenskap ved programmet (mkdtemp(prefix=…)), ikke ved miljøet; pinnet stderr er fire linjer. Masken må ikke kunne vokse: P4 pkt. 2 betalte for at en NY advarsel fortsatt når stderr, og en normalisering som maskerte hele linjer ville opphevet det i ett trekk — derfor ertest_normalisation_does_not_mask_a_new_warningkontrollen som forbyr det (MÅLT: en droppende normaliserer med fasiten regenerert under seg holder BEGGE likhets-testene grønne og felles kun av kontrollen).PYTHONIOENCODINGpinnes, ellers måler sammenligningen operatørens locale i stedet for programmet. Regenerering er en beslutning, aldri rydding — fasiten kan ikke bevise sin egen kjøring, den pinner outputen P3-kriteriene ble målt mot. - Frø-setningen AVLEDES fra kjøringen (P4 pkt. 4): demoen sier høyt hvor Kjøring B's tidligere
dommer kommer fra, og splitten (
_verdict_origin_line) regnes ut — linja rett over printer allerede antallet, så en håndskrevet «én av tre» ville vært den andre kopien som drifter (samme regel som pkt. 0-baselinen), og ville blitt sagt uendret etter at en framtidig bundle shipper en ANDRE frøsatt dom. Klassifisereren er de to markørene demoen alt sporer; den hviler på at den frøsatte dommen bærer INGEN av dem, som MÅLES på levert bundle. Planens forhåndsskrevne ordlyd var FEIL mot levert innhold («én av de TO») — målt henter Kjøring B TRE: én fulgte med kunnskapsbasen, to er demoens egne (én per tidsskala). Load-bearing MÅLT (tests/test_p4_honesty_sentences_loadbearing.py+ golden-transkriptet), fem mutasjoner alle røde- grønn kontroll: ett byte i en stdout-linje · detach dempingen · over-normaliser stderr · literal splitt (fanget av INGENTING i 800 tester bortsett fra skille-testen) · detach frø-setningens print.
- Delt ekspert-persona som Agent Skill (§8, framework-nøytral): ekspert-reviewer-personaen bor i
shared/skills/expert-reviewer/(SKILL.md+references/example-verdict.json) og er den ENE delte artefakten begge stacker instansierer reviewer-en fra.shared/forblir REN DATA — MAF-siden leser den viaportfolio_optimiser.persona.load_persona_example(call-time, fail-fast), Claude-SDK- søskenet med sin egen loader mot samme JSON. Dette AV-STUBBER simuleringen: persona-dommen (decision- rationale + sporet markør) hentes nå fra artefaktet ved call-time, ikke en inline-literal — så
personaen er genuint konsumert og kan ikke råtne stille. Decision er binær (
approved/rejected—FeedbackContractrun-stien tar;approved_with_adjustmentavvises der, bor kun i bundle-seedens frontmatter + promoterings-gaten); realiseringskorreksjonen lever i rationale-prosaen, ikke et tredje enum. SKILL.md-prosaen nevner ALDRI en konkret framework (maf-guarden er import-formet). Load-bearing- trio (tests/test_persona_skill_loadbearing.py): struktur+framework-nøytralitet (RØD på framework- import), eksempelet er gyldig pipeline-input inkl.FeedbackContract(RØD på skjema-/kontrakt-drift, på en throwaway-kopi — aldri den git-tracked fixturen), og sim-ens markør følger artefakt-fila (RØD i det øyeblikk personaen re-inlines).
- rationale + sporet markør) hentes nå fra artefaktet ved call-time, ikke en inline-literal — så
personaen er genuint konsumert og kan ikke råtne stille. Decision er binær (
- STATE.md er local-only (gitignored). Voyage session-state er efemert; STATE.md er kanonisk kontinuitet.
- Prosess: Voyage-plugin (
/trekbrief → /trekplan → /trekexecute → /trekreview) per større fase.
Communication patterns
When linking to local files in responses, use named markdown links — [Human-friendly name](file:///absolute/path), never bare file:// URLs or autolinks <file://...>, always absolute paths (never ~/ or relative), one bullet per file when there are several. (Bare file:// URLs render only the first as clickable across multiple lines; named links stay independently clickable.)