ms-ai-architect/tests/kb-update/test-ai-act-deadlines-sync.test.mjs
Kjell Tore Guttormsen a8ae1fd276 feat(ms-ai-architect): RX-REG — én maskinlesbar AI Act-frist-kilde + synk-test (B2-B5, R3-5, M10) [skip-docs]
Rotårsak-mekanisme: scripts/kb-update/data/ai-act-deadlines.json er nå eneste
kilde for AI Act-frister; CLAUDE.md-tabellen, assessor-malen og begge hooks
synk-testes mot den (tests/kb-update/test-ai-act-deadlines-sync.test.mjs, 9
tester, TDD rød→grønn). Suite 815/815.

- B2: «Kommisjonen kan fremskynde»/«ytre grense» fjernet (trigger droppet i
  endelig Omnibus-tekst)
- B3: status oppdatert i alle 3 lag — formelt vedtatt (EP 2026-06-16, Rådet
  2026-06-29), trer i kraft ved OJ-publisering (ikke publisert per 2026-07-15,
  verifisert mot EUR-Lex 32024R1689)
- B4: Art. 50-raden «Gjeldende» → «Fra 2026-08-02»
- B5: stop-hooken feilmerket 2026-08-02 som «GPAI-frist» — begge hooks leser
  nå kilden, 0 hardkodede frist-literals
- R3-5: 2026-12-02-raden (Art. 50(2)) inn i assessor-malen
- M10: Nkom navngitt som koordinerende markedstilsynsmyndighet + nasjonalt
  kontaktpunkt (kilde: nkom.no/ki/regulering/hvem-handhever)
- pending_oj-markører i kilden for RX-REG² (nudifiserings-forbud, Art. 111)
- .gitignore: whitelist for den kuraterte frist-kilden
2026-07-15 06:13:36 +02:00

109 lines
4.8 KiB
JavaScript

// tests/kb-update/test-ai-act-deadlines-sync.test.mjs
// TDD for RX-REG (review 2026-07-09, B2/B3/B4/B5 + R3-5/M10): the EU AI Act
// deadlines get ONE machine-readable source — scripts/kb-update/data/ai-act-deadlines.json
// — and every consumer is sync-tested against it:
// - CLAUDE.md «Viktige frister»-tabellen (date + label + status verbatim)
// - agents/ai-act-assessor.md frist-malen (identical DATE SET; labels are
// template-compact by design — R3-5: the 2026-12-02 row was missing)
// - hooks/scripts/{session-start-context,stop-assessment-reminder}.mjs must READ
// the source instead of carrying hardcoded date literals (B5: the stop hook
// labelled 2026-08-02 «GPAI-frist» — wrong article; GPAI was 2025-08-02)
// Refuted-claim guard: the final Omnibus text (EP 2026-06-16, Council 2026-06-29)
// dropped the acceleration trigger, so «kan fremskynde»/«ytre grense» must not
// appear; Nkom is the designated coordinating authority, so «under etablering»
// must be gone (source: nkom.no/ki/regulering/hvem-handhever).
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs';
import { loadAiActDeadlines, DATA_URL } from '../../scripts/kb-update/lib/ai-act-deadlines.mjs';
const pluginRoot = new URL('../../', import.meta.url);
const read = (rel) => readFileSync(new URL(rel, pluginRoot), 'utf8');
const source = loadAiActDeadlines();
// --- 1. The source itself ---
test('deadline source loads with omnibus adoption-status + deadline entries', () => {
assert.ok(source, 'ai-act-deadlines.json must exist and parse');
assert.ok(source.omnibus, 'source must carry an omnibus adoption block');
assert.equal(typeof source.omnibus.status, 'string', 'omnibus.status (vedtaks-status) required');
assert.ok(Array.isArray(source.deadlines) && source.deadlines.length >= 6, 'at least the 6 known deadlines');
for (const dl of source.deadlines) {
assert.match(dl.date, /^\d{4}-\d{2}-\d{2}$/, `ISO date, got: ${dl.date}`);
assert.ok(dl.label && dl.status, `label + status required for ${dl.date}`);
}
const dates = source.deadlines.map((d) => d.date);
assert.deepEqual(dates, [...dates].sort(), 'deadlines sorted ascending');
assert.equal(new Set(dates).size, dates.length, 'no duplicate dates');
});
test('source carries no refuted claims and no stale authority phrasing', () => {
const raw = readFileSync(DATA_URL, 'utf8');
assert.ok(!/fremskynde|ytre grense|under etablering/i.test(raw),
'refuted Omnibus-acceleration claim / stale Nkom phrasing must not re-enter the source');
});
test('status wording never claims «Gjeldende» for a future date (rot-guard)', () => {
const today = new Date().toISOString().slice(0, 10);
for (const dl of source.deadlines) {
if (/^Gjeldende/i.test(dl.status)) {
assert.ok(dl.date <= today, `${dl.date} marked «${dl.status}» before it applies`);
}
}
});
// --- 2. Markdown consumers ---
// Rows whose FIRST cell is an ISO date; other tables (#-keyed) never match.
function parseDeadlineTable(md) {
const rows = [];
for (const line of md.split('\n')) {
const m = line.match(/^\|\s*(\d{4}-\d{2}-\d{2})\s*\|\s*(.+?)\s*\|\s*(.+?)\s*\|\s*$/);
if (m) rows.push({ date: m[1], label: m[2], status: m[3] });
}
return rows;
}
test('CLAUDE.md frist-tabell matches the source verbatim (date + label + status)', () => {
const rows = parseDeadlineTable(read('CLAUDE.md'));
assert.deepEqual(
rows.map((r) => [r.date, r.label, r.status]),
source.deadlines.map((d) => [d.date, d.label, d.status]),
);
});
test('CLAUDE.md drops refuted claims and names Nkom', () => {
const md = read('CLAUDE.md');
assert.ok(!/fremskynde|ytre grense|under etablering/.test(md));
assert.ok(md.includes('Nkom'), 'M10: Nkom (koordinerende tilsynsmyndighet) must be named');
});
test('ai-act-assessor frist-mal covers exactly the source dates (R3-5: 2026-12-02)', () => {
const rows = parseDeadlineTable(read('agents/ai-act-assessor.md'));
assert.deepEqual(
rows.map((r) => r.date).sort(),
source.deadlines.map((d) => d.date).sort(),
);
});
test('ai-act-assessor names Nkom and drops «under etablering»', () => {
const md = read('agents/ai-act-assessor.md');
assert.ok(md.includes('Nkom'));
assert.ok(!/under etablering/.test(md));
});
// --- 3. Hook consumers read the source, no drift-prone literals ---
for (const hook of [
'hooks/scripts/session-start-context.mjs',
'hooks/scripts/stop-assessment-reminder.mjs',
]) {
test(`${hook} reads deadlines from the shared source`, () => {
const src = read(hook);
assert.ok(src.includes('ai-act-deadlines'), 'must import the shared loader');
assert.ok(!/new Date\('20\d{2}-\d{2}-\d{2}'\)/.test(src), 'no hardcoded deadline literals');
assert.ok(!src.includes('GPAI'), 'B5: no GPAI-labelled deadline left in hook code');
});
}