fix(okr): siterte markoerverdier korrumperte begge rot-markoerene

Upstreams eneste kanoniske eksempel med verdi (okf/SPEC.md:773) skriver
`okf_version: "0.2"` -- sitert. parseExistingIndex fanget anfoerselstegnene
raatt, UPSTREAM_VERSION_RE avviste `"0.2"` som ikke-upstream-form, og
resolveMarkers behandlet den som en layout-verdi fra foer 1.8.1-splitten:

  inn:  okf_version: "0.2"
  ut:   okf_version: 0.1      <- nedgradert til vaar konstant
        okf_layout: "0.2"     <- ekte upstream-versjon i feil markoer

Begge markoerene oedelagt, og dataene ikke gjenopprettelige uten aa kjenne
originalen. Bugen er i released 1.8.1-kode og traff enhver bundle som hadde
skrevet markoeren slik upstream selv viser den.

Fiks: anfoerselstegnene er YAML-strengsyntaks, ikke del av verdien, saa de
strippes ved parse -- foer verdien tolkes og foer den emitteres. Verdien
emitteres normalisert (unquoted), slik at vaar egen utskrift bestaar en
form-sjekk som kjoeres paa raa streng. Unquote er konservativ: kun et
matchende par strippes, en halv sekvens bevares uroert.

Tester: 187 -> 192. Fire nye kjoert roede foer fiksen; den femte
(ubalansert-vakten) mutasjons-verifisert roed mot en graadig unquote.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmXFhc6v9cs4YZ5AWFnQs8
This commit is contained in:
Kjell Tore Guttormsen 2026-07-31 17:12:24 +02:00
commit 85143be7b1
2 changed files with 98 additions and 3 deletions

View file

