@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,4 +1,4 @@
1
- const HEART_SCHEMA_VERSION = 6;
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,
@@ -47,31 +39,72 @@ export const HEART_SCHEMA = `
47
39
  admitted_at TEXT NOT NULL
48
40
  ) STRICT;
49
41
  CREATE TABLE IF NOT EXISTS turns (
50
- sequence INTEGER PRIMARY KEY AUTOINCREMENT,
42
+ sequence INTEGER PRIMARY KEY REFERENCES timeline(sequence) ON DELETE CASCADE,
51
43
  body_sequence INTEGER NOT NULL REFERENCES bodies(sequence),
52
- outcome TEXT NOT NULL CHECK (outcome IN ('answered', 'failed')),
44
+ started_at TEXT NOT NULL,
45
+ end_sequence INTEGER UNIQUE REFERENCES timeline(sequence) ON DELETE SET NULL,
46
+ outcome TEXT CHECK (outcome IN ('answered', 'failed')),
53
47
  history_id TEXT UNIQUE,
54
48
  session_json TEXT CHECK (session_json IS NULL OR json_valid(session_json)),
55
49
  answer TEXT,
56
50
  diagnostic TEXT,
57
- completed_at TEXT NOT NULL,
51
+ completed_at TEXT,
58
52
  CHECK (
59
- (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)
60
54
  OR (outcome = 'failed' AND history_id IS NULL AND session_json IS NULL AND answer IS NULL AND diagnostic IS NOT NULL)
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)
61
56
  )
62
57
  ) STRICT;
63
- CREATE TABLE IF NOT EXISTS activity (
58
+ CREATE TABLE IF NOT EXISTS timeline (
64
59
  sequence INTEGER PRIMARY KEY AUTOINCREMENT,
65
- body_sequence INTEGER NOT NULL REFERENCES bodies(sequence),
60
+ kind TEXT NOT NULL CHECK (kind IN ('turn-start', 'call', 'activity', 'tell', 'turn-end'))
61
+ ) STRICT;
62
+ CREATE TABLE IF NOT EXISTS calls (
63
+ sequence INTEGER PRIMARY KEY REFERENCES timeline(sequence) ON DELETE CASCADE,
64
+ turn_sequence INTEGER NOT NULL REFERENCES turns(sequence) ON DELETE CASCADE,
65
+ body TEXT NOT NULL,
66
+ at TEXT NOT NULL
67
+ ) STRICT;
68
+ CREATE TABLE IF NOT EXISTS activity (
69
+ sequence INTEGER PRIMARY KEY REFERENCES timeline(sequence) ON DELETE CASCADE,
70
+ turn_sequence INTEGER NOT NULL REFERENCES turns(sequence) ON DELETE CASCADE,
66
71
  event_json TEXT NOT NULL CHECK (json_valid(event_json)),
67
72
  at TEXT NOT NULL
68
73
  ) STRICT;
69
74
  CREATE TABLE IF NOT EXISTS tells (
70
75
  id TEXT PRIMARY KEY,
76
+ sequence INTEGER NOT NULL UNIQUE REFERENCES timeline(sequence) ON DELETE CASCADE,
71
77
  body TEXT NOT NULL,
72
- state TEXT NOT NULL CHECK (state IN ('recorded', 'delivered', 'seen', 'consumed', 'voided-by-death')),
73
78
  recorded_at TEXT NOT NULL
74
79
  ) STRICT;
80
+ CREATE TABLE IF NOT EXISTS tell_deliveries (
81
+ sequence INTEGER PRIMARY KEY AUTOINCREMENT,
82
+ tell_id TEXT NOT NULL REFERENCES tells(id) ON DELETE CASCADE,
83
+ route TEXT NOT NULL CHECK (route IN ('launch', 'live')),
84
+ turn_sequence INTEGER NOT NULL REFERENCES turns(sequence) ON DELETE CASCADE,
85
+ fence TEXT NOT NULL,
86
+ receipt TEXT CHECK (receipt IN ('unavailable', 'required')),
87
+ delivered_at TEXT NOT NULL,
88
+ CHECK ((route = 'launch' AND receipt IS NULL) OR (route = 'live' AND receipt IS NOT NULL)),
89
+ UNIQUE (tell_id, turn_sequence, fence)
90
+ ) STRICT;
91
+ CREATE TABLE IF NOT EXISTS tell_receipts (
92
+ sequence INTEGER PRIMARY KEY AUTOINCREMENT,
93
+ evidence TEXT NOT NULL CHECK (evidence IN ('exact', 'fence')),
94
+ tell_id TEXT REFERENCES tells(id) ON DELETE CASCADE,
95
+ turn_sequence INTEGER REFERENCES turns(sequence) ON DELETE CASCADE,
96
+ fence TEXT,
97
+ kind TEXT NOT NULL,
98
+ received_at TEXT NOT NULL,
99
+ CHECK (
100
+ (evidence = 'exact' AND tell_id IS NOT NULL AND turn_sequence IS NULL AND fence IS NULL)
101
+ OR (evidence = 'fence' AND tell_id IS NULL AND turn_sequence IS NOT NULL AND fence IS NOT NULL)
102
+ )
103
+ ) STRICT;
104
+ CREATE UNIQUE INDEX IF NOT EXISTS tell_receipts_exact
105
+ ON tell_receipts(tell_id, kind) WHERE evidence = 'exact';
106
+ CREATE UNIQUE INDEX IF NOT EXISTS tell_receipts_fence
107
+ ON tell_receipts(turn_sequence, fence, kind) WHERE evidence = 'fence';
75
108
  CREATE TABLE IF NOT EXISTS requests (
76
109
  sequence INTEGER PRIMARY KEY AUTOINCREMENT,
77
110
  id TEXT NOT NULL UNIQUE,
@@ -93,10 +126,16 @@ export const HEART_SCHEMA = `
93
126
  )
