build(deps): pin llm-ingestion-guard v1.3.0 so the gate reads our own goldens

The guard could not read back what this library WRITES. At 1.2.0,
`okf.parse_frontmatter` refused the OKF v0.2 golden outright --
`OKFFrontmatterError: value begins with a disallowed YAML indicator '['`
against `sources: [{ id: golden-v0-2-sales, resource: fixture }]`. Flow is
the only form this library can emit, because its own line-oriented parser
cannot round-trip the block form at all, so a gate that refuses flow
refuses everything Door A produces under `OKF_V0_2`.

The control was run BEFORE the bump, which is the only moment it exists:
the probe raised on 1.2.0, so the new test discriminates rather than
merely passes. `[project.dependencies]` already said `>=1.2,<2.0` and is
unchanged; only `[tool.uv.sources]` and `uv.lock` move.

TWO gate rows moved, not the one the work was scoped around, which is why
the whole documented probe was re-run instead of just the `sources` case:
the BLOCK form of `sources` now passes too, retiring G30. That changes
nothing about what we emit -- our own parser is still the binding
constraint on writing flow -- and `docs/okf-nokkelinventar.md` now carries
a `guard 1.3.0` column beside the 1.2.0 measurement rather than
overwriting it. A third row kept its verdict but changed its reason, so
the quoted message was corrected too.

The Door C boundary is unmoved, verified with a known-positive:
`resource` is allowlisted only inside a `sources` entry, so section
10.2's `executor.resource` and `attester.resource` are still rejected
("not on the OKF mapping allowlist under 'executor'") while top-level
`resource` passes.

`uv.lock` also gains `pypandoc-binary==1.17`. That is a stale lockfile
being corrected, not a new dependency: it was already declared in the
`[extract]` extra, and `uv lock --check` reports the lockfile out of date
on the untouched tree. Core keeps exactly one runtime dependency.

Not addressed, and recorded rather than built: the guard reports that
`sources[].resource` is scanned as text but never URL-validated, because
SPEC 5.1 permits bundle-relative paths and scope descriptions. No
consumer has asked for a gate there.

Guard 1.3.0 installed from 44e2b31, verified anonymously over https
against the remote tag. 1054 -> 1055 tests. `mypy --strict` clean, `ruff`
clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Kjell Tore Guttormsen 2026-09-03 20:35:27 +02:00
commit 2d9fb0f934
5 changed files with 92 additions and 24 deletions

View file

