@davidahmann/mill 0.1.4

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 (184) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +335 -0
  3. package/dist/audit/repository.d.ts +8 -0
  4. package/dist/audit/repository.d.ts.map +1 -0
  5. package/dist/audit/repository.js +232 -0
  6. package/dist/audit/repository.js.map +1 -0
  7. package/dist/cli-program.d.ts +12 -0
  8. package/dist/cli-program.d.ts.map +1 -0
  9. package/dist/cli-program.js +1020 -0
  10. package/dist/cli-program.js.map +1 -0
  11. package/dist/cli.d.ts +3 -0
  12. package/dist/cli.d.ts.map +1 -0
  13. package/dist/cli.js +7 -0
  14. package/dist/cli.js.map +1 -0
  15. package/dist/config/lock.d.ts +12 -0
  16. package/dist/config/lock.d.ts.map +1 -0
  17. package/dist/config/lock.js +83 -0
  18. package/dist/config/lock.js.map +1 -0
  19. package/dist/contracts/canonical.d.ts +7 -0
  20. package/dist/contracts/canonical.d.ts.map +1 -0
  21. package/dist/contracts/canonical.js +30 -0
  22. package/dist/contracts/canonical.js.map +1 -0
  23. package/dist/contracts/schemas.d.ts +2705 -0
  24. package/dist/contracts/schemas.d.ts.map +1 -0
  25. package/dist/contracts/schemas.js +988 -0
  26. package/dist/contracts/schemas.js.map +1 -0
  27. package/dist/doctor.d.ts +21 -0
  28. package/dist/doctor.d.ts.map +1 -0
  29. package/dist/doctor.js +138 -0
  30. package/dist/doctor.js.map +1 -0
  31. package/dist/errors.d.ts +18 -0
  32. package/dist/errors.d.ts.map +1 -0
  33. package/dist/errors.js +31 -0
  34. package/dist/errors.js.map +1 -0
  35. package/dist/index.d.ts +20 -0
  36. package/dist/index.d.ts.map +1 -0
  37. package/dist/index.js +20 -0
  38. package/dist/index.js.map +1 -0
  39. package/dist/intake/prd.d.ts +14 -0
  40. package/dist/intake/prd.d.ts.map +1 -0
  41. package/dist/intake/prd.js +39 -0
  42. package/dist/intake/prd.js.map +1 -0
  43. package/dist/planning/impact.d.ts +59 -0
  44. package/dist/planning/impact.d.ts.map +1 -0
  45. package/dist/planning/impact.js +400 -0
  46. package/dist/planning/impact.js.map +1 -0
  47. package/dist/planning/specification.d.ts +42 -0
  48. package/dist/planning/specification.d.ts.map +1 -0
  49. package/dist/planning/specification.js +235 -0
  50. package/dist/planning/specification.js.map +1 -0
  51. package/dist/qualification/public-alpha.d.ts +16 -0
  52. package/dist/qualification/public-alpha.d.ts.map +1 -0
  53. package/dist/qualification/public-alpha.js +128 -0
  54. package/dist/qualification/public-alpha.js.map +1 -0
  55. package/dist/recipes/node-typescript-next-web.d.ts +19 -0
  56. package/dist/recipes/node-typescript-next-web.d.ts.map +1 -0
  57. package/dist/recipes/node-typescript-next-web.js +136 -0
  58. package/dist/recipes/node-typescript-next-web.js.map +1 -0
  59. package/dist/repository/integration.d.ts +58 -0
  60. package/dist/repository/integration.d.ts.map +1 -0
  61. package/dist/repository/integration.js +1385 -0
  62. package/dist/repository/integration.js.map +1 -0
  63. package/dist/repository/scan.d.ts +27 -0
  64. package/dist/repository/scan.d.ts.map +1 -0
  65. package/dist/repository/scan.js +388 -0
  66. package/dist/repository/scan.js.map +1 -0
  67. package/dist/result.d.ts +23 -0
  68. package/dist/result.d.ts.map +1 -0
  69. package/dist/result.js +24 -0
  70. package/dist/result.js.map +1 -0
  71. package/dist/runtime/codex.d.ts +28 -0
  72. package/dist/runtime/codex.d.ts.map +1 -0
  73. package/dist/runtime/codex.js +439 -0
  74. package/dist/runtime/codex.js.map +1 -0
  75. package/dist/runtime/context.d.ts +10 -0
  76. package/dist/runtime/context.d.ts.map +1 -0
  77. package/dist/runtime/context.js +151 -0
  78. package/dist/runtime/context.js.map +1 -0
  79. package/dist/runtime/delivery.d.ts +58 -0
  80. package/dist/runtime/delivery.d.ts.map +1 -0
  81. package/dist/runtime/delivery.js +1073 -0
  82. package/dist/runtime/delivery.js.map +1 -0
  83. package/dist/runtime/dependencies.d.ts +19 -0
  84. package/dist/runtime/dependencies.d.ts.map +1 -0
  85. package/dist/runtime/dependencies.js +495 -0
  86. package/dist/runtime/dependencies.js.map +1 -0
  87. package/dist/runtime/github.d.ts +112 -0
  88. package/dist/runtime/github.d.ts.map +1 -0
  89. package/dist/runtime/github.js +510 -0
  90. package/dist/runtime/github.js.map +1 -0
  91. package/dist/runtime/inputs.d.ts +24 -0
  92. package/dist/runtime/inputs.d.ts.map +1 -0
  93. package/dist/runtime/inputs.js +237 -0
  94. package/dist/runtime/inputs.js.map +1 -0
  95. package/dist/runtime/lease.d.ts +11 -0
  96. package/dist/runtime/lease.d.ts.map +1 -0
  97. package/dist/runtime/lease.js +52 -0
  98. package/dist/runtime/lease.js.map +1 -0
  99. package/dist/runtime/lifecycle.d.ts +87 -0
  100. package/dist/runtime/lifecycle.d.ts.map +1 -0
  101. package/dist/runtime/lifecycle.js +1024 -0
  102. package/dist/runtime/lifecycle.js.map +1 -0
  103. package/dist/runtime/process.d.ts +39 -0
  104. package/dist/runtime/process.d.ts.map +1 -0
  105. package/dist/runtime/process.js +304 -0
  106. package/dist/runtime/process.js.map +1 -0
  107. package/dist/runtime/repository.d.ts +36 -0
  108. package/dist/runtime/repository.d.ts.map +1 -0
  109. package/dist/runtime/repository.js +458 -0
  110. package/dist/runtime/repository.js.map +1 -0
  111. package/dist/runtime/state.d.ts +121 -0
  112. package/dist/runtime/state.d.ts.map +1 -0
  113. package/dist/runtime/state.js +1017 -0
  114. package/dist/runtime/state.js.map +1 -0
  115. package/dist/runtime/verifier.d.ts +31 -0
  116. package/dist/runtime/verifier.d.ts.map +1 -0
  117. package/dist/runtime/verifier.js +478 -0
  118. package/dist/runtime/verifier.js.map +1 -0
  119. package/dist/runtime/worker.d.ts +67 -0
  120. package/dist/runtime/worker.d.ts.map +1 -0
  121. package/dist/runtime/worker.js +34 -0
  122. package/dist/runtime/worker.js.map +1 -0
  123. package/dist/security/safe-path.d.ts +3 -0
  124. package/dist/security/safe-path.d.ts.map +1 -0
  125. package/dist/security/safe-path.js +102 -0
  126. package/dist/security/safe-path.js.map +1 -0
  127. package/dist/version.d.ts +4 -0
  128. package/dist/version.d.ts.map +1 -0
  129. package/dist/version.js +4 -0
  130. package/dist/version.js.map +1 -0
  131. package/dist/workflows/founder.d.ts +33 -0
  132. package/dist/workflows/founder.d.ts.map +1 -0
  133. package/dist/workflows/founder.js +422 -0
  134. package/dist/workflows/founder.js.map +1 -0
  135. package/package.json +79 -0
  136. package/recipes/node-typescript-next-web/.github/workflows/ci.yml +34 -0
  137. package/recipes/node-typescript-next-web/.node-version +1 -0
  138. package/recipes/node-typescript-next-web/.tool-versions +1 -0
  139. package/recipes/node-typescript-next-web/AGENTS.md +44 -0
  140. package/recipes/node-typescript-next-web/README.md +16 -0
  141. package/recipes/node-typescript-next-web/WORKFLOW.md +6 -0
  142. package/recipes/node-typescript-next-web/app/api/health/route.ts +5 -0
  143. package/recipes/node-typescript-next-web/app/layout.tsx +19 -0
  144. package/recipes/node-typescript-next-web/app/page.tsx +12 -0
  145. package/recipes/node-typescript-next-web/app/styles.css +21 -0
  146. package/recipes/node-typescript-next-web/docs/development.md +16 -0
  147. package/recipes/node-typescript-next-web/eslint.config.mjs +9 -0
  148. package/recipes/node-typescript-next-web/gitignore.template +5 -0
  149. package/recipes/node-typescript-next-web/next-env.d.ts +7 -0
  150. package/recipes/node-typescript-next-web/next.config.ts +11 -0
  151. package/recipes/node-typescript-next-web/package-lock.json +7540 -0
  152. package/recipes/node-typescript-next-web/package.json +39 -0
  153. package/recipes/node-typescript-next-web/playwright.config.ts +23 -0
  154. package/recipes/node-typescript-next-web/recipe.yaml +71 -0
  155. package/recipes/node-typescript-next-web/scripts/check-package.mjs +3 -0
  156. package/recipes/node-typescript-next-web/src/health.ts +3 -0
  157. package/recipes/node-typescript-next-web/test/browser/home.spec.ts +14 -0
  158. package/recipes/node-typescript-next-web/test/integration/health-route.test.ts +11 -0
  159. package/recipes/node-typescript-next-web/test/unit/health.test.ts +9 -0
  160. package/recipes/node-typescript-next-web/tsconfig.json +33 -0
  161. package/schemas/README.md +64 -0
  162. package/schemas/audit-report.schema.json +59 -0
  163. package/schemas/blueprint.schema.json +34 -0
  164. package/schemas/context-manifest.schema.json +88 -0
  165. package/schemas/delivery-record.schema.json +266 -0
  166. package/schemas/impact-manifest.schema.json +146 -0
  167. package/schemas/managed-repository.schema.json +36 -0
  168. package/schemas/mill-config.schema.json +212 -0
  169. package/schemas/mill-lock.schema.json +96 -0
  170. package/schemas/outcome-plan.schema.json +51 -0
  171. package/schemas/product-contract.schema.json +181 -0
  172. package/schemas/public-alpha-qualification.schema.json +391 -0
  173. package/schemas/recipe-manifest.schema.json +187 -0
  174. package/schemas/release-evidence.schema.json +170 -0
  175. package/schemas/repository-integration-plan.schema.json +156 -0
  176. package/schemas/review-result.schema.json +57 -0
  177. package/schemas/scenario-set.schema.json +84 -0
  178. package/schemas/source-manifest.schema.json +96 -0
  179. package/schemas/specification-proposal.schema.json +406 -0
  180. package/schemas/support-tuple.schema.json +107 -0
  181. package/schemas/task-packet.schema.json +395 -0
  182. package/schemas/validation-evidence.schema.json +125 -0
  183. package/schemas/worker-invocation.schema.json +119 -0
  184. package/schemas/worker-profile.schema.json +53 -0