94
127
  ) STRICT;
95
128
  CREATE TABLE IF NOT EXISTS control (
96
- kind TEXT PRIMARY KEY CHECK (kind IN ('stop', 'pause', 'death')),
129
+ kind TEXT PRIMARY KEY CHECK (kind IN ('stop', 'pause')),
97
130
  value_json TEXT NOT NULL CHECK (json_valid(value_json)),
98
131
  at TEXT NOT NULL
99
132
  ) STRICT;
133
+ CREATE TABLE IF NOT EXISTS kills (
134
+ sequence INTEGER PRIMARY KEY AUTOINCREMENT,
135
+ body_sequence INTEGER NOT NULL UNIQUE REFERENCES bodies(sequence),
136
+ evidence TEXT NOT NULL CHECK (evidence = 'killed'),
137
+ at TEXT NOT NULL
138
+ ) STRICT;
100
139
  `;
101
140
  export const LEASH_SCHEMA = `
102
141
  PRAGMA journal_mode=DELETE;
@@ -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
+ }
@@ -0,0 +1,47 @@
1
+ import { DatabaseSync } from "node:sqlite";
2
+ import type { AkumaPaths } from "../identity.js";
3
+ import type { BodyFact, LeashProbe, SessionFact, Soul, StopFact, TellFact } from "./facts.js";
4
+ import { sealFact } from "./rows.js";
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>;
11
+ export declare function transaction<T>(database: DatabaseSync, body: () => T): T;
12
+ export declare function initializeHeart(paths: AkumaPaths): Promise<void>;
13
+ export declare class HeldAkumaLeash {
14
+ private readonly database;
15
+ private closed;
16
+ private bodySequence;
17
+ private constructor();
18
+ static try(paths: AkumaPaths): Promise<HeldAkumaLeash | null>;
19
+ birth(paths: AkumaPaths, soul: Soul, session?: Omit<SessionFact, "sequence">): Promise<"born" | "already-born" | "sealed">;
20
+ sealIfUnborn(paths: AkumaPaths, input: Readonly<{
21
+ evidence: string;
22
+ at: string;
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<{
35
+ target: StopFact;
36
+ result: "recorded" | "already-killed";
37
+ }> | null>;
38
+ recordInterruptTell(paths: AkumaPaths, tell: Omit<TellFact, "sequence" | "state" | "deliveries">): Promise<Readonly<{
39
+ kind: "not-born";
40
+ } | {
41
+ kind: "recorded";
42
+ tell: TellFact;
43
+ }>>;
44
+ release(): void;
45
+ }
46
+ export declare function probeLeash(paths: AkumaPaths): Promise<LeashProbe>;
47
+ export declare function readSealFromLeash(paths: AkumaPaths): Promise<ReturnType<typeof sealFact>>;
@@ -0,0 +1,224 @@
1
+ import { DatabaseSync } from "node:sqlite";
2
+ import { lstat } from "node:fs/promises";
3
+ import { pathToFileURL } from "node:url";
4
+ import { HEART_SCHEMA, LEASH_SCHEMA, assertHeartSchemaVersion, assertLeashSchemaVersion } from "./schema.js";
5
+ import { deletePauseControl, deleteStopControl, insertBodyFact, insertKillFact, insertSealFact, insertSessionFact, killFactForBody, latestBodyFact, latestKillFact, markBodyHung, sealExists, sealFact, stopFact, } from "./rows.js";
6
+ import { insertTellFact } from "./tells.js";
7
+ import { pruneActivityFacts } from "./timeline.js";
8
+ import { insertSoulFact, soulFact } from "./soul.js";
9
+ const ACTIVITY_LIMIT = 5_000;
10
+ const SQLITE_CANTOPEN = 14;
11
+ export class HeartAbsentError extends Error {
12
+ path;
13
+ constructor(path, options) {
14
+ super(`Akuma Heart database is absent: ${path}`, options);
15
+ this.path = path;
16
+ this.name = "HeartAbsentError";
17
+ }
18
+ }
19
+ export function isHeartAbsent(error) {
20
+ return error instanceof HeartAbsentError;
21
+ }
22
+ function isBusy(error) {
23
+ const value = error;
24
+ const message = String(value?.message ?? "").toLowerCase();
25
+ return value?.code === "ERR_SQLITE_BUSY" || value?.code === "ERR_SQLITE_LOCKED"
26
+ || value?.errcode === 5 || message.includes("database is locked") || message.includes("database is busy");
27
+ }
28
+ async function openExistingDatabase(path, timeout) {
29
+ const uri = pathToFileURL(path);
30
+ uri.searchParams.set("mode", "rw");
31
+ try {
32
+ return timeout === undefined
33
+ ? new DatabaseSync(uri.href)
34
+ : new DatabaseSync(uri.href, { timeout });
35
+ }
36
+ catch (error) {
37
+ if (error.errcode === SQLITE_CANTOPEN) {
38
+ try {
39
+ await lstat(path);
40
+ }
41
+ catch (metadataError) {
42
+ if (metadataError.code === "ENOENT") {
43
+ throw new HeartAbsentError(path, { cause: error });
44
+ }
45
+ }
46
+ }
47
+ throw error;
48
+ }
49
+ }
50
+ async function openHeart(path, verify = true) {
51
+ const database = await openExistingDatabase(path);
52
+ try {
53
+ database.exec("PRAGMA foreign_keys=ON; PRAGMA busy_timeout=5000; PRAGMA journal_mode=WAL");
54
+ if (verify)
55
+ assertHeartSchemaVersion(database);
56
+ return database;
57
+ }
58
+ catch (error) {
59
+ database.close();
60
+ throw error;
61
+ }
62
+ }
63
+ export async function withHeart(paths, body) {
64
+ const database = await openHeart(paths.heart);
65
+ try {
66
+ return body(database);
67
+ }
68
+ finally {
69
+ database.close();
70
+ }
71
+ }
72
+ export function transaction(database, body) {
73
+ database.exec("BEGIN IMMEDIATE");
74
+ try {
75
+ const result = body();
76
+ database.exec("COMMIT");
77
+ return result;
78
+ }
79
+ catch (error) {
80
+ try {
81
+ database.exec("ROLLBACK");
82
+ }
83
+ catch { /* preserve the adjudication failure */ }
84
+ throw error;
85
+ }
86
+ }
87
+ export async function initializeHeart(paths) {
88
+ const heart = new DatabaseSync(paths.heart);
89
+ try {
90
+ heart.exec(HEART_SCHEMA);
91
+ assertHeartSchemaVersion(heart);
92
+ }
93
+ finally {
94
+ heart.close();
95
+ }
96
+ const leash = new DatabaseSync(paths.leash);
97
+ try {
98
+ leash.exec(LEASH_SCHEMA);
99
+ assertLeashSchemaVersion(leash);
100
+ }
101
+ finally {
102
+ leash.close();
103
+ }
104
+ }
105
+ export class HeldAkumaLeash {
106
+ database;
107
+ closed = false;
108
+ bodySequence;
109
+ constructor(database) {
110
+ this.database = database;
111
+ }
112
+ static async try(paths) {
113
+ const database = await openExistingDatabase(paths.leash, 0);
114
+ try {
115
+ assertLeashSchemaVersion(database);
116
+ database.exec("PRAGMA busy_timeout=0; BEGIN EXCLUSIVE");
117
+ return new HeldAkumaLeash(database);
118
+ }
119
+ catch (error) {
120
+ database.close();
121
+ if (isBusy(error))
122
+ return null;
123
+ throw error;
124
+ }
125
+ }
126
+ async birth(paths, soul, session) {
127
+ if (sealExists(this.database))
128
+ return "sealed";
129
+ return await withHeart(paths, (heart) => transaction(heart, () => {
130
+ if (soulFact(heart) !== null)
131
+ return "already-born";
132
+ insertSoulFact(heart, soul);
133
+ if (session !== undefined)
134
+ insertSessionFact(heart, session);
135
+ return "born";
136
+ }));
137
+ }
138
+ async sealIfUnborn(paths, input) {
139
+ if (await withHeart(paths, (heart) => soulFact(heart)) !== null)
140
+ return "born";
141
+ insertSealFact(this.database, input);
142
+ this.database.exec("COMMIT");
143
+ this.closed = true;
144
+ this.database.close();
145
+ return "sealed";
146
+ }
147
+ async clearPause(paths) { await withHeart(paths, deletePauseControl); }
148
+ async clearStop(paths) { await withHeart(paths, deleteStopControl); }
149
+ async recordBody(paths, input) {
150
+ if (this.closed || this.bodySequence !== undefined)
151
+ throw new Error("Akuma leash cannot start another Body");
152
+ const sequence = await withHeart(paths, (heart) => insertBodyFact(heart, input));
153
+ this.bodySequence = sequence;
154
+ return { sequence, leashTakenAt: input.leashTakenAt };
155
+ }
156
+ async recordBodyHung(paths, input) {
157
+ if (this.closed || this.bodySequence !== input.sequence) {
158
+ throw new Error(`Akuma Body ${input.sequence} is not owned by this leash`);
159
+ }
160
+ await withHeart(paths, (heart) => markBodyHung(heart, input));
161
+ }
162
+ async settleStop(paths, expectedBodySequence) {
163
+ return await withHeart(paths, (heart) => transaction(heart, () => {
164
+ const target = stopFact(heart);
165
+ if (target === null) {
166
+ if (expectedBodySequence === undefined)
167
+ return null;
168
+ const existing = killFactForBody(heart, expectedBodySequence);
169
+ return existing === null ? null : { target: { bodySequence: expectedBodySequence, requestedAt: existing.at }, result: "already-killed" };
170
+ }
171
+ if (expectedBodySequence !== undefined && target.bodySequence !== expectedBodySequence) {
172
+ throw new Error("Akuma stop target changed while kill was in progress");
173
+ }
174
+ const latest = latestBodyFact(heart);
175
+ if (latest?.sequence !== target.bodySequence)
176
+ throw new Error("Akuma stop target is not the latest Body");
177
+ if (latest.end !== "put-down")
178
+ return null;
179
+ const result = latestKillFact(heart)?.bodySequence === target.bodySequence
180
+ ? "already-killed"
181
+ : (insertKillFact(heart, target.bodySequence, target.requestedAt), "recorded");
182
+ deleteStopControl(heart);
183
+ return { target, result };
184
+ }));
185
+ }
186
+ async recordInterruptTell(paths, tell) {
187
+ return await withHeart(paths, (heart) => transaction(heart, () => {
188
+ deletePauseControl(heart);
189
+ if (soulFact(heart) === null)
190
+ return { kind: "not-born" };
191
+ const sequence = insertTellFact(heart, tell);
192
+ pruneActivityFacts(heart, ACTIVITY_LIMIT);
193
+ return { kind: "recorded", tell: { sequence, ...tell, state: "pending", deliveries: [] } };
194
+ }));
195
+ }
196
+ release() {
197
+ if (this.closed)
198
+ return;
199
+ this.closed = true;
200
+ try {
201
+ this.database.exec("ROLLBACK");
202
+ }
203
+ finally {
204
+ this.database.close();
205
+ }
206
+ }
207
+ }
208
+ export async function probeLeash(paths) {
209
+ const claim = await HeldAkumaLeash.try(paths);
210
+ if (claim === null)
211
+ return "held";
212
+ claim.release();
213
+ return "free";
214
+ }
215
+ export async function readSealFromLeash(paths) {
216
+ const leash = await openExistingDatabase(paths.leash);
217
+ try {
218
+ assertLeashSchemaVersion(leash);
219
+ return sealFact(leash);
220
+ }
221
+ finally {
222
+ leash.close();
223
+ }
224
+ }
@@ -0,0 +1,12 @@
1
+ import type { DatabaseSync } from "node:sqlite";
2
+ import type { TellDeliveryInput, TellFact, TellReceiptInput } from "./facts.js";
3
+ export declare const tellStateSql = "CASE\n WHEN EXISTS (\n SELECT 1 FROM tell_deliveries d\n WHERE d.tell_id = tells.id AND (d.route = 'launch' OR d.receipt = 'unavailable')\n ) THEN 'told'\n WHEN EXISTS (SELECT 1 FROM tell_receipts r WHERE r.evidence = 'exact' AND r.tell_id = tells.id) THEN 'told'\n WHEN EXISTS (\n SELECT 1 FROM tell_receipts r\n JOIN tell_deliveries d ON d.turn_sequence = r.turn_sequence AND d.fence = r.fence\n WHERE r.evidence = 'fence' AND d.tell_id = tells.id\n ) THEN 'told'\n ELSE 'pending'\nEND";
4
+ export declare const pendingTellSequencesSql = "SELECT tells.sequence FROM tells WHERE CASE\n WHEN EXISTS (\n SELECT 1 FROM tell_deliveries d\n WHERE d.tell_id = tells.id AND (d.route = 'launch' OR d.receipt = 'unavailable')\n ) THEN 'told'\n WHEN EXISTS (SELECT 1 FROM tell_receipts r WHERE r.evidence = 'exact' AND r.tell_id = tells.id) THEN 'told'\n WHEN EXISTS (\n SELECT 1 FROM tell_receipts r\n JOIN tell_deliveries d ON d.turn_sequence = r.turn_sequence AND d.fence = r.fence\n WHERE r.evidence = 'fence' AND d.tell_id = tells.id\n ) THEN 'told'\n ELSE 'pending'\nEND = 'pending'";
5
+ export declare const pendingTellProtectionSql = "SELECT d.turn_sequence FROM tell_deliveries d\n JOIN tells ON tells.id = d.tell_id WHERE CASE\n WHEN EXISTS (\n SELECT 1 FROM tell_deliveries d\n WHERE d.tell_id = tells.id AND (d.route = 'launch' OR d.receipt = 'unavailable')\n ) THEN 'told'\n WHEN EXISTS (SELECT 1 FROM tell_receipts r WHERE r.evidence = 'exact' AND r.tell_id = tells.id) THEN 'told'\n WHEN EXISTS (\n SELECT 1 FROM tell_receipts r\n JOIN tell_deliveries d ON d.turn_sequence = r.turn_sequence AND d.fence = r.fence\n WHERE r.evidence = 'fence' AND d.tell_id = tells.id\n ) THEN 'told'\n ELSE 'pending'\nEND = 'pending'";
6
+ export declare function decodeTellAtSequence(database: DatabaseSync, sequence: number): TellFact;
7
+ export declare function insertTellFact(database: DatabaseSync, tell: Omit<TellFact, "sequence" | "state" | "deliveries">): number;
8
+ export declare function tellFact(database: DatabaseSync, id: string): TellFact | null;
9
+ export declare function insertTellDeliveryFact(database: DatabaseSync, input: TellDeliveryInput): void;
10
+ export declare function insertTellReceiptFact(database: DatabaseSync, input: TellReceiptInput): void;
11
+ export declare function tellIdsForFence(database: DatabaseSync, turnSequence: number, fence: string): readonly string[];
12
+ export declare function pendingTellFacts(database: DatabaseSync): readonly TellFact[];
@@ -0,0 +1,79 @@
1
+ export const tellStateSql = `CASE
2
+ WHEN EXISTS (
3
+ SELECT 1 FROM tell_deliveries d
4
+ WHERE d.tell_id = tells.id AND (d.route = 'launch' OR d.receipt = 'unavailable')
5
+ ) THEN 'told'
6
+ WHEN EXISTS (SELECT 1 FROM tell_receipts r WHERE r.evidence = 'exact' AND r.tell_id = tells.id) THEN 'told'
7
+ WHEN EXISTS (
8
+ SELECT 1 FROM tell_receipts r
9
+ JOIN tell_deliveries d ON d.turn_sequence = r.turn_sequence AND d.fence = r.fence
10
+ WHERE r.evidence = 'fence' AND d.tell_id = tells.id
11
+ ) THEN 'told'
12
+ ELSE 'pending'
13
+ END`;
14
+ export const pendingTellSequencesSql = `SELECT tells.sequence FROM tells WHERE ${tellStateSql} = 'pending'`;
15
+ export const pendingTellProtectionSql = `SELECT d.turn_sequence FROM tell_deliveries d
16
+ JOIN tells ON tells.id = d.tell_id WHERE ${tellStateSql} = 'pending'`;
17
+ function tellDeliveries(database, id) {
18
+ const rows = database.prepare(`SELECT turn_sequence, route, receipt, delivered_at FROM tell_deliveries
19
+ WHERE tell_id = ? ORDER BY sequence`).all(id);
20
+ return rows.map((row) => row.receipt == null
21
+ ? { turnSequence: row.turn_sequence, route: row.route, deliveredAt: row.delivered_at }
22
+ : { turnSequence: row.turn_sequence, route: row.route, receipt: row.receipt, deliveredAt: row.delivered_at });
23
+ }
24
+ function decodeTellRow(database, row) {
25
+ return {
26
+ kind: "tell",
27
+ sequence: row.sequence,
28
+ id: row.id,
29
+ body: row.body,
30
+ state: row.state,
31
+ recordedAt: row.recorded_at,
32
+ deliveries: tellDeliveries(database, row.id),
33
+ };
34
+ }
35
+ export function decodeTellAtSequence(database, sequence) {
36
+ const row = database.prepare(`SELECT sequence, id, body, recorded_at, ${tellStateSql} AS state
37
+ FROM tells WHERE sequence = ?`).get(sequence);
38
+ if (row === undefined)
39
+ throw new Error(`Akuma timeline references missing tell ${sequence}`);
40
+ return decodeTellRow(database, row);
41
+ }
42
+ export function insertTellFact(database, tell) {
43
+ const sequence = Number(database.prepare("INSERT INTO timeline(kind) VALUES ('tell')").run().lastInsertRowid);
44
+ database.prepare("INSERT INTO tells(id, sequence, body, recorded_at) VALUES (?, ?, ?, ?)")
45
+ .run(tell.id, sequence, tell.body, tell.recordedAt);
46
+ return sequence;
47
+ }
48
+ export function tellFact(database, id) {
49
+ const row = database.prepare(`SELECT sequence, id, body, recorded_at, ${tellStateSql} AS state
50
+ FROM tells WHERE id = ?`).get(id);
51
+ return row === undefined ? null : decodeTellRow(database, row);
52
+ }
53
+ export function insertTellDeliveryFact(database, input) {
54
+ const result = database.prepare(`INSERT OR IGNORE INTO tell_deliveries(
55
+ tell_id, route, turn_sequence, fence, receipt, delivered_at
56
+ ) VALUES (?, ?, ?, ?, ?, ?)`).run(input.tellId, input.route, input.turnSequence, input.fence, input.route === "live" ? input.receipt : null, input.deliveredAt);
57
+ const row = database.prepare(`SELECT route, receipt FROM tell_deliveries
58
+ WHERE tell_id = ? AND turn_sequence = ? AND fence = ?`)
59
+ .get(input.tellId, input.turnSequence, input.fence);
60
+ if (row === undefined || (result.changes === 0
61
+ && (row.route !== input.route || row.receipt !== (input.route === "live" ? input.receipt : null)))) {
62
+ throw new Error(`tell delivery ${input.tellId} has conflicting evidence`);
63
+ }
64
+ }
65
+ export function insertTellReceiptFact(database, input) {
66
+ database.prepare(`INSERT OR IGNORE INTO tell_receipts(
67
+ evidence, tell_id, turn_sequence, fence, kind, received_at
68
+ ) VALUES (?, ?, ?, ?, ?, ?)`).run(input.evidence, input.evidence === "exact" ? input.tellId : null, input.evidence === "fence" ? input.turnSequence : null, input.evidence === "fence" ? input.fence : null, input.kind, input.receivedAt);
69
+ }
70
+ export function tellIdsForFence(database, turnSequence, fence) {
71
+ return database.prepare(`SELECT tell_id FROM tell_deliveries
72
+ WHERE turn_sequence = ? AND fence = ? ORDER BY sequence`)
73
+ .all(turnSequence, fence).map((row) => row.tell_id);
74
+ }
75
+ export function pendingTellFacts(database) {
76
+ const rows = database.prepare(`SELECT sequence, id, body, recorded_at, ${tellStateSql} AS state FROM tells
77
+ WHERE ${tellStateSql} = 'pending' ORDER BY sequence`).all();
78
+ return rows.map((row) => decodeTellRow(database, row));
79
+ }
@@ -0,0 +1,15 @@
1
+ import type { DatabaseSync } from "node:sqlite";
2
+ import type { CallFact, TellFact, TurnEndFact, TurnStartFact } from "./facts.js";
3
+ import { type ActivityFact } from "./rows.js";
4
+ export type TimelineFact = TurnStartFact | CallFact | ActivityFact | TellFact | TurnEndFact;
5
+ export type ActivityFactSlice = Readonly<{
6
+ rows: readonly TimelineFact[];
7
+ lowestRetained: number | null;
8
+ highest: number | null;
9
+ }>;
10
+ export declare function pruneActivityFacts(database: DatabaseSync, limit: number): void;
11
+ export declare function activityFactSlice(database: DatabaseSync, input: Readonly<{
12
+ before?: number;
13
+ since?: number;
14
+ limit: number;
15
+ }>): ActivityFactSlice;