@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
@@ -0,0 +1,329 @@
1
+ /**
2
+ * EuConform Evidence Engine — Type Definitions
3
+ *
4
+ * The scanner produces evidence, hints, and open questions — not legal verdicts.
5
+ * Classification is done by the wizard from human input, not by the scanner.
6
+ */
7
+
8
+ // ---------------------------------------------------------------------------
9
+ // Signal types
10
+ // ---------------------------------------------------------------------------
11
+
12
+ export type SignalCategory =
13
+ | "framework"
14
+ | "runtime"
15
+ | "ai-provider"
16
+ | "ai-framework"
17
+ | "ai-model"
18
+ | "local-inference"
19
+ | "rag"
20
+ | "training"
21
+ | "compliance-disclosure"
22
+ | "compliance-logging"
23
+ | "compliance-oversight"
24
+ | "compliance-bias"
25
+ | "compliance-data"
26
+ | "compliance-incident"
27
+ | "compliance-reporting";
28
+
29
+ export type ConfidenceLevel = "high" | "medium" | "low";
30
+
31
+ export type ScanScope = "production" | "all";
32
+
33
+ export type FileProvenance =
34
+ | "runtime"
35
+ | "config"
36
+ | "root-docs"
37
+ | "docs"
38
+ | "test"
39
+ | "fixture"
40
+ | "example"
41
+ | "generated"
42
+ | "tooling";
43
+
44
+ export interface SignalEvidence {
45
+ file: string;
46
+ line?: number;
47
+ snippet: string;
48
+ provenance?: FileProvenance;
49
+ }
50
+
51
+ export interface DetectedSignal {
52
+ id: string;
53
+ name: string;
54
+ category: SignalCategory;
55
+ confidence: ConfidenceLevel;
56
+ evidence: SignalEvidence[];
57
+ }
58
+
59
+ // ---------------------------------------------------------------------------
60
+ // Open questions
61
+ // ---------------------------------------------------------------------------
62
+
63
+ export interface OpenQuestion {
64
+ id: string;
65
+ question: string;
66
+ context: string;
67
+ relatedSignalIds: string[];
68
+ suggestedAction: string;
69
+ }
70
+
71
+ // ---------------------------------------------------------------------------
72
+ // Scanner result (raw output of scanRepository)
73
+ // ---------------------------------------------------------------------------
74
+
75
+ export interface ScanMeta {
76
+ scannedAt: string;
77
+ scanDurationMs: number;
78
+ toolVersion: string;
79
+ targetPath: string;
80
+ scanScope: ScanScope;
81
+ filesScanned: number;
82
+ filesSkipped: number;
83
+ }
84
+
85
+ export interface RepoProfile {
86
+ name: string;
87
+ version?: string;
88
+ description?: string;
89
+ license?: string;
90
+ isMonorepo: boolean;
91
+ packageManager?: "npm" | "pnpm" | "yarn" | "bun";
92
+ languages: string[];
93
+ }
94
+
95
+ export type AppType =
96
+ | "web-app"
97
+ | "api-server"
98
+ | "cli-tool"
99
+ | "library"
100
+ | "chatbot"
101
+ | "ml-pipeline"
102
+ | "unknown";
103
+
104
+ export interface ScanResult {
105
+ meta: ScanMeta;
106
+ repo: RepoProfile;
107
+ appTypes: AppType[];
108
+ signals: DetectedSignal[];
109
+ openQuestions: OpenQuestion[];
110
+ signalSummary: {
111
+ total: number;
112
+ byCategory: Partial<Record<SignalCategory, number>>;
113
+ byConfidence: Record<ConfidenceLevel, number>;
114
+ };
115
+ }
116
+
117
+ // ---------------------------------------------------------------------------
118
+ // File discovery
119
+ // ---------------------------------------------------------------------------
120
+
121
+ export interface ScanOptions {
122
+ targetPath: string;
123
+ scope?: ScanScope;
124
+ extensions?: string[];
125
+ excludeGlobs?: string[];
126
+ maxFileSizeBytes?: number;
127
+ maxFiles?: number;
128
+ }
129
+
130
+ export interface ScanFile {
131
+ relativePath: string;
132
+ absolutePath: string;
133
+ extension: string;
134
+ content: string;
135
+ sizeBytes: number;
136
+ provenance: FileProvenance;
137
+ }
138
+
139
+ // ---------------------------------------------------------------------------
140
+ // Detector interface
141
+ // ---------------------------------------------------------------------------
142
+
143
+ export interface DetectorContext {
144
+ file: ScanFile;
145
+ /** Pre-split lines of file.content — avoids redundant .split("\n") in each detector */
146
+ lines: string[];
147
+ allFiles: ScanFile[];
148
+ /** Pre-parsed root package.json, if available */
149
+ rootPackageJson?: Record<string, unknown>;
150
+ }
151
+
152
+ export type SignalDetector = (ctx: DetectorContext) => DetectedSignal[];
153
+
154
+ // ---------------------------------------------------------------------------
155
+ // Scan report (euconform.report.v1)
156
+ // ---------------------------------------------------------------------------
157
+
158
+ export interface ComplianceSignalGroup {
159
+ status: "present" | "partial" | "absent" | "unknown";
160
+ confidence: ConfidenceLevel;
161
+ evidence: SignalEvidence[];
162
+ }
163
+
164
+ export interface AssessmentHint {
165
+ hint: string;
166
+ articleRef?: string;
167
+ confidence: ConfidenceLevel;
168
+ }
169
+
170
+ export interface ScanGap {
171
+ id: string;
172
+ title: string;
173
+ description: string;
174
+ priority: "critical" | "high" | "medium" | "low";
175
+ status: "missing" | "partial";
176
+ basis: "scanner-rule";
177
+ evidence: SignalEvidence[];
178
+ }
179
+
180
+ export interface ScanReport {
181
+ schemaVersion: "euconform.report.v1";
182
+ generatedAt: string;
183
+ tool: { name: string; version: string };
184
+ target: {
185
+ rootPath: string;
186
+ name: string;
187
+ repoType: string;
188
+ detectedStack: string[];
189
+ };
190
+ aiFootprint: {
191
+ usesAI: boolean;
192
+ inferenceModes: string[];
193
+ providerHints: string[];
194
+ ragHints: string[];
195
+ };
196
+ complianceSignals: {
197
+ disclosure: ComplianceSignalGroup;
198
+ biasTesting: ComplianceSignalGroup;
199
+ reportingExports: ComplianceSignalGroup;
200
+ loggingMonitoring: ComplianceSignalGroup;
201
+ humanOversight: ComplianceSignalGroup;
202
+ dataGovernance: ComplianceSignalGroup;
203
+ incidentReporting: ComplianceSignalGroup;
204
+ };
205
+ assessmentHints: {
206
+ possibleModes: string[];
207
+ riskIndicators: AssessmentHint[];
208
+ gpaiIndicators: AssessmentHint[];
209
+ openQuestions: string[];
210
+ };
211
+ gaps: ScanGap[];
212
+ recommendationSummary: string[];
213
+ }
214
+
215
+ // ---------------------------------------------------------------------------
216
+ // AI Bill of Materials (euconform.aibom.v1)
217
+ // ---------------------------------------------------------------------------
218
+
219
+ export type BomComponentKind =
220
+ | "framework"
221
+ | "runtime"
222
+ | "inference-provider"
223
+ | "ai-framework"
224
+ | "model"
225
+ | "vector-store"
226
+ | "embedding"
227
+ | "dataset"
228
+ | "tool";
229
+
230
+ export interface BomComponent {
231
+ id: string;
232
+ kind: BomComponentKind;
233
+ name: string;
234
+ version?: string;
235
+ source: "package.json" | "lock-file" | "code" | "config" | "docs";
236
+ }
237
+
238
+ export interface AiBillOfMaterials {
239
+ schemaVersion: "euconform.aibom.v1";
240
+ generatedAt: string;
241
+ project: {
242
+ name: string;
243
+ rootPath: string;
244
+ };
245
+ components: BomComponent[];
246
+ complianceCapabilities: {
247
+ biasEvaluation: boolean;
248
+ jsonExport: boolean;
249
+ pdfExport: boolean;
250
+ loggingInfrastructure: boolean;
251
+ humanReviewFlow: boolean;
252
+ incidentHandling: boolean;
253
+ };
254
+ }
255
+
256
+ // ---------------------------------------------------------------------------
257
+ // CI report (euconform.ci.v1)
258
+ // ---------------------------------------------------------------------------
259
+
260
+ export type FailOnLevel = "none" | "critical" | "high" | "medium" | "low";
261
+
262
+ export interface GapCounts {
263
+ critical: number;
264
+ high: number;
265
+ medium: number;
266
+ low: number;
267
+ }
268
+
269
+ export interface CiReport {
270
+ schemaVersion: "euconform.ci.v1";
271
+ generatedAt: string;
272
+ target: {
273
+ name: string;
274
+ rootPath: string;
275
+ };
276
+ status: {
277
+ failOn: FailOnLevel;
278
+ failing: boolean;
279
+ gapCounts: GapCounts;
280
+ openQuestions: number;
281
+ };
282
+ aiDetected: boolean;
283
+ scanScope: ScanScope;
284
+ artifacts: string[];
285
+ complianceOverview: Array<{
286
+ area: string;
287
+ status: ComplianceSignalGroup["status"];
288
+ confidence: ComplianceSignalGroup["confidence"];
289
+ }>;
290
+ topGaps: Array<{
291
+ id: string;
292
+ title: string;
293
+ priority: ScanGap["priority"];
294
+ status: ScanGap["status"];
295
+ }>;
296
+ }
297
+
298
+ // ---------------------------------------------------------------------------
299
+ // Combined scan output
300
+ // ---------------------------------------------------------------------------
301
+
302
+ export interface ScanOutput {
303
+ report: ScanReport;
304
+ aibom: AiBillOfMaterials;
305
+ summaryMarkdown: string;
306
+ }
307
+
308
+ // ---------------------------------------------------------------------------
309
+ // Bundle manifest (euconform.bundle.v1)
310
+ // ---------------------------------------------------------------------------
311
+
312
+ export type BundleArtifactRole = "report" | "aibom" | "ci" | "summary";
313
+
314
+ export interface BundleArtifactRef {
315
+ role: BundleArtifactRole;
316
+ fileName: string;
317
+ sha256: string;
318
+ schemaVersion?: string;
319
+ mimeType?: string;
320
+ required: boolean;
321
+ }
322
+
323
+ export interface ScanBundle {
324
+ schemaVersion: "euconform.bundle.v1";
325
+ generatedAt: string;
326
+ tool: { name: string; version: string };
327
+ target: { name: string; rootPath: string };
328
+ artifacts: BundleArtifactRef[];
329
+ }
@@ -0,0 +1,270 @@
1
+ import type { AiBillOfMaterials, CiReport, ScanBundle, ScanReport } from "./types";
2
+
3
+ function assertObject(data: unknown, label: string): Record<string, unknown> {
4
+ if (!data || typeof data !== "object" || Array.isArray(data)) {
5
+ throw new Error(`Invalid ${label}: expected a JSON object`);
6
+ }
7
+ return data as Record<string, unknown>;
8
+ }
9
+
10
+ function requireSchemaVersion(obj: Record<string, unknown>, expected: string, label: string): void {
11
+ if (obj.schemaVersion !== expected) {
12
+ throw new Error(
13
+ `Invalid ${label} schema version: expected "${expected}", got "${String(obj.schemaVersion)}"`
14
+ );
15
+ }
16
+ }
17
+
18
+ function requireField(
19
+ obj: Record<string, unknown>,
20
+ key: string,
21
+ type: "string" | "boolean" | "object" | "array" | "number",
22
+ label: string,
23
+ fieldName = key
24
+ ): unknown {
25
+ if (!(key in obj)) {
26
+ throw new Error(`Invalid ${label}: missing '${fieldName}'`);
27
+ }
28
+
29
+ const value = obj[key];
30
+ const isValid =
31
+ (type === "array" && Array.isArray(value)) ||
32
+ (type === "object" && value && typeof value === "object" && !Array.isArray(value)) ||
33
+ (type === "string" && typeof value === "string") ||
34
+ (type === "boolean" && typeof value === "boolean") ||
35
+ (type === "number" && typeof value === "number");
36
+
37
+ if (!isValid) {
38
+ throw new Error(`Invalid ${label}: '${fieldName}' must be a ${type}`);
39
+ }
40
+
41
+ return value;
42
+ }
43
+
44
+ function requireStringArray(
45
+ obj: Record<string, unknown>,
46
+ key: string,
47
+ label: string,
48
+ fieldName = key
49
+ ): string[] {
50
+ const value = requireField(obj, key, "array", label, fieldName) as unknown[];
51
+ if (!value.every((item) => typeof item === "string")) {
52
+ throw new Error(`Invalid ${label}: '${fieldName}' must contain strings`);
53
+ }
54
+ return value as string[];
55
+ }
56
+
57
+ function majorVersion(schemaVersion: string): string | null {
58
+ const match = /\.v(\d+)$/.exec(schemaVersion);
59
+ return match?.[1] ?? null;
60
+ }
61
+
62
+ export function getSchemaMajorVersion(schemaVersion: string): string | null {
63
+ return majorVersion(schemaVersion);
64
+ }
65
+
66
+ export function validateScanReport(data: unknown): ScanReport {
67
+ const obj = assertObject(data, "report");
68
+ requireSchemaVersion(obj, "euconform.report.v1", "report");
69
+ requireField(obj, "generatedAt", "string", "report");
70
+
71
+ const tool = requireField(obj, "tool", "object", "report") as Record<string, unknown>;
72
+ requireField(tool, "name", "string", "report", "tool.name");
73
+ requireField(tool, "version", "string", "report", "tool.version");
74
+
75
+ const target = requireField(obj, "target", "object", "report") as Record<string, unknown>;
76
+ requireField(target, "rootPath", "string", "report", "target.rootPath");
77
+ requireField(target, "name", "string", "report", "target.name");
78
+ requireField(target, "repoType", "string", "report", "target.repoType");
79
+ requireField(target, "detectedStack", "array", "report", "target.detectedStack");
80
+
81
+ const aiFootprint = requireField(obj, "aiFootprint", "object", "report") as Record<
82
+ string,
83
+ unknown
84
+ >;
85
+ requireField(aiFootprint, "usesAI", "boolean", "report", "aiFootprint.usesAI");
86
+ requireStringArray(aiFootprint, "inferenceModes", "report", "aiFootprint.inferenceModes");
87
+ requireStringArray(aiFootprint, "providerHints", "report", "aiFootprint.providerHints");
88
+ requireStringArray(aiFootprint, "ragHints", "report", "aiFootprint.ragHints");
89
+
90
+ requireField(obj, "complianceSignals", "object", "report");
91
+ const hints = requireField(obj, "assessmentHints", "object", "report") as Record<string, unknown>;
92
+ requireStringArray(hints, "possibleModes", "report", "assessmentHints.possibleModes");
93
+ requireField(hints, "riskIndicators", "array", "report", "assessmentHints.riskIndicators");
94
+ requireField(hints, "gpaiIndicators", "array", "report", "assessmentHints.gpaiIndicators");
95
+ requireStringArray(hints, "openQuestions", "report", "assessmentHints.openQuestions");
96
+
97
+ requireField(obj, "gaps", "array", "report");
98
+ requireStringArray(obj, "recommendationSummary", "report");
99
+
100
+ return data as ScanReport;
101
+ }
102
+
103
+ export function validateAiBillOfMaterials(data: unknown): AiBillOfMaterials {
104
+ const obj = assertObject(data, "AIBOM");
105
+ requireSchemaVersion(obj, "euconform.aibom.v1", "AIBOM");
106
+ requireField(obj, "generatedAt", "string", "AIBOM");
107
+
108
+ const project = requireField(obj, "project", "object", "AIBOM") as Record<string, unknown>;
109
+ requireField(project, "name", "string", "AIBOM", "project.name");
110
+ requireField(project, "rootPath", "string", "AIBOM", "project.rootPath");
111
+
112
+ requireField(obj, "components", "array", "AIBOM");
113
+ requireField(obj, "complianceCapabilities", "object", "AIBOM");
114
+
115
+ return data as AiBillOfMaterials;
116
+ }
117
+
118
+ export function validateCiReport(data: unknown): CiReport {
119
+ const obj = assertObject(data, "CI report");
120
+ requireSchemaVersion(obj, "euconform.ci.v1", "CI report");
121
+ requireField(obj, "generatedAt", "string", "CI report");
122
+
123
+ const target = requireField(obj, "target", "object", "CI report") as Record<string, unknown>;
124
+ requireField(target, "name", "string", "CI report", "target.name");
125
+ requireField(target, "rootPath", "string", "CI report", "target.rootPath");
126
+
127
+ const status = requireField(obj, "status", "object", "CI report") as Record<string, unknown>;
128
+ requireField(status, "failOn", "string", "CI report", "status.failOn");
129
+ requireField(status, "failing", "boolean", "CI report", "status.failing");
130
+ requireField(status, "openQuestions", "number", "CI report", "status.openQuestions");
131
+
132
+ const gapCounts = requireField(
133
+ status,
134
+ "gapCounts",
135
+ "object",
136
+ "CI report",
137
+ "status.gapCounts"
138
+ ) as Record<string, unknown>;
139
+ for (const key of ["critical", "high", "medium", "low"] as const) {
140
+ requireField(gapCounts, key, "number", "CI report", `status.gapCounts.${key}`);
141
+ }
142
+
143
+ requireField(obj, "aiDetected", "boolean", "CI report");
144
+ requireField(obj, "scanScope", "string", "CI report");
145
+ requireField(obj, "artifacts", "array", "CI report");
146
+ requireField(obj, "complianceOverview", "array", "CI report");
147
+ requireField(obj, "topGaps", "array", "CI report");
148
+
149
+ return data as CiReport;
150
+ }
151
+
152
+ const VALID_BUNDLE_ROLES = new Set(["report", "aibom", "ci", "summary"]);
153
+ const SHA256_HEX = /^[a-f0-9]{64}$/;
154
+
155
+ export function validateScanBundle(data: unknown): ScanBundle {
156
+ const obj = assertObject(data, "bundle");
157
+ requireSchemaVersion(obj, "euconform.bundle.v1", "bundle");
158
+ requireField(obj, "generatedAt", "string", "bundle");
159
+
160
+ const tool = requireField(obj, "tool", "object", "bundle") as Record<string, unknown>;
161
+ requireField(tool, "name", "string", "bundle", "tool.name");
162
+ requireField(tool, "version", "string", "bundle", "tool.version");
163
+
164
+ const target = requireField(obj, "target", "object", "bundle") as Record<string, unknown>;
165
+ requireField(target, "name", "string", "bundle", "target.name");
166
+ requireField(target, "rootPath", "string", "bundle", "target.rootPath");
167
+
168
+ const artifacts = requireField(obj, "artifacts", "array", "bundle") as unknown[];
169
+ if (artifacts.length === 0) {
170
+ throw new Error("Invalid bundle: 'artifacts' must contain at least one artifact");
171
+ }
172
+
173
+ const seenRoles = new Set<string>();
174
+ const seenFiles = new Set<string>();
175
+ const bundleMajor = majorVersion("euconform.bundle.v1");
176
+ let hasRequiredReport = false;
177
+
178
+ for (let index = 0; index < artifacts.length; index++) {
179
+ const artifact = assertObject(artifacts[index], "bundle artifact");
180
+ const role = requireField(
181
+ artifact,
182
+ "role",
183
+ "string",
184
+ "bundle artifact",
185
+ `artifacts[${index}].role`
186
+ ) as string;
187
+ const fileName = requireField(
188
+ artifact,
189
+ "fileName",
190
+ "string",
191
+ "bundle artifact",
192
+ `artifacts[${index}].fileName`
193
+ ) as string;
194
+ const sha256 = requireField(
195
+ artifact,
196
+ "sha256",
197
+ "string",
198
+ "bundle artifact",
199
+ `artifacts[${index}].sha256`
200
+ ) as string;
201
+ const required = requireField(
202
+ artifact,
203
+ "required",
204
+ "boolean",
205
+ "bundle artifact",
206
+ `artifacts[${index}].required`
207
+ ) as boolean;
208
+
209
+ if (!VALID_BUNDLE_ROLES.has(role)) {
210
+ throw new Error(`Invalid bundle artifact: unsupported role '${role}'`);
211
+ }
212
+ if (!SHA256_HEX.test(sha256)) {
213
+ throw new Error(`Invalid bundle artifact: '${fileName}' has invalid sha256`);
214
+ }
215
+ if (seenRoles.has(role)) {
216
+ throw new Error(`Invalid bundle artifact: duplicate role '${role}'`);
217
+ }
218
+ if (seenFiles.has(fileName)) {
219
+ throw new Error(`Invalid bundle artifact: duplicate fileName '${fileName}'`);
220
+ }
221
+ seenRoles.add(role);
222
+ seenFiles.add(fileName);
223
+
224
+ const declaredSchemaVersion = artifact.schemaVersion;
225
+ if (declaredSchemaVersion !== undefined && typeof declaredSchemaVersion !== "string") {
226
+ throw new Error(`Invalid bundle artifact: '${fileName}' has non-string schemaVersion`);
227
+ }
228
+ if (artifact.mimeType !== undefined && typeof artifact.mimeType !== "string") {
229
+ throw new Error(`Invalid bundle artifact: '${fileName}' has non-string mimeType`);
230
+ }
231
+
232
+ if (
233
+ declaredSchemaVersion &&
234
+ bundleMajor &&
235
+ majorVersion(declaredSchemaVersion) !== bundleMajor
236
+ ) {
237
+ throw new Error(
238
+ `Invalid bundle artifact: '${fileName}' uses schemaVersion '${declaredSchemaVersion}' with a different major than the bundle`
239
+ );
240
+ }
241
+
242
+ if (role === "report") {
243
+ hasRequiredReport = required;
244
+ }
245
+ }
246
+
247
+ if (!hasRequiredReport) {
248
+ throw new Error("Invalid bundle: a required 'report' artifact is mandatory");
249
+ }
250
+
251
+ return data as ScanBundle;
252
+ }
253
+
254
+ export function validateEcefJsonDocument(
255
+ schemaVersion: string,
256
+ data: unknown
257
+ ): ScanReport | AiBillOfMaterials | CiReport | ScanBundle {
258
+ switch (schemaVersion) {
259
+ case "euconform.report.v1":
260
+ return validateScanReport(data);
261
+ case "euconform.aibom.v1":
262
+ return validateAiBillOfMaterials(data);
263
+ case "euconform.ci.v1":
264
+ return validateCiReport(data);
265
+ case "euconform.bundle.v1":
266
+ return validateScanBundle(data);
267
+ default:
268
+ throw new Error(`Unsupported ECEF schemaVersion '${schemaVersion}'`);
269
+ }
270
+ }
package/src/index.ts CHANGED
@@ -31,6 +31,12 @@ export {
31
31
  classifyAnnexIIIRisk,
32
32
  getAnnexIIIQuestions,
33
33
  getProhibitedAIScreeningQuestions,
34
+ GPAI_QUESTIONS,
35
+ classifyGPAICompliance,
36
+ getGPAIQuestions,
37
+ isSystemicRiskModel,
38
+ generateAnnexIIIGapAnalysis,
39
+ generateGPAIGapAnalysis,
34
40
  AI_ACT_SOURCES,
35
41
  METRIC_SOURCES,
36
42
  createPerspectiveApiToxicityScorer,
@@ -48,6 +54,14 @@ export {
48
54
  runCrowsPairsLogProbTest,
49
55
  runRegardTest,
50
56
  runToxicityTest,
57
+ EU_AI_ACT_TIMELINE,
58
+ DIGITAL_OMNIBUS_PROPOSALS,
59
+ getDeadlineStatus,
60
+ getDaysUntilDeadline,
61
+ getAudiencesForRiskLevel,
62
+ getNextDeadline,
63
+ getTimelineForRiskLevel,
64
+ getTimelineForAudiences,
51
65
  } from "./legal-checks";
52
66
  export type {
53
67
  AnnexIIIAnswer,
@@ -71,6 +85,17 @@ export type {
71
85
  SensitiveAttribute,
72
86
  ToxicityResult,
73
87
  ToxicityScorer,
88
+ DeadlineStatus,
89
+ AffectedAudience,
90
+ DeadlineObligation,
91
+ DeadlinePhase,
92
+ GPAIObligationType,
93
+ GPAIComplianceLevel,
94
+ GPAIComplianceFlag,
95
+ GPAIComplianceResult,
96
+ GapPriority,
97
+ GapAction,
98
+ GapAnalysisResult,
74
99
  } from "./legal-checks";
75
100
 
76
101
  // Risk Engine