diff --git a/.gitignore b/.gitignore index 748ddba..432fbd9 100644 --- a/.gitignore +++ b/.gitignore @@ -23,10 +23,12 @@ node_modules/ .work/ org/ # Generated KB-update artifacts (registry, reports) are ignored, but the -# hand-authored taxonomy (lag 0) and the decision ledger (lag 2) are tracked. +# hand-authored taxonomy (lag 0), the decision ledger (lag 2) and the curated +# AI Act deadline source (RX-REG, sync-tested consumer contract) are tracked. scripts/kb-update/data/* !scripts/kb-update/data/domain-taxonomy.json !scripts/kb-update/data/decisions.json +!scripts/kb-update/data/ai-act-deadlines.json # Generated skill-lifecycle detection report (Spor B / B1) — regenerated on demand, # like the kb-update reports above. The detector script + curated inputs are tracked. scripts/kb-eval/data/skill-lifecycle-report.json diff --git a/CLAUDE.md b/CLAUDE.md index ba4bfee..1a9590a 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -108,18 +108,20 @@ Agenter leser navngitte kjernefiler, ikke hele kataloger. «3 kjernefiler» er n ## Viktige frister (EU AI Act) -> **NB — Digital Omnibus (provisorisk):** EU-rådet og Parlamentet ble 7. mai 2026 enige om å utsette høyrisiko-fristene (Digital Omnibus). Endringene trer i kraft først ved formell vedtakelse + publisering i Official Journal (ventet før 2026-08-02), så datoene under er **foreløpige**. Des. 2027 er en **ytre grense** — Kommisjonen kan fremskynde til 6 mnd etter at standarder/spesifikasjoner/veiledning er på plass. Kjør `/architect:classify` for systemspesifikk vurdering. +> **NB — Digital Omnibus (vedtatt, avventer OJ):** Digital Omnibus utsetter høyrisiko-fristene i AI Act. Endringen er **formelt vedtatt** (Europaparlamentet 16. juni, Rådet 29. juni 2026) og trer i kraft tredje dag etter publisering i Official Journal — senest 2026-07-30 for anvendelse før 2026-08-02. Datoene under følger vedtatt tekst og bekreftes mot OJ ved publisering. Kjør `/architect:classify` for systemspesifikk vurdering. | Frist | Krav | Status | |-------|------|--------| | 2025-02-02 | Forbudte AI-praksiser (Art. 5) | Gjeldende | | 2025-08-02 | GPAI-krav + governance/sanksjoner (Art. 99) | Gjeldende | -| 2026-08-02 | Transparens (Art. 50): merking av syntetisk innhold gjelder | Gjeldende | +| 2026-08-02 | Transparens (Art. 50): merking av syntetisk innhold | Fra 2026-08-02 | | 2026-12-02 | Art. 50(2): frist for maskinlesbar merking i eksisterende generative systemer | Overgang (Omnibus) | -| 2027-12-02 | Annex III høyrisiko (frittstående) — utsatt fra 2026-08-02 | Provisorisk (Omnibus) | -| 2028-08-02 | Annex I høyrisiko (innebygd i regulerte produkter) | Provisorisk (Omnibus) | +| 2027-12-02 | Annex III høyrisiko (frittstående) — utsatt fra 2026-08-02 | Vedtatt (Omnibus, avventer OJ) | +| 2028-08-02 | Annex I høyrisiko (innebygd i regulerte produkter) | Vedtatt (Omnibus, avventer OJ) | -**Tilsynsmyndigheter:** Datatilsynet (personvern), nasjonal AI-tilsynsmyndighet (under etablering), sektortilsyn. +> Maskinlesbar kilde: `scripts/kb-update/data/ai-act-deadlines.json` — synk-testet mot denne tabellen, assessor-malen og hookene (`tests/kb-update/test-ai-act-deadlines-sync.test.mjs`). Oppdater kilden først. + +**Tilsynsmyndigheter:** Nkom (koordinerende markedstilsynsmyndighet og nasjonalt kontaktpunkt for AI-forordningen), Datatilsynet (personvern), sektortilsyn kan utpekes i tillegg. ## Relaterte plugins (fremtidig) diff --git a/agents/ai-act-assessor.md b/agents/ai-act-assessor.md index 5e21ef5..2494b9c 100644 --- a/agents/ai-act-assessor.md +++ b/agents/ai-act-assessor.md @@ -142,8 +142,9 @@ Anbefal oppfølgingsaktiviteter: | 2025-02-02 | Forbudte AI-praksiser (Art. 5) | [Gjelder/Gjelder ikke] | | 2025-08-02 | GPAI-krav + governance/sanksjoner (Art. 99) | [Gjelder/Gjelder ikke] | | 2026-08-02 | Transparens (Art. 50, syntetisk innhold) | [Gjelder/Gjelder ikke] | -| 2027-12-02 | Annex III høyrisiko — provisorisk (utsatt fra 2026-08-02 via Omnibus, avventer OJ) | [Gjelder/Gjelder ikke] | -| 2028-08-02 | Annex I høyrisiko innebygd (provisorisk) | [Gjelder/Gjelder ikke] | +| 2026-12-02 | Art. 50(2): maskinlesbar merking i eksisterende generative systemer | [Gjelder/Gjelder ikke] | +| 2027-12-02 | Annex III høyrisiko — utsatt fra 2026-08-02 (Omnibus vedtatt, avventer OJ) | [Gjelder/Gjelder ikke] | +| 2028-08-02 | Annex I høyrisiko innebygd (Omnibus vedtatt, avventer OJ) | [Gjelder/Gjelder ikke] | ### Referanser - [Liste over KB-filer og MCP-kilder brukt] @@ -176,8 +177,8 @@ Bruk `microsoft_docs_search` for: ## Norwegian Public Sector Context - Alle vurderinger gjøres i norsk kontekst (EØS-implementering) -- Datatilsynet er sannsynlig tilsynsmyndighet (personverndimensjon) -- Nasjonal AI-tilsynsmyndighet er under etablering +- Nkom er koordinerende markedstilsynsmyndighet og nasjonalt kontaktpunkt for AI-forordningen i Norge +- Datatilsynet er tilsynsmyndighet for personverndimensjonen; sektortilsyn kan utpekes i tillegg - Forvaltningsloven gjelder i tillegg til AI Act for vedtakssystemer - Offentlig sektor er nesten alltid deployer, sjelden provider diff --git a/hooks/scripts/session-start-context.mjs b/hooks/scripts/session-start-context.mjs index 5f98d05..ad18173 100644 --- a/hooks/scripts/session-start-context.mjs +++ b/hooks/scripts/session-start-context.mjs @@ -20,6 +20,7 @@ import { FREE_CONTEXT_FILE, buildOrgSummary, } from '../../scripts/kb-update/lib/user-data.mjs'; +import { loadAiActDeadlines } from '../../scripts/kb-update/lib/ai-act-deadlines.mjs'; const pluginRoot = process.env.CLAUDE_PLUGIN_ROOT || join(process.cwd()); const cwd = process.cwd(); @@ -93,20 +94,12 @@ if (shouldRunDetection(scheduleConfig, lastPollDaysAgo).run) { } } -// --- 3. Check EU AI Act deadlines --- -const AI_ACT_DEADLINES = [ - // NB: Digital Omnibus (prov. enighet 2026-05-07) utsatte høyrisiko; datoer foreløpige til OJ-publisering. - { date: new Date('2025-02-02'), label: 'Forbudte AI-praksiser (Art. 5)' }, - { date: new Date('2025-08-02'), label: 'GPAI-krav + governance/sanksjoner (Art. 99)' }, - { date: new Date('2026-08-02'), label: 'Transparens Art. 50 (syntetisk innhold)' }, - { date: new Date('2026-12-02'), label: 'Art. 50(2) merking — frist for eksisterende generative systemer (Omnibus)' }, - { date: new Date('2027-12-02'), label: 'Annex III høyrisiko (provisorisk, Omnibus — utsatt fra 2026-08-02)' }, - { date: new Date('2028-08-02'), label: 'Annex I høyrisiko innebygd (provisorisk, Omnibus)' }, -]; +// --- 3. Check EU AI Act deadlines (single source: scripts/kb-update/data/ai-act-deadlines.json) --- +const aiActSource = loadAiActDeadlines(); let nearestDeadline = null; -for (const dl of AI_ACT_DEADLINES) { - const daysLeft = Math.ceil((dl.date.getTime() - now) / DAY_MS); +for (const dl of aiActSource ? aiActSource.deadlines : []) { + const daysLeft = Math.ceil((new Date(dl.date).getTime() - now) / DAY_MS); if (daysLeft > 0 && daysLeft <= 180) { if (!nearestDeadline || daysLeft < nearestDeadline.daysLeft) { nearestDeadline = { ...dl, daysLeft }; diff --git a/hooks/scripts/stop-assessment-reminder.mjs b/hooks/scripts/stop-assessment-reminder.mjs index 7973aec..a52f885 100644 --- a/hooks/scripts/stop-assessment-reminder.mjs +++ b/hooks/scripts/stop-assessment-reminder.mjs @@ -5,6 +5,7 @@ import { readdirSync, statSync, existsSync } from 'node:fs'; import { join } from 'node:path'; +import { loadAiActDeadlines } from '../../scripts/kb-update/lib/ai-act-deadlines.mjs'; const cwd = process.cwd(); const workDir = join(cwd, '.work'); @@ -60,12 +61,18 @@ const suggestions = [ '/architect:summary — lag beslutningsnotat', ]; -// Add AI Act suggestion if deadline is within 180 days +// Add AI Act suggestion if the nearest deadline (from the shared source) is within 180 days const DAY_MS = 24 * 60 * 60 * 1000; -const gpaiDeadline = new Date('2026-08-02'); -const daysToGpai = Math.ceil((gpaiDeadline.getTime() - now) / DAY_MS); -if (daysToGpai > 0 && daysToGpai <= 180) { - suggestions.push(`/architect:classify — EU AI Act-klassifisering (${daysToGpai}d til GPAI-frist)`); +const aiActSource = loadAiActDeadlines(); +let nearestDeadline = null; +for (const dl of aiActSource ? aiActSource.deadlines : []) { + const daysLeft = Math.ceil((new Date(dl.date).getTime() - now) / DAY_MS); + if (daysLeft > 0 && daysLeft <= 180 && (!nearestDeadline || daysLeft < nearestDeadline.daysLeft)) { + nearestDeadline = { ...dl, daysLeft }; + } +} +if (nearestDeadline) { + suggestions.push(`/architect:classify — EU AI Act-klassifisering (${nearestDeadline.daysLeft}d til ${nearestDeadline.label})`); } const sessionList = recentSessions.join(', '); diff --git a/scripts/kb-update/data/ai-act-deadlines.json b/scripts/kb-update/data/ai-act-deadlines.json new file mode 100644 index 0000000..4dd0bb8 --- /dev/null +++ b/scripts/kb-update/data/ai-act-deadlines.json @@ -0,0 +1,36 @@ +{ + "_comment": "Single source of truth for EU AI Act deadlines. Consumers: CLAUDE.md «Viktige frister»-tabellen, agents/ai-act-assessor.md frist-malen, hooks/scripts/session-start-context.mjs, hooks/scripts/stop-assessment-reminder.mjs. Sync enforced by tests/kb-update/test-ai-act-deadlines-sync.test.mjs — update THIS file first, then the markdown consumers, and keep the suite green.", + "verified": "2026-07-15", + "sources": [ + "https://www.consilium.europa.eu/en/press/press-releases/2026/06/29/artificial-intelligence-council-gives-final-green-light-to-simplify-and-streamline-rules/", + "https://www.europarl.europa.eu/legislative-train/package-digital-package/file-digital-omnibus-on-ai", + "https://eur-lex.europa.eu/legal-content/EN/ALL/?uri=CELEX:32024R1689", + "https://nkom.no/ki/regulering/hvem-handhever" + ], + "omnibus": { + "status": "vedtatt_avventer_oj", + "ep_plenary": "2026-06-16", + "council_final": "2026-06-29", + "oj_published": null, + "oj_expected_by": "2026-07-30", + "note": "Digital Omnibus er formelt vedtatt (Europaparlamentet 16. juni, Rådet 29. juni 2026) og trer i kraft tredje dag etter publisering i Official Journal — senest 2026-07-30 for anvendelse før 2026-08-02. Per verified-datoen er den IKKE OJ-publisert (EUR-Lex 32024R1689 viser ingen 2026-endringsakt). Datoene under følger vedtatt tekst; RX-REG² flipper status til i_kraft med CELEX-referanse ved OJ-publisering." + }, + "deadlines": [ + { "date": "2025-02-02", "label": "Forbudte AI-praksiser (Art. 5)", "status": "Gjeldende" }, + { "date": "2025-08-02", "label": "GPAI-krav + governance/sanksjoner (Art. 99)", "status": "Gjeldende" }, + { "date": "2026-08-02", "label": "Transparens (Art. 50): merking av syntetisk innhold", "status": "Fra 2026-08-02" }, + { "date": "2026-12-02", "label": "Art. 50(2): frist for maskinlesbar merking i eksisterende generative systemer", "status": "Overgang (Omnibus)" }, + { "date": "2027-12-02", "label": "Annex III høyrisiko (frittstående) — utsatt fra 2026-08-02", "status": "Vedtatt (Omnibus, avventer OJ)" }, + { "date": "2028-08-02", "label": "Annex I høyrisiko (innebygd i regulerte produkter)", "status": "Vedtatt (Omnibus, avventer OJ)" } + ], + "pending_oj": [ + "Nudifiserings-/CSAM-forbudet (nytt Art. 5-forbud i Omnibus; tekniske sikringstiltak innen 2026-12-02): artikkelplassering + anvendelsesdato verifiseres mot OJ-tekst i RX-REG²", + "Art. 111-legacy-radene verifiseres mot OJ-tekst i RX-REG²", + "Ved OJ-publisering: omnibus.status → i_kraft, oj_published + CELEX-referanse inn, «avventer OJ»-statusene flippes (RX-REG²)" + ], + "authorities": { + "coordinating": "Nkom — koordinerende markedstilsynsmyndighet og nasjonalt kontaktpunkt for AI-forordningen i Norge", + "privacy": "Datatilsynet — personverndimensjonen", + "sector": "Sektortilsyn kan utpekes i tillegg" + } +} diff --git a/scripts/kb-update/lib/ai-act-deadlines.mjs b/scripts/kb-update/lib/ai-act-deadlines.mjs new file mode 100644 index 0000000..f60b005 --- /dev/null +++ b/scripts/kb-update/lib/ai-act-deadlines.mjs @@ -0,0 +1,18 @@ +// ai-act-deadlines.mjs +// Accessor for the machine-readable EU AI Act deadline source (RX-REG). +// The JSON is resolved relative to THIS module, so it works both in dev mode +// (cwd = plugin root) and installed mode (any cwd), without CLAUDE_PLUGIN_ROOT. + +import { readFileSync } from 'node:fs'; + +export const DATA_URL = new URL('../data/ai-act-deadlines.json', import.meta.url); + +// Returns the parsed source, or null when unreadable — hook consumers are +// advisory-never-blocking and silently skip the deadline signal on null. +export function loadAiActDeadlines() { + try { + return JSON.parse(readFileSync(DATA_URL, 'utf8')); + } catch { + return null; + } +} diff --git a/tests/kb-update/test-ai-act-deadlines-sync.test.mjs b/tests/kb-update/test-ai-act-deadlines-sync.test.mjs new file mode 100644 index 0000000..a878795 --- /dev/null +++ b/tests/kb-update/test-ai-act-deadlines-sync.test.mjs @@ -0,0 +1,109 @@ +// 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'); + }); +}