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
This commit is contained in:
Kjell Tore Guttormsen 2026-07-15 06:13:36 +02:00
commit a8ae1fd276
8 changed files with 195 additions and 27 deletions

4
.gitignore vendored
View file

@ -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

View file

@ -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)

View file

@ -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

View file

@ -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 };

View file

@ -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(', ');

View file

@ -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"
}
}

View file

@ -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;
}
}

View file

@ -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');
});
}