@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,412 @@
1
+ export class ProjectionGenerationError extends Error {
2
+ code = "PROJECTION_GENERATION_ERROR";
3
+ constructor(message, options) {
4
+ super(message, options);
5
+ this.name = "ProjectionGenerationError";
6
+ }
7
+ }
8
+ export const GENERATION_VERDICTS = [
9
+ "completed",
10
+ "failed",
11
+ "launch-failed",
12
+ "lost-runner",
13
+ "dismissed",
14
+ ];
15
+ const VERDICTS = new Set(GENERATION_VERDICTS);
16
+ function isPlainObject(value) {
17
+ if (value === null || typeof value !== "object" || Array.isArray(value))
18
+ return false;
19
+ const prototype = Object.getPrototypeOf(value);
20
+ return prototype === Object.prototype || prototype === null;
21
+ }
22
+ function assertJsonValue(value, location, ancestors) {
23
+ if (value === null || typeof value === "string" || typeof value === "boolean")
24
+ return;
25
+ if (typeof value === "number") {
26
+ if (!Number.isFinite(value)) {
27
+ throw new ProjectionGenerationError(`${location} contains a non-finite number`);
28
+ }
29
+ return;
30
+ }
31
+ if (typeof value !== "object") {
32
+ throw new ProjectionGenerationError(`${location} contains a non-JSON value`);
33
+ }
34
+ if (ancestors.has(value)) {
35
+ throw new ProjectionGenerationError(`${location} contains a cycle`);
36
+ }
37
+ ancestors.add(value);
38
+ try {
39
+ if (Array.isArray(value)) {
40
+ value.forEach((item, index) => assertJsonValue(item, `${location}[${index}]`, ancestors));
41
+ return;
42
+ }
43
+ if (!isPlainObject(value)) {
44
+ throw new ProjectionGenerationError(`${location} must contain only plain JSON objects`);
45
+ }
46
+ for (const [key, item] of Object.entries(value)) {
47
+ assertJsonValue(item, `${location}.${key}`, ancestors);
48
+ }
49
+ }
50
+ finally {
51
+ ancestors.delete(value);
52
+ }
53
+ }
54
+ function cloneFacts(value, location) {
55
+ if (!isPlainObject(value)) {
56
+ throw new ProjectionGenerationError(`${location} must be a JSON object`);
57
+ }
58
+ assertJsonValue(value, location, new Set());
59
+ return JSON.parse(JSON.stringify(value));
60
+ }
61
+ function assertExecutionId(executionId) {
62
+ if (typeof executionId !== "string" || executionId.trim() === "") {
63
+ throw new ProjectionGenerationError("executionId must be a non-blank string");
64
+ }
65
+ return executionId;
66
+ }
67
+ function assertNonNegativeSafeInteger(value, location) {
68
+ if (!Number.isSafeInteger(value) || value < 0) {
69
+ throw new ProjectionGenerationError(`${location} must be a non-negative safe integer`);
70
+ }
71
+ return value;
72
+ }
73
+ function assertPositiveSafeInteger(value, location) {
74
+ if (!Number.isSafeInteger(value) || value <= 0) {
75
+ throw new ProjectionGenerationError(`${location} must be a positive safe integer`);
76
+ }
77
+ return value;
78
+ }
79
+ function assertIsoTimestamp(value, location) {
80
+ if (typeof value !== "string" || value.trim() === "") {
81
+ throw new ProjectionGenerationError(`${location} must be an ISO-8601 timestamp`);
82
+ }
83
+ const milliseconds = Date.parse(value);
84
+ if (!Number.isFinite(milliseconds) || new Date(Date.parse(value)).toISOString() !== value) {
85
+ throw new ProjectionGenerationError(`${location} must be a canonical ISO-8601 timestamp`);
86
+ }
87
+ return value;
88
+ }
89
+ function validateTellFence(value, location) {
90
+ if (!Array.isArray(value)) {
91
+ throw new ProjectionGenerationError(`${location} must be an array`);
92
+ }
93
+ const seen = new Set();
94
+ return value.map((tellId, index) => {
95
+ if (typeof tellId !== "string" || tellId.trim() === "") {
96
+ throw new ProjectionGenerationError(`${location}[${index}] must be a non-blank string`);
97
+ }
98
+ if (seen.has(tellId)) {
99
+ throw new ProjectionGenerationError(`${location} contains duplicate tell '${tellId}'`);
100
+ }
101
+ seen.add(tellId);
102
+ return tellId;
103
+ });
104
+ }
105
+ function validateLaunchFacts(value, location) {
106
+ const facts = cloneFacts(value, location);
107
+ facts.createdAt = assertIsoTimestamp(facts.createdAt, `${location}.createdAt`);
108
+ if (facts.tellFence !== undefined) {
109
+ facts.tellFence = validateTellFence(facts.tellFence, `${location}.tellFence`);
110
+ }
111
+ if ("state" in facts) {
112
+ throw new ProjectionGenerationError(`${location}.state is reserved for verdict records`);
113
+ }
114
+ return facts;
115
+ }
116
+ function validateAdoptionFacts(value, location) {
117
+ const facts = cloneFacts(value, location);
118
+ if ("state" in facts) {
119
+ throw new ProjectionGenerationError(`${location}.state is reserved for verdict records`);
120
+ }
121
+ return facts;
122
+ }
123
+ function validateVerdictFacts(value, location) {
124
+ const facts = cloneFacts(value, location);
125
+ if (typeof facts.state !== "string" || !VERDICTS.has(facts.state)) {
126
+ throw new ProjectionGenerationError(`${location}.state is not in the closed verdict vocabulary`);
127
+ }
128
+ return facts;
129
+ }
130
+ function parseStoredRow(row) {
131
+ const seq = typeof row.seq === "bigint" ? Number(row.seq) : row.seq;
132
+ if (!Number.isSafeInteger(seq) || seq <= 0) {
133
+ throw new ProjectionGenerationError(`generation row has invalid seq '${String(row.seq)}'`);
134
+ }
135
+ if (row.kind !== "launch" && row.kind !== "adoption" && row.kind !== "verdict") {
136
+ throw new ProjectionGenerationError(`generation row ${seq} has unknown kind '${row.kind}'`);
137
+ }
138
+ const executionId = assertExecutionId(row.execution_id);
139
+ let decoded;
140
+ try {
141
+ decoded = JSON.parse(row.payload_json);
142
+ }
143
+ catch (error) {
144
+ throw new ProjectionGenerationError(`generation row ${seq} contains malformed facts JSON`, {
145
+ cause: error,
146
+ });
147
+ }
148
+ const location = `generation row ${seq} facts`;
149
+ if (row.kind === "launch") {
150
+ return {
151
+ seq,
152
+ kind: "launch",
153
+ executionId,
154
+ facts: validateLaunchFacts(decoded, location),
155
+ };
156
+ }
157
+ if (row.kind === "adoption") {
158
+ return {
159
+ seq,
160
+ kind: "adoption",
161
+ executionId,
162
+ facts: validateAdoptionFacts(decoded, location),
163
+ };
164
+ }
165
+ return {
166
+ seq,
167
+ kind: "verdict",
168
+ executionId,
169
+ facts: validateVerdictFacts(decoded, location),
170
+ };
171
+ }
172
+ function validateHistory(records) {
173
+ let current = null;
174
+ const launchIds = new Set();
175
+ for (let index = 0; index < records.length; index += 1) {
176
+ const record = records[index];
177
+ const expectedSeq = index + 1;
178
+ if (record.seq !== expectedSeq) {
179
+ throw new ProjectionGenerationError(`generation history is not dense: expected seq ${expectedSeq}, found ${record.seq}`);
180
+ }
181
+ if (record.kind === "launch") {
182
+ if (current && !current.verdict) {
183
+ throw new ProjectionGenerationError(`generation row ${record.seq} launches before execution '${current.launch.executionId}' is terminal`);
184
+ }
185
+ if (launchIds.has(record.executionId)) {
186
+ throw new ProjectionGenerationError(`generation row ${record.seq} reuses executionId '${record.executionId}'`);
187
+ }
188
+ launchIds.add(record.executionId);
189
+ current = { launch: record };
190
+ continue;
191
+ }
192
+ if (!current) {
193
+ throw new ProjectionGenerationError(`generation row ${record.seq} appears before any launch`);
194
+ }
195
+ if (record.executionId !== current.launch.executionId) {
196
+ throw new ProjectionGenerationError(`generation row ${record.seq} does not join the current launch`);
197
+ }
198
+ if (record.kind === "adoption") {
199
+ if (current.adoption) {
200
+ throw new ProjectionGenerationError(`generation row ${record.seq} duplicates adoption`);
201
+ }
202
+ if (current.verdict) {
203
+ throw new ProjectionGenerationError(`generation row ${record.seq} adopts a terminal execution`);
204
+ }
205
+ current.adoption = record;
206
+ continue;
207
+ }
208
+ if (current.verdict) {
209
+ throw new ProjectionGenerationError(`generation row ${record.seq} duplicates verdict`);
210
+ }
211
+ current.verdict = record;
212
+ }
213
+ return current;
214
+ }
215
+ function readValidatedHistory(database) {
216
+ const rows = database.prepare(`
217
+ SELECT seq, kind, execution_id, payload_json
218
+ FROM generation_records
219
+ ORDER BY seq
220
+ `).all();
221
+ const records = rows.map(parseStoredRow);
222
+ return { records, current: validateHistory(records) };
223
+ }
224
+ function appendRecord(database, kind, executionId, facts, seq) {
225
+ const encoded = JSON.stringify(facts);
226
+ database.prepare(`
227
+ INSERT INTO generation_records (seq, kind, execution_id, payload_json)
228
+ VALUES (?, ?, ?, ?)
229
+ `).run(seq, kind, executionId, encoded);
230
+ return parseStoredRow({
231
+ seq,
232
+ kind,
233
+ execution_id: executionId,
234
+ payload_json: encoded,
235
+ });
236
+ }
237
+ function committed(records) {
238
+ return { status: "committed", records };
239
+ }
240
+ function rejected(reason, current) {
241
+ return { status: "rejected", reason, current };
242
+ }
243
+ /** Read every validated generation record in dense seq order. */
244
+ export function readGenerationRecords(heart) {
245
+ return heart.read((database) => readValidatedHistory(database).records);
246
+ }
247
+ /** Fold the validated history into the current open or terminal generation. */
248
+ export function readCurrentGeneration(heart) {
249
+ return heart.read((database) => readValidatedHistory(database).current);
250
+ }
251
+ export function launchIfSettled(heart, input) {
252
+ const executionId = assertExecutionId(input.executionId);
253
+ const facts = validateLaunchFacts(input.facts, "launch facts");
254
+ return heart.write((database) => {
255
+ const history = readValidatedHistory(database);
256
+ if (history.records.some((record) => record.kind === "launch" && record.executionId === executionId)) {
257
+ return rejected("execution-id-already-used", history.current);
258
+ }
259
+ if (history.current && !history.current.verdict) {
260
+ return rejected("current-generation-open", history.current);
261
+ }
262
+ return committed([
263
+ appendRecord(database, "launch", executionId, facts, history.records.length + 1),
264
+ ]);
265
+ });
266
+ }
267
+ export function adoptionIfCurrent(heart, input) {
268
+ const executionId = assertExecutionId(input.executionId);
269
+ const facts = validateAdoptionFacts(input.facts, "adoption facts");
270
+ return heart.write((database) => {
271
+ const history = readValidatedHistory(database);
272
+ const current = history.current;
273
+ if (!current)
274
+ return rejected("no-current-launch", null);
275
+ if (current.launch.executionId !== executionId)
276
+ return rejected("not-current", current);
277
+ if (current.verdict)
278
+ return rejected("already-terminal", current);
279
+ if (current.adoption)
280
+ return rejected("already-adopted", current);
281
+ return committed([
282
+ appendRecord(database, "adoption", executionId, facts, history.records.length + 1),
283
+ ]);
284
+ });
285
+ }
286
+ export function verdictIfOpen(heart, input) {
287
+ const executionId = assertExecutionId(input.executionId);
288
+ if (!VERDICTS.has(input.verdict)) {
289
+ throw new ProjectionGenerationError(`unknown verdict '${String(input.verdict)}'`);
290
+ }
291
+ if (input.facts !== undefined) {
292
+ const supplied = cloneFacts(input.facts, "verdict facts");
293
+ if ("state" in supplied) {
294
+ throw new ProjectionGenerationError("verdict facts.state is reserved");
295
+ }
296
+ }
297
+ const facts = validateVerdictFacts({ ...(input.facts ?? {}), state: input.verdict }, "verdict facts");
298
+ return heart.write((database) => {
299
+ const history = readValidatedHistory(database);
300
+ const current = history.current;
301
+ if (!current)
302
+ return rejected("no-current-launch", null);
303
+ if (current.launch.executionId !== executionId)
304
+ return rejected("not-current", current);
305
+ if (current.verdict)
306
+ return rejected("already-terminal", current);
307
+ if (input.verdict === "launch-failed" && current.adoption) {
308
+ return rejected("already-adopted", current);
309
+ }
310
+ return committed([
311
+ appendRecord(database, "verdict", executionId, facts, history.records.length + 1),
312
+ ]);
313
+ });
314
+ }
315
+ export function supersedeStartupTimeout(heart, input) {
316
+ const executionId = assertExecutionId(input.executionId);
317
+ const successorExecutionId = assertExecutionId(input.successorExecutionId);
318
+ const observedAtMs = assertNonNegativeSafeInteger(input.observedAtMs, "observedAtMs");
319
+ const startupTimeoutMs = assertPositiveSafeInteger(input.startupTimeoutMs, "startupTimeoutMs");
320
+ const diagnostic = cloneFacts(input.diagnostic, "startup-timeout diagnostic");
321
+ const successorFacts = validateLaunchFacts(input.successorFacts, "successor launch facts");
322
+ return heart.write((database) => {
323
+ const history = readValidatedHistory(database);
324
+ const current = history.current;
325
+ if (!current)
326
+ return rejected("no-current-launch", null);
327
+ if (current.launch.executionId !== executionId)
328
+ return rejected("not-current", current);
329
+ if (current.verdict)
330
+ return rejected("already-terminal", current);
331
+ if (current.adoption)
332
+ return rejected("already-adopted", current);
333
+ if (history.records.some((record) => record.kind === "launch" && record.executionId === successorExecutionId)) {
334
+ return rejected("execution-id-already-used", current);
335
+ }
336
+ const launchedAtMs = Date.parse(current.launch.facts.createdAt);
337
+ if (observedAtMs - launchedAtMs < startupTimeoutMs) {
338
+ return rejected("startup-timeout-not-elapsed", current);
339
+ }
340
+ const verdictFacts = validateVerdictFacts({ ...diagnostic, state: "launch-failed" }, "startup-timeout verdict facts");
341
+ const firstSeq = history.records.length + 1;
342
+ return committed([
343
+ appendRecord(database, "verdict", executionId, verdictFacts, firstSeq),
344
+ appendRecord(database, "launch", successorExecutionId, successorFacts, firstSeq + 1),
345
+ ]);
346
+ });
347
+ }
348
+ export function replaceDeadRunner(heart, input) {
349
+ const executionId = assertExecutionId(input.executionId);
350
+ const successorExecutionId = assertExecutionId(input.successorExecutionId);
351
+ const releasedLockEvidence = cloneFacts(input.releasedLockEvidence, "released runner-lock evidence");
352
+ if (Object.keys(releasedLockEvidence).length === 0) {
353
+ throw new ProjectionGenerationError("released runner-lock evidence must not be empty");
354
+ }
355
+ const successorFacts = validateLaunchFacts(input.successorFacts, "dead-runner successor launch facts");
356
+ return heart.write((database) => {
357
+ const history = readValidatedHistory(database);
358
+ const current = history.current;
359
+ if (!current)
360
+ return rejected("no-current-launch", null);
361
+ if (current.launch.executionId !== executionId)
362
+ return rejected("not-current", current);
363
+ if (current.verdict)
364
+ return rejected("already-terminal", current);
365
+ if (!current.adoption)
366
+ return rejected("not-adopted", current);
367
+ if (history.records.some((record) => record.kind === "launch" && record.executionId === successorExecutionId)) {
368
+ return rejected("execution-id-already-used", current);
369
+ }
370
+ const verdictFacts = validateVerdictFacts({ state: "lost-runner", releasedLockEvidence }, "dead-runner verdict facts");
371
+ const firstSeq = history.records.length + 1;
372
+ return committed([
373
+ appendRecord(database, "verdict", executionId, verdictFacts, firstSeq),
374
+ appendRecord(database, "launch", successorExecutionId, successorFacts, firstSeq + 1),
375
+ ]);
376
+ });
377
+ }
378
+ export function handoff(heart, input) {
379
+ const executionId = assertExecutionId(input.executionId);
380
+ const successorExecutionId = assertExecutionId(input.successorExecutionId);
381
+ const completionFacts = cloneFacts(input.completionFacts, "handoff completion facts");
382
+ if ("state" in completionFacts) {
383
+ throw new ProjectionGenerationError("handoff completion facts.state is reserved");
384
+ }
385
+ const successorFactsBase = validateLaunchFacts(input.successorFacts, "handoff successor launch facts");
386
+ if (successorFactsBase.tellFence !== undefined) {
387
+ throw new ProjectionGenerationError("handoff successor launch facts.tellFence must be supplied only through handoff.tellFence");
388
+ }
389
+ const tellFence = Object.freeze(validateTellFence([...input.tellFence], "handoff tellFence"));
390
+ const successorFacts = validateLaunchFacts({ ...successorFactsBase, tellFence: [...tellFence] }, "handoff successor launch facts");
391
+ return heart.write((database) => {
392
+ const history = readValidatedHistory(database);
393
+ const current = history.current;
394
+ if (!current)
395
+ return rejected("no-current-launch", null);
396
+ if (current.launch.executionId !== executionId)
397
+ return rejected("not-current", current);
398
+ if (current.verdict)
399
+ return rejected("already-terminal", current);
400
+ if (!current.adoption)
401
+ return rejected("not-adopted", current);
402
+ if (history.records.some((record) => record.kind === "launch" && record.executionId === successorExecutionId)) {
403
+ return rejected("execution-id-already-used", current);
404
+ }
405
+ const verdictFacts = validateVerdictFacts({ ...completionFacts, state: "completed" }, "handoff verdict facts");
406
+ const firstSeq = history.records.length + 1;
407
+ return committed([
408
+ appendRecord(database, "verdict", executionId, verdictFacts, firstSeq),
409
+ appendRecord(database, "launch", successorExecutionId, successorFacts, firstSeq + 1),
410
+ ]);
411
+ });
412
+ }