@astrosheep/keiyaku 4.0.1 → 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 (245) hide show
  1. package/README.md +137 -5
  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/package.json +1 -1
  5. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku/SKILL.md +3 -3
  6. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-akuma/SKILL.md +91 -35
  7. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-bind/SKILL.md +98 -58
  8. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-workflow/SKILL.md +152 -53
  9. package/build/src/akuma/abort.d.ts +2 -0
  10. package/build/src/akuma/abort.js +50 -0
  11. package/build/src/akuma/akuma.d.ts +23 -14
  12. package/build/src/akuma/akuma.js +120 -104
  13. package/build/src/akuma/archetype.d.ts +7 -6
  14. package/build/src/akuma/archetype.js +68 -41
  15. package/build/src/akuma/body.d.ts +3 -5
  16. package/build/src/akuma/body.js +408 -213
  17. package/build/src/akuma/heart/facts.d.ts +25 -34
  18. package/build/src/akuma/heart/facts.js +18 -7
  19. package/build/src/akuma/heart/index.d.ts +39 -42
  20. package/build/src/akuma/heart/index.js +134 -93
  21. package/build/src/akuma/heart/rows.d.ts +9 -31
  22. package/build/src/akuma/heart/rows.js +26 -44
  23. package/build/src/akuma/heart/schema.d.ts +1 -1
  24. package/build/src/akuma/heart/schema.js +7 -15
  25. package/build/src/akuma/heart/soul.d.ts +8 -0
  26. package/build/src/akuma/heart/soul.js +136 -0
  27. package/build/src/akuma/heart/storage.d.ts +28 -14
  28. package/build/src/akuma/heart/storage.js +86 -26
  29. package/build/src/akuma/identity.d.ts +2 -2
  30. package/build/src/akuma/identity.js +9 -9
  31. package/build/src/akuma/index.d.ts +6 -1
  32. package/build/src/akuma/index.js +6 -0
  33. package/build/src/akuma/projection.d.ts +117 -38
  34. package/build/src/akuma/projection.js +208 -97
  35. package/build/src/akuma/provider-recipe.d.ts +24 -0
  36. package/build/src/akuma/provider-recipe.js +100 -0
  37. package/build/src/akuma/provider.d.ts +12 -4
  38. package/build/src/akuma/provider.js +117 -70
  39. package/build/src/akuma/providers/acp/core.d.ts +19 -0
  40. package/build/src/akuma/providers/acp/core.js +159 -0
  41. package/build/src/akuma/providers/acp/events.d.ts +20 -0
  42. package/build/src/akuma/providers/acp/events.js +73 -0
  43. package/build/src/akuma/providers/acp/index.d.ts +12 -0
  44. package/build/src/akuma/providers/acp/index.js +94 -0
  45. package/build/src/akuma/providers/claude/events.js +66 -13
  46. package/build/src/akuma/providers/claude/index.d.ts +2 -1
  47. package/build/src/akuma/providers/claude/index.js +35 -21
  48. package/build/src/akuma/providers/codex-app-server/events.js +4 -1
  49. package/build/src/akuma/providers/codex-app-server/index.d.ts +3 -3
  50. package/build/src/akuma/providers/codex-app-server/index.js +24 -31
  51. package/build/src/akuma/providers/grok-build/index.d.ts +4 -0
  52. package/build/src/akuma/providers/grok-build/index.js +70 -0
  53. package/build/src/akuma/providers/index.d.ts +5 -3
  54. package/build/src/akuma/providers/index.js +24 -59
  55. package/build/src/akuma/providers/opencode-sdk/events.d.ts +40 -34
  56. package/build/src/akuma/providers/opencode-sdk/events.js +220 -101
  57. package/build/src/akuma/providers/opencode-sdk/index.d.ts +3 -2
  58. package/build/src/akuma/providers/opencode-sdk/index.js +233 -128
  59. package/build/src/akuma/providers/opencode-sdk/session.d.ts +11 -26
  60. package/build/src/akuma/providers/opencode-sdk/session.js +10 -9
  61. package/build/src/akuma/providers/pi/events.js +49 -11
  62. package/build/src/akuma/providers/pi/index.d.ts +1 -1
  63. package/build/src/akuma/providers/pi/index.js +50 -21
  64. package/build/src/akuma/publication.d.ts +3 -3
  65. package/build/src/akuma/publication.js +28 -40
  66. package/build/src/akuma/requests.d.ts +9 -7
  67. package/build/src/akuma/requests.js +92 -65
  68. package/build/src/alias/index.d.ts +2 -2
  69. package/build/src/alias/index.js +10 -10
  70. package/build/src/body/decode.js +7 -1
  71. package/build/src/body/region.d.ts +2 -0
  72. package/build/src/body/region.js +77 -7
  73. package/build/src/cli/accepted.d.ts +17 -13
  74. package/build/src/cli/accepted.js +72 -34
  75. package/build/src/cli/actor.js +3 -3
  76. package/build/src/cli/commands/akuma-invoke.d.ts +4 -3
  77. package/build/src/cli/commands/akuma-invoke.js +14 -9
  78. package/build/src/cli/commands/akuma.d.ts +13 -3
  79. package/build/src/cli/commands/akuma.js +67 -34
  80. package/build/src/cli/commands/contract.d.ts +18 -5
  81. package/build/src/cli/commands/contract.js +9 -3
  82. package/build/src/cli/commands/task-invoke.d.ts +17 -4
  83. package/build/src/cli/commands/task-invoke.js +43 -20
  84. package/build/src/cli/commands/task-query.d.ts +5 -0
  85. package/build/src/cli/commands/task-query.js +247 -0
  86. package/build/src/cli/commands/task.d.ts +3 -1
  87. package/build/src/cli/commands/task.js +51 -13
  88. package/build/src/cli/coordinates.d.ts +18 -0
  89. package/build/src/cli/coordinates.js +120 -0
  90. package/build/src/cli/draft.d.ts +8 -0
  91. package/build/src/cli/draft.js +96 -0
  92. package/build/src/cli/invoke.d.ts +4 -3
  93. package/build/src/cli/invoke.js +160 -179
  94. package/build/src/cli/main.js +34 -6
  95. package/build/src/cli/parse.d.ts +9 -2
  96. package/build/src/cli/parse.js +29 -7
  97. package/build/src/cli/render/akuma-tool-command.d.ts +6 -0
  98. package/build/src/cli/render/akuma-tool-command.js +144 -0
  99. package/build/src/cli/render/akuma-tool.d.ts +2 -2
  100. package/build/src/cli/render/akuma-tool.js +30 -4
  101. package/build/src/cli/render/akuma.d.ts +1 -0
  102. package/build/src/cli/render/akuma.js +211 -116
  103. package/build/src/cli/render/audit.d.ts +3 -0
  104. package/build/src/cli/render/audit.js +104 -0
  105. package/build/src/cli/render/contract.d.ts +3 -3
  106. package/build/src/cli/render/contract.js +192 -79
  107. package/build/src/cli/render/kanshi.js +263 -168
  108. package/build/src/cli/render/receipt.d.ts +19 -0
  109. package/build/src/cli/render/receipt.js +106 -0
  110. package/build/src/cli/render/refusal.d.ts +16 -2
  111. package/build/src/cli/render/refusal.js +88 -20
  112. package/build/src/cli/render/region.d.ts +2 -0
  113. package/build/src/cli/render/region.js +17 -0
  114. package/build/src/cli/render/task.d.ts +2 -1
  115. package/build/src/cli/render/task.js +223 -60
  116. package/build/src/cli/render/terminal.d.ts +7 -0
  117. package/build/src/cli/render/terminal.js +46 -0
  118. package/build/src/cli/render/text.js +6 -3
  119. package/build/src/cli/result.d.ts +115 -20
  120. package/build/src/cli/usage.d.ts +1 -0
  121. package/build/src/cli/usage.js +3 -0
  122. package/build/src/contract-worktree.d.ts +6 -5
  123. package/build/src/contract-worktree.js +95 -57
  124. package/build/src/coordination/durable-file.d.ts +4 -3
  125. package/build/src/coordination/durable-file.js +29 -25
  126. package/build/src/coordination/sqlite-transaction-lock.d.ts +4 -0
  127. package/build/src/coordination/sqlite-transaction-lock.js +22 -5
  128. package/build/src/core/facts/fold.js +1 -6
  129. package/build/src/core/facts/gate.d.ts +1 -0
  130. package/build/src/core/facts/gate.js +1 -0
  131. package/build/src/core/verbs/amend.d.ts +1 -1
  132. package/build/src/core/verbs/amend.js +0 -11
  133. package/build/src/core/verbs/deliver.d.ts +1 -1
  134. package/build/src/core/verbs/deliver.js +1 -7
  135. package/build/src/core/verbs/placement.d.ts +1 -1
  136. package/build/src/core/verbs/placement.js +4 -1
  137. package/build/src/dispatch/index.d.ts +2 -2
  138. package/build/src/dispatch/index.js +17 -17
  139. package/build/src/git/admission.d.ts +1 -1
  140. package/build/src/git/admission.js +11 -11
  141. package/build/src/git/hooks.js +13 -13
  142. package/build/src/git/integration.d.ts +14 -15
  143. package/build/src/git/integration.js +80 -44
  144. package/build/src/git/observe.d.ts +5 -3
  145. package/build/src/git/observe.js +18 -12
  146. package/build/src/git/read-observation.js +9 -9
  147. package/build/src/git/reconcile.d.ts +2 -0
  148. package/build/src/git/reconcile.js +138 -95
  149. package/build/src/git/repository.d.ts +27 -19
  150. package/build/src/git/repository.js +155 -83
  151. package/build/src/git/scratch.d.ts +7 -3
  152. package/build/src/git/scratch.js +44 -37
  153. package/build/src/git/target-placement.d.ts +40 -4
  154. package/build/src/git/target-placement.js +221 -101
  155. package/build/src/git/tender.d.ts +6 -5
  156. package/build/src/git/tender.js +24 -43
  157. package/build/src/git/terminal-seal.d.ts +1 -1
  158. package/build/src/git/terminal-seal.js +7 -5
  159. package/build/src/git/workspace.d.ts +36 -4
  160. package/build/src/git/workspace.js +53 -18
  161. package/build/src/identity/selector.js +1 -1
  162. package/build/src/index.d.ts +3 -3
  163. package/build/src/index.js +1 -1
  164. package/build/src/kanshi/index.d.ts +2 -2
  165. package/build/src/kanshi/index.js +1 -1
  166. package/build/src/kanshi/read.d.ts +2 -1
  167. package/build/src/kanshi/read.js +118 -27
  168. package/build/src/kanshi/report.d.ts +50 -0
  169. package/build/src/kanshi/select.d.ts +5 -0
  170. package/build/src/kanshi/select.js +32 -0
  171. package/build/src/library/address.d.ts +12 -3
  172. package/build/src/library/address.js +77 -46
  173. package/build/src/library/akuma-creation.d.ts +6 -1
  174. package/build/src/library/akuma-creation.js +86 -13
  175. package/build/src/library/audit.d.ts +18 -0
  176. package/build/src/library/audit.js +44 -0
  177. package/build/src/library/bind.js +4 -4
  178. package/build/src/library/catalog.d.ts +1 -1
  179. package/build/src/library/catalog.js +5 -8
  180. package/build/src/library/contract.d.ts +5 -6
  181. package/build/src/library/contract.js +11 -35
  182. package/build/src/library/delivery.d.ts +2 -1
  183. package/build/src/library/fleet.d.ts +2 -2
  184. package/build/src/library/fleet.js +35 -71
  185. package/build/src/library/keiyaku.d.ts +3 -2
  186. package/build/src/library/keiyaku.js +1 -0
  187. package/build/src/library/mutation.d.ts +7 -6
  188. package/build/src/library/mutation.js +23 -25
  189. package/build/src/library/reconcile.d.ts +29 -0
  190. package/build/src/library/reconcile.js +179 -0
  191. package/build/src/library/region.d.ts +12 -0
  192. package/build/src/library/region.js +15 -1
  193. package/build/src/library/repo.d.ts +4 -16
  194. package/build/src/library/repo.js +13 -38
  195. package/build/src/protocol/attempt.js +6 -6
  196. package/build/src/protocol/bind.js +4 -4
  197. package/build/src/protocol/intent.d.ts +15 -2
  198. package/build/src/protocol/intent.js +21 -6
  199. package/build/src/protocol/operations.d.ts +73 -22
  200. package/build/src/protocol/operations.js +292 -88
  201. package/build/src/protocol/outcome.d.ts +9 -3
  202. package/build/src/protocol/outcome.js +3 -1
  203. package/build/src/protocol/placement.d.ts +15 -4
  204. package/build/src/protocol/placement.js +58 -25
  205. package/build/src/protocol/read/status.d.ts +8 -1
  206. package/build/src/protocol/read/status.js +61 -14
  207. package/build/src/runtime/proc/line-rpc.d.ts +3 -8
  208. package/build/src/runtime/proc/line-rpc.js +16 -43
  209. package/build/src/runtime/proc/run.d.ts +15 -29
  210. package/build/src/runtime/proc/run.js +113 -105
  211. package/build/src/runtime/proc/stdio.d.ts +18 -0
  212. package/build/src/runtime/proc/stdio.js +58 -0
  213. package/build/src/settings.d.ts +2 -2
  214. package/build/src/settings.js +8 -8
  215. package/build/src/settlement/fence.d.ts +0 -4
  216. package/build/src/settlement/fence.js +0 -9
  217. package/build/src/settlement/holder.d.ts +11 -1
  218. package/build/src/settlement/holder.js +25 -0
  219. package/build/src/settlement/settle.d.ts +1 -1
  220. package/build/src/settlement/settle.js +52 -89
  221. package/build/src/task/board.d.ts +3 -3
  222. package/build/src/task/board.js +6 -9
  223. package/build/src/task/compose.d.ts +1 -1
  224. package/build/src/task/compose.js +18 -10
  225. package/build/src/task/context.d.ts +3 -3
  226. package/build/src/task/context.js +14 -14
  227. package/build/src/task/document.d.ts +2 -1
  228. package/build/src/task/document.js +15 -5
  229. package/build/src/task/index.d.ts +19 -7
  230. package/build/src/task/index.js +42 -23
  231. package/build/src/task/operations.d.ts +15 -9
  232. package/build/src/task/operations.js +78 -48
  233. package/build/src/task/query.d.ts +67 -0
  234. package/build/src/task/query.js +279 -0
  235. package/build/src/task/store.d.ts +2 -2
  236. package/build/src/task/store.js +40 -25
  237. package/build/src/verification/execution.d.ts +4 -2
  238. package/build/src/verification/execution.js +9 -5
  239. package/build/src/workspace-place.d.ts +41 -0
  240. package/build/src/workspace-place.js +386 -0
  241. package/build/src/world.d.ts +13 -2
  242. package/build/src/world.js +81 -48
  243. package/package.json +3 -2
  244. package/build/src/protocol/read/audit.d.ts +0 -31
  245. package/build/src/protocol/read/audit.js +0 -55
