@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.
Files changed (87) hide show
  1. package/build/.tsbuildinfo +1 -1
  2. package/build/agents/harness/event-persistence.js +7 -5
  3. package/build/agents/harness/events.js +3 -2
  4. package/build/agents/harness/outcome.js +10 -0
  5. package/build/agents/providers/codex-app-server/adapter.js +6 -1
  6. package/build/agents/providers/codex-app-server/session.js +8 -7
  7. package/build/agents/selector.js +12 -1
  8. package/build/cli/commands/akuma/view/handler.js +3 -11
  9. package/build/cli/commands/contract/amend/handler.js +1 -1
  10. package/build/cli/commands/contract/amend/meta.js +4 -4
  11. package/build/cli/commands/contract/bind/handler.js +10 -4
  12. package/build/cli/commands/contract/bind/meta.js +6 -6
  13. package/build/cli/commands/contract/petition/handler.js +16 -3
  14. package/build/cli/commands/contract/petition/meta.js +4 -4
  15. package/build/cli/commands/metadata.js +3 -2
  16. package/build/cli/commands/projection/status/handler.js +5 -4
  17. package/build/cli/commands/projection/status/meta.js +2 -2
  18. package/build/cli/commands/projection/tell/handler.js +10 -2
  19. package/build/cli/commands/projection/tell/meta.js +3 -3
  20. package/build/cli/commands/task/add/meta.js +9 -1
  21. package/build/cli/commands/task/catalog.js +2 -0
  22. package/build/cli/commands/task/log/handler.js +12 -0
  23. package/build/cli/commands/task/log/meta.js +8 -0
  24. package/build/cli/commands/task/shared.js +2 -1
  25. package/build/cli/completion.js +8 -0
  26. package/build/cli/flags.js +8 -0
  27. package/build/cli/index.js +10 -6
  28. package/build/cli/parse-flags.js +6 -0
  29. package/build/cli/parse-metadata.js +1 -1
  30. package/build/cli/render/kanshi.js +2 -2
  31. package/build/cli/render/line-width.js +33 -0
  32. package/build/cli/render/path-prefix-compaction.js +131 -0
  33. package/build/cli/render/petition.js +4 -0
  34. package/build/cli/render/projection-activity.js +103 -21
  35. package/build/cli/render/shared.js +38 -15
  36. package/build/cli/render/status.js +8 -5
  37. package/build/cli/render/success-response.js +2 -0
  38. package/build/cli/render/tool-presentation.js +3 -3
  39. package/build/cli/render/wait.js +68 -48
  40. package/build/cli/subagent-guard.js +3 -0
  41. package/build/cli/types.js +1 -1
  42. package/build/config/settings/disease.js +4 -4
  43. package/build/config/settings/loader.js +44 -21
  44. package/build/core/addressing.js +40 -9
  45. package/build/core/amend.js +21 -5
  46. package/build/core/bind.js +111 -6
  47. package/build/core/call/context.js +19 -3
  48. package/build/core/call/execution.js +43 -20
  49. package/build/core/draft.js +1 -1
  50. package/build/core/ledger-batch.js +194 -0
  51. package/build/core/projection/generation/database.js +43 -2
  52. package/build/core/projection/generation/model.js +24 -3
  53. package/build/core/projection/generation/projection-generation-continuation.js +75 -10
  54. package/build/core/projection/generation/projection-generation-execution.js +46 -37
  55. package/build/core/projection/generation/projection-generation-launcher.js +148 -20
  56. package/build/core/projection/generation/projection-generation-process.js +3 -1
  57. package/build/core/projection/generation/projection-generation-runner.js +145 -66
  58. package/build/core/projection/generation/projection-generation-runtime.js +93 -11
  59. package/build/core/projection/generation/store.js +26 -1
  60. package/build/core/projection/generation/transitions.js +193 -14
  61. package/build/core/projection/index.js +5 -5
  62. package/build/core/projection/projection-core.js +1 -1
  63. package/build/core/projection/projection-kill.js +53 -10
  64. package/build/core/projection/projection-life-protocol.js +10 -0
  65. package/build/core/projection/projection-runner-lock.js +177 -37
  66. package/build/core/projection/projection-status.js +143 -55
  67. package/build/core/projection/projection-wait.js +53 -15
  68. package/build/core/projection/projection-wake.js +177 -46
  69. package/build/core/projection/tell/database.js +18 -0
  70. package/build/core/projection/tell/model.js +1 -0
  71. package/build/core/projection/tell/store.js +102 -55
  72. package/build/core/registry.js +82 -69
  73. package/build/core/scope.js +9 -9
  74. package/build/core/status/board.js +42 -4
  75. package/build/core/status/drift.js +21 -5
  76. package/build/core/status/ledger-batch.js +1 -158
  77. package/build/core/task/index.js +2 -2
  78. package/build/core/task/task-contract.js +18 -0
  79. package/build/core/task/task-git-runtime.js +8 -10
  80. package/build/core/task/task-git-store.js +55 -3
  81. package/build/core/worktree-path.js +39 -25
  82. package/build/flow-error.js +1 -1
  83. package/build/generated/version.js +2 -2
  84. package/build/git/refs.js +47 -1
  85. package/package.json +1 -1
  86. package/skills/keiyaku-akuma/SKILL.md +28 -0
  87. package/skills/keiyaku-workflow/SKILL.md +142 -18
