@goah/cli 0.13.0 → 0.13.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 (236) hide show
  1. package/dist/LICENSE +201 -0
  2. package/dist/THIRD-PARTY-NOTICES.md +2319 -0
  3. package/dist/anthropic-messages-WBFOHES5.js +7214 -0
  4. package/dist/azure-openai-responses-NZFH3PO6.js +253 -0
  5. package/dist/chunk-25XSBFX6.js +639 -0
  6. package/dist/chunk-2QBQZGXF.js +110 -0
  7. package/dist/chunk-2ZP2NBW3.js +345 -0
  8. package/dist/chunk-4KA63VNZ.js +2 -0
  9. package/dist/chunk-4M6TXTT5.js +86 -0
  10. package/dist/chunk-4US7ZHT3.js +20 -0
  11. package/dist/chunk-6M3OOCKO.js +11809 -0
  12. package/dist/chunk-7AXAFDPG.js +17 -0
  13. package/dist/chunk-7E7V6OIU.js +920 -0
  14. package/dist/chunk-BXA47DTF.js +548 -0
  15. package/dist/chunk-D2DVAH37.js +25 -0
  16. package/dist/chunk-FSBUS2BI.js +26 -0
  17. package/dist/chunk-GKDRAHLI.js +4787 -0
  18. package/dist/chunk-H462EFJP.js +89 -0
  19. package/dist/chunk-IIJQ3DW4.js +35 -0
  20. package/dist/chunk-JQD5EWJY.js +34 -0
  21. package/dist/chunk-K26BVR6O.js +61 -0
  22. package/dist/chunk-KAO7WIXG.js +31 -0
  23. package/dist/chunk-KJ4HJCTK.js +1178 -0
  24. package/dist/chunk-LBKI23CC.js +9810 -0
  25. package/dist/chunk-LKBKZEHT.js +41 -0
  26. package/dist/chunk-MD7TLTXZ.js +18 -0
  27. package/dist/chunk-MPXK7QBL.js +868 -0
  28. package/dist/chunk-NRLGWZJC.js +11 -0
  29. package/dist/chunk-QBD6EJ2Z.js +1746 -0
  30. package/dist/chunk-R6I3VKME.js +132 -0
  31. package/dist/chunk-TJON22J4.js +87 -0
  32. package/dist/chunk-ULY36ME4.js +33097 -0
  33. package/dist/chunk-XALZLRYK.js +9221 -0
  34. package/dist/chunk-XPQKM3L7.js +57 -0
  35. package/dist/chunk-YH6YJ2IB.js +1773 -0
  36. package/dist/cli.d.ts +0 -2
  37. package/dist/cli.js +13716 -580
  38. package/dist/console/assets/{index-Rtc_B7cs.js → index-B1vGMXKr.js} +11 -11
  39. package/dist/console/assets/{index-Rtc_B7cs.js.map → index-B1vGMXKr.js.map} +1 -1
  40. package/dist/console/index.html +1 -1
  41. package/dist/dist-65MDQA4Q.js +1126 -0
  42. package/dist/dist-KNR2XL6J.js +39 -0
  43. package/dist/execution.d.ts +557 -2
  44. package/dist/execution.js +37 -2
  45. package/dist/faux-runner-worker.js +128 -0
  46. package/dist/google-generative-ai-YMHXZSFE.js +422 -0
  47. package/dist/google-vertex-X6DB3ZQ6.js +471 -0
  48. package/dist/index.d.ts +853 -25
  49. package/dist/index.js +84 -385
  50. package/dist/kernel.d.ts +39 -2
  51. package/dist/kernel.js +21 -2
  52. package/dist/mistral-conversations-TY3MMD6G.js +718 -0
  53. package/dist/multipart-parser-F6M4NJZU.js +373 -0
  54. package/dist/openai-codex-responses-BRDUMLN5.js +1374 -0
  55. package/dist/openai-completions-CX634XN3.js +1139 -0
  56. package/dist/openai-responses-HMCXJIHT.js +300 -0
  57. package/dist/openrouter-images-KTWET2H2.js +148 -0
  58. package/dist/pi-messages-XL5OEC4K.js +307 -0
  59. package/dist/pi-worker.js +671 -0
  60. package/dist/runner-pi.d.ts +342 -2
  61. package/dist/runner-pi.js +46 -2
  62. package/dist/runner-registry-GD4BYCQC.js +22 -0
  63. package/dist/sqlite.d.ts +446 -2
  64. package/dist/sqlite.js +15 -2
  65. package/dist/src-F2KBSV4T.js +1398 -0
  66. package/dist/supervisor.d.ts +625 -2
  67. package/dist/supervisor.js +39 -2
  68. package/dist/testkit.d.ts +564 -2
  69. package/dist/testkit.js +127 -2
  70. package/dist/transcript.d.ts +84 -2
  71. package/dist/transcript.js +25 -2
  72. package/dist/verification-worker.js +72 -0
  73. package/package.json +2 -26
  74. package/dist/cli.d.ts.map +0 -1
  75. package/dist/cli.js.map +0 -1
  76. package/dist/control.d.ts +0 -106
  77. package/dist/control.d.ts.map +0 -1
  78. package/dist/control.js +0 -242
  79. package/dist/control.js.map +0 -1
  80. package/dist/execution.d.ts.map +0 -1
  81. package/dist/execution.js.map +0 -1
  82. package/dist/index.d.ts.map +0 -1
  83. package/dist/index.js.map +0 -1
  84. package/dist/kernel.d.ts.map +0 -1
  85. package/dist/kernel.js.map +0 -1
  86. package/dist/live-config.d.ts +0 -10
  87. package/dist/live-config.d.ts.map +0 -1
  88. package/dist/live-config.js +0 -44
  89. package/dist/live-config.js.map +0 -1
  90. package/dist/prompt-queue.d.ts +0 -13
  91. package/dist/prompt-queue.d.ts.map +0 -1
  92. package/dist/prompt-queue.js +0 -35
  93. package/dist/prompt-queue.js.map +0 -1
  94. package/dist/runner-pi.d.ts.map +0 -1
  95. package/dist/runner-pi.js.map +0 -1
  96. package/dist/runner-registry.d.ts +0 -8
  97. package/dist/runner-registry.d.ts.map +0 -1
  98. package/dist/runner-registry.js +0 -12
  99. package/dist/runner-registry.js.map +0 -1
  100. package/dist/searchable-select.d.ts +0 -24
  101. package/dist/searchable-select.d.ts.map +0 -1
  102. package/dist/searchable-select.js +0 -61
  103. package/dist/searchable-select.js.map +0 -1
  104. package/dist/secret-input.d.ts +0 -8
  105. package/dist/secret-input.d.ts.map +0 -1
  106. package/dist/secret-input.js +0 -14
  107. package/dist/secret-input.js.map +0 -1
  108. package/dist/setup-wizard.d.ts +0 -22
  109. package/dist/setup-wizard.d.ts.map +0 -1
  110. package/dist/setup-wizard.js +0 -154
  111. package/dist/setup-wizard.js.map +0 -1
  112. package/dist/sqlite.d.ts.map +0 -1
  113. package/dist/sqlite.js.map +0 -1
  114. package/dist/supervisor.d.ts.map +0 -1
  115. package/dist/supervisor.js.map +0 -1
  116. package/dist/testkit.d.ts.map +0 -1
  117. package/dist/testkit.js.map +0 -1
  118. package/dist/thread-inspect.d.ts +0 -47
  119. package/dist/thread-inspect.d.ts.map +0 -1
  120. package/dist/thread-inspect.js +0 -78
  121. package/dist/thread-inspect.js.map +0 -1
  122. package/dist/transcript.d.ts.map +0 -1
  123. package/dist/transcript.js.map +0 -1
  124. package/dist/tui-theme.d.ts +0 -17
  125. package/dist/tui-theme.d.ts.map +0 -1
  126. package/dist/tui-theme.js +0 -19
  127. package/dist/tui-theme.js.map +0 -1
  128. package/dist/tui.d.ts +0 -24
  129. package/dist/tui.d.ts.map +0 -1
  130. package/dist/tui.js +0 -784
  131. package/dist/tui.js.map +0 -1
  132. package/dist/update.d.ts +0 -16
  133. package/dist/update.d.ts.map +0 -1
  134. package/dist/update.js +0 -67
  135. package/dist/update.js.map +0 -1
  136. package/dist/web-console.d.ts +0 -33
  137. package/dist/web-console.d.ts.map +0 -1
  138. package/dist/web-console.js +0 -242
  139. package/dist/web-console.js.map +0 -1
  140. package/dist/welcome.d.ts +0 -30
  141. package/dist/welcome.d.ts.map +0 -1
  142. package/dist/welcome.js +0 -69
  143. package/dist/welcome.js.map +0 -1
  144. package/node_modules/goah-ledger-contract/dist/execution.d.ts +0 -544
  145. package/node_modules/goah-ledger-contract/dist/execution.d.ts.map +0 -1
  146. package/node_modules/goah-ledger-contract/dist/execution.js +0 -52
  147. package/node_modules/goah-ledger-contract/dist/execution.js.map +0 -1
  148. package/node_modules/goah-ledger-contract/dist/index.d.ts +0 -4
  149. package/node_modules/goah-ledger-contract/dist/index.d.ts.map +0 -1
  150. package/node_modules/goah-ledger-contract/dist/index.js +0 -4
  151. package/node_modules/goah-ledger-contract/dist/index.js.map +0 -1
  152. package/node_modules/goah-ledger-contract/dist/kernel.d.ts +0 -37
  153. package/node_modules/goah-ledger-contract/dist/kernel.d.ts.map +0 -1
  154. package/node_modules/goah-ledger-contract/dist/kernel.js +0 -8
  155. package/node_modules/goah-ledger-contract/dist/kernel.js.map +0 -1
  156. package/node_modules/goah-ledger-contract/dist/transcript.d.ts +0 -63
  157. package/node_modules/goah-ledger-contract/dist/transcript.d.ts.map +0 -1
  158. package/node_modules/goah-ledger-contract/dist/transcript.js +0 -115
  159. package/node_modules/goah-ledger-contract/dist/transcript.js.map +0 -1
  160. package/node_modules/goah-ledger-contract/package.json +0 -28
  161. package/node_modules/goah-ledger-sqlite/dist/index.d.ts +0 -86
  162. package/node_modules/goah-ledger-sqlite/dist/index.d.ts.map +0 -1
  163. package/node_modules/goah-ledger-sqlite/dist/index.js +0 -1254
  164. package/node_modules/goah-ledger-sqlite/dist/index.js.map +0 -1
  165. package/node_modules/goah-ledger-sqlite/package.json +0 -26
  166. package/node_modules/goah-runner-pi/dist/configurator.d.ts +0 -20
  167. package/node_modules/goah-runner-pi/dist/configurator.d.ts.map +0 -1
  168. package/node_modules/goah-runner-pi/dist/configurator.js +0 -423
  169. package/node_modules/goah-runner-pi/dist/configurator.js.map +0 -1
  170. package/node_modules/goah-runner-pi/dist/credential-store.d.ts +0 -14
  171. package/node_modules/goah-runner-pi/dist/credential-store.d.ts.map +0 -1
  172. package/node_modules/goah-runner-pi/dist/credential-store.js +0 -163
  173. package/node_modules/goah-runner-pi/dist/credential-store.js.map +0 -1
  174. package/node_modules/goah-runner-pi/dist/env-spec.d.ts +0 -17
  175. package/node_modules/goah-runner-pi/dist/env-spec.d.ts.map +0 -1
  176. package/node_modules/goah-runner-pi/dist/env-spec.js +0 -95
  177. package/node_modules/goah-runner-pi/dist/env-spec.js.map +0 -1
  178. package/node_modules/goah-runner-pi/dist/index.d.ts +0 -76
  179. package/node_modules/goah-runner-pi/dist/index.d.ts.map +0 -1
  180. package/node_modules/goah-runner-pi/dist/index.js +0 -326
  181. package/node_modules/goah-runner-pi/dist/index.js.map +0 -1
  182. package/node_modules/goah-runner-pi/dist/model-provider.d.ts +0 -30
  183. package/node_modules/goah-runner-pi/dist/model-provider.d.ts.map +0 -1
  184. package/node_modules/goah-runner-pi/dist/model-provider.js +0 -96
  185. package/node_modules/goah-runner-pi/dist/model-provider.js.map +0 -1
  186. package/node_modules/goah-runner-pi/dist/pi-worker.d.ts +0 -31
  187. package/node_modules/goah-runner-pi/dist/pi-worker.d.ts.map +0 -1
  188. package/node_modules/goah-runner-pi/dist/pi-worker.js +0 -579
  189. package/node_modules/goah-runner-pi/dist/pi-worker.js.map +0 -1
  190. package/node_modules/goah-runner-pi/dist/steering-no-ack.test-fixture.d.ts +0 -2
  191. package/node_modules/goah-runner-pi/dist/steering-no-ack.test-fixture.d.ts.map +0 -1
  192. package/node_modules/goah-runner-pi/dist/steering-no-ack.test-fixture.js +0 -3
  193. package/node_modules/goah-runner-pi/dist/steering-no-ack.test-fixture.js.map +0 -1
  194. package/node_modules/goah-runner-pi/dist/steering-reject.test-fixture.d.ts +0 -2
  195. package/node_modules/goah-runner-pi/dist/steering-reject.test-fixture.d.ts.map +0 -1
  196. package/node_modules/goah-runner-pi/dist/steering-reject.test-fixture.js +0 -5
  197. package/node_modules/goah-runner-pi/dist/steering-reject.test-fixture.js.map +0 -1
  198. package/node_modules/goah-runner-pi/dist/steering-worker.test-fixture.d.ts +0 -2
  199. package/node_modules/goah-runner-pi/dist/steering-worker.test-fixture.d.ts.map +0 -1
  200. package/node_modules/goah-runner-pi/dist/steering-worker.test-fixture.js +0 -5
  201. package/node_modules/goah-runner-pi/dist/steering-worker.test-fixture.js.map +0 -1
  202. package/node_modules/goah-runner-pi/dist/test-helpers.d.ts +0 -4
  203. package/node_modules/goah-runner-pi/dist/test-helpers.d.ts.map +0 -1
  204. package/node_modules/goah-runner-pi/dist/test-helpers.js +0 -13
  205. package/node_modules/goah-runner-pi/dist/test-helpers.js.map +0 -1
  206. package/node_modules/goah-runner-pi/dist/verification-worker.d.ts +0 -2
  207. package/node_modules/goah-runner-pi/dist/verification-worker.d.ts.map +0 -1
  208. package/node_modules/goah-runner-pi/dist/verification-worker.js +0 -46
  209. package/node_modules/goah-runner-pi/dist/verification-worker.js.map +0 -1
  210. package/node_modules/goah-runner-pi/package.json +0 -28
  211. package/node_modules/goah-supervisor/dist/context-view.d.ts +0 -30
  212. package/node_modules/goah-supervisor/dist/context-view.d.ts.map +0 -1
  213. package/node_modules/goah-supervisor/dist/context-view.js +0 -58
  214. package/node_modules/goah-supervisor/dist/context-view.js.map +0 -1
  215. package/node_modules/goah-supervisor/dist/index.d.ts +0 -96
  216. package/node_modules/goah-supervisor/dist/index.d.ts.map +0 -1
  217. package/node_modules/goah-supervisor/dist/index.js +0 -1075
  218. package/node_modules/goah-supervisor/dist/index.js.map +0 -1
  219. package/node_modules/goah-supervisor/dist/roles.d.ts +0 -4
  220. package/node_modules/goah-supervisor/dist/roles.d.ts.map +0 -1
  221. package/node_modules/goah-supervisor/dist/roles.js +0 -29
  222. package/node_modules/goah-supervisor/dist/roles.js.map +0 -1
  223. package/node_modules/goah-supervisor/dist/verification.d.ts +0 -61
  224. package/node_modules/goah-supervisor/dist/verification.d.ts.map +0 -1
  225. package/node_modules/goah-supervisor/dist/verification.js +0 -135
  226. package/node_modules/goah-supervisor/dist/verification.js.map +0 -1
  227. package/node_modules/goah-supervisor/package.json +0 -26
  228. package/node_modules/goah-testkit/dist/faux-runner-worker.d.ts +0 -2
  229. package/node_modules/goah-testkit/dist/faux-runner-worker.d.ts.map +0 -1
  230. package/node_modules/goah-testkit/dist/faux-runner-worker.js +0 -82
  231. package/node_modules/goah-testkit/dist/faux-runner-worker.js.map +0 -1
  232. package/node_modules/goah-testkit/dist/index.d.ts +0 -49
  233. package/node_modules/goah-testkit/dist/index.d.ts.map +0 -1
  234. package/node_modules/goah-testkit/dist/index.js +0 -96
  235. package/node_modules/goah-testkit/dist/index.js.map +0 -1
  236. package/node_modules/goah-testkit/package.json +0 -29
