@astrosheep/keiyaku 2.9.6 → 2.9.8
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.
- package/build/.tsbuildinfo +1 -1
- package/build/agents/harness/event-persistence.js +7 -5
- package/build/agents/harness/events.js +3 -2
- package/build/agents/harness/outcome.js +10 -0
- package/build/agents/providers/codex-app-server/adapter.js +6 -1
- package/build/agents/providers/codex-app-server/session.js +8 -7
- package/build/agents/selector.js +12 -1
- package/build/cli/commands/akuma/view/handler.js +3 -11
- package/build/cli/commands/contract/amend/handler.js +1 -1
- package/build/cli/commands/contract/amend/meta.js +4 -4
- package/build/cli/commands/contract/bind/handler.js +10 -4
- package/build/cli/commands/contract/bind/meta.js +6 -6
- package/build/cli/commands/contract/petition/handler.js +16 -3
- package/build/cli/commands/contract/petition/meta.js +4 -4
- package/build/cli/commands/metadata.js +3 -2
- package/build/cli/commands/projection/status/handler.js +5 -4
- package/build/cli/commands/projection/status/meta.js +2 -2
- package/build/cli/commands/projection/tell/handler.js +10 -2
- package/build/cli/commands/projection/tell/meta.js +3 -3
- package/build/cli/commands/task/add/meta.js +9 -1
- package/build/cli/commands/task/catalog.js +2 -0
- package/build/cli/commands/task/log/handler.js +12 -0
- package/build/cli/commands/task/log/meta.js +8 -0
- package/build/cli/commands/task/shared.js +2 -1
- package/build/cli/completion.js +8 -0
- package/build/cli/flags.js +8 -0
- package/build/cli/index.js +10 -6
- package/build/cli/parse-flags.js +6 -0
- package/build/cli/parse-metadata.js +1 -1
- package/build/cli/render/kanshi.js +2 -2
- package/build/cli/render/line-width.js +33 -0
- package/build/cli/render/path-prefix-compaction.js +131 -0
- package/build/cli/render/petition.js +4 -0
- package/build/cli/render/projection-activity.js +103 -21
- package/build/cli/render/shared.js +38 -15
- package/build/cli/render/status.js +8 -5
- package/build/cli/render/success-response.js +2 -0
- package/build/cli/render/tool-presentation.js +3 -3
- package/build/cli/render/wait.js +68 -48
- package/build/cli/subagent-guard.js +3 -0
- package/build/cli/types.js +1 -1
- package/build/config/settings/disease.js +4 -4
- package/build/config/settings/loader.js +44 -21
- package/build/core/addressing.js +40 -9
- package/build/core/amend.js +21 -5
- package/build/core/bind.js +111 -6
- package/build/core/call/context.js +19 -3
- package/build/core/call/execution.js +43 -20
- package/build/core/draft.js +1 -1
- package/build/core/ledger-batch.js +194 -0
- package/build/core/projection/generation/database.js +43 -2
- package/build/core/projection/generation/model.js +24 -3
- package/build/core/projection/generation/projection-generation-continuation.js +75 -10
- package/build/core/projection/generation/projection-generation-execution.js +46 -37
- package/build/core/projection/generation/projection-generation-launcher.js +148 -20
- package/build/core/projection/generation/projection-generation-process.js +3 -1
- package/build/core/projection/generation/projection-generation-runner.js +145 -66
- package/build/core/projection/generation/projection-generation-runtime.js +93 -11
- package/build/core/projection/generation/store.js +26 -1
- package/build/core/projection/generation/transitions.js +193 -14
- package/build/core/projection/index.js +5 -5
- package/build/core/projection/projection-core.js +1 -1
- package/build/core/projection/projection-kill.js +53 -10
- package/build/core/projection/projection-life-protocol.js +10 -0
- package/build/core/projection/projection-runner-lock.js +177 -37
- package/build/core/projection/projection-status.js +143 -55
- package/build/core/projection/projection-wait.js +53 -15
- package/build/core/projection/projection-wake.js +177 -46
- package/build/core/projection/tell/database.js +18 -0
- package/build/core/projection/tell/model.js +1 -0
- package/build/core/projection/tell/store.js +102 -55
- package/build/core/registry.js +82 -69
- package/build/core/scope.js +9 -9
- package/build/core/status/board.js +42 -4
- package/build/core/status/drift.js +21 -5
- package/build/core/status/ledger-batch.js +1 -158
- package/build/core/task/index.js +2 -2
- package/build/core/task/task-contract.js +18 -0
- package/build/core/task/task-git-runtime.js +8 -10
- package/build/core/task/task-git-store.js +55 -3
- package/build/core/worktree-path.js +39 -25
- package/build/flow-error.js +1 -1
- package/build/generated/version.js +2 -2
- package/build/git/refs.js +47 -1
- package/package.json +1 -1
- package/skills/keiyaku-akuma/SKILL.md +28 -0
- package/skills/keiyaku-workflow/SKILL.md +142 -18
|
@@ -1,22 +1,87 @@
|
|
|
1
1
|
import { ProjectionStateError } from "../../atomic-publish.js";
|
|
2
2
|
import { applySnapshotEffort, parseResolvedAkumaLaunchSnapshot, } from "../../../agents/launch-snapshot/model.js";
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
3
|
+
import { listTellMetadataFromDatabase, readPendingTellWindowFromDatabase, } from "../tell/store.js";
|
|
4
|
+
import { PENDING_PROJECTION_TELL_STATES } from "../tell/model.js";
|
|
5
|
+
import { readValidatedHistory, withGenerationReadSnapshot } from "./database.js";
|
|
6
|
+
function retainedFenceFor(current) {
|
|
7
|
+
return current?.verdict?.facts.state === "launch-failed"
|
|
8
|
+
? current.launch.facts.tellFence ?? []
|
|
9
|
+
: [];
|
|
10
|
+
}
|
|
11
|
+
function interruptedForReplay(current) {
|
|
12
|
+
return current?.verdict?.facts.state === "interrupted";
|
|
13
|
+
}
|
|
14
|
+
function snapshotTells(database, layers, admit) {
|
|
15
|
+
const pending = listTellMetadataFromDatabase(database, layers)
|
|
16
|
+
.filter((item) => admit?.({
|
|
17
|
+
layer: item.state,
|
|
18
|
+
tellId: item.tellId,
|
|
19
|
+
...(item.executionId ? { executionId: item.executionId } : {}),
|
|
20
|
+
}) ?? true);
|
|
9
21
|
const effort = pending.reduce((selected, item) => {
|
|
10
|
-
|
|
11
|
-
? readTellSubmitted(projectionDirectory, item.tellId)
|
|
12
|
-
: readTellOriginal(projectionDirectory, item.layer, item.tellId);
|
|
13
|
-
return tell.effort ?? selected;
|
|
22
|
+
return item.effort ?? selected;
|
|
14
23
|
}, undefined);
|
|
15
24
|
return {
|
|
16
25
|
tellIds: pending.map((item) => item.tellId),
|
|
17
26
|
...(effort ? { effort } : {}),
|
|
18
27
|
};
|
|
19
28
|
}
|
|
29
|
+
/** Ordinary completed-generation handoff retains only tells not yet delivered. */
|
|
30
|
+
export function snapshotPendingTells(projectionDirectory) {
|
|
31
|
+
return withGenerationReadSnapshot(projectionDirectory, (database) => snapshotTells(database, PENDING_PROJECTION_TELL_STATES));
|
|
32
|
+
}
|
|
33
|
+
/** Interrupted generations replay pending mail and their own delivered tells. */
|
|
34
|
+
export function snapshotInterruptTells(projectionDirectory, executionId) {
|
|
35
|
+
return withGenerationReadSnapshot(projectionDirectory, (database) => snapshotTells(database, [...PENDING_PROJECTION_TELL_STATES, "delivered"], (item) => item.layer !== "delivered" || item.executionId === executionId));
|
|
36
|
+
}
|
|
37
|
+
/** Retry ordinary pending mail plus exactly the durable fence of a failed launch. */
|
|
38
|
+
export function snapshotRetainedSuccessorTells(projectionDirectory, retainedFence) {
|
|
39
|
+
const retained = new Set(retainedFence);
|
|
40
|
+
return withGenerationReadSnapshot(projectionDirectory, (database) => snapshotTells(database, [...PENDING_PROJECTION_TELL_STATES, "delivered"], ({ layer, tellId }) => layer !== "delivered" || retained.has(tellId)));
|
|
41
|
+
}
|
|
42
|
+
/** Build the durable successor fence only after continuation is admitted. */
|
|
43
|
+
export function snapshotProjectionContinuationTells(projectionDirectory, current) {
|
|
44
|
+
const snapshot = readProjectionGenerationContinuationSnapshot(projectionDirectory, 0);
|
|
45
|
+
if (snapshot.current?.launch.executionId !== current?.launch.executionId) {
|
|
46
|
+
throw new ProjectionStateError("projection generation changed while selecting continuation tells");
|
|
47
|
+
}
|
|
48
|
+
return snapshot.successor;
|
|
49
|
+
}
|
|
50
|
+
function readContinuationFromDatabase(database, current, limit = 10) {
|
|
51
|
+
const tells = readPendingTellWindowFromDatabase(database, limit);
|
|
52
|
+
const retainedFence = retainedFenceFor(current);
|
|
53
|
+
const successor = interruptedForReplay(current)
|
|
54
|
+
? snapshotTells(database, [...PENDING_PROJECTION_TELL_STATES, "delivered"], (item) => item.layer !== "delivered" || item.executionId === current.launch.executionId)
|
|
55
|
+
: retainedFence.length > 0
|
|
56
|
+
? snapshotTells(database, [...PENDING_PROJECTION_TELL_STATES, "delivered"], ({ layer, tellId }) => layer !== "delivered" || retainedFence.includes(tellId))
|
|
57
|
+
: snapshotTells(database, PENDING_PROJECTION_TELL_STATES);
|
|
58
|
+
return {
|
|
59
|
+
tells,
|
|
60
|
+
successor,
|
|
61
|
+
requiresWake: successor.tellIds.length > 0,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
/** One heart snapshot owns generation selection and all continuation facts. */
|
|
65
|
+
export function readProjectionGenerationContinuationSnapshot(projectionDirectory, limit = 10) {
|
|
66
|
+
return withGenerationReadSnapshot(projectionDirectory, (database) => {
|
|
67
|
+
const { records, current } = readValidatedHistory(database);
|
|
68
|
+
const continuation = readContinuationFromDatabase(database, current, limit);
|
|
69
|
+
return {
|
|
70
|
+
records,
|
|
71
|
+
current,
|
|
72
|
+
generationSequence: records.at(-1)?.seq ?? 0,
|
|
73
|
+
...continuation,
|
|
74
|
+
};
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
/** One continuation eligibility fact shared by tell, wait, and wake. */
|
|
78
|
+
export function readProjectionContinuation(projectionDirectory, current, limit = 10) {
|
|
79
|
+
const snapshot = readProjectionGenerationContinuationSnapshot(projectionDirectory, limit);
|
|
80
|
+
if (snapshot.current?.launch.executionId !== current?.launch.executionId) {
|
|
81
|
+
throw new ProjectionStateError("projection generation changed while reading continuation");
|
|
82
|
+
}
|
|
83
|
+
return snapshot;
|
|
84
|
+
}
|
|
20
85
|
export function applyContinuationEffort(inputs, effort) {
|
|
21
86
|
if (!effort)
|
|
22
87
|
return inputs;
|
|
@@ -2,7 +2,7 @@ import { initializeConfig } from "../../../config/env.js";
|
|
|
2
2
|
import { buildCallTerms } from "../../../agents/call-terms.js";
|
|
3
3
|
import { parseResolvedAkumaLaunchSnapshot } from "../../../agents/launch-snapshot/model.js";
|
|
4
4
|
import { revive, call, } from "../../../agents/harness/index.js";
|
|
5
|
-
import { claimFencedTells,
|
|
5
|
+
import { claimFencedTells, readTellOriginal, readProjectionEvents, } from "../projection-core.js";
|
|
6
6
|
import { createProjectionExecutionObserver, renderProjectionTellFrame, } from "../projection-execution-observer.js";
|
|
7
7
|
import { readProjectionIdentity } from "../projection-identity.js";
|
|
8
8
|
import { selectValidStoredAgentEvents } from "../../stored-agent-event.js";
|
|
@@ -136,11 +136,10 @@ async function completionFacts(projectionDirectory, executionId, inputs, outcome
|
|
|
136
136
|
* Construct the public Projection once and expose a provider-neutral execution
|
|
137
137
|
* handle. Provider adapters and harness public types are unchanged.
|
|
138
138
|
*/
|
|
139
|
-
export function
|
|
139
|
+
export function prepareProjectionGenerationExecution(projectionDirectory, launch, providerLoaderPorts = {}) {
|
|
140
140
|
const inputs = parseLaunchInputs(launch);
|
|
141
141
|
initializeConfig(process.env, inputs.cwd);
|
|
142
142
|
const executionId = launch.executionId;
|
|
143
|
-
demoteSubmittedTellsForReplay(projectionDirectory, executionId);
|
|
144
143
|
const initialTellIds = claimFencedTells(projectionDirectory, launch.facts.tellFence ?? [], executionId);
|
|
145
144
|
const tellPrompt = initialTellIds.map((tellId) => {
|
|
146
145
|
const tell = readTellOriginal(projectionDirectory, "inflight", tellId);
|
|
@@ -161,41 +160,51 @@ export function executeProjectionGeneration(projectionDirectory, launch, provide
|
|
|
161
160
|
executionId,
|
|
162
161
|
initialTellIds,
|
|
163
162
|
});
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
163
|
+
return {
|
|
164
|
+
start() {
|
|
165
|
+
let projection;
|
|
166
|
+
try {
|
|
167
|
+
projection = inputs.session
|
|
168
|
+
? revive({
|
|
169
|
+
...terms,
|
|
170
|
+
session: inputs.session,
|
|
171
|
+
resumePath: inputs.resumePath ?? projectionDirectory,
|
|
172
|
+
}, { observer: observerController.observer })
|
|
173
|
+
: call(terms, { observer: observerController.observer });
|
|
174
|
+
}
|
|
175
|
+
catch (error) {
|
|
176
|
+
void observerController.close();
|
|
177
|
+
throw error;
|
|
178
|
+
}
|
|
179
|
+
const outcome = projection.outcome.then(async (result) => {
|
|
180
|
+
try {
|
|
181
|
+
return {
|
|
182
|
+
verdict: result.status === "completed"
|
|
183
|
+
? "completed"
|
|
184
|
+
: result.status === "dismissed"
|
|
185
|
+
? "dismissed"
|
|
186
|
+
: result.status === "interrupted"
|
|
187
|
+
? "interrupted"
|
|
188
|
+
: "failed",
|
|
189
|
+
facts: await completionFacts(projectionDirectory, executionId, inputs, result),
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
finally {
|
|
193
|
+
await observerController.close();
|
|
194
|
+
}
|
|
195
|
+
}, async (error) => {
|
|
196
|
+
await observerController.close();
|
|
197
|
+
throw error;
|
|
198
|
+
});
|
|
180
199
|
return {
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
? "dismissed"
|
|
185
|
-
: "failed",
|
|
186
|
-
facts: await completionFacts(projectionDirectory, executionId, inputs, result),
|
|
200
|
+
events: projection.events,
|
|
201
|
+
outcome,
|
|
202
|
+
abort: (mode) => projection.abort(mode),
|
|
187
203
|
};
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
await observerController.close();
|
|
191
|
-
}
|
|
192
|
-
}, async (error) => {
|
|
193
|
-
await observerController.close();
|
|
194
|
-
throw error;
|
|
195
|
-
});
|
|
196
|
-
return {
|
|
197
|
-
events: projection.events,
|
|
198
|
-
outcome,
|
|
199
|
-
abort: (mode) => projection.abort(mode),
|
|
204
|
+
},
|
|
205
|
+
dispose: () => observerController.close(),
|
|
200
206
|
};
|
|
201
207
|
}
|
|
208
|
+
export function executeProjectionGeneration(projectionDirectory, launch, providerLoaderPorts = {}) {
|
|
209
|
+
return prepareProjectionGenerationExecution(projectionDirectory, launch, providerLoaderPorts).start();
|
|
210
|
+
}
|
|
@@ -1,37 +1,179 @@
|
|
|
1
1
|
import { setTimeout as delay } from "node:timers/promises";
|
|
2
2
|
import { openProjectionGenerationStore } from "./store.js";
|
|
3
3
|
import { spawnProjectionGenerationRunner, } from "./projection-generation-process.js";
|
|
4
|
+
import { acquireProjectionRunnerLock, observeProjectionRunnerLock, } from "../projection-runner-lock.js";
|
|
4
5
|
export const PROJECTION_ADOPTION_TIMEOUT_MS = 4_000;
|
|
5
6
|
export const PROJECTION_ADOPTION_POLL_MS = 20;
|
|
6
7
|
function errorDetail(error) {
|
|
7
8
|
return error instanceof Error ? error.message : String(error);
|
|
8
9
|
}
|
|
9
|
-
function
|
|
10
|
+
function recordDeadStartup(projectionDirectory, executionId, detail, stage, terminalAt) {
|
|
10
11
|
const store = openProjectionGenerationStore(projectionDirectory);
|
|
11
12
|
try {
|
|
12
|
-
return store.
|
|
13
|
+
return store.settleStartupFailure({
|
|
13
14
|
executionId,
|
|
14
|
-
|
|
15
|
-
|
|
15
|
+
releasedLockEvidence: { stage, detail },
|
|
16
|
+
diagnostic: { stage, detail },
|
|
17
|
+
terminalAt,
|
|
16
18
|
}).status === "committed";
|
|
17
19
|
}
|
|
18
20
|
finally {
|
|
19
21
|
store.close();
|
|
20
22
|
}
|
|
21
23
|
}
|
|
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
|
+
});
|
|
91
|
+
}
|
|
22
92
|
/** Spawn one committed launch and return only after its exact adoption row. */
|
|
23
93
|
export async function startProjectionGeneration(projectionDirectory, executionId, dependencies = {}) {
|
|
24
94
|
const now = dependencies.now ?? Date.now;
|
|
25
95
|
const sleep = dependencies.sleep ?? ((milliseconds) => delay(milliseconds));
|
|
96
|
+
const launcherAcquiredOwnership = dependencies.launchOwnership === undefined;
|
|
97
|
+
let launchOwnership;
|
|
98
|
+
try {
|
|
99
|
+
launchOwnership = dependencies.launchOwnership
|
|
100
|
+
?? acquireProjectionRunnerLock(projectionDirectory);
|
|
101
|
+
}
|
|
102
|
+
catch (error) {
|
|
103
|
+
if (observeProjectionRunnerLock(projectionDirectory).state === "held") {
|
|
104
|
+
return { status: "pending" };
|
|
105
|
+
}
|
|
106
|
+
return {
|
|
107
|
+
status: "failed",
|
|
108
|
+
cause: "adoption",
|
|
109
|
+
detail: `runner construction failed: ${errorDetail(error)}`,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
let launchCommitted = false;
|
|
113
|
+
let childPromoted = false;
|
|
26
114
|
let child;
|
|
27
115
|
try {
|
|
28
116
|
child = spawnProjectionGenerationRunner(projectionDirectory, executionId, dependencies);
|
|
29
117
|
}
|
|
30
118
|
catch (error) {
|
|
31
119
|
const detail = `runner spawn failed: ${errorDetail(error)}`;
|
|
32
|
-
|
|
120
|
+
recordDeadStartup(projectionDirectory, executionId, detail, "spawn", new Date(now()).toISOString());
|
|
121
|
+
launchOwnership.close();
|
|
33
122
|
return { status: "failed", cause: "spawn", detail };
|
|
34
123
|
}
|
|
124
|
+
const bootstrap = observeBootstrapMessages(child, executionId);
|
|
125
|
+
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
|
+
}
|
|
145
|
+
}
|
|
146
|
+
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)}` };
|
|
173
|
+
}
|
|
174
|
+
finally {
|
|
175
|
+
bootstrap.close();
|
|
176
|
+
}
|
|
35
177
|
const deadline = now() + PROJECTION_ADOPTION_TIMEOUT_MS;
|
|
36
178
|
for (;;) {
|
|
37
179
|
const observedAt = now();
|
|
@@ -74,19 +216,5 @@ export async function startProjectionGeneration(projectionDirectory, executionId
|
|
|
74
216
|
finally {
|
|
75
217
|
store.close();
|
|
76
218
|
}
|
|
77
|
-
|
|
78
|
-
if (!recordLaunchFailure(projectionDirectory, executionId, detail, "adoption", new Date(now()).toISOString())) {
|
|
79
|
-
const raced = openProjectionGenerationStore(projectionDirectory);
|
|
80
|
-
try {
|
|
81
|
-
const current = raced.readCurrentGeneration();
|
|
82
|
-
if (current?.launch.executionId === executionId && current.adoption) {
|
|
83
|
-
return { status: "adopted", child };
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
finally {
|
|
87
|
-
raced.close();
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
child.kill("SIGTERM");
|
|
91
|
-
return { status: "failed", cause: "adoption", detail };
|
|
219
|
+
return { status: "pending", child };
|
|
92
220
|
}
|
|
@@ -17,6 +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";
|
|
20
21
|
export function spawnProjectionGenerationRunner(projectionDirectory, executionId, dependencies = {}) {
|
|
21
22
|
const spawn = dependencies.spawn ?? nodeSpawn;
|
|
22
23
|
const openSync = dependencies.openSync ?? fs.openSync;
|
|
@@ -30,7 +31,8 @@ export function spawnProjectionGenerationRunner(projectionDirectory, executionId
|
|
|
30
31
|
try {
|
|
31
32
|
const child = spawn(command, args, {
|
|
32
33
|
detached: true,
|
|
33
|
-
stdio: ["ignore", logFd, logFd],
|
|
34
|
+
stdio: ["ignore", logFd, logFd, "ipc"],
|
|
35
|
+
env: { ...process.env, [PROJECTION_RUNNER_BOOTSTRAP_ENV]: "1" },
|
|
34
36
|
});
|
|
35
37
|
if (child.pid === undefined) {
|
|
36
38
|
throw new Error("projection runner did not expose a process id");
|