Lenient KB-atom reader in @dossier/okf-view (readKbAtoms) — faithful getCollection reproduction for the SvelteKit app
0044-okf-view-lenient-kb-atom-reader
- Reversibility
- two-way door
DEC-0044 — Lenient KB-atom reader in @dossier/okf-view (readKbAtoms)
Decided + built this turn by the Principal SvelteKit Engineer while executing DEC-0043 Phase 2 (port
/graph+ shared chrome to@dossier/app). Recorded automatically in the same turn (the standing rule).confidence: verified— built, and the reproduction was measured against the live KB.
Context
DEC-0043 says the SvelteKit app loads its data via +page.server.ts load() calling @dossier/okf + @dossier/okf-view with prerender = true, reproducing "the same static output profile as Astro's getCollection" — noting "the content collection was always a thin layer over a filesystem reader we own."
Porting /graph exposed a gap in which reader. The Astro docs collection (content.config.ts) used a lenient schema: Starlight's docsSchema extended with an all-optional okfFields block, unknown keys stripped. It therefore surfaced every id-bearing .md atom to buildGraph. The strict @dossier/okf parse() (full per-type Zod validate()) is a different contract: it rejects atoms that don't conform to a type's exact schema. Measured on Dossier's own KB: 87 of 87 id-bearing atoms parse leniently, but only 56 pass strict parse() — 31 are rejected (almost all authored decision ADRs, including DEC-0043 itself). The existing getRouteMap reader in @dossier/okf-view is regex-only (id/title/type, no body, no full frontmatter), so it can't feed buildGraph either.
Using strict parse() in the graph loader would have silently dropped ~⅓ of the graph — the opposite of "reproduce the Astro profile exactly."
Options considered
- Strict
@dossier/okfparse()in the loader. Rejected: drops 31/87 atoms — a visible, wrong regression vs. the live Astro graph. - Re-implement a frontmatter splitter + YAML parse inside
@dossier/app. Rejected: forks the KB read path (DEC-0043's whole point of@dossier/okf-viewis one shared reader, never forked). - Add a lenient
readKbAtoms()to@dossier/okf-view(CHOSEN). The shared leaf is the correct SSOT home — both the app (graph/board) and the docs surface (when it ports) consume one reader. Reuses theyamlparser@dossier/okfalready uses, and the same frontmatter-split + slug-derivation as the Astro loader /getRouteMap, so the readers can never drift.
Decision
Add readKbAtoms(): KbAtom[] (KbAtom = { id, data, body }) to @dossier/okf-view (src/kb.ts, exported from the index). It walks knowledgeDir() for **/*.md, strips a BOM, splits the leading --- … --- block, YAML.parses it without Zod validation, normalizes bare-Date frontmatter fields (timestamp/decided_on → ISO date, lease_expires → ISO datetime) the way the Astro dateish transforms did, and derives the id slug identically to the Astro loader's generateId (index.md → knowledge, foo/index.md → foo). Crash-safe (missing/unreadable/frontmatter-less → skipped, never thrown) and KB-agnostic (DOSSIER_KB/knowledgeDir()).
This adds yaml@^2 (already in the workspace lockfile via @dossier/okf) as the first declared dependency of @dossier/okf-view; the shared vite lib build externalizes it.
Rationale
- Faithful, not lax. "Lenient" reproduces what Astro's collection actually fed renderers (the superset), which is the literal requirement of DEC-0043's data-profile promise. Verified:
readKbAtoms→buildGraphyields 87 nodes, 439 live edges, 7 concept types, with DEC-0043 present and carrying its description + body — matching the live Astro graph, not the 56-atom strict subset. - Single source of truth for the KB read (DEC-0043 + the atomic SSOT bar). One reader in the shared leaf, consumed by both surfaces; the regex
getRouteMapand this full reader split the frontmatter the same way, so they can't drift. - One parser across the repo. Reuses
@dossier/okf'syaml, rather than inventing a second YAML path. - Sovereignty (DEC-0001) preserved. Read-only, derived; the git KB stays the source of truth.
Consequences
@dossier/okf-viewgains one runtime dep (yaml), losing its prior zero-dep status — bought back by not forking a YAML reader and by keeping the surface read faithful. Still a leaf (no@dossier/*runtime deps; node builtins +yaml).- Atoms that fail strict OKF validation still appear in the graph/board (as they did under Astro). This is intentional for surface fidelity; strict conformance is the extraction/validation layer's job (DEC-0008), not the renderer's.
- The board port (Phase 3) and a future docs-side use can consume
readKbAtomsdirectly — no second reader.
Reversibility
Two-way door: readKbAtoms is an additive, pure read function in a shared leaf; nothing existing changed (the route map / sidebar / board view-model are untouched). A future caller wanting strict conformance can layer @dossier/okf validate() on top per atom; if the lenient profile is ever wrong it is one function to adjust. The durable commitment is the principle — the surface read must reproduce what Astro's collection surfaced (the lenient superset), not the strict schema subset — and the SSOT placement (one KB reader in @dossier/okf-view, consumed by both surfaces, never forked). Removing the function reverts to no shared full-atom reader; the yaml dep is trivially dropped with it.
Review
confidence: verified: built and exercised — pnpm --filter @dossier/app check is 0/0, the workspace test suite is green (385 passed), and the dev-server + prerendered /graph both emit the 87-node / 439-edge payload. See Migrate chrome-light app surfaces to SvelteKit; docs stay on Astro/Starlight (two apps, one origin) and Execute the SvelteKit app migration (DEC-0043) — phased, no big-bang, apex domain moved only at final cutover.