@astrosheep/keiyaku 2.9.8 → 2.9.10

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 (230) hide show
  1. package/README.md +50 -182
  2. package/build/.tsbuildinfo +1 -1
  3. package/build/agents/harness/control-types.js +18 -1
  4. package/build/agents/harness/index.js +2 -0
  5. package/build/agents/harness/outcome-error.js +81 -0
  6. package/build/agents/harness/projection.js +10 -7
  7. package/build/agents/harness/router.js +5 -1
  8. package/build/agents/providers/claude-agent-sdk/adapter.js +18 -4
  9. package/build/agents/providers/claude-agent-sdk/registration.js +11 -1
  10. package/build/agents/providers/claude-agent-sdk/session.js +19 -5
  11. package/build/agents/providers/codex-app-server/adapter.js +11 -6
  12. package/build/agents/providers/codex-app-server/events.js +67 -6
  13. package/build/agents/providers/codex-app-server/registration.js +11 -1
  14. package/build/agents/providers/opencode-sdk/adapter.js +8 -1
  15. package/build/agents/providers/opencode-sdk/events.js +24 -2
  16. package/build/agents/providers/opencode-sdk/registration.js +11 -1
  17. package/build/agents/providers/pi/checkpoint.js +9 -1
  18. package/build/agents/providers/pi/events.js +2 -0
  19. package/build/agents/providers/pi/registration.js +14 -1
  20. package/build/cli/commands/akuma/akuma/handler.js +1 -1
  21. package/build/cli/commands/akuma/akuma/meta.js +3 -3
  22. package/build/cli/commands/akuma/catalog.js +2 -2
  23. package/build/cli/commands/akuma/{view → show}/handler.js +5 -5
  24. package/build/cli/commands/akuma/{view → show}/meta.js +3 -3
  25. package/build/cli/commands/akuma.js +1 -1
  26. package/build/cli/commands/contract/amend/handler.js +5 -4
  27. package/build/cli/commands/contract/arc/handler.js +5 -4
  28. package/build/cli/commands/contract/audit/handler.js +2 -2
  29. package/build/cli/commands/contract/bind/handler.js +12 -3
  30. package/build/cli/commands/contract/bind/meta.js +45 -15
  31. package/build/cli/commands/contract/forfeit/handler.js +9 -5
  32. package/build/cli/commands/contract/log/handler.js +2 -2
  33. package/build/cli/commands/contract/petition/handler.js +34 -7
  34. package/build/cli/commands/contract/renew/handler.js +7 -7
  35. package/build/cli/commands/contract/renew/meta.js +3 -3
  36. package/build/cli/commands/metadata.js +10 -6
  37. package/build/cli/commands/projection/call/meta.js +1 -1
  38. package/build/cli/commands/projection/catalog.js +2 -0
  39. package/build/cli/commands/projection/history/handler.js +21 -0
  40. package/build/cli/commands/projection/history/meta.js +9 -0
  41. package/build/cli/commands/projection/shared.js +3 -4
  42. package/build/cli/commands/projection/status/handler.js +3 -3
  43. package/build/cli/commands/projection/wait/handler.js +20 -11
  44. package/build/cli/commands/task/add/handler.js +14 -5
  45. package/build/cli/commands/task/add/meta.js +2 -2
  46. package/build/cli/commands/task/catalog.js +4 -0
  47. package/build/cli/commands/task/done/handler.js +1 -4
  48. package/build/cli/commands/task/done/meta.js +1 -1
  49. package/build/cli/commands/task/drop/meta.js +1 -1
  50. package/build/cli/commands/task/hold/handler.js +3 -0
  51. package/build/cli/commands/task/hold/meta.js +8 -0
  52. package/build/cli/commands/task/log/handler.js +1 -1
  53. package/build/cli/commands/task/log/meta.js +2 -2
  54. package/build/cli/commands/task/resume/handler.js +3 -0
  55. package/build/cli/commands/task/resume/meta.js +8 -0
  56. package/build/cli/commands/task/shared.js +39 -14
  57. package/build/cli/commands/task/show/handler.js +1 -1
  58. package/build/cli/commands/task/show/meta.js +1 -1
  59. package/build/cli/commands/task/start/meta.js +1 -1
  60. package/build/cli/commands/task/stop/handler.js +1 -4
  61. package/build/cli/commands/task/stop/meta.js +1 -1
  62. package/build/cli/commands/task/update/handler.js +11 -3
  63. package/build/cli/commands/task/update/meta.js +3 -3
  64. package/build/cli/completion.js +3 -2
  65. package/build/cli/flags.js +51 -14
  66. package/build/cli/help.js +4 -1
  67. package/build/cli/index.js +40 -25
  68. package/build/cli/parse-flags.js +40 -7
  69. package/build/cli/parse-metadata.js +5 -2
  70. package/build/cli/parse-selectors.js +2 -3
  71. package/build/cli/parse.js +40 -6
  72. package/build/cli/projection-address.js +12 -10
  73. package/build/cli/render/amendment-warning.js +8 -0
  74. package/build/cli/render/arc.js +6 -2
  75. package/build/cli/render/audit.js +400 -81
  76. package/build/cli/render/kanshi.js +242 -79
  77. package/build/cli/render/lifecycle.js +56 -0
  78. package/build/cli/render/misc.js +24 -28
  79. package/build/cli/render/petition.js +5 -0
  80. package/build/cli/render/projection-activity.js +65 -29
  81. package/build/cli/render/projection-history.js +64 -0
  82. package/build/cli/render/shared.js +23 -9
  83. package/build/cli/render/status.js +13 -35
  84. package/build/cli/render/task.js +51 -26
  85. package/build/cli/render/terminal-failure.js +3 -1
  86. package/build/cli/render/wait.js +45 -69
  87. package/build/cli/subagent-guard.js +1 -1
  88. package/build/cli/types.js +1 -1
  89. package/build/config/env-keys.js +3 -1
  90. package/build/config/env.js +24 -1
  91. package/build/config/settings/schema.js +3 -1
  92. package/build/core/address-carrier.js +89 -0
  93. package/build/core/addressing.js +255 -196
  94. package/build/core/amend.js +115 -38
  95. package/build/core/amendment-criteria.js +17 -0
  96. package/build/core/amendment-warning.js +10 -0
  97. package/build/core/arc.js +184 -83
  98. package/build/core/audit/candidate.js +110 -9
  99. package/build/core/audit/coordinates.js +120 -65
  100. package/build/core/audit/evidence.js +19 -23
  101. package/build/core/audit/facade.js +34 -11
  102. package/build/core/audit/report.js +5 -5
  103. package/build/core/bind-reconciliation.js +390 -0
  104. package/build/core/bind-workspace.js +12 -20
  105. package/build/core/bind.js +242 -273
  106. package/build/core/call/call.js +1 -0
  107. package/build/core/call/context.js +46 -71
  108. package/build/core/call/prompt.js +6 -3
  109. package/build/core/context.js +49 -35
  110. package/build/core/contract-carrier-runtime.js +408 -0
  111. package/build/core/contract-carrier.js +270 -0
  112. package/build/core/contract-view.js +33 -27
  113. package/build/core/contract.js +5 -1
  114. package/build/core/derived-replay.js +207 -0
  115. package/build/core/draft.js +125 -57
  116. package/build/core/forfeit.js +113 -56
  117. package/build/core/identifier-slug.js +63 -13
  118. package/build/core/ids.js +4 -4
  119. package/build/core/lifecycle-history.js +35 -0
  120. package/build/core/lifecycle-recovery.js +117 -0
  121. package/build/core/lifecycle-runner.js +377 -0
  122. package/build/core/log.js +37 -11
  123. package/build/core/outcome-base.js +181 -0
  124. package/build/core/process-group.js +139 -0
  125. package/build/core/projection/generation/database.js +82 -7
  126. package/build/core/projection/generation/model.js +9 -36
  127. package/build/core/projection/generation/projection-generation-continuation.js +10 -22
  128. package/build/core/projection/generation/projection-generation-execution.js +13 -6
  129. package/build/core/projection/index.js +5 -4
  130. package/build/core/projection/projection-activity.js +29 -8
  131. package/build/core/projection/projection-alias.js +23 -10
  132. package/build/core/projection/projection-core.js +21 -7
  133. package/build/core/projection/projection-execution-observer.js +91 -39
  134. package/build/core/projection/projection-history.js +474 -0
  135. package/build/core/projection/projection-runner-lock.js +12 -0
  136. package/build/core/projection/projection-status-observation.js +365 -0
  137. package/build/core/projection/projection-status.js +35 -337
  138. package/build/core/projection/projection-terminal-failure.js +13 -8
  139. package/build/core/projection/projection-wait.js +7 -2
  140. package/build/core/projection/tell/launch-store.js +244 -0
  141. package/build/core/projection/tell/model.js +3 -3
  142. package/build/core/projection/tell/store.js +70 -118
  143. package/build/core/registry.js +22 -87
  144. package/build/core/renew-build.js +424 -0
  145. package/build/core/renew-plan.js +239 -0
  146. package/build/core/renew-rewrite.js +77 -0
  147. package/build/core/renew.js +295 -520
  148. package/build/core/repository-ledger/accepted-fold-read.js +19 -0
  149. package/build/core/repository-ledger/accepted-tail.js +116 -0
  150. package/build/core/repository-ledger/admission.js +38 -0
  151. package/build/core/repository-ledger/authoritative-transaction.js +1 -0
  152. package/build/core/repository-ledger/claim-evidence.js +147 -0
  153. package/build/core/repository-ledger/codec.js +297 -0
  154. package/build/core/repository-ledger/current-state-store.js +853 -0
  155. package/build/core/repository-ledger/fold-repository.js +375 -0
  156. package/build/core/repository-ledger/fold.js +248 -0
  157. package/build/core/repository-ledger/git-object-store.js +243 -0
  158. package/build/core/repository-ledger/history-read.js +15 -0
  159. package/build/core/repository-ledger/incremental-read.js +153 -0
  160. package/build/core/repository-ledger/index.js +15 -0
  161. package/build/core/repository-ledger/inventory-plan.js +75 -0
  162. package/build/core/repository-ledger/inventory.js +310 -0
  163. package/build/core/repository-ledger/publication-admission.js +125 -0
  164. package/build/core/repository-ledger/publication.js +353 -0
  165. package/build/core/repository-ledger/read-model.js +469 -0
  166. package/build/core/repository-ledger/transaction-membership.js +78 -0
  167. package/build/core/repository-ledger/traversal.js +404 -0
  168. package/build/core/repository-ledger/write-transaction.js +339 -0
  169. package/build/core/seal.js +168 -161
  170. package/build/core/settlement/claim-delivery.js +42 -215
  171. package/build/core/settlement/claim.js +235 -153
  172. package/build/core/settlement/index.js +4 -4
  173. package/build/core/settlement/petition-claim-gates.js +52 -33
  174. package/build/core/settlement/petition-forfeit.js +12 -5
  175. package/build/core/settlement/petition-head-guard.js +8 -5
  176. package/build/core/settlement/petition-preview.js +68 -56
  177. package/build/core/settlement/petition-window.js +50 -0
  178. package/build/core/settlement/petition.js +252 -131
  179. package/build/core/settlement/queue-read-model.js +72 -95
  180. package/build/core/settlement/queue-seat-allocation.js +35 -126
  181. package/build/core/settlement/queue.js +1 -1
  182. package/build/core/settlement/settlement.js +172 -80
  183. package/build/core/settlement/verdict.js +112 -66
  184. package/build/core/settlement/verification-coordination.js +305 -0
  185. package/build/core/settlement/verification-supervisor.js +275 -0
  186. package/build/core/settlement/verification.js +398 -31
  187. package/build/core/status/board.js +125 -127
  188. package/build/core/status/lifecycle.js +7 -80
  189. package/build/core/structured-query.js +23 -0
  190. package/build/core/target-ref.js +1 -1
  191. package/build/core/task/board.js +59 -24
  192. package/build/core/task/commands.js +181 -33
  193. package/build/core/task/coordinate.js +56 -0
  194. package/build/core/task/document.js +25 -9
  195. package/build/core/task/index.js +7 -4
  196. package/build/core/task/query.js +17 -27
  197. package/build/core/task/settlement-git.js +164 -179
  198. package/build/core/task/settlement-policy.js +15 -11
  199. package/build/core/task/source-board.js +31 -13
  200. package/build/core/task/task-bind-preparation.js +193 -0
  201. package/build/core/task/task-contract.js +102 -193
  202. package/build/core/task/task-file-transaction.js +171 -0
  203. package/build/core/task/task-git-runtime.js +76 -14
  204. package/build/core/task/task-git-store.js +68 -169
  205. package/build/core/task/task-store-repository.js +13 -6
  206. package/build/core/task/task-worktree.js +6 -33
  207. package/build/core/task/task.js +1 -1
  208. package/build/core/task/tree.js +3 -3
  209. package/build/core/task/validation.js +4 -3
  210. package/build/core/transcripts.js +93 -1
  211. package/build/core/worktree-bootstrap.js +44 -36
  212. package/build/core/worktree-path.js +210 -77
  213. package/build/flow-error.js +7 -7
  214. package/build/generated/version.js +2 -2
  215. package/build/git/core.js +76 -13
  216. package/build/git/refs.js +168 -46
  217. package/build/git/streaming-batch.js +73 -13
  218. package/package.json +1 -1
  219. package/skills/keiyaku/SKILL.md +1 -1
  220. package/skills/keiyaku-akuma/SKILL.md +3 -3
  221. package/skills/keiyaku-task/SKILL.md +4 -2
  222. package/skills/keiyaku-workflow/SKILL.md +88 -203
  223. package/build/core/entry.js +0 -305
  224. package/build/core/ledger-batch.js +0 -194
  225. package/build/core/ledger.js +0 -84
  226. package/build/core/projection/leash.js +0 -87
  227. package/build/core/ref-log.js +0 -119
  228. package/build/core/renew-session.js +0 -128
  229. package/build/core/status/ledger-batch.js +0 -1
  230. package/build/core/status/reconciliation.js +0 -137
