@astrosheep/keiyaku 4.0.0 → 4.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (308) hide show
  1. package/README.md +133 -8
  2. package/build/integrations/marketplace/plugins/keiyaku/.claude-plugin/plugin.json +2 -2
  3. package/build/integrations/marketplace/plugins/keiyaku/.codex-plugin/plugin.json +4 -4
  4. package/build/integrations/marketplace/plugins/keiyaku/opencode.js +2 -2
  5. package/build/integrations/marketplace/plugins/keiyaku/package.json +2 -2
  6. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku/SKILL.md +13 -5
  7. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-akuma/SKILL.md +91 -32
  8. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-bind/SKILL.md +134 -0
  9. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-bind/agents/openai.yaml +4 -0
  10. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-task/SKILL.md +41 -6
  11. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-workflow/SKILL.md +262 -46
  12. package/build/src/akuma/abort.d.ts +2 -0
  13. package/build/src/akuma/abort.js +50 -0
  14. package/build/src/akuma/akuma.d.ts +50 -33
  15. package/build/src/akuma/akuma.js +175 -127
  16. package/build/src/akuma/archetype.d.ts +14 -5
  17. package/build/src/akuma/archetype.js +85 -39
  18. package/build/src/akuma/body.d.ts +3 -5
  19. package/build/src/akuma/body.js +490 -183
  20. package/build/src/akuma/coordinate.d.ts +9 -0
  21. package/build/src/akuma/coordinate.js +22 -0
  22. package/build/src/akuma/heart/facts.d.ts +88 -45
  23. package/build/src/akuma/heart/facts.js +19 -8
  24. package/build/src/akuma/heart/index.d.ts +64 -55
  25. package/build/src/akuma/heart/index.js +172 -252
  26. package/build/src/akuma/heart/rows.d.ts +38 -83
  27. package/build/src/akuma/heart/rows.js +117 -155
  28. package/build/src/akuma/heart/schema.d.ts +1 -1
  29. package/build/src/akuma/heart/schema.js +61 -22
  30. package/build/src/akuma/heart/soul.d.ts +8 -0
  31. package/build/src/akuma/heart/soul.js +136 -0
  32. package/build/src/akuma/heart/storage.d.ts +47 -0
  33. package/build/src/akuma/heart/storage.js +224 -0
  34. package/build/src/akuma/heart/tells.d.ts +12 -0
  35. package/build/src/akuma/heart/tells.js +79 -0
  36. package/build/src/akuma/heart/timeline.d.ts +15 -0
  37. package/build/src/akuma/heart/timeline.js +86 -0
  38. package/build/src/akuma/identity.d.ts +2 -2
  39. package/build/src/akuma/identity.js +9 -9
  40. package/build/src/akuma/index.d.ts +10 -3
  41. package/build/src/akuma/index.js +7 -1
  42. package/build/src/akuma/projection.d.ts +176 -0
  43. package/build/src/akuma/projection.js +256 -0
  44. package/build/src/akuma/provider-recipe.d.ts +24 -0
  45. package/build/src/akuma/provider-recipe.js +100 -0
  46. package/build/src/akuma/provider.d.ts +67 -13
  47. package/build/src/akuma/provider.js +172 -105
  48. package/build/src/akuma/providers/acp/core.d.ts +19 -0
  49. package/build/src/akuma/providers/acp/core.js +159 -0
  50. package/build/src/akuma/providers/acp/events.d.ts +20 -0
  51. package/build/src/akuma/providers/acp/events.js +73 -0
  52. package/build/src/akuma/providers/acp/index.d.ts +12 -0
  53. package/build/src/akuma/providers/acp/index.js +94 -0
  54. package/build/src/akuma/providers/{claude.d.ts → claude/events.d.ts} +9 -41
  55. package/build/src/akuma/providers/claude/events.js +238 -0
  56. package/build/src/akuma/providers/claude/index.d.ts +47 -0
  57. package/build/src/akuma/providers/claude/index.js +279 -0
  58. package/build/src/akuma/providers/claude/input.d.ts +13 -0
  59. package/build/src/akuma/providers/claude/input.js +109 -0
  60. package/build/src/akuma/providers/codex-app-server/events.d.ts +1 -1
  61. package/build/src/akuma/providers/codex-app-server/events.js +15 -12
  62. package/build/src/akuma/providers/codex-app-server/index.d.ts +15 -13
  63. package/build/src/akuma/providers/codex-app-server/index.js +77 -33
  64. package/build/src/akuma/providers/grok-build/index.d.ts +4 -0
  65. package/build/src/akuma/providers/grok-build/index.js +70 -0
  66. package/build/src/akuma/providers/index.d.ts +5 -3
  67. package/build/src/akuma/providers/index.js +35 -56
  68. package/build/src/akuma/providers/opencode-sdk/events.d.ts +54 -0
  69. package/build/src/akuma/providers/opencode-sdk/events.js +235 -0
  70. package/build/src/akuma/providers/opencode-sdk/index.d.ts +33 -0
  71. package/build/src/akuma/providers/opencode-sdk/index.js +308 -0
  72. package/build/src/akuma/providers/opencode-sdk/session.d.ts +26 -0
  73. package/build/src/akuma/providers/opencode-sdk/session.js +63 -0
  74. package/build/src/akuma/providers/pi/events.d.ts +33 -0
  75. package/build/src/akuma/providers/pi/events.js +166 -0
  76. package/build/src/akuma/providers/pi/index.d.ts +11 -0
  77. package/build/src/akuma/providers/pi/index.js +191 -0
  78. package/build/src/akuma/publication.d.ts +3 -3
  79. package/build/src/akuma/publication.js +28 -40
  80. package/build/src/akuma/requests.d.ts +9 -6
  81. package/build/src/akuma/requests.js +95 -65
  82. package/build/src/alias/index.d.ts +4 -3
  83. package/build/src/alias/index.js +12 -13
  84. package/build/src/body/decode.js +7 -1
  85. package/build/src/body/region.d.ts +2 -0
  86. package/build/src/body/region.js +77 -7
  87. package/build/src/body/render.js +2 -1
  88. package/build/src/body/shape.js +7 -1
  89. package/build/src/body/verification.js +19 -4
  90. package/build/src/cli/accepted.d.ts +18 -12
  91. package/build/src/cli/accepted.js +73 -34
  92. package/build/src/cli/actor.js +3 -3
  93. package/build/src/cli/commands/akuma-invoke.d.ts +44 -19
  94. package/build/src/cli/commands/akuma-invoke.js +101 -52
  95. package/build/src/cli/commands/akuma.d.ts +27 -9
  96. package/build/src/cli/commands/akuma.js +151 -80
  97. package/build/src/cli/commands/contract.d.ts +155 -0
  98. package/build/src/cli/commands/contract.js +20 -0
  99. package/build/src/cli/commands/install.js +2 -0
  100. package/build/src/cli/commands/task-invoke.d.ts +19 -4
  101. package/build/src/cli/commands/task-invoke.js +65 -17
  102. package/build/src/cli/commands/task-query.d.ts +5 -0
  103. package/build/src/cli/commands/task-query.js +247 -0
  104. package/build/src/cli/commands/task.d.ts +3 -1
  105. package/build/src/cli/commands/task.js +52 -14
  106. package/build/src/cli/coordinates.d.ts +18 -0
  107. package/build/src/cli/coordinates.js +120 -0
  108. package/build/src/cli/draft.d.ts +8 -0
  109. package/build/src/cli/draft.js +96 -0
  110. package/build/src/cli/invoke.d.ts +9 -3
  111. package/build/src/cli/invoke.js +287 -108
  112. package/build/src/cli/main.js +54 -7
  113. package/build/src/cli/parse.d.ts +21 -117
  114. package/build/src/cli/parse.js +79 -84
  115. package/build/src/cli/render/akuma-tool-command.d.ts +6 -0
  116. package/build/src/cli/render/akuma-tool-command.js +144 -0
  117. package/build/src/cli/render/akuma-tool.d.ts +4 -2
  118. package/build/src/cli/render/akuma-tool.js +35 -5
  119. package/build/src/cli/render/akuma.d.ts +3 -1
  120. package/build/src/cli/render/akuma.js +247 -212
  121. package/build/src/cli/render/audit.d.ts +3 -0
  122. package/build/src/cli/render/audit.js +104 -0
  123. package/build/src/cli/render/catalog.d.ts +2 -0
  124. package/build/src/cli/render/catalog.js +16 -0
  125. package/build/src/cli/render/contract.d.ts +3 -3
  126. package/build/src/cli/render/contract.js +200 -54
  127. package/build/src/cli/render/kanshi.js +272 -129
  128. package/build/src/cli/render/receipt.d.ts +19 -0
  129. package/build/src/cli/render/receipt.js +106 -0
  130. package/build/src/cli/render/refusal.d.ts +16 -2
  131. package/build/src/cli/render/refusal.js +88 -3
  132. package/build/src/cli/render/region.d.ts +2 -0
  133. package/build/src/cli/render/region.js +17 -0
  134. package/build/src/cli/render/task.d.ts +2 -1
  135. package/build/src/cli/render/task.js +223 -60
  136. package/build/src/cli/render/terminal.d.ts +20 -0
  137. package/build/src/cli/render/terminal.js +121 -0
  138. package/build/src/cli/render/text.js +11 -3
  139. package/build/src/cli/result.d.ts +126 -19
  140. package/build/src/cli/usage.d.ts +1 -0
  141. package/build/src/cli/usage.js +3 -0
  142. package/build/src/contract-worktree.d.ts +44 -0
  143. package/build/src/contract-worktree.js +251 -0
  144. package/build/src/coordination/durable-file.d.ts +5 -1
  145. package/build/src/coordination/durable-file.js +57 -10
  146. package/build/src/coordination/sqlite-transaction-lock.d.ts +4 -0
  147. package/build/src/coordination/sqlite-transaction-lock.js +22 -5
  148. package/build/src/core/facts/codec.js +23 -4
  149. package/build/src/core/facts/eligibility.d.ts +1 -10
  150. package/build/src/core/facts/eligibility.js +0 -49
  151. package/build/src/core/facts/fold.js +1 -6
  152. package/build/src/core/facts/gate.d.ts +1 -0
  153. package/build/src/core/facts/gate.js +1 -0
  154. package/build/src/core/facts/observation.d.ts +3 -0
  155. package/build/src/core/facts/observation.js +12 -0
  156. package/build/src/core/facts/types.d.ts +10 -3
  157. package/build/src/core/subject.js +2 -2
  158. package/build/src/core/verbs/amend.d.ts +1 -1
  159. package/build/src/core/verbs/amend.js +3 -18
  160. package/build/src/core/verbs/bind.js +2 -16
  161. package/build/src/core/verbs/deliver.d.ts +1 -1
  162. package/build/src/core/verbs/deliver.js +14 -4
  163. package/build/src/core/verbs/placement.d.ts +1 -1
  164. package/build/src/core/verbs/placement.js +7 -9
  165. package/build/src/dispatch/index.d.ts +5 -3
  166. package/build/src/dispatch/index.js +30 -21
  167. package/build/src/duration.d.ts +10 -0
  168. package/build/src/duration.js +22 -0
  169. package/build/src/git/admission.d.ts +2 -2
  170. package/build/src/git/admission.js +33 -23
  171. package/build/src/git/hooks.d.ts +15 -0
  172. package/build/src/git/hooks.js +70 -18
  173. package/build/src/git/identity.d.ts +5 -1
  174. package/build/src/git/identity.js +23 -4
  175. package/build/src/git/integration.d.ts +48 -0
  176. package/build/src/git/integration.js +250 -0
  177. package/build/src/git/observe.d.ts +29 -15
  178. package/build/src/git/observe.js +185 -76
  179. package/build/src/git/read-observation.d.ts +41 -0
  180. package/build/src/git/read-observation.js +361 -0
  181. package/build/src/git/reconcile.d.ts +17 -5
  182. package/build/src/git/reconcile.js +305 -109
  183. package/build/src/git/repository.d.ts +41 -18
  184. package/build/src/git/repository.js +220 -114
  185. package/build/src/git/scratch.d.ts +17 -0
  186. package/build/src/git/scratch.js +73 -0
  187. package/build/src/git/target-placement.d.ts +92 -0
  188. package/build/src/git/target-placement.js +375 -0
  189. package/build/src/git/tender.d.ts +49 -0
  190. package/build/src/git/tender.js +63 -0
  191. package/build/src/git/terminal-seal.d.ts +24 -0
  192. package/build/src/git/terminal-seal.js +89 -0
  193. package/build/src/git/tree.d.ts +2 -2
  194. package/build/src/git/tree.js +4 -0
  195. package/build/src/git/workspace.d.ts +53 -0
  196. package/build/src/git/workspace.js +139 -0
  197. package/build/src/identity/selector.d.ts +6 -0
  198. package/build/src/identity/selector.js +19 -0
  199. package/build/src/index.d.ts +4 -2
  200. package/build/src/index.js +2 -1
  201. package/build/src/kanshi/index.d.ts +2 -2
  202. package/build/src/kanshi/index.js +1 -1
  203. package/build/src/kanshi/read.d.ts +7 -3
  204. package/build/src/kanshi/read.js +245 -45
  205. package/build/src/kanshi/report.d.ts +81 -6
  206. package/build/src/kanshi/select.d.ts +5 -0
  207. package/build/src/kanshi/select.js +33 -1
  208. package/build/src/library/address.d.ts +57 -0
  209. package/build/src/library/address.js +165 -0
  210. package/build/src/library/akuma-creation.d.ts +16 -7
  211. package/build/src/library/akuma-creation.js +96 -22
  212. package/build/src/library/audit.d.ts +18 -0
  213. package/build/src/library/audit.js +44 -0
  214. package/build/src/library/bind.d.ts +21 -0
  215. package/build/src/library/bind.js +56 -0
  216. package/build/src/library/catalog.d.ts +58 -0
  217. package/build/src/library/catalog.js +69 -0
  218. package/build/src/library/configuration.d.ts +6 -15
  219. package/build/src/library/configuration.js +14 -17
  220. package/build/src/library/contract.d.ts +23 -49
  221. package/build/src/library/contract.js +171 -185
  222. package/build/src/library/delivery.d.ts +24 -0
  223. package/build/src/library/delivery.js +21 -0
  224. package/build/src/library/fleet.d.ts +65 -0
  225. package/build/src/library/fleet.js +153 -0
  226. package/build/src/library/input.d.ts +3 -0
  227. package/build/src/library/input.js +19 -0
  228. package/build/src/library/keiyaku.d.ts +18 -3
  229. package/build/src/library/keiyaku.js +12 -1
  230. package/build/src/library/mutation.d.ts +39 -0
  231. package/build/src/library/mutation.js +37 -0
  232. package/build/src/library/reconcile.d.ts +29 -0
  233. package/build/src/library/reconcile.js +179 -0
  234. package/build/src/library/refusal.d.ts +21 -0
  235. package/build/src/library/refusal.js +29 -0
  236. package/build/src/library/region.d.ts +14 -1
  237. package/build/src/library/region.js +18 -4
  238. package/build/src/library/repo.d.ts +4 -13
  239. package/build/src/library/repo.js +14 -24
  240. package/build/src/protocol/attempt.d.ts +16 -3
  241. package/build/src/protocol/attempt.js +41 -10
  242. package/build/src/protocol/bind.d.ts +9 -2
  243. package/build/src/protocol/bind.js +48 -40
  244. package/build/src/protocol/intent.d.ts +57 -20
  245. package/build/src/protocol/intent.js +55 -79
  246. package/build/src/protocol/operations.d.ts +152 -43
  247. package/build/src/protocol/operations.js +482 -166
  248. package/build/src/protocol/outcome.d.ts +14 -3
  249. package/build/src/protocol/outcome.js +11 -3
  250. package/build/src/protocol/placement.d.ts +41 -0
  251. package/build/src/protocol/placement.js +147 -0
  252. package/build/src/protocol/read/documents.d.ts +2 -2
  253. package/build/src/protocol/read/documents.js +4 -4
  254. package/build/src/protocol/read/status.d.ts +18 -5
  255. package/build/src/protocol/read/status.js +73 -13
  256. package/build/src/protocol/run.d.ts +49 -7
  257. package/build/src/protocol/run.js +53 -25
  258. package/build/src/runtime/proc/line-rpc.d.ts +4 -8
  259. package/build/src/runtime/proc/line-rpc.js +22 -28
  260. package/build/src/runtime/proc/run.d.ts +23 -21
  261. package/build/src/runtime/proc/run.js +123 -89
  262. package/build/src/runtime/proc/stdio.d.ts +18 -0
  263. package/build/src/runtime/proc/stdio.js +58 -0
  264. package/build/src/settings.d.ts +7 -1
  265. package/build/src/settings.js +32 -17
  266. package/build/src/settlement/fence.d.ts +5 -0
  267. package/build/src/settlement/fence.js +14 -0
  268. package/build/src/settlement/holder.d.ts +35 -4
  269. package/build/src/settlement/holder.js +127 -28
  270. package/build/src/settlement/settle.d.ts +9 -1
  271. package/build/src/settlement/settle.js +74 -37
  272. package/build/src/task/board.d.ts +23 -5
  273. package/build/src/task/board.js +17 -10
  274. package/build/src/task/compose.d.ts +2 -2
  275. package/build/src/task/compose.js +18 -10
  276. package/build/src/task/context.d.ts +3 -3
  277. package/build/src/task/context.js +15 -20
  278. package/build/src/task/document.d.ts +2 -1
  279. package/build/src/task/document.js +15 -5
  280. package/build/src/task/index.d.ts +27 -15
  281. package/build/src/task/index.js +57 -35
  282. package/build/src/task/operations.d.ts +38 -13
  283. package/build/src/task/operations.js +80 -46
  284. package/build/src/task/query.d.ts +67 -0
  285. package/build/src/task/query.js +279 -0
  286. package/build/src/task/store.d.ts +5 -7
  287. package/build/src/task/store.js +43 -28
  288. package/build/src/verification/declaration.d.ts +1 -0
  289. package/build/src/verification/execution.d.ts +51 -0
  290. package/build/src/verification/execution.js +108 -0
  291. package/build/src/workspace-place.d.ts +41 -0
  292. package/build/src/workspace-place.js +386 -0
  293. package/build/src/world.d.ts +24 -0
  294. package/build/src/world.js +136 -0
  295. package/package.json +8 -4
  296. package/build/src/akuma/activity.d.ts +0 -66
  297. package/build/src/akuma/activity.js +0 -139
  298. package/build/src/akuma/providers/claude.js +0 -305
  299. package/build/src/context-root.d.ts +0 -4
  300. package/build/src/context-root.js +0 -15
  301. package/build/src/git/delivery.d.ts +0 -20
  302. package/build/src/git/delivery.js +0 -109
  303. package/build/src/git/verification.d.ts +0 -14
  304. package/build/src/git/verification.js +0 -56
  305. package/build/src/protocol/read/audit.d.ts +0 -25
  306. package/build/src/protocol/read/audit.js +0 -39
  307. package/build/src/verification/producer.d.ts +0 -23
  308. package/build/src/verification/producer.js +0 -54
