@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,27 +1,15 @@
1
1
  import type { DatabaseSync } from "node:sqlite";
2
2
  import type { AkuId } from "../identity.js";
3
- import type { BodyEnd, BodyFact, CallFact, Collar, KillFact, RequestFact, RequestInput, ResumeCoordinate, SessionFact, Soul, StopFact, TurnFact, TurnStartFact, TurnEndFact } from "./facts.js";
4
- export type SoulRow = Readonly<{
5
- id: string;
6
- archetype: string;
7
- description: string | null;
8
- provider_json: string;
9
- options_json: string;
10
- cwd: string;
11
- origin_json: string;
12
- confinement_json: string;
13
- created_at: string;
14
- }>;
3
+ import type { BodyEnd, BodyFact, CallFact, KillFact, PauseFact, RequestFact, RequestInput, ResumeCoordinate, SessionFact, StopFact, TurnFact, TurnStartFact, TurnEndFact } from "./facts.js";
15
4
  export type SealRow = Readonly<{
16
5
  evidence: string;
17
6
  at: string;
18
7
  }>;
19
8
  export type BodyRow = Readonly<{
20
9
  sequence: number;
21
- pid: number;
22
- process_group: number;
23
- spawned_at: string;
24
10
  leash_taken_at: string;
11
+ hung_diagnostic: string | null;
12
+ hung_at: string | null;
25
13
  end: BodyEnd | null;
26
14
  ended_at: string | null;
27
15
  }>;
@@ -84,18 +72,6 @@ export type KillRow = Readonly<{
84
72
  evidence: "killed";
85
73
  at: string;
86
74
  }>;
87
- export declare function encodeSoulRow(soul: Soul): readonly [
88
- AkuId,
89
- string,
90
- string | null,
91
- string,
92
- string,
93
- string,
94
- string,
95
- string,
96
- string
97
- ];
98
- export declare function decodeSoulRow(row: SoulRow): Soul;
99
75
  export declare function encodeSessionRow(session: Omit<SessionFact, "sequence">): readonly [string, string, string, string, string];
100
76
  export declare function encodeResumeCoordinate(coordinate: ResumeCoordinate): string;
101
77
  export declare function decodeSessionRow(row: SessionRow): SessionFact;
@@ -105,9 +81,7 @@ export declare function decodeCallRow(row: CallRow): CallFact;
105
81
  export declare function decodeRequestRow(row: RequestRow): RequestFact;
106
82
  export declare function encodeActivityEvent(event: unknown): string;
107
83
  export declare function decodeActivityRow(row: ActivityRow): ActivityFact;
108
- export declare function soulFact(database: DatabaseSync): Soul | null;
109
84
  export declare function sealExists(database: DatabaseSync): boolean;
