assertledger 1.3.1 → 1.5.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.
Files changed (107) hide show
  1. package/README.fr.md +6 -3
  2. package/README.md +6 -3
  3. package/conformance/schema-extensions.json +15 -0
  4. package/dist/build-info.json +1 -1
  5. package/dist/cli.d.ts.map +1 -1
  6. package/dist/cli.js +218 -8
  7. package/dist/cli.js.map +1 -1
  8. package/dist/contracts/index.d.ts +467 -4
  9. package/dist/contracts/index.d.ts.map +1 -1
  10. package/dist/contracts/index.js +256 -3
  11. package/dist/contracts/index.js.map +1 -1
  12. package/dist/contracts/qualification.d.ts +528 -0
  13. package/dist/contracts/qualification.d.ts.map +1 -0
  14. package/dist/contracts/qualification.js +357 -0
  15. package/dist/contracts/qualification.js.map +1 -0
  16. package/dist/core/index.d.ts +9 -0
  17. package/dist/core/index.d.ts.map +1 -1
  18. package/dist/core/index.js +46 -2
  19. package/dist/core/index.js.map +1 -1
  20. package/dist/core/qualification.d.ts +17 -0
  21. package/dist/core/qualification.d.ts.map +1 -0
  22. package/dist/core/qualification.js +571 -0
  23. package/dist/core/qualification.js.map +1 -0
  24. package/dist/diagnostics.d.ts.map +1 -1
  25. package/dist/diagnostics.js +81 -4
  26. package/dist/diagnostics.js.map +1 -1
  27. package/dist/engine/adapters/bun-native.d.ts +35 -0
  28. package/dist/engine/adapters/bun-native.d.ts.map +1 -0
  29. package/dist/engine/adapters/bun-native.js +179 -0
  30. package/dist/engine/adapters/bun-native.js.map +1 -0
  31. package/dist/engine/adapters/bun-test-designated-profile.d.ts +23 -0
  32. package/dist/engine/adapters/bun-test-designated-profile.d.ts.map +1 -0
  33. package/dist/engine/adapters/bun-test-designated-profile.js +24 -0
  34. package/dist/engine/adapters/bun-test-designated-profile.js.map +1 -0
  35. package/dist/engine/container.d.ts +7 -0
  36. package/dist/engine/container.d.ts.map +1 -1
  37. package/dist/engine/container.js +22 -7
  38. package/dist/engine/container.js.map +1 -1
  39. package/dist/engine/git-regression.d.ts.map +1 -1
  40. package/dist/engine/git-regression.js +9 -2
  41. package/dist/engine/git-regression.js.map +1 -1
  42. package/dist/engine/index.d.ts +14 -0
  43. package/dist/engine/index.d.ts.map +1 -1
  44. package/dist/engine/index.js +828 -97
  45. package/dist/engine/index.js.map +1 -1
  46. package/dist/engine/qualification.d.ts +154 -0
  47. package/dist/engine/qualification.d.ts.map +1 -0
  48. package/dist/engine/qualification.js +725 -0
  49. package/dist/engine/qualification.js.map +1 -0
  50. package/dist/engine/test-closure.d.ts +39 -0
  51. package/dist/engine/test-closure.d.ts.map +1 -0
  52. package/dist/engine/test-closure.js +513 -0
  53. package/dist/engine/test-closure.js.map +1 -0
  54. package/dist/engine/witness-import.d.ts +31 -0
  55. package/dist/engine/witness-import.d.ts.map +1 -0
  56. package/dist/engine/witness-import.js +224 -0
  57. package/dist/engine/witness-import.js.map +1 -0
  58. package/dist/index.d.ts +3 -0
  59. package/dist/index.d.ts.map +1 -1
  60. package/dist/index.js +3 -0
  61. package/dist/index.js.map +1 -1
  62. package/dist/mcp/index.d.ts.map +1 -1
  63. package/dist/mcp/index.js +65 -0
  64. package/dist/mcp/index.js.map +1 -1
  65. package/dist/sdk/index.d.ts +292 -3
  66. package/dist/sdk/index.d.ts.map +1 -1
  67. package/dist/sdk/index.js +39 -1
  68. package/dist/sdk/index.js.map +1 -1
  69. package/docs/container-isolation.md +6 -0
  70. package/docs/migration-orchestration-qualification-v1.md +109 -0
  71. package/docs/migration-verification-v4.md +94 -0
  72. package/docs/orchestration-qualification.md +167 -0
  73. package/docs/reference.md +13 -0
  74. package/docs/witness-import.md +67 -0
  75. package/examples/orchestration/README.md +68 -0
  76. package/examples/orchestration/consumer-obligations.json +101 -0
  77. package/examples/orchestration/workspace/bitbucket-pipelines.yml +10 -0
  78. package/examples/orchestration/workspace/command-shape.test.mjs +25 -0
  79. package/examples/orchestration/workspace/commands.mjs +14 -0
  80. package/examples/orchestration/workspace/completion.mjs +39 -0
  81. package/examples/orchestration/workspace/faults.json +1 -0
  82. package/examples/orchestration/workspace/native.test.ts +4 -0
  83. package/examples/orchestration/workspace/package.json +6 -0
  84. package/examples/orchestration/workspace/packages/app/package.json +12 -0
  85. package/examples/orchestration/workspace/packages/app/verdict.txt +1 -0
  86. package/examples/orchestration/workspace/packages/leaf/package.json +9 -0
  87. package/examples/orchestration/workspace/packages/leaf/verdict.txt +1 -0
  88. package/examples/orchestration/workspace/pnpm-lock.yaml +170 -0
  89. package/examples/orchestration/workspace/pnpm-workspace.yaml +2 -0
  90. package/examples/orchestration/workspace/task.mjs +23 -0
  91. package/examples/orchestration/workspace/turbo.json +38 -0
  92. package/examples/orchestration/workspace/wrapper.mjs +45 -0
  93. package/integrations/bun/driver.d.mts +30 -1
  94. package/integrations/bun/driver.mjs +215 -6
  95. package/integrations/bun/preload.mjs +186 -5
  96. package/integrations/bun-native/README.md +86 -0
  97. package/integrations/bun-native/driver.mjs +396 -0
  98. package/integrations/bun-native/preload.mjs +375 -0
  99. package/integrations/skill/SKILL.md +25 -5
  100. package/package.json +1 -1
  101. package/schemas/evidence-manifest.v4.json +995 -0
  102. package/schemas/qualification-execution-request.v1.json +571 -0
  103. package/schemas/qualification-plan.v1.json +528 -0
  104. package/schemas/qualification-receipt.v1.json +821 -0
  105. package/schemas/qualification-replay-result.v1.json +37 -0
  106. package/schemas/verification-request.v4.json +590 -0
  107. package/schemas/witness-import-request.v1.json +231 -0
