okf asked outright whether the v1.0 gate requires 'the tagged artifact resolves 0.3.1' or 'the fixture set passes against 0.3.1'. It is the second, and the answer is locked here so a later session cannot quietly upgrade the ask into a release request against their operator. They refused to widen their published range to run the measurement, and the refusal is better than the ask it answers: widening a published range in order to generate the evidence that would justify widening it is circular, and a red result would then have meant unwinding a release to fix a test setup. The measurement has to be able to say no. Their scratch-venv method -- outside the project environment, --no-deps, guard installed from the v0.3.1 tag, resolved version asserted from importlib.metadata -- satisfies the gate exactly.
502 lines
32 KiB
Markdown
502 lines
32 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.1`** (de har FORPLIKTET seg til å kjøre Door B+C-fixtures og sende
|
|
resultatet UANSETT utfall — et rødt FP-resultat er like verdifullt), 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).
|
|
- **⚠️ GATEN KAN IKKE ÅPNES AV ET GRØNT SVAR ALENE — verifiser hvilken versjon som ble
|
|
resolvet (funnet 2026-07-25, premiss-verifisering).** Setningen «v0.4.0 kjører grønt
|
|
mot v0.3.1» var skrevet mot en pin vi ikke hadde lest. okf pinner guarden
|
|
`>=0.2,<0.3` i `[project].dependencies` **og** `[tool.uv.sources]`-taggen til
|
|
`v0.2.0` — verifisert på deres `v0.4.0`-tag OG deres HEAD (`4ea00a9`). `<0.3`
|
|
ekskluderer både 0.3.0 og 0.3.1. Kjører de fixturene på et uendret tre, **feiler det
|
|
ikke** — uv resolver v0.2.0, constrainten er tilfredsstilt, og fixturene kommer
|
|
grønt tilbake fordi v0.2.0 aldri hadde regresjonen. Et grønt svar er dermed det
|
|
ENESTE utfallet som intet forteller og ser ut som det forteller alt.
|
|
**Gate-kravet, presist formulert (okf spurte rett ut 2026-07-25 — svaret er låst):**
|
|
gaten krever at **fixture-settet passerer mot guard 0.3.1**, med den resolvede
|
|
versjonen lest fra `importlib.metadata` ved kjøretid og oppgitt i resultatet. Gaten
|
|
krever **IKKE** at okfs taggede artefakt kan *resolve* 0.3.1 — det er en RELEASE-
|
|
beslutning eid av deres operatør, ikke et bevis om vår kalibrering. Vi stiller ingen
|
|
release-forespørsel. Målingen skal kunne si nei uten at noen først har shippet et
|
|
utvidet range; ville vi krevd det motsatte, ville vi bedt dem utgi en range som
|
|
slipper inn en versjon de ennå ikke har målt.
|
|
Deres metode (scratch-venv utenfor prosjektmiljøet, `--no-deps`, guard installert
|
|
direkte fra `v0.3.1`-taggen, resolved versjon assertert) tilfredsstiller gaten.
|
|
- **Transitiv ankomst teller IKKE som integrasjon** (portfolio-optimisers innsikt,
|
|
2026-07-25). okf v0.4.0 gjør guarden til en OBLIGATORISK runtime-dep, så en
|
|
konsument kan få oss i grafen uten å velge oss — og i dag lander de da på v0.2.0,
|
|
uten 0.3.0-hardningen og uten 0.3.1-fiksen. Stille under-forsvar, ikke brudd. En
|
|
konsument som fikk oss ved uhell måler ingenting med hensikt; grønt derfra beviser
|
|
mindre enn rødt fra en som valgte oss.
|
|
- **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.1`**; `__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]` + `[0.3.1]` 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).
|
|
|
|
---
|
|
|
|
## 🔒 Konsument-varslingsplikt — to LÅSTE løfter (gitt 2026-07-25)
|
|
|
|
Begge er gitt til navngitte konsumenter som bygger på nåværende atferd. De bor HER og
|
|
ikke bare i `STATE.md`, fordi STATE er LOCAL-ONLY og gitignored — et løfte som kun
|
|
finnes der overlever ikke en maskin. **Å bryte ett av disse stille er en release-defekt,
|
|
ikke en preferanse.**
|
|
|
|
1. **Gradering av ordinære lenker/bilder under `PRESET_USER_UPLOAD`** — enhver endring
|
|
krever varsel til `linkedin-studio` **før** den shippes. De pinner v0.3.1 ved wiring
|
|
og bygger på 0.3.1-formen; en stille re-stramming lander som produksjonsincident hos
|
|
dem, ikke som en release-note. Gjelder også 0.4.0: akse-separasjonen skal endre
|
|
DISPOSISJON, ikke graderingen — viser det seg feil under scoping, fyrer løftet.
|
|
2. **Relativ-mål-asymmetrien** (relative lenker/bilder i en OKF-bundle flagges ikke) —
|
|
lukkes den, får `llm-ingestion-okf` varsel **før** det shippes. Den er en Door
|
|
C-egenskap, ikke et guard-gap: et merget konsept skrives VERBATIM, så vi reparerer
|
|
og omskriver ikke (designprinsipp 3). Men lukking endrer hva som ankommer deres
|
|
persist-gate.
|
|
|
|
**Kjent, ikke modellert:** okf rapporterer at percent-escapes når filnavn gjennom en
|
|
slugger — dvs. `%20` produseres SYSTEMATISK fra dokumenttitler, ikke tilfeldig. Vår
|
|
percent-escape-FP ble kalibrert mot dokumentasjons-URL-er der escapes er insidentelle.
|
|
Er okfs mekanisme representativ, er dette en strukturell FP-klasse og ikke en sjelden
|
|
form. Måles i deres re-run og i `linkedin-studio`s korpus.
|
|
|
|
---
|
|
|
|
## 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).
|