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
132 lines
9.8 KiB
Markdown
132 lines
9.8 KiB
Markdown
# 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).
|