fix(okr): siterings-aksen naadde produsenten, ikke checkeren

1.8.2 unquotet parseExistingIndex (produsent-siden). okf-check sin
rootMarkers/pick returnerte fortsatt raa streng, saa samme fil ga to
lesninger av samme markoer:

  paa disk:     okf_version: "0.2"        (upstreams eget eksempel, SPEC.md:773)
  produsenten:  0.2                       <- unquotet
  checkeren:    "0.2"                     <- raa

Catalog maalte divergensen direkte mot sin egen checker gjennom
evaluateBundle: begge implementasjonene AKSEPTERER -- avviket ligger paa
verdien, ikke paa dommen. Ingen gate rammes i dag fordi paritetskorpuset
ikke har en sitert fikstur, hvilket ogsaa betyr at paritetsgatens groenne
farge ikke dekket denne aksen: den var "ikke kjoert", ikke "som forventet".

Fiks: unquote() eksporteres fra okf-index.mjs og importeres av
okf-check.mjs -- EN regel delt, ikke to kopier som kan drifte fra
hverandre. Script-til-script-import foelger etablert moenster
(innboks-ingest.mjs:51).

Tester: 192 -> 197. Fire kjoert roede foer fiksen. Den femte
(ubalansert-vakten) var groenn foer fiksen -- raa streng bevarer "0.2
naturlig -- og er derfor mutasjons-verifisert: en graadig unquote roedner
baade produsent- og checker-vakten.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XKoS7Eb8YmAfeCf1EKZXt1
This commit is contained in:
Kjell Tore Guttormsen 2026-07-31 18:25:58 +02:00
commit 65c9412cca
4 changed files with 79 additions and 2 deletions

View file

@ -12,9 +12,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- **Styringssløyfas ut-side i `okr-offentlig-governance.md`** — der guiden tidligere hadde én 4-raders tabell om årsrapport, dekker den nå styringsdialogen som helhet: etatsstyringsmøtet som sentralt møtepunkt, årsrapportens seks faste deler med del III («Årets aktiviteter og resultater») som ankeret for måloppnåelse og del IV som stedet OKR-prosessen selv dokumenteres, samt tre regler for å rapportere aspirational OKR oppover uten at 0.7 leses som svikt.
- **Gevinstrealisering som eget kapittel i `okr-offentlig-governance.md`** — DFØs metodikk mappet mot OKR (gevinst → outcome-KR, gevinstansvarlig → KR-eier), med linjeeierskapet til gevinstansvarlig som hovedpoeng og Prosjektveiviseren-koblingen. Lukker at gevinstrealisering tidligere var ett enkelt datapunkt i `metrics-library.md` uten metodikk.
### Fixed
- **Siterings-aksen nådde produsenten i 1.8.2, men ikke checkeren.** `okf-check`s `rootMarkers`/`pick` returnerte rå streng, så samme fil ga to lesninger: produsenten tolket `okf_version: "0.2"` som `0.2`, mens checkeren ekkoet `"0.2"`. Catalog målte divergensen direkte mot sin egen checker på en bundle som følger upstreams eget kanoniske eksempel (`SPEC.md:773`) — begge implementasjonene *aksepterte*, avviket lå på verdien. `unquote()` er nå eksportert fra `okf-index.mjs` og importert av `okf-check.mjs`, så begge sider deler én regel i stedet for hver sin kopi. Ingen gate rødnet av dette i dag, fordi paritetskorpuset ikke har en sitert fikstur — hvilket også betyr at paritetsgatens grønne farge ikke dekket siterings-aksen; den var «ikke kjørt», ikke «som forventet».
### Changed
- **F-j lukket.** Dette lukker det siste av de ti F-funnene fra `review-2026-07-16.md` §3. F-j (styringssløyfas ut-side, kommunal styringslinje, gevinstrealisering) ble eksplisitt utsatt fra 1.8.0 til denne fasen via Non-Goal i C-fasens brief, mens SC-85 samtidig påsto at alle ti var lukket. Diskrepansen var reell og er nå løst ved å faktisk lukke funnet — SC-85 leses fra og med her som dekkende for F-a…F-j.
- Referansetellingen oppdatert på alle flater som bærer den: `CLAUDE.md`, `README.md` og `/okr:freshen-references` (som scorer 17 av 18 filer — kvalitetsrubrikken er fortsatt ekskludert, siden å score scoringsinstrumentet er sirkulært).
- **Testsuite 192 → 197 cases**, alle grønne. Fem nye i `tests/okf-check.test.mjs` for checker-siden av siterings-aksen: sitert `okf_version`, enkeltsitert `okf_version`, sitert `okf_layout`, CLI-ekkoet, og en ubalansert sekvens som skal bevares urørt. De fire første ble kjørt røde før fiksen; den femte er mutasjons-verifisert rød mot en grådig unquote (som da rødner både produsent- og checker-vakten).
## [1.8.2] - 2026-07-31

