@demlik/tea 0.14.0 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent/index.d.ts +14 -13
- package/dist/agent/index.js +6 -6
- package/dist/{chunk-F2US5BW6.js → chunk-22POWGFA.js} +17 -11
- package/dist/chunk-22POWGFA.js.map +1 -0
- package/dist/{chunk-HYDWG35Q.js → chunk-3B5X7SBB.js} +4 -4
- package/dist/{chunk-HYDWG35Q.js.map → chunk-3B5X7SBB.js.map} +1 -1
- package/dist/{chunk-RQULHGYH.js → chunk-66PY5WYF.js} +15 -3
- package/dist/{chunk-RQULHGYH.js.map → chunk-66PY5WYF.js.map} +1 -1
- package/dist/{chunk-AQ6ZIMEA.js → chunk-AYS5N6WY.js} +121 -37
- package/dist/chunk-AYS5N6WY.js.map +1 -0
- package/dist/{chunk-HUKZQX6U.js → chunk-L3TND7LA.js} +3 -3
- package/dist/{chunk-HUKZQX6U.js.map → chunk-L3TND7LA.js.map} +1 -1
- package/dist/{chunk-I5MQMJDW.js → chunk-Q5CQTBFA.js} +3 -3
- package/dist/{chunk-I5MQMJDW.js.map → chunk-Q5CQTBFA.js.map} +1 -1
- package/dist/{chunk-F7FMAWLA.js → chunk-QPEVCJ5Q.js} +3 -3
- package/dist/{chunk-F7FMAWLA.js.map → chunk-QPEVCJ5Q.js.map} +1 -1
- package/dist/{chunk-Z2L3LSNV.js → chunk-TCLBCERH.js} +17 -7
- package/dist/chunk-TCLBCERH.js.map +1 -0
- package/dist/{chunk-BLJ2JQSH.js → chunk-V46HSVCK.js} +3 -3
- package/dist/{chunk-BLJ2JQSH.js.map → chunk-V46HSVCK.js.map} +1 -1
- package/dist/{chunk-LYLTKJWM.js → chunk-VTQI2NHP.js} +3 -3
- package/dist/{chunk-LYLTKJWM.js.map → chunk-VTQI2NHP.js.map} +1 -1
- package/dist/{core-CTzMJ82M.d.ts → core-DnzqWy6u.d.ts} +1 -1
- package/dist/do/index.d.ts +13 -12
- package/dist/do/index.js +7 -7
- package/dist/{durable-effects-B0-qGQDy.d.ts → durable-effects-CWzA-o2B.d.ts} +1 -1
- package/dist/extension/index.d.ts +3 -3
- package/dist/extension/index.js +1 -1
- package/dist/flow/index.d.ts +15 -26
- package/dist/flow/index.js +8 -8
- package/dist/idempotency/index.d.ts +3 -3
- package/dist/idempotency/index.js +2 -2
- package/dist/{index-4vZzljCD.d.ts → index-C2DwkjlP.d.ts} +4 -4
- package/dist/{index-B5BZ-_cT.d.ts → index-CA_7I5g9.d.ts} +277 -87
- package/dist/{index-DJWIER_3.d.ts → index-CSyMYDAB.d.ts} +1 -1
- package/dist/{index-6A7dB8f6.d.ts → index-CuNO9m8i.d.ts} +2 -2
- package/dist/{index-TvRb1KVf.d.ts → index-DrIRb_Ch.d.ts} +1 -1
- package/dist/{index-q0hXFeZ0.d.ts → index-HSYgG2Yt.d.ts} +50 -15
- package/dist/{index-NjrT3fL2.d.ts → index-KXqTRJyb.d.ts} +4 -4
- package/dist/{index-DMCeMPMJ.d.ts → index-ZCMhnVKN.d.ts} +1 -1
- package/dist/index.d.ts +66 -6
- package/dist/index.js +1 -1
- package/dist/jev/index.d.ts +34 -22
- package/dist/jev/index.js +178 -126
- package/dist/jev/index.js.map +1 -1
- package/dist/{machine-CdIcN2Xm.d.ts → machine-DGlPWCWI.d.ts} +34 -26
- package/dist/machine-viz/index.d.ts +1 -1
- package/dist/machine-viz/index.js +1 -1
- package/dist/mem/index.d.ts +2 -2
- package/dist/mem/index.js +1 -1
- package/dist/node/index.d.ts +2 -2
- package/dist/node/index.js +1 -1
- package/dist/paginate/index.d.ts +6 -17
- package/dist/paginate/index.js +4 -4
- package/dist/parity/index.d.ts +3 -3
- package/dist/parity/index.js +2 -2
- package/dist/pbt/index.d.ts +1 -1
- package/dist/pbt/index.js +1 -1
- package/dist/persistence/index.d.ts +5 -5
- package/dist/persistence/index.js +3 -3
- package/dist/protocol-D8VbH-VH.d.ts +54 -0
- package/dist/react/index.d.ts +2 -2
- package/dist/react/index.js +1 -1
- package/dist/resilience/index.d.ts +8 -7
- package/dist/resilience/index.js +5 -5
- package/dist/{runtime-types-LWDoi943.d.ts → runtime-types-CMF13BBh.d.ts} +1 -1
- package/dist/testing/index.d.ts +147 -3
- package/dist/testing/index.js +95 -2
- package/dist/testing/index.js.map +1 -1
- package/dist/timing/index.d.ts +4 -4
- package/dist/timing/index.js +3 -3
- package/dist/{types-DP3f5NIl.d.ts → types-BseNPIhr.d.ts} +1 -1
- package/dist/work-queue/index.d.ts +4 -4
- package/package.json +1 -1
- package/dist/chunk-AQ6ZIMEA.js.map +0 -1
- package/dist/chunk-F2US5BW6.js.map +0 -1
- package/dist/chunk-Z2L3LSNV.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/testing/assert-wrapper-faithful.ts","../../src/testing/assertions.ts","../../src/testing/bind-machine.ts","../../src/testing/noop-runtime.ts","../../src/testing/state-factory.ts"],"names":["step","expect"],"mappings":";;;AAiJO,SAAS,qBAAA,CAYd,IAAA,EACA,WAAA,EACA,IAAA,EACM;AACN,EAAA,MAAM,OAAA,GAAW,IAAA,CAAK,OAAA,IAAY,IAAA,CAAK,GAAA;AACvC,EAAA,MAAM,cAAc,IAAA,CAAK,IAAA;AACzB,EAAA,MAAM,UAAA,GACJ,KAAK,UAAA,KAAe,IAAA,CAAK,UAAU,IAAA,GAAQ,IAAA,CAAK,OAAO,IAAA,GAAa,IAAA,CAAA;AAStE,EAAA,MAAM,EAAA,GAAK,EAAE,GAAA,EAAK,GAAA,EAAW,KAAK,KAAA,EAAM;AACxC,EAAA,MAAM,EAAA,GAAK,EAAE,GAAA,EAAK,OAAA,EAAW,KAAK,KAAA,EAAM;AAIxC,EAAA,MAAM,IAAA,GAAO,YAAA;AAAA,IAAa,EAAA;AAAA,IAAI,MAC5B,MAAA,CAAO,IAAA,EAAM,EAAE,IAAA,EAAM,IAAA,CAAK,IAAA,EAAM,GAAA,EAAK,OAAA,EAAS,MAAA,EAAQ,UAAA,EAAY;AAAA,GACpE;AACA,EAAA,MAAM,OAAA,GAAU,YAAA,CAAa,EAAA,EAAI,MAAM,aAAa,CAAA;AACpD,EAAA,MAAM,QAAA,GAAW,YAAA;AAAA,IAAa,EAAA;AAAA,IAAI,MAChC,OAAO,OAAA,EAAS;AAAA,MACd,IAAA,EAAM,WAAA;AAAA,MACN,KAAK,IAAA,CAAK,GAAA;AAAA,MACV,MAAA,EAAQ,KAAK,MAAA,IAAU;AAAA,KACxB;AAAA,GACH;AAIA,EAAA,MAAM,QAAA,GAAW,YAAA;AAAA,IAAa,EAAA;AAAA,IAAI,MAChC,MAAA,CAAO,WAAA,EAAY,EAAG;AAAA,MACpB,IAAA,EAAM,WAAA;AAAA,MACN,KAAK,IAAA,CAAK,GAAA;AAAA,MACV,MAAA,EAAQ,KAAK,MAAA,IAAU;AAAA,KACxB;AAAA,GACH;AAKA,EAAA,MAAA,CAAO,SAAS,KAAA,CAAM,IAAI,CAAA,CAAE,OAAA,CAAQ,KAAK,KAAK,CAAA;AAO9C,EAAA,oBAAA,CAAqB,KAAK,IAAA,EAAM,QAAA,CAAS,IAAA,EAAM,MAAA,EAAW,KAAK,YAAY,CAAA;AAU3E,EAAA,MAAM,QAAA,GAAW,eAAA,CAAgB,QAAA,CAAS,KAAK,CAAA;AAC/C,EAAA,MAAA;AAAA,IACE,OAAA,CAAQ,QAAA,CAAS,KAAA,EAAO,QAAQ,CAAA;AAAA,IAChC,CAAA,oJAAA;AAAA,IAEA,OAAA,CAAQ,OAAA,CAAQ,QAAA,CAAS,KAAA,EAAO,QAAQ,CAAC,CAAA;AAc3C,EAAA,MAAM,aAAA,GAAgB,GAAG,QAAQ,CAAA,CAAA,CAAA;AACjC,EAAA,MAAM,YAAA,GAAe,SAAS,IAAA,CAAK,MAAA;AAAA,IAAO,CAAC,CAAA,KACzC,CAAA,CAAE,IAAA,CAAK,WAAW,aAAa;AAAA,GACjC;AACA,EAAA,MAAM,YAAA,GAAe,SAAS,IAAA,CAAK,MAAA;AAAA,IAAO,CAAC,CAAA,KACzC,CAAA,CAAE,IAAA,CAAK,WAAW,aAAa;AAAA,GACjC;AACA,EAAA,MAAA;AAAA,IACE,YAAA;AAAA,IACA,kBAAkB,aAAa,CAAA,4MAAA;AAAA,GAIjC,CAAE,QAAQ,YAAY,CAAA;AAWtB,EAAA,IAAI,IAAA,CAAK,IAAA,CAAK,MAAA,GAAS,CAAA,EAAG;AACxB,IAAA,MAAA;AAAA,MACE,YAAA,CAAa,MAAA;AAAA,MACb,CAAA,uBAAA,EAA0B,aAAa,CAAA,6DAAA,EACf,IAAA,CAAK,KAAK,MAAM,CAAA,+GAAA;AAAA,KAE1C,CAAE,gBAAgB,CAAC,CAAA;AAAA,EACrB;AAWA,EAAA,KAAA,IAAS,IAAI,CAAA,EAAG,CAAA,GAAI,IAAA,CAAK,IAAA,CAAK,QAAQ,CAAA,EAAA,EAAK;AACzC,IAAA,MAAM,QAAA,GAAW,QAAA;AAAA,MACf,MACE,YAAA;AAAA,QAAa,EAAA;AAAA,QAAI,MACf,OAAO,IAAA,EAAM;AAAA,UACX,IAAA,EAAM,IAAA,CAAK,IAAA,CAAK,KAAA,CAAM,GAAG,CAAC,CAAA;AAAA,UAC1B,GAAA,EAAK,OAAA;AAAA,UACL,MAAA,EAAQ;AAAA,SACT;AAAA,OACH,CAAE,IAAA;AAAA,MACJ,MACE,YAAA;AAAA,QAAa,EAAA;AAAA,QAAI,MACf,OAAO,IAAA,EAAM;AAAA,UACX,MAAM,IAAA,CAAK,IAAA,CAAK,KAAA,CAAM,CAAA,EAAG,IAAI,CAAC,CAAA;AAAA,UAC9B,GAAA,EAAK,OAAA;AAAA,UACL,MAAA,EAAQ;AAAA,SACT;AAAA,OACH,CAAE;AAAA,KACN;AACA,IAAA,MAAM,WAAA,GAAc,QAAA;AAAA,MAClB,MACE,YAAA;AAAA,QAAa,EAAA;AAAA,QAAI,MACf,OAAO,OAAA,EAAS;AAAA,UACd,IAAA,EAAM,WAAA,CAAY,KAAA,CAAM,CAAA,EAAG,CAAC,CAAA;AAAA,UAC5B,KAAK,IAAA,CAAK,GAAA;AAAA,UACV,MAAA,EAAQ,KAAK,MAAA,IAAU;AAAA,SACxB;AAAA,OACH,CAAE,IAAA;AAAA,MACJ,MACE,YAAA;AAAA,QAAa,EAAA;AAAA,QAAI,MACf,OAAO,OAAA,EAAS;AAAA,UACd,IAAA,EAAM,WAAA,CAAY,KAAA,CAAM,CAAA,EAAG,IAAI,CAAC,CAAA;AAAA,UAChC,KAAK,IAAA,CAAK,GAAA;AAAA,UACV,MAAA,EAAQ,KAAK,MAAA,IAAU;AAAA,SACxB;AAAA,OACH,CAAE;AAAA,KACN;AACA,IAAA,oBAAA,CAAqB,QAAA,EAAU,WAAA,EAAa,CAAA,EAAG,IAAA,CAAK,YAAY,CAAA;AAAA,EAClE;AAKA,EAAA,MAAM,KAAA,GAAQ,OAAA,CAAQ,QAAA,CAAS,KAAA,EAAO,QAAQ,CAAA;AAC9C,EAAA,MAAA,CAAO,IAAA,CAAK,MAAM,IAAA,CAAK,SAAA,CAAU,KAAK,CAAC,CAAC,CAAA,CAAE,OAAA,CAAQ,KAAK,CAAA;AACzD;AASA,SAAS,YAAA,CACP,SACA,EAAA,EACG;AACH,EAAA,MAAM,MAAA,GAAS,GAAG,KAAA,CAAM,IAAA,EAAM,KAAK,CAAA,CAAE,eAAA,CAAgB,QAAQ,GAAG,CAAA;AAChE,EAAA,MAAM,MAAA,GAAS,GAAG,KAAA,CAAM,IAAA,EAAM,QAAQ,CAAA,CAAE,eAAA,CAAgB,QAAQ,GAAG,CAAA;AACnE,EAAA,IAAI;AACF,IAAA,OAAO,EAAA,EAAG;AAAA,EACZ,CAAA,SAAE;AACA,IAAA,MAAA,CAAO,WAAA,EAAY;AACnB,IAAA,MAAA,CAAO,WAAA,EAAY;AAAA,EACrB;AACF;AAGA,SAAS,OAAA,CAAQ,OAAe,QAAA,EAA2B;AACzD,EAAA,OAAQ,MAAkC,QAAQ,CAAA;AACpD;AAQA,SAAS,QAAA,CACP,YACA,WAAA,EACgB;AAChB,EAAA,MAAM,SAAS,UAAA,EAAW;AAC1B,EAAA,MAAM,QAAQ,WAAA,EAAY;AAC1B,EAAA,OAAO,KAAA,CAAM,KAAA,CAAM,MAAA,CAAO,MAAM,CAAA;AAClC;AAOA,SAAS,gBAAgB,KAAA,EAAuB;AAC9C,EAAA,MAAM,UAAA,GAAa,MAAA,CAAO,IAAA,CAAK,KAAK,CAAA,CAAE,MAAA,CAAO,CAAC,CAAA,KAAM,CAAA,CAAE,UAAA,CAAW,GAAG,CAAC,CAAA;AACrE,EAAA,IAAI,UAAA,CAAW,WAAW,CAAA,EAAG;AAC3B,IAAA,MAAM,IAAI,KAAA;AAAA,MACR,CAAA,oGAAA,EACkC,WAAW,MAAM,CAAA,EAAA,EAAK,WAAW,IAAA,CAAK,IAAI,KAAK,MAAM,CAAA,wEAAA;AAAA,KAEzF;AAAA,EACF;AAEA,EAAA,OAAO,WAAW,CAAC,CAAA;AACrB;AAoBA,SAAS,oBAAA,CACP,MAAA,EACA,QAAA,EACAA,KAAAA,EACA,YAAA,EACM;AACN,EAAA,IAAI,CAAA,GAAI,CAAA;AACR,EAAA,KAAA,MAAW,OAAO,QAAA,EAAU;AAC1B,IAAA,IAAI,CAAA,IAAK,OAAO,MAAA,EAAQ;AACxB,IAAA,IAAI,WAAA,CAAY,MAAA,CAAO,CAAC,CAAA,EAAG,GAAG,CAAA,EAAG;AAC/B,MAAA,CAAA,EAAA;AACA,MAAA;AAAA,IACF;AAIA,IAAA,IACE,YAAA,KAAiB,UACjB,cAAA,CAAe,GAAA,EAAK,OAAO,CAAC,CAAA,EAAU,YAAY,CAAA,EAClD;AACA,MAAA,CAAA,EAAA;AAAA,IACF;AAAA,EACF;AACA,EAAA,MAAM,KAAA,GACJA,KAAAA,KAAS,MAAA,GACL,wBAAA,GACA,wCAAwCA,KAAI,CAAA,iFAAA,CAAA;AAElD,EAAA,MAAM,WAAA,GACJ,iBAAiB,MAAA,GACb,4DAAA,GACA,mEACI,YAAA,CAAa,cAAc,CAAA,wBAAA,EAC3B,YAAA,CAAa,UAAU,CAAA,mIAAA,CAAA;AAGjC,EAAA,MAAA;AAAA,IACE,CAAA;AAAA,IACA,CAAA,2DAAA,EAA8D,CAAC,CAAA,IAAA,EAC1D,MAAA,CAAO,MAAM,CAAA,wCAAA,EAA2C,KAAK,YAC7D,WAAW,CAAA;AAAA,GAClB,CAAE,IAAA,CAAK,MAAA,CAAO,MAAM,CAAA;AACtB;AAgBA,SAAS,cAAA,CAAe,GAAA,EAAU,OAAA,EAAc,GAAA,EAA+B;AAC7E,EAAA,IAAI,CAAC,GAAA,CAAI,IAAA,CAAK,UAAA,CAAW,GAAG,GAAG,OAAO,KAAA;AACtC,EAAA,MAAM,GAAA,GAAM,GAAA;AACZ,EAAA,IACE,GAAA,CAAI,SAAS,GAAA,CAAI,cAAA,IACjB,YAAY,GAAA,CAAI,GAAA,CAAI,UAAU,CAAA,EAAG,OAAO,CAAA;AAExC,IAAA,OAAO,IAAA;AACT,EAAA,IACE,GAAA,CAAI,iBAAiB,MAAA,IACrB,WAAA,CAAY,IAAI,GAAA,CAAI,YAAY,GAAG,OAAO,CAAA;AAE1C,IAAA,OAAO,IAAA;AACT,EAAA,OAAO,KAAA;AACT;AAoBO,SAAS,WAAA,CAAY,GAAY,CAAA,EAAqB;AAC3D,EAAA,IAAI,MAAA,CAAO,EAAA,CAAG,CAAA,EAAG,CAAC,GAAG,OAAO,IAAA;AAM5B,EAAA,IACE,OAAO,MAAM,QAAA,IACb,OAAO,MAAM,QAAA,IACb,CAAA,KAAM,IAAA,IACN,CAAA,KAAM,IAAA,EACN;AACA,IAAA,OAAO,KAAA;AAAA,EACT;AAEA,EAAA,MAAM,QAAA,GAAW,KAAA,CAAM,OAAA,CAAQ,CAAC,CAAA;AAChC,EAAA,IAAI,QAAA,KAAa,KAAA,CAAM,OAAA,CAAQ,CAAC,GAAG,OAAO,KAAA;AAE1C,EAAA,IAAI,QAAA,EAAU;AACZ,IAAA,MAAM,IAAA,GAAO,CAAA;AACb,IAAA,IAAI,CAAA,CAAE,MAAA,KAAW,IAAA,CAAK,MAAA,EAAQ,OAAO,KAAA;AACrC,IAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,CAAA,CAAE,QAAQ,CAAA,EAAA,EAAK;AACjC,MAAA,IAAI,CAAC,YAAY,CAAA,CAAE,CAAC,GAAG,IAAA,CAAK,CAAC,CAAC,CAAA,EAAG,OAAO,KAAA;AAAA,IAC1C;AACA,IAAA,OAAO,IAAA;AAAA,EACT;AAKA,EAAA,MAAM,IAAA,GAAO,CAAA;AACb,EAAA,MAAM,IAAA,GAAO,CAAA;AACb,EAAA,MAAM,KAAA,GAAQ,MAAA,CAAO,IAAA,CAAK,IAAI,CAAA;AAC9B,EAAA,MAAM,KAAA,GAAQ,MAAA,CAAO,IAAA,CAAK,IAAI,CAAA;AAC9B,EAAA,IAAI,KAAA,CAAM,MAAA,KAAW,KAAA,CAAM,MAAA,EAAQ,OAAO,KAAA;AAC1C,EAAA,KAAA,MAAW,OAAO,KAAA,EAAO;AACvB,IAAA,IAAI,CAAC,MAAA,CAAO,MAAA,CAAO,IAAA,EAAM,GAAG,GAAG,OAAO,KAAA;AACtC,IAAA,IAAI,CAAC,YAAY,IAAA,CAAK,GAAG,GAAG,IAAA,CAAK,GAAG,CAAC,CAAA,EAAG,OAAO,KAAA;AAAA,EACjD;AACA,EAAA,OAAO,IAAA;AACT;ACleO,SAAS,gBAAA,CAOd,OAAA,EACA,IAAA,EACA,QAAA,EACM;AACN,EAAA,MAAM,EAAE,KAAA,EAAM,GAAI,MAAA,CAAO,SAAS,IAAI,CAAA;AACtC,EAAAC,MAAAA,CAAO,KAAK,CAAA,CAAE,OAAA,CAAQ,QAAQ,CAAA;AAChC;AAaO,SAAS,gBAAA,CAOd,OAAA,EACA,IAAA,EACA,GAAA,EACM;AACN,EAAA,MAAM,EAAE,IAAA,EAAK,GAAI,MAAA,CAAO,SAAS,IAAI,CAAA;AACrC,EAAAA,MAAAA,CAAO,IAAI,CAAA,CAAE,cAAA,CAAe,GAAG,CAAA;AACjC;AAaO,SAAS,iBAAA,CAOd,OAAA,EACA,IAAA,EACA,QAAA,EACM;AACN,EAAA,MAAM,EAAE,IAAA,EAAK,GAAI,MAAA,CAAO,SAAS,IAAI,CAAA;AACrC,EAAAA,MAAAA,CAAO,IAAI,CAAA,CAAE,OAAA,CAAQ,QAAQ,CAAA;AAC/B;AAWO,SAAS,gBAAA,CAOd,OAAA,EACA,IAAA,EACA,QAAA,EACM;AACN,EAAA,MAAM,EAAE,IAAA,EAAK,GAAI,MAAA,CAAO,SAAS,IAAI,CAAA;AACrC,EAAAA,MAAAA,CAAO,IAAI,CAAA,CAAE,OAAA,CAAQ,QAAQ,CAAA;AAC/B;AAkBO,SAAS,IAAA,CAOd,OAAA,EACA,MAAA,EACA,GAAA,EACA,GAAA,EAC4B;AAC5B,EAAA,MAAM,EAAE,KAAA,EAAO,IAAA,EAAK,GAAI,MAAA,CAAO,OAAA,EAAS,EAAE,IAAA,EAAM,CAAC,GAAG,CAAA,EAAG,GAAA,EAAK,QAAQ,CAAA;AACpE,EAAA,OAAO,CAAC,OAAO,IAAI,CAAA;AACrB;ACpEO,SAAS,WAAA,CAMd,SAAmC,GAAA,EAAyC;AAC5E,EAAA,OAAO;AAAA,IACL,IAAA,CAAK,QAAQ,GAAA,EAAK;AAChB,MAAA,MAAM,EAAE,KAAA,EAAO,IAAA,EAAK,GAAI,MAAA,CAAO,OAAA,EAAS,EAAE,IAAA,EAAM,CAAC,GAAG,CAAA,EAAG,GAAA,EAAK,QAAQ,CAAA;AACpE,MAAA,OAAO,CAAC,OAAO,IAAI,CAAA;AAAA,IACrB,CAAA;AAAA,IACA,gBAAA,CAAiB,MAAM,QAAA,EAAU;AAC/B,MAAA,MAAM,EAAE,OAAM,GAAI,MAAA,CAAO,SAAS,EAAE,GAAG,IAAA,EAAM,GAAA,EAAK,CAAA;AAClD,MAAAA,MAAAA,CAAO,KAAK,CAAA,CAAE,OAAA,CAAQ,QAAQ,CAAA;AAAA,IAChC,CAAA;AAAA,IACA,gBAAA,CAAiB,MAAM,GAAA,EAAK;AAC1B,MAAA,MAAM,EAAE,MAAK,GAAI,MAAA,CAAO,SAAS,EAAE,GAAG,IAAA,EAAM,GAAA,EAAK,CAAA;AACjD,MAAAA,MAAAA,CAAO,IAAI,CAAA,CAAE,cAAA,CAAe,GAAG,CAAA;AAAA,IACjC,CAAA;AAAA,IACA,iBAAA,CAAkB,MAAM,QAAA,EAAU;AAChC,MAAA,MAAM,EAAE,MAAK,GAAI,MAAA,CAAO,SAAS,EAAE,GAAG,IAAA,EAAM,GAAA,EAAK,CAAA;AACjD,MAAAA,MAAAA,CAAO,IAAI,CAAA,CAAE,OAAA,CAAQ,QAAQ,CAAA;AAAA,IAC/B,CAAA;AAAA,IACA,gBAAA,CAAiB,MAAM,QAAA,EAAU;AAC/B,MAAA,MAAM,EAAE,MAAK,GAAI,MAAA,CAAO,SAAS,EAAE,GAAG,IAAA,EAAM,GAAA,EAAK,CAAA;AACjD,MAAAA,MAAAA,CAAO,IAAI,CAAA,CAAE,OAAA,CAAQ,QAAQ,CAAA;AAAA,IAC/B,CAAA;AAAA,IACA,OAAO,IAAA,EAAM;AACX,MAAA,OAAO,OAAO,OAAA,EAAS,EAAE,GAAG,IAAA,EAAM,KAAK,CAAA;AAAA,IACzC;AAAA,GACF;AACF;;;ACjGO,SAAS,YAA2C,IAAA,EAEzC;AAChB,EAAA,MAAM,eAAe,IAAA,EAAM,YAAA;AAI3B,EAAA,MAAM,kBAAkB,MAAY;AAAA,EAAC,CAAA;AACrC,EAAA,MAAM,OAAA,GAAyB;AAAA,IAC7B,MAAM,SAAS,IAAA,EAAwB;AAAA,IAIvC,CAAA;AAAA,IACA,MAAM,aAAa,IAAA,EAAwB;AAAA,IAG3C,CAAA;AAAA,IACA,MAAM,IAAA,GAAsB;AAAA,IAE5B,CAAA;AAAA,IACA,MAAA,GAAwB;AAEtB,MAAA,OAAO,MAAA;AAAA,IACT,CAAA;AAAA,IACA,IAAA,GAAmB;AAGjB,MAAA,OAAO,IAAI,QAAW,MAAM;AAAA,MAAC,CAAC,CAAA;AAAA,IAChC,CAAA;AAAA,IACA,QAAA,GAAc;AAKZ,MAAA,OAAO,YAAA;AAAA,IACT,CAAA;AAAA,IACA,UAAU,SAAA,EAAmC;AAC3C,MAAA,OAAO,eAAA;AAAA,IACT,CAAA;AAAA,IACA,QAAQ,SAAA,EAAmD;AACzD,MAAA,OAAO,eAAA;AAAA,IACT,CAAA;AAAA,IACA,OAAO,QAAA,EAA0C;AAG/C,MAAA,OAAO,eAAA;AAAA,IACT,CAAA;AAAA,IACA,EAAA,CACE,OACA,QAAA,EACY;AAIZ,MAAA,OAAO,eAAA;AAAA,IACT,CAAA;AAAA,IACA,aAAA,CACE,OACA,SAAA,EACY;AACZ,MAAA,OAAO,eAAA;AAAA,IACT,CAAA;AAAA,IACA,QAAA,CAAY,OAAgB,MAAA,EAAiB;AAAA,IAE7C,CAAA;AAAA;AAAA;AAAA,IAGA,KAAA,EAAO,MAAA;AAAA,IACP,MAAM,IAAA,GAAsB;AAAA,IAE5B;AAAA,GACF;AAEA,EAAA,OAAA,CAAQ,KAAA,GAAQ,OAAA,CAAQ,OAAA,CAAQ,OAAO,CAAA;AACvC,EAAA,OAAO,OAAA;AACT;;;ACpCA,SAAS,eAAiC,GAAA,EAAoB;AAC5D,EAAA,MAAM,MAAkB,EAAC;AACzB,EAAA,KAAA,MAAW,GAAA,IAAO,MAAA,CAAO,IAAA,CAAK,GAAG,CAAA,EAAkB;AACjD,IAAA,MAAM,KAAA,GAAQ,IAAI,GAAG,CAAA;AACrB,IAAA,IAAI,UAAU,MAAA,EAAW;AACvB,MAAA,GAAA,CAAI,GAAG,CAAA,GAAI,KAAA;AAAA,IACb;AAAA,EACF;AACA,EAAA,OAAO,GAAA;AACT;AAeO,SAAS,aACd,QAAA,EACoB;AAIpB,EAAA,MAAM,MAAM,EAAC;AACb,EAAA,KAAA,MAAW,OAAO,MAAA,CAAO,IAAA;AAAA,IACvB;AAAA,GACF,EAAwC;AACtC,IAAA,MAAM,aAAA,GAAgB,SAAS,GAAG,CAAA;AAClC,IAAA,GAAA,CAAI,GAAa,CAAA,GAAI,CAAC,SAAA,KAAuB;AAG3C,MAAA,MAAM,gBAAA,GAAmB,SAAA,GAAY,cAAA,CAAe,SAAS,IAAI,EAAC;AAClE,MAAA,OAAO,EAAE,IAAA,EAAM,GAAA,EAAK,GAAG,aAAA,EAAe,GAAG,gBAAA,EAAiB;AAAA,IAC5D,CAAA;AAAA,EACF;AACA,EAAA,OAAO,GAAA;AACT","file":"index.js","sourcesContent":["// ---------------------------------------------------------------------------\n// `assertWrapperFaithful` — the shared conformance gate for the wrapper tier.\n//\n// A `withX` wrapper bolts a cross-cutting concern (telemetry, deadline,\n// resilience) onto a base machine as an AUTHORING property — the base's source\n// never hand-wires it. The DANGER is the runtime betrayal: hiding wrapper state\n// in a closure, or silently swallowing / retiming a base Cmd, so the wrapper's\n// behavior is invisible to the reducer + Msg log. This helper is the binary\n// betrayal-detector every wrapper inherits. Pass = TEA-faithful, ship it.\n// Fail = betrayal, does not merge.\n//\n// It asserts, by `replay` (which composes `init` + `update` only — the\n// conformance path):\n//\n// 1. **Base behavior byte-identical.** `replay(wrapped).state.base` deep-\n// equals `replay(base).state`. The wrapper never alters base behavior.\n// 2. **Slice AND wrapper Cmds reconstruct under any ambient clock/RNG.**\n// Replaying the SAME msgs under a DIFFERENT global wall-clock + RNG yields\n// a deep-equal wrapper slice AND a deep-equal `$x:*` wrapper-Cmd subset —\n// BOTH halves of the wrapper's output (state slice + emitted Cmds) are a\n// pure function of the Msg log, NOT of `Date.now()` / `Math.random()`.\n// (`update` cells never receive `ctx`, so an impure update read reaches for\n// the GLOBAL ambient; stubbing two distinct ambients and comparing catches\n// a folded wall-clock read that two back-to-back replays under one ambient\n// would hide.) The slice comparison alone has a blind spot: a clock/RNG\n// read folded into a wrapper Cmd PAYLOAD (not the slice) leaves the slice\n// identical, so we ALSO compare the `$x:*` Cmds across the two ambients.\n// Legit time enters only as Msg `at` payloads, identical across replays.\n// 3. **Every wrapper decision is in the log.** At least one wrapper-tagged\n// Cmd (`$x:*`) or Msg appears in the replayed Cmd stream whenever there is\n// a transition — the wrapper's decisions are visible, not off-ledger.\n// PLUS a PER-TRANSITION check: at each step the bare machine's step-i Cmds\n// are an ordered subsequence of the wrapped machine's step-i Cmds, so a\n// base Cmd deferred to a LATER transition (intra-window retiming a\n// flattened subsequence would miss) surfaces as a step-level failure.\n// 4. **Slice is JSON-round-trip equal.** `$x` survives\n// `JSON.parse(JSON.stringify(...))` — durable across DO eviction / reload,\n// never a closure.\n//\n// Strengthens invariant 9 (the testing surface is named and small) and is the\n// mechanical CI form of the wrapper-tier conformance gate.\n// ---------------------------------------------------------------------------\n\nimport { expect, vi } from \"vitest\";\nimport { type Cmd, type Machine, replay, type Sub } from \"../index\";\n\n/**\n * The composed Model shape every `withX` wrapper produces: the base machine's\n * state nested under `base`, and the wrapper's own NAMED, serializable slice\n * under a single `$`-prefixed key. This helper does not care WHICH `$`-key the\n * wrapper uses — it discovers it structurally (the one non-`base` field).\n */\nexport type WrapperModel<S> = {\n readonly base: S;\n} & { readonly [K: `$${string}`]: unknown };\n\n/** Options for the conformance replay. Mirrors `replay`'s second argument. */\nexport interface AssertWrapperFaithfulOpts<S, WM, BaseM, BaseCtx, WrapperCtx> {\n /** The Msg sequence to fold through BOTH machines. Use the base's Msgs. */\n readonly msgs: readonly BaseM[];\n /** Ctx for the WRAPPED machine (base ctx + wrapper ports). */\n readonly ctx: WrapperCtx;\n /** Ctx for the BARE base machine. Defaults to `ctx` when omitted. */\n readonly baseCtx?: BaseCtx;\n /** Optional pre-`init` snapshot of the COMPOSED Model (wrapped rehydrate). */\n readonly loaded?: WM | null;\n /**\n * Optional pre-`init` snapshot of the BARE base state. Supply when the\n * wrapped `loaded` is non-null so the base-equivalence check rehydrates the\n * base machine from the matching nested slice. Defaults to `loaded?.base`.\n */\n readonly baseLoaded?: S | null;\n /**\n * Opt-in relaxation for an INTERCEPTING wrapper (e.g. `withResilience`).\n *\n * DEFAULT (`undefined` / `false`) — the OBSERVE-ONLY contract that\n * `withTelemetry` and `withDeadline` satisfy: every bare base Cmd must appear\n * UNCHANGED, in order, as a subsequence of the wrapped Cmd stream (properties\n * 1 and 3b). The wrapper may APPEND its own Cmds but may NOT alter, drop, or\n * retime a base Cmd. This is the strict default and it is NOT weakened.\n *\n * WHEN SET — for a wrapper whose whole job is to INTERCEPT a chosen base Cmd\n * (retag it into a wrapper-owned carrier so the concern — cache / breaker /\n * rate-limit / retry — can gate it), a target base Cmd LEGITIMATELY does not\n * appear raw at the top level of the wrapped stream. The contract is still\n * **retag-and-re-emit, never swallow** (rule 2): the Cmd must be RECORDED, not\n * vanished. So when a bare base Cmd is ABSENT from the top-level wrapped\n * subsequence, this option accepts it IFF, in that SAME transition window, it\n * is recorded by the wrapper as one of:\n *\n * - the inner payload of a `$x:*` \"run\" carrier Cmd (`carrierCmdType` +\n * `innerField`) — the retag path; OR\n * - any other `$x:*` divergence Cmd whose `recordsField` deep-equals the\n * base Cmd — the gate/defer path (e.g. a \"fast-failed\" or \"deferred\"\n * record Cmd that carries the base Cmd it diverged on).\n *\n * A target base Cmd that is NEITHER present raw NOR recorded by any `$x:*`\n * Cmd in its window is a SWALLOW betrayal and still fails the gate.\n *\n * Provide the field names the carrier uses so the gate can unwrap it\n * structurally without hard-coding `withResilience`'s vocabulary.\n */\n readonly intercepting?: InterceptingOpt;\n}\n\n/**\n * Configures the intercepting relaxation (see\n * `AssertWrapperFaithfulOpts.intercepting`). All fields name the carrier /\n * divergence shape so the gate can structurally find a retagged base Cmd\n * inside a `$x:*` Cmd. The gate only EVER inspects `$x:*`-prefixed wrapped\n * Cmds for a recorded base Cmd — it never relaxes the check for a base Cmd\n * that appears under any non-wrapper Cmd type.\n */\nexport interface InterceptingOpt {\n /**\n * The `Cmd.type` of the wrapper's run-carrier (e.g. `\"$resilience:run\"`).\n * A bare base Cmd that is retagged appears as `carrier[innerField]`.\n */\n readonly carrierCmdType: string;\n /** The field on the carrier Cmd that holds the original base Cmd. */\n readonly innerField: string;\n /**\n * OPTIONAL field on ANY OTHER `$x:*` Cmd that records a base Cmd the wrapper\n * gated/deferred without running it (e.g. a fast-fail record). When present\n * on a `$x:*` Cmd and deep-equal to the missing base Cmd, it counts as a\n * recorded divergence. Omit when the wrapper only ever retags via the\n * carrier.\n */\n readonly recordsField?: string;\n}\n\n/**\n * Assert a `withX` wrapper is TEA-faithful. Replays the wrapped machine and the\n * bare base machine over the same Msgs and checks the four conformance\n * properties (see file header).\n *\n * @param base the bare base `Machine`.\n * @param makeWrapped a thunk that wraps `base` and returns the composed machine\n * (e.g. `() => withTelemetry(base, cfg)`). A thunk — not the wrapped value —\n * so a wrapper that registers process-scoped ports (`definePort`) is\n * constructed inside the assertion's control, and so the same betrayal-\n * detector reads as `assertWrapperFaithful(base, () => withX(base, cfg), ...)`\n * at every wrapper's test site.\n * @param opts the conformance replay options.\n */\nexport function assertWrapperFaithful<\n S,\n BaseM extends { type: string },\n BaseC extends Cmd,\n BaseU extends Sub,\n BaseCtx,\n WM extends WrapperModel<S>,\n WrapperM extends { type: string },\n WrapperC extends Cmd,\n WrapperU extends Sub,\n WrapperCtx,\n>(\n base: Machine<S, BaseM, BaseC, BaseU, BaseCtx>,\n makeWrapped: () => Machine<WM, WrapperM, WrapperC, WrapperU, WrapperCtx>,\n opts: AssertWrapperFaithfulOpts<S, WM, BaseM, BaseCtx, WrapperCtx>,\n): void {\n const baseCtx = (opts.baseCtx ?? (opts.ctx as unknown as BaseCtx)) as BaseCtx;\n const wrappedMsgs = opts.msgs as unknown as readonly WrapperM[];\n const baseLoaded =\n opts.baseLoaded ?? (opts.loaded != null ? (opts.loaded.base as S) : null);\n\n // Two DISTINCT global ambients (wall-clock + RNG). `replay` composes only\n // `init` + `update`, and `update` cells are `(state, msg)` — they never\n // receive `ctx`. So an impure update read reaches for the GLOBAL `Date.now()`\n // / `Math.random()`, not an injected clock. Running the two determinism\n // replays under DIFFERENT ambients turns a folded wall-clock read into an\n // OBSERVABLE slice divergence (property 2) — a single fixed ambient (two\n // back-to-back replays) would return the same ms both times and hide it.\n const A0 = { now: 1_000_000, rng: 0.123 };\n const A1 = { now: 9_876_543, rng: 0.987 };\n\n // The bare baseline, `wrappedA`, and every per-transition baseline (props 3/4)\n // run under the SAME fixed ambient A0 so their cmds/state are deterministic.\n const bare = underAmbient(A0, () =>\n replay(base, { msgs: opts.msgs, ctx: baseCtx, loaded: baseLoaded }),\n );\n const wrapped = underAmbient(A0, () => makeWrapped());\n const wrappedA = underAmbient(A0, () =>\n replay(wrapped, {\n msgs: wrappedMsgs,\n ctx: opts.ctx,\n loaded: opts.loaded ?? null,\n }),\n );\n // The second determinism replay runs under a DISTINCT ambient A1. A faithful\n // wrapper's slice (a pure function of the Msg log) is identical under A0 and\n // A1; an impure update that folds `Date.now()` / `Math.random()` diverges.\n const wrappedB = underAmbient(A1, () =>\n replay(makeWrapped(), {\n msgs: wrappedMsgs,\n ctx: opts.ctx,\n loaded: opts.loaded ?? null,\n }),\n );\n\n // --- Property 1: base behavior byte-identical -----------------------------\n // `state.base` after wrapping === state after running the base alone. The\n // wrapper observes; it does not alter base behavior.\n expect(wrappedA.state.base).toEqual(bare.state);\n // The base's OWN Cmds appear in the wrapped Cmd stream unchanged: every bare\n // Cmd is present, in order, among the wrapped Cmds (with wrapper Cmds\n // interleaved). We assert the bare Cmds are a subsequence of the wrapped ones.\n // For an INTERCEPTING wrapper (`opts.intercepting` set) a target base Cmd is\n // accepted when it appears RETAGGED inside a `$x:*` carrier / recorded by a\n // `$x:*` divergence Cmd instead of raw — retag, not swallow.\n expectCmdSubsequence(bare.cmds, wrappedA.cmds, undefined, opts.intercepting);\n\n // --- Property 2: slice reconstructs under any ambient clock/RNG -----------\n // Replaying the same Msgs under a DIFFERENT global ambient yields a deep-equal\n // wrapper SLICE — the slice is a pure function of the Msg log, never of\n // `Date.now()` / `Math.random()`. We compare the single `$`-slice (not the\n // whole composed state) because comparing whole state would FALSE-POSITIVE\n // when the BASE machine reads the clock — that is the base's behavior, not the\n // wrapper's fault, and the wrapper's faithfulness is precisely what its own\n // slice records.\n const sliceKey = wrapperSliceKey(wrappedA.state);\n expect(\n sliceOf(wrappedB.state, sliceKey),\n `wrapper slice changed under a different ambient clock/RNG → update is ` +\n `reading Date.now()/Math.random(); time must enter only as Msg \\`at\\` data`,\n ).toEqual(sliceOf(wrappedA.state, sliceKey));\n\n // ...AND the wrapper-tagged Cmds reconstruct under any ambient too. The slice\n // is only HALF the wrapper's output — the other half is its emitted Cmds. A\n // wall-clock / RNG read folded into a wrapper Cmd PAYLOAD (not the state\n // slice) escapes the slice comparison above: the slice stays identical while\n // the Cmd carries a leaked `Date.now()` / `Math.random()`. So mirror the\n // slice check on the `$x:*` subset of the replayed Cmd stream — a faithful\n // wrapper emits Cmds that are a pure function of the Msg log, identical under\n // A0 and A1. Only Msg `at` payloads (identical across both replays) may carry\n // time; if a wrapper Cmd diverges between ambients, the wrapper read the\n // ambient clock/RNG into a Cmd. (Base Cmds are EXCLUDED — a base machine that\n // reads the clock is the base's behavior, not the wrapper's, exactly as the\n // slice comparison excludes whole-state.)\n const wrapperFamily = `${sliceKey}:`;\n const wrapperCmdsA = wrappedA.cmds.filter((c) =>\n c.type.startsWith(wrapperFamily),\n );\n const wrapperCmdsB = wrappedB.cmds.filter((c) =>\n c.type.startsWith(wrapperFamily),\n );\n expect(\n wrapperCmdsB,\n `wrapper Cmds (\"${wrapperFamily}*\") changed under a different ambient ` +\n `clock/RNG → a wrapper Cmd PAYLOAD is reading Date.now()/Math.random(); ` +\n `wrapper Cmds must be a pure function of the Msg log, time entering only ` +\n `as Msg \\`at\\` data`,\n ).toEqual(wrapperCmdsA);\n\n // --- Property 3: every wrapper decision appears as an XMsg/XCmd in the log -\n // The composed Model's wrapper slice is the single non-`base` `$`-key. We\n // discover its key family (`$telemetry` → `$telemetry:*`) and require that,\n // whenever there is at least one transition, the replayed Cmd stream carries\n // at least one wrapper-tagged Cmd. (Wrappers whose decisions are Msgs route\n // them through the SAME log; for a Cmd-emitting wrapper like withTelemetry the\n // decision is the `$x:emit` Cmd.) A wrapper that performed its work off-ledger\n // would have an EMPTY wrapper-Cmd set here despite real transitions — the\n // exact betrayal this gate exists to catch.\n if (opts.msgs.length > 0) {\n expect(\n wrapperCmdsA.length,\n `expected at least one \"${wrapperFamily}*\" wrapper-decision Cmd in the replay log ` +\n `(found none across ${opts.msgs.length} transition(s)) — a wrapper whose ` +\n `decisions are invisible to the Msg/Cmd log has betrayed total visibility`,\n ).toBeGreaterThan(0);\n }\n\n // --- Property 3b: per-transition Cmd subsequence --------------------------\n // The flattened subsequence (property 1) misses INTRA-WINDOW retiming — a\n // base Cmd that a saga-style wrapper DEFERS to a later transition still drains\n // within the replay, so the flattened bare-Cmd set stays an ordered\n // subsequence and slips through. Pin it per step: for each transition i, the\n // bare machine's step-i Cmds must be an ordered subsequence of the wrapped\n // machine's step-i Cmds. A deferred base Cmd is then visible as a step-i\n // subsequence failure (it shows up at i+1 in the wrapped run but at i in the\n // bare run). Prefix replays run under the fixed ambient A0 for determinism.\n for (let i = 0; i < opts.msgs.length; i++) {\n const bareStep = stepCmds(\n () =>\n underAmbient(A0, () =>\n replay(base, {\n msgs: opts.msgs.slice(0, i),\n ctx: baseCtx,\n loaded: baseLoaded,\n }),\n ).cmds,\n () =>\n underAmbient(A0, () =>\n replay(base, {\n msgs: opts.msgs.slice(0, i + 1),\n ctx: baseCtx,\n loaded: baseLoaded,\n }),\n ).cmds,\n );\n const wrappedStep = stepCmds(\n () =>\n underAmbient(A0, () =>\n replay(wrapped, {\n msgs: wrappedMsgs.slice(0, i),\n ctx: opts.ctx,\n loaded: opts.loaded ?? null,\n }),\n ).cmds,\n () =>\n underAmbient(A0, () =>\n replay(wrapped, {\n msgs: wrappedMsgs.slice(0, i + 1),\n ctx: opts.ctx,\n loaded: opts.loaded ?? null,\n }),\n ).cmds,\n );\n expectCmdSubsequence(bareStep, wrappedStep, i, opts.intercepting);\n }\n\n // --- Property 4: slice is JSON-round-trip equal ---------------------------\n // The wrapper slice survives JSON.parse(JSON.stringify(...)) — durable across\n // DO eviction / reload, never a closure.\n const slice = sliceOf(wrappedA.state, sliceKey);\n expect(JSON.parse(JSON.stringify(slice))).toEqual(slice);\n}\n\n/**\n * Run `fn` with the GLOBAL `Date.now` and `Math.random` stubbed to fixed\n * values, restoring them in a `finally` so the stub never leaks across\n * assertions or tests. `update` cells receive no `ctx`, so an impure read\n * inside one resolves to these globals — the lever this gate pulls to make a\n * folded wall-clock / RNG read observable in the wrapper slice.\n */\nfunction underAmbient<T>(\n ambient: { readonly now: number; readonly rng: number },\n fn: () => T,\n): T {\n const nowSpy = vi.spyOn(Date, \"now\").mockReturnValue(ambient.now);\n const rngSpy = vi.spyOn(Math, \"random\").mockReturnValue(ambient.rng);\n try {\n return fn();\n } finally {\n nowSpy.mockRestore();\n rngSpy.mockRestore();\n }\n}\n\n/** The wrapper slice value (the single `$`-key) on a composed Model. */\nfunction sliceOf(model: object, sliceKey: string): unknown {\n return (model as Record<string, unknown>)[sliceKey];\n}\n\n/**\n * The Cmds a single transition `i` emitted: the suffix of the cmds after\n * `msgs.slice(0, i+1)` past the cmds after `msgs.slice(0, i)`. Cmds accumulate\n * in order across `replay`, so the longer run is a prefix-extension of the\n * shorter one and the suffix is exactly step `i`'s contribution.\n */\nfunction stepCmds(\n prefixCmds: () => readonly Cmd[],\n throughCmds: () => readonly Cmd[],\n): readonly Cmd[] {\n const before = prefixCmds();\n const after = throughCmds();\n return after.slice(before.length);\n}\n\n/**\n * The wrapper slice key on a composed Model — the single own enumerable field\n * whose name starts with `$`. Throws if zero or more than one is found, because\n * the wrapper-tier contract is exactly one named `$`-slice per wrapper.\n */\nfunction wrapperSliceKey(model: object): string {\n const dollarKeys = Object.keys(model).filter((k) => k.startsWith(\"$\"));\n if (dollarKeys.length !== 1) {\n throw new Error(\n `assertWrapperFaithful: expected exactly one \"$\"-prefixed wrapper slice ` +\n `on the composed Model, found ${dollarKeys.length} (${dollarKeys.join(\", \") || \"none\"}). ` +\n `Every withX wrapper owns exactly one named slice (e.g. \"$telemetry\").`,\n );\n }\n // biome-ignore lint/style/noNonNullAssertion: length checked to be exactly 1 above\n return dollarKeys[0]!;\n}\n\n/**\n * Assert `needle` is an (order-preserving) subsequence of `haystack` by deep\n * equality — every base Cmd appears, in order, somewhere in the wrapped Cmd\n * stream (wrapper Cmds may be interleaved). This is the \"base Cmds pass through\n * UNCHANGED\" check that tolerates the wrapper's own appended Cmds without\n * requiring an exact-equal stream. When `step` is supplied the message names\n * the transition index, so a per-transition retiming failure points at the\n * exact step a base Cmd was deferred away from.\n *\n * `intercepting` (default `undefined` → the strict OBSERVE-ONLY check above)\n * relaxes the match for a base Cmd that an INTERCEPTING wrapper retags: when a\n * needle Cmd is NOT found raw in `haystack`, it is accepted IFF some `$x:*`\n * wrapper Cmd in `haystack` RECORDS it — as the carrier's `innerField` payload\n * (retag) or another `$x:*` Cmd's `recordsField` (recorded gate/defer). The\n * relaxation ONLY ever looks inside `$x:*` Cmds, so a base Cmd that vanished\n * with no `$x:*` record still fails. The order check is preserved: the carrier\n * / divergence Cmd must come at or after the cursor, exactly like a raw match.\n */\nfunction expectCmdSubsequence(\n needle: readonly Cmd[],\n haystack: readonly Cmd[],\n step?: number,\n intercepting?: InterceptingOpt,\n): void {\n let i = 0;\n for (const cmd of haystack) {\n if (i >= needle.length) break;\n if (__deepEqual(needle[i], cmd)) {\n i++;\n continue;\n }\n // Intercepting relaxation: the base Cmd at the cursor may legitimately be\n // absent raw because the wrapper RETAGGED it. Accept this haystack Cmd as\n // the matching slot IFF it is a `$x:*` wrapper Cmd that records `needle[i]`.\n if (\n intercepting !== undefined &&\n recordsBaseCmd(cmd, needle[i] as Cmd, intercepting)\n ) {\n i++;\n }\n }\n const where =\n step === undefined\n ? \"the wrapped Cmd stream\"\n : `the wrapped Cmd stream AT TRANSITION ${step} (a base Cmd deferred to a ` +\n `later transition is an intra-window retiming betrayal)`;\n const swallowHint =\n intercepting === undefined\n ? \"a missing/rewritten/retimed base Cmd is a swallow betrayal\"\n : `an intercepting wrapper must RETAG each target base Cmd into a ` +\n `\"${intercepting.carrierCmdType}\" carrier (inner field ` +\n `\"${intercepting.innerField}\") or record it on a \"$*:\" divergence Cmd ` +\n `— a base Cmd neither present raw NOR recorded by any \"$*:\" Cmd is a ` +\n `swallow betrayal`;\n expect(\n i,\n `expected every base Cmd to pass through unchanged (matched ${i} of ` +\n `${needle.length} base Cmds as an ordered subsequence of ${where}) — ` +\n `${swallowHint}`,\n ).toBe(needle.length);\n}\n\n/**\n * Does the wrapped Cmd `cmd` RECORD the base Cmd `baseCmd` under the\n * intercepting contract? True iff `cmd` is a `$`-prefixed wrapper Cmd AND\n * either:\n * - `cmd.type === carrierCmdType` and `cmd[innerField]` deep-equals\n * `baseCmd` (the retag carrier), OR\n * - `recordsField` is configured and `cmd[recordsField]` deep-equals\n * `baseCmd` (a recorded gate/defer divergence on any `$x:*` Cmd).\n *\n * The `$`-prefix guard is load-bearing: the relaxation must NEVER accept a base\n * Cmd hidden inside a NON-wrapper Cmd — that would let a wrapper launder a\n * swallowed base Cmd through an arbitrary re-emit. Only the wrapper's own\n * `$x:*` family may carry a retagged base Cmd.\n */\nfunction recordsBaseCmd(cmd: Cmd, baseCmd: Cmd, opt: InterceptingOpt): boolean {\n if (!cmd.type.startsWith(\"$\")) return false;\n const rec = cmd as unknown as Record<string, unknown>;\n if (\n cmd.type === opt.carrierCmdType &&\n __deepEqual(rec[opt.innerField], baseCmd)\n )\n return true;\n if (\n opt.recordsField !== undefined &&\n __deepEqual(rec[opt.recordsField], baseCmd)\n )\n return true;\n return false;\n}\n\n/**\n * Structural deep equality for plain Cmd data.\n *\n * NOT `JSON.stringify` equality: `JSON.stringify` DROPS undefined-valued fields,\n * so `{ type: \"x\" }` would falsely equal `{ type: \"x\", foo: undefined }` and the\n * gate could accept a wrapper that smuggles an extra (undefined) field into a\n * \"byte-identical\" base Cmd or a retag carrier. This walk distinguishes an\n * ABSENT key from an `undefined`-VALUED key by comparing the full own-enumerable\n * key SET (not just the defined values) in both directions, then recursing.\n *\n * Cmds are plain data — primitives, `null`, arrays, and plain objects — so this\n * intentionally does not model `Date`, `Map`, `Set`, cycles, or class instances.\n *\n * Exported under the `__` test-only convention so the conformance suite can\n * pin the absent-vs-undefined distinction directly. Not part of the public API.\n *\n * @internal test-only\n */\nexport function __deepEqual(a: unknown, b: unknown): boolean {\n if (Object.is(a, b)) return true;\n\n // Past the identity check, equality requires same kind on both sides. Anything\n // non-object (primitive, function, symbol) that wasn't `Object.is`-equal above\n // is unequal. `null` is `typeof \"object\"` but only equals `null` (caught by\n // `Object.is`), so screen it out explicitly.\n if (\n typeof a !== \"object\" ||\n typeof b !== \"object\" ||\n a === null ||\n b === null\n ) {\n return false;\n }\n\n const aIsArray = Array.isArray(a);\n if (aIsArray !== Array.isArray(b)) return false;\n\n if (aIsArray) {\n const bArr = b as unknown[];\n if (a.length !== bArr.length) return false;\n for (let i = 0; i < a.length; i++) {\n if (!__deepEqual(a[i], bArr[i])) return false;\n }\n return true;\n }\n\n // Plain objects: compare the own-enumerable key SET first so a key present on\n // one side with an `undefined` value (and absent on the other) is caught —\n // the betrayal `JSON.stringify` hid. Then recurse on every value.\n const aRec = a as Record<string, unknown>;\n const bRec = b as Record<string, unknown>;\n const aKeys = Object.keys(aRec);\n const bKeys = Object.keys(bRec);\n if (aKeys.length !== bKeys.length) return false;\n for (const key of aKeys) {\n if (!Object.hasOwn(bRec, key)) return false;\n if (!__deepEqual(aRec[key], bRec[key])) return false;\n }\n return true;\n}\n","// ---------------------------------------------------------------------------\n// Test-side assertions over `replay`. Each helper composes the replay call\n// with a single `expect(...)` so consumers stop hand-rolling the 5-line\n// \"destructure { state, cmds } → expect.toBe / toEqual / toContainEqual\"\n// boilerplate at every test site.\n//\n// Each helper is a thin wrapper that:\n// 1. Calls `replay(machine, opts)` once.\n// 2. Pipes the result into a single vitest `expect(...)` matcher.\n//\n// All helpers return `void` — assertions throw on failure (vitest contract).\n// No mutation of the machine or opts. Pure test ergonomics.\n//\n// Type-inference note: each helper anchors its generic parameters to the\n// `machine` argument. The `expected` (and `opts.msgs`) arguments are wrapped\n// in `NoInfer<...>` so they do NOT participate in inference — otherwise a\n// `[]`-shaped `expected` would force `C = never` and reject a machine whose\n// real Cmd union is non-empty. Anchoring inference to the machine is the\n// same discipline `replay` uses (it has nothing but the machine and opts).\n//\n// Strengthens invariant 9 (the testing surface is named, small, and forks\n// nothing of the substrate's purity guarantees).\n// ---------------------------------------------------------------------------\n\nimport { expect } from \"vitest\";\nimport { type Cmd, type Machine, replay, type Sub } from \"../index\";\n\n// `NoInfer<T>` is the built-in TypeScript 5.4+ intrinsic that blocks\n// inference from a generic parameter at a position. We use it on `expected`\n// and `opts` so inference flows purely FROM the machine — `[]`-shaped\n// `expected` no longer drags `C` down to `never` and rejects the machine.\n\n/**\n * Shared options shape for every test assertion below. Mirrors the\n * second argument of `replay(machine, opts)` — `msgs` is the sequence to\n * fold over (may be empty for init-only assertions), `ctx` is the\n * substrate-required ctx value, `loaded` is the optional pre-`init`\n * snapshot (omit / pass `null` for fresh boot).\n */\nexport interface ReplayOpts<S, M, Ctx> {\n readonly msgs: readonly M[];\n readonly ctx: Ctx;\n readonly loaded?: S | null;\n}\n\n/**\n * Assert the final state after replaying `opts.msgs` equals `expected`.\n * Replaces the boilerplate:\n *\n * const { state } = replay(machine, opts);\n * expect(state).toEqual(expected);\n *\n * Uses `toEqual` (deep structural). For type-narrowing assertions that\n * branch on `state.type` afterwards, fall back to plain `replay` —\n * `expectFinalState` is the \"exact match\" tool.\n */\nexport function expectFinalState<\n S,\n M extends { type: string },\n C extends Cmd,\n U extends Sub,\n Ctx,\n>(\n machine: Machine<S, M, C, U, Ctx>,\n opts: NoInfer<ReplayOpts<S, M, Ctx>>,\n expected: NoInfer<S>,\n): void {\n const { state } = replay(machine, opts);\n expect(state).toEqual(expected);\n}\n\n/**\n * Assert that `cmd` appears at least once in the cmds array produced by\n * replaying `opts.msgs`. Uses `toContainEqual` (deep structural). Order\n * and surrounding cmds are not asserted — use `expectCmdSequence` for\n * exact ordered match.\n *\n * Replaces:\n *\n * const { cmds } = replay(machine, opts);\n * expect(cmds).toContainEqual(cmd);\n */\nexport function expectCmdEmitted<\n S,\n M extends { type: string },\n C extends Cmd,\n U extends Sub,\n Ctx,\n>(\n machine: Machine<S, M, C, U, Ctx>,\n opts: NoInfer<ReplayOpts<S, M, Ctx>>,\n cmd: NoInfer<C>,\n): void {\n const { cmds } = replay(machine, opts);\n expect(cmds).toContainEqual(cmd);\n}\n\n/**\n * Assert the exact ordered sequence of cmds emitted by replaying\n * `opts.msgs`. Uses `toEqual` against the full cmds array — extra cmds\n * fail the assertion, missing cmds fail the assertion, wrong order\n * fails the assertion.\n *\n * Replaces:\n *\n * const { cmds } = replay(machine, opts);\n * expect(cmds).toEqual(expected);\n */\nexport function expectCmdSequence<\n S,\n M extends { type: string },\n C extends Cmd,\n U extends Sub,\n Ctx,\n>(\n machine: Machine<S, M, C, U, Ctx>,\n opts: NoInfer<ReplayOpts<S, M, Ctx>>,\n expected: NoInfer<readonly C[]>,\n): void {\n const { cmds } = replay(machine, opts);\n expect(cmds).toEqual(expected);\n}\n\n/**\n * Assert the exact set of subs desired at the final state after\n * replaying `opts.msgs`. Uses `toEqual` against the full subs array.\n *\n * Replaces:\n *\n * const { subs } = replay(machine, opts);\n * expect(subs).toEqual(expected);\n */\nexport function expectActiveSubs<\n S,\n M extends { type: string },\n C extends Cmd,\n U extends Sub,\n Ctx,\n>(\n machine: Machine<S, M, C, U, Ctx>,\n opts: NoInfer<ReplayOpts<S, M, Ctx>>,\n expected: NoInfer<readonly U[]>,\n): void {\n const { subs } = replay(machine, opts);\n expect(subs).toEqual(expected);\n}\n\n/**\n * Single-msg step helper — feeds `loaded → msg → next state + cmds emitted by\n * that msg`. Returns a readonly tuple in the shape consumers used before the\n * substrate (`[state, cmds]`), so existing tests that destructure with\n * `[next, cmds]` can swap `reduce(state, msg)` for `step(machine, state, msg,\n * ctx)` without changing the assertion code.\n *\n * Internally a `replay` call with `loaded: prevState` and a single-element\n * `msgs` array. The cmds returned are exactly the cmds emitted by THIS msg\n * (init emits no cmds when `loaded` is provided, so the slice is the entire\n * array).\n *\n * Use when consumers want to walk a machine one msg at a time and assert per\n * step. For one-shot \"final state after N msgs\" assertions, prefer\n * `expectFinalState`.\n */\nexport function step<\n S,\n M extends { type: string },\n C extends Cmd,\n U extends Sub,\n Ctx,\n>(\n machine: Machine<S, M, C, U, Ctx>,\n loaded: NoInfer<S>,\n msg: NoInfer<M>,\n ctx: NoInfer<Ctx>,\n): readonly [S, readonly C[]] {\n const { state, cmds } = replay(machine, { msgs: [msg], ctx, loaded });\n return [state, cmds] as const;\n}\n","// ---------------------------------------------------------------------------\n// `bindMachine(machine, ctx)` — bound test harness over the free-function\n// assertions in `./assertions.ts`. Closes over the `machine` and `ctx` once\n// so per-test callsites stop repeating both at every invocation.\n//\n// Why this exists: every consumer test file ends up hand-rolling a local\n// two-arg `step(state, msg)` wrapper around `step(machine, state, msg, ctx)`,\n// and the four `expect*` assertions repeat `machine` + `ctx` at every\n// callsite. The bound form pulls both into one place and gives every\n// per-test call its tightest possible shape (2-arg max).\n//\n// `bindMachine` is built ON TOP of the free helpers — it does not replace\n// them. Both forms coexist; pick the one that fits the call site:\n//\n// - Free form → one-off call inside a helper that isn't bound to one\n// machine (e.g. cross-machine integration test).\n// - Bound form → a `describe` block runs many assertions against one\n// machine + ctx. The bound form is the common case.\n//\n// Strengthens invariant 9 (the testing surface is named, small, and forks\n// nothing of the substrate's purity guarantees).\n// ---------------------------------------------------------------------------\n\nimport { expect } from \"vitest\";\nimport { type Cmd, type Machine, replay, type Sub } from \"../index\";\n\n/**\n * Replay options shape used by every bound method. `ctx` is captured by\n * `bindMachine`; the per-call opts only need `msgs` and an optional\n * `loaded` snapshot. (Compare `ReplayOpts` in `./assertions.ts`, which\n * still takes `ctx` because the free helpers cannot capture it.)\n */\ninterface BoundOpts<S, M> {\n readonly msgs: readonly M[];\n readonly loaded?: S | null;\n}\n\n/**\n * The bound testing surface. Every method is 2-arg max — `ctx` and\n * `machine` are captured by `bindMachine`.\n *\n * `NoInfer<...>` on `expected` / `cmd` arguments mirrors the discipline\n * the free helpers use (see `./assertions.ts`): inference flows purely\n * FROM the machine, so a `[]`-shaped expected does not drag `C` down to\n * `never` and reject the machine.\n */\nexport interface BoundMachine<\n S,\n M extends { type: string },\n C extends Cmd,\n U extends Sub,\n // `Ctx` is a phantom-style parameter — present for consumers that want to\n // spell `BoundMachine<S, M, C, U, Ctx>` explicitly, mirroring `Machine`.\n // biome-ignore lint/correctness/noUnusedVariables: phantom type param (see above)\n Ctx,\n> {\n /**\n * Single-msg step — feeds `loaded → msg → [next state, cmds emitted by\n * that msg]`. Mirrors the free `step(machine, loaded, msg, ctx)`.\n */\n step(loaded: S, msg: M): readonly [S, readonly C[]];\n /**\n * Assert the final state after replaying `opts.msgs` deep-equals\n * `expected`. Mirrors the free `expectFinalState(machine, opts, expected)`.\n */\n expectFinalState(opts: BoundOpts<S, M>, expected: NoInfer<S>): void;\n /**\n * Assert `cmd` appears at least once in the emitted cmd list. Order is\n * not asserted. Mirrors the free `expectCmdEmitted(machine, opts, cmd)`.\n */\n expectCmdEmitted(opts: BoundOpts<S, M>, cmd: NoInfer<C>): void;\n /**\n * Assert the exact ordered sequence of cmds emitted by replaying\n * `opts.msgs`. Mirrors the free `expectCmdSequence(machine, opts, expected)`.\n */\n expectCmdSequence(\n opts: BoundOpts<S, M>,\n expected: readonly NoInfer<C>[],\n ): void;\n /**\n * Assert the exact set of subs desired at the final state. Mirrors the\n * free `expectActiveSubs(machine, opts, expected)`.\n */\n expectActiveSubs(\n opts: BoundOpts<S, M>,\n expected: readonly NoInfer<U>[],\n ): void;\n /**\n * Bound `replay` — returns `{ state, cmds, subs }` for the given opts.\n * Use for the \"narrow-then-assert\" pattern where the test inspects a\n * specific field after `state.type === \"...\"` discrimination.\n */\n replay(opts: BoundOpts<S, M>): {\n state: S;\n cmds: readonly C[];\n subs: readonly U[];\n };\n}\n\n/**\n * Bind a machine + ctx into a small bag of 2-arg helpers. Each method\n * delegates to `replay(machine, { ...opts, ctx })` and pipes the result\n * through a single `expect(...)` matcher (or returns the raw tuple, for\n * `step` and `replay`).\n *\n * Use one bind per `describe` block (or per file) when many assertions\n * share the same machine + ctx. For one-off cross-machine calls, stick\n * with the free helpers from `./assertions.ts`.\n */\nexport function bindMachine<\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>, ctx: Ctx): BoundMachine<S, M, C, U, Ctx> {\n return {\n step(loaded, msg) {\n const { state, cmds } = replay(machine, { msgs: [msg], ctx, loaded });\n return [state, cmds] as const;\n },\n expectFinalState(opts, expected) {\n const { state } = replay(machine, { ...opts, ctx });\n expect(state).toEqual(expected);\n },\n expectCmdEmitted(opts, cmd) {\n const { cmds } = replay(machine, { ...opts, ctx });\n expect(cmds).toContainEqual(cmd);\n },\n expectCmdSequence(opts, expected) {\n const { cmds } = replay(machine, { ...opts, ctx });\n expect(cmds).toEqual(expected);\n },\n expectActiveSubs(opts, expected) {\n const { subs } = replay(machine, { ...opts, ctx });\n expect(subs).toEqual(expected);\n },\n replay(opts) {\n return replay(machine, { ...opts, ctx });\n },\n };\n}\n","// ---------------------------------------------------------------------------\n// noopRuntime — a Runtime<S, M>-shaped value whose every method is inert.\n//\n// The motivating callsite: a machine's `ctx` requires a sibling Runtime<S,\n// M> reference (e.g. the queue runtime in extension/lib/queue-machine\n// reaches into an `auditRuntime: Runtime<...>` slot on its ctx so its\n// interpret handlers can dispatch into the audit machine). When the test\n// replays such a machine, `replay` never actually invokes `interpret`, so\n// the runtime reference is never dereferenced — but the type system\n// still requires a value of `Runtime<S, M>` shape at the ctx slot. Tests\n// today work around this with a hand-rolled 12-line NOOP_AUDIT_RUNTIME\n// const cast through `as unknown as Runtime<...>`.\n//\n// `noopRuntime<S, M>()` is that hand-roll, named once. It returns a real\n// Runtime<S, M> value whose every method is a structurally-correct no-op:\n//\n// - `dispatch`: resolves immediately, never advances state.\n// - `getState`: returns the optional `initialState` argument (or a\n// deliberate `undefined` cast as S — the function lies about its\n// return type when no initial state is provided; this is the trade-off\n// we accept for an ergonomic test substrate).\n// - `subscribe` / `observe` / `subscribePort`: register the listener,\n// return a no-op unsubscribe. The listener is never fired because the\n// state never changes.\n// - `emitPort`: no-op.\n// - `ready`: a Promise that resolves to the no-op runtime itself (the #45\n// `ready: Promise<Runtime<S, M>>` shape — already booted, nothing to wait\n// for).\n// - `stop`: resolved Promise<void>.\n//\n// Strengthens invariant 9 (the testing surface is named and small).\n// ---------------------------------------------------------------------------\n\nimport type { Port, Runtime } from \"../index\";\n\n/**\n * Construct an inert `Runtime<S, M>` value. Use at ctx sites that require\n * a Runtime reference but where the test never invokes `interpret` (so\n * the runtime is never dereferenced in practice).\n *\n * @param opts.initialState Optional value returned by `getState()`. Omit\n * when the test never calls `getState()` on the no-op runtime — most\n * tests only need the type-level slot filled.\n */\nexport function noopRuntime<S, M extends { type: string }>(opts?: {\n initialState?: S;\n}): Runtime<S, M> {\n const initialState = opts?.initialState;\n // Single source of truth for the \"do-nothing\" unsubscribe so multiple\n // subscribe call sites share the same function instance — purely cosmetic\n // (the function would be a no-op either way).\n const noopUnsubscribe = (): void => {};\n const runtime: Runtime<S, M> = {\n async dispatch(_msg: M): Promise<void> {\n // Resolves immediately. State never advances; listeners never fire.\n // Quiescent by default (#50), but the no-op has no follow-up chain to\n // drain, so single-step and run-to-quiescence collapse to the same no-op.\n },\n async dispatchOnce(_msg: M): Promise<void> {\n // Single-step sibling of `dispatch` (#50). Same no-op for the no-op\n // runtime — there is no tail to leave in flight.\n },\n async idle(): Promise<void> {\n // No tail to drain — the no-op runtime never enqueues a follow-up.\n },\n result(): S | undefined {\n // The no-op runtime never advances, so it is never terminal (#46).\n return undefined;\n },\n done(): Promise<S> {\n // Never terminal → the awaitable never settles, matching `run()` with no\n // `terminal` predicate. Returned as a never-resolving promise.\n return new Promise<S>(() => {});\n },\n getState(): S {\n // Lie if no initial state was provided. The caller's contract is\n // \"you accept the lie because you know replay never invokes\n // interpret and therefore never reads this.\" When the caller DOES\n // pass an initialState the lie collapses to truth.\n return initialState as S;\n },\n subscribe(_listener: () => void): () => void {\n return noopUnsubscribe;\n },\n observe(_observer: (msg: M, state: S) => void): () => void {\n return noopUnsubscribe;\n },\n onBoot(_handler: (state: S) => void): () => void {\n // Boot never fires on the inert runtime — it is \"already booted\" with no\n // initial fanout, so the handler is registered and never called (#47).\n return noopUnsubscribe;\n },\n on<K extends never>(\n _type: K,\n _handler: (event: never) => void,\n ): () => void {\n // The no-op runtime's event surface is `never` (no `events` projector),\n // so `on` is structurally present but uncallable — there is no `K` to\n // pass. The body is unreachable; it returns the shared no-op unsubscribe.\n return noopUnsubscribe;\n },\n subscribePort<T>(\n _port: Port<T>,\n _listener: (value: T) => void,\n ): () => void {\n return noopUnsubscribe;\n },\n emitPort<T>(_port: Port<T>, _value: T): void {\n // No-op. No subscribers to fan out to; nothing to do.\n },\n // Assigned just below — `ready` must resolve to `runtime` itself, which\n // can't be referenced inside its own object literal.\n ready: undefined as unknown as Promise<Runtime<S, M>>,\n async stop(): Promise<void> {\n // Resolves immediately. No in-flight tail to drain, no subs to clean.\n },\n };\n // Already \"booted\" — `ready` hands back the same runtime synchronously.\n runtime.ready = Promise.resolve(runtime);\n return runtime;\n}\n","// ---------------------------------------------------------------------------\n// stateFactory — typed phase-constructor API for a discriminated-union State.\n//\n// The motivating callsite: every reducer test today opens with ~60–80 lines\n// of hand-rolled factory functions, one per phase, each one's defaults\n// duplicated across files:\n//\n// function idleState() { return { type: \"idle\" }; }\n// function creatingWindowState(overrides?: Partial<...>) {\n// return { type: \"creating_window\", auditId: ..., wsUrl: ..., ...overrides };\n// }\n// ...\n//\n// `stateFactory<BackgroundState>({...})` collapses the whole block to one\n// declaration: pass each phase's REQUIRED defaults once (the type system\n// enforces every phase has an entry), and the helper builds an API of\n// `factory.idle()`, `factory.creating_window({...})`, etc.\n//\n// Two type-level guarantees:\n//\n// 1. Every phase has a defaults entry — `StateFactoryDefaults<S>` is a\n// mapped type keyed by every `S[\"type\"]`. Missing an entry is a\n// compile error (the @ts-expect-error in testing.spec.ts pins this).\n//\n// 2. Each phase's defaults match that phase's required shape minus the\n// `type` discriminator. Passing wrong fields fails to compile.\n//\n// The runtime is pure data:\n//\n// api[k] = (overrides) => ({\n// type: k,\n// ...defaults[k],\n// ...stripUndefined(overrides ?? {}),\n// })\n//\n// The `stripUndefined` pass is what preserves `exactOptionalPropertyTypes`\n// assignability — without it, calling `factory.creating_window({\n// queueItemId: undefined })` would land an explicit `queueItemId: undefined`\n// on the object, which is NOT assignable to `queueItemId?: string` under\n// `exactOptionalPropertyTypes`. Stripping undefineds before spread keeps\n// the boundary clean.\n//\n// `stripUndefined` is inlined (not imported from a shared stdlib) so this\n// package can stay dep-free of stdlib.\n// ---------------------------------------------------------------------------\n\n/** A factory-constructed phase: `S` narrowed to the arm whose discriminator equals `K`. */\ntype StateOf<S, K> = Extract<S, { type: K }>;\n\n/** A factory-constructed phase with the `type` discriminator removed — the \"fields the caller cares about\" half. */\ntype RequiredFields<T> = Omit<T, \"type\">;\n\n/**\n * Defaults shape passed to `stateFactory`. Mapped type over the discriminant\n * set: every phase MUST have an entry, and that entry MUST match the phase's\n * required fields minus the `type` discriminator.\n */\nexport type StateFactoryDefaults<S extends { type: string }> = {\n readonly [K in S[\"type\"]]: RequiredFields<StateOf<S, K>>;\n};\n\n/**\n * The API returned by `stateFactory`. One method per phase; calling it with\n * no args returns the default snapshot, calling it with a partial overrides\n * object merges those overrides in (with undefineds stripped — see\n * stripUndefined below).\n */\nexport type StateFactoryAPI<S extends { type: string }> = {\n readonly [K in S[\"type\"]]: (\n overrides?: Partial<RequiredFields<StateOf<S, K>>>,\n ) => StateOf<S, K>;\n};\n\n/**\n * Strip own-enumerable properties whose value is `undefined`. Used inside\n * the factory's spread chain so callers can pass `{ queueItemId: undefined }`\n * without the explicit-undefined landing on the result object (which would\n * fail to assign to a `queueItemId?: string` field under\n * `exactOptionalPropertyTypes`).\n *\n * Inlined (not imported from `a shared stdlib`) so `@demlik/tea/testing` can stay\n * dependency-free of stdlib. The 8-line cost of duplication is cheaper than\n * a package-graph edge.\n */\nfunction stripUndefined<T extends object>(obj: T): Partial<T> {\n const out: Partial<T> = {};\n for (const key of Object.keys(obj) as (keyof T)[]) {\n const value = obj[key];\n if (value !== undefined) {\n out[key] = value;\n }\n }\n return out;\n}\n\n/**\n * Build a typed phase-constructor API from per-phase defaults. The defaults\n * object MUST have one entry per `S[\"type\"]` discriminant; the returned API\n * has one method per phase. Each method merges the defaults with optional\n * overrides (undefineds stripped), then stamps the `type` discriminator.\n *\n * @example\n * type S = { type: \"a\"; n: number } | { type: \"b\"; s: string };\n * const f = stateFactory<S>({ a: { n: 0 }, b: { s: \"\" } });\n * f.a(); // { type: \"a\", n: 0 }\n * f.a({ n: 42 }); // { type: \"a\", n: 42 }\n * f.b({ s: \"hi\" }); // { type: \"b\", s: \"hi\" }\n */\nexport function stateFactory<S extends { type: string }>(\n defaults: StateFactoryDefaults<S>,\n): StateFactoryAPI<S> {\n // Build the API by walking the defaults' own keys. Each key corresponds\n // to one phase discriminator; the constructor closes over both the key\n // (for the `type` stamp) and that phase's defaults snapshot.\n const api = {} as { [k: string]: (overrides?: object) => unknown };\n for (const key of Object.keys(\n defaults,\n ) as (keyof StateFactoryDefaults<S>)[]) {\n const phaseDefaults = defaults[key];\n api[key as string] = (overrides?: object) => {\n // The strip-undefined pass lives here, not at the caller. See the\n // module header for the `exactOptionalPropertyTypes` rationale.\n const cleanedOverrides = overrides ? stripUndefined(overrides) : {};\n return { type: key, ...phaseDefaults, ...cleanedOverrides };\n };\n }\n return api as StateFactoryAPI<S>;\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/testing/assert-wrapper-faithful.ts","../../src/testing/assertions.ts","../../src/testing/bind-machine.ts","../../src/testing/drive.ts","../../src/testing/noop-runtime.ts","../../src/testing/state-factory.ts"],"names":["step","expect"],"mappings":"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","file":"index.js","sourcesContent":["// ---------------------------------------------------------------------------\n// `assertWrapperFaithful` — the shared conformance gate for the wrapper tier.\n//\n// A `withX` wrapper bolts a cross-cutting concern (telemetry, deadline,\n// resilience) onto a base machine as an AUTHORING property — the base's source\n// never hand-wires it. The DANGER is the runtime betrayal: hiding wrapper state\n// in a closure, or silently swallowing / retiming a base Cmd, so the wrapper's\n// behavior is invisible to the reducer + Msg log. This helper is the binary\n// betrayal-detector every wrapper inherits. Pass = TEA-faithful, ship it.\n// Fail = betrayal, does not merge.\n//\n// It asserts, by `replay` (which composes `init` + `update` only — the\n// conformance path):\n//\n// 1. **Base behavior byte-identical.** `replay(wrapped).state.base` deep-\n// equals `replay(base).state`. The wrapper never alters base behavior.\n// 2. **Slice AND wrapper Cmds reconstruct under any ambient clock/RNG.**\n// Replaying the SAME msgs under a DIFFERENT global wall-clock + RNG yields\n// a deep-equal wrapper slice AND a deep-equal `$x:*` wrapper-Cmd subset —\n// BOTH halves of the wrapper's output (state slice + emitted Cmds) are a\n// pure function of the Msg log, NOT of `Date.now()` / `Math.random()`.\n// (`update` cells never receive `ctx`, so an impure update read reaches for\n// the GLOBAL ambient; stubbing two distinct ambients and comparing catches\n// a folded wall-clock read that two back-to-back replays under one ambient\n// would hide.) The slice comparison alone has a blind spot: a clock/RNG\n// read folded into a wrapper Cmd PAYLOAD (not the slice) leaves the slice\n// identical, so we ALSO compare the `$x:*` Cmds across the two ambients.\n// Legit time enters only as Msg `at` payloads, identical across replays.\n// 3. **Every wrapper decision is in the log.** At least one wrapper-tagged\n// Cmd (`$x:*`) or Msg appears in the replayed Cmd stream whenever there is\n// a transition — the wrapper's decisions are visible, not off-ledger.\n// PLUS a PER-TRANSITION check: at each step the bare machine's step-i Cmds\n// are an ordered subsequence of the wrapped machine's step-i Cmds, so a\n// base Cmd deferred to a LATER transition (intra-window retiming a\n// flattened subsequence would miss) surfaces as a step-level failure.\n// 4. **Slice is JSON-round-trip equal.** `$x` survives\n// `JSON.parse(JSON.stringify(...))` — durable across DO eviction / reload,\n// never a closure.\n//\n// Strengthens invariant 9 (the testing surface is named and small) and is the\n// mechanical CI form of the wrapper-tier conformance gate.\n// ---------------------------------------------------------------------------\n\nimport { expect, vi } from \"vitest\";\nimport { type Cmd, type Machine, replay, type Sub } from \"../index\";\n\n/**\n * The composed Model shape every `withX` wrapper produces: the base machine's\n * state nested under `base`, and the wrapper's own NAMED, serializable slice\n * under a single `$`-prefixed key. This helper does not care WHICH `$`-key the\n * wrapper uses — it discovers it structurally (the one non-`base` field).\n */\nexport type WrapperModel<S> = {\n readonly base: S;\n} & { readonly [K: `$${string}`]: unknown };\n\n/** Options for the conformance replay. Mirrors `replay`'s second argument. */\nexport interface AssertWrapperFaithfulOpts<S, WM, BaseM, BaseCtx, WrapperCtx> {\n /** The Msg sequence to fold through BOTH machines. Use the base's Msgs. */\n readonly msgs: readonly BaseM[];\n /** Ctx for the WRAPPED machine (base ctx + wrapper ports). */\n readonly ctx: WrapperCtx;\n /** Ctx for the BARE base machine. Defaults to `ctx` when omitted. */\n readonly baseCtx?: BaseCtx;\n /** Optional pre-`init` snapshot of the COMPOSED Model (wrapped rehydrate). */\n readonly loaded?: WM | null;\n /**\n * Optional pre-`init` snapshot of the BARE base state. Supply when the\n * wrapped `loaded` is non-null so the base-equivalence check rehydrates the\n * base machine from the matching nested slice. Defaults to `loaded?.base`.\n */\n readonly baseLoaded?: S | null;\n /**\n * Opt-in relaxation for an INTERCEPTING wrapper (e.g. `withResilience`).\n *\n * DEFAULT (`undefined` / `false`) — the OBSERVE-ONLY contract that\n * `withTelemetry` and `withDeadline` satisfy: every bare base Cmd must appear\n * UNCHANGED, in order, as a subsequence of the wrapped Cmd stream (properties\n * 1 and 3b). The wrapper may APPEND its own Cmds but may NOT alter, drop, or\n * retime a base Cmd. This is the strict default and it is NOT weakened.\n *\n * WHEN SET — for a wrapper whose whole job is to INTERCEPT a chosen base Cmd\n * (retag it into a wrapper-owned carrier so the concern — cache / breaker /\n * rate-limit / retry — can gate it), a target base Cmd LEGITIMATELY does not\n * appear raw at the top level of the wrapped stream. The contract is still\n * **retag-and-re-emit, never swallow** (rule 2): the Cmd must be RECORDED, not\n * vanished. So when a bare base Cmd is ABSENT from the top-level wrapped\n * subsequence, this option accepts it IFF, in that SAME transition window, it\n * is recorded by the wrapper as one of:\n *\n * - the inner payload of a `$x:*` \"run\" carrier Cmd (`carrierCmdType` +\n * `innerField`) — the retag path; OR\n * - any other `$x:*` divergence Cmd whose `recordsField` deep-equals the\n * base Cmd — the gate/defer path (e.g. a \"fast-failed\" or \"deferred\"\n * record Cmd that carries the base Cmd it diverged on).\n *\n * A target base Cmd that is NEITHER present raw NOR recorded by any `$x:*`\n * Cmd in its window is a SWALLOW betrayal and still fails the gate.\n *\n * Provide the field names the carrier uses so the gate can unwrap it\n * structurally without hard-coding `withResilience`'s vocabulary.\n */\n readonly intercepting?: InterceptingOpt;\n}\n\n/**\n * Configures the intercepting relaxation (see\n * `AssertWrapperFaithfulOpts.intercepting`). All fields name the carrier /\n * divergence shape so the gate can structurally find a retagged base Cmd\n * inside a `$x:*` Cmd. The gate only EVER inspects `$x:*`-prefixed wrapped\n * Cmds for a recorded base Cmd — it never relaxes the check for a base Cmd\n * that appears under any non-wrapper Cmd type.\n */\nexport interface InterceptingOpt {\n /**\n * The `Cmd.type` of the wrapper's run-carrier (e.g. `\"$resilience:run\"`).\n * A bare base Cmd that is retagged appears as `carrier[innerField]`.\n */\n readonly carrierCmdType: string;\n /** The field on the carrier Cmd that holds the original base Cmd. */\n readonly innerField: string;\n /**\n * OPTIONAL field on ANY OTHER `$x:*` Cmd that records a base Cmd the wrapper\n * gated/deferred without running it (e.g. a fast-fail record). When present\n * on a `$x:*` Cmd and deep-equal to the missing base Cmd, it counts as a\n * recorded divergence. Omit when the wrapper only ever retags via the\n * carrier.\n */\n readonly recordsField?: string;\n}\n\n/**\n * Assert a `withX` wrapper is TEA-faithful. Replays the wrapped machine and the\n * bare base machine over the same Msgs and checks the four conformance\n * properties (see file header).\n *\n * @param base the bare base `Machine`.\n * @param makeWrapped a thunk that wraps `base` and returns the composed machine\n * (e.g. `() => withTelemetry(base, cfg)`). A thunk — not the wrapped value —\n * so a wrapper that registers process-scoped ports (`definePort`) is\n * constructed inside the assertion's control, and so the same betrayal-\n * detector reads as `assertWrapperFaithful(base, () => withX(base, cfg), ...)`\n * at every wrapper's test site.\n * @param opts the conformance replay options.\n */\nexport function assertWrapperFaithful<\n S,\n BaseM extends { type: string },\n BaseC extends Cmd,\n BaseU extends Sub,\n BaseCtx,\n WM extends WrapperModel<S>,\n WrapperM extends { type: string },\n WrapperC extends Cmd,\n WrapperU extends Sub,\n WrapperCtx,\n>(\n base: Machine<S, BaseM, BaseC, BaseU, BaseCtx>,\n makeWrapped: () => Machine<WM, WrapperM, WrapperC, WrapperU, WrapperCtx>,\n opts: AssertWrapperFaithfulOpts<S, WM, BaseM, BaseCtx, WrapperCtx>,\n): void {\n const baseCtx = (opts.baseCtx ?? (opts.ctx as unknown as BaseCtx)) as BaseCtx;\n const wrappedMsgs = opts.msgs as unknown as readonly WrapperM[];\n const baseLoaded =\n opts.baseLoaded ?? (opts.loaded != null ? (opts.loaded.base as S) : null);\n\n // Two DISTINCT global ambients (wall-clock + RNG). `replay` composes only\n // `init` + `update`, and `update` cells are `(state, msg)` — they never\n // receive `ctx`. So an impure update read reaches for the GLOBAL `Date.now()`\n // / `Math.random()`, not an injected clock. Running the two determinism\n // replays under DIFFERENT ambients turns a folded wall-clock read into an\n // OBSERVABLE slice divergence (property 2) — a single fixed ambient (two\n // back-to-back replays) would return the same ms both times and hide it.\n const A0 = { now: 1_000_000, rng: 0.123 };\n const A1 = { now: 9_876_543, rng: 0.987 };\n\n // The bare baseline, `wrappedA`, and every per-transition baseline (props 3/4)\n // run under the SAME fixed ambient A0 so their cmds/state are deterministic.\n const bare = underAmbient(A0, () =>\n replay(base, { msgs: opts.msgs, ctx: baseCtx, loaded: baseLoaded }),\n );\n const wrapped = underAmbient(A0, () => makeWrapped());\n const wrappedA = underAmbient(A0, () =>\n replay(wrapped, {\n msgs: wrappedMsgs,\n ctx: opts.ctx,\n loaded: opts.loaded ?? null,\n }),\n );\n // The second determinism replay runs under a DISTINCT ambient A1. A faithful\n // wrapper's slice (a pure function of the Msg log) is identical under A0 and\n // A1; an impure update that folds `Date.now()` / `Math.random()` diverges.\n const wrappedB = underAmbient(A1, () =>\n replay(makeWrapped(), {\n msgs: wrappedMsgs,\n ctx: opts.ctx,\n loaded: opts.loaded ?? null,\n }),\n );\n\n // --- Property 1: base behavior byte-identical -----------------------------\n // `state.base` after wrapping === state after running the base alone. The\n // wrapper observes; it does not alter base behavior.\n expect(wrappedA.state.base).toEqual(bare.state);\n // The base's OWN Cmds appear in the wrapped Cmd stream unchanged: every bare\n // Cmd is present, in order, among the wrapped Cmds (with wrapper Cmds\n // interleaved). We assert the bare Cmds are a subsequence of the wrapped ones.\n // For an INTERCEPTING wrapper (`opts.intercepting` set) a target base Cmd is\n // accepted when it appears RETAGGED inside a `$x:*` carrier / recorded by a\n // `$x:*` divergence Cmd instead of raw — retag, not swallow.\n expectCmdSubsequence(bare.cmds, wrappedA.cmds, undefined, opts.intercepting);\n\n // --- Property 2: slice reconstructs under any ambient clock/RNG -----------\n // Replaying the same Msgs under a DIFFERENT global ambient yields a deep-equal\n // wrapper SLICE — the slice is a pure function of the Msg log, never of\n // `Date.now()` / `Math.random()`. We compare the single `$`-slice (not the\n // whole composed state) because comparing whole state would FALSE-POSITIVE\n // when the BASE machine reads the clock — that is the base's behavior, not the\n // wrapper's fault, and the wrapper's faithfulness is precisely what its own\n // slice records.\n const sliceKey = wrapperSliceKey(wrappedA.state);\n expect(\n sliceOf(wrappedB.state, sliceKey),\n `wrapper slice changed under a different ambient clock/RNG → update is ` +\n `reading Date.now()/Math.random(); time must enter only as Msg \\`at\\` data`,\n ).toEqual(sliceOf(wrappedA.state, sliceKey));\n\n // ...AND the wrapper-tagged Cmds reconstruct under any ambient too. The slice\n // is only HALF the wrapper's output — the other half is its emitted Cmds. A\n // wall-clock / RNG read folded into a wrapper Cmd PAYLOAD (not the state\n // slice) escapes the slice comparison above: the slice stays identical while\n // the Cmd carries a leaked `Date.now()` / `Math.random()`. So mirror the\n // slice check on the `$x:*` subset of the replayed Cmd stream — a faithful\n // wrapper emits Cmds that are a pure function of the Msg log, identical under\n // A0 and A1. Only Msg `at` payloads (identical across both replays) may carry\n // time; if a wrapper Cmd diverges between ambients, the wrapper read the\n // ambient clock/RNG into a Cmd. (Base Cmds are EXCLUDED — a base machine that\n // reads the clock is the base's behavior, not the wrapper's, exactly as the\n // slice comparison excludes whole-state.)\n const wrapperFamily = `${sliceKey}:`;\n const wrapperCmdsA = wrappedA.cmds.filter((c) =>\n c.type.startsWith(wrapperFamily),\n );\n const wrapperCmdsB = wrappedB.cmds.filter((c) =>\n c.type.startsWith(wrapperFamily),\n );\n expect(\n wrapperCmdsB,\n `wrapper Cmds (\"${wrapperFamily}*\") changed under a different ambient ` +\n `clock/RNG → a wrapper Cmd PAYLOAD is reading Date.now()/Math.random(); ` +\n `wrapper Cmds must be a pure function of the Msg log, time entering only ` +\n `as Msg \\`at\\` data`,\n ).toEqual(wrapperCmdsA);\n\n // --- Property 3: every wrapper decision appears as an XMsg/XCmd in the log -\n // The composed Model's wrapper slice is the single non-`base` `$`-key. We\n // discover its key family (`$telemetry` → `$telemetry:*`) and require that,\n // whenever there is at least one transition, the replayed Cmd stream carries\n // at least one wrapper-tagged Cmd. (Wrappers whose decisions are Msgs route\n // them through the SAME log; for a Cmd-emitting wrapper like withTelemetry the\n // decision is the `$x:emit` Cmd.) A wrapper that performed its work off-ledger\n // would have an EMPTY wrapper-Cmd set here despite real transitions — the\n // exact betrayal this gate exists to catch.\n if (opts.msgs.length > 0) {\n expect(\n wrapperCmdsA.length,\n `expected at least one \"${wrapperFamily}*\" wrapper-decision Cmd in the replay log ` +\n `(found none across ${opts.msgs.length} transition(s)) — a wrapper whose ` +\n `decisions are invisible to the Msg/Cmd log has betrayed total visibility`,\n ).toBeGreaterThan(0);\n }\n\n // --- Property 3b: per-transition Cmd subsequence --------------------------\n // The flattened subsequence (property 1) misses INTRA-WINDOW retiming — a\n // base Cmd that a saga-style wrapper DEFERS to a later transition still drains\n // within the replay, so the flattened bare-Cmd set stays an ordered\n // subsequence and slips through. Pin it per step: for each transition i, the\n // bare machine's step-i Cmds must be an ordered subsequence of the wrapped\n // machine's step-i Cmds. A deferred base Cmd is then visible as a step-i\n // subsequence failure (it shows up at i+1 in the wrapped run but at i in the\n // bare run). Prefix replays run under the fixed ambient A0 for determinism.\n for (let i = 0; i < opts.msgs.length; i++) {\n const bareStep = stepCmds(\n () =>\n underAmbient(A0, () =>\n replay(base, {\n msgs: opts.msgs.slice(0, i),\n ctx: baseCtx,\n loaded: baseLoaded,\n }),\n ).cmds,\n () =>\n underAmbient(A0, () =>\n replay(base, {\n msgs: opts.msgs.slice(0, i + 1),\n ctx: baseCtx,\n loaded: baseLoaded,\n }),\n ).cmds,\n );\n const wrappedStep = stepCmds(\n () =>\n underAmbient(A0, () =>\n replay(wrapped, {\n msgs: wrappedMsgs.slice(0, i),\n ctx: opts.ctx,\n loaded: opts.loaded ?? null,\n }),\n ).cmds,\n () =>\n underAmbient(A0, () =>\n replay(wrapped, {\n msgs: wrappedMsgs.slice(0, i + 1),\n ctx: opts.ctx,\n loaded: opts.loaded ?? null,\n }),\n ).cmds,\n );\n expectCmdSubsequence(bareStep, wrappedStep, i, opts.intercepting);\n }\n\n // --- Property 4: slice is JSON-round-trip equal ---------------------------\n // The wrapper slice survives JSON.parse(JSON.stringify(...)) — durable across\n // DO eviction / reload, never a closure.\n const slice = sliceOf(wrappedA.state, sliceKey);\n expect(JSON.parse(JSON.stringify(slice))).toEqual(slice);\n}\n\n/**\n * Run `fn` with the GLOBAL `Date.now` and `Math.random` stubbed to fixed\n * values, restoring them in a `finally` so the stub never leaks across\n * assertions or tests. `update` cells receive no `ctx`, so an impure read\n * inside one resolves to these globals — the lever this gate pulls to make a\n * folded wall-clock / RNG read observable in the wrapper slice.\n */\nfunction underAmbient<T>(\n ambient: { readonly now: number; readonly rng: number },\n fn: () => T,\n): T {\n const nowSpy = vi.spyOn(Date, \"now\").mockReturnValue(ambient.now);\n const rngSpy = vi.spyOn(Math, \"random\").mockReturnValue(ambient.rng);\n try {\n return fn();\n } finally {\n nowSpy.mockRestore();\n rngSpy.mockRestore();\n }\n}\n\n/** The wrapper slice value (the single `$`-key) on a composed Model. */\nfunction sliceOf(model: object, sliceKey: string): unknown {\n return (model as Record<string, unknown>)[sliceKey];\n}\n\n/**\n * The Cmds a single transition `i` emitted: the suffix of the cmds after\n * `msgs.slice(0, i+1)` past the cmds after `msgs.slice(0, i)`. Cmds accumulate\n * in order across `replay`, so the longer run is a prefix-extension of the\n * shorter one and the suffix is exactly step `i`'s contribution.\n */\nfunction stepCmds(\n prefixCmds: () => readonly Cmd[],\n throughCmds: () => readonly Cmd[],\n): readonly Cmd[] {\n const before = prefixCmds();\n const after = throughCmds();\n return after.slice(before.length);\n}\n\n/**\n * The wrapper slice key on a composed Model — the single own enumerable field\n * whose name starts with `$`. Throws if zero or more than one is found, because\n * the wrapper-tier contract is exactly one named `$`-slice per wrapper.\n */\nfunction wrapperSliceKey(model: object): string {\n const dollarKeys = Object.keys(model).filter((k) => k.startsWith(\"$\"));\n if (dollarKeys.length !== 1) {\n throw new Error(\n `assertWrapperFaithful: expected exactly one \"$\"-prefixed wrapper slice ` +\n `on the composed Model, found ${dollarKeys.length} (${dollarKeys.join(\", \") || \"none\"}). ` +\n `Every withX wrapper owns exactly one named slice (e.g. \"$telemetry\").`,\n );\n }\n // biome-ignore lint/style/noNonNullAssertion: length checked to be exactly 1 above\n return dollarKeys[0]!;\n}\n\n/**\n * Assert `needle` is an (order-preserving) subsequence of `haystack` by deep\n * equality — every base Cmd appears, in order, somewhere in the wrapped Cmd\n * stream (wrapper Cmds may be interleaved). This is the \"base Cmds pass through\n * UNCHANGED\" check that tolerates the wrapper's own appended Cmds without\n * requiring an exact-equal stream. When `step` is supplied the message names\n * the transition index, so a per-transition retiming failure points at the\n * exact step a base Cmd was deferred away from.\n *\n * `intercepting` (default `undefined` → the strict OBSERVE-ONLY check above)\n * relaxes the match for a base Cmd that an INTERCEPTING wrapper retags: when a\n * needle Cmd is NOT found raw in `haystack`, it is accepted IFF some `$x:*`\n * wrapper Cmd in `haystack` RECORDS it — as the carrier's `innerField` payload\n * (retag) or another `$x:*` Cmd's `recordsField` (recorded gate/defer). The\n * relaxation ONLY ever looks inside `$x:*` Cmds, so a base Cmd that vanished\n * with no `$x:*` record still fails. The order check is preserved: the carrier\n * / divergence Cmd must come at or after the cursor, exactly like a raw match.\n */\nfunction expectCmdSubsequence(\n needle: readonly Cmd[],\n haystack: readonly Cmd[],\n step?: number,\n intercepting?: InterceptingOpt,\n): void {\n let i = 0;\n for (const cmd of haystack) {\n if (i >= needle.length) break;\n if (__deepEqual(needle[i], cmd)) {\n i++;\n continue;\n }\n // Intercepting relaxation: the base Cmd at the cursor may legitimately be\n // absent raw because the wrapper RETAGGED it. Accept this haystack Cmd as\n // the matching slot IFF it is a `$x:*` wrapper Cmd that records `needle[i]`.\n if (\n intercepting !== undefined &&\n recordsBaseCmd(cmd, needle[i] as Cmd, intercepting)\n ) {\n i++;\n }\n }\n const where =\n step === undefined\n ? \"the wrapped Cmd stream\"\n : `the wrapped Cmd stream AT TRANSITION ${step} (a base Cmd deferred to a ` +\n `later transition is an intra-window retiming betrayal)`;\n const swallowHint =\n intercepting === undefined\n ? \"a missing/rewritten/retimed base Cmd is a swallow betrayal\"\n : `an intercepting wrapper must RETAG each target base Cmd into a ` +\n `\"${intercepting.carrierCmdType}\" carrier (inner field ` +\n `\"${intercepting.innerField}\") or record it on a \"$*:\" divergence Cmd ` +\n `— a base Cmd neither present raw NOR recorded by any \"$*:\" Cmd is a ` +\n `swallow betrayal`;\n expect(\n i,\n `expected every base Cmd to pass through unchanged (matched ${i} of ` +\n `${needle.length} base Cmds as an ordered subsequence of ${where}) — ` +\n `${swallowHint}`,\n ).toBe(needle.length);\n}\n\n/**\n * Does the wrapped Cmd `cmd` RECORD the base Cmd `baseCmd` under the\n * intercepting contract? True iff `cmd` is a `$`-prefixed wrapper Cmd AND\n * either:\n * - `cmd.type === carrierCmdType` and `cmd[innerField]` deep-equals\n * `baseCmd` (the retag carrier), OR\n * - `recordsField` is configured and `cmd[recordsField]` deep-equals\n * `baseCmd` (a recorded gate/defer divergence on any `$x:*` Cmd).\n *\n * The `$`-prefix guard is load-bearing: the relaxation must NEVER accept a base\n * Cmd hidden inside a NON-wrapper Cmd — that would let a wrapper launder a\n * swallowed base Cmd through an arbitrary re-emit. Only the wrapper's own\n * `$x:*` family may carry a retagged base Cmd.\n */\nfunction recordsBaseCmd(cmd: Cmd, baseCmd: Cmd, opt: InterceptingOpt): boolean {\n if (!cmd.type.startsWith(\"$\")) return false;\n const rec = cmd as unknown as Record<string, unknown>;\n if (\n cmd.type === opt.carrierCmdType &&\n __deepEqual(rec[opt.innerField], baseCmd)\n )\n return true;\n if (\n opt.recordsField !== undefined &&\n __deepEqual(rec[opt.recordsField], baseCmd)\n )\n return true;\n return false;\n}\n\n/**\n * Structural deep equality for plain Cmd data.\n *\n * NOT `JSON.stringify` equality: `JSON.stringify` DROPS undefined-valued fields,\n * so `{ type: \"x\" }` would falsely equal `{ type: \"x\", foo: undefined }` and the\n * gate could accept a wrapper that smuggles an extra (undefined) field into a\n * \"byte-identical\" base Cmd or a retag carrier. This walk distinguishes an\n * ABSENT key from an `undefined`-VALUED key by comparing the full own-enumerable\n * key SET (not just the defined values) in both directions, then recursing.\n *\n * Cmds are plain data — primitives, `null`, arrays, and plain objects — so this\n * intentionally does not model `Date`, `Map`, `Set`, cycles, or class instances.\n *\n * Exported under the `__` test-only convention so the conformance suite can\n * pin the absent-vs-undefined distinction directly. Not part of the public API.\n *\n * @internal test-only\n */\nexport function __deepEqual(a: unknown, b: unknown): boolean {\n if (Object.is(a, b)) return true;\n\n // Past the identity check, equality requires same kind on both sides. Anything\n // non-object (primitive, function, symbol) that wasn't `Object.is`-equal above\n // is unequal. `null` is `typeof \"object\"` but only equals `null` (caught by\n // `Object.is`), so screen it out explicitly.\n if (\n typeof a !== \"object\" ||\n typeof b !== \"object\" ||\n a === null ||\n b === null\n ) {\n return false;\n }\n\n const aIsArray = Array.isArray(a);\n if (aIsArray !== Array.isArray(b)) return false;\n\n if (aIsArray) {\n const bArr = b as unknown[];\n if (a.length !== bArr.length) return false;\n for (let i = 0; i < a.length; i++) {\n if (!__deepEqual(a[i], bArr[i])) return false;\n }\n return true;\n }\n\n // Plain objects: compare the own-enumerable key SET first so a key present on\n // one side with an `undefined` value (and absent on the other) is caught —\n // the betrayal `JSON.stringify` hid. Then recurse on every value.\n const aRec = a as Record<string, unknown>;\n const bRec = b as Record<string, unknown>;\n const aKeys = Object.keys(aRec);\n const bKeys = Object.keys(bRec);\n if (aKeys.length !== bKeys.length) return false;\n for (const key of aKeys) {\n if (!Object.hasOwn(bRec, key)) return false;\n if (!__deepEqual(aRec[key], bRec[key])) return false;\n }\n return true;\n}\n","// ---------------------------------------------------------------------------\n// Test-side assertions over `replay`. Each helper composes the replay call\n// with a single `expect(...)` so consumers stop hand-rolling the 5-line\n// \"destructure { state, cmds } → expect.toBe / toEqual / toContainEqual\"\n// boilerplate at every test site.\n//\n// Each helper is a thin wrapper that:\n// 1. Calls `replay(machine, opts)` once.\n// 2. Pipes the result into a single vitest `expect(...)` matcher.\n//\n// All helpers return `void` — assertions throw on failure (vitest contract).\n// No mutation of the machine or opts. Pure test ergonomics.\n//\n// Type-inference note: each helper anchors its generic parameters to the\n// `machine` argument. The `expected` (and `opts.msgs`) arguments are wrapped\n// in `NoInfer<...>` so they do NOT participate in inference — otherwise a\n// `[]`-shaped `expected` would force `C = never` and reject a machine whose\n// real Cmd union is non-empty. Anchoring inference to the machine is the\n// same discipline `replay` uses (it has nothing but the machine and opts).\n//\n// Strengthens invariant 9 (the testing surface is named, small, and forks\n// nothing of the substrate's purity guarantees).\n// ---------------------------------------------------------------------------\n\nimport { expect } from \"vitest\";\nimport { type Cmd, type Machine, replay, type Sub } from \"../index\";\n\n// `NoInfer<T>` is the built-in TypeScript 5.4+ intrinsic that blocks\n// inference from a generic parameter at a position. We use it on `expected`\n// and `opts` so inference flows purely FROM the machine — `[]`-shaped\n// `expected` no longer drags `C` down to `never` and rejects the machine.\n\n/**\n * Shared options shape for every test assertion below. Mirrors the\n * second argument of `replay(machine, opts)` — `msgs` is the sequence to\n * fold over (may be empty for init-only assertions), `ctx` is the\n * substrate-required ctx value, `loaded` is the optional pre-`init`\n * snapshot (omit / pass `null` for fresh boot).\n */\nexport interface ReplayOpts<S, M, Ctx> {\n readonly msgs: readonly M[];\n readonly ctx: Ctx;\n readonly loaded?: S | null;\n}\n\n/**\n * Assert the final state after replaying `opts.msgs` equals `expected`.\n * Replaces the boilerplate:\n *\n * const { state } = replay(machine, opts);\n * expect(state).toEqual(expected);\n *\n * Uses `toEqual` (deep structural). For type-narrowing assertions that\n * branch on `state.type` afterwards, fall back to plain `replay` —\n * `expectFinalState` is the \"exact match\" tool.\n */\nexport function expectFinalState<\n S,\n M extends { type: string },\n C extends Cmd,\n U extends Sub,\n Ctx,\n>(\n machine: Machine<S, M, C, U, Ctx>,\n opts: NoInfer<ReplayOpts<S, M, Ctx>>,\n expected: NoInfer<S>,\n): void {\n const { state } = replay(machine, opts);\n expect(state).toEqual(expected);\n}\n\n/**\n * Assert that `cmd` appears at least once in the cmds array produced by\n * replaying `opts.msgs`. Uses `toContainEqual` (deep structural). Order\n * and surrounding cmds are not asserted — use `expectCmdSequence` for\n * exact ordered match.\n *\n * Replaces:\n *\n * const { cmds } = replay(machine, opts);\n * expect(cmds).toContainEqual(cmd);\n */\nexport function expectCmdEmitted<\n S,\n M extends { type: string },\n C extends Cmd,\n U extends Sub,\n Ctx,\n>(\n machine: Machine<S, M, C, U, Ctx>,\n opts: NoInfer<ReplayOpts<S, M, Ctx>>,\n cmd: NoInfer<C>,\n): void {\n const { cmds } = replay(machine, opts);\n expect(cmds).toContainEqual(cmd);\n}\n\n/**\n * Assert the exact ordered sequence of cmds emitted by replaying\n * `opts.msgs`. Uses `toEqual` against the full cmds array — extra cmds\n * fail the assertion, missing cmds fail the assertion, wrong order\n * fails the assertion.\n *\n * Replaces:\n *\n * const { cmds } = replay(machine, opts);\n * expect(cmds).toEqual(expected);\n */\nexport function expectCmdSequence<\n S,\n M extends { type: string },\n C extends Cmd,\n U extends Sub,\n Ctx,\n>(\n machine: Machine<S, M, C, U, Ctx>,\n opts: NoInfer<ReplayOpts<S, M, Ctx>>,\n expected: NoInfer<readonly C[]>,\n): void {\n const { cmds } = replay(machine, opts);\n expect(cmds).toEqual(expected);\n}\n\n/**\n * Assert the exact set of subs desired at the final state after\n * replaying `opts.msgs`. Uses `toEqual` against the full subs array.\n *\n * Replaces:\n *\n * const { subs } = replay(machine, opts);\n * expect(subs).toEqual(expected);\n */\nexport function expectActiveSubs<\n S,\n M extends { type: string },\n C extends Cmd,\n U extends Sub,\n Ctx,\n>(\n machine: Machine<S, M, C, U, Ctx>,\n opts: NoInfer<ReplayOpts<S, M, Ctx>>,\n expected: NoInfer<readonly U[]>,\n): void {\n const { subs } = replay(machine, opts);\n expect(subs).toEqual(expected);\n}\n\n/**\n * Single-msg step helper — feeds `loaded → msg → next state + cmds emitted by\n * that msg`. Returns a readonly tuple in the shape consumers used before the\n * substrate (`[state, cmds]`), so existing tests that destructure with\n * `[next, cmds]` can swap `reduce(state, msg)` for `step(machine, state, msg,\n * ctx)` without changing the assertion code.\n *\n * Internally a `replay` call with `loaded: prevState` and a single-element\n * `msgs` array. The cmds returned are exactly the cmds emitted by THIS msg\n * (init emits no cmds when `loaded` is provided, so the slice is the entire\n * array).\n *\n * Use when consumers want to walk a machine one msg at a time and assert per\n * step. For one-shot \"final state after N msgs\" assertions, prefer\n * `expectFinalState`.\n */\nexport function step<\n S,\n M extends { type: string },\n C extends Cmd,\n U extends Sub,\n Ctx,\n>(\n machine: Machine<S, M, C, U, Ctx>,\n loaded: NoInfer<S>,\n msg: NoInfer<M>,\n ctx: NoInfer<Ctx>,\n): readonly [S, readonly C[]] {\n const { state, cmds } = replay(machine, { msgs: [msg], ctx, loaded });\n return [state, cmds] as const;\n}\n","// ---------------------------------------------------------------------------\n// `bindMachine(machine, ctx)` — bound test harness over the free-function\n// assertions in `./assertions.ts`. Closes over the `machine` and `ctx` once\n// so per-test callsites stop repeating both at every invocation.\n//\n// Why this exists: every consumer test file ends up hand-rolling a local\n// two-arg `step(state, msg)` wrapper around `step(machine, state, msg, ctx)`,\n// and the four `expect*` assertions repeat `machine` + `ctx` at every\n// callsite. The bound form pulls both into one place and gives every\n// per-test call its tightest possible shape (2-arg max).\n//\n// `bindMachine` is built ON TOP of the free helpers — it does not replace\n// them. Both forms coexist; pick the one that fits the call site:\n//\n// - Free form → one-off call inside a helper that isn't bound to one\n// machine (e.g. cross-machine integration test).\n// - Bound form → a `describe` block runs many assertions against one\n// machine + ctx. The bound form is the common case.\n//\n// Strengthens invariant 9 (the testing surface is named, small, and forks\n// nothing of the substrate's purity guarantees).\n// ---------------------------------------------------------------------------\n\nimport { expect } from \"vitest\";\nimport { type Cmd, type Machine, replay, type Sub } from \"../index\";\n\n/**\n * Replay options shape used by every bound method. `ctx` is captured by\n * `bindMachine`; the per-call opts only need `msgs` and an optional\n * `loaded` snapshot. (Compare `ReplayOpts` in `./assertions.ts`, which\n * still takes `ctx` because the free helpers cannot capture it.)\n */\ninterface BoundOpts<S, M> {\n readonly msgs: readonly M[];\n readonly loaded?: S | null;\n}\n\n/**\n * The bound testing surface. Every method is 2-arg max — `ctx` and\n * `machine` are captured by `bindMachine`.\n *\n * `NoInfer<...>` on `expected` / `cmd` arguments mirrors the discipline\n * the free helpers use (see `./assertions.ts`): inference flows purely\n * FROM the machine, so a `[]`-shaped expected does not drag `C` down to\n * `never` and reject the machine.\n */\nexport interface BoundMachine<\n S,\n M extends { type: string },\n C extends Cmd,\n U extends Sub,\n // `Ctx` is a phantom-style parameter — present for consumers that want to\n // spell `BoundMachine<S, M, C, U, Ctx>` explicitly, mirroring `Machine`.\n // biome-ignore lint/correctness/noUnusedVariables: phantom type param (see above)\n Ctx,\n> {\n /**\n * Single-msg step — feeds `loaded → msg → [next state, cmds emitted by\n * that msg]`. Mirrors the free `step(machine, loaded, msg, ctx)`.\n */\n step(loaded: S, msg: M): readonly [S, readonly C[]];\n /**\n * Assert the final state after replaying `opts.msgs` deep-equals\n * `expected`. Mirrors the free `expectFinalState(machine, opts, expected)`.\n */\n expectFinalState(opts: BoundOpts<S, M>, expected: NoInfer<S>): void;\n /**\n * Assert `cmd` appears at least once in the emitted cmd list. Order is\n * not asserted. Mirrors the free `expectCmdEmitted(machine, opts, cmd)`.\n */\n expectCmdEmitted(opts: BoundOpts<S, M>, cmd: NoInfer<C>): void;\n /**\n * Assert the exact ordered sequence of cmds emitted by replaying\n * `opts.msgs`. Mirrors the free `expectCmdSequence(machine, opts, expected)`.\n */\n expectCmdSequence(\n opts: BoundOpts<S, M>,\n expected: readonly NoInfer<C>[],\n ): void;\n /**\n * Assert the exact set of subs desired at the final state. Mirrors the\n * free `expectActiveSubs(machine, opts, expected)`.\n */\n expectActiveSubs(\n opts: BoundOpts<S, M>,\n expected: readonly NoInfer<U>[],\n ): void;\n /**\n * Bound `replay` — returns `{ state, cmds, subs }` for the given opts.\n * Use for the \"narrow-then-assert\" pattern where the test inspects a\n * specific field after `state.type === \"...\"` discrimination.\n */\n replay(opts: BoundOpts<S, M>): {\n state: S;\n cmds: readonly C[];\n subs: readonly U[];\n };\n}\n\n/**\n * Bind a machine + ctx into a small bag of 2-arg helpers. Each method\n * delegates to `replay(machine, { ...opts, ctx })` and pipes the result\n * through a single `expect(...)` matcher (or returns the raw tuple, for\n * `step` and `replay`).\n *\n * Use one bind per `describe` block (or per file) when many assertions\n * share the same machine + ctx. For one-off cross-machine calls, stick\n * with the free helpers from `./assertions.ts`.\n */\nexport function bindMachine<\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>, ctx: Ctx): BoundMachine<S, M, C, U, Ctx> {\n return {\n step(loaded, msg) {\n const { state, cmds } = replay(machine, { msgs: [msg], ctx, loaded });\n return [state, cmds] as const;\n },\n expectFinalState(opts, expected) {\n const { state } = replay(machine, { ...opts, ctx });\n expect(state).toEqual(expected);\n },\n expectCmdEmitted(opts, cmd) {\n const { cmds } = replay(machine, { ...opts, ctx });\n expect(cmds).toContainEqual(cmd);\n },\n expectCmdSequence(opts, expected) {\n const { cmds } = replay(machine, { ...opts, ctx });\n expect(cmds).toEqual(expected);\n },\n expectActiveSubs(opts, expected) {\n const { subs } = replay(machine, { ...opts, ctx });\n expect(subs).toEqual(expected);\n },\n replay(opts) {\n return replay(machine, { ...opts, ctx });\n },\n };\n}\n","// ---------------------------------------------------------------------------\n// `drive(machine, initial, msg, handlers, opts)` — the runtime's own loop,\n// said once, for a test.\n//\n// `bindMachine` hands back the Cmds a fold emitted and stops there; performing\n// them is the caller's. So every consumer test that exercises a Cmd-emitting\n// machine hand-writes the same fixture: step the Msg, await the real interpret\n// handler over each emitted Cmd, feed the settle Msg back, repeat until quiet,\n// with a hand-picked guard bound in the loop header. The guard count, the\n// accumulate-then-swap order and the \"did I drain this round before starting\n// the next\" question are re-decided at every copy site, and one of those copies\n// ships in `docs/how-to/ask-jev-a-typed-question.md`.\n//\n// `drive` is that fixture as one call. It composes `bindMachine`'s synchronous\n// 2-arg `step` and adds NO second reducer path — there is no `Runtime` here, no\n// observation, no clock, and nothing borrowed from the internal flow helpers.\n// The only impure thing it does is `await` the handler the caller handed it.\n//\n// **It returns the history, not just the endpoint.** `{ state, trace }`, where\n// `trace` is every Msg folded and every Cmd dispatched in order, is what makes\n// this worth a stable export rather than a snippet: a test asserts on the\n// SEQUENCE as well as the settled state — which handler ran, in which round,\n// and what came back — and the same trace's Msgs replayed through `replay` from\n// the same initial state reproduce the returned state exactly.\n//\n// Prior art converged on the same answer: Temporal's `TestWorkflowEnvironment`\n// hands a test the event history rather than the final result, and Effect's\n// `TestClock` runs make the effect log inspectable rather than opaque.\n//\n// **Exhausting `maxRounds` THROWS.** A driver that quietly returns a\n// half-driven state lets a test assert green on a machine that never settled —\n// the exact failure a hand-rolled `guard < 10` already risks.\n//\n// Strengthens invariant 9 (the testing surface is named and small).\n// ---------------------------------------------------------------------------\n\nimport type {\n Cmd,\n CtxArg,\n Interpret,\n Machine,\n PortEmitter,\n Sub,\n} from \"../index\";\nimport { bindMachine } from \"./bind-machine\";\n\n/**\n * The default round bound when `opts.maxRounds` is omitted: **100**.\n *\n * A round is one drain of the pending Msgs plus one drain of the Cmds those\n * folds emitted, so a settling machine costs a handful; 100 is loose enough\n * that a legitimate retry ladder finishes inside it and tight enough that a\n * genuine cycle stops rather than hangs.\n */\nexport const DEFAULT_MAX_ROUNDS = 100;\n\n/**\n * One entry of a driven run's history, in dispatch order.\n *\n * - `msg` — a Msg folded through the machine's reducer. The seed Msg is the\n * first entry of every trace; every later one is a settle Msg a handler\n * returned (or fired through its injected `dispatch`).\n * - `cmd` — a Cmd handed to an interpret handler. It is recorded BEFORE the\n * handler is awaited, so a trace recovered from a rejected handler names the\n * Cmd that rejected as its last entry.\n */\nexport type DriveTraceEntry<M, C> =\n | { readonly kind: \"msg\"; readonly msg: M }\n | { readonly kind: \"cmd\"; readonly cmd: C };\n\n/** What a settled {@link drive} hands back. */\nexport interface DriveResult<S, M, C> {\n /** The state after the machine went quiet — no Msg pending, no Cmd unperformed. */\n readonly state: S;\n /**\n * Every Msg folded and every Cmd dispatched, in order. Filtering it to its\n * `msg` entries and replaying those through `replay` from the same initial\n * state reproduces {@link DriveResult.state}.\n */\n readonly trace: readonly DriveTraceEntry<M, C>[];\n}\n\n/**\n * {@link drive}'s `ctx` field. `CtxArg` widened by exactly one case: a machine\n * declaring `ctx: undefined` in its `types` block — the pure shape every\n * how-to on this package writes — may omit it too. `CtxArg` alone keeps `ctx`\n * REQUIRED there, because `{}` is not assignable to `undefined`, which would\n * make the common pure call site spell `{ ctx: undefined }` to say nothing.\n * A machine whose `Ctx` carries a field a handler reads still requires it.\n */\nexport type DriveCtxArg<Ctx> = [undefined] extends [Ctx]\n ? { readonly ctx?: Ctx }\n : CtxArg<Ctx>;\n\n/**\n * {@link drive}'s options. `ctx` is conditionally optional (see\n * {@link DriveCtxArg}).\n */\nexport type DriveOptions<Ctx> = DriveCtxArg<Ctx> & {\n /**\n * The round bound. Exceeding it throws {@link DriveRoundsExceededError};\n * `drive` never returns a half-driven state. Defaults to\n * {@link DEFAULT_MAX_ROUNDS} (100).\n */\n readonly maxRounds?: number;\n};\n\n/**\n * Raised when a driven machine is still emitting work after `maxRounds` rounds.\n * Follows the tea error dialect (`override readonly name`) and carries a `_tag`\n * discriminant, so a test branches on it by `instanceof` or on the tag.\n *\n * It carries the round count it stopped at and the PARTIAL trace up to that\n * point, which is what a test needs to see the cycle — the bound alone says\n * only that one exists.\n */\nexport class DriveRoundsExceededError<M, C> extends Error {\n override readonly name = \"DriveRoundsExceededError\";\n readonly _tag = \"DriveRoundsExceededError\" as const;\n constructor(\n /** The bound that was exceeded. */\n public readonly maxRounds: number,\n /** The round the driver stopped at — always `maxRounds + 1`. */\n public readonly rounds: number,\n /** Every Msg folded and Cmd dispatched before the driver gave up. */\n public readonly trace: readonly DriveTraceEntry<M, C>[],\n ) {\n super(\n `@demlik/tea: drive did not settle within ${maxRounds} round(s) — the ` +\n `machine was still emitting work at round ${rounds}. The partial ` +\n `trace (${trace.length} entr${trace.length === 1 ? \"y\" : \"ies\"}) is ` +\n `on the error's \\`trace\\` field.`,\n );\n }\n}\n\n/**\n * Raised when a Cmd reaches {@link drive} with no handler for its `type` in the\n * `handlers` record. `Interpret`'s mapped type makes that a compile error at a\n * well-typed call site; this is the runtime arm for a handler record assembled\n * dynamically or cast.\n */\nexport class DriveNoHandlerError<M, C> extends Error {\n override readonly name = \"DriveNoHandlerError\";\n readonly _tag = \"DriveNoHandlerError\" as const;\n constructor(\n /** The Cmd `type` nothing in the handler record answered. */\n public readonly cmdType: string,\n /** Every Msg folded and Cmd dispatched before the miss. */\n public readonly trace: readonly DriveTraceEntry<M, C>[],\n ) {\n super(\n `@demlik/tea: drive found no interpret handler for Cmd type ` +\n `\"${cmdType}\" — the handlers record must answer every Cmd the ` +\n `machine emits.`,\n );\n }\n}\n\n/** The key `drive` hangs a partial trace off a rejected handler's error under. */\nconst TRACE_KEY = \"driveTrace\";\n\n/**\n * Read back the partial trace `drive` attached to an error a handler rejected\n * with. Returns `undefined` for anything `drive` did not annotate.\n *\n * A rejecting handler's error propagates out of `drive` UNSWALLOWED — same\n * value, same `instanceof`, same `_tag` — so a test that already branches on\n * the handler's own error type keeps doing so. The trace rides along on a\n * non-enumerable property so it is recoverable without changing how the error\n * prints or serializes.\n */\nexport function driveTraceOf<M, C>(\n err: unknown,\n): readonly DriveTraceEntry<M, C>[] | undefined {\n if (typeof err !== \"object\" || err === null) return undefined;\n const carried = (err as Record<string, unknown>)[TRACE_KEY];\n return Array.isArray(carried)\n ? (carried as readonly DriveTraceEntry<M, C>[])\n : undefined;\n}\n\n/**\n * Drive `machine` from `initial` through `msg` against the REAL interpret\n * `handlers`, feeding every settle Msg back until the machine goes quiet, and\n * hand back the settled state together with the whole history.\n *\n * `handlers` is the same record the machine declares as its `interpret` (e.g.\n * `ask.handlers()` for a jev knob), so the test drives the real interpreter and\n * mocks only the port beneath it.\n *\n * One round is: fold every pending Msg, then await a handler for every Cmd\n * those folds emitted. A Msg a handler returns — or fires through its injected\n * `dispatch` — is pending for the next round. A machine that emits no Cmd for\n * its seed Msg therefore settles in ONE round and never awaits a handler at\n * all.\n *\n * The `ctx` handed to each handler is `opts.ctx` augmented with a NO-OP `emit`,\n * so a handler that emits to a `Port` runs without a live runtime; nothing here\n * observes ports. Supply your own `emit` on `ctx` to capture them.\n *\n * @throws {DriveRoundsExceededError} when the machine is still emitting work\n * after `opts.maxRounds` rounds. It never returns a half-driven state.\n * @throws whatever a handler rejects with, unchanged — annotated with the\n * partial trace, readable via {@link driveTraceOf}.\n */\nexport async function drive<\n S,\n M extends { type: string },\n C extends Cmd,\n U extends Sub,\n Ctx,\n>(\n machine: Machine<S, M, C, U, Ctx>,\n initial: S,\n msg: M,\n handlers: Interpret<M, C, Ctx>,\n ...[opts]: Record<never, never> extends DriveOptions<Ctx>\n ? [opts?: DriveOptions<Ctx>]\n : [opts: DriveOptions<Ctx>]\n): Promise<DriveResult<S, M, C>> {\n const options = (opts ?? {}) as { ctx?: Ctx; maxRounds?: number };\n const maxRounds = options.maxRounds ?? DEFAULT_MAX_ROUNDS;\n const bound = bindMachine(machine, options.ctx as Ctx);\n\n // A handler's ctx is `Ctx & RequirementsOf<C> & PortEmitter`. The Ctx half is\n // the caller's; the PortEmitter half is the kernel's, and a driven test has\n // no kernel — so a no-op `emit` stands in, placed FIRST so a caller's own\n // `emit` wins.\n const noopEmit: PortEmitter = { emit: () => {} };\n const handlerCtx = {\n ...noopEmit,\n ...(options.ctx as unknown as Record<string, unknown> | undefined),\n };\n\n // Indexed dynamically: `Interpret`'s mapped type already proved every Cmd\n // variant has a cell, but `cmd.type` is a union at the value level and TS\n // cannot correlate the two sides of that index.\n const cells = handlers as unknown as Record<\n string,\n | ((\n cmd: C,\n ctx: unknown,\n dispatch?: (msg: M) => void,\n // biome-ignore lint/suspicious/noConfusingVoidType: mirrors `Interpret`'s own cell return — a follow-up Msg, or nothing\n ) => Promise<M | void>)\n | undefined\n >;\n\n const trace: DriveTraceEntry<M, C>[] = [];\n let state = initial;\n let pending: M[] = [msg];\n let round = 0;\n\n while (pending.length > 0) {\n round += 1;\n if (round > maxRounds) {\n throw new DriveRoundsExceededError<M, C>(maxRounds, round, trace);\n }\n\n // Drain the round's Msgs first, THEN its Cmds. Swapping the buffer before\n // the fold is what keeps a settle Msg out of the round that produced it —\n // the ordering question every hand-rolled copy of this loop re-answers.\n const folding = pending;\n pending = [];\n const emitted: C[] = [];\n for (const next of folding) {\n trace.push({ kind: \"msg\", msg: next });\n const [advanced, cmds] = bound.step(state, next);\n state = advanced;\n emitted.push(...cmds);\n }\n\n for (const cmd of emitted) {\n trace.push({ kind: \"cmd\", cmd });\n const cell = cells[cmd.type];\n if (cell === undefined) {\n throw new DriveNoHandlerError<M, C>(cmd.type, trace);\n }\n // biome-ignore lint/suspicious/noConfusingVoidType: mirrors `Interpret`'s own cell return — a follow-up Msg, or nothing\n let settle: M | void;\n try {\n settle = await cell(cmd, handlerCtx, (fired) => {\n pending.push(fired);\n });\n } catch (err) {\n // Unswallowed: the handler's own error is what leaves `drive`, so a\n // test's `instanceof` / `_tag` branch still lands. The trace rides on\n // a non-enumerable property (see `driveTraceOf`).\n if (typeof err === \"object\" && err !== null) {\n Object.defineProperty(err, TRACE_KEY, {\n value: [...trace],\n enumerable: false,\n configurable: true,\n writable: true,\n });\n }\n throw err;\n }\n if (settle !== undefined) pending.push(settle);\n }\n }\n\n return { state, trace };\n}\n","// ---------------------------------------------------------------------------\n// noopRuntime — a Runtime<S, M>-shaped value whose every method is inert.\n//\n// The motivating callsite: a machine's `ctx` requires a sibling Runtime<S,\n// M> reference (e.g. the queue runtime in extension/lib/queue-machine\n// reaches into an `auditRuntime: Runtime<...>` slot on its ctx so its\n// interpret handlers can dispatch into the audit machine). When the test\n// replays such a machine, `replay` never actually invokes `interpret`, so\n// the runtime reference is never dereferenced — but the type system\n// still requires a value of `Runtime<S, M>` shape at the ctx slot. Tests\n// today work around this with a hand-rolled 12-line NOOP_AUDIT_RUNTIME\n// const cast through `as unknown as Runtime<...>`.\n//\n// `noopRuntime<S, M>()` is that hand-roll, named once. It returns a real\n// Runtime<S, M> value whose every method is a structurally-correct no-op:\n//\n// - `dispatch`: resolves immediately, never advances state.\n// - `getState`: returns the optional `initialState` argument (or a\n// deliberate `undefined` cast as S — the function lies about its\n// return type when no initial state is provided; this is the trade-off\n// we accept for an ergonomic test substrate).\n// - `subscribe` / `observe` / `subscribePort`: register the listener,\n// return a no-op unsubscribe. The listener is never fired because the\n// state never changes.\n// - `emitPort`: no-op.\n// - `ready`: a Promise that resolves to the no-op runtime itself (the #45\n// `ready: Promise<Runtime<S, M>>` shape — already booted, nothing to wait\n// for).\n// - `stop`: resolved Promise<void>.\n//\n// Strengthens invariant 9 (the testing surface is named and small).\n// ---------------------------------------------------------------------------\n\nimport type { Port, Runtime } from \"../index\";\n\n/**\n * Construct an inert `Runtime<S, M>` value. Use at ctx sites that require\n * a Runtime reference but where the test never invokes `interpret` (so\n * the runtime is never dereferenced in practice).\n *\n * @param opts.initialState Optional value returned by `getState()`. Omit\n * when the test never calls `getState()` on the no-op runtime — most\n * tests only need the type-level slot filled.\n */\nexport function noopRuntime<S, M extends { type: string }>(opts?: {\n initialState?: S;\n}): Runtime<S, M> {\n const initialState = opts?.initialState;\n // Single source of truth for the \"do-nothing\" unsubscribe so multiple\n // subscribe call sites share the same function instance — purely cosmetic\n // (the function would be a no-op either way).\n const noopUnsubscribe = (): void => {};\n const runtime: Runtime<S, M> = {\n async dispatch(_msg: M): Promise<void> {\n // Resolves immediately. State never advances; listeners never fire.\n // Quiescent by default (#50), but the no-op has no follow-up chain to\n // drain, so single-step and run-to-quiescence collapse to the same no-op.\n },\n async dispatchOnce(_msg: M): Promise<void> {\n // Single-step sibling of `dispatch` (#50). Same no-op for the no-op\n // runtime — there is no tail to leave in flight.\n },\n async idle(): Promise<void> {\n // No tail to drain — the no-op runtime never enqueues a follow-up.\n },\n result(): S | undefined {\n // The no-op runtime never advances, so it is never terminal (#46).\n return undefined;\n },\n done(): Promise<S> {\n // Never terminal → the awaitable never settles, matching `run()` with no\n // `terminal` predicate. Returned as a never-resolving promise.\n return new Promise<S>(() => {});\n },\n getState(): S {\n // Lie if no initial state was provided. The caller's contract is\n // \"you accept the lie because you know replay never invokes\n // interpret and therefore never reads this.\" When the caller DOES\n // pass an initialState the lie collapses to truth.\n return initialState as S;\n },\n subscribe(_listener: () => void): () => void {\n return noopUnsubscribe;\n },\n observe(_observer: (msg: M, state: S) => void): () => void {\n return noopUnsubscribe;\n },\n onBoot(_handler: (state: S) => void): () => void {\n // Boot never fires on the inert runtime — it is \"already booted\" with no\n // initial fanout, so the handler is registered and never called (#47).\n return noopUnsubscribe;\n },\n on<K extends never>(\n _type: K,\n _handler: (event: never) => void,\n ): () => void {\n // The no-op runtime's event surface is `never` (no `events` projector),\n // so `on` is structurally present but uncallable — there is no `K` to\n // pass. The body is unreachable; it returns the shared no-op unsubscribe.\n return noopUnsubscribe;\n },\n subscribePort<T>(\n _port: Port<T>,\n _listener: (value: T) => void,\n ): () => void {\n return noopUnsubscribe;\n },\n emitPort<T>(_port: Port<T>, _value: T): void {\n // No-op. No subscribers to fan out to; nothing to do.\n },\n // Assigned just below — `ready` must resolve to `runtime` itself, which\n // can't be referenced inside its own object literal.\n ready: undefined as unknown as Promise<Runtime<S, M>>,\n async stop(): Promise<void> {\n // Resolves immediately. No in-flight tail to drain, no subs to clean.\n },\n };\n // Already \"booted\" — `ready` hands back the same runtime synchronously.\n runtime.ready = Promise.resolve(runtime);\n return runtime;\n}\n","// ---------------------------------------------------------------------------\n// stateFactory — typed phase-constructor API for a discriminated-union State.\n//\n// The motivating callsite: every reducer test today opens with ~60–80 lines\n// of hand-rolled factory functions, one per phase, each one's defaults\n// duplicated across files:\n//\n// function idleState() { return { type: \"idle\" }; }\n// function creatingWindowState(overrides?: Partial<...>) {\n// return { type: \"creating_window\", auditId: ..., wsUrl: ..., ...overrides };\n// }\n// ...\n//\n// `stateFactory<BackgroundState>({...})` collapses the whole block to one\n// declaration: pass each phase's REQUIRED defaults once (the type system\n// enforces every phase has an entry), and the helper builds an API of\n// `factory.idle()`, `factory.creating_window({...})`, etc.\n//\n// Two type-level guarantees:\n//\n// 1. Every phase has a defaults entry — `StateFactoryDefaults<S>` is a\n// mapped type keyed by every `S[\"type\"]`. Missing an entry is a\n// compile error (the @ts-expect-error in testing.spec.ts pins this).\n//\n// 2. Each phase's defaults match that phase's required shape minus the\n// `type` discriminator. Passing wrong fields fails to compile.\n//\n// The runtime is pure data:\n//\n// api[k] = (overrides) => ({\n// type: k,\n// ...defaults[k],\n// ...stripUndefined(overrides ?? {}),\n// })\n//\n// The `stripUndefined` pass is what preserves `exactOptionalPropertyTypes`\n// assignability — without it, calling `factory.creating_window({\n// queueItemId: undefined })` would land an explicit `queueItemId: undefined`\n// on the object, which is NOT assignable to `queueItemId?: string` under\n// `exactOptionalPropertyTypes`. Stripping undefineds before spread keeps\n// the boundary clean.\n//\n// `stripUndefined` is inlined (not imported from a shared stdlib) so this\n// package can stay dep-free of stdlib.\n// ---------------------------------------------------------------------------\n\n/** A factory-constructed phase: `S` narrowed to the arm whose discriminator equals `K`. */\ntype StateOf<S, K> = Extract<S, { type: K }>;\n\n/** A factory-constructed phase with the `type` discriminator removed — the \"fields the caller cares about\" half. */\ntype RequiredFields<T> = Omit<T, \"type\">;\n\n/**\n * Defaults shape passed to `stateFactory`. Mapped type over the discriminant\n * set: every phase MUST have an entry, and that entry MUST match the phase's\n * required fields minus the `type` discriminator.\n */\nexport type StateFactoryDefaults<S extends { type: string }> = {\n readonly [K in S[\"type\"]]: RequiredFields<StateOf<S, K>>;\n};\n\n/**\n * The API returned by `stateFactory`. One method per phase; calling it with\n * no args returns the default snapshot, calling it with a partial overrides\n * object merges those overrides in (with undefineds stripped — see\n * stripUndefined below).\n */\nexport type StateFactoryAPI<S extends { type: string }> = {\n readonly [K in S[\"type\"]]: (\n overrides?: Partial<RequiredFields<StateOf<S, K>>>,\n ) => StateOf<S, K>;\n};\n\n/**\n * Strip own-enumerable properties whose value is `undefined`. Used inside\n * the factory's spread chain so callers can pass `{ queueItemId: undefined }`\n * without the explicit-undefined landing on the result object (which would\n * fail to assign to a `queueItemId?: string` field under\n * `exactOptionalPropertyTypes`).\n *\n * Inlined (not imported from `a shared stdlib`) so `@demlik/tea/testing` can stay\n * dependency-free of stdlib. The 8-line cost of duplication is cheaper than\n * a package-graph edge.\n */\nfunction stripUndefined<T extends object>(obj: T): Partial<T> {\n const out: Partial<T> = {};\n for (const key of Object.keys(obj) as (keyof T)[]) {\n const value = obj[key];\n if (value !== undefined) {\n out[key] = value;\n }\n }\n return out;\n}\n\n/**\n * Build a typed phase-constructor API from per-phase defaults. The defaults\n * object MUST have one entry per `S[\"type\"]` discriminant; the returned API\n * has one method per phase. Each method merges the defaults with optional\n * overrides (undefineds stripped), then stamps the `type` discriminator.\n *\n * @example\n * type S = { type: \"a\"; n: number } | { type: \"b\"; s: string };\n * const f = stateFactory<S>({ a: { n: 0 }, b: { s: \"\" } });\n * f.a(); // { type: \"a\", n: 0 }\n * f.a({ n: 42 }); // { type: \"a\", n: 42 }\n * f.b({ s: \"hi\" }); // { type: \"b\", s: \"hi\" }\n */\nexport function stateFactory<S extends { type: string }>(\n defaults: StateFactoryDefaults<S>,\n): StateFactoryAPI<S> {\n // Build the API by walking the defaults' own keys. Each key corresponds\n // to one phase discriminator; the constructor closes over both the key\n // (for the `type` stamp) and that phase's defaults snapshot.\n const api = {} as { [k: string]: (overrides?: object) => unknown };\n for (const key of Object.keys(\n defaults,\n ) as (keyof StateFactoryDefaults<S>)[]) {\n const phaseDefaults = defaults[key];\n api[key as string] = (overrides?: object) => {\n // The strip-undefined pass lives here, not at the caller. See the\n // module header for the `exactOptionalPropertyTypes` rationale.\n const cleanedOverrides = overrides ? stripUndefined(overrides) : {};\n return { type: key, ...phaseDefaults, ...cleanedOverrides };\n };\n }\n return api as StateFactoryAPI<S>;\n}\n"]}
|
package/dist/timing/index.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
export { D as Debounced, d as debounce } from '../index-DHSUmQTR.js';
|
|
2
|
-
import { C as Cmd, S as Sub } from '../core-
|
|
3
|
-
import { T as TtlCache, a as DeadlineSub } from '../index-
|
|
4
|
-
export { D as ThrottledInputSettled, s as subscribeThrottledInput, d as throttledInputSettled } from '../index-
|
|
2
|
+
import { C as Cmd, S as Sub } from '../core-DnzqWy6u.js';
|
|
3
|
+
import { T as TtlCache, a as DeadlineSub } from '../index-HSYgG2Yt.js';
|
|
4
|
+
export { D as ThrottledInputSettled, s as subscribeThrottledInput, d as throttledInputSettled } from '../index-HSYgG2Yt.js';
|
|
5
5
|
import 'zod';
|
|
6
|
-
import '../types-
|
|
6
|
+
import '../types-BseNPIhr.js';
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* internal/timing/throttle — a timer-based call transformer that caps invocation
|
package/dist/timing/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export { debounce } from '../chunk-LFZUK6J2.js';
|
|
2
|
-
import { deadlineSub, initCache, get, set } from '../chunk-
|
|
3
|
-
export { subscribeDeadline as subscribeThrottledInput, deadlineExceeded as throttledInputSettled } from '../chunk-
|
|
4
|
-
import '../chunk-
|
|
2
|
+
import { deadlineSub, initCache, get, set } from '../chunk-22POWGFA.js';
|
|
3
|
+
export { subscribeDeadline as subscribeThrottledInput, deadlineExceeded as throttledInputSettled } from '../chunk-22POWGFA.js';
|
|
4
|
+
import '../chunk-66PY5WYF.js';
|
|
5
5
|
|
|
6
6
|
// src/internal/timing/throttle/index.ts
|
|
7
7
|
function throttle(fn, ms, opts) {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { Q as QueueItem } from '../index-
|
|
2
|
-
export { E as EnqueueInput, a as QueueItemStatus, c as createQueue } from '../index-
|
|
3
|
-
import '../runtime-types-
|
|
1
|
+
import { Q as QueueItem } from '../index-CSyMYDAB.js';
|
|
2
|
+
export { E as EnqueueInput, a as QueueItemStatus, c as createQueue } from '../index-CSyMYDAB.js';
|
|
3
|
+
import '../runtime-types-CMF13BBh.js';
|
|
4
4
|
import 'better-result';
|
|
5
|
-
import '../core-
|
|
5
|
+
import '../core-DnzqWy6u.js';
|
|
6
6
|
import 'zod';
|
|
7
7
|
|
|
8
8
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@demlik/tea",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.0",
|
|
4
4
|
"description": "TEA-faithful state machine substrate — one package, every host adapter. Core + subs + testing, plus React, Durable Object, Chrome extension, Node, in-memory, devtools, and property-testing modules as subpaths.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"tea",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/protocol.ts","../src/internal/resilience/circuit-breaker/index.ts","../src/internal/resilience/rate-limit/index.ts","../src/internal/resilience/resilient-call/index.ts"],"names":[],"mappings":"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","file":"chunk-AQ6ZIMEA.js","sourcesContent":["// ===========================================================================\n// Protocol — the ONE typed source of truth for the cross-module Msg/Cmd\n// discriminant strings (issue #60).\n// ===========================================================================\n//\n// The resilient/agent/auth plumbing speaks a small fixed vocabulary of\n// discriminant strings on `{ type: ... }` Msgs and Cmds. Before this module\n// those literals were retyped inline across ~8 modules — `resilient_ok` alone\n// appeared as a hand-written `\"resilient_ok\"` in resilient-call, with-resilience,\n// llm-call, agent, do/host, paginated-walk, reconciler. Adding a variant or\n// renaming one was shotgun surgery with no compiler to catch a missed site.\n//\n// `MsgType` is that vocabulary, declared ONCE `as const`. Each entry is BOTH:\n// - a value — `MsgType.ResilientOk` is the string `\"resilient_ok\"`, used at\n// construction sites (`{ type: MsgType.ResilientOk, ... }`), reducer keys\n// (`[MsgType.ResilientOk]: ...`), and switch labels (`case MsgType.ResilientOk:`).\n// - a type — `typeof MsgType.ResilientOk` is the literal type `\"resilient_ok\"`,\n// used in the `readonly type:` field of every Msg/Cmd interface.\n//\n// Because the value and the type both derive from the single `as const`\n// declaration, a new variant or a rename is a ONE-LINE change here that\n// propagates with full exhaustiveness — not an 8-file edit.\n//\n// This module is BEHAVIOR-PRESERVING: every string is byte-identical to the\n// literal it replaces. It is internal plumbing vocabulary, NOT a public event\n// surface — `resilient_ok` / `agent_tool_ok` stay PRIVATE (the agent's\n// `agentEvents` projector is the ONE place they are read; #47). Centralizing\n// the literal here does NOT re-expose them to consumers.\n\n/**\n * The closed set of cross-module Msg/Cmd discriminant strings. Declared `as\n * const` so each entry is usable as both a value (construction / reducer keys /\n * switch labels) and, via `typeof MsgType.X`, a literal type (`readonly type:`\n * fields). The single source of truth for issue #60.\n */\nexport const MsgType = {\n // --- resilient-call: the run Cmd + its two settle Msgs -------------------\n /** The \"run the port for `key`\" effect Cmd resilient-call emits. */\n ResilientRun: \"resilient_run\",\n /** PRIVATE success settle Msg (read only by `agentEvents`, #47). */\n ResilientOk: \"resilient_ok\",\n /** Failure settle Msg — routed to the `fail` verb (back off via retry). */\n ResilientErr: \"resilient_err\",\n\n // --- agent: the wired machine's own entry-point Msgs ---------------------\n /** Begin a run. */\n AgentStart: \"agent_start\",\n /** PRIVATE tool-fan-out success settle Msg (read only by `agentEvents`, #47). */\n AgentToolOk: \"agent_tool_ok\",\n /** Tool-fan-out failure settle Msg. */\n AgentToolErr: \"agent_tool_err\",\n /**\n * Re-fire the one outstanding effect on rehydrate (the do↔agent seam). Fired\n * by `do/host`'s `autoBoot` via the typed `agentBootMsg` constructor, matched\n * by the agent reducer — see `AgentBootPort` in `../agent`.\n */\n AgentBoot: \"agent_boot\",\n /**\n * Stop the run from outside and settle it `cancelled` — what an `AbortSignal`\n * handed to `driveToDone` / a defined agent's `run` dispatches on abort. A\n * transition, never a throw: the outcome lands in the Model, so a reload reads\n * a run that ended rather than one to resume.\n */\n AgentCancel: \"agent_cancel\",\n\n // --- agent: the context-compaction round-trip (#85) ----------------------\n /**\n * The \"summarize the oldest N turns\" effect Cmd the agent emits when its\n * `CompactionPolicy` asks to compact before a brain call. A DEDICATED\n * round-trip (design B1), distinct from the brain call's `resilient_run`, so\n * compaction is its own honest `Awaiting` state — see `../agent`.\n */\n CompactRun: \"compact_run\",\n /** Compaction round-trip resolved — carries the summary text the fold-back folds. */\n CompactOk: \"compact_ok\",\n /** Compaction round-trip failed (exhausted retry) — surfaced as data, not a stall. */\n CompactErr: \"compact_err\",\n\n // --- token-refresh: the two refresh-result Msgs --------------------------\n /** Refresh port resolved — carries the fresh `Token`. */\n TokenRefreshed: \"token_refreshed\",\n /** Refresh port rejected — carries the rejection (errors are data). */\n TokenRefreshFailed: \"token_refresh_failed\",\n\n // --- authed-call: the 401 trigger ----------------------------------------\n /** The 401 the consumer dispatches when a guarded call comes back unauthorized. */\n Unauthorized: \"unauthorized\",\n} as const;\n\n/** The union of every discriminant string in the protocol. */\nexport type MsgTypeValue = (typeof MsgType)[keyof typeof MsgType];\n","/**\n * @packageDocumentation\n * internal/resilience/circuit-breaker — per-target failure tracking as pure state + ops.\n *\n * Same shape as `../../work-queue/ops.ts`, `../rate-limit/`, and\n * `../../idempotency/`: a state type plus pure transition functions.\n * Host-agnostic by construction — no `Date.now()`, no timers, no I/O. The\n * caller injects the clock (`nowMs`) and wires the result into its own reducer\n * / interpret. This keeps the breaker inside TEA's invariant 2 (transitions are\n * pure: same inputs → same outputs, no `Date.now`) so a consumer can fold it\n * through `update` and exercise it with `replay` / the pbt fold without a fake\n * clock.\n *\n * Every op returns a NEW state value and never mutates its input (invariant 1 —\n * state is a value, not a place). `canPass` returns a `[next, allowed]` tuple so\n * the caller branches on the decision while threading the advanced state forward\n * — same convention as `rate-limit`'s `tryConsume` / `record`. `onSuccess` /\n * `onFailure` are the outcome-recording transitions the caller folds in once the\n * guarded call resolves.\n *\n * ## The phase machine\n *\n * A circuit breaker guards a fallible downstream target (an HTTP backend, a DB,\n * a flaky tool call). Instead of hammering a target that is already failing, it\n * trips OPEN after enough failures and fast-fails subsequent calls for a\n * cooldown, then probes with a few trial calls before fully closing again.\n * Three phases, each carrying ONLY its own data so impossible states are\n * unrepresentable (pattern 11):\n *\n * - **closed** — the healthy steady state. Calls pass through. Carries\n * `failures`: the count of *consecutive* failures since the last success.\n * A success resets it to 0; the `failureThreshold`-th failure trips the\n * breaker OPEN. Only `closed` has a failure counter — `open` and\n * `half_open` don't, because in those phases the count is irrelevant\n * (the breaker has already tripped / is already probing).\n *\n * - **open** — the tripped state. All calls fast-fail (no downstream load).\n * Carries `openedAtMs`: the clock reading at which the breaker tripped.\n * `canPass` compares `nowMs - openedAtMs` against `cooldownMs` to decide\n * when to start probing. Only `open` carries a timestamp — neither other\n * phase is timing anything.\n *\n * - **half_open** — the probing state. A limited number of trial calls are\n * allowed through to test whether the target has recovered. Carries\n * `probes`: the count of probes admitted so far this probing round.\n * `canPass` admits while `probes < halfOpenMaxProbes`, then fast-fails the\n * rest (so a slow target can't be flooded by every queued caller at once).\n * A probe success closes the breaker; a probe failure re-opens it (and\n * resets the cooldown clock). Only `half_open` counts in-flight probes.\n *\n * ### Transition table\n *\n * Rows are the current phase; columns are the event. `→` denotes the next\n * phase; `self` means unchanged. `nowMs` is the injected clock.\n *\n * | from \\ event | onSuccess | onFailure | canPass (→ allowed?) |\n * | ------------ | -------------- | -------------------------------------- | ------------------------------------------------------------------------------------ |\n * | closed | closed(0) | failures+1; trip → open(nowMs) at thr. | self → true (always) |\n * | open | self *(¹)* | self | elapsed ≥ cooldown → half_open(1), true; else self → false |\n * | half_open | → closed(0) | → open(nowMs) (probe failed) | probes < max → half_open(probes+1), true; else self → false |\n *\n * *(¹)* `onSuccess` on `open` \"shouldn't happen\": no call was admitted while\n * open, so no success can be reported for one. We leave the state unchanged\n * rather than throw — the breaker is a coordination aid, not a correctness\n * gate, and a spurious success report (e.g. a late-arriving result from before\n * the trip) must not silently un-trip the breaker. `onFailure` on `open` is\n * likewise a no-op: the breaker is already tripped; the `open` clock is anchored\n * at the trip, not bumped by late failures.\n *\n * ### The probe-admission asymmetry\n *\n * Entering `half_open` happens inside `canPass`, not `onSuccess` / `onFailure`,\n * because the cooldown is a *time* condition and only `canPass` reads the clock\n * against `openedAtMs`. The first `canPass` after the cooldown elapses both\n * transitions the phase AND admits the probe (`[half_open(1), true]`) — one\n * caller drives the recovery attempt. Subsequent `canPass` calls in the same\n * round admit up to `halfOpenMaxProbes` total, then fast-fail until a probe\n * resolves and `onSuccess` / `onFailure` moves the phase out of `half_open`.\n *\n * This module is a textbook three-state machine — a clean `/machine-viz` demo.\n *\n * Internal to the package (`src/internal/resilience/`), not a published\n * subpath (#46); same one-shape-per-module rule as `work-queue`, `rate-limit`,\n * and `idempotency`.\n */\n\n/**\n * Circuit-breaker policy — pure configuration, no mutable state. One policy is\n * shared across all the calls guarded by one logical breaker (often a\n * module-scope const). `CircuitState` carries the mutable counts; this carries\n * the thresholds those counts are compared against.\n */\nexport interface CircuitPolicy {\n /**\n * Consecutive failures in `closed` that trip the breaker `open`. The\n * `failureThreshold`-th failure is the one that trips — with a threshold of\n * 5, the breaker survives 4 failures and opens on the 5th.\n */\n readonly failureThreshold: number;\n /**\n * How long the breaker stays `open` before `canPass` starts admitting\n * probes, in milliseconds. Measured from `openedAtMs`; the cutoff is\n * half-open (`elapsed >= cooldownMs` admits), matching `rate-limit` /\n * `idempotency` window edges — a breaker opened at `t` first probes at\n * `t + cooldownMs`.\n */\n readonly cooldownMs: number;\n /**\n * How many probe calls `half_open` admits before fast-failing the rest of\n * the round. Caps the load on a recovering target — a single slow probe\n * can't be joined by every queued caller. The round ends when a probe\n * resolves: success closes the breaker, failure re-opens it.\n */\n readonly halfOpenMaxProbes: number;\n}\n\n/**\n * Sensible defaults: trip after 5 consecutive failures, cool down for 30s,\n * admit a single probe before deciding. Tuned for guarding a network backend;\n * override per call site rather than mutating this shared const.\n */\nexport const defaultCircuitPolicy: CircuitPolicy = {\n failureThreshold: 5,\n cooldownMs: 30_000,\n halfOpenMaxProbes: 1,\n};\n\n/**\n * The breaker's phase. A discriminated union on `phase` so each phase carries\n * ONLY its own data and impossible combinations are unrepresentable (pattern\n * 11): there is no state with both a failure count AND an open timestamp,\n * because such a state is meaningless. `phase` is the discriminant; every field\n * is `readonly` so the whole value is trivially JSON-serializable into a\n * consumer's `Store<S>` and safe to thread through a pure reducer.\n *\n * - `closed` carries `failures` — consecutive failures since last success.\n * - `open` carries `openedAtMs` — clock reading when it tripped.\n * - `half_open` carries `probes` — probes admitted this round.\n */\nexport type CircuitState =\n | { readonly phase: \"closed\"; readonly failures: number }\n | { readonly phase: \"open\"; readonly openedAtMs: number }\n | { readonly phase: \"half_open\"; readonly probes: number };\n\n/** The starting state: closed with zero recorded failures. */\nexport function initCircuit(): CircuitState {\n return { phase: \"closed\", failures: 0 };\n}\n\n/**\n * Record a successful guarded call. PURE — returns a new state; the input is\n * never mutated.\n *\n * - `closed` → reset `failures` to 0 (a success breaks the consecutive run).\n * - `half_open` → the probe succeeded; the target has recovered → `closed`.\n * - `open` → unchanged. A success \"shouldn't happen\" while open (no call\n * was admitted to produce one); a late/spurious success must\n * not silently un-trip the breaker. See module header note ¹.\n */\nexport function onSuccess(\n state: CircuitState,\n _policy: CircuitPolicy,\n): CircuitState {\n switch (state.phase) {\n case \"closed\":\n // Already closed at 0 → return the same shape (cheap; the consumer's\n // store only re-persists on a real change if it dedupes).\n return state.failures === 0 ? state : { phase: \"closed\", failures: 0 };\n case \"half_open\":\n return { phase: \"closed\", failures: 0 };\n case \"open\":\n return state;\n }\n}\n\n/**\n * Record a failed guarded call. PURE — returns a new state; the input is never\n * mutated. `nowMs` is the injected clock, stamped onto the `open` phase when the\n * breaker trips so `canPass` can measure the cooldown later.\n *\n * - `closed` → `failures + 1`; if that reaches `failureThreshold`, trip to\n * `open(nowMs)`. The threshold-th failure is the one that trips.\n * - `half_open` → the probe failed; the target is still unhealthy →\n * `open(nowMs)`, restarting the cooldown clock so a fresh\n * cooldown elapses before the next probe round.\n * - `open` → unchanged. The breaker is already tripped; the `open` clock\n * is anchored at the original trip, not bumped by late\n * failures. See module header note ¹.\n */\nexport function onFailure(\n state: CircuitState,\n policy: CircuitPolicy,\n nowMs: number,\n): CircuitState {\n switch (state.phase) {\n case \"closed\": {\n const failures = state.failures + 1;\n // `>=` not `===`: a threshold of 0 (or any already-exceeded count from a\n // policy change) trips immediately rather than over-counting forever.\n return failures >= policy.failureThreshold\n ? { phase: \"open\", openedAtMs: nowMs }\n : { phase: \"closed\", failures };\n }\n case \"half_open\":\n // A failed probe re-opens and resets the cooldown clock to nowMs.\n return { phase: \"open\", openedAtMs: nowMs };\n case \"open\":\n return state;\n }\n}\n\n/**\n * Decide whether a call may pass, advancing the phase as a side effect of the\n * decision. PURE — returns `[next, allowed]`; the input is never mutated.\n * Branch on the boolean, thread the state forward regardless:\n *\n * const [c1, allowed] = canPass(state.circuit, policy, nowMs);\n * if (!allowed) return [{ ...state, circuit: c1 }, [fastFailCmd]];\n * return [{ ...state, circuit: c1 }, [doGuardedCallCmd]];\n *\n * - `closed` → `[state, true]` — always passes; failures are recorded by\n * `onFailure` once the call resolves, not gated here.\n * - `open` → if `nowMs - openedAtMs >= cooldownMs` the cooldown has\n * elapsed → enter `half_open` and admit this caller as the\n * first probe: `[half_open(1), true]`. Otherwise still cooling\n * down → `[state, false]` (fast-fail, no downstream load).\n * - `half_open` → admit while `probes < halfOpenMaxProbes`, incrementing the\n * count: `[half_open(probes+1), true]`. Once the cap is hit →\n * `[state, false]` until a probe resolves and `onSuccess` /\n * `onFailure` moves the phase out of `half_open`.\n *\n * Entering `half_open` lives here (not in `onSuccess` / `onFailure`) because the\n * cooldown is a time condition and only `canPass` reads `nowMs` against\n * `openedAtMs`. See the module header's probe-admission note.\n */\nexport function canPass(\n state: CircuitState,\n policy: CircuitPolicy,\n nowMs: number,\n): readonly [CircuitState, boolean] {\n switch (state.phase) {\n case \"closed\":\n return [state, true];\n case \"open\": {\n const elapsed = nowMs - state.openedAtMs;\n // Half-open cutoff: elapsed exactly == cooldownMs admits the first probe,\n // matching the >= edge `rate-limit` / `idempotency` use for their windows.\n if (elapsed >= policy.cooldownMs) {\n return [{ phase: \"half_open\", probes: 1 }, true];\n }\n return [state, false];\n }\n case \"half_open\":\n if (state.probes < policy.halfOpenMaxProbes) {\n return [{ phase: \"half_open\", probes: state.probes + 1 }, true];\n }\n return [state, false];\n }\n}\n","/**\n * internal/resilience/rate-limit — two rate limiters as pure state + ops.\n *\n * Same shape as `../../work-queue/ops.ts`: a state type plus pure\n * transition functions. Host-agnostic by construction — no `Date.now()`, no\n * timers, no I/O. The caller injects the clock (`nowMs`) and wires the result\n * into its own reducer / interpret. This keeps the limiters inside TEA's\n * invariant 2 (transitions are pure: same inputs → same outputs, no `Date.now`)\n * so a consumer can fold them through `update` and exercise them with `replay`\n * / the pbt fold without a fake clock.\n *\n * Every op returns a NEW state value and never mutates its input (invariant 1 —\n * state is a value, not a place). The `tryConsume` / `record` ops return a\n * `[next, ok]` tuple so the caller branches on the decision while threading the\n * advanced state forward; `refill` / `remaining` are read-shaped helpers.\n *\n * Two complementary algorithms, picked by the cost the caller wants to pay:\n *\n * - **Token bucket** — O(1) state (four numbers), smooths bursts against a\n * steady refill rate. Allows a burst up to `capacity`, then throttles to\n * `refillPerSec`. Cheapest; the default choice.\n * - **Sliding window log** — O(limit) state (one timestamp per in-window\n * hit), exact \"no more than `limit` events per `windowMs`\". Pay the memory\n * for precise window edges when a token bucket's continuous refill would\n * let through too many at a boundary.\n *\n * Internal to the package (`src/internal/resilience/`), not a published\n * subpath (#46); same one-shape-per-module rule as `work-queue` and `do`.\n */\n\n// ===========================================================================\n// Token bucket\n// ===========================================================================\n\n/**\n * A token bucket: `tokens` of `capacity` available now, replenished at\n * `refillPerSec` tokens per second. `lastRefillMs` is the clock reading at\n * which `tokens` was last computed — `refill` advances it. All four fields are\n * plain numbers so the whole bucket is trivially JSON-serializable into a\n * consumer's `Store<S>`.\n */\nexport interface TokenBucket {\n readonly tokens: number;\n readonly capacity: number;\n readonly refillPerSec: number;\n readonly lastRefillMs: number;\n}\n\n/**\n * Create a full bucket. Starts at `capacity` tokens (the conventional \"a fresh\n * limiter lets the first burst through\" behavior) and stamps `lastRefillMs`\n * with the caller's clock so the first `refill` measures elapsed time from\n * boot, not from epoch 0.\n */\nexport function initBucket(\n capacity: number,\n refillPerSec: number,\n nowMs: number,\n): TokenBucket {\n return {\n tokens: capacity,\n capacity,\n refillPerSec,\n lastRefillMs: nowMs,\n };\n}\n\n/**\n * Add the tokens accrued since `lastRefillMs`, clamped to `capacity`, and\n * advance `lastRefillMs` to `nowMs`. PURE — returns a new bucket; the input is\n * untouched.\n *\n * A `nowMs` at or before `lastRefillMs` (clock not advanced, or skewed\n * backwards) adds a non-positive amount; `Math.max(0, …)` floors the elapsed\n * term so a backwards clock can never *remove* tokens. `lastRefillMs` still\n * advances to `nowMs` so the bucket tracks the latest reading the caller\n * presented — the next forward step measures from there, not from a stale\n * future timestamp.\n */\nexport function refill(bucket: TokenBucket, nowMs: number): TokenBucket {\n const elapsedSec = Math.max(0, nowMs - bucket.lastRefillMs) / 1000;\n const replenished = bucket.tokens + elapsedSec * bucket.refillPerSec;\n return {\n ...bucket,\n tokens: Math.min(bucket.capacity, replenished),\n lastRefillMs: nowMs,\n };\n}\n\n/**\n * Refill against `nowMs`, then attempt to take `n` tokens (default 1).\n *\n * On success returns `[next, true]` with `n` subtracted; on failure returns\n * `[refilled, false]` — the refilled bucket (so the caller keeps the advanced\n * clock + accrued tokens) with no tokens spent. Branch on the boolean, thread\n * the bucket forward regardless:\n *\n * const [b1, ok] = tryConsume(bucket, nowMs);\n * return ok ? [{ ...state, bucket: b1 }, [doWork]] : [{ ...state, bucket: b1 }, []];\n *\n * PURE — input bucket is never mutated.\n */\nexport function tryConsume(\n bucket: TokenBucket,\n nowMs: number,\n n = 1,\n): readonly [TokenBucket, boolean] {\n const refilled = refill(bucket, nowMs);\n if (refilled.tokens >= n) {\n return [{ ...refilled, tokens: refilled.tokens - n }, true];\n }\n return [refilled, false];\n}\n\n// ===========================================================================\n// Sliding window log\n// ===========================================================================\n\n/**\n * A sliding-window log: the timestamps of every hit still inside the trailing\n * `windowMs`, capped at `limit` events per window. `hits` is `readonly` and\n * kept oldest-first (pruning slices the stale prefix off the front, recording\n * appends to the back), so the array is its own sorted index. Exact at the\n * window edge — unlike a token bucket's continuous refill, a hit leaves the\n * count the instant it ages past `windowMs`.\n */\nexport interface SlidingWindow {\n readonly hits: readonly number[];\n readonly windowMs: number;\n readonly limit: number;\n}\n\n/** Create an empty window. No hits recorded yet. */\nexport function initWindow(windowMs: number, limit: number): SlidingWindow {\n return { hits: [], windowMs, limit };\n}\n\n/**\n * The window-edge rule, in one place: keep only the hits still live at `nowMs`.\n * The cutoff is half-open — a hit exactly `windowMs` old is dropped (`> cutoff`,\n * not `>=`), so a hit recorded at `t` no longer counts at `t + windowMs` and the\n * window's capacity fully recovers `windowMs` after the last hit. `record` (the\n * writer) and `remaining` (the read helper) both prune through here so a change\n * to the edge semantics can't leave the reader disagreeing with the writer.\n */\nfunction prune(window: SlidingWindow, nowMs: number): readonly number[] {\n const cutoff = nowMs - window.windowMs;\n return window.hits.filter((t) => t > cutoff);\n}\n\n/**\n * Prune hits older than `nowMs - windowMs`, then record `nowMs` if there is\n * room. Returns `[next, true]` with the hit appended when `hits.length < limit`\n * after pruning, else `[pruned, false]` — the pruned window (capacity recovers\n * as old hits age out) with nothing appended.\n *\n * The cutoff is half-open (see {@link prune}): a hit exactly `windowMs` old is\n * pruned, so a hit recorded at `t` no longer counts at `t + windowMs`. That\n * makes the window's capacity fully recover `windowMs` after the last hit, the\n * behavior tests pin.\n *\n * PURE — input window is never mutated; `hits` is rebuilt by `filter` +\n * spread.\n */\nexport function record(\n window: SlidingWindow,\n nowMs: number,\n): readonly [SlidingWindow, boolean] {\n const pruned = prune(window, nowMs);\n if (pruned.length < window.limit) {\n return [{ ...window, hits: [...pruned, nowMs] }, true];\n }\n return [{ ...window, hits: pruned }, false];\n}\n\n/**\n * How many more hits `record` would accept at `nowMs` without blocking —\n * `limit` minus the live (post-prune) hit count, floored at 0. Read-only: it\n * prunes a local copy to answer but returns no new window. Use it to render a\n * \"N requests remaining\" affordance without committing a hit.\n */\nexport function remaining(window: SlidingWindow, nowMs: number): number {\n const live = prune(window, nowMs).length;\n return Math.max(0, window.limit - live);\n}\n","/**\n * @packageDocumentation\n *\n * Internal to the package (`src/internal/resilience/`, #46). The published\n * `./resilient-call` door is closed; the consumer-facing shape is the\n * higher-order `withResilience(base, config)` wrapper in `../with-resilience`\n * (TEA-idiomatic: it wraps any existing machine instead of forcing a bespoke\n * embedded slice), and this module is its implementation. The APIs are NOT\n * drop-in; migration map, for the in-tree embedded-slice consumers:\n *\n * - `createResilientCall(config, rng)` + hand-wiring `init` / the verbs /\n * `subs` / `handlers` into your machine → `withResilience(base, { target,\n * ...bricks }, rng)`. Nothing is wired by hand anymore: the wrapper\n * intercepts the `target` base Cmd and routes it through the gate.\n * - `ResilientConfig` bricks (`retry` / `circuit` / `rateLimit` / `cache` /\n * `deadline`) → unchanged; `ResilienceConfig` extends the same knob and\n * adds `target` (required), `keyOf?` (per-call key — was `attempt`'s `key`\n * argument), and `at?` (time-as-data reader — REQUIRED with any\n * time-sensitive brick; `withResilience` throws at construction without it).\n * - The slice at your chosen Model field + `liftResilience` → the wrapper\n * owns `model.$resilience`; there is nothing to lift.\n * - Verbs `attempt` / `succeed` / `fail` / `onTimer` → internal to the\n * wrapper; results route as `$resilience:ok` / `$resilience:err` /\n * `$resilience:timer` Msgs through the merged `update` (the old\n * `resilient_run` / `resilient_ok` / `resilient_err` vocabulary is renamed\n * into the `$resilience:` namespace).\n * - `settleFailed` → NO equivalent. It exists for embedded-slice consumers\n * (`authed-call`, `paginated-walk`) that settle a call without touching the\n * breaker; the wrapper exposes no caller-facing settle verb.\n * - `handlers(ports)` / `subs(state)` / the `subscribeDeadline` re-export →\n * handled by the wrapper; run the returned machine like any other.\n *\n * The in-tree modules that embed the slice (`with-resilience` itself, `agent`,\n * `llm-call`, `authed-call`, `reconciler`, `paginated-walk`) import this module\n * directly — that is its role.\n *\n * internal/resilience/resilient-call — the ROOT composition: a single resilience knob\n * that bundles cache → circuit-breaker → rate-limit → exponential-backoff retry\n * (with a deadline-driven retry timer) around any fallible `(input) => Promise`\n * port.\n *\n * This is `examples/resilient-fetch.ts`'s hand-wired `attempt()` generalized:\n *\n * - It is **keyed**. The example tracked one URL at a time; this tracks N\n * concurrent logical calls, each under a string `key`. The cache, the\n * waiting-retry bookkeeping, and the retry timers are all per-key, while the\n * circuit breaker and token bucket are shared (one downstream target, one\n * breaker / one bucket — the example's implicit single-target assumption\n * made explicit).\n * - It is **config-driven**, not hand-wired. You hand `createResilientCall`\n * a `ResilientConfig` and get back the uniform knob contract every L2\n * composition exposes: `init()`, the four verbs (`attempt` / `succeed` /\n * `fail` / `onTimer`), `subs(state)`, and `handlers(ports)`.\n * - Every brick is **optional**. Omit `cache` and the cache gate disappears\n * (`get` is never consulted); omit `circuit` and every call passes the\n * breaker; omit `rateLimit` and the bucket never throttles; omit `retry`\n * and a failure is terminal (no backoff, no timer); omit `deadline` and no\n * overall time budget is armed. \"Omit a brick → omit its gate\" is the\n * whole config story — there are no other modes.\n *\n * Four later compositions (`llm-call`, `paginated-walk`, `authed-call`,\n * `reconciler`) inherit THIS slice + verb shape, so the contract here is\n * load-bearing: the slice is a flat record of brick states, every verb returns\n * `readonly [State, Cmd[]]`, time is always an `at` / `now` parameter, and the\n * RNG used for jitter is injected on the config (never read inside a verb).\n *\n * ## The two non-negotiables (canon)\n *\n * - **Durable** — the slice is a plain-data Model field (brick states are all\n * JSON-serializable), so it survives DO eviction / reload.\n * - **Replayable** — every transition is a verb returning new state + Cmds;\n * `replay` reconstructs it exactly. Nothing here closes over time or RNG.\n *\n * ## Where the clock / RNG live\n *\n * Inside the verbs: nowhere. Time arrives as `at` on `attempt` / `onTimer` and\n * is carried on the result / timer Msgs the consumer dispatches. Jitter RNG is\n * injected once at `createResilientCall(config, rng)` and threaded into\n * `nextDelayMs` at the verb boundary — deterministic in tests, `Math.random`\n * by default. The ONLY clock read is `Date.now()` inside the `handlers` port\n * (the effect boundary), exactly as `resilient-fetch.ts` stamps its result Msgs.\n *\n * That same `at` is what makes a DURATION-bounded retry budget reachable here:\n * `recordFailure` / `shouldRetry` are fed the failure's observation instant,\n * so `config.retry` accepts any `AnyRetryPolicy` — a count, a wall-clock outage\n * budget (`maxElapsedMs`), or explicit `unbounded: true` — with no new argument\n * on any verb and no clock read inside one.\n *\n * ## Typical wiring\n *\n * const rc = createResilientCall<string, Result>({\n * cache: { ttlMs: 60_000 },\n * circuit: { threshold: 5, cooldownMs: 30_000 },\n * rateLimit: { capacity: 10, refillPerSec: 5 },\n * retry: defaultRetryPolicy,\n * deadline: { ms: 5_000 },\n * });\n *\n * // in the machine:\n * init: () => [{ resilience: rc.init() }, []],\n * update: {\n * fetch: (s, m) => lift(s, rc.attempt(s.resilience, m.key, m.input, m.at)),\n * call_ok: (s, m) => lift(s, rc.succeed(s.resilience, m.key, m)),\n * call_err:(s, m) => lift(s, rc.fail(s.resilience, m.key, m)),\n * retry_due:(s, m) => lift(s, rc.onTimer(s.resilience, m)),\n * },\n * subscriptions: (s) => rc.subs(s.resilience),\n * subscribe: { deadline: subscribeDeadline },\n * interpret: rc.handlers({ run: ctx.call }),\n */\n\nimport { z } from \"zod\";\nimport { Cmd, type CmdOf, type NoCtx, tryInterpret } from \"../../../index\";\nimport { MsgType } from \"../../../protocol\";\nimport { without } from \"../../../pure/core\";\nimport {\n type AnyRetryPolicy,\n asRng,\n initRetry,\n nextDelayMs,\n type RetryState,\n recordFailure,\n shouldRetry,\n} from \"../../../retry-backoff\";\nimport {\n get as cacheGet,\n set as cacheSet,\n initCache,\n type TtlCache,\n} from \"../cache\";\nimport {\n type CircuitState,\n canPass,\n initCircuit,\n onFailure,\n onSuccess,\n} from \"../circuit-breaker\";\nimport {\n type DeadlineExceeded,\n type DeadlineSub,\n deadlineSub,\n subscribeDeadline,\n} from \"../deadline\";\nimport { initBucket, type TokenBucket, tryConsume } from \"../rate-limit\";\n\n// ===========================================================================\n// Config — the knob. Every brick optional; omit a brick → omit its gate.\n// ===========================================================================\n\n/** Circuit-breaker knob — only the two numbers a consumer ever tunes. */\nexport interface CircuitConfig {\n /** Consecutive failures that trip the breaker open. */\n readonly threshold: number;\n /** How long the breaker stays open before admitting a probe, in ms. */\n readonly cooldownMs: number;\n /**\n * Probes admitted in `half_open` before fast-failing the rest of the round.\n * Defaults to 1 (a single probe decides recovery) — matches the circuit-\n * breaker module's `defaultCircuitPolicy`.\n */\n readonly halfOpenMaxProbes?: number;\n}\n\n/** Token-bucket rate-limit knob. */\nexport interface RateLimitConfig {\n /** Burst size — the bucket starts full at `capacity`. */\n readonly capacity: number;\n /** Steady-state refill rate, tokens per second. */\n readonly refillPerSec: number;\n}\n\n/** Per-entry TTL cache knob. */\nexport interface CacheConfig {\n /** How long a cached success stays fresh, in ms. */\n readonly ttlMs: number;\n}\n\n/**\n * Overall deadline knob — a budget of IN-PROCESS time per in-flight call.\n *\n * The budget is charged in elapsed time this process actually observed, not by\n * a position on the host's wall clock. Time in which no process exists — a DO\n * eviction, a crash between attempts, a redeploy — is NOT charged: `resume`\n * re-anchors every live call's charging clock, so downtime lands outside every\n * segment. A timeout therefore means \"the tool took too long\", never \"the host\n * was away too long\" (#144).\n */\nexport interface DeadlineConfig {\n /** Budget of in-process time from the moment a call starts, in ms. */\n readonly ms: number;\n}\n\n/**\n * The resilience knob. EVERY field is optional: omit a brick and its gate is\n * skipped entirely (the slice still carries a default brick state so the shape\n * stays uniform across configs, but the gate is never consulted). An empty\n * config `{}` is a valid pass-through — a bare effect with no resilience at all.\n */\nexport interface ResilientConfig {\n /**\n * The backoff brick. Omit and a failure is terminal (no backoff, no timer).\n *\n * Any bound the `../retry-backoff` union admits: a count (`RetryPolicy`), a\n * wall-clock outage budget (`DurationRetryPolicy`), or explicit\n * `unbounded: true`. A duration bound needs no extra wiring: every path that\n * records a failure already holds the instant it was observed as DATA —\n * `fail`'s `msg.at` (stamped at the interpret boundary) and `gate`'s `at`\n * (the caller's `attempt` / the retry timer's `atMs`) — so the streak clock\n * is fed from a Msg, never from a `Date.now()` inside a verb (invariant 2).\n */\n readonly retry?: AnyRetryPolicy;\n readonly circuit?: CircuitConfig;\n readonly rateLimit?: RateLimitConfig;\n readonly cache?: CacheConfig;\n readonly deadline?: DeadlineConfig;\n}\n\n// ===========================================================================\n// Slice — the Model field this knob owns. A flat record of brick states.\n// ===========================================================================\n\n/**\n * What is left of one call's deadline budget, and where the currently open\n * charging segment starts. Plain data, so it persists with the rest of the slice\n * — but the DURABLE fact is `remainingMs`, a duration, never an instant.\n *\n * The accounting has one rule, applied at every transition that carries a clock\n * (`attempt`'s `at`, a settle Msg's `at`, a timer's `atMs`): charge the segment\n * that just closed and open a new one at the same instant —\n * `remainingMs -= at - chargingSinceMs; chargingSinceMs = at`. See {@link charge}.\n *\n * `chargingSinceMs` is an IN-PROCESS anchor, not a persisted deadline. It is\n * meaningful only while the process that wrote it is alive, and `resume` re-bases\n * it on every live call before anything reads it after a reload — which is what\n * keeps downtime out of the budget. Nothing derives a timeout from it across a\n * resume; the only durable answer to \"how much budget is left\" is `remainingMs`.\n *\n * `remainingMs` is `0` when the `deadline` brick is absent, which is also the\n * \"no cap armed\" reading `subs` uses — a call with a real budget always has\n * `remainingMs > 0`, because `gate` settles a call whose budget reached zero\n * rather than letting it enter `running`.\n */\nexport interface CallBudget {\n /** In-process ms left when the open segment started. `0` = no deadline brick. */\n readonly remainingMs: number;\n /** The instant the open segment started, on the clock of the process that opened it. */\n readonly chargingSinceMs: number;\n}\n\n/**\n * Per-key call phase. `key` indexes a logical call; several can be in flight at\n * once (the generalization of the example's single `url`). Discriminated on\n * `phase` so each phase carries only its own data (pattern 11):\n *\n * - `idle` — never attempted, or fully settled and forgotten.\n * - `running` — the effect Cmd is out; awaiting `succeed` / `fail`.\n * Carries the deadline budget ({@link CallBudget}) and the\n * last `input` so a retry can re-issue it.\n * - `waiting_retry` — a transient failure backed off; the retry timer is armed\n * for `retryAtMs`. Carries `input` for the re-attempt, and\n * the same budget — backoff is in-process time, so it is\n * charged like an attempt.\n * - `circuit_open` — the breaker fast-failed this attempt. Terminal for this\n * call until the consumer re-attempts after cooldown.\n * - `succeeded` — settled OK. `result` is the value the port produced.\n * - `failed` — settled Err (out of retries, or no retry brick). `error`\n * carries the last failure.\n */\nexport type CallPhase<I, R> =\n | { readonly phase: \"idle\" }\n | {\n readonly phase: \"running\";\n readonly input: I;\n readonly budget: CallBudget;\n }\n | {\n readonly phase: \"waiting_retry\";\n readonly input: I;\n readonly retryAtMs: number;\n readonly budget: CallBudget;\n }\n | { readonly phase: \"circuit_open\" }\n | { readonly phase: \"succeeded\"; readonly result: R }\n | { readonly phase: \"failed\"; readonly error: unknown };\n\n/**\n * The slice. `circuit` / `bucket` are SHARED across keys (one downstream target\n * → one breaker, one bucket). `retry` and `cache` are PER KEY (each logical call\n * has its own backoff count + cached result), as is the per-call `CallPhase`.\n * Every field is plain data → the whole slice serializes into a `Store<S>`.\n *\n * `retry` / `cache` keep a default brick value even when the corresponding\n * config brick is omitted, so the slice shape is identical regardless of which\n * bricks the consumer enabled — the verbs simply never consult the unused brick.\n */\nexport interface ResilientState<I, R> {\n readonly circuit: CircuitState;\n readonly bucket: TokenBucket;\n /**\n * Per-key backoff counter. Entries minted by `backoff` are `TimedRetryState`s\n * (they carry the streak's `firstFailureAtMs`, since every failure path holds\n * the observation instant); the field is typed at the `RetryState` supertype\n * because a key that has never failed has no streak, and a slice persisted\n * before the origin existed rehydrates without one. Still plain data.\n */\n readonly retry: Readonly<Record<string, RetryState>>;\n readonly cache: TtlCache<R>;\n readonly calls: Readonly<Record<string, CallPhase<I, R>>>;\n}\n\n// ===========================================================================\n// Cmds + Msgs the knob speaks. Generic over the port input `I`.\n// ===========================================================================\n\n/**\n * The one effect this knob emits: \"run the port for `key` with `input`\". The\n * consumer's `handlers(ports)` interprets it; it carries no closure — `input`\n * is plain data (invariant 3).\n */\nexport function runCmdDef<I, R>() {\n return Cmd.define(MsgType.ResilientRun, {\n input: z.custom<{ readonly key: string; readonly input: I }>(),\n ok: z.custom<R>(),\n err: [\"port_rejected\", \"deadline_exceeded\"],\n });\n}\nexport type RunCmdDef<I, R> = ReturnType<typeof runCmdDef<I, R>>;\nexport type RunCmd<I> = CmdOf<RunCmdDef<I, unknown>>;\n\n/** Settle Msgs the `handlers` port dispatches back. `at` is stamped at the boundary. */\nexport type SucceedMsg<R> = {\n readonly type: typeof MsgType.ResilientOk;\n readonly key: string;\n readonly result: R;\n readonly at: number;\n};\nexport type FailMsg = {\n readonly type: typeof MsgType.ResilientErr;\n readonly key: string;\n readonly error: unknown;\n readonly at: number;\n};\n\n/** The retry / deadline timer Msg — `DeadlineExceeded`, keyed by the call `key`. */\nexport type ResilientTimerMsg = DeadlineExceeded;\n\n/**\n * The plain-data error a deadline-failed call settles with. A `{_tag, ...}`\n * sentinel — NEVER a `new Error(...)` — so the slice stays JSON-serializable\n * (Error objects round-trip to `{}` and carry a stack trace, both of which break\n * the slice's durability invariant). `id` / `atMs` echo the timer that fired so\n * a consumer can attribute the failure to its deadline.\n */\nexport type DeadlineExceededError = {\n readonly _tag: \"deadline_exceeded\";\n readonly id: string;\n readonly atMs: number;\n};\n\n/** Ports the consumer supplies to `handlers`. */\nexport interface ResilientPorts<I, R> {\n /** The fallible work this knob wraps. Throws on failure → routed to `fail`. */\n readonly run: (input: I, key: string) => Promise<R>;\n}\n\n// ===========================================================================\n// Internal brick-policy derivation — config → the L1 policy shapes.\n// ===========================================================================\n\n// A circuit policy is only built when the brick is configured; absence is\n// represented as `null` so the gate can short-circuit to \"always pass\".\nfunction circuitPolicy(config: ResilientConfig) {\n if (config.circuit === undefined) return null;\n return {\n failureThreshold: config.circuit.threshold,\n cooldownMs: config.circuit.cooldownMs,\n halfOpenMaxProbes: config.circuit.halfOpenMaxProbes ?? 1,\n };\n}\n\n/**\n * Close the open charging segment at `at` and open the next one there: the ONE\n * place a {@link CallBudget} is debited. Every caller holds `at` as data (a\n * verb's argument, a Msg's stamp), so the budget advances without any verb\n * reading a clock. PURE.\n *\n * A budget with no deadline brick (`remainingMs` 0) is left alone — there is\n * nothing to spend, and debiting it would push it negative and read as \"spent\".\n * `at` before the anchor cannot happen on a monotonic feed, but a clamped charge\n * keeps a skewed one from CREDITING budget back.\n */\nfunction charge(budget: CallBudget, at: number): CallBudget {\n if (budget.remainingMs <= 0) return { ...budget, chargingSinceMs: at };\n const elapsed = Math.max(0, at - budget.chargingSinceMs);\n return { remainingMs: budget.remainingMs - elapsed, chargingSinceMs: at };\n}\n\n// Sub-id families. Each call's retry timer is a deadline keyed by `key`, so a\n// machine running many concurrent calls reconciles each independently.\nfunction retryTimerId(key: string): string {\n return `resilient:retry:${key}`;\n}\nfunction deadlineTimerId(key: string): string {\n return `resilient:deadline:${key}`;\n}\n\n// ===========================================================================\n// The knob factory.\n// ===========================================================================\n\n/**\n * Build a resilient-call knob from `config`. `rng` is injected for retry\n * jitter — pass a fixed `() => 0.5` in tests to pin backoff; defaults to\n * `Math.random` (read at the verb boundary, never inside a brick op). It is\n * curried onto the factory rather than threaded through every verb so callers\n * spell the determinism choice once.\n *\n * Returns the uniform L2 knob contract. `I` is the port input type, `R` the\n * port result type. Consumers outside the package reach this through\n * `withResilience` (`../with-resilience`) — see the migration map at the top\n * of this module.\n */\nexport function createResilientCall<I, R>(\n config: ResilientConfig,\n rng: () => number = Math.random,\n) {\n const cPolicy = circuitPolicy(config);\n // Re-affirm the `[0, 1)` contract at the seam where the injected generator\n // feeds the backoff math (brand from `../retry-backoff`). The factory's\n // public param stays a plain `() => number` so transitive callers are\n // unchanged; `asRng` is the single point it's branded for `nextDelayMs`.\n const rngBranded = asRng(rng);\n const runCmd = runCmdDef<I, R>();\n\n /** The starting slice. Bricks not in `config` still get a default value. */\n function init(): ResilientState<I, R> {\n return {\n circuit: initCircuit(),\n // The bucket carries a default value even without the rateLimit brick so\n // the slice shape is uniform, but the rate-limit GATE is skipped entirely\n // when the brick is absent (see `gate`) — the bucket is simply never\n // consulted. A `0/0` placeholder bucket would be touched by `tryConsume`;\n // a `1-cap` default is harmless because the gate never calls it.\n bucket: config.rateLimit\n ? initBucket(\n config.rateLimit.capacity,\n config.rateLimit.refillPerSec,\n 0,\n )\n : initBucket(1, 1, 0),\n retry: {},\n cache: initCache<R>(),\n calls: {},\n };\n }\n\n /** Read this key's retry state, defaulting to a fresh one. */\n function retryOf(s: ResilientState<I, R>, key: string): RetryState {\n return s.retry[key] ?? initRetry();\n }\n\n /** A fresh full budget for a call starting at `at` (`remainingMs` 0 = no cap). */\n function freshBudget(at: number): CallBudget {\n return {\n remainingMs: config.deadline ? config.deadline.ms : 0,\n chargingSinceMs: at,\n };\n }\n\n /**\n * The budget a call already live under `key` carries forward, charged up to\n * `at`; a fresh full budget when the key has no live call. This is what makes a\n * re-attempt share ONE budget with the attempts before it rather than buying a\n * new one — the in-process invariant the deadline brick has always had.\n */\n function budgetFor(\n s: ResilientState<I, R>,\n key: string,\n at: number,\n ): CallBudget {\n const call = s.calls[key];\n if (call?.phase === \"running\" || call?.phase === \"waiting_retry\") {\n return charge(call.budget, at);\n }\n return freshBudget(at);\n }\n\n /**\n * The shared decision: budget → cache → rate-limit → circuit gate, then either\n * emit the effect or schedule a retry. `budget` is threaded so a re-attempt\n * (from `onTimer`) keeps the ORIGINAL overall budget rather than restarting it.\n * PURE — `at` is the only clock; `rng` is the injected jitter source.\n *\n * ## Why the budget is checked BEFORE the effect is emitted\n *\n * The check is step 0 and it settles rather than dispatches. A call whose\n * budget is spent must not reach the port at all: the port is a real side\n * effect, and re-issuing an attempt only to settle `deadline_exceeded` when it\n * returns spends that effect for an answer nobody reads. This is exactly the\n * resume path's shape — a process that comes back to a spent budget settles\n * here, with zero further port calls (#144).\n *\n * Used by both `attempt` (fresh call) and `onTimer` (retry timer fired) so\n * the gate logic lives in exactly one place — the example's shared `attempt`\n * helper, lifted to a keyed knob.\n *\n * ## Why rate-limit is gated BEFORE the circuit probe\n *\n * The circuit-breaker's `canPass` is not a passive read: in `half_open` it\n * *consumes* a probe slot (advancing `probes`), and the consumed slot is only\n * released when a probe RESOLVES via `onSuccess` / `onFailure`. So a probe\n * slot may only be taken when this attempt is actually going to reach the\n * backend (i.e. emit the `resilient_run` Cmd). The rate-limit gate can reject\n * an attempt and return a backoff WITHOUT any Cmd — no backend call, so no\n * settle Msg ever comes back. If the circuit probe were consumed first, that\n * rate-limited attempt would burn the half-open probe budget on a call that\n * never happened, wedging the breaker `half_open` at its probe cap forever\n * (nothing moves it out of `half_open` but a resolved probe). Running the\n * rate-limit gate first means the circuit probe is consumed ONLY on the path\n * that emits the effect.\n */\n function gate(\n s: ResilientState<I, R>,\n key: string,\n input: I,\n at: number,\n budget: CallBudget,\n ): readonly [ResilientState<I, R>, readonly RunCmd<I>[]] {\n // 0) Budget gate. Skipped without the deadline brick (`remainingMs` stays 0\n // and never moves). A spent budget settles here — before the cache read, the\n // token spend and the circuit probe, none of which a dead call should touch.\n if (config.deadline !== undefined && budget.remainingMs <= 0) {\n return [\n setCall(s, key, {\n phase: \"failed\",\n error: {\n _tag: \"deadline_exceeded\",\n id: deadlineTimerId(key),\n atMs: at,\n } satisfies DeadlineExceededError,\n }),\n [],\n ];\n }\n\n // 1) Serve from cache if fresh — no effect at all. Skipped when no cache brick.\n if (config.cache !== undefined) {\n const cached = cacheGet(s.cache, key, at);\n if (cached !== undefined) {\n return [setCall(s, key, { phase: \"succeeded\", result: cached }), []];\n }\n }\n\n // 2) Rate-limit gate FIRST — it may reject with no Cmd, so it must run\n // before any circuit probe is consumed (see header note). Skipped entirely\n // when no rateLimit brick — the bucket is never consulted, so the default\n // placeholder bucket stays untouched.\n let bucket = s.bucket;\n if (config.rateLimit !== undefined) {\n const [next, hasToken] = tryConsume(bucket, at);\n bucket = next;\n if (!hasToken) {\n // Rate-limited: treat as a transient failure and back off via retry.\n // The circuit is untouched — no probe is spent on a call that never ran.\n return backoff(\n { ...s, bucket },\n key,\n input,\n \"rate_limited\",\n at,\n budget,\n );\n }\n }\n\n // 3) Circuit-breaker gate. `null` policy → no breaker → always pass. Reached\n // only once a token is secured, so a half-open probe is consumed exclusively\n // on the path that emits the effect below.\n let circuit = s.circuit;\n if (cPolicy !== null) {\n const [next, ok] = canPass(circuit, cPolicy, at);\n circuit = next;\n if (!ok) {\n return [\n { ...setCall(s, key, { phase: \"circuit_open\" }), bucket, circuit },\n [],\n ];\n }\n }\n\n // 4) All gates passed — emit the effect as data.\n return [\n {\n ...setCall(s, key, { phase: \"running\", input, budget }),\n circuit,\n bucket,\n },\n [runCmd({ key, input })],\n ];\n }\n\n /**\n * Back off after a transient failure: record it, and if `retry` permits\n * another attempt, enter `waiting_retry` with the retry timer armed; else\n * settle `failed`. When no `retry` brick is configured the call fails on the\n * first failure (no backoff, no timer). PURE.\n *\n * `at` is both the backoff anchor AND the streak clock: it is passed to\n * `recordFailure` (which starts `firstFailureAtMs` on the streak's first\n * failure and preserves it on every later one) and to `shouldRetry`, so a\n * `DurationRetryPolicy` on `config.retry` is honoured with no extra wiring.\n * A success drops `retry[key]` entirely, which drops the origin with it —\n * only an unbroken run of failures grows toward a duration budget.\n */\n function backoff(\n s: ResilientState<I, R>,\n key: string,\n input: I,\n error: unknown,\n at: number,\n budget: CallBudget,\n ): readonly [ResilientState<I, R>, readonly RunCmd<I>[]] {\n if (config.retry === undefined) {\n return [setCall(s, key, { phase: \"failed\", error }), []];\n }\n const retry = recordFailure(retryOf(s, key), error, at);\n const withRetry = { ...s, retry: { ...s.retry, [key]: retry } };\n if (!shouldRetry(retry, config.retry, at)) {\n return [setCall(withRetry, key, { phase: \"failed\", error }), []];\n }\n const retryAtMs = at + nextDelayMs(retry, config.retry, rngBranded);\n return [\n setCall(withRetry, key, {\n phase: \"waiting_retry\",\n input,\n retryAtMs,\n budget,\n }),\n [],\n ];\n }\n\n // Set the phase for one key, leaving every other field untouched.\n function setCall(\n s: ResilientState<I, R>,\n key: string,\n phase: CallPhase<I, R>,\n ): ResilientState<I, R> {\n return { ...s, calls: { ...s.calls, [key]: phase } };\n }\n\n // === Verb: attempt =======================================================\n\n /**\n * Start (or restart) the call for `key` with `input` at time `at`. Runs the\n * cache → circuit → rate-limit gate and either emits the `resilient_run`\n * effect, schedules a retry, fast-fails (circuit open), or serves the cache.\n * PURE.\n *\n * A key already live keeps the budget it is under (charged up to `at`) rather\n * than being handed a fresh one, so re-issuing an in-flight call — the boot\n * path's move — cannot silently extend its deadline. Only a key with no live\n * call starts a full budget.\n */\n function attempt(\n s: ResilientState<I, R>,\n key: string,\n input: I,\n at: number,\n ): readonly [ResilientState<I, R>, readonly RunCmd<I>[]] {\n return gate(s, key, input, at, budgetFor(s, key, at));\n }\n\n // === Verb: resume ========================================================\n\n /**\n * Re-anchor every live call's charging clock to `at`: the verb a host calls\n * once on the boot path, before any Sub is armed or any attempt re-issued.\n *\n * This is what keeps DOWNTIME out of the budget. `chargingSinceMs` is an\n * instant on the clock of the process that wrote it; after an eviction, a crash\n * or a redeploy, the gap between it and now is time in which no attempt ran and\n * no port was called, so charging it would time a call out on the host's\n * absence rather than on the tool's slowness. `remainingMs` — the durable half\n * of the budget — is left exactly as the last live transition set it. PURE.\n */\n function resume(s: ResilientState<I, R>, at: number): ResilientState<I, R> {\n const calls: Record<string, CallPhase<I, R>> = { ...s.calls };\n for (const [key, call] of Object.entries(calls)) {\n if (call.phase !== \"running\" && call.phase !== \"waiting_retry\") continue;\n calls[key] = {\n ...call,\n budget: { ...call.budget, chargingSinceMs: at },\n };\n }\n return { ...s, calls };\n }\n\n // === Verb: succeed =======================================================\n\n /**\n * Record a success for `key`: close the breaker, fill the cache (if a cache\n * brick exists), reset this key's retry count, and settle `succeeded`. PURE —\n * `msg.at` is the cache write clock.\n */\n function succeed(\n s: ResilientState<I, R>,\n key: string,\n msg: SucceedMsg<R>,\n ): readonly [ResilientState<I, R>, readonly RunCmd<I>[]] {\n const circuit =\n cPolicy !== null ? onSuccess(s.circuit, cPolicy) : s.circuit;\n const cache =\n config.cache !== undefined\n ? cacheSet(s.cache, key, msg.result, msg.at, config.cache.ttlMs)\n : s.cache;\n // Drop this key's retry counter — a success ends the retry run.\n const retry = without(s.retry, key);\n return [\n {\n ...setCall(s, key, { phase: \"succeeded\", result: msg.result }),\n circuit,\n cache,\n retry,\n },\n [],\n ];\n }\n\n // === Verb: fail ==========================================================\n\n /**\n * Record a failure for `key`: trip the breaker (it may open), then back off —\n * schedule a retry if `retry` permits, else settle `failed`. PURE — `msg.at`\n * stamps the breaker trip + the retry delay base. Re-issues from the call's\n * remembered `input` and carries the original budget forward, charged up to\n * `msg.at` — the attempt that just failed spent in-process time, and a retry\n * ladder shares ONE budget across its attempts rather than getting a fresh one\n * per attempt.\n */\n function fail(\n s: ResilientState<I, R>,\n key: string,\n msg: FailMsg,\n ): readonly [ResilientState<I, R>, readonly RunCmd<I>[]] {\n const circuit =\n cPolicy !== null ? onFailure(s.circuit, cPolicy, msg.at) : s.circuit;\n const withCircuit = { ...s, circuit };\n const call = s.calls[key];\n const input = call?.phase === \"running\" ? call.input : undefined;\n const budget = budgetFor(s, key, msg.at);\n // No remembered input (e.g. a stray fail for a key not running) → just\n // settle failed after tripping the breaker; nothing to re-issue.\n if (input === undefined) {\n return [\n setCall(withCircuit, key, { phase: \"failed\", error: msg.error }),\n [],\n ];\n }\n return backoff(withCircuit, key, input, msg.error, msg.at, budget);\n }\n\n // === Verb: settleFailed ==================================================\n\n /**\n * Settle `key` to terminal `failed` with `error` WITHOUT touching the circuit\n * breaker or this key's retry counter. PURE.\n *\n * This is the verb for a failure that is NOT a downstream-health signal: the\n * call must end now, but the breaker must not trip and the backoff run must\n * not advance. `fail` is the opposite — it records the failure against the\n * breaker (it may open) and feeds the retry policy (it may back off). Use\n * `settleFailed` when the *reason* the call cannot proceed has nothing to do\n * with downstream health:\n *\n * - `authed-call` settles a 401 it cannot fix (e.g. the refresh budget is\n * spent): a bad token is the caller's problem, not the backend's —\n * tripping the breaker on it would punish a healthy target, and backing\n * off would just retry a request that will 401 again.\n * - `paginated-walk` settles a terminal walk error (e.g. a malformed page\n * the consumer rejects) that should stop the walk without poisoning the\n * shared breaker the next page fetch passes through.\n *\n * Because it leaves `circuit` and `retry[key]` untouched, the breaker keeps\n * its health view of the actual backend and a later `attempt` for this key\n * starts a fresh retry run. Both sibling knobs previously hand-rolled this by\n * splicing `calls` directly; this verb gives them one named, tested entry\n * point so the slice's settle invariants live in exactly one place.\n */\n function settleFailed(\n s: ResilientState<I, R>,\n key: string,\n error: unknown,\n ): readonly [ResilientState<I, R>, readonly RunCmd<I>[]] {\n return [setCall(s, key, { phase: \"failed\", error }), []];\n }\n\n // === Verb: onTimer =======================================================\n\n /**\n * A timer fired for some key. Two kinds, disambiguated by the Sub id:\n *\n * - retry timer → re-run the gate for that key's remembered input,\n * re-checking the circuit + bucket at THIS time.\n * - deadline timer → the overall budget elapsed → settle `failed` with a\n * deadline error, no matter the current phase.\n *\n * A timer for a key no longer `waiting_retry` (retry) / no longer in flight\n * (deadline) is a no-op — the reconcile pass races the result; the verb must\n * tolerate a stale fire. PURE.\n */\n function onTimer(\n s: ResilientState<I, R>,\n msg: ResilientTimerMsg,\n ): readonly [ResilientState<I, R>, readonly RunCmd<I>[]] {\n for (const [key, call] of Object.entries(s.calls)) {\n if (msg.id === retryTimerId(key)) {\n if (call.phase !== \"waiting_retry\") return [s, []];\n // The backoff wait was in-process time, so it is charged before the gate\n // reads the budget — and the gate settles rather than dispatching when\n // that leaves nothing, which is how a resumed ladder ends without one\n // more port call.\n return gate(\n s,\n key,\n call.input,\n msg.atMs,\n charge(call.budget, msg.atMs),\n );\n }\n if (msg.id === deadlineTimerId(key)) {\n // Deadline only fires for a still-active call. A settled call has no\n // armed deadline Sub, but tolerate a stale fire defensively.\n if (call.phase !== \"running\" && call.phase !== \"waiting_retry\")\n return [s, []];\n // Plain-data sentinel, NOT a `new Error(...)`: an Error object is not\n // JSON-serializable (it round-trips to `{}`) and captures a stack trace,\n // both of which break the slice's durability invariant once it is\n // persisted into a `Store<S>` and reloaded. A `{_tag, ...}` record keeps\n // the slice round-trip-equal under JSON.parse(JSON.stringify(...)).\n return [\n setCall(s, key, {\n phase: \"failed\",\n error: {\n _tag: \"deadline_exceeded\",\n id: msg.id,\n atMs: msg.atMs,\n } satisfies DeadlineExceededError,\n }),\n [],\n ];\n }\n }\n return [s, []];\n }\n\n // === Subs ================================================================\n\n /**\n * Pre-wired subscriptions: a retry timer for every `waiting_retry` call, and\n * (when the `deadline` brick is configured) an overall deadline timer for\n * every still-active call. Both are `DeadlineSub`s reconciled by id, so a\n * phase change cancels the matching timer automatically — no manual\n * `clearTimeout`. Wire `subscribe: { deadline: subscribeDeadline }`.\n */\n function subs(s: ResilientState<I, R>): readonly DeadlineSub[] {\n const out: DeadlineSub[] = [];\n for (const [key, call] of Object.entries(s.calls)) {\n if (call.phase === \"waiting_retry\") {\n out.push(deadlineSub(retryTimerId(key), call.retryAtMs));\n }\n if (\n config.deadline !== undefined &&\n (call.phase === \"running\" || call.phase === \"waiting_retry\") &&\n call.budget.remainingMs > 0\n ) {\n // The absolute instant the timer arms at is DERIVED here, from the open\n // segment's anchor plus what is left — it is never persisted, so a slice\n // that came back from storage arms off the anchor `resume` just re-based\n // rather than off a stamp the downtime ran past.\n out.push(\n deadlineSub(\n deadlineTimerId(key),\n call.budget.chargingSinceMs + call.budget.remainingMs,\n ),\n );\n }\n }\n return out;\n }\n\n // === Handlers ============================================================\n\n /**\n * Pre-wired interpret handler for `resilient_run`. Wraps the consumer's\n * `run` port via `tryInterpret` (Railway): success → `resilient_ok`, failure\n * → `resilient_err`, each stamped with `Date.now()` at the effect boundary —\n * the ONE permitted clock read (interpret is impure; the reducer never reads\n * the clock). Assign to the machine's `interpret`:\n *\n * interpret: rc.handlers({ run: (input, key) => ctx.callBackend(input) })\n */\n function handlers(ports: ResilientPorts<I, R>) {\n return {\n resilient_run: tryInterpret<\n RunCmd<I>,\n R,\n SucceedMsg<R> | FailMsg,\n // The work fn reads nothing from ctx — it forwards `cmd.input` to the\n // consumer-supplied `run` port. `NoCtx` (not `unknown`) marks this as a\n // DELIBERATE context-free seam, so callers see intent, not looseness.\n NoCtx\n >(\n (cmd) => ports.run(cmd.input, cmd.key),\n (result, cmd): SucceedMsg<R> => ({\n type: MsgType.ResilientOk,\n key: cmd.key,\n result,\n at: Date.now(),\n }),\n (error, cmd): FailMsg => ({\n type: MsgType.ResilientErr,\n key: cmd.key,\n error,\n at: Date.now(),\n }),\n ),\n };\n }\n\n return {\n init,\n attempt,\n resume,\n succeed,\n fail,\n settleFailed,\n onTimer,\n subs,\n handlers,\n };\n}\n\n/**\n * Lift a knob result `[slice, cmds]` into a host `[State, cmds]` where the\n * slice lives at `state.resilience`. A convenience for the common single-slice\n * host; consumers with a differently-named field spread by hand. Pure — a thin\n * record rebuild, no clock / RNG. Under `withResilience` the wrapper owns\n * `model.$resilience`, so there is nothing to lift; this is for the embedded\n * slice consumers only.\n */\nexport function liftResilience<\n S extends { resilience: ResilientState<I, R> },\n I,\n R,\n C extends Cmd,\n>(\n state: S,\n [slice, cmds]: readonly [ResilientState<I, R>, readonly C[]],\n): readonly [S, readonly C[]] {\n return [{ ...state, resilience: slice }, cmds];\n}\n\n/**\n * Re-export the deadline Sub primitives so consumers (and tests) wire one\n * import: `subscribeDeadline` is the `subscribe` cell, `deadlineSub` builds the\n * Sub literal this knob's `subs` emits.\n */\nexport { subscribeDeadline, deadlineSub };\nexport type { DeadlineSub, DeadlineExceeded };\n"]}
|