feat(okr): okf-check leser okf_version fra frontmatter og broedtekst

This commit is contained in:
Kjell Tore Guttormsen 2026-08-02 21:11:58 +02:00
commit 049259d9a6
2 changed files with 155 additions and 11 deletions

View file

@ -58,23 +58,51 @@ function walkConcepts(root) {
return out;
}
// Les rotens to markoerer (markdown-tekst i index.md, ikke frontmatter).
// `okf_version` = upstream OKF-versjon (spec §3), `okf_layout` = emitterens egen
// layout-revisjon (spec §12, valgfri). Fravaerende markoer -> null. Ren ekko:
// verdiene valideres ikke (spec §3 er ikke haandhevende paa form ennaa).
// Les rotens to markoerer fra index.md. `okf_version` = upstream OKF-versjon
// (spec §3), `okf_layout` = emitterens egen layout-revisjon (spec §12, valgfri).
// Fravaerende markoer -> null. Ren ekko: verdiene valideres ikke (spec §3 er
// ikke haandhevende paa form ennaa).
//
// Verdien unquotes med SAMME konservative regel som produsent-siden (okf-index
// unquote(), delt import -- ikke en kopi). Uten den ga samme fil to lesninger:
// produsenten tolket `okf_version: "0.2"` som 0.2, checkeren ekkoet «"0.2"».
// Anfoerselstegn er YAML-strengsyntaks, ikke del av verdien -- og siden
// upstreams eneste kanoniske eksempel med verdi (SPEC.md:773) er sitert, er
// dette den formen en spec-tro bundle faktisk har paa disk.
// TO PLASSERINGER, bevisst (D8 steg 20). Beslutning 6 flytter `okf_version` til
// frontmatter mens `okf_layout` blir i broedteksten, men bundles paa disk
// migrerer ikke samtidig -- en bundle skrevet av en eldre okf-index baerer
// fortsatt begge i broedteksten. Lesingen proever derfor frontmatter FOERST og
// faller tilbake til broedteksten. Presedensen er en REGEL her, ikke en
// bivirkning av at frontmatter tilfeldigvis staar oeverst i fila.
//
// De to lagene har ULIKE tolkningsregler, og det er hele grunnen til at de er
// skilt:
// - frontmatter ER YAML -> parseFrontmatter haandterer sitering OG trailing
// ` # kommentar` (lib/frontmatter.mjs:39). En raa `^key:`-scan lot
// kommentaren lekke inn i verdien.
// - broedteksten er IKKE YAML -> der er unquote() alene riktig regel, delt
// import fra okf-index (produsent-siden) og ikke en kopi. Uten den ga samme
// fil to lesninger: produsenten tolket `okf_version: "0.2"` som 0.2,
// checkeren ekkoet «"0.2"». Upstreams eneste kanoniske eksempel med verdi
// (SPEC.md:773) er sitert, saa det er formen en spec-tro bundle har paa disk.
function rootMarkers(root) {
const idx = join(root, 'index.md');
if (!existsSync(idx)) return { okfVersion: null, okfLayout: null };
const raw = readFileSync(idx, 'utf8');
const { raw: fmRaw, get: fmGet } = parseFrontmatter(raw);
// Broedteksten = alt UNDER frontmatter-blokken, slik at de to lagene kan feile
// hver for seg (mutasjon M1 roedner (20a)/(20d) NETTOPP fordi fallbacken ikke
// ser frontmatter-linjene).
//
// AERLIG OM DEKNINGEN: uttrekket er ikke produksjonsobserverbart i dag. Naar
// noekkelen finnes i frontmatter i en form parseFrontmatter i det hele tatt
// returnerer, vinner fm-laget foer fallbacken kjoeres -- saa `body = raw`
// roedner ingen test (maalt, ikke antatt). Det beholdes likevel: fjernes det,
// ligger en latent defekt og venter paa at lib/frontmatter.mjs's list-key-
// kontrakt (:13-15) blir sann. I dag returnerer get() paa en list-key foerste
// LIST-ELEMENT, ikke null, fordi `\s*` i :29-regexen spiser linjeskiftet.
// Fikses det (patch-lane), begynner fallbacken aa kjoere for list-keys -- og
// uten dette uttrekket ville den plukket «- a» ut av frontmatter.
const body = fmRaw === null ? raw : raw.slice(raw.indexOf('\n---', 3) + 4);
const pick = (key) => {
const m = raw.match(new RegExp(`^${key}:\\s*(.+)$`, 'm'));
const fromFm = fmGet(key);
if (fromFm !== null) return unquote(fromFm);
const m = body.match(new RegExp(`^${key}:\\s*(.+)$`, 'm'));
return m ? unquote(m[1].trim()) : null;
};
return { okfVersion: pick('okf_version'), okfLayout: pick('okf_layout') };

View file

@ -899,3 +899,119 @@ test('okf-check: rot-index uten okf_layout ekkoer MANGLER for den, ikke for okf_
rmSync(dir, { recursive: true, force: true });
}
});
// --- D8 steg 20: okf-check leser markoerene fra BEGGE plasseringene ---
// Beslutning 6 flytter `okf_version` til rot-indeksens frontmatter (steg 21),
// mens `okf_layout` blir staaende i broedteksten. Bundles migrerer IKKE
// samtidig -- en bundle skrevet av en eldre okf-index baerer fortsatt begge i
// broedteksten -- saa LESINGEN maa tolerere begge former gjennom migrasjonen.
// Presedens: frontmatter foerst, deretter broedteksten UNDER frontmatter-blokken.
// Fortsatt ren ekko (ingen verdivalidering) og fortsatt ingen PRESENCE-krav.
//
// De tre casene er valgt for aa DISKRIMINERE de to lagene. S49-laerdommen:
// en assertion som ikke kan skille HVILKET lag som fanget gjoer en tolagsvakt
// til en ettlagsvakt uten at noe blir roedt. Her roedner hvert lag for seg:
// (20a) kun frontmatter -> roedner naar frontmatter-laget fjernes
// (20b) frontmatter UTEN noekkelen, verdi i -> roedner naar broedtekst-laget fjernes
// broedteksten (den naive "har fila frontmatter?
// bruk kun den"-regresjonen)
// (20c) begge, ULIKE verdier -> pinner presedensen; roedner naar
// rekkefoelgen snus
// Uten (20b) ville steg 20 bestaatt UTEN kodeendring: dagens rootMarkers()
// regex-scanner raa filtekst med `m`-flagg og treffer allerede frontmatter-
// linjer -- foerste treff vinner, og frontmatter staar oeverst.
// Rot-index med VILKAARLIG innhold (helleren writeRootIndex laaser formen).
function writeRawRootIndex(dir, content) {
mkdirSync(dir, { recursive: true });
writeFileSync(join(dir, 'index.md'), content);
}
test('(20a) okf-check: okf_version i rot-indeksens FRONTMATTER leses', () => {
const dir = tmpRoot();
try {
buildCleanIngest(dir);
writeRawRootIndex(dir, '---\nokf_version: 0.1\n---\n\n# Bundle\n\nokf_layout: kb-layout-2026-06\n');
const r = checkBundle(dir);
assert.equal(r.okfVersion, '0.1', 'frontmatter-plassert okf_version skal leses');
assert.equal(r.okfLayout, 'kb-layout-2026-06', 'broedtekst-plassert okf_layout skal fortsatt leses');
assert.equal(runCheck(dir).status, 0, 'migrert form skal gi exit 0');
} finally {
rmSync(dir, { recursive: true, force: true });
}
});
test('(20b) okf-check: frontmatter UTEN okf_version faller tilbake til broedteksten', () => {
const dir = tmpRoot();
try {
buildCleanIngest(dir);
// Umigrert bundle som likevel har en frontmatter-blokk: verdien staar i
// broedteksten. En lesing som slaar seg til ro med "fila HAR frontmatter"
// mister markoeren her -- det er regresjonen dette steget er likest til.
writeRawRootIndex(dir, '---\ntitle: Bundle\n---\n\n# Bundle\n\nokf_version: 0.1\nokf_layout: kb-layout-2026-06\n');
const r = checkBundle(dir);
assert.equal(r.okfVersion, '0.1', 'broedtekst-verdien skal finnes selv naar frontmatter finnes uten noekkelen');
assert.equal(r.okfLayout, 'kb-layout-2026-06');
} finally {
rmSync(dir, { recursive: true, force: true });
}
});
test('(20c) okf-check: staar markoeren BEGGE steder, vinner frontmatter', () => {
const dir = tmpRoot();
try {
buildCleanIngest(dir);
// Overgangstilstand: en delvis migrert index kan baere begge. Frontmatter er
// maalformen, saa den er den autoritative -- broedteksten er restene.
writeRawRootIndex(dir, '---\nokf_version: 0.9\n---\n\n# Bundle\n\nokf_version: 0.1\nokf_layout: kb-layout-2026-06\n');
const r = checkBundle(dir);
assert.equal(r.okfVersion, '0.9', 'frontmatter skal ha presedens over broedteksten');
} finally {
rmSync(dir, { recursive: true, force: true });
}
});
test('(20d) okf-check: sitert okf_version i frontmatter unquotes som paa produsent-siden', () => {
const dir = tmpRoot();
try {
buildCleanIngest(dir);
// Upstreams eget kanoniske eksempel (SPEC.md:773) er sitert. Samme fil skal
// ikke gi to lesninger avhengig av hvor markoeren staar.
writeRawRootIndex(dir, '---\nokf_version: "0.2"\n---\n\n# Bundle\n');
assert.equal(checkBundle(dir).okfVersion, '0.2', 'anfoerselstegn er YAML-syntaks, ikke del av verdien');
} finally {
rmSync(dir, { recursive: true, force: true });
}
});
test('(20e) okf-check: markoer helt fravaerende gir fortsatt exit 0 (ingen PRESENCE-krav)', () => {
const dir = tmpRoot();
try {
buildCleanIngest(dir);
writeRawRootIndex(dir, '---\ntitle: Bundle\n---\n\n# Bundle\n');
const r = checkBundle(dir);
assert.equal(r.okfVersion, null, 'fravaerende markoer skal vaere null');
assert.equal(r.okfLayout, null);
assert.equal(runCheck(dir).status, 0, 'okf_version er MAY i specen -- fravaer er ikke en feil');
} finally {
rmSync(dir, { recursive: true, force: true });
}
});
test('(20f) okf-check: frontmatter-markoer tolkes som YAML -- trailing kommentar lekker ikke inn i verdien', () => {
const dir = tmpRoot();
try {
buildCleanIngest(dir);
// Dette er caset som SKILLER de to lagene, og det eneste som roedner uten
// kodeendring: frontmatter ER YAML, saa ` # ...` er en kommentar og ikke del
// av verdien (lib/frontmatter.mjs:39, comment-leak-fiksen). Broedtekst-
// markoeren er IKKE YAML -- der er unquote() alene fortsatt riktig regel.
// Uten denne casen kunne steg 20 «bestaa» med dagens ettlags-regex, som
// scanner raa filtekst og ikke vet hva en frontmatter-blokk er.
writeRawRootIndex(dir, '---\nokf_version: 0.1 # upstream OKF-versjon\n---\n\n# Bundle\n');
assert.equal(checkBundle(dir).okfVersion, '0.1', 'YAML-kommentaren skal strippes i frontmatter');
} finally {
rmSync(dir, { recursive: true, force: true });
}
});