@astrosheep/keiyaku 2.8.4 → 2.9.0

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 (229) hide show
  1. package/README.md +35 -15
  2. package/build/.tsbuildinfo +1 -1
  3. package/build/agents/{call-terms_v2.js → call-terms.js} +12 -8
  4. package/build/agents/harness/control-types.js +1 -0
  5. package/build/agents/harness/execution-handle.js +23 -3
  6. package/build/agents/harness/index.js +6 -607
  7. package/build/agents/harness/outcome.js +141 -0
  8. package/build/agents/harness/projection.js +310 -0
  9. package/build/agents/harness/router.js +201 -0
  10. package/build/agents/harness/runtime.js +2 -1
  11. package/build/agents/{launch-snapshot_v2.js → launch-snapshot/model.js} +69 -74
  12. package/build/agents/launch-snapshot/resolve.js +72 -0
  13. package/build/agents/opencode-sdk.js +24 -3
  14. package/build/agents/providers/{claude-agent-sdk.js → claude-agent-sdk/adapter.js} +46 -368
  15. package/build/agents/providers/claude-agent-sdk/events.js +218 -0
  16. package/build/agents/providers/claude-agent-sdk/session.js +158 -0
  17. package/build/agents/providers/codex-app-server/adapter.js +347 -0
  18. package/build/agents/providers/codex-app-server/events.js +352 -0
  19. package/build/agents/providers/codex-app-server/session.js +266 -0
  20. package/build/agents/providers/codex-sdk.js +23 -5
  21. package/build/agents/providers/opencode-sdk/adapter.js +344 -0
  22. package/build/agents/providers/opencode-sdk/events.js +354 -0
  23. package/build/agents/providers/opencode-sdk/session.js +180 -0
  24. package/build/agents/providers/pi/adapter.js +269 -0
  25. package/build/agents/providers/pi/checkpoint.js +41 -0
  26. package/build/agents/providers/pi/events.js +282 -0
  27. package/build/agents/{selector_v2.js → selector.js} +8 -6
  28. package/build/cli/commands/akuma/akuma/handler.js +4 -0
  29. package/build/cli/commands/akuma/akuma/meta.js +16 -0
  30. package/build/cli/commands/akuma/catalog.js +8 -0
  31. package/build/cli/commands/akuma/list/handler.js +15 -0
  32. package/build/cli/commands/akuma/list/meta-ls.js +9 -0
  33. package/build/cli/commands/akuma/view/handler.js +34 -0
  34. package/build/cli/commands/akuma/view/meta.js +8 -0
  35. package/build/cli/commands/akuma.js +1 -1
  36. package/build/cli/commands/contract/amend/handler.js +10 -0
  37. package/build/cli/commands/contract/amend/meta.js +12 -0
  38. package/build/cli/commands/contract/arc/handler.js +9 -0
  39. package/build/cli/commands/contract/arc/meta.js +26 -0
  40. package/build/cli/commands/contract/bind/handler.js +23 -0
  41. package/build/cli/commands/contract/bind/meta.js +30 -0
  42. package/build/cli/commands/contract/catalog.js +16 -0
  43. package/build/cli/commands/contract/forfeit/handler.js +13 -0
  44. package/build/cli/commands/contract/forfeit/meta.js +12 -0
  45. package/build/cli/commands/contract/log/handler.js +12 -0
  46. package/build/cli/commands/contract/log/meta.js +12 -0
  47. package/build/cli/commands/contract/petition/handler.js +26 -0
  48. package/build/cli/commands/contract/petition/meta.js +23 -0
  49. package/build/cli/commands/contract/renew/handler.js +10 -0
  50. package/build/cli/commands/contract/renew/meta.js +17 -0
  51. package/build/cli/commands/projection/call/handler.js +39 -0
  52. package/build/cli/commands/projection/call/meta.js +9 -0
  53. package/build/cli/commands/projection/catalog.js +14 -0
  54. package/build/cli/commands/projection/kill/handler.js +35 -0
  55. package/build/cli/commands/projection/kill/meta.js +11 -0
  56. package/build/cli/commands/projection/revive/handler.js +53 -0
  57. package/build/cli/commands/projection/revive/meta.js +9 -0
  58. package/build/cli/commands/projection/status/handler.js +23 -0
  59. package/build/cli/commands/projection/status/meta.js +9 -0
  60. package/build/cli/commands/projection/tell/handler.js +34 -0
  61. package/build/cli/commands/projection/tell/meta.js +9 -0
  62. package/build/cli/commands/projection/wait/handler.js +21 -0
  63. package/build/cli/commands/projection/wait/meta.js +11 -0
  64. package/build/cli/commands/registry.js +100 -0
  65. package/build/cli/commands/shared.js +71 -0
  66. package/build/cli/commands/skills/catalog.js +6 -0
  67. package/build/cli/commands/skills/install/handler.js +10 -0
  68. package/build/cli/commands/skills/install/meta.js +9 -0
  69. package/build/cli/commands/skills/skills/handler.js +4 -0
  70. package/build/cli/commands/skills/skills/meta.js +10 -0
  71. package/build/cli/commands/system/catalog.js +8 -0
  72. package/build/cli/commands/system/completion/handler.js +10 -0
  73. package/build/cli/commands/system/completion/meta.js +9 -0
  74. package/build/cli/commands/system/dump-env/handler.js +3 -0
  75. package/build/cli/commands/system/dump-env/meta.js +10 -0
  76. package/build/cli/commands/system/guide/handler.js +4 -0
  77. package/build/cli/commands/system/guide/meta.js +9 -0
  78. package/build/cli/commands/task/add/handler.js +21 -0
  79. package/build/cli/commands/task/add/meta.js +8 -0
  80. package/build/cli/commands/task/catalog.js +22 -0
  81. package/build/cli/commands/task/doctor/handler.js +4 -0
  82. package/build/cli/commands/task/doctor/meta.js +8 -0
  83. package/build/cli/commands/task/done/handler.js +17 -0
  84. package/build/cli/commands/task/done/meta.js +8 -0
  85. package/build/cli/commands/task/drop/handler.js +6 -0
  86. package/build/cli/commands/task/drop/meta.js +8 -0
  87. package/build/cli/commands/task/ls/handler.js +13 -0
  88. package/build/cli/commands/task/ls/meta.js +8 -0
  89. package/build/cli/commands/task/shared.js +131 -0
  90. package/build/cli/commands/task/start/handler.js +6 -0
  91. package/build/cli/commands/task/start/meta.js +8 -0
  92. package/build/cli/commands/task/stop/handler.js +17 -0
  93. package/build/cli/commands/task/stop/meta.js +8 -0
  94. package/build/cli/commands/task/task/handler.js +5 -0
  95. package/build/cli/commands/task/task/meta.js +11 -0
  96. package/build/cli/commands/task/update/handler.js +18 -0
  97. package/build/cli/commands/task/update/meta.js +8 -0
  98. package/build/cli/commands/task/view/handler.js +14 -0
  99. package/build/cli/commands/task/view/meta.js +8 -0
  100. package/build/cli/completion.js +26 -27
  101. package/build/cli/flags.js +79 -2
  102. package/build/cli/help.js +27 -49
  103. package/build/cli/index.js +121 -374
  104. package/build/cli/output-stream-error-policy.js +17 -0
  105. package/build/cli/parse.js +159 -72
  106. package/build/cli/projection-address.js +5 -0
  107. package/build/cli/render/arc.js +1 -1
  108. package/build/cli/render/errors.js +4 -3
  109. package/build/cli/render/format.js +14 -0
  110. package/build/cli/render/kanshi.js +106 -0
  111. package/build/cli/render/petition.js +24 -1
  112. package/build/cli/render/projection-activity.js +5 -1
  113. package/build/cli/render/shared.js +2 -2
  114. package/build/cli/render/status.js +79 -13
  115. package/build/cli/render/task.js +113 -0
  116. package/build/cli/render/wait.js +23 -3
  117. package/build/cli/types.js +1 -1
  118. package/build/config/{akuma-loader_v2.js → akuma-loader.js} +2 -2
  119. package/build/config/env.js +9 -4
  120. package/build/config/settings/disease.js +26 -0
  121. package/build/config/settings/knobs.js +8 -0
  122. package/build/config/settings/loader.js +244 -0
  123. package/build/config/settings/schema.js +276 -0
  124. package/build/core/addressing.js +1 -1
  125. package/build/core/amend.js +3 -3
  126. package/build/core/arc.js +5 -4
  127. package/build/core/bind-workspace.js +123 -0
  128. package/build/core/bind.js +75 -155
  129. package/build/core/call/call.js +224 -0
  130. package/build/core/call/context.js +238 -0
  131. package/build/core/call/execution.js +217 -0
  132. package/build/core/call/prompt.js +42 -0
  133. package/build/core/call/reference.js +24 -0
  134. package/build/core/call-persist.js +1 -1
  135. package/build/core/claim-delivery.js +247 -0
  136. package/build/core/claim.js +39 -277
  137. package/build/core/context.js +2 -1
  138. package/build/core/{render.js → contract-view.js} +12 -140
  139. package/build/core/draft.js +107 -9
  140. package/build/core/entry.js +9 -1
  141. package/build/core/execution-coordinate.js +4 -20
  142. package/build/core/execution-pact.js +16 -31
  143. package/build/core/forfeit.js +35 -22
  144. package/build/core/hints.js +2 -2
  145. package/build/core/log.js +2 -2
  146. package/build/core/petition-claim-gates.js +8 -6
  147. package/build/core/petition-preview.js +97 -0
  148. package/build/core/petition-run.js +3 -2
  149. package/build/core/petition.js +39 -26
  150. package/build/core/projection/generation/database.js +306 -0
  151. package/build/core/projection/generation/identity.js +84 -0
  152. package/build/core/projection/generation/model.js +201 -0
  153. package/build/core/projection/generation/store.js +73 -0
  154. package/build/core/projection/generation/transitions.js +258 -0
  155. package/build/core/projection/heart.js +0 -307
  156. package/build/core/projection/tell/database.js +109 -0
  157. package/build/core/projection/tell/model.js +97 -0
  158. package/build/core/{projection-tells.js → projection/tell/store.js} +3 -158
  159. package/build/core/projection-activity.js +23 -2
  160. package/build/core/projection-core.js +2 -1
  161. package/build/core/projection-execution-observer.js +259 -0
  162. package/build/core/projection-generation-continuation.js +1 -1
  163. package/build/core/projection-generation-execution.js +90 -20
  164. package/build/core/projection-generation-launcher.js +1 -1
  165. package/build/core/projection-generation-runner.js +198 -4
  166. package/build/core/projection-identity.js +1 -1
  167. package/build/core/projection-kill.js +50 -17
  168. package/build/core/projection-life-observer.js +1 -1
  169. package/build/core/projection-life-protocol.js +10 -0
  170. package/build/core/projection-mint.js +1 -1
  171. package/build/core/projection-status.js +45 -32
  172. package/build/core/projection-wait.js +12 -2
  173. package/build/core/projection-wake.js +5 -2
  174. package/build/core/{queue_v2.js → queue.js} +2 -1
  175. package/build/core/registry.js +1 -1
  176. package/build/core/renew.js +7 -7
  177. package/build/core/response-artifact-catalog-sqlite.js +112 -0
  178. package/build/core/response-artifact-catalog.js +7 -0
  179. package/build/core/seal.js +44 -16
  180. package/build/core/status/board.js +286 -0
  181. package/build/core/status/drift.js +99 -0
  182. package/build/core/status/lifecycle.js +84 -0
  183. package/build/core/status/reconciliation.js +131 -0
  184. package/build/core/stored-agent-event.js +28 -4
  185. package/build/core/target-ref.js +10 -0
  186. package/build/core/task/board.js +175 -0
  187. package/build/core/task/commands.js +211 -0
  188. package/build/core/task/document.js +189 -0
  189. package/build/core/task/model.js +1 -0
  190. package/build/core/task/settlement-git.js +207 -0
  191. package/build/core/task/settlement-policy.js +82 -0
  192. package/build/core/task/source-board.js +84 -0
  193. package/build/core/task/source-model.js +1 -0
  194. package/build/core/task/validation.js +44 -0
  195. package/build/core/task-contract.js +224 -0
  196. package/build/core/task-doctor.js +14 -0
  197. package/build/core/task-git-actor.js +38 -0
  198. package/build/core/task-git-runtime.js +34 -0
  199. package/build/core/task-git-store.js +375 -0
  200. package/build/core/task-repository.js +1 -0
  201. package/build/core/task-store-repository.js +86 -0
  202. package/build/core/task-store.js +1 -0
  203. package/build/core/task-worktree.js +51 -0
  204. package/build/core/task.js +4 -0
  205. package/build/core/transcripts.js +138 -56
  206. package/build/core/validation.js +17 -0
  207. package/build/core/verdict.js +2 -2
  208. package/build/core/worktree-path.js +24 -6
  209. package/build/flow-error.js +3 -2
  210. package/build/generated/version.js +1 -1
  211. package/build/git/commits.js +3 -0
  212. package/build/git/diff/parsers.js +0 -94
  213. package/build/index.js +2 -0
  214. package/build/keiyaku-constants.js +2 -0
  215. package/build/keiyaku.js +4 -3
  216. package/package.json +3 -2
  217. package/skills/keiyaku/SKILL.md +1 -1
  218. package/skills/keiyaku-akuma/SKILL.md +11 -6
  219. package/build/agents/harness/pump.js +0 -158
  220. package/build/agents/providers/codex-app-server.js +0 -864
  221. package/build/agents/providers/opencode-sdk.js +0 -363
  222. package/build/agents/providers/pi.js +0 -557
  223. package/build/config/settings.js +0 -533
  224. package/build/core/call.js +0 -686
  225. package/build/core/projection/generation.js +0 -412
  226. package/build/core/projection/identity.js +0 -81
  227. package/build/core/projection/mint.js +0 -174
  228. package/build/core/projection-generation-store.js +0 -707
  229. package/build/core/status.js +0 -539
