docs(okr): fase-4-kartlegging for llm-ingestion-okf (planned, ingen kode)

Forarbeid mot fase 4: all OKF-kode (okf-check/okf-index/innboks-ingest,
lib/okf-*.mjs, lib/innboks-*.mjs, lib/convert/) lest mot ground truth og
skilt i generelt vs. okr-spesifikt. Kravliste for at okr skal kunne vendore
en delt Node-utgave, samt okrs faktiske bruk av okf_version (ekko-tekst i
rot-index.md, kb-layout-2026-06) og hvorfor avviket mot spec-ens 0.1 er et
felt-type-avvik, ikke bare en verdiforskjell.

Ingen kode endret, ingenting wiret. Markørlinje satt til planned i STATE.md
(local-only). Meldt avvik: bibliotekets koordineringsvedlegg er utdatert.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AGkEqS3Zxf7rVuUQHrsQG2
This commit is contained in:
Kjell Tore Guttormsen 2026-07-20 07:23:04 +02:00
commit 29401bcd60

View file

@ -0,0 +1,163 @@
# llm-ingestion-okf — fase 4-kartlegging (okr-siden)
**Status:** `planned` — kartlegging og kravgrunnlag. Ingen kode endret, ingenting wiret.
**Dato:** 2026-07-20. **Grunnlag:** ground-truth-lesing av all OKF-kode i dette repoet
(suite 149/149 grønn ved kartleggingstidspunkt).
Node-halvdelen av `llm-ingestion-okf` finnes ikke ennå. Dette dokumentet er forarbeid:
hva okr faktisk har, hva som er generelt vs. okr-spesifikt, og hva et delt bibliotek må
oppfylle før okr kan vendore det i stedet for å eie egen kode.
---
## 1. Kartlegging — hva finnes i dag
Zero-runtime-dependency Node/ESM. Alle moduler er både importerbare (named exports) og
CLI-kjørbare (`import.meta.url === process.argv[1]`-vakt). Node >= 22.
| Modul | Linjer | Rolle | Løftbarhet |
|---|---|---|---|
| `lib/frontmatter.mjs` | 70 | Flat FM-parse/skriv. BOM+CRLF-tolerant, siterte verdier bevarer intern `#`, multi-linje list-verdier tolereres | **Generell** |
| `lib/okf-links.mjs` | 39 | Den ENE lenke-allow-listen: trygg bundle-lenke = leading `/`, `.md`, ingen scheme/`..`/backslash/NUL. `resolveBundleLink` re-sjekker confinement | **Generell** |
| `lib/okf-vocab.mjs` | 73 | Lukket TYPE/TAGS-vokab + `routeLevel(type)` | **okr-spesifikk data, generell mekanisme** |
| `lib/innboks-split.mjs` | 100 | Ren funksjon: `#`/`##`-heading-split → konsepter. Preamble bevares, flat-fallback | **Generell** (én no-detalj) |
| `lib/innboks-frontmatter.mjs` | 96 | Regelbasert FM-projeksjon. `timestamp` = kilde-mtime (idempotens by construction) | **Blandet** |
| `lib/innboks-relations.mjs` | 49 | Relasjoner innen kjøringens sett, exact-title-substring → root-lenker. Assert zero-dangling | **Generell** |
| `lib/innboks-write.mjs` | 186 | Atomisk skriv (temp+rename), path-confinement, realpath/symlink-guard, reservert-navn-guard, kuratert-fil-vern (`kilde: innboks`), kryss-kilde-`claimed`-register | **Generell** (én okr-konstant) |
| `lib/convert/index.mjs` | 132 | docx/eml/pdf/txt/md → md. Lenke-nøytralisering i alle fem lenkeformer | **Generell** |
| `scripts/okf-check.mjs` | 160 | Bundle-validering. Default tolerant (kun `type` kreves); `--strict-ingest` = vokab + lenke-allow-liste + `files`-scoping | **Generell** |
| `scripts/okf-index.mjs` | 162 | Per-nivå `index.md`, idempotent, `sanitizeEntry` (C0/C1, zero-width, bidi, Unicode-tags, link-nøytralisering), atomisk | **Generell** |
| `scripts/innboks-ingest.mjs` | 240 | Orkestrator: discover → per-dok staging → gate → discard-on-fail → relasjoner → index → sluttsjekk | **Generell arkitektur** |
**Arkitektur-invarianter verdt å bevare i et løft:**
- Ingen LLM. Fil-settet er en funksjon av (drop-zone-innhold + mtime) alene → run1 og run2
byte-identisk. Veggklokke brukes aldri.
- Per-dokument-isolasjon (Map-Reduce): ett fiendtlig dokument discardes alene og forgifter
ikke de andre. Relasjoner emitteres ETTER gaten, så hvert mål garantert er på disk.
- Strict-gaten scopes til kjøringens skrevne filer, ALDRI hele roten — ellers feller den
legitimt håndkuratert innhold med lovlige eksterne lenker.
- Ikke-destruktiv: originalene blir liggende i drop-zonen; hver original får en peker-fil.
---
## 2. Hva er okr-spesifikt vs. generelt
### Generelt (kan løftes tilnærmet uendret)
`frontmatter.mjs`, `okf-links.mjs`, `innboks-split.mjs`, `innboks-relations.mjs`,
`okf-index.mjs`, `okf-check.mjs`-kjernen, `convert/`-adapterne, og hele skrive-/
confinement-maskineriet i `innboks-write.mjs`. Ingenting her vet at domenet er OKR.
### okr-spesifikt (må parametriseres før løft)
1. **`TYPE_VOCAB`/`TAGS_VOCAB`/`LEVEL_BY_TYPE`** (`okf-vocab.mjs`) — norske, lukkede,
OKR-domene. Mekanismen (lukket vokab + type→nivå-ruting) er generell; *innholdet* er
ikke. Må inn som konfigurasjon, ikke kode. Merk at spec-en er engelskspråklig konvensjon
mens vokabet er norsk — eierskapet (bibliotek-default vs. konsument-profil) må avklares.
2. **`HOME_ORG`-konstanten** (`innboks-write.mjs:44`, `~/.claude/okr/org`) — okrs to-rot-
modell. Generaliseres til en «forbudte skriverøtter»-liste fra kaller.
3. **Type-utledningen** (`deriveType`, `innboks-frontmatter.mjs`) — matcher vokab-termer som
helt ord i tittel + kilde-basename, med `\p{L}\p{N}`-lookaround fordi `\b` ikke håndterer
æ/ø/å. Regelen er generell, vokabet ikke.
4. **`slugify`s æ→ae/ø→oe-translitterering** (`innboks-split.mjs:15-25`) — nordisk, ikke
universelt. NFKD dekomponerer *ikke* æ/ø, så uten dette faller bokstaven bort. Et delt
bibliotek trenger en pluggbar translitterasjonstabell, ellers regresserer norsk input.
5. **Faste nivånavn** (`strategisk-kontekst`/`historikk`/`dokumenter`) og
`.claude/okr/innboks/` som drop-zone. Rene konfig-verdier.
6. **Norske feilmeldinger og `kilde: innboks`-provenansnøkkelen.** Meldingsspråk må være
pluggbart hvis biblioteket skal tjene ikke-norske konsumenter; `kilde`-nøkkelen er en
OKF-extension key okr er avhengig av for kuratert-fil-vernet.
### `lib/convert/` spesielt
Adapterne er **domene-agnostiske** og det mest direkte gjenbrukbare i repoet. Men:
- Fire exact-pinnede deps (mammoth 1.12.0, turndown 7.2.4, postal-mime 2.7.5, unpdf 1.6.2).
okrs zero-dep-posisjon er bevart ved at de er lazy-importert med norsk installasjonshint,
og at txt/md kjører helt dependency-fritt. **Et delt bibliotek må bevare denne
egenskapen** — deps må være valgfrie (peer/optional), aldri obligatoriske. Vendored-vs-peer
er en åpen beslutning som må tas sammen, ikke arves.
- `turndown` er pinnet til `headingStyle: 'atx'`. Default er setext, som heading-splitten
aldri ser. Denne koblingen mellom konverter og splitter må dokumenteres i kontrakten,
ellers reintroduseres bugen i en delt utgave.
- `unpdf` kalles med eksplisitt `isEvalSupported: false` (CVE-2024-4367), selv om det er
default i 1.6.2. Belte + seler — må overleve løftet.
- PDF gir flat tekst uten headings (kjent v1-caveat): PDF-dokumenter blir alltid ett konsept.
- **`neutralizeExternalLinks` er sikkerhetskritisk** og hører sammen med `okf-links.mjs`.
Emit-siden og validerings-siden deler allow-liste nettopp for at de aldri skal divergere.
Splittes de i et løft, er divergens et spørsmål om tid.
---
## 3. Forutsetninger for at okr kan vendore en delt utgave
Rangert, alle må være oppfylt:
1. **Vokabet er data, ikke kode.** Bibliotekets API tar TYPE/TAGS/`routeLevel` som argument.
Uten dette må okr forke uansett.
2. **Zero-dep-kjerne bevart.** txt/md-stien må kjøre uten npm-installasjon. Binærformat-deps
optional/lazy med samme feilmeldings-kvalitet (klar hint, aldri traceback).
3. **Sikkerhetsegenskapene er testbart bevart.** Path-confinement, realpath/symlink-guard,
reservert-navn-guard, kuratert-fil-vern, `sanitizeEntry`-settet (C0/C1, ZWSP, bidi,
Unicode tag-blokk), lenke-nøytralisering i alle fem former, `--strict-ingest`-scoping.
Alle 149 testene her er tilgjengelige som seed; et løft som ikke porter dem er ikke
ferdig. Særlig: gate-scoping (`files`) og discard-on-fail-isolasjon er lærte lekser fra
B5/B2 — de er ikke kosmetikk.
4. **Idempotens by construction.** mtime-basert timestamp, deterministisk sortering,
stabil kryss-kilde-disambiguering. Et bibliotek som introduserer veggklokke er ubrukelig
for okr.
5. **Pluggbar slugify-translitterering** (æ/ø/å), ellers regresjon på norsk input.
6. **Pluggbar meldingsstreng/språk**, eller minst en `onNotice`-seam som i dag.
7. **Versjonspinning etter polyrepo-disiplinen.** okr vendorer mot en tag, ikke mot main.
8. **Guard-grensen respekteres.** Dør A er ugatet; `llm-ingestion-okf` er plumbing.
okrs egen strict-gate er *ikke* en sikkerhetsguard i guard-repoets forstand og skal ikke
forveksles med en. Vil vi ha guard-gating, går det via `llm-ingestion-guard` på kallstedet
— og det er en separat beslutning, ikke en del av fase 4.
---
## 4. `okf_version` — hvordan okr bruker feltet i dag (kjent avvik)
**Faktisk bruk, verifisert:**
- Verdien er `kb-layout-2026-06` (`scripts/okf-index.mjs:27`, eksportert som `OKF_VERSION`).
- Den bor som **markdown-tekstlinje i rot-`index.md`**, ikke i frontmatter — `index.md` er
OKF-reservert og bærer aldri frontmatter. Kun rot-index; undernivåer har den aldri
(testet eksplisitt).
- `okf-check.mjs` **ekkoer** verdien for menneskelig sammenligning. Den validerer den ikke,
sammenligner den ikke mot noe, og feiler aldri på den. Hooks/scripts er no-network, så
auto-fetch mot en standard er utelukket by design.
- `okf-index.mjs` **bevarer** eksisterende verdi ved re-kjøring; en eksplisitt
`--okf-version <ver>` vinner (bump-mekanismen). Begge grener er testet.
- Feltet er per rot: prosjekt-`.claude/okr/` og home-`~/.claude/okr/org/` har hver sin.
**Avviket:** spec-en sier `0.1`; okr sier `kb-layout-2026-06`. Dette er ikke bare ulik
verdi — det er to ulike *typer* felt. okrs verdi navngir hvilket **layout-mønster** treet
følger (datert konvensjons-snapshot); spec-ens `0.1` er et **spec-versjonsnummer**. De kan
begge være riktige og likevel uforenlige i ett felt.
**okrs posisjon:** dette avgjøres av catalog som konvensjonseier, ikke av okr og ikke av
biblioteket. okr har lav byttekostnad — feltet er ren ekko-tekst uten validerings-semantikk,
så en verdiendring koster én konstant + fixture-oppdateringer. Men hvis begge betydningene
skal bæres, trenger vi to felt (f.eks. `okf_version` = spec-versjon, `okf_layout` =
layout-snapshot), og det er en spec-endring som må gå via commons/catalog.
---
## 5. Hva som aldri bør flyttes til biblioteket
- **`okf-vocab.mjs`s innhold** — norsk OKR-domenevokab hører hjemme i konsumenten eller i
en profil, aldri som bibliotek-default.
- **Orkestratoren `innboks-ingest.mjs` som helhet** — fase-rekkefølgen er generell og verdt
å dele som *mønster*, men den konkrete drop-zone-plasseringen, to-rot-modellen og
peker-filkonvensjonen er okrs. Del kjeden, ikke policyen.
- **Hook-integrasjonen** (`inject-okr-context.mjs`, `coaching-hook.mjs`) og
`okr-second-brain-search`-skillen — ren plugin-UX.
- **Alt sikkerhetsansvar.** Guard-grensen står: `llm-ingestion-guard` eier sikkerhet,
`llm-ingestion-okf` er plumbing, og okr reimplementerer ingen av delene.
---
## 6. Neste steg (ikke utført)
1. Operatør melder kartleggingen inn til biblioteket som kravgrunnlag (§3 er kravlisten).
2. Catalog avklarer `okf_version`-semantikken (§4) — blokkerer løftet.
3. Parser-sett + vendored-vs-peer avgjøres i samråd (§2, `lib/convert/`).
4. Først når Node-halvdelen finnes med §3 oppfylt: vurder adopsjon i egen sesjon.
Ingen av disse stegene endrer kode i okr. Markørlinjen i STATE.md står på `planned`.