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,1372 @@
1
+ import { createHash, randomUUID } from "node:crypto";
2
+ import { closeSync, constants, fstatSync, fsyncSync, linkSync, lstatSync, mkdirSync, openSync, readFileSync, readSync, realpathSync, unlinkSync, writeFileSync, } from "node:fs";
3
+ import { link, lstat, mkdir, open, realpath, rename, rm } from "node:fs/promises";
4
+ import { dirname, isAbsolute, relative, resolve, sep } from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+ import { Ajv2020 } from "ajv/dist/2020.js";
7
+ import * as formats from "ajv-formats";
8
+ import { closeBundleRoot, isSafeArtifactRelativePath, openBundleRoot, removeInterruptedPublicationLink, resolveBundleArtifact, } from "./contracts.js";
9
+ const addFormats = formats.default;
10
+ const schemaDirectory = fileURLToPath(new URL("../../schemas/", import.meta.url));
11
+ const runBundleSchemaId = "urn:ebo:schema:run-bundle:v1";
12
+ const validators = loadValidators();
13
+ const RESERVED_MANIFEST_PATHS = new Set(["manifest.json"]);
14
+ export const SUPPORTED_ARTIFACT_SCHEMA_VERSIONS = [...validators.keys()];
15
+ export function canonicalizeMetadata(value) {
16
+ return canonicalJson(value, new Set());
17
+ }
18
+ export function digestBytes(bytes) {
19
+ return { algorithm: "sha256", value: createHash("sha256").update(bytes).digest("hex") };
20
+ }
21
+ export function digestMetadata(metadata) {
22
+ return digestBytes(Buffer.from(canonicalizeMetadata(metadata)));
23
+ }
24
+ export function verifyDigest(bytes, expected) {
25
+ const actual = digestBytes(bytes);
26
+ return expected.algorithm === actual.algorithm && expected.value === actual.value;
27
+ }
28
+ export function assertNoDuplicateJsonKeys(text) {
29
+ let index = 0;
30
+ const skipWhitespace = () => {
31
+ while (/\s/.test(text[index] ?? ""))
32
+ index += 1;
33
+ };
34
+ const readString = () => {
35
+ const start = index;
36
+ index += 1;
37
+ while (index < text.length) {
38
+ if (text[index] === "\\")
39
+ index += 2;
40
+ else if (text[index++] === '"')
41
+ return text.slice(start, index);
42
+ }
43
+ throw new Error("Verifier output contains an unterminated JSON string.");
44
+ };
45
+ const readValue = () => {
46
+ skipWhitespace();
47
+ if (text[index] === "{")
48
+ readObject();
49
+ else if (text[index] === "[")
50
+ readArray();
51
+ else if (text[index] === '"')
52
+ readString();
53
+ else
54
+ while (index < text.length && !/[\s,\]}]/.test(text[index]))
55
+ index += 1;
56
+ };
57
+ const readObject = () => {
58
+ index += 1;
59
+ skipWhitespace();
60
+ const keys = new Set();
61
+ if (text[index] === "}") {
62
+ index += 1;
63
+ return;
64
+ }
65
+ while (index < text.length) {
66
+ skipWhitespace();
67
+ const key = JSON.parse(readString());
68
+ if (keys.has(key))
69
+ throw new Error("Verifier output contains duplicate JSON object keys.");
70
+ keys.add(key);
71
+ skipWhitespace();
72
+ index += 1;
73
+ readValue();
74
+ skipWhitespace();
75
+ if (text[index] === "}") {
76
+ index += 1;
77
+ return;
78
+ }
79
+ index += 1;
80
+ }
81
+ throw new Error("Verifier output contains an unterminated JSON object.");
82
+ };
83
+ const readArray = () => {
84
+ index += 1;
85
+ skipWhitespace();
86
+ if (text[index] === "]") {
87
+ index += 1;
88
+ return;
89
+ }
90
+ while (index < text.length) {
91
+ readValue();
92
+ skipWhitespace();
93
+ if (text[index] === "]") {
94
+ index += 1;
95
+ return;
96
+ }
97
+ index += 1;
98
+ }
99
+ throw new Error("Verifier output contains an unterminated JSON array.");
100
+ };
101
+ readValue();
102
+ skipWhitespace();
103
+ }
104
+ export function assertUniqueArtifactIdentities(identities) {
105
+ const ids = new Set();
106
+ const paths = new Set();
107
+ const ancestorPaths = new Set();
108
+ for (const identity of identities) {
109
+ if (identity.id.trim() === "" || ids.has(identity.id)) {
110
+ throw new Error(`Duplicate artifact identity "${identity.id}".`);
111
+ }
112
+ if (!isSafeArtifactRelativePath(identity.relativePath)) {
113
+ throw new Error(`Artifact path "${identity.relativePath}" is unsafe.`);
114
+ }
115
+ const normalizedPath = identity.relativePath.toLowerCase();
116
+ if (paths.has(normalizedPath) || ancestorPaths.has(normalizedPath)) {
117
+ throw new Error(`Artifact path "${identity.relativePath}" collides with another artifact path.`);
118
+ }
119
+ for (let boundary = normalizedPath.lastIndexOf("/"); boundary > 0; boundary = normalizedPath.lastIndexOf("/", boundary - 1)) {
120
+ const ancestor = normalizedPath.slice(0, boundary);
121
+ if (paths.has(ancestor)) {
122
+ throw new Error(`Artifact path "${identity.relativePath}" collides with another artifact path.`);
123
+ }
124
+ ancestorPaths.add(ancestor);
125
+ }
126
+ ids.add(identity.id);
127
+ paths.add(normalizedPath);
128
+ }
129
+ }
130
+ export function validateArtifact(artifact, document) {
131
+ const schemaVersion = schemaVersionOf(document);
132
+ const validate = validators.get(schemaVersion);
133
+ if (validate === undefined) {
134
+ return [{ artifact, schemaVersion, field: "/schemaVersion", message: "Unsupported schema version." }];
135
+ }
136
+ const errors = validate(document)
137
+ ? []
138
+ : (validate.errors ?? []).map((error) => validationError(artifact, schemaVersion, error));
139
+ if (schemaVersion === "run-manifest/v1" && isRecord(document) && Array.isArray(document.evidence)) {
140
+ errors.push(...runManifestIntegrityErrors(artifact, schemaVersion, document));
141
+ }
142
+ if (schemaVersion === "verifier-result/v1" && isRecord(document) && Array.isArray(document.assertions)) {
143
+ const assertionIds = document.assertions
144
+ .filter((assertion) => isRecord(assertion) && typeof assertion.id === "string")
145
+ .map((assertion) => assertion.id);
146
+ if (new Set(assertionIds).size !== assertionIds.length) {
147
+ errors.push({ artifact, schemaVersion, field: "/assertions", message: "Verifier assertion IDs must be unique." });
148
+ }
149
+ if (Array.isArray(document.diagnostics)) {
150
+ const diagnosticLocators = document.diagnostics
151
+ .filter((diagnostic) => isRecord(diagnostic) && typeof diagnostic.locator === "string")
152
+ .map((diagnostic) => diagnostic.locator.toLowerCase());
153
+ const diagnosticStreams = document.diagnostics
154
+ .filter((diagnostic) => isRecord(diagnostic) && typeof diagnostic.stream === "string")
155
+ .map((diagnostic) => diagnostic.stream);
156
+ if (new Set(diagnosticStreams).size !== diagnosticStreams.length) {
157
+ errors.push({ artifact, schemaVersion, field: "/diagnostics", message: "Verifier diagnostic streams must be unique." });
158
+ }
159
+ const seenDiagnosticLocators = new Set();
160
+ if (diagnosticLocators.some((locator) => {
161
+ const collision = hasPortablePathCollision(locator, seenDiagnosticLocators);
162
+ seenDiagnosticLocators.add(locator);
163
+ return collision;
164
+ })) {
165
+ errors.push({ artifact, schemaVersion, field: "/diagnostics", message: "Verifier diagnostic locators must be unique and non-overlapping." });
166
+ }
167
+ }
168
+ }
169
+ return errors;
170
+ }
171
+ export function validateRunManifestEvidence(artifact, manifest, bundleRoot) {
172
+ if (!isRecord(manifest) || manifest.schemaVersion !== "run-manifest/v1" || !Array.isArray(manifest.evidence))
173
+ return [];
174
+ const errors = [];
175
+ const expectedBundleId = typeof manifest.bundleId === "string" ? manifest.bundleId : undefined;
176
+ const expectedVerifier = isRecord(manifest.run) && isRecord(manifest.run.verifier)
177
+ ? manifest.run.verifier
178
+ : undefined;
179
+ const workspaceEvidence = new Map(manifest.evidence
180
+ .filter((entry) => isRecord(entry)
181
+ && typeof entry.id === "string" && entry.kind === "workspace")
182
+ .map((entry) => [entry.id, entry]));
183
+ const verifierOutcomes = [];
184
+ const verifierResults = new Map();
185
+ const evidenceByPath = new Map(manifest.evidence
186
+ .filter((entry) => isRecord(entry)
187
+ && typeof entry.relativePath === "string")
188
+ .map((entry) => [entry.relativePath.toLowerCase(), entry]));
189
+ const evidencePaths = new Set(manifest.evidence
190
+ .filter((entry) => isRecord(entry) && typeof entry.relativePath === "string")
191
+ .map((entry) => entry.relativePath.toLowerCase()));
192
+ const nestedDiagnosticPaths = new Set();
193
+ for (const descriptor of manifest.evidence) {
194
+ if (!isRecord(descriptor) || typeof descriptor.id !== "string" || typeof descriptor.relativePath !== "string"
195
+ || typeof descriptor.digest !== "string" || typeof descriptor.sizeBytes !== "number")
196
+ continue;
197
+ try {
198
+ const bytes = resolveBundleArtifact(bundleRoot, {
199
+ locator: descriptor.relativePath,
200
+ digest: runDigest(descriptor.digest),
201
+ });
202
+ if (bytes.length !== descriptor.sizeBytes)
203
+ throw new Error("Artifact size does not match its manifest descriptor.");
204
+ if (descriptor.kind === "verifier") {
205
+ const verifierErrors = nestedVerifierDiagnosticErrors(artifact, descriptor.id, bytes, bundleRoot, expectedBundleId, expectedVerifier, workspaceEvidence, descriptor.sanitizedFrom !== undefined, isRecord(descriptor.sanitizedFrom) && typeof descriptor.sanitizedFrom.artifactId === "string"
206
+ ? descriptor.sanitizedFrom.artifactId
207
+ : undefined, evidenceByPath, evidencePaths, nestedDiagnosticPaths);
208
+ errors.push(...verifierErrors);
209
+ if (verifierErrors.length === 0) {
210
+ const outcome = nestedVerifierOutcome(bytes);
211
+ if (outcome !== undefined) {
212
+ verifierResults.set(descriptor.id, outcome);
213
+ if (descriptor.sanitizedFrom === undefined)
214
+ verifierOutcomes.push(outcome);
215
+ }
216
+ }
217
+ }
218
+ }
219
+ catch (error) {
220
+ errors.push({
221
+ artifact,
222
+ schemaVersion: "run-manifest/v1",
223
+ field: `/evidence/${escapeJsonPointer(descriptor.id)}`,
224
+ message: error instanceof Error ? error.message : "Evidence could not be verified.",
225
+ });
226
+ }
227
+ }
228
+ for (const descriptor of manifest.evidence) {
229
+ if (!isRecord(descriptor) || descriptor.kind !== "verifier" || !isRecord(descriptor.sanitizedFrom)
230
+ || typeof descriptor.id !== "string" || typeof descriptor.sanitizedFrom.artifactId !== "string")
231
+ continue;
232
+ const source = verifierResults.get(descriptor.sanitizedFrom.artifactId);
233
+ const derivative = verifierResults.get(descriptor.id);
234
+ if (source === undefined || derivative === undefined)
235
+ continue;
236
+ if (source.status !== derivative.status) {
237
+ errors.push({
238
+ artifact,
239
+ schemaVersion: "run-manifest/v1",
240
+ field: `/evidence/${escapeJsonPointer(descriptor.id)}/status`,
241
+ message: "Sanitized verifier must preserve its source outcome status.",
242
+ });
243
+ }
244
+ const sourceAssertions = new Map(source.assertions.map((assertion) => [assertion.id, assertion.status]));
245
+ const derivativeAssertions = new Map(derivative.assertions.map((assertion) => [assertion.id, assertion.status]));
246
+ if (sourceAssertions.size !== derivativeAssertions.size
247
+ || [...sourceAssertions].some(([id, status]) => derivativeAssertions.get(id) !== status)) {
248
+ errors.push({
249
+ artifact,
250
+ schemaVersion: "run-manifest/v1",
251
+ field: `/evidence/${escapeJsonPointer(descriptor.id)}/assertions`,
252
+ message: "Sanitized verifier must preserve source assertion outcomes.",
253
+ });
254
+ }
255
+ if (derivative.exitCode !== source.exitCode) {
256
+ errors.push({
257
+ artifact,
258
+ schemaVersion: "run-manifest/v1",
259
+ field: `/evidence/${escapeJsonPointer(descriptor.id)}/exitCode`,
260
+ message: "Sanitized verifier must preserve its source exit code.",
261
+ });
262
+ }
263
+ const sourceVerifier = source.verifier;
264
+ const derivativeVerifier = derivative.verifier;
265
+ const sameVerifier = sourceVerifier === undefined
266
+ ? derivativeVerifier === undefined
267
+ : derivativeVerifier !== undefined
268
+ && derivativeVerifier.locator === sourceVerifier.locator
269
+ && derivativeVerifier.digest === sourceVerifier.digest
270
+ && derivativeVerifier.format === sourceVerifier.format;
271
+ if (!sameVerifier) {
272
+ errors.push({
273
+ artifact,
274
+ schemaVersion: "run-manifest/v1",
275
+ field: `/evidence/${escapeJsonPointer(descriptor.id)}/verifier`,
276
+ message: "Sanitized verifier must preserve its source verifier binding.",
277
+ });
278
+ }
279
+ if (source.durationMs !== derivative.durationMs) {
280
+ errors.push({
281
+ artifact,
282
+ schemaVersion: "run-manifest/v1",
283
+ field: `/evidence/${escapeJsonPointer(descriptor.id)}/durationMs`,
284
+ message: "Sanitized verifier must preserve its source duration.",
285
+ });
286
+ }
287
+ const sameError = derivative.errorRedacted === true
288
+ ? source.status === "error" && derivative.error === "[redacted]"
289
+ : source.error === derivative.error && source.errorRedacted === derivative.errorRedacted;
290
+ if (!sameError) {
291
+ errors.push({
292
+ artifact,
293
+ schemaVersion: "run-manifest/v1",
294
+ field: `/evidence/${escapeJsonPointer(descriptor.id)}/error`,
295
+ message: "Sanitized verifier must preserve or explicitly redact its source error.",
296
+ });
297
+ }
298
+ const sourceWorkspace = source.workspace;
299
+ const derivativeWorkspace = derivative.workspace;
300
+ const sameWorkspace = sourceWorkspace === undefined
301
+ ? derivativeWorkspace === undefined
302
+ : derivativeWorkspace !== undefined
303
+ && derivativeWorkspace.artifactId === sourceWorkspace.artifactId
304
+ && derivativeWorkspace.digest === sourceWorkspace.digest
305
+ && derivativeWorkspace.fingerprint === sourceWorkspace.fingerprint;
306
+ if (!sameWorkspace) {
307
+ errors.push({
308
+ artifact,
309
+ schemaVersion: "run-manifest/v1",
310
+ field: `/evidence/${escapeJsonPointer(descriptor.id)}/workspace`,
311
+ message: "Sanitized verifier must preserve its source workspace binding.",
312
+ });
313
+ }
314
+ if (derivative.diagnostics.some((diagnostic) => diagnostic.source === undefined
315
+ || !source.diagnostics.some((sourceDiagnostic) => sameDiagnosticOrigin(sourceDiagnostic, diagnostic.source)))) {
316
+ errors.push({
317
+ artifact,
318
+ schemaVersion: "run-manifest/v1",
319
+ field: `/evidence/${escapeJsonPointer(descriptor.id)}/diagnostics`,
320
+ message: "Sanitized verifier diagnostics must identify source diagnostics.",
321
+ });
322
+ }
323
+ }
324
+ const terminal = isRecord(manifest.terminal) ? manifest.terminal : undefined;
325
+ const assessmentMode = isRecord(manifest.run) ? manifest.run.assessmentMode ?? "verified" : undefined;
326
+ const terminalWorkspace = typeof terminal?.workspaceArtifactId === "string"
327
+ ? workspaceEvidence.get(terminal.workspaceArtifactId)
328
+ : undefined;
329
+ const matchesTerminalWorkspace = (outcome, status) => outcome.status === status
330
+ && outcome.workspace !== undefined
331
+ && terminalWorkspace !== undefined
332
+ && outcome.workspace.artifactId === terminalWorkspace.id
333
+ && outcome.workspace.digest === terminalWorkspace.digest
334
+ && (outcome.workspace.fingerprint === undefined
335
+ ? terminalWorkspace.fingerprint === undefined
336
+ : terminalWorkspace.fingerprint !== undefined && outcome.workspace.fingerprint === terminalWorkspace.fingerprint);
337
+ if (assessmentMode === "verified" && terminal?.state === "completed"
338
+ && !verifierOutcomes.some((outcome) => matchesTerminalWorkspace(outcome, "passed"))) {
339
+ errors.push({
340
+ artifact,
341
+ schemaVersion: "run-manifest/v1",
342
+ field: "/terminal/state",
343
+ message: "Completed runs require a passed verifier bound to the terminal workspace.",
344
+ });
345
+ }
346
+ if (terminal?.state === "failed" && terminal.failureClass === "task"
347
+ && !verifierOutcomes.some((outcome) => matchesTerminalWorkspace(outcome, "failed"))) {
348
+ errors.push({
349
+ artifact,
350
+ schemaVersion: "run-manifest/v1",
351
+ field: "/terminal/state",
352
+ message: "Task-failed runs require a failed verifier bound to the terminal workspace.",
353
+ });
354
+ }
355
+ if (assessmentMode === "observational" && terminal?.failureClass === "task") {
356
+ errors.push({
357
+ artifact,
358
+ schemaVersion: "run-manifest/v1",
359
+ field: "/terminal/failureClass",
360
+ message: "Observational runs cannot claim task failure.",
361
+ });
362
+ }
363
+ if (assessmentMode === "observational" && verifierOutcomes.length > 0) {
364
+ errors.push({
365
+ artifact,
366
+ schemaVersion: "run-manifest/v1",
367
+ field: "/evidence",
368
+ message: "Observational runs cannot retain verifier outcome evidence.",
369
+ });
370
+ }
371
+ return errors;
372
+ }
373
+ function isNestedVerifierDiagnostic(value) {
374
+ if (!isRecord(value) || ![5, 6].includes(Object.keys(value).length)
375
+ || typeof value.stream !== "string" || !["stdout", "stderr"].includes(value.stream)
376
+ || typeof value.locator !== "string" || typeof value.digest !== "string"
377
+ || !/^sha256:[a-f0-9]{64}$/.test(value.digest) || !Number.isSafeInteger(value.sizeBytes)
378
+ || value.sizeBytes < 0 || typeof value.truncated !== "boolean")
379
+ return false;
380
+ return !Object.hasOwn(value, "source") || isNestedVerifierDiagnosticOrigin(value.source);
381
+ }
382
+ function isNestedVerifierDiagnosticOrigin(value) {
383
+ return isRecord(value) && Object.keys(value).length === 5
384
+ && typeof value.stream === "string" && ["stdout", "stderr"].includes(value.stream)
385
+ && typeof value.locator === "string" && typeof value.digest === "string"
386
+ && /^sha256:[a-f0-9]{64}$/.test(value.digest) && Number.isSafeInteger(value.sizeBytes)
387
+ && value.sizeBytes >= 0 && typeof value.truncated === "boolean";
388
+ }
389
+ function isDiagnosticSourceBinding(value) {
390
+ return isRecord(value) && Object.keys(value).length === 6
391
+ && typeof value.verifierId === "string"
392
+ && isNestedVerifierDiagnosticOrigin(Object.fromEntries(Object.entries(value).filter(([key]) => key !== "verifierId")));
393
+ }
394
+ function sameDiagnosticOrigin(left, right) {
395
+ return left.stream === right.stream
396
+ && left.locator === right.locator
397
+ && left.digest === right.digest
398
+ && left.sizeBytes === right.sizeBytes
399
+ && left.truncated === right.truncated;
400
+ }
401
+ function nestedVerifierOutcome(bytes) {
402
+ try {
403
+ const result = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
404
+ if (!isRecord(result) || typeof result.status !== "string")
405
+ return undefined;
406
+ const verifier = isRecord(result.verifier)
407
+ && typeof result.verifier.locator === "string"
408
+ && typeof result.verifier.digest === "string"
409
+ ? {
410
+ locator: result.verifier.locator,
411
+ digest: result.verifier.digest,
412
+ ...(typeof result.verifier.format === "string" ? { format: result.verifier.format } : {}),
413
+ }
414
+ : undefined;
415
+ const workspace = isRecord(result.workspace)
416
+ && typeof result.workspace.artifactId === "string"
417
+ && typeof result.workspace.digest === "string"
418
+ ? {
419
+ artifactId: result.workspace.artifactId,
420
+ digest: result.workspace.digest,
421
+ ...(typeof result.workspace.fingerprint === "string" ? { fingerprint: result.workspace.fingerprint } : {}),
422
+ }
423
+ : undefined;
424
+ const assertions = Array.isArray(result.assertions)
425
+ ? result.assertions.flatMap((assertion) => isRecord(assertion)
426
+ && typeof assertion.id === "string" && typeof assertion.status === "string"
427
+ ? [{ id: assertion.id, status: assertion.status }]
428
+ : [])
429
+ : [];
430
+ const diagnostics = Array.isArray(result.diagnostics)
431
+ ? result.diagnostics.flatMap((diagnostic) => {
432
+ if (!isRecord(diagnostic) || typeof diagnostic.stream !== "string" || typeof diagnostic.locator !== "string"
433
+ || typeof diagnostic.digest !== "string" || typeof diagnostic.sizeBytes !== "number"
434
+ || typeof diagnostic.truncated !== "boolean")
435
+ return [];
436
+ const source = isRecord(diagnostic.source)
437
+ && typeof diagnostic.source.stream === "string"
438
+ && typeof diagnostic.source.locator === "string"
439
+ && typeof diagnostic.source.digest === "string"
440
+ && typeof diagnostic.source.sizeBytes === "number"
441
+ && typeof diagnostic.source.truncated === "boolean"
442
+ ? {
443
+ stream: diagnostic.source.stream,
444
+ locator: diagnostic.source.locator,
445
+ digest: diagnostic.source.digest,
446
+ sizeBytes: diagnostic.source.sizeBytes,
447
+ truncated: diagnostic.source.truncated,
448
+ }
449
+ : undefined;
450
+ return [{
451
+ stream: diagnostic.stream,
452
+ locator: diagnostic.locator,
453
+ digest: diagnostic.digest,
454
+ sizeBytes: diagnostic.sizeBytes,
455
+ truncated: diagnostic.truncated,
456
+ ...(source === undefined ? {} : { source }),
457
+ }];
458
+ })
459
+ : [];
460
+ const exitCode = typeof result.exitCode === "number" ? result.exitCode : undefined;
461
+ const durationMs = typeof result.durationMs === "number" ? result.durationMs : undefined;
462
+ const error = typeof result.error === "string" ? result.error : undefined;
463
+ const errorRedacted = typeof result.errorRedacted === "boolean" ? result.errorRedacted : undefined;
464
+ return {
465
+ status: result.status,
466
+ ...(durationMs === undefined ? {} : { durationMs }),
467
+ ...(error === undefined ? {} : { error }),
468
+ ...(errorRedacted === undefined ? {} : { errorRedacted }),
469
+ assertions,
470
+ diagnostics,
471
+ ...(verifier === undefined ? {} : { verifier }),
472
+ ...(workspace === undefined ? {} : { workspace }),
473
+ ...(exitCode === undefined ? {} : { exitCode }),
474
+ };
475
+ }
476
+ catch {
477
+ return undefined;
478
+ }
479
+ }
480
+ function nestedVerifierDiagnosticErrors(artifact, verifierId, bytes, bundleRoot, expectedBundleId, expectedVerifier, workspaceEvidence, sanitized, sanitizedSourceVerifierId, evidenceByPath, evidencePaths, nestedDiagnosticPaths) {
481
+ const scope = `/evidence/${escapeJsonPointer(verifierId)}`;
482
+ let result;
483
+ try {
484
+ const text = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
485
+ result = JSON.parse(text);
486
+ assertNoDuplicateJsonKeys(text);
487
+ }
488
+ catch (error) {
489
+ return [{ artifact, schemaVersion: "run-manifest/v1", field: scope, message: `Verifier artifact could not be parsed: ${error instanceof Error ? error.message : "invalid UTF-8 or JSON."}` }];
490
+ }
491
+ const schemaErrors = validateArtifact(`${artifact}/${verifierId}`, result);
492
+ if (schemaErrors.length > 0) {
493
+ return schemaErrors.map((error) => ({ ...error, artifact, schemaVersion: "run-manifest/v1", field: `${scope}${error.field}` }));
494
+ }
495
+ if (!isRecord(result))
496
+ return [];
497
+ const bindingErrors = [];
498
+ if (expectedBundleId !== undefined && result.bundleId !== expectedBundleId) {
499
+ bindingErrors.push({
500
+ artifact,
501
+ schemaVersion: "run-manifest/v1",
502
+ field: `${scope}/bundleId`,
503
+ message: "Verifier bundleId must match its containing run manifest.",
504
+ });
505
+ }
506
+ if (expectedVerifier !== undefined) {
507
+ const actualVerifier = isRecord(result.verifier) ? result.verifier : undefined;
508
+ const expectedFormat = typeof expectedVerifier.format === "string"
509
+ ? expectedVerifier.format
510
+ : typeof expectedVerifier.locator === "string" && expectedVerifier.locator.toLowerCase().endsWith(".mjs")
511
+ ? "module"
512
+ : "commonjs";
513
+ if (actualVerifier === undefined
514
+ || actualVerifier.locator !== expectedVerifier.locator
515
+ || actualVerifier.digest !== expectedVerifier.digest
516
+ || actualVerifier.format !== expectedFormat) {
517
+ bindingErrors.push({
518
+ artifact,
519
+ schemaVersion: "run-manifest/v1",
520
+ field: `${scope}/verifier`,
521
+ message: "Verifier execution reference must match the run configuration.",
522
+ });
523
+ }
524
+ }
525
+ if (isRecord(result.workspace)) {
526
+ const workspaceArtifactId = result.workspace.artifactId;
527
+ const retainedWorkspace = typeof workspaceArtifactId === "string" ? workspaceEvidence.get(workspaceArtifactId) : undefined;
528
+ if (retainedWorkspace === undefined) {
529
+ bindingErrors.push({
530
+ artifact,
531
+ schemaVersion: "run-manifest/v1",
532
+ field: `${scope}/workspace/artifactId`,
533
+ message: "Verifier workspace must reference retained workspace evidence.",
534
+ });
535
+ }
536
+ else if (retainedWorkspace.digest !== result.workspace.digest
537
+ || (result.workspace.fingerprint === undefined
538
+ ? retainedWorkspace.fingerprint !== undefined
539
+ : retainedWorkspace.fingerprint === undefined || retainedWorkspace.fingerprint !== result.workspace.fingerprint)) {
540
+ bindingErrors.push({
541
+ artifact,
542
+ schemaVersion: "run-manifest/v1",
543
+ field: `${scope}/workspace/digest`,
544
+ message: "Verifier workspace digest must match retained workspace evidence.",
545
+ });
546
+ }
547
+ }
548
+ if (result.diagnostics === undefined)
549
+ return bindingErrors;
550
+ if (!Array.isArray(result.diagnostics)) {
551
+ return [
552
+ ...bindingErrors,
553
+ { artifact, schemaVersion: "run-manifest/v1", field: `${scope}/diagnostics`, message: "Verifier diagnostics must be an array." },
554
+ ];
555
+ }
556
+ const errors = [...bindingErrors];
557
+ for (const [index, diagnostic] of result.diagnostics.entries()) {
558
+ if (!isNestedVerifierDiagnostic(diagnostic)) {
559
+ errors.push({ artifact, schemaVersion: "run-manifest/v1", field: `${scope}/diagnostics/${index}`, message: "Verifier diagnostic reference is malformed." });
560
+ continue;
561
+ }
562
+ const normalizedLocator = diagnostic.locator.toLowerCase();
563
+ const retainedEvidence = evidenceByPath.get(normalizedLocator);
564
+ const exactSidecar = retainedEvidence !== undefined
565
+ && retainedEvidence.relativePath === diagnostic.locator
566
+ && retainedEvidence.digest === diagnostic.digest
567
+ && retainedEvidence.sizeBytes === diagnostic.sizeBytes;
568
+ const invalidUnsanitizedReference = !sanitized && diagnostic.source !== undefined;
569
+ const sourceOrigin = diagnostic.source;
570
+ const sourceBinding = retainedEvidence !== undefined && isDiagnosticSourceBinding(retainedEvidence.diagnosticSource)
571
+ ? retainedEvidence.diagnosticSource
572
+ : undefined;
573
+ const sourceBindingMatches = sourceBinding !== undefined
574
+ && sourceOrigin !== undefined
575
+ && sourceBinding.verifierId === sanitizedSourceVerifierId
576
+ && sourceBinding.stream === diagnostic.stream
577
+ && sourceOrigin.stream === diagnostic.stream
578
+ && sameDiagnosticOrigin(sourceBinding, sourceOrigin);
579
+ const byteIdentical = sanitized && sourceOrigin !== undefined && diagnostic.digest === sourceOrigin.digest;
580
+ const isSanitizedSidecar = exactSidecar
581
+ && retainedEvidence.kind === "diagnostic"
582
+ && retainedEvidence.authority === "outcome"
583
+ && (retainedEvidence.mediaType === "text/plain" || retainedEvidence.mediaType === "application/octet-stream")
584
+ && (retainedEvidence.sharingClass === "partner" || retainedEvidence.sharingClass === "public")
585
+ && isRecord(retainedEvidence.sanitizedFrom)
586
+ && retainedEvidence.sanitizedFrom.artifactId === sanitizedSourceVerifierId
587
+ && sourceBindingMatches
588
+ && !byteIdentical;
589
+ const invalidSanitizedReference = sanitized && !isSanitizedSidecar;
590
+ const aliasesRetainedEvidence = !sanitized && hasPortablePathCollision(normalizedLocator, evidencePaths);
591
+ if (invalidUnsanitizedReference
592
+ || hasPortablePathCollision(normalizedLocator, RESERVED_MANIFEST_PATHS)
593
+ || invalidSanitizedReference
594
+ || aliasesRetainedEvidence
595
+ || hasPortablePathCollision(normalizedLocator, nestedDiagnosticPaths)) {
596
+ errors.push({
597
+ artifact,
598
+ schemaVersion: "run-manifest/v1",
599
+ field: `${scope}/diagnostics`,
600
+ message: invalidUnsanitizedReference
601
+ ? "Unsanitized verifier diagnostics cannot carry source provenance."
602
+ : invalidSanitizedReference
603
+ ? "Sanitized verifier diagnostics require classified, source-bound sidecars with changed bytes."
604
+ : "Verifier diagnostics cannot alias retained evidence paths.",
605
+ });
606
+ continue;
607
+ }
608
+ nestedDiagnosticPaths.add(normalizedLocator);
609
+ try {
610
+ const diagnosticBytes = resolveBundleArtifact(bundleRoot, {
611
+ locator: diagnostic.locator,
612
+ digest: runDigest(diagnostic.digest),
613
+ });
614
+ if (diagnosticBytes.length !== diagnostic.sizeBytes) {
615
+ throw new Error("Diagnostic size does not match its result reference.");
616
+ }
617
+ }
618
+ catch (error) {
619
+ errors.push({
620
+ artifact,
621
+ schemaVersion: "run-manifest/v1",
622
+ field: `${scope}/diagnostics`,
623
+ message: error instanceof Error ? error.message : "Verifier diagnostic could not be verified.",
624
+ });
625
+ }
626
+ }
627
+ return errors;
628
+ }
629
+ function hasPortablePathCollision(path, existingPaths) {
630
+ return [...existingPaths].some((existing) => existing === path || existing.startsWith(`${path}/`) || path.startsWith(`${existing}/`));
631
+ }
632
+ export function validateExportManifest(artifact, exportManifest, containingManifest, bundleRoot) {
633
+ if (!isRecord(exportManifest) || exportManifest.schemaVersion !== "export-manifest/v1"
634
+ || !["ready", "exported"].includes(String(exportManifest.status)))
635
+ return [];
636
+ if (!isRecord(containingManifest) || containingManifest.schemaVersion !== "run-manifest/v1" || !Array.isArray(containingManifest.evidence)) {
637
+ return [{ artifact, schemaVersion: "export-manifest/v1", field: "/bundleId", message: "Ready exports require their containing run manifest." }];
638
+ }
639
+ if (exportManifest.bundleId !== containingManifest.bundleId) {
640
+ return [{ artifact, schemaVersion: "export-manifest/v1", field: "/bundleId", message: "Export bundle ID does not match its containing run manifest." }];
641
+ }
642
+ if (!isRecord(containingManifest.run)
643
+ || (exportManifest.assessmentMode ?? "verified") !== (containingManifest.run.assessmentMode ?? "verified")) {
644
+ return [{ artifact, schemaVersion: "export-manifest/v1", field: "/assessmentMode", message: "Export assessment mode does not match its containing run manifest." }];
645
+ }
646
+ const evidenceById = new Map();
647
+ for (const descriptor of containingManifest.evidence) {
648
+ if (isRecord(descriptor) && typeof descriptor.id === "string")
649
+ evidenceById.set(descriptor.id, descriptor);
650
+ }
651
+ const errors = [];
652
+ const expectedBundleId = typeof containingManifest.bundleId === "string" ? containingManifest.bundleId : undefined;
653
+ const expectedVerifier = isRecord(containingManifest.run) && isRecord(containingManifest.run.verifier)
654
+ ? containingManifest.run.verifier
655
+ : undefined;
656
+ const workspaceEvidence = new Map(containingManifest.evidence
657
+ .filter((entry) => isRecord(entry)
658
+ && typeof entry.id === "string" && entry.kind === "workspace")
659
+ .map((entry) => [entry.id, entry]));
660
+ const evidenceByPath = new Map(containingManifest.evidence
661
+ .filter((entry) => isRecord(entry)
662
+ && typeof entry.relativePath === "string")
663
+ .map((entry) => [entry.relativePath.toLowerCase(), entry]));
664
+ const exportedIds = new Set(Array.isArray(exportManifest.artifactIds)
665
+ ? exportManifest.artifactIds.filter((id) => typeof id === "string")
666
+ : []);
667
+ if (bundleRoot !== undefined) {
668
+ errors.push(...validateRunManifestEvidence(artifact, containingManifest, bundleRoot));
669
+ }
670
+ const referencedDiagnosticPaths = new Set();
671
+ const exportedDiagnosticDescriptors = [];
672
+ let hasNonExportArtifact = false;
673
+ for (const id of Array.isArray(exportManifest.artifactIds) ? exportManifest.artifactIds : []) {
674
+ const descriptor = typeof id === "string" ? evidenceById.get(id) : undefined;
675
+ if (descriptor === undefined || descriptor.sharingClass === "unknown" || descriptor.sharingClass !== exportManifest.sharingClass) {
676
+ errors.push({ artifact, schemaVersion: "export-manifest/v1", field: "/artifactIds", message: `Export cannot include artifact ${String(id)}.` });
677
+ continue;
678
+ }
679
+ if (exportManifest.sharingClass === "partner" || exportManifest.sharingClass === "public") {
680
+ validateSanitizedProvenance(artifact, "export-manifest/v1", descriptor, evidenceById, errors);
681
+ if (descriptor.kind === "verifier" && descriptor.sanitizedFrom !== undefined) {
682
+ errors.push(...validateExportedVerifierDiagnostics(artifact, descriptor, bundleRoot, expectedBundleId, expectedVerifier, workspaceEvidence, evidenceByPath, exportedIds));
683
+ for (const locator of exportedVerifierDiagnosticLocators(bundleRoot, descriptor)) {
684
+ referencedDiagnosticPaths.add(locator);
685
+ }
686
+ }
687
+ if (descriptor.kind === "diagnostic" && descriptor.sanitizedFrom !== undefined) {
688
+ errors.push(...validateExportedDiagnosticSidecar(artifact, descriptor, bundleRoot, evidenceById));
689
+ exportedDiagnosticDescriptors.push(descriptor);
690
+ }
691
+ }
692
+ hasNonExportArtifact ||= descriptor.kind !== "export-manifest";
693
+ }
694
+ for (const descriptor of exportedDiagnosticDescriptors) {
695
+ if (typeof descriptor.relativePath === "string" && !referencedDiagnosticPaths.has(descriptor.relativePath.toLowerCase())) {
696
+ errors.push({
697
+ artifact,
698
+ schemaVersion: "export-manifest/v1",
699
+ field: `/artifactIds/${escapeJsonPointer(String(descriptor.id))}`,
700
+ message: "Exported diagnostic sidecars must be referenced by an exported sanitized verifier.",
701
+ });
702
+ }
703
+ }
704
+ if (!hasNonExportArtifact)
705
+ errors.push({ artifact, schemaVersion: "export-manifest/v1", field: "/artifactIds", message: "Ready exports must include non-export evidence." });
706
+ return errors;
707
+ }
708
+ function exportedVerifierDiagnosticLocators(bundleRoot, descriptor) {
709
+ if (bundleRoot === undefined || typeof descriptor.relativePath !== "string" || typeof descriptor.digest !== "string")
710
+ return [];
711
+ try {
712
+ const bytes = resolveBundleArtifact(bundleRoot, {
713
+ locator: descriptor.relativePath,
714
+ digest: runDigest(descriptor.digest),
715
+ });
716
+ const result = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
717
+ return isRecord(result) && Array.isArray(result.diagnostics)
718
+ ? result.diagnostics.flatMap((diagnostic) => isRecord(diagnostic) && typeof diagnostic.locator === "string"
719
+ ? [diagnostic.locator.toLowerCase()]
720
+ : [])
721
+ : [];
722
+ }
723
+ catch {
724
+ return [];
725
+ }
726
+ }
727
+ function validateExportedDiagnosticSidecar(artifact, descriptor, bundleRoot, evidenceById) {
728
+ const scope = `/artifactIds/${escapeJsonPointer(String(descriptor.id))}`;
729
+ if (bundleRoot === undefined) {
730
+ return [{ artifact, schemaVersion: "export-manifest/v1", field: scope, message: "Exported diagnostic sidecars require a bundle root." }];
731
+ }
732
+ const sourceBinding = isDiagnosticSourceBinding(descriptor.diagnosticSource) ? descriptor.diagnosticSource : undefined;
733
+ if (sourceBinding === undefined) {
734
+ return [{ artifact, schemaVersion: "export-manifest/v1", field: `${scope}/diagnosticSource`, message: "Exported diagnostic sidecars require source-specific provenance." }];
735
+ }
736
+ if (!isRecord(descriptor.sanitizedFrom) || descriptor.sanitizedFrom.artifactId !== sourceBinding.verifierId) {
737
+ return [{ artifact, schemaVersion: "export-manifest/v1", field: `${scope}/sanitizedFrom`, message: "Diagnostic sidecar provenance must name its diagnostic source verifier." }];
738
+ }
739
+ const sourceDescriptor = evidenceById.get(sourceBinding.verifierId);
740
+ if (sourceDescriptor === undefined || sourceDescriptor.kind !== "verifier"
741
+ || typeof sourceDescriptor.relativePath !== "string" || typeof sourceDescriptor.digest !== "string") {
742
+ return [{ artifact, schemaVersion: "export-manifest/v1", field: `${scope}/diagnosticSource/verifierId`, message: "Diagnostic source provenance must reference a retained verifier." }];
743
+ }
744
+ let sourceOutcome;
745
+ try {
746
+ const sourceBytes = resolveBundleArtifact(bundleRoot, {
747
+ locator: sourceDescriptor.relativePath,
748
+ digest: runDigest(sourceDescriptor.digest),
749
+ });
750
+ sourceOutcome = nestedVerifierOutcome(sourceBytes);
751
+ }
752
+ catch {
753
+ sourceOutcome = undefined;
754
+ }
755
+ if (sourceOutcome === undefined || !sourceOutcome.diagnostics.some((diagnostic) => sameDiagnosticOrigin(diagnostic, sourceBinding))) {
756
+ return [{ artifact, schemaVersion: "export-manifest/v1", field: `${scope}/diagnosticSource`, message: "Diagnostic source provenance must match a retained source diagnostic." }];
757
+ }
758
+ const errors = [];
759
+ if (typeof descriptor.relativePath !== "string" || typeof descriptor.digest !== "string" || typeof descriptor.sizeBytes !== "number")
760
+ return errors;
761
+ try {
762
+ const sidecarBytes = resolveBundleArtifact(bundleRoot, {
763
+ locator: descriptor.relativePath,
764
+ digest: runDigest(descriptor.digest),
765
+ });
766
+ if (sidecarBytes.length !== descriptor.sizeBytes)
767
+ throw new Error("Diagnostic sidecar size does not match its descriptor.");
768
+ }
769
+ catch (error) {
770
+ errors.push({ artifact, schemaVersion: "export-manifest/v1", field: scope, message: error instanceof Error ? error.message : "Diagnostic sidecar could not be verified." });
771
+ }
772
+ if (descriptor.digest === sourceBinding.digest) {
773
+ errors.push({ artifact, schemaVersion: "export-manifest/v1", field: `${scope}/digest`, message: "Diagnostic sidecar bytes must differ from the source diagnostic." });
774
+ }
775
+ return errors;
776
+ }
777
+ function validateExportedVerifierDiagnostics(artifact, descriptor, bundleRoot, expectedBundleId, expectedVerifier, workspaceEvidence, evidenceByPath, exportedIds) {
778
+ const scope = `/artifactIds/${escapeJsonPointer(String(descriptor.id))}`;
779
+ if (bundleRoot === undefined) {
780
+ return [{ artifact, schemaVersion: "export-manifest/v1", field: scope, message: "Sanitized verifier exports require a bundle root to validate diagnostics." }];
781
+ }
782
+ let bytes;
783
+ try {
784
+ bytes = resolveBundleArtifact(bundleRoot, {
785
+ locator: descriptor.relativePath,
786
+ digest: runDigest(descriptor.digest),
787
+ });
788
+ }
789
+ catch (error) {
790
+ return [{ artifact, schemaVersion: "export-manifest/v1", field: scope, message: error instanceof Error ? error.message : "Sanitized verifier could not be read." }];
791
+ }
792
+ const evidencePaths = new Set([...evidenceByPath.keys()]);
793
+ const errors = nestedVerifierDiagnosticErrors(artifact, String(descriptor.id), bytes, bundleRoot, expectedBundleId, expectedVerifier, workspaceEvidence, true, isRecord(descriptor.sanitizedFrom) && typeof descriptor.sanitizedFrom.artifactId === "string"
794
+ ? descriptor.sanitizedFrom.artifactId
795
+ : undefined, evidenceByPath, evidencePaths, new Set());
796
+ if (errors.length > 0)
797
+ return errors.map((error) => ({ ...error, schemaVersion: "export-manifest/v1", field: `${scope}${error.field.replace(/^\/evidence\/[^/]*/, "")}` }));
798
+ let result;
799
+ try {
800
+ result = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
801
+ }
802
+ catch {
803
+ return [];
804
+ }
805
+ if (!isRecord(result) || !Array.isArray(result.diagnostics))
806
+ return [];
807
+ for (const [index, diagnostic] of result.diagnostics.entries()) {
808
+ if (!isRecord(diagnostic) || typeof diagnostic.locator !== "string")
809
+ continue;
810
+ const sidecar = evidenceByPath.get(diagnostic.locator.toLowerCase());
811
+ if (sidecar !== undefined && typeof sidecar.id === "string" && !exportedIds.has(sidecar.id)) {
812
+ errors.push({
813
+ artifact,
814
+ schemaVersion: "export-manifest/v1",
815
+ field: `${scope}/diagnostics/${index}/locator`,
816
+ message: "Sanitized verifier diagnostic sidecars must be included in the export.",
817
+ });
818
+ }
819
+ }
820
+ return errors;
821
+ }
822
+ export async function readVerifiedArtifact(artifactRoot, relativePath, expectedDigest, maxBytes) {
823
+ if (maxBytes !== undefined && (!Number.isSafeInteger(maxBytes) || maxBytes < 0)) {
824
+ throw new Error("Artifact byte limit must be a nonnegative safe integer.");
825
+ }
826
+ const { root, path } = await resolveExistingArtifactPath(artifactRoot, relativePath);
827
+ removeInterruptedPublicationLink(path, lstatSync(path));
828
+ const handle = await open(path, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
829
+ try {
830
+ const opened = await handle.stat();
831
+ const current = await lstat(path);
832
+ if (!opened.isFile() || !(await isReadablePublishedArtifact(path, opened))
833
+ || current.isSymbolicLink() || opened.dev !== current.dev || opened.ino !== current.ino) {
834
+ throw new Error(`Artifact path "${relativePath}" is not an isolated regular file.`);
835
+ }
836
+ if (maxBytes !== undefined && opened.size > maxBytes) {
837
+ throw new Error(`Artifact "${relativePath}" exceeds the qualification byte limit of ${maxBytes}.`);
838
+ }
839
+ await assertExistingArtifactPath(root, relativePath);
840
+ const bytes = await handle.readFile();
841
+ const completed = await handle.stat();
842
+ if (!completed.isFile() || !(await isReadablePublishedArtifact(path, completed))
843
+ || completed.dev !== opened.dev || completed.ino !== opened.ino || completed.size !== opened.size) {
844
+ throw new Error(`Artifact path "${relativePath}" changed while it was being read.`);
845
+ }
846
+ if (!verifyDigest(bytes, expectedDigest)) {
847
+ throw new Error(`Artifact "${relativePath}" digest does not match its source reference.`);
848
+ }
849
+ return bytes;
850
+ }
851
+ finally {
852
+ await handle.close();
853
+ }
854
+ }
855
+ export async function inspectRetainedArtifact(artifactRoot, relativePath) {
856
+ const { path } = await resolveExistingArtifactPath(artifactRoot, relativePath);
857
+ return inspectExistingPath(path, relativePath);
858
+ }
859
+ export async function writeMetadataAtomically(artifactRoot, relativePath, metadata, signal, options = {}) {
860
+ const bytes = Buffer.from(canonicalizeMetadata(metadata));
861
+ return writeArtifactAtomically(artifactRoot, relativePath, bytes, signal, options);
862
+ }
863
+ export async function writeArtifactAtomically(artifactRoot, relativePath, content, signal, options = {}) {
864
+ const bytes = Buffer.from(content);
865
+ const { parent, path } = await prepareArtifactPath(artifactRoot, relativePath);
866
+ const temporaryPath = resolve(parent, `.${randomUUID()}.tmp`);
867
+ let handle;
868
+ try {
869
+ handle = await open(temporaryPath, "wx", 0o600);
870
+ await handle.writeFile(bytes);
871
+ await handle.sync();
872
+ await handle.close();
873
+ handle = undefined;
874
+ if (signal?.aborted)
875
+ throw new Error("Artifact metadata write interrupted.");
876
+ if (options.overwrite === false) {
877
+ try {
878
+ await link(temporaryPath, path);
879
+ }
880
+ catch (error) {
881
+ if (error.code === "EEXIST") {
882
+ throw new Error(`Artifact metadata path "${relativePath}" already exists.`);
883
+ }
884
+ throw error;
885
+ }
886
+ await rm(temporaryPath, { force: true });
887
+ }
888
+ else {
889
+ await rename(temporaryPath, path);
890
+ }
891
+ await syncDirectory(parent);
892
+ }
893
+ finally {
894
+ await handle?.close();
895
+ await rm(temporaryPath, { force: true });
896
+ }
897
+ const digest = digestBytes(bytes);
898
+ await readVerifiedArtifact(artifactRoot, relativePath, digest);
899
+ return digest;
900
+ }
901
+ export function writeMetadataAtomicallyIfAbsentSync(artifactRoot, relativePath, metadata, rootHandle, beforeDestinationPublish, afterDestinationPublish) {
902
+ const bytes = Buffer.from(canonicalizeMetadata(metadata));
903
+ const rootIdentity = rootHandle ?? openBundleRoot(artifactRoot);
904
+ const ownsRoot = rootHandle === undefined;
905
+ let descriptor;
906
+ let temporaryPath;
907
+ try {
908
+ const { createdDirectories, parent, path } = prepareArtifactPathSync(rootIdentity.path, relativePath);
909
+ temporaryPath = resolve(parent, `.${randomUUID()}.tmp`);
910
+ descriptor = openSync(temporaryPath, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW, 0o600);
911
+ writeFileSync(descriptor, bytes);
912
+ fsyncSync(descriptor);
913
+ beforeDestinationPublish?.();
914
+ let created = false;
915
+ try {
916
+ linkSync(temporaryPath, path);
917
+ created = true;
918
+ }
919
+ catch (error) {
920
+ if (error.code !== "EEXIST")
921
+ throw error;
922
+ }
923
+ if (created) {
924
+ try {
925
+ unlinkSync(temporaryPath);
926
+ }
927
+ catch (error) {
928
+ if (error.code !== "ENOENT")
929
+ throw error;
930
+ }
931
+ temporaryPath = undefined;
932
+ }
933
+ syncPublicationDirectories(rootIdentity.path, createdDirectories, parent);
934
+ if (created)
935
+ afterDestinationPublish?.();
936
+ return { created, digest: digestExistingPath(path, relativePath) };
937
+ }
938
+ finally {
939
+ if (descriptor !== undefined)
940
+ closeSync(descriptor);
941
+ if (temporaryPath !== undefined) {
942
+ try {
943
+ unlinkSync(temporaryPath);
944
+ }
945
+ catch (error) {
946
+ if (error.code !== "ENOENT") {
947
+ // ponytail: a unique stale temp is harmless; add managed cleanup only if accumulation is observed.
948
+ }
949
+ }
950
+ }
951
+ if (ownsRoot)
952
+ closeBundleRoot(rootIdentity);
953
+ }
954
+ }
955
+ function loadValidators() {
956
+ const readSchema = (name) => JSON.parse(readFileSync(`${schemaDirectory}${name}`, "utf8"));
957
+ const ajv = new Ajv2020({ allErrors: true, strict: false });
958
+ addFormats(ajv);
959
+ ajv.addSchema(readSchema("task-packet.v1.schema.json"));
960
+ ajv.addSchema(readSchema("task-packet-freeze.v1.schema.json"));
961
+ ajv.addSchema(readSchema("experiment.v1.schema.json"));
962
+ ajv.addSchema(readSchema("run-queue.v1.schema.json"));
963
+ ajv.addSchema(readSchema("run-bundles/v1.json"));
964
+ ajv.addSchema(readSchema("uniform-events/v1.json"));
965
+ ajv.addSchema(readSchema("normalization-integrity.v1.json"));
966
+ ajv.addSchema(readSchema("structural-observations.v1.json"));
967
+ ajv.addSchema(readSchema("behavior-assertions.v1.json"));
968
+ ajv.addSchema(readSchema("semantic-judge.v1.json"));
969
+ ajv.addSchema(readSchema("human-calibration.v1.json"));
970
+ ajv.addSchema(readSchema("aggregation.v1.json"));
971
+ ajv.addSchema(readSchema("deepseek-runtime-composition.v1.schema.json"));
972
+ return new Map([
973
+ ["ebo.task-packet/v1", requiredValidator(ajv, "https://ebo.dev/schemas/task-packet.v1.schema.json")],
974
+ ["ebo.task-packet-freeze/v1", requiredValidator(ajv, "https://ebo.dev/schemas/task-packet-freeze.v1.schema.json")],
975
+ ["ebo.experiment/v1", requiredValidator(ajv, "https://ebo.dev/schemas/experiment.v1.schema.json")],
976
+ ["ebo.run-queue/v1", requiredValidator(ajv, "https://ebo.dev/schemas/run-queue.v1.schema.json")],
977
+ ["run-manifest/v1", requiredValidator(ajv, runBundleSchemaId)],
978
+ ["verifier-result/v1", requiredValidator(ajv, `${runBundleSchemaId}#/$defs/verifierResult`)],
979
+ ["capture-report/v1", requiredValidator(ajv, `${runBundleSchemaId}#/$defs/captureReport`)],
980
+ ["export-manifest/v1", requiredValidator(ajv, `${runBundleSchemaId}#/$defs/exportManifest`)],
981
+ ["ebo.uniform-event/v1", requiredValidator(ajv, "urn:ebo:schema:uniform-events:v1#/$defs/uniformEvent")],
982
+ ["ebo.adapter-capability-profile/v1", requiredValidator(ajv, "urn:ebo:schema:uniform-events:v1#/$defs/capabilityProfile")],
983
+ ["ebo.normalized-dataset/v1", requiredValidator(ajv, "urn:ebo:schema:normalization-integrity:v1#/$defs/normalizedDataset")],
984
+ ["ebo.adapter-coverage-report/v1", requiredValidator(ajv, "urn:ebo:schema:normalization-integrity:v1#/$defs/coverageReport")],
985
+ ["ebo.comparison-request/v1", requiredValidator(ajv, "urn:ebo:schema:normalization-integrity:v1#/$defs/comparisonRequest")],
986
+ ["ebo.comparison-report/v1", requiredValidator(ajv, "urn:ebo:schema:normalization-integrity:v1#/$defs/comparisonReport")],
987
+ ["ebo.comparison-request/v2", requiredValidator(ajv, "urn:ebo:schema:normalization-integrity:v1#/$defs/comparisonRequestV2")],
988
+ ["ebo.comparison-report/v2", requiredValidator(ajv, "urn:ebo:schema:normalization-integrity:v1#/$defs/comparisonReportV2")],
989
+ ["ebo.structural-observation/v1", requiredValidator(ajv, "urn:ebo:schema:structural-observations:v1#/$defs/observation")],
990
+ ["ebo.structural-observation-set/v1", requiredValidator(ajv, "urn:ebo:schema:structural-observations:v1#/$defs/set")],
991
+ ["ebo.behavior-vocabulary/v1", requiredValidator(ajv, "urn:ebo:schema:behavior-assertions:v1#/$defs/vocabulary")],
992
+ ["ebo.behavior-assertion/v1", requiredValidator(ajv, "urn:ebo:schema:behavior-assertions:v1#/$defs/assertion")],
993
+ ["ebo.behavior-review/v1", requiredValidator(ajv, "urn:ebo:schema:behavior-assertions:v1#/$defs/review")],
994
+ ["ebo.semantic-judge-request/v1", requiredValidator(ajv, "urn:ebo:schema:semantic-judge:v1#/$defs/request")],
995
+ ["ebo.semantic-judge-input/v1", requiredValidator(ajv, "urn:ebo:schema:semantic-judge:v1#/$defs/input")],
996
+ ["ebo.semantic-judgment/v1", requiredValidator(ajv, "urn:ebo:schema:semantic-judge:v1#/$defs/judgment")],
997
+ ["ebo.review-source-set/v1", requiredValidator(ajv, "urn:ebo:schema:human-calibration:v1#/$defs/sourceSet")],
998
+ ["ebo.review-sample-criteria/v1", requiredValidator(ajv, "urn:ebo:schema:human-calibration:v1#/$defs/criteria")],
999
+ ["ebo.review-sample/v1", requiredValidator(ajv, "urn:ebo:schema:human-calibration:v1#/$defs/sample")],
1000
+ ["ebo.review-packet/v1", requiredValidator(ajv, "urn:ebo:schema:human-calibration:v1#/$defs/packet")],
1001
+ ["ebo.human-review-decision/v1", requiredValidator(ajv, "urn:ebo:schema:human-calibration:v1#/$defs/decision")],
1002
+ ["ebo.review-history/v1", requiredValidator(ajv, "urn:ebo:schema:human-calibration:v1#/$defs/history")],
1003
+ ["ebo.calibration-summary/v1", requiredValidator(ajv, "urn:ebo:schema:human-calibration:v1#/$defs/summary")],
1004
+ ["ebo.aggregation-request/v1", requiredValidator(ajv, "urn:ebo:schema:aggregation:v1#/$defs/request")],
1005
+ ["ebo.aggregation-report/v1", requiredValidator(ajv, "urn:ebo:schema:aggregation:v1#/$defs/report")],
1006
+ ["ebo.deepseek-runtime-composition/v1", requiredValidator(ajv, "https://ebo.dev/schemas/deepseek-runtime-composition.v1.schema.json")],
1007
+ ]);
1008
+ }
1009
+ function requiredValidator(ajv, schemaId) {
1010
+ const validate = ajv.getSchema(schemaId);
1011
+ if (validate === undefined)
1012
+ throw new Error(`Missing bundled schema ${schemaId}.`);
1013
+ return validate;
1014
+ }
1015
+ function canonicalJson(value, ancestors) {
1016
+ if (value === null)
1017
+ return "null";
1018
+ if (typeof value === "string" || typeof value === "boolean")
1019
+ return JSON.stringify(value);
1020
+ if (typeof value === "number") {
1021
+ if (!Number.isFinite(value))
1022
+ throw new Error("Metadata must contain only finite JSON numbers.");
1023
+ return JSON.stringify(value);
1024
+ }
1025
+ if (Array.isArray(value)) {
1026
+ if (ancestors.has(value))
1027
+ throw new Error("Metadata must not contain cycles.");
1028
+ if (Object.keys(value).some((key) => !/^(?:0|[1-9][0-9]*)$/.test(key) || Number(key) >= value.length)) {
1029
+ throw new Error("Metadata arrays must not contain non-index properties.");
1030
+ }
1031
+ ancestors.add(value);
1032
+ try {
1033
+ const entries = [];
1034
+ for (let index = 0; index < value.length; index += 1) {
1035
+ if (!Object.hasOwn(value, index))
1036
+ throw new Error("Metadata arrays must not contain holes.");
1037
+ entries.push(canonicalJson(value[index], ancestors));
1038
+ }
1039
+ return `[${entries.join(",")}]`;
1040
+ }
1041
+ finally {
1042
+ ancestors.delete(value);
1043
+ }
1044
+ }
1045
+ if (!isRecord(value) || (Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null)
1046
+ || Object.getOwnPropertySymbols(value).length > 0) {
1047
+ throw new Error("Metadata must contain only JSON values.");
1048
+ }
1049
+ if (ancestors.has(value))
1050
+ throw new Error("Metadata must not contain cycles.");
1051
+ ancestors.add(value);
1052
+ try {
1053
+ return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${canonicalJson(value[key], ancestors)}`).join(",")}}`;
1054
+ }
1055
+ finally {
1056
+ ancestors.delete(value);
1057
+ }
1058
+ }
1059
+ function schemaVersionOf(document) {
1060
+ return isRecord(document) && typeof document.schemaVersion === "string" ? document.schemaVersion : "unknown";
1061
+ }
1062
+ function validationError(artifact, schemaVersion, error) {
1063
+ const missing = error.keyword === "required" && typeof error.params.missingProperty === "string"
1064
+ ? `/${escapeJsonPointer(error.params.missingProperty)}`
1065
+ : "";
1066
+ return {
1067
+ artifact,
1068
+ schemaVersion,
1069
+ field: `${error.instancePath}${missing}` || "/",
1070
+ message: error.message ?? error.keyword,
1071
+ };
1072
+ }
1073
+ function runManifestIntegrityErrors(artifact, schemaVersion, manifest) {
1074
+ const errors = [];
1075
+ const evidence = manifest.evidence;
1076
+ try {
1077
+ assertUniqueArtifactIdentities(evidence.map((entry) => ({
1078
+ id: isRecord(entry) && typeof entry.id === "string" ? entry.id : "",
1079
+ relativePath: isRecord(entry) && typeof entry.relativePath === "string" ? entry.relativePath : "",
1080
+ })));
1081
+ }
1082
+ catch (error) {
1083
+ errors.push({ artifact, schemaVersion, field: "/evidence", message: error instanceof Error ? error.message : "Duplicate evidence identity." });
1084
+ }
1085
+ const evidenceClasses = new Map();
1086
+ const evidenceById = new Map();
1087
+ for (const descriptor of evidence) {
1088
+ if (!isRecord(descriptor))
1089
+ continue;
1090
+ const { digest, id, kind, authority, relativePath } = descriptor;
1091
+ if (typeof id === "string")
1092
+ evidenceById.set(id, descriptor);
1093
+ if (typeof relativePath === "string" && (relativePath.toLowerCase() === "manifest.json" || relativePath.toLowerCase().startsWith("manifest.json/"))) {
1094
+ errors.push({ artifact, schemaVersion, field: "/evidence", message: "Evidence cannot reuse the containing manifest path." });
1095
+ }
1096
+ if (typeof digest !== "string" || typeof kind !== "string" || typeof authority !== "string")
1097
+ continue;
1098
+ const evidenceClass = `${kind}:${authority}`;
1099
+ const existing = evidenceClasses.get(digest);
1100
+ if (existing !== undefined && existing !== evidenceClass) {
1101
+ errors.push({ artifact, schemaVersion, field: "/evidence", message: `Evidence reuses ${digest} across evidence classes.` });
1102
+ }
1103
+ evidenceClasses.set(digest, evidenceClass);
1104
+ }
1105
+ for (const descriptor of evidenceById.values()) {
1106
+ validateSanitizedProvenance(artifact, schemaVersion, descriptor, evidenceById, errors);
1107
+ }
1108
+ if (isRecord(manifest.attempt) && manifest.attempt.retryOf === manifest.attempt.id) {
1109
+ errors.push({ artifact, schemaVersion, field: "/attempt/retryOf", message: "Retry lineage cannot reference the current attempt." });
1110
+ }
1111
+ const sourceEvidence = [...evidenceById.values()].filter((descriptor) => descriptor.sanitizedFrom === undefined);
1112
+ const run = isRecord(manifest.run) ? manifest.run : undefined;
1113
+ if (run !== undefined && isRecord(run.native)) {
1114
+ validateNativeReference(artifact, schemaVersion, "sessionId", "session", "session", run.native, sourceEvidence, errors);
1115
+ validateNativeReference(artifact, schemaVersion, "traceId", "telemetry", "trace", run.native, sourceEvidence, errors);
1116
+ }
1117
+ const harness = run !== undefined && isRecord(run.harness) ? run.harness : undefined;
1118
+ if (run !== undefined && harness !== undefined && Array.isArray(run.runtime)
1119
+ && !run.runtime.some((component) => isRecord(component)
1120
+ && component.version === harness.version
1121
+ && (component.name === harness.id || component.source === harness.id))) {
1122
+ errors.push({ artifact, schemaVersion, field: "/run/runtime", message: "Declared harness is not represented by its runtime composition." });
1123
+ }
1124
+ const terminal = isRecord(manifest.terminal) ? manifest.terminal : undefined;
1125
+ if (terminal !== undefined && typeof terminal.workspaceArtifactId === "string"
1126
+ && !sourceEvidence.some((descriptor) => descriptor.id === terminal.workspaceArtifactId && descriptor.kind === "workspace")) {
1127
+ errors.push({ artifact, schemaVersion, field: "/terminal/workspaceArtifactId", message: "Terminal workspace must reference retained workspace evidence." });
1128
+ }
1129
+ return errors;
1130
+ }
1131
+ function validateSanitizedProvenance(artifact, schemaVersion, descriptor, evidenceById, errors) {
1132
+ if (!isRecord(descriptor.sanitizedFrom) || typeof descriptor.id !== "string" || typeof descriptor.digest !== "string")
1133
+ return;
1134
+ const field = `/evidence/${escapeJsonPointer(descriptor.id)}/sanitizedFrom`;
1135
+ const shared = descriptor.sharingClass === "partner" || descriptor.sharingClass === "public";
1136
+ const seen = new Set([descriptor.id]);
1137
+ const sharedDigest = descriptor.digest;
1138
+ let current = descriptor;
1139
+ while (isRecord(current.sanitizedFrom)) {
1140
+ const { artifactId, digest } = current.sanitizedFrom;
1141
+ if (typeof artifactId !== "string" || typeof digest !== "string" || seen.has(artifactId)) {
1142
+ errors.push({ artifact, schemaVersion, field, message: "Sanitized provenance cannot contain a cycle." });
1143
+ return;
1144
+ }
1145
+ const source = evidenceById.get(artifactId);
1146
+ if (source === undefined) {
1147
+ errors.push({ artifact, schemaVersion, field, message: "Sanitized provenance must reference retained source evidence." });
1148
+ return;
1149
+ }
1150
+ const currentShared = current.sharingClass === "partner" || current.sharingClass === "public";
1151
+ const diagnosticDerivative = current.kind === "diagnostic" && source.kind === "verifier";
1152
+ if (["source", "authority"].some((key) => source[key] !== current[key])
1153
+ || (!diagnosticDerivative && source.kind !== current.kind)
1154
+ || (currentShared ? current.nativeReference !== undefined : !sameNativeReference(source.nativeReference, current.nativeReference))
1155
+ || source.digest !== digest || source.digest === current.digest || source.digest === sharedDigest || source.relativePath === current.relativePath) {
1156
+ errors.push({ artifact, schemaVersion, field, message: "Sanitized provenance does not preserve its source evidence." });
1157
+ return;
1158
+ }
1159
+ seen.add(artifactId);
1160
+ current = source;
1161
+ }
1162
+ if (shared && [...evidenceById.values()].some((candidate) => !seen.has(String(candidate.id)) && candidate.digest === sharedDigest)) {
1163
+ errors.push({ artifact, schemaVersion, field, message: "Sanitized shared evidence cannot reuse unrelated retained bytes." });
1164
+ }
1165
+ }
1166
+ function validateNativeReference(artifact, schemaVersion, field, kind, nativeType, native, sourceEvidence, errors) {
1167
+ if (typeof native[field] === "string" && !sourceEvidence.some((descriptor) => descriptor.kind === kind && isRecord(descriptor.nativeReference)
1168
+ && descriptor.nativeReference.type === nativeType && descriptor.nativeReference.id === native[field])) {
1169
+ errors.push({ artifact, schemaVersion, field: `/run/native/${field}`, message: `Declared ${field} does not match retained ${kind} evidence.` });
1170
+ }
1171
+ }
1172
+ function sameNativeReference(left, right) {
1173
+ if (left == null || right == null)
1174
+ return left == null && right == null;
1175
+ return isRecord(left) && isRecord(right) && left.type === right.type && left.id === right.id;
1176
+ }
1177
+ function runDigest(value) {
1178
+ return { algorithm: "sha256", value: value.slice("sha256:".length) };
1179
+ }
1180
+ function escapeJsonPointer(value) {
1181
+ return value.replaceAll("~", "~0").replaceAll("/", "~1");
1182
+ }
1183
+ async function resolveExistingArtifactPath(artifactRoot, relativePath) {
1184
+ const root = await realpath(artifactRoot);
1185
+ return { root, path: await assertExistingArtifactPath(root, relativePath) };
1186
+ }
1187
+ async function assertExistingArtifactPath(root, relativePath) {
1188
+ assertSafeArtifactPath(relativePath);
1189
+ let path = root;
1190
+ const segments = relativePath.split("/");
1191
+ for (const [index, segment] of segments.entries()) {
1192
+ path = resolve(path, segment);
1193
+ const entry = await lstat(path);
1194
+ if (!isContained(root, path) || entry.isSymbolicLink()) {
1195
+ throw new Error(`Artifact path "${relativePath}" escapes its declared root.`);
1196
+ }
1197
+ if (index === segments.length - 1 && !entry.isFile()) {
1198
+ throw new Error(`Artifact path "${relativePath}" is not an isolated regular file.`);
1199
+ }
1200
+ }
1201
+ return path;
1202
+ }
1203
+ async function prepareArtifactPath(artifactRoot, relativePath) {
1204
+ assertSafeArtifactPath(relativePath);
1205
+ const root = await realpath(artifactRoot);
1206
+ const segments = relativePath.split("/");
1207
+ let parent = root;
1208
+ for (const segment of segments.slice(0, -1)) {
1209
+ parent = resolve(parent, segment);
1210
+ if (!isContained(root, parent))
1211
+ throw new Error(`Artifact path "${relativePath}" escapes its declared root.`);
1212
+ await mkdir(parent).catch((error) => {
1213
+ if (error.code !== "EEXIST")
1214
+ throw error;
1215
+ });
1216
+ const entry = await lstat(parent);
1217
+ if (!entry.isDirectory() || entry.isSymbolicLink()) {
1218
+ throw new Error(`Artifact path "${relativePath}" crosses a symbolic link.`);
1219
+ }
1220
+ }
1221
+ const path = resolve(parent, segments.at(-1));
1222
+ try {
1223
+ const entry = await lstat(path);
1224
+ if (!entry.isFile() || entry.isSymbolicLink()) {
1225
+ throw new Error(`Artifact path "${relativePath}" is not an isolated regular file.`);
1226
+ }
1227
+ }
1228
+ catch (error) {
1229
+ if (error.code !== "ENOENT")
1230
+ throw error;
1231
+ }
1232
+ return { parent, path };
1233
+ }
1234
+ function prepareArtifactPathSync(artifactRoot, relativePath) {
1235
+ assertSafeArtifactPath(relativePath);
1236
+ const root = realpathSync(artifactRoot);
1237
+ const segments = relativePath.split("/");
1238
+ const createdDirectories = [];
1239
+ let parent = root;
1240
+ for (const segment of segments.slice(0, -1)) {
1241
+ parent = resolve(parent, segment);
1242
+ if (!isContained(root, parent))
1243
+ throw new Error(`Artifact path "${relativePath}" escapes its declared root.`);
1244
+ try {
1245
+ mkdirSync(parent, { mode: 0o700 });
1246
+ createdDirectories.push(parent);
1247
+ }
1248
+ catch (error) {
1249
+ if (error.code !== "EEXIST")
1250
+ throw error;
1251
+ }
1252
+ const entry = lstatSync(parent);
1253
+ if (!entry.isDirectory() || entry.isSymbolicLink()) {
1254
+ throw new Error(`Artifact path "${relativePath}" crosses a symbolic link.`);
1255
+ }
1256
+ }
1257
+ const path = resolve(parent, segments.at(-1));
1258
+ try {
1259
+ const entry = lstatSync(path);
1260
+ if (!entry.isFile() || entry.isSymbolicLink()) {
1261
+ throw new Error(`Artifact path "${relativePath}" is not an isolated regular file.`);
1262
+ }
1263
+ }
1264
+ catch (error) {
1265
+ if (error.code !== "ENOENT")
1266
+ throw error;
1267
+ }
1268
+ return { createdDirectories, parent, path };
1269
+ }
1270
+ function digestExistingPath(path, relativePath) {
1271
+ return inspectExistingPath(path, relativePath).digest;
1272
+ }
1273
+ function inspectExistingPath(path, relativePath) {
1274
+ const descriptor = openSync(path, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
1275
+ try {
1276
+ let opened = fstatSync(descriptor);
1277
+ if (opened.nlink > 1) {
1278
+ removeInterruptedPublicationLink(path, opened);
1279
+ opened = fstatSync(descriptor);
1280
+ }
1281
+ const openedTimes = fstatSync(descriptor, { bigint: true });
1282
+ if (!opened.isFile() || !isReadablePublishedFile(path, opened)
1283
+ || !Number.isSafeInteger(opened.size) || opened.size < 0) {
1284
+ throw new Error(`Artifact path "${relativePath}" is not an isolated regular file.`);
1285
+ }
1286
+ const hash = createHash("sha256");
1287
+ const chunk = Buffer.allocUnsafe(64 * 1024);
1288
+ for (let offset = 0; offset < opened.size;) {
1289
+ const read = readSync(descriptor, chunk, 0, Math.min(chunk.length, opened.size - offset), offset);
1290
+ if (read === 0)
1291
+ throw new Error(`Artifact path "${relativePath}" changed while it was being read.`);
1292
+ hash.update(chunk.subarray(0, read));
1293
+ offset += read;
1294
+ }
1295
+ const trailing = Buffer.allocUnsafe(1);
1296
+ if (readSync(descriptor, trailing, 0, 1, opened.size) !== 0) {
1297
+ throw new Error(`Artifact path "${relativePath}" changed while it was being read.`);
1298
+ }
1299
+ const completed = fstatSync(descriptor);
1300
+ const completedTimes = fstatSync(descriptor, { bigint: true });
1301
+ if (!completed.isFile() || !isReadablePublishedFile(path, completed)
1302
+ || !sameFileIdentity(opened, completed) || completed.size !== opened.size
1303
+ || completedTimes.mtimeNs !== openedTimes.mtimeNs || completedTimes.ctimeNs !== openedTimes.ctimeNs) {
1304
+ throw new Error(`Artifact path "${relativePath}" changed while it was being read.`);
1305
+ }
1306
+ return {
1307
+ digest: { algorithm: "sha256", value: hash.digest("hex") },
1308
+ sizeBytes: opened.size,
1309
+ };
1310
+ }
1311
+ finally {
1312
+ closeSync(descriptor);
1313
+ }
1314
+ }
1315
+ function isReadablePublishedFile(path, target) {
1316
+ try {
1317
+ const current = lstatSync(path);
1318
+ return target.nlink === 1 && current.nlink === 1
1319
+ && current.isFile() && !current.isSymbolicLink() && sameFileIdentity(current, target);
1320
+ }
1321
+ catch {
1322
+ return false;
1323
+ }
1324
+ }
1325
+ async function isReadablePublishedArtifact(path, target) {
1326
+ try {
1327
+ const current = await lstat(path);
1328
+ return target.nlink === 1 && current.nlink === 1
1329
+ && current.isFile() && !current.isSymbolicLink() && sameFileIdentity(current, target);
1330
+ }
1331
+ catch {
1332
+ return false;
1333
+ }
1334
+ }
1335
+ function sameFileIdentity(left, right) {
1336
+ return left.dev === right.dev && left.ino === right.ino;
1337
+ }
1338
+ function syncDirectorySync(path) {
1339
+ const descriptor = openSync(path, constants.O_RDONLY);
1340
+ try {
1341
+ fsyncSync(descriptor);
1342
+ }
1343
+ finally {
1344
+ closeSync(descriptor);
1345
+ }
1346
+ }
1347
+ function syncPublicationDirectories(root, createdDirectories, parent) {
1348
+ const directories = new Set([parent, ...createdDirectories.slice().reverse(), ...createdDirectories.map(dirname), root]);
1349
+ for (const directory of directories)
1350
+ syncDirectorySync(directory);
1351
+ }
1352
+ async function syncDirectory(path) {
1353
+ const handle = await open(path, constants.O_RDONLY);
1354
+ try {
1355
+ await handle.sync();
1356
+ }
1357
+ finally {
1358
+ await handle.close();
1359
+ }
1360
+ }
1361
+ function assertSafeArtifactPath(relativePath) {
1362
+ if (!isSafeArtifactRelativePath(relativePath)) {
1363
+ throw new Error(`Artifact path "${relativePath}" is unsafe.`);
1364
+ }
1365
+ }
1366
+ function isContained(root, path) {
1367
+ const fromRoot = relative(root, path);
1368
+ return fromRoot === "" || (!isAbsolute(fromRoot) && fromRoot !== ".." && !fromRoot.startsWith(`..${sep}`));
1369
+ }
1370
+ function isRecord(value) {
1371
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1372
+ }