@@ -1,11 +1,13 @@
1
1
  import { randomUUID } from "node:crypto";
2
- import { existsSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, writeFileSync, } from "node:fs";
2
+ import { access, mkdir, readFile, readdir, rename, rm, writeFile } from "node:fs/promises";
3
3
  import { isAbsolute, join, resolve } from "node:path";
4
4
  import { HeldAkumaLeash, admitRequest, readNonterminalRequests, readRequest, readSoul, refuseRequest, reserveRequest, serveRequest, voidRequest, } from "./heart/index.js";
5
5
  import { parseAkuId, pathsForAkuId, archetypeName, worldRootForAkumaPaths, } from "./identity.js";
6
6
  import { BIRTH_TIMEOUT_MS, publishAkuma } from "./publication.js";
7
- import { AKUMA_REQUESTS_ENV, decodeProviderOptions } from "./provider.js";
8
- import { decodeProviderExecution, providerNamed } from "./providers/index.js";
7
+ import { abortableDelay } from "./abort.js";
8
+ import { AKUMA_REQUESTS_ENV } from "./provider.js";
9
+ import { decodeProviderOptions, decodeReadonlyRestraint } from "./provider-recipe.js";
10
+ import { resolveProviderExecution } from "./providers/index.js";
9
11
  const POLL_MS = 25;
