@graphrefly/graphrefly 0.47.2 → 0.48.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 (266) hide show
  1. package/dist/base/composition/index.cjs +4 -3
  2. package/dist/base/composition/index.cjs.map +1 -1
  3. package/dist/base/composition/index.d.cts +14 -5
  4. package/dist/base/composition/index.d.ts +14 -5
  5. package/dist/base/composition/index.js +8 -8
  6. package/dist/base/index.cjs +152 -78
  7. package/dist/base/index.cjs.map +1 -1
  8. package/dist/base/index.d.cts +2 -2
  9. package/dist/base/index.d.ts +2 -2
  10. package/dist/base/index.js +75 -70
  11. package/dist/base/io/index.cjs +31 -17
  12. package/dist/base/io/index.cjs.map +1 -1
  13. package/dist/base/io/index.d.cts +32 -5
  14. package/dist/base/io/index.d.ts +32 -5
  15. package/dist/base/io/index.js +1 -1
  16. package/dist/base/mutation/index.cjs +21 -0
  17. package/dist/base/mutation/index.cjs.map +1 -1
  18. package/dist/base/mutation/index.d.cts +23 -1
  19. package/dist/base/mutation/index.d.ts +23 -1
  20. package/dist/base/mutation/index.js +3 -1
  21. package/dist/base/sources/browser/index.cjs +5 -3
  22. package/dist/base/sources/browser/index.cjs.map +1 -1
  23. package/dist/base/sources/browser/index.d.cts +20 -2
  24. package/dist/base/sources/browser/index.d.ts +20 -2
  25. package/dist/base/sources/browser/index.js +5 -3
  26. package/dist/base/sources/browser/index.js.map +1 -1
  27. package/dist/base/sources/event/index.cjs +28 -0
  28. package/dist/base/sources/event/index.cjs.map +1 -1
  29. package/dist/base/sources/event/index.d.cts +67 -3
  30. package/dist/base/sources/event/index.d.ts +67 -3
  31. package/dist/base/sources/event/index.js +4 -1
  32. package/dist/base/sources/index.cjs +75 -37
  33. package/dist/base/sources/index.cjs.map +1 -1
  34. package/dist/base/sources/index.d.cts +1 -1
  35. package/dist/base/sources/index.d.ts +1 -1
  36. package/dist/base/sources/index.js +5 -2
  37. package/dist/{chunk-R6ZCSXKX.js → chunk-23MAWVOJ.js} +3 -3
  38. package/dist/{chunk-MS3WPRJR.js → chunk-3REMCHSS.js} +6 -6
  39. package/dist/chunk-3REMCHSS.js.map +1 -0
  40. package/dist/{chunk-CEVNQ74M.js → chunk-3YGXPUHW.js} +2 -2
  41. package/dist/{chunk-CEVNQ74M.js.map → chunk-3YGXPUHW.js.map} +1 -1
  42. package/dist/{chunk-6ZLCPUXS.js → chunk-46X2EFQH.js} +15 -4
  43. package/dist/chunk-46X2EFQH.js.map +1 -0
  44. package/dist/{chunk-NY2PYHNC.js → chunk-5UY3PNFY.js} +12 -5
  45. package/dist/chunk-5UY3PNFY.js.map +1 -0
  46. package/dist/{chunk-FQSQONOU.js → chunk-65OM4XLQ.js} +49 -3
  47. package/dist/chunk-65OM4XLQ.js.map +1 -0
  48. package/dist/{chunk-3PSLNJDU.js → chunk-6DQYBIHW.js} +314 -49
  49. package/dist/chunk-6DQYBIHW.js.map +1 -0
  50. package/dist/{chunk-LDCSZ72P.js → chunk-6YBER5UP.js} +3 -3
  51. package/dist/{chunk-LDCSZ72P.js.map → chunk-6YBER5UP.js.map} +1 -1
  52. package/dist/{chunk-3O3NKZJW.js → chunk-7T7WLEPM.js} +24 -3
  53. package/dist/chunk-7T7WLEPM.js.map +1 -0
  54. package/dist/{chunk-PKPO3JTZ.js → chunk-AQAKDE7F.js} +29 -11
  55. package/dist/chunk-AQAKDE7F.js.map +1 -0
  56. package/dist/{chunk-6MRSX3YK.js → chunk-B5Y5GPD5.js} +2 -2
  57. package/dist/{chunk-BXGZFGZ4.js → chunk-C5QD5DQX.js} +22 -1
  58. package/dist/chunk-C5QD5DQX.js.map +1 -0
  59. package/dist/{chunk-4XCHZRUJ.js → chunk-D5YGR4TP.js} +58 -7
  60. package/dist/chunk-D5YGR4TP.js.map +1 -0
  61. package/dist/{chunk-NPRP3MCV.js → chunk-DHDCOOJU.js} +2 -2
  62. package/dist/chunk-DHDCOOJU.js.map +1 -0
  63. package/dist/{chunk-VP3TIUDF.js → chunk-DVTDF5OI.js} +2 -2
  64. package/dist/{chunk-OXD5LFQP.js → chunk-G7H6PN7P.js} +2 -2
  65. package/dist/{chunk-EL5VHUGK.js → chunk-GGKHHG5Y.js} +32 -18
  66. package/dist/chunk-GGKHHG5Y.js.map +1 -0
  67. package/dist/{chunk-446I4EGD.js → chunk-J5TBZFBD.js} +2 -2
  68. package/dist/{chunk-7AVQIGF6.js → chunk-K4ZYJ4EM.js} +554 -460
  69. package/dist/chunk-K4ZYJ4EM.js.map +1 -0
  70. package/dist/{chunk-QFE5BQH7.js → chunk-LTSI7ULC.js} +2 -2
  71. package/dist/{chunk-5GVURVIG.js → chunk-MMHGYX44.js} +12 -2
  72. package/dist/{chunk-5GVURVIG.js.map → chunk-MMHGYX44.js.map} +1 -1
  73. package/dist/{chunk-KRFGO5QH.js → chunk-MQMTRKY3.js} +118 -43
  74. package/dist/chunk-MQMTRKY3.js.map +1 -0
  75. package/dist/{chunk-42FQ27MQ.js → chunk-MTODGQBR.js} +44 -179
  76. package/dist/chunk-MTODGQBR.js.map +1 -0
  77. package/dist/{chunk-FVINAAKA.js → chunk-NBK6QQMG.js} +14 -13
  78. package/dist/{chunk-FVINAAKA.js.map → chunk-NBK6QQMG.js.map} +1 -1
  79. package/dist/{chunk-KNU73RZW.js → chunk-NSA5K5G2.js} +2 -2
  80. package/dist/{chunk-MLTPJMH6.js → chunk-QQYULEZL.js} +2 -2
  81. package/dist/chunk-QSW4DFKE.js +31 -0
  82. package/dist/chunk-QSW4DFKE.js.map +1 -0
  83. package/dist/{chunk-VAZXUK6G.js → chunk-SUNCHMML.js} +2 -2
  84. package/dist/{chunk-EP4WVQLX.js → chunk-T2U6N3FV.js} +6 -6
  85. package/dist/{chunk-T7SP3EYR.js → chunk-T5URUIIY.js} +33 -24
  86. package/dist/chunk-T5URUIIY.js.map +1 -0
  87. package/dist/{chunk-VNXAF2KE.js → chunk-TPTZZV25.js} +6 -6
  88. package/dist/chunk-TPTZZV25.js.map +1 -0
  89. package/dist/{chunk-IOJDYUA7.js → chunk-V46JWFGV.js} +6 -5
  90. package/dist/chunk-V46JWFGV.js.map +1 -0
  91. package/dist/{chunk-WGDEBIP4.js → chunk-X6ESZDR6.js} +5 -6
  92. package/dist/chunk-X6ESZDR6.js.map +1 -0
  93. package/dist/{chunk-N65E26UL.js → chunk-XEWV254I.js} +2 -2
  94. package/dist/{chunk-N65E26UL.js.map → chunk-XEWV254I.js.map} +1 -1
  95. package/dist/{chunk-PTWADEH3.js → chunk-YBJVKMTM.js} +34 -14
  96. package/dist/chunk-YBJVKMTM.js.map +1 -0
  97. package/dist/{chunk-DDTS7F5O.js → chunk-ZW32BPXV.js} +12 -3
  98. package/dist/chunk-ZW32BPXV.js.map +1 -0
  99. package/dist/compat/index.cjs +51 -4
  100. package/dist/compat/index.cjs.map +1 -1
  101. package/dist/compat/index.d.cts +1 -1
  102. package/dist/compat/index.d.ts +1 -1
  103. package/dist/compat/index.js +6 -6
  104. package/dist/compat/nestjs/index.cjs +51 -4
  105. package/dist/compat/nestjs/index.cjs.map +1 -1
  106. package/dist/compat/nestjs/index.d.cts +1 -1
  107. package/dist/compat/nestjs/index.d.ts +1 -1
  108. package/dist/compat/nestjs/index.js +3 -3
  109. package/dist/{fallback-Bx46zqky.d.cts → fallback-BROR6ZhO.d.cts} +1 -1
  110. package/dist/{fallback-pIWW8A2d.d.ts → fallback-DO80aM_3.d.ts} +1 -1
  111. package/dist/{index-B_p8tnvf.d.cts → index-D1z3XcF9.d.cts} +1 -0
  112. package/dist/{index-_HDSmPyp.d.ts → index-DZ6yua0Q.d.ts} +1 -0
  113. package/dist/index.cjs +2215 -1676
  114. package/dist/index.cjs.map +1 -1
  115. package/dist/index.d.cts +10 -10
  116. package/dist/index.d.ts +10 -10
  117. package/dist/index.js +169 -146
  118. package/dist/index.js.map +1 -1
  119. package/dist/presets/ai/index.cjs +46 -0
  120. package/dist/presets/ai/index.cjs.map +1 -1
  121. package/dist/presets/ai/index.js +12 -12
  122. package/dist/presets/harness/index.cjs +130 -18
  123. package/dist/presets/harness/index.cjs.map +1 -1
  124. package/dist/presets/harness/index.d.cts +15 -5
  125. package/dist/presets/harness/index.d.ts +15 -5
  126. package/dist/presets/harness/index.js +22 -22
  127. package/dist/presets/index.cjs +222 -53
  128. package/dist/presets/index.cjs.map +1 -1
  129. package/dist/presets/index.d.cts +2 -2
  130. package/dist/presets/index.d.ts +2 -2
  131. package/dist/presets/index.js +45 -45
  132. package/dist/presets/inspect/index.cjs +63 -14
  133. package/dist/presets/inspect/index.cjs.map +1 -1
  134. package/dist/presets/inspect/index.d.cts +1 -1
  135. package/dist/presets/inspect/index.d.ts +1 -1
  136. package/dist/presets/inspect/index.js +6 -6
  137. package/dist/presets/resilience/index.cjs +29 -21
  138. package/dist/presets/resilience/index.cjs.map +1 -1
  139. package/dist/presets/resilience/index.d.cts +12 -8
  140. package/dist/presets/resilience/index.d.ts +12 -8
  141. package/dist/presets/resilience/index.js +3 -3
  142. package/dist/{rate-limiter-DpVbSYdH.d.cts → rate-limiter-DC26FM8J.d.cts} +10 -1
  143. package/dist/{rate-limiter-CEALq4N1.d.ts → rate-limiter-DyWpwpQP.d.ts} +10 -1
  144. package/dist/{reactive-layout-fswlBUvX.d.ts → reactive-layout-BBBWH0V_.d.cts} +85 -4
  145. package/dist/{reactive-layout-fswlBUvX.d.cts → reactive-layout-BBBWH0V_.d.ts} +85 -4
  146. package/dist/solutions/index.cjs +168 -47
  147. package/dist/solutions/index.cjs.map +1 -1
  148. package/dist/solutions/index.d.cts +2 -2
  149. package/dist/solutions/index.d.ts +2 -2
  150. package/dist/solutions/index.js +28 -28
  151. package/dist/{spawnable-5mDY501F.d.cts → spawnable-B2IlW60f.d.cts} +23 -2
  152. package/dist/{spawnable-D3lR0oQu.d.ts → spawnable-tttFz2Nh.d.ts} +23 -2
  153. package/dist/testing/index.cjs +94 -0
  154. package/dist/testing/index.cjs.map +1 -0
  155. package/dist/testing/index.d.cts +59 -0
  156. package/dist/testing/index.d.ts +59 -0
  157. package/dist/testing/index.js +73 -0
  158. package/dist/testing/index.js.map +1 -0
  159. package/dist/utils/ai/browser.cjs.map +1 -1
  160. package/dist/utils/ai/browser.d.cts +2 -2
  161. package/dist/utils/ai/browser.d.ts +2 -2
  162. package/dist/utils/ai/browser.js +6 -6
  163. package/dist/utils/ai/browser.js.map +1 -1
  164. package/dist/utils/ai/index.cjs +250 -166
  165. package/dist/utils/ai/index.cjs.map +1 -1
  166. package/dist/utils/ai/index.d.cts +108 -12
  167. package/dist/utils/ai/index.d.ts +108 -12
  168. package/dist/utils/ai/index.js +21 -19
  169. package/dist/utils/ai/node.cjs.map +1 -1
  170. package/dist/utils/ai/node.d.cts +5 -5
  171. package/dist/utils/ai/node.d.ts +5 -5
  172. package/dist/utils/ai/node.js +2 -2
  173. package/dist/utils/ai/node.js.map +1 -1
  174. package/dist/utils/cqrs/index.cjs +29 -3
  175. package/dist/utils/cqrs/index.cjs.map +1 -1
  176. package/dist/utils/cqrs/index.d.cts +12 -7
  177. package/dist/utils/cqrs/index.d.ts +12 -7
  178. package/dist/utils/cqrs/index.js +2 -2
  179. package/dist/utils/demo-shell/index.cjs +45 -19
  180. package/dist/utils/demo-shell/index.cjs.map +1 -1
  181. package/dist/utils/demo-shell/index.d.cts +1 -1
  182. package/dist/utils/demo-shell/index.d.ts +1 -1
  183. package/dist/utils/demo-shell/index.js +2 -2
  184. package/dist/utils/domain-templates/index.cjs.map +1 -1
  185. package/dist/utils/domain-templates/index.js +3 -3
  186. package/dist/utils/graphspec/index.cjs.map +1 -1
  187. package/dist/utils/graphspec/index.js +3 -3
  188. package/dist/utils/index.cjs +1642 -1225
  189. package/dist/utils/index.cjs.map +1 -1
  190. package/dist/utils/index.d.cts +7 -7
  191. package/dist/utils/index.d.ts +7 -7
  192. package/dist/utils/index.js +72 -54
  193. package/dist/utils/inspect/index.cjs +52 -4
  194. package/dist/utils/inspect/index.cjs.map +1 -1
  195. package/dist/utils/inspect/index.d.cts +32 -3
  196. package/dist/utils/inspect/index.d.ts +32 -3
  197. package/dist/utils/inspect/index.js +4 -4
  198. package/dist/utils/job-queue/index.cjs +46 -9
  199. package/dist/utils/job-queue/index.cjs.map +1 -1
  200. package/dist/utils/job-queue/index.d.cts +33 -3
  201. package/dist/utils/job-queue/index.d.ts +33 -3
  202. package/dist/utils/job-queue/index.js +2 -2
  203. package/dist/utils/memory/index.cjs +556 -462
  204. package/dist/utils/memory/index.cjs.map +1 -1
  205. package/dist/utils/memory/index.d.cts +203 -24
  206. package/dist/utils/memory/index.d.ts +203 -24
  207. package/dist/utils/memory/index.js +10 -2
  208. package/dist/utils/messaging/index.cjs.map +1 -1
  209. package/dist/utils/messaging/index.d.cts +4 -3
  210. package/dist/utils/messaging/index.d.ts +4 -3
  211. package/dist/utils/messaging/index.js +2 -2
  212. package/dist/utils/orchestration/index.cjs +9 -0
  213. package/dist/utils/orchestration/index.cjs.map +1 -1
  214. package/dist/utils/orchestration/index.js +3 -3
  215. package/dist/utils/process/index.cjs +32 -2
  216. package/dist/utils/process/index.cjs.map +1 -1
  217. package/dist/utils/process/index.d.cts +4 -3
  218. package/dist/utils/process/index.d.ts +4 -3
  219. package/dist/utils/process/index.js +2 -2
  220. package/dist/utils/reactive-layout/index.cjs +184 -55
  221. package/dist/utils/reactive-layout/index.cjs.map +1 -1
  222. package/dist/utils/reactive-layout/index.d.cts +128 -3
  223. package/dist/utils/reactive-layout/index.d.ts +128 -3
  224. package/dist/utils/reactive-layout/index.js +16 -8
  225. package/dist/utils/reduction/index.cjs.map +1 -1
  226. package/dist/utils/reduction/index.js +2 -2
  227. package/dist/utils/resilience/index.cjs +29 -20
  228. package/dist/utils/resilience/index.cjs.map +1 -1
  229. package/dist/utils/resilience/index.d.cts +1 -1
  230. package/dist/utils/resilience/index.d.ts +1 -1
  231. package/dist/utils/resilience/index.js +2 -2
  232. package/dist/utils/surface/index.cjs.map +1 -1
  233. package/dist/utils/surface/index.js +4 -4
  234. package/package.json +15 -3
  235. package/dist/chunk-3O3NKZJW.js.map +0 -1
  236. package/dist/chunk-3PSLNJDU.js.map +0 -1
  237. package/dist/chunk-42FQ27MQ.js.map +0 -1
  238. package/dist/chunk-4XCHZRUJ.js.map +0 -1
  239. package/dist/chunk-6ZLCPUXS.js.map +0 -1
  240. package/dist/chunk-7AVQIGF6.js.map +0 -1
  241. package/dist/chunk-BXGZFGZ4.js.map +0 -1
  242. package/dist/chunk-DDTS7F5O.js.map +0 -1
  243. package/dist/chunk-EL5VHUGK.js.map +0 -1
  244. package/dist/chunk-FQSQONOU.js.map +0 -1
  245. package/dist/chunk-IOJDYUA7.js.map +0 -1
  246. package/dist/chunk-KRFGO5QH.js.map +0 -1
  247. package/dist/chunk-MS3WPRJR.js.map +0 -1
  248. package/dist/chunk-NPRP3MCV.js.map +0 -1
  249. package/dist/chunk-NY2PYHNC.js.map +0 -1
  250. package/dist/chunk-PKPO3JTZ.js.map +0 -1
  251. package/dist/chunk-PTWADEH3.js.map +0 -1
  252. package/dist/chunk-T7SP3EYR.js.map +0 -1
  253. package/dist/chunk-VNXAF2KE.js.map +0 -1
  254. package/dist/chunk-W2BOPXTI.js +0 -1
  255. package/dist/chunk-W2BOPXTI.js.map +0 -1
  256. package/dist/chunk-WGDEBIP4.js.map +0 -1
  257. /package/dist/{chunk-R6ZCSXKX.js.map → chunk-23MAWVOJ.js.map} +0 -0
  258. /package/dist/{chunk-6MRSX3YK.js.map → chunk-B5Y5GPD5.js.map} +0 -0
  259. /package/dist/{chunk-VP3TIUDF.js.map → chunk-DVTDF5OI.js.map} +0 -0
  260. /package/dist/{chunk-OXD5LFQP.js.map → chunk-G7H6PN7P.js.map} +0 -0
  261. /package/dist/{chunk-446I4EGD.js.map → chunk-J5TBZFBD.js.map} +0 -0
  262. /package/dist/{chunk-QFE5BQH7.js.map → chunk-LTSI7ULC.js.map} +0 -0
  263. /package/dist/{chunk-KNU73RZW.js.map → chunk-NSA5K5G2.js.map} +0 -0
  264. /package/dist/{chunk-MLTPJMH6.js.map → chunk-QQYULEZL.js.map} +0 -0
  265. /package/dist/{chunk-VAZXUK6G.js.map → chunk-SUNCHMML.js.map} +0 -0
  266. /package/dist/{chunk-EP4WVQLX.js.map → chunk-T2U6N3FV.js.map} +0 -0
