harnery 0.22.1 → 0.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. package/README.md +1 -0
  2. package/dist/commands/workflow.d.ts.map +1 -1
  3. package/dist/commands/workflow.js +91 -2
  4. package/dist/core/work/state.d.ts +7 -0
  5. package/dist/core/work/state.d.ts.map +1 -1
  6. package/dist/core/work/state.js +112 -23
  7. package/dist/core/workflow/child-env.d.ts +10 -6
  8. package/dist/core/workflow/child-env.d.ts.map +1 -1
  9. package/dist/core/workflow/child-env.js +15 -7
  10. package/dist/core/workflow/durable-record.d.ts +7 -0
  11. package/dist/core/workflow/durable-record.d.ts.map +1 -0
  12. package/dist/core/workflow/durable-record.js +83 -0
  13. package/dist/core/workflow/engine.d.ts.map +1 -1
  14. package/dist/core/workflow/engine.js +208 -29
  15. package/dist/core/workflow/index.d.ts +2 -0
  16. package/dist/core/workflow/index.d.ts.map +1 -1
  17. package/dist/core/workflow/index.js +1 -0
  18. package/dist/core/workflow/proof.d.ts +4 -0
  19. package/dist/core/workflow/proof.d.ts.map +1 -1
  20. package/dist/core/workflow/proof.js +50 -15
  21. package/dist/core/workflow/run-state.d.ts +3 -0
  22. package/dist/core/workflow/run-state.d.ts.map +1 -1
  23. package/dist/core/workflow/run-state.js +25 -13
  24. package/dist/core/workflow/types.d.ts +10 -0
  25. package/dist/core/workflow/types.d.ts.map +1 -1
  26. package/dist/core/workflow/workspaces/attestation-error.d.ts +7 -0
  27. package/dist/core/workflow/workspaces/attestation-error.d.ts.map +1 -0
  28. package/dist/core/workflow/workspaces/attestation-error.js +25 -0
  29. package/dist/core/workflow/workspaces/cancellation.d.ts +8 -0
  30. package/dist/core/workflow/workspaces/cancellation.d.ts.map +1 -0
  31. package/dist/core/workflow/workspaces/cancellation.js +95 -0
  32. package/dist/core/workflow/workspaces/cleanup.d.ts +9 -0
  33. package/dist/core/workflow/workspaces/cleanup.d.ts.map +1 -0
  34. package/dist/core/workflow/workspaces/cleanup.js +223 -0
  35. package/dist/core/workflow/workspaces/execution.d.ts +25 -0
  36. package/dist/core/workflow/workspaces/execution.d.ts.map +1 -0
  37. package/dist/core/workflow/workspaces/execution.js +316 -0
  38. package/dist/core/workflow/workspaces/git.d.ts +25 -0
  39. package/dist/core/workflow/workspaces/git.d.ts.map +1 -0
  40. package/dist/core/workflow/workspaces/git.js +102 -0
  41. package/dist/core/workflow/workspaces/index.d.ts +15 -0
  42. package/dist/core/workflow/workspaces/index.d.ts.map +1 -0
  43. package/dist/core/workflow/workspaces/index.js +7 -0
  44. package/dist/core/workflow/workspaces/inspect.d.ts +85 -0
  45. package/dist/core/workflow/workspaces/inspect.d.ts.map +1 -0
  46. package/dist/core/workflow/workspaces/inspect.js +255 -0
  47. package/dist/core/workflow/workspaces/integration.d.ts +24 -0
  48. package/dist/core/workflow/workspaces/integration.d.ts.map +1 -0
  49. package/dist/core/workflow/workspaces/integration.js +617 -0
  50. package/dist/core/workflow/workspaces/leases.d.ts +37 -0
  51. package/dist/core/workflow/workspaces/leases.d.ts.map +1 -0
  52. package/dist/core/workflow/workspaces/leases.js +380 -0
  53. package/dist/core/workflow/workspaces/lifecycle.d.ts +43 -0
  54. package/dist/core/workflow/workspaces/lifecycle.d.ts.map +1 -0
  55. package/dist/core/workflow/workspaces/lifecycle.js +622 -0
  56. package/dist/core/workflow/workspaces/local-git.d.ts +8 -0
  57. package/dist/core/workflow/workspaces/local-git.d.ts.map +1 -0
  58. package/dist/core/workflow/workspaces/local-git.js +1474 -0
  59. package/dist/core/workflow/workspaces/paths.d.ts +23 -0
  60. package/dist/core/workflow/workspaces/paths.d.ts.map +1 -0
  61. package/dist/core/workflow/workspaces/paths.js +191 -0
  62. package/dist/core/workflow/workspaces/recovery.d.ts +3 -0
  63. package/dist/core/workflow/workspaces/recovery.d.ts.map +1 -0
  64. package/dist/core/workflow/workspaces/recovery.js +50 -0
  65. package/dist/core/workflow/workspaces/state.d.ts +60 -0
  66. package/dist/core/workflow/workspaces/state.d.ts.map +1 -0
  67. package/dist/core/workflow/workspaces/state.js +296 -0
  68. package/dist/core/workflow/workspaces/types.d.ts +445 -0
  69. package/dist/core/workflow/workspaces/types.d.ts.map +1 -0
  70. package/dist/core/workflow/workspaces/types.js +2 -0
  71. package/dist/core/workflow/workspaces/validate.d.ts +14 -0
  72. package/dist/core/workflow/workspaces/validate.d.ts.map +1 -0
  73. package/dist/core/workflow/workspaces/validate.js +476 -0
  74. package/package.json +5 -3
  75. package/src/commands/workflow.ts +110 -4
  76. package/src/core/work/state.ts +156 -28
  77. package/src/core/workflow/child-env.ts +18 -7
  78. package/src/core/workflow/durable-record.ts +94 -0
  79. package/src/core/workflow/engine.ts +230 -30
  80. package/src/core/workflow/index.ts +73 -0
  81. package/src/core/workflow/proof.ts +70 -22
  82. package/src/core/workflow/run-state.ts +46 -11
  83. package/src/core/workflow/types.ts +14 -0
  84. package/src/core/workflow/workspaces/attestation-error.ts +30 -0
  85. package/src/core/workflow/workspaces/cancellation.ts +137 -0
  86. package/src/core/workflow/workspaces/cleanup.ts +308 -0
  87. package/src/core/workflow/workspaces/execution.ts +412 -0
  88. package/src/core/workflow/workspaces/git.ts +125 -0
  89. package/src/core/workflow/workspaces/index.ts +85 -0
  90. package/src/core/workflow/workspaces/inspect.ts +421 -0
  91. package/src/core/workflow/workspaces/integration.ts +829 -0
  92. package/src/core/workflow/workspaces/leases.ts +495 -0
  93. package/src/core/workflow/workspaces/lifecycle.ts +792 -0
  94. package/src/core/workflow/workspaces/local-git.ts +1892 -0
  95. package/src/core/workflow/workspaces/paths.ts +247 -0
  96. package/src/core/workflow/workspaces/recovery.ts +56 -0
  97. package/src/core/workflow/workspaces/state.ts +481 -0
  98. package/src/core/workflow/workspaces/types.ts +517 -0
  99. package/src/core/workflow/workspaces/validate.ts +595 -0