10
12
  const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/u;
11
13
  export class AkumaBodyRequestError extends Error {
@@ -19,9 +21,6 @@ export class AkumaBodyRequestError extends Error {
19
21
  this.name = "AkumaBodyRequestError";
20
22
  }
21
23
  }
22
- function wait(milliseconds) {
23
- return new Promise((resolveWait) => setTimeout(resolveWait, milliseconds));
24
- }
25
24
  function diagnostic(error) {
26
25
  return error instanceof Error ? error.message : String(error);
27
26
  }
@@ -57,6 +56,7 @@ function decodeRecipe(value, cwd) {
57
56
  ...(recipe.description === undefined ? [] : ["description"]),
58
57
  "options",
59
58
  "provider",
59
+ ...(recipe.readonly === undefined ? [] : ["readonly"]),
60
60
  ];
61
61
  if (!exactKeys(recipe, expectedKeys))
62
62
  return null;
@@ -64,19 +64,21 @@ function decodeRecipe(value, cwd) {
64
64
  && (typeof recipe.description !== "string" || recipe.description.trim().length === 0))
65
65
  return null;
66
66
  try {
67
- const provider = decodeProviderExecution(recipe.provider);
68
- const adapter = providerNamed(provider);
67
+ const selected = resolveProviderExecution(recipe.provider);
68
+ const provider = selected.execution;
69
+ const adapter = selected.adapter;
69
70
  const decodedOptions = decodeProviderOptions(recipe.options);
70
- const admission = adapter.admitOptions(decodedOptions);
71
- if (admission.kind === "refused")
71
+ const readonly = recipe.readonly === undefined ? undefined : decodeReadonlyRestraint(recipe.readonly);
72
+ if ((decodedOptions.readonly === true) !== (readonly !== undefined))
72
73
  return null;
73
- const confinement = adapter.confinement({ cwd, options: admission.options });
74
+ const confinement = adapter.confinement({ cwd, options: decodedOptions });
74
75
  if (!matchingConfinement(recipe.confinement, confinement))
75
76
  return null;
76
77
  return Object.freeze({
77
78
  ...(recipe.description === undefined ? {} : { description: recipe.description }),
78
79
  provider,
79
- options: admission.options,
80
+ options: decodedOptions,
81
+ ...(readonly === undefined ? {} : { readonly }),
80
82
  confinement,
81
83
  });
82
84
  }