@@ -2,17 +2,41 @@ import { spawn } from "node:child_process";
2
2
  import { getConfig } from "../../config/env.js";
3
3
  import { assertSettingsKnobUsable } from "../../config/settings/disease.js";
4
4
  import { loadKeiyakuSettings } from "../../config/settings/loader.js";
5
- import { FlowError } from "../../flow-error.js";
6
- const AUDIT_OUTPUT_TAIL_BYTES = 8_192;
7
- function tail(value, limit = AUDIT_OUTPUT_TAIL_BYTES) {
8
- if (value.byteLength <= limit) {
9
- return { tail: value.toString("utf8"), truncated: false, omittedBytes: 0 };
5
+ import { PROCESS_GROUP_KILL_GRACE_MS, ProcessTreeTerminationError, ownedProcessIdentity, ownedProcessIsLive, terminateOwnedProcess, terminateProcessGroup, } from "../process-group.js";
6
+ import { VerificationCoordination } from "./verification-coordination.js";
7
+ import { VERIFICATION_SUPERVISOR_PROTOCOL_VERSION, supervisorMessageSchema, verificationSupervisorArguments, } from "./verification-supervisor.js";
8
+ const OUTPUT_TAIL_BYTES = 8_192;
9
+ const COORDINATION_POLL_MS = 50;
10
+ const COORDINATION_RECOVERY_MARGIN_MS = 60_000;
11
+ class OutputTailCollector {
12
+ #chunks = [];
13
+ #retainedBytes = 0;
14
+ #totalBytes = 0;
15
+ push(chunk) {
16
+ const value = Buffer.from(chunk);
17
+ this.#totalBytes += value.byteLength;
18
+ this.#chunks.push(value);
19
+ this.#retainedBytes += value.byteLength;
20
+ while (this.#retainedBytes > OUTPUT_TAIL_BYTES && this.#chunks.length > 0) {
21
+ const first = this.#chunks[0];
22
+ const excess = this.#retainedBytes - OUTPUT_TAIL_BYTES;
23
+ if (first.byteLength <= excess) {
24
+ this.#chunks.shift();
25
+ this.#retainedBytes -= first.byteLength;
26
+ }
27
+ else {
28
+ this.#chunks[0] = first.subarray(excess);
29
+ this.#retainedBytes -= excess;
30
+ }
31
+ }
32
+ }
33
+ result() {
34
+ return {
35
+ tail: Buffer.concat(this.#chunks).toString("utf8"),
36
+ truncated: this.#totalBytes > this.#retainedBytes,
37
+ omittedBytes: this.#totalBytes - this.#retainedBytes,
38
+ };
10
39
  }
11
- return {
12
- tail: value.subarray(value.byteLength - limit).toString("utf8"),
13
- truncated: true,
14
- omittedBytes: value.byteLength - limit,
15
- };
16
40
  }
17
41
  export function verificationExecutor(command) {
18
42
  const basename = command.command.split(/[\\/]/).pop() ?? "";
@@ -26,34 +50,377 @@ export function verificationExecutor(command) {
26
50
  return { cause: "missing_command_body" };
27
51
  return { executor, body };
28
52
  }
29
- export async function resolveVerificationCommands(cwd) {
53
+ /** The document schema owns parsing and persistence; this only consumes its resolved declarations. */
54
+ export function contractVerificationDeclarations(lifecycle) {
55
+ const bind = lifecycle.history.find(({ entry }) => entry.kind === "bind")?.entry;
56
+ return bind?.kind === "bind" ? bind.data.verification : [];
57
+ }
58
+ export function composeVerificationPlan(input) {
59
+ const contract = input.contract ?? [];
60
+ const timeoutMs = input.timeoutMs ?? 300_000;
61
+ return [
62
+ ...contract.map((declaration, index) => ({
63
+ source: "contract",
64
+ label: `Verification ${index + 1}`,
65
+ declaration,
66
+ command: declaration,
67
+ args: [],
68
+ shell: true,
69
+ timeoutMs,
70
+ })),
71
+ ...input.repositoryGates.map((gate) => ({
72
+ source: "repository-gate",
73
+ label: gate.label,
74
+ declaration: gate.label,
75
+ command: gate.command,
76
+ args: [...gate.args],
77
+ shell: false,
78
+ timeoutMs,
79
+ })),
80
+ ];
81
+ }
82
+ export async function resolveVerificationPolicy(cwd) {
30
83
  const loaded = await loadKeiyakuSettings(cwd);
31
84
  assertSettingsKnobUsable(loaded, "gate");
32
- return loaded.knobs?.gate?.commands ?? [];
85
+ return {
86
+ commands: loaded.knobs?.gate?.commands ?? [],
87
+ timeoutMs: loaded.knobs?.gate?.timeoutMs ?? 300_000,
88
+ };
89
+ }
90
+ function executorFor(entry) {
91
+ if (!entry.shell)
92
+ return entry.command;
93
+ return process.platform === "win32" ? (process.env.ComSpec ?? "cmd.exe") : "/bin/sh";
33
94
  }
34
- export async function runVerificationCommand(cwd, verification) {
95
+ export async function runVerificationPlanEntry(cwd, entry, signal, onSupervisorSpawned) {
35
96
  const started = process.hrtime.bigint();
36
- return await new Promise((resolve, reject) => {
37
- const child = spawn(verification.command, verification.args, {
38
- cwd,
39
- stdio: ["ignore", "pipe", "pipe"],
97
+ return new Promise((resolve, reject) => {
98
+ const stdout = new OutputTailCollector();
99
+ const stderr = new OutputTailCollector();
100
+ let settled = false;
101
+ let timer;
102
+ let cancel;
103
+ const finish = (execution) => {
104
+ if (settled)
105
+ return;
106
+ settled = true;
107
+ if (timer)
108
+ clearTimeout(timer);
109
+ if (cancel)
110
+ signal?.removeEventListener("abort", cancel);
111
+ resolve(execution);
112
+ };
113
+ const streams = () => ({
114
+ durationMs: Number((process.hrtime.bigint() - started) / 1000000n),
115
+ stdout: stdout.result(),
116
+ stderr: stderr.result(),
117
+ });
118
+ let terminating;
119
+ let terminal;
120
+ const child = spawn(process.execPath, verificationSupervisorArguments(), {
121
+ cwd: process.cwd(),
122
+ shell: false,
123
+ stdio: ["ignore", "pipe", "pipe", "ipc"],
40
124
  env: getConfig().runtimeEnv,
125
+ detached: process.platform !== "win32",
126
+ });
127
+ child.stdout?.on("data", (chunk) => stdout.push(chunk));
128
+ child.stderr?.on("data", (chunk) => stderr.push(chunk));
129
+ child.once("error", (error) => {
130
+ finish({
131
+ status: "runner_failed",
132
+ ...streams(),
133
+ cause: { code: "SPAWN_FAILED", message: error.message },
134
+ });
135
+ });
136
+ const detachUncleanSupervisor = () => {
137
+ child.stdout?.destroy();
138
+ child.stderr?.destroy();
139
+ if (child.connected)
140
+ child.disconnect();
141
+ child.unref();
142
+ };
143
+ const identity = ownedProcessIdentity(child);
144
+ if (identity && onSupervisorSpawned) {
145
+ try {
146
+ onSupervisorSpawned(identity);
147
+ }
148
+ catch (error) {
149
+ void terminateProcessGroup(child).then(() => reject(error), (terminationError) => reject(new ProcessTreeTerminationError(`runner registration failed and cleanup failed: ${terminationError instanceof Error ? terminationError.message : String(terminationError)}`, { cause: error })));
150
+ return;
151
+ }
152
+ }
153
+ child.on("message", (raw) => {
154
+ const parsed = supervisorMessageSchema.safeParse(raw);
155
+ if (!parsed.success)
156
+ return;
157
+ const message = parsed.data;
158
+ if (message.type === "ready") {
159
+ if (terminating)
160
+ return;
161
+ const configureMessage = {
162
+ version: VERIFICATION_SUPERVISOR_PROTOCOL_VERSION,
163
+ type: "configure",
164
+ cwd,
165
+ command: entry.command,
166
+ args: entry.args,
167
+ shell: entry.shell,
168
+ timeoutMs: entry.timeoutMs,
169
+ };
170
+ child.send?.(configureMessage);
171
+ }
172
+ else if (message.type === "configured") {
173
+ if (terminating)
174
+ return;
175
+ const startMessage = {
176
+ version: VERIFICATION_SUPERVISOR_PROTOCOL_VERSION,
177
+ type: "start",
178
+ };
179
+ child.send?.(startMessage);
180
+ }
181
+ else if (message.type === "terminal") {
182
+ terminal = message;
183
+ if (message.outcome.kind === "tree-termination-failed" && !terminating) {
184
+ const supervisorFailure = message.outcome.message;
185
+ terminating = "TIMED_OUT";
186
+ void terminateProcessGroup(child).then(() => {
187
+ finish({
188
+ status: "runner_failed",
189
+ ...streams(),
190
+ cause: { code: "TIMED_OUT", message: `verification exceeded ${entry.timeoutMs}ms` },
191
+ });
192
+ }, (error) => {
193
+ detachUncleanSupervisor();
194
+ finish({
195
+ status: "runner_failed",
196
+ ...streams(),
197
+ cause: {
198
+ code: "TREE_TERMINATION_FAILED",
199
+ message: `${supervisorFailure}; parent cleanup failed: ${error instanceof Error ? error.message : String(error)}`,
200
+ },
201
+ });
202
+ });
203
+ }
204
+ }
41
205
  });
42
- const stdout = [];
43
- const stderr = [];
44
- child.stdout.on("data", (chunk) => stdout.push(Buffer.from(chunk)));
45
- child.stderr.on("data", (chunk) => stderr.push(Buffer.from(chunk)));
46
- child.on("error", reject);
47
- child.on("close", (code) => {
48
- const durationMs = Number((process.hrtime.bigint() - started) / 1000000n);
49
- resolve({
50
- code: typeof code === "number" ? code : -1,
51
- durationMs,
52
- stdout: tail(Buffer.concat(stdout)),
53
- stderr: tail(Buffer.concat(stderr)),
206
+ const terminate = (cause) => {
207
+ if (settled || terminating)
208
+ return;
209
+ terminating = cause;
210
+ void terminateProcessGroup(child).then(() => {
211
+ finish({
212
+ status: "runner_failed",
213
+ ...streams(),
214
+ cause: {
215
+ code: cause,
216
+ message: cause === "TIMED_OUT" ? `verification exceeded ${entry.timeoutMs}ms` : "verification cancelled",
217
+ },
218
+ });
219
+ }, (error) => {
220
+ detachUncleanSupervisor();
221
+ finish({
222
+ status: "runner_failed",
223
+ ...streams(),
224
+ cause: {
225
+ code: error instanceof ProcessTreeTerminationError ? error.code : "TREE_TERMINATION_FAILED",
226
+ message: error instanceof Error ? error.message : String(error),
227
+ },
228
+ });
54
229
  });
230
+ };
231
+ timer = setTimeout(() => terminate("TIMED_OUT"), entry.timeoutMs);
232
+ timer.unref?.();
233
+ cancel = () => terminate("CANCELLED");
234
+ signal?.addEventListener("abort", cancel, { once: true });
235
+ if (signal?.aborted)
236
+ cancel();
237
+ child.once("close", (code, closeSignal) => {
238
+ void (async () => {
239
+ if (timer)
240
+ clearTimeout(timer);
241
+ if (cancel)
242
+ signal?.removeEventListener("abort", cancel);
243
+ if (terminating)
244
+ return;
245
+ if (identity && ownedProcessIsLive(identity)) {
246
+ try {
247
+ await terminateOwnedProcess(identity);
248
+ }
249
+ catch (error) {
250
+ finish({
251
+ status: "runner_failed",
252
+ ...streams(),
253
+ cause: {
254
+ code: "TREE_TERMINATION_FAILED",
255
+ message: error instanceof Error ? error.message : String(error),
256
+ },
257
+ });
258
+ return;
259
+ }
260
+ }
261
+ if (!terminal) {
262
+ finish({
263
+ status: "runner_failed",
264
+ ...streams(),
265
+ cause: {
266
+ code: "PROCESS_TERMINATED",
267
+ message: `verification supervisor terminated${closeSignal ? ` by ${closeSignal}` : code === null ? " without an exit status" : ` with exit ${code}`}`,
268
+ },
269
+ });
270
+ return;
271
+ }
272
+ const observed = { durationMs: terminal.durationMs, stdout: stdout.result(), stderr: stderr.result() };
273
+ if (terminal.outcome.kind === "exit") {
274
+ finish({ status: "executed", ...observed, code: terminal.outcome.code });
275
+ }
276
+ else if (terminal.outcome.kind === "spawn-failed") {
277
+ finish({ status: "runner_failed", ...observed, cause: { code: "SPAWN_FAILED", message: terminal.outcome.message } });
278
+ }
279
+ else if (terminal.outcome.kind === "timeout") {
280
+ finish({ status: "runner_failed", ...observed, cause: { code: "TIMED_OUT", message: `verification exceeded ${entry.timeoutMs}ms` } });
281
+ }
282
+ else if (terminal.outcome.kind === "signal") {
283
+ finish({
284
+ status: "runner_failed",
285
+ ...observed,
286
+ cause: {
287
+ code: "PROCESS_TERMINATED",
288
+ message: `executor terminated${terminal.outcome.signal ? ` by ${terminal.outcome.signal}` : " without an exit status"}`,
289
+ },
290
+ });
291
+ }
292
+ else {
293
+ finish({
294
+ status: "runner_failed",
295
+ ...observed,
296
+ cause: { code: "TREE_TERMINATION_FAILED", message: terminal.outcome.message },
297
+ });
298
+ }
299
+ })();
55
300
  });
56
- }).catch((error) => {
57
- throw new FlowError("CLOSE_QUALITY_GATE_FAILED", `verification '${verification.label}' could not start`, { cause: error });
58
301
  });
59
302
  }
303
+ export async function runVerificationPlan(input) {
304
+ const execute = async (supervisorLifecycle) => {
305
+ const results = [];
306
+ for (const entry of input.plan) {
307
+ if (input.signal?.aborted) {
308
+ const empty = { tail: "", truncated: false, omittedBytes: 0 };
309
+ results.push({
310
+ ...entry,
311
+ candidate: input.candidate,
312
+ executor: executorFor(entry),
313
+ execution: {
314
+ status: "runner_failed",
315
+ durationMs: 0,
316
+ stdout: empty,
317
+ stderr: empty,
318
+ cause: { code: "CANCELLED", message: "verification cancelled before runner spawn" },
319
+ },
320
+ });
321
+ break;
322
+ }
323
+ let supervisorRecorded = false;
324
+ const execution = await runVerificationPlanEntry(input.cwd, entry, input.signal, supervisorLifecycle
325
+ ? (identity) => {
326
+ supervisorLifecycle.spawned(identity);
327
+ supervisorRecorded = true;
328
+ }
329
+ : undefined);
330
+ const cleanupFailed = execution.status === "runner_failed"
331
+ && execution.cause.code === "TREE_TERMINATION_FAILED";
332
+ if (supervisorRecorded && !cleanupFailed)
333
+ supervisorLifecycle?.cleaned();
334
+ results.push({
335
+ ...entry,
336
+ candidate: input.candidate,
337
+ executor: executorFor(entry),
338
+ execution,
339
+ });
340
+ if (execution.status === "runner_failed"
341
+ && (execution.cause.code === "CANCELLED" || execution.cause.code === "TREE_TERMINATION_FAILED"))
342
+ break;
343
+ }
344
+ return results;
345
+ };
346
+ const key = input.coordinationKey ?? JSON.stringify({
347
+ cwd: input.cwd,
348
+ candidate: input.candidate,
349
+ plan: input.plan,
350
+ });
351
+ const memoryKey = JSON.stringify({ root: input.coordinationRoot ?? null, key });
352
+ const existing = verificationRuns.get(memoryKey);
353
+ if (existing)
354
+ return await existing;
355
+ const run = input.coordinationRoot
356
+ ? runCoordinatedVerification({
357
+ root: input.coordinationRoot,
358
+ key,
359
+ execute,
360
+ signal: input.signal,
361
+ candidate: input.candidate,
362
+ plan: input.plan,
363
+ })
364
+ : execute();
365
+ const tracked = run.finally(() => verificationRuns.delete(memoryKey));
366
+ verificationRuns.set(memoryKey, tracked);
367
+ return await tracked;
368
+ }
369
+ const verificationRuns = new Map();
370
+ async function wait(ms) {
371
+ await new Promise((resolve) => setTimeout(resolve, ms));
372
+ }
373
+ function cancelledResults(candidate, plan) {
374
+ const entry = plan[0];
375
+ if (!entry)
376
+ return [];
377
+ const empty = { tail: "", truncated: false, omittedBytes: 0 };
378
+ return [{
379
+ ...entry,
380
+ candidate,
381
+ executor: executorFor(entry),
382
+ execution: {
383
+ status: "runner_failed",
384
+ durationMs: 0,
385
+ stdout: empty,
386
+ stderr: empty,
387
+ cause: { code: "CANCELLED", message: "verification cancelled while waiting for an identical active execution" },
388
+ },
389
+ }];
390
+ }
391
+ async function runCoordinatedVerification(input) {
392
+ const coordination = new VerificationCoordination(input.root);
393
+ const fingerprint = coordination.fingerprint(input.key);
394
+ const staleAfterMs = input.plan.reduce((total, entry) => total + entry.timeoutMs + PROCESS_GROUP_KILL_GRACE_MS, COORDINATION_RECOVERY_MARGIN_MS);
395
+ for (;;) {
396
+ if (input.signal?.aborted)
397
+ return cancelledResults(input.candidate, input.plan);
398
+ const claim = coordination.claim(fingerprint, staleAfterMs);
399
+ if (claim.status === "receipt")
400
+ return claim.results;
401
+ if (claim.status === "wait") {
402
+ await wait(COORDINATION_POLL_MS);
403
+ continue;
404
+ }
405
+ try {
406
+ if (claim.priorSupervisor) {
407
+ await terminateOwnedProcess(claim.priorSupervisor);
408
+ coordination.clearSupervisor(fingerprint, claim.token);
409
+ }
410
+ const lifecycle = {
411
+ spawned: (identity) => coordination.recordSupervisor(fingerprint, claim.token, identity),
412
+ cleaned: () => coordination.clearSupervisor(fingerprint, claim.token),
413
+ };
414
+ const results = await input.execute(lifecycle);
415
+ const completion = coordination.complete(fingerprint, claim.token, results);
416
+ if (completion.status === "ownership-lost") {
417
+ throw new Error("verification coordination ownership changed before receipt publication");
418
+ }
419
+ return results;
420
+ }
421
+ catch (error) {
422
+ coordination.releaseForTakeover(fingerprint, claim.token);
423
+ throw error;
424
+ }
425
+ }
426
+ }