@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.
Files changed (104) hide show
  1. package/LICENSE +191 -0
  2. package/assets/vectors/dsg.infra.adoption-fact_1/accept/fact-cutover-transfer.json +7 -0
  3. package/assets/vectors/dsg.infra.adoption-fact_1/accept/fact-synthetic-initialize.json +7 -0
  4. package/assets/vectors/dsg.infra.adoption-fact_1/reject/fact-extra-field.json +8 -0
  5. package/assets/vectors/dsg.infra.adoption-fact_1/reject/fact-generation-zero.json +8 -0
  6. package/assets/vectors/dsg.infra.adoption-fact_1/reject/fact-restore-claims-ownership.json +8 -0
  7. package/assets/vectors/dsg.infra.adoption-fact_1/reject/fact-short-snapshot-digest.json +8 -0
  8. package/assets/vectors/dsg.infra.adoption-fact_1/reject/fact-wrong-profile-v2.json +8 -0
  9. package/assets/vectors/dsg.infra.resource-descriptor_1/accept/descriptor-cpu-only-null-accelerator.json +7 -0
  10. package/assets/vectors/dsg.infra.resource-descriptor_1/accept/descriptor-s02-batch-fixture.json +7 -0
  11. package/assets/vectors/dsg.infra.resource-descriptor_1/accept/descriptor-s02-fake-provider-fixture.json +7 -0
  12. package/assets/vectors/dsg.infra.resource-descriptor_1/reject/descriptor-external-ip-true.json +8 -0
  13. package/assets/vectors/dsg.infra.resource-descriptor_1/reject/descriptor-extra-field.json +8 -0
  14. package/assets/vectors/dsg.infra.resource-descriptor_1/reject/descriptor-missing-reservation.json +8 -0
  15. package/assets/vectors/dsg.infra.resource-descriptor_1/reject/descriptor-open-machine-shape.json +8 -0
  16. package/assets/vectors/dsg.infra.resource-descriptor_1/reject/descriptor-public-network.json +8 -0
  17. package/assets/vectors/dsg.infra.resource-descriptor_1/reject/descriptor-schema-version-2.json +8 -0
  18. package/assets/vectors/dsg.infra.resource-descriptor_1/reject/descriptor-unprefixed-image-digest.json +8 -0
  19. package/assets/vectors/dsg.infra.resource-descriptor_1/reject/descriptor-wrong-kind.json +8 -0
  20. package/assets/vectors/dsg.infra.signed-law_1/accept/bundle-k02-automation-control.json +7 -0
  21. package/assets/vectors/dsg.infra.signed-law_1/accept/bundle-k02-synthetic-law.json +7 -0
  22. package/assets/vectors/dsg.infra.signed-law_1/reject/bundle-bootstrap-two-uses.json +8 -0
  23. package/assets/vectors/dsg.infra.signed-law_1/reject/bundle-capabilities-descriptor-version-mismatch.json +8 -0
  24. package/assets/vectors/dsg.infra.signed-law_1/reject/bundle-capability-public-network.json +8 -0
  25. package/assets/vectors/dsg.infra.signed-law_1/reject/bundle-ceiling-negative-value.json +8 -0
  26. package/assets/vectors/dsg.infra.signed-law_1/reject/bundle-extra-field.json +8 -0
  27. package/assets/vectors/dsg.infra.signed-law_1/reject/bundle-policy-manifest-traversal-key.json +8 -0
  28. package/assets/vectors/dsg.infra.signed-law_1/reject/bundle-scope-descriptor-version-v2.json +8 -0
  29. package/assets/vectors/dsg.infra.signed-law_1/reject/bundle-short-signature.json +8 -0
  30. package/assets/vectors/dsg.infra.signed-law_1/reject/bundle-unknown-key-class.json +8 -0
  31. package/assets/vectors/dsg.infra.signed-law_1/reject/bundle-wrong-kind.json +8 -0
  32. package/assets/vectors/dsg.kernel.bootstrap_1/accept/intent-enroll-human-root.json +7 -0
  33. package/assets/vectors/dsg.kernel.bootstrap_1/accept/intent-initialize.json +7 -0
  34. package/assets/vectors/dsg.kernel.bootstrap_1/accept/signed-enroll-signed.json +7 -0
  35. package/assets/vectors/dsg.kernel.bootstrap_1/reject/intent-empty-resources.json +8 -0
  36. package/assets/vectors/dsg.kernel.bootstrap_1/reject/intent-extra-field.json +8 -0
  37. package/assets/vectors/dsg.kernel.bootstrap_1/reject/intent-two-uses.json +8 -0
  38. package/assets/vectors/dsg.kernel.bootstrap_1/reject/intent-unknown-action.json +8 -0
  39. package/assets/vectors/dsg.kernel.bootstrap_1/reject/intent-version-v2.json +8 -0
  40. package/assets/vectors/dsg.kernel.bootstrap_1/reject/signed-short-signature.json +8 -0
  41. package/assets/vectors/dsg.kernel.dispatch-consume_1/accept/lease-kernel-201-live-capture.json +7 -0
  42. package/assets/vectors/dsg.kernel.dispatch-consume_1/accept/lease-replayed-stale.json +7 -0
  43. package/assets/vectors/dsg.kernel.dispatch-consume_1/accept/refusal-revoked.json +7 -0
  44. package/assets/vectors/dsg.kernel.dispatch-consume_1/accept/request-first-consume.json +7 -0
  45. package/assets/vectors/dsg.kernel.dispatch-consume_1/accept/request-renewal.json +7 -0
  46. package/assets/vectors/dsg.kernel.dispatch-consume_1/reject/lease-fractional-expires-at.json +8 -0
  47. package/assets/vectors/dsg.kernel.dispatch-consume_1/reject/lease-missing-profile.json +8 -0
  48. package/assets/vectors/dsg.kernel.dispatch-consume_1/reject/lease-renewal-wrong-literal.json +8 -0
  49. package/assets/vectors/dsg.kernel.dispatch-consume_1/reject/lease-skew-above-bound.json +8 -0
  50. package/assets/vectors/dsg.kernel.dispatch-consume_1/reject/refusal-empty-issues.json +8 -0
  51. package/assets/vectors/dsg.kernel.dispatch-consume_1/reject/refusal-unknown-code.json +8 -0
  52. package/assets/vectors/dsg.kernel.dispatch-consume_1/reject/request-extra-field.json +8 -0
  53. package/assets/vectors/dsg.kernel.dispatch-consume_1/reject/request-fractional-requested-at.json +8 -0
  54. package/assets/vectors/dsg.kernel.dispatch-consume_1/reject/request-malformed-case-orn.json +8 -0
  55. package/assets/vectors/dsg.kernel.dispatch-consume_1/reject/request-negative-generation.json +8 -0
  56. package/assets/vectors/dsg.kernel.dispatch-consume_1/reject/request-uppercase-envelope-digest.json +8 -0
  57. package/assets/vectors/dsg.kernel.ordinary-authority_1/accept/authority-human-enrollment.json +7 -0
  58. package/assets/vectors/dsg.kernel.ordinary-authority_1/accept/authority-synthetic-enrollment.json +7 -0
  59. package/assets/vectors/dsg.kernel.ordinary-authority_1/reject/authority-empty-actions.json +8 -0
  60. package/assets/vectors/dsg.kernel.ordinary-authority_1/reject/authority-extra-field.json +8 -0
  61. package/assets/vectors/dsg.kernel.ordinary-authority_1/reject/authority-fractional-not-before.json +8 -0
  62. package/assets/vectors/dsg.kernel.ordinary-authority_1/reject/authority-missing-ceilings.json +8 -0
  63. package/assets/vectors/dsg.kernel.ordinary-authority_1/reject/authority-release-automation-key-class.json +8 -0
  64. package/assets/vectors/dsg.kernel.ordinary-authority_1/reject/authority-version-v2.json +8 -0
  65. package/assets/vectors/dsg.registry.revocations_1/accept/pointer-v2.json +7 -0
  66. package/assets/vectors/dsg.registry.revocations_1/accept/snapshot-chained-with-entries.json +7 -0
  67. package/assets/vectors/dsg.registry.revocations_1/accept/snapshot-genesis.json +7 -0
  68. package/assets/vectors/dsg.registry.revocations_1/reject/pointer-traversal-url.json +8 -0
  69. package/assets/vectors/dsg.registry.revocations_1/reject/snapshot-extra-field.json +8 -0
  70. package/assets/vectors/dsg.registry.revocations_1/reject/snapshot-not-test-only.json +8 -0
  71. package/assets/vectors/dsg.registry.revocations_1/reject/snapshot-version-zero.json +8 -0
  72. package/assets/vectors/dsg.registry.revocations_1/reject/snapshot-wrong-domain-v2.json +8 -0
  73. package/assets/vectors/dsg.run.operation_1/accept/accepted-receipt-fractional-accepted-at.json +7 -0
  74. package/assets/vectors/dsg.run.operation_1/accept/readback-queued-pending.json +7 -0
  75. package/assets/vectors/dsg.run.operation_1/accept/request-minimal-valid.json +7 -0
  76. package/assets/vectors/dsg.run.operation_1/digest/envelope-m010-envelope-digest.json +8 -0
  77. package/assets/vectors/dsg.run.operation_1/reject/accepted-missing-plan-digest.json +8 -0
  78. package/assets/vectors/dsg.run.operation_1/reject/request-document-schema-version-2.json +8 -0
  79. package/assets/vectors/dsg.run.operation_1/reject/request-extra-field.json +8 -0
  80. package/assets/vectors/dsg.run.operation_1/reject/request-r01-original-short-orn.json +8 -0
  81. package/assets/vectors/dsg.run.operation_1/reject/request-uppercase-plan-digest.json +8 -0
  82. package/assets/vectors/dsg.run.operation_1/reject/request-wrong-profile-other.json +8 -0
  83. package/assets/vectors/dsg.run.operation_1/reject/request-wrong-profile-v2.json +8 -0
  84. package/checklist.json +12 -0
  85. package/dist/cli.cjs +16 -0
  86. package/dist/cli.d.cts +1 -0
  87. package/dist/cli.d.mts +1 -0
  88. package/dist/cli.mjs +19 -0
  89. package/dist/cli.mjs.map +1 -0
  90. package/dist/index.cjs +4 -0
  91. package/dist/index.d.cts +57 -0
  92. package/dist/index.d.cts.map +1 -0
  93. package/dist/index.d.mts +57 -0
  94. package/dist/index.d.mts.map +1 -0
  95. package/dist/index.mjs +2 -0
  96. package/dist/reference-adapter-CaAo2iIH.mjs +1441 -0
  97. package/dist/reference-adapter-CaAo2iIH.mjs.map +1 -0
  98. package/dist/reference-adapter-DWurDAI9.cjs +1450 -0
  99. package/package.json +64 -0
  100. package/src/adapter.ts +32 -0
  101. package/src/cli.ts +19 -0
  102. package/src/index.ts +4 -0
  103. package/src/reference-adapter.ts +42 -0
  104. 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,4 @@
1
+ export type { Adapter, ParseResult, Vector } from "./adapter.js";
2
+ export { runVectors } from "./runner.js";
3
+ export type { RunReport, VectorVerdict } from "./runner.js";
4
+ export { referenceAdapter } from "./reference-adapter.js";
@@ -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
+ }