portfolio-optimiser/docs/plan/2026-08-09-innholdsgate-og-aerlighet.md
Kjell Tore Guttormsen c255662802 feat(ingest): P2/S1.b — innholdsgaten står, rundt materialize og ikke i den
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
2026-08-09 22:58:00 +02:00

14 KiB

Innholdsgate og ærlighet — plan fram til demoen 13. august

SPOR B LANDET 2026-08-09 — alle seks kriterier grønne

materialize_gated i src/portfolio_optimiser/ingest.py; gate-pin v0.3.4. 801 → 810 tester. Fem mutasjoner målt røde mot HELE suiten + grønn kontroll.

§3s premiss var FEIL, og målingen felte det før byggingen. Planen plasserer gaten i ingest.materialize, «repoets ENE skrivepunkt på Door A». Målt er materialize en ren delegasjon til det pinnede llm_ingestion_okf v0.3.2s materialize_bundle, som stager i minnet og utfører sin egen disk-fase. Ingen callback finnes mellom de to — en gate plassert der kunne bare kjørt ETTER at bytene hadde landet, altså 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, kollisjons-gaten mot kuratert innhold og §6 index-merge leser ALLE den EKSISTERENDE bundelen. Staging i en tom temp-katalog ville mistet alle tre og publisert en bundle uten kuraterte naboer og deres index-lenker — et datatapsbug forkledd som en sikkerhetsfiks. Mutasjon 4 beviser det.

De fire beslutningene i §4, tatt og målt

# Beslutning Utfall Begrunnelse
1 Policy-preset Ingen av demOrigin.EXTERNAL + Channel.AUTOMATIC import_bundle tar ikke et preset; trust_for utleder policy fra origin alene. PRESET_USER_UPLOAD bærer quarantine_default=True (opplastings-semantikk Door A ikke har); PRESET_TRUSTED_SOURCE gir en tier en manifest-navngitt ekstern kilde ikke har fortjent
2 Fail-closed, per dokument eller per bundle Utfall per BUNDLE, diagnostikk per DOKUMENT materialize_bundle er allerede alt-eller-ingenting per manifest — den sletter hver stemplet fil og regenererer index.md. Delvis publisering ville etterlatt en bundle + index som svarer til INTET manifest. Planens motargument («ett av femti avvist gir en halv bundle») er argumentet FOR: per-bundle gir ingen halv bundle. import_bundle itererer likevel forbi første avvisning, så visibiliteten taper ingenting
3 Hvor havner Report log.md (OKF §7), ALDRI konsept-frontmatter Konseptbytene er det pinnede bibliotekets, og fire golden-suiter pinner dem. Et gate-felt i frontmatter ville brutt alle fire. test_gated_output_is_byte_identical_to_ungated_materialize er vakten som holder det ute
4 mypy --strict over sømmen Override OG adapter, som planen krevde Override alene gjør sømmen type-BLIND, ikke type-sikker: alt ankommer som Any. _stamp_line + koersering i verdicts stopper Any ved grensen

Kriterier (§6)

  1. uv sync grønn; llm_ingestion_guard.__version__0.3.4
  2. Load-bearing-test grønn; fem mutasjoner røde (planen krevde tre) + kontroll
  3. uv run pytest -q grønn, antall økt 801 → 810
  4. ruff check + ruff format --check + mypy src rene
  5. Demo-stdout BYTE-IDENTISK med tests/golden/demo-transcript.stdout — gaten lekket ikke inn i det som fryses. Målt både av golden-testen i suiten og ved eksplisitt kjøring
  6. git diff --stat shared/ tomt

Mutasjons-manifest (alle mot HELE suiten, ~120 s hver)

# Mutasjon Utfall
1 Detach gate-kallet (if False) 🔴 4 tester
2 Gaten fyrer ETTER publisering (advarsel, ikke gate) 🔴 3 tester
3 Origin.INTERNAL som default 🔴 2 tester
4 Staging i TOM katalog i stedet for kopi 🔴 1 test — og KUN den; 809 andre merket ingenting
5 Rapporter kun FØRSTE avviste dokument 🔴 1 test
Kontroll: urørt kode 🟢 810 passed / 4 skipped

Lærdom — mutasjon 3 avslørte en vakuøs test, og rettelsen kom av å måle. Første kjøring av mutasjon 3 felte KUN logg-testen; alle tre avvisnings-testene forble GRØNNE. Årsak: en hard injeksjon scorer fail_secure under BEGGE trust-tierene, så beslutning 1 så dekket ut uten å være testet. Målingen fant båndet der tieren faktisk avgjør — et høy-entropi-innhold gir quarantine_review under EXTERNAL og warn under INTERNAL — og test_external_origin_refuses_what_internal_would_publish (med trusted-kontroll) ble skrevet mot nøyaktig det. Uten den re-målingen ville §4-beslutning 1 stått ubevist bak en grønn suite.

Lærdom 2 — den lave dispositionen er warn, ikke allow. Et RENT konsept scorer warn; Disposition er warn < quarantine_review < fail_secure, og noe allow finnes ikke. En gate skrevet mot == allow ville avvist hvert dokument som noensinne ingestes. Kontroll-testen er det som fanger den klassen.

