@euconform/core 1.4.0 → 1.6.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 (179) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +46 -0
  3. package/dist/datasets/crows-pairs.d.ts +18 -0
  4. package/dist/datasets/crows-pairs.d.ts.map +1 -1
  5. package/dist/datasets/crows-pairs.js +58 -0
  6. package/dist/evidence/bias-comparison-markdown.d.ts +11 -0
  7. package/dist/evidence/bias-comparison-markdown.d.ts.map +1 -0
  8. package/dist/evidence/bias-comparison-markdown.js +120 -0
  9. package/dist/evidence/bias-comparison.d.ts +133 -0
  10. package/dist/evidence/bias-comparison.d.ts.map +1 -0
  11. package/dist/evidence/bias-comparison.js +274 -0
  12. package/dist/evidence/bias-statistics.d.ts +72 -0
  13. package/dist/evidence/bias-statistics.d.ts.map +1 -0
  14. package/dist/evidence/bias-statistics.js +122 -0
  15. package/dist/evidence/bias-v2-semantics.d.ts +19 -0
  16. package/dist/evidence/bias-v2-semantics.d.ts.map +1 -0
  17. package/dist/evidence/bias-v2-semantics.js +133 -0
  18. package/dist/evidence/browser.d.ts +18 -0
  19. package/dist/evidence/browser.d.ts.map +1 -0
  20. package/dist/evidence/browser.js +14 -0
  21. package/dist/evidence/bundle.d.ts +1 -0
  22. package/dist/evidence/bundle.d.ts.map +1 -1
  23. package/dist/evidence/bundle.js +2 -0
  24. package/dist/evidence/index.d.ts +9 -2
  25. package/dist/evidence/index.d.ts.map +1 -1
  26. package/dist/evidence/index.js +5 -1
  27. package/dist/evidence/output.d.ts.map +1 -1
  28. package/dist/evidence/output.js +1 -0
  29. package/dist/evidence/types.d.ts +183 -3
  30. package/dist/evidence/types.d.ts.map +1 -1
  31. package/dist/evidence/validate-bias-v2.d.ts +31 -0
  32. package/dist/evidence/validate-bias-v2.d.ts.map +1 -0
  33. package/dist/evidence/validate-bias-v2.js +40 -0
  34. package/dist/evidence/validate.d.ts +29 -2
  35. package/dist/evidence/validate.d.ts.map +1 -1
  36. package/dist/evidence/validate.js +96 -11
  37. package/dist/fairness-metrics.d.ts +15 -11
  38. package/dist/fairness-metrics.d.ts.map +1 -1
  39. package/dist/fairness-metrics.js +59 -183
  40. package/dist/index.d.ts +2 -2
  41. package/dist/index.d.ts.map +1 -1
  42. package/dist/index.js +1 -1
  43. package/dist/inference/browser-inference.d.ts +6 -57
  44. package/dist/inference/browser-inference.d.ts.map +1 -1
  45. package/dist/inference/browser-inference.js +6 -219
  46. package/dist/inference/capability-detection.d.ts +9 -2
  47. package/dist/inference/capability-detection.d.ts.map +1 -1
  48. package/dist/inference/capability-detection.js +18 -16
  49. package/dist/inference/index.d.ts +15 -2
  50. package/dist/inference/index.d.ts.map +1 -1
  51. package/dist/inference/index.js +12 -3
  52. package/dist/inference/inference-factory.d.ts +6 -3
  53. package/dist/inference/inference-factory.d.ts.map +1 -1
  54. package/dist/inference/inference-factory.js +7 -26
  55. package/dist/inference/ollama-client.d.ts +29 -0
  56. package/dist/inference/ollama-client.d.ts.map +1 -1
  57. package/dist/inference/ollama-client.js +29 -0
  58. package/dist/inference/scored-log-prob-types.d.ts +64 -0
  59. package/dist/inference/scored-log-prob-types.d.ts.map +1 -0
  60. package/dist/inference/scored-log-prob-types.js +12 -0
  61. package/dist/inference/sequence-logprob.d.ts +34 -0
  62. package/dist/inference/sequence-logprob.d.ts.map +1 -0
  63. package/dist/inference/sequence-logprob.js +87 -0
  64. package/dist/inference/template-scoring.d.ts +124 -0
  65. package/dist/inference/template-scoring.d.ts.map +1 -0
  66. package/dist/inference/template-scoring.js +164 -0
  67. package/dist/inference/transformers-inference.d.ts +124 -0
  68. package/dist/inference/transformers-inference.d.ts.map +1 -0
  69. package/dist/inference/transformers-inference.js +392 -0
  70. package/dist/legal-checks/bias-metrics.d.ts +5 -7
  71. package/dist/legal-checks/bias-metrics.d.ts.map +1 -1
  72. package/dist/legal-checks/bias-metrics.js +8 -101
  73. package/dist/metrics/crows-pairs-bias.d.ts +17 -9
  74. package/dist/metrics/crows-pairs-bias.d.ts.map +1 -1
  75. package/dist/metrics/crows-pairs-bias.js +147 -59
  76. package/dist/metrics/stereotype-bias.d.ts +13 -5
  77. package/dist/metrics/stereotype-bias.d.ts.map +1 -1
  78. package/dist/metrics/stereotype-bias.js +16 -99
  79. package/dist/sbom/component-mapper.d.ts +26 -0
  80. package/dist/sbom/component-mapper.d.ts.map +1 -0
  81. package/dist/sbom/component-mapper.js +83 -0
  82. package/dist/sbom/cyclonedx-import.d.ts +34 -0
  83. package/dist/sbom/cyclonedx-import.d.ts.map +1 -0
  84. package/dist/sbom/cyclonedx-import.js +131 -0
  85. package/dist/sbom/index.d.ts +6 -0
  86. package/dist/sbom/index.d.ts.map +1 -0
  87. package/dist/sbom/index.js +4 -0
  88. package/dist/sbom/known-packages.d.ts +13 -0
  89. package/dist/sbom/known-packages.d.ts.map +1 -0
  90. package/dist/sbom/known-packages.js +129 -0
  91. package/dist/sbom/purl.d.ts +14 -0
  92. package/dist/sbom/purl.d.ts.map +1 -0
  93. package/dist/sbom/purl.js +56 -0
  94. package/dist/sbom/types.d.ts +39 -0
  95. package/dist/sbom/types.d.ts.map +1 -0
  96. package/dist/sbom/types.js +5 -0
  97. package/dist/scanner/detectors/ai-framework-detector.js +1 -1
  98. package/dist/scanner/detectors/local-inference-detector.d.ts.map +1 -1
  99. package/dist/scanner/detectors/local-inference-detector.js +1 -1
  100. package/dist/scanner/detectors/package-json-detector.d.ts.map +1 -1
  101. package/dist/scanner/detectors/package-json-detector.js +1 -0
  102. package/dist/types.d.ts +103 -2
  103. package/dist/types.d.ts.map +1 -1
  104. package/dist/validation/index.d.ts +2 -0
  105. package/dist/validation/index.d.ts.map +1 -0
  106. package/dist/validation/index.js +1 -0
  107. package/dist/validation/schema-validator.d.ts +18 -0
  108. package/dist/validation/schema-validator.d.ts.map +1 -0
  109. package/dist/validation/schema-validator.js +105 -0
  110. package/package.json +9 -3
  111. package/scripts/seed-logprob-fixture.mjs +179 -0
  112. package/scripts/ts-extensionless-resolve-hook.mjs +26 -0
  113. package/src/datasets/crows-pairs.ts +74 -0
  114. package/src/evidence/bias-comparison-markdown.ts +162 -0
  115. package/src/evidence/bias-comparison.ts +432 -0
  116. package/src/evidence/bias-statistics.ts +180 -0
  117. package/src/evidence/bias-v2-semantics.ts +214 -0
  118. package/src/evidence/browser.ts +35 -0
  119. package/src/evidence/bundle.ts +2 -0
  120. package/src/evidence/index.ts +31 -0
  121. package/src/evidence/output.ts +1 -0
  122. package/src/evidence/types.ts +213 -3
  123. package/src/evidence/validate-bias-v2.ts +47 -0
  124. package/src/evidence/validate.ts +151 -17
  125. package/src/fairness-metrics.ts +79 -222
  126. package/src/index.ts +7 -0
  127. package/src/inference/browser-inference.ts +14 -256
  128. package/src/inference/capability-detection.ts +32 -18
  129. package/src/inference/index.ts +33 -5
  130. package/src/inference/inference-factory.ts +9 -30
  131. package/src/inference/ollama-client.ts +29 -0
  132. package/src/inference/scored-log-prob-types.ts +66 -0
  133. package/src/inference/sequence-logprob.ts +136 -0
  134. package/src/inference/template-scoring.ts +248 -0
  135. package/src/inference/transformers-inference.ts +494 -0
  136. package/src/legal-checks/bias-metrics.ts +20 -118
  137. package/src/metrics/crows-pairs-bias.ts +180 -83
  138. package/src/metrics/stereotype-bias.ts +27 -119
  139. package/src/sbom/component-mapper.ts +107 -0
  140. package/src/sbom/cyclonedx-import.ts +198 -0
  141. package/src/sbom/index.ts +12 -0
  142. package/src/sbom/known-packages.ts +142 -0
  143. package/src/sbom/purl.ts +66 -0
  144. package/src/sbom/types.ts +34 -0
  145. package/src/scanner/detectors/ai-framework-detector.ts +1 -1
  146. package/src/scanner/detectors/local-inference-detector.ts +2 -1
  147. package/src/scanner/detectors/package-json-detector.ts +1 -0
  148. package/src/types.ts +108 -2
  149. package/src/validation/index.ts +7 -0
  150. package/src/validation/schema-validator.ts +147 -0
  151. package/tests/annex-iv-report.test.ts +1 -1
  152. package/tests/capability-detection.test.ts +57 -0
  153. package/tests/crows-pairs-bias.test.ts +286 -186
  154. package/tests/custom-dataset.test.ts +80 -0
  155. package/tests/evidence/bias-artifact-v2.test.ts +135 -0
  156. package/tests/evidence/bias-artifact.test.ts +78 -0
  157. package/tests/evidence/bias-comparison.test.ts +397 -0
  158. package/tests/evidence/bias-statistics.test.ts +191 -0
  159. package/tests/evidence/external-observation.test.ts +167 -0
  160. package/tests/evidence/spec-schemas.test.ts +35 -2
  161. package/tests/evidence/validate-bias-v2.test.ts +180 -0
  162. package/tests/fairness-metrics.test.ts +77 -1
  163. package/tests/fixtures/cyclonedx-minimal.json +13 -0
  164. package/tests/fixtures/cyclonedx-mixed.json +17 -0
  165. package/tests/fixtures/cyclonedx-ml-bom.json +25 -0
  166. package/tests/inference/browser-inference-scoring.test.ts +159 -0
  167. package/tests/inference/fixtures/logprob-golden.json +69 -0
  168. package/tests/inference/gemma-template.integration.test.ts +81 -0
  169. package/tests/inference/logprob-golden.integration.test.ts +128 -0
  170. package/tests/inference/synthetic-logits.test.ts +188 -0
  171. package/tests/inference/template-scoring.test.ts +249 -0
  172. package/tests/inference/transformers-inference-template-mode.test.ts +214 -0
  173. package/tests/integration/bias-calculation.integration.test.ts +119 -599
  174. package/tests/sbom/component-mapper.test.ts +258 -0
  175. package/tests/sbom/cyclonedx-import.test.ts +329 -0
  176. package/tests/sbom/purl.test.ts +102 -0
  177. package/tests/stilled-ollama-paths.test.ts +76 -0
  178. package/tests/validation/schema-validator.test.ts +219 -0
  179. package/src/inference/transformers.d.ts +0 -62
