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,255 @@
1
+ import { existsSync, readdirSync, readFileSync } from "node:fs";
2
+ import { join, resolve } from "node:path";
3
+ import { readWorkItem } from "../../work/state.js";
4
+ import { readWorkflowProof } from "../proof.js";
5
+ import { readWorkflowRunManifest } from "../run-state.js";
6
+ import { deriveWorkspaceLifecycle } from "./lifecycle.js";
7
+ import { fileSha256, readCleanupAttempts, readIntegrationAttempts, readWorkflowSupplement, readWorkspaceEvents, workflowRunDir, } from "./state.js";
8
+ import { isWorkspaceBoundExecutionEvidence } from "./validate.js";
9
+ const RUN_ID = /^[A-Za-z0-9][A-Za-z0-9._-]{0,199}$/;
10
+ /**
11
+ * Read one workflow's workspace state from validated durable authority.
12
+ *
13
+ * Corrupt, contradictory, or foreign records throw instead of being projected
14
+ * as healthy. Use inspectWorkflowWorkspace when a UI needs a typed error row.
15
+ */
16
+ export function readWorkflowWorkspaceStatus(coordRoot, runId) {
17
+ assertRunId(runId);
18
+ const root = resolve(coordRoot);
19
+ const runDir = workflowRunDir(root, runId);
20
+ const manifest = readWorkflowRunManifest(root, runId);
21
+ const journal = readWorkspaceJournal(runDir, runId);
22
+ const proofPath = join(runDir, "proof.json");
23
+ const proof = existsSync(proofPath) ? readWorkflowProof(root, runId) : undefined;
24
+ const proofSha256 = proof ? fileSha256(proofPath) : undefined;
25
+ const binding = manifest.execution.workspace_binding;
26
+ const fallback = manifest.execution.workspace_fallback;
27
+ if (!binding) {
28
+ const lifecycle = deriveWorkspaceLifecycle({
29
+ manifest,
30
+ workflow_journal: journal,
31
+ proof,
32
+ proof_sha256: proofSha256,
33
+ allocation_unsupported: fallback !== undefined,
34
+ });
35
+ return {
36
+ schema_version: 1,
37
+ run_id: runId,
38
+ run_name: manifest.name,
39
+ work_item_id: manifest.work_item_id,
40
+ started_at: manifest.started_at,
41
+ selection: fallback ? "compatibility" : "shared",
42
+ requested_isolation: manifest.execution.isolation,
43
+ effective_isolation: "shared",
44
+ provider: fallback?.provider,
45
+ compatibility: fallback
46
+ ? {
47
+ reason: fallback.selection_reason,
48
+ unsupported: [...fallback.unsupported],
49
+ unknowns: [...fallback.unknowns],
50
+ }
51
+ : undefined,
52
+ lifecycle,
53
+ verification: {
54
+ status: "not_applicable",
55
+ workflow_status: proof?.run.status,
56
+ drift: [...(fallback?.drift ?? [])],
57
+ unsupported: [...(fallback?.unsupported ?? [])],
58
+ unknowns: [...(fallback?.unknowns ?? [])],
59
+ },
60
+ integration: { state: "none", changed_paths: [] },
61
+ cleanup: { state: "not_requested", attempts: 0 },
62
+ repository: { dirty_paths: [], conflicts: [], operations_in_progress: [] },
63
+ integrity: {
64
+ status: "verified",
65
+ proof_sha256: proofSha256,
66
+ provider_event_chain_sha256: null,
67
+ },
68
+ };
69
+ }
70
+ const providerEvents = readWorkspaceEvents(root, binding.provider.id, binding.binding_id);
71
+ const integrationPlan = readWorkflowSupplement(root, runId, "integration/plan.json");
72
+ const integrationAuthorization = readWorkflowSupplement(root, runId, "integration/authorization.json");
73
+ const integrationAttempts = readIntegrationAttempts(root, runId);
74
+ const integrationReceipt = readWorkflowSupplement(root, runId, "integration/receipt.json");
75
+ const cleanupIntent = readWorkflowSupplement(root, runId, "cleanup/intent.json");
76
+ const cleanupAttempts = readCleanupAttempts(root, runId);
77
+ const cleanupReceipt = readWorkflowSupplement(root, runId, "cleanup/receipt.json");
78
+ const cancellationReceipt = readWorkflowSupplement(root, runId, "cancellation/outcome.json");
79
+ const workEvents = binding.owner.kind === "work_attempt"
80
+ ? readWorkItem(root, binding.owner.work_item_id).events
81
+ : undefined;
82
+ const lifecycle = deriveWorkspaceLifecycle({
83
+ binding,
84
+ provider_events: providerEvents,
85
+ manifest,
86
+ workflow_journal: journal,
87
+ proof,
88
+ proof_sha256: proofSha256,
89
+ integration_plan: integrationPlan,
90
+ integration_authorization: integrationAuthorization,
91
+ integration_attempts: integrationAttempts,
92
+ integration_receipt: integrationReceipt,
93
+ cleanup_intent: cleanupIntent,
94
+ cleanup_attempts: cleanupAttempts,
95
+ cleanup_receipt: cleanupReceipt,
96
+ work_events: workEvents,
97
+ cancellation_receipt: cancellationReceipt,
98
+ });
99
+ const latestCleanup = cleanupAttempts.at(-1);
100
+ const attestation = latestAttestation(proof, latestCleanup?.attestation, cleanupReceipt);
101
+ return {
102
+ schema_version: 1,
103
+ run_id: runId,
104
+ run_name: manifest.name,
105
+ work_item_id: manifest.work_item_id,
106
+ started_at: manifest.started_at,
107
+ selection: "isolated",
108
+ requested_isolation: manifest.execution.isolation,
109
+ effective_isolation: binding.isolation,
110
+ provider: binding.provider,
111
+ allocation: {
112
+ binding_id: binding.binding_id,
113
+ workspace_id: binding.workspace_id,
114
+ workspace_root: binding.workspace_root,
115
+ active_root: binding.active_root,
116
+ created_at: binding.created_at,
117
+ },
118
+ lifecycle,
119
+ verification: {
120
+ status: verificationStatus(proof, attestation),
121
+ workflow_status: proof?.run.status,
122
+ attested_at: attestation?.recorded_at,
123
+ drift: [...(attestation?.provider_drift ?? [])],
124
+ unsupported: [...(attestation?.unsupported ?? [])],
125
+ unknowns: [...(attestation?.unknowns ?? [])],
126
+ },
127
+ integration: {
128
+ state: lifecycle.integration_state,
129
+ changed_paths: [...(integrationPlan?.provider_preview.changed_paths ?? [])],
130
+ target_ref: integrationPlan?.provider_preview.target_ref,
131
+ target_commit: integrationReceipt?.target_commit ?? integrationPlan?.provider_preview.target_commit,
132
+ receipt_id: integrationReceipt?.receipt_id,
133
+ },
134
+ cleanup: {
135
+ state: cleanupState(cleanupIntent, latestCleanup?.status, cleanupReceipt),
136
+ attempts: cleanupAttempts.length,
137
+ reason: latestCleanup?.reason,
138
+ receipt_id: cleanupReceipt?.receipt_id,
139
+ },
140
+ repository: {
141
+ dirty_paths: [...(attestation?.repository?.dirty_paths ?? [])],
142
+ conflicts: [...(attestation?.repository?.conflicts ?? [])],
143
+ operations_in_progress: [...(attestation?.repository?.operation_in_progress ?? [])],
144
+ },
145
+ integrity: {
146
+ status: "verified",
147
+ proof_sha256: proofSha256,
148
+ provider_event_chain_sha256: lifecycle.provider_event_chain_sha256,
149
+ },
150
+ };
151
+ }
152
+ export function inspectWorkflowWorkspace(coordRoot, runId) {
153
+ try {
154
+ return { ok: true, value: readWorkflowWorkspaceStatus(coordRoot, runId) };
155
+ }
156
+ catch (error) {
157
+ return {
158
+ ok: false,
159
+ run_id: runId,
160
+ error: error instanceof Error ? error.message : String(error),
161
+ };
162
+ }
163
+ }
164
+ export function listWorkflowWorkspaceInspections(coordRoot) {
165
+ const workflowsRoot = join(resolve(coordRoot), ".harnery", "workflows");
166
+ if (!existsSync(workflowsRoot))
167
+ return [];
168
+ return readdirSync(workflowsRoot)
169
+ .filter((runId) => RUN_ID.test(runId) && existsSync(join(workflowsRoot, runId, "run.json")))
170
+ .sort((a, b) => b.localeCompare(a))
171
+ .map((runId) => inspectWorkflowWorkspace(coordRoot, runId));
172
+ }
173
+ export function renderWorkflowWorkspaceStatus(status) {
174
+ const lines = [
175
+ `run ${status.run_id} (${status.run_name}): ${status.selection}`,
176
+ `isolation: requested ${status.requested_isolation}; effective ${status.effective_isolation}`,
177
+ `lifecycle: ${status.lifecycle.state}; resource ${status.lifecycle.resource_state ?? "not applicable"}; integration ${status.lifecycle.integration_state}; cancellation ${status.lifecycle.cancellation}`,
178
+ ];
179
+ if (status.provider) {
180
+ lines.push(`provider: ${status.provider.id}@${status.provider.version}`);
181
+ }
182
+ if (status.compatibility) {
183
+ lines.push(`compatibility: ${status.compatibility.reason}`);
184
+ }
185
+ if (status.allocation) {
186
+ lines.push(`binding: ${status.allocation.binding_id}; workspace ${status.allocation.workspace_id}`, `workspace root: ${status.allocation.workspace_root}`, `active root: ${status.allocation.active_root}`);
187
+ }
188
+ lines.push(`verification: ${status.verification.status}; workflow ${status.verification.workflow_status ?? "not terminal"}; ${status.verification.drift.length} drift; ${status.verification.unknowns.length} unknown`, `repository: ${status.repository.dirty_paths.length} dirty; ${status.repository.conflicts.length} conflict; ${status.repository.operations_in_progress.length} operation in progress`, `integration: ${status.integration.state}; ${status.integration.changed_paths.length} changed path`, `cleanup: ${status.cleanup.state}; ${status.cleanup.attempts} attempt`);
189
+ if (status.cleanup.reason)
190
+ lines.push(`cleanup reason: ${status.cleanup.reason}`);
191
+ return `${lines.join("\n")}\n`;
192
+ }
193
+ function readWorkspaceJournal(runDir, runId) {
194
+ const path = join(runDir, "journal.jsonl");
195
+ if (!existsSync(path))
196
+ return [];
197
+ return readFileSync(path, "utf8")
198
+ .split("\n")
199
+ .filter(Boolean)
200
+ .map((line, index) => {
201
+ let record;
202
+ try {
203
+ record = JSON.parse(line);
204
+ }
205
+ catch (error) {
206
+ throw new Error(`cannot parse workflow journal event ${index + 1}: ${error instanceof Error ? error.message : String(error)}`);
207
+ }
208
+ if (typeof record.event !== "string" ||
209
+ (record.run_id !== undefined && record.run_id !== runId)) {
210
+ throw new Error(`workflow journal event ${index + 1} is foreign or unsupported`);
211
+ }
212
+ return {
213
+ event: record.event,
214
+ ok: typeof record.ok === "boolean" ? record.ok : undefined,
215
+ status: journalStatus(record.status),
216
+ work_event_seq: typeof record.work_event_seq === "number" ? record.work_event_seq : undefined,
217
+ work_event_sha256: typeof record.work_event_sha256 === "string" ? record.work_event_sha256 : undefined,
218
+ };
219
+ });
220
+ }
221
+ function latestAttestation(proof, cleanupAttempt, cleanupReceipt) {
222
+ if (cleanupReceipt)
223
+ return cleanupReceipt.attestation;
224
+ if (cleanupAttempt)
225
+ return cleanupAttempt;
226
+ if (proof && isWorkspaceBoundExecutionEvidence(proof.execution, proof.run.id)) {
227
+ return proof.execution.terminal_attestation;
228
+ }
229
+ return undefined;
230
+ }
231
+ function verificationStatus(proof, attestation) {
232
+ if (!proof || !attestation)
233
+ return "pending";
234
+ return attestation.status;
235
+ }
236
+ function cleanupState(intent, attemptStatus, receipt) {
237
+ if (receipt)
238
+ return "released";
239
+ if (attemptStatus === "preserved_dirty")
240
+ return "preserved_dirty";
241
+ if (attemptStatus && ["blocked", "unsupported", "partial"].includes(attemptStatus)) {
242
+ return "blocked";
243
+ }
244
+ return intent || attemptStatus ? "pending" : "not_requested";
245
+ }
246
+ function journalStatus(value) {
247
+ return typeof value === "string" &&
248
+ ["blocked", "lost", "cancelled", "unsupported", "preserved_dirty"].includes(value)
249
+ ? value
250
+ : undefined;
251
+ }
252
+ function assertRunId(runId) {
253
+ if (!RUN_ID.test(runId))
254
+ throw new Error("workflow run id is invalid");
255
+ }
@@ -0,0 +1,24 @@
1
+ import { type NormalizedPolicy, type PolicySpec } from "../../policy/index.js";
2
+ import type { IntegrationApplyReceipt, IntegrationPlan, WorkspaceProvider } from "./types.js";
3
+ export interface PrepareIntegrationInput {
4
+ coordRoot: string;
5
+ runId: string;
6
+ provider: WorkspaceProvider;
7
+ policy: PolicySpec | NormalizedPolicy;
8
+ targetRoot?: string;
9
+ review?: {
10
+ actor: string;
11
+ reason?: string;
12
+ };
13
+ acceptedUnknowns?: readonly string[];
14
+ approvalAddressee?: string;
15
+ }
16
+ export interface ApplyIntegrationInput {
17
+ coordRoot: string;
18
+ runId: string;
19
+ provider: WorkspaceProvider;
20
+ plan?: IntegrationPlan;
21
+ }
22
+ export declare function prepareIntegration(input: PrepareIntegrationInput): Promise<IntegrationPlan>;
23
+ export declare function applyIntegration(input: ApplyIntegrationInput): Promise<IntegrationApplyReceipt>;
24
+ //# sourceMappingURL=integration.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"integration.d.ts","sourceRoot":"","sources":["../../../../src/core/workflow/workspaces/integration.ts"],"names":[],"mappings":"AAEA,OAAO,EAEL,KAAK,gBAAgB,EAGrB,KAAK,UAAU,EAGhB,MAAM,uBAAuB,CAAC;AAgB/B,OAAO,KAAK,EAEV,uBAAuB,EAEvB,eAAe,EAOf,iBAAiB,EAClB,MAAM,YAAY,CAAC;AAUpB,MAAM,WAAW,uBAAuB;IACtC,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,iBAAiB,CAAC;IAC5B,MAAM,EAAE,UAAU,GAAG,gBAAgB,CAAC;IACtC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAC5C,gBAAgB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACrC,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,qBAAqB;IACpC,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,iBAAiB,CAAC;IAC5B,IAAI,CAAC,EAAE,eAAe,CAAC;CACxB;AAED,wBAAsB,kBAAkB,CAAC,KAAK,EAAE,uBAAuB,GAAG,OAAO,CAAC,eAAe,CAAC,CA2GjG;AAED,wBAAsB,gBAAgB,CACpC,KAAK,EAAE,qBAAqB,GAC3B,OAAO,CAAC,uBAAuB,CAAC,CAuNlC"}