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
This commit is contained in:
Kjell Tore Guttormsen 2026-07-25 19:58:04 +02:00
commit ffb0503da3

View file

@ -15,7 +15,8 @@
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 rundingen i A5, som er en LIVE divergens.
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)
@ -96,19 +97,66 @@ eventuell konvergens via verktøykassa) og lukker likevel divergensen som gjør
kapabilitet» forenlig med ulike totaler på identiske inndata.
**Foreslått ordlyd (tre regler, én per skjønnsvalg):**
1. **Runding:** «Monetary figures MUST NOT be rounded in the projection — neither per row nor
on the total. Rounding is a rendering concern and MUST NOT change a persisted figure.
Derived non-monetary ratios MAY be rounded, at a fixed documented precision.»
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).»
**Divergensen er live, ikke uenighet:** vi runder ingen kronebeløp (kun deriverte andeler, 6
siffer — `_SHARE_DIGITS`, `valuereport.py:61`); MAF runder ett monetært beløp. Regel 1 slik
den er formulert lander på D7s nåværende oppførsel, og det er nettopp derfor den ikke kan
vedtas herfra — begge sider venter på amendmenten, ingen patcher unilateralt. Regel 2 og 3 er
**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.