@@ -0,0 +1,595 @@
1
+ import { isAbsolute, relative, resolve } from "node:path";
2
+ import { policyDigest } from "../../policy/index.ts";
3
+ import { stableDigest } from "../durable-record.ts";
4
+ import type { WorkflowRunManifest } from "../run-state.ts";
5
+ import type {
6
+ RepositoryObservation,
7
+ WorkspaceAllocationRequest,
8
+ WorkspaceAttestation,
9
+ WorkspaceBinding,
10
+ WorkspaceBoundExecutionEvidence,
11
+ WorkspaceCompatibilityExecutionEvidence,
12
+ WorkspaceExecutionEvidence,
13
+ WorkspaceOwner,
14
+ WorkspaceProofLifecycleState,
15
+ WorkspaceProofOutcome,
16
+ } from "./types.ts";
17
+
18
+ const DIGEST = /^[a-f0-9]{64}$/;
19
+ const OBJECT_ID = /^[a-f0-9]{40,64}$/;
20
+
21
+ export function isWorkspaceOwner(value: unknown): value is WorkspaceOwner {
22
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
23
+ const owner = value as WorkspaceOwner;
24
+ return owner.kind === "standalone"
25
+ ? owner.work_item_id === null && owner.attempt === null
26
+ : owner.kind === "work_attempt" &&
27
+ typeof owner.work_item_id === "string" &&
28
+ owner.work_item_id.length > 0 &&
29
+ Number.isSafeInteger(owner.attempt) &&
30
+ owner.attempt > 0;
31
+ }
32
+
33
+ export function isWorkspaceAllocationRequest(value: unknown): value is WorkspaceAllocationRequest {
34
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
35
+ const request = value as WorkspaceAllocationRequest;
36
+ if (
37
+ request.schema_version !== 1 ||
38
+ typeof request.run_id !== "string" ||
39
+ !isWorkspaceOwner(request.owner) ||
40
+ !isAbsolute(request.requested_cwd) ||
41
+ !["worktree", "sandbox", "remote"].includes(request.requested_isolation) ||
42
+ !["enabled", "disabled", "unknown"].includes(request.network_access) ||
43
+ !isAbsolute(request.script?.path) ||
44
+ !DIGEST.test(request.script?.sha256) ||
45
+ !(request.policy_sha256 === null || DIGEST.test(request.policy_sha256)) ||
46
+ !validatedPathArray(request.allowed_paths) ||
47
+ !validatedPathArray(request.writable_roots) ||
48
+ request.writable_roots.length !== 1 ||
49
+ !validValidatedPath(request.selected_writable_root) ||
50
+ stableDigest(request.writable_roots[0]) !== stableDigest(request.selected_writable_root) ||
51
+ typeof request.provider_id !== "string" ||
52
+ request.provider_id.length === 0 ||
53
+ !DIGEST.test(request.capability_digest) ||
54
+ !DIGEST.test(request.idempotency_key)
55
+ ) {
56
+ return false;
57
+ }
58
+ const { idempotency_key: _idempotencyKey, ...authority } = request;
59
+ return request.idempotency_key === stableDigest(authority);
60
+ }
61
+
62
+ export function isWorkspaceBinding(value: unknown, runId?: string): value is WorkspaceBinding {
63
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
64
+ const binding = value as WorkspaceBinding;
65
+ return (
66
+ binding.schema_version === 1 &&
67
+ typeof binding.binding_id === "string" &&
68
+ binding.binding_id.length > 0 &&
69
+ typeof binding.workspace_id === "string" &&
70
+ binding.workspace_id.length > 0 &&
71
+ typeof binding.run_id === "string" &&
72
+ (runId === undefined || binding.run_id === runId) &&
73
+ isWorkspaceOwner(binding.owner) &&
74
+ binding.provider !== undefined &&
75
+ typeof binding.provider.id === "string" &&
76
+ typeof binding.provider.version === "string" &&
77
+ DIGEST.test(binding.provider.capability_digest) &&
78
+ ["worktree", "sandbox", "remote"].includes(binding.isolation) &&
79
+ ["enabled", "disabled", "unknown"].includes(binding.network_access) &&
80
+ isAbsolute(binding.workspace_root) &&
81
+ resolve(binding.workspace_root) === binding.workspace_root &&
82
+ validIdentity(binding.workspace_root_identity) &&
83
+ isAbsolute(binding.active_root) &&
84
+ resolve(binding.active_root) === binding.active_root &&
85
+ containsResolvedPath(binding.workspace_root, binding.active_root) &&
86
+ validIdentity(binding.active_root_identity) &&
87
+ (binding.integration_root === undefined ||
88
+ (isAbsolute(binding.integration_root) &&
89
+ resolve(binding.integration_root) === binding.integration_root)) &&
90
+ validValidatedPath(binding.writable_root) &&
91
+ Number.isSafeInteger(binding.generation) &&
92
+ binding.generation > 0 &&
93
+ /^[a-f0-9]{64}$/.test(binding.recovery_token) &&
94
+ DIGEST.test(binding.request_sha256) &&
95
+ validTimestamp(binding.created_at) &&
96
+ (binding.repository === undefined ||
97
+ (validValidatedPath(binding.repository.source_root) &&
98
+ validObservedIdentity(binding.repository.common_dir) &&
99
+ OBJECT_ID.test(binding.repository.base_commit) &&
100
+ OBJECT_ID.test(binding.repository.target_commit) &&
101
+ typeof binding.repository.target_ref === "string" &&
102
+ typeof binding.repository.workspace_ref === "string" &&
103
+ typeof binding.repository.workspace_branch === "string" &&
104
+ validObservedIdentity(binding.repository.gitdir) &&
105
+ typeof binding.repository.branch_created_by_provider === "boolean"))
106
+ );
107
+ }
108
+
109
+ export function isWorkspaceAttestation(
110
+ value: unknown,
111
+ binding: WorkspaceBinding,
112
+ ): value is WorkspaceAttestation {
113
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
114
+ const attestation = value as WorkspaceAttestation;
115
+ const containment = attestation.containment;
116
+ const filesystem = attestation.filesystem;
117
+ const validContainment =
118
+ containment !== undefined &&
119
+ containment !== null &&
120
+ typeof containment === "object" &&
121
+ typeof containment.writable_root === "boolean" &&
122
+ typeof containment.workspace_root === "boolean" &&
123
+ typeof containment.active_root === "boolean" &&
124
+ (typeof containment.integration_root === "boolean" || containment.integration_root === null);
125
+ const validFilesystem =
126
+ filesystem !== undefined &&
127
+ filesystem !== null &&
128
+ typeof filesystem === "object" &&
129
+ typeof filesystem.root_identity_match === "boolean" &&
130
+ (filesystem.workspace_identity === undefined || validIdentity(filesystem.workspace_identity)) &&
131
+ (filesystem.active_identity === undefined || validIdentity(filesystem.active_identity));
132
+ const repository = attestation.repository;
133
+ const validDrift = stringArray(attestation.provider_drift);
134
+ const validUnsupported = providerFactArray(attestation.unsupported);
135
+ const validUnknowns = providerFactArray(attestation.unknowns);
136
+ const validRepository =
137
+ repository === undefined ||
138
+ (typeof repository === "object" &&
139
+ repository !== null &&
140
+ validObservedSourceRoot(repository.source_root) &&
141
+ (repository.common_dir === undefined || validObservedIdentity(repository.common_dir)) &&
142
+ (repository.active_gitdir === undefined || validObservedIdentity(repository.active_gitdir)) &&
143
+ typeof repository.worktree_registered === "boolean" &&
144
+ (repository.current_ref === undefined || typeof repository.current_ref === "string") &&
145
+ (repository.workspace_ref_oid === undefined ||
146
+ OBJECT_ID.test(repository.workspace_ref_oid)) &&
147
+ (repository.target_ref_oid === undefined || OBJECT_ID.test(repository.target_ref_oid)) &&
148
+ (repository.head_commit === undefined || OBJECT_ID.test(repository.head_commit)) &&
149
+ (typeof repository.base_is_ancestor === "boolean" || repository.base_is_ancestor === null) &&
150
+ (typeof repository.target_is_ancestor === "boolean" ||
151
+ repository.target_is_ancestor === null) &&
152
+ stringArray(repository.dirty_paths) &&
153
+ stringArray(repository.conflicts) &&
154
+ stringArray(repository.operation_in_progress));
155
+ const validReleasedAuthority =
156
+ attestation.resource_state === "released" &&
157
+ attestation.workspace_exists === false &&
158
+ containment?.writable_root === true &&
159
+ containment.workspace_root === false &&
160
+ containment.active_root === false &&
161
+ filesystem?.root_identity_match === true &&
162
+ filesystem.workspace_identity === undefined &&
163
+ filesystem.active_identity === undefined &&
164
+ (binding.repository === undefined ||
165
+ (repository !== undefined &&
166
+ repositorySourceMatches(repository, binding) &&
167
+ repositoryCommonDirMatches(repository, binding) &&
168
+ repository?.active_gitdir === undefined &&
169
+ repository.worktree_registered === false &&
170
+ repository.current_ref === undefined &&
171
+ repository.workspace_ref_oid === undefined &&
172
+ repository.target_ref_oid !== undefined &&
173
+ repository.head_commit === undefined &&
174
+ repository.base_is_ancestor === null &&
175
+ repository.target_is_ancestor === null &&
176
+ repository.dirty_paths.length === 0 &&
177
+ repository.conflicts.length === 0 &&
178
+ repository.operation_in_progress.length === 0));
179
+ const validActiveAuthority =
180
+ (attestation.resource_state === "active" || attestation.resource_state === "preserved_dirty") &&
181
+ attestation.workspace_exists === true &&
182
+ containment?.writable_root === true &&
183
+ containment.workspace_root === true &&
184
+ containment.active_root === true &&
185
+ (binding.integration_root === undefined || containment.integration_root === true) &&
186
+ filesystem?.root_identity_match === true &&
187
+ validIdentity(filesystem.workspace_identity) &&
188
+ validIdentity(filesystem.active_identity) &&
189
+ sameIdentity(filesystem.workspace_identity, binding.workspace_root_identity) &&
190
+ sameIdentity(filesystem.active_identity, binding.active_root_identity) &&
191
+ (binding.repository === undefined ||
192
+ (repository !== undefined &&
193
+ repositorySourceMatches(repository, binding) &&
194
+ repositoryCommonDirMatches(repository, binding) &&
195
+ repositoryGitdirMatches(repository, binding) &&
196
+ repository.worktree_registered === true &&
197
+ binding.repository.branch_created_by_provider === true &&
198
+ repository.current_ref === binding.repository.workspace_ref &&
199
+ repository.workspace_ref_oid !== undefined &&
200
+ repository.target_ref_oid !== undefined &&
201
+ repository.head_commit !== undefined &&
202
+ repository.workspace_ref_oid === repository.head_commit &&
203
+ repository.base_is_ancestor === true &&
204
+ repository.target_is_ancestor === true));
205
+ const validOkAuthority =
206
+ attestation.status !== "ok" ||
207
+ (validDrift &&
208
+ attestation.provider_drift.length === 0 &&
209
+ (validActiveAuthority || validReleasedAuthority));
210
+ const validFailureEvidence =
211
+ validDrift &&
212
+ validUnsupported &&
213
+ validUnknowns &&
214
+ (attestation.status === "ok" ||
215
+ repository !== undefined ||
216
+ attestation.provider_drift.length > 0 ||
217
+ attestation.unsupported.length > 0 ||
218
+ attestation.unknowns.length > 0);
219
+ return (
220
+ attestation.schema_version === 1 &&
221
+ attestation.binding_id === binding.binding_id &&
222
+ attestation.workspace_id === binding.workspace_id &&
223
+ attestation.run_id === binding.run_id &&
224
+ sameOwner(attestation.owner, binding.owner) &&
225
+ validProviderRef(attestation.provider) &&
226
+ attestation.provider.id === binding.provider.id &&
227
+ attestation.provider.version === binding.provider.version &&
228
+ attestation.provider.capability_digest === binding.provider.capability_digest &&
229
+ validTimestamp(attestation.recorded_at) &&
230
+ validContainment &&
231
+ validFilesystem &&
232
+ validRepository &&
233
+ validOkAuthority &&
234
+ validFailureEvidence &&
235
+ (binding.repository === undefined
236
+ ? repository === undefined
237
+ : attestation.status !== "ok" || repository !== undefined) &&
238
+ typeof attestation.workspace_exists === "boolean" &&
239
+ ["active", "preserved_dirty", "released", "blocked", "lost"].includes(
240
+ attestation.resource_state,
241
+ ) &&
242
+ (attestation.status === "blocked"
243
+ ? attestation.resource_state === "blocked"
244
+ : attestation.status === "lost"
245
+ ? attestation.resource_state === "lost"
246
+ : attestation.resource_state === "active" ||
247
+ attestation.resource_state === "preserved_dirty" ||
248
+ attestation.resource_state === "released") &&
249
+ ["ok", "blocked", "lost"].includes(attestation.status) &&
250
+ validDrift &&
251
+ validUnsupported &&
252
+ validUnknowns
253
+ );
254
+ }
255
+
256
+ export function isWorkspaceExecutionEvidence(
257
+ value: unknown,
258
+ runId: string,
259
+ workflowStatus?: "succeeded" | "failed",
260
+ ): value is WorkspaceExecutionEvidence {
261
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
262
+ const execution = value as WorkspaceExecutionEvidence;
263
+ if (isWorkspaceCompatibilityExecutionEvidence(execution, runId)) return true;
264
+ return isWorkspaceBoundExecutionEvidence(execution, runId, workflowStatus);
265
+ }
266
+
267
+ export function isWorkspaceBoundExecutionEvidence(
268
+ value: unknown,
269
+ runId?: string,
270
+ workflowStatus?: "succeeded" | "failed",
271
+ ): value is WorkspaceBoundExecutionEvidence {
272
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
273
+ const execution = value as WorkspaceBoundExecutionEvidence;
274
+ return (
275
+ execution.schema_version === 1 &&
276
+ isWorkspaceBinding(execution.binding, runId) &&
277
+ isWorkspaceAttestation(execution.terminal_attestation, execution.binding) &&
278
+ validWorkspaceProofOutcome(
279
+ execution.terminal_lifecycle_state,
280
+ execution.terminal_attestation,
281
+ workflowStatus,
282
+ ) &&
283
+ stableDigest(execution.drift) === stableDigest(execution.terminal_attestation.provider_drift) &&
284
+ stableDigest(execution.unsupported) ===
285
+ stableDigest(execution.terminal_attestation.unsupported) &&
286
+ stableDigest(execution.unknowns) === stableDigest(execution.terminal_attestation.unknowns) &&
287
+ validBoundReceiptLocations(execution.receipts)
288
+ );
289
+ }
290
+
291
+ export function workspaceProofLifecycle(
292
+ workflowStatus: "succeeded" | "failed",
293
+ attestation: WorkspaceAttestation,
294
+ ): WorkspaceProofOutcome {
295
+ if (attestation.status === "lost") return "lost";
296
+ if (attestation.status === "blocked") return "blocked";
297
+ return workflowStatus === "succeeded" ? "completed_unintegrated" : "failed_retained";
298
+ }
299
+
300
+ export function assertWorkspaceManifestAuthority(
301
+ manifest: WorkflowRunManifest,
302
+ request: WorkspaceAllocationRequest | undefined,
303
+ ): void {
304
+ const binding = manifest.execution.workspace_binding;
305
+ const fallback = manifest.execution.workspace_fallback;
306
+ if (binding) {
307
+ if (fallback) {
308
+ throw new Error("workflow manifest cannot bind a workspace and a fallback");
309
+ }
310
+ const expectedOwner: WorkspaceOwner =
311
+ manifest.work_item_id && manifest.attempt_context
312
+ ? {
313
+ kind: "work_attempt",
314
+ work_item_id: manifest.work_item_id,
315
+ attempt: manifest.attempt_context.number,
316
+ }
317
+ : { kind: "standalone", work_item_id: null, attempt: null };
318
+ const expectedPolicyDigest = manifest.execution.policy
319
+ ? policyDigest(manifest.execution.policy)
320
+ : null;
321
+ if (
322
+ !isWorkspaceAllocationRequest(request) ||
323
+ request.run_id !== manifest.run_id ||
324
+ binding.run_id !== manifest.run_id ||
325
+ stableDigest(request.owner) !== stableDigest(expectedOwner) ||
326
+ stableDigest(binding.owner) !== stableDigest(expectedOwner) ||
327
+ request.requested_isolation !== manifest.execution.isolation ||
328
+ binding.isolation !== manifest.execution.isolation ||
329
+ request.network_access !== manifest.execution.network_access ||
330
+ binding.network_access !== manifest.execution.network_access ||
331
+ manifest.execution.cwd !== binding.active_root ||
332
+ request.provider_id !== binding.provider.id ||
333
+ request.capability_digest !== binding.provider.capability_digest ||
334
+ stableDigest(request.selected_writable_root) !== stableDigest(binding.writable_root) ||
335
+ request.writable_roots.length !== 1 ||
336
+ stableDigest(request.writable_roots[0]) !== stableDigest(request.selected_writable_root) ||
337
+ stableDigest(request.script) !== stableDigest(manifest.script) ||
338
+ request.policy_sha256 !== expectedPolicyDigest ||
339
+ stableDigest(request.allowed_paths.map((path) => path.configured)) !==
340
+ stableDigest(manifest.execution.policy?.allowed_paths ?? []) ||
341
+ binding.request_sha256 !== stableDigest(request) ||
342
+ (binding.repository !== undefined &&
343
+ (!workspaceCwdMatchesBinding(request, binding) ||
344
+ binding.integration_root !== binding.repository.source_root.realpath))
345
+ ) {
346
+ throw new Error("workflow manifest, workspace request, and provider binding disagree");
347
+ }
348
+ return;
349
+ }
350
+ if (request) {
351
+ throw new Error("workflow manifest has an unbound workspace request");
352
+ }
353
+ if (
354
+ fallback &&
355
+ (fallback.run_id !== manifest.run_id ||
356
+ fallback.requested_isolation !== manifest.execution.isolation ||
357
+ fallback.effective_isolation !== "shared")
358
+ ) {
359
+ throw new Error("workflow manifest and workspace fallback disagree");
360
+ }
361
+ }
362
+
363
+ function workspaceCwdMatchesBinding(
364
+ request: WorkspaceAllocationRequest,
365
+ binding: WorkspaceBinding,
366
+ ): boolean {
367
+ if (!binding.repository) return true;
368
+ const relativeCwd = relative(binding.repository.source_root.configured, request.requested_cwd);
369
+ return (
370
+ (relativeCwd === "" ||
371
+ (!relativeCwd.startsWith(`..${process.platform === "win32" ? "\\" : "/"}`) &&
372
+ relativeCwd !== ".." &&
373
+ !isAbsolute(relativeCwd))) &&
374
+ binding.active_root === resolve(binding.workspace_root, relativeCwd)
375
+ );
376
+ }
377
+
378
+ function validWorkspaceProofOutcome(
379
+ outcome: WorkspaceProofLifecycleState,
380
+ attestation: WorkspaceAttestation,
381
+ workflowStatus?: "succeeded" | "failed",
382
+ ): boolean {
383
+ if (workflowStatus !== undefined) {
384
+ return outcome === workspaceProofLifecycle(workflowStatus, attestation);
385
+ }
386
+ if (attestation.status === "lost") return outcome === "lost";
387
+ if (attestation.status === "blocked") return outcome === "blocked";
388
+ return outcome === "completed_unintegrated" || outcome === "failed_retained";
389
+ }
390
+
391
+ export function isWorkspaceCompatibilityExecutionEvidence(
392
+ value: unknown,
393
+ runId?: string,
394
+ ): value is WorkspaceCompatibilityExecutionEvidence {
395
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
396
+ const execution = value as WorkspaceCompatibilityExecutionEvidence;
397
+ const providerNotConfigured =
398
+ execution.selection_reason === "provider_not_configured" &&
399
+ execution.provider === undefined &&
400
+ execution.unsupported.some((fact) => fact.code === "provider_not_configured");
401
+ const providerUnsupported =
402
+ execution.selection_reason === "provider_unsupported" && validProviderRef(execution.provider);
403
+ return (
404
+ execution.schema_version === 1 &&
405
+ typeof execution.run_id === "string" &&
406
+ (runId === undefined || execution.run_id === runId) &&
407
+ ["worktree", "sandbox", "remote"].includes(execution.requested_isolation) &&
408
+ execution.effective_isolation === "shared" &&
409
+ (providerNotConfigured || providerUnsupported) &&
410
+ execution.terminal_lifecycle_state === "shared" &&
411
+ Array.isArray(execution.drift) &&
412
+ execution.drift.length === 0 &&
413
+ providerFactArray(execution.unsupported) &&
414
+ execution.unsupported.length > 0 &&
415
+ providerFactArray(execution.unknowns) &&
416
+ execution.receipts !== null &&
417
+ typeof execution.receipts === "object" &&
418
+ !Array.isArray(execution.receipts) &&
419
+ Object.keys(execution.receipts).length === 0
420
+ );
421
+ }
422
+
423
+ /** @deprecated Use isWorkspaceCompatibilityExecutionEvidence. */
424
+ export const isWorkspaceUnsupportedExecutionEvidence = isWorkspaceCompatibilityExecutionEvidence;
425
+
426
+ function sameOwner(left: WorkspaceOwner, right: WorkspaceOwner): boolean {
427
+ return (
428
+ left.kind === right.kind &&
429
+ left.work_item_id === right.work_item_id &&
430
+ left.attempt === right.attempt
431
+ );
432
+ }
433
+
434
+ function repositorySourceMatches(
435
+ observation: RepositoryObservation | undefined,
436
+ binding: WorkspaceBinding,
437
+ ): boolean {
438
+ const expected = binding.repository?.source_root;
439
+ return Boolean(
440
+ observation &&
441
+ expected &&
442
+ observation.source_root &&
443
+ observation.source_root.configured === expected.configured &&
444
+ observation.source_root.realpath === expected.realpath &&
445
+ sameIdentity(observation.source_root.identity, expected.identity),
446
+ );
447
+ }
448
+
449
+ function repositoryCommonDirMatches(
450
+ observation: RepositoryObservation | undefined,
451
+ binding: WorkspaceBinding,
452
+ ): boolean {
453
+ const expected = binding.repository?.common_dir;
454
+ return Boolean(
455
+ observation &&
456
+ expected &&
457
+ observation.common_dir?.realpath === expected.realpath &&
458
+ sameIdentity(observation.common_dir?.identity, expected.identity),
459
+ );
460
+ }
461
+
462
+ function repositoryGitdirMatches(
463
+ observation: RepositoryObservation | undefined,
464
+ binding: WorkspaceBinding,
465
+ ): boolean {
466
+ const expected = binding.repository?.gitdir;
467
+ return Boolean(
468
+ observation &&
469
+ expected &&
470
+ observation.active_gitdir?.realpath === expected.realpath &&
471
+ sameIdentity(observation.active_gitdir?.identity, expected.identity),
472
+ );
473
+ }
474
+
475
+ function validObservedSourceRoot(value: unknown): boolean {
476
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
477
+ const root = value as Record<string, unknown>;
478
+ return (
479
+ typeof root.configured === "string" &&
480
+ isAbsolute(root.configured) &&
481
+ resolve(root.configured) === root.configured &&
482
+ ((root.realpath === undefined && root.identity === undefined) ||
483
+ (typeof root.realpath === "string" &&
484
+ isAbsolute(root.realpath) &&
485
+ resolve(root.realpath) === root.realpath &&
486
+ validIdentity(root.identity)))
487
+ );
488
+ }
489
+
490
+ function validObservedIdentity(value: unknown): boolean {
491
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
492
+ const root = value as Record<string, unknown>;
493
+ return (
494
+ typeof root.realpath === "string" &&
495
+ isAbsolute(root.realpath) &&
496
+ resolve(root.realpath) === root.realpath &&
497
+ validIdentity(root.identity)
498
+ );
499
+ }
500
+
501
+ function sameIdentity(left: unknown, right: unknown): boolean {
502
+ if (!validIdentity(left) || !validIdentity(right)) return false;
503
+ return stableDigest(left) === stableDigest(right);
504
+ }
505
+
506
+ function validBoundReceiptLocations(
507
+ value: WorkspaceBoundExecutionEvidence["receipts"] | undefined,
508
+ ): boolean {
509
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
510
+ const expected: Record<string, string> = {
511
+ request: "workspace-request.json",
512
+ cancellation_outcome: "cancellation/outcome.json",
513
+ integration_plan: "integration/plan.json",
514
+ integration_authorization: "integration/authorization.json",
515
+ integration_apply: "integration/receipt.json",
516
+ cleanup_intent: "cleanup/intent.json",
517
+ cleanup_receipt: "cleanup/receipt.json",
518
+ };
519
+ return (
520
+ value.request === expected.request &&
521
+ Object.entries(value).every(([key, location]) => expected[key] === location)
522
+ );
523
+ }
524
+
525
+ function validIdentity(value: unknown): boolean {
526
+ if (!value || typeof value !== "object") return false;
527
+ const identity = value as Record<string, unknown>;
528
+ return (
529
+ typeof identity.platform === "string" &&
530
+ typeof identity.device === "string" &&
531
+ typeof identity.inode === "string"
532
+ );
533
+ }
534
+
535
+ function validProviderRef(value: unknown): boolean {
536
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
537
+ const provider = value as Record<string, unknown>;
538
+ return (
539
+ typeof provider.id === "string" &&
540
+ provider.id.length > 0 &&
541
+ typeof provider.version === "string" &&
542
+ provider.version.length > 0 &&
543
+ typeof provider.capability_digest === "string" &&
544
+ DIGEST.test(provider.capability_digest)
545
+ );
546
+ }
547
+
548
+ function validTimestamp(value: unknown): value is string {
549
+ return typeof value === "string" && value.length <= 40 && Number.isFinite(Date.parse(value));
550
+ }
551
+
552
+ function stringArray(value: unknown): value is string[] {
553
+ return Array.isArray(value) && value.every((item) => typeof item === "string");
554
+ }
555
+
556
+ function validValidatedPath(value: unknown): boolean {
557
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
558
+ const path = value as Record<string, unknown>;
559
+ return (
560
+ typeof path.configured === "string" &&
561
+ isAbsolute(path.configured) &&
562
+ resolve(path.configured) === path.configured &&
563
+ typeof path.realpath === "string" &&
564
+ isAbsolute(path.realpath) &&
565
+ resolve(path.realpath) === path.realpath &&
566
+ validIdentity(path.identity)
567
+ );
568
+ }
569
+
570
+ function validatedPathArray(value: unknown): boolean {
571
+ return Array.isArray(value) && value.every(validValidatedPath);
572
+ }
573
+
574
+ function containsResolvedPath(root: string, candidate: string): boolean {
575
+ const rel = relative(root, candidate);
576
+ return (
577
+ rel === "" ||
578
+ (rel !== ".." && !rel.startsWith("../") && !rel.startsWith("..\\") && !isAbsolute(rel))
579
+ );
580
+ }
581
+
582
+ function providerFactArray(value: unknown): boolean {
583
+ return (
584
+ Array.isArray(value) &&
585
+ value.every(
586
+ (item) =>
587
+ item !== null &&
588
+ typeof item === "object" &&
589
+ typeof item.code === "string" &&
590
+ item.code.length > 0 &&
591
+ typeof item.message === "string" &&
592
+ item.message.length > 0,
593
+ )
594
+ );
595
+ }