110
- export declare function insertSoulFact(database: DatabaseSync, soul: Soul): void;
111
85
  export declare function insertSealFact(database: DatabaseSync, input: Readonly<{
112
86
  evidence: string;
113
87
  at: string;
@@ -119,7 +93,6 @@ export declare function sealFact(database: DatabaseSync): Readonly<{
119
93
  at: string;
120
94
  }> | null;
121
95
  export declare function insertBodyFact(database: DatabaseSync, input: Readonly<{
122
- collar: Collar;
123
96
  leashTakenAt: string;
124
97
  }>): number;
125
98
  export declare function insertSessionFact(database: DatabaseSync, input: Omit<SessionFact, "sequence">): number;
@@ -140,13 +113,18 @@ export declare function updateRequestVoided(database: DatabaseSync, id: string,
140
113
  export declare function insertStopControl(database: DatabaseSync, bodySequence: number, at: string): void;
141
114
  export declare function insertPauseControl(database: DatabaseSync, at: string): void;
142
115
  export declare function stopFact(database: DatabaseSync): StopFact | null;
143
- export declare function pauseExists(database: DatabaseSync): boolean;
116
+ export declare function pauseFact(database: DatabaseSync): PauseFact | null;
144
117
  export declare function insertKillFact(database: DatabaseSync, bodySequence: number, at: string): void;
145
118
  export declare function endBodyFact(database: DatabaseSync, input: Readonly<{
146
119
  sequence: number;
147
120
  end: BodyEnd;
148
121
  at: string;
149
122
  }>): void;
123
+ export declare function markBodyHung(database: DatabaseSync, input: Readonly<{
124
+ sequence: number;
125
+ diagnostic: string;
126
+ at: string;
127
+ }>): void;
150
128
  export declare function insertTurnStartFact(database: DatabaseSync, input: Readonly<{
151
129
  bodySequence: number;
152
130
  startedAt: string;
@@ -13,32 +13,6 @@ function resumeCoordinate(value) {
13
13
  throw new Error("Akuma authority contains an invalid resume coordinate");
14
14
  return coordinate;
15
15
  }
16
- export function encodeSoulRow(soul) {
17
- return [
18
- soul.id,
19
- soul.archetype,
20
- soul.description ?? null,
21
- json(soul.provider),
22
- json(soul.options),
23
- soul.cwd,
24
- json(soul.origin),
25
- json(soul.confinement),
26
- soul.createdAt,
27
- ];
28
- }
29
- export function decodeSoulRow(row) {
30
- return {
31
- id: row.id,
32
- archetype: row.archetype,
33
- ...(row.description === null ? {} : { description: row.description }),
34
- provider: parsed(row.provider_json),
35
- options: parsed(row.options_json),
36
- cwd: row.cwd,
37
- origin: parsed(row.origin_json),
38
- confinement: parsed(row.confinement_json),
39
- createdAt: row.created_at,
40
- };
41
- }
42
16
  export function encodeSessionRow(session) {
43
17
  return [session.provider, encodeResumeCoordinate(session.coordinate), session.cwd, json(session.options), session.admittedAt];
44
18
  }
@@ -58,8 +32,8 @@ export function decodeSessionRow(row) {
58
32
  export function decodeBodyRow(row) {
59
33
  return {
60
34
  sequence: row.sequence,
61
- collar: { pid: row.pid, processGroup: row.process_group, spawnedAt: row.spawned_at },
62
35
  leashTakenAt: row.leash_taken_at,
36
+ ...(row.hung_diagnostic === null ? {} : { hung: { diagnostic: row.hung_diagnostic, at: row.hung_at } }),
63
37
  ...(row.end === null ? {} : { end: row.end }),
64
38
  ...(row.ended_at === null ? {} : { endedAt: row.ended_at }),
65
39
  };
@@ -80,7 +54,7 @@ export function decodeTurnRow(row) {
80
54
  outcome: row.outcome === "answered"
81
55
  ? {
82
56
  kind: "answered",
83
- historyId: row.history_id,
57
+ ...(row.history_id === null ? {} : { historyId: row.history_id }),
84
58
  session: resumeCoordinate(parsed(row.session_json)),
85
59
  answer: row.answer,
86
60
  }
@@ -123,18 +97,9 @@ export function decodeActivityRow(row) {
123
97
  at: row.at,
124
98
  };
125
99
  }
126
- export function soulFact(database) {
127
- const row = database.prepare(`SELECT id, archetype, description, provider_json, options_json, cwd,
128
- origin_json, confinement_json, created_at FROM soul WHERE singleton = 1`).get();
129
- return row === undefined ? null : decodeSoulRow(row);
130
- }
131
100
  export function sealExists(database) {
132
101
  return database.prepare("SELECT singleton FROM seal WHERE singleton = 1").get() !== undefined;
133
102
  }
134
- export function insertSoulFact(database, soul) {
135
- database.prepare(`INSERT INTO soul(singleton, id, archetype, description, provider_json, options_json, cwd, origin_json, confinement_json, created_at)
136
- VALUES (1, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(...encodeSoulRow(soul));
137
- }
138
103
  export function insertSealFact(database, input) {
139
104
  database.prepare("INSERT OR IGNORE INTO seal(singleton, evidence, at) VALUES (1, ?, ?)")
140
105
  .run(input.evidence, input.at);
@@ -150,8 +115,7 @@ export function sealFact(database) {
150
115
  return row === undefined ? null : { evidence: row.evidence, at: row.at };
151
116
  }
152
117
  export function insertBodyFact(database, input) {
153
- const result = database.prepare(`INSERT INTO bodies(pid, process_group, spawned_at, leash_taken_at)
154
- VALUES (?, ?, ?, ?)`).run(input.collar.pid, input.collar.processGroup, input.collar.spawnedAt, input.leashTakenAt);
118
+ const result = database.prepare("INSERT INTO bodies(leash_taken_at) VALUES (?)").run(input.leashTakenAt);
155
119
  return Number(result.lastInsertRowid);
156
120
  }
157
121
  export function insertSessionFact(database, input) {
@@ -201,7 +165,11 @@ export function insertStopControl(database, bodySequence, at) {
201
165
  .run(json({ bodySequence }), at);
202
166
  }
203
167
  export function insertPauseControl(database, at) {
204
- database.prepare("INSERT OR IGNORE INTO control(kind, value_json, at) VALUES ('pause', '{}', ?)").run(at);
168
+ const body = latestBodyFact(database);
169
+ if (body === null)
170
+ throw new Error("Akuma has no Body to interrupt");
171
+ database.prepare("INSERT OR IGNORE INTO control(kind, value_json, at) VALUES ('pause', ?, ?)")
172
+ .run(json({ bodySequence: body.sequence }), at);
205
173
  }
206
174
  export function stopFact(database) {
207
175
  const row = database.prepare("SELECT value_json, at FROM control WHERE kind = 'stop'").get();
@@ -213,8 +181,15 @@ export function stopFact(database) {
213
181
  }
214
182
  return { bodySequence: value.bodySequence, requestedAt: row.at };
215
183
  }
216
- export function pauseExists(database) {
217
- return database.prepare("SELECT kind FROM control WHERE kind = 'pause'").get() !== undefined;
184
+ export function pauseFact(database) {
185
+ const row = database.prepare("SELECT value_json, at FROM control WHERE kind = 'pause'").get();
186
+ if (row === undefined)
187
+ return null;
188
+ const value = parsed(row.value_json);
189
+ if (!Number.isSafeInteger(value.bodySequence) || value.bodySequence <= 0) {
190
+ throw new Error("Akuma pause control has an invalid Body sequence");
191
+ }
192
+ return { bodySequence: value.bodySequence, requestedAt: row.at };
218
193
  }
219
194
  export function insertKillFact(database, bodySequence, at) {
220
195
  database.prepare("INSERT OR IGNORE INTO kills(body_sequence, evidence, at) VALUES (?, 'killed', ?)")
@@ -224,6 +199,13 @@ export function endBodyFact(database, input) {
224
199
  database.prepare("UPDATE bodies SET end = ?, ended_at = ? WHERE sequence = ? AND end IS NULL")
225
200
  .run(input.end, input.at, input.sequence);
226
201
  }
202
+ export function markBodyHung(database, input) {
203
+ const result = database.prepare(`UPDATE bodies SET hung_diagnostic = ?, hung_at = ?
204
+ WHERE sequence = ? AND end IS NULL AND hung_diagnostic IS NULL`)
205
+ .run(input.diagnostic, input.at, input.sequence);
206
+ if (result.changes !== 1)
207
+ throw new Error(`Akuma Body ${input.sequence} cannot record hung custody`);
208
+ }
227
209
  export function insertTurnStartFact(database, input) {
228
210
  const sequence = Number(database.prepare("INSERT INTO timeline(kind) VALUES ('turn-start')").run().lastInsertRowid);
229
211
  database.prepare("INSERT INTO turns(sequence, body_sequence, started_at) VALUES (?, ?, ?)")
@@ -239,7 +221,7 @@ export function insertTurnEndFact(database, input) {
239
221
  const sequence = Number(database.prepare("INSERT INTO timeline(kind) VALUES ('turn-end')").run().lastInsertRowid);
240
222
  const result = input.outcome.kind === "answered"
241
223
  ? database.prepare(`UPDATE turns SET end_sequence = ?, outcome = 'answered', history_id = ?,
242
- session_json = ?, answer = ?, completed_at = ? WHERE sequence = ? AND end_sequence IS NULL`).run(sequence, input.outcome.historyId, encodeResumeCoordinate(input.outcome.session), input.outcome.answer, input.completedAt, input.turnSequence)
224
+ session_json = ?, answer = ?, completed_at = ? WHERE sequence = ? AND end_sequence IS NULL`).run(sequence, input.outcome.historyId ?? null, encodeResumeCoordinate(input.outcome.session), input.outcome.answer, input.completedAt, input.turnSequence)
243
225
  : database.prepare(`UPDATE turns SET end_sequence = ?, outcome = 'failed', diagnostic = ?, completed_at = ?
244
226
  WHERE sequence = ? AND end_sequence IS NULL`).run(sequence, input.outcome.diagnostic, input.completedAt, input.turnSequence);
245
227
  if (result.changes !== 1)
@@ -251,7 +233,7 @@ export function finishBodyFact(database, input) {
251
233
  .run(input.at, input.sequence);
252
234
  }
253
235
  export function latestBodyFact(database) {
254
- const row = database.prepare(`SELECT sequence, pid, process_group, spawned_at, leash_taken_at, end, ended_at
236
+ const row = database.prepare(`SELECT sequence, leash_taken_at, hung_diagnostic, hung_at, end, ended_at
255
237
  FROM bodies ORDER BY sequence DESC LIMIT 1`).get();
256
238
  return row === undefined ? null : decodeBodyRow(row);
257
239
  }
@@ -1,5 +1,5 @@
1
1
  import type { DatabaseSync } from "node:sqlite";
2
2
  export declare function assertHeartSchemaVersion(database: DatabaseSync): void;
3
3
  export declare function assertLeashSchemaVersion(database: DatabaseSync): void;
4
- export declare const HEART_SCHEMA = "\n CREATE TABLE IF NOT EXISTS akuma_schema (\n singleton INTEGER PRIMARY KEY CHECK (singleton = 1),\n version INTEGER NOT NULL CHECK (version = 10)\n ) STRICT;\n INSERT OR IGNORE INTO akuma_schema(singleton, version) VALUES (1, 10);\n CREATE TABLE IF NOT EXISTS soul (\n singleton INTEGER PRIMARY KEY CHECK (singleton = 1),\n id TEXT NOT NULL UNIQUE,\n archetype TEXT NOT NULL,\n description TEXT,\n provider_json TEXT NOT NULL CHECK (json_valid(provider_json)),\n options_json TEXT NOT NULL CHECK (json_valid(options_json)),\n cwd TEXT NOT NULL,\n origin_json TEXT NOT NULL CHECK (json_valid(origin_json)),\n confinement_json TEXT NOT NULL CHECK (json_valid(confinement_json)),\n created_at TEXT NOT NULL\n ) STRICT;\n CREATE TABLE IF NOT EXISTS bodies (\n sequence INTEGER PRIMARY KEY AUTOINCREMENT,\n pid INTEGER NOT NULL CHECK (pid > 0),\n process_group INTEGER NOT NULL CHECK (process_group > 0),\n spawned_at TEXT NOT NULL,\n leash_taken_at TEXT NOT NULL,\n end TEXT CHECK (end IN ('exited', 'broke-off', 'put-down')),\n ended_at TEXT\n ) STRICT;\n CREATE TABLE IF NOT EXISTS sessions (\n sequence INTEGER PRIMARY KEY AUTOINCREMENT,\n provider TEXT NOT NULL,\n coordinate_json TEXT NOT NULL CHECK (json_valid(coordinate_json)),\n cwd TEXT NOT NULL,\n options_json TEXT NOT NULL CHECK (json_valid(options_json)),\n admitted_at TEXT NOT NULL\n ) STRICT;\n CREATE TABLE IF NOT EXISTS turns (\n sequence INTEGER PRIMARY KEY REFERENCES timeline(sequence) ON DELETE CASCADE,\n body_sequence INTEGER NOT NULL REFERENCES bodies(sequence),\n started_at TEXT NOT NULL,\n end_sequence INTEGER UNIQUE REFERENCES timeline(sequence) ON DELETE SET NULL,\n outcome TEXT CHECK (outcome IN ('answered', 'failed')),\n history_id TEXT UNIQUE,\n session_json TEXT CHECK (session_json IS NULL OR json_valid(session_json)),\n answer TEXT,\n diagnostic TEXT,\n completed_at TEXT,\n CHECK (\n (outcome = 'answered' AND history_id IS NOT NULL AND session_json IS NOT NULL AND answer IS NOT NULL AND diagnostic IS NULL)\n OR (outcome = 'failed' AND history_id IS NULL AND session_json IS NULL AND answer IS NULL AND diagnostic IS NOT NULL)\n OR (outcome IS NULL AND end_sequence IS NULL AND history_id IS NULL AND session_json IS NULL AND answer IS NULL AND diagnostic IS NULL AND completed_at IS NULL)\n )\n ) STRICT;\n CREATE TABLE IF NOT EXISTS timeline (\n sequence INTEGER PRIMARY KEY AUTOINCREMENT,\n kind TEXT NOT NULL CHECK (kind IN ('turn-start', 'call', 'activity', 'tell', 'turn-end'))\n ) STRICT;\n CREATE TABLE IF NOT EXISTS calls (\n sequence INTEGER PRIMARY KEY REFERENCES timeline(sequence) ON DELETE CASCADE,\n turn_sequence INTEGER NOT NULL REFERENCES turns(sequence) ON DELETE CASCADE,\n body TEXT NOT NULL,\n at TEXT NOT NULL\n ) STRICT;\n CREATE TABLE IF NOT EXISTS activity (\n sequence INTEGER PRIMARY KEY REFERENCES timeline(sequence) ON DELETE CASCADE,\n turn_sequence INTEGER NOT NULL REFERENCES turns(sequence) ON DELETE CASCADE,\n event_json TEXT NOT NULL CHECK (json_valid(event_json)),\n at TEXT NOT NULL\n ) STRICT;\n CREATE TABLE IF NOT EXISTS tells (\n id TEXT PRIMARY KEY,\n sequence INTEGER NOT NULL UNIQUE REFERENCES timeline(sequence) ON DELETE CASCADE,\n body TEXT NOT NULL,\n recorded_at TEXT NOT NULL\n ) STRICT;\n CREATE TABLE IF NOT EXISTS tell_deliveries (\n sequence INTEGER PRIMARY KEY AUTOINCREMENT,\n tell_id TEXT NOT NULL REFERENCES tells(id) ON DELETE CASCADE,\n route TEXT NOT NULL CHECK (route IN ('launch', 'live')),\n turn_sequence INTEGER NOT NULL REFERENCES turns(sequence) ON DELETE CASCADE,\n fence TEXT NOT NULL,\n receipt TEXT CHECK (receipt IN ('unavailable', 'required')),\n delivered_at TEXT NOT NULL,\n CHECK ((route = 'launch' AND receipt IS NULL) OR (route = 'live' AND receipt IS NOT NULL)),\n UNIQUE (tell_id, turn_sequence, fence)\n ) STRICT;\n CREATE TABLE IF NOT EXISTS tell_receipts (\n sequence INTEGER PRIMARY KEY AUTOINCREMENT,\n evidence TEXT NOT NULL CHECK (evidence IN ('exact', 'fence')),\n tell_id TEXT REFERENCES tells(id) ON DELETE CASCADE,\n turn_sequence INTEGER REFERENCES turns(sequence) ON DELETE CASCADE,\n fence TEXT,\n kind TEXT NOT NULL,\n received_at TEXT NOT NULL,\n CHECK (\n (evidence = 'exact' AND tell_id IS NOT NULL AND turn_sequence IS NULL AND fence IS NULL)\n OR (evidence = 'fence' AND tell_id IS NULL AND turn_sequence IS NOT NULL AND fence IS NOT NULL)\n )\n ) STRICT;\n CREATE UNIQUE INDEX IF NOT EXISTS tell_receipts_exact\n ON tell_receipts(tell_id, kind) WHERE evidence = 'exact';\n CREATE UNIQUE INDEX IF NOT EXISTS tell_receipts_fence\n ON tell_receipts(turn_sequence, fence, kind) WHERE evidence = 'fence';\n CREATE TABLE IF NOT EXISTS requests (\n sequence INTEGER PRIMARY KEY AUTOINCREMENT,\n id TEXT NOT NULL UNIQUE,\n archetype TEXT NOT NULL,\n body TEXT NOT NULL,\n cwd TEXT,\n world TEXT NOT NULL,\n recipe_json TEXT NOT NULL CHECK (json_valid(recipe_json)),\n admitted_at TEXT NOT NULL,\n state TEXT NOT NULL CHECK (state IN ('admitted', 'reserved', 'served', 'refused', 'voided')),\n child TEXT,\n diagnostic TEXT,\n evidence TEXT,\n CHECK (\n (state = 'admitted' AND child IS NULL AND diagnostic IS NULL AND evidence IS NULL)\n OR (state IN ('reserved', 'served') AND child IS NOT NULL AND diagnostic IS NULL AND evidence IS NULL)\n OR (state = 'refused' AND child IS NULL AND diagnostic IS NOT NULL AND evidence IS NULL)\n OR (state = 'voided' AND child IS NULL AND diagnostic IS NULL AND evidence IS NOT NULL)\n )\n ) STRICT;\n CREATE TABLE IF NOT EXISTS control (\n kind TEXT PRIMARY KEY CHECK (kind IN ('stop', 'pause')),\n value_json TEXT NOT NULL CHECK (json_valid(value_json)),\n at TEXT NOT NULL\n ) STRICT;\n CREATE TABLE IF NOT EXISTS kills (\n sequence INTEGER PRIMARY KEY AUTOINCREMENT,\n body_sequence INTEGER NOT NULL UNIQUE REFERENCES bodies(sequence),\n evidence TEXT NOT NULL CHECK (evidence = 'killed'),\n at TEXT NOT NULL\n ) STRICT;\n";
4
+ export declare const HEART_SCHEMA = "\n CREATE TABLE IF NOT EXISTS akuma_schema (\n singleton INTEGER PRIMARY KEY CHECK (singleton = 1),\n version INTEGER NOT NULL CHECK (version = 14)\n ) STRICT;\n INSERT OR IGNORE INTO akuma_schema(singleton, version) VALUES (1, 14);\n CREATE TABLE IF NOT EXISTS soul (\n singleton INTEGER PRIMARY KEY CHECK (singleton = 1),\n soul_json TEXT NOT NULL CHECK (json_valid(soul_json))\n ) STRICT;\n CREATE TABLE IF NOT EXISTS bodies (\n sequence INTEGER PRIMARY KEY AUTOINCREMENT,\n leash_taken_at TEXT NOT NULL,\n hung_diagnostic TEXT,\n hung_at TEXT,\n end TEXT CHECK (end IN ('exited', 'broke-off', 'put-down')),\n ended_at TEXT,\n CHECK ((hung_diagnostic IS NULL AND hung_at IS NULL) OR (hung_diagnostic IS NOT NULL AND hung_at IS NOT NULL AND end IS NULL))\n ) STRICT;\n CREATE TABLE IF NOT EXISTS sessions (\n sequence INTEGER PRIMARY KEY AUTOINCREMENT,\n provider TEXT NOT NULL,\n coordinate_json TEXT NOT NULL CHECK (json_valid(coordinate_json)),\n cwd TEXT NOT NULL,\n options_json TEXT NOT NULL CHECK (json_valid(options_json)),\n admitted_at TEXT NOT NULL\n ) STRICT;\n CREATE TABLE IF NOT EXISTS turns (\n sequence INTEGER PRIMARY KEY REFERENCES timeline(sequence) ON DELETE CASCADE,\n body_sequence INTEGER NOT NULL REFERENCES bodies(sequence),\n started_at TEXT NOT NULL,\n end_sequence INTEGER UNIQUE REFERENCES timeline(sequence) ON DELETE SET NULL,\n outcome TEXT CHECK (outcome IN ('answered', 'failed')),\n history_id TEXT UNIQUE,\n session_json TEXT CHECK (session_json IS NULL OR json_valid(session_json)),\n answer TEXT,\n diagnostic TEXT,\n completed_at TEXT,\n CHECK (\n (outcome = 'answered' AND session_json IS NOT NULL AND answer IS NOT NULL AND diagnostic IS NULL)\n OR (outcome = 'failed' AND history_id IS NULL AND session_json IS NULL AND answer IS NULL AND diagnostic IS NOT NULL)\n OR (outcome IS NULL AND end_sequence IS NULL AND history_id IS NULL AND session_json IS NULL AND answer IS NULL AND diagnostic IS NULL AND completed_at IS NULL)\n )\n ) STRICT;\n CREATE TABLE IF NOT EXISTS timeline (\n sequence INTEGER PRIMARY KEY AUTOINCREMENT,\n kind TEXT NOT NULL CHECK (kind IN ('turn-start', 'call', 'activity', 'tell', 'turn-end'))\n ) STRICT;\n CREATE TABLE IF NOT EXISTS calls (\n sequence INTEGER PRIMARY KEY REFERENCES timeline(sequence) ON DELETE CASCADE,\n turn_sequence INTEGER NOT NULL REFERENCES turns(sequence) ON DELETE CASCADE,\n body TEXT NOT NULL,\n at TEXT NOT NULL\n ) STRICT;\n CREATE TABLE IF NOT EXISTS activity (\n sequence INTEGER PRIMARY KEY REFERENCES timeline(sequence) ON DELETE CASCADE,\n turn_sequence INTEGER NOT NULL REFERENCES turns(sequence) ON DELETE CASCADE,\n event_json TEXT NOT NULL CHECK (json_valid(event_json)),\n at TEXT NOT NULL\n ) STRICT;\n CREATE TABLE IF NOT EXISTS tells (\n id TEXT PRIMARY KEY,\n sequence INTEGER NOT NULL UNIQUE REFERENCES timeline(sequence) ON DELETE CASCADE,\n body TEXT NOT NULL,\n recorded_at TEXT NOT NULL\n ) STRICT;\n CREATE TABLE IF NOT EXISTS tell_deliveries (\n sequence INTEGER PRIMARY KEY AUTOINCREMENT,\n tell_id TEXT NOT NULL REFERENCES tells(id) ON DELETE CASCADE,\n route TEXT NOT NULL CHECK (route IN ('launch', 'live')),\n turn_sequence INTEGER NOT NULL REFERENCES turns(sequence) ON DELETE CASCADE,\n fence TEXT NOT NULL,\n receipt TEXT CHECK (receipt IN ('unavailable', 'required')),\n delivered_at TEXT NOT NULL,\n CHECK ((route = 'launch' AND receipt IS NULL) OR (route = 'live' AND receipt IS NOT NULL)),\n UNIQUE (tell_id, turn_sequence, fence)\n ) STRICT;\n CREATE TABLE IF NOT EXISTS tell_receipts (\n sequence INTEGER PRIMARY KEY AUTOINCREMENT,\n evidence TEXT NOT NULL CHECK (evidence IN ('exact', 'fence')),\n tell_id TEXT REFERENCES tells(id) ON DELETE CASCADE,\n turn_sequence INTEGER REFERENCES turns(sequence) ON DELETE CASCADE,\n fence TEXT,\n kind TEXT NOT NULL,\n received_at TEXT NOT NULL,\n CHECK (\n (evidence = 'exact' AND tell_id IS NOT NULL AND turn_sequence IS NULL AND fence IS NULL)\n OR (evidence = 'fence' AND tell_id IS NULL AND turn_sequence IS NOT NULL AND fence IS NOT NULL)\n )\n ) STRICT;\n CREATE UNIQUE INDEX IF NOT EXISTS tell_receipts_exact\n ON tell_receipts(tell_id, kind) WHERE evidence = 'exact';\n CREATE UNIQUE INDEX IF NOT EXISTS tell_receipts_fence\n ON tell_receipts(turn_sequence, fence, kind) WHERE evidence = 'fence';\n CREATE TABLE IF NOT EXISTS requests (\n sequence INTEGER PRIMARY KEY AUTOINCREMENT,\n id TEXT NOT NULL UNIQUE,\n archetype TEXT NOT NULL,\n body TEXT NOT NULL,\n cwd TEXT,\n world TEXT NOT NULL,\n recipe_json TEXT NOT NULL CHECK (json_valid(recipe_json)),\n admitted_at TEXT NOT NULL,\n state TEXT NOT NULL CHECK (state IN ('admitted', 'reserved', 'served', 'refused', 'voided')),\n child TEXT,\n diagnostic TEXT,\n evidence TEXT,\n CHECK (\n (state = 'admitted' AND child IS NULL AND diagnostic IS NULL AND evidence IS NULL)\n OR (state IN ('reserved', 'served') AND child IS NOT NULL AND diagnostic IS NULL AND evidence IS NULL)\n OR (state = 'refused' AND child IS NULL AND diagnostic IS NOT NULL AND evidence IS NULL)\n OR (state = 'voided' AND child IS NULL AND diagnostic IS NULL AND evidence IS NOT NULL)\n )\n ) STRICT;\n CREATE TABLE IF NOT EXISTS control (\n kind TEXT PRIMARY KEY CHECK (kind IN ('stop', 'pause')),\n value_json TEXT NOT NULL CHECK (json_valid(value_json)),\n at TEXT NOT NULL\n ) STRICT;\n CREATE TABLE IF NOT EXISTS kills (\n sequence INTEGER PRIMARY KEY AUTOINCREMENT,\n body_sequence INTEGER NOT NULL UNIQUE REFERENCES bodies(sequence),\n evidence TEXT NOT NULL CHECK (evidence = 'killed'),\n at TEXT NOT NULL\n ) STRICT;\n";
5
5
  export declare const LEASH_SCHEMA = "\n PRAGMA journal_mode=DELETE;\n CREATE TABLE IF NOT EXISTS leash_schema (\n singleton INTEGER PRIMARY KEY CHECK (singleton = 1),\n version INTEGER NOT NULL CHECK (version = 4)\n ) STRICT;\n INSERT OR IGNORE INTO leash_schema(singleton, version) VALUES (1, 4);\n CREATE TABLE IF NOT EXISTS seal (\n singleton INTEGER PRIMARY KEY CHECK (singleton = 1),\n evidence TEXT NOT NULL,\n at TEXT NOT NULL\n ) STRICT;\n";
@@ -1,4 +1,4 @@
1
- const HEART_SCHEMA_VERSION = 10;
1
+ const HEART_SCHEMA_VERSION = 14;
2
2
  const LEASH_SCHEMA_VERSION = 4;
3
3
  function assertSchemaVersion(database, table, expected) {
4
4
  const row = database.prepare(`SELECT version FROM ${table} WHERE singleton = 1`).get();
@@ -19,24 +19,16 @@ export const HEART_SCHEMA = `
19
19
  INSERT OR IGNORE INTO akuma_schema(singleton, version) VALUES (1, ${HEART_SCHEMA_VERSION});
20
20
  CREATE TABLE IF NOT EXISTS soul (
21
21
  singleton INTEGER PRIMARY KEY CHECK (singleton = 1),
22
- id TEXT NOT NULL UNIQUE,
23
- archetype TEXT NOT NULL,
24
- description TEXT,
25
- provider_json TEXT NOT NULL CHECK (json_valid(provider_json)),
26
- options_json TEXT NOT NULL CHECK (json_valid(options_json)),
27
- cwd TEXT NOT NULL,
28
- origin_json TEXT NOT NULL CHECK (json_valid(origin_json)),
29
- confinement_json TEXT NOT NULL CHECK (json_valid(confinement_json)),
30
- created_at TEXT NOT NULL
22
+ soul_json TEXT NOT NULL CHECK (json_valid(soul_json))
31
23
  ) STRICT;
32
24
  CREATE TABLE IF NOT EXISTS bodies (
33
25
  sequence INTEGER PRIMARY KEY AUTOINCREMENT,
34
- pid INTEGER NOT NULL CHECK (pid > 0),
35
- process_group INTEGER NOT NULL CHECK (process_group > 0),
36
- spawned_at TEXT NOT NULL,
37
26
  leash_taken_at TEXT NOT NULL,
27
+ hung_diagnostic TEXT,
28
+ hung_at TEXT,
38
29
  end TEXT CHECK (end IN ('exited', 'broke-off', 'put-down')),
39
- ended_at TEXT
30
+ ended_at TEXT,
31
+ CHECK ((hung_diagnostic IS NULL AND hung_at IS NULL) OR (hung_diagnostic IS NOT NULL AND hung_at IS NOT NULL AND end IS NULL))
40
32
  ) STRICT;
41
33
  CREATE TABLE IF NOT EXISTS sessions (
42
34
  sequence INTEGER PRIMARY KEY AUTOINCREMENT,
@@ -58,7 +50,7 @@ export const HEART_SCHEMA = `
58
50
  diagnostic TEXT,
59
51
  completed_at TEXT,
60
52
  CHECK (
61
- (outcome = 'answered' AND history_id IS NOT NULL AND session_json IS NOT NULL AND answer IS NOT NULL AND diagnostic IS NULL)
53
+ (outcome = 'answered' AND session_json IS NOT NULL AND answer IS NOT NULL AND diagnostic IS NULL)
62
54
  OR (outcome = 'failed' AND history_id IS NULL AND session_json IS NULL AND answer IS NULL AND diagnostic IS NOT NULL)
63
55
  OR (outcome IS NULL AND end_sequence IS NULL AND history_id IS NULL AND session_json IS NULL AND answer IS NULL AND diagnostic IS NULL AND completed_at IS NULL)
64
56
  )
@@ -0,0 +1,8 @@
1
+ import type { DatabaseSync } from "node:sqlite";
2
+ import type { Soul, SoulRow } from "./facts.js";
3
+ export declare function decodeSoul(value: unknown): Soul;
4
+ export declare function encodeSoul(soul: Soul): string;
5
+ export declare function encodeSoulRow(soul: Soul): readonly [string];
6
+ export declare function decodeSoulRow(row: SoulRow): Soul;
7
+ export declare function soulFact(database: DatabaseSync): Soul | null;
8
+ export declare function insertSoulFact(database: DatabaseSync, soul: Soul): void;
@@ -0,0 +1,136 @@
1
+ import { parseAkuId } from "../identity.js";
2
+ import { decodeProviderOptions, decodeProviderRecipe, decodeReadonlyRestraint, } from "../provider-recipe.js";
3
+ function record(value) {
4
+ return value !== null && typeof value === "object" && !Array.isArray(value)
5
+ ? value
6
+ : null;
7
+ }
8
+ function unknownKey(value, allowed) {
9
+ return Object.keys(value).find((key) => !allowed.includes(key));
10
+ }
11
+ function nonblank(value, what) {
12
+ if (typeof value !== "string" || value.trim().length === 0) {
13
+ throw new Error(`Akuma soul ${what} must be a nonblank string`);
14
+ }
15
+ return value;
16
+ }
17
+ function soulAkuIdentity(value, what) {
18
+ try {
19
+ return parseAkuId(nonblank(value, what));
20
+ }
21
+ catch (error) {
22
+ throw new Error(`Akuma soul ${what} is not an Akuma identity coordinate`, { cause: error });
23
+ }
24
+ }
25
+ function decodeOrigin(value) {
26
+ const origin = record(value);
27
+ if (origin === null)
28
+ throw new Error("Akuma soul origin must be an object");
29
+ if (origin.kind === "direct") {
30
+ if (unknownKey(origin, ["kind"]) !== undefined)
31
+ throw new Error("Akuma soul direct origin must have only kind");
32
+ return { kind: "direct" };
33
+ }
34
+ if (origin.kind === "request") {
35
+ if (unknownKey(origin, ["kind", "parent", "requestId"]) !== undefined) {
36
+ throw new Error("Akuma soul request origin must have only kind, parent, and requestId");
37
+ }
38
+ return {
39
+ kind: "request",
40
+ parent: soulAkuIdentity(origin.parent, "request parent").id,
41
+ requestId: nonblank(origin.requestId, "request requestId"),
42
+ };
43
+ }
44
+ if (origin.kind === "fork") {
45
+ if (unknownKey(origin, ["at", "kind", "parent"]) !== undefined) {
46
+ throw new Error("Akuma soul fork origin must have only kind, parent, and at");
47
+ }
48
+ return {
49
+ kind: "fork",
50
+ parent: soulAkuIdentity(origin.parent, "fork parent").id,
51
+ at: nonblank(origin.at, "fork at"),
52
+ };
53
+ }
54
+ throw new Error("Akuma soul origin must be direct, request, or fork");
55
+ }
56
+ function decodeConfinement(value) {
57
+ const confinement = record(value);
58
+ if (confinement === null)
59
+ throw new Error("Akuma soul confinement must be an object");
60
+ if (confinement.kind === "unconfined") {
61
+ if (unknownKey(confinement, ["kind"]) !== undefined)
62
+ throw new Error("Akuma soul unconfined confinement must have only kind");
63
+ return { kind: "unconfined" };
64
+ }
65
+ if (confinement.kind === "declared") {
66
+ if (unknownKey(confinement, ["kind", "writableRoots"]) !== undefined) {
67
+ throw new Error("Akuma soul declared confinement must have only kind and writableRoots");
68
+ }
69
+ if (!Array.isArray(confinement.writableRoots)
70
+ || confinement.writableRoots.some((root) => typeof root !== "string" || root.trim().length === 0)) {
71
+ throw new Error("Akuma soul declared confinement writableRoots must be nonblank strings");
72
+ }
73
+ return { kind: "declared", writableRoots: [...confinement.writableRoots] };
74
+ }
75
+ throw new Error("Akuma soul confinement must be unconfined or declared");
76
+ }
77
+ function validateSoul(value) {
78
+ const soul = record(value);
79
+ if (soul === null)
80
+ throw new Error("Akuma soul must be an object");
81
+ if (unknownKey(soul, ["archetype", "confinement", "createdAt", "cwd", "description", "id", "options", "origin", "provider", "readonly"]) !== undefined) {
82
+ throw new Error("Akuma soul has an unknown field");
83
+ }
84
+ const identity = soulAkuIdentity(soul.id, "id");
85
+ const id = identity.id;
86
+ const archetype = nonblank(soul.archetype, "name");
87
+ if (identity.archetype !== archetype)
88
+ throw new Error("Akuma soul id and name must agree");
89
+ const description = soul.description === undefined ? undefined : nonblank(soul.description, "description");
90
+ const provider = decodeProviderRecipe(soul.provider);
91
+ const options = decodeProviderOptions(soul.options);
92
+ const restraint = soul.readonly === undefined ? undefined : decodeReadonlyRestraint(soul.readonly);
93
+ if ((options.readonly === true) !== (restraint !== undefined)) {
94
+ throw new Error("Akuma soul readonly option and restraint must agree");
95
+ }
96
+ return {
97
+ id,
98
+ archetype,
99
+ ...(description === undefined ? {} : { description }),
100
+ provider,
101
+ options,
102
+ ...(restraint === undefined ? {} : { readonly: restraint }),
103
+ cwd: nonblank(soul.cwd, "cwd"),
104
+ origin: decodeOrigin(soul.origin),
105
+ confinement: decodeConfinement(soul.confinement),
106
+ createdAt: nonblank(soul.createdAt, "createdAt"),
107
+ };
108
+ }
109
+ function deepFreeze(value) {
110
+ if (value !== null && typeof value === "object") {
111
+ for (const key of Object.keys(value)) {
112
+ deepFreeze(value[key]);
113
+ }
114
+ Object.freeze(value);
115
+ }
116
+ return value;
117
+ }
118
+ export function decodeSoul(value) {
119
+ return deepFreeze(validateSoul(value));
120
+ }
121
+ export function encodeSoul(soul) {
122
+ return JSON.stringify(validateSoul(soul));
123
+ }
124
+ export function encodeSoulRow(soul) {
125
+ return [encodeSoul(soul)];
126
+ }
127
+ export function decodeSoulRow(row) {
128
+ return decodeSoul(JSON.parse(row.soul_json));
129
+ }
130
+ export function soulFact(database) {
131
+ const row = database.prepare("SELECT soul_json FROM soul WHERE singleton = 1").get();
132
+ return row === undefined ? null : decodeSoulRow(row);
133
+ }
134
+ export function insertSoulFact(database, soul) {
135
+ database.prepare("INSERT INTO soul(singleton, soul_json) VALUES (1, ?)").run(...encodeSoulRow(soul));
136
+ }
@@ -1,33 +1,47 @@
1
1
  import { DatabaseSync } from "node:sqlite";
2
2
  import type { AkumaPaths } from "../identity.js";
3
- import type { LeashProbe, SessionFact, Soul, StopFact, TellFact } from "./facts.js";
3
+ import type { BodyFact, LeashProbe, SessionFact, Soul, StopFact, TellFact } from "./facts.js";
4
4
  import { sealFact } from "./rows.js";
5
- export declare function openHeart(path: string, verify?: boolean): DatabaseSync;
6
- export declare function withHeart<T>(paths: AkumaPaths, body: (database: DatabaseSync) => T): T;
5
+ export declare class HeartAbsentError extends Error {
6
+ readonly path: string;
7
+ constructor(path: string, options?: ErrorOptions);
8
+ }
9
+ export declare function isHeartAbsent(error: unknown): error is HeartAbsentError;
10
+ export declare function withHeart<T>(paths: AkumaPaths, body: (database: DatabaseSync) => T): Promise<T>;
7
11
  export declare function transaction<T>(database: DatabaseSync, body: () => T): T;
8
- export declare function initializeHeart(paths: AkumaPaths): void;
12
+ export declare function initializeHeart(paths: AkumaPaths): Promise<void>;
9
13
  export declare class HeldAkumaLeash {
10
14
  private readonly database;
11
15
  private closed;
16
+ private bodySequence;
12
17
  private constructor();
13
- static try(paths: AkumaPaths): HeldAkumaLeash | null;
14
- birth(paths: AkumaPaths, soul: Soul, session?: Omit<SessionFact, "sequence">): "born" | "already-born" | "sealed";
18
+ static try(paths: AkumaPaths): Promise<HeldAkumaLeash | null>;
19
+ birth(paths: AkumaPaths, soul: Soul, session?: Omit<SessionFact, "sequence">): Promise<"born" | "already-born" | "sealed">;
15
20
  sealIfUnborn(paths: AkumaPaths, input: Readonly<{
16
21
  evidence: string;
17
22
  at: string;
18
- }>): "born" | "sealed";
19
- clearPause(paths: AkumaPaths): void;
20
- settleStop(paths: AkumaPaths, expectedBodySequence?: number): Readonly<{
23
+ }>): Promise<"born" | "sealed">;
24
+ clearPause(paths: AkumaPaths): Promise<void>;
25
+ clearStop(paths: AkumaPaths): Promise<void>;
26
+ recordBody(paths: AkumaPaths, input: Readonly<{
27
+ leashTakenAt: string;
28
+ }>): Promise<BodyFact>;
29
+ recordBodyHung(paths: AkumaPaths, input: Readonly<{
30
+ sequence: number;
31
+ diagnostic: string;
32
+ at: string;
33
+ }>): Promise<void>;
34
+ settleStop(paths: AkumaPaths, expectedBodySequence?: number): Promise<Readonly<{
21
35
  target: StopFact;
22
36
  result: "recorded" | "already-killed";
23
- }> | null;
24
- recordInterruptTell(paths: AkumaPaths, tell: Omit<TellFact, "sequence" | "state" | "deliveries">): Readonly<{
37
+ }> | null>;
38
+ recordInterruptTell(paths: AkumaPaths, tell: Omit<TellFact, "sequence" | "state" | "deliveries">): Promise<Readonly<{
25
39
  kind: "not-born";
26
40
  } | {
27
41
  kind: "recorded";
28
42
  tell: TellFact;
29
- }>;
43
+ }>>;
30
44
  release(): void;
31
45
  }
32
- export declare function probeLeash(paths: AkumaPaths): LeashProbe;
33
- export declare function readSealFromLeash(paths: AkumaPaths): ReturnType<typeof sealFact>;
46
+ export declare function probeLeash(paths: AkumaPaths): Promise<LeashProbe>;
47
+ export declare function readSealFromLeash(paths: AkumaPaths): Promise<ReturnType<typeof sealFact>>;