@@ -5,8 +5,9 @@ import {
5
5
  bumpCursor,
6
6
  createAuditLog,
7
7
  mutate,
8
+ readonlyAuditLog,
8
9
  registerCursor
9
- } from "./chunk-BXGZFGZ4.js";
10
+ } from "./chunk-C5QD5DQX.js";
10
11
 
11
12
  // src/utils/job-queue/index.ts
12
13
  import {
@@ -46,7 +47,10 @@ var JobQueueGraph = class extends Graph {
46
47
  depth;
47
48
  /** Audit log of every queue mutation (Audit 2). */
48
49
  events;
49
- /** Alias for {@link JobQueueGraph.events} — Audit 2 `.audit` duplication. */
50
+ /**
51
+ * Read-only view of {@link JobQueueGraph.events} — Audit 2 `.audit`
52
+ * duplication; M7 (cannot mutate the canonical log via the alias).
53
+ */
50
54
  audit;
51
55
  // Tier 8 / COMPOSITION-GUIDE §35: mutate wrappers for the four
52
56
  // single-record mutation methods. Assigned in the constructor (NOT via
@@ -87,7 +91,7 @@ var JobQueueGraph = class extends Graph {
87
91
  retainedLimit: 1024,
88
92
  graph: this
89
93
  });
90
- this.audit = this.events;
94
+ this.audit = readonlyAuditLog(this.events);
91
95
  this._seqCursor = registerCursor(this, "seq", 0);
92
96
  this._enqueueImpl = mutate(
93
97
  (payload, enqueueOpts) => {
@@ -140,7 +144,7 @@ var JobQueueGraph = class extends Graph {
140
144
  }
141
145
  );
142
146
  this._nackImpl = mutate(
143
- (id, job, requeue) => {
147
+ (id, job, requeue, _error) => {
144
148
  if (requeue) {
145
149
  this._jobs.set(id, { ...job, state: "queued" });
146
150
  this._pending.append(id);
@@ -153,12 +157,16 @@ var JobQueueGraph = class extends Graph {
153
157
  log: this.events,
154
158
  seq: this._seqCursor,
155
159
  freeze: false,
156
- onSuccessRecord: ([id, job], _r, { t_ns, seq }) => ({
160
+ onSuccessRecord: ([id, job, requeue, error], _r, { t_ns, seq }) => ({
157
161
  action: "nack",
158
162
  id,
159
163
  attempts: job.attempts,
160
164
  t_ns,
161
- seq: seq ?? 0
165
+ seq: seq ?? 0,
166
+ // F-CATCH D-3: surface the failure cause on the no-requeue
167
+ // (terminal-failure) nack only. A requeue nack is a retry,
168
+ // not a failure, so it carries no error.
169
+ ...!requeue && error !== void 0 ? { error } : {}
162
170
  })
163
171
  }
164
172
  );
@@ -230,10 +238,20 @@ var JobQueueGraph = class extends Graph {
230
238
  this._ackImpl(id, job);
231
239
  return true;
232
240
  }
241
+ /**
242
+ * Negatively-acknowledge an inflight job.
243
+ *
244
+ * @param opts.requeue - `true` (default) returns the job to the queue for
245
+ * retry; `false` drops it permanently (terminal failure).
246
+ * @param opts.error - Optional failure cause for a `requeue: false` nack.
247
+ * Recorded on the emitted `"nack"` {@link JobEvent} as `error` so the
248
+ * reason a job failed is recoverable from the event stream (F-CATCH
249
+ * D-3). Ignored when `requeue` is `true` (a retry is not a failure).
250
+ */
233
251
  nack(id, opts = {}) {
234
252
  const job = this._jobs.get(id);
235
253
  if (!job || job.state !== "inflight") return false;
236
- this._nackImpl(id, job, opts.requeue ?? true);
254
+ this._nackImpl(id, job, opts.requeue ?? true, opts.error);
237
255
  return true;
238
256
  }
239
257
  /**
@@ -399,10 +417,10 @@ var JobFlowGraph = class extends Graph {
399
417
  let result;
400
418
  try {
401
419
  result = workFn(job, { signal: ac.signal });
402
- } catch {
420
+ } catch (err) {
403
421
  inflight.delete(entry);
404
422
  inflightCounter?.emit(inflight.size);
405
- current.nack(job.id, { requeue: false });
423
+ current.nack(job.id, { requeue: false, error: err });
406
424
  processed += 1;
407
425
  continue;
408
426
  }
@@ -453,7 +471,7 @@ var JobFlowGraph = class extends Graph {
453
471
  } else if (m[0] === ERROR) {
454
472
  settled = true;
455
473
  cleanupSub();
456
- current.nack(job.id, { requeue: false });
474
+ current.nack(job.id, { requeue: false, error: m[1] });
457
475
  return;
458
476
  }
459
477
  }
@@ -558,4 +576,4 @@ export {
558
576
  jobQueue,
559
577
  jobFlow
560
578
  };
561
- //# sourceMappingURL=chunk-PKPO3JTZ.js.map
579
+ //# sourceMappingURL=chunk-AQAKDE7F.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/utils/job-queue/index.ts"],"sourcesContent":["/**\n * Job queue patterns (roadmap §4.2).\n *\n * Queue / flow primitives modeled as graph factories:\n * - `jobQueue()` — claim/ack/nack workflow with reactive depth.\n * - `jobFlow()` — multi-stage queue chain.\n *\n * Topic / subscription / hub primitives live in `patterns/messaging`.\n */\n\nimport {\n\tbatch,\n\tDATA,\n\tERROR,\n\ttype Node,\n\tnode,\n\tplaceholderArgs,\n\twallClockNs,\n} from \"@graphrefly/pure-ts/core\";\nimport type { AppendLogStorageTier } from \"@graphrefly/pure-ts/extra\";\nimport {\n\tfromAny,\n\tkeepalive,\n\ttype NodeInput,\n\ttype ReactiveLogBundle,\n\treactiveList,\n\treactiveLog,\n\treactiveMap,\n} from \"@graphrefly/pure-ts/extra\";\nimport { Graph, type GraphOptions } from \"@graphrefly/pure-ts/graph\";\nimport { domainMeta } from \"../../base/meta/domain-meta.js\";\nimport {\n\ttype BaseAuditRecord,\n\tbumpCursor,\n\tcreateAuditLog,\n\tmutate,\n\ttype ReadonlyAuditLog,\n\treadonlyAuditLog,\n\tregisterCursor,\n} from \"../../base/mutation/index.js\";\n\nconst DEFAULT_MAX_PER_PUMP = 256;\nconst DEFAULT_COMPLETED_RETAINED_LIMIT = 1024;\n\nfunction requireNonNegativeInt(value: number, label: string): number {\n\tif (!Number.isFinite(value) || !Number.isInteger(value) || value < 0) {\n\t\tthrow new Error(`${label} must be a non-negative integer`);\n\t}\n\treturn value;\n}\n\nfunction jobQueueMeta(kind: string, extra?: Record<string, unknown>): Record<string, unknown> {\n\treturn domainMeta(\"job_queue\", kind, extra);\n}\n\nexport type JobState = \"queued\" | \"inflight\";\n\nexport type JobEnvelope<T> = {\n\tid: string;\n\tpayload: T;\n\tattempts: number;\n\tmetadata: Readonly<Record<string, unknown>>;\n\tstate: JobState;\n};\n\n/** Audit record for a job-queue mutation (Audit 2 cross-cutting). */\nexport type JobEventAction = \"enqueue\" | \"claim\" | \"ack\" | \"nack\" | \"remove\";\n\nexport interface JobEvent<T = unknown> extends BaseAuditRecord {\n\treadonly action: JobEventAction;\n\treadonly id: string;\n\treadonly attempts?: number;\n\treadonly payload?: T;\n\t/**\n\t * The failure cause for a no-requeue `\"nack\"` (F-CATCH D-3). Present only\n\t * when a job was nack'd-without-requeue because its work threw\n\t * synchronously or its result Node emitted `ERROR`. Without this, a failed\n\t * job is observable as a `\"nack\"` event but the *reason* it failed is\n\t * unrecoverable from the event stream. Carries the raw thrown value /\n\t * ERROR payload (not normalized) so callers keep full fidelity.\n\t *\n\t * Caveats: a thrown `undefined` (legal but pathological) is not recorded —\n\t * it carries no diagnostic payload and the `\"nack\"` action itself already\n\t * surfaces the failure. And like {@link JobEvent.payload}, a raw `Error` is\n\t * not JSON-round-trippable, so keyed-storage codecs wired via\n\t * `attachEventStorage` should normalize this field (e.g. capture\n\t * `message`/`stack`) before serializing.\n\t */\n\treadonly error?: unknown;\n}\n\n/** Recommended `keyOf` for keyed-storage adapters (Audit 2 #7). */\nexport const jobEventKeyOf = <T>(e: JobEvent<T>): string => e.action;\n\nexport type JobQueueOptions = {\n\tgraph?: GraphOptions;\n};\n\nexport class JobQueueGraph<T> extends Graph {\n\tprivate readonly _pending;\n\tprivate readonly _jobs;\n\tprivate readonly _seqCursor: Node<number>;\n\treadonly pending: Node<readonly string[]>;\n\treadonly jobs: Node<ReadonlyMap<string, JobEnvelope<T>>>;\n\treadonly depth: Node<number>;\n\t/** Audit log of every queue mutation (Audit 2). */\n\treadonly events: ReactiveLogBundle<JobEvent<T>>;\n\t/**\n\t * Read-only view of {@link JobQueueGraph.events} — Audit 2 `.audit`\n\t * duplication; M7 (cannot mutate the canonical log via the alias).\n\t */\n\treadonly audit: ReadonlyAuditLog<JobEvent<T>>;\n\n\t// Tier 8 / COMPOSITION-GUIDE §35: mutate wrappers for the four\n\t// single-record mutation methods. Assigned in the constructor (NOT via\n\t// class-field initializers) because field initializers run before the\n\t// constructor body — `this.events` and `this._seqCursor` aren't ready yet.\n\t// `claim` stays inline because it emits one record per claimed job.\n\tprivate readonly _enqueueImpl: (\n\t\tpayload: T,\n\t\topts: { id?: string; metadata?: Record<string, unknown> },\n\t) => string;\n\tprivate readonly _ackImpl: (id: string, job: JobEnvelope<T>) => void;\n\tprivate readonly _nackImpl: (\n\t\tid: string,\n\t\tjob: JobEnvelope<T>,\n\t\trequeue: boolean,\n\t\terror?: unknown,\n\t) => void;\n\tprivate readonly _removeByIdImpl: (id: string, job: JobEnvelope<T>) => void;\n\n\tconstructor(name: string, opts: JobQueueOptions = {}) {\n\t\tsuper(name, opts.graph);\n\t\tthis._pending = reactiveList<string>([], { name: \"pending\" });\n\t\tthis._jobs = reactiveMap<string, JobEnvelope<T>>({ name: \"jobs\" });\n\t\tthis.pending = this._pending.items;\n\t\tthis.jobs = this._jobs.entries;\n\t\tthis.add(this.pending, { name: \"pending\" });\n\t\tthis.add(this.jobs, { name: \"jobs\" });\n\t\tthis.depth = node(\n\t\t\t[this.pending],\n\t\t\t(batchData, actions, ctx) => {\n\t\t\t\tconst data = batchData.map((batch, i) =>\n\t\t\t\t\tbatch != null && batch.length > 0 ? batch.at(-1) : ctx.prevData[i],\n\t\t\t\t);\n\t\t\t\tactions.emit((data[0] as readonly string[]).length);\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"depth\",\n\t\t\t\tdescribeKind: \"derived\",\n\t\t\t\tmeta: jobQueueMeta(\"queue_depth\"),\n\t\t\t\tinitial: 0,\n\t\t\t},\n\t\t);\n\t\tthis.add(this.depth, { name: \"depth\" });\n\t\tthis.addDisposer(keepalive(this.depth));\n\n\t\tthis.events = createAuditLog<JobEvent<T>>({\n\t\t\tname: \"events\",\n\t\t\tretainedLimit: 1024,\n\t\t\tgraph: this,\n\t\t});\n\t\tthis.audit = readonlyAuditLog(this.events);\n\t\tthis._seqCursor = registerCursor(this, \"seq\", 0);\n\n\t\t// `freeze: false` everywhere because the payload may be large and\n\t\t// per-mutation cost matters on hot paths. mutate bumps `seq` via\n\t\t// the registered cursor BEFORE the action runs, so action bodies that\n\t\t// need the just-bumped value (e.g. enqueue's auto-id) read\n\t\t// `this._seqCursor.cache`.\n\t\tthis._enqueueImpl = mutate<\n\t\t\t[T, { id?: string; metadata?: Record<string, unknown> }],\n\t\t\tstring,\n\t\t\tJobEvent<T>\n\t\t>(\n\t\t\t(payload, enqueueOpts): string => {\n\t\t\t\tconst seq = this._seqCursor.cache as number;\n\t\t\t\tconst id = enqueueOpts.id ?? `${this.name}-${seq}`;\n\t\t\t\tif (this._jobs.get(id) !== undefined) {\n\t\t\t\t\tthrow new Error(`jobQueue(\"${this.name}\"): duplicate job id \"${id}\"`);\n\t\t\t\t}\n\t\t\t\tconst job: JobEnvelope<T> = {\n\t\t\t\t\tid,\n\t\t\t\t\tpayload,\n\t\t\t\t\tattempts: 0,\n\t\t\t\t\tmetadata: Object.freeze({ ...(enqueueOpts.metadata ?? {}) }),\n\t\t\t\t\tstate: \"queued\",\n\t\t\t\t};\n\t\t\t\tthis._jobs.set(id, job);\n\t\t\t\tthis._pending.append(id);\n\t\t\t\treturn id;\n\t\t\t},\n\t\t\t{\n\t\t\t\tframe: \"inline\",\n\t\t\t\tlog: this.events,\n\t\t\t\tseq: this._seqCursor,\n\t\t\t\tfreeze: false,\n\t\t\t\tonSuccessRecord: ([payload], id, { t_ns, seq }) => ({\n\t\t\t\t\taction: \"enqueue\",\n\t\t\t\t\tid,\n\t\t\t\t\tpayload,\n\t\t\t\t\tt_ns,\n\t\t\t\t\tseq: seq ?? 0,\n\t\t\t\t}),\n\t\t\t},\n\t\t);\n\n\t\tthis._ackImpl = mutate<[string, JobEnvelope<T>], void, JobEvent<T>>(\n\t\t\t(id, _job): void => {\n\t\t\t\tthis._jobs.delete(id);\n\t\t\t},\n\t\t\t{\n\t\t\t\tframe: \"inline\",\n\t\t\t\tlog: this.events,\n\t\t\t\tseq: this._seqCursor,\n\t\t\t\tfreeze: false,\n\t\t\t\tonSuccessRecord: ([id, job], _r, { t_ns, seq }) => ({\n\t\t\t\t\taction: \"ack\",\n\t\t\t\t\tid,\n\t\t\t\t\tattempts: job.attempts,\n\t\t\t\t\tt_ns,\n\t\t\t\t\tseq: seq ?? 0,\n\t\t\t\t}),\n\t\t\t},\n\t\t);\n\n\t\tthis._nackImpl = mutate<[string, JobEnvelope<T>, boolean, unknown], void, JobEvent<T>>(\n\t\t\t(id, job, requeue, _error): void => {\n\t\t\t\tif (requeue) {\n\t\t\t\t\tthis._jobs.set(id, { ...job, state: \"queued\" });\n\t\t\t\t\tthis._pending.append(id);\n\t\t\t\t} else {\n\t\t\t\t\tthis._jobs.delete(id);\n\t\t\t\t}\n\t\t\t},\n\t\t\t{\n\t\t\t\tframe: \"inline\",\n\t\t\t\tlog: this.events,\n\t\t\t\tseq: this._seqCursor,\n\t\t\t\tfreeze: false,\n\t\t\t\tonSuccessRecord: ([id, job, requeue, error], _r, { t_ns, seq }) => ({\n\t\t\t\t\taction: \"nack\",\n\t\t\t\t\tid,\n\t\t\t\t\tattempts: job.attempts,\n\t\t\t\t\tt_ns,\n\t\t\t\t\tseq: seq ?? 0,\n\t\t\t\t\t// F-CATCH D-3: surface the failure cause on the no-requeue\n\t\t\t\t\t// (terminal-failure) nack only. A requeue nack is a retry,\n\t\t\t\t\t// not a failure, so it carries no error.\n\t\t\t\t\t...(!requeue && error !== undefined ? { error } : {}),\n\t\t\t\t}),\n\t\t\t},\n\t\t);\n\n\t\tthis._removeByIdImpl = mutate<[string, JobEnvelope<T>], void, JobEvent<T>>(\n\t\t\t(id, job): void => {\n\t\t\t\tif (job.state === \"queued\") {\n\t\t\t\t\tconst pending = this.pending.cache as readonly string[];\n\t\t\t\t\tconst idx = pending.indexOf(id);\n\t\t\t\t\tif (idx >= 0) this._pending.pop(idx);\n\t\t\t\t}\n\t\t\t\tthis._jobs.delete(id);\n\t\t\t},\n\t\t\t{\n\t\t\t\tframe: \"inline\",\n\t\t\t\tlog: this.events,\n\t\t\t\tseq: this._seqCursor,\n\t\t\t\tfreeze: false,\n\t\t\t\tonSuccessRecord: ([id, job], _r, { t_ns, seq }) => ({\n\t\t\t\t\taction: \"remove\",\n\t\t\t\t\tid,\n\t\t\t\t\tattempts: job.attempts,\n\t\t\t\t\tt_ns,\n\t\t\t\t\tseq: seq ?? 0,\n\t\t\t\t}),\n\t\t\t},\n\t\t);\n\t}\n\n\t/**\n\t * Wire append-log storage tiers (Audit 4). Returns a disposer.\n\t *\n\t * Named `attachEventStorage` to avoid colliding with {@link Graph.attachSnapshotStorage}.\n\t */\n\tattachEventStorage(tiers: readonly AppendLogStorageTier<JobEvent<T>>[]): () => void {\n\t\treturn this.events.attachStorage(tiers);\n\t}\n\n\tenqueue(payload: T, opts: { id?: string; metadata?: Record<string, unknown> } = {}): string {\n\t\treturn this._enqueueImpl(payload, opts);\n\t}\n\n\tclaim(limit = 1): readonly JobEnvelope<T>[] {\n\t\tconst max = requireNonNegativeInt(limit, \"job queue claim limit\");\n\t\tif (max === 0) return [];\n\t\tconst out: JobEnvelope<T>[] = [];\n\t\twhile (out.length < max) {\n\t\t\tconst ids = this.pending.cache as readonly string[];\n\t\t\tif (ids.length === 0) break;\n\t\t\tconst id = this._pending.pop(0);\n\t\t\tconst job = this._jobs.get(id);\n\t\t\tif (!job || job.state !== \"queued\") continue;\n\t\t\tconst inflight: JobEnvelope<T> = {\n\t\t\t\t...job,\n\t\t\t\tstate: \"inflight\",\n\t\t\t\tattempts: job.attempts + 1,\n\t\t\t};\n\t\t\tthis._jobs.set(id, inflight);\n\t\t\tout.push(inflight);\n\t\t\t// claim emits one audit record per claimed job; mutate wraps a\n\t\t\t// single call → single record, so claim stays inline and bumps the\n\t\t\t// cursor directly via the shared `bumpCursor` helper.\n\t\t\tthis.events.append({\n\t\t\t\taction: \"claim\",\n\t\t\t\tid,\n\t\t\t\tattempts: inflight.attempts,\n\t\t\t\tt_ns: wallClockNs(),\n\t\t\t\tseq: bumpCursor(this._seqCursor),\n\t\t\t});\n\t\t}\n\t\treturn out;\n\t}\n\n\tack(id: string): boolean {\n\t\tconst job = this._jobs.get(id);\n\t\tif (!job || job.state !== \"inflight\") return false;\n\t\tthis._ackImpl(id, job);\n\t\treturn true;\n\t}\n\n\t/**\n\t * Negatively-acknowledge an inflight job.\n\t *\n\t * @param opts.requeue - `true` (default) returns the job to the queue for\n\t * retry; `false` drops it permanently (terminal failure).\n\t * @param opts.error - Optional failure cause for a `requeue: false` nack.\n\t * Recorded on the emitted `\"nack\"` {@link JobEvent} as `error` so the\n\t * reason a job failed is recoverable from the event stream (F-CATCH\n\t * D-3). Ignored when `requeue` is `true` (a retry is not a failure).\n\t */\n\tnack(id: string, opts: { requeue?: boolean; error?: unknown } = {}): boolean {\n\t\tconst job = this._jobs.get(id);\n\t\tif (!job || job.state !== \"inflight\") return false;\n\t\tthis._nackImpl(id, job, opts.requeue ?? true, opts.error);\n\t\treturn true;\n\t}\n\n\t/**\n\t * Remove a job by id regardless of its current state. Returns `true` if\n\t * the job existed and was removed, `false` if no job has this id.\n\t *\n\t * `ack` only works on inflight; `nack` only works on inflight.\n\t * `removeById` is the state-agnostic escape hatch — useful for\n\t * audit/observability layers that enqueue but never claim, and need to\n\t * finalize a job when an external decision (e.g. harness verify\n\t * outcome) resolves it. Distinct name from the inherited\n\t * {@link Graph.remove}, which removes a mounted child subgraph by path.\n\t *\n\t * When the job is in `queued` state, its id is also pulled from the\n\t * `pending` list — depth + pending snapshot stay consistent.\n\t */\n\tremoveById(id: string): boolean {\n\t\tconst job = this._jobs.get(id);\n\t\tif (!job) return false;\n\t\tthis._removeByIdImpl(id, job);\n\t\treturn true;\n\t}\n\n\t/**\n\t * Subscribe to a Node and enqueue each DATA payload into this queue.\n\t * Returns a disposer that stops consuming when called.\n\t *\n\t * Used internally by {@link JobFlowGraph} for stage-to-stage wiring but\n\t * also useful for users wiring an external source into a job queue.\n\t *\n\t * @param source - Node whose DATA values are enqueued.\n\t * @param opts - Optional enqueue options (id generator, metadata prefix).\n\t */\n\tconsumeFrom(\n\t\tsource: Node<T>,\n\t\topts?: {\n\t\t\tmetadata?: Record<string, unknown>;\n\t\t},\n\t): () => void {\n\t\treturn source.subscribe((msgs) => {\n\t\t\tfor (const m of msgs) {\n\t\t\t\tif (m[0] !== DATA) continue;\n\t\t\t\tconst payload = m[1] as T;\n\t\t\t\tthis.enqueue(payload, opts ? { metadata: opts.metadata } : undefined);\n\t\t\t}\n\t\t});\n\t}\n}\n\n// ── StageDef ─────────────────────────────────────────────────────────────\n\n/**\n * Work function for a job flow stage. Receives the full job envelope and\n * an optional per-claim options object carrying an `AbortSignal`; returns\n * a `NodeInput<T>` — raw value (sync), Promise (async), or Node (composed\n * pipeline). `fromAny` coerces any of these shapes.\n *\n * On error / rejection: the stage nacks the job (no requeue by default).\n *\n * **Per-claim signal (Tier 6.5 2.5b, 2026-04-29).** The pump mints an\n * `AbortController` per claim and supplies its `signal` via `opts`. The\n * signal aborts when (a) the result node settles (first DATA / first\n * ERROR — auto-cleanup after the pump captures), OR (b) the pump itself\n * tears down (e.g. parent Graph `destroy()`). User-supplied work fns that\n * do long-running async (HTTP, LLM streams, evaluator subgraphs) can\n * forward this signal into `fetch({ signal })`, `adapter.invoke({ signal\n * })`, etc. for cooperative cancellation. Sync work fns ignore `opts` —\n * the second arg is optional, no behavior change for legacy callers.\n *\n * Mirrors the `LLMInvokeOptions.signal` / `apply(item, { signal })` /\n * tool-handler `(args, { signal })` precedents — same shape across the\n * library's user-callback boundaries.\n */\nexport type WorkFn<T> = (job: JobEnvelope<T>, opts?: { signal: AbortSignal }) => NodeInput<T>;\n\n/**\n * Stage definition for {@link JobFlowGraph}. Either a bare name string\n * (no work hook, pure pass-through) or a full definition object.\n */\nexport type StageDef<T> =\n\t| string\n\t| {\n\t\t\tname: string;\n\t\t\twork?: WorkFn<T>;\n\t\t\thandlerVersion?: { id: string; version: string | number };\n\t\t\t/**\n\t\t\t * Per-stage cap on `claim → work → ack` cycles per pump tick.\n\t\t\t * Overrides {@link JobFlowOptions.maxPerPump} for this stage. Useful\n\t\t\t * when stages have asymmetric cost (e.g. an LLM-execute stage capped\n\t\t\t * at 4 concurrent calls while a cheap verify stage runs unbounded).\n\t\t\t *\n\t\t\t * Falls back to the top-level `JobFlowOptions.maxPerPump`, which in\n\t\t\t * turn falls back to `DEFAULT_MAX_PER_PUMP` (256).\n\t\t\t */\n\t\t\tmaxPerPump?: number;\n\t\t\t/**\n\t\t\t * Per-stage cap on TOTAL concurrent inflight claims (Tier 6.5 3.1,\n\t\t\t * 2026-04-29). Distinct from {@link maxPerPump}: `maxPerPump` caps\n\t\t\t * claims per pump tick, while `maxInflight` caps the number of\n\t\t\t * unsettled in-flight claims at any moment across all ticks. Use\n\t\t\t * for rigorous LLM cost ceilings (e.g. \"no more than 4 concurrent\n\t\t\t * adapter.invoke calls regardless of pending depth\").\n\t\t\t *\n\t\t\t * When set, the stage mounts an internal `state(0)` counter as a\n\t\t\t * pump dep so the pump re-fires on each settle — pending items\n\t\t\t * resume claiming as soon as inflight drops below the cap.\n\t\t\t *\n\t\t\t * Unset (default): unbounded inflight, gated only by `maxPerPump`.\n\t\t\t */\n\t\t\tmaxInflight?: number;\n\t };\n\nexport type JobFlowOptions<T = unknown> = {\n\tgraph?: GraphOptions;\n\t/**\n\t * Stage definitions. Each stage is either a bare name string (pure\n\t * pass-through) or a `{ name, work?, handlerVersion?, maxPerPump? }` object.\n\t *\n\t * For back-compat, `string[]` values behave as stages with no work hook.\n\t */\n\tstages?: readonly StageDef<T>[];\n\t/**\n\t * Default cap on claims per pump tick across all stages. Per-stage\n\t * overrides can be set on each {@link StageDef.maxPerPump}; if neither is\n\t * set, falls back to `DEFAULT_MAX_PER_PUMP` (256).\n\t */\n\tmaxPerPump?: number;\n};\n\nexport class JobFlowGraph<T> extends Graph {\n\tprivate readonly _stageNames: readonly string[];\n\tprivate readonly _stageWorkFns: ReadonlyMap<string, WorkFn<T>>;\n\tprivate readonly _queues = new Map<string, JobQueueGraph<T>>();\n\tprivate readonly _completed;\n\treadonly completed: Node<readonly JobEnvelope<T>[]>;\n\treadonly completedCount: Node<number>;\n\n\tconstructor(name: string, opts: JobFlowOptions<T> = {}) {\n\t\tsuper(name, opts.graph);\n\n\t\t// Normalise stage definitions.\n\t\tconst rawStages = opts.stages ?? ([\"incoming\", \"processing\", \"done\"] as readonly StageDef<T>[]);\n\t\tconst stageNames: string[] = [];\n\t\tconst stageWorkFns = new Map<string, WorkFn<T>>();\n\t\tconst stageMaxPerPump = new Map<string, number>();\n\t\tconst stageMaxInflight = new Map<string, number>();\n\n\t\tfor (const raw of rawStages) {\n\t\t\tconst stageName = typeof raw === \"string\" ? raw.trim() : raw.name.trim();\n\t\t\tif (typeof raw !== \"string\" && raw.work) {\n\t\t\t\tstageWorkFns.set(stageName, raw.work);\n\t\t\t}\n\t\t\tif (typeof raw !== \"string\" && raw.maxPerPump != null) {\n\t\t\t\tstageMaxPerPump.set(\n\t\t\t\t\tstageName,\n\t\t\t\t\tMath.max(\n\t\t\t\t\t\t1,\n\t\t\t\t\t\trequireNonNegativeInt(raw.maxPerPump, `job flow stage \"${stageName}\" maxPerPump`),\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (typeof raw !== \"string\" && raw.maxInflight != null) {\n\t\t\t\tstageMaxInflight.set(\n\t\t\t\t\tstageName,\n\t\t\t\t\tMath.max(\n\t\t\t\t\t\t1,\n\t\t\t\t\t\trequireNonNegativeInt(raw.maxInflight, `job flow stage \"${stageName}\" maxInflight`),\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\t}\n\t\t\tstageNames.push(stageName);\n\t\t}\n\n\t\tif (stageNames.length < 2) {\n\t\t\tthrow new Error(`jobFlow(\"${name}\"): requires at least 2 stages`);\n\t\t}\n\t\tconst unique = new Set(stageNames);\n\t\tif (unique.size !== stageNames.length) {\n\t\t\tthrow new Error(`jobFlow(\"${name}\"): stage names must be unique`);\n\t\t}\n\t\tthis._stageNames = Object.freeze([...stageNames]);\n\t\tthis._stageWorkFns = stageWorkFns;\n\n\t\tfor (const stage of this._stageNames) {\n\t\t\tconst q = jobQueue<T>(`${name}-${stage}`);\n\t\t\tthis._queues.set(stage, q);\n\t\t\tthis.mount(stage, q);\n\t\t}\n\n\t\tthis._completed = reactiveLog<JobEnvelope<T>>([], {\n\t\t\tname: \"completed\",\n\t\t\tmaxSize: DEFAULT_COMPLETED_RETAINED_LIMIT,\n\t\t});\n\t\tthis.completed = this._completed.entries;\n\t\tthis.add(this.completed, { name: \"completed\" });\n\t\tthis.completedCount = node(\n\t\t\t[this.completed],\n\t\t\t(batchData, actions, ctx) => {\n\t\t\t\tconst data = batchData.map((batch, i) =>\n\t\t\t\t\tbatch != null && batch.length > 0 ? batch.at(-1) : ctx.prevData[i],\n\t\t\t\t);\n\t\t\t\tactions.emit((data[0] as readonly JobEnvelope<T>[]).length);\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"completedCount\",\n\t\t\t\tdescribeKind: \"derived\",\n\t\t\t\tmeta: jobQueueMeta(\"job_flow_completed_count\"),\n\t\t\t\tinitial: 0,\n\t\t\t},\n\t\t);\n\t\tthis.add(this.completedCount, { name: \"completedCount\" });\n\t\tthis.addDisposer(keepalive(this.completedCount));\n\n\t\tconst defaultMaxPerPump = Math.max(\n\t\t\t1,\n\t\t\trequireNonNegativeInt(opts.maxPerPump ?? DEFAULT_MAX_PER_PUMP, \"job flow maxPerPump\"),\n\t\t);\n\n\t\t// Wire up per-stage pumps.\n\t\tfor (let i = 0; i < this._stageNames.length; i += 1) {\n\t\t\tconst stage = this._stageNames[i] as string;\n\t\t\tconst current = this.queue(stage);\n\t\t\tconst next =\n\t\t\t\ti + 1 < this._stageNames.length ? this.queue(this._stageNames[i + 1] as string) : null;\n\t\t\tconst workFn = this._stageWorkFns.get(stage);\n\t\t\t// Per-stage `maxPerPump` override falls back to the top-level cap.\n\t\t\t// Captured per stage so each pump's loop sees its own resolved limit.\n\t\t\tconst stagePerPump = stageMaxPerPump.get(stage) ?? defaultMaxPerPump;\n\t\t\tconst stageMaxInflightCap = stageMaxInflight.get(stage);\n\t\t\t// When `maxInflight` is set, mount a state(0) counter on the graph\n\t\t\t// and wire it as an extra pump dep — settles `inflightCounter.emit`\n\t\t\t// re-fire the pump so pending items resume claiming after each\n\t\t\t// settle (without a counter, the pump only fires on `pending`\n\t\t\t// changes, which `ack` does not affect → maxInflight at saturation\n\t\t\t// would deadlock the queue).\n\t\t\t// qa F-D (Tier 5 /qa pass, 2026-04-29): mount under `__inflight__/`\n\t\t\t// internal namespace so the counter cannot collide with a user-named\n\t\t\t// stage (e.g. `inflight_my-stage`). Matches the EH-16\n\t\t\t// `__processManagers__/<name>` convention (COMPOSITION-GUIDE §38 —\n\t\t\t// internal infrastructure paths use the `__` prefix).\n\t\t\tconst inflightCounter =\n\t\t\t\tstageMaxInflightCap !== undefined\n\t\t\t\t\t? node<number>([], { name: `__inflight__/${stage}`, initial: 0 })\n\t\t\t\t\t: null;\n\t\t\tif (inflightCounter) {\n\t\t\t\tthis.add(inflightCounter, { name: `__inflight__/${stage}` });\n\t\t\t}\n\n\t\t\t// `isTerminal` marks the last stage — completed jobs go into\n\t\t\t// `_completed` log instead of a next queue.\n\t\t\tconst isTerminal = next === null;\n\n\t\t\tif (workFn) {\n\t\t\t\t// ── Stage with work hook ──────────────────────────────────────\n\t\t\t\t// Pump effect: claim one job, run work(job), forward result on\n\t\t\t\t// success; nack on failure.\n\t\t\t\t// Per B.3 lock: effects ARE sanctioned for side-effects.\n\t\t\t\t// `fromAny` bridges sync value / Promise / Node → Node<T>.\n\t\t\t\t//\n\t\t\t\t// **Inflight teardown drain (Tier 6.5 2.5a, 2026-04-29).** Each\n\t\t\t\t// claim mints a per-claim `AbortController` and tracks the\n\t\t\t\t// `(unsub, ac)` pair in a `ctx.store.inflight` Set. The\n\t\t\t\t// per-claim signal is supplied to the work fn via the optional\n\t\t\t\t// second-arg `{ signal }` (mirrors `LLMInvokeOptions.signal` /\n\t\t\t\t// `apply(item, {signal})` / tool-handler precedents). On the\n\t\t\t\t// pump's `deactivate` hook (parent Graph TEARDOWN cascade —\n\t\t\t\t// e.g. `harness.destroy()`), every inflight entry is aborted +\n\t\t\t\t// unsubscribed so user-supplied async work (LLM streams, eval\n\t\t\t\t// HTTP calls, refineLoop iterations) gets cooperative\n\t\t\t\t// cancellation instead of leaking past the harness lifetime.\n\t\t\t\ttype InflightEntry = { unsub: () => void; ac: AbortController };\n\t\t\t\ttype InflightStore = { entries: Set<InflightEntry>; terminated: boolean };\n\t\t\t\tconst pumpDeps: Node[] =\n\t\t\t\t\tinflightCounter != null ? [current.pending, inflightCounter] : [current.pending];\n\t\t\t\tconst pump = node<unknown>(\n\t\t\t\t\tpumpDeps,\n\t\t\t\t\t(_data, _actions, ctx) => {\n\t\t\t\t\t\tif (!(\"inflight\" in ctx.store)) {\n\t\t\t\t\t\t\tctx.store.inflight = {\n\t\t\t\t\t\t\t\tentries: new Set<InflightEntry>(),\n\t\t\t\t\t\t\t\tterminated: false,\n\t\t\t\t\t\t\t} satisfies InflightStore;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst inflightStore = ctx.store.inflight as InflightStore;\n\t\t\t\t\t\tconst inflight = inflightStore.entries;\n\t\t\t\t\t\tlet processed = 0;\n\t\t\t\t\t\twhile (processed < stagePerPump) {\n\t\t\t\t\t\t\t// 3.1 maxInflight gate: cap concurrent inflight across pump\n\t\t\t\t\t\t\t// ticks. The inflightCounter (mounted as a pump dep) re-fires\n\t\t\t\t\t\t\t// the pump when a settle decrements it, so pending items\n\t\t\t\t\t\t\t// resume claiming when capacity frees up.\n\t\t\t\t\t\t\tif (stageMaxInflightCap !== undefined && inflight.size >= stageMaxInflightCap) {\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tconst claims = current.claim(1);\n\t\t\t\t\t\t\tif (claims.length === 0) break;\n\t\t\t\t\t\t\tconst job = claims[0] as JobEnvelope<T>;\n\t\t\t\t\t\t\tif (!job) break;\n\n\t\t\t\t\t\t\t// Build the updated path accumulator.\n\t\t\t\t\t\t\tconst prevPath = (job.metadata.job_flow_path as readonly string[] | undefined) ?? [];\n\t\t\t\t\t\t\tconst newPath = [...prevPath, stage];\n\n\t\t\t\t\t\t\tconst ac = new AbortController();\n\t\t\t\t\t\t\tconst entry: InflightEntry = { unsub: () => undefined, ac };\n\t\t\t\t\t\t\tinflight.add(entry);\n\t\t\t\t\t\t\tinflightCounter?.emit(inflight.size);\n\n\t\t\t\t\t\t\tlet result: NodeInput<T>;\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\tresult = workFn(job, { signal: ac.signal });\n\t\t\t\t\t\t\t} catch (err) {\n\t\t\t\t\t\t\t\t// Sync throw → nack no-requeue. F-CATCH D-3: thread\n\t\t\t\t\t\t\t\t// the thrown value onto the nack event so the\n\t\t\t\t\t\t\t\t// failure is diagnosable, not just observable.\n\t\t\t\t\t\t\t\tinflight.delete(entry);\n\t\t\t\t\t\t\t\tinflightCounter?.emit(inflight.size);\n\t\t\t\t\t\t\t\tcurrent.nack(job.id, { requeue: false, error: err });\n\t\t\t\t\t\t\t\tprocessed += 1;\n\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// Coerce to Node<T> via fromAny.\n\t\t\t\t\t\t\tconst resultNode = fromAny<T>(result);\n\n\t\t\t\t\t\t\t// M8: Subscribe once to the result node; on DATA forward; on ERROR nack.\n\t\t\t\t\t\t\t// Use `let unsub` + TDZ guard (same pattern as toPromise) so sync\n\t\t\t\t\t\t\t// DATA delivery inside subscribe() doesn't hit TDZ on `unsub`.\n\t\t\t\t\t\t\tlet settled = false;\n\t\t\t\t\t\t\tlet unsub: (() => void) | undefined;\n\t\t\t\t\t\t\tconst cleanupSub = (): void => {\n\t\t\t\t\t\t\t\tif (unsub) {\n\t\t\t\t\t\t\t\t\tunsub();\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tPromise.resolve().then(() => unsub?.());\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tinflight.delete(entry);\n\t\t\t\t\t\t\t\t// qa F-F (Tier 5 /qa pass, 2026-04-29): skip the counter\n\t\t\t\t\t\t\t\t// emit after teardown — the counter Node is itself in the\n\t\t\t\t\t\t\t\t// cascade. Late ERROR/DATA arriving via the deferred\n\t\t\t\t\t\t\t\t// `Promise.resolve().then(unsub)` path could otherwise emit\n\t\t\t\t\t\t\t\t// on a torn-down node.\n\t\t\t\t\t\t\t\tif (!inflightStore.terminated) {\n\t\t\t\t\t\t\t\t\tinflightCounter?.emit(inflight.size);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\tunsub = resultNode.subscribe((msgs) => {\n\t\t\t\t\t\t\t\tif (settled) return;\n\t\t\t\t\t\t\t\tfor (const m of msgs) {\n\t\t\t\t\t\t\t\t\tif (m[0] === DATA) {\n\t\t\t\t\t\t\t\t\t\tsettled = true;\n\t\t\t\t\t\t\t\t\t\tcleanupSub();\n\t\t\t\t\t\t\t\t\t\tconst newPayload = m[1] as T;\n\t\t\t\t\t\t\t\t\t\tconst newMetadata = {\n\t\t\t\t\t\t\t\t\t\t\t...job.metadata,\n\t\t\t\t\t\t\t\t\t\t\tjob_flow_path: newPath,\n\t\t\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\t\t\tif (isTerminal) {\n\t\t\t\t\t\t\t\t\t\t\tconst completedJob: JobEnvelope<T> = {\n\t\t\t\t\t\t\t\t\t\t\t\t...job,\n\t\t\t\t\t\t\t\t\t\t\t\tpayload: newPayload,\n\t\t\t\t\t\t\t\t\t\t\t\tmetadata: Object.freeze(newMetadata),\n\t\t\t\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\t\t\t\tbatch(() => {\n\t\t\t\t\t\t\t\t\t\t\t\tcurrent.ack(job.id);\n\t\t\t\t\t\t\t\t\t\t\t\tthis._completed.append(completedJob);\n\t\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\tbatch(() => {\n\t\t\t\t\t\t\t\t\t\t\t\tcurrent.ack(job.id);\n\t\t\t\t\t\t\t\t\t\t\t\t(next as JobQueueGraph<T>).enqueue(newPayload, {\n\t\t\t\t\t\t\t\t\t\t\t\t\tmetadata: newMetadata,\n\t\t\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t\t\t} else if (m[0] === ERROR) {\n\t\t\t\t\t\t\t\t\t\tsettled = true;\n\t\t\t\t\t\t\t\t\t\tcleanupSub();\n\t\t\t\t\t\t\t\t\t\t// F-CATCH D-3: carry the ERROR payload onto\n\t\t\t\t\t\t\t\t\t\t// the nack event (symmetric with the sync\n\t\t\t\t\t\t\t\t\t\t// -throw path) so failed jobs are diagnosable.\n\t\t\t\t\t\t\t\t\t\tcurrent.nack(job.id, { requeue: false, error: m[1] });\n\t\t\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\tentry.unsub = () => unsub?.();\n\n\t\t\t\t\t\t\tprocessed += 1;\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tonDeactivation: () => {\n\t\t\t\t\t\t\t\t// qa F-F: set terminated BEFORE draining so any\n\t\t\t\t\t\t\t\t// `cleanupSub` racing via the deferred-microtask path\n\t\t\t\t\t\t\t\t// (`Promise.resolve().then(() => unsub?.())`) sees\n\t\t\t\t\t\t\t\t// `terminated === true` and skips its `inflightCounter.emit`.\n\t\t\t\t\t\t\t\tinflightStore.terminated = true;\n\t\t\t\t\t\t\t\tfor (const e of inflight) {\n\t\t\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t\t\te.ac.abort();\n\t\t\t\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\t\t\t\t// best-effort\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t\t\te.unsub();\n\t\t\t\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\t\t\t\t// best-effort\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tinflight.clear();\n\t\t\t\t\t\t\t\t// Lock 6.D (Phase 13.6.B): drop the `inflight` key\n\t\t\t\t\t\t\t\t// so the next activation re-initializes a fresh\n\t\t\t\t\t\t\t\t// `InflightStore` with `terminated: false`. Without\n\t\t\t\t\t\t\t\t// this, post-flip preserve-by-default keeps the\n\t\t\t\t\t\t\t\t// stale `terminated: true` flag and silently\n\t\t\t\t\t\t\t\t// suppresses inflight-counter emits forever.\n\t\t\t\t\t\t\t\tdelete ctx.store.inflight;\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t};\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tmeta: jobQueueMeta(\"job_flow_pump\", { stage, has_work: true }),\n\t\t\t\t\t},\n\t\t\t\t);\n\t\t\t\tthis.addDisposer(keepalive(pump));\n\t\t\t} else {\n\t\t\t\t// ── Stage without work hook (pass-through ferry) ──────────────\n\t\t\t\t// Claim, accumulate path, forward to next stage or completed.\n\t\t\t\tconst pump = this.effect(\n\t\t\t\t\t`pump_${stage}`,\n\t\t\t\t\t[`${stage}::pending`],\n\t\t\t\t\t() => {\n\t\t\t\t\t\tlet moved = 0;\n\t\t\t\t\t\twhile (moved < stagePerPump) {\n\t\t\t\t\t\t\tconst claim = current.claim(1);\n\t\t\t\t\t\t\tif (claim.length === 0) break;\n\t\t\t\t\t\t\tconst job = claim[0] as JobEnvelope<T>;\n\t\t\t\t\t\t\tif (!job) break;\n\n\t\t\t\t\t\t\tconst prevPath = (job.metadata.job_flow_path as readonly string[] | undefined) ?? [];\n\t\t\t\t\t\t\tconst newPath = [...prevPath, stage];\n\t\t\t\t\t\t\tconst newMetadata = { ...job.metadata, job_flow_path: newPath };\n\n\t\t\t\t\t\t\tif (isTerminal) {\n\t\t\t\t\t\t\t\tconst completedJob: JobEnvelope<T> = {\n\t\t\t\t\t\t\t\t\t...job,\n\t\t\t\t\t\t\t\t\tmetadata: Object.freeze(newMetadata),\n\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\tbatch(() => {\n\t\t\t\t\t\t\t\t\tcurrent.ack(job.id);\n\t\t\t\t\t\t\t\t\tthis._completed.append(completedJob);\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tbatch(() => {\n\t\t\t\t\t\t\t\t\tcurrent.ack(job.id);\n\t\t\t\t\t\t\t\t\t(next as JobQueueGraph<T>).enqueue(job.payload, {\n\t\t\t\t\t\t\t\t\t\tmetadata: newMetadata,\n\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tmoved += 1;\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tmeta: jobQueueMeta(\"job_flow_pump\", { stage, has_work: false }),\n\t\t\t\t\t},\n\t\t\t\t);\n\t\t\t\tthis.addDisposer(keepalive(pump));\n\t\t\t}\n\t\t}\n\t}\n\n\tstages(): readonly string[] {\n\t\treturn this._stageNames;\n\t}\n\n\tqueue(stage: string): JobQueueGraph<T> {\n\t\tconst q = this._queues.get(stage);\n\t\tif (!q) throw new Error(`jobFlow(\"${this.name}\"): unknown stage \"${stage}\"`);\n\t\treturn q;\n\t}\n\n\tenqueue(payload: T, opts: { id?: string; metadata?: Record<string, unknown> } = {}): string {\n\t\treturn this.queue(this._stageNames[0] as string).enqueue(payload, opts);\n\t}\n\n\tretainedCompleted(): readonly JobEnvelope<T>[] {\n\t\treturn this.completed.cache as readonly JobEnvelope<T>[];\n\t}\n}\n\n/**\n * Creates a Pulsar-inspired job queue graph with claim/ack/nack workflow.\n */\nexport function jobQueue<T>(name: string, opts?: JobQueueOptions): JobQueueGraph<T> {\n\treturn new JobQueueGraph<T>(name, opts);\n}\n\n/**\n * Creates an autonomous multi-stage queue chain graph.\n */\nexport function jobFlow<T>(name: string, opts?: JobFlowOptions<T>): JobFlowGraph<T> {\n\tconst g = new JobFlowGraph<T>(name, opts);\n\t// Tier 1.5.3 Phase 2.5 (DG1=B): tag the Graph with its constructing\n\t// factory so `describe()` surfaces provenance. Route through\n\t// `placeholderArgs` since `stages[].work` is a function and\n\t// `opts.graph` may carry non-JSON fields.\n\tconst { factory: _f, factoryArgs: _fa, ...tagArgs } = (opts ?? {}) as Record<string, unknown>;\n\tg.tagFactory(\"jobFlow\", placeholderArgs(tagArgs));\n\treturn g;\n}\n"],"mappings":";;;;;;;;;;;;AAUA;AAAA,EACC;AAAA,EACA;AAAA,EACA;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,OACM;AAEP;AAAA,EACC;AAAA,EACA;AAAA,EAGA;AAAA,EACA;AAAA,EACA;AAAA,OACM;AACP,SAAS,aAAgC;AAYzC,IAAM,uBAAuB;AAC7B,IAAM,mCAAmC;AAEzC,SAAS,sBAAsB,OAAe,OAAuB;AACpE,MAAI,CAAC,OAAO,SAAS,KAAK,KAAK,CAAC,OAAO,UAAU,KAAK,KAAK,QAAQ,GAAG;AACrE,UAAM,IAAI,MAAM,GAAG,KAAK,iCAAiC;AAAA,EAC1D;AACA,SAAO;AACR;AAEA,SAAS,aAAa,MAAc,OAA0D;AAC7F,SAAO,WAAW,aAAa,MAAM,KAAK;AAC3C;AAuCO,IAAM,gBAAgB,CAAI,MAA2B,EAAE;AAMvD,IAAM,gBAAN,cAA+B,MAAM;AAAA,EAC1B;AAAA,EACA;AAAA,EACA;AAAA,EACR;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAEA;AAAA;AAAA;AAAA;AAAA;AAAA,EAKA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOQ;AAAA,EAIA;AAAA,EACA;AAAA,EAMA;AAAA,EAEjB,YAAY,MAAc,OAAwB,CAAC,GAAG;AACrD,UAAM,MAAM,KAAK,KAAK;AACtB,SAAK,WAAW,aAAqB,CAAC,GAAG,EAAE,MAAM,UAAU,CAAC;AAC5D,SAAK,QAAQ,YAAoC,EAAE,MAAM,OAAO,CAAC;AACjE,SAAK,UAAU,KAAK,SAAS;AAC7B,SAAK,OAAO,KAAK,MAAM;AACvB,SAAK,IAAI,KAAK,SAAS,EAAE,MAAM,UAAU,CAAC;AAC1C,SAAK,IAAI,KAAK,MAAM,EAAE,MAAM,OAAO,CAAC;AACpC,SAAK,QAAQ;AAAA,MACZ,CAAC,KAAK,OAAO;AAAA,MACb,CAAC,WAAW,SAAS,QAAQ;AAC5B,cAAM,OAAO,UAAU;AAAA,UAAI,CAACA,QAAO,MAClCA,UAAS,QAAQA,OAAM,SAAS,IAAIA,OAAM,GAAG,EAAE,IAAI,IAAI,SAAS,CAAC;AAAA,QAClE;AACA,gBAAQ,KAAM,KAAK,CAAC,EAAwB,MAAM;AAAA,MACnD;AAAA,MACA;AAAA,QACC,MAAM;AAAA,QACN,cAAc;AAAA,QACd,MAAM,aAAa,aAAa;AAAA,QAChC,SAAS;AAAA,MACV;AAAA,IACD;AACA,SAAK,IAAI,KAAK,OAAO,EAAE,MAAM,QAAQ,CAAC;AACtC,SAAK,YAAY,UAAU,KAAK,KAAK,CAAC;AAEtC,SAAK,SAAS,eAA4B;AAAA,MACzC,MAAM;AAAA,MACN,eAAe;AAAA,MACf,OAAO;AAAA,IACR,CAAC;AACD,SAAK,QAAQ,iBAAiB,KAAK,MAAM;AACzC,SAAK,aAAa,eAAe,MAAM,OAAO,CAAC;AAO/C,SAAK,eAAe;AAAA,MAKnB,CAAC,SAAS,gBAAwB;AACjC,cAAM,MAAM,KAAK,WAAW;AAC5B,cAAM,KAAK,YAAY,MAAM,GAAG,KAAK,IAAI,IAAI,GAAG;AAChD,YAAI,KAAK,MAAM,IAAI,EAAE,MAAM,QAAW;AACrC,gBAAM,IAAI,MAAM,aAAa,KAAK,IAAI,yBAAyB,EAAE,GAAG;AAAA,QACrE;AACA,cAAM,MAAsB;AAAA,UAC3B;AAAA,UACA;AAAA,UACA,UAAU;AAAA,UACV,UAAU,OAAO,OAAO,EAAE,GAAI,YAAY,YAAY,CAAC,EAAG,CAAC;AAAA,UAC3D,OAAO;AAAA,QACR;AACA,aAAK,MAAM,IAAI,IAAI,GAAG;AACtB,aAAK,SAAS,OAAO,EAAE;AACvB,eAAO;AAAA,MACR;AAAA,MACA;AAAA,QACC,OAAO;AAAA,QACP,KAAK,KAAK;AAAA,QACV,KAAK,KAAK;AAAA,QACV,QAAQ;AAAA,QACR,iBAAiB,CAAC,CAAC,OAAO,GAAG,IAAI,EAAE,MAAM,IAAI,OAAO;AAAA,UACnD,QAAQ;AAAA,UACR;AAAA,UACA;AAAA,UACA;AAAA,UACA,KAAK,OAAO;AAAA,QACb;AAAA,MACD;AAAA,IACD;AAEA,SAAK,WAAW;AAAA,MACf,CAAC,IAAI,SAAe;AACnB,aAAK,MAAM,OAAO,EAAE;AAAA,MACrB;AAAA,MACA;AAAA,QACC,OAAO;AAAA,QACP,KAAK,KAAK;AAAA,QACV,KAAK,KAAK;AAAA,QACV,QAAQ;AAAA,QACR,iBAAiB,CAAC,CAAC,IAAI,GAAG,GAAG,IAAI,EAAE,MAAM,IAAI,OAAO;AAAA,UACnD,QAAQ;AAAA,UACR;AAAA,UACA,UAAU,IAAI;AAAA,UACd;AAAA,UACA,KAAK,OAAO;AAAA,QACb;AAAA,MACD;AAAA,IACD;AAEA,SAAK,YAAY;AAAA,MAChB,CAAC,IAAI,KAAK,SAAS,WAAiB;AACnC,YAAI,SAAS;AACZ,eAAK,MAAM,IAAI,IAAI,EAAE,GAAG,KAAK,OAAO,SAAS,CAAC;AAC9C,eAAK,SAAS,OAAO,EAAE;AAAA,QACxB,OAAO;AACN,eAAK,MAAM,OAAO,EAAE;AAAA,QACrB;AAAA,MACD;AAAA,MACA;AAAA,QACC,OAAO;AAAA,QACP,KAAK,KAAK;AAAA,QACV,KAAK,KAAK;AAAA,QACV,QAAQ;AAAA,QACR,iBAAiB,CAAC,CAAC,IAAI,KAAK,SAAS,KAAK,GAAG,IAAI,EAAE,MAAM,IAAI,OAAO;AAAA,UACnE,QAAQ;AAAA,UACR;AAAA,UACA,UAAU,IAAI;AAAA,UACd;AAAA,UACA,KAAK,OAAO;AAAA;AAAA;AAAA;AAAA,UAIZ,GAAI,CAAC,WAAW,UAAU,SAAY,EAAE,MAAM,IAAI,CAAC;AAAA,QACpD;AAAA,MACD;AAAA,IACD;AAEA,SAAK,kBAAkB;AAAA,MACtB,CAAC,IAAI,QAAc;AAClB,YAAI,IAAI,UAAU,UAAU;AAC3B,gBAAM,UAAU,KAAK,QAAQ;AAC7B,gBAAM,MAAM,QAAQ,QAAQ,EAAE;AAC9B,cAAI,OAAO,EAAG,MAAK,SAAS,IAAI,GAAG;AAAA,QACpC;AACA,aAAK,MAAM,OAAO,EAAE;AAAA,MACrB;AAAA,MACA;AAAA,QACC,OAAO;AAAA,QACP,KAAK,KAAK;AAAA,QACV,KAAK,KAAK;AAAA,QACV,QAAQ;AAAA,QACR,iBAAiB,CAAC,CAAC,IAAI,GAAG,GAAG,IAAI,EAAE,MAAM,IAAI,OAAO;AAAA,UACnD,QAAQ;AAAA,UACR;AAAA,UACA,UAAU,IAAI;AAAA,UACd;AAAA,UACA,KAAK,OAAO;AAAA,QACb;AAAA,MACD;AAAA,IACD;AAAA,EACD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,mBAAmB,OAAiE;AACnF,WAAO,KAAK,OAAO,cAAc,KAAK;AAAA,EACvC;AAAA,EAEA,QAAQ,SAAY,OAA4D,CAAC,GAAW;AAC3F,WAAO,KAAK,aAAa,SAAS,IAAI;AAAA,EACvC;AAAA,EAEA,MAAM,QAAQ,GAA8B;AAC3C,UAAM,MAAM,sBAAsB,OAAO,uBAAuB;AAChE,QAAI,QAAQ,EAAG,QAAO,CAAC;AACvB,UAAM,MAAwB,CAAC;AAC/B,WAAO,IAAI,SAAS,KAAK;AACxB,YAAM,MAAM,KAAK,QAAQ;AACzB,UAAI,IAAI,WAAW,EAAG;AACtB,YAAM,KAAK,KAAK,SAAS,IAAI,CAAC;AAC9B,YAAM,MAAM,KAAK,MAAM,IAAI,EAAE;AAC7B,UAAI,CAAC,OAAO,IAAI,UAAU,SAAU;AACpC,YAAM,WAA2B;AAAA,QAChC,GAAG;AAAA,QACH,OAAO;AAAA,QACP,UAAU,IAAI,WAAW;AAAA,MAC1B;AACA,WAAK,MAAM,IAAI,IAAI,QAAQ;AAC3B,UAAI,KAAK,QAAQ;AAIjB,WAAK,OAAO,OAAO;AAAA,QAClB,QAAQ;AAAA,QACR;AAAA,QACA,UAAU,SAAS;AAAA,QACnB,MAAM,YAAY;AAAA,QAClB,KAAK,WAAW,KAAK,UAAU;AAAA,MAChC,CAAC;AAAA,IACF;AACA,WAAO;AAAA,EACR;AAAA,EAEA,IAAI,IAAqB;AACxB,UAAM,MAAM,KAAK,MAAM,IAAI,EAAE;AAC7B,QAAI,CAAC,OAAO,IAAI,UAAU,WAAY,QAAO;AAC7C,SAAK,SAAS,IAAI,GAAG;AACrB,WAAO;AAAA,EACR;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYA,KAAK,IAAY,OAA+C,CAAC,GAAY;AAC5E,UAAM,MAAM,KAAK,MAAM,IAAI,EAAE;AAC7B,QAAI,CAAC,OAAO,IAAI,UAAU,WAAY,QAAO;AAC7C,SAAK,UAAU,IAAI,KAAK,KAAK,WAAW,MAAM,KAAK,KAAK;AACxD,WAAO;AAAA,EACR;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAgBA,WAAW,IAAqB;AAC/B,UAAM,MAAM,KAAK,MAAM,IAAI,EAAE;AAC7B,QAAI,CAAC,IAAK,QAAO;AACjB,SAAK,gBAAgB,IAAI,GAAG;AAC5B,WAAO;AAAA,EACR;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYA,YACC,QACA,MAGa;AACb,WAAO,OAAO,UAAU,CAAC,SAAS;AACjC,iBAAW,KAAK,MAAM;AACrB,YAAI,EAAE,CAAC,MAAM,KAAM;AACnB,cAAM,UAAU,EAAE,CAAC;AACnB,aAAK,QAAQ,SAAS,OAAO,EAAE,UAAU,KAAK,SAAS,IAAI,MAAS;AAAA,MACrE;AAAA,IACD,CAAC;AAAA,EACF;AACD;AAkFO,IAAM,eAAN,cAA8B,MAAM;AAAA,EACzB;AAAA,EACA;AAAA,EACA,UAAU,oBAAI,IAA8B;AAAA,EAC5C;AAAA,EACR;AAAA,EACA;AAAA,EAET,YAAY,MAAc,OAA0B,CAAC,GAAG;AACvD,UAAM,MAAM,KAAK,KAAK;AAGtB,UAAM,YAAY,KAAK,UAAW,CAAC,YAAY,cAAc,MAAM;AACnE,UAAM,aAAuB,CAAC;AAC9B,UAAM,eAAe,oBAAI,IAAuB;AAChD,UAAM,kBAAkB,oBAAI,IAAoB;AAChD,UAAM,mBAAmB,oBAAI,IAAoB;AAEjD,eAAW,OAAO,WAAW;AAC5B,YAAM,YAAY,OAAO,QAAQ,WAAW,IAAI,KAAK,IAAI,IAAI,KAAK,KAAK;AACvE,UAAI,OAAO,QAAQ,YAAY,IAAI,MAAM;AACxC,qBAAa,IAAI,WAAW,IAAI,IAAI;AAAA,MACrC;AACA,UAAI,OAAO,QAAQ,YAAY,IAAI,cAAc,MAAM;AACtD,wBAAgB;AAAA,UACf;AAAA,UACA,KAAK;AAAA,YACJ;AAAA,YACA,sBAAsB,IAAI,YAAY,mBAAmB,SAAS,cAAc;AAAA,UACjF;AAAA,QACD;AAAA,MACD;AACA,UAAI,OAAO,QAAQ,YAAY,IAAI,eAAe,MAAM;AACvD,yBAAiB;AAAA,UAChB;AAAA,UACA,KAAK;AAAA,YACJ;AAAA,YACA,sBAAsB,IAAI,aAAa,mBAAmB,SAAS,eAAe;AAAA,UACnF;AAAA,QACD;AAAA,MACD;AACA,iBAAW,KAAK,SAAS;AAAA,IAC1B;AAEA,QAAI,WAAW,SAAS,GAAG;AAC1B,YAAM,IAAI,MAAM,YAAY,IAAI,gCAAgC;AAAA,IACjE;AACA,UAAM,SAAS,IAAI,IAAI,UAAU;AACjC,QAAI,OAAO,SAAS,WAAW,QAAQ;AACtC,YAAM,IAAI,MAAM,YAAY,IAAI,gCAAgC;AAAA,IACjE;AACA,SAAK,cAAc,OAAO,OAAO,CAAC,GAAG,UAAU,CAAC;AAChD,SAAK,gBAAgB;AAErB,eAAW,SAAS,KAAK,aAAa;AACrC,YAAM,IAAI,SAAY,GAAG,IAAI,IAAI,KAAK,EAAE;AACxC,WAAK,QAAQ,IAAI,OAAO,CAAC;AACzB,WAAK,MAAM,OAAO,CAAC;AAAA,IACpB;AAEA,SAAK,aAAa,YAA4B,CAAC,GAAG;AAAA,MACjD,MAAM;AAAA,MACN,SAAS;AAAA,IACV,CAAC;AACD,SAAK,YAAY,KAAK,WAAW;AACjC,SAAK,IAAI,KAAK,WAAW,EAAE,MAAM,YAAY,CAAC;AAC9C,SAAK,iBAAiB;AAAA,MACrB,CAAC,KAAK,SAAS;AAAA,MACf,CAAC,WAAW,SAAS,QAAQ;AAC5B,cAAM,OAAO,UAAU;AAAA,UAAI,CAACA,QAAO,MAClCA,UAAS,QAAQA,OAAM,SAAS,IAAIA,OAAM,GAAG,EAAE,IAAI,IAAI,SAAS,CAAC;AAAA,QAClE;AACA,gBAAQ,KAAM,KAAK,CAAC,EAAgC,MAAM;AAAA,MAC3D;AAAA,MACA;AAAA,QACC,MAAM;AAAA,QACN,cAAc;AAAA,QACd,MAAM,aAAa,0BAA0B;AAAA,QAC7C,SAAS;AAAA,MACV;AAAA,IACD;AACA,SAAK,IAAI,KAAK,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACxD,SAAK,YAAY,UAAU,KAAK,cAAc,CAAC;AAE/C,UAAM,oBAAoB,KAAK;AAAA,MAC9B;AAAA,MACA,sBAAsB,KAAK,cAAc,sBAAsB,qBAAqB;AAAA,IACrF;AAGA,aAAS,IAAI,GAAG,IAAI,KAAK,YAAY,QAAQ,KAAK,GAAG;AACpD,YAAM,QAAQ,KAAK,YAAY,CAAC;AAChC,YAAM,UAAU,KAAK,MAAM,KAAK;AAChC,YAAM,OACL,IAAI,IAAI,KAAK,YAAY,SAAS,KAAK,MAAM,KAAK,YAAY,IAAI,CAAC,CAAW,IAAI;AACnF,YAAM,SAAS,KAAK,cAAc,IAAI,KAAK;AAG3C,YAAM,eAAe,gBAAgB,IAAI,KAAK,KAAK;AACnD,YAAM,sBAAsB,iBAAiB,IAAI,KAAK;AAYtD,YAAM,kBACL,wBAAwB,SACrB,KAAa,CAAC,GAAG,EAAE,MAAM,gBAAgB,KAAK,IAAI,SAAS,EAAE,CAAC,IAC9D;AACJ,UAAI,iBAAiB;AACpB,aAAK,IAAI,iBAAiB,EAAE,MAAM,gBAAgB,KAAK,GAAG,CAAC;AAAA,MAC5D;AAIA,YAAM,aAAa,SAAS;AAE5B,UAAI,QAAQ;AAoBX,cAAM,WACL,mBAAmB,OAAO,CAAC,QAAQ,SAAS,eAAe,IAAI,CAAC,QAAQ,OAAO;AAChF,cAAM,OAAO;AAAA,UACZ;AAAA,UACA,CAAC,OAAO,UAAU,QAAQ;AACzB,gBAAI,EAAE,cAAc,IAAI,QAAQ;AAC/B,kBAAI,MAAM,WAAW;AAAA,gBACpB,SAAS,oBAAI,IAAmB;AAAA,gBAChC,YAAY;AAAA,cACb;AAAA,YACD;AACA,kBAAM,gBAAgB,IAAI,MAAM;AAChC,kBAAM,WAAW,cAAc;AAC/B,gBAAI,YAAY;AAChB,mBAAO,YAAY,cAAc;AAKhC,kBAAI,wBAAwB,UAAa,SAAS,QAAQ,qBAAqB;AAC9E;AAAA,cACD;AACA,oBAAM,SAAS,QAAQ,MAAM,CAAC;AAC9B,kBAAI,OAAO,WAAW,EAAG;AACzB,oBAAM,MAAM,OAAO,CAAC;AACpB,kBAAI,CAAC,IAAK;AAGV,oBAAM,WAAY,IAAI,SAAS,iBAAmD,CAAC;AACnF,oBAAM,UAAU,CAAC,GAAG,UAAU,KAAK;AAEnC,oBAAM,KAAK,IAAI,gBAAgB;AAC/B,oBAAM,QAAuB,EAAE,OAAO,MAAM,QAAW,GAAG;AAC1D,uBAAS,IAAI,KAAK;AAClB,+BAAiB,KAAK,SAAS,IAAI;AAEnC,kBAAI;AACJ,kBAAI;AACH,yBAAS,OAAO,KAAK,EAAE,QAAQ,GAAG,OAAO,CAAC;AAAA,cAC3C,SAAS,KAAK;AAIb,yBAAS,OAAO,KAAK;AACrB,iCAAiB,KAAK,SAAS,IAAI;AACnC,wBAAQ,KAAK,IAAI,IAAI,EAAE,SAAS,OAAO,OAAO,IAAI,CAAC;AACnD,6BAAa;AACb;AAAA,cACD;AAGA,oBAAM,aAAa,QAAW,MAAM;AAKpC,kBAAI,UAAU;AACd,kBAAI;AACJ,oBAAM,aAAa,MAAY;AAC9B,oBAAI,OAAO;AACV,wBAAM;AAAA,gBACP,OAAO;AACN,0BAAQ,QAAQ,EAAE,KAAK,MAAM,QAAQ,CAAC;AAAA,gBACvC;AACA,yBAAS,OAAO,KAAK;AAMrB,oBAAI,CAAC,cAAc,YAAY;AAC9B,mCAAiB,KAAK,SAAS,IAAI;AAAA,gBACpC;AAAA,cACD;AACA,sBAAQ,WAAW,UAAU,CAAC,SAAS;AACtC,oBAAI,QAAS;AACb,2BAAW,KAAK,MAAM;AACrB,sBAAI,EAAE,CAAC,MAAM,MAAM;AAClB,8BAAU;AACV,+BAAW;AACX,0BAAM,aAAa,EAAE,CAAC;AACtB,0BAAM,cAAc;AAAA,sBACnB,GAAG,IAAI;AAAA,sBACP,eAAe;AAAA,oBAChB;AACA,wBAAI,YAAY;AACf,4BAAM,eAA+B;AAAA,wBACpC,GAAG;AAAA,wBACH,SAAS;AAAA,wBACT,UAAU,OAAO,OAAO,WAAW;AAAA,sBACpC;AACA,4BAAM,MAAM;AACX,gCAAQ,IAAI,IAAI,EAAE;AAClB,6BAAK,WAAW,OAAO,YAAY;AAAA,sBACpC,CAAC;AAAA,oBACF,OAAO;AACN,4BAAM,MAAM;AACX,gCAAQ,IAAI,IAAI,EAAE;AAClB,wBAAC,KAA0B,QAAQ,YAAY;AAAA,0BAC9C,UAAU;AAAA,wBACX,CAAC;AAAA,sBACF,CAAC;AAAA,oBACF;AACA;AAAA,kBACD,WAAW,EAAE,CAAC,MAAM,OAAO;AAC1B,8BAAU;AACV,+BAAW;AAIX,4BAAQ,KAAK,IAAI,IAAI,EAAE,SAAS,OAAO,OAAO,EAAE,CAAC,EAAE,CAAC;AACpD;AAAA,kBACD;AAAA,gBACD;AAAA,cACD,CAAC;AACD,oBAAM,QAAQ,MAAM,QAAQ;AAE5B,2BAAa;AAAA,YACd;AACA,mBAAO;AAAA,cACN,gBAAgB,MAAM;AAKrB,8BAAc,aAAa;AAC3B,2BAAW,KAAK,UAAU;AACzB,sBAAI;AACH,sBAAE,GAAG,MAAM;AAAA,kBACZ,QAAQ;AAAA,kBAER;AACA,sBAAI;AACH,sBAAE,MAAM;AAAA,kBACT,QAAQ;AAAA,kBAER;AAAA,gBACD;AACA,yBAAS,MAAM;AAOf,uBAAO,IAAI,MAAM;AAAA,cAClB;AAAA,YACD;AAAA,UACD;AAAA,UACA;AAAA,YACC,MAAM,aAAa,iBAAiB,EAAE,OAAO,UAAU,KAAK,CAAC;AAAA,UAC9D;AAAA,QACD;AACA,aAAK,YAAY,UAAU,IAAI,CAAC;AAAA,MACjC,OAAO;AAGN,cAAM,OAAO,KAAK;AAAA,UACjB,QAAQ,KAAK;AAAA,UACb,CAAC,GAAG,KAAK,WAAW;AAAA,UACpB,MAAM;AACL,gBAAI,QAAQ;AACZ,mBAAO,QAAQ,cAAc;AAC5B,oBAAM,QAAQ,QAAQ,MAAM,CAAC;AAC7B,kBAAI,MAAM,WAAW,EAAG;AACxB,oBAAM,MAAM,MAAM,CAAC;AACnB,kBAAI,CAAC,IAAK;AAEV,oBAAM,WAAY,IAAI,SAAS,iBAAmD,CAAC;AACnF,oBAAM,UAAU,CAAC,GAAG,UAAU,KAAK;AACnC,oBAAM,cAAc,EAAE,GAAG,IAAI,UAAU,eAAe,QAAQ;AAE9D,kBAAI,YAAY;AACf,sBAAM,eAA+B;AAAA,kBACpC,GAAG;AAAA,kBACH,UAAU,OAAO,OAAO,WAAW;AAAA,gBACpC;AACA,sBAAM,MAAM;AACX,0BAAQ,IAAI,IAAI,EAAE;AAClB,uBAAK,WAAW,OAAO,YAAY;AAAA,gBACpC,CAAC;AAAA,cACF,OAAO;AACN,sBAAM,MAAM;AACX,0BAAQ,IAAI,IAAI,EAAE;AAClB,kBAAC,KAA0B,QAAQ,IAAI,SAAS;AAAA,oBAC/C,UAAU;AAAA,kBACX,CAAC;AAAA,gBACF,CAAC;AAAA,cACF;AACA,uBAAS;AAAA,YACV;AAAA,UACD;AAAA,UACA;AAAA,YACC,MAAM,aAAa,iBAAiB,EAAE,OAAO,UAAU,MAAM,CAAC;AAAA,UAC/D;AAAA,QACD;AACA,aAAK,YAAY,UAAU,IAAI,CAAC;AAAA,MACjC;AAAA,IACD;AAAA,EACD;AAAA,EAEA,SAA4B;AAC3B,WAAO,KAAK;AAAA,EACb;AAAA,EAEA,MAAM,OAAiC;AACtC,UAAM,IAAI,KAAK,QAAQ,IAAI,KAAK;AAChC,QAAI,CAAC,EAAG,OAAM,IAAI,MAAM,YAAY,KAAK,IAAI,sBAAsB,KAAK,GAAG;AAC3E,WAAO;AAAA,EACR;AAAA,EAEA,QAAQ,SAAY,OAA4D,CAAC,GAAW;AAC3F,WAAO,KAAK,MAAM,KAAK,YAAY,CAAC,CAAW,EAAE,QAAQ,SAAS,IAAI;AAAA,EACvE;AAAA,EAEA,oBAA+C;AAC9C,WAAO,KAAK,UAAU;AAAA,EACvB;AACD;AAKO,SAAS,SAAY,MAAc,MAA0C;AACnF,SAAO,IAAI,cAAiB,MAAM,IAAI;AACvC;AAKO,SAAS,QAAW,MAAc,MAA2C;AACnF,QAAM,IAAI,IAAI,aAAgB,MAAM,IAAI;AAKxC,QAAM,EAAE,SAAS,IAAI,aAAa,KAAK,GAAG,QAAQ,IAAK,QAAQ,CAAC;AAChE,IAAE,WAAW,WAAW,gBAAgB,OAAO,CAAC;AAChD,SAAO;AACR;","names":["batch"]}
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  feedback
3
- } from "./chunk-QFE5BQH7.js";
3
+ } from "./chunk-LTSI7ULC.js";
4
4
 
5
5
  // src/utils/graphspec/index.ts
6
6
  import { node } from "@graphrefly/pure-ts/core";
@@ -1062,4 +1062,4 @@ export {
1062
1062
  llmCompose,
1063
1063
  llmRefine
1064
1064
  };
1065
- //# sourceMappingURL=chunk-6MRSX3YK.js.map
1065
+ //# sourceMappingURL=chunk-B5Y5GPD5.js.map
@@ -35,6 +35,26 @@ function createAuditLog(opts) {
35
35
  }
36
36
  return log;
37
37
  }
38
+ function readonlyAuditLog(log) {
39
+ return Object.freeze({
40
+ get entries() {
41
+ return log.entries;
42
+ },
43
+ get size() {
44
+ return log.size;
45
+ },
46
+ get lastValue() {
47
+ return log.lastValue;
48
+ },
49
+ get mutationLog() {
50
+ return log.mutationLog;
51
+ },
52
+ at: log.at.bind(log),
53
+ withLatest: log.withLatest.bind(log),
54
+ view: log.view.bind(log),
55
+ scan: log.scan.bind(log)
56
+ });
57
+ }
38
58
  function deepFreeze(value) {
39
59
  if (value === null || typeof value !== "object" || Object.isFrozen(value)) return value;
40
60
  for (const k of Object.keys(value)) {
@@ -180,10 +200,11 @@ export {
180
200
  tryIncrementBounded,
181
201
  DEFAULT_AUDIT_GUARD,
182
202
  createAuditLog,
203
+ readonlyAuditLog,
183
204
  mutate,
184
205
  bumpCursor,
185
206
  appendAudit,
186
207
  registerCursor,
187
208
  registerCursorMap
188
209
  };
189
- //# sourceMappingURL=chunk-BXGZFGZ4.js.map
210
+ //# sourceMappingURL=chunk-C5QD5DQX.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/base/mutation/index.ts"],"sourcesContent":["/**\n * Universal mutation framework (Phase 14 — DS-14 locked 2026-05-05).\n *\n * Single `mutate(act, opts)` factory replaces the prior `lightMutation` +\n * `wrapMutation` two-tier split (pre-1.0 break per Q-O2).\n *\n * Two frames:\n * - `\"inline\"` — no batch; up() runs raw. Seq bumps before action; persists\n * on throw. Hot-path-friendly for atomic single-write mutations.\n * - `\"transactional\"` — opens `batch(() => up(...))`. On throw: batch discards\n * deferred deliveries, then `down()` runs (if provided), then failure record.\n *\n * Phase-4 primitives share the same shape: imperative mutation methods +\n * closure state + reactive audit log + freeze-at-entry + rollback-on-throw.\n * This module factors out the common machinery so each primitive becomes\n * declarative wiring over typed audit records.\n */\n\nimport {\n\tbatch,\n\tDATA,\n\tDIRTY,\n\ttype Node,\n\ttype NodeGuard,\n\tnode,\n\tpolicy,\n\twallClockNs,\n} from \"@graphrefly/pure-ts/core\";\nimport {\n\ttype ReactiveLogBundle,\n\ttype ReactiveLogOptions,\n\treactiveLog,\n} from \"@graphrefly/pure-ts/extra\";\nimport { Graph } from \"@graphrefly/pure-ts/graph\";\n\n// ── tryIncrementBounded ──────────────────────────────────────────────────\n\n/**\n * Bounded increment for a self-owned counter state node.\n *\n * Reads `counter.cache`, bumps by `by` (default 1) if `cur + by <= cap`,\n * writes back. Returns `false` when the cap would be exceeded (no-op write).\n * Documented P3 exception: the counter is not a declared dep of the caller —\n * it's a private budget read+written from a single call site. This helper\n * keeps the `.cache` access in one named place so caller bodies (which may\n * be inside reactive fn execution paths) stay free of cross-node `.cache`\n * reads.\n *\n * **Safety today:**\n * 1. Single-threaded JS runner never invokes the caller concurrently.\n * 2. `counter.down` writes the cache synchronously before returning, so\n * synchronous re-entry through a downstream publish reads the\n * freshly-incremented value — no double-count.\n *\n * **Future risk:** under a free-threaded runner (PY no-GIL or hypothetical\n * concurrent TS runner), two concurrent firings could still race. Revisit\n * when that surfaces.\n *\n * @param counter - Self-owned counter Node. Caller is the sole writer.\n * @param cap - Upper bound (inclusive). Pass `Number.MAX_SAFE_INTEGER` for\n * \"effectively unbounded\" use cases (e.g. token meters).\n * @param by - Delta to add (default `1`). Must be a finite non-negative\n * number; callers should pre-validate. Overflow-safe via\n * `by > cap - cur` check rather than `cur + by >= cap`.\n */\nexport function tryIncrementBounded(counter: Node<number>, cap: number, by = 1): boolean {\n\tconst cur = (counter.cache as number | undefined) ?? 0;\n\tif (by > cap - cur) return false;\n\tcounter.down([[DIRTY], [DATA, cur + by]]);\n\treturn true;\n}\n\n// ── Audit record schema ──────────────────────────────────────────────────\n\n/** Shared base shape for every audit record. Per-primitive types extend this. */\nexport interface BaseAuditRecord {\n\treadonly t_ns: number;\n\treadonly seq?: number;\n\treadonly handlerVersion?: { id: string; version: string | number };\n}\n\n// ── Default audit guard ──────────────────────────────────────────────────\n\n/**\n * Allow `observe` and `signal`; deny external `write` on the audit log so\n * consumers can subscribe + signal-bridge but cannot inject fake records.\n */\nexport const DEFAULT_AUDIT_GUARD: NodeGuard = policy((allow, deny) => {\n\tallow(\"observe\");\n\tallow(\"signal\");\n\tdeny(\"write\");\n});\n\n// ── createAuditLog ───────────────────────────────────────────────────────\n\nexport type AuditLogOpts<R extends BaseAuditRecord> = {\n\tname: string;\n\t/** Bounded retention; default 1024 per Audit 2 / cross-cutting bounded-default policy. */\n\tretainedLimit?: number;\n\t/** Override the default audit guard. */\n\tguard?: NodeGuard;\n\t/** Mount the audit `entries` Node under this graph (and activate withLatest). */\n\tgraph?: Graph;\n\t/** Pass-through to {@link reactiveLog}. */\n\tversioning?: ReactiveLogOptions<R>[\"versioning\"];\n};\n\n/**\n * Build a reactive audit log with sane defaults: bounded retention, deny-write\n * guard, `withLatest()` companions activated. Returns the {@link ReactiveLogBundle}\n * directly — primitives expose this as `<primitive>.events` / `.decisions` /\n * `.dispatches` / `.invocations` and alias it as `.audit`.\n *\n * @category internal\n */\nexport function createAuditLog<R extends BaseAuditRecord>(\n\topts: AuditLogOpts<R>,\n): ReactiveLogBundle<R> {\n\tconst log = reactiveLog<R>([], {\n\t\tname: opts.name,\n\t\tmaxSize: opts.retainedLimit ?? 1024,\n\t\tguard: opts.guard ?? DEFAULT_AUDIT_GUARD,\n\t\t...(opts.versioning != null ? { versioning: opts.versioning } : {}),\n\t});\n\t// Lazy companion activation up-front so `bundle.lastValue` / `hasLatest`\n\t// are queryable without an explicit `withLatest()` call.\n\tlog.withLatest();\n\tif (opts.graph) {\n\t\topts.graph.add(log.entries, { name: opts.name });\n\t}\n\treturn log;\n}\n\n/**\n * Read-only projection of a {@link ReactiveLogBundle}. Exposes only the\n * observation surface (`entries` / `size` / `at` / `withLatest` / `lastValue`\n * / `view` / `scan` / `mutationLog`) — the mutators (`append` / `appendMany`\n * / `clear` / `trimHead` / `attach` / `attachStorage`) and the lifecycle\n * disposers are intentionally absent.\n */\nexport type ReadonlyAuditLog<R> = Pick<\n\tReactiveLogBundle<R>,\n\t\"entries\" | \"size\" | \"at\" | \"withLatest\" | \"lastValue\" | \"view\" | \"scan\" | \"mutationLog\"\n>;\n\n/**\n * Wrap an audit log so the `.audit` alias a primitive exposes (the Audit-2\n * `.audit` duplication convention — `saga.audit` / `cqrs.audit` /\n * `jobQueue.audit` / `processManager.audit`) is a **stable read-only view**,\n * not the live mutable bundle. Closes M7: a consumer calling\n * `someGraph.audit.append(...)` would otherwise silently mutate the owning\n * primitive's canonical log. The returned object is frozen and shares the\n * underlying log's nodes (zero copy) — reads are byte-identical to the live\n * bundle; mutators are simply not present (compile-time) and the object is\n * frozen (run-time defense for JS callers).\n *\n * @category internal\n */\nexport function readonlyAuditLog<R>(log: ReactiveLogBundle<R>): ReadonlyAuditLog<R> {\n\treturn Object.freeze({\n\t\tget entries() {\n\t\t\treturn log.entries;\n\t\t},\n\t\tget size() {\n\t\t\treturn log.size;\n\t\t},\n\t\tget lastValue() {\n\t\t\treturn log.lastValue;\n\t\t},\n\t\tget mutationLog() {\n\t\t\treturn log.mutationLog;\n\t\t},\n\t\tat: log.at.bind(log),\n\t\twithLatest: log.withLatest.bind(log),\n\t\tview: log.view.bind(log),\n\t\tscan: log.scan.bind(log),\n\t}) satisfies ReadonlyAuditLog<R>;\n}\n\n// ── Universal mutation factory (Phase 14 — DS-14 lock Q-O2/Q-O3) ────────\n//\n// Single `mutate(act, opts)` factory. Two frames:\n//\n// - `\"inline\"` — no batch frame; up() runs raw. Seq bumps before action;\n// persists on throw. Hot-path-friendly for atomic single-write mutations.\n//\n// - `\"transactional\"` — opens `batch(() => up(...))`. On throw: batch discards\n// deferred deliveries, then `down()` runs, then failure record persists.\n//\n// **Heuristic:** if your imperative method's body is one or two lines (mutate\n// state, emit), use `frame: \"inline\"`. If it runs a user-supplied handler or\n// has multiple steps that could leave inconsistent state mid-throw, use\n// `frame: \"transactional\"`.\n\nexport type FailureMeta = {\n\tt_ns: number;\n\tseq?: number;\n\terrorType: string;\n};\n\nexport type SuccessMeta = {\n\tt_ns: number;\n\tseq?: number;\n};\n\n/**\n * Mutation action shape. Plain function shorthand auto-wraps as `{ up: fn }`.\n *\n * - `up` — the mutation action (the \"up migration\").\n * - `down` — optional rollback for closure mutations that `batch()` can't\n * reach. Receives the SAME frozen args as `up`. Runs AFTER batch reactive\n * rollback, BEFORE the failure record. Throws inside `down` are\n * console.error'd without masking the original error. Only meaningful\n * with `frame: \"transactional\"`.\n */\nexport type MutationAct<TArgs extends readonly unknown[], TResult> = {\n\tup: (...args: TArgs) => TResult;\n\tdown?: (...args: TArgs) => void;\n};\n\nexport type MutationFrame = \"inline\" | \"transactional\";\n\nexport type MutateOpts<TArgs extends readonly unknown[], TResult, R extends BaseAuditRecord> = {\n\t/** Frame mode. `\"inline\"` = no batch; `\"transactional\"` = batch + rollback. */\n\tframe: MutationFrame;\n\t/**\n\t * Optional log to append records to. When omitted, the wrapper still\n\t * provides freeze / seq-advance / rollback-on-throw but skips record\n\t * emission — useful for primitives that want centralized mutation\n\t * semantics without a dedicated log surface (e.g. `Topic.publish`).\n\t */\n\tlog?: ReactiveLogBundle<R>;\n\t/** Build the success record from the action's args + result + meta. */\n\tonSuccessRecord?: (args: TArgs, result: TResult, meta: SuccessMeta) => R | undefined;\n\t/** Build the failure record from the args + error + meta. */\n\tonFailureRecord?: (args: TArgs, error: unknown, meta: FailureMeta) => R | undefined;\n\t/** Deep-freeze args at entry (default `true`). Opt out for hot paths. */\n\tfreeze?: boolean;\n\t/** Optional sequence cursor — auto-advanced and stamped onto records. */\n\tseq?: Node<number>;\n\t/** Optional handler version — stamped per Audit 5. */\n\thandlerVersion?: { id: string; version: string | number };\n};\n\nfunction deepFreeze<T>(value: T): T {\n\tif (value === null || typeof value !== \"object\" || Object.isFrozen(value)) return value;\n\tfor (const k of Object.keys(value as Record<string, unknown>)) {\n\t\tdeepFreeze((value as Record<string, unknown>)[k]);\n\t}\n\treturn Object.freeze(value);\n}\n\n/**\n * Universal mutation factory (Phase 14 — DS-14 Q-O2).\n *\n * Replaces the prior `lightMutation` + `wrapMutation` two-tier split.\n * Single factory with `frame: \"inline\" | \"transactional\"` discriminant.\n *\n * @param act - The mutation action. Either a plain function (auto-wrapped as\n * `{ up: fn }`) or a `{ up, down? }` object for explicit rollback.\n * @param opts - Configuration: frame, log, record builders, freeze, seq.\n * @returns A typed wrapper function with the same signature as `act.up`.\n */\nexport function mutate<TArgs extends readonly unknown[], TResult, R extends BaseAuditRecord>(\n\tact: MutationAct<TArgs, TResult> | ((...args: TArgs) => TResult),\n\topts: MutateOpts<TArgs, TResult, R>,\n): (...args: TArgs) => TResult {\n\tconst { up, down } = typeof act === \"function\" ? { up: act, down: undefined } : act;\n\tconst freeze = opts.freeze ?? true;\n\n\tif (opts.frame === \"inline\") {\n\t\treturn function wrapped(...args: TArgs): TResult {\n\t\t\tconst sealed = freeze ? (args.map(deepFreeze) as unknown as TArgs) : args;\n\t\t\tconst t_ns = wallClockNs();\n\t\t\tconst seq = opts.seq ? bumpCursor(opts.seq) : undefined;\n\t\t\ttry {\n\t\t\t\tconst result = up(...sealed);\n\t\t\t\tif (opts.log && opts.onSuccessRecord) {\n\t\t\t\t\tappendAudit<TArgs, TResult, R, SuccessMeta>(\n\t\t\t\t\t\topts.log,\n\t\t\t\t\t\topts.onSuccessRecord,\n\t\t\t\t\t\tsealed,\n\t\t\t\t\t\tresult,\n\t\t\t\t\t\t{ t_ns, seq },\n\t\t\t\t\t\topts.handlerVersion,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\treturn result;\n\t\t\t} catch (err) {\n\t\t\t\tif (opts.log && opts.onFailureRecord) {\n\t\t\t\t\tconst errorType = err instanceof Error ? err.name : typeof err;\n\t\t\t\t\tappendAudit<TArgs, unknown, R, FailureMeta>(\n\t\t\t\t\t\topts.log,\n\t\t\t\t\t\topts.onFailureRecord,\n\t\t\t\t\t\tsealed,\n\t\t\t\t\t\terr,\n\t\t\t\t\t\t{ t_ns, seq, errorType },\n\t\t\t\t\t\topts.handlerVersion,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tthrow err;\n\t\t\t}\n\t\t};\n\t}\n\n\t// frame === \"transactional\"\n\treturn function wrapped(...args: TArgs): TResult {\n\t\tconst sealed = freeze ? (args.map(deepFreeze) as unknown as TArgs) : args;\n\t\tconst t_ns = wallClockNs();\n\t\tlet result: TResult;\n\t\tlet captured: unknown;\n\t\tlet captureSet = false;\n\t\tlet seq: number | undefined;\n\t\ttry {\n\t\t\tbatch(() => {\n\t\t\t\tif (opts.seq) seq = bumpCursor(opts.seq);\n\t\t\t\ttry {\n\t\t\t\t\tresult = up(...sealed);\n\t\t\t\t\tif (opts.log && opts.onSuccessRecord) {\n\t\t\t\t\t\tappendAudit<TArgs, TResult, R, SuccessMeta>(\n\t\t\t\t\t\t\topts.log,\n\t\t\t\t\t\t\topts.onSuccessRecord,\n\t\t\t\t\t\t\tsealed,\n\t\t\t\t\t\t\tresult,\n\t\t\t\t\t\t\t{ t_ns, seq },\n\t\t\t\t\t\t\topts.handlerVersion,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t} catch (err) {\n\t\t\t\t\tcaptured = err;\n\t\t\t\t\tcaptureSet = true;\n\t\t\t\t\tthrow err;\n\t\t\t\t}\n\t\t\t});\n\t\t} catch (outerErr) {\n\t\t\t// Fire `down` AFTER batch's reactive rollback, BEFORE failure record.\n\t\t\t// Gate on `captureSet` — if the throw came from outside the inner try\n\t\t\t// (framework-level batch error before action ran), don't fire down.\n\t\t\tif (captureSet && down) {\n\t\t\t\ttry {\n\t\t\t\t\tdown(...sealed);\n\t\t\t\t} catch (downErr) {\n\t\t\t\t\tconsole.error(\n\t\t\t\t\t\t`mutate: down hook threw — original action error preserved (${\n\t\t\t\t\t\t\tcaptured instanceof Error ? captured.name : typeof captured\n\t\t\t\t\t\t}). Down error:`,\n\t\t\t\t\t\tdownErr,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (captureSet && opts.log && opts.onFailureRecord) {\n\t\t\t\tconst errorType = captured instanceof Error ? captured.name : typeof captured;\n\t\t\t\tappendAudit<TArgs, unknown, R, FailureMeta>(\n\t\t\t\t\topts.log,\n\t\t\t\t\topts.onFailureRecord,\n\t\t\t\t\tsealed,\n\t\t\t\t\tcaptured,\n\t\t\t\t\t{ t_ns, seq, errorType },\n\t\t\t\t\topts.handlerVersion,\n\t\t\t\t);\n\t\t\t}\n\t\t\tthrow captureSet ? captured : outerErr;\n\t\t}\n\t\treturn result!;\n\t};\n}\n\n/**\n * Advance a cursor node and return the new value. Emits `[DIRTY], [DATA, next]`\n * directly on the cursor — atomic outside a batch, rollback-discardable inside.\n *\n * Resets to `0` if the cursor cache is missing, non-numeric, `NaN`, or\n * non-finite (e.g. corrupted by `restore()` from a malformed snapshot, or\n * by a misbehaving codec). `??` alone would let `NaN` and `\"\"` pass through\n * and silently corrupt audit ordering downstream.\n *\n * **Silent reset diagnostic (EH-12).** When the cache holds a non-numeric\n * value at bump time, the cursor restarts at 0 and the next bump returns 1\n * — colliding with the seq stamped on the very first record after construct.\n * To make seq-monotonicity violations after a restore visible to operators,\n * the helper emits a one-shot `console.warn` per cursor instance describing\n * the offending value. The cursor is identified by a `WeakSet<Node<number>>`\n * so the warning fires exactly once per node — repeat malformed bumps stay\n * quiet to avoid log spam. Production callers wanting to suppress can swap\n * the global `console` (universal-safe code path; no Node-only API used).\n *\n * Works whether or not the cursor has any subscribers — `down` updates the\n * cache regardless, so primitives that bump before consumers attach (e.g.\n * `JobQueueGraph.enqueue`) still see a coherent sequence.\n *\n * @category internal\n */\nconst _bumpCursorWarned = new WeakSet<Node<number>>();\nexport function bumpCursor(seq: Node<number>): number {\n\tconst raw = seq.cache;\n\tconst valid = typeof raw === \"number\" && Number.isFinite(raw);\n\tif (!valid && raw !== undefined && !_bumpCursorWarned.has(seq)) {\n\t\t_bumpCursorWarned.add(seq);\n\t\tconsole.warn(\n\t\t\t`bumpCursor: cursor cache held a non-numeric value (${String(raw)}); resetting to 0. ` +\n\t\t\t\t\"Causes include: a snapshot codec round-tripping the cursor as a string / null / NaN, \" +\n\t\t\t\t\"OR a malformed initial seed (e.g. state<number>(NaN)). \" +\n\t\t\t\t\"Audit consumers may see colliding seq values after this point.\",\n\t\t);\n\t}\n\tconst cur = valid ? raw : 0;\n\tconst next = cur + 1;\n\tseq.down([[DIRTY], [DATA, next]]);\n\treturn next;\n}\n\n/**\n * Build a record via the supplied builder, stamp `handlerVersion` if present,\n * and append it to the audit log. `undefined` records are skipped (callers\n * pass an `onSuccess` / `onFailure` that returns `undefined` to opt out per\n * call).\n *\n * @category internal\n */\nexport function appendAudit<\n\tTArgs extends readonly unknown[],\n\tTValue,\n\tR extends BaseAuditRecord,\n\tM extends SuccessMeta | FailureMeta,\n>(\n\taudit: ReactiveLogBundle<R>,\n\tbuilder: (args: TArgs, value: TValue, meta: M) => R | undefined,\n\targs: TArgs,\n\tvalue: TValue,\n\tmeta: M,\n\thandlerVersion?: { id: string; version: string | number },\n): void {\n\tconst record = builder(args, value, meta);\n\tif (record === undefined) return;\n\tconst stamped = handlerVersion != null ? ({ ...record, handlerVersion } as R) : record;\n\taudit.append(stamped);\n}\n\n// ── registerCursor / registerCursorMap ───────────────────────────────────\n\n/**\n * Promote a closure counter to a state node mounted under `graph`.\n * Replaces ad-hoc `let _seq = 0` patterns with a node observable in\n * `describe()` and persistable via storage tiers.\n *\n * @category internal\n */\nexport function registerCursor(graph: Graph, name: string, initial = 0): Node<number> {\n\tconst cursor = node<number>([], { initial, name, describeKind: \"state\" });\n\tgraph.add(cursor, { name });\n\treturn cursor;\n}\n\n/**\n * Promote a closure `Map<K, number>` to N state nodes (one per key) mounted\n * under `<graph>::<name>::<key>`. Used by saga (per-event-type cursor).\n *\n * @category internal\n */\nexport function registerCursorMap<K extends string>(\n\tgraph: Graph,\n\tname: string,\n\tkeys: readonly K[],\n\tinitial = 0,\n): { readonly [P in K]: Node<number> } {\n\tconst out = {} as { [P in K]: Node<number> };\n\t// Mount cursors under a child plain-Graph so per-key node names stay flat\n\t// (path-separator `::` is reserved by Graph.add). Using `Graph` directly\n\t// rather than `graph.constructor` avoids spawning a typed subclass with\n\t// an incompatible constructor signature (e.g., CqrsGraph(name, opts)).\n\tconst sub = new Graph(name);\n\tfor (const k of keys) {\n\t\tconst cursor = node<number>([], {\n\t\t\tinitial,\n\t\t\tname: k,\n\t\t\tdescribeKind: \"state\",\n\t\t});\n\t\tsub.add(cursor, { name: k });\n\t\tout[k] = cursor;\n\t}\n\tgraph.mount(name, sub);\n\treturn out;\n}\n"],"mappings":"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","names":[]}
@@ -3,8 +3,11 @@ import {
3
3
  } from "./chunk-FMPF42Q4.js";
4
4
 
5
5
  // src/utils/inspect/lens.ts
6
- import { node } from "@graphrefly/pure-ts/core";
7
- import { keepalive } from "@graphrefly/pure-ts/extra";
6
+ import { node, wallClockNs } from "@graphrefly/pure-ts/core";
7
+ import {
8
+ keepalive,
9
+ reactiveLog
10
+ } from "@graphrefly/pure-ts/extra";
8
11
  import {
9
12
  reachable
10
13
  } from "@graphrefly/pure-ts/graph";
@@ -39,7 +42,11 @@ function healthReportEqual(a, b) {
39
42
  }
40
43
  return true;
41
44
  }
42
- function graphLens(target) {
45
+ function graphLens(target, opts = {}) {
46
+ const mutLogOpt = opts.mutations;
47
+ const mutationsEnabled = mutLogOpt != null;
48
+ const pendingFlowChanges = [];
49
+ let disposed = false;
43
50
  const topologyHandle = target.describe({
44
51
  reactive: true,
45
52
  detail: "standard",
@@ -82,17 +89,26 @@ function graphLens(target) {
82
89
  const path = event.path;
83
90
  if (path == null || path === "") continue;
84
91
  const prior = flowMap.get(path);
92
+ const count = (prior?.count ?? 0) + 1;
85
93
  flowMap.set(path, {
86
94
  path,
87
- count: (prior?.count ?? 0) + 1,
95
+ count,
88
96
  lastUpdate_ns: c.flushedAt_ns
89
97
  });
98
+ if (mutationsEnabled && !disposed) {
99
+ pendingFlowChanges.push({ kind: "tick", path, count });
100
+ }
90
101
  }
91
102
  }
92
103
  if (desc != null && flowMap.size > 0) {
93
104
  const valid = new Set(Object.keys(desc.nodes));
94
105
  for (const k of [...flowMap.keys()]) {
95
- if (!valid.has(k)) flowMap.delete(k);
106
+ if (!valid.has(k)) {
107
+ flowMap.delete(k);
108
+ if (mutationsEnabled && !disposed) {
109
+ pendingFlowChanges.push({ kind: "evict", path: k });
110
+ }
111
+ }
96
112
  }
97
113
  }
98
114
  actions.emit(new Map(flowMap));
@@ -103,15 +119,50 @@ function graphLens(target) {
103
119
  meta: domainMeta("lens", "flow")
104
120
  }
105
121
  );
122
+ let flowMutations;
123
+ let stopFlowMutKeep;
124
+ if (mutLogOpt != null) {
125
+ const log = reactiveLog(void 0, {
126
+ name: mutLogOpt === true ? "graphLens.flowMutations" : mutLogOpt.name ?? "graphLens.flowMutations",
127
+ maxSize: mutLogOpt === true ? void 0 : mutLogOpt.maxSize
128
+ });
129
+ flowMutations = log;
130
+ let mutVersion = 0;
131
+ const flowMutationsDrain = node(
132
+ [flow],
133
+ (_batchData, _actions, _ctx) => {
134
+ if (pendingFlowChanges.length === 0) return;
135
+ const drained = pendingFlowChanges.splice(0);
136
+ for (const change of drained) {
137
+ log.append({
138
+ structure: "lensFlow",
139
+ version: ++mutVersion,
140
+ t_ns: wallClockNs(),
141
+ lifecycle: "data",
142
+ change
143
+ });
144
+ }
145
+ },
146
+ {
147
+ name: "graphLens.flowMutationsDrain",
148
+ describeKind: "effect",
149
+ meta: domainMeta("lens", "flowMutations")
150
+ }
151
+ );
152
+ stopFlowMutKeep = keepalive(flowMutationsDrain);
153
+ }
106
154
  const stopFlowKeep = keepalive(flow);
107
- let disposed = false;
108
155
  return {
109
156
  topology,
110
157
  health,
111
158
  flow,
159
+ ...flowMutations ? { flowMutations } : {},
112
160
  dispose() {
113
161
  if (disposed) return;
114
162
  disposed = true;
163
+ stopFlowMutKeep?.();
164
+ flowMutations?.dispose();
165
+ pendingFlowChanges.length = 0;
115
166
  stopFlowKeep();
116
167
  stopHealthKeep();
117
168
  topologyHandle.dispose();
@@ -125,4 +176,4 @@ export {
125
176
  graphLens,
126
177
  watchTopologyTree
127
178
  };
128
- //# sourceMappingURL=chunk-4XCHZRUJ.js.map
179
+ //# sourceMappingURL=chunk-D5YGR4TP.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/utils/inspect/lens.ts"],"sourcesContent":["/**\n * Reactive graph observability preset (Tier 5.3 reshape — Session A.4 lock).\n *\n * `graphLens(target)` is a thin compositor (~30 LOC of glue) over two\n * already-shipped reactive primitives:\n *\n * - `target.describe({ reactive: true })` — live topology snapshot.\n * - `target.observe({ reactive: true, tiers: [\"data\"] })` — coalesced data\n * changesets per outermost batch flush.\n *\n * It returns three Nodes plus a dispose function:\n *\n * - `topology: Node<GraphDescribeOutput>` — the live describe snapshot,\n * re-emitting on every settle of the target tree (structural change,\n * error/complete/teardown transition).\n * - `health: Node<HealthReport>` — `{ok, problems[]}` aggregated from the\n * live describe; `ok=false` when any node enters `\"errored\"` status,\n * with `upstreamCause` walked backward through deps.\n * - `flow: Node<ReadonlyMap<string, FlowEntry>>` — per-path DATA counters\n * accumulating as `target.observe({reactive: true, tiers: [\"data\"]})`\n * delivers changesets. The map is reconciled against `topology` on each\n * topology change so removed nodes drop their entries.\n *\n * Pre-Tier-5.3 shipped a `LensGraph` class with `stats` / `health` / `flow`\n * (as a `ReactiveMapBundle`) / `why(from, to)` / `flowEntryNode(...)`. The\n * surface collapsed because every concern was already a one-liner over\n * `describe({reactive:true})` + `observe({reactive:true, tiers})` once those\n * landed in Tier 1.5.1 / 1.5.2 — the class added no protocol-level concept,\n * just glue. Callers who need topology stats compose a derived over\n * `topology` directly; callers who need a causal chain call\n * `target.describe({ explain: { from, to }, reactive: true })`.\n *\n * The transitive topology-subscription helper {@link watchTopologyTree} is\n * re-exported here for downstream factories that need full-tree dynamic\n * coverage without taking a dep on `graph/`.\n *\n * @module\n */\nimport { type Node, node, wallClockNs } from \"@graphrefly/pure-ts/core\";\nimport {\n\tkeepalive,\n\ttype LensFlowChange,\n\ttype LensFlowChangePayload,\n\ttype ReactiveLogBundle,\n\treactiveLog,\n} from \"@graphrefly/pure-ts/extra\";\nimport {\n\ttype Graph,\n\ttype GraphDescribeOutput,\n\ttype ObserveChangeset,\n\ttype ObserveEvent,\n\treachable,\n} from \"@graphrefly/pure-ts/graph\";\nimport { domainMeta } from \"../../base/meta/domain-meta.js\";\n\nexport { watchTopologyTree } from \"@graphrefly/pure-ts/graph\";\n\n// ---------------------------------------------------------------------------\n// Payload types\n// ---------------------------------------------------------------------------\n\n/** A single health problem entry. */\nexport interface HealthProblem {\n\tpath: string;\n\t/** V1 only reports `\"errored\"`. Future versions may add `\"completed\"`, `\"disconnected\"`. */\n\tstatus: \"errored\";\n\t/** First errored upstream ancestor along the dep chain, when one exists and is distinct from `path`. */\n\tupstreamCause?: string;\n}\n\n/** Aggregate health snapshot. `ok=true` iff `problems.length === 0`. */\nexport interface HealthReport {\n\tok: boolean;\n\tproblems: readonly HealthProblem[];\n}\n\n/** Per-path flow entry stored in the {@link GraphLensView.flow} map. */\nexport interface FlowEntry {\n\tpath: string;\n\t/** Cumulative DATA emissions observed since the lens activated. */\n\tcount: number;\n\t/** Monotonic ns at the most recent DATA emission for this path, or `null` if none yet. */\n\tlastUpdate_ns: number | null;\n}\n\n/**\n * Options for {@link graphLens}.\n *\n * Shape mirrors `reactiveMap`/`pubsub`'s `mutationLog` option so the\n * delta-companion ergonomics are uniform across structures.\n */\nexport interface GraphLensOptions {\n\t/**\n\t * Enable the {@link GraphLensView.flowMutations} delta companion log.\n\t * `true` for defaults, or `{ maxSize, name }` to bound / name the\n\t * underlying `reactiveLog`. Off by default — zero overhead (no buffer\n\t * accumulation, no companion node) when omitted.\n\t */\n\tmutations?: true | { maxSize?: number; name?: string };\n}\n\n/** Output of {@link graphLens}. */\nexport interface GraphLensView {\n\t/** Live describe snapshot. Re-emits on structural change AND status transitions. */\n\ttopology: Node<GraphDescribeOutput>;\n\t/** Live `{ok, problems[]}`. Aggregated from `topology`; equality-deduped. */\n\thealth: Node<HealthReport>;\n\t/** Per-path DATA counter map. Re-emits per outermost batch flush. */\n\tflow: Node<ReadonlyMap<string, FlowEntry>>;\n\t/**\n\t * Delta companion to {@link flow}. Present iff {@link GraphLensOptions.mutations}\n\t * was configured. Each per-path `tick` (a DATA emission incremented\n\t * the counter) and each `evict` (a path dropped during topology\n\t * reconciliation) appends one typed {@link LensFlowChange} record in\n\t * the same batch frame as the corresponding `flow` snapshot — an\n\t * O(1)-per-event peer of the snapshot, mirroring `reactiveMap`'s\n\t * `mutationLog`. Lets consumers tail *what changed* without diffing\n\t * successive `flow` map snapshots.\n\t */\n\tflowMutations?: ReactiveLogBundle<LensFlowChange>;\n\t/**\n\t * Tear down the underlying `describe({reactive:true})` subscription.\n\t * The `flow` node activates lazily; subscribing-then-unsubscribing reclaims\n\t * its observe stream automatically. Call `dispose()` once when finished.\n\t */\n\tdispose(): void;\n}\n\n// ---------------------------------------------------------------------------\n// Pure helpers (exported for testing / composition)\n// ---------------------------------------------------------------------------\n\n/** Build a `HealthReport` from a fresh `GraphDescribeOutput`. */\nexport function computeHealthReport(described: GraphDescribeOutput): HealthReport {\n\tconst problems: HealthProblem[] = [];\n\tfor (const [path, desc] of Object.entries(described.nodes)) {\n\t\tif (desc.status !== \"errored\") continue;\n\t\tconst entry: HealthProblem = { path, status: \"errored\" };\n\t\t// Walk upstream to find the first errored ancestor (if any) distinct from `path`.\n\t\t// Explicit empty options disambiguates the overload (otherwise both match\n\t\t// and TS picks the `withDetail:true` signature first).\n\t\tconst upstream = reachable(described, path, \"upstream\", {});\n\t\tfor (const p of upstream) {\n\t\t\tif (p === path) continue;\n\t\t\tif (described.nodes[p]?.status === \"errored\") {\n\t\t\t\tentry.upstreamCause = p;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tproblems.push(entry);\n\t}\n\tproblems.sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0));\n\treturn { ok: problems.length === 0, problems };\n}\n\n/** Structural equality for {@link HealthReport} — used as the `health` derived's `equals`. */\nexport function healthReportEqual(a: HealthReport, b: HealthReport): boolean {\n\tif (a.ok !== b.ok) return false;\n\tif (a.problems.length !== b.problems.length) return false;\n\tfor (let i = 0; i < a.problems.length; i++) {\n\t\tconst x = a.problems[i]!;\n\t\tconst y = b.problems[i]!;\n\t\tif (x.path !== y.path) return false;\n\t\tif (x.status !== y.status) return false;\n\t\tif (x.upstreamCause !== y.upstreamCause) return false;\n\t}\n\treturn true;\n}\n\n// ---------------------------------------------------------------------------\n// graphLens preset\n// ---------------------------------------------------------------------------\n\n/**\n * Reactive observability preset over a target {@link Graph}.\n *\n * @param target - The graph to observe.\n *\n * @example\n * ```ts\n * const g = new Graph(\"app\");\n * g.add(state(0, { name: \"counter\" }), { name: \"counter\" });\n *\n * const lens = graphLens(g);\n * lens.topology.subscribe((msgs) => console.log(\"topology:\", msgs));\n * lens.health.subscribe((msgs) => console.log(\"health:\", msgs));\n * lens.flow.subscribe((msgs) => {\n * for (const [type, payload] of msgs) {\n * if (type === DATA) console.log(\"flow map size:\", (payload as ReadonlyMap<string, FlowEntry>).size);\n * }\n * });\n *\n * // Causal chains: use the underlying primitive directly — `graphLens` no\n * // longer wraps it, since `graph.describe({ explain: {...}, reactive: true })`\n * // already provides everything the old `lens.why()` did.\n * const why = g.describe({\n * explain: { from: \"counter\", to: \"consumer\" },\n * reactive: true,\n * });\n *\n * // Tear down when done.\n * lens.dispose();\n * ```\n *\n * @category observability\n */\nexport function graphLens(target: Graph, opts: GraphLensOptions = {}): GraphLensView {\n\tconst mutLogOpt = opts.mutations;\n\tconst mutationsEnabled = mutLogOpt != null;\n\t// Closure buffer: the `flow` derived fills this per wave with the\n\t// tick/evict deltas it just applied; a sibling effect drains it into\n\t// `flowMutations` in the SAME wave (sanctioned side-effect-in-effect\n\t// pattern — mirrors the bridge.ts `topic.publish`-from-effect and\n\t// reactiveMap's pending-changes-flushed-in-the-snapshot-batch shape).\n\t// Single source of truth for the diff stays the `flow` fn.\n\tconst pendingFlowChanges: LensFlowChangePayload[] = [];\n\t// Hoisted (QA-P3) so the `flow` fn's buffer push can short-circuit\n\t// once `dispose()` ran — a still-warm `flow` (external subscriber)\n\t// must not keep growing `pendingFlowChanges` with no drain alive.\n\tlet disposed = false;\n\n\tconst topologyHandle = target.describe({\n\t\treactive: true,\n\t\tdetail: \"standard\",\n\t\treactiveName: \"graphLens.topology\",\n\t});\n\tconst topology = topologyHandle.node;\n\n\tconst health = node<HealthReport>(\n\t\t[topology],\n\t\t(batchData, actions, ctx) => {\n\t\t\tconst data = batchData.map((batch, i) =>\n\t\t\t\tbatch != null && batch.length > 0 ? batch.at(-1) : ctx.prevData[i],\n\t\t\t);\n\t\t\tactions.emit(computeHealthReport(data[0] as GraphDescribeOutput));\n\t\t},\n\t\t{\n\t\t\tname: \"graphLens.health\",\n\t\t\tdescribeKind: \"derived\",\n\t\t\tequals: healthReportEqual,\n\t\t\tmeta: domainMeta(\"lens\", \"health\"),\n\t\t},\n\t);\n\t// `topology` is already kept alive by `_describeReactive`'s internal\n\t// keepalive; `health` needs its own so the derived stays warm even if\n\t// the consumer subscribes lazily.\n\tconst stopHealthKeep = keepalive(health);\n\n\t// `flow` accumulates per-path counters across emissions. Closure-mirror\n\t// (COMPOSITION-GUIDE §28) holds the canonical map; the derived returns a\n\t// fresh ReadonlyMap projection per emit so consumers never observe an\n\t// in-place mutation. Topology drives reconciliation: paths that disappear\n\t// from the describe drop their counter entry.\n\t//\n\t// `lastAppliedFlush_ns` guards against double-applying the same changeset\n\t// when topology re-emits without a new dataFlow event (e.g. a node remove\n\t// re-fires `topology` while `dataFlow.cache` still holds the previous\n\t// changeset by reference). Using the monotonic `flushedAt_ns` cursor (qa\n\t// G2A — EC4 fix) is more robust than ref-comparison: an empty changeset\n\t// re-emitted with a fresh object identity but the same `flushedAt_ns`\n\t// won't trigger a stale-events replay, and a genuinely-new changeset\n\t// always advances the cursor.\n\tconst flowMap = new Map<string, FlowEntry>();\n\tlet lastAppliedFlush_ns = -1;\n\tconst dataFlow = target.observe({ reactive: true, tiers: [\"data\"] });\n\tconst flow = node<ReadonlyMap<string, FlowEntry>>(\n\t\t[dataFlow, topology],\n\t\t(batchData, actions, ctx) => {\n\t\t\tconst data = batchData.map((batch, i) =>\n\t\t\t\tbatch != null && batch.length > 0 ? batch.at(-1) : ctx.prevData[i],\n\t\t\t);\n\t\t\tconst changeset = data[0];\n\t\t\tconst described = data[1];\n\t\t\tconst c = changeset as ObserveChangeset | undefined;\n\t\t\tconst desc = described as GraphDescribeOutput | undefined;\n\n\t\t\t// Apply NEW changeset events first so a freshly-emitted node-remove\n\t\t\t// observed below cleanly drops the entry. (If we reconciled first\n\t\t\t// then applied, an event referencing a path the topology had already\n\t\t\t// dropped would re-create the entry only to be wiped on the next\n\t\t\t// topology re-emit — order events-then-topology to avoid that.)\n\t\t\t//\n\t\t\t// Skip when `events.length === 0`: an empty changeset has no work\n\t\t\t// to do. The cursor advances anyway so a future identical-content\n\t\t\t// changeset (different ref, same flushedAt_ns) is also skipped.\n\t\t\tif (c != null && c.flushedAt_ns > lastAppliedFlush_ns) {\n\t\t\t\tlastAppliedFlush_ns = c.flushedAt_ns;\n\t\t\t\tfor (const event of c.events as readonly ObserveEvent[]) {\n\t\t\t\t\tif (event.type !== \"data\") continue;\n\t\t\t\t\tconst path = event.path;\n\t\t\t\t\tif (path == null || path === \"\") continue;\n\t\t\t\t\tconst prior = flowMap.get(path);\n\t\t\t\t\tconst count = (prior?.count ?? 0) + 1;\n\t\t\t\t\tflowMap.set(path, {\n\t\t\t\t\t\tpath,\n\t\t\t\t\t\tcount,\n\t\t\t\t\t\tlastUpdate_ns: c.flushedAt_ns,\n\t\t\t\t\t});\n\t\t\t\t\tif (mutationsEnabled && !disposed) {\n\t\t\t\t\t\tpendingFlowChanges.push({ kind: \"tick\", path, count });\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Reconcile against current topology — drop entries whose paths\n\t\t\t// no longer exist in the describe.\n\t\t\tif (desc != null && flowMap.size > 0) {\n\t\t\t\tconst valid = new Set(Object.keys(desc.nodes));\n\t\t\t\tfor (const k of [...flowMap.keys()]) {\n\t\t\t\t\tif (!valid.has(k)) {\n\t\t\t\t\t\tflowMap.delete(k);\n\t\t\t\t\t\tif (mutationsEnabled && !disposed) {\n\t\t\t\t\t\t\tpendingFlowChanges.push({ kind: \"evict\", path: k });\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Snapshot — consumers receive a frozen view.\n\t\t\tactions.emit(new Map(flowMap) as ReadonlyMap<string, FlowEntry>);\n\t\t},\n\t\t{\n\t\t\tname: \"graphLens.flow\",\n\t\t\tdescribeKind: \"derived\",\n\t\t\tmeta: domainMeta(\"lens\", \"flow\"),\n\t\t},\n\t);\n\t// ── flow delta companion (Phase 14 — last DS-14-substrate thread) ────────\n\t// `flow` is otherwise just a snapshot map; tailing *what changed*\n\t// previously required diffing successive map snapshots. The companion\n\t// surfaces each per-path `tick` / `evict` as a typed `LensFlowChange`,\n\t// O(1) per event, in the same batch frame as the snapshot — mirroring\n\t// `reactiveMap`/`pubsub` `mutationLog`. Opt-in (zero overhead off).\n\t//\n\t// QA-P1: the drain effect is created + kept alive BEFORE\n\t// `keepalive(flow)` (below) so it is subscribed to `flow` before\n\t// `flow`'s first activation. Otherwise `keepalive(flow)`'s\n\t// push-on-subscribe would replay cached changeset(s) into\n\t// `pendingFlowChanges` before the drain exists, collapsing pre-wiring\n\t// deltas into the drain's first flush and breaking the \"same batch\n\t// frame as the snapshot\" contract (COMPOSITION-GUIDE §2 — wire\n\t// observers before emitters).\n\tlet flowMutations: ReactiveLogBundle<LensFlowChange> | undefined;\n\tlet stopFlowMutKeep: (() => void) | undefined;\n\tif (mutLogOpt != null) {\n\t\tconst log = reactiveLog<LensFlowChange>(undefined, {\n\t\t\tname:\n\t\t\t\tmutLogOpt === true\n\t\t\t\t\t? \"graphLens.flowMutations\"\n\t\t\t\t\t: (mutLogOpt.name ?? \"graphLens.flowMutations\"),\n\t\t\tmaxSize: mutLogOpt === true ? undefined : mutLogOpt.maxSize,\n\t\t});\n\t\tflowMutations = log;\n\t\tlet mutVersion = 0;\n\t\t// Effect dependent on `flow`: it fires in the SAME wave `flow`\n\t\t// emits, so the appended records coalesce into the same outermost\n\t\t// batch flush as the snapshot (the `mutationLog` \"same batch\n\t\t// frame\" contract). Side-effecting append from an `effect` node is\n\t\t// the sanctioned home (cf. bridge.ts `topic.publish`-from-effect);\n\t\t// `flow`'s fn stays the single source of the diff.\n\t\tconst flowMutationsDrain = node(\n\t\t\t[flow],\n\t\t\t(_batchData, _actions, _ctx) => {\n\t\t\t\tif (pendingFlowChanges.length === 0) return;\n\t\t\t\tconst drained = pendingFlowChanges.splice(0);\n\t\t\t\tfor (const change of drained) {\n\t\t\t\t\tlog.append({\n\t\t\t\t\t\tstructure: \"lensFlow\",\n\t\t\t\t\t\tversion: ++mutVersion,\n\t\t\t\t\t\tt_ns: wallClockNs(),\n\t\t\t\t\t\tlifecycle: \"data\",\n\t\t\t\t\t\tchange,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"graphLens.flowMutationsDrain\",\n\t\t\t\tdescribeKind: \"effect\",\n\t\t\t\tmeta: domainMeta(\"lens\", \"flowMutations\"),\n\t\t\t},\n\t\t);\n\t\tstopFlowMutKeep = keepalive(flowMutationsDrain);\n\t}\n\n\t// AFTER the drain is wired (QA-P1) so `flow`'s push-on-subscribe\n\t// reaches the drain in the same wave.\n\tconst stopFlowKeep = keepalive(flow);\n\n\treturn {\n\t\ttopology,\n\t\thealth,\n\t\tflow,\n\t\t...(flowMutations ? { flowMutations } : {}),\n\t\tdispose() {\n\t\t\tif (disposed) return;\n\t\t\tdisposed = true;\n\t\t\tstopFlowMutKeep?.();\n\t\t\t// QA-P2: release the reactiveLog bundle's own internal\n\t\t\t// keepalives (view caches / lastValue / nested mutLog), not\n\t\t\t// just the drain effect's keepalive.\n\t\t\tflowMutations?.dispose();\n\t\t\t// QA-P3: a still-warm `flow` (external subscriber) keeps\n\t\t\t// running its fn post-dispose; the buffer push is gated on\n\t\t\t// `!disposed`, but drop any residue so it can't be replayed.\n\t\t\tpendingFlowChanges.length = 0;\n\t\t\tstopFlowKeep();\n\t\t\tstopHealthKeep();\n\t\t\ttopologyHandle.dispose();\n\t\t},\n\t};\n}\n"],"mappings":"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","names":[]}
@@ -3,7 +3,7 @@ import {
3
3
  } from "./chunk-FMPF42Q4.js";
4
4
  import {
5
5
  mutate
6
- } from "./chunk-BXGZFGZ4.js";
6
+ } from "./chunk-C5QD5DQX.js";
7
7
 
8
8
  // src/utils/messaging/audit-records.ts
9
9
  var topicPublishKeyOf = (r) => `${r.topicName}::${r.itemKey}`;
@@ -616,4 +616,4 @@ export {
616
616
  LogProjectorGraph,
617
617
  logProjector
618
618
  };
619
- //# sourceMappingURL=chunk-NPRP3MCV.js.map
619
+ //# sourceMappingURL=chunk-DHDCOOJU.js.map