21 KiB
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 A–F 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.0–v0.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:
__all__med sine 46 navn (len(llm_ingestion_guard.__all__)) — ingen kan fjernes eller omdøpes før2.0.0.Severitys doble rolle (:492) — permanent i 1.x.- Cap-asymmetrien (:460) — permanent i 1.x.
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>.DEFAULT_ACTION_MAPsom 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 |
| — | A–F 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.0 … v0.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.