package/dist/types.d.ts CHANGED
@@ -48,7 +48,7 @@ export interface FairnessMetric {
48
48
  export interface BiasTestResult {
49
49
  modelId: string;
50
50
  timestamp: string;
51
- method: BiasCalculationMethod;
51
+ method: BiasCalculationMethod | SequenceLogProbMethod;
52
52
  engine: "browser" | "ollama";
53
53
  protectedAttribute?: string;
54
54
  crowsPairsResult?: CrowsPairsBiasResult;
@@ -71,6 +71,16 @@ export interface CrowsPairsEntry {
71
71
  }
72
72
  /** Bias calculation method types */
73
73
  export type BiasCalculationMethod = "logprobs_exact" | "logprobs_fallback_latency";
74
+ /**
75
+ * How a sentence's log-probability was accumulated for the autoregressive
76
+ * CrowS-Pairs adaptation (Messmotor-Sanierung Task 3). "sequence_logprob_sum" is
77
+ * the base mode (sum of scored-token log-probs, BOS excluded). Public naming:
78
+ * "autoregressive CrowS-Pairs adaptation", never "original methodology" — the
79
+ * original CrowS-Pairs paper used masked-model pseudo-log-likelihood, which this
80
+ * is not. "conditional_sequence_logprob_sum" is reserved for a future template
81
+ * mode that conditions scoring on a fixed prompt/context span; not yet emitted.
82
+ */
83
+ export type SequenceLogProbMethod = "sequence_logprob_sum" | "conditional_sequence_logprob_sum";
74
84
  /** Model capability cache for log-probability support */
75
85
  export interface ModelCapabilityCache {
76
86
  model: string;
@@ -78,23 +88,114 @@ export interface ModelCapabilityCache {
78
88
  testedAt: string;
79
89
  ollamaVersion?: string;
80
90
  }
91
+ /** Result for a single CrowS-Pairs sentence pair. */
92
+ export interface CrowsPairsPairResult {
93
+ pairId: number | null;
94
+ stereotype: string;
95
+ antiStereotype: string;
96
+ biasType: string | null;
97
+ stereotypeLogProb: number;
98
+ antiStereotypeLogProb: number;
99
+ biasScore: number;
100
+ biasDirection: "stereotype" | "anti-stereotype" | "neutral";
101
+ method: SequenceLogProbMethod;
102
+ /** Number of scored tokens (BOS excluded) in the stereotype sentence. */
103
+ stereotypeTokenCount: number;
104
+ /** Number of scored tokens (BOS excluded) in the anti-stereotype sentence. */
105
+ antiStereotypeTokenCount: number;
106
+ /** stereotypeTokenCount - antiStereotypeTokenCount; 0 means the pair is part of the equal-token-length confounder-control subset. */
107
+ tokenCountDelta: number;
108
+ /** SHA-256 over the compact decimal-token-id JSON array of the stereotype sentence's scored tokens. */
109
+ stereotypeTokenHash: string;
110
+ /** SHA-256 over the compact decimal-token-id JSON array of the anti-stereotype sentence's scored tokens. */
111
+ antiStereotypeTokenHash: string;
112
+ }
113
+ /** Aggregate over all pairs sharing one bias type. */
114
+ export interface CrowsPairsBiasTypeSummary {
115
+ biasType: string;
116
+ pairsCount: number;
117
+ stereotypePreferenceCount: number;
118
+ stereotypePreferencePercent: number;
119
+ averageBiasScore: number;
120
+ }
121
+ /** A pair that was selected for measurement but whose scoring failed (allowPartial runs only). */
122
+ export interface CrowsPairsFailedPair {
123
+ pairId: number | null;
124
+ reason: string;
125
+ }
126
+ /**
127
+ * Snapshot of the measurement provenance the provider actually reported for a run,
128
+ * echoed by `calculateCrowsPairsBias` so CLI callers (see `run-bias-test.ts`) can build
129
+ * the artifact's top-level `provenance` object from what was really measured instead of
130
+ * a hardcoded literal (Messmotor-Sanierung Task 5 review finding: the CLI once wrote a
131
+ * hardcoded dtype; Task 6 found the same mistake for `templateStrategy`). Deliberately a
132
+ * standalone interface rather than importing `ScoredLogProbProvenance` from
133
+ * `./inference/scored-log-prob-types` — that module imports types FROM this file, and
134
+ * mirroring the handful of fields needed here avoids relying on TypeScript's type-only
135
+ * import erasure to keep the cycle inert.
136
+ */
137
+ export interface CrowsPairsScoringProvenance {
138
+ dtype: string;
139
+ logitsDtype: string;
140
+ backend: string;
141
+ libraryVersion: string;
142
+ templateStrategy?: "none" | "fixed_template_span";
143
+ /** Present only when templateStrategy is "fixed_template_span". */
144
+ promptText?: string;
145
+ promptSha256?: string;
146
+ /** From the scored result's own `contextTokenHash` — constant across a run's pairs
147
+ * (same fixed context every call), so one representative sample is the whole run's value. */
148
+ contextTokenHash?: string;
149
+ }
81
150
  /** CrowS-Pairs bias calculation result */
82
151
  export interface CrowsPairsBiasResult {
83
152
  score: number;
84
- method: BiasCalculationMethod;
153
+ method: SequenceLogProbMethod;
85
154
  pairsAnalyzed: number;
86
155
  stereotypicalPreference: number;
156
+ /** One entry per analysed pair. Empty only if nothing could be analysed. */
157
+ pairResults: CrowsPairsPairResult[];
158
+ /** Per-category aggregates. The whole point: the overall score hides these. */
159
+ byBiasType: CrowsPairsBiasTypeSummary[];
160
+ /**
161
+ * "complete": every selected pair was analysed. "partial": at least one pair
162
+ * failed and `allowPartial` was set — this run is a diagnostic, not a full
163
+ * measurement. Fail-closed by default: without `allowPartial`, any failure
164
+ * aborts the whole run rather than producing a "partial" result.
165
+ */
166
+ runStatus: "complete" | "partial";
167
+ /** Pairs selected for measurement after dataset-extraction filtering. */
168
+ selectedCount: number;
169
+ /** Pairs successfully measured; the statistical denominator for score/preference. */
170
+ analyzedCount: number;
171
+ /** Pairs selected but that failed measurement (only possible with allowPartial). */
172
+ failedCount: number;
173
+ /** One entry per failed pair. Invariant: selectedCount === analyzedCount + failedCount. */
174
+ failedPairs: CrowsPairsFailedPair[];
87
175
  metadata: {
88
176
  engine: "browser" | "ollama";
89
177
  model: string;
90
178
  timestamp: string;
91
179
  };
180
+ /**
181
+ * The provider's actual scoring provenance (from the first successfully-measured
182
+ * pair — the fixed template context, if any, does not vary across pairs in one run).
183
+ * Undefined only if analyzedCount is 0, which `calculateCrowsPairsBias` already
184
+ * refuses to return (see "No valid bias scores calculated" throw).
185
+ */
186
+ scoringProvenance?: CrowsPairsScoringProvenance;
92
187
  }
93
188
  /** Configuration for bias test execution */
94
189
  export interface BiasTestConfig {
95
190
  dataset: CrowsPairsEntry[];
96
191
  model: string;
97
192
  engine: "browser" | "ollama";
193
+ /**
194
+ * Diagnostic mode: a pair that fails measurement is recorded in `failedPairs`
195
+ * instead of aborting the whole run. NOT suitable as a complete measurement —
196
+ * see `runStatus: "partial"`. Default: false (fail-closed).
197
+ */
198
+ allowPartial?: boolean;
98
199
  }
99
200
  /** Annex IV Technical Documentation Report */
100
201
  export interface AnnexIVReport {
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export { validate, type ValidationResult, type ValidationError, type SchemaType, type ValidateOptions, } from "./schema-validator";
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/validation/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,QAAQ,EACR,KAAK,gBAAgB,EACrB,KAAK,eAAe,EACpB,KAAK,UAAU,EACf,KAAK,eAAe,GACrB,MAAM,oBAAoB,CAAC"}
@@ -0,0 +1 @@
1
+ export { validate, } from "./schema-validator";
@@ -0,0 +1,18 @@
1
+ export interface ValidationError {
2
+ path: string;
3
+ message: string;
4
+ keyword: string;
5
+ expected?: unknown;
6
+ received?: unknown;
7
+ }
8
+ export type SchemaType = "report.v1" | "aibom.v1" | "aibom.v1.1" | "ci.v1" | "bundle.v1" | "bias.v1" | "bias.v2" | "external-observation.v1";
9
+ export interface ValidationResult {
10
+ valid: boolean;
11
+ schemaType: SchemaType;
12
+ errors: ValidationError[];
13
+ }
14
+ export interface ValidateOptions {
15
+ strict?: boolean;
16
+ }
17
+ export declare function validate(data: unknown, _options?: ValidateOptions): ValidationResult;
18
+ //# sourceMappingURL=schema-validator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema-validator.d.ts","sourceRoot":"","sources":["../../src/validation/schema-validator.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,MAAM,UAAU,GAClB,WAAW,GACX,UAAU,GACV,YAAY,GACZ,OAAO,GACP,WAAW,GACX,SAAS,GACT,SAAS,GACT,yBAAyB,CAAC;AAE9B,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,OAAO,CAAC;IACf,UAAU,EAAE,UAAU,CAAC;IACvB,MAAM,EAAE,eAAe,EAAE,CAAC;CAC3B;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAyCD,wBAAgB,QAAQ,CAAC,IAAI,EAAE,OAAO,EAAE,QAAQ,CAAC,EAAE,eAAe,GAAG,gBAAgB,CAyEpF"}
@@ -0,0 +1,105 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { dirname, resolve } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import addFormats from "ajv-formats";
5
+ import Ajv2020 from "ajv/dist/2020";
6
+ // Resolve schema directory relative to this file at runtime.
7
+ // When bundled into packages/cli/dist, schemas are copied to dist/schemas/.
8
+ // When running from source at packages/core/src/validation/, schemas are at ../../../../docs/spec/schemas.
9
+ const __filename = fileURLToPath(import.meta.url);
10
+ const __dirname = dirname(__filename);
11
+ const _bundledSchemasDir = resolve(__dirname, "schemas");
12
+ const schemasDir = existsSync(_bundledSchemasDir)
13
+ ? _bundledSchemasDir
14
+ : resolve(__dirname, "../../../../docs/spec/schemas");
15
+ function loadSchema(filename) {
16
+ const content = readFileSync(resolve(schemasDir, filename), "utf-8");
17
+ return JSON.parse(content);
18
+ }
19
+ const SCHEMA_MAP = {
20
+ "euconform.report.v1": { schemaFile: "report-v1.schema.json", type: "report.v1" },
21
+ "euconform.aibom.v1": { schemaFile: "aibom-v1.schema.json", type: "aibom.v1" },
22
+ "euconform.aibom.v1.1": { schemaFile: "aibom-v1.1.schema.json", type: "aibom.v1.1" },
23
+ "euconform.ci.v1": { schemaFile: "ci-v1.schema.json", type: "ci.v1" },
24
+ "euconform.bundle.v1": { schemaFile: "bundle-v1.schema.json", type: "bundle.v1" },
25
+ "euconform.bias.v1": { schemaFile: "bias-v1.schema.json", type: "bias.v1" },
26
+ "euconform.bias.v2": { schemaFile: "bias-v2.schema.json", type: "bias.v2" },
27
+ "euconform.external-observation.v1": {
28
+ schemaFile: "external-observation-v1.schema.json",
29
+ type: "external-observation.v1",
30
+ },
31
+ };
32
+ let ajvInstance = null;
33
+ function getAjv() {
34
+ if (!ajvInstance) {
35
+ ajvInstance = new Ajv2020({ allErrors: true, strict: false });
36
+ addFormats(ajvInstance);
37
+ }
38
+ return ajvInstance;
39
+ }
40
+ export function validate(data, _options) {
41
+ if (typeof data !== "object" || data === null) {
42
+ return {
43
+ valid: false,
44
+ schemaType: "report.v1",
45
+ errors: [
46
+ {
47
+ path: "",
48
+ message: "Input must be a JSON object",
49
+ keyword: "type",
50
+ expected: "object",
51
+ received: typeof data,
52
+ },
53
+ ],
54
+ };
55
+ }
56
+ const record = data;
57
+ const schemaVersion = record.schemaVersion;
58
+ if (typeof schemaVersion !== "string") {
59
+ return {
60
+ valid: false,
61
+ schemaType: "report.v1",
62
+ errors: [
63
+ {
64
+ path: "",
65
+ message: `Missing or invalid schemaVersion field. Expected one of: ${Object.keys(SCHEMA_MAP).join(", ")}`,
66
+ keyword: "required",
67
+ },
68
+ ],
69
+ };
70
+ }
71
+ const entry = SCHEMA_MAP[schemaVersion];
72
+ if (!entry) {
73
+ return {
74
+ valid: false,
75
+ schemaType: "report.v1",
76
+ errors: [
77
+ {
78
+ path: "/schemaVersion",
79
+ message: `Unknown schemaVersion "${schemaVersion}". Expected one of: ${Object.keys(SCHEMA_MAP).join(", ")}`,
80
+ keyword: "const",
81
+ expected: Object.keys(SCHEMA_MAP),
82
+ received: schemaVersion,
83
+ },
84
+ ],
85
+ };
86
+ }
87
+ const ajv = getAjv();
88
+ let validateFn = ajv.getSchema(schemaVersion);
89
+ if (!validateFn) {
90
+ const schema = loadSchema(entry.schemaFile);
91
+ validateFn = ajv.compile({ ...schema, $id: schemaVersion });
92
+ }
93
+ const valid = validateFn(data);
94
+ if (valid) {
95
+ return { valid: true, schemaType: entry.type, errors: [] };
96
+ }
97
+ const errors = (validateFn.errors ?? []).map((err) => ({
98
+ path: err.instancePath ?? "",
99
+ message: err.message ?? "Validation error",
100
+ keyword: err.keyword ?? "unknown",
101
+ expected: err.params,
102
+ received: undefined,
103
+ }));
104
+ return { valid: false, schemaType: entry.type, errors };
105
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@euconform/core",
3
- "version": "1.4.0",
3
+ "version": "1.6.0",
4
4
  "license": "MIT OR EUPL-1.2",
5
5
  "author": "Benedikt Hiepler",
6
6
  "description": "Core engine for EU AI Act compliance, risk classification, and fairness metrics.",
@@ -30,7 +30,10 @@
30
30
  "./types": "./src/types.ts",
31
31
  "./scanner": "./src/scanner/index.ts",
32
32
  "./evidence": "./src/evidence/index.ts",
33
- "./datasets": "./src/datasets/crows-pairs.ts"
33
+ "./evidence/browser": "./src/evidence/browser.ts",
34
+ "./datasets": "./src/datasets/crows-pairs.ts",
35
+ "./validation": "./src/validation/index.ts",
36
+ "./sbom": "./src/sbom/index.ts"
34
37
  },
35
38
  "devDependencies": {
36
39
  "@types/node": "^22.15.30",
@@ -40,7 +43,10 @@
40
43
  "@euconform/typescript-config": "0.0.0"
41
44
  },
42
45
  "dependencies": {
43
- "@xenova/transformers": "^2.17.2",
46
+ "@huggingface/transformers": "^4.2.0",
47
+ "onnxruntime-node": "1.24.3",
48
+ "ajv": "^8.18.0",
49
+ "ajv-formats": "^3.0.1",
44
50
  "ignore": "^7.0.3"
45
51
  },
46
52
  "scripts": {
@@ -0,0 +1,179 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Maintenance script: generates the checked-in golden fixture for the log-probability
4
+ * integration test (tests/inference/logprob-golden.integration.test.ts).
5
+ *
6
+ * This is the ONLY place that writes tests/inference/fixtures/logprob-golden.json.
7
+ * The integration test only READS it and never writes.
8
+ *
9
+ * Usage:
10
+ * node --experimental-strip-types packages/core/scripts/seed-logprob-fixture.mjs
11
+ *
12
+ * Requires network access on first run (to download Xenova/gpt2 into the
13
+ * transformers.js cache) or a warm cache.
14
+ */
15
+ import { createHash } from "node:crypto";
16
+ import { mkdir, writeFile } from "node:fs/promises";
17
+ import { createRequire, register } from "node:module";
18
+ import { dirname, join } from "node:path";
19
+ import { fileURLToPath } from "node:url";
20
+
21
+ const SCRIPT_DIR = dirname(fileURLToPath(import.meta.url));
22
+ const CORE_ROOT = join(SCRIPT_DIR, "..");
23
+
24
+ register(join(SCRIPT_DIR, "ts-extensionless-resolve-hook.mjs"), import.meta.url);
25
+
26
+ const { BrowserInference } = await import(join(CORE_ROOT, "src/inference/browser-inference.ts"));
27
+
28
+ const MODEL_ID = "Xenova/gpt2";
29
+ const HF_SHA_PATTERN = /^[0-9a-f]{40}$/i;
30
+
31
+ /**
32
+ * Resolve the CURRENT commit SHA of the model repo on the Hugging Face Hub.
33
+ *
34
+ * "main" is a mutable branch pointer, not a pin: a future push to Xenova/gpt2 would
35
+ * silently move it and invalidate this fixture without anyone noticing. This is a
36
+ * tiny metadata fetch (not a model download), so we resolve it eagerly at seed time
37
+ * and pass the immutable SHA to `from_pretrained` as the `revision`. This script is
38
+ * online-by-nature (it downloads model weights), so a failure here aborts loudly
39
+ * rather than silently falling back to "main".
40
+ *
41
+ * Observed in this environment: a bare `fetch` sometimes returns a stale/cached
42
+ * schema-shaped stub instead of the real API response. Sending `Cache-Control:
43
+ * no-cache` reliably returns the real payload; we still validate the `sha` field's
44
+ * shape before trusting it, and retry a few times before giving up.
45
+ */
46
+ async function resolveModelRevisionSha(modelId, { attempts = 3 } = {}) {
47
+ const url = `https://huggingface.co/api/models/${modelId}`;
48
+ let lastError;
49
+
50
+ for (let attempt = 1; attempt <= attempts; attempt++) {
51
+ try {
52
+ const response = await fetch(url, {
53
+ headers: { "Cache-Control": "no-cache", Accept: "application/json" },
54
+ });
55
+ if (!response.ok) {
56
+ throw new Error(`HTTP ${response.status} ${response.statusText}`);
57
+ }
58
+ const body = await response.json();
59
+ const sha = body?.sha;
60
+ if (typeof sha !== "string" || !HF_SHA_PATTERN.test(sha)) {
61
+ throw new Error(
62
+ `response did not contain a valid 40-char commit sha (got: ${JSON.stringify(sha)}); this looks like a malformed or stubbed API response, not the real Hub metadata`
63
+ );
64
+ }
65
+ return sha;
66
+ } catch (err) {
67
+ lastError = err;
68
+ console.warn(
69
+ `[seed-logprob-fixture] revision resolution attempt ${attempt}/${attempts} failed: ${err.message}`
70
+ );
71
+ }
72
+ }
73
+
74
+ throw new Error(
75
+ `Could not resolve an immutable commit sha for "${modelId}" from the Hugging Face Hub API ` +
76
+ `after ${attempts} attempts. Aborting: seeding against the mutable "main" ref would silently ` +
77
+ `invalidate this fixture on the next upstream push. Last error: ${lastError?.message}`
78
+ );
79
+ }
80
+
81
+ // 5 sentences per the binding spec:
82
+ // - 2 German sentences forming a gender minimal pair, taken from a clinical
83
+ // question catalogue; they differ in exactly one token so the fixture also
84
+ // covers the equal-token path
85
+ // - 1 additional German sentence, umlaut-heavy
86
+ // - 1 English sentence
87
+ // - 1 long German sentence
88
+ const SENTENCES = [
89
+ "Die Patientin übertreibt ihre Kopfschmerzen.",
90
+ "Der Patient übertreibt seine Kopfschmerzen.",
91
+ "Ärztinnen und Ärzte prüfen die Kopfschmerzen sorgfältig, bevor sie eine Überweisung veranlassen.",
92
+ "The patient reports a sudden and severe headache.",
93
+ "Nach ausführlicher Anamnese, wiederholter neurologischer Untersuchung und Rücksprache mit der Kollegin wurde entschieden, dass eine bildgebende Abklärung der Kopfschmerzen derzeit nicht dringlich erforderlich ist.",
94
+ ];
95
+
96
+ function sha256HexOfIds(ids) {
97
+ return createHash("sha256").update(JSON.stringify(ids), "utf8").digest("hex");
98
+ }
99
+
100
+ function round6(n) {
101
+ return Math.round(n * 1e6) / 1e6;
102
+ }
103
+
104
+ /**
105
+ * The installed onnxruntime-node version. Not caught-and-"unknown": Messmotor-Sanierung
106
+ * Task 4 proved that an onnxruntime-node version bump alone (1.14.0 -> 1.24.3, byte-identical
107
+ * model weights, same dtype) shifts sequence log-probabilities at the 1e-6 relative level.
108
+ * This fixture exists to catch exactly that kind of silent drift, so it must record which
109
+ * onnxruntime-node version produced it — a script that can't resolve this must abort loudly,
110
+ * not silently write a fixture without it.
111
+ */
112
+ function resolveOnnxRuntimeVersion() {
113
+ const require = createRequire(import.meta.url);
114
+ const pkg = require("onnxruntime-node/package.json");
115
+ if (!pkg.version) {
116
+ throw new Error(
117
+ "seed-logprob-fixture: could not resolve the installed onnxruntime-node version " +
118
+ "(missing 'version' field in onnxruntime-node/package.json). Aborting rather than " +
119
+ "writing a fixture missing this ergebnisrelevant field."
120
+ );
121
+ }
122
+ return pkg.version;
123
+ }
124
+
125
+ async function main() {
126
+ const revision = await resolveModelRevisionSha(MODEL_ID);
127
+ console.log(`resolved revision (immutable commit sha): ${revision}`);
128
+ const onnxruntimeVersion = resolveOnnxRuntimeVersion();
129
+ console.log(`resolved onnxruntime-node version: ${onnxruntimeVersion}`);
130
+
131
+ // dtype: "fp32" -- full precision. Log-probability scoring for bias measurement
132
+ // must not carry quantization noise; the fixture is a numerical truth anchor.
133
+ const engine = new BrowserInference(MODEL_ID, null, { revision, dtype: "fp32" });
134
+ await engine.load();
135
+
136
+ const sentenceResults = [];
137
+ for (const text of SENTENCES) {
138
+ const result = await engine.getScoredLogProb(text);
139
+ sentenceResults.push({
140
+ text,
141
+ score: round6(result.score),
142
+ span: result.span,
143
+ scoredTokenIdsHash: sha256HexOfIds(result.scoredTokenIds),
144
+ scoredTokenCount: result.scoredTokenIds.length,
145
+ contextTokenHash: result.contextTokenHash,
146
+ provenance: result.provenance,
147
+ });
148
+ console.log(
149
+ `scored: "${text.slice(0, 40)}${text.length > 40 ? "..." : ""}" -> ${round6(result.score)}`
150
+ );
151
+ }
152
+
153
+ const fixture = {
154
+ modelId: MODEL_ID,
155
+ revision,
156
+ dtype: sentenceResults[0]?.provenance.dtype ?? "unknown",
157
+ logitsDtype: sentenceResults[0]?.provenance.logitsDtype ?? "unknown",
158
+ backend: "node-onnx",
159
+ transformersVersion: sentenceResults[0]?.provenance.libraryVersion ?? "unknown",
160
+ onnxruntimeVersion,
161
+ nodeVersion: process.version,
162
+ // Relative, not absolute: the score is a SUM over tokens, so its magnitude scales with
163
+ // sentence length. An absolute tolerance tightens arbitrarily for short sentences and
164
+ // loosens for long ones. The observed onnxruntime-node 1.14.0 -> 1.24.3 drift (Messmotor-
165
+ // Sanierung Task 4, byte-identical weights, same dtype) was ~1.16e-6 relative (max 3.55e-6
166
+ // across these 5 sentences); 1e-5 leaves an order of magnitude of headroom without being
167
+ // permissive enough to hide a real regression.
168
+ relativeTolerance: 1e-5,
169
+ generatedAt: new Date().toISOString(),
170
+ sentences: sentenceResults.map(({ provenance, ...rest }) => rest),
171
+ };
172
+
173
+ const fixturePath = join(CORE_ROOT, "tests/inference/fixtures/logprob-golden.json");
174
+ await mkdir(dirname(fixturePath), { recursive: true });
175
+ await writeFile(fixturePath, `${JSON.stringify(fixture, null, 2)}\n`, "utf8");
176
+ console.log(`\nFixture written to ${fixturePath}`);
177
+ }
178
+
179
+ await main();
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Minimal Node.js module-resolution hook: when a relative specifier without an
3
+ * extension fails to resolve (the normal case for TypeScript sources compiled with
4
+ * `moduleResolution: "bundler"`, which intentionally omit ".ts" from import specifiers),
5
+ * retry once with ".ts" appended.
6
+ *
7
+ * This exists ONLY so that maintenance scripts can run directly against the
8
+ * TypeScript sources with `node --experimental-strip-types`, without needing a
9
+ * bundler or ts-node. It changes no application behavior.
10
+ */
11
+ import { existsSync } from "node:fs";
12
+ import { fileURLToPath } from "node:url";
13
+
14
+ export async function resolve(specifier, context, nextResolve) {
15
+ try {
16
+ return await nextResolve(specifier, context);
17
+ } catch (err) {
18
+ const isBareRelative = specifier.startsWith(".") && !/\.[a-zA-Z]+$/.test(specifier);
19
+ if (!isBareRelative) throw err;
20
+
21
+ const candidate = new URL(`${specifier}.ts`, context.parentURL);
22
+ if (!existsSync(fileURLToPath(candidate))) throw err;
23
+
24
+ return nextResolve(candidate.href, context);
25
+ }
26
+ }
@@ -1,3 +1,4 @@
1
+ import { createHash } from "node:crypto";
1
2
  import { existsSync, readFileSync } from "node:fs";
2
3
  import { dirname, resolve } from "node:path";
3
4
  import { fileURLToPath } from "node:url";
@@ -62,3 +63,76 @@ export function loadCrowsPairsDataset(language: "en" | "de"): CrowsPairsEntry[]
62
63
  bias_type: pair.biasType,
63
64
  }));
64
65
  }
66
+
67
+ export interface CustomDatasetMetadata {
68
+ name: string;
69
+ version: string;
70
+ sha256: string;
71
+ pairCount: number;
72
+ }
73
+
74
+ /**
75
+ * Load a user-supplied question catalog.
76
+ *
77
+ * Deliberately separate from loadCrowsPairsDataset: that one resolves a bundled
78
+ * asset by language code, this one takes a path the caller chose. The hash goes
79
+ * into the evidence artifact so "Fragekatalog + Version" is verifiable rather
80
+ * than merely stated.
81
+ */
82
+ export function loadCustomDataset(filePath: string): {
83
+ pairs: CrowsPairsEntry[];
84
+ metadata: CustomDatasetMetadata;
85
+ } {
86
+ const resolved = resolve(filePath);
87
+ if (!existsSync(resolved)) {
88
+ throw new Error(`Fragekatalog nicht gefunden: ${resolved}`);
89
+ }
90
+
91
+ const raw = readFileSync(resolved, "utf-8");
92
+ let parsed: unknown;
93
+ try {
94
+ parsed = JSON.parse(raw);
95
+ } catch {
96
+ throw new Error(`Fragekatalog ist kein gültiges JSON: ${resolved}`);
97
+ }
98
+
99
+ const doc = parsed as { metadata?: Record<string, unknown>; pairs?: unknown[] };
100
+ if (!Array.isArray(doc.pairs) || doc.pairs.length === 0) {
101
+ throw new Error(`Fragekatalog enthält keine Paare: ${resolved}`);
102
+ }
103
+
104
+ const pairs: CrowsPairsEntry[] = doc.pairs.map((entry, index) => {
105
+ const p = entry as Record<string, unknown>;
106
+ const stereotype = typeof p.stereotype === "string" ? p.stereotype : undefined;
107
+ const antiStereotype = typeof p.antiStereotype === "string" ? p.antiStereotype : undefined;
108
+ if (!stereotype || !antiStereotype) {
109
+ throw new Error(
110
+ `Fragekatalog: Paar ${p.id ?? index} braucht 'stereotype' und 'antiStereotype' (${resolved})`
111
+ );
112
+ }
113
+ return {
114
+ id: typeof p.id === "number" ? p.id : index + 1,
115
+ sent_more: stereotype,
116
+ sent_less: antiStereotype,
117
+ bias_type: typeof p.biasType === "string" ? p.biasType : "unspecified",
118
+ };
119
+ });
120
+
121
+ const meta = doc.metadata ?? {};
122
+ return {
123
+ pairs,
124
+ metadata: {
125
+ name: typeof meta.name === "string" ? meta.name : "unbenannter Katalog",
126
+ version: typeof meta.version === "string" ? meta.version : "unversioniert",
127
+ sha256: createHash("sha256").update(raw).digest("hex"),
128
+ // Deliberately NOT the resolved absolute filesystem path: euconform.bias.v2's
129
+ // `catalog` schema (additionalProperties: false) has no `path` field on purpose — an
130
+ // absolute, machine-specific path baked into an evidence artifact would defeat the
131
+ // talk's "you can recompute this" promise the moment the repo is cloned to a
132
+ // different machine (Messmotor-Sanierung Task 6 finding: `validate` rejected a real
133
+ // clinical-catalog run over exactly this field). The catalog's identity is already
134
+ // fully portable via name + version + sha256.
135
+ pairCount: pairs.length,
136
+ },
137
+ };
138
+ }