@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,7 +1,10 @@
1
1
  import type { TreeUpdate } from "../core/facts/offer.js";
2
2
  import { type ContractId } from "../core/facts/types.js";
3
- import { type GitRepository, type GitSnapshot } from "../git/repository.js";
3
+ import type { GitDecisionObservation } from "../git/observe.js";
4
+ import { type GitRepository } from "../git/repository.js";
5
+ import { type GitDecodeChannel, type GitReadObservation, type GitTreeSelection } from "../git/read-observation.js";
4
6
  import { type TaskId } from "../task/identity.js";
7
+ export declare function taskHolderObservationSelection(): GitTreeSelection;
5
8
  export type TaskHolder = Readonly<{
6
9
  version: 1;
7
10
  taskId: TaskId;
@@ -10,6 +13,34 @@ export type TaskHolder = Readonly<{
10
13
  }>;
11
14
  export type TaskHolderProjection = ReadonlyMap<ContractId, TaskHolder>;
12
15
  export declare function claimTaskHolder(taskId: TaskId, owner: ContractId): TreeUpdate;
13
- export declare function releaseTaskHolder(repository: GitRepository, snapshot: GitSnapshot, owner: ContractId): TreeUpdate | null;
14
- export declare function readTaskHolders(repository: GitRepository): readonly TaskHolder[];
15
- export declare function readTaskHolderProjection(repository: GitRepository): TaskHolderProjection;
16
+ type AdmissionOutcome = Readonly<{
17
+ kind: string;
18
+ }>;
19
+ export type TaskHolderAdmission<T extends AdmissionOutcome> = Readonly<{
20
+ kind: "completed";
21
+ result: T;
22
+ }> | Readonly<{
23
+ kind: "accepted-release-failed";
24
+ result: T;
25
+ taskId: TaskId;
26
+ diagnostic: string;
27
+ }>;
28
+ export declare function finishTaskHolderAdmission<T extends AdmissionOutcome>(taskId: TaskId, result: T, close: () => void): TaskHolderAdmission<T>;
29
+ export declare function claimTaskHolderWithFence<T extends AdmissionOutcome>(repository: GitRepository, taskId: TaskId, action: () => T | Promise<T>): Promise<TaskHolderAdmission<T>>;
30
+ export declare function releaseTaskHolder(channel: GitDecodeChannel, observation: GitDecisionObservation, owner: ContractId): Promise<TreeUpdate | null>;
31
+ export type TaskHolderReleasePublication = Readonly<{
32
+ kind: "released";
33
+ }> | Readonly<{
34
+ kind: "not-held";
35
+ }> | Readonly<{
36
+ kind: "non-published";
37
+ diagnostic: string;
38
+ }>;
39
+ /** 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 readTaskHolderProjectionFromDecision(channel: GitDecodeChannel, observation: GitDecisionObservation): Promise<TaskHolderProjection>;
42
+ export declare function observeTaskHolderProjection(repository: GitRepository, channel: GitDecodeChannel): Promise<TaskHolderProjection>;
43
+ export declare function releaseTaskHolderWithFence<T extends AdmissionOutcome>(repository: GitRepository, channel: GitDecodeChannel, owner: ContractId, action: () => T | Promise<T>): Promise<TaskHolderAdmission<T>>;
44
+ export declare function readTaskHoldersAt(observation: Pick<GitReadObservation, "snapshot" | "readBlobs">): Promise<readonly TaskHolder[]>;
45
+ export declare function readTaskHolderProjectionAt(observation: GitReadObservation): Promise<TaskHolderProjection>;
46
+ export {};
@@ -1,11 +1,16 @@
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 { expandGitSnapshot, readBlob, readGit, } from "../git/repository.js";
4
+ import { GIT_REF, updateGitTree, updateRefsAtomically, writeBlob, writeCommit, } from "../git/repository.js";
5
+ import { withGitTargetedReadObservation, } from "../git/read-observation.js";
5
6
  import { parseTaskId } from "../task/identity.js";
7
+ import { acquireTaskSettlementFence } from "./fence.js";
6
8
  const HOLDER_ROOT = "settlement/task-holders";
7
9
  const HOLDER_PREFIX = `${HOLDER_ROOT}/`;
8
10
  const HOLDER_SUFFIX = ".json";
11
+ export function taskHolderObservationSelection() {
12
+ return { subtrees: [HOLDER_ROOT] };
13
+ }
9
14
  function holderPath(taskId) {
10
15
  const digest = createHash("sha256").update(taskId).digest("hex");
11
16
  return `${HOLDER_PREFIX}${digest}${HOLDER_SUFFIX}`;
@@ -62,27 +67,6 @@ function decodeHolder(path, bytes) {
62
67
  corruption(`TaskHolder bytes are not canonical: ${path}`);
63
68
  return holder;
64
69
  }
65
- function holderEntries(repository, snapshot, scope) {
66
- const expanded = scope === "complete" ? snapshot : expandGitSnapshot(repository, snapshot);
67
- const holders = [];
68
- const seen = new Set();
69
- for (const [path, entry] of expanded.paths) {
70
- if (path === HOLDER_ROOT)
71
- corruption(`TaskHolder authority root is not a tree: ${path}`);
72
- if (!path.startsWith(HOLDER_PREFIX))
73
- continue;
74
- if (!path.endsWith(HOLDER_SUFFIX))
75
- corruption(`unexpected TaskHolder authority path: ${path}`);
76
- if (entry.type !== "blob")
77
- corruption(`TaskHolder path is not a blob: ${path}`);
78
- const holder = decodeHolder(path, readBlob(repository, entry.oid));
79
- if (seen.has(holder.taskId))
80
- corruption(`duplicate TaskHolder identity: ${holder.taskId}`);
81
- seen.add(holder.taskId);
82
- holders.push(holder);
83
- }
84
- return holders.sort((left, right) => Buffer.compare(Buffer.from(left.taskId), Buffer.from(right.taskId)));
85
- }
86
70
  function projectTaskHolders(holders) {
87
71
  const projection = new Map();
88
72
  for (const holder of holders) {
@@ -99,15 +83,130 @@ function update(holder) {
99
83
  export function claimTaskHolder(taskId, owner) {
100
84
  return update({ version: 1, taskId, contractId: owner, disposition: "held" });
101
85
  }
102
- export function releaseTaskHolder(repository, snapshot, owner) {
103
- const current = projectTaskHolders(holderEntries(repository, snapshot, "targeted")).get(owner) ?? null;
86
+ function detail(error) {
87
+ return error instanceof Error ? error.message : String(error);
88
+ }
89
+ export function finishTaskHolderAdmission(taskId, result, close) {
90
+ try {
91
+ close();
92
+ return { kind: "completed", result };
93
+ }
94
+ catch (error) {
95
+ if (result.kind !== "accepted")
96
+ throw error;
97
+ return { kind: "accepted-release-failed", result, taskId, diagnostic: detail(error) };
98
+ }
99
+ }
100
+ async function admitWithTaskHolderFence(repository, taskId, action) {
101
+ const held = await acquireTaskSettlementFence(repository, taskId);
102
+ let result;
103
+ try {
104
+ result = await action();
105
+ }
106
+ catch (error) {
107
+ try {
108
+ held.close();
109
+ }
110
+ catch { /* The operation failure remains decisive before admission. */ }
111
+ throw error;
112
+ }
113
+ return finishTaskHolderAdmission(taskId, result, () => held.close());
114
+ }
115
+ export function claimTaskHolderWithFence(repository, taskId, action) {
116
+ return admitWithTaskHolderFence(repository, taskId, action);
117
+ }
118
+ export async function releaseTaskHolder(channel, observation, owner) {
119
+ const current = (await readTaskHolderProjectionFromDecision(channel, observation)).get(owner) ?? null;
104
120
  return current === null || current.disposition !== "held"
105
121
  ? null
106
122
  : update({ ...current, disposition: "released" });
107
123
  }
