@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.
- package/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +46 -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/output.d.ts.map +1 -1
- package/dist/evidence/output.js +1 -0
- package/dist/evidence/types.d.ts +183 -3
- 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 +96 -11
- 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/sbom/component-mapper.d.ts +26 -0
- package/dist/sbom/component-mapper.d.ts.map +1 -0
- package/dist/sbom/component-mapper.js +83 -0
- package/dist/sbom/cyclonedx-import.d.ts +34 -0
- package/dist/sbom/cyclonedx-import.d.ts.map +1 -0
- package/dist/sbom/cyclonedx-import.js +131 -0
- package/dist/sbom/index.d.ts +6 -0
- package/dist/sbom/index.d.ts.map +1 -0
- package/dist/sbom/index.js +4 -0
- package/dist/sbom/known-packages.d.ts +13 -0
- package/dist/sbom/known-packages.d.ts.map +1 -0
- package/dist/sbom/known-packages.js +129 -0
- package/dist/sbom/purl.d.ts +14 -0
- package/dist/sbom/purl.d.ts.map +1 -0
- package/dist/sbom/purl.js +56 -0
- package/dist/sbom/types.d.ts +39 -0
- package/dist/sbom/types.d.ts.map +1 -0
- package/dist/sbom/types.js +5 -0
- 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/index.d.ts +2 -0
- package/dist/validation/index.d.ts.map +1 -0
- package/dist/validation/index.js +1 -0
- package/dist/validation/schema-validator.d.ts +18 -0
- package/dist/validation/schema-validator.d.ts.map +1 -0
- package/dist/validation/schema-validator.js +105 -0
- package/package.json +9 -3
- 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/output.ts +1 -0
- package/src/evidence/types.ts +213 -3
- package/src/evidence/validate-bias-v2.ts +47 -0
- package/src/evidence/validate.ts +151 -17
- 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/sbom/component-mapper.ts +107 -0
- package/src/sbom/cyclonedx-import.ts +198 -0
- package/src/sbom/index.ts +12 -0
- package/src/sbom/known-packages.ts +142 -0
- package/src/sbom/purl.ts +66 -0
- package/src/sbom/types.ts +34 -0
- 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/index.ts +7 -0
- package/src/validation/schema-validator.ts +147 -0
- 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/fixtures/cyclonedx-minimal.json +13 -0
- package/tests/fixtures/cyclonedx-mixed.json +17 -0
- package/tests/fixtures/cyclonedx-ml-bom.json +25 -0
- 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/sbom/component-mapper.test.ts +258 -0
- package/tests/sbom/cyclonedx-import.test.ts +329 -0
- package/tests/sbom/purl.test.ts +102 -0
- package/tests/stilled-ollama-paths.test.ts +76 -0
- package/tests/validation/schema-validator.test.ts +219 -0
- 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:
|
|
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 {
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,8EAA8E;AAC9E,MAAM,MAAM,SAAS,GAAG,cAAc,GAAG,MAAM,GAAG,SAAS,GAAG,SAAS,CAAC;AAExE,wDAAwD;AACxD,MAAM,MAAM,gBAAgB,GACxB,0BAA0B,GAC1B,yBAAyB,GACzB,oBAAoB,GACpB,oBAAoB,GACpB,oBAAoB,GACpB,iBAAiB,GACjB,kBAAkB,GAClB,mBAAmB,CAAC;AAExB,wCAAwC;AACxC,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,UAAU,EAAE,CAAC;CACvB;AAED,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,CAAC,EAAE,SAAS,CAAC;CAC3B;AAED,uCAAuC;AACvC,MAAM,WAAW,UAAU;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,oCAAoC;AACpC,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,SAAS,CAAC;IACjB,QAAQ,CAAC,EAAE,gBAAgB,CAAC;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,QAAQ,EAAE,CAAC;IAClB,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,UAAU,EAAE,MAAM,EAAE,CAAC;CACtB;AAED,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,SAAS,GAAG,UAAU,GAAG,MAAM,CAAC;IACtC,OAAO,EAAE,MAAM,CAAC;IAChB,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,6BAA6B;AAC7B,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,OAAO,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -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.
|
|
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
|
-
"./
|
|
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
|
-
"@
|
|
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
|
+
}
|