@astrosheep/keiyaku 4.5.6 → 4.5.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 (245) 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 +10 -8
  7. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-akuma/SKILL.md +22 -1
  8. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-babysit/SKILL.md +53 -0
  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 +372 -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/body.d.ts +7 -5
  23. package/build/src/akuma/body.js +25 -36
  24. package/build/src/akuma/call-request.d.ts +80 -0
  25. package/build/src/akuma/call-request.js +211 -0
  26. package/build/src/akuma/heart/facts.d.ts +6 -78
  27. package/build/src/akuma/heart/index.d.ts +3 -3
  28. package/build/src/akuma/heart/index.js +2 -2
  29. package/build/src/akuma/heart/request-authority.d.ts +6 -4
  30. package/build/src/akuma/heart/request-authority.js +31 -35
  31. package/build/src/akuma/heart/request-rows.js +12 -23
  32. package/build/src/akuma/heart/schema.d.ts +1 -1
  33. package/build/src/akuma/heart/schema.js +5 -13
  34. package/build/src/akuma/heart/storage.d.ts +0 -1
  35. package/build/src/akuma/heart/storage.js +0 -54
  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 +856 -137
  41. package/build/src/akuma/projection.js +190 -0
  42. package/build/src/akuma/provider.d.ts +30 -3
  43. package/build/src/akuma/provider.js +104 -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/index.d.ts +3 -3
  48. package/build/src/akuma/providers/claude/index.js +16 -5
  49. package/build/src/akuma/providers/codex-app-server/events.js +1 -1
  50. package/build/src/akuma/providers/codex-app-server/index.js +18 -6
  51. package/build/src/akuma/providers/grok-build/index.js +5 -4
  52. package/build/src/akuma/providers/opencode-sdk/index.js +66 -25
  53. package/build/src/akuma/providers/opencode-sdk/session.d.ts +3 -2
  54. package/build/src/akuma/providers/opencode-sdk/session.js +28 -11
  55. package/build/src/akuma/providers/pi/index.js +42 -14
  56. package/build/src/akuma/publication.js +75 -13
  57. package/build/src/akuma/request-lifecycle.d.ts +3 -9
  58. package/build/src/akuma/request-lifecycle.js +12 -25
  59. package/build/src/akuma/request-rendezvous.d.ts +26 -0
  60. package/build/src/akuma/request-rendezvous.js +100 -0
  61. package/build/src/akuma/request-serve.d.ts +5 -65
  62. package/build/src/akuma/request-serve.js +92 -204
  63. package/build/src/akuma/request-wire.d.ts +109 -115
  64. package/build/src/akuma/request-wire.js +63 -318
  65. package/build/src/akuma/requests.d.ts +19 -47
  66. package/build/src/akuma/requests.js +51 -107
  67. package/build/src/akuma/turn-drive.d.ts +5 -3
  68. package/build/src/akuma/turn-drive.js +32 -10
  69. package/build/src/akuma-body.d.ts +7 -2
  70. package/build/src/akuma-body.js +68 -46
  71. package/build/src/cli/accepted.d.ts +1 -1
  72. package/build/src/cli/accepted.js +2 -2
  73. package/build/src/cli/commands/akuma-invoke.d.ts +2 -0
  74. package/build/src/cli/commands/akuma-invoke.js +17 -9
  75. package/build/src/cli/commands/akuma.js +7 -7
  76. package/build/src/cli/commands/contract-invoke.d.ts +3 -2
  77. package/build/src/cli/commands/contract-invoke.js +26 -19
  78. package/build/src/cli/commands/contract.d.ts +13 -13
  79. package/build/src/cli/commands/contract.js +32 -17
  80. package/build/src/cli/commands/install.d.ts +1 -1
  81. package/build/src/cli/commands/install.js +1 -1
  82. package/build/src/cli/commands/task-invoke.d.ts +7 -2
  83. package/build/src/cli/commands/task-invoke.js +67 -158
  84. package/build/src/cli/commands/task.d.ts +1 -1
  85. package/build/src/cli/commands/task.js +60 -22
  86. package/build/src/cli/coordinates.d.ts +2 -0
  87. package/build/src/cli/coordinates.js +18 -2
  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 +37 -22
  92. package/build/src/cli/render/akuma-activity.d.ts +4 -1
  93. package/build/src/cli/render/akuma-activity.js +45 -30
  94. package/build/src/cli/render/akuma-tool.d.ts +5 -1
  95. package/build/src/cli/render/akuma.js +5 -9
  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 +25 -65
  101. package/build/src/cli/render/kanshi-akuma.d.ts +8 -0
  102. package/build/src/cli/render/kanshi-akuma.js +111 -0
  103. package/build/src/cli/render/kanshi.js +108 -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 +28 -10
  111. package/build/src/cli/render/terminal.d.ts +28 -0
  112. package/build/src/cli/render/terminal.js +65 -0
  113. package/build/src/cli/result.d.ts +3 -3
  114. package/build/src/cli/runtime.js +3 -1
  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 +36 -18
  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 +19 -7
  140. package/build/src/git/read-observation.js +29 -20
  141. package/build/src/git/reconcile.d.ts +11 -0
  142. package/build/src/git/reconcile.js +64 -11
  143. package/build/src/git/repository.d.ts +8 -2
  144. package/build/src/git/repository.js +42 -21
  145. package/build/src/git/target-placement.d.ts +1 -0
  146. package/build/src/git/target-placement.js +5 -6
  147. package/build/src/git/tender.d.ts +1 -0
  148. package/build/src/git/terminal-reconcile.d.ts +1 -1
  149. package/build/src/git/terminal-reconcile.js +22 -20
  150. package/build/src/git/workspace.d.ts +14 -0
  151. package/build/src/git/workspace.js +44 -0
  152. package/build/src/kanshi/index.d.ts +1 -1
  153. package/build/src/kanshi/read.js +16 -16
  154. package/build/src/kanshi/report.d.ts +2 -2
  155. package/build/src/library/address.d.ts +9 -3
  156. package/build/src/library/address.js +3 -2
  157. package/build/src/library/akuma-creation.d.ts +4 -3
  158. package/build/src/library/akuma-creation.js +19 -13
  159. package/build/src/library/audit.d.ts +1 -1
  160. package/build/src/library/audit.js +5 -3
  161. package/build/src/library/catalog.d.ts +3 -1
  162. package/build/src/library/catalog.js +20 -6
  163. package/build/src/library/composition.d.ts +371 -0
  164. package/build/src/library/composition.js +28 -0
  165. package/build/src/library/configuration.js +8 -2
  166. package/build/src/library/continuation.js +66 -30
  167. package/build/src/library/contract-bind.js +10 -2
  168. package/build/src/library/contract-forwarding-reconciliation-result.d.ts +96 -0
  169. package/build/src/library/contract-forwarding-reconciliation-result.js +83 -0
  170. package/build/src/library/contract-forwarding-result.d.ts +6723 -0
  171. package/build/src/library/contract-forwarding-result.js +521 -0
  172. package/build/src/library/contract-forwarding.d.ts +72 -0
  173. package/build/src/library/contract-forwarding.js +125 -0
  174. package/build/src/library/contract-handle.d.ts +36 -0
  175. package/build/src/library/contract-handle.js +313 -0
  176. package/build/src/library/contract-operations.d.ts +1044 -67
  177. package/build/src/library/contract-operations.js +190 -118
  178. package/build/src/library/contract.d.ts +11 -32
  179. package/build/src/library/contract.js +16 -289
  180. package/build/src/library/fleet-result.d.ts +1454 -0
  181. package/build/src/library/fleet-result.js +96 -0
  182. package/build/src/library/fleet.d.ts +112 -54
  183. package/build/src/library/fleet.js +211 -71
  184. package/build/src/library/input.d.ts +2 -0
  185. package/build/src/library/input.js +4 -0
  186. package/build/src/library/keiyaku.d.ts +371 -20
  187. package/build/src/library/keiyaku.js +8 -21
  188. package/build/src/library/mutation.d.ts +3 -3
  189. package/build/src/library/mutation.js +5 -9
  190. package/build/src/library/nuke.d.ts +1 -1
  191. package/build/src/library/nuke.js +4 -3
  192. package/build/src/library/reconcile.d.ts +4 -0
  193. package/build/src/library/reconcile.js +2 -0
  194. package/build/src/library/refusal.d.ts +0 -9
  195. package/build/src/library/refusal.js +0 -7
  196. package/build/src/protocol/amend.js +56 -50
  197. package/build/src/protocol/attempt.d.ts +2 -0
  198. package/build/src/protocol/attempt.js +17 -5
  199. package/build/src/protocol/completion.js +7 -0
  200. package/build/src/protocol/deliver.js +6 -2
  201. package/build/src/protocol/operations.d.ts +4 -1
  202. package/build/src/protocol/operations.js +5 -2
  203. package/build/src/protocol/placement.d.ts +1 -0
  204. package/build/src/protocol/placement.js +52 -38
  205. package/build/src/protocol/read/observation.d.ts +1 -4
  206. package/build/src/protocol/read/observation.js +2 -15
  207. package/build/src/protocol/reintegrate.js +62 -58
  208. package/build/src/protocol/review.js +5 -0
  209. package/build/src/protocol/run.js +17 -13
  210. package/build/src/runtime/proc/run.d.ts +8 -0
  211. package/build/src/runtime/proc/run.js +27 -0
  212. package/build/src/runtime/proc/termination.js +26 -22
  213. package/build/src/runtime/proc/windows-launch.exe +0 -0
  214. package/build/src/settlement/holder.d.ts +2 -1
  215. package/build/src/settlement/holder.js +15 -4
  216. package/build/src/settlement/settle.js +99 -33
  217. package/build/src/task/board.d.ts +68 -27
  218. package/build/src/task/board.js +31 -1
  219. package/build/src/task/catalog.d.ts +4 -0
  220. package/build/src/task/catalog.js +23 -0
  221. package/build/src/task/compose-language.d.ts +4 -0
  222. package/build/src/task/compose-language.js +40 -7
  223. package/build/src/task/compose-parser.d.ts +2 -1
  224. package/build/src/task/compose-parser.js +5 -3
  225. package/build/src/task/compose.d.ts +1 -0
  226. package/build/src/task/compose.js +4 -1
  227. package/build/src/task/context.d.ts +7 -1
  228. package/build/src/task/context.js +25 -6
  229. package/build/src/task/index.d.ts +18 -5
  230. package/build/src/task/index.js +97 -16
  231. package/build/src/task/mutation-result.d.ts +923 -0
  232. package/build/src/task/mutation-result.js +140 -0
  233. package/build/src/task/mutation.d.ts +552 -28
  234. package/build/src/task/mutation.js +304 -97
  235. package/build/src/task/operations.d.ts +1 -4
  236. package/build/src/task/operations.js +0 -6
  237. package/build/src/task/public.d.ts +2 -0
  238. package/build/src/task/public.js +1 -0
  239. package/build/src/workspace-place.d.ts +7 -2
  240. package/build/src/workspace-place.js +34 -24
  241. package/build/src/world.d.ts +1 -0
  242. package/build/src/world.js +11 -0
  243. package/package.json +8 -6
  244. package/build/src/akuma/request-execution.d.ts +0 -8
  245. 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,13 @@ 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
