1
0
Fork 0
llm-ingestion-pipeline-secu.../docs/GATE-G-v1.md

21 KiB
Raw Blame History

Beslutningsgrunnlag — Session G, v1.0-frysen

Målt: 2026-08-13, mot HEAD = 2466d26 (v0.7.0 + tre test-commits). Hva dette er: underlaget for én operatørbeslutning — skal Python-surfacen fryses som 1.0.0. Hva dette ikke er: beslutningen. Ingen tag, ingen versjonsbump, ingen frosset surface er utført i økten som skrev dette. Ingen kode er endret.

Alle tall her er produsert av en kommando i samme økt; verifiseringsloggen står nederst. docs/PLAN-v1.md:231 er gate-teksten dette måles mot.


1. Rammen som gjør gaten tellbar

docs/PLAN-v1.md:24 sier det selv: v1.0 er primært et governance-løfte under semver. Det er ikke en påstand om at biblioteket er ferdig, og ikke en påstand om at de 35 begrensningene er borte. Uten den rammen leses docs/LIMITATIONS.md som 35 blokkere, og dokumentet argumenterer mot sin egen konklusjon.

Med rammen blir spørsmålet tellbart: hvor mange av de 35 kan bare lukkes ved å endre betydningen, formen eller medlemskapet til noe i __all__? Bare de blokkerer, fordi bare de tvinger 2.0.0. Tre bøtter, én per begrensning:

  • (a) lukkes med kalibrering, lexicon-data eller et predikat → ikke-brytende
  • (b) lukkes additivt (ny funksjon, nytt felt, ny implementasjon i en eksisterende søm) → minor
  • (c) lukkes bare ved å endre et eksportert symbols betydning eller form → major
  • () kan ikke lukkes i det hele tatt (permanent konsesjon, scope-grense, ren måling)

En fjerde akse er uavhengig av semver og må ikke blandes med den: hvilke begrensninger som, når de lukkes, fyrer konsument-varslingsplikten i docs/PLAN-v1.md:419. Et løfte kan brytes av en endring som er helt lovlig under semver. Den aksen er merket separat under.


2. Gatens literalkrav — hva som faktisk er sant

2.1 Session AF ferdig — OPPFYLT

F var den siste operatør-gaten, og den landet som anbefalt spor F1 (konsesjon), ikke som TODO: docs/PLAN.md:309 fører .pdf-raden som conceded, docs/PLAN.md:323 og README.md:226 beskriver den som «refused as an unsupported format», ikke som et kjent hull. A, A2, B, C, D, E er ute i tagger (v0.3.0v0.7.0, tolv tagger totalt).

2.2 Suite, dekningsmatrise og dokumenttall — OPPFYLT, re-målt

påstand kilde målt i dag
792 tester STATE 792 passed in 13.11s
129/129 klasser STATE/README Caught classes: 129/129 demonstrated (recall 100%)
6/6 dokumenterte gap STATE Documented gaps: 6/6 still hold as documented
35 begrensninger README grep -c '^- \*\*'35

Ingen avvik. CHANGELOG.md [Unreleased] er tom («Nothing yet»), så det finnes ingen ushippet atferd som frysen ville binde uten å ha beskrevet.

2.3 Ni versjonsflater synkrone — OPPFYLT på 0.7.0

Alle ni current-state-flatene docs/PLAN-v1.md:277 navngir står på 0.7.0: pyproject.toml:7, __init__.py:66, README badge/status/install-pin (:5, :36, :46), SECURITY.md:9 («pre-1.0 (0.7.x, alpha)»), docs/BRIEF.md:6, CLAUDE.md:14, docs/ADOPTION-BRIEF.md:7/:143. Sorteringen current-state vs. proveniens er ikke gjort her — den hører til selve release-utførelsen, og docs/PLAN-v1.md:290 sier den kommer før første redigering.

2.4 «Første ekte integrasjon grønn» — IKKE OPPFYLT ETTER BOKSTAVEN, dekket etter hensikten

