--mandate mangler i report_forbidden. Maalt under fase 2, rapportert her, ikke fikset: utenfor ordren, og eldre enn den. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
9.7 KiB
Hvor kommer forslaget fra? — måling før S7b
Dato: 2026-09-03 · Ordre: 20260903T014107Z-72282048-from-.claude (S7b-forberedelse), fase 1
Status: måling. Ingen kode endret av dette dokumentet.
Bruksscenarioet ordren stiller opp er «dokumenter + konkret oppgave → forslag». Ordren beskriver
hindringen slik: forslaget leses fra en håndskrevet validator-input.json. Dette dokumentet måler
den påstanden mot koden før noe bygges på den, og legger fram tabellen ordren ber om: hvert felt i
SavingsProposal → hvor det kommer fra i dag → om det kan avledes deterministisk fra mandatet, den
deriverte kostbaselinen eller den rutede tilnærmingen.
1. Premisset er felt: symptomet stemmer, diagnosen ikke
Ordrens symptom er bekreftet. En kunnskapsbase uten validator-input.json nekter, og nekten kommer
før første modellkall:
$ run_project('P1', docs_dir=<base>, bundle_dir=<base>, live_dry_run=True)
FileNotFoundError: IR projection not found in bundle: 'validator-input.json'
Diagnosen stemmer derimot ikke. Forslaget har aldri blitt lest fra den fila. Målt i kilden:
SavingsProposalkonstrueres avgenerate._parse_irfra modellens svar (generate.py,generate_via_llm→_fetch_parsed→_parse_ir). Ingen kodesti leser et forslag fravalidator-input.json.run._project_from_bundle(run.py:444) leser IR-projeksjonen til nøyaktig to ting: en fail-fast av kjøringensproject_idmot basens, og et fallback for prosjektnavnet — navnet hentes primært fratype: project-konseptetstitle-frontmatter, og IR-en brukes ikke til det i det hele tatt.
At validator-input.json er en komplett håndskrevet SavingsProposal (den er det — se
shared/examples/tunnel-hauglia/validator-input.json) gjør den til en fasit ved siden av
kjørestien, ikke til kjørestiens inngang. Det som faktisk blokkerer, er at fila er en påkrevd
inngangsbetingelse for prosjekt-identiteten.
Skillet er ikke pedanteri: det avgjør at S7b er to sømmer, ikke én.
| Hva som er i veien | Hva som må til | |
|---|---|---|
| Søm 1 — prosjekt-identitet | load_ir_projection er fail-fast påkrevd på tre kallsteder |
fravær tolereres; en fil som FINNES nekter fortsatt ved avvik |
| Søm 2 — kandidat-kilde | den eneste kandidat-kilden er generate_via_llm |
en deterministisk kilde ved siden av, eksplisitt bestilt |
1.1 Nevneren for søm 1: tre kallsteder, ikke ett
src/portfolio_optimiser/run.py:453 _project_from_bundle (prosjekt + fail-fast)
src/portfolio_optimiser/run.py:1662 run_mandate_across_bundles (dispatcherens rutingsnøkkel)
src/portfolio_optimiser/verdicts.py:507 bundle_candidate_features (ExpeL-nøkkelen)
Alle tre er målt til å reise samme FileNotFoundError mot en base uten fila. De er ulike i når de
fyrer:
run.py:453fyrer på hver bundle-kjøring — dette er nekten ordren observerte.verdicts.py:507fyrer bare når storen er ikke-tom (Steg-1-folden er gated på det), altså først når basen har lært noe. En kjøring uten dommer merker den ikke.run.py:1662er den som betyr mest for S7b: multi-base-dispatchen tar bevisst ingenproject_id-parameter, fordi hver bases prosjekt leses fra DEN basens egen IR-projeksjon (CLAUDE.md, multi-base-raden). En ingestert base uten fila kan derfor ikke rutes i det hele tatt — og etter S7a-3 finnes det en åpenbar erstatning: basens erklærtebundle_id.
1.2 Det som IKKE er i veien
derive_cost_baseline trenger ingen validator-input.json. Målt mot den samme base som nektet over,
og mot MAJOR-4-fixturparet:
| base | utfall |
|---|---|
| syntetisk to-linjers base (uten IR-projeksjon) | A1: 100 × 50, A2: 20 × 250 |
tests/fixtures/k2-prisskjema-SYNTETISK |
21.1: 1250 × 850, 24.2: 48000 × 42.5, 36.1: 4 × 187500 |
tests/fixtures/k2-prisskjema-uprisert-SYNTETISK |
CostBaselineDerivationError (uprisert skjema nekter i sin helhet) |
Forankringen er altså allerede på plass for et ingestert korpus. Det som mangler, er en kandidat å forankre.
2. Tabellen: felt → kilde → skjønn
SavingsProposal har fem felt (målt: ['project_id', 'measure', 'affected_items', 'claimed_saving_nok', 'assumptions']).
| felt | hvor det kommer fra i dag | (a) mandatet | (b) derive_cost_baseline |
(c) rutet approach | skjønn? |
|---|---|---|---|---|---|
project_id |
modellens svar, med project.id som default (_parse_ir) |
— | CostBaseline.project_id — men den er et påkrevd argument, ikke en avledning |
— | NEI — kjøringen vet allerede hvilket prosjekt den kjører |
measure |
modellens prosa | — | — | Approach.label (ev. + description), VERBATIM |
NEI — men se § 2.1 |
affected_items |
modellen velger både koder og tall | — | items[code] → (quantity, unit_cost) for hver kode |
approachen må navngi kodene | JA hvis kodene ikke er navngitt → må nekte, ikke gjette |
claimed_saving_nok |
modellens tall | ingen plass i dag | kan ikke avledes — baselinen er en KOSTNAD, ikke en besparelse | ingen plass i dag | JA → må komme fra eksperten, eller nekt ved navn |
assumptions |
modellens bånd | — | kan ikke avledes — én pris, ikke et bånd | valgfritt eksplisitt | JA → tomt er lovlig, men se § 2.2 |
Målt: Approach bærer ['id', 'label', 'description', 'bundle_id'] og Mandate bærer
['objective', 'approaches', 'allow_own_proposals', 'success_criteria']. Ingen av dem har et
tallfelt. De to feltene tabellen krever av eksperten — anslaget og kostkodene — finnes altså ikke,
og må legges til i mandate.py.
2.1 measure er ikke bare prosa — den er en oppslagsnøkkel
validate_proposal stage 5 slår opp METHOD_CAPS[proposal.measure] (validator.py:248). Registeret
har i dag én oppføring, energy_efficiency → 0,15. En measure satt til ekspertens label treffer
den derfor aldri, med mindre labelen ordrett er energy_efficiency.
Det er ikke en defekt, men det er en grense som må sies: en mandat-avledet kandidat dømmes av den
generiske 30 %-cap-en (MAX_SAVING_FRACTION), ikke av en metode-spesifikk. Metode-cap-en er
opt-in-data (F8), og en mandat-vei som skal treffe den, må la eksperten navngi metoden — det er en
egen beslutning, ikke noe denne veien kan avlede.
2.2 Tom assumptions gjør den stokastiske falsifisereren inert
validator._monte_carlo faller tilbake på item.unit_cost for hver kode uten bånd. Uten bånd i det
hele tatt er alle samples identiske og P10 == P50 == P90. Det er nøyaktig faren
generate._ASSUMPTIONS_WIRE_NODE ble skrevet for å unngå på LLM-stien.
Konsekvensen for en mandat-avledet kandidat: stage 3 rapporterer fortsatt persentiler, men de bærer
ingen usikkerhet. Kandidaten dømmes reelt av stage 0 (avstemming mot baselinen), stage 2/4b (CBC-solve
mot 30 %-cap-en) og pydantics claimed ≤ total. Dette skal stå som en uttalt ærlighets-grense, ikke
skjules bak at persentiler blir printet.
2.3 Hva som gjør en mandat-kandidat ikke-triviell å gjøre grønn
Kopieres affected_items fra den deriverte baselinen, avstemmer stage 0 med 0 % avvik ved
konstruksjon. Det er verdt å si høyt, fordi det betyr at en test som bare sjekker «validatoren kjørte»
er grønn ved konstruksjon — repoets vakuøs-gate-klasse.
Den levende beskrankningen er anslaget: MAX_SAVING_FRACTION = 0.30. Et anslag over 30 % av de
berørte linjenes total blir avvist av den ordinære gaten, uten at kandidat-veien vet noe om det.
Det er den skarpe diskriminatoren en test må bruke for å vise at kandidaten går gjennom
validate_proposal og ikke rundt den.
3. Hva som følger av dette
Fase 2 av ordren bygger på tre beslutninger tabellen tvinger fram:
- Anslaget og kostkodene bor på
Approach, ikke påMandate.mandate.settlesier selv at tilnærminger er ALTERNATIVER og aldri summeres, så ett tall på mandatnivå ville vært tvetydig over N tilnærminger.Mandate-docstringen nekter å bære målet (det eiercontracts.GoalContract) — et per-kandidat-anslag er en annen ting enn et mål for kjøringen, og skillet må skrives ned der feltet innføres, ellers leses det som nøyaktig den driften modulen forbyr. - Hver ny nekt subklasser
ValueError—MandateRoutingError/BundleIdMismatch/CostBaselineDerivationError-presedensen — så den lander på CLI-ens nekt-tuppel og hostings 400-arm, aldri krasj-kanalen. allow_own_proposalshar ingen deterministisk motpart. En «egen» kandidat krever en modell. Den raden kan derfor ikke fylles, og skal rapporteres somnot_evaluatedmed en grunn ved navn —ApproachOutcomes egen regel: en rad som utelates er umulig å skille fra en tilnærming ingen bestilte. Å nekte hele kjøringen ville vært feil, siden feltet defaulter tilTrueog hvert eksisterende mandat bærer det.
3.1 Målt underveis, rapportert, IKKE fikset (utenfor ordren)
--mandate står ikke i main()s report_forbidden-partisjon. Report-modus returnerer FØR
kjøre-dispatchen, så --report --ledger X --mandate Y dropper kommisjonen i stillhet i stedet
for å nekte — nøyaktig den gap-klassen F4 målte på --plan-review, på et flagg som er eldre enn
denne ordren. --proposals-from-mandate er lagt inn i partisjonen (og gatet av M11); --mandate
selv er ikke rørt, fordi den er utenfor det ordren ba om.
Ærlighets-grenser i denne målingen: ingen levende K2-fil er lest (fixturene er syntetiske, som i
MAJOR-4); run.py:1662-konsekvensen for dispatchen er lest i kilden, ikke kjørt mot en base uten
IR-projeksjon; og tabellen beskriver hva som KAN avledes, ikke at en ekspert vil fylle feltene godt.