# 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_context` → `loop.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 1–2, 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_SECURE` → **ikke** 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_inbox`s 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.error` → **ikke** 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).