harnery 0.22.1 → 0.24.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 (103) hide show
  1. package/README.md +1 -0
  2. package/dist/commands/workflow.d.ts.map +1 -1
  3. package/dist/commands/workflow.js +302 -5
  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/spawn-cursor.d.ts +3 -0
  25. package/dist/core/workflow/spawn-cursor.d.ts.map +1 -1
  26. package/dist/core/workflow/spawn-cursor.js +8 -4
  27. package/dist/core/workflow/types.d.ts +10 -0
  28. package/dist/core/workflow/types.d.ts.map +1 -1
  29. package/dist/core/workflow/workspaces/attestation-error.d.ts +7 -0
  30. package/dist/core/workflow/workspaces/attestation-error.d.ts.map +1 -0
  31. package/dist/core/workflow/workspaces/attestation-error.js +25 -0
  32. package/dist/core/workflow/workspaces/cancellation.d.ts +8 -0
  33. package/dist/core/workflow/workspaces/cancellation.d.ts.map +1 -0
  34. package/dist/core/workflow/workspaces/cancellation.js +95 -0
  35. package/dist/core/workflow/workspaces/cleanup.d.ts +9 -0
  36. package/dist/core/workflow/workspaces/cleanup.d.ts.map +1 -0
  37. package/dist/core/workflow/workspaces/cleanup.js +223 -0
  38. package/dist/core/workflow/workspaces/execution.d.ts +25 -0
  39. package/dist/core/workflow/workspaces/execution.d.ts.map +1 -0
  40. package/dist/core/workflow/workspaces/execution.js +316 -0
  41. package/dist/core/workflow/workspaces/git.d.ts +25 -0
  42. package/dist/core/workflow/workspaces/git.d.ts.map +1 -0
  43. package/dist/core/workflow/workspaces/git.js +102 -0
  44. package/dist/core/workflow/workspaces/index.d.ts +15 -0
  45. package/dist/core/workflow/workspaces/index.d.ts.map +1 -0
  46. package/dist/core/workflow/workspaces/index.js +7 -0
  47. package/dist/core/workflow/workspaces/inspect.d.ts +85 -0
  48. package/dist/core/workflow/workspaces/inspect.d.ts.map +1 -0
  49. package/dist/core/workflow/workspaces/inspect.js +255 -0
  50. package/dist/core/workflow/workspaces/integration.d.ts +32 -0
  51. package/dist/core/workflow/workspaces/integration.d.ts.map +1 -0
  52. package/dist/core/workflow/workspaces/integration.js +631 -0
  53. package/dist/core/workflow/workspaces/leases.d.ts +37 -0
  54. package/dist/core/workflow/workspaces/leases.d.ts.map +1 -0
  55. package/dist/core/workflow/workspaces/leases.js +397 -0
  56. package/dist/core/workflow/workspaces/lifecycle.d.ts +43 -0
  57. package/dist/core/workflow/workspaces/lifecycle.d.ts.map +1 -0
  58. package/dist/core/workflow/workspaces/lifecycle.js +622 -0
  59. package/dist/core/workflow/workspaces/local-git.d.ts +8 -0
  60. package/dist/core/workflow/workspaces/local-git.d.ts.map +1 -0
  61. package/dist/core/workflow/workspaces/local-git.js +1474 -0
  62. package/dist/core/workflow/workspaces/paths.d.ts +23 -0
  63. package/dist/core/workflow/workspaces/paths.d.ts.map +1 -0
  64. package/dist/core/workflow/workspaces/paths.js +191 -0
  65. package/dist/core/workflow/workspaces/recovery.d.ts +3 -0
  66. package/dist/core/workflow/workspaces/recovery.d.ts.map +1 -0
  67. package/dist/core/workflow/workspaces/recovery.js +50 -0
  68. package/dist/core/workflow/workspaces/state.d.ts +60 -0
  69. package/dist/core/workflow/workspaces/state.d.ts.map +1 -0
  70. package/dist/core/workflow/workspaces/state.js +296 -0
  71. package/dist/core/workflow/workspaces/types.d.ts +445 -0
  72. package/dist/core/workflow/workspaces/types.d.ts.map +1 -0
  73. package/dist/core/workflow/workspaces/types.js +2 -0
  74. package/dist/core/workflow/workspaces/validate.d.ts +14 -0
  75. package/dist/core/workflow/workspaces/validate.d.ts.map +1 -0
  76. package/dist/core/workflow/workspaces/validate.js +476 -0
  77. package/package.json +5 -3
  78. package/src/commands/workflow.ts +366 -8
  79. package/src/core/work/state.ts +156 -28
  80. package/src/core/workflow/child-env.ts +18 -7
  81. package/src/core/workflow/durable-record.ts +94 -0
  82. package/src/core/workflow/engine.ts +230 -30
  83. package/src/core/workflow/index.ts +74 -0
  84. package/src/core/workflow/proof.ts +70 -22
  85. package/src/core/workflow/run-state.ts +46 -11
  86. package/src/core/workflow/spawn-cursor.ts +8 -4
  87. package/src/core/workflow/types.ts +14 -0
  88. package/src/core/workflow/workspaces/attestation-error.ts +30 -0
  89. package/src/core/workflow/workspaces/cancellation.ts +137 -0
  90. package/src/core/workflow/workspaces/cleanup.ts +308 -0
  91. package/src/core/workflow/workspaces/execution.ts +412 -0
  92. package/src/core/workflow/workspaces/git.ts +125 -0
  93. package/src/core/workflow/workspaces/index.ts +89 -0
  94. package/src/core/workflow/workspaces/inspect.ts +421 -0
  95. package/src/core/workflow/workspaces/integration.ts +845 -0
  96. package/src/core/workflow/workspaces/leases.ts +517 -0
  97. package/src/core/workflow/workspaces/lifecycle.ts +792 -0
  98. package/src/core/workflow/workspaces/local-git.ts +1892 -0
  99. package/src/core/workflow/workspaces/paths.ts +247 -0
  100. package/src/core/workflow/workspaces/recovery.ts +56 -0
  101. package/src/core/workflow/workspaces/state.ts +481 -0
  102. package/src/core/workflow/workspaces/types.ts +517 -0
  103. package/src/core/workflow/workspaces/validate.ts +595 -0