Dette er gatens tyngste krav og det eneste som ikke lar seg avgjøre med en kommando i dette repoet. Måling av hva som faktisk foreligger, lest fra coord-arkivet:

dato hva okf faktisk gjorde guard-versjon
2026-07-26 0.3.1-målingen utsatt (kvote)
2026-07-31 «signalet er mottatt og rutet, kjøringen er ikke gjort»
2026-08-02 scratch-venv, resolvet versjon bekreftet via importlib.metadata, ingen nye avvik 0.3.4
2026-08-12 differensial på egen dør, begge dører, Python 3.11 og 3.14, tagger resolvet 0.3.4 → 0.6.1

Kjøringen gaten ber om ordrett — fixture-settet grønt mot v0.3.1 — ble aldri gjort. Den ble utsatt to ganger og deretter overhalt av virkeligheten: okf gikk rett på 0.3.4 og senere på en 0.3.4→0.6.1-differensial.

Målt mot gatens hensikt (docs/PLAN-v1.md:19-25: bevisbyrden skal komme utenfra, og release-hygienen skal ha overlevd én ekte syklus) er det som foreligger sterkere enn det som ble bestilt: en differensial på konsumentens egen dør, på to Python-versjoner, med taggene resolvet — som dessuten korrigerte en påstand vi hadde publisert («raw-html loosening is 1 of 3 forms»). En fixture-kjøring mot en tagg vi valgte ville ikke ha gjort det.

Restgapet er presist og lite: ingen har kjørt 0.7.0-runtimen. Nyeste guard-versjon noen integrator har eksekvert er 0.6.1. 0.7.0-deltaet er bærersplitten og no-URL-narrowingen. Det deltaet har én ekstern kryss-sjekk fra en annen vinkel: llm-security-commons bygde klassifikatoren opp igjen fra sin egen JSON, uten import fra pakken vår, og differensialtestet mot active_tag_class over 42 probe-tagger med 0 uenigheter. Det validerer klassifikatoren som data, ikke runtime-atferden.

2.5 Surface-deltaet siden sist eksternt pinnede versjon — MÅLT

git diff v0.3.4..HEAD -- src/llm_ingestion_guard/__init__.py, per symbol:

symbol endring siden 0.3.4 semver-klasse målt eksternt?
Risk lagt til (0.5.0, aksesplittelsen) additiv nei
DEFAULT_ACTION_MAP lagt til (0.5.0) additiv nei
assert_within_input_cap lagt til (0.4.0) additiv nei
OversizeInputError lagt til (0.4.0) additiv nei
alle øvrige 42 uendret navn og signatur 0.3.4 / 0.6.1

Ingen symboler er fjernet eller omdøpt siden 0.3.4. Verifisert med git diff v0.3.4..HEAD -- __init__.py | grep '^-': de eneste slettede linjene er versjonsstrengen, en kommentar, og en __all__-linje som ble skrevet om for å legge til navn. Hele surface-veksten er additiv.

__all__ er ikke hele den frosne flaten — okf eksporteres som navnerom, så signaturene der fryses også. Målt separat (git diff v0.3.4..HEAD -- okf.py | grep -E '^[-+](def |class )'): én endring, link_graph(bundle)link_graph(bundle, max_scan_chars=MAX_SCAN_CHARS). En keyword-parameter med default, bakoverkompatibel for enhver eksisterende kaller — men den gjør okf.link_graphs trunkeringsgrense til del av kontrakten fra 1.0.0. Det som har flyttet seg er atferd inne i allerede eksporterte funksjoner — elleve commits over src/, hvorav de som endrer utfall er: input-cap-refusjonen (0.4.0), aksesplittelsen (0.5.0), rå-HTML-narrowingen (0.6.0), ZWJ-kontekstfiksen (0.6.1) og bærersplitten (0.7.0).

Det er den ærlige formuleringen av risikoen ved å fryse nå: formen er stabil, atferden har beveget seg i fem strekk, og fire eksporterte symboler har aldri vært gjennom en ekstern kjøring.


3. De 35 begrensningene, bøttet