@ -35,9 +35,23 @@ export const OKF_VERSION = '0.1';
export const OKF_LAYOUT = 'kb-layout-2026-06';
// Upstream-versjoner er numerisk punktnotasjon (`0.1`). Alt annet i et
// `okf_version`-felt er en layout-verdi fra foer splitten.
// `okf_version`-felt er en layout-verdi fra foer splitten. Testes ALLTID mot en
// unquotet verdi -- se unquote().
const UPSTREAM_VERSION_RE = /^\d+(?:\.\d+)+$/;
// En markoerverdi kan vaere sitert: upstreams eget kanoniske eksempel
// (`okf/SPEC.md:773`) skriver `okf_version: "0.2"`. Anfoerselstegnene er
// YAML-strengsyntaks, ikke del av verdien, saa de maa vekk FOER verdien tolkes
// (UPSTREAM_VERSION_RE) og foer den emitteres -- ellers leses en gyldig
// upstream-versjon som en layout-verdi og migreres til feil markoer.
// KUN et matchende par strippes: en halv anfoerselstegn-sekvens er en ugyldig
// verdi som skal bevares uroert, ikke gjettes paa.
function unquote(s) {
const q = s[0];
if ((q === '"' || q === "'") && s.length >= 2 && s.endsWith(q)) return s.slice(1, -1);
return s;
}
// Drop-zone (raa innboks-filer) + skjulte kataloger (.cache osv.) er ikke nivaaer:
// de skal verken faa egen index.md eller listes som underkatalog-peker. Maa
// filtreres i BEGGE enumererings-steder (subdir-listing + rekursjon).
@ -66,9 +80,9 @@ function parseExistingIndex(path) {
result.heading = line.slice(2).trim();
}
const ver = line.match(/^okf_version:\s*(.+)$/);
if (ver) result.okfVersion = ver[1].trim();
if (ver) result.okfVersion = unquote(ver[1].trim());
const lay = line.match(/^okf_layout:\s*(.+)$/);
if (lay) result.okfLayout = lay[1].trim();
if (lay) result.okfLayout = unquote(lay[1].trim());
const entry = line.match(/^\*\s*\[([^\]]*)\]\(([^)]+)\)(?:\s*-\s*(.*))?$/);
if (entry) {
if (!(entry[2] in result.descByLink)) result.linkOrder.push(entry[2]);

View file

@ -665,6 +665,87 @@ test('okf-index: eksisterende splittet form bevares verbatim (idempotent vedlike
}
});
// --- S38: SITERTE markoerverdier (datakorrumperings-fiks) ---
// Upstreams ENESTE kanoniske eksempel med verdi (`okf/SPEC.md:773`) skriver
// `okf_version: "0.2"` -- SITERT. Anfoerselstegnene er YAML-strengsyntaks, ikke
// del av verdien. Foer denne fiksen fanget parsen dem raatt, UPSTREAM_VERSION_RE
// avviste `"0.2"` som ikke-upstream-form, og migrasjonen flyttet den til
// okf_layout mens okf_version ble nedgradert til vaar konstant -- BEGGE
// markoerene oedelagt, uten gjenoppretting. Verdien tolkes derfor unquoted, og
// emitteres normalisert (unquoted) saa vaar egen utskrift bestaar en form-sjekk
// som kjoeres paa raa streng.
test('okf-index: SITERT upstream-verdi i okf_version migreres IKKE til okf_layout', () => {
const dir = tmpRoot();
try {
writeRootIndex(dir, ['okf_version: "0.2"']);
generateIndexes(dir);
const root = readFileSync(join(dir, 'index.md'), 'utf8');
assert.match(root, /^okf_version: 0\.2$/m, 'sitert upstream-verdi skal bevares, normalisert');
assert.match(root, /^okf_layout: kb-layout-2026-06$/m, 'layout fylles fra konstant');
assert.ok(!/okf_layout: "?0\.2"?$/m.test(root), 'upstream-verdien skal ALDRI havne i okf_layout');
// Idempotent: andre kjoering leser sin egen normaliserte utskrift uendret.
generateIndexes(dir);
assert.equal(readFileSync(join(dir, 'index.md'), 'utf8'), root, 'andre kjoering skal ikke endre noe');
} finally {
rmSync(dir, { recursive: true, force: true });
}
});
test('okf-index: ENKELTSITERT upstream-verdi behandles likt (YAML-syntaks, ikke verdi)', () => {
const dir = tmpRoot();
try {
writeRootIndex(dir, ["okf_version: '0.2'"]);
generateIndexes(dir);
const root = readFileSync(join(dir, 'index.md'), 'utf8');
assert.match(root, /^okf_version: 0\.2$/m);
assert.ok(!/okf_layout: '?0\.2'?$/m.test(root), 'upstream-verdien skal ALDRI havne i okf_layout');
} finally {
rmSync(dir, { recursive: true, force: true });
}
});
test('okf-index: SITERT legacy layout-verdi migreres og emitteres unquoted', () => {
const dir = tmpRoot();
try {
writeRootIndex(dir, ['okf_version: "kb-layout-2025-01"']);
generateIndexes(dir);
const root = readFileSync(join(dir, 'index.md'), 'utf8');
assert.match(root, /^okf_version: 0\.1$/m, 'ekte layout-verdi flyttes fortsatt ut av okf_version');
assert.match(root, /^okf_layout: kb-layout-2025-01$/m, 'verdien flyttes uten anfoerselstegn');
} finally {
rmSync(dir, { recursive: true, force: true });
}
});
test('okf-index: SITERT okf_layout bevares, normalisert', () => {
const dir = tmpRoot();
try {
writeRootIndex(dir, ['okf_version: 0.2', 'okf_layout: "kb-layout-2099-12"']);
generateIndexes(dir);
const root = readFileSync(join(dir, 'index.md'), 'utf8');
assert.match(root, /^okf_version: 0\.2$/m);
assert.match(root, /^okf_layout: kb-layout-2099-12$/m, 'eksisterende layout bevares uten anfoerselstegn');
} finally {
rmSync(dir, { recursive: true, force: true });
}
});
test('okf-index: UBALANSERT anfoerselstegn strippes ikke (konservativ unquote)', () => {
const dir = tmpRoot();
try {
// Kun et MATCHENDE par er YAML-syntaks. En halv anfoerselstegn-sekvens er en
// ugyldig verdi, ikke en upstream-versjon, og faar migrasjonens legacy-vei.
writeRootIndex(dir, ['okf_version: "0.2']);
generateIndexes(dir);
const root = readFileSync(join(dir, 'index.md'), 'utf8');
assert.match(root, /^okf_version: 0\.1$/m, 'ugyldig verdi er ikke upstream-form');
assert.match(root, /^okf_layout: "0\.2$/m, 'verdien flyttes verbatim, uroert');
} finally {
rmSync(dir, { recursive: true, force: true });
}
});
test('okf-index CLI: --okf-layout bumper layout og lar okf_version staa', () => {
const dir = tmpRoot();
try {