portfolio-optimiser-claude/docs/2026-07-25-d-a-amendment-forslag.md
Kjell Tore Guttormsen ffb0503da3 docs(portfolio): A5 premise corrected — the quantisation POINT is the live divergence
MAF measured their own tree and corrected our claim that they "round one
monetary figure": their projection rounds nothing (value_report.py:48,
0 hits for round()); the one operation is a NOK->ore quantisation at the
ledger edge (ledger.py:204-214, Decimal + ROUND_HALF_UP). Rule 1 as we
wrote it therefore solved a divergence that did not exist and opened one
that did not — it condemned their deliberate, documented quantisation as
"rendering". Adopted their proposed split as rules 1a/1b.

Then measured here, which REFUTED our own first hypothesis: we assumed
float accumulation was the divergence. It is not — 0 of 20 000 random
portfolios diverge on 2-decimal NOK input. The real mechanism is where
quantisation sits relative to summation, and it only wakes on sub-ore
input: 66.4% of totals diverge, worst 6 ore over 28 rows, and a designed
100 x 0.005 case diverges by a factor of 2.

Sub-ore input is reachable in BOTH stacks — neither ir.py:42
(claimed_saving_nok) nor ledger.py:42 (amount_nok) constrains decimals;
both are float = Field(gt=0, allow_inf_nan=False). So two stacks that
both obey 1a+1b can still return different totals on identical input,
which is exactly what A5 exists to close. Hence new rule 1c: the
amendment MUST fix one quantisation point — reject finer-than-ore claims
at the contract edge, or quantise once on the total — and both stacks
must land on the same choice.

Doc-only; no source touched. Suite 627 passed. Nothing patched
unilaterally: shared/ is pull-only and the amendment is not in commons.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MQu2xxwedckjU56byu1aUG
2026-07-25 19:58:04 +02:00

183 lines
12 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.

