engineering-behavior-observatory 0.2.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 (178) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +151 -0
  3. package/contracts/codex-app-server-0.150.1/manifest.json +25 -0
  4. package/contracts/codex-app-server-0.150.1/schema/InitializeParams.json +84 -0
  5. package/contracts/codex-app-server-0.150.1/schema/JSONRPCMessage.json +137 -0
  6. package/contracts/codex-app-server-0.150.1/schema/v2/ThreadReadParams.json +17 -0
  7. package/contracts/codex-app-server-0.150.1/schema/v2/ThreadTokenUsageUpdatedNotification.json +82 -0
  8. package/contracts/codex-app-server-0.150.1/schema/v2/TurnInterruptParams.json +17 -0
  9. package/contracts/codex-app-server-0.150.1/types/AskForApproval.ts +5 -0
  10. package/contracts/codex-app-server-0.150.1/types/ClientNotification.ts +5 -0
  11. package/contracts/codex-app-server-0.150.1/types/SandboxMode.ts +5 -0
  12. package/contracts/codex-app-server-0.150.1/types/ThreadReadParams.ts +9 -0
  13. package/contracts/codex-app-server-0.150.1/types/TokenUsageBreakdown.ts +5 -0
  14. package/contracts/codex-app-server-0.150.1/types/TurnInterruptParams.ts +5 -0
  15. package/contracts/codex-app-server-0.153.4/manifest.json +27 -0
  16. package/contracts/codex-app-server-0.153.4/schema/JSONRPCMessage.json +137 -0
  17. package/contracts/codex-app-server-0.153.4/schema/v1/InitializeParams.json +84 -0
  18. package/contracts/codex-app-server-0.153.4/schema/v2/ThreadReadParams.json +17 -0
  19. package/contracts/codex-app-server-0.153.4/schema/v2/ThreadStartParams.json +515 -0
  20. package/contracts/codex-app-server-0.153.4/schema/v2/ThreadTokenUsageUpdatedNotification.json +82 -0
  21. package/contracts/codex-app-server-0.153.4/schema/v2/TurnInterruptParams.json +17 -0
  22. package/contracts/codex-app-server-0.153.4/schema/v2/TurnStartParams.json +921 -0
  23. package/contracts/codex-app-server-0.153.4/types/AskForApproval.ts +5 -0
  24. package/contracts/codex-app-server-0.153.4/types/ClientNotification.ts +5 -0
  25. package/contracts/codex-app-server-0.153.4/types/SandboxMode.ts +5 -0
  26. package/contracts/codex-app-server-0.153.4/types/ThreadReadParams.ts +12 -0
  27. package/contracts/codex-app-server-0.153.4/types/TokenUsageBreakdown.ts +5 -0
  28. package/contracts/codex-app-server-0.153.4/types/TurnInterruptParams.ts +5 -0
  29. package/contracts/openhands-agent-server-v1.44.1.json +65 -0
  30. package/contracts/openhands-agent-server-v1.46.0.json +46 -0
  31. package/dist/contracts/codex-app-server-0.153.4/types/AskForApproval.d.ts +9 -0
  32. package/dist/contracts/codex-app-server-0.153.4/types/AskForApproval.js +2 -0
  33. package/dist/contracts/codex-app-server-0.153.4/types/ClientNotification.d.ts +3 -0
  34. package/dist/contracts/codex-app-server-0.153.4/types/ClientNotification.js +2 -0
  35. package/dist/contracts/codex-app-server-0.153.4/types/SandboxMode.d.ts +1 -0
  36. package/dist/contracts/codex-app-server-0.153.4/types/SandboxMode.js +2 -0
  37. package/dist/contracts/codex-app-server-0.153.4/types/ThreadReadParams.d.ts +10 -0
  38. package/dist/contracts/codex-app-server-0.153.4/types/ThreadReadParams.js +2 -0
  39. package/dist/contracts/codex-app-server-0.153.4/types/TokenUsageBreakdown.d.ts +8 -0
  40. package/dist/contracts/codex-app-server-0.153.4/types/TokenUsageBreakdown.js +2 -0
  41. package/dist/contracts/codex-app-server-0.153.4/types/TurnInterruptParams.d.ts +4 -0
  42. package/dist/contracts/codex-app-server-0.153.4/types/TurnInterruptParams.js +2 -0
  43. package/dist/src/agent-sdk-normalizer.d.ts +22 -0
  44. package/dist/src/agent-sdk-normalizer.js +995 -0
  45. package/dist/src/agent-sdk-run.d.ts +31 -0
  46. package/dist/src/agent-sdk-run.js +242 -0
  47. package/dist/src/agent-sdk-runner.d.ts +96 -0
  48. package/dist/src/agent-sdk-runner.js +407 -0
  49. package/dist/src/agent-sdk.d.ts +294 -0
  50. package/dist/src/agent-sdk.js +644 -0
  51. package/dist/src/aggregation.d.ts +183 -0
  52. package/dist/src/aggregation.js +513 -0
  53. package/dist/src/artifacts.d.ts +36 -0
  54. package/dist/src/artifacts.js +1372 -0
  55. package/dist/src/atlas-grafana.d.ts +377 -0
  56. package/dist/src/atlas-grafana.js +47 -0
  57. package/dist/src/atlas-html.d.ts +2 -0
  58. package/dist/src/atlas-html.js +34 -0
  59. package/dist/src/atlas.d.ts +130 -0
  60. package/dist/src/atlas.js +235 -0
  61. package/dist/src/behavior-assertions.d.ts +95 -0
  62. package/dist/src/behavior-assertions.js +154 -0
  63. package/dist/src/cli.d.ts +5 -0
  64. package/dist/src/cli.js +957 -0
  65. package/dist/src/codex-judge.d.ts +44 -0
  66. package/dist/src/codex-judge.js +214 -0
  67. package/dist/src/codex-run.d.ts +114 -0
  68. package/dist/src/codex-run.js +519 -0
  69. package/dist/src/codex.d.ts +187 -0
  70. package/dist/src/codex.js +1028 -0
  71. package/dist/src/contracts.d.ts +155 -0
  72. package/dist/src/contracts.js +866 -0
  73. package/dist/src/corpus.d.ts +67 -0
  74. package/dist/src/corpus.js +540 -0
  75. package/dist/src/cursor-sdk-runner.d.ts +81 -0
  76. package/dist/src/cursor-sdk-runner.js +362 -0
  77. package/dist/src/cursor-sdk.d.ts +81 -0
  78. package/dist/src/cursor-sdk.js +1078 -0
  79. package/dist/src/deepseek-adapter.d.ts +152 -0
  80. package/dist/src/deepseek-adapter.js +777 -0
  81. package/dist/src/exports.d.ts +69 -0
  82. package/dist/src/exports.js +966 -0
  83. package/dist/src/human-calibration.d.ts +167 -0
  84. package/dist/src/human-calibration.js +618 -0
  85. package/dist/src/index.d.ts +71 -0
  86. package/dist/src/index.js +38 -0
  87. package/dist/src/lifecycle.d.ts +196 -0
  88. package/dist/src/lifecycle.js +1889 -0
  89. package/dist/src/normalization-integrity.d.ts +128 -0
  90. package/dist/src/normalization-integrity.js +429 -0
  91. package/dist/src/openhands-run.d.ts +36 -0
  92. package/dist/src/openhands-run.js +415 -0
  93. package/dist/src/openhands.d.ts +130 -0
  94. package/dist/src/openhands.js +859 -0
  95. package/dist/src/pi.d.ts +203 -0
  96. package/dist/src/pi.js +1345 -0
  97. package/dist/src/process-protocol.d.ts +245 -0
  98. package/dist/src/process-protocol.js +1261 -0
  99. package/dist/src/retained-evidence.d.ts +12 -0
  100. package/dist/src/retained-evidence.js +201 -0
  101. package/dist/src/run-bundles.d.ts +193 -0
  102. package/dist/src/run-bundles.js +993 -0
  103. package/dist/src/scheduler.d.ts +109 -0
  104. package/dist/src/scheduler.js +1080 -0
  105. package/dist/src/semantic-judge.d.ts +194 -0
  106. package/dist/src/semantic-judge.js +875 -0
  107. package/dist/src/structural-observations.d.ts +124 -0
  108. package/dist/src/structural-observations.js +671 -0
  109. package/dist/src/task-packets.d.ts +115 -0
  110. package/dist/src/task-packets.js +683 -0
  111. package/dist/src/uniform-events.d.ts +120 -0
  112. package/dist/src/uniform-events.js +158 -0
  113. package/dist/src/verifiers.d.ts +116 -0
  114. package/dist/src/verifiers.js +819 -0
  115. package/dist/src/workspaces.d.ts +56 -0
  116. package/dist/src/workspaces.js +1314 -0
  117. package/docs/README.md +60 -0
  118. package/docs/development/README.md +71 -0
  119. package/docs/development/documentation-sync.md +58 -0
  120. package/docs/development/extension-contracts.md +307 -0
  121. package/docs/evaluation/README.md +48 -0
  122. package/docs/evaluation/aggregation.md +112 -0
  123. package/docs/evaluation/behavior-assertions.md +45 -0
  124. package/docs/evaluation/human-calibration.md +170 -0
  125. package/docs/evaluation/normalization-integrity.md +80 -0
  126. package/docs/evaluation/semantic-judge.md +188 -0
  127. package/docs/evaluation/structural-observations.md +84 -0
  128. package/docs/evaluation/uniform-events.md +131 -0
  129. package/docs/guides/atlas.md +144 -0
  130. package/docs/guides/evidence-and-sharing.md +100 -0
  131. package/docs/guides/operator-guide.md +424 -0
  132. package/docs/guides/quickstart.md +152 -0
  133. package/docs/guides/telemetry.md +66 -0
  134. package/docs/harnesses/README.md +31 -0
  135. package/docs/harnesses/claude-agent-sdk.md +61 -0
  136. package/docs/harnesses/codex-harness.md +174 -0
  137. package/docs/harnesses/cursor-sdk.md +149 -0
  138. package/docs/harnesses/deepseek-harness.md +134 -0
  139. package/docs/harnesses/openhands-agent-server.md +153 -0
  140. package/docs/harnesses/pi-sdk.md +66 -0
  141. package/docs/reference/README.md +19 -0
  142. package/docs/reference/agent-sdk-operational-runner.md +118 -0
  143. package/docs/reference/cli.md +114 -0
  144. package/docs/reference/contracts.md +222 -0
  145. package/docs/reference/run-bundle-contract.md +354 -0
  146. package/docs/reference/run-lifecycle.md +54 -0
  147. package/examples/cursor-sdk/README.md +11 -0
  148. package/examples/cursor-sdk/capture-profile.json +1 -0
  149. package/examples/cursor-sdk/harness.json +1 -0
  150. package/examples/cursor-sdk/model.json +1 -0
  151. package/examples/cursor-sdk/native-limits.json +1 -0
  152. package/examples/cursor-sdk/native-tool-policy.json +1 -0
  153. package/ontology/behavior-categories.v1.json +46 -0
  154. package/package.json +66 -0
  155. package/release/0.1.0/KNOWN_LIMITATIONS.md +35 -0
  156. package/release/0.1.0/README.md +46 -0
  157. package/release/0.1.0/reproducibility.json +61 -0
  158. package/release/0.2.0/KNOWN_LIMITATIONS.md +40 -0
  159. package/release/0.2.0/README.md +42 -0
  160. package/release/0.2.0/reproducibility.json +73 -0
  161. package/release/0.2.1/KNOWN_LIMITATIONS.md +18 -0
  162. package/release/0.2.1/README.md +63 -0
  163. package/release/0.2.1/reproducibility.json +73 -0
  164. package/release/README.md +13 -0
  165. package/schemas/aggregation.v1.json +293 -0
  166. package/schemas/behavior-assertions.v1.json +220 -0
  167. package/schemas/deepseek-runtime-composition.v1.schema.json +122 -0
  168. package/schemas/experiment.v1.schema.json +188 -0
  169. package/schemas/human-calibration.v1.json +336 -0
  170. package/schemas/normalization-integrity.v1.json +302 -0
  171. package/schemas/run-bundles/v1.json +1040 -0
  172. package/schemas/run-queue.v1.schema.json +206 -0
  173. package/schemas/semantic-judge.v1.json +380 -0
  174. package/schemas/structural-observations.v1.json +131 -0
  175. package/schemas/task-packet-freeze.v1.schema.json +106 -0
  176. package/schemas/task-packet.v1.schema.json +234 -0
  177. package/schemas/uniform-events/v1.json +289 -0
  178. package/scripts/atlas-grafana.sh +18 -0