+ terminate(force?: boolean): Promise<void>;
16
+ waitTermination(): Promise<void>;
17
+ }>;
18
+ export declare function spawnCancellableProcess(input: ProcessLaunch): CancellableProcess;
11
19
  export type ProcessInput = ProcessLaunch & Readonly<{
12
20
  readonly timeoutMs?: number;
13
21
  }>;
@@ -7,6 +7,33 @@ 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
+ export function spawnCancellableProcess(input) {
11
+ const child = spawn(input.argv[0], input.argv.slice(1), spawnOptionsFor(input, ["pipe", "pipe", "pipe"]));
12
+ let cancelled = false;
13
+ let termination;
14
+ const terminate = (force = false) => {
15
+ termination ??= terminateOwnedProcess(child, force);
16
+ return termination;
17
+ };
18
+ const cancel = () => {
19
+ if (cancelled)
20
+ return;
21
+ cancelled = true;
22
+ void terminate();
23
+ };
24
+ input.signal?.addEventListener("abort", cancel, { once: true });
25
+ if (input.signal?.aborted === true)
26
+ cancel();
27
+ return {
28
+ child,
29
+ cancelled: () => cancelled,
30
+ terminate,
31
+ async waitTermination() {
32
+ input.signal?.removeEventListener("abort", cancel);
33
+ await termination;
34
+ },
35
+ };
36
+ }
10
37
  function tailCapture(limit) {
11
38
  const bytes = Buffer.allocUnsafe(limit);
12
39
  let total = 0;
@@ -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)));
@@ -0,0 +1,4 @@
1
+ import { type TaskRow } from "./board.js";
2
+ import type { WorldRoot } from "../world.js";
3
+ export declare function parseTaskNamespaceSelector(value: string): readonly string[];
4
+ export declare function observeTaskCatalogRows(world: WorldRoot, namespace?: readonly string[]): Promise<readonly TaskRow[]>;
@@ -0,0 +1,23 @@
1
+ import { createTaskRelations, projectRows } from "./board.js";
2
+ import { isTaskSegment, parseTaskId } from "./identity.js";
3
+ import { readBoard } from "./store.js";
4
+ export function parseTaskNamespaceSelector(value) {
5
+ if (value === "task/")
6
+ return [];
7
+ if (!value.startsWith("task/") || !value.endsWith("/")) {
8
+ throw new TypeError(`invalid Task namespace selector: ${value}`);
9
+ }
10
+ const body = value.slice("task/".length, -1);
11
+ const segments = body.split("/");
12
+ if (body.length === 0 || !segments.every(isTaskSegment)) {
13
+ throw new TypeError(`invalid Task namespace selector: ${value}`);
14
+ }
15
+ return parseTaskId(`task/${body}/placeholder`).namespace;
16
+ }
17
+ export async function observeTaskCatalogRows(world, namespace) {
18
+ if (namespace !== undefined && !namespace.every(isTaskSegment)) {
19
+ throw new TypeError("namespace must contain canonical segments");
20
+ }
21
+ const board = (await readBoard(world)).board;
22
+ return projectRows(board, createTaskRelations(board), namespace ?? null, "all");
23
+ }
@@ -2,6 +2,10 @@ import type { TaskBoard } from "./board.js";
2
2
  import { type TaskDocument } from "./document.js";
3
3
  import { type TaskId } from "./identity.js";
4
4
  import { type TaskCompositionDiagnostic } from "./operations.js";
5
+ export declare function taskCompositionNamespaceHeader(markdown: string): Readonly<{
6
+ specified: boolean;
7
+ namespace?: readonly string[];
8
+ }>;
5
9
  export type TaskCompositionAlias = Readonly<{
6
10
  alias: string;
7
11
  taskId: TaskId;