# D-A-bolken — godkjent 2026-07-25, forslag til amendment-tekst (D7-siden)
> **Hva dette er:** operatøren godkjente 2026-07-25 HELE D-A-bolken som én amendment —
> de fire spec-rettelsene (C-F2/C-F3/C-F4/C-F5), brief-§7 (projeksjons-kontrakten over
> hovedboken), og C-P1-kollisjonen løst ved **re-scoping** (ikke ved å amendere det frosne
> §3 Steg 1-prinsippet). Dette dokumentet er D7-repoets bidrag til teksten: spec-anker,
> foreslått normativ ordlyd, og hva vi har MÅLT om kompatibilitet.
>
> **Hva dette IKKE er:** beslutningen selv. Den protokollføres i hovedrepoets delte kø.
> `shared/` er en PULL-ONLY subtree — amendment-teksten kan ikke committes herfra; den må
> landes i commons og hentes av BEGGE søsken. Ordlyden under er et forslag, ikke en vedtatt
> tekst.
## 0. Prosedyre (uendret mal)
amendment i commons → **koordinert** `git subtree pull` i BEGGE repo (samme commons-commit)
→ begge suiter grønne → deretter én byggsesjon per punkt, failing test først (§11).
Ingen av søsknene retter ensidig — heller ikke A5s kvantiseringspunkt, som er en LIVE, målt
divergens (66,4 % av totaler ved sub-øre-inndata; se A5).
## A1 — Validator: den nominale grensen må gate (C-F2 → sesjon C2.7)
**Anker:** §3 Steg 4 pkt. 2 beregner «maximum feasible saving ... 0.30 × Σ quantity·unit_cost»,
men pkt. 4 blokkerer KUN over `p90`. §7.1 har ingen invariant som binder usikkerhetsbåndet til
`unit_cost`, og båndet forfattes av modellen selv.
**Foreslått ordlyd (to tillegg):**
- §3 Steg 4, nytt pkt.: «A claim above `nominal_feasible` MUST yield the same distinct
rejection type as pkt. 4, regardless of the percentiles. The structural block is evaluated
against BOTH bounds; the deterministic bound is never advisory.»
- §7.1, ny IR-invariant: «When an item carries an uncertainty band, `low ≤ unit_cost ≤ high`
MUST hold; a band that excludes the unit cost is a schema violation, not an input.»
**Kompatibilitet (målt på D7-siden, ikke antatt — `review-2026-07.md` §1 C-F2):** golden-claim
30 000 ≤ nominal 90 000 (passerer); alle ikke-tomme bånd i repoets fixtures omslutter
`unit_cost` ([0.8,1.2]∋1.0 · [0.70,1.40]∋1.0 · (0.70,1.50)∋1.0; resten tomme). Verken
fasit-eksemplet eller eksisterende testdata brytes.
## A2 — Kostbaseline: claim må avstemmes mot noe utenfor modellens egne tall (C-F3 → C4.0)
**Anker:** ingenting i bundle-formatet bærer en kostbaseline. §3 Steg 4 pkt. 1 regner kun på
selv-deklarerte linjer, så en diktet kostlinje passerer validering (kjørt bevis: `XX-DIKTET`,
1e6 × 10 → claim 2,9 MNOK stemplet `validated`).
**Foreslått ordlyd:** «A bundle MAY carry a cost baseline projection (`cost-baseline.json`).
When present, every `affected_items` code MUST reconcile against it, fail-closed: an unknown
code or a quantity above the baseline's is a rejection of the same distinct type as §3 Steg 4
pkt. 4. **A run that calls a model MUST have a baseline** — a bundle without one MUST be
refused at startup (§10), never run unreconciled.»
**Åpent underspørsmål amendmenten MÅ svare på:** skal baselinen være obligatorisk for ALLE
kjøringer, eller kun for kjøringer som kan bruke penger? Vårt forslag er formulert som det
siste, med vilje: fasit-bundelen (`shared/examples/bygg-energi-mikro/`) har ingen
`cost-baseline.json`, så et ubetinget krav ville endret fasit-bytene og dermed §7-fasiten for
begge stacker. Feilscenarioet C-F3 beskriver er dessuten live-stien. Velger amendmenten
likevel «obligatorisk overalt», må fasit-endringen planlegges eksplisitt i begge repo.
## A3 — Ledende `/` i OKF-lenker skal forstås, ikke hoppes over (C-F4 → C2.8)
**Anker (verbatim, §3 Steg 1):** «Targets containing a path separator are out-of-bundle and
MUST be skipped.» OKF-spec-en anbefaler bundle-rot-relativ `/`-form — spec-en er altså
spec-tro implementert mot en spec-feil.
**Foreslått ordlyd:** «A target with a LEADING `/` is bundle-root-relative and MUST be
resolved against the bundle root. Any other path separator remains out-of-bundle and MUST be
skipped. Resolution stays boundary-checked against the bundle directory, fail-closed — the
leading slash changes the base, never the boundary.»
**Merknad til hovedrepoet:** MAF-planen har i dag ingen byggsesjon for F4-implementasjonen på
sin side; den bør legges til når D-A lander, ellers divergerer stackene på et punkt
amendmenten nettopp harmoniserer.
## A4 — Seeding må nøkles på dommens EGNE features (C-F5 → C3.2, folder inn R-8)
**Anker (verbatim, §3 Steg 1):** «Seeding: every `type: verdict` file in the bundle becomes a
store entry keyed on those candidate features» — «those» er bundelens ENE IR-projeksjon, så
dommer om ulike tiltak får identisk nøkkel.
**Foreslått ordlyd:** «A seeded verdict MUST be keyed on ITS OWN candidate features when its
frontmatter carries them (the promotion gate, §6, writes them); the bundle's IR projection is
the fallback for hand-authored seeds that do not. Two verdicts about different candidates MUST
NOT share a retrieval key.»
**Nyanse (D7-målt):** id-halvdelen av MAFs F5-funn er allerede løst her — `verdict_id` leses
verbatim fra frontmatter, load-bearing-bevist. Det er feature-halvdelen som står igjen.
## A5 — Projeksjons-kontrakten over hovedboken (brief-§7 — meldt uavhengig fra BEGGE stacker)
**Anker:** ingen. Method-spec-en nevner ikke hovedbok eller verdirapport i det hele tatt
(verifisert: 0 treff på «ledger»). Spec-en sier hva som skal produseres, ikke hva en
projeksjon over resultatene må garantere — derfor traff begge søsken samme hull samme døgn og
måtte ta samme tre skjønnsvalg.
**Formen vi foreslår:** kontrakten bindes til projeksjonens ARITMETIKK og fullstendighet, ikke
til hovedbokens filformat. Det holder brief pkt. 6 åpen (formatene forblir stack-lokale til en
eventuell konvergens via verktøykassa) og lukker likevel divergensen som gjør «samme
kapabilitet» forenlig med ulike totaler på identiske inndata.
**Foreslått ordlyd (tre regler, én per skjønnsvalg):**
1. **Runding og kvantisering** (REVIDERT 2026-07-25 etter MAFs målte korreksjon + egen måling
— se «A5-premisset korrigert» under; erstatter den opprinnelige ettsetnings-regelen):
- a. *Projeksjons-aritmetikk:* «Monetary figures MUST NOT be rounded in the projection —
neither per row nor on the total. Derived non-monetary ratios MAY be rounded, at a fixed
documented precision.»
- b. *Enhets-kvantisering ved persisterings-kanten:* «A stack MAY quantise monetary input to
a fixed minor unit ONCE, at the point of persistence, at a documented precision. This is
a representation choice, NOT a rendering concern, and it is explicitly permitted.»
- c. *Kvantiseringspunktet* (NYTT — uten dette er a+b ikke tilstrekkelig): «Where a stack
quantises, the contract MUST fix quantisation relative to summation: either input is
constrained to the minor unit at the contract edge (a finer-grained claim is a rejection),
or quantisation happens once on the total. Per-row quantisation of unconstrained input is
NOT interchangeable with quantisation of the sum.»
2. **Null kvalifiserende rader:** «A report MUST be produced even when no row qualifies. Every
figure the expert layer has not supplied MUST be UNMARKED (null), never 0 — a zero that
means "unjudged" is a false claim (§1).»
3. **Renhet:** «The projection MUST NOT restate provenance fields. It is a view over the
layers it reads; provenance is asserted once, by the run that produced it (§9).»
**A5-premisset korrigert (2026-07-25) — vår opprinnelige formulering var feil, og korreksjonen
flytter divergensen.** Vi skrev «MAF runder ett monetært beløp». MAF målte i sitt eget tre og
korrigerte oss: projeksjonen deres runder INGENTING (`build_value_report`, `value_report.py:48`
summerer heltalls-øre; `grep -n "round(" value_report.py` → 0 treff). Den ene monetære
operasjonen er en NOK→øre-**kvantisering** ved hovedbok-kanten (`ledger.py:204-214`, `Decimal`
+ `ROUND_HALF_UP`, dokumentert som «happens HERE and only here … to avoid binary-float error»).
Det er en enhetskonvertering ved persistering, ikke en runding i en projeksjon. Vi tar
korreksjonen: den opprinnelige regel 1 løste en divergens som ikke fantes (begge stacker runder
allerede intet i projeksjonen) og åpnet én som ikke fantes (andre setning fordømte MAFs
bevisste, dokumenterte kvantisering som «rendering»). Regel 1a+1b over er MAFs foreslåtte
splitt, ordrett i intensjon.
**Men splitten er ikke tilstrekkelig — målt her, og den avkreftet vår egen første hypotese.**
Vi antok at divergensen lå i float-akkumulering (vi summerer `float`-NOK, MAF heltalls-øre).
Målt: **0 av 20 000** tilfeldige porteføljer (240 rader, 2-desimal NOK) ga ulik total. Float
er ikke mekanismen, og vi rapporterer det selv om det svekker vår egen opprinnelige lesning.
Den reelle mekanismen er **hvor kvantiseringen står i forhold til summeringen**, og den bare
våkner når inndata bærer finere presisjon enn øre:
| inndata | MAF (kvantiser per rad, summer heltall) | D7 (summer fullt, kvantiser til slutt) |
|---|---|---|
| `[0.004, 0.004, 0.004]` | `0+0+0` = **0 øre** | `0.012`**1 øre** |
| `100 × 0.005` | **100 øre** | **50 øre** — faktor 2 |
| 20 000 tilfeldige, 2-desimal | *ingen divergens***0/20 000** | *ingen divergens***0/20 000** |
| 20 000 tilfeldige, sub-øre | **66,4 % divergerer** — verste avvik 6 øre over 28 rader | (samme kjøring, motparten) |
*Reproduksjon:* `random.seed(20260725)`, 240 rader, `uniform(0.01, 500000.0)`; MAF-siden
modellert etter deres siterte `ledger.py:204-214` (`Decimal(str(v))*100`, `ROUND_HALF_UP`).
Målt mot vårt tre ved `457a0f4`. De to første radene er deterministiske, ikke seed-avhengige.
Og sub-øre-inndata er **nåbart, ikke hypotetisk, i begge stacker**: verken vår
`claimed_saving_nok` (`ir.py:42`) eller `amount_nok` (`ledger.py:42`) har noen
desimal-begrensning — begge er `float = Field(gt=0, allow_inf_nan=False)`. To stacker som
begge lyder 1a+1b kan altså fortsatt gi ULIK total på identiske inndata, som er nøyaktig det
A5 finnes for å lukke. Derfor regel 1c: amendmenten må velge ETT kvantiseringspunkt — enten
avvis finere-enn-øre-claims ved kontraktskanten, eller kvantiser én gang på totalen — og begge
stacker må lande på samme valg. **Ingen av delene kan patches herfra.**
**Status:** divergensen er fortsatt LIVE, men den er nå navngitt og målt i stedet for antatt.
Vi runder ingen kronebeløp (kun deriverte andeler, 6 siffer — `_SHARE_DIGITS`,
`valuereport.py:61`, verifisert). Regel 2 og 3 er
allerede D7s oppførsel (`_corrected`, `valuereport.py:182-194`: `pending` og
`adjusted_unquantified` gir `None`, aldri 0 — og `rejected` gir 0.0 fordi NULL er dommen, ikke
et fravær av den; `report_payload`, `valuereport.py:424`) og pkt. 3 er også MAFs valg.
## A6 — C-P1: semantisk henting re-scopes (INGEN spec-endring)
**Anker (verbatim, §3 Steg 1):** «Ranking is **structural, never textual** — surface text MUST
NOT contribute to similarity.» Den delte D-C-planen (cosine over tekst) kolliderer direkte.
**Vedtatt retning:** re-scoping, ikke amendment. Det frosne prinsippet består; semantisk
henting bygges på strukturelle kjennetegn alene. Konsekvens for planene i begge repo: rad 13
(K17) forblir GATED på D-C, men gaten er nå «D-C re-scopet», ikke «D-C som skrevet».
## Verifisering (hvordan vi vet at bolken faktisk har landet)
1. Commons har amendment-commiten; BEGGE repo har hentet SAMME commit (`git log shared/` viser
samme squash-hash i begge).
2. Begge suiter grønne rett etter pull, FØR noen byggsesjon starter — amendmenten alene skal
ikke rødne noe (den utvider spec-en; A1s stramming er målt kompatibel, A2s valg avgjør om
fasit må endres).
3. Deretter én sesjon per punkt, failing test først: C2.7 (A1) · C4.0 (A2) · C2.8 (A3) ·
C3.2 (A4) · verdirapport-kontrakten (A5, treffer K11-koden her).
4. A5 er ikke ferdig før BEGGE stacker produserer samme total på identiske inndata — det er
testen som beviser at divergensen faktisk er lukket, ikke at ordlyden finnes.