@astrosheep/keiyaku 1.0.0 → 1.0.2

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 (123) hide show
  1. package/README.md +73 -56
  2. package/build/.tsbuildinfo +1 -1
  3. package/build/agents/call-terms_v2.js +103 -0
  4. package/build/agents/harness/activity-values.js +54 -0
  5. package/build/agents/harness/event-channel.js +55 -20
  6. package/build/agents/harness/events.js +47 -1
  7. package/build/agents/harness/execution-handle.js +292 -61
  8. package/build/agents/harness/index.js +103 -121
  9. package/build/agents/harness/pump.js +36 -47
  10. package/build/agents/harness/runtime.js +3 -0
  11. package/build/agents/launch-snapshot_v2.js +348 -0
  12. package/build/agents/opencode-sdk.js +42 -169
  13. package/build/agents/providers/claude-agent-sdk.js +240 -79
  14. package/build/agents/providers/codex-app-server.js +280 -72
  15. package/build/agents/providers/codex-sdk.js +159 -41
  16. package/build/agents/providers/event-type-tail.js +16 -0
  17. package/build/agents/providers/opencode-sdk.js +165 -59
  18. package/build/agents/providers/pi.js +254 -42
  19. package/build/agents/selector_v2.js +122 -0
  20. package/build/cli/commands/akuma.js +120 -42
  21. package/build/cli/completion.js +6 -7
  22. package/build/cli/flags.js +36 -10
  23. package/build/cli/help.js +42 -26
  24. package/build/cli/index.js +225 -93
  25. package/build/cli/parse.js +194 -75
  26. package/build/cli/projection-address.js +11 -0
  27. package/build/cli/render/address.js +88 -0
  28. package/build/cli/render/arc.js +10 -10
  29. package/build/cli/render/call.js +59 -0
  30. package/build/cli/render/compact-text.js +3 -0
  31. package/build/cli/render/errors.js +4 -2
  32. package/build/cli/render/format.js +6 -0
  33. package/build/cli/render/line-width.js +70 -0
  34. package/build/cli/render/misc.js +7 -4
  35. package/build/cli/render/petition.js +4 -3
  36. package/build/cli/render/projection-activity.js +201 -0
  37. package/build/cli/render/shared.js +46 -12
  38. package/build/cli/render/status.js +59 -10
  39. package/build/cli/render/tool-ledger-rollup.js +74 -0
  40. package/build/cli/render/tool-presentation.js +97 -0
  41. package/build/cli/render/wait.js +229 -0
  42. package/build/cli/skills-install.js +42 -24
  43. package/build/cli/subagent-guard.js +1 -1
  44. package/build/config/akuma-loader_v2.js +222 -0
  45. package/build/config/env-keys.js +0 -4
  46. package/build/config/env.js +0 -6
  47. package/build/config/settings.js +325 -12
  48. package/build/core/addressing.js +224 -0
  49. package/build/core/amend.js +30 -47
  50. package/build/core/arc.js +68 -60
  51. package/build/core/bind.js +105 -25
  52. package/build/core/call-persist.js +69 -0
  53. package/build/core/call.js +658 -0
  54. package/build/core/claim.js +72 -44
  55. package/build/core/command-io.js +19 -19
  56. package/build/core/context.js +11 -36
  57. package/build/core/draft.js +2 -3
  58. package/build/core/entry.js +101 -6
  59. package/build/core/execution-coordinate.js +62 -0
  60. package/build/core/execution-pact.js +141 -0
  61. package/build/core/forfeit.js +2 -3
  62. package/build/core/hints.js +10 -10
  63. package/build/core/ids.js +6 -0
  64. package/build/core/ledger.js +16 -1
  65. package/build/core/petition-claim-gates.js +14 -12
  66. package/build/core/petition-claim.js +1 -0
  67. package/build/core/petition-run.js +6 -2
  68. package/build/core/petition.js +63 -21
  69. package/build/core/places.js +106 -13
  70. package/build/core/projection/generation.js +412 -0
  71. package/build/core/projection/heart.js +316 -0
  72. package/build/core/projection/identity.js +81 -0
  73. package/build/core/projection/leash.js +87 -0
  74. package/build/core/projection/mint.js +154 -0
  75. package/build/core/projection-activity.js +219 -0
  76. package/build/core/projection-coordinate.js +35 -0
  77. package/build/core/projection-core.js +69 -463
  78. package/build/core/projection-generation-continuation.js +31 -0
  79. package/build/core/projection-generation-execution.js +148 -0
  80. package/build/core/projection-generation-identity.js +17 -0
  81. package/build/core/projection-generation-launcher.js +90 -0
  82. package/build/core/projection-generation-process.js +43 -0
  83. package/build/core/projection-generation-runner.js +144 -0
  84. package/build/core/projection-generation-runtime.js +15 -0
  85. package/build/core/projection-generation-store.js +707 -0
  86. package/build/core/projection-identity.js +11 -0
  87. package/build/core/projection-life-observer.js +87 -0
  88. package/build/core/projection-life-protocol.js +95 -0
  89. package/build/core/projection-mint.js +96 -24
  90. package/build/core/projection-runner-lock.js +230 -0
  91. package/build/core/projection-status.js +207 -107
  92. package/build/core/projection-tells.js +401 -0
  93. package/build/core/projection-wait.js +155 -0
  94. package/build/core/projection-wake.js +109 -231
  95. package/build/core/queue_v2.js +342 -0
  96. package/build/core/registry.js +6 -3
  97. package/build/core/render.js +63 -1
  98. package/build/core/renew.js +80 -53
  99. package/build/core/scope.js +186 -137
  100. package/build/core/seal.js +16 -19
  101. package/build/core/status.js +88 -25
  102. package/build/core/stored-agent-event.js +101 -0
  103. package/build/core/transcripts.js +214 -228
  104. package/build/core/verdict.js +27 -15
  105. package/build/core/worktree-bootstrap.js +187 -0
  106. package/build/core/worktree-path.js +72 -18
  107. package/build/flow-error.js +4 -6
  108. package/build/generated/version.js +1 -1
  109. package/build/git/branches.js +6 -1
  110. package/build/index.js +8 -5
  111. package/build/keiyaku.js +1 -3
  112. package/package.json +6 -5
  113. package/skills/keiyaku/SKILL.md +6 -47
  114. package/skills/keiyaku-akuma/SKILL.md +75 -0
  115. package/build/agents/effective-policy.js +0 -32
  116. package/build/agents/index.js +0 -114
  117. package/build/agents/selector.js +0 -28
  118. package/build/cli/render/summon.js +0 -17
  119. package/build/cli/render/tell.js +0 -11
  120. package/build/config/akuma-loader.js +0 -268
  121. package/build/core/queue.js +0 -73
  122. package/build/core/summon-persist.js +0 -73
  123. package/build/core/summon.js +0 -377