package/README.fr.md CHANGED
@@ -9,12 +9,12 @@ Vous obtenez un verdict, les observations qui le justifient et un fichier de pre
9
9
 
10
10
  [Essayer l’exemple](#essayer-lexemple) · [Comprendre le résultat](#comprendre-le-résultat) · [Utiliser votre dépôt](#utiliser-votre-dépôt) · [Documentation](#documentation)
11
11
 
12
- **1.3 · node:test et bun:test · CLI, SDK et MCP · MIT**
12
+ **1.4 · node:test et bun:test · CLI, SDK et MCP · MIT**
13
13
 
14
14
  Installez l’outil dans votre dépôt avec Node.js 22.15 ou une version ultérieure :
15
15
 
16
16
  ```sh
17
- npm install --save-dev assertledger@1.3.1
17
+ npm install --save-dev assertledger@1.5.0
18
18
  npx assertledger doctor .
19
19
  ```
20
20
 
@@ -60,7 +60,7 @@ flowchart LR
60
60
  Prérequis : **Git**, **Node.js 22.15+** et **pnpm 11.1.2**. L’exemple utilise l’adaptateur intégré `node:test`.
61
61
 
62
62
  ```sh
63
- git clone --branch v1.3.1 https://github.com/hoklims/assertledger.git
63
+ git clone --branch v1.5.0 https://github.com/hoklims/assertledger.git
64
64
  cd assertledger
65
65
  pnpm install --frozen-lockfile
66
66
  pnpm build
@@ -181,6 +181,9 @@ L’adaptateur cible précisément Bun 1.4.2 et attribue les échecs de cet outi
181
181
  natifs restent des erreurs d’exécution et ne comptent pas comme détection du défaut. Le
182
182
  [guide de migration Bun](docs/migration-verification-v3.md) décrit la requête, l’exécution et le
183
183
  rejeu. Ce mode exécute du code local de confiance sans bac à sable.
184
+ Pour vérifier un test `bun:test` existant désigné par son nom, ou rejouer un témoin rouge/vert
185
+ enregistré, utilisez une requête v4 ou `import-witness`, qui s’exécutent aussi en conteneur ou dans un
186
+ mode Windows natif déclaré ; voir le [guide de migration v4](docs/migration-verification-v4.md).
184
187
 
185
188
  ### Qualifier un test de régression commité
186
189
 
package/README.md CHANGED
@@ -9,12 +9,12 @@ You get a verdict, the observations behind it and an evidence file you can repla
9
9
 
10
10
  [Try the example](#try-the-example) · [Understand the result](#understand-the-result) · [Use your repository](#use-your-repository) · [Documentation](#documentation)
11
11
 
12
- **1.3 · node:test and bun:test · CLI, SDK and MCP · MIT**
12
+ **1.4 · node:test and bun:test · CLI, SDK and MCP · MIT**
13
13
 
14
14
  Install in your repository with Node.js 22.15 or later:
15
15
 
16
16
  ```sh
17
- npm install --save-dev assertledger@1.3.1
17
+ npm install --save-dev assertledger@1.5.0
18
18
  npx assertledger doctor .
19
19
  ```
20
20
 
@@ -59,7 +59,7 @@ flowchart LR
59
59
  You need **Git**, **Node.js 22.15+** and **pnpm 11.1.2**. The example uses the built-in `node:test` adapter.
60
60
 
61
61
  ```sh
62
- git clone --branch v1.3.1 https://github.com/hoklims/assertledger.git
62
+ git clone --branch v1.5.0 https://github.com/hoklims/assertledger.git
63
63
  cd assertledger
64
64
  pnpm install --frozen-lockfile
65
65
  pnpm build
@@ -173,6 +173,9 @@ The Bun adapter pins version 1.4.2 and attributes failures from this helper. Nat
173
173
  failures remain operational failures and cannot count as target detection. See the
174
174
  [Bun migration guide](docs/migration-verification-v3.md) for the request, execution and replay
175
175
  contract. The execution mode is explicitly unsandboxed and intended for trusted local code.
176
+ To verify an existing `bun:test` test by name, or to replay a recorded red/green witness, use a v4
177
+ request or `import-witness`, which also run in a container or a declared Windows-native backend; see
178
+ the [v4 migration guide](docs/migration-verification-v4.md).
176
179
 
177
180
  ### Qualify a committed regression test
178
181
 
@@ -27,6 +27,11 @@
27
27
  "rawSha256": "sha256:460925ce29c51e47a38e439264f56dac9d444f52a07ec103f38b44e59679adbd",
28
28
  "$id": "https://testforge.dev/schemas/evidence-manifest.v3.json"
29
29
  },
