fix(okr): okf-check leser OKF-felt paa rot-nivaa (patch-lane #2)
parseFrontmatter.get() er bevisst innrykk-tolerant (`^\s*key:`) fordi nestet oppslag er load-bearing for inject-okr-context.mjs. Gaten arvet toleransen og leste en `type:` under `metadata:` som filas type -- en utypet fil passerte (exit 0). Rot-nivaa-kravet ligger derfor i kalleren; lib/frontmatter.mjs er uroert. Ny rootLevelGet() filtrerer bort innrykkede linjer og re-parser visningen med SAMME lib, saa sitering/kommentar-stripping har fortsatt en implementasjon. Brukt paa alle OKF-felt i checkBundle (type/title/anbefalte) + paa rootMarkers' frontmatter-gren, som ellers ga et annet svar enn den rot-ankrede broedtekst-fallbacken i samme funksjon. Fire utslag av samme defekt lukket: falsk negativ paa nestet type, nestet type FOER rot-noekkelen skygget rot-verdien (first-match), falsk positiv naar en NESTET title bar en lenke, og et nestet anbefalt felt dempet en advarsel. Tester (N1)-(N5) verifisert roede FOER fiksen; mutasjons-verifisert etterpaa. Suite 316 -> 321.
This commit is contained in:
parent
b10153692d
commit
35a0bb8377
2 changed files with 130 additions and 2 deletions
|
|
@ -23,6 +23,26 @@ import { unquote } from './okf-index.mjs';
|
|||
|
||||
const RECOMMENDED = ['resource', 'title', 'description', 'timestamp'];
|
||||
|
||||
// OKFs konsept-felt er ROT-noekler. parseFrontmatter.get() er derimot bevisst
|
||||
// innrykk-tolerant (`^\s*key:`, lib/frontmatter.mjs:29) fordi nestet oppslag er
|
||||
// load-bearing for inject-okr-context.mjs's organisasjon:/program:-lesing. Den
|
||||
// toleransen hoerer ikke hjemme i denne gaten, saa rot-nivaa-kravet ligger her
|
||||
// i KALLEREN -- lib-en skal fortsatt tolerere innrykk for sine egne konsumenter.
|
||||
//
|
||||
// Uten dette leste gaten en `type:` under `metadata:` som filas type (falsk
|
||||
// negativ: utypet fil passerte), lot en nestet `type:` FOER rot-noekkelen skygge
|
||||
// rot-verdien (first-match), felte dokumenter paa en lenke i en NESTET `title:`
|
||||
// (falsk positiv i strict-ingest) og lot et nestet `description:` dempe en
|
||||
// advarsel som burde staatt.
|
||||
//
|
||||
// Visningen re-parses av SAMME lib, aldri av en kopi av regelen: sitering og
|
||||
// trailing-kommentar-stripping har fortsatt kun EN implementasjon.
|
||||
function rootLevelGet(fmRaw) {
|
||||
if (fmRaw === null) return () => null;
|
||||
const flat = fmRaw.split('\n').filter((l) => !/^\s/.test(l)).join('\n');
|
||||
return parseFrontmatter(`---\n${flat}\n---`).get;
|
||||
}
|
||||
|
||||
// Lenke-deteksjon for --strict-ingest: hvert lenke-maal maa vaere en trygg,
|
||||
// on-disk bundle-root-relativ .md (anti-RAG-poison). B3 (A1): ALLE fire
|
||||
// standard lenkeformer fanges -- inline, referanse-definisjon, autolink og
|
||||
|
|
@ -84,7 +104,11 @@ 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);
|
||||
const { raw: fmRaw } = parseFrontmatter(raw);
|
||||
// Rot-nivaa ogsaa her: broedtekst-fallbacken under har ALLTID vaert rot-ankret
|
||||
// (`^key:` uten \s*), saa en innrykk-tolerant frontmatter-gren ga samme fil to
|
||||
// svar avhengig av hvilket lag som traff foerst.
|
||||
const fmGet = rootLevelGet(fmRaw);
|
||||
// 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).
|
||||
|
|
@ -124,7 +148,7 @@ export function checkBundle(root, { strictIngest = false, files } = {}) {
|
|||
const strictErrors = [];
|
||||
for (const f of concepts) {
|
||||
const raw = readFileSync(f, 'utf8');
|
||||
const { get } = parseFrontmatter(raw);
|
||||
const get = rootLevelGet(parseFrontmatter(raw).raw);
|
||||
const rel = relative(root, f);
|
||||
const type = get('type');
|
||||
if (!type) {
|
||||
|
|
|
|||
|
|
@ -1108,3 +1108,107 @@ test('(21e) okf-index -> okf-check: det emitteren skriver, leser checkeren', ()
|
|||
rmSync(dir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
// --- S57 (patch-lane #2): OKF-felt leses paa ROT-NIVAA, ikke innrykket ---
|
||||
// lib/frontmatter.mjs's `^\s*key:` er BEVISST innrykk-tolerant (nestet oppslag er
|
||||
// load-bearing for inject-okr-context.mjs's organisasjon:/program:-lesing) -- saa
|
||||
// rot-nivaa-kravet hoerer hjemme i KALLEREN. OKFs konsept-felt er rot-noekler:
|
||||
// en `type:` under `metadata:` er ikke filas type.
|
||||
|
||||
test('(N1) okf-check: type: KUN nestet -> fila teller som utypet (exit != 0)', () => {
|
||||
const dir = tmpRoot();
|
||||
try {
|
||||
buildCleanIngest(dir);
|
||||
writeFileSync(
|
||||
join(dir, 'dokumenter', 'nestet-type.md'),
|
||||
'---\ntitle: Uten rot-type\nmetadata:\n type: OKR\n---\n# Uten rot-type\n',
|
||||
);
|
||||
const { status, stdout } = runCheck(dir);
|
||||
assert.notEqual(status, 0, `nestet type: skal ikke telle som typet:\n${stdout}`);
|
||||
assert.match(stdout, /nestet-type\.md/, 'skal navngi den utypede filen');
|
||||
} finally {
|
||||
rmSync(dir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test('(N2) okf-check: en nestet type: FOER rot-type: skygger ikke rot-verdien', () => {
|
||||
const dir = tmpRoot();
|
||||
try {
|
||||
buildCleanIngest(dir);
|
||||
// Rot-verdien er i vokabularet; den nestede (som staar FOERST, og som
|
||||
// first-match-regelen ellers ville plukket) er det ikke.
|
||||
writeFileSync(
|
||||
join(dir, 'dokumenter', 'skygge.md'),
|
||||
'---\nmetadata:\n type: Tilfeldig\ntype: Notat\ntitle: Skygge\ndescription: x\n---\n# Skygge\n',
|
||||
);
|
||||
const r = checkBundle(dir, { strictIngest: true, files: [join(dir, 'dokumenter', 'skygge.md')] });
|
||||
assert.deepEqual(r.missingType, [], 'rot-type: finnes -> fila er typet');
|
||||
assert.deepEqual(r.strictErrors, [], 'vokab-sjekken skal se rot-verdien «Notat», ikke den nestede');
|
||||
} finally {
|
||||
rmSync(dir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test('(N3) okf-check --strict-ingest: lenke i en NESTET title er ikke filas title', () => {
|
||||
const dir = tmpRoot();
|
||||
try {
|
||||
buildCleanIngest(dir);
|
||||
// Ingen rot-title. Den nestede lenka leses av ingen konsument -> ingen
|
||||
// injeksjons-vektor, og skal derfor ikke felle dokumentet.
|
||||
writeFileSync(
|
||||
join(dir, 'dokumenter', 'nestet-title.md'),
|
||||
'---\ntype: Notat\nmetadata:\n title: "[Klikk her](http://evil.example)"\n---\n# Nestet title\n',
|
||||
);
|
||||
const r = checkBundle(dir, { strictIngest: true, files: [join(dir, 'dokumenter', 'nestet-title.md')] });
|
||||
assert.ok(
|
||||
!r.strictErrors.some((e) => /lenke-baerende title/.test(e)),
|
||||
`nestet title skal ikke gi lenke-baerende-title-feil, fikk:\n${r.strictErrors.join('\n')}`,
|
||||
);
|
||||
// Lenke-PORTEN er en annen akse og uendret: den skanner hele filen, ogsaa
|
||||
// frontmatteren, saa den eksterne URL-en felles fortsatt -- av lenke-regelen,
|
||||
// ikke av title-regelen.
|
||||
assert.ok(
|
||||
r.strictErrors.some((e) => /utrygg lenke/.test(e)),
|
||||
'den filbrede lenke-porten skal fortsatt felle den eksterne URL-en',
|
||||
);
|
||||
} finally {
|
||||
rmSync(dir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test('(N4) okf-check: et NESTET anbefalt felt demper ikke advarselen', () => {
|
||||
const dir = tmpRoot();
|
||||
try {
|
||||
buildCleanIngest(dir);
|
||||
writeFileSync(
|
||||
join(dir, 'dokumenter', 'nestet-anbefalt.md'),
|
||||
'---\ntype: Notat\nmetadata:\n description: Skjult beskrivelse.\n---\n# Nestet anbefalt\n',
|
||||
);
|
||||
const r = checkBundle(dir, { files: [join(dir, 'dokumenter', 'nestet-anbefalt.md')] });
|
||||
assert.ok(
|
||||
r.warnings.some((w) => /nestet-anbefalt\.md: mangler anbefalt felt .description./.test(w)),
|
||||
`rot-nivaa description mangler -> advarsel forventet, fikk:\n${r.warnings.join('\n')}`,
|
||||
);
|
||||
} finally {
|
||||
rmSync(dir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test('(N5) okf-check: en NESTET okf_version i rot-index leses ikke som rot-markoer', () => {
|
||||
const dir = tmpRoot();
|
||||
try {
|
||||
buildCleanIngest(dir);
|
||||
// Frontmatter-grenen i rootMarkers var innrykk-tolerant mens broedtekst-
|
||||
// fallbacken alltid har vaert rot-ankret (`^key:`). Samme fil ga da to svar
|
||||
// avhengig av hvilket lag som traff foerst -- her skal begge si MANGLER.
|
||||
writeFileSync(
|
||||
join(dir, 'index.md'),
|
||||
'---\nmeta:\n okf_version: "9.9"\n---\n# Bundle\n\nokf_layout: kb-layout-2026-06\n',
|
||||
);
|
||||
const r = checkBundle(dir);
|
||||
assert.equal(r.okfVersion, null, 'nestet okf_version er ikke rotens markoer');
|
||||
assert.equal(r.okfLayout, 'kb-layout-2026-06', 'den ekte broedtekst-markoeren skal fortsatt leses');
|
||||
} finally {
|
||||
rmSync(dir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue