@demlik/tea 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (196) hide show
  1. package/README.md +49 -0
  2. package/dist/agent/index.d.ts +16 -770
  3. package/dist/agent/index.js +18 -16
  4. package/dist/authed-call/index.d.ts +3 -3
  5. package/dist/authed-call/index.js +22 -36
  6. package/dist/authed-call/index.js.map +1 -1
  7. package/dist/await-terminal/index.d.ts +2 -2
  8. package/dist/await-terminal/index.js +2 -2
  9. package/dist/batch-window/index.d.ts +3 -3
  10. package/dist/batch-window/index.js +3 -3
  11. package/dist/cache/index.d.ts +2 -2
  12. package/dist/cache/index.js +5 -2
  13. package/dist/{chunk-3545BOYL.js → chunk-25LBDCBC.js} +6 -5
  14. package/dist/chunk-25LBDCBC.js.map +1 -0
  15. package/dist/{chunk-IK4S5DJN.js → chunk-2AHAR7CX.js} +7 -14
  16. package/dist/chunk-2AHAR7CX.js.map +1 -0
  17. package/dist/{chunk-IN33Q4VJ.js → chunk-2KNLKQVN.js} +3 -3
  18. package/dist/{chunk-IN33Q4VJ.js.map → chunk-2KNLKQVN.js.map} +1 -1
  19. package/dist/{chunk-VFGKTGGC.js → chunk-5W7CNAHB.js} +191 -178
  20. package/dist/chunk-5W7CNAHB.js.map +1 -0
  21. package/dist/{chunk-6GIGQT6C.js → chunk-B5RAZVXW.js} +12 -11
  22. package/dist/chunk-B5RAZVXW.js.map +1 -0
  23. package/dist/{chunk-6VDCTR7N.js → chunk-D4NJBQAH.js} +258 -151
  24. package/dist/chunk-D4NJBQAH.js.map +1 -0
  25. package/dist/{chunk-UV6UWGEO.js → chunk-D736QUGF.js} +6 -5
  26. package/dist/chunk-D736QUGF.js.map +1 -0
  27. package/dist/{chunk-EGWTT5XJ.js → chunk-ET6QQNSQ.js} +76 -95
  28. package/dist/chunk-ET6QQNSQ.js.map +1 -0
  29. package/dist/chunk-EWJP3YYP.js +21 -0
  30. package/dist/chunk-EWJP3YYP.js.map +1 -0
  31. package/dist/{chunk-M6UZT7HS.js → chunk-FCKP6N7Y.js} +6 -5
  32. package/dist/chunk-FCKP6N7Y.js.map +1 -0
  33. package/dist/{chunk-TGG26XMK.js → chunk-FV3Z5MBE.js} +10 -5
  34. package/dist/chunk-FV3Z5MBE.js.map +1 -0
  35. package/dist/chunk-G5E74IJB.js +8 -0
  36. package/dist/chunk-G5E74IJB.js.map +1 -0
  37. package/dist/{chunk-G2U7DJHL.js → chunk-JFFVBEDI.js} +3 -3
  38. package/dist/{chunk-G2U7DJHL.js.map → chunk-JFFVBEDI.js.map} +1 -1
  39. package/dist/{chunk-FOGL3TC7.js → chunk-JXYYRKD6.js} +8 -10
  40. package/dist/chunk-JXYYRKD6.js.map +1 -0
  41. package/dist/{chunk-ZDRN3YCE.js → chunk-LJJV6ZD5.js} +17 -24
  42. package/dist/chunk-LJJV6ZD5.js.map +1 -0
  43. package/dist/chunk-LYLDCIPH.js +365 -0
  44. package/dist/chunk-LYLDCIPH.js.map +1 -0
  45. package/dist/{chunk-HJO6VNQI.js → chunk-MU6MCIAC.js} +3 -3
  46. package/dist/{chunk-HJO6VNQI.js.map → chunk-MU6MCIAC.js.map} +1 -1
  47. package/dist/{chunk-UYWUVEP6.js → chunk-MUUROGIH.js} +2 -2
  48. package/dist/chunk-MUUROGIH.js.map +1 -0
  49. package/dist/{chunk-DYRKDCJI.js → chunk-PBDYW2TU.js} +41 -7
  50. package/dist/chunk-PBDYW2TU.js.map +1 -0
  51. package/dist/{chunk-5VNWZADP.js → chunk-Q2UM7BXU.js} +18 -7
  52. package/dist/chunk-Q2UM7BXU.js.map +1 -0
  53. package/dist/chunk-RMX4EOQK.js +26 -0
  54. package/dist/chunk-RMX4EOQK.js.map +1 -0
  55. package/dist/{chunk-2YOE5PIS.js → chunk-S2EGRPCM.js} +8 -6
  56. package/dist/chunk-S2EGRPCM.js.map +1 -0
  57. package/dist/{chunk-5KVHID6A.js → chunk-SPWVPIFQ.js} +4 -4
  58. package/dist/{chunk-5KVHID6A.js.map → chunk-SPWVPIFQ.js.map} +1 -1
  59. package/dist/chunk-SSHDBOXF.js +71 -0
  60. package/dist/chunk-SSHDBOXF.js.map +1 -0
  61. package/dist/{chunk-6OUDPSSG.js → chunk-TWDHNIY6.js} +3 -3
  62. package/dist/{chunk-6OUDPSSG.js.map → chunk-TWDHNIY6.js.map} +1 -1
  63. package/dist/{chunk-SC3FAFZN.js → chunk-UF4MZSLT.js} +2 -2
  64. package/dist/chunk-UF4MZSLT.js.map +1 -0
  65. package/dist/chunk-ZJ5AOYBH.js +8 -0
  66. package/dist/chunk-ZJ5AOYBH.js.map +1 -0
  67. package/dist/{chunk-LEC4U6CX.js → chunk-ZVJZ72HY.js} +3 -3
  68. package/dist/{chunk-LEC4U6CX.js.map → chunk-ZVJZ72HY.js.map} +1 -1
  69. package/dist/circuit-breaker/index.d.ts +1 -0
  70. package/dist/circuit-breaker/index.js +1 -1
  71. package/dist/{core-BmlJOvxM.d.ts → core-42kapW0n.d.ts} +96 -6
  72. package/dist/deadline/index.d.ts +77 -11
  73. package/dist/deadline/index.js +3 -3
  74. package/dist/devtools/index.js +8 -28
  75. package/dist/devtools/index.js.map +1 -1
  76. package/dist/do/index.d.ts +52 -24
  77. package/dist/do/index.js +94 -92
  78. package/dist/do/index.js.map +1 -1
  79. package/dist/{durable-effects-9LxFpmh3.d.ts → durable-effects-CFRngTNA.d.ts} +3 -3
  80. package/dist/extension/index.d.ts +25 -7
  81. package/dist/extension/index.js +4 -3
  82. package/dist/extension/index.js.map +1 -1
  83. package/dist/extension/react.js +4 -3
  84. package/dist/extension/react.js.map +1 -1
  85. package/dist/extension/subs/index.d.ts +2 -2
  86. package/dist/extension/subs/index.js +7 -10
  87. package/dist/extension/subs/index.js.map +1 -1
  88. package/dist/fan-out/index.d.ts +10 -10
  89. package/dist/fan-out/index.js +3 -3
  90. package/dist/idempotent-intake/index.d.ts +2 -2
  91. package/dist/idempotent-intake/index.js +3 -3
  92. package/dist/index.d.ts +50 -489
  93. package/dist/index.js +2 -2
  94. package/dist/llm-call/index.d.ts +3 -2
  95. package/dist/llm-call/index.js +12 -10
  96. package/dist/machine-B_BF4jij.d.ts +804 -0
  97. package/dist/machine-viz/index.d.ts +1 -1
  98. package/dist/machine-viz/index.js +2 -2
  99. package/dist/mem/index.d.ts +3 -2
  100. package/dist/mem/index.js.map +1 -1
  101. package/dist/monitored-run/index.d.ts +54 -30
  102. package/dist/monitored-run/index.js +7 -7
  103. package/dist/node/index.d.ts +3 -2
  104. package/dist/node/index.js +2 -3
  105. package/dist/node/index.js.map +1 -1
  106. package/dist/paginated-walk/index.d.ts +2 -2
  107. package/dist/paginated-walk/index.js +11 -10
  108. package/dist/parity/index.d.ts +6 -5
  109. package/dist/parity/index.js +5 -4
  110. package/dist/parity/index.js.map +1 -1
  111. package/dist/pbt/arbitraries/index.d.ts +1 -1
  112. package/dist/pbt/index.d.ts +1 -1
  113. package/dist/pbt/index.js +3 -3
  114. package/dist/pbt/index.js.map +1 -1
  115. package/dist/pbt/runners/index.d.ts +1 -1
  116. package/dist/pbt/runners/index.js +3 -3
  117. package/dist/poller/index.d.ts +59 -17
  118. package/dist/poller/index.js +56 -20
  119. package/dist/poller/index.js.map +1 -1
  120. package/dist/prediction/index.d.ts +1 -1
  121. package/dist/prediction/index.js +2 -2
  122. package/dist/pure/index.d.ts +1 -1
  123. package/dist/pure/index.js +2 -2
  124. package/dist/rate-limit/index.d.ts +2 -2
  125. package/dist/rate-limit/index.js +1 -1
  126. package/dist/react/index.d.ts +10 -3
  127. package/dist/react/index.js +2 -2
  128. package/dist/react/index.js.map +1 -1
  129. package/dist/reconciler/index.d.ts +2 -2
  130. package/dist/reconciler/index.js +13 -15
  131. package/dist/reconciler/index.js.map +1 -1
  132. package/dist/recorder/index.d.ts +20 -20
  133. package/dist/recorder/index.js +1 -1
  134. package/dist/resilient-call/index.d.ts +31 -6
  135. package/dist/resilient-call/index.js +10 -9
  136. package/dist/retry-backoff/index.d.ts +191 -14
  137. package/dist/retry-backoff/index.js +1 -1
  138. package/dist/retry-to-success/index.js +1 -1
  139. package/dist/runtime-types-CukV8kMX.d.ts +406 -0
  140. package/dist/saga/index.d.ts +3 -2
  141. package/dist/saga/index.js +2 -2
  142. package/dist/saga/index.js.map +1 -1
  143. package/dist/snapshot/index.d.ts +1 -1
  144. package/dist/snapshot/index.js +3 -3
  145. package/dist/subs/index.d.ts +372 -4
  146. package/dist/subs/index.js +4 -1
  147. package/dist/testing/index.d.ts +2 -2
  148. package/dist/testing/index.js +2 -2
  149. package/dist/throttled-input/index.d.ts +2 -2
  150. package/dist/throttled-input/index.js +14 -13
  151. package/dist/throttled-input/index.js.map +1 -1
  152. package/dist/token-refresh/index.d.ts +1 -1
  153. package/dist/token-refresh/index.js +3 -3
  154. package/dist/trace-replay/index.d.ts +12 -7
  155. package/dist/trace-replay/index.js +4 -3
  156. package/dist/{types-BoUWgwSm.d.ts → types-KbL3W1mj.d.ts} +1 -1
  157. package/dist/with-deadline/index.d.ts +1 -1
  158. package/dist/with-deadline/index.js +2 -2
  159. package/dist/with-resilience/index.d.ts +21 -2
  160. package/dist/with-resilience/index.js +12 -23
  161. package/dist/with-resilience/index.js.map +1 -1
  162. package/dist/with-telemetry/index.d.ts +1 -1
  163. package/dist/with-telemetry/index.js +2 -2
  164. package/dist/work-queue/adapter.d.ts +2 -2
  165. package/dist/work-queue/adapter.js +2 -2
  166. package/dist/work-queue/index.d.ts +3 -2
  167. package/dist/work-queue/index.js +1 -1
  168. package/dist/work-queue/index.js.map +1 -1
  169. package/dist/work-queue/ops.d.ts +2 -2
  170. package/dist/work-queue/ops.js +1 -1
  171. package/dist/workflow/index.d.ts +51 -19
  172. package/dist/workflow/index.js +71 -52
  173. package/dist/workflow/index.js.map +1 -1
  174. package/package.json +4 -1
  175. package/dist/chunk-2YOE5PIS.js.map +0 -1
  176. package/dist/chunk-3545BOYL.js.map +0 -1
  177. package/dist/chunk-5VNWZADP.js.map +0 -1
  178. package/dist/chunk-6GIGQT6C.js.map +0 -1
  179. package/dist/chunk-6VDCTR7N.js.map +0 -1
  180. package/dist/chunk-DYRKDCJI.js.map +0 -1
  181. package/dist/chunk-EGWTT5XJ.js.map +0 -1
  182. package/dist/chunk-EMJTPHI7.js +0 -91
  183. package/dist/chunk-EMJTPHI7.js.map +0 -1
  184. package/dist/chunk-FOGL3TC7.js.map +0 -1
  185. package/dist/chunk-IK4S5DJN.js.map +0 -1
  186. package/dist/chunk-M6UZT7HS.js.map +0 -1
  187. package/dist/chunk-NSDFA6M6.js +0 -198
  188. package/dist/chunk-NSDFA6M6.js.map +0 -1
  189. package/dist/chunk-SC3FAFZN.js.map +0 -1
  190. package/dist/chunk-SKAMZKTE.js +0 -20
  191. package/dist/chunk-SKAMZKTE.js.map +0 -1
  192. package/dist/chunk-TGG26XMK.js.map +0 -1
  193. package/dist/chunk-UV6UWGEO.js.map +0 -1
  194. package/dist/chunk-UYWUVEP6.js.map +0 -1
  195. package/dist/chunk-VFGKTGGC.js.map +0 -1
  196. package/dist/chunk-ZDRN3YCE.js.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/subs/define-listener.ts","../src/subs/from-broadcast-channel.ts","../src/subs/from-event-source.ts","../src/subs/from-event-target.ts","../src/subs/from-interval.ts","../src/subs/from-port.ts","../src/subs/from-reconnecting-web-socket.ts","../src/subs/from-web-socket.ts","../src/subs/managed-resource.ts","../src/subs/transport.ts"],"names":[],"mappings":"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","file":"chunk-LYLDCIPH.js","sourcesContent":["// ---------------------------------------------------------------------------\n// defineListener — listener-as-resource. The kernel primitive that turns an\n// imperative `add`/`remove` listener pair into a substrate-owned Sub whose\n// cleanup is MANDATORY and DERIVED — a forgotten or no-op disposer is\n// unrepresentable.\n//\n// The leak it kills: every author who wires a raw listener hand-rolls the SAME\n// skeleton —\n//\n// return (sub, _ctx, dispatch) => {\n// const listener = (e) => { const m = msgFn(e, sub); if (m !== null) dispatch(m); };\n// target.addListener(listener);\n// return () => target.removeListener(listener); // ← author-driven\n// };\n//\n// — and each re-write can SILENTLY LEAK two ways the type system never sees:\n//\n// 1. The disposer is a no-op (`return () => {}`) — the listener stays armed\n// forever. Nothing forces the returned cleanup to undo the add.\n// 2. The disposer calls `remove` with a DIFFERENT function than `add`\n// received (a fresh closure, a re-bound `this`) — the original listener\n// is never removed; it leaks while a phantom remove succeeds silently.\n//\n// `fromBroadcastChannel`, `fromEventTarget`, and every host-side bridge\n// hand-roll this skeleton today. They happen to be correct — but the\n// correctness is convention (rung 5), not structure. `defineListener` moves it\n// to rung 1: the listener the substrate builds is the SAME reference handed to\n// both `add` and `remove`, and the cleanup IS `() => remove(thatListener, ...)`.\n// You cannot write a no-op disposer because you do not write the disposer at\n// all.\n//\n// Shape:\n//\n// // Args is the tuple chrome invokes the listener with — here a single\n// // [Alarm]. The substrate builds ONE listener of this exact native shape.\n// const fromChromeAlarm = defineListener<[chrome.alarms.Alarm], S>({\n// add: (l) => chrome.alarms.onAlarm.addListener(l),\n// remove: (l) => chrome.alarms.onAlarm.removeListener(l),\n// });\n//\n// // ...then at the subscribe cell, supply the Msg projection:\n// subscribe: { tick: fromChromeAlarm<M>((sub, alarm) => ({ type: \"tick\", ... })) }\n//\n// - `add`/`remove` receive `(listener, sub, ctx)`. The `listener` is the\n// EXACT function the substrate built; pass it straight to the platform's\n// addListener/removeListener — NO wrapper. A wrapper inside `add` would\n// break reference identity (the platform stores the wrapper, but the\n// substrate hands `remove` the unwrapped listener → silent leak). Carrying\n// the platform's native `Args` tuple on the listener is what lets it be\n// registered directly. `sub`/`ctx` let the target be pulled from `ctx` or\n// keyed on the `sub` (the `getTarget()`-from-ctx shape fromPort needs).\n//\n// - `msgFn(sub, ...args) => M | null` — the universal drop-on-null filter\n// every factory in this subpath carries. `null` drops the dispatch; the\n// listener stays armed. `sub` is first (always present); the platform args\n// follow in their native order.\n//\n// Generalizes the `from*` pattern: a closure-owned disposer makes a forgotten\n// cleanup a syntax error, and this brings it to every host/satellite.\n//\n// Strengthens invariant 4 (external events enter as Subs; the lifecycle —\n// add on subscribe, remove on cleanup — is owned by the substrate, never the\n// author) and invariant 6 (no silent looseness: a leaked listener is a silent\n// failure, and this primitive removes the way to write one).\n// ---------------------------------------------------------------------------\n\nimport type { Sub } from \"../index\";\nimport { dispatchIfPresent, type SubscribeHandler } from \"./types\";\n\n/**\n * The imperative listener target, expressed as the `add`/`remove` pair the\n * substrate pairs into a reconciled resource. Both halves receive the SAME\n * `listener` reference — the substrate builds it once — so `remove` can never\n * be handed a different function than `add` saw.\n *\n * `add`/`remove` also receive `(sub, ctx)` so the target may be resolved from\n * `ctx` (a runtime, a port, a DOM node looked up lazily) or keyed on the Sub\n * (an alarm name, a channel name). Resolving the target inside `add` and\n * closing over nothing but the listener keeps a target swap from leaking the\n * old target's listener — same rationale as `fromEventTarget`'s captured\n * target.\n *\n * `Args` is the tuple the platform invokes the listener with — `[Alarm]` for\n * `chrome.alarms.onAlarm`, `[message, sender, sendResponse]` for\n * `chrome.runtime.onMessage`, `[changes, areaName]` for\n * `chrome.storage.onChanged`. The substrate builds ONE function of this exact\n * shape and threads the IDENTICAL reference to both `add` and `remove` — so a\n * platform that matches `removeListener` by reference (every chrome surface)\n * removes exactly what it added. A consumer that wrapped the listener inside\n * `add` would break reference identity (leak #2 above); carrying the native\n * `Args` on the listener itself removes the need for any wrapper.\n */\nexport interface ListenerTarget<\n Args extends readonly unknown[],\n S extends Sub,\n Ctx,\n> {\n add: (listener: (...args: Args) => void, sub: S, ctx: Ctx) => void;\n remove: (listener: (...args: Args) => void, sub: S, ctx: Ctx) => void;\n}\n\n/**\n * Build a listener-backed Sub factory whose cleanup is mandatory and derived.\n *\n * `Args`, `S`, `Ctx` are fixed when the target is defined — they describe the\n * one listener topology this factory owns (the platform arg tuple, which Sub\n * shape it filters on, what context resolves it). `M` is supplied at the\n * subscribe cell because the same listener feeds different Msgs in different\n * machines. Pinning `S` here (rather than at the `msgFn` step) lets\n * `add`/`remove` read the concrete Sub's fields — `sub.alarmName`,\n * `sub.channel` — with NO cast at the substrate edge: the narrowing happens in\n * author-land where the Sub shape is in scope.\n *\n * The returned function is a `SubscribeHandler` factory in the exact shape of\n * every other `@demlik/tea/subs` battery: supply the `msgFn` projection, get a\n * `(sub, ctx, dispatch) => cleanup` handler assignable to `machine.subscribe`.\n * The cleanup the author NEVER writes is `() => remove(listener, sub, ctx)`,\n * with `listener` the identical reference passed to `add`.\n */\nexport function defineListener<\n Args extends readonly unknown[],\n S extends Sub = Sub,\n Ctx = unknown,\n>(\n target: ListenerTarget<Args, S, Ctx>,\n): <M>(\n msgFn: (sub: S, ...args: Args) => M | null,\n) => SubscribeHandler<S, M, Ctx> {\n return <M>(\n msgFn: (sub: S, ...args: Args) => M | null,\n ): SubscribeHandler<S, M, Ctx> => {\n return (sub, ctx, dispatch) => {\n const listener = (...args: Args): void => {\n dispatchIfPresent(dispatch, msgFn(sub, ...args));\n };\n // ONE listener, identical reference to both halves — chrome's\n // removeListener matches by reference, so this removes exactly what\n // add registered. No wrapper, no WeakMap, no reference drift.\n target.add(listener, sub, ctx);\n return () => {\n target.remove(listener, sub, ctx);\n };\n };\n };\n}\n","// ---------------------------------------------------------------------------\n// fromBroadcastChannel — universal `BroadcastChannel`-shaped Sub factory.\n//\n// `BroadcastChannel` is a same-origin pub/sub primitive available in\n// every modern browser context (window, worker, service worker) AND in\n// Cloudflare Workers (with origin scoped to the worker isolate). The\n// constructor opens the channel; the `message` event fires whenever\n// another participant on the same channel name posts a message; the\n// `close()` method releases the OS resource.\n//\n// The factory carries `channelName` on the Sub itself (not in the\n// closure) so the same factory instance handles every BroadcastChannel\n// Sub in a machine — one Sub per channel, named by id, sharing the\n// factory's lifecycle code. Renaming a channel requires a new Sub.id\n// (the reconcile pass otherwise leaves the existing channel open).\n//\n// `msgFn` returns `M | null` — same drop-on-null pattern as\n// `fromEventTarget`. The MessageEvent's `data` is `unknown` from the\n// platform's perspective; the caller decides whether the payload is a\n// recognizable shape, and returns `null` to drop unknown messages\n// without polluting the reducer with parse-discard cells.\n//\n// Cleanup calls `channel.close()`, which removes the listener AND\n// releases the channel reference — `removeEventListener` alone would\n// leak the BroadcastChannel handle. The pair is intentional.\n// ---------------------------------------------------------------------------\n\nimport type { Sub } from \"../index\";\nimport type {\n MinimalBroadcastChannelCtor,\n MinimalMessageEvent,\n} from \"./platform\";\nimport { dispatchIfPresent, type SubscribeHandler } from \"./types\";\n\ntype BroadcastSubData = { channelName: string };\n\ndeclare const BroadcastChannel: MinimalBroadcastChannelCtor;\n\nexport function fromBroadcastChannel<S extends Sub & BroadcastSubData, M>(\n msgFn: (event: MinimalMessageEvent, sub: S) => M | null,\n): SubscribeHandler<S, M, unknown> {\n return (sub, _ctx, dispatch) => {\n const channel = new BroadcastChannel(sub.channelName);\n const listener = (event: MinimalMessageEvent): void => {\n dispatchIfPresent(dispatch, msgFn(event, sub));\n };\n channel.addEventListener(\"message\", listener);\n return () => {\n channel.removeEventListener(\"message\", listener);\n channel.close();\n };\n };\n}\n","// ---------------------------------------------------------------------------\n// fromEventSource — universal `EventSource` (server-sent events) Sub\n// factory.\n//\n// SSE is the asymmetric streaming protocol used by the agent log /\n// audit log pipelines: HTTP-shaped open, server pushes events forever,\n// client receives until close. `EventSource` is the standard browser\n// API; the constructor opens the connection, `message` fires per\n// server-pushed event, `error` fires on disconnect or non-2xx, `open`\n// fires once on initial handshake, `close()` tears the connection\n// down.\n//\n// Three callbacks because three observable events ARE three concerns\n// the caller routinely cares about:\n//\n// - `onMessage(data, sub)` — required. The SSE payload's `data` field\n// is `string` from the platform; the caller decides parse + Msg\n// shape. Returns `M | null`; null drops the dispatch.\n//\n// - `onError(event, sub)` — optional. SSE's `error` event fires both\n// on transient network drops (the browser auto-reconnects) AND on\n// permanent failures. Most consumers want to surface ONE of these\n// as a Msg (e.g. \"stream-disconnected\") without folding both into\n// `onMessage`'s discriminant. Omitting the callback drops error\n// events silently.\n//\n// - `onOpen(sub)` — optional. The connection's initial handshake\n// succeeded. Useful for \"show 'connected' indicator\" Msgs. Omitting\n// drops open events silently. NOT fired on auto-reconnect by every\n// browser implementation — treat the event as best-effort.\n//\n// `url` lives on the Sub itself (`EventSourceSubData`). Same id +\n// different url = the substrate's reconcile pass leaves the existing\n// connection alone (the URL change is silent). Changing the URL\n// requires a new Sub.id.\n//\n// Cleanup is `close()` + `removeEventListener` for all three —\n// belt-and-suspenders against EventSource implementations that\n// dispatch events into already-closed sources.\n// ---------------------------------------------------------------------------\n\nimport type { Sub } from \"../index\";\nimport type {\n MinimalEvent,\n MinimalEventSourceCtor,\n MinimalMessageEvent,\n} from \"./platform\";\nimport { dispatchIfPresent, type SubscribeHandler } from \"./types\";\n\ntype EventSourceSubData = { url: string };\n\ndeclare const EventSource: MinimalEventSourceCtor;\n\nexport interface EventSourceFactoryOpts<S, M> {\n onMessage: (data: string, sub: S) => M | null;\n onError?: (event: MinimalEvent, sub: S) => M | null;\n onOpen?: (sub: S) => M | null;\n}\n\nexport function fromEventSource<S extends Sub & EventSourceSubData, M>(\n opts: EventSourceFactoryOpts<S, M>,\n): SubscribeHandler<S, M, unknown> {\n return (sub, _ctx, dispatch) => {\n const source = new EventSource(sub.url);\n\n const messageListener = (event: MinimalMessageEvent): void => {\n // The SSE `data` payload is a string per the platform contract.\n // Cast at the boundary: callers express `(data: string) => M | null`\n // and never see `unknown` flowing in from the platform global.\n dispatchIfPresent(dispatch, opts.onMessage(event.data as string, sub));\n };\n source.addEventListener(\"message\", messageListener);\n\n const onError = opts.onError;\n const errorListener = onError\n ? (event: MinimalEvent): void => {\n dispatchIfPresent(dispatch, onError(event, sub));\n }\n : undefined;\n if (errorListener) source.addEventListener(\"error\", errorListener);\n\n const onOpen = opts.onOpen;\n const openListener = onOpen\n ? (): void => {\n dispatchIfPresent(dispatch, onOpen(sub));\n }\n : undefined;\n if (openListener) source.addEventListener(\"open\", openListener);\n\n return () => {\n source.removeEventListener(\"message\", messageListener);\n if (errorListener) source.removeEventListener(\"error\", errorListener);\n if (openListener) source.removeEventListener(\"open\", openListener);\n source.close();\n };\n };\n}\n","// ---------------------------------------------------------------------------\n// fromEventTarget — universal addEventListener/removeEventListener Sub\n// factory over any `EventTarget` (DOM nodes, `window`, `document`,\n// `BroadcastChannel`, `MessagePort`, `Worker`, `ServiceWorker`, etc.).\n//\n// Two non-obvious shape choices:\n//\n// - `getTarget: () => EventTarget` is a thunk, not a direct value. The\n// factory MUST work in environments where the target is not available\n// at module-load time — service workers run before any `document`\n// exists; some targets are constructed lazily from `ctx`. Deferring\n// the lookup to subscribe time also means a state-driven swap of the\n// target requires only a new Sub.id; the factory pulls the current\n// target on subscribe.\n//\n// - `msgFn` may return `null` — that emission is *dropped*. The event\n// fired, the factory called `msgFn`, but the caller decided \"this\n// event is not interesting given current state.\" No dispatch is made;\n// the listener stays armed for the next event. This is the explicit\n// filter point at the Sub boundary — the alternative (filtering inside\n// the reducer via a noop cell) costs a transition + reconcile pass per\n// filtered event.\n//\n// Cleanup uses the SAME `getTarget()` instance captured at subscribe time\n// so a target swap mid-Sub doesn't leak the listener on the old target.\n// We close over the resolved target rather than calling `getTarget()`\n// again at cleanup — the alternative (re-resolving) is the cause of\n// silent listener leaks in long-lived runtimes.\n// ---------------------------------------------------------------------------\n\nimport type { Sub } from \"../index\";\nimport type { MinimalEvent, MinimalEventTarget } from \"./platform\";\nimport { dispatchIfPresent, type SubscribeHandler } from \"./types\";\n\nexport function fromEventTarget<S extends Sub, M>(\n getTarget: () => MinimalEventTarget,\n eventName: string,\n msgFn: (event: MinimalEvent, sub: S) => M | null,\n): SubscribeHandler<S, M, unknown> {\n return (sub, _ctx, dispatch) => {\n const target = getTarget();\n const listener = (event: MinimalEvent): void => {\n dispatchIfPresent(dispatch, msgFn(event, sub));\n };\n target.addEventListener(eventName, listener);\n return () => {\n target.removeEventListener(eventName, listener);\n };\n };\n}\n","// ---------------------------------------------------------------------------\n// fromInterval — universal `setInterval`-shaped Sub factory.\n//\n// The recurring shape at every `subscribe[type]` cell that wraps a periodic\n// timer is the same five lines: `setInterval` the dispatch, return a\n// `clearInterval` cleanup. The factory absorbs both halves of the lifecycle\n// (subscribe + cleanup); the caller keeps the intent — which Sub variant,\n// which Msg to dispatch.\n//\n// Timing data (`intervalMs`) lives on the Sub itself, not in the closure.\n// The substrate already reconciles Subs by id on every transition; carrying\n// the period on the Sub means a `subscriptions(state)` function that\n// returns `{ id, type, intervalMs: 30_000 }` in one state and the same id\n// with `intervalMs: 60_000` later... will NOT re-wire the interval (the id\n// is unchanged, the reconcile pass leaves it running). That's a tradeoff\n// the substrate's reconcile contract pins, not this factory — if you want\n// the interval to change, change the Sub's id too.\n//\n// Strengthens invariant 9 (named, small surface) and invariant 2 (the Sub\n// carries the data; the lifecycle is in the factory; the reducer never\n// sees the timer).\n// ---------------------------------------------------------------------------\n\nimport type { Sub } from \"../index\";\nimport type { SubscribeHandler } from \"./types\";\n\ntype IntervalSubData = { intervalMs: number };\n\nexport function fromInterval<S extends Sub & IntervalSubData, M>(\n msgFn: (sub: S) => M,\n): SubscribeHandler<S, M, unknown> {\n return (sub, _ctx, dispatch) => {\n const handle = setInterval(() => {\n dispatch(msgFn(sub));\n }, sub.intervalMs);\n return () => {\n clearInterval(handle);\n };\n };\n}\n","// ---------------------------------------------------------------------------\n// fromPort — universal `Runtime.subscribePort`-shaped Sub factory.\n//\n// The canonical cross-runtime coordination pattern in @demlik/tea: one\n// runtime emits to a typed Port; another runtime's machine declares an\n// `audit-idle`-shaped Sub whose subscribe handler attaches to the\n// publisher's Port. The recurring 4-line shape at every such cell:\n//\n// \"audit-idle\": (_sub, ctx, dispatch) =>\n// ctx.auditRuntime.subscribePort(auditIdlePort, (value) => {\n// dispatch(buildMsg(value));\n// });\n//\n// The factory absorbs the cleanup-returning subscribePort call AND the\n// dispatch-on-emit wiring. The caller keeps the three pieces of intent:\n//\n// 1. `selectRuntime(ctx)` — which sibling Runtime publishes the Port.\n// This is a selector (not a direct ctx.auditRuntime reference) so\n// the factory does NOT pin the ctx shape — any machine whose ctx\n// exposes some Runtime<...> reference is compatible by selecting it.\n// A direct `Runtime` parameter would have made the factory shape\n// depend on having that Runtime in scope at machine-definition\n// time, which is a worse match for the \"ctx is the boundary\"\n// contract this substrate already commits to.\n//\n// 2. `port: Port<T>` — the typed channel itself. Identity is by\n// reference (the substrate enforces unique names at definePort\n// time); pass the same `Port<T>` value the publisher emits to.\n//\n// 3. `msgFn(value, sub) => M | null` — the value-to-Msg mapping, with\n// drop-on-null semantics matching every other factory in this\n// subpath. The published value's type `T` flows through to `msgFn`'s\n// first param so the caller never re-asserts the payload shape.\n//\n// Strengthens invariant 9 (the cross-runtime Port topology is named at\n// one factory site instead of redrawn at every subscribe cell).\n// ---------------------------------------------------------------------------\n\nimport type { Port, Runtime, Sub } from \"../index\";\nimport { dispatchIfPresent, type SubscribeHandler } from \"./types\";\n\n// `RS` / `RM` carry the sibling Runtime's concrete State and Msg through the\n// selector: a caller that selects a `Runtime<AuditState, AuditMsg>` keeps that\n// type at the seam instead of collapsing it to `Runtime<unknown, {type:string}>`.\n// They default to the prior broad pair so existing call sites that select a\n// less-specific Runtime still compile — the factory still only ever calls\n// `subscribePort`, so it does not read RS/RM, but the boundary no longer\n// hardcodes the widest type when the caller knows a narrower one.\nexport function fromPort<\n S extends Sub,\n M,\n Ctx,\n T,\n RS = unknown,\n RM extends { type: string } = { type: string },\n>(\n selectRuntime: (ctx: Ctx) => Runtime<RS, RM>,\n port: Port<T>,\n msgFn: (value: T, sub: S) => M | null,\n): SubscribeHandler<S, M, Ctx> {\n return (sub, ctx, dispatch) => {\n const runtime = selectRuntime(ctx);\n return runtime.subscribePort(port, (value) => {\n dispatchIfPresent(dispatch, msgFn(value, sub));\n });\n };\n}\n","// ---------------------------------------------------------------------------\n// fromReconnectingWebSocket — a `WebSocket` Sub factory that survives drops.\n//\n// `fromWebSocket` is the SSE-sibling ONE-SHOT factory: its cleanup `close()`s\n// the socket and it never re-opens on an unexpected drop (issue #188). That is\n// the right shape for a connection the caller manages, but any tea client over\n// an unreliable transport — games, presence, collab — needs the connection to\n// recover transparently, without a page reload. Rather than re-roll a backoff\n// loop in every consumer (vortex's `ReconnectingSocket` is the proven shape,\n// services/vortex/src/reconnecting-socket.ts), this is the reconnecting variant\n// folded into the substrate's Sub surface.\n//\n// The reconnect contract, mirroring vortex's wrapper:\n//\n// - On an UNEXPECTED close (`onclose` the substrate didn't initiate) it\n// re-opens after a CAPPED exponential backoff: `min(base * 2^attempt, max)`.\n// A successful handshake resets `attempt` to 0, so the next drop retries\n// fast again.\n//\n// - A CALLER-INITIATED close (the Sub's cleanup, e.g. the machine\n// transitioned the Sub away) suppresses reconnect: handlers are detached\n// BEFORE `close(1000)` so the teardown can't fire `onClose` or schedule a\n// reconnect. After cleanup the factory never re-opens.\n//\n// - `connect` and `schedule` are INJECTABLE (defaulting to the global\n// `WebSocket` and `setTimeout`/`clearTimeout`), the test seam that lets the\n// reconnect/backoff behavior be unit-tested with a fake socket + a manual\n// clock — no DOM, no real network, no timers — exactly as vortex's wrapper\n// is tested.\n//\n// Callback contract (same `M | null` \"null drops the dispatch\" shape as\n// `fromWebSocket`, plus one reconnect-specific hook):\n//\n// - `onMessage(data, sub)` — required. Raw `unknown` frame; the caller parses.\n// - `onOpen(sub)` — optional. Fires on EVERY successful handshake — the first\n// open AND every reconnect — so a \"connected\" indicator stays accurate.\n// - `onError(event, sub)` — optional. The `error` event; `close` still drives\n// the reconnect (one path), so this is purely a notification.\n// - `onClose(code, reason, sub)` — optional. Fires on every close the\n// substrate observes (a drop that will be retried, too) — NOT on the\n// caller's own teardown, whose handlers are detached first.\n// - `onReconnect(attempt, sub)` — optional. Fires when a RE-open (any open\n// after the first) succeeds, carrying the 1-based reconnect count. This is\n// the resync hook: the place to dispatch a \"re-fetch the authoritative\n// snapshot, discard predicted state\" Msg after the transport recovered.\n//\n// Like `fromWebSocket`, `wsUrl` lives on the Sub (`WebSocketSubData`); every\n// reconnect reads `sub.wsUrl` afresh, so a reconcile that re-subscribes with a\n// new url reconnects to the new endpoint.\n// ---------------------------------------------------------------------------\n\nimport type { Sub } from \"../index\";\nimport type { WebSocketSubData } from \"./from-web-socket\";\nimport type {\n MinimalEvent,\n MinimalWebSocket,\n MinimalWebSocketCtor,\n} from \"./platform\";\nimport { dispatchIfPresent, type SubscribeHandler } from \"./types\";\n\ndeclare const WebSocket: MinimalWebSocketCtor;\n\n/** Cancels a scheduled reconnect timer (the inverse of `schedule`). */\nexport type CancelTimer = () => void;\n\nexport interface ReconnectingWebSocketFactoryOpts<S, M> {\n onMessage: (data: unknown, sub: S) => M | null;\n onOpen?: (sub: S) => M | null;\n onError?: (event: MinimalEvent, sub: S) => M | null;\n onClose?: (code: number, reason: string, sub: S) => M | null;\n /**\n * Fires when a reconnection (any open after the first) succeeds, carrying the\n * 1-based reconnect count — the resync hook. Omitting drops reconnect events\n * silently.\n */\n onReconnect?: (attempt: number, sub: S) => M | null;\n /** First backoff delay in ms (doubles each failed attempt). Default 250. */\n backoffBaseMs?: number;\n /** Backoff ceiling in ms. Default 5000. */\n backoffMaxMs?: number;\n /** Inject the socket constructor (defaults to the global `WebSocket`); the test seam. */\n connect?: (url: string) => MinimalWebSocket;\n /** Inject the timer (defaults to `setTimeout`/`clearTimeout`); the test seam. */\n schedule?: (fn: () => void, ms: number) => CancelTimer;\n}\n\nconst defaultConnect = (url: string): MinimalWebSocket => new WebSocket(url);\n\nconst defaultSchedule = (fn: () => void, ms: number): CancelTimer => {\n const handle = setTimeout(fn, ms);\n return () => clearTimeout(handle);\n};\n\nexport function fromReconnectingWebSocket<S extends Sub & WebSocketSubData, M>(\n opts: ReconnectingWebSocketFactoryOpts<S, M>,\n): SubscribeHandler<S, M, unknown> {\n const backoffBaseMs = opts.backoffBaseMs ?? 250;\n const backoffMaxMs = opts.backoffMaxMs ?? 5000;\n const connect = opts.connect ?? defaultConnect;\n const schedule = opts.schedule ?? defaultSchedule;\n\n const { onMessage, onOpen, onError, onClose, onReconnect } = opts;\n\n return (sub, _ctx, dispatch) => {\n let ws: MinimalWebSocket | null = null;\n let attempt = 0;\n let opened = false;\n let closedByCaller = false;\n let cancelReconnect: CancelTimer | null = null;\n\n const emit = (msg: M | null): void => dispatchIfPresent(dispatch, msg);\n\n const handleClose = (code: number, reason: string): void => {\n ws = null;\n if (onClose) emit(onClose(code, reason, sub));\n // A teardown the substrate initiated detaches handlers first, so this\n // path only runs for an UNEXPECTED close — reconnect with capped backoff.\n if (closedByCaller) return;\n const delay = Math.min(backoffBaseMs * 2 ** attempt, backoffMaxMs);\n attempt += 1;\n cancelReconnect = schedule(() => {\n cancelReconnect = null;\n if (!closedByCaller) open();\n }, delay);\n };\n\n function open(): void {\n const socket = connect(sub.wsUrl);\n ws = socket;\n\n socket.onopen = (): void => {\n // `opened` BEFORE this open marks it a reconnect; `attempt` is the\n // pending count for the drop that triggered it (1-based once we reset).\n const reconnected = opened;\n const reconnectCount = attempt;\n attempt = 0;\n opened = true;\n if (onOpen) emit(onOpen(sub));\n if (reconnected && onReconnect) emit(onReconnect(reconnectCount, sub));\n };\n\n socket.onmessage = (event): void => {\n emit(onMessage(event.data, sub));\n };\n\n // `error` precedes `close`; let `close` drive the reconnect so there is\n // one path. The handler is still wired (as a no-op when the caller didn't\n // supply `onError`) so a platform error event has a sink.\n socket.onerror = onError\n ? (event): void => emit(onError(event, sub))\n : (): void => {};\n\n socket.onclose = (event): void => {\n handleClose(event.code, event.reason);\n };\n }\n\n open();\n\n return () => {\n // Deliberate teardown: cancel any pending reconnect, detach handlers so\n // the cleanup `close()` can't fire `onClose` or schedule a reconnect,\n // then close the live socket. After this the factory never re-opens.\n closedByCaller = true;\n if (cancelReconnect !== null) {\n cancelReconnect();\n cancelReconnect = null;\n }\n const socket = ws;\n ws = null;\n if (socket !== null) {\n socket.onopen = null;\n socket.onmessage = null;\n socket.onerror = null;\n socket.onclose = null;\n // 1000 = \"normal closure\" per RFC 6455 §7.4.1.\n socket.close(1000);\n }\n };\n };\n}\n","// ---------------------------------------------------------------------------\n// fromWebSocket — universal `WebSocket` Sub factory. The symmetric sibling\n// of `fromEventSource`.\n//\n// WebSocket is the bidirectional streaming protocol used wherever the\n// server pushes events AND the client pushes commands on the same\n// connection. The constructor opens the connection, `onopen` fires once\n// on handshake, `onmessage` fires per server-pushed frame, `onerror`\n// fires on transient or permanent failure, `onclose` fires once on\n// teardown carrying `code` + `reason`. `close(code)` initiates a clean\n// shutdown from the client side.\n//\n// Four callbacks because four observable events ARE four concerns the\n// caller routinely cares about. The Sub's `wsUrl` lives on the Sub itself\n// (`WebSocketSubData`) — same id + different `wsUrl` is silent under the\n// reconcile pass, identical to `fromEventSource`'s `url` contract.\n//\n// - `onMessage(data, sub)` — required. The platform delivers `data` as\n// `string | Blob | ArrayBuffer` depending on `binaryType` and the\n// server's frame type. The factory does NOT bake in a parse contract:\n// the callback receives the raw `unknown` and decides parse + Msg\n// shape. Returns `M | null`; null drops the dispatch.\n//\n// - `onOpen(sub)` — optional. Connection handshake succeeded. Useful\n// for \"show 'connected' indicator\" Msgs. Omitting drops open events\n// silently.\n//\n// - `onError(event, sub)` — optional. WebSocket's `error` event fires\n// on transient drops AND permanent failures; like SSE, most consumers\n// want one Msg (\"connection-errored\") without folding both into\n// `onMessage`'s discriminant. Omitting drops error events silently.\n//\n// - `onClose(code, reason, sub)` — optional. The connection is gone.\n// `code` is the WebSocket close code (1000 = clean, 1006 = abnormal,\n// etc.). `reason` is the server-supplied string (often empty).\n// Omitting drops close events silently.\n//\n// Cleanup is `close(1000)` — the IANA-registered clean-shutdown code.\n// The factory intentionally does NOT bake in registry semantics (e.g.\n// the extension's `ctx.wsRegistry` so `interpret.send_ws` can reach the\n// live socket): that's consumer glue. A consumer that needs the live\n// socket for outbound sends wires it from inside the subscribe handler\n// or keeps a hand-written Sub alongside this factory.\n// ---------------------------------------------------------------------------\n\nimport type { Sub } from \"../index\";\nimport type {\n MinimalCloseEvent,\n MinimalEvent,\n MinimalMessageEvent,\n MinimalWebSocketCtor,\n} from \"./platform\";\nimport { dispatchIfPresent, type SubscribeHandler } from \"./types\";\n\nexport type WebSocketSubData = { wsUrl: string };\n\ndeclare const WebSocket: MinimalWebSocketCtor;\n\nexport interface WebSocketFactoryOpts<S, M> {\n onMessage: (data: unknown, sub: S) => M | null;\n onOpen?: (sub: S) => M | null;\n onError?: (event: MinimalEvent, sub: S) => M | null;\n onClose?: (code: number, reason: string, sub: S) => M | null;\n}\n\nexport function fromWebSocket<S extends Sub & WebSocketSubData, M>(\n opts: WebSocketFactoryOpts<S, M>,\n): SubscribeHandler<S, M, unknown> {\n return (sub, _ctx, dispatch) => {\n const ws = new WebSocket(sub.wsUrl);\n\n ws.onmessage = (event: MinimalMessageEvent): void => {\n // The platform delivers `data` as `unknown` — the factory hands it\n // through untouched and the caller chooses the parse strategy.\n dispatchIfPresent(dispatch, opts.onMessage(event.data, sub));\n };\n\n const onOpen = opts.onOpen;\n if (onOpen) {\n ws.onopen = (): void => {\n dispatchIfPresent(dispatch, onOpen(sub));\n };\n }\n\n const onError = opts.onError;\n if (onError) {\n ws.onerror = (event: MinimalEvent): void => {\n dispatchIfPresent(dispatch, onError(event, sub));\n };\n }\n\n const onClose = opts.onClose;\n if (onClose) {\n ws.onclose = (event: MinimalCloseEvent): void => {\n dispatchIfPresent(dispatch, onClose(event.code, event.reason, sub));\n };\n }\n\n return () => {\n // Detach the lifecycle handlers BEFORE close() so the consumer's\n // `onClose` Msg isn't fired from a teardown the substrate initiated.\n // Mirrors fromEventSource's removeEventListener pass.\n ws.onmessage = null;\n ws.onopen = null;\n ws.onerror = null;\n ws.onclose = null;\n // 1000 = \"normal closure\" per RFC 6455 §7.4.1.\n ws.close(1000);\n };\n };\n}\n","// ---------------------------------------------------------------------------\n// defineManagedResource — a Model-gated resource with a MANDATORY release.\n//\n// foldkit's ManagedResource, ported. The recurring shape: a resource whose\n// lifetime is \"while the machine is in phase X, hold Y open; on leaving X,\n// tear Y down\" — a checkpoint store, a span, a lease, a pooled connection.\n// Rule 9 says that is a Sub the substrate reconciles, NOT a start/end Cmd\n// pair hand-driven across cells. Hand-driving leaks: every transition cell\n// has to remember to end-then-start, and one missed cell leaks the resource\n// forever (the checkpoint-namespace leak this battery was built to kill).\n//\n// The author writes three things — `{ name, acquire, release }` — and gets\n// back a `.sub(key)` to drop into `subscriptions(state)` plus a `.subscribe`\n// handler to drop into the machine's `subscribe` record. The substrate's\n// existing `reconcileSubs` (packages/tea/src/run.ts) does the rest:\n// - sub APPEARS in the desired set → handler runs → `acquire(key, ctx)`.\n// - sub DISAPPEARS (phase left) → cleanup runs → `release(handle)`.\n// - sub KEY changes (id changes) → old cleanup (release) + new acquire.\n// Same reconcile mechanism `fromTransport` rides for open/close — no new\n// kernel hook.\n//\n// THE ACQUIRE-AS-SUCCESS-VALUE DISCIPLINE\n// ----------------------------------------\n// `release` receives ONLY what `acquire` returned (the Handle). Nothing else\n// is in scope. So:\n// - An `acquire` that throws never produces a Handle, so `release` is never\n// called with a half-built resource — an interrupted acquire can't dangle.\n// - Whatever `release` needs to tear down must be captured INTO the Handle\n// by `acquire`. The Handle is the closure over the live resource.\n// `release` is NOT optional in the type. A managed resource without a teardown\n// is just a Cmd (Rule 5); the whole point of this battery is the guaranteed\n// teardown, so the type forces the author to write one.\n//\n// NEUTRAL MECHANISM — no policy. The battery knows `acquire`, `release`,\n// `name`, `key`. It does not know what a checkpoint, a span, or a socket is.\n// Could a reasonable machine want the opposite of any choice here? The only\n// choices are \"release is mandatory\" (yes — that's the contract) and \"release\n// runs on disappear OR key-change\" (yes — that's reconcile semantics, shared\n// with every other Sub). No Cloudflare, no audit domain.\n//\n// Strengthens invariant 4 (external lifecycle owned by the substrate — the\n// acquire/release pair rides reconcileSubs, never hand-driven by a cell) and\n// invariant 6 (no silent looseness — every live resource is a Sub in the\n// desired set, visible in the Sub topology, and an unroutable one throws).\n// ---------------------------------------------------------------------------\n\nimport type { DepKeyedSub, Sub, SubId } from \"../index\";\nimport { structuralHash, subId } from \"../index\";\n\n/**\n * The Sub the battery builds. The consumer's `Sub` union includes\n * `ManagedResourceSub<TKey>` directly — the substrate reconciles by `id` and\n * `type` like any other Sub.\n */\nexport interface ManagedResourceSub<TKey> extends Sub<\"managed_resource\"> {\n /** The lifetime key. Stable while the resource should be held; a change\n * retires the old resource (release) and acquires a fresh one. */\n readonly key: TKey;\n}\n\n/**\n * A battery paired with a state-gate. Self-contained: it knows its `name`\n * (for the unique-name assert), how to derive its live Sub from a state\n * (`subFor`), how to handle its own `subscribe`, and whether it `owns` a given\n * Sub id.\n *\n * Generic in the consumer state `S`, the shared lifetime key `TKey`, and the\n * shared `Ctx`. `Handle` is the ONLY thing erased — each battery's handle type\n * differs (a checkpoint saver vs. a tool-call bridge) but never crosses this\n * interface, so a `combineManagedResources` array holds entries of\n * heterogeneous handle types with no cast. The sub type stays\n * `ManagedResourceSub<TKey>` end to end, so the substrate's narrowed Sub flows\n * straight in.\n */\nexport interface GatedManagedResource<S, TKey, Ctx> {\n /** The owning battery's `name`. The combinator asserts these are unique. */\n readonly name: string;\n\n /**\n * Derive this resource's live Sub for a given state: the battery's\n * `.sub(key)` when `when(state)` returns a key, or `null` when it returns\n * `null` (the resource should be torn down in that state).\n */\n readonly subFor: (state: S) => ManagedResourceSub<TKey> | null;\n\n /**\n * This battery's own `.subscribe`. The combinator routes an incoming\n * `managed_resource` Sub to the entry whose `owns(sub)` is true, then calls\n * this.\n */\n readonly subscribe: (\n sub: ManagedResourceSub<TKey>,\n ctx: Ctx,\n dispatch: (msg: never) => void,\n ) => () => void;\n\n /**\n * Does this entry own the given Sub? `sub.id === subIdFor(sub.key)` encodes\n * the battery `name`, so a match is unambiguous.\n */\n readonly owns: (sub: ManagedResourceSub<TKey>) => boolean;\n}\n\n/**\n * What `combineManagedResources` returns: the single managed-resource\n * `subscribe` cell for the machine's `subscribe` record, and a `subs(state)`\n * builder for `subscriptions(state)`.\n */\nexport interface CombinedManagedResources<S, TKey, Ctx> {\n /**\n * Map every entry through its `when(state)`, drop the `null`s, and build the\n * live Subs. Spread into `subscriptions(state)`. The derived list and the\n * `subscribe` routing share one source — the entries — so they never drift.\n */\n readonly subs: (state: S) => ManagedResourceSub<TKey>[];\n\n /**\n * The SINGLE `subscribe.managed_resource` handler. Routes an incoming Sub to\n * the owning battery by matching `sub.id === battery.subIdFor(sub.key)` (the\n * id encodes the battery `name`). Throws loudly on an unrouted id — a wiring\n * bug, never a silent no-op resource.\n */\n readonly subscribe: (\n sub: ManagedResourceSub<TKey>,\n ctx: Ctx,\n dispatch: (msg: never) => void,\n ) => () => void;\n}\n\nexport interface DefineManagedResourceOpts<TKey, Handle, Ctx> {\n /**\n * A short, stable name. The Sub id is `managed:${name}:${stringify(key)}`,\n * so two managed resources in one machine never collide and neither\n * collides with a `fromTransport` Sub keyed on the same identity.\n */\n readonly name: string;\n\n /**\n * Build the resource. Runs when the Sub appears in `subscriptions(state)`.\n * The returned Handle is the ONLY thing `release` receives — capture\n * everything teardown needs into it. If `acquire` throws, the substrate's\n * reconcile does NOT register the Sub and `release` is never called (no\n * dangling half-built resource).\n */\n readonly acquire: (key: TKey, ctx: Ctx) => Handle;\n\n /**\n * Tear the resource down. Runs when the Sub leaves the desired set (phase\n * exited) or when its key changes. MANDATORY — the guaranteed teardown is\n * the reason this battery exists. May be sync or async; the substrate's\n * cleanup is fire-and-forget (Rule 2), so a rejected teardown is logged at\n * the boundary, not surfaced as a Msg.\n */\n readonly release: (handle: Handle) => void | Promise<void>;\n}\n\n/**\n * What the battery returns: a `.sub(key)` builder for `subscriptions`, the\n * `.subscribe` handler for the machine's `subscribe` record, a `.get(key)`\n * accessor so Cmd handlers can reach the live Handle while the resource is\n * held, and a `.subIdFor(key)` for tests.\n */\nexport interface ManagedResourceBattery<TKey, Handle, Ctx> {\n /**\n * Build the Sub for `subscriptions(state)`. The consumer calls this from\n * inside their `subscriptions` cell for the phases where the resource\n * should be live.\n */\n readonly sub: (key: TKey) => ManagedResourceSub<TKey>;\n\n /**\n * Pair this battery with a state-gate, producing a self-contained entry for\n * `combineManagedResources`. `when(state)` returns the resource's KEY when\n * it should be live in that state, or `null` when it should be torn down.\n *\n * Folding \"is it active?\" and \"what key?\" into ONE declaration at the\n * battery's own definition site is the point: a central `subscriptions(state)`\n * list can silently forget to gate a new phase, but a per-resource gate\n * travels with the resource. The combinator derives BOTH the active-sub list\n * and the subscribe routing from these entries, so the two can never disagree.\n *\n * Generic in the consumer's state `S` (each `.gated` call binds its own `S`).\n * The returned entry erases this battery's `Handle` — `combineManagedResources`\n * holds entries of heterogeneous handle types with no cast — while keeping\n * `TKey` and `Ctx` so the substrate's narrowed `ManagedResourceSub<TKey>`\n * flows straight into the entry's `subscribe`.\n */\n readonly gated: <S>(\n when: (state: S) => TKey | null,\n ) => GatedManagedResource<S, TKey, Ctx>;\n\n /**\n * Dep-keyed entry for the machine's `subs` array. `when(state)` returns the\n * resource's KEY when it should be held, or `null` when it should be torn\n * down — the exact gate `.gated` takes, now producing a `DepKeyedSub` the\n * kernel folds directly instead of an entry the `combineManagedResources`\n * router multiplexes.\n *\n * This is the simplification the dep-keyed kernel unlocks: two managed\n * resources used to COLLIDE on the single `subscribe.managed_resource` key\n * (both subs share `type: \"managed_resource\"`), forcing\n * `combineManagedResources` to route by SubId. A dep-keyed Sub has its OWN\n * reconcile slot (its array position), so there is no shared `type` to route\n * — each resource is one `subs` entry, no combinator, no router. `acquire` /\n * `release` still ride the kernel's dispose-on-change / dispose-on-null\n * reconcile, now via the dep-keyed source's `Dispose` instead of the\n * `subscribe` cell's cleanup.\n */\n readonly depKeyed: <S>(\n when: (state: S) => TKey | null,\n ) => DepKeyedSub<S, never, Ctx>;\n\n /**\n * The `subscribe.managed_resource` handler. Drop straight into the\n * machine's `subscribe` record. Runs `acquire` on install, holds the\n * Handle in the battery's handle table, and returns a cleanup that runs\n * `release` (and forgets the Handle) on reconcile-out.\n */\n readonly subscribe: (\n sub: ManagedResourceSub<TKey>,\n ctx: Ctx,\n // The managed resource dispatches no Msgs — acquire/release are pure\n // lifecycle. `dispatch` is accepted to match the substrate's\n // `subscribe[type]` shape and is intentionally unused.\n dispatch: (msg: never) => void,\n ) => () => void;\n\n /**\n * Accessor for the live Handle, keyed on `key`. Returns `undefined` when\n * the resource is not currently held (no active sub for that key) — the\n * caller decides whether that is an error. A Cmd handler wires its\n * `ctx.getX(key)` through this, so the handler reads the SAME owner the\n * reconciler holds, never a hand-built duplicate.\n */\n readonly get: (key: TKey) => Handle | undefined;\n\n /** Pre-derived subId for `(key)` — handy for tests. */\n readonly subIdFor: (key: TKey) => SubId;\n}\n\n/**\n * Build the battery. One call per managed resource at the machine's host\n * file.\n *\n * @example\n * const checkpointStore = defineManagedResource<RunId, CheckpointSaver, Ctx>({\n * name: \"checkpoint\",\n * acquire: (runId, ctx) =>\n * new CheckpointSaver({ storage: ctx.storage, bucket: ctx.bucket, runId }),\n * release: (saver) => saver.clear(),\n * });\n *\n * // In the machine:\n * subscriptions: (state) =>\n * state.type === \"running\" ? [checkpointStore.sub(state.runId)] : [],\n * subscribe: { managed_resource: checkpointStore.subscribe },\n *\n * // In interpret (read the live handle the reconciler holds):\n * start_graph: async (cmd, ctx) => {\n * const saver = checkpointStore.get(cmd.runId); // same owner, no rebuild\n * ...\n * }\n */\nexport function defineManagedResource<TKey, Handle, Ctx>(\n opts: DefineManagedResourceOpts<TKey, Handle, Ctx>,\n): ManagedResourceBattery<TKey, Handle, Ctx> {\n // Handle table — one live resource per key. Lives in this closure for the\n // lifetime of the battery (the machine's lifetime). Interpret-local; never\n // crosses into reducer state. Same shape as `fromTransport`'s `live` map.\n const held = new Map<string, Handle>();\n\n // Single-sourced on the kernel's `structuralHash`: `\"1\"` (string) and `1`\n // (number) render distinctly, so a key collision can't return/release the\n // wrong held handle; non-JSON keys throw loudly instead of silently returning\n // undefined.\n const keyString = (k: TKey): string => structuralHash(k);\n\n const subIdFor = (key: TKey): SubId =>\n subId(`managed:${opts.name}:${keyString(key)}`);\n\n const subBuilder = (key: TKey): ManagedResourceSub<TKey> => ({\n id: subIdFor(key),\n type: \"managed_resource\",\n key,\n });\n\n const subscribeHandler = (\n sub: ManagedResourceSub<TKey>,\n ctx: Ctx,\n _dispatch: (msg: never) => void,\n ): (() => void) => {\n // Acquire. A throw here propagates out of reconcileSubs (the substrate\n // records it and re-throws after the diff pass) and the Sub is NOT\n // registered — so no cleanup runs and `held` stays clean. The\n // acquire-as-success-value discipline: no Handle, no release.\n const handle = opts.acquire(sub.key, ctx);\n const k = keyString(sub.key);\n held.set(k, handle);\n\n // Cleanup runs when the substrate's reconcileSubs leaves the phase OR\n // the key changes. Forget the Handle first so a concurrent `get` after\n // teardown begins sees `undefined`, then release.\n return () => {\n held.delete(k);\n try {\n const result = opts.release(handle);\n if (result instanceof Promise) {\n result.catch((err) => {\n // Boundary log: teardown failures are fire-and-forget (Rule 2).\n // The reducer is already past caring (the phase was left).\n console.warn(`[managed:${opts.name}] release failed`, err);\n });\n }\n } catch (err) {\n console.warn(`[managed:${opts.name}] release threw`, err);\n }\n };\n };\n\n return {\n sub: subBuilder,\n\n // Pair the battery with a state-gate. `TKey` / `Handle` / `Ctx` are in\n // scope here, so `subFor` builds the fully-typed sub and `subscribe`\n // forwards straight to the battery's own typed `subscribe`. The entry\n // erases ONLY `Handle` for the combinator (`TKey` and `Ctx` stay), so the\n // substrate's narrowed `ManagedResourceSub<TKey>` flows in with no cast.\n gated: <S>(\n when: (state: S) => TKey | null,\n ): GatedManagedResource<S, TKey, Ctx> => ({\n name: opts.name,\n owns: (sub) => sub.id === subIdFor(sub.key),\n subFor: (state) => {\n const key = when(state);\n return key === null ? null : subBuilder(key);\n },\n subscribe: subscribeHandler,\n }),\n\n // Dep-keyed entry. `when` is the gate (the deps = the KEY); the source runs\n // the SAME `acquire` → hold → `release`-on-dispose lifecycle the `subscribe`\n // cell does, now driven by the kernel's dep-keyed reconcile. The Msg type is\n // `never` (a managed resource dispatches nothing); the ignored `_dispatch`\n // matches the source signature.\n depKeyed: <S>(\n when: (state: S) => TKey | null,\n ): DepKeyedSub<S, never, Ctx> => ({\n deps: when,\n source: (state, _dispatch, ctx) => {\n const key = when(state);\n if (key === null) return () => {};\n return subscribeHandler(subBuilder(key), ctx, () => {});\n },\n }),\n\n subscribe: subscribeHandler,\n\n get: (key) => held.get(keyString(key)),\n\n subIdFor,\n };\n}\n\n// ---------------------------------------------------------------------------\n// combineManagedResources — fold N gated batteries into ONE subscribe cell + a\n// derived active-sub list.\n//\n// Two managed resources colliding on the single `subscribe.managed_resource`\n// key forced the machine to hand-roll a router (`if (sub.id === X.subIdFor(...))`).\n// A third copy-pastes another `if`. That router is glue that belongs in the\n// substrate, not the machine. AND the resources' active-phase gating lived in\n// a central `subscriptions(state)` list a new phase could silently forget.\n//\n// This combinator closes both gaps. Each entry carries its OWN gate at its\n// definition site (`battery.gated((s) => active(s) ? key : null)`), and the\n// combinator derives BOTH the active-sub list (`subs`) and the routing\n// (`subscribe`) from the same entries — so they can never disagree, and a new\n// phase that the gate doesn't cover simply produces no sub (no silent leak of\n// the central-list-forgot kind).\n//\n// NEUTRAL MECHANISM — no domain knowledge. The combinator knows `name` (for\n// the unique assert), `owns` (for routing), `subFor` (for the list), and\n// `subscribe` (to forward). It does not know what a checkpoint, a bridge, or a\n// Cloudflare anything is.\n//\n// Strengthens invariant 4 (external lifecycle owned by the substrate — both\n// the list and the routing are derived, never hand-driven) and invariant 6 (no\n// silent looseness — ONE managed-resource cell, and an unrouted id throws\n// loudly instead of silently installing a no-op).\n// ---------------------------------------------------------------------------\nexport function combineManagedResources<S, TKey, Ctx>(\n entries: readonly GatedManagedResource<S, TKey, Ctx>[],\n): CombinedManagedResources<S, TKey, Ctx> {\n // Assert unique names at construction — two batteries with the same `name`\n // collide on Sub id (`managed:${name}:${key}`), so the router could not tell\n // them apart. Throw immediately (mirrors foldkit's `combineSubscriptions` on\n // a duplicate key) rather than mis-route at runtime.\n const seen = new Set<string>();\n for (const entry of entries) {\n if (seen.has(entry.name)) {\n throw new Error(\n `[combineManagedResources] duplicate managed-resource name \"${entry.name}\" — names must be unique (the Sub id encodes the name; two batteries sharing it collide)`,\n );\n }\n seen.add(entry.name);\n }\n\n return {\n subs: (state) => {\n const live: ManagedResourceSub<TKey>[] = [];\n for (const entry of entries) {\n const sub = entry.subFor(state);\n if (sub !== null) live.push(sub);\n }\n return live;\n },\n\n subscribe: (sub, ctx, dispatch) => {\n const owner = entries.find((entry) => entry.owns(sub));\n if (owner === undefined) {\n // No battery owns this managed-resource SubId — a wiring bug. Surface\n // loudly rather than silently install a no-op resource (invariant 6).\n throw new Error(\n `[combineManagedResources] no battery for sub id ${sub.id}`,\n );\n }\n return owner.subscribe(sub, ctx, dispatch);\n },\n };\n}\n","// ---------------------------------------------------------------------------\n// fromTransport — the SEAM battery.\n//\n// At every seam between two TEA-shaped processes (a DO ↔ its hands container,\n// a SW background ↔ a content script, a parent ↔ a child runtime), there is\n// the SAME hand-rolled boilerplate:\n//\n// 1. The inbound message stream — a Sub keyed on a phase, opened when the\n// machine enters the phase, closed when the machine leaves it.\n// 2. A transport-close detector — when the underlying channel drops, a\n// single Msg is dispatched (`*_lost`). NOT a Sub the consumer\n// re-publishes; the lifetime IS the inbound Sub's lifetime.\n// 3. The outbound side — a Cmd whose handler hands a value to the live\n// transport, looked up by an interpret-local handle table keyed on the\n// phase's run-identity.\n//\n// Every hand-rolled seam (`hands-inbound.ts`, `forwardOutbound`, the\n// `client_disconnected` Msg arm, the WS handle map) is the same three\n// pieces with different vocabularies. The battery bakes those three in.\n//\n// What the author writes:\n//\n// 1. The vocabulary (Inbound / Outbound types).\n// 2. ONE call: `fromTransport({ name, openTransport, parseInbound, ... })`.\n//\n// What the author NEVER writes again:\n//\n// - The `Sub` shape, the `subscribe` cell, the `cleanup` Promise dance.\n// - The transport-close → `*_lost` Msg wiring.\n// - The \"find the live socket from inside an interpret handler\" lookup.\n// - The keepalive / heartbeat / sequence-number ceremony (none of those\n// belong in the machine — that's Rule 9: lifetime-bound resources are\n// Subs, not state-machine cells).\n//\n// Host-pluggable: the battery takes a `TransportFactory`, not a concrete\n// WebSocket. A Durable Object passes a workerd `WebSocket` adapter; a worker\n// thread can pass a `MessagePort` adapter; an in-process test can pass a\n// stub. Same battery; three different hosts.\n//\n// Distinct from `fromWebSocket` / `fromReconnectingWebSocket` in this same\n// subpath, which own a CONCRETE `WebSocket` and are inbound-only. This battery\n// is transport-agnostic and duplex: it also owns the outbound handle table, so\n// a Cmd handler can reach the live channel without a hand-written registry.\n//\n// Strengthens invariant 4 (external events / observation enter as Subs;\n// lifecycle owned by the substrate — the inbound stream, the close\n// detection, AND the outbound handle are all owned by the battery's Sub\n// lifecycle, not hand-driven by the consumer's cells).\n// ---------------------------------------------------------------------------\n\nimport type { DepKeyedSub, Sub, SubId } from \"../index\";\nimport { structuralHash, subId } from \"../index\";\n\n/**\n * Duplex transport. The host (DO, worker thread, in-process bus) constructs\n * one from whatever the platform offers. `fromTransport` does not know what\n * a WebSocket is — it knows `send`, `onMessage`, `onClose`.\n */\nexport interface Transport {\n /** Send a string frame. Best-effort; failures are logged at the boundary. */\n send(data: string): void;\n /**\n * Subscribe to inbound frames. Returns a cleanup that removes the listener.\n * The battery wires this in its Sub's subscribe handler; cleanup runs when\n * the phase exits (the substrate's reconcileSubs).\n */\n onMessage(listener: (data: string) => void): () => void;\n /**\n * Subscribe to a one-shot close. Returns a cleanup that removes the\n * listener. The battery dispatches `lostMsg` on close.\n */\n onClose(listener: () => void): () => void;\n /** Close the underlying channel cleanly. Called by the Sub cleanup. */\n close(): void;\n}\n\n/**\n * Factory the consumer wires to a platform-specific transport. The battery\n * passes the runtime-derived key (`runId`, or whatever identity is on the\n * phase) so per-run transports can be addressed.\n */\nexport type TransportFactory<K> = (key: K) => Transport;\n\n/**\n * The Sub the battery builds. The consumer's `Sub` union includes\n * `TransportSub<TKey>` directly — no wrapping needed, the substrate\n * reconciles by `id` and `type` like any other Sub.\n */\nexport interface TransportSub<TKey> extends Sub<\"transport\"> {\n /** The phase / run identity. Stable for the lifetime of the seam. */\n readonly key: TKey;\n}\n\nexport interface FromTransportOpts<TKey, Inbound, Outbound, M, Ctx> {\n /**\n * A short, stable name for this seam. The Sub id is derived from\n * `${name}:${stringify(key)}` so two seams in the same machine (e.g.\n * `hands` + `worker`) never collide.\n */\n readonly name: string;\n\n /**\n * Build the transport. The battery owns the lifetime; the factory is the\n * platform-specific constructor (`new WebSocket(...)` adapter on a DO,\n * `MessagePort` adapter in a worker, in-memory stub in tests).\n */\n readonly openTransport: (key: TKey, ctx: Ctx) => Transport;\n\n /**\n * Parse one inbound frame. The battery hands `unknown` through (boundary\n * parse is the consumer's responsibility per invariant 8). Returning\n * `null` drops the frame silently — useful for frames the seam doesn't\n * recognize at this layer (e.g. transport-level keepalive).\n */\n readonly parseInbound: (raw: string, key: TKey) => Inbound | null;\n\n /**\n * Map a parsed inbound to a domain Msg. Returning `null` drops the\n * dispatch (e.g. a transport-level pong that doesn't change domain state).\n */\n readonly onInbound: (inbound: Inbound, key: TKey) => M | null;\n\n /**\n * The Msg dispatched when the transport closes (peer gone, network\n * dropped, normal close). ONE Msg arm, not a Sub, not a heartbeat-derived\n * deadline — the transport's own close signal is the truth.\n */\n readonly lostMsg: (key: TKey) => M;\n\n /**\n * Serialize an outbound. Called by the Cmd handler the battery wires.\n * Pure data → string at the seam boundary.\n */\n readonly serializeOutbound: (outbound: Outbound) => string;\n}\n\n/**\n * What the battery returns: a Sub builder (for `subscriptions`), the Sub's\n * `subscribe` handler (for `subscribe.transport`), and a `send(key, outbound)`\n * helper the consumer's Cmd handler calls. Plus a `subIdFor` factory the\n * consumer can use when keying related Subs (e.g. a deadline) to the same seam\n * identity.\n */\nexport interface TransportBattery<TKey, Outbound, M, Ctx> {\n /**\n * Build the Sub for `subscriptions(state)`. The consumer calls this from\n * inside their `subscriptions` cell when the seam should be open.\n */\n readonly sub: (key: TKey) => TransportSub<TKey>;\n\n /**\n * The `subscribe.transport` handler. Drop straight into the machine's\n * `subscribe` record — the battery wires inbound + close in one shot.\n */\n readonly subscribe: (\n sub: TransportSub<TKey>,\n ctx: Ctx,\n dispatch: (msg: M) => void,\n ) => () => void;\n\n /**\n * Outbound send. The Cmd handler calls this; the battery's handle table\n * (built when subscribe ran) routes to the live transport. If the seam\n * is closed (no active sub), the send is dropped honestly (logged, not\n * thrown — the reducer already moved past caring, same shape as Rule 2\n * fire-and-forget Cmds).\n */\n readonly send: (key: TKey, outbound: Outbound) => void;\n\n /** Pre-derived subId for `(key)` — handy for deadline Subs keyed to the same seam. */\n readonly subIdFor: (key: TKey) => SubId;\n\n /**\n * Pair this seam with a state-gate, producing a dep-keyed Sub for the\n * machine's `subs` array. `when(state)` returns the seam's KEY when it\n * should be open, or `null` when it should be torn down. The kernel derives\n * the id from `structuralHash(key)` and reconciles the seam's lifetime — so\n * the author neither lists this Sub in a central `subscriptions(state)` nor\n * writes its id. Replaces `subscriptions: (s) => active(s) ? [hands.sub(...)] : []`\n * + `subscribe: { transport: hands.subscribe }` with one `subs` entry.\n *\n * The `source` closes over the battery's own `subscribe` (open inbound +\n * close, wire the handle table) using the same `ctx` the kernel passes every\n * Sub source.\n */\n readonly depKeyed: <S>(\n when: (state: S) => TKey | null,\n ) => DepKeyedSub<S, M, Ctx>;\n}\n\n/**\n * Build the battery. One call per seam at the machine's host file.\n *\n * @example\n * const handsSeam = fromTransport<RunId, HandsInbound, HandsOutbound, Msg, Ctx>({\n * name: \"hands\",\n * openTransport: (runId, ctx) => ctx.openHandsWs(runId),\n * parseInbound: (raw) => parseHandsInbound(JSON.parse(raw)),\n * onInbound: (inbound, runId) => ({ type: \"hands_said\", runId, inbound }),\n * lostMsg: (runId) => ({ type: \"hands_lost\", runId }),\n * serializeOutbound: (out) => JSON.stringify(out),\n * });\n *\n * // In the machine:\n * subscriptions: (state) => state.type === \"auditing\" ? [handsSeam.sub(state.runId)] : [],\n * subscribe: { transport: handsSeam.subscribe },\n *\n * // In interpret:\n * send_hands: async (cmd) => { handsSeam.send(cmd.runId, cmd.outbound); },\n */\nexport function fromTransport<TKey, Inbound, Outbound, M, Ctx>(\n opts: FromTransportOpts<TKey, Inbound, Outbound, M, Ctx>,\n): TransportBattery<TKey, Outbound, M, Ctx> {\n // Handle table — one live transport per key. Lives in this closure for the\n // lifetime of the battery (i.e. the machine's lifetime). Interpret-local;\n // never crosses into reducer state. The same shape as a brain's\n // `pending: Map<callId, deferred>`: imperative handles in a Map keyed on\n // run-identity, not on durable state.\n const live = new Map<string, Transport>();\n\n // Single-sourced on the kernel's `structuralHash`: `\"1\"` (string) and `1`\n // (number) render distinctly, so a key collision can't mis-route an outbound\n // send or overwrite the wrong live transport; non-JSON keys throw loudly.\n const keyString = (k: TKey): string => structuralHash(k);\n\n const subIdFor = (key: TKey): SubId =>\n subId(`${opts.name}:${keyString(key)}`);\n\n const sub = (key: TKey): TransportSub<TKey> => ({\n id: subIdFor(key),\n type: \"transport\",\n key,\n });\n\n const subscribe: TransportBattery<TKey, Outbound, M, Ctx>[\"subscribe\"] = (\n s,\n ctx,\n dispatch,\n ) => {\n // Open the transport. The battery owns the lifetime.\n const t = opts.openTransport(s.key, ctx);\n const k = keyString(s.key);\n live.set(k, t);\n\n // Wire inbound. Parse boundary lives here per invariant 8.\n const offMessage = t.onMessage((raw) => {\n const parsed = opts.parseInbound(raw, s.key);\n if (parsed === null) return;\n const msg = opts.onInbound(parsed, s.key);\n if (msg === null) return;\n dispatch(msg);\n });\n\n // Wire close. ONE Msg arm — `*_lost`. No heartbeat, no sequence\n // numbers, no \"is the peer still alive\" derivation: the transport's\n // own close IS the liveness signal.\n const offClose = t.onClose(() => {\n dispatch(opts.lostMsg(s.key));\n });\n\n // Cleanup runs when the substrate's reconcileSubs leaves the phase.\n return () => {\n offMessage();\n offClose();\n live.delete(k);\n try {\n t.close();\n } catch (err) {\n // Boundary log: cleanup-time close failures are fire-and-forget\n // (Rule 2). The reducer is already past caring.\n console.warn(`[fromTransport:${opts.name}] close failed`, err);\n }\n };\n };\n\n return {\n sub,\n subscribe,\n\n send: (key, outbound) => {\n const t = live.get(keyString(key));\n if (t === undefined) {\n // Honest drop. The reducer's Cmd burst can race a `*_lost`\n // transition; the Cmd is fire-and-forget at that point.\n console.warn(\n `[fromTransport:${opts.name}] send dropped — no live transport for key`,\n keyString(key),\n );\n return;\n }\n t.send(opts.serializeOutbound(outbound));\n },\n\n subIdFor,\n\n // Dep-keyed entry: `when` is the gate (the deps slice → the seam key);\n // `source` re-runs `when(state)` to recover the typed key and opens the seam\n // via the battery's own `subscribe`. The kernel only calls `source` when\n // `deps(state)` was non-null, so `when` is non-null here; the key type never\n // escapes the entry (cast-free existential).\n depKeyed: <S>(when: (state: S) => TKey | null): DepKeyedSub<S, M, Ctx> => ({\n deps: when,\n source: (state, dispatch, ctx) => {\n const key = when(state);\n if (key === null) return () => {};\n return subscribe(sub(key), ctx, dispatch);\n },\n }),\n };\n}\n"]}