@@ -0,0 +1,1024 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import path from "node:path";
3
+ import { contextManifestSchema, reviewResultSchema, validationEvidenceSchema, } from "../contracts/schemas.js";
4
+ import { canonicalDigest } from "../contracts/canonical.js";
5
+ import { codexWorkerAdapter, codexAuthStatus, } from "./codex.js";
6
+ import { assertContextFresh, buildContextManifest, } from "./context.js";
7
+ import { ExitCode, MillError, asMillError } from "../errors.js";
8
+ import { assertNewRunTaskContract, loadMillConfig, loadRuntimeInputs, } from "./inputs.js";
9
+ import { assertCandidateIdentity, assertGitControlState, captureGitControlState, commitCandidate, commonGitDirectory, createCandidateWorktree, createDetachedWorktree, deleteCandidateBranch, qualifyRepositoryForBuild, removeCandidateWorktree, resetCandidateWorktree, resolveCommit, } from "./repository.js";
10
+ import { acquireWriterLease, isPurgeSafeRun, isTerminalRun, purgeRepositoryState, publicRunRecord, restoreStateBackup, StateStore, } from "./state.js";
11
+ import { createWorkerInvocation } from "./worker.js";
12
+ import { dependencySnapshotDirectory } from "./dependencies.js";
13
+ import { verifyDeclaredCommands } from "./verifier.js";
14
+ import { processCancellationScope, processIdentityStatus, } from "./process.js";
15
+ import { MILL_VERSION } from "../version.js";
16
+ function operationDeadline(seconds) {
17
+ return Date.now() + seconds * 1000;
18
+ }
19
+ function persistedRunDeadline(run) {
20
+ const deadline = Date.parse(run.deadlineAt);
21
+ if (!Number.isSafeInteger(deadline) || deadline <= Date.now()) {
22
+ throw new MillError("RUN_DEADLINE_EXCEEDED", "The approved run deadline has elapsed; a fresh qualification and run are required.", ExitCode.temporary, { deadlineAt: run.deadlineAt });
23
+ }
24
+ return deadline;
25
+ }
26
+ function baselineEvidenceDigest(evidence) {
27
+ return canonicalDigest({
28
+ schemaVersion: evidence.schemaVersion,
29
+ candidateCommit: evidence.candidateCommit,
30
+ verifierImage: evidence.verifierImage,
31
+ network: evidence.network,
32
+ passed: evidence.passed,
33
+ commands: evidence.commands.map((command) => ({
34
+ commandId: command.commandId,
35
+ required: command.required,
36
+ status: command.status,
37
+ exitCode: command.exitCode,
38
+ outputDigest: command.outputDigest,
39
+ reason: command.reason ?? null,
40
+ })),
41
+ });
42
+ }
43
+ function baselineApprovalDigest(input) {
44
+ return canonicalDigest({
45
+ schemaVersion: "1",
46
+ ...input,
47
+ });
48
+ }
49
+ function assertBuildAuthorized(inputs) {
50
+ if (inputs.config.trustCeiling === "inspect") {
51
+ throw new MillError("BUILD_NOT_AUTHORIZED", "mill.yaml trust ceiling does not authorize build mode.", ExitCode.configuration);
52
+ }
53
+ }
54
+ async function openRunContext(root, taskPath) {
55
+ const inputs = await loadRuntimeInputs(root, taskPath);
56
+ assertBuildAuthorized(inputs);
57
+ const commonDirectory = await commonGitDirectory(root);
58
+ const store = await StateStore.open(inputs.config.repositoryId, commonDirectory);
59
+ return { inputs, store, commonDirectory };
60
+ }
61
+ function storedManifest(run) {
62
+ if (run.contextJson === undefined || run.contextDigest === undefined) {
63
+ throw new MillError("CONTEXT_MANIFEST_MISSING", "Run has no frozen context manifest.", ExitCode.configuration);
64
+ }
65
+ let value;
66
+ try {
67
+ value = JSON.parse(run.contextJson);
68
+ }
69
+ catch (error) {
70
+ throw new MillError("CONTEXT_MANIFEST_INVALID", "Stored context manifest is not valid JSON.", ExitCode.data, { cause: String(error) });
71
+ }
72
+ const parsed = contextManifestSchema.safeParse(value);
73
+ if (!parsed.success) {
74
+ throw new MillError("CONTEXT_MANIFEST_INVALID", "Stored context manifest does not satisfy its schema.", ExitCode.data, { issues: parsed.error.issues });
75
+ }
76
+ const manifest = parsed.data;
77
+ if (canonicalDigest(manifest) !== run.contextDigest) {
78
+ throw new MillError("CONTEXT_MANIFEST_DRIFT", "Stored context manifest digest does not match.", ExitCode.configuration);
79
+ }
80
+ return manifest;
81
+ }
82
+ function storedReviewFindings(run) {
83
+ const source = run.blockCode === "REMOTE_REVIEW_FINDINGS"
84
+ ? run.remoteFeedbackJson
85
+ : run.blockCode === "REVIEW_FINDINGS" ||
86
+ run.blockCode === "REVIEW_NON_CONVERGENCE"
87
+ ? run.reviewJson
88
+ : undefined;
89
+ if (source === undefined)
90
+ return undefined;
91
+ try {
92
+ const parsed = reviewResultSchema.safeParse(JSON.parse(source));
93
+ if (!parsed.success ||
94
+ parsed.data.candidateCommit !== run.candidateCommit) {
95
+ throw parsed.success
96
+ ? new Error("candidate identity mismatch")
97
+ : parsed.error;
98
+ }
99
+ return parsed.data.findings;
100
+ }
101
+ catch (error) {
102
+ throw new MillError("REVIEW_EVIDENCE_INVALID", "Stored local or remote review evidence is invalid or bound to another candidate.", ExitCode.data, { cause: String(error) });
103
+ }
104
+ }
105
+ function storedGitControl(run) {
106
+ if (run.controlJson === undefined) {
107
+ throw new MillError("GIT_CONTROL_SNAPSHOT_MISSING", "Run has no frozen Git control snapshot.", ExitCode.configuration);
108
+ }
109
+ try {
110
+ const value = JSON.parse(run.controlJson);
111
+ if (value.schemaVersion !== "1" ||
112
+ typeof value.currentRef !== "string" ||
113
+ ![value.commonConfig, value.worktreeConfig, value.infoAttributes].every((item) => item === null || /^sha256:[a-f0-9]{64}$/u.test(String(item))) ||
114
+ !/^sha256:[a-f0-9]{64}$/u.test(String(value.otherRefs))) {
115
+ throw new Error("snapshot shape invalid");
116
+ }
117
+ return value;
118
+ }
119
+ catch (error) {
120
+ throw new MillError("GIT_CONTROL_SNAPSHOT_INVALID", "Stored Git control snapshot is invalid.", ExitCode.data, { cause: String(error) });
121
+ }
122
+ }
123
+ export async function assertRunBindings(root, run, inputs) {
124
+ if (run.taskDigest !== inputs.taskDigest ||
125
+ run.configDigest !== inputs.configDigest) {
126
+ throw new MillError("RUN_POLICY_DRIFT", "Task or repository configuration changed after approval.", ExitCode.configuration);
127
+ }
128
+ if ((await resolveCommit(root, inputs.task.baseRef)) !== run.baseCommit) {
129
+ throw new MillError("BASE_REF_DRIFT", "The approved base reference moved after the run started.", ExitCode.configuration);
130
+ }
131
+ if (run.worktreePath === undefined ||
132
+ run.candidateCommit === undefined ||
133
+ run.candidateTree === undefined) {
134
+ throw new MillError("CANDIDATE_MISSING", "Run has no committed candidate.", ExitCode.configuration);
135
+ }
136
+ await assertCandidateIdentity(run.worktreePath, {
137
+ commit: run.candidateCommit,
138
+ tree: run.candidateTree,
139
+ });
140
+ const manifest = storedManifest(run);
141
+ await assertContextFresh(run.worktreePath, manifest);
142
+ await assertGitControlState(run.worktreePath, storedGitControl(run));
143
+ return {
144
+ commit: run.candidateCommit,
145
+ tree: run.candidateTree,
146
+ worktree: run.worktreePath,
147
+ manifest,
148
+ };
149
+ }
150
+ function safeBlock(store, runId, error) {
151
+ try {
152
+ const run = store.getRun(runId);
153
+ if (isTerminalRun(run.status))
154
+ return;
155
+ if (run.status === "blocked") {
156
+ store.replaceBlocker(runId, error.code, "run.blocker_replaced");
157
+ return;
158
+ }
159
+ store.transition(runId, "blocked", "run.blocked", { code: error.code });
160
+ }
161
+ catch {
162
+ // Preserve the primary error when even failure bookkeeping is unavailable.
163
+ }
164
+ }
165
+ function settleFailure(store, runId, error) {
166
+ try {
167
+ const run = store.getRun(runId);
168
+ if (isTerminalRun(run.status))
169
+ return;
170
+ if (run.status === "effect_unknown") {
171
+ store.recordEvent(runId, "run.reconciliation_required", {
172
+ code: error.code,
173
+ });
174
+ return;
175
+ }
176
+ if (run.cancelRequested) {
177
+ store.transition(runId, "cancelled", "run.cancelled", {
178
+ code: error.code,
179
+ });
180
+ return;
181
+ }
182
+ }
183
+ catch {
184
+ // Let safeBlock preserve the primary failure when possible.
185
+ }
186
+ safeBlock(store, runId, error);
187
+ }
188
+ function assertNotCancelled(store, runId) {
189
+ const run = store.getRun(runId);
190
+ if (!run.cancelRequested)
191
+ return;
192
+ if (!isTerminalRun(run.status)) {
193
+ store.transition(runId, "cancelled", "run.cancelled", {
194
+ code: "OPERATOR_CANCELLED",
195
+ });
196
+ }
197
+ throw new MillError("OPERATOR_CANCELLED", "The operator cancelled the active run.", ExitCode.temporary);
198
+ }
199
+ function lifecycleHooks(store, runId, invocationId) {
200
+ return {
201
+ onBeforeSpawn() {
202
+ if (invocationId !== undefined) {
203
+ store.markWorkerLaunchStarted(invocationId);
204
+ }
205
+ },
206
+ onSpawn(process) {
207
+ store.setActiveProcess(runId, process);
208
+ },
209
+ onExit(process) {
210
+ if (invocationId !== undefined) {
211
+ store.recordWorkerProcessExit(runId, invocationId, process?.id);
212
+ }
213
+ else if (process !== undefined) {
214
+ store.clearActiveProcess(runId, process.id);
215
+ }
216
+ },
217
+ cancellationRequested() {
218
+ return store.getRun(runId).cancelRequested;
219
+ },
220
+ };
221
+ }
222
+ async function admitWorker(input) {
223
+ const profile = await codexWorkerAdapter.profile(input.root, input.role);
224
+ const admitted = createWorkerInvocation({
225
+ runId: input.run.id,
226
+ phase: input.phase,
227
+ attempt: input.attempt,
228
+ task: input.inputs.task,
229
+ taskDigest: input.inputs.taskDigest,
230
+ manifest: input.manifest,
231
+ baseCommit: input.run.baseCommit,
232
+ ...(input.candidateCommit === undefined
233
+ ? {}
234
+ : { candidateCommit: input.candidateCommit }),
235
+ ...(input.inputs.continuity === undefined
236
+ ? {}
237
+ : { impactManifestDigest: input.inputs.continuity.impactDigest }),
238
+ profile,
239
+ deadlineAt: input.run.deadlineAt,
240
+ });
241
+ input.store.admitWorkerInvocation({
242
+ runId: input.run.id,
243
+ invocationId: admitted.invocation.invocationId,
244
+ phase: input.phase,
245
+ envelopeDigest: admitted.digest,
246
+ envelopeJson: JSON.stringify(admitted.invocation),
247
+ });
248
+ return {
249
+ invocationId: admitted.invocation.invocationId,
250
+ hooks: lifecycleHooks(input.store, input.run.id, admitted.invocation.invocationId),
251
+ };
252
+ }
253
+ function settleWorkerFailure(store, invocationId, role, error) {
254
+ if (store.workerInvocationStatus(invocationId) !== "launch_started")
255
+ return;
256
+ const failure = asMillError(error);
257
+ store.settleWorkerInvocation(invocationId, role === "builder" ? "uncertain" : "failed", {
258
+ code: failure.code,
259
+ ...(role === "builder" ? { processExited: true } : {}),
260
+ });
261
+ }
262
+ function storedActiveProcess(run) {
263
+ if (run.activeProcessId === undefined ||
264
+ run.activePid === undefined ||
265
+ run.activeProcessGroup === undefined ||
266
+ run.activeProcessIdentity === undefined) {
267
+ return undefined;
268
+ }
269
+ return {
270
+ id: run.activeProcessId,
271
+ pid: run.activePid,
272
+ processGroup: run.activeProcessGroup,
273
+ identity: run.activeProcessIdentity,
274
+ };
275
+ }
276
+ function reconcileMutatingWorkerAdmissions(store, run, active) {
277
+ const activeStatus = active === undefined ? undefined : processIdentityStatus(active);
278
+ if (active !== undefined && activeStatus !== "mismatch") {
279
+ throw new MillError("ORPHANED_EXECUTION_RECONCILIATION_REQUIRED", "A recorded execution may still be active without its controller; Mill will not signal it or resume automatically.", ExitCode.temporary);
280
+ }
281
+ const unresolved = store.unresolvedMutatingWorkerInvocations(run.id);
282
+ const unobservedLaunches = unresolved.filter((invocation) => invocation.status === "launch_started" && !invocation.processExited);
283
+ if (unobservedLaunches.length > 0 &&
284
+ (active === undefined ||
285
+ activeStatus !== "mismatch" ||
286
+ unobservedLaunches.length !== 1)) {
287
+ throw new MillError("WORKER_INVOCATION_RECONCILIATION_REQUIRED", "A mutating worker may have started before process identity was durably observed; attended disposition is required.", ExitCode.temporary, { invocationIds: unobservedLaunches.map((item) => item.invocationId) });
288
+ }
289
+ for (const invocation of unresolved) {
290
+ if (invocation.processExited) {
291
+ store.reconcileWorkerInvocation(run.id, invocation.invocationId, "process_exit_observed");
292
+ }
293
+ else if (invocation.status === "launch_started") {
294
+ store.reconcileWorkerInvocation(run.id, invocation.invocationId, "recorded_process_absent");
295
+ }
296
+ else {
297
+ throw new MillError("WORKER_INVOCATION_RECONCILIATION_REQUIRED", "An uncertain mutating worker lacks durable process-exit evidence; attended disposition is required.", ExitCode.temporary, { invocationId: invocation.invocationId });
298
+ }
299
+ }
300
+ store.setActiveProcess(run.id, null);
301
+ }
302
+ function recordProviderUsage(store, runId, eventType, usage) {
303
+ store.recordEvent(runId, eventType, {
304
+ usageSource: usage.source,
305
+ costSource: usage.cost,
306
+ inputTokens: usage.inputTokens ?? null,
307
+ outputTokens: usage.outputTokens ?? null,
308
+ });
309
+ }
310
+ export async function startLocalRun(input) {
311
+ const context = await openRunContext(input.root, input.taskPath);
312
+ const { inputs, store } = context;
313
+ let run;
314
+ let lease;
315
+ let provisionalWorktree;
316
+ let provisionalBranch;
317
+ const signals = processCancellationScope();
318
+ try {
319
+ assertNewRunTaskContract(inputs.task);
320
+ const qualified = await qualifyRepositoryForBuild(input.root, "HEAD", inputs.config.sensitivePaths);
321
+ if (inputs.task.baseRef !== "HEAD") {
322
+ const requestedBase = await resolveCommit(input.root, inputs.task.baseRef);
323
+ if (requestedBase !== qualified.baseCommit) {
324
+ throw new MillError("BASE_REF_NOT_CHECKED_OUT", "The approved base must equal the clean checked-out HEAD in Wave 2.", ExitCode.configuration);
325
+ }
326
+ }
327
+ lease = await acquireWriterLease(store);
328
+ const activeRuns = store
329
+ .runs()
330
+ .filter((candidate) => !isTerminalRun(candidate.status));
331
+ if (activeRuns.length > 0) {
332
+ throw new MillError("ACTIVE_OUTCOME_CONFLICT", "A new run cannot be created while another nonterminal lifecycle exists.", ExitCode.configuration, {
333
+ activeRuns: activeRuns.map((candidate) => ({
334
+ runId: candidate.id,
335
+ taskId: candidate.taskId,
336
+ status: candidate.status,
337
+ })),
338
+ });
339
+ }
340
+ if (!store.hasBaselineQualification({
341
+ approvalDigest: input.approvalDigest,
342
+ repositoryId: inputs.config.repositoryId,
343
+ taskDigest: inputs.taskDigest,
344
+ configDigest: inputs.configDigest,
345
+ baseCommit: qualified.baseCommit,
346
+ })) {
347
+ throw new MillError("TASK_APPROVAL_REQUIRED", "Run requires an approval digest from a successful matching baseline qualification.", ExitCode.configuration);
348
+ }
349
+ run = store.createRun({
350
+ repositoryId: inputs.config.repositoryId,
351
+ taskId: inputs.task.id,
352
+ taskDigest: inputs.taskDigest,
353
+ configDigest: inputs.configDigest,
354
+ baseCommit: qualified.baseCommit,
355
+ deadlineAt: new Date(operationDeadline(inputs.task.budget.deadlineSeconds)).toISOString(),
356
+ });
357
+ store.transition(run.id, "ready", "run.ready");
358
+ const worktree = path.join(store.worktreesDirectory, run.id);
359
+ const branch = await createCandidateWorktree(input.root, worktree, qualified.baseCommit, inputs.task.id, run.id);
360
+ provisionalWorktree = worktree;
361
+ provisionalBranch = branch;
362
+ const frozen = await buildContextManifest(worktree, qualified.baseCommit, inputs.task, inputs.config, inputs.taskDigest);
363
+ const gitControl = await captureGitControlState(worktree);
364
+ store.setWorkspace(run.id, worktree, frozen.digest, JSON.stringify(frozen.manifest), JSON.stringify(gitControl));
365
+ provisionalWorktree = undefined;
366
+ provisionalBranch = undefined;
367
+ run = store.transition(run.id, "running", "builder.started");
368
+ store.beginBuilderAttempt(run.id, inputs.task.budget.retryCount + 1);
369
+ run = store.getRun(run.id);
370
+ const admission = await admitWorker({
371
+ store,
372
+ run,
373
+ inputs,
374
+ manifest: frozen.manifest,
375
+ root: worktree,
376
+ phase: "build",
377
+ role: "builder",
378
+ attempt: run.attemptCount,
379
+ });
380
+ let invocation;
381
+ let candidate;
382
+ try {
383
+ invocation = await codexWorkerAdapter.runBuilder({
384
+ root: worktree,
385
+ task: inputs.task,
386
+ manifest: frozen.manifest,
387
+ deadlineMs: persistedRunDeadline(run),
388
+ maxOutputBytes: inputs.task.budget.maxOutputBytes,
389
+ signal: signals.signal,
390
+ ...admission.hooks,
391
+ });
392
+ assertNotCancelled(store, run.id);
393
+ await assertGitControlState(worktree, gitControl);
394
+ candidate = await commitCandidate(worktree, qualified.baseCommit, inputs.task, inputs.protectedPaths);
395
+ store.commitCandidate(run.id, candidate.commit, candidate.tree, admission.invocationId);
396
+ }
397
+ catch (error) {
398
+ settleWorkerFailure(store, admission.invocationId, "builder", error);
399
+ throw error;
400
+ }
401
+ recordProviderUsage(store, run.id, "builder.completed", invocation.usage);
402
+ const completed = store.getRun(run.id);
403
+ return { run: publicRunRecord(completed), usage: invocation.usage };
404
+ }
405
+ catch (error) {
406
+ let failure = asMillError(error);
407
+ if (run !== undefined &&
408
+ provisionalWorktree !== undefined &&
409
+ provisionalBranch !== undefined) {
410
+ try {
411
+ await removeCandidateWorktree(input.root, provisionalWorktree);
412
+ await deleteCandidateBranch(input.root, provisionalBranch, run.baseCommit);
413
+ const current = store.getRun(run.id);
414
+ if (!isTerminalRun(current.status)) {
415
+ store.transition(run.id, "failed", "workspace.setup_failed", {
416
+ code: failure.code,
417
+ });
418
+ }
419
+ }
420
+ catch (cleanupError) {
421
+ failure = new MillError("WORKSPACE_SETUP_CLEANUP_FAILED", "Candidate workspace setup failed and its provisional Git state could not be removed safely.", ExitCode.io, {
422
+ primaryCode: failure.code,
423
+ cleanupCause: String(cleanupError),
424
+ });
425
+ }
426
+ }
427
+ if (run !== undefined) {
428
+ settleFailure(store, run.id, failure);
429
+ }
430
+ throw failure;
431
+ }
432
+ finally {
433
+ signals.dispose();
434
+ try {
435
+ await lease?.release();
436
+ }
437
+ finally {
438
+ store.close();
439
+ }
440
+ }
441
+ }
442
+ export async function qualifyBaseline(input) {
443
+ const inputs = await loadRuntimeInputs(input.root, input.taskPath);
444
+ assertNewRunTaskContract(inputs.task);
445
+ assertBuildAuthorized(inputs);
446
+ const signals = processCancellationScope();
447
+ const signal = input.signal === undefined
448
+ ? signals.signal
449
+ : AbortSignal.any([signals.signal, input.signal]);
450
+ try {
451
+ const qualified = await qualifyRepositoryForBuild(input.root, inputs.task.baseRef, inputs.config.sensitivePaths);
452
+ const store = await StateStore.open(inputs.config.repositoryId, qualified.commonDirectory);
453
+ let lease;
454
+ const destination = path.join(store.worktreesDirectory, `baseline-${randomUUID()}`);
455
+ try {
456
+ lease = await acquireWriterLease(store);
457
+ await createDetachedWorktree(input.root, destination, qualified.baseCommit);
458
+ const dependencyRoot = await dependencySnapshotDirectory({
459
+ root: input.root,
460
+ stateDirectory: store.directory,
461
+ config: inputs.config,
462
+ });
463
+ const evidence = await verifyDeclaredCommands({
464
+ root: destination,
465
+ ...(dependencyRoot === undefined ? {} : { dependencyRoot }),
466
+ candidateCommit: qualified.baseCommit,
467
+ config: inputs.config,
468
+ task: inputs.task,
469
+ deadlineMs: operationDeadline(inputs.task.budget.deadlineSeconds),
470
+ maxOutputBytes: inputs.task.budget.maxOutputBytes,
471
+ signal,
472
+ });
473
+ if (!evidence.passed)
474
+ return { approvalDigest: null, evidence };
475
+ const evidenceDigest = baselineEvidenceDigest(evidence);
476
+ const approvalDigest = baselineApprovalDigest({
477
+ taskDigest: inputs.taskDigest,
478
+ configDigest: inputs.configDigest,
479
+ baseCommit: qualified.baseCommit,
480
+ evidenceDigest,
481
+ });
482
+ store.recordBaselineQualification({
483
+ approvalDigest,
484
+ repositoryId: inputs.config.repositoryId,
485
+ taskDigest: inputs.taskDigest,
486
+ configDigest: inputs.configDigest,
487
+ baseCommit: qualified.baseCommit,
488
+ evidenceDigest,
489
+ });
490
+ return { approvalDigest, evidence };
491
+ }
492
+ finally {
493
+ try {
494
+ if (lease !== undefined) {
495
+ await removeCandidateWorktree(input.root, destination);
496
+ }
497
+ }
498
+ finally {
499
+ try {
500
+ await lease?.release();
501
+ }
502
+ finally {
503
+ store.close();
504
+ }
505
+ }
506
+ }
507
+ }
508
+ finally {
509
+ signals.dispose();
510
+ }
511
+ }
512
+ export async function verifyRun(input) {
513
+ const context = await openRunContext(input.root, input.taskPath);
514
+ const { inputs, store } = context;
515
+ let lease;
516
+ const signals = processCancellationScope();
517
+ try {
518
+ lease = await acquireWriterLease(store);
519
+ const run = store.getRun(input.runId);
520
+ if (run.status !== "committed") {
521
+ throw new MillError("RUN_NOT_COMMITTED", "Only a committed candidate can be verified.", ExitCode.configuration);
522
+ }
523
+ const deadlineMs = persistedRunDeadline(run);
524
+ const candidate = await assertRunBindings(input.root, run, inputs);
525
+ const hooks = lifecycleHooks(store, run.id);
526
+ const dependencyRoot = await dependencySnapshotDirectory({
527
+ root: input.root,
528
+ stateDirectory: store.directory,
529
+ config: inputs.config,
530
+ });
531
+ const evidence = await verifyDeclaredCommands({
532
+ root: candidate.worktree,
533
+ ...(dependencyRoot === undefined ? {} : { dependencyRoot }),
534
+ candidateCommit: candidate.commit,
535
+ config: inputs.config,
536
+ task: inputs.task,
537
+ ...(inputs.continuity === undefined
538
+ ? {}
539
+ : {
540
+ impact: inputs.continuity.impact,
541
+ product: inputs.continuity.product,
542
+ scenarios: inputs.continuity.scenarios,
543
+ }),
544
+ deadlineMs,
545
+ maxOutputBytes: inputs.task.budget.maxOutputBytes,
546
+ signal: signals.signal,
547
+ ...hooks,
548
+ });
549
+ assertNotCancelled(store, run.id);
550
+ await resetCandidateWorktree(candidate.worktree, candidate.commit);
551
+ await assertCandidateIdentity(candidate.worktree, candidate);
552
+ return {
553
+ run: publicRunRecord(store.completeValidation(run.id, JSON.stringify(evidence), evidence.passed)),
554
+ evidence,
555
+ };
556
+ }
557
+ catch (error) {
558
+ const failure = asMillError(error);
559
+ if (lease !== undefined)
560
+ settleFailure(store, input.runId, failure);
561
+ throw failure;
562
+ }
563
+ finally {
564
+ signals.dispose();
565
+ try {
566
+ await lease?.release();
567
+ }
568
+ finally {
569
+ store.close();
570
+ }
571
+ }
572
+ }
573
+ export async function reviewRun(input) {
574
+ const context = await openRunContext(input.root, input.taskPath);
575
+ const { inputs, store } = context;
576
+ let lease;
577
+ const signals = processCancellationScope();
578
+ try {
579
+ lease = await acquireWriterLease(store);
580
+ let run = store.getRun(input.runId);
581
+ const deadlineMs = persistedRunDeadline(run);
582
+ const retryableReviewBlocks = new Set([
583
+ "CODEX_CANCELLED",
584
+ "CODEX_DEADLINE_EXCEEDED",
585
+ "CODEX_OUTPUT_BUDGET_EXCEEDED",
586
+ "CODEX_EXECUTION_FAILED",
587
+ "CODEX_PROFILE_UNAVAILABLE",
588
+ "INVALID_REVIEW_RESULT",
589
+ "MALFORMED_WORKER_EVENT",
590
+ "WORKER_SETTLEMENT_MISSING",
591
+ "WORKER_SETTLEMENT_CONFLICT",
592
+ "WORKER_RESULT_MISSING",
593
+ "WORKER_RESULT_CONFLICT",
594
+ ]);
595
+ if (run.status === "blocked" &&
596
+ run.blockCode !== undefined &&
597
+ retryableReviewBlocks.has(run.blockCode) &&
598
+ run.validationJson !== undefined) {
599
+ run = store.transition(run.id, "verified", "review.retry_ready");
600
+ }
601
+ if (run.status !== "verified" || run.validationJson === undefined) {
602
+ throw new MillError("RUN_NOT_VERIFIED", "Only an exact verified candidate can be reviewed.", ExitCode.configuration);
603
+ }
604
+ let evidence;
605
+ try {
606
+ const parsed = validationEvidenceSchema.safeParse(JSON.parse(run.validationJson));
607
+ if (!parsed.success)
608
+ throw parsed.error;
609
+ evidence = parsed.data;
610
+ }
611
+ catch (error) {
612
+ throw new MillError("VALIDATION_EVIDENCE_INVALID", "Stored validation evidence is not valid schema-versioned JSON.", ExitCode.data, { cause: String(error) });
613
+ }
614
+ if (!evidence.passed || evidence.candidateCommit !== run.candidateCommit) {
615
+ throw new MillError("VALIDATION_EVIDENCE_STALE", "Validation evidence is missing, failed, or bound to another candidate.", ExitCode.configuration);
616
+ }
617
+ const candidate = await assertRunBindings(input.root, run, inputs);
618
+ const reviewAttempt = store.beginReviewAttempt(run.id, inputs.task.budget.retryCount + 1);
619
+ const admission = await admitWorker({
620
+ store,
621
+ run,
622
+ inputs,
623
+ manifest: candidate.manifest,
624
+ root: candidate.worktree,
625
+ phase: "review",
626
+ role: "reviewer",
627
+ attempt: reviewAttempt,
628
+ candidateCommit: candidate.commit,
629
+ });
630
+ let result;
631
+ try {
632
+ result = await codexWorkerAdapter.runReviewer({
633
+ root: candidate.worktree,
634
+ task: inputs.task,
635
+ manifest: candidate.manifest,
636
+ candidateCommit: candidate.commit,
637
+ deadlineMs,
638
+ maxOutputBytes: inputs.task.budget.maxOutputBytes,
639
+ signal: signals.signal,
640
+ ...admission.hooks,
641
+ });
642
+ assertNotCancelled(store, run.id);
643
+ await assertCandidateIdentity(candidate.worktree, candidate);
644
+ const completed = store.completeReview(run.id, JSON.stringify(result.review), result.review.findings.length, run.repairCount >= 1, admission.invocationId, {
645
+ usageSource: result.usage.source,
646
+ costSource: result.usage.cost,
647
+ inputTokens: result.usage.inputTokens ?? null,
648
+ outputTokens: result.usage.outputTokens ?? null,
649
+ });
650
+ return {
651
+ run: publicRunRecord(completed),
652
+ review: result.review,
653
+ usage: result.usage,
654
+ };
655
+ }
656
+ catch (error) {
657
+ settleWorkerFailure(store, admission.invocationId, "reviewer", error);
658
+ throw error;
659
+ }
660
+ }
661
+ catch (error) {
662
+ const failure = asMillError(error);
663
+ if (lease !== undefined)
664
+ settleFailure(store, input.runId, failure);
665
+ throw failure;
666
+ }
667
+ finally {
668
+ signals.dispose();
669
+ try {
670
+ await lease?.release();
671
+ }
672
+ finally {
673
+ store.close();
674
+ }
675
+ }
676
+ }
677
+ export async function resumeRun(input) {
678
+ const context = await openRunContext(input.root, input.taskPath);
679
+ const { inputs, store } = context;
680
+ let lease;
681
+ const signals = processCancellationScope();
682
+ try {
683
+ lease = await acquireWriterLease(store);
684
+ let run = store.getRun(input.runId);
685
+ const active = storedActiveProcess(run);
686
+ reconcileMutatingWorkerAdmissions(store, run, active);
687
+ run = store.getRun(run.id);
688
+ if (run.status === "effect_unknown") {
689
+ throw new MillError("GITHUB_RECONCILIATION_REQUIRED", "An unknown GitHub effect must be reconciled before cancellation or local resume.", ExitCode.temporary);
690
+ }
691
+ if (run.cancelRequested && !isTerminalRun(run.status)) {
692
+ return publicRunRecord(store.transition(run.id, "cancelled", "run.cancelled", {
693
+ code: "OPERATOR_CANCELLED",
694
+ }));
695
+ }
696
+ const deadlineMs = persistedRunDeadline(run);
697
+ if (run.status === "running") {
698
+ run = store.transition(run.id, "blocked", "run.interrupted", {
699
+ code: "INTERRUPTED_RUN",
700
+ });
701
+ }
702
+ if (run.status !== "blocked" || run.worktreePath === undefined) {
703
+ throw new MillError("RUN_NOT_RESUMABLE", "Only a blocked run with a preserved worktree can resume.", ExitCode.configuration);
704
+ }
705
+ const worktreePath = run.worktreePath;
706
+ if (run.taskDigest !== inputs.taskDigest ||
707
+ run.configDigest !== inputs.configDigest) {
708
+ throw new MillError("RUN_POLICY_DRIFT", "Task or repository configuration changed after approval.", ExitCode.configuration);
709
+ }
710
+ const manifest = storedManifest(run);
711
+ const gitControl = storedGitControl(run);
712
+ await assertGitControlState(worktreePath, gitControl);
713
+ const findings = storedReviewFindings(run);
714
+ if (findings !== undefined) {
715
+ if (run.repairCount >= 1) {
716
+ throw new MillError("REVIEW_NON_CONVERGENCE", "A second review repair is not permitted.", ExitCode.configuration);
717
+ }
718
+ const reviewedCandidate = await assertRunBindings(input.root, run, inputs);
719
+ const base = reviewedCandidate.commit;
720
+ run = store.beginRepair(run.id);
721
+ const admission = await admitWorker({
722
+ store,
723
+ run,
724
+ inputs,
725
+ manifest,
726
+ root: worktreePath,
727
+ phase: "repair",
728
+ role: "builder",
729
+ attempt: run.repairCount,
730
+ candidateCommit: base,
731
+ });
732
+ let invocation;
733
+ let candidate;
734
+ try {
735
+ invocation = await codexWorkerAdapter.runBuilder({
736
+ root: worktreePath,
737
+ task: inputs.task,
738
+ manifest,
739
+ repairFindings: findings,
740
+ deadlineMs,
741
+ maxOutputBytes: inputs.task.budget.maxOutputBytes,
742
+ signal: signals.signal,
743
+ ...admission.hooks,
744
+ });
745
+ assertNotCancelled(store, run.id);
746
+ await assertGitControlState(worktreePath, gitControl);
747
+ candidate = await commitCandidate(worktreePath, base, inputs.task, inputs.protectedPaths);
748
+ store.commitCandidate(run.id, candidate.commit, candidate.tree, admission.invocationId);
749
+ }
750
+ catch (error) {
751
+ settleWorkerFailure(store, admission.invocationId, "builder", error);
752
+ throw error;
753
+ }
754
+ recordProviderUsage(store, run.id, "repair.builder_completed", invocation.usage);
755
+ return publicRunRecord(store.getRun(run.id));
756
+ }
757
+ if (run.candidateCommit !== undefined) {
758
+ throw new MillError("RUN_REQUIRES_HUMAN_DISPOSITION", "A blocked committed candidate without review findings cannot be retried automatically.", ExitCode.configuration);
759
+ }
760
+ store.beginBuilderAttempt(run.id, inputs.task.budget.retryCount + 1);
761
+ await resetCandidateWorktree(worktreePath, run.baseCommit);
762
+ run = store.transition(run.id, "running", "builder.resumed");
763
+ run = store.getRun(run.id);
764
+ const admission = await admitWorker({
765
+ store,
766
+ run,
767
+ inputs,
768
+ manifest,
769
+ root: worktreePath,
770
+ phase: "build",
771
+ role: "builder",
772
+ attempt: run.attemptCount,
773
+ });
774
+ let invocation;
775
+ let candidate;
776
+ try {
777
+ invocation = await codexWorkerAdapter.runBuilder({
778
+ root: worktreePath,
779
+ task: inputs.task,
780
+ manifest,
781
+ deadlineMs,
782
+ maxOutputBytes: inputs.task.budget.maxOutputBytes,
783
+ signal: signals.signal,
784
+ ...admission.hooks,
785
+ });
786
+ assertNotCancelled(store, run.id);
787
+ await assertGitControlState(worktreePath, gitControl);
788
+ candidate = await commitCandidate(worktreePath, run.baseCommit, inputs.task, inputs.protectedPaths);
789
+ store.commitCandidate(run.id, candidate.commit, candidate.tree, admission.invocationId);
790
+ }
791
+ catch (error) {
792
+ settleWorkerFailure(store, admission.invocationId, "builder", error);
793
+ throw error;
794
+ }
795
+ recordProviderUsage(store, run.id, "builder.resume_completed", invocation.usage);
796
+ return publicRunRecord(store.getRun(run.id));
797
+ }
798
+ catch (error) {
799
+ const failure = asMillError(error);
800
+ if (lease !== undefined)
801
+ settleFailure(store, input.runId, failure);
802
+ throw failure;
803
+ }
804
+ finally {
805
+ signals.dispose();
806
+ try {
807
+ await lease?.release();
808
+ }
809
+ finally {
810
+ store.close();
811
+ }
812
+ }
813
+ }
814
+ export async function cancelRun(input) {
815
+ const config = await loadMillConfig(input.root);
816
+ const commonDirectory = await commonGitDirectory(input.root);
817
+ const store = await StateStore.open(config.repositoryId, commonDirectory);
818
+ let lease;
819
+ try {
820
+ const run = store.requestCancellation(input.runId);
821
+ try {
822
+ lease = await acquireWriterLease(store);
823
+ }
824
+ catch (error) {
825
+ if (error instanceof MillError &&
826
+ error.code === "WRITER_ALREADY_ACTIVE") {
827
+ return publicRunRecord(store.getRun(run.id));
828
+ }
829
+ throw error;
830
+ }
831
+ const current = store.getRun(run.id);
832
+ if (isTerminalRun(current.status)) {
833
+ return publicRunRecord(current);
834
+ }
835
+ if (current.status === "effect_unknown") {
836
+ store.recordEvent(current.id, "run.cancellation_pending", {
837
+ code: "GITHUB_RECONCILIATION_REQUIRED",
838
+ });
839
+ return publicRunRecord(current);
840
+ }
841
+ const active = storedActiveProcess(current);
842
+ try {
843
+ reconcileMutatingWorkerAdmissions(store, current, active);
844
+ }
845
+ catch (error) {
846
+ const failure = asMillError(error);
847
+ if (failure.code !== "ORPHANED_EXECUTION_RECONCILIATION_REQUIRED" &&
848
+ failure.code !== "WORKER_INVOCATION_RECONCILIATION_REQUIRED") {
849
+ throw failure;
850
+ }
851
+ store.recordEvent(current.id, "run.cancellation_pending", {
852
+ code: failure.code,
853
+ });
854
+ return publicRunRecord(current);
855
+ }
856
+ return publicRunRecord(store.transition(current.id, "cancelled", "run.cancelled", {
857
+ code: "OPERATOR_CANCELLED",
858
+ }));
859
+ }
860
+ finally {
861
+ try {
862
+ await lease?.release();
863
+ }
864
+ finally {
865
+ store.close();
866
+ }
867
+ }
868
+ }
869
+ export async function runStatus(input) {
870
+ const config = await loadMillConfig(input.root);
871
+ const commonDirectory = await commonGitDirectory(input.root);
872
+ const store = await StateStore.open(config.repositoryId, commonDirectory);
873
+ let lease;
874
+ try {
875
+ const run = input.runId === undefined ? store.latestRun() : store.getRun(input.runId);
876
+ if (run === undefined)
877
+ return {};
878
+ let interrupted = false;
879
+ let reconciliationRequired = run.status === "effect_unknown" ||
880
+ store.unresolvedMutatingWorkerInvocations(run.id).length > 0;
881
+ const active = storedActiveProcess(run);
882
+ let controllerAbsent = false;
883
+ if (!isTerminalRun(run.status) &&
884
+ (run.status === "running" || active !== undefined)) {
885
+ try {
886
+ lease = await acquireWriterLease(store);
887
+ controllerAbsent = true;
888
+ }
889
+ catch (error) {
890
+ if (!(error instanceof MillError && error.code === "WRITER_ALREADY_ACTIVE")) {
891
+ throw error;
892
+ }
893
+ }
894
+ }
895
+ if (controllerAbsent) {
896
+ if (active !== undefined &&
897
+ processIdentityStatus(active) !== "mismatch") {
898
+ reconciliationRequired = true;
899
+ }
900
+ else if (run.status === "running") {
901
+ interrupted = true;
902
+ }
903
+ }
904
+ return {
905
+ run: publicRunRecord(run),
906
+ ...(interrupted ? { interrupted: true } : {}),
907
+ ...(reconciliationRequired ? { reconciliationRequired: true } : {}),
908
+ };
909
+ }
910
+ finally {
911
+ try {
912
+ await lease?.release();
913
+ }
914
+ finally {
915
+ store.close();
916
+ }
917
+ }
918
+ }
919
+ export async function runInventory(input) {
920
+ const config = await loadMillConfig(input.root);
921
+ const commonDirectory = await commonGitDirectory(input.root);
922
+ const store = await StateStore.open(config.repositoryId, commonDirectory);
923
+ try {
924
+ return store.runs().map(publicRunRecord);
925
+ }
926
+ finally {
927
+ store.close();
928
+ }
929
+ }
930
+ export async function stateBackup(input) {
931
+ const config = await loadMillConfig(input.root);
932
+ const commonDirectory = await commonGitDirectory(input.root);
933
+ const store = await StateStore.open(config.repositoryId, commonDirectory);
934
+ try {
935
+ return await store.backup();
936
+ }
937
+ finally {
938
+ store.close();
939
+ }
940
+ }
941
+ export async function stateRestore(input) {
942
+ const config = await loadMillConfig(input.root);
943
+ const commonDirectory = await commonGitDirectory(input.root);
944
+ const store = await StateStore.open(config.repositoryId, commonDirectory);
945
+ let lease;
946
+ try {
947
+ lease = await acquireWriterLease(store);
948
+ store.close();
949
+ return await restoreStateBackup(config.repositoryId, commonDirectory, input.backupPath);
950
+ }
951
+ finally {
952
+ store.close();
953
+ await lease?.release();
954
+ }
955
+ }
956
+ export async function statePurge(input) {
957
+ const config = await loadMillConfig(input.root);
958
+ if (input.confirmation !== config.repositoryId) {
959
+ throw new MillError("PURGE_CONFIRMATION_MISMATCH", "Purge confirmation does not match the managed repository UUID.", ExitCode.configuration);
960
+ }
961
+ const commonDirectory = await commonGitDirectory(input.root);
962
+ const store = await StateStore.open(config.repositoryId, commonDirectory);
963
+ let lease;
964
+ let storeClosed = false;
965
+ try {
966
+ lease = await acquireWriterLease(store);
967
+ const runs = store.runs();
968
+ if (runs.some((run) => !isPurgeSafeRun(run.status))) {
969
+ throw new MillError("ACTIVE_RUNS_BLOCK_PURGE", "All runs must be locally reviewed or terminal before state can be purged.", ExitCode.configuration);
970
+ }
971
+ store.close();
972
+ storeClosed = true;
973
+ for (const run of runs) {
974
+ if (run.worktreePath !== undefined) {
975
+ await removeCandidateWorktree(input.root, run.worktreePath);
976
+ }
977
+ }
978
+ await purgeRepositoryState(config.repositoryId, commonDirectory);
979
+ }
980
+ finally {
981
+ if (!storeClosed)
982
+ store.close();
983
+ await lease?.release();
984
+ }
985
+ }
986
+ export async function supportBundle(input) {
987
+ const config = await loadMillConfig(input.root);
988
+ const commonDirectory = await commonGitDirectory(input.root);
989
+ const store = await StateStore.open(config.repositoryId, commonDirectory);
990
+ try {
991
+ const selected = input.runId === undefined ? store.latestRun() : store.getRun(input.runId);
992
+ return {
993
+ schemaVersion: "1",
994
+ millVersion: MILL_VERSION,
995
+ runtime: {
996
+ node: process.versions.node,
997
+ platform: process.platform,
998
+ arch: process.arch,
999
+ },
1000
+ repositoryId: config.repositoryId,
1001
+ ...(selected === undefined
1002
+ ? { run: null, events: [] }
1003
+ : {
1004
+ run: {
1005
+ id: selected.id,
1006
+ taskId: selected.taskId,
1007
+ status: selected.status,
1008
+ baseCommit: selected.baseCommit,
1009
+ candidateCommit: selected.candidateCommit ?? null,
1010
+ blockCode: selected.blockCode ?? null,
1011
+ repairCount: selected.repairCount,
1012
+ attemptCount: selected.attemptCount,
1013
+ },
1014
+ events: store.events(selected.id),
1015
+ }),
1016
+ redaction: "credentials, prompts, model streams, command output, and host paths are excluded",
1017
+ };
1018
+ }
1019
+ finally {
1020
+ store.close();
1021
+ }
1022
+ }
1023
+ export { codexAuthStatus };
1024
+ //# sourceMappingURL=lifecycle.js.map