§5-ærlighetsavsnittet: bruk JA-varianten. Gaten er landet, så setningen som leses på scenen er «ingest-veien skanner nå innholdet før det skrives, med en egen sikkerhetskomponent». P1 og P3 (håndkuratert bundle, ingen generisk fabrikk) er UENDRET ærlighets-punkter.

Utløser. Demoen viser «last ned → kjør». Sier vi, eller antyder vi, at man kan peke systemet mot sine egne kilder og bygge en kunnskapsbase, påstår vi noe koden ikke bærer. Det er A5-brudd i muntlig form — koden får ikke påstå mer enn den gjør, og det gjelder også presentatøren.

Kjører parallelt med demo-uke-planen (2026-08-06-demo-uke-plan.md), ikke i stedet for den. Den planen eier kjørestien og fryser den onsdag 12. Denne eier Door A, som ikke er på kjørestien.

0. Målte premisser (verifisert 2026-08-09, ikke gjenbrukt fra eldre dokument)

Påstand Målt Kommando
Guarden er «v0.2 alpha» FEILv0.3.4, syv publiserte tagger git -C ~/repos/llm-ingestion-pipeline-security tag
Guarden er tung å adoptere FEILdependencies = [], stdlib-only pyproject.toml i guard-repoet
Vår okf-pin drar inn guarden NEIv0.3.2 har dependencies = [] git show v0.3.2:pyproject.toml
Vi må vente på release-nummer-striden NEI — den gjelder utgivelsen etter v0.3.4 guard-repoets STATE
Door A er på demoens kjøresti NEIsimulation.py importerer ikke ingest grep "^from portfolio_optimiser" simulation.py
Ingesten skanner innhold i dag NEIingest.py:20-25 sier det selv modul-docstring

Den første raden er grunnen til at denne planen finnes: inkluderingsplanen fra 16. juli (2026-07-16-llm-ingestion-guard-inclusion.md) sier «v0.2 (alpha)», og det premisset gjorde adopsjonen urimelig risikabel. Målt er den tre mindre-versjoner nyere og uten avhengigheter. Den planen skal merkes som utdatert på dette punktet, ikke slettes.

1. De tre påstandene, presist — og hvilke som kan lukkes

Formuleringen «tre ting som ikke holder» var upresis. Målt er de:

# Påstand Status Til torsdag?
P1 Bundelen i demoen ble laget av pipelinen Nei — den er håndkuratert i commons Ærlighet. Kan ikke bygges bort
P2 Ingesten skanner innhold før skriving Neimaterialize skriver det den får JA — bygges
P3 Det finnes en generisk bundle-fabrikk (okf-toolkit, T0) Nei — eget repo, finnes ikke, utsatt ved O1 Ærlighet. Nytt repo på fire dager er utelukket

To av tre lukkes med kode, én med en setning. Å forsøke P3 før torsdag ville brutt scope-vaktet (O1 er en tatt beslutning) og satt demoen i spill for en gevinst ingen tilhører etterspør.

2. To spor, med vilje adskilt

Spor A — demoen. Eies av demo-uke-planen. Fryses onsdag 12. Rører ikke ingest.py. Spor B — innholdsgaten. Denne planen. Rører KUN ingest.py + pyproject.toml + ny test.

Adskillelsen er ikke kosmetisk: simulation.py importerer ikke ingest (målt), så Spor B kan lande etter frysen uten å røre det som fryses. Faller Spor B, står demoen uendret — den mister kun retten til å si at gaten finnes, som er nøyaktig statusen i dag.

3. Dagsplan

Søndag 9. / mandag 10. — Spor B, byggingen. Wire llm_ingestion_guard inn i ingest.materialize (ingest.py:167), som er repoets ENE skrivepunkt på Door A. Fire beslutninger må tas, ikke antas — de er listet i §4.

Tirsdag 11. — GO/NO-GO på commons (Spor A, uendret). Spor B rører ikke commons; de to konkurrerer ikke om samme fil.

Onsdag 12. — generalprøve + frys (Spor A). Spor B skal være committet FØR frysen hvis den skal nevnes i demoen, men den er ikke en frys-betingelse. Er den ikke ferdig: si status som den er, ikke som den var planlagt.

Torsdag 13. — demo. Ærlighets-avsnittet leses opp som skrevet i §5.

4. Byggingen — fire beslutninger som må måles, ikke antas

  1. Hvilken policy-preset? Guarden eksporterer PRESET_TRUSTED_SOURCE og PRESET_USER_UPLOAD. Door A henter fra eksterne kilder oppgitt i et manifest — det er ikke det samme som en fil en bruker laster opp, og ikke det samme som en kilde vi eier. Les begge presetene i installerte bytes før du velger. Feil preset gir enten en gate som slipper alt gjennom eller en som gjør ingest ubrukelig.
  2. Fail-closed eller flagg-og-skriv? Repoets presedens er entydig — promote_verdict er fail-closed, write_concept_file validerer og reparerer aldri. Forslag: avvist dokument skrives IKKE, og materialize raiser en typet feil på linje med IngestStampError. Motargument som må avvises eksplisitt før valget: en bundle der ett av femti dokumenter avvises blir en halv bundle. Avgjør om avvisning er per dokument eller per bundle.
  3. Hvor havner guardens Report? Provenance er en invariant her. Funnene skal ikke kastes — de hører i ingest_manifest, ved siden av eierskaps-stempelet. Avgjør felt-navn og form.
  4. mypy --strict over sømmen. Guarden shipper ingen py.typed, så symbolene ankommer som Any. okf løser det med en [[tool.mypy.overrides]]-blokk (ignore_missing_imports = true) OG en adapter som koerserer hver verdi til konkret type. Kopier begge deler — kun overriden ville gjort --strict meningsløs på vår side av sømmen.

