@zhivex-ai/harness 1.2.0 → 1.3.0-rc.7

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 (164) hide show
  1. package/CHANGELOG.md +64 -0
  2. package/README.md +9 -10
  3. package/contracts/engine-api.json +2921 -0
  4. package/contracts/public-api.json +267 -1
  5. package/contracts/stable-api-signatures.json +43 -43
  6. package/dist/acp-cli.js +137 -15002
  7. package/dist/approvals/approval-history.d.ts +22 -0
  8. package/dist/approvals/explicit-review.d.ts +24 -0
  9. package/dist/approvals/host-policy-identity.d.ts +3 -0
  10. package/dist/cli/arguments.d.ts +6 -2
  11. package/dist/cli/checkpoints.d.ts +230 -0
  12. package/dist/cli/cli-options.d.ts +2 -41
  13. package/dist/cli/cli-profiles.d.ts +4 -2
  14. package/dist/cli/cli-stream.d.ts +2 -167
  15. package/dist/cli/governance-report.d.ts +5 -0
  16. package/dist/cli/policy-decisions.d.ts +9 -0
  17. package/dist/cli/policy.d.ts +5 -0
  18. package/dist/cli/presentation.d.ts +3 -1
  19. package/dist/cli/run-document.d.ts +2 -91
  20. package/dist/cli/terminal/terminal-continuation.d.ts +2 -3
  21. package/dist/cli/terminal/terminal-ui.d.ts +4 -0
  22. package/dist/{cli-entry-c2f58zj2.js → cli-entry-ae7x44d4.js} +14969 -10731
  23. package/dist/{cli-entry-427869g9.js → cli-entry-d5788aay.js} +388 -47
  24. package/dist/cli-entry-ev76ezcd.js +656 -0
  25. package/dist/{cli-3jmy4h5h.js → cli-t7hrhayq.js} +918 -2223
  26. package/dist/cli.js +3 -5
  27. package/dist/client/acp-mcp-admission.d.ts +32 -0
  28. package/dist/client/acp-mcp-host.d.ts +19 -0
  29. package/dist/client/acp-mcp-session.d.ts +14 -0
  30. package/dist/client/acp-stdio.d.ts +2 -0
  31. package/dist/client/acp.d.ts +4 -1
  32. package/dist/client/adapter.d.ts +13 -1
  33. package/dist/client/continuation.d.ts +3 -0
  34. package/dist/client/json-contracts.d.ts +107 -4
  35. package/dist/client/local-service.d.ts +2 -0
  36. package/dist/client/protocol.d.ts +127 -1
  37. package/dist/client/run-document.d.ts +91 -0
  38. package/dist/client/service-events.d.ts +4 -0
  39. package/dist/client/stream.d.ts +167 -0
  40. package/dist/compat/cli-options.d.ts +53 -0
  41. package/dist/compat/code-support.d.ts +22 -0
  42. package/dist/compat/code-support.js +74 -0
  43. package/dist/{config-1k9t4jhh.js → config-c002f06f.js} +1 -1
  44. package/dist/config-j3jga5wh.js +61 -0
  45. package/dist/context/adaptive-compaction.d.ts +1 -0
  46. package/dist/context/compaction.d.ts +1 -1
  47. package/dist/context/request-measurements.d.ts +17 -0
  48. package/dist/context/request-projection.d.ts +10 -0
  49. package/dist/context/task-memory.d.ts +1 -0
  50. package/dist/engine/acp.d.ts +6 -0
  51. package/dist/engine/acp.js +205 -0
  52. package/dist/engine/client.d.ts +6 -0
  53. package/dist/engine/client.js +28 -0
  54. package/dist/engine/desktop-providers-v1.d.ts +4 -0
  55. package/dist/engine/desktop-providers-v1.js +18 -0
  56. package/dist/engine/desktop-state-v1.d.ts +10 -0
  57. package/dist/engine/desktop-state-v1.js +168 -0
  58. package/dist/engine/index.d.ts +73 -0
  59. package/dist/engine/index.js +977 -0
  60. package/dist/engine/mcp-stdio-v1.d.ts +11 -0
  61. package/dist/engine/mcp-stdio-v1.js +108 -0
  62. package/dist/engine/models.d.ts +3 -0
  63. package/dist/engine/models.js +894 -0
  64. package/dist/engine/protocol.d.ts +7 -0
  65. package/dist/engine/protocol.js +182 -0
  66. package/dist/engine/service.d.ts +5 -0
  67. package/dist/engine/service.js +28 -0
  68. package/dist/execution/acceptance-snapshot.d.ts +2 -0
  69. package/dist/execution/execution-environment.d.ts +10 -0
  70. package/dist/execution/mcp-oci-channel.d.ts +11 -0
  71. package/dist/execution/mcp-oci-server.d.ts +38 -0
  72. package/dist/execution/mcp-oci-workspace.d.ts +23 -0
  73. package/dist/execution/mcp-resource-recovery.d.ts +19 -0
  74. package/dist/execution/mcp-workspace-snapshot.d.ts +20 -0
  75. package/dist/hostile-repository-demo.js +3368 -1304
  76. package/dist/index-1bx83sw4.js +4 -0
  77. package/dist/index-2gz7jv7g.js +549 -0
  78. package/dist/{cli-entry-b1mfvmxe.js → index-2jrdcj6b.js} +72 -115
  79. package/dist/index-3ah1f369.js +356 -0
  80. package/dist/index-42gp2hy8.js +1555 -0
  81. package/dist/index-4jw3tvy8.js +301 -0
  82. package/dist/index-8705zxpr.js +145 -0
  83. package/dist/index-8vm2m34s.js +2080 -0
  84. package/dist/index-8zzhggxv.js +477 -0
  85. package/dist/index-a97bezbz.js +1073 -0
  86. package/dist/index-gq9xagx2.js +1343 -0
  87. package/dist/index-h0mqx2ed.js +1373 -0
  88. package/dist/index-jgrx804e.js +5589 -0
  89. package/dist/index-qfpz5y2v.js +174 -0
  90. package/dist/index-t60rva31.js +1004 -0
  91. package/dist/index-wg1sd3at.js +80 -0
  92. package/dist/index-xqxeypt5.js +888 -0
  93. package/dist/index-yb239ghk.js +961 -0
  94. package/dist/index-z0qxmkm2.js +3389 -0
  95. package/dist/index-z4e8d0ph.js +1768 -0
  96. package/dist/index.js +251 -18649
  97. package/dist/integrations/mcp-host-session.d.ts +26 -0
  98. package/dist/integrations/mcp-stdio-admission.d.ts +54 -0
  99. package/dist/integrations/mcp-stdio-client.d.ts +44 -0
  100. package/dist/integrations/mcp-stdio-framing.d.ts +18 -0
  101. package/dist/integrations/mcp-stdio-launch.d.ts +20 -0
  102. package/dist/integrations/mcp-stdio-session.d.ts +7 -0
  103. package/dist/integrations/mcp-stdio-tools.d.ts +12 -0
  104. package/dist/internal/desktop/catalog.d.ts +2 -0
  105. package/dist/internal/desktop/persistence.d.ts +2 -3
  106. package/dist/internal/desktop/providers.d.ts +1 -0
  107. package/dist/models/catalog.d.ts +2 -0
  108. package/dist/persistence/governance-context.d.ts +126 -0
  109. package/dist/persistence/governance-report.d.ts +299 -0
  110. package/dist/persistence/host-database-backup.d.ts +28 -0
  111. package/dist/persistence/mcp-execution-journal.d.ts +19 -0
  112. package/dist/persistence/mcp-reconciliation.d.ts +33 -0
  113. package/dist/persistence/mcp-resource-journal.d.ts +54 -0
  114. package/dist/persistence/workspace-checkpoints.d.ts +138 -0
  115. package/dist/providers/anthropic-continuation.d.ts +5 -0
  116. package/dist/providers/providers.d.ts +1 -1
  117. package/dist/providers/vertex-auth.d.ts +27 -0
  118. package/dist/runtime/delegation-contracts.d.ts +9 -0
  119. package/dist/runtime/delegation-correction.d.ts +9 -0
  120. package/dist/runtime/delegation-result.d.ts +73 -0
  121. package/dist/runtime/error-diagnostics.d.ts +86 -0
  122. package/dist/runtime/harness.d.ts +11 -1
  123. package/dist/runtime/policy-decisions.d.ts +66 -0
  124. package/dist/runtime/policy-inspection.d.ts +52 -0
  125. package/dist/runtime/provider-diagnostics.d.ts +1 -0
  126. package/dist/runtime/runtime-instructions.d.ts +8 -0
  127. package/dist/runtime/task-acceptance-checks.d.ts +103 -0
  128. package/dist/runtime/task-acceptance-delivery.d.ts +107 -0
  129. package/dist/runtime/task-acceptance-host.d.ts +20 -0
  130. package/dist/runtime/task-acceptance-record.d.ts +107 -0
  131. package/dist/runtime/task-acceptance-validation.d.ts +25 -0
  132. package/dist/runtime/task-acceptance.d.ts +47 -0
  133. package/dist/runtime/time-to-safe-fix.d.ts +83 -0
  134. package/dist/runtime/tool-policy-file.d.ts +23 -0
  135. package/dist/runtime/tool-policy-paths.d.ts +5 -0
  136. package/dist/runtime/tool-policy.d.ts +6 -0
  137. package/dist/service-cli.js +19 -14504
  138. package/dist/{service-client-cli-yx56n0hc.js → service-client-cli-kns4mf57.js} +307 -248
  139. package/dist/verify-historical-migrations.js +3314 -1251
  140. package/dist/workspace/path-policy.d.ts +4 -0
  141. package/dist/zhx.js +3 -5
  142. package/docs/ACP.md +14 -2
  143. package/docs/ANTHROPIC.md +33 -0
  144. package/docs/CLI.md +132 -0
  145. package/docs/CLIENT_PROTOCOL.md +45 -0
  146. package/docs/CODE_RELEASE.md +93 -0
  147. package/docs/ENGINE_API.md +319 -0
  148. package/docs/EXTENSIBILITY.md +46 -3
  149. package/docs/FIRST_USE.md +6 -6
  150. package/docs/LOCAL_SERVICE.md +13 -0
  151. package/docs/MCP_STDIO.md +20 -0
  152. package/docs/README.md +6 -0
  153. package/docs/STABILITY.md +9 -1
  154. package/docs/SUPPORT_MATRIX.md +1 -1
  155. package/docs/TOOL_POLICY.md +81 -0
  156. package/docs/VERTEX.md +50 -0
  157. package/docs/adr/0002-isolated-mcp-stdio.md +135 -0
  158. package/docs/fixtures/mcp-stdio-contract-v1.json +189 -0
  159. package/docs/support-matrix.json +2 -0
  160. package/evaluations/representative-assembly-matrix.json +64 -1
  161. package/evaluations/task-acceptance-routes.json +7 -0
  162. package/examples/README.md +1 -1
  163. package/package.json +74 -12
  164. package/dist/cli-entry-64pdp3k5.js +0 -17