@ -49,14 +49,15 @@ V=~/repos/vegnormal-okf/docs/VEGNORMAL_V1.md
K1=~/repos/vegnormal-okf/docs/okf-spec-konformitet.md # ordre K1, levert 27.08
K3=~/repos/vegnormal-okf/docs/okf-eksempelkorpus.md # ordre K3, levert 28.08
R=~/repos/vegnormal-okf/docs/2026-08-26-resonnering-resultater.md
G=~/repos/llm-ingestion-pipeline-security/src/llm_ingestion_guard/okf.py # guard-kilden; installert 1.2.0 måles i .venv
G=~/repos/llm-ingestion-pipeline-security/src/llm_ingestion_guard/okf.py # guard-kilden; installert versjon måles i .venv
```
Nevnere: `find $E -name '*.md' | wc -l`**270** · `ls $M/*.md | wc -l` → **271**
(270 konsepter + `index.md`) · `find $B -name '*.md' ! -name index.md | wc -l`
**48 983** (hele `build/`, alle armer og grenbaser) · `wc -l $P`**1047** ·
`.venv/bin/python -c "import importlib.metadata as m; print(m.version('llm-ingestion-guard'))"`
**1.2.0** (= pinnen `pyproject.toml` l.35/113).
**1.3.0** (området `pyproject.toml` l.35 `>=1.2,<2.0`; taggen som avgjør
versjonen er l.139 `tag = "v1.3.0"` — var `v1.2.0` til 2026-09-03).
### 0.3 Tallene 17, 3 og 33 — produsert av kommando og avstemt mot operatørens
@ -89,7 +90,8 @@ find $E -name '*.md' -print0 | xargs -0 grep -l '^sources:' | wc -l # -
**3 i M (`type`, `title`, `status`) — operatørens tall holder for bundlen armene leste.**
I E er det **4**: `sources` skrives 270/270 av emitteren og fjernes før import fordi
guarden avviser formen (G30; målt på nytt mot 1.2.0 i § 0.7). Avstemt.
guarden avviste formen (G30; **falt ved 1.3.0**, § 0.7 — men M-tallet er målt på
en import kjørt under 1.2.0 og er ikke re-målt). Avstemt.
**33 egne:**
@ -185,7 +187,7 @@ datetime with an explicit UTC offset». Nøklene den binder: `generated.at`,
nøklene den strammet inn** (`stale_after`, `last_modified`, `usage_window`). Den gamle
`YYYY-MM-DD`-formen finnes hos oss bare i produsentnøkler (`kravdato`, `date`).
### 0.7 Guard 1.2.0 — hva porten faktisk slipper gjennom (probe, ikke målearm)
### 0.7 Guard 1.2.0 og 1.3.0 — hva porten faktisk slipper gjennom (probe, ikke målearm)
Kjørt mot den installerte guarden i `.venv` (samme som Dør C bruker), med kjent-positiv
først. Dette er verdiformer, ikke en tiende arm.
@ -208,19 +210,19 @@ for n, d in {
except Exception as e: print("REJECT", n, "->", str(e)[:70])'
```
| verdi | guard 1.2.0 | betydning |
|---|---|---|
| `title` én linje (kjent-positiv) | PASS | spørringen kan skille — `okf.parse_frontmatter` returnerer `({'type': 'Krav', 'title': …}, body)` |
| `description` én linje | **PASS** | en énlinjes `description` ville passert Dør C i dag (`okf.parse_frontmatter``{'description': 'One sentence …'}`) |
| `description` brettet over to linjer | REJECT | `nested mappings are not supported in OKF frontmatter` — G33s «29/53 brettet» står for upstreams bundler (`~/.claude/docs/okf-bundle-prosessen.md` l.130) |
| `tags: [a, b]` | REJECT | `value begins with a disallowed YAML indicator '['` |
| `tags:` blokkliste | **PASS** | G33s «44/53 blokk avvist … ingen sekvens-verditype» gjelder en eldre guard; 1.2.0 leser `['a', 'b']` (`okf.parse_frontmatter`) |
| `sources:` blokk-sekvens av mappinger (E-formen, `bundle.py` l.167) | REJECT | `a mapping is not expressible in OKF frontmatter`**G30 står ved 1.2.0** (registeret sa «UMÅLT mot 1.2.0», l.133; nå målt) |
| `sources: [{ … }]` flow-sekvens (Dør A under `OKF_V0_2`, `materialize.py` l.241) | REJECT | vår egen golden avvises: `.venv/bin/python -c 'from llm_ingestion_guard import okf; okf.parse_frontmatter(open("examples/ingest-golden-okf-v0-2/expected-bundle/ingest-sales.md").read())'` → `OKFFrontmatterError … '['` — den «målte begrensningen» i `$P` l.9981005 (mot 0.2.0) **står fortsatt** |
| `generated: { by, at }` flow-mapping | **PASS** | 1.2.0 leser mappingen som `{'by': …, 'at': …}` — kommentaren i `$P` l.9981003 («no expressible form») er foreldet for denne nøkkelen |
| `generated: true` (Dør B) | PASS | passerer porten som streng `'true'`; porten dømmer form, ikke § 5.2 |
| verdi | guard 1.2.0 | guard 1.3.0 | betydning |
|---|---|---|---|
| `title` én linje (kjent-positiv) | PASS | PASS | spørringen kan skille — `okf.parse_frontmatter` returnerer `({'type': 'Krav', 'title': …}, body)` |
| `description` én linje | **PASS** | PASS | en énlinjes `description` ville passert Dør C i dag (`okf.parse_frontmatter``{'description': 'One sentence …'}`) |
| `description` brettet over to linjer | REJECT | REJECT | `nested mappings are not supported in OKF frontmatter` — G33s «29/53 brettet» står for upstreams bundler (`~/.claude/docs/okf-bundle-prosessen.md` l.130) |
| `tags: [a, b]` | REJECT | REJECT | **samme dom, ny begrunnelse ved 1.3.0:** `a flow sequence admits flow mappings only: '[a, b]'` (var: `value begins with a disallowed YAML indicator '['`). En flow-sekvens av skalarer er fortsatt ute; det er flow-sekvensen av *mappinger* som åpnet |
| `tags:` blokkliste | **PASS** | PASS | G33s «44/53 blokk avvist … ingen sekvens-verditype» gjelder en eldre guard; 1.2.0 leser `['a', 'b']` (`okf.parse_frontmatter`) |
| `sources:` blokk-sekvens av mappinger (E-formen, `bundle.py` l.167) | REJECT | **PASS** | **G30 FALT ved 1.3.0.** Var `a mapping is not expressible in OKF frontmatter`. Men porten er ikke det eneste hinderet: *vår egen* parser er linjeorientert og kan ikke lese blokkformen tilbake, så at guarden nå tar imot den endrer ikke at dette biblioteket bare skriver flow (`CLAUDE.md § Upstream version policy`) |
| `sources: [{ … }]` flow-sekvens (Dør A under `OKF_V0_2`, `materialize.py` l.241) | REJECT | **PASS** | vår egen golden passerer nå: `.venv/bin/python -c 'from llm_ingestion_guard import okf; fm, _ = okf.parse_frontmatter(open("examples/ingest-golden-okf-v0-2/expected-bundle/ingest-sales.md").read()); print(fm["sources"])'` → `[{'id': 'golden-v0-2-sales', 'resource': 'fixture'}]`. Ved 1.2.0: `OKFFrontmatterError … '['`. Pinnet av `tests/test_guard_adapter.py::test_the_guard_parses_the_flow_form_sources_our_goldens_emit` |
| `generated: { by, at }` flow-mapping | **PASS** | PASS | 1.2.0 leser mappingen som `{'by': …, 'at': …}` — kommentaren i `$P` l.9981003 («no expressible form») er foreldet for denne nøkkelen |
| `generated: true` (Dør B) | PASS | PASS | passerer porten som streng `'true'`; porten dømmer form, ikke § 5.2 |
Guardens mapping-nøkkel-allowlist: `sed -n 89,92p $G``by at from to id title author usage_count last_modified` — nøyaktig § 5.1/§ 5.2s feltnavn, **uten `resource`**, som er hvorfor `sources[].resource` inne i en mapping stopper (registerets G30-forklaring, l.133).
Guardens mapping-nøkkel-allowlist **flyttet ved 1.3.0, og foreldrenøkkelen avgjør nå.** Ved 1.2.0: `by at from to id title author usage_count last_modified`**uten `resource`**, som var hvorfor `sources[].resource` inne i en mapping stoppet (registerets G30-forklaring, l.133). Ved 1.3.0 er `resource` tillatt **kun inne i en `sources`-oppføring** (`_SOURCES_ENTRY_KEYS`, `okf.py` l.102). Målt, med kjent-positiv: `sources: [{ id: a, resource: fixture }]` → PASS · `executor: { id: e, resource: … }` → REJECT `mapping key 'resource' is not on the OKF mapping allowlist under 'executor'` · `attester: { … }` → REJECT tilsvarende · toppnivå `resource:` (§ 4.1) → PASS. § 10.2s kjøretids-`resource` er altså fortsatt ute gjennom hver bærer, som er den grensen Dør C hviler på.
---
@ -236,8 +238,8 @@ måling, arm og celle der en finnes; ellers «ikke målt».
| `title` | 4.1 recommended (l.192193) | display name; consumers MAY derive from filename | 0.1 | **bib: ja** — A og B skriver (`inbox.py` l.133); B utleder ved fravær (`structure.py` l.58). **korpus: 270/270** = `<req_number> <seksjonstittel>` (`bundle.py` l.194) | siterbar overskrift (`VEGNORMAL_V1 § 4` l.176) | **indeksen bruker den ikke som lenketekst:** Dør C setter konsept-id som label (`importer.py` l.268), så UUID står to ganger per rad og `title` legges som fasett bak — UUID 27,7 % + `title:`-felt 16,6 % av indeksen (K1 S8b, l.173; 75 837 tegn / 270 rader). Ikke målt som celler | `grep -l '^title:' $M/*.md \| wc -l` → 270 · `grep -c '^- \[krav/N500/id-' $M/index.md` → 270 · `grep -c ' — title: ' $M/index.md` → 270 · `sed -n 268,275p $I/importer.py` |
| `description` | 4.1 recommended (l.194196); 8 SHOULD (l.527) | one-sentence summary; «Used by `index.md` generators, search snippets, and previews» | 0.1 | **bib: ingen dør skriver den.** `STRICT_V1` navngir (l.818) og bærer den i indeksmalen (l.859); `DEFAULT`/`OKF_V0_2`-malen har ingen plass (l.797). **korpus: 0/270 (E), 0/270 (M)** | ikke skrevet; `VEGNORMAL_V1` nevner den ikke (0 av 1073 linjer, kjent-positiv `` `kravtype` `` → 8), `bundle.py` navngir den ikke (0) | **se A.3** — indeksplassen § 8 gir den er fylt med en fasettdump; oppslagsaksen taper 4/13 (F4 9/13 mot R 13/13); hypotesen om årsak er **ikke målt** | `grep -l '^description:' $M/*.md \| wc -l` → 0 · `find $E -name '*.md' -print0 \| xargs -0 grep -l '^description:' \| wc -l` → 0 · `grep -l '^title:' $M/*.md \| wc -l` → 270 (kjent-positiv) · `grep -c '\`description\`' $V` → 0 · `grep -c '"description"' ~/repos/vegnormal-okf/src/vegnormal_okf/bundle.py` → 0 |
| `resource` | 4.1 recommended (l.197199) | URI that uniquely identifies the underlying asset; absent for abstract concepts | 0.1 | **bib: nei** — ingen dør skriver, ingen profil navngir (§ B.2-enumereringen: 35 navn, `resource` ikke blant dem). **korpus: 0** på toppnivå; den ene URI-en vi har står i `sources[].resource` | ikke skrevet; om kilde-API-et tilbyr en per-krav-URI er **ikke undersøkt** | ikke målt | `find $E -name '*.md' -print0 \| xargs -0 grep -c '^resource:' \| awk -F: '{s+=$NF}END{print s}'` → 0 · kjent-positiv `'^ - resource:'` → 270 · `grep -c '\`resource\`' $V` → 1 (l.383, om `sources`) |
| `tags` | 4.1 recommended (l.200201); 3.1 (l.148151) | YAML list of short strings; tag-browsing views are synthesized from it | 0.1 | **bib: aldri** skrevet eller lest (1 treff i 11 moduler, om HTML-tags). **korpus: 0** | ingen tverrgående kategorisering er definert utover `type`/`kravtype`/`seksjon` (`grep -c '\`tags\`' $V` → 0) | ikke målt. Porten: blokkliste passerer 1.2.0, flow avvises (§ 0.7) | `grep -n tags $I/*.py` → `extract.py:86` (HTML) · `grep -l '^tags:' $M/*.md \| wc -l` → 0 · `grep -c '^tags:' $S` → 3 (spec-ens egne eksempler, kjent-positiv) |
| `sources` | 5.1 (l.287364) | provenance: the materials a concept derives from; list of entries | 0.2 | **bib: navngitt i `OKF_V0_2`** (l.947), **skrevet av Dør A kun under den profilen** som flow-sekvens (`materialize.py` l.241242; golden 1/1) — **som guard 1.2.0 avviser** (§ 0.7). Parseren leser den ikke tilbake (`materialize.py` l.119, skalar). **korpus: E 270/270** blokk-sekvens av mappinger (`bundle.py` l.167177); **M 0/270 — fjernet før import** (G30, står ved 1.2.0) | skrives fordi § 5.1 er den konforme formen (`VEGNORMAL_V1 § 9` l.381); fjernes fordi porten avviser mapping-i-sekvens | **proveniensen finnes ikke i noe armene leste:** 0/270 i M. Etter import er `utgave` (B.1) eneste bærer av utgaven. Ikke målt som celler | `find $E -name '*.md' -print0 \| xargs -0 grep -l '^sources:' \| wc -l` → 270 · `grep -l '^sources:' $M/*.md \| wc -l` → 0 · `sed -n 241,242p $I/materialize.py` · `grep -rl '^sources:' examples \| wc -l` → 1 |
| `tags` | 4.1 recommended (l.200201); 3.1 (l.148151) | YAML list of short strings; tag-browsing views are synthesized from it | 0.1 | **bib: aldri** skrevet eller lest (1 treff i 11 moduler, om HTML-tags). **korpus: 0** | ingen tverrgående kategorisering er definert utover `type`/`kravtype`/`seksjon` (`grep -c '\`tags\`' $V` → 0) | ikke målt. Porten: blokkliste passerer, flow-sekvens av skalarer avvises — likt ved 1.2.0 og 1.3.0, men med ulik begrunnelse (§ 0.7) | `grep -n tags $I/*.py` → `extract.py:86` (HTML) · `grep -l '^tags:' $M/*.md \| wc -l` → 0 · `grep -c '^tags:' $S` → 3 (spec-ens egne eksempler, kjent-positiv) |
| `sources` | 5.1 (l.287364) | provenance: the materials a concept derives from; list of entries | 0.2 | **bib: navngitt i `OKF_V0_2`** (l.947), **skrevet av Dør A kun under den profilen** som flow-sekvens (`materialize.py` l.241242; golden 1/1) — **som guard 1.2.0 avviste og 1.3.0 slipper gjennom** (§ 0.7). Parseren leser den ikke tilbake (`materialize.py` l.119, skalar). **korpus: E 270/270** blokk-sekvens av mappinger (`bundle.py` l.167177); **M 0/270 — fjernet før import** (G30; falt ved 1.3.0, så grunnen til å fjerne den er borte — men M-tallet er målt på en import kjørt under 1.2.0 og er ikke re-målt) | skrives fordi § 5.1 er den konforme formen (`VEGNORMAL_V1 § 9` l.381); fjernes fordi porten avviser mapping-i-sekvens | **proveniensen finnes ikke i noe armene leste:** 0/270 i M. Etter import er `utgave` (B.1) eneste bærer av utgaven. Ikke målt som celler | `find $E -name '*.md' -print0 \| xargs -0 grep -l '^sources:' \| wc -l` → 270 · `grep -l '^sources:' $M/*.md \| wc -l` → 0 · `sed -n 241,242p $I/materialize.py` · `grep -rl '^sources:' examples \| wc -l` → 1 |
| `sources[].resource` | 5.1 REQUIRED i oppføringen (l.303306) | artifact a consumer can follow (URL, bundle path, `references/`) **or a scope descriptor** («all queries in project X») | 0.2 | **bib:** Dør A skriver `resource: fixture`-plassholder i golden (l.184). **korpus: E 270/270** — én absolutt URL til kilde-API-et, samme for alle (K1 N13) | peker tilbake til kilden | ikke målt (0 i M, se `sources`) | `find $E -name '*.md' -print0 \| xargs -0 grep -h '^ - resource:' \| sort -u \| wc -l` → 1 · `… \| grep -c '^ - resource: https://'` → 270 |
| `sources[].id` | 5.1 SHOULD når kroppen siterer (l.307309) | stable key for per-claim attribution via `[^id]` footnotes (l.348364) | 0.2 | **bib:** golden skriver `id: golden-v0-2-sales`. **korpus: 0** — ingen kropp siterer med fotnote | ikke utløst (K1 N10: 0 `[^`) | ikke målt | `find $E -name '*.md' -print0 \| xargs -0 grep -c '^ id:' \| awk -F: '{s+=$NF}END{print s}'` → 0 · kjent-positiv `'^ title:'` → 270 · `grep -l '\[\^' $M/*.md \| wc -l` → 0 |
| `sources[].title` | 5.1 optional (l.310311) | human-readable label for the source | 0.2 | **bib: nei.** **korpus: E 270/270** = `productTitle` (`bundle.py` l.177) | kildens navn | **dupliserer `utgave` 270/270 byte for byte** (B.1) — etter G30 er duplikatet det som overlever | `n=0; m=0; for f in $(find $E -name '*.md'); do u=$(grep -m1 '^utgave:' "$f" \| sed 's/^utgave: //'); s=$(grep -m1 '^ title:' "$f" \| sed 's/^ title: //'); m=$((m+1)); [ -n "$u" ] && [ "$u" = "$s" ] && n=$((n+1)); done; echo "$n av $m"``270 av 270` |
@ -327,7 +329,8 @@ seksjon fra kilden (T7 veiledningsfelle 4/4 mot 2/4; T8 mutert hypotese 5/5 mot
det er **modalitet (U2)**, ikke en beskrivelse. Begge står åpne. Fase 2 velger hva som
måles; dette dokumentet gjør det ikke.
**7. Porten.** En énlinjes `description` **passerer guard 1.2.0** (§ 0.7); en brettet
**7. Porten.** En énlinjes `description` **passerer** (målt ved både 1.2.0 og 1.3.0,
§ 0.7); en brettet
avvises. G33s «29/53 brettet» (`~/.claude/docs/okf-bundle-prosessen.md` l.130) gjelder
upstreams bundler, ikke en verdi vi selv ville skrevet.