feat(verdicts): key each verdict on its own candidate, not the bundle's one IR projection (S3.2)
seed_store_from_bundle keyed EVERY `type: verdict` file on bundle_candidate_features — the single candidate the bundle's validator-input.json describes. A bundle carrying verdicts about several candidates collapsed them onto one key, so a verdict about candidate B scored a perfect structural match against candidate A's query and could be folded into A's hypothesis prompt. The ExpeL substrate was single-candidate by construction. A verdict file may now carry its own structural key in frontmatter (affected_codes / measure_type / claimed_saving_nok); absent, keying falls back to the bundle candidate, so every pre-S3.2 seed keeps working unchanged. promote_verdict writes the three fields, so a promoted verdict — frequently about a different candidate than the target bundle's projection — does not impersonate that candidate. Semantics decided HERE, not pulled: commons' seeding rule (method-spec §3 Steg 1 + bundle example) has not arrived; we said we would build locally first. D7 mirroring stays open. - ALL THREE fields or none. A partial declaration raises VerdictFrontmatterError rather than merging with the bundle candidate, which would mint a key belonging to NEITHER candidate. Validation, never repair (mirrors write_concept_file); the tolerant-skip rule belongs to the RAW inbox layer. - claimed_saving_nok parses via json.loads — the SAME literal rule the IR projection went through — and is written back with str() of the raw value. _mint_id hashes that value, so 30000 and 30000.0 are different keys; a normalising writer would split one candidate's signal across two ids. - The structural key is signal-free, so it does not weaken the Step-8 no-leak property (Test C green). Load-bearing MEASURED, five mutations all red: detach per-verdict keying · detach the fields promote_verdict writes · make a partial/unparseable key tolerant · normalise the magnitude on write · remove the fallback (control — breaks the step1 suite at collection, proving the fallback bears load). 589 -> 597 tests. Full gate green (pytest, ruff, mypy). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QkjvTTxrg9LTrmghebfiij
This commit is contained in:
parent
e8cec2e2c0
commit
012adc0a3c
10 changed files with 496 additions and 17 deletions
19
CLAUDE.md
19
CLAUDE.md
|
|
@ -105,6 +105,25 @@ Python ≥3.10. MAF (`agent-framework-core` 1.9.0). Pakkehåndtering: `uv`. To b
|
|||
(`tests/test_step8_promotion_loadbearing.py`): gaten avviser ikke-godkjent dom (RØD uten gate); godkjent
|
||||
dom er navigerbar (RØD når `link_in_index` detaches); promotert signal holdes ute av `bundle_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_bundle`
|
||||
leser hver `type: verdict`-fils EGNE strukturelle felt fra frontmatter (`affected_codes` /
|
||||
`measure_type` / `claimed_saving_nok`); mangler de, faller nøklingen tilbake til
|
||||
`bundle_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 raiser
|
||||
`VerdictFrontmatterError` i stedet for å slås sammen med bundle-kandidaten — sammenslåingen ville
|
||||
myntet en nøkkel som tilhører INGEN av kandidatene. Validering, ALDRI reparasjon (speiler
|
||||
`write_concept_file`); den tolerante hopp-over-regelen hører til RAW-innboks-laget.
|
||||
`claimed_saving_nok` parses med `json.loads` — SAMME literal-regel IR-projeksjonen gikk gjennom —
|
||||
og skrives tilbake som `str()` av råverdien, fordi `_mint_id` hasher den (`30000` ≠ `30000.0`; en
|
||||
normaliserende skriver ville splittet én kandidats signal på to id-er). `promote_verdict` skriver
|
||||
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 feltene `promote_verdict` skriver · gjør en
|
||||
delvis/uparsebar nøkkel tolerant · normaliser magnituden ved skriving · fjern fallbacken (kontroll).
|
||||
- **Globalt token-tak håndheves FØR kall, aldri bare etterpå (S3.4, F10):** `PortfolioBudget` +
|
||||
`PortfolioMeter` er ÉN ledger over hele porteføljepasset (og — seedet av `read_spend` — på tvers
|
||||
av pass), mens per-run `Budget`/`TokenMeter` er uendret. Taket har tre tenner, med hver sin jobb:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue