@euconform/core 1.5.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 (141) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +34 -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/types.d.ts +175 -1
  28. package/dist/evidence/types.d.ts.map +1 -1
  29. package/dist/evidence/validate-bias-v2.d.ts +31 -0
  30. package/dist/evidence/validate-bias-v2.d.ts.map +1 -0
  31. package/dist/evidence/validate-bias-v2.js +40 -0
  32. package/dist/evidence/validate.d.ts +29 -2
  33. package/dist/evidence/validate.d.ts.map +1 -1
  34. package/dist/evidence/validate.js +90 -8
  35. package/dist/fairness-metrics.d.ts +15 -11
  36. package/dist/fairness-metrics.d.ts.map +1 -1
  37. package/dist/fairness-metrics.js +59 -183
  38. package/dist/index.d.ts +2 -2
  39. package/dist/index.d.ts.map +1 -1
  40. package/dist/index.js +1 -1
  41. package/dist/inference/browser-inference.d.ts +6 -57
  42. package/dist/inference/browser-inference.d.ts.map +1 -1
  43. package/dist/inference/browser-inference.js +6 -219
  44. package/dist/inference/capability-detection.d.ts +9 -2
  45. package/dist/inference/capability-detection.d.ts.map +1 -1
  46. package/dist/inference/capability-detection.js +18 -16
  47. package/dist/inference/index.d.ts +15 -2
  48. package/dist/inference/index.d.ts.map +1 -1
  49. package/dist/inference/index.js +12 -3
  50. package/dist/inference/inference-factory.d.ts +6 -3
  51. package/dist/inference/inference-factory.d.ts.map +1 -1
  52. package/dist/inference/inference-factory.js +7 -26
  53. package/dist/inference/ollama-client.d.ts +29 -0
  54. package/dist/inference/ollama-client.d.ts.map +1 -1
  55. package/dist/inference/ollama-client.js +29 -0
  56. package/dist/inference/scored-log-prob-types.d.ts +64 -0
  57. package/dist/inference/scored-log-prob-types.d.ts.map +1 -0
  58. package/dist/inference/scored-log-prob-types.js +12 -0
  59. package/dist/inference/sequence-logprob.d.ts +34 -0
  60. package/dist/inference/sequence-logprob.d.ts.map +1 -0
  61. package/dist/inference/sequence-logprob.js +87 -0
  62. package/dist/inference/template-scoring.d.ts +124 -0
  63. package/dist/inference/template-scoring.d.ts.map +1 -0
  64. package/dist/inference/template-scoring.js +164 -0
  65. package/dist/inference/transformers-inference.d.ts +124 -0
  66. package/dist/inference/transformers-inference.d.ts.map +1 -0
  67. package/dist/inference/transformers-inference.js +392 -0
  68. package/dist/legal-checks/bias-metrics.d.ts +5 -7
  69. package/dist/legal-checks/bias-metrics.d.ts.map +1 -1
  70. package/dist/legal-checks/bias-metrics.js +8 -101
  71. package/dist/metrics/crows-pairs-bias.d.ts +17 -9
  72. package/dist/metrics/crows-pairs-bias.d.ts.map +1 -1
  73. package/dist/metrics/crows-pairs-bias.js +147 -59
  74. package/dist/metrics/stereotype-bias.d.ts +13 -5
  75. package/dist/metrics/stereotype-bias.d.ts.map +1 -1
  76. package/dist/metrics/stereotype-bias.js +16 -99
  77. package/dist/scanner/detectors/ai-framework-detector.js +1 -1
  78. package/dist/scanner/detectors/local-inference-detector.d.ts.map +1 -1
  79. package/dist/scanner/detectors/local-inference-detector.js +1 -1
  80. package/dist/scanner/detectors/package-json-detector.d.ts.map +1 -1
  81. package/dist/scanner/detectors/package-json-detector.js +1 -0
  82. package/dist/types.d.ts +103 -2
  83. package/dist/types.d.ts.map +1 -1
  84. package/dist/validation/schema-validator.d.ts +1 -1
  85. package/dist/validation/schema-validator.d.ts.map +1 -1
  86. package/dist/validation/schema-validator.js +6 -0
  87. package/package.json +4 -2
  88. package/scripts/seed-logprob-fixture.mjs +179 -0
  89. package/scripts/ts-extensionless-resolve-hook.mjs +26 -0
  90. package/src/datasets/crows-pairs.ts +74 -0
  91. package/src/evidence/bias-comparison-markdown.ts +162 -0
  92. package/src/evidence/bias-comparison.ts +432 -0
  93. package/src/evidence/bias-statistics.ts +180 -0
  94. package/src/evidence/bias-v2-semantics.ts +214 -0
  95. package/src/evidence/browser.ts +35 -0
  96. package/src/evidence/bundle.ts +2 -0
  97. package/src/evidence/index.ts +31 -0
  98. package/src/evidence/types.ts +204 -1
  99. package/src/evidence/validate-bias-v2.ts +47 -0
  100. package/src/evidence/validate.ts +140 -13
  101. package/src/fairness-metrics.ts +79 -222
  102. package/src/index.ts +7 -0
  103. package/src/inference/browser-inference.ts +14 -256
  104. package/src/inference/capability-detection.ts +32 -18
  105. package/src/inference/index.ts +33 -5
  106. package/src/inference/inference-factory.ts +9 -30
  107. package/src/inference/ollama-client.ts +29 -0
  108. package/src/inference/scored-log-prob-types.ts +66 -0
  109. package/src/inference/sequence-logprob.ts +136 -0
  110. package/src/inference/template-scoring.ts +248 -0
  111. package/src/inference/transformers-inference.ts +494 -0
  112. package/src/legal-checks/bias-metrics.ts +20 -118
  113. package/src/metrics/crows-pairs-bias.ts +180 -83
  114. package/src/metrics/stereotype-bias.ts +27 -119
  115. package/src/scanner/detectors/ai-framework-detector.ts +1 -1
  116. package/src/scanner/detectors/local-inference-detector.ts +2 -1
  117. package/src/scanner/detectors/package-json-detector.ts +1 -0
  118. package/src/types.ts +108 -2
  119. package/src/validation/schema-validator.ts +15 -1
  120. package/tests/annex-iv-report.test.ts +1 -1
  121. package/tests/capability-detection.test.ts +57 -0
  122. package/tests/crows-pairs-bias.test.ts +286 -186
  123. package/tests/custom-dataset.test.ts +80 -0
  124. package/tests/evidence/bias-artifact-v2.test.ts +135 -0
  125. package/tests/evidence/bias-artifact.test.ts +78 -0
  126. package/tests/evidence/bias-comparison.test.ts +397 -0
  127. package/tests/evidence/bias-statistics.test.ts +191 -0
  128. package/tests/evidence/external-observation.test.ts +167 -0
  129. package/tests/evidence/spec-schemas.test.ts +35 -2
  130. package/tests/evidence/validate-bias-v2.test.ts +180 -0
  131. package/tests/fairness-metrics.test.ts +77 -1
  132. package/tests/inference/browser-inference-scoring.test.ts +159 -0
  133. package/tests/inference/fixtures/logprob-golden.json +69 -0
  134. package/tests/inference/gemma-template.integration.test.ts +81 -0
  135. package/tests/inference/logprob-golden.integration.test.ts +128 -0
  136. package/tests/inference/synthetic-logits.test.ts +188 -0
  137. package/tests/inference/template-scoring.test.ts +249 -0
  138. package/tests/inference/transformers-inference-template-mode.test.ts +214 -0
  139. package/tests/integration/bias-calculation.integration.test.ts +119 -599
  140. package/tests/stilled-ollama-paths.test.ts +76 -0
  141. package/src/inference/transformers.d.ts +0 -62
