@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
|
@@ -76,6 +76,8 @@ export async function runProjectionGeneration(input) {
|
|
|
76
76
|
let settled = false;
|
|
77
77
|
let tellObserver;
|
|
78
78
|
let graceTimer;
|
|
79
|
+
let intentTimer;
|
|
80
|
+
let handle;
|
|
79
81
|
const clearGraceTimer = () => {
|
|
80
82
|
if (graceTimer === undefined)
|
|
81
83
|
return;
|
|
@@ -83,15 +85,19 @@ export async function runProjectionGeneration(input) {
|
|
|
83
85
|
graceTimer = undefined;
|
|
84
86
|
};
|
|
85
87
|
try {
|
|
88
|
+
runnerLock = input.runnerLock
|
|
89
|
+
?? acquireRunnerLockOrObserveDuplicate(input.projectionDirectory);
|
|
90
|
+
if (!runnerLock)
|
|
91
|
+
return { status: "duplicate" };
|
|
86
92
|
const initial = store.readCurrentGeneration();
|
|
87
93
|
if (initial?.launch.executionId !== input.executionId
|
|
88
94
|
|| initial.adoption
|
|
89
95
|
|| initial.verdict) {
|
|
90
96
|
return { status: "rejected" };
|
|
91
97
|
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
98
|
+
tellObserver = openProjectionTellObserver(input.projectionDirectory);
|
|
99
|
+
// Prime before adoption so a concurrent stop write cannot become the baseline.
|
|
100
|
+
tellObserver.hasDataVersionChanged();
|
|
95
101
|
const adoption = store.adoptionIfCurrent({
|
|
96
102
|
executionId: input.executionId,
|
|
97
103
|
facts: { adoptedAt: new Date(now()).toISOString() },
|
|
@@ -99,11 +105,12 @@ export async function runProjectionGeneration(input) {
|
|
|
99
105
|
if (adoption.status !== "committed")
|
|
100
106
|
return { status: "rejected" };
|
|
101
107
|
adopted = true;
|
|
102
|
-
|
|
108
|
+
let current = store.readCurrentGeneration();
|
|
103
109
|
if (current?.launch.executionId !== input.executionId) {
|
|
104
110
|
throw new Error("adopted generation stopped being current");
|
|
105
111
|
}
|
|
106
|
-
//
|
|
112
|
+
// Adoption is the provider-admission boundary. A kill that committed first
|
|
113
|
+
// settles without constructing a provider execution.
|
|
107
114
|
if (current.killIntent) {
|
|
108
115
|
const killed = store.runnerKilledIfRequested({ executionId: input.executionId });
|
|
109
116
|
if (killed.status !== "committed")
|
|
@@ -111,12 +118,61 @@ export async function runProjectionGeneration(input) {
|
|
|
111
118
|
settled = true;
|
|
112
119
|
return { status: "completed" };
|
|
113
120
|
}
|
|
114
|
-
const
|
|
115
|
-
|
|
121
|
+
const prepared = await input.prepareExecution(current.launch);
|
|
122
|
+
current = store.readCurrentGeneration();
|
|
123
|
+
if (current?.launch.executionId !== input.executionId) {
|
|
124
|
+
await prepared.dispose();
|
|
125
|
+
throw new Error("prepared generation stopped being current");
|
|
126
|
+
}
|
|
127
|
+
if (current.killIntent) {
|
|
128
|
+
await prepared.dispose();
|
|
129
|
+
const killed = store.runnerKilledIfRequested({ executionId: input.executionId });
|
|
130
|
+
if (killed.status !== "committed")
|
|
131
|
+
return { status: "rejected" };
|
|
132
|
+
settled = true;
|
|
133
|
+
return { status: "completed" };
|
|
134
|
+
}
|
|
135
|
+
handle = prepared.start();
|
|
136
|
+
const settleInterrupted = (facts = {}) => {
|
|
137
|
+
const transition = store.runnerInterruptedIfRequested({
|
|
138
|
+
executionId: input.executionId,
|
|
139
|
+
facts,
|
|
140
|
+
terminalAt: new Date(now()).toISOString(),
|
|
141
|
+
});
|
|
142
|
+
if (transition.status !== "committed")
|
|
143
|
+
return { status: "rejected" };
|
|
144
|
+
settled = true;
|
|
145
|
+
runnerLock.close();
|
|
146
|
+
runnerLock = undefined;
|
|
147
|
+
void input.wakeAfterInterrupt?.().catch(() => {
|
|
148
|
+
// The ordinary wake path persists its typed failure. A later tell can retry.
|
|
149
|
+
});
|
|
150
|
+
return { status: "completed" };
|
|
151
|
+
};
|
|
152
|
+
const settleRunnerOutcome = (execution) => {
|
|
153
|
+
const transition = store.runnerVerdictIfOpen({
|
|
154
|
+
executionId: input.executionId,
|
|
155
|
+
verdict: execution.verdict,
|
|
156
|
+
facts: execution.facts,
|
|
157
|
+
terminalAt: new Date(now()).toISOString(),
|
|
158
|
+
});
|
|
159
|
+
if (transition.status !== "committed")
|
|
160
|
+
return { status: "rejected" };
|
|
161
|
+
settled = true;
|
|
162
|
+
if (transition.records[0]?.facts.state === "interrupted") {
|
|
163
|
+
runnerLock.close();
|
|
164
|
+
runnerLock = undefined;
|
|
165
|
+
void input.wakeAfterInterrupt?.().catch(() => {
|
|
166
|
+
// The ordinary wake path persists its typed failure. A later observer retries.
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
return { status: "completed" };
|
|
170
|
+
};
|
|
116
171
|
let gracefulAbortInvoked = false;
|
|
117
172
|
let forceAbortInvoked = false;
|
|
118
173
|
let sawKillIntent = false;
|
|
119
|
-
|
|
174
|
+
let sawInterruptIntent = false;
|
|
175
|
+
const observeStopBoundary = () => {
|
|
120
176
|
try {
|
|
121
177
|
if (!tellObserver.hasDataVersionChanged())
|
|
122
178
|
return;
|
|
@@ -125,9 +181,10 @@ export async function runProjectionGeneration(input) {
|
|
|
125
181
|
return;
|
|
126
182
|
if (live.verdict)
|
|
127
183
|
return;
|
|
128
|
-
|
|
184
|
+
sawKillIntent = live.killIntent !== undefined;
|
|
185
|
+
sawInterruptIntent = live.interruptIntent !== undefined;
|
|
186
|
+
if (!sawKillIntent && !sawInterruptIntent)
|
|
129
187
|
return;
|
|
130
|
-
sawKillIntent = true;
|
|
131
188
|
if (gracefulAbortInvoked)
|
|
132
189
|
return;
|
|
133
190
|
gracefulAbortInvoked = true;
|
|
@@ -153,12 +210,12 @@ export async function runProjectionGeneration(input) {
|
|
|
153
210
|
const outcomeWatch = handle.outcome.then((execution) => {
|
|
154
211
|
outcomeBox.status = "fulfilled";
|
|
155
212
|
outcomeBox.execution = execution;
|
|
156
|
-
|
|
213
|
+
observeStopBoundary();
|
|
157
214
|
bumpActivity();
|
|
158
215
|
}, (error) => {
|
|
159
216
|
outcomeBox.status = "rejected";
|
|
160
217
|
outcomeBox.error = error;
|
|
161
|
-
|
|
218
|
+
observeStopBoundary();
|
|
162
219
|
bumpActivity();
|
|
163
220
|
});
|
|
164
221
|
// Always rejection-observe so abandon paths never surface unhandled rejections.
|
|
@@ -168,7 +225,7 @@ export async function runProjectionGeneration(input) {
|
|
|
168
225
|
for await (const _event of handle.events) {
|
|
169
226
|
if (forceAbortInvoked)
|
|
170
227
|
return;
|
|
171
|
-
|
|
228
|
+
observeStopBoundary();
|
|
172
229
|
bumpActivity();
|
|
173
230
|
}
|
|
174
231
|
}
|
|
@@ -178,10 +235,17 @@ export async function runProjectionGeneration(input) {
|
|
|
178
235
|
bumpActivity();
|
|
179
236
|
})();
|
|
180
237
|
observeAbandoned(eventDrain);
|
|
181
|
-
|
|
238
|
+
// A provider may be silently executing when an operator interrupts it.
|
|
239
|
+
// The durable observer is the sole stop-intent authority in that interval.
|
|
240
|
+
intentTimer = setInterval(() => {
|
|
241
|
+
observeStopBoundary();
|
|
242
|
+
bumpActivity();
|
|
243
|
+
}, 100);
|
|
244
|
+
intentTimer.unref?.();
|
|
245
|
+
while (outcomeBox.status === "pending" && !sawKillIntent && !sawInterruptIntent) {
|
|
182
246
|
await activityChanged();
|
|
183
247
|
}
|
|
184
|
-
if (sawKillIntent) {
|
|
248
|
+
if (sawKillIntent || sawInterruptIntent) {
|
|
185
249
|
// Grace race: outcome settlement inside the window is stop proof.
|
|
186
250
|
if (outcomeBox.status === "pending") {
|
|
187
251
|
const graceExpired = new Promise((resolve) => {
|
|
@@ -201,6 +265,16 @@ export async function runProjectionGeneration(input) {
|
|
|
201
265
|
const facts = killOutcome.status === "fulfilled"
|
|
202
266
|
? nonReservedRunnerKillFacts(killOutcome.execution.facts)
|
|
203
267
|
: undefined;
|
|
268
|
+
if (sawInterruptIntent && killOutcome.status === "fulfilled" && killOutcome.execution.verdict === "interrupted") {
|
|
269
|
+
return settleInterrupted(facts);
|
|
270
|
+
}
|
|
271
|
+
if (sawInterruptIntent) {
|
|
272
|
+
if (killOutcome.status === "fulfilled")
|
|
273
|
+
return settleRunnerOutcome(killOutcome.execution);
|
|
274
|
+
if (killOutcome.status === "rejected")
|
|
275
|
+
throw killOutcome.error;
|
|
276
|
+
throw new Error("interrupt outcome did not establish provider stop");
|
|
277
|
+
}
|
|
204
278
|
const killed = store.runnerKilledIfRequested({
|
|
205
279
|
executionId: input.executionId,
|
|
206
280
|
...(facts && Object.keys(facts).length > 0 ? { facts } : {}),
|
|
@@ -214,6 +288,8 @@ export async function runProjectionGeneration(input) {
|
|
|
214
288
|
forceAbortInvoked = true;
|
|
215
289
|
try {
|
|
216
290
|
await handle.abort("force");
|
|
291
|
+
if (sawInterruptIntent)
|
|
292
|
+
return settleInterrupted();
|
|
217
293
|
const killed = store.runnerKilledIfRequested({ executionId: input.executionId });
|
|
218
294
|
if (killed.status !== "committed")
|
|
219
295
|
return { status: "rejected" };
|
|
@@ -225,7 +301,7 @@ export async function runProjectionGeneration(input) {
|
|
|
225
301
|
executionId: input.executionId,
|
|
226
302
|
verdict: "failed",
|
|
227
303
|
facts: {
|
|
228
|
-
operatorAction: "kill",
|
|
304
|
+
...(sawKillIntent ? { operatorAction: "kill" } : {}),
|
|
229
305
|
diagnostic: "escalation-failed",
|
|
230
306
|
detail: errorDetail(error),
|
|
231
307
|
failedAt: new Date(now()).toISOString(),
|
|
@@ -261,83 +337,86 @@ export async function runProjectionGeneration(input) {
|
|
|
261
337
|
const tellFence = normalizeTellFence(pending.tellIds);
|
|
262
338
|
if (tellFence.length > 0) {
|
|
263
339
|
const successorExecutionId = createProjectionExecutionId(now());
|
|
264
|
-
const
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
successorExecutionId,
|
|
268
|
-
successorFacts: successorLaunchFacts(current.launch, execution.facts, pending.effort, now),
|
|
269
|
-
tellFence,
|
|
270
|
-
});
|
|
271
|
-
if (transition.status !== "committed")
|
|
340
|
+
const successorFacts = successorLaunchFacts(current.launch, execution.facts, pending.effort, now);
|
|
341
|
+
let transitionReason;
|
|
342
|
+
if (!input.startSuccessor)
|
|
272
343
|
return { status: "rejected" };
|
|
273
|
-
settled = true;
|
|
274
|
-
// Handoff makes the predecessor terminal before life authority moves.
|
|
275
|
-
// A held projection lock must always mean that a current runner exists,
|
|
276
|
-
// so release it before the successor makes its one-shot acquisition.
|
|
277
|
-
runnerLock.close();
|
|
278
|
-
runnerLock = undefined;
|
|
279
344
|
try {
|
|
280
|
-
|
|
345
|
+
const successorOwnership = runnerLock;
|
|
346
|
+
await input.startSuccessor(successorExecutionId, () => {
|
|
347
|
+
const transition = store.handoff({
|
|
348
|
+
executionId: input.executionId,
|
|
349
|
+
completionFacts: execution.facts,
|
|
350
|
+
successorExecutionId,
|
|
351
|
+
successorFacts,
|
|
352
|
+
tellFence,
|
|
353
|
+
});
|
|
354
|
+
if (transition.status !== "committed") {
|
|
355
|
+
transitionReason = transition.reason;
|
|
356
|
+
return false;
|
|
357
|
+
}
|
|
358
|
+
settled = true;
|
|
359
|
+
return true;
|
|
360
|
+
}, successorOwnership);
|
|
361
|
+
runnerLock = undefined;
|
|
281
362
|
}
|
|
282
|
-
catch
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
facts: {
|
|
287
|
-
stage: "spawn",
|
|
288
|
-
detail: `successor runner spawn failed: ${errorDetail(error)}`,
|
|
289
|
-
terminalAt: new Date(now()).toISOString(),
|
|
290
|
-
},
|
|
291
|
-
});
|
|
363
|
+
catch {
|
|
364
|
+
if (settled)
|
|
365
|
+
return { status: "handed-off", successorExecutionId };
|
|
366
|
+
return settleRunnerOutcome(execution);
|
|
292
367
|
}
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
const verdict = store.verdictIfOpen({
|
|
296
|
-
executionId: input.executionId,
|
|
297
|
-
verdict: execution.verdict,
|
|
298
|
-
facts: execution.facts,
|
|
299
|
-
});
|
|
300
|
-
if (verdict.status !== "committed") {
|
|
301
|
-
// Kill intent may commit after the last outcome-boundary observation and
|
|
302
|
-
// before this transaction. The settled outcome is already stop proof.
|
|
303
|
-
if (verdict.reason === "kill-requested") {
|
|
304
|
-
const facts = nonReservedRunnerKillFacts(execution.facts);
|
|
305
|
-
const killed = store.runnerKilledIfRequested({
|
|
306
|
-
executionId: input.executionId,
|
|
307
|
-
...(Object.keys(facts).length > 0 ? { facts } : {}),
|
|
308
|
-
});
|
|
309
|
-
if (killed.status !== "committed")
|
|
368
|
+
if (!settled) {
|
|
369
|
+
if (transitionReason !== "kill-requested" && transitionReason !== "interrupt-requested") {
|
|
310
370
|
return { status: "rejected" };
|
|
311
|
-
|
|
312
|
-
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
else {
|
|
374
|
+
return { status: "handed-off", successorExecutionId };
|
|
313
375
|
}
|
|
314
|
-
return { status: "rejected" };
|
|
315
376
|
}
|
|
316
|
-
|
|
317
|
-
return { status: "completed" };
|
|
377
|
+
return settleRunnerOutcome(execution);
|
|
318
378
|
}
|
|
319
379
|
catch (error) {
|
|
320
380
|
if (adopted && !settled) {
|
|
321
|
-
|
|
381
|
+
let cleanupError;
|
|
382
|
+
if (handle) {
|
|
383
|
+
try {
|
|
384
|
+
await handle.abort("force");
|
|
385
|
+
}
|
|
386
|
+
catch (failure) {
|
|
387
|
+
cleanupError = failure;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
const live = store.readCurrentGeneration();
|
|
391
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
392
|
+
store.runnerVerdictIfOpen({
|
|
322
393
|
executionId: input.executionId,
|
|
323
394
|
verdict: "failed",
|
|
324
395
|
facts: {
|
|
325
396
|
failedAt: new Date(now()).toISOString(),
|
|
326
|
-
detail
|
|
397
|
+
detail,
|
|
398
|
+
...(cleanupError ? {
|
|
399
|
+
...(live?.killIntent ? { operatorAction: "kill" } : {}),
|
|
400
|
+
diagnostic: "cleanup-escalation-failed",
|
|
401
|
+
cleanupDetail: errorDetail(cleanupError),
|
|
402
|
+
} : {}),
|
|
327
403
|
error: {
|
|
328
404
|
version: OUTCOME_ERROR_VERSION,
|
|
329
405
|
code: "SUBAGENT_EXEC_ERROR",
|
|
330
|
-
message:
|
|
406
|
+
message: detail,
|
|
331
407
|
timeoutMs: null,
|
|
332
408
|
exitCode: null,
|
|
333
409
|
},
|
|
334
410
|
},
|
|
411
|
+
terminalAt: new Date(now()).toISOString(),
|
|
335
412
|
});
|
|
336
413
|
}
|
|
337
414
|
throw error;
|
|
338
415
|
}
|
|
339
416
|
finally {
|
|
340
417
|
clearGraceTimer();
|
|
418
|
+
if (intentTimer !== undefined)
|
|
419
|
+
clearInterval(intentTimer);
|
|
341
420
|
try {
|
|
342
421
|
tellObserver?.close();
|
|
343
422
|
}
|
|
@@ -1,15 +1,97 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { prepareProjectionGenerationExecution } from "./projection-generation-execution.js";
|
|
2
|
+
import { PROJECTION_RUNNER_BOOTSTRAP_ENV, } from "./projection-generation-process.js";
|
|
3
3
|
import { runProjectionGeneration } from "./projection-generation-runner.js";
|
|
4
|
+
import { PROJECTION_ADOPTION_TIMEOUT_MS } from "./projection-generation-launcher.js";
|
|
5
|
+
import { startProjectionGeneration } from "./projection-generation-launcher.js";
|
|
4
6
|
import { snapshotPendingTells } from "./projection-generation-continuation.js";
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
7
|
+
import { persistInterruptWakeFailure, tellProjection } from "../projection-wake.js";
|
|
8
|
+
import { readProjectionIdentity } from "../projection-identity.js";
|
|
9
|
+
import * as path from "node:path";
|
|
10
|
+
import { acquireProjectionRunnerTransferTarget } from "../projection-runner-lock.js";
|
|
11
|
+
function sendBootstrapMessage(message) {
|
|
12
|
+
return new Promise((resolve, reject) => {
|
|
13
|
+
if (!process.send || !process.connected) {
|
|
14
|
+
reject(new Error("projection runner bootstrap channel is unavailable"));
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
process.send(message, (error) => error ? reject(error) : resolve());
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
function waitForLaunchCommand(executionId, expected) {
|
|
21
|
+
return new Promise((resolve, reject) => {
|
|
22
|
+
const cleanup = () => {
|
|
23
|
+
process.off("message", onMessage);
|
|
24
|
+
process.off("disconnect", onDisconnect);
|
|
25
|
+
};
|
|
26
|
+
const onDisconnect = () => {
|
|
27
|
+
cleanup();
|
|
28
|
+
reject(new Error("projection runner bootstrap channel disconnected before launch authorization"));
|
|
29
|
+
};
|
|
30
|
+
const onMessage = (value) => {
|
|
31
|
+
const command = value;
|
|
32
|
+
if (command.executionId !== executionId)
|
|
33
|
+
return;
|
|
34
|
+
if (expected === "promote-leash" && command.kind !== "promote-leash" && command.kind !== "launch-rejected")
|
|
35
|
+
return;
|
|
36
|
+
if (expected === "launch-authority" && command.kind !== "launch-committed" && command.kind !== "launch-rejected")
|
|
37
|
+
return;
|
|
38
|
+
cleanup();
|
|
39
|
+
resolve(command);
|
|
40
|
+
};
|
|
41
|
+
process.on("message", onMessage);
|
|
42
|
+
process.on("disconnect", onDisconnect);
|
|
14
43
|
});
|
|
15
44
|
}
|
|
45
|
+
export async function wakeProjectionAfterInterrupt(projectionDirectory, wake = tellProjection) {
|
|
46
|
+
const pact = readProjectionIdentity(projectionDirectory);
|
|
47
|
+
try {
|
|
48
|
+
await wake(projectionDirectory, `${pact.akuma}/${path.basename(projectionDirectory)}`, undefined);
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
persistInterruptWakeFailure(projectionDirectory, error);
|
|
52
|
+
throw error;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
export async function runProjectionGenerationRuntime(projectionDirectory, executionId) {
|
|
56
|
+
if (process.env[PROJECTION_RUNNER_BOOTSTRAP_ENV] !== "1") {
|
|
57
|
+
throw new Error("projection runner was not spawned with inherited leash coordination");
|
|
58
|
+
}
|
|
59
|
+
let runnerLock;
|
|
60
|
+
let transferTarget;
|
|
61
|
+
try {
|
|
62
|
+
transferTarget = acquireProjectionRunnerTransferTarget(projectionDirectory, process.platform, PROJECTION_ADOPTION_TIMEOUT_MS);
|
|
63
|
+
await sendBootstrapMessage({ kind: "leash-inherited", executionId });
|
|
64
|
+
const promotion = await waitForLaunchCommand(executionId, "promote-leash");
|
|
65
|
+
if (promotion.kind === "launch-rejected")
|
|
66
|
+
return;
|
|
67
|
+
runnerLock = transferTarget.promote();
|
|
68
|
+
transferTarget = undefined;
|
|
69
|
+
await sendBootstrapMessage({ kind: "leash-acquired", executionId });
|
|
70
|
+
const command = await waitForLaunchCommand(executionId, "launch-authority");
|
|
71
|
+
if (command.kind === "launch-rejected")
|
|
72
|
+
return;
|
|
73
|
+
process.disconnect?.();
|
|
74
|
+
const ownedLock = runnerLock;
|
|
75
|
+
runnerLock = undefined;
|
|
76
|
+
await runProjectionGeneration({
|
|
77
|
+
projectionDirectory,
|
|
78
|
+
executionId,
|
|
79
|
+
runnerLock: ownedLock,
|
|
80
|
+
prepareExecution: (launch) => prepareProjectionGenerationExecution(projectionDirectory, launch),
|
|
81
|
+
snapshotPendingTells: () => snapshotPendingTells(projectionDirectory),
|
|
82
|
+
startSuccessor: async (successorExecutionId, commitLaunch, launchOwnership) => {
|
|
83
|
+
const started = await startProjectionGeneration(projectionDirectory, successorExecutionId, {
|
|
84
|
+
commitLaunch,
|
|
85
|
+
launchOwnership,
|
|
86
|
+
});
|
|
87
|
+
if (started.status === "failed")
|
|
88
|
+
throw new Error(started.detail);
|
|
89
|
+
},
|
|
90
|
+
wakeAfterInterrupt: () => wakeProjectionAfterInterrupt(projectionDirectory),
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
finally {
|
|
94
|
+
runnerLock?.close();
|
|
95
|
+
transferTarget?.close();
|
|
96
|
+
}
|
|
97
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { PROJECTION_GENERATION_SCHEMA_VERSION, PROJECTION_GENERATION_STORE_FILE, assertDatabaseOpen, closeDatabase, openMutableDatabase, openReadOnlyDatabase, readValidatedHistory, } from "./database.js";
|
|
1
|
+
import { PROJECTION_GENERATION_SCHEMA_VERSION, PROJECTION_GENERATION_STORE_FILE, assertDatabaseOpen, beginGenerationWriteTransaction, closeDatabase, commitGenerationWriteTransaction, openMutableDatabase, openReadOnlyDatabase, readValidatedHistory, rollbackGenerationWriteTransaction, } from "./database.js";
|
|
2
2
|
import { initializeIdentity as initializeProjectionIdentity, readIdentity as readProjectionIdentity, } from "./identity.js";
|
|
3
3
|
import * as transitions from "./transitions.js";
|
|
4
4
|
export { PROJECTION_GENERATION_SCHEMA_VERSION, PROJECTION_GENERATION_STORE_FILE, };
|
|
@@ -21,8 +21,18 @@ export class ProjectionGenerationStore {
|
|
|
21
21
|
return new ProjectionGenerationStore(databasePath, database, true);
|
|
22
22
|
}
|
|
23
23
|
close() {
|
|
24
|
+
rollbackGenerationWriteTransaction(this.#database);
|
|
24
25
|
closeDatabase(this.#database);
|
|
25
26
|
}
|
|
27
|
+
beginWriteTransaction() {
|
|
28
|
+
beginGenerationWriteTransaction(this.#database, this.readOnly);
|
|
29
|
+
}
|
|
30
|
+
commitWriteTransaction() {
|
|
31
|
+
commitGenerationWriteTransaction(this.#database);
|
|
32
|
+
}
|
|
33
|
+
rollbackWriteTransaction() {
|
|
34
|
+
rollbackGenerationWriteTransaction(this.#database);
|
|
35
|
+
}
|
|
26
36
|
readRecords() {
|
|
27
37
|
assertDatabaseOpen(this.#database);
|
|
28
38
|
return readValidatedHistory(this.#database).records;
|
|
@@ -40,15 +50,27 @@ export class ProjectionGenerationStore {
|
|
|
40
50
|
launchIfSettled(input) {
|
|
41
51
|
return transitions.launchIfSettled(this.#database, this.readOnly, input);
|
|
42
52
|
}
|
|
53
|
+
failInterruptWakeIfSettled(input) {
|
|
54
|
+
return transitions.failInterruptWakeIfSettled(this.#database, this.readOnly, input);
|
|
55
|
+
}
|
|
43
56
|
adoptionIfCurrent(input) {
|
|
44
57
|
return transitions.adoptionIfCurrent(this.#database, this.readOnly, input);
|
|
45
58
|
}
|
|
46
59
|
requestKillIfOpen(input) {
|
|
47
60
|
return transitions.requestKillIfOpen(this.#database, this.readOnly, input);
|
|
48
61
|
}
|
|
62
|
+
requestInterruptIfOpen(input) {
|
|
63
|
+
return transitions.requestInterruptIfOpen(this.#database, this.readOnly, input);
|
|
64
|
+
}
|
|
49
65
|
runnerKilledIfRequested(input) {
|
|
50
66
|
return transitions.runnerKilledIfRequested(this.#database, this.readOnly, input);
|
|
51
67
|
}
|
|
68
|
+
runnerInterruptedIfRequested(input) {
|
|
69
|
+
return transitions.runnerInterruptedIfRequested(this.#database, this.readOnly, input);
|
|
70
|
+
}
|
|
71
|
+
runnerVerdictIfOpen(input) {
|
|
72
|
+
return transitions.runnerVerdictIfOpen(this.#database, this.readOnly, input);
|
|
73
|
+
}
|
|
52
74
|
settleKillIfRunnerDead(input) {
|
|
53
75
|
return transitions.settleKillIfRunnerDead(this.#database, this.readOnly, input);
|
|
54
76
|
}
|
|
@@ -58,6 +80,9 @@ export class ProjectionGenerationStore {
|
|
|
58
80
|
supersedeStartupTimeout(input) {
|
|
59
81
|
return transitions.supersedeStartupTimeout(this.#database, this.readOnly, input);
|
|
60
82
|
}
|
|
83
|
+
settleStartupFailure(input) {
|
|
84
|
+
return transitions.settleStartupFailure(this.#database, this.readOnly, input);
|
|
85
|
+
}
|
|
61
86
|
replaceDeadRunner(input) {
|
|
62
87
|
return transitions.replaceDeadRunner(this.#database, this.readOnly, input);
|
|
63
88
|
}
|