@@ -0,0 +1,819 @@
1
+ import { createHash, randomUUID } from "node:crypto";
2
+ import { spawn } from "node:child_process";
3
+ import { createReadStream, createWriteStream } from "node:fs";
4
+ import { chmod, lstat, mkdir, mkdtemp, open, readdir, readFile, realpath, rm, utimes } from "node:fs/promises";
5
+ import { basename, dirname, isAbsolute, join, posix, relative, resolve, sep } from "node:path";
6
+ import { tmpdir } from "node:os";
7
+ import { performance } from "node:perf_hooks";
8
+ import { pipeline } from "node:stream/promises";
9
+ import { execFile } from "node:child_process";
10
+ import { promisify } from "node:util";
11
+ import { assertNoDuplicateJsonKeys, canonicalizeMetadata, digestBytes, readVerifiedArtifact, validateArtifact, writeMetadataAtomically, } from "./artifacts.js";
12
+ import { isSafeArtifactRelativePath } from "./contracts.js";
13
+ const DEFAULT_TIMEOUT_MS = 60_000;
14
+ const MAX_TIMEOUT_MS = 2_147_483_647;
15
+ const DEFAULT_MAX_OUTPUT_BYTES = 64 * 1024;
16
+ const MAX_OUTPUT_BYTES = 64 * 1024 * 1024;
17
+ const RESERVED_MANIFEST_PATHS = new Set(["manifest.json"]);
18
+ const execFileAsync = promisify(execFile);
19
+ const COMPLETION_MESSAGE = "ebo-verifier-complete/v1\n";
20
+ const VERIFIER_COMPLETION_HOOK = `
21
+ const fs = require("node:fs");
22
+ const path = require("node:path");
23
+ const Module = require("node:module");
24
+ const stageRoot = __dirname;
25
+ const nodeModulePaths = Module._nodeModulePaths;
26
+ Module._nodeModulePaths = (from) => nodeModulePaths(from)
27
+ .filter((candidate) => candidate === stageRoot || candidate.startsWith(stageRoot + path.sep));
28
+ const resolveFilename = Module._resolveFilename;
29
+ Module._resolveFilename = function (request, parent, isMain, options) {
30
+ const resolved = resolveFilename.call(this, request, parent, isMain, options);
31
+ if (typeof resolved === "string"
32
+ && (resolved.startsWith("node:") || Module.builtinModules.includes(resolved)
33
+ || resolved === stageRoot || resolved.startsWith(stageRoot + path.sep))) return resolved;
34
+ throw new Error("Verifier dependency resolves outside its private staging root.");
35
+ };
36
+ let abnormal = false;
37
+ const report = (error) => {
38
+ abnormal = true;
39
+ console.error(error instanceof Error ? error.stack ?? error.message : String(error));
40
+ process.exitCode = 1;
41
+ };
42
+ process.on("uncaughtException", report);
43
+ process.on("unhandledRejection", report);
44
+ process.on("exit", () => {
45
+ if (!abnormal) {
46
+ try { fs.writeSync(3, ${JSON.stringify(COMPLETION_MESSAGE)}); } catch {}
47
+ }
48
+ });
49
+ `;
50
+ const VERIFIER_DEPENDENCY_LOADER = `
51
+ const stageRoot = new URL(".", import.meta.url).href;
52
+ export async function resolve(specifier, context, nextResolve) {
53
+ const result = await nextResolve(specifier, context);
54
+ if (result.url.startsWith("node:") || result.url.startsWith(stageRoot)) {
55
+ return result;
56
+ }
57
+ throw new Error("Verifier dependency resolves outside its private staging root.");
58
+ }
59
+ `;
60
+ export async function digestWorkspace(workspacePath, signal) {
61
+ return digestWorkspaceEntries(workspacePath, true, signal);
62
+ }
63
+ export async function digestWorkspaceTree(workspacePath, signal) {
64
+ return digestWorkspaceEntries(workspacePath, false, signal);
65
+ }
66
+ async function digestWorkspaceEntries(workspacePath, includeTimestamps, signal) {
67
+ assertNotAborted(signal);
68
+ const root = await realpath(workspacePath);
69
+ const hash = createHash("sha256");
70
+ hash.update(includeTimestamps ? "ebo.workspace/v1\0" : "ebo.workspace-tree/v1\0");
71
+ const metadata = await lstat(root, { bigint: true });
72
+ if (!metadata.isDirectory() || metadata.isSymbolicLink())
73
+ throw new Error("Workspace root is not a directory.");
74
+ hash.update(`root\0${metadata.mode & 4095n}\0${includeTimestamps ? `${workspaceTimestamp(metadata)}\0` : ""}`);
75
+ await hashWorkspaceDirectory(root, "", hash, includeTimestamps, signal);
76
+ return `sha256:${hash.digest("hex")}`;
77
+ }
78
+ async function hashWorkspaceDirectory(directory, relativeDirectory, hash, includeTimestamps, signal) {
79
+ assertNotAborted(signal);
80
+ const entries = await readdir(directory, { withFileTypes: true });
81
+ entries.sort((left, right) => left.name < right.name ? -1 : left.name > right.name ? 1 : 0);
82
+ for (const entry of entries) {
83
+ const path = join(directory, entry.name);
84
+ const relativePath = posix.join(relativeDirectory, entry.name);
85
+ const metadata = await lstat(path, { bigint: true });
86
+ if (metadata.isSymbolicLink())
87
+ throw new Error(`Workspace contains a symbolic link at "${relativePath}".`);
88
+ if (metadata.isDirectory()) {
89
+ hash.update(`directory\0${relativePath}\0${metadata.mode & 4095n}\0${includeTimestamps ? `${workspaceTimestamp(metadata)}\0` : ""}`);
90
+ await hashWorkspaceDirectory(path, relativePath, hash, includeTimestamps, signal);
91
+ }
92
+ else if (metadata.isFile()) {
93
+ if (metadata.nlink > 1n)
94
+ throw new Error(`Workspace contains a hard-linked file at "${relativePath}".`);
95
+ const bytes = await readFile(path, { signal });
96
+ hash.update(`file\0${relativePath}\0${metadata.mode & 4095n}\0${includeTimestamps ? `${workspaceTimestamp(metadata)}\0` : ""}${bytes.length}\0`);
97
+ hash.update(bytes);
98
+ }
99
+ else {
100
+ throw new Error(`Workspace contains an unsupported entry at "${relativePath}".`);
101
+ }
102
+ }
103
+ }
104
+ async function captureWorkspaceContent(workspacePath, signal) {
105
+ assertNotAborted(signal);
106
+ const root = await realpath(workspacePath);
107
+ const metadata = await lstat(root, { bigint: true });
108
+ if (!metadata.isDirectory() || metadata.isSymbolicLink())
109
+ throw new Error("Workspace root is not a directory.");
110
+ const entries = new Map([["", { kind: "directory", mode: metadata.mode & 4095n }]]);
111
+ await captureWorkspaceDirectory(root, "", entries, signal);
112
+ return entries;
113
+ }
114
+ async function captureWorkspaceDirectory(directory, relativeDirectory, entries, signal) {
115
+ assertNotAborted(signal);
116
+ const children = await readdir(directory, { withFileTypes: true });
117
+ children.sort((left, right) => left.name < right.name ? -1 : left.name > right.name ? 1 : 0);
118
+ for (const child of children) {
119
+ const path = join(directory, child.name);
120
+ const relativePath = posix.join(relativeDirectory, child.name);
121
+ const metadata = await lstat(path, { bigint: true });
122
+ if (metadata.isSymbolicLink())
123
+ throw new Error(`Workspace contains a symbolic link at "${relativePath}".`);
124
+ if (metadata.isDirectory()) {
125
+ entries.set(relativePath, { kind: "directory", mode: metadata.mode & 4095n });
126
+ await captureWorkspaceDirectory(path, relativePath, entries, signal);
127
+ }
128
+ else if (metadata.isFile()) {
129
+ if (metadata.nlink > 1n)
130
+ throw new Error(`Workspace contains a hard-linked file at "${relativePath}".`);
131
+ entries.set(relativePath, { kind: "file", mode: metadata.mode & 4095n, bytes: await readFile(path, { signal }) });
132
+ }
133
+ else {
134
+ throw new Error(`Workspace contains an unsupported entry at "${relativePath}".`);
135
+ }
136
+ }
137
+ }
138
+ async function assertWorkspaceSnapshotContent(snapshotPath, expected, signal) {
139
+ const actual = await captureWorkspaceContent(snapshotPath, signal);
140
+ if (actual.size !== expected.size)
141
+ throw new Error("Workspace changed while its private evaluation snapshot was being created.");
142
+ for (const [relativePath, expectedEntry] of expected) {
143
+ const actualEntry = actual.get(relativePath);
144
+ if (actualEntry === undefined || actualEntry.kind !== expectedEntry.kind || actualEntry.mode !== expectedEntry.mode
145
+ || (expectedEntry.kind === "file" && !actualEntry.bytes.equals(expectedEntry.bytes))) {
146
+ throw new Error("Workspace changed while its private evaluation snapshot was being created.");
147
+ }
148
+ }
149
+ }
150
+ async function copyWorkspaceSnapshot(source, destination, signal) {
151
+ assertNotAborted(signal);
152
+ if (process.platform !== "win32") {
153
+ await execFileAsync("/bin/cp", ["-a", source, destination], { signal });
154
+ return;
155
+ }
156
+ await copyWorkspaceSnapshotOnWindows(source, destination, signal);
157
+ await restoreWorkspaceTimestamps(source, destination, signal);
158
+ }
159
+ async function copyWorkspaceSnapshotOnWindows(source, destination, signal) {
160
+ assertNotAborted(signal);
161
+ const sourceMetadata = await lstat(source, { bigint: true });
162
+ await mkdir(destination);
163
+ await chmod(destination, Number(sourceMetadata.mode & 4095n));
164
+ for (const entry of await readdir(source, { withFileTypes: true })) {
165
+ assertNotAborted(signal);
166
+ const sourcePath = join(source, entry.name);
167
+ const destinationPath = join(destination, entry.name);
168
+ const metadata = await lstat(sourcePath, { bigint: true });
169
+ if (metadata.isSymbolicLink())
170
+ throw new Error(`Workspace contains a symbolic link at "${entry.name}".`);
171
+ if (metadata.isDirectory()) {
172
+ await copyWorkspaceSnapshotOnWindows(sourcePath, destinationPath, signal);
173
+ }
174
+ else if (metadata.isFile()) {
175
+ await pipeline(createReadStream(sourcePath), createWriteStream(destinationPath, { flags: "wx", mode: Number(metadata.mode & 4095n) }), ...(signal === undefined ? [] : [{ signal }]));
176
+ await chmod(destinationPath, Number(metadata.mode & 4095n));
177
+ }
178
+ else {
179
+ throw new Error(`Workspace contains an unsupported entry at "${entry.name}".`);
180
+ }
181
+ }
182
+ }
183
+ async function restoreWorkspaceTimestamps(source, destination, signal) {
184
+ assertNotAborted(signal);
185
+ const entries = await readdir(source, { withFileTypes: true });
186
+ for (const entry of entries) {
187
+ const sourcePath = join(source, entry.name);
188
+ const destinationPath = join(destination, entry.name);
189
+ const metadata = await lstat(sourcePath, { bigint: true });
190
+ if (metadata.isSymbolicLink())
191
+ throw new Error(`Workspace contains a symbolic link at "${entry.name}".`);
192
+ if (metadata.isDirectory()) {
193
+ await restoreWorkspaceTimestamps(sourcePath, destinationPath, signal);
194
+ }
195
+ else if (!metadata.isFile()) {
196
+ throw new Error(`Workspace contains an unsupported entry at "${entry.name}".`);
197
+ }
198
+ await utimes(destinationPath, timestampSeconds(metadata.atimeMs), timestampSeconds(metadata.mtimeMs));
199
+ }
200
+ const metadata = await lstat(source, { bigint: true });
201
+ await utimes(destination, timestampSeconds(metadata.atimeMs), timestampSeconds(metadata.mtimeMs));
202
+ }
203
+ function timestampSeconds(milliseconds) {
204
+ return Number(milliseconds) / 1_000 + 0.000001;
205
+ }
206
+ function assertNotAborted(signal) {
207
+ if (signal?.aborted)
208
+ throw new Error("Verifier execution was aborted.");
209
+ }
210
+ function workspaceTimestamp(metadata) {
211
+ return process.platform === "win32" ? metadata.mtimeMs : metadata.mtimeNs;
212
+ }
213
+ /**
214
+ * Execute a digest-pinned verifier in a private staging directory.
215
+ *
216
+ * The verifier receives the staged implementation path followed by the
217
+ * evaluated workspace path. Its stdout is the assertion JSON protocol; stderr
218
+ * is retained as bounded diagnostic evidence.
219
+ */
220
+ export async function executeVerifier(options) {
221
+ const startedAt = performance.now();
222
+ const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
223
+ const maxOutputBytes = options.maxOutputBytes ?? DEFAULT_MAX_OUTPUT_BYTES;
224
+ validateOptions(options, timeoutMs, maxOutputBytes);
225
+ const workspacePath = await realpath(options.workspacePath);
226
+ const verifierRoot = await realpath(options.verifierRoot);
227
+ const artifactCandidate = await resolveRootPath(options.artifactRoot);
228
+ assertDisjointRoots(workspacePath, verifierRoot, "Verifier and workspace roots");
229
+ assertDisjointRoots(workspacePath, artifactCandidate, "Artifact and workspace roots");
230
+ assertDisjointRoots(verifierRoot, artifactCandidate, "Verifier and artifact roots");
231
+ if (options.workspaceFingerprint !== await digestWorkspace(workspacePath, options.signal)) {
232
+ throw new Error("Workspace fingerprint does not match the evaluated workspace.");
233
+ }
234
+ const artifactRoot = await prepareRoot(artifactCandidate);
235
+ const diagnosticDirectory = options.diagnosticDirectory ?? "diagnostics";
236
+ if (!isSafeArtifactRelativePath(diagnosticDirectory)) {
237
+ throw new Error(`Diagnostic directory "${diagnosticDirectory}" is unsafe.`);
238
+ }
239
+ if (hasPortablePathCollision(diagnosticDirectory.toLowerCase(), RESERVED_MANIFEST_PATHS)) {
240
+ throw new Error("Diagnostic directory collides with the reserved manifest path.");
241
+ }
242
+ const executionDiagnosticDirectory = posix.join(diagnosticDirectory, randomUUID());
243
+ const diagnosticSetup = await openDiagnosticFiles(artifactRoot, executionDiagnosticDirectory);
244
+ const diagnosticFiles = diagnosticSetup.files;
245
+ try {
246
+ let stdout = emptyOutput();
247
+ let stderr = emptyOutput();
248
+ let exitCode;
249
+ let status = "error";
250
+ let assertions = [];
251
+ let internalError = diagnosticSetup.error;
252
+ let stagingRoot;
253
+ let evaluatedWorkspaceFingerprint;
254
+ const verifierFormat = options.moduleFormat ?? inferredModuleFormat(options.verifier.locator);
255
+ let spawnAttempted = false;
256
+ try {
257
+ if (internalError === undefined) {
258
+ const verifierBytes = await readVerifiedArtifact(verifierRoot, options.verifier.locator, options.verifier.digest);
259
+ const workspaceContent = await captureWorkspaceContent(workspacePath, options.signal);
260
+ stagingRoot = await createStagingRoot(workspacePath);
261
+ const evaluatedWorkspacePath = join(stagingRoot, "workspace");
262
+ await copyWorkspaceSnapshot(workspacePath, evaluatedWorkspacePath, options.signal);
263
+ await assertWorkspaceSnapshotContent(evaluatedWorkspacePath, workspaceContent, options.signal);
264
+ if (await digestWorkspace(workspacePath, options.signal) !== options.workspaceFingerprint) {
265
+ throw new Error("Workspace changed while its private evaluation snapshot was being created.");
266
+ }
267
+ evaluatedWorkspaceFingerprint = await digestWorkspace(evaluatedWorkspacePath, options.signal);
268
+ if (evaluatedWorkspaceFingerprint !== options.workspaceFingerprint) {
269
+ throw new Error("Workspace snapshot metadata does not match the evaluated workspace.");
270
+ }
271
+ const trustedRoot = join(stagingRoot, "trusted");
272
+ await mkdir(trustedRoot, { recursive: true });
273
+ await mkdir(join(trustedRoot, "bin"));
274
+ const stagedVerifier = join(trustedRoot, stagedVerifierName(verifierFormat));
275
+ await writePrivateFile(stagedVerifier, verifierBytes);
276
+ const completionHookPath = join(trustedRoot, "completion-hook.cjs");
277
+ await writePrivateFile(completionHookPath, Buffer.from(VERIFIER_COMPLETION_HOOK));
278
+ const dependencyLoaderPath = join(trustedRoot, "dependency-loader.mjs");
279
+ await writePrivateFile(dependencyLoaderPath, Buffer.from(VERIFIER_DEPENDENCY_LOADER));
280
+ const processResult = await runProcess(options.command ?? process.execPath, ["--no-warnings", "--require", completionHookPath, "--loader", dependencyLoaderPath, stagedVerifier, evaluatedWorkspacePath], evaluatedWorkspacePath, options.env, timeoutMs, maxOutputBytes, diagnosticFiles, trustedRoot, options.signal);
281
+ stdout = processResult.stdout;
282
+ stderr = processResult.stderr;
283
+ exitCode = processResult.exitCode;
284
+ spawnAttempted = processResult.started;
285
+ if (processResult.error !== undefined)
286
+ internalError = processResult.error;
287
+ if (processResult.started && !processResult.completed) {
288
+ internalError = combineErrors(internalError, "Verifier did not complete normally.");
289
+ }
290
+ if (stdout.error !== undefined)
291
+ internalError = combineErrors(internalError, `stdout capture failed: ${stdout.error}`);
292
+ if (stderr.error !== undefined)
293
+ internalError = combineErrors(internalError, `stderr capture failed: ${stderr.error}`);
294
+ if (processResult.started && stdout.error === undefined) {
295
+ if (stdout.truncated) {
296
+ internalError = combineErrors(internalError, `Verifier stdout exceeded ${maxOutputBytes} bytes.`);
297
+ }
298
+ else {
299
+ try {
300
+ assertions = parseAssertions(stdout.bytes);
301
+ }
302
+ catch (error) {
303
+ internalError = combineErrors(internalError, error instanceof Error ? error.message : "Verifier output is invalid.");
304
+ }
305
+ }
306
+ }
307
+ if (internalError === undefined && processResult.signal === undefined && exitCode !== undefined) {
308
+ if (assertions.some((assertion) => assertion.status === "failed")) {
309
+ if (exitCode === 0) {
310
+ internalError = "Verifier exit status contradicts its failed assertions.";
311
+ }
312
+ else {
313
+ status = "failed";
314
+ }
315
+ }
316
+ else if (exitCode === 0 && assertions.every((assertion) => assertion.status === "passed")) {
317
+ status = "passed";
318
+ }
319
+ else {
320
+ internalError = "Verifier exit status contradicts its assertions.";
321
+ }
322
+ }
323
+ if (processResult.timedOut)
324
+ internalError = combineErrors(internalError, `Verifier timed out after ${timeoutMs} ms.`);
325
+ else if (processResult.signal !== undefined)
326
+ internalError = combineErrors(internalError, `Verifier terminated by ${processResult.signal}.`);
327
+ }
328
+ }
329
+ catch (error) {
330
+ internalError = error instanceof Error ? error.message : "Verifier could not be executed.";
331
+ }
332
+ finally {
333
+ if (stagingRoot !== undefined) {
334
+ await rm(stagingRoot, { force: true, recursive: true }).catch((error) => {
335
+ internalError = combineErrors(internalError, error instanceof Error ? error.message : "Verifier staging cleanup failed.");
336
+ });
337
+ }
338
+ }
339
+ if (internalError !== undefined)
340
+ status = "error";
341
+ await finalizeDiagnosticFiles(diagnosticFiles);
342
+ if (diagnosticFiles.some((file) => file.error !== undefined)) {
343
+ status = "error";
344
+ internalError = combineErrors(internalError, "Verifier diagnostics could not be persisted.");
345
+ }
346
+ let diagnostics = [];
347
+ if (!spawnAttempted) {
348
+ const cleanupError = await removeDiagnosticFiles(artifactRoot, diagnosticFiles);
349
+ if (cleanupError !== undefined) {
350
+ status = "error";
351
+ internalError = combineErrors(internalError, cleanupError);
352
+ }
353
+ }
354
+ else {
355
+ const references = await Promise.all(diagnosticFiles.map((file, index) => diagnosticReference(artifactRoot, file, index === 0 ? "stdout" : "stderr", index === 0 ? stdout : stderr)));
356
+ if (references.some((reference) => reference === undefined)) {
357
+ status = "error";
358
+ internalError = combineErrors(internalError, "Verifier diagnostics could not be verified.");
359
+ }
360
+ diagnostics = references.flatMap((reference) => reference === undefined ? [] : [reference]);
361
+ }
362
+ const result = {
363
+ schemaVersion: "verifier-result/v1",
364
+ bundleId: options.bundleId,
365
+ status,
366
+ verifier: {
367
+ locator: options.verifier.locator,
368
+ digest: `sha256:${options.verifier.digest.value}`,
369
+ format: verifierFormat,
370
+ },
371
+ durationMs: Math.max(0, Math.round(performance.now() - startedAt)),
372
+ ...(internalError === undefined ? {} : { error: internalError }),
373
+ ...(exitCode === undefined ? {} : { exitCode }),
374
+ workspace: {
375
+ ...options.workspace,
376
+ ...(evaluatedWorkspaceFingerprint === undefined ? {} : { fingerprint: evaluatedWorkspaceFingerprint }),
377
+ },
378
+ assertions,
379
+ diagnostics,
380
+ };
381
+ assertVerifierResult(result, "verifier-result");
382
+ return result;
383
+ }
384
+ finally {
385
+ await finalizeDiagnosticFiles(diagnosticFiles);
386
+ }
387
+ }
388
+ function combineErrors(current, next) {
389
+ return current === undefined ? next : `${current}; ${next}`;
390
+ }
391
+ export function serializeVerifierResult(result) {
392
+ assertVerifierResult(result, "verifier-result");
393
+ return canonicalizeMetadata(result);
394
+ }
395
+ export async function writeVerifierResult(artifactRoot, relativePath, result) {
396
+ assertVerifierResult(result, relativePath);
397
+ const resultLocator = relativePath.toLowerCase();
398
+ if (hasPortablePathCollision(resultLocator, RESERVED_MANIFEST_PATHS)) {
399
+ throw new Error("Verifier result path collides with the reserved manifest path.");
400
+ }
401
+ const diagnosticLocators = new Set((result.diagnostics ?? []).map((diagnostic) => diagnostic.locator.toLowerCase()));
402
+ if ([...diagnosticLocators].some((locator) => hasPortablePathCollision(locator, RESERVED_MANIFEST_PATHS))) {
403
+ throw new Error("Verifier diagnostic locator collides with the reserved manifest path.");
404
+ }
405
+ if (hasPortablePathCollision(resultLocator, diagnosticLocators)) {
406
+ throw new Error("Verifier result path collides with a diagnostic locator.");
407
+ }
408
+ for (const diagnostic of result.diagnostics ?? []) {
409
+ const bytes = await readVerifiedArtifact(artifactRoot, diagnostic.locator, {
410
+ algorithm: "sha256",
411
+ value: diagnostic.digest.slice("sha256:".length),
412
+ });
413
+ if (bytes.length !== diagnostic.sizeBytes) {
414
+ throw new Error(`Diagnostic "${diagnostic.locator}" size does not match its result reference.`);
415
+ }
416
+ }
417
+ return writeMetadataAtomically(artifactRoot, relativePath, result, undefined, { overwrite: false });
418
+ }
419
+ function validateOptions(options, timeoutMs, maxOutputBytes) {
420
+ if (!isValidIdentifier(options.bundleId))
421
+ throw new Error("Bundle ID is invalid.");
422
+ if (options.command !== undefined && options.command !== process.execPath) {
423
+ throw new Error("Verifier command must be the pinned Node runtime.");
424
+ }
425
+ if (options.moduleFormat !== undefined && !["commonjs", "module"].includes(options.moduleFormat)) {
426
+ throw new Error("Verifier module format is invalid.");
427
+ }
428
+ const locatorFormat = explicitModuleFormat(options.verifier.locator);
429
+ if (options.moduleFormat !== undefined && locatorFormat !== undefined && options.moduleFormat !== locatorFormat) {
430
+ throw new Error("Verifier module format conflicts with its locator.");
431
+ }
432
+ if (options.args !== undefined && options.args.length > 0) {
433
+ throw new Error("Verifier launcher arguments cannot replace the staged verifier.");
434
+ }
435
+ if (!isValidIdentifier(options.workspace.artifactId) || !/^sha256:[a-f0-9]{64}$/.test(options.workspace.digest)) {
436
+ throw new Error("Workspace reference is invalid.");
437
+ }
438
+ if (!/^sha256:[a-f0-9]{64}$/.test(options.workspaceFingerprint)
439
+ || !/^sha256:[a-f0-9]{64}$/.test(options.workspace.fingerprint)
440
+ || options.workspaceFingerprint !== options.workspace.fingerprint) {
441
+ throw new Error("Workspace fingerprint is invalid.");
442
+ }
443
+ if (!Number.isSafeInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > MAX_TIMEOUT_MS) {
444
+ throw new Error(`Verifier timeout must be between 1 and ${MAX_TIMEOUT_MS} ms.`);
445
+ }
446
+ if (!Number.isSafeInteger(maxOutputBytes) || maxOutputBytes < 0 || maxOutputBytes > MAX_OUTPUT_BYTES) {
447
+ throw new Error(`Verifier output limit must be between 0 and ${MAX_OUTPUT_BYTES} bytes.`);
448
+ }
449
+ }
450
+ function isValidIdentifier(value) {
451
+ return typeof value === "string" && value.trim() !== "" && [...value].length <= 256;
452
+ }
453
+ function inferredModuleFormat(locator) {
454
+ const lowerLocator = locator.toLowerCase();
455
+ return lowerLocator.endsWith(".mjs") ? "module" : "commonjs";
456
+ }
457
+ function explicitModuleFormat(locator) {
458
+ const lowerLocator = locator.toLowerCase();
459
+ if (lowerLocator.endsWith(".mjs"))
460
+ return "module";
461
+ if (lowerLocator.endsWith(".cjs"))
462
+ return "commonjs";
463
+ return undefined;
464
+ }
465
+ function stagedVerifierName(format) {
466
+ return format === "module" ? "verifier.mjs" : "verifier.cjs";
467
+ }
468
+ async function prepareRoot(root) {
469
+ await mkdir(root, { recursive: true });
470
+ return realpath(root);
471
+ }
472
+ async function resolveRootPath(root) {
473
+ const absoluteRoot = resolve(root);
474
+ try {
475
+ return await realpath(absoluteRoot);
476
+ }
477
+ catch (error) {
478
+ if (error.code !== "ENOENT")
479
+ throw error;
480
+ const parent = dirname(absoluteRoot);
481
+ if (parent === absoluteRoot)
482
+ return absoluteRoot;
483
+ return resolve(await resolveRootPath(parent), basename(absoluteRoot));
484
+ }
485
+ }
486
+ async function createStagingRoot(workspacePath) {
487
+ const candidates = [
488
+ tmpdir(),
489
+ ...(process.platform === "win32"
490
+ ? (process.env.SystemRoot === undefined ? [] : [join(process.env.SystemRoot, "Temp")])
491
+ : ["/tmp", "/var/tmp"]),
492
+ ];
493
+ let lastError;
494
+ for (const candidate of new Set(candidates)) {
495
+ let resolvedStagingRoot;
496
+ try {
497
+ const candidateRoot = await realpath(candidate);
498
+ if (isContained(workspacePath, candidateRoot)) {
499
+ lastError = new Error("temporary directory candidate is inside the evaluated workspace");
500
+ continue;
501
+ }
502
+ resolvedStagingRoot = await realpath(await mkdtemp(join(candidateRoot, "ebo-verifier-")));
503
+ }
504
+ catch (error) {
505
+ lastError = error;
506
+ continue;
507
+ }
508
+ if (isContained(workspacePath, resolvedStagingRoot) || isContained(resolvedStagingRoot, workspacePath)) {
509
+ await rm(resolvedStagingRoot, { force: true, recursive: true });
510
+ lastError = new Error("temporary directory is inside the evaluated workspace");
511
+ continue;
512
+ }
513
+ return resolvedStagingRoot;
514
+ }
515
+ throw new Error(`Could not create verifier staging outside the evaluated workspace: ${lastError instanceof Error ? lastError.message : "no separate temporary directory is available."}`);
516
+ }
517
+ function assertDisjointRoots(left, right, label) {
518
+ if (isContained(left, right) || isContained(right, left)) {
519
+ throw new Error(`${label} must be separate.`);
520
+ }
521
+ }
522
+ function isContained(root, path) {
523
+ const fromRoot = relative(root, path);
524
+ return fromRoot === "" || (!isAbsolute(fromRoot) && fromRoot !== ".." && !fromRoot.startsWith(`..${sep}`));
525
+ }
526
+ async function writePrivateFile(path, bytes) {
527
+ const handle = await open(path, "wx", 0o700);
528
+ try {
529
+ await handle.writeFile(bytes);
530
+ await handle.sync();
531
+ }
532
+ finally {
533
+ await handle.close();
534
+ }
535
+ }
536
+ async function runProcess(command, args, cwd, overrides, timeoutMs, maxOutputBytes, diagnosticFiles, verifierRoot, signal) {
537
+ const environment = overrides === undefined ? { PATH: "" } : cleanEnvironment(overrides);
538
+ environment.PATH = join(verifierRoot, "bin");
539
+ const child = spawn(command, args, {
540
+ cwd,
541
+ env: environment,
542
+ detached: process.platform !== "win32",
543
+ stdio: ["ignore", "pipe", "pipe", "pipe"],
544
+ windowsHide: true,
545
+ });
546
+ const stdout = capture(child.stdout, maxOutputBytes, diagnosticFiles[0]);
547
+ const stderr = capture(child.stderr, maxOutputBytes, diagnosticFiles[1]);
548
+ const completionStream = child.stdio[3];
549
+ const completion = capture(completionStream, COMPLETION_MESSAGE.length);
550
+ let started = false;
551
+ let timedOut = false;
552
+ let termination;
553
+ const terminate = () => termination ??= killProcessTree(child);
554
+ return new Promise((resolveProcess) => {
555
+ let error;
556
+ let settled = false;
557
+ let timer;
558
+ const finish = async (code, signal) => {
559
+ if (settled)
560
+ return;
561
+ settled = true;
562
+ clearTimeout(timer);
563
+ abortSignal?.removeEventListener("abort", abort);
564
+ await terminate();
565
+ resolveProcess({
566
+ started,
567
+ completed: (await completion).bytes.equals(Buffer.from(COMPLETION_MESSAGE)),
568
+ stdout: await stdout,
569
+ stderr: await stderr,
570
+ ...(code === null ? {} : { exitCode: code }),
571
+ ...(signal === null ? {} : { signal }),
572
+ timedOut,
573
+ ...(error === undefined ? {} : { error }),
574
+ });
575
+ };
576
+ child.once("spawn", () => {
577
+ started = true;
578
+ });
579
+ child.once("error", (cause) => {
580
+ error = cause.message;
581
+ });
582
+ child.once("exit", () => {
583
+ void terminate().catch(() => undefined);
584
+ });
585
+ child.once("close", (code, signal) => {
586
+ void finish(code, signal);
587
+ });
588
+ timer = setTimeout(() => {
589
+ timedOut = true;
590
+ void terminate().catch(() => undefined);
591
+ child.stdout?.destroy();
592
+ child.stderr?.destroy();
593
+ completionStream?.destroy?.();
594
+ void finish(null, null);
595
+ }, timeoutMs);
596
+ timer.unref();
597
+ const abortSignal = signal;
598
+ const abort = () => {
599
+ error ??= "Verifier execution was aborted.";
600
+ void terminate().catch(() => undefined);
601
+ child.stdout?.destroy();
602
+ child.stderr?.destroy();
603
+ completionStream?.destroy?.();
604
+ void finish(null, null);
605
+ };
606
+ if (abortSignal?.aborted)
607
+ abort();
608
+ else
609
+ abortSignal?.addEventListener("abort", abort, { once: true });
610
+ });
611
+ }
612
+ async function killProcessTree(child) {
613
+ if (child.pid === undefined)
614
+ return;
615
+ if (process.platform === "win32") {
616
+ const killer = spawn("taskkill", ["/pid", String(child.pid), "/t", "/f"], {
617
+ stdio: "ignore",
618
+ windowsHide: true,
619
+ });
620
+ await new Promise((resolveKiller) => killer.once("close", () => resolveKiller()));
621
+ return;
622
+ }
623
+ try {
624
+ process.kill(-child.pid, "SIGKILL");
625
+ }
626
+ catch {
627
+ child.kill("SIGKILL");
628
+ }
629
+ }
630
+ function capture(stream, limit, diagnosticFile) {
631
+ if (stream === null)
632
+ return Promise.resolve(emptyOutput());
633
+ return new Promise((resolveCapture) => {
634
+ const chunks = [];
635
+ let size = 0;
636
+ let truncated = false;
637
+ let settled = false;
638
+ stream.on("data", (chunk) => {
639
+ const bytes = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
640
+ const retained = bytes.subarray(0, Math.max(0, limit - size));
641
+ if (retained.length > 0) {
642
+ chunks.push(retained);
643
+ size += retained.length;
644
+ if (diagnosticFile !== undefined)
645
+ queueDiagnosticWrite(diagnosticFile, retained);
646
+ }
647
+ truncated ||= retained.length < bytes.length;
648
+ });
649
+ stream.once("error", (error) => {
650
+ if (!settled) {
651
+ settled = true;
652
+ resolveCapture({
653
+ bytes: Buffer.concat(chunks),
654
+ truncated,
655
+ error: error instanceof Error ? error.message : "unknown stream error",
656
+ });
657
+ }
658
+ });
659
+ stream.once("end", () => {
660
+ if (!settled) {
661
+ settled = true;
662
+ resolveCapture({ bytes: Buffer.concat(chunks), truncated });
663
+ }
664
+ });
665
+ stream.once("close", () => {
666
+ if (!settled) {
667
+ settled = true;
668
+ resolveCapture({ bytes: Buffer.concat(chunks), truncated });
669
+ }
670
+ });
671
+ });
672
+ }
673
+ function emptyOutput() {
674
+ return { bytes: Buffer.alloc(0), truncated: false };
675
+ }
676
+ function parseAssertions(bytes) {
677
+ const text = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
678
+ const parsed = JSON.parse(text);
679
+ assertNoDuplicateJsonKeys(text);
680
+ if (!isRecord(parsed) || !hasExactKeys(parsed, ["assertions"]) || !Array.isArray(parsed.assertions)
681
+ || parsed.assertions.length === 0) {
682
+ throw new Error("Verifier output must contain a non-empty assertions array.");
683
+ }
684
+ const identifiers = new Set();
685
+ return parsed.assertions.map((value) => {
686
+ if (!isRecord(value) || !hasExactKeys(value, ["id", "status"]) || typeof value.id !== "string"
687
+ || value.id.trim() === "" || [...value.id].length > 256
688
+ || typeof value.status !== "string" || !["passed", "failed", "not-run"].includes(value.status)
689
+ || identifiers.has(value.id)) {
690
+ throw new Error("Verifier output contains an invalid or duplicate assertion.");
691
+ }
692
+ identifiers.add(value.id);
693
+ return { id: value.id, status: value.status };
694
+ });
695
+ }
696
+ function hasExactKeys(value, expected) {
697
+ const keys = Object.keys(value);
698
+ return keys.length === expected.length && expected.every((key) => Object.hasOwn(value, key));
699
+ }
700
+ async function openDiagnosticFiles(root, directory) {
701
+ const files = [];
702
+ try {
703
+ for (const stream of ["stdout", "stderr"]) {
704
+ const locator = posix.join(directory, `${stream}.log`);
705
+ if (!isSafeArtifactRelativePath(locator))
706
+ throw new Error(`Diagnostic locator "${locator}" is unsafe.`);
707
+ const rootPath = await realpath(root);
708
+ const { path } = await prepareDiagnosticPath(rootPath, locator);
709
+ files.push({ locator, handle: await open(path, "wx", 0o600), writes: Promise.resolve(), closed: false });
710
+ }
711
+ return { files };
712
+ }
713
+ catch (error) {
714
+ await finalizeDiagnosticFiles(files);
715
+ let setupError = error instanceof Error ? error.message : "Verifier diagnostic setup failed.";
716
+ const cleanupError = await removeDiagnosticFiles(root, files);
717
+ if (cleanupError !== undefined)
718
+ setupError = combineErrors(setupError, cleanupError);
719
+ return {
720
+ files: [],
721
+ error: setupError,
722
+ };
723
+ }
724
+ }
725
+ async function removeDiagnosticFiles(root, files) {
726
+ let error;
727
+ for (const file of files) {
728
+ await rm(resolve(root, file.locator), { force: true }).catch((cleanupError) => {
729
+ error = combineErrors(error, cleanupError instanceof Error ? cleanupError.message : "Verifier diagnostic cleanup failed.");
730
+ });
731
+ }
732
+ return error;
733
+ }
734
+ function queueDiagnosticWrite(file, bytes) {
735
+ if (bytes.length === 0 || file.closed)
736
+ return;
737
+ file.writes = file.writes.then(async () => {
738
+ await file.handle.write(bytes);
739
+ }).catch((error) => {
740
+ file.error = error instanceof Error ? error.message : "Diagnostic write failed.";
741
+ });
742
+ }
743
+ async function finalizeDiagnosticFiles(files) {
744
+ await Promise.all(files.map(async (file) => {
745
+ if (file.closed)
746
+ return;
747
+ await file.writes;
748
+ await file.handle.sync().catch((error) => {
749
+ file.error ??= error instanceof Error ? error.message : "Diagnostic flush failed.";
750
+ });
751
+ await file.handle.close().catch((error) => {
752
+ file.error ??= error instanceof Error ? error.message : "Diagnostic close failed.";
753
+ });
754
+ file.closed = true;
755
+ }));
756
+ }
757
+ async function diagnosticReference(root, file, stream, output) {
758
+ const digest = digestBytes(output.bytes);
759
+ try {
760
+ const bytes = await readVerifiedArtifact(root, file.locator, digest);
761
+ return {
762
+ stream,
763
+ locator: file.locator,
764
+ digest: `sha256:${digest.value}`,
765
+ sizeBytes: bytes.length,
766
+ truncated: output.truncated,
767
+ };
768
+ }
769
+ catch {
770
+ return undefined;
771
+ }
772
+ }
773
+ function hasPortablePathCollision(path, existingPaths) {
774
+ return [...existingPaths].some((existing) => existing === path || existing.startsWith(`${path}/`) || path.startsWith(`${existing}/`));
775
+ }
776
+ async function prepareDiagnosticPath(root, locator) {
777
+ const segments = locator.split("/");
778
+ let parent = root;
779
+ for (const segment of segments.slice(0, -1)) {
780
+ parent = resolve(parent, segment);
781
+ await mkdir(parent, { recursive: true });
782
+ const entry = await lstat(parent);
783
+ if (!entry.isDirectory() || entry.isSymbolicLink()) {
784
+ throw new Error(`Diagnostic locator "${locator}" crosses a symbolic link.`);
785
+ }
786
+ }
787
+ const path = resolve(parent, segments.at(-1));
788
+ try {
789
+ const entry = await lstat(path);
790
+ if (!entry.isFile() || entry.isSymbolicLink()) {
791
+ throw new Error(`Diagnostic locator "${locator}" is not an isolated regular file.`);
792
+ }
793
+ }
794
+ catch (error) {
795
+ if (error.code !== "ENOENT")
796
+ throw error;
797
+ }
798
+ return { parent, path };
799
+ }
800
+ function assertVerifierResult(result, artifact) {
801
+ if (new Set(result.assertions.map((assertion) => assertion.id)).size !== result.assertions.length) {
802
+ throw new Error("Verifier assertion IDs must be unique.");
803
+ }
804
+ if (result.status === "failed" && (result.exitCode === undefined || result.exitCode === 0)
805
+ && result.assertions.some((assertion) => assertion.status === "failed")) {
806
+ throw new Error("Verifier exit status contradicts its failed assertions.");
807
+ }
808
+ const errors = validateArtifact(artifact, result);
809
+ if (errors.length > 0) {
810
+ throw new Error(errors.map((error) => `${error.field}: ${error.message}`).join("; "));
811
+ }
812
+ }
813
+ function cleanEnvironment(environment) {
814
+ void environment;
815
+ return {};
816
+ }
817
+ function isRecord(value) {
818
+ return typeof value === "object" && value !== null && !Array.isArray(value);
819
+ }