@astrosheep/keiyaku 2.9.7 → 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/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/projection/status/handler.js +5 -4
- package/build/cli/commands/projection/status/meta.js +2 -2
- package/build/cli/commands/task/add/meta.js +9 -1
- package/build/cli/commands/task/shared.js +2 -1
- package/build/cli/completion.js +8 -0
- package/build/cli/render/kanshi.js +2 -2
- package/build/cli/render/path-prefix-compaction.js +119 -76
- package/build/cli/render/projection-activity.js +10 -1
- package/build/cli/render/shared.js +8 -7
- package/build/cli/render/status.js +8 -5
- package/build/cli/render/wait.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/call/context.js +19 -3
- package/build/core/call/execution.js +43 -20
- package/build/core/ledger-batch.js +194 -0
- package/build/core/projection/generation/database.js +22 -0
- package/build/core/projection/generation/projection-generation-execution.js +45 -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 +76 -40
- package/build/core/projection/generation/projection-generation-runtime.js +82 -19
- package/build/core/projection/generation/store.js +17 -1
- package/build/core/projection/generation/transitions.js +89 -12
- package/build/core/projection/index.js +3 -3
- package/build/core/projection/projection-kill.js +22 -10
- package/build/core/projection/projection-runner-lock.js +177 -37
- package/build/core/projection/projection-status.js +143 -55
- package/build/core/projection/projection-wake.js +171 -72
- 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/task-git-runtime.js +8 -10
- package/build/core/task/task-git-store.js +5 -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 +18 -0
- package/skills/keiyaku-workflow/SKILL.md +68 -13
|
@@ -77,6 +77,7 @@ export async function runProjectionGeneration(input) {
|
|
|
77
77
|
let tellObserver;
|
|
78
78
|
let graceTimer;
|
|
79
79
|
let intentTimer;
|
|
80
|
+
let handle;
|
|
80
81
|
const clearGraceTimer = () => {
|
|
81
82
|
if (graceTimer === undefined)
|
|
82
83
|
return;
|
|
@@ -84,15 +85,19 @@ export async function runProjectionGeneration(input) {
|
|
|
84
85
|
graceTimer = undefined;
|
|
85
86
|
};
|
|
86
87
|
try {
|
|
88
|
+
runnerLock = input.runnerLock
|
|
89
|
+
?? acquireRunnerLockOrObserveDuplicate(input.projectionDirectory);
|
|
90
|
+
if (!runnerLock)
|
|
91
|
+
return { status: "duplicate" };
|
|
87
92
|
const initial = store.readCurrentGeneration();
|
|
88
93
|
if (initial?.launch.executionId !== input.executionId
|
|
89
94
|
|| initial.adoption
|
|
90
95
|
|| initial.verdict) {
|
|
91
96
|
return { status: "rejected" };
|
|
92
97
|
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
98
|
+
tellObserver = openProjectionTellObserver(input.projectionDirectory);
|
|
99
|
+
// Prime before adoption so a concurrent stop write cannot become the baseline.
|
|
100
|
+
tellObserver.hasDataVersionChanged();
|
|
96
101
|
const adoption = store.adoptionIfCurrent({
|
|
97
102
|
executionId: input.executionId,
|
|
98
103
|
facts: { adoptedAt: new Date(now()).toISOString() },
|
|
@@ -100,12 +105,12 @@ export async function runProjectionGeneration(input) {
|
|
|
100
105
|
if (adoption.status !== "committed")
|
|
101
106
|
return { status: "rejected" };
|
|
102
107
|
adopted = true;
|
|
103
|
-
|
|
108
|
+
let current = store.readCurrentGeneration();
|
|
104
109
|
if (current?.launch.executionId !== input.executionId) {
|
|
105
110
|
throw new Error("adopted generation stopped being current");
|
|
106
111
|
}
|
|
107
|
-
//
|
|
108
|
-
//
|
|
112
|
+
// Adoption is the provider-admission boundary. A kill that committed first
|
|
113
|
+
// settles without constructing a provider execution.
|
|
109
114
|
if (current.killIntent) {
|
|
110
115
|
const killed = store.runnerKilledIfRequested({ executionId: input.executionId });
|
|
111
116
|
if (killed.status !== "committed")
|
|
@@ -113,8 +118,21 @@ export async function runProjectionGeneration(input) {
|
|
|
113
118
|
settled = true;
|
|
114
119
|
return { status: "completed" };
|
|
115
120
|
}
|
|
116
|
-
const
|
|
117
|
-
|
|
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();
|
|
118
136
|
const settleInterrupted = (facts = {}) => {
|
|
119
137
|
const transition = store.runnerInterruptedIfRequested({
|
|
120
138
|
executionId: input.executionId,
|
|
@@ -319,39 +337,40 @@ export async function runProjectionGeneration(input) {
|
|
|
319
337
|
const tellFence = normalizeTellFence(pending.tellIds);
|
|
320
338
|
if (tellFence.length > 0) {
|
|
321
339
|
const successorExecutionId = createProjectionExecutionId(now());
|
|
322
|
-
const
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
340
|
+
const successorFacts = successorLaunchFacts(current.launch, execution.facts, pending.effort, now);
|
|
341
|
+
let transitionReason;
|
|
342
|
+
if (!input.startSuccessor)
|
|
343
|
+
return { status: "rejected" };
|
|
344
|
+
try {
|
|
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;
|
|
362
|
+
}
|
|
363
|
+
catch {
|
|
364
|
+
if (settled)
|
|
365
|
+
return { status: "handed-off", successorExecutionId };
|
|
366
|
+
return settleRunnerOutcome(execution);
|
|
367
|
+
}
|
|
368
|
+
if (!settled) {
|
|
369
|
+
if (transitionReason !== "kill-requested" && transitionReason !== "interrupt-requested") {
|
|
331
370
|
return { status: "rejected" };
|
|
332
371
|
}
|
|
333
372
|
}
|
|
334
373
|
else {
|
|
335
|
-
settled = true;
|
|
336
|
-
// Handoff makes the predecessor terminal before life authority moves.
|
|
337
|
-
// A held projection lock must always mean that a current runner exists,
|
|
338
|
-
// so release it before the successor makes its one-shot acquisition.
|
|
339
|
-
runnerLock.close();
|
|
340
|
-
runnerLock = undefined;
|
|
341
|
-
try {
|
|
342
|
-
input.spawnSuccessor?.(successorExecutionId);
|
|
343
|
-
}
|
|
344
|
-
catch (error) {
|
|
345
|
-
store.verdictIfOpen({
|
|
346
|
-
executionId: successorExecutionId,
|
|
347
|
-
verdict: "launch-failed",
|
|
348
|
-
facts: {
|
|
349
|
-
stage: "spawn",
|
|
350
|
-
detail: `successor runner spawn failed: ${errorDetail(error)}`,
|
|
351
|
-
terminalAt: new Date(now()).toISOString(),
|
|
352
|
-
},
|
|
353
|
-
});
|
|
354
|
-
}
|
|
355
374
|
return { status: "handed-off", successorExecutionId };
|
|
356
375
|
}
|
|
357
376
|
}
|
|
@@ -359,20 +378,37 @@ export async function runProjectionGeneration(input) {
|
|
|
359
378
|
}
|
|
360
379
|
catch (error) {
|
|
361
380
|
if (adopted && !settled) {
|
|
362
|
-
|
|
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({
|
|
363
393
|
executionId: input.executionId,
|
|
364
394
|
verdict: "failed",
|
|
365
395
|
facts: {
|
|
366
396
|
failedAt: new Date(now()).toISOString(),
|
|
367
|
-
detail
|
|
397
|
+
detail,
|
|
398
|
+
...(cleanupError ? {
|
|
399
|
+
...(live?.killIntent ? { operatorAction: "kill" } : {}),
|
|
400
|
+
diagnostic: "cleanup-escalation-failed",
|
|
401
|
+
cleanupDetail: errorDetail(cleanupError),
|
|
402
|
+
} : {}),
|
|
368
403
|
error: {
|
|
369
404
|
version: OUTCOME_ERROR_VERSION,
|
|
370
405
|
code: "SUBAGENT_EXEC_ERROR",
|
|
371
|
-
message:
|
|
406
|
+
message: detail,
|
|
372
407
|
timeoutMs: null,
|
|
373
408
|
exitCode: null,
|
|
374
409
|
},
|
|
375
410
|
},
|
|
411
|
+
terminalAt: new Date(now()).toISOString(),
|
|
376
412
|
});
|
|
377
413
|
}
|
|
378
414
|
throw error;
|
|
@@ -1,34 +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
|
-
import {
|
|
7
|
+
import { persistInterruptWakeFailure, tellProjection } from "../projection-wake.js";
|
|
6
8
|
import { readProjectionIdentity } from "../projection-identity.js";
|
|
7
|
-
import { FlowError } from "../../../flow-error.js";
|
|
8
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);
|
|
43
|
+
});
|
|
44
|
+
}
|
|
9
45
|
export async function wakeProjectionAfterInterrupt(projectionDirectory, wake = tellProjection) {
|
|
10
46
|
const pact = readProjectionIdentity(projectionDirectory);
|
|
11
47
|
try {
|
|
12
48
|
await wake(projectionDirectory, `${pact.akuma}/${path.basename(projectionDirectory)}`, undefined);
|
|
13
49
|
}
|
|
14
50
|
catch (error) {
|
|
15
|
-
|
|
16
|
-
&& error.facts?.kind === "projection_wake_failure"
|
|
17
|
-
? error.facts.cause
|
|
18
|
-
: "spawn";
|
|
19
|
-
recordInterruptWakeFailure(projectionDirectory, cause);
|
|
51
|
+
persistInterruptWakeFailure(projectionDirectory, error);
|
|
20
52
|
throw error;
|
|
21
53
|
}
|
|
22
54
|
}
|
|
23
55
|
export async function runProjectionGenerationRuntime(projectionDirectory, executionId) {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
|
|
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
|
+
}
|
|
34
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,6 +50,9 @@ 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
|
}
|
|
@@ -67,6 +80,9 @@ export class ProjectionGenerationStore {
|
|
|
67
80
|
supersedeStartupTimeout(input) {
|
|
68
81
|
return transitions.supersedeStartupTimeout(this.#database, this.readOnly, input);
|
|
69
82
|
}
|
|
83
|
+
settleStartupFailure(input) {
|
|
84
|
+
return transitions.settleStartupFailure(this.#database, this.readOnly, input);
|
|
85
|
+
}
|
|
70
86
|
replaceDeadRunner(input) {
|
|
71
87
|
return transitions.replaceDeadRunner(this.#database, this.readOnly, input);
|
|
72
88
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { appendRecord, readValidatedHistory, writeTransition, } from "./database.js";
|
|
2
|
-
import { ProjectionGenerationStoreError, VERDICTS, assertExecutionId, assertIsoTimestamp,
|
|
2
|
+
import { ProjectionGenerationStoreError, VERDICTS, assertExecutionId, assertIsoTimestamp, cloneFacts, validateAdoptionFacts, validateInterruptIntentFacts, validateKillIntentFacts, validateLaunchFacts, validateTellFence, validateVerdictFacts, } from "./model.js";
|
|
3
3
|
function committed(records) {
|
|
4
4
|
return { status: "committed", records };
|
|
5
5
|
}
|
|
@@ -20,6 +20,38 @@ export function launchIfSettled(database, readOnly, input) {
|
|
|
20
20
|
return committed([appendRecord(database, "launch", executionId, facts, history.records.length + 1)]);
|
|
21
21
|
});
|
|
22
22
|
}
|
|
23
|
+
/** Persist an asynchronous interrupt wake rejection as one closed successor. */
|
|
24
|
+
export function failInterruptWakeIfSettled(database, readOnly, input) {
|
|
25
|
+
const predecessorExecutionId = assertExecutionId(input.predecessorExecutionId);
|
|
26
|
+
const successorExecutionId = assertExecutionId(input.successorExecutionId);
|
|
27
|
+
const successorFacts = validateLaunchFacts(input.successorFacts, "interrupt wake successor launch facts");
|
|
28
|
+
const terminalAt = assertIsoTimestamp(input.terminalAt, "terminalAt");
|
|
29
|
+
return writeTransition(database, readOnly, () => {
|
|
30
|
+
const history = readValidatedHistory(database);
|
|
31
|
+
const current = history.current;
|
|
32
|
+
if (!current)
|
|
33
|
+
return rejected("no-current-launch", null);
|
|
34
|
+
if (current.launch.executionId !== predecessorExecutionId)
|
|
35
|
+
return rejected("not-current", current);
|
|
36
|
+
if (current.verdict?.facts.state !== "interrupted") {
|
|
37
|
+
return rejected(current.verdict ? "already-terminal" : "current-generation-open", current);
|
|
38
|
+
}
|
|
39
|
+
if (history.records.some((record) => record.kind === "launch" && record.executionId === successorExecutionId)) {
|
|
40
|
+
return rejected("execution-id-already-used", current);
|
|
41
|
+
}
|
|
42
|
+
const verdictFacts = validateVerdictFacts({
|
|
43
|
+
state: "launch-failed",
|
|
44
|
+
cause: input.cause,
|
|
45
|
+
tellRetained: true,
|
|
46
|
+
terminalAt,
|
|
47
|
+
}, "interrupt wake failure verdict facts");
|
|
48
|
+
const firstSeq = history.records.length + 1;
|
|
49
|
+
return committed([
|
|
50
|
+
appendRecord(database, "launch", successorExecutionId, successorFacts, firstSeq),
|
|
51
|
+
appendRecord(database, "verdict", successorExecutionId, verdictFacts, firstSeq + 1),
|
|
52
|
+
]);
|
|
53
|
+
});
|
|
54
|
+
}
|
|
23
55
|
export function adoptionIfCurrent(database, readOnly, input) {
|
|
24
56
|
const executionId = assertExecutionId(input.executionId);
|
|
25
57
|
const facts = validateAdoptionFacts(input.facts, "adoption facts");
|
|
@@ -240,15 +272,15 @@ export function verdictIfOpen(database, readOnly, input) {
|
|
|
240
272
|
return rejected("not-current", current);
|
|
241
273
|
if (current.verdict)
|
|
242
274
|
return rejected("already-terminal", current);
|
|
275
|
+
if (input.verdict === "launch-failed" && current.adoption) {
|
|
276
|
+
return rejected("already-adopted", current);
|
|
277
|
+
}
|
|
243
278
|
if (current.killIntent) {
|
|
244
279
|
const killEscalationFailed = input.verdict === "failed"
|
|
245
280
|
&& input.facts?.operatorAction === "kill";
|
|
246
281
|
if (!killEscalationFailed)
|
|
247
282
|
return rejected("kill-requested", current);
|
|
248
283
|
}
|
|
249
|
-
if (input.verdict === "launch-failed" && current.adoption) {
|
|
250
|
-
return rejected("already-adopted", current);
|
|
251
|
-
}
|
|
252
284
|
const facts = validateVerdictFacts({ ...(input.facts ?? {}), state: input.verdict, ...(input.terminalAt ? { terminalAt: assertIsoTimestamp(input.terminalAt, "terminalAt") } : {}) }, "verdict facts");
|
|
253
285
|
return committed([appendRecord(database, "verdict", executionId, facts, history.records.length + 1)]);
|
|
254
286
|
});
|
|
@@ -256,8 +288,10 @@ export function verdictIfOpen(database, readOnly, input) {
|
|
|
256
288
|
export function supersedeStartupTimeout(database, readOnly, input) {
|
|
257
289
|
const executionId = assertExecutionId(input.executionId);
|
|
258
290
|
const successorExecutionId = assertExecutionId(input.successorExecutionId);
|
|
259
|
-
const
|
|
260
|
-
|
|
291
|
+
const releasedLockEvidence = cloneFacts(input.releasedLockEvidence, "released startup-lock evidence");
|
|
292
|
+
if (Object.keys(releasedLockEvidence).length === 0) {
|
|
293
|
+
throw new ProjectionGenerationStoreError("released startup-lock evidence must not be empty");
|
|
294
|
+
}
|
|
261
295
|
const diagnostic = cloneFacts(input.diagnostic, "startup-timeout diagnostic");
|
|
262
296
|
const successorFacts = validateLaunchFacts(input.successorFacts, "successor launch facts");
|
|
263
297
|
const terminalAt = input.terminalAt === undefined
|
|
@@ -272,19 +306,24 @@ export function supersedeStartupTimeout(database, readOnly, input) {
|
|
|
272
306
|
return rejected("not-current", current);
|
|
273
307
|
if (current.verdict)
|
|
274
308
|
return rejected("already-terminal", current);
|
|
275
|
-
if (current.killIntent)
|
|
276
|
-
return rejected("kill-requested", current);
|
|
277
309
|
if (current.adoption)
|
|
278
310
|
return rejected("already-adopted", current);
|
|
311
|
+
if (current.killIntent) {
|
|
312
|
+
const verdictFacts = validateVerdictFacts({
|
|
313
|
+
state: "killed",
|
|
314
|
+
killIntentSeq: current.killIntent.seq,
|
|
315
|
+
killedAt: current.killIntent.facts.killedAt,
|
|
316
|
+
releasedLockEvidence,
|
|
317
|
+
...(terminalAt === undefined ? {} : { terminalAt }),
|
|
318
|
+
}, "startup-timeout killed verdict facts");
|
|
319
|
+
return committed([appendRecord(database, "verdict", executionId, verdictFacts, history.records.length + 1)]);
|
|
320
|
+
}
|
|
279
321
|
if (history.records.some((record) => record.kind === "launch" && record.executionId === successorExecutionId)) {
|
|
280
322
|
return rejected("execution-id-already-used", current);
|
|
281
323
|
}
|
|
282
|
-
const launchedAtMs = Date.parse(current.launch.facts.createdAt);
|
|
283
|
-
if (observedAtMs - launchedAtMs < startupTimeoutMs) {
|
|
284
|
-
return rejected("startup-timeout-not-elapsed", current);
|
|
285
|
-
}
|
|
286
324
|
const verdictFactsBase = {
|
|
287
325
|
...diagnostic,
|
|
326
|
+
releasedLockEvidence,
|
|
288
327
|
state: "launch-failed",
|
|
289
328
|
...(terminalAt === undefined ? {} : { terminalAt }),
|
|
290
329
|
};
|
|
@@ -296,6 +335,44 @@ export function supersedeStartupTimeout(database, readOnly, input) {
|
|
|
296
335
|
]);
|
|
297
336
|
});
|
|
298
337
|
}
|
|
338
|
+
export function settleStartupFailure(database, readOnly, input) {
|
|
339
|
+
const executionId = assertExecutionId(input.executionId);
|
|
340
|
+
const releasedLockEvidence = cloneFacts(input.releasedLockEvidence, "released startup-lock evidence");
|
|
341
|
+
if (Object.keys(releasedLockEvidence).length === 0) {
|
|
342
|
+
throw new ProjectionGenerationStoreError("released startup-lock evidence must not be empty");
|
|
343
|
+
}
|
|
344
|
+
const diagnostic = cloneFacts(input.diagnostic, "startup failure diagnostic");
|
|
345
|
+
const terminalAt = input.terminalAt === undefined
|
|
346
|
+
? undefined
|
|
347
|
+
: assertIsoTimestamp(input.terminalAt, "terminalAt");
|
|
348
|
+
return writeTransition(database, readOnly, () => {
|
|
349
|
+
const history = readValidatedHistory(database);
|
|
350
|
+
const current = history.current;
|
|
351
|
+
if (!current)
|
|
352
|
+
return rejected("no-current-launch", null);
|
|
353
|
+
if (current.launch.executionId !== executionId)
|
|
354
|
+
return rejected("not-current", current);
|
|
355
|
+
if (current.verdict)
|
|
356
|
+
return rejected("already-terminal", current);
|
|
357
|
+
if (current.adoption)
|
|
358
|
+
return rejected("already-adopted", current);
|
|
359
|
+
const facts = current.killIntent
|
|
360
|
+
? validateVerdictFacts({
|
|
361
|
+
state: "killed",
|
|
362
|
+
killIntentSeq: current.killIntent.seq,
|
|
363
|
+
killedAt: current.killIntent.facts.killedAt,
|
|
364
|
+
releasedLockEvidence,
|
|
365
|
+
...(terminalAt === undefined ? {} : { terminalAt }),
|
|
366
|
+
}, "startup failure killed verdict facts")
|
|
367
|
+
: validateVerdictFacts({
|
|
368
|
+
...diagnostic,
|
|
369
|
+
releasedLockEvidence,
|
|
370
|
+
state: "launch-failed",
|
|
371
|
+
...(terminalAt === undefined ? {} : { terminalAt }),
|
|
372
|
+
}, "startup failure verdict facts");
|
|
373
|
+
return committed([appendRecord(database, "verdict", executionId, facts, history.records.length + 1)]);
|
|
374
|
+
});
|
|
375
|
+
}
|
|
299
376
|
export function replaceDeadRunner(database, readOnly, input) {
|
|
300
377
|
const executionId = assertExecutionId(input.executionId);
|
|
301
378
|
const successorExecutionId = assertExecutionId(input.successorExecutionId);
|
|
@@ -12,8 +12,8 @@ export { readProjectionIdentity } from "./projection-identity.js";
|
|
|
12
12
|
export { interruptProjectionGeneration, killProjectionGeneration } from "./projection-kill.js";
|
|
13
13
|
export { observeProjectionLife, observeProjectionLifeSnapshot, } from "./projection-life-observer.js";
|
|
14
14
|
export { mintProjection, mintRepositoryProjection, ProjectionMintCollisionError, } from "./projection-mint.js";
|
|
15
|
-
export { acquireProjectionRunnerLock, initializeProjectionRunnerLockDatabase, isProjectionRunnerLockPlatformSupported, observeProjectionRunnerLock, projectionRunnerLockPath, } from "./projection-runner-lock.js";
|
|
16
|
-
export { hasStrandableProjectionGeneration, prioritizeProjectionStatusRows,
|
|
15
|
+
export { acquireProjectionRunnerLock, acquireProjectionRunnerSettlementLock, acquireProjectionRunnerTransferTarget, initializeProjectionRunnerLockDatabase, isProjectionRunnerLockPlatformSupported, observeProjectionRunnerLock, projectionRunnerLockPath, } from "./projection-runner-lock.js";
|
|
16
|
+
export { hasStrandableProjectionGeneration, prioritizeProjectionStatusRows, readAkumaNonterminalProjectionStatus, readExactProjectionStatus, readOperationalProjectionStatusBoard, } from "./projection-status.js";
|
|
17
17
|
export { projectTerminalFailure, } from "./projection-terminal-failure.js";
|
|
18
18
|
export { snapshotCommissionProjectionIds, waitForProjection, waitForProjectionSet, } from "./projection-wait.js";
|
|
19
19
|
export { assertTellEffortSupported, assertProjectionTellable, tellProjection, } from "./projection-wake.js";
|
|
@@ -22,6 +22,6 @@ export { createProjectionExecutionId } from "./generation/projection-generation-
|
|
|
22
22
|
export { PROJECTION_ADOPTION_TIMEOUT_MS, startProjectionGeneration, } from "./generation/projection-generation-launcher.js";
|
|
23
23
|
export { projectionGenerationRunnerArgv, spawnProjectionGenerationRunner, } from "./generation/projection-generation-process.js";
|
|
24
24
|
export { runProjectionGeneration, } from "./generation/projection-generation-runner.js";
|
|
25
|
-
export { executeProjectionGeneration } from "./generation/projection-generation-execution.js";
|
|
25
|
+
export { executeProjectionGeneration, prepareProjectionGenerationExecution, } from "./generation/projection-generation-execution.js";
|
|
26
26
|
export { runProjectionGenerationRuntime } from "./generation/projection-generation-runtime.js";
|
|
27
27
|
export { claimFencedTells, claimTellToInflight, listTellIds, markTellConsumed, markTellSubmitted, readPendingTellWindow, readProjectionTellCounts, readTellOriginal, readTellSubmitted, writeInboxTell, } from "./tell/store.js";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { openProjectionGenerationStore } from "./generation/store.js";
|
|
2
|
-
import { observeProjectionRunnerLock } from "./projection-runner-lock.js";
|
|
2
|
+
import { acquireProjectionRunnerSettlementLock, observeProjectionRunnerLock, ProjectionRunnerLockUnavailableError, } from "./projection-runner-lock.js";
|
|
3
3
|
/** The runner observes this durable intent and owns native termination. */
|
|
4
4
|
export function interruptProjectionGeneration(projectionDirectory, nowMs = Date.now()) {
|
|
5
5
|
const store = openProjectionGenerationStore(projectionDirectory);
|
|
@@ -32,18 +32,30 @@ export function interruptProjectionGeneration(projectionDirectory, nowMs = Date.
|
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
34
|
function settleReleasedLeash(store, projectionDirectory, executionId) {
|
|
35
|
-
const
|
|
36
|
-
if (
|
|
35
|
+
const current = store.readCurrentGeneration();
|
|
36
|
+
if (current?.launch.executionId !== executionId
|
|
37
|
+
|| current.verdict) {
|
|
37
38
|
return { status: "accepted", executionId };
|
|
38
39
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
40
|
+
let deathEvidence;
|
|
41
|
+
try {
|
|
42
|
+
deathEvidence = acquireProjectionRunnerSettlementLock(projectionDirectory);
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
if (error instanceof ProjectionRunnerLockUnavailableError) {
|
|
46
|
+
return { status: "accepted", executionId };
|
|
47
|
+
}
|
|
48
|
+
return { status: "accepted", executionId };
|
|
49
|
+
}
|
|
50
|
+
try {
|
|
51
|
+
store.settleKillIfRunnerDead({
|
|
44
52
|
executionId,
|
|
45
|
-
|
|
46
|
-
|
|
53
|
+
releasedLockEvidence: { state: "acquired", executionId },
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
finally {
|
|
57
|
+
deathEvidence.close();
|
|
58
|
+
}
|
|
47
59
|
return { status: "accepted", executionId };
|
|
48
60
|
}
|
|
49
61
|
/**
|