@astrosheep/keiyaku 4.5.7 → 4.5.9

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 (244) hide show
  1. package/README.md +10 -0
  2. package/build/integrations/marketplace/plugins/keiyaku/.claude-plugin/plugin.json +1 -1
  3. package/build/integrations/marketplace/plugins/keiyaku/.codex-plugin/plugin.json +1 -1
  4. package/build/integrations/marketplace/plugins/keiyaku/opencode.js +3 -2
  5. package/build/integrations/marketplace/plugins/keiyaku/package.json +1 -1
  6. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku/SKILL.md +12 -11
  7. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-akuma/SKILL.md +33 -12
  8. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-babysit/SKILL.md +35 -96
  9. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-bind/SKILL.md +28 -49
  10. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-task/SKILL.md +21 -15
  11. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-workflow/SKILL.md +148 -89
  12. package/build/src/akuma/abort.d.ts +2 -1
  13. package/build/src/akuma/abort.js +6 -23
  14. package/build/src/akuma/akuma-handle.js +4 -2
  15. package/build/src/akuma/akuma-observe.d.ts +2 -0
  16. package/build/src/akuma/akuma-observe.js +1 -0
  17. package/build/src/akuma/akuma-product.js +9 -7
  18. package/build/src/akuma/akuma.d.ts +392 -9
  19. package/build/src/akuma/akuma.js +31 -1
  20. package/build/src/akuma/allowed.d.ts +1 -1
  21. package/build/src/akuma/allowed.js +1 -0
  22. package/build/src/akuma/archetype.js +1 -1
  23. package/build/src/akuma/body.d.ts +8 -5
  24. package/build/src/akuma/body.js +12 -5
  25. package/build/src/akuma/call-request.d.ts +80 -0
  26. package/build/src/akuma/call-request.js +211 -0
  27. package/build/src/akuma/heart/facts.d.ts +16 -78
  28. package/build/src/akuma/heart/facts.js +11 -0
  29. package/build/src/akuma/heart/index.d.ts +3 -2
  30. package/build/src/akuma/heart/index.js +4 -1
  31. package/build/src/akuma/heart/request-authority.d.ts +5 -4
  32. package/build/src/akuma/heart/request-authority.js +20 -34
  33. package/build/src/akuma/heart/request-rows.js +12 -23
  34. package/build/src/akuma/heart/schema.d.ts +1 -1
  35. package/build/src/akuma/heart/schema.js +5 -13
  36. package/build/src/akuma/index.d.ts +1 -1
  37. package/build/src/akuma/index.js +1 -1
  38. package/build/src/akuma/projection-read.d.ts +1 -0
  39. package/build/src/akuma/projection-read.js +8 -1
  40. package/build/src/akuma/projection.d.ts +907 -137
  41. package/build/src/akuma/projection.js +193 -9
  42. package/build/src/akuma/provider.d.ts +30 -3
  43. package/build/src/akuma/provider.js +111 -0
  44. package/build/src/akuma/providers/acp/core.d.ts +2 -2
  45. package/build/src/akuma/providers/acp/core.js +13 -6
  46. package/build/src/akuma/providers/acp/index.js +5 -4
  47. package/build/src/akuma/providers/claude/events.d.ts +2 -2
  48. package/build/src/akuma/providers/claude/events.js +16 -3
  49. package/build/src/akuma/providers/claude/index.d.ts +3 -3
  50. package/build/src/akuma/providers/claude/index.js +16 -5
  51. package/build/src/akuma/providers/codex-app-server/events.d.ts +2 -2
  52. package/build/src/akuma/providers/codex-app-server/events.js +25 -4
  53. package/build/src/akuma/providers/codex-app-server/index.js +18 -6
  54. package/build/src/akuma/providers/grok-build/index.js +5 -4
  55. package/build/src/akuma/providers/opencode-sdk/index.js +66 -25
  56. package/build/src/akuma/providers/opencode-sdk/session.d.ts +3 -2
  57. package/build/src/akuma/providers/opencode-sdk/session.js +28 -11
  58. package/build/src/akuma/providers/pi/index.js +72 -58
  59. package/build/src/akuma/request-lifecycle.d.ts +4 -10
  60. package/build/src/akuma/request-lifecycle.js +7 -5
  61. package/build/src/akuma/request-rendezvous.d.ts +26 -0
  62. package/build/src/akuma/request-rendezvous.js +100 -0
  63. package/build/src/akuma/request-serve.d.ts +6 -66
  64. package/build/src/akuma/request-serve.js +83 -210
  65. package/build/src/akuma/request-wire.d.ts +109 -115
  66. package/build/src/akuma/request-wire.js +63 -318
  67. package/build/src/akuma/requests.d.ts +19 -47
  68. package/build/src/akuma/requests.js +51 -107
  69. package/build/src/akuma/turn-drive.d.ts +5 -3
  70. package/build/src/akuma/turn-drive.js +39 -11
  71. package/build/src/akuma-body.d.ts +7 -2
  72. package/build/src/akuma-body.js +68 -46
  73. package/build/src/body/amend.js +18 -19
  74. package/build/src/cli/accepted.d.ts +1 -1
  75. package/build/src/cli/accepted.js +2 -2
  76. package/build/src/cli/commands/akuma-invoke.d.ts +2 -0
  77. package/build/src/cli/commands/akuma-invoke.js +17 -9
  78. package/build/src/cli/commands/akuma.js +8 -8
  79. package/build/src/cli/commands/contract-invoke.d.ts +3 -2
  80. package/build/src/cli/commands/contract-invoke.js +42 -30
  81. package/build/src/cli/commands/contract.d.ts +16 -20
  82. package/build/src/cli/commands/contract.js +58 -25
  83. package/build/src/cli/commands/install.d.ts +2 -1
  84. package/build/src/cli/commands/install.js +3 -2
  85. package/build/src/cli/commands/task-invoke.d.ts +3 -0
  86. package/build/src/cli/commands/task-invoke.js +23 -140
  87. package/build/src/cli/commands/task.js +54 -20
  88. package/build/src/cli/invoke.js +39 -25
  89. package/build/src/cli/main.js +2 -1
  90. package/build/src/cli/parse.d.ts +2 -2
  91. package/build/src/cli/parse.js +56 -27
  92. package/build/src/cli/render/akuma-activity.d.ts +3 -0
  93. package/build/src/cli/render/akuma-activity.js +45 -29
  94. package/build/src/cli/render/akuma-tool.d.ts +5 -1
  95. package/build/src/cli/render/akuma.js +4 -8
  96. package/build/src/cli/render/audit.js +1 -1
  97. package/build/src/cli/render/catalog.js +80 -21
  98. package/build/src/cli/render/contract-observation.d.ts +2 -0
  99. package/build/src/cli/render/contract-observation.js +11 -0
  100. package/build/src/cli/render/contract.js +35 -62
  101. package/build/src/cli/render/kanshi-akuma.d.ts +8 -0
  102. package/build/src/cli/render/kanshi-akuma.js +132 -0
  103. package/build/src/cli/render/kanshi.js +131 -213
  104. package/build/src/cli/render/receipt.d.ts +1 -2
  105. package/build/src/cli/render/receipt.js +76 -26
  106. package/build/src/cli/render/refusal.js +2 -10
  107. package/build/src/cli/render/region.js +5 -2
  108. package/build/src/cli/render/settings.d.ts +1 -1
  109. package/build/src/cli/render/settings.js +27 -8
  110. package/build/src/cli/render/task.js +22 -5
  111. package/build/src/cli/render/terminal.d.ts +32 -1
  112. package/build/src/cli/render/terminal.js +77 -1
  113. package/build/src/cli/result.d.ts +3 -3
  114. package/build/src/cli/runtime.js +4 -2
  115. package/build/src/cli/selectors.d.ts +2 -1
  116. package/build/src/cli/selectors.js +4 -3
  117. package/build/src/cli/square-edge.js +120 -36
  118. package/build/src/cli/usage.js +2 -1
  119. package/build/src/contract-guidance.d.ts +5 -0
  120. package/build/src/contract-guidance.js +121 -0
  121. package/build/src/contract-worktree.d.ts +0 -1
  122. package/build/src/contract-worktree.js +32 -83
  123. package/build/src/coordination/sqlite-transaction-lock.d.ts +1 -0
  124. package/build/src/coordination/sqlite-transaction-lock.js +5 -0
  125. package/build/src/core/facts/codec.d.ts +2 -0
  126. package/build/src/core/facts/codec.js +4 -0
  127. package/build/src/core/verbs/placement.d.ts +6 -1
  128. package/build/src/core/verbs/placement.js +5 -3
  129. package/build/src/dispatch/index.d.ts +0 -2
  130. package/build/src/dispatch/index.js +54 -55
  131. package/build/src/git/admission.js +2 -2
  132. package/build/src/git/hooks.d.ts +9 -15
  133. package/build/src/git/hooks.js +23 -317
  134. package/build/src/git/nuke.d.ts +2 -1
  135. package/build/src/git/nuke.js +39 -21
  136. package/build/src/git/private-state-seat.d.ts +12 -0
  137. package/build/src/git/private-state-seat.js +39 -0
  138. package/build/src/git/process.d.ts +6 -0
  139. package/build/src/git/process.js +26 -11
  140. package/build/src/git/read-observation.js +29 -20
  141. package/build/src/git/reconcile.d.ts +13 -1
  142. package/build/src/git/reconcile.js +70 -11
  143. package/build/src/git/ref-migration.d.ts +22 -0
  144. package/build/src/git/ref-migration.js +55 -0
  145. package/build/src/git/repository.d.ts +10 -6
  146. package/build/src/git/repository.js +16 -8
  147. package/build/src/git/target-placement.d.ts +1 -0
  148. package/build/src/git/target-placement.js +5 -0
  149. package/build/src/git/tender.d.ts +1 -0
  150. package/build/src/git/terminal-reconcile.d.ts +1 -1
  151. package/build/src/git/terminal-reconcile.js +22 -20
  152. package/build/src/git/workspace.d.ts +14 -0
  153. package/build/src/git/workspace.js +44 -0
  154. package/build/src/kanshi/index.d.ts +1 -1
  155. package/build/src/kanshi/read.js +16 -16
  156. package/build/src/kanshi/report.d.ts +2 -2
  157. package/build/src/library/address.d.ts +9 -3
  158. package/build/src/library/address.js +3 -2
  159. package/build/src/library/akuma-creation.d.ts +4 -3
  160. package/build/src/library/akuma-creation.js +19 -13
  161. package/build/src/library/audit.d.ts +14 -6
  162. package/build/src/library/audit.js +13 -23
  163. package/build/src/library/catalog.d.ts +3 -1
  164. package/build/src/library/catalog.js +20 -6
  165. package/build/src/library/composition.d.ts +385 -0
  166. package/build/src/library/composition.js +28 -0
  167. package/build/src/library/configuration.js +8 -2
  168. package/build/src/library/continuation.js +66 -30
  169. package/build/src/library/contract-bind.js +10 -2
  170. package/build/src/library/contract-forwarding-reconciliation-result.d.ts +102 -0
  171. package/build/src/library/contract-forwarding-reconciliation-result.js +92 -0
  172. package/build/src/library/contract-forwarding-result.d.ts +4464 -0
  173. package/build/src/library/contract-forwarding-result.js +521 -0
  174. package/build/src/library/contract-forwarding.d.ts +71 -0
  175. package/build/src/library/contract-forwarding.js +114 -0
  176. package/build/src/library/contract-handle.d.ts +38 -0
  177. package/build/src/library/contract-handle.js +314 -0
  178. package/build/src/library/contract-operations.d.ts +214 -69
  179. package/build/src/library/contract-operations.js +189 -118
  180. package/build/src/library/contract-types.d.ts +7 -3
  181. package/build/src/library/contract.d.ts +21 -34
  182. package/build/src/library/contract.js +29 -287
  183. package/build/src/library/fleet-result.d.ts +1580 -0
  184. package/build/src/library/fleet-result.js +98 -0
  185. package/build/src/library/fleet.d.ts +112 -54
  186. package/build/src/library/fleet.js +211 -71
  187. package/build/src/library/input.d.ts +3 -1
  188. package/build/src/library/input.js +11 -1
  189. package/build/src/library/keiyaku.d.ts +388 -20
  190. package/build/src/library/keiyaku.js +13 -21
  191. package/build/src/library/mutation.d.ts +3 -3
  192. package/build/src/library/mutation.js +5 -9
  193. package/build/src/library/nuke.d.ts +1 -1
  194. package/build/src/library/nuke.js +4 -3
  195. package/build/src/library/reconcile.d.ts +4 -0
  196. package/build/src/library/reconcile.js +2 -0
  197. package/build/src/library/refusal.d.ts +0 -9
  198. package/build/src/library/refusal.js +0 -7
  199. package/build/src/protocol/amend.js +56 -50
  200. package/build/src/protocol/attempt.d.ts +2 -0
  201. package/build/src/protocol/attempt.js +8 -1
  202. package/build/src/protocol/completion.js +7 -0
  203. package/build/src/protocol/deliver.js +6 -2
  204. package/build/src/protocol/operations.d.ts +4 -1
  205. package/build/src/protocol/operations.js +5 -2
  206. package/build/src/protocol/placement.d.ts +1 -0
  207. package/build/src/protocol/placement.js +52 -38
  208. package/build/src/protocol/read/observation.d.ts +1 -4
  209. package/build/src/protocol/read/observation.js +2 -15
  210. package/build/src/protocol/reintegrate.js +62 -58
  211. package/build/src/protocol/review.js +5 -0
  212. package/build/src/protocol/run.js +17 -13
  213. package/build/src/runtime/proc/run.d.ts +9 -0
  214. package/build/src/runtime/proc/run.js +51 -4
  215. package/build/src/runtime/proc/termination.js +26 -22
  216. package/build/src/runtime/proc/windows-launch.exe +0 -0
  217. package/build/src/settlement/holder.d.ts +2 -1
  218. package/build/src/settlement/holder.js +15 -4
  219. package/build/src/settlement/settle.js +99 -33
  220. package/build/src/task/board.d.ts +68 -27
  221. package/build/src/task/board.js +31 -1
  222. package/build/src/task/catalog.d.ts +4 -0
  223. package/build/src/task/catalog.js +23 -0
  224. package/build/src/task/compose-language.js +33 -7
  225. package/build/src/task/compose-parser.d.ts +1 -1
  226. package/build/src/task/compose-parser.js +1 -1
  227. package/build/src/task/compose.js +2 -0
  228. package/build/src/task/index.d.ts +12 -4
  229. package/build/src/task/index.js +97 -16
  230. package/build/src/task/mutation-result.d.ts +923 -0
  231. package/build/src/task/mutation-result.js +140 -0
  232. package/build/src/task/mutation.d.ts +552 -28
  233. package/build/src/task/mutation.js +304 -97
  234. package/build/src/task/operations.d.ts +1 -4
  235. package/build/src/task/operations.js +0 -6
  236. package/build/src/task/public.d.ts +2 -0
  237. package/build/src/task/public.js +1 -0
  238. package/build/src/workspace-place.d.ts +7 -2
  239. package/build/src/workspace-place.js +34 -24
  240. package/build/src/world.d.ts +1 -0
  241. package/build/src/world.js +11 -0
  242. package/package.json +8 -6
  243. package/build/src/akuma/request-execution.d.ts +0 -8
  244. package/build/src/akuma/request-execution.js +0 -98
