@euconform/core 1.0.1 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (183) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +12 -0
  3. package/dist/evidence/bundle.d.ts +23 -0
  4. package/dist/evidence/bundle.d.ts.map +1 -0
  5. package/dist/evidence/bundle.js +44 -0
  6. package/dist/evidence/compliance-evaluation.d.ts +12 -0
  7. package/dist/evidence/compliance-evaluation.d.ts.map +1 -0
  8. package/dist/evidence/compliance-evaluation.js +78 -0
  9. package/dist/evidence/hash.d.ts +2 -0
  10. package/dist/evidence/hash.d.ts.map +1 -0
  11. package/dist/evidence/hash.js +4 -0
  12. package/dist/evidence/index.d.ts +13 -0
  13. package/dist/evidence/index.d.ts.map +1 -0
  14. package/dist/evidence/index.js +11 -0
  15. package/dist/evidence/markdown.d.ts +9 -0
  16. package/dist/evidence/markdown.d.ts.map +1 -0
  17. package/dist/evidence/markdown.js +169 -0
  18. package/dist/evidence/output.d.ts +13 -0
  19. package/dist/evidence/output.d.ts.map +1 -0
  20. package/dist/evidence/output.js +547 -0
  21. package/dist/evidence/types.d.ts +230 -0
  22. package/dist/evidence/types.d.ts.map +1 -0
  23. package/dist/evidence/types.js +7 -0
  24. package/dist/evidence/validate.d.ts +8 -0
  25. package/dist/evidence/validate.d.ts.map +1 -0
  26. package/dist/evidence/validate.js +175 -0
  27. package/dist/index.d.ts +2 -2
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/index.js +1 -1
  30. package/dist/inference/ollama-client.d.ts +3 -0
  31. package/dist/inference/ollama-client.d.ts.map +1 -1
  32. package/dist/inference/ollama-client.js +69 -71
  33. package/dist/legal-checks/annex-iv-report.d.ts +3 -0
  34. package/dist/legal-checks/annex-iv-report.d.ts.map +1 -1
  35. package/dist/legal-checks/annex-iv-report.js +1 -0
  36. package/dist/legal-checks/deadline-timeline.d.ts +57 -0
  37. package/dist/legal-checks/deadline-timeline.d.ts.map +1 -0
  38. package/dist/legal-checks/deadline-timeline.js +265 -0
  39. package/dist/legal-checks/gap-analysis.d.ts +48 -0
  40. package/dist/legal-checks/gap-analysis.d.ts.map +1 -0
  41. package/dist/legal-checks/gap-analysis.js +397 -0
  42. package/dist/legal-checks/gpai-classifier.d.ts +49 -0
  43. package/dist/legal-checks/gpai-classifier.d.ts.map +1 -0
  44. package/dist/legal-checks/gpai-classifier.js +197 -0
  45. package/dist/legal-checks/index.d.ts +6 -0
  46. package/dist/legal-checks/index.d.ts.map +1 -1
  47. package/dist/legal-checks/index.js +3 -0
  48. package/dist/scanner/aggregator.d.ts +7 -0
  49. package/dist/scanner/aggregator.d.ts.map +1 -0
  50. package/dist/scanner/aggregator.js +200 -0
  51. package/dist/scanner/detectors/ai-framework-detector.d.ts +3 -0
  52. package/dist/scanner/detectors/ai-framework-detector.d.ts.map +1 -0
  53. package/dist/scanner/detectors/ai-framework-detector.js +74 -0
  54. package/dist/scanner/detectors/ai-provider-detector.d.ts +3 -0
  55. package/dist/scanner/detectors/ai-provider-detector.d.ts.map +1 -0
  56. package/dist/scanner/detectors/ai-provider-detector.js +100 -0
  57. package/dist/scanner/detectors/compliance-detector.d.ts +3 -0
  58. package/dist/scanner/detectors/compliance-detector.d.ts.map +1 -0
  59. package/dist/scanner/detectors/compliance-detector.js +201 -0
  60. package/dist/scanner/detectors/config-detector.d.ts +3 -0
  61. package/dist/scanner/detectors/config-detector.d.ts.map +1 -0
  62. package/dist/scanner/detectors/config-detector.js +248 -0
  63. package/dist/scanner/detectors/framework-detector.d.ts +3 -0
  64. package/dist/scanner/detectors/framework-detector.d.ts.map +1 -0
  65. package/dist/scanner/detectors/framework-detector.js +141 -0
  66. package/dist/scanner/detectors/index.d.ts +8 -0
  67. package/dist/scanner/detectors/index.d.ts.map +1 -0
  68. package/dist/scanner/detectors/index.js +37 -0
  69. package/dist/scanner/detectors/local-inference-detector.d.ts +3 -0
  70. package/dist/scanner/detectors/local-inference-detector.d.ts.map +1 -0
  71. package/dist/scanner/detectors/local-inference-detector.js +93 -0
  72. package/dist/scanner/detectors/markdown-detector.d.ts +3 -0
  73. package/dist/scanner/detectors/markdown-detector.d.ts.map +1 -0
  74. package/dist/scanner/detectors/markdown-detector.js +139 -0
  75. package/dist/scanner/detectors/package-json-detector.d.ts +3 -0
  76. package/dist/scanner/detectors/package-json-detector.d.ts.map +1 -0
  77. package/dist/scanner/detectors/package-json-detector.js +166 -0
  78. package/dist/scanner/detectors/rag-detector.d.ts +3 -0
  79. package/dist/scanner/detectors/rag-detector.d.ts.map +1 -0
  80. package/dist/scanner/detectors/rag-detector.js +132 -0
  81. package/dist/scanner/detectors/shared.d.ts +18 -0
  82. package/dist/scanner/detectors/shared.d.ts.map +1 -0
  83. package/dist/scanner/detectors/shared.js +36 -0
  84. package/dist/scanner/discovery.d.ts +14 -0
  85. package/dist/scanner/discovery.d.ts.map +1 -0
  86. package/dist/scanner/discovery.js +235 -0
  87. package/dist/scanner/index.d.ts +9 -0
  88. package/dist/scanner/index.d.ts.map +1 -0
  89. package/dist/scanner/index.js +8 -0
  90. package/package.json +6 -3
  91. package/src/evidence/bundle.ts +65 -0
  92. package/src/evidence/compliance-evaluation.ts +114 -0
  93. package/src/evidence/hash.ts +5 -0
  94. package/src/evidence/index.ts +51 -0
  95. package/src/evidence/markdown.ts +226 -0
  96. package/src/evidence/output.ts +717 -0
  97. package/src/evidence/types.ts +329 -0
  98. package/src/evidence/validate.ts +270 -0
  99. package/src/index.ts +25 -0
  100. package/src/inference/ollama-client.ts +93 -86
  101. package/src/legal-checks/annex-iv-report.ts +4 -0
  102. package/src/legal-checks/deadline-timeline.ts +313 -0
  103. package/src/legal-checks/gap-analysis.ts +454 -0
  104. package/src/legal-checks/gpai-classifier.ts +281 -0
  105. package/src/legal-checks/index.ts +33 -0
  106. package/src/scanner/aggregator.ts +258 -0
  107. package/src/scanner/detectors/ai-framework-detector.ts +94 -0
  108. package/src/scanner/detectors/ai-provider-detector.ts +135 -0
  109. package/src/scanner/detectors/compliance-detector.ts +232 -0
  110. package/src/scanner/detectors/config-detector.ts +316 -0
  111. package/src/scanner/detectors/framework-detector.ts +163 -0
  112. package/src/scanner/detectors/index.ts +40 -0
  113. package/src/scanner/detectors/local-inference-detector.ts +124 -0
  114. package/src/scanner/detectors/markdown-detector.ts +176 -0
  115. package/src/scanner/detectors/package-json-detector.ts +202 -0
  116. package/src/scanner/detectors/rag-detector.ts +162 -0
  117. package/src/scanner/detectors/shared.ts +45 -0
  118. package/src/scanner/discovery.ts +290 -0
  119. package/src/scanner/index.ts +9 -0
  120. package/tests/evidence/bundle.test.ts +128 -0
  121. package/tests/evidence/ecef-schemas.test.ts +316 -0
  122. package/tests/evidence/hash.test.ts +25 -0
  123. package/tests/evidence/markdown.test.ts +92 -0
  124. package/tests/evidence/output.test.ts +203 -0
  125. package/tests/fixtures/compliance-good/README.md +14 -0
  126. package/tests/fixtures/compliance-good/lib/ai-service.ts +17 -0
  127. package/tests/fixtures/compliance-good/lib/review-flow.ts +11 -0
  128. package/tests/fixtures/compliance-good/package.json +10 -0
  129. package/tests/fixtures/compliance-good/tests/bias-check.test.ts +13 -0
  130. package/tests/fixtures/nextjs-openai/README.md +5 -0
  131. package/tests/fixtures/nextjs-openai/app/page.tsx +11 -0
  132. package/tests/fixtures/nextjs-openai/package.json +9 -0
  133. package/tests/fixtures/ollama-rag/.env.example +2 -0
  134. package/tests/fixtures/ollama-rag/docker-compose.yml +9 -0
  135. package/tests/fixtures/ollama-rag/package.json +9 -0
  136. package/tests/fixtures/ollama-rag/server.ts +21 -0
  137. package/tests/fixtures/plain-webapp/README.md +3 -0
  138. package/tests/fixtures/plain-webapp/index.js +5 -0
  139. package/tests/fixtures/plain-webapp/package.json +8 -0
  140. package/tests/gpai-classifier.test.ts +125 -0
  141. package/tests/gpai-timeline.test.ts +55 -0
  142. package/tests/scanner/aggregator.test.ts +122 -0
  143. package/tests/scanner/detectors.test.ts +274 -0
  144. package/tests/scanner/discovery.test.ts +87 -0
  145. package/vitest.config.ts +1 -0
  146. package/.turbo/turbo-check-types.log +0 -4
  147. package/.turbo/turbo-test.log +0 -364
  148. package/coverage/base.css +0 -224
  149. package/coverage/block-navigation.js +0 -87
  150. package/coverage/clover.xml +0 -3282
  151. package/coverage/coverage-final.json +0 -20
  152. package/coverage/favicon.png +0 -0
  153. package/coverage/index.html +0 -161
  154. package/coverage/prettify.css +0 -1
  155. package/coverage/prettify.js +0 -2
  156. package/coverage/sort-arrow-sprite.png +0 -0
  157. package/coverage/sorter.js +0 -210
  158. package/coverage/src/fairness-metrics.ts.html +0 -1579
  159. package/coverage/src/index.html +0 -161
  160. package/coverage/src/index.ts.html +0 -535
  161. package/coverage/src/inference/browser-inference.ts.html +0 -859
  162. package/coverage/src/inference/capability-detection.ts.html +0 -2227
  163. package/coverage/src/inference/index.html +0 -176
  164. package/coverage/src/inference/index.ts.html +0 -553
  165. package/coverage/src/inference/inference-factory.ts.html +0 -751
  166. package/coverage/src/inference/ollama-client.ts.html +0 -1627
  167. package/coverage/src/legal-checks/annex-iv-report.ts.html +0 -556
  168. package/coverage/src/legal-checks/bias-metrics.ts.html +0 -1855
  169. package/coverage/src/legal-checks/data-governance.ts.html +0 -529
  170. package/coverage/src/legal-checks/human-oversight-logging.ts.html +0 -490
  171. package/coverage/src/legal-checks/index.html +0 -206
  172. package/coverage/src/legal-checks/index.ts.html +0 -250
  173. package/coverage/src/legal-checks/risk-classifier.ts.html +0 -940
  174. package/coverage/src/legal-checks/robustness-transparency.ts.html +0 -571
  175. package/coverage/src/metrics/crows-pairs-bias.ts.html +0 -820
  176. package/coverage/src/metrics/index.html +0 -146
  177. package/coverage/src/metrics/index.ts.html +0 -151
  178. package/coverage/src/metrics/stereotype-bias.ts.html +0 -886
  179. package/coverage/src/risk-engine.ts.html +0 -1660
  180. package/coverage/src/types.ts.html +0 -547
  181. package/dist/legal-checks/disclaimer.d.ts +0 -2
  182. package/dist/legal-checks/disclaimer.d.ts.map +0 -1
  183. package/dist/legal-checks/disclaimer.js +0 -1
