@stoneforge/quarry 0.1.0

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 (330) hide show
  1. package/LICENSE +13 -0
  2. package/README.md +160 -0
  3. package/dist/api/index.d.ts +8 -0
  4. package/dist/api/index.d.ts.map +1 -0
  5. package/dist/api/index.js +8 -0
  6. package/dist/api/index.js.map +1 -0
  7. package/dist/api/quarry-api.d.ts +268 -0
  8. package/dist/api/quarry-api.d.ts.map +1 -0
  9. package/dist/api/quarry-api.js +3905 -0
  10. package/dist/api/quarry-api.js.map +1 -0
  11. package/dist/api/types.d.ts +1359 -0
  12. package/dist/api/types.d.ts.map +1 -0
  13. package/dist/api/types.js +204 -0
  14. package/dist/api/types.js.map +1 -0
  15. package/dist/bin/sf.d.ts +3 -0
  16. package/dist/bin/sf.d.ts.map +1 -0
  17. package/dist/bin/sf.js +9 -0
  18. package/dist/bin/sf.js.map +1 -0
  19. package/dist/cli/commands/admin.d.ts +11 -0
  20. package/dist/cli/commands/admin.d.ts.map +1 -0
  21. package/dist/cli/commands/admin.js +465 -0
  22. package/dist/cli/commands/admin.js.map +1 -0
  23. package/dist/cli/commands/alias.d.ts +8 -0
  24. package/dist/cli/commands/alias.d.ts.map +1 -0
  25. package/dist/cli/commands/alias.js +70 -0
  26. package/dist/cli/commands/alias.js.map +1 -0
  27. package/dist/cli/commands/channel.d.ts +13 -0
  28. package/dist/cli/commands/channel.d.ts.map +1 -0
  29. package/dist/cli/commands/channel.js +680 -0
  30. package/dist/cli/commands/channel.js.map +1 -0
  31. package/dist/cli/commands/completion.d.ts +8 -0
  32. package/dist/cli/commands/completion.d.ts.map +1 -0
  33. package/dist/cli/commands/completion.js +87 -0
  34. package/dist/cli/commands/completion.js.map +1 -0
  35. package/dist/cli/commands/config.d.ts +12 -0
  36. package/dist/cli/commands/config.d.ts.map +1 -0
  37. package/dist/cli/commands/config.js +242 -0
  38. package/dist/cli/commands/config.js.map +1 -0
  39. package/dist/cli/commands/crud.d.ts +64 -0
  40. package/dist/cli/commands/crud.d.ts.map +1 -0
  41. package/dist/cli/commands/crud.js +805 -0
  42. package/dist/cli/commands/crud.js.map +1 -0
  43. package/dist/cli/commands/dep.d.ts +16 -0
  44. package/dist/cli/commands/dep.d.ts.map +1 -0
  45. package/dist/cli/commands/dep.js +499 -0
  46. package/dist/cli/commands/dep.js.map +1 -0
  47. package/dist/cli/commands/document.d.ts +12 -0
  48. package/dist/cli/commands/document.d.ts.map +1 -0
  49. package/dist/cli/commands/document.js +1039 -0
  50. package/dist/cli/commands/document.js.map +1 -0
  51. package/dist/cli/commands/embeddings.d.ts +12 -0
  52. package/dist/cli/commands/embeddings.d.ts.map +1 -0
  53. package/dist/cli/commands/embeddings.js +273 -0
  54. package/dist/cli/commands/embeddings.js.map +1 -0
  55. package/dist/cli/commands/entity.d.ts +16 -0
  56. package/dist/cli/commands/entity.d.ts.map +1 -0
  57. package/dist/cli/commands/entity.js +522 -0
  58. package/dist/cli/commands/entity.js.map +1 -0
  59. package/dist/cli/commands/gc.d.ts +10 -0
  60. package/dist/cli/commands/gc.d.ts.map +1 -0
  61. package/dist/cli/commands/gc.js +257 -0
  62. package/dist/cli/commands/gc.js.map +1 -0
  63. package/dist/cli/commands/help.d.ts +11 -0
  64. package/dist/cli/commands/help.d.ts.map +1 -0
  65. package/dist/cli/commands/help.js +169 -0
  66. package/dist/cli/commands/help.js.map +1 -0
  67. package/dist/cli/commands/history.d.ts +9 -0
  68. package/dist/cli/commands/history.d.ts.map +1 -0
  69. package/dist/cli/commands/history.js +160 -0
  70. package/dist/cli/commands/history.js.map +1 -0
  71. package/dist/cli/commands/identity.d.ts +18 -0
  72. package/dist/cli/commands/identity.d.ts.map +1 -0
  73. package/dist/cli/commands/identity.js +698 -0
  74. package/dist/cli/commands/identity.js.map +1 -0
  75. package/dist/cli/commands/inbox.d.ts +20 -0
  76. package/dist/cli/commands/inbox.d.ts.map +1 -0
  77. package/dist/cli/commands/inbox.js +493 -0
  78. package/dist/cli/commands/inbox.js.map +1 -0
  79. package/dist/cli/commands/init.d.ts +20 -0
  80. package/dist/cli/commands/init.d.ts.map +1 -0
  81. package/dist/cli/commands/init.js +144 -0
  82. package/dist/cli/commands/init.js.map +1 -0
  83. package/dist/cli/commands/install.d.ts +9 -0
  84. package/dist/cli/commands/install.d.ts.map +1 -0
  85. package/dist/cli/commands/install.js +200 -0
  86. package/dist/cli/commands/install.js.map +1 -0
  87. package/dist/cli/commands/library.d.ts +12 -0
  88. package/dist/cli/commands/library.d.ts.map +1 -0
  89. package/dist/cli/commands/library.js +665 -0
  90. package/dist/cli/commands/library.js.map +1 -0
  91. package/dist/cli/commands/message.d.ts +11 -0
  92. package/dist/cli/commands/message.d.ts.map +1 -0
  93. package/dist/cli/commands/message.js +608 -0
  94. package/dist/cli/commands/message.js.map +1 -0
  95. package/dist/cli/commands/plan.d.ts +17 -0
  96. package/dist/cli/commands/plan.d.ts.map +1 -0
  97. package/dist/cli/commands/plan.js +698 -0
  98. package/dist/cli/commands/plan.js.map +1 -0
  99. package/dist/cli/commands/playbook.d.ts +12 -0
  100. package/dist/cli/commands/playbook.d.ts.map +1 -0
  101. package/dist/cli/commands/playbook.js +730 -0
  102. package/dist/cli/commands/playbook.js.map +1 -0
  103. package/dist/cli/commands/reset.d.ts +12 -0
  104. package/dist/cli/commands/reset.d.ts.map +1 -0
  105. package/dist/cli/commands/reset.js +306 -0
  106. package/dist/cli/commands/reset.js.map +1 -0
  107. package/dist/cli/commands/serve.d.ts +11 -0
  108. package/dist/cli/commands/serve.d.ts.map +1 -0
  109. package/dist/cli/commands/serve.js +106 -0
  110. package/dist/cli/commands/serve.js.map +1 -0
  111. package/dist/cli/commands/stats.d.ts +8 -0
  112. package/dist/cli/commands/stats.d.ts.map +1 -0
  113. package/dist/cli/commands/stats.js +82 -0
  114. package/dist/cli/commands/stats.js.map +1 -0
  115. package/dist/cli/commands/sync.d.ts +14 -0
  116. package/dist/cli/commands/sync.d.ts.map +1 -0
  117. package/dist/cli/commands/sync.js +370 -0
  118. package/dist/cli/commands/sync.js.map +1 -0
  119. package/dist/cli/commands/task.d.ts +25 -0
  120. package/dist/cli/commands/task.d.ts.map +1 -0
  121. package/dist/cli/commands/task.js +1153 -0
  122. package/dist/cli/commands/task.js.map +1 -0
  123. package/dist/cli/commands/team.d.ts +13 -0
  124. package/dist/cli/commands/team.d.ts.map +1 -0
  125. package/dist/cli/commands/team.js +471 -0
  126. package/dist/cli/commands/team.js.map +1 -0
  127. package/dist/cli/commands/workflow.d.ts +16 -0
  128. package/dist/cli/commands/workflow.d.ts.map +1 -0
  129. package/dist/cli/commands/workflow.js +753 -0
  130. package/dist/cli/commands/workflow.js.map +1 -0
  131. package/dist/cli/completion.d.ts +28 -0
  132. package/dist/cli/completion.d.ts.map +1 -0
  133. package/dist/cli/completion.js +295 -0
  134. package/dist/cli/completion.js.map +1 -0
  135. package/dist/cli/db.d.ts +38 -0
  136. package/dist/cli/db.d.ts.map +1 -0
  137. package/dist/cli/db.js +90 -0
  138. package/dist/cli/db.js.map +1 -0
  139. package/dist/cli/formatter.d.ts +87 -0
  140. package/dist/cli/formatter.d.ts.map +1 -0
  141. package/dist/cli/formatter.js +464 -0
  142. package/dist/cli/formatter.js.map +1 -0
  143. package/dist/cli/index.d.ts +33 -0
  144. package/dist/cli/index.d.ts.map +1 -0
  145. package/dist/cli/index.js +38 -0
  146. package/dist/cli/index.js.map +1 -0
  147. package/dist/cli/parser.d.ts +45 -0
  148. package/dist/cli/parser.d.ts.map +1 -0
  149. package/dist/cli/parser.js +256 -0
  150. package/dist/cli/parser.js.map +1 -0
  151. package/dist/cli/plugin-loader.d.ts +39 -0
  152. package/dist/cli/plugin-loader.d.ts.map +1 -0
  153. package/dist/cli/plugin-loader.js +165 -0
  154. package/dist/cli/plugin-loader.js.map +1 -0
  155. package/dist/cli/plugin-registry.d.ts +50 -0
  156. package/dist/cli/plugin-registry.d.ts.map +1 -0
  157. package/dist/cli/plugin-registry.js +206 -0
  158. package/dist/cli/plugin-registry.js.map +1 -0
  159. package/dist/cli/plugin-types.d.ts +106 -0
  160. package/dist/cli/plugin-types.d.ts.map +1 -0
  161. package/dist/cli/plugin-types.js +103 -0
  162. package/dist/cli/plugin-types.js.map +1 -0
  163. package/dist/cli/runner.d.ts +35 -0
  164. package/dist/cli/runner.d.ts.map +1 -0
  165. package/dist/cli/runner.js +340 -0
  166. package/dist/cli/runner.js.map +1 -0
  167. package/dist/cli/suggest.d.ts +15 -0
  168. package/dist/cli/suggest.d.ts.map +1 -0
  169. package/dist/cli/suggest.js +49 -0
  170. package/dist/cli/suggest.js.map +1 -0
  171. package/dist/cli/types.d.ts +138 -0
  172. package/dist/cli/types.d.ts.map +1 -0
  173. package/dist/cli/types.js +63 -0
  174. package/dist/cli/types.js.map +1 -0
  175. package/dist/config/config.d.ts +86 -0
  176. package/dist/config/config.d.ts.map +1 -0
  177. package/dist/config/config.js +348 -0
  178. package/dist/config/config.js.map +1 -0
  179. package/dist/config/defaults.d.ts +66 -0
  180. package/dist/config/defaults.d.ts.map +1 -0
  181. package/dist/config/defaults.js +114 -0
  182. package/dist/config/defaults.js.map +1 -0
  183. package/dist/config/duration.d.ts +75 -0
  184. package/dist/config/duration.d.ts.map +1 -0
  185. package/dist/config/duration.js +190 -0
  186. package/dist/config/duration.js.map +1 -0
  187. package/dist/config/env.d.ts +67 -0
  188. package/dist/config/env.d.ts.map +1 -0
  189. package/dist/config/env.js +207 -0
  190. package/dist/config/env.js.map +1 -0
  191. package/dist/config/file.d.ts +97 -0
  192. package/dist/config/file.d.ts.map +1 -0
  193. package/dist/config/file.js +365 -0
  194. package/dist/config/file.js.map +1 -0
  195. package/dist/config/index.d.ts +35 -0
  196. package/dist/config/index.d.ts.map +1 -0
  197. package/dist/config/index.js +41 -0
  198. package/dist/config/index.js.map +1 -0
  199. package/dist/config/merge.d.ts +53 -0
  200. package/dist/config/merge.d.ts.map +1 -0
  201. package/dist/config/merge.js +226 -0
  202. package/dist/config/merge.js.map +1 -0
  203. package/dist/config/types.d.ts +257 -0
  204. package/dist/config/types.d.ts.map +1 -0
  205. package/dist/config/types.js +72 -0
  206. package/dist/config/types.js.map +1 -0
  207. package/dist/config/validation.d.ts +55 -0
  208. package/dist/config/validation.d.ts.map +1 -0
  209. package/dist/config/validation.js +251 -0
  210. package/dist/config/validation.js.map +1 -0
  211. package/dist/http/index.d.ts +8 -0
  212. package/dist/http/index.d.ts.map +1 -0
  213. package/dist/http/index.js +12 -0
  214. package/dist/http/index.js.map +1 -0
  215. package/dist/http/sync-handlers.d.ts +162 -0
  216. package/dist/http/sync-handlers.d.ts.map +1 -0
  217. package/dist/http/sync-handlers.js +271 -0
  218. package/dist/http/sync-handlers.js.map +1 -0
  219. package/dist/index.d.ts +25 -0
  220. package/dist/index.d.ts.map +1 -0
  221. package/dist/index.js +69 -0
  222. package/dist/index.js.map +1 -0
  223. package/dist/server/index.d.ts +34 -0
  224. package/dist/server/index.d.ts.map +1 -0
  225. package/dist/server/index.js +3329 -0
  226. package/dist/server/index.js.map +1 -0
  227. package/dist/server/static.d.ts +18 -0
  228. package/dist/server/static.d.ts.map +1 -0
  229. package/dist/server/static.js +71 -0
  230. package/dist/server/static.js.map +1 -0
  231. package/dist/server/ws/broadcaster.d.ts +8 -0
  232. package/dist/server/ws/broadcaster.d.ts.map +1 -0
  233. package/dist/server/ws/broadcaster.js +7 -0
  234. package/dist/server/ws/broadcaster.js.map +1 -0
  235. package/dist/server/ws/handler.d.ts +55 -0
  236. package/dist/server/ws/handler.d.ts.map +1 -0
  237. package/dist/server/ws/handler.js +160 -0
  238. package/dist/server/ws/handler.js.map +1 -0
  239. package/dist/services/blocked-cache.d.ts +297 -0
  240. package/dist/services/blocked-cache.d.ts.map +1 -0
  241. package/dist/services/blocked-cache.js +755 -0
  242. package/dist/services/blocked-cache.js.map +1 -0
  243. package/dist/services/dependency.d.ts +205 -0
  244. package/dist/services/dependency.d.ts.map +1 -0
  245. package/dist/services/dependency.js +566 -0
  246. package/dist/services/dependency.js.map +1 -0
  247. package/dist/services/embeddings/fusion.d.ts +33 -0
  248. package/dist/services/embeddings/fusion.d.ts.map +1 -0
  249. package/dist/services/embeddings/fusion.js +34 -0
  250. package/dist/services/embeddings/fusion.js.map +1 -0
  251. package/dist/services/embeddings/index.d.ts +12 -0
  252. package/dist/services/embeddings/index.d.ts.map +1 -0
  253. package/dist/services/embeddings/index.js +10 -0
  254. package/dist/services/embeddings/index.js.map +1 -0
  255. package/dist/services/embeddings/local-provider.d.ts +31 -0
  256. package/dist/services/embeddings/local-provider.d.ts.map +1 -0
  257. package/dist/services/embeddings/local-provider.js +80 -0
  258. package/dist/services/embeddings/local-provider.js.map +1 -0
  259. package/dist/services/embeddings/service.d.ts +76 -0
  260. package/dist/services/embeddings/service.d.ts.map +1 -0
  261. package/dist/services/embeddings/service.js +153 -0
  262. package/dist/services/embeddings/service.js.map +1 -0
  263. package/dist/services/embeddings/types.d.ts +70 -0
  264. package/dist/services/embeddings/types.d.ts.map +1 -0
  265. package/dist/services/embeddings/types.js +8 -0
  266. package/dist/services/embeddings/types.js.map +1 -0
  267. package/dist/services/id-length-cache.d.ts +156 -0
  268. package/dist/services/id-length-cache.d.ts.map +1 -0
  269. package/dist/services/id-length-cache.js +197 -0
  270. package/dist/services/id-length-cache.js.map +1 -0
  271. package/dist/services/inbox.d.ts +147 -0
  272. package/dist/services/inbox.d.ts.map +1 -0
  273. package/dist/services/inbox.js +428 -0
  274. package/dist/services/inbox.js.map +1 -0
  275. package/dist/services/index.d.ts +10 -0
  276. package/dist/services/index.d.ts.map +1 -0
  277. package/dist/services/index.js +10 -0
  278. package/dist/services/index.js.map +1 -0
  279. package/dist/services/priority-service.d.ts +145 -0
  280. package/dist/services/priority-service.d.ts.map +1 -0
  281. package/dist/services/priority-service.js +272 -0
  282. package/dist/services/priority-service.js.map +1 -0
  283. package/dist/services/search-utils.d.ts +47 -0
  284. package/dist/services/search-utils.d.ts.map +1 -0
  285. package/dist/services/search-utils.js +83 -0
  286. package/dist/services/search-utils.js.map +1 -0
  287. package/dist/sync/hash.d.ts +48 -0
  288. package/dist/sync/hash.d.ts.map +1 -0
  289. package/dist/sync/hash.js +136 -0
  290. package/dist/sync/hash.js.map +1 -0
  291. package/dist/sync/index.d.ts +11 -0
  292. package/dist/sync/index.d.ts.map +1 -0
  293. package/dist/sync/index.js +16 -0
  294. package/dist/sync/index.js.map +1 -0
  295. package/dist/sync/merge.d.ts +80 -0
  296. package/dist/sync/merge.d.ts.map +1 -0
  297. package/dist/sync/merge.js +310 -0
  298. package/dist/sync/merge.js.map +1 -0
  299. package/dist/sync/serialization.d.ts +132 -0
  300. package/dist/sync/serialization.d.ts.map +1 -0
  301. package/dist/sync/serialization.js +306 -0
  302. package/dist/sync/serialization.js.map +1 -0
  303. package/dist/sync/service.d.ts +102 -0
  304. package/dist/sync/service.d.ts.map +1 -0
  305. package/dist/sync/service.js +493 -0
  306. package/dist/sync/service.js.map +1 -0
  307. package/dist/sync/types.d.ts +275 -0
  308. package/dist/sync/types.d.ts.map +1 -0
  309. package/dist/sync/types.js +76 -0
  310. package/dist/sync/types.js.map +1 -0
  311. package/dist/systems/identity.d.ts +479 -0
  312. package/dist/systems/identity.d.ts.map +1 -0
  313. package/dist/systems/identity.js +817 -0
  314. package/dist/systems/identity.js.map +1 -0
  315. package/dist/systems/index.d.ts +8 -0
  316. package/dist/systems/index.d.ts.map +1 -0
  317. package/dist/systems/index.js +29 -0
  318. package/dist/systems/index.js.map +1 -0
  319. package/package.json +121 -0
  320. package/web/assets/charts-vendor-D1YcbGux.js +55 -0
  321. package/web/assets/dnd-vendor-DmxE-_ZH.js +5 -0
  322. package/web/assets/editor-vendor-BxraAWts.js +279 -0
  323. package/web/assets/index-B77vv208.js +341 -0
  324. package/web/assets/index-CF_XnVLh.css +1 -0
  325. package/web/assets/router-vendor-BCKpRBrB.js +41 -0
  326. package/web/assets/ui-vendor-DUahGnbT.js +45 -0
  327. package/web/assets/utils-vendor-CfYKiENT.js +813 -0
  328. package/web/favicon.ico +0 -0
  329. package/web/index.html +23 -0
  330. package/web/logo.png +0 -0
