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,829 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { join, resolve } from "node:path";
3
+ import {
4
+ evaluatePolicy,
5
+ type NormalizedPolicy,
6
+ normalizePolicy,
7
+ type PolicyDecision,
8
+ type PolicySpec,
9
+ policyDigest,
10
+ summarizePolicyRequest,
11
+ } from "../../policy/index.ts";
12
+ import { readWorkItem } from "../../work/state.ts";
13
+ import { createWorkflowApproval, readWorkflowApproval } from "../approvals.ts";
14
+ import { readWorkflowProof } from "../proof.ts";
15
+ import { readWorkflowRunManifest } from "../run-state.ts";
16
+ import { acquireNoClobberLease } from "./leases.ts";
17
+ import {
18
+ appendIntegrationAttempt,
19
+ appendWorkflowJournalEvent,
20
+ fileSha256,
21
+ readIntegrationAttempts,
22
+ readWorkflowSupplement,
23
+ stableDigest,
24
+ workflowRunDir,
25
+ writeWorkflowSupplement,
26
+ } from "./state.ts";
27
+ import type {
28
+ AuthorizedIntegrationPlan,
29
+ IntegrationApplyReceipt,
30
+ IntegrationAuthorization,
31
+ IntegrationPlan,
32
+ IntegrationReviewRecord,
33
+ ProviderIntegrationInput,
34
+ ProviderIntegrationPreview,
35
+ ProviderIntegrationResult,
36
+ WorkspaceBinding,
37
+ WorkspaceBoundExecutionEvidence,
38
+ WorkspaceProvider,
39
+ } from "./types.ts";
40
+ import {
41
+ isWorkspaceAllocationRequest,
42
+ isWorkspaceAttestation,
43
+ isWorkspaceBoundExecutionEvidence,
44
+ } from "./validate.ts";
45
+
46
+ const INTEGRATION_DECISION_ID = "p99999";
47
+ const TARGET_LEASE_STALE_MS = 5 * 60 * 1_000;
48
+
49
+ export interface PrepareIntegrationInput {
50
+ coordRoot: string;
51
+ runId: string;
52
+ provider: WorkspaceProvider;
53
+ policy: PolicySpec | NormalizedPolicy;
54
+ targetRoot?: string;
55
+ review?: { actor: string; reason?: string };
56
+ acceptedUnknowns?: readonly string[];
57
+ approvalAddressee?: string;
58
+ }
59
+
60
+ export interface ApplyIntegrationInput {
61
+ coordRoot: string;
62
+ runId: string;
63
+ provider: WorkspaceProvider;
64
+ plan?: IntegrationPlan;
65
+ }
66
+
67
+ export async function prepareIntegration(input: PrepareIntegrationInput): Promise<IntegrationPlan> {
68
+ const evidence = await readAndValidateEvidence(input.coordRoot, input.runId, input.provider);
69
+ const targetRoot = resolve(input.targetRoot ?? requiredIntegrationRoot(evidence.binding));
70
+ const release = acquireTargetLease(input.coordRoot, targetRoot, input.runId);
71
+ try {
72
+ const proofSha256 = fileSha256(
73
+ join(workflowRunDir(input.coordRoot, input.runId), "proof.json"),
74
+ );
75
+ const acceptedUnknowns = [...new Set(input.acceptedUnknowns ?? [])].sort();
76
+ const unknownCodes = [
77
+ ...evidence.proof.unknowns.map((item) => item.code),
78
+ ...evidence.attestation.unknowns.map((item) => item.code),
79
+ ];
80
+ const unaccepted = unknownCodes.filter((code) => !acceptedUnknowns.includes(code));
81
+ if (unaccepted.length > 0) {
82
+ throw new Error(
83
+ `integration has unaccepted unknown verification facts: ${unaccepted.join(", ")}`,
84
+ );
85
+ }
86
+ const terminalAttestationSha256 = stableDigest(evidence.proof.execution!.terminal_attestation);
87
+ const review = resolveReview(
88
+ input,
89
+ evidence.binding,
90
+ proofSha256,
91
+ terminalAttestationSha256,
92
+ acceptedUnknowns,
93
+ );
94
+ const reviewSha256 = stableDigest(review);
95
+
96
+ const preview = await input.provider.previewIntegration({
97
+ schema_version: 1,
98
+ run_id: input.runId,
99
+ binding: evidence.binding,
100
+ binding_sha256: stableDigest(evidence.binding),
101
+ proof_sha256: proofSha256,
102
+ terminal_attestation: evidence.proof.execution!.terminal_attestation,
103
+ terminal_attestation_sha256: terminalAttestationSha256,
104
+ review_sha256: reviewSha256,
105
+ accepted_unknowns: acceptedUnknowns,
106
+ target: { root: targetRoot },
107
+ });
108
+ assertVerifiedSourceCommit(evidence.proof, preview.source_commit);
109
+ if (preview.blocked.length > 0) {
110
+ throw new Error(
111
+ `integration preview is blocked: ${preview.blocked.map((item) => item.message).join("; ")}`,
112
+ );
113
+ }
114
+ const previewUnknowns = preview.unknowns
115
+ .map((item) => item.code)
116
+ .filter((code) => !acceptedUnknowns.includes(code));
117
+ if (previewUnknowns.length > 0) {
118
+ throw new Error(`integration preview has unaccepted unknowns: ${previewUnknowns.join(", ")}`);
119
+ }
120
+ const planAuthority = {
121
+ run: input.runId,
122
+ binding: stableDigest(evidence.binding),
123
+ proof: proofSha256,
124
+ terminalAttestation: stableDigest(evidence.proof.execution!.terminal_attestation),
125
+ review: reviewSha256,
126
+ target: preview.target_root,
127
+ targetRef: preview.target_ref,
128
+ targetCommit: preview.target_commit,
129
+ sourceCommit: preview.source_commit,
130
+ operation: preview.operation,
131
+ };
132
+ const proposed: IntegrationPlan = {
133
+ schema_version: 1,
134
+ plan_id: `integration-plan-${stableDigest(planAuthority).slice(0, 24)}`,
135
+ run_id: input.runId,
136
+ operation: "fast_forward",
137
+ binding: evidence.binding,
138
+ binding_sha256: stableDigest(evidence.binding),
139
+ proof_sha256: proofSha256,
140
+ terminal_attestation_sha256: terminalAttestationSha256,
141
+ review_sha256: reviewSha256,
142
+ accepted_unknowns: acceptedUnknowns,
143
+ provider_preview: preview,
144
+ target_identity_sha256: stableDigest({
145
+ root: preview.target_root,
146
+ ref: preview.target_ref,
147
+ commit: preview.target_commit,
148
+ tree: preview.target_tree,
149
+ }),
150
+ idempotency_sha256: stableDigest(planAuthority),
151
+ prepared_at: new Date().toISOString(),
152
+ };
153
+ const prior = readWorkflowSupplement<IntegrationPlan>(
154
+ input.coordRoot,
155
+ input.runId,
156
+ "integration/plan.json",
157
+ );
158
+ const plan = prior ?? proposed;
159
+ if (prior && !samePlanAuthority(prior, proposed)) {
160
+ throw new Error("existing integration plan no longer matches current evidence");
161
+ }
162
+ if (!prior) {
163
+ writeWorkflowSupplement(input.coordRoot, input.runId, "integration/plan.json", plan);
164
+ appendWorkflowJournalEvent(input.coordRoot, input.runId, "integration.plan", {
165
+ plan_id: plan.plan_id,
166
+ plan_sha256: stableDigest(plan),
167
+ });
168
+ }
169
+ authorizePlan(input, plan);
170
+ return plan;
171
+ } finally {
172
+ release();
173
+ }
174
+ }
175
+
176
+ export async function applyIntegration(
177
+ input: ApplyIntegrationInput,
178
+ ): Promise<IntegrationApplyReceipt> {
179
+ const storedPlan = readWorkflowSupplement<IntegrationPlan>(
180
+ input.coordRoot,
181
+ input.runId,
182
+ "integration/plan.json",
183
+ );
184
+ if (!storedPlan) throw new Error("integration apply requires a durable plan");
185
+ if (input.plan && stableDigest(input.plan) !== stableDigest(storedPlan)) {
186
+ throw new Error("integration apply caller plan does not match the durable plan");
187
+ }
188
+ const release = acquireTargetLease(
189
+ input.coordRoot,
190
+ storedPlan.provider_preview.target_root,
191
+ input.runId,
192
+ );
193
+ try {
194
+ const evidence = await readAndValidateEvidence(input.coordRoot, input.runId, input.provider);
195
+ const proofSha256 = fileSha256(
196
+ join(workflowRunDir(input.coordRoot, input.runId), "proof.json"),
197
+ );
198
+ if (
199
+ proofSha256 !== storedPlan.proof_sha256 ||
200
+ stableDigest(evidence.binding) !== storedPlan.binding_sha256 ||
201
+ stableDigest(storedPlan.binding) !== storedPlan.binding_sha256 ||
202
+ stableDigest(evidence.proof.execution!.terminal_attestation) !==
203
+ storedPlan.terminal_attestation_sha256
204
+ ) {
205
+ throw new Error("integration proof or binding changed after planning");
206
+ }
207
+ const review = readReviewForPlan(input.coordRoot, storedPlan);
208
+ if (stableDigest(review) !== storedPlan.review_sha256) {
209
+ throw new Error("integration review authority changed or disappeared");
210
+ }
211
+ const authorization = readWorkflowSupplement<IntegrationAuthorization>(
212
+ input.coordRoot,
213
+ input.runId,
214
+ "integration/authorization.json",
215
+ );
216
+ if (!authorization) throw new Error("integration authorization disappeared");
217
+ const planSha256 = stableDigest(storedPlan);
218
+ if (authorization.plan_sha256 !== planSha256 || authorization.decision.verdict !== "allow") {
219
+ throw new Error("integration authorization does not permit the durable plan");
220
+ }
221
+ if (
222
+ authorization.decision_sha256 !== stableDigest(authorization.decision) ||
223
+ authorization.journal_anchor.event !== "integration.plan" ||
224
+ authorization.journal_anchor.plan_sha256 !== planSha256
225
+ ) {
226
+ throw new Error("integration authorization digest or journal anchor is corrupt");
227
+ }
228
+ assertIntegrationPlanJournal(input.coordRoot, input.runId, planSha256);
229
+ if (authorization.approval_id) {
230
+ const approval = readWorkflowApproval(input.coordRoot, authorization.approval_id);
231
+ if (
232
+ approval.status !== "approved" ||
233
+ approval.request.run_id !== input.runId ||
234
+ approval.decision?.actor !== authorization.approval_actor ||
235
+ authorization.approval_sha256 !== stableDigest(approval)
236
+ ) {
237
+ throw new Error("integration approval authority is missing or mismatched");
238
+ }
239
+ }
240
+
241
+ const authorized: AuthorizedIntegrationPlan = {
242
+ schema_version: 1,
243
+ plan: storedPlan,
244
+ plan_sha256: planSha256,
245
+ authorization,
246
+ authorization_sha256: stableDigest(authorization),
247
+ };
248
+ const previewInput: ProviderIntegrationInput = {
249
+ schema_version: 1,
250
+ run_id: input.runId,
251
+ binding: evidence.binding,
252
+ binding_sha256: stableDigest(evidence.binding),
253
+ proof_sha256: proofSha256,
254
+ terminal_attestation: evidence.proof.execution!.terminal_attestation,
255
+ terminal_attestation_sha256: storedPlan.terminal_attestation_sha256,
256
+ review_sha256: storedPlan.review_sha256,
257
+ accepted_unknowns: storedPlan.accepted_unknowns,
258
+ target: {
259
+ root: storedPlan.provider_preview.target_root,
260
+ ref: storedPlan.provider_preview.target_ref,
261
+ },
262
+ };
263
+ const currentPreview = await input.provider.previewIntegration(previewInput);
264
+ assertVerifiedSourceCommit(evidence.proof, currentPreview.source_commit);
265
+ if (
266
+ currentPreview.provider_id !== storedPlan.provider_preview.provider_id ||
267
+ currentPreview.binding_id !== storedPlan.binding.binding_id ||
268
+ currentPreview.target_root !== storedPlan.provider_preview.target_root ||
269
+ currentPreview.target_ref !== storedPlan.provider_preview.target_ref ||
270
+ currentPreview.source_commit !== storedPlan.provider_preview.source_commit ||
271
+ currentPreview.source_tree !== storedPlan.provider_preview.source_tree
272
+ ) {
273
+ throw new Error("integration source, provider, or target identity drifted");
274
+ }
275
+ if (currentPreview.blocked.length > 0) {
276
+ throw new Error(
277
+ `integration preview is blocked: ${currentPreview.blocked
278
+ .map((item) => item.message)
279
+ .join("; ")}`,
280
+ );
281
+ }
282
+ for (const attempt of readIntegrationAttempts(input.coordRoot, input.runId)) {
283
+ if (
284
+ attempt.run_id !== input.runId ||
285
+ attempt.plan_sha256 !== planSha256 ||
286
+ attempt.authorization_sha256 !== authorized.authorization_sha256 ||
287
+ attempt.proof_sha256 !== proofSha256 ||
288
+ attempt.binding_sha256 !== stableDigest(evidence.binding)
289
+ ) {
290
+ throw new Error("integration attempt does not match current durable authority");
291
+ }
292
+ }
293
+ const prior = readWorkflowSupplement<IntegrationApplyReceipt>(
294
+ input.coordRoot,
295
+ input.runId,
296
+ "integration/receipt.json",
297
+ );
298
+ if (prior) {
299
+ if (
300
+ prior.plan_sha256 !== planSha256 ||
301
+ prior.authorization_sha256 !== authorized.authorization_sha256 ||
302
+ prior.proof_sha256 !== proofSha256 ||
303
+ prior.binding_sha256 !== stableDigest(evidence.binding) ||
304
+ prior.target_commit !== currentPreview.target_commit ||
305
+ prior.target_tree !== currentPreview.target_tree ||
306
+ prior.source_commit !== storedPlan.provider_preview.source_commit
307
+ ) {
308
+ throw new Error("existing integration receipt does not match current target authority");
309
+ }
310
+ return prior;
311
+ }
312
+ appendIntegrationAttempt(input.coordRoot, input.runId, {
313
+ schema_version: 1,
314
+ run_id: input.runId,
315
+ plan_sha256: planSha256,
316
+ authorization_sha256: authorized.authorization_sha256,
317
+ proof_sha256: proofSha256,
318
+ binding_sha256: stableDigest(evidence.binding),
319
+ status: "started",
320
+ recorded_at: new Date().toISOString(),
321
+ });
322
+ const providerResult = await input.provider.applyAuthorizedIntegration({
323
+ schema_version: 1,
324
+ run_id: input.runId,
325
+ binding: evidence.binding,
326
+ binding_sha256: stableDigest(evidence.binding),
327
+ plan_id: storedPlan.plan_id,
328
+ plan_sha256: planSha256,
329
+ authorization_sha256: authorized.authorization_sha256,
330
+ proof_sha256: proofSha256,
331
+ terminal_attestation_sha256: storedPlan.terminal_attestation_sha256,
332
+ review_sha256: storedPlan.review_sha256,
333
+ accepted_unknowns: storedPlan.accepted_unknowns,
334
+ preview: storedPlan.provider_preview,
335
+ });
336
+ validateProviderIntegrationResult(providerResult, storedPlan, currentPreview);
337
+ const postApplyAttestation = await input.provider.reattach(evidence.binding);
338
+ if (
339
+ !isWorkspaceAttestation(postApplyAttestation, evidence.binding) ||
340
+ postApplyAttestation.status !== "ok" ||
341
+ stableDigest(postApplyAttestation.owner) !== stableDigest(evidence.binding.owner)
342
+ ) {
343
+ throw new Error("integration result could not reattach the frozen workspace");
344
+ }
345
+ const postApplyPreview = await input.provider.previewIntegration(previewInput);
346
+ validatePostApplyState(providerResult, storedPlan, postApplyPreview);
347
+ appendIntegrationAttempt(input.coordRoot, input.runId, {
348
+ schema_version: 1,
349
+ run_id: input.runId,
350
+ plan_sha256: planSha256,
351
+ authorization_sha256: authorized.authorization_sha256,
352
+ proof_sha256: proofSha256,
353
+ binding_sha256: stableDigest(evidence.binding),
354
+ status: providerResult.status,
355
+ target_commit: providerResult.target_commit,
356
+ target_tree: providerResult.target_tree,
357
+ recorded_at: providerResult.applied_at,
358
+ });
359
+
360
+ const receipt: IntegrationApplyReceipt = {
361
+ schema_version: 1,
362
+ receipt_id: `integration-apply-${stableDigest({
363
+ plan: planSha256,
364
+ authorization: authorized.authorization_sha256,
365
+ target: providerResult.target_commit,
366
+ }).slice(0, 24)}`,
367
+ run_id: input.runId,
368
+ plan_id: storedPlan.plan_id,
369
+ plan_sha256: planSha256,
370
+ authorization_sha256: authorized.authorization_sha256,
371
+ proof_sha256: proofSha256,
372
+ binding_sha256: stableDigest(evidence.binding),
373
+ binding_id: storedPlan.binding.binding_id,
374
+ provider_id: storedPlan.binding.provider.id,
375
+ status: providerResult.status,
376
+ target_root: storedPlan.provider_preview.target_root,
377
+ target_ref: storedPlan.provider_preview.target_ref,
378
+ source_commit: storedPlan.provider_preview.source_commit,
379
+ target_commit: providerResult.target_commit,
380
+ target_tree: providerResult.target_tree,
381
+ applied_at: providerResult.applied_at,
382
+ };
383
+ writeWorkflowSupplement(input.coordRoot, input.runId, "integration/receipt.json", receipt);
384
+ appendWorkflowJournalEvent(input.coordRoot, input.runId, "integration.apply", {
385
+ receipt_id: receipt.receipt_id,
386
+ status: receipt.status,
387
+ target_commit: receipt.target_commit,
388
+ });
389
+ return receipt;
390
+ } finally {
391
+ release();
392
+ }
393
+ }
394
+
395
+ function validateProviderIntegrationResult(
396
+ result: ProviderIntegrationResult,
397
+ plan: IntegrationPlan,
398
+ before: ProviderIntegrationPreview,
399
+ ): void {
400
+ const expectedStatus =
401
+ before.target_commit === plan.provider_preview.source_commit ? "already_applied" : "applied";
402
+ if (
403
+ result.schema_version !== 1 ||
404
+ result.binding_id !== plan.binding.binding_id ||
405
+ result.plan_id !== plan.plan_id ||
406
+ result.status !== expectedStatus ||
407
+ result.target_commit !== plan.provider_preview.source_commit ||
408
+ result.target_tree !== plan.provider_preview.source_tree ||
409
+ !Number.isFinite(Date.parse(result.applied_at))
410
+ ) {
411
+ throw new Error("workspace provider returned an invalid integration result");
412
+ }
413
+ }
414
+
415
+ function validatePostApplyState(
416
+ result: ProviderIntegrationResult,
417
+ plan: IntegrationPlan,
418
+ current: ProviderIntegrationPreview,
419
+ ): void {
420
+ const expected = plan.provider_preview;
421
+ if (
422
+ current.schema_version !== 1 ||
423
+ current.provider_id !== expected.provider_id ||
424
+ current.binding_id !== plan.binding.binding_id ||
425
+ current.operation !== plan.operation ||
426
+ current.target_root !== expected.target_root ||
427
+ current.target_ref !== expected.target_ref ||
428
+ current.source_commit !== expected.source_commit ||
429
+ current.source_tree !== expected.source_tree ||
430
+ current.target_commit !== result.target_commit ||
431
+ current.target_tree !== result.target_tree ||
432
+ current.target_commit !== expected.source_commit ||
433
+ current.target_tree !== expected.source_tree ||
434
+ current.blocked.length > 0
435
+ ) {
436
+ throw new Error("integration result does not match the reattached target state");
437
+ }
438
+ }
439
+
440
+ async function readAndValidateEvidence(
441
+ coordRoot: string,
442
+ runId: string,
443
+ provider: WorkspaceProvider,
444
+ ): Promise<{
445
+ proof: ReturnType<typeof readWorkflowProof> & {
446
+ execution: WorkspaceBoundExecutionEvidence;
447
+ };
448
+ binding: WorkspaceBinding;
449
+ attestation: Awaited<ReturnType<WorkspaceProvider["reattach"]>>;
450
+ }> {
451
+ const manifest = readWorkflowRunManifest(coordRoot, runId);
452
+ const proof = readWorkflowProof(coordRoot, runId);
453
+ if (proof.run.status !== "succeeded") throw new Error("integration requires succeeded proof");
454
+ if (proof.acceptance.summary.unsatisfied > 0 || proof.acceptance.summary.unknown > 0) {
455
+ throw new Error("integration requires fully satisfied acceptance");
456
+ }
457
+ const binding = manifest.execution.workspace_binding;
458
+ if (!binding || !isWorkspaceBoundExecutionEvidence(proof.execution, runId))
459
+ throw new Error("integration requires isolated execution evidence");
460
+ const boundProof = proof as ReturnType<typeof readWorkflowProof> & {
461
+ execution: WorkspaceBoundExecutionEvidence;
462
+ };
463
+ if (boundProof.execution.terminal_lifecycle_state !== "completed_unintegrated") {
464
+ throw new Error("integration requires a successful completed workspace proof");
465
+ }
466
+ if (
467
+ stableDigest(binding) !== stableDigest(boundProof.execution.binding) ||
468
+ stableDigest(binding.owner) !== stableDigest(boundProof.execution.terminal_attestation.owner)
469
+ ) {
470
+ throw new Error("manifest, proof, binding, and terminal attestation do not agree");
471
+ }
472
+ const attestation = await provider.reattach(binding);
473
+ if (
474
+ !isWorkspaceAttestation(attestation, binding) ||
475
+ attestation.status !== "ok" ||
476
+ stableDigest(attestation.owner) !== stableDigest(binding.owner)
477
+ ) {
478
+ throw new Error("workspace provider reattachment did not attest the frozen binding");
479
+ }
480
+ const request = readWorkflowSupplement<Record<string, unknown>>(
481
+ coordRoot,
482
+ runId,
483
+ "workspace-request.json",
484
+ );
485
+ if (
486
+ !request ||
487
+ !isWorkspaceAllocationRequest(request) ||
488
+ stableDigest(request) !== binding.request_sha256
489
+ ) {
490
+ throw new Error("workspace request is missing or does not match the binding");
491
+ }
492
+ const capabilities = await provider.probe({
493
+ requested_cwd: request.requested_cwd,
494
+ writable_roots: request.writable_roots.map((root) => root.configured),
495
+ });
496
+ if (
497
+ capabilities.capabilities.provider_id !== binding.provider.id ||
498
+ capabilities.capabilities.provider_version !== binding.provider.version ||
499
+ capabilities.capabilities.capability_digest !== binding.provider.capability_digest
500
+ ) {
501
+ throw new Error("workspace provider identity or capabilities drifted");
502
+ }
503
+ return { proof: boundProof, binding, attestation };
504
+ }
505
+
506
+ function resolveReview(
507
+ input: PrepareIntegrationInput,
508
+ binding: WorkspaceBinding,
509
+ proofSha256: string,
510
+ terminalAttestationSha256: string,
511
+ acceptedUnknowns: string[],
512
+ ): IntegrationReviewRecord {
513
+ if (binding.owner.kind === "work_attempt") {
514
+ const record = readWorkAcceptance(
515
+ input.coordRoot,
516
+ binding,
517
+ proofSha256,
518
+ terminalAttestationSha256,
519
+ acceptedUnknowns,
520
+ );
521
+ if (!record) throw new Error("work-linked integration requires exact durable work acceptance");
522
+ return record;
523
+ }
524
+ if (!input.review?.actor) throw new Error("standalone integration requires explicit review");
525
+ const review: IntegrationReviewRecord = {
526
+ schema_version: 1,
527
+ run_id: input.runId,
528
+ owner: binding.owner,
529
+ proof_sha256: proofSha256,
530
+ binding_id: binding.binding_id,
531
+ terminal_attestation_sha256: terminalAttestationSha256,
532
+ accepted_unknowns: acceptedUnknowns,
533
+ actor: input.review.actor,
534
+ reason: input.review.reason,
535
+ reviewed_at: new Date().toISOString(),
536
+ };
537
+ const prior = readWorkflowSupplement<IntegrationReviewRecord>(
538
+ input.coordRoot,
539
+ input.runId,
540
+ "integration/review.json",
541
+ );
542
+ if (prior) {
543
+ if (
544
+ prior.run_id !== review.run_id ||
545
+ prior.proof_sha256 !== review.proof_sha256 ||
546
+ prior.binding_id !== review.binding_id ||
547
+ prior.terminal_attestation_sha256 !== review.terminal_attestation_sha256 ||
548
+ stableDigest(prior.accepted_unknowns) !== stableDigest(review.accepted_unknowns) ||
549
+ prior.actor !== review.actor ||
550
+ prior.reason !== review.reason
551
+ ) {
552
+ throw new Error("existing integration review does not match current evidence");
553
+ }
554
+ return prior;
555
+ }
556
+ writeWorkflowSupplement(input.coordRoot, input.runId, "integration/review.json", review);
557
+ return review;
558
+ }
559
+
560
+ function readReviewForPlan(coordRoot: string, plan: IntegrationPlan): IntegrationReviewRecord {
561
+ if (plan.binding.owner.kind === "work_attempt") {
562
+ const review = readWorkAcceptance(
563
+ coordRoot,
564
+ plan.binding,
565
+ plan.proof_sha256,
566
+ plan.terminal_attestation_sha256,
567
+ plan.accepted_unknowns,
568
+ );
569
+ if (!review) throw new Error("durable work acceptance disappeared");
570
+ return review;
571
+ }
572
+ const review = readWorkflowSupplement<IntegrationReviewRecord>(
573
+ coordRoot,
574
+ plan.run_id,
575
+ "integration/review.json",
576
+ );
577
+ if (!review) throw new Error("standalone integration review disappeared");
578
+ return review;
579
+ }
580
+
581
+ function readWorkAcceptance(
582
+ coordRoot: string,
583
+ binding: WorkspaceBinding,
584
+ proofSha256: string,
585
+ terminalAttestationSha256: string,
586
+ acceptedUnknowns: readonly string[],
587
+ ): IntegrationReviewRecord | undefined {
588
+ if (binding.owner.kind !== "work_attempt") return undefined;
589
+ const work = readWorkItem(coordRoot, binding.owner.work_item_id);
590
+ const accepted = [...work.events]
591
+ .reverse()
592
+ .find((event) => ["work.accepted", "work.cancelled", "work.reopened"].includes(event.event));
593
+ if (
594
+ work.projection.state !== "succeeded" ||
595
+ !accepted ||
596
+ accepted.event !== "work.accepted" ||
597
+ accepted.run_id !== binding.run_id ||
598
+ accepted.attempt !== binding.owner.attempt ||
599
+ accepted.proof_sha256 !== proofSha256 ||
600
+ accepted.workspace_binding_id !== binding.binding_id ||
601
+ accepted.terminal_attestation_sha256 !== terminalAttestationSha256 ||
602
+ stableDigest(accepted.accepted_unknowns ?? []) !== stableDigest(acceptedUnknowns) ||
603
+ typeof accepted.actor !== "string" ||
604
+ typeof accepted.ts !== "string"
605
+ ) {
606
+ return undefined;
607
+ }
608
+ return {
609
+ schema_version: 1,
610
+ run_id: binding.run_id,
611
+ owner: binding.owner,
612
+ proof_sha256: proofSha256,
613
+ binding_id: binding.binding_id,
614
+ terminal_attestation_sha256: terminalAttestationSha256,
615
+ accepted_unknowns: [...acceptedUnknowns],
616
+ actor: accepted.actor,
617
+ reason: typeof accepted.reason === "string" ? accepted.reason : undefined,
618
+ reviewed_at: accepted.ts,
619
+ };
620
+ }
621
+
622
+ function assertVerifiedSourceCommit(
623
+ proof: ReturnType<typeof readWorkflowProof> & {
624
+ execution: WorkspaceBoundExecutionEvidence;
625
+ },
626
+ sourceCommit: string,
627
+ ): void {
628
+ const verifiedCommit = proof.execution.terminal_attestation.repository?.head_commit;
629
+ if (!verifiedCommit || verifiedCommit !== sourceCommit) {
630
+ throw new Error("integration source commit does not match the terminal proof");
631
+ }
632
+ }
633
+
634
+ function authorizePlan(input: PrepareIntegrationInput, plan: IntegrationPlan): void {
635
+ const policy = normalizePolicy(input.policy, {
636
+ baseDir: plan.provider_preview.target_root,
637
+ });
638
+ const request = summarizePolicyRequest({
639
+ phase: "external_mutation",
640
+ action: `fast-forward ${plan.provider_preview.target_ref}`,
641
+ path: plan.provider_preview.target_root,
642
+ isolation: plan.binding.isolation,
643
+ network_access: "disabled",
644
+ });
645
+ const evaluation = evaluatePolicy(policy, request);
646
+ if (evaluation.verdict === "deny") {
647
+ throw new Error(`integration policy denied: ${evaluation.reason}`);
648
+ }
649
+ let decision: PolicyDecision;
650
+ let approvalId: string | undefined;
651
+ let approvalActor: string | undefined;
652
+ let approvalSha256: string | undefined;
653
+ if (evaluation.verdict === "ask") {
654
+ const created = createWorkflowApproval({
655
+ coordRoot: input.coordRoot,
656
+ runId: input.runId,
657
+ decisionId: INTEGRATION_DECISION_ID,
658
+ addressedTo: input.approvalAddressee,
659
+ policy: { name: policy.name, sha256: policyDigest(policy) },
660
+ request,
661
+ evaluation,
662
+ });
663
+ approvalId = created.approval.request.id;
664
+ if (created.approval.status === "pending") {
665
+ throw new Error(`integration approval ${approvalId} is pending`);
666
+ }
667
+ if (created.approval.status !== "approved" || !created.approval.decision) {
668
+ throw new Error(`integration approval ${approvalId} was denied`);
669
+ }
670
+ approvalActor = created.approval.decision.actor;
671
+ approvalSha256 = stableDigest(created.approval);
672
+ decision = {
673
+ id: INTEGRATION_DECISION_ID,
674
+ checked_at: created.approval.decision.decided_at,
675
+ policy: policy.name,
676
+ phase: "external_mutation",
677
+ initial_verdict: "ask",
678
+ verdict: "allow",
679
+ resolved_by: "approval",
680
+ reason: created.approval.decision.reason ?? evaluation.reason,
681
+ rule_codes: evaluation.rules.map((rule) => rule.code),
682
+ request,
683
+ };
684
+ } else {
685
+ decision = {
686
+ id: INTEGRATION_DECISION_ID,
687
+ checked_at: new Date().toISOString(),
688
+ policy: policy.name,
689
+ phase: "external_mutation",
690
+ initial_verdict: "allow",
691
+ verdict: "allow",
692
+ resolved_by: "policy",
693
+ reason: evaluation.reason,
694
+ rule_codes: evaluation.rules.map((rule) => rule.code),
695
+ request,
696
+ };
697
+ }
698
+ const authorization: IntegrationAuthorization = {
699
+ schema_version: 1,
700
+ run_id: input.runId,
701
+ plan_sha256: stableDigest(plan),
702
+ policy_sha256: policyDigest(policy),
703
+ decision,
704
+ decision_sha256: stableDigest(decision),
705
+ approval_id: approvalId,
706
+ approval_actor: approvalActor,
707
+ approval_sha256: approvalSha256,
708
+ journal_anchor: {
709
+ event: "integration.plan",
710
+ plan_sha256: stableDigest(plan),
711
+ },
712
+ authorized_at: new Date().toISOString(),
713
+ };
714
+ const prior = readWorkflowSupplement<IntegrationAuthorization>(
715
+ input.coordRoot,
716
+ input.runId,
717
+ "integration/authorization.json",
718
+ );
719
+ if (prior) {
720
+ if (
721
+ prior.plan_sha256 !== authorization.plan_sha256 ||
722
+ prior.policy_sha256 !== authorization.policy_sha256 ||
723
+ prior.decision.verdict !== "allow" ||
724
+ prior.decision_sha256 !== stableDigest(prior.decision) ||
725
+ prior.approval_id !== authorization.approval_id ||
726
+ prior.approval_sha256 !== authorization.approval_sha256 ||
727
+ stableDigest(prior.journal_anchor) !== stableDigest(authorization.journal_anchor)
728
+ ) {
729
+ throw new Error("existing integration authorization does not match the exact plan");
730
+ }
731
+ return;
732
+ }
733
+ writeWorkflowSupplement(
734
+ input.coordRoot,
735
+ input.runId,
736
+ "integration/authorization.json",
737
+ authorization,
738
+ );
739
+ appendWorkflowJournalEvent(input.coordRoot, input.runId, "integration.authorized", {
740
+ plan_id: plan.plan_id,
741
+ authorization_sha256: stableDigest(authorization),
742
+ policy_sha256: authorization.policy_sha256,
743
+ approval_id: authorization.approval_id ?? null,
744
+ });
745
+ }
746
+
747
+ function samePlanAuthority(left: IntegrationPlan, right: IntegrationPlan): boolean {
748
+ return (
749
+ left.plan_id === right.plan_id &&
750
+ left.operation === right.operation &&
751
+ left.binding_sha256 === right.binding_sha256 &&
752
+ left.proof_sha256 === right.proof_sha256 &&
753
+ left.terminal_attestation_sha256 === right.terminal_attestation_sha256 &&
754
+ left.review_sha256 === right.review_sha256 &&
755
+ stableDigest(left.binding) === stableDigest(right.binding) &&
756
+ left.provider_preview.provider_id === right.provider_preview.provider_id &&
757
+ left.provider_preview.target_root === right.provider_preview.target_root &&
758
+ left.provider_preview.target_ref === right.provider_preview.target_ref &&
759
+ left.provider_preview.target_commit === right.provider_preview.target_commit &&
760
+ left.provider_preview.target_tree === right.provider_preview.target_tree &&
761
+ left.provider_preview.source_commit === right.provider_preview.source_commit &&
762
+ left.provider_preview.source_tree === right.provider_preview.source_tree &&
763
+ left.target_identity_sha256 === right.target_identity_sha256 &&
764
+ left.idempotency_sha256 === right.idempotency_sha256 &&
765
+ stableDigest(left.accepted_unknowns) === stableDigest(right.accepted_unknowns)
766
+ );
767
+ }
768
+
769
+ function requiredIntegrationRoot(binding: WorkspaceBinding): string {
770
+ if (!binding.integration_root) throw new Error("workspace binding has no integration root");
771
+ return binding.integration_root;
772
+ }
773
+
774
+ function assertIntegrationPlanJournal(coordRoot: string, runId: string, planSha256: string): void {
775
+ const path = join(workflowRunDir(coordRoot, runId), "journal.jsonl");
776
+ let records: Array<Record<string, unknown>>;
777
+ try {
778
+ records = readFileSync(path, "utf8")
779
+ .split("\n")
780
+ .filter(Boolean)
781
+ .map((line) => JSON.parse(line) as Record<string, unknown>);
782
+ } catch (error) {
783
+ throw new Error(`workflow journal is corrupt: ${(error as Error).message}`);
784
+ }
785
+ if (
786
+ records.some((record) => record.run_id !== runId || typeof record.event !== "string") ||
787
+ !records.some(
788
+ (record) => record.event === "integration.plan" && record.plan_sha256 === planSha256,
789
+ )
790
+ ) {
791
+ throw new Error("integration plan journal anchor is missing or mismatched");
792
+ }
793
+ }
794
+
795
+ function acquireTargetLease(coordRoot: string, targetRoot: string, runId: string): () => void {
796
+ const resolvedTarget = resolve(targetRoot);
797
+ const authoritySha256 = stableDigest({
798
+ run_id: runId,
799
+ target_root: resolvedTarget,
800
+ operation: "integration",
801
+ });
802
+ const lease = acquireNoClobberLease({
803
+ path: join(
804
+ resolve(coordRoot),
805
+ ".harnery",
806
+ "workspaces",
807
+ ".integration-leases",
808
+ `${stableDigest(resolvedTarget)}.lease`,
809
+ ),
810
+ scope: "integration",
811
+ authoritySha256,
812
+ staleAfterMs: TARGET_LEASE_STALE_MS,
813
+ metadata: { run_id: runId, target_root: resolvedTarget },
814
+ validateStaleOwner: (owner) => {
815
+ const ownerRunId = owner.metadata?.run_id;
816
+ return (
817
+ ownerRunId !== undefined &&
818
+ owner.metadata?.target_root === resolvedTarget &&
819
+ owner.authority_sha256 ===
820
+ stableDigest({
821
+ run_id: ownerRunId,
822
+ target_root: resolvedTarget,
823
+ operation: "integration",
824
+ })
825
+ );
826
+ },
827
+ });
828
+ return () => lease.release();
829
+ }