@graphrefly/graphrefly 0.47.1 → 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.
- package/dist/base/composition/index.cjs +28 -19
- package/dist/base/composition/index.cjs.map +1 -1
- package/dist/base/composition/index.d.cts +14 -5
- package/dist/base/composition/index.d.ts +14 -5
- package/dist/base/composition/index.js +9 -9
- package/dist/base/index.cjs +294 -164
- package/dist/base/index.cjs.map +1 -1
- package/dist/base/index.d.cts +2 -2
- package/dist/base/index.d.ts +2 -2
- package/dist/base/index.js +77 -72
- package/dist/base/io/index.cjs +145 -85
- package/dist/base/io/index.cjs.map +1 -1
- package/dist/base/io/index.d.cts +32 -5
- package/dist/base/io/index.d.ts +32 -5
- package/dist/base/io/index.js +5 -5
- package/dist/base/mutation/index.cjs +21 -0
- package/dist/base/mutation/index.cjs.map +1 -1
- package/dist/base/mutation/index.d.cts +23 -1
- package/dist/base/mutation/index.d.ts +23 -1
- package/dist/base/mutation/index.js +3 -1
- package/dist/base/sources/browser/index.cjs +18 -12
- package/dist/base/sources/browser/index.cjs.map +1 -1
- package/dist/base/sources/browser/index.d.cts +20 -2
- package/dist/base/sources/browser/index.d.ts +20 -2
- package/dist/base/sources/browser/index.js +18 -12
- package/dist/base/sources/browser/index.js.map +1 -1
- package/dist/base/sources/event/index.cjs +29 -1
- package/dist/base/sources/event/index.cjs.map +1 -1
- package/dist/base/sources/event/index.d.cts +67 -3
- package/dist/base/sources/event/index.d.ts +67 -3
- package/dist/base/sources/event/index.js +5 -2
- package/dist/base/sources/index.cjs +96 -50
- package/dist/base/sources/index.cjs.map +1 -1
- package/dist/base/sources/index.d.cts +1 -1
- package/dist/base/sources/index.d.ts +1 -1
- package/dist/base/sources/index.js +7 -4
- package/dist/base/sources/node/index.cjs +43 -37
- package/dist/base/sources/node/index.cjs.map +1 -1
- package/dist/base/sources/node/index.js +43 -37
- package/dist/base/sources/node/index.js.map +1 -1
- package/dist/{chunk-J5WFUEO4.js → chunk-23MAWVOJ.js} +3 -3
- package/dist/{chunk-YXCPV26R.js → chunk-3REMCHSS.js} +39 -27
- package/dist/chunk-3REMCHSS.js.map +1 -0
- package/dist/{chunk-CEVNQ74M.js → chunk-3YGXPUHW.js} +2 -2
- package/dist/{chunk-CEVNQ74M.js.map → chunk-3YGXPUHW.js.map} +1 -1
- package/dist/{chunk-EVYY4X5A.js → chunk-46X2EFQH.js} +16 -5
- package/dist/chunk-46X2EFQH.js.map +1 -0
- package/dist/{chunk-NY2PYHNC.js → chunk-5UY3PNFY.js} +12 -5
- package/dist/chunk-5UY3PNFY.js.map +1 -0
- package/dist/{chunk-RGMTUZCL.js → chunk-65OM4XLQ.js} +50 -4
- package/dist/chunk-65OM4XLQ.js.map +1 -0
- package/dist/{chunk-3PSLNJDU.js → chunk-6DQYBIHW.js} +314 -49
- package/dist/chunk-6DQYBIHW.js.map +1 -0
- package/dist/{chunk-LDCSZ72P.js → chunk-6YBER5UP.js} +3 -3
- package/dist/{chunk-LDCSZ72P.js.map → chunk-6YBER5UP.js.map} +1 -1
- package/dist/{chunk-7EGRP2VX.js → chunk-7BULJTL6.js} +2 -2
- package/dist/{chunk-7EGRP2VX.js.map → chunk-7BULJTL6.js.map} +1 -1
- package/dist/{chunk-VLAGJZSL.js → chunk-7T7WLEPM.js} +25 -4
- package/dist/chunk-7T7WLEPM.js.map +1 -0
- package/dist/{chunk-PKPO3JTZ.js → chunk-AQAKDE7F.js} +29 -11
- package/dist/chunk-AQAKDE7F.js.map +1 -0
- package/dist/{chunk-2OB3CEJS.js → chunk-B5Y5GPD5.js} +2 -2
- package/dist/{chunk-BXGZFGZ4.js → chunk-C5QD5DQX.js} +22 -1
- package/dist/chunk-C5QD5DQX.js.map +1 -0
- package/dist/{chunk-4XCHZRUJ.js → chunk-D5YGR4TP.js} +58 -7
- package/dist/chunk-D5YGR4TP.js.map +1 -0
- package/dist/{chunk-NPRP3MCV.js → chunk-DHDCOOJU.js} +2 -2
- package/dist/chunk-DHDCOOJU.js.map +1 -0
- package/dist/{chunk-MTTRCEJT.js → chunk-DVTDF5OI.js} +2 -2
- package/dist/{chunk-SOOKUYVM.js → chunk-F7EKHR32.js} +13 -9
- package/dist/chunk-F7EKHR32.js.map +1 -0
- package/dist/{chunk-A7KV5UK4.js → chunk-G7H6PN7P.js} +2 -2
- package/dist/{chunk-OCUDSN63.js → chunk-GGKHHG5Y.js} +110 -64
- package/dist/chunk-GGKHHG5Y.js.map +1 -0
- package/dist/{chunk-RAGGHLCV.js → chunk-GUNIRPEJ.js} +8 -6
- package/dist/{chunk-RAGGHLCV.js.map → chunk-GUNIRPEJ.js.map} +1 -1
- package/dist/{chunk-YJ4U2D2C.js → chunk-J5TBZFBD.js} +9 -7
- package/dist/chunk-J5TBZFBD.js.map +1 -0
- package/dist/{chunk-Y52CS6YA.js → chunk-JA67ZQG2.js} +2 -2
- package/dist/{chunk-Y52CS6YA.js.map → chunk-JA67ZQG2.js.map} +1 -1
- package/dist/{chunk-U225SKB4.js → chunk-K4ZYJ4EM.js} +569 -424
- package/dist/chunk-K4ZYJ4EM.js.map +1 -0
- package/dist/{chunk-Z4YXAUDN.js → chunk-KUFXLAEY.js} +11 -7
- package/dist/{chunk-Z4YXAUDN.js.map → chunk-KUFXLAEY.js.map} +1 -1
- package/dist/{chunk-IHTWQEDR.js → chunk-LTSI7ULC.js} +3 -3
- package/dist/{chunk-IHTWQEDR.js.map → chunk-LTSI7ULC.js.map} +1 -1
- package/dist/{chunk-DKNHAICT.js → chunk-MMHGYX44.js} +25 -9
- package/dist/chunk-MMHGYX44.js.map +1 -0
- package/dist/{chunk-K7PDZYQE.js → chunk-MQMTRKY3.js} +129 -50
- package/dist/chunk-MQMTRKY3.js.map +1 -0
- package/dist/{chunk-42FQ27MQ.js → chunk-MTODGQBR.js} +44 -179
- package/dist/chunk-MTODGQBR.js.map +1 -0
- package/dist/{chunk-O3MT7DYI.js → chunk-N6MNJNHB.js} +2 -2
- package/dist/{chunk-FVINAAKA.js → chunk-NBK6QQMG.js} +14 -13
- package/dist/{chunk-FVINAAKA.js.map → chunk-NBK6QQMG.js.map} +1 -1
- package/dist/{chunk-DM4OMPWK.js → chunk-NSA5K5G2.js} +2 -2
- package/dist/{chunk-MLTPJMH6.js → chunk-QQYULEZL.js} +2 -2
- package/dist/chunk-QSW4DFKE.js +31 -0
- package/dist/chunk-QSW4DFKE.js.map +1 -0
- package/dist/{chunk-PZWISPIQ.js → chunk-S7HN5FHL.js} +17 -11
- package/dist/chunk-S7HN5FHL.js.map +1 -0
- package/dist/{chunk-4S53H2KR.js → chunk-SUNCHMML.js} +2 -2
- package/dist/{chunk-4GYMCUDZ.js → chunk-T2U6N3FV.js} +7 -7
- package/dist/{chunk-RJOG4IJU.js → chunk-T5URUIIY.js} +50 -35
- package/dist/chunk-T5URUIIY.js.map +1 -0
- package/dist/{chunk-B4AKFXGE.js → chunk-TPTZZV25.js} +6 -6
- package/dist/chunk-TPTZZV25.js.map +1 -0
- package/dist/{chunk-BU3SEFA5.js → chunk-V46JWFGV.js} +7 -6
- package/dist/chunk-V46JWFGV.js.map +1 -0
- package/dist/{chunk-IJRR6YAI.js → chunk-VLDRAMP7.js} +18 -12
- package/dist/chunk-VLDRAMP7.js.map +1 -0
- package/dist/{chunk-6XZYT4SW.js → chunk-X6ESZDR6.js} +8 -9
- package/dist/chunk-X6ESZDR6.js.map +1 -0
- package/dist/{chunk-E5OZPDIW.js → chunk-X7BA5PWG.js} +7 -5
- package/dist/chunk-X7BA5PWG.js.map +1 -0
- package/dist/{chunk-CXANAIZU.js → chunk-XEWV254I.js} +3 -3
- package/dist/{chunk-CXANAIZU.js.map → chunk-XEWV254I.js.map} +1 -1
- package/dist/{chunk-V4Y3TM7U.js → chunk-YBJVKMTM.js} +38 -16
- package/dist/chunk-YBJVKMTM.js.map +1 -0
- package/dist/{chunk-7ADWWI2T.js → chunk-ZW32BPXV.js} +17 -6
- package/dist/chunk-ZW32BPXV.js.map +1 -0
- package/dist/compat/index.cjs +52 -5
- package/dist/compat/index.cjs.map +1 -1
- package/dist/compat/index.d.cts +1 -1
- package/dist/compat/index.d.ts +1 -1
- package/dist/compat/index.js +7 -7
- package/dist/compat/nestjs/index.cjs +52 -5
- package/dist/compat/nestjs/index.cjs.map +1 -1
- package/dist/compat/nestjs/index.d.cts +1 -1
- package/dist/compat/nestjs/index.d.ts +1 -1
- package/dist/compat/nestjs/index.js +4 -4
- package/dist/{fallback-Bx46zqky.d.cts → fallback-BROR6ZhO.d.cts} +1 -1
- package/dist/{fallback-pIWW8A2d.d.ts → fallback-DO80aM_3.d.ts} +1 -1
- package/dist/{index-B_p8tnvf.d.cts → index-D1z3XcF9.d.cts} +1 -0
- package/dist/{index-_HDSmPyp.d.ts → index-DZ6yua0Q.d.ts} +1 -0
- package/dist/index.cjs +2387 -1707
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +10 -10
- package/dist/index.d.ts +10 -10
- package/dist/index.js +173 -150
- package/dist/index.js.map +1 -1
- package/dist/presets/ai/index.cjs +88 -26
- package/dist/presets/ai/index.cjs.map +1 -1
- package/dist/presets/ai/index.js +14 -14
- package/dist/presets/harness/index.cjs +183 -51
- package/dist/presets/harness/index.cjs.map +1 -1
- package/dist/presets/harness/index.d.cts +15 -5
- package/dist/presets/harness/index.d.ts +15 -5
- package/dist/presets/harness/index.js +26 -26
- package/dist/presets/index.cjs +298 -101
- package/dist/presets/index.cjs.map +1 -1
- package/dist/presets/index.d.cts +2 -2
- package/dist/presets/index.d.ts +2 -2
- package/dist/presets/index.js +49 -49
- package/dist/presets/inspect/index.cjs +63 -14
- package/dist/presets/inspect/index.cjs.map +1 -1
- package/dist/presets/inspect/index.d.cts +1 -1
- package/dist/presets/inspect/index.d.ts +1 -1
- package/dist/presets/inspect/index.js +6 -6
- package/dist/presets/resilience/index.cjs +64 -44
- package/dist/presets/resilience/index.cjs.map +1 -1
- package/dist/presets/resilience/index.d.cts +12 -8
- package/dist/presets/resilience/index.d.ts +12 -8
- package/dist/presets/resilience/index.js +6 -6
- package/dist/{rate-limiter-DpVbSYdH.d.cts → rate-limiter-DC26FM8J.d.cts} +10 -1
- package/dist/{rate-limiter-CEALq4N1.d.ts → rate-limiter-DyWpwpQP.d.ts} +10 -1
- package/dist/{reactive-layout-fswlBUvX.d.ts → reactive-layout-BBBWH0V_.d.cts} +85 -4
- package/dist/{reactive-layout-fswlBUvX.d.cts → reactive-layout-BBBWH0V_.d.ts} +85 -4
- package/dist/solutions/index.cjs +239 -92
- package/dist/solutions/index.cjs.map +1 -1
- package/dist/solutions/index.d.cts +2 -2
- package/dist/solutions/index.d.ts +2 -2
- package/dist/solutions/index.js +32 -32
- package/dist/{spawnable-5mDY501F.d.cts → spawnable-B2IlW60f.d.cts} +23 -2
- package/dist/{spawnable-D3lR0oQu.d.ts → spawnable-tttFz2Nh.d.ts} +23 -2
- package/dist/testing/index.cjs +94 -0
- package/dist/testing/index.cjs.map +1 -0
- package/dist/testing/index.d.cts +59 -0
- package/dist/testing/index.d.ts +59 -0
- package/dist/testing/index.js +73 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/{timeout-U5O4ESK3.js → timeout-BEABACRP.js} +2 -2
- package/dist/utils/ai/browser.cjs.map +1 -1
- package/dist/utils/ai/browser.d.cts +2 -2
- package/dist/utils/ai/browser.d.ts +2 -2
- package/dist/utils/ai/browser.js +10 -10
- package/dist/utils/ai/browser.js.map +1 -1
- package/dist/utils/ai/index.cjs +291 -191
- package/dist/utils/ai/index.cjs.map +1 -1
- package/dist/utils/ai/index.d.cts +108 -12
- package/dist/utils/ai/index.d.ts +108 -12
- package/dist/utils/ai/index.js +23 -21
- package/dist/utils/ai/node.cjs.map +1 -1
- package/dist/utils/ai/node.d.cts +5 -5
- package/dist/utils/ai/node.d.ts +5 -5
- package/dist/utils/ai/node.js +3 -3
- package/dist/utils/ai/node.js.map +1 -1
- package/dist/utils/cqrs/index.cjs +29 -3
- package/dist/utils/cqrs/index.cjs.map +1 -1
- package/dist/utils/cqrs/index.d.cts +12 -7
- package/dist/utils/cqrs/index.d.ts +12 -7
- package/dist/utils/cqrs/index.js +2 -2
- package/dist/utils/demo-shell/index.cjs +45 -19
- package/dist/utils/demo-shell/index.cjs.map +1 -1
- package/dist/utils/demo-shell/index.d.cts +1 -1
- package/dist/utils/demo-shell/index.d.ts +1 -1
- package/dist/utils/demo-shell/index.js +2 -2
- package/dist/utils/domain-templates/index.cjs +1 -1
- package/dist/utils/domain-templates/index.cjs.map +1 -1
- package/dist/utils/domain-templates/index.js +3 -3
- package/dist/utils/graphspec/index.cjs +1 -1
- package/dist/utils/graphspec/index.cjs.map +1 -1
- package/dist/utils/graphspec/index.js +3 -3
- package/dist/utils/harness/index.cjs +16 -10
- package/dist/utils/harness/index.cjs.map +1 -1
- package/dist/utils/harness/index.js +1 -1
- package/dist/utils/index.cjs +1692 -1192
- package/dist/utils/index.cjs.map +1 -1
- package/dist/utils/index.d.cts +7 -7
- package/dist/utils/index.d.ts +7 -7
- package/dist/utils/index.js +77 -59
- package/dist/utils/inspect/index.cjs +52 -4
- package/dist/utils/inspect/index.cjs.map +1 -1
- package/dist/utils/inspect/index.d.cts +32 -3
- package/dist/utils/inspect/index.d.ts +32 -3
- package/dist/utils/inspect/index.js +4 -4
- package/dist/utils/job-queue/index.cjs +46 -9
- package/dist/utils/job-queue/index.cjs.map +1 -1
- package/dist/utils/job-queue/index.d.cts +33 -3
- package/dist/utils/job-queue/index.d.ts +33 -3
- package/dist/utils/job-queue/index.js +2 -2
- package/dist/utils/memory/index.cjs +570 -425
- package/dist/utils/memory/index.cjs.map +1 -1
- package/dist/utils/memory/index.d.cts +261 -33
- package/dist/utils/memory/index.d.ts +261 -33
- package/dist/utils/memory/index.js +10 -2
- package/dist/utils/messaging/index.cjs.map +1 -1
- package/dist/utils/messaging/index.d.cts +4 -3
- package/dist/utils/messaging/index.d.ts +4 -3
- package/dist/utils/messaging/index.js +2 -2
- package/dist/utils/orchestration/index.cjs +14 -3
- package/dist/utils/orchestration/index.cjs.map +1 -1
- package/dist/utils/orchestration/index.js +3 -3
- package/dist/utils/process/index.cjs +32 -2
- package/dist/utils/process/index.cjs.map +1 -1
- package/dist/utils/process/index.d.cts +4 -3
- package/dist/utils/process/index.d.ts +4 -3
- package/dist/utils/process/index.js +3 -3
- package/dist/utils/reactive-layout/index.cjs +184 -55
- package/dist/utils/reactive-layout/index.cjs.map +1 -1
- package/dist/utils/reactive-layout/index.d.cts +128 -3
- package/dist/utils/reactive-layout/index.d.ts +128 -3
- package/dist/utils/reactive-layout/index.js +16 -8
- package/dist/utils/reduction/index.cjs +1 -1
- package/dist/utils/reduction/index.cjs.map +1 -1
- package/dist/utils/reduction/index.js +2 -2
- package/dist/utils/resilience/index.cjs +64 -43
- package/dist/utils/resilience/index.cjs.map +1 -1
- package/dist/utils/resilience/index.d.cts +1 -1
- package/dist/utils/resilience/index.d.ts +1 -1
- package/dist/utils/resilience/index.js +5 -5
- package/dist/utils/surface/index.cjs +1 -1
- package/dist/utils/surface/index.cjs.map +1 -1
- package/dist/utils/surface/index.js +4 -4
- package/package.json +15 -3
- package/dist/chunk-3PSLNJDU.js.map +0 -1
- package/dist/chunk-42FQ27MQ.js.map +0 -1
- package/dist/chunk-4XCHZRUJ.js.map +0 -1
- package/dist/chunk-6XZYT4SW.js.map +0 -1
- package/dist/chunk-7ADWWI2T.js.map +0 -1
- package/dist/chunk-B4AKFXGE.js.map +0 -1
- package/dist/chunk-BU3SEFA5.js.map +0 -1
- package/dist/chunk-BXGZFGZ4.js.map +0 -1
- package/dist/chunk-DKNHAICT.js.map +0 -1
- package/dist/chunk-E5OZPDIW.js.map +0 -1
- package/dist/chunk-EVYY4X5A.js.map +0 -1
- package/dist/chunk-IJRR6YAI.js.map +0 -1
- package/dist/chunk-K7PDZYQE.js.map +0 -1
- package/dist/chunk-NPRP3MCV.js.map +0 -1
- package/dist/chunk-NY2PYHNC.js.map +0 -1
- package/dist/chunk-OCUDSN63.js.map +0 -1
- package/dist/chunk-PKPO3JTZ.js.map +0 -1
- package/dist/chunk-PZWISPIQ.js.map +0 -1
- package/dist/chunk-RGMTUZCL.js.map +0 -1
- package/dist/chunk-RJOG4IJU.js.map +0 -1
- package/dist/chunk-SOOKUYVM.js.map +0 -1
- package/dist/chunk-U225SKB4.js.map +0 -1
- package/dist/chunk-V4Y3TM7U.js.map +0 -1
- package/dist/chunk-VLAGJZSL.js.map +0 -1
- package/dist/chunk-W2BOPXTI.js +0 -1
- package/dist/chunk-YJ4U2D2C.js.map +0 -1
- package/dist/chunk-YXCPV26R.js.map +0 -1
- package/dist/timeout-U5O4ESK3.js.map +0 -1
- /package/dist/{chunk-J5WFUEO4.js.map → chunk-23MAWVOJ.js.map} +0 -0
- /package/dist/{chunk-2OB3CEJS.js.map → chunk-B5Y5GPD5.js.map} +0 -0
- /package/dist/{chunk-MTTRCEJT.js.map → chunk-DVTDF5OI.js.map} +0 -0
- /package/dist/{chunk-A7KV5UK4.js.map → chunk-G7H6PN7P.js.map} +0 -0
- /package/dist/{chunk-O3MT7DYI.js.map → chunk-N6MNJNHB.js.map} +0 -0
- /package/dist/{chunk-DM4OMPWK.js.map → chunk-NSA5K5G2.js.map} +0 -0
- /package/dist/{chunk-MLTPJMH6.js.map → chunk-QQYULEZL.js.map} +0 -0
- /package/dist/{chunk-4S53H2KR.js.map → chunk-SUNCHMML.js.map} +0 -0
- /package/dist/{chunk-4GYMCUDZ.js.map → chunk-T2U6N3FV.js.map} +0 -0
- /package/dist/{chunk-W2BOPXTI.js.map → timeout-BEABACRP.js.map} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/utils/process/index.ts"],"sourcesContent":["/**\n * Process Manager pattern (Phase 7 — roadmap §4.6, Audit 3 — locked 2026-04-24).\n *\n * Reactive long-running workflow primitive over CQRS event nodes.\n * Correlates events across aggregates, tracks per-instance state, supports\n * retries with backoff, and runs compensation on failure or explicit cancel.\n *\n * ## Architecture\n *\n * - Per-instance state lives in a `Map<correlationId, TState>` closure (in-memory).\n * The `_process_<name>_started` synthetic event is dispatched per `start()`\n * for an event-sourced audit trail using `correlationId` as `aggregateId`.\n * Cross-restart state recovery is opt-in via\n * `opts.persistence.stateStorage` (kv-tier per-correlationId snapshot,\n * Tier 6.5 3.5) plus an explicit `restore()` call after construction.\n * - Watched-event subscriptions are imperative (coordinator role) — each\n * watched CQRS event type is subscribed to via `entries.subscribe(...)`.\n * These are NOT reactive node edges; the process manager is intentionally\n * a coordinator that bridges reactive CQRS events into imperative instance logic.\n * - Step execution uses `fromAny` to uniformly handle sync and async handlers.\n * - Retry delays use `setTimeout` (same sanctioned pattern as `extra/resilience.ts`\n * retry helper — this primitive is a coordinator, not a reactive pipeline stage).\n * - Timer scheduling uses `fromTimer` from `extra/sources.ts` per spec §5.8.\n * - Audit log uses `createAuditLog` per Audit 2.\n *\n * @module\n */\n\nimport {\n\tbatch,\n\tCOMPLETE,\n\tDATA,\n\tERROR,\n\ttype Messages,\n\ttype Node,\n\tnode,\n\twallClockNs,\n} from \"@graphrefly/pure-ts/core\";\nimport type {\n\tAppendLogStorageTier,\n\tKvStorageTier,\n\tReactiveLogBundle,\n} from \"@graphrefly/pure-ts/extra\";\nimport {\n\tfromAny,\n\tfromIter,\n\tfromTimer,\n\tmergeMap,\n\ttype NodeInput,\n\tvalve,\n} from \"@graphrefly/pure-ts/extra\";\nimport { Graph } from \"@graphrefly/pure-ts/graph\";\nimport {\n\ttype BaseAuditRecord,\n\tcreateAuditLog,\n\tmutate,\n\ttype ReadonlyAuditLog,\n\treadonlyAuditLog,\n\tregisterCursor,\n} from \"../../base/mutation/index.js\";\nimport type { StatusValue } from \"../../base/resilience/status.js\";\nimport { firstWhere } from \"../../base/sources/settled.js\";\nimport type { CqrsEvent, CqrsEventMap, CqrsGraph } from \"../cqrs/index.js\";\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\n/**\n * Discriminated union returned by each step handler.\n *\n * - `\"success\"` — step ran cleanly; update state, optionally emit\n * side-effect events and schedule a future synthetic event. The process\n * instance stays `\"running\"`.\n * - `\"terminate\"` — workflow complete; instance moves to `\"completed\"`.\n * Process-specific extension to the canonical outcome enum.\n * - `\"failure\"` — triggers compensation; instance moves to `\"cancelled\"` /\n * `\"errored\"`.\n *\n * Field name is `outcome` (matching `cqrs.DispatchRecord.outcome` and the\n * canonical Tier 1.6.2 / 2.3 enum). `\"success\"` and `\"failure\"` are the\n * canonical values; `\"terminate\"` is the process-specific extension for\n * \"early-return success\".\n */\nexport type ProcessStepResult<TState> =\n\t| {\n\t\t\toutcome: \"success\";\n\t\t\tstate: TState;\n\t\t\temit?: readonly { type: string; payload: unknown }[];\n\t\t\tschedule?: ProcessSchedule;\n\t }\n\t| {\n\t\t\toutcome: \"terminate\";\n\t\t\tstate: TState;\n\t\t\temit?: readonly { type: string; payload: unknown }[];\n\t\t\treason?: string;\n\t }\n\t| { outcome: \"failure\"; error: unknown };\n\n/**\n * Schedule a synthetic timer event after `afterMs` milliseconds.\n * When the timer fires, the synthetic event of `eventType` is routed to the\n * matching step (if one is registered) for this correlationId.\n */\nexport type ProcessSchedule = { afterMs: number; eventType: string };\n\n/**\n * Step handler signature.\n *\n * Receives the current instance state and the triggering CQRS event.\n * Returns a {@link ProcessStepResult} — sync value, Promise, or any\n * {@link NodeInput} consumed via `fromAny`.\n */\nexport type ProcessStep<TState, EM extends CqrsEventMap, K extends keyof EM & string> = (\n\tstate: TState,\n\tevent: CqrsEvent<EM[K]>,\n) => NodeInput<ProcessStepResult<TState>>;\n\n/**\n * Compensation handler. Runs when a step returns `outcome: \"failure\"`, throws, or\n * when `cancel(correlationId)` is called on a running instance.\n *\n * Should undo any side effects performed by prior steps (refund, cancel\n * reservation, etc.). Errors thrown inside compensate are swallowed and\n * recorded in the audit log with `status: \"errored\"` to prevent cascading\n * failure loops.\n */\nexport type ProcessCompensate<TState> = (state: TState, error: unknown) => NodeInput<void>;\n\n/**\n * Audit record for a single process instance state transition.\n *\n * Every status change (start → running → completed / errored / cancelled)\n * appends one record. `correlationId` is the stable process key.\n *\n * Extends {@link BaseAuditRecord} so records carry `t_ns` / `seq` /\n * `handlerVersion` from the cross-cutting Audit 2 schema.\n */\nexport interface ProcessInstance<TState> extends BaseAuditRecord {\n\t/** Stable correlation key that identifies this process instance. */\n\treadonly correlationId: string;\n\t/** Most-recent instance state at this transition. */\n\treadonly state: TState;\n\t/** Current lifecycle status after this transition. */\n\treadonly status: \"running\" | \"completed\" | \"errored\" | \"cancelled\";\n\t/** Wall-clock nanoseconds when `start()` was called. */\n\treadonly startedAt: number;\n\t/** Wall-clock nanoseconds of this transition. */\n\treadonly updatedAt: number;\n\t/** Handler version stamped at transition time (Audit 5). */\n\treadonly handlerVersion?: { id: string; version: string | number };\n\t/** Optional human-readable reason for cancellation. Present only on `\"cancelled\"` records produced by `cancel()`. */\n\treadonly reason?: string;\n}\n\n/**\n * Recommended `keyOf` for storage tiers keyed by correlationId (Audit 2).\n */\nexport const processInstanceKeyOf = <TState>(i: ProcessInstance<TState>): string => i.correlationId;\n\n/**\n * Per-correlationId state snapshot persisted via\n * {@link ProcessManagerOpts.persistence.stateStorage} (Tier 6.5 3.5,\n * 2026-04-29). Captures the running instance's current state plus\n * lifecycle metadata so a fresh `processManager` can resume in-flight\n * workflows after restart via {@link ProcessManagerResult.restore}.\n *\n * Terminal records (`status` ∈ `\"completed\" | \"errored\" | \"cancelled\"`)\n * are deleted from the kv tier on transition — only running instances\n * persist between restarts.\n */\nexport interface ProcessStateSnapshot<TState> {\n\treadonly correlationId: string;\n\treadonly state: TState;\n\treadonly status: \"running\" | \"completed\" | \"errored\" | \"cancelled\";\n\treadonly startedAt: number;\n\treadonly updatedAt: number;\n\treadonly handlerVersion?: { id: string; version: string | number };\n}\n\n/** Recommended `keyOf` for `KvStorageTier<ProcessStateSnapshot<...>>`. */\nexport const processStateKeyOf = <TState>(s: ProcessStateSnapshot<TState>): string =>\n\ts.correlationId;\n\n/**\n * Options for {@link processManager}.\n */\nexport interface ProcessManagerOpts<TState, EM extends CqrsEventMap> {\n\t/** Initial state value for every new process instance. */\n\treadonly initial: TState;\n\t/** CQRS event types to watch for correlation routing. */\n\treadonly watching: readonly (keyof EM & string)[];\n\t/**\n\t * Per-event-type step handlers. A step is invoked when a watched event's\n\t * `correlationId` matches a running instance and the event type is in\n\t * `steps`. Events with no matching step are silently ignored.\n\t */\n\treadonly steps: { [K in keyof EM & string]?: ProcessStep<TState, EM, K> };\n\t/**\n\t * Optional compensation handler. Runs on step `outcome: \"failure\"` / step throw\n\t * and on explicit `cancel()`. If omitted, instances fail silently with\n\t * status `\"errored\"` instead of `\"cancelled\"`.\n\t */\n\treadonly compensate?: ProcessCompensate<TState>;\n\t/**\n\t * Optional predicate called after each `\"success\"` step. When it returns\n\t * `true`, the instance is moved to `\"completed\"` immediately without\n\t * waiting for a `\"terminate\"` step result.\n\t */\n\treadonly isTerminal?: (state: TState) => boolean;\n\t/**\n\t * Maximum number of retry attempts after a step throws (not counting the\n\t * first attempt). Default: `0` (no retry — fail immediately on throw).\n\t */\n\treadonly retryMax?: number;\n\t/**\n\t * Per-retry backoff delays in milliseconds. `backoffMs[i]` is the delay\n\t * before attempt `i + 1`. If fewer entries than `retryMax`, the last entry\n\t * is repeated. Default: `[0]` (no delay).\n\t *\n\t * **Implementation note:** retry delays are implemented with `setTimeout`\n\t * (same sanctioned exception as `extra/resilience.ts`). This is a\n\t * coordinator-layer primitive — `fromTimer` would require subscribing to\n\t * an additional node per attempt, which would leak timer nodes without a\n\t * clear disposal scope.\n\t */\n\treadonly backoffMs?: readonly number[];\n\t/** Handler version tag stamped onto audit records (Audit 5). */\n\treadonly handlerVersion?: { id: string; version: string | number };\n\t/**\n\t * When `true`, do NOT auto-restore on construction. The caller must invoke\n\t * {@link ProcessManagerResult.restore} explicitly to load persisted\n\t * snapshots and arm watch dispatch.\n\t *\n\t * **Default `false`:** the factory kicks off restoration immediately so\n\t * watch dispatch arms as soon as snapshots have loaded (or instantly when\n\t * no `stateStorage` tier is configured). Until restoration completes,\n\t * watched events accumulate at the source but are valve-blocked from\n\t * reaching the per-instance step pipeline (B5 — locked 2026-05-01).\n\t */\n\treadonly deferRestore?: boolean;\n\t/**\n\t * Maximum number of concurrent `tier.load(key)` calls during restore.\n\t *\n\t * The restore pipeline streams keys from `tier.list()` through\n\t * `mergeMap`, which by default subscribes to inner sources unbounded\n\t * (parallel up to the number of keys). For storage tiers with large\n\t * persisted-instance counts (10K+), unbounded concurrency can exhaust\n\t * file handles, connection pools, or the backend's concurrent-request\n\t * budget. This option caps the in-flight load count.\n\t *\n\t * **Default `8`** (D2 lock 2026-05-01). Set higher for backends with\n\t * generous concurrency budgets; set to `Number.POSITIVE_INFINITY` for\n\t * the prior unbounded behavior.\n\t */\n\treadonly restoreConcurrency?: number;\n\t/** Optional persistence wiring (Audit 4). */\n\treadonly persistence?: {\n\t\t/**\n\t\t * Wire the per-process synthetic state event stream to append-log tiers.\n\t\t * Reuses `CqrsGraph.attachEventStorage` so events persist across restarts.\n\t\t */\n\t\teventStorage?: readonly AppendLogStorageTier<CqrsEvent>[];\n\t\t/**\n\t\t * Wire per-correlationId state snapshots to kv tiers (Tier 6.5 3.5,\n\t\t * 2026-04-29). Each `start()` and step transition writes the running\n\t\t * instance's state under its `correlationId`; terminal transitions\n\t\t * (`completed` / `errored` / `cancelled`) `delete` the key. After\n\t\t * restart, callers invoke {@link ProcessManagerResult.restore} to\n\t\t * reload running instances from the first tier.\n\t\t *\n\t\t * Uses {@link KvStorageTier} (not snapshot tier) because per-instance\n\t\t * state is N records keyed by correlationId, not a single global\n\t\t * snapshot. {@link processStateKeyOf} is the recommended `keyOf`\n\t\t * (already aligned with the kv tier's `save(key, value)` shape).\n\t\t *\n\t\t * Terminal records are NOT preserved — historical lifecycle is the\n\t\t * audit log's job. State persistence covers crash-recovery only.\n\t\t */\n\t\tstateStorage?: readonly KvStorageTier<ProcessStateSnapshot<TState>>[];\n\t};\n}\n\n/**\n * Result handle returned by {@link processManager}.\n */\nexport interface ProcessManagerResult<TState> {\n\t/**\n\t * Reactive audit log of every process instance state transition.\n\t * Every `start()`, step result, retry, cancellation, and compensation\n\t * appends a {@link ProcessInstance} record.\n\t */\n\treadonly instances: ReactiveLogBundle<ProcessInstance<TState>>;\n\t/**\n\t * Read-only view of {@link instances} (Audit 2 `.audit` duplication\n\t * convention; M7 — cannot mutate the canonical log via the alias).\n\t */\n\treadonly audit: ReadonlyAuditLog<ProcessInstance<TState>>;\n\t/**\n\t * Start a new process instance identified by `correlationId`.\n\t *\n\t * Emits a synthetic `_process_<name>_started` event into the CQRS graph\n\t * with `correlationId` as `aggregateId` so per-aggregate streams record\n\t * the process lifecycle. If the correlationId already has an active\n\t * (running) instance, this call is a no-op (idempotent).\n\t *\n\t * @param correlationId - Stable key for this workflow instance.\n\t * @param initialPayload - Optional payload carried on the start event.\n\t */\n\tstart(correlationId: string, initialPayload?: unknown): void;\n\t/**\n\t * Cancel a running instance by correlationId.\n\t *\n\t * Triggers the `compensate` handler (if configured), then marks the\n\t * instance as `\"cancelled\"`. If the instance is not running, this is\n\t * a no-op.\n\t *\n\t * @param correlationId - Instance to cancel.\n\t * @param reason - Optional human-readable reason recorded in the audit log.\n\t */\n\tcancel(correlationId: string, reason?: string): void;\n\t/**\n\t * Synchronous read of the current in-memory state for a correlationId.\n\t * Returns `undefined` if the instance does not exist or has terminated.\n\t */\n\tgetState(correlationId: string): TState | undefined;\n\t/**\n\t * Reactive lifecycle of the restore pipeline. Typed as the central\n\t * {@link StatusValue} enum (`\"pending\" | \"running\" | \"completed\" | \"errored\"`);\n\t * the process-manager restore state machine currently emits the `\"pending\"`\n\t * and `\"completed\"` literals only — `\"running\"` / `\"errored\"` reserved\n\t * for future fine-grained restore observability. Starts at `\"pending\"`,\n\t * flips to `\"completed\"` once snapshot loads complete (or immediately when\n\t * no `stateStorage` is configured). On {@link dispose}, the node receives\n\t * TEARDOWN via the standard subgraph teardown cascade — there is no\n\t * `\"disposed\"` literal; consumers detect tear-down via subscription\n\t * COMPLETE on {@link dispose}. Watched events are valve-gated on this\n\t * node: dispatch is blocked while `restoreState !== \"completed\"`.\n\t *\n\t * Exposed for observability and tests. Subscribers can compose\n\t * `derived([restoreState], …)` to build their own gates / readouts.\n\t */\n\treadonly restoreState: Node<StatusValue>;\n\t/**\n\t * Trigger restoration of running instances from the first\n\t * {@link ProcessManagerOpts.persistence.stateStorage} tier (Tier 6.5\n\t * 3.5, 2026-04-29). Loads every record in the tier reactively and\n\t * re-hydrates `instanceStates` / `activeInstances` / `startedAt` for\n\t * any record whose `status === \"running\"`. Terminal records, if any\n\t * persisted before delete fired, are silently skipped.\n\t *\n\t * **Reactive composition (B5 — locked 2026-05-01):** internally,\n\t * `tier.list()` and `tier.load()` are wrapped in `fromAny` sources\n\t * (handles sync values, Promises, async iterables, and existing Nodes\n\t * uniformly per `~/src/graphrefly/COMPOSITION-GUIDE.md` §3 source\n\t * bridging); a `mergeMap` flattens per-key load results; an `effect`\n\t * populates closure state and flips {@link restoreState} to\n\t * `\"completed\"` on the `COMPLETE` boundary. No `await` inside the\n\t * reactive interior — the single async boundary is the returned\n\t * `Promise<void>`, which resolves when {@link restoreState} transitions\n\t * to `\"completed\"` OR when {@link dispose} tears down the restore node\n\t * (in which case `firstWhere`'s COMPLETE-rejection is swallowed and\n\t * the promise resolves to `undefined`).\n\t *\n\t * Idempotent — calling twice subscribes to the same restore pipeline\n\t * and resolves on the same gate flip. No-op when no `stateStorage`\n\t * tier is configured OR the first tier lacks a `list?` method:\n\t * `restoreState` flips to `\"completed\"` immediately so watches can arm.\n\t *\n\t * **Auto-restore default.** With `deferRestore: false` (the default),\n\t * the factory invokes `restore()` once at construction so callers do\n\t * not need to remember to wire it. Pass `deferRestore: true` to\n\t * suppress auto-restore and call `restore()` manually.\n\t */\n\trestore(): Promise<void>;\n\t/**\n\t * Release all watched-event subscriptions and stop processing new events.\n\t *\n\t * After `dispose()`, subsequent `start()` and `cancel()` calls are no-ops.\n\t * In-flight async steps complete naturally; no new steps are dispatched.\n\t *\n\t * Tears down the {@link restoreState} node via the standard subgraph\n\t * teardown cascade. Any pending `restore()` Promise resolves (the\n\t * COMPLETE-rejection from `firstWhere` is swallowed at the API edge);\n\t * the watch valve closes via TEARDOWN propagation; no further dispatch\n\t * even if a `fromAny(tier.load…)` would resolve later (the per-key\n\t * load source's cleanup sets `settled = true`, dropping the late DATA).\n\t */\n\tdispose(): void;\n}\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Materialise a `NodeInput<T>` into a Promise.\n * Uses `fromAny` to normalise Node / Promise / iterable / scalar, then\n * collects the first DATA message.\n *\n * - If `input` is `null` or `undefined`, resolves immediately with `undefined`\n * (skips constructing a `fromAny` source).\n * - On COMPLETE without prior DATA, resolves with `undefined` (supports `void`\n * compensate handlers whose `NodeInput<void>` delivers COMPLETE only).\n *\n * Implementation note: `fromAny` over a scalar (e.g. `void` / `undefined`) or\n * sync iterable delivers DATA synchronously inside `n.subscribe()`, BEFORE the\n * `subscribe()` call returns. We therefore use `let unsub` (not `const`) and\n * avoid calling `unsub()` until after `subscribe()` has returned — deferring\n * the cleanup to a microtask via `Promise.resolve().then(unsub)` to sidestep\n * the Temporal Dead Zone.\n */\nfunction toPromise<T>(input: NodeInput<T>): Promise<T> {\n\t// Short-circuit: null/undefined input resolves immediately.\n\tif (input == null) return Promise.resolve(undefined as T);\n\n\tconst n = fromAny<T>(input);\n\treturn new Promise<T>((resolve, reject) => {\n\t\tlet settled = false;\n\t\t// `let` instead of `const` so that synchronous DATA delivery during\n\t\t// n.subscribe() (before the assignment completes) doesn't hit TDZ.\n\t\tlet unsub: (() => void) | undefined;\n\t\tconst cleanup = () => {\n\t\t\tif (unsub) {\n\t\t\t\tunsub();\n\t\t\t}\n\t\t};\n\t\tunsub = n.subscribe((msgs) => {\n\t\t\tif (settled) return;\n\t\t\tfor (const m of msgs) {\n\t\t\t\tif (m[0] === DATA) {\n\t\t\t\t\tsettled = true;\n\t\t\t\t\t// Defer cleanup to after the subscribe() call returns (TDZ-safe).\n\t\t\t\t\tPromise.resolve().then(cleanup);\n\t\t\t\t\tresolve(m[1] as T);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (m[0] === ERROR) {\n\t\t\t\t\tsettled = true;\n\t\t\t\t\tPromise.resolve().then(cleanup);\n\t\t\t\t\treject(m[1] as unknown);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (m[0] === COMPLETE) {\n\t\t\t\t\t// COMPLETE without prior DATA — resolve with undefined.\n\t\t\t\t\t// Supports void compensate handlers that return without emitting DATA.\n\t\t\t\t\tsettled = true;\n\t\t\t\t\tPromise.resolve().then(cleanup);\n\t\t\t\t\tresolve(undefined as T);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t});\n}\n\n/** Run `step(state, event)` with retry logic. Returns the step result. */\nasync function runWithRetry<TState>(\n\tstep: (state: TState, event: CqrsEvent) => NodeInput<ProcessStepResult<TState>>,\n\tstate: TState,\n\tevent: CqrsEvent,\n\tretryMax: number,\n\tbackoffMs: readonly number[],\n): Promise<ProcessStepResult<TState>> {\n\tlet lastError: unknown;\n\tfor (let attempt = 0; attempt <= retryMax; attempt++) {\n\t\tif (attempt > 0) {\n\t\t\t// Sanctioned setTimeout for retry backoff in coordinator primitives.\n\t\t\t// Same pattern as extra/resilience.ts retry implementation.\n\t\t\tconst delayMs = backoffMs[Math.min(attempt - 1, backoffMs.length - 1)] ?? 0;\n\t\t\tif (delayMs > 0) {\n\t\t\t\tawait new Promise<void>((r) => setTimeout(r, delayMs));\n\t\t\t}\n\t\t}\n\t\ttry {\n\t\t\tconst result = await toPromise(step(state, event));\n\t\t\treturn result;\n\t\t} catch (err) {\n\t\t\tlastError = err;\n\t\t\t// If we've exhausted retries, fall through to return a fail result.\n\t\t}\n\t}\n\treturn { outcome: \"failure\", error: lastError };\n}\n\n// ---------------------------------------------------------------------------\n// processManager factory\n// ---------------------------------------------------------------------------\n\n/**\n * Create a process manager that coordinates long-running reactive workflows\n * over a {@link CqrsGraph}.\n *\n * Process instances are identified by `correlationId`. Events from the watched\n * event types are routed to per-instance step handlers when the event's\n * `correlationId` matches a running instance.\n *\n * ```ts\n * const app = cqrs<{ orderPlaced: { orderId: string }; paymentReceived: { amount: number } }>(\"orders\");\n *\n * const pm = processManager(app, \"fulfillment\", {\n * initial: { step: \"awaiting-payment\", total: 0 },\n * watching: [\"orderPlaced\", \"paymentReceived\"],\n * steps: {\n * orderPlaced(state, event) {\n * return { outcome: \"success\", state: { ...state, orderId: event.payload.orderId } };\n * },\n * paymentReceived(state, event) {\n * return { outcome: \"terminate\", state: { ...state, total: event.payload.amount } };\n * },\n * },\n * compensate(state, _error) {\n * // undo reservation, issue refund, etc.\n * },\n * retryMax: 2,\n * backoffMs: [100, 500],\n * });\n *\n * pm.start(\"order-123\");\n * app.dispatch(\"orderPlaced\", { orderId: \"order-123\" }, { correlationId: \"order-123\" });\n * ```\n *\n * @param cqrsGraph - The CQRS graph whose event streams the manager watches.\n * @param name - Stable identifier for this process type; used for the\n * synthetic event-type prefix `_process_<name>_*`. Currently emits\n * `_process_<name>_started` per `start()`; the prefix is reserved for\n * future `_state` / `_timer` channels.\n * @param opts - Configuration: initial state, watched events, steps, retry,\n * compensation, and optional persistence.\n * @returns {@link ProcessManagerResult} with `instances` audit log and\n * `start`, `cancel`, `getState` imperative controls.\n *\n * @category patterns\n */\nexport function processManager<TState, EM extends CqrsEventMap = Record<string, unknown>>(\n\tcqrsGraph: CqrsGraph<EM>,\n\tname: string,\n\topts: ProcessManagerOpts<TState, EM>,\n): ProcessManagerResult<TState> {\n\tconst retryMax = opts.retryMax ?? 0;\n\tconst backoffMs: readonly number[] = opts.backoffMs ?? [0];\n\tconst retainedLimit = 1024;\n\n\t// ── Per-instance in-memory state ──────────────────────────────────────\n\t// Map from correlationId → current TState for running instances.\n\t// Imperative coordinator state — documented pattern for this primitive.\n\tconst instanceStates = new Map<string, TState>();\n\t// Track which instances are \"active\" (running) to prevent double-start\n\t// and to gate step delivery.\n\tconst activeInstances = new Set<string>();\n\t// Track startedAt per instance.\n\tconst startedAt = new Map<string, number>();\n\n\t// ── Audit log + seq cursor ────────────────────────────────────────────\n\t// EH-16 (Tier 6.5 3.3, 2026-04-29): the audit log + seq cursor are\n\t// mounted under a per-instance child Graph (`__processManagers__/<name>`)\n\t// rather than directly under `cqrsGraph._nodes`. `dispose()` then calls\n\t// `cqrsGraph.remove(...)` to unmount the subgraph cleanly via the\n\t// existing mount/removeMount lifecycle — no leaked nodes after repeated\n\t// create/dispose cycles. Pre-1.0 path-schema change: paths shift from\n\t// `${name}_process_instances` / `${name}_process_seq` (top-level) to\n\t// `__processManagers__/${name}::instances` / `::seq` (mounted).\n\tconst mountName = `__processManagers__/${name}`;\n\tconst subgraph = new Graph(name);\n\ttry {\n\t\tcqrsGraph.mount(mountName, subgraph);\n\t} catch (err) {\n\t\t// `Graph.mount` throws if the mount name is in use; surface a\n\t\t// processManager-specific message so callers see actionable context.\n\t\tconst detail = err instanceof Error ? err.message : String(err);\n\t\tthrow new Error(\n\t\t\t`processManager: name \"${name}\" is already in use on this CQRS graph ` +\n\t\t\t\t`(mount path \"${mountName}\" collides). Call .dispose() on the prior ` +\n\t\t\t\t`manager OR pick a different name before re-creating. (${detail})`,\n\t\t);\n\t}\n\n\tconst instances = createAuditLog<ProcessInstance<TState>>({\n\t\tname: \"instances\",\n\t\tretainedLimit,\n\t\tgraph: subgraph,\n\t});\n\n\t// Tier 8 γ-7-A (2026-04-28): seq cursor promoted from `let seq = 0` closure\n\t// to a `state(0)` node mounted on the per-process subgraph (visible in\n\t// `describe()` at `__processManagers__/<name>::seq`). The audit-record\n\t// stamping routes through `mutate` for centralized freeze + seq\n\t// advance + `handlerVersion` stamping + batch-frame rollback. The batch\n\t// frame closes EH-17 (re-entrancy hazard).\n\tconst seqCursor = registerCursor(subgraph, \"seq\", 0);\n\n\t// D4 (qa lock): `freeze: true` so step-handler-supplied state values\n\t// captured into audit records cannot be mutated post-record. Process\n\t// states are typically small workflow records (an order ID + a few\n\t// flags), so the `deepFreeze` tax is negligible — the safety vs. mutation\n\t// trade-off favors freeze. (The 768-dim-vector concern that motivates\n\t// `freeze: false` in memory primitives doesn't apply here.)\n\tconst appendRecord = mutate<\n\t\t[string, TState, ProcessInstance<TState>[\"status\"], string | undefined],\n\t\tvoid,\n\t\tProcessInstance<TState>\n\t>(\n\t\t// No closure-state mutation in the action — the audit-record append IS\n\t\t// the effect, performed by the framework via `onSuccessRecord`.\n\t\t() => undefined,\n\t\t{\n\t\t\tframe: \"transactional\",\n\t\t\tlog: instances,\n\t\t\tseq: seqCursor,\n\t\t\tfreeze: true,\n\t\t\t...(opts.handlerVersion !== undefined ? { handlerVersion: opts.handlerVersion } : {}),\n\t\t\tonSuccessRecord: ([correlationId, state, status, reason], _r, { t_ns, seq }) => ({\n\t\t\t\tcorrelationId,\n\t\t\t\tstate,\n\t\t\t\tstatus,\n\t\t\t\tstartedAt: startedAt.get(correlationId) ?? t_ns,\n\t\t\t\tupdatedAt: t_ns,\n\t\t\t\tt_ns,\n\t\t\t\tseq: seq ?? 0,\n\t\t\t\t...(reason !== undefined ? { reason } : {}),\n\t\t\t}),\n\t\t},\n\t);\n\n\t// ── State-snapshot persistence (Tier 6.5 3.5) ─────────────────────────\n\tconst stateStorageTiers = opts.persistence?.stateStorage ?? [];\n\n\t/**\n\t * Build the snapshot payload + iterate tiers. Returns the iterator over\n\t * tiers so the caller decides whether sync throws propagate (B4 — start\n\t * path inside mutate) or are swallowed (step path, fire-and-forget).\n\t */\n\tconst buildSnapshot = (\n\t\tcorrelationId: string,\n\t\tstatus: ProcessStateSnapshot<TState>[\"status\"],\n\t): ProcessStateSnapshot<TState> | undefined => {\n\t\tconst stateValue = instanceStates.get(correlationId);\n\t\tif (stateValue === undefined) return undefined;\n\t\treturn {\n\t\t\tcorrelationId,\n\t\t\tstate: stateValue,\n\t\t\tstatus,\n\t\t\tstartedAt: startedAt.get(correlationId) ?? wallClockNs(),\n\t\t\tupdatedAt: wallClockNs(),\n\t\t\t...(opts.handlerVersion !== undefined ? { handlerVersion: opts.handlerVersion } : {}),\n\t\t};\n\t};\n\n\t/**\n\t * Best-effort persistence (used by step transitions). Sync throws are\n\t * swallowed so persistence failures do NOT poison reactive step dispatch.\n\t * Async rejections are caught at the Promise boundary.\n\t */\n\tconst persistState = (\n\t\tcorrelationId: string,\n\t\tstatus: ProcessStateSnapshot<TState>[\"status\"],\n\t): void => {\n\t\tif (stateStorageTiers.length === 0) return;\n\t\tconst snapshot = buildSnapshot(correlationId, status);\n\t\tif (snapshot === undefined) return;\n\t\tfor (const tier of stateStorageTiers) {\n\t\t\ttry {\n\t\t\t\tconst r = tier.save(correlationId, snapshot);\n\t\t\t\t// Tier may return Promise — fire-and-forget. Storage errors\n\t\t\t\t// surface via the tier's own onError plumbing (Tier 4 storage).\n\t\t\t\tif (r != null && typeof (r as Promise<void>).then === \"function\") {\n\t\t\t\t\t(r as Promise<void>).catch(() => undefined);\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t// best-effort; persistence failures don't block step execution\n\t\t\t}\n\t\t}\n\t};\n\n\t/**\n\t * Throwing variant (B4 — used inside `startInternal`'s mutate\n\t * action body so a sync-throwing tier rolls back the audit-log append +\n\t * seq cursor advance). Async rejections from `tier.save()` still cannot\n\t * unwind the synchronous batch frame — that's a known limitation of\n\t * Promise-returning storage; sync-throwing tiers (the actual D2 hazard)\n\t * are fully covered.\n\t */\n\tconst persistStateThrowing = (\n\t\tcorrelationId: string,\n\t\tstatus: ProcessStateSnapshot<TState>[\"status\"],\n\t): void => {\n\t\tif (stateStorageTiers.length === 0) return;\n\t\tconst snapshot = buildSnapshot(correlationId, status);\n\t\tif (snapshot === undefined) return;\n\t\tfor (const tier of stateStorageTiers) {\n\t\t\tconst r = tier.save(correlationId, snapshot);\n\t\t\tif (r != null && typeof (r as Promise<void>).then === \"function\") {\n\t\t\t\t(r as Promise<void>).catch(() => undefined);\n\t\t\t}\n\t\t}\n\t};\n\tconst removeState = (correlationId: string): void => {\n\t\tif (stateStorageTiers.length === 0) return;\n\t\tfor (const tier of stateStorageTiers) {\n\t\t\tif (!tier.delete) continue;\n\t\t\ttry {\n\t\t\t\tconst r = tier.delete(correlationId);\n\t\t\t\tif (r != null && typeof (r as Promise<void>).then === \"function\") {\n\t\t\t\t\t(r as Promise<void>).catch(() => undefined);\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t// best-effort\n\t\t\t}\n\t\t}\n\t};\n\n\t// ── Synthetic event helpers ───────────────────────────────────────────\n\tconst startedEventType = `_process_${name}_started`;\n\n\t// Pre-register the started event stream so it appears in describe().\n\t// Side-effect events (result.emit) dispatch using their own declared event type\n\t// via _appendEvent directly — no separate state event stream needed.\n\tcqrsGraph.event(startedEventType);\n\n\t// Wire persistence: event storage for synthetic state stream.\n\tif (opts.persistence?.eventStorage) {\n\t\tcqrsGraph.attachEventStorage(opts.persistence.eventStorage);\n\t}\n\n\t// ── Compensation helper ───────────────────────────────────────────────\n\tasync function runCompensate(\n\t\tcorrelationId: string,\n\t\tstate: TState,\n\t\terror: unknown,\n\t\treason?: string,\n\t): Promise<void> {\n\t\t// Eagerly remove from active state BEFORE any await so that concurrent\n\t\t// cancel() calls or in-flight step completions that arrive while we are\n\t\t// awaiting the compensate handler find the instance already gone and\n\t\t// exit early (C1 — double-compensation race fix).\n\t\tactiveInstances.delete(correlationId);\n\t\tinstanceStates.delete(correlationId);\n\t\tstartedAt.delete(correlationId);\n\n\t\tif (opts.compensate) {\n\t\t\ttry {\n\t\t\t\tawait toPromise(opts.compensate(state, error) as NodeInput<void>);\n\t\t\t\tappendRecord(correlationId, state, \"cancelled\", reason);\n\t\t\t\tremoveState(correlationId);\n\t\t\t} catch (_compErr) {\n\t\t\t\t// Compensation itself failed — still mark as errored so instance\n\t\t\t\t// doesn't stay in limbo. Swallow error to prevent cascading.\n\t\t\t\tappendRecord(correlationId, state, \"errored\", undefined);\n\t\t\t\tremoveState(correlationId);\n\t\t\t}\n\t\t} else {\n\t\t\tappendRecord(correlationId, state, \"errored\", undefined);\n\t\t\tremoveState(correlationId);\n\t\t}\n\t}\n\n\t// ── Step result handler ───────────────────────────────────────────────\n\tasync function handleStepResult(\n\t\tcorrelationId: string,\n\t\tresult: ProcessStepResult<TState>,\n\t): Promise<void> {\n\t\tif (!activeInstances.has(correlationId)) return; // cancelled during async step\n\n\t\tif (result.outcome === \"failure\") {\n\t\t\t// Capture state before eager delete (C1 — step-fail eager-delete).\n\t\t\tconst state = instanceStates.get(correlationId) ?? opts.initial;\n\t\t\t// runCompensate handles the eager delete; the early-exit guard above\n\t\t\t// ensures we won't double-compensate for an already-inactive instance.\n\t\t\tawait runCompensate(correlationId, state, result.error);\n\t\t\treturn;\n\t\t}\n\n\t\tif (result.outcome === \"success\") {\n\t\t\tinstanceStates.set(correlationId, result.state);\n\n\t\t\t// Emit side-effect CQRS events.\n\t\t\tif (result.emit) {\n\t\t\t\tfor (const ev of result.emit) {\n\t\t\t\t\t// Dispatch via _appendEvent is internal. Use a synthetic command\n\t\t\t\t\t// channel via the public `dispatch` API by pre-registering a\n\t\t\t\t\t// passthrough command, OR emit directly via an internal event.\n\t\t\t\t\t// Strategy: use event stream directly — the process manager is\n\t\t\t\t\t// an internal coordinator allowed to call internal CQRS APIs.\n\t\t\t\t\t// We use the synthetic stateEventType to carry side-effect events\n\t\t\t\t\t// so they appear in the aggregate stream.\n\t\t\t\t\ttry {\n\t\t\t\t\t\t// Emit side-effect events using the process state event stream with\n\t\t\t\t\t\t// the correlationId as aggregateId, but use the declared event type\n\t\t\t\t\t\t// as the type field for downstream sagas/projections to react to.\n\t\t\t\t\t\t// We do this by directly dispatching into the CQRS graph via the\n\t\t\t\t\t\t// dedicated per-process synthetic event channel.\n\t\t\t\t\t\t(\n\t\t\t\t\t\t\tcqrsGraph as unknown as {\n\t\t\t\t\t\t\t\t_appendEvent(\n\t\t\t\t\t\t\t\t\tname: string,\n\t\t\t\t\t\t\t\t\tpayload: unknown,\n\t\t\t\t\t\t\t\t\textra?: { correlationId?: string; aggregateId?: string },\n\t\t\t\t\t\t\t\t): void;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t)._appendEvent(ev.type, ev.payload, {\n\t\t\t\t\t\t\tcorrelationId,\n\t\t\t\t\t\t\taggregateId: correlationId,\n\t\t\t\t\t\t});\n\t\t\t\t\t} catch (_emitErr) {\n\t\t\t\t\t\t// Non-fatal: side-effect event emission failures are not\n\t\t\t\t\t\t// step-fatal (they are fire-and-forget coordination signals).\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tappendRecord(correlationId, result.state, \"running\", undefined);\n\t\t\tpersistState(correlationId, \"running\");\n\n\t\t\t// Check isTerminal predicate.\n\t\t\tif (opts.isTerminal?.(result.state)) {\n\t\t\t\tactiveInstances.delete(correlationId);\n\t\t\t\tinstanceStates.delete(correlationId);\n\t\t\t\tstartedAt.delete(correlationId); // M3 — cleanup startedAt on isTerminal terminate\n\t\t\t\tappendRecord(correlationId, result.state, \"completed\", undefined);\n\t\t\t\tremoveState(correlationId);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Handle schedule: fire synthetic event after delay via fromTimer.\n\t\t\tif (result.schedule) {\n\t\t\t\tconst { afterMs, eventType } = result.schedule;\n\t\t\t\t// fromTimer per spec §5.8 — reactive timer source.\n\t\t\t\t// Subscribe to fire once and deliver the synthetic event.\n\t\t\t\t// M6: use `let timerUnsub` + TDZ guard to avoid referencing the\n\t\t\t\t// variable before its assignment if the callback fires synchronously.\n\t\t\t\tlet timerUnsub: (() => void) | undefined;\n\t\t\t\tconst timerNode = fromTimer(afterMs);\n\t\t\t\tconst timerCb: Parameters<typeof timerNode.subscribe>[0] = (msgs) => {\n\t\t\t\t\tfor (const m of msgs) {\n\t\t\t\t\t\tif (m[0] === DATA) {\n\t\t\t\t\t\t\t// TDZ guard: if subscribe() hasn't returned yet, defer cleanup.\n\t\t\t\t\t\t\tif (timerUnsub) {\n\t\t\t\t\t\t\t\ttimerUnsub();\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t// fromTimer is async (setTimeout-backed) so this path is\n\t\t\t\t\t\t\t\t// never hit in practice, but guard for safety.\n\t\t\t\t\t\t\t\tqueueMicrotask(() => timerUnsub?.());\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (!activeInstances.has(correlationId)) return;\n\t\t\t\t\t\t\tconst currentState = instanceStates.get(correlationId);\n\t\t\t\t\t\t\tif (currentState === undefined) return;\n\t\t\t\t\t\t\tconst step = (\n\t\t\t\t\t\t\t\topts.steps as unknown as Record<string, ProcessStep<TState, EM, string>>\n\t\t\t\t\t\t\t)[eventType];\n\t\t\t\t\t\t\tif (!step) return;\n\t\t\t\t\t\t\tconst syntheticEvent: CqrsEvent = {\n\t\t\t\t\t\t\t\ttype: eventType,\n\t\t\t\t\t\t\t\t// m5: null payload (not undefined) to avoid soft §1.2 risk.\n\t\t\t\t\t\t\t\t// seq: Number.NaN — sentinel for synthetic events that do not\n\t\t\t\t\t\t\t\t// participate in cross-event ordering.\n\t\t\t\t\t\t\t\tpayload: null,\n\t\t\t\t\t\t\t\ttimestampNs: wallClockNs(),\n\t\t\t\t\t\t\t\tseq: Number.NaN,\n\t\t\t\t\t\t\t\tcorrelationId,\n\t\t\t\t\t\t\t\taggregateId: correlationId,\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\tdispatchStep(correlationId, step, currentState, syntheticEvent);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t\ttimerUnsub = timerNode.subscribe(timerCb);\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tif (result.outcome === \"terminate\") {\n\t\t\tinstanceStates.set(correlationId, result.state);\n\t\t\t// Emit side-effect events.\n\t\t\tif (result.emit) {\n\t\t\t\tfor (const ev of result.emit) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\t(\n\t\t\t\t\t\t\tcqrsGraph as unknown as {\n\t\t\t\t\t\t\t\t_appendEvent(\n\t\t\t\t\t\t\t\t\tname: string,\n\t\t\t\t\t\t\t\t\tpayload: unknown,\n\t\t\t\t\t\t\t\t\textra?: { correlationId?: string; aggregateId?: string },\n\t\t\t\t\t\t\t\t): void;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t)._appendEvent(ev.type, ev.payload, {\n\t\t\t\t\t\t\tcorrelationId,\n\t\t\t\t\t\t\taggregateId: correlationId,\n\t\t\t\t\t\t});\n\t\t\t\t\t} catch (_emitErr) {\n\t\t\t\t\t\t// non-fatal\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tactiveInstances.delete(correlationId);\n\t\t\tinstanceStates.delete(correlationId);\n\t\t\tstartedAt.delete(correlationId); // M3 — cleanup startedAt on terminate\n\t\t\tappendRecord(correlationId, result.state, \"completed\", undefined);\n\t\t\tremoveState(correlationId);\n\t\t}\n\t}\n\n\t// ── Step dispatch ─────────────────────────────────────────────────────\n\t// C2: Per-correlationId in-flight serialization map.\n\t// Multiple events for the same correlationId in one DATA wave both read the\n\t// same instanceStates snapshot if dispatched concurrently. Serializing via a\n\t// promise chain ensures the second step sees the first step's written state.\n\t// Cross-correlationId events still parallelize (separate map entries).\n\tconst inFlight = new Map<string, Promise<void>>();\n\n\tfunction dispatchStep(\n\t\tcorrelationId: string,\n\t\tstep: ProcessStep<TState, EM, string>,\n\t\t_state: TState, // C2: state is re-read inside the serialized closure; this param is kept for call-site clarity\n\t\tevent: CqrsEvent,\n\t): void {\n\t\tconst prior = inFlight.get(correlationId) ?? Promise.resolve();\n\t\tconst next = prior.then(async () => {\n\t\t\t// Re-read current state at execution time (prior steps may have updated it).\n\t\t\tconst currentState = instanceStates.get(correlationId);\n\t\t\tif (currentState === undefined) return; // instance was cancelled/terminated in prior step\n\t\t\tif (!activeInstances.has(correlationId)) return;\n\t\t\tlet result: ProcessStepResult<TState>;\n\t\t\ttry {\n\t\t\t\tresult = await runWithRetry(\n\t\t\t\t\tstep as (s: TState, e: CqrsEvent) => NodeInput<ProcessStepResult<TState>>,\n\t\t\t\t\tcurrentState,\n\t\t\t\t\tevent,\n\t\t\t\t\tretryMax,\n\t\t\t\t\tbackoffMs,\n\t\t\t\t);\n\t\t\t} catch (err) {\n\t\t\t\t// runWithRetry itself should not throw (it returns fail on exhaustion),\n\t\t\t\t// but guard against unexpected errors.\n\t\t\t\tawait runCompensate(correlationId, instanceStates.get(correlationId) ?? opts.initial, err);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tawait handleStepResult(correlationId, result);\n\t\t});\n\t\tinFlight.set(correlationId, next);\n\t\tnext.finally(() => {\n\t\t\tif (inFlight.get(correlationId) === next) inFlight.delete(correlationId);\n\t\t});\n\t}\n\n\t// C3: disposal flag — set true by dispose(); gates start() and cancel().\n\t// Synchronous liveness check used by start() / cancel() to avoid\n\t// reaching into the reactive layer (and to short-circuit before the\n\t// teardown cascade has propagated through restoreState).\n\tlet _disposed = false;\n\n\t// ── Restore lifecycle state (B5 — locked 2026-05-01; post-fix using\n\t// central StatusValue enum 2026-05-01) ───────────────────────────────\n\t// Reactive lifecycle node typed as the central StatusValue enum\n\t// (`\"pending\" | \"running\" | \"completed\" | \"errored\"`). Currently emits\n\t// the \"pending\" → \"completed\" subset only:\n\t// \"pending\" → constructed; restore not yet finished. Watched events\n\t// arrive at the source but are valve-blocked from\n\t// reaching step dispatch.\n\t// \"completed\" → restoration complete. Watch valve is open; events\n\t// accumulated during \"pending\" are delivered as the\n\t// latest cumulative cqrs log array; the per-watch\n\t// cursor catches up to that array in one step.\n\t// (\"running\" / \"errored\" reserved for future fine-grained restore\n\t// observability; not emitted today.)\n\t// Disposal: no \"disposed\" literal — dispose() unmounts the subgraph\n\t// which TEARDOWNs restoreState; the gateOpen valve closes via standard\n\t// cascade; pending restore() Promise resolves via the .catch at the\n\t// public API edge.\n\tconst restoreState = node<StatusValue>([], {\n\t\tinitial: \"pending\",\n\t\tname: \"restoreState\",\n\t\tdescribeKind: \"state\",\n\t});\n\tsubgraph.add(restoreState, { name: \"restoreState\" });\n\n\t// `gateOpen` is the valve control: true only while\n\t// restoreState === \"completed\". Lazy: activates when the first valve\n\t// subscribes.\n\tconst gateOpen = node<boolean>(\n\t\t[restoreState as Node],\n\t\t(data, a, ctx) => {\n\t\t\tconst batch0 = data[0];\n\t\t\tconst v = (batch0 != null && batch0.length > 0 ? batch0.at(-1) : ctx.prevData[0]) as\n\t\t\t\t| StatusValue\n\t\t\t\t| undefined;\n\t\t\ta.emit(v === \"completed\");\n\t\t},\n\t\t{ name: \"gateOpen\", describeKind: \"derived\" },\n\t);\n\tsubgraph.add(gateOpen, { name: \"gateOpen\" });\n\n\t// ── Watched event subscriptions (valve-gated) ────────────────────────\n\t// COMPOSITION-GUIDE §28 cursor pattern + valve(eventNode, gateOpen):\n\t// per watched event type, subscribe to a valve over the cqrs event\n\t// stream. While restoreState !== \"completed\", valve emits RESOLVED so\n\t// no DATA reaches the cursor — events are NOT lost; the cqrs event\n\t// log retains them. When the gate flips open, valve re-emits the\n\t// latest cumulative event array (control-only wave path inside\n\t// `valve`); the cursor processes everything from `lastCount` (still\n\t// 0 at that point) in one shot.\n\tconst watchDisposers: Array<() => void> = [];\n\n\tfor (const eventType of opts.watching) {\n\t\tconst eventNode = cqrsGraph.event(eventType as string);\n\t\tconst gated = valve(eventNode, gateOpen, { name: `gatedEvent:${eventType as string}` });\n\n\t\t// Cursor starts at 0 — pre-restore events are NOT pre-counted because\n\t\t// they may be pre-restart events that the persisted snapshot already\n\t\t// consumed. The restore pipeline's job is to seed instanceStates /\n\t\t// activeInstances; events for instances NOT in activeInstances after\n\t\t// restore drop on the floor (the per-event activeInstances.has guard).\n\t\tlet lastCount = 0;\n\n\t\tconst unsub = gated.subscribe((msgs: Messages) => {\n\t\t\tfor (const m of msgs) {\n\t\t\t\tif (m[0] !== DATA) continue;\n\t\t\t\tconst events = m[1] as readonly CqrsEvent[];\n\t\t\t\tif (events.length <= lastCount) continue;\n\t\t\t\tconst newEvents = events.slice(lastCount);\n\t\t\t\tlastCount = events.length;\n\n\t\t\t\tfor (const ev of newEvents) {\n\t\t\t\t\tconst corrId = ev.correlationId;\n\t\t\t\t\tif (corrId === undefined) continue;\n\t\t\t\t\tif (!activeInstances.has(corrId)) continue;\n\n\t\t\t\t\tconst step = (opts.steps as unknown as Record<string, ProcessStep<TState, EM, string>>)[\n\t\t\t\t\t\teventType as string\n\t\t\t\t\t];\n\t\t\t\t\tif (!step) continue;\n\n\t\t\t\t\tconst state = instanceStates.get(corrId);\n\t\t\t\t\tif (state === undefined) continue;\n\n\t\t\t\t\tdispatchStep(corrId, step, state, ev);\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t\twatchDisposers.push(unsub);\n\t}\n\n\t// ── Public API ────────────────────────────────────────────────────────\n\n\t// Tier 8 γ-7-A (2026-04-28): `start()` body is mutate-wrapped so the\n\t// synthetic-start-event emit + the running audit record commit in one\n\t// batch frame. If `_appendEvent` throws (e.g. event stream terminated),\n\t// mutate rolls back the in-band batch (audit append discarded, seq\n\t// cursor advance discarded) and re-throws to the caller. Pre-1.0 behavior\n\t// change vs. γ-7-B: the previous form silently swallowed `_appendEvent`\n\t// failures and still appended the running record. Per COMPOSITION-GUIDE\n\t// §35, closure mutations are NOT rolled back — so they are deferred to\n\t// after `_appendEvent` succeeds inside the action body.\n\t//\n\t// B4 (D2 — locked 2026-05-01): `persistState(...)` lives INSIDE the action\n\t// body (after the closure mutations that seed `instanceStates`) so a\n\t// sync-throwing `stateStorage` tier rolls back the in-band batch the same\n\t// way `_appendEvent` failures do — neither the audit log entry nor the\n\t// state snapshot ends up in the \"running\" record store. Per §35 the\n\t// closure mutations stay (instanceStates / activeInstances / startedAt\n\t// are already set), but the audit-log + persisted-snapshot are coherent\n\t// with each other: both absent on throw, both present on success.\n\tconst startInternal = mutate<[string, unknown], void, ProcessInstance<TState>>(\n\t\t(correlationId, initialPayload) => {\n\t\t\t// Synthetic start event first (potentially throws). Closure\n\t\t\t// mutations below only run if this call succeeds — per §35,\n\t\t\t// rollback does not undo them, so they must come after the\n\t\t\t// throwing work.\n\t\t\t(\n\t\t\t\tcqrsGraph as unknown as {\n\t\t\t\t\t_appendEvent(\n\t\t\t\t\t\tname: string,\n\t\t\t\t\t\tpayload: unknown,\n\t\t\t\t\t\textra?: { correlationId?: string; aggregateId?: string },\n\t\t\t\t\t): void;\n\t\t\t\t}\n\t\t\t)._appendEvent(startedEventType, initialPayload ?? null, {\n\t\t\t\tcorrelationId,\n\t\t\t\taggregateId: correlationId,\n\t\t\t});\n\t\t\tstartedAt.set(correlationId, wallClockNs());\n\t\t\tinstanceStates.set(correlationId, opts.initial);\n\t\t\tactiveInstances.add(correlationId);\n\t\t\t// B4: inside the rollback boundary so a sync-throwing tier\n\t\t\t// discards the audit-log append + seq cursor advance.\n\t\t\tpersistStateThrowing(correlationId, \"running\");\n\t\t},\n\t\t{\n\t\t\tframe: \"transactional\",\n\t\t\tlog: instances,\n\t\t\tseq: seqCursor,\n\t\t\tfreeze: true,\n\t\t\t...(opts.handlerVersion !== undefined ? { handlerVersion: opts.handlerVersion } : {}),\n\t\t\tonSuccessRecord: ([correlationId], _r, { t_ns, seq }) => ({\n\t\t\t\tcorrelationId,\n\t\t\t\tstate: opts.initial,\n\t\t\t\tstatus: \"running\",\n\t\t\t\tstartedAt: startedAt.get(correlationId) ?? t_ns,\n\t\t\t\tupdatedAt: t_ns,\n\t\t\t\tt_ns,\n\t\t\t\tseq: seq ?? 0,\n\t\t\t}),\n\t\t},\n\t);\n\n\t/**\n\t * Start a new process instance.\n\t *\n\t * Idempotent: if a running instance with the same `correlationId` already\n\t * exists, the call is a no-op. Also a no-op after `dispose()`.\n\t *\n\t * **Throws** if the synthetic `_process_<name>_started` event stream is\n\t * terminated (γ-7-A, 2026-04-28). The audit log is not appended in that\n\t * case — the in-band batch rolls back so the seq cursor and audit log\n\t * stay consistent with the pre-call state.\n\t */\n\tfunction start(correlationId: string, initialPayload?: unknown): void {\n\t\tif (_disposed) return;\n\t\tif (activeInstances.has(correlationId)) return;\n\t\t// B4 (D2): persistState now lives INSIDE `startInternal`'s mutate\n\t\t// action body so sync-throwing tiers roll back the audit-log entry too.\n\t\tstartInternal(correlationId, initialPayload);\n\t}\n\n\t/**\n\t * Cancel a running instance and trigger compensation.\n\t *\n\t * No-op if the instance is not currently running (or after `dispose()`).\n\t */\n\tfunction cancel(correlationId: string, reason?: string): void {\n\t\tif (_disposed) return;\n\t\tif (!activeInstances.has(correlationId)) return;\n\n\t\t// Capture state before the async compensate path.\n\t\tconst state = instanceStates.get(correlationId) ?? opts.initial;\n\t\t// Run compensation asynchronously (fire-and-forget from caller perspective).\n\t\t// C1: runCompensate does the eager activeInstances.delete before any await,\n\t\t// so concurrent calls or in-flight steps that complete concurrently find the\n\t\t// instance already removed and exit early.\n\t\t// M4: pass reason through so it lands on the audit record.\n\t\trunCompensate(\n\t\t\tcorrelationId,\n\t\t\tstate,\n\t\t\tnew Error(`cancelled: ${reason ?? \"no reason given\"}`),\n\t\t\treason,\n\t\t);\n\t}\n\n\t/**\n\t * Read the current in-memory state for a correlationId.\n\t *\n\t * Returns `undefined` if the instance does not exist or has terminated.\n\t */\n\tfunction getState(correlationId: string): TState | undefined {\n\t\treturn instanceStates.get(correlationId);\n\t}\n\n\t/**\n\t * Reactive restore pipeline (B5 — locked 2026-05-01; post-fix using\n\t * fromAny + StatusValue 2026-05-01).\n\t *\n\t * `restoreSubscription` is the single keepalive over the snapshot-load\n\t * effect. Set on first `restore()` call (or on construction when\n\t * `deferRestore !== true`). The effect:\n\t *\n\t * 1. Subscribes to `mergeMap(fromAny(tier.list()), keys =>\n\t * mergeMap(fromIter(keys), key => fromAny(tier.load(key))))` —\n\t * a fully reactive chain whose only async boundaries are the source\n\t * `fromAny` nodes (spec §5.10). `fromAny` accepts sync values,\n\t * Promises, async iterables, and existing Nodes — future-proof\n\t * against tier impls that decide to expose paginated/streaming\n\t * `list()` or batched `load()`.\n\t * 2. On each per-key load DATA, populates `instanceStates` /\n\t * `activeInstances` / `startedAt` for `status === \"running\"` records.\n\t * 3. On `COMPLETE` (all loads finished), flips `restoreState` to\n\t * `\"completed\"` — the watch valve opens, queued cqrs events become\n\t * deliverable, and any `firstWhere(restoreState …)` awaiter resolves.\n\t *\n\t * Idempotent: subsequent calls reuse the same subscription and resolve\n\t * on the same gate flip.\n\t */\n\tlet restoreSubscription: (() => void) | undefined;\n\n\tfunction startRestorePipeline(): void {\n\t\tif (restoreSubscription !== undefined) return;\n\t\tconst tier = stateStorageTiers[0];\n\t\tif (tier == null || tier.list == null || tier.load == null) {\n\t\t\t// No snapshot tier — flip immediately so watches can arm.\n\t\t\t// `restoreState` is a state node so write directly.\n\t\t\tif (!_disposed) restoreState.emit(\"completed\");\n\t\t\trestoreSubscription = () => undefined;\n\t\t\treturn;\n\t\t}\n\t\tconst tierLoad = tier.load.bind(tier);\n\t\tconst tierList = tier.list.bind(tier);\n\n\t\t// Reactive source chain. `tier.list()` and `tier.load(key)` are the\n\t\t// only async boundaries. Bridged via `fromAny`, which (post DS-13.5\n\t\t// follow-up, 2026-05-01) treats sync iterables as single DATA values\n\t\t// by default — `tier.list()`'s `readonly string[]` flows through as\n\t\t// ONE DATA carrying the whole list, not as a per-key stream. Sync\n\t\t// tier returns emit immediately at subscribe time; async tier\n\t\t// returns emit on resolve.\n\t\tconst listSource = fromAny<readonly string[]>(tierList());\n\t\tconst flattened = mergeMap(listSource, (keys: readonly string[]) => {\n\t\t\tif (keys.length === 0) {\n\t\t\t\t// fromIter([]) emits no DATA, only COMPLETE — which\n\t\t\t\t// propagates up through mergeMap so the effect sees its\n\t\t\t\t// own COMPLETE.\n\t\t\t\treturn fromIter<ProcessStateSnapshot<TState> | undefined>([]);\n\t\t\t}\n\t\t\t// Inner: per-key load via fromAny, flattened across keys.\n\t\t\t// Outer fromIter(keys) DOES want per-element emission here —\n\t\t\t// each key triggers a fresh load via mergeMap.\n\t\t\treturn mergeMap(\n\t\t\t\tfromIter(keys),\n\t\t\t\t(key: string) => fromAny(tierLoad(key)) as Node<ProcessStateSnapshot<TState> | undefined>,\n\t\t\t\t// Bound concurrent in-flight loads (D2, 2026-05-01) so a\n\t\t\t\t// large persisted-instance count doesn't exhaust file\n\t\t\t\t// handles / connection pools on the storage backend.\n\t\t\t\t{ concurrent: opts.restoreConcurrency ?? 8 },\n\t\t\t);\n\t\t});\n\n\t\t// Effect node: populate closure state on each load, flip the gate on\n\t\t// COMPLETE. Reactive — no awaits.\n\t\t//\n\t\t// Re-entrancy guard (A3, 2026-05-01): wrap the closure-mutation loop\n\t\t// + the gate flip in a single `batch()`. Without it, the synchronous\n\t\t// `restoreState.emit(\"completed\")` mid-fn-body would cascade through\n\t\t// gateOpen → valves → cqrs cursors → step dispatch BEFORE the\n\t\t// snapshot loop returns — external observers would see a half-\n\t\t// populated `instanceStates` map.\n\t\t//\n\t\t// Mid-iteration dispose hoisting (A4, 2026-05-01): the `_disposed`\n\t\t// check is hoisted to BEFORE the snapshot loop. The previous per-\n\t\t// snapshot check leaked state when `dispose()` fired between\n\t\t// snapshots N and N+1: snapshots 0..N populated `instanceStates`,\n\t\t// then dispose flipped `_disposed`, the terminalDeps branch\n\t\t// short-circuited the gate flip, and the cleanup path tore down the\n\t\t// watch valve — leaving orphan entries that subsequent `start()`\n\t\t// calls would treat as \"already active\" via `activeInstances.has`.\n\t\tconst restoreEffect = node(\n\t\t\t[flattened as Node],\n\t\t\t(data, _a, ctx) => {\n\t\t\t\tif (_disposed) return;\n\t\t\t\tconst batch0 = data[0];\n\t\t\t\tconst hasSnapshots = batch0 != null && batch0.length > 0;\n\t\t\t\tconst allDone = ctx.terminalDeps[0] === true;\n\t\t\t\tif (!hasSnapshots && !allDone) return;\n\t\t\t\tbatch(() => {\n\t\t\t\t\tif (hasSnapshots) {\n\t\t\t\t\t\tfor (const snap of batch0 as readonly (ProcessStateSnapshot<TState> | undefined)[]) {\n\t\t\t\t\t\t\tif (snap == null) continue;\n\t\t\t\t\t\t\tif (snap.status !== \"running\") continue;\n\t\t\t\t\t\t\tinstanceStates.set(snap.correlationId, snap.state);\n\t\t\t\t\t\t\tactiveInstances.add(snap.correlationId);\n\t\t\t\t\t\t\tstartedAt.set(snap.correlationId, snap.startedAt);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tif (allDone) restoreState.emit(\"completed\");\n\t\t\t\t});\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"restoreEffect\",\n\t\t\t\tdescribeKind: \"effect\",\n\t\t\t\t// Spec §2.7 R2.7.1 (DS-2.7.A). Empty-tier restore: `flattened`\n\t\t\t\t// delivers COMPLETE without any DATA (no keys → inner\n\t\t\t\t// `fromIter([])` COMPLETEs immediately). fn MUST fire on that\n\t\t\t\t// terminal-only wave to call `restoreState.emit(\"completed\")`\n\t\t\t\t// — otherwise the watch valve never opens and dispatched\n\t\t\t\t// events never reach `handleStepResult`.\n\t\t\t\tterminalAsRealInput: true,\n\t\t\t},\n\t\t);\n\t\tsubgraph.add(restoreEffect, { name: \"restoreEffect\" });\n\t\trestoreSubscription = restoreEffect.subscribe(() => undefined);\n\t}\n\n\t/**\n\t * Trigger restoration (idempotent) and return a Promise that resolves\n\t * when `restoreState` transitions to `\"completed\"` OR when {@link dispose}\n\t * tears down the restore node (firstWhere's COMPLETE-rejection is\n\t * swallowed at the API edge so the caller's promise settles cleanly).\n\t */\n\tfunction restore(): Promise<void> {\n\t\tstartRestorePipeline();\n\t\tif (_disposed) return Promise.resolve();\n\t\tif (restoreState.cache === \"completed\") return Promise.resolve();\n\t\t// firstWhere is the canonical reactive→Promise bridge (spec §5.10);\n\t\t// async boundary lives at the public API edge, not in the graph.\n\t\t// .catch distinguishes the COMPLETE-without-match shape (which is\n\t\t// the dispose-tears-down case — swallow + resolve undefined) from\n\t\t// any other rejection (re-throw — caller sees a real failure).\n\t\treturn firstWhere(restoreState, (s) => s === \"completed\")\n\t\t\t.then(() => undefined)\n\t\t\t.catch((err: unknown) => {\n\t\t\t\tif (err instanceof Error && err.message === \"completed without matching value\") {\n\t\t\t\t\treturn undefined;\n\t\t\t\t}\n\t\t\t\tthrow err;\n\t\t\t});\n\t}\n\n\tfunction dispose(): void {\n\t\tif (_disposed) return;\n\t\t_disposed = true;\n\t\t// Pending `firstWhere(restoreState, ...)` awaiters (i.e. a `restore()`\n\t\t// Promise still waiting on the gate flip) settle via the framework's\n\t\t// DS-13.5.A Q16 auto-precede rule: TEARDOWN delivery on a non-terminal\n\t\t// node automatically prepends [COMPLETE] in `_frameBatch`, so sinks\n\t\t// see [[COMPLETE], [TEARDOWN]] in order. firstWhere rejects on\n\t\t// COMPLETE-without-match; the .catch in `restore()` swallows that\n\t\t// rejection and the public Promise resolves cleanly.\n\t\t// Tear down the restore pipeline keepalive. fromAny.cleanup sets\n\t\t// `settled = true`, so when the underlying tier.list/load promises\n\t\t// finally resolve, the DATA never propagates — the closure mutations\n\t\t// in `restoreEffect` are never invoked (D5 belt + suspenders).\n\t\tif (restoreSubscription) {\n\t\t\ttry {\n\t\t\t\trestoreSubscription();\n\t\t\t} catch {\n\t\t\t\t// non-fatal\n\t\t\t}\n\t\t\trestoreSubscription = undefined;\n\t\t}\n\t\t// Release all watched-event subscriptions (C3 — watchDisposers leak fix).\n\t\tfor (const unsub of watchDisposers) {\n\t\t\ttry {\n\t\t\t\tunsub();\n\t\t\t} catch (_err) {\n\t\t\t\t// non-fatal: best-effort teardown\n\t\t\t}\n\t\t}\n\t\twatchDisposers.length = 0;\n\t\t// EH-16 (Tier 6.5 3.3): unmount the per-instance subgraph so the\n\t\t// audit log + seq cursor nodes are removed from the CQRS graph and\n\t\t// don't leak across repeated create/dispose cycles. `Graph.remove`\n\t\t// fires TEARDOWN through the subtree.\n\t\ttry {\n\t\t\tcqrsGraph.remove(mountName);\n\t\t} catch (_err) {\n\t\t\t// non-fatal: best-effort teardown (e.g. cqrsGraph already destroyed)\n\t\t}\n\t}\n\n\t// Auto-restore on construction unless explicitly deferred (B5).\n\tif (opts.deferRestore !== true) {\n\t\tstartRestorePipeline();\n\t}\n\n\treturn {\n\t\tinstances,\n\t\taudit: readonlyAuditLog(instances),\n\t\trestoreState,\n\t\tstart,\n\t\tcancel,\n\t\tgetState,\n\t\trestore,\n\t\tdispose,\n\t};\n}\n"],"mappings":"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","names":[]}
|
|
@@ -4,9 +4,10 @@ import {
|
|
|
4
4
|
import {
|
|
5
5
|
createAuditLog,
|
|
6
6
|
mutate,
|
|
7
|
+
readonlyAuditLog,
|
|
7
8
|
registerCursor,
|
|
8
9
|
registerCursorMap
|
|
9
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-C5QD5DQX.js";
|
|
10
11
|
|
|
11
12
|
// src/utils/cqrs/index.ts
|
|
12
13
|
import {
|
|
@@ -142,7 +143,10 @@ var CqrsGraph = class extends Graph {
|
|
|
142
143
|
_dispatchSeqCursor;
|
|
143
144
|
/** Audit log of every command dispatch (Audit 2). */
|
|
144
145
|
dispatches;
|
|
145
|
-
/**
|
|
146
|
+
/**
|
|
147
|
+
* Read-only view of {@link CqrsGraph.dispatches} (Audit 2 `.audit`
|
|
148
|
+
* duplication; M7 — cannot mutate the canonical log via the alias).
|
|
149
|
+
*/
|
|
146
150
|
audit;
|
|
147
151
|
/** Per-aggregate LRU eviction observability; secondary log to `dispatches`. */
|
|
148
152
|
aggregateEvictions;
|
|
@@ -157,7 +161,7 @@ var CqrsGraph = class extends Graph {
|
|
|
157
161
|
retainedLimit: this._retainedLimit,
|
|
158
162
|
graph: this
|
|
159
163
|
});
|
|
160
|
-
this.audit = this.dispatches;
|
|
164
|
+
this.audit = readonlyAuditLog(this.dispatches);
|
|
161
165
|
this.aggregateEvictions = createAuditLog({
|
|
162
166
|
name: "aggregateEvictions",
|
|
163
167
|
retainedLimit: this._retainedLimit,
|
|
@@ -342,6 +346,9 @@ var CqrsGraph = class extends Graph {
|
|
|
342
346
|
}
|
|
343
347
|
);
|
|
344
348
|
} catch {
|
|
349
|
+
console.warn(
|
|
350
|
+
`[GraphReFly] cqrs: per-aggregate event node "${mountName}" hit a name-collision race (event "${type}", aggregate "${aggregateId}") \u2014 falling back to an unmounted node; this aggregate's stream still works but will NOT appear in graph.describe()/observe().`
|
|
351
|
+
);
|
|
345
352
|
guarded = node(
|
|
346
353
|
[entries],
|
|
347
354
|
(batchData, actions, ctx) => {
|
|
@@ -843,7 +850,7 @@ var CqrsGraph = class extends Graph {
|
|
|
843
850
|
return {
|
|
844
851
|
node: sagaNode,
|
|
845
852
|
invocations,
|
|
846
|
-
audit: invocations,
|
|
853
|
+
audit: readonlyAuditLog(invocations),
|
|
847
854
|
cursors
|
|
848
855
|
};
|
|
849
856
|
}
|
|
@@ -870,4 +877,4 @@ export {
|
|
|
870
877
|
CqrsGraph,
|
|
871
878
|
cqrs
|
|
872
879
|
};
|
|
873
|
-
//# sourceMappingURL=chunk-
|
|
880
|
+
//# sourceMappingURL=chunk-5UY3PNFY.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/utils/cqrs/index.ts","../src/utils/_errors/index.ts"],"sourcesContent":["/**\n * CQRS patterns (roadmap §4.5).\n *\n * Composition layer over reactiveLog (3.2), pipeline/sagas (4.1), event bus (4.2),\n * projections (4.3). Guards (1.5) enforce command/query boundary.\n *\n * - `cqrs(name, opts?)` → `CqrsGraph` — top-level factory\n * - `CqrsGraph.command(name, handler)` — write-only node; guard rejects `observe`\n * - `CqrsGraph.event(name)` — backed by `reactiveLog`; append-only\n * - `CqrsGraph.projection(name, events, reducer, initial)` — read-only derived; guard rejects `write`\n * - `CqrsGraph.saga(name, events, handler)` — event-driven side effects\n */\n\nimport {\n\tDATA,\n\ttype Node,\n\tnode,\n\tplaceholderArgs,\n\tpolicy,\n\twallClockNs,\n} from \"@graphrefly/pure-ts/core\";\nimport type { AppendLogStorageTier } from \"@graphrefly/pure-ts/extra\";\nimport { type ReactiveLogBundle, reactiveLog } from \"@graphrefly/pure-ts/extra\";\nimport { Graph, type GraphOptions } from \"@graphrefly/pure-ts/graph\";\nimport {\n\ttype BaseAuditRecord,\n\tcreateAuditLog,\n\tmutate,\n\ttype ReadonlyAuditLog,\n\treadonlyAuditLog,\n\tregisterCursor,\n\tregisterCursorMap,\n} from \"../../base/mutation/index.js\";\nimport {\n\tCommandHandlerError,\n\tDuplicateRegistrationError,\n\tOptimisticConcurrencyError,\n\tRebuildError,\n\tUndeclaredEmitError,\n\tUnknownCommandError,\n} from \"../_errors/index.js\";\n\n// ---------------------------------------------------------------------------\n// Guards\n// ---------------------------------------------------------------------------\n\n/** Commands: write + signal allowed, observe denied. */\nconst COMMAND_GUARD = policy((allow, deny) => {\n\tallow(\"write\");\n\tallow(\"signal\");\n\tdeny(\"observe\");\n});\n\n/** Projections: observe + signal allowed, write denied. */\nconst PROJECTION_GUARD = policy((allow, deny) => {\n\tallow(\"observe\");\n\tallow(\"signal\");\n\tdeny(\"write\");\n});\n\n/** Events: observe + signal allowed, write denied (appended internally). */\nconst EVENT_GUARD = policy((allow, deny) => {\n\tallow(\"observe\");\n\tallow(\"signal\");\n\tdeny(\"write\");\n});\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nimport { keepalive } from \"@graphrefly/pure-ts/extra\";\nimport { domainMeta } from \"../../base/meta/domain-meta.js\";\n\nfunction cqrsMeta(kind: string, extra?: Record<string, unknown>): Record<string, unknown> {\n\treturn domainMeta(\"cqrs\", kind, extra);\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// Event envelope\n// ---------------------------------------------------------------------------\n\n/**\n * Immutable envelope for events emitted by command handlers.\n *\n * **Wave C.1 Unit 17 (locked 2026-04-24):** Extended ES standard fields —\n * `aggregateId` / `aggregateVersion` for per-aggregate streams; correlation /\n * causation IDs for distributed tracing; `metadata` for free-form context.\n * Optional `handlerVersion` (Audit 5) traces which handler version produced\n * the event.\n *\n * `seq` is a per-graph monotonic counter that provides stable ordering when\n * multiple events share the same `timestampNs` (same wall-clock tick).\n */\nexport type CqrsEvent<T = unknown> = {\n\ttype: string;\n\tpayload: T;\n\t/** Wall-clock nanoseconds (via `wallClockNs()`). */\n\ttimestampNs: number;\n\t/** Monotonic sequence within this CqrsGraph instance. */\n\tseq: number;\n\t/** Aggregate identifier (per-aggregate streams). */\n\taggregateId?: string;\n\t/** Per-aggregate monotonic version (set when `aggregateId` is provided). */\n\taggregateVersion?: number;\n\t/** Distributed-trace correlation id. */\n\tcorrelationId?: string;\n\t/** Causation chain id (this event was caused by event `causationId`). */\n\tcausationId?: string;\n\t/** Free-form metadata frozen at append. */\n\tmetadata?: Readonly<Record<string, unknown>>;\n\t/** V0 identity of the event log node at append time (§6.0b). */\n\tv0?: { id: string; version: number };\n\t/** Handler version stamped on emit (Audit 5). */\n\thandlerVersion?: { id: string; version: string | number };\n};\n\n/** Compile-time event-map registry: `{ \"orderPlaced\": OrderPayload, ... }`. */\nexport type CqrsEventMap = Record<string, unknown>;\n\n/** Recommended `keyOf` for CQRS event-store storage tiers (Audit 4). */\nexport const cqrsEventKeyOf = (e: CqrsEvent): string =>\n\t`${e.type}::${e.aggregateId ?? \"__default__\"}`;\n\n// ── Audit records (Audit 2 cross-cutting) ────────────────────────────────\n\nexport interface DispatchRecord<T = unknown> extends BaseAuditRecord {\n\treadonly commandName: string;\n\treadonly payload: T;\n\t/** Action result. Tier 1.6.2 canonical enum (renamed from `status`). */\n\treadonly outcome: \"success\" | \"failure\";\n\treadonly error?: unknown;\n\treadonly errorType?: string;\n\t/**\n\t * Event names emitted by the handler.\n\t * - `outcome: \"success\"`: events that persisted in the event log.\n\t * - `outcome: \"failure\"`: events the handler ATTEMPTED to emit before throwing;\n\t * they were rolled back and did NOT persist. Documents the failed attempt's\n\t * intentions for debugging handler logic. The actual event log shows only\n\t * what's durable.\n\t */\n\treadonly emittedEvents?: readonly string[];\n}\n\nexport const dispatchKeyOf = <T>(r: DispatchRecord<T>): string => r.commandName;\n\nexport interface SagaInvocation<T = unknown> extends BaseAuditRecord {\n\treadonly eventType: string;\n\t/** Action result. Tier 1.6.2 canonical enum (renamed from `status`). */\n\treadonly outcome: \"success\" | \"failure\";\n\treadonly error?: unknown;\n\treadonly errorType?: string;\n\treadonly aggregateId?: string;\n\treadonly event?: CqrsEvent<T>;\n}\n\nexport const sagaInvocationKeyOf = <T>(i: SagaInvocation<T>): string => i.eventType;\n\n/**\n * Saga registration result (M10) — a typed bundle replacing the prior\n * `Node<unknown>` return that side-attached `_saga` via an unsafe cast.\n *\n * `node` is the saga's effect node (subscribe to observe processing\n * activity). `invocations` is the per-event-type audit log; `audit` is a\n * **read-only view** of `invocations` (Audit 2 `.audit` duplication; M7 —\n * the alias can no longer silently `append`/`clear` the canonical log).\n * `cursors` exposes the per-event-type cursor state nodes for monitoring /\n * testing.\n *\n * @category patterns\n */\nexport interface SagaController<T = unknown> {\n\treadonly node: Node<unknown>;\n\treadonly invocations: ReactiveLogBundle<SagaInvocation<T>>;\n\treadonly audit: ReadonlyAuditLog<SagaInvocation<T>>;\n\treadonly cursors: { readonly [eventName: string]: Node<number> };\n}\n\n// ---------------------------------------------------------------------------\n// Handler types\n// ---------------------------------------------------------------------------\n\nexport type CommandActions = {\n\t/** Append an event to a named event log (bypasses event guard). */\n\temit: (eventName: string, payload: unknown) => void;\n};\n\n/**\n * Command handler receives the dispatch payload and actions to emit events.\n *\n * **Purity:** Handlers should not mutate the payload. Event emission via\n * `actions.emit()` is the only sanctioned side effect.\n */\nexport type CommandHandler<T = unknown> = (payload: T, actions: CommandActions) => void;\n\n/**\n * Projection reducer folds events into a read model.\n *\n * **Purity contract:** Reducers MUST be pure — return a new state value\n * without mutating `state` or any event.\n *\n * - In **`\"replay\"`** mode the `state` parameter is always the original\n * `initial` value (full event-sourcing replay on every recompute).\n * - In **`\"scan\"`** mode the `state` parameter is the _previous_ output\n * (incremental fold); `events` contains only the events appended since\n * the last computation.\n */\nexport type ProjectionReducer<TState = unknown, TEvent = unknown> = (\n\tstate: TState,\n\tevents: readonly CqrsEvent<TEvent>[],\n) => TState;\n\n/**\n * Snapshot integration for {@link ProjectionOptions}.\n *\n * `load` is called once at projection construction and the returned value\n * seeds the initial state. `save` (optional) is called after each reducer\n * run, debounced by `saveDebounceMs` (default 1000 ms) and capped by\n * `saveEvery` (default 1000 events).\n */\nexport type ProjectionSnapshotOpts<TState> = {\n\t/** Load a previously-saved state. `undefined` → start from `initial`. */\n\tload: () => TState | undefined | Promise<TState | undefined>;\n\t/** Persist the current state. Called after reducer; may be async. */\n\tsave?: (state: TState) => void | Promise<void>;\n\t/**\n\t * Debounce window (ms) before `save` fires after the last event. Default 1000.\n\t */\n\tsaveDebounceMs?: number;\n\t/**\n\t * Force a save after every Nth state change regardless of debounce.\n\t * Default 1000. Both knobs compose: save fires at whichever condition is\n\t * met first.\n\t */\n\tsaveEvery?: number;\n};\n\n/**\n * Options for {@link CqrsGraph.projection}.\n *\n * **Wave C.3 Unit 21 (locked 2026-04-24):**\n * - `mode: \"scan\"` (default) — incremental fold; `\"replay\"` — full replay\n * each wave.\n * - `snapshot` — load/save integration for cold-start + auto-checkpoint.\n * - `freezeInputs` (default `true`) — freeze event arrays before passing\n * to reducer (purity enforcement).\n * - `rebuild()` / `reset()` on the returned {@link ProjectionController}.\n *\n * @category patterns\n */\nexport type ProjectionOptions<TState> = {\n\tname: string;\n\tevents: readonly string[];\n\treducer: ProjectionReducer<TState>;\n\tinitial: TState;\n\t/**\n\t * Fold strategy. Default `\"scan\"` (incremental). `\"replay\"` = full replay.\n\t *\n\t * **Scan-mode ordering caveat:** scan-mode assumes monotonic per-stream\n\t * arrival order. When multiple event streams are merged for a projection,\n\t * events arriving with a `timestampNs` earlier than the current sort cursor\n\t * are skipped from the incremental sweep. This is an acceptable trade-off\n\t * for incremental fold; use `mode: \"replay\"` for strict cross-stream\n\t * ordering.\n\t */\n\tmode?: \"replay\" | \"scan\";\n\t/** Snapshot integration for rebuild + auto-checkpoint. */\n\tsnapshot?: ProjectionSnapshotOpts<TState>;\n\t/**\n\t * Freeze event arrays before passing to reducer (default `true`).\n\t * Set to `false` only if your reducer intentionally mutates the input\n\t * (strongly discouraged — prefer immutable reducers).\n\t */\n\tfreezeInputs?: boolean;\n};\n\n/**\n * Controller returned by {@link CqrsGraph.projection}.\n *\n * `node` is the reactive read model. `rebuild()` performs a paginated\n * cold-storage replay (requires `attachEventStorage` tiers). `reset()`\n * reloads from `snapshot.load()` and re-folds the live event log on top.\n *\n * @category patterns\n */\nexport interface ProjectionController<TState> {\n\treadonly node: Node<TState>;\n\t/**\n\t * Async paginated rebuild from attached storage tiers. Throws\n\t * {@link RebuildError} on adapter / decode / reducer failure.\n\t *\n\t * @param opts.fromTier - Storage tier to read from (default: first attached).\n\t * @param opts.pageSize - Entries per page (default 1000).\n\t */\n\trebuild(opts?: {\n\t\tfromTier?: AppendLogStorageTier<CqrsEvent>;\n\t\tpageSize?: number;\n\t}): Promise<TState>;\n\t/**\n\t * Reload from `snapshot.load()` (if configured) and re-fold the live\n\t * in-memory event log on top. Returns the rebuilt state. No-op on the\n\t * reactive node if the state is unchanged.\n\t */\n\treset(): Promise<TState>;\n}\n\nexport type SagaHandler<T = unknown> = (event: CqrsEvent<T>) => void;\n\n// ---------------------------------------------------------------------------\n// Options\n// ---------------------------------------------------------------------------\n\nexport type CqrsOptions = {\n\tgraph?: GraphOptions;\n\t/** Bounded retention for event streams; default 1024 (cross-cutting). */\n\tretainedLimit?: number;\n\t/** Freeze command payloads on dispatch (default `true`). */\n\tfreezeCommandPayload?: boolean;\n\t/** Freeze event payloads on emit (default `true`). */\n\tfreezeEventPayload?: boolean;\n\t/** LRU eviction threshold for per-aggregate streams (default 10_000). */\n\tmaxAggregates?: number;\n};\n\nexport type CommandRegistration<TPayload = unknown> = {\n\thandler: CommandHandler<TPayload>;\n\temits?: readonly string[];\n\thandlerVersion?: { id: string; version: string | number };\n};\n\nexport type DispatchOptions = {\n\tcorrelationId?: string;\n\tcausationId?: string;\n\tmetadata?: Record<string, unknown>;\n\t/**\n\t * Optimistic-concurrency check: if set, dispatch verifies the aggregate\n\t * (identified by `aggregateId`) is at this version. On mismatch, dispatch\n\t * throws {@link OptimisticConcurrencyError} BEFORE the handler runs.\n\t *\n\t * Requires `aggregateId` to be set. Without it the check is a no-op.\n\t */\n\texpectedAggregateVersion?: number;\n\t/**\n\t * Aggregate this dispatch targets. Events emitted by the handler that\n\t * also carry this `aggregateId` participate in per-aggregate versioning\n\t * and LRU eviction (see {@link CqrsOptions.maxAggregates}). Events whose\n\t * handler-supplied `aggregateId` differs from the dispatch's `aggregateId`\n\t * are emitted untouched (their own `aggregateVersion` is computed from\n\t * their own aggregate's stream).\n\t */\n\taggregateId?: string;\n};\n\nexport type SagaOptions = {\n\taggregateId?: string;\n\terrorPolicy?: \"advance\" | \"hold\";\n\thandlerVersion?: { id: string; version: string | number };\n};\n\n// ---------------------------------------------------------------------------\n// CqrsGraph\n// ---------------------------------------------------------------------------\n\ntype EventEntry = {\n\tlog: ReturnType<typeof reactiveLog<CqrsEvent>>;\n\tnode: Node<readonly CqrsEvent[]>;\n};\n\n/**\n * Eviction record emitted on `aggregateEvictions` when an aggregate's\n * per-aggregate stream is removed under `maxAggregates` LRU pressure. The\n * eviction does NOT delete events from the fan-in stream — only the\n * per-aggregate dedicated stream and its version counter.\n */\nexport interface AggregateEvictionRecord {\n\treadonly aggregateId: string;\n\treadonly type: string;\n\treadonly t_ns: number;\n\t/** The version count the aggregate reached before eviction (for diagnostics). */\n\treadonly lastVersion: number;\n}\n\nexport class CqrsGraph<_EM extends CqrsEventMap = Record<string, unknown>> extends Graph {\n\t/** Fan-in event streams (one per type, all aggregates merged). */\n\tprivate readonly _eventLogs = new Map<string, EventEntry>();\n\t/**\n\t * Per-aggregate event streams: type → aggregateId → entry. Used for\n\t * `event(type, aggregateId)` dual-form access and per-aggregate version\n\t * tracking. Only populated when an event with `aggregateId` is emitted.\n\t */\n\tprivate readonly _eventLogsByAggregate = new Map<string, Map<string, EventEntry>>();\n\t/** Per-aggregate version counters: `${type}::${aggregateId}` → current version. */\n\tprivate readonly _aggregateVersions = new Map<string, number>();\n\t/**\n\t * LRU access order for `${type}::${aggregateId}`. Map insertion order\n\t * tracks recency — `delete` + `set` on access moves to the end.\n\t */\n\tprivate readonly _aggregateLru = new Map<string, true>();\n\tprivate readonly _commandRegs = new Map<\n\t\tstring,\n\t\t{\n\t\t\thandler: CommandHandler<any>;\n\t\t\temits?: readonly string[];\n\t\t\thandlerVersion?: { id: string; version: string | number };\n\t\t}\n\t>();\n\tprivate readonly _projections = new Set<string>();\n\tprivate readonly _sagas = new Set<string>();\n\tprivate _seq = 0;\n\tprivate readonly _retainedLimit: number;\n\tprivate readonly _freezeCommandPayload: boolean;\n\tprivate readonly _freezeEventPayload: boolean;\n\tprivate readonly _maxAggregates: number;\n\tprivate readonly _dispatchSeqCursor: Node<number>;\n\t/** Audit log of every command dispatch (Audit 2). */\n\treadonly dispatches: ReactiveLogBundle<DispatchRecord>;\n\t/**\n\t * Read-only view of {@link CqrsGraph.dispatches} (Audit 2 `.audit`\n\t * duplication; M7 — cannot mutate the canonical log via the alias).\n\t */\n\treadonly audit: ReadonlyAuditLog<DispatchRecord>;\n\t/** Per-aggregate LRU eviction observability; secondary log to `dispatches`. */\n\treadonly aggregateEvictions: ReactiveLogBundle<AggregateEvictionRecord>;\n\n\tconstructor(name: string, opts: CqrsOptions = {}) {\n\t\tsuper(name, opts.graph);\n\t\tthis._retainedLimit = opts.retainedLimit ?? 1024;\n\t\tthis._freezeCommandPayload = opts.freezeCommandPayload ?? true;\n\t\tthis._freezeEventPayload = opts.freezeEventPayload ?? true;\n\t\tthis._maxAggregates = opts.maxAggregates ?? 10_000;\n\t\tthis.dispatches = createAuditLog<DispatchRecord>({\n\t\t\tname: \"dispatches\",\n\t\t\tretainedLimit: this._retainedLimit,\n\t\t\tgraph: this,\n\t\t});\n\t\tthis.audit = readonlyAuditLog(this.dispatches);\n\t\tthis.aggregateEvictions = createAuditLog<AggregateEvictionRecord>({\n\t\t\tname: \"aggregateEvictions\",\n\t\t\tretainedLimit: this._retainedLimit,\n\t\t\tgraph: this,\n\t\t});\n\t\tthis._dispatchSeqCursor = registerCursor(this, \"dispatch_seq\", 0);\n\t}\n\n\t/**\n\t * Read the current per-aggregate version (last emitted `aggregateVersion`\n\t * for that `(type, aggregateId)` pair). Returns `0` if no events have been\n\t * emitted yet for this aggregate. Useful for callers preparing\n\t * {@link DispatchOptions.expectedAggregateVersion}.\n\t */\n\taggregateVersion(type: string, aggregateId: string): number {\n\t\treturn this._aggregateVersions.get(`${type}::${aggregateId}`) ?? 0;\n\t}\n\n\t/** LRU touch — moves the key to the end of the access order. */\n\tprivate _touchAggregate(key: string): void {\n\t\t// Delete + set re-inserts at the end of Map iteration order.\n\t\tthis._aggregateLru.delete(key);\n\t\tthis._aggregateLru.set(key, true);\n\t}\n\n\t/**\n\t * Evict the oldest aggregate streams (least-recently-touched) until the\n\t * aggregate count is back within `_maxAggregates`. Emits one\n\t * `AggregateEvictionRecord` per eviction. The fan-in stream is NOT touched\n\t * — events stay in the type-level log; only the per-aggregate stream and\n\t * version counter are removed.\n\t */\n\tprivate _enforceAggregateLru(): void {\n\t\twhile (this._aggregateLru.size > this._maxAggregates) {\n\t\t\tconst oldest = this._aggregateLru.keys().next();\n\t\t\tif (oldest.done) break;\n\t\t\tconst key = oldest.value;\n\t\t\tthis._aggregateLru.delete(key);\n\t\t\tconst sep = key.indexOf(\"::\");\n\t\t\tif (sep < 0) continue;\n\t\t\tconst type = key.slice(0, sep);\n\t\t\tconst aggregateId = key.slice(sep + 2);\n\t\t\tconst lastVersion = this._aggregateVersions.get(key) ?? 0;\n\t\t\tthis._aggregateVersions.delete(key);\n\t\t\tconst byType = this._eventLogsByAggregate.get(type);\n\t\t\tif (byType) {\n\t\t\t\tbyType.delete(aggregateId);\n\t\t\t\tif (byType.size === 0) this._eventLogsByAggregate.delete(type);\n\t\t\t}\n\t\t\tthis.aggregateEvictions.append({\n\t\t\t\taggregateId,\n\t\t\t\ttype,\n\t\t\t\tlastVersion,\n\t\t\t\tt_ns: wallClockNs(),\n\t\t\t});\n\t\t}\n\t}\n\n\t/** Tiers attached via {@link attachEventStorage}; auto-wired into future event streams. */\n\tprivate readonly _attachedEventTiers: Array<readonly AppendLogStorageTier<CqrsEvent>[]> = [];\n\tprivate readonly _attachedTierDisposers = new Map<string, Array<() => void>>();\n\n\t/**\n\t * Wire append-log storage tiers for ALL CQRS event streams — both currently\n\t * registered AND any future streams created via `event(name)` /\n\t * `event(name, aggregateId)` / handler emit. (M4 fix.)\n\t *\n\t * Returns a disposer that releases all storage subscriptions wired by this\n\t * call (including those for streams that were created after the call).\n\t */\n\tattachEventStorage(tiers: readonly AppendLogStorageTier<CqrsEvent>[]): () => void {\n\t\tthis._attachedEventTiers.push(tiers);\n\t\t// Wire currently-existing streams.\n\t\tfor (const [name, entry] of this._eventLogs) {\n\t\t\tconst dispose = entry.log.attachStorage(tiers);\n\t\t\tlet arr = this._attachedTierDisposers.get(name);\n\t\t\tif (!arr) {\n\t\t\t\tarr = [];\n\t\t\t\tthis._attachedTierDisposers.set(name, arr);\n\t\t\t}\n\t\t\tarr.push(dispose);\n\t\t}\n\t\t// Per-aggregate streams existing now.\n\t\tfor (const [type, byAgg] of this._eventLogsByAggregate) {\n\t\t\tfor (const [aggId, entry] of byAgg) {\n\t\t\t\tconst key = `${type}::${aggId}`;\n\t\t\t\tconst dispose = entry.log.attachStorage(tiers);\n\t\t\t\tlet arr = this._attachedTierDisposers.get(key);\n\t\t\t\tif (!arr) {\n\t\t\t\t\tarr = [];\n\t\t\t\t\tthis._attachedTierDisposers.set(key, arr);\n\t\t\t\t}\n\t\t\t\tarr.push(dispose);\n\t\t\t}\n\t\t}\n\t\treturn () => {\n\t\t\t// Remove from auto-wire list so newly-created streams skip.\n\t\t\tconst idx = this._attachedEventTiers.indexOf(tiers);\n\t\t\tif (idx >= 0) this._attachedEventTiers.splice(idx, 1);\n\t\t\t// We can't precisely undo the per-stream attach for THIS tier set\n\t\t\t// alone (Map values commingle disposers across multiple\n\t\t\t// attachEventStorage calls). Caller wanting fine-grained control\n\t\t\t// should call `tier.flush()` / dispose tiers themselves. This\n\t\t\t// disposer is best-effort: it stops auto-wiring future streams.\n\t\t};\n\t}\n\n\t/** Wire newly-created event stream into all currently-attached tier sets. */\n\tprivate _autoWireStreamStorage(\n\t\tkey: string,\n\t\tlog: ReturnType<typeof reactiveLog<CqrsEvent>>,\n\t): void {\n\t\tif (this._attachedEventTiers.length === 0) return;\n\t\tlet arr = this._attachedTierDisposers.get(key);\n\t\tif (!arr) {\n\t\t\tarr = [];\n\t\t\tthis._attachedTierDisposers.set(key, arr);\n\t\t}\n\t\tfor (const tiers of this._attachedEventTiers) {\n\t\t\tarr.push(log.attachStorage(tiers));\n\t\t}\n\t}\n\n\t// -- Events ---------------------------------------------------------------\n\n\t/**\n\t * Register a named event stream backed by `reactiveLog`.\n\t * Guard denies external `write` — only commands append internally.\n\t */\n\tevent(name: string): Node<readonly CqrsEvent[]>;\n\tevent(name: string, aggregateId: string): Node<readonly CqrsEvent[]>;\n\tevent(name: string, aggregateId?: string): Node<readonly CqrsEvent[]> {\n\t\tif (aggregateId !== undefined) {\n\t\t\treturn this._ensureAggregateStream(name, aggregateId).node;\n\t\t}\n\t\tconst existing = this._eventLogs.get(name);\n\t\tif (existing) return existing.node;\n\n\t\t// V0 versioning is attached at construction — post-hoc\n\t\t// `_applyVersioning` was deleted because it opened a re-entrance\n\t\t// window where a wave could observe `_versioning` transitioning from\n\t\t// `undefined` to a fresh state. Construction-time-only means the\n\t\t// flag is frozen at birth.\n\t\tconst log = reactiveLog<CqrsEvent>([], {\n\t\t\tname,\n\t\t\tversioning: 0,\n\t\t\tmaxSize: this._retainedLimit,\n\t\t});\n\t\tlog.withLatest();\n\t\tconst entries = log.entries;\n\t\tconst guarded = this.derived<readonly CqrsEvent[]>(\n\t\t\tname,\n\t\t\t[entries],\n\t\t\t(batchData, ctx) => {\n\t\t\t\tconst latest =\n\t\t\t\t\tbatchData[0] != null && batchData[0].length > 0\n\t\t\t\t\t\t? (batchData[0].at(-1) as readonly CqrsEvent[])\n\t\t\t\t\t\t: (ctx.prevData[0] as readonly CqrsEvent[]);\n\t\t\t\treturn [latest];\n\t\t\t},\n\t\t\t{\n\t\t\t\tmeta: cqrsMeta(\"event\", { event_name: name }),\n\t\t\t\tguard: EVENT_GUARD,\n\t\t\t\tinitial: entries.cache as readonly CqrsEvent[],\n\t\t\t},\n\t\t);\n\t\tthis.addDisposer(keepalive(guarded));\n\t\tthis._eventLogs.set(name, { log, node: guarded });\n\t\t// M4: auto-wire any storage tiers attached via `attachEventStorage`.\n\t\tthis._autoWireStreamStorage(name, log);\n\t\treturn guarded;\n\t}\n\n\t/**\n\t * Get-or-create the per-aggregate event stream for `(type, aggregateId)`.\n\t * Mounts the stream as a sibling node named `<type>_<aggregateId>` so it\n\t * appears in `describe()`. LRU access is touched on every call.\n\t */\n\tprivate _ensureAggregateStream(type: string, aggregateId: string): EventEntry {\n\t\t// Ensure the fan-in stream exists too (call sites usually expect both).\n\t\tif (!this._eventLogs.has(type)) this.event(type);\n\n\t\tlet byType = this._eventLogsByAggregate.get(type);\n\t\tif (!byType) {\n\t\t\tbyType = new Map();\n\t\t\tthis._eventLogsByAggregate.set(type, byType);\n\t\t}\n\t\tconst lruKey = `${type}::${aggregateId}`;\n\t\tthis._touchAggregate(lruKey);\n\t\tconst existing = byType.get(aggregateId);\n\t\tif (existing) return existing;\n\n\t\tconst nodeName = `${type}_${aggregateId.replace(/[^a-zA-Z0-9_-]/g, \"_\")}`;\n\t\tconst log = reactiveLog<CqrsEvent>([], {\n\t\t\tname: nodeName,\n\t\t\tversioning: 0,\n\t\t\tmaxSize: this._retainedLimit,\n\t\t});\n\t\tlog.withLatest();\n\t\tconst entries = log.entries;\n\t\t// Avoid name collisions if multiple aggregates have ids that sanitize\n\t\t// to the same node-name; suffix with a counter when necessary.\n\t\t// Resolve the mount name BEFORE constructing the derived (the\n\t\t// `this.derived(...)` form registers under the supplied `name`).\n\t\tlet mountName = nodeName;\n\t\tlet collisionIdx = 0;\n\t\twhile (this.resolveOptional(mountName) !== undefined) {\n\t\t\tcollisionIdx += 1;\n\t\t\tmountName = `${nodeName}_${collisionIdx}`;\n\t\t}\n\t\tlet guarded: Node<readonly CqrsEvent[]>;\n\t\ttry {\n\t\t\tguarded = this.derived<readonly CqrsEvent[]>(\n\t\t\t\tmountName,\n\t\t\t\t[entries],\n\t\t\t\t(batchData, ctx) => {\n\t\t\t\t\tconst latest =\n\t\t\t\t\t\tbatchData[0] != null && batchData[0].length > 0\n\t\t\t\t\t\t\t? (batchData[0].at(-1) as readonly CqrsEvent[])\n\t\t\t\t\t\t\t: (ctx.prevData[0] as readonly CqrsEvent[]);\n\t\t\t\t\treturn [latest];\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tmeta: cqrsMeta(\"event_aggregate\", {\n\t\t\t\t\t\tevent_name: type,\n\t\t\t\t\t\taggregate_id: aggregateId,\n\t\t\t\t\t}),\n\t\t\t\t\tguard: EVENT_GUARD,\n\t\t\t\t\tinitial: entries.cache as readonly CqrsEvent[],\n\t\t\t\t},\n\t\t\t);\n\t\t} catch {\n\t\t\t// Name collision raced with another constructor — fall back to a\n\t\t\t// raw, unmounted node so the per-aggregate stream still functions\n\t\t\t// (just not graph-visible). Mirrors the prior best-effort branch.\n\t\t\t// F-CATCH D-4: surface-loud — the fallback node silently vanishes\n\t\t\t// from `graph.describe()`, which contradicts the dynamic-graph\n\t\t\t// observability guarantee. Warn once so the visibility loss is\n\t\t\t// diagnosable rather than mysterious.\n\t\t\tconsole.warn(\n\t\t\t\t`[GraphReFly] cqrs: per-aggregate event node \"${mountName}\" hit a ` +\n\t\t\t\t\t`name-collision race (event \"${type}\", aggregate \"${aggregateId}\") — ` +\n\t\t\t\t\t`falling back to an unmounted node; this aggregate's stream still ` +\n\t\t\t\t\t`works but will NOT appear in graph.describe()/observe().`,\n\t\t\t);\n\t\t\tguarded = node<readonly CqrsEvent[]>(\n\t\t\t\t[entries],\n\t\t\t\t(batchData, actions, ctx) => {\n\t\t\t\t\tconst latest =\n\t\t\t\t\t\tbatchData[0] != null && batchData[0].length > 0 ? batchData[0].at(-1) : ctx.prevData[0];\n\t\t\t\t\tactions.emit(latest as readonly CqrsEvent[]);\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tname: nodeName,\n\t\t\t\t\tdescribeKind: \"derived\",\n\t\t\t\t\tmeta: cqrsMeta(\"event_aggregate\", {\n\t\t\t\t\t\tevent_name: type,\n\t\t\t\t\t\taggregate_id: aggregateId,\n\t\t\t\t\t}),\n\t\t\t\t\tguard: EVENT_GUARD,\n\t\t\t\t\tinitial: entries.cache as readonly CqrsEvent[],\n\t\t\t\t},\n\t\t\t);\n\t\t}\n\t\tthis.addDisposer(keepalive(guarded));\n\t\tconst entry = { log, node: guarded };\n\t\tbyType.set(aggregateId, entry);\n\t\t// M4: auto-wire any tiers attached via `attachEventStorage`.\n\t\tthis._autoWireStreamStorage(`${type}::${aggregateId}`, log);\n\t\tthis._enforceAggregateLru();\n\t\treturn entry;\n\t}\n\n\t/** Try `resolve(path)`; return `undefined` instead of throwing on missing. */\n\tprivate resolveOptional(path: string): Node | undefined {\n\t\ttry {\n\t\t\treturn this.resolve(path);\n\t\t} catch {\n\t\t\treturn undefined;\n\t\t}\n\t}\n\n\t/** Internal: append to an event log, auto-registering if needed. */\n\tprivate _appendEvent(\n\t\teventName: string,\n\t\tpayload: unknown,\n\t\textra?: {\n\t\t\taggregateId?: string;\n\t\t\tcorrelationId?: string;\n\t\t\tcausationId?: string;\n\t\t\tmetadata?: Readonly<Record<string, unknown>>;\n\t\t\thandlerVersion?: { id: string; version: string | number };\n\t\t},\n\t): CqrsEvent {\n\t\tlet entry = this._eventLogs.get(eventName);\n\t\tif (!entry) {\n\t\t\tthis.event(eventName);\n\t\t\tentry = this._eventLogs.get(eventName)!;\n\t\t}\n\t\tif (entry.node.status === \"completed\" || entry.node.status === \"errored\") {\n\t\t\tthrow new Error(\n\t\t\t\t`Cannot dispatch to terminated event stream \"${eventName}\" (status: ${entry.node.status}).`,\n\t\t\t);\n\t\t}\n\n\t\t// Per-aggregate version + stream wiring (D1).\n\t\tlet aggregateVersion: number | undefined;\n\t\tlet aggregateEntry: EventEntry | undefined;\n\t\tif (extra?.aggregateId !== undefined) {\n\t\t\tconst lruKey = `${eventName}::${extra.aggregateId}`;\n\t\t\taggregateVersion = (this._aggregateVersions.get(lruKey) ?? 0) + 1;\n\t\t\tthis._aggregateVersions.set(lruKey, aggregateVersion);\n\t\t\taggregateEntry = this._ensureAggregateStream(eventName, extra.aggregateId);\n\t\t}\n\n\t\tconst nv = entry.log.entries.v;\n\t\tconst frozenPayload = this._freezeEventPayload ? deepFreeze(payload) : payload;\n\t\tconst evt: CqrsEvent = {\n\t\t\ttype: eventName,\n\t\t\tpayload: frozenPayload,\n\t\t\ttimestampNs: wallClockNs(),\n\t\t\tseq: ++this._seq,\n\t\t\t...(extra?.aggregateId !== undefined ? { aggregateId: extra.aggregateId } : {}),\n\t\t\t...(aggregateVersion !== undefined ? { aggregateVersion } : {}),\n\t\t\t...(extra?.correlationId !== undefined ? { correlationId: extra.correlationId } : {}),\n\t\t\t...(extra?.causationId !== undefined ? { causationId: extra.causationId } : {}),\n\t\t\t...(extra?.metadata !== undefined ? { metadata: Object.freeze({ ...extra.metadata }) } : {}),\n\t\t\t...(extra?.handlerVersion !== undefined ? { handlerVersion: extra.handlerVersion } : {}),\n\t\t\t...(nv != null ? { v0: { id: nv.id, version: nv.version } } : {}),\n\t\t};\n\t\t// Append to fan-in stream (always) and per-aggregate stream (when set).\n\t\tentry.log.append(evt);\n\t\tif (aggregateEntry) {\n\t\t\taggregateEntry.log.append(evt);\n\t\t}\n\t\treturn evt;\n\t}\n\n\t// -- Commands -------------------------------------------------------------\n\n\t/**\n\t * Register a command with its handler. Guard denies `observe` (write-only).\n\t * Use `dispatch(name, payload)` to execute.\n\t *\n\t * The command node carries dynamic `meta.error` — a reactive companion\n\t * that holds the last handler error (or `null` on success).\n\t */\n\tcommand<T = unknown>(\n\t\tname: string,\n\t\thandlerOrReg: CommandHandler<T> | CommandRegistration<T>,\n\t): Node<T> {\n\t\tif (this._commandRegs.has(name)) {\n\t\t\tthrow new DuplicateRegistrationError(\"command\", name);\n\t\t}\n\t\tconst reg: CommandRegistration<T> =\n\t\t\ttypeof handlerOrReg === \"function\" ? { handler: handlerOrReg } : handlerOrReg;\n\t\tconst cmdNode = this.state<T>(name, undefined as T, {\n\t\t\tmeta: {\n\t\t\t\t...cqrsMeta(\"command\", { command_name: name }),\n\t\t\t\terror: null,\n\t\t\t},\n\t\t\tguard: COMMAND_GUARD,\n\t\t});\n\t\tthis._commandRegs.set(name, {\n\t\t\thandler: reg.handler as CommandHandler<unknown>,\n\t\t\t...(reg.emits !== undefined ? { emits: reg.emits } : {}),\n\t\t\t...(reg.handlerVersion !== undefined ? { handlerVersion: reg.handlerVersion } : {}),\n\t\t});\n\t\t// Pre-register declared event streams so describe() shows them.\n\t\tif (reg.emits) {\n\t\t\tfor (const e of reg.emits) {\n\t\t\t\tif (!this._eventLogs.has(e)) this.event(e);\n\t\t\t}\n\t\t}\n\t\treturn cmdNode;\n\t}\n\n\t/**\n\t * Execute a registered command. Wraps the entire dispatch in `batch()` so\n\t * the command node DATA and all emitted events settle atomically.\n\t *\n\t * If the handler throws, `meta.error` on the command node is set to the\n\t * error and the exception is re-thrown.\n\t *\n\t * **Tier 8 / COMPOSITION-GUIDE §35:** dispatch routes through the shared\n\t * {@link mutate} framework so freeze / rollback-on-throw / seq-cursor\n\t * advance / audit-record stamping flow through one centralized helper.\n\t * Failure records emit OUTSIDE the rolled-back batch (M5 / C4 invariants\n\t * preserved by the framework).\n\t */\n\tdispatch<T = unknown>(commandName: string, payload: T, opts?: DispatchOptions): void {\n\t\tconst reg = this._commandRegs.get(commandName);\n\t\tif (!reg) throw new UnknownCommandError(commandName);\n\n\t\t// D1: optimistic-concurrency check fires BEFORE the handler runs and\n\t\t// BEFORE the batch opens, so a stale-version dispatch is a clean\n\t\t// no-op (no audit record, no rollback). Only meaningful when both\n\t\t// aggregateId and expectedAggregateVersion are set; otherwise no-op.\n\t\tif (\n\t\t\topts?.aggregateId !== undefined &&\n\t\t\topts.expectedAggregateVersion !== undefined &&\n\t\t\treg.emits !== undefined\n\t\t) {\n\t\t\t// Verify against ANY of the declared `emits` types — the dispatch's\n\t\t\t// aggregate version is per (type, aggregateId), but a single\n\t\t\t// dispatch may emit across multiple types. We check the FIRST\n\t\t\t// declared `emits` type that has a version recorded for this\n\t\t\t// aggregate; if none has a version, the aggregate is considered\n\t\t\t// at version 0.\n\t\t\tlet observedVersion = 0;\n\t\t\tfor (const t of reg.emits) {\n\t\t\t\tconst v = this._aggregateVersions.get(`${t}::${opts.aggregateId}`);\n\t\t\t\tif (v !== undefined && v > observedVersion) observedVersion = v;\n\t\t\t}\n\t\t\tif (observedVersion !== opts.expectedAggregateVersion) {\n\t\t\t\tthrow new OptimisticConcurrencyError(\n\t\t\t\t\topts.aggregateId,\n\t\t\t\t\topts.expectedAggregateVersion,\n\t\t\t\t\tobservedVersion,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tconst cmdNode = this.resolve(commandName);\n\t\tconst emittedEvents: string[] = [];\n\t\t// `actionThrew` distinguishes user-handler failures (where we want to\n\t\t// stamp `cmdNode.meta.error`) from framework-internal failures (which\n\t\t// shouldn't leak as a \"command failed\" signal).\n\t\tlet actionThrew = false;\n\n\t\tconst action = (sealed: T): void => {\n\t\t\tcmdNode.emit(sealed, { internal: true });\n\t\t\ttry {\n\t\t\t\treg.handler(sealed, {\n\t\t\t\t\temit: (eName, data) => {\n\t\t\t\t\t\t// Wave C.2 Unit 19: if emits was declared, reject undeclared names.\n\t\t\t\t\t\tif (reg.emits !== undefined && !reg.emits.includes(eName)) {\n\t\t\t\t\t\t\tthrow new UndeclaredEmitError(commandName, eName, reg.emits);\n\t\t\t\t\t\t}\n\t\t\t\t\t\temittedEvents.push(eName);\n\t\t\t\t\t\tthis._appendEvent(eName, data, {\n\t\t\t\t\t\t\t// D1: thread the dispatch's aggregateId through so events\n\t\t\t\t\t\t\t// participate in per-aggregate versioning. Handlers can\n\t\t\t\t\t\t\t// override per-emit by passing their own through a richer\n\t\t\t\t\t\t\t// emit signature (future extension).\n\t\t\t\t\t\t\t...(opts?.aggregateId !== undefined ? { aggregateId: opts.aggregateId } : {}),\n\t\t\t\t\t\t\t...(opts?.correlationId !== undefined ? { correlationId: opts.correlationId } : {}),\n\t\t\t\t\t\t\t...(opts?.causationId !== undefined ? { causationId: opts.causationId } : {}),\n\t\t\t\t\t\t\t...(opts?.metadata !== undefined\n\t\t\t\t\t\t\t\t? { metadata: Object.freeze({ ...opts.metadata }) }\n\t\t\t\t\t\t\t\t: {}),\n\t\t\t\t\t\t\t...(reg.handlerVersion !== undefined ? { handlerVersion: reg.handlerVersion } : {}),\n\t\t\t\t\t\t});\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t\tcmdNode.meta.error.emit(null, { internal: true });\n\t\t\t} catch (err) {\n\t\t\t\tactionThrew = true;\n\t\t\t\tthrow err;\n\t\t\t}\n\t\t};\n\n\t\ttry {\n\t\t\tmutate<[T], void, DispatchRecord>(action, {\n\t\t\t\tframe: \"transactional\",\n\t\t\t\tlog: this.dispatches,\n\t\t\t\tseq: this._dispatchSeqCursor,\n\t\t\t\tfreeze: this._freezeCommandPayload,\n\t\t\t\tonSuccessRecord: ([sealed], _result, { t_ns, seq }) => ({\n\t\t\t\t\tcommandName,\n\t\t\t\t\tpayload: sealed,\n\t\t\t\t\toutcome: \"success\",\n\t\t\t\t\temittedEvents: [...emittedEvents],\n\t\t\t\t\tt_ns,\n\t\t\t\t\tseq: seq ?? 0,\n\t\t\t\t\t...(reg.handlerVersion !== undefined ? { handlerVersion: reg.handlerVersion } : {}),\n\t\t\t\t}),\n\t\t\t\tonFailureRecord: ([sealed], err, { t_ns, seq, errorType }) => {\n\t\t\t\t\tconst wrapped =\n\t\t\t\t\t\terr instanceof CommandHandlerError ? err : new CommandHandlerError(commandName, err);\n\t\t\t\t\treturn {\n\t\t\t\t\t\tcommandName,\n\t\t\t\t\t\tpayload: sealed,\n\t\t\t\t\t\toutcome: \"failure\",\n\t\t\t\t\t\terror: wrapped,\n\t\t\t\t\t\terrorType,\n\t\t\t\t\t\temittedEvents: [...emittedEvents],\n\t\t\t\t\t\tt_ns,\n\t\t\t\t\t\tseq: seq ?? 0,\n\t\t\t\t\t\t...(reg.handlerVersion !== undefined ? { handlerVersion: reg.handlerVersion } : {}),\n\t\t\t\t\t};\n\t\t\t\t},\n\t\t\t})(payload);\n\t\t} catch (outerErr) {\n\t\t\t// C4 preservation: only stamp `cmdNode.meta.error` when the user\n\t\t\t// handler threw (not when framework infra threw before the action\n\t\t\t// ran). The framework already routed onFailure for the action-throw\n\t\t\t// case via `mutate` outside the rolled-back batch.\n\t\t\tif (actionThrew) {\n\t\t\t\tcmdNode.meta.error.emit(outerErr, { internal: true });\n\t\t\t}\n\t\t\tthrow outerErr;\n\t\t}\n\t}\n\n\t// -- Projections ----------------------------------------------------------\n\n\t/**\n\t * Register a read-only projection derived from event streams.\n\t * Guard denies `write` — value is computed from events only.\n\t *\n\t * **Wave C.3 Unit 21 (locked 2026-04-24):**\n\t * - Object-bag signature replaces the positional `(name, events, reducer, initial)` form.\n\t * - `mode: \"scan\"` (default) — incremental fold; `\"replay\"` — full replay each wave.\n\t * - `snapshot` integration for cold-start load + auto-checkpoint save.\n\t * - `freezeInputs` (default `true`) — freeze the event array before passing to reducer.\n\t * - Returns `ProjectionController<TState>` with `.node`, `.rebuild()`, `.reset()`.\n\t *\n\t * Fan-in across `events` is implemented by depending on all event-type fan-in\n\t * nodes directly, which preserves `describe()` edges (e.g. `orderPlaced →\n\t * orderCount`). Events are sorted by `(timestampNs, seq, aggregateId)` before\n\t * passing to the reducer (Option-3 cross-aggregate ordering, C.3).\n\t */\n\tprojection<TState>(opts: ProjectionOptions<TState>): ProjectionController<TState> {\n\t\tconst { name, events: eventNames, reducer, initial } = opts;\n\t\tconst mode = opts.mode ?? \"scan\";\n\t\tconst freezeInputs = opts.freezeInputs ?? true;\n\t\tconst snapshotOpts = opts.snapshot;\n\n\t\t// Ensure each event stream exists and collect its node.\n\t\t// Using the event-type fan-in nodes directly as deps preserves\n\t\t// `describe()` edges (orderPlaced → orderCount) per Audit 1 §24.\n\t\tconst eventNodes = eventNames.map((eName) => {\n\t\t\tif (!this._eventLogs.has(eName)) this.event(eName);\n\t\t\treturn this._eventLogs.get(eName)!.node;\n\t\t});\n\n\t\t// Sort comparator: timestampNs → seq → aggregateId lex (Option-3, C.3).\n\t\tfunction sortEvents(evts: CqrsEvent[]): void {\n\t\t\tevts.sort(\n\t\t\t\t(a, b) =>\n\t\t\t\t\ta.timestampNs - b.timestampNs ||\n\t\t\t\t\ta.seq - b.seq ||\n\t\t\t\t\t(a.aggregateId ?? \"\").localeCompare(b.aggregateId ?? \"\"),\n\t\t\t);\n\t\t}\n\n\t\t// Collect all events from the current snapshots (for seeding + rebuild).\n\t\tfunction collectAllEvents(snapshots: readonly (readonly CqrsEvent[])[]): CqrsEvent[] {\n\t\t\tconst evts: CqrsEvent[] = [];\n\t\t\tfor (const snap of snapshots) evts.push(...snap);\n\t\t\tsortEvents(evts);\n\t\t\treturn evts;\n\t\t}\n\n\t\t// Seed: collect any events already present at construction time.\n\t\tconst seedSnapshots = eventNodes.map(\n\t\t\t(n) => (n.cache as readonly CqrsEvent[] | undefined) ?? ([] as readonly CqrsEvent[]),\n\t\t);\n\t\tconst sortedSeed = collectAllEvents(seedSnapshots);\n\t\tconst frozenSeed = (\n\t\t\tfreezeInputs ? Object.freeze(sortedSeed) : sortedSeed\n\t\t) as readonly CqrsEvent[];\n\n\t\t// Scan state: tracks count of events processed in last run.\n\t\tlet lastProcessedCount = 0;\n\t\tlet scanState: TState = initial;\n\n\t\tif (mode === \"scan\" && sortedSeed.length > 0) {\n\t\t\tscanState = reducer(initial, frozenSeed);\n\t\t\tlastProcessedCount = sortedSeed.length;\n\t\t}\n\t\tconst seedState = mode === \"replay\" ? reducer(initial, frozenSeed) : scanState;\n\n\t\t// Snapshot save state — debounce + saveEvery.\n\t\tconst saveDebounceMs = snapshotOpts?.saveDebounceMs ?? 1000;\n\t\tconst saveEvery = snapshotOpts?.saveEvery ?? 1000;\n\t\tlet saveTimer: ReturnType<typeof setTimeout> | undefined;\n\t\tlet savesSinceLastFlush = 0;\n\n\t\tfunction scheduleSave(currentState: TState): void {\n\t\t\tif (!snapshotOpts?.save) return;\n\t\t\tsavesSinceLastFlush += 1;\n\t\t\tif (savesSinceLastFlush >= saveEvery) {\n\t\t\t\tsavesSinceLastFlush = 0;\n\t\t\t\tif (saveTimer !== undefined) {\n\t\t\t\t\tclearTimeout(saveTimer);\n\t\t\t\t\tsaveTimer = undefined;\n\t\t\t\t}\n\t\t\t\tconst result = snapshotOpts.save(currentState);\n\t\t\t\tif (result instanceof Promise) result.catch(() => undefined);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (saveTimer !== undefined) clearTimeout(saveTimer);\n\t\t\tsaveTimer = setTimeout(() => {\n\t\t\t\tsaveTimer = undefined;\n\t\t\t\tsavesSinceLastFlush = 0;\n\t\t\t\tconst result = snapshotOpts!.save!(currentState);\n\t\t\t\tif (result instanceof Promise) result.catch(() => undefined);\n\t\t\t}, saveDebounceMs);\n\t\t}\n\n\t\tconst projNode = this.derived<TState>(\n\t\t\tname,\n\t\t\teventNames as readonly string[],\n\t\t\t(batchData, ctx) => {\n\t\t\t\tconst snapshots = 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\tconst allEvents = collectAllEvents(snapshots as readonly (readonly CqrsEvent[])[]);\n\n\t\t\t\tlet newState: TState;\n\t\t\t\tif (mode === \"replay\") {\n\t\t\t\t\t// m1: freeze only in the replay branch where `frozen` is actually used.\n\t\t\t\t\tconst frozen = (\n\t\t\t\t\t\tfreezeInputs ? Object.freeze(allEvents) : allEvents\n\t\t\t\t\t) as readonly CqrsEvent[];\n\t\t\t\t\tnewState = reducer(initial, frozen);\n\t\t\t\t} else {\n\t\t\t\t\t// scan: only fold NEW events since last run.\n\t\t\t\t\tconst newOnly = allEvents.slice(lastProcessedCount);\n\t\t\t\t\tlastProcessedCount = allEvents.length;\n\t\t\t\t\tconst frozenNew = (\n\t\t\t\t\t\tfreezeInputs ? Object.freeze(newOnly) : newOnly\n\t\t\t\t\t) as readonly CqrsEvent[];\n\t\t\t\t\tnewState = reducer(scanState, frozenNew);\n\t\t\t\t\tscanState = newState;\n\t\t\t\t}\n\n\t\t\t\tscheduleSave(newState);\n\t\t\t\treturn [newState];\n\t\t\t},\n\t\t\t{\n\t\t\t\tmeta: cqrsMeta(\"projection\", { projection_name: name, source_events: eventNames }),\n\t\t\t\tguard: PROJECTION_GUARD,\n\t\t\t\tinitial: seedState,\n\t\t\t},\n\t\t);\n\n\t\tthis.addDisposer(keepalive(projNode));\n\t\tthis.addDisposer(() => {\n\t\t\tif (saveTimer !== undefined) {\n\t\t\t\tclearTimeout(saveTimer);\n\t\t\t\tsaveTimer = undefined;\n\t\t\t}\n\t\t});\n\t\tthis._projections.add(name);\n\n\t\t// -- Controller methods ---------------------------------------------------\n\n\t\tconst rebuild = async (rebuildOpts?: {\n\t\t\tfromTier?: AppendLogStorageTier<CqrsEvent>;\n\t\t\tpageSize?: number;\n\t\t}): Promise<TState> => {\n\t\t\ttry {\n\t\t\t\tconst pageSize = rebuildOpts?.pageSize ?? 1000;\n\t\t\t\tconst tier = rebuildOpts?.fromTier ?? this._attachedEventTiers[0]?.[0];\n\n\t\t\t\t// M5: snapshot in-memory event count BEFORE any async work so we can\n\t\t\t\t// drain events that arrive concurrently during the paginated rebuild.\n\t\t\t\tconst preBuildCount = collectAllEvents(\n\t\t\t\t\teventNodes.map((n) => (n.cache as readonly CqrsEvent[] | undefined) ?? []),\n\t\t\t\t).length;\n\n\t\t\t\t// Seed from snapshot.load if provided.\n\t\t\t\tlet rebuildState: TState = initial;\n\t\t\t\tif (snapshotOpts?.load) {\n\t\t\t\t\tconst loaded = await snapshotOpts.load();\n\t\t\t\t\tif (loaded !== undefined) rebuildState = loaded;\n\t\t\t\t}\n\n\t\t\t\tif (!tier || !tier.loadEntries) {\n\t\t\t\t\t// No storage tier — fold in-memory events as a best-effort rebuild.\n\t\t\t\t\tconst inMemory = collectAllEvents(\n\t\t\t\t\t\teventNodes.map((n) => (n.cache as readonly CqrsEvent[] | undefined) ?? []),\n\t\t\t\t\t);\n\t\t\t\t\tconst frozen = (\n\t\t\t\t\t\tfreezeInputs ? Object.freeze(inMemory) : inMemory\n\t\t\t\t\t) as readonly CqrsEvent[];\n\t\t\t\t\trebuildState = reducer(rebuildState, frozen);\n\t\t\t\t} else {\n\t\t\t\t\t// Paginated load from tier.\n\t\t\t\t\t// m3: only fold events that belong to this projection's event-type set.\n\t\t\t\t\t// Tiers may hold events from other projections; filtering keeps reducer\n\t\t\t\t\t// correctness when the tier is shared across projections.\n\t\t\t\t\tconst watchedEvents = new Set<string>(eventNames as readonly string[]);\n\t\t\t\t\tlet cursor: import(\"@graphrefly/pure-ts/extra\").AppendCursor | undefined;\n\t\t\t\t\tlet done = false;\n\t\t\t\t\twhile (!done) {\n\t\t\t\t\t\tconst result = await tier.loadEntries({ cursor, pageSize });\n\t\t\t\t\t\tconst page = [...result.entries].filter((e) => watchedEvents.has(e.type));\n\t\t\t\t\t\tsortEvents(page);\n\t\t\t\t\t\tconst frozenPage = (freezeInputs ? Object.freeze(page) : page) as readonly CqrsEvent[];\n\t\t\t\t\t\trebuildState = reducer(rebuildState, frozenPage);\n\t\t\t\t\t\tcursor = result.cursor;\n\t\t\t\t\t\tdone = !cursor || result.entries.length === 0;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Update the live projection node with the rebuilt state.\n\t\t\t\tif (mode === \"scan\") {\n\t\t\t\t\t// M5: drain events that arrived during the async rebuild so they are\n\t\t\t\t\t// not lost (race between paginated load and concurrent dispatch).\n\t\t\t\t\tconst allInMemory = collectAllEvents(\n\t\t\t\t\t\teventNodes.map((n) => (n.cache as readonly CqrsEvent[] | undefined) ?? []),\n\t\t\t\t\t);\n\t\t\t\t\tconst pendingEvents = allInMemory.slice(preBuildCount);\n\t\t\t\t\tif (pendingEvents.length > 0) {\n\t\t\t\t\t\tconst frozenPending = (\n\t\t\t\t\t\t\tfreezeInputs ? Object.freeze(pendingEvents) : pendingEvents\n\t\t\t\t\t\t) as readonly CqrsEvent[];\n\t\t\t\t\t\trebuildState = reducer(rebuildState, frozenPending);\n\t\t\t\t\t}\n\t\t\t\t\tscanState = rebuildState;\n\t\t\t\t\tlastProcessedCount = allInMemory.length;\n\t\t\t\t}\n\t\t\t\tprojNode.emit(rebuildState, { internal: true });\n\t\t\t\treturn rebuildState;\n\t\t\t} catch (err) {\n\t\t\t\tthrow new RebuildError(name, err);\n\t\t\t}\n\t\t};\n\n\t\tconst reset = async (): Promise<TState> => {\n\t\t\ttry {\n\t\t\t\t// Reload from snapshot.load (if configured).\n\t\t\t\tlet baseState: TState = initial;\n\t\t\t\tif (snapshotOpts?.load) {\n\t\t\t\t\tconst loaded = await snapshotOpts.load();\n\t\t\t\t\tif (loaded !== undefined) baseState = loaded;\n\t\t\t\t}\n\n\t\t\t\t// Re-fold all in-memory events on top of the snapshot state.\n\t\t\t\tconst inMemory = collectAllEvents(\n\t\t\t\t\teventNodes.map((n) => (n.cache as readonly CqrsEvent[] | undefined) ?? []),\n\t\t\t\t);\n\t\t\t\tconst frozen = (freezeInputs ? Object.freeze(inMemory) : inMemory) as readonly CqrsEvent[];\n\t\t\t\tconst newState = reducer(baseState, frozen);\n\n\t\t\t\tif (mode === \"scan\") {\n\t\t\t\t\tscanState = newState;\n\t\t\t\t\tlastProcessedCount = inMemory.length;\n\t\t\t\t}\n\t\t\t\tprojNode.emit(newState, { internal: true });\n\t\t\t\treturn newState;\n\t\t\t} catch (err) {\n\t\t\t\tthrow new RebuildError(name, err);\n\t\t\t}\n\t\t};\n\n\t\treturn { node: projNode, rebuild, reset };\n\t}\n\n\t// -- Sagas ----------------------------------------------------------------\n\n\t/**\n\t * Register an event-driven side effect. Runs handler for each **new** event\n\t * from the specified streams (tracks last-processed entry count per stream).\n\t *\n\t * The saga node carries dynamic `meta.error` — a reactive companion that\n\t * holds the last handler error (or `null` on success). Handler errors do\n\t * not propagate out of the saga run (the event cursor still advances so\n\t * the same entry is not delivered twice).\n\t */\n\tsaga<T = unknown>(\n\t\tname: string,\n\t\teventNames: readonly string[],\n\t\thandler: SagaHandler<T>,\n\t\topts: SagaOptions = {},\n\t): SagaController<T> {\n\t\tconst _eventNodes = eventNames.map((eName) => {\n\t\t\tif (!this._eventLogs.has(eName)) this.event(eName);\n\t\t\treturn this._eventLogs.get(eName)!.node;\n\t\t});\n\n\t\t// Audit 2: per-event-type cursor state nodes (replaces closure Map).\n\t\t// Mount under `<saga>_cursor` (no `::` — that's the path separator).\n\t\tconst cursors = registerCursorMap(this, `${name}_cursor`, eventNames as readonly string[], 0);\n\t\t// Audit 2: invocations log (companion + alias).\n\t\tconst invocations = createAuditLog<SagaInvocation<T>>({\n\t\t\tname: `${name}_invocations`,\n\t\t\tretainedLimit: this._retainedLimit,\n\t\t\tgraph: this,\n\t\t});\n\t\tconst aggregateFilter = opts.aggregateId;\n\t\tconst errorPolicy = opts.errorPolicy ?? \"advance\";\n\n\t\t// D2: subscribe-and-capture-mirror for cursor reads — avoid `.cache`\n\t\t// access from inside the saga node's fn. Each cursor mirrors into a\n\t\t// closure variable updated by an external subscription. Cursor writes\n\t\t// (`cursor.emit(advancedTo)`) and `invocations.append(...)` from inside\n\t\t// the fn are sanctioned effect-side-effects (saga's `describeKind:\n\t\t// \"effect\"`) — not §5.9 imperative-trigger violations.\n\t\tconst latestCursors = new Map<string, number>();\n\t\tfor (const eName of eventNames) {\n\t\t\tconst cursor = cursors[eName]!;\n\t\t\tlatestCursors.set(eName, (cursor.cache as number | undefined) ?? 0);\n\t\t\tconst sub = cursor.subscribe((msgs) => {\n\t\t\t\tfor (const m of msgs) if (m[0] === DATA) latestCursors.set(eName, m[1] as number);\n\t\t\t});\n\t\t\tthis.addDisposer(sub);\n\t\t}\n\n\t\t// Tier 8 / COMPOSITION-GUIDE §35: per-event handler invocation routes\n\t\t// through `mutate` so handler-version stamping + audit-record\n\t\t// shape stay centralized. Failure path re-throws — the saga's outer\n\t\t// try/catch honors `errorPolicy` (\"advance\" vs \"hold\"). Action takes\n\t\t// `(ev, eName)` so the wrapper can be hoisted once for all event types.\n\t\tconst auditedHandler = mutate<[CqrsEvent<T>, string], void, SagaInvocation<T>>(\n\t\t\t(ev, _eName) => {\n\t\t\t\thandler(ev);\n\t\t\t},\n\t\t\t{\n\t\t\t\tframe: \"inline\",\n\t\t\t\tlog: invocations,\n\t\t\t\tfreeze: false,\n\t\t\t\t...(opts.handlerVersion !== undefined ? { handlerVersion: opts.handlerVersion } : {}),\n\t\t\t\t// D5 (qa lock): always include the `aggregateId` key (even when\n\t\t\t\t// undefined) for parity with the pre-Tier-8 saga record shape.\n\t\t\t\t// Consumers using `Object.hasOwn(record, \"aggregateId\")` or JSON\n\t\t\t\t// serialization shape would observe a pre-1.0 break otherwise.\n\t\t\t\tonSuccessRecord: ([ev, eName], _r, { t_ns }) => ({\n\t\t\t\t\teventType: eName,\n\t\t\t\t\toutcome: \"success\",\n\t\t\t\t\taggregateId: ev.aggregateId,\n\t\t\t\t\tevent: ev,\n\t\t\t\t\tt_ns,\n\t\t\t\t}),\n\t\t\t\tonFailureRecord: ([ev, eName], err, { t_ns, errorType }) => ({\n\t\t\t\t\teventType: eName,\n\t\t\t\t\toutcome: \"failure\",\n\t\t\t\t\terror: err,\n\t\t\t\t\terrorType,\n\t\t\t\t\taggregateId: ev.aggregateId,\n\t\t\t\t\tevent: ev,\n\t\t\t\t\tt_ns,\n\t\t\t\t}),\n\t\t\t},\n\t\t);\n\n\t\tconst sagaRef: { n?: Node<unknown> } = {};\n\t\tconst sagaNode = this.effect(\n\t\t\tname,\n\t\t\teventNames as readonly string[],\n\t\t\t(snapshots, _up) => {\n\t\t\t\tconst errNode = sagaRef.n!.meta.error as Node<unknown>;\n\t\t\t\tfor (let i = 0; i < snapshots.length; i++) {\n\t\t\t\t\tconst batch = snapshots[i];\n\t\t\t\t\tif (batch == null || batch.length === 0) continue;\n\t\t\t\t\tconst entries = batch.at(-1) as readonly CqrsEvent<T>[] | undefined;\n\t\t\t\t\tif (!entries) continue;\n\t\t\t\t\tconst eName = eventNames[i] as string;\n\t\t\t\t\tconst cursor = cursors[eName]!;\n\t\t\t\t\tconst lastCount = latestCursors.get(eName) ?? 0;\n\t\t\t\t\tif (entries.length > lastCount) {\n\t\t\t\t\t\tconst newEntries = entries.slice(lastCount);\n\t\t\t\t\t\tlet advancedTo = lastCount;\n\t\t\t\t\t\tfor (const entry of newEntries) {\n\t\t\t\t\t\t\tconst ev = entry as CqrsEvent<T>;\n\t\t\t\t\t\t\tif (aggregateFilter !== undefined && ev.aggregateId !== aggregateFilter) {\n\t\t\t\t\t\t\t\tadvancedTo += 1;\n\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\tauditedHandler(ev, eName);\n\t\t\t\t\t\t\t\terrNode.emit(null, { internal: true });\n\t\t\t\t\t\t\t\tadvancedTo += 1;\n\t\t\t\t\t\t\t} catch (err) {\n\t\t\t\t\t\t\t\terrNode.emit(err, { internal: true });\n\t\t\t\t\t\t\t\tif (errorPolicy === \"hold\") break;\n\t\t\t\t\t\t\t\t// \"advance\" — skip past failure, keep going.\n\t\t\t\t\t\t\t\tadvancedTo += 1;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcursor.emit(advancedTo);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t},\n\t\t\t{\n\t\t\t\tmeta: {\n\t\t\t\t\t...cqrsMeta(\"saga\", { saga_name: name, source_events: eventNames }),\n\t\t\t\t\terror: null,\n\t\t\t\t},\n\t\t\t},\n\t\t) as Node<unknown>;\n\t\tsagaRef.n = sagaNode;\n\n\t\tthis.addDisposer(keepalive(sagaNode));\n\t\tthis._sagas.add(name);\n\t\treturn {\n\t\t\tnode: sagaNode,\n\t\t\tinvocations,\n\t\t\taudit: readonlyAuditLog(invocations),\n\t\t\tcursors,\n\t\t};\n\t}\n}\n\n// ---------------------------------------------------------------------------\n// Factory\n// ---------------------------------------------------------------------------\n\n/**\n * Create a CQRS graph container.\n *\n * @example\n * ```ts\n * const app = cqrs(\"orders\");\n * app.event(\"orderPlaced\");\n * app.command(\"placeOrder\", (payload, { emit }) => {\n * emit(\"orderPlaced\", { orderId: payload.id, amount: payload.amount });\n * });\n * const { node: orderCount } = app.projection({\n * name: \"orderCount\",\n * events: [\"orderPlaced\"],\n * reducer: (_s, events) => events.length,\n * initial: 0,\n * });\n * app.dispatch(\"placeOrder\", { id: \"1\", amount: 100 });\n * ```\n */\nexport function cqrs<EM extends CqrsEventMap = Record<string, unknown>>(\n\tname: string,\n\topts?: CqrsOptions,\n): CqrsGraph<EM> {\n\tconst g = new CqrsGraph<EM>(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 `CqrsOptions.graph` may carry non-JSON fields.\n\tconst { factory: _f, factoryArgs: _fa, ...tagArgs } = (opts ?? {}) as Record<string, unknown>;\n\tg.tagFactory(\"cqrs\", placeholderArgs(tagArgs));\n\treturn g;\n}\n","/**\n * Shared pattern-layer error hierarchy (Audit 2 — locked 2026-04-24).\n *\n * Cross-primitive `instanceof` checks: gate, queue, cqrs.dispatch, saga,\n * projection, subscription, etc. all use these classes so consumer error\n * handlers can branch by kind rather than by message string.\n *\n * @internal — exposed via patterns/index for primitive impls; consumers\n * should import the relevant class from the primitive's barrel.\n */\n\n/** Root error class. All pattern-layer errors extend this. */\nexport class GraphReFlyError extends Error {\n\tconstructor(message: string, options?: ErrorOptions) {\n\t\tsuper(message, options);\n\t\tthis.name = this.constructor.name;\n\t}\n}\n\n/** Re-registering a name that's already taken (command, gate, queue, saga, projection). */\nexport class DuplicateRegistrationError extends GraphReFlyError {\n\tconstructor(\n\t\treadonly kind: string,\n\t\treadonly registrationName: string,\n\t) {\n\t\tsuper(`Duplicate ${kind} registration: \"${registrationName}\"`);\n\t}\n}\n\n/** CQRS handler emitted an event type not in its declared `emits` set. */\nexport class UndeclaredEmitError extends GraphReFlyError {\n\tconstructor(\n\t\treadonly commandName: string,\n\t\treadonly eventName: string,\n\t\treadonly declaredEmits: readonly string[],\n\t) {\n\t\tsuper(\n\t\t\t`Command \"${commandName}\" emitted undeclared event \"${eventName}\". Declared emits: [${declaredEmits.join(\", \")}]`,\n\t\t);\n\t}\n}\n\n/** Aggregate version expected vs observed mismatch on dispatch. */\nexport class OptimisticConcurrencyError extends GraphReFlyError {\n\tconstructor(\n\t\treadonly aggregateId: string,\n\t\treadonly expected: number,\n\t\treadonly actual: number,\n\t) {\n\t\tsuper(\n\t\t\t`Optimistic concurrency conflict on aggregate \"${aggregateId}\": expected version ${expected}, got ${actual}`,\n\t\t);\n\t}\n}\n\n/** `dispatch(name, ...)` for a name that wasn't registered via `command()`. */\nexport class UnknownCommandError extends GraphReFlyError {\n\tconstructor(readonly commandName: string) {\n\t\tsuper(`Unknown command: \"${commandName}\". Register with command() first.`);\n\t}\n}\n\n/** Wrap any error thrown from inside a command handler. Original on `cause`. */\nexport class CommandHandlerError extends GraphReFlyError {\n\tconstructor(\n\t\treadonly commandName: string,\n\t\tcause: unknown,\n\t) {\n\t\tsuper(\n\t\t\t`Command handler \"${commandName}\" threw: ${cause instanceof Error ? cause.message : String(cause)}`,\n\t\t\t{ cause },\n\t\t);\n\t}\n}\n\n/** Mutation method called after the primitive was torn down. */\nexport class TeardownError extends GraphReFlyError {\n\tconstructor(\n\t\treadonly kind: string,\n\t\treadonly method: string,\n\t) {\n\t\tsuper(`${kind}: ${method}() called after teardown`);\n\t}\n}\n\n/** Projection rebuild failure — adapter / decode / reducer error. */\nexport class RebuildError extends GraphReFlyError {\n\tconstructor(\n\t\treadonly projectionName: string,\n\t\tcause: unknown,\n\t) {\n\t\tsuper(\n\t\t\t`Projection \"${projectionName}\" rebuild failed: ${cause instanceof Error ? cause.message : String(cause)}`,\n\t\t\t{ cause },\n\t\t);\n\t}\n}\n"],"mappings":";;;;;;;;;;;;AAaA;AAAA,EACC;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACM;AAEP,SAAiC,mBAAmB;AACpD,SAAS,aAAgC;;;ACXlC,IAAM,kBAAN,cAA8B,MAAM;AAAA,EAC1C,YAAY,SAAiB,SAAwB;AACpD,UAAM,SAAS,OAAO;AACtB,SAAK,OAAO,KAAK,YAAY;AAAA,EAC9B;AACD;AAGO,IAAM,6BAAN,cAAyC,gBAAgB;AAAA,EAC/D,YACU,MACA,kBACR;AACD,UAAM,aAAa,IAAI,mBAAmB,gBAAgB,GAAG;AAHpD;AACA;AAAA,EAGV;AACD;AAGO,IAAM,sBAAN,cAAkC,gBAAgB;AAAA,EACxD,YACU,aACA,WACA,eACR;AACD;AAAA,MACC,YAAY,WAAW,+BAA+B,SAAS,uBAAuB,cAAc,KAAK,IAAI,CAAC;AAAA,IAC/G;AANS;AACA;AACA;AAAA,EAKV;AACD;AAGO,IAAM,6BAAN,cAAyC,gBAAgB;AAAA,EAC/D,YACU,aACA,UACA,QACR;AACD;AAAA,MACC,iDAAiD,WAAW,uBAAuB,QAAQ,SAAS,MAAM;AAAA,IAC3G;AANS;AACA;AACA;AAAA,EAKV;AACD;AAGO,IAAM,sBAAN,cAAkC,gBAAgB;AAAA,EACxD,YAAqB,aAAqB;AACzC,UAAM,qBAAqB,WAAW,mCAAmC;AADrD;AAAA,EAErB;AACD;AAGO,IAAM,sBAAN,cAAkC,gBAAgB;AAAA,EACxD,YACU,aACT,OACC;AACD;AAAA,MACC,oBAAoB,WAAW,YAAY,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,CAAC;AAAA,MACjG,EAAE,MAAM;AAAA,IACT;AANS;AAAA,EAOV;AACD;AAGO,IAAM,gBAAN,cAA4B,gBAAgB;AAAA,EAClD,YACU,MACA,QACR;AACD,UAAM,GAAG,IAAI,KAAK,MAAM,0BAA0B;AAHzC;AACA;AAAA,EAGV;AACD;AAGO,IAAM,eAAN,cAA2B,gBAAgB;AAAA,EACjD,YACU,gBACT,OACC;AACD;AAAA,MACC,eAAe,cAAc,qBAAqB,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,CAAC;AAAA,MACxG,EAAE,MAAM;AAAA,IACT;AANS;AAAA,EAOV;AACD;;;ADzBA,SAAS,iBAAiB;AAxB1B,IAAM,gBAAgB,OAAO,CAAC,OAAO,SAAS;AAC7C,QAAM,OAAO;AACb,QAAM,QAAQ;AACd,OAAK,SAAS;AACf,CAAC;AAGD,IAAM,mBAAmB,OAAO,CAAC,OAAO,SAAS;AAChD,QAAM,SAAS;AACf,QAAM,QAAQ;AACd,OAAK,OAAO;AACb,CAAC;AAGD,IAAM,cAAc,OAAO,CAAC,OAAO,SAAS;AAC3C,QAAM,SAAS;AACf,QAAM,QAAQ;AACd,OAAK,OAAO;AACb,CAAC;AASD,SAAS,SAAS,MAAc,OAA0D;AACzF,SAAO,WAAW,QAAQ,MAAM,KAAK;AACtC;AAEA,SAAS,WAAc,OAAa;AACnC,MAAI,UAAU,QAAQ,OAAO,UAAU,YAAY,OAAO,SAAS,KAAK,EAAG,QAAO;AAClF,aAAW,KAAK,OAAO,KAAK,KAAgC,GAAG;AAC9D,eAAY,MAAkC,CAAC,CAAC;AAAA,EACjD;AACA,SAAO,OAAO,OAAO,KAAK;AAC3B;AA6CO,IAAM,iBAAiB,CAAC,MAC9B,GAAG,EAAE,IAAI,KAAK,EAAE,eAAe,aAAa;AAsBtC,IAAM,gBAAgB,CAAI,MAAiC,EAAE;AAY7D,IAAM,sBAAsB,CAAI,MAAiC,EAAE;AAkOnE,IAAM,YAAN,cAA4E,MAAM;AAAA;AAAA,EAEvE,aAAa,oBAAI,IAAwB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMzC,wBAAwB,oBAAI,IAAqC;AAAA;AAAA,EAEjE,qBAAqB,oBAAI,IAAoB;AAAA;AAAA;AAAA;AAAA;AAAA,EAK7C,gBAAgB,oBAAI,IAAkB;AAAA,EACtC,eAAe,oBAAI,IAOlC;AAAA,EACe,eAAe,oBAAI,IAAY;AAAA,EAC/B,SAAS,oBAAI,IAAY;AAAA,EAClC,OAAO;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAER;AAAA;AAAA;AAAA;AAAA;AAAA,EAKA;AAAA;AAAA,EAEA;AAAA,EAET,YAAY,MAAc,OAAoB,CAAC,GAAG;AACjD,UAAM,MAAM,KAAK,KAAK;AACtB,SAAK,iBAAiB,KAAK,iBAAiB;AAC5C,SAAK,wBAAwB,KAAK,wBAAwB;AAC1D,SAAK,sBAAsB,KAAK,sBAAsB;AACtD,SAAK,iBAAiB,KAAK,iBAAiB;AAC5C,SAAK,aAAa,eAA+B;AAAA,MAChD,MAAM;AAAA,MACN,eAAe,KAAK;AAAA,MACpB,OAAO;AAAA,IACR,CAAC;AACD,SAAK,QAAQ,iBAAiB,KAAK,UAAU;AAC7C,SAAK,qBAAqB,eAAwC;AAAA,MACjE,MAAM;AAAA,MACN,eAAe,KAAK;AAAA,MACpB,OAAO;AAAA,IACR,CAAC;AACD,SAAK,qBAAqB,eAAe,MAAM,gBAAgB,CAAC;AAAA,EACjE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,iBAAiB,MAAc,aAA6B;AAC3D,WAAO,KAAK,mBAAmB,IAAI,GAAG,IAAI,KAAK,WAAW,EAAE,KAAK;AAAA,EAClE;AAAA;AAAA,EAGQ,gBAAgB,KAAmB;AAE1C,SAAK,cAAc,OAAO,GAAG;AAC7B,SAAK,cAAc,IAAI,KAAK,IAAI;AAAA,EACjC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASQ,uBAA6B;AACpC,WAAO,KAAK,cAAc,OAAO,KAAK,gBAAgB;AACrD,YAAM,SAAS,KAAK,cAAc,KAAK,EAAE,KAAK;AAC9C,UAAI,OAAO,KAAM;AACjB,YAAM,MAAM,OAAO;AACnB,WAAK,cAAc,OAAO,GAAG;AAC7B,YAAM,MAAM,IAAI,QAAQ,IAAI;AAC5B,UAAI,MAAM,EAAG;AACb,YAAM,OAAO,IAAI,MAAM,GAAG,GAAG;AAC7B,YAAM,cAAc,IAAI,MAAM,MAAM,CAAC;AACrC,YAAM,cAAc,KAAK,mBAAmB,IAAI,GAAG,KAAK;AACxD,WAAK,mBAAmB,OAAO,GAAG;AAClC,YAAM,SAAS,KAAK,sBAAsB,IAAI,IAAI;AAClD,UAAI,QAAQ;AACX,eAAO,OAAO,WAAW;AACzB,YAAI,OAAO,SAAS,EAAG,MAAK,sBAAsB,OAAO,IAAI;AAAA,MAC9D;AACA,WAAK,mBAAmB,OAAO;AAAA,QAC9B;AAAA,QACA;AAAA,QACA;AAAA,QACA,MAAM,YAAY;AAAA,MACnB,CAAC;AAAA,IACF;AAAA,EACD;AAAA;AAAA,EAGiB,sBAAyE,CAAC;AAAA,EAC1E,yBAAyB,oBAAI,IAA+B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAU7E,mBAAmB,OAA+D;AACjF,SAAK,oBAAoB,KAAK,KAAK;AAEnC,eAAW,CAAC,MAAM,KAAK,KAAK,KAAK,YAAY;AAC5C,YAAM,UAAU,MAAM,IAAI,cAAc,KAAK;AAC7C,UAAI,MAAM,KAAK,uBAAuB,IAAI,IAAI;AAC9C,UAAI,CAAC,KAAK;AACT,cAAM,CAAC;AACP,aAAK,uBAAuB,IAAI,MAAM,GAAG;AAAA,MAC1C;AACA,UAAI,KAAK,OAAO;AAAA,IACjB;AAEA,eAAW,CAAC,MAAM,KAAK,KAAK,KAAK,uBAAuB;AACvD,iBAAW,CAAC,OAAO,KAAK,KAAK,OAAO;AACnC,cAAM,MAAM,GAAG,IAAI,KAAK,KAAK;AAC7B,cAAM,UAAU,MAAM,IAAI,cAAc,KAAK;AAC7C,YAAI,MAAM,KAAK,uBAAuB,IAAI,GAAG;AAC7C,YAAI,CAAC,KAAK;AACT,gBAAM,CAAC;AACP,eAAK,uBAAuB,IAAI,KAAK,GAAG;AAAA,QACzC;AACA,YAAI,KAAK,OAAO;AAAA,MACjB;AAAA,IACD;AACA,WAAO,MAAM;AAEZ,YAAM,MAAM,KAAK,oBAAoB,QAAQ,KAAK;AAClD,UAAI,OAAO,EAAG,MAAK,oBAAoB,OAAO,KAAK,CAAC;AAAA,IAMrD;AAAA,EACD;AAAA;AAAA,EAGQ,uBACP,KACA,KACO;AACP,QAAI,KAAK,oBAAoB,WAAW,EAAG;AAC3C,QAAI,MAAM,KAAK,uBAAuB,IAAI,GAAG;AAC7C,QAAI,CAAC,KAAK;AACT,YAAM,CAAC;AACP,WAAK,uBAAuB,IAAI,KAAK,GAAG;AAAA,IACzC;AACA,eAAW,SAAS,KAAK,qBAAqB;AAC7C,UAAI,KAAK,IAAI,cAAc,KAAK,CAAC;AAAA,IAClC;AAAA,EACD;AAAA,EAUA,MAAM,MAAc,aAAkD;AACrE,QAAI,gBAAgB,QAAW;AAC9B,aAAO,KAAK,uBAAuB,MAAM,WAAW,EAAE;AAAA,IACvD;AACA,UAAM,WAAW,KAAK,WAAW,IAAI,IAAI;AACzC,QAAI,SAAU,QAAO,SAAS;AAO9B,UAAM,MAAM,YAAuB,CAAC,GAAG;AAAA,MACtC;AAAA,MACA,YAAY;AAAA,MACZ,SAAS,KAAK;AAAA,IACf,CAAC;AACD,QAAI,WAAW;AACf,UAAM,UAAU,IAAI;AACpB,UAAM,UAAU,KAAK;AAAA,MACpB;AAAA,MACA,CAAC,OAAO;AAAA,MACR,CAAC,WAAW,QAAQ;AACnB,cAAM,SACL,UAAU,CAAC,KAAK,QAAQ,UAAU,CAAC,EAAE,SAAS,IAC1C,UAAU,CAAC,EAAE,GAAG,EAAE,IAClB,IAAI,SAAS,CAAC;AACnB,eAAO,CAAC,MAAM;AAAA,MACf;AAAA,MACA;AAAA,QACC,MAAM,SAAS,SAAS,EAAE,YAAY,KAAK,CAAC;AAAA,QAC5C,OAAO;AAAA,QACP,SAAS,QAAQ;AAAA,MAClB;AAAA,IACD;AACA,SAAK,YAAY,UAAU,OAAO,CAAC;AACnC,SAAK,WAAW,IAAI,MAAM,EAAE,KAAK,MAAM,QAAQ,CAAC;AAEhD,SAAK,uBAAuB,MAAM,GAAG;AACrC,WAAO;AAAA,EACR;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOQ,uBAAuB,MAAc,aAAiC;AAE7E,QAAI,CAAC,KAAK,WAAW,IAAI,IAAI,EAAG,MAAK,MAAM,IAAI;AAE/C,QAAI,SAAS,KAAK,sBAAsB,IAAI,IAAI;AAChD,QAAI,CAAC,QAAQ;AACZ,eAAS,oBAAI,IAAI;AACjB,WAAK,sBAAsB,IAAI,MAAM,MAAM;AAAA,IAC5C;AACA,UAAM,SAAS,GAAG,IAAI,KAAK,WAAW;AACtC,SAAK,gBAAgB,MAAM;AAC3B,UAAM,WAAW,OAAO,IAAI,WAAW;AACvC,QAAI,SAAU,QAAO;AAErB,UAAM,WAAW,GAAG,IAAI,IAAI,YAAY,QAAQ,mBAAmB,GAAG,CAAC;AACvE,UAAM,MAAM,YAAuB,CAAC,GAAG;AAAA,MACtC,MAAM;AAAA,MACN,YAAY;AAAA,MACZ,SAAS,KAAK;AAAA,IACf,CAAC;AACD,QAAI,WAAW;AACf,UAAM,UAAU,IAAI;AAKpB,QAAI,YAAY;AAChB,QAAI,eAAe;AACnB,WAAO,KAAK,gBAAgB,SAAS,MAAM,QAAW;AACrD,sBAAgB;AAChB,kBAAY,GAAG,QAAQ,IAAI,YAAY;AAAA,IACxC;AACA,QAAI;AACJ,QAAI;AACH,gBAAU,KAAK;AAAA,QACd;AAAA,QACA,CAAC,OAAO;AAAA,QACR,CAAC,WAAW,QAAQ;AACnB,gBAAM,SACL,UAAU,CAAC,KAAK,QAAQ,UAAU,CAAC,EAAE,SAAS,IAC1C,UAAU,CAAC,EAAE,GAAG,EAAE,IAClB,IAAI,SAAS,CAAC;AACnB,iBAAO,CAAC,MAAM;AAAA,QACf;AAAA,QACA;AAAA,UACC,MAAM,SAAS,mBAAmB;AAAA,YACjC,YAAY;AAAA,YACZ,cAAc;AAAA,UACf,CAAC;AAAA,UACD,OAAO;AAAA,UACP,SAAS,QAAQ;AAAA,QAClB;AAAA,MACD;AAAA,IACD,QAAQ;AAQP,cAAQ;AAAA,QACP,gDAAgD,SAAS,uCACzB,IAAI,iBAAiB,WAAW;AAAA,MAGjE;AACA,gBAAU;AAAA,QACT,CAAC,OAAO;AAAA,QACR,CAAC,WAAW,SAAS,QAAQ;AAC5B,gBAAM,SACL,UAAU,CAAC,KAAK,QAAQ,UAAU,CAAC,EAAE,SAAS,IAAI,UAAU,CAAC,EAAE,GAAG,EAAE,IAAI,IAAI,SAAS,CAAC;AACvF,kBAAQ,KAAK,MAA8B;AAAA,QAC5C;AAAA,QACA;AAAA,UACC,MAAM;AAAA,UACN,cAAc;AAAA,UACd,MAAM,SAAS,mBAAmB;AAAA,YACjC,YAAY;AAAA,YACZ,cAAc;AAAA,UACf,CAAC;AAAA,UACD,OAAO;AAAA,UACP,SAAS,QAAQ;AAAA,QAClB;AAAA,MACD;AAAA,IACD;AACA,SAAK,YAAY,UAAU,OAAO,CAAC;AACnC,UAAM,QAAQ,EAAE,KAAK,MAAM,QAAQ;AACnC,WAAO,IAAI,aAAa,KAAK;AAE7B,SAAK,uBAAuB,GAAG,IAAI,KAAK,WAAW,IAAI,GAAG;AAC1D,SAAK,qBAAqB;AAC1B,WAAO;AAAA,EACR;AAAA;AAAA,EAGQ,gBAAgB,MAAgC;AACvD,QAAI;AACH,aAAO,KAAK,QAAQ,IAAI;AAAA,IACzB,QAAQ;AACP,aAAO;AAAA,IACR;AAAA,EACD;AAAA;AAAA,EAGQ,aACP,WACA,SACA,OAOY;AACZ,QAAI,QAAQ,KAAK,WAAW,IAAI,SAAS;AACzC,QAAI,CAAC,OAAO;AACX,WAAK,MAAM,SAAS;AACpB,cAAQ,KAAK,WAAW,IAAI,SAAS;AAAA,IACtC;AACA,QAAI,MAAM,KAAK,WAAW,eAAe,MAAM,KAAK,WAAW,WAAW;AACzE,YAAM,IAAI;AAAA,QACT,+CAA+C,SAAS,cAAc,MAAM,KAAK,MAAM;AAAA,MACxF;AAAA,IACD;AAGA,QAAI;AACJ,QAAI;AACJ,QAAI,OAAO,gBAAgB,QAAW;AACrC,YAAM,SAAS,GAAG,SAAS,KAAK,MAAM,WAAW;AACjD,0BAAoB,KAAK,mBAAmB,IAAI,MAAM,KAAK,KAAK;AAChE,WAAK,mBAAmB,IAAI,QAAQ,gBAAgB;AACpD,uBAAiB,KAAK,uBAAuB,WAAW,MAAM,WAAW;AAAA,IAC1E;AAEA,UAAM,KAAK,MAAM,IAAI,QAAQ;AAC7B,UAAM,gBAAgB,KAAK,sBAAsB,WAAW,OAAO,IAAI;AACvE,UAAM,MAAiB;AAAA,MACtB,MAAM;AAAA,MACN,SAAS;AAAA,MACT,aAAa,YAAY;AAAA,MACzB,KAAK,EAAE,KAAK;AAAA,MACZ,GAAI,OAAO,gBAAgB,SAAY,EAAE,aAAa,MAAM,YAAY,IAAI,CAAC;AAAA,MAC7E,GAAI,qBAAqB,SAAY,EAAE,iBAAiB,IAAI,CAAC;AAAA,MAC7D,GAAI,OAAO,kBAAkB,SAAY,EAAE,eAAe,MAAM,cAAc,IAAI,CAAC;AAAA,MACnF,GAAI,OAAO,gBAAgB,SAAY,EAAE,aAAa,MAAM,YAAY,IAAI,CAAC;AAAA,MAC7E,GAAI,OAAO,aAAa,SAAY,EAAE,UAAU,OAAO,OAAO,EAAE,GAAG,MAAM,SAAS,CAAC,EAAE,IAAI,CAAC;AAAA,MAC1F,GAAI,OAAO,mBAAmB,SAAY,EAAE,gBAAgB,MAAM,eAAe,IAAI,CAAC;AAAA,MACtF,GAAI,MAAM,OAAO,EAAE,IAAI,EAAE,IAAI,GAAG,IAAI,SAAS,GAAG,QAAQ,EAAE,IAAI,CAAC;AAAA,IAChE;AAEA,UAAM,IAAI,OAAO,GAAG;AACpB,QAAI,gBAAgB;AACnB,qBAAe,IAAI,OAAO,GAAG;AAAA,IAC9B;AACA,WAAO;AAAA,EACR;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,QACC,MACA,cACU;AACV,QAAI,KAAK,aAAa,IAAI,IAAI,GAAG;AAChC,YAAM,IAAI,2BAA2B,WAAW,IAAI;AAAA,IACrD;AACA,UAAM,MACL,OAAO,iBAAiB,aAAa,EAAE,SAAS,aAAa,IAAI;AAClE,UAAM,UAAU,KAAK,MAAS,MAAM,QAAgB;AAAA,MACnD,MAAM;AAAA,QACL,GAAG,SAAS,WAAW,EAAE,cAAc,KAAK,CAAC;AAAA,QAC7C,OAAO;AAAA,MACR;AAAA,MACA,OAAO;AAAA,IACR,CAAC;AACD,SAAK,aAAa,IAAI,MAAM;AAAA,MAC3B,SAAS,IAAI;AAAA,MACb,GAAI,IAAI,UAAU,SAAY,EAAE,OAAO,IAAI,MAAM,IAAI,CAAC;AAAA,MACtD,GAAI,IAAI,mBAAmB,SAAY,EAAE,gBAAgB,IAAI,eAAe,IAAI,CAAC;AAAA,IAClF,CAAC;AAED,QAAI,IAAI,OAAO;AACd,iBAAW,KAAK,IAAI,OAAO;AAC1B,YAAI,CAAC,KAAK,WAAW,IAAI,CAAC,EAAG,MAAK,MAAM,CAAC;AAAA,MAC1C;AAAA,IACD;AACA,WAAO;AAAA,EACR;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAeA,SAAsB,aAAqB,SAAY,MAA8B;AACpF,UAAM,MAAM,KAAK,aAAa,IAAI,WAAW;AAC7C,QAAI,CAAC,IAAK,OAAM,IAAI,oBAAoB,WAAW;AAMnD,QACC,MAAM,gBAAgB,UACtB,KAAK,6BAA6B,UAClC,IAAI,UAAU,QACb;AAOD,UAAI,kBAAkB;AACtB,iBAAW,KAAK,IAAI,OAAO;AAC1B,cAAM,IAAI,KAAK,mBAAmB,IAAI,GAAG,CAAC,KAAK,KAAK,WAAW,EAAE;AACjE,YAAI,MAAM,UAAa,IAAI,gBAAiB,mBAAkB;AAAA,MAC/D;AACA,UAAI,oBAAoB,KAAK,0BAA0B;AACtD,cAAM,IAAI;AAAA,UACT,KAAK;AAAA,UACL,KAAK;AAAA,UACL;AAAA,QACD;AAAA,MACD;AAAA,IACD;AAEA,UAAM,UAAU,KAAK,QAAQ,WAAW;AACxC,UAAM,gBAA0B,CAAC;AAIjC,QAAI,cAAc;AAElB,UAAM,SAAS,CAAC,WAAoB;AACnC,cAAQ,KAAK,QAAQ,EAAE,UAAU,KAAK,CAAC;AACvC,UAAI;AACH,YAAI,QAAQ,QAAQ;AAAA,UACnB,MAAM,CAAC,OAAO,SAAS;AAEtB,gBAAI,IAAI,UAAU,UAAa,CAAC,IAAI,MAAM,SAAS,KAAK,GAAG;AAC1D,oBAAM,IAAI,oBAAoB,aAAa,OAAO,IAAI,KAAK;AAAA,YAC5D;AACA,0BAAc,KAAK,KAAK;AACxB,iBAAK,aAAa,OAAO,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA,cAK9B,GAAI,MAAM,gBAAgB,SAAY,EAAE,aAAa,KAAK,YAAY,IAAI,CAAC;AAAA,cAC3E,GAAI,MAAM,kBAAkB,SAAY,EAAE,eAAe,KAAK,cAAc,IAAI,CAAC;AAAA,cACjF,GAAI,MAAM,gBAAgB,SAAY,EAAE,aAAa,KAAK,YAAY,IAAI,CAAC;AAAA,cAC3E,GAAI,MAAM,aAAa,SACpB,EAAE,UAAU,OAAO,OAAO,EAAE,GAAG,KAAK,SAAS,CAAC,EAAE,IAChD,CAAC;AAAA,cACJ,GAAI,IAAI,mBAAmB,SAAY,EAAE,gBAAgB,IAAI,eAAe,IAAI,CAAC;AAAA,YAClF,CAAC;AAAA,UACF;AAAA,QACD,CAAC;AACD,gBAAQ,KAAK,MAAM,KAAK,MAAM,EAAE,UAAU,KAAK,CAAC;AAAA,MACjD,SAAS,KAAK;AACb,sBAAc;AACd,cAAM;AAAA,MACP;AAAA,IACD;AAEA,QAAI;AACH,aAAkC,QAAQ;AAAA,QACzC,OAAO;AAAA,QACP,KAAK,KAAK;AAAA,QACV,KAAK,KAAK;AAAA,QACV,QAAQ,KAAK;AAAA,QACb,iBAAiB,CAAC,CAAC,MAAM,GAAG,SAAS,EAAE,MAAM,IAAI,OAAO;AAAA,UACvD;AAAA,UACA,SAAS;AAAA,UACT,SAAS;AAAA,UACT,eAAe,CAAC,GAAG,aAAa;AAAA,UAChC;AAAA,UACA,KAAK,OAAO;AAAA,UACZ,GAAI,IAAI,mBAAmB,SAAY,EAAE,gBAAgB,IAAI,eAAe,IAAI,CAAC;AAAA,QAClF;AAAA,QACA,iBAAiB,CAAC,CAAC,MAAM,GAAG,KAAK,EAAE,MAAM,KAAK,UAAU,MAAM;AAC7D,gBAAM,UACL,eAAe,sBAAsB,MAAM,IAAI,oBAAoB,aAAa,GAAG;AACpF,iBAAO;AAAA,YACN;AAAA,YACA,SAAS;AAAA,YACT,SAAS;AAAA,YACT,OAAO;AAAA,YACP;AAAA,YACA,eAAe,CAAC,GAAG,aAAa;AAAA,YAChC;AAAA,YACA,KAAK,OAAO;AAAA,YACZ,GAAI,IAAI,mBAAmB,SAAY,EAAE,gBAAgB,IAAI,eAAe,IAAI,CAAC;AAAA,UAClF;AAAA,QACD;AAAA,MACD,CAAC,EAAE,OAAO;AAAA,IACX,SAAS,UAAU;AAKlB,UAAI,aAAa;AAChB,gBAAQ,KAAK,MAAM,KAAK,UAAU,EAAE,UAAU,KAAK,CAAC;AAAA,MACrD;AACA,YAAM;AAAA,IACP;AAAA,EACD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAoBA,WAAmB,MAA+D;AACjF,UAAM,EAAE,MAAM,QAAQ,YAAY,SAAS,QAAQ,IAAI;AACvD,UAAM,OAAO,KAAK,QAAQ;AAC1B,UAAM,eAAe,KAAK,gBAAgB;AAC1C,UAAM,eAAe,KAAK;AAK1B,UAAM,aAAa,WAAW,IAAI,CAAC,UAAU;AAC5C,UAAI,CAAC,KAAK,WAAW,IAAI,KAAK,EAAG,MAAK,MAAM,KAAK;AACjD,aAAO,KAAK,WAAW,IAAI,KAAK,EAAG;AAAA,IACpC,CAAC;AAGD,aAAS,WAAW,MAAyB;AAC5C,WAAK;AAAA,QACJ,CAAC,GAAG,MACH,EAAE,cAAc,EAAE,eAClB,EAAE,MAAM,EAAE,QACT,EAAE,eAAe,IAAI,cAAc,EAAE,eAAe,EAAE;AAAA,MACzD;AAAA,IACD;AAGA,aAAS,iBAAiB,WAA2D;AACpF,YAAM,OAAoB,CAAC;AAC3B,iBAAW,QAAQ,UAAW,MAAK,KAAK,GAAG,IAAI;AAC/C,iBAAW,IAAI;AACf,aAAO;AAAA,IACR;AAGA,UAAM,gBAAgB,WAAW;AAAA,MAChC,CAAC,MAAO,EAAE,SAA+C,CAAC;AAAA,IAC3D;AACA,UAAM,aAAa,iBAAiB,aAAa;AACjD,UAAM,aACL,eAAe,OAAO,OAAO,UAAU,IAAI;AAI5C,QAAI,qBAAqB;AACzB,QAAI,YAAoB;AAExB,QAAI,SAAS,UAAU,WAAW,SAAS,GAAG;AAC7C,kBAAY,QAAQ,SAAS,UAAU;AACvC,2BAAqB,WAAW;AAAA,IACjC;AACA,UAAM,YAAY,SAAS,WAAW,QAAQ,SAAS,UAAU,IAAI;AAGrE,UAAM,iBAAiB,cAAc,kBAAkB;AACvD,UAAM,YAAY,cAAc,aAAa;AAC7C,QAAI;AACJ,QAAI,sBAAsB;AAE1B,aAAS,aAAa,cAA4B;AACjD,UAAI,CAAC,cAAc,KAAM;AACzB,6BAAuB;AACvB,UAAI,uBAAuB,WAAW;AACrC,8BAAsB;AACtB,YAAI,cAAc,QAAW;AAC5B,uBAAa,SAAS;AACtB,sBAAY;AAAA,QACb;AACA,cAAM,SAAS,aAAa,KAAK,YAAY;AAC7C,YAAI,kBAAkB,QAAS,QAAO,MAAM,MAAM,MAAS;AAC3D;AAAA,MACD;AACA,UAAI,cAAc,OAAW,cAAa,SAAS;AACnD,kBAAY,WAAW,MAAM;AAC5B,oBAAY;AACZ,8BAAsB;AACtB,cAAM,SAAS,aAAc,KAAM,YAAY;AAC/C,YAAI,kBAAkB,QAAS,QAAO,MAAM,MAAM,MAAS;AAAA,MAC5D,GAAG,cAAc;AAAA,IAClB;AAEA,UAAM,WAAW,KAAK;AAAA,MACrB;AAAA,MACA;AAAA,MACA,CAAC,WAAW,QAAQ;AACnB,cAAM,YAAY,UAAU;AAAA,UAAI,CAAC,OAAO,MACvC,SAAS,QAAQ,MAAM,SAAS,IAAI,MAAM,GAAG,EAAE,IAAI,IAAI,SAAS,CAAC;AAAA,QAClE;AACA,cAAM,YAAY,iBAAiB,SAA8C;AAEjF,YAAI;AACJ,YAAI,SAAS,UAAU;AAEtB,gBAAM,SACL,eAAe,OAAO,OAAO,SAAS,IAAI;AAE3C,qBAAW,QAAQ,SAAS,MAAM;AAAA,QACnC,OAAO;AAEN,gBAAM,UAAU,UAAU,MAAM,kBAAkB;AAClD,+BAAqB,UAAU;AAC/B,gBAAM,YACL,eAAe,OAAO,OAAO,OAAO,IAAI;AAEzC,qBAAW,QAAQ,WAAW,SAAS;AACvC,sBAAY;AAAA,QACb;AAEA,qBAAa,QAAQ;AACrB,eAAO,CAAC,QAAQ;AAAA,MACjB;AAAA,MACA;AAAA,QACC,MAAM,SAAS,cAAc,EAAE,iBAAiB,MAAM,eAAe,WAAW,CAAC;AAAA,QACjF,OAAO;AAAA,QACP,SAAS;AAAA,MACV;AAAA,IACD;AAEA,SAAK,YAAY,UAAU,QAAQ,CAAC;AACpC,SAAK,YAAY,MAAM;AACtB,UAAI,cAAc,QAAW;AAC5B,qBAAa,SAAS;AACtB,oBAAY;AAAA,MACb;AAAA,IACD,CAAC;AACD,SAAK,aAAa,IAAI,IAAI;AAI1B,UAAM,UAAU,OAAO,gBAGA;AACtB,UAAI;AACH,cAAM,WAAW,aAAa,YAAY;AAC1C,cAAM,OAAO,aAAa,YAAY,KAAK,oBAAoB,CAAC,IAAI,CAAC;AAIrE,cAAM,gBAAgB;AAAA,UACrB,WAAW,IAAI,CAAC,MAAO,EAAE,SAA8C,CAAC,CAAC;AAAA,QAC1E,EAAE;AAGF,YAAI,eAAuB;AAC3B,YAAI,cAAc,MAAM;AACvB,gBAAM,SAAS,MAAM,aAAa,KAAK;AACvC,cAAI,WAAW,OAAW,gBAAe;AAAA,QAC1C;AAEA,YAAI,CAAC,QAAQ,CAAC,KAAK,aAAa;AAE/B,gBAAM,WAAW;AAAA,YAChB,WAAW,IAAI,CAAC,MAAO,EAAE,SAA8C,CAAC,CAAC;AAAA,UAC1E;AACA,gBAAM,SACL,eAAe,OAAO,OAAO,QAAQ,IAAI;AAE1C,yBAAe,QAAQ,cAAc,MAAM;AAAA,QAC5C,OAAO;AAKN,gBAAM,gBAAgB,IAAI,IAAY,UAA+B;AACrE,cAAI;AACJ,cAAI,OAAO;AACX,iBAAO,CAAC,MAAM;AACb,kBAAM,SAAS,MAAM,KAAK,YAAY,EAAE,QAAQ,SAAS,CAAC;AAC1D,kBAAM,OAAO,CAAC,GAAG,OAAO,OAAO,EAAE,OAAO,CAAC,MAAM,cAAc,IAAI,EAAE,IAAI,CAAC;AACxE,uBAAW,IAAI;AACf,kBAAM,aAAc,eAAe,OAAO,OAAO,IAAI,IAAI;AACzD,2BAAe,QAAQ,cAAc,UAAU;AAC/C,qBAAS,OAAO;AAChB,mBAAO,CAAC,UAAU,OAAO,QAAQ,WAAW;AAAA,UAC7C;AAAA,QACD;AAGA,YAAI,SAAS,QAAQ;AAGpB,gBAAM,cAAc;AAAA,YACnB,WAAW,IAAI,CAAC,MAAO,EAAE,SAA8C,CAAC,CAAC;AAAA,UAC1E;AACA,gBAAM,gBAAgB,YAAY,MAAM,aAAa;AACrD,cAAI,cAAc,SAAS,GAAG;AAC7B,kBAAM,gBACL,eAAe,OAAO,OAAO,aAAa,IAAI;AAE/C,2BAAe,QAAQ,cAAc,aAAa;AAAA,UACnD;AACA,sBAAY;AACZ,+BAAqB,YAAY;AAAA,QAClC;AACA,iBAAS,KAAK,cAAc,EAAE,UAAU,KAAK,CAAC;AAC9C,eAAO;AAAA,MACR,SAAS,KAAK;AACb,cAAM,IAAI,aAAa,MAAM,GAAG;AAAA,MACjC;AAAA,IACD;AAEA,UAAM,QAAQ,YAA6B;AAC1C,UAAI;AAEH,YAAI,YAAoB;AACxB,YAAI,cAAc,MAAM;AACvB,gBAAM,SAAS,MAAM,aAAa,KAAK;AACvC,cAAI,WAAW,OAAW,aAAY;AAAA,QACvC;AAGA,cAAM,WAAW;AAAA,UAChB,WAAW,IAAI,CAAC,MAAO,EAAE,SAA8C,CAAC,CAAC;AAAA,QAC1E;AACA,cAAM,SAAU,eAAe,OAAO,OAAO,QAAQ,IAAI;AACzD,cAAM,WAAW,QAAQ,WAAW,MAAM;AAE1C,YAAI,SAAS,QAAQ;AACpB,sBAAY;AACZ,+BAAqB,SAAS;AAAA,QAC/B;AACA,iBAAS,KAAK,UAAU,EAAE,UAAU,KAAK,CAAC;AAC1C,eAAO;AAAA,MACR,SAAS,KAAK;AACb,cAAM,IAAI,aAAa,MAAM,GAAG;AAAA,MACjC;AAAA,IACD;AAEA,WAAO,EAAE,MAAM,UAAU,SAAS,MAAM;AAAA,EACzC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAaA,KACC,MACA,YACA,SACA,OAAoB,CAAC,GACD;AACpB,UAAM,cAAc,WAAW,IAAI,CAAC,UAAU;AAC7C,UAAI,CAAC,KAAK,WAAW,IAAI,KAAK,EAAG,MAAK,MAAM,KAAK;AACjD,aAAO,KAAK,WAAW,IAAI,KAAK,EAAG;AAAA,IACpC,CAAC;AAID,UAAM,UAAU,kBAAkB,MAAM,GAAG,IAAI,WAAW,YAAiC,CAAC;AAE5F,UAAM,cAAc,eAAkC;AAAA,MACrD,MAAM,GAAG,IAAI;AAAA,MACb,eAAe,KAAK;AAAA,MACpB,OAAO;AAAA,IACR,CAAC;AACD,UAAM,kBAAkB,KAAK;AAC7B,UAAM,cAAc,KAAK,eAAe;AAQxC,UAAM,gBAAgB,oBAAI,IAAoB;AAC9C,eAAW,SAAS,YAAY;AAC/B,YAAM,SAAS,QAAQ,KAAK;AAC5B,oBAAc,IAAI,OAAQ,OAAO,SAAgC,CAAC;AAClE,YAAM,MAAM,OAAO,UAAU,CAAC,SAAS;AACtC,mBAAW,KAAK,KAAM,KAAI,EAAE,CAAC,MAAM,KAAM,eAAc,IAAI,OAAO,EAAE,CAAC,CAAW;AAAA,MACjF,CAAC;AACD,WAAK,YAAY,GAAG;AAAA,IACrB;AAOA,UAAM,iBAAiB;AAAA,MACtB,CAAC,IAAI,WAAW;AACf,gBAAQ,EAAE;AAAA,MACX;AAAA,MACA;AAAA,QACC,OAAO;AAAA,QACP,KAAK;AAAA,QACL,QAAQ;AAAA,QACR,GAAI,KAAK,mBAAmB,SAAY,EAAE,gBAAgB,KAAK,eAAe,IAAI,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA,QAKnF,iBAAiB,CAAC,CAAC,IAAI,KAAK,GAAG,IAAI,EAAE,KAAK,OAAO;AAAA,UAChD,WAAW;AAAA,UACX,SAAS;AAAA,UACT,aAAa,GAAG;AAAA,UAChB,OAAO;AAAA,UACP;AAAA,QACD;AAAA,QACA,iBAAiB,CAAC,CAAC,IAAI,KAAK,GAAG,KAAK,EAAE,MAAM,UAAU,OAAO;AAAA,UAC5D,WAAW;AAAA,UACX,SAAS;AAAA,UACT,OAAO;AAAA,UACP;AAAA,UACA,aAAa,GAAG;AAAA,UAChB,OAAO;AAAA,UACP;AAAA,QACD;AAAA,MACD;AAAA,IACD;AAEA,UAAM,UAAiC,CAAC;AACxC,UAAM,WAAW,KAAK;AAAA,MACrB;AAAA,MACA;AAAA,MACA,CAAC,WAAW,QAAQ;AACnB,cAAM,UAAU,QAAQ,EAAG,KAAK;AAChC,iBAAS,IAAI,GAAG,IAAI,UAAU,QAAQ,KAAK;AAC1C,gBAAM,QAAQ,UAAU,CAAC;AACzB,cAAI,SAAS,QAAQ,MAAM,WAAW,EAAG;AACzC,gBAAM,UAAU,MAAM,GAAG,EAAE;AAC3B,cAAI,CAAC,QAAS;AACd,gBAAM,QAAQ,WAAW,CAAC;AAC1B,gBAAM,SAAS,QAAQ,KAAK;AAC5B,gBAAM,YAAY,cAAc,IAAI,KAAK,KAAK;AAC9C,cAAI,QAAQ,SAAS,WAAW;AAC/B,kBAAM,aAAa,QAAQ,MAAM,SAAS;AAC1C,gBAAI,aAAa;AACjB,uBAAW,SAAS,YAAY;AAC/B,oBAAM,KAAK;AACX,kBAAI,oBAAoB,UAAa,GAAG,gBAAgB,iBAAiB;AACxE,8BAAc;AACd;AAAA,cACD;AACA,kBAAI;AACH,+BAAe,IAAI,KAAK;AACxB,wBAAQ,KAAK,MAAM,EAAE,UAAU,KAAK,CAAC;AACrC,8BAAc;AAAA,cACf,SAAS,KAAK;AACb,wBAAQ,KAAK,KAAK,EAAE,UAAU,KAAK,CAAC;AACpC,oBAAI,gBAAgB,OAAQ;AAE5B,8BAAc;AAAA,cACf;AAAA,YACD;AACA,mBAAO,KAAK,UAAU;AAAA,UACvB;AAAA,QACD;AAAA,MACD;AAAA,MACA;AAAA,QACC,MAAM;AAAA,UACL,GAAG,SAAS,QAAQ,EAAE,WAAW,MAAM,eAAe,WAAW,CAAC;AAAA,UAClE,OAAO;AAAA,QACR;AAAA,MACD;AAAA,IACD;AACA,YAAQ,IAAI;AAEZ,SAAK,YAAY,UAAU,QAAQ,CAAC;AACpC,SAAK,OAAO,IAAI,IAAI;AACpB,WAAO;AAAA,MACN,MAAM;AAAA,MACN;AAAA,MACA,OAAO,iBAAiB,WAAW;AAAA,MACnC;AAAA,IACD;AAAA,EACD;AACD;AAyBO,SAAS,KACf,MACA,MACgB;AAChB,QAAM,IAAI,IAAI,UAAc,MAAM,IAAI;AAItC,QAAM,EAAE,SAAS,IAAI,aAAa,KAAK,GAAG,QAAQ,IAAK,QAAQ,CAAC;AAChE,IAAE,WAAW,QAAQ,gBAAgB,OAAO,CAAC;AAC7C,SAAO;AACR;","names":[]}
|
|
@@ -3,14 +3,14 @@ import {
|
|
|
3
3
|
chatStream,
|
|
4
4
|
toolExecution,
|
|
5
5
|
toolRegistry
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-3REMCHSS.js";
|
|
7
7
|
import {
|
|
8
8
|
subscription,
|
|
9
9
|
topic
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-DHDCOOJU.js";
|
|
11
11
|
import {
|
|
12
12
|
awaitSettled
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-N6MNJNHB.js";
|
|
14
14
|
|
|
15
15
|
// src/presets/ai/agent-loop.ts
|
|
16
16
|
import {
|
|
@@ -230,6 +230,11 @@ var AgentLoopGraph = class extends Graph {
|
|
|
230
230
|
chat.append("assistant", response.content, {
|
|
231
231
|
toolCalls: response.toolCalls
|
|
232
232
|
});
|
|
233
|
+
if (capReached && hasToolCalls) {
|
|
234
|
+
for (const tc of response.toolCalls) {
|
|
235
|
+
chat.appendToolResult(tc.id, "[tool call denied: maxTurns reached]");
|
|
236
|
+
}
|
|
237
|
+
}
|
|
233
238
|
});
|
|
234
239
|
},
|
|
235
240
|
{ describeKind: "effect" }
|
|
@@ -251,6 +256,45 @@ var AgentLoopGraph = class extends Graph {
|
|
|
251
256
|
},
|
|
252
257
|
{ describeKind: "effect" }
|
|
253
258
|
);
|
|
259
|
+
const effFullDeny = gatedToolCallsNode !== toolCallsRaw ? node(
|
|
260
|
+
[toolCallsRaw, gatedToolCallsNode],
|
|
261
|
+
(batchData) => {
|
|
262
|
+
if (latestAborted) return;
|
|
263
|
+
const rawBatch = batchData[0];
|
|
264
|
+
const gatedBatch = batchData[1];
|
|
265
|
+
const rawCalls = rawBatch != null && rawBatch.length > 0 ? rawBatch.at(-1) : null;
|
|
266
|
+
if (rawCalls == null || rawCalls.length === 0) return;
|
|
267
|
+
const gatedCalls = gatedBatch != null && gatedBatch.length > 0 ? gatedBatch.at(-1) : null;
|
|
268
|
+
const allowedIds = gatedCalls === null ? null : new Set(gatedCalls.map((c) => c.id));
|
|
269
|
+
const denied = allowedIds === null ? rawCalls : rawCalls.filter((c) => !allowedIds.has(c.id));
|
|
270
|
+
if (denied.length === 0) return;
|
|
271
|
+
const isFullDeny = gatedCalls === null;
|
|
272
|
+
batch(() => {
|
|
273
|
+
if (isFullDeny) {
|
|
274
|
+
statusNode.emit(latestTurn >= maxTurns ? "done" : "thinking");
|
|
275
|
+
}
|
|
276
|
+
for (const c of denied)
|
|
277
|
+
chat.appendToolResult(c.id, "[tool call denied by interceptor]");
|
|
278
|
+
});
|
|
279
|
+
},
|
|
280
|
+
{
|
|
281
|
+
name: "fullDenyRecovery",
|
|
282
|
+
describeKind: "effect",
|
|
283
|
+
meta: aiMeta("agent_full_deny_recovery"),
|
|
284
|
+
// MUST be explicit: the core `node()` default is
|
|
285
|
+
// `partial: false` (node.ts `opts.partial ?? false`),
|
|
286
|
+
// and `gatedToolCallsNode` only ever emits RESOLVED on the
|
|
287
|
+
// full-deny path (never DATA/terminal). Spec R2.7.0
|
|
288
|
+
// (DS-2.7.A, 2026-05-19) holds the `partial: false`
|
|
289
|
+
// first-run gate until every dep has contributed real
|
|
290
|
+
// DATA, so a `partial: false` effFullDeny would hold the
|
|
291
|
+
// fn FOREVER. Spec R2.7.2 = `partial: true` disables the
|
|
292
|
+
// gate; the fn body's `denied`-subtraction guard above
|
|
293
|
+
// covers the SENTINEL slot per the R2.7.2 author
|
|
294
|
+
// contract.
|
|
295
|
+
partial: true
|
|
296
|
+
}
|
|
297
|
+
) : null;
|
|
254
298
|
let latestStatus = statusNode.cache ?? "idle";
|
|
255
299
|
const statusSub = statusNode.subscribe((msgs) => {
|
|
256
300
|
for (const m of msgs) if (m[0] === DATA) latestStatus = m[1];
|
|
@@ -270,6 +314,7 @@ var AgentLoopGraph = class extends Graph {
|
|
|
270
314
|
);
|
|
271
315
|
const kaResponse = keepalive(effResponse);
|
|
272
316
|
const kaResults = keepalive(effResults);
|
|
317
|
+
const kaFullDeny = effFullDeny ? keepalive(effFullDeny) : null;
|
|
273
318
|
const kaAbort = keepalive(effAbort);
|
|
274
319
|
this._terminalResult = nodeFactory(
|
|
275
320
|
[statusNode, lastResponseState],
|
|
@@ -322,6 +367,7 @@ var AgentLoopGraph = class extends Graph {
|
|
|
322
367
|
this.addDisposer(statusSub);
|
|
323
368
|
this.addDisposer(kaResponse);
|
|
324
369
|
this.addDisposer(kaResults);
|
|
370
|
+
if (kaFullDeny) this.addDisposer(kaFullDeny);
|
|
325
371
|
this.addDisposer(kaAbort);
|
|
326
372
|
this._disposeRunWiring = () => {
|
|
327
373
|
};
|
|
@@ -722,4 +768,4 @@ export {
|
|
|
722
768
|
agent,
|
|
723
769
|
presetRegistry
|
|
724
770
|
};
|
|
725
|
-
//# sourceMappingURL=chunk-
|
|
771
|
+
//# sourceMappingURL=chunk-65OM4XLQ.js.map
|