@@ -1,4 +1,5 @@
1
1
  import { extendAdmissionPathsAt, observeContractsForAdmissionAt } from "../git/observe.js";
2
+ import { withPrivateStatePublicationSeat } from "../git/private-state-seat.js";
2
3
  import { admitDecidedOffer } from "./attempt.js";
3
4
  /** Form one decided offer without admitting or reinterpreting it. */
4
5
  export async function prepareProtocolAttempt(input, baseAttempt) {
@@ -41,20 +42,23 @@ export async function prepareProtocolAttempt(input, baseAttempt) {
41
42
  export async function runProtocol(input) {
42
43
  const attempts = input.attempts;
43
44
  for (let index = 0; index < attempts.length; index += 1) {
44
- const prepared = await prepareProtocolAttempt(input, attempts[index]);
45
- if (prepared.kind === "refused")
46
- return prepared;
47
- const result = await admitDecidedOffer({
48
- channel: input.channel,
49
- repository: input.repository,
50
- decisionObservation: prepared.observation,
51
- attempt: prepared.attempt,
52
- offer: prepared.offer,
53
- primaryContract: input.input.contractId,
54
- assertions: prepared.assertions,
55
- ...(input.validateAdmission === undefined ? {} : { validateAdmission: input.validateAdmission }),
45
+ const result = await withPrivateStatePublicationSeat(input.repository, async (seat) => {
46
+ const prepared = await prepareProtocolAttempt(input, attempts[index]);
47
+ if (prepared.kind === "refused")
48
+ return prepared;
49
+ return await admitDecidedOffer({
50
+ channel: input.channel,
51
+ repository: input.repository,
52
+ seat,
53
+ decisionObservation: prepared.observation,
54
+ attempt: prepared.attempt,
55
+ offer: prepared.offer,
56
+ primaryContract: input.input.contractId,
57
+ assertions: prepared.assertions,
58
+ ...(input.validateAdmission === undefined ? {} : { validateAdmission: input.validateAdmission }),
59
+ });
56
60
  });
57
- if (result.kind === "accepted" || result.kind === "publication-failed" || result.kind === "refused")
61
+ if (result.kind === "refused" || result.kind === "accepted" || result.kind === "publication-failed")
58
62
  return result;
59
63
  if (result.kind === "collision" && index + 1 === attempts.length)
60
64
  return result;
@@ -1,3 +1,4 @@
1
+ import { type ChildProcess } from "node:child_process";
1
2
  import { type DetachedProcessInput } from "./launch.js";
2
3
  import type { OwnedProcess } from "./types.js";
3
4
  export type { DetachedProcessInput } from "./launch.js";
@@ -8,6 +9,14 @@ type ProcessLaunch = Readonly<{
8
9
  readonly env?: NodeJS.ProcessEnv;
9
10
  readonly signal?: AbortSignal;
10
11
  }>;
12
+ export type CancellableProcess = Readonly<{
13
+ child: ChildProcess;
14
+ cancelled(): boolean;
15
+ terminationFailure: Promise<never>;
16
+ terminate(force?: boolean): Promise<void>;
17
+ waitTermination(): Promise<void>;
18
+ }>;
19
+ export declare function spawnCancellableProcess(input: ProcessLaunch): CancellableProcess;
11
20
  export type ProcessInput = ProcessLaunch & Readonly<{
12
21
  readonly timeoutMs?: number;
13
22
  }>;
@@ -7,6 +7,49 @@ import { createProcessLifecycle } from "./lifecycle.js";
7
7
  import { terminateOwnedProcess } from "./termination.js";
8
8
  import { spawnWindowsRetainedProcess } from "./windows-run.js";
9
9
  const STREAM_TAIL_BYTES = 16 * 1024;
10
+ function waitForProcessEnd(child, terminationFailure) {
11
+ const childEnd = new Promise((resolve) => {
12
+ child.once("close", (code) => resolve({ kind: "closed", code }));
13
+ child.once("error", (error) => resolve({ kind: "spawn-error", error }));
14
+ });
15
+ return Promise.race([childEnd, terminationFailure]);
16
+ }
17
+ export function spawnCancellableProcess(input) {
18
+ const child = spawn(input.argv[0], input.argv.slice(1), spawnOptionsFor(input, ["pipe", "pipe", "pipe"]));
19
+ let cancelled = false;
20
+ let termination;
21
+ let reportTerminationFailure;
22
+ const terminationFailure = new Promise((_resolve, reject) => {
23
+ reportTerminationFailure = reject;
24
+ });
25
+ void terminationFailure.catch(() => undefined);
26
+ const terminate = (force = false) => {
27
+ if (termination === undefined) {
28
+ termination = terminateOwnedProcess(child, force);
29
+ void termination.catch(reportTerminationFailure);
30
+ }
31
+ return termination;
32
+ };
33
+ const cancel = () => {
34
+ if (cancelled)
35
+ return;
36
+ cancelled = true;
37
+ void terminate();
38
+ };
39
+ input.signal?.addEventListener("abort", cancel, { once: true });
40
+ if (input.signal?.aborted === true)
41
+ cancel();
42
+ return {
43
+ child,
44
+ cancelled: () => cancelled,
45
+ terminationFailure,
46
+ terminate,
47
+ async waitTermination() {
48
+ input.signal?.removeEventListener("abort", cancel);
49
+ await termination;
50
+ },
51
+ };
52
+ }
10
53
  function tailCapture(limit) {
11
54
  const bytes = Buffer.allocUnsafe(limit);
12
55
  let total = 0;
@@ -116,11 +159,16 @@ async function executeProcess(input, timeoutMs, consumeStdout, spawnProcess = sp
116
159
  let stop;
117
160
  let streamError;
118
161
  let termination;
162
+ let reportTerminationFailure;
163
+ const terminationFailure = new Promise((resolve) => {
164
+ reportTerminationFailure = (error) => resolve({ kind: "termination-error", error });
165
+ });
119
166
  const requestStop = (reason) => {
120
167
  if (stop !== undefined)
121
168
  return;
122
169
  stop = reason;
123
170
  termination = terminateOwnedProcess(child);
171
+ void termination.catch(reportTerminationFailure);
124
172
  };
125
173
  const failStream = (error) => {
126
174
  if (streamError !== undefined)
@@ -148,13 +196,12 @@ async function executeProcess(input, timeoutMs, consumeStdout, spawnProcess = sp
148
196
  const cancel = () => requestStop("cancelled");
149
197
  input.signal?.addEventListener("abort", cancel, { once: true });
150
198
  const timeout = timeoutMs === undefined ? undefined : setTimeout(() => requestStop("timeout"), timeoutMs);
151
- const terminal = await new Promise((resolve) => {
152
- child.once("close", (code) => resolve({ kind: "closed", code }));
153
- child.once("error", (error) => resolve({ kind: "spawn-error", error }));
154
- });
199
+ const terminal = await waitForProcessEnd(child, terminationFailure);
155
200
  if (timeout !== undefined)
156
201
  clearTimeout(timeout);
157
202
  input.signal?.removeEventListener("abort", cancel);
203
+ if (terminal.kind === "termination-error")
204
+ throw terminal.error;
158
205
  await termination;
159
206
  const pid = child.pid ?? null;
160
207
  if (terminal.kind === "spawn-error") {
@@ -5,9 +5,25 @@ const TERMINATION_GRACE_MS = 250;
5
5
  const WINDOWS_TERMINATION_SETTLE_MS = 250;
6
6
  const WINDOWS_TERMINATION_TIMEOUT_MS = 1_000;
7
7
  const execFileAsync = promisify(execFile);
8
- function ignoreMissingProcess(error) {
9
- if (error.code !== "ESRCH")
10
- throw error;
8
+ function isMissingProcess(error) {
9
+ return error.code === "ESRCH";
10
+ }
11
+ async function signalOwnedProcessGroup(pid, signal, exit) {
12
+ try {
13
+ process.kill(-pid, signal);
14
+ }
15
+ catch (error) {
16
+ if (isMissingProcess(error))
17
+ return;
18
+ if (error.code !== "EPERM")
19
+ throw error;
20
+ const exited = await Promise.race([
21
+ exit.then(() => true),
22
+ delay(TERMINATION_GRACE_MS, false, { ref: false }).then(() => false),
23
+ ]);
24
+ if (!exited)
25
+ throw error;
26
+ }
11
27
  }
12
28
  export async function terminateWindowsTree(pid) {
13
29
  try {
@@ -53,6 +69,9 @@ export async function terminateOwnedProcess(child, force = false) {
53
69
  resolve();
54
70
  });
55
71
  });
72
+ await new Promise((resolve) => setImmediate(resolve));
73
+ if (exited || child.exitCode !== null || child.signalCode !== null)
74
+ return;
56
75
  if (process.platform === "win32") {
57
76
  try {
58
77
  await terminateWindowsTree(pid);
@@ -64,31 +83,16 @@ export async function terminateOwnedProcess(child, force = false) {
64
83
  return;
65
84
  }
66
85
  if (force) {
67
- try {
68
- process.kill(-pid, "SIGKILL");
69
- }
70
- catch (error) {
71
- ignoreMissingProcess(error);
72
- }
86
+ await signalOwnedProcessGroup(pid, "SIGKILL", exit);
73
87
  await exit;
74
88
  return;
75
89
  }
76
- try {
77
- process.kill(-pid, "SIGTERM");
78
- }
79
- catch (error) {
80
- ignoreMissingProcess(error);
81
- await exit;
90
+ await signalOwnedProcessGroup(pid, "SIGTERM", exit);
91
+ if (exited || child.exitCode !== null || child.signalCode !== null)
82
92
  return;
83
- }
84
93
  await Promise.race([exit, delay(TERMINATION_GRACE_MS)]);
85
94
  if (exited || child.exitCode !== null || child.signalCode !== null)
86
95
  return;
87
- try {
88
- process.kill(-pid, "SIGKILL");
89
- }
90
- catch (error) {
91
- ignoreMissingProcess(error);
92
- }
96
+ await signalOwnedProcessGroup(pid, "SIGKILL", exit);
93
97
  await exit;
94
98
  }
@@ -1,6 +1,7 @@
1
1
  import type { TreeUpdate } from "../core/facts/offer.js";
2
2
  import { type ContractId } from "../core/facts/types.js";
3
3
  import type { GitDecisionObservation } from "../git/observe.js";
4
+ import { type PrivateStatePublicationSeat } from "../git/private-state-seat.js";
4
5
  import type { GitRepository } from "../git/process.js";
5
6
  import { type GitDecodeChannel, type GitReadObservation, type GitTreeSelection } from "../git/read-observation.js";
6
7
  import { type TaskId } from "../task/identity.js";
@@ -37,7 +38,7 @@ export type TaskHolderReleasePublication = Readonly<{
37
38
  diagnostic: string;
38
39
  }>;
39
40
  /** Publish the released holder against the same frozen observation that decided the release. */
40
- export declare function publishTaskHolderRelease(repository: GitRepository, channel: GitDecodeChannel, observation: GitDecisionObservation, owner: ContractId): Promise<TaskHolderReleasePublication>;
41
+ export declare function publishTaskHolderRelease(repository: GitRepository, channel: GitDecodeChannel, observation: GitDecisionObservation, owner: ContractId, seat: PrivateStatePublicationSeat): Promise<TaskHolderReleasePublication>;
41
42
  export declare function readTaskHolderProjectionFromDecision(channel: GitDecodeChannel, observation: GitDecisionObservation): Promise<TaskHolderProjection>;
42
43
  export declare function observeTaskHolderProjection(repository: GitRepository, channel: GitDecodeChannel): Promise<TaskHolderProjection>;
43
44
  export declare function releaseTaskHolderWithFence<T extends AdmissionOutcome>(repository: GitRepository, channel: GitDecodeChannel, owner: ContractId, action: () => T | Promise<T>): Promise<TaskHolderAdmission<T>>;
@@ -1,7 +1,8 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { AuthorityCorruptionError } from "../core/facts/errors.js";
3
3
  import { contractId } from "../core/facts/types.js";
4
- import { GIT_REF, updateGitTree, updateRefsAtomically, writeBlob, writeCommit } from "../git/repository.js";
4
+ import { GIT_REF, updateGitTree, updateRefsAtomically, writeBlob, writeStateCommit } from "../git/repository.js";
5
+ import { confirmPrivateStatePublication } from "../git/private-state-seat.js";
5
6
  import { withGitTargetedReadObservation, } from "../git/read-observation.js";
6
7
  import { parseTaskId } from "../task/identity.js";
7
8
  import { acquireTaskSettlementFence } from "./fence.js";
@@ -121,13 +122,17 @@ export async function releaseTaskHolder(channel, observation, owner) {
121
122
  const current = (await readTaskHolderProjectionFromDecision(channel, observation)).get(owner) ?? null;
122
123
  return current === null || current.disposition !== "held" ? null : update({ ...current, disposition: "released" });
123
124
  }
125
+ async function releasedHolderIsCurrent(repository, channel, owner, expected) {
126
+ const current = (await observeTaskHolderProjection(repository, channel)).get(owner) ?? null;
127
+ return current?.disposition === "released" && Buffer.from(canonicalBytes(current)).equals(expected);
128
+ }
124
129
  /** Publish the released holder against the same frozen observation that decided the release. */
125
- export async function publishTaskHolderRelease(repository, channel, observation, owner) {
130
+ export async function publishTaskHolderRelease(repository, channel, observation, owner, seat) {
126
131
  const release = await releaseTaskHolder(channel, observation, owner);
127
132
  if (release === null)
128
133
  return { kind: "not-held" };
129
134
  const tree = await updateGitTree(repository, observation.admission.snapshot.tree, new Map([[release.path, { oid: await writeBlob(repository, release.bytes) }]]));
130
- const commit = await writeCommit({
135
+ const commit = await writeStateCommit({
131
136
  repository,
132
137
  tree,
133
138
  parent: observation.admission.snapshot.commit,
@@ -140,8 +145,14 @@ export async function publishTaskHolderRelease(repository, channel, observation,
140
145
  expectedOid: observation.admission.snapshot.commit,
141
146
  },
142
147
  ]);
143
- if (publication.kind === "published")
148
+ if (publication.kind === "published") {
149
+ confirmPrivateStatePublication(seat);
150
+ return { kind: "released" };
151
+ }
152
+ if (publication.kind === "unknown" && (await releasedHolderIsCurrent(repository, channel, owner, release.bytes))) {
153
+ confirmPrivateStatePublication(seat);
144
154
  return { kind: "released" };
155
+ }
145
156
  return {
146
157
  kind: "non-published",
147
158
  diagnostic: publication.kind === "non-published"
@@ -1,8 +1,10 @@
1
1
  import { contractSegment } from "../core/facts/types.js";
2
2
  import { observeContractsForAdmissionAt } from "../git/observe.js";
3
+ import { withPrivateStatePublicationSeat } from "../git/private-state-seat.js";
3
4
  import { repairNamespaceContext } from "../task/context.js";
4
5
  import { settleTask } from "../task/operations.js";
5
6
  import { publishTaskHolderRelease, readTaskHolderProjectionFromDecision, taskHolderObservationSelection, } from "./holder.js";
7
+ import { acquireTaskSettlementFence } from "./fence.js";
6
8
  import { World } from "../world.js";
7
9
  export function deferredTaskHolderSettlement(input) {
8
10
  return { actions: [], lags: [{ kind: "settlement-failed", surface: "task-holder", ...input }] };
@@ -18,11 +20,16 @@ function taskFailure(result) {
18
20
  ? `Task settlement requires retry: ${result.reason}`
19
21
  : `Task settlement refused: ${JSON.stringify(result.refusal)}`;
20
22
  }
21
- async function settleTasks(input) {
22
- const { repository, channel, world, candidate, actions, lags } = input;
23
+ async function observeApplicableHolder(input) {
24
+ const { repository, channel, candidate, lags } = input;
23
25
  let observation;
24
26
  try {
25
27
  observation = await observeContractsForAdmissionAt(repository, channel, [candidate.id], taskHolderObservationSelection());
28
+ const state = observation.journals.get(candidate.id)?.state ?? null;
29
+ if (state === null || state.terminal?.kind !== "claimed")
30
+ return null;
31
+ const holder = (await readTaskHolderProjectionFromDecision(channel, observation)).get(candidate.id) ?? null;
32
+ return holder?.disposition === "held" ? holder : null;
26
33
  }
27
34
  catch (error) {
28
35
  lags.push({
@@ -31,27 +38,25 @@ async function settleTasks(input) {
31
38
  contractId: candidate.id,
32
39
  diagnostic: diagnostic(error),
33
40
  });
34
- return;
41
+ return null;
35
42
  }
36
- const state = observation.journals.get(candidate.id)?.state ?? null;
37
- if (state === null || state.terminal?.kind !== "claimed")
38
- return;
39
- let holder;
43
+ }
44
+ async function completeHeldTask(input) {
45
+ const { repository, candidate, actions, lags, taskId } = input;
46
+ let world;
40
47
  try {
41
- holder = (await readTaskHolderProjectionFromDecision(channel, observation)).get(candidate.id) ?? null;
48
+ world = await World.at(repository.primaryWorktree);
42
49
  }
43
50
  catch (error) {
44
51
  lags.push({
45
52
  kind: "settlement-failed",
46
- surface: "task-holder",
53
+ surface: "task",
47
54
  contractId: candidate.id,
55
+ taskId,
48
56
  diagnostic: diagnostic(error),
49
57
  });
50
- return;
58
+ return false;
51
59
  }
52
- if (holder === null || holder.disposition !== "held")
53
- return;
54
- const taskId = holder.taskId;
55
60
  let result;
56
61
  try {
57
62
  result = await settleTask(world, taskId);
@@ -64,44 +69,104 @@ async function settleTasks(input) {
64
69
  taskId,
65
70
  diagnostic: diagnostic(error),
66
71
  });
67
- return;
72
+ return false;
68
73
  }
69
74
  if (result.kind === "changed")
70
75
  actions.push({ kind: "task", taskId, action: "done" });
71
- else if (result.kind !== "unchanged") {
76
+ if (result.kind === "changed" || result.kind === "unchanged")
77
+ return true;
78
+ lags.push({
79
+ kind: "settlement-failed",
80
+ surface: "task",
81
+ contractId: candidate.id,
82
+ taskId,
83
+ diagnostic: taskFailure(result),
84
+ });
85
+ return false;
86
+ }
87
+ async function releaseHeldTaskHolder(input) {
88
+ const { repository, channel, candidate, lags, taskId } = input;
89
+ try {
90
+ await withPrivateStatePublicationSeat(repository, async (seat) => {
91
+ const observation = await observeContractsForAdmissionAt(repository, channel, [candidate.id], taskHolderObservationSelection());
92
+ const state = observation.journals.get(candidate.id)?.state ?? null;
93
+ if (state === null || state.terminal?.kind !== "claimed")
94
+ return;
95
+ const holder = (await readTaskHolderProjectionFromDecision(channel, observation)).get(candidate.id) ?? null;
96
+ if (holder === null || holder.disposition !== "held" || holder.taskId !== taskId)
97
+ return;
98
+ const publication = await publishTaskHolderRelease(repository, channel, observation, candidate.id, seat);
99
+ if (publication.kind === "non-published") {
100
+ lags.push({
101
+ kind: "settlement-failed",
102
+ surface: "task-holder",
103
+ contractId: candidate.id,
104
+ taskId,
105
+ diagnostic: `Task holder release requires retry: ${publication.diagnostic}`,
106
+ });
107
+ }
108
+ });
109
+ }
110
+ catch (error) {
72
111
  lags.push({
73
112
  kind: "settlement-failed",
74
- surface: "task",
113
+ surface: "task-holder",
75
114
  contractId: candidate.id,
76
115
  taskId,
77
- diagnostic: taskFailure(result),
116
+ diagnostic: diagnostic(error),
78
117
  });
79
- return;
80
118
  }
119
+ }
120
+ async function settleTasks(input) {
121
+ const { repository, channel, candidate, actions, lags } = input;
122
+ const hint = await observeApplicableHolder({ repository, channel, candidate, lags });
123
+ if (hint === null)
124
+ return false;
125
+ const taskId = hint.taskId;
126
+ let fence;
81
127
  try {
82
- const publication = await publishTaskHolderRelease(repository, channel, observation, candidate.id);
83
- if (publication.kind === "non-published") {
84
- lags.push({
85
- kind: "settlement-failed",
86
- surface: "task-holder",
87
- contractId: candidate.id,
88
- taskId,
89
- diagnostic: `Task holder release requires retry: ${publication.diagnostic}`,
90
- });
91
- }
128
+ fence = await acquireTaskSettlementFence(repository, taskId);
92
129
  }
93
130
  catch (error) {
94
131
  lags.push({
95
132
  kind: "settlement-failed",
96
- surface: "task-holder",
133
+ surface: "task",
97
134
  contractId: candidate.id,
98
135
  taskId,
99
136
  diagnostic: diagnostic(error),
100
137
  });
138
+ return true;
139
+ }
140
+ let taskSettled = false;
141
+ try {
142
+ const holder = await observeApplicableHolder({ repository, channel, candidate, lags });
143
+ if (holder === null || holder.taskId !== taskId)
144
+ return false;
145
+ if (!(await completeHeldTask({ repository, candidate, actions, lags, taskId })))
146
+ return true;
147
+ taskSettled = true;
148
+ await releaseHeldTaskHolder({ repository, channel, candidate, taskId, lags });
149
+ return true;
150
+ }
151
+ finally {
152
+ try {
153
+ await fence.close();
154
+ }
155
+ catch (error) {
156
+ if (!taskSettled)
157
+ throw error;
158
+ lags.push({
159
+ kind: "settlement-failed",
160
+ surface: "task-holder",
161
+ contractId: candidate.id,
162
+ taskId,
163
+ diagnostic: diagnostic(error),
164
+ });
165
+ }
101
166
  }
102
167
  }
103
168
  async function settleNamespace(state, effects, actions, lags) {
104
- if (state.terminal !== null || state.coordinates.workspace !== "worktree")
169
+ if (state.coordinates.workspace !== "worktree")
105
170
  return;
106
171
  const worktrees = effects.filter((effect) => effect.kind === "worktree" && effect.action !== "removed");
107
172
  for (const effect of worktrees) {
@@ -127,12 +192,12 @@ async function settleObserved(input) {
127
192
  if (input.state === null)
128
193
  return { actions: [], lags: [] };
129
194
  const actions = [], lags = [];
195
+ let holderApplies = false;
130
196
  if (input.state.terminal?.kind === "claimed") {
131
197
  try {
132
- await settleTasks({
198
+ holderApplies = await settleTasks({
133
199
  repository: input.repository,
134
200
  channel: input.channel,
135
- world: await World.at(input.repository.primaryWorktree),
136
201
  candidate: input.state,
137
202
  actions,
138
203
  lags,
@@ -147,7 +212,8 @@ async function settleObserved(input) {
147
212
  });
148
213
  }
149
214
  }
150
- await settleNamespace(input.state, input.effects, actions, lags);
215
+ if (holderApplies)
216
+ await settleNamespace(input.state, input.effects, actions, lags);
151
217
  return { actions, lags };
152
218
  }
153
219
  function onPrimaryWorktree(repository) {
@@ -1,24 +1,65 @@
1
1
  import type { TaskDocument, TaskPriority, TaskState } from "./document.js";
2
2
  import { type TaskId } from "./identity.js";
3
+ import { z } from "zod";
3
4
  export type TaskRef = Readonly<{
4
5
  id: TaskId;
5
6
  title: string | null;
6
7
  state: TaskState | "missing";
7
8
  }>;
8
- export type TaskDisposition = "ready" | "blocked" | "in_progress" | "on_hold" | "done" | "drop";
9
- export type TaskRow = Readonly<{
10
- id: TaskId;
11
- title: string;
12
- state: TaskState;
13
- priority: TaskPriority;
14
- disposition: TaskDisposition;
15
- updatedAt: string;
16
- bodyPresent: boolean;
17
- children?: Readonly<{
18
- live: number;
19
- total: number;
9
+ export declare const taskRowSchema: z.ZodObject<{
10
+ id: z.ZodPipe<z.ZodString, z.ZodTransform<`task/${string}`, string>>;
11
+ title: z.ZodString;
12
+ state: z.ZodEnum<{
13
+ open: "open";
14
+ in_progress: "in_progress";
15
+ on_hold: "on_hold";
16
+ done: "done";
17
+ drop: "drop";
20
18
  }>;
21
- }>;
19
+ priority: z.ZodUnion<readonly [z.ZodLiteral<0>, z.ZodLiteral<1>, z.ZodLiteral<2>, z.ZodLiteral<3>]>;
20
+ disposition: z.ZodEnum<{
21
+ in_progress: "in_progress";
22
+ on_hold: "on_hold";
23
+ done: "done";
24
+ drop: "drop";
25
+ ready: "ready";
26
+ blocked: "blocked";
27
+ }>;
28
+ updatedAt: z.ZodString;
29
+ bodyPresent: z.ZodBoolean;
30
+ children: z.ZodOptional<z.ZodObject<{
31
+ live: z.ZodNumber;
32
+ total: z.ZodNumber;
33
+ }, z.core.$strict>>;
34
+ }, z.core.$strict>;
35
+ export declare const taskRowsSchema: z.ZodReadonly<z.ZodArray<z.ZodObject<{
36
+ id: z.ZodPipe<z.ZodString, z.ZodTransform<`task/${string}`, string>>;
37
+ title: z.ZodString;
38
+ state: z.ZodEnum<{
39
+ open: "open";
40
+ in_progress: "in_progress";
41
+ on_hold: "on_hold";
42
+ done: "done";
43
+ drop: "drop";
44
+ }>;
45
+ priority: z.ZodUnion<readonly [z.ZodLiteral<0>, z.ZodLiteral<1>, z.ZodLiteral<2>, z.ZodLiteral<3>]>;
46
+ disposition: z.ZodEnum<{
47
+ in_progress: "in_progress";
48
+ on_hold: "on_hold";
49
+ done: "done";
50
+ drop: "drop";
51
+ ready: "ready";
52
+ blocked: "blocked";
53
+ }>;
54
+ updatedAt: z.ZodString;
55
+ bodyPresent: z.ZodBoolean;
56
+ children: z.ZodOptional<z.ZodObject<{
57
+ live: z.ZodNumber;
58
+ total: z.ZodNumber;
59
+ }, z.core.$strict>>;
60
+ }, z.core.$strict>>>;
61
+ export type TaskDisposition = z.infer<typeof taskRowSchema>["disposition"];
62
+ export type TaskRow = z.infer<typeof taskRowSchema>;
22
63
  export type BlockedTaskRow = TaskRow & Readonly<{
23
64
  blockers: readonly TaskRef[];
24
65
  }>;
@@ -74,35 +115,35 @@ export declare function projectDetailFacts(board: TaskBoard, id: TaskId, relatio
74
115
  export declare function projectTaskRow(board: TaskBoard, relations: TaskRelationProjection, task: TaskDocument): TaskRow;
75
116
  export declare function projectRows(board: TaskBoard, relations: TaskRelationProjection, scope: readonly string[] | null, selection: "active" | "closed" | "all"): readonly TaskRow[];
76
117
  export declare function projectBlocked(board: TaskBoard, scope: readonly string[] | null, relations: TaskRelationProjection): readonly BlockedTaskRow[];
77
- export declare function projectStatusRows(board: TaskBoard, relations: TaskRelationProjection, scope: readonly string[] | null): (Readonly<{
78
- id: TaskId;
118
+ export declare function projectStatusRows(board: TaskBoard, relations: TaskRelationProjection, scope: readonly string[] | null): ({
119
+ id: `task/${string}`;
79
120
  title: string;
80
- state: TaskState;
81
- priority: TaskPriority;
82
- disposition: TaskDisposition;
121
+ state: "open" | "in_progress" | "on_hold" | "done" | "drop";
122
+ priority: 0 | 1 | 2 | 3;
123
+ disposition: "in_progress" | "on_hold" | "done" | "drop" | "ready" | "blocked";
83
124
  updatedAt: string;
84
125
  bodyPresent: boolean;
85
- children?: Readonly<{
126
+ children?: {
86
127
  live: number;
87
128
  total: number;
88
- }>;
89
- }> | {
129
+ } | undefined;
130
+ } | {
90
131
  blockers: readonly Readonly<{
91
132
  id: TaskId;
92
133
  title: string | null;
93
134
  state: TaskState | "missing";
94
135
  }>[];
95
- id: TaskId;
136
+ id: `task/${string}`;
96
137
  title: string;
97
- state: TaskState;
98
- priority: TaskPriority;
99
- disposition: TaskDisposition;
138
+ state: "open" | "in_progress" | "on_hold" | "done" | "drop";
139
+ priority: 0 | 1 | 2 | 3;
140
+ disposition: "in_progress" | "on_hold" | "done" | "drop" | "ready" | "blocked";
100
141
  updatedAt: string;
101
142
  bodyPresent: boolean;
102
- children?: Readonly<{
143
+ children?: {
103
144
  live: number;
104
145
  total: number;
105
- }>;
146
+ } | undefined;
106
147
  })[];
107
148
  export type TaskBoardObservation = Readonly<{
108
149
  statusRows: ReturnType<typeof projectStatusRows>;
@@ -1,4 +1,34 @@
1
- import { parseTaskId, sameNamespace } from "./identity.js";
1
+ import { formatTaskId, parseTaskId, sameNamespace } from "./identity.js";
2
+ import { z } from "zod";
3
+ const taskRowIdSchema = z.string().transform((value, context) => {
4
+ try {
5
+ const id = formatTaskId(parseTaskId(value));
6
+ if (id !== value)
7
+ throw new Error("not canonical");
8
+ return id;
9
+ }
10
+ catch {
11
+ context.addIssue({ code: "custom", message: "expected canonical TaskId" });
12
+ return z.NEVER;
13
+ }
14
+ });
15
+ const taskRowTimestampSchema = z.string().refine((value) => Number.isFinite(Date.parse(value)), "expected timestamp");
16
+ export const taskRowSchema = z
17
+ .object({
18
+ id: taskRowIdSchema,
19
+ title: z.string().refine((value) => value.trim() !== ""),
20
+ state: z.enum(["open", "in_progress", "on_hold", "done", "drop"]),
21
+ priority: z.union([z.literal(0), z.literal(1), z.literal(2), z.literal(3)]),
22
+ disposition: z.enum(["ready", "blocked", "in_progress", "on_hold", "done", "drop"]),
23
+ updatedAt: taskRowTimestampSchema,
24
+ bodyPresent: z.boolean(),
25
+ children: z
26
+ .object({ live: z.number().int().nonnegative(), total: z.number().int().nonnegative() })
27
+ .strict()
28
+ .optional(),
29
+ })
30
+ .strict();
31
+ export const taskRowsSchema = z.array(taskRowSchema).readonly();
2
32
  const terminal = (state) => state === "done" || state === "drop";
3
33
  const none = Object.freeze([]);
4
34
  const sorted = (values) => [...values].sort((a, b) => Buffer.compare(Buffer.from(a.id), Buffer.from(b.id)));