@@ -0,0 +1,961 @@
1
+ // src/workspace/edit-contracts.ts
2
+ import { createHash } from "node:crypto";
3
+ import path from "node:path";
4
+ import { z } from "zod";
5
+ var EDIT_CONTRACT_SCHEMA_VERSION = 1;
6
+ var EDIT_DIGEST_ALGORITHM = "sha256";
7
+ var MAX_EDIT_CHANGES = 50;
8
+ var MAX_EDIT_FILE_BYTES = 1024 * 1024;
9
+ var MAX_EDIT_PROPOSAL_BYTES = 4 * 1024 * 1024;
10
+ var SHA256_DIGEST_PATTERN = /^sha256:[a-f0-9]{64}$/;
11
+ var fileDigestSchema = z.string().regex(SHA256_DIGEST_PATTERN, "Digest must use the format sha256:<64 lowercase hexadecimal characters>.");
12
+ var expectedEditDigestSchema = fileDigestSchema.nullable().describe("Required. Use the inspected sha256 digest for an existing file, or explicit JSON null for a create-only target. Never omit this field.");
13
+ var workspaceFilePathSchema = z.string().min(1).max(1024).refine((value) => !value.includes("\x00"), "Path cannot contain a NUL byte.").refine((value) => !path.isAbsolute(value) && !/^[A-Za-z]:[\\/]/.test(value), "Path must be relative.").refine((value) => {
14
+ const segments = value.split(/[\\/]+/);
15
+ return segments.every((segment) => segment !== "" && segment !== "." && segment !== "..");
16
+ }, "Path must be normalized and cannot contain empty, dot, or parent segments.");
17
+ var editChangeSchema = z.strictObject({
18
+ path: workspaceFilePathSchema,
19
+ expectedDigest: expectedEditDigestSchema,
20
+ content: z.string()
21
+ }).superRefine((change, context) => {
22
+ if (Buffer.byteLength(change.content, "utf8") > MAX_EDIT_FILE_BYTES) {
23
+ context.addIssue({
24
+ code: "custom",
25
+ path: ["content"],
26
+ message: `Content exceeds the ${MAX_EDIT_FILE_BYTES}-byte per-file limit.`
27
+ });
28
+ }
29
+ });
30
+ var editChangesSchema = z.array(editChangeSchema).min(1).max(MAX_EDIT_CHANGES).superRefine((changes, context) => {
31
+ const paths = new Set;
32
+ let totalBytes = 0;
33
+ for (let index = 0;index < changes.length; index += 1) {
34
+ const change = changes[index];
35
+ if (!change) {
36
+ continue;
37
+ }
38
+ if (paths.has(change.path)) {
39
+ context.addIssue({
40
+ code: "custom",
41
+ path: [index, "path"],
42
+ message: `Duplicate target path: ${change.path}.`
43
+ });
44
+ }
45
+ paths.add(change.path);
46
+ totalBytes += Buffer.byteLength(change.content, "utf8");
47
+ }
48
+ if (totalBytes > MAX_EDIT_PROPOSAL_BYTES) {
49
+ context.addIssue({
50
+ code: "custom",
51
+ message: `Proposal exceeds the ${MAX_EDIT_PROPOSAL_BYTES}-byte aggregate limit.`
52
+ });
53
+ }
54
+ });
55
+ var editProposalInputSchema = z.strictObject({
56
+ changes: editChangesSchema
57
+ });
58
+ var editProposalChangeSchema = z.strictObject({
59
+ path: workspaceFilePathSchema,
60
+ expectedDigest: expectedEditDigestSchema,
61
+ contentDigest: fileDigestSchema,
62
+ bytes: z.number().int().min(0).max(MAX_EDIT_FILE_BYTES)
63
+ });
64
+ var editProposalSchema = z.strictObject({
65
+ schemaVersion: z.literal(EDIT_CONTRACT_SCHEMA_VERSION),
66
+ kind: z.literal("edit-proposal"),
67
+ proposalId: fileDigestSchema,
68
+ digestAlgorithm: z.literal(EDIT_DIGEST_ALGORITHM),
69
+ changes: z.array(editProposalChangeSchema).min(1).max(MAX_EDIT_CHANGES)
70
+ });
71
+ var applyEditProposalInputSchema = z.strictObject({
72
+ proposalId: fileDigestSchema,
73
+ changes: editChangesSchema
74
+ }).superRefine((input, context) => {
75
+ if (!editProposalInputSchema.safeParse({ changes: input.changes }).success)
76
+ return;
77
+ if (createEditProposal({ changes: input.changes }).proposalId !== input.proposalId) {
78
+ context.addIssue({
79
+ code: "custom",
80
+ path: ["proposalId"],
81
+ message: "proposalId does not match the supplied paths, preconditions, and content. Read proposalId from the matching propose_edits result; do not invent it."
82
+ });
83
+ }
84
+ });
85
+ var moveFileInputSchema = z.strictObject({
86
+ source: workspaceFilePathSchema,
87
+ destination: workspaceFilePathSchema,
88
+ expectedDigest: fileDigestSchema
89
+ }).refine((input) => input.source !== input.destination, {
90
+ path: ["destination"],
91
+ message: "Move destination must differ from source."
92
+ });
93
+ var quarantineFileInputSchema = z.strictObject({
94
+ path: workspaceFilePathSchema,
95
+ expectedDigest: fileDigestSchema
96
+ });
97
+ var restoreFileInputSchema = z.strictObject({
98
+ quarantineId: z.string().min(1).max(200).regex(/^[A-Za-z0-9._:-]+$/),
99
+ destination: workspaceFilePathSchema.optional(),
100
+ expectedDigest: fileDigestSchema.optional()
101
+ });
102
+ var mutationOperationSchema = z.enum(["create", "update", "move", "quarantine", "restore"]);
103
+ var mutationAuditEntrySchema = z.strictObject({
104
+ id: z.string().min(1).max(200),
105
+ operation: mutationOperationSchema,
106
+ path: workspaceFilePathSchema,
107
+ destination: workspaceFilePathSchema.optional(),
108
+ beforeDigest: fileDigestSchema.optional(),
109
+ afterDigest: fileDigestSchema.optional(),
110
+ timestamp: z.iso.datetime(),
111
+ quarantineId: z.string().min(1).max(200).optional()
112
+ });
113
+ var digest = (value) => `sha256:${createHash("sha256").update(value).digest("hex")}`;
114
+ var canonicalProposalPayload = (changes) => ({
115
+ schemaVersion: EDIT_CONTRACT_SCHEMA_VERSION,
116
+ kind: "edit-proposal",
117
+ digestAlgorithm: EDIT_DIGEST_ALGORITHM,
118
+ changes: [...changes].sort((left, right) => left.path < right.path ? -1 : left.path > right.path ? 1 : 0).map((change) => ({
119
+ path: change.path,
120
+ expectedDigest: change.expectedDigest,
121
+ contentDigest: digest(change.content),
122
+ bytes: Buffer.byteLength(change.content, "utf8")
123
+ }))
124
+ });
125
+ var createEditProposal = (input) => {
126
+ const parsed = editProposalInputSchema.parse(input);
127
+ const payload = canonicalProposalPayload(parsed.changes);
128
+ return editProposalSchema.parse({
129
+ ...payload,
130
+ proposalId: digest(JSON.stringify(payload))
131
+ });
132
+ };
133
+ var validateEditProposal = (input) => {
134
+ const parsed = applyEditProposalInputSchema.parse(input);
135
+ const proposal = createEditProposal({ changes: parsed.changes });
136
+ if (proposal.proposalId !== parsed.proposalId) {
137
+ throw new Error("proposalId does not match the supplied paths, preconditions, and content.");
138
+ }
139
+ return parsed;
140
+ };
141
+ var editContractDocument = (kind, result) => ({
142
+ schemaVersion: EDIT_CONTRACT_SCHEMA_VERSION,
143
+ kind,
144
+ result
145
+ });
146
+
147
+ // src/persistence/persistence-secrets.ts
148
+ var protectedValues = new Set;
149
+ function protectPersistenceSecret(secret) {
150
+ if (!secret)
151
+ return;
152
+ protectedValues.add(secret);
153
+ protectedValues.add(JSON.stringify(secret).slice(1, -1));
154
+ }
155
+ function assertNoPersistenceSecret(value) {
156
+ if (!protectedValues.size)
157
+ return;
158
+ const seen = new WeakSet;
159
+ const visit = (item) => {
160
+ if (typeof item === "string") {
161
+ for (const secret of protectedValues)
162
+ if (item.includes(secret))
163
+ throw new Error("PERSISTENCE_SECRET_REJECTED");
164
+ } else if (item instanceof Uint8Array) {
165
+ const bytes = Buffer.from(item.buffer, item.byteOffset, item.byteLength);
166
+ for (const secret of protectedValues)
167
+ if (bytes.includes(Buffer.from(secret)))
168
+ throw new Error("PERSISTENCE_SECRET_REJECTED");
169
+ } else if (item && typeof item === "object" && !seen.has(item)) {
170
+ seen.add(item);
171
+ for (const [key, entry] of Object.entries(item)) {
172
+ visit(key);
173
+ visit(entry);
174
+ }
175
+ }
176
+ };
177
+ visit(value);
178
+ }
179
+
180
+ // src/persistence/sqlite-access.ts
181
+ import { closeSync, constants, fchmodSync, fstatSync, lstatSync, openSync, realpathSync } from "node:fs";
182
+ import path2 from "node:path";
183
+ var O_SHLOCK = 16;
184
+ var O_EXLOCK = 32;
185
+ var held = new WeakMap;
186
+ var activeDescriptors = new Set;
187
+ function filename(databasePath) {
188
+ const directory = realpathSync(path2.dirname(path2.resolve(databasePath)));
189
+ return path2.join(directory, `.${path2.basename(databasePath)}.access-lock`);
190
+ }
191
+ function releaseDescriptor(state) {
192
+ closeSync(state.fd);
193
+ activeDescriptors.delete(state.fd);
194
+ }
195
+ function makeLease(lock, fd, exclusive) {
196
+ const state = { filename: lock, fd, closed: false, borrowers: 0, exclusive };
197
+ const lease = Object.freeze({ fd, close() {
198
+ if (!state.closed) {
199
+ state.closed = true;
200
+ if (state.borrowers === 0)
201
+ releaseDescriptor(state);
202
+ }
203
+ } });
204
+ held.set(lease, state);
205
+ activeDescriptors.add(fd);
206
+ return lease;
207
+ }
208
+ function acquireSqliteAccess(databasePath, exclusive = false) {
209
+ if (databasePath === ":memory:" || process.platform !== "darwin")
210
+ return;
211
+ let fd;
212
+ try {
213
+ const lock = filename(databasePath);
214
+ fd = openSync(lock, constants.O_RDWR | constants.O_CREAT | constants.O_NOFOLLOW | constants.O_NONBLOCK | (exclusive ? O_EXLOCK : O_SHLOCK), 384);
215
+ const info = fstatSync(fd);
216
+ const current = lstatSync(lock);
217
+ if (!info.isFile() || info.nlink !== 1 || info.uid !== process.getuid?.() || info.size !== 0 || current.isSymbolicLink() || current.dev !== info.dev || current.ino !== info.ino)
218
+ throw new Error;
219
+ if (info.mode & 63)
220
+ fchmodSync(fd, 384);
221
+ if (fstatSync(fd).mode & 63)
222
+ throw new Error;
223
+ return makeLease(lock, fd, exclusive);
224
+ } catch {
225
+ if (fd !== undefined)
226
+ closeSync(fd);
227
+ throw new Error("SQLITE_ACCESS_UNAVAILABLE");
228
+ }
229
+ }
230
+ function retainSqliteAccess(lease, databasePath) {
231
+ validateSqliteAccess(lease, databasePath);
232
+ const state = held.get(lease);
233
+ state.borrowers++;
234
+ let released = false;
235
+ return () => {
236
+ if (!released) {
237
+ released = true;
238
+ state.borrowers--;
239
+ if (state.closed && state.borrowers === 0)
240
+ releaseDescriptor(state);
241
+ }
242
+ };
243
+ }
244
+ function exclusiveSqliteAccessDescriptor(lease, databasePath) {
245
+ validateSqliteAccess(lease, databasePath);
246
+ const state = held.get(lease);
247
+ if (!state.exclusive || state.borrowers !== 0)
248
+ throw new Error("SQLITE_ACCESS_UNAVAILABLE");
249
+ return state.fd;
250
+ }
251
+ function adoptExclusiveSqliteAccess(databasePath, fd) {
252
+ try {
253
+ if (process.platform !== "darwin" || !Number.isInteger(fd) || fd < 4 || fd > 603 || activeDescriptors.has(fd))
254
+ throw new Error;
255
+ const lock = filename(databasePath), current = lstatSync(lock), owned = fstatSync(fd);
256
+ if (!owned.isFile() || owned.nlink !== 1 || owned.uid !== process.getuid?.() || owned.mode & 63 || owned.size !== 0 || current.isSymbolicLink() || current.dev !== owned.dev || current.ino !== owned.ino)
257
+ throw new Error;
258
+ return makeLease(lock, fd, true);
259
+ } catch {
260
+ throw new Error("SQLITE_ACCESS_UNAVAILABLE");
261
+ }
262
+ }
263
+ function validateSqliteAccess(lease, databasePath) {
264
+ try {
265
+ const state = held.get(lease);
266
+ if (!state || state.closed || state.filename !== filename(databasePath))
267
+ throw new Error;
268
+ const current = lstatSync(state.filename), owned = fstatSync(state.fd);
269
+ if (current.isSymbolicLink() || !current.isFile() || current.nlink !== 1 || current.dev !== owned.dev || current.ino !== owned.ino)
270
+ throw new Error;
271
+ } catch {
272
+ throw new Error("SQLITE_ACCESS_UNAVAILABLE");
273
+ }
274
+ }
275
+
276
+ // src/persistence/sqlite-database.ts
277
+ import { createRequire } from "node:module";
278
+ var require2 = createRequire(process.execPath);
279
+ var DatabaseSyncConstructor;
280
+ var loadDatabaseSync = () => {
281
+ DatabaseSyncConstructor ??= require2("node:sqlite").DatabaseSync;
282
+ return DatabaseSyncConstructor;
283
+ };
284
+ var normalizeNumberedParameters = (sql) => {
285
+ const indexes = new Set;
286
+ const normalizedSql = sql.replace(/\?([1-9]\d*)/g, (_match, rawIndex) => {
287
+ const index = Number(rawIndex);
288
+ indexes.add(index);
289
+ return `$__zhivex_${index}`;
290
+ });
291
+ return {
292
+ sql: normalizedSql,
293
+ indexes: [...indexes].sort((left, right) => left - right)
294
+ };
295
+ };
296
+ var numberedBindings = (indexes, parameters) => {
297
+ if (indexes.length === 0)
298
+ return;
299
+ const maximumIndex = indexes.at(-1);
300
+ if (parameters.length !== maximumIndex) {
301
+ throw new Error(`SQLite numbered parameters require exactly ${maximumIndex} binding${maximumIndex === 1 ? "" : "s"}; received ${parameters.length}.`);
302
+ }
303
+ return Object.fromEntries(indexes.map((index) => [`$__zhivex_${index}`, parameters[index - 1]]));
304
+ };
305
+
306
+ class SqliteDatabase {
307
+ #database;
308
+ #access;
309
+ #ownedAccess;
310
+ #releaseBorrowed;
311
+ #databasePath;
312
+ constructor(databasePath, options = {}) {
313
+ options.create;
314
+ options.strict;
315
+ if (options.accessLease)
316
+ validateSqliteAccess(options.accessLease, databasePath);
317
+ this.#access = options.accessLease ?? acquireSqliteAccess(databasePath);
318
+ this.#ownedAccess = options.accessLease === undefined;
319
+ this.#databasePath = databasePath;
320
+ this.#releaseBorrowed = options.accessLease ? retainSqliteAccess(options.accessLease, databasePath) : undefined;
321
+ try {
322
+ const DatabaseSync = loadDatabaseSync();
323
+ this.#database = new DatabaseSync(databasePath, {
324
+ ...options.readonly === undefined ? {} : { readOnly: options.readonly }
325
+ });
326
+ } catch (error) {
327
+ if (this.#ownedAccess)
328
+ this.#access?.close();
329
+ this.#releaseBorrowed?.();
330
+ throw error;
331
+ }
332
+ }
333
+ exec(sql) {
334
+ if (this.#access)
335
+ validateSqliteAccess(this.#access, this.#databasePath);
336
+ assertNoPersistenceSecret(sql);
337
+ this.#database.exec(sql);
338
+ }
339
+ query(sql) {
340
+ assertNoPersistenceSecret(sql);
341
+ if (this.#access)
342
+ validateSqliteAccess(this.#access, this.#databasePath);
343
+ const normalized = normalizeNumberedParameters(sql);
344
+ const statement = this.#database.prepare(normalized.sql);
345
+ const invoke = (method, parameters) => {
346
+ if (this.#access)
347
+ validateSqliteAccess(this.#access, this.#databasePath);
348
+ assertNoPersistenceSecret(sql);
349
+ assertNoPersistenceSecret(parameters);
350
+ const bindings = numberedBindings(normalized.indexes, parameters);
351
+ return bindings ? method(bindings) : method(...parameters);
352
+ };
353
+ return {
354
+ run: (...parameters) => invoke(statement.run.bind(statement), parameters),
355
+ get: (...parameters) => invoke(statement.get.bind(statement), parameters),
356
+ all: (...parameters) => invoke(statement.all.bind(statement), parameters)
357
+ };
358
+ }
359
+ close(throwOnError = true) {
360
+ try {
361
+ this.#database.close();
362
+ if (this.#ownedAccess)
363
+ this.#access?.close();
364
+ this.#releaseBorrowed?.();
365
+ } catch (error) {
366
+ if (throwOnError) {
367
+ throw error;
368
+ }
369
+ }
370
+ }
371
+ }
372
+
373
+ // src/workspace/file-security.ts
374
+ import { constants as fsConstants } from "node:fs";
375
+ import { open } from "node:fs/promises";
376
+ import path3 from "node:path";
377
+ var sameOpenFile = (before, after) => before.dev === after.dev && before.ino === after.ino && before.size === after.size && before.mtimeMs === after.mtimeMs && before.ctimeMs === after.ctimeMs && before.mode === after.mode && before.nlink === after.nlink;
378
+
379
+ class UnsafeFileTypeError extends Error {
380
+ constructor(label) {
381
+ super(`${label} must be a regular file and must not be a symbolic link.`);
382
+ this.name = "UnsafeFileTypeError";
383
+ }
384
+ }
385
+
386
+ class FileSizeLimitError extends Error {
387
+ label;
388
+ maxBytes;
389
+ constructor(label, maxBytes) {
390
+ super(`${label} exceeds the ${maxBytes}-byte limit.`);
391
+ this.label = label;
392
+ this.maxBytes = maxBytes;
393
+ this.name = "FileSizeLimitError";
394
+ }
395
+ }
396
+
397
+ class FileChangedWhileReadingError extends Error {
398
+ constructor(label) {
399
+ super(`${label} changed while it was being read.`);
400
+ this.name = "FileChangedWhileReadingError";
401
+ }
402
+ }
403
+ var invalidFileError = (label) => new UnsafeFileTypeError(label);
404
+ var openRegularFileNoFollow = async (target, label) => {
405
+ try {
406
+ const flags = fsConstants.O_RDONLY | fsConstants.O_NOFOLLOW | fsConstants.O_NONBLOCK;
407
+ let absolute = path3.resolve(target);
408
+ if (process.platform === "darwin") {
409
+ absolute = absolute.replace(/^\/(tmp|var|etc)(\/|$)/, "/private/$1$2");
410
+ return await open(absolute, fsConstants.O_RDONLY | fsConstants.O_NONBLOCK | 536870912, 384);
411
+ }
412
+ if (process.platform !== "linux")
413
+ throw new Error("Safe descriptor-relative reads require Linux or macOS.");
414
+ const segments = absolute.split("/").filter(Boolean);
415
+ const leaf = segments.pop();
416
+ if (!leaf)
417
+ throw invalidFileError(label);
418
+ let directory = await open("/", fsConstants.O_RDONLY | fsConstants.O_DIRECTORY);
419
+ try {
420
+ for (const segment of segments) {
421
+ const next = await open(`/proc/self/fd/${directory.fd}/${segment}`, fsConstants.O_RDONLY | fsConstants.O_DIRECTORY | fsConstants.O_NOFOLLOW);
422
+ await directory.close();
423
+ directory = next;
424
+ }
425
+ return await open(`/proc/self/fd/${directory.fd}/${leaf}`, flags);
426
+ } finally {
427
+ await directory.close();
428
+ }
429
+ } catch (error) {
430
+ const code = error.code;
431
+ if (code === "ELOOP" || code === "EMLINK" || code === "ENOTDIR")
432
+ throw invalidFileError(label);
433
+ throw error;
434
+ }
435
+ };
436
+ var statRegularFileNoFollow = async (target, options) => {
437
+ const handle = await openRegularFileNoFollow(target, options.label);
438
+ try {
439
+ const stat = await handle.stat();
440
+ if (!stat.isFile() || options.requireSingleLink && stat.nlink !== 1) {
441
+ throw invalidFileError(options.label);
442
+ }
443
+ return stat;
444
+ } finally {
445
+ await handle.close();
446
+ }
447
+ };
448
+ var readRegularFileNoFollow = async (target, options) => {
449
+ const handle = await openRegularFileNoFollow(target, options.label);
450
+ try {
451
+ const before = await handle.stat();
452
+ if (!before.isFile() || options.requireSingleLink && before.nlink !== 1) {
453
+ throw invalidFileError(options.label);
454
+ }
455
+ if (!Number.isSafeInteger(before.size) || before.size < 0) {
456
+ throw new Error(`${options.label} has an unsupported size.`);
457
+ }
458
+ if (!Number.isSafeInteger(options.maxBytes) || options.maxBytes < 0) {
459
+ throw new Error(`${options.label} has an invalid byte limit.`);
460
+ }
461
+ if (before.size > options.maxBytes) {
462
+ throw new FileSizeLimitError(options.label, options.maxBytes);
463
+ }
464
+ const contents = Buffer.alloc(before.size);
465
+ let offset = 0;
466
+ while (offset < contents.byteLength) {
467
+ const { bytesRead } = await handle.read(contents, offset, contents.byteLength - offset, offset);
468
+ if (bytesRead === 0)
469
+ break;
470
+ offset += bytesRead;
471
+ }
472
+ const probe = Buffer.allocUnsafe(1);
473
+ const { bytesRead: extraBytes } = await handle.read(probe, 0, 1, before.size);
474
+ const after = await handle.stat();
475
+ if (offset !== before.size || extraBytes !== 0 || !sameOpenFile(before, after) || options.requireSingleLink && after.nlink !== 1) {
476
+ throw new FileChangedWhileReadingError(options.label);
477
+ }
478
+ return { contents, stat: after };
479
+ } finally {
480
+ await handle.close();
481
+ }
482
+ };
483
+ async function* readRegularFileChunksNoFollow(target, options) {
484
+ const handle = await openRegularFileNoFollow(target, options.label);
485
+ try {
486
+ const before = await handle.stat();
487
+ if (!before.isFile() || options.requireSingleLink && before.nlink !== 1)
488
+ throw invalidFileError(options.label);
489
+ if (options.requirePrivate && (before.mode & 63) !== 0)
490
+ throw new Error(`${options.label} permissions must not grant group or other access.`);
491
+ let offset = 0;
492
+ while (offset < before.size) {
493
+ const buffer = Buffer.allocUnsafe(Math.min(64 * 1024, before.size - offset));
494
+ const { bytesRead: bytesRead2 } = await handle.read(buffer, 0, buffer.length, offset);
495
+ if (!bytesRead2)
496
+ throw new FileChangedWhileReadingError(options.label);
497
+ offset += bytesRead2;
498
+ yield buffer.subarray(0, bytesRead2);
499
+ }
500
+ const probe = Buffer.allocUnsafe(1);
501
+ const { bytesRead } = await handle.read(probe, 0, 1, offset);
502
+ if (bytesRead || !sameOpenFile(before, await handle.stat()))
503
+ throw new FileChangedWhileReadingError(options.label);
504
+ } finally {
505
+ await handle.close();
506
+ }
507
+ }
508
+
509
+ // src/execution/package-manager.ts
510
+ import { lstat } from "node:fs/promises";
511
+ import path4 from "node:path";
512
+ var HARNESS_PACKAGE_MANAGERS = ["npm", "pnpm", "yarn", "bun"];
513
+ var LOCKFILES = {
514
+ npm: ["package-lock.json", "npm-shrinkwrap.json"],
515
+ pnpm: ["pnpm-lock.yaml"],
516
+ yarn: ["yarn.lock"],
517
+ bun: ["bun.lock", "bun.lockb"]
518
+ };
519
+ var isPackageManager = (value) => HARNESS_PACKAGE_MANAGERS.includes(value);
520
+ var parsePackageManager = (value) => {
521
+ if (value === undefined)
522
+ return;
523
+ if (typeof value !== "string") {
524
+ throw new Error("package.json packageManager must be a string when present.");
525
+ }
526
+ const match = /^(npm|pnpm|yarn|bun)@([^\s]+)$/.exec(value.trim());
527
+ if (!match?.[1] || !match[2]) {
528
+ throw new Error("package.json packageManager must pin npm, pnpm, yarn, or bun using <manager>@<version>.");
529
+ }
530
+ if (!isPackageManager(match[1])) {
531
+ throw new Error(`Unsupported package manager: ${match[1]}.`);
532
+ }
533
+ return match[1];
534
+ };
535
+ var existingLockfiles = async (root) => {
536
+ const matches = [];
537
+ for (const manager of HARNESS_PACKAGE_MANAGERS) {
538
+ for (const file of LOCKFILES[manager]) {
539
+ try {
540
+ const entry = await lstat(path4.join(root, file));
541
+ if (entry.isSymbolicLink()) {
542
+ throw new Error(`Package-manager lockfile must not be a symbolic link: ${file}.`);
543
+ }
544
+ if (entry.isFile())
545
+ matches.push({ manager, file });
546
+ } catch (error) {
547
+ if (error.code !== "ENOENT")
548
+ throw error;
549
+ }
550
+ }
551
+ }
552
+ return matches;
553
+ };
554
+ var resolvePackageManager = async (root, manifest) => {
555
+ const declared = parsePackageManager(manifest.packageManager);
556
+ if (declared) {
557
+ return {
558
+ manager: declared,
559
+ source: "packageManager",
560
+ evidence: manifest.packageManager
561
+ };
562
+ }
563
+ const lockfiles = await existingLockfiles(root);
564
+ const managers = [...new Set(lockfiles.map((entry) => entry.manager))];
565
+ if (managers.length > 1) {
566
+ throw new Error(`Ambiguous package manager: found lockfiles for ${managers.join(", ")}; declare packageManager explicitly.`);
567
+ }
568
+ const detected = managers[0];
569
+ if (detected) {
570
+ return {
571
+ manager: detected,
572
+ source: "lockfile",
573
+ evidence: lockfiles.filter((entry) => entry.manager === detected).map((entry) => entry.file).join(",")
574
+ };
575
+ }
576
+ return { manager: "npm", source: "default" };
577
+ };
578
+ var runCommand = (manager, check) => {
579
+ switch (manager) {
580
+ case "bun":
581
+ return ["bun", "--no-env-file", "run", check];
582
+ case "npm":
583
+ return ["npm", "--ignore-scripts", "run", check];
584
+ case "pnpm":
585
+ return ["pnpm", "--ignore-scripts", "run", check];
586
+ case "yarn":
587
+ return ["yarn", "run", check];
588
+ }
589
+ };
590
+ var resolvePackageCheckCommand = async (root, manifest, check, expectedScript, allowedChecks) => {
591
+ if (!/^[A-Za-z0-9:_-]+$/.test(check) || !allowedChecks.includes(check)) {
592
+ throw new Error(`The check "${check}" is not in the explicit allowlist.`);
593
+ }
594
+ const scripts = manifest.scripts && typeof manifest.scripts === "object" ? manifest.scripts : {};
595
+ const actual = scripts[check];
596
+ if (typeof actual !== "string") {
597
+ throw new Error(`package.json does not define the "${check}" script.`);
598
+ }
599
+ if (actual !== expectedScript) {
600
+ throw new Error(`The "${check}" script changed or does not match expectedScript.`);
601
+ }
602
+ for (const lifecycleName of [`pre${check}`, `post${check}`]) {
603
+ if (typeof scripts[lifecycleName] === "string") {
604
+ throw new Error(`The approved "${check}" check has an implicit ${lifecycleName} lifecycle script; approve a standalone script instead.`);
605
+ }
606
+ }
607
+ const resolution = await resolvePackageManager(root, manifest);
608
+ return { ...resolution, command: runCommand(resolution.manager, check) };
609
+ };
610
+
611
+ // src/execution/process-runtime.ts
612
+ import { spawn } from "node:child_process";
613
+ var DEFAULT_MAX_OUTPUT_CHARACTERS = 20000;
614
+ var DEFAULT_TERMINATION_GRACE_MS = 1000;
615
+ var PORTABLE_ENVIRONMENT_KEYS = [
616
+ "CI",
617
+ "FORCE_COLOR",
618
+ "LANG",
619
+ "LC_ALL",
620
+ "NO_COLOR",
621
+ "PATH",
622
+ "TEMP",
623
+ "TERM",
624
+ "TMP",
625
+ "TMPDIR"
626
+ ];
627
+
628
+ class BoundedTextCapture {
629
+ decoder = new TextDecoder;
630
+ maxCharacters;
631
+ output = "";
632
+ totalCharacters = 0;
633
+ constructor(maxCharacters) {
634
+ this.maxCharacters = maxCharacters;
635
+ }
636
+ write(chunk) {
637
+ this.append(this.decoder.decode(chunk, { stream: true }));
638
+ }
639
+ finish() {
640
+ this.append(this.decoder.decode());
641
+ return this.totalCharacters <= this.maxCharacters ? this.output : `${this.output}
642
+ … output truncated (${this.totalCharacters - this.maxCharacters} characters omitted)`;
643
+ }
644
+ append(value) {
645
+ this.totalCharacters += value.length;
646
+ if (this.output.length < this.maxCharacters) {
647
+ this.output += value.slice(0, this.maxCharacters - this.output.length);
648
+ }
649
+ }
650
+ }
651
+ var processEnvironment = (additional) => {
652
+ const env = {};
653
+ for (const key of PORTABLE_ENVIRONMENT_KEYS) {
654
+ const value = process.env[key];
655
+ if (value !== undefined)
656
+ env[key] = value;
657
+ }
658
+ for (const [key, value] of Object.entries(additional ?? {})) {
659
+ if (value === undefined)
660
+ delete env[key];
661
+ else
662
+ env[key] = value;
663
+ }
664
+ return env;
665
+ };
666
+ var exitCodeForSignal = (signal) => {
667
+ if (signal === "SIGKILL")
668
+ return 137;
669
+ if (signal === "SIGTERM")
670
+ return 143;
671
+ return 1;
672
+ };
673
+ var abortReason = (signal) => signal.reason instanceof Error ? signal.reason : new DOMException("The operation was aborted.", "AbortError");
674
+ var runPortableProcess = async (command, options = {}) => {
675
+ if (command.length === 0 || !command[0])
676
+ throw new Error("The process command must not be empty.");
677
+ if (options.signal?.aborted)
678
+ throw abortReason(options.signal);
679
+ const maxOutputCharacters = options.maxOutputCharacters ?? DEFAULT_MAX_OUTPUT_CHARACTERS;
680
+ if (!Number.isSafeInteger(maxOutputCharacters) || maxOutputCharacters < 0) {
681
+ throw new Error("maxOutputCharacters must be a non-negative safe integer.");
682
+ }
683
+ if (options.timeoutMs !== undefined && (!Number.isSafeInteger(options.timeoutMs) || options.timeoutMs <= 0)) {
684
+ throw new Error("timeoutMs must be a positive safe integer.");
685
+ }
686
+ const argv = [...command];
687
+ const stdout = new BoundedTextCapture(maxOutputCharacters);
688
+ const stderr = new BoundedTextCapture(maxOutputCharacters);
689
+ const useProcessGroup = process.platform !== "win32";
690
+ const child = spawn(argv[0], argv.slice(1), {
691
+ ...options.cwd ? { cwd: options.cwd } : {},
692
+ env: processEnvironment(options.env),
693
+ detached: useProcessGroup,
694
+ shell: false,
695
+ stdio: ["ignore", "pipe", "pipe"],
696
+ windowsHide: true
697
+ });
698
+ let timedOut = false;
699
+ let aborted = false;
700
+ let terminationTimer;
701
+ child.stdout.on("data", (chunk) => stdout.write(chunk));
702
+ child.stderr?.on("data", (chunk) => stderr.write(chunk));
703
+ const signalProcessTree = (signal) => {
704
+ if (useProcessGroup && child.pid !== undefined) {
705
+ try {
706
+ process.kill(-child.pid, signal);
707
+ return;
708
+ } catch (error) {
709
+ if (error.code !== "ESRCH") {
710
+ child.kill(signal);
711
+ return;
712
+ }
713
+ }
714
+ }
715
+ if (child.exitCode === null && child.signalCode === null)
716
+ child.kill(signal);
717
+ };
718
+ const terminate = () => {
719
+ if (terminationTimer)
720
+ return;
721
+ signalProcessTree("SIGTERM");
722
+ terminationTimer = setTimeout(() => {
723
+ signalProcessTree("SIGKILL");
724
+ }, DEFAULT_TERMINATION_GRACE_MS);
725
+ terminationTimer.unref?.();
726
+ };
727
+ const handleAbort = () => {
728
+ aborted = true;
729
+ terminate();
730
+ };
731
+ options.signal?.addEventListener("abort", handleAbort, { once: true });
732
+ const timeout = options.timeoutMs === undefined ? undefined : setTimeout(() => {
733
+ timedOut = true;
734
+ terminate();
735
+ }, options.timeoutMs);
736
+ timeout?.unref?.();
737
+ try {
738
+ return await new Promise((resolve, reject) => {
739
+ let processError;
740
+ child.once("error", (error) => {
741
+ processError = error;
742
+ });
743
+ child.once("close", (exitCode, signal) => {
744
+ if (processError) {
745
+ reject(processError);
746
+ return;
747
+ }
748
+ if (aborted && options.signal) {
749
+ reject(abortReason(options.signal));
750
+ return;
751
+ }
752
+ resolve({
753
+ command: argv,
754
+ exitCode: exitCode ?? exitCodeForSignal(signal),
755
+ stdout: stdout.finish(),
756
+ stderr: options.stderr === "ignore" ? "" : stderr.finish(),
757
+ timedOut
758
+ });
759
+ });
760
+ });
761
+ } finally {
762
+ if (timeout)
763
+ clearTimeout(timeout);
764
+ if (terminationTimer)
765
+ clearTimeout(terminationTimer);
766
+ options.signal?.removeEventListener("abort", handleAbort);
767
+ }
768
+ };
769
+
770
+ // src/runtime/task-acceptance-checks.ts
771
+ import { createHash as createHash2 } from "node:crypto";
772
+ import { z as z2 } from "zod";
773
+ var sha = z2.string().regex(/^sha256:[a-f0-9]{64}$/);
774
+ var taskCheckReceiptSchema = z2.strictObject({
775
+ schemaVersion: z2.literal(1),
776
+ checkId: z2.string().max(128),
777
+ contractDigest: sha,
778
+ contractRevision: z2.number().int().positive(),
779
+ runId: z2.string().max(512),
780
+ executionIdentity: z2.string().max(512),
781
+ patchId: sha,
782
+ snapshotDigest: sha,
783
+ argvDigest: sha,
784
+ exitCode: z2.number().int(),
785
+ timedOut: z2.boolean(),
786
+ unchanged: z2.boolean(),
787
+ source: z2.strictObject({ id: z2.string().min(1).max(512), name: z2.string().min(1).max(128), startedAt: z2.number().finite().nonnegative().optional() }).optional()
788
+ });
789
+ var argvDigest = (command, args) => "sha256:" + createHash2("sha256").update(JSON.stringify([command, ...args])).digest("hex");
790
+ function createTaskAcceptanceChecks(ledger, previous = [], journal = []) {
791
+ const current = ledger.revisions.at(-1);
792
+ const receipts = new Map;
793
+ const parsed = z2.array(taskCheckReceiptSchema).max(32).safeParse(previous);
794
+ if (parsed.success)
795
+ for (const receipt of parsed.data) {
796
+ const check = current.contract.requiredChecks.find((c) => c.id === receipt.checkId);
797
+ if (check && receipt.contractDigest === current.digest && receipt.contractRevision === current.revision && receipt.argvDigest === argvDigest(check.command, check.args) && reconciledReceipt(receipt, journal))
798
+ receipts.set(check.id, receipt);
799
+ }
800
+ return {
801
+ matching(command, args) {
802
+ return current.contract.requiredChecks.filter((check) => argvDigest(check.command, check.args) === argvDigest(command, args));
803
+ },
804
+ begin(checkIds) {
805
+ for (const id of checkIds)
806
+ receipts.delete(id);
807
+ },
808
+ record(checkId, before, after, result, source) {
809
+ const check = current.contract.requiredChecks.find((c) => c.id === checkId);
810
+ if (!check)
811
+ throw new Error("TASK_ACCEPTANCE_UNKNOWN_CHECK");
812
+ const receipt = taskCheckReceiptSchema.parse({
813
+ schemaVersion: 1,
814
+ checkId,
815
+ contractDigest: current.digest,
816
+ contractRevision: current.revision,
817
+ ...before,
818
+ ...source ? { source: { id: source.id, name: source.name, ...source.startedAt !== undefined ? { startedAt: source.startedAt } : {} } } : {},
819
+ argvDigest: argvDigest(check.command, check.args),
820
+ exitCode: result.exitCode,
821
+ timedOut: result.timedOut,
822
+ unchanged: before.runId === after.runId && before.executionIdentity === after.executionIdentity && before.patchId === after.patchId && before.snapshotDigest === after.snapshotDigest
823
+ });
824
+ receipts.set(checkId, receipt);
825
+ return structuredClone(receipt);
826
+ },
827
+ missing(binding) {
828
+ return current.contract.requiredChecks.filter((check) => {
829
+ const receipt = receipts.get(check.id);
830
+ return !receipt || receipt.exitCode !== 0 || receipt.timedOut || !receipt.unchanged || receipt.runId !== binding.runId || receipt.executionIdentity !== binding.executionIdentity || receipt.patchId !== binding.patchId || receipt.snapshotDigest !== binding.snapshotDigest;
831
+ }).map((check) => check.id);
832
+ },
833
+ snapshot() {
834
+ return [...receipts.values()].map((receipt) => structuredClone(receipt));
835
+ }
836
+ };
837
+ }
838
+ var object = (value) => value !== null && typeof value === "object" && !Array.isArray(value) ? value : undefined;
839
+ var commandDigest = (value) => {
840
+ const input = object(value);
841
+ return typeof input?.command === "string" && Array.isArray(input.args) && input.args.every((arg) => typeof arg === "string") ? argvDigest(input.command, input.args) : undefined;
842
+ };
843
+ function reconciledReceipt(receipt, journal) {
844
+ if (!receipt.source)
845
+ return false;
846
+ const matches = journal.filter((row2) => row2.runId === receipt.runId && row2.providerToolCallId === receipt.source.id && row2.toolName === receipt.source.name);
847
+ if (matches.length !== 1)
848
+ return false;
849
+ const row = matches[0];
850
+ const startedAt = receipt.source.startedAt ?? row.startedAt;
851
+ if (row.status !== "completed" || !Number.isFinite(startedAt) || !Number.isFinite(row.completedAt) || row.completedAt < startedAt)
852
+ return false;
853
+ if (!["run_environment_command", "run_check", "verify_and_apply_environment_patch", "verify_and_apply_reviewed_edits"].includes(row.toolName))
854
+ return false;
855
+ const output = object(row.output), result = object(output?.verification) ?? output;
856
+ if (!result || result.exitCode !== receipt.exitCode || result.timedOut !== receipt.timedOut || !Array.isArray(result.command) || !result.command.every((arg) => typeof arg === "string") || typeof result.command[0] !== "string" || argvDigest(result.command[0], result.command.slice(1)) !== receipt.argvDigest)
857
+ return false;
858
+ if (row.toolName !== "run_check" && commandDigest(row.input) !== receipt.argvDigest)
859
+ return false;
860
+ for (const attempt of journal) {
861
+ if (attempt === row || attempt.runId !== receipt.runId)
862
+ continue;
863
+ if (attempt.status === "pending" || attempt.status === "running")
864
+ return false;
865
+ const input = object(attempt.input);
866
+ const sameCheck = commandDigest(input) === receipt.argvDigest || attempt.toolName === "run_check" && row.toolName === "run_check" && input?.check === object(row.input)?.check || attempt.toolName === "run_environment_batch" && Array.isArray(input?.commands) && input.commands.some((command) => commandDigest(command) === receipt.argvDigest);
867
+ if (sameCheck && (!Number.isFinite(attempt.completedAt) || attempt.completedAt >= startedAt))
868
+ return false;
869
+ }
870
+ return true;
871
+ }
872
+
873
+ // src/runtime/task-acceptance-delivery.ts
874
+ import { AsyncLocalStorage } from "node:async_hooks";
875
+ import { z as z3 } from "zod";
876
+ var scopes = new AsyncLocalStorage;
877
+ function withTaskAcceptanceDelivery(workspace, ledger, work, previousEvidence, journal = []) {
878
+ const latest = ledger.revisions.at(-1);
879
+ const previous = z3.object({ contractDigest: z3.literal(latest.digest), contractRevision: z3.literal(latest.revision), diagnostic: z3.strictObject({
880
+ code: z3.literal("TASK_ACCEPTANCE_SCOPE_VIOLATION"),
881
+ runId: z3.string().max(512),
882
+ patchId: z3.string().regex(/^sha256:[a-f0-9]{64}$/),
883
+ paths: z3.array(z3.string().max(1024)).max(256)
884
+ }) }).safeParse(previousEvidence);
885
+ const previousDelivery = z3.object({ contractDigest: z3.literal(latest.digest), contractRevision: z3.literal(latest.revision), delivery: z3.strictObject({
886
+ runId: z3.string().min(1).max(512),
887
+ executionIdentity: z3.string().min(1).max(512),
888
+ patchId: z3.string().regex(/^sha256:[a-f0-9]{64}$/),
889
+ snapshotDigest: z3.string().regex(/^sha256:[a-f0-9]{64}$/)
890
+ }) }).safeParse(previousEvidence);
891
+ return scopes.run({
892
+ ...previousDelivery.success ? { previousDelivery: previousDelivery.data.delivery } : {},
893
+ workspace,
894
+ revision: latest.revision,
895
+ digest: latest.digest,
896
+ checks: createTaskAcceptanceChecks(ledger, previousEvidence && typeof previousEvidence === "object" && "checks" in previousEvidence ? previousEvidence.checks : [], journal),
897
+ allowed: new Set(latest.contract.allowedWritePaths),
898
+ protected: new Set(latest.contract.protectedFiles.map((file) => file.toLowerCase())),
899
+ ...previous.success ? { violation: previous.data.diagnostic } : {}
900
+ }, work);
901
+ }
902
+ function assertTaskAcceptanceImport(input) {
903
+ const scope = scopes.getStore();
904
+ if (!scope)
905
+ return;
906
+ const blocked = input.entries.filter((entry) => {
907
+ const candidate = entry.path.toLowerCase();
908
+ return input.workspace !== scope.workspace || !scope.allowed.has(entry.path) || [...scope.protected].some((file) => candidate === file || candidate.startsWith(file + "/") || file.startsWith(candidate + "/"));
909
+ });
910
+ if (blocked.length) {
911
+ scope.violation = { code: "TASK_ACCEPTANCE_SCOPE_VIOLATION", runId: input.runId, patchId: input.patchId, paths: blocked.map((entry) => entry.path).slice(0, 256) };
912
+ throw new Error("TASK_ACCEPTANCE_SCOPE_VIOLATION: the OCI patch touches files outside the task contract; no host import was performed.");
913
+ }
914
+ if (!input.executionIdentity || !input.snapshotDigest || scope.checks.missing({ runId: input.runId, patchId: input.patchId, executionIdentity: input.executionIdentity, snapshotDigest: input.snapshotDigest }).length) {
915
+ throw new Error("TASK_ACCEPTANCE_CHECKS_MISSING: every required check must succeed on the exact final snapshot and patch before import.");
916
+ }
917
+ }
918
+ var taskAcceptanceChecks = () => scopes.getStore()?.checks;
919
+ function taskAcceptanceDeliveryDiagnostic(digest2, revision) {
920
+ const scope = scopes.getStore();
921
+ return scope?.digest === digest2 && scope.revision === revision && scope.violation ? structuredClone(scope.violation) : undefined;
922
+ }
923
+ function confirmTaskAcceptanceImport(binding, validate) {
924
+ const scope = scopes.getStore();
925
+ if (scope)
926
+ scope.imported = { binding: structuredClone(binding), validate };
927
+ }
928
+ async function evaluateTaskAcceptanceOutcome(state, ledger) {
929
+ const current = ledger.revisions.at(-1), scope = scopes.getStore();
930
+ const terminal = ["completed", "failed", "cancelled", "timed_out"].includes(state.status);
931
+ if (!scope || scope.digest !== current.digest || scope.revision !== current.revision) {
932
+ return { status: terminal ? "incomplete" : "pending", reason: "TASK_ACCEPTANCE_NO_DELIVERY_EVIDENCE" };
933
+ }
934
+ if (scope.violation)
935
+ return { status: "failed", reason: scope.violation.code };
936
+ if (state.status !== "completed")
937
+ return { status: state.status === "failed" ? "failed" : terminal ? "incomplete" : "pending", reason: "TASK_ACCEPTANCE_RUN_NOT_COMPLETED" };
938
+ if (state.finishReason !== "stop")
939
+ return { status: "incomplete", reason: "TASK_ACCEPTANCE_RUN_NOT_FINISHED" };
940
+ const imported = scope.imported;
941
+ if (!imported || imported.binding.runId !== state.runId)
942
+ return { status: "incomplete", reason: "TASK_ACCEPTANCE_IMPORT_UNCONFIRMED" };
943
+ const delivery = { ...imported.binding };
944
+ if (scope.checks.missing(delivery).length)
945
+ return { status: "incomplete", reason: "TASK_ACCEPTANCE_CHECKS_MISSING", delivery };
946
+ try {
947
+ if (!await imported.validate())
948
+ return { status: "incomplete", reason: "TASK_ACCEPTANCE_DELIVERY_DRIFT", delivery };
949
+ } catch {
950
+ return { status: "incomplete", reason: "TASK_ACCEPTANCE_DELIVERY_INSPECTION_FAILED", delivery };
951
+ }
952
+ return { status: current.contract.humanReview.length ? "pending_review" : "verified", reason: current.contract.humanReview.length ? "TASK_ACCEPTANCE_HUMAN_REVIEW_REQUIRED" : "TASK_ACCEPTANCE_VERIFIED", delivery };
953
+ }
954
+ async function taskAcceptanceOutcome(state, ledger) {
955
+ const outcome = await evaluateTaskAcceptanceOutcome(state, ledger);
956
+ const scope = scopes.getStore(), latest = ledger.revisions.at(-1);
957
+ const delivery = scope?.digest === latest.digest && scope.revision === latest.revision ? scope.imported?.binding ?? scope.previousDelivery : undefined;
958
+ return { ...outcome, ...delivery && delivery.runId === state.runId ? { delivery: structuredClone(delivery) } : {} };
959
+ }
960
+
961
+ export { EDIT_CONTRACT_SCHEMA_VERSION, EDIT_DIGEST_ALGORITHM, MAX_EDIT_CHANGES, MAX_EDIT_FILE_BYTES, MAX_EDIT_PROPOSAL_BYTES, fileDigestSchema, workspaceFilePathSchema, editChangeSchema, editChangesSchema, editProposalInputSchema, editProposalChangeSchema, editProposalSchema, applyEditProposalInputSchema, moveFileInputSchema, quarantineFileInputSchema, restoreFileInputSchema, mutationOperationSchema, mutationAuditEntrySchema, createEditProposal, validateEditProposal, editContractDocument, protectPersistenceSecret, acquireSqliteAccess, exclusiveSqliteAccessDescriptor, adoptExclusiveSqliteAccess, SqliteDatabase, UnsafeFileTypeError, FileSizeLimitError, FileChangedWhileReadingError, statRegularFileNoFollow, readRegularFileNoFollow, readRegularFileChunksNoFollow, taskCheckReceiptSchema, withTaskAcceptanceDelivery, assertTaskAcceptanceImport, taskAcceptanceChecks, taskAcceptanceDeliveryDiagnostic, confirmTaskAcceptanceImport, taskAcceptanceOutcome, resolvePackageManager, resolvePackageCheckCommand, runPortableProcess };