@@ -1,5 +1,5 @@
1
1
  import { appendRecord, readValidatedHistory, writeTransition, } from "./database.js";
2
- import { ProjectionGenerationStoreError, VERDICTS, assertExecutionId, assertIsoTimestamp, assertNonNegativeSafeInteger, assertPositiveSafeInteger, cloneFacts, validateAdoptionFacts, validateKillIntentFacts, validateLaunchFacts, validateTellFence, validateVerdictFacts, } from "./model.js";
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");
@@ -51,10 +83,32 @@ export function requestKillIfOpen(database, readOnly, input) {
51
83
  return rejected("already-terminal", current);
52
84
  if (current.killIntent)
53
85
  return rejected("kill-requested", current);
86
+ if (current.interruptIntent)
87
+ return rejected("interrupt-requested", current);
54
88
  const facts = validateKillIntentFacts({ operatorAction: "kill", killedAt }, "kill-intent facts");
55
89
  return committed([appendRecord(database, "kill-intent", executionId, facts, history.records.length + 1)]);
56
90
  });
57
91
  }
92
+ export function requestInterruptIfOpen(database, readOnly, input) {
93
+ const executionId = assertExecutionId(input.executionId);
94
+ const interruptedAt = assertIsoTimestamp(input.interruptedAt, "interruptedAt");
95
+ return writeTransition(database, readOnly, () => {
96
+ const history = readValidatedHistory(database);
97
+ const current = history.current;
98
+ if (!current)
99
+ return rejected("no-current-launch", null);
100
+ if (current.launch.executionId !== executionId)
101
+ return rejected("not-current", current);
102
+ if (current.verdict)
103
+ return rejected("already-terminal", current);
104
+ if (current.killIntent)
105
+ return rejected("kill-requested", current);
106
+ if (current.interruptIntent)
107
+ return rejected("interrupt-requested", current);
108
+ const facts = validateInterruptIntentFacts({ interruptedAt }, "interrupt-intent facts");
109
+ return committed([appendRecord(database, "interrupt-intent", executionId, facts, history.records.length + 1)]);
110
+ });
111
+ }
58
112
  export function runnerKilledIfRequested(database, readOnly, input) {
59
113
  const executionId = assertExecutionId(input.executionId);
60
114
  const callerFacts = cloneFacts(input.facts ?? {}, "killed verdict facts");
@@ -87,6 +141,84 @@ export function runnerKilledIfRequested(database, readOnly, input) {
87
141
  return committed([appendRecord(database, "verdict", executionId, facts, history.records.length + 1)]);
88
142
  });
89
143
  }
