@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
@@ -1,4 +1,27 @@
1
- import type { AiBillOfMaterials, CiReport, ScanBundle, ScanReport } from "./types";
1
+ import type {
2
+ AiBillOfMaterials,
3
+ BiasReport,
4
+ BiasReportV2,
5
+ CiReport,
6
+ ExternalObservation,
7
+ ScanBundle,
8
+ ScanReport,
9
+ } from "./types";
10
+ import { validateBiasReportV2 } from "./validate-bias-v2";
11
+
12
+ const V1_BIAS_LEGACY_WARNING =
13
+ "This artifact uses euconform.bias.v1: the method label 'logprobs_exact' on v1 bias " +
14
+ "artifacts is unreliable (Messmotor-Sanierung Task 1/2 found the measurement path it " +
15
+ "described could silently fall back to wall-clock nanoseconds instead of real " +
16
+ "log-probabilities). Re-measure with `euconform bias --engine transformers` to get a " +
17
+ "euconform.bias.v2 artifact.";
18
+
19
+ /** Result of validating any bias artifact, v1 (legacy) or v2. */
20
+ export interface BiasValidationResult {
21
+ legacy: boolean;
22
+ warning?: string;
23
+ document: BiasReport | BiasReportV2;
24
+ }
2
25
 
3
26
  function assertObject(data: unknown, label: string): Record<string, unknown> {
4
27
  if (!data || typeof data !== "object" || Array.isArray(data)) {
@@ -155,7 +178,7 @@ export function validateCiReport(data: unknown): CiReport {
155
178
  return data as CiReport;
156
179
  }
157
180
 
158
- const VALID_BUNDLE_ROLES = new Set(["report", "aibom", "ci", "summary"]);
181
+ const VALID_BUNDLE_ROLES = new Set(["report", "aibom", "ci", "summary", "bias"]);
159
182
  const SHA256_HEX = /^[a-f0-9]{64}$/;
160
183
 
161
184
  interface BundleArtifactFields {
@@ -213,13 +236,20 @@ function readBundleArtifactFields(
213
236
  };
214
237
  }
215
238
 
239
+ /**
240
+ * Bundle decoupling (Messmotor-Sanierung Task 3): artifact schema versions are
241
+ * independent of the bundle's own schema version. A `euconform.bundle.v1`
242
+ * manifest may reference a `euconform.bias.v2` artifact — the bundle's job is
243
+ * to bind artifacts by hash and role, not to pin their versions. There is
244
+ * therefore no "same major as the bundle" check here; per-artifact schema
245
+ * shape is the responsibility of that artifact's own validator.
246
+ */
216
247
  function assertBundleArtifactIntegrity(
217
248
  fields: BundleArtifactFields,
218
249
  seenRoles: Set<string>,
219
- seenFiles: Set<string>,
220
- bundleMajor: string | null
250
+ seenFiles: Set<string>
221
251
  ): void {
222
- const { role, fileName, sha256, declaredSchemaVersion } = fields;
252
+ const { role, fileName, sha256 } = fields;
223
253
 
224
254
  if (!VALID_BUNDLE_ROLES.has(role)) {
225
255
  throw new Error(`Invalid bundle artifact: unsupported role '${role}'`);
@@ -233,11 +263,6 @@ function assertBundleArtifactIntegrity(
233
263
  if (seenFiles.has(fileName)) {
234
264
  throw new Error(`Invalid bundle artifact: duplicate fileName '${fileName}'`);
235
265
  }
236
- if (declaredSchemaVersion && bundleMajor && majorVersion(declaredSchemaVersion) !== bundleMajor) {
237
- throw new Error(
238
- `Invalid bundle artifact: '${fileName}' uses schemaVersion '${declaredSchemaVersion}' with a different major than the bundle`
239
- );
240
- }
241
266
  }
242
267
 
243
268
  function validateBundleHeader(obj: Record<string, unknown>): void {
@@ -253,6 +278,107 @@ function validateBundleHeader(obj: Record<string, unknown>): void {
253
278
  requireField(target, "rootPath", "string", "bundle", "target.rootPath");
254
279
  }
255
280
 
281
+ export function validateBiasReport(data: unknown): BiasReport {
282
+ const obj = assertObject(data, "bias report");
283
+ requireSchemaVersion(obj, "euconform.bias.v1", "bias report");
284
+ requireField(obj, "generatedAt", "string", "bias report");
285
+ requireField(obj, "model", "string", "bias report");
286
+ requireField(obj, "dataset", "string", "bias report");
287
+
288
+ const result = requireField(obj, "result", "object", "bias report") as Record<string, unknown>;
289
+ requireField(result, "score", "number", "bias report", "result.score");
290
+ requireField(result, "method", "string", "bias report", "result.method");
291
+ requireField(result, "pairsAnalyzed", "number", "bias report", "result.pairsAnalyzed");
292
+ requireField(
293
+ result,
294
+ "stereotypicalPreference",
295
+ "number",
296
+ "bias report",
297
+ "result.stereotypicalPreference"
298
+ );
299
+
300
+ return data as BiasReport;
301
+ }
302
+
303
+ /**
304
+ * The ONE entry point for bias artifacts, dispatching on schemaVersion.
305
+ * `validate` (CLI) and `verify` both call this instead of picking their own
306
+ * validator, so they can no longer disagree on the same file.
307
+ *
308
+ * v1 keeps its existing (lenient) validation behaviour EXACTLY as before —
309
+ * `validateBiasReport` is untouched — but is now flagged `legacy: true` with a
310
+ * warning naming the incident. v2 is delegated to the shared semantic
311
+ * validator (`validate-bias-v2.ts`), which also recomputes statistics.
312
+ */
313
+ export function validateBiasArtifact(data: unknown): BiasValidationResult {
314
+ const obj = assertObject(data, "bias report");
315
+ if (obj.schemaVersion === "euconform.bias.v2") {
316
+ return { legacy: false, document: validateBiasReportV2(data) };
317
+ }
318
+ return {
319
+ legacy: true,
320
+ warning: V1_BIAS_LEGACY_WARNING,
321
+ document: validateBiasReport(data),
322
+ };
323
+ }
324
+
325
+ /**
326
+ * Shape check for `euconform.external-observation.v1`. The schema is the source
327
+ * of truth for shape (`euconform validate` delegates to it); this function is the
328
+ * programmatic entrypoint, mirroring the other document validators.
329
+ *
330
+ * It deliberately performs no interpretation: whether a declaration exists is
331
+ * data, and nothing here may turn `unknown` into permission.
332
+ */
333
+ export function validateExternalObservation(data: unknown): ExternalObservation {
334
+ const label = "external observation";
335
+ const obj = assertObject(data, label);
336
+ requireSchemaVersion(obj, "euconform.external-observation.v1", label);
337
+ requireField(obj, "observationId", "string", label);
338
+ requireField(obj, "observedAt", "string", label);
339
+
340
+ const provider = requireField(obj, "provider", "object", label) as Record<string, unknown>;
341
+ requireField(provider, "name", "string", label, "provider.name");
342
+ requireField(provider, "version", "string", label, "provider.version");
343
+
344
+ const subject = requireField(obj, "subject", "object", label) as Record<string, unknown>;
345
+ requireField(subject, "kind", "string", label, "subject.kind");
346
+ requireField(subject, "identifier", "string", label, "subject.identifier");
347
+ requireField(subject, "retrievedAt", "string", label, "subject.retrievedAt");
348
+
349
+ const observation = requireField(obj, "observation", "object", label) as Record<string, unknown>;
350
+ requireField(observation, "kind", "string", label, "observation.kind");
351
+ requireStringArray(observation, "intendedUse", label, "observation.intendedUse");
352
+ const result = requireField(
353
+ observation,
354
+ "result",
355
+ "string",
356
+ label,
357
+ "observation.result"
358
+ ) as string;
359
+
360
+ if (result === "declared") {
361
+ const declaration = requireField(
362
+ observation,
363
+ "declaration",
364
+ "object",
365
+ label,
366
+ "observation.declaration"
367
+ ) as Record<string, unknown>;
368
+ requireField(declaration, "text", "string", label, "observation.declaration.text");
369
+ requireField(declaration, "sha256", "string", label, "observation.declaration.sha256");
370
+ }
371
+
372
+ const limitations = requireStringArray(obj, "limitations", label);
373
+ if (limitations.length === 0) {
374
+ throw new Error(
375
+ `Invalid ${label}: 'limitations' must name at least one coverage limit — an observation without stated limits is not reviewable`
376
+ );
377
+ }
378
+
379
+ return data as ExternalObservation;
380
+ }
381
+
256
382
  export function validateScanBundle(data: unknown): ScanBundle {
257
383
  const obj = assertObject(data, "bundle");
258
384
  validateBundleHeader(obj);
@@ -264,13 +390,12 @@ export function validateScanBundle(data: unknown): ScanBundle {
264
390
 
265
391
  const seenRoles = new Set<string>();
266
392
  const seenFiles = new Set<string>();
267
- const bundleMajor = majorVersion("euconform.bundle.v1");
268
393
  let hasRequiredReport = false;
269
394
 
270
395
  for (let index = 0; index < artifacts.length; index++) {
271
396
  const raw = assertObject(artifacts[index], "bundle artifact");
272
397
  const fields = readBundleArtifactFields(raw, index);
273
- assertBundleArtifactIntegrity(fields, seenRoles, seenFiles, bundleMajor);
398
+ assertBundleArtifactIntegrity(fields, seenRoles, seenFiles);
274
399
 
275
400
  seenRoles.add(fields.role);
276
401
  seenFiles.add(fields.fileName);
@@ -290,7 +415,7 @@ export function validateScanBundle(data: unknown): ScanBundle {
290
415
  export function validateEcefJsonDocument(
291
416
  schemaVersion: string,
292
417
  data: unknown
293
- ): ScanReport | AiBillOfMaterials | CiReport | ScanBundle {
418
+ ): ScanReport | AiBillOfMaterials | CiReport | ScanBundle | ExternalObservation {
294
419
  switch (schemaVersion) {
295
420
  case "euconform.report.v1":
296
421
  return validateScanReport(data);
@@ -301,6 +426,8 @@ export function validateEcefJsonDocument(
301
426
  return validateCiReport(data);
302
427
  case "euconform.bundle.v1":
303
428
  return validateScanBundle(data);
429
+ case "euconform.external-observation.v1":
430
+ return validateExternalObservation(data);
304
431
  default:
305
432
  throw new Error(`Unsupported EuConform Evidence Format schemaVersion '${schemaVersion}'`);
306
433
  }
@@ -3,9 +3,8 @@
3
3
  * Based on EU AI Act requirements for non-discrimination
4
4
  */
5
5
 
6
- import type { BrowserInference } from "./inference/browser-inference";
7
- import { InferenceFactory, type InferenceResult } from "./inference/inference-factory";
8
- import type { OllamaClient } from "./inference/ollama-client";
6
+ import type { SequenceLogProbProvider } from "./inference";
7
+ import type { InferenceResult } from "./inference/inference-factory";
9
8
  import { calculateCrowsPairsBias } from "./metrics/crows-pairs-bias";
10
9
  import type { BiasTestConfig, BiasTestResult, FairnessMetric, PredictionData } from "./types";
11
10
 
@@ -209,95 +208,45 @@ export function runFairnessAnalysis(
209
208
  */
210
209
  export async function runCrowsPairsBiasAnalysis(
211
210
  config: BiasTestConfig,
212
- inferenceClient: BrowserInference | OllamaClient
211
+ inferenceClient: SequenceLogProbProvider
213
212
  ): Promise<BiasTestResult> {
214
- try {
215
- const crowsPairsResult = await calculateCrowsPairsBias(config, inferenceClient);
213
+ // A measurement failure (including the core's intentional fail-closed abort)
214
+ // must propagate, not be replaced by a fabricated "failed" result — a
215
+ // synthesized `BiasTestResult` is indistinguishable from a real one to any
216
+ // caller that doesn't inspect stack traces, which is exactly the
217
+ // silent-fallback pattern this remediation removes.
218
+ const crowsPairsResult = await calculateCrowsPairsBias(config, inferenceClient);
219
+
220
+ // Determine if bias test passed based on score threshold
221
+ // A score close to 0 indicates less bias, positive scores indicate stereotypical bias
222
+ const biasThreshold = 0.1; // Configurable threshold for acceptable bias
223
+ const overallPassed = Math.abs(crowsPairsResult.score) < biasThreshold;
216
224
 
217
- // Determine if bias test passed based on score threshold
218
- // A score close to 0 indicates less bias, positive scores indicate stereotypical bias
219
- const biasThreshold = 0.1; // Configurable threshold for acceptable bias
220
- const overallPassed = Math.abs(crowsPairsResult.score) < biasThreshold;
221
-
222
- return {
223
- modelId: config.model,
224
- timestamp: new Date().toISOString(),
225
- method: crowsPairsResult.method,
226
- engine: config.engine,
227
- crowsPairsResult,
228
- overallPassed,
229
- samplesAnalyzed: crowsPairsResult.pairsAnalyzed,
230
- };
231
- } catch (error) {
232
- console.error("CrowS-Pairs bias analysis failed:", error);
233
-
234
- // Return failed result with error information
235
- return {
236
- modelId: config.model,
237
- timestamp: new Date().toISOString(),
238
- method: "logprobs_fallback_latency", // Default fallback
239
- engine: config.engine,
240
- overallPassed: false,
241
- samplesAnalyzed: 0,
242
- };
243
- }
225
+ return {
226
+ modelId: config.model,
227
+ timestamp: new Date().toISOString(),
228
+ method: crowsPairsResult.method,
229
+ engine: config.engine,
230
+ crowsPairsResult,
231
+ overallPassed,
232
+ samplesAnalyzed: crowsPairsResult.pairsAnalyzed,
233
+ };
244
234
  }
245
235
 
246
236
  /**
247
- * Run CrowS-Pairs bias analysis using InferenceFactory with automatic method detection
248
- * This version provides method metadata and automatic best method selection
237
+ * @deprecated Uses the removed Ollama logprob path; see bias --engine transformers.
238
+ * `InferenceFactory.createBiasCalculation` auto-selected between browser and
239
+ * Ollama engines, which no longer measures anything honest for Ollama — that
240
+ * auto-selection logic needs a real redesign (Task 3+), not a throw-or-convert
241
+ * fix, so this entry point is stilled rather than converted.
249
242
  */
250
243
  export async function runCrowsPairsBiasAnalysisWithFactory(
251
- config: BiasTestConfig,
252
- statusCallback?: (status: string) => void
244
+ _config: BiasTestConfig,
245
+ _statusCallback?: (status: string) => void
253
246
  ): Promise<InferenceResult<BiasTestResult>> {
254
- try {
255
- const factory = new InferenceFactory(statusCallback);
256
- const result = await factory.createBiasCalculation(config);
257
-
258
- // Determine if bias test passed based on score threshold
259
- const biasThreshold = 0.1; // Configurable threshold for acceptable bias
260
- const overallPassed = Math.abs(result.data.score) < biasThreshold;
261
-
262
- const biasTestResult: BiasTestResult = {
263
- modelId: config.model,
264
- timestamp: new Date().toISOString(),
265
- method: result.method,
266
- engine: result.engine,
267
- crowsPairsResult: result.data,
268
- overallPassed,
269
- samplesAnalyzed: result.data.pairsAnalyzed,
270
- };
271
-
272
- // Clean up factory resources
273
- factory.dispose();
274
-
275
- return {
276
- data: biasTestResult,
277
- method: result.method,
278
- engine: result.engine,
279
- model: result.model,
280
- };
281
- } catch (error) {
282
- console.error("CrowS-Pairs bias analysis with factory failed:", error);
283
-
284
- // Return failed result with error information
285
- const failedResult: BiasTestResult = {
286
- modelId: config.model,
287
- timestamp: new Date().toISOString(),
288
- method: "logprobs_fallback_latency", // Default fallback
289
- engine: config.engine,
290
- overallPassed: false,
291
- samplesAnalyzed: 0,
292
- };
293
-
294
- return {
295
- data: failedResult,
296
- method: "logprobs_fallback_latency",
297
- engine: config.engine,
298
- model: config.model,
299
- };
300
- }
247
+ throw new Error(
248
+ "runCrowsPairsBiasAnalysisWithFactory verwendet den entfernten Ollama-Logprob-Pfad; siehe bias --engine transformers."
249
+ );
301
250
  }
302
251
 
303
252
  /**
@@ -307,154 +256,62 @@ export async function runCrowsPairsBiasAnalysisWithFactory(
307
256
  export async function runComprehensiveBiasAnalysis(
308
257
  predictionData: PredictionData[],
309
258
  crowsPairsConfig: BiasTestConfig,
310
- inferenceClient: BrowserInference | OllamaClient,
259
+ inferenceClient: SequenceLogProbProvider,
311
260
  protectedAttribute: string
312
261
  ): Promise<BiasTestResult> {
313
- try {
314
- // Run CrowS-Pairs analysis (primary method)
315
- const crowsPairsResult = await calculateCrowsPairsBias(crowsPairsConfig, inferenceClient);
316
-
317
- // Run traditional fairness metrics if prediction data is available
318
- let traditionalMetrics: FairnessMetric[] = [];
319
- if (predictionData && predictionData.length > 0) {
320
- const traditionalResult = runFairnessAnalysis(
321
- predictionData,
322
- crowsPairsConfig.model,
323
- protectedAttribute
324
- );
325
- traditionalMetrics = traditionalResult.metrics || [];
326
- }
327
-
328
- // Determine overall pass/fail based on CrowS-Pairs result and traditional metrics
329
- const biasThreshold = 0.1;
330
- const crowsPairsPassed = Math.abs(crowsPairsResult.score) < biasThreshold;
331
- const traditionalPassed =
332
- traditionalMetrics.length === 0 || traditionalMetrics.every((m) => m.passed);
333
- const overallPassed = crowsPairsPassed && traditionalPassed;
334
-
335
- return {
336
- modelId: crowsPairsConfig.model,
337
- timestamp: new Date().toISOString(),
338
- method: crowsPairsResult.method,
339
- engine: crowsPairsConfig.engine,
340
- protectedAttribute,
341
- crowsPairsResult,
342
- metrics: traditionalMetrics.length > 0 ? traditionalMetrics : undefined,
343
- overallPassed,
344
- samplesAnalyzed: crowsPairsResult.pairsAnalyzed + (predictionData?.length || 0),
345
- };
346
- } catch (error) {
347
- console.error("Comprehensive bias analysis failed:", error);
262
+ // Run CrowS-Pairs analysis (primary method). A failure here — including the
263
+ // core's intentional fail-closed abort — must propagate. It must NOT be
264
+ // caught and replaced by a traditional-metrics-only result or a fabricated
265
+ // "failed" result: either would silently present a degraded or synthesized
266
+ // outcome as if it were the real, complete measurement.
267
+ const crowsPairsResult = await calculateCrowsPairsBias(crowsPairsConfig, inferenceClient);
268
+
269
+ // Run traditional fairness metrics if prediction data is available
270
+ let traditionalMetrics: FairnessMetric[] = [];
271
+ if (predictionData && predictionData.length > 0) {
272
+ const traditionalResult = runFairnessAnalysis(
273
+ predictionData,
274
+ crowsPairsConfig.model,
275
+ protectedAttribute
276
+ );
277
+ traditionalMetrics = traditionalResult.metrics || [];
278
+ }
348
279
 
349
- // Fallback to traditional metrics only if CrowS-Pairs fails
350
- if (predictionData && predictionData.length > 0) {
351
- return runFairnessAnalysis(predictionData, crowsPairsConfig.model, protectedAttribute);
352
- }
280
+ // Determine overall pass/fail based on CrowS-Pairs result and traditional metrics
281
+ const biasThreshold = 0.1;
282
+ const crowsPairsPassed = Math.abs(crowsPairsResult.score) < biasThreshold;
283
+ const traditionalPassed =
284
+ traditionalMetrics.length === 0 || traditionalMetrics.every((m) => m.passed);
285
+ const overallPassed = crowsPairsPassed && traditionalPassed;
353
286
 
354
- // Return failed result if both methods fail
355
- return {
356
- modelId: crowsPairsConfig.model,
357
- timestamp: new Date().toISOString(),
358
- method: "logprobs_fallback_latency",
359
- engine: crowsPairsConfig.engine,
360
- protectedAttribute,
361
- overallPassed: false,
362
- samplesAnalyzed: 0,
363
- };
364
- }
287
+ return {
288
+ modelId: crowsPairsConfig.model,
289
+ timestamp: new Date().toISOString(),
290
+ method: crowsPairsResult.method,
291
+ engine: crowsPairsConfig.engine,
292
+ protectedAttribute,
293
+ crowsPairsResult,
294
+ metrics: traditionalMetrics.length > 0 ? traditionalMetrics : undefined,
295
+ overallPassed,
296
+ samplesAnalyzed: crowsPairsResult.pairsAnalyzed + (predictionData?.length || 0),
297
+ };
365
298
  }
366
299
 
367
300
  /**
368
- * Run comprehensive bias analysis using InferenceFactory with automatic method detection
369
- * Combines prediction-based fairness metrics with CrowS-Pairs stereotypical bias detection
301
+ * @deprecated Uses the removed Ollama logprob path via InferenceFactory's
302
+ * auto-selection; see bias --engine transformers. Stilled for the same reason
303
+ * as `runCrowsPairsBiasAnalysisWithFactory` — the factory's engine
304
+ * auto-selection needs a real redesign, not a throw-or-convert fix.
370
305
  */
371
306
  export async function runComprehensiveBiasAnalysisWithFactory(
372
- predictionData: PredictionData[],
373
- crowsPairsConfig: BiasTestConfig,
374
- protectedAttribute: string,
375
- statusCallback?: (status: string) => void
307
+ _predictionData: PredictionData[],
308
+ _crowsPairsConfig: BiasTestConfig,
309
+ _protectedAttribute: string,
310
+ _statusCallback?: (status: string) => void
376
311
  ): Promise<InferenceResult<BiasTestResult>> {
377
- try {
378
- const factory = new InferenceFactory(statusCallback);
379
-
380
- // Run CrowS-Pairs analysis (primary method) with automatic method detection
381
- const crowsPairsResult = await factory.createBiasCalculation(crowsPairsConfig);
382
-
383
- // Run traditional fairness metrics if prediction data is available
384
- let traditionalMetrics: FairnessMetric[] = [];
385
- if (predictionData && predictionData.length > 0) {
386
- const traditionalResult = runFairnessAnalysis(
387
- predictionData,
388
- crowsPairsConfig.model,
389
- protectedAttribute
390
- );
391
- traditionalMetrics = traditionalResult.metrics || [];
392
- }
393
-
394
- // Determine overall pass/fail based on CrowS-Pairs result and traditional metrics
395
- const biasThreshold = 0.1;
396
- const crowsPairsPassed = Math.abs(crowsPairsResult.data.score) < biasThreshold;
397
- const traditionalPassed =
398
- traditionalMetrics.length === 0 || traditionalMetrics.every((m) => m.passed);
399
- const overallPassed = crowsPairsPassed && traditionalPassed;
400
-
401
- const biasTestResult: BiasTestResult = {
402
- modelId: crowsPairsConfig.model,
403
- timestamp: new Date().toISOString(),
404
- method: crowsPairsResult.method,
405
- engine: crowsPairsResult.engine,
406
- protectedAttribute,
407
- crowsPairsResult: crowsPairsResult.data,
408
- metrics: traditionalMetrics.length > 0 ? traditionalMetrics : undefined,
409
- overallPassed,
410
- samplesAnalyzed: crowsPairsResult.data.pairsAnalyzed + (predictionData?.length || 0),
411
- };
412
-
413
- // Clean up factory resources
414
- factory.dispose();
415
-
416
- return {
417
- data: biasTestResult,
418
- method: crowsPairsResult.method,
419
- engine: crowsPairsResult.engine,
420
- model: crowsPairsResult.model,
421
- };
422
- } catch (error) {
423
- console.error("Comprehensive bias analysis with factory failed:", error);
424
-
425
- // Fallback to traditional metrics only if CrowS-Pairs fails
426
- if (predictionData && predictionData.length > 0) {
427
- const traditionalResult = runFairnessAnalysis(
428
- predictionData,
429
- crowsPairsConfig.model,
430
- protectedAttribute
431
- );
432
- return {
433
- data: traditionalResult,
434
- method: "logprobs_fallback_latency",
435
- engine: crowsPairsConfig.engine,
436
- model: crowsPairsConfig.model,
437
- };
438
- }
439
-
440
- // Return failed result if both methods fail
441
- const failedResult: BiasTestResult = {
442
- modelId: crowsPairsConfig.model,
443
- timestamp: new Date().toISOString(),
444
- method: "logprobs_fallback_latency",
445
- engine: crowsPairsConfig.engine,
446
- protectedAttribute,
447
- overallPassed: false,
448
- samplesAnalyzed: 0,
449
- };
450
-
451
- return {
452
- data: failedResult,
453
- method: "logprobs_fallback_latency",
454
- engine: crowsPairsConfig.engine,
455
- model: crowsPairsConfig.model,
456
- };
457
- }
312
+ throw new Error(
313
+ "runComprehensiveBiasAnalysisWithFactory verwendet den entfernten Ollama-Logprob-Pfad; siehe bias --engine transformers."
314
+ );
458
315
  }
459
316
 
460
317
  /**
package/src/index.ts CHANGED
@@ -21,6 +21,7 @@ export type {
21
21
  BiasCalculationMethod,
22
22
  BiasTestConfig,
23
23
  CrowsPairsBiasResult,
24
+ CrowsPairsFailedPair,
24
25
  ModelCapabilityCache,
25
26
  } from "./types";
26
27
 
@@ -126,6 +127,7 @@ export {
126
127
  listOllamaModels,
127
128
  OllamaClient,
128
129
  BrowserInference,
130
+ TransformersInference,
129
131
  checkWebGPU,
130
132
  BROWSER_MODELS,
131
133
  detectCapabilities,
@@ -139,6 +141,9 @@ export {
139
141
  type InferenceEngine,
140
142
  type InferenceCapabilities,
141
143
  type InferenceClient,
144
+ type SequenceLogProbProvider,
145
+ type ScoredLogProb,
146
+ type ScoredLogProbProvenance,
142
147
  type CapabilityDetectionService,
143
148
  type ModelCapability,
144
149
  type ModelSelectionState,
@@ -148,6 +153,8 @@ export {
148
153
  type EnhancedModelSelectionState,
149
154
  type CapabilityDetectionStatus,
150
155
  type CapabilityCache,
156
+ isConditionalGenerationConfig,
157
+ type MinimalModelConfig,
151
158
  } from "./inference";
152
159
 
153
160
  // Inference Factory