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
206 lines
14 KiB
Markdown
206 lines
14 KiB
Markdown
# 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 dem** — `Origin.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» | **FEIL** — `v0.3.4`, syv publiserte tagger | `git -C ~/repos/llm-ingestion-pipeline-security tag` |
|
|
| Guarden er tung å adoptere | **FEIL** — `dependencies = []`, stdlib-only | `pyproject.toml` i guard-repoet |
|
|
| Vår okf-pin drar inn guarden | **NEI** — `v0.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 | **NEI** — `simulation.py` importerer ikke `ingest` | `grep "^from portfolio_optimiser" simulation.py` |
|
|
| Ingesten skanner innhold i dag | **NEI** — `ingest.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 | **Nei** — `materialize` 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 --stat` på `shared/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 |
|