@runcofactor/cli 0.54.0 → 0.55.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (233) hide show
  1. package/README.md +2 -1
  2. package/dist/{account-6YJAU53I.js → account-RHHS7HT4.js} +5 -5
  3. package/dist/{alert-Z7V2ZUEW.js → alert-B5WEOKHP.js} +8 -8
  4. package/dist/{api-catalog-UEDTRM6E.js → api-catalog-UXONX3BR.js} +5 -5
  5. package/dist/{api-key-V5GEPRRG.js → api-key-J3A4TJKN.js} +5 -5
  6. package/dist/{attachment-M7VZ6GSE.js → attachment-ZQW7DR4G.js} +5 -5
  7. package/dist/{auth-DVTMWJKF.js → auth-HQGKUZBS.js} +3 -3
  8. package/dist/{benchmark-KZJZYUTQ.js → benchmark-TIKJA32J.js} +5 -5
  9. package/dist/{blackcat-QJXYDE66.js → blackcat-Z5LNV2GS.js} +3 -3
  10. package/dist/{chunk-7MRZWPVC.js → chunk-3KBYI6RR.js} +3 -3
  11. package/dist/{chunk-LPQTXBJF.js → chunk-3TZABC7D.js} +7 -7
  12. package/dist/{chunk-ZOPVBINJ.js → chunk-5OKCT6LF.js} +5 -5
  13. package/dist/{chunk-ZO5MSM4Q.js → chunk-635IQV6B.js} +2 -2
  14. package/dist/{chunk-4EWSONHO.js → chunk-6X6ZTDXP.js} +4 -4
  15. package/dist/{chunk-ZFN5FWN2.js → chunk-BJHAVM7Z.js} +2 -2
  16. package/dist/{chunk-Q7UAEFW5.js → chunk-CQSQNDMV.js} +4 -4
  17. package/dist/{chunk-AQOHNAEN.js → chunk-CZDQVQ57.js} +5 -5
  18. package/dist/{chunk-CV22445U.js → chunk-EGWVL5XA.js} +2 -2
  19. package/dist/{chunk-FLDIN6VX.js → chunk-JQG3V5E3.js} +2 -2
  20. package/dist/{chunk-KNWKOKM7.js → chunk-KL2ARGH6.js} +20 -11
  21. package/dist/chunk-KL2ARGH6.js.map +1 -0
  22. package/dist/{chunk-T6EQFICG.js → chunk-L4JQ236V.js} +4 -4
  23. package/dist/{chunk-2M4PZK6Q.js → chunk-MB4FS35J.js} +8 -8
  24. package/dist/{chunk-3YNR6U6Q.js → chunk-OU4I3MEH.js} +3 -3
  25. package/dist/{chunk-O7MBDOMO.js → chunk-QHRWXCON.js} +25 -9
  26. package/dist/chunk-QHRWXCON.js.map +1 -0
  27. package/dist/{chunk-HGEJHS45.js → chunk-QVMGXMP5.js} +5 -5
  28. package/dist/{chunk-G2SCV2CY.js → chunk-R2CRH6JQ.js} +3 -3
  29. package/dist/{chunk-HESV3F6U.js → chunk-RV7FZ4ID.js} +3 -3
  30. package/dist/{chunk-7VBX2M4F.js → chunk-SCQVBLNR.js} +3 -3
  31. package/dist/{chunk-74TU6PGO.js → chunk-TGI2AAOZ.js} +4 -4
  32. package/dist/{chunk-R2CVV7QT.js → chunk-U3MRXQZS.js} +5 -1
  33. package/dist/chunk-U3MRXQZS.js.map +1 -0
  34. package/dist/{chunk-L4Y5PZUE.js → chunk-W6DL4IP6.js} +2 -2
  35. package/dist/{chunk-TRC63RSK.js → chunk-WWUEKQLR.js} +3 -3
  36. package/dist/{chunk-MQQCUU5S.js → chunk-XM3WTJ7Y.js} +2 -2
  37. package/dist/{chunk-YACDH6MJ.js → chunk-XWJJYZQC.js} +2 -2
  38. package/dist/{chunk-QGP67JOK.js → chunk-YEH7UIUY.js} +4 -4
  39. package/dist/{chunk-RA2NRAWU.js → chunk-YTXZUWMN.js} +6 -6
  40. package/dist/{chunk-JAIMLQOU.js → chunk-Z72GWGH2.js} +6 -6
  41. package/dist/{chunk-QYHJUHL7.js → chunk-ZH7HWVEE.js} +2 -2
  42. package/dist/{chunk-IVWWYKAM.js → chunk-ZZZ33KTO.js} +3 -3
  43. package/dist/{credential-546ER7WQ.js → credential-2IEZDTH3.js} +5 -5
  44. package/dist/{credential-template-BWMEBGBN.js → credential-template-S5WSOI3P.js} +5 -5
  45. package/dist/{digest-NIVBDDI2.js → digest-HQGL7H6E.js} +8 -8
  46. package/dist/{docs-AK4MDP6K.js → docs-CJQZVERZ.js} +5 -5
  47. package/dist/{doctor-NCLA47EL.js → doctor-EGK5AB77.js} +3 -3
  48. package/dist/{environment-X3PV5IN2.js → environment-MWVXYEDU.js} +7 -7
  49. package/dist/{eval-5FZKYZ6O.js → eval-F4NORBQI.js} +9 -9
  50. package/dist/{exploration-2RVGIUKT.js → exploration-JC7R7KTJ.js} +5 -5
  51. package/dist/{exploratory-test-SJ5HFI3L.js → exploratory-test-BHK5FTMJ.js} +13 -5
  52. package/dist/exploratory-test-BHK5FTMJ.js.map +1 -0
  53. package/dist/{external-run-4NDVAHQB.js → external-run-UL5CKG54.js} +9 -9
  54. package/dist/{feature-flag-C4IRE724.js → feature-flag-NMO25TZN.js} +5 -5
  55. package/dist/{feedback-337OZCKC.js → feedback-3JNPSUXS.js} +6 -6
  56. package/dist/{flow-D7ZLBKCH.js → flow-AYE4RTZN.js} +5 -5
  57. package/dist/{github-LKXYIALH.js → github-DM2DCXRY.js} +6 -6
  58. package/dist/index.js +76 -76
  59. package/dist/{init-GKEC6LPH.js → init-CPDT5XAU.js} +5 -5
  60. package/dist/{install-K45SAT2V.js → install-JZRNCHMO.js} +5 -5
  61. package/dist/{issues-2HMTQYWR.js → issues-GRFJB52C.js} +8 -8
  62. package/dist/{knobs-KUCGLO5Z.js → knobs-3Y4QXWAD.js} +5 -5
  63. package/dist/{landing-KAET2I73.js → landing-ABYL5WVG.js} +7 -7
  64. package/dist/{list-GDV4UYWV.js → list-WG5QB4NK.js} +5 -5
  65. package/dist/{local-NFOSJLLV.js → local-KX24OR5Z.js} +7 -7
  66. package/dist/{login-Y77HWROY.js → login-MLJQAIFK.js} +5 -5
  67. package/dist/{mcp-CPCFMU6B.js → mcp-DDTO54XD.js} +10 -10
  68. package/dist/{metrics-Y245ST63.js → metrics-BJLLOSPQ.js} +5 -5
  69. package/dist/{monitor-BUDWSC3S.js → monitor-E2CKGHPM.js} +5 -5
  70. package/dist/{perf-3JOAXPHP.js → perf-IX7JOMUE.js} +11 -11
  71. package/dist/{product-CXL3YTMD.js → product-2ECWM2JN.js} +5 -5
  72. package/dist/{product-map-656BTU74.js → product-map-O542LZQL.js} +7 -7
  73. package/dist/{prompts-3SZ7O5E6.js → prompts-Y7O4XJ5P.js} +5 -5
  74. package/dist/{proofs-4CSCCSNL.js → proofs-TNOBLSRP.js} +9 -9
  75. package/dist/{property-IEKBXRVY.js → property-TZCGOZQG.js} +6 -6
  76. package/dist/{psql-M4CM6TSI.js → psql-M66YSUXW.js} +5 -5
  77. package/dist/{record-QP4TYZBS.js → record-3JINBRAR.js} +6 -6
  78. package/dist/{redis-KG47IGJV.js → redis-FOIBINYA.js} +5 -5
  79. package/dist/{release-QHPSQULK.js → release-B4VWMVEI.js} +5 -5
  80. package/dist/{runbook-YVBG2QK3.js → runbook-EYMSE37E.js} +5 -5
  81. package/dist/{sandbox-ZXDZTCCN.js → sandbox-R6EMGOKR.js} +16 -13
  82. package/dist/sandbox-R6EMGOKR.js.map +1 -0
  83. package/dist/{scenarios-2SX2NUCD.js → scenarios-W7YNLBVO.js} +5 -5
  84. package/dist/{schedule-P6F7MLXX.js → schedule-EDHQJUUZ.js} +8 -8
  85. package/dist/{session-SZMCZTPE.js → session-4N62APNQ.js} +9 -9
  86. package/dist/{skill-D4XFW6QI.js → skill-XFGMNOVM.js} +107 -9
  87. package/dist/skill-XFGMNOVM.js.map +1 -0
  88. package/dist/skills/cofactor-debug-run/SKILL.md +5 -1
  89. package/dist/skills/cofactor-debug-run/history/2.0.9.md +319 -0
  90. package/dist/skills/cofactor-debug-run/version.json +1 -1
  91. package/dist/{slack-UDXTPOSW.js → slack-RUAXG6TB.js} +6 -6
  92. package/dist/{spec-YCBBIV27.js → spec-ZZKMW7ER.js} +5 -5
  93. package/dist/{src-ZKVNVLXY.js → src-CQ2YAW7F.js} +3 -3
  94. package/dist/{src-NG7DRE5A.js → src-DGIVA22K.js} +3 -3
  95. package/dist/{src-DEGBASCD.js → src-ECZWYNSV.js} +5 -5
  96. package/dist/{src-DY7GLRO2.js → src-OKSAEHQS.js} +4 -4
  97. package/dist/{start-ZYZSORPY.js → start-OCZGILRL.js} +5 -5
  98. package/dist/{status-ESIG6A7W.js → status-YTJ6YITG.js} +5 -5
  99. package/dist/{tag-3K5Y25AR.js → tag-KBG4SWRL.js} +6 -6
  100. package/dist/{task-JLNX7O3M.js → task-O6XRMHYE.js} +6 -6
  101. package/dist/{teams-F4IU3EXE.js → teams-NFUHS7XI.js} +7 -7
  102. package/dist/{test-suite-QHDUW5W5.js → test-suite-6BH3DESN.js} +12 -12
  103. package/dist/{tickets-NBL5WACL.js → tickets-JU2STK4B.js} +5 -5
  104. package/dist/{trace-ZKYAI5JF.js → trace-NMSGKASJ.js} +8 -8
  105. package/dist/{triage-5NWCREUM.js → triage-JGECJJU5.js} +6 -6
  106. package/dist/{trigger-KXYYY5IR.js → trigger-OSPEVCCQ.js} +9 -9
  107. package/dist/{update-check-Q2URVJLC.js → update-check-FNP6RFZO.js} +4 -4
  108. package/dist/{upgrade-4XSASC7U.js → upgrade-3PPBXFAT.js} +5 -5
  109. package/dist/{usage-UY7VZYVY.js → usage-QMCU2JIO.js} +7 -7
  110. package/dist/{utilities-53NXNLA7.js → utilities-7VHWTSJT.js} +6 -6
  111. package/dist/{visual-IJAT7X7D.js → visual-WIZSPTVK.js} +5 -5
  112. package/dist/{workflow-OQNH2FIS.js → workflow-23HKTJLS.js} +62 -33
  113. package/dist/workflow-23HKTJLS.js.map +1 -0
  114. package/dist/{workflow-cache-FP6MKLVR.js → workflow-cache-ACGM3BOI.js} +16 -16
  115. package/dist/{workflow-cache-convergence-resolve-HS5KO7US.js → workflow-cache-convergence-resolve-KEQDQMKL.js} +5 -5
  116. package/dist/{workflow-debug-EM55H2QE.js → workflow-debug-TVLBNRCB.js} +11 -11
  117. package/dist/{workflow-feedback-GNWNWVHH.js → workflow-feedback-GB5VPGE2.js} +5 -5
  118. package/dist/{workflow-fork-M5AQWST7.js → workflow-fork-2IPJNR76.js} +5 -5
  119. package/dist/{workflow-promotion-HYULZDYL.js → workflow-promotion-QEQUXVUT.js} +5 -5
  120. package/dist/{workflow-proposals-EN327FSE.js → workflow-proposals-KHGTPMD4.js} +5 -5
  121. package/dist/{workflow-revisions-ZMTVNWRW.js → workflow-revisions-F5KQEEXB.js} +5 -5
  122. package/dist/{workflow-status-FNAJG34W.js → workflow-status-66HC5MEA.js} +5 -5
  123. package/package.json +1 -1
  124. package/dist/chunk-KNWKOKM7.js.map +0 -1
  125. package/dist/chunk-O7MBDOMO.js.map +0 -1
  126. package/dist/chunk-R2CVV7QT.js.map +0 -1
  127. package/dist/exploratory-test-SJ5HFI3L.js.map +0 -1
  128. package/dist/sandbox-ZXDZTCCN.js.map +0 -1
  129. package/dist/skill-D4XFW6QI.js.map +0 -1
  130. package/dist/workflow-OQNH2FIS.js.map +0 -1
  131. /package/dist/{account-6YJAU53I.js.map → account-RHHS7HT4.js.map} +0 -0
  132. /package/dist/{alert-Z7V2ZUEW.js.map → alert-B5WEOKHP.js.map} +0 -0
  133. /package/dist/{api-catalog-UEDTRM6E.js.map → api-catalog-UXONX3BR.js.map} +0 -0
  134. /package/dist/{api-key-V5GEPRRG.js.map → api-key-J3A4TJKN.js.map} +0 -0
  135. /package/dist/{attachment-M7VZ6GSE.js.map → attachment-ZQW7DR4G.js.map} +0 -0
  136. /package/dist/{auth-DVTMWJKF.js.map → auth-HQGKUZBS.js.map} +0 -0
  137. /package/dist/{benchmark-KZJZYUTQ.js.map → benchmark-TIKJA32J.js.map} +0 -0
  138. /package/dist/{blackcat-QJXYDE66.js.map → blackcat-Z5LNV2GS.js.map} +0 -0
  139. /package/dist/{chunk-7MRZWPVC.js.map → chunk-3KBYI6RR.js.map} +0 -0
  140. /package/dist/{chunk-LPQTXBJF.js.map → chunk-3TZABC7D.js.map} +0 -0
  141. /package/dist/{chunk-ZOPVBINJ.js.map → chunk-5OKCT6LF.js.map} +0 -0
  142. /package/dist/{chunk-ZO5MSM4Q.js.map → chunk-635IQV6B.js.map} +0 -0
  143. /package/dist/{chunk-4EWSONHO.js.map → chunk-6X6ZTDXP.js.map} +0 -0
  144. /package/dist/{chunk-ZFN5FWN2.js.map → chunk-BJHAVM7Z.js.map} +0 -0
  145. /package/dist/{chunk-Q7UAEFW5.js.map → chunk-CQSQNDMV.js.map} +0 -0
  146. /package/dist/{chunk-AQOHNAEN.js.map → chunk-CZDQVQ57.js.map} +0 -0
  147. /package/dist/{chunk-CV22445U.js.map → chunk-EGWVL5XA.js.map} +0 -0
  148. /package/dist/{chunk-FLDIN6VX.js.map → chunk-JQG3V5E3.js.map} +0 -0
  149. /package/dist/{chunk-T6EQFICG.js.map → chunk-L4JQ236V.js.map} +0 -0
  150. /package/dist/{chunk-2M4PZK6Q.js.map → chunk-MB4FS35J.js.map} +0 -0
  151. /package/dist/{chunk-3YNR6U6Q.js.map → chunk-OU4I3MEH.js.map} +0 -0
  152. /package/dist/{chunk-HGEJHS45.js.map → chunk-QVMGXMP5.js.map} +0 -0
  153. /package/dist/{chunk-G2SCV2CY.js.map → chunk-R2CRH6JQ.js.map} +0 -0
  154. /package/dist/{chunk-HESV3F6U.js.map → chunk-RV7FZ4ID.js.map} +0 -0
  155. /package/dist/{chunk-7VBX2M4F.js.map → chunk-SCQVBLNR.js.map} +0 -0
  156. /package/dist/{chunk-74TU6PGO.js.map → chunk-TGI2AAOZ.js.map} +0 -0
  157. /package/dist/{chunk-L4Y5PZUE.js.map → chunk-W6DL4IP6.js.map} +0 -0
  158. /package/dist/{chunk-TRC63RSK.js.map → chunk-WWUEKQLR.js.map} +0 -0
  159. /package/dist/{chunk-MQQCUU5S.js.map → chunk-XM3WTJ7Y.js.map} +0 -0
  160. /package/dist/{chunk-YACDH6MJ.js.map → chunk-XWJJYZQC.js.map} +0 -0
  161. /package/dist/{chunk-QGP67JOK.js.map → chunk-YEH7UIUY.js.map} +0 -0
  162. /package/dist/{chunk-RA2NRAWU.js.map → chunk-YTXZUWMN.js.map} +0 -0
  163. /package/dist/{chunk-JAIMLQOU.js.map → chunk-Z72GWGH2.js.map} +0 -0
  164. /package/dist/{chunk-QYHJUHL7.js.map → chunk-ZH7HWVEE.js.map} +0 -0
  165. /package/dist/{chunk-IVWWYKAM.js.map → chunk-ZZZ33KTO.js.map} +0 -0
  166. /package/dist/{credential-546ER7WQ.js.map → credential-2IEZDTH3.js.map} +0 -0
  167. /package/dist/{credential-template-BWMEBGBN.js.map → credential-template-S5WSOI3P.js.map} +0 -0
  168. /package/dist/{digest-NIVBDDI2.js.map → digest-HQGL7H6E.js.map} +0 -0
  169. /package/dist/{docs-AK4MDP6K.js.map → docs-CJQZVERZ.js.map} +0 -0
  170. /package/dist/{doctor-NCLA47EL.js.map → doctor-EGK5AB77.js.map} +0 -0
  171. /package/dist/{environment-X3PV5IN2.js.map → environment-MWVXYEDU.js.map} +0 -0
  172. /package/dist/{eval-5FZKYZ6O.js.map → eval-F4NORBQI.js.map} +0 -0
  173. /package/dist/{exploration-2RVGIUKT.js.map → exploration-JC7R7KTJ.js.map} +0 -0
  174. /package/dist/{external-run-4NDVAHQB.js.map → external-run-UL5CKG54.js.map} +0 -0
  175. /package/dist/{feature-flag-C4IRE724.js.map → feature-flag-NMO25TZN.js.map} +0 -0
  176. /package/dist/{feedback-337OZCKC.js.map → feedback-3JNPSUXS.js.map} +0 -0
  177. /package/dist/{flow-D7ZLBKCH.js.map → flow-AYE4RTZN.js.map} +0 -0
  178. /package/dist/{github-LKXYIALH.js.map → github-DM2DCXRY.js.map} +0 -0
  179. /package/dist/{init-GKEC6LPH.js.map → init-CPDT5XAU.js.map} +0 -0
  180. /package/dist/{install-K45SAT2V.js.map → install-JZRNCHMO.js.map} +0 -0
  181. /package/dist/{issues-2HMTQYWR.js.map → issues-GRFJB52C.js.map} +0 -0
  182. /package/dist/{knobs-KUCGLO5Z.js.map → knobs-3Y4QXWAD.js.map} +0 -0
  183. /package/dist/{landing-KAET2I73.js.map → landing-ABYL5WVG.js.map} +0 -0
  184. /package/dist/{list-GDV4UYWV.js.map → list-WG5QB4NK.js.map} +0 -0
  185. /package/dist/{local-NFOSJLLV.js.map → local-KX24OR5Z.js.map} +0 -0
  186. /package/dist/{login-Y77HWROY.js.map → login-MLJQAIFK.js.map} +0 -0
  187. /package/dist/{mcp-CPCFMU6B.js.map → mcp-DDTO54XD.js.map} +0 -0
  188. /package/dist/{metrics-Y245ST63.js.map → metrics-BJLLOSPQ.js.map} +0 -0
  189. /package/dist/{monitor-BUDWSC3S.js.map → monitor-E2CKGHPM.js.map} +0 -0
  190. /package/dist/{perf-3JOAXPHP.js.map → perf-IX7JOMUE.js.map} +0 -0
  191. /package/dist/{product-CXL3YTMD.js.map → product-2ECWM2JN.js.map} +0 -0
  192. /package/dist/{product-map-656BTU74.js.map → product-map-O542LZQL.js.map} +0 -0
  193. /package/dist/{prompts-3SZ7O5E6.js.map → prompts-Y7O4XJ5P.js.map} +0 -0
  194. /package/dist/{proofs-4CSCCSNL.js.map → proofs-TNOBLSRP.js.map} +0 -0
  195. /package/dist/{property-IEKBXRVY.js.map → property-TZCGOZQG.js.map} +0 -0
  196. /package/dist/{psql-M4CM6TSI.js.map → psql-M66YSUXW.js.map} +0 -0
  197. /package/dist/{record-QP4TYZBS.js.map → record-3JINBRAR.js.map} +0 -0
  198. /package/dist/{redis-KG47IGJV.js.map → redis-FOIBINYA.js.map} +0 -0
  199. /package/dist/{release-QHPSQULK.js.map → release-B4VWMVEI.js.map} +0 -0
  200. /package/dist/{runbook-YVBG2QK3.js.map → runbook-EYMSE37E.js.map} +0 -0
  201. /package/dist/{scenarios-2SX2NUCD.js.map → scenarios-W7YNLBVO.js.map} +0 -0
  202. /package/dist/{schedule-P6F7MLXX.js.map → schedule-EDHQJUUZ.js.map} +0 -0
  203. /package/dist/{session-SZMCZTPE.js.map → session-4N62APNQ.js.map} +0 -0
  204. /package/dist/{slack-UDXTPOSW.js.map → slack-RUAXG6TB.js.map} +0 -0
  205. /package/dist/{spec-YCBBIV27.js.map → spec-ZZKMW7ER.js.map} +0 -0
  206. /package/dist/{src-ZKVNVLXY.js.map → src-CQ2YAW7F.js.map} +0 -0
  207. /package/dist/{src-DEGBASCD.js.map → src-DGIVA22K.js.map} +0 -0
  208. /package/dist/{src-DY7GLRO2.js.map → src-ECZWYNSV.js.map} +0 -0
  209. /package/dist/{src-NG7DRE5A.js.map → src-OKSAEHQS.js.map} +0 -0
  210. /package/dist/{start-ZYZSORPY.js.map → start-OCZGILRL.js.map} +0 -0
  211. /package/dist/{status-ESIG6A7W.js.map → status-YTJ6YITG.js.map} +0 -0
  212. /package/dist/{tag-3K5Y25AR.js.map → tag-KBG4SWRL.js.map} +0 -0
  213. /package/dist/{task-JLNX7O3M.js.map → task-O6XRMHYE.js.map} +0 -0
  214. /package/dist/{teams-F4IU3EXE.js.map → teams-NFUHS7XI.js.map} +0 -0
  215. /package/dist/{test-suite-QHDUW5W5.js.map → test-suite-6BH3DESN.js.map} +0 -0
  216. /package/dist/{tickets-NBL5WACL.js.map → tickets-JU2STK4B.js.map} +0 -0
  217. /package/dist/{trace-ZKYAI5JF.js.map → trace-NMSGKASJ.js.map} +0 -0
  218. /package/dist/{triage-5NWCREUM.js.map → triage-JGECJJU5.js.map} +0 -0
  219. /package/dist/{trigger-KXYYY5IR.js.map → trigger-OSPEVCCQ.js.map} +0 -0
  220. /package/dist/{update-check-Q2URVJLC.js.map → update-check-FNP6RFZO.js.map} +0 -0
  221. /package/dist/{upgrade-4XSASC7U.js.map → upgrade-3PPBXFAT.js.map} +0 -0
  222. /package/dist/{usage-UY7VZYVY.js.map → usage-QMCU2JIO.js.map} +0 -0
  223. /package/dist/{utilities-53NXNLA7.js.map → utilities-7VHWTSJT.js.map} +0 -0
  224. /package/dist/{visual-IJAT7X7D.js.map → visual-WIZSPTVK.js.map} +0 -0
  225. /package/dist/{workflow-cache-FP6MKLVR.js.map → workflow-cache-ACGM3BOI.js.map} +0 -0
  226. /package/dist/{workflow-cache-convergence-resolve-HS5KO7US.js.map → workflow-cache-convergence-resolve-KEQDQMKL.js.map} +0 -0
  227. /package/dist/{workflow-debug-EM55H2QE.js.map → workflow-debug-TVLBNRCB.js.map} +0 -0
  228. /package/dist/{workflow-feedback-GNWNWVHH.js.map → workflow-feedback-GB5VPGE2.js.map} +0 -0
  229. /package/dist/{workflow-fork-M5AQWST7.js.map → workflow-fork-2IPJNR76.js.map} +0 -0
  230. /package/dist/{workflow-promotion-HYULZDYL.js.map → workflow-promotion-QEQUXVUT.js.map} +0 -0
  231. /package/dist/{workflow-proposals-EN327FSE.js.map → workflow-proposals-KHGTPMD4.js.map} +0 -0
  232. /package/dist/{workflow-revisions-ZMTVNWRW.js.map → workflow-revisions-F5KQEEXB.js.map} +0 -0
  233. /package/dist/{workflow-status-FNAJG34W.js.map → workflow-status-66HC5MEA.js.map} +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../types/src/sandbox-egress.ts","../../types/src/exploration.ts","../../types/src/external-test.ts","../../types/src/exploratory-effort.ts","../../types/src/review-flavors.ts","../../types/src/workflows.ts","../../types/src/sandbox-instance-size.ts","../../types/src/adhoc-test.ts","../../types/src/visual-api.ts","../../types/src/browser-viewport.ts","../../types/src/proof.ts","../../types/src/proof-body.ts","../../types/src/agent-feedback.ts","../../types/src/index.ts","../../types/src/workflow-cache.ts","../../types/src/workflow-node-schemas.ts","../../types/src/api-sequence.ts","../../types/src/sandbox-preview.ts","../../types/src/credentials.ts","../../types/src/secret-redaction.ts","../../types/src/body-redaction.ts","../../types/src/issues.ts","../../types/src/issues-schema-utils.ts","../../types/src/issue-core.ts","../../types/src/issues-manual.ts","../../types/src/issue-evidence.ts","../../types/src/issues-diagnostic.ts","../../types/src/visual-review.ts","../../types/src/visual-text.ts","../../types/src/cache-action-contract.ts","../../types/src/local-browser.ts","../../types/src/benchmark.ts","../../types/src/diagnostics-filters.ts","../../types/src/sandbox-tasks.ts","../../types/src/macros.ts","../../types/src/variable-utils.ts","../../types/src/mcp-node.ts","../../types/src/node-type-labels.ts","../../types/src/visual-checkpoint.ts","../../types/src/browser-engine.ts","../../types/src/identifier-stability.ts","../../types/src/product-map.ts","../../types/src/announcements.ts","../../types/src/assistant-pages.ts"],"sourcesContent":["import { z } from 'zod';\n\n// v1 ships observe-only: `observe` logs unmatched egress without dropping it.\n// A future `enforce` mode (actually blocking traffic) is added here only once the\n// drop-and-block script/provisioning path exists — exposing it before then would\n// let callers pick an enforcement-sounding mode that silently still allows egress.\nexport const SANDBOX_EGRESS_MODES = ['off', 'observe'] as const;\nexport type SandboxEgressMode = (typeof SANDBOX_EGRESS_MODES)[number];\n\nexport const SandboxEgressModeSchema = z.enum(SANDBOX_EGRESS_MODES);\n\nconst domainPattern =\n /^(?=.{1,253}$)(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\\.)+[a-zA-Z]{2,63}$/;\n\nconst ipv4CidrPattern =\n /^(?:(?:25[0-5]|2[0-4]\\d|1?\\d?\\d)\\.){3}(?:25[0-5]|2[0-4]\\d|1?\\d?\\d)\\/(?:[0-9]|[12]\\d|3[0-2])$/;\n\nconst ipv6CidrPattern = /^(?:[0-9a-fA-F:]+)\\/(?:[0-9]|[1-9]\\d|1[01]\\d|12[0-8])$/;\n\nexport const SandboxEgressAllowlistEntrySchema = z\n .object({\n kind: z.enum(['domain', 'cidr']),\n value: z.string().trim().min(1).max(253),\n note: z.string().trim().max(500).optional(),\n addedBy: z.string().uuid().optional(),\n addedAt: z.string().datetime().optional(),\n })\n .superRefine((entry, ctx) => {\n if (entry.kind === 'domain' && !domainPattern.test(entry.value)) {\n ctx.addIssue({\n code: 'custom',\n path: ['value'],\n message: 'domain allowlist entries must be fully qualified domain names',\n });\n }\n\n if (\n entry.kind === 'cidr' &&\n !ipv4CidrPattern.test(entry.value) &&\n !ipv6CidrPattern.test(entry.value)\n ) {\n ctx.addIssue({\n code: 'custom',\n path: ['value'],\n message: 'cidr allowlist entries must be IPv4 or IPv6 CIDR ranges',\n });\n }\n });\n\nexport const SandboxEgressAllowlistSchema = z\n .array(SandboxEgressAllowlistEntrySchema)\n .max(200);\n\nexport type SandboxEgressAllowlistEntry = z.infer<\n typeof SandboxEgressAllowlistEntrySchema\n>;\nexport type SandboxEgressAllowlist = z.infer<typeof SandboxEgressAllowlistSchema>;\n","/**\n * Exploration cycle vocabulary shared by the API, the web client and the\n * CLI, so \"is this cycle still running\" is answered in one place.\n *\n * @module exploration\n */\n\nexport type ExplorationCycleStatus =\n | 'dispatching'\n | 'exploring'\n | 'reconciling'\n | 'completed'\n | 'failed';\n\n/** Statuses that hold the one-active-cycle-per-property slot. */\nexport const ACTIVE_EXPLORATION_CYCLE_STATUSES: readonly ExplorationCycleStatus[] = [\n 'dispatching',\n 'exploring',\n 'reconciling',\n];\n\nexport function isActiveExplorationCycleStatus(status: string): boolean {\n return (ACTIVE_EXPLORATION_CYCLE_STATUSES as readonly string[]).includes(status);\n}\n","import { z } from 'zod';\n\n/**\n * External test-suite ingest contract — the provider-agnostic report shape that\n * every producer (our hosted runner now; a customer's CI later) emits, and that\n * the ingest API validates. Kept deliberately small: raw per-test results +\n * a normalized step timeline + artifact references. The durable per-test\n * catalog, flaky-vs-fail classification, and read-model projection are computed\n * server-side from these (see the design doc). Storage lives in\n * `packages/db/src/schema/external-test.ts`.\n */\n\nexport const EXTERNAL_TEST_PROVIDERS = ['selenium', 'playwright', 'cypress', 'junit'] as const;\nexport type ExternalTestProvider = (typeof EXTERNAL_TEST_PROVIDERS)[number];\n\n/**\n * Per-attempt wall-clock cap applied when a runner sets no `shardTimeoutMs`.\n *\n * Lives here rather than in the API because the config UI has to *name* the\n * resolved value — a bare `0` in a timeout field reads as \"no limit\", which is\n * the opposite of what it means. Two copies of this number would drift, and the\n * drift would surface only as a suite killed earlier than its own settings page\n * claimed. `run.job.ts` re-exports it, so API callers are unchanged.\n */\nexport const DEFAULT_SHARD_TIMEOUT_MS = 60 * 60 * 1000;\n\n/**\n * Ceiling on a runner's configured `shardTimeoutMs`. Policy: a slice that cannot\n * finish in four hours wants more shards, not more patience. Raised from two\n * hours when three directory areas of a 53-area customer suite ran into the cap\n * once per-test retries started re-running failures; every other runtime bound\n * on the sandbox path (lease TTL, action and SSM timeouts, poll deadline) is\n * derived from the configured value, so this is the only number to move. The\n * pool maintenance reaper's 12 h is counted from the instance's original pool\n * claim, so a reused idle instance claimed more than ~8 h earlier could still be\n * reaped mid-area — rare (it needs chained leases on one instance), and\n * pre-existing at the old cap with ~10 h of margin.\n */\nexport const MAX_SHARD_TIMEOUT_MS = 4 * 60 * 60 * 1000;\n\n/** Durable outbox job that moves a superseded uploaded suite bundle onto lifecycle retention. */\nexport const EXTERNAL_TEST_BUNDLE_RETIREMENT_JOB_TYPE = 'external-test.bundle-retirement';\nexport const EXTERNAL_AREA_VERIFICATION_JOB_TYPE = 'external-test.verify-areas';\n\n/** Raw per-attempt result status as reported by the framework. */\nexport const EXTERNAL_TEST_RESULT_STATUSES = ['passed', 'failed', 'skipped'] as const;\nexport type ExternalTestResultStatus = (typeof EXTERNAL_TEST_RESULT_STATUSES)[number];\n\n/** Coarse category for a normalized step (drives icons/grouping in the UI). */\nexport const EXTERNAL_TEST_STEP_CATEGORIES = [\n 'navigate',\n 'click',\n 'type',\n 'assert',\n 'api',\n 'other',\n] as const;\nexport type ExternalTestStepCategory = (typeof EXTERNAL_TEST_STEP_CATEGORIES)[number];\n\nexport const EXTERNAL_TEST_ARTIFACT_KINDS = [\n 'report',\n 'video',\n 'trace',\n 'log',\n 'screenshot',\n] as const;\nexport type ExternalTestArtifactKind = (typeof EXTERNAL_TEST_ARTIFACT_KINDS)[number];\n\n/** Source location in the authored feature file. */\nexport const ExternalTestLocationSchema = z.object({\n file: z.string().min(1).max(500),\n line: z.number().int().nonnegative(),\n});\nexport type ExternalTestLocation = z.infer<typeof ExternalTestLocationSchema>;\n\n/** One attempt's own wall-clock execution window. */\nexport const ExternalTestAttemptTimingSchema = z.object({\n startedAt: z.string().datetime(),\n finishedAt: z.string().datetime(),\n});\nexport type ExternalTestAttemptTiming = z.infer<typeof ExternalTestAttemptTimingSchema>;\n\n/** Resolved authored input attached to a Gherkin step. */\nexport const ExternalTestBddArgumentSchema = z.discriminatedUnion('kind', [\n z.object({\n kind: z.literal('docString'),\n content: z.string().max(64 * 1024),\n mediaType: z.string().max(200).nullable(),\n }),\n z.object({\n kind: z.literal('dataTable'),\n rows: z.array(z.array(z.string().max(16 * 1024)).max(100)).max(100),\n }),\n]);\nexport type ExternalTestBddArgument = z.infer<typeof ExternalTestBddArgumentSchema>;\n\n/** BDD step details kept separate from provider execution identity. */\nexport const ExternalTestBddStepSchema = z.object({\n keyword: z.string().max(100),\n location: ExternalTestLocationSchema,\n background: z.boolean(),\n argument: ExternalTestBddArgumentSchema.nullable(),\n});\nexport type ExternalTestBddStep = z.infer<typeof ExternalTestBddStepSchema>;\n\nconst ExternalTestBddNodeSchema = z.object({\n name: z.string().max(2_000),\n description: z.string().max(16 * 1024),\n location: ExternalTestLocationSchema,\n});\n\nconst MAX_PROPERTIES = 30;\nconst MAX_PROPERTY_LABEL = 100;\nconst MAX_PROPERTY_VALUE = 500;\n\n/**\n * One annotation rendered as a label/value pair (e.g. Tenant ID: tenant-1). The\n * shape only: `normalizeTestProperties` owns the limits and truncates rather than\n * rejects, so one long annotation cannot fail the whole result batch.\n */\nconst ExternalTestPropertySchema = z.object({\n label: z.string(),\n value: z.string(),\n});\nexport type ExternalTestProperty = z.infer<typeof ExternalTestPropertySchema>;\n\n/** Authored and resolved Gherkin context for one executed scenario/pickle. */\nexport const ExternalTestBddContextSchema = z.object({\n feature: ExternalTestBddNodeSchema,\n scenario: ExternalTestBddNodeSchema,\n tags: z.array(z.string().min(1).max(200)).max(50),\n rule: ExternalTestBddNodeSchema.nullable(),\n example: z\n .object({\n line: z.number().int().nonnegative(),\n column: z.number().int().nonnegative(),\n values: z\n .array(z.object({ column: z.string().max(2_000), value: z.string().max(16 * 1024) }))\n .max(100),\n })\n .nullable(),\n});\nexport type ExternalTestBddContext = z.infer<typeof ExternalTestBddContextSchema>;\n\n/** Return elapsed wall time only when both valid instants were recorded. */\nexport function elapsedTestAttemptMs(\n startedAt: string | null | undefined,\n finishedAt: string | null | undefined\n): number | null {\n if (!startedAt || !finishedAt) return null;\n const start = Date.parse(startedAt);\n const finish = Date.parse(finishedAt);\n return Number.isFinite(start) && Number.isFinite(finish) && finish >= start\n ? finish - start\n : null;\n}\n\n/** One structured error from a failing test. */\nexport const ExternalTestErrorSchema = z.object({\n message: z.string().optional(),\n stack: z.string().optional(),\n type: z.string().optional(),\n});\nexport type ExternalTestError = z.infer<typeof ExternalTestErrorSchema>;\n\n/** A normalized step within a test — powers the left-nav timeline + video scrub. */\nexport const ExternalTestStepSchema = z.object({\n index: z.number().int().nonnegative(),\n title: z.string(),\n status: z.enum(EXTERNAL_TEST_RESULT_STATUSES),\n category: z.enum(EXTERNAL_TEST_STEP_CATEGORIES).default('other'),\n startMs: z.number().nonnegative().optional(),\n durationMs: z.number().nonnegative().optional(),\n /** Reference (storage key) to a screenshot captured at this step. */\n screenshotRef: z.string().optional(),\n /** Reference (storage key) to a DOM snapshot captured at this step. */\n domRef: z.string().optional(),\n /** BDD-authored step metadata; absent for ordinary provider steps. */\n bdd: ExternalTestBddStepSchema.optional(),\n});\nexport type ExternalTestStep = z.infer<typeof ExternalTestStepSchema>;\n\n/**\n * A text-only attachment captured by the framework (e.g. a Cucumber data\n * dump, a JSON payload). Binary attachments (screenshots/videos/traces) are\n * artifacts, handled by {@link ExternalTestArtifactRefSchema}, not this.\n */\nexport const ExternalTestOutputAttachmentSchema = z.object({\n name: z.string().min(1).max(200),\n contentType: z.string().min(1).max(100),\n /** Capped well below the batch body limit — this rides inline, not via S3. */\n body: z.string().max(16 * 1024),\n});\nexport type ExternalTestOutputAttachment = z.infer<typeof ExternalTestOutputAttachmentSchema>;\n\n/** A test's own stdout/stderr and text attachments (capped; see the boundary reporter). */\nexport const ExternalTestResultOutputSchema = z.object({\n stdout: z\n .string()\n .max(70 * 1024)\n .optional(),\n stderr: z\n .string()\n .max(70 * 1024)\n .optional(),\n attachments: z.array(ExternalTestOutputAttachmentSchema).max(20).optional(),\n});\nexport type ExternalTestResultOutput = z.infer<typeof ExternalTestResultOutputSchema>;\n\nconst MAX_TAGS = 50;\n// Playwright's built-in annotation types carry no label/value meaning.\nconst PLAYWRIGHT_INTERNAL_ANNOTATIONS = new Set(['skip', 'fixme', 'slow', 'fail']);\nconst MAX_TAG_LENGTH = 200;\nconst MAX_OUTPUT_CHARS = 70 * 1024;\nconst MAX_ATTACHMENTS = 20;\nconst MAX_ATTACHMENT_CHARS = 16 * 1024;\n\nfunction clampText(text: string, max: number): string {\n if (text.length <= max) return text;\n // The marker takes room from the kept text, so the count covers it too.\n const markerFor = (cut: number) => `\\n… [truncated ${cut} chars]`;\n const kept = max - markerFor(text.length).length;\n return text.slice(0, kept) + markerFor(text.length - kept);\n}\n\n/** Control characters (incl. newlines and ANSI escapes) become spaces: values are printed by the CLI. */\nconst CONTROL_CHARS = /[\\u0000-\\u001f\\u007f-\\u009f]+/g;\n\n/**\n * Annotations as stored: {label, value} pairs in report order, with Playwright's\n * internal annotation types and entries without a value dropped, control\n * characters flattened, text truncated (never rejected), and exact duplicates\n * removed.\n */\nexport function normalizeTestProperties(value: unknown): ExternalTestProperty[] | undefined {\n if (!Array.isArray(value)) return undefined;\n const seen = new Set<string>();\n const properties: ExternalTestProperty[] = [];\n for (const entry of value) {\n if (properties.length >= MAX_PROPERTIES) break;\n if (!entry || typeof entry !== 'object') continue;\n const { label: rawLabel, value: rawText } = entry as Record<string, unknown>;\n if (typeof rawLabel !== 'string' || typeof rawText !== 'string') continue;\n const label = rawLabel.replace(CONTROL_CHARS, ' ').trim();\n const text = rawText.replace(CONTROL_CHARS, ' ').trim();\n if (label.length === 0 || text.length === 0) continue;\n if (PLAYWRIGHT_INTERNAL_ANNOTATIONS.has(label)) continue;\n const property = {\n label: label.slice(0, MAX_PROPERTY_LABEL),\n value: text.slice(0, MAX_PROPERTY_VALUE),\n };\n const key = JSON.stringify([property.label, property.value]);\n if (seen.has(key)) continue;\n seen.add(key);\n properties.push(property);\n }\n return properties.length > 0 ? properties : undefined;\n}\n\n/**\n * Tags as stored: strings only, deduplicated, clamped to the ingest schema's\n * limits. Container output is not schema-validated on the way in, so every\n * writer normalizes through this and the read side can trust the shape.\n */\nexport function normalizeTestTags(value: unknown): string[] | undefined {\n if (!Array.isArray(value)) return undefined;\n const tags = [\n ...new Set(value.filter((tag): tag is string => typeof tag === 'string' && tag.length > 0)),\n ]\n .slice(0, MAX_TAGS)\n .map((tag) => tag.slice(0, MAX_TAG_LENGTH));\n return tags.length > 0 ? tags : undefined;\n}\n\n/**\n * Output as stored: the {@link ExternalTestResultOutputSchema} shape and\n * limits, whatever the container wrote. Runs after secret scrubbing too, which\n * can lengthen text (`[REDACTED]` outgrows a short secret).\n */\nexport function normalizeTestOutput(value: unknown): ExternalTestResultOutput | undefined {\n if (!value || typeof value !== 'object') return undefined;\n const output = value as Record<string, unknown>;\n const out: ExternalTestResultOutput = {};\n if (typeof output.stdout === 'string' && output.stdout.length > 0) {\n out.stdout = clampText(output.stdout, MAX_OUTPUT_CHARS);\n }\n if (typeof output.stderr === 'string' && output.stderr.length > 0) {\n out.stderr = clampText(output.stderr, MAX_OUTPUT_CHARS);\n }\n if (Array.isArray(output.attachments)) {\n const attachments = output.attachments\n .flatMap((entry) => {\n if (!entry || typeof entry !== 'object') return [];\n const attachment = entry as Record<string, unknown>;\n if (\n typeof attachment.contentType !== 'string' ||\n attachment.contentType.length === 0 ||\n typeof attachment.body !== 'string'\n ) {\n return [];\n }\n const name =\n typeof attachment.name === 'string' && attachment.name.length > 0\n ? attachment.name\n : 'attachment';\n return [\n {\n name: name.slice(0, 200),\n contentType: attachment.contentType.slice(0, 100),\n body: clampText(attachment.body, MAX_ATTACHMENT_CHARS),\n },\n ];\n })\n .slice(0, MAX_ATTACHMENTS);\n if (attachments.length > 0) out.attachments = attachments;\n }\n return Object.keys(out).length > 0 ? out : undefined;\n}\n\n/** A location as stored, or undefined when the shape is not one. */\nexport function normalizeTestLocation(value: unknown): ExternalTestLocation | undefined {\n const parsed = ExternalTestLocationSchema.safeParse(value);\n return parsed.success ? parsed.data : undefined;\n}\n\n/** One test's result within a shard (one attempt). */\nexport const ExternalTestResultSchema = z.object({\n /**\n * Provider-immutable stable identity (repo + normalized spec path + describe/it\n * title path). NOT `file#title`. Used to attribute history to the durable test.\n */\n testKey: z.string().min(1),\n displayName: z.string().min(1),\n /** describe → it hierarchy (suite → section → scenario → action). */\n suitePath: z.array(z.string()).default([]),\n status: z.enum(EXTERNAL_TEST_RESULT_STATUSES),\n durationMs: z.number().nonnegative().optional(),\n /** 1-based attempt index (retries append). */\n attempt: z.number().int().positive().default(1),\n error: ExternalTestErrorSchema.optional(),\n steps: z.array(ExternalTestStepSchema).default([]),\n /**\n * Framework tags (e.g. playwright-bdd's mapped Gherkin `@tags`). Optional:\n * only providers that expose tags on the test send this.\n */\n tags: z.array(z.string().min(1).max(200)).max(50).optional(),\n /**\n * Ordered label/value pairs from Playwright `testInfo.annotations`\n * (`type` -> label, `description` -> value). Optional: absent when empty.\n */\n properties: z.array(ExternalTestPropertySchema).optional(),\n /** Where the test is defined. Optional: only Playwright reports this today. */\n location: ExternalTestLocationSchema.optional(),\n /** The test's own stdout/stderr/attachments. Optional: absent when empty. */\n output: ExternalTestResultOutputSchema.optional(),\n /** Exact execution window; absent when a provider did not emit boundaries. */\n timing: ExternalTestAttemptTimingSchema.optional(),\n /** Resolved Gherkin context; absent for non-BDD providers. */\n bdd: ExternalTestBddContextSchema.optional(),\n});\nexport type ExternalTestResult = z.infer<typeof ExternalTestResultSchema>;\n\n/** Reference to an uploaded artifact (bytes are PUT to a presigned URL first). */\nexport const ExternalTestArtifactRefSchema = z.object({\n kind: z.enum(EXTERNAL_TEST_ARTIFACT_KINDS),\n storageKey: z.string().min(1),\n /** Optional link to the specific test this artifact belongs to. */\n testKey: z.string().optional(),\n sizeBytes: z.number().int().nonnegative().optional(),\n});\nexport type ExternalTestArtifactRef = z.infer<typeof ExternalTestArtifactRefSchema>;\n\n/** Manifest posted when a run (or a shard within it) opens. */\nexport const ExternalTestRunManifestSchema = z.object({\n runnerId: z.string().uuid(),\n provider: z.enum(EXTERNAL_TEST_PROVIDERS),\n /** The framework/CI-side run id, if any (used for idempotent grouping). */\n providerRunId: z.string().optional(),\n commit: z.string().optional(),\n branch: z.string().optional(),\n /** Pinned commit SHA the shards ran against. */\n repoRevision: z.string().optional(),\n targetUrl: z.string().url().optional(),\n shardIndex: z.number().int().nonnegative().optional(),\n shardTotal: z.number().int().positive().optional(),\n startedAt: z.string().datetime().optional(),\n});\nexport type ExternalTestRunManifest = z.infer<typeof ExternalTestRunManifestSchema>;\n\n/** Body for `POST /api/v1/external-test-runs/:id/results` (idempotent append). */\nexport const ExternalTestResultsBatchSchema = z.object({\n shardIndex: z.number().int().nonnegative().optional(),\n results: z.array(ExternalTestResultSchema).min(1),\n artifacts: z.array(ExternalTestArtifactRefSchema).default([]),\n});\nexport type ExternalTestResultsBatch = z.infer<typeof ExternalTestResultsBatchSchema>;\n\n/**\n * Cross-attempt verdict computed server-side at seal. `flaky` = failed at least\n * one attempt but passed a later one; `failed` = failed every attempt\n * (definitive — the only verdict that triggers triage).\n */\nexport const EXTERNAL_TEST_VERDICTS = ['passed', 'flaky', 'failed', 'skipped'] as const;\nexport type ExternalTestVerdict = (typeof EXTERNAL_TEST_VERDICTS)[number];\n\n/** Rolled-up reliability of a durable test over its history. */\nexport const EXTERNAL_TEST_HEALTHS = ['unverified', 'passing', 'flaky', 'failing'] as const;\nexport type ExternalTestHealth = (typeof EXTERNAL_TEST_HEALTHS)[number];\n","/**\n * How long an exploratory run may work, chosen at start. Only flavors that\n * declare an `effort` capability (the fuzzer today) take one; the API start\n * route, the run's budget and the web launcher all read this one list.\n */\nexport const EXPLORATORY_EFFORT_VALUES = ['quick', 'standard', 'deep'] as const;\n\nexport type ExploratoryEffort = (typeof EXPLORATORY_EFFORT_VALUES)[number];\n\n/** The run's time budget: about 5, 15 and 30 minutes. */\nexport const EXPLORATORY_EFFORT_MINUTES: Record<ExploratoryEffort, number> = {\n quick: 5,\n standard: 15,\n deep: 30,\n};\n\n/** What the fuzzer found and covered, stored as the run's `reportJson`. */\nexport type FuzzReportJson = {\n kind: 'fuzz';\n effort: ExploratoryEffort;\n minutesUsed: number;\n /**\n * Why the run ended: it worked through its plan and consecutive attacks stopped\n * turning up anything new (`saturated`), it used its whole budget (`budget_used`),\n * the agent finished a quick run on its own (`agent_finished`), or it ended\n * without a final report and the report was built from its state (`no_report`).\n */\n stopReason: 'saturated' | 'budget_used' | 'agent_finished' | 'no_report';\n summary: string;\n /** Every area the fuzzer planned to attack, in the order it was added, with its result. */\n attackSurface: Array<{\n id: string;\n area: string;\n status: 'untried' | 'clean' | 'bug_found' | 'blocked';\n result: string | null;\n }>;\n /** Reproduced bugs filed as issues. */\n findings: {\n total: number;\n byCategory: Record<string, number>;\n bySeverity: { low: number; medium: number; high: number };\n };\n /** Private environments created and browser sessions opened. */\n environmentsUsed: number;\n sessionsUsed: number;\n /** What the fuzzer did not get to, or could not attack, in its own words. */\n coverageGaps: string[];\n};\n","import type { ExploratoryEffort } from './exploratory-effort.js';\n\n/**\n * Canonical review-flavor value list — the single source of truth consumed by\n * the `review_flavor` pg enum (`packages/db/src/schema/enums.ts`), API zod\n * schemas, repository types, and the web client. Adding a new flavor here\n * requires a pg enum migration (`bun run db:generate`), a\n * `FlavorDefinition` (see `apps/api/src/adhoc-test/flavors/index.ts`) and a\n * `PLAN_PREPARERS` entry in `apps/api/src/adhoc-test/run.ts`.\n */\nexport const REVIEW_FLAVOR_VALUES = ['functional', 'ux', 'spec', 'fuzz'] as const;\n\nexport type ReviewFlavor = (typeof REVIEW_FLAVOR_VALUES)[number];\n\n/** Where a run can execute. Mirrors the `kind` of the start-route target. */\nexport type FlavorTargetKind = 'live' | 'sandbox' | 'sandbox-v2-environment' | 'sandbox-v2';\n\n/**\n * How a flavor treats credentials.\n * - `optional`: one credential may be chosen; none runs anonymously.\n * - `tempIdentity`: like `optional`, and may mint a throwaway identity for sign-up walkthroughs.\n * - `inventory`: the agent chooses every actor from the property's credential inventory\n * (no single credential is picked at start; the inventory must not be empty).\n */\nexport type FlavorCredentialMode = 'optional' | 'tempIdentity' | 'inventory';\n\n/**\n * The capabilities both the API (start validation, executor) and the web\n * launcher read, so the target and credential rules cannot drift between them.\n */\nexport const FLAVOR_CAPABILITIES: Record<\n ReviewFlavor,\n {\n supportedTargets: readonly FlavorTargetKind[];\n credentials: FlavorCredentialMode;\n /** Flavors with an effort choice (see `ExploratoryEffort`) preselect `default`; `null` = no effort. */\n effort: { default: ExploratoryEffort } | null;\n }\n> = {\n functional: {\n supportedTargets: ['live', 'sandbox', 'sandbox-v2-environment', 'sandbox-v2'],\n credentials: 'optional',\n effort: null,\n },\n ux: { supportedTargets: ['live'], credentials: 'tempIdentity', effort: null },\n spec: { supportedTargets: ['live'], credentials: 'inventory', effort: null },\n // Fuzzing is destructive, so it runs only in private sandbox v2 copies, never live or on sandbox v1.\n fuzz: {\n supportedTargets: ['sandbox-v2-environment', 'sandbox-v2'],\n credentials: 'optional',\n effort: { default: 'standard' },\n },\n};\n","export const WORKFLOW_RUN_SEGMENT_JOB_TYPE = 'workflows.run-segment' as const;\n\n/**\n * Wall-clock budget for one `workflows.run-segment` job, in seconds. Single\n * source for the job's own `timeoutSeconds` and for anything that must size a\n * dependency to outlive a run segment (e.g. a MicroVM's\n * `maximumDurationInSeconds` — see `apps/api/src/microvm-browser/service.ts`).\n */\nexport const WORKFLOW_RUN_SEGMENT_TIMEOUT_SECONDS = 90 * 60;\n\n// Suite Runner Orchestrator Pattern\nexport const WORKFLOW_TEST_SUITE_ORCHESTRATOR_JOB_TYPE =\n 'workflows.test-suite-orchestrator' as const;\nexport const WORKFLOW_TEST_SUITE_WORKFLOW_JOB_TYPE = 'workflows.test-suite-workflow' as const;\nexport const WORKFLOW_TEST_SUITE_CREDENTIAL_WARMUP_JOB_TYPE =\n 'workflows.test-suite-credential-warmup' as const;\nexport const WORKFLOW_SUITE_SETUP_JOB_TYPE = 'workflows.suite-setup' as const;\nexport const WORKFLOW_SEED_JOB_TYPE = 'workflow.seed' as const;\nexport const WORKFLOW_TEARDOWN_JOB_TYPE = 'workflow.teardown' as const;\n\n/**\n * Maximum wait a seed or teardown flow may declare.\n *\n * These flow types run their waits inline — the executing fiber sleeps in place\n * rather than ending the segment and scheduling a resume job — so an unbounded\n * wait would hold a browser session (and a job slot) open for its whole\n * duration. Standard flows keep the segmented behaviour and are not capped by\n * this.\n */\nexport const SEED_MAX_WAIT_SECONDS = 300;\n\n/**\n * Flow types whose `wait` nodes sleep inline instead of splitting the run into\n * segments.\n *\n * Seed and teardown flows run as nested sub-runs driven by the parent flow's\n * job. Nothing enqueues a continuation segment for them, so a segmented wait\n * strands the nested run in `waiting` forever. Capped inline sleeps keep them\n * single-segment and always terminal.\n *\n * `setup` is deliberately excluded: inline setup flows strip wait nodes, and\n * suite setups reject them outright.\n */\nexport function flowTypeUsesInlineWaits(flowType: string | null | undefined): boolean {\n return flowType === 'seed' || flowType === 'teardown';\n}\n\n/** A full 40-hex commit id: normalize only SHA-shaped suite pins; push tags stay case-sensitive. */\nexport const PINNED_COMMIT_PATTERN = /^[0-9a-fA-F]{40}$/;\n\nexport type WorkflowSuiteRunPayload = {\n workflowId?: string; // For single-workflow authoring\n /** Filter to specific workflow IDs (e.g. CSV import auto-run) */\n workflowIds?: string[];\n /**\n * Restrict the suite to workflows whose credential references all resolve\n * to this property. When set, login nodes' `credentialTemplateId` /\n * `credentialId` are resolved to their backing property and the suite skips\n * (a) workflows with zero resolvable credentials, and (b) workflows whose\n * credentials resolve to any other property. Null/undefined = no filter.\n */\n propertyId?: string | null;\n /** Target environment for credential resolution and base-URL substitution. */\n environmentId?: string | null;\n /**\n * How the suite picks an environment per flow:\n * - `'mixed'` (default) — each flow runs against its own preferred env\n * (`workflows.defaultEnvironmentId`, else the property default). Nothing is\n * skipped for env-eligibility reasons.\n * - `'force'` — every flow runs against `environmentId`; flows ineligible for\n * it are skipped (legacy single-env behavior).\n * Omitted ⇒ `'force'` when an `environmentId` is supplied (back-compat with\n * existing single-env callers), else `'mixed'`.\n */\n environmentMode?: 'mixed' | 'force';\n /**\n * Explicit live sandbox reused from a prior non-successful execution. These\n * fields are internal job plumbing; callers must validate the instance and\n * runtime environment before enqueueing, and the orchestrator validates them\n * again before spawning children.\n */\n retainedSandboxInstanceId?: string | null;\n retainedSandboxRuntimeEnvironmentId?: string | null;\n retainedSandboxTemplateId?: string | null;\n retainedSandboxSourceExecutionId?: string | null;\n triggeredByUserId?: string | null;\n triggeredReason?: string | null;\n /**\n * Filter workflows by tag(s) — any-of semantics. A workflow runs if it has\n * at least one of the supplied tag names. Empty/undefined means \"no tag filter\".\n */\n tags?: string[] | null;\n /** Filter workflows by name pattern (case-insensitive substring match) */\n namePattern?: string | null;\n /**\n * Clear `degraded` health on every in-scope flow before the run, then run them\n * instead of recording them as \"degraded — not run\". Equivalent to calling\n * POST /workflows/:id/re-enable on each: health → 'unverified',\n * healthComputedAt → null, healthResetAt → now(). Applies to \"run all\"/tag\n * scopes and saved suites. Ad-hoc explicit-ID runs remain directly runnable\n * and do not mutate health through this option.\n */\n reEnableDegraded?: boolean;\n /**\n * Run a persisted, user-curated suite. When set, the orchestrator loads the\n * suite's members (ignoring tag/name/id selectors) and runs them in the\n * suite's layer order — layer N+1 spawns only after every workflow in layer N\n * reaches a terminal state.\n */\n suiteId?: string;\n /**\n * \"Retry all failed\" — reuse an existing terminal execution and retry only\n * its failed/timeout/inconclusive (workflow, scenario) pairs as a mini-suite\n * through the same orchestrator, appending new `attempt` rows against the\n * SAME execution id rather than starting a fresh one. When set, the\n * orchestrator's init phase calls `prepareWorkflowTestSuiteRetry` instead of\n * the normal suite prepare and every other selector field is ignored.\n */\n retryExecutionId?: string;\n /**\n * Optional source attempt ids that narrow a retry to specific\n * (workflow, scenario) buckets. Omitted means every latest retryable bucket.\n */\n retryTestRunIds?: string[];\n /** Internal: selected reruns may target any rerunnable terminal status. */\n retrySelectedTerminal?: boolean;\n /**\n * Internal: resolved suite pins, keyed by lower-cased repository full name\n * for GitHub applications or property id for pushed applications. Release QA\n * seeds the release's commit per repository. Unseeded targets resolve their\n * default version when the suite starts; `pinnedCommit` backstops them.\n */\n sandboxV2CommitByRepo?: Record<string, string>;\n /**\n * The caller's <sha|pushId|tag> pin (`cofactor test-suite run --at`). The run\n * route resolves it for each target's delivery and seeds `sandboxV2CommitByRepo`.\n * Its presence always excludes the run from the default revision, so a\n * pinned run never moves visual baselines.\n */\n pinnedCommit?: string;\n /**\n * Internal (retry in a new orchestrator): the commit the retried execution\n * pinned for each Sandboxes v2 target environment, so a retry runs on the\n * same commit as the attempts it retries.\n */\n sandboxV2PinnedShaByEnvironmentId?: Record<string, string>;\n};\n\n/** @deprecated Use WorkflowSuiteRunPayload — kept as alias during in-flight job migration. */\nexport type WorkflowPropertyTestRunPayload = WorkflowSuiteRunPayload;\n\nexport type WorkflowRunSegmentPayload = {\n runId: string;\n};\n\nexport type WorkflowTestStatus =\n | 'queued'\n | 'running'\n | 'waiting'\n | 'success'\n | 'failed'\n | 'inconclusive'\n | 'skipped'\n | 'flaky_success'\n | 'canceled'\n | 'timeout'\n // Not run because the workflow's health is degraded (suite runs only).\n | 'degraded';\n\n/** Test-run statuses eligible for retry-all. */\nexport const RETRYABLE_TEST_RUN_STATUSES: ReadonlySet<WorkflowTestStatus> = new Set([\n 'failed',\n 'timeout',\n 'inconclusive',\n]);\n\n/** Terminal test-run statuses eligible for an explicit single rerun. */\nexport const RERUNNABLE_TEST_RUN_STATUSES: ReadonlySet<WorkflowTestStatus> = new Set([\n 'success',\n 'failed',\n 'inconclusive',\n 'skipped',\n 'flaky_success',\n 'canceled',\n 'timeout',\n]);\n\n/** Hard ceiling for append-only attempts in one workflow/scenario bucket. */\nexport const MAX_WORKFLOW_TEST_ATTEMPTS = 10;\n\nexport type WorkflowTestAttemptLike = {\n workflowId: string;\n status: string;\n scenarioId?: string | null;\n attempt?: number | null;\n createdAt?: Date | string | null;\n layerIndex?: number | null;\n};\n\nexport type WorkflowTestActiveLayer =\n | { kind: 'none' }\n | { kind: 'unlayered' }\n | { kind: 'layered'; index: number };\n\n/**\n * Return the live layer without collapsing parallel-suite `null` into layer 0.\n * A suite cannot mix layered and unlayered live rows; if malformed data does,\n * the lowest explicit layer wins so later layers cannot start early.\n */\nexport const getWorkflowTestActiveLayer = (\n runs: ReadonlyArray<Pick<WorkflowTestAttemptLike, 'status' | 'layerIndex'>>\n): WorkflowTestActiveLayer => {\n let lowestLayer: number | null = null;\n let hasUnlayered = false;\n for (const run of runs) {\n if (!['queued', 'running', 'waiting'].includes(run.status)) continue;\n if (run.layerIndex === null || run.layerIndex === undefined) {\n hasUnlayered = true;\n } else if (lowestLayer === null || run.layerIndex < lowestLayer) {\n lowestLayer = run.layerIndex;\n }\n }\n if (lowestLayer !== null) return { kind: 'layered', index: lowestLayer };\n return hasUnlayered ? { kind: 'unlayered' } : { kind: 'none' };\n};\n\nexport const isWorkflowTestRunInActiveLayer = (\n run: Pick<WorkflowTestAttemptLike, 'layerIndex'>,\n activeLayer: WorkflowTestActiveLayer\n): boolean =>\n activeLayer.kind === 'unlayered'\n ? run.layerIndex === null || run.layerIndex === undefined\n : activeLayer.kind === 'layered' && run.layerIndex === activeLayer.index;\n\n/** Canonical bucket used by persistence, rollups, retry selection, and UI history. */\nexport const workflowTestAttemptBucketKey = (\n run: Pick<WorkflowTestAttemptLike, 'workflowId' | 'scenarioId'>\n): string => `${run.workflowId}:${run.scenarioId ?? ''}`;\n\nconst WORKFLOW_TEST_STATUS_PRIORITY: Readonly<Record<string, number>> = {\n canceled: 0,\n timeout: 1,\n skipped: 2,\n inconclusive: 3,\n failed: 4,\n waiting: 5,\n queued: 6,\n running: 7,\n flaky_success: 8,\n success: 9,\n};\n\nconst attemptCreatedAt = (value: Date | string | null | undefined): number => {\n if (value instanceof Date) return value.getTime();\n if (typeof value !== 'string') return -Infinity;\n const timestamp = Date.parse(value);\n return Number.isNaN(timestamp) ? -Infinity : timestamp;\n};\n\n/**\n * Compare rows in one workflow/scenario bucket. Highest attempt wins, then\n * newest creation time, then the legacy status priority for pre-index rows.\n */\nexport const isNewerWorkflowTestAttempt = <T extends WorkflowTestAttemptLike>(\n candidate: T,\n existing: T\n): boolean => {\n const candidateAttempt = candidate.attempt ?? 1;\n const existingAttempt = existing.attempt ?? 1;\n if (candidateAttempt !== existingAttempt) return candidateAttempt > existingAttempt;\n\n const candidateCreatedAt = attemptCreatedAt(candidate.createdAt);\n const existingCreatedAt = attemptCreatedAt(existing.createdAt);\n if (candidateCreatedAt !== existingCreatedAt) return candidateCreatedAt > existingCreatedAt;\n\n return (\n (WORKFLOW_TEST_STATUS_PRIORITY[candidate.status] ?? 0) >\n (WORKFLOW_TEST_STATUS_PRIORITY[existing.status] ?? 0)\n );\n};\n\n/** Keep the authoritative row for every workflow/scenario attempt bucket. */\nexport const latestWorkflowTestRunAttempts = <T extends WorkflowTestAttemptLike>(\n runs: readonly T[]\n): T[] => {\n const latestByBucket = new Map<string, T>();\n for (const run of runs) {\n const key = workflowTestAttemptBucketKey(run);\n const existing = latestByBucket.get(key);\n if (!existing || isNewerWorkflowTestAttempt(run, existing)) {\n latestByBucket.set(key, run);\n }\n }\n return [...latestByBucket.values()];\n};\n\n/**\n * Canonical set of **test-run** statuses that are terminal (no further\n * transitions). Import this instead of re-declaring it — the orchestrator's\n * layer gating and the suite run-detail \"layer complete\" check share it.\n * NOTE: distinct from terminal *execution* statuses (completed /\n * completed_with_errors / …), which are a separate enum.\n */\nexport const TERMINAL_TEST_RUN_STATUSES: ReadonlySet<string> = new Set([\n 'success',\n 'failed',\n 'inconclusive',\n 'skipped',\n 'flaky_success',\n 'canceled',\n 'timeout',\n 'degraded',\n]);\n\nexport const isTerminalTestRunStatus = (status: string): boolean =>\n TERMINAL_TEST_RUN_STATUSES.has(status);\n\n/**\n * Canonical **test-run** statuses that count as a pass and as a hard failure.\n *\n * These decide what the suite's persisted counters (`successfulWorkflows` /\n * `failedWorkflows`) hold, which is in turn what every reporting surface shows —\n * the runs list, the suite detail page, and the Slack suite card. Import them\n * instead of re-declaring: two surfaces having their own copy of this mapping is\n * exactly how Slack came to count `canceled` as skipped while the counters\n * counted it as failed.\n *\n * Every other terminal status (`skipped`, `degraded`, `inconclusive`) is neither\n * a pass nor a hard failure and is tracked in its own counter column.\n */\nexport const WORKFLOW_TEST_SUCCESS_STATUSES: ReadonlySet<string> = new Set([\n 'success',\n 'flaky_success',\n]);\n\nexport const WORKFLOW_TEST_FAILED_STATUSES: ReadonlySet<string> = new Set([\n 'failed',\n 'timeout',\n 'canceled',\n]);\n\n/**\n * Terminal statuses that are neither a pass nor a hard failure — the run\n * produced no product verdict. `skipped` and `degraded` never ran at all;\n * `inconclusive` ran but hit a platform error.\n *\n * Computed by subtraction rather than enumerated, so a status added to\n * `workflow_test_run_status` lands here automatically instead of being silently\n * dropped by a surface that only knows the two named sets above.\n */\nexport const WORKFLOW_TEST_CONTEXT_STATUSES: ReadonlySet<string> = new Set(\n [...TERMINAL_TEST_RUN_STATUSES].filter(\n (status) =>\n !WORKFLOW_TEST_SUCCESS_STATUSES.has(status) && !WORKFLOW_TEST_FAILED_STATUSES.has(status)\n )\n);\n\n/**\n * Workflow reliability dimension, orthogonal to {@link WorkflowDefinitionStatus}\n * (lifecycle). Recomputed after each suite run from the workflow's test-run history.\n *\n * - `unverified` — no terminal test runs yet\n * - `never_passed` — has run, never a single pass (authoritative via firstPassedAt)\n * - `passing` — recent window all green, streak below the reliable threshold\n * - `reliable` — enough consecutive clean passes\n * - `flaky` — latest run passed, but the recent window had failures / a flaky_success\n * - `failing` — latest run failed but NOT auto-skipped (< DEGRADE_AFTER failures, or not eligible)\n * - `degraded` — eligible + enough consecutive failures; skipped in suite runs\n * (\"degraded — not run\") until the workflow is edited or manually re-enabled\n */\nexport const WORKFLOW_HEALTH = [\n 'unverified',\n 'never_passed',\n 'passing',\n 'reliable',\n 'flaky',\n 'failing',\n 'degraded',\n] as const;\n\nexport type WorkflowHealth = (typeof WORKFLOW_HEALTH)[number];\n\n/** Display label per health state, shared by the web's flow list, filters, and health badges. */\nexport const WORKFLOW_HEALTH_LABEL: Record<WorkflowHealth, string> = {\n unverified: 'Unverified',\n never_passed: 'Never passed',\n passing: 'Passing',\n reliable: 'Reliable',\n flaky: 'Flaky',\n failing: 'Failing',\n degraded: 'Degraded',\n};\n\n/**\n * Management-status axis for a workflow — the *manual* lifecycle control, distinct\n * from approval ({@link WorkflowDefinitionStatus}: \"is the test testing the right\n * thing?\") and reliability ({@link WorkflowHealth}: computed from run history).\n * This axis answers \"should this flow run right now, and if not, why not?\".\n *\n * - `active` — normal state; the flow participates in suites when eligible\n * - `paused` — manually taken out of suite runs (temporary, expected to resume)\n * - `blocked` — cannot meaningfully run/pass (e.g. waiting on a bug fix or ticket)\n * - `under_construction` — being actively built/reworked; not ready to run\n * - `deprecated` — kept for history but no longer maintained or run\n *\n * Every value other than `active` excludes the flow from suite runs (via the\n * `paused_at` mirror — see the column doc in the DB schema). Each transition is\n * recorded as an auditable `workflow_events` row (actor, reason, linked tickets).\n *\n * Note: \"has a regression\" is deliberately NOT a management status — it is derivable\n * from {@link WorkflowHealth} (`failing`/`degraded` on a previously-passing flow).\n */\nexport const WORKFLOW_MANAGEMENT_STATUSES = [\n 'active',\n 'paused',\n 'blocked',\n 'under_construction',\n 'deprecated',\n] as const;\n\nexport type WorkflowManagementStatus = (typeof WORKFLOW_MANAGEMENT_STATUSES)[number];\n\n/**\n * Approval-axis states for a workflow definition. This axis answers \"is the test\n * testing the right thing?\" — suite inclusion is derived {@link isWorkflowEligible eligibility}\n * (approved + ever-passed), and reliability lives in {@link WorkflowHealth}.\n *\n * User-settable via the API/CLI: `draft`, `approved`\n * (see {@link USER_SETTABLE_WORKFLOW_STATUSES}).\n *\n * Lifecycle-internal (set by the system, not by users):\n * - `generating` — the workflow is being built from a recording/import\n * - `building` — a builder validation run is in progress\n * - `diagnostic` — builder validation failed; the workflow is parked for inspection\n * - `deleted` — soft-deleted\n *\n */\nexport const WORKFLOW_STATUSES = [\n 'draft',\n 'generating',\n 'building',\n 'deleted',\n 'diagnostic',\n 'approved',\n] as const;\n\nexport type WorkflowDefinitionStatus = (typeof WORKFLOW_STATUSES)[number];\n\n/**\n * Statuses a user (or agent) can set directly via the API or CLI.\n *\n * - `draft` — not yet approved; a human has not signed off on the spec\n * - `approved` — a human verified the test checks the right thing\n *\n * Approval alone does NOT put a flow into suites — a flow becomes suite-runnable\n * ({@link isWorkflowEligible eligible}) once it is approved AND has passed at least\n * once. Reliability (reliable, flaky, degraded) is the {@link WorkflowHealth}\n * dimension, computed automatically — not user-set here.\n */\nexport const USER_SETTABLE_WORKFLOW_STATUSES = [\n 'draft',\n 'approved',\n] as const satisfies readonly WorkflowDefinitionStatus[];\n\nexport type UserSettableWorkflowStatus = (typeof USER_SETTABLE_WORKFLOW_STATUSES)[number];\n\n/**\n * Every value of the `workflow_flow_type` pg enum. `workflow-flow-types.test.ts`\n * in `packages/db` pins the two together.\n *\n * - `standard` — the default; one user journey end to end\n * - `setup` — a prerequisite flow hoisted ahead of the flows that reference it\n * - `seed` — provisions test data and exports it as `$var.{dataKey}`\n * - `teardown` — cleans up after the seed flow that owns it\n *\n * Note this is the FULL enum, not a settable subset — unlike\n * {@link USER_SETTABLE_WORKFLOW_STATUSES}, which deliberately excludes\n * lifecycle-internal statuses. Every flow type is user-settable today. A gate\n * that genuinely needs a narrower set should name its own list and say why,\n * rather than trimming this one.\n *\n * Prefer {@link isWorkflowFlowType} over widening this tuple at a call site.\n */\nexport const WORKFLOW_FLOW_TYPES = ['standard', 'setup', 'seed', 'teardown'] as const;\n\nexport type WorkflowFlowType = (typeof WORKFLOW_FLOW_TYPES)[number];\n\n/** Narrowing membership check for {@link WORKFLOW_FLOW_TYPES}. */\nexport function isWorkflowFlowType(value: unknown): value is (typeof WORKFLOW_FLOW_TYPES)[number] {\n return (\n typeof value === 'string' && (WORKFLOW_FLOW_TYPES as readonly string[]).includes(value)\n );\n}\n\n/**\n * Suite inclusion is a **derived, deterministic gate** called *eligibility* — there\n * is no manual \"promote\" action and no stored eligibility column. A flow is eligible\n * to run in suites, tags, and \"run all eligible\" once it is both:\n *\n * - **approved** (`status === 'approved'`) — a human signed off on the spec, and\n * - **has passed at least once** (`firstPassedAt` is set) — it worked in reality.\n *\n * Eligibility is a one-time gate: `firstPassedAt` is write-once and approval is\n * sticky, so once eligible a flow stays eligible. Two orthogonal exclusions layer\n * on top at run time: **paused** (`pausedAt` set — mirrored from the\n * {@link WorkflowManagementStatus management status}: any non-`active` status stamps\n * it) and **degraded** ({@link WorkflowHealth}) both keep an otherwise-eligible flow\n * out of suite runs.\n *\n * Single-sourced here so the API, repos, and the web app derive eligibility the\n * same way instead of re-implementing the `approved && everPassed` check.\n */\nexport function isWorkflowEligible(wf: {\n status: string;\n firstPassedAt?: Date | string | null;\n}): boolean {\n return wf.status === 'approved' && wf.firstPassedAt != null;\n}\n\n/**\n * Legacy suite-inclusion axis (the *promotion model*), used when an org has NOT been\n * moved to the eligibility model (per-org `workflow.eligibility` flag). Stored on a\n * nullable column: `null` = never promoted, `'promoted'` = runs in suites, `'paused'`\n * = explicitly excluded. Removed once the flag graduates and the eligibility model is\n * the only model.\n */\nexport const WORKFLOW_PROMOTIONS = ['promoted', 'paused'] as const;\n\nexport type WorkflowPromotion = (typeof WORKFLOW_PROMOTIONS)[number];\n\n/** The promotion column's full value space: `null` = never promoted (the default). */\nexport type WorkflowPromotionState = WorkflowPromotion | null;\n\n/** Context provided by the agent when a workflow step fails */\nexport type WorkflowFailureContext = {\n /** What the agent observed happening when the step failed */\n observedBehavior: string;\n /** What was expected to happen */\n expectedBehavior: string;\n /** Steps to reproduce the failure */\n reproSteps: string;\n /** Specific questions the agent poses to help improve instructions (debug mode only) */\n questions?: string[];\n};\n\nexport type WorkspaceRedactionReason = 'secret' | 'binary' | 'large' | 'system';\n\nexport type WorkspaceManifestEntry = {\n path: string;\n type: 'file' | 'dir';\n size?: number;\n mtime?: string;\n language?: string;\n redacted?: boolean;\n redactionReason?: WorkspaceRedactionReason;\n};\n\nexport type WorkspaceManifest = {\n version: string;\n root: string;\n generatedAt: string;\n entries: WorkspaceManifestEntry[];\n};\n\nexport type WorkspaceStreamEvent =\n | {\n type: 'workspace-manifest';\n data: WorkspaceManifest;\n }\n | {\n type: 'workspace-file-created';\n data: WorkspaceManifestEntry;\n }\n | {\n type: 'workspace-file-updated';\n data: {\n path: string;\n size?: number;\n mtime?: string;\n version?: string;\n };\n }\n | {\n type: 'workspace-file-deleted';\n data: { path: string };\n }\n | {\n type: 'workspace-file-content';\n data: {\n path: string;\n content: string;\n encoding?: 'utf-8';\n version?: string;\n };\n };\n\nexport type WorkflowTestRunSummary = {\n id: string;\n workflowId: string;\n status: WorkflowTestStatus;\n snapshotVersion: number;\n /** 1-based retry attempt number; see `workflow_test_runs.attempt`. */\n attempt?: number;\n errorMessage: string | null;\n linkedIssueId: string | null;\n startedAt: string | null;\n finishedAt: string | null;\n createdAt: string;\n};\n\nexport type WorkflowTestSuiteStatus = 'queued' | 'running' | 'completed';\n\nexport type WorkflowTestEvent = {\n type: 'workflow-test';\n suiteId: string;\n emittedAt: string;\n workflowId: string;\n testRunId: string;\n workflowRunId: string | null;\n status: WorkflowTestStatus;\n startedAt: string | null;\n finishedAt: string | null;\n durationMs: number | null;\n errorMessage?: string | null;\n skipReason?: string | null;\n linkedIssueId?: string | null;\n message?: string;\n /** The workflow's reliability dimension at emit time (drives the UI health badge). */\n health?: WorkflowHealth;\n // Wait/segment tracking\n resumeAt?: string | null;\n segmentIndex?: number | null;\n totalSegments?: number | null;\n /** Monotonic sequence number for ordering concurrent events */\n _seq?: number;\n};\n\nexport type WorkflowTestSuiteEvent = {\n type: 'workflow-test-suite';\n suiteId: string;\n emittedAt: string;\n status: WorkflowTestSuiteStatus;\n startedAt: string;\n finishedAt: string | null;\n durationMs: number | null;\n totalWorkflows: number;\n completedWorkflows: number;\n failedWorkflows: number;\n inconclusiveWorkflows: number;\n successfulWorkflows: number;\n skippedWorkflows: number;\n waitingWorkflows: number;\n /** @deprecated Quarantine removed; always 0. Drop with the column in a follow-up. */\n quarantinedWorkflows: number;\n /** Workflows skipped because their health is `degraded` (shown \"degraded — not run\"). */\n degradedWorkflows: number;\n seedTotal: number;\n seedSucceeded: number;\n seedFailed: number;\n teardownTotal: number;\n teardownSucceeded: number;\n teardownFailed: number;\n errorMessage?: string | null;\n message?: string;\n /** Monotonic sequence number for ordering concurrent events */\n _seq?: number;\n};\n\nexport type WorkflowTestStreamEvent = WorkflowTestEvent | WorkflowTestSuiteEvent;\n","/**\n * Sandbox Instance Sizing\n *\n * Closed preset enum of EC2 sizes a template can run on, plus a per-size\n * resource spec consumed by provisioning, compose generation, and prompts.\n *\n * Single source of truth for: launch-template instance type override,\n * docker compose mem_limit/cpus per role, prompt-template variables.\n *\n * @module types/sandbox-instance-size\n */\n\nexport const INSTANCE_SIZES = ['small', 'medium', 'large', 'xlarge', '2xlarge'] as const;\nexport type InstanceSize = (typeof INSTANCE_SIZES)[number];\n\nexport const DEFAULT_INSTANCE_SIZE: InstanceSize = 'medium';\n\nexport interface InstanceSizeSpec {\n /** EC2 instance type for `RunInstancesCommand` override. */\n ec2InstanceType: string;\n vcpu: number;\n memoryMiB: number;\n /**\n * Per-role memory caps (MiB) consumed by `resourceLimitsFor()`. Must cover\n * every key in compose-helpers.ts STATIC_LIMITS.\n *\n * Sum × per-role multiplicity should fit within `memoryMiB × 1.5` to keep\n * swap pressure bounded. See INSTANCE_SIZE_SUM_LIMIT_RATIO.\n */\n agentCapMiB: number;\n appSoftCapMiB: number;\n proxyCapMiB: number;\n emulationCapMiB: number;\n storageCapMiB: number;\n /**\n * Per-size hard CPU quota (cgroup `cpus`) for the user app tier. Other\n * roles (agent, proxy, emulation, storage) keep fixed CPU caps from\n * STATIC_LIMITS — their workloads don't scale with host vCPU. App is the\n * only role that sees a real benefit from a larger instance, so it's the\n * only one keyed here.\n *\n * Formula: max('1.0', vcpu - 1.5) — reserve 1.5 vCPU for the agent.\n * Stored as a string so docker compose receives `'1.0'` (not `'1'`),\n * matching the format of STATIC_LIMITS.cpus values.\n */\n appCpus: string;\n}\n\n/** Cap on (sum of role caps) ÷ memoryMiB — anything above this swaps too hard. */\nexport const INSTANCE_SIZE_SUM_LIMIT_RATIO = 1.5;\n\nexport const INSTANCE_SIZE_SPEC: Record<InstanceSize, InstanceSizeSpec> = {\n small: {\n ec2InstanceType: 't3.small',\n vcpu: 2,\n memoryMiB: 2048,\n // 2 GiB instance — agent capped at min(2048, 0.4 × 2048) = 819\n agentCapMiB: 819,\n appSoftCapMiB: 1024,\n proxyCapMiB: 256,\n emulationCapMiB: 128,\n storageCapMiB: 256,\n appCpus: '1.0',\n },\n medium: {\n ec2InstanceType: 't3.medium',\n vcpu: 2,\n memoryMiB: 4096,\n agentCapMiB: 1536,\n appSoftCapMiB: 1536,\n proxyCapMiB: 256,\n emulationCapMiB: 128,\n storageCapMiB: 256,\n appCpus: '1.0',\n },\n large: {\n ec2InstanceType: 't3.large',\n vcpu: 2,\n memoryMiB: 8192,\n agentCapMiB: 2048,\n appSoftCapMiB: 5120,\n proxyCapMiB: 256,\n emulationCapMiB: 128,\n storageCapMiB: 512,\n // t3.large is memory-only — same 2 vCPU as medium.\n appCpus: '1.0',\n },\n xlarge: {\n ec2InstanceType: 't3.xlarge',\n vcpu: 4,\n memoryMiB: 16384,\n agentCapMiB: 2048,\n appSoftCapMiB: 12288,\n proxyCapMiB: 256,\n emulationCapMiB: 256,\n storageCapMiB: 512,\n appCpus: '2.5',\n },\n '2xlarge': {\n ec2InstanceType: 't3.2xlarge',\n vcpu: 8,\n memoryMiB: 32768,\n agentCapMiB: 2048,\n appSoftCapMiB: 28672,\n proxyCapMiB: 512,\n emulationCapMiB: 512,\n storageCapMiB: 1024,\n appCpus: '6.5',\n },\n};\n\nconst INSTANCE_SIZE_SET = new Set<string>(INSTANCE_SIZES);\n\n/**\n * Narrowing helper for the repo boundary. Returns null on unknown input so\n * callers can surface a typed user error without `as never` / `as any`.\n */\nexport function parseInstanceSize(s: string | null | undefined): InstanceSize | null {\n return s && INSTANCE_SIZE_SET.has(s) ? (s as InstanceSize) : null;\n}\n\nconst RECOMMENDATION_MAP: Record<string, InstanceSize> = {\n // Exact preset names\n small: 'small',\n medium: 'medium',\n large: 'large',\n xlarge: 'xlarge',\n '2xlarge': '2xlarge',\n // Common AWS instance types the authoring agent may emit\n 't3.nano': 'small',\n 't3.micro': 'small',\n 't3.small': 'small',\n 't3.medium': 'medium',\n 't3.large': 'large',\n 't3.xlarge': 'xlarge',\n 't3.2xlarge': '2xlarge',\n 't2.small': 'small',\n 't2.medium': 'medium',\n 't2.large': 'large',\n 't2.xlarge': 'xlarge',\n 't2.2xlarge': '2xlarge',\n};\n\n/**\n * Maps a free-form `recommendedInstanceType` string (often produced by the\n * authoring agent before this enum existed) to the closest preset.\n *\n * Strategy: exact match against known names first, then fall back to nearest\n * memoryMiB. Ties broken toward cheaper (lower `memoryMiB`).\n */\nexport function inferInstanceSizeFromRecommendation(\n s: string | null | undefined\n): InstanceSize | null {\n if (!s) return null;\n const trimmed = s.trim().toLowerCase();\n if (!trimmed) return null;\n const exact = RECOMMENDATION_MAP[trimmed];\n if (exact) return exact;\n\n // Last-resort heuristic: parse a memory hint like \"4 GiB\" / \"8GB\" / \"2g\".\n const match = trimmed.match(/(\\d+(?:\\.\\d+)?)\\s*(g|gi|gb|gib|m|mi|mb|mib)\\b/);\n if (!match) return null;\n const value = Number.parseFloat(match[1]!);\n if (!Number.isFinite(value)) return null;\n const memMiB = match[2]!.startsWith('g') ? value * 1024 : value;\n\n let best: InstanceSize | null = null;\n let bestDelta = Number.POSITIVE_INFINITY;\n for (const size of INSTANCE_SIZES) {\n const delta = Math.abs(INSTANCE_SIZE_SPEC[size].memoryMiB - memMiB);\n if (delta < bestDelta) {\n best = size;\n bestDelta = delta;\n }\n }\n return best;\n}\n","import type { ReviewFlavor } from './review-flavors.js';\nimport type { BrowserEngine } from './browser-engine.js';\nimport type { BrowserViewportPreset } from './browser-viewport.js';\n\nexport const ADHOC_TEST_JOB_TYPE = 'adhoc.test' as const;\n/**\n * The completion waiter a chat-started (Slack/Teams) ad-hoc test parks beside\n * the generic `adhoc.test-complete` one. Lives here so `cancelAdhocTest` can\n * name it without importing the chat job module (and its agent, resolver and\n * job-registration side effects).\n */\nexport const CHAT_ADHOC_TEST_COMPLETE_JOB_TYPE = 'chat.adhoc-test-complete' as const;\nexport const ADHOC_TEST_NAMING_JOB_TYPE = 'adhoc.test.naming' as const;\nexport const ADHOC_TEST_INSTRUCTIONS_MAX_LENGTH = 5000;\n\nexport type AdhocTestPayload = {\n adhocTestId: string;\n propertyId: string;\n credentialId?: string | null;\n /** Live property environment selected at run start. Null falls back to credential/default env. */\n environmentId?: string | null;\n instructions: string;\n startUrl?: string | null;\n /**\n * Review flavor. Absent/`functional` runs the existing pass/fail QA executor;\n * `ux` (and future graded-critique flavors) dispatch to the review executor.\n */\n flavor?: ReviewFlavor;\n /** Concrete viewport preset resolved when the run was created. */\n viewportSize: BrowserViewportPreset;\n /** Concrete Playwright engine resolved when the run was created. */\n browserEngine: BrowserEngine;\n /**\n * UX flavor: mint a throwaway email+password identity (with a live inbox) so\n * the agent can sign up / onboard itself. The run still starts logged out.\n */\n provisionTempIdentity?: boolean;\n explorationAssignmentId?: string;\n};\n\n/**\n * Payload for the ad-hoc test naming job.\n * Generates an AI-powered label for an exploratory test (or the legacy run\n * title when there is no parent).\n */\nexport type AdhocTestNamingPayload = {\n adhocTestId: string;\n propertyName: string;\n instructions: string;\n /**\n * Parent exploratory test to name. When set, the generated label is written\n * to `exploratoryTests.name`. When null (PR/ticket runs), the label falls\n * back to the legacy `adhocTests.title`.\n */\n exploratoryTestId?: string | null;\n};\n","/**\n * JSON contracts for checkpoint review, shared by the API, web and CLI.\n *\n * @module visual-api\n * @see docs/features/test-suite-execution.md\n */\nimport type { VisualMask, VisualSeverity } from './visual-checkpoint.js';\nimport type { VisualExplanation, VisualRect, VisualTextChanges } from './visual-review.js';\n\nexport type VisualOutcome =\n | 'new'\n | 'unchanged'\n | 'unstable'\n | 'cant_compare'\n | 'healed'\n | 'resolved'\n | 'changed';\nexport const VISUAL_CHECKPOINT_STATUSES = ['changed', 'healed', 'new', 'unstable', 'all'] as const;\nexport type VisualCheckpointStatus = (typeof VISUAL_CHECKPOINT_STATUSES)[number];\nexport type VisualOutcomeCounts = {\n changed: number;\n healed: number;\n new: number;\n unstable: number;\n cantCompare: number;\n};\n\n/** Skinny list row. Dates are ISO strings; absent values are always null. */\nexport type VisualCheckpointListItem = {\n id: string;\n source: 'native' | 'playwright';\n label: string;\n environmentName: string;\n suiteId: string | null;\n suiteName: string | null;\n workflowId: string | null;\n workflowName: string | null;\n specFile: string | null;\n testTitle: string | null;\n executionId: string | null;\n externalTestRunId: string | null;\n outcome: VisualOutcome | null;\n outcomeReason: string | null;\n createdAt: string;\n imageUrl: string | null;\n};\n\nexport type VisualCheckpointDetail = VisualCheckpointListItem & {\n width: number | null;\n height: number | null;\n severity: VisualSeverity | null;\n explanation: VisualExplanation | null;\n healedMask: VisualMask | null;\n maskMisses: string[];\n regions: VisualRect[];\n textChanges: VisualTextChanges | null;\n changedPercent: number | null;\n regressionMarkedAt: string | null;\n baselineUrl: string | null;\n diffUrl: string | null;\n};\n\nexport type VisualCheckpointListResponse = {\n ok: true;\n data: { items: VisualCheckpointListItem[]; nextCursor: string | null };\n};\nexport type VisualCheckpointDetailResponse = { ok: true; data: VisualCheckpointDetail };\nexport type VisualSummaryResponse = { ok: true; data: VisualOutcomeCounts };\n","/**\n * Browser viewport presets for agent browser sessions.\n *\n * Organizations pick a default preset (Settings → Agent Browser) and individual\n * Workflows may save a preference, and individual runs may override it\n * (Flow Designer → Run with config). The stored value is\n * the preset *key*; `resolveBrowserViewport` maps it to concrete dimensions\n * before reaching `PlaywrightClient.connect({ viewport })`.\n *\n * The `laptop` default (1440×900) matches `DEFAULT_VIEWPORT` in\n * `@chatsdet/browser-core`. It replaced `desktop` (1280×800, the resolution\n * usually quoted for computer-use models) on 2026-09-14: apps that dock a side\n * panel — our own Flow Designer with the Assistant drawer open — left agents\n * ~1000 px of working canvas at 1280 and they misread it as clipped.\n *\n * @module browser-viewport\n */\n\n/** Device family for a preset — drives the icon shown in the visual picker. */\nexport type BrowserViewportDevice = 'desktop' | 'laptop' | 'tablet' | 'mobile';\n\nexport interface BrowserViewportPresetSpec {\n /** Human-friendly label shown in the UI. */\n label: string;\n width: number;\n height: number;\n device: BrowserViewportDevice;\n}\n\nexport const BROWSER_VIEWPORT_PRESETS = {\n desktop: { label: 'Desktop', width: 1280, height: 800, device: 'desktop' },\n desktop_large: { label: 'Large desktop', width: 1920, height: 1080, device: 'desktop' },\n laptop: { label: 'Laptop', width: 1440, height: 900, device: 'laptop' },\n tablet: { label: 'Tablet', width: 768, height: 1024, device: 'tablet' },\n mobile: { label: 'Mobile', width: 390, height: 844, device: 'mobile' },\n} as const satisfies Record<string, BrowserViewportPresetSpec>;\n\nexport type BrowserViewportPreset = keyof typeof BROWSER_VIEWPORT_PRESETS;\n\nexport const DEFAULT_BROWSER_VIEWPORT_PRESET: BrowserViewportPreset = 'laptop';\n\nexport const BROWSER_VIEWPORT_PRESET_KEYS = Object.keys(\n BROWSER_VIEWPORT_PRESETS\n) as BrowserViewportPreset[];\n\n/** Type guard: is `value` a known preset key? */\nexport function isBrowserViewportPreset(value: unknown): value is BrowserViewportPreset {\n return typeof value === 'string' && Object.hasOwn(BROWSER_VIEWPORT_PRESETS, value);\n}\n\n/** Map a canonical preset onto the deprecated desktop/mobile API contract. */\nexport function toLegacyDeviceViewport(preset: BrowserViewportPreset): 'desktop' | 'mobile' {\n return preset === 'mobile' ? 'mobile' : 'desktop';\n}\n\n/**\n * Map a stored preset key (possibly null/unknown) to concrete viewport\n * dimensions, falling back to the laptop default.\n */\nexport function resolveBrowserViewport(preset: string | null | undefined): {\n width: number;\n height: number;\n} {\n const spec = isBrowserViewportPreset(preset)\n ? BROWSER_VIEWPORT_PRESETS[preset]\n : BROWSER_VIEWPORT_PRESETS[DEFAULT_BROWSER_VIEWPORT_PRESET];\n return { width: spec.width, height: spec.height };\n}\n","/**\n * Shared proof domain types and promote-on parsing.\n *\n * @module proof\n * @see docs/project/conventions.md\n */\n\n/**\n * The event that makes a proven workflow eligible for promotion. `never`: the test proves\n * the pull request and is archived at merge instead of joining any suite (proofs-v2 M8).\n */\nexport type PromoteOn = 'merge' | 'never' | `deploy:${string}`;\n\n/** Parse a stored promote-on value, returning null when it is not supported. */\nexport function parsePromoteOn(value: string): PromoteOn | null {\n if (value === 'merge' || value === 'never') return value;\n if (!value.startsWith('deploy:')) return null;\n const stage = value.slice('deploy:'.length);\n if (!stage) return null;\n return value as PromoteOn;\n}\n\n/** The current evaluation state of a criterion's evidence. */\nexport type CriterionStatus =\n | { kind: 'proved' }\n | { kind: 'stale'; provedSha: string }\n | { kind: 'failed'; problem: string };\n\n/** The durable evidence kinds accepted by proofs. `manual` is the agent's own account (D11). */\nexport const PROOF_EVIDENCE_KINDS: readonly ['e2e', 'perf', 'eval', 'manual'] = [\n 'e2e',\n 'perf',\n 'eval',\n 'manual',\n];\nexport type ProofEvidenceKind = (typeof PROOF_EVIDENCE_KINDS)[number];\n\n/**\n * A criterion shown only by the agent's own account (M23): it has evidence and every item is manual.\n * The one rule behind the check line's 📝, the PR block's `(manual)` and the page's Manual badge.\n */\nexport const isManualOnly = (kinds: readonly ProofEvidenceKind[]): boolean =>\n kinds.length > 0 && kinds.every((kind) => kind === 'manual');\n\n/** A visual checkpoint comparison shown for a UI test proof. */\nexport type ProofCheckpoint = {\n label: string;\n nodeId: string;\n status: 'changed' | 'unchanged' | 'incompatible' | 'unavailable' | 'no_base' | 'pending';\n changedPercent: number | null;\n width: number | null;\n height: number | null;\n imageUrl: string | null;\n baselineUrl: string | null;\n diffUrl: string | null;\n};\n\n/** Evidence detail shared by API responses, checks, and the proof page. */\nexport type EvidenceView =\n | {\n kind: 'e2e';\n workflowId: string;\n runId: string;\n nodeId: string | null;\n asserted: string;\n /** An existing approved test cited unchanged: proves it still works, never promoted. */\n existing: boolean;\n }\n | { kind: 'perf'; runId: string; asserted: string }\n | { kind: 'eval'; runId: string; asserted: string }\n | { kind: 'manual'; notes: string; artifacts: string[] };\n\n/** The evaluated result used by both GitHub checks and the Actions job. */\nexport type ProofVerdict = {\n conclusion: 'success' | 'failure' | 'neutral';\n title: string;\n criteria: {\n position: number;\n text: string;\n detail: string;\n status: CriterionStatus;\n evidence: EvidenceView[];\n }[];\n summaryMarkdown: string;\n /** The proof page for criteria, flows for a criteria block without a record, or null for `none:`. */\n pageUrl: string | null;\n};\n\nexport type ProofRefusal = 'not-found' | 'approved-not-fork' | 'not-a-draft' | 'origin-unavailable';\n\n/** A test as the server classified it: usable as proof (a new draft, or a fork of an approved test), or refused with why. */\nexport type ClassifiedProof =\n | { id: string }\n | { id: string; refusal: ProofRefusal; message: string };\n","/**\n * Parser and formatter for the v2 proof block in a pull request body.\n *\n * The block is a snapshot: it states the stoplight as of one commit and links to the proof\n * page, which reads the live record. The parser reads only the markers and the `none:` form,\n * so the layout of the criteria inside may change without breaking `proofs check`.\n *\n * @module proof-body\n * @see docs/project/conventions.md\n */\n\nconst PROOF_BLOCK_OPEN = '<!-- cofactor:proof -->';\nconst PROOF_BLOCK_CLOSE = '<!-- /cofactor:proof -->';\nconst NONE = /^none:\\s*/i;\n\n/** What the v2 proof block says; criteria themselves live in the proof record. */\nexport type ProofBody = { kind: 'none'; reason: string } | { kind: 'criteria' };\n\n/** Read the first complete v2 proof block, or null when the body has none. */\nexport function parseProofBody(body: string | null | undefined): ProofBody | null {\n if (!body) return null;\n const open = body.indexOf(PROOF_BLOCK_OPEN);\n if (open < 0) return null;\n const start = open + PROOF_BLOCK_OPEN.length;\n const close = body.indexOf(PROOF_BLOCK_CLOSE, start);\n if (close < 0) return null;\n\n const content = body.slice(start, close).trim();\n if (NONE.test(content)) return { kind: 'none', reason: content.replace(NONE, '').trim() };\n return { kind: 'criteria' };\n}\n\n/** The stoplight of one criterion: passed, failed, stale (proved at an older head) or manual (Cofactor could not prove it). */\nexport type ProofLight = 'passed' | 'failed' | 'stale' | 'manual';\n\n/** One piece of evidence under a criterion: what was tested, how, what it asserted, and where to look. */\nexport type ProofBodyItem = {\n /** The test's name (its user goal) or the eval's name, or null for evidence with no test (perf, manual). */\n tested: string | null;\n /** How it was shown, in words: \"E2E run in a sandbox\", \"Performance run (prod)\", \"Manual check\". */\n how: string;\n asserted: string;\n /** `url` is server-built from the org origin and encoded ids; never put agent text in it. */\n link: { label: string; url: string };\n};\n\nexport type ProofBodyCriterion = {\n position: number;\n text: string;\n light: ProofLight;\n /** The failure problem, or the sha7 the stale evidence was proved at; null otherwise. */\n note: string | null;\n items: ProofBodyItem[];\n};\n\nconst GLYPH: Record<ProofLight, string> = { passed: '🟢', failed: '🔴', stale: '🟡', manual: '⚪' };\nconst WORD: Record<ProofLight, string> = {\n passed: 'Passed',\n failed: 'Failed',\n stale: 'Stale',\n manual: 'Manual',\n};\nconst ASSERTED_MAX = 200;\n\nconst ESCAPES: Record<string, string> = {\n '&': '&amp;',\n '<': '&lt;',\n '@': '@&#8203;',\n '#': '#&#8203;',\n '\\\\': '\\\\\\\\',\n '`': '\\\\`',\n '*': '\\\\*',\n '[': '\\\\[',\n ']': '\\\\]',\n};\n\n/**\n * One line of agent text: bounded, whitespace collapsed, and escaped so it cannot add\n * a live link or image (bare URLs are broken with a zero-width space), notify a user or close an issue (`@`, `#`), or write a `<!--` comment marker\n * (so the closing marker never appears inside the block).\n */\nconst line = (text: string, max = 300): string => {\n const flat = Array.from(text.replace(/\\s+/g, ' ').trim());\n const bounded = flat.length > max ? `${flat.slice(0, max - 1).join('')}…` : flat.join('');\n return bounded\n .replace(/[\\u202a-\\u202e\\u2066-\\u2069]/g, '')\n .replace(/[&<@#\\\\`*[\\]]/g, (char) => ESCAPES[char] ?? char)\n .replace(/:\\/\\//g, ':&#8203;//')\n .replace(/www\\./gi, (match) => `${match.slice(0, 3)}&#8203;.`);\n};\n\n/**\n * Format the criteria as a v2 PR-body block: a header with the proof-page link and the counts,\n * then per criterion a stoplight line and, per evidence item, what was tested, how, and what it asserted.\n */\nexport function formatProofBody(input: {\n criteria: ProofBodyCriterion[];\n proofUrl: string;\n headSha: string;\n}): string {\n const counts = (['passed', 'stale', 'failed', 'manual'] as const)\n .map((light) => ({ light, n: input.criteria.filter((c) => c.light === light).length }))\n .filter(({ n }) => n > 0)\n .map(({ light, n }) => `${GLYPH[light]} ${n} ${WORD[light].toLowerCase()}`);\n const header = [\n `**Proof of acceptance criteria** · [View all proofs in Cofactor](${input.proofUrl})`,\n [...counts, `as of \\`${input.headSha.slice(0, 7)}\\``].join(' · '),\n ].join('\\n\\n');\n\n const sections = input.criteria.length === 0 ? ['🔴 **No criteria attached**'] : input.criteria.map((criterion) => {\n const note = criterion.note ? ` (${line(criterion.note)})` : '';\n const title = `${GLYPH[criterion.light]} **${criterion.position}. ${line(criterion.text)}** · ${WORD[criterion.light]}${note}`;\n const items = criterion.items.flatMap((item) => [\n `- **Tested:** ${item.tested ? `${line(item.tested)} · ` : ''}${line(item.how)} · [${line(item.link.label)}](${item.link.url})`,\n ` - ${line(item.asserted, ASSERTED_MAX)}`,\n ]);\n return [title, ...items].join('\\n');\n });\n\n const legend =\n '_🟢 passed · 🟡 passed at an earlier commit · 🔴 failed · ⚪ shown manually. This block is a snapshot; the live verdict is the `cofactor / proofs` check._';\n return [PROOF_BLOCK_OPEN, header, ...sections, legend, PROOF_BLOCK_CLOSE].join('\\n\\n');\n}\n\n/** The v2 block for a pull request with no observable criterion; the reason is the whole block. */\nexport function formatProofNone(reason: string): string {\n return [PROOF_BLOCK_OPEN, `none: ${reason}`, PROOF_BLOCK_CLOSE].join('\\n');\n}\n","/**\n * What agent feedback (`cofactor feedback`, the `cofactor_feedback_submit` MCP\n * tool, `POST /feedback`) can be about, and which client sent it. Shared by the\n * `agent_feedback` table enums, the API, the CLI and the MCP tool.\n */\nexport const AGENT_FEEDBACK_TOPICS = ['skill', 'instructions', 'tool', 'confusing', 'bug', 'other'] as const;\nexport const AGENT_FEEDBACK_SOURCES = ['cli', 'mcp'] as const;\n\nexport type AgentFeedbackTopic = (typeof AGENT_FEEDBACK_TOPICS)[number];\nexport type AgentFeedbackSource = (typeof AGENT_FEEDBACK_SOURCES)[number];\n","import { z } from 'zod';\nimport type { AssertionSeverity } from './workflow-node-schemas.js';\nimport type { WorkflowHealth } from './workflows.js';\nimport {\n cacheCodeDurabilityAttestationSchema,\n type AssertionSpec,\n type CacheCodeDurabilityAttestation,\n type DurableTabRef,\n} from './workflow-cache.js';\n\nexport * from './sandbox-egress.js';\nexport * from './sandbox-preview.js';\nexport * from './provision-timings.js';\nexport * from './exploration-map.js';\nexport * from './exploration.js';\n\nexport type ActionInputType =\n | 'text'\n | 'email'\n | 'password'\n | 'number'\n | 'textarea'\n | 'select'\n | 'checkbox'\n | 'multiselect'\n | 'file';\n\nexport interface ActionInputOption {\n value: string;\n label: string;\n}\n\nexport interface ActionDataRequirement {\n /**\n * Unique field identifier used to store the provided input value.\n */\n name: string;\n /**\n * Human-friendly label to display in the UI.\n */\n label: string;\n /**\n * Expected input type. Defaults to 'text' if omitted.\n */\n type?: ActionInputType;\n /**\n * Whether the field is required for the action to succeed.\n */\n required?: boolean;\n /**\n * Helpful description shown to the operator.\n */\n description?: string;\n /**\n * Placeholder text for the input.\n */\n placeholder?: string;\n /**\n * Suggested example value provided by the agent.\n */\n exampleValue?: string;\n /**\n * Optional select options for dropdown-style inputs.\n */\n options?: ActionInputOption[];\n}\n\nexport type ActionInputValues = Record<string, string>;\n\n// ── Workflow-level input definitions ──\n\n/** Value input — a simple typed field (text, number, email, select, etc.) */\nexport interface WorkflowValueInputDefinition {\n kind: 'value';\n /** Variable name (snake_case, e.g. \"user_email\") */\n name: string;\n /** Human-friendly label */\n label: string;\n /** Expected input type. Defaults to 'text' if omitted. */\n type?: ActionInputType;\n required?: boolean;\n description?: string;\n placeholder?: string;\n /** Pre-filled default value */\n defaultValue?: string;\n options?: ActionInputOption[];\n}\n\n/** Credential input — represents a login identity (future: full credential provisioning) */\nexport interface WorkflowCredentialInputDefinition {\n kind: 'credential';\n /** Namespace for credential vars (e.g. \"admin_user\" → $var.admin_user.username) */\n name: string;\n /** Human-friendly label */\n label: string;\n required?: boolean;\n description?: string;\n /** Which credential fields this input needs */\n credentialFields: {\n username?: boolean;\n password?: boolean;\n loginUrl?: boolean;\n mfaSecret?: boolean;\n };\n /** Default credential values (for scenarios) */\n defaults?: {\n username?: string;\n password?: string;\n loginUrl?: string;\n };\n /** Default logical credential template used when a scenario does not override it */\n defaultCredentialTemplateId?: string;\n}\n\nexport type WorkflowInputDefinition =\n | WorkflowValueInputDefinition\n | WorkflowCredentialInputDefinition;\n\n// ── Workflow scenarios (named input-value sets) ──\n\n/** Value-input overrides keyed by WorkflowValueInputDefinition.name */\nexport type WorkflowScenarioValueInputs = Record<string, string>;\n\n/** Credential-input overrides keyed by WorkflowCredentialInputDefinition.name */\nexport type WorkflowScenarioCredentialInputs = Record<\n string,\n {\n /** Logical credential template to resolve against the run environment */\n credentialTemplateId?: string;\n /** Legacy inline fields; retained for backward-compatible imports/storage */\n username?: string;\n password?: string;\n loginUrl?: string;\n mfaSecret?: string;\n }\n>;\n\n/**\n * A named set of workflow input values — parameterized test case for a workflow.\n * Scenarios reference existing `inputsJson` entries by name; orphan keys are\n * surfaced as warnings in the UI but are tolerated in storage.\n */\nexport interface WorkflowScenario {\n /** Stable uuid (survives renames) */\n id: string;\n /** Human-friendly name, e.g. \"Happy path\" */\n name: string;\n description?: string;\n /** At most one scenario per workflow may be default; UI enforces this. */\n isDefault?: boolean;\n /**\n * When true (or undefined — defaults to true for backwards compatibility),\n * this scenario is included when the workflow runs as part of a test suite,\n * causing the suite to fan the workflow out to one run per included scenario.\n */\n includeInSuite?: boolean;\n values?: WorkflowScenarioValueInputs;\n credentials?: WorkflowScenarioCredentialInputs;\n}\n\n// ── Workflow environment eligibility ──\n\nexport type WorkflowEnvironmentType = 'permanent' | 'ephemeral' | 'local' | 'sandbox';\n\n/**\n * Client-facing readiness of a `type='sandbox'` environment's backing\n * flow-authoring instance. Shared by the API status/ensure/heartbeat routes\n * and the web `useSandboxEnvironment` hook so the union can't drift.\n */\nexport type SandboxEnvironmentStatus = 'asleep' | 'warming' | 'ready' | 'stopping' | 'failed';\n\n/** Status-route payload for a sandbox environment's backing instance. */\nexport interface SandboxEnvironmentState {\n status: SandboxEnvironmentStatus;\n templateId: string;\n instanceId: string | null;\n /** Raw `environment_instances.status` when an instance exists. */\n instanceStatus: string | null;\n url: string | null;\n /** Instance creation time — anchors the provisioning elapsed timer. */\n createdAt: string | null;\n expiresAt: string | null;\n /** Provisioning failure detail when `status === 'failed'`. */\n failureReason: string | null;\n}\n\n/**\n * Explicit environment eligibility constraints for a workflow.\n * Null/absent means \"any\" — workflow can run against any environment.\n * `only`: pin to specific environment IDs (e.g. prod smoke).\n * `envTypes`: allow only listed environment types (e.g. permanent only).\n */\nexport type WorkflowEnvironmentConstraints =\n | { mode: 'only'; environmentIds: string[] }\n | { mode: 'envTypes'; types: WorkflowEnvironmentType[] };\n\n/**\n * Per-environment pass history for a workflow — \"does this flow pass on this\n * env/template?\". Keyed on the STABLE target environment (`workflow_runs.environment_id`),\n * so sandbox runs attribute to the persistent sandbox env, not the throwaway\n * ephemeral instance. Computed read-time over the recent terminal-run window.\n */\nexport interface PassByEnvironment {\n environmentId: string;\n environmentName: string;\n environmentType: WorkflowEnvironmentType;\n /**\n * Whether any terminal pass (success/flaky_success) appears in the recent\n * fetched window for this env — NOT an all-time signal. This is a read-time\n * aggregate over the same bounded window the flow-health badge uses, so a flow\n * that passed long ago but only has recent failures reads as `everPassed:false`\n * here. (The authoritative all-time \"has ever passed\" lives on the workflow's\n * `firstPassedAt`, which is global, not per-env.)\n */\n everPassed: boolean;\n /** Passes within the recent window. */\n passed: number;\n /** Terminal runs within the recent window. */\n total: number;\n /** classifyHealth verdict for this env's window (e.g. passing/flaky/failing). */\n health: WorkflowHealth;\n}\n\n/**\n * Metadata about input values, including whether synthetic data was used.\n * @deprecated Synthetic tracking has been removed. Form field reporting is\n * now unified via {@link ActionFormField} in debug mode only.\n */\nexport interface ActionInputMetadata {\n /**\n * True if any input values were synthetically generated by the agent\n * rather than provided by the user.\n */\n usedSyntheticData?: boolean;\n /**\n * List of field names that were synthetically generated.\n */\n syntheticFields?: string[];\n}\n\n/**\n * Unified form field representation for debug-mode agent reporting.\n * Combines field metadata (schema) with the value used, so the agent\n * reports everything in a single array instead of separate\n * `dataRequirements` + `inputData` fields.\n */\nexport interface ActionFormField {\n /** Unique field identifier (snake_case, e.g. task_title). */\n name: string;\n /** Human-friendly label from the UI. */\n label: string;\n /** Input type. Defaults to 'text' if omitted. */\n type?: ActionInputType;\n /** Whether the field is required. */\n required?: boolean;\n /** The value entered/used for this field. */\n value: string;\n /** Select/multiselect options, if applicable. */\n options?: ActionInputOption[];\n}\n\nconst PLAYBOOK_STEP_AGENT_ACTIONS = [\n 'click',\n 'fill',\n 'fill_form',\n 'select',\n 'navigate',\n 'wait',\n 'scroll',\n 'hover',\n 'upload',\n 'verify',\n 'press_key',\n 'drag',\n 'resize',\n 'capture',\n 'extract_table',\n 'python_execute',\n 'verify_download',\n 'verify_download_content',\n 'screenshot',\n 'agent_loop',\n] as const;\n\nexport const PLAYBOOK_STEP_ACTIONS = [...PLAYBOOK_STEP_AGENT_ACTIONS, 'call'] as const;\n\nexport type PlaybookStepAction = (typeof PLAYBOOK_STEP_ACTIONS)[number];\nexport type LegacyPlaybookStepAction =\n | PlaybookStepAction\n | 'other'\n | 'handle_native_dialog'\n | 'toggle'\n | 'focus';\n\nfunction isPositiveIntegerString(value: string | undefined): value is string {\n if (!value || !/^\\d+$/.test(value)) return false;\n return Number(value) > 0;\n}\n\n/**\n * Zod schema for the agent-settable fields of a playbook step. This is the\n * single source of truth for what the LLM can author via the\n * `updateWorkflowStep` tool — `apps/api` imports this directly instead of\n * keeping a parallel hard-coded schema (which used to drift, e.g. duplicate\n * action enum and missing `expectedDialogs`).\n *\n * Internal fields the *runtime* attaches (actionRefsSource, screenshotS3Key)\n * are NOT in this schema — they belong on the wider {@link PlaybookStep}\n * type below. Keeping them out keeps the agent's input surface tight and\n * prevents prompt injection of internal-only fields.\n *\n * Field descriptions are LLM-tuned: they are read directly into the tool's\n * function-calling schema. Don't shorten them without considering the agent.\n */\nfunction buildActionDescription(includeRecipeFields: boolean): string {\n return (\n 'The type of browser interaction. PREFER DETERMINISTIC VERBS even when verbose — three deterministic steps beat one agentic step, because deterministic steps replay via Playwright with no agent dispatch. ' +\n 'Each replayable playbook step is one atomic intent executed in one exposed browser tool call and corresponds to zero or one recorded actionRef. Draw boundaries at stable page states: fuse actions coupled through ephemeral UI state into that call; split unrelated intents and every stable intermediate into separate steps. If you cannot accomplish the intent with the browser tools you have, do not add a playbook step for it; report the limitation outside playbookSteps. Do not summarize several recorded tool calls into one \"other\" step; report the same 1:1 browser primitives you actually used. One fill_form call is one atomic intent even when it fills multiple fields. ' +\n 'Use \"verify\" for assertion checks — both user-declared inline assertions and agent-authored guardrails/completion checks that keep execution on the rails. Set assertionId to the inline assertion UUID only when accounting for that declared assertion; otherwise use \"completion\". Extra completion checks are not user assertion results. ' +\n 'Use \"verify\" for inspection language such as \"inspect whether\", \"confirm\", \"observe\", or \"check\" when the result is visible in the DOM. Use \"capture\" instead only when the observed value is needed by later steps. ' +\n 'Use \"fill\" for single-field inputs. Use \"fill_form\" when browser_fill_form was called to fill multiple fields at once — one fill_form step per call. ' +\n 'Use \"click\" for checkboxes, switches, toggle buttons, and click-to-focus interactions. There is no separate \"toggle\" or \"focus\" action. ' +\n 'Use \"upload\" for file upload actions, including generated CSV/file, attach file, choose file, import file, and document/image attachment intents (browser_action action=\"upload\"). Set params.path to the staged attachment handle, generated file variable, or filename reference used by the upload tool (e.g. {path: \"$var.csv_file\"}), and include actionRefs pointing to the recorded file_upload action. Do NOT use \"other\" for file attachment/upload/import intents. ' +\n 'Use \"scroll\" only when scrolling is the user-visible action being tested; routine browser_scroll calls for visibility, inspection, or video framing are observation steps and should usually be omitted from playbookSteps. ' +\n 'Use \"press_key\" for keyboard actions (Enter, Escape, Tab, arrows). ' +\n 'Use \"press_key\" with params.key=\"Escape\" to dismiss a popover, dropdown, panel, or modal when Escape was the recorded action; use \"click\" if the recorded action clicked a close button or outside target. There is no separate \"dismiss\" action. ' +\n 'Use \"drag\" for drag-and-drop interactions. ' +\n 'Use \"resize\" when browser_resize was used to resize the viewport. Set params.width and params.height to positive integer pixel values and include the actionRef pointing to the recorded resize action. ' +\n 'Use \"capture\" to extract app-generated values (IDs, codes, URLs) for later steps via $var.name, but only from a visible DOM target or the current URL. Do not capture from performance/network timing, storage, hidden DOM, or app-global state. ' +\n 'Use \"screenshot\" only when browser_screenshot itself is the requested evidence capture. Set params.label when a label was used, optionally params.fullPage=\"true\". Do not use screenshot for routine visual checks after actions; use verify when a browser_assert proves visible state. ' +\n 'Use \"extract_table\" when browser_extract_table was used to extract tabular data from the page. ' +\n 'Use \"python_execute\" when python_execute was used with cache_label to run a data validation script. ' +\n 'Use \"verify_download\" when browser_download(action=\"list\") / browser_list_downloads proves a file was downloaded. params.pattern is an optional filename glob (supports *, ?); omit it when any newly downloaded file is acceptable. ' +\n 'Use \"verify_download_content\" when browser_download(action=\"read\") / browser_read_download content proves CSV headers, PDF text, exported rows, or similar downloaded-file content. Set params.contains or params.pattern to the expected content predicate. ' +\n 'Use \"agent_loop\" ONLY when the action set is genuinely unknown until runtime — variable count of items (e.g. \"toggle row checkboxes until 3 selected\"), conditional retry that depends on UI state, or a runtime predicate over non-DOM state. Set params.goal to a one-sentence checkable predicate (e.g. params.goal = \"3 grid row checkboxes are selected\"). DO NOT use agent_loop for: known-N iteration (write N deterministic steps), single click that might double-fire (use \"click\"), retry-on-flake (the runtime handles that). agent_loop steps replay via a Playwright override authored after first run; they are non-deterministic on first run, so the bar is high. ' +\n (includeRecipeFields\n ? 'When recipe fields are available, use \"call\" for one completed call_recipe invocation. Treat the recipe as one atomic transaction: report one call step for the whole invocation, never its internal browser actions as additional playbook steps. '\n : '') +\n 'Network/devtools/API inspections and unsupported browser environment mutations are diagnostic-only unless there is an exposed browser tool for the action. If visible page state proves the condition, record that as \"verify\"; otherwise omit the diagnostic observation from playbookSteps. ' +\n 'Do not use \"other\". If no concrete browser-tool-backed action above fits, omit the observation from playbookSteps and report the limitation outside the playbook.'\n );\n}\n\nconst playbookStepAgentObjectSchema = z.object({\n order: z.number().describe('Step number (1-based).'),\n action: z.enum(PLAYBOOK_STEP_AGENT_ACTIONS).describe(buildActionDescription(false)),\n target: z\n .string()\n .describe(\n 'Human-readable element description using visible labels, placeholders, or aria-labels. ' +\n 'Examples: \"\\'Submit\\' button\", \"\\'Email\\' input field\", \"\\'Status\\' dropdown\". ' +\n 'For upload actions, describe the file input and include the attachment name (e.g. \"CSV file input for \\'testing-checklist.csv\\'\"). ' +\n 'Never use coordinates, ref IDs, or internal identifiers.'\n ),\n params: z\n .record(z.string(), z.string())\n .optional()\n .describe(\n 'Data values using $var.name references and $MACRO() expressions — NEVER hard-coded literals. ' +\n 'Common keys: value (for fill: {value: \"$EMAIL()\"}), option (for select: {option: \"Admin\"} — ' +\n 'fixed app constants like dropdown options are allowed as literals), url (for navigate: {url: \"$var.target_url\"}), ' +\n 'path (for upload: {path: \"$var.file_path\"}), key (for press_key: {key: \"Enter\"}, {key: \"Escape\"}), ' +\n 'width and height (for resize: {width: \"390\", height: \"844\"}), ' +\n 'label and fullPage (for screenshot: {label: \"seats-table\", fullPage: \"true\"}), ' +\n 'pattern or contains (for download verification: {pattern: \"report-*.csv\"} or {contains: \"Expected Header\"}), ' +\n 'expression + saveAs (for capture: prefer a visible DOM ref expression or \"() => window.location.href\"; do not use performance.getEntries() or other network/storage/global state; e.g. {saveAs: \"order_confirmation_id\"}). ' +\n 'For fill_form: keys are field names from the fill_form call, values are $var.name or $MACRO() (e.g. {company: \"$var.company\", email: \"$EMAIL()\"}). ' +\n 'For call: keys are the recipe argument names and values are strings using the same $var.name / $MACRO() conventions. The compiler pins and inlines the recipe as one atomic transaction. ' +\n 'Available macros: $UUID(), $EMAIL(), $FIRST_NAME(), $LAST_NAME(), $PHONE(), $RANDOM_STRING(n), ' +\n '$RANDOM_INT(min,max), $TODAY(), $DATE(days), $NOW(), $TIMESTAMP(), $SEQUENCE(\"name\").'\n ),\n note: z\n .string()\n .optional()\n .describe(\n 'Timing or quirk notes ONLY (e.g. \"Wait 1s for modal animation\", \"Scroll into view first\"). ' +\n 'Do NOT put data values, optional markers, or verification notes here.'\n ),\n actionRefs: z\n .array(z.number())\n .max(1)\n .optional()\n .describe(\n 'Zero or one recorded browser action index that corresponds to this playbook step. ' +\n 'Interaction tools, browser_resize, capture evaluates (browser_evaluate with saveAs), and browser_assert produce [Action #N] tags — collect those indices here. ' +\n 'Read-only browser_snapshot and plain browser_evaluate without saveAs do NOT produce action indices. browser_scroll and browser_screenshot may produce action indices when they are the primary requested tool call; omit actionRefs when no action tag was emitted. ' +\n 'If you took multiple cacheable UI actions, report multiple 1:1 playbook steps instead of putting multiple refs on one step or summarizing them as \"other\". ' +\n 'A fill_form call is a single action — use one fill_form playbook step with one actionRef. ' +\n 'For a click that triggers a native dialog, keep one click actionRef on the click step and set expectedDialogs rather than adding a separate dialog actionRef. ' +\n 'Actions not referenced by any playbook step are considered exploratory and will be discarded by the cache compiler.'\n ),\n expectedDialogs: z\n .enum(['accept', 'dismiss'])\n .optional()\n .describe(\n 'Set when this step is known to fire a native browser dialog (alert/confirm/prompt) — ' +\n 'e.g. clicking a \"Delete\" button that prompts \"Are you sure?\". On replay the runtime ' +\n 'pre-arms the dialog handler with this action automatically. In free-form runs, you ' +\n 'should call the dialog tool (browser_dialog for consolidated tools, browser_handle_dialog for legacy tools) with this action immediately before the step. ' +\n 'Do not add a separate dialog step — keeping the pre-arm on the triggering step keeps the ' +\n 'pre-arm and the triggering action in one place.'\n ),\n assertionId: z\n .string()\n .optional()\n .describe(\n 'REQUIRED on verify steps when this node declares inline assertions: the inline assertion ' +\n 'UUID from the node config (the exact (id=<uuid>) shown in the prompt) that this verify ' +\n \"step proves, or 'completion' for an agent-authored guardrail or completion assertion proving the action succeeded. \" +\n 'A completion guardrail is not tied to, and must not be reported as, a user-declared assertion. ' +\n 'Must equal the inlineAssertionId passed to browser_assert for this check. Never set on ' +\n 'non-verify steps.'\n ),\n});\n\ntype PlaybookStepAgentRefinementInput = Omit<\n z.infer<typeof playbookStepAgentObjectSchema>,\n 'action'\n> & {\n action: PlaybookStepAction;\n recipeRef?: { slug: string; version: number };\n};\n\n/** Actions that can bind an `assertionId` — the verify family. */\nconst VERIFY_BINDING_ACTIONS = new Set<PlaybookStepAction>([\n 'verify',\n 'verify_download',\n 'verify_download_content',\n]);\n\nconst playbookStepAgentSuperRefine = (\n step: PlaybookStepAgentRefinementInput,\n includeRecipeFields: boolean,\n ctx: z.RefinementCtx,\n requireVerifyBinding = false\n): void => {\n if (includeRecipeFields && step.action === 'call') {\n if (!step.recipeRef) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: ['recipeRef'],\n message:\n 'call steps must pin the invoked recipe with recipeRef.slug and recipeRef.version.',\n });\n }\n if (step.target.trim().length < 3) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: ['target'],\n message:\n 'call steps must keep a non-trivial human-readable target as the fallback instruction.',\n });\n }\n }\n if (step.action === 'agent_loop') {\n const goal = step.params?.goal;\n if (!goal || goal.trim().length === 0) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: ['params', 'goal'],\n message:\n 'agent_loop steps must set params.goal to a one-sentence checkable predicate (e.g. \"3 grid row checkboxes are selected\").',\n });\n }\n }\n if (step.action === 'verify_download_content') {\n const pattern = step.params?.pattern;\n const contains = step.params?.contains;\n if ((!pattern || pattern.trim().length === 0) && (!contains || contains.trim().length === 0)) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: ['params'],\n message:\n 'verify_download_content steps must set params.contains or params.pattern for the expected downloaded content.',\n });\n }\n }\n if (step.action === 'resize') {\n const width = step.params?.width;\n const height = step.params?.height;\n if (!isPositiveIntegerString(width)) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: ['params', 'width'],\n message: 'resize steps must set params.width to a positive integer pixel value.',\n });\n }\n if (!isPositiveIntegerString(height)) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: ['params', 'height'],\n message: 'resize steps must set params.height to a positive integer pixel value.',\n });\n }\n }\n if (step.assertionId !== undefined && !VERIFY_BINDING_ACTIONS.has(step.action)) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: ['assertionId'],\n message: 'assertionId is only valid on verify steps.',\n });\n }\n if (\n requireVerifyBinding &&\n VERIFY_BINDING_ACTIONS.has(step.action) &&\n step.assertionId === undefined\n ) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: ['assertionId'],\n message:\n \"verify steps must bind the assertion they prove via assertionId — use the inline assertion UUID from the prompt or 'completion'.\",\n });\n }\n};\n\n/** Max length of an agent-reported `cacheCode` snippet (chars) — mirrors the L2 lane. */\nexport const MAX_PLAYBOOK_CACHE_CODE_LEN = 8000;\n\n/**\n * Per-node deactivation flag, stored INSIDE the node's `configJson` (key\n * `deactivated: true`) rather than as a table column. A deactivated node is\n * skipped at execution time (its run step records status `skipped`), letting\n * authors converge a flow node-by-node from the front without running the\n * whole thing.\n *\n * Why configJson and not a column: workflow sync (`syncDraftState`) and\n * `.cofactor.md` spec compilation both carry nodes as `{nodeType, configJson}`\n * only — a top-level column would be silently wiped on every sync. The key is\n * also deliberately OUTSIDE every cache-hash whitelist (`extractHashInput`,\n * `STRUCTURE_RELEVANT_CONFIG_KEYS`) so toggling it never invalidates cache\n * rows — do not add it to either.\n */\nexport function isNodeConfigDeactivated(configJson: unknown): boolean {\n return (\n typeof configJson === 'object' &&\n configJson !== null &&\n (configJson as { deactivated?: unknown }).deactivated === true\n );\n}\n\n/**\n * Build the agent-facing playbook step schema.\n *\n * `includeCacheCode` adds the fast-drive `cacheCode` report lane: the minimal\n * deterministic Playwright snippet that reproduces the step, used by the cache\n * mint to compile code-backed entries when the interactions themselves were\n * driven via `browser_run_code` (whose synthetic actions never enter the\n * replay cache). The lane must ONLY be enabled for the minimal (fast-drive)\n * toolset — ref-based runs report `actionRefs` and have no way to produce\n * trustworthy step code (see the L2 `buildStepProgressSchema` counterpart in\n * `apps/api/src/workflows/cache/recovery-agent/playbook-runner-schemas.ts`).\n *\n * `includeRecipeFields` adds the atomic recipe-call authoring lane: the `call`\n * action plus its pinned `recipeRef`. The persisted {@link PlaybookStep} type\n * always accepts those fields; this option gates only what an agent can emit.\n *\n * `requireVerifyBinding` makes `assertionId` mandatory on verify-family steps\n * (`verify`, `verify_download`, `verify_download_content`) — the node declares\n * inline assertions, so every verify step must name which one it proves (or\n * `'completion'`). Cross-step uniqueness of `assertionId` (no two steps\n * claiming the same inline assertion) is validated at the array level in\n * `workflowStepSchema.ts`, not here — a per-step `superRefine` cannot see\n * sibling steps.\n */\nexport function buildPlaybookStepAgentSchema(opts?: {\n includeCacheCode?: boolean;\n includeRecipeFields?: boolean;\n requireVerifyBinding?: boolean;\n}) {\n const recipeAwareBase = opts?.includeRecipeFields\n ? playbookStepAgentObjectSchema.extend({\n action: z.enum(PLAYBOOK_STEP_ACTIONS).describe(buildActionDescription(true)),\n recipeRef: z\n .object({\n slug: z.string().trim().min(1),\n version: z.number().int().positive(),\n })\n .optional()\n .describe(\n 'Required for action=\"call\": the exact slug and version returned by the invoked recipe. ' +\n 'A call is one atomic transaction, so pin the recipe once on the call step and do not ' +\n 'also report its internal browser actions as playbook steps.'\n ),\n })\n : playbookStepAgentObjectSchema;\n const base = opts?.includeCacheCode\n ? recipeAwareBase.extend({\n cacheCode: z\n .string()\n .trim()\n .min(1)\n .max(MAX_PLAYBOOK_CACHE_CODE_LEN)\n .optional()\n .describe(\n 'Fast-drive only: the MINIMAL self-contained Playwright snippet (uses `page`, and the ' +\n '`vars` / `macro()` / `attach()` bindings) that reproduces THIS completed atomic intent ' +\n 'from its stable entry state — the deterministic replacement for actionRefs when you ' +\n 'drove the step via browser_run_code. Fuse actions coupled through ephemeral UI state: ' +\n 'keep a visibility-guarded opener with the terminal action. If one call batched unrelated ' +\n \"intents, report only this intent's acting lines; omit scouting and observation. \" +\n 'Never hardcode data/secrets — use `vars.x` and `await macro(\"EMAIL\")`. ' +\n 'Never use cacheCode for action=\"call\": recipeRef pins the whole recipe as one atomic ' +\n 'transaction and the compiler inlines its versioned code. ' +\n 'For action=\"capture\" driven through browser_run_code, the snippet must top-level ' +\n 'return the captured string so replay can bind params.saveAs; omit cacheCode for a ' +\n 'capture performed with the dedicated capture tool. Omit for steps performed with ' +\n 'other dedicated tools (verify/navigate/upload) and for ' +\n 'steps that cannot be reduced to a stable snippet. ' +\n 'Must be self-contained — replays in an isolated scope; re-declare every variable/locator; ' +\n 'only page/fetch/vars/macro/attach/dialog exist.'\n ),\n cacheCodeDurability: cacheCodeDurabilityAttestationSchema\n .optional()\n .describe(\n 'Fast-drive only: structured evidence for a genuinely unavoidable positional locator ' +\n 'in cacheCode. Omit for semantic locators, coordinates, and direct read-only ' +\n 'diagnostics. This permits a normal prove pass; it never marks code verified.'\n ),\n cacheable: z\n .literal(false)\n .optional()\n .describe(\n 'Fast-drive only: set false only when this step is genuinely non-deterministic or ' +\n 'cannot be reduced to a stable replay artifact. This records an intentional cache gap; ' +\n 'do not use it for ordinary locator difficulty or a tool call you can cite with actionRefs.'\n ),\n uncacheableReason: z\n .string()\n .trim()\n .min(1)\n .max(500)\n .optional()\n .describe(\n 'Fast-drive only: required when cacheable=false. Briefly identify the transient, randomized, ' +\n 'external, or otherwise non-deterministic boundary that prevents durable replay.'\n ),\n })\n : recipeAwareBase;\n return base.superRefine((step, ctx) => {\n playbookStepAgentSuperRefine(\n step,\n opts?.includeRecipeFields === true,\n ctx,\n opts?.requireVerifyBinding === true\n );\n if (\n 'cacheCodeDurability' in step &&\n step.cacheCodeDurability &&\n !('cacheCode' in step && step.cacheCode)\n ) {\n ctx.addIssue({\n code: 'custom',\n path: ['cacheCodeDurability'],\n message: 'cacheCodeDurability requires cacheCode on the same step',\n });\n }\n if ('cacheable' in step && step.cacheable === false) {\n const uncacheableReason =\n 'uncacheableReason' in step && typeof step.uncacheableReason === 'string'\n ? step.uncacheableReason\n : undefined;\n if (!uncacheableReason?.trim()) {\n ctx.addIssue({\n code: 'custom',\n path: ['uncacheableReason'],\n message: 'uncacheableReason is required when cacheable is false',\n });\n }\n if (\n ('cacheCode' in step && step.cacheCode) ||\n step.actionRefs?.length ||\n ('recipeRef' in step && step.recipeRef)\n ) {\n ctx.addIssue({\n code: 'custom',\n path: ['cacheable'],\n message:\n 'cacheable:false is an intentional gap and cannot also carry cacheCode, actionRefs, or recipeRef',\n });\n }\n } else if ('uncacheableReason' in step && step.uncacheableReason) {\n ctx.addIssue({\n code: 'custom',\n path: ['uncacheableReason'],\n message: 'uncacheableReason requires cacheable:false on the same step',\n });\n }\n });\n}\n\n/** Default agent-facing playbook step schema (no fast-drive cacheCode lane). */\nexport const playbookStepAgentSchema = buildPlaybookStepAgentSchema();\n\n/** Inferred type of the agent-authored fields of a playbook step. */\nexport type PlaybookStepAgentInput = z.infer<typeof playbookStepAgentSchema>;\n\n/**\n * A single step in a workflow playbook.\n *\n * Composed of {@link PlaybookStepAgentInput} (everything the agent can set\n * via `updateWorkflowStep`) plus internal fields the runtime attaches during\n * compilation/healing. Splitting these keeps the agent's input surface\n * tight while letting the runtime carry provenance + screenshot keys.\n */\nexport interface PlaybookStep extends Omit<PlaybookStepAgentInput, 'action'> {\n /** Legacy persisted rows may still contain `other`; new agent-authored steps cannot. */\n action: LegacyPlaybookStepAction;\n\n /** Exact interaction-recipe version pinned by an atomic `call` step. */\n recipeRef?: { slug: string; version: number };\n\n /**\n * Provenance of `actionRefs`:\n * `'recording'` (default when omitted) — emitted by the initial recording\n * compilation; transient session-specific indices that should be stripped\n * after the cache row is built.\n * `'healing'` — emitted by the heal agent during cache-failure recovery and\n * proven correct by replay. These should be preserved across compiles so\n * the next compile lands the entry as `matchStrategy='actionRef'`.\n */\n actionRefsSource?: 'recording' | 'healing';\n /**\n * Fast-drive report lane ONLY: the minimal deterministic Playwright snippet\n * the agent reported for this step (see {@link buildPlaybookStepAgentSchema}).\n * Consumed in-memory by the cache mint to compile a code-backed entry; it is\n * STRIPPED at every persist boundary (node config writes,\n * `sanitizeStepsForCache`) — the durable home for step code is the cache\n * row's `actions_json` (`run_code` action) + `compiled_entries`, exactly as\n * on the L2 lane.\n */\n cacheCode?: string;\n /**\n * Compile-only structured evidence for a narrow positional durability\n * exception. Stripped with `cacheCode`; its bound durable home is the\n * synthesized `CachedAction.durabilityAttestation`.\n */\n cacheCodeDurability?: CacheCodeDurabilityAttestation;\n /** Fast-drive report explicitly classified this step as an intentional cache gap. */\n cacheable?: false;\n /** Required rationale when `cacheable === false`. */\n uncacheableReason?: string;\n /**\n * Layer 4 cache-durable multi-tab replay (fast-drive `cacheCode` lane only):\n * durable descriptor of the tab this step ran on. Threaded onto the\n * synthesized `run_code` `CachedAction.activeTab` at compile time so step-driven\n * replay re-resolves the active page before running the step's snippet. Absent\n * ⇒ single-tab provenance ⇒ replay is unchanged.\n */\n activeTab?: DurableTabRef;\n /** S3 key for screenshot captured after this playbook step completed. */\n screenshotS3Key?: string;\n /**\n * Legacy run-mode provenance retained so historical playbooks deserialize.\n * New writes omit this field; cache sanitization still strips either value.\n */\n playbookSource?: 'production' | 'validation';\n /**\n * Binds this verify step to the inline assertion it proves: either an id\n * from `configJson.inlineAssertions[].id` on this node, or the `'completion'`\n * sentinel for the implicit action-succeeded check. Deliberately excluded\n * from node-hash inputs (`extractHashInput`, `STRUCTURE_RELEVANT_CONFIG_KEYS`)\n * — it identifies which assertion a step proves, not the node's structure.\n */\n assertionId?: string;\n}\n\n/**\n * Bind replay verify steps to configured assertions without relying on result order.\n * Explicit assertion identities are authoritative; legacy unbound steps may match\n * unique condition/label text or the sole available assertion.\n */\nexport function matchAssertionSpecsToVerifySteps(\n playbookSteps: readonly PlaybookStep[],\n specs: readonly AssertionSpec[]\n): Record<number, AssertionSpec> | undefined {\n const verifySteps = playbookSteps\n .filter((playbookStep) => playbookStep.action === 'verify')\n .sort((a, b) => a.order - b.order);\n if (verifySteps.length === 0 || specs.length === 0) return undefined;\n\n const claimedSpecs = new Set<AssertionSpec>();\n const byOrder: Record<number, AssertionSpec> = {};\n for (const playbookStep of verifySteps) {\n const candidates = specs.filter((spec) => {\n if (claimedSpecs.has(spec)) return false;\n if (playbookStep.assertionId !== undefined) {\n return spec.inlineAssertionId === playbookStep.assertionId;\n }\n const target = playbookStep.target.trim();\n return spec.condition.trim() === target || spec.label?.trim() === target;\n });\n const spec =\n candidates.length === 1\n ? candidates[0]\n : playbookStep.assertionId === undefined && verifySteps.length === 1 && specs.length === 1\n ? specs[0]\n : undefined;\n if (!spec || claimedSpecs.has(spec)) continue;\n claimedSpecs.add(spec);\n byOrder[playbookStep.order] = spec;\n }\n return Object.keys(byOrder).length > 0 ? byOrder : undefined;\n}\n\n/**\n * Execution-time snapshot of one playbook step, persisted into\n * `workflow_run_steps.metadataJson.executedPlaybook` when step-driven cache\n * replay ran for a step (full hit or partial replay before agent fallback).\n *\n * Deliberately slim: only the display-relevant agent-authored fields. Runtime\n * plumbing (`actionRefs`, `actionRefsSource`, `screenshotS3Key`,\n * `playbookSource`, `expectedDialogs`) is stripped so run rows stay small.\n * Values are sanitized back to `$var.`/`$MACRO` tokens before persistence —\n * the cache row is mutable, so this snapshot is the only exact record of what\n * replayed for a historical run.\n */\nexport interface ExecutedPlaybookStep {\n /** Step number (1-based) — joins against `StepOutcome.stepOrder`. */\n order: number;\n action: LegacyPlaybookStepAction;\n /** Human-readable element description. */\n target: string;\n /** Data values, with variable references preserved as `$var.`/`$MACRO` tokens. */\n params?: Record<string, string>;\n /** Optional human context authored alongside the step. */\n note?: string;\n /** Exact configured assertion identity for a bound verify step. */\n assertionId?: string;\n}\n\n/**\n * An assertion attached directly to an action node.\n * Verified after the action completes; cacheable assertions are replayed mechanically.\n */\nexport interface InlineAssertion {\n /** Unique ID within this node */\n id: string;\n /** Freeform description of what to verify */\n condition: string;\n /** fatal = stop the test now; fail = mark the run failed but continue; warn = log and continue */\n severity: AssertionSeverity;\n /** When true, requires agent evaluation (e.g. computer vision) and cannot be cached */\n agentic?: boolean;\n}\n\nexport type BrowserMode = 'headed' | 'headless';\n\nexport * from './crawler/analysis.js';\nexport * from './api-catalog.js';\nexport * from './credentials.js';\nexport * from './test-sign-in.js';\nexport * from './test-sign-in-setup-prompt.js';\nexport * from './environment-kinds.js';\nexport * from './automation-alerts.js';\nexport * from './external-test.js';\nexport * from './exploratory-effort.js';\nexport * from './review-flavors.js';\n// The `$var.USER.*` / `$var.ENV.*` grammar an external runner's `envMapping`\n// speaks. Shared because the API materializes the `.env` from it and the web\n// config editor authors it — a client-side copy of a security-critical\n// allowlist grammar is exactly the drift worth designing out.\nexport * from './runner-env-mapping.js';\n// The run-progress SSE vocabulary, shared between the emitter and the UI that\n// subscribes to it — a hand-mirrored client copy drifts silently.\nexport * from './external-test-events.js';\nexport * from './runbook-run-events.js';\nexport * from './git-source.js';\nexport * from './secret-redaction.js';\nexport * from './body-redaction.js';\nexport * from './trace-uploads.js';\nexport * from './qa/testing.js';\nexport * from './qa/events.js';\nexport * from './issues.js';\nexport * from './pagination.js';\nexport * from './workflows.js';\nexport * from './visual-review.js';\nexport * from './visual-text.js';\nexport * from './digests.js';\nexport * from './login-host.js';\nexport * from './notifications.js';\nexport * from './run-target.js';\nexport * from './run-prestart.js';\nexport * from './browser-stream.js';\nexport * from './attachments.js';\nexport * from './permissions.js';\nexport * from './workflow-cache.js';\nexport * from './cache-action-contract.js';\nexport * from './cache-optimizer-events.js';\nexport * from './cache-telemetry.js';\nexport * from './instance-logs.js';\nexport * from './local-browser.js';\nexport * from './benchmark.js';\n\nexport * from './api-sequence.js';\nexport * from './web-client-header.js';\nexport * from './diagnostics.js';\nexport * from './diagnostics-filters.js';\nexport * from './conversation-stream.js';\nexport * from './sandbox-tasks.js';\nexport * from './sandbox-instance-size.js';\nexport * from './routes/index.js';\nexport * from './macros.js';\nexport * from './recording.js';\nexport * from './variable-utils.js';\nexport * from './node-outputs.js';\nexport * from './sandbox-action-node.js';\nexport * from './mcp-node.js';\nexport * from './credential-output.js';\nexport * from './action-output.js';\nexport * from './adhoc-test.js';\nexport * from './mentions.js';\nexport * from './element-hints.js';\nexport * from './ai-routing.js';\nexport * from './workflow-node-schemas.js';\nexport * from './node-type-labels.js';\nexport * from './visual-checkpoint.js';\nexport * from './visual-api.js';\nexport * from './browser-viewport.js';\nexport * from './browser-engine.js';\nexport * from './identifier-stability.js';\nexport * from './workflow-review.js';\nexport * from './fast-video.js';\nexport * from './user-features.js';\nexport * from './product-map.js';\nexport * from './password.js';\nexport * from './announcements.js';\nexport * from './assistant-pages.js';\nexport * from './assistant-onboardings.js';\nexport * from './readable-slug.js';\n\nexport { calculatePassRate, formatPassRate } from './pass-rate.js';\nexport * from './proof.js';\nexport * from './proof-body.js';\nexport * from './billing.js';\nexport * from './agent-feedback.js';\n","/**\n * Workflow Action Cache Types\n *\n * Types for caching browser actions from workflow runs for fast replay\n * without the AI agent.\n */\n\nimport { z } from 'zod';\nimport { AssertionSeverityEnum, type AssertionSeverity } from './workflow-node-schemas.js';\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Selector Strategy Types\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Role-based selector (most stable for accessibility-focused apps)\n */\nexport type RoleSelector = {\n role: string;\n name?: string;\n exact?: boolean;\n};\n\n/**\n * Text content selector\n */\nexport type TextSelector = {\n text: string;\n exact?: boolean;\n};\n\n/**\n * Parent context for disambiguating elements with similar selectors\n */\n/**\n * Dynamic-text annotation for parent-context fields whose disambiguating text\n * originates from a workflow variable and must be re-resolved at replay time.\n */\nexport type ParentContextDynamicText = {\n source: 'inputValue' | 'workflowState';\n key: string;\n originalText: string;\n};\n\n/**\n * A single node in a parent-scoping chain.\n */\nexport type ParentContextNode = {\n role: string;\n name?: string;\n css?: string;\n nameFromHeading?: boolean;\n /**\n * Filter the parent by text it contains. Used to anchor on row-like\n * ancestors (role=row/listitem/gridcell) where the row has no accessible\n * name of its own but contains a stable cell value (e.g. a variable name).\n * Applied at replay as `.filter({ hasText: containsText })`.\n */\n containsText?: string;\n /** Dynamic binding for containsText when it originated from a workflow variable. */\n containsTextDynamic?: ParentContextDynamicText;\n};\n\nexport type ParentContext = {\n /** Single parent role (backward compatible) */\n role?: string;\n /** Single parent name (backward compatible) */\n name?: string;\n /** Optional CSS selector for parent scoping when role-based lookup is unavailable */\n css?: string;\n /** True when name was extracted from a child heading, not the parent's own accessible name */\n nameFromHeading?: boolean;\n /**\n * Filter the parent by text it contains. Used to anchor on row-like\n * ancestors that lack a unique accessible name (see ParentContextNode).\n */\n containsText?: string;\n /** Dynamic binding for containsText when it originated from a workflow variable. */\n containsTextDynamic?: ParentContextDynamicText;\n\n /** Recursive parent chain for deep disambiguation (innermost to outermost) */\n parentChain?: ParentContextNode[];\n\n /** Fallback: index of matched element when no unique parent found */\n matchIndex?: number;\n};\n\n/**\n * Frame/container context for elements inside iframes or shadow DOM.\n * Captured at recording time to ensure deterministic replay.\n */\nexport type FrameContext = {\n /** Type of container */\n type: 'iframe' | 'shadow' | 'named-frame';\n /** Selector to locate the container (e.g., 'iframe.designer-client-frame') */\n selector: string;\n /** Optional: frame name attribute for named frames */\n name?: string;\n /** Optional: frame ID attribute */\n id?: string;\n};\n\n/** Who authored the active Playwright locator. */\nexport type SelectorAuthor = 'deterministic' | 'agentic';\n\n/** Alternate Playwright locator candidate stored for comparison/debugging. */\nexport type PlaywrightLocatorCandidate = {\n /** Locator chain string. */\n locator: string;\n /** Whether the locator contains variable placeholders. */\n usesVariableBinding?: boolean;\n /** Variable key used by the locator, if any. */\n boundVariableKey?: string;\n};\n\n/**\n * Multiple selector strategies for fallback during replay.\n * Tried in order: testId -> role -> ariaLabel -> placeholder -> text -> css\n */\nexport type SelectorStrategy = {\n /** data-testid selector (most stable when available) */\n testId?: string;\n /** Role-based selector (most stable) */\n role?: RoleSelector;\n /** Label text selector (getByLabel) */\n ariaLabel?: string;\n /** Placeholder selector (getByPlaceholder) */\n placeholder?: string;\n /** Text content selector */\n text?: TextSelector;\n /** Additional text parts to require within the resolved element */\n containsText?: string[];\n /** CSS selector (least stable, last resort) */\n css?: string;\n /** Parent context for disambiguation */\n parentContext?: ParentContext;\n /** Frame/container context - element lives inside this container */\n frameContext?: FrameContext;\n\n // ─────────────────────────────────────────────────────────────────────────────\n // Data attribute disambiguation (for list/table elements)\n // ─────────────────────────────────────────────────────────────────────────────\n\n /** Item index for list disambiguation (data-item-index, data-selection-index) */\n itemIndex?: number;\n /** Row index for table/grid disambiguation (aria-rowindex) */\n rowIndex?: number;\n /** Automation ID for verification (data-automationid) */\n automationId?: string;\n\n /** Filter to disambiguate by a child element's role + name (e.g., child img alt text) */\n childFilter?: {\n role: string;\n name: string;\n };\n\n // ─────────────────────────────────────────────────────────────────────────────\n // Playwright locator chain (authored by cache selector agent)\n // ─────────────────────────────────────────────────────────────────────────────\n\n /**\n * Active Playwright locator chain used during replay.\n * Supports variable placeholders: {{var.project_name}} hydrated at replay time.\n * Example: \"getByRole('row').filter({ hasText: '{{var.project_name}}' }).getByRole('button', { name: 'Edit' })\"\n */\n playwrightLocator?: string;\n\n /** Alternate deterministic/agentic locator candidates preserved for comparison. */\n playwrightLocatorCandidates?: Partial<Record<SelectorAuthor, PlaywrightLocatorCandidate>>;\n\n // ─────────────────────────────────────────────────────────────────────────────\n // Dynamic text binding (for selectors containing run-specific content)\n // ─────────────────────────────────────────────────────────────────────────────\n\n /** Dynamic text binding — selector text that varies between runs */\n dynamicText?: {\n /** Data source for the dynamic value */\n source: 'inputValue' | 'workflowState';\n /** Key in the data source */\n key: string;\n /** The literal text at recording time (for token replacement) */\n originalText: string;\n };\n};\n\nconst nonBlankStringSchema = z.string().refine((value) => value.trim().length > 0, {\n message: 'Expected a non-empty string',\n});\n\nconst parentContextDynamicTextSchema = z.strictObject({\n source: z.enum(['inputValue', 'workflowState']),\n key: nonBlankStringSchema,\n originalText: nonBlankStringSchema,\n}) satisfies z.ZodType<ParentContextDynamicText>;\n\nconst parentContextNodeSchema = z.strictObject({\n role: nonBlankStringSchema,\n name: nonBlankStringSchema.optional(),\n css: nonBlankStringSchema.optional(),\n nameFromHeading: z.boolean().optional(),\n containsText: nonBlankStringSchema.optional(),\n containsTextDynamic: parentContextDynamicTextSchema.optional(),\n}) satisfies z.ZodType<ParentContextNode>;\n\nconst parentContextSchema = z.strictObject({\n role: nonBlankStringSchema.optional(),\n name: nonBlankStringSchema.optional(),\n css: nonBlankStringSchema.optional(),\n nameFromHeading: z.boolean().optional(),\n containsText: nonBlankStringSchema.optional(),\n containsTextDynamic: parentContextDynamicTextSchema.optional(),\n parentChain: z.array(parentContextNodeSchema).optional(),\n matchIndex: z.number().int().nonnegative().optional(),\n}) satisfies z.ZodType<ParentContext>;\n\nconst frameContextSchema = z.strictObject({\n type: z.enum(['iframe', 'shadow', 'named-frame']),\n selector: nonBlankStringSchema,\n name: nonBlankStringSchema.optional(),\n id: nonBlankStringSchema.optional(),\n}) satisfies z.ZodType<FrameContext>;\n\nconst playwrightLocatorCandidateSchema = z.strictObject({\n locator: nonBlankStringSchema,\n usesVariableBinding: z.boolean().optional(),\n boundVariableKey: nonBlankStringSchema.optional(),\n}) satisfies z.ZodType<PlaywrightLocatorCandidate>;\n\n/**\n * Runtime schema for selector artifacts crossing cache boundaries.\n *\n * Keep this beside {@link SelectorStrategy}: the optimizer route, recipe\n * builder, and replay validator all consume the same schema so a selector\n * accepted at commit cannot fail later because its JSON value shapes drifted.\n */\nexport const selectorStrategySchema = z\n .strictObject({\n testId: nonBlankStringSchema.optional(),\n role: z\n .strictObject({\n role: nonBlankStringSchema,\n name: nonBlankStringSchema.optional(),\n exact: z.boolean().optional(),\n })\n .optional(),\n ariaLabel: nonBlankStringSchema.optional(),\n placeholder: nonBlankStringSchema.optional(),\n text: z\n .strictObject({\n text: nonBlankStringSchema,\n exact: z.boolean().optional(),\n })\n .optional(),\n containsText: z.array(nonBlankStringSchema).min(1).optional(),\n css: nonBlankStringSchema.optional(),\n parentContext: parentContextSchema.optional(),\n frameContext: frameContextSchema.optional(),\n itemIndex: z.number().int().nonnegative().optional(),\n rowIndex: z.number().int().nonnegative().optional(),\n automationId: nonBlankStringSchema.optional(),\n childFilter: z\n .strictObject({\n role: nonBlankStringSchema,\n name: nonBlankStringSchema,\n })\n .optional(),\n playwrightLocator: nonBlankStringSchema.optional(),\n playwrightLocatorCandidates: z\n .strictObject({\n deterministic: playwrightLocatorCandidateSchema.optional(),\n agentic: playwrightLocatorCandidateSchema.optional(),\n })\n .optional(),\n dynamicText: parentContextDynamicTextSchema.optional(),\n })\n .superRefine((selectors, ctx) => {\n const hasLocatorCandidate = Object.values(selectors.playwrightLocatorCandidates ?? {}).some(\n (candidate) => candidate?.locator.trim().length\n );\n const hasExecutableSelector = Boolean(\n selectors.testId ||\n selectors.role ||\n selectors.ariaLabel ||\n selectors.placeholder ||\n selectors.text ||\n selectors.css ||\n selectors.playwrightLocator ||\n hasLocatorCandidate\n );\n if (!hasExecutableSelector) {\n ctx.addIssue({\n code: 'custom',\n message: 'Expected at least one executable selector',\n });\n }\n }) satisfies z.ZodType<SelectorStrategy>;\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Context Extractor Types\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * A small JS extraction script that runs via page.evaluate() after cache replay\n * to extract fresh application-generated values (IDs, codes, computed results) from the live page.\n *\n * Should only target data produced by the application (auto-generated IDs, confirmation codes,\n * computed totals), NOT data the agent entered (form inputs, search terms).\n *\n * Authored by agents in debug mode when step notes contain run-specific data.\n */\nexport type ContextExtractor = {\n /** Snake_case key for the extracted value (e.g., \"project_id\") */\n key: string;\n /** JS expression for page.evaluate(). Must return string | null. */\n expression: string;\n /** Human-readable description of what this extractor captures */\n description?: string;\n};\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Cached Data Script Types\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * A Python script cached for automatic replay alongside browser action cache.\n * When the agent uses python_execute with cache_label, the script is stored here\n * so it can be re-run without the agent during cached replay.\n */\nexport type CachedDataScript = {\n /** Short descriptive label (e.g., \"validate-row-count\") */\n label: string;\n /** Python code to execute */\n code: string;\n /** Table name from the preceding browser_extract_table call */\n tableName: string;\n /** Optional ref for a specific table (matches browser_extract_table ref parameter) */\n tableRef?: string;\n /** ISO timestamp when this script was cached */\n createdAt: string;\n /** Index of the corresponding CachedActionRecord (for playbook linkage). */\n actionIndex?: number;\n};\n\nexport const cachedDataScriptSchema = z.strictObject({\n label: nonBlankStringSchema,\n code: nonBlankStringSchema,\n tableName: nonBlankStringSchema,\n tableRef: nonBlankStringSchema.optional(),\n createdAt: nonBlankStringSchema,\n actionIndex: z.number().int().min(0).optional(),\n}) satisfies z.ZodType<CachedDataScript>;\n\n/**\n * Result of replaying a cached data script during cache replay.\n */\nexport interface DataScriptReplayResult {\n label: string;\n success: boolean;\n stdout?: string;\n stderr?: string;\n error?: string;\n durationMs: number;\n}\n\nexport type CacheCaptureWarningReason =\n | 'empty'\n | 'invalid'\n | 'error'\n | 'timeout'\n | 'missing_callback';\n\nexport type CacheCaptureWarning = {\n key: string;\n required: boolean;\n reason: CacheCaptureWarningReason;\n actionIndex?: number;\n expression?: string;\n currentUrl?: string;\n lastValue?: string;\n validationReason?: string;\n};\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Cached Action Types\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Types of browser actions that can be cached\n */\nexport type CachedActionType =\n | 'navigate'\n | 'click'\n | 'type'\n | 'press_key'\n | 'select_option'\n | 'fill_form'\n | 'wait_for'\n | 'hover'\n | 'drag'\n | 'resize'\n | 'scroll'\n | 'screenshot'\n | 'file_upload'\n | 'handle_dialog'\n | 'snapshot'\n | 'evaluate'\n | 'annotation'\n | 'assert'\n | 'move'\n | 'capture'\n | 'extract_table'\n | 'python_execute'\n // Ref-less fast-drive: a self-contained Playwright snippet the L2 agent\n // authored to reproduce a single playbook step. Stored as `CachedAction.code`\n // and replayed via the shared run_code primitive. See `code` below.\n | 'run_code'\n // MCP workflow nodes: one recorded remote MCP tool call (`toolName` is the\n // REMOTE tool name, `args` the recorded arguments with `$var.<key>` tokens).\n // Replays via the dedicated all-or-nothing mcp lane\n // (apps/api/src/workflows/cache/mcp-replay.ts), never the browser runtime.\n | 'mcp_tool_call';\n\n/**\n * Action types the cache replay runtime executes deterministically.\n *\n * This tuple is the admission/replay source of truth. Inert historical\n * observation actions (`snapshot`, `evaluate`) remain valid stored\n * `CachedActionType`s but cannot be authored as executable recipe steps.\n */\nexport const CACHE_REPLAYABLE_ACTION_TYPES = [\n 'navigate',\n 'click',\n 'type',\n 'press_key',\n 'select_option',\n 'fill_form',\n 'wait_for',\n 'hover',\n 'resize',\n 'scroll',\n 'screenshot',\n 'file_upload',\n 'handle_dialog',\n 'annotation',\n 'assert',\n 'move',\n 'capture',\n 'extract_table',\n 'python_execute',\n 'run_code',\n 'mcp_tool_call',\n] as const satisfies readonly CachedActionType[];\n\nexport type CacheReplayableActionType = (typeof CACHE_REPLAYABLE_ACTION_TYPES)[number];\n\nexport const cacheReplayableActionTypeSchema = z.enum(CACHE_REPLAYABLE_ACTION_TYPES);\n\n/**\n * Types of wait strategies before executing an action\n */\nexport type WaitStrategyType = 'network_idle' | 'dom_stable' | 'selector' | 'delay';\n\n/**\n * Wait strategy configuration\n */\nexport type WaitStrategy = {\n type: WaitStrategyType;\n /** CSS selector to wait for (when type is 'selector') */\n selector?: string;\n /** Delay in milliseconds (when type is 'delay') */\n delayMs?: number;\n};\n\n/**\n * Types of assertions that can be cached\n */\nexport type AssertionType =\n | 'text_present'\n | 'text_absent'\n | 'element_visible'\n | 'element_hidden'\n | 'evaluate'\n /** Driver-side, read-only Playwright statements returning a primitive value. */\n | 'playwright'\n | 'toast_present'\n /** A file matching condition was captured after the step began. */\n | 'download_present'\n /** A uniquely matching text download contains the expected text/pattern. */\n | 'download_content';\n\n/**\n * Structured result of evaluating recipe-authored browser code.\n *\n * Only authored evidence may affect recipe health. Infrastructure failures\n * fail the active execution but are excluded from health accounting.\n */\nexport type AssertionEvaluationOutcome =\n | 'passed'\n | 'refuted'\n | 'authored_error'\n | 'infrastructure_error';\n\n/**\n * Assertion specification for cache replay verification\n */\nexport type AssertionSpec = {\n type: AssertionType;\n /** The condition to check (text, selector, in-page expression, or Playwright statements) */\n condition: string;\n /** Expected value for evaluate/playwright assertions */\n expected?: unknown;\n /** Severity from an inline assertion: fatal halts the run, fail marks it failed but continues, warn logs only */\n severity?: AssertionSeverity;\n /** Source inline assertion ID for correlating results back to the node config */\n inlineAssertionId?: string;\n /** Short human-readable description supplied by the agent at browser_assert time */\n label?: string;\n};\n\n/**\n * Zod schema for `AssertionSpec` — single source for every surface that\n * accepts assertion specs from clients (the optimizer agent's commit tool and\n * the CLI cache-recipe route). The `satisfies` bind fails the build if the\n * schema and the documented type ever drift.\n */\nexport const assertionSpecSchema = z.object({\n type: z.enum([\n 'text_present',\n 'text_absent',\n 'element_visible',\n 'element_hidden',\n 'evaluate',\n 'playwright',\n 'toast_present',\n 'download_present',\n 'download_content',\n ]),\n condition: z.string(),\n expected: z.unknown().optional(),\n severity: AssertionSeverityEnum.optional(),\n inlineAssertionId: z.string().optional(),\n label: z.string().optional(),\n}) satisfies z.ZodType<AssertionSpec>;\n\n/**\n * MCP node verify/extract snippet, stored ONCE per cache row in the\n * `codified_assertion` jsonb column (discriminated from browser\n * {@link AssertionSpec} rows by the `kind: 'mcp-verify'` key — AssertionSpec\n * has no `kind` field). The snippet is the agent-authored JS body executed by\n * `runMcpVerifySnippet` against the replayed tool-call results; it must return\n * `{ pass: boolean, reason?: string, outputs?: Record<string, unknown> }`.\n */\nexport type McpVerifyCodifiedAssertion = {\n kind: 'mcp-verify';\n /** Agent-authored JS function body (async IIFE-wrapped at execution). */\n code: string;\n /** Node-declared output names the snippet must return in `outputs`. */\n declaredOutputs?: string[];\n};\n\nexport const mcpVerifyCodifiedAssertionSchema = z.object({\n kind: z.literal('mcp-verify'),\n code: z.string().min(1),\n declaredOutputs: z.array(z.string()).optional(),\n}) satisfies z.ZodType<McpVerifyCodifiedAssertion>;\n\nexport function isMcpVerifyCodifiedAssertion(value: unknown): value is McpVerifyCodifiedAssertion {\n return (\n typeof value === 'object' &&\n value !== null &&\n (value as { kind?: unknown }).kind === 'mcp-verify' &&\n typeof (value as { code?: unknown }).code === 'string'\n );\n}\n\n/**\n * What the `workflow_node_cache.codified_assertion` column stores: a browser\n * assertion spec (assertion/condition nodes) or the mcp verify snippet (mcp\n * nodes). Browser consumers must narrow via\n * {@link isMcpVerifyCodifiedAssertion} before reading AssertionSpec fields.\n */\nexport type CodifiedAssertion = AssertionSpec | McpVerifyCodifiedAssertion;\n\n/**\n * Assertion payload attached to a composite recipe `run_code` action.\n *\n * Deliberately excludes ordinary cached-assert controls (`severity`,\n * `inlineAssertionId`, `strict`). A recipe oracle is part of the action's\n * success contract, never advisory, so replay has no field it could consult to\n * turn a refutation into a warning.\n */\nexport type CachedRecipeOracleAssertionSpec = {\n type: 'text_present' | 'text_absent' | 'element_visible' | 'element_hidden' | 'evaluate';\n condition: string;\n expected?: unknown;\n label?: string;\n};\n\nexport type CachedRecipeOracle =\n | {\n kind: 'assertion';\n spec: CachedRecipeOracleAssertionSpec;\n frameHints?: string[];\n }\n | {\n kind: 'function';\n source: string;\n };\n\nexport const cachedRecipeOracleAssertionSpecSchema = z\n .object({\n type: z.enum(['text_present', 'text_absent', 'element_visible', 'element_hidden', 'evaluate']),\n condition: z.string().refine((value) => value.trim().length > 0, {\n message: 'Expected a non-empty string',\n }),\n expected: z.unknown().optional(),\n label: z.string().optional(),\n })\n .strict() satisfies z.ZodType<CachedRecipeOracleAssertionSpec>;\n\nexport const cachedRecipeOracleSchema = z.discriminatedUnion('kind', [\n z\n .object({\n kind: z.literal('assertion'),\n spec: cachedRecipeOracleAssertionSpecSchema,\n frameHints: z.array(z.string()).optional(),\n })\n .strict(),\n z\n .object({\n kind: z.literal('function'),\n source: z.string().refine((value) => value.trim().length > 0, {\n message: 'Expected a non-empty string',\n }),\n })\n .strict(),\n]) satisfies z.ZodType<CachedRecipeOracle>;\n\n/**\n * Evaluable assertion types usable for deterministic session validation. Derived from\n * {@link AssertionType} so it tracks automatically, minus two exclusions:\n * - `toast_present` — toasts depend on transient captured state that isn't present on\n * a cold navigate to the session-check URL.\n * - `evaluate` — runs agent-authored JS via `page.evaluate` in the target app's origin\n * and surfaces the result in logs; not worth that surface for a session-validity gate,\n * and the four DOM types fully express \"am I logged in?\".\n */\nexport type AuthoredAssertionType = Exclude<\n AssertionType,\n 'toast_present' | 'evaluate' | 'playwright' | 'download_present' | 'download_content'\n>;\n\n/**\n * A durable assertion the login agent authors after a successful login to prove the\n * logged-in state. Recorded on the credential's login cache and replayed\n * deterministically (via `IBrowserClient.assert`) on subsequent runs, so a valid\n * session can be confirmed without booting the AI session-validation agent. Re-authored\n * (overwritten) on every fresh AI login to stay current with the app's UI.\n *\n * Each authored assertion must PASS when the session is valid. Positive indicators\n * (e.g. an account menu) use `element_visible` / `text_present`; negative indicators\n * (e.g. a login form that should be gone once authenticated) use `element_hidden` /\n * `text_absent`, which pass quickly when the target is absent.\n */\nexport type AuthoredAssertion = {\n type: AuthoredAssertionType;\n /** Text content or CSS selector — same meaning as `AssertionSpec.condition` */\n condition: string;\n /** Short human-readable description of what this verifies */\n label: string;\n};\n\n/**\n * Zod schema for {@link AuthoredAssertion}. Shared by the login agent's\n * `report_login_result` tool input and the login-cache persistence layer so the\n * authored shape, the stored shape, and the evaluator agree.\n */\nexport const authoredAssertionSchema = z.object({\n type: z.enum(['text_present', 'text_absent', 'element_visible', 'element_hidden']),\n condition: z.string(),\n label: z.string(),\n}) satisfies z.ZodType<AuthoredAssertion>;\n\n/**\n * Stored login-cache validation payload (the `login_cache_validation_json` column).\n * Single source of truth for the jsonb shape, imported by the DB schema `$type`, the\n * credentials repo, and the session-check module so all three agree.\n *\n * `assertions` are the agent-authored session assertions. The legacy regex `*Indicators`\n * fields are kept optional only so pre-existing rows still deserialize — they are no\n * longer written or read.\n */\nexport type LoginCacheValidation = {\n authenticatedIndicators?: string[];\n unauthenticatedIndicators?: string[];\n /** Exact protected page where the agent authored `assertions`. */\n validationUrl?: string;\n assertions?: AuthoredAssertion[];\n};\n\n/**\n * Durable descriptor of the browser tab a cached action/step ran on, for\n * cache-durable multi-tab replay (Layer 4). It is a PREDICATE — a normalized\n * URL shape and/or a title substring — never a positional tab index (tab order\n * is not stable across runs, mirroring the positional-locator ban in\n * `locator-durability`). Absent ⇒ single-tab provenance ⇒ replay keeps today's\n * exact single-page behavior. Stamped at record time only when the browser\n * context has more than one open tab.\n */\nexport type DurableTabRef = {\n /**\n * Normalized URL shape of the active tab at record time\n * (via `normalizeUrlShapeForCacheReplay`), e.g. `https://app.example.com/orders/:uuid`.\n * Dynamic id segments are collapsed so the predicate survives fresh ids.\n */\n urlShape?: string;\n /**\n * Secret-safe hash of the full tab URL identity when query or hash data exists.\n * This lets replay reject query-only tab mismatches without storing token-bearing\n * query strings in cache rows or model-visible prompt state.\n */\n urlIdentityHash?: string;\n /** Document title of the active tab at record time; matched as a substring. */\n title?: string;\n};\n\n/**\n * Record-time confidence about which browser tab an action started on.\n *\n * `unique` is paired with {@link DurableTabRef}; `single` proves that exactly\n * one active tab was observed; `ambiguous` is a fail-closed marker that must\n * survive selector healing and force agent fallback. Omitted on legacy rows or\n * while the multi-tab feature is disabled.\n */\nexport type TabProvenance = 'single' | 'unique' | 'ambiguous';\n\n/**\n * Agent-supplied evidence for the narrow cases where a positional locator is\n * genuinely the durable expression of the authored intent. This is admission\n * evidence only: it never grants verification credit, and the server binds it\n * to the exact compiled code before persistence. Coordinates need no evidence\n * (decision 2026-10-22). A stored legacy coordinate-only attestation fails\n * this parse; a `coordinate` key beside `positional` is stripped.\n */\nexport const cacheCodeDurabilityAttestationSchema = z.object({\n positional: z.discriminatedUnion(\n 'reason',\n [\n z.object({\n reason: z.literal('single_visible_candidate'),\n observedCandidates: z.number().int().min(2),\n observedVisibleCandidates: z.literal(1),\n semanticScope: z.string().trim().min(3).max(240),\n evidence: z.string().trim().min(10).max(500),\n }),\n z.object({\n reason: z.literal('explicit_ordinal_intent'),\n ordinal: z.number().int().nonnegative(),\n authoredIntent: z.string().trim().min(1).max(240),\n evidence: z.string().trim().min(10).max(500),\n }),\n ],\n {\n error:\n 'positional evidence (single_visible_candidate or explicit_ordinal_intent) is required; coordinates need no cacheCodeDurability, so omit it for coordinate-only code',\n }\n ),\n});\n\nexport type CacheCodeDurabilityAttestation = z.infer<typeof cacheCodeDurabilityAttestationSchema>;\n\n/** Server-bound durable form stored on the synthesized run_code action. */\nexport type BoundCacheCodeDurabilityAttestation = CacheCodeDurabilityAttestation & {\n /** SHA-256 of the exact `CachedAction.code` accepted with this evidence. */\n codeSha256: string;\n};\n\n/**\n * A single cached browser action with stable selectors\n */\nexport type CachedAction = {\n /** Sequence number within the cache */\n index: number;\n /** Original tool name from agent (e.g., 'browser_click') */\n toolName: string;\n /** Normalized action type category */\n actionType: CachedActionType;\n /** Workflow node this action belongs to */\n nodeId: string;\n /** Stable selector strategies (multiple for fallback) */\n selectors?: SelectorStrategy;\n /** Original tool arguments (sanitized - no ephemeral refs) */\n args: Record<string, unknown>;\n /** Assertion to verify during replay (for assertion nodes) */\n assertion?: AssertionSpec;\n /** Wait strategy to execute before this action */\n waitBefore?: WaitStrategy;\n /** Key from variable registry for variable-aware data substitution */\n variableKey?: string;\n /** Original element description for debugging */\n elementDescription?: string;\n /** Duration of original action in ms (for timing hints) */\n originalDurationMs?: number;\n /** Flag for actions that require agent fallback (missing selectors) */\n requiresAgent?: boolean;\n /** Who authored the active selector: deterministic or agentic */\n selectorAuthor?: SelectorAuthor;\n /** Why the AI locator agent failed, if attempted but unsuccessful */\n selectorAgentFailureReason?: string;\n /** True for injected annotations (vs browser actions) */\n isAnnotation?: boolean;\n /** Annotation type (e.g., 'step-completed', 'milestone') */\n annotationType?: string;\n /** Hex color or named color for annotation UI rendering */\n annotationColor?: string;\n /**\n * Maps arg paths to their original macro expressions.\n * At replay time, these are resolved fresh instead of using the literal in args.\n * e.g. { \"text\": \"$UUID()\", \"fields.1.value\": \"user-$UUID()@test.com\" }\n */\n macroBindings?: Record<string, string>;\n /**\n * Maps arg paths to variable registry keys for fill_form per-field substitution.\n * At replay time, these are resolved fresh from the variable bag.\n * e.g. { \"fields.0.value\": \"company\", \"fields.1.value\": \"display_name\" }\n */\n variableBindings?: Record<string, string>;\n /** For decomposed fill_form fields: the field index within the original fill_form */\n decomposedFieldIndex?: number;\n /**\n * Ref-less fast-drive only (`actionType === 'run_code'`): the self-contained\n * Playwright snippet the L2 agent authored to reproduce this step. Executed at\n * replay via the shared run_code primitive with fresh `vars`/`macro()`/`attach()`\n * bindings, so it carries no baked literals (no `macroBindings`/`variableBindings`).\n */\n code?: string;\n /**\n * Server-authored post-state contract for a composite interaction-recipe\n * `run_code` action. The code runs the recipe body and invokes the replay\n * oracle binding before returning, preserving one recipeRef → one action →\n * one classified step outcome.\n */\n recipeOracle?: CachedRecipeOracle;\n /**\n * Narrow durability exception for agent-authored positional code.\n * The strict gate accepts it only when `codeSha256` matches the exact current\n * code and no non-overridable anchor is present. It does not affect lifecycle\n * state: minted rows remain unverified until the normal prove pass succeeds.\n */\n durabilityAttestation?: BoundCacheCodeDurabilityAttestation;\n /**\n * Layer 4 cache-durable multi-tab replay: the durable descriptor of the tab\n * this action ran on, stamped at record time when the context had >1 open\n * tab. At replay the loop re-resolves the active page to this predicate before\n * executing the action. Absent ⇒ single-tab provenance ⇒ replay is unchanged.\n */\n activeTab?: DurableTabRef;\n /** Record-time tab confidence. See {@link TabProvenance}. */\n tabProvenance?: TabProvenance;\n};\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Cache Formation Diagnostics\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport type CacheFormationStepMatchStrategy =\n | 'actionRef'\n | 'heuristic'\n | 'agent'\n | 'cacheCode'\n | 'captureSaveAs'\n | 'assertionId'\n | 'retained'\n | 'gap'\n | 'missing';\n\nexport type CacheFormationPlaybookStepDiagnostic = {\n order: number;\n action: string;\n target: string;\n actionRefCount: number;\n resolvedActionRefCount: number;\n matchStrategy: CacheFormationStepMatchStrategy;\n matchedActionIndex?: number;\n};\n\nexport type CacheFormationPlaybookDiagnostics = {\n stepCount: number;\n stepsWithActionRefs: number;\n stepsWithoutActionRefs: number;\n stepsWithResolvedActionRefs: number;\n stepsWithUnresolvedActionRefs: number;\n stepsMatchedByActionRef: number;\n stepsMatchedHeuristically: number;\n gapCount: number;\n steps: CacheFormationPlaybookStepDiagnostic[];\n};\n\nexport type CacheFormationActionDiagnostic = {\n index: number;\n actionType: string;\n elementDescription?: string;\n hasSelectors: boolean;\n hasPlaywrightLocator: boolean;\n selectorAuthor?: SelectorAuthor;\n selectorAgentFailureReason?: string;\n requiresAgent: boolean;\n selectorTaskCount: number;\n};\n\nexport type CacheFormationLocatorDiagnostics = {\n totalActions: number;\n selectorCapableActions: number;\n actionsWithSelectors: number;\n actionsWithPlaywrightLocator: number;\n agenticLocatorActions: number;\n deterministicLocatorActions: number;\n requiresAgentActions: number;\n actionsWithSelectorTasks: number;\n actions: CacheFormationActionDiagnostic[];\n};\n\nexport type CacheFormationSelectorAgentTaskSource = 'live-queue' | 'proactive';\n\nexport type CacheFormationSelectorAgentTaskDiagnostic = {\n actionIndex: number;\n fieldIndex?: number;\n source: CacheFormationSelectorAgentTaskSource;\n hasHeuristicSelectors: boolean;\n};\n\nexport type CacheFormationSelectorAgentStatus =\n | 'not-needed'\n | 'pending'\n | 'queued'\n | 'completed'\n | 'failed';\n\nexport type CacheFormationSelectorAgentDiagnostics = {\n liveQueueTaskCount: number;\n proactiveTaskCount: number;\n totalTaskCount: number;\n actionTaskCount: number;\n fieldTaskCount: number;\n bundleUploaded: boolean;\n drainJobEnqueued: boolean;\n status: CacheFormationSelectorAgentStatus;\n successCount?: number;\n failureCount?: number;\n failureReasons?: string[];\n tasks: CacheFormationSelectorAgentTaskDiagnostic[];\n};\n\nexport type CacheFormationDiagnostics = {\n playbook: CacheFormationPlaybookDiagnostics;\n locators: CacheFormationLocatorDiagnostics;\n selectorAgent: CacheFormationSelectorAgentDiagnostics;\n};\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Cache Metadata Types\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Metadata about a workflow cache for eligibility and debugging\n */\nexport type WorkflowCacheMetadata = {\n /** Whether the inbox tool was used (disqualifies caching) */\n usedInboxTool: boolean;\n /** Total number of cached actions */\n actionCount: number;\n /** Node types that were executed */\n nodeTypes: string[];\n /** Whether all nodes completed successfully */\n allNodesSuccessful: boolean;\n /** Keys from inputValues used for dynamic data */\n inputValueKeys: string[];\n};\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Eligibility Types\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Result of cache eligibility check\n */\nexport type CacheEligibilityResult = { eligible: true } | { eligible: false; reason: string };\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Replay Types\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Per-action timing breakdown for cache replay telemetry.\n * Captures where time is spent within each replayed action.\n */\nexport interface ReplayActionTiming {\n /** Action index within the replay sequence */\n index: number;\n /** Type of action (click, type, navigate, etc.) */\n actionType: string;\n /** Time spent in page stabilization (DOM delay + network idle + overlay check) */\n stabilizationMs: number;\n /** Time spent in pre-action wait strategy */\n waitStrategyMs: number;\n /** Time spent resolving the element (selector fallback chain + visibility wait) */\n resolutionMs: number;\n /** Subset of resolutionMs spent in verifyElementReady visibility wait */\n visibilityWaitMs: number;\n /** Subset of visibilityWaitMs spent waiting for position to stabilize (animations) */\n stabilityWaitMs: number;\n /** Time spent in the Playwright action itself (click, fill, etc.) */\n actionMs: number;\n /** Total wall-clock time for this action */\n totalMs: number;\n /** Whether this action was retried */\n retried?: boolean;\n /** Which selector strategy resolved the element */\n selectorUsed?: string;\n /**\n * Locator author that resolved (or attempted to resolve) the element.\n * 'deterministic' for role/testId/label/text/css strategies; 'agentic' for\n * the selector-agent-authored Playwright locator. Powers the cache-telemetry\n * deterministic-vs-agentic step success breakdown.\n */\n selectorAuthor?: SelectorAuthor;\n /**\n * Whether this action completed successfully during replay. False for the\n * action that triggered a replay failure; undefined when not classified.\n */\n actionSucceeded?: boolean;\n /** Short reason when actionSucceeded === false (mirrors the step cacheOutcomeReason). */\n failureReason?: string;\n /** Time spent waiting for a disabled element to become enabled (pre-click guard) */\n enabledWaitMs?: number;\n /** Pre-click actionability diagnostics (present when click fell back to coordinates) */\n clickDiagnostics?: {\n visible: boolean;\n enabled: boolean;\n stable: boolean;\n receivesPointerEvents: boolean;\n coveringElement?: { tag: string; className: string; id: string };\n /** True when pointer-event interception was detected before calling locator.click() */\n proactiveCoordinateClick?: boolean;\n };\n /** Debug-mode snapshot of the click target element state at click time (CACHE_REPLAY_DEBUG=true) */\n clickTargetState?: {\n tagName: string;\n outerHTML: string;\n disabled: boolean | null;\n ariaDisabled: string | null;\n className: string;\n id: string;\n };\n}\n\n/** Result of an inline assertion evaluation during replay */\nexport interface InlineAssertionResult {\n assertionId: string;\n condition: string;\n severity: AssertionSeverity;\n passed: boolean;\n reason?: string;\n source: 'cache' | 'agent';\n /** The assertion mechanism type, when available from the AssertionSpec */\n assertionType?: AssertionType;\n /** L2 verification verdict. When 'uncertain', Phase 3 cascades to L3 for re-evaluation. */\n verdict?: 'passed' | 'failed' | 'uncertain';\n /** Deterministic observed pass that could not be encoded after an attempted browser_assert. */\n downgradedFromDeterministic?: boolean;\n /**\n * True when this confirmed-failed verdict caused a cascade to a fresh agent\n * and is open to re-evaluation against the live DOM. Disputed results are\n * rendered in the agent prompt as \"re-evaluate and report\" instead of\n * \"do NOT re-evaluate\", and the fresh agent's verdict supersedes this one.\n *\n * EPHEMERAL prompt-composition hint: set only on the L2 assertion-eject\n * handoff path, consumed only by the prompt renderer, never persisted to\n * cache rows or step metadata. Do not gate runtime logic on it — the\n * runtime supersede keys off `source`, not this flag.\n */\n disputed?: boolean;\n /** Short human-readable description supplied by the agent at browser_assert time */\n label?: string;\n}\n\n/**\n * Normalized assertion result for non-blocking assertion accumulation.\n * Persisted to workflowRunSteps.metadataJson.assertionResults.\n */\nexport interface WorkflowAssertionResult {\n /** Assertion key assigned by the orchestrator (e.g., \"assert-0\", \"assert-1\") */\n key: string;\n /** Workflow node this assertion belongs to */\n nodeId: string;\n /** Workflow run step ID */\n stepId: string;\n /** Whether the assertion originated from a standalone assertion node or an inline assertion on an action node */\n origin: 'assertion_node' | 'inline_assertion';\n /** The human-readable assertion condition */\n condition: string;\n /** The assertion mechanism used */\n assertionType?: AssertionType | 'agentic';\n /** What a failure means: fatal halts the run, fail marks it failed but continues, warn logs only */\n severity: AssertionSeverity;\n /** Whether the assertion passed */\n passed: boolean;\n /**\n * False when the assertion was configured on the node but never evaluated\n * because the step failed before assertions ran. Omitted/true = evaluated.\n * Not-checked results are excluded from pass/warn/failure counts.\n */\n checked?: boolean;\n /** Human-readable reason for failure */\n reason?: string;\n /** How the assertion was evaluated */\n source: 'cache' | 'L2' | 'agent';\n /** ISO-8601 timestamp of evaluation */\n evaluatedAt: string;\n /** Source inline assertion ID for correlation back to node config */\n inlineAssertionId?: string;\n /** Short human-readable description supplied by the agent at browser_assert time */\n label?: string;\n /**\n * True when the agent judged a non-agentic-flagged inline assertion agentically,\n * because the condition is not programmatically assertable (e.g. vision-based\n * claim). Signal that the assertion should be re-authored as agentic; never set\n * by the browser/cache path.\n */\n downgradedFromDeterministic?: boolean;\n /**\n * Open issue linked to this assertion (`issues.subject_assertion_key === key`)\n * at evaluation time. Set only on failing results. An open linked issue is the\n * whole mechanism — there is no separate \"known issue\" concept.\n */\n issueId?: string;\n}\n\n/**\n * Summary of assertion results for a workflow run.\n * Persisted to workflowRuns.metadataJson.assertionSummary.\n * Derived from accumulated WorkflowAssertionResult records at finalization.\n */\nexport interface WorkflowAssertionSummary {\n total: number;\n passed: number;\n warnings: number;\n failures: number;\n /** Assertions configured on a failed step that were never evaluated (step broke before assertions ran). */\n notChecked?: number;\n /** True when any fail- or fatal-severity assertion failed (determines run failure) */\n hasFailSeverityFailures: boolean;\n /** Step ID of the first fail/fatal-severity failure (for UI navigation) */\n firstFailureStepId?: string;\n /**\n * True when the run failed ONLY because of assertions and every failing\n * assertion carries an open linked issue (`WorkflowAssertionResult.issueId`).\n * Written once at finalization, only ever `true`; absence means false. The\n * run still fails — this waives promotion/eligibility, never the result.\n */\n issueCovered?: boolean;\n}\n\n/**\n * Result of cache replay execution\n */\nexport type ReplayResult =\n | {\n success: true;\n evaluationOutcome: AssertionEvaluationOutcome;\n durationMs: number;\n actionTimings?: ReplayActionTiming[];\n /** Time spent in pre-replay page readiness wait (ms) */\n preReplayWaitMs?: number;\n /** Inline assertion results evaluated during replay (pass and fail) */\n assertionResults?: InlineAssertionResult[];\n /** Non-blocking capture failures for opportunistic captures */\n captureWarnings?: CacheCaptureWarning[];\n }\n | {\n success: false;\n evaluationOutcome: AssertionEvaluationOutcome;\n failedAtIndex: number;\n error: string;\n /** Whether to fall back to agent execution */\n shouldFallback: boolean;\n /** Classification of the failure for cache invalidation decisions */\n failureCategory?:\n | 'timing_hidden'\n | 'overlay_intercepted'\n | 'element_absent'\n | 'structural'\n | 'network'\n | 'other';\n /** Per-action timing for actions completed before failure */\n actionTimings?: ReplayActionTiming[];\n /** Time spent in pre-replay page readiness wait (ms) */\n preReplayWaitMs?: number;\n /** Inline assertion results collected before the failure */\n assertionResults?: InlineAssertionResult[];\n /** Capture warnings collected before the failure */\n captureWarnings?: CacheCaptureWarning[];\n };\n\n/**\n * Element info extracted from accessibility snapshot\n */\nexport type ElementInfo = {\n /** Ephemeral ref from snapshot (e.g., 'e5') */\n ref: string;\n /** ARIA role */\n role?: string;\n /** Accessible name */\n accessibleName?: string;\n /** Text content */\n textContent?: string;\n /** Text segments (individual visible text nodes) */\n textSegments?: string[];\n /** Aria label attribute */\n ariaLabel?: string;\n /** Label text (from <label>, aria-labelledby, or aria-label) */\n labelText?: string;\n /** Placeholder text (inputs) */\n placeholder?: string;\n /** Title attribute */\n title?: string;\n /** Alt text (images) */\n altText?: string;\n /** Form field name attribute */\n nameAttr?: string;\n /** data-testid or similar */\n dataTestId?: string;\n /** HTML tag name */\n tagName?: string;\n /** CSS class name */\n className?: string;\n /** Element ID */\n id?: string;\n /** Parent element role for context */\n parentRole?: string;\n /** Parent element name for context */\n parentName?: string;\n /** True when parentName was extracted from a child heading, not the parent's own accessible name */\n parentNameFromHeading?: boolean;\n /** Center X coordinate (from enriched snapshot) */\n centerX?: number;\n /** Center Y coordinate (from enriched snapshot) */\n centerY?: number;\n /** Frame context if element is inside an iframe or shadow DOM */\n frameContext?: FrameContext;\n\n // ─────────────────────────────────────────────────────────────────────────────\n // Data attributes for disambiguation\n // ─────────────────────────────────────────────────────────────────────────────\n\n /** data-item-index or data-selection-index for list items */\n itemIndex?: number;\n /** aria-rowindex for grid/table rows */\n ariaRowIndex?: number;\n /** data-automationid for Microsoft controls */\n automationId?: string;\n /** Generic data-* attributes for disambiguation */\n dataAttributes?: Record<string, string>;\n\n /** Child img alt text for disambiguation of generic containers */\n childImgAlt?: string;\n};\n","/**\n * Shared zod schemas for workflow node config primitives.\n *\n * Lives in @chatsdet/types so both `apps/api` (server-side validation +\n * tool schemas) and `packages/canary-cli` (the spec compiler) can import\n * the same source of truth without canary-cli taking a dependency on apps/api.\n *\n * @module workflow-node-schemas\n */\n\nimport { z } from 'zod';\nimport { ValidationOperatorSchema } from './api-sequence.js';\n\nexport const FormFieldSchema = z.object({\n name: z\n .string()\n .describe('Unique field identifier (snake_case, e.g., \"search_query\", \"recipient_email\")'),\n label: z.string().describe('Human-friendly label (e.g., \"Search Query\", \"Recipient Email\")'),\n type: z\n .enum([\n 'text',\n 'email',\n 'password',\n 'number',\n 'textarea',\n 'select',\n 'checkbox',\n 'multiselect',\n 'file',\n ])\n .optional()\n .describe('Input type, defaults to \"text\"'),\n required: z.boolean().optional().describe('Whether this field is required'),\n value: z.string().describe('The value to use for this field'),\n options: z\n .array(z.object({ value: z.string(), label: z.string() }))\n .optional()\n .describe('If type is select/multiselect, provide allowed options here'),\n});\n\n/**\n * Assertion severity — what happens when the assertion fails:\n * - `fatal`: the test must end now. The run halts immediately at this step.\n * - `fail`: the test may continue. The step is marked failed and the run\n * finishes marked failed, but later steps still execute.\n * - `warn`: the test should continue. The failure is recorded as a warning\n * and the run still passes.\n */\nexport const AssertionSeverityEnum = z.enum(['warn', 'fail', 'fatal']);\n\nexport type AssertionSeverity = z.infer<typeof AssertionSeverityEnum>;\n\nexport const InlineAssertionSchema = z.object({\n condition: z.string().describe('What to verify after the action'),\n severity: AssertionSeverityEnum.describe(\n 'fatal = stop the test immediately on failure, fail = mark the run failed but keep going, warn = log a warning and continue (run still passes)'\n ),\n agentic: z\n .boolean()\n .optional()\n .describe('true if requires visual/complex evaluation that cannot be cached'),\n});\n\n/**\n * Subject of a deterministic condition predicate — the left-hand operand.\n *\n * - `variable`: a workflow variable referenced by its input name (resolved from\n * the run's variable registry as `$var.<name>` at evaluation time).\n * - `acting_as`: the credential identity currently logged in. Compared against a\n * chosen credential template id; resolved from `currentActingAsTemplateId` on\n * the run's workflow state.\n */\nexport const ConditionSubjectSchema = z.discriminatedUnion('kind', [\n z.object({\n kind: z.literal('variable'),\n name: z.string().describe('Input/variable name, e.g. \"count\" for $var.count'),\n }),\n z.object({\n kind: z.literal('acting_as'),\n }),\n]);\n\nexport type ConditionSubject = z.infer<typeof ConditionSubjectSchema>;\n\n/**\n * A single typed comparison that decides a condition node's branch\n * deterministically — no LLM agent. When a condition node has a predicate it is\n * evaluated in the orchestrator; without one it falls back to agentic evaluation\n * of `conditionDescription`. v1 is a single comparison; the shape leaves room to\n * grow into compound AND/OR later.\n */\nexport const ConditionPredicateSchema = z.object({\n subject: ConditionSubjectSchema,\n operator: ValidationOperatorSchema,\n /**\n * Right-hand operand. For a `variable` subject this is a literal (or `$var`\n * reference); for `acting_as` it is a credential template id. Omitted for the\n * unary operators `is_set` / `is_not_set`.\n */\n value: z.string().optional(),\n});\n\nexport type ConditionPredicate = z.infer<typeof ConditionPredicateSchema>;\n\nexport const NodeTypeEnum = z.enum([\n 'login',\n 'navigate',\n 'action',\n 'assertion',\n 'seed',\n 'setup',\n 'wait',\n 'condition',\n 'api_sequence',\n 'end',\n 'notification',\n 'audio',\n 'audio-assertion',\n 'visual_checkpoint',\n 'sandbox_action',\n 'mcp',\n 'call_flow',\n]);\n\nexport type NodeType = z.infer<typeof NodeTypeEnum>;\nexport type FormField = z.infer<typeof FormFieldSchema>;\nexport type InlineAssertion = z.infer<typeof InlineAssertionSchema>;\n","/**\n * API Sequence Types\n *\n * Types for configuring and executing API sequence nodes in workflows.\n * An API sequence node executes multiple API calls in order, with support\n * for template variables that reference previous step responses.\n */\n\nimport { z } from 'zod';\n\n/**\n * Comparison operators shared by API-sequence validations and deterministic\n * workflow condition predicates. Single source of truth — both the validation\n * schemas and the condition predicate schema derive from this enum.\n */\nexport const ValidationOperatorSchema = z.enum([\n 'equals',\n 'not_equals',\n 'gt',\n 'lt',\n 'gte',\n 'lte',\n 'contains',\n 'not_contains',\n 'is_set',\n 'is_not_set',\n]);\n\nexport type ValidationOperator = z.infer<typeof ValidationOperatorSchema>;\n\n/**\n * Pure operator evaluation shared by API-sequence validations and deterministic\n * workflow conditions. `actual` / `expected` are pre-resolved string values\n * (`undefined` means \"not set\"). Returns whether the comparison passed plus a\n * human-readable message. Numeric operators coerce both operands via `Number()`;\n * non-numeric input fails with a descriptive message rather than throwing.\n */\nexport function evaluateOperator(args: {\n actual: string | undefined;\n operator: ValidationOperator;\n expected?: string;\n /** Label for the left operand in messages (e.g. a ref or variable name). */\n label?: string;\n}): { passed: boolean; message: string } {\n const { actual, operator, expected } = args;\n const label = args.label ?? 'value';\n const isSet = actual !== undefined && actual !== null;\n\n switch (operator) {\n case 'is_set':\n return {\n passed: isSet,\n message: isSet ? `${label} is set (value: ${actual})` : `${label} is not set`,\n };\n case 'is_not_set':\n return {\n passed: !isSet,\n message: !isSet ? `${label} is not set` : `${label} is set (value: ${actual})`,\n };\n case 'equals':\n return {\n passed: actual === expected,\n message:\n actual === expected\n ? `${label} equals \"${expected}\"`\n : `Expected ${label} to equal \"${expected}\", got \"${actual}\"`,\n };\n case 'not_equals':\n return {\n passed: actual !== expected,\n message:\n actual !== expected\n ? `${label} does not equal \"${expected}\"`\n : `Expected ${label} to not equal \"${expected}\"`,\n };\n case 'contains': {\n const pass =\n isSet && expected !== undefined && expected !== null && actual.includes(expected);\n return {\n passed: pass,\n message: pass\n ? `${label} contains \"${expected}\"`\n : `Expected ${label} to contain \"${expected}\", got \"${actual}\"`,\n };\n }\n case 'not_contains': {\n const pass =\n !isSet || expected === undefined || expected === null || !actual.includes(expected);\n return {\n passed: pass,\n message: pass\n ? `${label} does not contain \"${expected}\"`\n : `Expected ${label} to not contain \"${expected}\", got \"${actual}\"`,\n };\n }\n case 'gt':\n case 'lt':\n case 'gte':\n case 'lte': {\n const numActual = Number(actual);\n const numExpected = Number(expected);\n if (isNaN(numActual) || isNaN(numExpected)) {\n return {\n passed: false,\n message: `Cannot compare non-numeric values: \"${actual}\" ${operator} \"${expected}\"`,\n };\n }\n const ops = {\n gt: (a: number, b: number) => a > b,\n lt: (a: number, b: number) => a < b,\n gte: (a: number, b: number) => a >= b,\n lte: (a: number, b: number) => a <= b,\n } as const;\n const passed = ops[operator](numActual, numExpected);\n return {\n passed,\n message: passed\n ? `${label} (${numActual}) ${operator} ${numExpected}`\n : `Expected ${label} (${numActual}) ${operator} ${numExpected}`,\n };\n }\n default:\n return { passed: false, message: `Unknown operator: ${operator as string}` };\n }\n}\n\nexport interface ApiSequenceValidation {\n /** Reference to a step value: \"stepId.path.to.value\" (e.g., \"step3.data.accountId\") */\n left: string;\n operator: ValidationOperator;\n /** Literal value or step reference. Not needed for is_set/is_not_set. */\n right?: string;\n description?: string;\n}\n\nexport interface ApiSequenceValidationResult {\n validation: ApiSequenceValidation;\n passed: boolean;\n actualValue?: string;\n expectedValue?: string;\n message?: string;\n}\n\n/**\n * A plain-text part of a multipart/form-data step body.\n */\nexport interface ApiSequenceFormTextPart {\n kind: 'text';\n /** Multipart field name. */\n name: string;\n /** Value template — supports the same `{{step.response.x}}` / `$var.` / macro syntax as bodyTemplate. */\n value: string;\n}\n\n/**\n * A file part of a multipart/form-data step body, sourced from an attachment.\n *\n * Names an attachment by UUID. Its bytes are staged per run\n * (`createAdhocAttachmentStagingRegistry`) and resolved into the request at\n * execution time — a step containing a well-formed file part is executable.\n * See `validate-config.ts`.\n */\nexport interface ApiSequenceFormFilePart {\n kind: 'file';\n /** Multipart field name. */\n name: string;\n /** UUID of the attachment (`attachments` table) providing this part's bytes. */\n attachmentId: string;\n /** Filename sent in the part's Content-Disposition. Defaults to the attachment's own filename. */\n filename?: string;\n}\n\nexport type ApiSequenceFormPart = ApiSequenceFormTextPart | ApiSequenceFormFilePart;\n\n/**\n * A single part of a resolved multipart/form-data request body — text or file,\n * with all templates/macros already substituted and file bytes already\n * fetched. This is the shape every transport builds its native representation\n * from (undici `FormData`, Playwright `multipart:`, …).\n *\n * Passed as `PreparedFormPart[]`, never as a `FormData` instance (Decision\n * 11): `FormData` doesn't survive the remote-broker's JSON transport\n * (`local-browser/remote-browser-client.ts`) and isn't what Playwright's\n * `APIRequestContext` accepts. File bytes are base64-encoded so they survive\n * that same JSON transport uniformly with text parts.\n */\nexport interface PreparedFormTextPart {\n kind: 'text';\n name: string;\n value: string;\n}\n\nexport interface PreparedFormFilePart {\n kind: 'file';\n name: string;\n filename: string;\n mimeType: string;\n /** Base64-encoded file bytes. */\n data: string;\n}\n\nexport type PreparedFormPart = PreparedFormTextPart | PreparedFormFilePart;\n\n/**\n * Human-readable, redacted summary of a resolved multipart body — never the\n * raw bytes. Used anywhere a step's request body is surfaced as a string\n * (`ApiSequenceStepResult.requestBody`, exports, run display) so a multipart\n * request never gets its file bytes logged, stored, or JSON-parsed as if it\n * were a JSON/text body.\n */\nexport function summarizePreparedFormParts(parts: readonly PreparedFormPart[]): string {\n const partSummaries = parts.map((part) =>\n part.kind === 'text'\n ? `\"${part.name}\" (text)`\n : `\"${part.name}\" (file: ${part.filename}, ${part.mimeType})`\n );\n return `[multipart/form-data: ${parts.length} part${parts.length === 1 ? '' : 's'} — ${partSummaries.join(', ')}]`;\n}\n\n/**\n * A single step in an API sequence.\n */\nexport interface ApiSequenceStep {\n /**\n * Unique identifier for this step (used for variable references).\n * If not provided, defaults to `step{index}`.\n */\n stepId?: string;\n\n /**\n * Reference to an API request in the api_requests table.\n * Provides default method, URL pattern, and example request/response data.\n * Empty string indicates a custom step whose request shape comes from\n * method/urlTemplate/bodyTemplate/headersTemplate instead of the catalog.\n */\n apiRequestId: string;\n\n /**\n * Which credential to use for this request.\n * If null, the request is made without authentication.\n */\n credentialId: string | null;\n\n /**\n * Authenticate via a credential template/persona resolved against the run's\n * environment (mirrors a login node's `credentialSource: from_template`).\n * Takes precedence over `credentialId` when set — the executor resolves it to\n * the concrete credential bound for the environment (e.g. the sandbox-minted\n * credential), so an api_sequence isn't pinned to one static per-env id.\n */\n credentialTemplateId?: string | null;\n\n /**\n * Author-facing name of the credential template/persona. Resolved to\n * `credentialTemplateId` server-side on save (and populated from the id on\n * pull) so a `.cofactor.md` sidecar can name the persona instead of a raw uuid.\n * Not read at execution time — the executor uses `credentialTemplateId`.\n */\n credentialTemplateName?: string | null;\n\n /**\n * Override the HTTP method (GET, POST, PUT, PATCH, DELETE, etc.).\n * If not specified, uses the method from the referenced API request.\n */\n method?: string;\n\n /**\n * URL template with optional variable placeholders.\n * Supports {{stepN.response.path.to.value}} syntax.\n * If not specified, backfilled from the API catalog at execution time.\n *\n * @example \"https://api.example.com/users/{{step1.response.data.userId}}\"\n */\n urlTemplate?: string;\n\n /**\n * Query parameters as key-value pairs. Values support template variables.\n * These are merged into the URL at execution time (appended to any existing query string).\n */\n queryParams?: Array<{ key: string; value: string }>;\n\n /**\n * Request body template (JSON string) with optional variable placeholders.\n * Supports {{stepN.response.path.to.value}} syntax.\n * If null/undefined, uses the sample from the API request for POST/PUT/PATCH,\n * or sends no body for GET/DELETE.\n */\n bodyTemplate?: string | null;\n\n /**\n * Request header overrides with optional variable placeholders.\n * These are merged with headers from the credential and API request sample.\n */\n headersTemplate?: Record<string, string> | null;\n\n /**\n * Name to store this step's response under for use in subsequent steps.\n * Defaults to `step{index}` if not specified.\n *\n * @example \"createUserResponse\"\n */\n responseVariableName?: string;\n\n /**\n * Expected HTTP status codes for validation.\n * If specified, the step fails if the response status is not in this list.\n * If not specified, any successful response (2xx) is accepted.\n *\n * @example [200, 201]\n */\n expectedStatusCodes?: number[];\n\n /**\n * Optional description for this step (for UI display).\n */\n description?: string;\n\n /**\n * Assertions to run after this step's HTTP request completes.\n * Each validation checks a value from step results against a condition.\n */\n validations?: ApiSequenceValidation[];\n\n /** Require all response validations (the default), or at least one. HTTP status must still pass. */\n validationMode?: 'all' | 'any';\n\n /**\n * multipart/form-data parts for this step's request body. Mutually\n * exclusive with `bodyTemplate` (enforced in `validate-config.ts`) — there\n * is no separate `bodyMode` discriminator; a step is multipart exactly when\n * `formParts` is present and non-empty. A file part names an attachment by\n * UUID, whose bytes are staged per run and resolved into the request at\n * execution time.\n */\n formParts?: ApiSequenceFormPart[];\n}\n\n/**\n * A custom step (empty `apiRequestId`) has no catalog URL to fall back on, so\n * it must carry its own `urlTemplate`. The single rule behind the server's\n * STEP_CUSTOM_URL_REQUIRED and the editor's save guard.\n */\nexport function isCustomStepMissingUrl(\n step: Pick<Partial<ApiSequenceStep>, 'apiRequestId' | 'urlTemplate'>\n): boolean {\n return step.apiRequestId === '' && (step.urlTemplate ?? '').trim().length === 0;\n}\n\n/**\n * A variable export declaration that extracts a value from a step's response\n * and registers it in the workflow's variable registry for downstream steps.\n */\nexport interface ApiSequenceExport {\n /** Variable name using resource_field convention (e.g., \"project_id\", \"project_name\", \"user_email\") */\n variableName: string;\n /** Which step's response to extract from */\n stepId: string;\n /** Dot-notation path in the JSON body (e.g., \"data.id\") */\n responsePath: string;\n /** Whether to extract from the request body or response body. Defaults to 'response'. */\n source?: 'request' | 'response';\n /** Description for UI display */\n description?: string;\n}\n\n/**\n * Configuration for an API sequence workflow node.\n */\nexport interface ApiSequenceConfig {\n /**\n * Ordered list of API calls to execute.\n */\n steps: ApiSequenceStep[];\n\n /**\n * Human-readable description of what this sequence does.\n */\n description?: string;\n\n /**\n * If true, stop execution on the first failed step.\n * If false, continue executing remaining steps even if one fails.\n * Defaults to true.\n */\n stopOnFailure?: boolean;\n\n /**\n * Variable exports to extract from step responses and register\n * in the workflow's variable registry for downstream nodes.\n */\n exports?: ApiSequenceExport[];\n}\n\n/**\n * Result of executing a single API sequence step.\n */\nexport interface ApiSequenceStepResult {\n stepId: string;\n success: boolean;\n statusCode?: number;\n statusText?: string;\n headers?: Record<string, string>;\n body?: string | null;\n durationMs?: number;\n error?: string;\n timestamp: string;\n /** The fully resolved URL that was actually requested */\n resolvedUrl?: string;\n /** The HTTP method used */\n method?: string;\n /**\n * The request body sent (if any). For a multipart request this is a\n * redacted summary (`summarizePreparedFormParts`), never the raw bytes —\n * see `requestBodyKind`.\n */\n requestBody?: string | null;\n /**\n * Discriminates `requestBody`'s shape. `'multipart'` means `requestBody` is\n * a redacted summary string, not JSON/text — consumers (export\n * `source:'request'`, run display) must not attempt to parse it as the\n * literal request payload.\n */\n requestBodyKind?: 'raw' | 'multipart';\n /** The request headers sent */\n requestHeaders?: Record<string, string>;\n /** Results of validation assertions run after this step */\n validationResults?: ApiSequenceValidationResult[];\n}\n\n/**\n * Verification result comparing declared seed outputs against actually exported variables.\n */\nexport interface SeedOutputVerification {\n /** Whether all declared seed outputs were satisfied by exports */\n allSatisfied: boolean;\n /** dataKeys that were found in exportedVariables */\n satisfied: string[];\n /** dataKeys that were declared but missing from exportedVariables */\n missing: string[];\n}\n\n/**\n * How an api_sequence_runs row was initiated. Drives the \"trigger\" chip in the\n * flow designer's Runs panel and lets us distinguish manual designer-button\n * runs from runs spawned by a workflow execution or test suite.\n */\nexport type ApiSequenceTriggeredVia = 'designer' | 'workflow' | 'seed' | 'test-suite';\n\n/**\n * Outbound HTTP header names attached to every request issued by an\n * api_sequence step (and to Playwright browser contexts when the run\n * originates from a workflow). Together they reconstruct a deep link to the\n * failing step:\n * `https://app.trycanary.ai/flows/{flowId}?runId={runId}`\n *\n * NOTE: these headers are sent to **third-party hosts** that the step targets.\n * Values are opaque UUIDs (workflows.id / workflow_runs.id) — no credentials,\n * no PII — but be aware they cross our perimeter intentionally so customers\n * can correlate canary traffic in their own server logs.\n */\nexport const CANARY_TRACE_HEADERS = {\n flowId: 'x-canary-flow-id',\n runId: 'x-canary-run-id',\n} as const;\n\n/**\n * Build the canary trace header pair from a run's identifiers. Returns an\n * empty object when no context is available so callers can spread freely.\n */\nexport function buildCanaryTraceHeaders(\n context: { flowId: string; runId: string } | undefined\n): Record<string, string> {\n if (!context) return {};\n return {\n [CANARY_TRACE_HEADERS.flowId]: context.flowId,\n [CANARY_TRACE_HEADERS.runId]: context.runId,\n };\n}\n\n/**\n * Result of executing an entire API sequence.\n */\nexport interface ApiSequenceResult {\n success: boolean;\n stepResults: ApiSequenceStepResult[];\n totalDurationMs: number;\n failedStepId?: string;\n /** Why the sequence failed when no step result carries it (e.g. a sequence with no steps). */\n errorMessage?: string;\n /** Variables extracted from step responses via the exports config */\n exportedVariables?: Record<string, string>;\n /** Verification of declared seed outputs against actual exports (seed workflows only) */\n seedOutputVerification?: SeedOutputVerification;\n}\n","/**\n * Where a pull request's Sandboxes v2 preview page lives in the web app. The\n * GitHub deployment status and `cofactor sandbox preview` both link it; the\n * web route (`/sandboxes/previews/$applicationId/$prNumber`) serves it.\n */\nexport const sandboxPreviewPagePath = (applicationId: string, prNumber: number): string =>\n `/sandboxes/previews/${applicationId}/${prNumber}`;\n","/**\n * Credential Types\n *\n * Strongly-typed credential data structures for each auth provider\n */\n\nimport { z } from 'zod';\n\nexport type AuthProvider =\n | 'basic'\n | 'google'\n | 'google_oauth'\n | 'github'\n | 'microsoft'\n | 'api_key'\n | 'twitter'\n | 'custom'\n | 'api_login'\n | 'api_basic'\n | 'test_sign_in';\n\nexport type CredentialRole = 'admin' | 'user' | 'viewer' | 'custom';\n\n/**\n * Custom HTTP headers for browser automation.\n * Stored encrypted alongside credential data.\n * Useful for bypassing bot detection (e.g., User-Agent, Accept-Language).\n */\nexport type CustomHttpHeaders = Record<string, string>;\n\n/**\n * HTTP Basic Authentication credentials for browser-level auth popups.\n * Passed to Playwright via browser.newContext({ httpCredentials }).\n */\nexport type HttpCredentials = {\n username: string;\n password: string;\n};\n\n// Individual credential data types\n\nexport type BasicAuthCredential = {\n username: string;\n password: string;\n customHeaders?: CustomHttpHeaders;\n httpCredentials?: HttpCredentials;\n};\n\nexport type GoogleCredential = {\n email: string;\n password: string;\n customHeaders?: CustomHttpHeaders;\n httpCredentials?: HttpCredentials;\n};\n\n/**\n * Google OAuth \"minted session\" credential: instead of driving the app's Google\n * sign-in UI (impossible for an agent — third-party origin, bot detection), we\n * hold a long-lived refresh token for the test account and exchange it for a\n * fresh `id_token` at run time, then seed the result into storage state.\n *\n * `clientId` / `clientSecret` are the CUSTOMER's OAuth client — the one their\n * app validates the `id_token` `aud` claim against. They are required and have\n * no default: a token minted by any other client is rejected by the target app,\n * so falling back to a platform-owned client would only fail later and less\n * legibly.\n */\nexport type GoogleOauthCredential = {\n clientId: string;\n clientSecret: string;\n refreshToken: string;\n /** Extra scopes beyond `openid email profile`. */\n scopes?: string[];\n /** Minted-token cache. Same shape/semantics as ApiLoginCredential's cachedToken. */\n cachedIdToken?: string;\n cachedAccessToken?: string;\n cachedProfile?: GoogleOauthProfile;\n /**\n * The provider's raw token-endpoint response. Cached alongside the tokens\n * because session seeds may template over ANY of its fields\n * (`{{response.<path>}}`) — restoring only the named tokens would silently\n * break those placeholders on every cache hit.\n */\n cachedResponse?: Record<string, unknown>;\n cachedTokenExpiresAt?: string;\n cachedTokenObtainedAt?: string;\n customHeaders?: CustomHttpHeaders;\n httpCredentials?: HttpCredentials;\n};\n\n/** OpenID Connect userinfo claims we surface to session-seed templates. */\nexport type GoogleOauthProfile = {\n sub?: string;\n email?: string;\n email_verified?: boolean;\n name?: string;\n given_name?: string;\n family_name?: string;\n picture?: string;\n hd?: string;\n};\n\nexport type GithubCredential = {\n username: string;\n password: string;\n customHeaders?: CustomHttpHeaders;\n httpCredentials?: HttpCredentials;\n};\n\nexport type MicrosoftCredential = {\n email: string;\n password: string;\n customHeaders?: CustomHttpHeaders;\n httpCredentials?: HttpCredentials;\n};\n\nexport type ApiKeyCredential = {\n key: string;\n prefix?: string;\n customHeaders?: CustomHttpHeaders;\n httpCredentials?: HttpCredentials;\n};\n\nexport type TwitterCredential = {\n username: string;\n password: string;\n customHeaders?: CustomHttpHeaders;\n httpCredentials?: HttpCredentials;\n};\n\nexport type ApiLoginCredential = {\n loginFields: Record<string, string>;\n /** Encrypted headers sent only to the token endpoint (for example SOAPAction). */\n tokenRequestHeaders?: Record<string, string>;\n cachedToken?: string;\n cachedTokenExpiresAt?: string;\n cachedTokenObtainedAt?: string;\n customHeaders?: CustomHttpHeaders;\n httpCredentials?: HttpCredentials;\n};\n\nexport type ApiBasicCredential = {\n username: string;\n password: string;\n customHeaders?: CustomHttpHeaders;\n httpCredentials?: HttpCredentials;\n};\n\n/**\n * Test sign-in credential: the test user IS this credential. Cofactor's own\n * OpenID provider vouches for `email` (which must sit inside the org\n * provider's email domain) and the customer's app signs them in through its\n * real login flow — no password, no login UI driven by an agent.\n */\nexport type TestSignInCredential = {\n email: string;\n name: string;\n /**\n * Where \"Sign in with Cofactor\" starts: a path on the environment's app\n * (e.g. `/auth/cofactor`) or a full https URL when the route lives on another\n * host, such as a separate API. Absent means the environment's login page.\n */\n startUrl?: string;\n /**\n * Playwright selector for a sign-in button to click after loading the start\n * page — for apps (e.g. Auth.js) that only start a login from a form POST.\n * Absent means the start page itself redirects to Cofactor.\n */\n signInButtonSelector?: string;\n customHeaders?: CustomHttpHeaders;\n httpCredentials?: HttpCredentials;\n};\n\nexport type CustomCredential = Record<string, any> & {\n customHeaders?: CustomHttpHeaders;\n httpCredentials?: HttpCredentials;\n};\n\n// Union type for all credential data\nexport type CredentialData =\n | { provider: 'basic'; data: BasicAuthCredential }\n | { provider: 'google'; data: GoogleCredential }\n | { provider: 'google_oauth'; data: GoogleOauthCredential }\n | { provider: 'github'; data: GithubCredential }\n | { provider: 'microsoft'; data: MicrosoftCredential }\n | { provider: 'api_key'; data: ApiKeyCredential }\n | { provider: 'twitter'; data: TwitterCredential }\n | { provider: 'api_login'; data: ApiLoginCredential }\n | { provider: 'api_basic'; data: ApiBasicCredential }\n | { provider: 'test_sign_in'; data: TestSignInCredential }\n | { provider: 'custom'; data: CustomCredential };\n\n/**\n * Login validity tracking data for session duration prediction\n */\nexport type LoginValidityInfo = {\n /** When a login was last attempted */\n lastLoginAt: Date | null;\n /** When a login last succeeded */\n lastLoginSucceededAt: Date | null;\n /** When the session was detected as expired (requiring re-login) */\n nextLoginRequiredAt: Date | null;\n /** Calculated session validity duration in minutes */\n loginValidityDurationMinutes: number | null;\n /** Estimated session expiry based on last login + known validity */\n estimatedExpiresAt: Date | null;\n};\n\n/**\n * HeaderExtractor: declarative way to derive an HTTP header value from a\n * credential's saved storage state (Playwright cookies + localStorage +\n * extracted IndexedDB). Materialized at request time by the executor and\n * merged into the same `extraHeaders` map as `customHeaders`.\n *\n * Modern SPAs (Firebase Auth, Auth0, Clerk, …) stash JWTs in localStorage or\n * IndexedDB and rely on page JS to forward them as request headers. Playwright\n * APIRequestContext only carries cookies, so those headers go missing when we\n * fetch through the browser context. Extractors close that gap.\n */\nexport const tokenRefreshPolicySchema = z.discriminatedUnion('kind', [\n z.object({\n kind: z.literal('firebase'),\n refreshTokenPath: z.string().default('$.value.stsTokenManager.refreshToken'),\n expirationPath: z.string().default('$.value.stsTokenManager.expirationTime'),\n accessTokenPath: z.string().default('$.value.stsTokenManager.accessToken'),\n apiKeyPath: z.string().default('$.value.apiKey'),\n }),\n // oidc-client-ts (Keycloak, generic OIDC SPAs) keeps its user in\n // sessionStorage under `oidc.user:<authority>:<client_id>` as\n // `{ access_token, refresh_token, expires_at (epoch seconds), … }`. The\n // authority and client id come from that key and the token endpoint from the\n // authority's discovery document, so the policy needs no fields of its own.\n // Requires a sessionStorage source with such a key (see headerExtractorSchema).\n z.object({\n kind: z.literal('oidc-client-ts'),\n }),\n]);\n\nexport type TokenRefreshPolicy = z.infer<typeof tokenRefreshPolicySchema>;\n\nconst localStorageSourceSchema = z.object({\n kind: z.literal('localStorage'),\n origin: z.string().min(1),\n key: z.string().min(1),\n jsonPath: z.string().optional(),\n});\n\n// sessionStorage of the page's origin at storage-state capture time. Apps such\n// as oidc-client-ts keep their tokens here; the app re-authenticates from its\n// IdP cookies on restore, so this is captured for extraction only.\nconst sessionStorageSourceSchema = z.object({\n kind: z.literal('sessionStorage'),\n origin: z.string().min(1),\n key: z.string().min(1),\n jsonPath: z.string().optional(),\n});\n\nconst indexedDBSourceSchema = z.object({\n kind: z.literal('indexedDB'),\n database: z.string().min(1),\n store: z.string().min(1),\n key: z.string().min(1),\n jsonPath: z.string().min(1),\n});\n\nconst cookieSourceSchema = z.object({\n kind: z.literal('cookie'),\n domain: z.string().min(1),\n name: z.string().min(1),\n});\n\nexport const headerExtractorSourceSchema = z.discriminatedUnion('kind', [\n localStorageSourceSchema,\n sessionStorageSourceSchema,\n indexedDBSourceSchema,\n cookieSourceSchema,\n]);\n\nexport const OIDC_CLIENT_TS_USER_KEY_PREFIX = 'oidc.user:';\n\nexport const headerExtractorSchema = z\n .object({\n name: z.string().min(1),\n header: z.string().min(1).optional(),\n prefix: z.string().optional(),\n source: headerExtractorSourceSchema,\n refresh: tokenRefreshPolicySchema.optional(),\n })\n .superRefine((extractor, ctx) => {\n if (extractor.refresh?.kind !== 'oidc-client-ts') return;\n if (\n extractor.source.kind !== 'sessionStorage' ||\n !extractor.source.key.startsWith(OIDC_CLIENT_TS_USER_KEY_PREFIX)\n ) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: ['refresh'],\n message: `oidc-client-ts refresh needs a sessionStorage source whose key is \"${OIDC_CLIENT_TS_USER_KEY_PREFIX}<authority>:<client_id>\"`,\n });\n }\n });\n\nexport type HeaderExtractorSource = z.infer<typeof headerExtractorSourceSchema>;\nexport type HeaderExtractor = z.infer<typeof headerExtractorSchema>;\n\n/**\n * SessionSeed: the write-direction mirror of {@link headerExtractorSchema}.\n *\n * An extractor reads a value OUT of a captured storage state to build a request\n * header. A seed writes a templated value INTO a synthesized storage state, so\n * the browser boots already authenticated without ever driving a login UI.\n * Same `kind` vocabulary on purpose — one mental model, one JSON editor.\n *\n * Cypress' documented Google recipe expressed as a seed:\n *\n * { name: 'googleSession',\n * target: { kind: 'localStorage', origin: 'https://app.acme.com', key: 'googleCypress' },\n * valueTemplate: '{\"token\":\"{{id_token}}\",\"user\":{\"googleId\":\"{{profile.sub}}\", ...}}' }\n *\n * @see https://docs.cypress.io/app/guides/authentication-testing/google-authentication\n */\nexport const sessionSeedTargetSchema = z.discriminatedUnion('kind', [\n z.object({\n kind: z.literal('localStorage'),\n origin: z.string().min(1),\n key: z.string().min(1),\n }),\n // NB: no `sessionStorage` target. Playwright's storage state does not carry\n // sessionStorage and browser-core has no restore path for it, so such a seed\n // would be silently dropped. Adding it means teaching the browser client to\n // seed it via addInitScript first.\n z.object({\n kind: z.literal('cookie'),\n domain: z.string().min(1),\n name: z.string().min(1),\n path: z.string().default('/'),\n secure: z.boolean().default(true),\n httpOnly: z.boolean().default(false),\n sameSite: z.enum(['Strict', 'Lax', 'None']).default('Lax'),\n }),\n z.object({\n kind: z.literal('indexedDB'),\n /**\n * Required, unlike the read-direction extractor's indexedDB source.\n * IndexedDB is origin-scoped and Playwright cannot restore it from storage\n * state alone — `restoreIndexedDBFromStorageState` has to navigate to an\n * origin first, and it derives that origin from `origins[]`/`cookies[]`. An\n * IndexedDB-only seed carries neither, so without an explicit origin the\n * restore silently no-ops. Same reason `sessionStorage` is absent above:\n * a target that can't actually be restored must not be accepted.\n */\n origin: z.string().min(1),\n database: z.string().min(1),\n store: z.string().min(1),\n key: z.string().min(1),\n }),\n]);\n\nexport const sessionSeedSchema = z.object({\n name: z.string().min(1),\n target: sessionSeedTargetSchema,\n /**\n * The value written to `target`. `{{placeholder}}` tokens are substituted\n * from the minted session — see MINTED_SESSION_PLACEHOLDERS in\n * apps/api/src/credentials/minted-session/seeds.ts for the full list.\n * Substituted values are JSON-escaped when the template parses as JSON.\n */\n valueTemplate: z.string().min(1),\n});\n\nexport type SessionSeedTarget = z.infer<typeof sessionSeedTargetSchema>;\nexport type SessionSeed = z.infer<typeof sessionSeedSchema>;\n\n/**\n * Cross-seed rule: when any IndexedDB seed is present, every localStorage and\n * IndexedDB seed must declare the same origin.\n *\n * Playwright's storage state carries ONE global IndexedDB payload, and\n * `restoreIndexedDBFromStorageState` restores all of it under `origins[0]` —\n * there is no way to say \"this database belongs to that origin\". With a mixed\n * set the database lands on whichever origin happens to be listed first, and\n * several IndexedDB origins collapse onto one. Refusing is the only honest\n * option short of teaching browser-core to group IndexedDB by origin.\n *\n * localStorage-only sets are unconstrained: Playwright restores those\n * per-origin natively.\n *\n * Lives here, in the types package, so save-time validation (API + editor) and\n * run-time rendering all enforce one rule from one definition rather than three\n * drifting copies.\n *\n * @returns a human-readable explanation, or null when the set is fine.\n */\nexport function findSessionSeedOriginConflict(\n seeds: ReadonlyArray<{ target: { kind: string; origin?: string } }>\n): string | null {\n const origins = new Set<string>();\n let hasIndexedDb = false;\n\n for (const seed of seeds) {\n const { kind, origin } = seed.target;\n if (kind === 'indexedDB') {\n hasIndexedDb = true;\n if (origin) origins.add(origin);\n } else if (kind === 'localStorage') {\n if (origin) origins.add(origin);\n }\n }\n\n if (!hasIndexedDb || origins.size <= 1) return null;\n return (\n `IndexedDB seeds require every localStorage and IndexedDB seed to share one origin, ` +\n `but ${origins.size} were declared (${[...origins].sort().join(', ')}). Playwright ` +\n `restores a single global IndexedDB payload under one origin, so a mixed set would ` +\n `silently restore the database under the wrong one.`\n );\n}\n\n/**\n * The array form, with the cross-seed rule attached. Use this — not\n * `z.array(sessionSeedSchema)` — wherever a seed set is accepted, so a config\n * that run time would refuse can never be saved in the first place.\n */\nexport const sessionSeedsSchema = z\n .array(sessionSeedSchema)\n // Bound lives here rather than at each call site: `superRefine` yields a\n // ZodEffects, which has no `.max()`, so a caller cannot add it afterwards.\n .max(50)\n .superRefine((seeds, ctx) => {\n const conflict = findSessionSeedOriginConflict(seeds);\n if (conflict) ctx.addIssue({ code: z.ZodIssueCode.custom, message: conflict });\n });\n\n/**\n * Typed view of credential.metadata. Existing fields (headerName / token*)\n * predate extractors and remain supported. `headerExtractors` is the new\n * generalized lane.\n */\nexport const credentialMetadataSchema = z\n .object({\n headerName: z.string().optional(),\n tokenHeaderName: z.string().optional(),\n tokenHeaderPrefix: z.string().optional(),\n headerExtractors: z.array(headerExtractorSchema).optional(),\n /**\n * Per-credential override of the property's `sessionSeeds`. Where the app\n * keeps its session is normally an app-level fact, so the property is the\n * primary home; this exists for the case where one test user's session\n * lands somewhere different.\n */\n sessionSeeds: sessionSeedsSchema.optional(),\n })\n .passthrough();\n\nexport type CredentialMetadata = z.infer<typeof credentialMetadataSchema>;\n\n// Extract email from credential data if available\nexport function extractEmail(provider: AuthProvider, data: any): string | null {\n switch (provider) {\n case 'basic':\n return data.username || null;\n case 'google':\n case 'microsoft':\n return data.email || null;\n // Nothing identifies the account until a token has been minted at least\n // once — the refresh token itself is opaque.\n case 'google_oauth':\n return data.cachedProfile?.email || null;\n case 'github':\n case 'twitter': {\n // May be username or email\n const val = data.username || null;\n return val && val.includes('@') ? val : null;\n }\n case 'api_login':\n return data.loginFields?.username || data.loginFields?.email || null;\n case 'api_basic': {\n const val = data.username || null;\n return val && val.includes('@') ? val : null;\n }\n default:\n return null;\n }\n}\n","/**\n * Secret scrubbing for anything a runner attempt persists.\n *\n * @module types/secret-redaction\n * @description Lifted verbatim out of `apps/api/src/external-tests/artifact-harvest.ts`\n * (which now re-exports it) because redaction has to happen in **two** places\n * once a runner provisions itself:\n *\n * - on the worker, over files harvested out of a bind-mounted workspace, and\n * - **inside the container**, over the output tree, before it is tarred and\n * PUT to S3 — because under Fargate the worker never sees the raw bytes.\n *\n * One implementation, one set of tests, no possibility of the container being\n * laxer than the harvester.\n */\n\n/** What a scrubbed secret is replaced with. */\nexport const REDACTION_PLACEHOLDER = '[REDACTED]';\n\n/**\n * Shortest secret we will scrub. A 1–3 character value carries almost no\n * entropy, while rewriting every occurrence of, say, `1` would shred the log\n * we're keeping in order to debug with. Real credentials are far longer.\n */\nexport const MIN_REDACTABLE_SECRET_LENGTH = 4;\n\n/**\n * The strings to scrub for a run's sensitive `values`: each value long enough\n * to scrub, plus its JSON-escaped form (how a value with a quote or backslash\n * appears inside serialized JSON, e.g. a trace event) and its URL-escaped form\n * (a form or query body).\n */\nexport function sensitiveNeedles(values: readonly string[]): string[] {\n const needles = new Set<string>();\n for (const value of values) {\n if (value.length < MIN_REDACTABLE_SECRET_LENGTH) continue;\n needles.add(value);\n // How the value appears inside serialized JSON and in a URL or form body.\n needles.add(JSON.stringify(value).slice(1, -1));\n needles.add(encodeURIComponent(value));\n // An HTML form body encodes a space as `+`.\n needles.add(encodeURIComponent(value).replace(/%20/g, '+'));\n }\n return [...needles];\n}\n\n/** The scrubbed text plus how much it had to scrub. */\nexport interface RedactionResult {\n text: string;\n /** Number of substitutions made, summed over every secret. */\n hits: number;\n}\n\n/**\n * Scrub materialized secret values out of text destined for storage, reporting\n * how many substitutions it made.\n *\n * Replaces longest-first so that when one secret contains another (a password\n * that is a prefix of a token, say) the outer match wins and can't leave a\n * readable fragment behind.\n *\n * **Why the count exists.** This function runs TWICE on the same bytes for a\n * self-provisioning runner: once in-container (`runner-entrypoint/pack.ts`) and\n * once again control-plane-side (`external-tests/artifact-harvest.ts`). The\n * second pass is versioned with the API; the first ships inside a container\n * image that can be months stale. If the two ever drift — a new `$var.*` source\n * the old image does not know to scrub, a changed minimum length — the *only*\n * signal available today is a customer finding a live credential in an artifact.\n *\n * A non-zero count on the second pass is that signal, months earlier: it means\n * bytes reached us that the in-container pass should already have scrubbed. The\n * caller decides what to do with it (see `artifact-harvest.ts`, which warns);\n * this module only has to make it observable.\n *\n * @remarks Substring matching only. A secret that the suite re-encodes before\n * printing — base64, URL-escaped, JSON-escaped — will not match. This narrows\n * the blast radius rather than eliminating it; the real containment is that\n * `envMapping` is an allowlist that should only ever carry least-privilege test\n * personas (see the env-materializer module docs).\n */\nexport function redactSecretsWithCount(\n text: string,\n secretValues: readonly string[]\n): RedactionResult {\n const targets = [...new Set(secretValues)]\n .filter((v) => v.length >= MIN_REDACTABLE_SECRET_LENGTH)\n .sort((a, b) => b.length - a.length);\n\n let out = text;\n let hits = 0;\n for (const secret of targets) {\n const parts = out.split(secret);\n // `split` yields one more part than there were occurrences, and `join` is\n // then a no-op when there were none — so this stays a single pass per\n // secret rather than a separate count-then-replace.\n hits += parts.length - 1;\n out = parts.join(REDACTION_PLACEHOLDER);\n }\n return { text: out, hits };\n}\n\n/**\n * Scrub materialized secret values out of text destined for storage.\n *\n * The ergonomic form — a string in, a string out — for the callers that only\n * need the bytes. {@link redactSecretsWithCount} is the same pass with the\n * drift signal attached.\n */\nexport function redactSecrets(text: string, secretValues: readonly string[]): string {\n return redactSecretsWithCount(text, secretValues).text;\n}\n\n/** Extensions worth a redaction pass. Binaries (video, trace zips) are skipped. */\nexport const REDACTABLE_TEXT_EXTENSIONS = ['.jsonl', '.json', '.html', '.log', '.txt'] as const;\n\n/**\n * Text artifacts get a redaction pass; binaries do not.\n *\n * A substring scrub only works on text, and it is only worth the re-encode for\n * files that plausibly echo a credential — a login POST body in `network.jsonl`,\n * an app that logs its own auth payload to the console.\n */\nexport function isRedactableTextPath(relPath: string): boolean {\n const lowered = relPath.toLowerCase();\n return REDACTABLE_TEXT_EXTENSIONS.some((ext) => lowered.endsWith(ext));\n}\n\n/** Skip any single file larger than this. */\nexport const MAX_ARTIFACT_BYTES = 200 * 1024 * 1024;\n\n/** A separate ceiling for long session recordings; other artifacts stay at 200 MiB. */\nexport const MAX_VIDEO_ARTIFACT_BYTES = 384 * 1024 * 1024;\n\n/**\n * Largest text artifact we will attempt to REDACT.\n *\n * Redaction is not streaming and cannot cheaply be made so: the whole file is\n * read into a JS string and {@link redactSecretsWithCount} does one\n * `split`/`join` per secret, each allocating a fresh copy. In UTF-16 that is\n * ~2× the file on disk, *per secret*, all of it live at once — a 200 MB\n * `network.jsonl` with a handful of `$var.*` values is comfortably over a\n * gigabyte of transient allocation inside a Fargate task whose entire memory\n * budget is smaller than that. The container OOMs, PID 1 dies, and the attempt\n * reports nothing at all: the cure is worse than the artifact.\n *\n * So there is a second, much lower cap for text that has to be scrubbed. It is\n * NOT the per-file cap: a 100 MB video is copied byte-for-byte and never\n * touched by the redactor, and capping it here would throw away evidence for no\n * reason.\n *\n * **A file over this cap is DROPPED, never uploaded raw.** That direction is\n * the whole point — the file is over the cap precisely because it is a huge\n * text file produced by the customer's suite, which is exactly the shape of\n * thing most likely to contain a credential. Shipping it unscrubbed to trade a\n * memory risk for a disclosure risk would be the wrong trade in the only case\n * that matters. 32 MB is roughly 300k log lines: far beyond what anyone reads,\n * and still only ~130 MB of transient allocation at four secrets.\n */\nexport const MAX_REDACTABLE_TEXT_BYTES = 32 * 1024 * 1024;\n/** Stop collecting once an attempt's artifacts exceed this in total. */\nexport const MAX_ATTEMPT_BYTES = 500 * 1024 * 1024;\n/** Upper bound on files collected from one attempt. */\n","/**\n * Redact credential-named fields in a recorded request or response body, so a\n * capture never stores, say, the API key a browser test sent when it created a\n * connection. The body-side counterpart of the header redaction in\n * `runner-capture` (`REDACTED_HEADERS`) and the trace redactor.\n *\n * @module types/body-redaction\n */\n\n/**\n * Normalized (lower-case, `-` and `_` removed) key endings that name a\n * credential. An ending, not a substring, so `bearerToken` and `clientSecret`\n * match while `tokens` and `passwordPolicy` do not.\n */\nconst CREDENTIAL_KEY_ENDINGS = [\n 'apikey',\n 'accesskey',\n 'password',\n 'passwd',\n 'passphrase',\n 'secret',\n 'secrets',\n 'secretkey',\n 'token',\n 'authorization',\n 'privatekey',\n 'credential',\n 'credentials',\n];\n\nconst isCredentialKey = (key: string): boolean => {\n // `[]` too, so a Rails/PHP style `connection[api_key]` matches.\n const normalized = key.toLowerCase().replace(/[-_[\\]]/g, '');\n return CREDENTIAL_KEY_ENDINGS.some((ending) => normalized.endsWith(ending));\n};\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value);\n\n/** Replace credential values inside `node`, returning how many were replaced. */\nexport function redactJsonInPlace(node: unknown, placeholder: string): number {\n let count = 0;\n if (Array.isArray(node)) {\n for (const item of node) count += redactJsonInPlace(item, placeholder);\n return count;\n }\n if (!isRecord(node)) return 0;\n // A `{ name, value }` pair (a form param list) names its key in `name`.\n if (\n typeof node.name === 'string' &&\n isCredentialKey(node.name) &&\n typeof node.value === 'string' &&\n node.value !== placeholder\n ) {\n node.value = placeholder;\n count++;\n }\n for (const [key, value] of Object.entries(node)) {\n if (isCredentialKey(key) && value !== null && value !== placeholder) {\n node[key] = placeholder;\n count++;\n } else {\n count += redactJsonInPlace(value, placeholder);\n }\n }\n return count;\n}\n\n/** `connection%5Bapi%5Fkey%5D` -> `connection[api_key]`; a malformed escape is matched as written. */\nfunction decodeFormKey(key: string): string {\n try {\n return decodeURIComponent(key.replace(/\\+/g, ' '));\n } catch {\n return key;\n }\n}\n\nfunction redactFormBody(text: string, placeholder: string): { text: string; hits: number } {\n let hits = 0;\n const pairs = text.split('&').map((pair) => {\n const eq = pair.indexOf('=');\n if (eq <= 0 || !isCredentialKey(decodeFormKey(pair.slice(0, eq))) || pair.slice(eq + 1) === placeholder) {\n return pair;\n }\n hits++;\n return `${pair.slice(0, eq + 1)}${placeholder}`;\n });\n return hits === 0 ? { text, hits } : { text: pairs.join('&'), hits };\n}\n\n/** No whitespace and at least one `=`; segments without one (`a&token=x`) are left as they are. */\nconst isFormBody = (text: string): boolean => text.includes('=') && !/\\s/.test(text);\n\n/**\n * Replace the value of every credential-named field in `text`, keeping the\n * structure. A JSON body is walked at any depth; a form-encoded body is split\n * into its pairs. Anything else (multipart, plain text, base64) and JSON nested\n * inside a string value are returned as they were. Re-serializing JSON drops\n * its formatting and any integer precision above 2^53, only when a field was\n * redacted.\n */\nexport function redactCredentialBody(\n text: string,\n placeholder: string\n): { text: string; hits: number } {\n const trimmed = text.trim();\n if (trimmed.startsWith('{') || trimmed.startsWith('[')) {\n let parsed: unknown;\n try {\n parsed = JSON.parse(trimmed);\n } catch {\n // A body that opens like JSON but is not (truncated, JSON-lines) has no fields to find.\n return { text, hits: 0 };\n }\n const hits = redactJsonInPlace(parsed, placeholder);\n return hits === 0 ? { text, hits } : { text: JSON.stringify(parsed), hits };\n }\n return isFormBody(trimmed) ? redactFormBody(trimmed, placeholder) : { text, hits: 0 };\n}\n","import { z } from 'zod';\nimport {\n DIAGNOSTIC_LIMIT,\n ISO_DATETIME,\n STORAGE_KEY_LIMIT,\n TITLE_LIMIT,\n TLDR_LIMIT,\n URL_LIMIT,\n UUID,\n optionalCategoryKey,\n optionalLimitedString,\n requiredLimitedString,\n} from './issues-schema-utils.js';\nimport {\n IssueSeveritySchema,\n IssueSourceKindSchema,\n type IssueCategorySummary,\n type IssueSeverity,\n type IssueSourceKind,\n type IssueStatus,\n} from './issue-core.js';\nimport type { FailureDiagnosticEvidence } from './issue-evidence.js';\nimport { AgentProductMapLinkSchema } from './issues-manual.js';\nimport type { Pagination } from './pagination.js';\nexport * from './issue-core.js';\nexport * from './issue-evidence.js';\nexport * from './issues-manual.js';\nexport * from './issues-diagnostic.js';\nexport type { Pagination } from './pagination.js';\n\n/**\n * A single network request captured from a Playwright trace.\n * Bodies are truncated to 100KB.\n */\nexport interface NetworkRequestEntry {\n url: string;\n method: string;\n status: number | null;\n durationMs: number | null;\n requestBody: string | null;\n responseBody: string | null;\n requestHeaders: Record<string, string> | null;\n responseHeaders: Record<string, string> | null;\n contentType: string | null;\n}\n\n/**\n * A single console log entry captured from a Playwright trace.\n */\nexport interface ConsoleLogEntry {\n type: 'log' | 'warning' | 'error' | 'info' | 'debug';\n text: string;\n timestamp: number | null;\n location: string | null;\n}\n\n/**\n * Category of failure root cause — add new values to this array (type is derived).\n *\n * `app_changed` is distinct from both `software_bug` and `test_underspecified`: the\n * application's behavior/labels/flow changed versus the last passing run while the\n * authored test stayed the same, and the agent cannot verify from inside the sandbox\n * whether the change was intentional. The recommendation pattern is \"X appears to have\n * changed to Y; update the test to match if intentional.\" Reserve `software_bug` for\n * behavior deltas that are clearly defects (error page, 5xx, broken layout).\n */\nexport const FAILURE_DIAGNOSTIC_CATEGORIES = [\n 'software_bug',\n 'dependency_issue',\n 'agent_confusion',\n 'platform_error',\n 'test_underspecified',\n 'data_collision',\n 'app_changed',\n 'test_defect',\n] as const;\nexport type FailureDiagnosticCategory = (typeof FAILURE_DIAGNOSTIC_CATEGORIES)[number];\n\n/**\n * Categories a native (natural-language Cofactor flow) diagnosis may choose.\n * `test_underspecified` and `agent_confusion` only make sense when an AI agent\n * interpreted authored natural language; `test_defect` belongs to customer code.\n */\nexport const NATIVE_DIAGNOSTIC_CATEGORIES = [\n 'software_bug',\n 'dependency_issue',\n 'agent_confusion',\n 'platform_error',\n 'test_underspecified',\n 'data_collision',\n 'app_changed',\n] as const satisfies readonly FailureDiagnosticCategory[];\n\n/**\n * Categories an external-test (customer Playwright/Selenium/… code) diagnosis may\n * choose, in the order the external prompt's decision procedure checks them.\n * `test_defect` means the customer's test code is wrong: a locator, helper,\n * assertion, wait, or a setup step in the test repository that does not produce\n * the state the test asserts on.\n */\nexport const EXTERNAL_DIAGNOSTIC_CATEGORIES = [\n 'platform_error',\n 'data_collision',\n 'software_bug',\n 'app_changed',\n 'dependency_issue',\n 'test_defect',\n] as const satisfies readonly FailureDiagnosticCategory[];\n\n/**\n * One key moment in the diagnostic's root-cause timeline — an ordered \"what to\n * watch\" event the QA tester can jump to in the run video. Surfaced both as\n * markers on the video's diagnostic-timeline track and as the timestamped\n * key-evidence list at the top of the run Summary.\n */\nexport interface RootCauseTimelineEvent {\n /**\n * Milliseconds from the start of the video recording — the same scale as the\n * run's `videoTimelineJson` entries and `FailureDiagnosticEvidence.timestamp`\n * (the `relativeTimestampMs` column in the diagnostic workspace's\n * `timeline/actions.jsonl`, NOT the absolute wall-clock `timestampMs`).\n */\n timestampMs: number;\n /** Short marker/row label (≤8 words). */\n title: string;\n /** Optional one-line elaboration shown on hover / under the row. */\n detail?: string;\n /** Visual weight of the marker. Defaults to `info` when omitted. */\n severity?: 'info' | 'warning' | 'critical';\n}\n\n/** Structured diagnostic analysis produced by the failure diagnostic agent */\nexport interface FailureDiagnostic {\n /**\n * Provenance of this record. Absent (or `'agent'`) = produced by the\n * failure-diagnostic agent — a real root-cause analysis. `'manual'` = evidence\n * attached to a manually-filed issue (via `cofactor issue create`); the UI\n * renders these as \"Attached evidence\" rather than a completed root-cause\n * analysis, since `category`/`recommendation` are not asserted by the filer.\n * `'diagnostic'` = evidence attached by the failure-diagnostic agent's\n * `file_issue` tool — category IS asserted (the diagnosis root cause), so the\n * UI shows the category badge, but the full root-cause narrative lives on the\n * linked `failure_diagnostics` row rather than here.\n */\n source?: 'agent' | 'manual' | 'diagnostic';\n category: FailureDiagnosticCategory;\n /** One-sentence thesis — the QA tester reads only this 80% of the time. */\n title: string;\n /**\n * Legacy freeform Markdown narrative (kept for back-compat with already-persisted rows).\n * The current prompt emits `failurePoints` bullets instead; new diagnostics may omit `analysis`.\n */\n analysis?: string;\n /** 2–4 concise bullets describing the failure (current report shape). */\n failurePoints?: string[];\n evidence: FailureDiagnosticEvidence[];\n smokingGun?: {\n type: string;\n description: string;\n detail?: string;\n };\n recommendation: string;\n comparisonWithHistory?: string;\n /**\n * The agent's affirmation that it walked backwards and confirmed the effect of the\n * upstream cache-fallback / cache-replay / variable-capture steps before blaming a\n * cause outside the flow. Required-by-nudge (not schema) on external-cause verdicts\n * (`app_changed` / `software_bug` / `dependency_issue` / `platform_error`). Persisted\n * for audit + telemetry on the upstream-walk-back rate. Absent on non-external verdicts\n * and on diagnoses that predate the field.\n */\n upstreamStepsVerified?: string;\n /** Things the agent could not determine from available evidence (display-only for now). */\n openQuestions?: string[];\n /**\n * Whether the agent materialized a green-baseline comparison workspace before this\n * verdict (v2 `materialize_run` tool). `false` on an `app_changed` verdict means the\n * comparison gate was bypassed (second submission or step-cap salvage) — treat the\n * verdict as low-trust. Absent on diagnoses that predate the field.\n */\n comparisonPerformed?: boolean;\n /** Explicit limitations when gates were bypassed or findings were salvaged. */\n qualityWarnings?: string[];\n completedAt: string;\n agentRunId?: string;\n /**\n * Set when the agent drafted a flow-fix proposal for this failure (via the\n * diagnostic `propose_flow_fix` tool). The run-detail summary uses these to\n * describe the suggested change and deep-link to its proposal review. Absent\n * when the agent proposed no flow change.\n */\n proposalId?: string;\n /** One-line description of the proposed change (the proposal's title). */\n proposalSummary?: string;\n /**\n * Optional ordered root-cause timeline — key moments to watch in the run\n * video, each anchored to a video-relative timestamp so the UI can mark them\n * on the player and let the tester jump to them. Absent on older diagnoses.\n */\n rootCauseTimeline?: RootCauseTimelineEvent[];\n /**\n * Present ONLY on a repeat-failure verdict: the native triage phase that runs\n * when an already-red flow fails again, or an external test whose failure\n * signature matches its previous failure. The verdict inherits the prior\n * diagnosis's `category`/`recommendation`, so this is what distinguishes\n * \"confirmed the established root cause still applies\" from a fresh\n * root-cause analysis. Absent on every full-agent diagnosis.\n */\n triage?: {\n /** Plain-language justification for treating this as a repeat failure. */\n reasoning: string;\n /**\n * External tests only: the agent diagnosis an external test's repeat\n * failure inherited, by signature, without running the agent. Always the\n * original analysis — a repeat of a repeat links to it, not to the copy.\n * Absent on native triage verdicts. `runCreatedAt` (ISO) is when the\n * original run started, for \"Same failure as <date>\".\n */\n repeatOf?: { runId: string; itemId: string; diagnosticId: string; runCreatedAt: string };\n /**\n * The open issue this run was recorded against — absent when the flow had\n * none describing this failure, which is the common case.\n */\n matchedIssueId?: string;\n /** Shortcode of that issue, for display without a join. */\n matchedIssueShortcode?: string;\n };\n\n /** A narrowly guarded suite-result correction applied after this diagnosis. */\n outcomeCorrection?: {\n from: 'failed';\n to: 'success';\n correctedAt: string;\n };\n}\n\nexport interface IssueOccurrence {\n id: string;\n issueId: string;\n orgId: string;\n propertyId: string;\n canonicalPageId: string | null;\n credentialId: string | null;\n sourceKind: IssueSourceKind;\n sourceId: string;\n detectedAt: string;\n primaryUrl: string;\n tldr: string | null;\n summary: string | null;\n errorDetails: string | null;\n screenshotStorageKey: string | null;\n screenshotCaption: string | null;\n traceStorageKey: string | null;\n videoStorageKey: string | null;\n /** Action timeline for video overlay synchronization */\n videoTimelineJson: unknown | null;\n /** Condensed \"fast\" video variant (preferred for playback when present). */\n fastVideoStorageKey: string | null;\n /** Remapped timeline + cut metadata for the fast video variant. */\n fastVideoTimelineJson: unknown | null;\n consoleLogExcerpt: string | null;\n networkFailureUrl: string | null;\n networkFailureStatus: number | null;\n ticketReproSteps: string | null;\n ticketUserFacingSummary: string | null;\n ticketDiagnosticsAnalysis: string | null;\n /** What the test expected to observe. Presence signals the simplified three-section issue layout. */\n ticketExpectedBehavior: string | null;\n dedupReasoning: string | null;\n /** Status of async failure diagnostic analysis */\n diagnosticStatus: string | null;\n /** Structured diagnostic analysis from the failure diagnostic agent */\n diagnosticJson: FailureDiagnostic | null;\n /** Resolved workflow IDs when sourceKind is 'workflow_test' */\n workflowSourceInfo?: { workflowId: string; workflowRunId: string } | null;\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface IssueAssignee {\n id: string;\n displayName: string | null;\n primaryEmail: string | null;\n}\n\nexport interface IssueSummary {\n id: string;\n orgId: string;\n propertyId: string;\n credentialId: string | null;\n title: string;\n tldr: string | null;\n severity: IssueSeverity;\n /** The flow this issue is filed against (workflow-test sources); null otherwise. */\n workflowId: string | null;\n category: IssueCategorySummary;\n confidence: number | null;\n firstSeenAt: string;\n lastSeenAt: string;\n occurrenceCount: number;\n status: IssueStatus;\n statusUpdatedAt: string;\n closedAt: string | null;\n /** The canonical issue this one was closed as a duplicate of; null when it isn't a duplicate. */\n duplicateOfIssueId: string | null;\n isArchived: boolean;\n assignedToId: string | null;\n assignedTo: IssueAssignee | null;\n createdAt: string;\n updatedAt: string;\n latestOccurrence: IssueOccurrence | null;\n tags?: Array<{ id: string; name: string; color: string }>;\n}\n\nexport interface IssueDetail extends IssueSummary {\n occurrences: IssueOccurrence[];\n}\n\nexport interface PaginatedIssuesResponse {\n data: IssueSummary[];\n pagination: Pagination;\n}\n\nexport interface IssueDetailResponse {\n issue: IssueSummary;\n occurrences: IssueOccurrence[];\n occurrencesPagination: Pagination;\n commentCount: number;\n}\n\nexport interface PaginatedIssueOccurrencesResponse {\n data: IssueOccurrence[];\n pagination: Pagination;\n}\n\nexport interface IssueCommentAuthor {\n id: string;\n displayName: string | null;\n primaryEmail: string | null;\n}\n\nexport interface IssueComment {\n id: string;\n issueId: string;\n orgId: string;\n propertyId: string;\n authorId: string;\n author: IssueCommentAuthor | null;\n body: string;\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface PaginatedIssueCommentsResponse {\n data: IssueComment[];\n pagination: Pagination;\n}\n\nexport const RecordIssueOccurrencePayloadSchema = z.object({\n orgId: UUID,\n propertyId: UUID,\n canonicalPageId: UUID.optional()\n .nullable()\n .transform((value) => value ?? null),\n credentialId: UUID.optional()\n .nullable()\n .transform((value) => value ?? null),\n issueTitle: requiredLimitedString('Issue title', TITLE_LIMIT),\n issueSeverity: IssueSeveritySchema,\n issueCategoryId: UUID.optional()\n .nullable()\n .transform((value) => value ?? null),\n issueCategoryKey: optionalCategoryKey,\n issueConfidence: z\n .number()\n .int()\n .min(0)\n .max(100)\n .optional()\n .nullable()\n .transform((value) => {\n if (value === null || value === undefined) return null;\n return value;\n }),\n detectedAt: ISO_DATETIME,\n sourceKind: IssueSourceKindSchema,\n sourceId: requiredLimitedString('Source identifier', STORAGE_KEY_LIMIT),\n primaryUrl: requiredLimitedString('Primary URL', URL_LIMIT),\n summary: requiredLimitedString('Summary', DIAGNOSTIC_LIMIT),\n tldr: requiredLimitedString('TLDR', TLDR_LIMIT),\n screenshotStorageKey: requiredLimitedString('Screenshot storage key', STORAGE_KEY_LIMIT),\n screenshotCaption: optionalLimitedString(DIAGNOSTIC_LIMIT),\n traceStorageKey: optionalLimitedString(STORAGE_KEY_LIMIT),\n videoStorageKey: optionalLimitedString(STORAGE_KEY_LIMIT),\n /** Action timeline for video overlay synchronization */\n videoTimelineJson: z.unknown().optional().nullable(),\n consoleLogExcerpt: optionalLimitedString(DIAGNOSTIC_LIMIT),\n networkFailureUrl: optionalLimitedString(URL_LIMIT),\n networkFailureStatus: z\n .number()\n .int()\n .optional()\n .nullable()\n .transform((value) => value ?? null),\n /** Structured network requests captured from Playwright trace */\n networkRequestsJson: z.array(z.unknown()).optional().nullable(),\n /** Structured console logs captured from Playwright trace */\n consoleLogsJson: z.array(z.unknown()).optional().nullable(),\n ticketReproSteps: optionalLimitedString(DIAGNOSTIC_LIMIT),\n ticketUserFacingSummary: requiredLimitedString('Ticket summary', DIAGNOSTIC_LIMIT),\n ticketDiagnosticsAnalysis: requiredLimitedString('Ticket diagnostics analysis', DIAGNOSTIC_LIMIT),\n /** The capability (and rule) the filing agent says the bug breaks; see `AgentProductMapLinkFields`. */\n productMapLink: AgentProductMapLinkSchema.optional(),\n});\n\nexport type RecordIssueOccurrencePayload = z.infer<typeof RecordIssueOccurrencePayloadSchema>;\n","import { z } from 'zod';\n\nexport const TITLE_LIMIT = 512;\nexport const TLDR_LIMIT = 512;\nexport const URL_LIMIT = 2048;\nexport const STORAGE_KEY_LIMIT = 1024;\nexport const DIAGNOSTIC_LIMIT = 4000;\n\nconst clamp = (value: string, max: number): string =>\n value.length > max ? value.slice(0, max) : value;\n\nexport const requiredLimitedString = (label: string, max: number) =>\n z\n .string()\n .transform((value) => value.trim())\n .pipe(z.string().min(1, `${label} is required`))\n .transform((value) => clamp(value, max));\n\nexport const optionalLimitedString = (max: number) =>\n z\n .string()\n .optional()\n .nullable()\n .transform((value) => {\n if (typeof value !== 'string') return null;\n const trimmed = value.trim();\n if (!trimmed) return null;\n return clamp(trimmed, max);\n });\n\nconst isSafeHttpOrRelativeUrl = (value: string): boolean => {\n let parsed: URL;\n try {\n parsed = new URL(value);\n } catch {\n return true;\n }\n return parsed.protocol === 'http:' || parsed.protocol === 'https:';\n};\n\nexport const optionalSafeUrl = (max: number) =>\n optionalLimitedString(max).refine((value) => value === null || isSafeHttpOrRelativeUrl(value), {\n message: 'URL must be an http(s) or relative URL',\n });\n\nexport const optionalCategoryKey = z\n .string()\n .min(1)\n .max(64)\n .optional()\n .nullable()\n .transform((value) => {\n if (typeof value !== 'string') return null;\n const trimmed = value.trim();\n return trimmed.length ? trimmed : null;\n });\n\nexport const ISO_DATETIME = z.string().datetime({ offset: true });\nexport const UUID = z.string().uuid();\n","import { z } from 'zod';\n\nexport const IssueSeveritySchema = z.enum(['low', 'medium', 'high', 'unknown']);\nexport type IssueSeverity = z.infer<typeof IssueSeveritySchema>;\n\nexport const IssueStatusSchema = z.enum(['open', 'closed', 'not_a_bug']);\nexport type IssueStatus = z.infer<typeof IssueStatusSchema>;\n\nexport interface IssueCategorySummary {\n id: string;\n key: string;\n label: string;\n description: string | null;\n instructions: string | null;\n}\n\nexport interface IssueCategorySetting extends IssueCategorySummary {\n isActive: boolean;\n isSystem: boolean;\n sortOrder: number;\n}\n\nexport const IssueSourceKindSchema = z.enum([\n 'crawler_analysis',\n 'exploratory_attempt',\n 'qa_page_test',\n 'workflow_test',\n 'chaos_test',\n 'adhoc_test',\n // A human or agent filed this issue directly via `cofactor issue create`.\n 'manual',\n]);\nexport type IssueSourceKind = z.infer<typeof IssueSourceKindSchema>;\n","import { z } from 'zod';\nimport { FailureDiagnosticEvidenceSchema } from './issue-evidence.js';\nimport { IssueSeveritySchema } from './issue-core.js';\nimport {\n DIAGNOSTIC_LIMIT,\n TITLE_LIMIT,\n URL_LIMIT,\n UUID,\n optionalCategoryKey,\n optionalLimitedString,\n optionalSafeUrl,\n requiredLimitedString,\n} from './issues-schema-utils.js';\n\nconst ManualEvidenceInput = z\n .array(FailureDiagnosticEvidenceSchema)\n .max(50, 'At most 50 evidence items may be attached')\n .optional()\n .nullable()\n .transform((value) => value ?? null);\n\nconst OptionalUuid = UUID.optional()\n .nullable()\n .transform((value) => value ?? null);\n\n/**\n * Ties an issue to the product behaviour it breaks: a capability (`CAP-…`\n * code, resolved within the issue's application, or `pcap_` id), optionally\n * narrowed to one of its rules (`prule_` id).\n */\nexport const IssueProductMapLinkSchema = z.object({\n capability: z.string().trim().min(1, 'capability is required'),\n rule: z.string().trim().min(1, 'rule must not be empty').optional(),\n});\nexport type IssueProductMapLink = z.infer<typeof IssueProductMapLinkSchema>;\n\n/**\n * The product-map link a Cofactor agent names on its own filing tool: the\n * capability (and optionally the rule) the bug breaks, chosen by the agent\n * from the map it was shown, never inferred from a URL. Both fields are\n * optional because the agent leaves them out whenever it is not sure.\n *\n * Spread into AI tool input schemas, so no `.min()` or `.transform()`: a\n * schema rejection would cost the whole filing, while a bad value here only\n * costs the link (`apps/api/src/issues/agent-product-map-link.ts`).\n */\nexport const AgentProductMapLinkFields = {\n capability: z\n .string()\n .optional()\n .describe(\n 'The product-map capability code (CAP-…) this bug breaks. Set it only when the bug clearly breaks that capability; leave it out otherwise.'\n ),\n rule: z\n .string()\n .optional()\n .describe(\n \"The id of the rule this bug breaks, one of that capability's rules. Set it only together with capability, and only when the bug clearly breaks that rule.\"\n ),\n};\nexport const AgentProductMapLinkSchema = z.object(AgentProductMapLinkFields);\nexport type AgentProductMapLink = z.infer<typeof AgentProductMapLinkSchema>;\n\nconst ManualOccurrenceFields = {\n body: optionalLimitedString(DIAGNOSTIC_LIMIT),\n primaryUrl: optionalSafeUrl(URL_LIMIT),\n runId: OptionalUuid,\n /**\n * Link the issue to this failing assertion of `runId` (an assertion result\n * `key`). Requires `runId`; validated against the run's failing verdicts.\n * Once linked, the assertion's next failure is issue-covered.\n */\n subjectAssertionKey: OptionalUuid,\n evidence: ManualEvidenceInput,\n /** Link the issue to a capability (and rule) in the same call; a refused link files nothing. */\n productMapLink: IssueProductMapLinkSchema.optional(),\n};\n\nexport const CreateManualIssuePayloadSchema = z.object({\n propertyId: UUID,\n issueTitle: requiredLimitedString('Issue title', TITLE_LIMIT),\n issueSeverity: IssueSeveritySchema,\n issueCategoryId: OptionalUuid,\n issueCategoryKey: optionalCategoryKey,\n ...ManualOccurrenceFields,\n});\nexport type CreateManualIssuePayload = z.infer<typeof CreateManualIssuePayloadSchema>;\n\nexport const AddIssueOccurrencePayloadSchema = z.object({\n ...ManualOccurrenceFields,\n});\nexport type AddIssueOccurrencePayload = z.infer<typeof AddIssueOccurrencePayloadSchema>;\n\n/**\n * Edit an occurrence's report. Each field is optional because a caller edits\n * only what it names (`cofactor issues update --summary` sends just `summary`);\n * at least one is required. `evidence` replaces the occurrence's whole list —\n * the CLI reads the current list, applies its add/replace/remove, and sends\n * the result.\n */\nexport const UpdateIssueOccurrencePayloadSchema = z\n .object({\n summary: requiredLimitedString('Summary', DIAGNOSTIC_LIMIT).optional(),\n reproSteps: requiredLimitedString('Repro steps', DIAGNOSTIC_LIMIT).optional(),\n evidence: z\n .array(FailureDiagnosticEvidenceSchema)\n .max(50, 'At most 50 evidence items may be attached')\n .optional(),\n })\n .refine(\n (value) =>\n value.summary !== undefined || value.reproSteps !== undefined || value.evidence !== undefined,\n 'Nothing to update: pass summary, reproSteps, or evidence'\n );\nexport type UpdateIssueOccurrencePayload = z.infer<typeof UpdateIssueOccurrencePayloadSchema>;\n\n/** Close the path issue as a duplicate of `canonicalIssueId` (a uuid or `iss_` id). */\nexport const MarkIssueDuplicatePayloadSchema = z.object({\n canonicalIssueId: z.string().trim().min(1, 'canonicalIssueId is required'),\n});\nexport type MarkIssueDuplicatePayload = z.infer<typeof MarkIssueDuplicatePayloadSchema>;\n","import { z } from 'zod';\n\nexport const IssueEvidenceKindSchema = z.enum([\n 'network_error',\n 'console_error',\n 'visual',\n 'agent_thought',\n 'timeline_event',\n 'history_comparison',\n 'before_after',\n 'code_reference',\n 'log_reference',\n]);\nexport type IssueEvidenceKind = z.infer<typeof IssueEvidenceKindSchema>;\n\nexport const IssueLogSystemSchema = z.enum(['sentry', 'cloudwatch', 'datadog', 'other']);\nexport type IssueLogSystem = z.infer<typeof IssueLogSystemSchema>;\n\nconst EVIDENCE_ID_LIMIT = 1024;\nconst EVIDENCE_TEXT_LIMIT = 4000;\nconst EVIDENCE_DETAIL_LIMIT = 16000;\n\nexport const FailureDiagnosticEvidenceSchema = z.object({\n type: IssueEvidenceKindSchema,\n timestamp: z.number().optional(),\n description: z.string().max(EVIDENCE_TEXT_LIMIT),\n detail: z.string().max(EVIDENCE_DETAIL_LIMIT).optional(),\n frameStorageKey: z.string().max(EVIDENCE_ID_LIMIT).optional(),\n\n /** Short representative video clip extracted from the run recording (MP4). */\n clipStorageKey: z.string().max(EVIDENCE_ID_LIMIT).optional(),\n /** Where the clip starts, in video-relative ms (same scale as `timestamp`). */\n clipStartMs: z.number().optional(),\n /** Duration of the (possibly tightened) clip in ms. */\n clipDurationMs: z.number().optional(),\n\n beforeFrameStorageKey: z.string().max(EVIDENCE_ID_LIMIT).optional(),\n beforeCaption: z.string().max(EVIDENCE_TEXT_LIMIT).optional(),\n beforeTimestamp: z.number().optional(),\n beforeRunId: z.string().max(EVIDENCE_ID_LIMIT).optional(),\n afterFrameStorageKey: z.string().max(EVIDENCE_ID_LIMIT).optional(),\n afterCaption: z.string().max(EVIDENCE_TEXT_LIMIT).optional(),\n afterTimestamp: z.number().optional(),\n afterRunId: z.string().max(EVIDENCE_ID_LIMIT).optional(),\n\n repo: z.string().max(EVIDENCE_ID_LIMIT).optional(),\n path: z.string().max(EVIDENCE_ID_LIMIT).optional(),\n line: z.number().int().optional(),\n endLine: z.number().int().optional(),\n commit: z.string().max(EVIDENCE_ID_LIMIT).optional(),\n permalink: z.string().max(EVIDENCE_TEXT_LIMIT).optional(),\n symbol: z.string().max(EVIDENCE_ID_LIMIT).optional(),\n\n logSystem: IssueLogSystemSchema.optional(),\n query: z.string().max(EVIDENCE_TEXT_LIMIT).optional(),\n logId: z.string().max(EVIDENCE_ID_LIMIT).optional(),\n traceId: z.string().max(EVIDENCE_ID_LIMIT).optional(),\n url: z.string().max(EVIDENCE_TEXT_LIMIT).optional(),\n});\nexport type FailureDiagnosticEvidence = z.infer<typeof FailureDiagnosticEvidenceSchema>;\n","import { z } from 'zod';\nimport { FailureDiagnosticEvidenceSchema } from './issue-evidence.js';\nimport { IssueSeveritySchema } from './issue-core.js';\nimport { DIAGNOSTIC_LIMIT, TITLE_LIMIT, URL_LIMIT } from './issues-schema-utils.js';\nimport { AgentProductMapLinkFields } from './issues-manual.js';\n\n/**\n * Payload the failure-diagnostic agent submits per discrete issue via its\n * `file_issue` tool. Deliberately simpler than the legacy auto-file payload:\n * plain user language, a minimal repro path, and curated evidence — never the\n * full test's step dump.\n *\n * IMPORTANT: this schema is used as an AI tool input schema, so it must stay\n * representable in JSON Schema — no `.transform()` / `.pipe()` (see\n * `packages/ai/src/schema.ts`). Trimming/normalization happens in the filing\n * service, not here.\n */\nexport const FileIssuePayloadSchema = z.object({\n /** Plain user language naming the defect — never a \"Flow X failed:\" template. */\n title: z.string().min(1).max(TITLE_LIMIT),\n /** What actually happened, in the words a user of the app would use. */\n whatHappened: z.string().min(1).max(DIAGNOSTIC_LIMIT),\n /** What the test (and a user) expected to observe instead. */\n whatWasExpected: z.string().min(1).max(DIAGNOSTIC_LIMIT),\n /**\n * The QUICK path to reproduce — numbered markdown steps starting from the\n * closest reachable state, not a walkthrough of the whole flow.\n */\n reproSteps: z.string().min(1).max(DIAGNOSTIC_LIMIT),\n severity: IssueSeveritySchema,\n /** The URL where the defect is observable (http/https). */\n primaryUrl: z.string().max(URL_LIMIT).optional(),\n /** Curated evidence for this specific issue (frames, clips, logs, network). */\n evidence: z.array(FailureDiagnosticEvidenceSchema).max(20).optional(),\n /**\n * Attach this report as a new occurrence of an already-open issue on the same\n * flow instead of creating a duplicate.\n */\n existingIssueId: z.string().uuid().optional(),\n /**\n * This issue is about ONE specific assertion on this run (its\n * `workflow_run_steps.metadataJson.assertionResults[].key`) — auto-close\n * then waits for that exact assertion to pass, instead of the whole\n * workflow. Only meaningful when the run actually has failing assertions;\n * the tool omits this field from its schema otherwise.\n */\n assertionKey: z.string().uuid().optional(),\n /**\n * This issue is about ONE specific node on this run — auto-close then waits\n * for that exact node to pass. Only meaningful when the run executed nodes\n * to reference; the tool omits this field from its schema otherwise.\n * Ignored when `assertionKey` is also set (assertion is the finer altitude).\n */\n nodeId: z.string().uuid().optional(),\n /** The product-map capability (and rule) this bug breaks, when the agent is sure. */\n ...AgentProductMapLinkFields,\n});\nexport type FileIssuePayload = z.infer<typeof FileIssuePayloadSchema>;\n","/** Shared checkpoint capture and comparison vocabulary. */\n\nexport type VisualRect = { x: number; y: number; width: number; height: number };\n\n/** Structures a run can sit in: the nearest table/grid or list around it. */\nexport const VISUAL_TEXT_CONTAINERS = ['table', 'list'] as const;\nexport type VisualTextContainer = (typeof VISUAL_TEXT_CONTAINERS)[number];\n\n/**\n * ARIA landmarks a run can sit in. `<header>`/`<footer>` count as banner/contentinfo only outside\n * article, aside, main, nav and section; `alertdialog` reads as dialog; `<form>` and `<section>`\n * count only with an explicit role (ARIA makes them landmarks only when named).\n */\nexport const VISUAL_TEXT_LANDMARKS = [\n 'navigation',\n 'main',\n 'banner',\n 'contentinfo',\n 'complementary',\n 'form',\n 'dialog',\n 'search',\n 'region',\n] as const;\nexport type VisualTextLandmark = (typeof VISUAL_TEXT_LANDMARKS)[number];\n\n/**\n * Generic DOM facts about a text run, read with the run. A fact that does not apply is left out:\n * no role, no heading level, no container, no landmark.\n */\nexport type VisualTextContext = {\n /** Lowercase tag of the element holding the text (`span`, `h1`, `input`); `unknown` for odd names. */\n tag: string;\n /**\n * Explicit or implicit ARIA role of the nearest element that has one (the first token of a `role`\n * attribute, plain lowercase words only), landmarks, `presentation` and `none` skipped.\n */\n role?: string;\n /** 1-6, when that role is a heading. */\n level?: number;\n container?: VisualTextContainer;\n landmark?: VisualTextLandmark;\n};\n\n/** One visible text node at capture time, in viewport pixels. */\nexport type VisualTextRun = { text: string; rect: VisualRect; context: VisualTextContext };\n\n/**\n * Why a visual capture was taken when it was, so a capture of a page that was still changing can be\n * explained.\n * - `settled`: the page stopped moving and every request it was waiting on had answered.\n * - `budget`: the settle budget ran out; `blockers` names what was still moving: `network:<METHOD>\n * <origin+path>` for up to three unanswered requests, `navigation`, or the page-side blockers\n * such as `mutations`, `toast` or `text:Loading...`.\n * - `unavailable`: the gate could not run; `blockers` is `hung` (the page evaluate never returned)\n * or `error:<message>`.\n */\nexport type VisualCaptureSettle = {\n reason: 'settled' | 'budget' | 'unavailable';\n waitedMs: number;\n blockers: string[];\n};\n\n/** How a text run differs between the baseline and the capture. */\nexport type VisualTextChangeKind = 'changed' | 'added' | 'removed' | 'moved';\n\n/** One side of a text change: the run as it was in the baseline or is in the capture. */\nexport type VisualTextChangeSide = {\n text: string;\n rect: VisualRect;\n context: VisualTextContext;\n};\n\n/** One text difference that falls inside a diff region of a compared screen. */\nexport type VisualTextChange = {\n kind: VisualTextChangeKind;\n /** The baseline run; null when `added`. */\n before: VisualTextChangeSide | null;\n /** The capture run; null when `removed`. */\n after: VisualTextChangeSide | null;\n /**\n * Index into the same comparison's `regions` of the diff region this change overlaps most. Any\n * step that filters or reorders `regions` must remap it.\n */\n region: number;\n};\n\n/**\n * Text differences of one compared screen, aligned from both sides' text runs. Stored with the\n * comparison; nothing is flagged or ignored because of it.\n */\nexport type VisualTextChanges = {\n /**\n * Ordered by region, then top to bottom, left to right. When there are more than a cap, regions\n * take turns (every region's first change before any region's second), so no region is crowded\n * out by a busier one.\n */\n changes: VisualTextChange[];\n /** How many changes there were before the cap. */\n total: number;\n /**\n * Indexes of diff regions explained by text alone: every change in them is the same run with new\n * text on the same line (no text added, removed or moved, and not a form field's value), and the\n * runs' boxes cover the region.\n */\n textOnlyRegions: number[];\n};\n\n/**\n * A region left out of the comparison because the same dynamic text (a date, id, ...) sat there in\n * both the baseline and the capture and only its value changed. `kind` names the dynamic parts.\n */\nexport type VisualAutoIgnore = { kind: string; rect: VisualRect };\n\nexport type VisualExplanation = {\n verdict: 'real' | 'matches_expected' | 'unsure';\n intent: 'intended' | 'likely_bug' | 'unclear';\n text: string;\n};\n\nexport type VisualRenderProfile = {\n browserVersion: string;\n platform: string;\n viewportWidth: number;\n viewportHeight: number;\n deviceScaleFactor: number;\n locale: string;\n timezone: string;\n colorScheme: 'light' | 'dark' | 'no-preference';\n fontFingerprint: string;\n};\n","/**\n * Dynamic text (dates, times, ids) that legitimately differs between a baseline and a\n * later capture. A text run is ignored only when it has the same shape on both sides: its\n * dynamic parts are swapped for placeholders and the rest must read identically. A date\n * that became \"ERROR\", or \"Updated\" that became \"Deleted\", changes the shape and still shows.\n */\nimport { VISUAL_TEXT_CONTAINERS, VISUAL_TEXT_LANDMARKS, type VisualAutoIgnore, type VisualRect, type VisualTextRun } from './visual-review.js';\nimport { z } from 'zod';\n\n// Whole month names or exact abbreviations only: \"Decide 5\" and \"Marketing 2\" are not dates.\nconst MONTH =\n '(?:Jan(?:uary)?|Feb(?:ruary)?|Mar(?:ch)?|Apr(?:il)?|May|June?|July?|Aug(?:ust)?|Sep(?:t(?:ember)?)?|Oct(?:ober)?|Nov(?:ember)?|Dec(?:ember)?)\\\\.?';\n\n// Per-run sandbox hosts (`<random>.sbx.runcofactor.com`, dev `<random>.sbx.dev.runcofactor.com`),\n// see `previewDomain` in infra/components/blackcat-dev.constants.ts. Only the varying subdomain is\n// replaced; the suffix stays literal, so a different registrable domain changes the shape.\nconst SANDBOX_HOST_SUFFIX = '\\\\.sbx(?:\\\\.dev)?\\\\.runcofactor\\\\.com(?![\\\\w-]|\\\\.[\\\\w-])';\n\n/** Order matters: earlier patterns consume text before later ones see it. */\nexport const DYNAMIC_PATTERNS: ReadonlyArray<{ kind: string; pattern: RegExp }> = [\n // First: a random label can look like a hex id or a date and must not be split by those patterns.\n {\n kind: 'hostname',\n pattern: new RegExp(\n `(?<![\\\\w.-])[a-z0-9][a-z0-9-]*(?:\\\\.[a-z0-9-]+)*(?=${SANDBOX_HOST_SUFFIX})`,\n 'gi'\n ),\n },\n {\n kind: 'uuid',\n pattern: /\\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\\b/gi,\n },\n // Prefixed entity ids (`exec_<22 base58 chars>`), see docs/project/prefixed-ids.md.\n { kind: 'id', pattern: /\\b[a-z]{2,6}_[1-9A-HJ-NP-Za-km-z]{20,24}\\b/g },\n // Hex digests and dashless uuids: needs a letter, so a long bare number is not an id.\n { kind: 'id', pattern: /\\b(?=[0-9a-f]*[a-f])[0-9a-f]{16,}\\b/gi },\n {\n kind: 'date',\n pattern:\n /\\b\\d{4}-\\d{2}-\\d{2}(?:[T ]\\d{2}:\\d{2}(?::\\d{2}(?:\\.\\d+)?)?(?:Z|[+-]\\d{2}:?\\d{2})?)?(?!\\d)/g,\n },\n // 10/14/2026 or 14.10.2026; a bare 2.14.1 is a version, not a date.\n { kind: 'date', pattern: /\\b\\d{1,2}(?:\\/\\d{1,2}\\/\\d{2,4}|\\.\\d{1,2}\\.\\d{4})\\b/g },\n {\n kind: 'date',\n pattern: new RegExp(`\\\\b${MONTH}\\\\s+\\\\d{1,2}(?:st|nd|rd|th)?(?:,?\\\\s+\\\\d{4})?\\\\b`, 'g'),\n },\n {\n kind: 'date',\n pattern: new RegExp(`\\\\b\\\\d{1,2}(?:st|nd|rd|th)?\\\\s+${MONTH}(?:,?\\\\s+\\\\d{4})?\\\\b`, 'g'),\n },\n // \"5 minutes ago\", plus the qualified forms date-fns style libraries print: \"less than a minute\n // ago\", \"about an hour ago\", \"almost 2 years ago\". Without \"ago\" a phrase is plain text.\n {\n kind: 'relative-time',\n pattern:\n /\\b(?:just now|(?:(?:less than|about|almost|over|under|more than)\\s+)?(?:an?|\\d+)\\s*(?:s|secs?|seconds?|m|mins?|minutes?|h|hrs?|hours?|d|days?|w|wks?|weeks?|mo|months?|y|yrs?|years?)\\s+ago)\\b/gi,\n },\n {\n kind: 'time',\n pattern:\n /\\b\\d{1,2}:\\d{2}(?::\\d{2})?(?:\\s?[AaPp]\\.?[Mm]\\.?)?(?:\\s?(?:UTC|GMT|[ECMP][SD]T))?(?![\\dA-Za-z])/g,\n },\n // Elapsed time as \"1m 23s\", \"1h 2m\", \"45s\", \"3m\", and measured seconds with two or more\n // decimals (\"50.291s\", \"1m 26.83s\"). Hours need minutes (\"1h 2m\"; a lone \"5h\" is plain text).\n // Bare seconds must be under 60, so a one-decimal \"2.5s\" and 60s-90s decades stay plain text;\n // bare minutes are a run's elapsed time (\"Duration 3m\"), except after a currency sign (\"$5m\").\n // Known limits: \"10s\"-\"50s\" read as seconds, so \"the 40s\" becoming \"the 50s\" is ignored, and a\n // distance in metres (\"5m\") reads as minutes; \"1m 23s ago\" is taken by relative-time first.\n // \"0:05\" is the time pattern.\n {\n kind: 'duration',\n pattern:\n /(?<![\\w.])(?:\\d{1,3}h\\s?[1-5]?\\dm(?:\\s?[1-5]?\\ds)?|\\d{1,3}m\\s?(?:[1-5]?\\d\\.\\d{2,}|[1-5]?\\d)s|\\d{1,3}\\.\\d{2,}s|[1-5]?\\ds|(?<![$€£¥])\\d{1,3}m)(?![\\w])/g,\n },\n // Name tokens that end in a millisecond timestamp (`Date.now()`: 13 digits starting with 1, as in\n // \"Example-Run-1792065674902\"). A bare 13-digit number with no letter and separator before it is\n // a real number.\n { kind: 'id', pattern: /(?<=[A-Za-z][-_])1\\d{12}(?!\\d)/g },\n // Random 8-hex suffix of a generated mailbox (\"qa-teammate-b2c0299e@example.com\"). Needs a separator\n // before it and a letter in it, so \"deadbeef@example.com\" and \"user-12345678@example.com\" stay.\n { kind: 'id', pattern: /(?<=[A-Za-z0-9][-_+.])(?=[0-9a-f]{0,7}[a-f])[0-9a-f]{8}(?=@)/gi },\n // Random alphanumeric mailbox suffix of 8-12 characters (\"test-k3j2h1p0@example.com\"). Only before \"@\",\n // and only when letters and digits alternate at least three times, so \"john.smith1990@\" and\n // \"gamer-99xx@\" (a name plus a number) stay. The second lookahead reads: letter, digits, letters,\n // digit (or digit, letters, digits, letter), i.e. at least four alternating runs.\n {\n kind: 'id',\n pattern:\n /(?<=[A-Za-z0-9][-_+.])(?=[a-z0-9]{8,12}@)(?=[a-z0-9]*?(?:[a-z]\\d+[a-z]+\\d|\\d[a-z]+\\d+[a-z]))[a-z0-9]{8,12}(?=@)/gi,\n },\n // Short ids: a run id, a commit (\"2e04132d\", \"cfff25a\"), 6-12 hex characters of one case with a\n // digit and a letter, as a token of its own (not joined to a word by \"_\", part of a longer hex\n // run, or a mailbox, whose patterns above decide). A number with no letter, or a word of hex letters (\"facade\"), is\n // not one.\n {\n kind: 'id',\n pattern:\n /(?<![\\p{L}\\p{N}_.])(?:(?=[0-9a-f]{0,11}\\d)(?=[0-9a-f]{0,11}[a-f])[0-9a-f]{6,12}|(?=[0-9A-F]{0,11}\\d)(?=[0-9A-F]{0,11}[A-F])[0-9A-F]{6,12})(?![\\p{L}\\p{N}_@])/gu,\n },\n];\n\ntype DynamicShape = { skeleton: string; kinds: string[] };\n\n/** The text with each dynamic part replaced by `<kind>`, and the kinds found, in order of the patterns. */\nfunction dynamicShape(text: string): DynamicShape {\n const kinds: string[] = [];\n let skeleton = text;\n for (const { kind, pattern } of DYNAMIC_PATTERNS) {\n skeleton = skeleton.replace(pattern, () => {\n kinds.push(kind);\n return `<${kind}>`;\n });\n }\n return { skeleton: skeleton.replace(/\\s+/g, ' ').trim(), kinds };\n}\n\n/** Rects this close (px) still count as the same spot: a longer value shifts its own right edge. */\nconst SAME_SPOT_PAD = 8;\n\n/**\n * Overlap of the two rects once each is grown by `SAME_SPOT_PAD`; 0 when they do not touch.\n * Vertically that pad on each rect is the tolerance for a run that moved: about a line height\n * plus 16px, so content above growing by a line or two still pairs.\n */\nconst overlapArea = (a: VisualRect, b: VisualRect) => {\n const w =\n Math.min(a.x + a.width, b.x + b.width) + SAME_SPOT_PAD - (Math.max(a.x, b.x) - SAME_SPOT_PAD);\n const h =\n Math.min(a.y + a.height, b.y + b.height) + SAME_SPOT_PAD - (Math.max(a.y, b.y) - SAME_SPOT_PAD);\n return w > 0 && h > 0 ? w * h : 0;\n};\n\n/** Text boxes that only touch, or overlap by rounding, are neighbours, not occupants: \"Updated:\" and its value. */\nconst TOUCH_TOLERANCE = 2;\n\nconst intersects = (a: VisualRect, b: VisualRect) =>\n a.x + TOUCH_TOLERANCE < b.x + b.width &&\n b.x + TOUCH_TOLERANCE < a.x + a.width &&\n a.y + TOUCH_TOLERANCE < b.y + b.height &&\n b.y + TOUCH_TOLERANCE < a.y + a.height;\n\nconst union = (a: VisualRect, b: VisualRect): VisualRect => {\n const x = Math.min(a.x, b.x);\n const y = Math.min(a.y, b.y);\n return {\n x,\n y,\n width: Math.max(a.x + a.width, b.x + b.width) - x,\n height: Math.max(a.y + a.height, b.y + b.height) - y,\n };\n};\n\ntype Shaped = DynamicShape & { run: VisualTextRun };\n\n/** Remove and return the unused baseline run with the same shape that sits closest to `current`. */\nfunction takeBestPartner(available: Shaped[], current: Shaped): Shaped | null {\n let best = -1;\n let bestArea = 0;\n for (const [index, candidate] of available.entries()) {\n if (candidate.skeleton !== current.skeleton) continue;\n const area = overlapArea(candidate.run.rect, current.run.rect);\n if (area > bestArea) {\n best = index;\n bestArea = area;\n }\n }\n return best < 0 ? null : (available.splice(best, 1)[0] ?? null);\n}\n\n/**\n * Text on the same line, to the right of a value whose width changed, that kept its words and\n * moved sideways by that change: the value pushed it, nothing about it changed. Returns those runs\n * (both sides), which no longer count as crowding the value, and the box of each separator among\n * them, which the diff leaves out.\n */\nfunction shiftedNeighbours(\n baseline: VisualTextRun[],\n capture: VisualTextRun[],\n before: VisualTextRun,\n after: VisualTextRun\n): { runs: Set<VisualTextRun>; rects: VisualRect[] } {\n const runs = new Set<VisualTextRun>();\n const rects: VisualRect[] = [];\n const widthChange = after.rect.width - before.rect.width;\n if (Math.abs(widthChange) <= TOUCH_TOLERANCE) return { runs, rects };\n const sameLine = (a: VisualRect, b: VisualRect) =>\n Math.abs(a.y - b.y) <= TOUCH_TOLERANCE && Math.abs(a.height - b.height) <= TOUCH_TOLERANCE;\n const unused = new Set(capture);\n for (const old of baseline) {\n if (\n old === before ||\n !sameLine(old.rect, before.rect) ||\n old.rect.x < before.rect.x + before.rect.width - TOUCH_TOLERANCE\n )\n continue;\n const moved = [...unused].find(\n (candidate) =>\n candidate !== after &&\n candidate.text === old.text &&\n sameLine(candidate.rect, old.rect) &&\n Math.abs(candidate.rect.x - old.rect.x - widthChange) <= 1\n );\n if (!moved) continue;\n unused.delete(moved);\n runs.add(old).add(moved);\n // Only a run with no letter or digit (\"·\", \"|\", an icon glyph) is left out of the diff. A word\n // that slid keeps being compared as before, so a pill that changed colour or style is still seen,\n // and one that merely slid is flagged exactly as it was before this change.\n if (!/[\\p{L}\\p{N}]/u.test(old.text)) rects.push(union(old.rect, moved.rect));\n }\n return { runs, rects };\n}\n\n/** What happened to one capture run that contains a dynamic value. */\nexport type DynamicRunOutcome =\n | {\n status: 'ignored';\n kind: string;\n rect: VisualRect;\n before: string;\n after: string;\n /** Boxes of separators (text with no letter or digit) that only slid by the value's change in width. */\n shifted: VisualRect[];\n }\n /** The same text sat in the same spot before: nothing changed there, so nothing is left out. */\n | { status: 'identical'; text: string }\n /** No unused baseline run of the same shape sits at this spot, so the change counts. */\n | { status: 'no_partner'; text: string; skeleton: string }\n /** A partner exists, but other text sits inside the combined box, so the change counts. */\n | { status: 'crowded'; before: string; after: string; rect: VisualRect };\n\n/** One outcome per capture run that contains a dynamic value, in document order. */\nexport function explainDynamicRuns(\n baseline: VisualTextRun[],\n capture: VisualTextRun[]\n): DynamicRunOutcome[] {\n const shaped = (runs: VisualTextRun[]) =>\n runs.flatMap((run) => {\n const shape = dynamicShape(run.text);\n return shape.kinds.length > 0 ? [{ run, ...shape }] : [];\n });\n const available = shaped(baseline);\n return shaped(capture).map((current): DynamicRunOutcome => {\n const partner = takeBestPartner(available, current);\n if (!partner)\n return { status: 'no_partner', text: current.run.text, skeleton: current.skeleton };\n if (partner.run.text === current.run.text)\n return { status: 'identical', text: current.run.text };\n const rect = union(partner.run.rect, current.run.rect);\n const shifts = shiftedNeighbours(baseline, capture, partner.run, current.run);\n // The box also covers whatever else sits there on either side. Text that appeared where a\n // shorter value used to end is caught here; icons, images and other non-text content are not.\n // Text that only slid sideways with the value (\"·\", \"Passed\" after a longer date) is not new.\n const crowded = [...baseline, ...capture].some(\n (other) =>\n other !== partner.run &&\n other !== current.run &&\n !shifts.runs.has(other) &&\n intersects(other.rect, rect)\n );\n return crowded\n ? { status: 'crowded', before: partner.run.text, after: current.run.text, rect }\n : {\n status: 'ignored',\n kind: [...new Set(current.kinds)].join('+'),\n rect,\n before: partner.run.text,\n after: current.run.text,\n shifted: shifts.rects,\n };\n });\n}\n\n/**\n * Regions to leave out of the pixel diff: capture runs whose text differs from a baseline run in\n * the same spot only inside dynamic parts. A run with no partner, or whose partner has a different\n * shape, is not returned, so whatever changed there is compared as usual. Identical text is never\n * returned, so an unrelated change under a stable date still shows.\n */\nexport function pairDynamicRuns(\n baseline: VisualTextRun[],\n capture: VisualTextRun[]\n): VisualAutoIgnore[] {\n return explainDynamicRuns(baseline, capture).flatMap((outcome) =>\n outcome.status === 'ignored'\n ? [\n { kind: outcome.kind, rect: outcome.rect },\n ...outcome.shifted.map((rect) => ({ kind: 'shift', rect })),\n ]\n : []\n );\n}\n\n/* ── Ignore rules ────────────────────────────────────────────────────────── */\n\nconst VISUAL_TEXT_LIMITS = {\n maxRuns: 400,\n maxChars: 200,\n maxVisitedNodes: 5_000,\n /** Text wrapped over more lines than this is a paragraph, not a value. */\n maxLines: 4,\n maxCoordinate: 100_000,\n} as const;\n\nconst isBoundedRect = (value: unknown) => {\n if (typeof value !== 'object' || value === null) return false;\n const rect = value as Record<string, unknown>;\n const bounded = (key: string) =>\n typeof rect[key] === 'number' &&\n Number.isFinite(rect[key]) &&\n Math.abs(rect[key]) <= VISUAL_TEXT_LIMITS.maxCoordinate;\n return (\n bounded('x') &&\n bounded('y') &&\n bounded('width') &&\n bounded('height') &&\n (rect.width as number) > 0 &&\n (rect.height as number) > 0\n );\n};\n\n/**\n * What a context fact may look like. The page script below builds its output from these same\n * values, so a well-behaved page always passes; only forged output fails, and then the whole read\n * is refused like any other malformed output.\n */\nconst CONTEXT_TAG = /^[a-z][a-z0-9-]{0,31}$/;\nconst CONTEXT_ROLE = /^[a-z]{1,32}$/;\nconst CONTAINERS: ReadonlySet<string> = new Set(VISUAL_TEXT_CONTAINERS);\nconst LANDMARKS: ReadonlySet<string> = new Set(VISUAL_TEXT_LANDMARKS);\n\nconst isBoundedContext = (value: unknown) => {\n if (typeof value !== 'object' || value === null) return false;\n const { tag, role, level, container, landmark } = value as Record<string, unknown>;\n return (\n typeof tag === 'string' &&\n CONTEXT_TAG.test(tag) &&\n (role === undefined || (typeof role === 'string' && CONTEXT_ROLE.test(role))) &&\n (level === undefined || (Number.isInteger(level) && (level as number) >= 1 && (level as number) <= 6)) &&\n (container === undefined || (typeof container === 'string' && CONTAINERS.has(container))) &&\n (landmark === undefined || (typeof landmark === 'string' && LANDMARKS.has(landmark)))\n );\n};\n\nexport const isVisualTextRuns = (value: unknown): value is VisualTextRun[] =>\n Array.isArray(value) &&\n value.length <= VISUAL_TEXT_LIMITS.maxRuns &&\n value.every(\n (run) =>\n typeof run === 'object' &&\n run !== null &&\n typeof (run as Record<string, unknown>).text === 'string' &&\n ((run as Record<string, unknown>).text as string).length <= VISUAL_TEXT_LIMITS.maxChars &&\n isBoundedRect((run as Record<string, unknown>).rect) &&\n isBoundedContext((run as Record<string, unknown>).context)\n );\n\n/**\n * A function expression, not an invoked one: both browser clients call `(${fn})()` themselves.\n * Reads only; the page the agent keeps driving is left exactly as it was. Rects are scaled by the\n * device pixel ratio because the screenshot is taken in device pixels.\n */\nexport const VISUAL_TEXT_RUNS_SCRIPT = `(cssPixels = false) => {\n const runs = [];\n // Generic DOM facts per run (see VisualTextContext), read on one walk up from the text's element.\n const landmarkRoles = new Set(${JSON.stringify(VISUAL_TEXT_LANDMARKS)});\n const landmarkTags = { NAV: 'navigation', MAIN: 'main', ASIDE: 'complementary', DIALOG: 'dialog', HEADER: 'banner', FOOTER: 'contentinfo' };\n // A header or footer inside one of these belongs to that section, not to the page.\n const sectioning = new Set(['ARTICLE', 'ASIDE', 'MAIN', 'NAV', 'SECTION']);\n const roleTags = { BUTTON: 'button', TH: 'columnheader', TD: 'cell', LI: 'listitem', OPTION: 'option', INPUT: 'textbox', TEXTAREA: 'textbox', SELECT: 'combobox', H1: 'heading', H2: 'heading', H3: 'heading', H4: 'heading', H5: 'heading', H6: 'heading' };\n const tableRoles = new Set(['table', 'grid', 'treegrid']);\n const listRoles = new Set(['list', 'listbox', 'menu', 'menubar', 'tree', 'tablist', 'radiogroup']);\n const tagPattern = ${CONTEXT_TAG};\n const rolePattern = ${CONTEXT_ROLE};\n const inSectioning = (el) => {\n for (let up = el.parentElement; up && up !== document.body; up = up.parentElement)\n if (sectioning.has(up.tagName)) return true;\n return false;\n };\n const tagLandmark = (el) =>\n (el.tagName === 'HEADER' || el.tagName === 'FOOTER') && inSectioning(el) ? undefined : landmarkTags[el.tagName];\n const describe = (start) => {\n const tag = start.tagName.toLowerCase();\n const context = { tag: tagPattern.test(tag) ? tag : 'unknown' };\n for (let el = start; el && el !== document.body; el = el.parentElement) {\n const token = (el.getAttribute('role') || '').trim().split(/\\\\s+/)[0].toLowerCase();\n const explicit = token === 'alertdialog' ? 'dialog' : token;\n const landmark = landmarkRoles.has(explicit) ? explicit : explicit ? undefined : tagLandmark(el);\n if (landmark) {\n context.landmark = landmark;\n break;\n }\n if (!context.container) {\n if (tableRoles.has(explicit) || (!explicit && el.tagName === 'TABLE')) context.container = 'table';\n else if (listRoles.has(explicit) || (!explicit && ['UL', 'OL', 'DL', 'MENU'].includes(el.tagName))) context.container = 'list';\n }\n if (context.role || explicit === 'presentation' || explicit === 'none') continue;\n // An explicit role that is not a plain word (doc-*, a typo with digits) gives no role at all.\n const implicit = el.tagName === 'A' ? (el.getAttribute('href') === null ? undefined : 'link') : roleTags[el.tagName];\n const found = explicit ? (rolePattern.test(explicit) ? explicit : undefined) : implicit;\n if (!found) continue;\n context.role = found;\n if (found !== 'heading') continue;\n const level = /^H[1-6]$/.test(el.tagName) ? Number(el.tagName[1]) : Number(el.getAttribute('aria-level'));\n if (Number.isInteger(level) && level >= 1 && level <= 6) context.level = level;\n }\n return context;\n };\n const skip = new Set(['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA']);\n const scale = cssPixels ? 1 : (window.devicePixelRatio || 1);\n const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT);\n const range = document.createRange();\n let visited = 0;\n for (\n let node = walker.nextNode();\n node && runs.length < ${VISUAL_TEXT_LIMITS.maxRuns} && visited < ${VISUAL_TEXT_LIMITS.maxVisitedNodes};\n node = walker.nextNode()\n ) {\n visited++;\n const text = (node.nodeValue || '').replace(/\\\\s+/g, ' ').trim();\n const el = node.parentElement;\n if (!text || text.length > ${VISUAL_TEXT_LIMITS.maxChars} || !el || skip.has(el.tagName)) continue;\n if (!el.checkVisibility({ opacityProperty: true, visibilityProperty: true })) continue;\n range.selectNodeContents(node);\n // Wrapped text is one run whose box spans its lines; pairDynamicRuns refuses a box that\n // also covers other text. A paragraph (more lines than a value wraps over) is skipped.\n const lines = range.getClientRects().length;\n if (lines < 1 || lines > ${VISUAL_TEXT_LIMITS.maxLines}) continue;\n const r = range.getBoundingClientRect();\n if (r.width <= 0 || r.height <= 0 || r.bottom <= 0 || r.right <= 0) continue;\n if (r.top >= window.innerHeight || r.left >= window.innerWidth) continue;\n // Text clipped by its element (ellipsis, overflow hidden) has a layout box wider than what shows.\n const box = el.getBoundingClientRect();\n if (r.right > box.right + 1 || r.bottom > box.bottom + 1 || r.left < box.left - 1 || r.top < box.top - 1) continue;\n // Text under a modal, drawer or sticky bar is not what the screenshot shows at that spot.\n const hit = document.elementFromPoint(r.left + r.width / 2, r.top + r.height / 2);\n if (!hit || !(el.contains(hit) || hit.contains(el))) continue;\n runs.push({\n text,\n rect: {\n x: Math.floor(r.left * scale),\n y: Math.floor(r.top * scale),\n width: Math.ceil(r.width * scale),\n height: Math.ceil(r.height * scale),\n },\n context: describe(el),\n });\n }\n // What was typed into a field is not a text node. Passwords, card and secret-looking fields, and\n // non-text inputs are never read.\n const readable = new Set(['text', 'email', 'search', 'url', 'tel', 'number']);\n for (const field of document.querySelectorAll('input, textarea')) {\n if (runs.length >= ${VISUAL_TEXT_LIMITS.maxRuns}) break;\n if (field.tagName === 'INPUT' && !readable.has(field.type)) continue;\n // A show-password toggle turns a password field into type=text; card and one-time-code fields too.\n if (/cc-|password|one-time-code/i.test(field.autocomplete || '')) continue;\n if (/pass|pwd|secret|token|otp|cvv|cvc|card/i.test((field.name || '') + ' ' + (field.id || ''))) continue;\n const text = (field.value || '').replace(/\\\\s+/g, ' ').trim();\n if (!text || text.length > ${VISUAL_TEXT_LIMITS.maxChars}) continue;\n if (!field.checkVisibility({ opacityProperty: true, visibilityProperty: true })) continue;\n const r = field.getBoundingClientRect();\n if (r.width <= 0 || r.height <= 0 || r.bottom <= 0 || r.right <= 0) continue;\n if (r.top >= window.innerHeight || r.left >= window.innerWidth) continue;\n const hit = document.elementFromPoint(r.left + r.width / 2, r.top + r.height / 2);\n if (hit !== field) continue;\n runs.push({\n text,\n rect: {\n x: Math.floor(r.left * scale),\n y: Math.floor(r.top * scale),\n width: Math.ceil(r.width * scale),\n height: Math.ceil(r.height * scale),\n },\n context: describe(field),\n });\n }\n return JSON.stringify(runs);\n}`;\n\n/** Payload returned by the anchor script, shared by target resolution and capture. */\nexport const VisualAnchorCaptureSchema = z.object({\n elements: z.array(z.object({\n index: z.number().int().nonnegative(),\n rect: z.object({ x: z.number(), y: z.number(), width: z.number().positive(), height: z.number().positive() }),\n locator: z.string().min(1),\n label: z.string(),\n })).max(400),\n containers: z.array(z.object({\n label: z.string(),\n kind: z.string(),\n candidateLocators: z.array(z.string().min(1)),\n })).max(100),\n});\nexport type VisualAnchorCapture = z.infer<typeof VisualAnchorCaptureSchema>;\n\n/** Collects stable anchors from one page walk; all text/media locators are unique in the DOM. */\nexport const VISUAL_ANCHORS_SCRIPT = `() => {\n const generatedId = /^(?:[0-9]+|[a-f0-9]{8,}|radix-|headlessui-|react-aria-|:r[0-9a-z]+:)$/i;\n const cssEscape = (value) => CSS.escape(value);\n const cssPath = (el) => {\n const parts = [];\n for (let node = el; node && node.nodeType === 1 && node !== document.body; node = node.parentElement) {\n let part = node.tagName.toLowerCase();\n const siblings = node.parentElement ? [...node.parentElement.children].filter((child) => child.tagName === node.tagName) : [];\n if (siblings.length > 1) part += ':nth-of-type(' + (siblings.indexOf(node) + 1) + ')';\n parts.unshift(part);\n const candidate = parts.join(' > ');\n try { if (document.querySelectorAll(candidate).length === 1) return candidate; } catch { /* Keep walking toward a valid unique path. */ }\n }\n return parts.join(' > ');\n };\n const accessibleName = (el) => {\n const labelledBy = el.getAttribute('aria-labelledby');\n if (labelledBy) {\n const name = labelledBy.split(/\\\\s+/).map((id) => document.getElementById(id)?.innerText || '').join(' ').trim();\n if (name) return name;\n }\n return (el.getAttribute('aria-label') || el.getAttribute('alt') || el.getAttribute('title') || '').replace(/\\\\s+/g, ' ').trim().slice(0, 120);\n };\n const roleOf = (el) => el.getAttribute('role') || ({ BUTTON: 'button', A: el.hasAttribute('href') ? 'link' : '', IMG: 'img', TABLE: 'table', FORM: 'form', NAV: 'navigation', MAIN: 'main', DIALOG: 'dialog', UL: 'list', OL: 'list' }[el.tagName] || '');\n const rectOf = (el) => {\n const r = el.getBoundingClientRect();\n return { x: Math.floor(r.left), y: Math.floor(r.top), width: Math.ceil(r.width), height: Math.ceil(r.height) };\n };\n const uniqueCss = (selector) => { try { return document.querySelectorAll(selector).length === 1; } catch { return false; } };\n const candidates = (el) => {\n const out = [];\n const testId = el.getAttribute('data-testid');\n if (testId) out.push('[data-testid=\"' + CSS.escape(testId) + '\"]');\n if (el.id && !generatedId.test(el.id)) out.push('#' + cssEscape(el.id));\n const role = roleOf(el), name = accessibleName(el);\n if (role && name) out.push('role=' + role + '[name=' + JSON.stringify(name) + ']');\n out.push(cssPath(el));\n return [...new Set(out)];\n };\n const rendered = (el) => {\n if (!el.checkVisibility({ opacityProperty: true, visibilityProperty: true })) return false;\n const r = el.getBoundingClientRect();\n return r.width > 0 && r.height > 0;\n };\n const inViewport = (el) => {\n const r = el.getBoundingClientRect();\n return r.bottom > 0 && r.right > 0 && r.top < innerHeight && r.left < innerWidth;\n };\n const visible = (el) => rendered(el) && inViewport(el);\n const elements = [];\n for (const el of document.querySelectorAll('body *')) {\n if (elements.length >= 400 || !visible(el)) continue;\n const media = /^(IMG|CANVAS|SVG|VIDEO|IFRAME)$/.test(el.tagName);\n const hasDirectText = [...el.childNodes].some((node) => node.nodeType === Node.TEXT_NODE && (node.nodeValue || '').trim());\n if (!media && !hasDirectText) continue;\n const label = accessibleName(el) || (el.innerText || '').replace(/\\\\s+/g, ' ').trim().slice(0, 160) || el.tagName.toLowerCase();\n const locator = candidates(el).find((candidate) => !candidate.startsWith('role=') && uniqueCss(candidate));\n if (!locator) continue;\n elements.push({ index: elements.length, rect: rectOf(el), locator, label });\n }\n const containers = [];\n const semanticSelector = 'table,[role=\"table\"],[role=\"grid\"],section,article,form,[role=\"dialog\"],dialog,ul,ol,[role=\"list\"],[role=\"region\"],main,nav,aside,[role=\"main\"],[role=\"navigation\"],[role=\"complementary\"]';\n const semantic = [...document.querySelectorAll(semanticSelector)];\n const cards = [...document.querySelectorAll('[data-testid],[class],[role=\"group\"]')].filter((el) => {\n const identity = (el.getAttribute('data-testid') || '') + ' ' + String(el.className || '');\n const hasHeading = el.matches('div') && el.querySelector(':scope > h1,:scope > h2,:scope > h3,:scope > h4,:scope > h5,:scope > h6');\n return /card/i.test(identity) || el.getAttribute('role') === 'group' || hasHeading;\n });\n const seenContainers = new Set();\n for (const el of [...semantic, ...cards]) {\n if (containers.length >= 100 || seenContainers.has(el) || !rendered(el)) continue;\n seenContainers.add(el);\n const role = roleOf(el), kind = role || el.tagName.toLowerCase();\n const heading = el.querySelector('h1,h2,h3,h4,h5,h6')?.innerText || '';\n const label = accessibleName(el) || heading.replace(/\\\\s+/g, ' ').trim() || (el.innerText || '').replace(/\\\\s+/g, ' ').trim().slice(0, 80) || kind;\n containers.push({ label: kind + ': ' + label, kind, candidateLocators: candidates(el) });\n }\n return JSON.stringify({ elements, containers });\n}`;\n","import {\n assertionSpecSchema,\n cachedRecipeOracleSchema,\n cachedDataScriptSchema,\n cacheReplayableActionTypeSchema,\n selectorStrategySchema,\n} from './workflow-cache.js';\n\nexport type CacheActionContractInput = {\n actionType: unknown;\n args?: unknown;\n selectors?: unknown;\n assertion?: unknown;\n code?: unknown;\n recipeOracle?: unknown;\n};\n\nexport type CacheActionContractIssue = {\n path: PropertyKey[];\n message: string;\n};\n\nexport type CacheActionContractContext = {\n /** Stored data-script artifacts available to execute data actions. */\n dataScripts?: unknown;\n};\n\nconst isRecordValue = (value: unknown): value is Record<string, unknown> =>\n value !== null && typeof value === 'object' && !Array.isArray(value);\n\nconst isNonBlankString = (value: unknown): value is string =>\n typeof value === 'string' && value.trim().length > 0;\n\nconst isPositiveIntegerValue = (value: unknown): boolean => {\n const parsed =\n typeof value === 'number'\n ? value\n : typeof value === 'string' && value.trim() !== ''\n ? Number(value)\n : NaN;\n return Number.isInteger(parsed) && parsed > 0;\n};\n\nconst isFiniteNumberValue = (value: unknown): boolean => {\n const parsed =\n typeof value === 'number'\n ? value\n : typeof value === 'string' && value.trim() !== ''\n ? Number(value)\n : NaN;\n return Number.isFinite(parsed);\n};\n\n/**\n * Conservative, syntax-only detection of the authoring shape that lets the\n * evaluator invent a verdict from JS truthiness\n * (`packages/browser-core/src/assertion-evaluator.ts`): a `playwright` or\n * `evaluate` assertion whose condition's sole return is a bare\n * `.textContent()` / `.innerText()` / `.inputValue()` / `.count()` call, or a\n * template literal, with no `expected` to compare against.\n *\n * This is deliberately narrow and can only reject what is syntactically\n * evident in the condition string. It gives up (returns `false`, i.e. \"not\n * obviously bad\") whenever the shape is ambiguous:\n * - more than one `return` in a `playwright` body (branching/multi-path code)\n * - the return expression is wrapped in any operator (`!`, `===`, `>`, `&&`,\n * a ternary, string concatenation, …) — those can and do coerce to\n * boolean, e.g. `!!x.textContent()` or `x.count() > 0` are legitimate and\n * must NOT be flagged\n * - the risky call carries arguments (`.textContent(opts)` isn't a shape we\n * recognize, so we don't guess)\n *\n * \"Does this snippet return a non-boolean\" is not decidable in general\n * (arbitrary expressions, helper calls, branching). The runtime check in\n * `assertion-evaluator.ts` is the actual guarantee — every stored assertion\n * re-evaluates live on replay, so anything this heuristic misses is still\n * caught there, just one run later. This function exists only to reject the\n * single most common mistake early, at the five ingresses this contract\n * already sits behind (commit admission, replay admission, the CLI\n * cache-recipe push route, external tests, and the optimizer's playbook\n * compiler). It does NOT run at live `browser_assert` authoring —\n * `apps/api/src/ai/tools/assertTool.ts` records the agent's spec directly and\n * does not call this contract — so a live-authored instance of this mistake\n * is caught only by boundary 1, on the next replay.\n */\n// `playwright` conditions call Playwright's Locator API, where these are\n// async methods (`locator.textContent()`, `locator.count()`). `evaluate`\n// conditions run inside the page as plain DOM access, where the same names\n// are properties, never calls (`element.textContent`, no parens). The risky\n// shape is therefore call-shaped for one and property-shaped for the other.\n//\n// Match: \"await page.locator('.task-count').textContent()\"\n// Match: \"page.getByRole('status').count()\"\n// No match: \"await page.locator('.count-badge').count() > 0\" (wrapped — a\n// legitimate boolean; trailing content after the call breaks `$`)\n// No match: \"await page.locator('.x').textContent(opts)\" (call carries args)\nconst PLAYWRIGHT_BARE_NON_BOOLEAN_RETURN_RE =\n /^[A-Za-z_$][\\w$]*(?:\\.[A-Za-z_$][\\w$]*(?:\\([^()]*\\))?)*\\.(?:textContent|innerText|inputValue|count)\\(\\s*\\)$/;\n// Match: \"document.querySelector('.task-count').textContent\"\n// Match: \"document.querySelector('input').value\"\n// No match: \"document.querySelector('.x').textContent === 'Done'\" (wrapped)\n// No match: \"document.querySelector('.x').textContent.length\" (chained past\n// the risky property onto something else — no longer a bare read)\n//\n// `.value` is the one entry here whose type is not guaranteed non-boolean —\n// a Vue `ref<boolean>().value` is a legitimate boolean read with this exact\n// shape. Left in anyway: boundary 1 (the runtime evaluator) is the actual\n// guarantee and lets a real boolean through regardless of what this\n// heuristic guesses, so the cost of including `.value` is an occasional\n// early rejection of a legitimate condition rather than a missed unsafe one.\nconst EVALUATE_BARE_NON_BOOLEAN_RETURN_RE =\n /^[A-Za-z_$][\\w$]*(?:\\.[A-Za-z_$][\\w$]*(?:\\([^()]*\\))?)*\\.(?:textContent|innerText|innerHTML|value)$/;\n\n// A template literal is unambiguously a string in JS regardless of what its\n// interpolations compute, so this needs no operator-wrapping exclusion.\n// Match: \"`${a}|${b}|${c}`\"\n// No match: \"`${a}` === 'x'\" (wrapped in a comparison — a legitimate boolean)\n// No match: \"`` \" (empty; `length > 1` rejects it rather than guessing)\nconst isBareTemplateLiteral = (expr: string): boolean =>\n expr.startsWith('`') && expr.endsWith('`') && expr.length > 1 && expr.indexOf('`', 1) === expr.length - 1;\n\n/**\n * Extract the sole return expression to evaluate for the non-boolean-return\n * heuristic, or `undefined` when the shape is too ambiguous to decide\n * statically. `playwright` conditions are a statement list (`return X;`);\n * `evaluate` conditions are a single JS expression, optionally wrapped in an\n * arrow function (`page.evaluate(condition)` semantics — see\n * `validateEvaluateAssertionExpression` in `@chatsdet/browser-core`, which\n * this package cannot import without a circular dependency).\n */\nconst extractSoleReturnExpression = (\n condition: string,\n type: 'playwright' | 'evaluate'\n): string | undefined => {\n const trimmed = condition.trim();\n if (type === 'evaluate') {\n const arrowMatch =\n trimmed.match(/^\\([^()]*\\)\\s*=>\\s*([\\s\\S]+)$/) ??\n trimmed.match(/^[A-Za-z_$][\\w$]*\\s*=>\\s*([\\s\\S]+)$/);\n return (arrowMatch ? arrowMatch[1] : trimmed).trim().replace(/;\\s*$/, '');\n }\n const returns = [...trimmed.matchAll(/\\breturn\\s+([^;]+?);?\\s*(?:\\n|$)/g)];\n if (returns.length !== 1) return undefined;\n return returns[0][1].trim();\n};\n\nconst returnsObviousNonBoolean = (condition: string, type: 'playwright' | 'evaluate'): boolean => {\n const expr = extractSoleReturnExpression(condition, type);\n if (!expr) return false;\n const stripped = expr.replace(/^await\\s+/, '').trim();\n if (isBareTemplateLiteral(stripped)) return true;\n const suffixPattern =\n type === 'playwright' ? PLAYWRIGHT_BARE_NON_BOOLEAN_RETURN_RE : EVALUATE_BARE_NON_BOOLEAN_RETURN_RE;\n return suffixPattern.test(stripped);\n};\n\n/**\n * Validate the executable portion of a cached action.\n *\n * This is deliberately a pure issue collector rather than a second action\n * type. Planned optimizer actions and stored cache actions have different\n * metadata, but the payload replay consumes must obey one contract at route\n * admission, final recipe construction, and replay trust validation.\n */\nexport function cacheActionContractIssues(\n input: unknown,\n context: CacheActionContractContext = {}\n): CacheActionContractIssue[] {\n const issues: CacheActionContractIssue[] = [];\n const add = (path: PropertyKey[], message: string) => issues.push({ path, message });\n const validateDataScript = (script: unknown, scriptIndex: number) => {\n const parsed = cachedDataScriptSchema.safeParse(script);\n if (!parsed.success) {\n for (const issue of parsed.error.issues) {\n add(['dataScriptsJson', scriptIndex, ...issue.path], issue.message);\n }\n }\n };\n\n if (!isRecordValue(input)) {\n add([], 'Expected an action object');\n return issues;\n }\n const action: CacheActionContractInput = {\n actionType: input.actionType,\n args: input.args,\n selectors: input.selectors,\n assertion: input.assertion,\n code: input.code,\n recipeOracle: input.recipeOracle,\n };\n\n const actionTypeResult = cacheReplayableActionTypeSchema.safeParse(action.actionType);\n if (!actionTypeResult.success) {\n const actionType = String(action.actionType);\n const hint =\n actionType === 'fill'\n ? '; use \"type\" for fill steps'\n : actionType === 'select'\n ? '; use \"select_option\" for select steps'\n : '';\n add(['actionType'], `Action type \"${actionType}\" is not replayable${hint}`);\n return issues;\n }\n const actionType = actionTypeResult.data;\n\n if (action.recipeOracle !== undefined && actionType !== 'run_code') {\n add(['recipeOracle'], 'Recipe oracles are only valid on run_code actions');\n }\n\n const args = isRecordValue(action.args) ? action.args : null;\n if (!args) {\n add(['args'], 'Expected an object');\n return issues;\n }\n\n if (action.selectors !== undefined) {\n const parsed = selectorStrategySchema.safeParse(action.selectors);\n if (!parsed.success) {\n for (const issue of parsed.error.issues) {\n add(['selectors', ...issue.path], issue.message);\n }\n }\n }\n const requireSelectors = () => {\n if (action.selectors === undefined) {\n add(['selectors'], `Expected selectors for ${actionType}`);\n }\n };\n\n switch (actionType) {\n case 'navigate':\n if (args.direction !== 'back' && !isNonBlankString(args.url)) {\n add([], 'Navigate missing url and direction');\n }\n break;\n case 'click':\n if (args.doubleClick !== undefined && typeof args.doubleClick !== 'boolean') {\n add(['args', 'doubleClick'], 'Expected a boolean');\n }\n if (\n args.button !== undefined &&\n args.button !== 'left' &&\n args.button !== 'right' &&\n args.button !== 'middle'\n ) {\n add(['args', 'button'], 'Expected \"left\", \"right\", or \"middle\"');\n }\n if (\n args.modifiers !== undefined &&\n (!Array.isArray(args.modifiers) ||\n args.modifiers.some(\n (modifier) =>\n modifier !== 'Alt' &&\n modifier !== 'Control' &&\n modifier !== 'Meta' &&\n modifier !== 'Shift'\n ))\n ) {\n add(\n ['args', 'modifiers'],\n 'Expected an array containing only \"Alt\", \"Control\", \"Meta\", or \"Shift\"'\n );\n }\n requireSelectors();\n break;\n case 'hover':\n requireSelectors();\n break;\n case 'type':\n requireSelectors();\n if (!isNonBlankString(args.text) && !isNonBlankString(args.value)) {\n add(['args', 'text'], 'Expected non-empty text or value');\n }\n if (args.submit !== undefined && typeof args.submit !== 'boolean') {\n add(['args', 'submit'], 'Expected a boolean');\n }\n break;\n case 'press_key':\n if (!isNonBlankString(args.key)) add(['args', 'key'], 'Expected a non-empty string');\n break;\n case 'select_option':\n requireSelectors();\n if (!isNonBlankString(args.value)) {\n add(['args', 'value'], 'Expected a non-empty string');\n }\n break;\n case 'fill_form': {\n const fields = args.fields;\n if (!Array.isArray(fields) || fields.length === 0) {\n add(['args', 'fields'], 'Expected a non-empty array');\n break;\n }\n for (const [fieldIndex, field] of fields.entries()) {\n const fieldPath: PropertyKey[] = ['args', 'fields', fieldIndex];\n if (!isRecordValue(field)) {\n add(fieldPath, 'Expected an object');\n continue;\n }\n if (!isNonBlankString(field.value)) {\n add([...fieldPath, 'value'], 'Expected a non-empty string');\n }\n if (field.selectors === undefined) {\n add([...fieldPath, 'selectors'], 'Expected selectors for fill_form field');\n continue;\n }\n const parsed = selectorStrategySchema.safeParse(field.selectors);\n if (!parsed.success) {\n for (const issue of parsed.error.issues) {\n add([...fieldPath, 'selectors', ...issue.path], issue.message);\n }\n }\n }\n break;\n }\n case 'resize':\n if (!isPositiveIntegerValue(args.width)) {\n add(['args', 'width'], 'Expected a positive integer');\n }\n if (!isPositiveIntegerValue(args.height)) {\n add(['args', 'height'], 'Expected a positive integer');\n }\n break;\n case 'scroll':\n if (args.mode === 'coordinates') {\n if (!isFiniteNumberValue(args.x)) add(['args', 'x'], 'Expected a finite number');\n if (!isFiniteNumberValue(args.y)) add(['args', 'y'], 'Expected a finite number');\n } else if (\n (args.mode === 'toRef' || args.mode === 'withinRef') &&\n action.selectors === undefined\n ) {\n add(['selectors'], `Expected selectors for scroll mode ${String(args.mode)}`);\n }\n break;\n case 'screenshot':\n break;\n case 'file_upload':\n if (\n !Array.isArray(args.paths) ||\n args.paths.length === 0 ||\n args.paths.some((path) => !isNonBlankString(path))\n ) {\n add(['args', 'paths'], 'Expected a non-empty array of file paths');\n }\n break;\n case 'move':\n if (typeof args.x !== 'number' || !Number.isFinite(args.x)) {\n add(['args', 'x'], 'Expected a finite number');\n }\n if (typeof args.y !== 'number' || !Number.isFinite(args.y)) {\n add(['args', 'y'], 'Expected a finite number');\n }\n break;\n case 'wait_for': {\n const hasCondition =\n (typeof args.timeSec === 'number' && Number.isFinite(args.timeSec) && args.timeSec > 0) ||\n isNonBlankString(args.text) ||\n isNonBlankString(args.textGone) ||\n isNonBlankString(args.selector);\n if (!hasCondition) {\n add([], 'Wait_for requires positive timeSec or non-empty text, textGone, or selector');\n }\n break;\n }\n case 'handle_dialog':\n if (args.action !== 'accept' && args.action !== 'dismiss') {\n add(['args', 'action'], 'Expected \"accept\" or \"dismiss\"');\n }\n if (args.promptText !== undefined && typeof args.promptText !== 'string') {\n add(['args', 'promptText'], 'Expected a string');\n }\n break;\n case 'annotation':\n break;\n case 'assert': {\n const parsed = assertionSpecSchema.safeParse(action.assertion);\n if (!parsed.success) {\n for (const issue of parsed.error.issues) {\n add(['assertion', ...issue.path], issue.message);\n }\n } else if (!parsed.data.condition.trim()) {\n add(['assertion', 'condition'], 'Expected a non-empty string');\n } else if (\n parsed.data.expected === undefined &&\n (parsed.data.type === 'playwright' || parsed.data.type === 'evaluate') &&\n returnsObviousNonBoolean(parsed.data.condition, parsed.data.type)\n ) {\n add(\n ['assertion', 'expected'],\n 'Assertion condition returns a non-boolean value with no \"expected\" to compare ' +\n 'against — the evaluator cannot judge pass/fail from truthiness. Return a boolean, ' +\n 'or add \"expected\" to compare the returned value against.'\n );\n }\n break;\n }\n case 'capture':\n if (!isNonBlankString(args.expression)) {\n add(['args', 'expression'], 'Expected a non-empty string');\n }\n if (!isNonBlankString(args.saveAs)) {\n add(['args', 'saveAs'], 'Expected a non-empty string');\n }\n break;\n case 'extract_table':\n if (!isNonBlankString(args.name)) {\n add(['args', 'name'], 'Expected a non-empty string');\n } else {\n const dataScripts = Array.isArray(context.dataScripts) ? context.dataScripts : [];\n const scriptIndex = dataScripts.findIndex(\n (script) => isRecordValue(script) && script.tableName === args.name\n );\n if (scriptIndex < 0) {\n add(['dataScriptsJson'], `Expected a data script for table \"${args.name}\"`);\n } else {\n validateDataScript(dataScripts[scriptIndex], scriptIndex);\n }\n }\n break;\n case 'python_execute':\n if (!isNonBlankString(args.cacheLabel)) {\n add(['args', 'cacheLabel'], 'Expected a non-empty string');\n } else {\n const dataScripts = Array.isArray(context.dataScripts) ? context.dataScripts : [];\n const scriptIndex = dataScripts.findIndex(\n (script) => isRecordValue(script) && script.label === args.cacheLabel\n );\n if (scriptIndex < 0) {\n add(['dataScriptsJson'], `Expected a data script labeled \"${args.cacheLabel}\"`);\n } else {\n validateDataScript(dataScripts[scriptIndex], scriptIndex);\n }\n }\n break;\n case 'run_code':\n if (!isNonBlankString(action.code)) {\n add(['code'], 'Expected a non-empty string');\n } else if (action.code.length > 16_000) {\n add(['code'], 'Expected at most 16000 characters');\n }\n if (action.recipeOracle !== undefined) {\n const parsed = cachedRecipeOracleSchema.safeParse(action.recipeOracle);\n if (!parsed.success) {\n for (const issue of parsed.error.issues) {\n add(['recipeOracle', ...issue.path], issue.message);\n }\n }\n if (typeof action.code === 'string' && !action.code.includes('__evaluateRecipeOracle')) {\n add(['code'], 'Composite recipe code must invoke the server-authored oracle binding');\n }\n }\n break;\n case 'mcp_tool_call':\n // Minted exclusively by the mcp node lane (mcp-cache-save.ts) and\n // replayed by mcp-replay.ts. Browser recipe surfaces (optimizer commit,\n // CLI cache-recipe route) must not author it — the browser replay\n // runtime cannot execute it.\n add([], 'mcp_tool_call actions are minted by the mcp node lane and cannot be authored here');\n break;\n }\n\n return issues;\n}\n","/**\n * Local Browser Protocol Types\n *\n * WebSocket message protocol for communication between the CLI (LocalBrowserHost)\n * and the cloud API (RemoteBrowserClient). Enables cloud agents to control a\n * browser running on the user's local machine.\n */\n\n// ============================================================================\n// Session Types\n// ============================================================================\n\nexport type LocalBrowserMode = 'playwright' | 'cdp';\n\nexport type LocalBrowserSessionStatus =\n | 'pending' // Session created, waiting for CLI to connect\n | 'connecting' // CLI is connecting\n | 'connected' // CLI connected, browser ready\n | 'running' // Agent is actively using browser\n | 'disconnected' // CLI disconnected\n | 'expired' // Session timed out\n | 'error'; // Error occurred\n\nexport type LocalBrowserSession = {\n id: string;\n orgId: string;\n createdBy: string | null;\n status: LocalBrowserSessionStatus;\n browserMode: LocalBrowserMode;\n instructions: string | null;\n startUrl: string | null;\n jobId: string | null;\n connectedAt: string | null;\n disconnectedAt: string | null;\n expiresAt: string | null;\n createdAt: string;\n updatedAt: string;\n};\n\n// ============================================================================\n// WebSocket Message Types\n// ============================================================================\n\n/**\n * Base message envelope for all WebSocket messages.\n */\ninterface BaseMessage {\n /** Unique message ID for request/response correlation */\n id: string;\n /** Unix timestamp in milliseconds */\n timestamp: number;\n}\n\n/**\n * Command sent from cloud API to CLI to execute a browser action.\n */\nexport interface BrowserCommand extends BaseMessage {\n type: 'command';\n /** Method name from IBrowserClient interface */\n method: string;\n /** Serialized method arguments */\n args: unknown[];\n /** Optional context ID for multi-context support. Omitted = default context. */\n contextId?: string;\n}\n\n/**\n * Response from CLI to cloud API with execution result.\n */\nexport interface BrowserResponse extends BaseMessage {\n type: 'response';\n /** ID of the original command */\n requestId: string;\n /** Whether the command executed successfully */\n success: boolean;\n /** Return value from the method (if success=true) */\n result?: unknown;\n /** Error message (if success=false) */\n error?: string;\n /** Error stack trace for debugging */\n stack?: string;\n /** Echoed context ID from the originating command */\n contextId?: string;\n}\n\n/**\n * Binary data message for large payloads (screenshots, traces).\n * Data is base64-encoded to work over JSON WebSocket.\n */\nexport interface BinaryDataMessage extends BaseMessage {\n type: 'binary';\n /** ID of the original command this data belongs to */\n requestId: string;\n /** Type of binary data */\n dataType: 'screenshot' | 'video' | 'trace';\n /** Base64-encoded binary data */\n data: string;\n /** MIME type of the data */\n mimeType: string;\n}\n\n/**\n * Heartbeat message for connection health monitoring.\n */\nexport interface HeartbeatMessage extends BaseMessage {\n type: 'heartbeat';\n /** Direction of the heartbeat */\n direction: 'ping' | 'pong';\n}\n\n/**\n * Session lifecycle event message.\n */\nexport interface SessionMessage extends BaseMessage {\n type: 'session';\n /** Type of session event */\n event:\n | 'connected' // CLI successfully connected\n | 'disconnected' // CLI disconnected\n | 'browser_ready' // Browser launched and ready\n | 'browser_closed' // Browser was closed\n | 'error'; // Session error occurred\n /** Browser mode being used */\n browserMode?: LocalBrowserMode;\n /** Error message if event='error' */\n error?: string;\n /** Additional metadata */\n metadata?: Record<string, unknown>;\n}\n\n/**\n * Union of all WebSocket message types.\n */\nexport type LocalBrowserMessage =\n | BrowserCommand\n | BrowserResponse\n | BinaryDataMessage\n | HeartbeatMessage\n | SessionMessage;\n\n// ============================================================================\n// Type Guards\n// ============================================================================\n\nexport function isBrowserCommand(msg: unknown): msg is BrowserCommand {\n return (\n typeof msg === 'object' &&\n msg !== null &&\n (msg as BrowserCommand).type === 'command' &&\n typeof (msg as BrowserCommand).method === 'string'\n );\n}\n\nexport function isBrowserResponse(msg: unknown): msg is BrowserResponse {\n return (\n typeof msg === 'object' &&\n msg !== null &&\n (msg as BrowserResponse).type === 'response' &&\n typeof (msg as BrowserResponse).requestId === 'string'\n );\n}\n\nexport function isBinaryDataMessage(msg: unknown): msg is BinaryDataMessage {\n return (\n typeof msg === 'object' &&\n msg !== null &&\n (msg as BinaryDataMessage).type === 'binary' &&\n typeof (msg as BinaryDataMessage).data === 'string'\n );\n}\n\nexport function isHeartbeatMessage(msg: unknown): msg is HeartbeatMessage {\n return (\n typeof msg === 'object' &&\n msg !== null &&\n (msg as HeartbeatMessage).type === 'heartbeat'\n );\n}\n\nexport function isSessionMessage(msg: unknown): msg is SessionMessage {\n return (\n typeof msg === 'object' &&\n msg !== null &&\n (msg as SessionMessage).type === 'session'\n );\n}\n\n// ============================================================================\n// API Request/Response Types\n// ============================================================================\n\nexport type CreateLocalBrowserSessionRequest = {\n /** Browser mode: 'playwright' for fresh browser, 'cdp' for existing Chrome */\n browserMode?: LocalBrowserMode;\n /** Instructions for the cloud agent */\n instructions?: string | null;\n /** Initial URL to navigate to */\n startUrl?: string | null;\n};\n\nexport type CreateLocalBrowserSessionResponse = {\n /** Session ID */\n sessionId: string;\n /** WebSocket token for CLI connection */\n wsToken: string;\n /** WebSocket URL for CLI to connect to */\n wsUrl: string;\n /** Session expiration time */\n expiresAt: string;\n};\n\nexport type LocalBrowserSessionDetail = {\n session: LocalBrowserSession;\n createdBy: { id: string; name: string | null; email: string | null } | null;\n};\n\nexport type ListLocalBrowserSessionsResponse = {\n items: LocalBrowserSessionDetail[];\n pagination: {\n page: number;\n pageSize: number;\n totalItems: number;\n totalPages: number;\n };\n};\n\n// ============================================================================\n// Job Types\n// ============================================================================\n\nexport const LOCAL_BROWSER_JOB_TYPE = 'local-browser.run' as const;\n\nexport type LocalBrowserJobPayload = {\n sessionId: string;\n instructions: string;\n startUrl?: string | null;\n};\n\n// ============================================================================\n// Constants\n// ============================================================================\n\n/** Default timeout for browser commands in milliseconds */\nexport const BROWSER_COMMAND_TIMEOUT_MS = 60_000;\n\n/** Timeout for navigation commands in milliseconds */\nexport const BROWSER_NAVIGATION_TIMEOUT_MS = 120_000;\n\n/**\n * Transport-level timeout for screenshot/snapshot commands sent to a remote\n * browser (MicroVM guest, local-browser gateway).\n *\n * MUST stay ABOVE the guest-side `TIMEOUTS.SCREENSHOT` (60s, browser-core\n * constants.ts) so the inner cap is the one that fires — the same outer/inner\n * invariant `ARIA_SNAPSHOT_CAP` documents. When this was 30s and the inner cap\n * was raised to 60s, a slow-but-alive page (a cold sandbox reaches\n * DOMContentLoaded at ~21s) would be killed here first, replacing the inner\n * call's diagnosable error with a generic \"command timed out\" — and cancelling\n * exactly the capture the higher inner cap exists to allow.\n *\n * `snapshot` shares this bound and its inner cap (SNAPSHOT, 130s) is still\n * higher, so the same inversion remains for that method. Deliberately NOT fixed\n * here: nothing has exhibited a snapshot slow enough to need it, and this move\n * already widened its headroom 30s -> 75s. Fixing it properly means a separate\n * per-method bound above 130s (and below TOOL_DEFAULT, 150s) — worth doing the\n * first time a snapshot is actually observed dying at this ceiling, not before.\n */\nexport const BROWSER_SCREENSHOT_TIMEOUT_MS = 75_000;\n\n/** Session expiration time in milliseconds (1 hour) */\nexport const SESSION_EXPIRATION_MS = 60 * 60 * 1000;\n\n/** Heartbeat interval in milliseconds */\nexport const HEARTBEAT_INTERVAL_MS = 30_000;\n","/**\n * Benchmark System Types\n *\n * Shared TypeScript types for the benchmark system. Used by:\n * - `@chatsdet/benchmark` package (expectations)\n * - `@chatsdet/db` (repository types)\n * - `apps/api/src/benchmark` (service, routes, scoring)\n * - `apps/web/src/ui/pages/superadmin` (frontend UI)\n *\n * @module @chatsdet/types/benchmark\n * @see docs/api/services/benchmark.md\n */\n\n// Job types that can be benchmarked\nexport type BenchmarkJobType = 'workflow_suite';\n\n// Run status\nexport type BenchmarkRunStatus = 'pending' | 'running' | 'completed' | 'failed' | 'timeout';\n\n// Trigger type\nexport type BenchmarkTriggerType = 'scheduled' | 'manual';\n\n// ---------------------------------------------------------------------------\n// Workflow Suite Benchmark Types\n// ---------------------------------------------------------------------------\n\nexport type BenchmarkWorkflowRunStatus = 'pending' | 'running' | 'completed' | 'failed' | 'skipped';\n\n/**\n * Configuration for workflow suite benchmarks (stored in jobConfig jsonb)\n */\nexport interface BenchmarkWorkflowSuiteConfig {\n runsPerWorkflow?: number;\n runTimeoutMinutes?: number;\n suiteTimeoutHours?: number;\n sendSlackSummary?: boolean;\n}\n\nimport { z } from 'zod';\n\nexport const benchmarkWorkflowSuiteConfigSchema = z.object({\n runsPerWorkflow: z.number().int().min(1).max(20).optional(),\n runTimeoutMinutes: z.number().int().min(1).max(720).optional(),\n suiteTimeoutHours: z.number().int().min(1).max(24).optional(),\n sendSlackSummary: z.boolean().optional(),\n});\n\n/**\n * Per-node metrics captured during a benchmark workflow run\n */\nexport interface BenchmarkNodeMetrics {\n nodeId: string;\n nodeName: string;\n nodeType: string;\n durationMs: number;\n inputTokens: number;\n outputTokens: number;\n totalTokens: number;\n status: string;\n cacheOutcome: 'hit' | 'miss' | 'fallback' | 'skipped' | null;\n cacheHitSource: 'replay' | 'recovery' | 'l2_gap' | 'wait_shortcut' | null;\n cacheOutcomeReason: string | null;\n compilationScore?: number | null;\n gapCount?: number | null;\n repairApplied?: boolean | null;\n maxInputTokens: number | null;\n contextWindowTokens: number | null;\n}\n\n/**\n * Aggregated metrics for a single benchmark workflow run\n */\nexport interface BenchmarkWorkflowRunMetrics {\n id: string;\n benchmarkRunId: string;\n sourceWorkflowId: string;\n copyWorkflowId: string;\n workflowName: string;\n runNumber: number;\n workflowRunId: string | null;\n status: BenchmarkWorkflowRunStatus;\n isCached: boolean;\n durationMs: number | null;\n totalTokens: number | null;\n inputTokens: number | null;\n outputTokens: number | null;\n totalNodes: number | null;\n successfulNodes: number | null;\n failedNodes: number | null;\n nodeMetrics: BenchmarkNodeMetrics[] | null;\n errorMessage: string | null;\n startedAt: string | null;\n completedAt: string | null;\n createdAt: string;\n}\n\n/**\n * Result of importing a workflow into the benchmark org\n */\nexport interface BenchmarkImportResult {\n workflowId: string;\n workflowName: string;\n propertyId: string;\n propertyName: string;\n credentialMappings: Array<{\n sourceCredentialId: string;\n newCredentialId: string;\n credentialName: string;\n }>;\n}\n\n// ---------------------------------------------------------------------------\n// Environment Snapshot (captured at suite run time for comparison)\n// ---------------------------------------------------------------------------\n\n/**\n * Structured snapshot of the full execution environment captured at benchmark run time.\n * Enables multi-dimensional comparison between runs to attribute result changes.\n */\nexport interface EnvironmentSnapshot {\n /** Workflow-relevant prompt versions and content hashes */\n prompts: Record<string, { version: number; contentHash: string }>;\n /** Resolved AI provider/model configuration */\n modelConfig: {\n baseProvider: string;\n defaultModel: string;\n contextWindowTokens: number;\n providerOverride: string | null;\n modelOverride: string | null;\n };\n /** Per-workflow flow graph metadata (keyed by source workflow ID) */\n workflows: Record<\n string,\n {\n name: string;\n snapshotVersion: number;\n snapshotHash: string;\n nodeCount: number;\n edgeCount: number;\n }\n >;\n /** Benchmark config metadata at run time */\n benchmarkConfig: {\n version: number;\n provider: string | null;\n model: string | null;\n promptVersion: string | null;\n jobConfig: Record<string, unknown>;\n };\n}\n\nexport interface BenchmarkWorkflowTrendPoint {\n date: string;\n benchmarkRunId: string;\n /** Null for ad-hoc A/B runs (no saved config). */\n configId: string | null;\n workflowName: string;\n runNumber: number;\n isCached: boolean;\n status: BenchmarkWorkflowRunStatus;\n triggerType: BenchmarkTriggerType;\n durationMs: number | null;\n totalTokens: number | null;\n successfulNodes: number | null;\n failedNodes: number | null;\n totalNodes: number | null;\n cachedSteps: number | null;\n l2RecoverySteps: number | null;\n ejectedSteps: number | null;\n agentSteps: number | null;\n skippedSteps: number | null;\n}\n","/**\n * Shared noise filters for captured console/network diagnostics.\n *\n * @module types/diagnostics-filters\n * @description The same \"what counts as noise\" decision has to be made by every\n * producer of diagnostics: the Playwright trace extractor on the host, and the\n * in-container collector that records a customer's Selenium suite. Two copies\n * would mean a bug fixed in one surface and not the other, and — worse — the\n * same failing run rendering differently depending on which lane recorded it.\n *\n * Lives in `@chatsdet/types` because it is the only package both sides already\n * depend on; the values are plain data (regexes and a byte cap), not behaviour.\n */\n\nexport interface DiagnosticsFilters {\n /** URL patterns to exclude from network requests. */\n excludeUrlPatterns?: RegExp[];\n /** Console message patterns to exclude. */\n excludeConsolePatterns?: RegExp[];\n /** Maximum body size in bytes for network requests (truncate beyond). */\n maxBodySize?: number;\n}\n\n/**\n * Default filters for excluding development noise, static assets and SSR data.\n *\n * The bar is \"would a human triaging a failed test ever read this line?\" — HMR\n * chatter, favicon fetches and font requests never clear it, and leaving them in\n * buries the request that actually 500'd.\n */\nexport const DEFAULT_DIAGNOSTICS_FILTERS: DiagnosticsFilters = {\n excludeUrlPatterns: [\n /_next\\/data\\//, // Next.js SSR data\n /_next\\/static\\//, // Next.js static assets\n /_next\\/image/, // Next.js image optimization\n /\\.hot-update\\./, // HMR updates\n /webpack-hmr/, // Webpack HMR\n /\\/__webpack_hmr/, // Webpack HMR\n /favicon\\.ico/, // Favicon\n /\\.(png|jpg|jpeg|gif|svg|ico|webp|woff2?|ttf|eot|otf)(\\?|$)/i, // Static assets\n /sockjs-node/, // Dev server socket\n /@vite\\/client/, // Vite client\n /__vite_ping/, // Vite ping\n /chrome-extension:\\/\\//, // Browser extensions\n ],\n excludeConsolePatterns: [\n /^\\[HMR\\]/,\n /^\\[Fast Refresh\\]/,\n /Download the React DevTools/,\n /^\\[vite\\]/,\n /^%c/, // Styled console logs (often debug output)\n ],\n /** 50KB — enough for a JSON error payload, not enough to bloat an artifact. */\n maxBodySize: 50 * 1024,\n};\n\n/** True when a network URL should be dropped as noise. */\nexport function isNoisyUrl(url: string, filters: DiagnosticsFilters): boolean {\n return filters.excludeUrlPatterns?.some((pattern) => pattern.test(url)) ?? false;\n}\n\n/** True when a console message should be dropped as noise. */\nexport function isNoisyConsoleText(text: string, filters: DiagnosticsFilters): boolean {\n return filters.excludeConsolePatterns?.some((pattern) => pattern.test(text)) ?? false;\n}\n","/**\n * Sandbox Task & Checklist Catalog\n *\n * Code-defined task/checklist definitions shared between frontend and backend.\n * The legacy SANDBOX_TASKS is preserved for backward compat.\n * The new checklist system uses profile-driven, DAG-ordered items.\n *\n * @module types/sandbox-tasks\n */\n\n// ---------------------------------------------------------------------------\n// Artifact types\n// ---------------------------------------------------------------------------\n\n/** Artifact types with special platform behavior (template sync, file path mapping). */\nexport type WellKnownArtifactType =\n | 'docker-compose'\n | 'dockerfile'\n | 'startup-script'\n | 'setup-script'\n | 'seed-script'\n | 'environment-config';\n\n/** Any string is a valid artifact type. Well-known types get special treatment. */\nexport type SandboxArtifactType = WellKnownArtifactType | (string & {});\n\n// ---------------------------------------------------------------------------\n// Legacy task types (backward compat)\n// ---------------------------------------------------------------------------\n\nexport interface SandboxTaskDefinition {\n id: string;\n label: string;\n description: string;\n icon: string;\n producesArtifacts: SandboxArtifactType[];\n order: number;\n /** Prompt template ID in apps/api/src/ai/prompts/ */\n promptTemplate: string;\n}\n\nexport interface SandboxTaskStatus {\n status: 'pending' | 'running' | 'completed' | 'failed';\n startedAt?: string;\n completedAt?: string;\n summary?: string;\n error?: string;\n}\n\n/** A task definition merged with its runtime status and artifact info */\nexport interface SandboxTaskWithStatus extends SandboxTaskDefinition {\n taskStatus: SandboxTaskStatus;\n artifacts: Array<{\n artifactType: string;\n filename: string;\n }>;\n}\n\n/** Task SSE events are defined in conversation-stream.ts */\nimport type { TaskArtifactPublishedEvent, TaskCompletedEvent } from './conversation-stream.js';\n\nexport type TaskStreamEvent =\n | TaskArtifactPublishedEvent\n | TaskCompletedEvent;\n\nexport const SANDBOX_TASKS: SandboxTaskDefinition[] = [\n {\n id: 'setup-environment',\n label: 'Setup Environment',\n description: 'Create Docker Compose, Dockerfile, and startup script to run the app',\n icon: 'GearSix',\n producesArtifacts: ['docker-compose', 'dockerfile', 'startup-script', 'seed-script'],\n order: 1,\n promptTemplate: 'env-template-setup-environment',\n },\n];\n\n// ---------------------------------------------------------------------------\n// Checklist system — profile-driven, DAG-ordered setup items\n// ---------------------------------------------------------------------------\n\nexport type ChecklistItemId =\n | 'provisioned'\n | 'code-cloned'\n | 'environment-analyzed'\n | 'setup-script-created'\n | 'dockerfile-created'\n | 'docker-compose-created'\n | 'server-running'\n | 'server-accessible'\n | 'adhoc-test-ready'\n | 'seed-script-created'\n | 'unit-tests-runnable'\n | 'integration-tests-runnable'\n | 'template-verified';\n\nexport interface ChecklistItemDefinition {\n id: ChecklistItemId;\n label: string;\n description: string;\n dependsOn: ChecklistItemId[];\n producesArtifacts: SandboxArtifactType[];\n agentAutomatable: boolean;\n /** Whether this item must be completed/skipped before the template can flip to verified. */\n countsTowardVerification?: boolean;\n /** Prompt template ID in apps/api/src/ai/prompts/ (only for agent-automatable items) */\n promptTemplate?: string;\n}\n\nexport type ChecklistProfile = 'minimal' | 'ci-ready' | 'full-stack';\n\nexport interface ChecklistProfileDefinition {\n id: ChecklistProfile;\n label: string;\n description: string;\n items: ChecklistItemId[];\n}\n\nexport interface ChecklistItemStatus {\n status: 'pending' | 'running' | 'completed' | 'failed' | 'skipped';\n startedAt?: string;\n completedAt?: string;\n summary?: string;\n error?: string;\n}\n\n/** A checklist item definition merged with its runtime status and artifact info */\nexport interface ChecklistItemWithStatus extends ChecklistItemDefinition {\n itemStatus: ChecklistItemStatus;\n /** True when all deps are met and item is not yet completed/running/skipped */\n actionable: boolean;\n artifacts: Array<{ artifactType: string; filename: string }>;\n}\n\n// ---------------------------------------------------------------------------\n// Checklist item definitions (DAG)\n// ---------------------------------------------------------------------------\n\nexport const CHECKLIST_ITEMS: ChecklistItemDefinition[] = [\n {\n id: 'provisioned',\n label: 'Provisioned',\n description: 'Sandbox instance has been provisioned',\n dependsOn: [],\n producesArtifacts: [],\n agentAutomatable: false,\n },\n {\n id: 'code-cloned',\n label: 'Code Cloned',\n description: 'Repository has been cloned into the sandbox',\n dependsOn: ['provisioned'],\n producesArtifacts: [],\n agentAutomatable: false,\n },\n {\n id: 'environment-analyzed',\n label: 'Environment Analyzed',\n description: 'Repository analyzed for runtime, services, and dependencies',\n dependsOn: ['code-cloned'],\n producesArtifacts: ['environment-config'],\n agentAutomatable: true,\n promptTemplate: 'env-checklist-analyze-environment',\n },\n {\n id: 'setup-script-created',\n label: 'Setup Script Created',\n description: 'Environment setup script for installing dependencies and build prerequisites',\n dependsOn: ['environment-analyzed'],\n producesArtifacts: ['setup-script'],\n agentAutomatable: true,\n promptTemplate: 'env-checklist-create-setup-script',\n },\n {\n id: 'unit-tests-runnable',\n label: 'Unit Tests Runnable',\n description: 'Unit test suite can be executed successfully',\n dependsOn: ['setup-script-created'],\n producesArtifacts: [],\n agentAutomatable: true,\n promptTemplate: 'env-checklist-verify-unit-tests',\n },\n {\n id: 'dockerfile-created',\n label: 'Dockerfile Created',\n description: 'Dockerfile generated from environment analysis',\n dependsOn: ['environment-analyzed'],\n producesArtifacts: ['dockerfile'],\n agentAutomatable: true,\n promptTemplate: 'env-checklist-create-dockerfile',\n },\n {\n id: 'docker-compose-created',\n label: 'Docker Compose Created',\n description: 'Docker Compose file with application and service containers',\n dependsOn: ['dockerfile-created'],\n producesArtifacts: ['docker-compose'],\n agentAutomatable: true,\n promptTemplate: 'env-checklist-create-docker-compose',\n },\n {\n id: 'integration-tests-runnable',\n label: 'Integration Tests Runnable',\n description: 'Integration tests can connect to services and run',\n dependsOn: ['docker-compose-created'],\n producesArtifacts: [],\n agentAutomatable: true,\n promptTemplate: 'env-checklist-verify-integration-tests',\n },\n {\n id: 'seed-script-created',\n label: 'Seed Script Created',\n description: 'Database seed script for initial data population',\n dependsOn: ['docker-compose-created'],\n producesArtifacts: ['seed-script'],\n agentAutomatable: true,\n promptTemplate: 'env-checklist-create-seed-script',\n },\n {\n id: 'server-running',\n label: 'Server Running',\n description: 'Application server started via startup script',\n dependsOn: ['docker-compose-created'],\n producesArtifacts: ['startup-script'],\n agentAutomatable: true,\n promptTemplate: 'env-checklist-start-server',\n },\n {\n id: 'server-accessible',\n label: 'Server Accessible',\n description: 'Application server responds to health check requests',\n dependsOn: ['server-running'],\n producesArtifacts: [],\n agentAutomatable: true,\n promptTemplate: 'env-checklist-verify-server',\n },\n {\n id: 'adhoc-test-ready',\n label: 'Ad Hoc Test Ready',\n description:\n 'Credential-producing sandbox action validated against a running instance for sandbox-backed ad hoc tests',\n dependsOn: ['server-accessible'],\n producesArtifacts: [],\n agentAutomatable: true,\n countsTowardVerification: false,\n promptTemplate: 'env-checklist-enable-adhoc-testing',\n },\n {\n id: 'template-verified',\n label: 'Template Verified',\n description: 'A fresh instance provisioned from this template comes up healthy',\n dependsOn: ['server-accessible'],\n producesArtifacts: [],\n agentAutomatable: true,\n promptTemplate: 'env-checklist-verify-template',\n },\n];\n\n/** O(1) lookup by checklist item ID */\nexport const CHECKLIST_ITEM_MAP: Record<ChecklistItemId, ChecklistItemDefinition> =\n Object.fromEntries(CHECKLIST_ITEMS.map((item) => [item.id, item])) as Record<\n ChecklistItemId,\n ChecklistItemDefinition\n >;\n\n// ---------------------------------------------------------------------------\n// Profile definitions\n// ---------------------------------------------------------------------------\n\nconst ALL_CHECKLIST_ITEM_IDS: ChecklistItemId[] = CHECKLIST_ITEMS.map((item) => item.id);\n\nexport const CHECKLIST_PROFILES: Record<ChecklistProfile, ChecklistProfileDefinition> = {\n minimal: {\n id: 'minimal',\n label: 'Minimal',\n description: 'Provisioned sandbox with code cloned — git access only',\n items: ['provisioned', 'code-cloned'],\n },\n 'ci-ready': {\n id: 'ci-ready',\n label: 'CI Ready',\n description: 'Code cloned with unit tests runnable — commit PRs and let CI handle the rest',\n items: ['provisioned', 'code-cloned', 'environment-analyzed', 'setup-script-created', 'unit-tests-runnable'],\n },\n 'full-stack': {\n id: 'full-stack',\n label: 'Full Stack',\n description: 'Complete containerized environment with all services, tests, and server running',\n items: ALL_CHECKLIST_ITEM_IDS,\n },\n};\n\nexport function getVerificationChecklistItemIds(profile: ChecklistProfile): ChecklistItemId[] {\n const profileItems = new Set(CHECKLIST_PROFILES[profile].items);\n return CHECKLIST_ITEMS.filter(\n (item) => profileItems.has(item.id) && item.countsTowardVerification !== false\n ).map((item) => item.id);\n}\n","/**\n * Macro system shared types and catalog.\n *\n * @module macros\n * @see apps/docs/src/content/docs/reference/macros.mdx\n *\n * Macros allow dynamic data generation in workflow test inputs.\n * Format: $MACRO_NAME(args) — resolved at execution time to fresh values.\n */\n\nexport interface MacroCatalogEntry {\n /** Macro name (uppercase, e.g. \"UUID\") */\n name: string;\n /** A valid call with example arguments, e.g. `$RANDOM_STRING(8)`. */\n call: string;\n /** Human-readable description */\n description: string;\n /** Argument definitions (empty for no-arg macros) */\n args: Array<{\n name: string;\n type: 'number' | 'string';\n description: string;\n /** The macro works without this arg (e.g. `$NOW()`). */\n optional?: boolean;\n }>;\n /** Example resolved output */\n example: string;\n}\n\n/**\n * Full catalog of available macros — shared by backend resolver and frontend typeahead.\n */\nexport const MACRO_CATALOG: MacroCatalogEntry[] = [\n {\n name: 'UUID',\n call: '$UUID()',\n description: 'Generate a random UUID v4',\n args: [],\n example: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890',\n },\n {\n name: 'NOW',\n call: '$NOW(+5m)',\n description:\n 'Current ISO 8601 timestamp, optionally shifted by an offset (+5m, -2h, 30s, 1d) and rendered in UTC with a format (YYYY MM DD HH mm ss, unpadded H m)',\n args: [\n { name: 'offset', type: 'string', description: 'Shift from now, e.g. +5m or -2h', optional: true },\n {\n name: 'format',\n type: 'string',\n description: 'UTC format, e.g. \"m H * * *\" for a daily cron; no commas or parentheses',\n optional: true,\n },\n ],\n example: '2026-02-19T14:30:00.000Z',\n },\n {\n name: 'TODAY',\n call: '$TODAY()',\n description: 'Current date (YYYY-MM-DD)',\n args: [],\n example: '2026-02-19',\n },\n {\n name: 'DATE',\n call: '$DATE(7)',\n description: 'Date offset from today by N days (negative for past, positive for future)',\n args: [{ name: 'days', type: 'number', description: 'Number of days offset (e.g. -7, +30)' }],\n example: '2026-02-26',\n },\n {\n name: 'TIMESTAMP',\n call: '$TIMESTAMP()',\n description: 'Current Unix timestamp in milliseconds',\n args: [],\n example: '1771520400000',\n },\n {\n name: 'RANDOM_INT',\n call: '$RANDOM_INT(1,100)',\n description: 'Random integer in range (inclusive)',\n args: [\n { name: 'min', type: 'number', description: 'Minimum value' },\n { name: 'max', type: 'number', description: 'Maximum value' },\n ],\n example: '42',\n },\n {\n name: 'RANDOM_STRING',\n call: '$RANDOM_STRING(8)',\n description: 'Random alphanumeric string',\n args: [{ name: 'length', type: 'number', description: 'Character count' }],\n example: 'xK9mQ',\n },\n {\n name: 'EMAIL',\n call: '$EMAIL()',\n description: 'Generate a unique test email address',\n args: [],\n example: 'test-a1b2@example-test.com',\n },\n {\n name: 'FIRST_NAME',\n call: '$FIRST_NAME()',\n description: 'Random first name',\n args: [],\n example: 'Jordan',\n },\n {\n name: 'LAST_NAME',\n call: '$LAST_NAME()',\n description: 'Random last name',\n args: [],\n example: 'Chen',\n },\n {\n name: 'PHONE',\n call: '$PHONE()',\n description: 'Random US phone number',\n args: [],\n example: '+1-555-382-7491',\n },\n {\n name: 'SEQUENCE',\n call: '$SEQUENCE(\"order\")',\n description: 'Auto-incrementing counter (resets per run)',\n args: [{ name: 'name', type: 'string', description: 'Counter name' }],\n example: '1',\n },\n {\n name: 'AI',\n call: '$AI(\"email subject\")',\n description:\n 'AI-generated string for the given prompt (resolved once per run, cached by prompt)',\n args: [\n {\n name: 'prompt',\n type: 'string',\n description: 'Quoted description of what to generate, e.g. \"email subject\"',\n },\n ],\n example: 'Quick question about your refund',\n },\n];\n\n/** Names of all valid macros, derived from MACRO_CATALOG. */\nexport const VALID_MACRO_NAMES = new Set(MACRO_CATALOG.map((m) => m.name));\n\nexport type MacroReferenceIssueCode =\n | 'macro_missing_parentheses'\n | 'macro_unknown'\n | 'macro_invalid_arguments';\n\nexport interface MacroReferenceIssue {\n code: MacroReferenceIssueCode;\n /** Full expression when available, otherwise the bare token. */\n token: string;\n /** Macro name without the leading dollar sign. */\n name: string;\n message: string;\n /** A working call to copy for repairable syntax errors (the catalog's `call`). */\n suggestion?: string;\n index: number;\n}\n\nexport type WorkflowInputExpressionIssueCode =\n | MacroReferenceIssueCode\n | 'variable_self_reference'\n | 'variable_forward_reference'\n | 'variable_unknown'\n | 'variable_not_value_input'\n | 'variable_invalid_syntax';\n\nexport interface WorkflowInputExpressionIssue {\n code: WorkflowInputExpressionIssueCode;\n inputName: string;\n inputIndex: number;\n message: string;\n hint?: string;\n token: string;\n}\n\n/** Minimal structural shape accepted by workflow-input expression validation. */\nexport interface WorkflowInputExpressionDefinition {\n kind: 'value' | 'credential';\n name: string;\n defaultValue?: string;\n}\n\nexport interface MacroReferenceValidationOptions {\n /** Reserved bare tokens owned by a caller rather than the macro registry. */\n allowedBareTokens?: readonly string[];\n}\n\nconst MACRO_CANDIDATE_PATTERN = /(?<!\\\\)\\$([A-Z][A-Z0-9_]*)(?:\\(([^)]*)\\))?/g;\n\nfunction macroArgs(argsString: string | undefined): string[] {\n return argsString !== undefined && argsString.length > 0 ? argsString.split(',') : [];\n}\n\nfunction integerArgument(value: string | undefined): number | undefined {\n if (value === undefined) return undefined;\n const parsed = Number(value.trim());\n return Number.isInteger(parsed) ? parsed : undefined;\n}\n\nfunction stripArgumentQuotes(value: string | undefined): string {\n return (value ?? '').trim().replace(/^[\"']|[\"']$/g, '');\n}\n\n/**\n * How to write a macro, for error messages: its signature and, when it takes\n * arguments, a working example — e.g. \"`$RANDOM_STRING(length)`, e.g.\n * `$RANDOM_STRING(8)`\". One message has to be enough to fix the call.\n */\nfunction macroCallForm(entry: MacroCatalogEntry): string {\n const signature = `$${entry.name}(${entry.args.map((a) => (a.optional ? `${a.name}?` : a.name)).join(', ')})`;\n return signature === entry.call ? `\\`${signature}\\`` : `\\`${signature}\\`, e.g. \\`${entry.call}\\``;\n}\n\nfunction validateMacroArguments(name: string, argsString: string): string | undefined {\n const entry = MACRO_CATALOG.find((macro) => macro.name === name);\n if (!entry) return undefined;\n\n const args = macroArgs(argsString);\n const required = entry.args.filter((arg) => !arg.optional).length;\n if (args.length < required || args.length > entry.args.length) {\n const expected =\n required === entry.args.length ? `${required}` : `${required} to ${entry.args.length}`;\n return `expected ${expected} argument${entry.args.length === 1 ? '' : 's'}, got ${args.length}`;\n }\n\n if (name === 'DATE') {\n if (integerArgument(args[0]) === undefined) return 'days must be an integer';\n }\n\n if (name === 'RANDOM_INT') {\n const min = integerArgument(args[0]);\n const max = integerArgument(args[1]);\n if (min === undefined) return 'min must be an integer';\n if (max === undefined) return 'max must be an integer';\n if (min > max) return `min (${min}) must be less than or equal to max (${max})`;\n }\n\n if (name === 'RANDOM_STRING') {\n const length = integerArgument(args[0]);\n if (length === undefined) return 'length must be an integer';\n if (length < 1 || length > 1000) return `length must be between 1 and 1000, got ${length}`;\n }\n\n if (name === 'SEQUENCE' && !stripArgumentQuotes(args[0])) return 'name cannot be empty';\n if (name === 'AI' && !stripArgumentQuotes(args[0])) return 'prompt cannot be empty';\n\n return undefined;\n}\n\n/**\n * Validate macro-like tokens in a dynamic string.\n *\n * Built-ins must use their canonical call form (`$FIRST_NAME()`), unknown\n * calls are rejected, arguments mirror the runtime registry constraints, and\n * escaped dollar signs remain literal.\n */\nexport function validateMacroReferences(\n value: string,\n options?: MacroReferenceValidationOptions\n): MacroReferenceIssue[] {\n const issues: MacroReferenceIssue[] = [];\n const allowedBareTokens = new Set(options?.allowedBareTokens ?? []);\n const pattern = new RegExp(MACRO_CANDIDATE_PATTERN.source, MACRO_CANDIDATE_PATTERN.flags);\n let match: RegExpExecArray | null;\n\n while ((match = pattern.exec(value)) !== null) {\n const [token, name, argsString] = match;\n const hasParentheses = argsString !== undefined;\n const known = VALID_MACRO_NAMES.has(name);\n\n if (!hasParentheses && allowedBareTokens.has(name)) continue;\n\n if (!known) {\n issues.push({\n code: 'macro_unknown',\n token,\n name,\n message: `Unknown macro \\`${token}\\`.`,\n index: match.index,\n });\n continue;\n }\n\n const entry = MACRO_CATALOG.find((macro) => macro.name === name);\n if (!hasParentheses) {\n issues.push({\n code: 'macro_missing_parentheses',\n token,\n name,\n message: entry\n ? `Macro \\`${token}\\` must use parentheses: write ${macroCallForm(entry)}.`\n : `Macro \\`${token}\\` must use parentheses.`,\n suggestion: entry?.call ?? `$${name}()`,\n index: match.index,\n });\n continue;\n }\n\n const argumentError = validateMacroArguments(name, argsString);\n if (argumentError) {\n issues.push({\n code: 'macro_invalid_arguments',\n token,\n name,\n message: `Invalid arguments for \\`$${name}()\\`: ${argumentError}.${entry ? ` Call it as ${macroCallForm(entry)}.` : ''}`,\n index: match.index,\n });\n }\n }\n\n return issues;\n}\n\n/**\n * Validate input defaults (or an effective scenario value map) in declaration\n * order. An input may compose only earlier value inputs via `$var.<name>`.\n */\nexport function validateWorkflowInputExpressions(\n definitions: readonly WorkflowInputExpressionDefinition[],\n values?: Readonly<Record<string, string>>\n): WorkflowInputExpressionIssue[] {\n const issues: WorkflowInputExpressionIssue[] = [];\n const positions = new Map<string, number>();\n definitions.forEach((definition, index) => {\n if (!positions.has(definition.name)) positions.set(definition.name, index);\n });\n\n definitions.forEach((definition, inputIndex) => {\n if (definition.kind !== 'value') return;\n const value = values?.[definition.name] ?? definition.defaultValue;\n if (value === undefined) return;\n\n for (const macroIssue of validateMacroReferences(value)) {\n issues.push({\n code: macroIssue.code,\n inputName: definition.name,\n inputIndex,\n message: macroIssue.message,\n hint: macroIssue.suggestion\n ? `For example: \\`${macroIssue.suggestion}\\`.`\n : undefined,\n token: macroIssue.token,\n });\n }\n\n const malformedVariablePattern = /(?<!\\\\)\\$var(?:\\.(?!\\w)|\\b(?!\\.\\w))/g;\n let malformedVariableMatch: RegExpExecArray | null;\n while ((malformedVariableMatch = malformedVariablePattern.exec(value)) !== null) {\n issues.push({\n code: 'variable_invalid_syntax',\n inputName: definition.name,\n inputIndex,\n message: `Workflow input \"${definition.name}\" contains invalid variable reference \\`${malformedVariableMatch[0]}\\`; use a valid \\`$var.<name>\\` reference.`,\n token: malformedVariableMatch[0],\n });\n }\n\n const bareVariablePattern = /(?<!\\\\)\\$([a-z_][A-Za-z0-9_]*)/g;\n let bareVariableMatch: RegExpExecArray | null;\n while ((bareVariableMatch = bareVariablePattern.exec(value)) !== null) {\n const referencedName = bareVariableMatch[1]!;\n if (referencedName === 'var') continue;\n const knownInput = positions.has(referencedName);\n issues.push({\n code: 'variable_invalid_syntax',\n inputName: definition.name,\n inputIndex,\n message: knownInput\n ? `Workflow input \"${definition.name}\" contains bare reference \\`${bareVariableMatch[0]}\\`. Use \\`$var.${referencedName}\\` to reference a preceding input.`\n : `Workflow input \"${definition.name}\" contains invalid bare reference \\`${bareVariableMatch[0]}\\`; declare it as a preceding input and use \\`$var.${referencedName}\\`.`,\n token: bareVariableMatch[0],\n });\n }\n\n const variablePattern = new RegExp(VARIABLE_PATTERN.source, VARIABLE_PATTERN.flags);\n let variableMatch: RegExpExecArray | null;\n while ((variableMatch = variablePattern.exec(value)) !== null) {\n const referencedName = variableMatch[1]!;\n const token = variableMatch[0];\n const referencedIndex = positions.get(referencedName);\n\n if (referencedName === definition.name) {\n issues.push({\n code: 'variable_self_reference',\n inputName: definition.name,\n inputIndex,\n message: `Workflow input \"${definition.name}\" cannot reference itself with \\`${token}\\`.`,\n token,\n });\n } else if (referencedIndex === undefined) {\n issues.push({\n code: 'variable_unknown',\n inputName: definition.name,\n inputIndex,\n message: `Workflow input \"${definition.name}\" references unknown input \\`${token}\\`.`,\n token,\n });\n } else if (referencedIndex > inputIndex) {\n issues.push({\n code: 'variable_forward_reference',\n inputName: definition.name,\n inputIndex,\n message: `Workflow input \"${definition.name}\" may only reference a preceding value input; \"${referencedName}\" is declared later.`,\n token,\n });\n } else if (definitions[referencedIndex]?.kind !== 'value') {\n issues.push({\n code: 'variable_not_value_input',\n inputName: definition.name,\n inputIndex,\n message: `Workflow input \"${definition.name}\" references \"${referencedName}\", but only preceding value inputs can be referenced.`,\n token,\n });\n }\n }\n });\n\n return issues;\n}\n\n/**\n * Validate all macro expressions in a string. Returns array of invalid macro names.\n * Empty array = all macros valid (or no macros present).\n */\nexport function findInvalidMacros(value: string): string[] {\n return [\n ...new Set(\n validateMacroReferences(value)\n .filter((issue) => issue.code === 'macro_unknown')\n .map((issue) => issue.name)\n ),\n ];\n}\n\n/** Expected arg count per macro, derived from MACRO_CATALOG. */\nconst MACRO_ARG_COUNTS = new Map(MACRO_CATALOG.map((m) => [m.name, m.args.length]));\n\n/**\n * Normalize macro expressions in a string by stripping extra arguments\n * beyond what the catalog defines. LLMs sometimes add extra hints\n * (e.g. \"$RANDOM_STRING(10, alphanumeric)\") that crash the resolver.\n */\nexport function normalizeMacroArgs(value: string): string {\n const pattern = new RegExp(MACRO_PATTERN.source, MACRO_PATTERN.flags);\n return value.replace(pattern, (fullMatch, name: string, argsStr: string) => {\n const expectedArgCount = MACRO_ARG_COUNTS.get(name);\n if (expectedArgCount === undefined) return fullMatch;\n const args = argsStr.length > 0 ? argsStr.split(',') : [];\n if (args.length <= expectedArgCount) return fullMatch;\n const trimmed = args.slice(0, expectedArgCount).join(',');\n return `$${name}(${trimmed})`;\n });\n}\n\n/** Regex for matching macro expressions. Negative lookbehind for \\ handles escaping. */\nexport const MACRO_PATTERN = /(?<!\\\\)\\$([A-Z_]+)\\(([^)]*)\\)/g;\n\n/** Regex for matching $var.{name} variable references (simple, dotted, and step-qualified). */\nexport const VARIABLE_PATTERN = /(?<!\\\\)\\$var\\.(\\w+(?:\\.\\w+)*)/g;\n\n/**\n * Regex for matching step-qualified variable references: `$var.StepN_Label.name`.\n * Group 1 = step qualifier (e.g. \"Step2_Create_Client\"), Group 2 = variable name.\n */\nexport const QUALIFIED_VARIABLE_PATTERN = /(?<!\\\\)\\$var\\.(\\w+)\\.(\\w+)/g;\n\n// ── Typeahead entry types (used by macro/variable dropdown) ──\n\nexport type VariableSource =\n | 'inputValue'\n | 'macro'\n | 'extracted'\n | 'credential'\n | 'seed'\n | 'childFlowOutput'\n | 'workflowInput'\n | 'env'\n | 'unknown';\n\nexport interface VariableTypeaheadEntry {\n kind: 'variable';\n /** Variable name without $var. prefix (e.g. \"project_id\") */\n name: string;\n source: VariableSource;\n description: string;\n /** Step-qualified name for disambiguation (e.g. \"Step2_Create_Client.client\") */\n qualifiedName?: string;\n /** Human-readable step label (e.g. \"Step 2: Create Client\") */\n stepLabel?: string;\n /** Design-time template or expression (e.g. \"Acme Test - $RANDOM_STRING(6)\") */\n designTimeValue?: string;\n}\n\nexport interface MacroTypeaheadEntry extends MacroCatalogEntry {\n kind: 'macro';\n}\n\nexport type TypeaheadEntry = MacroTypeaheadEntry | VariableTypeaheadEntry;\n\n/** Map macro names to the variable keys they produce at runtime. */\nexport const MACRO_TO_VAR_KEY: Record<string, string> = {\n UUID: 'uuid',\n EMAIL: 'email',\n FIRST_NAME: 'first_name',\n LAST_NAME: 'last_name',\n PHONE: 'phone',\n NOW: 'now',\n TODAY: 'today',\n DATE: 'date',\n TIMESTAMP: 'timestamp',\n RANDOM_INT: 'random_int',\n RANDOM_STRING: 'random_string',\n SEQUENCE: 'sequence',\n AI: 'ai',\n};\n\n/**\n * Scan a string for all `$var.{name}` references, returning the variable names.\n * Also handles step-qualified references like `$var.Step2_Create_Client.name`,\n * extracting just the variable name for backward-compatible tracking.\n */\nexport function scanStringForVarRefs(text: string): string[] {\n const names: string[] = [];\n\n // First extract step-qualified references ($var.Step2_Label.name)\n const qualifiedPositions = new Set<number>();\n QUALIFIED_VARIABLE_PATTERN.lastIndex = 0;\n let qMatch: RegExpExecArray | null;\n while ((qMatch = QUALIFIED_VARIABLE_PATTERN.exec(text)) !== null) {\n if (/^Step\\d+/.test(qMatch[1]!)) {\n names.push(qMatch[2]!);\n qualifiedPositions.add(qMatch.index);\n }\n }\n\n // Then extract all references, skipping positions already matched as step-qualified\n VARIABLE_PATTERN.lastIndex = 0;\n let match: RegExpExecArray | null;\n while ((match = VARIABLE_PATTERN.exec(text)) !== null) {\n if (!qualifiedPositions.has(match.index)) {\n names.push(match[1]!);\n }\n }\n return names;\n}\n\n/** Scan a string for all `$MACRO()` expressions, returning the macro names. */\nexport function scanStringForMacros(text: string): string[] {\n const names: string[] = [];\n MACRO_PATTERN.lastIndex = 0;\n let match: RegExpExecArray | null;\n while ((match = MACRO_PATTERN.exec(text)) !== null) {\n names.push(match[1]!);\n }\n return names;\n}\n","/**\n * Variable utilities for step-qualified variable references.\n *\n * @module variable-utils\n *\n * Provides slugification of step labels for use in qualified variable\n * references like `$var.Step2_Create_Client.client`.\n */\n\nimport { MACRO_TO_VAR_KEY, scanStringForMacros } from './macros.js';\nimport { resolveNodeOutputs } from './node-outputs.js';\n\n/**\n * Slugify a seed workflow name for use in variable namespacing.\n *\n * Examples:\n * slugifySeedName(\"Create Client & Matter\") → \"Create_Client_Matter\"\n * slugifySeedName(\"OAuth Credential Seed\") → \"OAuth_Credential_Seed\"\n *\n * Used by both runtime (seed-execution-dag.ts) and frontend (useWorkflowVariables)\n * to produce consistent qualified variable references like `$var.Create_Client_Matter.client_id`.\n */\nexport function slugifySeedName(name: string): string {\n return name\n .replace(/[^A-Za-z0-9]+/g, '_')\n .replace(/_+/g, '_')\n .replace(/^_|_$/g, '');\n}\n\n/**\n * Convert a step index + label into a slug suitable for qualified variable references.\n *\n * Examples:\n * slugifyStepLabel(2, \"Create Client\") → \"Step2_Create_Client\"\n * slugifyStepLabel(5, \"Create Time Entry\") → \"Step5_Create_Time_Entry\"\n * slugifyStepLabel(1, \"Login\") → \"Step1_Login\"\n *\n * Always prefixed with step number for unambiguous references.\n * Non-alphanumeric characters are replaced with underscores, consecutive\n * underscores are collapsed, and leading/trailing underscores are trimmed.\n */\nexport function slugifyStepLabel(stepIndex: number, label: string): string {\n const slug = label\n .replace(/[^A-Za-z0-9]+/g, '_')\n .replace(/_+/g, '_')\n .replace(/^_|_$/g, '');\n return `Step${stepIndex}_${slug}`;\n}\n\n/**\n * Canonical form for a single workflow variable LEAF name.\n *\n * Variable names are produced and consumed by many independent paths — the web\n * UI output editor, the builder agent, the cache optimizer, the live agent's\n * `saveAs`, and raw API writes. Historically each path used its own casing\n * convention, so a recipe could capture `target_member_name` while the authored\n * node declared `targetMemberName` and a downstream step referenced\n * `$var.targetMemberName`. Resolution is exact-match, so the consumer silently\n * failed to resolve the producer's value (see the team-settings persistence\n * incident, June 2026).\n *\n * This collapses camelCase/PascalCase boundaries, spaces, hyphens, and other\n * separators to a single snake_case form, lower-cased. It is **idempotent**\n * (`canonicalizeVariableName(canonicalizeVariableName(x)) === canonicalizeVariableName(x)`),\n * so applying it at every boundary makes producer and consumer names converge.\n *\n * Examples:\n * canonicalizeVariableName(\"targetMemberName\") → \"target_member_name\"\n * canonicalizeVariableName(\"target_member_name\") → \"target_member_name\"\n * canonicalizeVariableName(\"URLPath\") → \"url_path\"\n *\n * Only IDENTIFIER-shaped names (`^[A-Za-z][A-Za-z0-9_]*$`) are normalized — these\n * are exactly the names that can be written as a bare `$var.X` reference, which\n * is where camelCase↔snake_case drift occurs. Any other name is returned\n * UNCHANGED:\n * - Freeform names with spaces (`\"Contract Name\"`, `\"Item Being Acquired\"`) are\n * a supported legacy form used as literal registry keys — normalizing them\n * would break literal resolution.\n * - Dotted names — qualified (`Step2_Create_Client.client`), credential\n * (`credential.username`), seed-namespaced (`Create_Client_Matter.client_id`)\n * — carry a qualifier segment whose casing is significant.\n * Label→identifier conversion (spaces/hyphens → snake_case) is a separate concern\n * handled at authoring input (see the web output/input editors).\n */\nexport function canonicalizeVariableName(name: string): string {\n if (typeof name !== 'string') return name;\n if (!/^[A-Za-z][A-Za-z0-9_]*$/.test(name)) return name;\n return name\n .replace(/([a-z0-9])([A-Z])/g, '$1_$2') // camelCase boundary: targetMember → target_Member\n .replace(/([A-Z]+)([A-Z][a-z])/g, '$1_$2') // acronym boundary: URLPath → URL_Path\n .replace(/_+/g, '_')\n .replace(/^_|_$/g, '')\n .toLowerCase();\n}\n\n/**\n * Extract variable names defined by a node's config.\n * Works on both frontend WorkflowNode.data and backend configJson.\n * Reusable by: frontend variableScanning, backend builder tools.\n *\n * Leaf names are returned in canonical snake_case form (see\n * {@link canonicalizeVariableName}) so producer/consumer matching is\n * casing-insensitive across the authoring paths.\n */\nexport function extractDefinedVariablesFromConfig(\n config: Record<string, unknown>,\n nodeType?: string\n): string[] {\n const names: string[] = [];\n\n // dataRequirements names (the schema that makes inputs visible in the UI)\n const dataRequirements = config.dataRequirements as Array<{ name: string }> | undefined;\n const dataReqNames = new Set<string>();\n if (Array.isArray(dataRequirements)) {\n for (const req of dataRequirements) {\n if (typeof req.name === 'string') {\n const canonical = canonicalizeVariableName(req.name);\n names.push(canonical);\n dataReqNames.add(canonical);\n }\n }\n }\n\n // inputValues keys define variables, but ONLY when backed by a dataRequirement.\n // Orphaned inputValues (no matching dataRequirement) are ghost data from previous\n // agent runs and should not be exposed as variables.\n const inputValues = config.inputValues as Record<string, string> | undefined;\n if (inputValues && dataReqNames.size > 0) {\n for (const [fieldName, fieldValue] of Object.entries(inputValues)) {\n const canonicalField = canonicalizeVariableName(fieldName);\n if (!dataReqNames.has(canonicalField)) continue;\n // dataRequirements already added the name; just scan for macro-produced vars\n if (typeof fieldValue === 'string') {\n const macroNames = scanStringForMacros(fieldValue);\n for (const macroName of macroNames) {\n const macroSuffix = MACRO_TO_VAR_KEY[macroName];\n if (macroSuffix) {\n names.push(`${canonicalField}_${macroSuffix}`);\n }\n }\n }\n }\n }\n\n // API sequence exports\n if (nodeType === 'api_sequence') {\n const exports = config.exports as Array<{ variableName?: string }> | undefined;\n if (Array.isArray(exports)) {\n for (const exp of exports) {\n if (typeof exp.variableName === 'string' && exp.variableName) {\n names.push(canonicalizeVariableName(exp.variableName));\n }\n }\n }\n }\n\n // Variables captured at runtime via context extractors (saveAs)\n const capturedVariableKeys = config.capturedVariableKeys as string[] | undefined;\n if (Array.isArray(capturedVariableKeys)) {\n for (const key of capturedVariableKeys) {\n if (typeof key === 'string') names.push(canonicalizeVariableName(key));\n }\n }\n\n // User-declared output variable names\n const declaredOutputs = config.declaredOutputs as string[] | undefined;\n if (Array.isArray(declaredOutputs)) {\n for (const key of declaredOutputs) {\n if (typeof key === 'string') names.push(canonicalizeVariableName(key));\n }\n }\n\n // Per-node-type output declarations (e.g. notification.to/subject/body)\n for (const output of resolveNodeOutputs(nodeType, config)) {\n names.push(output.name);\n }\n\n return names;\n}\n","/**\n * Config for the `mcp` workflow node type.\n *\n * Targets an MCP-server application (an org property with `type: 'mcp'`) and\n * carries plain-language instructions an agent executes against the server's\n * tools. Action-style: cache replay first, agent fallback (execution lands in\n * later stack members — see the native MCP testing stack).\n *\n * The node references the application by **propertyId** and its auth by a\n * persona (`credentialTemplateId`, preferred) or a direct credential pin\n * (`credentialId`, escape hatch). MCP applications reuse the `api_key`\n * credential provider, whose key field is the bearer token.\n */\nimport { z } from 'zod';\nimport type { PlaybookStep } from './index.js';\n\nexport interface McpNodeConfig {\n /** MCP application (org property with type 'mcp') this node drives. */\n propertyId: string;\n /** Persona (credential template) supplying auth; preferred over credentialId. */\n credentialTemplateId?: string;\n /** Direct credential pin (escape hatch). */\n credentialId?: string;\n /** Plain-language goal the agent accomplishes via the server's tools. */\n instructions: string;\n /** Variable names this node must produce (→ $var.StepN_<label>.<name>). */\n declaredOutputs?: string[];\n /** Authored/minted playbook steps (same shape as action nodes). */\n playbookSteps?: PlaybookStep[];\n /** Skip cache replay and always run the agent. */\n alwaysUseAgent?: boolean;\n /** Restrict the tools exposed to the agent (names as advertised by the server). */\n toolAllowlist?: string[];\n}\n\n/**\n * Zod schema for {@link McpNodeConfig}. `playbookSteps` entries are accepted\n * structurally (`z.custom`) — the persisted {@link PlaybookStep} shape has no\n * standalone zod schema (only the agent-input subset does, see\n * `buildPlaybookStepAgentSchema`), and this schema must not reject persisted\n * runtime-attached fields.\n */\nexport const McpNodeConfigSchema = z.object({\n propertyId: z.string().min(1),\n credentialTemplateId: z.string().optional(),\n credentialId: z.string().optional(),\n instructions: z.string().min(1),\n declaredOutputs: z.array(z.string()).optional(),\n playbookSteps: z\n .array(z.custom<PlaybookStep>((v) => typeof v === 'object' && v !== null))\n .optional(),\n alwaysUseAgent: z.boolean().optional(),\n toolAllowlist: z.array(z.string()).optional(),\n}) satisfies z.ZodType<McpNodeConfig>;\n","/**\n * Display labels for workflow node types. The JSON / persisted `node.type`\n * discriminator stays as-is (e.g. `'login'`) — only the human-facing label\n * changes here. Centralizing this lets us rename copy in one place.\n */\nexport const NODE_TYPE_LABEL = {\n login: 'Acting As',\n navigate: 'Navigate',\n action: 'Action',\n assertion: 'Assertion',\n condition: 'Condition',\n wait: 'Wait',\n api_sequence: 'API Sequence',\n seed: 'Seed Data',\n setup: 'Setup Flow',\n notification: 'Notification',\n audio: 'Audio input',\n 'audio-assertion': 'Audio assertion',\n visual_checkpoint: 'Visual checkpoint',\n sandbox_action: 'Sandbox action',\n mcp: 'MCP',\n call_flow: 'Run a flow',\n end: 'End',\n} as const;\n\nexport type NodeTypeKey = keyof typeof NODE_TYPE_LABEL;\nexport type NodeTypeLabel = (typeof NODE_TYPE_LABEL)[NodeTypeKey];\n\n/**\n * Coarse grouping used to organize node types in the \"Insert step\" menu.\n * `common` are the everyday building blocks, `data` provisions / drives data,\n * and `advanced` covers control flow and specialty steps.\n */\nexport type NodeTypeCategory = 'common' | 'data' | 'advanced';\n\n/** Human-readable section title for each {@link NodeTypeCategory}. */\nexport const NODE_TYPE_CATEGORY_LABEL: Record<NodeTypeCategory, string> = {\n common: 'Common',\n data: 'Data management',\n advanced: 'Advanced',\n};\n\n/** Render order for the categories in the menu. */\nexport const NODE_TYPE_CATEGORY_ORDER: NodeTypeCategory[] = ['common', 'data', 'advanced'];\n\n/**\n * Per-type metadata for the node picker: a concise one-line description and the\n * section it belongs to. Keyed by the same persisted discriminator as\n * {@link NODE_TYPE_LABEL}.\n */\nexport const NODE_TYPE_META: Record<NodeTypeKey, { description: string; category: NodeTypeCategory }> = {\n login: { description: 'Sign in as a test user', category: 'common' },\n navigate: { description: 'Go to a URL', category: 'common' },\n action: { description: 'Click, type, or interact', category: 'common' },\n assertion: { description: 'Check something on the page', category: 'common' },\n seed: { description: 'Provision test data first', category: 'data' },\n sandbox_action: { description: 'Run a server-side sandbox script', category: 'data' },\n api_sequence: { description: 'Call API endpoints, no browser', category: 'data' },\n mcp: { description: 'Drive an MCP server, no browser', category: 'data' },\n condition: { description: 'Branch on if/else', category: 'advanced' },\n wait: { description: 'Pause before continuing', category: 'advanced' },\n notification: { description: 'Send a Slack or email alert', category: 'advanced' },\n audio: { description: 'Send spoken audio input', category: 'advanced' },\n 'audio-assertion': { description: 'Verify audio output', category: 'advanced' },\n visual_checkpoint: { description: 'Capture a visual checkpoint', category: 'advanced' },\n // Present for completeness; not surfaced in the edge menu's visibleTypes.\n setup: { description: 'Run another flow first', category: 'advanced' },\n // Distinct from `setup`: `setup` runs a flow as a hoisted prerequisite before\n // the run starts, `call_flow` runs one *at this point in the chain* and feeds\n // its declared outputs to later steps.\n call_flow: { description: 'Run another flow here and use its outputs', category: 'advanced' },\n end: { description: 'End the workflow', category: 'advanced' },\n};\n\n/**\n * Normalizes legacy/alias node-type keywords to the canonical persisted type.\n * Accepts `'acting-as'` / `'acting_as'` as aliases for `'login'` so CLI\n * callers can use the new name while persisted data stays `'login'`.\n */\nexport function normalizeNodeType(input: string): string {\n if (input === 'acting-as' || input === 'acting_as') return 'login';\n return input;\n}\n","/**\n * Shared configuration schemas for visual checkpoint workflow nodes.\n *\n * @module visual-checkpoint\n * @see docs/features/test-suite-execution.md\n */\n\nimport { z } from 'zod';\nimport type { VisualAnchorCapture } from './visual-text.js';\n\nexport const VisualSeveritySchema = z.enum(['record', 'fail']);\nexport type VisualSeverity = z.infer<typeof VisualSeveritySchema>;\n\n/** Minimal persisted fields used to decide whether two captures can be compared. */\nexport type VisualCapturePairFields = {\n status: 'captured' | 'capture_failed';\n storageKey: string | null;\n renderProfileId: string;\n width: number | null;\n height: number | null;\n unstable: boolean;\n};\n\n/** Returns why a capture pair cannot produce a pixel comparison, or null when it can. */\nexport const visualCapturePairPrecondition = (\n head: VisualCapturePairFields,\n baseline: VisualCapturePairFields\n): 'unavailable' | 'incompatible' | null => {\n if (\n head.status !== 'captured' || baseline.status !== 'captured' ||\n !head.storageKey || !baseline.storageKey || head.unstable || baseline.unstable\n ) return 'unavailable';\n if (\n head.renderProfileId !== baseline.renderProfileId ||\n head.width !== baseline.width || head.height !== baseline.height\n ) return 'incompatible';\n return null;\n};\n\nexport const VisualMaskSchema = z.object({\n id: z.string().uuid(),\n description: z.string().min(1),\n locator: z.string().min(1),\n reason: z.string().min(1),\n healedAt: z.string().datetime(),\n healedCaptureId: z.string().uuid(),\n});\nexport type VisualMask = z.infer<typeof VisualMaskSchema>;\n\nexport type VisualElementAnchor = VisualAnchorCapture['elements'][number];\n\nexport const VisualCheckpointResolvedTargetSchema = z.object({\n text: z.string().min(1),\n locator: z.string().min(1),\n});\nexport type VisualCheckpointResolvedTarget = z.infer<typeof VisualCheckpointResolvedTargetSchema>;\n\nconst optionalText = z.preprocess((value) => value === '' ? undefined : value, z.string().min(1).optional());\n\nexport const VisualCheckpointNodeConfigSchema = z.object({\n title: z.string().min(1),\n description: z.string().min(1),\n name: z.string().min(1),\n target: optionalText,\n resolvedTarget: VisualCheckpointResolvedTargetSchema.optional(),\n expectedState: optionalText,\n severity: VisualSeveritySchema.default('record'),\n masks: z.array(VisualMaskSchema).default([]),\n});\nexport type VisualCheckpointNodeConfig = z.infer<typeof VisualCheckpointNodeConfigSchema>;\n","/**\n * Browser engines available to agent-driven test sessions.\n *\n * Chromium remains the default. Firefox and WebKit deliberately identify\n * Playwright engines rather than branded browsers.\n *\n * @module browser-engine\n */\n\nexport const BROWSER_ENGINES = ['chromium', 'firefox', 'webkit'] as const;\n\nexport type BrowserEngine = (typeof BROWSER_ENGINES)[number];\n\nexport const DEFAULT_BROWSER_ENGINE: BrowserEngine = 'chromium';\n\nexport const BROWSER_ENGINE_LABELS: Record<BrowserEngine, string> = {\n chromium: 'Chromium',\n firefox: 'Firefox',\n webkit: 'WebKit',\n};\n\nexport function isBrowserEngine(value: unknown): value is BrowserEngine {\n return typeof value === 'string' && BROWSER_ENGINES.includes(value as BrowserEngine);\n}\n","/**\n * Canonical classification of a DOM identifier's stability for use as a locator\n * anchor. Single source of truth — replaces the divergent, disagreeing\n * volatile/generated-id checks that used to live in (and drift between):\n * - apps/api/src/workflows/cache/build-locator.ts (`findVolatileTestIdToken`)\n * - packages/browser-core/src/snapshot-formatter-sections.ts (duplicate)\n * - packages/recording/src/scope.ts (`looksGeneratedId`)\n * - apps/api/src/workflows/cache/deterministic-locator-builder.ts (`isStableId`)\n *\n * Stability means **survives a re-seed**, NOT \"points at a real record\". The\n * canary app is re-seeded between runs, so any value derived from seeded data —\n * UUIDs, ULIDs, entity numbers (`invoice-DI-80368`), the `<timestamp>` in\n * `__new__1778798465222` — is VOLATILE even when it reliably identifies a record\n * within a single run. Only structural / framework-state tokens survive: a state\n * marker like `__new__`, a hard-coded structural `data-testid`, a landmark role.\n *\n * Output:\n * - `stable` → use the value verbatim (`[attr=\"value\"]` / `#id`).\n * - `stable-prefix`→ a structural head precedes a volatile token; anchor on\n * `[attr^=\"prefix\"]` (a non-unique prefix is then rejected\n * by the caller's global-uniqueness check).\n * - `volatile` → unusable as an anchor.\n *\n * This is locator-time classification (a context-dependent judgment), kept out\n * of the substrate per substrate-v2 principle #3 (\"synthesis is tagged, never\n * laundered\").\n */\nexport type IdentifierStability =\n | { kind: 'stable' }\n | { kind: 'stable-prefix'; prefix: string }\n | { kind: 'volatile' };\n\n// ── Whole-value generated patterns (no reusable structural prefix) ──────────\n// Framework-generated id prefixes (React useId, Radix, HeadlessUI, Mantine,\n// React Aria / Select, rc-, Vue menu-v-N).\nconst FRAMEWORK_PREFIX_RE =\n /^(?::r[0-9a-z]+:?$|_r_[0-9a-z]+_$|radix-|headlessui-|mantine-|react-aria-|react-select-|rc-|menu-v-\\d)/i;\n// A bare hash (styled-components / content hash), e.g. `deadbeef`, `a1b2c3d4e5`.\nconst BARE_HASH_RE = /^[a-f0-9]{8,}$/i;\n// `prefix-<hex6+>` styled-component-style class/id, e.g. `sc-bdVaJa` → `foo-a1b2c3d4`.\nconst PREFIX_HASH_RE = /^[a-z]+-[0-9a-f]{6,}$/i;\n\n// ── Embedded volatile tokens (a structural head may precede them) ───────────\n// Any 8-4-4-4-12 hex value is a UUID regardless of version/variant nibble — keep\n// it version-agnostic so v7/v8 (and non-RFC) ids are caught, not just v1-5.\nconst UUID_RE = /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/i;\n// ULID (Crockford base32, excludes I/L/O/U). Case-insensitive so lowercase\n// ULIDs are caught too.\nconst ULID_RE = /\\b[0-9A-HJKMNP-TV-Z]{26}\\b/i;\n// A 6+-digit run delimited by a boundary or `_` (so `__new__1778798465222`\n// is caught even though `_`→digit is not a `\\b`). Trailing/leading boundary\n// keeps `abc123456def` from matching (preserves the legacy `\\b\\d{6,}\\b`).\nconst DIGIT_RUN_RE = /(?:^|[^0-9A-Za-z])(\\d{6,})(?![0-9A-Za-z])/;\n// A trailing `-`/`_`-joined 3+-digit counter/number (`widget-123`,\n// `invoice-DI-80368`, the timestamp tail of `__new__<ts>`). 1–2 trailing digits\n// stay stable (`step-1`, `tab-42`).\nconst TRAILING_COUNTER_RE = /[-_](\\d{3,})$/;\n\nconst MIN_PREFIX_LEN = 3;\n\nfunction usablePrefix(prefix: string): boolean {\n return prefix.length >= MIN_PREFIX_LEN && /[a-z]/i.test(prefix);\n}\n\n/** Earliest index at which a volatile token begins, or null. */\nfunction earliestVolatileTokenIndex(value: string): number | null {\n let best: number | null = null;\n const consider = (idx: number | undefined) => {\n if (idx !== undefined && idx >= 0 && (best === null || idx < best)) best = idx;\n };\n consider(UUID_RE.exec(value)?.index);\n consider(ULID_RE.exec(value)?.index);\n const digit = DIGIT_RUN_RE.exec(value);\n if (digit) consider(digit.index + (digit[0].length - digit[1].length)); // start of the digits\n const counter = TRAILING_COUNTER_RE.exec(value);\n if (counter) consider(counter.index + 1); // after the `-`/`_` separator\n return best;\n}\n\nexport function classifyIdentifier(value: string): IdentifierStability {\n if (!value) return { kind: 'volatile' };\n\n // Whole-value generated → no reusable prefix.\n if (FRAMEWORK_PREFIX_RE.test(value) || BARE_HASH_RE.test(value) || PREFIX_HASH_RE.test(value)) {\n return { kind: 'volatile' };\n }\n\n const tokenIdx = earliestVolatileTokenIndex(value);\n if (tokenIdx !== null) {\n const prefix = value.slice(0, tokenIdx);\n return usablePrefix(prefix) ? { kind: 'stable-prefix', prefix } : { kind: 'volatile' };\n }\n\n return { kind: 'stable' };\n}\n\n/** True when the value is not safe to anchor on verbatim (volatile or prefix-only). */\nexport function isVolatileIdentifier(value: string): boolean {\n return classifyIdentifier(value).kind !== 'stable';\n}\n\n/** The structural prefix preceding a volatile token, or null. */\nexport function stableIdentifierPrefix(value: string): string | null {\n const c = classifyIdentifier(value);\n return c.kind === 'stable-prefix' ? c.prefix : null;\n}\n\nfunction escapeCssAttrValue(value: string): string {\n return value.replace(/\\\\/g, '\\\\\\\\').replace(/\"/g, '\\\\\"');\n}\n\n/**\n * CSS attribute selector for `value` on `attr` (`[attr=\"value\"]` for a stable\n * value, `[attr^=\"prefix\"]` for a structural prefix), or null when too volatile.\n */\nexport function stableAttrSelector(attr: string, value: string): string | null {\n const c = classifyIdentifier(value);\n if (c.kind === 'stable') return `[${attr}=\"${escapeCssAttrValue(value)}\"]`;\n if (c.kind === 'stable-prefix') return `[${attr}^=\"${escapeCssAttrValue(c.prefix)}\"]`;\n return null;\n}\n","/**\n * Shared Product Map domain vocabulary.\n *\n * The product map gives an org a legible map of its product (areas →\n * capabilities) and links tests (workflows) to capabilities so coverage is\n * showable. Populated primarily by the planning agent, edited by humans.\n *\n * @module product-map\n */\n\n/**\n * Lifecycle of an area, capability or rule. `deprecated` is a soft delete: the row stays\n * addressable and, critically, keeps owning its natural code (see\n * `packages/db/src/repositories/product-map/codes.ts`). Codes are meant to be\n * citable in tickets and specs forever, which is only true if nothing is ever\n * re-issued — so the product vocabulary is \"deprecate\", not \"delete\".\n */\nexport const PRODUCT_MAP_STATUSES = ['in_dev', 'alpha', 'beta', 'ga', 'deprecated'] as const;\nexport type ProductMapStatus = (typeof PRODUCT_MAP_STATUSES)[number];\n\n/** `agent` areas hold what an agent can do and the evals that prove it; otherwise they work like `feature` areas. */\nexport const PRODUCT_AREA_KINDS = ['feature', 'agent'] as const;\nexport type ProductAreaKind = (typeof PRODUCT_AREA_KINDS)[number];\n\n/** Lifecycle order is explicit; PostgreSQL enum sort order is not the stage order. */\nexport const PRODUCT_MAP_STAGE_RANK: Record<ProductMapStatus, number> = {\n deprecated: 0,\n in_dev: 1,\n alpha: 2,\n beta: 3,\n ga: 4,\n};\n\n/** Normalize legacy status in proposal acceptance, open-proposal comparison, and history field diffs. */\nexport function normalizeProductMapRevisionStatus(status: unknown): ProductMapStatus | undefined {\n if (status === 'active') return 'ga';\n return PRODUCT_MAP_STATUSES.find((stage) => stage === status);\n}\n\n/** Who last wrote a product-map row: the planning agent, or a human editor. */\nexport const PRODUCT_MAP_SOURCES = ['agent', 'user'] as const;\nexport type ProductMapSource = (typeof PRODUCT_MAP_SOURCES)[number];\n\n/**\n * Capability priority, ordered most to least urgent. Deliberately the bare\n * `p0`…`p3` shorthand rather than adjectives: every engineering org already\n * reads a p-level unambiguously, whereas \"critical\" vs \"high\" invites debate\n * about which is which. `p2` is the default for a newly created capability.\n */\nexport const PRODUCT_MAP_IMPORTANCES = ['p0', 'p1', 'p2', 'p3'] as const;\nexport type ProductMapImportance = (typeof PRODUCT_MAP_IMPORTANCES)[number];\n\n/** Entity kind a `product_map_revisions` row (or a lock) refers to. */\nexport const PRODUCT_MAP_ENTITY_TYPES = ['area', 'capability', 'rule'] as const;\nexport type ProductMapEntityType = (typeof PRODUCT_MAP_ENTITY_TYPES)[number];\n\nexport const PRODUCT_MAP_CHANGE_KINDS = [\n 'created',\n 'updated',\n 'deprecated',\n 'proposal_rejected',\n /** A capability's code changed — it moved area, or its area's code was edited. */\n 'recoded',\n 'persona_access',\n] as const;\nexport type ProductMapChangeKind = (typeof PRODUCT_MAP_CHANGE_KINDS)[number];\n\nexport const PRODUCT_MAP_ACTOR_KINDS = ['user', 'agent'] as const;\nexport type ProductMapActorKind = (typeof PRODUCT_MAP_ACTOR_KINDS)[number];\n\n/**\n * Per-capability coverage classification (see `packages/db/src/repositories/product-map/coverage.ts`):\n * - `covered` — an active link to an eligible, non-degraded workflow with a recent pass, or a\n * linked eval whose latest full run passed recently.\n * - `known_issue` — not covered, every linked workflow is red (never passed, failing or\n * degraded) or blocked on an open issue (its latest status change cites one), every\n * linked eval is red (never passed, or its latest full run failed), and an open issue\n * is linked to the capability or one of its rules, or cited by a blocked workflow: the\n * red is explained, not untriaged. Counted on its own: never as linked (mapped) or covered.\n * - `stale` — has links, none passed recently, and some eligible unpaused workflow or some\n * linked eval passed before (a workflow degraded or not recently passing, an eval whose last pass\n * is old or whose latest full run failed).\n * - `uncovered` — no active links and no linked eval, or every linked workflow is ineligible or\n * paused and no linked eval ever passed.\n * - `blocked` — coverage cannot be built: a person set `blocked_reason` (an environment or\n * fixture is missing), or no flow or eval is linked and an open issue is linked (derived). Excluded\n * from the linked/covered denominators regardless of what (if anything) is linked to it.\n */\nexport const CAPABILITY_COVERAGE_STATUSES = [\n 'covered',\n 'known_issue',\n 'stale',\n 'uncovered',\n 'blocked',\n] as const;\nexport type CapabilityCoverageStatus = (typeof CAPABILITY_COVERAGE_STATUSES)[number];\n\n/** Forward-compatible structured payload stored in Product Map JSONB columns. */\nexport type ProductMapJsonObject = Record<string, unknown>;\n\n/**\n * A single testable rule attached to a capability — previously a markdown\n * bullet under a capability's `## Rules` section, now its own record so\n * agents can add/edit one rule at a time with a per-rule lock. `status`\n * mirrors `ProductMapStatus`: `deprecated` is the soft delete.\n */\nexport interface CapabilityRule {\n id: string;\n capabilityId: string;\n statement: string;\n status: ProductMapStatus;\n source: ProductMapSource;\n createdAt: string;\n updatedAt: string;\n}\n","/**\n * Announcement config — the extensible, per-UI-component payload attached to a\n * feature-flag announcement.\n *\n * This is the single source of truth shared by:\n * - the API (superadmin route body validation + the viewer read endpoint)\n * - the web SPA (the component registry that renders each announcement)\n *\n * Extensibility: `uiComponent` is a discriminated union. Adding a future\n * surface (e.g. a `custom_view`) is one new `z.object({ uiComponent:\n * z.literal('…'), … })` member here plus one component in the web registry —\n * nothing else changes. The default (and only initial) surface is the\n * interstitial dialog.\n */\nimport { z } from 'zod';\n\n/** How a viewer's \"seen\" state is de-duplicated for an announcement. */\nexport const ANNOUNCEMENT_SEEN_SCOPES = ['user', 'org'] as const;\nexport type AnnouncementSeenScope = (typeof ANNOUNCEMENT_SEEN_SCOPES)[number];\n\n/** Known UI component kinds. Extend this union to add a new surface. */\nexport const ANNOUNCEMENT_UI_COMPONENTS = ['interstitial_dialog'] as const;\nexport type AnnouncementUiComponent = (typeof ANNOUNCEMENT_UI_COMPONENTS)[number];\n\n/**\n * Config for the default interstitial-dialog surface. `body` is rendered as\n * plain text for now (mirrors the Hints content surface); markdown is a\n * forward-compatible enhancement.\n */\nexport const interstitialDialogConfigSchema = z.object({\n uiComponent: z.literal('interstitial_dialog'),\n title: z.string().min(1).max(200),\n body: z.string().min(1).max(10_000),\n /** Optional call-to-action button. Both label and href required together. */\n ctaLabel: z.string().min(1).max(80).optional(),\n ctaHref: z.string().url().optional(),\n /** Overrides the default \"Got it\" dismiss button label. */\n dismissLabel: z.string().min(1).max(80).optional(),\n});\n\n/**\n * The discriminated union of every announcement UI-component config. Add future\n * variants as additional members keyed by a new `uiComponent` literal.\n */\nexport const announcementConfigSchema = z.discriminatedUnion('uiComponent', [\n interstitialDialogConfigSchema,\n]);\n\nexport type AnnouncementConfig = z.infer<typeof announcementConfigSchema>;\nexport type InterstitialDialogConfig = z.infer<typeof interstitialDialogConfigSchema>;\n\n/**\n * One announcement as delivered to a viewer by `GET /announcements/active`.\n * Deliberately slim — only what the renderer needs. The `config` discriminates\n * on `uiComponent`, so the frontend can pick a component without extra fields.\n */\nexport interface ActiveAnnouncement {\n id: string;\n uiComponent: AnnouncementUiComponent;\n config: AnnouncementConfig;\n}\n","/**\n * Shared page vocabulary for the unified page-aware assistant.\n *\n * ONE entry per page: its id, label, entity params, and the TanStack route\n * ids it covers. The web app derives its routeId→pageId mapping from\n * `routeIds` (so adding a page here wires the frontend automatically), and\n * the API's capability registry (`apps/api/src/assistant/capabilities/registry.ts`)\n * is keyed by `id`. Keeping the vocabulary here — like `user-features.ts` —\n * is what stops the two sides from drifting.\n *\n * Security note: the client-sent pageId only *selects* which capabilities the\n * agent can call; every capability handler re-validates entity ownership\n * through org-scoped repositories.\n */\n\nexport const ASSISTANT_PAGES = [\n { id: 'home', label: 'Home dashboard', entityParams: [], routeIds: ['/'] },\n { id: 'flows', label: 'Flows list', entityParams: [], routeIds: ['/flows'] },\n {\n id: 'flow-designer',\n label: 'Flow designer',\n // `runId` arrives via the ?runId search param, not the route path — the\n // web resolver carries it explicitly.\n entityParams: ['workflowId', 'runId'],\n routeIds: ['/flows/$workflowId'],\n },\n { id: 'runs', label: 'Runs list', entityParams: [], routeIds: ['/runs'] },\n { id: 'run-detail', label: 'Run detail', entityParams: ['runId'], routeIds: ['/runs/$runId'] },\n {\n id: 'releases',\n label: 'Release timeline',\n // `runId` is the selected run: the `/runs/$runId` route's path param, or\n // the timeline's `?runId=` search param (or the client's kickoff overlay\n // for the default-selected run) — `resolvePageContext` reads both.\n entityParams: ['propertyId', 'runId'],\n routeIds: ['/releases/$propertyId', '/releases/$propertyId/runs/$runId'],\n },\n { id: 'issues', label: 'Issues', entityParams: [], routeIds: ['/issues'] },\n { id: 'suites', label: 'Test suites', entityParams: [], routeIds: ['/suites'] },\n {\n id: 'suite-detail',\n label: 'Suite detail',\n entityParams: ['suiteId'],\n routeIds: ['/suites/$suiteId'],\n },\n { id: 'applications', label: 'Applications', entityParams: [], routeIds: ['/applications'] },\n {\n id: 'application-detail',\n label: 'Application detail',\n entityParams: ['propertyId'],\n routeIds: ['/applications/$propertyId'],\n },\n { id: 'exploratory', label: 'Exploratory tests', entityParams: [], routeIds: ['/exploratory'] },\n { id: 'explore', label: 'Explore', entityParams: [], routeIds: ['/explore'] },\n {\n id: 'explore-cycle',\n label: 'Exploration cycle',\n entityParams: ['cycleId'],\n routeIds: ['/explore/cycles/$cycleId'],\n },\n { id: 'settings', label: 'Settings', entityParams: [], routeIds: ['/settings'] },\n { id: 'test-data', label: 'Test data', entityParams: [], routeIds: ['/test-data'] },\n { id: 'boards', label: 'Boards', entityParams: [], routeIds: ['/boards'] },\n {\n id: 'product-map',\n label: 'Product map',\n entityParams: ['propertyId'],\n routeIds: ['/product-map'],\n },\n /** Fallback for routes without a registry entry — global capabilities only. */\n { id: 'unknown', label: 'Unknown page', entityParams: [], routeIds: [] },\n] as const;\n\nexport type AssistantPageId = (typeof ASSISTANT_PAGES)[number]['id'];\n\nexport const ASSISTANT_PAGE_IDS = ASSISTANT_PAGES.map((page) => page.id) as AssistantPageId[];\n\nexport function isAssistantPageId(value: string): value is AssistantPageId {\n return (ASSISTANT_PAGE_IDS as string[]).includes(value);\n}\n\nexport type AssistantPageInfo = (typeof ASSISTANT_PAGES)[number];\n\nexport function assistantPageLabel(pageId: AssistantPageId): string {\n return ASSISTANT_PAGES.find((page) => page.id === pageId)?.label ?? pageId;\n}\n\n/**\n * The name the capability meta-tool is registered under, and the only place it\n * is spelled.\n *\n * It crosses a package boundary that types cannot: the API registers the tool\n * under this key, and the web app filters AI SDK stream parts by\n * `tool-${name}`. Renaming this tool once already broke that filter silently —\n * the API side moved, no web file conflicted, and the effect that refetches\n * after an assistant write became dead code that no test could see. Import\n * this on both sides so the next rename is a compile error rather than a\n * missing refresh.\n */\nexport const CAPABILITY_TOOL_NAME = 'capabilities' as const;\n\n/** The AI SDK stream part `type` for a capability tool invocation. */\nexport const CAPABILITY_TOOL_STREAM_PART_TYPE = `tool-${CAPABILITY_TOOL_NAME}` as const;\n\n/**\n * Where the assistant is running. The same capability registry serves both, so\n * each capability declares which surfaces it belongs to rather than each\n * surface maintaining its own list of what it can do — that divergence is how\n * the chat agent ended up with a hand-rolled toolset that could not look\n * anything up.\n */\nexport const CAPABILITY_SURFACES = ['app', 'chat'] as const;\n\n/**\n * Named `CapabilitySurface`, not `AssistantSurface`: the latter already means\n * something different in `apps/api/src/assistant/schema.ts` (which parts of a\n * page the agent may drive). Two things called the same name in one subsystem\n * is how a reader ends up confidently wrong.\n */\nexport type CapabilitySurface = (typeof CAPABILITY_SURFACES)[number];\n\n/** Anything the page-scoped capability registry can be keyed by. */\nexport type CapabilityContextId = AssistantPageId;\n\n/**\n * Entity scope for a capability call. Chat starts at `unknown` and supplies\n * any capability-specific entity params explicitly on the call.\n */\nexport type CapabilityScope = {\n pageId: CapabilityContextId;\n /**\n * Only the app surface has one, and only its prompt builder reads it — no\n * capability does. Optional so the chat surface isn't forced to invent a\n * value it has no meaning for.\n */\n pathname?: string;\n params: Record<string, string>;\n};\n\n/**\n * The page context the web client sends with every assistant chat request.\n * `params` carries the entity params named in `ASSISTANT_PAGES` for the page\n * (plus nothing else); values are prefixed or raw ids as they appear in the\n * URL — the server extracts UUIDs before touching repositories.\n */\nexport type AssistantPageContext = {\n pageId: AssistantPageId;\n pathname: string;\n params: Record<string, string>;\n};\n"],"mappings":"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","names":["z","z","z","z","z","z","z","z","z","z","z","z","z","z","z","z","z","z","z","z","z","z","z","actionType","z","z","z","z","z","z","z","z","spec"]}