View file

@ -19,6 +19,7 @@ import { fileURLToPath } from 'node:url';
import { parseFrontmatter } from '../lib/frontmatter.mjs';
import { TYPE_VOCAB } from '../lib/okf-vocab.mjs';
import { resolveBundleLink } from '../lib/okf-links.mjs';
import { unquote } from './okf-index.mjs';
const RECOMMENDED = ['resource', 'title', 'description', 'timestamp'];
@ -61,13 +62,20 @@ function walkConcepts(root) {
// `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.
function rootMarkers(root) {
const idx = join(root, 'index.md');
if (!existsSync(idx)) return { okfVersion: null, okfLayout: null };
const raw = readFileSync(idx, 'utf8');
const pick = (key) => {
const m = raw.match(new RegExp(`^${key}:\\s*(.+)$`, 'm'));
return m ? m[1].trim() : null;
return m ? unquote(m[1].trim()) : null;
};
return { okfVersion: pick('okf_version'), okfLayout: pick('okf_layout') };
}

View file

@ -46,7 +46,7 @@ const UPSTREAM_VERSION_RE = /^\d+(?:\.\d+)+$/;
// 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) {
export function unquote(s) {
const q = s[0];
if ((q === '"' || q === "'") && s.length >= 2 && s.endsWith(q)) return s.slice(1, -1);
return s;

View file

@ -746,6 +746,71 @@ test('okf-index: UBALANSERT anfoerselstegn strippes ikke (konservativ unquote)',
}
});
// --- S40: siterings-aksen naar ogsaa CHECKEREN (lese-siden) ---
// 1.8.2 unquotet PRODUSENTEN (okf-index/parseExistingIndex). Checkerens
// rootMarkers/pick returnerte fortsatt raa streng, saa samme fil ga to lesninger:
// produsenten tolket `okf_version: "0.2"` som 0.2, checkeren ekkoet «"0.2"».
// Catalog maalte divergensen mot sin egen checker (begge AKSEPTERER -- avviket er
// paa VERDIEN) paa en bundle som foelger upstreams eget eksempel (SPEC.md:773).
// Unquote er samme konservative regel som paa produsent-siden: KUN et matchende
// par er YAML-syntaks; en halv sekvens er en ugyldig verdi som bevares uroert.
test('okf-check: SITERT okf_version rapporteres unquoted (siterings-akse, lese-siden)', () => {
const dir = tmpRoot();
try {
writeRootIndex(dir, ['okf_version: "0.2"']);
const r = checkBundle(dir);
assert.equal(r.okfVersion, '0.2', 'anfoerselstegn er YAML-syntaks, ikke del av verdien');
} finally {
rmSync(dir, { recursive: true, force: true });
}
});
test('okf-check: ENKELTSITERT okf_version rapporteres unquoted', () => {
const dir = tmpRoot();
try {
writeRootIndex(dir, ["okf_version: '0.2'"]);
const r = checkBundle(dir);
assert.equal(r.okfVersion, '0.2');
} finally {
rmSync(dir, { recursive: true, force: true });
}
});
test('okf-check: SITERT okf_layout rapporteres unquoted', () => {
const dir = tmpRoot();
try {
writeRootIndex(dir, ['okf_version: 0.2', 'okf_layout: "kb-layout-2099-12"']);
const r = checkBundle(dir);
assert.equal(r.okfLayout, 'kb-layout-2099-12', 'begge markoerene leses likt');
} finally {
rmSync(dir, { recursive: true, force: true });
}
});
test('okf-check: UBALANSERT anfoerselstegn bevares uroert (konservativ unquote)', () => {
const dir = tmpRoot();
try {
writeRootIndex(dir, ['okf_version: "0.2']);
const r = checkBundle(dir);
assert.equal(r.okfVersion, '"0.2', 'halv sekvens er ugyldig verdi, ikke noe aa gjette paa');
} finally {
rmSync(dir, { recursive: true, force: true });
}
});
test('okf-check CLI: sitert markoerverdi ekkoes unquoted i utskriften', () => {
const dir = tmpRoot();
try {
writeRootIndex(dir, ['okf_version: "0.2"', 'okf_layout: "kb-layout-2099-12"']);
const { stdout } = runCheck(dir);
assert.match(stdout, /okf_version: 0\.2$/m, 'menneskelig sammenligning skal se verdien, ikke syntaksen');
assert.match(stdout, /okf_layout: kb-layout-2099-12$/m);
} finally {
rmSync(dir, { recursive: true, force: true });
}
});
test('okf-index CLI: --okf-layout bumper layout og lar okf_version staa', () => {
const dir = tmpRoot();
try {