@@ -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,6 +255,7 @@ 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
260
  origin: { kind: "request", parent: input.parent.id, requestId: request.id },
254
261
  confinement: request.recipe.confinement,
@@ -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,48 +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
+ }
331
356
  }
332
- export function clearBodyRequestTransport(paths) {
333
- rmSync(join(paths.directory, "requests"), { recursive: true, force: true });
357
+ export async function clearBodyRequestTransport(paths) {
358
+ await rm(join(paths.directory, "requests"), { recursive: true, force: true });
334
359
  }
335
360
  function matchingRequestOrigin(soul, parent, requestId) {
336
361
  return soul.origin.kind === "request"
337
362
  && soul.origin.parent === parent
338
363
  && soul.origin.requestId === requestId;
339
364
  }
340
- function settleObservedSoul(paths, parent, request, soul) {
365
+ async function settleObservedSoul(paths, parent, request, soul) {
341
366
  if (matchingRequestOrigin(soul, parent.id, request.id))
342
- serveRequest(paths, request.id, request.child);
367
+ await serveRequest(paths, request.id, request.child);
343
368
  else
344
- 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");
345
370
  }
346
- async function settleReserved(paths, parent, request, now) {
371
+ async function settleReserved(paths, parent, request, now, signal) {
347
372
  const childPaths = pathsForAkuId(request.world, request.child);
348
373
  const deadline = performance.now() + BIRTH_TIMEOUT_MS;
349
374
  for (;;) {
350
- if (!existsSync(childPaths.directory)) {
351
- 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");
352
378
  return true;
353
379
  }
354
- const childSoul = readSoul(childPaths);
380
+ const childSoul = await readSoul(childPaths);
355
381
  if (childSoul !== null) {
356
- settleObservedSoul(paths, parent, request, childSoul);
382
+ await settleObservedSoul(paths, parent, request, childSoul);
357
383
  return true;
358
384
  }
359
- const leash = HeldAkumaLeash.try(childPaths);
385
+ const leash = await HeldAkumaLeash.try(childPaths);
360
386
  if (leash !== null) {
361
387
  try {
362
- const settledSoul = readSoul(childPaths);
388
+ const settledSoul = await readSoul(childPaths);
363
389
  if (settledSoul !== null) {
364
- settleObservedSoul(paths, parent, request, settledSoul);
390
+ await settleObservedSoul(paths, parent, request, settledSoul);
365
391
  }
366
392
  else {
367
- leash.sealIfUnborn(childPaths, { evidence: "request settlement", at: now() });
368
- 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");
369
395
  }
370
396
  }
371
397
  finally {
@@ -375,16 +401,17 @@ async function settleReserved(paths, parent, request, now) {
375
401
  }
376
402
  if (performance.now() >= deadline)
377
403
  return false;
378
- await wait(POLL_MS);
404
+ await abortableDelay(POLL_MS, signal);
379
405
  }
380
406
  }
381
- export async function settleBodyRequests(paths, parent, now) {
407
+ export async function settleBodyRequests(paths, parent, now, signal) {
382
408
  let pending = false;
383
- for (const request of readNonterminalRequests(paths)) {
409
+ for (const request of await readNonterminalRequests(paths)) {
410
+ signal?.throwIfAborted();
384
411
  if (request.state === "admitted") {
385
- voidRequest(paths, request.id, "body died before serving the request");
412
+ await voidRequest(paths, request.id, "body died before serving the request");
386
413
  }
387
- else if (request.state === "reserved" && !await settleReserved(paths, parent, request, now))
414
+ else if (request.state === "reserved" && !await settleReserved(paths, parent, request, now, signal))
388
415
  pending = true;
389
416
  }
390
417
  return pending ? "pending" : "settled";
@@ -9,8 +9,8 @@ export type AliasMove = Readonly<{
9
9
  alias: AliasBinding;
10
10
  previous: AkuId | null;
11
11
  }>;
12
- export declare function readAliases(world: WorldRoot): readonly AliasBinding[];
13
- export declare function resolveAlias(world: WorldRoot, 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>;
14
14
  export declare function moveAlias(input: Readonly<{
15
15
  world: WorldRoot;
16
16
  alias: AkumaAlias;
@@ -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";
@@ -67,9 +67,9 @@ function decode(path, bytes) {
67
67
  corruption(`Alias bytes are not canonical: ${path}`);
68
68
  return bindings;
69
69
  }
70
- function read(path) {
70
+ async function read(path) {
71
71
  try {
72
- return decode(path, readFileSync(path, "utf8"));
72
+ return decode(path, await readFile(path, "utf8"));
73
73
  }
74
74
  catch (error) {
75
75
  if (error.code === "ENOENT")
@@ -77,12 +77,12 @@ function read(path) {
77
77
  throw error;
78
78
  }
79
79
  }
80
- export function readAliases(world) {
81
- return read(paths(world).authority);
80
+ export async function readAliases(world) {
81
+ return await read(paths(world).authority);
82
82
  }
83
- export function resolveAlias(world, value) {
83
+ export async function resolveAlias(world, value) {
84
84
  const alias = parseAkumaAlias(value);
85
- return readAliases(world).find((binding) => binding.alias === alias)?.akuId ?? null;
85
+ return (await readAliases(world)).find((binding) => binding.alias === alias)?.akuId ?? null;
86
86
  }
87
87
  export async function moveAlias(input) {
88
88
  const alias = parseAkumaAlias(input.alias);
@@ -90,11 +90,11 @@ export async function moveAlias(input) {
90
90
  const location = paths(input.world);
91
91
  const held = await acquireSqliteTransactionLock({ path: location.lock, mode: "immediate" });
92
92
  try {
93
- const current = read(location.authority);
93
+ const current = await read(location.authority);
94
94
  const previous = current.find((binding) => binding.alias === alias)?.akuId ?? null;
95
95
  const next = [...current.filter((binding) => binding.alias !== alias), { alias, akuId }];
96
- mkdirSync(dirname(location.authority), { recursive: true });
97
- replaceFileDurably(location.authority, canonical(next));
96
+ await mkdir(dirname(location.authority), { recursive: true });
97
+ await replaceFileDurably(location.authority, canonical(next));
98
98
  return { alias: { alias, akuId }, previous };
99
99
  }
100
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,18 +1,22 @@
1
- import { type ContractId, type MutationResult } from "../index.js";
2
- import type { AcceptedResult, InvocationResult } from "./result.js";
1
+ import { type AmendResult, type AuditReport, type BindResult, type ContractId, type Delivery, type MutationResult, type Review } from "../index.js";
2
+ import type { AcceptedAbandonResult, AcceptedAmendResult, AcceptedArcResult, AcceptedAuditResult, AcceptedBindResult, AcceptedDeliverResult, AcceptedResult, AcceptedReviewResult, InvocationResult } from "./result.js";
3
3
  type MutationObservation = Pick<MutationResult<unknown>, "facts" | "head" | "effects" | "lags" | "settlement">;
4
- type ResultOptions = Readonly<{
4
+ type MutationCallOptions = Readonly<{
5
5
  coordinate?: ContractId;
6
- target?: string | null;
7
- report?: import("../index.js").AuditReport;
8
- workspace?: AcceptedResult["workspace"];
9
- obligations?: Pick<AcceptedResult, "verification" | "placement" | "leak">;
10
- diff?: AcceptedResult["diff"];
11
- }>;
12
- type MutationCallOptions<Result extends MutationObservation> = Readonly<{
13
- coordinate?: ContractId;
14
- project?: (result: Result) => ResultOptions | Promise<ResultOptions>;
15
6
  projectRefusal?: (refusal: unknown) => unknown;
16
7
  }>;
17
- export declare function resultFromMutationCall<Result extends MutationObservation & object>(verb: string, call: () => Promise<Result>, options?: MutationCallOptions<Result>): Promise<InvocationResult>;
8
+ export declare function acceptedBind(result: BindResult, target: string | null): AcceptedBindResult;
9
+ export declare function acceptedAmend(result: AmendResult, coordinate: ContractId): AcceptedAmendResult;
10
+ export declare function acceptedDeliver(result: MutationResult<Delivery>, coordinate: ContractId): AcceptedDeliverResult;
11
+ export declare function acceptedReview(result: MutationResult<Review>, coordinate: ContractId): AcceptedReviewResult;
12
+ export declare function acceptedArc(result: MutationResult<void>, coordinate: ContractId): AcceptedArcResult;
13
+ export declare function acceptedAbandon(result: MutationResult<void>, coordinate: ContractId): AcceptedAbandonResult;
14
+ export declare function acceptedAudit(result: MutationResult<AuditReport>, coordinate: ContractId): AcceptedAuditResult;
15
+ export declare function resultFromMutationCall<const Verb extends AcceptedResult["verb"], Result extends MutationObservation>(verb: Verb, call: () => Promise<Result>, project: (result: Result) => Extract<AcceptedResult, {
16
+ verb: NoInfer<Verb>;
17
+ }>, options?: MutationCallOptions): Promise<Extract<AcceptedResult, {
18
+ verb: Verb;
19
+ }> | Extract<InvocationResult, {
20
+ kind: "refused" | "retry";
21
+ }>>;
18
22
  export {};