diff --git a/docs/llm-ingestion-okf-fase4-kartlegging-2026-07.md b/docs/llm-ingestion-okf-fase4-kartlegging-2026-07.md new file mode 100644 index 0000000..d0b4e19 --- /dev/null +++ b/docs/llm-ingestion-okf-fase4-kartlegging-2026-07.md @@ -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 ` 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`.