108
- export function readTaskHolders(repository) {
109
- return holderEntries(repository, readGit(repository), "complete");
124
+ /** Publish the released holder against the same frozen observation that decided the release. */
125
+ export async function publishTaskHolderRelease(repository, channel, observation, owner) {
126
+ const release = await releaseTaskHolder(channel, observation, owner);
127
+ if (release === null)
128
+ return { kind: "not-held" };
129
+ const tree = await updateGitTree(repository, observation.admission.snapshot.tree, new Map([[release.path, { oid: await writeBlob(repository, release.bytes) }]]));
130
+ const commit = await writeCommit({
131
+ repository,
132
+ tree,
133
+ parent: observation.admission.snapshot.commit,
134
+ message: `release held Task completion: ${owner}`,
135
+ });
136
+ const publication = await updateRefsAtomically(repository, [{
137
+ ref: GIT_REF,
138
+ newOid: commit,
139
+ expectedOid: observation.admission.snapshot.commit,
140
+ }]);
141
+ if (publication.kind === "published")
142
+ return { kind: "released" };
143
+ return {
144
+ kind: "non-published",
145
+ diagnostic: publication.kind === "non-published" ? detail(publication.error) : "Task holder release publication state is unknown",
146
+ };
147
+ }
148
+ export async function readTaskHolderProjectionFromDecision(channel, observation) {
149
+ const read = {
150
+ snapshot: observation.admission.snapshot,
151
+ readBlobs: async (oids) => {
152
+ const objects = await channel.readObjects(oids);
153
+ const blobs = new Map();
154
+ for (const [oid, object] of objects) {
155
+ if (object.kind === "missing")
156
+ blobs.set(oid, object);
157
+ else {
158
+ if (object.type !== "blob")
159
+ corruption(`TaskHolder Git object is not a blob: ${oid}`);
160
+ blobs.set(oid, { kind: "present", bytes: object.bytes });
161
+ }
162
+ }
163
+ return blobs;
164
+ },
165
+ };
166
+ return projectTaskHolders(await readTaskHoldersAt(read));
167
+ }
168
+ export function observeTaskHolderProjection(repository, channel) {
169
+ return withGitTargetedReadObservation(repository, channel, taskHolderObservationSelection(), readTaskHolderProjectionAt);
170
+ }
171
+ async function heldTaskForContract(repository, channel, owner) {
172
+ const holder = (await observeTaskHolderProjection(repository, channel)).get(owner) ?? null;
173
+ return holder?.disposition === "held" ? holder.taskId : null;
174
+ }
175
+ export async function releaseTaskHolderWithFence(repository, channel, owner, action) {
176
+ const taskId = await heldTaskForContract(repository, channel, owner);
177
+ return taskId === null
178
+ ? { kind: "completed", result: await action() }
179
+ : admitWithTaskHolderFence(repository, taskId, action);
180
+ }
181
+ export async function readTaskHoldersAt(observation) {
182
+ const entries = [...observation.snapshot.paths]
183
+ .filter(([path, entry]) => path.startsWith(HOLDER_PREFIX) && entry.type === "blob");
184
+ const blobs = await observation.readBlobs(entries.map(([, entry]) => entry.oid));
185
+ const holders = [];
186
+ const seen = new Set();
187
+ for (const [path, entry] of observation.snapshot.paths) {
188
+ if (path === HOLDER_ROOT) {
189
+ if (entry.type !== "tree")
190
+ corruption(`TaskHolder authority root is not a tree: ${path}`);
191
+ continue;
192
+ }
193
+ if (!path.startsWith(HOLDER_PREFIX))
194
+ continue;
195
+ if (!path.endsWith(HOLDER_SUFFIX))
196
+ corruption(`unexpected TaskHolder authority path: ${path}`);
197
+ if (entry.type !== "blob")
198
+ corruption(`TaskHolder path is not a blob: ${path}`);
199
+ const result = blobs.get(entry.oid);
200
+ if (result?.kind !== "present")
201
+ corruption(`missing TaskHolder Git object: ${path}`);
202
+ const holder = decodeHolder(path, result.bytes);
203
+ if (seen.has(holder.taskId))
204
+ corruption(`duplicate TaskHolder identity: ${holder.taskId}`);
205
+ seen.add(holder.taskId);
206
+ holders.push(holder);
207
+ }
208
+ return holders.sort((left, right) => Buffer.compare(Buffer.from(left.taskId), Buffer.from(right.taskId)));
110
209
  }
111
- export function readTaskHolderProjection(repository) {
112
- return projectTaskHolders(readTaskHolders(repository));
210
+ export async function readTaskHolderProjectionAt(observation) {
211
+ return projectTaskHolders(await readTaskHoldersAt(observation));
113
212
  }
@@ -1,11 +1,12 @@
1
1
  import { type ContractId, type ContractState } from "../core/facts/types.js";
2
+ import type { GitDecodeChannel } from "../git/read-observation.js";
2
3
  import type { Effect } from "../git/reconcile.js";
3
4
  import type { GitRepository } from "../git/repository.js";
4
5
  import type { TaskId } from "../task/identity.js";
5
6
  export type SettlementAction = Readonly<{
6
7
  kind: "task";
7
8
  taskId: TaskId;
8
- action: "done" | "reopened";
9
+ action: "done";
9
10
  }> | Readonly<{
10
11
  kind: "namespace-context";
11
12
  path: string;
@@ -23,13 +24,20 @@ export type SettlementReport = Readonly<{
23
24
  actions: readonly SettlementAction[];
24
25
  lags: readonly SettlementLag[];
25
26
  }>;
27
+ export declare function deferredTaskHolderSettlement(input: Readonly<{
28
+ contractId: ContractId;
29
+ taskId: TaskId;
30
+ diagnostic: string;
31
+ }>): SettlementReport;
26
32
  export type SettlementInput = Readonly<{
27
33
  repository: GitRepository;
34
+ channel: GitDecodeChannel;
28
35
  state: ContractState | null;
29
36
  effects: readonly Effect[];
30
37
  }>;
31
38
  export type SettlementBatchInput = Readonly<{
32
39
  repository: GitRepository;
40
+ channel: GitDecodeChannel;
33
41
  contracts: readonly Readonly<Pick<SettlementInput, "state" | "effects">>[];
34
42
  }>;
35
43
  export declare function settle(input: SettlementInput): Promise<SettlementReport>;
@@ -1,7 +1,12 @@
1
1
  import { contractSegment } from "../core/facts/types.js";
2
+ import { observeContractsForAdmissionAt } from "../git/observe.js";
2
3
  import { repairNamespaceContext } from "../task/context.js";
3
4
  import { settleTask } from "../task/operations.js";
4
- import { readTaskHolderProjection } from "./holder.js";
5
+ import { publishTaskHolderRelease, readTaskHolderProjectionFromDecision, taskHolderObservationSelection, } from "./holder.js";
6
+ import { World } from "../world.js";
7
+ export function deferredTaskHolderSettlement(input) {
8
+ return { actions: [], lags: [{ kind: "settlement-failed", surface: "task-holder", ...input }] };
9
+ }
5
10
  function diagnostic(error) {
6
11
  return error instanceof Error ? error.message : String(error);
7
12
  }
@@ -10,46 +15,71 @@ function taskFailure(result) {
10
15
  ? `Task settlement requires retry: ${result.reason}`
11
16
  : `Task settlement refused: ${JSON.stringify(result.refusal)}`;
12
17
  }
13
- async function settleTasks(observation, world, state, actions, lags) {
14
- const terminal = state.terminal?.kind;
15
- if (terminal !== "claimed" && terminal !== "abandoned")
18
+ async function settleTasks(input) {
19
+ const { repository, channel, world, candidate, actions, lags } = input;
20
+ let observation;
21
+ try {
22
+ observation = await observeContractsForAdmissionAt(repository, channel, [candidate.id], taskHolderObservationSelection());
23
+ }
24
+ catch (error) {
25
+ lags.push({ kind: "settlement-failed", surface: "task-holder", contractId: candidate.id, diagnostic: diagnostic(error) });
16
26
  return;
17
- if (observation.kind === "failed") {
18
- lags.push({ kind: "settlement-failed", surface: "task-holder", contractId: state.id, diagnostic: observation.diagnostic });
27
+ }
28
+ const state = observation.journals.get(candidate.id)?.state ?? null;
29
+ if (state === null || state.terminal?.kind !== "claimed")
19
30
  return;
31
+ let holder;
32
+ try {
33
+ holder = (await readTaskHolderProjectionFromDecision(channel, observation)).get(candidate.id) ?? null;
20
34
  }
21
- const holder = observation.holders.get(state.id) ?? null;
22
- const expectedDisposition = terminal === "claimed" ? "held" : "released";
23
- if (holder === null || holder.disposition !== expectedDisposition)
35
+ catch (error) {
36
+ lags.push({ kind: "settlement-failed", surface: "task-holder", contractId: candidate.id, diagnostic: diagnostic(error) });
37
+ return;
38
+ }
39
+ if (holder === null || holder.disposition !== "held")
24
40
  return;
25
41
  const taskId = holder.taskId;
42
+ let result;
43
+ try {
44
+ result = await settleTask(world, taskId);
45
+ }
46
+ catch (error) {
47
+ lags.push({ kind: "settlement-failed", surface: "task", contractId: candidate.id, taskId, diagnostic: diagnostic(error) });
48
+ return;
49
+ }
50
+ if (result.kind === "changed")
51
+ actions.push({ kind: "task", taskId, action: "done" });
52
+ else if (result.kind !== "unchanged") {
53
+ lags.push({ kind: "settlement-failed", surface: "task", contractId: candidate.id, taskId, diagnostic: taskFailure(result) });
54
+ return;
55
+ }
26
56
  try {
27
- const result = await settleTask(world, taskId, terminal === "claimed" ? "done" : "open-from-done");
28
- if (result.kind === "changed")
29
- actions.push({ kind: "task", taskId, action: result.action });
30
- else if (result.kind !== "unchanged")
57
+ const publication = await publishTaskHolderRelease(repository, channel, observation, candidate.id);
58
+ if (publication.kind === "non-published") {
31
59
  lags.push({
32
60
  kind: "settlement-failed",
33
- surface: "task",
34
- contractId: state.id,
61
+ surface: "task-holder",
62
+ contractId: candidate.id,
35
63
  taskId,
36
- diagnostic: taskFailure(result),
64
+ diagnostic: `Task holder release requires retry: ${publication.diagnostic}`,
37
65
  });
66
+ }
38
67
  }
39
68
  catch (error) {
40
- lags.push({ kind: "settlement-failed", surface: "task", contractId: state.id, taskId, diagnostic: diagnostic(error) });
69
+ lags.push({ kind: "settlement-failed", surface: "task-holder", contractId: candidate.id, taskId, diagnostic: diagnostic(error) });
41
70
  }
42
71
  }
43
- function settleNamespace(state, effects, actions, lags) {
44
- if (state.terminal !== null || state.coordinates.workspace !== "worktree" || state.bound === null)
72
+ async function settleNamespace(state, effects, actions, lags) {
73
+ if (state.terminal !== null || state.coordinates.workspace !== "worktree")
45
74
  return;
46
75
  const worktrees = effects.filter((effect) => effect.kind === "worktree" && effect.action !== "removed");
47
76
  for (const effect of worktrees) {
48
77
  try {
78
+ const world = await World.at(effect.path);
49
79
  actions.push({
50
80
  kind: "namespace-context",
51
81
  path: effect.path,
52
- action: repairNamespaceContext(effect.path, [contractSegment(state.id)]),
82
+ action: await repairNamespaceContext(world, [contractSegment(state.id)]),
53
83
  });
54
84
  }
55
85
  catch (error) {
@@ -63,29 +93,36 @@ function settleNamespace(state, effects, actions, lags) {
63
93
  }
64
94
  }
65
95
  }
66
- function observeSettlement(repository) {
67
- try {
68
- return { kind: "present", holders: readTaskHolderProjection(repository) };
69
- }
70
- catch (error) {
71
- return { kind: "failed", diagnostic: diagnostic(error) };
72
- }
73
- }
74
- async function settleObserved(input, observation) {
96
+ async function settleObserved(input) {
75
97
  if (input.state === null)
76
98
  return { actions: [], lags: [] };
77
99
  const actions = [], lags = [];
78
- await settleTasks(observation, { root: input.repository.primaryWorktree }, input.state, actions, lags);
79
- settleNamespace(input.state, input.effects, actions, lags);
100
+ if (input.state.terminal?.kind === "claimed") {
101
+ try {
102
+ await settleTasks({
103
+ repository: input.repository,
104
+ channel: input.channel,
105
+ world: await World.at(input.repository.primaryWorktree),
106
+ candidate: input.state,
107
+ actions,
108
+ lags,
109
+ });
110
+ }
111
+ catch (error) {
112
+ lags.push({ kind: "settlement-failed", surface: "task", contractId: input.state.id, diagnostic: diagnostic(error) });
113
+ }
114
+ }
115
+ await settleNamespace(input.state, input.effects, actions, lags);
80
116
  return { actions, lags };
81
117
  }
118
+ function onPrimaryWorktree(repository) {
119
+ return { ...repository, effectiveCwd: repository.primaryWorktree };
120
+ }
82
121
  export async function settle(input) {
83
- return settleObserved(input, observeSettlement(input.repository));
122
+ const repository = onPrimaryWorktree(input.repository);
123
+ return await settleObserved({ ...input, repository });
84
124
  }
85
125
  export async function settleAll(input) {
86
- const observation = observeSettlement(input.repository);
87
- return Promise.all(input.contracts.map((contract) => settleObserved({
88
- repository: input.repository,
89
- ...contract,
90
- }, observation)));
126
+ const repository = onPrimaryWorktree(input.repository);
127
+ return await Promise.all(input.contracts.map((contract) => settleObserved({ repository, channel: input.channel, ...contract })));
91
128
  }
@@ -1,11 +1,11 @@
1
1
  import type { TaskDocument, TaskPriority, TaskState } from "./document.js";
2
2
  import { type TaskId } from "./identity.js";
3
- type TaskRef = Readonly<{
3
+ export type TaskRef = Readonly<{
4
4
  id: TaskId;
5
5
  title: string | null;
6
6
  state: TaskState | "missing";
7
7
  }>;
8
- type TaskDisposition = "ready" | "blocked" | "in_progress" | "on_hold" | "done" | "drop";
8
+ export type TaskDisposition = "ready" | "blocked" | "in_progress" | "on_hold" | "done" | "drop";
9
9
  export type TaskRow = Readonly<{
10
10
  id: TaskId;
11
11
  title: string;
@@ -34,8 +34,7 @@ export type TaskTreeNode = Readonly<{
34
34
  priority: TaskPriority | null;
35
35
  }>;
36
36
  cycle?: true;
37
- reference?: true;
38
- needs: readonly TaskTreeNode[];
37
+ children: readonly TaskTreeNode[];
39
38
  }>;
40
39
  export type TaskBoard = Readonly<{
41
40
  tasks: ReadonlyMap<TaskId, TaskDocument>;
@@ -55,11 +54,30 @@ export type TaskDoctorIssue = Readonly<{
55
54
  relation: "needs" | "parent" | "supersedes";
56
55
  tasks: readonly TaskId[];
57
56
  }>;
57
+ export declare function taskDisposition(board: TaskBoard, task: TaskDocument): TaskDisposition;
58
58
  export declare function projectDetailFacts(board: TaskBoard, id: TaskId): TaskDetailFacts | null;
59
59
  export declare function projectRows(board: TaskBoard, scope: readonly string[] | null, selection: "active" | "closed" | "all"): readonly TaskRow[];
60
60
  export declare function projectReady(board: TaskBoard, scope: readonly string[] | null): readonly TaskRow[];
61
61
  export declare function projectBlocked(board: TaskBoard, scope: readonly string[] | null): readonly BlockedTaskRow[];
62
+ export declare function projectStatusRows(board: TaskBoard, scope: readonly string[] | null): (Readonly<{
63
+ id: TaskId;
64
+ title: string;
65
+ state: TaskState;
66
+ priority: TaskPriority;
67
+ disposition: TaskDisposition;
68
+ }> | {
69
+ blockers: readonly Readonly<{
70
+ id: TaskId;
71
+ title: string | null;
72
+ state: TaskState | "missing";
73
+ }>[];
74
+ id: TaskId;
75
+ title: string;
76
+ state: TaskState;
77
+ priority: TaskPriority;
78
+ disposition: TaskDisposition;
79
+ })[];
62
80
  export declare function relationProblem(board: TaskBoard, before: TaskDocument | null, after: TaskDocument): string | null;
63
81
  export declare function diagnoseBoard(board: TaskBoard): readonly TaskDoctorIssue[];
64
- export declare function buildTree(board: TaskBoard, root: TaskId, full: boolean): TaskTreeNode | null;
82
+ export declare function buildTree(board: TaskBoard, root: TaskId): TaskTreeNode | null;
65
83
  export {};
@@ -19,7 +19,7 @@ function related(board, task) {
19
19
  ids.add(candidate.id);
20
20
  return sorted([...ids].map((id) => taskRef(board, id)));
21
21
  }
22
- function disposition(board, task) {
22
+ export function taskDisposition(board, task) {
23
23
  if (task.state !== "open")
24
24
  return task.state;
25
25
  return task.needs.every((id) => { const target = board.tasks.get(id); return target !== undefined && terminal(target.state); }) ? "ready" : "blocked";
@@ -30,7 +30,10 @@ export function projectDetailFacts(board, id) {
30
30
  return null;
31
31
  const needs = task.needs.map((need) => ({ ...taskRef(board, need), released: board.tasks.has(need) && terminal(board.tasks.get(need).state) }));
32
32
  return {
33
- task, needs, blockers: needs.filter((need) => !need.released), blocks: reverse(board, id, "needs"),
33
+ task,
34
+ needs,
35
+ blockers: needs.filter((need) => !need.released).map(({ released: _released, ...ref }) => ref),
36
+ blocks: reverse(board, id, "needs"),
34
37
  parent: task.parent === null ? null : taskRef(board, task.parent), children: children(board, id),
35
38
  supersedes: task.supersedes.map((target) => taskRef(board, target)), supersededBy: reverse(board, id, "supersedes"),
36
39
  related: related(board, task),
@@ -41,7 +44,7 @@ export function projectRows(board, scope, selection) {
41
44
  return [...board.tasks.values()].filter((task) => inScope(task, scope)).filter((task) => selection === "all"
42
45
  || (selection === "closed") === terminal(task.state)).map((task) => ({
43
46
  id: task.id, title: task.title, state: task.state, priority: task.priority,
44
- disposition: disposition(board, task),
47
+ disposition: taskDisposition(board, task),
45
48
  })).sort((a, b) => a.priority - b.priority || Buffer.compare(Buffer.from(a.id), Buffer.from(b.id)));
46
49
  }
47
50
  export function projectReady(board, scope) {
@@ -55,6 +58,13 @@ export function projectBlocked(board, scope) {
55
58
  return blockers.length === 0 ? [] : [{ ...row, blockers }];
56
59
  });
57
60
  }
61
+ export function projectStatusRows(board, scope) {
62
+ const blockers = new Map(projectBlocked(board, scope).map((row) => [row.id, row.blockers]));
63
+ return projectRows(board, scope, "all").map((row) => {
64
+ const unresolved = blockers.get(row.id);
65
+ return unresolved === undefined ? row : { ...row, blockers: unresolved };
66
+ });
67
+ }
58
68
  function relationIds(task, relation) {
59
69
  return relation === "parent" ? task.parent === null ? [] : [task.parent] : task[relation];
60
70
  }
@@ -137,19 +147,16 @@ export function diagnoseBoard(board) {
137
147
  }
138
148
  return issues;
139
149
  }
140
- export function buildTree(board, root, full) {
150
+ export function buildTree(board, root) {
141
151
  if (!board.tasks.has(root))
142
152
  return null;
143
- const expanded = new Set();
144
153
  const walk = (id, path) => {
145
154
  const ref = taskRef(board, id);
146
155
  const task = board.tasks.get(id);
156
+ const node = { task: { ...ref, priority: task?.priority ?? null } };
147
157
  if (path.includes(id))
148
- return { task: { ...ref, priority: task?.priority ?? null }, cycle: true, needs: [] };
149
- if (!full && expanded.has(id))
150
- return { task: { ...ref, priority: task?.priority ?? null }, reference: true, needs: [] };
151
- expanded.add(id);
152
- return { task: { ...ref, priority: task?.priority ?? null }, needs: task === undefined ? [] : task.needs.map((need) => walk(need, [...path, id])) };
158
+ return { ...node, cycle: true, children: [] };
159
+ return { ...node, children: children(board, id).map((child) => walk(child.id, [...path, id])) };
153
160
  };
154
161
  return walk(root, []);
155
162
  }
@@ -1,5 +1,5 @@
1
1
  import { type TaskId } from "./identity.js";
2
- import { type TaskWorld } from "./store.js";
2
+ import type { WorldRoot } from "../world.js";
3
3
  import type { TaskRefusal, TaskRetry } from "./operations.js";
4
4
  type TaskDocumentChange = Readonly<{
5
5
  taskId: TaskId;
@@ -21,5 +21,5 @@ export type TaskCompositionResult = Readonly<{
21
21
  }>;
22
22
  draft: string;
23
23
  }>;
24
- export declare function composeTasks(world: TaskWorld, markdown: string, signal?: AbortSignal): Promise<TaskCompositionResult>;
24
+ export declare function composeTasks(world: WorldRoot, markdown: string, signal?: AbortSignal, actor?: string): Promise<TaskCompositionResult>;
25
25
  export {};
@@ -1,3 +1,4 @@
1
+ import { resolve } from "node:path";
1
2
  import { documentDiff } from "../markdown/diff.js";
2
3
  import { readNamespaceContext } from "./context.js";
3
4
  import { relationProblem } from "./board.js";
@@ -119,7 +120,14 @@ function applyAssignments(document, assignments, body) {
119
120
  }
120
121
  function currentTimestamp() { return new Date().toISOString(); }
121
122
  function advancedTimestamp(previous, current) { return current > previous ? current : new Date(Date.parse(previous) + 1).toISOString(); }
122
- function plan(sketch, board, defaultNamespace, at) {
123
+ function createdTask(id, title, at, actor) {
124
+ return {
125
+ id, title, state: "open", priority: 2, needs: [], parent: null, supersedes: [], relates: [], note: "",
126
+ ...(actor === undefined ? {} : { createdBy: actor }),
127
+ createdAt: at, updatedAt: at, body: "",
128
+ };
129
+ }
130
+ function plan(sketch, board, defaultNamespace, at, actor) {
123
131
  try {
124
132
  const namespace = sketch.namespace ?? defaultNamespace;
125
133
  const occupied = new Set([...board.tasks.values()].flatMap((task) => {
@@ -132,7 +140,7 @@ function plan(sketch, board, defaultNamespace, at) {
132
140
  const localId = allocateLocalId(deriveLocalStem(node.title), occupied);
133
141
  occupied.add(localId);
134
142
  const coordinate = { namespace, localId };
135
- allocations.set(node.index, { id: formatTaskId(coordinate), title: node.title, state: "open", priority: 2, needs: [], parent: null, supersedes: [], relates: [], note: "", createdAt: at, updatedAt: at, body: "" });
143
+ allocations.set(node.index, createdTask(formatTaskId(coordinate), node.title, at, actor));
136
144
  }
137
145
  const all = new Map(board.tasks);
138
146
  for (const allocated of allocations.values())
@@ -190,24 +198,24 @@ function draft(namespace, remaining) {
190
198
  }
191
199
  return `${lines.join("\n")}\n`;
192
200
  }
193
- export async function composeTasks(world, markdown, signal) {
201
+ export async function composeTasks(world, markdown, signal, actor) {
194
202
  const sketch = parseSketch(markdown);
195
203
  if ("kind" in sketch)
196
204
  return { kind: "refused", refusal: sketch };
197
205
  const at = currentTimestamp();
198
- const context = sketch.namespace ?? readNamespaceContext(world.root);
206
+ const context = sketch.namespace ?? await readNamespaceContext(world);
199
207
  if (context === "malformed")
200
- return { kind: "refused", refusal: { kind: "invalid-namespace-context", path: `${world.root}/.keiyaku/namespace/current` } };
208
+ return { kind: "refused", refusal: { kind: "invalid-namespace-context", path: resolve(world, ".keiyaku", "namespace", "current") } };
201
209
  const namespace = context === "absent" ? [] : context;
202
- const initial = readBoard(world);
203
- const planned = plan(sketch, initial.board, namespace, at);
210
+ const initial = await readBoard(world);
211
+ const planned = plan(sketch, initial.board, namespace, at, actor);
204
212
  if ("kind" in planned)
205
213
  return { kind: "refused", refusal: planned };
206
214
  const ordered = [...planned].sort((a, b) => Buffer.compare(Buffer.from(a.after.id), Buffer.from(b.after.id)));
207
215
  const allocation = sketch.nodes.some((node) => node.kind === "new");
208
216
  const result = await withTaskLocks({ world, allocation, ids: ordered.map((item) => item.after.id), ...(signal === undefined ? {} : { signal }) }, async () => {
209
- const fresh = readBoard(world);
210
- const replanned = plan(sketch, fresh.board, namespace, at);
217
+ const fresh = await readBoard(world);
218
+ const replanned = plan(sketch, fresh.board, namespace, at, actor);
211
219
  if ("kind" in replanned)
212
220
  return { kind: "refused", refusal: replanned };
213
221
  const queue = [...replanned].sort((a, b) => Buffer.compare(Buffer.from(a.after.id), Buffer.from(b.after.id))), changes = [];
@@ -219,7 +227,7 @@ export async function composeTasks(world, markdown, signal) {
219
227
  const before = beforeBytes === null ? "" : Buffer.from(beforeBytes).toString("utf8"), after = Buffer.from(afterBytes).toString("utf8");
220
228
  if (before === after)
221
229
  continue;
222
- if (replaceAuthority({ path, expected: beforeBytes, next: afterBytes }) !== "replaced")
230
+ if (await replaceAuthority({ path, expected: beforeBytes, next: afterBytes }) !== "replaced")
223
231
  return {
224
232
  kind: "incomplete", documentChanges: changes, stopped: { kind: "retry", reason: "concurrent-modification" }, draft: draft(namespace, queue.slice(index)),
225
233
  };
@@ -1,4 +1,4 @@
1
1
  export type NamespaceContextRead = readonly string[] | "absent" | "malformed";
2
- export declare function readNamespaceContext(root: string): NamespaceContextRead;
3
- export declare function installNamespaceContext(root: string, segments: readonly string[]): void;
4
- export declare function repairNamespaceContext(root: string, segments: readonly string[]): "kept" | "installed";
2
+ export declare function readNamespaceContext(root: string): Promise<NamespaceContextRead>;
3
+ export declare function installNamespaceContext(root: string, segments: readonly string[]): Promise<void>;
4
+ export declare function repairNamespaceContext(root: string, segments: readonly string[]): Promise<"kept" | "installed">;