144
+ export function runnerInterruptedIfRequested(database, readOnly, input) {
145
+ const executionId = assertExecutionId(input.executionId);
146
+ const callerFacts = cloneFacts(input.facts ?? {}, "interrupted verdict facts");
147
+ for (const reserved of ["state", "interruptIntentSeq", "interruptedAt", "terminalAt"]) {
148
+ if (reserved in callerFacts) {
149
+ throw new ProjectionGenerationStoreError(`interrupted verdict facts.${reserved} is reserved`);
150
+ }
151
+ }
152
+ const terminalAt = input.terminalAt === undefined
153
+ ? undefined
154
+ : assertIsoTimestamp(input.terminalAt, "terminalAt");
155
+ return writeTransition(database, readOnly, () => {
156
+ const history = readValidatedHistory(database);
157
+ const current = history.current;
158
+ if (!current)
159
+ return rejected("no-current-launch", null);
160
+ if (current.launch.executionId !== executionId)
161
+ return rejected("not-current", current);
162
+ if (current.verdict)
163
+ return rejected("already-terminal", current);
164
+ if (!current.interruptIntent)
165
+ return rejected("kill-not-requested", current);
166
+ const facts = validateVerdictFacts({
167
+ ...callerFacts,
168
+ state: "interrupted",
169
+ interruptIntentSeq: current.interruptIntent.seq,
170
+ interruptedAt: current.interruptIntent.facts.interruptedAt,
171
+ ...(terminalAt ? { terminalAt } : {}),
172
+ }, "interrupted verdict facts");
173
+ return committed([appendRecord(database, "verdict", executionId, facts, history.records.length + 1)]);
174
+ });
175
+ }
176
+ /** Publish a stopped provider outcome while atomically honoring any stop intent that won first. */
177
+ export function runnerVerdictIfOpen(database, readOnly, input) {
178
+ const executionId = assertExecutionId(input.executionId);
179
+ if (!VERDICTS.has(input.verdict))
180
+ throw new ProjectionGenerationStoreError(`unknown verdict '${String(input.verdict)}'`);
181
+ if (input.verdict === "killed" || input.verdict === "interrupted" || input.verdict === "launch-failed" || input.verdict === "lost-runner") {
182
+ throw new ProjectionGenerationStoreError(`runner outcome verdict '${input.verdict}' is not publishable`);
183
+ }
184
+ const callerFacts = cloneFacts(input.facts ?? {}, "runner verdict facts");
185
+ const terminalAt = assertIsoTimestamp(input.terminalAt, "terminalAt");
186
+ return writeTransition(database, readOnly, () => {
187
+ const history = readValidatedHistory(database);
188
+ const current = history.current;
189
+ if (!current)
190
+ return rejected("no-current-launch", null);
191
+ if (current.launch.executionId !== executionId)
192
+ return rejected("not-current", current);
193
+ if (current.verdict)
194
+ return rejected("already-terminal", current);
195
+ let facts;
196
+ if (current.interruptIntent) {
197
+ const { state: _state, interruptIntentSeq: _seq, interruptedAt: _at, terminalAt: _terminal, ...rest } = callerFacts;
198
+ facts = validateVerdictFacts({
199
+ ...rest,
200
+ state: "interrupted",
201
+ interruptIntentSeq: current.interruptIntent.seq,
202
+ interruptedAt: current.interruptIntent.facts.interruptedAt,
203
+ terminalAt,
204
+ }, "runner interrupted verdict facts");
205
+ }
206
+ else if (current.killIntent && !(input.verdict === "failed" && callerFacts.operatorAction === "kill")) {
207
+ const { state: _state, killIntentSeq: _seq, killedAt: _at, terminalAt: _terminal, ...rest } = callerFacts;
208
+ facts = validateVerdictFacts({
209
+ ...rest,
210
+ state: "killed",
211
+ killIntentSeq: current.killIntent.seq,
212
+ killedAt: current.killIntent.facts.killedAt,
213
+ terminalAt,
214
+ }, "runner killed verdict facts");
215
+ }
216
+ else {
217
+ facts = validateVerdictFacts({ ...callerFacts, state: input.verdict, terminalAt }, "runner verdict facts");
218
+ }
219
+ return committed([appendRecord(database, "verdict", executionId, facts, history.records.length + 1)]);
220
+ });
221
+ }
90
222
  export function settleKillIfRunnerDead(database, readOnly, input) {
91
223
  const executionId = assertExecutionId(input.executionId);
92
224
  const releasedLockEvidence = cloneFacts(input.releasedLockEvidence, "released runner-lock evidence");
@@ -128,8 +260,8 @@ export function verdictIfOpen(database, readOnly, input) {
128
260
  const executionId = assertExecutionId(input.executionId);
129
261
  if (!VERDICTS.has(input.verdict))
130
262
  throw new ProjectionGenerationStoreError(`unknown verdict '${String(input.verdict)}'`);
131
- if (input.verdict === "killed") {
132
- throw new ProjectionGenerationStoreError("verdict 'killed' is reserved to runnerKilledIfRequested and settleKillIfRunnerDead");
263
+ if (input.verdict === "killed" || input.verdict === "interrupted") {
264
+ throw new ProjectionGenerationStoreError(`verdict '${input.verdict}' is reserved to its runner-owned transition`);
133
265
  }
134
266
  return writeTransition(database, readOnly, () => {
135
267
  const history = readValidatedHistory(database);
@@ -140,15 +272,15 @@ export function verdictIfOpen(database, readOnly, input) {
140
272
  return rejected("not-current", current);
141
273
  if (current.verdict)
142
274
  return rejected("already-terminal", current);
275
+ if (input.verdict === "launch-failed" && current.adoption) {
276
+ return rejected("already-adopted", current);
277
+ }
143
278
  if (current.killIntent) {
144
279
  const killEscalationFailed = input.verdict === "failed"
145
280
  && input.facts?.operatorAction === "kill";
146
281
  if (!killEscalationFailed)
147
282
  return rejected("kill-requested", current);
148
283
  }
149
- if (input.verdict === "launch-failed" && current.adoption) {
150
- return rejected("already-adopted", current);
151
- }
152
284
  const facts = validateVerdictFacts({ ...(input.facts ?? {}), state: input.verdict, ...(input.terminalAt ? { terminalAt: assertIsoTimestamp(input.terminalAt, "terminalAt") } : {}) }, "verdict facts");
153
285
  return committed([appendRecord(database, "verdict", executionId, facts, history.records.length + 1)]);
154
286
  });
@@ -156,8 +288,10 @@ export function verdictIfOpen(database, readOnly, input) {
156
288
  export function supersedeStartupTimeout(database, readOnly, input) {
157
289
  const executionId = assertExecutionId(input.executionId);
158
290
  const successorExecutionId = assertExecutionId(input.successorExecutionId);
159
- const observedAtMs = assertNonNegativeSafeInteger(input.observedAtMs, "observedAtMs");
160
- const startupTimeoutMs = assertPositiveSafeInteger(input.startupTimeoutMs, "startupTimeoutMs");
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
+ }
161
295
  const diagnostic = cloneFacts(input.diagnostic, "startup-timeout diagnostic");
162
296
  const successorFacts = validateLaunchFacts(input.successorFacts, "successor launch facts");
163
297
  const terminalAt = input.terminalAt === undefined
@@ -172,19 +306,24 @@ export function supersedeStartupTimeout(database, readOnly, input) {
172
306
  return rejected("not-current", current);
173
307
  if (current.verdict)
174
308
  return rejected("already-terminal", current);
175
- if (current.killIntent)
176
- return rejected("kill-requested", current);
177
309
  if (current.adoption)
178
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
+ }
179
321
  if (history.records.some((record) => record.kind === "launch" && record.executionId === successorExecutionId)) {
180
322
  return rejected("execution-id-already-used", current);
181
323
  }
182
- const launchedAtMs = Date.parse(current.launch.facts.createdAt);
183
- if (observedAtMs - launchedAtMs < startupTimeoutMs) {
184
- return rejected("startup-timeout-not-elapsed", current);
185
- }
186
324
  const verdictFactsBase = {
187
325
  ...diagnostic,
326
+ releasedLockEvidence,
188
327
  state: "launch-failed",
189
328
  ...(terminalAt === undefined ? {} : { terminalAt }),
190
329
  };
@@ -196,6 +335,44 @@ export function supersedeStartupTimeout(database, readOnly, input) {
196
335
  ]);
197
336
  });
198
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
+ }
199
376
  export function replaceDeadRunner(database, readOnly, input) {
200
377
  const executionId = assertExecutionId(input.executionId);
201
378
  const successorExecutionId = assertExecutionId(input.successorExecutionId);
@@ -259,6 +436,8 @@ export function handoff(database, readOnly, input) {
259
436
  return rejected("already-terminal", current);
260
437
  if (current.killIntent)
261
438
  return rejected("kill-requested", current);
439
+ if (current.interruptIntent)
440
+ return rejected("interrupt-requested", current);
262
441
  if (!current.adoption)
263
442
  return rejected("not-adopted", current);
264
443
  if (history.records.some((record) => record.kind === "launch" && record.executionId === successorExecutionId)) {
@@ -9,11 +9,11 @@ export { assertProjectionAlias, isProjectionAlias, moveProjectionAlias, projecti
9
9
  export { resolveProjectionCoordinate } from "./projection-coordinate.js";
10
10
  export { appendGenerationEvent, executionEventsPath, isProjectionId, isPublishedProjection, isoFromNowMs, projectionDir, projectionRoot, readProjectionEvents, } from "./projection-core.js";
11
11
  export { readProjectionIdentity } from "./projection-identity.js";
12
- export { killProjectionGeneration } from "./projection-kill.js";
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, readProjectionStatusBoard, } from "./projection-status.js";
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
- export { claimFencedTells, claimTellToInflight, demoteSubmittedTellsForReplay, listTellIds, markTellConsumed, markTellSubmitted, readProjectionTellCounts, readTellOriginal, readTellSubmitted, writeInboxTell, } from "./tell/store.js";
27
+ export { claimFencedTells, claimTellToInflight, listTellIds, markTellConsumed, markTellSubmitted, readPendingTellWindow, readProjectionTellCounts, readTellOriginal, readTellSubmitted, writeInboxTell, } from "./tell/store.js";
@@ -5,7 +5,7 @@ import { projectionCoordinateRoot } from "./projection-coordinate.js";
5
5
  import { ProjectionStateError, } from "../atomic-publish.js";
6
6
  export { atomicPublishFile, ProjectionStateError, randomHex8 } from "../atomic-publish.js";
7
7
  export { isTellCausallyConsumed, } from "./tell/model.js";
8
- export { claimAllInbox, claimFencedTells, claimTellToInflight, demoteSubmittedTellsForReplay, listTellIds, markTellConsumed, markTellSubmitted, readTellOriginal, readTellSubmitted, snapshotPendingTells, writeInboxTell, } from "./tell/store.js";
8
+ export { claimAllInbox, claimFencedTells, claimTellToInflight, listTellIds, markTellConsumed, markTellSubmitted, readTellOriginal, readTellSubmitted, snapshotPendingTells, writeInboxTell, } from "./tell/store.js";
9
9
  /** Relative segments for `.keiyaku/projection`. */
10
10
  export const PROJECTION_ROOT_SEGMENTS = [".keiyaku", "projection"];
11
11
  export const PROJECTION_ID_PATTERN = /^[a-zA-Z0-9]+(?:[.-][a-zA-Z0-9]+)*\/[0-9a-f]{8}$/;
@@ -1,18 +1,61 @@
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
+ /** The runner observes this durable intent and owns native termination. */
4
+ export function interruptProjectionGeneration(projectionDirectory, nowMs = Date.now()) {
5
+ const store = openProjectionGenerationStore(projectionDirectory);
6
+ try {
7
+ for (;;) {
8
+ const current = store.readCurrentGeneration();
9
+ if (!current)
10
+ return { status: "already-closed" };
11
+ if (current.verdict)
12
+ return { status: "already-closed", executionId: current.launch.executionId };
13
+ const executionId = current.launch.executionId;
14
+ if (!current.adoption || observeProjectionRunnerLock(projectionDirectory).state !== "held") {
15
+ return { status: "already-closed", executionId };
16
+ }
17
+ const request = store.requestInterruptIfOpen({
18
+ executionId,
19
+ interruptedAt: new Date(nowMs).toISOString(),
20
+ });
21
+ if (request.status === "committed")
22
+ return { status: "accepted", executionId };
23
+ if (request.reason === "not-current")
24
+ continue;
25
+ if (request.reason === "interrupt-requested")
26
+ return { status: "accepted", executionId };
27
+ return { status: "already-closed", executionId };
28
+ }
29
+ }
30
+ finally {
31
+ store.close();
32
+ }
33
+ }
3
34
  function settleReleasedLeash(store, projectionDirectory, executionId) {
4
- const observation = observeProjectionRunnerLock(projectionDirectory);
5
- if (observation.state === "held" || observation.state === "unknown") {
35
+ const current = store.readCurrentGeneration();
36
+ if (current?.launch.executionId !== executionId
37
+ || current.verdict) {
6
38
  return { status: "accepted", executionId };
7
39
  }
8
- // Positive released-leash evidence only; settlement race still reports accepted.
9
- store.settleKillIfRunnerDead({
10
- executionId,
11
- releasedLockEvidence: {
12
- state: "released",
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({
13
52
  executionId,
14
- },
15
- });
53
+ releasedLockEvidence: { state: "acquired", executionId },
54
+ });
55
+ }
56
+ finally {
57
+ deathEvidence.close();
58
+ }
16
59
  return { status: "accepted", executionId };
17
60
  }
18
61
  /**
@@ -4,6 +4,7 @@ export const GENERATION_VERDICT_STATES = [
4
4
  "launch-failed",
5
5
  "lost-runner",
6
6
  "dismissed",
7
+ "interrupted",
7
8
  "killed",
8
9
  ];
9
10
  function asLaunch(record) {
@@ -21,6 +22,11 @@ function asKillIntent(record) {
21
22
  ? record
22
23
  : undefined;
23
24
  }
25
+ function asInterruptIntent(record) {
26
+ return record.kind === "interrupt-intent"
27
+ ? record
28
+ : undefined;
29
+ }
24
30
  function asVerdict(record) {
25
31
  return record.kind === "verdict"
26
32
  ? record
@@ -41,18 +47,21 @@ export function selectCurrentGeneration(records) {
41
47
  return null;
42
48
  let adoption;
43
49
  let killIntent;
50
+ let interruptIntent;
44
51
  let verdict;
45
52
  for (const record of records) {
46
53
  if (record.seq <= launch.seq || record.executionId !== launch.executionId)
47
54
  continue;
48
55
  adoption ??= asAdoption(record);
49
56
  killIntent ??= asKillIntent(record);
57
+ interruptIntent ??= asInterruptIntent(record);
50
58
  verdict ??= asVerdict(record);
51
59
  }
52
60
  return {
53
61
  launch,
54
62
  ...(adoption ? { adoption } : {}),
55
63
  ...(killIntent ? { killIntent } : {}),
64
+ ...(interruptIntent ? { interruptIntent } : {}),
56
65
  ...(verdict ? { verdict } : {}),
57
66
  };
58
67
  }
@@ -67,6 +76,7 @@ function phaseForVerdict(state) {
67
76
  switch (state) {
68
77
  case "completed": return "done";
69
78
  case "dismissed": return "dismissed";
79
+ case "interrupted": return "done";
70
80
  case "killed": return "killed";
71
81
  case "lost-runner": return "lost";
72
82
  case "failed":