@graphrefly/graphrefly 0.25.0 → 0.27.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 (231) hide show
  1. package/README.md +8 -0
  2. package/dist/ai-CaR_912Q.d.cts +1033 -0
  3. package/dist/ai-WlRltJV7.d.ts +1033 -0
  4. package/dist/audit-ClmqGOCx.d.cts +245 -0
  5. package/dist/audit-DRlSzBu9.d.ts +245 -0
  6. package/dist/{chunk-QOWVNWOC.js → chunk-3ZWCKRHX.js} +27 -25
  7. package/dist/{chunk-QOWVNWOC.js.map → chunk-3ZWCKRHX.js.map} +1 -1
  8. package/dist/chunk-APFNLIRG.js +62 -0
  9. package/dist/chunk-APFNLIRG.js.map +1 -0
  10. package/dist/chunk-AT5LKYNL.js +395 -0
  11. package/dist/chunk-AT5LKYNL.js.map +1 -0
  12. package/dist/{chunk-IAHGTNOZ.js → chunk-BQ6RQQFF.js} +351 -2095
  13. package/dist/chunk-BQ6RQQFF.js.map +1 -0
  14. package/dist/{chunk-L2GLW2U7.js → chunk-BVZYTZ5H.js} +9 -103
  15. package/dist/chunk-BVZYTZ5H.js.map +1 -0
  16. package/dist/{chunk-EVR6UFUV.js → chunk-DST5DKZS.js} +19 -15
  17. package/dist/{chunk-EVR6UFUV.js.map → chunk-DST5DKZS.js.map} +1 -1
  18. package/dist/{chunk-TKE3JGOH.js → chunk-GTE6PWRZ.js} +5 -692
  19. package/dist/chunk-GTE6PWRZ.js.map +1 -0
  20. package/dist/chunk-HXZEYDUR.js +94 -0
  21. package/dist/chunk-HXZEYDUR.js.map +1 -0
  22. package/dist/chunk-J22W6HV3.js +107 -0
  23. package/dist/chunk-J22W6HV3.js.map +1 -0
  24. package/dist/{chunk-PY4XCDLR.js → chunk-J2VBW3DZ.js} +6 -95
  25. package/dist/chunk-J2VBW3DZ.js.map +1 -0
  26. package/dist/{chunk-HWPIFSW2.js → chunk-JSCT3CR4.js} +6 -4
  27. package/dist/{chunk-HWPIFSW2.js.map → chunk-JSCT3CR4.js.map} +1 -1
  28. package/dist/chunk-JWBCY4NC.js +330 -0
  29. package/dist/chunk-JWBCY4NC.js.map +1 -0
  30. package/dist/chunk-K2AUJHVP.js +2251 -0
  31. package/dist/chunk-K2AUJHVP.js.map +1 -0
  32. package/dist/chunk-MJ2NKQQL.js +119 -0
  33. package/dist/chunk-MJ2NKQQL.js.map +1 -0
  34. package/dist/chunk-N6UR7YVY.js +198 -0
  35. package/dist/chunk-N6UR7YVY.js.map +1 -0
  36. package/dist/chunk-NC6S43JJ.js +456 -0
  37. package/dist/chunk-NC6S43JJ.js.map +1 -0
  38. package/dist/chunk-OFVJBJXR.js +98 -0
  39. package/dist/chunk-OFVJBJXR.js.map +1 -0
  40. package/dist/chunk-OHISZPOJ.js +97 -0
  41. package/dist/chunk-OHISZPOJ.js.map +1 -0
  42. package/dist/chunk-OU5CQKNW.js +102 -0
  43. package/dist/chunk-OU5CQKNW.js.map +1 -0
  44. package/dist/{chunk-XOFWRC73.js → chunk-PF7GRZMW.js} +316 -21
  45. package/dist/chunk-PF7GRZMW.js.map +1 -0
  46. package/dist/{chunk-5DJTTKX3.js → chunk-PHOUUNK7.js} +74 -111
  47. package/dist/chunk-PHOUUNK7.js.map +1 -0
  48. package/dist/chunk-RNHBMHKA.js +1665 -0
  49. package/dist/chunk-RNHBMHKA.js.map +1 -0
  50. package/dist/chunk-SX52TAR4.js +110 -0
  51. package/dist/chunk-SX52TAR4.js.map +1 -0
  52. package/dist/{chunk-H4RVA4VE.js → chunk-VYPWMZ6H.js} +2 -2
  53. package/dist/chunk-WBZOVTYK.js +171 -0
  54. package/dist/chunk-WBZOVTYK.js.map +1 -0
  55. package/dist/chunk-WKNUIZOY.js +354 -0
  56. package/dist/chunk-WKNUIZOY.js.map +1 -0
  57. package/dist/chunk-X3VMZYBT.js +713 -0
  58. package/dist/chunk-X3VMZYBT.js.map +1 -0
  59. package/dist/chunk-X5R3GL6H.js +525 -0
  60. package/dist/chunk-X5R3GL6H.js.map +1 -0
  61. package/dist/chunk-XGPU467M.js +136 -0
  62. package/dist/chunk-XGPU467M.js.map +1 -0
  63. package/dist/compat/index.cjs +7656 -0
  64. package/dist/compat/index.cjs.map +1 -0
  65. package/dist/compat/index.d.cts +18 -0
  66. package/dist/compat/index.d.ts +18 -0
  67. package/dist/compat/index.js +50 -0
  68. package/dist/compat/index.js.map +1 -0
  69. package/dist/compat/jotai/index.cjs +2048 -0
  70. package/dist/compat/jotai/index.cjs.map +1 -0
  71. package/dist/compat/jotai/index.d.cts +2 -0
  72. package/dist/compat/jotai/index.d.ts +2 -0
  73. package/dist/compat/jotai/index.js +9 -0
  74. package/dist/compat/jotai/index.js.map +1 -0
  75. package/dist/compat/nanostores/index.cjs +2175 -0
  76. package/dist/compat/nanostores/index.cjs.map +1 -0
  77. package/dist/compat/nanostores/index.d.cts +2 -0
  78. package/dist/compat/nanostores/index.d.ts +2 -0
  79. package/dist/compat/nanostores/index.js +23 -0
  80. package/dist/compat/nanostores/index.js.map +1 -0
  81. package/dist/compat/nestjs/index.cjs +350 -16
  82. package/dist/compat/nestjs/index.cjs.map +1 -1
  83. package/dist/compat/nestjs/index.d.cts +6 -6
  84. package/dist/compat/nestjs/index.d.ts +6 -6
  85. package/dist/compat/nestjs/index.js +11 -9
  86. package/dist/compat/react/index.cjs +141 -0
  87. package/dist/compat/react/index.cjs.map +1 -0
  88. package/dist/compat/react/index.d.cts +2 -0
  89. package/dist/compat/react/index.d.ts +2 -0
  90. package/dist/compat/react/index.js +12 -0
  91. package/dist/compat/react/index.js.map +1 -0
  92. package/dist/compat/solid/index.cjs +128 -0
  93. package/dist/compat/solid/index.cjs.map +1 -0
  94. package/dist/compat/solid/index.d.cts +2 -0
  95. package/dist/compat/solid/index.d.ts +2 -0
  96. package/dist/compat/solid/index.js +12 -0
  97. package/dist/compat/solid/index.js.map +1 -0
  98. package/dist/compat/svelte/index.cjs +131 -0
  99. package/dist/compat/svelte/index.cjs.map +1 -0
  100. package/dist/compat/svelte/index.d.cts +2 -0
  101. package/dist/compat/svelte/index.d.ts +2 -0
  102. package/dist/compat/svelte/index.js +12 -0
  103. package/dist/compat/svelte/index.js.map +1 -0
  104. package/dist/compat/vue/index.cjs +146 -0
  105. package/dist/compat/vue/index.cjs.map +1 -0
  106. package/dist/compat/vue/index.d.cts +3 -0
  107. package/dist/compat/vue/index.d.ts +3 -0
  108. package/dist/compat/vue/index.js +12 -0
  109. package/dist/compat/vue/index.js.map +1 -0
  110. package/dist/compat/zustand/index.cjs +4931 -0
  111. package/dist/compat/zustand/index.cjs.map +1 -0
  112. package/dist/compat/zustand/index.d.cts +5 -0
  113. package/dist/compat/zustand/index.d.ts +5 -0
  114. package/dist/compat/zustand/index.js +12 -0
  115. package/dist/compat/zustand/index.js.map +1 -0
  116. package/dist/composite-C7PcQvcs.d.cts +303 -0
  117. package/dist/composite-aUCvjZVR.d.ts +303 -0
  118. package/dist/core/index.cjs +53 -4
  119. package/dist/core/index.cjs.map +1 -1
  120. package/dist/core/index.d.cts +4 -3
  121. package/dist/core/index.d.ts +4 -3
  122. package/dist/core/index.js +26 -24
  123. package/dist/demo-shell-BDkOptd6.d.ts +102 -0
  124. package/dist/demo-shell-Crid1WdR.d.cts +102 -0
  125. package/dist/extra/index.cjs +222 -110
  126. package/dist/extra/index.cjs.map +1 -1
  127. package/dist/extra/index.d.cts +6 -4
  128. package/dist/extra/index.d.ts +6 -4
  129. package/dist/extra/index.js +72 -65
  130. package/dist/extra/sources.cjs +2486 -0
  131. package/dist/extra/sources.cjs.map +1 -0
  132. package/dist/extra/sources.d.cts +465 -0
  133. package/dist/extra/sources.d.ts +465 -0
  134. package/dist/extra/sources.js +57 -0
  135. package/dist/extra/sources.js.map +1 -0
  136. package/dist/graph/index.cjs +408 -14
  137. package/dist/graph/index.cjs.map +1 -1
  138. package/dist/graph/index.d.cts +5 -5
  139. package/dist/graph/index.d.ts +5 -5
  140. package/dist/graph/index.js +13 -5
  141. package/dist/{graph-D-3JIQme.d.cts → graph-CCwGKLCm.d.ts} +195 -4
  142. package/dist/{graph-B6NFqv3z.d.ts → graph-DNCrvZSn.d.cts} +195 -4
  143. package/dist/index-3lsddbbS.d.ts +86 -0
  144. package/dist/index-B1tloyhO.d.cts +34 -0
  145. package/dist/{index-CYkjxu3s.d.ts → index-B6D3QNSA.d.ts} +33 -4
  146. package/dist/index-B6EhDnjH.d.cts +37 -0
  147. package/dist/index-B9B7_HEY.d.ts +37 -0
  148. package/dist/{index-Ds23Wvou.d.ts → index-BHlKbUwO.d.cts} +131 -883
  149. package/dist/{index-DiobMNwE.d.ts → index-BPVt8kqc.d.ts} +3 -3
  150. package/dist/index-BaSM3aYt.d.ts +195 -0
  151. package/dist/index-BuEoe-Qu.d.ts +121 -0
  152. package/dist/{index-Ch0IpIO0.d.cts → index-BwfLUNw4.d.ts} +131 -883
  153. package/dist/index-ByQxazQJ.d.cts +86 -0
  154. package/dist/index-C0svESO4.d.ts +127 -0
  155. package/dist/{index-OXImXMq6.d.ts → index-C8oil6M6.d.ts} +18 -196
  156. package/dist/{index-DKE1EATr.d.cts → index-CI3DprxP.d.cts} +18 -196
  157. package/dist/{index-AMWewNDe.d.cts → index-CO8uBlUh.d.cts} +33 -4
  158. package/dist/index-CxFrXH4m.d.ts +45 -0
  159. package/dist/index-D8wS_PeY.d.cts +121 -0
  160. package/dist/index-DO_6JN9Z.d.cts +127 -0
  161. package/dist/index-DVGiGFGT.d.cts +195 -0
  162. package/dist/index-DYme44FM.d.cts +44 -0
  163. package/dist/{index-J7Kc0oIQ.d.cts → index-DlLp-2Xn.d.cts} +3 -3
  164. package/dist/index-Dzk2hrlR.d.ts +44 -0
  165. package/dist/index-VHqptjhu.d.cts +45 -0
  166. package/dist/index-VdHQMPy1.d.ts +36 -0
  167. package/dist/index-Xi3u0HCQ.d.cts +36 -0
  168. package/dist/index-wEn0eFe8.d.ts +34 -0
  169. package/dist/index.cjs +1780 -176
  170. package/dist/index.cjs.map +1 -1
  171. package/dist/index.d.cts +784 -2082
  172. package/dist/index.d.ts +784 -2082
  173. package/dist/index.js +955 -4349
  174. package/dist/index.js.map +1 -1
  175. package/dist/memory-C6Z2tGpC.d.cts +139 -0
  176. package/dist/memory-li6FL5RM.d.ts +139 -0
  177. package/dist/messaging-Gt4LPbyA.d.cts +269 -0
  178. package/dist/messaging-XDoYablx.d.ts +269 -0
  179. package/dist/{meta-DWbkoq1s.d.cts → meta-BxCA7rcr.d.cts} +1 -1
  180. package/dist/{meta-CnkLA_43.d.ts → meta-CbznRPYJ.d.ts} +1 -1
  181. package/dist/{node-B-f-Lu-k.d.cts → node-BmerH3kS.d.cts} +26 -1
  182. package/dist/{node-B-f-Lu-k.d.ts → node-BmerH3kS.d.ts} +26 -1
  183. package/dist/{observable-uP-wy_uK.d.ts → observable-BgGUwcqp.d.ts} +1 -1
  184. package/dist/{observable-DBnrwcar.d.cts → observable-DJt_AxzQ.d.cts} +1 -1
  185. package/dist/patterns/ai.cjs +7930 -0
  186. package/dist/patterns/ai.cjs.map +1 -0
  187. package/dist/patterns/ai.d.cts +10 -0
  188. package/dist/patterns/ai.d.ts +10 -0
  189. package/dist/patterns/ai.js +71 -0
  190. package/dist/patterns/ai.js.map +1 -0
  191. package/dist/patterns/audit.cjs +5805 -0
  192. package/dist/patterns/audit.cjs.map +1 -0
  193. package/dist/patterns/audit.d.cts +6 -0
  194. package/dist/patterns/audit.d.ts +6 -0
  195. package/dist/patterns/audit.js +29 -0
  196. package/dist/patterns/audit.js.map +1 -0
  197. package/dist/patterns/demo-shell.cjs +5604 -0
  198. package/dist/patterns/demo-shell.cjs.map +1 -0
  199. package/dist/patterns/demo-shell.d.cts +6 -0
  200. package/dist/patterns/demo-shell.d.ts +6 -0
  201. package/dist/patterns/demo-shell.js +15 -0
  202. package/dist/patterns/demo-shell.js.map +1 -0
  203. package/dist/patterns/memory.cjs +5283 -0
  204. package/dist/patterns/memory.cjs.map +1 -0
  205. package/dist/patterns/memory.d.cts +5 -0
  206. package/dist/patterns/memory.d.ts +5 -0
  207. package/dist/patterns/memory.js +20 -0
  208. package/dist/patterns/memory.js.map +1 -0
  209. package/dist/patterns/reactive-layout/index.cjs +355 -13
  210. package/dist/patterns/reactive-layout/index.cjs.map +1 -1
  211. package/dist/patterns/reactive-layout/index.d.cts +6 -5
  212. package/dist/patterns/reactive-layout/index.d.ts +6 -5
  213. package/dist/patterns/reactive-layout/index.js +15 -12
  214. package/dist/reactive-layout-MQP--J3F.d.cts +183 -0
  215. package/dist/reactive-layout-u5Ulnqag.d.ts +183 -0
  216. package/dist/{storage-BuTdpCI1.d.cts → storage-CMjUUuxn.d.ts} +10 -2
  217. package/dist/{storage-F2X1U1x0.d.ts → storage-DdWlZo6U.d.cts} +10 -2
  218. package/dist/sugar-CCOxXK1e.d.ts +201 -0
  219. package/dist/sugar-D02n5JjF.d.cts +201 -0
  220. package/package.json +63 -3
  221. package/dist/chunk-5DJTTKX3.js.map +0 -1
  222. package/dist/chunk-IAHGTNOZ.js.map +0 -1
  223. package/dist/chunk-L2GLW2U7.js.map +0 -1
  224. package/dist/chunk-MW4VAKAO.js +0 -47
  225. package/dist/chunk-MW4VAKAO.js.map +0 -1
  226. package/dist/chunk-PY4XCDLR.js.map +0 -1
  227. package/dist/chunk-TKE3JGOH.js.map +0 -1
  228. package/dist/chunk-XOFWRC73.js.map +0 -1
  229. package/dist/index-BJB7t9gg.d.cts +0 -392
  230. package/dist/index-C-TXEa7C.d.ts +0 -392
  231. /package/dist/{chunk-H4RVA4VE.js.map → chunk-VYPWMZ6H.js.map} +0 -0
