@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
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import { basename, extname } from "node:path";
|
|
2
|
+
import type { AiBillOfMaterials, AibomMetadata, BomComponent } from "../evidence/types";
|
|
3
|
+
import type { ValidationResult } from "../validation/schema-validator";
|
|
4
|
+
import { validate } from "../validation/schema-validator";
|
|
5
|
+
import { mapComponent } from "./component-mapper";
|
|
6
|
+
import type { CycloneDxBom } from "./types";
|
|
7
|
+
import { SUPPORTED_SPEC_VERSIONS } from "./types";
|
|
8
|
+
|
|
9
|
+
export interface ImportWarning {
|
|
10
|
+
component: string;
|
|
11
|
+
message: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface ImportSourceInfo {
|
|
15
|
+
bomFormat: "CycloneDX";
|
|
16
|
+
specVersion: string;
|
|
17
|
+
projectNameSource: "metadata.component.name" | "sourcePath" | "fallback";
|
|
18
|
+
importTool?: string;
|
|
19
|
+
originalTimestamp?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface ImportSummary {
|
|
23
|
+
totalComponents: number;
|
|
24
|
+
filteredByScope: number;
|
|
25
|
+
aiRelevant: number;
|
|
26
|
+
skipped: number;
|
|
27
|
+
duplicatesRemoved: number;
|
|
28
|
+
byKind: Record<string, number>;
|
|
29
|
+
warnings: ImportWarning[];
|
|
30
|
+
source: ImportSourceInfo;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface ImportOptions {
|
|
34
|
+
scope?: "all" | "production";
|
|
35
|
+
sourcePath?: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface CycloneDxImportResult {
|
|
39
|
+
aibom: AiBillOfMaterials;
|
|
40
|
+
summary: ImportSummary;
|
|
41
|
+
validation: ValidationResult;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function importCycloneDx(sbom: unknown, options?: ImportOptions): CycloneDxImportResult {
|
|
45
|
+
const bom = parseBom(sbom);
|
|
46
|
+
const scope = options?.scope ?? "all";
|
|
47
|
+
|
|
48
|
+
const allComponents = bom.components ?? [];
|
|
49
|
+
const warnings: ImportWarning[] = [];
|
|
50
|
+
const totalComponents = allComponents.length;
|
|
51
|
+
|
|
52
|
+
const inScope =
|
|
53
|
+
scope === "production"
|
|
54
|
+
? allComponents.filter((c) => c.scope !== "optional" && c.scope !== "excluded")
|
|
55
|
+
: allComponents;
|
|
56
|
+
const filteredByScope = totalComponents - inScope.length;
|
|
57
|
+
|
|
58
|
+
const mapped: BomComponent[] = [];
|
|
59
|
+
for (const comp of inScope) {
|
|
60
|
+
const mapping = mapComponent(comp);
|
|
61
|
+
if (!mapping) {
|
|
62
|
+
if (!comp.name || comp.name.trim() === "") {
|
|
63
|
+
warnings.push({ component: "(empty)", message: "Skipped: missing or empty name" });
|
|
64
|
+
}
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const version = comp.version || mapping.purlVersion;
|
|
69
|
+
if (!version) {
|
|
70
|
+
warnings.push({ component: comp.name, message: "Missing version field" });
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
mapped.push({
|
|
74
|
+
id: version ? `${mapping.kind}:${comp.name}:${version}` : `${mapping.kind}:${comp.name}`,
|
|
75
|
+
kind: mapping.kind,
|
|
76
|
+
name: comp.name,
|
|
77
|
+
...(version ? { version } : {}),
|
|
78
|
+
source: "sbom-import",
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const seen = new Set<string>();
|
|
83
|
+
let duplicatesRemoved = 0;
|
|
84
|
+
const deduped: BomComponent[] = [];
|
|
85
|
+
for (const comp of mapped) {
|
|
86
|
+
if (seen.has(comp.id)) {
|
|
87
|
+
duplicatesRemoved++;
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
seen.add(comp.id);
|
|
91
|
+
deduped.push(comp);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const byKind: Record<string, number> = {};
|
|
95
|
+
for (const comp of deduped) {
|
|
96
|
+
byKind[comp.kind] = (byKind[comp.kind] ?? 0) + 1;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const { projectName, projectNameSource } = resolveProjectName(bom, options?.sourcePath);
|
|
100
|
+
const importTool = extractToolString(bom);
|
|
101
|
+
const originalTimestamp = bom.metadata?.timestamp;
|
|
102
|
+
|
|
103
|
+
const aibom: AiBillOfMaterials = {
|
|
104
|
+
schemaVersion: "euconform.aibom.v1.1",
|
|
105
|
+
generatedAt: new Date().toISOString(),
|
|
106
|
+
project: { name: projectName, rootPath: "." },
|
|
107
|
+
components: deduped,
|
|
108
|
+
complianceCapabilities: {
|
|
109
|
+
biasEvaluation: false,
|
|
110
|
+
jsonExport: false,
|
|
111
|
+
pdfExport: false,
|
|
112
|
+
loggingInfrastructure: false,
|
|
113
|
+
humanReviewFlow: false,
|
|
114
|
+
incidentHandling: false,
|
|
115
|
+
},
|
|
116
|
+
metadata: buildMetadata(importTool, originalTimestamp),
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
const validation = validate(aibom);
|
|
120
|
+
|
|
121
|
+
return {
|
|
122
|
+
aibom,
|
|
123
|
+
summary: {
|
|
124
|
+
totalComponents,
|
|
125
|
+
filteredByScope,
|
|
126
|
+
aiRelevant: deduped.length,
|
|
127
|
+
skipped: inScope.length - mapped.length,
|
|
128
|
+
duplicatesRemoved,
|
|
129
|
+
byKind,
|
|
130
|
+
warnings,
|
|
131
|
+
source: buildSourceInfo(bom.specVersion, projectNameSource, importTool, originalTimestamp),
|
|
132
|
+
},
|
|
133
|
+
validation,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function resolveProjectName(
|
|
138
|
+
bom: CycloneDxBom,
|
|
139
|
+
sourcePath?: string
|
|
140
|
+
): { projectName: string; projectNameSource: ImportSourceInfo["projectNameSource"] } {
|
|
141
|
+
const metaName = bom.metadata?.component?.name;
|
|
142
|
+
if (metaName) {
|
|
143
|
+
return { projectName: metaName, projectNameSource: "metadata.component.name" };
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (sourcePath) {
|
|
147
|
+
const name = basename(sourcePath, extname(sourcePath));
|
|
148
|
+
return { projectName: name, projectNameSource: "sourcePath" };
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return { projectName: "sbom-import", projectNameSource: "fallback" };
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function extractToolString(bom: CycloneDxBom): string | undefined {
|
|
155
|
+
const tool = bom.metadata?.tools?.[0];
|
|
156
|
+
if (!tool?.name) return undefined;
|
|
157
|
+
return tool.version ? `${tool.name} ${tool.version}` : tool.name;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function buildMetadata(importTool?: string, originalTimestamp?: string): AibomMetadata {
|
|
161
|
+
const meta: AibomMetadata = { importSource: "cyclonedx" };
|
|
162
|
+
if (importTool) meta.importTool = importTool;
|
|
163
|
+
if (originalTimestamp) meta.originalTimestamp = originalTimestamp;
|
|
164
|
+
return meta;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function buildSourceInfo(
|
|
168
|
+
specVersion: string,
|
|
169
|
+
projectNameSource: ImportSourceInfo["projectNameSource"],
|
|
170
|
+
importTool?: string,
|
|
171
|
+
originalTimestamp?: string
|
|
172
|
+
): ImportSourceInfo {
|
|
173
|
+
const info: ImportSourceInfo = { bomFormat: "CycloneDX", specVersion, projectNameSource };
|
|
174
|
+
if (importTool) info.importTool = importTool;
|
|
175
|
+
if (originalTimestamp) info.originalTimestamp = originalTimestamp;
|
|
176
|
+
return info;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function parseBom(sbom: unknown): CycloneDxBom {
|
|
180
|
+
if (typeof sbom !== "object" || sbom === null) {
|
|
181
|
+
throw new Error("Input must be a JSON object");
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const record = sbom as Record<string, unknown>;
|
|
185
|
+
|
|
186
|
+
if (record.bomFormat !== "CycloneDX") {
|
|
187
|
+
throw new Error(`Invalid bomFormat: expected "CycloneDX", got "${String(record.bomFormat)}"`);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const specVersion = String(record.specVersion ?? "");
|
|
191
|
+
if (!SUPPORTED_SPEC_VERSIONS.includes(specVersion as (typeof SUPPORTED_SPEC_VERSIONS)[number])) {
|
|
192
|
+
throw new Error(
|
|
193
|
+
`Unsupported specVersion "${specVersion}". Supported: ${SUPPORTED_SPEC_VERSIONS.join(", ")}`
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return sbom as CycloneDxBom;
|
|
198
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export {
|
|
2
|
+
importCycloneDx,
|
|
3
|
+
type CycloneDxImportResult,
|
|
4
|
+
type ImportSummary,
|
|
5
|
+
type ImportWarning,
|
|
6
|
+
type ImportOptions,
|
|
7
|
+
type ImportSourceInfo,
|
|
8
|
+
} from "./cyclonedx-import";
|
|
9
|
+
export { mapComponent, type ComponentMapping } from "./component-mapper";
|
|
10
|
+
export { lookupKnownPackage, KNOWN_AI_PACKAGES, KNOWN_AI_SCOPES } from "./known-packages";
|
|
11
|
+
export { parsePurl, type ParsedPurl } from "./purl";
|
|
12
|
+
export type { CycloneDxBom, CycloneDxComponent } from "./types";
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import type { BomComponentKind } from "../evidence/types";
|
|
2
|
+
|
|
3
|
+
export const KNOWN_AI_PACKAGES: Record<string, BomComponentKind> = {
|
|
4
|
+
// Models / model runtimes
|
|
5
|
+
"llama.cpp": "model",
|
|
6
|
+
"llama-cpp-python": "model",
|
|
7
|
+
"whisper.cpp": "model",
|
|
8
|
+
ggml: "model",
|
|
9
|
+
ctransformers: "model",
|
|
10
|
+
|
|
11
|
+
// AI Frameworks
|
|
12
|
+
transformers: "ai-framework",
|
|
13
|
+
torch: "ai-framework",
|
|
14
|
+
pytorch: "ai-framework",
|
|
15
|
+
tensorflow: "ai-framework",
|
|
16
|
+
"tensorflow-gpu": "ai-framework",
|
|
17
|
+
jax: "ai-framework",
|
|
18
|
+
keras: "ai-framework",
|
|
19
|
+
"pytorch-lightning": "ai-framework",
|
|
20
|
+
"scikit-learn": "ai-framework",
|
|
21
|
+
sklearn: "ai-framework",
|
|
22
|
+
xgboost: "ai-framework",
|
|
23
|
+
lightgbm: "ai-framework",
|
|
24
|
+
spacy: "ai-framework",
|
|
25
|
+
"huggingface-hub": "ai-framework",
|
|
26
|
+
diffusers: "ai-framework",
|
|
27
|
+
accelerate: "ai-framework",
|
|
28
|
+
peft: "ai-framework",
|
|
29
|
+
trl: "ai-framework",
|
|
30
|
+
onnxruntime: "ai-framework",
|
|
31
|
+
"onnxruntime-gpu": "ai-framework",
|
|
32
|
+
mlflow: "ai-framework",
|
|
33
|
+
|
|
34
|
+
// Embeddings
|
|
35
|
+
"sentence-transformers": "embedding",
|
|
36
|
+
fastembed: "embedding",
|
|
37
|
+
"openai-embeddings": "embedding",
|
|
38
|
+
"cohere-embed": "embedding",
|
|
39
|
+
|
|
40
|
+
// Inference Providers
|
|
41
|
+
openai: "inference-provider",
|
|
42
|
+
anthropic: "inference-provider",
|
|
43
|
+
ollama: "inference-provider",
|
|
44
|
+
vllm: "inference-provider",
|
|
45
|
+
"together-ai": "inference-provider",
|
|
46
|
+
replicate: "inference-provider",
|
|
47
|
+
groq: "inference-provider",
|
|
48
|
+
mistralai: "inference-provider",
|
|
49
|
+
cohere: "inference-provider",
|
|
50
|
+
"google-generativeai": "inference-provider",
|
|
51
|
+
ai21: "inference-provider",
|
|
52
|
+
|
|
53
|
+
// Vector Stores
|
|
54
|
+
pinecone: "vector-store",
|
|
55
|
+
"pinecone-client": "vector-store",
|
|
56
|
+
weaviate: "vector-store",
|
|
57
|
+
"weaviate-client": "vector-store",
|
|
58
|
+
qdrant: "vector-store",
|
|
59
|
+
"qdrant-client": "vector-store",
|
|
60
|
+
milvus: "vector-store",
|
|
61
|
+
pymilvus: "vector-store",
|
|
62
|
+
chromadb: "vector-store",
|
|
63
|
+
"chromadb-client": "vector-store",
|
|
64
|
+
pgvector: "vector-store",
|
|
65
|
+
faiss: "vector-store",
|
|
66
|
+
"faiss-cpu": "vector-store",
|
|
67
|
+
"faiss-gpu": "vector-store",
|
|
68
|
+
|
|
69
|
+
// Datasets
|
|
70
|
+
datasets: "dataset",
|
|
71
|
+
|
|
72
|
+
// Tools / Orchestration
|
|
73
|
+
langchain: "tool",
|
|
74
|
+
"langchain-core": "tool",
|
|
75
|
+
"langchain-community": "tool",
|
|
76
|
+
llamaindex: "tool",
|
|
77
|
+
"llama-index": "tool",
|
|
78
|
+
"semantic-kernel": "tool",
|
|
79
|
+
autogen: "tool",
|
|
80
|
+
crewai: "tool",
|
|
81
|
+
dspy: "tool",
|
|
82
|
+
"haystack-ai": "tool",
|
|
83
|
+
guidance: "tool",
|
|
84
|
+
guardrails: "tool",
|
|
85
|
+
"guardrails-ai": "tool",
|
|
86
|
+
lmql: "tool",
|
|
87
|
+
outlines: "tool",
|
|
88
|
+
|
|
89
|
+
// Scoped packages (npm)
|
|
90
|
+
"@langchain/core": "tool",
|
|
91
|
+
"@langchain/openai": "tool",
|
|
92
|
+
"@langchain/community": "tool",
|
|
93
|
+
"@langchain/anthropic": "tool",
|
|
94
|
+
"@huggingface/transformers": "ai-framework",
|
|
95
|
+
"@huggingface/inference": "inference-provider",
|
|
96
|
+
"@google/generative-ai": "inference-provider",
|
|
97
|
+
"@anthropic-ai/sdk": "inference-provider",
|
|
98
|
+
"@pinecone-database/pinecone": "vector-store",
|
|
99
|
+
"@qdrant/js-client-rest": "vector-store",
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
export const KNOWN_AI_SCOPES: Record<string, BomComponentKind> = {
|
|
103
|
+
"@langchain": "tool",
|
|
104
|
+
"@huggingface": "ai-framework",
|
|
105
|
+
"@tensorflow": "ai-framework",
|
|
106
|
+
"@anthropic-ai": "inference-provider",
|
|
107
|
+
"@pinecone-database": "vector-store",
|
|
108
|
+
"@qdrant": "vector-store",
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Look up a package name in the known AI packages registry.
|
|
113
|
+
* For scoped packages, tries:
|
|
114
|
+
* 1. Exact match (e.g. @langchain/core)
|
|
115
|
+
* 2. Scope match via KNOWN_AI_SCOPES (e.g. @langchain -> tool)
|
|
116
|
+
* 3. Strip scope, check bare name (e.g. @huggingface/transformers -> transformers)
|
|
117
|
+
* Returns the BomComponentKind if found, null otherwise.
|
|
118
|
+
*/
|
|
119
|
+
export function lookupKnownPackage(name: string): BomComponentKind | null {
|
|
120
|
+
const lower = name.toLowerCase();
|
|
121
|
+
|
|
122
|
+
// 1. Exact match
|
|
123
|
+
const exact = KNOWN_AI_PACKAGES[lower];
|
|
124
|
+
if (exact) return exact;
|
|
125
|
+
|
|
126
|
+
// Scoped package handling
|
|
127
|
+
if (lower.startsWith("@") && lower.includes("/")) {
|
|
128
|
+
const scopeEnd = lower.indexOf("/");
|
|
129
|
+
const scope = lower.slice(0, scopeEnd);
|
|
130
|
+
const bareName = lower.slice(scopeEnd + 1);
|
|
131
|
+
|
|
132
|
+
// 2. Known AI scope
|
|
133
|
+
const scopeKind = KNOWN_AI_SCOPES[scope];
|
|
134
|
+
if (scopeKind) return scopeKind;
|
|
135
|
+
|
|
136
|
+
// 3. Strip scope, check bare name
|
|
137
|
+
const bareKind = KNOWN_AI_PACKAGES[bareName];
|
|
138
|
+
if (bareKind) return bareKind;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return null;
|
|
142
|
+
}
|
package/src/sbom/purl.ts
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
export interface ParsedPurl {
|
|
2
|
+
scheme: "pkg";
|
|
3
|
+
type: string;
|
|
4
|
+
namespace?: string;
|
|
5
|
+
name: string;
|
|
6
|
+
version?: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Parse a Package URL (purl) string into its components.
|
|
11
|
+
* Follows the purl spec: pkg:type/namespace/name@version?qualifiers#subpath
|
|
12
|
+
* Returns null for invalid or non-pkg URLs.
|
|
13
|
+
*/
|
|
14
|
+
export function parsePurl(purl: string): ParsedPurl | null {
|
|
15
|
+
if (!purl || !purl.startsWith("pkg:")) return null;
|
|
16
|
+
|
|
17
|
+
// Strip qualifiers and subpath
|
|
18
|
+
let rest = purl.slice(4); // remove "pkg:"
|
|
19
|
+
const hashIdx = rest.indexOf("#");
|
|
20
|
+
if (hashIdx !== -1) rest = rest.slice(0, hashIdx);
|
|
21
|
+
const queryIdx = rest.indexOf("?");
|
|
22
|
+
if (queryIdx !== -1) rest = rest.slice(0, queryIdx);
|
|
23
|
+
|
|
24
|
+
// Split type from the rest: type/...
|
|
25
|
+
const slashIdx = rest.indexOf("/");
|
|
26
|
+
if (slashIdx <= 0) return null;
|
|
27
|
+
|
|
28
|
+
const type = rest.slice(0, slashIdx);
|
|
29
|
+
let remainder = rest.slice(slashIdx + 1);
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
remainder = decodeURIComponent(remainder);
|
|
33
|
+
} catch {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Extract version
|
|
38
|
+
let version: string | undefined;
|
|
39
|
+
const atIdx = remainder.lastIndexOf("@");
|
|
40
|
+
if (atIdx !== -1) {
|
|
41
|
+
version = remainder.slice(atIdx + 1);
|
|
42
|
+
remainder = remainder.slice(0, atIdx);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Split namespace/name (last segment is name, rest is namespace)
|
|
46
|
+
const lastSlash = remainder.lastIndexOf("/");
|
|
47
|
+
let namespace: string | undefined;
|
|
48
|
+
let name: string;
|
|
49
|
+
|
|
50
|
+
if (lastSlash !== -1) {
|
|
51
|
+
namespace = remainder.slice(0, lastSlash);
|
|
52
|
+
name = remainder.slice(lastSlash + 1);
|
|
53
|
+
} else {
|
|
54
|
+
name = remainder;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (!name) return null;
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
scheme: "pkg",
|
|
61
|
+
type,
|
|
62
|
+
namespace: namespace || undefined,
|
|
63
|
+
name,
|
|
64
|
+
version: version || undefined,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal CycloneDX types covering fields relevant to AI component detection.
|
|
3
|
+
* Supports CycloneDX JSON format versions 1.4, 1.5, 1.6.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export interface CycloneDxBom {
|
|
7
|
+
bomFormat: "CycloneDX";
|
|
8
|
+
specVersion: string;
|
|
9
|
+
version?: number;
|
|
10
|
+
metadata?: CycloneDxMetadata;
|
|
11
|
+
components?: CycloneDxComponent[];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface CycloneDxMetadata {
|
|
15
|
+
timestamp?: string;
|
|
16
|
+
tools?: Array<{ name?: string; version?: string }>;
|
|
17
|
+
component?: CycloneDxComponent;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface CycloneDxComponent {
|
|
21
|
+
type: string;
|
|
22
|
+
name: string;
|
|
23
|
+
version?: string;
|
|
24
|
+
purl?: string;
|
|
25
|
+
group?: string;
|
|
26
|
+
description?: string;
|
|
27
|
+
scope?: "required" | "optional" | "excluded";
|
|
28
|
+
properties?: Array<{ name: string; value: string }>;
|
|
29
|
+
/** CycloneDX ML-BOM extensions (1.5+) */
|
|
30
|
+
modelCard?: unknown;
|
|
31
|
+
data?: Array<{ type?: string }>;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export const SUPPORTED_SPEC_VERSIONS = ["1.4", "1.5", "1.6"] as const;
|
|
@@ -32,7 +32,7 @@ const FRAMEWORK_IMPORTS: FrameworkPattern[] = [
|
|
|
32
32
|
id: "ai-framework-transformers",
|
|
33
33
|
name: "Transformers.js",
|
|
34
34
|
importPattern:
|
|
35
|
-
/(?:from\s+['"]
|
|
35
|
+
/(?:from\s+['"](?:@xenova|@huggingface)\/transformers['"]|require\s*\(\s*['"](?:@xenova|@huggingface)\/transformers['"])/,
|
|
36
36
|
},
|
|
37
37
|
{
|
|
38
38
|
id: "ai-framework-vercel-ai",
|
|
@@ -40,7 +40,8 @@ const RULES: DetectionRule[] = [
|
|
|
40
40
|
{
|
|
41
41
|
id: "local-inference-transformers-browser",
|
|
42
42
|
name: "Transformers.js (browser inference)",
|
|
43
|
-
pattern:
|
|
43
|
+
pattern:
|
|
44
|
+
/(?:from\s+['"](?:@xenova|@huggingface)\/transformers['"]|\.pipeline\s*\(|env\.allowLocalModels)/,
|
|
44
45
|
confidence: "medium",
|
|
45
46
|
extensions: [".ts", ".tsx", ".js", ".jsx"],
|
|
46
47
|
},
|
|
@@ -24,6 +24,7 @@ const AI_PACKAGES: Record<string, PackageInfo> = {
|
|
|
24
24
|
"@langchain/core": { category: "ai-framework", label: "LangChain Core" },
|
|
25
25
|
llamaindex: { category: "ai-framework", label: "LlamaIndex" },
|
|
26
26
|
"@xenova/transformers": { category: "ai-framework", label: "Transformers.js" },
|
|
27
|
+
"@huggingface/transformers": { category: "ai-framework", label: "Transformers.js" },
|
|
27
28
|
ai: { category: "ai-framework", label: "Vercel AI SDK" },
|
|
28
29
|
"@huggingface/inference": { category: "ai-framework", label: "HuggingFace Inference" },
|
|
29
30
|
|
package/src/types.ts
CHANGED
|
@@ -65,7 +65,7 @@ export interface FairnessMetric {
|
|
|
65
65
|
export interface BiasTestResult {
|
|
66
66
|
modelId: string;
|
|
67
67
|
timestamp: string;
|
|
68
|
-
method: BiasCalculationMethod;
|
|
68
|
+
method: BiasCalculationMethod | SequenceLogProbMethod;
|
|
69
69
|
engine: "browser" | "ollama";
|
|
70
70
|
protectedAttribute?: string;
|
|
71
71
|
crowsPairsResult?: CrowsPairsBiasResult;
|
|
@@ -92,6 +92,17 @@ export interface CrowsPairsEntry {
|
|
|
92
92
|
/** Bias calculation method types */
|
|
93
93
|
export type BiasCalculationMethod = "logprobs_exact" | "logprobs_fallback_latency";
|
|
94
94
|
|
|
95
|
+
/**
|
|
96
|
+
* How a sentence's log-probability was accumulated for the autoregressive
|
|
97
|
+
* CrowS-Pairs adaptation (Messmotor-Sanierung Task 3). "sequence_logprob_sum" is
|
|
98
|
+
* the base mode (sum of scored-token log-probs, BOS excluded). Public naming:
|
|
99
|
+
* "autoregressive CrowS-Pairs adaptation", never "original methodology" — the
|
|
100
|
+
* original CrowS-Pairs paper used masked-model pseudo-log-likelihood, which this
|
|
101
|
+
* is not. "conditional_sequence_logprob_sum" is reserved for a future template
|
|
102
|
+
* mode that conditions scoring on a fixed prompt/context span; not yet emitted.
|
|
103
|
+
*/
|
|
104
|
+
export type SequenceLogProbMethod = "sequence_logprob_sum" | "conditional_sequence_logprob_sum";
|
|
105
|
+
|
|
95
106
|
/** Model capability cache for log-probability support */
|
|
96
107
|
export interface ModelCapabilityCache {
|
|
97
108
|
model: string;
|
|
@@ -100,17 +111,106 @@ export interface ModelCapabilityCache {
|
|
|
100
111
|
ollamaVersion?: string;
|
|
101
112
|
}
|
|
102
113
|
|
|
114
|
+
/** Result for a single CrowS-Pairs sentence pair. */
|
|
115
|
+
export interface CrowsPairsPairResult {
|
|
116
|
+
pairId: number | null;
|
|
117
|
+
stereotype: string;
|
|
118
|
+
antiStereotype: string;
|
|
119
|
+
biasType: string | null;
|
|
120
|
+
stereotypeLogProb: number;
|
|
121
|
+
antiStereotypeLogProb: number;
|
|
122
|
+
biasScore: number;
|
|
123
|
+
biasDirection: "stereotype" | "anti-stereotype" | "neutral";
|
|
124
|
+
method: SequenceLogProbMethod;
|
|
125
|
+
/** Number of scored tokens (BOS excluded) in the stereotype sentence. */
|
|
126
|
+
stereotypeTokenCount: number;
|
|
127
|
+
/** Number of scored tokens (BOS excluded) in the anti-stereotype sentence. */
|
|
128
|
+
antiStereotypeTokenCount: number;
|
|
129
|
+
/** stereotypeTokenCount - antiStereotypeTokenCount; 0 means the pair is part of the equal-token-length confounder-control subset. */
|
|
130
|
+
tokenCountDelta: number;
|
|
131
|
+
/** SHA-256 over the compact decimal-token-id JSON array of the stereotype sentence's scored tokens. */
|
|
132
|
+
stereotypeTokenHash: string;
|
|
133
|
+
/** SHA-256 over the compact decimal-token-id JSON array of the anti-stereotype sentence's scored tokens. */
|
|
134
|
+
antiStereotypeTokenHash: string;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** Aggregate over all pairs sharing one bias type. */
|
|
138
|
+
export interface CrowsPairsBiasTypeSummary {
|
|
139
|
+
biasType: string;
|
|
140
|
+
pairsCount: number;
|
|
141
|
+
stereotypePreferenceCount: number;
|
|
142
|
+
stereotypePreferencePercent: number;
|
|
143
|
+
averageBiasScore: number;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** A pair that was selected for measurement but whose scoring failed (allowPartial runs only). */
|
|
147
|
+
export interface CrowsPairsFailedPair {
|
|
148
|
+
pairId: number | null;
|
|
149
|
+
reason: string;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Snapshot of the measurement provenance the provider actually reported for a run,
|
|
154
|
+
* echoed by `calculateCrowsPairsBias` so CLI callers (see `run-bias-test.ts`) can build
|
|
155
|
+
* the artifact's top-level `provenance` object from what was really measured instead of
|
|
156
|
+
* a hardcoded literal (Messmotor-Sanierung Task 5 review finding: the CLI once wrote a
|
|
157
|
+
* hardcoded dtype; Task 6 found the same mistake for `templateStrategy`). Deliberately a
|
|
158
|
+
* standalone interface rather than importing `ScoredLogProbProvenance` from
|
|
159
|
+
* `./inference/scored-log-prob-types` — that module imports types FROM this file, and
|
|
160
|
+
* mirroring the handful of fields needed here avoids relying on TypeScript's type-only
|
|
161
|
+
* import erasure to keep the cycle inert.
|
|
162
|
+
*/
|
|
163
|
+
export interface CrowsPairsScoringProvenance {
|
|
164
|
+
dtype: string;
|
|
165
|
+
logitsDtype: string;
|
|
166
|
+
backend: string;
|
|
167
|
+
libraryVersion: string;
|
|
168
|
+
templateStrategy?: "none" | "fixed_template_span";
|
|
169
|
+
/** Present only when templateStrategy is "fixed_template_span". */
|
|
170
|
+
promptText?: string;
|
|
171
|
+
promptSha256?: string;
|
|
172
|
+
/** From the scored result's own `contextTokenHash` — constant across a run's pairs
|
|
173
|
+
* (same fixed context every call), so one representative sample is the whole run's value. */
|
|
174
|
+
contextTokenHash?: string;
|
|
175
|
+
}
|
|
176
|
+
|
|
103
177
|
/** CrowS-Pairs bias calculation result */
|
|
104
178
|
export interface CrowsPairsBiasResult {
|
|
105
179
|
score: number;
|
|
106
|
-
method:
|
|
180
|
+
method: SequenceLogProbMethod;
|
|
107
181
|
pairsAnalyzed: number;
|
|
108
182
|
stereotypicalPreference: number; // percentage
|
|
183
|
+
/** One entry per analysed pair. Empty only if nothing could be analysed. */
|
|
184
|
+
pairResults: CrowsPairsPairResult[];
|
|
185
|
+
/** Per-category aggregates. The whole point: the overall score hides these. */
|
|
186
|
+
byBiasType: CrowsPairsBiasTypeSummary[];
|
|
187
|
+
/**
|
|
188
|
+
* "complete": every selected pair was analysed. "partial": at least one pair
|
|
189
|
+
* failed and `allowPartial` was set — this run is a diagnostic, not a full
|
|
190
|
+
* measurement. Fail-closed by default: without `allowPartial`, any failure
|
|
191
|
+
* aborts the whole run rather than producing a "partial" result.
|
|
192
|
+
*/
|
|
193
|
+
runStatus: "complete" | "partial";
|
|
194
|
+
/** Pairs selected for measurement after dataset-extraction filtering. */
|
|
195
|
+
selectedCount: number;
|
|
196
|
+
/** Pairs successfully measured; the statistical denominator for score/preference. */
|
|
197
|
+
analyzedCount: number;
|
|
198
|
+
/** Pairs selected but that failed measurement (only possible with allowPartial). */
|
|
199
|
+
failedCount: number;
|
|
200
|
+
/** One entry per failed pair. Invariant: selectedCount === analyzedCount + failedCount. */
|
|
201
|
+
failedPairs: CrowsPairsFailedPair[];
|
|
109
202
|
metadata: {
|
|
110
203
|
engine: "browser" | "ollama";
|
|
111
204
|
model: string;
|
|
112
205
|
timestamp: string;
|
|
113
206
|
};
|
|
207
|
+
/**
|
|
208
|
+
* The provider's actual scoring provenance (from the first successfully-measured
|
|
209
|
+
* pair — the fixed template context, if any, does not vary across pairs in one run).
|
|
210
|
+
* Undefined only if analyzedCount is 0, which `calculateCrowsPairsBias` already
|
|
211
|
+
* refuses to return (see "No valid bias scores calculated" throw).
|
|
212
|
+
*/
|
|
213
|
+
scoringProvenance?: CrowsPairsScoringProvenance;
|
|
114
214
|
}
|
|
115
215
|
|
|
116
216
|
/** Configuration for bias test execution */
|
|
@@ -118,6 +218,12 @@ export interface BiasTestConfig {
|
|
|
118
218
|
dataset: CrowsPairsEntry[];
|
|
119
219
|
model: string;
|
|
120
220
|
engine: "browser" | "ollama";
|
|
221
|
+
/**
|
|
222
|
+
* Diagnostic mode: a pair that fails measurement is recorded in `failedPairs`
|
|
223
|
+
* instead of aborting the whole run. NOT suitable as a complete measurement —
|
|
224
|
+
* see `runStatus: "partial"`. Default: false (fail-closed).
|
|
225
|
+
*/
|
|
226
|
+
allowPartial?: boolean;
|
|
121
227
|
}
|
|
122
228
|
|
|
123
229
|
/** Annex IV Technical Documentation Report */
|