portfolio-optimiser-claude/docs/2026-07-16-llm-ingestion-guard-plan.md
Kjell Tore Guttormsen a7e8ffecb8 chore(deps): re-pin llm-ingestion-okf to v0.3.1 + migrate tests to stable error codes
Pin dae0bd1a -> v0.3.1 (=692f2df) on the public Forgejo mirror; uv.lock pins
the exact commit behind the tag.

- Drop the mypy override: the library ships py.typed from v0.2.0, so strict
  mode now follows its real types instead of follow_untyped_imports.
- Migrate 8 library-error assertions from pytest.raises(match=...) to
  exc.value.code — message text is explicitly unstable from v0.3.0, the
  codes are the stability contract.
- Fix a real breakage the bump surfaced: IngestResult gained a required
  `stamp` field (d3a3bcc), which the delegation fake did not construct.
- The read-only SQL test loses resolution under the code contract
  (`sql_failed` is generic), so it now proves read-onlyness by effect —
  the write never lands — instead of by message wording.
- Correct the guard plan: G1's persist-gate anchor (ingest.py:372-387) died
  with the 2026-07-16 adoption. Door A is ungated by the library's own
  README, so gating stays our responsibility at the call site.

Verified: 426 tests green, golden output byte-exact unchanged, full gate
clean (ruff + format + mypy strict).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RmNAgbRXUgvoSKxVK4Bevv
2026-07-20 07:22:09 +02:00

9.8 KiB
Raw Blame History

Plan — når/hvor llm-ingestion-guard wires inn i dette repoet

Status: PLAN (ikke wiret). Forfattet 2026-07-16 etter guard-repoets adopsjons-brief (~/repos/llm-ingestion-pipeline-security/docs/ADOPTION-BRIEF.md, guard v0.2 alpha). Dette dokumentet KLASSIFISERER repoets ingest-flater mot briefens §7-checklist og fastsetter NÅR/HVOR guarden skal wires — det IMPLEMENTERER ingenting. Maskinlesbar status i STATE.md (markørlinjen llm-ingestion-guard: planned — …).

Ærlighets-regelen (metode-spec §1) gjelder også her: en grønn guard-scan er IKKE «trygt». Guarden er write-time defense-in-depth FORAN validator/checker/kostbaseline — ikke en erstatning.

1. Hvorfor dette repoet er i målgruppen

Guarden hardner write-time-formen: untrusted innhold som flyter gjennom et enrichment-/ekstraksjons-steg og persisteres i en artefakt en downstream agent senere leser som betrodd kontekst. Dette repoet ER den formen:

  • Ingest-laget materialiserer en OKF-bundle (markdown-konseptfiler + index.md) — ingest.py:348-391 materialize.
  • Loopens Steg 1 navigerer bundelen og renderer den som read-context rett inn i hypotese-/debatt-prompten — okf.py:122-134 bundle_contextloop.py:119-133 (context i proposer/checker-promptene). Bundelen leses altså som betrodd kontekst.
  • Den lange lærings-sløyfa (Steg 7) folder ekspert-verdikter inn i samme prompt: experience.py:157-171 fold_experience bygger linjer - {id} [{decision}]: {rationale}decision og rationale når prompten verbatim (experience.py:3,6).

Bundelen/verdikt-storen er trust-grensen: en forgiftet konsept- eller verdikt-linje skrevet ved write-time leses senere som betrodd. Provenansen er kun kjent ved write-gaten.

2. Trust-grense-kartlegging (briefens §7 mot dette repoet)

