linkedin-studio/scripts/brain/tests/okf-conform.test.ts
Kjell Tore Guttormsen bc47c1842a fix(linkedin-studio): N24.6 — brain-restposter (slugify-translitterering + okf_version til rot-frontmatter) [skip-docs]
To restposter fra N24.5s FUNN-liste, begge i scripts/brain, i én berøring.

1. slugify() var tapsgivende for alt annet enn engelsk: hver ikke-ASCII-bokstav
   falt i [^a-z0-9]-kjøringen og ble en bindestrek. «Løkkene» ble «l-kkene», og
   verre: ø og å ble BEGGE «-», så «møte» og «måte» kollapset til én id.
   Nå: NFD + strip av kombinerende tegn (å, é, ñ), så en eksplisitt tabell for de
   ikke-dekomponerbare (æ→ae, ø→o, œ→oe, ß→ss, đ/ð→d, þ→th, ł→l).
   REKKEFØLGEN: translitterering kjører FØR kollapsen, så kollapsen forblir siste
   gate. Presist om hva det beviser — utdata-whitelisten [a-z0-9-] bæres av
   kollapsen selv, ikke av rekkefølgen; dagens tabell produserer bare [a-z], så
   rekkefølgen ville ikke brutt whitelisten i dag. Den er en invariant for
   FREMTIDIGE tabelloppføringer, og den er nå pinnet av test. Det er whitelisten
   ingestion-guardens tall hviler på (0/81 %-escapes, 0/81 bilde-URL-er,
   rapportert til guard-eieren som strukturelt); repo-vid grep bekrefter at
   scripts/brain/src/id.ts er den ENESTE slugify-implementasjonen i repoet, så
   attribusjonen holder.

   ID-STABILITET (den åpne beslutningen, avgjort på bevis, ikke skjønn):
   endre in-place — ingen versjonering av slugifieren, ingen migrering.
   - Ingen mintede ider finnes på disk: $DATA/brain og $DATA/ingest inneholder
     bare tomme kataloger (opprettet 23.06, null filer — initBrain lager kataloger
     OG filer i ett kall, så brain-en er aldri blitt genuint initialisert her).
   - Alle 27 profile-field-labels den shippede malen minter er ASCII (målt ved å
     kjøre extractFields' faktiske regler mot malen) ⇒ endringen er et BEVIST
     no-op for profil-laget. Fem golden-ider pinner det.
   - observed-ider (consolidate) mintes fra brukerskrevne nøkler og ville endret
     seg — men ingenting er persistert, så migrasjonsflaten er null rader.
   Ærlig avgrensning: dette KRYMPER kollisjonsklassen, det lukker den ikke —
   «møte» og «mote» møtes fortsatt, som de må for at stabiliteten over case og
   whitespace skal holde. Og en label uten latinske tegn i det hele tatt
   («日本語») slugger fortsatt til tom streng, så to slike minter samme id —
   pre-eksisterende, ikke innført her, men det hører hjemme ved siden av
   «krymper, lukker ikke» framfor å stå uskrevet. Adopter-forbehold: repoet er offentlig, men brain init
   er ikke session-start-wiret (SB-S2 eier det), så en persistert brain krever en
   eksplisitt invokasjon.

2. okf_version: 0.1 lå som BRØDTEKST i rot-index.md. Kanonisk plassering er
   frontmatter-blokken (OKF-form spec §6, v0.3) — upstreams ene utskårne unntak
   fra «index-filer har ingen frontmatter», og unntaket er oppregnet til ÉN
   nøkkel, så ingenting annet blir med (okf_layout blir i brødtekst per §12).
   Verdien flytter, den bumpes ikke: vi blir på 0.1.

VERIFISERING
- TDD: 6 røde først, så grønt. Brain 134 → 142 (+8), floor 127 → 142.
- Mutasjonstestet mot de FAKTISKE kildefilene, ikke bare self-tester:
  okf_version tilbake til brødtekst → 3 røde · fjern translitterering → 1 rød ·
  flytt translitterering til ETTER kollapsen → 1 rød · restaurert → 142/0.
- De to okf-testene dette erstattet var VAKUØSE: /^okf_version:/m matcher en
  frontmatter-linje like gjerne som en brødtekstlinje, og frontmatterType() leser
  type:, så den ga null med eller uten blokk. Begge gikk grønt på begge
  plasseringer — nøyaktig gjeldsklassen N24.5 feide. Meldingen «index.md carries
  NO frontmatter» ble usann og er skrevet om.
- Delt gate (katalogen, read-only): node catalog/scripts/okf-check.mjs <bundle>
  → exit 0, «OK: valid OKF bundle», okf_version 0.1. Bevist at verdien leses FRA
  frontmatter: fjern blokken → «MISSING». check-okf-parity-signaturen er
  conceptCount|untyped|okfVersion|okfVersionAccepted — plassering inngår ikke, så
  det å migrere først splitter ingen paritet (okr skriver fortsatt brødtekst).
- Ti suiter grønne: test-runner 303/0 · trends 300 · analytics 202 · hooks 191 ·
  brain 142 · editions 72 · render 63 · specifics-bank 45 · tests 35 ·
  contract-gate 33.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EgUSPs7vDejiHr7iZw8xUx
2026-08-01 20:05:54 +02:00

146 lines
7 KiB
TypeScript

import { describe, test, beforeEach, afterEach } from "node:test";
import assert from "node:assert/strict";
import { mkdtempSync, rmSync, existsSync, readFileSync, readdirSync } from "node:fs";
import { join } from "node:path";
import { tmpdir } from "node:os";
import { initBrain } from "../src/scaffold.js";
import { parseProfile } from "../src/profile.js";
/**
* OKF-compatible-form conformance for the `brain/` knowledge bundle.
*
* Stage 1 of the cross-plugin OKF convergence (docs/okf-convergence-brief.md): the
* brain is the REFERENCE design, OKF is a thin interop veneer. Contract — mirrors
* `okr/scripts/okf-check.mjs`, the reference checker:
* - every CONCEPT file (`*.md` except `index.md`) carries a non-empty `type` in a
* leading YAML frontmatter block;
* - the bundle-root `index.md` carries an `okf_version` marker in its FRONTMATTER
* block — upstream's single carved exception to "index files carry no
* frontmatter" (spec §6, v0.3; this was body text until N24.6);
* - each directory level has its own `index.md` (progressive disclosure).
*
* EXCLUDED by design (brief §6): the `ingest/` tributary. `ingest/published/*.md`
* is the byte-exact gold store with a hard round-trip invariant (SC2) that a YAML
* frontmatter block would break — it is a raw tributary, not a navigable concept,
* and the hub `index.md` points to it rather than folding it in. This test walks
* `brain/` ONLY; it never asserts frontmatter on the tributary.
*/
/**
* Minimal frontmatter `type` reader. We only ever EMIT OKF frontmatter; we never add
* a YAML-parser dependency (the brain is deliberately YAML-free), so a constrained
* regex over the leading `---` block is the right reader here.
*/
function frontmatterType(text: string): string | null {
const block = text.match(/^---\n([\s\S]*?)\n---\n/);
if (!block) return null;
const t = block[1].match(/^type:\s*(.+?)\s*$/m);
return t ? t[1].trim() : null;
}
/** All concept files (`*.md` except `index.md`) under `root`, recursive. */
function walkConceptMd(root: string): string[] {
const out: string[] = [];
const walk = (dir: string) => {
for (const e of readdirSync(dir, { withFileTypes: true })) {
const p = join(dir, e.name);
if (e.isDirectory()) walk(p);
else if (e.isFile() && e.name.endsWith(".md") && e.name !== "index.md") out.push(p);
}
};
walk(root);
return out;
}
describe("brain/ bundle is OKF-compatible form (Stage 1)", () => {
let root: string;
const prevEnv = process.env.LINKEDIN_STUDIO_DATA;
beforeEach(() => {
root = mkdtempSync(join(tmpdir(), "brain-okf-"));
process.env.LINKEDIN_STUDIO_DATA = root;
initBrain();
});
afterEach(() => {
if (prevEnv === undefined) delete process.env.LINKEDIN_STUDIO_DATA;
else process.env.LINKEDIN_STUDIO_DATA = prevEnv;
rmSync(root, { recursive: true, force: true });
});
// N24.6 (OKF 0.3 §6): the marker moved from body text INTO the root index.md's
// frontmatter block — upstream's one carved exception to "index files contain no
// frontmatter". The two assertions this replaced were vacuous: `/^okf_version:/m`
// matches a frontmatter line as happily as a body line, and `frontmatterType()`
// reads `type:`, so it returns null with or without a block. Both went green on
// either placement, which is exactly the debt class N24.5 swept.
test("bundle-root index.md carries okf_version INSIDE the leading frontmatter block", () => {
const index = readFileSync(join(root, "brain/index.md"), "utf8");
const block = index.match(/^---\n([\s\S]*?)\n---\n/);
assert.ok(block, "root index.md opens with a frontmatter block");
assert.match(block![1], /^okf_version:\s*\S+/m, "the block declares okf_version");
});
test("okf_version does NOT also sit in the body (one marker, one placement)", () => {
const index = readFileSync(join(root, "brain/index.md"), "utf8");
const body = index.replace(/^---\n[\s\S]*?\n---\n/, "");
assert.doesNotMatch(body, /^okf_version:/m, "no leftover body-text marker");
});
test("the root index frontmatter carries okf_version and nothing else", () => {
// Upstream's exception is enumerated to ONE key, so `okf_layout` (this
// convention's own extension marker, spec §12) stays in body text and no
// concept-style `type:`/`title:` may ride along in an index file.
const index = readFileSync(join(root, "brain/index.md"), "utf8");
const block = index.match(/^---\n([\s\S]*?)\n---\n/)![1];
const keys = block.split("\n").filter((l) => l.trim() !== "").map((l) => l.split(":")[0].trim());
assert.deepEqual(keys, ["okf_version"]);
assert.equal(frontmatterType(index), null, "an index file still carries no `type:`");
});
test("a non-root index.md carries no frontmatter at all", () => {
const journal = readFileSync(join(root, "brain/journal/index.md"), "utf8");
assert.doesNotMatch(journal, /^---\n/, "only the BUNDLE-ROOT index may carry a block");
});
test("every concept file under brain/ carries a non-empty frontmatter type", () => {
const concepts = walkConceptMd(join(root, "brain"));
assert.ok(concepts.length > 0, "the bundle has concept files to check");
for (const f of concepts) {
const type = frontmatterType(readFileSync(f, "utf8"));
assert.ok(type && type.length > 0, `concept ${f} is missing a frontmatter type:`);
}
});
test("profile.md is type: Profile AND still parses (rich fields preserved — round-trip intact)", () => {
const text = readFileSync(join(root, "brain/profile.md"), "utf8");
assert.equal(frontmatterType(text), "Profile");
const doc = parseProfile(text);
assert.equal(doc.schemaVersion, 1, "line-grammar still parses through the frontmatter");
assert.ok(doc.static.length > 0, "the seeded static facts survive conformance");
});
test("operations.md is type: Operations", () => {
const text = readFileSync(join(root, "brain/operations.md"), "utf8");
assert.equal(frontmatterType(text), "Operations");
});
test("concept files carry the cheap recommended fields (title + description)", () => {
// OKF recommends title/description; they are free (constant) here and give a
// foreign agent a human label without a clock (timestamp/resource stay out —
// a timestamp would break the pure serializer; resource is N/A for an internal
// concept). okf-check still only REQUIRES type; these clear its warnings.
for (const sub of ["brain/profile.md", "brain/operations.md"]) {
const block = readFileSync(join(root, sub), "utf8").match(/^---\n([\s\S]*?)\n---\n/);
assert.ok(block, `${sub} has a frontmatter block`);
assert.match(block![1], /^title:\s*\S/m, `${sub} has a title`);
assert.match(block![1], /^description:\s*\S/m, `${sub} has a description`);
}
});
test("each directory level under brain/ has its own index.md (progressive disclosure)", () => {
assert.ok(existsSync(join(root, "brain/index.md")), "brain/index.md");
assert.ok(existsSync(join(root, "brain/journal/index.md")), "brain/journal/index.md");
});
});