@astrosheep/keiyaku 2.9.12 → 2.9.13

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 (212) hide show
  1. package/README.md +22 -18
  2. package/build/.tsbuildinfo +1 -1
  3. package/build/agents/call-terms.js +1 -0
  4. package/build/agents/providers/claude-agent-sdk/adapter.js +2 -2
  5. package/build/cli/commands/akuma/akuma/handler.js +2 -1
  6. package/build/cli/commands/akuma/akuma/meta.js +4 -3
  7. package/build/cli/commands/akuma/catalog.js +2 -0
  8. package/build/cli/commands/akuma/list/handler.js +1 -1
  9. package/build/cli/commands/akuma/list/meta-list.js +12 -0
  10. package/build/cli/commands/akuma/list/meta-ls.js +2 -2
  11. package/build/cli/commands/akuma.js +5 -0
  12. package/build/cli/commands/contract/amend/meta.js +4 -3
  13. package/build/cli/commands/contract/arc/meta.js +2 -0
  14. package/build/cli/commands/contract/audit/handler.js +28 -2
  15. package/build/cli/commands/contract/audit/meta.js +4 -3
  16. package/build/cli/commands/contract/petition/handler.js +2 -1
  17. package/build/cli/commands/contract/petition/meta.js +2 -2
  18. package/build/cli/commands/contract/renew/handler.js +44 -3
  19. package/build/cli/commands/contract/renew/meta.js +5 -4
  20. package/build/cli/commands/metadata.js +12 -9
  21. package/build/cli/commands/projection/call/handler.js +1 -1
  22. package/build/cli/commands/projection/call/meta.js +1 -1
  23. package/build/cli/commands/projection/catalog.js +2 -2
  24. package/build/cli/commands/projection/{revive → fork}/handler.js +10 -5
  25. package/build/cli/commands/projection/fork/meta.js +12 -0
  26. package/build/cli/commands/projection/history/handler.js +46 -2
  27. package/build/cli/commands/projection/history/meta.js +3 -3
  28. package/build/cli/commands/projection/kill/meta.js +2 -2
  29. package/build/cli/commands/projection/status/handler.js +29 -13
  30. package/build/cli/commands/projection/status/meta.js +14 -3
  31. package/build/cli/commands/projection/tell/handler.js +8 -4
  32. package/build/cli/commands/projection/tell/meta.js +1 -1
  33. package/build/cli/commands/projection/wait/handler.js +45 -20
  34. package/build/cli/commands/projection/wait/meta.js +1 -1
  35. package/build/cli/commands/shared.js +1 -1
  36. package/build/cli/commands/system/catalog.js +2 -0
  37. package/build/cli/commands/task/add/handler.js +28 -12
  38. package/build/cli/commands/task/add/meta.js +18 -6
  39. package/build/cli/commands/task/catalog.js +2 -0
  40. package/build/cli/commands/task/compose/handler.js +2 -2
  41. package/build/cli/commands/task/compose/meta.js +2 -2
  42. package/build/cli/commands/task/doctor/handler.js +2 -3
  43. package/build/cli/commands/task/doctor/meta.js +2 -2
  44. package/build/cli/commands/task/done/meta.js +2 -2
  45. package/build/cli/commands/task/drop/handler.js +7 -2
  46. package/build/cli/commands/task/drop/meta.js +6 -3
  47. package/build/cli/commands/task/hold/meta.js +2 -2
  48. package/build/cli/commands/task/log/meta.js +2 -2
  49. package/build/cli/commands/task/ls/meta.js +2 -2
  50. package/build/cli/commands/task/note/handler.js +6 -0
  51. package/build/cli/commands/task/note/meta.js +11 -0
  52. package/build/cli/commands/task/resume/meta.js +2 -2
  53. package/build/cli/commands/task/shared.js +29 -15
  54. package/build/cli/commands/task/show/meta.js +2 -2
  55. package/build/cli/commands/task/start/meta.js +2 -2
  56. package/build/cli/commands/task/stop/meta.js +2 -2
  57. package/build/cli/commands/task/update/meta.js +3 -2
  58. package/build/cli/commands/verification/handler.js +43 -0
  59. package/build/cli/commands/verification/meta.js +13 -0
  60. package/build/cli/completion.js +16 -13
  61. package/build/cli/flags.js +43 -11
  62. package/build/cli/help.js +29 -30
  63. package/build/cli/index.js +6 -5
  64. package/build/cli/parse-flags.js +39 -0
  65. package/build/cli/parse-metadata.js +5 -2
  66. package/build/cli/parse-selectors.js +18 -0
  67. package/build/cli/parse.js +21 -8
  68. package/build/cli/render/arc.js +12 -3
  69. package/build/cli/render/audit.js +2 -2
  70. package/build/cli/render/call.js +4 -4
  71. package/build/cli/render/kanshi.js +122 -85
  72. package/build/cli/render/misc.js +2 -2
  73. package/build/cli/render/petition.js +16 -1
  74. package/build/cli/render/projection-history.js +17 -4
  75. package/build/cli/render/shared.js +5 -5
  76. package/build/cli/render/status-indicator.js +47 -0
  77. package/build/cli/render/status.js +106 -76
  78. package/build/cli/render/success-response.js +20 -5
  79. package/build/cli/render/task.js +6 -5
  80. package/build/cli/render/tell.js +10 -4
  81. package/build/cli/render/tool-command-normalization.js +137 -0
  82. package/build/cli/render/tool-presentation.js +2 -1
  83. package/build/cli/render/verification.js +23 -0
  84. package/build/cli/render/wait.js +101 -33
  85. package/build/cli/types.js +2 -1
  86. package/build/config/akuma-loader.js +21 -1
  87. package/build/config/settings/knobs.js +7 -1
  88. package/build/config/settings/schema.js +10 -5
  89. package/build/core/amend.js +102 -17
  90. package/build/core/arc.js +4 -4
  91. package/build/core/audit/candidate.js +10 -18
  92. package/build/core/audit/coordinates.js +12 -17
  93. package/build/core/audit/evidence.js +6 -4
  94. package/build/core/audit/facade.js +3 -3
  95. package/build/core/audit/report.js +1 -1
  96. package/build/core/bind-reconciliation.js +42 -23
  97. package/build/core/bind.js +182 -81
  98. package/build/core/call/call.js +6 -7
  99. package/build/core/call/context.js +43 -30
  100. package/build/core/call/execution.js +21 -6
  101. package/build/core/call/prompt.js +11 -42
  102. package/build/core/contract-carrier-runtime.js +209 -105
  103. package/build/core/contract-carrier.js +156 -73
  104. package/build/core/contract-view.js +42 -5
  105. package/build/core/contract.js +2 -1
  106. package/build/core/derived-replay.js +168 -35
  107. package/build/core/draft.js +9 -5
  108. package/build/core/forfeit.js +1 -1
  109. package/build/core/lifecycle-history.js +13 -0
  110. package/build/core/lifecycle-recovery.js +32 -27
  111. package/build/core/lifecycle-runner.js +14 -14
  112. package/build/core/log.js +1 -1
  113. package/build/core/outcome-base.js +2 -2
  114. package/build/core/projection/akuma-name.js +43 -0
  115. package/build/core/projection/generation/database.js +141 -79
  116. package/build/core/projection/generation/ledger.js +91 -0
  117. package/build/core/projection/generation/model.js +18 -13
  118. package/build/core/projection/generation/projection-generation-continuation.js +1 -1
  119. package/build/core/projection/generation/projection-generation-doorbell.js +88 -0
  120. package/build/core/projection/generation/projection-generation-execution.js +24 -0
  121. package/build/core/projection/generation/projection-generation-identity.js +4 -0
  122. package/build/core/projection/generation/projection-generation-launcher.js +72 -138
  123. package/build/core/projection/generation/projection-generation-process.js +16 -15
  124. package/build/core/projection/generation/projection-generation-runner.js +125 -79
  125. package/build/core/projection/generation/projection-generation-runtime.js +10 -61
  126. package/build/core/projection/generation/protocol.js +68 -0
  127. package/build/core/projection/generation/store.js +17 -7
  128. package/build/core/projection/generation/transitions.js +14 -14
  129. package/build/core/projection/index.js +5 -5
  130. package/build/core/projection/projection-core.js +6 -2
  131. package/build/core/projection/projection-execution-observer.js +1 -1
  132. package/build/core/projection/projection-history.js +7 -7
  133. package/build/core/projection/projection-kill.js +15 -6
  134. package/build/core/projection/projection-life-observer.js +8 -8
  135. package/build/core/projection/projection-mint.js +6 -8
  136. package/build/core/projection/projection-status-observation.js +77 -8
  137. package/build/core/projection/projection-status.js +151 -4
  138. package/build/core/projection/projection-wait.js +46 -27
  139. package/build/core/projection/projection-wake.js +27 -89
  140. package/build/core/projection/tell/launch-store.js +1 -1
  141. package/build/core/projection/tell/store.js +1 -1
  142. package/build/core/renew-build.js +204 -74
  143. package/build/core/renew-plan.js +34 -9
  144. package/build/core/renew-rewrite.js +56 -15
  145. package/build/core/renew.js +39 -35
  146. package/build/core/repository-ledger/accepted-fold-read.js +6 -3
  147. package/build/core/repository-ledger/codec.js +11 -5
  148. package/build/core/repository-ledger/current-state-store.js +13 -16
  149. package/build/core/repository-ledger/fold-repository.js +5 -6
  150. package/build/core/repository-ledger/identity.js +60 -0
  151. package/build/core/repository-ledger/inventory.js +3 -3
  152. package/build/core/repository-ledger/read-model.js +54 -6
  153. package/build/core/repository-ledger/write-transaction.js +9 -4
  154. package/build/core/run-control/connection-bound-close.js +41 -0
  155. package/build/core/run-control/detached-lease-bootstrap.js +208 -0
  156. package/build/core/run-control/detached-process.js +14 -0
  157. package/build/core/run-control/index.js +6 -0
  158. package/build/core/{process-group.js → run-control/process-tree.js} +1 -1
  159. package/build/core/run-control/runner-lease.js +172 -0
  160. package/build/core/{sqlite-process-lifetime-lock.js → run-control/sqlite-process-lifetime-lock.js} +4 -24
  161. package/build/core/seal.js +44 -32
  162. package/build/core/settlement/claim-delivery.js +22 -9
  163. package/build/core/settlement/claim.js +8 -6
  164. package/build/core/settlement/petition-claim-gates.js +9 -8
  165. package/build/core/settlement/petition-head-guard.js +5 -6
  166. package/build/core/settlement/petition-preview.js +21 -5
  167. package/build/core/settlement/petition.js +51 -17
  168. package/build/core/settlement/settlement.js +20 -15
  169. package/build/core/settlement/verdict.js +2 -2
  170. package/build/core/settlement/verification.js +492 -364
  171. package/build/core/status/board.js +184 -75
  172. package/build/core/status/drift.js +8 -10
  173. package/build/core/task/board.js +1 -0
  174. package/build/core/task/commands.js +25 -0
  175. package/build/core/task/compose.js +3 -2
  176. package/build/core/task/document.js +102 -39
  177. package/build/core/task/index.js +3 -3
  178. package/build/core/task/settlement-git.js +1 -4
  179. package/build/core/task/settlement-policy.js +27 -14
  180. package/build/core/task/source-board.js +1 -0
  181. package/build/core/task/task-bind-preparation.js +59 -9
  182. package/build/core/task/task-contract.js +2 -2
  183. package/build/core/task/task-store-repository.js +1 -0
  184. package/build/core/task/task.js +2 -2
  185. package/build/core/transcripts.js +4 -4
  186. package/build/core/verification-declaration.js +95 -0
  187. package/build/core/worktree-bootstrap.js +1 -1
  188. package/build/core/worktree-path.js +7 -11
  189. package/build/flow-error.js +9 -1
  190. package/build/generated/version.js +2 -2
  191. package/build/git/branches.js +41 -70
  192. package/build/git/commits.js +92 -21
  193. package/build/git/core.js +9 -161
  194. package/build/git/diff/preview.js +4 -4
  195. package/build/git/diff/read.js +4 -4
  196. package/build/git/diff/structured.js +5 -5
  197. package/build/git/process.js +229 -0
  198. package/build/git/refs.js +30 -5
  199. package/build/git/staging.js +39 -115
  200. package/build/git/streaming-batch.js +9 -16
  201. package/build/git/worktree.js +104 -91
  202. package/build/index.js +7 -0
  203. package/package.json +1 -1
  204. package/skills/keiyaku-akuma/SKILL.md +21 -20
  205. package/skills/keiyaku-task/SKILL.md +38 -4
  206. package/skills/keiyaku-workflow/SKILL.md +51 -11
  207. package/build/cli/commands/projection/revive/meta.js +0 -12
  208. package/build/core/projection/projection-life-protocol.js +0 -115
  209. package/build/core/projection/projection-runner-lock.js +0 -211
  210. package/build/core/projection/tell/database.js +0 -127
  211. package/build/core/settlement/verification-coordination.js +0 -305
  212. package/build/core/settlement/verification-supervisor.js +0 -275