# Inngang Klassifisering Belegg (verifisert) Guard-relevans
1 ingest.py file-kilde (lokal CSV under root) Førstepart i D7-scope Operatør peker manifest mot lokal CSV; boundary-sjekket fail-closed (ingest.py:264-276); null modellkall, ingen nett (ingest.py:1-6); celler rendres verbatim (ingest.py:164-185) Ikke mål som scopet. Forbehold (trust følger opphav, ikke kanal): hvis CSV-radene selv er eksternt hentet, blir cellene en untrusted bærer inn i Steg-1-prompten
2 ingest.py sql-kilde (lokal sqlite, read-only) Førstepart i D7-scope connection_ref → env, read-only URI, verbatim typing (ingest.py:230-268) Samme som (1): opphavs-forbeholdet gjelder
3 ingest.py http-kilde DORMANT untrusted-sti (avvist i dag) type:"http" avvises fail-fast av discriminated union (ingest.py:129) — bevisst ikke bygd (extending.md:128-160; sesjonsplan §4). Ingen egress-call-sites i src/ (grep: 0 treff) DEN avgjørende untrusted-grensen når den går live — auto-fetch av web/vendor-innhold → materialiseres i bundelen → leses av loopen. Guardens hovedplass
4 inbox.py verdikt-inbox Live injeksjonsflate, men tiltenkt førstepart decision valideres kun som ikke-tom streng; ingen sjekk på merge-stien (inbox.py:102-132); rationale verbatim til prompten (§1). C-F7: kjørt bevis at injeksjonstekst når prompten. Ennå ikke wiret til noen kjøresti (C-N2, merge_inbox_into_store kalles fra ingen leverbar sti) Depth-komplement til den alt planlagte C2.5-herdingen — ikke primærbehovet (forfatteren er førstepart; C2.5-allowlist lukker den kjørte injeksjonen)
5 Mottatt tredjeparts OKF-bundle Ikke en sti i dag Repoet PRODUSERER sin egen bundle (ingest); ingen import_bundle-inngang finnes okf.import_bundle er det eksakte verktøyet HVIS en bundle-utvekslings-sti åpnes (delt D-E: klynge/flerbruker)
6 Kuraterte / promoterte konseptfiler Førstepart Håndforfattet (golden) / gated promoterings-gate (promotion.py) Utenfor guardens trusselmodell (in-place trusted-author-edits, brief §7)

LLM-steget: i dag sitter modell-steget (loop) MELLOM bundelen og proposal-output — ikke mellom en untrusted kilde og storen. Ingest→bundle er deterministisk (null modellkall). Den klassiske «untrusted → LLM-enrich → persist»-formen oppstår FØRST når http-kilden (rad 3) går live. Det er derfor guarden er «når, ikke hvis».

3. Klassifisering + status: planned

  • Repoet HAR en OKF-bundle-store som leses som betrodd (rad 12, 6 mater den; loop leser den).
  • Ingen live untrusted-ingest-sti i en leverbar kommando i dag: http er avvist fail-fast og bevisst utsatt (rad 3); inbox-injeksjonen er reell men tiltenkt førstepart OG ikke wiret til noen kjøresti ennå (rad 4 / C-N2).
  • Ingenting å integrere nå, men klart applikabelt. not-applicable ville vært feil (repoet ER en OKF-second-brain med en designet-inn untrusted utvidelse + en kjørt-bevist inbox-injeksjonsflate). integrated ville vært feil (ingen wiring; briefen ber oss ikke implementere).

4. Integrasjonspunkter (persist-gaten) + minimal wiring — NÅR

Tre trigger-gatede wiringer, hver forankret i en eksisterende roadmap-beslutning:

G1 — http-kilden går live · gate: D-B-utvidelse (i dag IKKE på D7-roadmap)

  • Persist-gate: MELLOM http-fetch og skriving av ingest-{id}.md.
  • KORRIGERT 2026-07-20 (pin-bump til v0.3.1): den opprinnelige forankringen (_read_extraction/materialize i ingest.py:372-387) er DØD — adopsjonen 2026-07-16 flyttet den koden til llm-ingestion-okf; lokal ingest.py er nå en 63-linjers adapter. Biblioteket bekrefter selv at dør A er ugatet («What is gated today: nothing» — README): null runtime-avhengigheter, ingen guard-kall før skriving. Gating er derfor VÅRT ansvar på kallstedet. To mulige forankringer når http går live: (a) wrap i vår egen seam (ingest.materialize) rundt bibliotekkallet, eller (b) løft gaten inn i biblioteket via operatør-køen. Valget er IKKE tatt — det hører til D-B.
  • Minimal wiring: ingest har intet modell-steg, så bookend-formen kollapser til sanitér+scan over den hentede ekstraksjons-kroppen: prepared = prepare_input(fetched_body) → deterministisk render → screen_output(rendered, PRESET_…); FAIL_SECUREikke skriv konseptfila, alert med gate-kode (decision.reasons), ingen innhold i alert. Provenance-frontmatteret (§5) bærer allerede source_system/ingested_at — behold som er.
  • Dette er guardens hovedplass. Utsettes nøyaktig så lenge http er utsatt.

