feat(learning): S9 — D7 læringssløyfe: verdict-inbox, fail-closed promoteringsgate, artefakt-sourced persona

- inbox.py (§4.2+§5): VerdictDocument med verbatim-id-regel; write_verdict
  authoring-primitiv (deterministisk JSON); load_inbox tolerant (skip, aldri
  raise; sortert på filnavn); merge_inbox_into_store first-write-wins,
  idempotent, skriver aldri (rolle-splitt §3 steg 7)
- promotion.py (§6): promote fail-closed mot {approved,
  approved_with_adjustment}; eksplisitt påkrevd timestamp; minimal frontmatter
  (rationale → description, aldri strukturerte læringsfelt); path-safe token
  med content-hash-fallback; idempotent index-lenking med fast nøytral label
- persona.py (§4.3): load_persona_example fail-fast (run-path-vokabular,
  marker ⊆ rationale); drop_persona_verdict artefakt-sourced ved kalltid mot
  delt shared/-artefakt
- experience.py (kirurgisk): seeding leser verdict_id VERBATIM fra frontmatter
  — re-minting ville kollidert distinkte promoterte kandidater
- 43 nye load-bearing tester (step7/step8/persona), 164/164 uten API-nøkkel;
  to-runs-bevis med fersk store + tom-inbox-kontroll; fire detach-bevis kjørt
  røde og revertert grønne

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QdSfQdND84oeq2mbjueLTS
This commit is contained in:
Kjell Tore Guttormsen 2026-07-03 07:36:15 +02:00
commit 22bfc80dda
7 changed files with 947 additions and 2 deletions

View file

@ -124,7 +124,10 @@ def seed_store_from_bundle(store: VerdictStore, bundle_dir: Path) -> int:
Entries are keyed on the bundle's candidate features, read from the IR
projection (fail-fast, required input). The rationale is built from the
``description`` frontmatter plus, when present, the structured learning fields.
Returns the number of verdict files seeded.
A ``verdict_id`` in the frontmatter (promoted files, §6) is read VERBATIM
(§4.2) re-minting from the bundle features would collide distinct
promoted candidates into one first-write-wins store slot. Returns the
number of verdict files seeded.
"""
features = CandidateFeatures.from_proposal(load_validator_input(bundle_dir))
seeded = 0
@ -141,7 +144,7 @@ def seed_store_from_bundle(store: VerdictStore, bundle_dir: Path) -> int:
rationale = f"{rationale} {learning}".strip()
store.add(
VerdictRecord(
verdict_id=mint_verdict_id(features),
verdict_id=concept.frontmatter.get("verdict_id") or mint_verdict_id(features),
decision=concept.frontmatter.get("decision", _DEFAULT_SEED_DECISION),
rationale=rationale,
features=features,