@dynamicalsystems/idl-conformance 0.6.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/LICENSE +191 -0
- package/assets/vectors/dsg.infra.adoption-fact_1/accept/fact-cutover-transfer.json +7 -0
- package/assets/vectors/dsg.infra.adoption-fact_1/accept/fact-synthetic-initialize.json +7 -0
- package/assets/vectors/dsg.infra.adoption-fact_1/reject/fact-extra-field.json +8 -0
- package/assets/vectors/dsg.infra.adoption-fact_1/reject/fact-generation-zero.json +8 -0
- package/assets/vectors/dsg.infra.adoption-fact_1/reject/fact-restore-claims-ownership.json +8 -0
- package/assets/vectors/dsg.infra.adoption-fact_1/reject/fact-short-snapshot-digest.json +8 -0
- package/assets/vectors/dsg.infra.adoption-fact_1/reject/fact-wrong-profile-v2.json +8 -0
- package/assets/vectors/dsg.infra.resource-descriptor_1/accept/descriptor-cpu-only-null-accelerator.json +7 -0
- package/assets/vectors/dsg.infra.resource-descriptor_1/accept/descriptor-s02-batch-fixture.json +7 -0
- package/assets/vectors/dsg.infra.resource-descriptor_1/accept/descriptor-s02-fake-provider-fixture.json +7 -0
- package/assets/vectors/dsg.infra.resource-descriptor_1/reject/descriptor-external-ip-true.json +8 -0
- package/assets/vectors/dsg.infra.resource-descriptor_1/reject/descriptor-extra-field.json +8 -0
- package/assets/vectors/dsg.infra.resource-descriptor_1/reject/descriptor-missing-reservation.json +8 -0
- package/assets/vectors/dsg.infra.resource-descriptor_1/reject/descriptor-open-machine-shape.json +8 -0
- package/assets/vectors/dsg.infra.resource-descriptor_1/reject/descriptor-public-network.json +8 -0
- package/assets/vectors/dsg.infra.resource-descriptor_1/reject/descriptor-schema-version-2.json +8 -0
- package/assets/vectors/dsg.infra.resource-descriptor_1/reject/descriptor-unprefixed-image-digest.json +8 -0
- package/assets/vectors/dsg.infra.resource-descriptor_1/reject/descriptor-wrong-kind.json +8 -0
- package/assets/vectors/dsg.infra.signed-law_1/accept/bundle-k02-automation-control.json +7 -0
- package/assets/vectors/dsg.infra.signed-law_1/accept/bundle-k02-synthetic-law.json +7 -0
- package/assets/vectors/dsg.infra.signed-law_1/reject/bundle-bootstrap-two-uses.json +8 -0
- package/assets/vectors/dsg.infra.signed-law_1/reject/bundle-capabilities-descriptor-version-mismatch.json +8 -0
- package/assets/vectors/dsg.infra.signed-law_1/reject/bundle-capability-public-network.json +8 -0
- package/assets/vectors/dsg.infra.signed-law_1/reject/bundle-ceiling-negative-value.json +8 -0
- package/assets/vectors/dsg.infra.signed-law_1/reject/bundle-extra-field.json +8 -0
- package/assets/vectors/dsg.infra.signed-law_1/reject/bundle-policy-manifest-traversal-key.json +8 -0
- package/assets/vectors/dsg.infra.signed-law_1/reject/bundle-scope-descriptor-version-v2.json +8 -0
- package/assets/vectors/dsg.infra.signed-law_1/reject/bundle-short-signature.json +8 -0
- package/assets/vectors/dsg.infra.signed-law_1/reject/bundle-unknown-key-class.json +8 -0
- package/assets/vectors/dsg.infra.signed-law_1/reject/bundle-wrong-kind.json +8 -0
- package/assets/vectors/dsg.kernel.bootstrap_1/accept/intent-enroll-human-root.json +7 -0
- package/assets/vectors/dsg.kernel.bootstrap_1/accept/intent-initialize.json +7 -0
- package/assets/vectors/dsg.kernel.bootstrap_1/accept/signed-enroll-signed.json +7 -0
- package/assets/vectors/dsg.kernel.bootstrap_1/reject/intent-empty-resources.json +8 -0
- package/assets/vectors/dsg.kernel.bootstrap_1/reject/intent-extra-field.json +8 -0
- package/assets/vectors/dsg.kernel.bootstrap_1/reject/intent-two-uses.json +8 -0
- package/assets/vectors/dsg.kernel.bootstrap_1/reject/intent-unknown-action.json +8 -0
- package/assets/vectors/dsg.kernel.bootstrap_1/reject/intent-version-v2.json +8 -0
- package/assets/vectors/dsg.kernel.bootstrap_1/reject/signed-short-signature.json +8 -0
- package/assets/vectors/dsg.kernel.dispatch-consume_1/accept/lease-kernel-201-live-capture.json +7 -0
- package/assets/vectors/dsg.kernel.dispatch-consume_1/accept/lease-replayed-stale.json +7 -0
- package/assets/vectors/dsg.kernel.dispatch-consume_1/accept/refusal-revoked.json +7 -0
- package/assets/vectors/dsg.kernel.dispatch-consume_1/accept/request-first-consume.json +7 -0
- package/assets/vectors/dsg.kernel.dispatch-consume_1/accept/request-renewal.json +7 -0
- package/assets/vectors/dsg.kernel.dispatch-consume_1/reject/lease-fractional-expires-at.json +8 -0
- package/assets/vectors/dsg.kernel.dispatch-consume_1/reject/lease-missing-profile.json +8 -0
- package/assets/vectors/dsg.kernel.dispatch-consume_1/reject/lease-renewal-wrong-literal.json +8 -0
- package/assets/vectors/dsg.kernel.dispatch-consume_1/reject/lease-skew-above-bound.json +8 -0
- package/assets/vectors/dsg.kernel.dispatch-consume_1/reject/refusal-empty-issues.json +8 -0
- package/assets/vectors/dsg.kernel.dispatch-consume_1/reject/refusal-unknown-code.json +8 -0
- package/assets/vectors/dsg.kernel.dispatch-consume_1/reject/request-extra-field.json +8 -0
- package/assets/vectors/dsg.kernel.dispatch-consume_1/reject/request-fractional-requested-at.json +8 -0
- package/assets/vectors/dsg.kernel.dispatch-consume_1/reject/request-malformed-case-orn.json +8 -0
- package/assets/vectors/dsg.kernel.dispatch-consume_1/reject/request-negative-generation.json +8 -0
- package/assets/vectors/dsg.kernel.dispatch-consume_1/reject/request-uppercase-envelope-digest.json +8 -0
- package/assets/vectors/dsg.kernel.ordinary-authority_1/accept/authority-human-enrollment.json +7 -0
- package/assets/vectors/dsg.kernel.ordinary-authority_1/accept/authority-synthetic-enrollment.json +7 -0
- package/assets/vectors/dsg.kernel.ordinary-authority_1/reject/authority-empty-actions.json +8 -0
- package/assets/vectors/dsg.kernel.ordinary-authority_1/reject/authority-extra-field.json +8 -0
- package/assets/vectors/dsg.kernel.ordinary-authority_1/reject/authority-fractional-not-before.json +8 -0
- package/assets/vectors/dsg.kernel.ordinary-authority_1/reject/authority-missing-ceilings.json +8 -0
- package/assets/vectors/dsg.kernel.ordinary-authority_1/reject/authority-release-automation-key-class.json +8 -0
- package/assets/vectors/dsg.kernel.ordinary-authority_1/reject/authority-version-v2.json +8 -0
- package/assets/vectors/dsg.registry.revocations_1/accept/pointer-v2.json +7 -0
- package/assets/vectors/dsg.registry.revocations_1/accept/snapshot-chained-with-entries.json +7 -0
- package/assets/vectors/dsg.registry.revocations_1/accept/snapshot-genesis.json +7 -0
- package/assets/vectors/dsg.registry.revocations_1/reject/pointer-traversal-url.json +8 -0
- package/assets/vectors/dsg.registry.revocations_1/reject/snapshot-extra-field.json +8 -0
- package/assets/vectors/dsg.registry.revocations_1/reject/snapshot-not-test-only.json +8 -0
- package/assets/vectors/dsg.registry.revocations_1/reject/snapshot-version-zero.json +8 -0
- package/assets/vectors/dsg.registry.revocations_1/reject/snapshot-wrong-domain-v2.json +8 -0
- package/assets/vectors/dsg.run.operation_1/accept/accepted-receipt-fractional-accepted-at.json +7 -0
- package/assets/vectors/dsg.run.operation_1/accept/readback-queued-pending.json +7 -0
- package/assets/vectors/dsg.run.operation_1/accept/request-minimal-valid.json +7 -0
- package/assets/vectors/dsg.run.operation_1/digest/envelope-m010-envelope-digest.json +8 -0
- package/assets/vectors/dsg.run.operation_1/reject/accepted-missing-plan-digest.json +8 -0
- package/assets/vectors/dsg.run.operation_1/reject/request-document-schema-version-2.json +8 -0
- package/assets/vectors/dsg.run.operation_1/reject/request-extra-field.json +8 -0
- package/assets/vectors/dsg.run.operation_1/reject/request-r01-original-short-orn.json +8 -0
- package/assets/vectors/dsg.run.operation_1/reject/request-uppercase-plan-digest.json +8 -0
- package/assets/vectors/dsg.run.operation_1/reject/request-wrong-profile-other.json +8 -0
- package/assets/vectors/dsg.run.operation_1/reject/request-wrong-profile-v2.json +8 -0
- package/checklist.json +12 -0
- package/dist/cli.cjs +16 -0
- package/dist/cli.d.cts +1 -0
- package/dist/cli.d.mts +1 -0
- package/dist/cli.mjs +19 -0
- package/dist/cli.mjs.map +1 -0
- package/dist/index.cjs +4 -0
- package/dist/index.d.cts +57 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.mts +57 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +2 -0
- package/dist/reference-adapter-CaAo2iIH.mjs +1441 -0
- package/dist/reference-adapter-CaAo2iIH.mjs.map +1 -0
- package/dist/reference-adapter-DWurDAI9.cjs +1450 -0
- package/package.json +64 -0
- package/src/adapter.ts +32 -0
- package/src/cli.ts +19 -0
- package/src/index.ts +4 -0
- package/src/reference-adapter.ts +42 -0
- package/src/runner.ts +89 -0
package/package.json
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@dynamicalsystems/idl-conformance",
|
|
3
|
+
"version": "0.6.1",
|
|
4
|
+
"description": "Conformance vectors and runner for the DSG wire profiles. A consumer implements the adapter over its real boundary parser and replays every vector; a counterpart fake is built from accept vectors, never written by hand.",
|
|
5
|
+
"homepage": "https://github.com/DynamicalSystemsGroup/dsg-idl#readme",
|
|
6
|
+
"bugs": "https://github.com/DynamicalSystemsGroup/dsg-idl/issues",
|
|
7
|
+
"license": "Apache-2.0",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/DynamicalSystemsGroup/dsg-idl.git",
|
|
11
|
+
"directory": "packages/idl-conformance"
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"dist",
|
|
15
|
+
"src",
|
|
16
|
+
"assets",
|
|
17
|
+
"checklist.json",
|
|
18
|
+
"LICENSE",
|
|
19
|
+
"README.md"
|
|
20
|
+
],
|
|
21
|
+
"type": "module",
|
|
22
|
+
"sideEffects": false,
|
|
23
|
+
"main": "./dist/index.cjs",
|
|
24
|
+
"module": "./dist/index.mjs",
|
|
25
|
+
"types": "./dist/index.d.cts",
|
|
26
|
+
"exports": {
|
|
27
|
+
"./package.json": "./package.json",
|
|
28
|
+
".": {
|
|
29
|
+
"import": {
|
|
30
|
+
"types": "./dist/index.d.mts",
|
|
31
|
+
"default": "./dist/index.mjs"
|
|
32
|
+
},
|
|
33
|
+
"require": {
|
|
34
|
+
"types": "./dist/index.d.cts",
|
|
35
|
+
"default": "./dist/index.cjs"
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
"publishConfig": {
|
|
40
|
+
"access": "public"
|
|
41
|
+
},
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"@dynamicalsystems/orn-schemas": "0.1.0",
|
|
44
|
+
"@dynamicalsystems/idl": "0.6.1"
|
|
45
|
+
},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"@types/node": "24.13.3",
|
|
48
|
+
"oxfmt": "0.64.0",
|
|
49
|
+
"oxlint": "1.79.0",
|
|
50
|
+
"tsdown": "0.22.14",
|
|
51
|
+
"typescript": "7.0.2",
|
|
52
|
+
"vitest": "4.1.11"
|
|
53
|
+
},
|
|
54
|
+
"engines": {
|
|
55
|
+
"node": ">=24"
|
|
56
|
+
},
|
|
57
|
+
"scripts": {
|
|
58
|
+
"build": "tsdown",
|
|
59
|
+
"check-types": "tsc --noEmit",
|
|
60
|
+
"lint": "oxlint --config ../../.oxlintrc.json --type-aware .",
|
|
61
|
+
"format:check": "oxfmt --check .",
|
|
62
|
+
"test": "vitest run"
|
|
63
|
+
}
|
|
64
|
+
}
|
package/src/adapter.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// The interface a consumer implements over its real boundary parser: the
|
|
2
|
+
// function that reads bytes off the wire, not the schema object. One parse
|
|
3
|
+
// function per shape position, addressed "<profile literal>#<shape>", so a
|
|
4
|
+
// vector exercises the exact code path that runs in production.
|
|
5
|
+
export type ParseResult =
|
|
6
|
+
| { readonly ok: true; readonly digest?: string }
|
|
7
|
+
| { readonly ok: false; readonly reason: string };
|
|
8
|
+
|
|
9
|
+
export type Adapter = Readonly<Record<string, (bytes: Uint8Array) => ParseResult>>;
|
|
10
|
+
|
|
11
|
+
/** One vector file on disk. `bytes` is base64 of the exact wire bytes:
|
|
12
|
+
* re-serialization would normalize away the malformations reject vectors
|
|
13
|
+
* exist to carry. A reject names the one rule it breaks. */
|
|
14
|
+
export type Vector = {
|
|
15
|
+
readonly profile: string;
|
|
16
|
+
readonly shape: string;
|
|
17
|
+
readonly name: string;
|
|
18
|
+
readonly verdict: "accept" | "reject";
|
|
19
|
+
readonly rule?: string;
|
|
20
|
+
readonly bytes: string;
|
|
21
|
+
/** Digest vectors only: the expected sha256 of the canonical form of the
|
|
22
|
+
* document in `bytes`. The vector bytes are deliberately NON-canonical
|
|
23
|
+
* (different key order, whitespace), so agreement proves the consumer's
|
|
24
|
+
* own canonicalize-and-digest path, not a byte copy. */
|
|
25
|
+
readonly sha256?: string;
|
|
26
|
+
/** What agreeing on this vector obliges. "shape" (default): the boundary
|
|
27
|
+
* parser's structural verdict. "semantic": a validator beyond shape
|
|
28
|
+
* (law_contract.py, Rego decisions) must agree; shape-only adapters skip
|
|
29
|
+
* these. Passing a finite corpus establishes agreement on those cases,
|
|
30
|
+
* never equivalence. */
|
|
31
|
+
readonly obligation?: "shape" | "semantic";
|
|
32
|
+
};
|
package/src/cli.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// Runs every committed vector through the reference adapter. Exit 0 only
|
|
2
|
+
// when every vector agrees with the schemas. CI for this repo; consumers
|
|
3
|
+
// run the same runner with their own adapter instead.
|
|
4
|
+
import { readFileSync } from "node:fs";
|
|
5
|
+
import { dirname, join } from "node:path";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
import { referenceAdapter } from "./reference-adapter.js";
|
|
8
|
+
import { runVectors } from "./runner.js";
|
|
9
|
+
|
|
10
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
11
|
+
const root = join(here, "..");
|
|
12
|
+
const checklist = JSON.parse(readFileSync(join(root, "checklist.json"), "utf8"));
|
|
13
|
+
const report = runVectors(referenceAdapter(), join(root, "assets", "vectors"), checklist);
|
|
14
|
+
|
|
15
|
+
for (const verdict of report.verdicts) {
|
|
16
|
+
if (verdict.outcome === "fail") console.error(`FAIL ${verdict.id}: ${verdict.detail}`);
|
|
17
|
+
}
|
|
18
|
+
console.log(JSON.stringify({ passed: report.passed, failed: report.failed }));
|
|
19
|
+
process.exit(report.failed === 0 ? 0 : 1);
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// The adapter the package itself is judged by: TypeBox Value.Check over
|
|
2
|
+
// the registered schemas. Consumers implement their own adapter over their
|
|
3
|
+
// real parser; this one exists so a profile PR proves its vectors against
|
|
4
|
+
// the schemas before any consumer sees them.
|
|
5
|
+
import { createHash } from "node:crypto";
|
|
6
|
+
import { Value } from "@sinclair/typebox/value";
|
|
7
|
+
import { jcsStringify } from "@dynamicalsystems/orn-schemas";
|
|
8
|
+
import { PROFILES } from "@dynamicalsystems/idl";
|
|
9
|
+
import type { Adapter, ParseResult } from "./adapter.js";
|
|
10
|
+
|
|
11
|
+
export function referenceAdapter(): Adapter {
|
|
12
|
+
const adapter: Record<string, (bytes: Uint8Array) => ParseResult> = {};
|
|
13
|
+
for (const entry of Object.values(PROFILES)) {
|
|
14
|
+
adapter[`${entry.literal}#digest.envelope`] = (bytes) => {
|
|
15
|
+
let parsed: unknown;
|
|
16
|
+
try {
|
|
17
|
+
parsed = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
|
|
18
|
+
} catch (error) {
|
|
19
|
+
return { ok: false, reason: `not UTF-8 JSON: ${String(error)}` };
|
|
20
|
+
}
|
|
21
|
+
const digest = createHash("sha256").update(jcsStringify(parsed)).digest("hex");
|
|
22
|
+
return { ok: true, digest };
|
|
23
|
+
};
|
|
24
|
+
for (const [shapeName, shape] of Object.entries(entry.shapes)) {
|
|
25
|
+
adapter[`${entry.literal}#${shapeName}`] = (bytes) => {
|
|
26
|
+
let parsed: unknown;
|
|
27
|
+
try {
|
|
28
|
+
parsed = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
|
|
29
|
+
} catch (error) {
|
|
30
|
+
return { ok: false, reason: `not UTF-8 JSON: ${String(error)}` };
|
|
31
|
+
}
|
|
32
|
+
if (Value.Check(shape, parsed)) return { ok: true };
|
|
33
|
+
const first = Value.Errors(shape, parsed).First();
|
|
34
|
+
return {
|
|
35
|
+
ok: false,
|
|
36
|
+
reason: `${first?.path ?? ""}: ${first?.message ?? "schema mismatch"}`,
|
|
37
|
+
};
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return adapter;
|
|
42
|
+
}
|
package/src/runner.ts
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { readFileSync, readdirSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import type { Adapter, Vector } from "./adapter.js";
|
|
4
|
+
|
|
5
|
+
export type VectorVerdict = {
|
|
6
|
+
readonly id: string;
|
|
7
|
+
readonly expected: "accept" | "reject";
|
|
8
|
+
readonly outcome: "pass" | "fail";
|
|
9
|
+
readonly detail: string;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export type RunReport = {
|
|
13
|
+
readonly passed: number;
|
|
14
|
+
readonly failed: number;
|
|
15
|
+
readonly verdicts: readonly VectorVerdict[];
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
type Checklist = { readonly profiles: Readonly<Record<string, number>> };
|
|
19
|
+
|
|
20
|
+
/** Runs every vector under `assetsDir` through the adapter. The checklist
|
|
21
|
+
* is the coverage floor: a profile listed there with fewer vectors on disk
|
|
22
|
+
* than promised fails the run, so a silently deleted directory cannot pass. */
|
|
23
|
+
export function runVectors(adapter: Adapter, assetsDir: string, checklist: Checklist): RunReport {
|
|
24
|
+
const verdicts: VectorVerdict[] = [];
|
|
25
|
+
|
|
26
|
+
for (const [profile, expectedCount] of Object.entries(checklist.profiles)) {
|
|
27
|
+
const dir = join(assetsDir, profile.replaceAll("/", "_"));
|
|
28
|
+
let files: string[] = [];
|
|
29
|
+
for (const verdict of ["accept", "reject", "digest"] as const) {
|
|
30
|
+
let names: string[] = [];
|
|
31
|
+
try {
|
|
32
|
+
names = readdirSync(join(dir, verdict)).filter((f) => f.endsWith(".json"));
|
|
33
|
+
} catch {
|
|
34
|
+
names = [];
|
|
35
|
+
}
|
|
36
|
+
files = files.concat(names.map((n) => join(dir, verdict, n)));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (files.length < expectedCount) {
|
|
40
|
+
verdicts.push({
|
|
41
|
+
id: profile,
|
|
42
|
+
expected: "accept",
|
|
43
|
+
outcome: "fail",
|
|
44
|
+
detail: `checklist promises ${expectedCount} vectors, found ${files.length}`,
|
|
45
|
+
});
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
for (const file of files) {
|
|
50
|
+
const vector = JSON.parse(readFileSync(file, "utf8")) as Vector;
|
|
51
|
+
const isDigest = file.includes("/digest/");
|
|
52
|
+
const key = isDigest
|
|
53
|
+
? `${vector.profile}#digest.${vector.shape}`
|
|
54
|
+
: `${vector.profile}#${vector.shape}`;
|
|
55
|
+
const parse = adapter[key];
|
|
56
|
+
if (parse === undefined && vector.obligation === "semantic") continue;
|
|
57
|
+
if (parse === undefined) {
|
|
58
|
+
verdicts.push({
|
|
59
|
+
id: `${key}/${vector.name}`,
|
|
60
|
+
expected: vector.verdict,
|
|
61
|
+
outcome: "fail",
|
|
62
|
+
detail: "adapter has no parser for this shape",
|
|
63
|
+
});
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
const result = parse(Buffer.from(vector.bytes, "base64"));
|
|
67
|
+
const agreed = isDigest
|
|
68
|
+
? result.ok && result.digest === vector.sha256
|
|
69
|
+
: result.ok === (vector.verdict === "accept");
|
|
70
|
+
verdicts.push({
|
|
71
|
+
id: `${vector.profile}#${vector.shape}/${vector.name}`,
|
|
72
|
+
expected: vector.verdict,
|
|
73
|
+
outcome: agreed ? "pass" : "fail",
|
|
74
|
+
detail: agreed
|
|
75
|
+
? (vector.rule ?? "")
|
|
76
|
+
: isDigest
|
|
77
|
+
? result.ok
|
|
78
|
+
? `digest mismatch: computed ${result.digest ?? "nothing"}, vector pins ${vector.sha256 ?? "nothing"}`
|
|
79
|
+
: `could not digest: ${result.reason}`
|
|
80
|
+
: result.ok
|
|
81
|
+
? `accepted but must reject: ${vector.rule ?? "unnamed rule"}`
|
|
82
|
+
: `rejected but must accept: ${result.reason}`,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const failed = verdicts.filter((v) => v.outcome === "fail").length;
|
|
88
|
+
return { passed: verdicts.length - failed, failed, verdicts };
|
|
89
|
+
}
|