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
109 lines
4.8 KiB
JavaScript
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');
|
|
});
|
|
}
|