@@ -0,0 +1,476 @@
1
+ import { isAbsolute, relative, resolve } from "node:path";
2
+ import { policyDigest } from "../../policy/index.js";
3
+ import { stableDigest } from "../durable-record.js";
4
+ const DIGEST = /^[a-f0-9]{64}$/;
5
+ const OBJECT_ID = /^[a-f0-9]{40,64}$/;
6
+ export function isWorkspaceOwner(value) {
7
+ if (!value || typeof value !== "object" || Array.isArray(value))
8
+ return false;
9
+ const owner = value;
10
+ return owner.kind === "standalone"
11
+ ? owner.work_item_id === null && owner.attempt === null
12
+ : owner.kind === "work_attempt" &&
13
+ typeof owner.work_item_id === "string" &&
14
+ owner.work_item_id.length > 0 &&
15
+ Number.isSafeInteger(owner.attempt) &&
16
+ owner.attempt > 0;
17
+ }
18
+ export function isWorkspaceAllocationRequest(value) {
19
+ if (!value || typeof value !== "object" || Array.isArray(value))
20
+ return false;
21
+ const request = value;
22
+ if (request.schema_version !== 1 ||
23
+ typeof request.run_id !== "string" ||
24
+ !isWorkspaceOwner(request.owner) ||
25
+ !isAbsolute(request.requested_cwd) ||
26
+ !["worktree", "sandbox", "remote"].includes(request.requested_isolation) ||
27
+ !["enabled", "disabled", "unknown"].includes(request.network_access) ||
28
+ !isAbsolute(request.script?.path) ||
29
+ !DIGEST.test(request.script?.sha256) ||
30
+ !(request.policy_sha256 === null || DIGEST.test(request.policy_sha256)) ||
31
+ !validatedPathArray(request.allowed_paths) ||
32
+ !validatedPathArray(request.writable_roots) ||
33
+ request.writable_roots.length !== 1 ||
34
+ !validValidatedPath(request.selected_writable_root) ||
35
+ stableDigest(request.writable_roots[0]) !== stableDigest(request.selected_writable_root) ||
36
+ typeof request.provider_id !== "string" ||
37
+ request.provider_id.length === 0 ||
38
+ !DIGEST.test(request.capability_digest) ||
39
+ !DIGEST.test(request.idempotency_key)) {
40
+ return false;
41
+ }
42
+ const { idempotency_key: _idempotencyKey, ...authority } = request;
43
+ return request.idempotency_key === stableDigest(authority);
44
+ }
45
+ export function isWorkspaceBinding(value, runId) {
46
+ if (!value || typeof value !== "object" || Array.isArray(value))
47
+ return false;
48
+ const binding = value;
49
+ return (binding.schema_version === 1 &&
50
+ typeof binding.binding_id === "string" &&
51
+ binding.binding_id.length > 0 &&
52
+ typeof binding.workspace_id === "string" &&
53
+ binding.workspace_id.length > 0 &&
54
+ typeof binding.run_id === "string" &&
55
+ (runId === undefined || binding.run_id === runId) &&
56
+ isWorkspaceOwner(binding.owner) &&
57
+ binding.provider !== undefined &&
58
+ typeof binding.provider.id === "string" &&
59
+ typeof binding.provider.version === "string" &&
60
+ DIGEST.test(binding.provider.capability_digest) &&
61
+ ["worktree", "sandbox", "remote"].includes(binding.isolation) &&
62
+ ["enabled", "disabled", "unknown"].includes(binding.network_access) &&
63
+ isAbsolute(binding.workspace_root) &&
64
+ resolve(binding.workspace_root) === binding.workspace_root &&
65
+ validIdentity(binding.workspace_root_identity) &&
66
+ isAbsolute(binding.active_root) &&
67
+ resolve(binding.active_root) === binding.active_root &&
68
+ containsResolvedPath(binding.workspace_root, binding.active_root) &&
69
+ validIdentity(binding.active_root_identity) &&
70
+ (binding.integration_root === undefined ||
71
+ (isAbsolute(binding.integration_root) &&
72
+ resolve(binding.integration_root) === binding.integration_root)) &&
73
+ validValidatedPath(binding.writable_root) &&
74
+ Number.isSafeInteger(binding.generation) &&
75
+ binding.generation > 0 &&
76
+ /^[a-f0-9]{64}$/.test(binding.recovery_token) &&
77
+ DIGEST.test(binding.request_sha256) &&
78
+ validTimestamp(binding.created_at) &&
79
+ (binding.repository === undefined ||
80
+ (validValidatedPath(binding.repository.source_root) &&
81
+ validObservedIdentity(binding.repository.common_dir) &&
82
+ OBJECT_ID.test(binding.repository.base_commit) &&
83
+ OBJECT_ID.test(binding.repository.target_commit) &&
84
+ typeof binding.repository.target_ref === "string" &&
85
+ typeof binding.repository.workspace_ref === "string" &&
86
+ typeof binding.repository.workspace_branch === "string" &&
87
+ validObservedIdentity(binding.repository.gitdir) &&
88
+ typeof binding.repository.branch_created_by_provider === "boolean")));
89
+ }
90
+ export function isWorkspaceAttestation(value, binding) {
91
+ if (!value || typeof value !== "object" || Array.isArray(value))
92
+ return false;
93
+ const attestation = value;
94
+ const containment = attestation.containment;
95
+ const filesystem = attestation.filesystem;
96
+ const validContainment = containment !== undefined &&
97
+ containment !== null &&
98
+ typeof containment === "object" &&
99
+ typeof containment.writable_root === "boolean" &&
100
+ typeof containment.workspace_root === "boolean" &&
101
+ typeof containment.active_root === "boolean" &&
102
+ (typeof containment.integration_root === "boolean" || containment.integration_root === null);
103
+ const validFilesystem = filesystem !== undefined &&
104
+ filesystem !== null &&
105
+ typeof filesystem === "object" &&
106
+ typeof filesystem.root_identity_match === "boolean" &&
107
+ (filesystem.workspace_identity === undefined || validIdentity(filesystem.workspace_identity)) &&
108
+ (filesystem.active_identity === undefined || validIdentity(filesystem.active_identity));
109
+ const repository = attestation.repository;
110
+ const validDrift = stringArray(attestation.provider_drift);
111
+ const validUnsupported = providerFactArray(attestation.unsupported);
112
+ const validUnknowns = providerFactArray(attestation.unknowns);
113
+ const validRepository = repository === undefined ||
114
+ (typeof repository === "object" &&
115
+ repository !== null &&
116
+ validObservedSourceRoot(repository.source_root) &&
117
+ (repository.common_dir === undefined || validObservedIdentity(repository.common_dir)) &&
118
+ (repository.active_gitdir === undefined || validObservedIdentity(repository.active_gitdir)) &&
119
+ typeof repository.worktree_registered === "boolean" &&
120
+ (repository.current_ref === undefined || typeof repository.current_ref === "string") &&
121
+ (repository.workspace_ref_oid === undefined ||
122
+ OBJECT_ID.test(repository.workspace_ref_oid)) &&
123
+ (repository.target_ref_oid === undefined || OBJECT_ID.test(repository.target_ref_oid)) &&
124
+ (repository.head_commit === undefined || OBJECT_ID.test(repository.head_commit)) &&
125
+ (typeof repository.base_is_ancestor === "boolean" || repository.base_is_ancestor === null) &&
126
+ (typeof repository.target_is_ancestor === "boolean" ||
127
+ repository.target_is_ancestor === null) &&
128
+ stringArray(repository.dirty_paths) &&
129
+ stringArray(repository.conflicts) &&
130
+ stringArray(repository.operation_in_progress));
131
+ const validReleasedAuthority = attestation.resource_state === "released" &&
132
+ attestation.workspace_exists === false &&
133
+ containment?.writable_root === true &&
134
+ containment.workspace_root === false &&
135
+ containment.active_root === false &&
136
+ filesystem?.root_identity_match === true &&
137
+ filesystem.workspace_identity === undefined &&
138
+ filesystem.active_identity === undefined &&
139
+ (binding.repository === undefined ||
140
+ (repository !== undefined &&
141
+ repositorySourceMatches(repository, binding) &&
142
+ repositoryCommonDirMatches(repository, binding) &&
143
+ repository?.active_gitdir === undefined &&
144
+ repository.worktree_registered === false &&
145
+ repository.current_ref === undefined &&
146
+ repository.workspace_ref_oid === undefined &&
147
+ repository.target_ref_oid !== undefined &&
148
+ repository.head_commit === undefined &&
149
+ repository.base_is_ancestor === null &&
150
+ repository.target_is_ancestor === null &&
151
+ repository.dirty_paths.length === 0 &&
152
+ repository.conflicts.length === 0 &&
153
+ repository.operation_in_progress.length === 0));
154
+ const validActiveAuthority = (attestation.resource_state === "active" || attestation.resource_state === "preserved_dirty") &&
155
+ attestation.workspace_exists === true &&
156
+ containment?.writable_root === true &&
157
+ containment.workspace_root === true &&
158
+ containment.active_root === true &&
159
+ (binding.integration_root === undefined || containment.integration_root === true) &&
160
+ filesystem?.root_identity_match === true &&
161
+ validIdentity(filesystem.workspace_identity) &&
162
+ validIdentity(filesystem.active_identity) &&
163
+ sameIdentity(filesystem.workspace_identity, binding.workspace_root_identity) &&
164
+ sameIdentity(filesystem.active_identity, binding.active_root_identity) &&
165
+ (binding.repository === undefined ||
166
+ (repository !== undefined &&
167
+ repositorySourceMatches(repository, binding) &&
168
+ repositoryCommonDirMatches(repository, binding) &&
169
+ repositoryGitdirMatches(repository, binding) &&
170
+ repository.worktree_registered === true &&
171
+ binding.repository.branch_created_by_provider === true &&
172
+ repository.current_ref === binding.repository.workspace_ref &&
173
+ repository.workspace_ref_oid !== undefined &&
174
+ repository.target_ref_oid !== undefined &&
175
+ repository.head_commit !== undefined &&
176
+ repository.workspace_ref_oid === repository.head_commit &&
177
+ repository.base_is_ancestor === true &&
178
+ repository.target_is_ancestor === true));
179
+ const validOkAuthority = attestation.status !== "ok" ||
180
+ (validDrift &&
181
+ attestation.provider_drift.length === 0 &&
182
+ (validActiveAuthority || validReleasedAuthority));
183
+ const validFailureEvidence = validDrift &&
184
+ validUnsupported &&
185
+ validUnknowns &&
186
+ (attestation.status === "ok" ||
187
+ repository !== undefined ||
188
+ attestation.provider_drift.length > 0 ||
189
+ attestation.unsupported.length > 0 ||
190
+ attestation.unknowns.length > 0);
191
+ return (attestation.schema_version === 1 &&
192
+ attestation.binding_id === binding.binding_id &&
193
+ attestation.workspace_id === binding.workspace_id &&
194
+ attestation.run_id === binding.run_id &&
195
+ sameOwner(attestation.owner, binding.owner) &&
196
+ validProviderRef(attestation.provider) &&
197
+ attestation.provider.id === binding.provider.id &&
198
+ attestation.provider.version === binding.provider.version &&
199
+ attestation.provider.capability_digest === binding.provider.capability_digest &&
200
+ validTimestamp(attestation.recorded_at) &&
201
+ validContainment &&
202
+ validFilesystem &&
203
+ validRepository &&
204
+ validOkAuthority &&
205
+ validFailureEvidence &&
206
+ (binding.repository === undefined
207
+ ? repository === undefined
208
+ : attestation.status !== "ok" || repository !== undefined) &&
209
+ typeof attestation.workspace_exists === "boolean" &&
210
+ ["active", "preserved_dirty", "released", "blocked", "lost"].includes(attestation.resource_state) &&
211
+ (attestation.status === "blocked"
212
+ ? attestation.resource_state === "blocked"
213
+ : attestation.status === "lost"
214
+ ? attestation.resource_state === "lost"
215
+ : attestation.resource_state === "active" ||
216
+ attestation.resource_state === "preserved_dirty" ||
217
+ attestation.resource_state === "released") &&
218
+ ["ok", "blocked", "lost"].includes(attestation.status) &&
219
+ validDrift &&
220
+ validUnsupported &&
221
+ validUnknowns);
222
+ }
223
+ export function isWorkspaceExecutionEvidence(value, runId, workflowStatus) {
224
+ if (!value || typeof value !== "object" || Array.isArray(value))
225
+ return false;
226
+ const execution = value;
227
+ if (isWorkspaceCompatibilityExecutionEvidence(execution, runId))
228
+ return true;
229
+ return isWorkspaceBoundExecutionEvidence(execution, runId, workflowStatus);
230
+ }
231
+ export function isWorkspaceBoundExecutionEvidence(value, runId, workflowStatus) {
232
+ if (!value || typeof value !== "object" || Array.isArray(value))
233
+ return false;
234
+ const execution = value;
235
+ return (execution.schema_version === 1 &&
236
+ isWorkspaceBinding(execution.binding, runId) &&
237
+ isWorkspaceAttestation(execution.terminal_attestation, execution.binding) &&
238
+ validWorkspaceProofOutcome(execution.terminal_lifecycle_state, execution.terminal_attestation, workflowStatus) &&
239
+ stableDigest(execution.drift) === stableDigest(execution.terminal_attestation.provider_drift) &&
240
+ stableDigest(execution.unsupported) ===
241
+ stableDigest(execution.terminal_attestation.unsupported) &&
242
+ stableDigest(execution.unknowns) === stableDigest(execution.terminal_attestation.unknowns) &&
243
+ validBoundReceiptLocations(execution.receipts));
244
+ }
245
+ export function workspaceProofLifecycle(workflowStatus, attestation) {
246
+ if (attestation.status === "lost")
247
+ return "lost";
248
+ if (attestation.status === "blocked")
249
+ return "blocked";
250
+ return workflowStatus === "succeeded" ? "completed_unintegrated" : "failed_retained";
251
+ }
252
+ export function assertWorkspaceManifestAuthority(manifest, request) {
253
+ const binding = manifest.execution.workspace_binding;
254
+ const fallback = manifest.execution.workspace_fallback;
255
+ if (binding) {
256
+ if (fallback) {
257
+ throw new Error("workflow manifest cannot bind a workspace and a fallback");
258
+ }
259
+ const expectedOwner = manifest.work_item_id && manifest.attempt_context
260
+ ? {
261
+ kind: "work_attempt",
262
+ work_item_id: manifest.work_item_id,
263
+ attempt: manifest.attempt_context.number,
264
+ }
265
+ : { kind: "standalone", work_item_id: null, attempt: null };
266
+ const expectedPolicyDigest = manifest.execution.policy
267
+ ? policyDigest(manifest.execution.policy)
268
+ : null;
269
+ if (!isWorkspaceAllocationRequest(request) ||
270
+ request.run_id !== manifest.run_id ||
271
+ binding.run_id !== manifest.run_id ||
272
+ stableDigest(request.owner) !== stableDigest(expectedOwner) ||
273
+ stableDigest(binding.owner) !== stableDigest(expectedOwner) ||
274
+ request.requested_isolation !== manifest.execution.isolation ||
275
+ binding.isolation !== manifest.execution.isolation ||
276
+ request.network_access !== manifest.execution.network_access ||
277
+ binding.network_access !== manifest.execution.network_access ||
278
+ manifest.execution.cwd !== binding.active_root ||
279
+ request.provider_id !== binding.provider.id ||
280
+ request.capability_digest !== binding.provider.capability_digest ||
281
+ stableDigest(request.selected_writable_root) !== stableDigest(binding.writable_root) ||
282
+ request.writable_roots.length !== 1 ||
283
+ stableDigest(request.writable_roots[0]) !== stableDigest(request.selected_writable_root) ||
284
+ stableDigest(request.script) !== stableDigest(manifest.script) ||
285
+ request.policy_sha256 !== expectedPolicyDigest ||
286
+ stableDigest(request.allowed_paths.map((path) => path.configured)) !==
287
+ stableDigest(manifest.execution.policy?.allowed_paths ?? []) ||
288
+ binding.request_sha256 !== stableDigest(request) ||
289
+ (binding.repository !== undefined &&
290
+ (!workspaceCwdMatchesBinding(request, binding) ||
291
+ binding.integration_root !== binding.repository.source_root.realpath))) {
292
+ throw new Error("workflow manifest, workspace request, and provider binding disagree");
293
+ }
294
+ return;
295
+ }
296
+ if (request) {
297
+ throw new Error("workflow manifest has an unbound workspace request");
298
+ }
299
+ if (fallback &&
300
+ (fallback.run_id !== manifest.run_id ||
301
+ fallback.requested_isolation !== manifest.execution.isolation ||
302
+ fallback.effective_isolation !== "shared")) {
303
+ throw new Error("workflow manifest and workspace fallback disagree");
304
+ }
305
+ }
306
+ function workspaceCwdMatchesBinding(request, binding) {
307
+ if (!binding.repository)
308
+ return true;
309
+ const relativeCwd = relative(binding.repository.source_root.configured, request.requested_cwd);
310
+ return ((relativeCwd === "" ||
311
+ (!relativeCwd.startsWith(`..${process.platform === "win32" ? "\\" : "/"}`) &&
312
+ relativeCwd !== ".." &&
313
+ !isAbsolute(relativeCwd))) &&
314
+ binding.active_root === resolve(binding.workspace_root, relativeCwd));
315
+ }
316
+ function validWorkspaceProofOutcome(outcome, attestation, workflowStatus) {
317
+ if (workflowStatus !== undefined) {
318
+ return outcome === workspaceProofLifecycle(workflowStatus, attestation);
319
+ }
320
+ if (attestation.status === "lost")
321
+ return outcome === "lost";
322
+ if (attestation.status === "blocked")
323
+ return outcome === "blocked";
324
+ return outcome === "completed_unintegrated" || outcome === "failed_retained";
325
+ }
326
+ export function isWorkspaceCompatibilityExecutionEvidence(value, runId) {
327
+ if (!value || typeof value !== "object" || Array.isArray(value))
328
+ return false;
329
+ const execution = value;
330
+ const providerNotConfigured = execution.selection_reason === "provider_not_configured" &&
331
+ execution.provider === undefined &&
332
+ execution.unsupported.some((fact) => fact.code === "provider_not_configured");
333
+ const providerUnsupported = execution.selection_reason === "provider_unsupported" && validProviderRef(execution.provider);
334
+ return (execution.schema_version === 1 &&
335
+ typeof execution.run_id === "string" &&
336
+ (runId === undefined || execution.run_id === runId) &&
337
+ ["worktree", "sandbox", "remote"].includes(execution.requested_isolation) &&
338
+ execution.effective_isolation === "shared" &&
339
+ (providerNotConfigured || providerUnsupported) &&
340
+ execution.terminal_lifecycle_state === "shared" &&
341
+ Array.isArray(execution.drift) &&
342
+ execution.drift.length === 0 &&
343
+ providerFactArray(execution.unsupported) &&
344
+ execution.unsupported.length > 0 &&
345
+ providerFactArray(execution.unknowns) &&
346
+ execution.receipts !== null &&
347
+ typeof execution.receipts === "object" &&
348
+ !Array.isArray(execution.receipts) &&
349
+ Object.keys(execution.receipts).length === 0);
350
+ }
351
+ /** @deprecated Use isWorkspaceCompatibilityExecutionEvidence. */
352
+ export const isWorkspaceUnsupportedExecutionEvidence = isWorkspaceCompatibilityExecutionEvidence;
353
+ function sameOwner(left, right) {
354
+ return (left.kind === right.kind &&
355
+ left.work_item_id === right.work_item_id &&
356
+ left.attempt === right.attempt);
357
+ }
358
+ function repositorySourceMatches(observation, binding) {
359
+ const expected = binding.repository?.source_root;
360
+ return Boolean(observation &&
361
+ expected &&
362
+ observation.source_root &&
363
+ observation.source_root.configured === expected.configured &&
364
+ observation.source_root.realpath === expected.realpath &&
365
+ sameIdentity(observation.source_root.identity, expected.identity));
366
+ }
367
+ function repositoryCommonDirMatches(observation, binding) {
368
+ const expected = binding.repository?.common_dir;
369
+ return Boolean(observation &&
370
+ expected &&
371
+ observation.common_dir?.realpath === expected.realpath &&
372
+ sameIdentity(observation.common_dir?.identity, expected.identity));
373
+ }
374
+ function repositoryGitdirMatches(observation, binding) {
375
+ const expected = binding.repository?.gitdir;
376
+ return Boolean(observation &&
377
+ expected &&
378
+ observation.active_gitdir?.realpath === expected.realpath &&
379
+ sameIdentity(observation.active_gitdir?.identity, expected.identity));
380
+ }
381
+ function validObservedSourceRoot(value) {
382
+ if (!value || typeof value !== "object" || Array.isArray(value))
383
+ return false;
384
+ const root = value;
385
+ return (typeof root.configured === "string" &&
386
+ isAbsolute(root.configured) &&
387
+ resolve(root.configured) === root.configured &&
388
+ ((root.realpath === undefined && root.identity === undefined) ||
389
+ (typeof root.realpath === "string" &&
390
+ isAbsolute(root.realpath) &&
391
+ resolve(root.realpath) === root.realpath &&
392
+ validIdentity(root.identity))));
393
+ }
394
+ function validObservedIdentity(value) {
395
+ if (!value || typeof value !== "object" || Array.isArray(value))
396
+ return false;
397
+ const root = value;
398
+ return (typeof root.realpath === "string" &&
399
+ isAbsolute(root.realpath) &&
400
+ resolve(root.realpath) === root.realpath &&
401
+ validIdentity(root.identity));
402
+ }
403
+ function sameIdentity(left, right) {
404
+ if (!validIdentity(left) || !validIdentity(right))
405
+ return false;
406
+ return stableDigest(left) === stableDigest(right);
407
+ }
408
+ function validBoundReceiptLocations(value) {
409
+ if (!value || typeof value !== "object" || Array.isArray(value))
410
+ return false;
411
+ const expected = {
412
+ request: "workspace-request.json",
413
+ cancellation_outcome: "cancellation/outcome.json",
414
+ integration_plan: "integration/plan.json",
415
+ integration_authorization: "integration/authorization.json",
416
+ integration_apply: "integration/receipt.json",
417
+ cleanup_intent: "cleanup/intent.json",
418
+ cleanup_receipt: "cleanup/receipt.json",
419
+ };
420
+ return (value.request === expected.request &&
421
+ Object.entries(value).every(([key, location]) => expected[key] === location));
422
+ }
423
+ function validIdentity(value) {
424
+ if (!value || typeof value !== "object")
425
+ return false;
426
+ const identity = value;
427
+ return (typeof identity.platform === "string" &&
428
+ typeof identity.device === "string" &&
429
+ typeof identity.inode === "string");
430
+ }
431
+ function validProviderRef(value) {
432
+ if (!value || typeof value !== "object" || Array.isArray(value))
433
+ return false;
434
+ const provider = value;
435
+ return (typeof provider.id === "string" &&
436
+ provider.id.length > 0 &&
437
+ typeof provider.version === "string" &&
438
+ provider.version.length > 0 &&
439
+ typeof provider.capability_digest === "string" &&
440
+ DIGEST.test(provider.capability_digest));
441
+ }
442
+ function validTimestamp(value) {
443
+ return typeof value === "string" && value.length <= 40 && Number.isFinite(Date.parse(value));
444
+ }
445
+ function stringArray(value) {
446
+ return Array.isArray(value) && value.every((item) => typeof item === "string");
447
+ }
448
+ function validValidatedPath(value) {
449
+ if (!value || typeof value !== "object" || Array.isArray(value))
450
+ return false;
451
+ const path = value;
452
+ return (typeof path.configured === "string" &&
453
+ isAbsolute(path.configured) &&
454
+ resolve(path.configured) === path.configured &&
455
+ typeof path.realpath === "string" &&
456
+ isAbsolute(path.realpath) &&
457
+ resolve(path.realpath) === path.realpath &&
458
+ validIdentity(path.identity));
459
+ }
460
+ function validatedPathArray(value) {
461
+ return Array.isArray(value) && value.every(validValidatedPath);
462
+ }
463
+ function containsResolvedPath(root, candidate) {
464
+ const rel = relative(root, candidate);
465
+ return (rel === "" ||
466
+ (rel !== ".." && !rel.startsWith("../") && !rel.startsWith("..\\") && !isAbsolute(rel)));
467
+ }
468
+ function providerFactArray(value) {
469
+ return (Array.isArray(value) &&
470
+ value.every((item) => item !== null &&
471
+ typeof item === "object" &&
472
+ typeof item.code === "string" &&
473
+ item.code.length > 0 &&
474
+ typeof item.message === "string" &&
475
+ item.message.length > 0));
476
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "harnery",
3
- "version": "0.22.1",
3
+ "version": "0.24.0",
4
4
  "description": "Multi-agent coordination for AI coding agents - Claude Code, Cursor, and Codex.",
5
5
  "license": "MIT",
6
6
  "author": "Ryan Kelly",
@@ -148,7 +148,7 @@
148
148
  "agent-coord": "./bin/agent-coord",
149
149
  "agent-hook": "./bin/agent-hook"
150
150
  },
151
- "//files": "src ships as TS so the `bun` export condition resolves to source; negate co-located tests so they don't ride into the tarball (a .npmignore is inert for paths already inside the files allowlist).",
151
+ "//files": "src ships as TS so the `bun` export condition resolves to source; negate co-located tests and their shared fixture helper so they don't ride into the tarball (a .npmignore is inert for paths already inside the files allowlist).",
152
152
  "files": [
153
153
  "bin",
154
154
  "src",
@@ -157,7 +157,9 @@
157
157
  "README.md",
158
158
  "LICENSE",
159
159
  "!src/**/*.test.ts",
160
- "!src/**/*.test.tsx"
160
+ "!src/**/*.test.tsx",
161
+ "!dist/**/test-helpers.*",
162
+ "!src/**/test-helpers.ts"
161
163
  ],
162
164
  "engines": {
163
165
  "node": ">=20.19.0"