@@ -1,4 +1,4 @@
1
- import { patchItemOp, markDoneOp, claimNextOp, enqueueOp } from './chunk-TGG26XMK.js';
1
+ import { patchItemOp, markDoneOp, claimNextOp, enqueueOp } from './chunk-FV3Z5MBE.js';
2
2
 
3
3
  // src/work-queue/adapter.ts
4
4
  function queueAdapter() {
@@ -11,5 +11,5 @@ function queueAdapter() {
11
11
  }
12
12
 
13
13
  export { queueAdapter };
14
- //# sourceMappingURL=chunk-HJO6VNQI.js.map
15
- //# sourceMappingURL=chunk-HJO6VNQI.js.map
14
+ //# sourceMappingURL=chunk-MU6MCIAC.js.map
15
+ //# sourceMappingURL=chunk-MU6MCIAC.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/work-queue/adapter.ts"],"names":[],"mappings":";;;AAqFO,SAAS,YAAA,GAAmC;AACjD,EAAA,OAAO;AAAA,IACL,OAAA,EAAS,CAAC,KAAA,EAAO,KAAA,EAAO,GAAA,EAAK,OAAO,SAAA,CAAa,KAAA,EAAO,KAAA,EAAO,GAAA,EAAK,EAAE,CAAA;AAAA,IACtE,OAAO,CAAC,KAAA,EAAO,GAAA,KAAQ,WAAA,CAAe,OAAO,GAAG,CAAA;AAAA,IAChD,UAAU,CAAC,KAAA,EAAO,EAAA,KAAO,UAAA,CAAc,OAAO,EAAE,CAAA;AAAA,IAChD,KAAA,EAAO,CAAC,KAAA,EAAO,EAAA,EAAI,UAAU,WAAA,CAAe,KAAA,EAAO,IAAI,KAAK;AAAA,GAC9D;AACF","file":"chunk-HJO6VNQI.js","sourcesContent":["/**\n * @demlik/tea/work-queue/adapter — the verb seam over the pure queue ops.\n *\n * The pure ops in `./ops` are the BLESSED transitions, but their names and\n * argument tuples (`enqueueOp(queue, input, now, id)`, `claimNextOp(queue,\n * now)`, …) and their `{ next, item }` / `{ next, changed }` result shapes are\n * the STORE's vocabulary, not the consumer's. An L2 composition that owns a\n * `QueueItem<I>[]` slice (`monitored-run`, `saga`, `idempotent-intake`,\n * `fan-out`) used to import those ops by name and call them directly, so a\n * change to the op surface — or to `QueueItem`'s fields — cascaded to every\n * call site.\n *\n * `QueueAdapter<I>` is the insulating interface: four VERBS (`enqueue` /\n * `claim` / `markDone` / `patch`) that consumers depend on instead of the raw\n * ops. The verbs keep the ops' pure `(queue, …) => { next, … }` contract — they\n * are pure array transforms, no I/O, no `Date.now` (the clock is injected as\n * `now`, exactly as the ops require it) — so a consumer folding the adapter\n * through its reducer stays inside invariant 2 (pure transitions).\n *\n * `queueAdapter<I>()` is the canonical implementation, a thin binding of the\n * blessed ops. Because the verbs are the only surface consumers touch, a\n * `QueueItem` shape change (a new field, a renamed status, a different op\n * signature) is now absorbed HERE — re-bind the verb once — rather than at the\n * 4 consumer call sites. The `patch` verb deliberately takes the same\n * `(item) => item` closure `patchItemOp` does, so the one place a `QueueItem`\n * literal is spread stays the consumer's patch closure, never a re-rolled\n * record.\n *\n * This is an INTERNAL seam (no public-API port): consumers import the adapter\n * from this subpath the same way they imported the ops. The ops remain\n * exported for the in-process `createQueue` adapter (`./index`) and for any\n * call site that genuinely wants the op directly.\n */\n\nimport type { QueueItem } from \"./index\";\nimport { claimNextOp, enqueueOp, markDoneOp, patchItemOp } from \"./ops\";\n\n/**\n * The verb interface over a `QueueItem<I>[]` slice. Each verb mirrors a blessed\n * op's pure contract but speaks the consumer's lifecycle vocabulary, so the\n * consumer never names a `*Op` function or reaches into a `QueueItem` field it\n * did not put there.\n */\nexport interface QueueAdapter<I> {\n /** Append a new `pending` item stamped `now` under `id`. */\n enqueue(\n queue: readonly QueueItem<I>[],\n input: I,\n now: number,\n id: string,\n ): { next: QueueItem<I>[]; item: QueueItem<I> };\n\n /**\n * Claim the first `pending` item — flip it `running`, stamp `startedAt` to\n * `now` — or `null` if nothing is pending.\n */\n claim(\n queue: readonly QueueItem<I>[],\n now: number,\n ): { next: QueueItem<I>[]; claimed: QueueItem<I> } | null;\n\n /** Drop the item `id` from the queue (completed runs leave the queue). */\n markDone(\n queue: readonly QueueItem<I>[],\n id: string,\n ): { next: QueueItem<I>[]; changed: boolean };\n\n /**\n * Single-item transition: replace the item `id` with `patch(item)`. The\n * closure is the only place a `QueueItem` literal is constructed, so the\n * field shape stays out of the consumer's hands except through this verb.\n */\n patch(\n queue: readonly QueueItem<I>[],\n id: string,\n patch: (item: QueueItem<I>) => QueueItem<I>,\n ): { next: QueueItem<I>[]; changed: boolean };\n}\n\n/**\n * The canonical `QueueAdapter<I>`, binding each verb to its blessed op. Pure\n * and stateless — call once at module scope in a consumer and fold its verbs\n * through the reducer. A `QueueItem` shape change is absorbed by re-binding a\n * verb here, not at the consumers.\n */\nexport function queueAdapter<I>(): QueueAdapter<I> {\n return {\n enqueue: (queue, input, now, id) => enqueueOp<I>(queue, input, now, id),\n claim: (queue, now) => claimNextOp<I>(queue, now),\n markDone: (queue, id) => markDoneOp<I>(queue, id),\n patch: (queue, id, patch) => patchItemOp<I>(queue, id, patch),\n };\n}\n"]}
1
+ {"version":3,"sources":["../src/work-queue/adapter.ts"],"names":[],"mappings":";;;AAqFO,SAAS,YAAA,GAAmC;AACjD,EAAA,OAAO;AAAA,IACL,OAAA,EAAS,CAAC,KAAA,EAAO,KAAA,EAAO,GAAA,EAAK,OAAO,SAAA,CAAa,KAAA,EAAO,KAAA,EAAO,GAAA,EAAK,EAAE,CAAA;AAAA,IACtE,OAAO,CAAC,KAAA,EAAO,GAAA,KAAQ,WAAA,CAAe,OAAO,GAAG,CAAA;AAAA,IAChD,UAAU,CAAC,KAAA,EAAO,EAAA,KAAO,UAAA,CAAc,OAAO,EAAE,CAAA;AAAA,IAChD,KAAA,EAAO,CAAC,KAAA,EAAO,EAAA,EAAI,UAAU,WAAA,CAAe,KAAA,EAAO,IAAI,KAAK;AAAA,GAC9D;AACF","file":"chunk-MU6MCIAC.js","sourcesContent":["/**\n * @demlik/tea/work-queue/adapter — the verb seam over the pure queue ops.\n *\n * The pure ops in `./ops` are the BLESSED transitions, but their names and\n * argument tuples (`enqueueOp(queue, input, now, id)`, `claimNextOp(queue,\n * now)`, …) and their `{ next, item }` / `{ next, changed }` result shapes are\n * the STORE's vocabulary, not the consumer's. An L2 composition that owns a\n * `QueueItem<I>[]` slice (`monitored-run`, `saga`, `idempotent-intake`,\n * `fan-out`) used to import those ops by name and call them directly, so a\n * change to the op surface — or to `QueueItem`'s fields — cascaded to every\n * call site.\n *\n * `QueueAdapter<I>` is the insulating interface: four VERBS (`enqueue` /\n * `claim` / `markDone` / `patch`) that consumers depend on instead of the raw\n * ops. The verbs keep the ops' pure `(queue, …) => { next, … }` contract — they\n * are pure array transforms, no I/O, no `Date.now` (the clock is injected as\n * `now`, exactly as the ops require it) — so a consumer folding the adapter\n * through its reducer stays inside invariant 2 (pure transitions).\n *\n * `queueAdapter<I>()` is the canonical implementation, a thin binding of the\n * blessed ops. Because the verbs are the only surface consumers touch, a\n * `QueueItem` shape change (a new field, a renamed status, a different op\n * signature) is now absorbed HERE — re-bind the verb once — rather than at the\n * 4 consumer call sites. The `patch` verb deliberately takes the same\n * `(item) => item` closure `patchItemOp` does, so the one place a `QueueItem`\n * literal is spread stays the consumer's patch closure, never a re-rolled\n * record.\n *\n * This is an INTERNAL seam (no public-API port): consumers import the adapter\n * from this subpath the same way they imported the ops. The ops remain\n * exported for the in-process `createQueue` adapter (`./index`) and for any\n * call site that genuinely wants the op directly.\n */\n\nimport type { QueueItem } from \"./index\";\nimport { claimNextOp, enqueueOp, markDoneOp, patchItemOp } from \"./ops\";\n\n/**\n * The verb interface over a `QueueItem<I>[]` slice. Each verb mirrors a blessed\n * op's pure contract but speaks the consumer's lifecycle vocabulary, so the\n * consumer never names a `*Op` function or reaches into a `QueueItem` field it\n * did not put there.\n */\nexport interface QueueAdapter<I> {\n /** Append a new `pending` item stamped `now` under `id`. */\n enqueue(\n queue: readonly QueueItem<I>[],\n input: I,\n now: number,\n id: string,\n ): { next: QueueItem<I>[]; item: QueueItem<I> };\n\n /**\n * Claim the first `pending` item — flip it `running`, stamp `startedAt` to\n * `now` — or `null` if nothing is pending.\n */\n claim(\n queue: readonly QueueItem<I>[],\n now: number,\n ): { next: QueueItem<I>[]; claimed: QueueItem<I> } | null;\n\n /** Drop the item `id` from the queue (completed runs leave the queue). */\n markDone(\n queue: readonly QueueItem<I>[],\n id: string,\n ): { next: QueueItem<I>[]; changed: boolean };\n\n /**\n * Single-item transition: replace the item `id` with `patch(item)`. The\n * closure is the only place a `QueueItem` literal is constructed, so the\n * field shape stays out of the consumer's hands except through this verb.\n */\n patch(\n queue: readonly QueueItem<I>[],\n id: string,\n patch: (item: QueueItem<I>) => QueueItem<I>,\n ): { next: QueueItem<I>[]; changed: boolean };\n}\n\n/**\n * The canonical `QueueAdapter<I>`, binding each verb to its blessed op. Pure\n * and stateless — call once at module scope in a consumer and fold its verbs\n * through the reducer. A `QueueItem` shape change is absorbed by re-binding a\n * verb here, not at the consumers.\n */\nexport function queueAdapter<I>(): QueueAdapter<I> {\n return {\n enqueue: (queue, input, now, id) => enqueueOp<I>(queue, input, now, id),\n claim: (queue, now) => claimNextOp<I>(queue, now),\n markDone: (queue, id) => markDoneOp<I>(queue, id),\n patch: (queue, id, patch) => patchItemOp<I>(queue, id, patch),\n };\n}\n"]}
@@ -154,5 +154,5 @@ function traceToJSONL(trace) {
154
154
  }
155
155
 
156
156
  export { breadcrumbsFromTrace, parseJSONL, recorder, traceAttachment };
157
- //# sourceMappingURL=chunk-UYWUVEP6.js.map
158
- //# sourceMappingURL=chunk-UYWUVEP6.js.map
157
+ //# sourceMappingURL=chunk-MUUROGIH.js.map
158
+ //# sourceMappingURL=chunk-MUUROGIH.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/recorder/index.ts"],"names":[],"mappings":"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","file":"chunk-MUUROGIH.js","sourcesContent":["// ---------------------------------------------------------------------------\n// recorder — capture a Runtime's Msg sequence for later replay.\n//\n// `historyTracker` (in ../index) is the exemplar: a BOUNDED, in-memory ring\n// buffer over `runtime.observe`. The recorder is its UNBOUNDED, SERIALIZABLE\n// cousin. Where historyTracker exists to answer \"what were the last N\n// transitions?\", the recorder exists to answer \"reproduce this entire run\n// locally against the current reducer.\"\n//\n// The killer feature this enables: record a prod Msg sequence, dump it as\n// JSONL, ship the file home, and feed it to `../trace-replay`'s `replayTrace`\n// — which folds the SAME msgs through `replay(machine, ...)` and diffs the\n// resulting state against the recorded final state. If they diverge, the\n// reducer changed behavior for that input. Regression-as-a-trace.\n//\n// What `replay` needs to reproduce a run (see `replay` in ../index):\n// - `loaded` — the value `init` was booted with. Captured at the boot\n// observe, where `msg === null` and `state` is the post-init\n// initial state. (We capture the post-init state, not the raw\n// storage value, because replay re-runs `init(loaded, ctx)`\n// itself — and for a faithful re-run we feed it that initial\n// state as `loaded` so `init` returns `[loaded, []]`. This is\n// the rehydrate path, which the substrate guarantees is a pure\n// passthrough; see Invariant 2.)\n// - `msgs` — every non-null msg, in dispatch order.\n// - `ctx` — NOT captured here. Ctx is host wiring (sibling runtimes,\n// clients), supplied fresh at replay time. The trace is the\n// INPUT (loaded + msgs); ctx is the ENVIRONMENT.\n//\n// This module OWNS the `Trace` type. `../trace-replay` imports it.\n// ---------------------------------------------------------------------------\n\nimport type { BootingRuntime } from \"../index\";\n\n/**\n * A recorded run, sufficient to reproduce it via `../trace-replay`.\n *\n * The recorder OWNS this type; `../trace-replay` imports it. It is the wire\n * contract between the two halves of the record/replay feature.\n *\n * @typeParam S - the machine's State type.\n * @typeParam M - the machine's Msg union.\n */\nexport interface Trace<S, M> {\n /**\n * The initial state captured at the boot observe (the transition where\n * `msg === null`). Fed back to replay as `loaded` so `init(loaded, ctx)`\n * reproduces the exact starting point. `null` only if the recorder attached\n * AFTER boot (it missed the boot observe) — replay then re-runs fresh `init`.\n */\n readonly loaded: S | null;\n /** The ordered, non-null msgs as observed. The replay input sequence. */\n readonly msgs: readonly M[];\n /**\n * The last observed state. The oracle: `replayTrace` deep-compares its\n * recomputed final state against this. Equal ⇒ the reducer still produces\n * the same result for this input.\n */\n readonly finalState: S;\n /**\n * Per-transition `(msg, post-state)` pairs, in dispatch order, for human\n * inspection (e.g. \"show me the state after step 3\"). When present, there\n * is exactly one entry per recorded msg and `state` is the POST-transition\n * state for that msg (so `steps[i].msg === msgs[i]` and `steps.at(-1).state`\n * deep-equals `finalState`). NOT required for replay — replay only needs\n * `loaded` + `msgs` + `finalState`.\n *\n * Present iff the recorder was created with `captureSteps: true`, OR the\n * Trace was re-hydrated from JSONL via {@link parseJSONL} (the JSONL format\n * carries per-step state, so the parser populates `steps` whenever a step\n * line had a non-null state). A trace dumped with `captureSteps: false` has\n * `steps === undefined`.\n */\n readonly steps?: readonly { readonly msg: M; readonly state: S }[];\n}\n\n/** Options for {@link recorder}. */\nexport interface RecorderOptions {\n /**\n * Fraction of msgs to record, in `(0, 1]`. Default `1` (record everything).\n *\n * IMPORTANT — sampling breaks replay fidelity. A sampled trace DROPS msgs,\n * so its `msgs` array is no longer the complete input sequence and\n * `replayTrace` will NOT reproduce `finalState`. Sampling exists for\n * VOLUME STATISTICS (how much traffic, msg-type distribution), not for\n * faithful replay. Use the default `1` for any trace you intend to replay.\n *\n * The boot observe (`msg === null`) is ALWAYS recorded regardless of\n * `sampleRate` — `loaded` and `finalState` are anchors, not samples.\n */\n readonly sampleRate?: number;\n /**\n * When `true`, retain a per-transition `(msg, post-state)` pair in\n * {@link Trace.steps} for EVERY recorded transition. Default `false`.\n *\n * Size vs. fidelity — the precise trade-off:\n *\n * - `captureSteps: false` (default) — the MINIMAL replay-faithful trace.\n * The recorder keeps `loaded` (boot state), the ordered `msgs`, and\n * `finalState`. That is exactly what `../trace-replay` needs to refold\n * the run and diff the result. Memory is O(msgs) — one msg ref each, no\n * per-step state snapshot. `dump().steps` is `undefined`; in JSONL,\n * interior step lines carry `state: null` (the post-states were never\n * retained) and only the LAST step line carries `finalState`. A trace\n * dumped this way REPLAYS correctly but cannot answer \"what was the\n * state after step 3?\".\n *\n * - `captureSteps: true` — the FULL-fidelity trace. The recorder also\n * snapshots the post-transition state for every msg, so `dump().steps`\n * holds `{ msg, state }` for every transition (post-state) and\n * `toJSONL()` emits a non-null `state` on every step line. Memory is\n * O(msgs × |state|) — a state ref per transition (refs, not deep\n * copies; TEA states are conventionally immutable, so the runtime's\n * post-transition state object is retained by reference). This is the\n * mode for per-step inspection and for a JSONL file that round-trips\n * `steps` exactly via {@link parseJSONL}.\n *\n * Replay fidelity is identical in both modes — `../trace-replay` reads\n * `loaded` + `msgs` + `finalState` only and never touches `steps`. The\n * choice is purely \"do I also want to inspect intermediate states?\".\n */\n readonly captureSteps?: boolean;\n}\n\n/**\n * A live recorder attached to a Runtime. Mirrors `historyTracker`'s\n * stop/snapshot semantics: `dump()` works at any time (including after\n * `stop()`), `stop()` is idempotent and detaches the observer.\n *\n * @typeParam S - the machine's State type.\n * @typeParam M - the machine's Msg union.\n */\nexport interface Recorder<S, M> {\n /**\n * Snapshot the recording so far as a {@link Trace}. Returns a fresh object\n * with copied arrays on every call (callers may slice/replay without\n * affecting the recorder's buffers). Entry values are references to the\n * originals (TEA states/msgs are conventionally immutable).\n *\n * Throws only if called before the boot observe has fired AND no msgs have\n * been recorded — i.e. there is no `finalState` yet. In practice callers\n * `await runtime.ready` before `dump()`, after which boot has fired.\n */\n dump(): Trace<S, M>;\n /**\n * Serialize the recording as JSONL (one JSON object per line) — a streamable\n * format that appends cleanly and survives truncation (a partial tail line\n * is the only loss). The first line is the boot header\n * `{ kind: \"boot\", state }`; each subsequent line is\n * `{ kind: \"step\", msg, state }` for one recorded transition.\n *\n * Re-hydrate with {@link parseJSONL}.\n *\n * Round-trip fidelity depends on `captureSteps`:\n * - `captureSteps: true` — every step line carries its non-null\n * post-state, so `parseJSONL(rec.toJSONL())` reconstructs `steps`\n * EXACTLY and deep-equals `rec.dump()` (which also has `steps`). Full\n * per-step fidelity round-trips.\n * - `captureSteps: false` (default) — interior step lines carry\n * `state: null` (those post-states were never retained); only the last\n * step line carries `finalState`. `parseJSONL` then populates `steps`\n * with just the entries that had a non-null state (the final one), so\n * the re-hydrated Trace is NOT deep-equal to `rec.dump()` (which has no\n * `steps`). It is still fully replay-faithful: `loaded`, `msgs`, and\n * `finalState` are intact. Record with `captureSteps: true` if you need\n * interior states to survive the round-trip.\n */\n toJSONL(): string;\n /**\n * Detach the underlying observer. After `stop()`:\n * - no new transitions are recorded;\n * - `dump()` / `toJSONL()` keep working against the buffered contents;\n * - the recorder holds no observer reference into the runtime.\n *\n * Idempotent — subsequent calls are no-ops.\n */\n stop(): void;\n}\n\n/**\n * Attach a recorder to a Runtime. Subscribes via `runtime.observe`, capturing\n * the boot state as `loaded` and every subsequent non-null msg in order.\n *\n * One-line creation, mirroring `historyTracker`:\n *\n * const rec = recorder(runtime);\n * // ... drive the runtime ...\n * await runtime.ready; // boot observe has fired\n * const trace = rec.dump(); // { loaded, msgs, finalState }\n *\n * @param runtime - any `BootingRuntime<S, M>` (a full `Runtime` satisfies it).\n * The recorder uses only `observe`, which is total before boot\n * — so you attach it to the synchronous `run()` handle to\n * capture the boot transition as `loaded`.\n * @param opts - see {@link RecorderOptions}. Default records everything,\n * no step retention.\n */\nexport function recorder<S, M extends { type: string }>(\n runtime: BootingRuntime<S, M>,\n opts?: RecorderOptions,\n): Recorder<S, M> {\n // Clamp sampleRate into (0, 1]. A non-positive or NaN rate would record\n // nothing (and break replay anchors); default it to 1 (record all). Rates\n // above 1 are meaningless — clamp to 1.\n const rawRate = opts?.sampleRate;\n const sampleRate =\n rawRate === undefined || Number.isNaN(rawRate) || rawRate <= 0\n ? 1\n : Math.min(rawRate, 1);\n const captureSteps = opts?.captureSteps ?? false;\n\n // `loaded` is the post-init state captured at the boot observe. `undefined`\n // sentinel = boot not yet seen; resolved to `S | null` in dump() (null when\n // the recorder attached after boot and never observed msg === null).\n let loaded: S | undefined;\n let sawBoot = false;\n // The ordered, recorded msgs. Unbounded (the recorder's whole point vs the\n // bounded historyTracker).\n const msgs: M[] = [];\n // Per-step (msg, state) pairs — only populated when captureSteps is on.\n const steps: { msg: M; state: S }[] = [];\n // The last observed state — boot state until the first step, then advances\n // with every recorded OR dropped transition (finalState must reflect the\n // true latest state even under sampling, so it stays a valid replay oracle\n // for the unsampled case and an honest \"where the run ended\" marker).\n let finalState: S | undefined;\n let stopped = false;\n\n // The boot transition — the post-init initial state the old `observe(msg ===\n // null)` arm captured as `loaded` — now arrives via `onBoot` (#47). Always\n // captured (boot is the replay anchor). It is also the first `finalState`\n // until the first applied transition advances it.\n const unboot = runtime.onBoot((state) => {\n finalState = state;\n loaded = state;\n sawBoot = true;\n });\n\n const unobserve = runtime.observe((msg, state) => {\n // finalState tracks the latest state regardless of sampling — it is the\n // run's endpoint, an anchor, not a sample.\n finalState = state;\n\n // Sampling decision. sampleRate === 1 short-circuits to always-record so\n // the common (faithful) path pays no RNG cost. Math.random() < rate keeps\n // a `rate` fraction in expectation.\n if (sampleRate < 1 && Math.random() >= sampleRate) return;\n\n msgs.push(msg);\n if (captureSteps) steps.push({ msg, state });\n });\n\n return {\n dump(): Trace<S, M> {\n if (finalState === undefined) {\n throw new Error(\n \"@demlik/tea/recorder: dump() called before any transition was observed. \" +\n \"Await `runtime.ready` (boot fires the first observe) before dumping.\",\n );\n }\n return {\n // sawBoot ⇒ `loaded` is the captured boot state; otherwise the\n // recorder attached post-boot and replay must re-run fresh init.\n loaded: sawBoot ? (loaded as S) : null,\n msgs: msgs.slice(),\n finalState,\n // Only attach `steps` when captureSteps was requested — keeps the\n // dumped Trace shape honest about what was retained.\n ...(captureSteps ? { steps: steps.slice() } : {}),\n };\n },\n toJSONL(): string {\n if (finalState === undefined) {\n throw new Error(\n \"@demlik/tea/recorder: toJSONL() called before any transition was observed. \" +\n \"Await `runtime.ready` (boot fires the first observe) before serializing.\",\n );\n }\n // Serialize the live snapshot via the shared Trace→JSONL writer so the\n // recorder and the standalone `traceAttachment` path emit byte-identical\n // wire format. `dump()` resolves the captureSteps logic (steps present\n // iff captureSteps was on); `traceToJSONL` then emits a non-null state\n // on every step line when steps exist, or `null` interior + finalState\n // on the last line when they don't. The replay path never reads step\n // states, so the captureSteps:false null-interiors never affect replay.\n return traceToJSONL(this.dump());\n },\n stop(): void {\n if (stopped) return;\n stopped = true;\n unobserve();\n unboot();\n },\n };\n}\n\n/** One line of the JSONL format produced by {@link Recorder.toJSONL}. */\ntype JsonlLine<S, M> =\n | { readonly kind: \"boot\"; readonly state: S | null }\n | { readonly kind: \"step\"; readonly msg: M; readonly state: S | null };\n\n/**\n * Re-hydrate a {@link Trace} from the JSONL produced by\n * {@link Recorder.toJSONL}. The inverse of `toJSONL`, so\n * `parseJSONL(rec.toJSONL())` deep-equals `rec.dump()`.\n *\n * Parsing rules:\n * - The first non-blank line MUST be the boot header `{ kind: \"boot\", state }`.\n * Its `state` becomes `Trace.loaded`.\n * - Each `{ kind: \"step\", msg, state }` line contributes `msg` to\n * `Trace.msgs` and `(msg, state)` to `Trace.steps`.\n * - `Trace.finalState` is the `state` of the LAST step line, or the boot\n * `state` when there are no steps (a boot-only trace).\n *\n * Blank lines are skipped (tolerant of trailing newlines / hand-edited files).\n * A malformed line, a missing/duplicate boot header, or a step with `null`\n * final state throws — the trace is unusable for replay and silent corruption\n * is worse than a loud parse error (errors are data; surface them).\n *\n * @typeParam S - the machine's State type.\n * @typeParam M - the machine's Msg union.\n */\nexport function parseJSONL<S, M>(jsonl: string): Trace<S, M> {\n const rawLines = jsonl.split(\"\\n\");\n let loaded: S | null = null;\n let sawBoot = false;\n const msgs: M[] = [];\n const steps: { msg: M; state: S }[] = [];\n let finalState: S | null = null;\n let finalKnown = false;\n\n for (const [i, rawLine] of rawLines.entries()) {\n const line = rawLine.trim();\n if (line === \"\") continue; // tolerate blank / trailing-newline lines\n\n let parsed: JsonlLine<S, M>;\n try {\n parsed = JSON.parse(line) as JsonlLine<S, M>;\n } catch (err) {\n throw new Error(\n `@demlik/tea/recorder: parseJSONL failed on line ${i + 1}: not valid JSON. ${String(err)}`,\n );\n }\n\n if (parsed.kind === \"boot\") {\n if (sawBoot) {\n throw new Error(\n `@demlik/tea/recorder: parseJSONL saw a second boot header on line ${i + 1}. ` +\n \"A trace has exactly one boot line.\",\n );\n }\n sawBoot = true;\n loaded = parsed.state;\n // The boot state is the finalState until a step supersedes it (boot-only\n // trace case).\n finalState = parsed.state;\n finalKnown = parsed.state !== null;\n } else if (parsed.kind === \"step\") {\n if (!sawBoot) {\n throw new Error(\n `@demlik/tea/recorder: parseJSONL saw a step line before the boot header on line ${i + 1}.`,\n );\n }\n msgs.push(parsed.msg);\n if (parsed.state !== null) {\n steps.push({ msg: parsed.msg, state: parsed.state });\n finalState = parsed.state;\n finalKnown = true;\n }\n } else {\n throw new Error(\n `@demlik/tea/recorder: parseJSONL saw an unknown line kind on line ${i + 1}: ` +\n JSON.stringify((parsed as { kind?: unknown }).kind),\n );\n }\n }\n\n if (!sawBoot) {\n throw new Error(\n \"@demlik/tea/recorder: parseJSONL found no boot header. A trace must start with \" +\n '{ kind: \"boot\", state }.',\n );\n }\n if (!finalKnown || finalState === null) {\n throw new Error(\n \"@demlik/tea/recorder: parseJSONL could not determine finalState — the boot state \" +\n \"was null and no step carried a non-null state. The trace is unusable for replay.\",\n );\n }\n\n return { loaded, msgs, finalState, steps };\n}\n\n// ---------------------------------------------------------------------------\n// Sentry crash→trace adapters — pure, zero-dependency, Sentry-SHAPED.\n//\n// The killer DevX flow: a machine crashes in prod → its recorded Trace rides\n// along on the Sentry event → one click later you `parseJSONL` it and replay\n// the exact run locally against the (possibly fixed) reducer. These two\n// adapters are the bridge from a `Trace` to the two Sentry surfaces that carry\n// it: an Attachment (the full replayable trace) and Breadcrumbs (a human-\n// readable timeline of the msgs that led to the crash).\n//\n// They take NO dependency on `@sentry/*`. They emit PLAIN OBJECTS whose shapes\n// match Sentry's public `Attachment` and `Breadcrumb` types, and the consumer\n// wires them at their own Sentry boundary (browser or node). This keeps the\n// recorder runnable anywhere a machine runs (a Worker, a Node script, a test)\n// and lets the consumer pin whatever Sentry SDK version they already ship.\n// ---------------------------------------------------------------------------\n\n/**\n * A Sentry-shaped attachment carrying the full {@link Trace} as JSONL. The\n * shape matches Sentry's `Attachment` type so the consumer can hand it\n * straight to `addAttachment(...)` without any adaptation.\n *\n * - `filename` — `\"tea-trace.jsonl\"`. The name Sentry shows in the UI and\n * uses for download; `.jsonl` signals the streamable format.\n * - `data` — the trace serialized via {@link Recorder.toJSONL}'s format\n * (re-hydrate downstream with {@link parseJSONL}).\n * - `contentType` — `\"application/x-ndjson\"`, the standard MIME for\n * newline-delimited JSON.\n */\nexport interface TraceAttachment {\n readonly filename: string;\n readonly data: string;\n readonly contentType: string;\n}\n\n/**\n * Serialize a {@link Trace} into a Sentry-shaped attachment so a crash event\n * carries the full, replayable run. Pure — no Sentry import, no I/O. The\n * returned object matches Sentry's `Attachment` shape exactly.\n *\n * The `data` field is JSONL in the same format {@link Recorder.toJSONL}\n * produces, so the home-side flow is `parseJSONL(attachment.data)` →\n * `replayTrace(machine, trace, ctx)`.\n *\n * Consumer wiring (no Sentry import here — the consumer owns the boundary):\n *\n * ```ts\n * import * as Sentry from \"@sentry/browser\"; // or \"@sentry/node\"\n * import { traceAttachment } from \"@demlik/tea/recorder\";\n * Sentry.getCurrentScope().addAttachment(traceAttachment(rec.dump()));\n * ```\n *\n * @typeParam S - the machine's State type.\n * @typeParam M - the machine's Msg union.\n */\nexport function traceAttachment<S, M extends { type: string }>(\n trace: Trace<S, M>,\n): TraceAttachment {\n return {\n filename: \"tea-trace.jsonl\",\n data: traceToJSONL(trace),\n contentType: \"application/x-ndjson\",\n };\n}\n\n/** A Sentry-shaped breadcrumb. Matches the fields of Sentry's `Breadcrumb`. */\nexport interface TraceBreadcrumb {\n readonly category: string;\n readonly type: string;\n readonly level: string;\n readonly message: string;\n readonly data?: Record<string, unknown>;\n}\n\n/** Options for {@link breadcrumbsFromTrace}. */\nexport interface BreadcrumbsOptions<M> {\n /**\n * Max breadcrumbs to emit. Default `100` (Sentry's own default breadcrumb\n * cap). When the trace has more msgs than `limit`, the OLDEST are dropped —\n * the returned list is the newest-last tail, matching how a Sentry\n * breadcrumb ring buffer behaves (the crash context is the recent past).\n */\n readonly limit?: number;\n /**\n * Customize each breadcrumb's `message`. Default uses `msg.type`. Provide a\n * serializer to surface a richer one-liner (e.g. include an id). The msg is\n * ALSO always attached verbatim under `data.msg` regardless of this option,\n * so the structured payload is never lost to the string rendering.\n */\n readonly serialize?: (msg: M) => string;\n}\n\n/**\n * Project a {@link Trace}'s recorded msgs into Sentry-shaped breadcrumbs — a\n * human-readable timeline of \"what happened before the crash\". Pure — no\n * Sentry import, no I/O. Each returned object matches Sentry's `Breadcrumb`\n * shape, so the consumer can loop and call `addBreadcrumb(...)`.\n *\n * One breadcrumb per recorded msg, in dispatch order (NEWEST LAST — Sentry\n * renders breadcrumbs oldest-first and the crash is the most recent event).\n * Each breadcrumb:\n * - `category` — `\"tea\"` (groups them in the Sentry breadcrumb panel).\n * - `type` — `\"default\"` (the generic Sentry breadcrumb type).\n * - `level` — `\"info\"` (msgs are normal flow; the crash is the event).\n * - `message` — `msg.type`, or `serialize(msg)` when provided.\n * - `data.msg` — the full msg object (structured detail kept verbatim).\n *\n * Capped at `opts.limit` (default 100). When the trace has more msgs than the\n * cap, the OLDEST are dropped and the newest `limit` are kept — the recent\n * lead-up to the crash is the useful context.\n *\n * Consumer wiring (no Sentry import here — the consumer owns the boundary):\n *\n * ```ts\n * import * as Sentry from \"@sentry/browser\"; // or \"@sentry/node\"\n * import { breadcrumbsFromTrace } from \"@demlik/tea/recorder\";\n * for (const b of breadcrumbsFromTrace(rec.dump())) Sentry.addBreadcrumb(b);\n * ```\n *\n * @typeParam S - the machine's State type.\n * @typeParam M - the machine's Msg union.\n */\nexport function breadcrumbsFromTrace<S, M extends { type: string }>(\n trace: Trace<S, M>,\n opts?: BreadcrumbsOptions<M>,\n): TraceBreadcrumb[] {\n const rawLimit = opts?.limit;\n // Clamp limit to a non-negative integer. `undefined`/NaN → 100 (Sentry's\n // default). A negative or zero limit yields an empty list (the caller asked\n // for no breadcrumbs); fractional values floor (you can't emit half a crumb).\n const limit =\n rawLimit === undefined || Number.isNaN(rawLimit)\n ? 100\n : Math.max(0, Math.floor(rawLimit));\n const serialize = opts?.serialize;\n\n // Keep the NEWEST `limit` msgs (the recent lead-up to the crash). Slicing\n // from `length - limit` (clamped to 0) preserves dispatch order within the\n // kept window. Note we do NOT use `slice(-limit)`: `slice(-0)` is `slice(0)`\n // (JS treats -0 as 0), which would return the WHOLE array for `limit: 0`.\n const start = Math.max(0, trace.msgs.length - limit);\n const kept = start === 0 ? trace.msgs : trace.msgs.slice(start);\n\n const crumbs: TraceBreadcrumb[] = [];\n for (const msg of kept) {\n crumbs.push({\n category: \"tea\",\n type: \"default\",\n level: \"info\",\n message: serialize ? serialize(msg) : msg.type,\n // Full msg kept verbatim so the structured payload survives even when\n // `message` is a terse one-liner. Cast: a Msg is a `{ type: string }`\n // record, which is a `Record<string, unknown>` for Sentry's `data`.\n data: { msg: msg as unknown as Record<string, unknown> },\n });\n }\n return crumbs;\n}\n\n// ---------------------------------------------------------------------------\n// traceToJSONL — serialize a STANDALONE Trace to JSONL (the dump→file path).\n//\n// `Recorder.toJSONL()` serializes the recorder's live buffers. `traceAttachment`\n// works over a plain `Trace` value (a `dump()` snapshot or a `parseJSONL`\n// result), which has no recorder behind it — so it needs a Trace→JSONL\n// function. This is the same wire format `Recorder.toJSONL` emits and\n// `parseJSONL` reads, so `parseJSONL(traceToJSONL(trace))` round-trips.\n//\n// When `trace.steps` is present (captureSteps was on, or the trace came from\n// parseJSONL), every step line carries its post-state. When absent, interior\n// step states are unknown — emitted as `null`, with `finalState` on the LAST\n// step line (identical to the captureSteps:false path in `Recorder.toJSONL`).\n// ---------------------------------------------------------------------------\nfunction traceToJSONL<S, M>(trace: Trace<S, M>): string {\n const lines: string[] = [];\n lines.push(JSON.stringify({ kind: \"boot\", state: trace.loaded }));\n\n const steps = trace.steps;\n for (let i = 0; i < trace.msgs.length; i++) {\n const msg = trace.msgs[i];\n const stepState = steps\n ? steps[i]?.state\n : i === trace.msgs.length - 1\n ? trace.finalState\n : null;\n lines.push(JSON.stringify({ kind: \"step\", msg, state: stepState }));\n }\n return lines.join(\"\\n\");\n}\n"]}
@@ -54,6 +54,10 @@ function assertPureResult(result, msgType) {
54
54
  }
55
55
  }
56
56
  }
57
+ function without(rec, key) {
58
+ const { [key]: _dropped, ...rest } = rec;
59
+ return rest;
60
+ }
57
61
  function detectUpdateForm(update) {
58
62
  const firstKey = Object.keys(update)[0];
59
63
  if (firstKey === void 0) return "reducer";
@@ -72,6 +76,7 @@ var NoCellError = class extends Error {
72
76
  this.stateName = stateName;
73
77
  }
74
78
  name = "NoCellError";
79
+ _tag = "NoCellError";
75
80
  };
76
81
  function stateNameOf(state) {
77
82
  if (typeof state === "object" && state !== null && "type" in state) {
@@ -97,6 +102,12 @@ function applyCell(machine, state, msg) {
97
102
  }
98
103
  return cell(state, msg);
99
104
  }
105
+ function applyCellChecked(machine, state, msg) {
106
+ if (__DEV__) deepFreeze(state);
107
+ const result = applyCell(machine, state, msg);
108
+ if (__DEV__) assertPureResult(result, msg.type);
109
+ return result;
110
+ }
100
111
  function msgKeysOf(machine) {
101
112
  const keys = Object.keys(machine.update);
102
113
  const firstKey = keys[0];
@@ -165,14 +176,37 @@ var Cmd = {
165
176
  function subId(s) {
166
177
  return s;
167
178
  }
179
+ function structuralHash(deps) {
180
+ return stableStringify(deps);
181
+ }
182
+ function stableStringify(value) {
183
+ if (value === null) return "null";
184
+ const t = typeof value;
185
+ if (t === "string") return JSON.stringify(value);
186
+ if (t === "number" || t === "boolean") return String(value);
187
+ if (t === "undefined") return "undefined";
188
+ if (t === "function") {
189
+ throw new Error(
190
+ "@demlik/tea: structuralHash received a function in `deps`. A dep-keyed Sub's `deps` must be a plain state slice (string / number / boolean / null / array / object), not a closure \u2014 see invariant 1."
191
+ );
192
+ }
193
+ if (Array.isArray(value)) {
194
+ return `[${value.map(stableStringify).join(",")}]`;
195
+ }
196
+ if (t === "object") {
197
+ const obj = value;
198
+ const keys = Object.keys(obj).sort();
199
+ return `{${keys.map((k) => `${JSON.stringify(k)}:${stableStringify(obj[k])}`).join(",")}}`;
200
+ }
201
+ throw new Error(
202
+ `@demlik/tea: structuralHash received an unsupported \`deps\` value of type "${t}". Use plain JSON-compatible data for a dep-keyed Sub's \`deps\`.`
203
+ );
204
+ }
168
205
  function foldUpdates(machine, initialState, msgs) {
169
206
  let state = initialState;
170
207
  const cmds = [];
171
208
  for (const msg of msgs) {
172
- if (__DEV__) deepFreeze(state);
173
- const result = applyCell(machine, state, msg);
174
- if (__DEV__) assertPureResult(result, msg.type);
175
- const [next, emitted] = result;
209
+ const [next, emitted] = applyCellChecked(machine, state, msg);
176
210
  state = next;
177
211
  cmds.push(...emitted);
178
212
  }
@@ -182,6 +216,6 @@ function foldMsgs(machine, base, msgs) {
182
216
  return foldUpdates(machine, base, msgs).state;
183
217
  }
184
218
 
185
- export { Cmd, NoCellError, __DEV__, applyCell, assertPureResult, deepFreeze, detectUpdateForm, foldMsgs, foldUpdates, formOf, msgKeysOf, subId };
186
- //# sourceMappingURL=chunk-DYRKDCJI.js.map
187
- //# sourceMappingURL=chunk-DYRKDCJI.js.map
219
+ export { Cmd, NoCellError, applyCell, applyCellChecked, detectUpdateForm, foldMsgs, foldUpdates, formOf, msgKeysOf, structuralHash, subId, without };
220
+ //# sourceMappingURL=chunk-PBDYW2TU.js.map
221
+ //# sourceMappingURL=chunk-PBDYW2TU.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/pure/core.ts"],"names":["cell"],"mappings":"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","file":"chunk-PBDYW2TU.js","sourcesContent":["/**\n * @demlik/tea — pure-core leaf (ADR 0006).\n *\n * This is the runtime-free pure core of the substrate. It holds the\n * client-prediction fold seam (`foldMsgs`), the form reader\n * (`formOf`/`detectUpdateForm`), and the pure type vocabulary (`Machine`,\n * `Reducer`, `Transitions`, `Cmd`, `Sub`, `Port`, …).\n *\n * **Dependency direction (the actual decoupling):** this module imports\n * NOTHING from the runtime — no `better-result`, no `run`/host/`Store`. The\n * runtime (`run`, the host, interpret, `Store`, subscribe — all in\n * `../index.ts`) imports *from* here; never the reverse. The\n * `@demlik/tea/pure` subpath re-exports this surface, and\n * `pure/import-graph.test.ts` is the regression fence asserting the pure\n * entrypoint's import graph never reaches `run`.\n */\n\n// === Dev-mode invariant enforcement ===\n//\n// Three runtime guards that catch TEA invariant violations during development.\n// Dead-code-eliminated in production by any bundler that replaces\n// `import.meta.env.DEV` (Vite) or `process.env.NODE_ENV` (webpack/esbuild).\n//\n// 1. deepFreeze(state) before passing to update — catches mutation\n// 2. Thenable check on return value — catches async update\n// 3. Walk cmd fields for function types — catches closures-as-data\n//\n// These enforce invariants 1 and 2 at runtime where the type system cannot.\nexport const __DEV__: boolean = (() => {\n try {\n const meta = import.meta as unknown as { env?: { DEV?: boolean } };\n if (meta.env?.DEV !== undefined) {\n return meta.env.DEV;\n }\n } catch {\n // import.meta not available (Node CJS, service workers)\n }\n try {\n return (\n typeof process !== \"undefined\" && process.env?.NODE_ENV !== \"production\"\n );\n } catch {\n return false;\n }\n})();\n\nexport function deepFreeze<T>(obj: T, seen?: WeakSet<object>): T {\n if (obj === null || typeof obj !== \"object\") return obj;\n const proto = Object.getPrototypeOf(obj);\n if (proto !== Object.prototype && proto !== Array.prototype && proto !== null)\n return obj;\n const visited = seen ?? new WeakSet();\n if (visited.has(obj as object)) return obj;\n visited.add(obj as object);\n Object.freeze(obj);\n for (const v of Object.values(obj as Record<string, unknown>))\n deepFreeze(v, visited);\n return obj;\n}\n\nexport function hasFunctionValues(obj: unknown): string | null {\n if (obj === null || typeof obj !== \"object\") return null;\n for (const [key, value] of Object.entries(obj as Record<string, unknown>)) {\n if (typeof value === \"function\") return key;\n }\n return null;\n}\n\nexport function assertPureResult(result: unknown, msgType: string): void {\n if (result && typeof (result as { then?: unknown }).then === \"function\") {\n throw new Error(\n `@demlik/tea: update cell \"${msgType}\" returned a Promise. ` +\n `update must be synchronous. Move async work to interpret.`,\n );\n }\n if (\n !Array.isArray(result) ||\n result.length < 2 ||\n !Array.isArray(result[1])\n ) {\n throw new Error(\n `@demlik/tea: update cell \"${msgType}\" returned a non-tuple. ` +\n `Expected [State, Cmd[]], got ${typeof result}.`,\n );\n }\n const cmds = result[1] as readonly { type: string }[];\n for (const cmd of cmds) {\n const fnField = hasFunctionValues(cmd);\n if (fnField !== null) {\n throw new Error(\n `@demlik/tea: Cmd \"${cmd.type}\" has function field \"${fnField}\". ` +\n `Cmds must be plain data, not closures.`,\n );\n }\n }\n}\n\n/**\n * Drop one `key` from a `Record` immutably — returns a new record with `key`\n * omitted, leaving the input untouched. The immutable-record-key-drop the L2\n * resilience bricks reach for whenever a settled/restarted call must forget its\n * per-key bookkeeping (a retry counter, a parked input). A plain\n * object-rest spread, lifted here so `resilient-call` and `authed-call` share\n * one definition instead of open-coding `const { [key]: _drop, ...rest } = rec`\n * at each site.\n */\nexport function without<V>(\n rec: Readonly<Record<string, V>>,\n key: string,\n): Readonly<Record<string, V>> {\n const { [key]: _dropped, ...rest } = rec;\n return rest;\n}\n\n// === Cmd: tagged-union, one-shot effect ===\nexport type Cmd<T extends string = string> = { readonly type: T };\n\n// === SyncReturn<S, C>: the compile-time reentrancy guard (ADR 0003 #5) ===\n//\n// The canon rule (`.patterns/tea-do/reentrancy.md`,\n// `.patterns/tea-do/reentrancy-deadlock.md`): a reducer must never\n// inline-`await` a re-entrant result. \"Non-reentrant by default\" ≡ \"the reducer\n// is pure AND synchronous\": a reducer that returns a `Promise` (because it is\n// `async`, or inline-`await`s) is the suspended-but-still-blocking turn Orleans\n// serializes against — it occupies the single-writer slot across a suspension\n// point and can be re-entered mid-flight → deadlock.\n//\n// So the guard is purely type-level: every reducer/transition cell returns\n// `SyncReturn<S, C>` — the synchronous result tuple, intersected with `{ then?:\n// never }` to make it *non-thenable*. A `Promise<readonly [S, readonly C[]]>`\n// is an object with a `then` method, so it fails to satisfy `then?: never`; an\n// `async (s, m) => [...]` (or one that inline-`await`s) is therefore\n// unrepresentable at compile time — `tsc` rejects it *at the cell*, not at a\n// runtime hang. A plain synchronous tuple `[next, cmds]` has no `then` property,\n// so the optional-absent `then?: never` is satisfied and existing pure reducers\n// compile unchanged. `SyncReturn<S, C>` is also assignable *to* `readonly [S,\n// readonly C[]]` (the intersection is narrower), so the runtime dispatch in\n// `run`/`replay` consumes a cell result exactly as before.\n//\n// This mirrors `retry-backoff`'s `Rng` brand (#63): the obligation lives in the\n// type at the construction boundary (\"parse, don't validate\"), not in a comment.\nexport type SyncReturn<S, C extends Cmd> = readonly [S, readonly C[]] & {\n // A thenable carries a callable `then`; forbidding it rejects every Promise.\n // Optional + `never` means \"absent on a sync tuple, impossible on a Promise\".\n readonly then?: never;\n};\n\n// === update form: Reducer vs Transitions, tagged once ===\n//\n// The substrate accepts two `update` shapes (see the `Machine.update` union and\n// the `defineMachine` overloads):\n//\n// - \"reducer\" — flat record keyed by Msg.type, every cell a function.\n// - \"transitions\" — 2D table keyed by State.type then Msg.type, every cell a\n// record of functions.\n//\n// Which one a given machine is must be known at runtime by `run`, `replay`, and\n// the `withX` wrappers (their dispatch / key-enumeration / reserved-namespace\n// scans differ per form). Rather than re-derive it structurally at every reader\n// — a `typeof update[firstKey] === \"function\"` heuristic that breaks the day a\n// reducer cell is itself an object-with-a-call — `defineMachine` computes the\n// form ONCE at construction and stamps it on the machine as a non-enumerable\n// `__form` (so it never serializes, never collides with a Msg.type key, never\n// shows up in `Object.keys(machine)`). Every reader calls `formOf(machine)`.\nexport type UpdateForm = \"reducer\" | \"transitions\";\n\n// The single structural heuristic, defined ONCE. Used only by `defineMachine`\n// (and `formOf`'s fallback for machines built without it, e.g. a plain object\n// literal annotated as `Machine`). A Reducer's first own value is a function; a\n// Transitions table's first own value is a record (of functions). An empty\n// record (`M` is `never`) can never dispatch a Msg, so the form is irrelevant —\n// \"reducer\" is returned arbitrarily.\nexport function detectUpdateForm(update: object): UpdateForm {\n const firstKey = Object.keys(update)[0];\n if (firstKey === undefined) return \"reducer\";\n const firstValue = (update as Record<string, unknown>)[firstKey];\n return typeof firstValue === \"function\" ? \"reducer\" : \"transitions\";\n}\n\n// The single reader every form-sensitive site goes through. Prefers the\n// `__form` tag stamped by `defineMachine` (authoritative — computed once at the\n// typed construction boundary); falls back to `detectUpdateForm` only for a\n// machine that never passed through `defineMachine`. No reader re-implements the\n// heuristic.\nexport function formOf(machine: {\n update: object;\n __form?: UpdateForm;\n}): UpdateForm {\n return machine.__form ?? detectUpdateForm(machine.update);\n}\n\n// === NoCellError: the named cell-lookup failure (#276) ===\n//\n// An unknown `msg.type` (wire data reaching dispatch) or a type-bypassed\n// missing cell used to surface as a bare `TypeError: ... is not a function`\n// deep inside dispatch — no msg.type, no state name, nothing actionable. The\n// guard lives in `applyCell` because it is the single dispatch primitive\n// every stepping site goes through (#275), so one guard covers `run`,\n// replay/foldMsgs, the PBT fold runner, and the withX wrappers.\nexport class NoCellError extends Error {\n override readonly name = \"NoCellError\";\n readonly _tag = \"NoCellError\" as const;\n constructor(\n public readonly msgType: string,\n public readonly stateName: string,\n ) {\n super(\n `@demlik/tea: no update cell for msg.type \"${msgType}\" in state ` +\n `\"${stateName}\" — the machine's update does not handle this Msg ` +\n `(an unknown wire msg.type, or a missing cell reached by bypassing ` +\n `the mapped types).`,\n );\n }\n}\n\n// Reducer-form State carries no mandatory discriminant; best-effort read of a\n// string `state.type` for the error, else a placeholder.\nfunction stateNameOf(state: unknown): string {\n if (typeof state === \"object\" && state !== null && \"type\" in state) {\n const t = (state as { type: unknown }).type;\n if (typeof t === \"string\") return t;\n }\n return \"(untagged state)\";\n}\n\n// === applyCell: THE single reducer-vs-transitions dispatch primitive ===\n//\n// Applies the one update cell selected by `(formOf(machine), state, msg)` and\n// returns its `[nextState, cmds]` verbatim. Every site that steps a machine —\n// `run`'s applyUpdate, `foldUpdates` (replay/foldMsgs), the PBT fold runner,\n// and the withX wrappers — dispatches through THIS function, so production and\n// the verification tools agree on the update form by construction (#275).\n// Pure and dev-check-free: `deepFreeze`/`assertPureResult` stay at the call\n// sites that want them. A missing cell throws `NoCellError` (#276), never a\n// bare TypeError.\nexport function applyCell<S, M extends { type: string }, C extends Cmd>(\n machine: { update: object; __form?: UpdateForm },\n state: S,\n msg: M,\n): readonly [S, readonly C[]] {\n type CellFn = (state: S, msg: M) => readonly [S, readonly C[]];\n if (formOf(machine) === \"reducer\") {\n const record = machine.update as Record<string, CellFn | undefined>;\n const cell = record[msg.type];\n if (typeof cell !== \"function\") {\n throw new NoCellError(msg.type, stateNameOf(state));\n }\n return cell(state, msg);\n }\n const table = machine.update as Record<\n string,\n Record<string, CellFn | undefined> | undefined\n >;\n // The ONE sanctioned `state as unknown as { type }` read: the Transitions\n // overload constrains S to `{ type: string }` at the `defineMachine`\n // boundary, but that constraint is erased on the runtime-facing `object`\n // here — every former per-site copy of this double-cast collapsed into\n // this line (#275).\n const stateKey = (state as unknown as { type: string }).type;\n const cell = table[stateKey]?.[msg.type];\n if (typeof cell !== \"function\") {\n throw new NoCellError(msg.type, String(stateKey));\n }\n return cell(state, msg);\n}\n\n// === applyCellChecked: `applyCell` wrapped in the DEV pre/post invariant pair ===\n//\n// The dev-mode discipline every fold site shares: `deepFreeze` the input `state`\n// so a reducer that mutates it in place trips synchronously, then\n// `assertPureResult` the returned `[state, cmds]` shape. Both guards compile out\n// of production (`__DEV__`). `run`'s dispatch loop and `foldUpdates` both step\n// through THIS wrapper so the invariant enforcement lives in exactly one place\n// and cannot drift between the runtime and the pure fold.\nexport function applyCellChecked<S, M extends { type: string }, C extends Cmd>(\n machine: { update: object; __form?: UpdateForm },\n state: S,\n msg: M,\n): readonly [S, readonly C[]] {\n if (__DEV__) deepFreeze(state);\n const result = applyCell<S, M, C>(machine, state, msg);\n if (__DEV__) assertPureResult(result, msg.type);\n return result;\n}\n\n// === msgKeysOf: recover the Msg.type set from either update form ===\n//\n// A Reducer's own keys ARE the Msg.type set. A Transitions table's keys are\n// state.type; its INNER keys are the Msg.type set (uniform across phases by\n// the mapped-type contract), so the first phase's inner keys are read. An\n// empty update (`M` is `never`) yields `[]`. Keyed on `formOf` — the withX\n// wrappers and the PBT `msgTypeKeys` all read through this one helper (#275).\nexport function msgKeysOf(machine: {\n update: object;\n __form?: UpdateForm;\n}): readonly string[] {\n const keys = Object.keys(machine.update);\n const firstKey = keys[0];\n if (firstKey === undefined) return [];\n if (formOf(machine) === \"reducer\") return keys;\n const firstValue = (machine.update as Record<string, unknown>)[firstKey];\n return Object.keys(firstValue as object);\n}\n\n// === Reducer<S, M, C>: record-of-handlers form of `update` ===\n//\n// Flat dispatch table keyed by `Msg.type`. Each cell is a pure transition for\n// a single Msg variant, narrowed via `Extract<M, { type: K }>`. The mapped type\n// is *load-bearing*: adding a Msg variant without a matching key in the\n// Reducer record is a compile error — no `absurd()` helper at call sites, no\n// silent fall-through, no default branch hiding impurity.\n//\n// Strengthens invariant 2 (pure transitions — the record form has no\n// fall-through default to hide impurity behind) and invariant 7 (identity is\n// explicit — the Msg variant set is load-bearing at the type level).\n//\n// `defineMachine` accepts the Reducer record form via overload. The runtime\n// dispatches via `update[msg.type](state, msg)`.\nexport type Reducer<S, M extends { type: string }, C extends Cmd> = {\n [K in M[\"type\"]]: (\n state: S,\n msg: Extract<M, { type: K }>,\n // `SyncReturn<S, C>` (not `readonly [S, readonly C[]]`) is the reentrancy\n // guard: a non-thenable return type, so an `async`/inline-`await`ing cell —\n // which returns `Promise<...>` — fails to compile at the cell. See the\n // `SyncReturn` doc above.\n ) => SyncReturn<S, C>;\n};\n\n// === Transitions<S, M, C>: table form of `update` for state-machine-shaped machines ===\n//\n// When `State` is itself a discriminated union (`State.type` is the active\n// phase), the table form makes every (state.type × msg.type) cell explicit at\n// the type level. Missing a cell — e.g. forgetting how `phase: \"settling\"`\n// handles `msg.type: \"tab_stepped\"` — fails to compile. No nested switch, no\n// fall-through, no `_ -> (state, [])` default branch hiding regressions.\n//\n// Each cell receives the narrowed `State` for its phase and the narrowed\n// `Msg` for its variant via two `Extract` lookups. Phantom-narrow at the\n// type level; pure data at runtime.\n//\n// `defineMachine` accepts a third `update` form for `Transitions<S, M, C>` —\n// the runtime dispatches via `update[state.type][msg.type](state, msg)`.\n//\n// Strengthens invariant 2 (table form has no fall-through default),\n// invariant 6 (runtime walks the table predictably, no hidden dispatch\n// fallback), and invariant 7 (both state.type and msg.type are load-bearing\n// at the type level).\nexport type Transitions<\n S extends { type: string },\n M extends { type: string },\n C extends Cmd,\n> = {\n [P in S[\"type\"]]: {\n [K in M[\"type\"]]: (\n state: Extract<S, { type: P }>,\n msg: Extract<M, { type: K }>,\n // Same reentrancy guard as `Reducer`: a transition cell that returns a\n // Promise (async / inline-await) is the suspended-blocking turn the canon\n // forbids, so its return is the non-thenable `SyncReturn<S, C>`.\n ) => SyncReturn<S, C>;\n };\n};\n\n// === Cmd: namespace for conditional Cmd emission ===\n//\n// Two recurring frictions in reducer cells:\n//\n// 1. \"Emit cmd X only when condition Y holds.\"\n// Hand-rolled: `cond ? [{ type: \"x\", ... }] : []`\n// With Cmd: `Cmd.when(cond, { type: \"x\", ... })`\n//\n// 2. \"Emit cmd X carrying a value that may be undefined; if undefined,\n// emit nothing.\"\n// Hand-rolled: `value !== undefined ? [{ type: \"x\", value }] : []`\n// With Cmd: `Cmd.whenDefined(value, (v) => ({ type: \"x\", value: v }))`\n//\n// Both forms return `readonly C[]` so they spread cleanly into the cmds\n// array returned by a Transitions cell:\n//\n// return [next, [\n// ...Cmd.when(state.tabId !== undefined, { type: \"detach_debugger\", tabId: state.tabId }),\n// ...Cmd.whenDefined(state.queueItemId, (id) => ({\n// type: \"queue:complete\", queueItemId: id, status: \"done\",\n// })),\n// ]];\n//\n// `Cmd` as a value namespace coexists with `Cmd<T>` as a type — TypeScript's\n// declaration merging puts them in different name spaces (type vs. value).\n// The reader sees `Cmd<...>` in type position and `Cmd.when(...)` in\n// expression position; no ambiguity at use sites.\n//\n// Why a namespace and not top-level `when` / `whenDefined`: `when` at module\n// scope reads as a generic conditional, but these helpers are specifically\n// for *Cmd emission* (return `readonly C[]`, the cmds-array contract of\n// every Transitions cell). The namespace pins that intent at the call site.\nexport const Cmd = {\n /**\n * The empty Cmd array. Typed `readonly never[]` so it's assignable to any\n * `readonly C[]` for any `C extends Cmd`. Use in `init` returns and\n * Transitions cells that emit zero effects:\n *\n * init: (loaded) => [loaded ?? initial, Cmd.none],\n * tick: (state) => [state, Cmd.none],\n *\n * Elm's `Cmd.none` analogue. Cultural signal alongside the runtime help:\n * `[state, Cmd.none]` reads as intent (\"this transition emits nothing\"),\n * `[state, []]` reads as \"empty array of what\".\n *\n * Frozen at runtime so a downstream consumer can't `.push()` into the\n * shared reference.\n */\n none: Object.freeze([]) as readonly never[],\n\n /**\n * Flat concat of cmd arrays. Use when a cell composes effects from\n * multiple conditional sources:\n *\n * return [next, Cmd.batch(\n * Cmd.whenDefined(state.queueItemId, (id) => ({ type: \"complete\", id })),\n * Cmd.when(state.windowId !== undefined, { type: \"close_window\", windowId }),\n * [{ type: \"detach_debugger\", tabId: state.tabId }],\n * )];\n *\n * `<const C>` keeps inline cmd literals' discriminants narrow (same\n * rationale as `when` / `whenDefined` above).\n */\n batch: <const C extends Cmd>(\n ...arrs: readonly (readonly C[])[]\n ): readonly C[] => {\n const out: C[] = [];\n for (const arr of arrs) out.push(...arr);\n return out;\n },\n\n /**\n * Emit `cmd` wrapped in a single-element array iff `cond` is true.\n * Otherwise return the empty array. Spreads cleanly into the cmds array\n * returned by a Transitions cell.\n *\n * `const C` (TypeScript 5.0+) keeps the inferred `type:` discriminator\n * literal narrow when called with an object literal — so `Cmd.when(b,\n * { type: \"x\", ... })` infers as `{ type: \"x\", ... }`, not `{ type:\n * string, ... }`, and stays assignable to a discriminated-union arm\n * without `as const` at the call site.\n */\n when: <const C extends Cmd>(cond: boolean, cmd: C): readonly C[] =>\n cond ? [cmd] : [],\n\n /**\n * If `value` is defined, call `build(value)` and emit the resulting cmd\n * wrapped in a single-element array. If `value` is `undefined`, return\n * the empty array. `value` is narrowed to `T` inside `build`.\n *\n * `const C` on the return type keeps the callback's object-literal\n * `type:` field narrow (see `when` above for the full rationale).\n */\n whenDefined: <T, const C extends Cmd>(\n value: T | undefined,\n build: (value: T) => C,\n ): readonly C[] => (value !== undefined ? [build(value)] : []),\n} as const;\n\n// === SubId: branded string for Sub identity ===\n//\n// Subs are reconciled by id on every transition (canon §2.5, invariant 7).\n// Before branding, `Sub.id` was a raw `string` — a typo at one call site and a\n// different typo at another would silently produce two distinct subs that\n// looked identical to a human reader. The brand makes id construction explicit\n// (`subId(\"...\")`) and catches accidental string-where-SubId at the type\n// level.\n//\n// The brand is structural: `string & { __brand: \"SubId\" }`. The `subId(s)`\n// constructor is the ONE permitted cast in the substrate — every other call\n// site must go through it.\nexport type SubId = string & { readonly __brand: \"SubId\" };\n\n/**\n * Construct a `SubId` from a string. Use this everywhere a Sub literal is\n * built — including ids derived from runtime data (`subId(\\`ws:${auditId}\\`)`).\n * The brand is structural, not nominal, so dynamic ids are permitted; the\n * constructor's job is to make the identity decision explicit at every call\n * site so the type system can catch accidental raw-string drift.\n *\n * Strengthens invariant 7 (identity is explicit).\n */\nexport function subId(s: string): SubId {\n return s as SubId;\n}\n\n// === Sub: tagged-union, continuous source of msgs; stable id used for diff/reconcile ===\nexport type Sub<T extends string = string> = {\n readonly id: SubId;\n readonly type: T;\n};\n\n// === Dispose: the cleanup a dep-keyed Sub's source returns ===\n//\n// A `source` opens a resource and returns the function that closes it. The\n// substrate's reconcile calls it when the Sub's `deps` go null (torn down) or\n// change (re-armed: old `Dispose` then a fresh `source`). Same contract as the\n// `() => void` cleanup every `subscribe[type]` handler returns — named so a\n// dep-keyed Sub's `source` reads as \"open → returns close\".\nexport type Dispose = () => void;\n\n// === structuralHash: deterministic, order-independent id from a deps value ===\n//\n// The id of a dep-keyed Sub (invariant 7 — identity is explicit) is DERIVED\n// from the slice of state the Sub depends on, never hand-authored. So adding a\n// field to `deps` changes the id exactly when that field changes, and a\n// churning id — the named #1 Sub bug (a Sub that remounts every tick, so its\n// timer never fires) — becomes impossible by construction: the author cannot\n// build the id wrong because the author does not build it.\n//\n// Determinism is load-bearing (invariant 2 — the reconcile pass runs inside\n// the pure-transition path's reconcile step; the same `deps` must always hash\n// to the same id). So: NO `Date`, NO random, NO insertion-order dependence.\n// Object keys are sorted so `{ runId, phase }` and `{ phase, runId }` hash\n// identically — the canonical \"name the slice → stable key\" adaptation FoldKit\n// makes for TS (its `modelToDependencies`), which Elm gets free from its\n// structural compare.\n//\n// The supported `deps` shape is plain JSON-compatible data (string / number /\n// boolean / null / array / plain object). A function, class instance, symbol,\n// or bigint in `deps` is a programming error — `deps` names a state SLICE, and\n// state is a value (invariant 1); such a value would also break the\n// determinism this hash promises. The walk throws on a function to surface\n// that loudly rather than silently producing an unstable id.\n//\n// It is also the ONE \"turn this key value into a stable string\" primitive the\n// `@demlik/tea/subs` batteries address their interpret-local handle tables\n// with, so a battery's key rendering and the kernel's Sub identity can never\n// drift into two hashes for one fact.\nexport function structuralHash(deps: unknown): string {\n return stableStringify(deps);\n}\n\nfunction stableStringify(value: unknown): string {\n if (value === null) return \"null\";\n const t = typeof value;\n if (t === \"string\") return JSON.stringify(value);\n if (t === \"number\" || t === \"boolean\") return String(value);\n if (t === \"undefined\") return \"undefined\";\n if (t === \"function\") {\n throw new Error(\n \"@demlik/tea: structuralHash received a function in `deps`. \" +\n \"A dep-keyed Sub's `deps` must be a plain state slice (string / number / \" +\n \"boolean / null / array / object), not a closure — see invariant 1.\",\n );\n }\n if (Array.isArray(value)) {\n return `[${value.map(stableStringify).join(\",\")}]`;\n }\n if (t === \"object\") {\n const obj = value as Record<string, unknown>;\n const keys = Object.keys(obj).sort();\n return `{${keys.map((k) => `${JSON.stringify(k)}:${stableStringify(obj[k])}`).join(\",\")}}`;\n }\n // bigint / symbol — not JSON-representable; same class of error as function.\n throw new Error(\n `@demlik/tea: structuralHash received an unsupported \\`deps\\` value of type \"${t}\". ` +\n \"Use plain JSON-compatible data for a dep-keyed Sub's `deps`.\",\n );\n}\n\n// === DepKeyedSub<S, M, Ctx>: a Sub whose identity AND gate fall out of `deps` ===\n//\n// The author declares the slice of state the Sub depends on (`deps`) and how\n// to open the resource for that slice (`source`). The substrate derives BOTH:\n//\n// - the **id** = `structuralHash(deps(state))` — changes exactly when the\n// slice changes (re-arm), stable otherwise (no churn). The author never\n// writes `subId(...)` — the hand-written subId was the drift away from\n// Elm's structural identity, which this restores.\n// - the **gate** = `deps(state)` returning `null` ⇒ inactive in this state\n// (dispose if running); non-null ⇒ active. The substrate folds every\n// `DepKeyedSub` into its own active set, so the author deletes the central\n// `subscriptions(state)` aggregate — there is no list to forget to edit\n// when a new phase is added (a per-Sub gate travels with the Sub).\n//\n// This is the FoldKit `modelToDependencies` shape: a Sub names the state slice\n// it depends on, the kernel keys on that slice. The manual `subscriptions?:`\n// field stays as the documented escape hatch (see `Machine`).\n//\n// `source` returns a `Dispose`. On a `deps` change the substrate runs the old\n// `Dispose` then re-runs `source` with the new deps (re-arm); on `deps` going\n// null it runs `Dispose` (teardown). The same reconcile machinery the manual\n// `Sub` path uses (`reconcileSubs` in `../run.ts`) — no second loop.\n//\n// Strengthens invariant 4 (external lifecycle owned by the substrate — the\n// dep-keyed Sub is reconciled, never hand-driven) and invariant 7 (identity is\n// explicit — derived deterministically from `deps`, never an ad-hoc string).\nexport interface DepKeyedSub<S, M, Ctx = unknown> {\n /**\n * The slice of state this Sub depends on. `null` ⇒ the Sub is inactive in\n * this state (the substrate disposes it if it was running). Non-null ⇒ the\n * Sub is active, keyed on `structuralHash(deps)`.\n *\n * Pure (invariant 2). Plain JSON-compatible data only (invariant 1) — the\n * structural hash walks it deterministically.\n */\n readonly deps: (state: S) => unknown;\n /**\n * Open the resource for the current `state` (whose `deps` the kernel just\n * found non-null). Receives `dispatch` to fire follow-up Msgs (a timer's\n * fire, an inbound frame) and the machine's `ctx` (the host's transport\n * factory, storage handles). Returns the `Dispose` the substrate runs on\n * teardown / re-arm. Same role as a `subscribe[type]` handler's returned\n * cleanup.\n *\n * `source` takes `state` (not the deps slice) so the slice type `D` never\n * escapes the entry — each battery's `.depKeyed` closes over its own typed\n * deps internally (cast-free existential: the entry that produced the deps\n * is the entry that consumes them).\n */\n readonly source: (state: S, dispatch: (msg: M) => void, ctx: Ctx) => Dispose;\n}\n\n// === Identity<S, M>: declare the instance's identity once; the kernel drops\n// mis-addressed messages before they reach `update` ===\n//\n// Without this: the substrate dispatches EVERY message to `update`, so an\n// author who needs \"this message is for THIS run\" must repeat\n// `if (msg.runId !== state.runId) return [state, []]` in every reducer cell.\n// Forget one cell and a stale or foreign run's message is applied → silent\n// corruption. That per-cell check is a rung-4 runtime stand-in for invariant 7\n// (identity is explicit): the identity invariant the host already holds (one\n// instance per run — `idFromName(runId)`) is re-asserted, cell by cell, where\n// it can be forgotten.\n//\n// `Identity<S, M>` lifts that to ONE declaration the kernel enforces. The\n// machine names two projections:\n//\n// - `ofState(state)` → the identity THIS instance owns, derived from state.\n// - `ofMsg(msg)` → the identity a message is ADDRESSED to, or\n// `undefined` when the message carries no identity\n// (lifecycle messages like `boot`, or `start_audit`\n// before the run exists — those are never dropped).\n//\n// Per transition the kernel compares `ofMsg(msg)` to `ofState(state)` by\n// `structuralHash` (so the identity can be any plain value, same machinery the\n// dep-keyed Sub id uses). A message addressed to a DIFFERENT identity is\n// dropped BEFORE `update` runs — the reducer never sees it, so no cell needs a\n// guard. A message with `ofMsg === undefined` is identity-agnostic and always\n// reaches `update`.\n//\n// Two projections rather than a single `(state) => state.runId`: the kernel\n// needs BOTH the state's identity AND the message's to compare, and splitting\n// them keeps the comparison `K`-to-`K` with no `as` cast (a message union\n// carries its identity field on only SOME arms — `ofMsg` narrows per arm in\n// author-land, returning `undefined` on the arms that have none, instead of\n// the substrate reaching into `msg.runId` it cannot type).\n//\n// Opt-in (escape hatch): a machine that declares no `identity` skips the filter\n// entirely — every message reaches `update`, exactly as before.\n//\n// Strengthens invariant 7 (identity is explicit — declared once, derived\n// deterministically, kernel-enforced rather than re-checked per cell) and\n// invariant 6 (the runtime is small and inspectable — a mis-addressed message\n// is dropped at ONE observable point, not silently mishandled in N cells).\nexport interface Identity<S, M> {\n /** The identity THIS instance owns. Pure (invariant 2); plain value. */\n readonly ofState: (state: S) => unknown;\n /**\n * The identity a message is addressed to, or `undefined` when the message\n * carries no identity (lifecycle / pre-identity messages — never dropped).\n * Pure (invariant 2); plain value when defined.\n */\n readonly ofMsg: (msg: M) => unknown;\n}\n\n// === Port<T>: typed escape hatch for \"data leaving the runtime\" ===\n//\n// A Port is a named, typed channel that Cmd handlers can `emit` to via the\n// augmented `ctx.emit(port, value)`. Subscribers attach via\n// `runtime.subscribePort(port, listener)` and receive every emitted value\n// synchronously (same fanout discipline as `runtime.observe`).\n//\n// Why Ports are a substrate primitive, distinct from State and Observe:\n// - **State** is \"the world as the program sees it.\" Folding outgoing\n// announcements into State turns ephemeral signals into persisted facts —\n// the classic anti-pattern that motivated this primitive.\n// - **Observe** sees every transition `(msg, state)`. That's the right channel\n// for devtools and logging, but it forces every consumer to filter the\n// entire firehose for a single signal.\n// - **Ports** are typed and selective: one channel per concept, subscribers\n// only see what the handler chose to emit. Mirrors Elm's outgoing-port\n// semantics — a typed declaration with `port` keyword whose values flow\n// out of the program to the host.\n//\n// Port identity is **by reference** — the returned object IS the identity.\n// Name is metadata for debugging. To prevent the \"two modules definePort the\n// same name expecting to share a channel, silently get distinct ports\" class\n// of bug, `definePort` asserts the name has not been seen in this process —\n// the symmetric runtime check to `SubId` (canon §2.12, invariant 7). Each\n// definePort call must use a unique name; if two modules need the same port,\n// one module exports it and the other imports it.\nexport interface Port<T> {\n readonly __brand: \"port\";\n readonly name: string;\n // Phantom field — never assigned, never read at runtime. Carries `T` for\n // inference at `ctx.emit` and `subscribePort` call sites. Using `?` so\n // `definePort` can produce a Port without constructing a value of `T`.\n readonly __t?: T;\n}\n\n/**\n * Augmentation injected onto `ctx` inside Cmd handlers. Handlers receive\n * `ctx & PortEmitter` so they can call `ctx.emit(port, value)` synchronously.\n * Subscribers registered via `runtime.subscribePort(port, listener)` receive\n * the value immediately.\n *\n * Emitting to a port with no subscribers is a no-op (does not throw).\n */\nexport interface PortEmitter {\n emit<T>(port: Port<T>, value: T): void;\n}\n\n// === NoCtx: the context-free marker ===\n//\n// A handler, Sub, or machine that reads NOTHING from `ctx` says so by using\n// `NoCtx` for its Ctx slot. This is the DELIBERATE absence of context — not\n// the accidental `unknown` looseness that erodes a boundary (see issue #64).\n//\n// `unknown` at a Ctx seam is ambiguous: it can mean \"I intentionally need no\n// context\" OR \"I gave up tightening this type.\" `NoCtx` resolves that\n// ambiguity at the type level. A reader (and the next agent) sees `NoCtx` and\n// knows the context-free-ness is a choice the author made, not a hole.\n//\n// Shape: an empty readonly record. A `Ctx & PortEmitter` is-a `NoCtx`, so a\n// context-free handler still composes at a richer call site. It carries no\n// own fields, so the name's claim (\"reads nothing from ctx\") is what callers\n// read at the seam.\n//\n// Strengthens invariant 6 (no silent looseness — a context-free seam is named,\n// not inferred) and invariant 8 (the boundary is legible; `unknown` stays\n// reserved for genuine wire-edge erasure, not for \"didn't bother\").\nexport type NoCtx = Readonly<Record<never, never>>;\n\n/**\n * Spelled-out alias for `NoCtx`.\n *\n * @deprecated Use {@link NoCtx}. One Way to Do Each Thing — `NoCtx` is the\n * single non-deprecated name for the context-free-ctx marker (this alias has\n * no in-tree uses). Kept as a re-export for one minor per the deprecate-don't-\n * delete policy in `MAINTAINING.md`, then removed.\n */\nexport type ContextFree = NoCtx;\n\n// === Interpret<M, C, Ctx>: record-of-handlers form of `interpret` ===\n//\n// Flat dispatch table keyed by `Cmd.type`. Each cell receives the narrowed Cmd\n// and the runtime-augmented Ctx (`Ctx & PortEmitter`) and resolves to a\n// follow-up Msg or `void` (fire-and-forget). The mapped type makes a missing\n// handler a compile error — `defineMachine` cannot accept the dictionary until\n// every Cmd variant has one.\n//\n// **The injected `dispatch` — the typed Cmd→Msg edge.** A leaf handler that\n// resolves on its own returns its follow-up Msg (`Promise<M | void>`) and\n// IGNORES the third argument. A DETACHED handler (one that hands long-running\n// work to `ctx.waitUntil` and CANNOT return its terminal Msg inline — awaiting\n// it would deadlock the serial dispatch tail) fires its terminal Msg through\n// this injected `dispatch` instead. Today such a handler reaches for a\n// host-wired `ctx.dispatch` typed to the FULL Msg union — so a typo'd or wrong\n// terminal Msg from a detached site compiles silently (rung 5: the allowed set\n// lives only in a comment). `wrapDetached<C, Allowed>` (in `../runtime-types`)\n// narrows THIS injected `dispatch` to the Cmd's declared result-Msg set, so a\n// wrong terminal Msg fails to compile (rung 2).\n//\n// Additive: the third arg is OPTIONAL (`dispatch?: (msg: M) => void`), so a\n// handler declaring only `(cmd, ctx)` stays assignable, and a unit test that\n// invokes a handler directly with two args still typechecks. The kernel ALWAYS\n// passes the dispatch (see `runInterpret` in `../run.ts`); the optionality is\n// purely a backward-compatibility affordance on the TYPE, not a runtime \"maybe\n// absent\". A handler authored via `wrapDetached` receives a NARROWER view of\n// this dispatch (only its declared result-Msg set).\n//\n// Hoisted out of `Machine.interpret` so consumers can type a free-standing\n// handler dictionary with `Interpret<MyMsg, MyCmd, MyCtx>` instead of\n// re-declaring the mapped type at every effects module.\n//\n// Strengthens invariant 2 (the record form has no fall-through default to\n// hide impurity behind) and invariant 7 (identity is explicit — the Cmd\n// variant set is load-bearing at the type level).\nexport type Interpret<M extends { type: string }, C extends Cmd, Ctx> = {\n [K in C[\"type\"]]: (\n cmd: Extract<C, { type: K }>,\n ctx: Ctx & PortEmitter,\n dispatch?: (msg: M) => void,\n // biome-ignore lint/suspicious/noConfusingVoidType: an interpret handler returns a follow-up Msg or nothing; `void` permits no-return bodies that `M | undefined` would reject\n ) => Promise<M | void>;\n};\n\n// === InterpretDetached<C, Allowed, Ctx>: a detached interpret handler ===\n//\n// A handler that detaches long-running work (`ctx.waitUntil(...)`) and therefore\n// CANNOT return its terminal Msg inline (awaiting it would deadlock the serial\n// dispatch tail). It returns `Promise<void>` and fires its terminal Msg through\n// the kernel-injected `dispatch`, which is NARROWED to `Allowed` — the subset\n// of the Msg union this Cmd is permitted to produce. A wrong / typo'd terminal\n// Msg then fails to compile.\n//\n// `Allowed extends { type: string }` is the declared result-Msg set (e.g.\n// `GraphFinished | GraphFailed`). `dispatch: (msg: Allowed) => void` is the\n// narrowed edge. The handler still receives `ctx` (carrying `waitUntil`).\n//\n// Authored via `wrapDetached` (in `../runtime-types`), which adapts this shape\n// back into a plain `Interpret` cell so it drops into the existing `interpret`\n// dictionary with no kernel change at the call site.\nexport type InterpretDetached<\n C extends Cmd,\n Allowed extends { type: string },\n Ctx,\n> = (\n cmd: C,\n ctx: Ctx & PortEmitter,\n dispatch: (msg: Allowed) => void,\n) => Promise<void>;\n\n// === Subscribe<M, U, Ctx>: record-of-handlers form of `subscribe` ===\n//\n// Flat dispatch table keyed by `Sub.type`. Each cell receives the narrowed\n// Sub, the Ctx, and a `dispatch` to fire follow-up Msgs. Returns a cleanup\n// function the substrate calls when the Sub is reconciled out (state\n// transitioned away). The mapped type guarantees every Sub variant has a\n// handler at the type level; runtime dispatch is a single property lookup.\n//\n// Hoisted out of `Machine.subscribe` so consumers can type a free-standing\n// handler dictionary with `Subscribe<MyMsg, MySub, MyCtx>` instead of\n// re-declaring the mapped type at every subs module.\n//\n// Strengthens invariant 7 (identity is explicit — the Sub variant set is\n// load-bearing at the type level).\nexport type Subscribe<M extends { type: string }, U extends Sub, Ctx> = {\n [K in U[\"type\"]]: (\n sub: Extract<U, { type: K }>,\n ctx: Ctx,\n dispatch: (msg: M) => void,\n ) => () => void;\n};\n\n// === Machine: pure data, host-agnostic ===\n//\n// `update` is stored as a union of the two record forms — the runtime branches\n// on shape at dispatch time. The public `defineMachine` overloads enforce one\n// shape per call so consumers never see the union themselves.\n//\n// - Reducer form: `Reducer<S, M, C>` — flat record keyed by Msg.type;\n// exhaustiveness enforced by the mapped type.\n// - Transitions form: `Transitions<S, M, C>` — 2D table keyed by\n// `state.type` then `msg.type`; only available when `S extends { type:\n// string }`. Conditional in the union so machines without a discriminated\n// State don't widen to include this branch.\n//\n// `M` is constrained to `{ type: string }` because both record forms require\n// a string discriminant.\n//\n// `interpret` is conditionally optional: when `C` is `Cmd<never>` the\n// interpret map is keyed by `never`, so the only valid value is `{}`. Forcing\n// every cmdless machine to write `interpret: {} as never` is ceremony for a\n// shape the type already pins. The tuple-wrap (`[C] extends [Cmd<never>]`)\n// disables distributive conditional so a real cmd union (e.g.\n// `Cmd<\"a\"> | Cmd<\"b\">`) doesn't degrade to optional just because one arm\n// happens to be `Cmd<never>`. Same trick the `update` field uses.\nexport type Machine<\n S,\n M extends { type: string },\n C extends Cmd,\n U extends Sub,\n Ctx,\n> = {\n /**\n * Boot the runtime. Called once by `run(...)` with whatever `Store.load()`\n * returned: `null` on fresh boot, the persisted state on rehydrate.\n *\n * **Contract:** when `loaded !== null`, init MUST return `[loaded, []]` —\n * no Cmds. Init's rehydrate branch is the migration / parse boundary, not\n * the boot-effect hook. See Invariant 2 in `.patterns/tea/tea-invariants.md`.\n *\n * Boot effects routes:\n * - Stateless infrastructure → host module top, outside TEA.\n * - State-conditional resume → a `boot` Msg the host dispatches once\n * after `run(...)` returns.\n *\n * Violations are caught at runtime by `replay` (which throws with a\n * pointer to the alternatives).\n */\n init: (loaded: S | null, ctx: Ctx) => readonly [S, readonly C[]];\n update:\n | Reducer<S, M, C>\n // Wrap in tuple to disable distributive conditional behavior. Without\n // `[S]`, TS distributes the conditional across `S`'s union members,\n // producing `Transitions<A,...> | Transitions<B,...>` instead of the\n // desired `Transitions<A|B,...>` — the table form needs a single\n // mapping over the full union of state types, not separate tables per\n // member.\n | ([S] extends [{ type: string }] ? Transitions<S, M, C> : never);\n /**\n * Dep-keyed Subs. Each declares the state slice it depends on; the substrate\n * derives the id (`structuralHash(deps)`) and the gate (`deps` non-null) and\n * folds them into the SAME reconcile loop `subscriptions` feeds. The author\n * never writes `subId(...)` and never lists Subs in a central\n * `subscriptions(state)` aggregate — a per-Sub gate travels with the Sub, so\n * a new phase can't silently forget to arm it.\n *\n * The deps slice type is erased to `unknown` per entry — each Sub's slice\n * differs, but the slice type never crosses this field (the kernel only needs\n * to hash it and pass `state` back to that same entry's `source`).\n *\n * Optional and independent of `subscriptions` / `subscribe`: a machine that\n * omits `subs` reconciles exactly as before.\n *\n * Strengthens invariant 4 (lifecycle owned by the substrate) and invariant 7\n * (identity derived, not hand-authored).\n */\n subs?: ReadonlyArray<DepKeyedSub<S, M, Ctx>>;\n /**\n * Instance-identity filter. Declares THIS instance's identity once; the\n * substrate drops any message addressed to a DIFFERENT identity before it\n * reaches `update`. Replaces the per-cell `if (msg.runId !== state.runId)`\n * guard entirely — the reducer never sees a foreign-instance message, so no\n * cell needs to check.\n *\n * Opt-in: a machine that omits `identity` skips the filter (every message\n * reaches `update`, exactly as before). A message whose `ofMsg` returns\n * `undefined` is identity-agnostic and always reaches `update` (lifecycle /\n * pre-identity messages).\n *\n * Strengthens invariant 7 (identity is explicit — declared once, enforced by\n * the substrate) and invariant 6 (mis-addressed messages dropped at one\n * observable point, not per cell).\n */\n identity?: Identity<S, M>;\n /**\n * Manual Sub aggregate — the documented escape hatch, KEPT alongside `subs`.\n * The machine that needs cross-Sub logic the per-Sub `deps` fold can't\n * express lists Subs here; the substrate reconciles them via `subscribe`.\n * Both paths feed ONE reconcile pass.\n */\n subscriptions?: (state: S) => readonly U[];\n subscribe?: Subscribe<M, U, Ctx>;\n /**\n * The update form (\"reducer\" | \"transitions\"), stamped non-enumerably by\n * `defineMachine` at construction (see `UpdateForm` / `formOf`). Optional in\n * the type so the structural `Machine` annotation form keeps accepting plain\n * object literals; readers go through `formOf`, which falls back to\n * `detectUpdateForm` when the tag is absent. Never written by hand.\n */\n readonly __form?: UpdateForm;\n} & ([C] extends [Cmd<never>]\n ? { interpret?: Interpret<M, C, Ctx> }\n : { interpret: Interpret<M, C, Ctx> }) &\n // `subscribe`/`subscriptions` are conditionally REQUIRED the same way\n // `interpret` is (#276): a machine declaring a real Sub union without a\n // subscribe map compiled and silently wired no subs (`reconcileSubs` skips\n // undefined handlers) — the exact silent-failure class the `interpret`\n // conditional prevents. The optional declarations above stay as U's\n // inference sites (a conditional type is not an inference site); this\n // intersection only adds requiredness when U is a real union. Same\n // tuple-wrap trick as `interpret` to disable distribution.\n ([U] extends [Sub<never>]\n ? unknown\n : {\n subscriptions: (state: S) => readonly U[];\n subscribe: Subscribe<M, U, Ctx>;\n });\n\n// === foldUpdates: the single internal fold `replay` and `foldMsgs` share ===\n//\n// Folds `machine.update` over `msgs` from `initialState`, dispatching each\n// Msg through `applyCell` — the same primitive `run` uses — so every fold site\n// agrees on the reducer-vs-transitions form by construction (no second copy of\n// the dispatch to drift). Returns the final state plus the Cmds the cells emitted\n// along the way; the caller keeps them (`replay`) or discards them (`foldMsgs`).\n//\n// Touches no `Store`, no `interpret` handler, and starts no subscription — it\n// only calls `update` cells. The two public folds differ only in how they\n// enter and what they return: `replay` enters via `init` and returns\n// `{ state, cmds, subs }`; `foldMsgs` enters from a base state and returns `S`.\nexport function foldUpdates<S, M extends { type: string }, C extends Cmd>(\n machine: { update: object; __form?: UpdateForm },\n initialState: S,\n msgs: readonly M[],\n): { state: S; cmds: C[] } {\n let state: S = initialState;\n const cmds: C[] = [];\n\n for (const msg of msgs) {\n const [next, emitted] = applyCellChecked<S, M, C>(machine, state, msg);\n state = next;\n cmds.push(...emitted);\n }\n\n return { state, cmds };\n}\n\n// === foldMsgs: runtime-free client-prediction fold seam (ADR 0006, #211) ===\n//\n// Folds `machine.update` over an ordered `Msg[]` starting from a caller-supplied\n// `base` state and returns the resulting state ONLY. This is the client-side\n// replay primitive a prediction loop needs (the Gambetta/Valve\n// authoritative-server reconcile step): re-simulate a queue of un-acked inputs\n// on top of an authoritative snapshot —\n// `foldMsgs(machine, snapshot, pendingInputs)`.\n//\n// Distinct from `replay` (the test idiom), by design (ADR 0006):\n// - Enters from a direct `base` parameter, NOT via `init`/`loaded` — so\n// reconciliation correctness never depends on the machine author's `init`\n// rehydrate discipline. Takes no `ctx` (the fold calls `update` only).\n// - Returns just the final `S` — not `{ state, cmds, subs }`. During prediction\n// the inputs' effects were already sent to the server; re-emitting their Cmds\n// on the client would double-fire, and `subs` are a runtime concern. Returning\n// only `S` makes \"replay fires no effects\" structural, not caller discipline.\n// A caller that wants the emitted Cmds for assertions uses `replay`.\n//\n// Invokes no `Store`, no `interpret` handler, and starts no subscription — it\n// shares `replay`'s `foldUpdates` fold, which only calls `update` cells, and\n// reads the reducer-vs-transitions form via the same `formOf(machine)` reader\n// `run`/`replay` use, so it agrees with them by construction.\n//\n// Per ADR 0006 the runtime-free *guarantee* lives on a dedicated\n// `@demlik/tea/pure` subpath whose module graph never reaches `run`; that\n// subpath export + import-graph guard are #213's scope. Here `foldMsgs` ships\n// as a reachable public API from root; #213 formalizes the boundary.\nexport function foldMsgs<\n S,\n M extends { type: string },\n C extends Cmd,\n U extends Sub,\n Ctx,\n>(machine: Machine<S, M, C, U, Ctx>, base: S, msgs: readonly M[]): S {\n return foldUpdates<S, M, C>(machine, base, msgs).state;\n}\n"]}
@@ -34,16 +34,27 @@ function backoffDelay(attempt, policy, rng = defaultRng) {
34
34
  function initRetry() {
35
35
  return { attempt: 0 };
36
36
  }
37
- function recordFailure(state, error) {
38
- return { attempt: state.attempt + 1, lastError: error };
37
+ function recordFailure(state, error, atMs) {
38
+ const attempt = state.attempt + 1;
39
+ const firstFailureAtMs = state.firstFailureAtMs ?? atMs;
40
+ return firstFailureAtMs === void 0 ? { attempt, lastError: error } : { attempt, lastError: error, firstFailureAtMs };
39
41
  }
40
- function shouldRetry(state, policy) {
41
- return state.attempt < policy.maxAttempts;
42
+ function retryElapsedMs(state, nowMs) {
43
+ const elapsed = nowMs - state.firstFailureAtMs;
44
+ return elapsed > 0 ? elapsed : 0;
45
+ }
46
+ function shouldRetry(state, policy, nowMs) {
47
+ if (policy.maxAttempts !== void 0 && state.attempt >= policy.maxAttempts) {
48
+ return false;
49
+ }
50
+ if (policy.maxElapsedMs === void 0) return true;
51
+ if (state.firstFailureAtMs === void 0 || nowMs === void 0) return false;
52
+ return nowMs - state.firstFailureAtMs < policy.maxElapsedMs;
42
53
  }
43
54
  function nextDelayMs(state, policy, rng = defaultRng) {
44
55
  return backoffDelay(state.attempt, policy, rng);
45
56
  }
46
57
 
47
- export { asRng, backoffDelay, defaultRetryPolicy, defaultRng, initRetry, nextDelayMs, recordFailure, shouldRetry };
48
- //# sourceMappingURL=chunk-5VNWZADP.js.map
49
- //# sourceMappingURL=chunk-5VNWZADP.js.map
58
+ export { asRng, backoffDelay, defaultRetryPolicy, defaultRng, initRetry, nextDelayMs, recordFailure, retryElapsedMs, shouldRetry };
59
+ //# sourceMappingURL=chunk-Q2UM7BXU.js.map
60
+ //# sourceMappingURL=chunk-Q2UM7BXU.js.map