Hver rad navngir det eksporterte symbolet lukkingen ville røre, eller «ingen». Linjenummer er docs/LIMITATIONS.md. ⚠️ = lukking fyrer konsument-løfte 1.

# linje begrensning (kort) rører bøtte
1 :7 strukturell uløselighet i tekstlaget ingen
2 :11 lone HIGH i trusted prosa → WARN PRESET_TRUSTED_SOURCE, Policy a
3 :17 karantenegulvet er no-op under upload-preset Policy.quarantine_default a
4 :26 semantisk/faktisk poisoning usynlig SourceGroundingCheck (søm finnes) b
5 :30 adversarial-ML-evasion, tokenizer-mismatch ingen
6 :33 dormant / broken-link-injeksjon okf.link_graph b
7 :38 OKF reserverte filer (index.md/log.md) okf.import_bundle (avgjort i A2)
8 :43 én-nøkkels blokksekvens misparses stille okf.import_bundle b ⚑
9 :63 T2 begrenser import, ikke emisjon okf b
10 :68 OKF v0.2-konsept kan ikke traversere import okf b
11 :75 persist-gate dekker ikke kjøringsrisiko ingen
12 :83 dokument som beskriver angrep er FP ingen
13 :88 tospråklig tekst tripper homoglyf-regelen lexicon-data a ⚑ («fix is pending»)
14 :93 insider-redigeringer utenfor trusselmodell ingen
15 :95 text-only, parser ingen filer ingen
16 :99 kun ekstrahert tekst skannes; .pdf konsedert dev-showcase (F1)
17 :112 lexicon-funn dedupliseres per id (count=1) Finding.count a
18 :115 ren beaconing er bare LOW calibration.py a ⚠️
19 :123 korte opake URL-segmenter slipper gjennom scan_entropy-terskler a
20 :130 percent-escapes teller som databærende kalibrering a ⚠️
21 :166 percent-escape slår ut tokeniseringen kalibrering a
22 :190 legitime CDN-hex-id-er tripper permanent kalibrering a
23 :197 ikke-tom query graderes som databærende kalibrering a ⚠️
24 :219 rå-HTML-residualet er ekte HTML, ikke over-reach is_active_tag a
25 :267 bærersplitten strammer trusted tier shippet 0.7.0
26 :292 count teller ikke lenger endetagger Finding.count (allerede flyttet)
27 :300 stor minoritet av benigne dokumenter persisterer ikke måling
28 :391 URL-fragmenter graderes ikke kalibrering a ⚠️
29 :396 hex-innpakket secret-egress fanges ikke scan_entropy b
30 :402 prosa som nevner <script> fyrer XSS-labelen lexicon-data a
31 :426 connstr-passord >256 tegn matches ikke MAX_CONNSTR_VALUE a
32 :441 ReDoS-sveipet har en målt følsomhetsgrense metode
33 :460 cap-asymmetrien: noen reiser, andre trunkerer OversizeInputError + tre funksjoner c
34 :492 Severity bærer fortsatt disposisjonsintensjon Severity, Finding c ⚠️
35 :511 ZWJ mellom emoji unntatt; ZWNJ urørt predikat/kalibrering a

Sum: 2 i bøtte (c). 6 i (b). 15 i (a). 12 kan ikke lukkes.

De to (c)-punktene — den faktiske gaten

:492 — Severity bærer disposisjonsintensjon. 0.5.0 skilte vurdering (Risk) fra handling (Disposition), men bare på kallersiden. Inne i detektorene er en Findings Severity fortsatt kalibrert delvis etter disposisjonen den skal produsere. To steder i treet sier det rett ut (ACTIVE_CONTENT_ORDINARY_SEVERITY = LOW, og karantenegulvet hevet til MEDIUM+). Kostnaden: en ordinær ekstern <img> registreres som lav severity i stedet for som en reell utoverrettet fetch-kapabilitet som ikke er bevis på angrep — så ingen policy, uansett streng, kan handle på kapabiliteten, fordi detektoren allerede har bestemt at den ikke betydde noe. Lukking krever en kanal som sier hva som ble sett atskilt fra hvor ille det er. Det endrer Finding og Severity. Det er 2.0.0. Det fyrer også løfte 1.

