450 lines
29 KiB
Markdown
450 lines
29 KiB
Markdown
# Re-planlagt roadmap — v1.0 (Python) + Node/TS-port
|
|
|
|
**Forfattet:** Fable 5, 2026-07-09 (kryssmodell-review, `docs/review-2026-07.md`);
|
|
**promotert til live sesjonsplan** 2026-07-10. Sporet docs-fil; hjem = Forgejo `open/`
|
|
(eneste sanksjonerte offentlige flate, aldri GitHub). Utfyller `docs/PLAN.md`
|
|
(høynivå byggeplan) med detaljerte, Opus-eksekverbare sesjon-specer.
|
|
|
|
**Mållinje (bindende):** (a) en shippet, klasseledende **v1.0 av Python-biblioteket**
|
|
(lukk review-funn + format/kvalitets-gaps, konsolider terskler, verifiser
|
|
novelty-claimet, docs, versjons-sync + publish), OG (b) en **Node/TS-port** over den
|
|
delte JSON-lexicon. Node-porten starter FØRST når Python-v1.0-surfacen er frosset og
|
|
scan-ren (Session G). Stream 4 (pre-adaptasjons-scan) og consumer-integrasjon hører
|
|
til «Ambisiøse utvidelser» (review Del 1), ikke v1.0-sekvensen.
|
|
|
|
> **REVIDERT 2026-07-25 — v1.0-gaten er strammet, og en 0.3.0 er skutt inn foran G.**
|
|
> Setningen over sier at consumer-integrasjon IKKE er del av v1.0-sekvensen. Den
|
|
> gjelder ikke lenger. Operatør-beslutning: **1.0.0 gates på at den første ekte
|
|
> integrasjonen kommer grønt tilbake** (`llm-ingestion-okf` steg 4 / deres v0.4.0),
|
|
> ikke på vår egen suite. Begrunnelse: 522 egenskrevne tester + en egenskrevet
|
|
> coverage-matrise beviser at koden gjør det vi designet, ikke at designet overlever
|
|
> kontakt med virkeligheten. Bevisbyrden kom utenfra — okf fant, i sin FØRSTE
|
|
> utveksling med oss, at main hadde divergert fra v0.2.0 på `import_bundle`, og at
|
|
> CHANGELOG manglet tre atferdsendringer. Koden bestod; utgivelses-hygienen gjorde
|
|
> det ikke. v1.0 er primært et governance-løfte under semver, så det løftet avgis
|
|
> først etter én validert release-syklus.
|
|
>
|
|
> **`v0.3.0` er derfor kuttet 2026-07-25** (tag pushet; `467b9e3`). Den bærer
|
|
> Session A/A2/B ut til konsumenter som fortsatt pinner `v0.2.0` og dermed ikke har
|
|
> noe av hardningen — og den er det som i det hele tatt gjør ekte tilbakemelding
|
|
> mulig. Minor og ikke patch fordi A2 (`0772daf`) satte `allow_reserved=True` som
|
|
> default og dermed LØSNET en gate. Node-porten (TRACK 2) er fortsatt blokkert bak G.
|
|
|
|
**Endringer mot låst roadmap (STATE «re-sekvensert 2026-07-06»):** steg 2 «modne
|
|
guarden» utvides med review-injiserte fiks-sesjoner (A/B/C/D/E) FØR release (G).
|
|
`.pdf` (steg 2i) blir en eksplisitt operatør-beslutning (Session F) med anbefaling
|
|
om **konsesjon**. Node-porten (gammelt steg 3) splittes i P0-P7 med en delt
|
|
parity-fixture som ryggrad.
|
|
|
|
**Format per sesjon:** Mål · Scope-grense · Avhengigheter · Filer · TDD-plan ·
|
|
Nøkkelantakelser (+ test) · Verifisering. Testkommando alltid:
|
|
`PYTHONPATH=src .venv/bin/pytest …`. Én sesjon ≈ «Les STATE.md og følg instruksen».
|
|
|
|
---
|
|
|
|
## TRACK 1 — Python v1.0
|
|
|
|
### Session A — Aktivt-innhold-detektor wiret inn i gaten *(injisert av review MAJOR #1)*
|
|
|
|
- **Mål:** `screen_output` og `okf.import_bundle` skal surface EchoLeak-klassen
|
|
(markdown-bilde/lenke/refdef/autolink/aktiv-HTML) som findings som mater
|
|
`disposition` — uten å bryte report/mutasjon-separasjonen.
|
|
- **Scope-grense:** rører IKKE lexicon/entropy/secret-logikk, contract, fence,
|
|
sanitize. Ingen ny runtime-dep (stdlib-only). `neutralize`s *muterende* API
|
|
beholdes uendret (bakoverkompatibelt).
|
|
- **Avhengigheter:** ingen (kan starte først).
|
|
- **Filer:** nytt `src/llm_ingestion_guard/active_content.py` (report-only
|
|
`scan_active_content(text, source) -> Report`, OWASP LLM05); refaktor
|
|
`neutralize.py` til å dele regex-tabellen; edit `output.py` (`scan_output` steg 6:
|
|
kall `scan_active_content`); edit `__init__.py` (eksporter `scan_active_content`);
|
|
edit `tests/test_showcase.py` + `tests/test_okf_showcase.py` (plant EchoLeak-vektor);
|
|
ny `tests/test_active_content.py`.
|
|
- **TDD-plan (failing FØRST):**
|
|
1. `test_active_content.py::test_markdown_image_is_reported` — `scan_active_content("")` inneholder label `active:markdown-image`, severity HIGH. (Rødt: modulen finnes ikke.)
|
|
2. `test_screen_output_reports_echoleak` — `screen_output("", PRESET_USER_UPLOAD).disposition` er QUARANTINE_REVIEW+ (ikke WARN).
|
|
3. `test_okf_import_flags_body_echoleak` — bundle med markdown-bilde i body → aggregat ≠ WARN.
|
|
4. Minimal impl: del regexene, report-only pass, wire i `scan_output`.
|
|
5. Regresjon: hele suiten grønn (neutralize-tester uendret).
|
|
- **Nøkkelantakelser (+ test):**
|
|
- *«neutralize og den nye detektoren kan dele samme regex-tabell uten
|
|
atferdsendring i neutralize.»* Test: eksisterende `tests/test_neutralize.py`
|
|
passerer uendret etter refaktor.
|
|
- *«severity-valget (HIGH for bilde) gir ønsket disposition under begge preset.»*
|
|
Test: assertion 2/3 over. Risiko hvis feil: for lav severity → fortsatt WARN;
|
|
testes eksplisitt.
|
|
- **Verifisering:**
|
|
- `PYTHONPATH=src .venv/bin/pytest tests/test_active_content.py tests/test_showcase.py tests/test_okf_showcase.py` → alle grønne.
|
|
- `PYTHONPATH=src .venv/bin/python -c "from llm_ingestion_guard import screen_output, PRESET_USER_UPLOAD, Disposition; d=screen_output('', PRESET_USER_UPLOAD); assert d.disposition is not Disposition.WARN, d"` → exit 0.
|
|
- `python -c "import tomllib,pathlib; assert tomllib.loads(pathlib.Path('pyproject.toml').read_text())['project']['dependencies']==[]"` → exit 0 (kjerne-invariant intakt).
|
|
|
|
### Session A2 — OKF reservert-fil-håndtering (`index.md`/`log.md`) *(injisert av review MAJOR #2)*
|
|
|
|
- **Mål:** `import_bundle` skal behandle legitime reserverte strukturfiler
|
|
(`index.md`/`log.md`, spec §3.1/§6/§7) som *skann-body-men-ikke-path-rejekt*, ikke
|
|
hard-avvise dem — og faktisk skanne `index.md`-bodyen (lest først, høyest-prioritert
|
|
injeksjonsflate). Behold shadow-rejektet i upload/front-end-konteksten.
|
|
- **Scope-grense:** rører IKKE `validate_concept_path`s oppførsel i *upload*-konteksten
|
|
(front-end shadow-reject beholdes). Ingen endring i T1/T2/T3-gatene. Kun mode-b
|
|
bundle-import-grenen.
|
|
- **Avhengigheter:** ingen kode-avhengighet av A; men bør landes FØR G (frys). Kan
|
|
parallelliseres med A/B/C.
|
|
- **Filer:** edit `src/llm_ingestion_guard/okf.py` (`_validate_concept`/`import_bundle`:
|
|
reservert-basenavn → skann-gren i stedet for path-reject; `link_graph` uendret);
|
|
edit `tests/test_okf.py` + `tests/test_okf_showcase.py` (nytt: legitimt bundle med
|
|
`index.md`/`log.md` ADMITer; injeksjon i `index.md` FANGES; shadow-upload i front-end
|
|
REJECTer fortsatt).
|
|
- **TDD-plan (failing FØRST):**
|
|
1. `test_okf.py::test_legit_index_and_log_admit` — bundle {index.md, log.md,
|
|
tables/users.md} (rene) → aggregat WARN, ingen `error` på index/log. (Rødt i dag:
|
|
FAIL_SECURE, verifisert i review-proben.)
|
|
2. `test_okf.py::test_injection_in_index_body_is_caught` — injeksjon i `index.md`-body
|
|
→ concept-report har `override:ignore-previous`. (Rødt i dag: findings=[].)
|
|
3. `test_okf_inbox_uploads.py::test_reserved_name_upload_is_rejected` — MÅ fortsatt
|
|
REJECTe (front-end shadow-reject bevart).
|
|
4. Minimal impl: skill reservert-basenavn i bundle-import (skann-body) fra
|
|
upload-materialisering (shadow-reject).
|
|
- **Nøkkelantakelser (+ test):**
|
|
- *«index.md/log.md kan skannes som tekst uten path-reject uten å svekke
|
|
shadow-vernet i upload-konteksten.»* Test: assertion 1-3 samlet — legit bundle
|
|
admits, index-injeksjon fanges, upload-shadow rejects.
|
|
- *Risiko:* `okf_version`-frontmatter er tillatt KUN i bundle-root `index.md`
|
|
(spec). Hvis body-skann kjører `parse_frontmatter` på en index.md kan strict-gaten
|
|
tripp. Test: `test_index_with_okf_version_frontmatter_admits` — skann index.md-body,
|
|
ikke reject på lovlig `okf_version`.
|
|
- **Verifisering:**
|
|
- `PYTHONPATH=src .venv/bin/pytest tests/test_okf.py tests/test_okf_showcase.py tests/test_okf_inbox_uploads.py` → alle grønne.
|
|
- `PYTHONPATH=src .venv/bin/python -c "from llm_ingestion_guard import okf; r=okf.import_bundle({'index.md':'---\nokf_version: 0.1\n---\n# Listing\n','tables/users.md':'---\ntype: t\n---\nclean\n'}); assert r.disposition.value=='warn', [ (c.path,c.disposition.value,c.error) for c in r.concepts ]"` → exit 0.
|
|
|
|
### Session B — base64-innpakket secret-egress *(injisert av review MINOR)*
|
|
|
|
- **Mål:** decode-and-rescan skal også kjøre `scan_secret_egress` over dekodet
|
|
base64-plaintext, så en base64-innpakket credential fanges av LLM02-gaten.
|
|
- **Scope-grense:** kun `output.py` decode-rescan-løkken (steg 3). Ingen endring i
|
|
entropy-klassifisering, lexicon, eller egress-mønstrene selv.
|
|
- **Avhengigheter:** ingen (uavhengig av A; kan parallelliseres).
|
|
- **Filer:** edit `src/llm_ingestion_guard/output.py` (steg 3: legg til
|
|
`scan_secret_egress(blob.decoded)` med `decoded:egress:*`-relabel); edit
|
|
`tests/test_output.py`; edit README honest-limits (restgap: hex-innpakket).
|
|
- **TDD-plan:**
|
|
1. `test_output.py::test_base64_wrapped_secret_is_caught` — output med
|
|
base64(AWS-nøkkel, fragment-bygget gitleaks-safe) → finding-label
|
|
`decoded:egress:aws-access-key-id`. (Rødt i dag — Probe 3 bekreftet [].)
|
|
2. Minimal impl: i decode-rescan-løkken, kjør også `scan_secret_egress` på
|
|
`blob.decoded`, relabel `decoded:<label>`, bær blob-offset.
|
|
3. Restgap-test: hex-innpakket secret er FORTSATT ikke fanget → dokumenter som
|
|
honest-limit (bevisst avgrensning, ikke stille miss).
|
|
- **Nøkkelantakelser (+ test):**
|
|
- *«evidence bærer aldri secret-verdien, også for den dekodede varianten.»* Test:
|
|
assert nøkkel-fragmentet ikke i `finding.evidence`.
|
|
- **Verifisering:**
|
|
- `PYTHONPATH=src .venv/bin/pytest tests/test_output.py` → N grønne (N = før +2).
|
|
- `PYTHONPATH=src .venv/bin/python /path/to/probe.py` (Probe 3 fra reviewen) → base64-linjen viser nå `decoded:egress:aws-access-key-id`.
|
|
|
|
### Session C — Novelty-survey + README/BRIEF-reframe *(injisert av review MAJOR #2)*
|
|
|
|
- **Mål:** erstatt det uverifiserte/absolutte novelty-claimet med den forsvarbare
|
|
kompositt-kontrakt-formen; oppdater BRIEF §11 fra «assumed» til verifisert-med-
|
|
avgrensning.
|
|
- **Scope-grense:** docs only (`BRIEF.md`, `README.md`). Ingen kodeendring. Ingen
|
|
ny absolutt novelty-setning.
|
|
- **Avhengigheter:** ingen.
|
|
- **Filer:** edit `docs/BRIEF.md` §11; edit `README.md` posisjonering; ev. edit
|
|
`docs/PLAN.md`-posisjonering (§19-45) — men PLAN er live-plan, la Opus avgjøre om
|
|
den røres eller kun refereres.
|
|
- **TDD-plan (docs — verifiserbar via review, ikke pytest):** ingen failing test;
|
|
i stedet en **verifiseringslogg** i BRIEF §11 som lister `ipi-scanner` +
|
|
`aig-guardian` med URL og hvorfor de ikke motbeviser kompositt-kontraktet.
|
|
- **Nøkkelantakelser (+ test):**
|
|
- *«ingen bibliotek pakker det fulle firdelte kontraktet som minimal-dep kode.»*
|
|
Test: gjenta PyPI/GitHub-surveyen (søk «RAG ingestion security», «write-time
|
|
prompt injection», «ipi-scanner», «ingest guard»); bekreft at ingen ny kandidat
|
|
dekker karantene+isolasjon+scan-før-persist+fail-secure samlet. Merk dato.
|
|
- **Verifisering:** `grep -n "assumed, not verified" docs/BRIEF.md` → tom (claimet
|
|
ikke lenger uverifisert); `grep -niE "query-time.*or hosted|the only" README.md`
|
|
→ ingen absolutt formulering igjen.
|
|
|
|
### Session D — Kalibrerings-konsolidering *(injisert av review Akse 4; Node-prereq)*
|
|
|
|
- **Mål:** samle alle kalibrerings-konstanter (entropy-gulv, MAX_SCAN_CHARS,
|
|
rot13-min, cognitive-load-lengder, disposition-rangeringer) i én dokumentert flate
|
|
`calibration.py`, så Node-porten kan speile *nøyaktig* samme tall.
|
|
- **Scope-grense:** **ren refaktor — null atferdsendring.** Ingen terskeljustering
|
|
(det er en separat, senere kalibrerings-oppgave). Kun flytting + navngiving.
|
|
- **Avhengigheter:** bør komme ETTER A (så aktivt-innhold-severities også bor der).
|
|
- **Filer:** nytt `src/llm_ingestion_guard/calibration.py`; edit `entropy.py`,
|
|
`lexicon.py`, `disposition.py`, `active_content.py` til å importere derfra.
|
|
- **TDD-plan:**
|
|
1. Snapshot-test FØRST: kjør hele suiten, lagre at 321(+delta) er grønne.
|
|
2. Flytt konstanter; importer.
|
|
3. Regresjon: **identisk** testresultat (ingen ny/endret assertion) beviser
|
|
null atferdsendring.
|
|
- **Nøkkelantakelser (+ test):** *«flyttingen endrer ingen verdi.»* Test: hele
|
|
suiten grønn uendret; en eksplisitt `test_calibration.py` asserter de konkrete
|
|
tallene (5.4/128, 5.1/64, 4.7/40, 1_000_000, 40, 2000/2500) som en frossen
|
|
kontrakt Node-porten deler.
|
|
- **Verifisering:** `PYTHONPATH=src .venv/bin/pytest` → samme antall grønne som før
|
|
sesjonen (ingen delta i test-count utover `test_calibration.py`).
|
|
|
|
### Session E — Docs/versjons-sync + SECURITY/CONTRIBUTING + honest-limits *(injisert av review MINORs)*
|
|
|
|
- **Mål:** fjern versjons-drift; legg til manglende åpen-kildekode-artefakter;
|
|
oppdater honest-limits med de residualene reviewen avdekket.
|
|
- **Scope-grense:** docs/metadata only. Ingen kodeendring.
|
|
- **Avhengigheter:** etter A/B/C (så honest-limits reflekterer faktisk tilstand).
|
|
- **Filer:** `README.md` (badge `tests-275`→faktisk N; status `v0.1`→`v1.0`;
|
|
honest-limits: HIGH-i-trusted-prosa-residual, base64/hex-secret-restgap,
|
|
quarantine-floor-note); `docs/BRIEF.md:6-7` (fjern «No code yet»); nytt
|
|
`SECURITY.md` (disclosure-policy, Forgejo-kontakt); nytt `CONTRIBUTING.md`.
|
|
- **TDD-plan:** ingen pytest; verifiser via grep-sjekker under.
|
|
- **Nøkkelantakelser (+ test):** *«badge-tallet matcher faktisk suite.»* Test:
|
|
badge-N == `pytest`-output.
|
|
- **Verifisering:**
|
|
- `PYTHONPATH=src .venv/bin/pytest -q | tail -1` → «N passed»; `grep -n "tests-${N}_passing" README.md` treffer.
|
|
- `grep -niE "v0\.1|275_passing|No code yet" README.md docs/BRIEF.md` → tom.
|
|
- `test -f SECURITY.md && test -f CONTRIBUTING.md` → exit 0.
|
|
|
|
### Session F — `.pdf`-beslutning *(operatør-gate; to gjensidig utelukkende spor)*
|
|
|
|
- **Mål:** avklar det siste format-gapet. **Anbefaling: konsesjon (F1).**
|
|
- **Rasjonale for konsesjon:** front-end er en *dev-scoped showcase*, ikke shippet
|
|
kode; README honest-limits sier allerede pdf-ekstraksjon er upålitelig; å legge
|
|
til `reportlab` KUN for å *lage* white-on-white-test-fixtures er uforholdsmessig
|
|
(to nye dev-deps for et demo-format). Binærlag-carriers (OCR/font-stego) er
|
|
uansett eksplisitt out-of-scope. Konsesjon svekker ikke v1.0.
|
|
- **Spor F1 (anbefalt) — Konseder `.pdf` permanent:**
|
|
- Filer: `README.md` honest-limits (`.pdf` = bevisst honest-limit, ikke TODO);
|
|
`docs/PLAN.md` §247-tabell (marker `.pdf`-raden «conceded»).
|
|
- Verifisering: `grep -n "pdf" README.md` viser konsesjon, ikke «known gap».
|
|
- **Spor F2 (kun hvis operatør vil ha .pdf) — Bygg `.pdf`-slice:**
|
|
- **Operatør-gate FØRST:** bekreft `pypdf` (lesing) + `reportlab` (skrive
|
|
white-on-white fixtures) som **nye `[dev]`-deps** — aldri core. Kjerne-invariant
|
|
`dependencies=[]` MÅ holde.
|
|
- Filer: `pyproject.toml` (`[dev]` += `pypdf`, `reportlab`); `tests/inbox_frontend.py`
|
|
(`_extract_pdf` + dispatch `.pdf`); `tests/test_okf_inbox_uploads.py` (slice 2i:
|
|
`_make_pdf` med white-on-white + normal-tekst injeksjon, detach-proof).
|
|
- TDD: `test_pdf_whiteonwhite_injection_is_caught` (rødt) → `_extract_pdf` →
|
|
grønt; `test_pdf_detach_proof`.
|
|
- Verifisering: `PYTHONPATH=src .venv/bin/pytest tests/test_okf_inbox_uploads.py`
|
|
→ +N grønne; `python -c "import tomllib,pathlib; d=tomllib.loads(pathlib.Path('pyproject.toml').read_text()); assert d['project']['dependencies']==[] and 'pypdf' in ' '.join(d['project']['optional-dependencies']['dev'])"` → exit 0.
|
|
- **Avhengigheter:** uavhengig; kan gjøres når som helst før G.
|
|
|
|
### Session G — v1.0 freeze + release *(FRYSER Python-surfacen — Node-prereq)*
|
|
|
|
- **Mål:** shippe v1.0.0; fryse den offentlige surfacen som porten oversetter.
|
|
- **Scope-grense:** ingen ny feature. Kun versjons-bump, CHANGELOG, tag, push.
|
|
- **Avhengigheter:** A, B, C, D, E, F ferdig ✅ (alle review-funn lukket/konsedert per
|
|
2026-07-25) **PLUSS den reviderte gaten: første ekte integrasjon grønn** — se
|
|
REVIDERT-blokken øverst. Konkret: `llm-ingestion-okf` steg 4 / v0.4.0 kjører grønt
|
|
mot `v0.3.0`, OG ingen API-formsendring ut av den integrasjonen som ikke er absorbert.
|
|
Ett validert `integrated` teller mer enn fem `planned`-erklæringer; vi venter IKKE på
|
|
de øvrige (ms-ai-architect bygde sitt eget, `okr` er operatør-parkert).
|
|
- **Filer:** `pyproject.toml` (`version = "1.0.0"`, `Development Status :: 3 → 5 -
|
|
Production/Stable`); `README.md` badge + **`**Status:** \`v0.3\`, alpha`-linjen** +
|
|
**install-pinnen `@v0.3.0`**; `__init__.py` `__version__`; `CHANGELOG.md`.
|
|
**NB — entryen kan ikke lenger «liste A-F»:** A/A2/B ligger allerede ute under
|
|
`[0.3.0]`. `[1.0.0]` skal referere `[0.3.0]` for atferdsendringene og selv bære
|
|
frysepunktet (API-stabilitet + det integrasjonen beviste), ikke gjenta dem.
|
|
- **TDD-plan:** ingen ny test; hele suiten grønn er release-gaten.
|
|
- **Nøkkelantakelser (+ test):** *«alle versjonsreferanser er synkrone.»* Test:
|
|
grep alle fire filer for versjonsstreng, bekreft `1.0.0` overalt.
|
|
- **Verifisering:**
|
|
- `PYTHONPATH=src .venv/bin/pytest` → alle grønne.
|
|
- `grep -rn "1\.0\.0" pyproject.toml README.md src/llm_ingestion_guard/__init__.py CHANGELOG.md` → treffer i alle fire; `grep -rn "0\.3\.0" …` → ingen dangling ref utenfor CHANGELOG-historikken.
|
|
- Install-pinnen i README må verifiseres mot den NYE taggen i en ren venv (anonym
|
|
`pip install …@v1.0.0`) — README skal aldri avertere en kommando vi ikke har kjørt.
|
|
- `git tag v1.0.0` + push til `open/` (durabelt autorisert). **STATE.md røres ikke
|
|
av tag (local-only).**
|
|
|
|
---
|
|
|
|
### Session H — 0.3.1: regresjonsfiks for aktivt-innhold-kalibrering *(LANDET 2026-07-25)*
|
|
|
|
> **LANDET — `6e9b816`, tag `v0.3.1` pushet.** Begge defekter fikset sammen; repro-tabellen
|
|
> under er kjørt på nytt og snudd (ordinær lenke/bilde/autolink/refdef → `warn` på BEGGE
|
|
> dører; exfil-formet → `fail_secure`). 577 tester grønne, coverage-matrise exit 0
|
|
> (128/128 recall, 6/6 gap holder), anonym ren-venv-install av `@v0.3.1` verifisert.
|
|
> Nye residualer skrevet inn i `docs/LIMITATIONS.md` (beaconing, kort opak URL-del,
|
|
> percent-escape-FP) og asserteres av matrisen. Alle 7 varslede repo har fått MÅLINGENE.
|
|
> **Neste: Session G.** 0.4.0 er akse-separasjon (deteksjon ≠ disposisjon), ikke ny preset.
|
|
|
|
- **Mål:** gjøre den utrustede upload-stien brukbar igjen uten å miste EchoLeak-
|
|
deteksjonen, og lukke testgatens blindfelt som slapp regresjonen forbi 522 grønne tester.
|
|
- **Utløser:** `llm-ingestion-okf` projiserte konsekvensen fra CHANGELOG-teksten FØR
|
|
0.3.0 ble kuttet; vi shippet før vi leste innboksen. De traff retningen og
|
|
undervurderte alvoret.
|
|
|
|
**Målt på v0.3.0 (ikke antatt) — begge dører, `PRESET_USER_UPLOAD` / `origin=EXTERNAL`:**
|
|
|
|
| dokument | utfall |
|
|
|---|---|
|
|
| ingen lenker | `warn` |
|
|
| én ordinær lenke / autolink / refdef | `quarantine_review` |
|
|
| **én ordinær bilde-referanse** | **`fail_secure`** |
|
|
| relativt/lokalt bilde | `warn` |
|
|
|
|
- **Rotårsak — tre UAVHENGIGE defekter som forsterker hverandre:**
|
|
1. **Severity følger konstruksjons-TYPE, ikke URL-FORM.** `markdown-image: HIGH` fyrer
|
|
på ethvert eksternt bilde. Exfil-primitivet er ikke «et bilde» — det er en URL som
|
|
*bærer data utover*. `` bærer ingenting.
|
|
2. **Quarantine-floor fyrer på ENHVER finding** (`policy.quarantine_default and
|
|
report.found`). Premisset «findings er unntaket» brøt da hver lenke ble en finding.
|
|
Dette er den allerede dokumenterte «vakuøs quarantine-floor»-residualen.
|
|
3. **FP-korpuset kunne strukturelt ikke fange det:** `_FALSE_POSITIVE` (6 prosa-snutter)
|
|
har NULL markdown-lenker/bilder; asserteres KUN under `PRESET_TRUSTED_SOURCE` (hvor
|
|
alt gir `warn` uansett); og kjører `_scan_input`, så `scan_output` steg 6 — der
|
|
`active_content` faktisk bor — berøres aldri av korpuset.
|
|
- **Begge kodeendringer trengs sammen (verifisert):** kun (1) → floor karantenerer
|
|
fortsatt; kun (2) → HIGH-bilde fail_secure'er fortsatt.
|
|
- **Scope-grense:** INGEN ny offentlig API. **Ny mellom-preset er 0.4.0, ikke denne.**
|
|
Rør ikke trusted-tier-residualen (HIGH-i-trusted→WARN). Ingen omskriving/neutralize i
|
|
gaten — biblioteket reparerer ikke stille (bekreftet som bærende av okf).
|
|
- **Avhengigheter:** ingen.
|
|
- **Filer:** `calibration.py` (alle nye tall — låst konvensjon), `active_content.py`
|
|
(form-analyse), `disposition.py` (floor-terskel), `tests/test_corpus.py` (blindfeltet),
|
|
`tests/test_active_content.py`, `coverage.py`, `CHANGELOG.md`, `docs/LIMITATIONS.md`.
|
|
|
|
- **TDD-plan (failing test FØRST — Iron Law):**
|
|
1. Utvid `_FALSE_POSITIVE` med REALISTISKE markdown-dokumenter (ordinær lenke, bilde,
|
|
autolink, refdef, blandet) og legg til et FP-testtilfelle som kjører `screen_output`
|
|
under `PRESET_USER_UPLOAD`. **Dette skal være rødt før noen kildeendring** — det er
|
|
beviset på at gaten nå faktisk dekker regresjonen.
|
|
2. Form-analyse i `active_content`: ordinær ekstern URL (bar sti, ingen query, ingen
|
|
høy-entropi-segment) → LOW; exfil-form → behold HIGH/MEDIUM. Gjenbruk `entropy`-
|
|
modulen; ikke oppfinn ny heuristikk. `data-uri` og `raw-html` beholder HIGH
|
|
ubetinget (aktive uansett URL-form).
|
|
3. Floor-terskel i `_base_disposition`: fyrer på MEDIUM+ i stedet for enhver finding.
|
|
4. Adversarielt mot-korpus: exfil-formede URL-er (query som bærer verdi, høy-entropi
|
|
sti-segment/subdomene, percent/base64-payload) MÅ fortsatt nå HIGH → fail_secure.
|
|
|
|
- **Nøkkelantakelser (+ test):**
|
|
- *«0.3.1 er ærlig som patch, ikke minor.»* **VERIFISERT 2026-07-25:** lexicon har 0
|
|
LOW/INFO-mønstre (40 high / 22 medium / 21 critical) og ingen annen detektor
|
|
produserer LOW. Floor-endringen er derfor en **no-op for alt som fantes før 0.3.0** —
|
|
den løsner ingenting for en v0.2.0-konsument. Re-test:
|
|
`python -c "…Counter(severity)…"` skal fortsatt vise null LOW/INFO.
|
|
- *«form-heuristikken har ikke falske negativer på ekte exfil.»* Test: mot-korpuset i
|
|
TDD-steg 4. **Dette er den farlige antakelsen** — en for grov «ordinær»-definisjon
|
|
gjenåpner EchoLeak.
|
|
- *«ren beaconing forblir udekket.»* Et bilde som kun trigger et fetch uten å bære data
|
|
(IP/metadata-lekkasje) faller til LOW. Dette er et BEVISST nytt hull og **skal skrives
|
|
inn i `docs/LIMITATIONS.md`**, ikke ties i hjel.
|
|
|
|
- **Verifisering:**
|
|
- `PYTHONPATH=src .venv/bin/pytest` → alle grønne (`-q` skjuler summary — dropp den).
|
|
- `python -m llm_ingestion_guard.coverage` → exit 0; ingen dokumentert gap-assertion
|
|
brutt, og de nye FP-tilfellene er representert i matrisen.
|
|
- Repro-tabellen øverst kjørt på nytt: ordinær lenke/bilde/autolink/refdef → `warn`
|
|
på BEGGE dører; exfil-formet bilde → `fail_secure` under upload-preset.
|
|
- Versjons-sync 0.3.0→0.3.1 i alle fire filer + README install-pin; anonym ren-venv
|
|
`pip install …@v0.3.1` før README averterer den.
|
|
- Coord til de 7 varslede repoene med MÅLINGER, ikke beskrivelse (vi lovet det).
|
|
|
|
---
|
|
|
|
## TRACK 2 — Node/TS-port (stream 3). Starter etter Session G.
|
|
|
|
**Ryggrad:** en delt **parity-fixture** (`fixtures/parity/*.json`: `input → forventede
|
|
labels/severities`) som BÅDE Python og TS må tilfredsstille. Uten den porter du et
|
|
bevegelig mål. Den delte `injection_lexicon.json` **splittes aldri** (PLAN §13.3).
|
|
|
|
### Session P0 — Parity-fixture-ryggrad + TS-scaffold
|
|
- **Mål:** etabler golden-fixtures + TS-prosjektskjelett; Python-impl asserter mot
|
|
fixtures.
|
|
- **Scope-grense:** ingen TS-detektor-logikk ennå; kun scaffold + fixtures + Python-
|
|
parity-test.
|
|
- **Avhengigheter:** Session G (frossen surface) + D (kalibrering konsolidert).
|
|
- **Filer:** `fixtures/parity/{sanitize,lexicon,entropy,output,okf}.json`; ny
|
|
`tests/test_parity_fixtures.py` (Python-siden); `node/package.json`,
|
|
`node/tsconfig.json`, `node/vitest.config.ts`.
|
|
- **TDD-plan:** `test_parity_fixtures.py` kjører hvert fixture-input gjennom Python-
|
|
impl og asserter forventede labels (rødt til fixtures skrives, så grønt).
|
|
- **Nøkkelantakelse (+ test):** *«fixtures fanger den faktiske Python-atferden.»*
|
|
Test: Python-parity-test grønn.
|
|
- **Verifisering:** `PYTHONPATH=src .venv/bin/pytest tests/test_parity_fixtures.py`
|
|
→ grønt; `cd node && npm i && npx vitest run` → tomt/skjelett kjører.
|
|
|
|
### Session P1 — `report` + `severity` + lexicon-loader (TS)
|
|
- **Mål:** TS-typene + loader som leser SAMME `injection_lexicon.json`.
|
|
- **Avhengigheter:** P0.
|
|
- **Filer:** `node/src/report.ts`, `node/src/lexicon-loader.ts`, `node/test/*.test.ts`.
|
|
- **TDD:** vitest: loader kompilerer alle mønstre; antall == Python `load_lexicon()`.
|
|
- **Nøkkelantakelse (+ test):** *«JS-regex-motoren aksepterer alle mønstrene uten
|
|
flag-oversettelsestap.»* Test: hver pattern kompilerer; parity på pattern-count.
|
|
- **Verifisering:** `cd node && npx vitest run test/lexicon-loader.test.ts` → grønt;
|
|
count == `PYTHONPATH=src .venv/bin/python -c "from llm_ingestion_guard.lexicon import load_lexicon; print(len(load_lexicon()))"`.
|
|
|
|
### Session P2 — `sanitize` + `entropy` + `normalize` (TS)
|
|
- **Avhengigheter:** P1.
|
|
- **Filer:** `node/src/sanitize.ts`, `node/src/entropy.ts`, `node/src/normalize.ts`.
|
|
- **TDD:** vitest kjører `fixtures/parity/{sanitize,entropy}.json` → samme labels.
|
|
- **Nøkkelantakelse (+ test):** *«base64/rot13/homoglyph-primitiver gir bit-lik
|
|
output i JS og Python.»* Test: parity-fixtures grønne begge sider.
|
|
- **Verifisering:** `cd node && npx vitest run` (sanitize+entropy) grønt mot fixtures.
|
|
|
|
### Session P3 — `lexicon.scan` + variant-set (TS)
|
|
- **Avhengigheter:** P2.
|
|
- **Filer:** `node/src/lexicon.ts`.
|
|
- **TDD:** `fixtures/parity/lexicon.json` (raw/normalized/folded/rot13-varianter) →
|
|
samme dedupede labels.
|
|
- **Nøkkelantakelse (+ test):** *«dedup-by-id og variant-rekkefølge matcher.»* Test:
|
|
parity-fixture med multi-variant-treff.
|
|
- **Verifisering:** `cd node && npx vitest run test/lexicon.test.ts` grønt.
|
|
|
|
### Session P4 — `output` + `active_content` + `neutralize` + `disposition` (TS)
|
|
- **Avhengigheter:** P3. (Inkluderer aktivt-innhold fra Session A.)
|
|
- **Filer:** `node/src/output.ts`, `node/src/active_content.ts`,
|
|
`node/src/neutralize.ts`, `node/src/disposition.ts`.
|
|
- **TDD:** `fixtures/parity/output.json` + disposition-tabell-fixtures.
|
|
- **Nøkkelantakelse (+ test):** *«fail-closed + carrier/CRITICAL any-tier + compound
|
|
matcher Python.»* Test: disposition-parity-fixtures inkl. transform_failed-caset.
|
|
- **Verifisering:** `cd node && npx vitest run` (output+disposition) grønt mot fixtures.
|
|
|
|
### Session P5 — `contract`-asserters + top-level bookends (TS)
|
|
- **Avhengigheter:** P4.
|
|
- **Filer:** `node/src/contract.ts`, `node/src/index.ts` (`prepareInput`/`screenOutput`).
|
|
- **TDD:** tool-carrying request raiser; credential-leak raiser; happy path passerer.
|
|
- **Verifisering:** `cd node && npx vitest run test/contract.test.ts` grønt.
|
|
|
|
### Session P6 — OKF-adapter (TS)
|
|
- **Avhengigheter:** P5.
|
|
- **Filer:** `node/src/okf.ts`.
|
|
- **TDD:** port `_poisoned_bundle`/`_clean_bundle` fra `test_okf_showcase.py` som
|
|
fixture; samme aggregat-disposition + link-graf.
|
|
- **Nøkkelantakelse (+ test):** *«strict frontmatter-parser gir samme reject-set.»*
|
|
Test: OKF-parity-fixture (T2/T3/T4/T5a/dangling).
|
|
- **Verifisering:** `cd node && npx vitest run test/okf.test.ts` grønt.
|
|
|
|
### Session P7 — Parity-CI + Node-README + versjons-sync + tag
|
|
- **Avhengigheter:** P6.
|
|
- **Filer:** `node/README.md`, `node/package.json` (`version` synk med Python-linjen),
|
|
CI-hook som kjører begge suiter mot samme fixtures.
|
|
- **Verifisering:** både `PYTHONPATH=src .venv/bin/pytest` og `cd node && npx vitest
|
|
run` grønne mot samme `fixtures/parity/`; versjonsstreng synk; tag + push til `open/`.
|
|
|
|
---
|
|
|
|
## Avhengighetsgraf + anbefalt sekvens
|
|
|
|
```
|
|
A ─┐
|
|
A2 ─┤ (A, A2, B, C uavhengige; kjør i den rekkefølgen som passer)
|
|
B ─┤
|
|
C ─┤
|
|
├─► D ─► E ─┐
|
|
F ─┘ ├─► G (v1.0 FRYS) ─► P0 ─► P1 ─► P2 ─► P3 ─► P4 ─► P5 ─► P6 ─► P7
|
|
│
|
|
(F operatør-gated, uavhengig, må være lukket/konsedert før G)
|
|
```
|
|
|
|
- **A, A2, B, C** kan tas i valgfri rekkefølge (uavhengige). Start med **A** eller
|
|
**A2** (begge review-MAJOR; A = unsafe admit, A2 = over-block + uskannet index.md).
|
|
- **D** etter A (så aktivt-innhold-severities bor i `calibration.py`).
|
|
- **E** etter A/B/C (honest-limits skal reflektere faktisk tilstand).
|
|
- **F** når som helst før G; **anbefalt spor F1 (konsesjon)**. Dep-tillegg (F2) er
|
|
operatør-gate uansett.
|
|
- **G er frysepunktet.** Node-porten (P0-P7) starter FØRST etter G, ellers porter du
|
|
et bevegelig mål. P0 avhenger også av D (kalibrering konsolidert).
|
|
- **P0-P7** er sekvensielle (hver bygger på forrige), med parity-fixture som felles
|
|
kontrakt.
|
|
|
|
**.pdf-beslutningen sitter i F** (før G). **Python-frysen sitter i G** (før P0).
|