Avhengigheten pinnes som okf gjør det, git-kilde + tagg: llm-ingestion-guard = { git = "https://git.fromaitochitta.com/open/llm-ingestion-pipeline-security.git", rev = "v0.3.4" }. Aldri en flytende ref på en sikkerhetskomponent.

Load-bearing-krav (ufravikelig). En test som bare er grønn beviser ingenting. Testen skal:

  • mate materialize et fikstur-dokument med kjent injeksjons-innhold, og kreve at det IKKE skrives;
  • ha en kontroll: et rent dokument som skrives uendret (ellers kan ikke testen skille «gaten virker» fra «ingenting skrives»);
  • måles RØD ved minst tre mutasjoner: detach gate-kallet · gjør avvisningen til en advarsel · bytt til en preset som slipper alt gjennom.
  • Måles mot HELE suiten, med restaurering fra scratchpad + shasum -c.

5. Ærlighets-avsnittet — teksten som faktisk sies på scenen

Erstatter ingenting i demo-uke-planens §1; kommer i tillegg, og leses opp:

«To ting om innholdet dere ser. For det første: denne kunnskapsbasen er laget for hånd, ikke produsert av systemet. Det finnes en ingest-vei som henter eksterne kilder inn i formatet, men eksempelet her gikk ikke gjennom den, og den generiske fabrikken som skulle laget slike baser er ikke bygget — den er bevisst utsatt. For det andre, om sikkerhet: [HVIS SPOR B ER LANDET] ingest-veien skanner nå innholdet før det skrives, med en egen sikkerhetskomponent, slik at forgiftet kildeinnhold ikke havner i basen. [HVIS IKKE] ingest-veien skriver i dag det den får, uten innholds-skanning. Gaten er planlagt og komponenten finnes, men den er ikke koblet inn ennå. Peker dere dette mot upålitelige kilder i dag, er det deres ansvar, ikke systemets.»

De to variantene er skrevet på forhånd med vilje: valget mellom dem er en observasjon onsdag, ikke en vurdering torsdag morgen.

6. Verifisering — konkrete kriterier

  1. uv sync grønn med guarden som deklarert dep; uv run python -c "import llm_ingestion_guard as g; print(g.__version__)"0.3.4.
  2. Ny load-bearing-test grønn; tre mutasjoner målt røde mot hele suiten, med kontroll.
  3. uv run pytest -q grønn — og antallet skal ha økt, ikke bare være grønt.
  4. uv run ruff check . + uv run mypy src rene, med --strict fortsatt meningsfull over sømmen (adapter, ikke bare override).
  5. Demoens kjøresti byte-uendret: uv run python -m portfolio_optimiser.simulation gir fortsatt åtte merkede steg og identisk stdout over to kjøringer. Dette er Spor B's viktigste kriterium — det beviser at gaten ikke lekket inn i det som fryses.
  6. git diff --statshared/examples/ → tomt. Goldenene er load-bearing.

7. Hva denne planen IKKE gjør

  • Bygger ikke okf-toolkit (T0/D-G). O1 står. Håndteres av §5.
  • Adopterer ikke okf v0.4.0+. Vi står på v0.3.2 gjennom demoen; v0.4.0+ gjør guarden til hard runtime-dep, og den koblingen tas etter demoen, ikke før.
  • Rører ikke commons. Den har sin egen frist 11. august og sin egen økt.
  • Rører ikke docs/presentasjon-portfolio-optimiser.html — eies av en annen sesjon.
  • Wirer ikke guarden på bundle-LESE-stien. Gaten sitter ved skriving (Door A). En bundle som allerede ligger i repoet er kuratert av et menneske; å skanne den ved lesing er en annen beslutning med en annen begrunnelse, og den er ikke tatt.

8. Risiko

Risiko Utslag Tiltak
Preset-valget viser seg feil under bygging Gate som slipper alt, eller blokkerer alt §4.1: les begge i installerte bytes FØR valg; testen har både positiv og kontroll
Guarden avviser demo-bundelen ved en feil Panikk onsdag Gaten sitter kun på Door A; demo-bundelen leses, ikke ingestes. Kriterium 5 fanger lekkasje
Spor B tar lengre tid enn ventet Ett løfte mindre i demoen §5 har ferdigskrevet NEI-variant; ingen improvisasjon på scenen
mypy --strict blir meningsløs over sømmen Stille typetap §4.4: adapter OG override, som okf gjorde