@@ -0,0 +1,148 @@
1
+ import { initializeConfig } from "../config/env.js";
2
+ import { buildCallTermsV2 } from "../agents/call-terms_v2.js";
3
+ import { parseResolvedAkumaLaunchSnapshot, } from "../agents/launch-snapshot_v2.js";
4
+ import { revive, call } from "../agents/harness/index.js";
5
+ import { readProjectionEvents } from "./projection-core.js";
6
+ import { readProjectionIdentity } from "./projection-identity.js";
7
+ import { selectValidStoredAgentEvents } from "./stored-agent-event.js";
8
+ import { persistResponseHistory } from "./transcripts.js";
9
+ function isObject(value) {
10
+ return value !== null && typeof value === "object" && !Array.isArray(value);
11
+ }
12
+ function requireString(value, field) {
13
+ if (typeof value !== "string" || value.trim() === "") {
14
+ throw new Error(`generation launch ${field} must be a non-blank string`);
15
+ }
16
+ return value;
17
+ }
18
+ function optionalString(value, field) {
19
+ return value === undefined ? undefined : requireString(value, field);
20
+ }
21
+ function requirePositiveSafeInteger(value, field) {
22
+ if (typeof value !== "number" || !Number.isSafeInteger(value) || value <= 0) {
23
+ throw new Error(`generation launch ${field} must be a positive safe integer`);
24
+ }
25
+ return value;
26
+ }
27
+ function parseLaunchInputs(launch) {
28
+ const raw = launch.facts.inputs;
29
+ if (!isObject(raw))
30
+ throw new Error("generation launch inputs must be an object");
31
+ let launchSnapshot;
32
+ try {
33
+ launchSnapshot = parseResolvedAkumaLaunchSnapshot(raw.launchSnapshot);
34
+ }
35
+ catch (error) {
36
+ throw new Error(`generation launch launchSnapshot is invalid: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
37
+ }
38
+ if (raw.persistProjectionResponse !== true && raw.persistProjectionResponse !== false) {
39
+ throw new Error("generation launch persistProjectionResponse must be boolean");
40
+ }
41
+ if (raw.session !== undefined && !isObject(raw.session)) {
42
+ throw new Error("generation launch session must be an object");
43
+ }
44
+ const evidenceCwd = optionalString(raw.evidenceCwd, "evidenceCwd");
45
+ const title = optionalString(raw.title, "title");
46
+ const context = optionalString(raw.context, "context");
47
+ const resumePath = optionalString(raw.resumePath, "resumePath");
48
+ return {
49
+ prompt: requireString(raw.prompt, "prompt"),
50
+ cwd: requireString(raw.cwd, "cwd"),
51
+ task: requireString(raw.task, "task"),
52
+ historyCwd: requireString(raw.historyCwd, "historyCwd"),
53
+ ...(evidenceCwd ? { evidenceCwd } : {}),
54
+ ...(title ? { title } : {}),
55
+ ...(context ? { context } : {}),
56
+ launchSnapshot,
57
+ idleTimeoutMs: requirePositiveSafeInteger(raw.idleTimeoutMs, "idleTimeoutMs"),
58
+ persistProjectionResponse: raw.persistProjectionResponse,
59
+ ...(raw.session ? { session: raw.session } : {}),
60
+ ...(resumePath ? { resumePath } : {}),
61
+ };
62
+ }
63
+ function persistenceError(error) {
64
+ return (error instanceof Error ? error.message : String(error))
65
+ .replace(/[\r\n]+/g, " ")
66
+ .trim() || "response persistence failed";
67
+ }
68
+ function responseTitle(executionId, title) {
69
+ return `${executionId.slice(-16)}-${title ?? "call"}-${executionId}`;
70
+ }
71
+ function asJsonObject(value) {
72
+ return JSON.parse(JSON.stringify(value));
73
+ }
74
+ function maximumValidEnvelopeTurn(projectionDirectory) {
75
+ const { events } = selectValidStoredAgentEvents(readProjectionEvents(projectionDirectory));
76
+ let maximum = 0;
77
+ for (const event of events) {
78
+ if (event.turn > maximum)
79
+ maximum = event.turn;
80
+ }
81
+ return maximum;
82
+ }
83
+ async function completionFacts(projectionDirectory, executionId, inputs, outcome) {
84
+ let persistence = {};
85
+ if (inputs.persistProjectionResponse) {
86
+ try {
87
+ if (!inputs.evidenceCwd) {
88
+ throw new Error("projection response persistence is missing evidenceCwd");
89
+ }
90
+ const pact = readProjectionIdentity(projectionDirectory);
91
+ persistence = await persistResponseHistory({
92
+ tool: "call",
93
+ cwd: inputs.historyCwd,
94
+ provenance: {
95
+ authority: pact.authority,
96
+ binding: pact.binding,
97
+ evidenceCwd: inputs.evidenceCwd,
98
+ },
99
+ task: inputs.task,
100
+ response: outcome.finalMessage,
101
+ session: outcome.session,
102
+ launchSnapshot: inputs.launchSnapshot,
103
+ effectivePolicy: inputs.launchSnapshot.akuma.policy,
104
+ subagentName: inputs.launchSnapshot.akuma.name,
105
+ title: responseTitle(executionId, inputs.title),
106
+ ...(inputs.context ? { context: inputs.context } : {}),
107
+ });
108
+ }
109
+ catch (error) {
110
+ persistence = { responsePersistError: persistenceError(error) };
111
+ }
112
+ }
113
+ return asJsonObject({
114
+ completedAt: new Date().toISOString(),
115
+ finalMessage: outcome.finalMessage,
116
+ output: outcome.output,
117
+ diagnostics: outcome.diagnostics,
118
+ session: outcome.session,
119
+ warnings: outcome.warnings,
120
+ launchSnapshot: inputs.launchSnapshot,
121
+ effectivePolicy: inputs.launchSnapshot.akuma.policy,
122
+ ...(outcome.error ? { error: outcome.error } : {}),
123
+ ...persistence,
124
+ });
125
+ }
126
+ export async function executeProjectionGeneration(projectionDirectory, launch) {
127
+ const inputs = parseLaunchInputs(launch);
128
+ initializeConfig(process.env, inputs.cwd);
129
+ const terms = buildCallTermsV2(inputs.launchSnapshot, inputs.prompt, inputs.cwd, {
130
+ idleTimeoutMs: inputs.idleTimeoutMs,
131
+ projectionDir: projectionDirectory,
132
+ projectionExecutionId: launch.executionId,
133
+ projectionTellFence: launch.facts.tellFence ?? [],
134
+ initialTurn: maximumValidEnvelopeTurn(projectionDirectory),
135
+ });
136
+ const projection = inputs.session && inputs.resumePath
137
+ ? revive({ ...terms, session: inputs.session, resumePath: inputs.resumePath })
138
+ : call(terms);
139
+ const outcome = await projection.outcome;
140
+ return {
141
+ verdict: outcome.status === "completed"
142
+ ? "completed"
143
+ : outcome.status === "dismissed"
144
+ ? "dismissed"
145
+ : "failed",
146
+ facts: await completionFacts(projectionDirectory, launch.executionId, inputs, outcome),
147
+ };
148
+ }
@@ -0,0 +1,17 @@
1
+ import { randomBytes } from "node:crypto";
2
+ import * as path from "node:path";
3
+ import { ProjectionStateError } from "./atomic-publish.js";
4
+ import { createUlid } from "./ids.js";
5
+ const EXECUTION_ID_PATTERN = /^[0-9A-HJKMNP-TV-Z]{26}$/;
6
+ export function createProjectionExecutionId(nowMs = Date.now()) {
7
+ return createUlid(nowMs, [...randomBytes(10)]);
8
+ }
9
+ export function assertProjectionExecutionId(executionId) {
10
+ if (!EXECUTION_ID_PATTERN.test(executionId)) {
11
+ throw new ProjectionStateError(`invalid projection execution id: ${executionId}`);
12
+ }
13
+ }
14
+ export function executionStdioLogPath(projectionDirectory, executionId) {
15
+ assertProjectionExecutionId(executionId);
16
+ return path.join(projectionDirectory, `${executionId}.stdio.log`);
17
+ }
@@ -0,0 +1,90 @@
1
+ import { setTimeout as delay } from "node:timers/promises";
2
+ import { openProjectionGenerationStore } from "./projection-generation-store.js";
3
+ import { spawnProjectionGenerationRunner, } from "./projection-generation-process.js";
4
+ export const PROJECTION_ADOPTION_TIMEOUT_MS = 4_000;
5
+ export const PROJECTION_ADOPTION_POLL_MS = 20;
6
+ function errorDetail(error) {
7
+ return error instanceof Error ? error.message : String(error);
8
+ }
9
+ function recordLaunchFailure(projectionDirectory, executionId, detail, stage) {
10
+ const store = openProjectionGenerationStore(projectionDirectory);
11
+ try {
12
+ return store.verdictIfOpen({
13
+ executionId,
14
+ verdict: "launch-failed",
15
+ facts: { stage, detail },
16
+ }).status === "committed";
17
+ }
18
+ finally {
19
+ store.close();
20
+ }
21
+ }
22
+ /** Spawn one committed launch and return only after its exact adoption row. */
23
+ export async function startProjectionGeneration(projectionDirectory, executionId, dependencies = {}) {
24
+ const now = dependencies.now ?? Date.now;
25
+ const sleep = dependencies.sleep ?? ((milliseconds) => delay(milliseconds));
26
+ let child;
27
+ try {
28
+ child = spawnProjectionGenerationRunner(projectionDirectory, executionId, dependencies);
29
+ }
30
+ catch (error) {
31
+ const detail = `runner spawn failed: ${errorDetail(error)}`;
32
+ recordLaunchFailure(projectionDirectory, executionId, detail, "spawn");
33
+ return { status: "failed", detail };
34
+ }
35
+ const deadline = now() + PROJECTION_ADOPTION_TIMEOUT_MS;
36
+ for (;;) {
37
+ const observedAt = now();
38
+ const store = openProjectionGenerationStore(projectionDirectory);
39
+ try {
40
+ const current = store.readCurrentGeneration();
41
+ if (current?.launch.executionId !== executionId) {
42
+ return {
43
+ status: "failed",
44
+ detail: "launch stopped being current before adoption",
45
+ };
46
+ }
47
+ if (current.adoption)
48
+ return { status: "adopted", child };
49
+ if (current.verdict) {
50
+ return {
51
+ status: "failed",
52
+ detail: typeof current.verdict.facts.detail === "string"
53
+ ? current.verdict.facts.detail
54
+ : `launch ended as ${String(current.verdict.facts.state)}`,
55
+ };
56
+ }
57
+ }
58
+ finally {
59
+ store.close();
60
+ }
61
+ if (observedAt >= deadline)
62
+ break;
63
+ await sleep(Math.max(0, Math.min(PROJECTION_ADOPTION_POLL_MS, deadline - observedAt)));
64
+ }
65
+ const store = openProjectionGenerationStore(projectionDirectory);
66
+ try {
67
+ const current = store.readCurrentGeneration();
68
+ if (current?.launch.executionId === executionId && current.adoption) {
69
+ return { status: "adopted", child };
70
+ }
71
+ }
72
+ finally {
73
+ store.close();
74
+ }
75
+ const detail = "runner did not commit adoption before startup timeout";
76
+ if (!recordLaunchFailure(projectionDirectory, executionId, detail, "adoption")) {
77
+ const raced = openProjectionGenerationStore(projectionDirectory);
78
+ try {
79
+ const current = raced.readCurrentGeneration();
80
+ if (current?.launch.executionId === executionId && current.adoption) {
81
+ return { status: "adopted", child };
82
+ }
83
+ }
84
+ finally {
85
+ raced.close();
86
+ }
87
+ }
88
+ child.kill("SIGTERM");
89
+ return { status: "failed", detail };
90
+ }
@@ -0,0 +1,43 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+ import { spawn as nodeSpawn } from "node:child_process";
4
+ import { fileURLToPath } from "node:url";
5
+ import { executionStdioLogPath } from "./projection-generation-identity.js";
6
+ const MODULE_FILE = fileURLToPath(import.meta.url);
7
+ const PACKAGE_ROOT = path.resolve(path.dirname(MODULE_FILE), "..", "..");
8
+ const RUNNER_ENTRY = MODULE_FILE.endsWith(".ts")
9
+ ? [process.execPath, "--import", "tsx", path.join(PACKAGE_ROOT, "src", "index.ts")]
10
+ : [process.execPath, path.join(PACKAGE_ROOT, "build", "index.js")];
11
+ export function projectionGenerationRunnerArgv(projectionDirectory, executionId) {
12
+ return [
13
+ ...RUNNER_ENTRY,
14
+ "__keiyaku-wake",
15
+ path.resolve(projectionDirectory),
16
+ executionId,
17
+ ];
18
+ }
19
+ export function spawnProjectionGenerationRunner(projectionDirectory, executionId, dependencies = {}) {
20
+ const spawn = dependencies.spawn ?? nodeSpawn;
21
+ const openSync = dependencies.openSync ?? fs.openSync;
22
+ const closeSync = dependencies.closeSync ?? fs.closeSync;
23
+ const absoluteDirectory = path.resolve(projectionDirectory);
24
+ const [command, ...args] = projectionGenerationRunnerArgv(absoluteDirectory, executionId);
25
+ if (!command)
26
+ throw new Error("projection runner command is empty");
27
+ const logPath = executionStdioLogPath(absoluteDirectory, executionId);
28
+ const logFd = openSync(logPath, "a");
29
+ try {
30
+ const child = spawn(command, args, {
31
+ detached: true,
32
+ stdio: ["ignore", logFd, logFd],
33
+ });
34
+ if (child.pid === undefined) {
35
+ throw new Error("projection runner did not expose a process id");
36
+ }
37
+ child.unref();
38
+ return child;
39
+ }
40
+ finally {
41
+ closeSync(logFd);
42
+ }
43
+ }
@@ -0,0 +1,144 @@
1
+ import { openProjectionGenerationStore, } from "./projection-generation-store.js";
2
+ import { acquireProjectionRunnerLock, observeProjectionRunnerLock, } from "./projection-runner-lock.js";
3
+ import { createProjectionExecutionId } from "./projection-generation-identity.js";
4
+ import { applyContinuationEffort, } from "./projection-generation-continuation.js";
5
+ function successorLaunchFacts(launch, completionFacts, effort, now) {
6
+ const { tellFence: _tellFence, ...inherited } = launch.facts;
7
+ const rawInputs = launch.facts.inputs;
8
+ const inputs = rawInputs
9
+ && typeof rawInputs === "object"
10
+ && !Array.isArray(rawInputs)
11
+ ? rawInputs
12
+ : {};
13
+ return {
14
+ ...inherited,
15
+ createdAt: new Date(now()).toISOString(),
16
+ launchedBy: "sweep-wake",
17
+ parentExecutionId: launch.executionId,
18
+ inputs: applyContinuationEffort({
19
+ ...inputs,
20
+ ...(completionFacts.session ? { session: completionFacts.session } : {}),
21
+ ...(typeof completionFacts.responsePath === "string"
22
+ ? { resumePath: completionFacts.responsePath }
23
+ : {}),
24
+ }, effort),
25
+ };
26
+ }
27
+ function normalizeTellFence(tellIds) {
28
+ return [...new Set(tellIds)].sort();
29
+ }
30
+ function errorDetail(error) {
31
+ return error instanceof Error ? error.message : String(error);
32
+ }
33
+ function acquireRunnerLockOrObserveDuplicate(projectionDirectory) {
34
+ try {
35
+ return acquireProjectionRunnerLock(projectionDirectory);
36
+ }
37
+ catch (error) {
38
+ if (observeProjectionRunnerLock(projectionDirectory).state === "held") {
39
+ return undefined;
40
+ }
41
+ throw error;
42
+ }
43
+ }
44
+ /**
45
+ * Own one exact generation from kernel-lock acquisition through its terminal
46
+ * transaction. Provider execution is injected so this actor never depends on
47
+ * harness, catalog, renderer, or mailbox storage details.
48
+ */
49
+ export async function runProjectionGeneration(input) {
50
+ const now = input.now ?? Date.now;
51
+ const store = openProjectionGenerationStore(input.projectionDirectory);
52
+ let runnerLock;
53
+ let adopted = false;
54
+ let settled = false;
55
+ try {
56
+ const initial = store.readCurrentGeneration();
57
+ if (initial?.launch.executionId !== input.executionId
58
+ || initial.adoption
59
+ || initial.verdict) {
60
+ return { status: "rejected" };
61
+ }
62
+ runnerLock = acquireRunnerLockOrObserveDuplicate(input.projectionDirectory);
63
+ if (!runnerLock)
64
+ return { status: "duplicate" };
65
+ const adoption = store.adoptionIfCurrent({
66
+ executionId: input.executionId,
67
+ facts: { adoptedAt: new Date(now()).toISOString() },
68
+ });
69
+ if (adoption.status !== "committed")
70
+ return { status: "rejected" };
71
+ adopted = true;
72
+ const current = store.readCurrentGeneration();
73
+ if (current?.launch.executionId !== input.executionId) {
74
+ throw new Error("adopted generation stopped being current");
75
+ }
76
+ const execution = await input.execute(current.launch);
77
+ const pending = execution.verdict === "completed"
78
+ ? input.snapshotPendingTells()
79
+ : { tellIds: [] };
80
+ const tellFence = normalizeTellFence(pending.tellIds);
81
+ if (tellFence.length > 0) {
82
+ const successorExecutionId = createProjectionExecutionId(now());
83
+ const transition = store.handoff({
84
+ executionId: input.executionId,
85
+ completionFacts: execution.facts,
86
+ successorExecutionId,
87
+ successorFacts: successorLaunchFacts(current.launch, execution.facts, pending.effort, now),
88
+ tellFence,
89
+ });
90
+ if (transition.status !== "committed")
91
+ return { status: "rejected" };
92
+ settled = true;
93
+ // Handoff makes the predecessor terminal before life authority moves.
94
+ // A held projection lock must always mean that a current runner exists,
95
+ // so release it before the successor makes its one-shot acquisition.
96
+ runnerLock.close();
97
+ runnerLock = undefined;
98
+ try {
99
+ input.spawnSuccessor?.(successorExecutionId);
100
+ }
101
+ catch (error) {
102
+ store.verdictIfOpen({
103
+ executionId: successorExecutionId,
104
+ verdict: "launch-failed",
105
+ facts: {
106
+ stage: "spawn",
107
+ detail: `successor runner spawn failed: ${errorDetail(error)}`,
108
+ },
109
+ });
110
+ }
111
+ return { status: "handed-off", successorExecutionId };
112
+ }
113
+ const verdict = store.verdictIfOpen({
114
+ executionId: input.executionId,
115
+ verdict: execution.verdict,
116
+ facts: execution.facts,
117
+ });
118
+ if (verdict.status !== "committed")
119
+ return { status: "rejected" };
120
+ settled = true;
121
+ return { status: "completed" };
122
+ }
123
+ catch (error) {
124
+ if (adopted && !settled) {
125
+ store.verdictIfOpen({
126
+ executionId: input.executionId,
127
+ verdict: "failed",
128
+ facts: {
129
+ failedAt: new Date(now()).toISOString(),
130
+ detail: error instanceof Error ? error.message : String(error),
131
+ },
132
+ });
133
+ }
134
+ throw error;
135
+ }
136
+ finally {
137
+ try {
138
+ runnerLock?.close();
139
+ }
140
+ finally {
141
+ store.close();
142
+ }
143
+ }
144
+ }
@@ -0,0 +1,15 @@
1
+ import { executeProjectionGeneration } from "./projection-generation-execution.js";
2
+ import { spawnProjectionGenerationRunner } from "./projection-generation-process.js";
3
+ import { runProjectionGeneration } from "./projection-generation-runner.js";
4
+ import { snapshotPendingTells } from "./projection-generation-continuation.js";
5
+ export async function runProjectionGenerationRuntime(projectionDirectory, executionId) {
6
+ await runProjectionGeneration({
7
+ projectionDirectory,
8
+ executionId,
9
+ execute: (launch) => executeProjectionGeneration(projectionDirectory, launch),
10
+ snapshotPendingTells: () => snapshotPendingTells(projectionDirectory),
11
+ spawnSuccessor: (successorExecutionId) => {
12
+ spawnProjectionGenerationRunner(projectionDirectory, successorExecutionId);
13
+ },
14
+ });
15
+ }