@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.
- package/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +34 -0
- package/dist/datasets/crows-pairs.d.ts +18 -0
- package/dist/datasets/crows-pairs.d.ts.map +1 -1
- package/dist/datasets/crows-pairs.js +58 -0
- package/dist/evidence/bias-comparison-markdown.d.ts +11 -0
- package/dist/evidence/bias-comparison-markdown.d.ts.map +1 -0
- package/dist/evidence/bias-comparison-markdown.js +120 -0
- package/dist/evidence/bias-comparison.d.ts +133 -0
- package/dist/evidence/bias-comparison.d.ts.map +1 -0
- package/dist/evidence/bias-comparison.js +274 -0
- package/dist/evidence/bias-statistics.d.ts +72 -0
- package/dist/evidence/bias-statistics.d.ts.map +1 -0
- package/dist/evidence/bias-statistics.js +122 -0
- package/dist/evidence/bias-v2-semantics.d.ts +19 -0
- package/dist/evidence/bias-v2-semantics.d.ts.map +1 -0
- package/dist/evidence/bias-v2-semantics.js +133 -0
- package/dist/evidence/browser.d.ts +18 -0
- package/dist/evidence/browser.d.ts.map +1 -0
- package/dist/evidence/browser.js +14 -0
- package/dist/evidence/bundle.d.ts +1 -0
- package/dist/evidence/bundle.d.ts.map +1 -1
- package/dist/evidence/bundle.js +2 -0
- package/dist/evidence/index.d.ts +9 -2
- package/dist/evidence/index.d.ts.map +1 -1
- package/dist/evidence/index.js +5 -1
- package/dist/evidence/types.d.ts +175 -1
- package/dist/evidence/types.d.ts.map +1 -1
- package/dist/evidence/validate-bias-v2.d.ts +31 -0
- package/dist/evidence/validate-bias-v2.d.ts.map +1 -0
- package/dist/evidence/validate-bias-v2.js +40 -0
- package/dist/evidence/validate.d.ts +29 -2
- package/dist/evidence/validate.d.ts.map +1 -1
- package/dist/evidence/validate.js +90 -8
- package/dist/fairness-metrics.d.ts +15 -11
- package/dist/fairness-metrics.d.ts.map +1 -1
- package/dist/fairness-metrics.js +59 -183
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/inference/browser-inference.d.ts +6 -57
- package/dist/inference/browser-inference.d.ts.map +1 -1
- package/dist/inference/browser-inference.js +6 -219
- package/dist/inference/capability-detection.d.ts +9 -2
- package/dist/inference/capability-detection.d.ts.map +1 -1
- package/dist/inference/capability-detection.js +18 -16
- package/dist/inference/index.d.ts +15 -2
- package/dist/inference/index.d.ts.map +1 -1
- package/dist/inference/index.js +12 -3
- package/dist/inference/inference-factory.d.ts +6 -3
- package/dist/inference/inference-factory.d.ts.map +1 -1
- package/dist/inference/inference-factory.js +7 -26
- package/dist/inference/ollama-client.d.ts +29 -0
- package/dist/inference/ollama-client.d.ts.map +1 -1
- package/dist/inference/ollama-client.js +29 -0
- package/dist/inference/scored-log-prob-types.d.ts +64 -0
- package/dist/inference/scored-log-prob-types.d.ts.map +1 -0
- package/dist/inference/scored-log-prob-types.js +12 -0
- package/dist/inference/sequence-logprob.d.ts +34 -0
- package/dist/inference/sequence-logprob.d.ts.map +1 -0
- package/dist/inference/sequence-logprob.js +87 -0
- package/dist/inference/template-scoring.d.ts +124 -0
- package/dist/inference/template-scoring.d.ts.map +1 -0
- package/dist/inference/template-scoring.js +164 -0
- package/dist/inference/transformers-inference.d.ts +124 -0
- package/dist/inference/transformers-inference.d.ts.map +1 -0
- package/dist/inference/transformers-inference.js +392 -0
- package/dist/legal-checks/bias-metrics.d.ts +5 -7
- package/dist/legal-checks/bias-metrics.d.ts.map +1 -1
- package/dist/legal-checks/bias-metrics.js +8 -101
- package/dist/metrics/crows-pairs-bias.d.ts +17 -9
- package/dist/metrics/crows-pairs-bias.d.ts.map +1 -1
- package/dist/metrics/crows-pairs-bias.js +147 -59
- package/dist/metrics/stereotype-bias.d.ts +13 -5
- package/dist/metrics/stereotype-bias.d.ts.map +1 -1
- package/dist/metrics/stereotype-bias.js +16 -99
- package/dist/scanner/detectors/ai-framework-detector.js +1 -1
- package/dist/scanner/detectors/local-inference-detector.d.ts.map +1 -1
- package/dist/scanner/detectors/local-inference-detector.js +1 -1
- package/dist/scanner/detectors/package-json-detector.d.ts.map +1 -1
- package/dist/scanner/detectors/package-json-detector.js +1 -0
- package/dist/types.d.ts +103 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/validation/schema-validator.d.ts +1 -1
- package/dist/validation/schema-validator.d.ts.map +1 -1
- package/dist/validation/schema-validator.js +6 -0
- package/package.json +4 -2
- package/scripts/seed-logprob-fixture.mjs +179 -0
- package/scripts/ts-extensionless-resolve-hook.mjs +26 -0
- package/src/datasets/crows-pairs.ts +74 -0
- package/src/evidence/bias-comparison-markdown.ts +162 -0
- package/src/evidence/bias-comparison.ts +432 -0
- package/src/evidence/bias-statistics.ts +180 -0
- package/src/evidence/bias-v2-semantics.ts +214 -0
- package/src/evidence/browser.ts +35 -0
- package/src/evidence/bundle.ts +2 -0
- package/src/evidence/index.ts +31 -0
- package/src/evidence/types.ts +204 -1
- package/src/evidence/validate-bias-v2.ts +47 -0
- package/src/evidence/validate.ts +140 -13
- package/src/fairness-metrics.ts +79 -222
- package/src/index.ts +7 -0
- package/src/inference/browser-inference.ts +14 -256
- package/src/inference/capability-detection.ts +32 -18
- package/src/inference/index.ts +33 -5
- package/src/inference/inference-factory.ts +9 -30
- package/src/inference/ollama-client.ts +29 -0
- package/src/inference/scored-log-prob-types.ts +66 -0
- package/src/inference/sequence-logprob.ts +136 -0
- package/src/inference/template-scoring.ts +248 -0
- package/src/inference/transformers-inference.ts +494 -0
- package/src/legal-checks/bias-metrics.ts +20 -118
- package/src/metrics/crows-pairs-bias.ts +180 -83
- package/src/metrics/stereotype-bias.ts +27 -119
- package/src/scanner/detectors/ai-framework-detector.ts +1 -1
- package/src/scanner/detectors/local-inference-detector.ts +2 -1
- package/src/scanner/detectors/package-json-detector.ts +1 -0
- package/src/types.ts +108 -2
- package/src/validation/schema-validator.ts +15 -1
- package/tests/annex-iv-report.test.ts +1 -1
- package/tests/capability-detection.test.ts +57 -0
- package/tests/crows-pairs-bias.test.ts +286 -186
- package/tests/custom-dataset.test.ts +80 -0
- package/tests/evidence/bias-artifact-v2.test.ts +135 -0
- package/tests/evidence/bias-artifact.test.ts +78 -0
- package/tests/evidence/bias-comparison.test.ts +397 -0
- package/tests/evidence/bias-statistics.test.ts +191 -0
- package/tests/evidence/external-observation.test.ts +167 -0
- package/tests/evidence/spec-schemas.test.ts +35 -2
- package/tests/evidence/validate-bias-v2.test.ts +180 -0
- package/tests/fairness-metrics.test.ts +77 -1
- package/tests/inference/browser-inference-scoring.test.ts +159 -0
- package/tests/inference/fixtures/logprob-golden.json +69 -0
- package/tests/inference/gemma-template.integration.test.ts +81 -0
- package/tests/inference/logprob-golden.integration.test.ts +128 -0
- package/tests/inference/synthetic-logits.test.ts +188 -0
- package/tests/inference/template-scoring.test.ts +249 -0
- package/tests/inference/transformers-inference-template-mode.test.ts +214 -0
- package/tests/integration/bias-calculation.integration.test.ts +119 -599
- package/tests/stilled-ollama-paths.test.ts +76 -0
- package/src/inference/transformers.d.ts +0 -62
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure semantic checks for `euconform.bias.v2` artifacts (no Node builtins).
|
|
3
|
+
*
|
|
4
|
+
* Split out of `validate-bias-v2.ts` so the exact same recomputation can run in
|
|
5
|
+
* the browser (`@euconform/core/evidence/browser`). The schema-shape check stays
|
|
6
|
+
* Node-side because it reads the published JSON schema from disk; the semantic
|
|
7
|
+
* layer — "do the stored numbers follow from the raw pair results?" — must never
|
|
8
|
+
* have two implementations.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { CrowsPairsPairResult } from "../types";
|
|
12
|
+
import { type BiasStatisticsV2, type WilsonInterval, buildBiasStatistics } from "./bias-statistics";
|
|
13
|
+
import type { BiasReportV2 } from "./types";
|
|
14
|
+
|
|
15
|
+
const TOLERANCE = 1e-9;
|
|
16
|
+
|
|
17
|
+
function closeEnough(a: number, b: number): boolean {
|
|
18
|
+
return Math.abs(a - b) <= TOLERANCE;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function fail(message: string): never {
|
|
22
|
+
throw new Error(`Invalid euconform.bias.v2 artifact: ${message}`);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function assertNumberMatches(label: string, expected: number, actual: number): void {
|
|
26
|
+
if (!closeEnough(expected, actual)) {
|
|
27
|
+
fail(
|
|
28
|
+
`${label} does not match recomputation from pairResults: stored ${actual}, recomputed ${expected}`
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function assertWilsonMatches(
|
|
34
|
+
label: string,
|
|
35
|
+
expected: WilsonInterval | null,
|
|
36
|
+
actual: WilsonInterval | null
|
|
37
|
+
): void {
|
|
38
|
+
if (expected === null || actual === null) {
|
|
39
|
+
if (expected !== actual) {
|
|
40
|
+
fail(
|
|
41
|
+
`${label} should be ${expected === null ? "null" : "a Wilson interval"}, got ${actual === null ? "null" : JSON.stringify(actual)}`
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
assertNumberMatches(`${label}.lower`, expected.lower, actual.lower);
|
|
47
|
+
assertNumberMatches(`${label}.upper`, expected.upper, actual.upper);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function assertQuartilesMatch(
|
|
51
|
+
expected: BiasStatisticsV2["pairDiffQuartiles"],
|
|
52
|
+
actual: BiasStatisticsV2["pairDiffQuartiles"]
|
|
53
|
+
): void {
|
|
54
|
+
if (expected === null || actual === null) {
|
|
55
|
+
if (expected !== actual) {
|
|
56
|
+
fail(
|
|
57
|
+
`statistics.pairDiffQuartiles should be ${expected === null ? "null" : "present"}, got ${actual === null ? "null" : "present"}`
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
for (const key of ["min", "max", "median", "q1", "q3"] as const) {
|
|
63
|
+
assertNumberMatches(`statistics.pairDiffQuartiles.${key}`, expected[key], actual[key]);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function assertCategoryStatisticsMatch(
|
|
68
|
+
expected: BiasStatisticsV2["byBiasType"],
|
|
69
|
+
actual: BiasStatisticsV2["byBiasType"]
|
|
70
|
+
): void {
|
|
71
|
+
if (expected.length !== actual.length) {
|
|
72
|
+
fail(
|
|
73
|
+
`statistics.byBiasType has ${actual.length} categories, recomputation from pairResults yields ${expected.length}`
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
const actualByType = new Map(actual.map((c) => [c.biasType, c]));
|
|
77
|
+
for (const expectedCategory of expected) {
|
|
78
|
+
const actualCategory = actualByType.get(expectedCategory.biasType);
|
|
79
|
+
if (!actualCategory) {
|
|
80
|
+
fail(`statistics.byBiasType is missing category '${expectedCategory.biasType}'`);
|
|
81
|
+
}
|
|
82
|
+
const label = `statistics.byBiasType['${expectedCategory.biasType}']`;
|
|
83
|
+
if (expectedCategory.analyzedCount !== actualCategory.analyzedCount) {
|
|
84
|
+
fail(
|
|
85
|
+
`${label}.analyzedCount: stored ${actualCategory.analyzedCount}, recomputed ${expectedCategory.analyzedCount}`
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
if (expectedCategory.preferenceCount !== actualCategory.preferenceCount) {
|
|
89
|
+
fail(
|
|
90
|
+
`${label}.preferenceCount: stored ${actualCategory.preferenceCount}, recomputed ${expectedCategory.preferenceCount}`
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
if (expectedCategory.tiesCount !== actualCategory.tiesCount) {
|
|
94
|
+
fail(
|
|
95
|
+
`${label}.tiesCount: stored ${actualCategory.tiesCount}, recomputed ${expectedCategory.tiesCount}`
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
assertNumberMatches(
|
|
99
|
+
`${label}.preferencePercent`,
|
|
100
|
+
expectedCategory.preferencePercent,
|
|
101
|
+
actualCategory.preferencePercent
|
|
102
|
+
);
|
|
103
|
+
assertWilsonMatches(
|
|
104
|
+
`${label}.preferenceWilson95`,
|
|
105
|
+
expectedCategory.preferenceWilson95,
|
|
106
|
+
actualCategory.preferenceWilson95
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function assertStatisticsMatch(expected: BiasStatisticsV2, actual: BiasStatisticsV2): void {
|
|
112
|
+
if (expected.analyzedCount !== actual.analyzedCount) {
|
|
113
|
+
fail(
|
|
114
|
+
`statistics.analyzedCount: stored ${actual.analyzedCount}, recomputed ${expected.analyzedCount}`
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
if (expected.preferenceCount !== actual.preferenceCount) {
|
|
118
|
+
fail(
|
|
119
|
+
`statistics.preferenceCount: stored ${actual.preferenceCount}, recomputed ${expected.preferenceCount}`
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
if (expected.tiesCount !== actual.tiesCount) {
|
|
123
|
+
fail(`statistics.tiesCount: stored ${actual.tiesCount}, recomputed ${expected.tiesCount}`);
|
|
124
|
+
}
|
|
125
|
+
assertNumberMatches(
|
|
126
|
+
"statistics.preferencePercent",
|
|
127
|
+
expected.preferencePercent,
|
|
128
|
+
actual.preferencePercent
|
|
129
|
+
);
|
|
130
|
+
assertWilsonMatches(
|
|
131
|
+
"statistics.preferenceWilson95",
|
|
132
|
+
expected.preferenceWilson95,
|
|
133
|
+
actual.preferenceWilson95
|
|
134
|
+
);
|
|
135
|
+
assertCategoryStatisticsMatch(expected.byBiasType, actual.byBiasType);
|
|
136
|
+
assertQuartilesMatch(expected.pairDiffQuartiles, actual.pairDiffQuartiles);
|
|
137
|
+
if (expected.equalTokenPairsCount !== actual.equalTokenPairsCount) {
|
|
138
|
+
fail(
|
|
139
|
+
`statistics.equalTokenPairsCount: stored ${actual.equalTokenPairsCount}, recomputed ${expected.equalTokenPairsCount}`
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
if (expected.equalTokenPreferenceCount !== actual.equalTokenPreferenceCount) {
|
|
143
|
+
fail(
|
|
144
|
+
`statistics.equalTokenPreferenceCount: stored ${actual.equalTokenPreferenceCount}, recomputed ${expected.equalTokenPreferenceCount}`
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
if (
|
|
148
|
+
expected.equalTokenPreferencePercent === null ||
|
|
149
|
+
actual.equalTokenPreferencePercent === null
|
|
150
|
+
) {
|
|
151
|
+
if (expected.equalTokenPreferencePercent !== actual.equalTokenPreferencePercent) {
|
|
152
|
+
fail(
|
|
153
|
+
"statistics.equalTokenPreferencePercent must be null exactly when equalTokenPairsCount is 0 (recomputation disagrees)"
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
} else {
|
|
157
|
+
assertNumberMatches(
|
|
158
|
+
"statistics.equalTokenPreferencePercent",
|
|
159
|
+
expected.equalTokenPreferencePercent,
|
|
160
|
+
actual.equalTokenPreferencePercent
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
assertWilsonMatches(
|
|
164
|
+
"statistics.equalTokenPreferenceWilson95",
|
|
165
|
+
expected.equalTokenPreferenceWilson95,
|
|
166
|
+
actual.equalTokenPreferenceWilson95
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function assertPartialInvariants(report: BiasReportV2): void {
|
|
171
|
+
if (report.selectedCount !== report.analyzedCount + report.failedCount) {
|
|
172
|
+
fail(
|
|
173
|
+
`selectedCount (${report.selectedCount}) must equal analyzedCount (${report.analyzedCount}) + failedCount (${report.failedCount})`
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
if (report.failedPairs.length !== report.failedCount) {
|
|
177
|
+
fail(
|
|
178
|
+
`failedPairs.length (${report.failedPairs.length}) must equal failedCount (${report.failedCount})`
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
if (report.runStatus === "complete" && report.failedCount !== 0) {
|
|
182
|
+
fail(`runStatus is 'complete' but failedCount is ${report.failedCount}`);
|
|
183
|
+
}
|
|
184
|
+
if (report.runStatus === "partial" && report.failedCount === 0) {
|
|
185
|
+
fail("runStatus is 'partial' but failedCount is 0 (no evidence of a partial run)");
|
|
186
|
+
}
|
|
187
|
+
if (report.result.pairsAnalyzed !== report.analyzedCount) {
|
|
188
|
+
fail(
|
|
189
|
+
`result.pairsAnalyzed (${report.result.pairsAnalyzed}) must equal analyzedCount (${report.analyzedCount})`
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
if (report.pairResults.length !== report.analyzedCount) {
|
|
193
|
+
fail(
|
|
194
|
+
`pairResults.length (${report.pairResults.length}) must equal analyzedCount (${report.analyzedCount})`
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export function recomputedBiasScore(pairResults: CrowsPairsPairResult[]): number {
|
|
200
|
+
return pairResults.reduce((sum, p) => sum + p.biasScore, 0) / pairResults.length;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Recomputes every stored statistic from `pairResults` and rejects any artifact
|
|
205
|
+
* whose stored values do not follow from its own raw data (tolerance 1e-9, i.e.
|
|
206
|
+
* far below any human-rounding step). Throws on mismatch.
|
|
207
|
+
*/
|
|
208
|
+
export function assertBiasV2Semantics(data: unknown): BiasReportV2 {
|
|
209
|
+
const report = data as BiasReportV2;
|
|
210
|
+
assertPartialInvariants(report);
|
|
211
|
+
assertNumberMatches("result.score", recomputedBiasScore(report.pairResults), report.result.score);
|
|
212
|
+
assertStatisticsMatch(buildBiasStatistics(report.pairResults), report.statistics);
|
|
213
|
+
return report;
|
|
214
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser-safe subset of `@euconform/core/evidence`.
|
|
3
|
+
*
|
|
4
|
+
* Everything exported here is pure TypeScript with no Node builtins (`node:fs`,
|
|
5
|
+
* `node:path`, `node:url`, `node:crypto`), so it can be bundled into the web app.
|
|
6
|
+
* The Node-only entrypoint (`@euconform/core/evidence`) adds schema loading from
|
|
7
|
+
* disk, artifact writers and hashing on top of the same pure core — in particular
|
|
8
|
+
* the semantic check comes from the same `bias-v2-semantics.ts` module, so there
|
|
9
|
+
* is never a second implementation of "do these numbers follow from the data".
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
export type {
|
|
13
|
+
BiasComparison,
|
|
14
|
+
BiasComparisonPaired,
|
|
15
|
+
BiasPairChange,
|
|
16
|
+
BiasRunSummary,
|
|
17
|
+
BiasTypeComparison,
|
|
18
|
+
CompareBiasRunsOptions,
|
|
19
|
+
} from "./bias-comparison";
|
|
20
|
+
export type {
|
|
21
|
+
BiasStatisticsV2,
|
|
22
|
+
CategoryPreferenceStatistics,
|
|
23
|
+
Quartiles,
|
|
24
|
+
WilsonInterval,
|
|
25
|
+
} from "./bias-statistics";
|
|
26
|
+
export type {
|
|
27
|
+
BiasReportV2,
|
|
28
|
+
BiasReportV2Provenance,
|
|
29
|
+
BiasReportV2Sampling,
|
|
30
|
+
} from "./types";
|
|
31
|
+
|
|
32
|
+
export { compareBiasRuns, mcnemarExactPValue, summarizeBiasRun } from "./bias-comparison";
|
|
33
|
+
export { formatBiasComparisonMarkdown } from "./bias-comparison-markdown";
|
|
34
|
+
export { buildBiasStatistics, quartilesType7, wilson95, WILSON_Z95 } from "./bias-statistics";
|
|
35
|
+
export { assertBiasV2Semantics, recomputedBiasScore } from "./bias-v2-semantics";
|
package/src/evidence/bundle.ts
CHANGED
|
@@ -10,6 +10,7 @@ interface BuildBundleOptions {
|
|
|
10
10
|
report: ArtifactInput;
|
|
11
11
|
aibom?: ArtifactInput;
|
|
12
12
|
ci?: ArtifactInput;
|
|
13
|
+
bias?: ArtifactInput;
|
|
13
14
|
summary?: ArtifactInput;
|
|
14
15
|
tool: { name: string; version: string };
|
|
15
16
|
target: { name: string; rootPath: string };
|
|
@@ -53,6 +54,7 @@ export function buildBundleManifest(opts: BuildBundleOptions): ScanBundle {
|
|
|
53
54
|
|
|
54
55
|
if (opts.aibom) artifacts.push(makeArtifactRef("aibom", opts.aibom, false));
|
|
55
56
|
if (opts.ci) artifacts.push(makeArtifactRef("ci", opts.ci, false));
|
|
57
|
+
if (opts.bias) artifacts.push(makeArtifactRef("bias", opts.bias, false));
|
|
56
58
|
if (opts.summary) artifacts.push(makeArtifactRef("summary", opts.summary, false));
|
|
57
59
|
|
|
58
60
|
return {
|
package/src/evidence/index.ts
CHANGED
|
@@ -9,6 +9,10 @@ export type {
|
|
|
9
9
|
AiBillOfMaterials,
|
|
10
10
|
AppType,
|
|
11
11
|
AssessmentHint,
|
|
12
|
+
BiasReport,
|
|
13
|
+
BiasReportV2,
|
|
14
|
+
BiasReportV2Provenance,
|
|
15
|
+
BiasReportV2Sampling,
|
|
12
16
|
BomComponent,
|
|
13
17
|
BomComponentKind,
|
|
14
18
|
BundleArtifactRef,
|
|
@@ -18,6 +22,7 @@ export type {
|
|
|
18
22
|
ConfidenceLevel,
|
|
19
23
|
DetectedSignal,
|
|
20
24
|
DetectorContext,
|
|
25
|
+
ExternalObservation,
|
|
21
26
|
FailOnLevel,
|
|
22
27
|
FileProvenance,
|
|
23
28
|
GapCounts,
|
|
@@ -36,16 +41,42 @@ export type {
|
|
|
36
41
|
SignalDetector,
|
|
37
42
|
SignalEvidence,
|
|
38
43
|
} from "./types";
|
|
44
|
+
export type {
|
|
45
|
+
BiasStatisticsV2,
|
|
46
|
+
CategoryPreferenceStatistics,
|
|
47
|
+
Quartiles,
|
|
48
|
+
WilsonInterval,
|
|
49
|
+
} from "./bias-statistics";
|
|
50
|
+
export type {
|
|
51
|
+
BiasComparison,
|
|
52
|
+
BiasComparisonPaired,
|
|
53
|
+
BiasPairChange,
|
|
54
|
+
BiasRunSummary,
|
|
55
|
+
BiasTypeComparison,
|
|
56
|
+
CompareBiasRunsOptions,
|
|
57
|
+
} from "./bias-comparison";
|
|
58
|
+
export type { BiasValidationResult } from "./validate";
|
|
39
59
|
|
|
40
60
|
export { generateScanOutput } from "./output";
|
|
41
61
|
export { generateSummaryMarkdown } from "./markdown";
|
|
42
62
|
export { buildBundleManifest } from "./bundle";
|
|
43
63
|
export { sha256Hex } from "./hash";
|
|
64
|
+
export { buildBiasStatistics, quartilesType7, wilson95, WILSON_Z95 } from "./bias-statistics";
|
|
65
|
+
export {
|
|
66
|
+
compareBiasRuns,
|
|
67
|
+
mcnemarExactPValue,
|
|
68
|
+
summarizeBiasRun,
|
|
69
|
+
} from "./bias-comparison";
|
|
70
|
+
export { formatBiasComparisonMarkdown } from "./bias-comparison-markdown";
|
|
71
|
+
export { validateBiasReportV2, isPartialRun } from "./validate-bias-v2";
|
|
44
72
|
export {
|
|
45
73
|
getSchemaMajorVersion,
|
|
46
74
|
validateAiBillOfMaterials,
|
|
75
|
+
validateBiasArtifact,
|
|
76
|
+
validateBiasReport,
|
|
47
77
|
validateCiReport,
|
|
48
78
|
validateEcefJsonDocument,
|
|
79
|
+
validateExternalObservation,
|
|
49
80
|
validateScanBundle,
|
|
50
81
|
validateScanReport,
|
|
51
82
|
} from "./validate";
|
package/src/evidence/types.ts
CHANGED
|
@@ -5,6 +5,9 @@
|
|
|
5
5
|
* Classification is done by the wizard from human input, not by the scanner.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
import type { CrowsPairsFailedPair, CrowsPairsPairResult, SequenceLogProbMethod } from "../types";
|
|
9
|
+
import type { BiasStatisticsV2 } from "./bias-statistics";
|
|
10
|
+
|
|
8
11
|
// ---------------------------------------------------------------------------
|
|
9
12
|
// Signal types
|
|
10
13
|
// ---------------------------------------------------------------------------
|
|
@@ -312,11 +315,143 @@ export interface ScanOutput {
|
|
|
312
315
|
summaryMarkdown: string;
|
|
313
316
|
}
|
|
314
317
|
|
|
318
|
+
// ---------------------------------------------------------------------------
|
|
319
|
+
// Bias report (euconform.bias.v1)
|
|
320
|
+
// ---------------------------------------------------------------------------
|
|
321
|
+
|
|
322
|
+
export interface BiasReport {
|
|
323
|
+
schemaVersion: "euconform.bias.v1";
|
|
324
|
+
generatedAt: string;
|
|
325
|
+
model: string;
|
|
326
|
+
dataset: string;
|
|
327
|
+
result: {
|
|
328
|
+
score: number;
|
|
329
|
+
method: string;
|
|
330
|
+
pairsAnalyzed: number;
|
|
331
|
+
stereotypicalPreference: number;
|
|
332
|
+
};
|
|
333
|
+
/** Absent in runs written before sampling was recorded; null fields mean the full dataset. */
|
|
334
|
+
sampling?: {
|
|
335
|
+
datasetSize: number;
|
|
336
|
+
maxPairs: number | null;
|
|
337
|
+
seed: number | null;
|
|
338
|
+
};
|
|
339
|
+
limitations?: string[];
|
|
340
|
+
/** Provenance of a user-supplied question catalog. Absent when a bundled dataset was used. */
|
|
341
|
+
catalog?: {
|
|
342
|
+
name: string;
|
|
343
|
+
version: string;
|
|
344
|
+
sha256: string;
|
|
345
|
+
pairCount: number;
|
|
346
|
+
};
|
|
347
|
+
/** Per-category aggregates over pairResults. Optional so pre-existing artifacts stay valid. */
|
|
348
|
+
byBiasType?: {
|
|
349
|
+
biasType: string;
|
|
350
|
+
pairsCount: number;
|
|
351
|
+
stereotypePreferenceCount: number;
|
|
352
|
+
stereotypePreferencePercent: number;
|
|
353
|
+
averageBiasScore: number;
|
|
354
|
+
}[];
|
|
355
|
+
/** One entry per analysed pair, enabling third-party recomputation of the score. */
|
|
356
|
+
pairResults?: {
|
|
357
|
+
pairId: number | null;
|
|
358
|
+
stereotype: string;
|
|
359
|
+
antiStereotype: string;
|
|
360
|
+
biasType: string | null;
|
|
361
|
+
stereotypeLogProb: number;
|
|
362
|
+
antiStereotypeLogProb: number;
|
|
363
|
+
biasScore: number;
|
|
364
|
+
biasDirection: "stereotype" | "anti-stereotype" | "neutral";
|
|
365
|
+
method: string;
|
|
366
|
+
}[];
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// ---------------------------------------------------------------------------
|
|
370
|
+
// Bias report v2 (euconform.bias.v2) — Messmotor-Sanierung Task 3.
|
|
371
|
+
//
|
|
372
|
+
// v1 is demoted to legacy (see validateBiasArtifact): its "logprobs_exact"
|
|
373
|
+
// method label sat on artifacts that, before Tasks 1-2, measured wall-clock
|
|
374
|
+
// nanoseconds rather than log-probabilities. v2 makes provenance mandatory
|
|
375
|
+
// and statistics self-checkable (the shared semantic validator recomputes
|
|
376
|
+
// them from pairResults and rejects any mismatch).
|
|
377
|
+
// ---------------------------------------------------------------------------
|
|
378
|
+
|
|
379
|
+
/** Mandatory measurement provenance — v2's core honesty guarantee. */
|
|
380
|
+
export interface BiasReportV2Provenance {
|
|
381
|
+
/** Single supported engine (Ollama cannot measure sentence log-probabilities). */
|
|
382
|
+
engine: "transformers";
|
|
383
|
+
modelId: string;
|
|
384
|
+
/** Resolved immutable commit SHA (40 hex chars), never a mutable ref like "main". */
|
|
385
|
+
modelRevision: string;
|
|
386
|
+
/** The tokenizer actually used; equals modelId unless a distinct tokenizer repo was loaded. */
|
|
387
|
+
tokenizerId: string;
|
|
388
|
+
transformersVersion: string;
|
|
389
|
+
/**
|
|
390
|
+
* Installed onnxruntime-node version. Ergebnisrelevant: a native ONNX Runtime version
|
|
391
|
+
* bump (1.14.0 -> 1.24.3 across the @xenova/transformers -> @huggingface/transformers
|
|
392
|
+
* migration) shifted sequence log-probabilities at the 1e-6 relative level even with
|
|
393
|
+
* byte-identical model weights and dtype (Messmotor-Sanierung Task 4). Must be resolved
|
|
394
|
+
* from the installed package at measurement time; an artifact that cannot resolve it is
|
|
395
|
+
* a loud failure, not a placeholder.
|
|
396
|
+
*/
|
|
397
|
+
onnxruntimeVersion: string;
|
|
398
|
+
/** Numeric precision used for scoring, e.g. "fp32" or "q8" — ergebnisrelevant (Task 1: up to 11.9% deviation quantized vs fp32). */
|
|
399
|
+
dtype: string;
|
|
400
|
+
backend: "node-onnx";
|
|
401
|
+
templateStrategy: "none" | "fixed_template_span";
|
|
402
|
+
/** Present only when templateStrategy is "fixed_template_span". */
|
|
403
|
+
promptText?: string;
|
|
404
|
+
promptSha256?: string;
|
|
405
|
+
contextTokenHash?: string;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
export interface BiasReportV2Sampling {
|
|
409
|
+
mode: "seeded_sample" | "full";
|
|
410
|
+
datasetSize: number;
|
|
411
|
+
maxPairs: number | null;
|
|
412
|
+
seed: number | null;
|
|
413
|
+
/** SHA-256 over the compact JSON of the full dataset that was sampled from. */
|
|
414
|
+
datasetSha256: string;
|
|
415
|
+
/** Pair ids in selection order — the exact reproducible sample. */
|
|
416
|
+
pairIds: (number | null)[];
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
export interface BiasReportV2 {
|
|
420
|
+
schemaVersion: "euconform.bias.v2";
|
|
421
|
+
generatedAt: string;
|
|
422
|
+
model: string;
|
|
423
|
+
dataset: string;
|
|
424
|
+
provenance: BiasReportV2Provenance;
|
|
425
|
+
sampling: BiasReportV2Sampling;
|
|
426
|
+
result: {
|
|
427
|
+
score: number;
|
|
428
|
+
method: SequenceLogProbMethod;
|
|
429
|
+
pairsAnalyzed: number;
|
|
430
|
+
};
|
|
431
|
+
runStatus: "complete" | "partial";
|
|
432
|
+
selectedCount: number;
|
|
433
|
+
analyzedCount: number;
|
|
434
|
+
failedCount: number;
|
|
435
|
+
failedPairs: CrowsPairsFailedPair[];
|
|
436
|
+
/** Self-checkable: the semantic validator recomputes this from pairResults and rejects on mismatch (tolerance 1e-9, unrounded). */
|
|
437
|
+
statistics: BiasStatisticsV2;
|
|
438
|
+
/** One entry per analysed pair — the raw values statistics/validation recompute from. */
|
|
439
|
+
pairResults: CrowsPairsPairResult[];
|
|
440
|
+
limitations?: string[];
|
|
441
|
+
/** Provenance of a user-supplied question catalog. Absent when a bundled dataset was used. */
|
|
442
|
+
catalog?: {
|
|
443
|
+
name: string;
|
|
444
|
+
version: string;
|
|
445
|
+
sha256: string;
|
|
446
|
+
pairCount: number;
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
|
|
315
450
|
// ---------------------------------------------------------------------------
|
|
316
451
|
// Bundle manifest (euconform.bundle.v1)
|
|
317
452
|
// ---------------------------------------------------------------------------
|
|
318
453
|
|
|
319
|
-
export type BundleArtifactRole = "report" | "aibom" | "ci" | "summary";
|
|
454
|
+
export type BundleArtifactRole = "report" | "aibom" | "ci" | "summary" | "bias";
|
|
320
455
|
|
|
321
456
|
export interface BundleArtifactRef {
|
|
322
457
|
role: BundleArtifactRole;
|
|
@@ -334,3 +469,71 @@ export interface ScanBundle {
|
|
|
334
469
|
target: { name: string; rootPath: string };
|
|
335
470
|
artifacts: BundleArtifactRef[];
|
|
336
471
|
}
|
|
472
|
+
|
|
473
|
+
// ---------------------------------------------------------------------------
|
|
474
|
+
// External observations (euconform.external-observation.v1)
|
|
475
|
+
// ---------------------------------------------------------------------------
|
|
476
|
+
|
|
477
|
+
/**
|
|
478
|
+
* Provider-neutral envelope for an externally produced observation of a
|
|
479
|
+
* machine-readable declaration (rights, AI use, licence metadata, robots policy).
|
|
480
|
+
*
|
|
481
|
+
* It records what was visible at a point in time. It is NOT a licence, NOT an
|
|
482
|
+
* ownership determination, NOT proof of use and NOT permission — `result` only
|
|
483
|
+
* says whether a declaration was visible, and `unknown` must never be read as
|
|
484
|
+
* permission. The shape intentionally has no field that could express permission.
|
|
485
|
+
*/
|
|
486
|
+
export interface ExternalObservation {
|
|
487
|
+
schemaVersion: "euconform.external-observation.v1";
|
|
488
|
+
/** Provider-scoped unique id, so the exact record can be cited and re-fetched. */
|
|
489
|
+
observationId: string;
|
|
490
|
+
/** When the provider observed the declaration (not when the artifact was exported). */
|
|
491
|
+
observedAt: string;
|
|
492
|
+
/** Who produced the observation. Identification only; it does not change the meaning. */
|
|
493
|
+
provider: {
|
|
494
|
+
name: string;
|
|
495
|
+
version: string;
|
|
496
|
+
endpoint?: string;
|
|
497
|
+
contact?: string;
|
|
498
|
+
};
|
|
499
|
+
subject: {
|
|
500
|
+
kind: "url" | "repository" | "dataset" | "artifact";
|
|
501
|
+
identifier: string;
|
|
502
|
+
/** When the subject content was fetched; bounds the validity of the statement. */
|
|
503
|
+
retrievedAt: string;
|
|
504
|
+
/** Optional hash of the observed content. */
|
|
505
|
+
contentSha256?: string;
|
|
506
|
+
};
|
|
507
|
+
observation: {
|
|
508
|
+
kind:
|
|
509
|
+
| "rights-declaration"
|
|
510
|
+
| "ai-use-declaration"
|
|
511
|
+
| "license-metadata"
|
|
512
|
+
| "robots-policy"
|
|
513
|
+
| "terms-of-service";
|
|
514
|
+
/** Query vocabulary of the provider (e.g. ai-input, ai-train). Free-form on purpose. */
|
|
515
|
+
intendedUse: string[];
|
|
516
|
+
/**
|
|
517
|
+
* `declared` = a supported machine-readable declaration was visible — NOT that
|
|
518
|
+
* the use is permitted. `unknown` must never be converted into permission.
|
|
519
|
+
*/
|
|
520
|
+
result: "declared" | "not-declared" | "unknown" | "not-applicable";
|
|
521
|
+
/** Present exactly when `result` is `declared`, so the claim stays checkable. */
|
|
522
|
+
declaration?: {
|
|
523
|
+
text: string;
|
|
524
|
+
sha256: string;
|
|
525
|
+
url?: string;
|
|
526
|
+
license?: string;
|
|
527
|
+
};
|
|
528
|
+
};
|
|
529
|
+
/** Mandatory coverage limits; an observation without stated limits is not reviewable. */
|
|
530
|
+
limitations: string[];
|
|
531
|
+
/** Optional integrity proof. Unsigned observations are valid but must be treated as unverified. */
|
|
532
|
+
signature?: {
|
|
533
|
+
algorithm: string;
|
|
534
|
+
keyId: string;
|
|
535
|
+
value: string;
|
|
536
|
+
signedPayloadSha256: string;
|
|
537
|
+
verifyUrl?: string;
|
|
538
|
+
};
|
|
539
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The ONE validator entrypoint for `euconform.bias.v2` artifacts
|
|
3
|
+
* (Messmotor-Sanierung Task 3). Both `euconform validate` and `euconform verify`
|
|
4
|
+
* call this — before this task they used different validators (AJV-only vs. the
|
|
5
|
+
* lenient hand-rolled `validateBiasReport`) that could disagree on the same file.
|
|
6
|
+
*
|
|
7
|
+
* Validation has two layers:
|
|
8
|
+
* 1. Schema shape — delegated to the AJV schema (`bias-v2.schema.json` via
|
|
9
|
+
* `packages/core/src/validation/schema-validator.ts`), so there is exactly
|
|
10
|
+
* one source of truth for "what fields must exist". Node-only (reads the
|
|
11
|
+
* schema from disk); the browser calls `assertBiasV2Semantics` from
|
|
12
|
+
* `@euconform/core/evidence/browser` for layer 2.
|
|
13
|
+
* 2. Semantic recomputation — every statistic, and the partial-run invariants,
|
|
14
|
+
* are recomputed from the raw `pairResults` and compared against the stored
|
|
15
|
+
* values with an absolute tolerance of 1e-9 (ambiguity resolution #2:
|
|
16
|
+
* statistics are stored UNROUNDED; rounding only happens in the Markdown
|
|
17
|
+
* report, so a tolerance far below any human-rounding step is meaningful).
|
|
18
|
+
* A mismatch means the artifact's numbers do not follow from its own raw
|
|
19
|
+
* data — e.g. a manipulated Wilson bound — and the artifact is rejected
|
|
20
|
+
* with a message naming exactly which field disagreed.
|
|
21
|
+
* This layer lives in `bias-v2-semantics.ts` so it can also run in the browser.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import { validate as validateAgainstSchema } from "../validation/schema-validator";
|
|
25
|
+
import { assertBiasV2Semantics } from "./bias-v2-semantics";
|
|
26
|
+
import type { BiasReportV2 } from "./types";
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* `verify --strict` rejects a partial run outright (ambiguity resolution #3):
|
|
30
|
+
* a diagnostic run is, by definition, not the complete measurement a strict
|
|
31
|
+
* check demands. Non-strict callers get this back as a plain boolean instead.
|
|
32
|
+
*/
|
|
33
|
+
export function isPartialRun(report: BiasReportV2): boolean {
|
|
34
|
+
return report.runStatus === "partial";
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function validateBiasReportV2(data: unknown): BiasReportV2 {
|
|
38
|
+
const schemaResult = validateAgainstSchema(data);
|
|
39
|
+
if (!schemaResult.valid) {
|
|
40
|
+
const messages = schemaResult.errors.map((e) => `${e.path || "/"} ${e.message}`).join("; ");
|
|
41
|
+
throw new Error(
|
|
42
|
+
`Invalid euconform.bias.v2 artifact: ${messages || "schema validation failed"}`
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return assertBiasV2Semantics(data);
|
|
47
|
+
}
|