30
+ {
31
+ "path": "schemas/evidence-manifest.v4.json",
32
+ "rawSha256": "sha256:56bad50457b84864decbe911d98e3257880e4562d69487b4a820f7d516c3d11b",
33
+ "$id": "https://testforge.dev/schemas/evidence-manifest.v4.json"
34
+ },
30
35
  {
31
36
  "path": "schemas/evidence-provider-manifest.v1.json",
32
37
  "rawSha256": "sha256:6100995f772cd24b7369e4428fb1aba570f1ba6e1f61295bc42a1f7ce6b5bde3",
@@ -56,6 +61,16 @@
56
61
  "path": "schemas/verification-request.v3.json",
57
62
  "rawSha256": "sha256:3b88e4c58425aa9a45da330b27f4a48d778c1f9a2494ac2a9a4c01b29728fd77",
58
63
  "$id": "https://testforge.dev/schemas/verification-request.v3.json"
64
+ },
65
+ {
66
+ "path": "schemas/verification-request.v4.json",
67
+ "rawSha256": "sha256:f08389ef40c7d73d7f1c2006aee2efcd2bf53c8b78338883f4bc9f21808db842",
68
+ "$id": "https://testforge.dev/schemas/verification-request.v4.json"
69
+ },
70
+ {
71
+ "path": "schemas/witness-import-request.v1.json",
72
+ "rawSha256": "sha256:ec2aa94fd0d50e6dec7ff6f6b9bea294febf8d7ba4598e01fe89177f97348974",
73
+ "$id": "https://testforge.dev/schemas/witness-import-request.v1.json"
59
74
  }