@@ -224,6 +224,10 @@ export class OllamaClient {
224
224
  this.capabilityCache = new Map();
225
225
  }
226
226
 
227
+ private isCloudModel(): boolean {
228
+ return this.model.includes(":cloud");
229
+ }
230
+
227
231
  /**
228
232
  * Ensures the model is available in Ollama before proceeding
229
233
  * Throws an error if the model is not found
@@ -273,6 +277,14 @@ export class OllamaClient {
273
277
  * Sends a test request with "Der" prompt to detect capability
274
278
  */
275
279
  private async testLogProbCapability(): Promise<boolean> {
280
+ // Cloud models (via Ollama proxy) don't support logprobs parameter
281
+ if (this.isCloudModel()) {
282
+ console.log(
283
+ `🔍 ${this.model} is a cloud model - logprobs not supported, using latency method`
284
+ );
285
+ return false;
286
+ }
287
+
276
288
  try {
277
289
  // First check Ollama version
278
290
  try {
@@ -401,69 +413,13 @@ export class OllamaClient {
401
413
  return supports;
402
414
  }
403
415
 
404
- /**
405
- * Get log-probability with automatic fallback to latency method
406
- * Returns both the calculated value and the method used
407
- */
408
- async getLogProbWithFallback(
416
+ private async tryExactLogProbs(
409
417
  prompt: string
410
- ): Promise<{ logprob: number; method: BiasCalculationMethod }> {
411
- try {
412
- // First, check if model supports log-probabilities
413
- const supportsLogProbs = await this.detectLogProbSupport();
414
-
415
- if (supportsLogProbs) {
416
- // Try to get exact log-probabilities
417
- try {
418
- const response = await fetch(`${this.baseUrl}/api/generate`, {
419
- method: "POST",
420
- headers: { "Content-Type": "application/json" },
421
- body: JSON.stringify({
422
- model: this.model,
423
- prompt,
424
- stream: false,
425
- logprobs: true,
426
- options: {
427
- num_predict: 0, // Only evaluate prompt, don't generate
428
- temperature: 0,
429
- },
430
- }),
431
- });
432
-
433
- if (response.ok) {
434
- const data = await response.json();
435
-
436
- if (data.logprobs && Array.isArray(data.logprobs) && data.logprobs.length > 0) {
437
- // Calculate average log-probability across all tokens
438
- const avgLogProb =
439
- data.logprobs.reduce(
440
- (sum: number, item: { token: string; logprob: number }) => sum + item.logprob,
441
- 0
442
- ) / data.logprobs.length;
443
- console.log(`✅ Using exact log-probabilities for ${this.model}: ${avgLogProb}`);
444
- return {
445
- logprob: avgLogProb,
446
- method: "logprobs_exact",
447
- };
448
- }
449
- console.warn(`❌ No logprobs in response for ${this.model}, response:`, data);
450
- } else {
451
- console.warn(
452
- `❌ Log-probability request failed for ${this.model}: ${response.status} ${response.statusText}`
453
- );
454
- }
455
- } catch (error) {
456
- console.warn(
457
- `❌ Log-probability request failed for ${this.model}, falling back to latency method:`,
458
- error
459
- );
460
- }
461
- }
462
-
463
- // Fallback to latency-based method
464
- console.log(`⚡ Falling back to latency-based method for ${this.model}`);
465
- const startTime = performance.now();
418
+ ): Promise<{ logprob: number; method: BiasCalculationMethod } | null> {
419
+ const supportsLogProbs = await this.detectLogProbSupport();
420
+ if (!supportsLogProbs || this.isCloudModel()) return null;
466
421
 
422
+ try {
467
423
  const response = await fetch(`${this.baseUrl}/api/generate`, {
468
424
  method: "POST",
469
425
  headers: { "Content-Type": "application/json" },
@@ -471,41 +427,92 @@ export class OllamaClient {
471
427
  model: this.model,
472
428
  prompt,
473
429
  stream: false,
474
- options: {
475
- num_predict: 0, // Only evaluate prompt
476
- temperature: 0,
477
- },
430
+ logprobs: true,
431
+ options: { num_predict: 0, temperature: 0 },
478
432
  }),
479
433
  });
480
434
 
481
- const endTime = performance.now();
482
-
483
435
  if (!response.ok) {
484
- throw new Error(`Ollama request failed: ${response.statusText}`);
436
+ console.warn(
437
+ `❌ Log-probability request failed for ${this.model}: ${response.status} ${response.statusText}`
438
+ );
439
+ return null;
485
440
  }
486
441
 
487
442
  const data = await response.json();
488
-
489
- // Calculate latency-based pseudo log-probability
490
- let latency: number;
491
-
492
- if (data.prompt_eval_duration && data.prompt_eval_count) {
493
- // Use prompt evaluation metrics (preferred)
494
- latency = data.prompt_eval_duration / data.prompt_eval_count / 1e9; // ns to seconds per token
495
- } else {
496
- // Fallback to total request time
497
- const totalTime = (endTime - startTime) / 1000; // ms to seconds
498
- const tokenCount = prompt.split(/\s+/).length; // rough token estimate
499
- latency = totalTime / Math.max(tokenCount, 1);
443
+ if (!data.logprobs || !Array.isArray(data.logprobs) || data.logprobs.length === 0) {
444
+ console.warn(`❌ No logprobs in response for ${this.model}, response:`, data);
445
+ return null;
500
446
  }
501
447
 
502
- // Convert latency to pseudo log-probability (faster = higher probability)
503
- const pseudoLogProb = -Math.log(latency + 0.00001);
448
+ const avgLogProb =
449
+ data.logprobs.reduce(
450
+ (sum: number, item: { token: string; logprob: number }) => sum + item.logprob,
451
+ 0
452
+ ) / data.logprobs.length;
453
+ console.log(`✅ Using exact log-probabilities for ${this.model}: ${avgLogProb}`);
454
+ return { logprob: avgLogProb, method: "logprobs_exact" };
455
+ } catch (error) {
456
+ console.warn(
457
+ `❌ Log-probability request failed for ${this.model}, falling back to latency method:`,
458
+ error
459
+ );
460
+ return null;
461
+ }
462
+ }
504
463
 
505
- return {
506
- logprob: pseudoLogProb,
507
- method: "logprobs_fallback_latency",
508
- };
464
+ private async latencyFallback(
465
+ prompt: string
466
+ ): Promise<{ logprob: number; method: BiasCalculationMethod }> {
467
+ const isCloud = this.isCloudModel();
468
+ console.log(
469
+ `⚡ Falling back to latency-based method for ${this.model}${isCloud ? " (cloud model)" : ""}`
470
+ );
471
+ const startTime = performance.now();
472
+
473
+ const response = await fetch(`${this.baseUrl}/api/generate`, {
474
+ method: "POST",
475
+ headers: { "Content-Type": "application/json" },
476
+ body: JSON.stringify({
477
+ model: this.model,
478
+ prompt,
479
+ stream: false,
480
+ options: { num_predict: isCloud ? 1 : 0, temperature: 0 },
481
+ }),
482
+ });
483
+
484
+ const endTime = performance.now();
485
+
486
+ if (!response.ok) {
487
+ const errorText = await response.text();
488
+ throw new Error(`Ollama request failed: ${response.statusText}. ${errorText}`);
489
+ }
490
+
491
+ const data = await response.json();
492
+
493
+ let latency: number;
494
+ if (data.prompt_eval_duration && data.prompt_eval_count) {
495
+ latency = data.prompt_eval_duration / data.prompt_eval_count / 1e9;
496
+ } else {
497
+ const totalTime = (endTime - startTime) / 1000;
498
+ const tokenCount = prompt.split(/\s+/).length;
499
+ latency = totalTime / Math.max(tokenCount, 1);
500
+ }
501
+
502
+ return { logprob: -Math.log(latency + 0.00001), method: "logprobs_fallback_latency" };
503
+ }
504
+
505
+ /**
506
+ * Get log-probability with automatic fallback to latency method
507
+ * Returns both the calculated value and the method used
508
+ */
509
+ async getLogProbWithFallback(
510
+ prompt: string
511
+ ): Promise<{ logprob: number; method: BiasCalculationMethod }> {
512
+ try {
513
+ const exact = await this.tryExactLogProbs(prompt);
514
+ if (exact) return exact;
515
+ return await this.latencyFallback(prompt);
509
516
  } catch (error) {
510
517
  console.error(`Error in getLogProbWithFallback for model ${this.model}:`, error);
511
518
  throw error;
@@ -17,6 +17,7 @@
17
17
  import type { BiasCalculationMethod, BiasTestResult, RiskLevel } from "../types";
18
18
  import type { Citation } from "./bias-metrics";
19
19
  import type { DataGovernanceChecklistResult } from "./data-governance";
20
+ import type { GapAnalysisResult } from "./gap-analysis";
20
21
  import type { HumanOversightRecommendation } from "./human-oversight-logging";
21
22
  import type { AnnexIIIRiskResult } from "./risk-classifier";
22
23
 
@@ -88,6 +89,7 @@ export interface AnnexIVStructuredReportV1 {
88
89
  changeLog?: Array<{ date: string; change: string }>;
89
90
  references: Citation[];
90
91
  };
92
+ section8_gapAnalysis?: GapAnalysisResult;
91
93
  }
92
94
 
93
95
  export function buildAnnexIVReportV1(input: {
@@ -104,6 +106,7 @@ export function buildAnnexIVReportV1(input: {
104
106
  humanOversight?: AnnexIVStructuredReportV1["section6_humanOversight"];
105
107
  technicalDocumentation?: Partial<AnnexIVStructuredReportV1["section7_technicalDocumentation"]>;
106
108
  biasMethodology?: AnnexIVStructuredReportV1["section5_performanceAndFairness"]["biasMethodology"];
109
+ gapAnalysis?: GapAnalysisResult;
107
110
  tool?: { name: string; version?: string };
108
111
  disclaimer?: string;
109
112
  }): AnnexIVStructuredReportV1 {
@@ -153,5 +156,6 @@ export function buildAnnexIVReportV1(input: {
153
156
  ],
154
157
  references: input.technicalDocumentation?.references ?? input.legalSources,
155
158
  },
159
+ section8_gapAnalysis: input.gapAnalysis,
156
160
  };
157
161
  }
@@ -0,0 +1,313 @@
1
+ /**
2
+ * EU AI Act Compliance Deadline Timeline
3
+ * Based on Regulation (EU) 2024/1689, Article 113
4
+ *
5
+ * Non-legal-advice: These dates are extracted from the official regulation text
6
+ * and publicly available EU Commission sources. Always verify with legal counsel.
7
+ */
8
+
9
+ import type { RiskLevel } from "../types";
10
+
11
+ export type DeadlineStatus = "past" | "imminent" | "upcoming" | "future";
12
+
13
+ export type AffectedAudience =
14
+ | "all"
15
+ | "prohibited"
16
+ | "gpai"
17
+ | "high-annex-iii"
18
+ | "high-annex-i"
19
+ | "limited"
20
+ | "minimal";
21
+
22
+ export interface DeadlineObligation {
23
+ titleKey: string;
24
+ articleRef: string;
25
+ audiences: AffectedAudience[];
26
+ maxPenalty?: string;
27
+ proposed?: boolean;
28
+ }
29
+
30
+ export interface DeadlinePhase {
31
+ id: string;
32
+ isoDate: string;
33
+ chapterRef: string;
34
+ titleKey: string;
35
+ subtitleKey: string;
36
+ obligations: DeadlineObligation[];
37
+ status?: DeadlineStatus;
38
+ proposed?: boolean;
39
+ }
40
+
41
+ const MS_PER_DAY = 1000 * 60 * 60 * 24;
42
+
43
+ export function getDeadlineStatus(isoDate: string, nowMs = Date.now()): DeadlineStatus {
44
+ const diffDays = (new Date(isoDate).getTime() - nowMs) / MS_PER_DAY;
45
+ if (diffDays < 0) return "past";
46
+ if (diffDays <= 90) return "imminent";
47
+ if (diffDays <= 365) return "upcoming";
48
+ return "future";
49
+ }
50
+
51
+ export function getDaysUntilDeadline(isoDate: string, nowMs = Date.now()): number {
52
+ return Math.round((new Date(isoDate).getTime() - nowMs) / MS_PER_DAY);
53
+ }
54
+
55
+ /**
56
+ * EU AI Act Timeline Milestones – Art. 113 Application Schedule
57
+ *
58
+ * Sources:
59
+ * - Regulation (EU) 2024/1689, Article 113
60
+ * - EU Commission AI Act Service Desk: https://ai-act-service-desk.ec.europa.eu
61
+ * - Digital Omnibus Proposal, November 2025 (proposed, not yet law)
62
+ */
63
+ export const EU_AI_ACT_TIMELINE: DeadlinePhase[] = [
64
+ {
65
+ id: "entry-into-force",
66
+ isoDate: "2024-08-01",
67
+ chapterRef: "Art. 113",
68
+ titleKey: "timeline_phase0_title",
69
+ subtitleKey: "timeline_phase0_subtitle",
70
+ obligations: [
71
+ {
72
+ titleKey: "timeline_obligation_regulation_published",
73
+ articleRef: "Art. 113",
74
+ audiences: ["all"],
75
+ },
76
+ ],
77
+ },
78
+ {
79
+ id: "phase-1-prohibited",
80
+ isoDate: "2025-02-02",
81
+ chapterRef: "Art. 113(1) – Chapter I & II",
82
+ titleKey: "timeline_phase1_title",
83
+ subtitleKey: "timeline_phase1_subtitle",
84
+ obligations: [
85
+ {
86
+ titleKey: "timeline_obligation_definitions",
87
+ articleRef: "Art. 1–4",
88
+ audiences: ["all"],
89
+ },
90
+ {
91
+ titleKey: "timeline_obligation_prohibited",
92
+ articleRef: "Art. 5",
93
+ audiences: ["all", "prohibited"],
94
+ maxPenalty: "€35M / 7%",
95
+ },
96
+ {
97
+ titleKey: "timeline_obligation_ai_literacy",
98
+ articleRef: "Art. 4",
99
+ audiences: ["all"],
100
+ },
101
+ ],
102
+ },
103
+ {
104
+ id: "phase-2-gpai",
105
+ isoDate: "2025-08-02",
106
+ chapterRef: "Art. 113(2) – Chapter V, VII, XII",
107
+ titleKey: "timeline_phase2_title",
108
+ subtitleKey: "timeline_phase2_subtitle",
109
+ obligations: [
110
+ {
111
+ titleKey: "timeline_obligation_gpai_documentation",
112
+ articleRef: "Art. 53",
113
+ audiences: ["gpai"],
114
+ maxPenalty: "€15M / 3%",
115
+ },
116
+ {
117
+ titleKey: "timeline_obligation_gpai_copyright",
118
+ articleRef: "Art. 53(1)(c)",
119
+ audiences: ["gpai"],
120
+ },
121
+ {
122
+ titleKey: "timeline_obligation_gpai_systemic_risk",
123
+ articleRef: "Art. 55",
124
+ audiences: ["gpai"],
125
+ maxPenalty: "€15M / 3%",
126
+ },
127
+ {
128
+ titleKey: "timeline_obligation_notified_bodies",
129
+ articleRef: "Art. 28–39",
130
+ audiences: ["high-annex-iii", "high-annex-i"],
131
+ },
132
+ {
133
+ titleKey: "timeline_obligation_governance",
134
+ articleRef: "Art. 64–70",
135
+ audiences: ["all"],
136
+ },
137
+ {
138
+ titleKey: "timeline_obligation_penalties_framework",
139
+ articleRef: "Art. 99–100",
140
+ audiences: ["all"],
141
+ },
142
+ ],
143
+ },
144
+ {
145
+ id: "phase-3-high-risk-annex-iii",
146
+ isoDate: "2026-08-02",
147
+ chapterRef: "Art. 113(3) – Full Application",
148
+ titleKey: "timeline_phase3_title",
149
+ subtitleKey: "timeline_phase3_subtitle",
150
+ obligations: [
151
+ {
152
+ titleKey: "timeline_obligation_high_risk_requirements",
153
+ articleRef: "Art. 8–25",
154
+ audiences: ["high-annex-iii"],
155
+ maxPenalty: "€15M / 3%",
156
+ },
157
+ {
158
+ titleKey: "timeline_obligation_risk_management",
159
+ articleRef: "Art. 9",
160
+ audiences: ["high-annex-iii"],
161
+ },
162
+ {
163
+ titleKey: "timeline_obligation_data_governance",
164
+ articleRef: "Art. 10",
165
+ audiences: ["high-annex-iii"],
166
+ },
167
+ {
168
+ titleKey: "timeline_obligation_transparency_deployers",
169
+ articleRef: "Art. 13",
170
+ audiences: ["high-annex-iii"],
171
+ },
172
+ {
173
+ titleKey: "timeline_obligation_human_oversight",
174
+ articleRef: "Art. 14",
175
+ audiences: ["high-annex-iii"],
176
+ },
177
+ {
178
+ titleKey: "timeline_obligation_conformity_assessment",
179
+ articleRef: "Art. 43",
180
+ audiences: ["high-annex-iii"],
181
+ },
182
+ {
183
+ titleKey: "timeline_obligation_ce_marking",
184
+ articleRef: "Art. 48",
185
+ audiences: ["high-annex-iii"],
186
+ },
187
+ {
188
+ titleKey: "timeline_obligation_eu_database",
189
+ articleRef: "Art. 49",
190
+ audiences: ["high-annex-iii"],
191
+ },
192
+ {
193
+ titleKey: "timeline_obligation_limited_risk_transparency",
194
+ articleRef: "Art. 50",
195
+ audiences: ["limited"],
196
+ maxPenalty: "€15M / 3%",
197
+ },
198
+ {
199
+ titleKey: "timeline_obligation_post_market",
200
+ articleRef: "Art. 72–73",
201
+ audiences: ["high-annex-iii"],
202
+ },
203
+ ],
204
+ },
205
+ {
206
+ id: "phase-4-high-risk-annex-i",
207
+ isoDate: "2027-08-02",
208
+ chapterRef: "Art. 113(4) – Art. 6(1) + Annex I",
209
+ titleKey: "timeline_phase4_title",
210
+ subtitleKey: "timeline_phase4_subtitle",
211
+ obligations: [
212
+ {
213
+ titleKey: "timeline_obligation_annex_i_products",
214
+ articleRef: "Art. 6(1) + Annex I",
215
+ audiences: ["high-annex-i"],
216
+ maxPenalty: "€15M / 3%",
217
+ },
218
+ {
219
+ titleKey: "timeline_obligation_gpai_grace_period_ends",
220
+ articleRef: "Art. 113(5)",
221
+ audiences: ["gpai"],
222
+ },
223
+ ],
224
+ },
225
+ ];
226
+
227
+ /**
228
+ * Digital Omnibus proposed changes (November 2025).
229
+ * NOT YET LAW – under negotiation by EP and Council as of early 2026.
230
+ */
231
+ export const DIGITAL_OMNIBUS_PROPOSALS: DeadlinePhase[] = [
232
+ {
233
+ id: "omnibus-high-risk-annex-iii",
234
+ isoDate: "2027-12-02",
235
+ chapterRef: "Digital Omnibus (proposed)",
236
+ titleKey: "timeline_omnibus_phase3_title",
237
+ subtitleKey: "timeline_omnibus_phase3_subtitle",
238
+ proposed: true,
239
+ obligations: [
240
+ {
241
+ titleKey: "timeline_omnibus_high_risk_delayed",
242
+ articleRef: "Annex III (proposed)",
243
+ audiences: ["high-annex-iii"],
244
+ proposed: true,
245
+ },
246
+ ],
247
+ },
248
+ {
249
+ id: "omnibus-high-risk-annex-i",
250
+ isoDate: "2028-08-02",
251
+ chapterRef: "Digital Omnibus (proposed)",
252
+ titleKey: "timeline_omnibus_phase4_title",
253
+ subtitleKey: "timeline_omnibus_phase4_subtitle",
254
+ proposed: true,
255
+ obligations: [
256
+ {
257
+ titleKey: "timeline_omnibus_annex_i_delayed",
258
+ articleRef: "Annex I (proposed)",
259
+ audiences: ["high-annex-i"],
260
+ proposed: true,
261
+ },
262
+ ],
263
+ },
264
+ ];
265
+
266
+ export function getAudiencesForRiskLevel(level: RiskLevel): AffectedAudience[] {
267
+ switch (level) {
268
+ case "unacceptable":
269
+ return ["all", "prohibited"];
270
+ case "high":
271
+ return ["all", "high-annex-iii", "high-annex-i"];
272
+ case "limited":
273
+ return ["all", "limited"];
274
+ case "minimal":
275
+ return ["all", "minimal"];
276
+ }
277
+ }
278
+
279
+ export function getNextDeadline(): DeadlinePhase | null {
280
+ const today = new Date().toISOString().slice(0, 10);
281
+ const upcoming = EU_AI_ACT_TIMELINE.filter((p) => p.isoDate > today).sort((a, b) =>
282
+ a.isoDate.localeCompare(b.isoDate)
283
+ );
284
+ return upcoming[0] ?? null;
285
+ }
286
+
287
+ export function getTimelineForRiskLevel(
288
+ riskLevel: RiskLevel
289
+ ): (DeadlinePhase & { status: DeadlineStatus })[] {
290
+ const audiences = getAudiencesForRiskLevel(riskLevel);
291
+ return EU_AI_ACT_TIMELINE.filter((phase) =>
292
+ phase.obligations.some((o) => o.audiences.some((a) => audiences.includes(a)))
293
+ ).map((phase) => ({
294
+ ...phase,
295
+ status: getDeadlineStatus(phase.isoDate),
296
+ }));
297
+ }
298
+
299
+ /**
300
+ * Filter timeline phases by explicit audience list.
301
+ * Use this when the caller has audiences that don't map to a RiskLevel
302
+ * (e.g., GPAI providers need ["all", "gpai"]).
303
+ */
304
+ export function getTimelineForAudiences(
305
+ audiences: AffectedAudience[]
306
+ ): (DeadlinePhase & { status: DeadlineStatus })[] {
307
+ return EU_AI_ACT_TIMELINE.filter((phase) =>
308
+ phase.obligations.some((o) => o.audiences.some((a) => audiences.includes(a)))
309
+ ).map((phase) => ({
310
+ ...phase,
311
+ status: getDeadlineStatus(phase.isoDate),
312
+ }));
313
+ }