@@ -0,0 +1,755 @@
1
+ /**
2
+ * Blocked Cache Service
3
+ *
4
+ * Maintains a materialized view of blocked elements for efficient ready-work queries.
5
+ * The blocked_cache table provides ~25x speedup for large datasets by avoiding
6
+ * recursive dependency checks on every query.
7
+ *
8
+ * Blocking Rules:
9
+ * - `blocks` dependency: Blocked element waits for blocker to close
10
+ * - `parent-child` dependency: Blocked element (child) inherits blocked state from blocker (parent) (transitive)
11
+ * - `awaits` dependency: Blocked element waits until gate is satisfied (timer, approval, etc.)
12
+ *
13
+ * Cache Invalidation Triggers:
14
+ * - Blocking dependency added/removed
15
+ * - Element status changes (especially closing)
16
+ * - Gate satisfaction changes
17
+ * - Parent blocking state changes
18
+ */
19
+ import { DependencyType as DT, GateType, isValidAwaitsMetadata } from '@stoneforge/core';
20
+ // ============================================================================
21
+ // Types
22
+ // ============================================================================
23
+ /**
24
+ * Status values that indicate an element is "done" and doesn't block others
25
+ */
26
+ const COMPLETED_STATUSES = ['closed', 'completed', 'tombstone'];
27
+ // ============================================================================
28
+ // BlockedCacheService Class
29
+ // ============================================================================
30
+ /**
31
+ * Service for managing the blocked elements cache
32
+ */
33
+ export class BlockedCacheService {
34
+ db;
35
+ statusCallback;
36
+ constructor(db) {
37
+ this.db = db;
38
+ }
39
+ /**
40
+ * Set the callback for automatic status transitions
41
+ * This allows the service to notify when elements should be blocked/unblocked
42
+ */
43
+ setStatusTransitionCallback(callback) {
44
+ this.statusCallback = callback;
45
+ }
46
+ // --------------------------------------------------------------------------
47
+ // Query Operations
48
+ // --------------------------------------------------------------------------
49
+ /**
50
+ * Check if an element is blocked
51
+ *
52
+ * @param elementId - Element to check
53
+ * @returns Blocking info if blocked, null if not blocked
54
+ */
55
+ isBlocked(elementId) {
56
+ const row = this.db.queryOne('SELECT * FROM blocked_cache WHERE element_id = ?', [elementId]);
57
+ if (!row) {
58
+ return null;
59
+ }
60
+ return {
61
+ elementId: row.element_id,
62
+ blockedBy: row.blocked_by,
63
+ reason: row.reason ?? 'Blocked by dependency',
64
+ previousStatus: row.previous_status,
65
+ };
66
+ }
67
+ /**
68
+ * Get all blocked elements
69
+ *
70
+ * @returns Array of blocking info for all blocked elements
71
+ */
72
+ getAllBlocked() {
73
+ const rows = this.db.query('SELECT * FROM blocked_cache');
74
+ return rows.map((row) => ({
75
+ elementId: row.element_id,
76
+ blockedBy: row.blocked_by,
77
+ reason: row.reason ?? 'Blocked by dependency',
78
+ previousStatus: row.previous_status,
79
+ }));
80
+ }
81
+ /**
82
+ * Get all elements blocked by a specific element
83
+ *
84
+ * @param blockerId - The element causing blocks
85
+ * @returns Array of element IDs blocked by this element
86
+ */
87
+ getBlockedBy(blockerId) {
88
+ const rows = this.db.query('SELECT element_id FROM blocked_cache WHERE blocked_by = ?', [blockerId]);
89
+ return rows.map((row) => row.element_id);
90
+ }
91
+ /**
92
+ * Count blocked elements
93
+ */
94
+ count() {
95
+ const row = this.db.queryOne('SELECT COUNT(*) as count FROM blocked_cache');
96
+ return row?.count ?? 0;
97
+ }
98
+ // --------------------------------------------------------------------------
99
+ // Cache Maintenance
100
+ // --------------------------------------------------------------------------
101
+ /**
102
+ * Add a blocking entry to the cache
103
+ *
104
+ * @param elementId - Element being blocked
105
+ * @param blockedBy - Element causing the block
106
+ * @param reason - Human-readable reason
107
+ * @param previousStatus - The element's status before becoming blocked
108
+ */
109
+ addBlocked(elementId, blockedBy, reason, previousStatus) {
110
+ this.db.run(`INSERT OR REPLACE INTO blocked_cache (element_id, blocked_by, reason, previous_status)
111
+ VALUES (?, ?, ?, ?)`, [elementId, blockedBy, reason, previousStatus ?? null]);
112
+ }
113
+ /**
114
+ * Remove a blocking entry from the cache
115
+ *
116
+ * @param elementId - Element to unblock
117
+ */
118
+ removeBlocked(elementId) {
119
+ this.db.run('DELETE FROM blocked_cache WHERE element_id = ?', [elementId]);
120
+ }
121
+ /**
122
+ * Clear all entries from the cache
123
+ */
124
+ clear() {
125
+ this.db.run('DELETE FROM blocked_cache');
126
+ }
127
+ // --------------------------------------------------------------------------
128
+ // Blocking State Computation
129
+ // --------------------------------------------------------------------------
130
+ /**
131
+ * Check if an element is in a completed state (doesn't block others)
132
+ *
133
+ * @param elementId - Element to check
134
+ * @returns true if element is completed/closed/tombstone
135
+ */
136
+ isElementCompleted(elementId) {
137
+ const row = this.db.queryOne('SELECT id, data, deleted_at FROM elements WHERE id = ?', [elementId]);
138
+ // Element doesn't exist - treat as non-blocking (external reference)
139
+ if (!row) {
140
+ return true;
141
+ }
142
+ // Deleted (tombstone)
143
+ if (row.deleted_at) {
144
+ return true;
145
+ }
146
+ // Check status in data
147
+ try {
148
+ const data = JSON.parse(row.data);
149
+ const status = data.status;
150
+ return COMPLETED_STATUSES.includes(status);
151
+ }
152
+ catch {
153
+ return false;
154
+ }
155
+ }
156
+ /**
157
+ * Check if a blocker element is in a completed state (no longer blocking)
158
+ *
159
+ * @param blockerId - The blocker element to check
160
+ * @returns true if blocker is completed/closed/tombstone (no longer blocking)
161
+ */
162
+ isBlockerCompleted(blockerId) {
163
+ return this.isElementCompleted(blockerId);
164
+ }
165
+ /**
166
+ * Get the current status of an element
167
+ *
168
+ * @param elementId - Element to check
169
+ * @returns The element's status, or null if not found or no status
170
+ */
171
+ getElementStatus(elementId) {
172
+ const row = this.db.queryOne('SELECT data FROM elements WHERE id = ?', [elementId]);
173
+ if (!row) {
174
+ return null;
175
+ }
176
+ try {
177
+ const data = JSON.parse(row.data);
178
+ return data.status ?? null;
179
+ }
180
+ catch {
181
+ return null;
182
+ }
183
+ }
184
+ /**
185
+ * Check if element is a task (has status field)
186
+ */
187
+ isTask(elementId) {
188
+ const row = this.db.queryOne('SELECT type FROM elements WHERE id = ?', [elementId]);
189
+ return row?.type === 'task';
190
+ }
191
+ /**
192
+ * Check if element is a plan
193
+ */
194
+ isPlan(elementId) {
195
+ const row = this.db.queryOne('SELECT type FROM elements WHERE id = ?', [elementId]);
196
+ return row?.type === 'plan';
197
+ }
198
+ /**
199
+ * Check if an awaits gate is satisfied
200
+ *
201
+ * @param metadata - The awaits dependency metadata
202
+ * @param options - Gate check options
203
+ * @returns true if gate is satisfied (not blocking)
204
+ */
205
+ isGateSatisfied(metadata, options = {}) {
206
+ const now = options.currentTime ?? new Date();
207
+ switch (metadata.gateType) {
208
+ case GateType.TIMER:
209
+ // Timer gate: satisfied when current time >= waitUntil
210
+ const waitUntil = new Date(metadata.waitUntil);
211
+ return now >= waitUntil;
212
+ case GateType.APPROVAL:
213
+ // Approval gate: satisfied when enough approvers have approved
214
+ const required = metadata.approvalCount ?? metadata.requiredApprovers.length;
215
+ const current = metadata.currentApprovers?.length ?? 0;
216
+ return current >= required;
217
+ case GateType.EXTERNAL:
218
+ // External gates are satisfied when explicitly marked via API
219
+ return metadata.satisfied === true;
220
+ case GateType.WEBHOOK:
221
+ // Webhook gates are satisfied when explicitly marked via callback
222
+ return metadata.satisfied === true;
223
+ default:
224
+ return false;
225
+ }
226
+ }
227
+ /**
228
+ * Compute the blocking state for a single element
229
+ *
230
+ * @param elementId - Element to check
231
+ * @param options - Gate check options
232
+ * @returns Blocking info if blocked, null if not blocked
233
+ */
234
+ computeBlockingState(elementId, options = {}) {
235
+ // All blocking types now use consistent direction:
236
+ // blocked_id = element that is waiting, blocker_id = element doing the blocking
237
+ const blockingDeps = this.db.query(`SELECT * FROM dependencies
238
+ WHERE blocked_id = ? AND type IN (?, ?, ?)`, [elementId, DT.BLOCKS, DT.PARENT_CHILD, DT.AWAITS]);
239
+ for (const dep of blockingDeps) {
240
+ const blockerId = dep.blocker_id;
241
+ const type = dep.type;
242
+ switch (type) {
243
+ case DT.BLOCKS:
244
+ // Check if blocker is completed
245
+ if (!this.isBlockerCompleted(blockerId)) {
246
+ return {
247
+ elementId,
248
+ blockedBy: blockerId,
249
+ reason: `Blocked by ${blockerId} (blocks dependency)`,
250
+ };
251
+ }
252
+ break;
253
+ case DT.PARENT_CHILD:
254
+ // Check if parent is blocked (transitive)
255
+ const parentBlocked = this.isBlocked(blockerId);
256
+ if (parentBlocked) {
257
+ return {
258
+ elementId,
259
+ blockedBy: blockerId,
260
+ reason: `Blocked by parent ${blockerId} (parent is blocked)`,
261
+ };
262
+ }
263
+ // For task-task hierarchy: child is blocked until parent completes
264
+ // For task-plan hierarchy: tasks in a plan are NOT blocked by the plan's status
265
+ // Plans are collections, not blocking parents
266
+ if (!this.isPlan(blockerId) && !this.isBlockerCompleted(blockerId)) {
267
+ return {
268
+ elementId,
269
+ blockedBy: blockerId,
270
+ reason: `Blocked by parent ${blockerId} (parent not completed)`,
271
+ };
272
+ }
273
+ break;
274
+ case DT.AWAITS:
275
+ // Check if gate is satisfied
276
+ if (dep.metadata) {
277
+ try {
278
+ const metadata = JSON.parse(dep.metadata);
279
+ if (isValidAwaitsMetadata(metadata)) {
280
+ if (!this.isGateSatisfied(metadata, options)) {
281
+ return {
282
+ elementId,
283
+ blockedBy: blockerId,
284
+ reason: `Blocked by gate (${metadata.gateType})`,
285
+ };
286
+ }
287
+ }
288
+ }
289
+ catch {
290
+ // Invalid metadata, treat as blocking
291
+ return {
292
+ elementId,
293
+ blockedBy: blockerId,
294
+ reason: 'Blocked by gate (invalid metadata)',
295
+ };
296
+ }
297
+ }
298
+ break;
299
+ }
300
+ }
301
+ return null;
302
+ }
303
+ // --------------------------------------------------------------------------
304
+ // Invalidation
305
+ // --------------------------------------------------------------------------
306
+ /**
307
+ * Update blocking state for an element after a dependency change
308
+ *
309
+ * Handles automatic status transitions:
310
+ * - When element becomes blocked: saves current status and triggers BLOCKED transition
311
+ * - When element becomes unblocked: restores previous status
312
+ *
313
+ * @param elementId - Element whose dependencies changed
314
+ * @param options - Gate check options
315
+ */
316
+ invalidateElement(elementId, options = {}) {
317
+ // Check current cached state
318
+ const wasBlocked = this.isBlocked(elementId);
319
+ // Compute new blocking state
320
+ const shouldBeBlocked = this.computeBlockingState(elementId, options);
321
+ // Case 1: Becoming blocked (wasn't blocked, now should be)
322
+ if (!wasBlocked && shouldBeBlocked) {
323
+ // Only handle automatic status transitions for tasks
324
+ if (this.isTask(elementId) && this.statusCallback) {
325
+ const currentStatus = this.getElementStatus(elementId);
326
+ // Only trigger automatic block if not already in a terminal/deferred state
327
+ // and not already blocked
328
+ if (currentStatus && currentStatus !== 'blocked' &&
329
+ currentStatus !== 'closed' && currentStatus !== 'tombstone' &&
330
+ currentStatus !== 'deferred') {
331
+ this.addBlocked(shouldBeBlocked.elementId, shouldBeBlocked.blockedBy, shouldBeBlocked.reason, currentStatus);
332
+ this.statusCallback.onBlock(elementId, currentStatus);
333
+ return;
334
+ }
335
+ }
336
+ // Non-task or no callback: just update cache (no previous status to preserve)
337
+ this.addBlocked(shouldBeBlocked.elementId, shouldBeBlocked.blockedBy, shouldBeBlocked.reason, null);
338
+ }
339
+ // Case 2: Becoming unblocked (was blocked, now shouldn't be)
340
+ else if (wasBlocked && !shouldBeBlocked) {
341
+ // Get the status to restore
342
+ const statusToRestore = wasBlocked.previousStatus;
343
+ // Remove from cache first
344
+ this.removeBlocked(elementId);
345
+ // Only handle automatic status transitions for tasks
346
+ if (this.isTask(elementId) && this.statusCallback && statusToRestore) {
347
+ // Only restore if currently blocked
348
+ const currentStatus = this.getElementStatus(elementId);
349
+ if (currentStatus === 'blocked') {
350
+ this.statusCallback.onUnblock(elementId, statusToRestore);
351
+ }
352
+ }
353
+ }
354
+ // Case 3: Still blocked but by different element (update cache)
355
+ else if (wasBlocked && shouldBeBlocked) {
356
+ // Keep the original previousStatus, just update blocker/reason
357
+ this.addBlocked(shouldBeBlocked.elementId, shouldBeBlocked.blockedBy, shouldBeBlocked.reason, wasBlocked.previousStatus);
358
+ }
359
+ // Case 4: Was not blocked, still not blocked - no action needed
360
+ }
361
+ /**
362
+ * Update blocking state for all elements that depend on a changed element
363
+ * Called when an element's status changes (especially when completing)
364
+ *
365
+ * @param changedId - Element whose status changed
366
+ * @param options - Gate check options
367
+ */
368
+ invalidateDependents(changedId, options = {}) {
369
+ // All blocking types: when a blocker changes, invalidate all blocked elements
370
+ const deps = this.db.query(`SELECT DISTINCT blocked_id, type FROM dependencies
371
+ WHERE blocker_id = ? AND type IN (?, ?, ?)`, [changedId, DT.BLOCKS, DT.PARENT_CHILD, DT.AWAITS]);
372
+ for (const dep of deps) {
373
+ const blockedId = dep.blocked_id;
374
+ this.invalidateElement(blockedId, options);
375
+ // For parent-child, also invalidate children (transitive)
376
+ if (dep.type === DT.PARENT_CHILD) {
377
+ this.invalidateChildren(blockedId, options);
378
+ }
379
+ }
380
+ }
381
+ /**
382
+ * Recursively invalidate children of an element
383
+ * Used for transitive parent-child blocking
384
+ *
385
+ * @param parentId - Parent element
386
+ * @param options - Gate check options
387
+ * @param visited - Set of already visited elements (cycle prevention)
388
+ */
389
+ invalidateChildren(parentId, options = {}, visited = new Set()) {
390
+ if (visited.has(parentId)) {
391
+ return;
392
+ }
393
+ visited.add(parentId);
394
+ // Find all elements that have this as a parent
395
+ const children = this.db.query(`SELECT blocked_id FROM dependencies
396
+ WHERE blocker_id = ? AND type = ?`, [parentId, DT.PARENT_CHILD]);
397
+ for (const child of children) {
398
+ const childId = child.blocked_id;
399
+ this.invalidateElement(childId, options);
400
+ this.invalidateChildren(childId, options, visited);
401
+ }
402
+ }
403
+ // --------------------------------------------------------------------------
404
+ // Gate Satisfaction
405
+ // --------------------------------------------------------------------------
406
+ /**
407
+ * Result of a gate satisfaction operation
408
+ */
409
+ /**
410
+ * Mark an external or webhook gate as satisfied.
411
+ * Updates the dependency metadata and recomputes blocking state.
412
+ *
413
+ * @param blockedId - Element that has the awaits dependency
414
+ * @param blockerId - Blocker element ID of the awaits dependency
415
+ * @param actor - Entity marking the gate as satisfied
416
+ * @param options - Gate check options
417
+ * @returns True if gate was found and satisfied, false if not found
418
+ */
419
+ satisfyGate(blockedId, blockerId, actor, options = {}) {
420
+ // Find the awaits dependency
421
+ const dep = this.db.queryOne(`SELECT * FROM dependencies WHERE blocked_id = ? AND blocker_id = ? AND type = ?`, [blockedId, blockerId, DT.AWAITS]);
422
+ if (!dep || !dep.metadata) {
423
+ return false;
424
+ }
425
+ // Parse and validate metadata
426
+ let metadata;
427
+ try {
428
+ metadata = JSON.parse(dep.metadata);
429
+ }
430
+ catch {
431
+ return false;
432
+ }
433
+ // Check gate type - only external and webhook can be satisfied this way
434
+ const gateType = metadata.gateType;
435
+ if (gateType !== GateType.EXTERNAL && gateType !== GateType.WEBHOOK) {
436
+ return false;
437
+ }
438
+ // Already satisfied?
439
+ if (metadata.satisfied === true) {
440
+ return true;
441
+ }
442
+ // Mark as satisfied
443
+ metadata.satisfied = true;
444
+ metadata.satisfiedAt = new Date().toISOString();
445
+ metadata.satisfiedBy = actor;
446
+ // Update the dependency metadata in the database
447
+ this.db.run(`UPDATE dependencies SET metadata = ? WHERE blocked_id = ? AND blocker_id = ? AND type = ?`, [JSON.stringify(metadata), blockedId, blockerId, DT.AWAITS]);
448
+ // Recompute blocking state for the blocked element
449
+ this.invalidateElement(blockedId, options);
450
+ return true;
451
+ }
452
+ /**
453
+ * Record an approval for an approval gate.
454
+ * Updates the dependency metadata with the new approver.
455
+ *
456
+ * @param blockedId - Element that has the awaits dependency
457
+ * @param blockerId - Blocker element ID of the awaits dependency
458
+ * @param approver - Entity recording their approval
459
+ * @param options - Gate check options
460
+ * @returns Object indicating success and current approval count
461
+ */
462
+ recordApproval(blockedId, blockerId, approver, options = {}) {
463
+ // Find the awaits dependency
464
+ const dep = this.db.queryOne(`SELECT * FROM dependencies WHERE blocked_id = ? AND blocker_id = ? AND type = ?`, [blockedId, blockerId, DT.AWAITS]);
465
+ if (!dep || !dep.metadata) {
466
+ return { success: false, currentCount: 0, requiredCount: 0, satisfied: false };
467
+ }
468
+ // Parse and validate metadata
469
+ let metadata;
470
+ try {
471
+ metadata = JSON.parse(dep.metadata);
472
+ }
473
+ catch {
474
+ return { success: false, currentCount: 0, requiredCount: 0, satisfied: false };
475
+ }
476
+ // Check gate type - only approval gates support this
477
+ if (metadata.gateType !== GateType.APPROVAL) {
478
+ return { success: false, currentCount: 0, requiredCount: 0, satisfied: false };
479
+ }
480
+ // Get required approvers and count
481
+ const requiredApprovers = metadata.requiredApprovers;
482
+ const requiredCount = metadata.approvalCount ?? requiredApprovers.length;
483
+ // Check if approver is in the required list
484
+ if (!requiredApprovers.includes(approver)) {
485
+ return { success: false, currentCount: 0, requiredCount, satisfied: false };
486
+ }
487
+ // Initialize or get current approvers
488
+ const currentApprovers = metadata.currentApprovers ?? [];
489
+ // Check if already approved
490
+ if (currentApprovers.includes(approver)) {
491
+ return {
492
+ success: true,
493
+ currentCount: currentApprovers.length,
494
+ requiredCount,
495
+ satisfied: currentApprovers.length >= requiredCount,
496
+ };
497
+ }
498
+ // Add the approval
499
+ currentApprovers.push(approver);
500
+ metadata.currentApprovers = currentApprovers;
501
+ // Update the dependency metadata in the database
502
+ this.db.run(`UPDATE dependencies SET metadata = ? WHERE blocked_id = ? AND blocker_id = ? AND type = ?`, [JSON.stringify(metadata), blockedId, blockerId, DT.AWAITS]);
503
+ // Recompute blocking state for the blocked element
504
+ this.invalidateElement(blockedId, options);
505
+ const satisfied = currentApprovers.length >= requiredCount;
506
+ return {
507
+ success: true,
508
+ currentCount: currentApprovers.length,
509
+ requiredCount,
510
+ satisfied,
511
+ };
512
+ }
513
+ /**
514
+ * Remove an approval from an approval gate.
515
+ *
516
+ * @param blockedId - Element that has the awaits dependency
517
+ * @param blockerId - Blocker element ID of the awaits dependency
518
+ * @param approver - Entity removing their approval
519
+ * @param options - Gate check options
520
+ * @returns Object indicating success and current approval count
521
+ */
522
+ removeApproval(blockedId, blockerId, approver, options = {}) {
523
+ // Find the awaits dependency
524
+ const dep = this.db.queryOne(`SELECT * FROM dependencies WHERE blocked_id = ? AND blocker_id = ? AND type = ?`, [blockedId, blockerId, DT.AWAITS]);
525
+ if (!dep || !dep.metadata) {
526
+ return { success: false, currentCount: 0, requiredCount: 0, satisfied: false };
527
+ }
528
+ // Parse and validate metadata
529
+ let metadata;
530
+ try {
531
+ metadata = JSON.parse(dep.metadata);
532
+ }
533
+ catch {
534
+ return { success: false, currentCount: 0, requiredCount: 0, satisfied: false };
535
+ }
536
+ // Check gate type
537
+ if (metadata.gateType !== GateType.APPROVAL) {
538
+ return { success: false, currentCount: 0, requiredCount: 0, satisfied: false };
539
+ }
540
+ // Get required approvers and count
541
+ const requiredApprovers = metadata.requiredApprovers;
542
+ const requiredCount = metadata.approvalCount ?? requiredApprovers.length;
543
+ // Get current approvers
544
+ const currentApprovers = metadata.currentApprovers ?? [];
545
+ // Check if approver is in the list
546
+ const index = currentApprovers.indexOf(approver);
547
+ if (index === -1) {
548
+ return {
549
+ success: true,
550
+ currentCount: currentApprovers.length,
551
+ requiredCount,
552
+ satisfied: currentApprovers.length >= requiredCount,
553
+ };
554
+ }
555
+ // Remove the approval
556
+ currentApprovers.splice(index, 1);
557
+ metadata.currentApprovers = currentApprovers;
558
+ // Update the dependency metadata in the database
559
+ this.db.run(`UPDATE dependencies SET metadata = ? WHERE blocked_id = ? AND blocker_id = ? AND type = ?`, [JSON.stringify(metadata), blockedId, blockerId, DT.AWAITS]);
560
+ // Recompute blocking state for the blocked element
561
+ this.invalidateElement(blockedId, options);
562
+ const satisfied = currentApprovers.length >= requiredCount;
563
+ return {
564
+ success: true,
565
+ currentCount: currentApprovers.length,
566
+ requiredCount,
567
+ satisfied,
568
+ };
569
+ }
570
+ // --------------------------------------------------------------------------
571
+ // Full Rebuild
572
+ // --------------------------------------------------------------------------
573
+ /**
574
+ * Rebuild the entire blocked cache from scratch
575
+ *
576
+ * This is useful for:
577
+ * - Initial population after migration
578
+ * - Recovery from cache corruption
579
+ * - Periodic consistency checks
580
+ *
581
+ * The rebuild processes elements in topological order (parents before children)
582
+ * to ensure transitive blocking is computed correctly.
583
+ *
584
+ * @param options - Gate check options
585
+ * @returns Rebuild statistics
586
+ */
587
+ rebuild(options = {}) {
588
+ const startTime = Date.now();
589
+ // Clear existing cache
590
+ this.clear();
591
+ // Get all elements that could potentially be blocked
592
+ // All blocking types use blocked_id as the waiting element
593
+ const blocksBlocked = this.db.query(`SELECT DISTINCT blocked_id as element_id FROM dependencies WHERE type = ?`, [DT.BLOCKS]);
594
+ const parentChildBlocked = this.db.query(`SELECT DISTINCT blocked_id as element_id FROM dependencies WHERE type = ?`, [DT.PARENT_CHILD]);
595
+ const awaitsBlocked = this.db.query(`SELECT DISTINCT blocked_id as element_id FROM dependencies WHERE type = ?`, [DT.AWAITS]);
596
+ // Combine all potentially blocked elements
597
+ const allElements = new Set();
598
+ for (const e of blocksBlocked)
599
+ allElements.add(e.element_id);
600
+ for (const e of parentChildBlocked)
601
+ allElements.add(e.element_id);
602
+ for (const e of awaitsBlocked)
603
+ allElements.add(e.element_id);
604
+ let elementsChecked = 0;
605
+ let elementsBlocked = 0;
606
+ // Process elements in dependency order (BFS from roots)
607
+ // This ensures parents are processed before children for transitive blocking
608
+ const processed = new Set();
609
+ const queue = [];
610
+ // First pass: Find parent relationships for topological ordering
611
+ const parentOf = new Map(); // child -> parents
612
+ for (const elementId of allElements) {
613
+ const parents = this.db.query(`SELECT blocker_id FROM dependencies
614
+ WHERE blocked_id = ? AND type = ?`, [elementId, DT.PARENT_CHILD]);
615
+ parentOf.set(elementId, parents.map((p) => p.blocker_id).filter((p) => allElements.has(p)));
616
+ }
617
+ // Start with elements that have no parents in our set
618
+ for (const elementId of allElements) {
619
+ const parents = parentOf.get(elementId) ?? [];
620
+ if (parents.length === 0) {
621
+ queue.push(elementId);
622
+ }
623
+ }
624
+ // Process in order
625
+ while (queue.length > 0) {
626
+ const elementId = queue.shift();
627
+ if (processed.has(elementId)) {
628
+ continue;
629
+ }
630
+ // Check if all parents are processed
631
+ const parents = parentOf.get(elementId) ?? [];
632
+ const allParentsProcessed = parents.every((p) => processed.has(p));
633
+ if (!allParentsProcessed) {
634
+ // Put back in queue for later
635
+ queue.push(elementId);
636
+ continue;
637
+ }
638
+ processed.add(elementId);
639
+ elementsChecked++;
640
+ // Compute blocking state
641
+ const blocking = this.computeBlockingState(elementId, options);
642
+ if (blocking) {
643
+ this.addBlocked(blocking.elementId, blocking.blockedBy, blocking.reason);
644
+ elementsBlocked++;
645
+ }
646
+ // Add children to queue
647
+ const children = this.db.query(`SELECT blocked_id FROM dependencies
648
+ WHERE blocker_id = ? AND type = ?`, [elementId, DT.PARENT_CHILD]);
649
+ for (const child of children) {
650
+ if (!processed.has(child.blocked_id)) {
651
+ queue.push(child.blocked_id);
652
+ }
653
+ }
654
+ }
655
+ // Handle any remaining elements (shouldn't happen if graph is consistent)
656
+ for (const elementId of allElements) {
657
+ if (!processed.has(elementId)) {
658
+ elementsChecked++;
659
+ const blocking = this.computeBlockingState(elementId, options);
660
+ if (blocking) {
661
+ this.addBlocked(blocking.elementId, blocking.blockedBy, blocking.reason);
662
+ elementsBlocked++;
663
+ }
664
+ }
665
+ }
666
+ const durationMs = Date.now() - startTime;
667
+ return {
668
+ elementsChecked,
669
+ elementsBlocked,
670
+ durationMs,
671
+ };
672
+ }
673
+ // --------------------------------------------------------------------------
674
+ // Dependency Event Handlers
675
+ // --------------------------------------------------------------------------
676
+ /**
677
+ * Handle a blocking dependency being added
678
+ *
679
+ * @param blockedId - Element that is waiting/blocked
680
+ * @param blockerId - Element doing the blocking
681
+ * @param type - Type of dependency
682
+ * @param metadata - Dependency metadata (for awaits)
683
+ * @param options - Gate check options
684
+ */
685
+ onDependencyAdded(blockedId, blockerId, type, _metadata, options = {}) {
686
+ // Only handle blocking dependency types
687
+ if (type !== DT.BLOCKS && type !== DT.PARENT_CHILD && type !== DT.AWAITS) {
688
+ return;
689
+ }
690
+ // All blocking types: blockedId is the waiting element
691
+ this.invalidateElement(blockedId, options);
692
+ // For parent-child, also invalidate all children (transitive)
693
+ if (type === DT.PARENT_CHILD) {
694
+ this.invalidateChildren(blockedId, options);
695
+ }
696
+ }
697
+ /**
698
+ * Handle a blocking dependency being removed
699
+ *
700
+ * @param blockedId - Element that had a blocking dependency removed
701
+ * @param blockerId - Element that was doing the blocking
702
+ * @param type - Type of dependency that was removed
703
+ * @param options - Gate check options
704
+ */
705
+ onDependencyRemoved(blockedId, blockerId, type, options = {}) {
706
+ // Only handle blocking dependency types
707
+ if (type !== DT.BLOCKS && type !== DT.PARENT_CHILD && type !== DT.AWAITS) {
708
+ return;
709
+ }
710
+ // All blocking types: blockedId is the waiting element
711
+ this.invalidateElement(blockedId, options);
712
+ // For parent-child, also invalidate all children
713
+ if (type === DT.PARENT_CHILD) {
714
+ this.invalidateChildren(blockedId, options);
715
+ }
716
+ }
717
+ /**
718
+ * Handle an element's status changing
719
+ *
720
+ * @param elementId - Element whose status changed
721
+ * @param oldStatus - Previous status
722
+ * @param newStatus - New status
723
+ * @param options - Gate check options
724
+ */
725
+ onStatusChanged(elementId, oldStatus, newStatus, options = {}) {
726
+ const wasCompleted = COMPLETED_STATUSES.includes(oldStatus);
727
+ const isNowCompleted = COMPLETED_STATUSES.includes(newStatus);
728
+ // If completion status changed, invalidate all dependents
729
+ if (wasCompleted !== isNowCompleted) {
730
+ this.invalidateDependents(elementId, options);
731
+ }
732
+ }
733
+ /**
734
+ * Handle an element being deleted
735
+ *
736
+ * @param elementId - Element that was deleted
737
+ * @param options - Gate check options
738
+ */
739
+ onElementDeleted(elementId, options = {}) {
740
+ // Remove from cache if blocked
741
+ this.removeBlocked(elementId);
742
+ // Invalidate dependents (deletion is like completion)
743
+ this.invalidateDependents(elementId, options);
744
+ }
745
+ }
746
+ // ============================================================================
747
+ // Factory Function
748
+ // ============================================================================
749
+ /**
750
+ * Create a new BlockedCacheService instance
751
+ */
752
+ export function createBlockedCacheService(db) {
753
+ return new BlockedCacheService(db);
754
+ }
755
+ //# sourceMappingURL=blocked-cache.js.map