60
75
  ]
61
76
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "sourceRevision": {
3
3
  "status": "RECORDED",
4
- "commit": "a0d358248efc7f13556a989bd132da49b46cec36",
4
+ "commit": "9895fee7350669b65e033c47f6ebe33675fb6f5a",
5
5
  "worktree": "CLEAN"
6
6
  }
7
7
  }
package/dist/cli.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AASA,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAMlD,OAAO,EAAgD,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAWlG,MAAM,WAAW,KAAK;IACpB,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;IAC7B,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CACjC;AAED,MAAM,WAAW,eAAe;IAC9B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,eAAe,CAAC,EAAE,OAAO,eAAe,CAAC;IACzC,cAAc,CAAC,EAAE,OAAO,cAAc,CAAC;CACxC;AAkoBD,wBAAsB,MAAM,CAC1B,IAAI,EAAE,MAAM,EAAE,EACd,EAAE,EAAE,KAAK,EACT,YAAY,GAAE,eAAoB,GACjC,OAAO,CAAC,MAAM,CAAC,CAyvBjB"}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAcA,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAOlD,OAAO,EAAgD,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAYlG,MAAM,WAAW,KAAK;IACpB,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;IAC7B,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CACjC;AAED,MAAM,WAAW,eAAe;IAC9B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,eAAe,CAAC,EAAE,OAAO,eAAe,CAAC;IACzC,cAAc,CAAC,EAAE,OAAO,cAAc,CAAC;CACxC;AAkrBD,wBAAsB,MAAM,CAC1B,IAAI,EAAE,MAAM,EAAE,EACd,EAAE,EAAE,KAAK,EACT,YAAY,GAAE,eAAoB,GACjC,OAAO,CAAC,MAAM,CAAC,CAs7BjB"}
package/dist/cli.js CHANGED
@@ -4,12 +4,14 @@ import path from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
5
  import { serveStdio } from "@modelcontextprotocol/server/stdio";
6
6
  import { ContractError } from "./contracts/index.js";
7
- import { renderDiagnostics, renderRepositoryLinkRefusal } from "./diagnostics.js";
7
+ import { QualificationExpectedDomainSchema } from "./contracts/qualification.js";
8
+ import { quoteRepositoryEntry, renderDiagnostics, renderRepositoryLinkRefusal, } from "./diagnostics.js";
8
9
  import { connectClient, disconnectClient } from "./engine/connection.js";
9
10
  import { parseContainerRuntimeCommand } from "./engine/container.js";
10
11
  import { runFixtureDemo } from "./engine/demo.js";
11
12
  import { renderGitRegressionSummary, } from "./engine/git-regression.js";
12
13
  import { setupRepository } from "./engine/setup.js";
14
+ import { DEFAULT_WITNESS_ENVIRONMENT_ALLOWLIST } from "./engine/witness-import.js";
13
15
  import { AgenticCorpusError, evaluateAgenticCorpusHoldout, evaluateAgenticCorpusPublic, inspectAgenticCorpus, } from "./evaluation/agentic-corpus.js";
14
16
  import { createAssertLedgerServer } from "./mcp/index.js";
15
17
  import { AssertLedger } from "./sdk/index.js";
@@ -58,9 +60,21 @@ Commands:
58
60
  evidence-provider-manifest|evidence-export-request|evidence-export|
59
61
  evidence-export-replay-result>
60
62
  Print a JSON Schema
61
- verify [request.json|-] [--container-runtime JSON_ARGV | --allow-unsafe-execution]
62
- Execute a v2 container or trusted-local campaign
63
+ verify [request.json|-] [--container-runtime JSON_ARGV | --allow-unsafe-execution
64
+ | --allow-windows-native-execution] Execute a v2-v4 container, trusted-local or
65
+ declared Windows-native campaign
66
+ import-witness [request.json|-] --out DIRECTORY
67
+ (--container-image NAME@sha256:DIGEST [--container-runtime JSON_ARGV]
68
+ | --allow-unsafe-execution | --allow-windows-native-execution)
69
+ [--env NAME ...] [--json] Replay a recorded red/green witness
63
70
  replay [manifest.json|-] Verify an evidence digest