:460 — cap-asymmetrien. sanitize, fence og neutralize reiser OversizeInputError over 1 000 000 tegn; deteksjonsflatene trunkerer og emitterer active:oversize-input. Begge valg er begrunnet (de tre returnerer innhold, der en avkortet retur er stille datatap eller en bypass). Men asymmetrien er en surface-egenskap, ikke kalibrering: å gjøre dem like senere betyr enten en ny exception der en kaller i dag får en verdi, eller motsatt. Frysen gjør asymmetrien permanent i 1.x.

Ingen av de to er defekter som må fikses. Begge er valg som må konsederes bevisst og skriftlig før frysen, ikke stå som «deferred». Forskjellen mellom en konsesjon og en utsettelse er nettopp hva 1.0.0 lover.

Én åpen korrekthetsdefekt som ikke er (c)

:43 — en blokksekvens med nøyaktig én nøkkel per element parses stille til feil type (sources:\n - uri: https://e.com/a gir strengen, ikke en mapping), slik at en peker kan ri gjennom i en nøkkel resource-allowlisten aldri inspiserer. Lukking strammer hva okf.import_bundle slipper inn — atferd, ikke form, og konvensjonelt shippbart i en minor med note. Den blokkerer altså ikke frysen, men den bør ikke oppdages av noen andre etter at vi har lovet stabilitet. Nevnt her fordi «vi visste, og valgte å fryse først» er en holdbar posisjon og «vi hadde glemt den» ikke er det.


4. De to låste konsumentløftene — målt før/etter i samme økt

docs/PLAN-v1.md:419 binder oss til å varsle linkedin-studio før enhver endring i graderingen av ordinære lenker/bilder under PRESET_USER_UPLOAD. De pinner v0.3.1. Mellom 0.3.1 og 0.7.0 flyttet både rå-HTML-narrowingen og bærersplitten grading. Spørsmålet er om noen av dem traff den lovede stien. Målt med samme probe mot begge trær (git archive v0.3.1 scratch-tre vs. HEAD), samme økt:

tilfelle v0.3.1 v0.7.0
ordinær markdown-lenke WARN / LOW WARN / LOW
ordinært markdown-bilde WARN / LOW WARN / LOW
autolink WARN / LOW WARN / LOW
refdef WARN / LOW WARN / LOW
relativ lenke WARN / rent WARN / rent
lenke med query QUARANTINE_REVIEW / MEDIUM QUARANTINE_REVIEW / MEDIUM
<a href="…"> FAIL_SECURE / HIGH QUARANTINE_REVIEW / MEDIUM
<a aria-label="…"> FAIL_SECURE / HIGH WARN / rent
</a> FAIL_SECURE / HIGH WARN / rent
<iframe src> FAIL_SECURE / HIGH FAIL_SECURE / HIGH
<div onclick> FAIL_SECURE / HIGH FAIL_SECURE / HIGH
ZWJ-komponert emoji FAIL_SECURE / HIGH WARN / rent

Løfte 1 er ikke brutt på den stien det navngir. Alle fire ordinære markdown-formene — lenke, bilde, autolink, refdef — gir identisk disposisjon og identisk max-severity på 0.3.1 og 0.7.0. Det er den formen linkedin-studio pinner og bygger på.

Fire rader flyttet seg likevel, og alle i løsnende retning. Tre rå-HTML-bærere og ZWJ-fiksen. Løftets ordlyd er «ordinære lenker/bilder», og en <a href> er en ordinær lenke — bare i en annen bærer enn den løftet ble skrevet om. Løftets begrunnelse er derimot eksplisitt: «en stille re-stramming lander som produksjonsincident hos dem». Ingen av de fire er en stramming. Om ordlyden eller begrunnelsen styrer, er en operatørbeslutning (D4 under). Å konstatere bevegelsen er vår plikt; å avgjøre om den fyrer løftet er ikke.

Den ene grenen har en konsekvens som allerede er påløpt, og den må stå ved siden av valget. Løftet krever varsel før endringen shippes. Styrer ordlyden, ble varselet ikke gitt — ikke for 0.6.0, ikke for 0.6.1 og ikke for 0.7.0. docs/PLAN-v1.md:423 kaller det å bryte ett av de to løftene stille «en release-defekt, ikke en preferanse». 1.0.0 ville da være fjerde utgivelse forbi det. Botemiddelet på den grenen er et etterskuddsvarsel til linkedin-studio før frysen, med de fire målte radene — men det er operatørens å autorisere, ikke vår å sende på eget initiativ, nettopp fordi det er en innrømmelse av brudd.

Hva proben sammenlignet, og hva den ikke gjorde. Den sammenlignet disposition og max_severity. Den sammenlignet ikke label-identitet, og konsumenter nøkler på labels. «Identisk» i tabellen over betyr altså identisk utfall, ikke bevist identisk label-sett.

Løfte 2 (relativ-mål-asymmetrien mot llm-ingestion-okf) er urørt: den relative lenken er ren på begge versjoner.


5. Hva 1.0.0 faktisk binder oss til

Positivt, og verdt å si tydelig fordi det er lett å undervurdere: surfacen har ikke mistet et eneste symbol siden 0.3.4. Hele veksten er additiv. Fire minor-utgivelser har lagt til fire navn og ikke fjernet noen. Det er nettopp den formstabiliteten en 1.0 lover, og den er målt, ikke antatt.

Det 1.0.0 binder:

  1. __all__ med sine 46 navn (len(llm_ingestion_guard.__all__)) — ingen kan fjernes eller omdøpes før 2.0.0.
  2. Severitys doble rolle (:492) — permanent i 1.x.
  3. Cap-asymmetrien (:460) — permanent i 1.x.
  4. Finding.counts betydning, som nettopp flyttet i 0.7.0 (:292). Frysen kommer én utgivelse etter at et publisert felt endret tallverdi for hvert dokument med </a>.
  5. DEFAULT_ACTION_MAP som del av kontrakten, ikke som implementasjonsdetalj.

Punkt 4 er den skarpeste innvendingen mot å fryse akkurat nå, og den fortjener å stå uten pynt: vi ville fryse feltet ett steg etter at det sist beveget seg.


6. Den lukkede beslutningsmengden

Seks beslutninger. Ingen av dem kan tas av denne økten.

# beslutning status
D1 Teller okfs 0.3.4-måling + 0.3.4→0.6.1-differensialen som gatens «første ekte integrasjon grønn», når 0.3.1-kjøringen gaten ber om aldri ble gjort? operatørvalg
D2 Skal frysen skje på 0.7.0, når nyeste eksternt kjørte runtime er 0.6.1? Alternativer: (i) frys på 0.7.0 nå og før restgapet som residual, (ii) be okf kjøre sin eksisterende differensial én gang til på 0.7.0 først, (iii) frys på 0.6.1-atferd. operatørvalg
D3 Skal :492 (Severity-kanalen) og :460 (cap-asymmetrien) konsederes permanent i 1.x og skrives om fra «deferred» til konsesjon — eller lukkes før frysen? operatørvalg
D4 Fyrer rå-HTML-løsningen 0.3.1→0.7.0 varslingsplikten mot linkedin-studio? Ordlyden («ordinære lenker/bilder») sier kanskje ja; begrunnelsen (stramming = incident) sier nei. Sier ordlyden ja, er varselet allerede uteblitt i tre utgivelser, og valget inkluderer om et etterskuddsvarsel skal gå ut før frysen. operatørvalg
D5 :88 sier «a calibration fix is pending». Skal den lukkes før frysen, eller skrives om til en konsesjon? En løs ende med ordet «pending» i en 1.0 er et løfte vi ikke har gitt. operatørvalg
D6 Er 0.7.0 active_tag_class en settled shape llm-security-commons kan pinne som data en tredje implementør holdes til? Dette er frysebeslutningen for den flaten — svaret på deres melding følger av D2. operatørvalg
AF ferdig; suite/dekning/dokumenttall; ni versjonsflater synkrone; [Unreleased] tom oppfylt
Fixture-kjøring grønn mot v0.3.1 etter gatens ordlyd ikke oppfylt, og blir det ikke

7. Anbefaling

Gaten bør åpnes, på 0.7.0, uten å vente — D2 (i). Med to forbehold som ikke koster en økt hver.

Begrunnelsen er ikke at bevisene er komplette. Den er at det som mangler er tynt og kryss-sjekket fra en annen kant: 0.7.0-deltaet er én klassifikator, og den er uavhengig rekonstruert av llm-security-commons fra deres egen JSON og differensialtestet mot vår over 42 probe-tagger med 0 uenigheter. Fire eksporterte symboler er aldri eksternt kjørt, men alle fire er additive — en konsument som ikke kaller dem merker dem ikke.

Motargumentet, som er reelt: 0.7.0 er nøyaktig det området okf har målt to ganger, og Finding.count flyttet seg der for én utgivelse siden. En integrator som er primet til å måle akkurat dette billig, er den beste kilden vi har. Det som taler imot å vente er historikken: 0.3.1-målingen ble utsatt 26. juli, aldri hentet inn, og overhalt av at okf gikk videre på egen hånd. En gate som venter på et annet repos kvote er en gate som kan bli stående åpen i ukevis. Vi bør varsle okf om 0.7.0, ikke gjøre frysen avhengig av at de svarer.

Forbehold 1 (D3): skriv :492 og :460 om fra «deferred deliberately» til «konsedert i 1.x» i LIMITATIONS.md, og la SECURITY.md si hva 1.x faktisk lover. Det er tekstarbeid, ikke kodearbeid, og det er forskjellen mellom et løfte vi kan holde og et vi bare har formulert.

Forbehold 2 (D5): ta ordet «pending» ut av :88, i én av to retninger. Enten lukkes kalibreringen, eller så er den en konsesjon.

Det som ville endret anbefalingen: at okf svarer at 0.7.0-bærersplitten treffer deres .md-dør i en form de ikke har målt. Da er én kjøring verdt ventetiden, fordi det er den eneste flaten hvor 0.7.0 kan ha gjort noe vi ikke vet om.


8. Verifiseringslogg

påstand kommando resultat
suite grønn PYTHONPATH=src .venv/bin/pytest 792 passed in 13.11s
dekning PYTHONPATH=src .venv/bin/python -m llm_ingestion_guard.coverage 129/129, 6/6, exit 0
35 begrensninger grep -c '^- \*\*' docs/LIMITATIONS.md 35
surface-delta git diff v0.3.4..HEAD -- src/llm_ingestion_guard/__init__.py 4 tillegg, 0 fjerninger
atferdsflytt git log --oneline v0.3.4..HEAD -- src/ 11 commits
tagger git tag --list v0.1.0v0.7.0 (12)
løfte 1 probe kjørt mot git archive v0.3.1 scratch-tre og HEAD, samme skript 4 ordinære markdown-former identiske; 4 rader løsnet
.pdf konsedert grep -n -i pdf README.md docs/PLAN.md docs/PLAN.md:309 «conceded»
versjonsflater grep over de ni flatene docs/PLAN-v1.md:277 navngir alle 0.7.0
[Unreleased] sed -n '1,14p' CHANGELOG.md «Nothing yet»
integrasjonshistorikk coord-arkivet, meldinger fra llm-ingestion-okf 0.3.1-kjøring utsatt 07-26, aldri gjort; 0.3.4 målt 08-02; 0.3.4→0.6.1 målt 08-12

Probe-skriptet lå i en scratch-katalog og er ikke sporet — det er tolv linjer som kjører screen_output(text, PRESET_USER_UPLOAD) over tolv faste input og skriver disposition | max_severity | reasons. Reproduseres på et minutt mot et hvilket som helst par tagger.