1
0
Fork 0

docs(gate): the v1.0 freeze is countable, and two of thirty-five limits are the gate

This commit is contained in:
Kjell Tore Guttormsen 2026-08-13 22:02:28 +02:00
commit aff35118bf

335
docs/GATE-G-v1.md Normal file
View file

@ -0,0 +1,335 @@
# 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.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 40 | uendret navn og signatur | — | 0.3.4 / 0.6.1 |
**Ingen symboler er fjernet eller omdøpt siden 0.3.4.** Hele surface-veksten er
additiv. 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 `Finding`s
`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.
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. `Severity`s doble rolle (:492) — permanent i 1.x.
3. Cap-asymmetrien (:460) — permanent i 1.x.
4. `Finding.count`s 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. | **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.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.