@euconform/cli 1.0.0 → 1.1.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/README.md +136 -0
- package/dist/browser-inference-AWHXG2TB.js +13 -0
- package/dist/chunk-AYXSWY3I.js +11 -0
- package/dist/chunk-F6VBKHY4.js +186 -0
- package/dist/chunk-FEBJZMWY.js +175 -0
- package/dist/chunk-JQTB43B2.js +373 -0
- package/dist/chunk-JSBRDJBE.js +30 -0
- package/dist/index.js +588 -386
- package/dist/ollama-client-AJAW266L.js +17 -0
- package/dist/run-bias-test-S6DIKHIY.js +10 -0
- package/dist/severity-5EZAEKDS.js +7 -0
- package/package.json +13 -7
package/dist/index.js
CHANGED
|
@@ -1,29 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
16
|
-
}
|
|
17
|
-
return to;
|
|
18
|
-
};
|
|
19
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
20
|
-
// If the importer is in node compatibility mode or this is not an ESM
|
|
21
|
-
// file that has been converted to a CommonJS file using a Babel-
|
|
22
|
-
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
23
|
-
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
24
|
-
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
25
|
-
mod
|
|
26
|
-
));
|
|
2
|
+
import {
|
|
3
|
+
runBiasTest
|
|
4
|
+
} from "./chunk-F6VBKHY4.js";
|
|
5
|
+
import "./chunk-FEBJZMWY.js";
|
|
6
|
+
import "./chunk-JQTB43B2.js";
|
|
7
|
+
import {
|
|
8
|
+
formatBiasSeverity
|
|
9
|
+
} from "./chunk-AYXSWY3I.js";
|
|
10
|
+
import {
|
|
11
|
+
__commonJS,
|
|
12
|
+
__toESM
|
|
13
|
+
} from "./chunk-JSBRDJBE.js";
|
|
27
14
|
|
|
28
15
|
// ../../node_modules/.pnpm/ignore@7.0.5/node_modules/ignore/index.js
|
|
29
16
|
var require_ignore = __commonJS({
|
|
@@ -484,11 +471,137 @@ var require_ignore = __commonJS({
|
|
|
484
471
|
});
|
|
485
472
|
|
|
486
473
|
// src/index.ts
|
|
487
|
-
import { defineCommand as
|
|
474
|
+
import { defineCommand as defineCommand4, runMain } from "citty";
|
|
475
|
+
|
|
476
|
+
// src/commands/bias.ts
|
|
477
|
+
import { mkdir, stat, writeFile } from "fs/promises";
|
|
478
|
+
import { resolve } from "path";
|
|
479
|
+
import { defineCommand } from "citty";
|
|
480
|
+
import consola2 from "consola";
|
|
481
|
+
|
|
482
|
+
// src/utils/exit.ts
|
|
483
|
+
import consola from "consola";
|
|
484
|
+
function exitWithError(message) {
|
|
485
|
+
consola.error(message);
|
|
486
|
+
process.exit(1);
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// src/commands/bias.ts
|
|
490
|
+
var VALID_LANGS = /* @__PURE__ */ new Set(["en", "de"]);
|
|
491
|
+
var VALID_OUTPUTS = /* @__PURE__ */ new Set(["json", "md", "all"]);
|
|
492
|
+
var bias_default = defineCommand({
|
|
493
|
+
meta: {
|
|
494
|
+
name: "bias",
|
|
495
|
+
description: "Run CrowS-Pairs bias test on an Ollama model"
|
|
496
|
+
},
|
|
497
|
+
args: {
|
|
498
|
+
model: {
|
|
499
|
+
type: "positional",
|
|
500
|
+
description: "Ollama model name (e.g. llama3.2, mistral:7b)",
|
|
501
|
+
required: true
|
|
502
|
+
},
|
|
503
|
+
lang: {
|
|
504
|
+
type: "string",
|
|
505
|
+
description: "Dataset language: en or de",
|
|
506
|
+
default: "de"
|
|
507
|
+
},
|
|
508
|
+
url: {
|
|
509
|
+
type: "string",
|
|
510
|
+
description: "Ollama base URL",
|
|
511
|
+
default: "http://localhost:11434"
|
|
512
|
+
},
|
|
513
|
+
output: {
|
|
514
|
+
type: "string",
|
|
515
|
+
description: "Output format: json, md, or all",
|
|
516
|
+
default: "json"
|
|
517
|
+
},
|
|
518
|
+
"out-dir": {
|
|
519
|
+
type: "string",
|
|
520
|
+
description: "Output directory",
|
|
521
|
+
default: "./.euconform"
|
|
522
|
+
}
|
|
523
|
+
},
|
|
524
|
+
async run({ args }) {
|
|
525
|
+
const lang = args.lang;
|
|
526
|
+
const output = args.output;
|
|
527
|
+
const outDir = resolve(args["out-dir"]);
|
|
528
|
+
const model = args.model;
|
|
529
|
+
const url = args.url;
|
|
530
|
+
if (!VALID_LANGS.has(lang)) {
|
|
531
|
+
exitWithError(`Invalid language: ${lang}. Use one of: en, de.`);
|
|
532
|
+
}
|
|
533
|
+
if (!VALID_OUTPUTS.has(output)) {
|
|
534
|
+
exitWithError(`Invalid output format: ${output}. Use one of: json, md, all.`);
|
|
535
|
+
}
|
|
536
|
+
const result = await runBiasTest({
|
|
537
|
+
model,
|
|
538
|
+
lang,
|
|
539
|
+
url
|
|
540
|
+
});
|
|
541
|
+
try {
|
|
542
|
+
await stat(outDir);
|
|
543
|
+
} catch {
|
|
544
|
+
await mkdir(outDir, { recursive: true });
|
|
545
|
+
}
|
|
546
|
+
const biasOutput = {
|
|
547
|
+
schemaVersion: "euconform.bias.v1",
|
|
548
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
549
|
+
model,
|
|
550
|
+
dataset: `crows_pairs_${lang}`,
|
|
551
|
+
result: {
|
|
552
|
+
score: result.score,
|
|
553
|
+
method: result.method,
|
|
554
|
+
pairsAnalyzed: result.pairsAnalyzed,
|
|
555
|
+
stereotypicalPreference: result.stereotypicalPreference
|
|
556
|
+
}
|
|
557
|
+
};
|
|
558
|
+
const severity = formatBiasSeverity(result.score);
|
|
559
|
+
if (output === "json" || output === "all") {
|
|
560
|
+
const jsonPath = resolve(outDir, "euconform.bias.json");
|
|
561
|
+
await writeFile(jsonPath, JSON.stringify(biasOutput, null, 2));
|
|
562
|
+
consola2.success(`Written: ${jsonPath}`);
|
|
563
|
+
}
|
|
564
|
+
if (output === "md" || output === "all") {
|
|
565
|
+
const mdContent = [
|
|
566
|
+
"# EuConform Bias Test Report",
|
|
567
|
+
"",
|
|
568
|
+
`**Model:** ${model}`,
|
|
569
|
+
`**Dataset:** CrowS-Pairs (${lang.toUpperCase()})`,
|
|
570
|
+
`**Generated:** ${biasOutput.generatedAt}`,
|
|
571
|
+
"",
|
|
572
|
+
"## Results",
|
|
573
|
+
"",
|
|
574
|
+
"| Metric | Value |",
|
|
575
|
+
"|--------|-------|",
|
|
576
|
+
`| Bias Score | ${result.score.toFixed(4)} |`,
|
|
577
|
+
`| Severity | ${severity} |`,
|
|
578
|
+
`| Method | ${result.method} |`,
|
|
579
|
+
`| Pairs Analyzed | ${result.pairsAnalyzed} |`,
|
|
580
|
+
`| Stereotypical Preference | ${result.stereotypicalPreference.toFixed(1)}% |`,
|
|
581
|
+
"",
|
|
582
|
+
"## Thresholds",
|
|
583
|
+
"",
|
|
584
|
+
"| Range | Classification |",
|
|
585
|
+
"|-------|---------------|",
|
|
586
|
+
"| < 0.1 | Minimal Bias |",
|
|
587
|
+
"| 0.1 - 0.3 | Light Bias |",
|
|
588
|
+
"| > 0.3 | Strong Bias |",
|
|
589
|
+
"",
|
|
590
|
+
"---",
|
|
591
|
+
"",
|
|
592
|
+
"*Generated by [EuConform CLI](https://github.com/Hiepler/EuConform) using CrowS-Pairs methodology (Nangia et al., 2020)*",
|
|
593
|
+
""
|
|
594
|
+
].join("\n");
|
|
595
|
+
const mdPath = resolve(outDir, "euconform.bias.md");
|
|
596
|
+
await writeFile(mdPath, mdContent);
|
|
597
|
+
consola2.success(`Written: ${mdPath}`);
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
});
|
|
488
601
|
|
|
489
602
|
// src/commands/scan.ts
|
|
490
|
-
import { stat as
|
|
491
|
-
import { resolve as
|
|
603
|
+
import { stat as stat3 } from "fs/promises";
|
|
604
|
+
import { resolve as resolve3 } from "path";
|
|
492
605
|
|
|
493
606
|
// ../core/src/evidence/compliance-evaluation.ts
|
|
494
607
|
var IMPLEMENTATION_PROVENANCE = /* @__PURE__ */ new Set(["runtime", "config"]);
|
|
@@ -1409,8 +1522,67 @@ function validateCiReport(data) {
|
|
|
1409
1522
|
}
|
|
1410
1523
|
var VALID_BUNDLE_ROLES = /* @__PURE__ */ new Set(["report", "aibom", "ci", "summary"]);
|
|
1411
1524
|
var SHA256_HEX = /^[a-f0-9]{64}$/;
|
|
1412
|
-
function
|
|
1413
|
-
const
|
|
1525
|
+
function readBundleArtifactFields(raw, index) {
|
|
1526
|
+
const scope = "bundle artifact";
|
|
1527
|
+
const role = requireField(raw, "role", "string", scope, `artifacts[${index}].role`);
|
|
1528
|
+
const fileName = requireField(
|
|
1529
|
+
raw,
|
|
1530
|
+
"fileName",
|
|
1531
|
+
"string",
|
|
1532
|
+
scope,
|
|
1533
|
+
`artifacts[${index}].fileName`
|
|
1534
|
+
);
|
|
1535
|
+
const sha256 = requireField(
|
|
1536
|
+
raw,
|
|
1537
|
+
"sha256",
|
|
1538
|
+
"string",
|
|
1539
|
+
scope,
|
|
1540
|
+
`artifacts[${index}].sha256`
|
|
1541
|
+
);
|
|
1542
|
+
const required = requireField(
|
|
1543
|
+
raw,
|
|
1544
|
+
"required",
|
|
1545
|
+
"boolean",
|
|
1546
|
+
scope,
|
|
1547
|
+
`artifacts[${index}].required`
|
|
1548
|
+
);
|
|
1549
|
+
const declaredSchemaVersion = raw.schemaVersion;
|
|
1550
|
+
if (declaredSchemaVersion !== void 0 && typeof declaredSchemaVersion !== "string") {
|
|
1551
|
+
throw new Error(`Invalid bundle artifact: '${fileName}' has non-string schemaVersion`);
|
|
1552
|
+
}
|
|
1553
|
+
if (raw.mimeType !== void 0 && typeof raw.mimeType !== "string") {
|
|
1554
|
+
throw new Error(`Invalid bundle artifact: '${fileName}' has non-string mimeType`);
|
|
1555
|
+
}
|
|
1556
|
+
return {
|
|
1557
|
+
role,
|
|
1558
|
+
fileName,
|
|
1559
|
+
sha256,
|
|
1560
|
+
required,
|
|
1561
|
+
declaredSchemaVersion,
|
|
1562
|
+
mimeType: raw.mimeType
|
|
1563
|
+
};
|
|
1564
|
+
}
|
|
1565
|
+
function assertBundleArtifactIntegrity(fields, seenRoles, seenFiles, bundleMajor) {
|
|
1566
|
+
const { role, fileName, sha256, declaredSchemaVersion } = fields;
|
|
1567
|
+
if (!VALID_BUNDLE_ROLES.has(role)) {
|
|
1568
|
+
throw new Error(`Invalid bundle artifact: unsupported role '${role}'`);
|
|
1569
|
+
}
|
|
1570
|
+
if (!SHA256_HEX.test(sha256)) {
|
|
1571
|
+
throw new Error(`Invalid bundle artifact: '${fileName}' has invalid sha256`);
|
|
1572
|
+
}
|
|
1573
|
+
if (seenRoles.has(role)) {
|
|
1574
|
+
throw new Error(`Invalid bundle artifact: duplicate role '${role}'`);
|
|
1575
|
+
}
|
|
1576
|
+
if (seenFiles.has(fileName)) {
|
|
1577
|
+
throw new Error(`Invalid bundle artifact: duplicate fileName '${fileName}'`);
|
|
1578
|
+
}
|
|
1579
|
+
if (declaredSchemaVersion && bundleMajor && majorVersion(declaredSchemaVersion) !== bundleMajor) {
|
|
1580
|
+
throw new Error(
|
|
1581
|
+
`Invalid bundle artifact: '${fileName}' uses schemaVersion '${declaredSchemaVersion}' with a different major than the bundle`
|
|
1582
|
+
);
|
|
1583
|
+
}
|
|
1584
|
+
}
|
|
1585
|
+
function validateBundleHeader(obj) {
|
|
1414
1586
|
requireSchemaVersion(obj, "euconform.bundle.v1", "bundle");
|
|
1415
1587
|
requireField(obj, "generatedAt", "string", "bundle");
|
|
1416
1588
|
const tool = requireField(obj, "tool", "object", "bundle");
|
|
@@ -1419,6 +1591,10 @@ function validateScanBundle(data) {
|
|
|
1419
1591
|
const target = requireField(obj, "target", "object", "bundle");
|
|
1420
1592
|
requireField(target, "name", "string", "bundle", "target.name");
|
|
1421
1593
|
requireField(target, "rootPath", "string", "bundle", "target.rootPath");
|
|
1594
|
+
}
|
|
1595
|
+
function validateScanBundle(data) {
|
|
1596
|
+
const obj = assertObject(data, "bundle");
|
|
1597
|
+
validateBundleHeader(obj);
|
|
1422
1598
|
const artifacts = requireField(obj, "artifacts", "array", "bundle");
|
|
1423
1599
|
if (artifacts.length === 0) {
|
|
1424
1600
|
throw new Error("Invalid bundle: 'artifacts' must contain at least one artifact");
|
|
@@ -1428,63 +1604,13 @@ function validateScanBundle(data) {
|
|
|
1428
1604
|
const bundleMajor = majorVersion("euconform.bundle.v1");
|
|
1429
1605
|
let hasRequiredReport = false;
|
|
1430
1606
|
for (let index = 0; index < artifacts.length; index++) {
|
|
1431
|
-
const
|
|
1432
|
-
const
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
);
|
|
1439
|
-
const fileName = requireField(
|
|
1440
|
-
artifact,
|
|
1441
|
-
"fileName",
|
|
1442
|
-
"string",
|
|
1443
|
-
"bundle artifact",
|
|
1444
|
-
`artifacts[${index}].fileName`
|
|
1445
|
-
);
|
|
1446
|
-
const sha256 = requireField(
|
|
1447
|
-
artifact,
|
|
1448
|
-
"sha256",
|
|
1449
|
-
"string",
|
|
1450
|
-
"bundle artifact",
|
|
1451
|
-
`artifacts[${index}].sha256`
|
|
1452
|
-
);
|
|
1453
|
-
const required = requireField(
|
|
1454
|
-
artifact,
|
|
1455
|
-
"required",
|
|
1456
|
-
"boolean",
|
|
1457
|
-
"bundle artifact",
|
|
1458
|
-
`artifacts[${index}].required`
|
|
1459
|
-
);
|
|
1460
|
-
if (!VALID_BUNDLE_ROLES.has(role)) {
|
|
1461
|
-
throw new Error(`Invalid bundle artifact: unsupported role '${role}'`);
|
|
1462
|
-
}
|
|
1463
|
-
if (!SHA256_HEX.test(sha256)) {
|
|
1464
|
-
throw new Error(`Invalid bundle artifact: '${fileName}' has invalid sha256`);
|
|
1465
|
-
}
|
|
1466
|
-
if (seenRoles.has(role)) {
|
|
1467
|
-
throw new Error(`Invalid bundle artifact: duplicate role '${role}'`);
|
|
1468
|
-
}
|
|
1469
|
-
if (seenFiles.has(fileName)) {
|
|
1470
|
-
throw new Error(`Invalid bundle artifact: duplicate fileName '${fileName}'`);
|
|
1471
|
-
}
|
|
1472
|
-
seenRoles.add(role);
|
|
1473
|
-
seenFiles.add(fileName);
|
|
1474
|
-
const declaredSchemaVersion = artifact.schemaVersion;
|
|
1475
|
-
if (declaredSchemaVersion !== void 0 && typeof declaredSchemaVersion !== "string") {
|
|
1476
|
-
throw new Error(`Invalid bundle artifact: '${fileName}' has non-string schemaVersion`);
|
|
1477
|
-
}
|
|
1478
|
-
if (artifact.mimeType !== void 0 && typeof artifact.mimeType !== "string") {
|
|
1479
|
-
throw new Error(`Invalid bundle artifact: '${fileName}' has non-string mimeType`);
|
|
1480
|
-
}
|
|
1481
|
-
if (declaredSchemaVersion && bundleMajor && majorVersion(declaredSchemaVersion) !== bundleMajor) {
|
|
1482
|
-
throw new Error(
|
|
1483
|
-
`Invalid bundle artifact: '${fileName}' uses schemaVersion '${declaredSchemaVersion}' with a different major than the bundle`
|
|
1484
|
-
);
|
|
1485
|
-
}
|
|
1486
|
-
if (role === "report") {
|
|
1487
|
-
hasRequiredReport = required;
|
|
1607
|
+
const raw = assertObject(artifacts[index], "bundle artifact");
|
|
1608
|
+
const fields = readBundleArtifactFields(raw, index);
|
|
1609
|
+
assertBundleArtifactIntegrity(fields, seenRoles, seenFiles, bundleMajor);
|
|
1610
|
+
seenRoles.add(fields.role);
|
|
1611
|
+
seenFiles.add(fields.fileName);
|
|
1612
|
+
if (fields.role === "report") {
|
|
1613
|
+
hasRequiredReport = fields.required;
|
|
1488
1614
|
}
|
|
1489
1615
|
}
|
|
1490
1616
|
if (!hasRequiredReport) {
|
|
@@ -2742,8 +2868,8 @@ function runAllDetectors(ctx) {
|
|
|
2742
2868
|
|
|
2743
2869
|
// ../core/src/scanner/discovery.ts
|
|
2744
2870
|
var import_ignore = __toESM(require_ignore(), 1);
|
|
2745
|
-
import { readFile, readdir, stat } from "fs/promises";
|
|
2746
|
-
import { basename as basename2, extname, join, resolve } from "path";
|
|
2871
|
+
import { readFile, readdir, stat as stat2 } from "fs/promises";
|
|
2872
|
+
import { basename as basename2, extname, join, resolve as resolve2 } from "path";
|
|
2747
2873
|
var DEFAULT_EXTENSIONS = [".ts", ".tsx", ".js", ".jsx", ".json", ".md", ".yaml", ".yml"];
|
|
2748
2874
|
var NAMED_FILE_PATTERNS = [
|
|
2749
2875
|
/^package\.json$/,
|
|
@@ -2880,7 +3006,7 @@ function shouldProcessEntry(entry, targetPath, filterOpts) {
|
|
|
2880
3006
|
}
|
|
2881
3007
|
async function tryReadFile(absolutePath, entry, ext, provenance, maxSize) {
|
|
2882
3008
|
try {
|
|
2883
|
-
const fileStat = await
|
|
3009
|
+
const fileStat = await stat2(absolutePath);
|
|
2884
3010
|
if (!fileStat.isFile() || fileStat.size === 0) return "ignore";
|
|
2885
3011
|
if (fileStat.size > maxSize) return "skip";
|
|
2886
3012
|
const content = await readFile(absolutePath, "utf-8");
|
|
@@ -2898,7 +3024,7 @@ async function tryReadFile(absolutePath, entry, ext, provenance, maxSize) {
|
|
|
2898
3024
|
}
|
|
2899
3025
|
function resolveOptions(options) {
|
|
2900
3026
|
return {
|
|
2901
|
-
targetPath:
|
|
3027
|
+
targetPath: resolve2(options.targetPath),
|
|
2902
3028
|
scope: options.scope ?? DEFAULT_SCOPE,
|
|
2903
3029
|
extensions: options.extensions ?? DEFAULT_EXTENSIONS,
|
|
2904
3030
|
maxSize: options.maxFileSizeBytes ?? DEFAULT_MAX_FILE_SIZE,
|
|
@@ -3133,13 +3259,13 @@ async function scanRepository(options) {
|
|
|
3133
3259
|
}
|
|
3134
3260
|
|
|
3135
3261
|
// src/commands/scan.ts
|
|
3136
|
-
import { defineCommand } from "citty";
|
|
3137
|
-
import
|
|
3262
|
+
import { defineCommand as defineCommand2 } from "citty";
|
|
3263
|
+
import consola6 from "consola";
|
|
3138
3264
|
|
|
3139
3265
|
// src/output/ci.ts
|
|
3140
|
-
import { appendFile, mkdir, writeFile } from "fs/promises";
|
|
3266
|
+
import { appendFile, mkdir as mkdir2, writeFile as writeFile2 } from "fs/promises";
|
|
3141
3267
|
import { join as join2 } from "path";
|
|
3142
|
-
import
|
|
3268
|
+
import consola3 from "consola";
|
|
3143
3269
|
|
|
3144
3270
|
// src/utils/gap-priority.ts
|
|
3145
3271
|
var GAP_PRIORITY_ORDER = {
|
|
@@ -3290,15 +3416,15 @@ function emitGitHubAnnotations(output) {
|
|
|
3290
3416
|
}
|
|
3291
3417
|
async function writeCiArtifacts(output, outputDir, failOn, mode, scanScope, baseArtifacts) {
|
|
3292
3418
|
if (mode === "off") return null;
|
|
3293
|
-
await
|
|
3419
|
+
await mkdir2(outputDir, { recursive: true });
|
|
3294
3420
|
const ciReport = buildCiReport(output, failOn, scanScope, baseArtifacts);
|
|
3295
3421
|
const reportPath = join2(outputDir, "euconform.ci.json");
|
|
3296
3422
|
const summaryPath = join2(outputDir, "euconform.ci-summary.md");
|
|
3297
|
-
await
|
|
3423
|
+
await writeFile2(reportPath, `${JSON.stringify(ciReport, null, 2)}
|
|
3298
3424
|
`, "utf-8");
|
|
3299
|
-
await
|
|
3300
|
-
|
|
3301
|
-
|
|
3425
|
+
await writeFile2(summaryPath, renderCiSummaryMarkdown(output, ciReport), "utf-8");
|
|
3426
|
+
consola3.success(`Written ${reportPath}`);
|
|
3427
|
+
consola3.success(`Written ${summaryPath}`);
|
|
3302
3428
|
let githubStepSummaryPath;
|
|
3303
3429
|
if (mode === "github") {
|
|
3304
3430
|
emitGitHubAnnotations(output);
|
|
@@ -3307,14 +3433,14 @@ async function writeCiArtifacts(output, outputDir, failOn, mode, scanScope, base
|
|
|
3307
3433
|
await appendFile(stepSummaryPath, `
|
|
3308
3434
|
${renderCiSummaryMarkdown(output, ciReport)}`, "utf-8");
|
|
3309
3435
|
githubStepSummaryPath = stepSummaryPath;
|
|
3310
|
-
|
|
3436
|
+
consola3.info(`Appended GitHub step summary: ${stepSummaryPath}`);
|
|
3311
3437
|
}
|
|
3312
3438
|
}
|
|
3313
3439
|
return { reportPath, summaryPath, githubStepSummaryPath, baseArtifacts };
|
|
3314
3440
|
}
|
|
3315
3441
|
|
|
3316
3442
|
// src/output/terminal.ts
|
|
3317
|
-
import
|
|
3443
|
+
import consola4 from "consola";
|
|
3318
3444
|
var AREA_LABELS = {
|
|
3319
3445
|
disclosure: "Disclosure & Transparency",
|
|
3320
3446
|
biasTesting: "Bias Testing",
|
|
@@ -3335,56 +3461,56 @@ function printSignalCounts(complianceSignals) {
|
|
|
3335
3461
|
byConfidence[area.confidence]++;
|
|
3336
3462
|
}
|
|
3337
3463
|
const total = Object.values(complianceSignals).length;
|
|
3338
|
-
|
|
3464
|
+
consola4.info(
|
|
3339
3465
|
`${total} compliance areas assessed (${byConfidence.high} high, ${byConfidence.medium} medium, ${byConfidence.low} low confidence)`
|
|
3340
3466
|
);
|
|
3341
3467
|
}
|
|
3342
3468
|
function printComplianceOverview(complianceSignals) {
|
|
3343
|
-
|
|
3344
|
-
|
|
3469
|
+
consola4.log("");
|
|
3470
|
+
consola4.info("Compliance Overview:");
|
|
3345
3471
|
for (const [key, group] of Object.entries(complianceSignals)) {
|
|
3346
3472
|
const label = AREA_LABELS[key] ?? key;
|
|
3347
3473
|
const icon = STATUS_ICONS[group.status] ?? "[??]";
|
|
3348
|
-
|
|
3474
|
+
consola4.log(` ${icon} ${label.padEnd(28)} ${group.status} (${group.confidence})`);
|
|
3349
3475
|
}
|
|
3350
3476
|
}
|
|
3351
3477
|
function printTopGaps(gaps) {
|
|
3352
3478
|
if (gaps.length === 0) return;
|
|
3353
|
-
|
|
3354
|
-
|
|
3479
|
+
consola4.log("");
|
|
3480
|
+
consola4.warn(`Top gaps (${gaps.length} total):`);
|
|
3355
3481
|
for (const gap of gaps.slice(0, 3)) {
|
|
3356
|
-
|
|
3482
|
+
consola4.log(` - [${gap.priority}] ${gap.title}`);
|
|
3357
3483
|
}
|
|
3358
3484
|
if (gaps.length > 3) {
|
|
3359
|
-
|
|
3485
|
+
consola4.log(` ... and ${gaps.length - 3} more`);
|
|
3360
3486
|
}
|
|
3361
3487
|
}
|
|
3362
3488
|
function printTerminalSummary(output, outputDir) {
|
|
3363
3489
|
const { report } = output;
|
|
3364
|
-
|
|
3490
|
+
consola4.box("EuConform Scan Complete");
|
|
3365
3491
|
printSignalCounts(report.complianceSignals);
|
|
3366
3492
|
const ai = report.aiFootprint;
|
|
3367
|
-
|
|
3493
|
+
consola4.info(
|
|
3368
3494
|
`AI detected: ${ai.usesAI ? "yes" : "no"}${ai.inferenceModes.length > 0 ? ` | Inference: ${ai.inferenceModes.join(", ")}` : ""}`
|
|
3369
3495
|
);
|
|
3370
3496
|
printComplianceOverview(report.complianceSignals);
|
|
3371
3497
|
printTopGaps(report.gaps);
|
|
3372
3498
|
const openCount = report.assessmentHints.openQuestions.length;
|
|
3373
3499
|
if (openCount > 0) {
|
|
3374
|
-
|
|
3500
|
+
consola4.info(`${openCount} open question${openCount > 1 ? "s" : ""} for manual review`);
|
|
3375
3501
|
}
|
|
3376
|
-
|
|
3377
|
-
|
|
3378
|
-
|
|
3379
|
-
|
|
3380
|
-
|
|
3381
|
-
|
|
3502
|
+
consola4.log("");
|
|
3503
|
+
consola4.success(`Output written to: ${outputDir}`);
|
|
3504
|
+
consola4.info("Next steps:");
|
|
3505
|
+
consola4.log(" - Review `euconform.report.json` and `euconform.summary.md`");
|
|
3506
|
+
consola4.log(" - Complete role and risk classification in the EuConform web app");
|
|
3507
|
+
consola4.log(" - Use `--ci github --fail-on <level>` to turn scans into CI checks");
|
|
3382
3508
|
}
|
|
3383
3509
|
|
|
3384
3510
|
// src/output/writer.ts
|
|
3385
|
-
import { mkdir as
|
|
3511
|
+
import { mkdir as mkdir3, readFile as readFile2, writeFile as writeFile3 } from "fs/promises";
|
|
3386
3512
|
import { join as join3 } from "path";
|
|
3387
|
-
import
|
|
3513
|
+
import consola5 from "consola";
|
|
3388
3514
|
async function tryReadArtifact(outputDir, fileName) {
|
|
3389
3515
|
try {
|
|
3390
3516
|
const content = await readFile2(join3(outputDir, fileName), "utf-8");
|
|
@@ -3408,30 +3534,30 @@ async function writeBundleManifest(outputDir, metadata) {
|
|
|
3408
3534
|
generatedAt: metadata.generatedAt
|
|
3409
3535
|
});
|
|
3410
3536
|
const bundlePath = join3(outputDir, "euconform.bundle.json");
|
|
3411
|
-
await
|
|
3537
|
+
await writeFile3(bundlePath, `${JSON.stringify(bundle, null, 2)}
|
|
3412
3538
|
`, "utf-8");
|
|
3413
|
-
|
|
3539
|
+
consola5.success(`Written ${bundlePath}`);
|
|
3414
3540
|
return bundlePath;
|
|
3415
3541
|
}
|
|
3416
3542
|
async function writeOutputFiles(output, outputDir, format) {
|
|
3417
|
-
await
|
|
3543
|
+
await mkdir3(outputDir, { recursive: true });
|
|
3418
3544
|
const written = [];
|
|
3419
3545
|
if (format === "json" || format === "all") {
|
|
3420
3546
|
const reportContent = JSON.stringify(output.report, null, 2);
|
|
3421
3547
|
const reportPath = join3(outputDir, "euconform.report.json");
|
|
3422
|
-
await
|
|
3423
|
-
|
|
3548
|
+
await writeFile3(reportPath, reportContent, "utf-8");
|
|
3549
|
+
consola5.success(`Written ${reportPath}`);
|
|
3424
3550
|
written.push({ fileName: "euconform.report.json", content: reportContent });
|
|
3425
3551
|
const aibomContent = JSON.stringify(output.aibom, null, 2);
|
|
3426
3552
|
const aibomPath = join3(outputDir, "euconform.aibom.json");
|
|
3427
|
-
await
|
|
3428
|
-
|
|
3553
|
+
await writeFile3(aibomPath, aibomContent, "utf-8");
|
|
3554
|
+
consola5.success(`Written ${aibomPath}`);
|
|
3429
3555
|
written.push({ fileName: "euconform.aibom.json", content: aibomContent });
|
|
3430
3556
|
}
|
|
3431
3557
|
if (format === "md" || format === "all") {
|
|
3432
3558
|
const mdPath = join3(outputDir, "euconform.summary.md");
|
|
3433
|
-
await
|
|
3434
|
-
|
|
3559
|
+
await writeFile3(mdPath, output.summaryMarkdown, "utf-8");
|
|
3560
|
+
consola5.success(`Written ${mdPath}`);
|
|
3435
3561
|
written.push({ fileName: "euconform.summary.md", content: output.summaryMarkdown });
|
|
3436
3562
|
}
|
|
3437
3563
|
if (written.some((artifact) => artifact.fileName === "euconform.report.json")) {
|
|
@@ -3468,8 +3594,8 @@ async function writeZipBundle(outputDir) {
|
|
|
3468
3594
|
}
|
|
3469
3595
|
const zipped = zipSync(zipData);
|
|
3470
3596
|
const zipPath = join3(outputDir, "euconform.bundle.zip");
|
|
3471
|
-
await
|
|
3472
|
-
|
|
3597
|
+
await writeFile3(zipPath, zipped);
|
|
3598
|
+
consola5.success(`Written ${zipPath}`);
|
|
3473
3599
|
return zipPath;
|
|
3474
3600
|
}
|
|
3475
3601
|
|
|
@@ -3478,55 +3604,58 @@ var VALID_FORMATS = /* @__PURE__ */ new Set(["json", "md", "all"]);
|
|
|
3478
3604
|
var VALID_SCOPES = /* @__PURE__ */ new Set(["production", "all"]);
|
|
3479
3605
|
var FAIL_ON_LEVELS = ["none", "critical", "high", "medium", "low"];
|
|
3480
3606
|
var VALID_CI_MODES = /* @__PURE__ */ new Set(["off", "github"]);
|
|
3481
|
-
async function
|
|
3482
|
-
const targetPath = resolve2(args.path);
|
|
3607
|
+
async function assertDirectoryExists(targetPath) {
|
|
3483
3608
|
try {
|
|
3484
|
-
const info = await
|
|
3609
|
+
const info = await stat3(targetPath);
|
|
3485
3610
|
if (!info.isDirectory()) {
|
|
3486
|
-
|
|
3487
|
-
process.exit(1);
|
|
3611
|
+
exitWithError(`Not a directory: ${targetPath}`);
|
|
3488
3612
|
}
|
|
3489
3613
|
} catch {
|
|
3490
|
-
|
|
3491
|
-
process.exit(1);
|
|
3614
|
+
exitWithError(`Path does not exist: ${targetPath}`);
|
|
3492
3615
|
}
|
|
3493
|
-
|
|
3494
|
-
|
|
3495
|
-
|
|
3496
|
-
|
|
3497
|
-
if (!VALID_FORMATS.has(format)) {
|
|
3498
|
-
consola4.error(`Invalid format: ${format}. Use one of: json, md, all.`);
|
|
3499
|
-
process.exit(1);
|
|
3616
|
+
}
|
|
3617
|
+
function validateEnumArgs(args) {
|
|
3618
|
+
if (!VALID_FORMATS.has(args.format)) {
|
|
3619
|
+
exitWithError(`Invalid format: ${args.format}. Use one of: json, md, all.`);
|
|
3500
3620
|
}
|
|
3501
|
-
if (!VALID_SCOPES.has(scope)) {
|
|
3502
|
-
|
|
3503
|
-
process.exit(1);
|
|
3621
|
+
if (!VALID_SCOPES.has(args.scope)) {
|
|
3622
|
+
exitWithError(`Invalid scope: ${args.scope}. Use one of: production, all.`);
|
|
3504
3623
|
}
|
|
3505
|
-
if (!FAIL_ON_LEVELS.includes(failOn)) {
|
|
3506
|
-
|
|
3507
|
-
|
|
3624
|
+
if (!FAIL_ON_LEVELS.includes(args.failOn)) {
|
|
3625
|
+
exitWithError(
|
|
3626
|
+
`Invalid fail-on level: ${args.failOn}. Use one of: ${FAIL_ON_LEVELS.join(", ")}.`
|
|
3627
|
+
);
|
|
3508
3628
|
}
|
|
3509
|
-
if (!VALID_CI_MODES.has(ciMode)) {
|
|
3510
|
-
|
|
3511
|
-
process.exit(1);
|
|
3629
|
+
if (!VALID_CI_MODES.has(args.ciMode)) {
|
|
3630
|
+
exitWithError(`Invalid ci mode: ${args.ciMode}. Use one of: off, github.`);
|
|
3512
3631
|
}
|
|
3513
|
-
if (args.zip && format === "md") {
|
|
3514
|
-
|
|
3515
|
-
process.exit(1);
|
|
3632
|
+
if (args.zip && args.format === "md") {
|
|
3633
|
+
exitWithError('Cannot create euconform.bundle.zip when format is "md" only.');
|
|
3516
3634
|
}
|
|
3635
|
+
}
|
|
3636
|
+
function parseExcludeGlobs(raw) {
|
|
3637
|
+
if (!raw) return void 0;
|
|
3638
|
+
return Array.isArray(raw) ? raw : [raw];
|
|
3639
|
+
}
|
|
3640
|
+
async function validateAndParseArgs(args) {
|
|
3641
|
+
const targetPath = resolve3(args.path);
|
|
3642
|
+
await assertDirectoryExists(targetPath);
|
|
3643
|
+
const format = args.format ?? "all";
|
|
3644
|
+
const scope = args.scope ?? "production";
|
|
3645
|
+
const failOn = args["fail-on"] ?? "none";
|
|
3646
|
+
const ciMode = args.ci ?? "off";
|
|
3647
|
+
validateEnumArgs({ format, scope, failOn, ciMode, zip: args.zip });
|
|
3517
3648
|
if (args.verbose) {
|
|
3518
|
-
|
|
3649
|
+
consola6.level = 4;
|
|
3519
3650
|
}
|
|
3520
|
-
const rawExclude = args["exclude-glob"];
|
|
3521
|
-
const excludeGlobs = rawExclude ? Array.isArray(rawExclude) ? rawExclude : [rawExclude] : void 0;
|
|
3522
3651
|
return {
|
|
3523
3652
|
targetPath,
|
|
3524
|
-
outputDir: args.output ?
|
|
3653
|
+
outputDir: args.output ? resolve3(args.output) : resolve3(targetPath, ".euconform"),
|
|
3525
3654
|
format,
|
|
3526
3655
|
scope,
|
|
3527
3656
|
failOn,
|
|
3528
3657
|
ciMode,
|
|
3529
|
-
excludeGlobs
|
|
3658
|
+
excludeGlobs: parseExcludeGlobs(args["exclude-glob"])
|
|
3530
3659
|
};
|
|
3531
3660
|
}
|
|
3532
3661
|
function buildOutputFileNames(format) {
|
|
@@ -3539,7 +3668,24 @@ function buildOutputFileNames(format) {
|
|
|
3539
3668
|
}
|
|
3540
3669
|
return names;
|
|
3541
3670
|
}
|
|
3542
|
-
|
|
3671
|
+
async function runBiasAndInject(output, model, lang, url) {
|
|
3672
|
+
const { runBiasTest: runBiasTest2 } = await import("./run-bias-test-S6DIKHIY.js");
|
|
3673
|
+
const { formatBiasSeverity: formatBiasSeverity2 } = await import("./severity-5EZAEKDS.js");
|
|
3674
|
+
const biasResult = await runBiasTest2({ model, lang, url });
|
|
3675
|
+
const severity = formatBiasSeverity2(biasResult.score);
|
|
3676
|
+
output.report.complianceSignals.biasTesting = {
|
|
3677
|
+
status: "present",
|
|
3678
|
+
confidence: biasResult.method === "logprobs_exact" ? "high" : "medium",
|
|
3679
|
+
evidence: [
|
|
3680
|
+
{
|
|
3681
|
+
file: "bias-evaluation",
|
|
3682
|
+
snippet: `CrowS-Pairs bias evaluation on model '${model}'. Score: ${biasResult.score.toFixed(4)} (${severity}). Method: ${biasResult.method}. Pairs analyzed: ${biasResult.pairsAnalyzed}.`
|
|
3683
|
+
}
|
|
3684
|
+
]
|
|
3685
|
+
};
|
|
3686
|
+
output.aibom.complianceCapabilities.biasEvaluation = true;
|
|
3687
|
+
}
|
|
3688
|
+
var scan_default = defineCommand2({
|
|
3543
3689
|
meta: {
|
|
3544
3690
|
name: "scan",
|
|
3545
3691
|
description: "Scan a project directory for EU AI Act compliance evidence"
|
|
@@ -3587,16 +3733,52 @@ var scan_default = defineCommand({
|
|
|
3587
3733
|
type: "boolean",
|
|
3588
3734
|
default: false,
|
|
3589
3735
|
description: "Enable verbose logging"
|
|
3736
|
+
},
|
|
3737
|
+
bias: {
|
|
3738
|
+
type: "boolean",
|
|
3739
|
+
default: false,
|
|
3740
|
+
description: "Run CrowS-Pairs bias test after scan (requires --model)"
|
|
3741
|
+
},
|
|
3742
|
+
model: {
|
|
3743
|
+
type: "string",
|
|
3744
|
+
description: "Ollama model for bias testing (required with --bias)"
|
|
3745
|
+
},
|
|
3746
|
+
"bias-lang": {
|
|
3747
|
+
type: "string",
|
|
3748
|
+
default: "de",
|
|
3749
|
+
description: 'Bias test dataset language: "en" or "de"'
|
|
3750
|
+
},
|
|
3751
|
+
"bias-url": {
|
|
3752
|
+
type: "string",
|
|
3753
|
+
default: "http://localhost:11434",
|
|
3754
|
+
description: "Ollama base URL for bias testing"
|
|
3590
3755
|
}
|
|
3591
3756
|
},
|
|
3592
3757
|
async run({ args }) {
|
|
3593
3758
|
const { targetPath, outputDir, format, scope, failOn, ciMode, excludeGlobs } = await validateAndParseArgs(args);
|
|
3594
|
-
|
|
3759
|
+
if (args.bias && !args.model) {
|
|
3760
|
+
exitWithError(
|
|
3761
|
+
"Bias testing requires --model flag. Example: euconform scan ./project --bias --model llama3.2"
|
|
3762
|
+
);
|
|
3763
|
+
}
|
|
3764
|
+
const biasLang = args["bias-lang"] ?? "de";
|
|
3765
|
+
if (args.bias && !["en", "de"].includes(biasLang)) {
|
|
3766
|
+
exitWithError(`Invalid bias language: ${biasLang}. Use one of: en, de.`);
|
|
3767
|
+
}
|
|
3768
|
+
consola6.start(`Scanning ${targetPath} (${scope} scope)...`);
|
|
3595
3769
|
const scanResult = await scanRepository({ targetPath, scope, excludeGlobs });
|
|
3596
|
-
|
|
3770
|
+
consola6.info(
|
|
3597
3771
|
`Found ${scanResult.signals.length} signals across ${scanResult.meta.filesScanned} files`
|
|
3598
3772
|
);
|
|
3599
3773
|
const output = generateScanOutput(scanResult);
|
|
3774
|
+
if (args.bias) {
|
|
3775
|
+
await runBiasAndInject(
|
|
3776
|
+
output,
|
|
3777
|
+
args.model,
|
|
3778
|
+
biasLang,
|
|
3779
|
+
args["bias-url"]
|
|
3780
|
+
);
|
|
3781
|
+
}
|
|
3600
3782
|
await writeOutputFiles(output, outputDir, format);
|
|
3601
3783
|
const ciArtifacts = await writeCiArtifacts(
|
|
3602
3784
|
output,
|
|
@@ -3608,8 +3790,8 @@ var scan_default = defineCommand({
|
|
|
3608
3790
|
);
|
|
3609
3791
|
printTerminalSummary(output, outputDir);
|
|
3610
3792
|
if (ciArtifacts) {
|
|
3611
|
-
|
|
3612
|
-
`CI artifacts ready: ${
|
|
3793
|
+
consola6.info(
|
|
3794
|
+
`CI artifacts ready: ${resolve3(ciArtifacts.reportPath)} and ${resolve3(ciArtifacts.summaryPath)}`
|
|
3613
3795
|
);
|
|
3614
3796
|
}
|
|
3615
3797
|
await writeBundleManifest(outputDir, {
|
|
@@ -3624,67 +3806,67 @@ var scan_default = defineCommand({
|
|
|
3624
3806
|
await writeZipBundle(outputDir);
|
|
3625
3807
|
}
|
|
3626
3808
|
if (shouldFailOnGaps(output.report.gaps, failOn)) {
|
|
3627
|
-
|
|
3809
|
+
consola6.error(`Scan failed CI threshold: found gap(s) at or above "${failOn}".`);
|
|
3628
3810
|
process.exit(1);
|
|
3629
3811
|
}
|
|
3630
3812
|
}
|
|
3631
3813
|
});
|
|
3632
3814
|
|
|
3633
3815
|
// src/commands/verify.ts
|
|
3634
|
-
import { resolve as
|
|
3635
|
-
import { defineCommand as
|
|
3636
|
-
import
|
|
3816
|
+
import { resolve as resolve5 } from "path";
|
|
3817
|
+
import { defineCommand as defineCommand3 } from "citty";
|
|
3818
|
+
import consola8 from "consola";
|
|
3637
3819
|
|
|
3638
3820
|
// src/output/verify.ts
|
|
3639
|
-
import
|
|
3821
|
+
import consola7 from "consola";
|
|
3640
3822
|
function printVerifySummary(report) {
|
|
3641
|
-
|
|
3642
|
-
|
|
3643
|
-
|
|
3823
|
+
consola7.box("EuConform Bundle Verify");
|
|
3824
|
+
consola7.info(`Input: ${report.bundlePath}`);
|
|
3825
|
+
consola7.info(`Mode: ${report.inputType}`);
|
|
3644
3826
|
if (!report.bundle) {
|
|
3645
|
-
|
|
3827
|
+
consola7.error("Bundle manifest could not be validated.");
|
|
3646
3828
|
} else {
|
|
3647
|
-
|
|
3648
|
-
|
|
3829
|
+
consola7.info(`Bundle: ${report.bundle.schemaVersion}`);
|
|
3830
|
+
consola7.info(
|
|
3649
3831
|
`Target: ${report.bundle.target.name} (${report.bundle.tool.name} v${report.bundle.tool.version})`
|
|
3650
3832
|
);
|
|
3651
3833
|
}
|
|
3652
3834
|
if (report.artifacts.length > 0) {
|
|
3653
|
-
|
|
3654
|
-
|
|
3835
|
+
consola7.log("");
|
|
3836
|
+
consola7.info("Artifacts:");
|
|
3655
3837
|
for (const artifact of report.artifacts) {
|
|
3656
|
-
|
|
3838
|
+
consola7.log(
|
|
3657
3839
|
` - ${artifact.fileName} [${artifact.role}] hash=${artifact.hashStatus} schema=${artifact.schemaStatus} metadata=${artifact.metadataStatus}`
|
|
3658
3840
|
);
|
|
3659
3841
|
}
|
|
3660
3842
|
}
|
|
3661
3843
|
if (report.warnings.length > 0) {
|
|
3662
|
-
|
|
3663
|
-
|
|
3844
|
+
consola7.log("");
|
|
3845
|
+
consola7.warn(`Warnings (${report.warnings.length}):`);
|
|
3664
3846
|
for (const warning of report.warnings) {
|
|
3665
|
-
|
|
3847
|
+
consola7.warn(` - ${warning.message}`);
|
|
3666
3848
|
}
|
|
3667
3849
|
}
|
|
3668
3850
|
if (report.errors.length > 0) {
|
|
3669
|
-
|
|
3670
|
-
|
|
3851
|
+
consola7.log("");
|
|
3852
|
+
consola7.error(`Errors (${report.errors.length}):`);
|
|
3671
3853
|
for (const error of report.errors) {
|
|
3672
|
-
|
|
3854
|
+
consola7.error(` - ${error.message}`);
|
|
3673
3855
|
}
|
|
3674
3856
|
}
|
|
3675
|
-
|
|
3857
|
+
consola7.log("");
|
|
3676
3858
|
if (report.status === "valid") {
|
|
3677
|
-
|
|
3859
|
+
consola7.success("Bundle verification passed without warnings.");
|
|
3678
3860
|
} else if (report.status === "warnings") {
|
|
3679
|
-
|
|
3861
|
+
consola7.warn("Bundle verification completed with warnings.");
|
|
3680
3862
|
} else {
|
|
3681
|
-
|
|
3863
|
+
consola7.error("Bundle verification failed.");
|
|
3682
3864
|
}
|
|
3683
3865
|
}
|
|
3684
3866
|
|
|
3685
3867
|
// src/verify/verify.ts
|
|
3686
3868
|
import { readFile as readFile3 } from "fs/promises";
|
|
3687
|
-
import { dirname, join as join4, resolve as
|
|
3869
|
+
import { dirname, join as join4, resolve as resolve4 } from "path";
|
|
3688
3870
|
import { strFromU8, unzipSync } from "fflate";
|
|
3689
3871
|
function createReport(bundlePath, inputType) {
|
|
3690
3872
|
return {
|
|
@@ -3710,8 +3892,8 @@ function finalizeReport(report) {
|
|
|
3710
3892
|
return report;
|
|
3711
3893
|
}
|
|
3712
3894
|
async function loadBundleInput(inputPath) {
|
|
3713
|
-
const resolved =
|
|
3714
|
-
const stats = await import("fs/promises").then(({ stat:
|
|
3895
|
+
const resolved = resolve4(inputPath);
|
|
3896
|
+
const stats = await import("fs/promises").then(({ stat: stat4 }) => stat4(resolved));
|
|
3715
3897
|
if (stats.isDirectory()) {
|
|
3716
3898
|
const bundlePath = join4(resolved, "euconform.bundle.json");
|
|
3717
3899
|
const manifestContent2 = await readFile3(bundlePath, "utf8");
|
|
@@ -3759,79 +3941,74 @@ async function loadBundleInput(inputPath) {
|
|
|
3759
3941
|
}
|
|
3760
3942
|
};
|
|
3761
3943
|
}
|
|
3762
|
-
function
|
|
3944
|
+
function mismatchIssue(fileName, code, field) {
|
|
3945
|
+
return {
|
|
3946
|
+
artifact: fileName,
|
|
3947
|
+
code,
|
|
3948
|
+
message: `${fileName} ${field} does not match bundle manifest`
|
|
3949
|
+
};
|
|
3950
|
+
}
|
|
3951
|
+
function compareReportMetadata(bundle, fileName, report) {
|
|
3763
3952
|
const issues = [];
|
|
3764
|
-
if (
|
|
3953
|
+
if (report.tool.name !== bundle.tool.name) {
|
|
3954
|
+
issues.push(mismatchIssue(fileName, "metadata.tool.name", "tool.name"));
|
|
3955
|
+
}
|
|
3956
|
+
if (report.tool.version !== bundle.tool.version) {
|
|
3957
|
+
issues.push(mismatchIssue(fileName, "metadata.tool.version", "tool.version"));
|
|
3958
|
+
}
|
|
3959
|
+
if (report.target.name !== bundle.target.name) {
|
|
3960
|
+
issues.push(mismatchIssue(fileName, "metadata.target.name", "target.name"));
|
|
3961
|
+
}
|
|
3962
|
+
if (report.target.rootPath !== bundle.target.rootPath) {
|
|
3963
|
+
issues.push(mismatchIssue(fileName, "metadata.target.rootPath", "target.rootPath"));
|
|
3964
|
+
}
|
|
3965
|
+
return issues;
|
|
3966
|
+
}
|
|
3967
|
+
function compareAibomMetadata(bundle, fileName, aibom) {
|
|
3968
|
+
const issues = [];
|
|
3969
|
+
if (aibom.project.name !== bundle.target.name) {
|
|
3765
3970
|
issues.push({
|
|
3766
|
-
artifact:
|
|
3767
|
-
code: "metadata.
|
|
3768
|
-
message: `${
|
|
3971
|
+
artifact: fileName,
|
|
3972
|
+
code: "metadata.project.name",
|
|
3973
|
+
message: `${fileName} project.name does not match bundle target.name`
|
|
3769
3974
|
});
|
|
3770
3975
|
}
|
|
3771
|
-
if (
|
|
3772
|
-
|
|
3773
|
-
|
|
3774
|
-
|
|
3775
|
-
|
|
3776
|
-
|
|
3777
|
-
message: `${artifact.fileName} tool.name does not match bundle manifest`
|
|
3778
|
-
});
|
|
3779
|
-
}
|
|
3780
|
-
if (report.tool.version !== bundle.tool.version) {
|
|
3781
|
-
issues.push({
|
|
3782
|
-
artifact: artifact.fileName,
|
|
3783
|
-
code: "metadata.tool.version",
|
|
3784
|
-
message: `${artifact.fileName} tool.version does not match bundle manifest`
|
|
3785
|
-
});
|
|
3786
|
-
}
|
|
3787
|
-
if (report.target.name !== bundle.target.name) {
|
|
3788
|
-
issues.push({
|
|
3789
|
-
artifact: artifact.fileName,
|
|
3790
|
-
code: "metadata.target.name",
|
|
3791
|
-
message: `${artifact.fileName} target.name does not match bundle manifest`
|
|
3792
|
-
});
|
|
3793
|
-
}
|
|
3794
|
-
if (report.target.rootPath !== bundle.target.rootPath) {
|
|
3795
|
-
issues.push({
|
|
3796
|
-
artifact: artifact.fileName,
|
|
3797
|
-
code: "metadata.target.rootPath",
|
|
3798
|
-
message: `${artifact.fileName} target.rootPath does not match bundle manifest`
|
|
3799
|
-
});
|
|
3800
|
-
}
|
|
3976
|
+
if (aibom.project.rootPath !== bundle.target.rootPath) {
|
|
3977
|
+
issues.push({
|
|
3978
|
+
artifact: fileName,
|
|
3979
|
+
code: "metadata.project.rootPath",
|
|
3980
|
+
message: `${fileName} project.rootPath does not match bundle target.rootPath`
|
|
3981
|
+
});
|
|
3801
3982
|
}
|
|
3802
|
-
|
|
3803
|
-
|
|
3804
|
-
|
|
3805
|
-
|
|
3806
|
-
|
|
3807
|
-
|
|
3808
|
-
message: `${artifact.fileName} project.name does not match bundle target.name`
|
|
3809
|
-
});
|
|
3810
|
-
}
|
|
3811
|
-
if (aibom.project.rootPath !== bundle.target.rootPath) {
|
|
3812
|
-
issues.push({
|
|
3813
|
-
artifact: artifact.fileName,
|
|
3814
|
-
code: "metadata.project.rootPath",
|
|
3815
|
-
message: `${artifact.fileName} project.rootPath does not match bundle target.rootPath`
|
|
3816
|
-
});
|
|
3817
|
-
}
|
|
3983
|
+
return issues;
|
|
3984
|
+
}
|
|
3985
|
+
function compareCiMetadata(bundle, fileName, ci) {
|
|
3986
|
+
const issues = [];
|
|
3987
|
+
if (ci.target.name !== bundle.target.name) {
|
|
3988
|
+
issues.push(mismatchIssue(fileName, "metadata.target.name", "target.name"));
|
|
3818
3989
|
}
|
|
3819
|
-
if (
|
|
3820
|
-
|
|
3821
|
-
|
|
3822
|
-
|
|
3823
|
-
|
|
3824
|
-
|
|
3825
|
-
|
|
3826
|
-
|
|
3827
|
-
|
|
3828
|
-
|
|
3829
|
-
|
|
3830
|
-
|
|
3831
|
-
|
|
3832
|
-
|
|
3833
|
-
|
|
3834
|
-
|
|
3990
|
+
if (ci.target.rootPath !== bundle.target.rootPath) {
|
|
3991
|
+
issues.push(mismatchIssue(fileName, "metadata.target.rootPath", "target.rootPath"));
|
|
3992
|
+
}
|
|
3993
|
+
return issues;
|
|
3994
|
+
}
|
|
3995
|
+
function compareJsonMetadata(bundle, artifact, document) {
|
|
3996
|
+
const issues = [];
|
|
3997
|
+
if (document.generatedAt !== bundle.generatedAt) {
|
|
3998
|
+
issues.push(mismatchIssue(artifact.fileName, "metadata.generatedAt", "generatedAt"));
|
|
3999
|
+
}
|
|
4000
|
+
switch (artifact.role) {
|
|
4001
|
+
case "report":
|
|
4002
|
+
issues.push(...compareReportMetadata(bundle, artifact.fileName, document));
|
|
4003
|
+
break;
|
|
4004
|
+
case "aibom":
|
|
4005
|
+
issues.push(
|
|
4006
|
+
...compareAibomMetadata(bundle, artifact.fileName, document)
|
|
4007
|
+
);
|
|
4008
|
+
break;
|
|
4009
|
+
case "ci":
|
|
4010
|
+
issues.push(...compareCiMetadata(bundle, artifact.fileName, document));
|
|
4011
|
+
break;
|
|
3835
4012
|
}
|
|
3836
4013
|
return issues;
|
|
3837
4014
|
}
|
|
@@ -3850,27 +4027,160 @@ function validateJsonArtifactForRole(artifact, parsed) {
|
|
|
3850
4027
|
function metadataSeverity(strict) {
|
|
3851
4028
|
return strict ? "error" : "warning";
|
|
3852
4029
|
}
|
|
3853
|
-
|
|
3854
|
-
const loaded = await loadBundleInput(inputPath);
|
|
3855
|
-
const report = createReport(loaded.bundlePath, loaded.inputType);
|
|
4030
|
+
function parseBundleManifest(report, manifestContent) {
|
|
3856
4031
|
let parsedManifest;
|
|
3857
4032
|
try {
|
|
3858
|
-
parsedManifest = JSON.parse(
|
|
4033
|
+
parsedManifest = JSON.parse(manifestContent);
|
|
3859
4034
|
} catch {
|
|
3860
4035
|
addIssue(report, "error", {
|
|
3861
4036
|
code: "bundle.parse",
|
|
3862
4037
|
message: "Failed to parse euconform.bundle.json as JSON"
|
|
3863
4038
|
});
|
|
3864
|
-
return
|
|
4039
|
+
return null;
|
|
3865
4040
|
}
|
|
3866
|
-
let bundle;
|
|
3867
4041
|
try {
|
|
3868
|
-
|
|
4042
|
+
return validateScanBundle(parsedManifest);
|
|
3869
4043
|
} catch (error) {
|
|
3870
4044
|
addIssue(report, "error", {
|
|
3871
4045
|
code: "bundle.invalid",
|
|
3872
4046
|
message: error instanceof Error ? error.message : String(error)
|
|
3873
4047
|
});
|
|
4048
|
+
return null;
|
|
4049
|
+
}
|
|
4050
|
+
}
|
|
4051
|
+
function createArtifactResult(artifact, overrides = {}) {
|
|
4052
|
+
return {
|
|
4053
|
+
role: artifact.role,
|
|
4054
|
+
fileName: artifact.fileName,
|
|
4055
|
+
required: artifact.required,
|
|
4056
|
+
schemaVersion: artifact.schemaVersion,
|
|
4057
|
+
hashStatus: "missing",
|
|
4058
|
+
schemaStatus: artifact.role === "summary" ? "skipped" : "invalid",
|
|
4059
|
+
metadataStatus: "skipped",
|
|
4060
|
+
...overrides
|
|
4061
|
+
};
|
|
4062
|
+
}
|
|
4063
|
+
function checkHash(ctx) {
|
|
4064
|
+
if (sha256Hex(ctx.content) === ctx.artifact.sha256) {
|
|
4065
|
+
ctx.result.hashStatus = "match";
|
|
4066
|
+
} else {
|
|
4067
|
+
ctx.result.hashStatus = "mismatch";
|
|
4068
|
+
addIssue(ctx.report, metadataSeverity(ctx.strict), {
|
|
4069
|
+
artifact: ctx.artifact.fileName,
|
|
4070
|
+
code: "artifact.sha256",
|
|
4071
|
+
message: `${ctx.artifact.fileName} does not match the SHA-256 recorded in the bundle`
|
|
4072
|
+
});
|
|
4073
|
+
}
|
|
4074
|
+
}
|
|
4075
|
+
function handleSummaryArtifact(ctx) {
|
|
4076
|
+
ctx.result.schemaStatus = "skipped";
|
|
4077
|
+
ctx.result.metadataStatus = "skipped";
|
|
4078
|
+
if (ctx.artifact.mimeType && ctx.artifact.mimeType !== "text/markdown") {
|
|
4079
|
+
addIssue(ctx.report, metadataSeverity(ctx.strict), {
|
|
4080
|
+
artifact: ctx.artifact.fileName,
|
|
4081
|
+
code: "artifact.mimeType",
|
|
4082
|
+
message: `${ctx.artifact.fileName} has unexpected mimeType '${ctx.artifact.mimeType}'`
|
|
4083
|
+
});
|
|
4084
|
+
}
|
|
4085
|
+
}
|
|
4086
|
+
function extractSchemaVersion2(parsed) {
|
|
4087
|
+
if (!parsed || typeof parsed !== "object") return void 0;
|
|
4088
|
+
const candidate = parsed.schemaVersion;
|
|
4089
|
+
return typeof candidate === "string" ? candidate : void 0;
|
|
4090
|
+
}
|
|
4091
|
+
function checkSchemaVersionConsistency(ctx, actualSchemaVersion) {
|
|
4092
|
+
if (ctx.artifact.schemaVersion && ctx.artifact.schemaVersion !== actualSchemaVersion) {
|
|
4093
|
+
addIssue(ctx.report, "error", {
|
|
4094
|
+
artifact: ctx.artifact.fileName,
|
|
4095
|
+
code: "artifact.schemaVersion.mismatch",
|
|
4096
|
+
message: `${ctx.artifact.fileName} schemaVersion '${actualSchemaVersion}' does not match manifest '${ctx.artifact.schemaVersion}'`
|
|
4097
|
+
});
|
|
4098
|
+
}
|
|
4099
|
+
const bundleMajor = getSchemaMajorVersion(ctx.bundle.schemaVersion);
|
|
4100
|
+
if (bundleMajor && getSchemaMajorVersion(actualSchemaVersion) !== bundleMajor) {
|
|
4101
|
+
addIssue(ctx.report, "error", {
|
|
4102
|
+
artifact: ctx.artifact.fileName,
|
|
4103
|
+
code: "artifact.schemaVersion.major",
|
|
4104
|
+
message: `${ctx.artifact.fileName} uses schemaVersion '${actualSchemaVersion}' with a different major than the bundle`
|
|
4105
|
+
});
|
|
4106
|
+
}
|
|
4107
|
+
}
|
|
4108
|
+
function recordMetadataIssues(ctx, validatedDocument) {
|
|
4109
|
+
const metadataIssues = compareJsonMetadata(ctx.bundle, ctx.artifact, validatedDocument);
|
|
4110
|
+
if (metadataIssues.length === 0) {
|
|
4111
|
+
ctx.result.metadataStatus = "match";
|
|
4112
|
+
return;
|
|
4113
|
+
}
|
|
4114
|
+
ctx.result.metadataStatus = "mismatch";
|
|
4115
|
+
for (const issue of metadataIssues) {
|
|
4116
|
+
addIssue(ctx.report, metadataSeverity(ctx.strict), issue);
|
|
4117
|
+
}
|
|
4118
|
+
}
|
|
4119
|
+
function verifyJsonArtifact(ctx) {
|
|
4120
|
+
let parsedArtifact;
|
|
4121
|
+
try {
|
|
4122
|
+
parsedArtifact = JSON.parse(ctx.content);
|
|
4123
|
+
} catch {
|
|
4124
|
+
ctx.result.schemaStatus = "invalid";
|
|
4125
|
+
addIssue(ctx.report, "error", {
|
|
4126
|
+
artifact: ctx.artifact.fileName,
|
|
4127
|
+
code: "artifact.parse",
|
|
4128
|
+
message: `${ctx.artifact.fileName} is not valid JSON`
|
|
4129
|
+
});
|
|
4130
|
+
return;
|
|
4131
|
+
}
|
|
4132
|
+
const actualSchemaVersion = extractSchemaVersion2(parsedArtifact);
|
|
4133
|
+
if (!actualSchemaVersion) {
|
|
4134
|
+
ctx.result.schemaStatus = "invalid";
|
|
4135
|
+
addIssue(ctx.report, "error", {
|
|
4136
|
+
artifact: ctx.artifact.fileName,
|
|
4137
|
+
code: "artifact.schemaVersion",
|
|
4138
|
+
message: `${ctx.artifact.fileName} is missing a string schemaVersion`
|
|
4139
|
+
});
|
|
4140
|
+
return;
|
|
4141
|
+
}
|
|
4142
|
+
ctx.result.schemaVersion = actualSchemaVersion;
|
|
4143
|
+
checkSchemaVersionConsistency(ctx, actualSchemaVersion);
|
|
4144
|
+
let validatedDocument;
|
|
4145
|
+
try {
|
|
4146
|
+
validatedDocument = validateJsonArtifactForRole(ctx.artifact, parsedArtifact);
|
|
4147
|
+
ctx.result.schemaStatus = "valid";
|
|
4148
|
+
} catch (error) {
|
|
4149
|
+
ctx.result.schemaStatus = "invalid";
|
|
4150
|
+
addIssue(ctx.report, "error", {
|
|
4151
|
+
artifact: ctx.artifact.fileName,
|
|
4152
|
+
code: "artifact.invalid",
|
|
4153
|
+
message: error instanceof Error ? error.message : String(error)
|
|
4154
|
+
});
|
|
4155
|
+
return;
|
|
4156
|
+
}
|
|
4157
|
+
recordMetadataIssues(ctx, validatedDocument);
|
|
4158
|
+
}
|
|
4159
|
+
async function verifyArtifact(artifact, bundle, loaded, report, strict) {
|
|
4160
|
+
const result = createArtifactResult(artifact);
|
|
4161
|
+
const content = await loaded.artifactLoader(artifact.fileName);
|
|
4162
|
+
if (content === null) {
|
|
4163
|
+
addIssue(report, "error", {
|
|
4164
|
+
artifact: artifact.fileName,
|
|
4165
|
+
code: "artifact.missing",
|
|
4166
|
+
message: `Referenced artifact '${artifact.fileName}' is missing`
|
|
4167
|
+
});
|
|
4168
|
+
return result;
|
|
4169
|
+
}
|
|
4170
|
+
const ctx = { artifact, bundle, content, report, result, strict };
|
|
4171
|
+
checkHash(ctx);
|
|
4172
|
+
if (artifact.role === "summary") {
|
|
4173
|
+
handleSummaryArtifact(ctx);
|
|
4174
|
+
return result;
|
|
4175
|
+
}
|
|
4176
|
+
verifyJsonArtifact(ctx);
|
|
4177
|
+
return result;
|
|
4178
|
+
}
|
|
4179
|
+
async function verifyBundleInput(inputPath, options = {}) {
|
|
4180
|
+
const loaded = await loadBundleInput(inputPath);
|
|
4181
|
+
const report = createReport(loaded.bundlePath, loaded.inputType);
|
|
4182
|
+
const bundle = parseBundleManifest(report, loaded.manifestContent);
|
|
4183
|
+
if (!bundle) {
|
|
3874
4184
|
return finalizeReport(report);
|
|
3875
4185
|
}
|
|
3876
4186
|
report.bundle = {
|
|
@@ -3879,118 +4189,9 @@ async function verifyBundleInput(inputPath, options = {}) {
|
|
|
3879
4189
|
tool: bundle.tool,
|
|
3880
4190
|
target: bundle.target
|
|
3881
4191
|
};
|
|
3882
|
-
const
|
|
4192
|
+
const strict = Boolean(options.strict);
|
|
3883
4193
|
for (const artifact of bundle.artifacts) {
|
|
3884
|
-
const result =
|
|
3885
|
-
role: artifact.role,
|
|
3886
|
-
fileName: artifact.fileName,
|
|
3887
|
-
required: artifact.required,
|
|
3888
|
-
schemaVersion: artifact.schemaVersion,
|
|
3889
|
-
hashStatus: "missing",
|
|
3890
|
-
schemaStatus: artifact.role === "summary" ? "skipped" : "invalid",
|
|
3891
|
-
metadataStatus: "skipped"
|
|
3892
|
-
};
|
|
3893
|
-
const content = await loaded.artifactLoader(artifact.fileName);
|
|
3894
|
-
if (content === null) {
|
|
3895
|
-
result.hashStatus = "missing";
|
|
3896
|
-
result.schemaStatus = artifact.role === "summary" ? "skipped" : "invalid";
|
|
3897
|
-
result.metadataStatus = "skipped";
|
|
3898
|
-
report.artifacts.push(result);
|
|
3899
|
-
addIssue(report, "error", {
|
|
3900
|
-
artifact: artifact.fileName,
|
|
3901
|
-
code: "artifact.missing",
|
|
3902
|
-
message: `Referenced artifact '${artifact.fileName}' is missing`
|
|
3903
|
-
});
|
|
3904
|
-
continue;
|
|
3905
|
-
}
|
|
3906
|
-
if (sha256Hex(content) === artifact.sha256) {
|
|
3907
|
-
result.hashStatus = "match";
|
|
3908
|
-
} else {
|
|
3909
|
-
result.hashStatus = "mismatch";
|
|
3910
|
-
addIssue(report, metadataSeverity(Boolean(options.strict)), {
|
|
3911
|
-
artifact: artifact.fileName,
|
|
3912
|
-
code: "artifact.sha256",
|
|
3913
|
-
message: `${artifact.fileName} does not match the SHA-256 recorded in the bundle`
|
|
3914
|
-
});
|
|
3915
|
-
}
|
|
3916
|
-
if (artifact.role === "summary") {
|
|
3917
|
-
result.schemaStatus = "skipped";
|
|
3918
|
-
result.metadataStatus = "skipped";
|
|
3919
|
-
if (artifact.mimeType && artifact.mimeType !== "text/markdown") {
|
|
3920
|
-
addIssue(report, metadataSeverity(Boolean(options.strict)), {
|
|
3921
|
-
artifact: artifact.fileName,
|
|
3922
|
-
code: "artifact.mimeType",
|
|
3923
|
-
message: `${artifact.fileName} has unexpected mimeType '${artifact.mimeType}'`
|
|
3924
|
-
});
|
|
3925
|
-
}
|
|
3926
|
-
report.artifacts.push(result);
|
|
3927
|
-
continue;
|
|
3928
|
-
}
|
|
3929
|
-
let parsedArtifact;
|
|
3930
|
-
try {
|
|
3931
|
-
parsedArtifact = JSON.parse(content);
|
|
3932
|
-
} catch {
|
|
3933
|
-
result.schemaStatus = "invalid";
|
|
3934
|
-
result.metadataStatus = "skipped";
|
|
3935
|
-
report.artifacts.push(result);
|
|
3936
|
-
addIssue(report, "error", {
|
|
3937
|
-
artifact: artifact.fileName,
|
|
3938
|
-
code: "artifact.parse",
|
|
3939
|
-
message: `${artifact.fileName} is not valid JSON`
|
|
3940
|
-
});
|
|
3941
|
-
continue;
|
|
3942
|
-
}
|
|
3943
|
-
const actualSchemaVersion = parsedArtifact && typeof parsedArtifact === "object" ? parsedArtifact.schemaVersion : void 0;
|
|
3944
|
-
if (typeof actualSchemaVersion !== "string") {
|
|
3945
|
-
result.schemaStatus = "invalid";
|
|
3946
|
-
result.metadataStatus = "skipped";
|
|
3947
|
-
report.artifacts.push(result);
|
|
3948
|
-
addIssue(report, "error", {
|
|
3949
|
-
artifact: artifact.fileName,
|
|
3950
|
-
code: "artifact.schemaVersion",
|
|
3951
|
-
message: `${artifact.fileName} is missing a string schemaVersion`
|
|
3952
|
-
});
|
|
3953
|
-
continue;
|
|
3954
|
-
}
|
|
3955
|
-
result.schemaVersion = actualSchemaVersion;
|
|
3956
|
-
if (artifact.schemaVersion && artifact.schemaVersion !== actualSchemaVersion) {
|
|
3957
|
-
addIssue(report, "error", {
|
|
3958
|
-
artifact: artifact.fileName,
|
|
3959
|
-
code: "artifact.schemaVersion.mismatch",
|
|
3960
|
-
message: `${artifact.fileName} schemaVersion '${actualSchemaVersion}' does not match manifest '${artifact.schemaVersion}'`
|
|
3961
|
-
});
|
|
3962
|
-
}
|
|
3963
|
-
if (bundleMajor && getSchemaMajorVersion(actualSchemaVersion) !== bundleMajor) {
|
|
3964
|
-
addIssue(report, "error", {
|
|
3965
|
-
artifact: artifact.fileName,
|
|
3966
|
-
code: "artifact.schemaVersion.major",
|
|
3967
|
-
message: `${artifact.fileName} uses schemaVersion '${actualSchemaVersion}' with a different major than the bundle`
|
|
3968
|
-
});
|
|
3969
|
-
}
|
|
3970
|
-
let validatedDocument;
|
|
3971
|
-
try {
|
|
3972
|
-
validatedDocument = validateJsonArtifactForRole(artifact, parsedArtifact);
|
|
3973
|
-
result.schemaStatus = "valid";
|
|
3974
|
-
} catch (error) {
|
|
3975
|
-
result.schemaStatus = "invalid";
|
|
3976
|
-
result.metadataStatus = "skipped";
|
|
3977
|
-
report.artifacts.push(result);
|
|
3978
|
-
addIssue(report, "error", {
|
|
3979
|
-
artifact: artifact.fileName,
|
|
3980
|
-
code: "artifact.invalid",
|
|
3981
|
-
message: error instanceof Error ? error.message : String(error)
|
|
3982
|
-
});
|
|
3983
|
-
continue;
|
|
3984
|
-
}
|
|
3985
|
-
const metadataIssues = compareJsonMetadata(bundle, artifact, validatedDocument);
|
|
3986
|
-
if (metadataIssues.length === 0) {
|
|
3987
|
-
result.metadataStatus = "match";
|
|
3988
|
-
} else {
|
|
3989
|
-
result.metadataStatus = "mismatch";
|
|
3990
|
-
for (const issue of metadataIssues) {
|
|
3991
|
-
addIssue(report, metadataSeverity(Boolean(options.strict)), issue);
|
|
3992
|
-
}
|
|
3993
|
-
}
|
|
4194
|
+
const result = await verifyArtifact(artifact, bundle, loaded, report, strict);
|
|
3994
4195
|
report.artifacts.push(result);
|
|
3995
4196
|
}
|
|
3996
4197
|
return finalizeReport(report);
|
|
@@ -4007,7 +4208,7 @@ function shouldFailVerifyReport(report, failOn) {
|
|
|
4007
4208
|
|
|
4008
4209
|
// src/commands/verify.ts
|
|
4009
4210
|
var VALID_FAIL_ON = /* @__PURE__ */ new Set(["warnings", "errors"]);
|
|
4010
|
-
var verify_default =
|
|
4211
|
+
var verify_default = defineCommand3({
|
|
4011
4212
|
meta: {
|
|
4012
4213
|
name: "verify",
|
|
4013
4214
|
description: "Verify a EuConform bundle manifest, directory, or ZIP archive"
|
|
@@ -4035,10 +4236,10 @@ var verify_default = defineCommand2({
|
|
|
4035
4236
|
}
|
|
4036
4237
|
},
|
|
4037
4238
|
async run({ args }) {
|
|
4038
|
-
const inputPath =
|
|
4239
|
+
const inputPath = resolve5(args.path);
|
|
4039
4240
|
const failOn = args["fail-on"] ?? "errors";
|
|
4040
4241
|
if (!VALID_FAIL_ON.has(failOn)) {
|
|
4041
|
-
|
|
4242
|
+
consola8.error(`Invalid fail-on level: ${failOn}. Use one of: warnings, errors.`);
|
|
4042
4243
|
process.exit(1);
|
|
4043
4244
|
}
|
|
4044
4245
|
try {
|
|
@@ -4072,7 +4273,7 @@ var verify_default = defineCommand2({
|
|
|
4072
4273
|
`
|
|
4073
4274
|
);
|
|
4074
4275
|
} else {
|
|
4075
|
-
|
|
4276
|
+
consola8.error(message);
|
|
4076
4277
|
}
|
|
4077
4278
|
process.exit(1);
|
|
4078
4279
|
}
|
|
@@ -4080,14 +4281,15 @@ var verify_default = defineCommand2({
|
|
|
4080
4281
|
});
|
|
4081
4282
|
|
|
4082
4283
|
// src/index.ts
|
|
4083
|
-
var main =
|
|
4284
|
+
var main = defineCommand4({
|
|
4084
4285
|
meta: {
|
|
4085
4286
|
name: "euconform",
|
|
4086
4287
|
description: "EU AI Act compliance scanner for local AI projects"
|
|
4087
4288
|
},
|
|
4088
4289
|
subCommands: {
|
|
4089
4290
|
scan: scan_default,
|
|
4090
|
-
verify: verify_default
|
|
4291
|
+
verify: verify_default,
|
|
4292
|
+
bias: bias_default
|
|
4091
4293
|
}
|
|
4092
4294
|
});
|
|
4093
4295
|
runMain(main);
|