G2 — inbox-herding · gate: sesjon C2.5 (byggbar nå) — DEPTH, ikke påkrevd

  • Persist-gate: merge_inbox_into_store/load_inbox, FØR store.add (inbox.py:122-132).
  • C2.5s primære fiks (sesjonsplan §2) = vokabular-allowlist {approved, rejected, approved_with_adjustment} + tak på rationale-lengde/antall. Den lukker den kjørte C-F7- injeksjonen alene.
  • Guard-komplement (valgfritt depth): scan_output(rationale + " " + decision, PRESET_USER_UPLOAD) over fritekst-rationale (injeksjons-bæreren). MÅ beholde inboxens tolerante semantikk: scan-treff → SKIP den verdikten (aldri raise) — konsistent med load_inboxs skip-never-raise (inbox.py:102-119). Merkes «vurder ved C2.5», ikke blokkerende.

G3 — mottatt tredjeparts OKF-bundle · gate: delt D-E (klynge/flerbruker) eller enhver receive-bundle-sti

  • Wiring: okf.import_bundle(bundle, origin=Origin.EXTERNAL, channel=…); per-konsept FAIL_SECURE/c.errorikke merge det konseptet. result.links = kryss-lenke-graf (dormant-injeksjons-signal).
  • Trigger: hvis/når en kryss-prosjekt-bundle-utveksling åpnes (delt D-E-retning). Ikke en sti i dag.

5. Forutsetninger å avklare FØR en wiring

  • Guard er v0.2 alpha — public API kan endres. Ved wiring: pin + versjonsvakt, SAMME disiplin som SDK-pinnen (C-N3/C2.5): >=0.2,<0.3 + test som tvinger re-verifisering ved bump.
  • Offline/no-egress-invarianten består: guard-core er stdlib-only, gjør ingen nettkall, parser ingen filer (brief §6) → «testsuiten kjører uten nøkkel og uten nettverk» (S5-invariant) holder. Verifiseres eksplisitt ved wiring (guard-core i [dev]/core, ikke [ml]/[judge]).
  • Ærlighets-grensene (brief §8) som betyr mest for et kostbesparings-wiki: semantisk/faktisk poisoning er usynlig for lexicon+entropi — feil join-path/metrikk/tall passerer rent. For dette repoet er nettopp plausible-men-feil claimed_saving_nok/koder den farligste klassen; guarden fanger den IKKE. Det er validatorens/checkerens/kostbaselinens (C4.0) jobb. Guarden legges FORAN dem, ikke i stedet for.

6. Hva som IKKE gjøres nå (per inclusion-prompt)

  • Ingen guard-avhengighet i pyproject.toml, ingen import, ingen wiring, ingen kode.
  • http-kilden forblir avvist fail-fast (uendret).
  • Kun: dette plandokumentet + markørlinjen i STATE.md.

7. Verifisering NÅR en wiring engang skjer (ikke nå)

  • python -m llm_ingestion_guard.coverage exit 0 før man stoler på guarden (brief §5).
  • Load-bearing test (§11): detach guard-gaten → en injeksjons-fixture MÅ da nå bundelen/storen → testen RØD. Grønn-men-død er feilmoden.
  • Full gate grønn (pytest/ruff/mypy); golden-bytene uendret; offline-invarianten består (ingen nettkall introdusert i suiten).

8. Sekvens-forankring

  1. G2 vurderes SAMMEN med C2.5 (byggbar nå) — men C2.5-allowlist er den nødvendige delen; guard-depth er additiv.
  2. G1 materialiseres når/hvis D-B åpner en live-kilde-utvidelse som inkluderer http (i dag eksplisitt IKKE på D7-roadmap, extending.md:128-160).
  3. G3 materialiseres hvis delt D-E-retning åpner bundle-utveksling.

Ingen av de tre er live i dag → status planned. Oppdater markørlinjen til integrated når en av dem faktisk wires (og legg load-bearing detach-beviset ved).