feat(okr): KR-datakontrakt i skriv-malen

This commit is contained in:
Kjell Tore Guttormsen 2026-08-01 22:31:53 +02:00
commit e3a99b02ff
2 changed files with 84 additions and 0 deletions

View file

@ -104,6 +104,54 @@ KR[n]: [Formulering med baseline → target]
- Type: Committed / Aspirational
```
### Maskinlaget: KR-datakontrakten i frontmatter
Punktprosaen over er for mennesker og beholdes uendret. Ved siden av den legges
**samme tall som maskinlesbar frontmatter** i `okr-*.md`, slik at rapport-
generatorene (`/okr:rapport`) kan lese syklusen uten å tolke prosa.
Emitter fem nøkler per KR `n`, nummerert fra 1:
```yaml
---
type: OKR
kr1_navn: Saksbehandlingstid førerkort (dager)
kr1_baseline: 14
kr1_target: 5
kr1_naa: 11
kr1_type: committed
kr2_navn: Andel digitale søknader (%)
kr2_baseline: 60
kr2_target: 85
kr2_naa: 72
kr2_type: aspirational
---
```
| Nøkkel | Innhold |
|--------|---------|
| `krN_navn` | Tekst. Kortform av KR-en. **Enheten hører hjemme her** (og i prosaen) |
| `krN_baseline` | Utgangspunktet ved syklusstart |
| `krN_target` | Målverdien ved syklusslutt |
| `krN_naa` | Nåverdien. **Oppdateres ved hver check-in** |
| `krN_type` | `committed` eller `aspirational` — små bokstaver |
**Tre regler som gjør kontrakten lesbar for generatoren:**
1. **Rene tall uten enhet.** `kr1_baseline: 14`, aldri `14 dager` eller `60 %`.
Enheten bæres av `krN_navn` og av prosaen — et tall med enhet er ikke et tall.
2. **Score skrives aldri til fil.** Den beregnes av generatoren som
`(nåværende baseline) / (target baseline)`. En lagret score er en andre
sannhetskilde som drifter fra tallene den ble utledet av. Ingen `krN_score`.
3. **Nedadgående mål trenger ingen særbehandling**`baseline: 14, target: 5`
fungerer som-er, fordi teller og nevner begge blir negative.
For binære KR (milepæl): sett `krN_baseline: 0` og `krN_target: 1`, og la
`krN_naa` være `0` eller `1`.
`/okr:skriv` skriver ikke til disk. Emitter frontmatteren sammen med OKR-teksten
og be brukeren lagre den i `.claude/okr/syklus/[syklus-id]/okr-[navn].md`.
## Strategi-til-OKR
Når brukeren har strategidokument eller tildelingsbrev som input:

View file

@ -511,3 +511,39 @@ test('(m2) D4: kommunal-styring siterer ikke DFO-notat 2026:2 (sektoravgrensning
'DFO-notat 2026:2 er avgrenset mot kommunal sektor og kan ikke siteres her',
);
});
// --- (n) D5 steg 6: KR-datakontrakten (beslutning B-1) er dokumentert i skriv-malen ---
// Generatorene i lib/syklus-*.mjs leser KR-tall fra `krN_`-noekler i okr-*.md sin
// frontmatter. Kontrakten har ingen skjema-fil aa haandheve seg mot -- den haandheves
// av at DEFINISJONSFLATEN (skriv-malen) dokumenterer den, siden /okr:skriv er stedet
// tallene oppstaar. Vakten binder de to sidene sammen: endres kontrakten i lib/ uten
// at malen foelger etter, produserer brukeren filer generatoren ikke kan lese.
const KR_KONTRAKT_NOEKLER = ['navn', 'baseline', 'target', 'naa', 'type'];
test('(n) D5: skriv.md dokumenterer alle fem krN_-noeklene i KR-datakontrakten', () => {
const doc = readDoc('commands/skriv.md');
const missing = KR_KONTRAKT_NOEKLER.filter(
(k) => !new RegExp(`kr(?:N|\\d+)_${k}\\b`).test(doc),
);
assert.deepEqual(
missing,
[],
`KR-datakontrakten (B-1) mangler noekler i commands/skriv.md: ${missing.join(', ')}`,
);
});
// B-2: score BEREGNES, lagres aldri. En `krN_score`-noekkel i malen ville skapt en
// andre sannhetskilde som kan drifte fra (naa - baseline) / (target - baseline).
// Fravaers-invariant paa NOEKKELFORMEN slik lib/frontmatter.mjs:29 leser den
// (`^\s*key:`) -- ikke paa ordet "score", som malen legitimt bruker om
// moonshot/roofshot-kalibrering (:124), og ikke paa prosa som FORBYR noekkelen.
// Det er noekkelen i frontmatter som skaper den andre sannhetskilden; en omtale
// av den gjoer det ikke.
test('(n2) D5: skriv.md introduserer ingen krN_score-noekkel (score lagres aldri)', () => {
const offenders = linesMatching(readDoc('commands/skriv.md'), /^\s*kr(?:N|\d+)_score\s*:/m);
assert.deepEqual(
offenders,
[],
'score skal beregnes fra baseline/target/naa, aldri lagres som egen noekkel',
);
});