package/src/types.ts CHANGED
@@ -65,7 +65,7 @@ export interface FairnessMetric {
65
65
  export interface BiasTestResult {
66
66
  modelId: string;
67
67
  timestamp: string;
68
- method: BiasCalculationMethod;
68
+ method: BiasCalculationMethod | SequenceLogProbMethod;
69
69
  engine: "browser" | "ollama";
70
70
  protectedAttribute?: string;
71
71
  crowsPairsResult?: CrowsPairsBiasResult;
@@ -92,6 +92,17 @@ export interface CrowsPairsEntry {
92
92
  /** Bias calculation method types */
93
93
  export type BiasCalculationMethod = "logprobs_exact" | "logprobs_fallback_latency";
94
94
 
95
+ /**
96
+ * How a sentence's log-probability was accumulated for the autoregressive
97
+ * CrowS-Pairs adaptation (Messmotor-Sanierung Task 3). "sequence_logprob_sum" is
98
+ * the base mode (sum of scored-token log-probs, BOS excluded). Public naming:
99
+ * "autoregressive CrowS-Pairs adaptation", never "original methodology" — the
100
+ * original CrowS-Pairs paper used masked-model pseudo-log-likelihood, which this
101
+ * is not. "conditional_sequence_logprob_sum" is reserved for a future template
102
+ * mode that conditions scoring on a fixed prompt/context span; not yet emitted.
103
+ */
104
+ export type SequenceLogProbMethod = "sequence_logprob_sum" | "conditional_sequence_logprob_sum";
105
+
95
106
  /** Model capability cache for log-probability support */
96
107
  export interface ModelCapabilityCache {
97
108
  model: string;
@@ -100,17 +111,106 @@ export interface ModelCapabilityCache {
100
111
  ollamaVersion?: string;
101
112
  }
102
113
 
114
+ /** Result for a single CrowS-Pairs sentence pair. */
115
+ export interface CrowsPairsPairResult {
116
+ pairId: number | null;
117
+ stereotype: string;
118
+ antiStereotype: string;
119
+ biasType: string | null;
120
+ stereotypeLogProb: number;
121
+ antiStereotypeLogProb: number;
122
+ biasScore: number;
123
+ biasDirection: "stereotype" | "anti-stereotype" | "neutral";
124
+ method: SequenceLogProbMethod;
125
+ /** Number of scored tokens (BOS excluded) in the stereotype sentence. */
126
+ stereotypeTokenCount: number;
127
+ /** Number of scored tokens (BOS excluded) in the anti-stereotype sentence. */
128
+ antiStereotypeTokenCount: number;
129
+ /** stereotypeTokenCount - antiStereotypeTokenCount; 0 means the pair is part of the equal-token-length confounder-control subset. */
130
+ tokenCountDelta: number;
131
+ /** SHA-256 over the compact decimal-token-id JSON array of the stereotype sentence's scored tokens. */
132
+ stereotypeTokenHash: string;
133
+ /** SHA-256 over the compact decimal-token-id JSON array of the anti-stereotype sentence's scored tokens. */
134
+ antiStereotypeTokenHash: string;
135
+ }
136
+
137
+ /** Aggregate over all pairs sharing one bias type. */
138
+ export interface CrowsPairsBiasTypeSummary {
139
+ biasType: string;
140
+ pairsCount: number;
141
+ stereotypePreferenceCount: number;
142
+ stereotypePreferencePercent: number;
143
+ averageBiasScore: number;
144
+ }
145
+
146
+ /** A pair that was selected for measurement but whose scoring failed (allowPartial runs only). */
147
+ export interface CrowsPairsFailedPair {
148
+ pairId: number | null;
149
+ reason: string;
150
+ }
151
+
152
+ /**
153
+ * Snapshot of the measurement provenance the provider actually reported for a run,
154
+ * echoed by `calculateCrowsPairsBias` so CLI callers (see `run-bias-test.ts`) can build
155
+ * the artifact's top-level `provenance` object from what was really measured instead of
156
+ * a hardcoded literal (Messmotor-Sanierung Task 5 review finding: the CLI once wrote a
157
+ * hardcoded dtype; Task 6 found the same mistake for `templateStrategy`). Deliberately a
158
+ * standalone interface rather than importing `ScoredLogProbProvenance` from
159
+ * `./inference/scored-log-prob-types` — that module imports types FROM this file, and
160
+ * mirroring the handful of fields needed here avoids relying on TypeScript's type-only
161
+ * import erasure to keep the cycle inert.
162
+ */
163
+ export interface CrowsPairsScoringProvenance {
164
+ dtype: string;
165
+ logitsDtype: string;
166
+ backend: string;
167
+ libraryVersion: string;
168
+ templateStrategy?: "none" | "fixed_template_span";
169
+ /** Present only when templateStrategy is "fixed_template_span". */
170
+ promptText?: string;
171
+ promptSha256?: string;
172
+ /** From the scored result's own `contextTokenHash` — constant across a run's pairs
173
+ * (same fixed context every call), so one representative sample is the whole run's value. */
174
+ contextTokenHash?: string;
175
+ }
176
+
103
177
  /** CrowS-Pairs bias calculation result */
104
178
  export interface CrowsPairsBiasResult {
105
179
  score: number;
106
- method: BiasCalculationMethod;
180
+ method: SequenceLogProbMethod;
107
181
  pairsAnalyzed: number;
108
182
  stereotypicalPreference: number; // percentage
183
+ /** One entry per analysed pair. Empty only if nothing could be analysed. */
184
+ pairResults: CrowsPairsPairResult[];
185
+ /** Per-category aggregates. The whole point: the overall score hides these. */
186
+ byBiasType: CrowsPairsBiasTypeSummary[];
187
+ /**
188
+ * "complete": every selected pair was analysed. "partial": at least one pair
189
+ * failed and `allowPartial` was set — this run is a diagnostic, not a full
190
+ * measurement. Fail-closed by default: without `allowPartial`, any failure
191
+ * aborts the whole run rather than producing a "partial" result.
192
+ */
193
+ runStatus: "complete" | "partial";
194
+ /** Pairs selected for measurement after dataset-extraction filtering. */
195
+ selectedCount: number;
196
+ /** Pairs successfully measured; the statistical denominator for score/preference. */
197
+ analyzedCount: number;
198
+ /** Pairs selected but that failed measurement (only possible with allowPartial). */
199
+ failedCount: number;
200
+ /** One entry per failed pair. Invariant: selectedCount === analyzedCount + failedCount. */
201
+ failedPairs: CrowsPairsFailedPair[];
109
202
  metadata: {
110
203
  engine: "browser" | "ollama";
111
204
  model: string;
112
205
  timestamp: string;
113
206
  };
207
+ /**
208
+ * The provider's actual scoring provenance (from the first successfully-measured
209
+ * pair — the fixed template context, if any, does not vary across pairs in one run).
210
+ * Undefined only if analyzedCount is 0, which `calculateCrowsPairsBias` already
211
+ * refuses to return (see "No valid bias scores calculated" throw).
212
+ */
213
+ scoringProvenance?: CrowsPairsScoringProvenance;
114
214
  }
115
215
 
116
216
  /** Configuration for bias test execution */
@@ -118,6 +218,12 @@ export interface BiasTestConfig {
118
218
  dataset: CrowsPairsEntry[];
119
219
  model: string;
120
220
  engine: "browser" | "ollama";
221
+ /**
222
+ * Diagnostic mode: a pair that fails measurement is recorded in `failedPairs`
223
+ * instead of aborting the whole run. NOT suitable as a complete measurement —
224
+ * see `runStatus: "partial"`. Default: false (fail-closed).
225
+ */
226
+ allowPartial?: boolean;
121
227
  }
122
228
 
123
229
  /** Annex IV Technical Documentation Report */
@@ -12,7 +12,15 @@ export interface ValidationError {
12
12
  received?: unknown;
13
13
  }
14
14
 
15
- export type SchemaType = "report.v1" | "aibom.v1" | "aibom.v1.1" | "ci.v1" | "bundle.v1";
15
+ export type SchemaType =
16
+ | "report.v1"
17
+ | "aibom.v1"
18
+ | "aibom.v1.1"
19
+ | "ci.v1"
20
+ | "bundle.v1"
21
+ | "bias.v1"
22
+ | "bias.v2"
23
+ | "external-observation.v1";
16
24
 
17
25
  export interface ValidationResult {
18
26
  valid: boolean;
@@ -45,6 +53,12 @@ const SCHEMA_MAP: Record<string, { schemaFile: string; type: SchemaType }> = {
45
53
  "euconform.aibom.v1.1": { schemaFile: "aibom-v1.1.schema.json", type: "aibom.v1.1" },
46
54
  "euconform.ci.v1": { schemaFile: "ci-v1.schema.json", type: "ci.v1" },
47
55
  "euconform.bundle.v1": { schemaFile: "bundle-v1.schema.json", type: "bundle.v1" },
56
+ "euconform.bias.v1": { schemaFile: "bias-v1.schema.json", type: "bias.v1" },
57
+ "euconform.bias.v2": { schemaFile: "bias-v2.schema.json", type: "bias.v2" },
58
+ "euconform.external-observation.v1": {
59
+ schemaFile: "external-observation-v1.schema.json",
60
+ type: "external-observation.v1",
61
+ },
48
62
  };
49
63
 
50
64
  let ajvInstance: Ajv2020 | null = null;
@@ -78,7 +78,7 @@ describe("Annex IV Report Generation", () => {
78
78
  engine: "browser",
79
79
  crowsPairsResult: {
80
80
  score: 0.15,
81
- method: "logprobs_exact",
81
+ method: "sequence_logprob_sum",
82
82
  pairsAnalyzed: 100,
83
83
  stereotypicalPreference: 65.5,
84
84
  metadata: {
@@ -0,0 +1,57 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import {
3
+ DefaultCapabilityDetectionService,
4
+ EnhancedCapabilityDetectionService,
5
+ } from "../src/inference/capability-detection";
6
+
7
+ /**
8
+ * Browser/transformers.js capability entries are declared from a static model
9
+ * list — no model is actually loaded or tested to produce them. Reporting
10
+ * `status: "available"` with a fresh `lastTested` for that would claim a test
11
+ * ran when it didn't (see Messmotor-Sanierung Task 2). This pins the honest
12
+ * runtime values: `status: "unverified"` and no `lastTested`.
13
+ */
14
+ describe("capability detection: browser/transformers entries are not overclaimed", () => {
15
+ it("DefaultCapabilityDetectionService.detectModelCapability reports 'unverified', not 'available', for browser", async () => {
16
+ const service = new DefaultCapabilityDetectionService();
17
+ const capability = await service.detectModelCapability("Xenova/distilgpt2", "browser");
18
+
19
+ expect(capability.status).toBe("unverified");
20
+ expect(capability.lastTested).toBeUndefined();
21
+ // The declared method may still be reported (renaming is Task 3's job) —
22
+ // it just must not be dressed up as measured.
23
+ expect(capability.method).toBe("logprobs_exact");
24
+ });
25
+
26
+ it("EnhancedCapabilityDetectionService.detectModelCapability reports 'unverified', not 'available', for browser", async () => {
27
+ const service = new EnhancedCapabilityDetectionService();
28
+ const capability = await service.detectModelCapability("Xenova/distilgpt2", "browser");
29
+
30
+ expect(capability.status).toBe("unverified");
31
+ expect(capability.lastTested).toBeUndefined();
32
+ });
33
+
34
+ it("'unverified' is not scored as 0 by the recommendation ranking (regression guard)", async () => {
35
+ // Exercise the same code path detectAllCapabilities uses, without touching
36
+ // the network (no live Ollama in CI): detect a single browser capability
37
+ // and confirm it sorts ahead of an explicitly zero-scored "error" entry.
38
+ const service = new DefaultCapabilityDetectionService();
39
+ const browserCapability = await service.detectModelCapability("Xenova/distilgpt2", "browser");
40
+ const errorCapability = {
41
+ modelId: "broken-model",
42
+ engine: "ollama" as const,
43
+ method: "logprobs_fallback_latency" as const,
44
+ status: "error" as const,
45
+ lastTested: new Date().toISOString(),
46
+ };
47
+
48
+ // sortByRecommendation is protected; reach it through the public
49
+ // detectAllCapabilities-equivalent ordering contract by calling the same
50
+ // scoring indirectly via Array#sort with the class's own comparator is not
51
+ // exposed, so assert the documented invariant directly instead: an
52
+ // "unverified" capability must score above a definitionally-zero one.
53
+ // biome-ignore lint/suspicious/noExplicitAny: reaching a protected method for a focused unit check
54
+ const score = (service as any).getRecommendationScore.bind(service);
55
+ expect(score(browserCapability)).toBeGreaterThan(score(errorCapability));
56
+ });
57
+ });