@@ -20,6 +20,27 @@ function requireString(value, field) {
20
20
  function optionalString(value, field) {
21
21
  return value === undefined ? undefined : requireString(value, field);
22
22
  }
23
+ function optionalCarrier(value) {
24
+ if (value === undefined)
25
+ return undefined;
26
+ if (!isObject(value) || value.path !== ".keiyaku/KEIYAKU.md" || !isObject(value.identity)) {
27
+ throw new Error("generation launch carrier must be a verified carrier coordinate");
28
+ }
29
+ const { identity } = value;
30
+ if (typeof identity.contractId !== "string" || identity.contractId.trim() === ""
31
+ || typeof identity.contractHead !== "string" || identity.contractHead.trim() === ""
32
+ || typeof identity.bodyDigest !== "string" || identity.bodyDigest.trim() === "") {
33
+ throw new Error("generation launch carrier identity must contain non-blank contractId, contractHead, and bodyDigest");
34
+ }
35
+ return {
36
+ path: ".keiyaku/KEIYAKU.md",
37
+ identity: {
38
+ contractId: identity.contractId,
39
+ contractHead: identity.contractHead,
40
+ bodyDigest: identity.bodyDigest,
41
+ },
42
+ };
43
+ }
23
44
  function requirePositiveSafeInteger(value, field) {
24
45
  if (typeof value !== "number" || !Number.isSafeInteger(value) || value <= 0) {
25
46
  throw new Error(`generation launch ${field} must be a positive safe integer`);
@@ -54,6 +75,7 @@ function parseLaunchInputs(launch) {
54
75
  const context = optionalString(raw.context, "context");
55
76
  const resumePath = optionalString(raw.resumePath, "resumePath");
56
77
  const sourceArtifactId = optionalString(raw.sourceArtifactId, "sourceArtifactId");
78
+ const carrier = optionalCarrier(raw.carrier);
57
79
  return {
58
80
  prompt: requireString(raw.prompt, "prompt"),
59
81
  cwd: requireString(raw.cwd, "cwd"),
@@ -69,6 +91,7 @@ function parseLaunchInputs(launch) {
69
91
  ...(raw.session ? { session: raw.session } : {}),
70
92
  ...(resumePath ? { resumePath } : {}),
71
93
  ...(sourceArtifactId ? { sourceArtifactId } : {}),
94
+ ...(carrier ? { carrier } : {}),
72
95
  };
73
96
  }
74
97
  function persistenceError(error) {
@@ -161,6 +184,7 @@ export function prepareProjectionGenerationExecution(projectionDirectory, launch
161
184
  idleTimeoutMs: inputs.idleTimeoutMs,
162
185
  executionTimeoutMs: inputs.executionTimeoutMs,
163
186
  initialTurn: maximumValidEnvelopeTurn(projectionDirectory),
187
+ carrier: inputs.carrier,
164
188
  }), providerLoaderPorts);
165
189
  const observerController = createProjectionExecutionObserver({
166
190
  projectionDirectory,
@@ -3,6 +3,7 @@ import * as path from "node:path";
3
3
  import { ProjectionStateError } from "../../atomic-publish.js";
4
4
  import { createUlid } from "../../ids.js";
5
5
  const EXECUTION_ID_PATTERN = /^[0-9A-HJKMNP-TV-Z]{26}$/;
6
+ export const PROJECTION_RUNNER_LEASE_FILE = "leash";
6
7
  export function createProjectionExecutionId(nowMs = Date.now()) {
7
8
  return createUlid(nowMs, [...randomBytes(10)]);
8
9
  }
@@ -15,3 +16,6 @@ export function executionStdioLogPath(projectionDirectory, executionId) {
15
16
  assertProjectionExecutionId(executionId);
16
17
  return path.join(projectionDirectory, `${executionId}.stdio.log`);
17
18
  }
19
+ export function projectionRunnerLeasePath(projectionDirectory) {
20
+ return path.join(projectionDirectory, PROJECTION_RUNNER_LEASE_FILE);
21
+ }
@@ -1,9 +1,11 @@
1
1
  import { setTimeout as delay } from "node:timers/promises";
2
+ import { PROJECTION_ADOPTION_TIMEOUT_DEFAULT_MS } from "../../../config/settings/knobs.js";
2
3
  import { openProjectionGenerationStore } from "./store.js";
3
- import { spawnProjectionGenerationRunner, } from "./projection-generation-process.js";
4
- import { acquireProjectionRunnerLock, observeProjectionRunnerLock, } from "../projection-runner-lock.js";
5
- export const PROJECTION_ADOPTION_TIMEOUT_MS = 4_000;
6
- export const PROJECTION_ADOPTION_POLL_MS = 20;
4
+ import { prepareProjectionGenerationRunner, } from "./projection-generation-process.js";
5
+ import { acquireRunnerLease, observeRunnerLease, } from "../../run-control/runner-lease.js";
6
+ import { openDetachedLeaseBootstrap } from "../../run-control/detached-lease-bootstrap.js";
7
+ import { projectionRunnerLeasePath } from "./projection-generation-identity.js";
8
+ const PROJECTION_ADOPTION_POLL_MS = 20;
7
9
  function errorDetail(error) {
8
10
  return error instanceof Error ? error.message : String(error);
9
11
  }
@@ -21,86 +23,39 @@ function recordDeadStartup(projectionDirectory, executionId, detail, stage, term
21
23
  store.close();
22
24
  }
23
25
  }
24
- function observeBootstrapMessages(child, executionId) {
25
- const seen = new Set();
26
- const waiters = new Map();
27
- let failure;
28
- const onMessage = (value) => {
29
- const message = value;
30
- if (message.executionId !== executionId)
31
- return;
32
- if (message.kind !== "leash-inherited" && message.kind !== "leash-acquired")
33
- return;
34
- seen.add(message.kind);
35
- const waiter = waiters.get(message.kind);
36
- if (waiter) {
37
- waiter.resolve();
38
- waiters.delete(message.kind);
39
- }
40
- };
41
- const onError = (error) => {
42
- failure = error;
43
- for (const waiter of waiters.values())
44
- waiter.reject(error);
45
- waiters.clear();
46
- };
47
- const onExit = () => onError(new Error("runner exited during bootstrap"));
48
- child.on("message", onMessage);
49
- child.on("error", onError);
50
- child.on("exit", onExit);
51
- return {
52
- waitFor(kind) {
53
- if (seen.has(kind))
54
- return Promise.resolve();
55
- if (failure)
56
- return Promise.reject(failure);
57
- return new Promise((resolve, reject) => {
58
- const timer = setTimeout(() => {
59
- waiters.delete(kind);
60
- reject(failure ?? new Error(`runner did not report ${kind} before startup timeout`));
61
- }, PROJECTION_ADOPTION_TIMEOUT_MS);
62
- waiters.set(kind, {
63
- resolve: () => {
64
- clearTimeout(timer);
65
- waiters.delete(kind);
66
- resolve();
67
- },
68
- reject: (error) => {
69
- clearTimeout(timer);
70
- waiters.delete(kind);
71
- reject(error);
72
- },
73
- });
74
- });
75
- },
76
- close() {
77
- child.off("message", onMessage);
78
- child.off("error", onError);
79
- child.off("exit", onExit);
80
- },
81
- };
82
- }
83
- function sendBootstrapCommand(child, command) {
84
- return new Promise((resolve, reject) => {
85
- if (!child.send || !child.connected) {
86
- reject(new Error("runner bootstrap channel is unavailable"));
87
- return;
88
- }
89
- child.send(command, (error) => error ? reject(error) : resolve());
90
- });
26
+ const ADOPTION_TIMEOUT_DETAIL = "runner did not adopt before projection adoption timeout";
27
+ async function failAdoptionDeadline(projectionDirectory, executionId, child, now, terminateChild, detail = ADOPTION_TIMEOUT_DETAIL) {
28
+ try {
29
+ await terminateChild(child);
30
+ }
31
+ catch (error) {
32
+ return { status: "failed", cause: "adoption", detail: `${detail}; runner termination could not be verified: ${errorDetail(error)}` };
33
+ }
34
+ const lease = observeRunnerLease(projectionRunnerLeasePath(projectionDirectory));
35
+ if (lease.state !== "released")
36
+ return {
37
+ status: "failed",
38
+ cause: "adoption",
39
+ detail: `${detail}; runner lock ${lease.state === "held" ? "remained held" : `could not be verified: ${lease.detail}`}`,
40
+ };
41
+ recordDeadStartup(projectionDirectory, executionId, detail, "adoption", new Date(now()).toISOString());
42
+ return { status: "failed", cause: "adoption", detail };
91
43
  }
92
44
  /** Spawn one committed launch and return only after its exact adoption row. */
93
45
  export async function startProjectionGeneration(projectionDirectory, executionId, dependencies = {}) {
94
46
  const now = dependencies.now ?? Date.now;
95
47
  const sleep = dependencies.sleep ?? ((milliseconds) => delay(milliseconds));
96
- const launcherAcquiredOwnership = dependencies.launchOwnership === undefined;
48
+ const adoptionTimeoutMs = dependencies.adoptionTimeoutMs ?? PROJECTION_ADOPTION_TIMEOUT_DEFAULT_MS;
49
+ // Bootstrap IPC and durable adoption are one launch operation, not retries
50
+ // with independent startup allowances.
51
+ const deadline = now() + adoptionTimeoutMs;
97
52
  let launchOwnership;
98
53
  try {
99
54
  launchOwnership = dependencies.launchOwnership
100
- ?? acquireProjectionRunnerLock(projectionDirectory);
55
+ ?? acquireRunnerLease(projectionRunnerLeasePath(projectionDirectory));
101
56
  }
102
57
  catch (error) {
103
- if (observeProjectionRunnerLock(projectionDirectory).state === "held") {
58
+ if (observeRunnerLease(projectionRunnerLeasePath(projectionDirectory)).state === "held") {
104
59
  return { status: "pending" };
105
60
  }
106
61
  return {
@@ -109,11 +64,9 @@ export async function startProjectionGeneration(projectionDirectory, executionId
109
64
  detail: `runner construction failed: ${errorDetail(error)}`,
110
65
  };
111
66
  }
112
- let launchCommitted = false;
113
- let childPromoted = false;
114
- let child;
67
+ let prepared;
115
68
  try {
116
- child = spawnProjectionGenerationRunner(projectionDirectory, executionId, dependencies);
69
+ prepared = prepareProjectionGenerationRunner(projectionDirectory, executionId, dependencies);
117
70
  }
118
71
  catch (error) {
119
72
  const detail = `runner spawn failed: ${errorDetail(error)}`;
@@ -121,60 +74,48 @@ export async function startProjectionGeneration(projectionDirectory, executionId
121
74
  launchOwnership.close();
122
75
  return { status: "failed", cause: "spawn", detail };
123
76
  }
124
- const bootstrap = observeBootstrapMessages(child, executionId);
77
+ const opened = await openDetachedLeaseBootstrap({
78
+ coordinate: executionId,
79
+ leasePath: projectionRunnerLeasePath(projectionDirectory),
80
+ parentLease: launchOwnership,
81
+ launch: prepared.launch,
82
+ deadline,
83
+ now,
84
+ spawn: dependencies.spawn,
85
+ terminate: dependencies.terminateChild,
86
+ });
87
+ prepared.close();
88
+ if (opened.status === "failed") {
89
+ const detail = opened.stage === "spawn" ? `runner spawn failed: ${opened.detail}` : `runner construction failed: ${opened.detail}`;
90
+ recordDeadStartup(projectionDirectory, executionId, detail, opened.stage === "spawn" ? "spawn" : "adoption", new Date(now()).toISOString());
91
+ return { status: "failed", cause: opened.stage === "spawn" ? "spawn" : "adoption", detail };
92
+ }
93
+ let committed = true;
94
+ let commitFailure;
125
95
  try {
126
- await bootstrap.waitFor("leash-inherited");
127
- if (dependencies.commitLaunch && !dependencies.commitLaunch()) {
128
- await sendBootstrapCommand(child, { kind: "launch-rejected", executionId });
129
- if (launcherAcquiredOwnership)
130
- launchOwnership.close();
131
- return { status: "failed", cause: "adoption", detail: "generation launch transaction was rejected" };
132
- }
133
- launchCommitted = true;
134
- launchOwnership.releaseReservationForTransfer();
135
- await sendBootstrapCommand(child, { kind: "promote-leash", executionId });
136
- await bootstrap.waitFor("leash-acquired");
137
- childPromoted = true;
138
- await sendBootstrapCommand(child, { kind: "launch-committed", executionId });
139
- try {
140
- launchOwnership.close();
141
- }
142
- catch {
143
- // The promoted child owns lifecycle truth; parent guard cleanup is diagnostic only.
144
- }
96
+ committed = dependencies.commitLaunch?.() ?? true;
145
97
  }
146
98
  catch (error) {
147
- void sendBootstrapCommand(child, { kind: "launch-rejected", executionId }).catch(() => { });
148
- if (childPromoted) {
149
- try {
150
- launchOwnership.close();
151
- }
152
- catch { }
153
- return { status: "pending", child };
154
- }
155
- if (!launchCommitted) {
156
- launchOwnership.close();
157
- return { status: "failed", cause: "adoption", detail: `runner construction failed: ${errorDetail(error)}` };
158
- }
159
- try {
160
- launchOwnership.reclaimReservation();
161
- }
162
- catch {
163
- launchOwnership.close();
164
- return { status: "pending", child };
165
- }
166
- try {
167
- recordDeadStartup(projectionDirectory, executionId, `runner construction failed: ${errorDetail(error)}`, "adoption", new Date(now()).toISOString());
168
- }
169
- finally {
170
- launchOwnership.close();
171
- }
172
- return { status: "failed", cause: "adoption", detail: `runner construction failed: ${errorDetail(error)}` };
99
+ committed = false;
100
+ commitFailure = error;
173
101
  }
174
- finally {
175
- bootstrap.close();
102
+ if (!committed) {
103
+ const rejected = await opened.bootstrap.reject();
104
+ return {
105
+ status: "failed",
106
+ cause: "adoption",
107
+ detail: rejected.status === "failed"
108
+ ? rejected.detail
109
+ : commitFailure ? `generation launch transaction failed: ${errorDetail(commitFailure)}` : "generation launch transaction was rejected",
110
+ };
176
111
  }
177
- const deadline = now() + PROJECTION_ADOPTION_TIMEOUT_MS;
112
+ const authorized = await opened.bootstrap.authorize();
113
+ if (authorized.status === "failed") {
114
+ const detail = now() >= deadline ? ADOPTION_TIMEOUT_DETAIL : `runner construction failed: ${authorized.detail}`;
115
+ recordDeadStartup(projectionDirectory, executionId, detail, "adoption", new Date(now()).toISOString());
116
+ return { status: "failed", cause: "adoption", detail };
117
+ }
118
+ const child = authorized.child;
178
119
  for (;;) {
179
120
  const observedAt = now();
180
121
  const store = openProjectionGenerationStore(projectionDirectory);
@@ -202,19 +143,12 @@ export async function startProjectionGeneration(projectionDirectory, executionId
202
143
  finally {
203
144
  store.close();
204
145
  }
146
+ if (observeRunnerLease(projectionRunnerLeasePath(projectionDirectory)).state === "held") {
147
+ return { status: "adopted", child };
148
+ }
205
149
  if (observedAt >= deadline)
206
150
  break;
207
151
  await sleep(Math.max(0, Math.min(PROJECTION_ADOPTION_POLL_MS, deadline - observedAt)));
208
152
  }
209
- const store = openProjectionGenerationStore(projectionDirectory);
210
- try {
211
- const current = store.readCurrentGeneration();
212
- if (current?.launch.executionId === executionId && current.adoption) {
213
- return { status: "adopted", child };
214
- }
215
- }
216
- finally {
217
- store.close();
218
- }
219
- return { status: "pending", child };
153
+ return failAdoptionDeadline(projectionDirectory, executionId, child, now, dependencies.terminateChild ?? (await import("../../run-control/process-tree.js")).terminateProcessGroup);
220
154
  }
@@ -1,7 +1,7 @@
1
1
  import * as fs from "node:fs";
2
2
  import * as path from "node:path";
3
- import { spawn as nodeSpawn } from "node:child_process";
4
3
  import { fileURLToPath } from "node:url";
4
+ import { RUN_CONTROL_BOOTSTRAP_ENV } from "../../run-control/index.js";
5
5
  import { executionStdioLogPath } from "./projection-generation-identity.js";
6
6
  const MODULE_FILE = fileURLToPath(import.meta.url);
7
7
  // Lives at src/core/projection/generation/ — four levels up is the package root.
@@ -17,9 +17,7 @@ export function projectionGenerationRunnerArgv(projectionDirectory, executionId)
17
17
  executionId,
18
18
  ];
19
19
  }
20
- export const PROJECTION_RUNNER_BOOTSTRAP_ENV = "KEIYAKU_PROJECTION_RUNNER_BOOTSTRAP";
21
- export function spawnProjectionGenerationRunner(projectionDirectory, executionId, dependencies = {}) {
22
- const spawn = dependencies.spawn ?? nodeSpawn;
20
+ export function prepareProjectionGenerationRunner(projectionDirectory, executionId, dependencies = {}) {
23
21
  const openSync = dependencies.openSync ?? fs.openSync;
24
22
  const closeSync = dependencies.closeSync ?? fs.closeSync;
25
23
  const absoluteDirectory = path.resolve(projectionDirectory);
@@ -29,18 +27,21 @@ export function spawnProjectionGenerationRunner(projectionDirectory, executionId
29
27
  const logPath = executionStdioLogPath(absoluteDirectory, executionId);
30
28
  const logFd = openSync(logPath, "a");
31
29
  try {
32
- const child = spawn(command, args, {
33
- detached: true,
34
- stdio: ["ignore", logFd, logFd, "ipc"],
35
- env: { ...process.env, [PROJECTION_RUNNER_BOOTSTRAP_ENV]: "1" },
36
- });
37
- if (child.pid === undefined) {
38
- throw new Error("projection runner did not expose a process id");
39
- }
40
- child.unref();
41
- return child;
30
+ return {
31
+ launch: {
32
+ command,
33
+ args,
34
+ unref: true,
35
+ options: {
36
+ stdio: ["ignore", logFd, logFd, "ipc"],
37
+ env: { ...process.env, [RUN_CONTROL_BOOTSTRAP_ENV]: "1" },
38
+ },
39
+ },
40
+ close: () => closeSync(logFd),
41
+ };
42
42
  }
43
- finally {
43
+ catch (error) {
44
44
  closeSync(logFd);
45
+ throw error;
45
46
  }
46
47
  }