@@ -158,14 +160,14 @@ function decodeReceipt(bytes, requestId) {
158
160
  }
159
161
  return null;
160
162
  }
161
- function atomicJson(path, value) {
163
+ async function atomicJson(path, value) {
162
164
  const temporary = `${path}.${randomUUID()}.tmp`;
163
165
  try {
164
- writeFileSync(temporary, `${JSON.stringify(value)}\n`, { flag: "wx" });
165
- renameSync(temporary, path);
166
+ await writeFile(temporary, `${JSON.stringify(value)}\n`, { flag: "wx" });
167
+ await rename(temporary, path);
166
168
  }
167
169
  finally {
168
- rmSync(temporary, { force: true });
170
+ await rm(temporary, { force: true });
169
171
  }
170
172
  }
171
173
  function receiptFor(fact) {
@@ -177,10 +179,10 @@ function receiptFor(fact) {
177
179
  return { id: fact.id, state: fact.state, evidence: fact.evidence };
178
180
  return null;
179
181
  }
180
- function projectReceipt(directory, fact) {
182
+ async function projectReceipt(directory, fact) {
181
183
  const receipt = receiptFor(fact);
182
184
  if (receipt !== null)
183
- atomicJson(join(directory, `${fact.id}.receipt.json`), receipt);
185
+ await atomicJson(join(directory, `${fact.id}.receipt.json`), receipt);
184
186
  }
185
187
  export function injectedBodyRequests() {
186
188
  const directory = process.env[AKUMA_REQUESTS_ENV];
@@ -191,7 +193,7 @@ export function injectedBodyRequests() {
191
193
  return directory;
192
194
  }
193
195
  export async function requestBodyCall(input) {
194
- atomicJson(join(input.directory, `${input.id}.request.json`), {
196
+ await atomicJson(join(input.directory, `${input.id}.request.json`), {
195
197
  id: input.id,
196
198
  world: input.world,
197
199
  archetype: input.archetype,
@@ -202,7 +204,7 @@ export async function requestBodyCall(input) {
202
204
  const receiptPath = join(input.directory, `${input.id}.receipt.json`);
203
205
  for (;;) {
204
206
  try {
205
- const receipt = decodeReceipt(readFileSync(receiptPath, "utf8"), input.id);
207
+ const receipt = decodeReceipt(await readFile(receiptPath, "utf8"), input.id);
206
208
  if (receipt === null)
207
209
  throw new Error(`Akuma body request ${input.id} has an invalid receipt`);
208
210
  if (receipt.state === "served")
@@ -215,14 +217,16 @@ export async function requestBodyCall(input) {
215
217
  if (error.code !== "ENOENT")
216
218
  throw error;
217
219
  }
218
- await wait(POLL_MS);
220
+ await abortableDelay(POLL_MS);
219
221
  }
220
222
  }
221
223
  async function serveClaim(input) {
222
- let fact = admitRequest(input.paths, { ...input.claim, admittedAt: input.now() });
224
+ input.signal.throwIfAborted();
225
+ let fact = await admitRequest(input.paths, { ...input.claim, admittedAt: input.now() });
223
226
  const existing = receiptFor(fact);
224
227
  if (existing !== null) {
225
- projectReceipt(input.directory, fact);
228
+ if (!input.signal.aborted)
229
+ await projectReceipt(input.directory, fact);
226
230
  return;
227
231
  }
228
232
  if (fact.state !== "admitted")
@@ -230,17 +234,19 @@ async function serveClaim(input) {
230
234
  const request = fact;
231
235
  const servingWorld = worldRootForAkumaPaths(input.paths);
232
236
  if (request.world !== servingWorld) {
233
- fact = refuseRequest(input.paths, request.id, `request world ${request.world} does not match ${servingWorld}`);
234
- projectReceipt(input.directory, fact);
237
+ fact = await refuseRequest(input.paths, request.id, `request world ${request.world} does not match ${servingWorld}`);
238
+ await projectReceipt(input.directory, fact);
235
239
  return;
236
240
  }
237
241
  const cwd = resolve(request.cwd ?? servingWorld);
238
242
  let child;
239
243
  try {
244
+ input.signal.throwIfAborted();
240
245
  const published = await publishAkuma({
241
246
  worldPath: servingWorld,
242
247
  archetype: request.archetype,
243
- reserve: (allocated) => { fact = reserveRequest(input.paths, request.id, allocated.id); },
248
+ reserve: async (allocated) => { fact = await reserveRequest(input.paths, request.id, allocated.id); },
249
+ signal: input.signal,
244
250
  launch: async (allocated) => await input.spawn({
245
251
  paths: allocated.paths,
246
252
  seed: {
@@ -249,8 +255,9 @@ async function serveClaim(input) {
249
255
  ...(request.recipe.description === undefined ? {} : { description: request.recipe.description }),
250
256
  provider: request.recipe.provider,
251
257
  options: request.recipe.options,
258
+ ...(request.recipe.readonly === undefined ? {} : { readonly: request.recipe.readonly }),
252
259
  cwd,
253
- origin: { kind: "request", parentId: input.parent.id, requestId: request.id },
260
+ origin: { kind: "request", parent: input.parent.id, requestId: request.id },
254
261
  confinement: request.recipe.confinement,
255
262
  },
256
263
  initialBody: request.body,
@@ -259,21 +266,23 @@ async function serveClaim(input) {
259
266
  child = published.id;
260
267
  }
261
268
  catch (error) {
262
- const current = readRequest(input.paths, request.id);
269
+ if (input.signal.aborted)
270
+ return;
271
+ const current = await readRequest(input.paths, request.id);
263
272
  if (current === null)
264
273
  throw error;
265
274
  fact = current.state === "admitted" || current.state === "reserved"
266
- ? voidRequest(input.paths, request.id, diagnostic(error))
275
+ ? await voidRequest(input.paths, request.id, diagnostic(error))
267
276
  : current;
268
- projectReceipt(input.directory, fact);
277
+ await projectReceipt(input.directory, fact);
269
278
  return;
270
279
  }
271
- fact = serveRequest(input.paths, request.id, child);
272
- projectReceipt(input.directory, fact);
280
+ fact = await serveRequest(input.paths, request.id, child);
281
+ await projectReceipt(input.directory, fact);
273
282
  }
274
- function requestFiles(directory) {
283
+ async function requestFiles(directory) {
275
284
  try {
276
- return readdirSync(directory)
285
+ return (await readdir(directory))
277
286
  .filter((name) => name.endsWith(".request.json"))
278
287
  .sort();
279
288
  }
@@ -287,35 +296,47 @@ export class BodyRequestPump {
287
296
  input;
288
297
  directory;
289
298
  failure;
290
- closed = false;
299
+ closeSignal = new AbortController();
291
300
  handled = new Set();
292
301
  running;
302
+ cancelAdmission;
293
303
  constructor(input) {
294
304
  this.input = input;
295
305
  this.directory = join(input.paths.directory, "requests", String(input.bodySequence));
296
- mkdirSync(this.directory, { recursive: true });
306
+ this.cancelAdmission = () => this.stopAdmission();
307
+ input.signal.addEventListener("abort", this.cancelAdmission, { once: true });
308
+ if (input.signal.aborted)
309
+ this.cancelAdmission();
297
310
  this.running = this.run();
298
311
  this.failure = this.running.then(() => new Promise(() => { }), (error) => Promise.reject(error));
299
312
  }
313
+ static async open(input) {
314
+ const directory = join(input.paths.directory, "requests", String(input.bodySequence));
315
+ await mkdir(directory, { recursive: true });
316
+ return new BodyRequestPump(input);
317
+ }
300
318
  async run() {
301
- while (!this.closed) {
302
- for (const name of requestFiles(this.directory)) {
303
- if (this.closed)
319
+ while (!this.closeSignal.signal.aborted) {
320
+ for (const name of await requestFiles(this.directory)) {
321
+ if (this.closeSignal.signal.aborted)
304
322
  return;
305
323
  const id = name.slice(0, -".request.json".length);
306
324
  if (this.handled.has(id))
307
325
  continue;
308
- const claim = decodeClaim(readFileSync(join(this.directory, name), "utf8"), id);
326
+ const claim = decodeClaim(await readFile(join(this.directory, name), "utf8"), id);
309
327
  if (claim === null) {
310
328
  this.handled.add(id);
311
329
  continue;
312
330
  }
313
- if (this.closed)
314
- return;
315
- await serveClaim({ directory: this.directory, claim, ...this.input });
331
+ await serveClaim({ directory: this.directory, claim, ...this.input, signal: this.closeSignal.signal });
316
332
  this.handled.add(id);
317
333
  }
318
- await wait(POLL_MS);
334
+ try {
335
+ await abortableDelay(POLL_MS, this.closeSignal.signal);
336
+ }
337
+ catch {
338
+ return;
339
+ }
319
340
  }
320
341
  }
321
342
  async close() {
@@ -324,45 +345,53 @@ export class BodyRequestPump {
324
345
  await this.running;
325
346
  }
326
347
  finally {
327
- rmSync(this.directory, { recursive: true, force: true });
348
+ this.input.signal.removeEventListener("abort", this.cancelAdmission);
349
+ await rm(this.directory, { recursive: true, force: true });
328
350
  }
329
351
  }
330
- stopAdmission() { this.closed = true; }
352
+ stopAdmission() {
353
+ if (!this.closeSignal.signal.aborted)
354
+ this.closeSignal.abort(new Error("Body request pump closed"));
355
+ }
356
+ }
357
+ export async function clearBodyRequestTransport(paths) {
358
+ await rm(join(paths.directory, "requests"), { recursive: true, force: true });
331
359
  }
332
360
  function matchingRequestOrigin(soul, parent, requestId) {
333
361
  return soul.origin.kind === "request"
334
- && soul.origin.parentId === parent
362
+ && soul.origin.parent === parent
335
363
  && soul.origin.requestId === requestId;
336
364
  }
337
- function settleObservedSoul(paths, parent, request, soul) {
365
+ async function settleObservedSoul(paths, parent, request, soul) {
338
366
  if (matchingRequestOrigin(soul, parent.id, request.id))
339
- serveRequest(paths, request.id, request.child);
367
+ await serveRequest(paths, request.id, request.child);
340
368
  else
341
- voidRequest(paths, request.id, "reserved child origin does not match the request");
369
+ await voidRequest(paths, request.id, "reserved child origin does not match the request");
342
370
  }
343
- async function settleReserved(paths, parent, request, now) {
371
+ async function settleReserved(paths, parent, request, now, signal) {
344
372
  const childPaths = pathsForAkuId(request.world, request.child);
345
373
  const deadline = performance.now() + BIRTH_TIMEOUT_MS;
346
374
  for (;;) {
347
- if (!existsSync(childPaths.directory)) {
348
- voidRequest(paths, request.id, "reserved child directory is absent");
375
+ signal?.throwIfAborted();
376
+ if (!await access(childPaths.directory).then(() => true, () => false)) {
377
+ await voidRequest(paths, request.id, "reserved child directory is absent");
349
378
  return true;
350
379
  }
351
- const childSoul = readSoul(childPaths);
380
+ const childSoul = await readSoul(childPaths);
352
381
  if (childSoul !== null) {
353
- settleObservedSoul(paths, parent, request, childSoul);
382
+ await settleObservedSoul(paths, parent, request, childSoul);
354
383
  return true;
355
384
  }
356
- const leash = HeldAkumaLeash.try(childPaths);
385
+ const leash = await HeldAkumaLeash.try(childPaths);
357
386
  if (leash !== null) {
358
387
  try {
359
- const settledSoul = readSoul(childPaths);
388
+ const settledSoul = await readSoul(childPaths);
360
389
  if (settledSoul !== null) {
361
- settleObservedSoul(paths, parent, request, settledSoul);
390
+ await settleObservedSoul(paths, parent, request, settledSoul);
362
391
  }
363
392
  else {
364
- leash.sealIfUnborn(childPaths, { evidence: "request settlement", at: now() });
365
- voidRequest(paths, request.id, "reserved child was sealed unborn");
393
+ await leash.sealIfUnborn(childPaths, { evidence: "request settlement", at: now() });
394
+ await voidRequest(paths, request.id, "reserved child was sealed unborn");
366
395
  }
367
396
  }
368
397
  finally {
@@ -372,16 +401,17 @@ async function settleReserved(paths, parent, request, now) {
372
401
  }
373
402
  if (performance.now() >= deadline)
374
403
  return false;
375
- await wait(POLL_MS);
404
+ await abortableDelay(POLL_MS, signal);
376
405
  }
377
406
  }
378
- export async function settleBodyRequests(paths, parent, now) {
407
+ export async function settleBodyRequests(paths, parent, now, signal) {
379
408
  let pending = false;
380
- for (const request of readNonterminalRequests(paths)) {
409
+ for (const request of await readNonterminalRequests(paths)) {
410
+ signal?.throwIfAborted();
381
411
  if (request.state === "admitted") {
382
- voidRequest(paths, request.id, "body died before serving the request");
412
+ await voidRequest(paths, request.id, "body died before serving the request");
383
413
  }
384
- else if (request.state === "reserved" && !await settleReserved(paths, parent, request, now))
414
+ else if (request.state === "reserved" && !await settleReserved(paths, parent, request, now, signal))
385
415
  pending = true;
386
416
  }
387
417
  return pending ? "pending" : "settled";
@@ -1,5 +1,6 @@
1
1
  import { type AkuId } from "../akuma/identity.js";
2
2
  import { type AkumaAlias } from "../identity/selector.js";
3
+ import type { WorldRoot } from "../world.js";
3
4
  export type AliasBinding = Readonly<{
4
5
  alias: AkumaAlias;
5
6
  akuId: AkuId;
@@ -8,10 +9,10 @@ export type AliasMove = Readonly<{
8
9
  alias: AliasBinding;
9
10
  previous: AkuId | null;
10
11
  }>;
11
- export declare function readAliases(world: string): readonly AliasBinding[];
12
- export declare function resolveAlias(world: string, value: AkumaAlias): AkuId | null;
12
+ export declare function readAliases(world: WorldRoot): Promise<readonly AliasBinding[]>;
13
+ export declare function resolveAlias(world: WorldRoot, value: AkumaAlias): Promise<AkuId | null>;
13
14
  export declare function moveAlias(input: Readonly<{
14
- world: string;
15
+ world: WorldRoot;
15
16
  alias: AkumaAlias;
16
17
  akuId: AkuId;
17
18
  }>): Promise<AliasMove>;
@@ -1,4 +1,4 @@
1
- import { mkdirSync, readFileSync } from "node:fs";
1
+ import { mkdir, readFile } from "node:fs/promises";
2
2
  import { dirname, resolve } from "node:path";
3
3
  import { parseAkuId } from "../akuma/identity.js";
4
4
  import { replaceFileDurably } from "../coordination/durable-file.js";
@@ -10,10 +10,9 @@ function paths(world) {
10
10
  if (typeof world !== "string" || world.trim().length === 0) {
11
11
  throw new TypeError("Alias world must be a nonblank path");
12
12
  }
13
- const root = resolve(world);
14
13
  return {
15
- authority: resolve(root, ".keiyaku", "akuma", "alias.json"),
16
- lock: resolve(root, ".keiyaku", "locks", "akuma-alias.sqlite"),
14
+ authority: resolve(world, ".keiyaku", "akuma", "alias.json"),
15
+ lock: resolve(world, ".keiyaku", "locks", "akuma-alias.sqlite"),
17
16
  };
18
17
  }
19
18
  function canonical(bindings) {
@@ -68,9 +67,9 @@ function decode(path, bytes) {
68
67
  corruption(`Alias bytes are not canonical: ${path}`);
69
68
  return bindings;
70
69
  }
71
- function read(path) {
70
+ async function read(path) {
72
71
  try {
73
- return decode(path, readFileSync(path, "utf8"));
72
+ return decode(path, await readFile(path, "utf8"));
74
73
  }
75
74
  catch (error) {
76
75
  if (error.code === "ENOENT")
@@ -78,12 +77,12 @@ function read(path) {
78
77
  throw error;
79
78
  }
80
79
  }
81
- export function readAliases(world) {
82
- return read(paths(world).authority);
80
+ export async function readAliases(world) {
81
+ return await read(paths(world).authority);
83
82
  }
84
- export function resolveAlias(world, value) {
83
+ export async function resolveAlias(world, value) {
85
84
  const alias = parseAkumaAlias(value);
86
- return readAliases(world).find((binding) => binding.alias === alias)?.akuId ?? null;
85
+ return (await readAliases(world)).find((binding) => binding.alias === alias)?.akuId ?? null;
87
86
  }
88
87
  export async function moveAlias(input) {
89
88
  const alias = parseAkumaAlias(input.alias);
@@ -91,11 +90,11 @@ export async function moveAlias(input) {
91
90
  const location = paths(input.world);
92
91
  const held = await acquireSqliteTransactionLock({ path: location.lock, mode: "immediate" });
93
92
  try {
94
- const current = read(location.authority);
93
+ const current = await read(location.authority);
95
94
  const previous = current.find((binding) => binding.alias === alias)?.akuId ?? null;
96
95
  const next = [...current.filter((binding) => binding.alias !== alias), { alias, akuId }];
97
- mkdirSync(dirname(location.authority), { recursive: true });
98
- replaceFileDurably(location.authority, canonical(next));
96
+ await mkdir(dirname(location.authority), { recursive: true });
97
+ await replaceFileDurably(location.authority, canonical(next));
99
98
  return { alias: { alias, akuId }, previous };
100
99
  }
101
100
  finally {
@@ -91,7 +91,13 @@ function verification(document, section) {
91
91
  }
92
92
  }
93
93
  export function decodeContractDocument(source) {
94
- const document = parseToAST(source);
94
+ let document;
95
+ try {
96
+ document = parseToAST(source);
97
+ }
98
+ catch (error) {
99
+ refusal(error instanceof Error ? error.message : String(error));
100
+ }
95
101
  if (document.frontmatter !== undefined)
96
102
  refusal("contract document may not contain frontmatter");
97
103
  const titles = indexedHeadings(indexDocument(document), { level: 1 })
@@ -2,5 +2,7 @@ import type { DocumentNode, SectionNode } from "../markdown/types.js";
2
2
  export declare class RegionDocumentError extends Error {
3
3
  constructor(message: string);
4
4
  }
5
+ export declare function assertCanonicalRegionPath(path: string): void;
5
6
  export declare function decodeRegion(document: DocumentNode, section: SectionNode): readonly string[];
6
7
  export declare function regionsOverlap(mine: readonly string[], theirs: readonly string[]): readonly [string, string][];
8
+ export declare function regionPatternsMatchPath(patterns: readonly string[], path: string): readonly string[];
@@ -28,15 +28,79 @@ function compileRegionPattern(pattern) {
28
28
  refusal(`Region pattern '${pattern}' contains a forbidden glob form`);
29
29
  if (pattern.startsWith("/"))
30
30
  refusal(`Region pattern '${pattern}' must be repository-relative`);
31
- if (pattern.endsWith("/"))
32
- refusal(`Region pattern '${pattern}' may not end with /`);
33
31
  if (pattern.includes(".."))
34
32
  refusal(`Region pattern '${pattern}' may not contain ..`);
35
33
  const segments = pattern.split("/");
36
- if (segments.some((segment) => segment.length === 0)) {
34
+ const directory = pattern.endsWith("/");
35
+ if (segments.some((segment, index) => segment.length === 0 && !(directory && index === segments.length - 1))) {
37
36
  refusal(`Region pattern '${pattern}' may not contain an empty segment`);
38
37
  }
39
- return { source: pattern, segments: segments.map((segment) => compileSegment(segment, pattern)) };
38
+ const source = directory ? `${pattern}**` : pattern;
39
+ const canonical = directory ? [...segments.slice(0, -1), "**"] : segments;
40
+ return { source, segments: canonical.map((segment) => compileSegment(segment, pattern)) };
41
+ }
42
+ export function assertCanonicalRegionPath(path) {
43
+ if (path.length === 0 || /[\r\n\0]/u.test(path) || path.startsWith("/") || path.endsWith("/")) {
44
+ refusal(`Region path '${path}' must be a canonical repository-relative path`);
45
+ }
46
+ if (path.split("/").some((segment) => segment.length === 0 || segment === "." || segment === "..")) {
47
+ refusal(`Region path '${path}' must be a canonical repository-relative path`);
48
+ }
49
+ }
50
+ function segmentMatchesPath(pattern, path) {
51
+ const queue = [[0, 0]];
52
+ const visited = new Set();
53
+ let cursor = 0;
54
+ while (cursor < queue.length) {
55
+ const [patternIndex, pathIndex] = queue[cursor++];
56
+ const key = `${patternIndex}:${pathIndex}`;
57
+ if (visited.has(key))
58
+ continue;
59
+ visited.add(key);
60
+ if (patternIndex === pattern.length && pathIndex === path.length)
61
+ return true;
62
+ const character = pattern[patternIndex];
63
+ if (character === "*") {
64
+ queue.push([patternIndex + 1, pathIndex]);
65
+ if (pathIndex < path.length)
66
+ queue.push([patternIndex, pathIndex + 1]);
67
+ continue;
68
+ }
69
+ if (character === "?") {
70
+ if (pathIndex < path.length)
71
+ queue.push([patternIndex + 1, pathIndex + 1]);
72
+ continue;
73
+ }
74
+ if (character !== undefined && character === path[pathIndex])
75
+ queue.push([patternIndex + 1, pathIndex + 1]);
76
+ }
77
+ return false;
78
+ }
79
+ function patternMatchesPath(pattern, path) {
80
+ const queue = [[0, 0]];
81
+ const visited = new Set();
82
+ let cursor = 0;
83
+ while (cursor < queue.length) {
84
+ const [patternIndex, pathIndex] = queue[cursor++];
85
+ const key = `${patternIndex}:${pathIndex}`;
86
+ if (visited.has(key))
87
+ continue;
88
+ visited.add(key);
89
+ if (patternIndex === pattern.length && pathIndex === path.length)
90
+ return true;
91
+ const segment = pattern[patternIndex];
92
+ if (segment?.kind === "deep") {
93
+ queue.push([patternIndex + 1, pathIndex]);
94
+ if (pathIndex < path.length)
95
+ queue.push([patternIndex, pathIndex + 1]);
96
+ continue;
97
+ }
98
+ if (segment === undefined || path[pathIndex] === undefined)
99
+ continue;
100
+ if (segmentMatchesPath(segment.characters, Array.from(path[pathIndex])))
101
+ queue.push([patternIndex + 1, pathIndex + 1]);
102
+ }
103
+ return false;
40
104
  }
41
105
  function segmentTransition(characters, index) {
42
106
  const character = characters[index];
@@ -120,9 +184,7 @@ export function decodeRegion(document, section) {
120
184
  const patterns = blocks[0].lines.slice(1, -1).filter((line) => line.trim().length > 0);
121
185
  if (patterns.length === 0)
122
186
  refusal("Region must declare at least one path pattern");
123
- for (const pattern of patterns)
124
- compileRegionPattern(pattern);
125
- return patterns;
187
+ return patterns.map((pattern) => compileRegionPattern(pattern).source);
126
188
  }
127
189
  export function regionsOverlap(mine, theirs) {
128
190
  const myPatterns = mine.map((pattern) => compileRegionPattern(pattern));
@@ -137,3 +199,11 @@ export function regionsOverlap(mine, theirs) {
137
199
  }
138
200
  return overlaps;
139
201
  }
202
+ export function regionPatternsMatchPath(patterns, path) {
203
+ assertCanonicalRegionPath(path);
204
+ const pathSegments = path.split("/");
205
+ return patterns
206
+ .map((pattern) => compileRegionPattern(pattern))
207
+ .filter((pattern) => patternMatchesPath(pattern.segments, pathSegments))
208
+ .map((pattern) => pattern.source);
209
+ }
@@ -1,3 +1,4 @@
1
+ import { formatDuration } from "../duration.js";
1
2
  import { parseToAST } from "../markdown/parse.js";
2
3
  import { indexDocument, indexedHeadings, normalizeTitle, rawSlice } from "../markdown/query.js";
3
4
  import { CONTRACT_SECTIONS } from "./shape.js";
@@ -21,7 +22,7 @@ function fenced(value, info = "") {
21
22
  return `${boundary}${info}\n${value}\n${boundary}`;
22
23
  }
23
24
  function verificationFence(declaration) {
24
- return fenced(declaration.script, declaration.executor);
25
+ return fenced(declaration.script, `${declaration.executor}${declaration.timeoutMs === undefined ? "" : ` timeout=${formatDuration(declaration.timeoutMs)}`}`);
25
26
  }
26
27
  function arcSection(arc) {
27
28
  return [
@@ -6,7 +6,13 @@ export const CONTRACT_SECTIONS = {
6
6
  criteria: { title: "Criteria", required: true },
7
7
  verification: { title: "Verification", required: false },
8
8
  };
9
- export const RESERVED_SECTIONS = new Set(["gates", "pipeline", "after"]);
9
+ export const RESERVED_SECTIONS = new Set([
10
+ "gates",
11
+ "pipeline",
12
+ "after",
13
+ "arc",
14
+ "fulfillment",
15
+ ]);
10
16
  export function contractSectionName(title) {
11
17
  for (const name of Object.keys(CONTRACT_SECTIONS)) {
12
18
  if (CONTRACT_SECTIONS[name].title === title)
@@ -1,4 +1,5 @@
1
1
  import { directChildren, rawSlice } from "../markdown/query.js";
2
+ import { parseDuration } from "../duration.js";
2
3
  export class VerificationDocumentError extends Error {
3
4
  constructor(message) {
4
5
  super(message);
@@ -13,13 +14,27 @@ export function decodeVerificationDeclarations(document, section) {
13
14
  if (other.length > 0)
14
15
  throw new VerificationDocumentError("Verification may contain only fenced executor declarations");
15
16
  return blocks.map((block) => {
16
- const executor = block.info.trim();
17
- if (!block.closed || (executor !== "bash" && executor !== "zsh" && executor !== "pwsh")) {
18
- throw new VerificationDocumentError("Verification fences must be closed and use bash, zsh, or pwsh");
17
+ const info = /^(bash|zsh|pwsh)(?: timeout=([^ ]+))?$/.exec(block.info);
18
+ if (!block.closed || info === null) {
19
+ throw new VerificationDocumentError("Verification fences must be closed and use bash, zsh, or pwsh with optional timeout=<duration>");
19
20
  }
20
21
  const script = block.lines.slice(1, -1).join("\n");
21
22
  if (script.trim().length === 0)
22
23
  throw new VerificationDocumentError("Verification scripts must be nonblank");
23
- return { executor, script };
24
+ const duration = info[2] === undefined ? undefined : parseDuration(info[2]);
25
+ if (duration?.kind === "invalid") {
26
+ throw new VerificationDocumentError("Verification timeout must be an integer duration with unit ms, s, m, or h");
27
+ }
28
+ if (duration?.kind === "overflow" || (duration?.kind === "parsed" && duration.milliseconds > 2_147_483_647)) {
29
+ throw new VerificationDocumentError("Verification timeout exceeds the supported process duration");
30
+ }
31
+ if (duration?.kind === "parsed" && duration.milliseconds === 0) {
32
+ throw new VerificationDocumentError("Verification timeout must be positive");
33
+ }
34
+ return {
35
+ executor: info[1],
36
+ script,
37
+ ...(duration === undefined ? {} : { timeoutMs: duration.milliseconds }),
38
+ };
24
39
  });
25
40
  }