@@ -1,7 +1,10 @@
1
- import { openProjectionGenerationStore, } from "./projection-generation-store.js";
1
+ import { openProjectionGenerationStore, } from "./projection/generation/store.js";
2
2
  import { acquireProjectionRunnerLock, observeProjectionRunnerLock, } from "./projection-runner-lock.js";
3
3
  import { createProjectionExecutionId } from "./projection-generation-identity.js";
4
4
  import { applyContinuationEffort, } from "./projection-generation-continuation.js";
5
+ import { openProjectionTellObserver } from "./projection/tell/database.js";
6
+ /** Private runner-side kill grace. Not a setting, env, parameter, or export. */
7
+ const KILL_GRACE_MS = 5000;
5
8
  function successorLaunchFacts(launch, completionFacts, effort, now) {
6
9
  const { tellFence: _tellFence, ...inherited } = launch.facts;
7
10
  const rawInputs = launch.facts.inputs;
@@ -10,13 +13,15 @@ function successorLaunchFacts(launch, completionFacts, effort, now) {
10
13
  && !Array.isArray(rawInputs)
11
14
  ? rawInputs
12
15
  : {};
16
+ // Revive source identity is launch-local; never inherit it onto a tell handoff successor.
17
+ const { sourceArtifactId: _sourceArtifactId, ...inheritedInputs } = inputs;
13
18
  return {
14
19
  ...inherited,
15
20
  createdAt: new Date(now()).toISOString(),
16
21
  launchedBy: "sweep-wake",
17
22
  parentExecutionId: launch.executionId,
18
23
  inputs: applyContinuationEffort({
19
- ...inputs,
24
+ ...inheritedInputs,
20
25
  ...(completionFacts.session ? { session: completionFacts.session } : {}),
21
26
  ...(typeof completionFacts.responsePath === "string"
22
27
  ? { resumePath: completionFacts.responsePath }
@@ -30,6 +35,19 @@ function normalizeTellFence(tellIds) {
30
35
  function errorDetail(error) {
31
36
  return error instanceof Error ? error.message : String(error);
32
37
  }
38
+ function nonReservedRunnerKillFacts(facts) {
39
+ const { state: _state, killIntentSeq: _killIntentSeq, killedAt: _killedAt, ...rest } = facts;
40
+ return rest;
41
+ }
42
+ function readOutcomeState(state) {
43
+ if (state.status === "fulfilled") {
44
+ return { status: "fulfilled", execution: state.execution };
45
+ }
46
+ if (state.status === "rejected") {
47
+ return { status: "rejected", error: state.error };
48
+ }
49
+ return { status: "pending" };
50
+ }
33
51
  function acquireRunnerLockOrObserveDuplicate(projectionDirectory) {
34
52
  try {
35
53
  return acquireProjectionRunnerLock(projectionDirectory);
@@ -41,6 +59,9 @@ function acquireRunnerLockOrObserveDuplicate(projectionDirectory) {
41
59
  throw error;
42
60
  }
43
61
  }
62
+ function observeAbandoned(promise) {
63
+ void promise.then(() => { }, () => { });
64
+ }
44
65
  /**
45
66
  * Own one exact generation from kernel-lock acquisition through its terminal
46
67
  * transaction. Provider execution is injected so this actor never depends on
@@ -52,6 +73,14 @@ export async function runProjectionGeneration(input) {
52
73
  let runnerLock;
53
74
  let adopted = false;
54
75
  let settled = false;
76
+ let tellObserver;
77
+ let graceTimer;
78
+ const clearGraceTimer = () => {
79
+ if (graceTimer === undefined)
80
+ return;
81
+ clearTimeout(graceTimer);
82
+ graceTimer = undefined;
83
+ };
55
84
  try {
56
85
  const initial = store.readCurrentGeneration();
57
86
  if (initial?.launch.executionId !== input.executionId
@@ -73,7 +102,151 @@ export async function runProjectionGeneration(input) {
73
102
  if (current?.launch.executionId !== input.executionId) {
74
103
  throw new Error("adopted generation stopped being current");
75
104
  }
76
- const execution = await input.execute(current.launch);
105
+ // Pre-admission kill: skip provider work and commit killed directly.
106
+ if (current.killIntent) {
107
+ const killed = store.runnerKilledIfRequested({ executionId: input.executionId });
108
+ if (killed.status !== "committed")
109
+ return { status: "rejected" };
110
+ settled = true;
111
+ return { status: "completed" };
112
+ }
113
+ const handle = input.execute(current.launch);
114
+ tellObserver = openProjectionTellObserver(input.projectionDirectory);
115
+ let gracefulAbortInvoked = false;
116
+ let forceAbortInvoked = false;
117
+ let sawKillIntent = false;
118
+ const observeKillBoundary = () => {
119
+ try {
120
+ if (!tellObserver.hasDataVersionChanged())
121
+ return;
122
+ const live = store.readCurrentGeneration();
123
+ if (live?.launch.executionId !== input.executionId)
124
+ return;
125
+ if (live.verdict)
126
+ return;
127
+ if (!live.killIntent)
128
+ return;
129
+ sawKillIntent = true;
130
+ if (gracefulAbortInvoked)
131
+ return;
132
+ gracefulAbortInvoked = true;
133
+ void handle.abort("graceful").catch(() => {
134
+ // Abort observation failures fail closed for this boundary.
135
+ });
136
+ }
137
+ catch {
138
+ // Observation failures fail closed and must not change provider outcome.
139
+ }
140
+ };
141
+ // Mutable box so async settlement is readable without control-flow narrowing traps.
142
+ const outcomeBox = { status: "pending" };
143
+ let resolveActivity;
144
+ const activityChanged = () => new Promise((resolve) => {
145
+ resolveActivity = resolve;
146
+ });
147
+ const bumpActivity = () => {
148
+ const resolve = resolveActivity;
149
+ resolveActivity = undefined;
150
+ resolve?.();
151
+ };
152
+ const outcomeWatch = handle.outcome.then((execution) => {
153
+ outcomeBox.status = "fulfilled";
154
+ outcomeBox.execution = execution;
155
+ observeKillBoundary();
156
+ bumpActivity();
157
+ }, (error) => {
158
+ outcomeBox.status = "rejected";
159
+ outcomeBox.error = error;
160
+ observeKillBoundary();
161
+ bumpActivity();
162
+ });
163
+ // Always rejection-observe so abandon paths never surface unhandled rejections.
164
+ observeAbandoned(outcomeWatch);
165
+ const eventDrain = (async () => {
166
+ try {
167
+ for await (const _event of handle.events) {
168
+ if (forceAbortInvoked)
169
+ return;
170
+ observeKillBoundary();
171
+ bumpActivity();
172
+ }
173
+ }
174
+ catch {
175
+ // Event-stream failures fail closed for observation; abandon quietly.
176
+ }
177
+ bumpActivity();
178
+ })();
179
+ observeAbandoned(eventDrain);
180
+ while (outcomeBox.status === "pending" && !sawKillIntent) {
181
+ await activityChanged();
182
+ }
183
+ if (sawKillIntent) {
184
+ // Grace race: outcome settlement inside the window is stop proof.
185
+ if (outcomeBox.status === "pending") {
186
+ const graceExpired = new Promise((resolve) => {
187
+ graceTimer = setTimeout(() => {
188
+ graceTimer = undefined;
189
+ resolve("timeout");
190
+ }, KILL_GRACE_MS);
191
+ });
192
+ await Promise.race([
193
+ outcomeWatch.then(() => "outcome"),
194
+ graceExpired,
195
+ ]);
196
+ clearGraceTimer();
197
+ }
198
+ const killOutcome = readOutcomeState(outcomeBox);
199
+ if (killOutcome.status !== "pending") {
200
+ const facts = killOutcome.status === "fulfilled"
201
+ ? nonReservedRunnerKillFacts(killOutcome.execution.facts)
202
+ : undefined;
203
+ const killed = store.runnerKilledIfRequested({
204
+ executionId: input.executionId,
205
+ ...(facts && Object.keys(facts).length > 0 ? { facts } : {}),
206
+ });
207
+ if (killed.status !== "committed")
208
+ return { status: "rejected" };
209
+ settled = true;
210
+ return { status: "completed" };
211
+ }
212
+ // Force escalation: resolution is stop proof; rejection is kill failure.
213
+ forceAbortInvoked = true;
214
+ try {
215
+ await handle.abort("force");
216
+ const killed = store.runnerKilledIfRequested({ executionId: input.executionId });
217
+ if (killed.status !== "committed")
218
+ return { status: "rejected" };
219
+ settled = true;
220
+ return { status: "completed" };
221
+ }
222
+ catch (error) {
223
+ const failed = store.verdictIfOpen({
224
+ executionId: input.executionId,
225
+ verdict: "failed",
226
+ facts: {
227
+ operatorAction: "kill",
228
+ diagnostic: "escalation-failed",
229
+ detail: errorDetail(error),
230
+ failedAt: new Date(now()).toISOString(),
231
+ },
232
+ });
233
+ // Durably completed after the failed verdict; outer catch must not overwrite.
234
+ settled = true;
235
+ if (failed.status !== "committed")
236
+ return { status: "rejected" };
237
+ return { status: "completed" };
238
+ }
239
+ }
240
+ // No kill intent: ordinary outcome, then pending-tell handoff or verdict.
241
+ await outcomeWatch;
242
+ const ordinaryOutcome = readOutcomeState(outcomeBox);
243
+ if (ordinaryOutcome.status === "rejected") {
244
+ throw ordinaryOutcome.error;
245
+ }
246
+ if (ordinaryOutcome.status !== "fulfilled") {
247
+ throw new Error("projection generation outcome did not settle");
248
+ }
249
+ const execution = ordinaryOutcome.execution;
77
250
  const pending = execution.verdict === "completed"
78
251
  ? input.snapshotPendingTells()
79
252
  : { tellIds: [] };
@@ -115,8 +288,22 @@ export async function runProjectionGeneration(input) {
115
288
  verdict: execution.verdict,
116
289
  facts: execution.facts,
117
290
  });
118
- if (verdict.status !== "committed")
291
+ if (verdict.status !== "committed") {
292
+ // Kill intent may commit after the last outcome-boundary observation and
293
+ // before this transaction. The settled outcome is already stop proof.
294
+ if (verdict.reason === "kill-requested") {
295
+ const facts = nonReservedRunnerKillFacts(execution.facts);
296
+ const killed = store.runnerKilledIfRequested({
297
+ executionId: input.executionId,
298
+ ...(Object.keys(facts).length > 0 ? { facts } : {}),
299
+ });
300
+ if (killed.status !== "committed")
301
+ return { status: "rejected" };
302
+ settled = true;
303
+ return { status: "completed" };
304
+ }
119
305
  return { status: "rejected" };
306
+ }
120
307
  settled = true;
121
308
  return { status: "completed" };
122
309
  }
@@ -134,6 +321,13 @@ export async function runProjectionGeneration(input) {
134
321
  throw error;
135
322
  }
136
323
  finally {
324
+ clearGraceTimer();
325
+ try {
326
+ tellObserver?.close();
327
+ }
328
+ catch {
329
+ // Observer cleanup cannot reopen life or product authority.
330
+ }
137
331
  try {
138
332
  runnerLock?.close();
139
333
  }
@@ -1,4 +1,4 @@
1
- import { openProjectionGenerationStoreReadOnly, } from "./projection-generation-store.js";
1
+ import { openProjectionGenerationStoreReadOnly, } from "./projection/generation/store.js";
2
2
  /** Read the immutable projection identity from the heart database. */
3
3
  export function readProjectionIdentity(projectionDirectory) {
4
4
  const heart = openProjectionGenerationStoreReadOnly(projectionDirectory);
@@ -1,24 +1,57 @@
1
- import { openProjectionGenerationStore } from "./projection-generation-store.js";
1
+ import { openProjectionGenerationStore } from "./projection/generation/store.js";
2
+ import { observeProjectionRunnerLock } from "./projection-runner-lock.js";
3
+ function settleReleasedLeash(store, projectionDirectory, executionId) {
4
+ const observation = observeProjectionRunnerLock(projectionDirectory);
5
+ if (observation.state === "held" || observation.state === "unknown") {
6
+ return { status: "accepted", executionId };
7
+ }
8
+ // Positive released-leash evidence only; settlement race still reports accepted.
9
+ store.settleKillIfRunnerDead({
10
+ executionId,
11
+ releasedLockEvidence: {
12
+ state: "released",
13
+ executionId,
14
+ },
15
+ });
16
+ return { status: "accepted", executionId };
17
+ }
18
+ /**
19
+ * Durably request stop for the current open generation. Intent durability is
20
+ * never published as killed; terminal settlement is runner-owned or dead-leash
21
+ * settle-kill-if-runner-dead only.
22
+ */
2
23
  export function killProjectionGeneration(projectionDirectory, nowMs = Date.now()) {
3
24
  const store = openProjectionGenerationStore(projectionDirectory);
4
25
  try {
5
- const current = store.readCurrentGeneration();
6
- if (!current)
7
- return { status: "already-resting" };
8
- if (current.verdict) {
9
- return { status: "already-resting", executionId: current.launch.executionId };
26
+ const killedAt = new Date(nowMs).toISOString();
27
+ // Retry only on not-current races so an open successor is never labeled closed.
28
+ for (;;) {
29
+ const current = store.readCurrentGeneration();
30
+ if (!current)
31
+ return { status: "already-closed" };
32
+ if (current.verdict) {
33
+ return { status: "already-closed", executionId: current.launch.executionId };
34
+ }
35
+ const executionId = current.launch.executionId;
36
+ const request = store.requestKillIfOpen({ executionId, killedAt });
37
+ if (request.status === "committed") {
38
+ return settleReleasedLeash(store, projectionDirectory, executionId);
39
+ }
40
+ if (request.reason === "kill-requested") {
41
+ // Idempotent first intent: still accepted, then one leash observation.
42
+ return settleReleasedLeash(store, projectionDirectory, executionId);
43
+ }
44
+ if (request.reason === "already-terminal") {
45
+ return { status: "already-closed", executionId };
46
+ }
47
+ if (request.reason === "no-current-launch") {
48
+ return { status: "already-closed" };
49
+ }
50
+ if (request.reason === "not-current") {
51
+ continue;
52
+ }
53
+ return { status: "already-closed", executionId };
10
54
  }
11
- const transition = store.verdictIfOpen({
12
- executionId: current.launch.executionId,
13
- verdict: "completed",
14
- facts: {
15
- operatorAction: "kill",
16
- killedAt: new Date(nowMs).toISOString(),
17
- },
18
- });
19
- return transition.status === "committed"
20
- ? { status: "killed", executionId: current.launch.executionId }
21
- : { status: "already-resting", executionId: current.launch.executionId };
22
55
  }
23
56
  finally {
24
57
  store.close();
@@ -1,4 +1,4 @@
1
- import { openProjectionGenerationStoreReadOnly, } from "./projection-generation-store.js";
1
+ import { openProjectionGenerationStoreReadOnly, } from "./projection/generation/store.js";
2
2
  import { deriveProjectionLife, selectCurrentGeneration, } from "./projection-life-protocol.js";
3
3
  import { observeProjectionRunnerLock } from "./projection-runner-lock.js";
4
4
  function errorDetail(error) {
@@ -4,6 +4,7 @@ export const GENERATION_VERDICT_STATES = [
4
4
  "launch-failed",
5
5
  "lost-runner",
6
6
  "dismissed",
7
+ "killed",
7
8
  ];
8
9
  function asLaunch(record) {
9
10
  return record.kind === "launch"
@@ -15,6 +16,11 @@ function asAdoption(record) {
15
16
  ? record
16
17
  : undefined;
17
18
  }
19
+ function asKillIntent(record) {
20
+ return record.kind === "kill-intent"
21
+ ? record
22
+ : undefined;
23
+ }
18
24
  function asVerdict(record) {
19
25
  return record.kind === "verdict"
20
26
  ? record
@@ -34,16 +40,19 @@ export function selectCurrentGeneration(records) {
34
40
  if (!launch)
35
41
  return null;
36
42
  let adoption;
43
+ let killIntent;
37
44
  let verdict;
38
45
  for (const record of records) {
39
46
  if (record.seq <= launch.seq || record.executionId !== launch.executionId)
40
47
  continue;
41
48
  adoption ??= asAdoption(record);
49
+ killIntent ??= asKillIntent(record);
42
50
  verdict ??= asVerdict(record);
43
51
  }
44
52
  return {
45
53
  launch,
46
54
  ...(adoption ? { adoption } : {}),
55
+ ...(killIntent ? { killIntent } : {}),
47
56
  ...(verdict ? { verdict } : {}),
48
57
  };
49
58
  }
@@ -58,6 +67,7 @@ function phaseForVerdict(state) {
58
67
  switch (state) {
59
68
  case "completed": return "done";
60
69
  case "dismissed": return "dismissed";
70
+ case "killed": return "killed";
61
71
  case "lost-runner": return "lost";
62
72
  case "failed":
63
73
  case "launch-failed":
@@ -4,7 +4,7 @@ import { isErrnoException } from "../errno.js";
4
4
  import { ProjectionStateError, randomHex8 } from "./atomic-publish.js";
5
5
  import { isoFromNowMs, PROJECTION_ROOT_SEGMENTS, projectionDir, projectionRoot, } from "./projection-core.js";
6
6
  import { parseExecutionAuthoritySelection, parseExecutionPact, parseExecutionPactFields, } from "./execution-pact.js";
7
- import { openProjectionGenerationStore } from "./projection-generation-store.js";
7
+ import { openProjectionGenerationStore } from "./projection/generation/store.js";
8
8
  import { initializeProjectionRunnerLockDatabase } from "./projection-runner-lock.js";
9
9
  export class ProjectionMintCollisionError extends Error {
10
10
  code = "PROJECTION_MINT_COLLISION";
@@ -6,8 +6,8 @@ import { readProjectionAlias } from "./projection-alias.js";
6
6
  import { ProjectionStateError } from "./atomic-publish.js";
7
7
  import { foldAgentActivity, } from "./projection-activity.js";
8
8
  import { readProjectionIdentity } from "./projection-identity.js";
9
- import { readProjectionTellCounts, } from "./projection-tells.js";
10
- import { ProjectionGenerationStoreError } from "./projection-generation-store.js";
9
+ import { readProjectionTellCounts } from "./projection/tell/store.js";
10
+ import { isProjectionGenerationSchemaV1Residue, ProjectionGenerationStoreError, } from "./projection/generation/store.js";
11
11
  import { PROJECTION_ADOPTION_TIMEOUT_MS } from "./projection-generation-launcher.js";
12
12
  import { observeProjectionLifeSnapshot } from "./projection-life-observer.js";
13
13
  import { selectCurrentGeneration } from "./projection-life-protocol.js";
@@ -23,10 +23,15 @@ const STATUS_GROUP = {
23
23
  failed: 2,
24
24
  dead: 2,
25
25
  done: 3,
26
+ killed: 4,
26
27
  dismissed: 4,
27
28
  };
28
29
  function isOmissibleProjectionStatusState(state) {
29
- return state === "failed" || state === "dead" || state === "done" || state === "dismissed";
30
+ return state === "failed"
31
+ || state === "dead"
32
+ || state === "done"
33
+ || state === "killed"
34
+ || state === "dismissed";
30
35
  }
31
36
  function rowSortTime(row) {
32
37
  if (row.activityAtMs !== undefined && Number.isFinite(row.activityAtMs)) {
@@ -65,6 +70,7 @@ export function prioritizeProjectionStatusRows(rows, showAll) {
65
70
  failed: 0,
66
71
  dead: 0,
67
72
  done: 0,
73
+ killed: 0,
68
74
  dismissed: 0,
69
75
  };
70
76
  for (const row of ordered) {
@@ -84,27 +90,29 @@ class ProjectionStatusFileError extends Error {
84
90
  diagnostic;
85
91
  constructor(kind, filePath) {
86
92
  const label = path.basename(filePath);
87
- const detail = kind === "non-regular-projection-file"
88
- ? `${label} is not a regular file`
89
- : `${label} exceeds status read limit`;
93
+ const detail = `${label} is not a regular file`;
90
94
  super(detail);
91
95
  this.name = "ProjectionStatusFileError";
92
96
  this.diagnostic = { kind, detail };
93
97
  }
94
98
  }
99
+ function oversizedProjectionFileDiagnostic(filePath) {
100
+ const label = path.basename(filePath);
101
+ return {
102
+ kind: "oversized-projection-file",
103
+ detail: `${label} exceeds status read limit · newest complete events retained`,
104
+ };
105
+ }
95
106
  function recordProjectionStatusFileError(error, diagnostics) {
96
107
  if (!(error instanceof ProjectionStatusFileError))
97
108
  return false;
98
109
  diagnostics.push(error.diagnostic);
99
110
  return true;
100
111
  }
101
- function readBoundedRegularFile(filePath) {
112
+ function readBoundedRegularFileTail(filePath) {
102
113
  const before = fs.lstatSync(filePath);
103
114
  if (!before.isFile())
104
115
  throw new ProjectionStatusFileError("non-regular-projection-file", filePath);
105
- if (before.size > MAX_STATUS_FILE_BYTES) {
106
- throw new ProjectionStatusFileError("oversized-projection-file", filePath);
107
- }
108
116
  let fd;
109
117
  try {
110
118
  fd = fs.openSync(filePath, fs.constants.O_RDONLY | fs.constants.O_NONBLOCK | fs.constants.O_NOFOLLOW);
@@ -119,23 +127,27 @@ function readBoundedRegularFile(filePath) {
119
127
  const opened = fs.fstatSync(fd);
120
128
  if (!opened.isFile())
121
129
  throw new ProjectionStatusFileError("non-regular-projection-file", filePath);
122
- if (opened.size > MAX_STATUS_FILE_BYTES) {
123
- throw new ProjectionStatusFileError("oversized-projection-file", filePath);
124
- }
125
- const chunks = [];
130
+ const snapshotSize = opened.size;
131
+ const truncated = snapshotSize > MAX_STATUS_FILE_BYTES;
132
+ const readLength = Math.min(snapshotSize, MAX_STATUS_FILE_BYTES);
133
+ const readOffset = Math.max(0, snapshotSize - MAX_STATUS_FILE_BYTES);
134
+ const buffer = Buffer.allocUnsafe(readLength);
126
135
  let total = 0;
127
- while (total <= MAX_STATUS_FILE_BYTES) {
128
- const chunk = Buffer.allocUnsafe(Math.min(64 * 1024, MAX_STATUS_FILE_BYTES + 1 - total));
129
- const bytesRead = fs.readSync(fd, chunk, 0, chunk.length, null);
136
+ while (total < readLength) {
137
+ const bytesRead = fs.readSync(fd, buffer, total, readLength - total, readOffset + total);
130
138
  if (bytesRead === 0)
131
139
  break;
132
- chunks.push(chunk.subarray(0, bytesRead));
133
140
  total += bytesRead;
134
141
  }
135
- if (total > MAX_STATUS_FILE_BYTES) {
136
- throw new ProjectionStatusFileError("oversized-projection-file", filePath);
142
+ let tail = buffer.subarray(0, total);
143
+ if (truncated) {
144
+ const firstLf = tail.indexOf(0x0a);
145
+ if (firstLf < 0) {
146
+ return { content: "", truncated: true };
147
+ }
148
+ tail = tail.subarray(firstLf + 1);
137
149
  }
138
- return Buffer.concat(chunks, total).toString("utf8");
150
+ return { content: tail.toString("utf8"), truncated };
139
151
  }
140
152
  finally {
141
153
  fs.closeSync(fd);
@@ -196,8 +208,12 @@ function readHeartTellCounts(projectionDir, diagnostics) {
196
208
  }
197
209
  function readStoredEvents(projectionDir, executionId, diagnostics) {
198
210
  let content;
211
+ let truncated = false;
212
+ const eventsPath = executionEventsPath(projectionDir, executionId);
199
213
  try {
200
- content = readBoundedRegularFile(executionEventsPath(projectionDir, executionId));
214
+ const read = readBoundedRegularFileTail(eventsPath);
215
+ content = read.content;
216
+ truncated = read.truncated;
201
217
  }
202
218
  catch (error) {
203
219
  if (isErrnoException(error) && error.code === "ENOENT")
@@ -207,8 +223,12 @@ function readStoredEvents(projectionDir, executionId, diagnostics) {
207
223
  diagnostics.push({ kind: "unreadable-activity", detail: `activity ledger unreadable: ${String(error)}` });
208
224
  return [];
209
225
  }
226
+ if (truncated) {
227
+ diagnostics.push(oversizedProjectionFileDiagnostic(eventsPath));
228
+ }
229
+ // Parse only LF-terminated records. An unterminated final line is ignored even
230
+ // when it happens to parse: it may be a concurrent append fragment.
210
231
  const lines = content.split("\n");
211
- const terminated = content.endsWith("\n");
212
232
  const values = [];
213
233
  let malformed = 0;
214
234
  for (const line of lines.slice(0, -1)) {
@@ -219,15 +239,6 @@ function readStoredEvents(projectionDir, executionId, diagnostics) {
219
239
  malformed += 1;
220
240
  }
221
241
  }
222
- const finalLine = lines.at(-1);
223
- if (!terminated && finalLine) {
224
- try {
225
- values.push(JSON.parse(finalLine));
226
- }
227
- catch {
228
- // A concurrent append can leave only this final record incomplete.
229
- }
230
- }
231
242
  if (malformed > 0) {
232
243
  diagnostics.push({
233
244
  kind: "invalid-activity-event",
@@ -371,6 +382,8 @@ export function readProjectionStatusBoard(cwd, nowMs, options = {}) {
371
382
  catch {
372
383
  continue;
373
384
  }
385
+ if (isProjectionGenerationSchemaV1Residue(dir))
386
+ continue;
374
387
  rows.push(readProjectionRow(dir, id, aliases.get(id), nowMs));
375
388
  }
376
389
  }
@@ -4,7 +4,7 @@ import { readProjectionIdentity } from "./projection-identity.js";
4
4
  import { PROJECTION_ADOPTION_TIMEOUT_MS } from "./projection-generation-launcher.js";
5
5
  import { observeProjectionLifeSnapshot } from "./projection-life-observer.js";
6
6
  import { selectCurrentGeneration } from "./projection-life-protocol.js";
7
- import { readProjectionTellCounts } from "./projection-tells.js";
7
+ import { readProjectionTellCounts } from "./projection/tell/store.js";
8
8
  import { invalidActivityEventDiagnostic, selectValidStoredAgentEvents, } from "./stored-agent-event.js";
9
9
  const WAIT_POLL_MS = 100;
10
10
  function sleep(ms) {
@@ -39,7 +39,7 @@ function relativeEventLedgerFile(executionId) {
39
39
  }
40
40
  function timestampFact(current, nowMs) {
41
41
  const facts = current.verdict?.facts;
42
- for (const key of ["completedAt", "failedAt", "dismissedAt"]) {
42
+ for (const key of ["completedAt", "failedAt", "dismissedAt", "killedAt"]) {
43
43
  const value = facts?.[key];
44
44
  if (typeof value === "string" && Number.isFinite(Date.parse(value)))
45
45
  return value;
@@ -76,6 +76,14 @@ function buildSnapshot(input) {
76
76
  const invalidActivity = invalidActivityEventDiagnostic(malformedEvents);
77
77
  const responsePath = current?.verdict?.facts.responsePath;
78
78
  const artifactId = current?.verdict?.facts.artifactId;
79
+ const sourceArtifactId = current?.verdict?.facts.sourceArtifactId;
80
+ const errorFact = current?.verdict?.facts.error;
81
+ const failureCode = errorFact
82
+ && typeof errorFact === "object"
83
+ && !Array.isArray(errorFact)
84
+ && typeof errorFact.code === "string"
85
+ ? errorFact.code
86
+ : undefined;
79
87
  return {
80
88
  phase,
81
89
  observedAtMs: Number.isFinite(anchorMs) ? anchorMs : nowMs,
@@ -93,6 +101,8 @@ function buildSnapshot(input) {
93
101
  } : {}),
94
102
  ...(typeof responsePath === "string" && responsePath.trim() ? { responsePath } : {}),
95
103
  ...(typeof artifactId === "string" && artifactId.trim() ? { artifactId } : {}),
104
+ ...(typeof sourceArtifactId === "string" && sourceArtifactId.trim() ? { sourceArtifactId } : {}),
105
+ ...(failureCode ? { failureCode } : {}),
96
106
  };
97
107
  }
98
108
  export async function waitForProjection(input) {
@@ -1,10 +1,10 @@
1
1
  import { FlowError } from "../flow-error.js";
2
- import { parseResolvedAkumaLaunchSnapshot } from "../agents/launch-snapshot_v2.js";
2
+ import { parseResolvedAkumaLaunchSnapshot } from "../agents/launch-snapshot/model.js";
3
3
  import { ProjectionStateError } from "./atomic-publish.js";
4
4
  import { applyContinuationEffort, snapshotPendingTells, } from "./projection-generation-continuation.js";
5
5
  import { createProjectionExecutionId } from "./projection-generation-identity.js";
6
6
  import { startProjectionGeneration } from "./projection-generation-launcher.js";
7
- import { openProjectionGenerationStore, openProjectionGenerationStoreReadOnly, } from "./projection-generation-store.js";
7
+ import { openProjectionGenerationStore, openProjectionGenerationStoreReadOnly, } from "./projection/generation/store.js";
8
8
  import { PROJECTION_ADOPTION_TIMEOUT_MS } from "./projection-generation-launcher.js";
9
9
  import { observeProjectionLife } from "./projection-life-observer.js";
10
10
  import { observeProjectionRunnerLock } from "./projection-runner-lock.js";
@@ -59,6 +59,9 @@ export function assertTellEffortSupported(projectionDirectory, effort) {
59
59
  catch (error) {
60
60
  throw new ProjectionStateError("generation launch has an invalid immutable launchSnapshot", { cause: error });
61
61
  }
62
+ if (launchSnapshot.akuma.model === undefined) {
63
+ throw new FlowError("INVALID_EFFORT_OVERRIDE", `Effort '${effort}' requires an explicit model.`);
64
+ }
62
65
  const effortOptions = launchSnapshot.akuma.effortOptions;
63
66
  if (effortOptions === undefined)
64
67
  return;
@@ -2,7 +2,8 @@ import { createHash } from "node:crypto";
2
2
  import { FlowError } from "../flow-error.js";
3
3
  import { createGit, errorContainsAnyPattern, MISSING_HEAD_PATTERNS, wrapGitError } from "../git/core.js";
4
4
  import { listContractIds, readLedger } from "./ledger.js";
5
- import { commissionTarget, deriveContractState } from "./status.js";
5
+ import { deriveContractState } from "./status/lifecycle.js";
6
+ import { commissionTarget } from "./status/drift.js";
6
7
  const EMPTY_TREE_HASH = "4b825dc642cb6eb9a060e54bf8d69288fbee4904";
7
8
  const MISSING_REF_SENTINEL = "0".repeat(40);
8
9
  const ISO_UTC_RE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z$/;
@@ -2,7 +2,7 @@ import { FlowError } from "../flow-error.js";
2
2
  import { readLedger } from "./ledger.js";
3
3
  import { appendRefLogWithRetry } from "./ref-log.js";
4
4
  import { isNegativeScopePattern, patternsOverlap } from "./scope.js";
5
- import { deriveContractState, isTerminalState } from "./status.js";
5
+ import { deriveContractState, isTerminalState } from "./status/lifecycle.js";
6
6
  export const REGISTRY_REF = "refs/keiyaku/registry";
7
7
  function parseRegistryEntry(message) {
8
8
  const body = message.slice(message.indexOf("\n\n") + 2).trim();