audrey 0.23.1 → 1.0.1
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/CHANGELOG.md +101 -15
- package/LICENSE +21 -21
- package/README.md +232 -6
- package/SECURITY.md +2 -1
- package/benchmarks/adapter-kit.mjs +20 -0
- package/benchmarks/adapter-self-test.mjs +166 -0
- package/benchmarks/adapters/example-allow.mjs +28 -0
- package/benchmarks/adapters/mem0-platform.mjs +267 -0
- package/benchmarks/adapters/registry.json +51 -0
- package/benchmarks/adapters/zep-cloud.mjs +280 -0
- package/benchmarks/baselines.js +169 -0
- package/benchmarks/build-leaderboard.mjs +170 -0
- package/benchmarks/cases.js +537 -0
- package/benchmarks/create-conformance-card.mjs +139 -0
- package/benchmarks/create-submission-bundle.mjs +176 -0
- package/benchmarks/dry-run-external-adapters.mjs +165 -0
- package/benchmarks/guardbench.js +1125 -0
- package/benchmarks/output/adapter-self-test/guardbench-adapter-self-test.json +50 -0
- package/benchmarks/output/external/guardbench-external-dry-run.json +69 -0
- package/benchmarks/output/external/guardbench-external-evidence.json +56 -0
- package/benchmarks/output/guardbench-conformance-card.json +63 -0
- package/benchmarks/output/guardbench-manifest.json +414 -0
- package/benchmarks/output/guardbench-raw.json +1271 -0
- package/benchmarks/output/guardbench-summary.json +2107 -0
- package/benchmarks/output/leaderboard/guardbench-leaderboard.json +93 -0
- package/benchmarks/output/leaderboard/guardbench-leaderboard.md +7 -0
- package/benchmarks/output/submission-bundle/guardbench-conformance-card.json +63 -0
- package/benchmarks/output/submission-bundle/guardbench-manifest.json +414 -0
- package/benchmarks/output/submission-bundle/guardbench-raw.json +1271 -0
- package/benchmarks/output/submission-bundle/guardbench-summary.json +2107 -0
- package/benchmarks/output/submission-bundle/schemas/guardbench-adapter-registry.schema.json +69 -0
- package/benchmarks/output/submission-bundle/schemas/guardbench-adapter-self-test.schema.json +156 -0
- package/benchmarks/output/submission-bundle/schemas/guardbench-conformance-card.schema.json +184 -0
- package/benchmarks/output/submission-bundle/schemas/guardbench-external-dry-run.schema.json +74 -0
- package/benchmarks/output/submission-bundle/schemas/guardbench-external-evidence.schema.json +108 -0
- package/benchmarks/output/submission-bundle/schemas/guardbench-external-run.schema.json +160 -0
- package/benchmarks/output/submission-bundle/schemas/guardbench-leaderboard.schema.json +179 -0
- package/benchmarks/output/submission-bundle/schemas/guardbench-manifest.schema.json +213 -0
- package/benchmarks/output/submission-bundle/schemas/guardbench-publication-verification.schema.json +47 -0
- package/benchmarks/output/submission-bundle/schemas/guardbench-raw.schema.json +184 -0
- package/benchmarks/output/submission-bundle/schemas/guardbench-submission-manifest.schema.json +151 -0
- package/benchmarks/output/submission-bundle/schemas/guardbench-summary.schema.json +249 -0
- package/benchmarks/output/submission-bundle/submission-manifest.json +131 -0
- package/benchmarks/output/submission-bundle/validation-report.json +31 -0
- package/benchmarks/output/summary.json +2354 -0
- package/benchmarks/perf-snapshot.js +304 -0
- package/benchmarks/perf.bench.js +161 -0
- package/benchmarks/public-paths.mjs +78 -0
- package/benchmarks/reference-results.js +70 -0
- package/benchmarks/report.js +259 -0
- package/benchmarks/run-external-guardbench.mjs +281 -0
- package/benchmarks/run.js +682 -0
- package/benchmarks/schemas/guardbench-adapter-registry.schema.json +69 -0
- package/benchmarks/schemas/guardbench-adapter-self-test.schema.json +156 -0
- package/benchmarks/schemas/guardbench-conformance-card.schema.json +184 -0
- package/benchmarks/schemas/guardbench-external-dry-run.schema.json +74 -0
- package/benchmarks/schemas/guardbench-external-evidence.schema.json +108 -0
- package/benchmarks/schemas/guardbench-external-run.schema.json +160 -0
- package/benchmarks/schemas/guardbench-leaderboard.schema.json +179 -0
- package/benchmarks/schemas/guardbench-manifest.schema.json +213 -0
- package/benchmarks/schemas/guardbench-publication-verification.schema.json +47 -0
- package/benchmarks/schemas/guardbench-raw.schema.json +184 -0
- package/benchmarks/schemas/guardbench-submission-manifest.schema.json +151 -0
- package/benchmarks/schemas/guardbench-summary.schema.json +249 -0
- package/benchmarks/snapshots/perf-0.22.2.json +123 -0
- package/benchmarks/snapshots/perf-0.23.0.json +123 -0
- package/benchmarks/validate-adapter-module.mjs +104 -0
- package/benchmarks/validate-adapter-registry.mjs +134 -0
- package/benchmarks/validate-adapter-self-test.mjs +96 -0
- package/benchmarks/validate-guardbench-artifacts.mjs +343 -0
- package/benchmarks/verify-external-evidence.mjs +296 -0
- package/benchmarks/verify-publication-artifacts.mjs +286 -0
- package/benchmarks/verify-submission-bundle.mjs +167 -0
- package/dist/mcp-server/config.d.ts +1 -1
- package/dist/mcp-server/config.d.ts.map +1 -1
- package/dist/mcp-server/config.js +1 -1
- package/dist/mcp-server/config.js.map +1 -1
- package/dist/mcp-server/index.d.ts +65 -3
- package/dist/mcp-server/index.d.ts.map +1 -1
- package/dist/mcp-server/index.js +675 -157
- package/dist/mcp-server/index.js.map +1 -1
- package/dist/src/action-key.d.ts +9 -0
- package/dist/src/action-key.d.ts.map +1 -0
- package/dist/src/action-key.js +49 -0
- package/dist/src/action-key.js.map +1 -0
- package/dist/src/adaptive.js +5 -5
- package/dist/src/affect.js +8 -8
- package/dist/src/audrey.d.ts +13 -0
- package/dist/src/audrey.d.ts.map +1 -1
- package/dist/src/audrey.js +68 -3
- package/dist/src/audrey.js.map +1 -1
- package/dist/src/capsule.js +4 -4
- package/dist/src/causal.js +3 -3
- package/dist/src/consolidate.js +48 -48
- package/dist/src/controller.d.ts +78 -6
- package/dist/src/controller.d.ts.map +1 -1
- package/dist/src/controller.js +273 -53
- package/dist/src/controller.js.map +1 -1
- package/dist/src/db.js +172 -172
- package/dist/src/decay.js +8 -8
- package/dist/src/embedding.d.ts +2 -1
- package/dist/src/embedding.d.ts.map +1 -1
- package/dist/src/embedding.js +39 -29
- package/dist/src/embedding.js.map +1 -1
- package/dist/src/encode.js +6 -6
- package/dist/src/feedback.d.ts +6 -0
- package/dist/src/feedback.d.ts.map +1 -1
- package/dist/src/feedback.js +6 -0
- package/dist/src/feedback.js.map +1 -1
- package/dist/src/forget.js +12 -12
- package/dist/src/hybrid-recall.js +9 -9
- package/dist/src/impact.js +6 -6
- package/dist/src/import.d.ts +3 -3
- package/dist/src/import.js +41 -41
- package/dist/src/index.d.ts +5 -4
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +3 -3
- package/dist/src/index.js.map +1 -1
- package/dist/src/interference.js +14 -14
- package/dist/src/introspect.js +18 -18
- package/dist/src/preflight.d.ts.map +1 -1
- package/dist/src/preflight.js +41 -0
- package/dist/src/preflight.js.map +1 -1
- package/dist/src/promote.js +7 -7
- package/dist/src/prompts.js +118 -118
- package/dist/src/recall.js +30 -30
- package/dist/src/reflexes.d.ts +1 -0
- package/dist/src/reflexes.d.ts.map +1 -1
- package/dist/src/reflexes.js +3 -0
- package/dist/src/reflexes.js.map +1 -1
- package/dist/src/rollback.js +4 -4
- package/dist/src/routes.d.ts.map +1 -1
- package/dist/src/routes.js +71 -2
- package/dist/src/routes.js.map +1 -1
- package/dist/src/validate.js +25 -25
- package/docs/AUDREY_PAPER_OUTLINE.md +175 -0
- package/docs/MEMORY_BENCHMARKING.md +59 -0
- package/docs/PRODUCTION_BACKLOG.md +304 -0
- package/docs/paper/00-master.md +48 -0
- package/docs/paper/01-introduction.md +27 -0
- package/docs/paper/02-related-work.md +47 -0
- package/docs/paper/03-problem-definition.md +108 -0
- package/docs/paper/04-design.md +164 -0
- package/docs/paper/05-guardbench-spec.md +412 -0
- package/docs/paper/06-implementation.md +113 -0
- package/docs/paper/07-evaluation.md +168 -0
- package/docs/paper/08-discussion-limitations.md +61 -0
- package/docs/paper/09-conclusion.md +11 -0
- package/docs/paper/SUBMISSION_README.md +162 -0
- package/docs/paper/appendix-a-demo-transcript.md +114 -0
- package/docs/paper/arxiv-compile-report.schema.json +116 -0
- package/docs/paper/arxiv-source.schema.json +61 -0
- package/docs/paper/audrey-paper-v1.md +1106 -0
- package/docs/paper/browser-launch-plan.json +209 -0
- package/docs/paper/browser-launch-plan.schema.json +100 -0
- package/docs/paper/browser-launch-results.json +86 -0
- package/docs/paper/browser-launch-results.schema.json +66 -0
- package/docs/paper/claim-register.json +138 -0
- package/docs/paper/claim-register.schema.json +81 -0
- package/docs/paper/evidence-ledger.md +103 -0
- package/docs/paper/output/arxiv/README-arxiv.txt +8 -0
- package/docs/paper/output/arxiv/arxiv-manifest.json +41 -0
- package/docs/paper/output/arxiv/main.tex +949 -0
- package/docs/paper/output/arxiv/references.bib +222 -0
- package/docs/paper/output/arxiv-compile-report.json +24 -0
- package/docs/paper/output/submission-bundle/LICENSE +21 -0
- package/docs/paper/output/submission-bundle/README.md +555 -0
- package/docs/paper/output/submission-bundle/benchmarks/output/adapter-self-test/guardbench-adapter-self-test.json +50 -0
- package/docs/paper/output/submission-bundle/benchmarks/output/external/guardbench-external-dry-run.json +69 -0
- package/docs/paper/output/submission-bundle/benchmarks/output/external/guardbench-external-evidence.json +56 -0
- package/docs/paper/output/submission-bundle/benchmarks/output/guardbench-conformance-card.json +63 -0
- package/docs/paper/output/submission-bundle/benchmarks/output/guardbench-manifest.json +414 -0
- package/docs/paper/output/submission-bundle/benchmarks/output/guardbench-raw.json +1271 -0
- package/docs/paper/output/submission-bundle/benchmarks/output/guardbench-summary.json +2107 -0
- package/docs/paper/output/submission-bundle/benchmarks/output/leaderboard/guardbench-leaderboard.json +93 -0
- package/docs/paper/output/submission-bundle/benchmarks/output/leaderboard/guardbench-leaderboard.md +7 -0
- package/docs/paper/output/submission-bundle/benchmarks/output/submission-bundle/submission-manifest.json +131 -0
- package/docs/paper/output/submission-bundle/benchmarks/output/submission-bundle/validation-report.json +31 -0
- package/docs/paper/output/submission-bundle/benchmarks/output/summary.json +2354 -0
- package/docs/paper/output/submission-bundle/benchmarks/schemas/guardbench-adapter-registry.schema.json +69 -0
- package/docs/paper/output/submission-bundle/benchmarks/schemas/guardbench-adapter-self-test.schema.json +156 -0
- package/docs/paper/output/submission-bundle/benchmarks/schemas/guardbench-conformance-card.schema.json +184 -0
- package/docs/paper/output/submission-bundle/benchmarks/schemas/guardbench-external-dry-run.schema.json +74 -0
- package/docs/paper/output/submission-bundle/benchmarks/schemas/guardbench-external-evidence.schema.json +108 -0
- package/docs/paper/output/submission-bundle/benchmarks/schemas/guardbench-external-run.schema.json +160 -0
- package/docs/paper/output/submission-bundle/benchmarks/schemas/guardbench-leaderboard.schema.json +179 -0
- package/docs/paper/output/submission-bundle/benchmarks/schemas/guardbench-manifest.schema.json +213 -0
- package/docs/paper/output/submission-bundle/benchmarks/schemas/guardbench-publication-verification.schema.json +47 -0
- package/docs/paper/output/submission-bundle/benchmarks/schemas/guardbench-raw.schema.json +184 -0
- package/docs/paper/output/submission-bundle/benchmarks/schemas/guardbench-submission-manifest.schema.json +151 -0
- package/docs/paper/output/submission-bundle/benchmarks/schemas/guardbench-summary.schema.json +249 -0
- package/docs/paper/output/submission-bundle/docs/AUDREY_PAPER_OUTLINE.md +175 -0
- package/docs/paper/output/submission-bundle/docs/paper/00-master.md +48 -0
- package/docs/paper/output/submission-bundle/docs/paper/01-introduction.md +27 -0
- package/docs/paper/output/submission-bundle/docs/paper/02-related-work.md +47 -0
- package/docs/paper/output/submission-bundle/docs/paper/03-problem-definition.md +108 -0
- package/docs/paper/output/submission-bundle/docs/paper/04-design.md +164 -0
- package/docs/paper/output/submission-bundle/docs/paper/05-guardbench-spec.md +412 -0
- package/docs/paper/output/submission-bundle/docs/paper/06-implementation.md +113 -0
- package/docs/paper/output/submission-bundle/docs/paper/07-evaluation.md +168 -0
- package/docs/paper/output/submission-bundle/docs/paper/08-discussion-limitations.md +61 -0
- package/docs/paper/output/submission-bundle/docs/paper/09-conclusion.md +11 -0
- package/docs/paper/output/submission-bundle/docs/paper/SUBMISSION_README.md +162 -0
- package/docs/paper/output/submission-bundle/docs/paper/appendix-a-demo-transcript.md +114 -0
- package/docs/paper/output/submission-bundle/docs/paper/arxiv-compile-report.schema.json +116 -0
- package/docs/paper/output/submission-bundle/docs/paper/arxiv-source.schema.json +61 -0
- package/docs/paper/output/submission-bundle/docs/paper/audrey-paper-v1.md +1106 -0
- package/docs/paper/output/submission-bundle/docs/paper/browser-launch-plan.json +209 -0
- package/docs/paper/output/submission-bundle/docs/paper/browser-launch-plan.schema.json +100 -0
- package/docs/paper/output/submission-bundle/docs/paper/browser-launch-results.json +86 -0
- package/docs/paper/output/submission-bundle/docs/paper/browser-launch-results.schema.json +66 -0
- package/docs/paper/output/submission-bundle/docs/paper/claim-register.json +138 -0
- package/docs/paper/output/submission-bundle/docs/paper/claim-register.schema.json +81 -0
- package/docs/paper/output/submission-bundle/docs/paper/evidence-ledger.md +103 -0
- package/docs/paper/output/submission-bundle/docs/paper/output/arxiv/README-arxiv.txt +8 -0
- package/docs/paper/output/submission-bundle/docs/paper/output/arxiv/arxiv-manifest.json +41 -0
- package/docs/paper/output/submission-bundle/docs/paper/output/arxiv/main.tex +949 -0
- package/docs/paper/output/submission-bundle/docs/paper/output/arxiv/references.bib +222 -0
- package/docs/paper/output/submission-bundle/docs/paper/output/arxiv-compile-report.json +24 -0
- package/docs/paper/output/submission-bundle/docs/paper/paper-submission-bundle.schema.json +70 -0
- package/docs/paper/output/submission-bundle/docs/paper/publication-pack.json +81 -0
- package/docs/paper/output/submission-bundle/docs/paper/publication-pack.schema.json +60 -0
- package/docs/paper/output/submission-bundle/docs/paper/references.bib +222 -0
- package/docs/paper/output/submission-bundle/package.json +212 -0
- package/docs/paper/output/submission-bundle/paper-submission-manifest.json +379 -0
- package/docs/paper/paper-submission-bundle.schema.json +70 -0
- package/docs/paper/publication-pack.json +81 -0
- package/docs/paper/publication-pack.schema.json +60 -0
- package/docs/paper/references.bib +222 -0
- package/package.json +87 -4
- package/scripts/audit-release-completion.mjs +362 -0
- package/scripts/create-arxiv-source.mjs +362 -0
- package/scripts/create-paper-submission-bundle.mjs +210 -0
- package/scripts/finalize-release.mjs +526 -0
- package/scripts/prepare-release-cut.mjs +269 -0
- package/scripts/publish-release-bundle.mjs +209 -0
- package/scripts/publish-release-github-api.mjs +429 -0
- package/scripts/run-vitest.mjs +34 -0
- package/scripts/smoke-cli.js +92 -0
- package/scripts/sync-paper-artifacts.mjs +109 -0
- package/scripts/verify-arxiv-compile.mjs +440 -0
- package/scripts/verify-arxiv-source.mjs +194 -0
- package/scripts/verify-browser-launch-plan.mjs +237 -0
- package/scripts/verify-browser-launch-results.mjs +285 -0
- package/scripts/verify-paper-artifacts.mjs +338 -0
- package/scripts/verify-paper-claims.mjs +226 -0
- package/scripts/verify-paper-submission-bundle.mjs +207 -0
- package/scripts/verify-publication-pack.mjs +196 -0
- package/scripts/verify-python-package.py +201 -0
- package/scripts/verify-release-readiness.mjs +785 -0
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
3
|
+
import { basename, join, resolve } from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import { publicPath } from './public-paths.mjs';
|
|
6
|
+
|
|
7
|
+
const DEFAULT_DIR = 'benchmarks/output';
|
|
8
|
+
const DEFAULT_SEEDED_SECRET = 'sk-guardbench-secret-0000000000000000000000000000';
|
|
9
|
+
const SCHEMA_FILES = {
|
|
10
|
+
manifest: 'guardbench-manifest.schema.json',
|
|
11
|
+
summary: 'guardbench-summary.schema.json',
|
|
12
|
+
raw: 'guardbench-raw.schema.json',
|
|
13
|
+
externalRun: 'guardbench-external-run.schema.json',
|
|
14
|
+
conformanceCard: 'guardbench-conformance-card.schema.json',
|
|
15
|
+
};
|
|
16
|
+
const ARTIFACT_FILES = {
|
|
17
|
+
manifest: 'guardbench-manifest.json',
|
|
18
|
+
summary: 'guardbench-summary.json',
|
|
19
|
+
raw: 'guardbench-raw.json',
|
|
20
|
+
};
|
|
21
|
+
const OPTIONAL_ARTIFACT_FILES = {
|
|
22
|
+
externalRun: 'external-run-metadata.json',
|
|
23
|
+
conformanceCard: 'guardbench-conformance-card.json',
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
function parseArgs(argv = process.argv.slice(2)) {
|
|
27
|
+
const args = {
|
|
28
|
+
dir: DEFAULT_DIR,
|
|
29
|
+
schemasDir: 'benchmarks/schemas',
|
|
30
|
+
seededSecrets: [DEFAULT_SEEDED_SECRET],
|
|
31
|
+
json: false,
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
for (let i = 0; i < argv.length; i++) {
|
|
35
|
+
const token = argv[i];
|
|
36
|
+
if ((token === '--dir' || token === '--out-dir') && argv[i + 1]) args.dir = argv[++i];
|
|
37
|
+
else if (token === '--schemas-dir' && argv[i + 1]) args.schemasDir = argv[++i];
|
|
38
|
+
else if (token === '--seeded-secret' && argv[i + 1]) args.seededSecrets.push(argv[++i]);
|
|
39
|
+
else if (token === '--no-default-secret') args.seededSecrets = [];
|
|
40
|
+
else if (token === '--json') args.json = true;
|
|
41
|
+
else if (token === '--help') {
|
|
42
|
+
return { ...args, help: true };
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return args;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function usage() {
|
|
50
|
+
return [
|
|
51
|
+
'Usage: node benchmarks/validate-guardbench-artifacts.mjs [--dir benchmarks/output] [--json]',
|
|
52
|
+
'',
|
|
53
|
+
'Validates guardbench-manifest.json, guardbench-summary.json, and',
|
|
54
|
+
'guardbench-raw.json against the published GuardBench JSON schemas.',
|
|
55
|
+
'',
|
|
56
|
+
'Options:',
|
|
57
|
+
' --dir <path> Directory containing GuardBench output artifacts.',
|
|
58
|
+
' --schemas-dir <path> Directory containing GuardBench schema files.',
|
|
59
|
+
' --seeded-secret <value> Additional seeded raw secret that must not appear.',
|
|
60
|
+
' --no-default-secret Do not check the built-in GuardBench redaction probe.',
|
|
61
|
+
' --json Print a machine-readable validation report.',
|
|
62
|
+
].join('\n');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function readText(path) {
|
|
66
|
+
if (!existsSync(path)) throw new Error(`Missing required file: ${path}`);
|
|
67
|
+
return readFileSync(path, 'utf-8');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function readJson(path) {
|
|
71
|
+
return JSON.parse(readText(path));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function sha256File(path) {
|
|
75
|
+
return createHash('sha256').update(readFileSync(path)).digest('hex');
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function computeGuardBenchArtifactHashes(dir, files = Object.values(ARTIFACT_FILES)) {
|
|
79
|
+
const resolvedDir = resolve(dir);
|
|
80
|
+
return Object.fromEntries(files.map(file => [file, sha256File(join(resolvedDir, file))]));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function typeOf(value) {
|
|
84
|
+
if (Array.isArray(value)) return 'array';
|
|
85
|
+
if (value === null) return 'null';
|
|
86
|
+
return typeof value;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function validateSchema(value, schema, label, root = schema) {
|
|
90
|
+
const errors = [];
|
|
91
|
+
|
|
92
|
+
function validate(current, currentSchema, path) {
|
|
93
|
+
if (currentSchema.$ref) {
|
|
94
|
+
const refPath = currentSchema.$ref.replace(/^#\//, '').split('/');
|
|
95
|
+
const resolved = refPath.reduce((node, key) => node?.[key], root);
|
|
96
|
+
if (!resolved) {
|
|
97
|
+
errors.push(`${path}: unresolved schema ref ${currentSchema.$ref}`);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
validate(current, resolved, path);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (currentSchema.anyOf) {
|
|
105
|
+
const nested = currentSchema.anyOf.map(option => {
|
|
106
|
+
const before = errors.length;
|
|
107
|
+
validate(current, option, path);
|
|
108
|
+
return errors.splice(before);
|
|
109
|
+
});
|
|
110
|
+
if (!nested.some(group => group.length === 0)) {
|
|
111
|
+
errors.push(`${path}: did not match any allowed schema`);
|
|
112
|
+
}
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (currentSchema.const !== undefined && current !== currentSchema.const) {
|
|
117
|
+
errors.push(`${path}: expected constant ${currentSchema.const}`);
|
|
118
|
+
}
|
|
119
|
+
if (currentSchema.enum && !currentSchema.enum.includes(current)) {
|
|
120
|
+
errors.push(`${path}: expected one of ${currentSchema.enum.join(', ')}`);
|
|
121
|
+
}
|
|
122
|
+
if (currentSchema.type === 'integer') {
|
|
123
|
+
if (typeof current !== 'number' || !Number.isInteger(current)) {
|
|
124
|
+
errors.push(`${path}: expected integer, got ${typeOf(current)}`);
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
} else if (currentSchema.type) {
|
|
128
|
+
const actual = typeOf(current);
|
|
129
|
+
if (actual !== currentSchema.type) {
|
|
130
|
+
errors.push(`${path}: expected ${currentSchema.type}, got ${actual}`);
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
if (currentSchema.minLength != null && String(current).length < currentSchema.minLength) {
|
|
135
|
+
errors.push(`${path}: shorter than minLength ${currentSchema.minLength}`);
|
|
136
|
+
}
|
|
137
|
+
if (currentSchema.pattern && typeof current === 'string' && !(new RegExp(currentSchema.pattern).test(current))) {
|
|
138
|
+
errors.push(`${path}: does not match ${currentSchema.pattern}`);
|
|
139
|
+
}
|
|
140
|
+
if (currentSchema.minimum != null && typeof current === 'number' && current < currentSchema.minimum) {
|
|
141
|
+
errors.push(`${path}: below minimum ${currentSchema.minimum}`);
|
|
142
|
+
}
|
|
143
|
+
if (currentSchema.maximum != null && typeof current === 'number' && current > currentSchema.maximum) {
|
|
144
|
+
errors.push(`${path}: above maximum ${currentSchema.maximum}`);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (currentSchema.type === 'array') {
|
|
148
|
+
if (currentSchema.minItems != null && current.length < currentSchema.minItems) {
|
|
149
|
+
errors.push(`${path}: expected at least ${currentSchema.minItems} items`);
|
|
150
|
+
}
|
|
151
|
+
if (currentSchema.items) {
|
|
152
|
+
current.forEach((item, index) => validate(item, currentSchema.items, `${path}[${index}]`));
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (currentSchema.type === 'object') {
|
|
157
|
+
for (const required of currentSchema.required ?? []) {
|
|
158
|
+
if (!Object.hasOwn(current, required)) errors.push(`${path}: missing required property ${required}`);
|
|
159
|
+
}
|
|
160
|
+
if (currentSchema.additionalProperties === false) {
|
|
161
|
+
for (const key of Object.keys(current)) {
|
|
162
|
+
if (!Object.hasOwn(currentSchema.properties ?? {}, key)) {
|
|
163
|
+
errors.push(`${path}: unexpected property ${key}`);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
for (const [key, propertySchema] of Object.entries(currentSchema.properties ?? {})) {
|
|
168
|
+
if (Object.hasOwn(current, key)) validate(current[key], propertySchema, `${path}.${key}`);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
validate(value, schema, label);
|
|
174
|
+
return errors;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function stableJson(value) {
|
|
178
|
+
if (Array.isArray(value)) return `[${value.map(stableJson).join(',')}]`;
|
|
179
|
+
if (value && typeof value === 'object') {
|
|
180
|
+
return `{${Object.keys(value).sort().map(key => `${JSON.stringify(key)}:${stableJson(value[key])}`).join(',')}}`;
|
|
181
|
+
}
|
|
182
|
+
return JSON.stringify(value);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function assertSameJson(actual, expected, label, failures) {
|
|
186
|
+
if (stableJson(actual) !== stableJson(expected)) {
|
|
187
|
+
failures.push(`${label}: cross-artifact mismatch`);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export function validateGuardBenchArtifacts(options = {}) {
|
|
192
|
+
const dir = resolve(options.dir ?? DEFAULT_DIR);
|
|
193
|
+
const schemasDir = resolve(options.schemasDir ?? 'benchmarks/schemas');
|
|
194
|
+
const seededSecrets = options.seededSecrets ?? [DEFAULT_SEEDED_SECRET];
|
|
195
|
+
const failures = [];
|
|
196
|
+
const artifacts = {};
|
|
197
|
+
const schemas = {};
|
|
198
|
+
const artifactPaths = {};
|
|
199
|
+
const optionalArtifacts = {};
|
|
200
|
+
|
|
201
|
+
for (const [key, file] of Object.entries(ARTIFACT_FILES)) {
|
|
202
|
+
artifactPaths[key] = join(dir, file);
|
|
203
|
+
try {
|
|
204
|
+
artifacts[key] = readJson(artifactPaths[key]);
|
|
205
|
+
} catch (error) {
|
|
206
|
+
failures.push(error.message);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
for (const [key, file] of Object.entries(SCHEMA_FILES)) {
|
|
211
|
+
try {
|
|
212
|
+
schemas[key] = readJson(join(schemasDir, file));
|
|
213
|
+
} catch (error) {
|
|
214
|
+
failures.push(error.message);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (failures.length === 0) {
|
|
219
|
+
for (const key of Object.keys(ARTIFACT_FILES)) {
|
|
220
|
+
for (const error of validateSchema(artifacts[key], schemas[key], `guardbench-${key}`)) {
|
|
221
|
+
failures.push(`${basename(artifactPaths[key])}: ${error}`);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
for (const [key, file] of Object.entries(OPTIONAL_ARTIFACT_FILES)) {
|
|
225
|
+
const path = join(dir, file);
|
|
226
|
+
if (!existsSync(path)) continue;
|
|
227
|
+
artifactPaths[key] = path;
|
|
228
|
+
try {
|
|
229
|
+
optionalArtifacts[key] = readJson(path);
|
|
230
|
+
} catch (error) {
|
|
231
|
+
failures.push(error.message);
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
for (const error of validateSchema(optionalArtifacts[key], schemas[key], `guardbench-${key}`)) {
|
|
235
|
+
failures.push(`${basename(path)}: ${error}`);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const externalRun = optionalArtifacts.externalRun;
|
|
240
|
+
if (externalRun?.artifactHashes) {
|
|
241
|
+
const currentHashes = computeGuardBenchArtifactHashes(dir);
|
|
242
|
+
for (const [file, expectedHash] of Object.entries(externalRun.artifactHashes)) {
|
|
243
|
+
if (!Object.hasOwn(currentHashes, file)) {
|
|
244
|
+
failures.push(`external-run-metadata.json: artifactHashes includes unknown file ${file}`);
|
|
245
|
+
} else if (currentHashes[file] !== expectedHash) {
|
|
246
|
+
failures.push(`external-run-metadata.json: artifactHashes.${file} does not match current artifact`);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
for (const file of Object.values(ARTIFACT_FILES)) {
|
|
250
|
+
if (!Object.hasOwn(externalRun.artifactHashes, file)) {
|
|
251
|
+
failures.push(`external-run-metadata.json: artifactHashes missing ${file}`);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
const conformanceCard = optionalArtifacts.conformanceCard;
|
|
256
|
+
if (conformanceCard) {
|
|
257
|
+
const currentHashes = computeGuardBenchArtifactHashes(dir);
|
|
258
|
+
for (const [file, expectedHash] of Object.entries(conformanceCard.integrity?.artifactHashes ?? {})) {
|
|
259
|
+
if (!Object.hasOwn(currentHashes, file)) {
|
|
260
|
+
failures.push(`guardbench-conformance-card.json: integrity.artifactHashes includes unknown file ${file}`);
|
|
261
|
+
} else if (currentHashes[file] !== expectedHash) {
|
|
262
|
+
failures.push(`guardbench-conformance-card.json: integrity.artifactHashes.${file} does not match current artifact`);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
if (conformanceCard.manifestVersion !== artifacts.manifest.manifestVersion) {
|
|
266
|
+
failures.push('guardbench-conformance-card.json: manifestVersion does not match guardbench-manifest.json');
|
|
267
|
+
}
|
|
268
|
+
if (conformanceCard.suiteId !== artifacts.manifest.suiteId) {
|
|
269
|
+
failures.push('guardbench-conformance-card.json: suiteId does not match guardbench-manifest.json');
|
|
270
|
+
}
|
|
271
|
+
if (!artifacts.summary.systemSummaries?.some(row => row.system === conformanceCard.subject?.name)) {
|
|
272
|
+
failures.push('guardbench-conformance-card.json: subject.name is not present in guardbench-summary.json');
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
assertSameJson(artifacts.summary.manifest, artifacts.manifest, 'summary.manifest vs guardbench-manifest.json', failures);
|
|
277
|
+
assertSameJson(artifacts.summary.cases, artifacts.raw.cases, 'summary.cases vs raw.cases', failures);
|
|
278
|
+
assertSameJson(artifacts.summary.provenance, artifacts.raw.provenance, 'summary.provenance vs raw.provenance', failures);
|
|
279
|
+
if (artifacts.summary.generatedAt !== artifacts.raw.generatedAt) {
|
|
280
|
+
failures.push('summary.generatedAt vs raw.generatedAt: cross-artifact mismatch');
|
|
281
|
+
}
|
|
282
|
+
if (artifacts.manifest.manifestVersion !== artifacts.raw.manifestVersion) {
|
|
283
|
+
failures.push('manifest.manifestVersion vs raw.manifestVersion: cross-artifact mismatch');
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
if (artifacts.summary.artifactRedactionSweep?.passed !== true) {
|
|
287
|
+
failures.push('guardbench-summary.json: artifactRedactionSweep did not pass');
|
|
288
|
+
}
|
|
289
|
+
if (artifacts.raw.artifactRedactionSweep?.passed !== true) {
|
|
290
|
+
failures.push('guardbench-raw.json: artifactRedactionSweep did not pass');
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const artifactText = Object.values(artifacts).map(value => JSON.stringify(value)).join('\n');
|
|
294
|
+
for (const secret of seededSecrets) {
|
|
295
|
+
if (secret && artifactText.includes(secret)) {
|
|
296
|
+
failures.push(`raw seeded secret leaked into GuardBench artifacts: ${secret}`);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
const manifestText = JSON.stringify(artifacts.manifest);
|
|
300
|
+
if (!manifestText.includes('seededSecretRefs')) {
|
|
301
|
+
failures.push('guardbench-manifest.json: missing seededSecretRefs');
|
|
302
|
+
}
|
|
303
|
+
if (manifestText.includes('"seededSecrets"')) {
|
|
304
|
+
failures.push('guardbench-manifest.json: contains seededSecrets');
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
return {
|
|
309
|
+
ok: failures.length === 0,
|
|
310
|
+
dir: publicPath(dir),
|
|
311
|
+
schemasDir: publicPath(schemasDir),
|
|
312
|
+
files: Object.values(ARTIFACT_FILES),
|
|
313
|
+
optionalFiles: Object.values(OPTIONAL_ARTIFACT_FILES).filter(file => existsSync(join(dir, file))),
|
|
314
|
+
failures,
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
async function main() {
|
|
319
|
+
const args = parseArgs();
|
|
320
|
+
if (args.help) {
|
|
321
|
+
console.log(usage());
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
const report = validateGuardBenchArtifacts(args);
|
|
326
|
+
if (args.json) {
|
|
327
|
+
console.log(JSON.stringify(report, null, 2));
|
|
328
|
+
} else if (report.ok) {
|
|
329
|
+
console.log(`GuardBench artifact validation passed: ${report.dir}`);
|
|
330
|
+
} else {
|
|
331
|
+
console.error('GuardBench artifact validation failed:');
|
|
332
|
+
for (const failure of report.failures) console.error(`- ${failure}`);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
if (!report.ok) process.exit(1);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
|
339
|
+
main().catch(error => {
|
|
340
|
+
console.error(error.stack ?? error.message);
|
|
341
|
+
process.exit(1);
|
|
342
|
+
});
|
|
343
|
+
}
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { dirname, join, resolve } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { validateAdapterRegistry } from './validate-adapter-registry.mjs';
|
|
5
|
+
import { validateGuardBenchArtifacts, validateSchema } from './validate-guardbench-artifacts.mjs';
|
|
6
|
+
import { publicPath } from './public-paths.mjs';
|
|
7
|
+
|
|
8
|
+
const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
|
9
|
+
const DEFAULT_REGISTRY = 'benchmarks/adapters/registry.json';
|
|
10
|
+
const DEFAULT_REGISTRY_SCHEMA = 'benchmarks/schemas/guardbench-adapter-registry.schema.json';
|
|
11
|
+
const DEFAULT_EXTERNAL_RUN_SCHEMA = 'benchmarks/schemas/guardbench-external-run.schema.json';
|
|
12
|
+
const DEFAULT_EVIDENCE_SCHEMA = 'benchmarks/schemas/guardbench-external-evidence.schema.json';
|
|
13
|
+
const DEFAULT_OUT_ROOT = 'benchmarks/output/external';
|
|
14
|
+
const DEFAULT_REPORT = 'benchmarks/output/external/guardbench-external-evidence.json';
|
|
15
|
+
const PENDING_METADATA_STATUSES = new Set(['blocked', 'dry-run-missing-env', 'dry-run-ready']);
|
|
16
|
+
|
|
17
|
+
function fromRoot(path) {
|
|
18
|
+
return resolve(ROOT, path);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function readJson(path) {
|
|
22
|
+
return JSON.parse(readFileSync(path, 'utf-8'));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function writeJson(path, value) {
|
|
26
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
27
|
+
writeFileSync(path, `${JSON.stringify(value, null, 2)}\n`, 'utf-8');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function parseExternalEvidenceArgs(argv = process.argv.slice(2)) {
|
|
31
|
+
const args = {
|
|
32
|
+
registry: DEFAULT_REGISTRY,
|
|
33
|
+
registrySchema: DEFAULT_REGISTRY_SCHEMA,
|
|
34
|
+
externalRunSchema: DEFAULT_EXTERNAL_RUN_SCHEMA,
|
|
35
|
+
evidenceSchema: DEFAULT_EVIDENCE_SCHEMA,
|
|
36
|
+
outRoot: DEFAULT_OUT_ROOT,
|
|
37
|
+
report: DEFAULT_REPORT,
|
|
38
|
+
adapters: [],
|
|
39
|
+
allowPending: false,
|
|
40
|
+
json: false,
|
|
41
|
+
write: true,
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
for (let i = 0; i < argv.length; i++) {
|
|
45
|
+
const token = argv[i];
|
|
46
|
+
if (token === '--registry' && argv[i + 1]) args.registry = argv[++i];
|
|
47
|
+
else if (token === '--registry-schema' && argv[i + 1]) args.registrySchema = argv[++i];
|
|
48
|
+
else if (token === '--external-run-schema' && argv[i + 1]) args.externalRunSchema = argv[++i];
|
|
49
|
+
else if (token === '--evidence-schema' && argv[i + 1]) args.evidenceSchema = argv[++i];
|
|
50
|
+
else if (token === '--out-root' && argv[i + 1]) args.outRoot = argv[++i];
|
|
51
|
+
else if (token === '--report' && argv[i + 1]) args.report = argv[++i];
|
|
52
|
+
else if (token === '--adapter' && argv[i + 1]) args.adapters.push(argv[++i]);
|
|
53
|
+
else if (token === '--allow-pending') args.allowPending = true;
|
|
54
|
+
else if (token === '--json') args.json = true;
|
|
55
|
+
else if (token === '--no-write') args.write = false;
|
|
56
|
+
else if (token === '--help' || token === '-h') args.help = true;
|
|
57
|
+
else throw new Error(`Unknown argument: ${token}`);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return args;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function usage() {
|
|
64
|
+
return `Usage: node benchmarks/verify-external-evidence.mjs [options]
|
|
65
|
+
|
|
66
|
+
Options:
|
|
67
|
+
--registry <path> GuardBench adapter registry. Default: ${DEFAULT_REGISTRY}.
|
|
68
|
+
--out-root <path> External evidence root. Default: ${DEFAULT_OUT_ROOT}.
|
|
69
|
+
--report <path> Output report path. Default: ${DEFAULT_REPORT}.
|
|
70
|
+
--adapter <id> Limit verification to one adapter id. May repeat.
|
|
71
|
+
--allow-pending Treat missing, blocked, or dry-run-only evidence as pending.
|
|
72
|
+
--json Print the machine-readable evidence report.
|
|
73
|
+
--no-write Do not write the evidence report.
|
|
74
|
+
`;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function credentialLeaks(text, requiredEnv, env) {
|
|
78
|
+
const leaks = [];
|
|
79
|
+
for (const name of requiredEnv) {
|
|
80
|
+
const value = env[name];
|
|
81
|
+
if (typeof value === 'string' && value.length >= 8 && text.includes(value)) {
|
|
82
|
+
leaks.push(name);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return leaks;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function pendingRow(target, outDir, metadataPath, allowPending, reason, metadata = null, extraFailures = [], secretLeakCount = 0) {
|
|
89
|
+
return {
|
|
90
|
+
id: target.id,
|
|
91
|
+
name: target.name,
|
|
92
|
+
path: target.path,
|
|
93
|
+
credentialMode: target.credentialMode,
|
|
94
|
+
requiredEnv: target.requiredEnv,
|
|
95
|
+
outDir: publicPath(outDir),
|
|
96
|
+
metadataPath: publicPath(metadataPath),
|
|
97
|
+
status: 'pending',
|
|
98
|
+
evidenceKind: metadata?.dryRun ? 'dry-run' : reason === 'missing' ? 'missing' : 'blocked',
|
|
99
|
+
metadataStatus: metadata?.status ?? null,
|
|
100
|
+
dryRun: metadata?.dryRun ?? null,
|
|
101
|
+
missingEnv: metadata?.missingEnv ?? target.requiredEnv,
|
|
102
|
+
artifactValidationOk: null,
|
|
103
|
+
adapterConformanceOk: null,
|
|
104
|
+
secretLeakCount,
|
|
105
|
+
failures: allowPending ? extraFailures : [
|
|
106
|
+
...extraFailures,
|
|
107
|
+
reason === 'missing'
|
|
108
|
+
? `Missing external run metadata: ${metadataPath}`
|
|
109
|
+
: `External evidence is pending for ${target.id}: ${metadata?.status ?? reason}`,
|
|
110
|
+
],
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function verifyLiveMetadata(target, outDir, metadataPath, metadata, metadataText, schemas, env) {
|
|
115
|
+
const failures = [];
|
|
116
|
+
const schemaErrors = validateSchema(metadata, schemas.externalRun, 'guardbench-externalRun');
|
|
117
|
+
failures.push(...schemaErrors);
|
|
118
|
+
|
|
119
|
+
const artifactValidation = validateGuardBenchArtifacts({ dir: outDir });
|
|
120
|
+
if (!artifactValidation.ok) {
|
|
121
|
+
failures.push(...artifactValidation.failures.map(failure => `artifact validation: ${failure}`));
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (metadata.adapter !== target.id) failures.push(`metadata adapter ${metadata.adapter} does not match registry id ${target.id}`);
|
|
125
|
+
if (metadata.dryRun !== false) failures.push('metadata must come from a live run, not a dry run');
|
|
126
|
+
if (metadata.status !== 'passed') failures.push(`metadata status must be passed, got ${metadata.status}`);
|
|
127
|
+
if (metadata.exitCode !== 0) failures.push(`metadata exitCode must be 0, got ${metadata.exitCode}`);
|
|
128
|
+
if ((metadata.missingEnv ?? []).length !== 0) failures.push(`metadata still reports missing runtime env: ${(metadata.missingEnv ?? []).join(', ')}`);
|
|
129
|
+
for (const name of target.requiredEnv) {
|
|
130
|
+
if (!(metadata.requiredEnv ?? []).includes(name)) failures.push(`metadata requiredEnv missing ${name}`);
|
|
131
|
+
}
|
|
132
|
+
if (metadata.artifactValidation?.ok !== true) failures.push('metadata artifactValidation.ok must be true');
|
|
133
|
+
if (metadata.adapterConformance?.ok !== true) failures.push('metadata adapterConformance.ok must be true');
|
|
134
|
+
if (!metadata.artifactHashes) failures.push('metadata missing artifactHashes');
|
|
135
|
+
|
|
136
|
+
const leakedEnv = credentialLeaks(metadataText, target.requiredEnv, env);
|
|
137
|
+
failures.push(...leakedEnv.map(name => `metadata leaks runtime credential value for ${name}`));
|
|
138
|
+
|
|
139
|
+
return {
|
|
140
|
+
id: target.id,
|
|
141
|
+
name: target.name,
|
|
142
|
+
path: target.path,
|
|
143
|
+
credentialMode: target.credentialMode,
|
|
144
|
+
requiredEnv: target.requiredEnv,
|
|
145
|
+
outDir: publicPath(outDir),
|
|
146
|
+
metadataPath: publicPath(metadataPath),
|
|
147
|
+
status: failures.length === 0 ? 'verified' : 'failed',
|
|
148
|
+
evidenceKind: 'live',
|
|
149
|
+
metadataStatus: metadata.status ?? null,
|
|
150
|
+
dryRun: metadata.dryRun ?? null,
|
|
151
|
+
missingEnv: metadata.missingEnv ?? [],
|
|
152
|
+
artifactValidationOk: artifactValidation.ok,
|
|
153
|
+
adapterConformanceOk: metadata.adapterConformance?.ok ?? null,
|
|
154
|
+
secretLeakCount: leakedEnv.length,
|
|
155
|
+
failures,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function verifyTarget(target, options, schemas) {
|
|
160
|
+
const outDir = resolve(options.outRoot, target.id);
|
|
161
|
+
const metadataPath = join(outDir, 'external-run-metadata.json');
|
|
162
|
+
|
|
163
|
+
if (!existsSync(metadataPath)) {
|
|
164
|
+
return pendingRow(target, outDir, metadataPath, options.allowPending, 'missing');
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
let metadata = null;
|
|
168
|
+
let metadataText = '';
|
|
169
|
+
const parseFailures = [];
|
|
170
|
+
try {
|
|
171
|
+
metadataText = readFileSync(metadataPath, 'utf-8');
|
|
172
|
+
metadata = JSON.parse(metadataText);
|
|
173
|
+
} catch (error) {
|
|
174
|
+
parseFailures.push(error.message);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (!metadata) {
|
|
178
|
+
return {
|
|
179
|
+
id: target.id,
|
|
180
|
+
name: target.name,
|
|
181
|
+
path: target.path,
|
|
182
|
+
credentialMode: target.credentialMode,
|
|
183
|
+
requiredEnv: target.requiredEnv,
|
|
184
|
+
outDir: publicPath(outDir),
|
|
185
|
+
metadataPath: publicPath(metadataPath),
|
|
186
|
+
status: 'failed',
|
|
187
|
+
evidenceKind: 'missing',
|
|
188
|
+
metadataStatus: null,
|
|
189
|
+
dryRun: null,
|
|
190
|
+
missingEnv: target.requiredEnv,
|
|
191
|
+
artifactValidationOk: null,
|
|
192
|
+
adapterConformanceOk: null,
|
|
193
|
+
secretLeakCount: 0,
|
|
194
|
+
failures: parseFailures,
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const metadataSchemaFailures = validateSchema(metadata, schemas.externalRun, 'guardbench-externalRun');
|
|
199
|
+
const leakedEnv = credentialLeaks(metadataText, target.requiredEnv, options.env);
|
|
200
|
+
const metadataFailures = [
|
|
201
|
+
...metadataSchemaFailures,
|
|
202
|
+
...leakedEnv.map(name => `metadata leaks runtime credential value for ${name}`),
|
|
203
|
+
];
|
|
204
|
+
|
|
205
|
+
if (metadata.dryRun === true || PENDING_METADATA_STATUSES.has(metadata.status)) {
|
|
206
|
+
return pendingRow(target, outDir, metadataPath, options.allowPending, metadata.status ?? 'pending', metadata, metadataFailures, leakedEnv.length);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
return verifyLiveMetadata(target, outDir, metadataPath, metadata, metadataText, schemas, options.env);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function externalTargetsFromRegistry(registry, adapterIds) {
|
|
213
|
+
const selected = new Set(adapterIds ?? []);
|
|
214
|
+
return (registry.adapters ?? [])
|
|
215
|
+
.filter(adapter => adapter.credentialMode === 'runtime-env')
|
|
216
|
+
.filter(adapter => selected.size === 0 || selected.has(adapter.id));
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
export function validateExternalEvidenceReport(report, options = {}) {
|
|
220
|
+
const schema = readJson(fromRoot(options.schema ?? DEFAULT_EVIDENCE_SCHEMA));
|
|
221
|
+
return validateSchema(report, schema, 'guardbench-external-evidence');
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
export async function verifyExternalGuardBenchEvidence(options = {}) {
|
|
225
|
+
const registryPath = fromRoot(options.registry ?? DEFAULT_REGISTRY);
|
|
226
|
+
const registrySchemaPath = fromRoot(options.registrySchema ?? DEFAULT_REGISTRY_SCHEMA);
|
|
227
|
+
const outRoot = fromRoot(options.outRoot ?? DEFAULT_OUT_ROOT);
|
|
228
|
+
const allowPending = options.allowPending === true;
|
|
229
|
+
const registry = options.targets ? null : readJson(registryPath);
|
|
230
|
+
const registryValidation = options.targets
|
|
231
|
+
? { ok: true, failures: [] }
|
|
232
|
+
: await validateAdapterRegistry({ registry: registryPath, schema: registrySchemaPath });
|
|
233
|
+
const targets = options.targets ?? externalTargetsFromRegistry(registry, options.adapters);
|
|
234
|
+
const schemas = {
|
|
235
|
+
externalRun: readJson(fromRoot(options.externalRunSchema ?? DEFAULT_EXTERNAL_RUN_SCHEMA)),
|
|
236
|
+
};
|
|
237
|
+
const rows = targets.map(target => verifyTarget(target, {
|
|
238
|
+
outRoot,
|
|
239
|
+
allowPending,
|
|
240
|
+
env: options.env ?? process.env,
|
|
241
|
+
}, schemas));
|
|
242
|
+
const unknownAdapters = (options.adapters ?? []).filter(id => !targets.some(target => target.id === id));
|
|
243
|
+
const failures = [
|
|
244
|
+
...registryValidation.failures.map(failure => `registry: ${failure}`),
|
|
245
|
+
...unknownAdapters.map(id => `Unknown runtime-env adapter id: ${id}`),
|
|
246
|
+
...rows.flatMap(row => row.failures.map(failure => `${row.id}: ${failure}`)),
|
|
247
|
+
];
|
|
248
|
+
const report = {
|
|
249
|
+
schemaVersion: '1.0.0',
|
|
250
|
+
suite: 'GuardBench external evidence verification',
|
|
251
|
+
generatedAt: new Date().toISOString(),
|
|
252
|
+
ok: failures.length === 0,
|
|
253
|
+
allowPending,
|
|
254
|
+
registry: options.targets ? 'inline-targets' : publicPath(registryPath),
|
|
255
|
+
outRoot: publicPath(outRoot),
|
|
256
|
+
adapters: rows,
|
|
257
|
+
failures,
|
|
258
|
+
};
|
|
259
|
+
const schemaFailures = validateExternalEvidenceReport(report, { schema: options.evidenceSchema ?? DEFAULT_EVIDENCE_SCHEMA });
|
|
260
|
+
if (schemaFailures.length > 0) {
|
|
261
|
+
throw new Error(`GuardBench external evidence schema validation failed: ${schemaFailures.join('; ')}`);
|
|
262
|
+
}
|
|
263
|
+
if (options.write !== false) {
|
|
264
|
+
writeJson(fromRoot(options.report ?? DEFAULT_REPORT), report);
|
|
265
|
+
}
|
|
266
|
+
return report;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
async function main() {
|
|
270
|
+
const args = parseExternalEvidenceArgs();
|
|
271
|
+
if (args.help) {
|
|
272
|
+
console.log(usage());
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const report = await verifyExternalGuardBenchEvidence(args);
|
|
277
|
+
if (args.json) {
|
|
278
|
+
console.log(JSON.stringify(report, null, 2));
|
|
279
|
+
} else if (report.ok) {
|
|
280
|
+
const verified = report.adapters.filter(adapter => adapter.status === 'verified').length;
|
|
281
|
+
const pending = report.adapters.filter(adapter => adapter.status === 'pending').length;
|
|
282
|
+
console.log(`GuardBench external evidence verification passed: ${verified} verified, ${pending} pending`);
|
|
283
|
+
} else {
|
|
284
|
+
console.error('GuardBench external evidence verification failed:');
|
|
285
|
+
for (const failure of report.failures) console.error(`- ${failure}`);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
if (!report.ok) process.exit(1);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
|
292
|
+
main().catch(error => {
|
|
293
|
+
console.error(error.stack ?? error.message);
|
|
294
|
+
process.exit(1);
|
|
295
|
+
});
|
|
296
|
+
}
|