@@ -276,10 +276,6 @@ function SagaHandler(cqrsName, sagaName, eventNames) {
276
276
  };
277
277
  }
278
278
 
279
- // src/extra/sources.ts
280
- var import_node_fs = require("fs");
281
- var import_node_path = require("path");
282
-
283
279
  // src/core/clock.ts
284
280
  function monotonicNs() {
285
281
  return Math.trunc(performance.now() * 1e6);
@@ -1142,6 +1138,12 @@ var NodeImpl = class _NodeImpl {
1142
1138
  _autoError;
1143
1139
  _pausable;
1144
1140
  _guard;
1141
+ /**
1142
+ * @internal Additional guards stacked at runtime via {@link NodeImpl._pushGuard}
1143
+ * (e.g. by `policyEnforcer({ mode: "enforce" })`, roadmap §9.2). Effective
1144
+ * write/signal/observe checks AND the original `_guard` with every entry here.
1145
+ */
1146
+ _extraGuards;
1145
1147
  _hashFn;
1146
1148
  _versioning;
1147
1149
  /**
@@ -1315,18 +1317,61 @@ var NodeImpl = class _NodeImpl {
1315
1317
  if (this._inspectorHooks?.size === 0) this._inspectorHooks = void 0;
1316
1318
  };
1317
1319
  }
1320
+ /**
1321
+ * @internal Push an additional guard onto this node. Effective enforcement
1322
+ * is the AND of `_guard` and every guard pushed via this hook — any one
1323
+ * rejecting throws {@link GuardDenied}. Returns a disposer that removes
1324
+ * the pushed guard. Multiple guards may be stacked simultaneously.
1325
+ *
1326
+ * Used by `policyEnforcer({ mode: "enforce" })` (roadmap §9.2) to overlay
1327
+ * runtime constraint enforcement onto an existing graph without rebuilding
1328
+ * its nodes. Pre-1.0 internal API; not part of the public surface.
1329
+ *
1330
+ * **Identity semantics:** guards are tracked in a `Set`, so pushing the
1331
+ * same `NodeGuard` reference twice is a single registration. Wrap each
1332
+ * push in a unique closure if independent stacking is needed.
1333
+ *
1334
+ * **Iteration order:** insertion-ordered (`Set` semantics). Determinism
1335
+ * follows from single-threaded JS execution; nested re-entry from inside
1336
+ * a guard body (push/pop while iterating) is undefined-but-survivable.
1337
+ */
1338
+ _pushGuard(guard) {
1339
+ if (this._extraGuards == null) this._extraGuards = /* @__PURE__ */ new Set();
1340
+ this._extraGuards.add(guard);
1341
+ return () => {
1342
+ this._extraGuards?.delete(guard);
1343
+ if (this._extraGuards?.size === 0) this._extraGuards = void 0;
1344
+ };
1345
+ }
1318
1346
  allowsObserve(actor) {
1319
- if (this._guard == null) return true;
1320
- return this._guard(normalizeActor(actor), "observe");
1347
+ if (this._guard == null && this._extraGuards == null) return true;
1348
+ const a = normalizeActor(actor);
1349
+ if (this._guard != null && !this._guard(a, "observe")) return false;
1350
+ if (this._extraGuards != null) {
1351
+ for (const eg of this._extraGuards) {
1352
+ if (!eg(a, "observe")) return false;
1353
+ }
1354
+ }
1355
+ return true;
1321
1356
  }
1322
1357
  // --- Guard helper ---
1323
1358
  _checkGuard(options) {
1324
- if (options?.internal || this._guard == null) return;
1359
+ if (options?.internal) return;
1360
+ const hasGuard = this._guard != null || this._extraGuards != null;
1361
+ const hasActor = options?.actor != null;
1362
+ if (!hasGuard && !hasActor) return;
1325
1363
  const actor = normalizeActor(options?.actor);
1326
1364
  const action = options?.delivery === "signal" ? "signal" : "write";
1327
- if (!this._guard(actor, action)) {
1365
+ if (this._guard != null && !this._guard(actor, action)) {
1328
1366
  throw new GuardDenied({ actor, action, nodeName: this.name });
1329
1367
  }
1368
+ if (this._extraGuards != null) {
1369
+ for (const eg of this._extraGuards) {
1370
+ if (!eg(actor, action)) {
1371
+ throw new GuardDenied({ actor, action, nodeName: this.name });
1372
+ }
1373
+ }
1374
+ }
1330
1375
  this._lastMutation = { actor, timestamp_ns: wallClockNs() };
1331
1376
  }
1332
1377
  // --- Public transport ---
@@ -3291,6 +3336,200 @@ var RingBuffer = class {
3291
3336
  }
3292
3337
  };
3293
3338
 
3339
+ // src/graph/explain.ts
3340
+ function explainPath(described, from, to, opts = {}) {
3341
+ const fromExists = from in described.nodes;
3342
+ const toExists = to in described.nodes;
3343
+ if (!fromExists) return makeFailure(from, to, "no-such-from");
3344
+ if (!toExists) return makeFailure(from, to, "no-such-to");
3345
+ const maxDepth = opts.maxDepth;
3346
+ if (maxDepth != null && (!Number.isInteger(maxDepth) || maxDepth < 0)) {
3347
+ throw new Error(`explainPath: maxDepth must be an integer >= 0`);
3348
+ }
3349
+ if (from === to) {
3350
+ if (opts.findCycle === true) {
3351
+ const cycle = findShortestCycle(described, from, opts);
3352
+ if (cycle != null) return cycle;
3353
+ }
3354
+ const step = buildStep(from, described.nodes[from], 0, opts);
3355
+ return makeSuccess(from, to, [step]);
3356
+ }
3357
+ if (maxDepth === 0) return makeFailure(from, to, "no-path");
3358
+ const result = bfsShortestPath(described, from, to, maxDepth);
3359
+ if (!result.found) {
3360
+ return makeFailure(from, to, result.truncated ? "max-depth-exceeded" : "no-path");
3361
+ }
3362
+ return makeSuccess(from, to, materializeSteps(described, result.pathOrder, opts));
3363
+ }
3364
+ function bfsShortestPath(described, from, to, maxDepth) {
3365
+ const pred = /* @__PURE__ */ new Map();
3366
+ const queue = [{ path: to, depth: 0 }];
3367
+ const visited = /* @__PURE__ */ new Set([to]);
3368
+ let head = 0;
3369
+ let truncated = false;
3370
+ while (head < queue.length) {
3371
+ const cur = queue[head++];
3372
+ if (cur.path === from) break;
3373
+ if (maxDepth != null && cur.depth >= maxDepth) {
3374
+ const node3 = described.nodes[cur.path];
3375
+ if (node3?.deps && node3.deps.length > 0) truncated = true;
3376
+ continue;
3377
+ }
3378
+ const node2 = described.nodes[cur.path];
3379
+ if (node2 == null) continue;
3380
+ const deps = node2.deps ?? [];
3381
+ const slots = /* @__PURE__ */ new Map();
3382
+ for (let i = 0; i < deps.length; i++) {
3383
+ const dep = deps[i];
3384
+ if (!dep) continue;
3385
+ let arr = slots.get(dep);
3386
+ if (arr == null) {
3387
+ arr = [];
3388
+ slots.set(dep, arr);
3389
+ }
3390
+ arr.push(i);
3391
+ }
3392
+ for (const [dep, indices] of slots) {
3393
+ if (visited.has(dep)) continue;
3394
+ visited.add(dep);
3395
+ pred.set(dep, { from: cur.path, depIndices: indices });
3396
+ queue.push({ path: dep, depth: cur.depth + 1 });
3397
+ }
3398
+ }
3399
+ if (!pred.has(from)) {
3400
+ return { found: false, pathOrder: [], truncated };
3401
+ }
3402
+ const pathOrder = [{ path: from }];
3403
+ let cursor = from;
3404
+ while (cursor !== to) {
3405
+ const p = pred.get(cursor);
3406
+ if (p == null) return { found: false, pathOrder: [], truncated: false };
3407
+ pathOrder[pathOrder.length - 1].depIndices = p.depIndices;
3408
+ pathOrder.push({ path: p.from });
3409
+ cursor = p.from;
3410
+ }
3411
+ return { found: true, pathOrder, truncated: false };
3412
+ }
3413
+ function findShortestCycle(described, start, opts) {
3414
+ const startNode = described.nodes[start];
3415
+ if (startNode == null) return null;
3416
+ const startDeps = startNode.deps ?? [];
3417
+ const selfSlots = [];
3418
+ for (let i = 0; i < startDeps.length; i++) if (startDeps[i] === start) selfSlots.push(i);
3419
+ if (selfSlots.length > 0) {
3420
+ const step0 = buildStep(start, startNode, 0, opts);
3421
+ step0.dep_index = selfSlots[0];
3422
+ const step1 = buildStep(start, startNode, 1, opts);
3423
+ return makeSuccess(start, start, [step0, step1]);
3424
+ }
3425
+ let best = null;
3426
+ for (let i = 0; i < startDeps.length; i++) {
3427
+ const dep = startDeps[i];
3428
+ if (!dep || dep === start) continue;
3429
+ const sub = bfsShortestPath(described, dep, start, opts.maxDepth);
3430
+ if (!sub.found) continue;
3431
+ if (best == null || sub.pathOrder.length < best.pathOrder.length) {
3432
+ best = sub;
3433
+ best = {
3434
+ found: true,
3435
+ pathOrder: [{ path: start, depIndices: [i] }, ...sub.pathOrder],
3436
+ truncated: false
3437
+ };
3438
+ }
3439
+ }
3440
+ if (best == null) return null;
3441
+ return makeSuccess(start, start, materializeSteps(described, best.pathOrder, opts));
3442
+ }
3443
+ function materializeSteps(described, pathOrder, opts) {
3444
+ return pathOrder.map((entry, i) => {
3445
+ const node2 = described.nodes[entry.path];
3446
+ const step = buildStep(entry.path, node2, i, opts);
3447
+ if (entry.depIndices != null && entry.depIndices.length > 0) {
3448
+ step.dep_index = entry.depIndices[0];
3449
+ if (entry.depIndices.length > 1) step.dep_indices = [...entry.depIndices];
3450
+ }
3451
+ return step;
3452
+ });
3453
+ }
3454
+ function buildStep(path, node2, hop, opts) {
3455
+ const step = {
3456
+ path,
3457
+ type: node2.type,
3458
+ hop
3459
+ };
3460
+ if (node2.status !== void 0) step.status = node2.status;
3461
+ if ("value" in node2) step.value = node2.value;
3462
+ if (node2.v != null) step.v = node2.v;
3463
+ const annotation = opts.annotations?.get(path) ?? node2.reason;
3464
+ if (annotation != null) step.reason = annotation;
3465
+ const lastMutation = opts.lastMutations?.get(path) ?? node2.lastMutation;
3466
+ if (lastMutation != null) step.lastMutation = lastMutation;
3467
+ return step;
3468
+ }
3469
+ function makeSuccess(from, to, steps) {
3470
+ return finalize(from, to, true, "ok", steps);
3471
+ }
3472
+ function makeFailure(from, to, reason) {
3473
+ return finalize(from, to, false, reason, []);
3474
+ }
3475
+ function finalize(from, to, found, reason, steps) {
3476
+ const text = renderChain(from, to, found, reason, steps);
3477
+ return {
3478
+ from,
3479
+ to,
3480
+ found,
3481
+ reason,
3482
+ steps,
3483
+ text,
3484
+ toJSON() {
3485
+ return { from, to, found, reason, steps };
3486
+ }
3487
+ };
3488
+ }
3489
+ function renderChain(from, to, found, reason, steps) {
3490
+ if (!found) {
3491
+ switch (reason) {
3492
+ case "no-such-from":
3493
+ return `explainPath: no node named "${from}"`;
3494
+ case "no-such-to":
3495
+ return `explainPath: no node named "${to}"`;
3496
+ case "max-depth-exceeded":
3497
+ return `explainPath: no path from "${from}" to "${to}" within maxDepth`;
3498
+ default:
3499
+ return `explainPath: no path from "${from}" to "${to}"`;
3500
+ }
3501
+ }
3502
+ const lines = [`Causal path: ${from} \u2192 ${to} (${steps.length} step(s))`];
3503
+ for (const step of steps) {
3504
+ const arrow = step.hop === 0 ? "\xB7" : "\u2193";
3505
+ const head = ` ${arrow} ${step.path} (${step.type}${step.status ? `/${step.status}` : ""})`;
3506
+ lines.push(head);
3507
+ if ("value" in step) {
3508
+ lines.push(` value: ${formatValue(step.value)}`);
3509
+ }
3510
+ if (step.reason != null) {
3511
+ lines.push(` reason: ${step.reason}`);
3512
+ }
3513
+ if (step.lastMutation != null) {
3514
+ const a = step.lastMutation.actor;
3515
+ lines.push(` actor: ${a.type}${a.id ? `:${a.id}` : ""}`);
3516
+ }
3517
+ }
3518
+ return lines.join("\n");
3519
+ }
3520
+ function formatValue(v) {
3521
+ if (v === void 0) return "<sentinel>";
3522
+ if (v === null) return "null";
3523
+ if (typeof v === "string") return JSON.stringify(v);
3524
+ if (typeof v === "number" || typeof v === "boolean" || typeof v === "bigint") return String(v);
3525
+ try {
3526
+ const s = JSON.stringify(v);
3527
+ return s.length > 80 ? `${s.slice(0, 77)}...` : s;
3528
+ } catch {
3529
+ return String(v);
3530
+ }
3531
+ }
3532
+
3294
3533
  // src/extra/utils/sizeof.ts
3295
3534
  var OVERHEAD = {
3296
3535
  object: 56,
@@ -3924,6 +4163,20 @@ var Graph = class _Graph {
3924
4163
  _parent = void 0;
3925
4164
  _storageDisposers = /* @__PURE__ */ new Set();
3926
4165
  _disposers = /* @__PURE__ */ new Set();
4166
+ /**
4167
+ * @internal Lazy `TopologyEvent` producer. Created on first `.topology`
4168
+ * access. Zero cost until something subscribes — producer fn only runs when
4169
+ * the first sink attaches, registering one handler into
4170
+ * {@link Graph._topologyEmitters}.
4171
+ */
4172
+ _topology;
4173
+ /**
4174
+ * @internal Active emit handlers for the topology producer. Each entry is
4175
+ * the closure registered by the producer fn on activation; cleared on
4176
+ * deactivation. `_emitTopology` broadcasts through every entry (there is at
4177
+ * most one per activation cycle of the producer).
4178
+ */
4179
+ _topologyEmitters = /* @__PURE__ */ new Set();
3927
4180
  /**
3928
4181
  * @param name - Non-empty graph id (must not contain `::` and must not
3929
4182
  * equal the reserved meta segment `__meta__`).
@@ -3963,6 +4216,55 @@ var Graph = class _Graph {
3963
4216
  return out;
3964
4217
  }
3965
4218
  // ——————————————————————————————————————————————————————————————
4219
+ // Topology companion (structural-change event stream)
4220
+ // ——————————————————————————————————————————————————————————————
4221
+ /**
4222
+ * Reactive stream of structural changes to this graph's own registry
4223
+ * (add / mount / remove). Value mutations live on `observe()`; this
4224
+ * companion only fires when the topology shape changes.
4225
+ *
4226
+ * Lazy: the underlying node is created on first access and activates when
4227
+ * something subscribes. No emission replay — late subscribers do not
4228
+ * receive historical events and should snapshot via {@link Graph.describe}
4229
+ * before listening for incremental changes. Events that fire while the
4230
+ * producer has zero subscribers are dropped (no retention).
4231
+ *
4232
+ * Own-graph only: a parent's `topology` does NOT emit for structural
4233
+ * changes inside a mounted child. Transitive consumers subscribe to each
4234
+ * child's topology separately (recurse through `topology`'s own "added"
4235
+ * events with `nodeKind: "mount"` to discover new children).
4236
+ *
4237
+ * See {@link TopologyEvent} for payload shape.
4238
+ *
4239
+ * @category observability
4240
+ */
4241
+ get topology() {
4242
+ if (this._topology == null) {
4243
+ this._topology = producer(
4244
+ (actions) => {
4245
+ const handler = (event) => {
4246
+ actions.emit(event);
4247
+ };
4248
+ this._topologyEmitters.add(handler);
4249
+ return () => {
4250
+ this._topologyEmitters.delete(handler);
4251
+ };
4252
+ },
4253
+ { name: `${this.name}_topology` }
4254
+ );
4255
+ }
4256
+ return this._topology;
4257
+ }
4258
+ /**
4259
+ * @internal Fire a {@link TopologyEvent} to every active subscriber of
4260
+ * `this.topology`. No-op when the topology node has never been accessed or
4261
+ * currently has no sinks — zero cost for graphs nobody observes.
4262
+ */
4263
+ _emitTopology(event) {
4264
+ if (this._topology == null || this._topologyEmitters.size === 0) return;
4265
+ for (const h of this._topologyEmitters) h(event);
4266
+ }
4267
+ // ——————————————————————————————————————————————————————————————
3966
4268
  // Node registry
3967
4269
  // ——————————————————————————————————————————————————————————————
3968
4270
  /**
@@ -3992,6 +4294,7 @@ var Graph = class _Graph {
3992
4294
  }
3993
4295
  this._nodes.set(name, node2);
3994
4296
  this._nodeToName.set(node2, name);
4297
+ this._emitTopology({ kind: "added", name, nodeKind: "node" });
3995
4298
  return node2;
3996
4299
  }
3997
4300
  /**
@@ -4032,22 +4335,23 @@ var Graph = class _Graph {
4032
4335
  assertRegisterableName(name, this.name, "remove");
4033
4336
  const child = this._mounts.get(name);
4034
4337
  if (child) {
4035
- const audit = { kind: "mount", nodes: [], mounts: [] };
4338
+ const audit2 = { kind: "mount", nodes: [], mounts: [] };
4036
4339
  const targets = [];
4037
4340
  child._collectObserveTargets("", targets);
4038
4341
  for (const [p, n] of targets) {
4039
4342
  if (!p.includes(`${PATH_SEP}${GRAPH_META_SEGMENT}${PATH_SEP}`)) {
4040
- audit.nodes.push(p);
4343
+ audit2.nodes.push(p);
4041
4344
  }
4042
4345
  void n;
4043
4346
  }
4044
- audit.nodes.sort();
4045
- audit.mounts.push(name);
4046
- audit.mounts.push(...child._collectSubgraphs(`${name}${PATH_SEP}`));
4347
+ audit2.nodes.sort();
4348
+ audit2.mounts.push(name);
4349
+ audit2.mounts.push(...child._collectSubgraphs(`${name}${PATH_SEP}`));
4047
4350
  this._mounts.delete(name);
4048
4351
  child._parent = void 0;
4049
4352
  teardownMountedGraph(child);
4050
- return audit;
4353
+ this._emitTopology({ kind: "removed", name, nodeKind: "mount", audit: audit2 });
4354
+ return audit2;
4051
4355
  }
4052
4356
  const node2 = this._nodes.get(name);
4053
4357
  if (!node2) {
@@ -4056,7 +4360,9 @@ var Graph = class _Graph {
4056
4360
  this._nodes.delete(name);
4057
4361
  this._nodeToName.delete(node2);
4058
4362
  node2.down([[TEARDOWN]], { internal: true });
4059
- return { kind: "node", nodes: [name], mounts: [] };
4363
+ const audit = { kind: "node", nodes: [name], mounts: [] };
4364
+ this._emitTopology({ kind: "removed", name, nodeKind: "node", audit });
4365
+ return audit;
4060
4366
  }
4061
4367
  /**
4062
4368
  * Bulk remove — invokes {@link Graph.remove} for every local name matching
@@ -4285,6 +4591,7 @@ var Graph = class _Graph {
4285
4591
  }
4286
4592
  this._mounts.set(name, child);
4287
4593
  child._parent = this;
4594
+ this._emitTopology({ kind: "added", name, nodeKind: "mount" });
4288
4595
  return child;
4289
4596
  }
4290
4597
  /**
@@ -4575,6 +4882,33 @@ var Graph = class _Graph {
4575
4882
  }
4576
4883
  return reachable(this.describe(), from, direction, opts);
4577
4884
  }
4885
+ /**
4886
+ * Causal walkback: shortest dep-chain from `from` to `to`, enriched with
4887
+ * each node's value, status, last-mutation actor, and reasoning annotation
4888
+ * from {@link Graph.trace}. Wraps {@link explainPath} (roadmap §9.2).
4889
+ *
4890
+ * @param from - Upstream node (the cause).
4891
+ * @param to - Downstream node (the effect).
4892
+ * @param opts - Optional `maxDepth` and `findCycle`. When `findCycle:true`
4893
+ * and `from === to`, returns the shortest cycle through other nodes
4894
+ * (useful for diagnosing feedback loops, COMPOSITION-GUIDE §7).
4895
+ * Annotations and lastMutations are collected automatically from the
4896
+ * live graph.
4897
+ */
4898
+ explain(from, to, opts) {
4899
+ const described = this.describe({ detail: "full" });
4900
+ const annotations = new Map(this._annotations);
4901
+ const lastMutations = /* @__PURE__ */ new Map();
4902
+ for (const [path, n] of Object.entries(described.nodes)) {
4903
+ if (n.lastMutation != null) lastMutations.set(path, n.lastMutation);
4904
+ }
4905
+ return explainPath(described, from, to, {
4906
+ ...opts?.maxDepth != null ? { maxDepth: opts.maxDepth } : {},
4907
+ ...opts?.findCycle === true ? { findCycle: true } : {},
4908
+ annotations,
4909
+ lastMutations
4910
+ });
4911
+ }
4578
4912
  /**
4579
4913
  * @internal Collect all qualified paths in this graph tree matching a
4580
4914
  * glob pattern. Used by scoped autoCheckpoint subscription.
@@ -5253,7 +5587,7 @@ var Graph = class _Graph {
5253
5587
  return;
5254
5588
  }
5255
5589
  const nextSeq = s.seq + 1;
5256
- const timestamp_ns = monotonicNs();
5590
+ const timestamp_ns = wallClockNs();
5257
5591
  const isFirst = s.lastSnapshot == null;
5258
5592
  const shouldCompact = isFirst || nextSeq % s.compactEvery === 0;
5259
5593
  const record = shouldCompact ? {