@@ -0,0 +1,1773 @@
1
+ import { createRequire as __goahCreateRequire } from "node:module";
2
+ const require = __goahCreateRequire(import.meta.url);
3
+ import {
4
+ controlStream,
5
+ goalStream,
6
+ wakeStream,
7
+ workRecordStream
8
+ } from "./chunk-KAO7WIXG.js";
9
+ import {
10
+ assertGoalSnapshot,
11
+ assertGoalTransition,
12
+ assertHandoff,
13
+ goalAutomaticTarget,
14
+ goalRoute,
15
+ normalizeAssistantText
16
+ } from "./chunk-TJON22J4.js";
17
+
18
+ // node_modules/goah-ledger-sqlite/dist/index.js
19
+ import { DatabaseSync } from "node:sqlite";
20
+ import { isDeepStrictEqual } from "node:util";
21
+ var SQLITE_SCHEMA_VERSION = 27;
22
+ var createThreads = `CREATE TABLE IF NOT EXISTS threads (
23
+ id TEXT PRIMARY KEY,
24
+ agent TEXT NOT NULL,
25
+ role TEXT NOT NULL CHECK(role IN ('ceo','child','verifier','audit')),
26
+ parent_thread_id TEXT REFERENCES threads(id),
27
+ created_at TEXT NOT NULL,
28
+ updated_at TEXT NOT NULL,
29
+ UNIQUE(agent),
30
+ CHECK((agent='ceo' AND role='ceo') OR (agent<>'ceo' AND role<>'ceo'))
31
+ ) STRICT;`;
32
+ var createTurns = `CREATE TABLE IF NOT EXISTS turns (
33
+ id TEXT PRIMARY KEY,
34
+ thread_id TEXT NOT NULL REFERENCES threads(id) ON DELETE CASCADE,
35
+ trigger_kind TEXT NOT NULL CHECK(trigger_kind IN ('user_message','wake')),
36
+ goal_id TEXT REFERENCES goals(id),
37
+ goal_revision INTEGER CHECK(goal_revision IS NULL OR goal_revision >= 0),
38
+ status TEXT NOT NULL CHECK(status IN ('in_progress','completed','failed','interrupted')),
39
+ attempt INTEGER NOT NULL CHECK(attempt > 0),
40
+ error TEXT CHECK(error IS NULL OR json_valid(error)),
41
+ started_at TEXT NOT NULL,
42
+ ended_at TEXT,
43
+ lease_until TEXT,
44
+ lease_token TEXT,
45
+ runner_pid INTEGER CHECK(runner_pid IS NULL OR runner_pid > 0),
46
+ runner_profile_id TEXT,
47
+ CHECK((goal_id IS NULL) = (goal_revision IS NULL)),
48
+ CHECK((status='in_progress' AND ended_at IS NULL AND lease_until IS NOT NULL AND lease_token IS NOT NULL) OR (status<>'in_progress' AND ended_at IS NOT NULL AND lease_until IS NULL AND lease_token IS NULL))
49
+ ) STRICT;`;
50
+ var createTurnItems = `CREATE TABLE IF NOT EXISTS turn_items (
51
+ id TEXT PRIMARY KEY,
52
+ turn_id TEXT NOT NULL REFERENCES turns(id) ON DELETE CASCADE,
53
+ ordinal INTEGER NOT NULL CHECK(ordinal > 0),
54
+ type TEXT NOT NULL CHECK(type IN ('user_message','assistant_message','reasoning','tool_call','tool_result','plan','handoff')),
55
+ status TEXT NOT NULL CHECK(status IN ('in_progress','completed','failed')),
56
+ data TEXT NOT NULL CHECK(json_valid(data)),
57
+ created_at TEXT NOT NULL,
58
+ completed_at TEXT,
59
+ UNIQUE(turn_id, ordinal),
60
+ CHECK((status='in_progress' AND completed_at IS NULL) OR (status<>'in_progress' AND completed_at IS NOT NULL))
61
+ ) STRICT;`;
62
+ var createGoals = `CREATE TABLE IF NOT EXISTS goals (
63
+ id TEXT PRIMARY KEY,
64
+ parent_id TEXT REFERENCES goals(id),
65
+ objective TEXT NOT NULL,
66
+ observation_method TEXT CHECK(observation_method IS NULL OR length(trim(observation_method)) > 0),
67
+ verification_method TEXT CHECK(verification_method IS NULL OR length(trim(verification_method)) > 0),
68
+ owner TEXT NOT NULL,
69
+ phase TEXT NOT NULL CHECK(phase IN ('active','paused','blocked','complete')),
70
+ revision INTEGER NOT NULL CHECK(revision >= 0)
71
+ ) STRICT;`;
72
+ var createWorkRecords = `CREATE TABLE IF NOT EXISTS work_records (
73
+ goal_id TEXT PRIMARY KEY REFERENCES goals(id),
74
+ record_revision INTEGER NOT NULL CHECK(record_revision >= 0),
75
+ goal_revision INTEGER NOT NULL CHECK(goal_revision >= 0),
76
+ content TEXT NOT NULL CHECK(length(trim(content)) > 0),
77
+ updated_by TEXT NOT NULL,
78
+ updated_in_turn TEXT NOT NULL,
79
+ source_wake_id TEXT,
80
+ updated_at TEXT NOT NULL,
81
+ reason TEXT NOT NULL CHECK(length(trim(reason)) > 0),
82
+ evidence TEXT NOT NULL CHECK(json_valid(evidence)),
83
+ last_event_seq INTEGER NOT NULL REFERENCES events(seq)
84
+ ) STRICT;`;
85
+ var createWakes = `CREATE TABLE IF NOT EXISTS wakes (
86
+ id TEXT PRIMARY KEY,
87
+ target_kind TEXT NOT NULL CHECK(target_kind IN ('goal','specialist')),
88
+ agent TEXT NOT NULL,
89
+ specialist_role TEXT CHECK(specialist_role IS NULL OR specialist_role IN ('verifier','audit')),
90
+ trigger_ref TEXT NOT NULL,
91
+ status TEXT NOT NULL CHECK(status IN ('queued','claimed','consumed','cancelled')),
92
+ attempt INTEGER NOT NULL CHECK(attempt >= 0),
93
+ enqueued_seq INTEGER NOT NULL CHECK(enqueued_seq > 0),
94
+ claimed_at TEXT,
95
+ consumed_at TEXT,
96
+ turn_id TEXT REFERENCES turns(id),
97
+ goal_id TEXT REFERENCES goals(id),
98
+ UNIQUE(agent, trigger_ref),
99
+ UNIQUE(turn_id),
100
+ CHECK((status='claimed' AND claimed_at IS NOT NULL) OR status<>'claimed'),
101
+ CHECK((status='consumed' AND consumed_at IS NOT NULL AND turn_id IS NOT NULL) OR status<>'consumed'),
102
+ CHECK((target_kind='goal' AND goal_id IS NOT NULL AND specialist_role IS NULL) OR (target_kind='specialist' AND goal_id IS NULL AND specialist_role IS NOT NULL))
103
+ ) STRICT;`;
104
+ var createWakeTriggers = `CREATE TABLE IF NOT EXISTS wake_triggers (
105
+ wake_id TEXT NOT NULL REFERENCES wakes(id) ON DELETE CASCADE,
106
+ agent TEXT NOT NULL,
107
+ trigger_ref TEXT NOT NULL,
108
+ source TEXT NOT NULL CHECK(source IN ('goal','system')),
109
+ status TEXT NOT NULL CHECK(status IN ('pending','resolved')),
110
+ added_at TEXT NOT NULL,
111
+ resolved_at TEXT,
112
+ PRIMARY KEY(wake_id,trigger_ref),
113
+ UNIQUE(agent,trigger_ref),
114
+ CHECK((status='pending' AND resolved_at IS NULL) OR (status='resolved' AND resolved_at IS NOT NULL))
115
+ ) STRICT;`;
116
+ var indexesAndTriggers = `
117
+ CREATE UNIQUE INDEX IF NOT EXISTS turns_one_active_thread ON turns(thread_id) WHERE status='in_progress';
118
+ CREATE INDEX IF NOT EXISTS turns_thread_started ON turns(thread_id,started_at,id);
119
+ CREATE INDEX IF NOT EXISTS turn_items_turn_ordinal ON turn_items(turn_id,ordinal);
120
+ CREATE UNIQUE INDEX IF NOT EXISTS wakes_one_claimed_agent ON wakes(agent) WHERE status='claimed';
121
+ CREATE INDEX IF NOT EXISTS wakes_queue_order ON wakes(status, enqueued_seq);
122
+ CREATE INDEX IF NOT EXISTS wake_triggers_pending ON wake_triggers(wake_id,status);
123
+ CREATE INDEX IF NOT EXISTS schedule_due ON schedule(status,next_wake_at);
124
+ CREATE INDEX IF NOT EXISTS events_actor_type_seq ON events(actor, type, seq DESC);
125
+ CREATE INDEX IF NOT EXISTS events_stream_seq ON events(stream_id, stream_seq);
126
+ CREATE VIRTUAL TABLE IF NOT EXISTS events_fts USING fts5(actor, type, data, content='events', content_rowid='seq');
127
+
128
+ CREATE TRIGGER IF NOT EXISTS events_no_update BEFORE UPDATE ON events
129
+ BEGIN SELECT RAISE(ABORT, 'events are append-only'); END;
130
+ CREATE TRIGGER IF NOT EXISTS events_no_delete BEFORE DELETE ON events
131
+ BEGIN SELECT RAISE(ABORT, 'events are append-only'); END;
132
+ CREATE TRIGGER IF NOT EXISTS events_fts_insert AFTER INSERT ON events
133
+ BEGIN INSERT INTO events_fts(rowid,actor,type,data) VALUES (new.seq,new.actor,new.type,new.data); END;
134
+
135
+ CREATE TRIGGER IF NOT EXISTS wakes_valid_transition BEFORE UPDATE OF status ON wakes
136
+ WHEN OLD.status <> NEW.status AND NOT (
137
+ (OLD.status = 'queued' AND NEW.status IN ('claimed','cancelled')) OR
138
+ (OLD.status = 'claimed' AND NEW.status IN ('queued','consumed','cancelled'))
139
+ )
140
+ BEGIN SELECT RAISE(ABORT, 'invalid wake transition'); END;
141
+
142
+ CREATE TRIGGER IF NOT EXISTS schedule_valid_transition BEFORE UPDATE OF status ON schedule
143
+ WHEN OLD.status <> NEW.status AND NOT (
144
+ OLD.status = 'pending' AND NEW.status IN ('consumed','cancelled','superseded')
145
+ )
146
+ BEGIN SELECT RAISE(ABORT, 'invalid schedule transition'); END;
147
+
148
+ CREATE TRIGGER IF NOT EXISTS wake_triggers_valid_transition BEFORE UPDATE OF status ON wake_triggers
149
+ WHEN OLD.status <> NEW.status AND NOT (OLD.status='pending' AND NEW.status='resolved')
150
+ BEGIN SELECT RAISE(ABORT, 'invalid Wake trigger transition'); END;
151
+
152
+ CREATE TRIGGER IF NOT EXISTS goals_valid_transition BEFORE UPDATE OF phase ON goals
153
+ WHEN OLD.phase <> NEW.phase AND NOT (
154
+ (OLD.phase = 'active' AND NEW.phase IN ('paused','blocked','complete')) OR
155
+ (OLD.phase = 'paused' AND NEW.phase IN ('active','complete')) OR
156
+ (OLD.phase = 'blocked' AND NEW.phase IN ('active','complete'))
157
+ )
158
+ BEGIN SELECT RAISE(ABORT, 'invalid goal transition'); END;
159
+ `;
160
+ var schema = `
161
+ CREATE TABLE IF NOT EXISTS events (
162
+ seq INTEGER PRIMARY KEY AUTOINCREMENT,
163
+ stream_id TEXT NOT NULL,
164
+ stream_seq INTEGER NOT NULL CHECK(stream_seq > 0),
165
+ ts TEXT NOT NULL,
166
+ actor TEXT NOT NULL,
167
+ type TEXT NOT NULL,
168
+ data TEXT NOT NULL CHECK(json_valid(data)),
169
+ projection_name TEXT,
170
+ ignorable INTEGER CHECK(ignorable IS NULL OR ignorable = 1),
171
+ UNIQUE(stream_id, stream_seq)
172
+ ) STRICT;
173
+ ${createGoals}
174
+ ${createWorkRecords}
175
+ ${createThreads}
176
+ ${createTurns}
177
+ ${createTurnItems}
178
+ CREATE TABLE IF NOT EXISTS schedule (
179
+ id TEXT PRIMARY KEY,
180
+ target_kind TEXT NOT NULL CHECK(target_kind IN ('goal','specialist')),
181
+ agent TEXT NOT NULL,
182
+ specialist_role TEXT CHECK(specialist_role IS NULL OR specialist_role IN ('verifier','audit')),
183
+ next_wake_at TEXT NOT NULL,
184
+ reason TEXT NOT NULL,
185
+ set_by TEXT NOT NULL,
186
+ status TEXT NOT NULL CHECK(status IN ('pending','consumed','cancelled','superseded')),
187
+ resolved_at TEXT,
188
+ goal_id TEXT REFERENCES goals(id),
189
+ CHECK((status='pending' AND resolved_at IS NULL) OR (status<>'pending' AND resolved_at IS NOT NULL)),
190
+ CHECK((target_kind='goal' AND goal_id IS NOT NULL AND specialist_role IS NULL) OR (target_kind='specialist' AND goal_id IS NULL AND specialist_role IS NOT NULL))
191
+ ) STRICT;
192
+ ${createWakes}
193
+ ${createWakeTriggers}
194
+ CREATE TABLE IF NOT EXISTS mailbox (
195
+ id TEXT PRIMARY KEY,
196
+ route_kind TEXT NOT NULL CHECK(route_kind IN ('goal','human_inbox','human_request','specialist_inbox')),
197
+ to_agent TEXT NOT NULL,
198
+ from_agent TEXT NOT NULL,
199
+ specialist_role TEXT CHECK(specialist_role IS NULL OR specialist_role IN ('verifier','audit')),
200
+ level TEXT NOT NULL CHECK(level IN ('fyi','decision','emergency')),
201
+ goal_id TEXT REFERENCES goals(id),
202
+ body TEXT NOT NULL CHECK(json_valid(body)),
203
+ read_at TEXT,
204
+ CHECK((route_kind='goal' AND goal_id IS NOT NULL AND specialist_role IS NULL) OR (route_kind='human_inbox' AND to_agent='ceo' AND from_agent IN ('human','verifier','audit','supervisor') AND goal_id IS NULL AND specialist_role IS NULL) OR (route_kind='human_request' AND to_agent='human' AND from_agent='ceo' AND goal_id IS NULL AND specialist_role IS NULL) OR (route_kind='specialist_inbox' AND goal_id IS NULL AND specialist_role IS NOT NULL))
205
+ ) STRICT;
206
+ ${indexesAndTriggers}`;
207
+ var SystemClock = class {
208
+ now() {
209
+ return /* @__PURE__ */ new Date();
210
+ }
211
+ };
212
+ var SqliteLedger = class {
213
+ db;
214
+ #faultInjector;
215
+ #clock;
216
+ constructor(path = ":memory:", options = {}) {
217
+ this.db = new DatabaseSync(path);
218
+ this.db.exec(`PRAGMA journal_mode = WAL; PRAGMA foreign_keys = ON; PRAGMA busy_timeout = ${options.busyTimeoutMs ?? 5e3};`);
219
+ this.#faultInjector = options.faultInjector;
220
+ this.#clock = options.clock ?? new SystemClock();
221
+ const version = Number(this.db.prepare("PRAGMA user_version").get().user_version);
222
+ if (version > SQLITE_SCHEMA_VERSION) {
223
+ this.db.close();
224
+ throw new Error(`ledger schema ${version} is newer than supported schema ${SQLITE_SCHEMA_VERSION}`);
225
+ }
226
+ if (version > 0 && version < SQLITE_SCHEMA_VERSION) {
227
+ this.db.close();
228
+ throw new Error(`ledger schema ${version} predates runtime schema ${SQLITE_SCHEMA_VERSION}; recreate this development workspace`);
229
+ }
230
+ if (version === 0) {
231
+ this.db.exec(schema);
232
+ this.db.exec(`PRAGMA user_version = ${SQLITE_SCHEMA_VERSION}`);
233
+ } else {
234
+ this.db.exec(schema);
235
+ }
236
+ }
237
+ close() {
238
+ this.db.close();
239
+ }
240
+ appendEvent(input) {
241
+ this.#assertRawEvent(input);
242
+ return this.#transaction(() => this.#insertEvent(input));
243
+ }
244
+ appendEvents(inputs) {
245
+ for (const input of inputs)
246
+ this.#assertRawEvent(input);
247
+ return this.#transaction(() => inputs.map((input) => this.#insertEvent(input)));
248
+ }
249
+ readStream(streamId, fromStreamSeq = 1) {
250
+ return this.db.prepare("SELECT * FROM events WHERE stream_id=? AND stream_seq>=? ORDER BY stream_seq").all(streamId, fromStreamSeq).map(mapEvent);
251
+ }
252
+ putThread(thread, actor) {
253
+ return this.#transaction(() => this.#admitThread(thread, actor));
254
+ }
255
+ putTurn(turn, actor) {
256
+ if (!turn.id.trim() || !turn.threadId.trim())
257
+ throw new Error("turn id and thread are required");
258
+ this.#assertTurnShape(turn);
259
+ if (turn.status === "in_progress" && (!turn.leaseUntil || !turn.leaseToken) || turn.status !== "in_progress" && (turn.leaseUntil !== null || turn.leaseToken !== null))
260
+ throw new Error("Turn execution ownership does not match status");
261
+ const thread = this.thread(turn.threadId);
262
+ if (!thread)
263
+ throw new Error("Turn Thread does not exist");
264
+ const current = this.turn(turn.id);
265
+ if (current) {
266
+ if (turn.status !== "in_progress")
267
+ throw new Error("terminal Turn state must be committed through finishTurn or commitHandoff");
268
+ if (current.threadId !== turn.threadId || current.triggerKind !== turn.triggerKind)
269
+ throw new Error("turn identity cannot change");
270
+ if (current.runnerProfileId !== turn.runnerProfileId)
271
+ throw new Error("turn Runner Profile cannot change");
272
+ if (current.runnerPid !== turn.runnerPid)
273
+ throw new Error("Turn Runner process may change only through attach/release APIs");
274
+ if (current.status !== "in_progress")
275
+ throw new Error("terminal turn cannot change");
276
+ if (current.goalId !== turn.goalId || current.goalRevision !== turn.goalRevision)
277
+ throw new Error("Turn Goal commitment cannot change through putTurn");
278
+ if (turn.attempt !== current.attempt && turn.attempt !== current.attempt + 1)
279
+ throw new Error("turn attempt must stay current or increment by one");
280
+ if (turn.status === "in_progress" && turn.endedAt !== null || turn.status !== "in_progress" && turn.endedAt === null)
281
+ throw new Error("turn terminal time does not match status");
282
+ } else
283
+ throw new Error("new Human Turns require atomic admitHumanTurn; automatic Turns require a claimed Wake");
284
+ return this.#project("turns", turn, actor, current ? `turn.${turn.status}` : "turn.started", void 0, void 0, `turn:${turn.id}`);
285
+ }
286
+ admitHumanTurn(request) {
287
+ return this.#transaction(() => {
288
+ const { thread, turn, messageItem } = request;
289
+ this.#admitThread(thread, "supervisor");
290
+ if (thread.agent !== "ceo" || thread.role !== "ceo" || thread.parentThreadId !== null)
291
+ throw new Error("Human Turn requires the canonical CEO Thread");
292
+ const messageData = messageItem.data && typeof messageItem.data === "object" && !Array.isArray(messageItem.data) ? messageItem.data : null;
293
+ if (!messageData || typeof messageData.text !== "string" || !messageData.text.trim())
294
+ throw new Error("Human Turn admission requires a readable user message");
295
+ const active = this.activeTurn(thread.id);
296
+ if (request.replaceTurnId === null && active)
297
+ throw new Error("Human Turn admission requires the active Turn to be replaced");
298
+ if (request.replaceTurnId !== null && active?.id !== request.replaceTurnId)
299
+ throw new Error("Human Turn replacement lost its active Turn race");
300
+ let goal = null;
301
+ if (request.rootGoal) {
302
+ goal = normalizeGoal(request.rootGoal);
303
+ assertGoalSnapshot(goal);
304
+ if (goal.parentId !== null || goal.owner !== "ceo" || goal.phase !== "active" || goal.revision !== 0)
305
+ throw new Error("Human Goal admission requires a new active CEO Root Goal");
306
+ if (this.#getGoal(goal.id))
307
+ throw new Error("Goal id is already in use");
308
+ if (this.goals().some((candidate) => candidate.parentId === null && candidate.phase !== "complete"))
309
+ throw new Error("CEO already has an unfinished Root Goal");
310
+ this.#assertOwnerSeparation(goal.id, null, goal.owner);
311
+ this.#recordGoalChange(goal, null, "human", void 0, { operation: "create", reason: "Human established a durable Root Goal", evidence: [], authority: { kind: "human" } });
312
+ this.#createWorkRecord(goal, "human", `goal:create:${goal.id}`);
313
+ this.#faultInjector?.("after_human_goal_before_turn");
314
+ }
315
+ this.#assertTurnShape(turn);
316
+ if (turn.threadId !== thread.id || turn.triggerKind !== "user_message" || turn.status !== "in_progress" || turn.attempt !== 1 || turn.endedAt !== null || !turn.leaseUntil || !turn.leaseToken || turn.runnerPid !== null)
317
+ throw new Error("invalid Human Turn admission");
318
+ if (goal) {
319
+ if (turn.goalId !== goal.id || turn.goalRevision !== goal.revision)
320
+ throw new Error("Human Goal Turn does not commit to its admitted Root Goal");
321
+ } else if (turn.goalId !== null) {
322
+ const existing = this.#getGoal(turn.goalId);
323
+ if (!existing || existing.parentId !== null || existing.owner !== "ceo" || existing.phase !== "active" || existing.revision !== turn.goalRevision)
324
+ throw new Error("Human Turn commitment is not the current active Root Goal");
325
+ }
326
+ let replaced = null;
327
+ if (active) {
328
+ replaced = this.#finishTurn(active, "interrupted", { message: "interrupted by Human" }, turn.startedAt, "human", []);
329
+ this.#faultInjector?.("after_human_replacement_before_turn");
330
+ }
331
+ this.#recordProjection("turns", turn, "human", "turn.started", void 0, turn.startedAt, void 0, `turn:${turn.id}`);
332
+ if (messageItem.turnId !== turn.id || messageItem.ordinal !== 1 || messageItem.type !== "user_message" || messageItem.status !== "completed" || messageItem.createdAt !== turn.startedAt || messageItem.completedAt !== turn.startedAt)
333
+ throw new Error("Human Turn admission requires one completed initial user message");
334
+ this.#admitTurnItem(messageItem, "human");
335
+ return { turn, replacedTurn: replaced, goal };
336
+ });
337
+ }
338
+ commitTurnToGoal(turnId, binding, actor) {
339
+ if (actor !== "supervisor")
340
+ throw new Error("only supervisor may commit a Turn to a Goal");
341
+ return this.#transaction(() => {
342
+ const current = this.#requiredTurn(turnId);
343
+ if (current.status !== "in_progress")
344
+ throw new Error("only an active Turn may commit to a Goal");
345
+ if (current.goalId !== null) {
346
+ if (current.goalId === binding.goalId && current.goalRevision === binding.goalRevision)
347
+ return current;
348
+ throw new Error("Turn Goal commitment is immutable");
349
+ }
350
+ const thread = this.thread(current.threadId);
351
+ const goal = this.#getGoal(binding.goalId);
352
+ if (!thread || !goal || goal.phase !== "active" || goal.owner !== thread.agent || goal.revision !== binding.goalRevision)
353
+ throw new Error("Turn commitment must target the current active Goal owned by its Agent");
354
+ if (thread.role === "ceo" && goal.parentId !== null || thread.role === "child" && goal.parentId === null || thread.role === "verifier" || thread.role === "audit")
355
+ throw new Error("Thread role cannot commit to this Goal");
356
+ const next = { ...current, goalId: goal.id, goalRevision: goal.revision };
357
+ this.#recordProjection("turns", next, "supervisor", "turn.goal_committed", void 0, this.#now(), void 0, `turn:${turnId}`);
358
+ return next;
359
+ });
360
+ }
361
+ putTurnItem(item, actor) {
362
+ return this.#transaction(() => this.#admitTurnItem(item, actor));
363
+ }
364
+ thread(id) {
365
+ const row = this.db.prepare("SELECT * FROM threads WHERE id=?").get(id);
366
+ return row ? mapThread(row) : null;
367
+ }
368
+ threads() {
369
+ return this.db.prepare("SELECT * FROM threads ORDER BY created_at,id").all().map(mapThread);
370
+ }
371
+ turn(id) {
372
+ const row = this.db.prepare("SELECT * FROM turns WHERE id=?").get(id);
373
+ return row ? mapTurn(row) : null;
374
+ }
375
+ turns(threadId) {
376
+ return (threadId ? this.db.prepare("SELECT * FROM turns WHERE thread_id=? ORDER BY rowid").all(threadId) : this.db.prepare("SELECT * FROM turns ORDER BY rowid").all()).map(mapTurn);
377
+ }
378
+ turnItems(turnId) {
379
+ return this.db.prepare("SELECT * FROM turn_items WHERE turn_id=? ORDER BY ordinal").all(turnId).map(mapTurnItem);
380
+ }
381
+ activeTurn(threadId) {
382
+ const row = this.db.prepare("SELECT * FROM turns WHERE thread_id=? AND status='in_progress'").get(threadId);
383
+ return row ? mapTurn(row) : null;
384
+ }
385
+ putGoal(goal, actor, wakeId, change) {
386
+ const normalized = normalizeGoal(goal);
387
+ assertGoalSnapshot(normalized);
388
+ if (change?.idempotencyKey) {
389
+ const row = this.db.prepare("SELECT * FROM events WHERE type='goal.changed' AND actor=? AND json_extract(data,'$.idempotencyKey')=? ORDER BY seq LIMIT 1").get(actor, change.idempotencyKey);
390
+ if (row) {
391
+ const event = mapEvent(row);
392
+ const data = event.data;
393
+ const same = isDeepStrictEqual(data.snapshot, normalized) && data.operation === change.operation && data.reason === change.reason.trim() && isDeepStrictEqual(data.evidence, change.evidence) && (!change.authority || isDeepStrictEqual(data.authority, change.authority));
394
+ if (!same)
395
+ throw new Error("Goal idempotency key was reused with a different mutation");
396
+ return event;
397
+ }
398
+ }
399
+ const current = this.#getGoal(normalized.id);
400
+ if (current) {
401
+ if (normalized.revision !== current.revision + 1)
402
+ throw new Error("goal revision CAS failed");
403
+ if (current.phase === "complete")
404
+ throw new Error("completed goal cannot be modified");
405
+ if (normalized.parentId !== current.parentId)
406
+ throw new Error("goal reparenting is not supported");
407
+ if (normalized.phase === "complete")
408
+ throw new Error("goal completion requires reason and evidence");
409
+ const definitionChanged = normalized.objective !== current.objective || normalized.observationMethod !== current.observationMethod || normalized.verificationMethod !== current.verificationMethod;
410
+ if (current.phase !== normalized.phase && (current.owner !== normalized.owner || definitionChanged) || current.owner !== normalized.owner && definitionChanged)
411
+ throw new Error("Goal mutation must express exactly one lifecycle operation");
412
+ if (normalized.objective !== current.objective && (current.observationMethod !== null && normalized.observationMethod === current.observationMethod || current.verificationMethod !== null && normalized.verificationMethod === current.verificationMethod))
413
+ throw new Error("objective revision must replace or invalidate observation and verification methods");
414
+ assertGoalTransition(current.phase, normalized.phase);
415
+ this.#assertGoalAuthority(current.parentId, actor);
416
+ } else {
417
+ if (normalized.revision !== 0)
418
+ throw new Error("new goal revision must be 0");
419
+ this.#assertGoalAuthority(normalized.parentId, actor);
420
+ }
421
+ this.#assertOwnerSeparation(normalized.id, normalized.parentId, normalized.owner);
422
+ return this.#transaction(() => {
423
+ const event = this.#recordGoalChange(normalized, current, actor, wakeId, change);
424
+ if (!current)
425
+ this.#createWorkRecord(normalized, actor, wakeId ?? `goal:create:${normalized.id}`, wakeId);
426
+ return event;
427
+ });
428
+ }
429
+ updateWorkRecord(request, actor) {
430
+ if (!request.turnId.trim() || !request.reason.trim() || !request.content.trim())
431
+ throw new Error("work record turn, reason and content are required");
432
+ this.#assertEvidenceExists(request.evidence);
433
+ const goal = this.#getGoal(request.goalId);
434
+ if (!goal)
435
+ throw new Error("work record goal does not exist");
436
+ if (goal.revision !== request.goalRevision)
437
+ throw new Error("work record goal revision is stale");
438
+ this.#assertWorkRecordAuthority(goal, actor);
439
+ const turn = this.turn(request.turnId);
440
+ if (!turn || turn.status !== "in_progress" || turn.goalId !== goal.id || turn.goalRevision !== request.goalRevision)
441
+ throw new Error("work record update does not match an active committed Turn");
442
+ const current = this.workRecord(goal.id);
443
+ if (!current)
444
+ throw new Error("work record does not exist");
445
+ if (current.recordRevision !== request.expectedRevision)
446
+ throw new Error("work record revision CAS failed");
447
+ if (current.content === request.content)
448
+ throw new Error("work record content is unchanged");
449
+ return this.#transaction(() => this.#recordWorkRecord({
450
+ ...current,
451
+ recordRevision: current.recordRevision + 1,
452
+ goalRevision: goal.revision,
453
+ content: request.content,
454
+ updatedBy: actor,
455
+ updatedInTurn: request.turnId,
456
+ sourceWakeId: request.sourceWakeId ?? null,
457
+ updatedAt: this.#now(),
458
+ reason: request.reason,
459
+ evidence: request.evidence
460
+ }, "work_record.updated"));
461
+ }
462
+ commitDelegation(request, actor, wakeId) {
463
+ const existing = this.#delegationResult(request.id);
464
+ if (existing) {
465
+ const event = this.events().find((candidate) => candidate.type === "delegation.created" && candidate.data.delegationId === request.id);
466
+ const prior = event?.data?.request;
467
+ if (event?.actor !== actor || !isDeepStrictEqual(prior, withoutSourceTurn(request)))
468
+ throw new Error("delegation id was reused with a different request");
469
+ return existing;
470
+ }
471
+ if (!request.id.trim() || !request.reason.trim())
472
+ throw new Error("delegation id and reason are required");
473
+ if (request.childGoal.owner === actor)
474
+ throw new Error("delegate to a distinct worker agent; use goal.put for self-owned subgoals");
475
+ if (!request.childGoal.id.trim() || !request.childGoal.objective.trim() || !request.childGoal.observationMethod.trim() || !request.childGoal.verificationMethod.trim() || !request.childGoal.owner.trim())
476
+ throw new Error("delegation child goal is incomplete");
477
+ this.#assertEvidenceExists(request.evidence);
478
+ const parent = this.#getGoal(request.parentGoalId);
479
+ if (!parent)
480
+ throw new Error("delegation parent goal does not exist");
481
+ if (parent.revision !== request.expectedParentRevision)
482
+ throw new Error("delegation parent Goal revision is stale");
483
+ if (parent.owner !== actor)
484
+ throw new Error("only the parent goal owner may delegate");
485
+ if (parent.phase !== "active")
486
+ throw new Error("delegation parent goal must be active");
487
+ if (this.#getGoal(request.childGoal.id))
488
+ throw new Error("delegation child goal already exists");
489
+ return this.#transaction(() => {
490
+ const goal = normalizeGoal({ ...request.childGoal, parentId: request.parentGoalId, phase: "active", revision: 0 });
491
+ const mail = {
492
+ id: `delegation-mail:${request.id}`,
493
+ to: goal.owner,
494
+ from: actor,
495
+ level: "decision",
496
+ ...goalRoute(goal.id),
497
+ body: { type: "delegation", delegationId: request.id, goalId: goal.id, parentGoalId: request.parentGoalId, objective: goal.objective, observationMethod: goal.observationMethod, verificationMethod: goal.verificationMethod ?? null, brief: request.brief, reason: request.reason, evidence: request.evidence },
498
+ readAt: null
499
+ };
500
+ const wakeBase = {
501
+ id: `delegation-wake:${request.id}`,
502
+ ...goalAutomaticTarget(goal.owner, goal.id),
503
+ triggerRef: `delegation:${request.id}`,
504
+ status: "queued",
505
+ attempt: 0,
506
+ enqueuedSeq: 0,
507
+ claimedAt: null,
508
+ consumedAt: null,
509
+ turnId: null
510
+ };
511
+ this.#insertEvent({
512
+ streamId: wakeId ? wakeStream(wakeId) : controlStream("delegations"),
513
+ ts: this.#now(),
514
+ actor,
515
+ type: "delegation.created",
516
+ data: { delegationId: request.id, parentGoalId: request.parentGoalId, goalId: goal.id, mailId: mail.id, wakeId: wakeBase.id, reason: request.reason, evidence: request.evidence, request: withoutSourceTurn(request) }
517
+ });
518
+ this.#faultInjector?.("after_delegation_event");
519
+ this.#recordGoalChange(goal, null, actor, wakeId, { operation: "create", reason: request.reason, evidence: request.evidence, authority: { kind: "parent_goal", goalId: parent.id, goalRevision: parent.revision }, ...request.sourceTurnId ? { sourceTurnId: request.sourceTurnId } : {}, ...wakeId ? { sourceWakeId: wakeId } : {}, idempotencyKey: request.id });
520
+ this.#createWorkRecord(goal, actor, wakeId ?? `delegation:${request.id}`, wakeId);
521
+ this.#assertNewMail(mail, actor);
522
+ this.#recordProjection("mailbox", mail, actor, "mail.put", wakeId);
523
+ const wake = this.#admitWake(wakeBase, "supervisor", false).wake;
524
+ return { delegationId: request.id, goal, mail, wake };
525
+ });
526
+ }
527
+ commitReassignment(request, actor, wakeId) {
528
+ const existing = this.#reassignmentResult(request.id);
529
+ if (existing) {
530
+ const event = this.events().find((candidate) => candidate.type === "goal.reassigned" && candidate.data.reassignmentId === request.id);
531
+ const prior = event?.data?.request;
532
+ if (event?.actor !== actor || !isDeepStrictEqual(prior, withoutSourceTurn(request)))
533
+ throw new Error("reassignment id was reused with a different request");
534
+ return existing;
535
+ }
536
+ if (!request.id.trim() || !request.newOwner.trim() || !request.reason.trim())
537
+ throw new Error("reassignment id, owner and reason are required");
538
+ this.#assertEvidenceExists(request.evidence);
539
+ const current = this.#getGoal(request.goalId);
540
+ if (!current)
541
+ throw new Error("reassignment goal does not exist");
542
+ if (current.revision !== request.expectedGoalRevision)
543
+ throw new Error("reassignment Goal revision is stale");
544
+ if (!current.parentId)
545
+ throw new Error("root goals cannot be reassigned by CEO");
546
+ this.#assertGoalAuthority(current.parentId, actor);
547
+ if (current.phase === "complete")
548
+ throw new Error("completed goals cannot be reassigned");
549
+ if (current.owner === request.newOwner)
550
+ throw new Error("reassignment owner is unchanged");
551
+ this.#assertOwnerSeparation(current.id, current.parentId, request.newOwner);
552
+ return this.#transaction(() => {
553
+ const goal = { ...current, owner: request.newOwner, revision: current.revision + 1 };
554
+ const mail = [{ id: `reassignment-new-mail:${request.id}`, to: goal.owner, from: actor, level: "decision", ...goalRoute(goal.id), body: { type: "reassignment", reassignmentId: request.id, role: "new_owner", previousOwner: current.owner, brief: request.brief, reason: request.reason, evidence: request.evidence }, readAt: null }];
555
+ const wakeBase = goal.phase === "active" ? { id: `reassignment-wake:${request.id}`, ...goalAutomaticTarget(goal.owner, goal.id), triggerRef: `reassignment:${request.id}`, status: "queued", attempt: 0, enqueuedSeq: 0, claimedAt: null, consumedAt: null, turnId: null } : null;
556
+ this.#insertEvent({ streamId: wakeId ? wakeStream(wakeId) : controlStream("reassignments"), ts: this.#now(), actor, type: "goal.reassigned", data: { reassignmentId: request.id, goalId: goal.id, oldOwner: current.owner, newOwner: goal.owner, mailIds: mail.map((item) => item.id), wakeId: wakeBase?.id ?? null, reason: request.reason, evidence: request.evidence, request: withoutSourceTurn(request) } });
557
+ this.#faultInjector?.("after_reassignment_event");
558
+ this.#recordGoalChange(goal, current, actor, wakeId, { operation: "reassign", reason: request.reason, evidence: request.evidence, authority: { kind: "parent_goal", goalId: current.parentId, goalRevision: this.#getGoal(current.parentId).revision }, ...request.sourceTurnId ? { sourceTurnId: request.sourceTurnId } : {}, ...wakeId ? { sourceWakeId: wakeId } : {}, idempotencyKey: request.id });
559
+ for (const item of mail) {
560
+ this.#assertNewMail(item, actor);
561
+ this.#recordProjection("mailbox", item, actor, "mail.put", wakeId);
562
+ }
563
+ const wake = wakeBase ? this.#admitWake(wakeBase, "supervisor", false).wake : null;
564
+ return { reassignmentId: request.id, goal, mail, wake };
565
+ });
566
+ }
567
+ completeGoal(request, actor, wakeId) {
568
+ if (!request.reason.trim())
569
+ throw new Error("goal completion reason is required");
570
+ this.#assertEvidenceExists(request.evidence);
571
+ const current = this.#getGoal(request.goalId);
572
+ if (!current)
573
+ throw new Error("goal does not exist");
574
+ if (current.phase === "complete") {
575
+ const decision = this.readStream(goalStream(current.id)).findLast((event) => event.type === "goal.completion_decided");
576
+ const data = decision?.data;
577
+ if (decision?.actor === actor && data.revision === request.revision && data.reason === request.reason && JSON.stringify(data.evidence) === JSON.stringify(request.evidence))
578
+ return current;
579
+ throw new Error("completed goal cannot accept a different completion decision");
580
+ }
581
+ if (request.revision !== current.revision)
582
+ throw new Error("goal completion revision is stale");
583
+ if (current.observationMethod === null)
584
+ throw new Error("goal completion requires an observation method");
585
+ if (current.verificationMethod === null || current.verificationMethod === void 0)
586
+ throw new Error("goal completion requires a verification method");
587
+ this.#assertGoalAuthority(current.parentId, actor);
588
+ if (current.parentId === null && this.#hasNonCompleteDescendant(current.id))
589
+ throw new Error("root goal cannot complete while descendants remain non-complete");
590
+ const source = this.db.prepare("SELECT seq FROM events WHERE stream_id=? AND type='goal.changed' AND json_extract(data,'$.snapshot.revision')=? ORDER BY seq DESC LIMIT 1").get(goalStream(current.id), current.revision);
591
+ if (!source)
592
+ throw new Error("goal revision has no source event");
593
+ if (request.evidence.some((seq) => seq <= source.seq))
594
+ throw new Error("goal completion evidence predates the current revision");
595
+ return this.#transaction(() => {
596
+ this.#insertEvent({ streamId: goalStream(current.id), ts: this.#now(), actor, type: "goal.completion_decided", data: { goalId: current.id, revision: current.revision, observationMethod: current.observationMethod, verificationMethod: current.verificationMethod ?? null, reason: request.reason, evidence: request.evidence } });
597
+ const next = { ...current, phase: "complete", revision: current.revision + 1 };
598
+ this.#recordGoalChange(next, current, actor, wakeId, { operation: "complete", reason: request.reason, evidence: request.evidence, authority: this.#goalAuthority(current.parentId, actor), ...request.sourceTurnId ? { sourceTurnId: request.sourceTurnId } : {}, ...wakeId ? { sourceWakeId: wakeId } : {} });
599
+ return next;
600
+ });
601
+ }
602
+ putSchedule(value, actor, wakeId) {
603
+ return this.#transaction(() => this.#admitSchedule(value, actor, wakeId));
604
+ }
605
+ consumeSchedule(id, wake, now) {
606
+ const current = this.#requiredSchedule(id);
607
+ if (current.status !== "pending")
608
+ throw new Error("only a pending Schedule may be consumed");
609
+ if (wake.targetKind !== current.targetKind || wake.agent !== current.agent || wake.specialistRole !== current.specialistRole || wake.triggerRef !== `${current.id}@${current.nextWakeAt}` || wake.goalId !== current.goalId)
610
+ throw new Error("scheduled Wake does not match Schedule");
611
+ if (wake.status !== "queued" || wake.attempt !== 0 || wake.claimedAt || wake.consumedAt || wake.turnId)
612
+ throw new Error("scheduled Wake must be pristine");
613
+ return this.#transaction(() => {
614
+ const exact = this.wakeByTrigger(wake.agent, wake.triggerRef);
615
+ let stored;
616
+ if (exact) {
617
+ if (exact.status !== "queued" || exact.targetKind !== wake.targetKind || exact.specialistRole !== wake.specialistRole || exact.goalId !== wake.goalId)
618
+ throw new Error("scheduled Wake trigger is already terminal or bound differently");
619
+ stored = exact;
620
+ } else {
621
+ const queued = this.wakes().find((candidate) => candidate.agent === wake.agent && candidate.status === "queued" && candidate.targetKind === wake.targetKind && candidate.specialistRole === wake.specialistRole && candidate.goalId === wake.goalId);
622
+ if (queued) {
623
+ stored = queued;
624
+ this.#admitWakeTrigger(queued.id, wake.triggerRef, "supervisor", now);
625
+ } else
626
+ stored = this.#admitWake(wake, "supervisor", false).wake;
627
+ }
628
+ const schedule = { ...current, status: "consumed", resolvedAt: now };
629
+ this.#recordProjection("schedule", schedule, "supervisor", "schedule.consumed", void 0, now);
630
+ return { schedule, wake: stored };
631
+ });
632
+ }
633
+ cancelSchedule(id, now) {
634
+ const current = this.#requiredSchedule(id);
635
+ if (current.status !== "pending")
636
+ throw new Error("only a pending Schedule may be cancelled");
637
+ const next = { ...current, status: "cancelled", resolvedAt: now };
638
+ this.#project("schedule", next, "supervisor", "schedule.cancelled", void 0, now);
639
+ return next;
640
+ }
641
+ supersedeSchedule(id, now) {
642
+ const current = this.#requiredSchedule(id);
643
+ if (current.status !== "pending")
644
+ throw new Error("only a pending Schedule may be superseded");
645
+ const next = { ...current, status: "superseded", resolvedAt: now };
646
+ this.#project("schedule", next, "supervisor", "schedule.superseded", void 0, now);
647
+ return next;
648
+ }
649
+ enqueueWake(input, actor) {
650
+ return this.#transaction(() => {
651
+ const result = this.#admitWake(input, actor, true);
652
+ return { event: result.event, created: result.created };
653
+ });
654
+ }
655
+ addWakeTrigger(wakeId, triggerRef, actor) {
656
+ return this.#transaction(() => this.#admitWakeTrigger(wakeId, triggerRef, actor, this.#now()));
657
+ }
658
+ claimNextWake(now) {
659
+ return this.#transaction(() => {
660
+ const row = this.db.prepare(`SELECT * FROM wakes w WHERE status='queued' AND NOT EXISTS (
661
+ SELECT 1 FROM wakes active WHERE active.agent=w.agent AND active.status='claimed'
662
+ ) AND NOT EXISTS (
663
+ SELECT 1 FROM turns WHERE trigger_kind='user_message' AND status='in_progress'
664
+ ) AND NOT EXISTS (
665
+ SELECT 1 FROM turns t JOIN threads th ON th.id=t.thread_id WHERE th.agent=w.agent AND t.status='in_progress'
666
+ ) AND NOT EXISTS (
667
+ SELECT 1 FROM turns t JOIN threads th ON th.id=t.thread_id WHERE th.agent=w.agent AND t.runner_pid IS NOT NULL
668
+ ) ORDER BY enqueued_seq LIMIT 1`).get();
669
+ if (!row)
670
+ return null;
671
+ const current = mapWake(row);
672
+ const next = { ...current, status: "claimed", claimedAt: now, attempt: current.attempt + 1 };
673
+ this.#recordProjection("wakes", next, "supervisor", "wake.claimed", next.id, now);
674
+ return next;
675
+ });
676
+ }
677
+ startTurnFromWake(id, turn, now) {
678
+ const wake = this.#requiredWake(id);
679
+ if (wake.status !== "claimed")
680
+ throw new Error("only a claimed Wake may start a Turn");
681
+ if (this.turn(turn.id))
682
+ throw new Error("Wake Turn already exists");
683
+ this.#assertTurnShape(turn);
684
+ if (turn.status !== "in_progress" || turn.attempt !== 1 || !turn.leaseUntil || !turn.leaseToken || turn.triggerKind !== "wake" || turn.runnerPid !== null)
685
+ throw new Error("Wake must start an owned Turn at attempt one without a Runner process");
686
+ const thread = this.thread(turn.threadId);
687
+ if (!thread || thread.agent !== wake.agent)
688
+ throw new Error("Wake Turn agent does not match");
689
+ if (!sameWakeTurnTarget(wake, turn, thread))
690
+ throw new Error("Wake Turn target does not match");
691
+ return this.#transaction(() => {
692
+ if (this.db.prepare("SELECT 1 FROM turns WHERE trigger_kind='user_message' AND status='in_progress' LIMIT 1").get())
693
+ throw new Error("automatic Wake cannot start while a user Turn is in progress");
694
+ this.#assertCurrentAutomaticTurn(turn, thread);
695
+ this.#recordProjection("turns", turn, "supervisor", "turn.started", void 0, now, void 0, `turn:${turn.id}`);
696
+ this.#resolveWakeTriggers(id, now);
697
+ const next = { ...wake, status: "consumed", consumedAt: now, turnId: turn.id };
698
+ this.#recordProjection("wakes", next, "supervisor", "wake.consumed", id, now);
699
+ return next;
700
+ });
701
+ }
702
+ releaseWake(id, now) {
703
+ const current = this.#requiredWake(id);
704
+ if (current.status !== "claimed")
705
+ throw new Error("only a claimed Wake may be released");
706
+ const next = { ...current, status: "queued", claimedAt: null };
707
+ this.#project("wakes", next, "supervisor", "wake.released", id, now);
708
+ return next;
709
+ }
710
+ cancelWake(id, now) {
711
+ const current = this.#requiredWake(id);
712
+ return this.#transaction(() => this.#cancelWakeProjection(current, now));
713
+ }
714
+ attachTurnProcess(id, leaseToken, pid) {
715
+ const current = this.#requiredTurn(id);
716
+ this.#assertTurnLease(current, leaseToken);
717
+ if (current.status !== "in_progress" || current.runnerPid !== null || !Number.isInteger(pid) || pid <= 0)
718
+ throw new Error("a positive Runner pid may attach once to an active Turn");
719
+ return this.#transaction(() => {
720
+ const next = { ...current, runnerPid: pid };
721
+ this.#recordProjection("turns", next, "supervisor", "turn.process_attached", void 0, this.#now(), void 0, `turn:${id}`);
722
+ return next;
723
+ });
724
+ }
725
+ renewTurnLease(id, leaseToken, leaseUntil, now) {
726
+ const current = this.#requiredTurn(id);
727
+ this.#assertTurnLease(current, leaseToken);
728
+ if (current.status !== "in_progress")
729
+ throw new Error("only an active Turn lease may be renewed");
730
+ if (leaseUntil <= now)
731
+ throw new Error("renewed lease must expire in the future");
732
+ this.putTurn({ ...current, leaseUntil }, "supervisor");
733
+ return this.#requiredTurn(id);
734
+ }
735
+ appendTurnEvent(input, leaseToken) {
736
+ return this.#transaction(() => {
737
+ this.#assertRawEvent(input);
738
+ if (!input.streamId.startsWith("turn:"))
739
+ throw new Error("runner event requires a Turn stream");
740
+ const turn = this.#requiredTurn(input.streamId.slice("turn:".length));
741
+ this.#assertTurnLease(turn, leaseToken);
742
+ if (turn.status !== "in_progress" || !turn.leaseUntil || input.ts > turn.leaseUntil)
743
+ throw new Error("stale runner event rejected");
744
+ return this.#insertEvent(input);
745
+ });
746
+ }
747
+ repairTurnAttempt(id, reason, now, actor) {
748
+ const current = this.#requiredTurn(id);
749
+ if (current.status !== "in_progress")
750
+ throw new Error("only an active Turn attempt may be repaired");
751
+ return this.#transaction(() => this.#repairOpenTurnItems(id, reason, now, actor));
752
+ }
753
+ finishTurn(id, status, error, now, actor, mailIds = []) {
754
+ const current = this.#requiredTurn(id);
755
+ return this.#transaction(() => this.#finishTurn(current, status, error, now, actor, mailIds));
756
+ }
757
+ releaseTurnProcess(id, actor) {
758
+ if (actor !== "supervisor")
759
+ throw new Error("only supervisor may release Runner process ownership");
760
+ return this.#transaction(() => {
761
+ const current = this.#requiredTurn(id);
762
+ if (current.runnerPid === null)
763
+ throw new Error("Turn has no Runner process ownership to release");
764
+ const next = { ...current, runnerPid: null };
765
+ this.#recordProjection("turns", next, actor, "turn.process_released", void 0, this.#now(), void 0, `turn:${id}`);
766
+ return next;
767
+ });
768
+ }
769
+ putMail(mail, actor, wakeId) {
770
+ return this.putMails([mail], actor, wakeId)[0];
771
+ }
772
+ putMails(mails, actor, wakeId) {
773
+ const ids = /* @__PURE__ */ new Set();
774
+ const prior = /* @__PURE__ */ new Map();
775
+ for (const mail of mails) {
776
+ this.#assertMailRequest(mail, actor);
777
+ if (ids.has(mail.id))
778
+ throw new Error("mail batch contains a duplicate id");
779
+ ids.add(mail.id);
780
+ const row = this.db.prepare("SELECT * FROM mailbox WHERE id=?").get(mail.id);
781
+ if (!row)
782
+ continue;
783
+ const current = mapMail(row);
784
+ if (current.routeKind !== mail.routeKind || current.to !== mail.to || current.from !== mail.from || current.specialistRole !== mail.specialistRole || current.level !== mail.level || current.goalId !== mail.goalId || !isDeepStrictEqual(current.body, mail.body))
785
+ throw new Error("mail id was reused with different content");
786
+ const event = this.db.prepare("SELECT * FROM events WHERE type='mail.put' AND json_extract(data,'$.snapshot.id')=? ORDER BY seq LIMIT 1").get(mail.id);
787
+ if (!event)
788
+ throw new Error("mail projection has no source event");
789
+ prior.set(mail.id, mapEvent(event));
790
+ }
791
+ return this.#transaction(() => mails.map((mail) => prior.get(mail.id) ?? this.#recordProjection("mailbox", mail, actor, "mail.put", wakeId)));
792
+ }
793
+ commitHandoff(commit) {
794
+ return this.#transaction(() => {
795
+ const turn = this.#requiredTurn(commit.turnId);
796
+ const thread = this.thread(turn.threadId);
797
+ if (turn.status !== "in_progress" || thread?.agent !== commit.agent)
798
+ throw new Error("handoff does not match an active Turn");
799
+ if (turn.goalId === null)
800
+ throw new Error("only a committed Turn may commit Handoff");
801
+ const goal = this.#getGoal(turn.goalId);
802
+ if (!goal || goal.owner !== commit.agent || goal.phase !== "active" || goal.revision !== turn.goalRevision)
803
+ throw new Error("Handoff Turn Goal commitment is stale");
804
+ const record = this.workRecord(goal.id);
805
+ if (!record || record.updatedInTurn !== turn.id || record.goalRevision !== goal.revision || record.recordRevision < 1)
806
+ throw new Error("committed Turn must update its Work Record before Handoff");
807
+ assertHandoff(commit.output.handoff);
808
+ if (commit.output.handoff.goalId !== goal.id || commit.output.handoff.goalRevision !== goal.revision || commit.output.handoff.recordRevision !== record.recordRevision)
809
+ throw new Error("Goal Handoff does not match the current Goal and Work Record");
810
+ this.#assertEvidenceExists(commit.output.handoff.evidence);
811
+ const responseItem = this.turnItems(turn.id).find((item) => item.id === commit.responseItemId);
812
+ if (!responseItem || responseItem.type !== "assistant_message" || responseItem.status !== "completed" || typeof responseItem.data.text !== "string" || !responseItem.data.text.trim())
813
+ throw new Error("Handoff response does not match a readable Turn Item");
814
+ const ordinal = this.turnItems(turn.id).length + 1;
815
+ if (commit.item.turnId !== turn.id || commit.item.type !== "handoff" || commit.item.status !== "completed" || commit.item.completedAt !== commit.ts || commit.item.createdAt !== commit.ts || commit.item.ordinal !== ordinal || !isDeepStrictEqual(commit.item.data, commit.output.handoff))
816
+ throw new Error("handoff Item does not match active TurnOutput");
817
+ if (commit.sourceWakeId) {
818
+ const wake = this.#requiredWake(commit.sourceWakeId);
819
+ if (wake.status !== "consumed" || wake.turnId !== turn.id)
820
+ throw new Error("handoff source Wake does not match Turn");
821
+ }
822
+ const streamId = `turn:${commit.turnId}`;
823
+ this.#insertEvent({ streamId, ts: commit.ts, actor: commit.agent, type: "response.committed", data: { messageItemId: responseItem.id, text: responseItem.data.text } });
824
+ this.#admitTurnItem(commit.item, commit.agent, streamId);
825
+ const event = this.#insertEvent({ streamId, ts: commit.ts, actor: commit.agent, type: "handoff.recorded", data: commit.output.handoff });
826
+ const delivered = new Set(commit.mailIds);
827
+ this.#recordMailReads(this.unreadMail(commit.agent).filter((candidate) => delivered.has(candidate.id)), commit.ts, streamId);
828
+ if (this.turnItems(turn.id).some((item) => item.status === "in_progress"))
829
+ throw new Error("successful Turn cannot retain in-progress Items");
830
+ this.#insertEvent({ streamId, ts: commit.ts, actor: "supervisor", type: "transcript.completed", data: {} });
831
+ this.#recordProjection("turns", { ...turn, status: "completed", error: null, endedAt: commit.ts, leaseUntil: null, leaseToken: null }, "supervisor", "turn.completed", void 0, commit.ts, void 0, streamId);
832
+ return event;
833
+ });
834
+ }
835
+ dueSchedules(now) {
836
+ return this.db.prepare("SELECT * FROM schedule WHERE status='pending' AND next_wake_at <= ? ORDER BY next_wake_at,id").all(now).map(mapSchedule);
837
+ }
838
+ unreadMail(agent) {
839
+ return this.db.prepare("SELECT * FROM mailbox WHERE to_agent=? AND read_at IS NULL ORDER BY rowid").all(agent).map(mapMail);
840
+ }
841
+ lastEvent(actor, type) {
842
+ const row = this.db.prepare("SELECT * FROM events WHERE actor=? AND type=? ORDER BY seq DESC LIMIT 1").get(actor, type);
843
+ return row ? mapEvent(row) : null;
844
+ }
845
+ lastGoalHandoff(goalId) {
846
+ const row = this.db.prepare("SELECT * FROM events WHERE type='handoff.recorded' AND json_extract(data,'$.goalId')=? ORDER BY seq DESC LIMIT 1").get(goalId);
847
+ return row ? mapEvent(row) : null;
848
+ }
849
+ latestEvent() {
850
+ const row = this.db.prepare("SELECT * FROM events ORDER BY seq DESC LIMIT 1").get();
851
+ return row ? mapEvent(row) : null;
852
+ }
853
+ eventsForWake(wakeId) {
854
+ return this.readStream(wakeStream(wakeId));
855
+ }
856
+ wake(id) {
857
+ const row = this.db.prepare("SELECT * FROM wakes WHERE id=?").get(id);
858
+ return row ? mapWake(row) : null;
859
+ }
860
+ wakeByTrigger(agent, triggerRef) {
861
+ const row = this.db.prepare("SELECT w.* FROM wake_triggers t JOIN wakes w ON w.id=t.wake_id WHERE t.agent=? AND t.trigger_ref=?").get(agent, triggerRef);
862
+ return row ? mapWake(row) : null;
863
+ }
864
+ wakeTriggers(wakeId) {
865
+ return this.db.prepare("SELECT * FROM wake_triggers WHERE wake_id=? ORDER BY rowid").all(wakeId).map(mapWakeTrigger);
866
+ }
867
+ wakeTriggersForAgent(agent) {
868
+ return this.db.prepare("SELECT * FROM wake_triggers WHERE agent=? ORDER BY rowid").all(agent).map(mapWakeTrigger);
869
+ }
870
+ queuedWakeForAgent(agent) {
871
+ const row = this.db.prepare("SELECT * FROM wakes WHERE agent=? AND status='queued' ORDER BY enqueued_seq LIMIT 1").get(agent);
872
+ return row ? mapWake(row) : null;
873
+ }
874
+ goalsForOwner(owner) {
875
+ return this.db.prepare("SELECT * FROM goals WHERE owner=? ORDER BY id").all(owner).map(mapGoal);
876
+ }
877
+ goal(id) {
878
+ return this.#getGoal(id);
879
+ }
880
+ workRecord(goalId) {
881
+ const row = this.db.prepare("SELECT * FROM work_records WHERE goal_id=?").get(goalId);
882
+ return row ? mapWorkRecord(row) : null;
883
+ }
884
+ workRecordHistory(goalId) {
885
+ return this.readStream(workRecordStream(goalId)).filter((event) => event.type === "work_record.created" || event.type === "work_record.updated").map((event) => mapWorkRecordEvent(event));
886
+ }
887
+ workRecordDiff(goalId, fromRevision, toRevision) {
888
+ const history = this.workRecordHistory(goalId);
889
+ const from = history.find((record) => record.recordRevision === fromRevision);
890
+ const to = history.find((record) => record.recordRevision === toRevision);
891
+ if (!from || !to)
892
+ throw new Error("work record diff revision does not exist");
893
+ return { goalId, fromRevision, toRevision, text: lineDiff(from.content, to.content) };
894
+ }
895
+ searchWorkRecords(query, limit = 50) {
896
+ return this.db.prepare(`SELECT e.* FROM events_fts f JOIN events e ON e.seq=f.rowid
897
+ WHERE events_fts MATCH ? AND e.type IN ('work_record.created','work_record.updated') ORDER BY rank LIMIT ?`).all(query, limit).map((row) => mapWorkRecordEvent(mapEvent(row)));
898
+ }
899
+ triggeringMail() {
900
+ return this.db.prepare("SELECT * FROM mailbox WHERE read_at IS NULL AND level IN ('decision','emergency') ORDER BY rowid").all().map(mapMail);
901
+ }
902
+ eventsSince(seq, types) {
903
+ const events = this.db.prepare("SELECT * FROM events WHERE seq>? ORDER BY seq").all(seq).map(mapEvent);
904
+ return types?.length ? events.filter((event) => types.includes(event.type)) : events;
905
+ }
906
+ searchEvents(query, limit = 50) {
907
+ return this.db.prepare("SELECT e.* FROM events_fts f JOIN events e ON e.seq=f.rowid WHERE events_fts MATCH ? ORDER BY rank LIMIT ?").all(query, limit).map(mapEvent);
908
+ }
909
+ events() {
910
+ return this.db.prepare("SELECT * FROM events ORDER BY seq").all().map(mapEvent);
911
+ }
912
+ goals() {
913
+ return this.db.prepare("SELECT * FROM goals ORDER BY id").all().map(mapGoal);
914
+ }
915
+ workRecords() {
916
+ return this.db.prepare("SELECT * FROM work_records ORDER BY goal_id").all().map(mapWorkRecord);
917
+ }
918
+ schedules() {
919
+ return this.db.prepare("SELECT * FROM schedule ORDER BY id").all().map(mapSchedule);
920
+ }
921
+ wakes() {
922
+ return this.db.prepare("SELECT * FROM wakes ORDER BY enqueued_seq").all().map(mapWake);
923
+ }
924
+ mailbox() {
925
+ return this.db.prepare("SELECT * FROM mailbox ORDER BY rowid").all().map(mapMail);
926
+ }
927
+ rebuildProjections() {
928
+ const source = this.db.prepare("SELECT * FROM events ORDER BY seq").all();
929
+ this.#transaction(() => {
930
+ this.db.exec("DELETE FROM turn_items; DELETE FROM wake_triggers; DELETE FROM wakes; DELETE FROM turns; DELETE FROM threads; DELETE FROM work_records; DELETE FROM mailbox; DELETE FROM schedule; DELETE FROM goals;");
931
+ const goalKeys = /* @__PURE__ */ new Set();
932
+ for (const row of source) {
933
+ if (row.projection_name === null)
934
+ continue;
935
+ const event = mapEvent(row);
936
+ const projection = String(row.projection_name);
937
+ const expected = projectionForEvent(event.type);
938
+ if (expected !== projection)
939
+ throw new Error(`event ${event.seq} cannot drive ${projection} projection`);
940
+ const data = event.data && typeof event.data === "object" && !Array.isArray(event.data) ? event.data : null;
941
+ if (!data || !("snapshot" in data))
942
+ throw new Error(`projection event ${event.seq} has no snapshot`);
943
+ if (projection === "goals")
944
+ this.#replayGoalChange(event, goalKeys);
945
+ else
946
+ this.#replayProjection(projection, data.snapshot, event);
947
+ }
948
+ });
949
+ }
950
+ #delegationResult(id) {
951
+ const row = this.db.prepare("SELECT data,actor FROM events WHERE type='delegation.created' AND json_extract(data,'$.delegationId')=? ORDER BY seq LIMIT 1").get(id);
952
+ if (!row)
953
+ return null;
954
+ const data = JSON.parse(row.data);
955
+ const goal = this.#goalChangeSnapshot(id, row.actor);
956
+ const mail = this.#eventSnapshot("mail.put", data.mailId);
957
+ const wake = this.#eventSnapshot("wake.enqueued", data.wakeId);
958
+ if (!goal || !mail || !wake)
959
+ throw new Error("committed delegation facts are incomplete");
960
+ return { delegationId: id, goal, mail, wake };
961
+ }
962
+ #reassignmentResult(id) {
963
+ const row = this.db.prepare("SELECT data,actor FROM events WHERE type='goal.reassigned' AND json_extract(data,'$.reassignmentId')=? ORDER BY seq LIMIT 1").get(id);
964
+ if (!row)
965
+ return null;
966
+ const data = JSON.parse(row.data);
967
+ const goal = this.#goalChangeSnapshot(id, row.actor);
968
+ const mail = data.mailIds.map((mailId) => {
969
+ const value = this.#eventSnapshot("mail.put", mailId);
970
+ if (!value)
971
+ throw new Error("committed reassignment mail is missing");
972
+ return value;
973
+ });
974
+ const wake = data.wakeId ? this.#eventSnapshot("wake.enqueued", data.wakeId) : null;
975
+ if (!goal || data.wakeId && !wake)
976
+ throw new Error("committed reassignment facts are incomplete");
977
+ return { reassignmentId: id, goal, mail, wake };
978
+ }
979
+ #goalChangeSnapshot(id, actor) {
980
+ const row = this.db.prepare("SELECT data FROM events WHERE type='goal.changed' AND actor=? AND json_extract(data,'$.idempotencyKey')=? ORDER BY seq LIMIT 1").get(actor, id);
981
+ return row ? JSON.parse(row.data).snapshot : null;
982
+ }
983
+ #eventSnapshot(type, id) {
984
+ const row = this.db.prepare("SELECT data FROM events WHERE type=? AND json_extract(data,'$.snapshot.id')=? ORDER BY seq LIMIT 1").get(type, id);
985
+ return row ? JSON.parse(row.data).snapshot : null;
986
+ }
987
+ #recordGoalChange(snapshot, previous, actor, wakeId, change) {
988
+ const derivedOperation = goalOperation(previous, snapshot);
989
+ if (change && change.operation !== derivedOperation)
990
+ throw new Error("Goal change operation does not match its state transition");
991
+ const operation = change?.operation ?? derivedOperation;
992
+ if (change && !change.reason.trim())
993
+ throw new Error("Goal change reason is required");
994
+ const evidence = change?.evidence ?? [];
995
+ if (evidence.length)
996
+ this.#assertEvidenceExists(evidence);
997
+ const expectedAuthority = this.#goalAuthority(snapshot.parentId, actor);
998
+ if (change?.authority && !isDeepStrictEqual(change.authority, expectedAuthority))
999
+ throw new Error("Goal change authority does not match the authenticated actor");
1000
+ const authority = change?.authority ?? expectedAuthority;
1001
+ const sourceWakeId = change?.sourceWakeId ?? wakeId;
1002
+ this.#assertGoalProvenance(snapshot, actor, authority, change?.sourceTurnId, sourceWakeId);
1003
+ if (change?.idempotencyKey && this.db.prepare("SELECT 1 FROM events WHERE type='goal.changed' AND actor=? AND json_extract(data,'$.idempotencyKey')=?").get(actor, change.idempotencyKey))
1004
+ throw new Error("Goal idempotency key is already committed");
1005
+ const data = { version: 1, operation, previousRevision: previous?.revision ?? null, snapshot, reason: change?.reason?.trim() || defaultGoalReason(operation), evidence, authority, ...change?.sourceTurnId ? { sourceTurnId: change.sourceTurnId } : {}, ...sourceWakeId ? { sourceWakeId } : {}, ...change?.idempotencyKey ? { idempotencyKey: change.idempotencyKey } : {} };
1006
+ const event = this.#insertEvent({ streamId: goalStream(snapshot.id), ts: this.#now(), actor, type: "goal.changed", data }, void 0, "goals");
1007
+ this.#faultInjector?.("after_event_before_projection");
1008
+ this.#applyProjection("goals", snapshot, event.seq);
1009
+ if (previous && (previous.owner !== snapshot.owner || previous.phase !== snapshot.phase && snapshot.phase !== "active")) {
1010
+ this.#cancelWakesForGoal(snapshot.id, this.#now());
1011
+ this.#supersedeSchedulesForGoal(snapshot.id, this.#now());
1012
+ }
1013
+ return event;
1014
+ }
1015
+ #replayGoalChange(event, keys) {
1016
+ if (event.type !== "goal.changed")
1017
+ throw new Error(`event ${event.seq} is not an authoritative Goal change`);
1018
+ const data = event.data;
1019
+ if (data.version !== 1)
1020
+ throw new Error(`goal.changed ${event.seq} has an unsupported format`);
1021
+ const snapshot = normalizeGoal(data.snapshot);
1022
+ assertGoalSnapshot(snapshot);
1023
+ if (event.streamId !== goalStream(snapshot.id))
1024
+ throw new Error(`goal.changed ${event.seq} uses the wrong stream`);
1025
+ const previous = this.#getGoal(snapshot.id);
1026
+ if (data.previousRevision !== (previous?.revision ?? null) || snapshot.revision !== (previous ? previous.revision + 1 : 0) || data.operation !== goalOperation(previous, snapshot))
1027
+ throw new Error(`goal.changed ${event.seq} breaks the Goal revision chain`);
1028
+ if (!data.reason.trim())
1029
+ throw new Error(`goal.changed ${event.seq} has no reason`);
1030
+ for (const seq of data.evidence)
1031
+ if (!Number.isInteger(seq) || seq <= 0 || seq >= event.seq)
1032
+ throw new Error(`goal.changed ${event.seq} has invalid evidence`);
1033
+ const expected = this.#goalAuthority(snapshot.parentId, event.actor);
1034
+ if (!isDeepStrictEqual(data.authority, expected))
1035
+ throw new Error(`goal.changed ${event.seq} has invalid authority`);
1036
+ this.#assertGoalProvenance(snapshot, event.actor, data.authority, data.sourceTurnId, data.sourceWakeId);
1037
+ if (data.idempotencyKey) {
1038
+ const key = `${event.actor}\0${data.idempotencyKey}`;
1039
+ if (keys.has(key))
1040
+ throw new Error(`goal.changed ${event.seq} reuses an idempotency key`);
1041
+ keys.add(key);
1042
+ }
1043
+ this.#applyProjection("goals", snapshot, event.seq);
1044
+ }
1045
+ #assertGoalProvenance(snapshot, actor, authority, sourceTurnId, sourceWakeId) {
1046
+ if (sourceWakeId && !sourceTurnId)
1047
+ throw new Error("Goal change source Wake requires its source Turn");
1048
+ const turn = sourceTurnId ? this.turn(sourceTurnId) : null;
1049
+ if (sourceTurnId && !turn)
1050
+ throw new Error("Goal change source Turn does not exist");
1051
+ if (turn) {
1052
+ if (turn.status !== "in_progress")
1053
+ throw new Error("Goal change source Turn is not active");
1054
+ const thread = this.thread(turn.threadId);
1055
+ if (!thread)
1056
+ throw new Error("Goal change source Turn has no Thread");
1057
+ if (authority.kind === "human") {
1058
+ const legalCommitment = turn.goalId === null || turn.goalId === snapshot.id;
1059
+ if (turn.triggerKind !== "user_message" || thread.role !== "ceo" || !legalCommitment)
1060
+ throw new Error("Goal change source Turn does not carry Human authority");
1061
+ } else if (authority.kind === "parent_goal") {
1062
+ if (thread.agent !== actor || turn.goalId !== authority.goalId || turn.goalRevision !== authority.goalRevision)
1063
+ throw new Error("Goal change source Turn is not committed to the authorizing parent Goal");
1064
+ } else if (thread.agent !== actor)
1065
+ throw new Error("Goal change source Turn actor does not match");
1066
+ }
1067
+ if (sourceWakeId) {
1068
+ const wake = this.wake(sourceWakeId);
1069
+ if (!wake)
1070
+ throw new Error("Goal change source Wake does not exist");
1071
+ if (wake.status !== "consumed" || sourceTurnId && wake.turnId !== sourceTurnId)
1072
+ throw new Error("Goal change source Wake does not match its Turn");
1073
+ if (authority.kind === "parent_goal" && (wake.targetKind !== "goal" || wake.agent !== actor || wake.goalId !== authority.goalId))
1074
+ throw new Error("Goal change source Wake is not bound to the authorizing parent Goal");
1075
+ }
1076
+ }
1077
+ #goalAuthority(parentId, actor) {
1078
+ if (parentId === null)
1079
+ return actor === "human" ? { kind: "human" } : { kind: "system", reason: `root mutation by ${actor}` };
1080
+ const parent = this.#getGoal(parentId);
1081
+ if (!parent)
1082
+ throw new Error("parent goal does not exist");
1083
+ return { kind: "parent_goal", goalId: parent.id, goalRevision: parent.revision };
1084
+ }
1085
+ #createWorkRecord(goal, actor, turnId, wakeId, seed) {
1086
+ return this.#recordWorkRecord({
1087
+ goalId: goal.id,
1088
+ recordRevision: 0,
1089
+ goalRevision: goal.revision,
1090
+ content: seed?.content ?? initialWorkRecord(),
1091
+ updatedBy: actor,
1092
+ updatedInTurn: turnId,
1093
+ sourceWakeId: wakeId ?? null,
1094
+ updatedAt: this.#now(),
1095
+ reason: "Goal created",
1096
+ evidence: seed?.evidence ?? [],
1097
+ lastEventSeq: 0
1098
+ }, "work_record.created");
1099
+ }
1100
+ #recordWorkRecord(snapshot, type) {
1101
+ const seq = this.#nextEventSeq();
1102
+ const committed = { ...snapshot, lastEventSeq: seq };
1103
+ this.#recordProjection("work_records", committed, committed.updatedBy, type, committed.sourceWakeId ?? void 0, committed.updatedAt, seq, workRecordStream(committed.goalId));
1104
+ return committed;
1105
+ }
1106
+ #assertWorkRecordAuthority(goal, actor) {
1107
+ if (goal.owner === actor || goal.parentId === null && actor === "human")
1108
+ return;
1109
+ const parent = goal.parentId ? this.#getGoal(goal.parentId) : null;
1110
+ if (parent?.owner !== actor)
1111
+ throw new Error("only the goal owner or parent owner may update its work record");
1112
+ }
1113
+ #assertGoalAuthority(parentId, actor) {
1114
+ if (parentId === null) {
1115
+ if (actor !== "human")
1116
+ throw new Error("only human may modify a root goal");
1117
+ return;
1118
+ }
1119
+ const parent = this.#getGoal(parentId);
1120
+ if (!parent)
1121
+ throw new Error("parent goal does not exist");
1122
+ if (parent.owner !== actor)
1123
+ throw new Error("only the parent goal owner may modify a child goal");
1124
+ }
1125
+ #assertOwnerSeparation(goalId, parentId, owner) {
1126
+ if (parentId && this.#getGoal(parentId)?.owner === owner)
1127
+ throw new Error("parent and child Goals require distinct owners");
1128
+ if (this.goals().some((goal) => goal.parentId === goalId && goal.owner === owner))
1129
+ throw new Error("parent and child Goals require distinct owners");
1130
+ }
1131
+ #hasNonCompleteDescendant(goalId) {
1132
+ return Boolean(this.db.prepare(`WITH RECURSIVE descendants(id) AS (
1133
+ SELECT id FROM goals WHERE parent_id=?
1134
+ UNION ALL SELECT g.id FROM goals g JOIN descendants d ON g.parent_id=d.id
1135
+ ) SELECT 1 FROM goals WHERE id IN (SELECT id FROM descendants) AND phase<>'complete' LIMIT 1`).get(goalId));
1136
+ }
1137
+ #assertEvidenceExists(evidence) {
1138
+ if (evidence.length === 0)
1139
+ throw new Error("evidence is required");
1140
+ const exists = this.db.prepare("SELECT 1 FROM events WHERE seq=?");
1141
+ for (const seq of evidence)
1142
+ if (!Number.isInteger(seq) || seq <= 0 || !exists.get(seq))
1143
+ throw new Error(`evidence event does not exist: ${seq}`);
1144
+ }
1145
+ #assertTurnLease(turn, leaseToken) {
1146
+ if (turn.leaseToken !== leaseToken)
1147
+ throw new Error("stale Turn lease token");
1148
+ }
1149
+ #assertRawEvent(input) {
1150
+ if (input.type === "goal.changed")
1151
+ throw new Error("projection-driving events must use a typed Ledger domain API");
1152
+ }
1153
+ #getGoal(id) {
1154
+ const row = this.db.prepare("SELECT * FROM goals WHERE id=?").get(id);
1155
+ return row ? mapGoal(row) : null;
1156
+ }
1157
+ #requiredTurn(id) {
1158
+ const value = this.turn(id);
1159
+ if (!value)
1160
+ throw new Error(`turn not found: ${id}`);
1161
+ return value;
1162
+ }
1163
+ #requiredWake(id) {
1164
+ const value = this.wake(id);
1165
+ if (!value)
1166
+ throw new Error(`wake not found: ${id}`);
1167
+ return value;
1168
+ }
1169
+ #admitThread(thread, actor) {
1170
+ if (!thread.id.trim() || !thread.agent.trim())
1171
+ throw new Error("thread id and agent are required");
1172
+ if (thread.agent === "ceo" !== (thread.role === "ceo"))
1173
+ throw new Error("exactly the ceo Agent must use the ceo Thread role");
1174
+ const current = this.thread(thread.id);
1175
+ const agentThread = this.threads().find((candidate) => candidate.agent === thread.agent);
1176
+ if (agentThread && agentThread.id !== thread.id)
1177
+ throw new Error("agent already owns a Thread");
1178
+ if (current && (current.agent !== thread.agent || current.role !== thread.role || current.parentThreadId !== thread.parentThreadId || current.createdAt !== thread.createdAt))
1179
+ throw new Error("thread identity cannot change");
1180
+ return this.#recordProjection("threads", thread, actor, "thread.put", void 0, thread.updatedAt, void 0, `thread:${thread.id}`);
1181
+ }
1182
+ #finishTurn(current, status, error, now, actor, mailIds) {
1183
+ if (current.status !== "in_progress")
1184
+ throw new Error("only an active Turn may finish");
1185
+ if (status === "completed" && current.goalId !== null)
1186
+ throw new Error("committed Turn completion requires commitHandoff");
1187
+ if (status === "completed" && error !== null || status !== "completed" && error === null)
1188
+ throw new Error("Turn terminal error does not match status");
1189
+ if (status !== "completed")
1190
+ this.#repairOpenTurnItems(current.id, String(error.message ?? "Turn interrupted"), now, actor);
1191
+ else if (this.turnItems(current.id).some((item) => item.status === "in_progress"))
1192
+ throw new Error("successful Turn cannot retain in-progress Items");
1193
+ const streamId = `turn:${current.id}`;
1194
+ const thread = this.thread(current.threadId);
1195
+ const delivered = new Set(mailIds);
1196
+ if (status !== "completed" && mailIds.length)
1197
+ throw new Error("failed Turn cannot acknowledge Mail");
1198
+ if (status === "completed" && thread)
1199
+ this.#recordMailReads(this.unreadMail(thread.agent).filter((candidate) => delivered.has(candidate.id)), now, streamId);
1200
+ this.#insertEvent({ streamId, ts: now, actor, type: status === "completed" ? "transcript.completed" : "transcript.interrupted", data: status === "completed" ? {} : { reason: String(error.message ?? "Turn interrupted") } });
1201
+ const next = { ...current, status, error, endedAt: now, leaseUntil: null, leaseToken: null };
1202
+ this.#recordProjection("turns", next, actor, `turn.${status}`, void 0, now, void 0, streamId);
1203
+ return next;
1204
+ }
1205
+ #admitWake(input, actor, allowExisting) {
1206
+ if (actor !== "supervisor")
1207
+ throw new Error("only supervisor may enqueue wakes");
1208
+ this.#assertAutomaticTarget(input);
1209
+ if (!input.id.trim() || !input.agent.trim() || !input.triggerRef.trim())
1210
+ throw new Error("Wake id, agent, and trigger are required");
1211
+ if (input.status !== "queued" || input.attempt !== 0 || input.claimedAt || input.consumedAt || input.turnId)
1212
+ throw new Error("new wake must be pristine and queued");
1213
+ const sameId = this.wake(input.id);
1214
+ if (sameId) {
1215
+ const sameIdentity = sameId.targetKind === input.targetKind && sameId.agent === input.agent && sameId.specialistRole === input.specialistRole && sameId.triggerRef === input.triggerRef && sameId.goalId === input.goalId;
1216
+ if (!allowExisting || !sameIdentity)
1217
+ throw new Error("wake id was reused with a different identity");
1218
+ const row = this.db.prepare("SELECT * FROM events WHERE type='wake.enqueued' AND json_extract(data,'$.snapshot.id')=? ORDER BY seq LIMIT 1").get(sameId.id);
1219
+ if (!row)
1220
+ throw new Error("wake projection has no source event");
1221
+ return { event: mapEvent(row), created: false, wake: sameId };
1222
+ }
1223
+ const duplicate = this.wakeByTrigger(input.agent, input.triggerRef);
1224
+ if (duplicate) {
1225
+ const sameTarget = duplicate.targetKind === input.targetKind && duplicate.specialistRole === input.specialistRole && duplicate.goalId === input.goalId;
1226
+ if (!allowExisting || !sameTarget)
1227
+ throw new Error("Wake trigger was reused with a different automatic target");
1228
+ const row = this.db.prepare("SELECT * FROM events WHERE type='wake.enqueued' AND json_extract(data,'$.snapshot.id')=? ORDER BY seq LIMIT 1").get(duplicate.id);
1229
+ if (!row)
1230
+ throw new Error("wake projection has no source event");
1231
+ return { event: mapEvent(row), created: false, wake: duplicate };
1232
+ }
1233
+ if (input.targetKind === "goal") {
1234
+ const goal = this.#getGoal(input.goalId);
1235
+ if (!goal || goal.owner !== input.agent || goal.phase !== "active")
1236
+ throw new Error("Wake Goal target is inactive or owned by another Agent");
1237
+ }
1238
+ const enqueuedSeq = this.#nextEventSeq();
1239
+ const wake = { ...input, enqueuedSeq };
1240
+ const event = this.#recordProjection("wakes", wake, actor, "wake.enqueued", wake.id, void 0, enqueuedSeq);
1241
+ this.#admitWakeTrigger(wake.id, wake.triggerRef, actor, event.ts);
1242
+ return { event, created: true, wake };
1243
+ }
1244
+ #admitWakeTrigger(wakeId, triggerRef, actor, at) {
1245
+ if (actor !== "supervisor")
1246
+ throw new Error("only supervisor may add Wake triggers");
1247
+ if (!triggerRef.trim())
1248
+ throw new Error("Wake trigger is required");
1249
+ const wake = this.#requiredWake(wakeId);
1250
+ if (wake.status !== "queued" && wake.status !== "claimed")
1251
+ throw new Error("triggers may only be added before a Wake starts its Turn");
1252
+ const row = this.db.prepare("SELECT * FROM wake_triggers WHERE agent=? AND trigger_ref=?").get(wake.agent, triggerRef);
1253
+ if (row) {
1254
+ const current = mapWakeTrigger(row);
1255
+ if (current.wakeId !== wake.id)
1256
+ throw new Error("Wake trigger is already owned by another Wake");
1257
+ return current;
1258
+ }
1259
+ const mailId = mailIdFromTrigger(triggerRef);
1260
+ const mail = mailId ? this.mailbox().find((candidate) => candidate.id === mailId) : null;
1261
+ if (mail) {
1262
+ const matches = mail.routeKind === "goal" ? wake.targetKind === "goal" && wake.goalId === mail.goalId : mail.routeKind === "specialist_inbox" ? wake.targetKind === "specialist" && wake.specialistRole === mail.specialistRole : false;
1263
+ if (!matches)
1264
+ throw new Error("Mail trigger does not match the Wake automatic target");
1265
+ }
1266
+ const source = wake.targetKind === "goal" ? "goal" : "system";
1267
+ const trigger = { wakeId: wake.id, agent: wake.agent, triggerRef, source, status: "pending", addedAt: at, resolvedAt: null };
1268
+ this.#recordProjection("wake_triggers", trigger, "supervisor", "wake_trigger.added", wake.id, at);
1269
+ return trigger;
1270
+ }
1271
+ #resolveWakeTriggers(wakeId, now) {
1272
+ return this.wakeTriggers(wakeId).filter((trigger) => trigger.status === "pending").map((trigger) => {
1273
+ const next = { ...trigger, status: "resolved", resolvedAt: now };
1274
+ this.#recordProjection("wake_triggers", next, "supervisor", "wake_trigger.resolved", wakeId, now);
1275
+ return next;
1276
+ });
1277
+ }
1278
+ #cancelWakeProjection(wake, now) {
1279
+ if (wake.status !== "queued" && wake.status !== "claimed")
1280
+ throw new Error("only a pending Wake may be cancelled");
1281
+ this.#resolveWakeTriggers(wake.id, now);
1282
+ const next = { ...wake, status: "cancelled", consumedAt: now };
1283
+ this.#recordProjection("wakes", next, "supervisor", "wake.cancelled", wake.id, now);
1284
+ return next;
1285
+ }
1286
+ #requiredSchedule(id) {
1287
+ const row = this.db.prepare("SELECT * FROM schedule WHERE id=?").get(id);
1288
+ if (!row)
1289
+ throw new Error(`schedule not found: ${id}`);
1290
+ return mapSchedule(row);
1291
+ }
1292
+ #admitTurnItem(item, actor, streamId = `turn:${item.turnId}`) {
1293
+ if (item.type === "assistant_message") {
1294
+ const data = item.data && typeof item.data === "object" && !Array.isArray(item.data) ? item.data : null;
1295
+ const text = normalizeAssistantText(String(data?.text ?? ""));
1296
+ if (!data || !text)
1297
+ throw new Error("assistant message requires canonical readable text");
1298
+ item = { ...item, data: { ...data, text } };
1299
+ }
1300
+ if (!item.id.trim() || !item.turnId.trim())
1301
+ throw new Error("turn item id and turn are required");
1302
+ const turn = this.turn(item.turnId);
1303
+ if (!turn)
1304
+ throw new Error("turn item turn does not exist");
1305
+ if (turn.status !== "in_progress")
1306
+ throw new Error("terminal turn cannot accept items");
1307
+ if (item.status === "in_progress" && item.completedAt !== null || item.status !== "in_progress" && item.completedAt === null)
1308
+ throw new Error("turn item terminal time does not match status");
1309
+ const current = this.db.prepare("SELECT * FROM turn_items WHERE id=?").get(item.id);
1310
+ if (current && (String(current.turn_id) !== item.turnId || String(current.type) !== item.type || String(current.status) !== "in_progress"))
1311
+ throw new Error("turn item identity or terminal state cannot change");
1312
+ return this.#recordProjection("turn_items", item, actor, current ? `item.${item.type}.${item.status}` : `item.${item.type}.started`, void 0, item.completedAt ?? item.createdAt, void 0, streamId);
1313
+ }
1314
+ #assertMailRequest(mail, actor) {
1315
+ if (!mail.id.trim() || !mail.to.trim() || !mail.from.trim())
1316
+ throw new Error("mail id, sender, and recipient are required");
1317
+ if (mail.from !== actor && actor !== "supervisor")
1318
+ throw new Error("mail sender does not match actor");
1319
+ if (mail.readAt !== null)
1320
+ throw new Error("new mail must be unread");
1321
+ if (mail.routeKind === "goal") {
1322
+ if (mail.specialistRole !== null)
1323
+ throw new Error("Goal Mail cannot carry a specialist role");
1324
+ const goal = this.#getGoal(mail.goalId);
1325
+ if (!goal || goal.owner !== mail.to)
1326
+ throw new Error("Mail Goal route is not owned by its recipient");
1327
+ } else if (mail.routeKind === "human_inbox") {
1328
+ if (mail.to !== "ceo" || mail.goalId !== null || mail.specialistRole !== null || !["human", "verifier", "audit", "supervisor"].includes(mail.from))
1329
+ throw new Error("Human inbox Mail has an invalid sender or recipient");
1330
+ } else if (mail.routeKind === "human_request") {
1331
+ if (mail.to !== "human" || mail.from !== "ceo" || mail.goalId !== null || mail.specialistRole !== null)
1332
+ throw new Error("Human request Mail has an invalid sender or recipient");
1333
+ } else if (mail.goalId !== null || mail.specialistRole === null)
1334
+ throw new Error("Specialist inbox Mail has an invalid route");
1335
+ }
1336
+ #assertNewMail(mail, actor) {
1337
+ this.#assertMailRequest(mail, actor);
1338
+ if (this.db.prepare("SELECT 1 FROM mailbox WHERE id=?").get(mail.id))
1339
+ throw new Error("mail id is already in use");
1340
+ }
1341
+ #recordMailReads(mails, now, streamId) {
1342
+ for (const mail of mails)
1343
+ this.#recordProjection("mailbox", { ...mail, readAt: now }, "supervisor", "mail.read", void 0, now, void 0, streamId);
1344
+ const recipients = new Set(mails.map((mail) => mail.to));
1345
+ const ids = new Set(mails.map((mail) => mail.id));
1346
+ for (const wake of this.wakes().filter((candidate) => recipients.has(candidate.agent) && (candidate.status === "queued" || candidate.status === "claimed"))) {
1347
+ for (const trigger of this.wakeTriggers(wake.id).filter((candidate) => candidate.status === "pending")) {
1348
+ const mailId = mailIdFromTrigger(trigger.triggerRef);
1349
+ if (mailId && ids.has(mailId)) {
1350
+ const next = { ...trigger, status: "resolved", resolvedAt: now };
1351
+ this.#recordProjection("wake_triggers", next, "supervisor", "wake_trigger.resolved", wake.id, now);
1352
+ }
1353
+ }
1354
+ if (this.wakeTriggers(wake.id).every((trigger) => trigger.status === "resolved"))
1355
+ this.#cancelWakeProjection(wake, now);
1356
+ }
1357
+ }
1358
+ #normalizeSchedule(value, actor, binding) {
1359
+ this.#assertAutomaticTarget(value);
1360
+ if (actor !== "supervisor" && actor !== value.agent)
1361
+ throw new Error("schedule may only be set by its agent or supervisor");
1362
+ if (value.setBy !== actor)
1363
+ throw new Error("schedule setBy does not match actor");
1364
+ const parsed = Date.parse(value.nextWakeAt);
1365
+ if (!value.id.trim() || !value.agent.trim() || !value.reason.trim() || !Number.isFinite(parsed))
1366
+ throw new Error("schedule requires an id, agent, valid time, and reason");
1367
+ const normalized = { ...value, nextWakeAt: new Date(parsed).toISOString() };
1368
+ if (normalized.status !== "pending" || normalized.resolvedAt !== null)
1369
+ throw new Error("new schedule must be pending");
1370
+ if (binding && normalized.goalId !== binding.goalId)
1371
+ throw new Error("handoff schedule does not match the Turn Goal commitment");
1372
+ if (normalized.targetKind === "goal") {
1373
+ const goal = this.#getGoal(normalized.goalId);
1374
+ if (!goal || goal.owner !== normalized.agent || goal.phase !== "active")
1375
+ throw new Error("schedule Goal target is inactive or owned by another Agent");
1376
+ }
1377
+ return normalized;
1378
+ }
1379
+ #assertAutomaticTarget(value) {
1380
+ if (value.targetKind === "goal") {
1381
+ if (!value.goalId || value.specialistRole !== null)
1382
+ throw new Error("invalid Goal automatic target");
1383
+ return;
1384
+ }
1385
+ if (value.targetKind !== "specialist" || value.goalId !== null || !value.specialistRole || !["verifier", "audit"].includes(value.specialistRole))
1386
+ throw new Error("invalid Specialist automatic target");
1387
+ }
1388
+ #assertTurnShape(value) {
1389
+ if (!["user_message", "wake"].includes(value.triggerKind) || value.goalId === null !== (value.goalRevision === null) || value.goalRevision !== null && (!Number.isInteger(value.goalRevision) || value.goalRevision < 0))
1390
+ throw new Error("invalid Turn trigger or Goal commitment");
1391
+ }
1392
+ #replayProjection(projection, snapshot, event) {
1393
+ if (projection === "turns")
1394
+ this.#assertReplayTurn(snapshot, event);
1395
+ else if (projection === "wakes")
1396
+ this.#assertReplayWake(snapshot, event);
1397
+ this.#applyProjection(projection, snapshot, event.seq);
1398
+ }
1399
+ #assertReplayTurn(turn, event) {
1400
+ this.#assertTurnShape(turn);
1401
+ const thread = this.thread(turn.threadId);
1402
+ if (!thread)
1403
+ throw new Error(`turn event ${event.seq} has no Thread`);
1404
+ const current = this.turn(turn.id);
1405
+ if (!current) {
1406
+ if (event.type !== "turn.started" || turn.status !== "in_progress" || turn.attempt !== 1 || turn.runnerPid !== null)
1407
+ throw new Error(`turn event ${event.seq} has an invalid initial state`);
1408
+ if (event.actor === "human") {
1409
+ if (turn.triggerKind !== "user_message" || thread.role !== "ceo")
1410
+ throw new Error(`turn event ${event.seq} has invalid user ownership`);
1411
+ if (turn.goalId) {
1412
+ const goal = this.#getGoal(turn.goalId);
1413
+ if (!goal || goal.parentId !== null || goal.owner !== thread.agent || goal.phase !== "active" || goal.revision !== turn.goalRevision)
1414
+ throw new Error(`turn event ${event.seq} has an invalid direct Goal commitment`);
1415
+ }
1416
+ } else {
1417
+ if (event.actor !== "supervisor" || !this.wakes().some((wake) => wake.status === "claimed" && wake.agent === thread.agent && sameWakeTurnTarget(wake, turn, thread)))
1418
+ throw new Error(`turn event ${event.seq} bypasses Wake admission`);
1419
+ this.#assertCurrentAutomaticTurn(turn, thread, event.seq);
1420
+ }
1421
+ return;
1422
+ }
1423
+ if (current.threadId !== turn.threadId || current.triggerKind !== turn.triggerKind || current.startedAt !== turn.startedAt || current.runnerProfileId !== turn.runnerProfileId)
1424
+ throw new Error(`turn event ${event.seq} changes Turn identity`);
1425
+ const sameProcessState = sameTurnExceptRunnerPid(current, turn);
1426
+ if (event.type === "turn.process_attached") {
1427
+ if (event.actor !== "supervisor" || current.status !== "in_progress" || current.runnerPid !== null || !Number.isInteger(turn.runnerPid) || turn.runnerPid <= 0 || !sameProcessState)
1428
+ throw new Error(`turn event ${event.seq} has an invalid Runner process attachment`);
1429
+ return;
1430
+ }
1431
+ if (event.type === "turn.process_released") {
1432
+ if (event.actor !== "supervisor" || current.runnerPid === null || turn.runnerPid !== null || !sameProcessState)
1433
+ throw new Error(`turn event ${event.seq} has an invalid Runner process release`);
1434
+ return;
1435
+ }
1436
+ if (current.status !== "in_progress")
1437
+ throw new Error(`turn event ${event.seq} changes a terminal Turn`);
1438
+ if (current.runnerPid !== turn.runnerPid)
1439
+ throw new Error(`turn event ${event.seq} changes Runner process ownership outside attach/release`);
1440
+ const commitsGoal = event.type === "turn.goal_committed" && event.actor === "supervisor" && current.goalId === null && turn.goalId !== null;
1441
+ if (!commitsGoal && (current.goalId !== turn.goalId || current.goalRevision !== turn.goalRevision))
1442
+ throw new Error(`turn event ${event.seq} changes Goal commitment`);
1443
+ if (commitsGoal) {
1444
+ const goal = this.#getGoal(turn.goalId);
1445
+ if (!goal || goal.owner !== thread.agent || goal.phase !== "active" || goal.revision !== turn.goalRevision || thread.role === "ceo" && goal.parentId !== null || thread.role === "child" && goal.parentId === null || thread.role === "verifier" || thread.role === "audit")
1446
+ throw new Error(`turn event ${event.seq} has an invalid Goal commitment`);
1447
+ }
1448
+ if (turn.status === "in_progress") {
1449
+ if (!commitsGoal && event.type !== "turn.in_progress" || turn.attempt !== current.attempt && turn.attempt !== current.attempt + 1)
1450
+ throw new Error(`turn event ${event.seq} has an invalid retry transition`);
1451
+ } else {
1452
+ if (event.type !== `turn.${turn.status}` || turn.attempt !== current.attempt || turn.runnerPid !== current.runnerPid)
1453
+ throw new Error(`turn event ${event.seq} has an invalid terminal transition`);
1454
+ if (turn.status === "completed" && turn.goalId !== null)
1455
+ this.#assertReplayGoalCompletion(turn, event);
1456
+ }
1457
+ }
1458
+ #assertReplayGoalCompletion(turn, event) {
1459
+ if (turn.goalId === null || turn.goalRevision === null)
1460
+ throw new Error(`turn event ${event.seq} has no Goal commitment`);
1461
+ const items = this.turnItems(turn.id);
1462
+ const responses = items.filter((item) => item.type === "assistant_message" && item.status === "completed" && typeof item.data.text === "string" && Boolean(item.data.text.trim()));
1463
+ const handoffItem = items.findLast((item) => item.type === "handoff" && item.status === "completed");
1464
+ const responseRow = this.db.prepare("SELECT data FROM events WHERE stream_id=? AND type='response.committed' AND seq<? ORDER BY seq DESC LIMIT 1").get(`turn:${turn.id}`, event.seq);
1465
+ const handoffRow = this.db.prepare("SELECT data FROM events WHERE stream_id=? AND type='handoff.recorded' AND seq<? ORDER BY seq DESC LIMIT 1").get(`turn:${turn.id}`, event.seq);
1466
+ if (!responseRow || !handoffRow || !handoffItem)
1467
+ throw new Error(`turn event ${event.seq} completes a Goal without response and Handoff`);
1468
+ const response = JSON.parse(responseRow.data);
1469
+ const handoff = JSON.parse(handoffRow.data);
1470
+ const responseItem = responses.find((item) => item.id === response.messageItemId);
1471
+ if (!responseItem || responseItem.data.text !== response.text || !isDeepStrictEqual(handoffItem.data, handoff))
1472
+ throw new Error(`turn event ${event.seq} has mismatched response or Handoff facts`);
1473
+ assertHandoff(handoff);
1474
+ const record = this.workRecord(turn.goalId);
1475
+ if (handoff.goalId !== turn.goalId || handoff.goalRevision !== turn.goalRevision || record?.recordRevision !== handoff.recordRevision)
1476
+ throw new Error(`turn event ${event.seq} has a Handoff for another Goal or Work Record`);
1477
+ for (const seq of handoff.evidence)
1478
+ if (seq >= event.seq || !this.db.prepare("SELECT 1 FROM events WHERE seq=?").get(seq))
1479
+ throw new Error(`turn event ${event.seq} has invalid Handoff evidence`);
1480
+ }
1481
+ #assertReplayWake(wake, event) {
1482
+ this.#assertAutomaticTarget(wake);
1483
+ const current = this.wake(wake.id);
1484
+ if (!current) {
1485
+ if (event.type !== "wake.enqueued" || wake.status !== "queued" || wake.attempt !== 0 || wake.claimedAt || wake.consumedAt || wake.turnId)
1486
+ throw new Error(`wake event ${event.seq} has an invalid initial state`);
1487
+ if (wake.targetKind === "goal") {
1488
+ const goal = this.#getGoal(wake.goalId);
1489
+ if (!goal || goal.phase !== "active" || goal.owner !== wake.agent)
1490
+ throw new Error(`wake event ${event.seq} has an invalid Goal target`);
1491
+ }
1492
+ return;
1493
+ }
1494
+ if (current.targetKind !== wake.targetKind || current.agent !== wake.agent || current.goalId !== wake.goalId || current.specialistRole !== wake.specialistRole || current.triggerRef !== wake.triggerRef || current.enqueuedSeq !== wake.enqueuedSeq)
1495
+ throw new Error(`wake event ${event.seq} changes Wake identity`);
1496
+ if (current.status === "consumed" || current.status === "cancelled")
1497
+ throw new Error(`wake event ${event.seq} changes a terminal Wake`);
1498
+ if (wake.status === "claimed") {
1499
+ if (event.type !== "wake.claimed" || current.status !== "queued" || wake.attempt !== current.attempt + 1 || !wake.claimedAt || wake.consumedAt !== null || wake.turnId !== null)
1500
+ throw new Error(`wake event ${event.seq} has an invalid claim transition`);
1501
+ } else if (wake.status === "queued") {
1502
+ if (event.type !== "wake.released" || current.status !== "claimed" || wake.attempt !== current.attempt || wake.claimedAt !== null || wake.consumedAt !== null || wake.turnId !== null)
1503
+ throw new Error(`wake event ${event.seq} has an invalid release transition`);
1504
+ } else if (wake.status === "cancelled") {
1505
+ if (event.type !== "wake.cancelled" || wake.attempt !== current.attempt || wake.claimedAt !== current.claimedAt || wake.consumedAt === null || wake.turnId !== null)
1506
+ throw new Error(`wake event ${event.seq} has an invalid cancel transition`);
1507
+ } else {
1508
+ if (event.type !== "wake.consumed" || current.status !== "claimed" || wake.attempt !== current.attempt || wake.claimedAt !== current.claimedAt || wake.consumedAt === null || !wake.turnId)
1509
+ throw new Error(`wake event ${event.seq} has an invalid consume transition`);
1510
+ const turn = this.turn(wake.turnId);
1511
+ const thread = turn ? this.thread(turn.threadId) : null;
1512
+ if (!turn || !thread || thread.agent !== wake.agent || !sameWakeTurnTarget(wake, turn, thread))
1513
+ throw new Error(`wake event ${event.seq} does not match its Turn`);
1514
+ }
1515
+ }
1516
+ #assertCurrentAutomaticTurn(turn, thread, seq) {
1517
+ if (turn.triggerKind !== "wake")
1518
+ throw new Error(seq ? `turn event ${seq} is not Wake-triggered` : "automatic Turn must be Wake-triggered");
1519
+ if (turn.goalId !== null) {
1520
+ const goal = this.#getGoal(turn.goalId);
1521
+ if (thread.role !== "ceo" && thread.role !== "child" || !goal || goal.phase !== "active" || goal.owner !== thread.agent || goal.revision !== turn.goalRevision || thread.role === "ceo" && goal.parentId !== null || thread.role === "child" && goal.parentId === null)
1522
+ throw new Error(seq ? `turn event ${seq} has an invalid current Goal commitment for its Thread role` : "Wake Turn must commit to the current active Goal revision allowed by its Thread role");
1523
+ return;
1524
+ }
1525
+ if (thread.role !== "verifier" && thread.role !== "audit")
1526
+ throw new Error(seq ? `turn event ${seq} has an uncommitted Goal-owning Agent` : "only Specialists may start an uncommitted Wake Turn");
1527
+ }
1528
+ #admitSchedule(value, actor, wakeId, ts = this.#now(), streamId, binding, allowExisting = true) {
1529
+ const normalized = this.#normalizeSchedule(value, actor, binding);
1530
+ const existing = this.db.prepare("SELECT * FROM schedule WHERE id=?").get(normalized.id);
1531
+ if (existing) {
1532
+ const current = mapSchedule(existing);
1533
+ if (!allowExisting || !isDeepStrictEqual(current, normalized))
1534
+ throw new Error("schedule id was reused with different content");
1535
+ const row = this.db.prepare("SELECT * FROM events WHERE type='schedule.put' AND json_extract(data,'$.snapshot.id')=? ORDER BY seq LIMIT 1").get(normalized.id);
1536
+ if (!row)
1537
+ throw new Error("schedule projection has no source event");
1538
+ return mapEvent(row);
1539
+ }
1540
+ this.#supersedeScheduleRoute(normalized.agent, normalized.goalId ?? null, normalized.id, ts);
1541
+ return this.#recordProjection("schedule", normalized, actor, "schedule.put", wakeId, ts, void 0, streamId);
1542
+ }
1543
+ #supersedeScheduleRoute(agent, goalId, exceptId, now) {
1544
+ const rows = goalId === null ? this.db.prepare("SELECT * FROM schedule WHERE agent=? AND goal_id IS NULL AND id<>? AND status='pending' ORDER BY id").all(agent, exceptId) : this.db.prepare("SELECT * FROM schedule WHERE agent=? AND goal_id=? AND id<>? AND status='pending' ORDER BY id").all(agent, goalId, exceptId);
1545
+ return rows.map((row) => {
1546
+ const current = mapSchedule(row);
1547
+ const next = { ...current, status: "superseded", resolvedAt: now };
1548
+ this.#recordProjection("schedule", next, "supervisor", "schedule.superseded", void 0, now);
1549
+ return next;
1550
+ });
1551
+ }
1552
+ #supersedeSchedulesForGoal(goalId, now) {
1553
+ const rows = this.db.prepare("SELECT * FROM schedule WHERE goal_id=? AND status='pending' ORDER BY id").all(goalId);
1554
+ return rows.map((row) => {
1555
+ const current = mapSchedule(row);
1556
+ const next = { ...current, status: "superseded", resolvedAt: now };
1557
+ this.#recordProjection("schedule", next, "supervisor", "schedule.superseded", void 0, now);
1558
+ return next;
1559
+ });
1560
+ }
1561
+ #cancelWakesForGoal(goalId, now) {
1562
+ return this.wakes().filter((wake) => wake.goalId === goalId && (wake.status === "queued" || wake.status === "claimed")).map((wake) => this.#cancelWakeProjection(wake, now));
1563
+ }
1564
+ #repairOpenTurnItems(turnId, reason, now, actor) {
1565
+ const streamId = `turn:${turnId}`;
1566
+ const open = this.turnItems(turnId).filter((item) => item.status === "in_progress");
1567
+ const repaired = [];
1568
+ let ordinal = this.turnItems(turnId).length + 1;
1569
+ for (const item of open) {
1570
+ const failed = { ...item, status: "failed", completedAt: now };
1571
+ this.#recordProjection("turn_items", failed, actor, `item.${item.type}.failed`, void 0, now, void 0, streamId);
1572
+ repaired.push(failed);
1573
+ if (item.type === "tool_call") {
1574
+ const callId = String(item.data.callId ?? item.id);
1575
+ const result = { id: `repair:${turnId}:${item.ordinal}`, turnId, ordinal: ordinal++, type: "tool_result", status: "completed", data: { callId, result: { outcome: "unknown", synthetic: true, reason } }, createdAt: now, completedAt: now };
1576
+ this.#admitTurnItem(result, actor, streamId);
1577
+ this.#insertEvent({ streamId, ts: now, actor, type: "tool.completed", data: { callId, messageId: `repair:${callId}`, result: { outcome: "unknown", synthetic: true, reason }, isError: true } });
1578
+ repaired.push(result);
1579
+ }
1580
+ }
1581
+ return repaired;
1582
+ }
1583
+ #project(projection, snapshot, actor, type, wakeId, ts, streamId) {
1584
+ return this.#transaction(() => this.#recordProjection(projection, snapshot, actor, type, wakeId, ts, void 0, streamId));
1585
+ }
1586
+ #recordProjection(projection, snapshot, actor, type, wakeId, ts, expectedSeq, streamId) {
1587
+ const event = this.#insertEvent({ streamId: streamId ?? (wakeId ? wakeStream(wakeId) : controlStream(projection)), ts: ts ?? this.#now(), actor, type, data: { snapshot }, ignorable: true }, expectedSeq, projection);
1588
+ this.#faultInjector?.("after_event_before_projection");
1589
+ this.#applyProjection(projection, snapshot, event.seq);
1590
+ return event;
1591
+ }
1592
+ #insertEvent(input, expectedSeq, projection) {
1593
+ if (!input.streamId.trim() || !input.actor.trim() || !input.type.trim())
1594
+ throw new Error("event streamId, actor and type are required");
1595
+ const streamSeq = Number(this.db.prepare("SELECT COALESCE(MAX(stream_seq),0)+1 AS next FROM events WHERE stream_id=?").get(input.streamId).next);
1596
+ const result = expectedSeq === void 0 ? this.db.prepare("INSERT INTO events(stream_id,stream_seq,ts,actor,type,data,projection_name,ignorable) VALUES (?,?,?,?,?,json(?),?,?)").run(input.streamId, streamSeq, input.ts, input.actor, input.type, JSON.stringify(input.data), projection ?? null, input.ignorable === true ? 1 : null) : this.db.prepare("INSERT INTO events(seq,stream_id,stream_seq,ts,actor,type,data,projection_name,ignorable) VALUES (?,?,?,?,?,?,json(?),?,?)").run(expectedSeq, input.streamId, streamSeq, input.ts, input.actor, input.type, JSON.stringify(input.data), projection ?? null, input.ignorable === true ? 1 : null);
1597
+ return { ...input, seq: Number(result.lastInsertRowid), streamSeq };
1598
+ }
1599
+ #nextEventSeq() {
1600
+ const row = this.db.prepare("SELECT seq FROM sqlite_sequence WHERE name='events'").get();
1601
+ return Number(row?.seq ?? 0) + 1;
1602
+ }
1603
+ #applyProjection(projection, raw, sourceSeq) {
1604
+ if (projection === "threads") {
1605
+ const v = raw;
1606
+ this.db.prepare(`INSERT INTO threads VALUES (?,?,?,?,?,?) ON CONFLICT(id) DO UPDATE SET agent=excluded.agent,role=excluded.role,parent_thread_id=excluded.parent_thread_id,updated_at=excluded.updated_at`).run(v.id, v.agent, v.role, v.parentThreadId, v.createdAt, v.updatedAt);
1607
+ } else if (projection === "turns") {
1608
+ const v = raw;
1609
+ this.db.prepare(`INSERT INTO turns VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?) ON CONFLICT(id) DO UPDATE SET goal_id=excluded.goal_id,goal_revision=excluded.goal_revision,status=excluded.status,attempt=excluded.attempt,error=excluded.error,ended_at=excluded.ended_at,lease_until=excluded.lease_until,lease_token=excluded.lease_token,runner_pid=excluded.runner_pid`).run(v.id, v.threadId, v.triggerKind, v.goalId, v.goalRevision, v.status, v.attempt, v.error === null ? null : JSON.stringify(v.error), v.startedAt, v.endedAt, v.leaseUntil, v.leaseToken, v.runnerPid, v.runnerProfileId ?? null);
1610
+ this.db.prepare("UPDATE threads SET updated_at=CASE WHEN updated_at<? THEN ? ELSE updated_at END WHERE id=?").run(v.endedAt ?? v.startedAt, v.endedAt ?? v.startedAt, v.threadId);
1611
+ } else if (projection === "turn_items") {
1612
+ const v = raw;
1613
+ this.db.prepare(`INSERT INTO turn_items VALUES (?,?,?,?,?,json(?),?,?) ON CONFLICT(id) DO UPDATE SET status=excluded.status,data=excluded.data,completed_at=excluded.completed_at`).run(v.id, v.turnId, v.ordinal, v.type, v.status, JSON.stringify(v.data), v.createdAt, v.completedAt);
1614
+ this.db.prepare("UPDATE threads SET updated_at=CASE WHEN updated_at<? THEN ? ELSE updated_at END WHERE id=(SELECT thread_id FROM turns WHERE id=?)").run(v.completedAt ?? v.createdAt, v.completedAt ?? v.createdAt, v.turnId);
1615
+ } else if (projection === "goals") {
1616
+ const v = normalizeGoal(raw);
1617
+ this.db.prepare(`INSERT INTO goals VALUES (?,?,?,?,?,?,?,?) ON CONFLICT(id) DO UPDATE SET parent_id=excluded.parent_id,objective=excluded.objective,observation_method=excluded.observation_method,verification_method=excluded.verification_method,owner=excluded.owner,phase=excluded.phase,revision=excluded.revision`).run(v.id, v.parentId, v.objective, v.observationMethod, v.verificationMethod ?? null, v.owner, v.phase, v.revision);
1618
+ } else if (projection === "work_records") {
1619
+ const v = raw;
1620
+ this.db.prepare(`INSERT INTO work_records VALUES (?,?,?,?,?,?,?,?,?,?,?) ON CONFLICT(goal_id) DO UPDATE SET record_revision=excluded.record_revision,goal_revision=excluded.goal_revision,content=excluded.content,updated_by=excluded.updated_by,updated_in_turn=excluded.updated_in_turn,source_wake_id=excluded.source_wake_id,updated_at=excluded.updated_at,reason=excluded.reason,evidence=excluded.evidence,last_event_seq=excluded.last_event_seq`).run(v.goalId, v.recordRevision, v.goalRevision, v.content, v.updatedBy, v.updatedInTurn, v.sourceWakeId, v.updatedAt, v.reason, JSON.stringify(v.evidence), v.lastEventSeq || sourceSeq);
1621
+ } else if (projection === "schedule") {
1622
+ const v = raw;
1623
+ this.db.prepare(`INSERT INTO schedule VALUES (?,?,?,?,?,?,?,?,?,?) ON CONFLICT(id) DO UPDATE SET target_kind=excluded.target_kind,agent=excluded.agent,specialist_role=excluded.specialist_role,next_wake_at=excluded.next_wake_at,reason=excluded.reason,set_by=excluded.set_by,status=excluded.status,resolved_at=excluded.resolved_at,goal_id=excluded.goal_id`).run(v.id, v.targetKind, v.agent, v.specialistRole, v.nextWakeAt, v.reason, v.setBy, v.status, v.resolvedAt, v.goalId);
1624
+ } else if (projection === "wakes") {
1625
+ const v = raw;
1626
+ this.db.prepare(`INSERT INTO wakes VALUES (?,?,?,?,?,?,?,?,?,?,?,?) ON CONFLICT(id) DO UPDATE SET target_kind=excluded.target_kind,agent=excluded.agent,specialist_role=excluded.specialist_role,trigger_ref=excluded.trigger_ref,status=excluded.status,attempt=excluded.attempt,enqueued_seq=excluded.enqueued_seq,claimed_at=excluded.claimed_at,consumed_at=excluded.consumed_at,turn_id=excluded.turn_id,goal_id=excluded.goal_id`).run(v.id, v.targetKind, v.agent, v.specialistRole, v.triggerRef, v.status, v.attempt, v.enqueuedSeq || sourceSeq, v.claimedAt, v.consumedAt, v.turnId, v.goalId);
1627
+ } else if (projection === "wake_triggers") {
1628
+ const v = raw;
1629
+ this.db.prepare(`INSERT INTO wake_triggers VALUES (?,?,?,?,?,?,?) ON CONFLICT(wake_id,trigger_ref) DO UPDATE SET status=excluded.status,resolved_at=excluded.resolved_at`).run(v.wakeId, v.agent, v.triggerRef, v.source, v.status, v.addedAt, v.resolvedAt);
1630
+ } else if (projection === "mailbox") {
1631
+ const v = raw;
1632
+ this.db.prepare(`INSERT INTO mailbox VALUES (?,?,?,?,?,?,?,json(?),?) ON CONFLICT(id) DO UPDATE SET route_kind=excluded.route_kind,to_agent=excluded.to_agent,from_agent=excluded.from_agent,specialist_role=excluded.specialist_role,level=excluded.level,goal_id=excluded.goal_id,body=excluded.body,read_at=excluded.read_at`).run(v.id, v.routeKind, v.to, v.from, v.specialistRole, v.level, v.goalId, JSON.stringify(v.body), v.readAt);
1633
+ }
1634
+ }
1635
+ #transaction(fn) {
1636
+ this.db.exec("BEGIN IMMEDIATE");
1637
+ try {
1638
+ const result = fn();
1639
+ this.db.exec("COMMIT");
1640
+ return result;
1641
+ } catch (error) {
1642
+ this.db.exec("ROLLBACK");
1643
+ throw error;
1644
+ }
1645
+ }
1646
+ #now() {
1647
+ return this.#clock.now().toISOString();
1648
+ }
1649
+ };
1650
+ function mapEvent(r) {
1651
+ return { seq: Number(r.seq), streamId: String(r.stream_id), streamSeq: Number(r.stream_seq), ts: String(r.ts), actor: String(r.actor), type: String(r.type), data: JSON.parse(String(r.data)), ...Number(r.ignorable) === 1 ? { ignorable: true } : {} };
1652
+ }
1653
+ function mapThread(r) {
1654
+ return { id: String(r.id), agent: String(r.agent), role: String(r.role), parentThreadId: r.parent_thread_id === null ? null : String(r.parent_thread_id), createdAt: String(r.created_at), updatedAt: String(r.updated_at) };
1655
+ }
1656
+ function mapTurn(r) {
1657
+ return { id: String(r.id), threadId: String(r.thread_id), triggerKind: String(r.trigger_kind), goalId: r.goal_id === null ? null : String(r.goal_id), goalRevision: r.goal_revision === null ? null : Number(r.goal_revision), status: String(r.status), attempt: Number(r.attempt), error: r.error === null ? null : JSON.parse(String(r.error)), startedAt: String(r.started_at), endedAt: r.ended_at === null ? null : String(r.ended_at), leaseUntil: r.lease_until === null ? null : String(r.lease_until), leaseToken: r.lease_token === null ? null : String(r.lease_token), runnerPid: r.runner_pid === null ? null : Number(r.runner_pid), ...r.runner_profile_id === null || r.runner_profile_id === void 0 ? {} : { runnerProfileId: String(r.runner_profile_id) } };
1658
+ }
1659
+ function mapTurnItem(r) {
1660
+ return { id: String(r.id), turnId: String(r.turn_id), ordinal: Number(r.ordinal), type: String(r.type), status: String(r.status), data: JSON.parse(String(r.data)), createdAt: String(r.created_at), completedAt: r.completed_at === null ? null : String(r.completed_at) };
1661
+ }
1662
+ function normalizeGoal(goal) {
1663
+ return { ...goal, observationMethod: goal.observationMethod ?? null, verificationMethod: goal.verificationMethod === void 0 ? goal.observationMethod ?? null : goal.verificationMethod };
1664
+ }
1665
+ function mapGoal(r) {
1666
+ return { id: String(r.id), parentId: r.parent_id === null ? null : String(r.parent_id), objective: String(r.objective), observationMethod: r.observation_method === null || r.observation_method === void 0 ? null : String(r.observation_method), verificationMethod: r.verification_method === null || r.verification_method === void 0 ? null : String(r.verification_method), owner: String(r.owner), phase: String(r.phase), revision: Number(r.revision) };
1667
+ }
1668
+ function mapWorkRecord(r) {
1669
+ return { goalId: String(r.goal_id), recordRevision: Number(r.record_revision), goalRevision: Number(r.goal_revision), content: String(r.content), updatedBy: String(r.updated_by), updatedInTurn: String(r.updated_in_turn), sourceWakeId: r.source_wake_id === null ? null : String(r.source_wake_id), updatedAt: String(r.updated_at), reason: String(r.reason), evidence: JSON.parse(String(r.evidence)), lastEventSeq: Number(r.last_event_seq) };
1670
+ }
1671
+ function mapWorkRecordEvent(event) {
1672
+ const data = event.data;
1673
+ if (!data.snapshot)
1674
+ throw new Error(`work record event ${event.seq} has no snapshot`);
1675
+ return { ...data.snapshot, lastEventSeq: event.seq };
1676
+ }
1677
+ function initialWorkRecord() {
1678
+ return "# Current State\n\nGoal created. Work has not started.\n\n# Observations\n\n# Work Completed\n\n# Decisions\n\n# Blockers\n\n# Next Steps\n";
1679
+ }
1680
+ function goalOperation(previous, next) {
1681
+ if (!previous)
1682
+ return "create";
1683
+ if (previous.phase !== next.phase) {
1684
+ if (next.phase === "paused")
1685
+ return "pause";
1686
+ if (next.phase === "active")
1687
+ return "resume";
1688
+ if (next.phase === "blocked")
1689
+ return "block";
1690
+ if (next.phase === "complete")
1691
+ return "complete";
1692
+ }
1693
+ return previous.owner !== next.owner ? "reassign" : "revise";
1694
+ }
1695
+ function defaultGoalReason(operation) {
1696
+ return `Goal ${operation}`;
1697
+ }
1698
+ function withoutSourceTurn(value) {
1699
+ const { sourceTurnId: _, ...semantic } = value;
1700
+ return semantic;
1701
+ }
1702
+ function projectionForEvent(type) {
1703
+ if (type === "thread.put")
1704
+ return "threads";
1705
+ if (type.startsWith("turn."))
1706
+ return "turns";
1707
+ if (type.startsWith("item."))
1708
+ return "turn_items";
1709
+ if (type === "goal.changed")
1710
+ return "goals";
1711
+ if (type.startsWith("schedule."))
1712
+ return "schedule";
1713
+ if (type.startsWith("wake_trigger."))
1714
+ return "wake_triggers";
1715
+ if (type.startsWith("wake."))
1716
+ return "wakes";
1717
+ if (type.startsWith("mail."))
1718
+ return "mailbox";
1719
+ if (type === "work_record.created" || type === "work_record.updated")
1720
+ return "work_records";
1721
+ return null;
1722
+ }
1723
+ function lineDiff(from, to) {
1724
+ const before = from.split("\n");
1725
+ const after = to.split("\n");
1726
+ const lines = [];
1727
+ for (let index = 0; index < Math.max(before.length, after.length); index += 1) {
1728
+ if (before[index] === after[index]) {
1729
+ if (before[index] !== void 0)
1730
+ lines.push(` ${before[index]}`);
1731
+ continue;
1732
+ }
1733
+ if (before[index] !== void 0)
1734
+ lines.push(`-${before[index]}`);
1735
+ if (after[index] !== void 0)
1736
+ lines.push(`+${after[index]}`);
1737
+ }
1738
+ return lines.join("\n");
1739
+ }
1740
+ function mapSchedule(r) {
1741
+ return { id: String(r.id), targetKind: String(r.target_kind), agent: String(r.agent), specialistRole: r.specialist_role === null ? null : String(r.specialist_role), goalId: r.goal_id === null ? null : String(r.goal_id), nextWakeAt: String(r.next_wake_at), reason: String(r.reason), setBy: String(r.set_by), status: String(r.status), resolvedAt: r.resolved_at === null ? null : String(r.resolved_at) };
1742
+ }
1743
+ function mapWake(r) {
1744
+ return { id: String(r.id), targetKind: String(r.target_kind), agent: String(r.agent), specialistRole: r.specialist_role === null ? null : String(r.specialist_role), goalId: r.goal_id === null ? null : String(r.goal_id), triggerRef: String(r.trigger_ref), status: String(r.status), attempt: Number(r.attempt), enqueuedSeq: Number(r.enqueued_seq), claimedAt: r.claimed_at === null ? null : String(r.claimed_at), consumedAt: r.consumed_at === null ? null : String(r.consumed_at), turnId: r.turn_id === null ? null : String(r.turn_id) };
1745
+ }
1746
+ function sameTurnExceptRunnerPid(left, right) {
1747
+ const { runnerPid: _left, ...leftRest } = left;
1748
+ const { runnerPid: _right, ...rightRest } = right;
1749
+ return isDeepStrictEqual(leftRest, rightRest);
1750
+ }
1751
+ function sameWakeTurnTarget(wake, turn, thread) {
1752
+ if (turn.triggerKind !== "wake")
1753
+ return false;
1754
+ if (wake.targetKind === "goal")
1755
+ return wake.goalId === turn.goalId;
1756
+ return turn.goalId === null && thread.role === wake.specialistRole;
1757
+ }
1758
+ function mapWakeTrigger(r) {
1759
+ return { wakeId: String(r.wake_id), agent: String(r.agent), triggerRef: String(r.trigger_ref), source: String(r.source), status: String(r.status), addedAt: String(r.added_at), resolvedAt: r.resolved_at === null ? null : String(r.resolved_at) };
1760
+ }
1761
+ function mapMail(r) {
1762
+ return { id: String(r.id), routeKind: String(r.route_kind), to: String(r.to_agent), from: String(r.from_agent), specialistRole: r.specialist_role === null ? null : String(r.specialist_role), level: String(r.level), goalId: r.goal_id === null ? null : String(r.goal_id), body: JSON.parse(String(r.body)), readAt: r.read_at === null ? null : String(r.read_at) };
1763
+ }
1764
+ function mailIdFromTrigger(trigger) {
1765
+ if (!trigger.startsWith("mail:"))
1766
+ return null;
1767
+ return trigger.slice("mail:".length).split("@redelivery:")[0] || null;
1768
+ }
1769
+
1770
+ export {
1771
+ SQLITE_SCHEMA_VERSION,
1772
+ SqliteLedger
1773
+ };