docs(security): llm-ingestion-guard adopsjonsplan — trust-grense-kartlegging + status planned

Fulgte guard-repoets inclusion/adoption-brief. Kartla repoets ingest-flater mot
briefens §7-checklist: ingest file/sql = førstepart (verbatim render, 0 modellkall,
0 egress-call-sites i src); http = dormant untrusted (avvist fail-fast, extending.md);
inbox = kjørt-bevist injeksjonsflate men førstepart + uwiret (C-F7/C-N2). Status: planned.
Ingen wiring/dep lagt til — kun plan + maskinlesbar markørlinje i STATE.md (local-only).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Kjell Tore Guttormsen 2026-07-16 07:40:24 +02:00
commit b46f32c682

View file

@ -0,0 +1,125 @@
# 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 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:** i `_read_extraction`/`materialize`, MELLOM http-fetch og skriving av
`ingest-{id}.md` (`ingest.py:372-387`).
- **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).