Planens §3 sa at `ingest.materialize` er repoets ENE skrivepunkt på Door A, og det premisset ble felt av måling FØR bygging: `materialize` er en ren delegasjon til pinnet llm_ingestion_okf v0.3.2s `materialize_bundle`, som stager i minnet og utfører sin egen disk-fase. Det finnes ingen callback mellom de to, så en gate plassert der kunne bare kjørt ETTER at bytene landet — en opprydding, ikke en gate. Sømmen ble i stedet kopier bundelen → materialiser inn i kopien → skann det som ble generert → publiser eller forkast. Kopien er bærende, ikke bekvemmelighet: bibliotekets §3 eierskaps-skann, kollisjonsgaten mot kuratert innhold og §6 index-merge leser alle den EKSISTERENDE bundelen. Staging i tom katalog mister alle tre og publiserer en bundle uten kuraterte naboer — datatap forkledd som sikkerhetsfiks. De fire §4-beslutningene, tatt og målt: (1) ingen av guardens to preset — Origin.EXTERNAL/AUTOMATIC, fordi trust_for utleder policy fra origin alene og PRESET_USER_UPLOAD bærer en quarantine-semantikk Door A ikke har; (2) utfall per BUNDLE, diagnostikk per DOKUMENT — delvis publisering ville etterlatt bundle + index som svarer til intet manifest, men import_bundle itererer forbi første avvisning; (3) Report til log.md, aldri konsept-frontmatter, der fire golden-suiter pinner bytene; (4) mypy-override OG adapter, siden override alene gjør sømmen type-blind i stedet for type-sikker. `materialize` forblir ugatet med vilje — goldenene pinner den, og en kaller som vil ha gaten ber om den ved navn. Fem mutasjoner alle røde + grønn kontroll (hele suiten, ~120 s hver): detach gaten · la den fyre ETTER publisering · Origin.INTERNAL · tom staging-katalog · rapporter kun første avvisning. Målingen felte en VAKUØS test først: en hard injeksjon scorer fail_secure under BEGGE trust-tierene, så Origin.INTERNAL-mutasjonen lot alle tre avvisningstestene stå grønne — beslutning 1 så dekket ut uten å være testet. Båndet der tieren faktisk avgjør er høy-entropi-innhold (quarantine_review vs warn), og testen ble skrevet mot nøyaktig det før mutasjonen ble re-målt. Mutasjon 4 ble på sin side felt av KUN én test; 809 andre merket ikke at bundle-kopien forsvant. Laveste disposition er `warn`, ikke `allow` — `allow` finnes ikke i guarden. En gate skrevet mot == allow ville avvist hvert dokument som noensinne ingestes. Kriterium 5 står: demo-stdout er byte-identisk med tests/golden/demo-transcript.stdout, målt både i suiten og ved eksplisitt kjøring. shared/ er urørt. 801 -> 810 passed / 4 skipped. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DDu94KoyxAmhJsG2n63X8Q
14 KiB
Inkluderingsplan — llm-ingestion-guard som write-time persist-gate
⚠️ UTDATERT PÅ TO PUNKTER — REALISERT 2026-08-09 av innholdsgate-planen (P2/S1.b). Dokumentet beholdes som kartleggingen den var; disse to påstandene skal IKKE leses som gjeldende:
- «Guard-modenhet:
v0.2(alpha)» er FEIL (målt 2026-08-09: syv publiserte tagger, sistev0.3.4,dependencies = []). Det premisset gjorde adopsjonen urimelig risikabel og er grunnen til at innholdsgate-planen ble skrevet. Adoptert pin:v0.3.4.- Gate-plasseringen i denne planen er FEIL. Den plasserer gaten i
ingest.materialize, «repoets ene skrivepunkt». Målt ermaterializeen ren delegasjon til det pinnedellm_ingestion_okf-biblioteketsmaterialize_bundle, som stager i minnet og skriver selv — ingen callback mellom staging og disk. Gaten bor derfor imaterialize_gated(kopi → materialiser → skann → publiser eller avvis), ikke der denne planen sier.Status: PLAN (ikke implementert). Denne økta KARTLA repoets untrusted-ingest og la denne planen; guarden er IKKE wiret inn ennå (per adopsjons-briefens §7: «kun planlegg og oppdater planene»). Utløst av konsument-inkluderings-prompten fra
llm-ingestion-pipeline-security. Guard-modenhet:v0.2(alpha) — stdlib-only kjerne, framework-agnostisk, offentlig API kan fortsatt endres. Pin ved adopsjon.Kildepekere: guardens adopsjons-brief (open-remote:
open/llm-ingestion-pipeline-security/docs/ADOPTION-BRIEF.md). Guarden er allerede anerkjent i repoets veikart: sesjonsplan Fase 2–6 D-G («Guard forblir eget sikkerhets-repo (avhengighet)», linje 120) og toolkit-repo-brief §5/§8 («migreres IKKE; forblir eget repo … fabrikkens sikkerhetsvask kreverllm-ingestion-guardsom publisert»).
1. Hva guarden er, og hvorfor den er relevant HER
llm-ingestion-guard er write-time-søsknet til query-time chatbot-guardrails. Den sitter ikke
mellom bruker og modell ved spørring, men herder den andre formen: untrusted innhold som flyter
gjennom et LLM-berikelses-/ekstraksjons-steg inn i et persistert, downstream-lest artefakt — en
RAG-base, kunnskapsbase, wiki eller OKF-bundle.
Dette treffer arkitekturen vår direkte: ingest.py materialiserer eksterne kilder inn i OKF-bundles,
og optimiser-loopen konsumerer bundelen uendret som betrodd agent-kontekst (bundle_context).
En forgiftet konseptfil skrevet ved write-tid leses senere av forslagsstiller-agenten som betrodd
kontekst — agentens (framtidige) query-time-guardrail ser aldri hvor konseptet kom fra. Write-gaten
er det ENESTE stedet provenansen fortsatt er kjent. OKF har ingen skjema-registry, ingen sentral
autoritet og ingen signering — en mottatt bundles påståtte opphav er ikke verifiserbart på format-nivå.
Guarden gjør aldri modellkallet selv (relevant for kostdisiplinen: null modellkall, stdlib-only, ingen
nett). Den gir de to bibliotek-halvdelene rundt vår egen tool-less transform (prepare_input /
screen_output), pluss en OKF-adapter for hele bundles (okf.import_bundle).
2. §7-checklist scoret for dette repoet
| §7-kriterium | Repoet | Kilde |
|---|---|---|
| Persisterer LLM-beriket/eksternt-mottatt innhold i en store en downstream agent leser som betrodd | JA | ingest.materialize → OKF-bundle → bundle_context (run.py:302); verdict-inbox → store.add → ExpeLContextProvider (run.py:287-290) |
| Minst én ingest-sti tar UNTRUSTED innhold (ekstern URL / opplastet fil / mottatt bundle / auto-hentet web) — den avgjørende | JA | http-kilde (I6) auto-henter web/vendor-innhold (read_http/_urllib_get, ingest.py); verdict-inbox er «inbox som tar imot eksterne drops» (S2.5/F7/F11) |
| Et LLM-steg (summarize/extract/classify/rewrite) mellom untrusted kilde og store | Delvis — LLM-steget leser bundelen downstream (ingest er deterministisk, pre-loop); verdict-inbox injiseres direkte i agent-instruksjoner | ingest.py §1-docstring; verdicts.py:299-311 |
| Ønsker fail-secure (stopp før persist) framfor best-effort med stille commit | JA — hele repoets kontrakt er fail-fast/fail-closed (validator obligatorisk-blokkerende; allow_network; verdict-lag reservert) |
CLAUDE.md-invarianter |
Konklusjon: den avgjørende betingelsen holder → guarden er applicable (marker = planned).
Ærlig motargument (vurdert, forkastet): man kunne hevde not-applicable fordi de eneste kildene som
kjøres i praksis i dag er operatør-forfattet lokal data (reference_projects.json, lokale CSV-er).
Men (a) http-konnektoren (I6) FINNES i koden og ER en untrusted auto-fetch-sti, og (b) verdict-inboxen
er eksplisitt anerkjent som en injeksjonsflate i veikartet (S2.5: «verdict-inboxen slutter å være en
uvalidert injeksjonsflate»). Derfor er planned den ærlige klassifiseringen — ikke not-applicable,
og ikke integrated (ingenting er wiret ennå).
3. Untrusted-grenser vs first-party
Untrusted-grenser (guarden hører hjemme her) — persist-gater rangert etter eksponering:
| # | Persist-gate | Fil | Hva krysser inn | Nåværende delforsvar | Gap guarden lukker |
|---|---|---|---|---|---|
| 1 | ingest.materialize / read_http |
ingest.py:472 (http-gate 506-511) | Ekstern kilde (http/sql/csv) → ingest-{id}.md i bundelen → betrodd kontekst |
allow_network-egress-gate, safe_resolve-baneinnesperring, kodefence-marker-avvisning, tabellcelle-escaping, verdict-lag-reservasjon |
Ingen carrier-strip (zero-width/BIDI/Unicode-tag/HTML-comment/data:), ingen lexicon/entropy/active-content-scan av rendret kropp, ingen EchoLeak markdown-bilde/lenke-deteksjon. En zero-width-kodet instruksjon i en CSV-celle overlever i dag verbatim. |
| 2 | load_verdicts_from_dir → store.add |
verdicts.py:181, kalt run.py:287-290 | Eksternt-droppet JSON (id+rationale verbatim) → store → injisert i agent-instruksjoner via ExpeLContextProvider |
Tolerant skip, decision-vokabular ({approved,rejected}), max_rationale_len, max_files fail-fast (S2.5) |
Ingen innholds-scan av rationale/description før den blir few-shot. Høyest frekvens i normal-loopen. |
| 3 | promote_verdict → okf.write_concept_file |
verdicts.py:513 (skriv 563-564) | Godkjent dom → type: verdict-konseptfil i wikien → betrodd av ALLE framtidige kjøringer |
Fail-closed HITL-gate (PromotionRefused, kun approved/approved_with_adjustment), nøytral index-label, _safe_filename_token |
Innholdet stammer fra (untrusted) inboxen; guarden = defense-in-depth (scan rationale/description FØR permanent betrodd kontekst). Størst blast-radius. |
| 4 | Mottatt OKF-bundle (okf.import_bundle-mode) |
finnes ikke i dag | En hel tredjeparts-bundle merges | — | Roadmapen ÅPNER den: D-H demo-sti («unzip energi-eksempel i bundle-innboks»), D7-portabilitet, toolkit-fabrikk. «Når, ikke hvis»: bruk okf.import_bundle(origin=EXTERNAL) per-konsept-gate når en received-bundle-sti legges til. |
First-party (guarden gjelder IKKE):
datasource.py/reference_domain.py— operatør/syntetisk data, JSON-Schema-validert fail-fast. Deployerens egen kontekst.file/sql-kilder mot operatørens egen lokale katalog — first-party etter opphav. Tillit følger dataens opphav, ikke innsettings-kanalen: en CSV lastet ned fra en vendor er fortsatt ekstern og hører under gate 1. Deployeren eier klassifiseringen (jf. «rent teknisk rammeverk»-invarianten).write_verdict(verdicts.py:161) — forfatter-primitiv; tillitsgrensen er lastingen (gate 2), ikke skrivingen.outbox.write_outbox— systemets EGET output-lag (må aldri re-ingesteres som inbox, run.py:261-263).persona.load_persona_example(persona.py:47) — first-party fil-les; men NB: i simuleringen forplanter den seg gjennompromote_verdictinn i wiki/ExpeL-kjeden — dekkes indirekte av gate 3.- Optimiser-loopens interne resonnement (proposer/debatt/validator) — intet eksternt innhold hentes; resonnement over allerede-materialisert bundle-kontekst.
4. NÅR — innplassering i veikartet
Guarden er en persist-gate, så den skal wires før den første untrusted-ingest-stien går live, festet der beriket/mottatt innhold committes. Veikartet gir tidspunktet eksplisitt:
- Hard grense: FØR M3 (Fase 6-pilot). M3 er der «én ekte kilde … herdet og materialisert med ekte provenance; én ekte fagekspert onboardet på inbox/outbox-kontrakten» — dvs. første ekte untrusted-ingest (gate 1) OG første ekte inbox-drop (gate 2) samtidig. Guarden MÅ stå før M3.
- Naturlige wiring-økter (utvider eksisterende steg, ingen ny fase):
- Gate 1 → utvid S2.4 («Konnektor-herding mot mock»): S2.4 herder allerede http-konnektoren mot
injisert transport. Legg guardens
sanitize+scan_outputpå rendret ekstraksjons-kropp der. - Gate 2 → utvid S2.5 («Inbox-herding»): S2.5 gjør inboxen til «ikke lenger uvalidert injeksjonsflate»
med vokabular + lengde-tak. Guardens innholds-scan av
rationale/descriptioner den manglende brikka. - Gate 3 → ny opt-in ved
promote_verdict: defense-in-depth-scan, opt-in (speiler atpromote_verdictselv er opt-in, ikke wiret irun_project). - Gate 4 → når en received-bundle-sti faktisk legges til (D-H demo-sti / toolkit-fabrikk):
okf.import_bundle.
- Gate 1 → utvid S2.4 («Konnektor-herding mot mock»): S2.4 herder allerede http-konnektoren mot
injisert transport. Legg guardens
- Avhengighet allerede erklært: guarden er «eget sikkerhets-repo (avhengighet)» (D-G) og toolkit-fabrikkens
«sikkerhetsvask krever
llm-ingestion-guardsom publisert» (toolkit-brief §8). Denne planen KONKRETISERER hvor i DETTE repoet den avhengigheten lander.
Rekkefølge-anbefaling: gate 2 (S2.5) og gate 1 (S2.4) først (de er allerede planlagte herdings-økter i Fase 2-bolk B, gated på D-B), gate 3 som liten opt-in når som helst, gate 4 kun når received-bundle åpnes. Ingen av dem er på kritisk sti før M3-forberedelsen begynner.
5. HVOR + minimal wiring (per gate)
Skjematisk (adopsjons-briefens §2/§4 API; IKKE implementer nå):
Gate 1 — ingest.materialize (staging-loopen, før _write_bytes):
prepared = prepare_input(raw_source_text) # carrier-strip + fence FØR render
# ... eksisterende render_table / _render_fenced_block på prepared ...
decision = screen_output(rendered_body, PRESET_*) # scan rendret konseptkropp
if decision.disposition is Disposition.FAIL_SECURE:
raise IngestError(gate_code=decision.reasons) # fail-fast, matcher modulens kontrakt
Fail-secure = IngestError (byttet inn i modulens eksisterende fail-fast-mønster). Alert-payload = gate-kode
- run-id, ALDRI innhold (§8).
Gate 2 — load_verdicts_from_dir (i skip-loopen): kjør rationale/description gjennom screen_output
med EXTERNAL/MANUAL-provenance; fail-secure = skip fila (konsistent med den eksisterende tolerante
skip-kontrakten Steg-7-loopen hviler på) + logg gate-kode. Aldri raise (bevarer per-fil-toleransen).
Gate 3 — promote_verdict (opt-in, før write_concept_file): screen_output på rationale → fail-secure
hever PromotionRefused (gjenbruk eksisterende fail-closed-sti).
Gate 4 — received-bundle: okf.import_bundle(bundle, origin=EXTERNAL, channel=…) per-konsept-gate FØR
merge; c.error-konsepter skippes (FAIL_SECURE), aldri merget.
6. Invariant-forenlighet (må holde ved wiring)
- MAF-fri / D7-portabel: guarden er stdlib-only, null deps → forenlig med
ingest.py/okf.py's «pure stdlib + pydantic, MAF-fri»-krav (håndhevet avtest_okf_is_maf_free+test_ingest_loadbearing). Legg guarden til i_MAF_FREE_MODULES-probens tillatte tredjeparts-deps eller verifiser at den ikke drar innagent_framework/mcp(den gjør ikke). D7-speiling påkrevd: SDK-søsknet må wire samme guard (framework-agnostisk → samme pakke) i egen økt. shared/er PULL-ONLY + commons-koordinering: ingest-SPEC-en bor ishared/ingest-spec.md(frossen, commons). Hvis wiring krever et spec-felt (f.eks.screened_at/trust-tier i provenance-frontmatteren), MÅ amendmenten skrives iportfolio-optimiser-commonsog pulles hit — aldri lokalt. NB: samme inkluderings-prompt kjører i commons-repoet; koordiner så guard-vask i den delteshared/-halvdelen (persona/skills, ekspert-artefakt) eies der, og denne planen kun dekker repo-lokale moduler.- Alpha-pin:
v0.2— pin eksakt versjon ipyproject.toml; behandle API som ustabilt til v1.0. - Kostdisiplin: guarden gjør null modellkall og null nett → forenlig med offline-først. Semantisk/faktisk forgiftning er USYNLIG for lexicon+entropy (guardens ærlige grense §8) — den fanges av HITL-dommen + validatoren, ikke guarden. Ikke overselg: guarden er defense-in-depth på tekst-laget, ikke en vegg.
7. IKKE i scope nå (scope-grense)
- Ingen kildekode-endring i denne økta. Ingen ny dependency lagt til
pyproject.toml. - Ingen endring i
shared/eller ingest-spec (commons, PULL-ONLY). - Ingen wiring i
run_project. - Åpne operatør-beslutninger (ikke-blokkerende): (a) hvilke gater som faktisk skal wires ved M3-forberedelse
(anbefaling: gate 1+2 obligatorisk, gate 3 opt-in, gate 4 kun ved received-bundle); (b) om et
screened_at-provenance-felt skal inn i ingest-spec-en (krever commons-amendment — koordiner med D-B/S2.4).
8. Verifisering (testbare kriterier — for den framtidige wiring-økta, ikke nå)
Når guarden faktisk wires (S2.4/S2.5-utvidelse), skal disse holde:
- Load-bearing detach: en test der en kjent injeksjons-payload (zero-width-kodet instruksjon i en
CSV-celle /
rationale) MÅ gi FAIL_SECURE (gate 1:IngestError; gate 2: skip); detach guard-kallet → payloaden når bundelen/storen → RØD. - Fail-secure retning: gate 1 raiser (fail-fast), gate 2 skipper (tolerant) — bevist per gate.
- Ingen regresjon:
test_okf_is_maf_free+test_ingest_loadbearinggrønne (guarden drar ikke inn MAF). - Determinisme/offline:
grep-guard bekrefter ingen ny socket-sti; guard-kallet gjør null nett/modell. - Denne planen (nå):
docs/plan/2026-07-16-llm-ingestion-guard-inclusion.mdfinnes; STATE.md bærer markørlinjallm-ingestion-guard: planned — …(grep-bar av guard-repoets rollup).