71
+ qualification-plan [plan.json|-] Seal an operator-owned obligation inventory
72
+ qualify [request.json|-] --allow-unsafe-execution
73
+ Execute a scoped UNSANDBOXED qualification
74
+ qualification-replay [receipt.json|-] [--domain domain.json]
75
+ Replay integrity and explicit validity domain
76
+ qualification-ci [receipt.json|-] --observation observation.json
77
+ Admit an independently signed CI observation
64
78
  export [request.json|-] Export replay-valid evidence for external consumers
65
79
  export-replay [export.json|-] Replay an evidence export
66
80
  provider Describe the evidence provider and its limits
@@ -106,6 +120,10 @@ function authorizeTrustedLocalExecution(request) {
106
120
  },
107
121
  };
108
122
  }
123
+ /** Windows-native execution has its own operator flag; the request value alone never grants it. */
124
+ function authorizeWindowsNativeExecution(request) {
125
+ return authorizeTrustedLocalExecution(request);
126
+ }
109
127
  function authorizeBenchmarkAcquisition(request) {
110
128
  if (!isRecord(request) || !isRecord(request.verificationRequest))
111
129
  return request;
@@ -565,6 +583,20 @@ const VALIDATION_ERROR_CODES = new Set([
565
583
  "UNSUPPORTED_ADAPTER",
566
584
  "UNSUPPORTED_ISOLATION",
567
585
  "UNSUPPORTED_REPOSITORY_SYMLINK",
586
+ "REPOSITORY_LINK_IN_TEST_CLOSURE",
587
+ "REPOSITORY_LINK_ESCAPES_ROOT",
588
+ "TEST_CLOSURE_UNBOUNDED",
589
+ "WINDOWS_NATIVE_HOST_REQUIRED",
590
+ "WINDOWS_NATIVE_EXECUTION_NOT_ACKNOWLEDGED",
591
+ "DESIGNATED_TEST_OVERLAID",
592
+ "DESIGNATED_TEST_FILE_MISSING",
593
+ "REPOSITORY_DEPENDENCIES_EXCLUDED",
594
+ "WITNESS_REPOSITORY_INVALID",
595
+ "WITNESS_TARGET_PATH_INVALID",
596
+ "WITNESS_TARGET_BASE_MISMATCH",
597
+ "WITNESS_TARGET_UNCHANGED",
598
+ "WITNESS_OUTPUT_INSIDE_REPOSITORY",
599
+ "WITNESS_OUTPUT_EXISTS",
568
600
  "VERIFICATION_REQUEST_REPOSITORY_MISMATCH",
569
601
  "WORLD_BUDGET_EXCEEDED",
570
602
  "WORLD_OVERLAY_BYTES_EXCEEDED",
@@ -598,6 +630,19 @@ function classifyError(error) {
598
630
  }
599
631
  return 5;
600
632
  }
633
+ /** Names the link that refused a static diagnostic, or the computed imports that leave the closure
634
+ * unbounded. Repository paths are untrusted content and are quoted. */
635
+ function renderLinkAssessment(assessment) {
636
+ if (assessment === undefined)
637
+ return undefined;
638
+ if (assessment.refused !== undefined) {
639
+ return `Link detail: ${quoteRepositoryEntry(assessment.refused.path)} (${assessment.refused.reasonCode})`;
640
+ }
641
+ const unbounded = assessment.unbounded
642
+ .slice(0, 20)
643
+ .map((edge) => `Unbounded closure: ${quoteRepositoryEntry(edge.file)} (${edge.reason})`);
644
+ return [`Links outside every test closure: ${assessment.omitted.length}`, ...unbounded].join("\n");
645
+ }
601
646
  export async function runCli(argv, io, dependencies = {}) {
602
647
  const ledger = new AssertLedger();
603
648
  const positional = argv.filter((argument) => !argument.startsWith("--"));
@@ -699,10 +744,17 @@ export async function runCli(argv, io, dependencies = {}) {
699
744
  }
700
745
  return runtimeResult.status === "READY" ? 0 : 3;
701
746
  }
747
+ let linkAssessment;
702
748
  const result = await ledger.doctor(root, {
703
749
  ...(exclude.length === 0 ? {} : { exclude }),
704
750
  ...(framework === undefined ? {} : { framework }),
751
+ onLinkAssessment: (assessment) => {
752
+ linkAssessment = assessment;
753
+ },
705
754
  });
755
+ const linkDetail = renderLinkAssessment(linkAssessment);
756
+ if (linkDetail !== undefined)
757
+ io.writeStderr(`${linkDetail}\n`);
706
758
  if (argv.includes("--json")) {
707
759
  writeJson(io, result);
708
760
  }
@@ -894,6 +946,11 @@ export async function runCli(argv, io, dependencies = {}) {
894
946
  return 0;
895
947
  }
896
948
  case "analyze": {
949
+ // `--json` stays accepted: the output is always JSON and the documented invocations pass it.
950
+ if (argv.slice(1).some((argument) => argument.startsWith("-") && argument !== "--json")) {
951
+ io.writeStderr(USAGE);
952
+ return 64;
953
+ }
897
954
  const root = path.resolve(io.cwd, positional[1] ?? ".");
898
955
  writeJson(io, await ledger.analyze(root));
899
956
  return 0;
@@ -1024,6 +1081,75 @@ export async function runCli(argv, io, dependencies = {}) {
1024
1081
  io.writeStdout(renderGitRegressionSummary(result, options));
1025
1082
  return decisionExitCode(result);
1026
1083
  }
1084
+ case "import-witness": {
1085
+ const valueFlags = new Set(["--out", "--container-image", "--container-runtime", "--env"]);
1086
+ const booleanFlags = new Set([
1087
+ "--allow-unsafe-execution",
1088
+ "--allow-windows-native-execution",
1089
+ "--json",
1090
+ ]);
1091
+ let input;
1092
+ for (let index = 1; index < argv.length; index += 1) {
1093
+ const argument = argv[index] ?? "";
1094
+ if (valueFlags.has(argument)) {
1095
+ const value = argv[index + 1];
1096
+ if (value === undefined || value.startsWith("--")) {
1097
+ io.writeStderr(USAGE);
1098
+ return 64;
1099
+ }
1100
+ index += 1;
1101
+ }
1102
+ else if (!booleanFlags.has(argument)) {
1103
+ if (argument.startsWith("--") || input !== undefined) {
1104
+ io.writeStderr(USAGE);
1105
+ return 64;
1106
+ }
1107
+ input = argument;
1108
+ }
1109
+ }
1110
+ const out = optionalFlag(argv, "--out");
1111
+ if (out === undefined) {
1112
+ io.writeStderr(USAGE);
1113
+ return 64;
1114
+ }
1115
+ const allowUnsafeExecution = argv.includes("--allow-unsafe-execution");
1116
+ const allowWindowsNative = argv.includes("--allow-windows-native-execution");
1117
+ const containerImage = optionalFlag(argv, "--container-image");
1118
+ const containerRuntime = optionalFlag(argv, "--container-runtime");
1119
+ const environment = repeatedFlagValues(argv, "--env");
1120
+ const selected = [allowUnsafeExecution, allowWindowsNative, containerImage !== undefined];
1121
+ if (selected.filter(Boolean).length > 1 ||
1122
+ (containerImage === undefined && containerRuntime !== undefined) ||
1123
+ (containerImage !== undefined && environment.length > 0)) {
1124
+ throw new TypeError("ISOLATION_MODE_CONFLICT");
1125
+ }
1126
+ if (!selected.some(Boolean)) {
1127
+ io.writeStderr("Refusing to replay a witness without an authorized backend: pass --container-image, " +
1128
+ "--allow-unsafe-execution or --allow-windows-native-execution.\n");
1129
+ return 4;
1130
+ }
1131
+ const environmentAllowlist = environment.length === 0 ? [...DEFAULT_WITNESS_ENVIRONMENT_ALLOWLIST] : environment;
1132
+ const result = await ledger.importWitness(await readJsonInput(input, io), {
1133
+ out: path.resolve(io.cwd, out),
1134
+ isolation: containerImage !== undefined
1135
+ ? {
1136
+ kind: "container",
1137
+ image: containerImage,
1138
+ ...(containerRuntime === undefined
1139
+ ? {}
1140
+ : { runtimeCommand: parseContainerRuntimeCommand(containerRuntime) }),
1141
+ }
1142
+ : allowWindowsNative
1143
+ ? { kind: "windows-native", environmentAllowlist }
1144
+ : { kind: "trusted-local", environmentAllowlist },
1145
+ });
1146
+ if (argv.includes("--json"))
1147
+ writeJson(io, result);
1148
+ else {
1149
+ io.writeStdout(`${await readFile(path.join(path.resolve(io.cwd, out), "summary.md"), "utf8")}`);
1150
+ }
1151
+ return decisionExitCode(result);
1152
+ }
1027
1153
  case "schema": {
1028
1154
  const name = positional[1];
1029
1155
  if (name !== "verification-request" &&
@@ -1043,6 +1169,10 @@ export async function runCli(argv, io, dependencies = {}) {
1043
1169
  name !== "replay-result" &&
1044
1170
  name !== "agentic-benchmark-request" &&
1045
1171
  name !== "agentic-benchmark-artifact" &&
1172
+ name !== "qualification-plan" &&
1173
+ name !== "qualification-execution-request" &&
1174
+ name !== "qualification-receipt" &&
1175
+ name !== "qualification-replay-result" &&
1046
1176
  name !== "agentic-benchmark-replay-result" &&
1047
1177
  name !== "agentic-benchmark-acquisition-request" &&
1048
1178
  name !== "agentic-benchmark-acquisition-result" &&
@@ -1078,22 +1208,43 @@ export async function runCli(argv, io, dependencies = {}) {
1078
1208
  case "verify": {
1079
1209
  const request = await readJsonInput(positionalArguments(argv, new Set(["--container-runtime"]))[1], io);
1080
1210
  const allowUnsafeExecution = argv.includes("--allow-unsafe-execution");
1211
+ const allowWindowsNative = argv.includes("--allow-windows-native-execution");
1081
1212
  const runtimeArgument = optionalFlag(argv, "--container-runtime");
1082
- const containerRequest = isRecord(request) &&
1083
- isRecord(request.isolation) &&
1084
- request.isolation.kind === "container";
1085
- if (containerRequest ? allowUnsafeExecution : runtimeArgument !== undefined) {
1213
+ const isolationKind = isRecord(request) && isRecord(request.isolation) ? request.isolation.kind : undefined;
1214
+ const containerRequest = isolationKind === "container";
1215
+ const windowsNativeRequest = isolationKind === "windows-native";
1216
+ // Each backend has exactly one authorization; another backend's flag never stands in.
1217
+ if ((containerRequest && (allowUnsafeExecution || allowWindowsNative)) ||
1218
+ (windowsNativeRequest && (allowUnsafeExecution || runtimeArgument !== undefined)) ||
1219
+ (!containerRequest &&
1220
+ !windowsNativeRequest &&
1221
+ (allowWindowsNative || runtimeArgument !== undefined))) {
1086
1222
  throw new TypeError("ISOLATION_MODE_CONFLICT");
1087
1223
  }
1088
- if (!containerRequest && !allowUnsafeExecution) {
1224
+ if (windowsNativeRequest && !allowWindowsNative) {
1225
+ io.writeStderr("Refusing WINDOWS_NATIVE_UNSANDBOXED execution without --allow-windows-native-execution.\n");
1226
+ return 4;
1227
+ }
1228
+ if (!containerRequest && !windowsNativeRequest && !allowUnsafeExecution) {
1089
1229
  io.writeStderr("Refusing trusted-local execution without --allow-unsafe-execution.\n");
1090
1230
  return 4;
1091
1231
  }
1092
1232
  const version2 = isRecord(request) && request.schemaVersion === "2.0.0";
1093
1233
  const version3 = isRecord(request) && request.schemaVersion === "3.0.0";
1234
+ const version4 = isRecord(request) && request.schemaVersion === "4.0.0";
1094
1235
  const runtimeOptions = runtimeArgument === undefined
1095
1236
  ? {}
1096
1237
  : { containerRuntime: { command: parseContainerRuntimeCommand(runtimeArgument) } };
1238
+ if (version4) {
1239
+ const authorized = containerRequest
1240
+ ? request
1241
+ : windowsNativeRequest
1242
+ ? authorizeWindowsNativeExecution(request)
1243
+ : authorizeTrustedLocalExecution(request);
1244
+ const manifest = await ledger.verifyV4(authorized, runtimeOptions);
1245
+ writeJson(io, manifest);
1246
+ return decisionExitCode(manifest);
1247
+ }
1097
1248
  const result = version3
1098
1249
  ? await ledger.verifyV3(containerRequest ? request : authorizeTrustedLocalExecution(request), runtimeOptions)
1099
1250
  : containerRequest || version2
@@ -1102,6 +1253,65 @@ export async function runCli(argv, io, dependencies = {}) {
1102
1253
  writeJson(io, result);
1103
1254
  return decisionExitCode(result);
1104
1255
  }
1256
+ case "qualification-plan": {
1257
+ if (argv.slice(1).filter((item) => item !== "--json").length > 1 ||
1258
+ argv.slice(1).some((item) => item.startsWith("--") && item !== "--json")) {
1259
+ io.writeStderr(USAGE);
1260
+ return 64;
1261
+ }
1262
+ writeJson(io, ledger.sealQualificationPlan(await readJsonInput(positional[1], io)));
1263
+ return 0;
1264
+ }
1265
+ case "qualify": {
1266
+ if (argv
1267
+ .slice(1)
1268
+ .some((item) => item.startsWith("--") && item !== "--allow-unsafe-execution" && item !== "--json") ||
1269
+ positional.length > 2) {
1270
+ io.writeStderr(USAGE);
1271
+ return 64;
1272
+ }
1273
+ if (!argv.includes("--allow-unsafe-execution")) {
1274
+ io.writeStderr("Refusing UNSANDBOXED qualification without --allow-unsafe-execution.\n");
1275
+ return 4;
1276
+ }
1277
+ const result = await ledger.qualifyOrchestration(await readJsonInput(positional[1], io), {
1278
+ allowUnsafeExecution: true,
1279
+ });
1280
+ writeJson(io, result);
1281
+ return result.decision === "QUALIFIED" ? 0 : result.decision === "OPEN" ? 3 : 2;
1282
+ }
1283
+ case "qualification-replay": {
1284
+ const domainPath = optionalFlag(argv, "--domain");
1285
+ const args = positionalArguments(argv, new Set(["--domain"]));
1286
+ if (args.length > 2 ||
1287
+ argv
1288
+ .slice(1)
1289
+ .some((item) => item.startsWith("--") && item !== "--domain" && item !== "--json")) {
1290
+ io.writeStderr(USAGE);
1291
+ return 64;
1292
+ }
1293
+ const domain = domainPath === undefined
1294
+ ? undefined
1295
+ : QualificationExpectedDomainSchema.parse(await readJsonInput(domainPath, io));
1296
+ const result = ledger.replayQualification(await readJsonInput(args[1], io), domain);
1297
+ writeJson(io, result);
1298
+ return result.valid ? 0 : 4;
1299
+ }
1300
+ case "qualification-ci": {
1301
+ const observationPath = optionalFlag(argv, "--observation");
1302
+ const args = positionalArguments(argv, new Set(["--observation"]));
1303
+ if (observationPath === undefined ||
1304
+ args.length > 2 ||
1305
+ argv
1306
+ .slice(1)
1307
+ .some((item) => item.startsWith("--") && item !== "--observation" && item !== "--json")) {
1308
+ io.writeStderr(USAGE);
1309
+ return 64;
1310
+ }
1311
+ const result = ledger.admitQualificationCi(await readJsonInput(args[1], io), await readJsonInput(observationPath, io));
1312
+ writeJson(io, result);
1313
+ return result.decision === "QUALIFIED" ? 0 : result.decision === "OPEN" ? 3 : 2;
1314
+ }
1105
1315
  case "replay": {
1106
1316
  const result = ledger.replay(await readJsonInput(positional[1], io));
1107
1317
  writeJson(io, result);