@runcofactor/cli 0.55.0 → 0.56.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 (248) hide show
  1. package/dist/{account-RHHS7HT4.js → account-CZYZD2JS.js} +5 -5
  2. package/dist/{alert-B5WEOKHP.js → alert-JQ4SDEB7.js} +8 -8
  3. package/dist/{api-catalog-UXONX3BR.js → api-catalog-IWQC35P2.js} +5 -5
  4. package/dist/{api-key-J3A4TJKN.js → api-key-GUDXIIEA.js} +5 -5
  5. package/dist/{attachment-ZQW7DR4G.js → attachment-CPZYOBUB.js} +5 -5
  6. package/dist/{auth-HQGKUZBS.js → auth-DS6MBVEI.js} +3 -3
  7. package/dist/{benchmark-TIKJA32J.js → benchmark-6HPCIQHD.js} +5 -5
  8. package/dist/{blackcat-Z5LNV2GS.js → blackcat-V3L63WFE.js} +3 -3
  9. package/dist/{chunk-JQG3V5E3.js → chunk-2E3STBHS.js} +2 -2
  10. package/dist/{chunk-KL2ARGH6.js → chunk-2OHYMT3A.js} +3 -3
  11. package/dist/{chunk-KL2ARGH6.js.map → chunk-2OHYMT3A.js.map} +1 -1
  12. package/dist/{chunk-W6DL4IP6.js → chunk-3LGCHNBC.js} +2 -2
  13. package/dist/{chunk-BJHAVM7Z.js → chunk-4MFMPC5P.js} +46 -2
  14. package/dist/chunk-4MFMPC5P.js.map +1 -0
  15. package/dist/{chunk-MB4FS35J.js → chunk-56HFZHQM.js} +8 -8
  16. package/dist/{chunk-CZDQVQ57.js → chunk-5OYWNS6D.js} +20 -9
  17. package/dist/chunk-5OYWNS6D.js.map +1 -0
  18. package/dist/{chunk-YTXZUWMN.js → chunk-62EXS76C.js} +6 -6
  19. package/dist/{chunk-QVMGXMP5.js → chunk-6ILWJFKY.js} +368 -6
  20. package/dist/chunk-6ILWJFKY.js.map +1 -0
  21. package/dist/{chunk-6X6ZTDXP.js → chunk-BDOL622T.js} +4 -4
  22. package/dist/{chunk-L4JQ236V.js → chunk-C3CXVX2B.js} +4 -4
  23. package/dist/{chunk-33U56YGD.js → chunk-CWICLBLU.js} +2 -1
  24. package/dist/{chunk-33U56YGD.js.map → chunk-CWICLBLU.js.map} +1 -1
  25. package/dist/{chunk-XM3WTJ7Y.js → chunk-DEVD2WD2.js} +2 -2
  26. package/dist/{chunk-WWUEKQLR.js → chunk-EWK44F6V.js} +3 -3
  27. package/dist/chunk-EWK44F6V.js.map +1 -0
  28. package/dist/{chunk-CQSQNDMV.js → chunk-FNHPTTRF.js} +7 -5
  29. package/dist/chunk-FNHPTTRF.js.map +1 -0
  30. package/dist/{chunk-Z72GWGH2.js → chunk-GCD4EHCD.js} +6 -6
  31. package/dist/{chunk-3KBYI6RR.js → chunk-H6ZMKNP7.js} +3 -3
  32. package/dist/{chunk-5OKCT6LF.js → chunk-IEWAH7W3.js} +5 -5
  33. package/dist/{chunk-TGI2AAOZ.js → chunk-IV4HRGKX.js} +77 -12
  34. package/dist/chunk-IV4HRGKX.js.map +1 -0
  35. package/dist/{chunk-635IQV6B.js → chunk-J2ZQULU3.js} +2 -2
  36. package/dist/{chunk-ZZZ33KTO.js → chunk-K2IDR6FR.js} +3 -3
  37. package/dist/{chunk-YEH7UIUY.js → chunk-KTZRI27M.js} +4 -4
  38. package/dist/{chunk-RV7FZ4ID.js → chunk-L644NMDI.js} +3 -3
  39. package/dist/{chunk-RV7FZ4ID.js.map → chunk-L644NMDI.js.map} +1 -1
  40. package/dist/{chunk-3TZABC7D.js → chunk-LFT7GUWS.js} +63 -17
  41. package/dist/chunk-LFT7GUWS.js.map +1 -0
  42. package/dist/{chunk-QHRWXCON.js → chunk-O7B2IYCZ.js} +11 -11
  43. package/dist/chunk-O7B2IYCZ.js.map +1 -0
  44. package/dist/{chunk-U3MRXQZS.js → chunk-RHWPZKRV.js} +24 -1
  45. package/dist/chunk-RHWPZKRV.js.map +1 -0
  46. package/dist/{chunk-OU4I3MEH.js → chunk-UJSW72LG.js} +3 -3
  47. package/dist/{chunk-R2CRH6JQ.js → chunk-VACACCD6.js} +3 -3
  48. package/dist/{chunk-EGWVL5XA.js → chunk-VMAR67FF.js} +2 -2
  49. package/dist/{chunk-ZH7HWVEE.js → chunk-VMJ7TBVM.js} +2 -2
  50. package/dist/chunk-WRRA4ATV.js +332 -0
  51. package/dist/chunk-WRRA4ATV.js.map +1 -0
  52. package/dist/{chunk-XWJJYZQC.js → chunk-XHEVDABB.js} +2 -2
  53. package/dist/{chunk-SCQVBLNR.js → chunk-XHGF6VC6.js} +4 -4
  54. package/dist/{credential-2IEZDTH3.js → credential-GJW7LGYP.js} +5 -5
  55. package/dist/{credential-template-S5WSOI3P.js → credential-template-UWBYKKWD.js} +5 -5
  56. package/dist/dev-PBW7BSOD.js +605 -0
  57. package/dist/dev-PBW7BSOD.js.map +1 -0
  58. package/dist/{digest-HQGL7H6E.js → digest-XEHGCJM4.js} +8 -8
  59. package/dist/{docs-CJQZVERZ.js → docs-KH4BCEQF.js} +5 -5
  60. package/dist/{doctor-EGK5AB77.js → doctor-FOXXE5SK.js} +3 -3
  61. package/dist/{environment-MWVXYEDU.js → environment-EBDPQTIS.js} +7 -7
  62. package/dist/{eval-F4NORBQI.js → eval-QZSSEICV.js} +67 -27
  63. package/dist/eval-QZSSEICV.js.map +1 -0
  64. package/dist/{exploration-JC7R7KTJ.js → exploration-LCGKRGOQ.js} +5 -5
  65. package/dist/{exploratory-test-BHK5FTMJ.js → exploratory-test-SGECQGIX.js} +6 -6
  66. package/dist/{external-run-UL5CKG54.js → external-run-2AJAI5O7.js} +9 -9
  67. package/dist/{feature-flag-NMO25TZN.js → feature-flag-P7N4WQIH.js} +5 -5
  68. package/dist/{feedback-3JNPSUXS.js → feedback-LAT7TQ2T.js} +6 -6
  69. package/dist/{flow-AYE4RTZN.js → flow-NH2DC6AP.js} +5 -5
  70. package/dist/{github-DM2DCXRY.js → github-FCEKBLXI.js} +6 -6
  71. package/dist/index.js +106 -408
  72. package/dist/index.js.map +1 -1
  73. package/dist/{init-CPDT5XAU.js → init-5U5EZOAW.js} +5 -5
  74. package/dist/{install-JZRNCHMO.js → install-X3UNVXKY.js} +5 -5
  75. package/dist/{issues-GRFJB52C.js → issues-4P6I3YQW.js} +8 -8
  76. package/dist/{knobs-3Y4QXWAD.js → knobs-JKTWJ6ZG.js} +5 -5
  77. package/dist/{landing-ABYL5WVG.js → landing-6AGLKI66.js} +7 -7
  78. package/dist/{list-WG5QB4NK.js → list-TWP2RP33.js} +5 -5
  79. package/dist/{local-KX24OR5Z.js → local-D75QE5TB.js} +7 -7
  80. package/dist/{login-MLJQAIFK.js → login-RBET4TIP.js} +5 -5
  81. package/dist/{mcp-DDTO54XD.js → mcp-562SUUMK.js} +11 -11
  82. package/dist/{metrics-BJLLOSPQ.js → metrics-4WF2GEPA.js} +5 -5
  83. package/dist/{monitor-E2CKGHPM.js → monitor-WJXH54H2.js} +5 -5
  84. package/dist/{perf-IX7JOMUE.js → perf-C6N6P7YG.js} +12 -361
  85. package/dist/perf-C6N6P7YG.js.map +1 -0
  86. package/dist/{product-2ECWM2JN.js → product-CYEQ7HOB.js} +5 -5
  87. package/dist/{product-map-O542LZQL.js → product-map-MSDLIFZY.js} +7 -7
  88. package/dist/{prompts-Y7O4XJ5P.js → prompts-ME53WPN4.js} +5 -5
  89. package/dist/{proofs-TNOBLSRP.js → proofs-D3G6ZUFP.js} +11 -9
  90. package/dist/{property-TZCGOZQG.js → property-5GLJNAVB.js} +29 -10
  91. package/dist/property-5GLJNAVB.js.map +1 -0
  92. package/dist/{psql-M66YSUXW.js → psql-EXAUDU3W.js} +5 -5
  93. package/dist/{record-3JINBRAR.js → record-EE7NGNKY.js} +6 -6
  94. package/dist/{redis-FOIBINYA.js → redis-M4EYWKOJ.js} +5 -5
  95. package/dist/{release-B4VWMVEI.js → release-4KUWVWWC.js} +5 -5
  96. package/dist/{runbook-EYMSE37E.js → runbook-AHU77ANQ.js} +5 -5
  97. package/dist/{sandbox-R6EMGOKR.js → sandbox-FBLJEYA2.js} +1910 -1629
  98. package/dist/sandbox-FBLJEYA2.js.map +1 -0
  99. package/dist/{scenarios-W7YNLBVO.js → scenarios-TU656T2B.js} +5 -5
  100. package/dist/{schedule-EDHQJUUZ.js → schedule-DXJI6SYO.js} +8 -8
  101. package/dist/{session-4N62APNQ.js → session-RUFTJHF4.js} +10 -10
  102. package/dist/{skill-XFGMNOVM.js → skill-ZK76GQ47.js} +7 -7
  103. package/dist/{skill-XFGMNOVM.js.map → skill-ZK76GQ47.js.map} +1 -1
  104. package/dist/skills/cofactor-debug-run/SKILL.md +2 -1
  105. package/dist/skills/cofactor-debug-run/history/2.0.10.md +320 -0
  106. package/dist/skills/cofactor-debug-run/version.json +1 -1
  107. package/dist/{slack-RUAXG6TB.js → slack-M3OKDVMY.js} +6 -6
  108. package/dist/{spec-ZZKMW7ER.js → spec-5MXEBLYR.js} +5 -5
  109. package/dist/{src-OKSAEHQS.js → src-2X5OQRPJ.js} +4 -4
  110. package/dist/{src-DGIVA22K.js → src-72EFBA2R.js} +3 -3
  111. package/dist/{src-ECZWYNSV.js → src-KH6WSBJV.js} +5 -5
  112. package/dist/{src-CQ2YAW7F.js → src-VLV36QYH.js} +127 -20
  113. package/dist/src-VLV36QYH.js.map +1 -0
  114. package/dist/{start-OCZGILRL.js → start-PDYBRB5T.js} +5 -5
  115. package/dist/{status-YTJ6YITG.js → status-VMVPVCDA.js} +5 -5
  116. package/dist/{tag-KBG4SWRL.js → tag-7KTDLPVU.js} +6 -6
  117. package/dist/{task-O6XRMHYE.js → task-QLFYZNKR.js} +6 -6
  118. package/dist/{teams-NFUHS7XI.js → teams-Y7BXCY6P.js} +7 -7
  119. package/dist/{test-suite-6BH3DESN.js → test-suite-6U22AYFG.js} +15 -14
  120. package/dist/test-suite-6U22AYFG.js.map +1 -0
  121. package/dist/{tickets-JU2STK4B.js → tickets-CQDJDNPY.js} +5 -5
  122. package/dist/{trace-NMSGKASJ.js → trace-QDORWXYX.js} +8 -8
  123. package/dist/{triage-JGECJJU5.js → triage-GY7M3UGG.js} +6 -6
  124. package/dist/{trigger-OSPEVCCQ.js → trigger-DLFAORX4.js} +9 -9
  125. package/dist/{update-check-FNP6RFZO.js → update-check-ES6XQ7OF.js} +4 -4
  126. package/dist/{upgrade-3PPBXFAT.js → upgrade-WKEOW5GS.js} +5 -5
  127. package/dist/{usage-QMCU2JIO.js → usage-E6FDTR3W.js} +7 -7
  128. package/dist/{utilities-7VHWTSJT.js → utilities-4Z6YX3L5.js} +6 -6
  129. package/dist/{visual-WIZSPTVK.js → visual-TA6LPVDJ.js} +5 -5
  130. package/dist/{workflow-23HKTJLS.js → workflow-B5FXS2CJ.js} +58 -31
  131. package/dist/workflow-B5FXS2CJ.js.map +1 -0
  132. package/dist/{workflow-cache-ACGM3BOI.js → workflow-cache-MNLEMRMM.js} +16 -16
  133. package/dist/{workflow-cache-convergence-resolve-KEQDQMKL.js → workflow-cache-convergence-resolve-G5HJM6MF.js} +5 -5
  134. package/dist/{workflow-debug-TVLBNRCB.js → workflow-debug-TNBUGSBK.js} +11 -11
  135. package/dist/{workflow-feedback-GB5VPGE2.js → workflow-feedback-G2ZXAUGI.js} +5 -5
  136. package/dist/{workflow-fork-2IPJNR76.js → workflow-fork-6HHOMC2D.js} +5 -5
  137. package/dist/{workflow-promotion-QEQUXVUT.js → workflow-promotion-RG7L6JZT.js} +5 -5
  138. package/dist/{workflow-proposals-KHGTPMD4.js → workflow-proposals-ZNF2F3UE.js} +5 -5
  139. package/dist/{workflow-revisions-F5KQEEXB.js → workflow-revisions-TP64Z6FO.js} +5 -5
  140. package/dist/{workflow-status-66HC5MEA.js → workflow-status-5RG3JX56.js} +5 -5
  141. package/package.json +3 -2
  142. package/dist/chunk-3TZABC7D.js.map +0 -1
  143. package/dist/chunk-BJHAVM7Z.js.map +0 -1
  144. package/dist/chunk-CQSQNDMV.js.map +0 -1
  145. package/dist/chunk-CZDQVQ57.js.map +0 -1
  146. package/dist/chunk-QHRWXCON.js.map +0 -1
  147. package/dist/chunk-QVMGXMP5.js.map +0 -1
  148. package/dist/chunk-TGI2AAOZ.js.map +0 -1
  149. package/dist/chunk-U3MRXQZS.js.map +0 -1
  150. package/dist/chunk-WWUEKQLR.js.map +0 -1
  151. package/dist/eval-F4NORBQI.js.map +0 -1
  152. package/dist/perf-IX7JOMUE.js.map +0 -1
  153. package/dist/property-TZCGOZQG.js.map +0 -1
  154. package/dist/sandbox-R6EMGOKR.js.map +0 -1
  155. package/dist/src-CQ2YAW7F.js.map +0 -1
  156. package/dist/test-suite-6BH3DESN.js.map +0 -1
  157. package/dist/workflow-23HKTJLS.js.map +0 -1
  158. /package/dist/{account-RHHS7HT4.js.map → account-CZYZD2JS.js.map} +0 -0
  159. /package/dist/{alert-B5WEOKHP.js.map → alert-JQ4SDEB7.js.map} +0 -0
  160. /package/dist/{api-catalog-UXONX3BR.js.map → api-catalog-IWQC35P2.js.map} +0 -0
  161. /package/dist/{api-key-J3A4TJKN.js.map → api-key-GUDXIIEA.js.map} +0 -0
  162. /package/dist/{attachment-ZQW7DR4G.js.map → attachment-CPZYOBUB.js.map} +0 -0
  163. /package/dist/{auth-HQGKUZBS.js.map → auth-DS6MBVEI.js.map} +0 -0
  164. /package/dist/{benchmark-TIKJA32J.js.map → benchmark-6HPCIQHD.js.map} +0 -0
  165. /package/dist/{blackcat-Z5LNV2GS.js.map → blackcat-V3L63WFE.js.map} +0 -0
  166. /package/dist/{chunk-JQG3V5E3.js.map → chunk-2E3STBHS.js.map} +0 -0
  167. /package/dist/{chunk-W6DL4IP6.js.map → chunk-3LGCHNBC.js.map} +0 -0
  168. /package/dist/{chunk-MB4FS35J.js.map → chunk-56HFZHQM.js.map} +0 -0
  169. /package/dist/{chunk-YTXZUWMN.js.map → chunk-62EXS76C.js.map} +0 -0
  170. /package/dist/{chunk-6X6ZTDXP.js.map → chunk-BDOL622T.js.map} +0 -0
  171. /package/dist/{chunk-L4JQ236V.js.map → chunk-C3CXVX2B.js.map} +0 -0
  172. /package/dist/{chunk-XM3WTJ7Y.js.map → chunk-DEVD2WD2.js.map} +0 -0
  173. /package/dist/{chunk-Z72GWGH2.js.map → chunk-GCD4EHCD.js.map} +0 -0
  174. /package/dist/{chunk-3KBYI6RR.js.map → chunk-H6ZMKNP7.js.map} +0 -0
  175. /package/dist/{chunk-5OKCT6LF.js.map → chunk-IEWAH7W3.js.map} +0 -0
  176. /package/dist/{chunk-635IQV6B.js.map → chunk-J2ZQULU3.js.map} +0 -0
  177. /package/dist/{chunk-ZZZ33KTO.js.map → chunk-K2IDR6FR.js.map} +0 -0
  178. /package/dist/{chunk-YEH7UIUY.js.map → chunk-KTZRI27M.js.map} +0 -0
  179. /package/dist/{chunk-OU4I3MEH.js.map → chunk-UJSW72LG.js.map} +0 -0
  180. /package/dist/{chunk-R2CRH6JQ.js.map → chunk-VACACCD6.js.map} +0 -0
  181. /package/dist/{chunk-EGWVL5XA.js.map → chunk-VMAR67FF.js.map} +0 -0
  182. /package/dist/{chunk-ZH7HWVEE.js.map → chunk-VMJ7TBVM.js.map} +0 -0
  183. /package/dist/{chunk-XWJJYZQC.js.map → chunk-XHEVDABB.js.map} +0 -0
  184. /package/dist/{chunk-SCQVBLNR.js.map → chunk-XHGF6VC6.js.map} +0 -0
  185. /package/dist/{credential-2IEZDTH3.js.map → credential-GJW7LGYP.js.map} +0 -0
  186. /package/dist/{credential-template-S5WSOI3P.js.map → credential-template-UWBYKKWD.js.map} +0 -0
  187. /package/dist/{digest-HQGL7H6E.js.map → digest-XEHGCJM4.js.map} +0 -0
  188. /package/dist/{docs-CJQZVERZ.js.map → docs-KH4BCEQF.js.map} +0 -0
  189. /package/dist/{doctor-EGK5AB77.js.map → doctor-FOXXE5SK.js.map} +0 -0
  190. /package/dist/{environment-MWVXYEDU.js.map → environment-EBDPQTIS.js.map} +0 -0
  191. /package/dist/{exploration-JC7R7KTJ.js.map → exploration-LCGKRGOQ.js.map} +0 -0
  192. /package/dist/{exploratory-test-BHK5FTMJ.js.map → exploratory-test-SGECQGIX.js.map} +0 -0
  193. /package/dist/{external-run-UL5CKG54.js.map → external-run-2AJAI5O7.js.map} +0 -0
  194. /package/dist/{feature-flag-NMO25TZN.js.map → feature-flag-P7N4WQIH.js.map} +0 -0
  195. /package/dist/{feedback-3JNPSUXS.js.map → feedback-LAT7TQ2T.js.map} +0 -0
  196. /package/dist/{flow-AYE4RTZN.js.map → flow-NH2DC6AP.js.map} +0 -0
  197. /package/dist/{github-DM2DCXRY.js.map → github-FCEKBLXI.js.map} +0 -0
  198. /package/dist/{init-CPDT5XAU.js.map → init-5U5EZOAW.js.map} +0 -0
  199. /package/dist/{install-JZRNCHMO.js.map → install-X3UNVXKY.js.map} +0 -0
  200. /package/dist/{issues-GRFJB52C.js.map → issues-4P6I3YQW.js.map} +0 -0
  201. /package/dist/{knobs-3Y4QXWAD.js.map → knobs-JKTWJ6ZG.js.map} +0 -0
  202. /package/dist/{landing-ABYL5WVG.js.map → landing-6AGLKI66.js.map} +0 -0
  203. /package/dist/{list-WG5QB4NK.js.map → list-TWP2RP33.js.map} +0 -0
  204. /package/dist/{local-KX24OR5Z.js.map → local-D75QE5TB.js.map} +0 -0
  205. /package/dist/{login-MLJQAIFK.js.map → login-RBET4TIP.js.map} +0 -0
  206. /package/dist/{mcp-DDTO54XD.js.map → mcp-562SUUMK.js.map} +0 -0
  207. /package/dist/{metrics-BJLLOSPQ.js.map → metrics-4WF2GEPA.js.map} +0 -0
  208. /package/dist/{monitor-E2CKGHPM.js.map → monitor-WJXH54H2.js.map} +0 -0
  209. /package/dist/{product-2ECWM2JN.js.map → product-CYEQ7HOB.js.map} +0 -0
  210. /package/dist/{product-map-O542LZQL.js.map → product-map-MSDLIFZY.js.map} +0 -0
  211. /package/dist/{prompts-Y7O4XJ5P.js.map → prompts-ME53WPN4.js.map} +0 -0
  212. /package/dist/{proofs-TNOBLSRP.js.map → proofs-D3G6ZUFP.js.map} +0 -0
  213. /package/dist/{psql-M66YSUXW.js.map → psql-EXAUDU3W.js.map} +0 -0
  214. /package/dist/{record-3JINBRAR.js.map → record-EE7NGNKY.js.map} +0 -0
  215. /package/dist/{redis-FOIBINYA.js.map → redis-M4EYWKOJ.js.map} +0 -0
  216. /package/dist/{release-B4VWMVEI.js.map → release-4KUWVWWC.js.map} +0 -0
  217. /package/dist/{runbook-EYMSE37E.js.map → runbook-AHU77ANQ.js.map} +0 -0
  218. /package/dist/{scenarios-W7YNLBVO.js.map → scenarios-TU656T2B.js.map} +0 -0
  219. /package/dist/{schedule-EDHQJUUZ.js.map → schedule-DXJI6SYO.js.map} +0 -0
  220. /package/dist/{session-4N62APNQ.js.map → session-RUFTJHF4.js.map} +0 -0
  221. /package/dist/{slack-RUAXG6TB.js.map → slack-M3OKDVMY.js.map} +0 -0
  222. /package/dist/{spec-ZZKMW7ER.js.map → spec-5MXEBLYR.js.map} +0 -0
  223. /package/dist/{src-DGIVA22K.js.map → src-2X5OQRPJ.js.map} +0 -0
  224. /package/dist/{src-ECZWYNSV.js.map → src-72EFBA2R.js.map} +0 -0
  225. /package/dist/{src-OKSAEHQS.js.map → src-KH6WSBJV.js.map} +0 -0
  226. /package/dist/{start-OCZGILRL.js.map → start-PDYBRB5T.js.map} +0 -0
  227. /package/dist/{status-YTJ6YITG.js.map → status-VMVPVCDA.js.map} +0 -0
  228. /package/dist/{tag-KBG4SWRL.js.map → tag-7KTDLPVU.js.map} +0 -0
  229. /package/dist/{task-O6XRMHYE.js.map → task-QLFYZNKR.js.map} +0 -0
  230. /package/dist/{teams-NFUHS7XI.js.map → teams-Y7BXCY6P.js.map} +0 -0
  231. /package/dist/{tickets-JU2STK4B.js.map → tickets-CQDJDNPY.js.map} +0 -0
  232. /package/dist/{trace-NMSGKASJ.js.map → trace-QDORWXYX.js.map} +0 -0
  233. /package/dist/{triage-JGECJJU5.js.map → triage-GY7M3UGG.js.map} +0 -0
  234. /package/dist/{trigger-OSPEVCCQ.js.map → trigger-DLFAORX4.js.map} +0 -0
  235. /package/dist/{update-check-FNP6RFZO.js.map → update-check-ES6XQ7OF.js.map} +0 -0
  236. /package/dist/{upgrade-3PPBXFAT.js.map → upgrade-WKEOW5GS.js.map} +0 -0
  237. /package/dist/{usage-QMCU2JIO.js.map → usage-E6FDTR3W.js.map} +0 -0
  238. /package/dist/{utilities-7VHWTSJT.js.map → utilities-4Z6YX3L5.js.map} +0 -0
  239. /package/dist/{visual-WIZSPTVK.js.map → visual-TA6LPVDJ.js.map} +0 -0
  240. /package/dist/{workflow-cache-ACGM3BOI.js.map → workflow-cache-MNLEMRMM.js.map} +0 -0
  241. /package/dist/{workflow-cache-convergence-resolve-KEQDQMKL.js.map → workflow-cache-convergence-resolve-G5HJM6MF.js.map} +0 -0
  242. /package/dist/{workflow-debug-TVLBNRCB.js.map → workflow-debug-TNBUGSBK.js.map} +0 -0
  243. /package/dist/{workflow-feedback-GB5VPGE2.js.map → workflow-feedback-G2ZXAUGI.js.map} +0 -0
  244. /package/dist/{workflow-fork-2IPJNR76.js.map → workflow-fork-6HHOMC2D.js.map} +0 -0
  245. /package/dist/{workflow-promotion-QEQUXVUT.js.map → workflow-promotion-RG7L6JZT.js.map} +0 -0
  246. /package/dist/{workflow-proposals-KHGTPMD4.js.map → workflow-proposals-ZNF2F3UE.js.map} +0 -0
  247. /package/dist/{workflow-revisions-F5KQEEXB.js.map → workflow-revisions-TP64Z6FO.js.map} +0 -0
  248. /package/dist/{workflow-status-66HC5MEA.js.map → workflow-status-5RG3JX56.js.map} +0 -0
@@ -1,11 +1,11 @@
1
1
  import { createRequire as __cr } from "module"; import { fileURLToPath as __fup } from "url"; import { dirname as __dn } from "path"; const require = __cr(import.meta.url); const __filename = __fup(import.meta.url); const __dirname = __dn(__filename);
2
2
  import {
3
3
  getGuideSectionArg
4
- } from "./chunk-WWUEKQLR.js";
4
+ } from "./chunk-EWK44F6V.js";
5
5
  import {
6
6
  hasFlag,
7
7
  resolveApiUrl
8
- } from "./chunk-BJHAVM7Z.js";
8
+ } from "./chunk-4MFMPC5P.js";
9
9
  import {
10
10
  EVAL_GUIDE_SECTIONS,
11
11
  EXPLORATORY_TEST_GUIDE_SECTIONS,
@@ -18,7 +18,7 @@ import {
18
18
  command,
19
19
  stripControlCharacters,
20
20
  version
21
- } from "./chunk-KL2ARGH6.js";
21
+ } from "./chunk-2OHYMT3A.js";
22
22
 
23
23
  // ../content/src/sandbox-v2-guide/support.ts
24
24
  var SANDBOX_V2_SUPPORT_MATRIX = [
@@ -274,7 +274,7 @@ function wrap(text, first) {
274
274
  }
275
275
 
276
276
  // ../content/src/sandbox-v2-guide/overview.txt
277
- var overview_default = "A Sandboxes v2 sandbox is a VM restored from a stack of snapshot layers,\nnot a container stood up from scratch each time. That's what makes claims\nfast: the work (installing the toolchain and dependencies, building the\napp, seeding its data) happens once per commit and environment and is\nbaked into a snapshot, and every claim restores its own copy.\n\nEvery example in this guide uses one invented app: a project-tracking\nSaaS with workspaces, their members (roles: owner, member, viewer),\nprojects and tasks, a REST API with cookie sessions, and a Postgres\ndatabase. Substitute your own app's objects, roles and endpoints.\n\nSETTING UP A NEW APPLICATION: FEASIBILITY FIRST\n\n The goal is a hermetic sandbox: the app and everything it depends on\n (its databases, queues, workers and third-party services, as services,\n built-ins or emulators) run inside the sandbox, seeded and reproducible\n at every commit. Only the calls the app has to make to a real provider\n (see the `real` verdict) leave it. A hermetic sandbox is what the agent\n builds, without asking, unless it isn't feasible or the user says\n otherwise.\n\n Before writing a cofactor.sandbox.yaml, check what the app needs against\n what a sandbox supports (`cofactor sandbox guide feasibility`): list its\n databases, queues, functions, third-party services, sign-in and platform\n needs, give each its verdict, tell the user the gaps, and stop and ask\n about anything unsupported (the topic lists when else to stop, and\n when a procedure may batch its questions). Only if a hermetic sandbox\n isn't feasible, or the user asks for it, does the same topic's escape\n hatch apply: a frontend-only sandbox pointed at an existing\n non-production backend,\n explained to the user with its pros and cons before it is built. Then\n write the definition (`definition`), validate it (`cofactor sandbox\n validate`), connect or push it, claim a sandbox and check its logins\n (`verify`).\n\nLAYERS\n\n platform Cofactor's sandbox helper, which runs your services,\n prepare commands and actions. Rebuilt only when the\n helper's contract with Cofactor changes.\n\n toolchain `setup` from cofactor.sandbox.yaml, plus `resources` and the\n declared `ports`. Rebuilt only when those change \u2014 this is\n the expensive layer (apt packages, language runtimes,\n database clusters).\n\n repo base The repository checked out and built at a commit:\n `lifecycle.build` steps, each rerun only when the git\n pathspecs in its `inputs` changed since the layer this one\n is stacked on. This is what makes \"the same commit, again\"\n nearly free.\n\n ready One commit and one environment: the repo base moved to the\n snapshot commit, prepared, and the environment's seeds run \u2014 then\n captured while its services are running. Built once, the\n first time anything asks for that commit and environment;\n every claim is a copy of it.\n\nA GitHub-delivered sandbox is identified by **repository + definition\npath**, not by repository alone, so a monorepo can hold several (see\nbelow). Pushed images have no repository or build layers: the image is\nthe base, and each push gets its own ready snapshots (see the `upload`\ntopic).\n\nTHE CLAIM LIFECYCLE\n\n 1. snapshot The claim needs the ready snapshot for its commit and\n environment. If there isn't one yet, it is built first\n (once, however many claims are waiting on it):\n - the nearest layer (toolchain or repo base) is\n restored;\n - the outstanding `lifecycle.build` steps bring it to\n the commit (only the steps whose `inputs` changed);\n - `prepare` runs (see step 3);\n - the environment's seed chain runs, in order (parent's\n seeds before its own \u2014 see the `environments` topic);\n each step is an action or an emulator operation,\n and its output can be referenced by later steps;\n - the VM is captured with its services running.\n 2. restore A copy of the ready snapshot is restored. Nothing is\n built or seeded: the copy starts exactly where the\n snapshot left off, data included.\n 3. identity A copy whose port params are all live, with stored snapshot\n prepare results, receives public port URLs through one\n copy-values exec. The sandbox materializes\n /var/lib/cofactor/live-env.json for the\n running app; lifecycle.prepare does NOT run. There is no\n service restart, emulator reconfiguration or bucket creation.\n Secrets, persona passwords, emulator token/config and seeded\n storage stay exactly as captured. A user claim receives its\n private sync key by a separate exec, never in the copy-values\n exec or its file. Non-user copies cannot be synced.\n Legacy prepare runs instead when any port param is non-live,\n an env key mixes live/non-live params, or an emulator config\n references a port param; it keeps per-copy prepare and restart.\n Pinned PR-preview captures without stored prepare results\n also keep the existing copy prepare path.\n 4. ready The sandbox reports its port URLs, persona logins, and the\n seed outputs (the same for every copy of the snapshot).\n From here it behaves like a real running instance of the\n app until it's destroyed or its lease expires. A copy is\n never reused.\n\n`cofactor sandbox show` breaks the claim's time down into these phases.\n\nA failure while building the snapshot (a build step, `prepare` or a seed)\nfails the claims waiting on it; the next claim of that commit and\nenvironment builds the snapshot again, so a failure that happens every\ntime needs a fix and a new commit or push. See the `verify` topic for what to do\nwhen that happens.\n\nA failure of the sandbox platform itself (a worker that is slow to restore,\na capture that is still running) is retried: the snapshot's build tries\nagain, resuming where it stopped, and so does claiming the copy when the\nfleet has no room. Every claim has a time limit \u2014 2 hours unless\n`cofactor sandbox claim --timeout` sets another. A claim not ready by then\nfails with the last problem it met, and a sandbox is never handed over after\nit: a copy still being made at the limit is destroyed when it finishes. That\ncan be up to about 10 minutes of waiting for room plus the copy's prepare\nafter the limit, so the failure may arrive later than the limit itself.\n\nAPPLICATIONS, AND WHERE THEIR CODE COMES FROM\n\n A sandbox is always a sandbox of a Cofactor application. How the\n application's code reaches its sandboxes is its own setting:\n\n - GitHub (`cofactor sandbox connect --application A --repo owner/name\n [--dir D]`): the `cofactor.sandbox.yaml` under `D` at each commit\n installs (`setup`) and builds (`lifecycle.build`) a checkout. A\n monorepo can connect one application per definition directory.\n Commands run **from that definition's directory** \u2014 an action's\n `run:` path, build steps and services resolve relative to where the\n `cofactor.sandbox.yaml` lives, not the repo root.\n - Pushed images (`cofactor sandbox push`), for a team that gives\n Cofactor no repository access: see the `upload` topic.\n\n Either way, each environment in the definition is one sandbox\n environment of the application, and each persona one of its personas, so\n test runs on that environment sign in with the sandbox's own logins.\n There is no need to create the application first: `connect` or `push`\n with a new application name creates it, and its sandbox `default`\n environment is its default environment.\n\nHOW A TEST AGENT USES THIS\n\n 1. Browse what's available: `cofactor sandbox envs --application <name>`\n lists the definition's environments, actions, emulator operations,\n ports and personas.\n 2. Claim one: `cofactor sandbox claim --application <name>\n [--environment <name>] --wait`. Without `--environment` you get\n `default`.\n 3. Write the test against the claimed sandbox: drive the app through its\n port URLs, call actions to set up data the test needs\n (`cofactor sandbox run <id> <action>`), and call emulator operations\n to inspect or drive third parties (`cofactor sandbox run <id>\n <plugin.op>`).\n 4. Try uncommitted changes against it: `cofactor sandbox sync <id>`\n writes your working tree into the sandbox, then `cofactor workflow\n run <flow> --sandbox <id>` runs a flow on it (see the `local` topic); `cofactor sandbox claims` lists your live claims.\n 5. Destroy it when done: `cofactor sandbox destroy <id>`.\n";
277
+ var overview_default = "A Sandboxes v2 sandbox is a VM restored from a stack of snapshot layers,\nnot a container stood up from scratch each time. That's what makes claims\nfast: the work (installing the toolchain and dependencies, building the\napp, seeding its data) happens once per commit and environment and is\nbaked into a snapshot, and every claim restores its own copy.\n\nEvery example in this guide uses one invented app: a project-tracking\nSaaS with workspaces, their members (roles: owner, member, viewer),\nprojects and tasks, a REST API with cookie sessions, and a Postgres\ndatabase. Substitute your own app's objects, roles and endpoints.\n\nSETTING UP A NEW APPLICATION: FEASIBILITY FIRST\n\n The goal is a hermetic sandbox: the app and everything it depends on\n (its databases, queues, workers and third-party services, as services,\n built-ins or emulators) run inside the sandbox, seeded and reproducible\n at every commit. Only the calls the app has to make to a real provider\n (see the `real` verdict) leave it. A hermetic sandbox is what the agent\n builds, without asking, unless it isn't feasible or the user says\n otherwise.\n\n Before writing a cofactor.sandbox.yaml, check what the app needs against\n what a sandbox supports (`cofactor sandbox guide feasibility`): list its\n databases, queues, functions, third-party services, sign-in and platform\n needs, give each its verdict, tell the user the gaps, and stop and ask\n about anything unsupported (the topic lists when else to stop, and\n when a procedure may batch its questions). Only if a hermetic sandbox\n isn't feasible, or the user asks for it, does the same topic's escape\n hatch apply: a frontend-only sandbox pointed at an existing\n non-production backend,\n explained to the user with its pros and cons before it is built. Then\n write the definition (`definition`), validate it (`cofactor sandbox\n validate`), connect or push it, claim a sandbox and check its logins\n (`verify`). The steps for a GitHub repository, from the first claim on a\n local files or a branch through proof before merge, are in `cli`, SETTING UP A NEW GITHUB APPLICATION.\n\n For a GitHub-delivered application, never build or run the app in local\n Docker (or any local container) to try a definition. A sandbox is a VM\n built from the definition, not that container, so a local build proves\n nothing about it and costs minutes. `validate` is the instant local\n check; a claim is the real test. (A pushed application's image is built\n locally by design: see `upload`.)\n\n[cli >= 0.56.0]\n Setup and proof do not require a merge. Connect with --at <branch-sha>,\n or claim --from-local to use local files without a user commit. Iterate\n with sync --build, then verify-env --at the final source SHA. Merge only\n when the user wants the definition on the default branch.\n[/cli]\n\nLAYERS\n\n platform Cofactor's sandbox helper, which runs your services,\n prepare commands and actions. Rebuilt only when the\n helper's contract with Cofactor changes.\n\n toolchain `setup` from cofactor.sandbox.yaml, plus `resources` and the\n declared `ports`. Rebuilt only when those change \u2014 this is\n the expensive layer (apt packages, language runtimes,\n database clusters).\n\n repo base The repository checked out and built at a commit:\n `lifecycle.build` steps, each rerun only when the git\n pathspecs in its `inputs` changed since the layer this one\n is stacked on. This is what makes \"the same commit, again\"\n nearly free.\n\n ready One commit and one environment: the repo base moved to the\n snapshot commit, prepared, and the environment's seeds run \u2014 then\n captured while its services are running. Built once, the\n first time anything asks for that commit and environment;\n every claim is a copy of it.\n\nA GitHub-delivered sandbox is identified by **repository + definition\npath**, not by repository alone, so a monorepo can hold several (see\nbelow). Pushed images have no repository or build layers: the image is\nthe base, and each push gets its own ready snapshots (see the `upload`\ntopic).\n\nTHE CLAIM LIFECYCLE\n\n 1. snapshot The claim needs the ready snapshot for its commit and\n environment. If there isn't one yet, it is built first\n (once, however many claims are waiting on it):\n - the nearest layer (toolchain or repo base) is\n restored;\n - the outstanding `lifecycle.build` steps bring it to\n the commit (only the steps whose `inputs` changed);\n - `prepare` runs (see step 3);\n - the environment's seed chain runs, in order (parent's\n seeds before its own \u2014 see the `environments` topic);\n each step is an action or an emulator operation,\n and its output can be referenced by later steps;\n - the VM is captured with its services running.\n 2. restore A copy of the ready snapshot is restored. Nothing is\n built or seeded: the copy starts exactly where the\n snapshot left off, data included.\n 3. identity A copy whose port params are all live, with stored snapshot\n prepare results, receives public port URLs through one\n copy-values exec. The sandbox materializes\n /var/lib/cofactor/live-env.json for the\n running app; lifecycle.prepare does NOT run. There is no\n service restart, emulator reconfiguration or bucket creation.\n Secrets, persona passwords, emulator token/config and seeded\n storage stay exactly as captured. A user claim receives its\n private sync key by a separate exec, never in the copy-values\n exec or its file. Non-user copies cannot be synced.\n Legacy prepare runs instead when any port param is non-live,\n an env key mixes live/non-live params, or an emulator config\n references a port param; it keeps per-copy prepare and restart.\n Pinned PR-preview captures without stored prepare results\n also keep the existing copy prepare path.\n 4. ready The sandbox reports its port URLs, persona logins, and the\n seed outputs (the same for every copy of the snapshot).\n From here it behaves like a real running instance of the\n app until it's destroyed or its lease expires. A copy is\n never reused.\n\n`cofactor sandbox show` breaks the claim's time down into these phases.\n\nA failure while building the snapshot (a build step, `prepare` or a seed)\nfails the claims waiting on it; the next claim of that commit and\nenvironment builds the snapshot again, so a failure that happens every\ntime needs a fix and a new source version. See the `verify` topic for what to do\nwhen that happens.\n\nA failure of the sandbox platform itself (a worker that is slow to restore,\na capture that is still running) is retried: the snapshot's build tries\nagain, resuming where it stopped, and so does claiming the copy when the\nfleet has no room. Every claim has a time limit \u2014 2 hours unless\n`cofactor sandbox claim --timeout` sets another. A claim not ready by then\nfails with the last problem it met, and a sandbox is never handed over after\nit: a copy still being made at the limit is destroyed when it finishes. That\ncan be up to about 10 minutes of waiting for room plus the copy's prepare\nafter the limit, so the failure may arrive later than the limit itself.\n\nAPPLICATIONS, AND WHERE THEIR CODE COMES FROM\n\n A sandbox is always a sandbox of a Cofactor application. How the\n application's code reaches its sandboxes is its own setting:\n\n - GitHub (`cofactor sandbox connect --application A --repo owner/name\n [--dir D]`): the `cofactor.sandbox.yaml` under `D` at each commit\n installs (`setup`) and builds (`lifecycle.build`) a checkout. A\n monorepo can connect one application per definition directory.\n Commands run **from that definition's directory** \u2014 an action's\n `run:` path, build steps and services resolve relative to where the\n `cofactor.sandbox.yaml` lives, not the repo root.\n - Pushed images (`cofactor sandbox push`), for a team that gives\n Cofactor no repository access: see the `upload` topic.\n\n Either way, each environment in the definition is one sandbox\n environment of the application, and each persona one of its personas, so\n test runs on that environment sign in with the sandbox's own logins.\n There is no need to create the application first: `connect` or `push`\n with a new application name creates it, and its sandbox `default`\n environment is its default environment.\n\nHOW A TEST AGENT USES THIS\n\n 1. Browse what's available: `cofactor sandbox envs --application <name>`\n lists the definition's environments, actions, emulator operations,\n ports and personas.\n 2. Claim one: `cofactor sandbox claim --application <name>\n [--environment <name>] --wait`. Without `--environment` you get\n `default`.\n 3. Write the test against the claimed sandbox: drive the app through its\n port URLs, call actions to set up data the test needs\n (`cofactor sandbox run <id> <action>`), and call emulator operations\n to inspect or drive third parties (`cofactor sandbox run <id>\n <plugin.op>`).\n 4. Try uncommitted changes against it: `cofactor sandbox sync <id>`\n writes your working tree into the sandbox, then `cofactor workflow\n run <flow> --sandbox <id>` runs a flow on it (see the `local` topic); `cofactor sandbox claims` lists your live claims.\n 5. Destroy it when done: `cofactor sandbox destroy <id>`.\n";
278
278
 
279
279
  // ../content/src/sandbox-v2-guide/feasibility.txt
280
280
  var feasibility_default = "Check what the app needs before you write its cofactor.sandbox.yaml.\n\nTHE GOAL: A HERMETIC SANDBOX\n\n The app and everything it depends on run inside the sandbox: its\n databases, queues and workers as services, object storage and sign-in\n as built-ins, third parties as emulators. Every copy is seeded the same\n way at every commit and has its own data, so a test can't be flaked by\n anything shared, a pull request's backend changes are exercised, and a\n result at a commit means what it says.\n\n Build a hermetic sandbox unless this check shows it isn't feasible or\n the user tells you not to. Don't ask first, and don't offer the\n frontend-only escape hatch below as an equal choice.\n\n A sandbox is one Linux amd64 machine with no Docker inside, open egress,\n and emulators for some third parties. A dependency that can't run there\n is far cheaper to find now than after the first failed snapshot.\n\nTHE CHECK\n\n 1. List the app's dependencies from its own files, not from memory:\n - compose files (each `image:` is a dependency) and Dockerfiles\n (`FROM`: base OS and architecture);\n - package manifests (`package.json`, `requirements.txt`,\n `pyproject.toml`, `Gemfile`, `go.mod`, `*.csproj`; database\n drivers, queue clients, cloud SDKs such as `@aws-sdk/client-*` or\n `boto3`, payment, email and SMS libraries);\n - `.env.example` or the env schema (`*_URL`, `*_QUEUE_URL`,\n `*_TOPIC_ARN`, `AWS_*`, `SMTP_*`, provider keys);\n - infrastructure code (Terraform, CloudFormation or SAM, Serverless,\n CDK) for managed services the code reaches at runtime;\n - how users sign in.\n 2. Give each one its verdict from SUPPORT below:\n built-in Cofactor runs it for you.\n emulated An `emulation` plugin answers it inside the sandbox.\n native Nothing in the sandbox stops it, but Cofactor doesn't\n provide it: install it in `setup` (or the image) and\n run it as one of your `services`. Prove it comes up in\n a claimed copy.\n real The call leaves the sandbox for the real provider.\n Egress is open: a test key reaches the provider's test\n mode, a real credential acts on the real account.\n unsupported No way to run it in a sandbox today.\n Anything not listed is unknown: say so, and say what you would try.\n 3. Report it to the user before building anything: one line per\n dependency with its verdict, the file that shows it, and your plan\n (install it, declare the emulator, set a test key, leave the journey\n out). Put the same list in your final report and in the pull request\n that adds the definition.\n 4. Stop and ask the user when anything is unsupported, unknown on a\n journey they care about, or real with no test mode and no stand-in\n named for it in SUPPORT (a call to it acts on the real service:\n never give the sandbox real credentials for one). A `real` item with\n a stand-in named for it, such as ElasticMQ for SQS, is no blocker:\n run the stand-in as a service and point the app at it. Each blocker\n needs a decision: leave the journey out, disable the feature in the\n sandbox, stub it in the app, sign in another way, or, when the gaps\n make a hermetic sandbox infeasible, the escape hatch below. A\n procedure that batches its questions, such as the onboarding skill,\n may defer these per-item questions to one later question; it still\n stops before building when a hermetic sandbox isn't feasible.\n 5. A gap blocks a hermetic sandbox only when the app can't start or\n users can't sign in without it. A gap confined to some journeys, a\n feature or a test mode takes one of the step 4 decisions instead, and\n the sandbox is still hermetic. If nothing blocks, build it: go on to\n `definition`. If something does, explain to the user why, show them\n the table under HERMETIC OR FRONTEND-ONLY with your recommendation,\n and build only once they decide.\n\n AWS deserves a second look. `emulation.aws` answers only the services\n the definition gives a section, `emulation.email` answers SES, and\n `object_storage` answers S3 for a client that reads the standard\n endpoint settings. Every other AWS SDK client the app creates reaches\n real AWS: list each one in the report.\n\nTHE ESCAPE HATCH: A FRONTEND-ONLY SANDBOX\n\n Only when the check shows a hermetic sandbox isn't feasible (the app\n can't start or users can't sign in without an unsupported dependency:\n step 5), or the user explicitly asks for it, run only\n the frontend in the sandbox and point it at an existing backend: a\n staging or dev API. Never a production backend: tests and seeds create,\n change and delete data there. Ask the user which backend, and have them\n confirm it is not production and holds no real customer data, before you\n write it into the definition.\n\n It needs a backend the public internet can reach. Sandbox egress is\n open, but a backend only reachable over a VPN or a private network, or\n one that only accepts listed IP addresses, can't be reached.\n\n Serve the frontend and proxy the backend from the same origin. Each\n sandbox copy has its own public origin, and the browser sends a\n backend's cookies only to the host they were set for: a frontend on the\n copy's origin calling the backend's own origin is a cross-site call, its\n session cookies are third-party, and the backend's CORS would have to\n allow every copy's origin. So the sandbox's web service serves the\n frontend and forwards one fixed API path to the backend, and the\n browser only ever talks to the copy's own origin. Rewrite only the\n cookie's domain if the backend sets one (nginx `proxy_cookie_domain`, a\n dev server proxy's `cookieDomainRewrite`); keep Secure and SameSite as\n they are, since the copy is https. Hard-code the target: a proxy that\n takes it from the request is an open proxy on a public URL. Never have\n the proxy add a credential of its own (a service token, an\n Authorization header): anyone with the copy's URL would inherit it. The\n frontend's own API base URL becomes that same-origin path, and the\n backend's URL goes to the proxy. Both are fixed values in the\n definition's top-level `env` (with no credentials in the URL): secrets\n never reach build steps, so a bundle that bakes its API URL at build\n time reads it from there. The parts that differ from a hermetic\n definition (add `version`, `resources` and `environments` as in\n `definition`; install nginx and gettext-base in `setup`; for a pushed\n image, put them and the built bundle in the image instead, and leave\n `lifecycle.build` out):\n ports:\n web: { port: 3000, access: public }\n params:\n web_url: { port: web }\n env:\n PUBLIC_URL: ${params.web_url}\n API_BASE_URL: /api\n BACKEND_URL: https://api.staging.example.com\n BACKEND_HOST: api.staging.example.com\n services:\n web:\n run: sandbox/serve-web.sh\n health: { http: 3000, path: / }\n after_build: true # starts once the build made the bundle\n reload_on_prepare: true # sees the copy's PUBLIC_URL and secrets\n personas:\n owner: { email: qa-owner@example.com }\n authentication:\n run: sandbox/sign-in.sh\n lifecycle:\n build:\n - { name: frontend, run: npm ci && npm run build, inputs: [package-lock.json, \"src/**\"] }\n prepare: []\n where serve-web.sh renders the proxy config from $BACKEND_URL and\n $BACKEND_HOST with `envsubst` (nginx doesn't read the environment;\n pass envsubst the variable list so nginx's own `$host` is kept), then\n serves the built frontend on 3000 with:\n location /api/ {\n proxy_pass ${BACKEND_URL}/; # the trailing / strips /api\n proxy_ssl_server_name on;\n proxy_cookie_domain ${BACKEND_HOST} $host;\n proxy_redirect ${BACKEND_URL}/ /api/;\n }\n (BACKEND_URL is an origin with no trailing slash or path.)\n An app that sends a bearer token instead of a cookie can call the\n backend directly if the backend's CORS already allows the copy's\n origin. Don't loosen a shared backend's CORS for it (never `*` with\n credentials): use the proxy.\n A backend that checks the Origin header (CSRF protection, an origin\n allowlist) also sees the copy's origin through the proxy: have the\n proxy set Origin to the backend's own, or allow the copy's origin there.\n\n Personas are existing accounts on that backend: dedicated test accounts\n with only the access a test needs, never a person's own account or an\n administrator. The user stores each one's password as a secret from\n their own shell (`read -rs PASSWORD; printf %s \"$PASSWORD\" | cofactor\n sandbox secret set OWNER_PASSWORD --application <app>`), never in the\n definition or a committed file. Give the user that command to run; don't\n run it yourself. Name one secret per persona, `<PERSONA>_PASSWORD` with\n the persona's name upper-cased and `-` turned into `_` (OWNER_PASSWORD\n for `owner`); a secret is 8 to 8192 bytes. The user sets them before the\n first claim (after `push` or `connect` creates the application, since\n neither builds a snapshot): a snapshot built without them keeps failing\n sign-in until a new image push, or a new default-branch commit followed\n by `cofactor sandbox refresh --wait`, replaces it (`secrets`).\n `authentication.run` signs in through $PUBLIC_URL\n (see `authentication`) with the persona's email from COFACTOR_PERSONAS\n and the password from the secret for $COFACTOR_AUTH_PERSONA, which\n reaches the script as an environment variable; have the script stop with\n an error when that variable is empty. In POSIX sh:\n var=\"$(printf %s \"$COFACTOR_AUTH_PERSONA\" | tr 'a-z-' 'A-Z_')_PASSWORD\"\n password=\"$(printenv \"$var\" || true)\"\n [ -n \"$password\" ] || { echo \"$var is not set\" >&2; exit 1; }\n Never use the `password` in COFACTOR_PERSONAS here, as the\n `authentication` example does: it is the snapshot's generated one, which\n the backend doesn't know. This is what lets Cofactor's test runs and\n `cofactor sandbox verify-env` sign in (`validate` doesn't run the\n script). Every test run and agent acts as that account. Leave the\n persona step out of `lifecycle.prepare`: you can't set passwords on a\n shared backend. The password a claim prints for each persona is the\n snapshot's generated one, which that backend doesn't know, so a person\n using a frontend-only sandbox signs in with the account's own password.\n\n Seeds and actions change the shared backend for everyone using it:\n create what a test needs under names a later run can find and remove,\n and never delete or reset data a seed didn't create.\n\n Emulators only cover calls made inside the sandbox. In a frontend-only\n sandbox the backend makes its own third-party calls, from wherever it\n runs.\n\nHERMETIC OR FRONTEND-ONLY\n\n When a hermetic sandbox isn't feasible, show the user this with why and\n what you recommend, and let them decide:\n\n Hermetic Frontend-only\n Setup More work: every dependency Fast: the frontend and a\n runs inside proxy\n Data Every copy has its own, Shared with everyone using\n seeded the same way that backend; tests change\n real data there and need\n cleaning up\n Pull requests Frontend and backend changes Frontend changes only; the\n are both exercised PR's backend changes are not\n Flakiness Nothing shared The backend's uptime, data\n and deploys\n As proof Strong: everything at that Weaker: the backend is\n at a commit commit whatever it runs today\n Credentials Generated for each snapshot Real accounts on that\n backend, kept as secrets\n Backend Inside the sandbox Staging or dev, never\n production\n\nSUPPORT\n\n";
@@ -286,7 +286,7 @@ var definition_default = "Everything about a sandbox lives in one file: `cofacto
286
286
  var environments_default = "An environment (a \"flavor\") is the same sandbox \u2014 same code, same toolchain\n\u2014 started with different data and config. `default` is required; every\ndefinition must declare it. Previews use `default`, and so does `cofactor\nsandbox claim` when you don't pass `--environment`.\n\nWHEN TO ADD A FLAVOR VS. PASSING INPUTS IN THE TEST\n\n Rule of thumb: add a flavor when many tests need the same starting state.\n Call actions from inside the test when only that test needs the data.\n\n In the example app, `default` seeds one workspace with an owner, a\n member and a viewer, because nearly every test signs in to a workspace\n as one of those roles. A test about archiving a project, on the other\n hand, calls `create-project` itself (see the worked example in\n `seeds-and-actions`) rather than getting a new flavor. If you find\n yourself writing a flavor whose seeds only exist to serve one test,\n that data belongs in the test's own action calls instead.\n\n Add a second flavor only when real tests need a starting state `default`\n can't give them cheaply \u2014 typically a different plan or set of feature\n flags (see \"WHAT TO BUILD FIRST\" in `seeds-and-actions`). Don't add one\n speculatively.\n\nDESCRIPTIONS ARE FOR THE READER\n\n `description` is required and can be multi-line (YAML `|`). Write it for\n the agent choosing between environments \u2014 say what's already there and\n who can do what, not just a label:\n environments:\n default:\n description: |\n Workspace \"Acme\": owner is its owner, member a member,\n viewer a read-only viewer. No projects yet; create them\n with create-project.\n\n A vague description (\"the default setup\") gives an agent nothing to\n decide with. Say what exists, in terms of the ids and roles a test will\n actually use.\n\nEXTENDS\n\n `extends: <other-environment-name>` composes one environment on top of\n another. The child's snapshot runs the **parent's seeds first, then the\n child's own**, in that order. `env` merges the same way: parent's keys\n first, the child's last (so a child can override a parent's value).\n Chains can be more than two deep; a cycle is rejected by `validate`.\n\n Step ids must be **unique across the whole resolved chain** \u2014 a parent\n and its child can't reuse the same id, since the child's steps can\n reference the parent's outputs.\n\n An environment carries its own `description`, `extends`, `seeds` and\n `env`. It has no `params` or `personas` of its own: those belong to the\n definition and are the same in every environment.\n\nSEED STEPS AND `${id.key}`\n\n Each `seeds` step has an `id`, exactly one of `action` or `operation`,\n and an `input`:\n - `id`: unique within the resolved chain; later steps reference this\n step's output as `${id.key}`.\n - `action`: a declared action's slug, or `<plugin>.<op>` for an\n emulator operation (e.g. `email.sent-emails`).\n - `operation`: a platform built-in, today only `database.fast-forward`\n (see `data-fast-forward` and `seeds-and-actions`).\n - `input`: literal values, or `${id.key}` references to an earlier\n step's output.\n\n A reference that is the **entire** value keeps the output's real type:\n input: { workspace_id: \"${acme.workspace_id}\" } # exactly acme's workspace_id\n\n A reference **embedded** in a longer string becomes a string\n interpolation instead:\n input: { name: \"Board for ${acme.workspace_id}\" } # becomes a string\n\n `${id.key}` must point at a step that ran earlier in the chain and at one\n of that step's *declared* output keys \u2014 `cofactor sandbox validate`\n checks both and reports the path.\n\n The example app's environments, in full:\n environments:\n default:\n description: |\n Workspace \"Acme\": owner is its owner, member a member,\n viewer a read-only viewer. No projects yet.\n seeds:\n - id: acme\n action: create-workspace\n input: { name: Acme, as: owner }\n - id: add_member\n action: add-member\n input: { workspace_id: \"${acme.workspace_id}\", persona: member, role: member, as: owner }\n - id: add_viewer\n action: add-member\n input: { workspace_id: \"${acme.workspace_id}\", persona: viewer, role: viewer, as: owner }\n free-plan:\n extends: default\n description: Same as default, but Acme is moved to the Free plan (3-project limit).\n seeds:\n - id: downgrade\n action: set-plan\n input: { workspace_id: \"${acme.workspace_id}\", plan: free }\n beta-features:\n extends: default\n description: Same as default, with the timeline view turned on.\n env:\n FEATURE_FLAGS: timeline\n\n `add_member` couldn't run before `acme` \u2014 its `workspace_id` input\n depends on `acme`'s output. Seeds always run in the order they're\n written. `free-plan` sets its plan with a seed action because the app\n stores plans in its database; `beta-features` uses `env` because the\n app reads its flags from an environment variable (see the `env` topic\n for which processes see it).\n";
287
287
 
288
288
  // ../content/src/sandbox-v2-guide/seeds-and-actions.txt
289
- var seeds_and_actions_default = "An action is a script with a typed input and output schema. It is the one\nway data gets into a sandbox \u2014 seeds call it, and so do tests and\n`cofactor sandbox run`. Every environment's `seeds` list is\njust an ordered sequence of action (and emulator operation) calls. For\nthe script contract (`INPUT_<NAME>`, the result line), see the `upload`\ntopic's worked example; it is the same for GitHub-delivered apps.\n\nA SEED STEP THAT IS AN OPERATION\n\n A seed step runs exactly one of `action:` (a declared action, or an\n emulator operation named `<plugin>.<op>`) or `operation:` (a platform\n built-in). Today the only built-in is `database.fast-forward`, which\n moves a restored database's dates forward:\n - { id: fast_forward, operation: database.fast-forward, input: { database: app } }\n A step with both keys, or neither, fails `validate`. See the\n `data-fast-forward` topic.\n\nWHAT TO BUILD FIRST\n\n 1. One persona per role your tests need. The example app has three\n roles, so it declares `owner`, `member` and `viewer`. A persona in\n the definition is only a login (prepare makes it work \u2014 see\n `personas`); its role comes from a seed action such as `add-member`,\n or from prepare itself when the persona is an account your app\n already has with the right role. With one persona per role, tests can\n sign in as each and check what that role may and may not do.\n\n 2. One environment per plan or feature-flag combination your tests care\n about: `default`, `free-plan`, `beta-features`. Set each with a seed\n action that goes through your app's admin API or database (the\n example's `set-plan`), or with the environment's `env:` when your app\n reads the flag from an environment variable (the example's\n `FEATURE_FLAGS`; see the `env` topic for which processes see it).\n Params are per definition, not per environment, so they can't vary a\n flag between environments. See the `environments` topic.\n\n 3. Actions for the few core objects nearly every test needs, each taking\n `as: <persona>`: in the example, `create-workspace`, `add-member`,\n `create-project`, `create-task`. Keep the shared seeds thin; tests\n create the rest themselves with these actions.\n\n 4. If your app stores files, declare `object_storage` and seed the few\n files every test needs (a logo, an import template) with an action;\n see the `object-storage` topic.\n\nSEEDS VS. ACTIONS A TEST CALLS ITSELF\n\n Seeds are shared, thin, generic base data: the handful of things nearly\n every test needs before it can start \u2014 a workspace and its members, one\n per role. A shared seed stays thin and generic; it does not try to\n anticipate every test's needs.\n\n An action a test calls itself is data specific to that one test. If a\n test's job is \"archive a project,\" the project it archives should not\n live in a shared seed \u2014 a shared seed that every test's setup depends on\n becomes something every other test can break by changing it, and it\n bloats claim time for tests that never needed it. The test creates its\n own project by calling `create-project`, then operates on the id it got\n back.\n\n Rule of thumb: if removing this data would break tests that aren't about\n it, it's a seed. If only one test's own scenario needs it, it's an action\n call inside that test.\n\nWHAT MAKES A GOOD ACTION\n\n - Typed inputs and outputs. The input schema is what a test (or an\n agent) uses to call it correctly; the output schema is what the next\n step gets to chain from. Untyped free-form data defeats the ability to\n validate `${id.key}` references and CLI input ahead of time.\n - Fast. Seeds run once per version, while its snapshot is built; calls\n run inline in a test \u2014 slow seeds slow every new version's first\n claim, and slow calls slow every test.\n - Returns the ids a test needs next. `create-project` returns\n `project_id`, not just \"ok\" \u2014 that id is what the test's next action\n or assertion needs.\n - Safe to call more than once, or unique by its own input. Either the\n action is idempotent (`set-plan` re-run with the same plan changes\n nothing), or a distinct call naturally produces a distinct result\n (`create-project` with a new name makes a new project) rather than\n silently colliding with a previous call's data.\n - Explicit about who acts: the `as` persona pattern. Every action that\n performs a real business operation (creating a project, inviting a\n member) takes an explicit `as: <persona>` input naming which persona\n performs it, rather than assuming \"the admin\" or \"whoever is logged\n in.\" The script signs in as exactly that persona (its login is in\n `COFACTOR_PERSONAS`) and acts in the workspace it was given; the\n persona must already have a role there that allows the operation. So\n an action has **no hidden dependency** on some other seed having run\n first to establish who's \"in charge\" \u2014 the caller says so, in the\n input.\n - Goes through the app's own API when the data is encrypted or has side\n effects. `invite-user` calls the real invite endpoint so the\n invitation email is actually sent (and can be observed later with\n `email.sent-emails`); a raw insert would silently skip the send. The\n same goes for anything the app encrypts or hashes on write.\n - Goes through the database only for rows with no encryption and no side\n effects, and only when there is no API for it \u2014 e.g. `set-plan`\n updating a workspace's plan column, which in production only an\n internal billing process sets.\n\nANTI-PATTERNS\n\n - A giant do-everything seed. One seed step that creates a workspace,\n three members, two projects and their tasks in one script is harder to\n reuse, harder to debug when it fails partway, and forces every test\n that wants only part of it to take all of it. Break it into separate\n actions and chain them with `${id.key}`, as `default` does.\n - An action that silently acts as an admin. If an action doesn't take\n `as`, and instead assumes some fixed identity internally, a test\n writer has no way to know (or override) who actually performed the\n operation, and the action can't be reused for a test that needs a\n viewer to attempt (and be denied) the same thing.\n - Hardcoded ids. An action or seed that references a specific workspace\n or project id baked into the script defeats the whole point of\n chaining `${id.key}` \u2014 every id a later step needs should come from an\n earlier step's declared output.\n - An action that depends on seed ordering it doesn't declare. A script\n that assumes some other seed already ran (e.g. that a particular\n workspace exists) without that dependency showing up as an input is a\n hidden coupling \u2014 if the environment's seed order ever changes, the\n action breaks with no local sign of why. Take everything you need as\n an explicit input instead (as `create-project` takes `workspace_id`\n and `as`).\n - Writing output anywhere but the last `{`-starting stdout line. The\n action's result is exactly the *last* line of stdout that starts with\n `{`. Send every progress message, debugging `echo` and intermediate\n curl response to stderr \u2014 printing one to stdout by mistake silently\n corrupts the parsed output (or an earlier `{`-line gets picked up as a\n decoy if you print the wrong one last).\n\nWORKED EXAMPLE: CREATE THEN ARCHIVE A PROJECT\n\n Workspace \"Acme\" already exists (seeded by `default`'s `acme` step); the\n test supplies its own project rather than relying on a seeded one,\n because creating and archiving a project are the test's own subject.\n\n 1. Claim a sandbox on `default` and note `acme`'s output:\n cofactor sandbox claim --application \"Project Tracker\" --wait\n The claim's seed outputs include `acme.workspace_id`.\n\n 2. Create the project the test will operate on, acting as the member:\n cofactor sandbox run <id> create-project \\\n --input workspace_id=<acme.workspace_id> \\\n --input name=Temp \\\n --input as=member\n Returns `{\"project_id\": \"<id>\"}`.\n\n 3. Drive the app (via its `web`/`api` port URLs) to verify the project\n shows up wherever the product surfaces it, using the `project_id`\n from step 2 to find the right row.\n\n 4. Archive it the way the product's UI or API would \u2014 this is what the\n test is actually verifying, so it belongs in the test's own steps,\n not in a seed. Repeating it as `viewer` should be refused.\n\n 5. Destroy the sandbox:\n cofactor sandbox destroy <id>\n\n Nothing about the project this test created is shared with any other\n test, and nothing another seed does could break this test's setup.\n";
289
+ var seeds_and_actions_default = "An action is a script with a typed input and output schema. It is the one\nway data gets into a sandbox \u2014 seeds call it, and so do tests and\n`cofactor sandbox run`. Every environment's `seeds` list is\njust an ordered sequence of action (and emulator operation) calls. For\nthe script contract (`INPUT_<NAME>`, the result line), see the `upload`\ntopic's worked example; it is the same for GitHub-delivered apps.\n\nA SEED STEP THAT IS AN OPERATION\n\n A seed step runs exactly one of `action:` (a declared action, or an\n emulator operation named `<plugin>.<op>`) or `operation:` (a platform\n built-in). Today the only built-in is `database.fast-forward`, which\n moves a restored database's dates forward:\n - { id: fast_forward, operation: database.fast-forward, input: { database: app } }\n A step with both keys, or neither, fails `validate`. See the\n `data-fast-forward` topic.\n\nWHAT TO BUILD FIRST\n\n 1. One persona per role your tests need. The example app has three\n roles, so it declares `owner`, `member` and `viewer`. A persona in\n the definition is only a login (prepare makes it work \u2014 see\n `personas`); its role comes from a seed action such as `add-member`,\n or from prepare itself when the persona is an account your app\n already has with the right role. With one persona per role, tests can\n sign in as each and check what that role may and may not do.\n\n 2. One environment per plan or feature-flag combination your tests care\n about: `default`, `free-plan`, `beta-features`. Set each with a seed\n action that goes through your app's admin API or database (the\n example's `set-plan`), or with the environment's `env:` when your app\n reads the flag from an environment variable (the example's\n `FEATURE_FLAGS`; see the `env` topic for which processes see it).\n Params are per definition, not per environment, so they can't vary a\n flag between environments. See the `environments` topic.\n\n 3. Actions for the few core objects nearly every test needs, each taking\n `as: <persona>`: in the example, `create-workspace`, `add-member`,\n `create-project`, `create-task`. Keep the shared seeds thin; tests\n create the rest themselves with these actions.\n\n 4. If your app has a database, include a generic `load-sql` action as\n standard sandbox setup for visual-state fixtures. See `cofactor workflow\n guide visual-checkpoints` for the complete example. Each test owns\n its fixture; never create a seed or action for one screen's data.\n\n 5. If your app stores files, declare `object_storage` and seed the few\n files every test needs (a logo, an import template) with an action;\n see the `object-storage` topic.\n\nSEEDS, ACTIONS, AND VISUAL-STATE FIXTURES\n\n First ask: where must this test run? A test that loads SQL is sandbox-only,\n the usual choice for visual regression at an exact commit. If it must also\n run against staging or prod, seed only through the app's API, generic\n API-backed actions, or the test's own UI steps; only a sandbox exposes its\n database. Checkpoint regions independent of exact data, or rely on masking.\n\n Seeds are shared, thin, generic base data: the handful of things nearly\n every test needs before it can start \u2014 a workspace and its members, one\n per role. A shared seed stays thin and generic; it does not try to\n anticipate every test's needs.\n\n Actions are typed, parameterized, generic operations, API-first. A test\n about archiving a project calls `create-project` for its own project,\n then operates on the returned id; the operation is shared, its data is not.\n\n Visual-state fixtures are one SQL file per screen state, beside the test,\n never parameterized or shared. Use fixed, neutral values and timestamps\n relative to now() so relative-time text stays stable. Load them through\n the app's generic SQL action, only for rows with no encryption and no\n side effects. Per-screen data never becomes a named seed or action.\n Encrypted rows and side effects go through the app's API or generic\n actions that call it; interaction states use UI steps.\n\nWHAT MAKES A GOOD ACTION\n\n - Typed inputs and outputs. The input schema is what a test (or an\n agent) uses to call it correctly; the output schema is what the next\n step gets to chain from. Untyped free-form data defeats the ability to\n validate `${id.key}` references and CLI input ahead of time.\n - Fast. Seeds run once per version, while its snapshot is built; calls\n run inline in a test \u2014 slow seeds slow every new version's first\n claim, and slow calls slow every test.\n - Returns the ids a test needs next. `create-project` returns\n `project_id`, not just \"ok\" \u2014 that id is what the test's next action\n or assertion needs.\n - Safe to call more than once, or unique by its own input. Either the\n action is idempotent (`set-plan` re-run with the same plan changes\n nothing), or a distinct call naturally produces a distinct result\n (`create-project` with a new name makes a new project) rather than\n silently colliding with a previous call's data.\n - Explicit about who acts: the `as` persona pattern. Every action that\n performs a real business operation (creating a project, inviting a\n member) takes an explicit `as: <persona>` input naming which persona\n performs it, rather than assuming \"the admin\" or \"whoever is logged\n in.\" The script signs in as exactly that persona (its login is in\n `COFACTOR_PERSONAS`) and acts in the workspace it was given; the\n persona must already have a role there that allows the operation. So\n an action has **no hidden dependency** on some other seed having run\n first to establish who's \"in charge\" \u2014 the caller says so, in the\n input.\n - Goes through the app's own API when the data is encrypted or has side\n effects. `invite-user` calls the real invite endpoint so the\n invitation email is actually sent (and can be observed later with\n `email.sent-emails`); a raw insert would silently skip the send. The\n same goes for anything the app encrypts or hashes on write.\n - For business operations, uses the database only when there is no API,\n and only for rows with no encryption and no side effects (e.g. `set-plan`).\n The generic SQL-loading action is the sanctioned DB-direct action for\n visual fixtures, even when an API exists; the same row restrictions apply.\n\nANTI-PATTERNS\n\n - A giant do-everything seed. One seed step that creates a workspace,\n three members, two projects and their tasks in one script is harder to\n reuse, harder to debug when it fails partway, and forces every test\n that wants only part of it to take all of it. Break it into separate\n actions and chain them with `${id.key}`, as `default` does.\n - An action that silently acts as an admin. If an action doesn't take\n `as`, and instead assumes some fixed identity internally, a test\n writer has no way to know (or override) who actually performed the\n operation, and the action can't be reused for a test that needs a\n viewer to attempt (and be denied) the same thing.\n - Hardcoded ids. An action or seed that references a specific workspace\n or project id baked into the script defeats the whole point of\n chaining `${id.key}` \u2014 every id a later step needs should come from an\n earlier step's declared output.\n - An action that depends on seed ordering it doesn't declare. A script\n that assumes some other seed already ran (e.g. that a particular\n workspace exists) without that dependency showing up as an input is a\n hidden coupling \u2014 if the environment's seed order ever changes, the\n action breaks with no local sign of why. Take everything you need as\n an explicit input instead (as `create-project` takes `workspace_id`\n and `as`).\n - Writing output anywhere but the last `{`-starting stdout line. The\n action's result is exactly the *last* line of stdout that starts with\n `{`. Send every progress message, debugging `echo` and intermediate\n curl response to stderr \u2014 printing one to stdout by mistake silently\n corrupts the parsed output (or an earlier `{`-line gets picked up as a\n decoy if you print the wrong one last).\n\nWORKED EXAMPLE: CREATE THEN ARCHIVE A PROJECT\n\n Workspace \"Acme\" already exists (seeded by `default`'s `acme` step); the\n test supplies its own project rather than relying on a seeded one,\n because creating and archiving a project are the test's own subject.\n\n 1. Claim a sandbox on `default` and note `acme`'s output:\n cofactor sandbox claim --application \"Project Tracker\" --wait\n The claim's seed outputs include `acme.workspace_id`.\n\n 2. Create the project the test will operate on, acting as the member:\n cofactor sandbox run <id> create-project \\\n --input workspace_id=<acme.workspace_id> \\\n --input name=Temp \\\n --input as=member\n Returns `{\"project_id\": \"<id>\"}`.\n\n 3. Drive the app (via its `web`/`api` port URLs) to verify the project\n shows up wherever the product surfaces it, using the `project_id`\n from step 2 to find the right row.\n\n 4. Archive it the way the product's UI or API would \u2014 this is what the\n test is actually verifying, so it belongs in the test's own steps,\n not in a seed. Repeating it as `viewer` should be refused.\n\n 5. Destroy the sandbox:\n cofactor sandbox destroy <id>\n\n Nothing about the project this test created is shared with any other\n test, and nothing another seed does could break this test's setup.\n";
290
290
 
291
291
  // ../content/src/sandbox-v2-guide/data-fast-forward.txt
292
292
  var data_fast_forward_default = "A database dump keeps the dates of the day it was taken. Restore it into a\nsandbox months later and every trial, token and scheduled item in it has\nalready expired, so the app behaves like a dead system. The built-in\noperation `database.fast-forward` moves a restored Postgres database's\ndates forward to the moment the snapshot was built, so the data looks\nfreshly captured. It changes data only; the sandbox's clock is already\ncorrect (see \"WHAT IS NOT FAKED\" below).\n\nDECLARE THE DATABASE\n\n A top-level `databases` block names each database and the date its dump\n was captured. Like every top-level field, it works in GitHub-delivered\n and pushed definitions alike.\n databases:\n app:\n engine: postgres\n url: ${env.DATABASE_URL}\n as_of: 2026-09-30T00:00:00Z\n\n `engine` `postgres` is the only engine today.\n `url` Exactly one `${env.NAME}` reference and nothing else around\n it (no `postgres://${env.HOST}/db`). It is resolved inside the\n sandbox, against the env an action sees: the definition's\n `env` (with `${params.*}` filled in), the environment's `env`,\n the emulators' env and repository secrets. So a `DATABASE_URL` in `env`, or a\n secret of that name, works, and no credential is committed.\n `as_of` When the dump was captured, in UTC with a trailing `Z`. A date\n without a time, or a local offset, is rejected by `validate`.\n\nADD THE STEP AFTER THE RESTORE\n\n A seed step runs the operation with `operation:` instead of `action:`\n (see `seeds-and-actions`). It must come after the step that restores\n the dump:\n environments:\n default:\n description: A customer database restored from a 2026-09-30 dump,\n then moved forward to the snapshot's build time.\n seeds:\n - { id: restore, action: restore-dump }\n - { id: fast_forward, operation: database.fast-forward, input: { database: app } }\n\n `input.database` is a key of `databases`; `validate` reports an unknown\n one with its path. A restore that recreates the database also clears\n the record of earlier fast-forwards (below), which is right: the data is\n old again. A restore that only replaces tables must also run\n `drop schema if exists cofactor cascade`, or a later fast-forward\n starts from the old record and moves nothing (the worked example's\n dump does this).\n\nWHEN IT RUNS IS WHERE YOU PUT IT\n\n There is no mode flag. Placement is the \"when\":\n - As a seed step it runs once, while the version's snapshot is built,\n so every claim of that snapshot starts with shifted data.\n - On a live sandbox, run the same operation by hand:\n cofactor sandbox run <id> database.fast-forward --input database=app\n It shifts from the date the database recorded last time (below), so\n a sandbox kept for days can be brought current again.\n\nTHE SHIFT RULE\n\n - `as_of` is the anchor. The data moves by the whole days between its\n current date and now, rounded down, so it ends at most a day behind\n now and never in the future. Times of day are kept: a row at 14:30\n still reads 14:30.\n - The first run starts from `as_of`. When it finishes it records the\n data's new date in the database itself, in the table\n `cofactor.fast_forward` (its own schema, so your migration tool does\n not see it). Later runs start from that date instead of `as_of`, and\n the record travels with the data into every snapshot and copy.\n - If less than a day has passed the run changes no table (`offset_days:\n 0`). An `as_of` in the future fails.\n\nWHAT IS SHIFTED\n\n Every `timestamp`, `timestamptz` and `date` column of every base table\n in every schema except the system ones and `cofactor`. `infinity`\n values stay `infinity`.\n - A generated column is recomputed from its shifted inputs.\n - A partitioned table is updated through its parent, so rows move\n between partitions as their dates move.\n - User triggers are disabled while a table is shifted, so a trigger\n such as `updated_at = now()` leaves `updated_at` with its shifted\n value. Afterwards every user trigger on a touched table is enabled\n again, including one your dump had deliberately disabled.\n\nWHAT IS NOT SHIFTED\n\n Integer epoch columns, dates inside JSON, dates stored as text, columns\n of a domain over a date type, array columns, and materialized views.\n Refresh materialized views in a step after the fast-forward:\n - { id: refresh, action: refresh-views }\n\nREQUIREMENTS AND FAILURES\n\n - The role in `url` must own the tables (it is normally the role that\n restored the dump). A role that does not fails with Postgres's own\n \"must be owner of table\" error.\n - The whole shift is one transaction. Any error rolls everything back,\n so nothing is half-shifted, and the error names the table (and the\n constraint, when there is one):\n table public.sessions, constraint sessions_day_key: duplicate key ...\n - A unique constraint over a date column can collide while its rows are\n moving; that fails the run. A foreign key whose columns are shifted\n dates is checked as each table moves and can fail the run with\n Postgres's own error.\n - On a live sandbox the shift locks every table that has a date column\n until it commits. Run it when the app is idle.\n - After a large dump, follow the step with an action that runs\n `VACUUM (ANALYZE)`: the update leaves dead rows and stale statistics\n behind.\n\nWHAT IS NOT FAKED\n\n The sandbox's system clock is not changed, and does not need to be: it\n is correct after every claim. Only data moves. So state minted in memory\n while the snapshot was built (sessions, tokens an app created during a\n seed) is as old as the snapshot. A claim's data is as old as its\n snapshot plus under a day of rounding, and a snapshot is rebuilt on\n every push, so keep pushing (a nightly push is plenty). A preview moved\n in place to a new commit keeps its data and is not shifted again.\n\nOUTPUT\n\n The step (and a `sandbox run`) returns:\n database the key in `databases`\n offset_days whole days the data moved this run (0: nothing to do)\n tables, rows how many tables and rows were updated\n from, to the data's date before and after (ISO-8601, UTC)\n A later step can use them, e.g. `${fast_forward.offset_days}`.\n `cofactor sandbox show` lists each seed step with its time and prints\n these under the seed outputs.\n\nWORKED EXAMPLE\n\n `packages/demo-app-postgres/cofactor.sandbox.yaml` in TreebeardHQ/qa is\n a complete Postgres app: a dump from 2026-09-30, a restore action, the\n `databases` block and the two-step seed above, plus an `aged`\n environment that rewinds the data and fast-forwards it a second time.\n";
@@ -313,16 +313,16 @@ var object_storage_default = "An app that stores files (uploads, attachments, ex
313
313
  var ports_default = "A sandbox can declare several ports, and each one gets its own public URL:\n ports:\n web: { port: 3000, access: public }\n api: { port: 3001, access: public }\n\nWHY SEVERAL PORTS\n\n Bundling web and API into one sandbox means a single claimed environment\n serves both UI flows (driving the browser against `web`'s URL) and API\n sequences (calling `api`'s URL directly) against the *same* underlying\n data \u2014 the workspace, projects and tasks a UI test just created through\n the browser are the same rows an API test can immediately query.\n There's no separate \"API sandbox\" to keep in sync with the \"UI sandbox.\"\n\n`access`\n\n `public` gets a real, routable URL a test or a human can hit from\n outside the sandbox. `private` stays internal \u2014 reachable by other\n services in the sandbox, not from outside it.\n\n`{port}` PARAMS\n\n A `params` entry of the form `{port: <name>}` resolves to that port's own\n public URL, so the app (and its build/prepare steps) can be handed its\n own public origin rather than hardcoding it:\n params:\n web_url: { port: web }\n api_url: { port: api }\n env:\n PUBLIC_URL: ${params.web_url}\n API_URL: ${params.api_url}\n\n Each `{port}` param gives the app its own public URL for exactly the port\n it names \u2014 `web_url` always resolves to `web`'s URL, `api_url` to `api`'s,\n and so on for however many ports a definition declares.\n\n `{ port: emulation }` is also allowed when the definition declares an\n emulator under `emulation:`. It resolves to the emulator's public origin\n (the implicit `emulation` port), which is the issuer for the `oidc`\n plugin:\n params:\n issuer_url: { port: emulation }\n Without a declared emulator, `emulation` is an undeclared port and\n validation fails.\n\nLIMITS\n\n At most 8 ports total per sandbox \u2014 a platform limit. That count\n includes the implicit `emulation` port that gets added automatically the\n moment any plugin is declared under `emulation:` (see the `emulation`\n topic) \u2014 it isn't optional and isn't something you declare yourself, but\n it does count against the 8. `emulation` is also a reserved port name: you\n can't declare your own port called `emulation`. The same goes for the\n implicit `object-storage` port that `object_storage` adds (see the\n `object-storage` topic), and the implicit public `sync` port every\n sandbox has, which `cofactor sandbox sync` writes your working tree\n through (see the `local` topic): a definition can declare at most 7 ports\n of its own (fewer with `emulation` or `object_storage`), and `sync` and\n port 7099 are reserved. `cofactor sandbox\n validate` checks the reservations and the 8-port cap. The `sync` port is\n never shown as one of the sandbox's URLs.\n\n Ports 80 and 443 are reserved for the emulator whenever emulation is on \u2014\n a definition can't put its own `web` or `api` port there in that case.\n\nOAUTH CALLBACKS\n\n Map the callback through the application's existing configuration, e.g.\n GOOGLE_REDIRECT_URI: ${params.api_url}/auth/callback. Use the port that\n serves the actual callback, not necessarily the web port. Services that\n read this environment variable at startup need reload_on_prepare: true.\n Read `cofactor sandbox guide v2 authentication` before using live: true or\n configuring Google emulation.\n\n https://docs.runcofactor.com/reference/sandbox-emulation#callback-configuration-and-app-changes\n";
314
314
 
315
315
  // ../content/src/sandbox-v2-guide/verify.txt
316
- var verify_default = "Two different checks, at two different costs.\n\n`cofactor sandbox validate [file]`\n\n Offline \u2014 no sandbox, no claim. Parses the YAML, runs the full\n schema, and cross-checks everything static analysis can catch: unknown\n actions or operations referenced by a seed, `${id.key}` references to a\n later step or an undeclared output key, an `extends` cycle, a duplicate\n step id across a chain, an unknown emulation plugin, port 80/443 declared\n while emulation is on, and more. Every issue is reported with a dotted\n path to exactly where it is, so a broken definition doesn't need a claim\n to find the problem:\n environments.default.seeds[0].input.workspace_id: references step \"acme\", which has not run yet (or does not exist)\n environments.free-plan.extends: unknown environment \"defualt\"\n ports.emulation: \"emulation\" is a reserved port name\n\n Run this before pushing any change to `cofactor.sandbox.yaml` \u2014 it's\n instant and catches most authoring mistakes before they cost a real\n claim.\n\n`cofactor sandbox verify-env --application A [--environment E]`\n\n Actually claims each requested environment of the application's current\n version (or every environment in the definition, if none is named) in turn:\n claim, wait for ready, report every seed step's output and timing, sign\n every persona in as a test's Login step would (when the definition\n declares `authentication`), then destroy. This is the only way to prove an\n environment's seed chain and persona logins actually run end to end \u2014\n `validate` can tell you a reference is well-formed, but only a real claim\n proves the referenced action produces the output at runtime, and that\n `authentication.run` works in the image (a command the image lacks, a\n wrong URL). Each persona prints `signed in`, or FAILED with the script's\n exit code and the end of its stderr; any failure fails the environment.\n\nWHEN A SEED FAILS\n\n Seeds run once per version (commit or push) and environment, while its\n ready snapshot is built. Any step's failure fails that snapshot \u2014 the\n builder is destroyed, so there's no partially-seeded sandbox left around\n to poke at \u2014 and the claims waiting on it fail with that error. The next\n claim builds the snapshot again, so a seed that fails every time keeps\n failing until a fix is committed or pushed. An infrastructure failure,\n by contrast, is retried by the build, within the claim's time limit\n (`cofactor sandbox claim --timeout`, 2 hours by default).\n\n To debug a failing seed:\n 1. Read the error \u2014 the claim (or `verify-env`) reports which step\n failed, its action, and the script's stderr (everything a script\n prints besides its final `{...}` result line goes to stderr, so\n this is where the real error message is).\n 2. Claim a sandbox **without** the failing environment (or on an\n environment that stops short of the failing step) so you have a\n live sandbox to work on.\n 3. Run the failing action by hand against that live sandbox with\n `cofactor sandbox run <id> <action> --input k=v ...`, using the\n same inputs the seed step would have resolved (substitute in the\n actual values for any `${id.key}` references by hand). This lets\n you iterate on the action's script without repeatedly re-claiming.\n 4. Once the action succeeds standalone, fix the seed definition or the\n action script and rerun `verify-env` to confirm the full chain\n works.\n";
316
+ var verify_default = "Two different checks, at two different costs.\n\n`cofactor sandbox validate [file]`\n\n Offline \u2014 no sandbox, no claim. Parses the YAML, runs the full\n schema, and cross-checks everything static analysis can catch: unknown\n actions or operations referenced by a seed, `${id.key}` references to a\n later step or an undeclared output key, an `extends` cycle, a duplicate\n step id across a chain, an unknown emulation plugin, port 80/443 declared\n while emulation is on, and more. Every issue is reported with a dotted\n path to exactly where it is, so a broken definition doesn't need a claim\n to find the problem:\n environments.default.seeds[0].input.workspace_id: references step \"acme\", which has not run yet (or does not exist)\n environments.free-plan.extends: unknown environment \"defualt\"\n ports.emulation: \"emulation\" is a reserved port name\n\n Run this before pushing any change to `cofactor.sandbox.yaml` \u2014 it's\n instant and catches most authoring mistakes before they cost a real\n claim. It is the only local check; only a claim proves the definition.\n\n`cofactor sandbox verify-env --application A [--environment E]`\n\n Actually claims each requested environment of the application's current\n version (or every environment in the definition, if none is named) in turn:\n claim, wait for ready, report every seed step's output and timing, sign\n every persona in as a test's Login step would (when the definition\n declares `authentication`), then destroy. This is the only way to prove an\n environment's seed chain and persona logins actually run end to end \u2014\n `validate` can tell you a reference is well-formed, but only a real claim\n proves the referenced action produces the output at runtime, and that\n `authentication.run` works in the image (a command the image lacks, a\n wrong URL). Each persona prints `signed in`, or FAILED with the script's\n exit code and the end of its stderr; any failure fails the environment.\n Without a pinned version, a GitHub application uses the default\n environment's head (or the default branch's tip when that head is stale).\n A default branch without a definition is not ready for unpinned checks.\n\n[cli >= 0.56.0]\n`cofactor sandbox verify-env --application A --at <sha> [--environment E]`\n\n Reads and verifies that pushed commit's definition before any merge.\n Use either a branch SHA or the full source SHA printed by\n `claim --from-local`. The latter creates its source object without\n changing the user's HEAD or index. After the last edit, validate and\n make a fresh claim from local files or the final branch SHA, then run\n this check. A previously synced copy alone is not evidence that the\n source snapshot builds from scratch.\n\n Check the UI at the claimed copy's web URL and run a workflow with\n `cofactor workflow run <flow> --sandbox <id>` before merge. For one\n existing copy, `cofactor sandbox auth-check <id> --persona P` checks a\n native persona without a workflow. Merge only when the user wants\n default-branch adoption; unpinned refresh and verify-env check that\n adoption afterwards. See `cli`, SETTING UP A NEW GITHUB APPLICATION.\n[/cli]\n\nWHEN A SEED FAILS\n\n Seeds run once per version (commit or push) and environment, while its\n ready snapshot is built. Any step's failure fails that snapshot \u2014 the\n builder is destroyed, so there's no partially-seeded sandbox left around\n to poke at \u2014 and the claims waiting on it fail with that error. The next\n claim builds the snapshot again, so a seed that fails every time keeps\n failing until a fixed source version is supplied. An infrastructure failure,\n by contrast, is retried by the build, within the claim's time limit\n (`cofactor sandbox claim --timeout`, 2 hours by default).\n\n To debug a failing seed:\n 1. Read the error \u2014 the claim (or `verify-env`) reports which step\n failed, its action, and the script's stderr (everything a script\n prints besides its final `{...}` result line goes to stderr, so\n this is where the real error message is).\n 2. Claim a sandbox **without** the failing environment (or on an\n environment that stops short of the failing step) so you have a\n live sandbox to work on.\n 3. Run the failing action by hand against that live sandbox with\n `cofactor sandbox run <id> <action> --input k=v ...`, using the\n same inputs the seed step would have resolved (substitute in the\n actual values for any `${id.key}` references by hand). This lets\n you iterate on the action's script without repeatedly re-claiming.\n 4. Once the action succeeds standalone, fix the seed definition or the\n action script and confirm the full chain on a fresh source version.\n For a pushed image, push the fixed image and rerun verify-env.\n[cli >= 0.56.0]\n For GitHub, validate, claim --from-local again, and verify-env --at\n its new source SHA, or use a fixed pushed branch SHA. Neither path\n requires a merge or a user commit during local iteration.\n[/cli]\n";
317
317
 
318
318
  // ../content/src/sandbox-v2-guide/cli.txt
319
- var cli_default = "All commands are under `cofactor sandbox`, and every sandbox belongs to an\napplication (`--application <name|id>`). `--env` is the CLI's *global*\nflag for which Cofactor stage to talk to (`dev` / `prod`) \u2014 it has nothing\nto do with a sandbox's environment, which is always `--environment`.\n\ncofactor sandbox validate [file] [--upload]\n Offline schema + cross-check of a `cofactor.sandbox.yaml`. Defaults to\n the definition in the current directory if `file` is omitted. `--upload`\n checks a definition for pushed images (no `setup`);\n without it, one built from a GitHub checkout. See the `verify` topic for\n the issue format.\n\ncofactor sandbox connect --application A --repo owner/name [--dir D]\n Builds the application's sandboxes from a GitHub repository: the\n `cofactor.sandbox.yaml` under `--dir` (default: the repo root) at each\n commit. Its environments become the application's sandbox environments\n and its personas the application's personas. A new application name\n creates the application, with its sandbox `default` environment as its\n default environment; there is no base URL to give. It is created only\n after GitHub is connected (`cofactor github connect`), the repository is\n reachable and its definition is valid, so a refusal creates nothing.\n\ncofactor sandbox push --application A <image> [--definition F] [--tag T]... [--wait]\n For an application that gives Cofactor no repository access: pushes a\n built image (with its `cofactor.sandbox.yaml`) as the application's next\n version. A new application name creates the application, as `connect`\n does. See the `upload` topic.\n\ncofactor sandbox envs --application A [--json]\n The application's delivery (GitHub or pushed images), its current\n version, each environment's head (see `refresh`), recent pushes, sandbox environments, and what its definition\n offers: every environment (descriptions, seeds), ports, personas,\n actions and emulator operations (with their schemas). This is what an\n agent reads to decide which environment to claim and how to call each\n action once it has a sandbox.\n\ncofactor sandbox refresh --application A [--environment E] [--wait]\n Moves an environment's head to now. The head is what flow runs, the flow\n designer, scheduled runs and unpinned claims use: for GitHub, the ready\n snapshot of the default branch; for pushes, the newest push. It is never\n built per commit and never follows the default branch on its own, so it\n is normally behind it. It moves in two ways only: the background refresh\n (every 6 hours, for environments in use) and this command, which builds\n the default branch's tip as of this moment and advances the head when its\n snapshot is ready. A pushed application's head is already its newest push\n (\"already at head <push id>\"). Prints the old head, the new head and its\n build status; `--wait` blocks until the snapshot is ready or failed. A\n refresh while that snapshot is already building joins the build. Whoever\n can claim can refresh. `envs` shows each head and whether the default\n branch has moved past it, for information only.\n\ncofactor sandbox claim --application A [--environment E] [--at V] [--wait]\n Claims a sandbox: a fresh copy of the ready snapshot for the version and\n environment (built first \u2014 build, `prepare`, seeds \u2014 if it doesn't exist\n yet), given its own URLs and logins by `prepare`. A version is a commit\n SHA for a GitHub-delivered application, or a push id or tag for a pushed\n one. Without `--at`: the environment's head (see `refresh`); for GitHub,\n the default branch snapshot, else the default branch's tip if the head is\n stale; for pushes, the newest push. `--environment` defaults to `default`. `--wait`\n blocks until the sandbox is ready (or failed) instead of returning\n immediately with a claim id you'd otherwise have to poll with `show`.\n\ncofactor sandbox claims [--application A] [--json]\n Lists the sandboxes you claimed that are still claiming or ready: id,\n application, environment, status, when claimed and when the lease ends.\n Use it to find an id you lost, then `destroy` it.\n\ncofactor sandbox show <id>\n Current status, per-port URLs, persona logins (decrypted \u2014 this is where\n they're meant to be read from, never a PR comment), the snapshot's seed\n steps with the time each took, seed outputs, and the available actions\n and operations.\n\ncofactor sandbox run <id> <action|plugin.op> [--input k=v ...] [--json-input '{...}']\n Runs a definition action, an emulator operation (named `<plugin>.<op>`,\n e.g. `email.sent-emails`) or, when the definition declares `databases`,\n the built-in `database.fast-forward` (`--input database=<name>`; see the\n `data-fast-forward` topic) against a claimed sandbox, waits for it, and\n prints its output. `--input` is repeatable and coerced to the action/\n operation's declared type (string/number/integer/boolean/persona);\n `--json-input` passes the whole input as JSON, overriding `--input`.\n Invalid input prints the action or operation's input schema instead of\n running it, so you can see what's expected.\n\ncofactor sandbox exec <id> [--timeout S] [--json] -- <command...>\n Runs one shell command in a claimed sandbox, waits for it, and prints its\n output. One-shot: there is no interactive shell. It runs as the sandbox\n user in the definition's directory, with the environment actions get\n (the application's secrets included). Everything after `--` is the\n command, its words joined with single spaces (ssh semantics), so quote\n pipes and redirects yourself: `-- 'ls | wc -l'`.\n - Output is the last 4000 characters of stdout and of stderr, scrubbed of\n the sandbox's secrets and persona logins (best effort: an encoded value\n isn't caught). The CLI exits with the command's exit code (124 on a\n timeout). A failure that isn't the command's (the helper, or a sandbox\n that can't exec) prints `Error: ...` and exits 1. `--json` prints the\n whole run instead.\n - `--timeout S` is 1 to 1800 seconds (default 300). On a timeout the\n command's whole process group is killed. Background processes (`cmd &`)\n keep running after a normal exit; redirect their output\n (`cmd > log 2>&1 &`), because the exec's pipes close when it returns.\n If the CLI stops waiting (its deadline is the timeout plus 60 s), the\n command may still be running in the sandbox.\n - Permissions: a signed-in CLI session (`cofactor login`) isn't\n scope-checked, so every org member can exec, just as they can run\n actions. An API key needs the `sandbox:exec` scope explicitly:\n `sandbox:*` does not include it, full access (`*`) does. Create a key\n with `sandbox:exec` selected under Sandboxes in Settings -> API keys (or\n `POST /settings/api-keys` with `\"scopes\": [..., \"sandbox:exec\"]`).\n Existing `sandbox:*` keys don't gain it, and a key's scopes can't be\n edited, so mint a new key.\n - Commands and their scrubbed output are visible in the sandbox's run\n history to anyone with `sandbox:read`. Don't type credentials into a\n command; set them as secrets (see the `secrets` topic).\n - Every exec is audited in the organization's events: who ran it (and\n with which API key), the sandbox, the scrubbed command, and how it\n ended (exit code, or a failure). Never its output. An exec that cannot\n be audited does not run.\n - Platform: works on sandboxes built on a platform that has exec. An older\n sandbox answers \"This sandbox was built on an older platform; exec works\n once it is rebuilt on a newer one.\" Exec ships with the next sandbox\n platform update (it needs no bump of its own).\n\ncofactor sandbox sync <id> [--build] [--json]\ncofactor sandbox sync <id> <local-dir> --to <path> [--json]\n Makes a sandbox you claimed match your working tree, uncommitted and\n untracked changes included, straight over the sandbox's own sync port.\n It lists every file it writes (untracked ones marked) and deletes, then\n applies, records the synced tree id (`show` prints it) and extends the\n sandbox's lease (up to 8 h after the claim). Nothing restarts on its own:\n `exec <id> -- cofactor-sandbox restart <service>`. `--build` reruns the\n definition's changed build steps and restarts `after_build` services\n (GitHub and pushed applications alike). `--to` mirrors local build output into <path>. Needs the same\n `sandbox:exec` scope as exec. See the `local` topic.\n\ncofactor workflow run <flow> --sandbox <id>\n Runs a flow on a sandbox you claimed instead of a fresh copy, and never\n destroys it. The run targets the sandbox's own application and environment.\n An `--environment` of another application or sandbox environment is refused.\n The run records the version and synced tree (`cofactor workflow debug overview <run>`). An API key also needs\n `sandbox:read`.\n\ncofactor sandbox destroy <id>\n Tears the sandbox down. Always do this when a claimed sandbox is no\n longer needed \u2014 it isn't reclaimed automatically mid-session.\n\ncofactor sandbox secret set <NAME> --application A [--environment E] [--json]\n Stores a third-party secret (an API key) for the application, for every\n environment, or for just `--environment E` (an override). The value is\n read from stdin only and the CLI refuses a terminal:\n `printf %s \"$KEY\" | cofactor sandbox secret set NAME --application A`.\n One trailing newline is stripped. A name is an environment variable name\n (capital letters, digits and `_`, not starting with a digit; not PATH,\n HOME, USER, SHELL, NODE_OPTIONS, NODE_EXTRA_CA_CERTS, LD_* or COFACTOR_*);\n a value is 8 to 8192 bytes, with no NUL. Fast copies of an existing\n ready snapshot keep its old value until a new commit or image push\n rebuilds the snapshot. Legacy-prepare claims see the change on the next\n claim. Prints `Secret NAME set for all environments of A.` (or\n `... for environment \"E\" of A.`). See the `secrets` topic.\n\ncofactor sandbox secret list --application A [--json]\n Names and scopes (all environments, or one) with the time last set.\n Never values. With none set it prints `No secrets set for A.`\n\ncofactor sandbox secret rm <NAME> --application A [--environment E] [--json]\n Removes the secret at that scope: without `--environment`, the\n application-wide one; with it, that environment's override. Prints\n `Secret NAME removed for all environments of A.` (or\n `... for environment \"E\" of A.`). A secret that is not set fails with\n \"No secret with that name and scope.\". Fast copies of an existing ready\n snapshot keep the removed value until a new commit or image push rebuilds\n the snapshot. Legacy-prepare claims see the removal on the next claim.\n See the `secrets` topic.\n\ncofactor sandbox verify-env --application A [--environment E]\n Claim \u2192 wait \u2192 report seed outputs and timings \u2192 sign every persona in\n (when the definition declares `authentication`) \u2192 destroy, for each\n environment of the current version (or the one named). The end-to-end\n proof that an environment's seed chain and persona logins actually work,\n not just that they're well-formed. See the `verify` topic.\n\ncofactor sandbox preview --application A --pr N\n A pull request's preview of a GitHub-delivered application: status, URLs,\n the logins GitHub deliberately never sees, and the URL of its Cofactor page\n (the same details plus build output on failure, for signed-in org members).\n The PR's GitHub deployment links that page as its \"View details\".\n To run a test on it (a private copy at its applied head, never the shared preview):\n cofactor workflow run <workflowId> --preview N --application A\n and to list tests in a pull request as proof: `cofactor proofs guide`.\n\ncofactor sandbox guide [topic]\n Prints this guide (`guide v2 [topic]` is the same). Omit `topic` to\n print every section; name one (e.g. `upload`) to print just that topic.\n `guide v1 [section]` prints the guide for the earlier sandboxes (its\n `verify` section is `guide v1 verify`; plain `guide verify` is this\n guide's).\n\nTHE CORE LOOP\n\n validate \u2192 (connect | push) \u2192 envs \u2192 claim --wait \u2192 run (actions/\n operations, as many as the test needs) \u2192 destroy. Always validate a\n changed definition before pushing it \u2014 see the `verify` topic for why.\n";
319
+ var cli_default = "All commands are under `cofactor sandbox`, and every sandbox belongs to an\napplication (`--application <name|id>`). `--env` is the CLI's *global*\nflag for which Cofactor stage to talk to (`dev` / `prod`) \u2014 it has nothing\nto do with a sandbox's environment, which is always `--environment`.\n\ncofactor sandbox validate [file] [--upload]\n Offline schema + cross-check of a `cofactor.sandbox.yaml`. Defaults to\n the definition in the current directory if `file` is omitted. `--upload`\n checks a definition for pushed images (no `setup`);\n without it, one built from a GitHub checkout. See the `verify` topic for\n the issue format.\n\ncofactor sandbox connect --application A --repo owner/name [--dir D]\n Builds the application's sandboxes from a GitHub repository: the\n `cofactor.sandbox.yaml` under `--dir` (default: the repo root) at each\n commit. Its environments become the application's sandbox environments\n and its personas the application's personas. A new application name\n creates the application, with its sandbox `default` environment as its\n default environment; there is no base URL to give. It is created only\n after GitHub is connected (`cofactor github connect`), the repository is\n reachable and its definition is valid, so a refusal creates nothing.\n Without a pinned commit, connect reads the definition at the default\n branch's head; that version must hold a valid cofactor.sandbox.yaml.\n The selected definition supplies environments and personas. Later\n definitions add more when connected or refreshed; none is ever removed.\n\n[cli >= 0.56.0]\ncofactor sandbox connect --application A --repo owner/name [--dir D] --at <sha>\n Reads the definition at that full pushed commit SHA, on any branch. No\n merge is needed, even if the default branch has no definition yet. The\n response describes the selected version; added environments and personas\n are retained if the branch is abandoned. An unknown commit says to push\n it first; a missing definition names the path and commit problem.\n[/cli]\n\ncofactor sandbox push --application A <image> [--definition F] [--tag T]... [--wait]\n For an application that gives Cofactor no repository access: pushes a\n built image (with its `cofactor.sandbox.yaml`) as the application's next\n version. A new application name creates the application, as `connect`\n does. See the `upload` topic.\n\ncofactor sandbox envs --application A [--json]\n The application's delivery (GitHub or pushed images), its current\n version, each environment's head (see `refresh`), recent pushes, sandbox environments, and what its definition\n offers: every environment (descriptions, seeds), ports, personas,\n actions and emulator operations (with their schemas). This is what an\n agent reads to decide which environment to claim and how to call each\n action once it has a sandbox.\n\ncofactor sandbox refresh --application A [--environment E] [--wait]\n Moves an environment's head to now. The head is what flow runs, the flow\n designer, scheduled runs and unpinned claims use: for GitHub, the ready\n snapshot of the default branch; for pushes, the newest push. It is never\n built per commit and never follows the default branch on its own, so it\n is normally behind it. It moves in two ways only: the background refresh\n (every 6 hours, for environments in use) and this command, which builds\n the default branch's tip as of this moment and advances the head when its\n snapshot is ready. A pushed application's head is already its newest push\n (\"already at head <push id>\"). Prints the old head, the new head and its\n build status; `--wait` blocks until the snapshot is ready or failed. A\n refresh while that snapshot is already building joins the build. Whoever\n can claim can refresh. `envs` shows each head and whether the default\n branch has moved past it, for information only.\n\ncofactor sandbox claim --application A [--environment E] [--at V] [--wait]\n Claims a sandbox: a fresh copy of the ready snapshot for the version and\n environment (built first \u2014 build, `prepare`, seeds \u2014 if it doesn't exist\n yet), given its own URLs and logins by `prepare`. A version is a commit\n SHA for a GitHub-delivered application, or a push id or tag for a pushed\n one. A commit may be on any branch you pushed (it need not be merged),\n and is built with the definition in that commit. Without `--at`: the\n environment's head (see `refresh`); for GitHub, the default branch\n snapshot, else the default branch's tip if the head is stale; for\n pushes, the newest push. `--environment` defaults to `default`. `--wait`\n blocks until the sandbox is ready (or failed) instead of returning\n immediately with a claim id you'd otherwise have to poll with `show`.\n\n[cli >= 0.56.0]\ncofactor sandbox claim --application A --from-local [--repo owner/name] [--dir D] [--environment E] [--wait]\n Sets up and claims the local files without a user commit or merge. The\n CLI snapshots the whole Git working tree (tracked files, tracked\n deletions, and nonignored untracked files), using a temporary index. It\n leaves your index, HEAD and branches alone. It pushes a deterministic\n commit object to refs/cofactor/wip/<tree-hash> with your own Git\n credentials, connects the application at that SHA, and claims it.\n --repo defaults to the origin push repository; --dir is relative to the\n repository root and defaults to the current directory. New application\n names work as with connect. --from-local and --at are mutually exclusive.\n The full source SHA is printed (the version field with --json); keep it\n for verify-env or a pinned suite. Unchanged files reuse the same SHA.\n\n Inclusion follows Git: an unignored untracked file is pushed too. Set\n ignore rules before claiming, and keep credentials out of tracked files.\n Push protection or a Git credential failure stops the claim and reports\n the push error. The CLI deletes its wip refs on destroy, or reaps them\n after seven days on a later local claim for that application. Another\n live claim keeps its ref. Cleanup uses your Git credentials; remote refs\n changed by someone else are left intact. Re-run --from-local to publish\n an expired ref again before rebuilding a lost snapshot. Deleting a ref\n does not erase GitHub's object storage immediately.\n[/cli]\n\ncofactor sandbox claims [--application A] [--json]\n Lists the sandboxes you claimed that are still claiming or ready: id,\n application, environment, status, when claimed and when the lease ends.\n Use it to find an id you lost, then `destroy` it.\n\ncofactor sandbox show <id>\n Current status, per-port URLs, persona logins (decrypted \u2014 this is where\n they're meant to be read from, never a PR comment), the snapshot's seed\n steps with the time each took, seed outputs, and the available actions\n and operations.\n\ncofactor sandbox run <id> <action|plugin.op> [--input k=v ...] [--json-input '{...}']\n Runs a definition action, an emulator operation (named `<plugin>.<op>`,\n e.g. `email.sent-emails`) or, when the definition declares `databases`,\n the built-in `database.fast-forward` (`--input database=<name>`; see the\n `data-fast-forward` topic) against a claimed sandbox, waits for it, and\n prints its output. `--input` is repeatable and coerced to the action/\n operation's declared type (string/number/integer/boolean/persona);\n `--json-input` passes the whole input as JSON, overriding `--input`.\n Invalid input prints the action or operation's input schema instead of\n running it, so you can see what's expected.\n\ncofactor sandbox exec <id> [--timeout S] [--json] -- <command...>\n Runs one shell command in a claimed sandbox, waits for it, and prints its\n output. One-shot: there is no interactive shell. It runs as the sandbox\n user in the definition's directory, with the environment actions get\n (the application's secrets included). Everything after `--` is the\n command, its words joined with single spaces (ssh semantics), so quote\n pipes and redirects yourself: `-- 'ls | wc -l'`.\n - Output is the last 4000 characters of stdout and of stderr, scrubbed of\n the sandbox's secrets and persona logins (best effort: an encoded value\n isn't caught). The CLI exits with the command's exit code (124 on a\n timeout). A failure that isn't the command's (the helper, or a sandbox\n that can't exec) prints `Error: ...` and exits 1. `--json` prints the\n whole run instead.\n - `--timeout S` is 1 to 1800 seconds (default 300). On a timeout the\n command's whole process group is killed. Background processes (`cmd &`)\n keep running after a normal exit; redirect their output\n (`cmd > log 2>&1 &`), because the exec's pipes close when it returns.\n If the CLI stops waiting (its deadline is the timeout plus 60 s), the\n command may still be running in the sandbox.\n - Permissions: a signed-in CLI session (`cofactor login`) isn't\n scope-checked, so every org member can exec, just as they can run\n actions. An API key needs the `sandbox:exec` scope explicitly:\n `sandbox:*` does not include it, full access (`*`) does. Create a key\n with `sandbox:exec` selected under Sandboxes in Settings -> API keys (or\n `POST /settings/api-keys` with `\"scopes\": [..., \"sandbox:exec\"]`).\n Existing `sandbox:*` keys don't gain it, and a key's scopes can't be\n edited, so mint a new key.\n - Commands and their scrubbed output are visible in the sandbox's run\n history to anyone with `sandbox:read`. Don't type credentials into a\n command; set them as secrets (see the `secrets` topic).\n - Every exec is audited in the organization's events: who ran it (and\n with which API key), the sandbox, the scrubbed command, and how it\n ended (exit code, or a failure). Never its output. An exec that cannot\n be audited does not run.\n - Platform: works on sandboxes built on a platform that has exec. An older\n sandbox answers \"This sandbox was built on an older platform; exec works\n once it is rebuilt on a newer one.\" Exec ships with the next sandbox\n platform update (it needs no bump of its own).\n\ncofactor sandbox sync <id> [--build] [--json]\ncofactor sandbox sync <id> <local-dir> --to <path> [--json]\n Makes a sandbox you claimed match your working tree, uncommitted and\n untracked changes included, straight over the sandbox's own sync port.\n It lists every file it writes (untracked ones marked) and deletes, then\n applies, records the synced tree id (`show` prints it) and extends the\n sandbox's lease (up to 8 h after the claim). Nothing restarts on its own:\n `exec <id> -- cofactor-sandbox restart <service>`. `--build` reruns the\n definition's changed build steps and restarts `after_build` services\n (GitHub and pushed applications alike). `--to` mirrors local build output into <path>. Needs the same\n `sandbox:exec` scope as exec. See the `local` topic.\n\ncofactor workflow run <flow> --sandbox <id>\n Runs a flow on a sandbox you claimed instead of a fresh copy, and never\n destroys it. The run targets the sandbox's own application and environment.\n An `--environment` of another application or sandbox environment is refused.\n The run records the version and synced tree (`cofactor workflow debug overview <run>`). An API key also needs\n `sandbox:read`.\n\ncofactor sandbox destroy <id>\n Tears the sandbox down. Always do this when a claimed sandbox is no\n longer needed \u2014 it isn't reclaimed automatically mid-session.\n\ncofactor sandbox secret set <NAME> --application A [--environment E] [--json]\n Stores a third-party secret (an API key) for the application, for every\n environment, or for just `--environment E` (an override). The value is\n read from stdin only and the CLI refuses a terminal:\n `printf %s \"$KEY\" | cofactor sandbox secret set NAME --application A`.\n One trailing newline is stripped. A name is an environment variable name\n (capital letters, digits and `_`, not starting with a digit; not PATH,\n HOME, USER, SHELL, NODE_OPTIONS, NODE_EXTRA_CA_CERTS, LD_* or COFACTOR_*);\n a value is 8 to 8192 bytes, with no NUL. Fast copies of an existing\n ready snapshot keep its old value until a new commit or image push\n rebuilds the snapshot. Legacy-prepare claims see the change on the next\n claim. Prints `Secret NAME set for all environments of A.` (or\n `... for environment \"E\" of A.`). See the `secrets` topic.\n\ncofactor sandbox secret list --application A [--json]\n Names and scopes (all environments, or one) with the time last set.\n Never values. With none set it prints `No secrets set for A.`\n\ncofactor sandbox secret rm <NAME> --application A [--environment E] [--json]\n Removes the secret at that scope: without `--environment`, the\n application-wide one; with it, that environment's override. Prints\n `Secret NAME removed for all environments of A.` (or\n `... for environment \"E\" of A.`). A secret that is not set fails with\n \"No secret with that name and scope.\". Fast copies of an existing ready\n snapshot keep the removed value until a new commit or image push rebuilds\n the snapshot. Legacy-prepare claims see the removal on the next claim.\n See the `secrets` topic.\n\ncofactor sandbox verify-env --application A [--environment E]\n Claim \u2192 wait \u2192 report seed outputs and timings \u2192 sign every persona in\n (when the definition declares `authentication`) \u2192 destroy, for each\n environment of the current version (or the one named). The end-to-end\n proof that an environment's seed chain and persona logins actually work,\n not just that they're well-formed. Without a pinned version it verifies\n the application's default version. See the `verify` topic.\n\n[cli >= 0.56.0]\ncofactor sandbox verify-env --application A --at <sha> [--environment E]\n Verifies that pushed commit's own definition, environments and personas\n before any merge. Use the source SHA from claim --from-local for the\n same check without making a user commit.\n\ncofactor sandbox auth-check <id> --persona <name> [--json]\n Signs one persona in on an existing claim as a Login step would, prints\n pass/fail, and exits nonzero if sign-in fails. It returns no session state.\n[/cli]\n\ncofactor sandbox preview --application A --pr N\n A pull request's preview of a GitHub-delivered application: status, URLs,\n the logins GitHub deliberately never sees, and the URL of its Cofactor page\n (the same details plus build output on failure, for signed-in org members).\n The PR's GitHub deployment links that page as its \"View details\".\n To run a test on it (a private copy at its applied head, never the shared preview):\n cofactor workflow run <workflowId> --preview N --application A\n and to list tests in a pull request as proof: `cofactor proofs guide`.\n\ncofactor sandbox guide [topic]\n Prints this guide (`guide v2 [topic]` is the same). Omit `topic` to\n print every section; name one (e.g. `upload`) to print just that topic.\n `guide v1 [section]` prints the guide for the earlier sandboxes (its\n `verify` section is `guide v1 verify`; plain `guide verify` is this\n guide's).\n\nTHE CORE LOOP\n\n validate \u2192 (connect | push) \u2192 envs \u2192 claim --wait \u2192 run (actions/\n operations, as many as the test needs) \u2192 destroy. Always validate a\n changed definition before pushing it \u2014 see the `verify` topic for why.\n A GitHub-delivered application is never tried in local Docker (see\n `overview`).\n\nSETTING UP A NEW GITHUB APPLICATION\n\n Never build or run the GitHub app in local Docker or a local container.\n Validate locally; build and prove it with a Cofactor claim. Use CLI\n 0.56.0 or later for the following no-merge setup commands; upgrade an\n older CLI instead of merging an unproved definition to unlock setup.\n\n[cli >= 0.56.0]\n 1. Write cofactor.sandbox.yaml and run `cofactor sandbox validate` until\n clean. Review Git's ignored and untracked files before sending them.\n Keep third-party credentials in the application's secret store, never\n in the definition or local files sent to Git.\n 2. Choose the source:\n - Already pushed on a branch: use its full commit SHA, with the\n definition and scripts in that commit:\n cofactor sandbox connect --application A --repo owner/name --at <branch-sha>\n cofactor sandbox claim --application A --at <branch-sha> --wait\n Add --dir D to connect for a monorepo definition directory.\n - Local files, including a new uncommitted definition:\n cofactor sandbox claim --application A --from-local --wait\n This also connects the application. No user commit, branch push or\n merge is needed. Use --dir D from the repository root for a monorepo;\n --repo owner/name overrides origin's inferred GitHub name.\n For an existing application, set its required test secrets before\n the first claim (see `secrets`). If a new application is created and\n its first claim reports a missing secret, set that secret and retry\n with the same application name. Connect retains added environment\n and persona names; abandoning the branch does not prune them.\n 3. Iterate on that copy:\n cofactor sandbox sync <id> --build\n This adopts changed lifecycle.build steps, services and env, and\n restarts the affected services (see `local`). Existing action script\n files and authentication.run scripts also apply after a plain sync.\n Data and applied migrations stay. Changes to setup, resources,\n ports, prepare, seeds, personas, actions or other nonsyncable\n definition fields need a fresh snapshot: validate, then claim again\n with --from-local, or use a newly pushed branch SHA. A failed claim\n has no running copy to sync; fix the files and claim again by the\n same path. Use --from-local for retries without a user commit or merge.\n 4. Prove a fresh source snapshot, rather than just a synced copy. After\n the last edit, validate and claim --from-local again (or claim the\n final pushed SHA). Record its full source SHA, then:\n cofactor sandbox verify-env --application A --at <source-sha>\n This checks each environment and signs every declared native persona\n in. Open the claimed copy's web URL too and confirm the app renders\n and each role lands where it should. For a focused sign-in check:\n cofactor sandbox auth-check <id> --persona <name>\n A workflow can run before merge:\n cofactor workflow run <flow> --sandbox <id>\n A suite can pin the same SHA with `cofactor test-suite run --at`.\n Destroy your claims when finished. Merge only when the user wants\n the proven definition and code adopted on the default branch. At\n that point commit and push the files for review, and prove that final\n SHA if it differs; after merge, refresh --wait and unpinned verify-env\n check default-branch adoption. They are not prerequisites to setup.\n[/cli]\n";
320
320
 
321
321
  // ../content/src/sandbox-v2-guide/upload.txt
322
322
  var upload_default = 'PUSHED IMAGES: SANDBOXES WITHOUT REPOSITORY ACCESS\n\nAn application whose team gives Cofactor no access to its repository\ndelivers its sandboxes as built images: build your app\'s image as usual,\nthen push it with its `cofactor.sandbox.yaml` whenever you have a new\nbuild (nightly, per release, per merge \u2014 your cadence, your CI or your\nlaptop). Each push is a version of the application. A tag names the newest\nready push that carries it: pushing `--tag nightly` again moves the tag to\nthe new push, and older pushes stay claimable by their id.\n\nTag your main builds `main` (or `nightly`). These two tags are reserved: once\nthe application has a push carrying either, its head (what the flow designer,\nscheduled runs and claims without `--at` use) is the newest push tagged `main`\nor `nightly`, and only those runs move the visual-review baseline. Pull\nrequest and branch pushes are compared against them and change nothing. Until\nthen the newest push is the head. Claim any other push with\n`--at <push id or tag>`.\n\nUse tags that move (`nightly`, `release`, a branch name), not one per push\nsuch as a date. Cofactor keeps the layers of each application\'s newest push,\nits head, and its 10 newest tagged pushes. Older pushes stay claimable, but when the\nsandbox platform is updated only kept pushes move to it on their own; an\nolder one says to push again.\n\n docker buildx build --platform linux/amd64 -t tracker:sandbox --load .\n cofactor sandbox validate --upload cofactor.sandbox.yaml\n cofactor sandbox push --application "Project Tracker" tracker:sandbox \\\n --definition cofactor.sandbox.yaml --tag nightly --wait\n cofactor sandbox envs --application "Project Tracker"\n cofactor sandbox claim --application "Project Tracker" --environment default --wait\n\n`push` adds Cofactor\'s layers to your image (its sandbox helper as the\nentrypoint, plus the object storage server when the definition declares\n`object_storage`) and a small layer with your definition, reads the image\nout of your local Docker (`docker save`), and uploads only the layers\nCofactor doesn\'t already have for your organization (see FAST RE-PUSHES).\nCofactor boots it once until every service is healthy (`--wait` waits for\nthat). Then each environment in the definition\nbecomes a sandbox environment of the application, and each persona one of\nits personas. The first claim of an environment builds its ready snapshot\n(prepare + seeds, about a minute); later claims copy it in seconds.\n\nWHAT THE IMAGE MUST BE\n\n - linux/amd64. On an Apple Silicon Mac, build with\n `--platform linux/amd64`; `push` refuses any other platform.\n - glibc-based with bash (Debian, Ubuntu, `python:*-slim`, `node:*`\n non-alpine\u2026). Alpine/musl and distroless images do not work: the\n helper is a glibc binary and every command runs through `bash -lc`.\n If only your final stage is Alpine, keep the build stages and change\n that one `FROM` to its Debian equivalent (`node:24-alpine` \u2192\n `node:24-bookworm-slim`, `python:3.12-alpine` \u2192 `python:3.12-slim`),\n with `apt-get install` in place of `apk add`. Native dependencies\n compiled on Alpine don\'t run on glibc: install them in a glibc stage\n rather than copying `node_modules` or wheels out of an Alpine one.\n - A WORKDIR other than `/`. Services, prepare commands and actions run\n from it, and the helper gives it to the sandbox user (below).\n - Built already. Nothing builds at push or claim: everything the app\n needs (dependencies, compiled assets, a database server if it uses one)\n is in the image. Optional `lifecycle.build` steps run only on\n `sync --build` (the `local` topic), which needs `git` installed in the\n image: without it the build fails with `Executable not found in $PATH: "git"`.\n\nFAST RE-PUSHES\n\n A push uploads only the layers that changed since an earlier push (in\n the last 30 days); `push` says how much it sent and how much Cofactor\n already had. These keep a re-push small:\n\n - Order the Dockerfile so what changes most comes last: system packages,\n then the dependency manifest and lockfile and the install step, then\n your source. A code change then rebuilds, and uploads, only the last\n layers:\n FROM python:3.12-slim\n RUN apt-get update && apt-get install -y --no-install-recommends curl jq\n WORKDIR /app\n COPY requirements.txt .\n RUN pip install -r requirements.txt\n COPY . .\n - In CI, keep the build cache between runs. A runner that starts with an\n empty cache rebuilds every step, and every rebuilt layer is new to\n Cofactor even when nothing in it changed. With GitHub Actions, build\n with Docker\'s official actions, which pass the Actions cache to\n buildx for you:\n - uses: docker/setup-buildx-action@v4\n - uses: docker/build-push-action@v7\n with:\n context: .\n platforms: linux/amd64\n load: true\n tags: app:sandbox\n cache-from: type=gha\n cache-to: type=gha,mode=max\n (or `type=registry` with a cache image of your own). Docker\'s own\n actions are pinned by major version here, like `actions/checkout`;\n pin them to a commit if your policy requires it. A raw\n `docker buildx build ... --cache-from type=gha` in a `run:` step\n can\'t see the Actions cache token and skips the cache without a\n warning. If you must use one, add the community action\n `crazy-max/ghaction-github-runtime` before it, pinned to a full commit\n (`@04d248b84655b509d8c44dc1d6f990c879747487`, v4.0.0): it exposes the\n Actions runtime token to every later step of the job.\n - For compiled stacks (.NET, Java, Rust), build outside Docker and keep\n the Dockerfile thin. `RUN --mount=type=cache` (NuGet, Maven, cargo,\n incremental `obj/` or `target/` directories) is not exported by\n `--cache-to type=gha`, so inside a Dockerfile those caches are empty\n on every CI run and the whole compile reruns. Compile on the runner\n with `actions/cache` for the compiler\'s incremental state, and have\n the Dockerfile only `COPY` the published output onto a base image\n holding what rarely changes (database server, runtimes, seed data;\n rebuild it only when its inputs change). Three details decide whether\n the re-push is actually small:\n - Incremental compilers (MSBuild, make, cargo) compare file mtimes.\n A fresh `git clone` stamps every file "now" and invalidates the\n cached state. Before compiling, set tracked files to an old date\n (`git ls-files -z | xargs -0 touch -h -d \'2000-01-01\'`), then\n touch the files changed since the commit you last built. Keep its\n SHA in the cache entry, check out with `fetch-depth: 0` so it is\n present, and skip this on a cache miss (that build is full anyway):\n `git diff -z --name-only --diff-filter=d <last-sha> HEAD | xargs -0 -r touch -h`.\n - Layers are digested by content including mtimes, and the parent\n directories a COPY creates can get the build time, so identical\n files can yield a new layer digest on every build. Stage the\n output under its final path (`staged/opt/app/...`), pin every\n mtime in it including the directories\n (`find staged -exec touch -h -d \'2000-01-01\' {} +`), and use\n `COPY --link staged/ /` (BuildKit).\n - Split third-party files from your own into separate COPYs so a\n code change rewrites only the small one.\n\n CI authenticates with an API key (`COFACTOR_API_TOKEN`). Give it only\n what the job does: `sandbox:read`, `sandbox:publish` and `property:read`\n to push (plus `property:create` if CI\'s push may be the first, which\n creates the application); add `sandbox:create` and `sandbox:delete` to claim and destroy,\n `sandbox:run` to run actions, and `sandbox:exec` to run shell commands\n (not included in `sandbox:*`). Create it from the CLI (the secret prints\n once): `cofactor api-key create --name "CI image push" --scope sandbox:read\n --scope sandbox:publish --scope property:read`.\n\n Push main builds with `--tag main` (or `nightly`). For pull requests, push\n each with its own tag and claim it by that tag:\n cofactor sandbox push --application "Project Tracker" app:sandbox --tag pr-123 --wait\n cofactor sandbox claim --application "Project Tracker" --environment default --at pr-123 --wait\n A pull request\'s push shares every layer below the source with main\'s.\n\nNIGHTLY PUSH FROM GITHUB ACTIONS\n\n A complete `.github/workflows/cofactor-nightly.yml` for the example\n application "Acme Tracker": it builds the image every night (and on\n demand), pushes it tagged `nightly`, then runs your suite on that push.\n Replace the application and suite names with yours.\n\n Before the first run, create the CI key (see above) and store it as the\n repository secret COFACTOR_API_TOKEN. For the last step add `--scope\n test-run:run --scope test-run:read`; without that step the push scopes\n are enough.\n\n name: cofactor-nightly\n on:\n schedule:\n - cron: "0 3 * * *"\n workflow_dispatch:\n concurrency:\n group: cofactor-nightly\n cancel-in-progress: false\n permissions:\n contents: read\n jobs:\n nightly:\n runs-on: ubuntu-latest\n timeout-minutes: 180\n steps:\n - uses: actions/checkout@v4\n - uses: actions/setup-node@v4\n with:\n node-version: 22\n - run: npm install -g @runcofactor/cli\n - uses: docker/setup-buildx-action@v4\n - name: Build the image\n uses: docker/build-push-action@v7\n with:\n context: .\n platforms: linux/amd64\n load: true\n tags: acme-tracker:sandbox\n cache-from: type=gha\n cache-to: type=gha,mode=max\n - name: Check the definition\n run: cofactor sandbox validate --upload cofactor.sandbox.yaml\n - name: Push the image\n env:\n COFACTOR_API_TOKEN: ${{ secrets.COFACTOR_API_TOKEN }}\n run: |\n cofactor sandbox push --application "Acme Tracker" acme-tracker:sandbox \\\n --definition cofactor.sandbox.yaml --tag nightly --wait\n - name: Run the suite on the new push\n env:\n COFACTOR_API_TOKEN: ${{ secrets.COFACTOR_API_TOKEN }}\n run: cofactor test-suite run --suite "Acme Tracker nightly"\n\n The CLI reads `COFACTOR_API_TOKEN` from the environment (no login step);\n only the steps that call Cofactor get it, and `validate` needs none.\n The build step\'s cache settings are the ones under FAST RE-PUSHES.\n `--wait` follows the import for up to 50 minutes and fails the job if\n Cofactor cannot boot the image. A push still importing after 50\n minutes is not a failure: the job moves on and the suite runs on the\n previous head. The suite runs on the application\'s head: the newest push\n tagged `main` or `nightly`. Start the workflow by hand with "Run\n workflow" (the `workflow_dispatch` trigger) to test a change to this\n file. `timeout-minutes` covers a build, the longest import wait and the\n suite; raise it if your suite runs longer.\n\n The last step is optional: a suite can also run on a Cofactor schedule.\n\nMAKING YOUR IMAGE SYNCABLE\n\n `cofactor sandbox sync` writes your working tree into a claimed sandbox\n (see `guide v2 local`). For a pushed image it syncs the directory holding\n cofactor.sandbox.yaml into the image\'s WORKDIR, so the image must keep\n that directory\'s files at WORKDIR in the same layout. The rule: a\n syncable image has your definition file at its WORKDIR, as `COPY . .`\n gives you.\n\n WORKDIR /app\n COPY . .\n RUN npm ci && npm run build\n\n A multi-stage build that copies only some directories works too, as\n long as each lands at the same relative path under WORKDIR and the\n definition file is copied in:\n WORKDIR /app\n COPY --from=build /src/api /app/api\n COPY cofactor.sandbox.yaml /app/cofactor.sandbox.yaml\n\n Files the image doesn\'t ship (sources it compiles away, docs) are fine:\n `sync` writes them as new files and nothing runs them.\n\n Compiled output isn\'t rebuilt in the sandbox (nothing builds at push or claim; `sync --build` reruns a declared `lifecycle.build`):\n build it locally, then sync the result to where the image serves it:\n npm run build\n cofactor sandbox sync <id> dist --to public\n\n `push` checks the rule on the local image and says one of:\n Sync: syncable.\n Sync: syncable (the image is older than your working tree, which is fine).\n Sync: not syncable: cofactor.sandbox.yaml isn\'t at /app/cofactor.sandbox.yaml (...)\n Sync: not checked (...) (Docker, or git, couldn\'t answer)\n It never stops a push. It reads the definition at your WORKDIR, not the\n copy `push` adds for Cofactor itself.\n\nHOW YOUR IMAGE RUNS IN A SANDBOX\n\n - Its ENTRYPOINT and CMD are NOT used. Start each process yourself with a\n `services.<name>.run` command (often your entrypoint script).\n - Its ENV and WORKDIR ARE applied, as `docker run` would, its PATH\n included: commands run through a `bash -lc` login shell, and the image\'s\n own PATH is put back in front of whatever the login profile sets.\n - Leave the image\'s USER as root (or unset). The sandbox starts as root\n and drops to uid 1000 itself; an image that ended on another `USER`\n has failed to boot with a permission error on `/etc/cofactor`.\n - Everything \u2014 services, prepare commands, actions \u2014 runs as an\n unprivileged user with uid 1000 (the sandbox adds that account if your\n image has none). The WORKDIR tree is made theirs; anything the app\n writes must be under the WORKDIR or `/tmp`, not elsewhere in a root-\n owned path.\n - One machine, no Docker inside: a database is a process you run as a\n service (Postgres installed in the image, or SQLite), not a container.\n For S3 or GCS, declare `object_storage` and the sandbox runs the\n server for you (see the `object-storage` topic).\n - Each public port gets its own https URL, unique per sandbox. Its\n origin is a param \u2014 `params: { web_url: { port: web } }` \u2014 that you\n pass to the app through `env` (e.g. `PUBLIC_URL: ${params.web_url}`).\n\nTHE DEFINITION FOR PUSHED IMAGES\n\n The same `cofactor.sandbox.yaml` as for a repository (see the\n `definition` topic), minus installing the toolchain: no `setup`. `lifecycle.build`\n and `after_build` are optional and run only on `sync --build`. A minimal one for the example app,\n built as one image that serves its UI and API on port 8080:\n\n version: 1\n resources: { vcpus: 2, memory_mib: 4096 }\n ports:\n web: { port: 8080, access: public }\n params:\n web_url: { port: web }\n env:\n PUBLIC_URL: ${params.web_url}\n services:\n app:\n run: exec ./start.sh\n health: { http: 8080, path: /health }\n reload_on_prepare: true\n personas:\n owner: { email: owner@example.test }\n lifecycle:\n prepare:\n - bash sandbox/personas.sh\n actions:\n create-workspace:\n description: A new workspace, created by the given persona, who becomes its owner.\n run: sandbox/actions/create-workspace.sh\n input:\n name: { type: string, required: true }\n as: { type: persona, required: true }\n output:\n workspace_id: { type: string }\n environments:\n default:\n description: Workspace "Acme", owned by the owner persona.\n seeds:\n - id: acme\n action: create-workspace\n input: { name: Acme, as: owner }\n\n From here, add what your tests need, in the order "WHAT TO BUILD FIRST"\n in the `seeds-and-actions` topic gives: a persona per role, an\n environment per plan or flag combination, and actions for the core\n objects.\n\n Keep the file anywhere in your repo: `validate --upload <file>` and\n `push --definition <file>` read it from where you point them, and the\n push puts it in the image. Paths inside it (`run:` of actions, prepare\n commands) are relative to the WORKDIR, so ship your sandbox scripts in\n the image (e.g. `COPY sandbox/ sandbox/`).\n\n Services start when the image is first booted, BEFORE any sandbox exists,\n with params at placeholder values. A service that needs the sandbox\'s own\n values (its URL, a `{secret: true}` param) or reads a secret sets\n `reload_on_prepare: true`: it is started (and restarted) by each\n sandbox\'s prepare instead. In doubt, set it on your app\'s service.\n\n `health` passes when the http path answers below 400 (or the command\n exits 0) within 5 minutes of the service starting.\n\nPERSONAS: MAKING THE LOGINS WORK\n\n Cofactor generates a fresh password for every persona when a snapshot is\n built; every sandbox copied from it has the same ones. Your\n `lifecycle.prepare` commands (which also run on each copy) receive them as `COFACTOR_PERSONAS`, a\n JSON map `{ "<persona>": { "email": "...", "password": "..." } }`, and\n must make each login work in your app: create or update that user with\n that password (through your app\'s API, its CLI, or its database). A\n persona whose login doesn\'t work breaks every test that signs in as it.\n A persona\'s `email` is yours to choose: pick an account your app already\n has (its seed data) and set its password, or have prepare create it.\n Cofactor\'s test runs then sign in with these logins automatically, as\n the application\'s persona of the same name.\n\n Prepare runs while your app is up: the `reload_on_prepare` services are\n running and healthy (restarted first when their environment changed),\n the others have run since the image was booted. So write the logins\n somewhere the running app reads on every sign-in (its database, or its\n own API). If your app loads its accounts once at startup (a file or\n config read into memory), change it to read them per request, or the new\n passwords won\'t take. For an app whose logins are fixtures, a common fix\n is a small JSON file the prepare script writes from `COFACTOR_PERSONAS`\n and the app checks on each sign-in before its fixtures.\n\nACTIONS AND SEEDS\n\n An action is a script in the image with typed input and output (the\n `seeds-and-actions` topic covers designing them). Inputs arrive as\n `INPUT_<NAME>` env vars (a `persona` input carries the persona\'s name,\n e.g. `owner`), alongside the definition\'s `env:`, every param as\n `COFACTOR_PARAM_<NAME>`, and `COFACTOR_PERSONAS`. The script prints its\n result as a JSON object on the LAST stdout line that starts with `{`\n (send everything else to stderr). It runs inside the sandbox, so it\n reaches your app on localhost. An environment\'s `seeds` call actions in\n order, once per version, before the snapshot every claim copies;\n `${<step id>.<output>}` passes one step\'s output to a later step. Run\n one against a live sandbox with `cofactor sandbox run <id> <action>\n --input name=... --input as=owner`.\n\n An action that signs in as a persona and creates data through the app\'s\n own API (here a cookie-session JSON API on port 8080; the port, paths and\n JSON fields are the example app\'s, use your app\'s). It needs jq and\n curl, which slim base images lack: add them to your Dockerfile\n (`apt-get install -y --no-install-recommends jq curl`).\n\n #!/usr/bin/env bash\n # sandbox/actions/create-workspace.sh\n set -euo pipefail\n login=$(jq -c --arg p "$INPUT_AS" \'.[$p] | {email, password}\' <<<"$COFACTOR_PERSONAS")\n jar=$(mktemp)\n curl -fsS -c "$jar" -H \'content-type: application/json\' \\\n -d "$login" http://127.0.0.1:8080/api/auth/login >&2\n id=$(curl -fsS -b "$jar" -H \'content-type: application/json\' \\\n -d "$(jq -nc --arg n "$INPUT_NAME" \'{name: $n}\')" \\\n http://127.0.0.1:8080/api/workspaces | jq -er \'.id\')\n jq -nc --arg id "$id" \'{workspace_id: $id}\'\n\n\nTROUBLESHOOTING\n\n - `push` says the image is not linux/amd64, or its WORKDIR can\'t be\n used: rebuild as it says.\n - The push fails with "Cofactor could not start this image": a service\n exited or never turned healthy. The message ends with that service\'s\n log. To reproduce, open a shell in the image \u2014\n docker run --rm -it --entrypoint bash <image>\n \u2014 and run that service\'s `run` command from the WORKDIR. In the\n sandbox it runs as uid 1000 with the WORKDIR made theirs; plain\n `docker run --user 1000` leaves the WORKDIR root-owned, so a write\n failure there is not the sandbox\'s.\n - A claim fails in prepare or a seed: the error carries the command\'s\n output. Fix the script, rebuild the image and push again.\n - To try prepare and actions before pushing, run the app as usual and\n call your scripts in it with the variables they read (a sandbox also\n sets the definition\'s `env:` and runs them as uid 1000):\n docker run -d --name try -p 8080:8080 <image>\n docker exec -e COFACTOR_PERSONAS=\'{"owner":{"email":"owner@example.test","password":"pw-1"}}\' try bash sandbox/personas.sh\n docker exec -e COFACTOR_PERSONAS=\'\u2026\' -e INPUT_NAME=Acme -e INPUT_AS=owner try bash sandbox/actions/create-workspace.sh\n (`docker exec` starts in the image\'s WORKDIR.)\n - `cofactor sandbox logs` and `diagnostics` are for the older sandboxes;\n for these, `cofactor sandbox show <id>` has the status and any error.\n - "push the image again": Cofactor\'s sandbox helper changed since that\n push; a fresh `cofactor sandbox push` picks up the current one.\n - A layer larger than 5 GB is refused; split or slim it.\n - Every push uploads the whole image: the build cache is missing (see\n FAST RE-PUSHES), or the Dockerfile copies the source before\n installing dependencies.\n';
323
323
 
324
324
  // ../content/src/sandbox-v2-guide/local.txt
325
- var local_default = "YOUR UNCOMMITTED CHANGES IN A SANDBOX (SYNC)\n\nWrite a test next to your code and run it against your real app, before\nyou commit: claim a sandbox, sync your working tree into it, rebuild what\nthe change needs, and open it or run a flow on it. No commit, no image\nbuild, no pull request.\n\n cofactor sandbox claim --application \"Project Tracker\" --wait\n # edit files\n cofactor sandbox sync <id>\n cofactor sandbox exec <id> -- cofactor-sandbox restart app\n cofactor workflow run <flow> --sandbox <id>\n # edit, and sync again\n\nYou decide what to rebuild and restart: a sync only writes and deletes\nfiles. Each sync extends the sandbox's lease by up to 2 hours, up to 8 hours\nafter the claim. Syncing needs the `sandbox:exec` scope for API keys.\n\nWHAT A SYNC DOES\n\n `sync` makes the sandbox's files match your working tree, the way git\n sees it: tracked files with your uncommitted edits, plus untracked files\n that aren't ignored. Ignored files (node_modules/, .env if you ignore it,\n build output) are not synced. Your git index is never touched.\n\n - GitHub application: your repository's top level syncs into the\n sandbox's checkout. Run `sync` anywhere inside the repository.\n - Pushed image: run `sync` from the directory holding\n cofactor.sandbox.yaml; it syncs into the image's WORKDIR (see\n \"Making your image syncable\" in `guide v2 upload`).\n\n Before it writes anything, `sync` prints the files it will write (each\n untracked one marked), the files it will delete, and what it keeps. Check\n for a stray untracked file you didn't mean to send, such as an unignored\n .env.\n\n It ends with the synced tree id, which `cofactor sandbox show` and every\n flow run on the sandbox record. A later commit of exactly these files has\n the same tree (`git rev-parse <commit>^{tree}`, or `<commit>:<dir>` for a\n pushed image's directory).\n\nDELETIONS\n\n A file only the sandbox has is deleted only when all three hold:\n - it isn't in your working tree,\n - your ignore rules don't match it, and\n - some commit in your local history once had a file at that path.\n So a file you deleted, or a directory you removed, is deleted in the\n sandbox, while everything your image or build put there (node_modules/,\n .venv/, compiled assets, a script your Dockerfile copied elsewhere) is\n kept. `sync` lists both.\n\n Syncing again is always safe: it compares against the sandbox's real\n files, so a sync that stopped part-way converges on the next one.\n\nREBUILD AND RESTART\n\n Services keep running the old code until you restart them:\n cofactor sandbox exec <id> -- cofactor-sandbox restart app\n `restart` names one or more services, waits until they're healthy, and\n prints each one's status. An unknown name lists the sandbox's services.\n\n Install dependencies, run migrations or a build with `exec` first: it runs\n in the same directory and environment your services use (the definition's\n directory in the checkout, or a pushed image's WORKDIR):\n cofactor sandbox exec <id> -- npm ci\n cofactor sandbox exec <id> -- npx prisma migrate deploy\n\n Logs: each service writes /var/lib/cofactor/logs/<service>.log.\n cofactor sandbox exec <id> -- tail -n 100 /var/lib/cofactor/logs/app.log\n\n Let the definition do it (GitHub and pushed applications alike):\n cofactor sandbox sync <id> --build\n reruns each `lifecycle.build` step whose `inputs` changed (and every step\n after it), then restarts the `after_build` services. Only this flag runs\n them: a sync without it, a push and a claim never do. A pushed image's\n steps run in its WORKDIR and come from the definition baked into the image,\n so changing them takes a new `push`. The first `--build` on a claimed\n sandbox has no record of earlier runs, so it runs every step.\n\nCOMPILED OUTPUT\n\n When the sandbox runs compiled output (a bundled frontend, compiled\n TypeScript), build it locally and sync the result to where the sandbox\n serves it:\n bun run build\n cofactor sandbox sync <id> frontend/dist --to static\n `--to` is relative to the sync root (the checkout, or the image's\n WORKDIR). This mirrors the local directory as is: ignore rules don't\n apply, so a git-ignored dist/ is sent; every file in it is written, and\n every other file under --to is deleted.\n `show` and runs then record it as build output.\n\nDATA IS FORWARD-ONLY\n\n A sync changes files, never data. A migration you ran stays applied when\n you sync older files back, and seeded rows stay. To start over, destroy\n the sandbox and claim a new one.\n\nRUN A FLOW ON IT\n\n cofactor workflow run <flow> --sandbox <id>\n\n The run uses this sandbox instead of claiming a fresh copy, and never\n destroys it. The run targets the sandbox's own application and\n environment (no --environment needed; naming another one is refused), the\n sandbox must be ready, and it must be one you claimed, not a test run's. The run records the version and the synced tree it ran\n against, as of when it started; `cofactor workflow debug overview <run>`\n shows them. Syncing during a run changes the files under it, so sync\n between runs.\n\nLIMITS\n\n - Sync only sandboxes claimed with `cofactor sandbox claim`; a test run's\n own copy can't be synced.\n - Symlinks and submodules are not synced (`sync` lists them).\n - A single file larger than 63 MiB is refused; a sync of many files is\n sent in several requests.\n - The `sync` port counts toward the 8-port limit (see `guide v2 ports`).\n";
325
+ var local_default = "YOUR UNCOMMITTED CHANGES IN A SANDBOX (SYNC)\n\nWrite a test next to your code and run it against your real app, before\nyou commit: claim a sandbox, sync your working tree into it, rebuild what\nthe change needs, and open it or run a flow on it. No commit, no image\nbuild, no pull request.\n\n cofactor sandbox claim --application \"Project Tracker\" --wait\n # edit files\n cofactor sandbox sync <id>\n cofactor sandbox exec <id> -- cofactor-sandbox restart app\n cofactor workflow run <flow> --sandbox <id>\n # edit, and sync again\n\n[cli >= 0.56.0]\nStart even the first sandbox from uncommitted files:\n cofactor sandbox validate\n cofactor sandbox claim --application \"Project Tracker\" --from-local --wait\n # edit and iterate on the same copy\n cofactor sandbox sync <id> --build\n\nThe CLI connects from a deterministic wip commit object using your Git\ncredentials; your HEAD and index stay untouched. For changes sync cannot\nadopt (setup, prepare, seeds, personas or actions), validate and claim\n--from-local again. After the final edit, make a fresh claim and verify\nits printed source SHA with `verify-env --at <sha>`. No merge or user\ncommit is required. See `cli`, SETTING UP A NEW GITHUB APPLICATION.\n[/cli]\n\nYou decide what to rebuild and restart: a sync only writes and deletes\nfiles. Each sync extends the sandbox's lease by up to 2 hours, up to 8 hours\nafter the claim. Syncing needs the `sandbox:exec` scope for API keys.\n\nWHAT A SYNC DOES\n\n `sync` makes the sandbox's files match your working tree, the way git\n sees it: tracked files with your uncommitted edits, plus untracked files\n that aren't ignored. Ignored files (node_modules/, .env if you ignore it,\n build output) are not synced. Your git index is never touched.\n\n - GitHub application: your repository's top level syncs into the\n sandbox's checkout. Run `sync` anywhere inside the repository.\n - Pushed image: run `sync` from the directory holding\n cofactor.sandbox.yaml; it syncs into the image's WORKDIR (see\n \"Making your image syncable\" in `guide v2 upload`).\n\n Before it writes anything, `sync` prints the files it will write (each\n untracked one marked), the files it will delete, and what it keeps. Check\n for a stray untracked file you didn't mean to send, such as an unignored\n .env.\n\n It ends with the synced tree id, which `cofactor sandbox show` and every\n flow run on the sandbox record. A later commit of exactly these files has\n the same tree (`git rev-parse <commit>^{tree}`, or `<commit>:<dir>` for a\n pushed image's directory).\n\nDELETIONS\n\n A file only the sandbox has is deleted only when all three hold:\n - it isn't in your working tree,\n - your ignore rules don't match it, and\n - some commit in your local history once had a file at that path.\n So a file you deleted, or a directory you removed, is deleted in the\n sandbox, while everything your image or build put there (node_modules/,\n .venv/, compiled assets, a script your Dockerfile copied elsewhere) is\n kept. `sync` lists both.\n\n Syncing again is always safe: it compares against the sandbox's real\n files, so a sync that stopped part-way converges on the next one.\n\nREBUILD AND RESTART\n\n Services keep running the old code until you restart them:\n cofactor sandbox exec <id> -- cofactor-sandbox restart app\n `restart` names one or more services, waits until they're healthy, and\n prints each one's status. An unknown name lists the sandbox's services.\n\n Install dependencies, run migrations or a build with `exec` first: it runs\n in the same directory and environment your services use (the definition's\n directory in the checkout, or a pushed image's WORKDIR):\n cofactor sandbox exec <id> -- npm ci\n cofactor sandbox exec <id> -- npx prisma migrate deploy\n\n Logs: each service writes /var/lib/cofactor/logs/<service>.log.\n cofactor sandbox exec <id> -- tail -n 100 /var/lib/cofactor/logs/app.log\n\n Let the definition do it (GitHub and pushed applications alike):\n cofactor sandbox sync <id> --build\n reruns each `lifecycle.build` step whose `inputs` changed (and every step\n after it), then restarts the `after_build` services. Only this flag runs\n them: a sync without it, a push and a claim never do. A pushed image's\n steps run in its WORKDIR and come from the definition baked into the image,\n so changing them takes a new `push`. The first `--build` on a claimed\n sandbox has no record of earlier runs, so it runs every step.\n\nCOMPILED OUTPUT\n\n When the sandbox runs compiled output (a bundled frontend, compiled\n TypeScript), build it locally and sync the result to where the sandbox\n serves it:\n bun run build\n cofactor sandbox sync <id> frontend/dist --to static\n `--to` is relative to the sync root (the checkout, or the image's\n WORKDIR). This mirrors the local directory as is: ignore rules don't\n apply, so a git-ignored dist/ is sent; every file in it is written, and\n every other file under --to is deleted.\n `show` and runs then record it as build output.\n\nDATA IS FORWARD-ONLY\n\n A sync changes files, never data. A migration you ran stays applied when\n you sync older files back, and seeded rows stay. To start over, destroy\n the sandbox and claim a new one.\n\nRUN A FLOW ON IT\n\n cofactor workflow run <flow> --sandbox <id>\n\n The run uses this sandbox instead of claiming a fresh copy, and never\n destroys it. The run targets the sandbox's own application and\n environment (no --environment needed; naming another one is refused), the\n sandbox must be ready, and it must be one you claimed, not a test run's. The run records the version and the synced tree it ran\n against, as of when it started; `cofactor workflow debug overview <run>`\n shows them. Syncing during a run changes the files under it, so sync\n between runs.\n\nLIMITS\n\n - Sync only sandboxes claimed with `cofactor sandbox claim`; a test run's\n own copy can't be synced.\n - Symlinks and submodules are not synced (`sync` lists them).\n - A single file larger than 63 MiB is refused; a sync of many files is\n sent in several requests.\n - The `sync` port counts toward the 8-port limit (see `guide v2 ports`).\n";
326
326
 
327
327
  // ../content/src/sandbox-v2-guide/index.ts
328
328
  var SANDBOX_V2_GUIDE_SECTIONS = [
@@ -2428,7 +2428,7 @@ ASSERTION PLACEMENT
2428
2428
  var api_sequences_default = 'API Sequences are the API-only counterpart to browser workflows. Instead of\ndriving a browser node-by-node, an API sequence is a single node\n(nodeType: "api_sequence") inside an api-only workflow that executes an\nordered list of HTTP calls, chaining outputs from one step into the inputs\nof the next.\n\nWHEN TO USE\n\n - Exercise an API without a UI (integration tests, fixture setup).\n - Seed or tear down state via API before/after a browser run.\n - Fast, deterministic smoke test of a critical flow.\n\nCONFIG SHAPE (ApiSequenceConfig)\n\n {\n "steps": [ ... ], // ordered; required\n "description": "...", // optional\n "stopOnFailure": true, // default: true\n "exports": [ ... ] // optional; see below\n }\n\nSTEP SHAPE (ApiSequenceStep)\n\n The authoritative type is in packages/types/src/api-sequence.ts. Key fields:\n\n {\n "stepId": "createProject", // optional; defaults to "step{index}"\n "apiRequestId": "<catalog id>", // required \u2014 points to api_requests row\n "credentialId": "<uuid>" | null, // null = unauthenticated\n "credentialTemplateId": "<uuid>" | null, // auth by persona/template (per-env); wins over credentialId\n "credentialTemplateName": "<persona>", // author-friendly alias for the above; resolved to the id on save\n "method": "POST", // optional; falls back to catalog default\n "urlTemplate": "/projects/{{createProject.response.id}}",\n "queryParams": [ { "key": "limit", "value": "10" } ],\n "bodyTemplate": "{\\"name\\": \\"$EMAIL()\\"}", // STRING (JSON), not object\n "headersTemplate": { "X-Trace": "$UUID()" }, // optional\n "responseVariableName": "createProject", // optional alias for stepId\n "expectedStatusCodes": [200, 201], // step fails if status is outside\n "description": "...", // optional UI label\n "validations": [ ... ], // assertions against the response\n "validationMode": "all" // "all" (default) or "any" validation must pass\n }\n\n CREDENTIAL ROLES \u2014 `credentialId` is overloaded; two distinct uses exist:\n\n - Step-level `credentialId` (and a top-level/default credential on the\n sequence) is AUTH: it\'s what the api-sequence runner uses to\n authenticate each HTTP call. Editing this changes who is calling.\n\n - Step-level `credentialTemplateId` is AUTH-by-persona: instead of a fixed\n credential id, name a credential template/persona and the runner resolves\n it to the credential bound for the run\'s environment (mirrors a login\n node\'s from_template). Takes precedence over `credentialId`. Use this when\n the same sequence must run across environments (e.g. Dev + a sandbox that\n mints its own per-instance credential) rather than a single static id.\n\n - `credentialId` appearing INSIDE a step\'s `bodyTemplate` JSON string is\n DATA: it gets written into the request body and becomes part of the\n provisioned resource (e.g. the credential a created workflow logs in\n with). Editing this changes what the call creates.\n\n The two are independent. Swapping the auth credential won\'t change what\n the call provisions; swapping the body credential won\'t change auth.\n Mixing them up silently produces confusing test failures.\n\n `apiRequestId` references a row in the `api_requests` table. Discover IDs\n from the API catalog for the target property. If `urlTemplate`, `method`,\n or `bodyTemplate` are omitted, they are backfilled from the catalog entry\'s\n defaults at execution time. A custom step that calls a URL directly uses\n `"apiRequestId": ""` and sets `method` and `urlTemplate` itself.\n\n `bodyTemplate` is a JSON *string*, not a JSON object \u2014 templates and macros\n are interpolated into it as raw text before parsing.\n\n MULTIPART UPLOADS \u2014 to send multipart/form-data instead of a raw body, set\n `formParts` on the step and omit `bodyTemplate`. The two are mutually\n exclusive; there is no separate mode field, a step is multipart exactly when\n `formParts` is present. Each part is either text or file:\n\n "formParts": [\n { "kind": "text", "name": "projectId", "value": "$var.project_id" },\n { "kind": "file", "name": "file", "attachmentId": "<uuid>" }\n ]\n\n A text part\'s `value` supports the same macro/variable/step-reference syntax\n as `bodyTemplate`. A file part names an attachment by UUID; add `filename`\n to override the name sent in Content-Disposition, otherwise the\n attachment\'s own filename is used. Don\'t set a `Content-Type` header on a\n multipart step: the transport generates its own boundary-bearing header at\n request time, so any author-supplied `Content-Type` is stripped rather than\n allowed to shadow it with a stale or missing boundary.\n\n API LOGIN CREDENTIALS \u2014 use these for token exchanges that precede the\n sequence request. POST bodies support application/json,\n application/x-www-form-urlencoded, and text/xml. With no body template,\n form fields are URL-encoded automatically. JSON token responses use normal\n dot paths such as access_token; XML/SOAP responses are parsed with namespace\n prefixes removed, so a Salesforce session path is\n Envelope.Body.loginResponse.result.sessionId. Keep returned tokens inside\n the Credential boundary \u2014 do not export them as workflow variables. Token\n request headers (for example Salesforce SOAPAction=login) are encrypted with\n the Credential and apply only to the token exchange.\n\nTEMPLATE SYNTAX\n\n Three ways to inject dynamic values. All three can appear in the same\n string (urlTemplate, bodyTemplate, headersTemplate values, or queryParams\n values).\n\n 1. STEP REFERENCES \u2014 outputs of prior steps.\n\n {{stepId.response.path.to.value}}\n\n The `.response.` segment is REQUIRED. This is the most common foot-gun:\n\n \u2713 {{createProject.response.data.id}}\n \u2717 {{createProject.data.id}} \u2014 missing .response., will warn at\n runtime and resolve to undefined\n \u2717 {{projectId}} \u2014 no step reference at all\n\n The executor logs a warning for malformed references but does NOT\n block execution \u2014 a malformed template resolves to an empty string and\n the request goes out anyway. Always run once and check the run log.\n\n 2. MACROS \u2014 dynamic data generated fresh per run.\n\n $UUID() Random UUID v4\n $NOW() ISO 8601 timestamp (UTC)\n $NOW(+5m) ISO timestamp shifted by +5m / -2h / 30s / 1d\n $NOW(+5m, \'m H * * *\') Shifted time rendered in UTC (see macros guide);\n single-quote the format inside a JSON bodyTemplate\n $TODAY() YYYY-MM-DD\n $DATE(N) Date offset by N days\n $TIMESTAMP() Unix ms\n $RANDOM_INT(min,max) Random integer in range\n $RANDOM_STRING(N) Alphanumeric string of length N\n $EMAIL() test-<rand>@example-test.com\n $FIRST_NAME() Random first name\n $LAST_NAME() Random last name\n $PHONE() +1-555-...\n $SEQUENCE("name") Per-run auto-increment counter\n\n Each run resolves macros independently. $SEQUENCE counters persist\n across steps within a single run.\n\n 3. WORKFLOW VARIABLES \u2014 values injected by the workflow runtime.\n\n $var.<name>\n\n These come from two places:\n\n a) Upstream credential variables \u2014 pre-provisioned before the\n sequence runs. Typical names: `$var.credential.username`,\n `$var.credential.password`, `$var.credential.email`. If the\n workflow has a credential, USE these for the matching fields\n instead of `$EMAIL()` / `$RANDOM_STRING(6)` \u2014 the values must\n match the already-provisioned account.\n\n b) Upstream exports \u2014 values exported by earlier nodes in the same\n workflow (e.g. a seed step produced `$var.project_id`).\n\n Naming convention for exported variables: `resource_field` with\n underscores (e.g. `project_id`, `user_email`).\n\nEXPORTS\n\n After all steps finish, the sequence can register values from the final\n state into the workflow variable registry for downstream nodes:\n\n {\n "exports": [\n {\n "variableName": "project_id", // appears as $var.project_id\n "stepId": "createProject",\n "responsePath": "data.id",\n "source": "response", // "response" (default) | "request"\n "description": "..." // optional UI label\n }\n ]\n }\n\n Only export fields downstream nodes actually need. Use the\n `resource_field` naming convention.\n\nVALIDATIONS\n\n `validationMode` is "all" by default: every configured validation must pass.\n Set it to "any" to require at least one configured validation. The expected\n HTTP status requirement must still pass independently. Individual validation\n results retain their pass/fail values even when another alternative passes.\n\n Per-step assertions against the response. Each validation:\n\n {\n "left": "createProject.response.data.status", // "stepId.path" form\n "operator": "equals", // see list\n "right": "active", // omit for is_set/is_not_set\n "description": "..."\n }\n\n Operators: equals, not_equals, gt, lt, gte, lte, contains, not_contains,\n is_set, is_not_set.\n\nWHAT IS VALIDATED AT COMMIT TIME\n\n Save/update routes reject malformed step fields, unknown validation modes,\n invalid HTTP status codes, duplicate step IDs, invalid step references,\n unknown macros, and invalid exports. `validationMode` accepts only "all"\n or "any"; omitting it keeps all-checks behavior.\n\n Saving does not call the target API or prove its response. Smoke-test the\n sequence before relying on it in a workflow.\n\n CLI users: see `cofactor workflow guide cli-api-sequences` for command usage.\n\nEXAMPLE (3-step sequence, correct shape)\n\n {\n "description": "Create an item, fetch it, delete it",\n "stopOnFailure": true,\n "steps": [\n {\n "stepId": "create",\n "apiRequestId": "<POST /items id>",\n "credentialId": null,\n "method": "POST",\n "bodyTemplate": "{\\"name\\": \\"item-$RANDOM_STRING(6)\\"}",\n "expectedStatusCodes": [201]\n },\n {\n "stepId": "fetch",\n "apiRequestId": "<GET /items/:id id>",\n "credentialId": null,\n "method": "GET",\n "urlTemplate": "/items/{{create.response.id}}",\n "expectedStatusCodes": [200],\n "validations": [\n { "left": "fetch.response.name", "operator": "is_set" }\n ]\n },\n {\n "stepId": "remove",\n "apiRequestId": "<DELETE /items/:id id>",\n "credentialId": null,\n "method": "DELETE",\n "urlTemplate": "/items/{{create.response.id}}",\n "expectedStatusCodes": [200, 204]\n }\n ],\n "exports": [\n { "variableName": "item_id", "stepId": "create", "responsePath": "id" }\n ]\n }\n\nTIPS\n\n - Give every step a short, stable `stepId`. Later steps and exports\n reference it; the default `stepN` label is brittle to reordering.\n - Always include `.response.` in step references. The runtime won\'t\n error out, but values will silently resolve to empty strings.\n - Prefer `$EMAIL()`, `$UUID()`, `$RANDOM_STRING(6)` for fields that must\n be unique per run \u2014 unless the workflow has an upstream credential,\n in which case use `$var.credential.username` / `.password` / `.email`.\n - Set `expectedStatusCodes` on every step so unexpected 4xx/5xx fails\n the step instead of silently flowing into the next template.\n - `bodyTemplate` is a JSON STRING. Escape inner quotes.\n - `workflow api-sequence generate` can AI-draft a config from a prompt\n or a prior browser run; refine with `update`. The generated output is\n just JSON \u2014 review it before committing.\n';
2429
2429
 
2430
2430
  // ../content/src/workflow-guide/cli-seeds.txt
2431
- var cli_seeds_default = 'Use this section when you need to create or inspect seed workflows from the\nCofactor CLI. The conceptual model lives in `seed-workflows`; this section is\nonly command orientation. Prefer each command\'s `--help` output for the\ncurrent flag list.\n\nCREATE A SIMPLE SEED SHELL\n\n For simple data outputs, create the seed shell with repeatable output\n declarations:\n\n cofactor workflow seed create \\\n --name "Create Test Project" \\\n --description "Provisions a test project with default settings" \\\n --output "project_id:Project ID:The created project\'s UUID" \\\n --output "project_name:Project Name"\n\n This creates a draft seed workflow and, by default, a paired teardown\n workflow linked to the seed.\n\nCREATE A COMPLEX SEED SHELL\n\n For credential outputs or larger configs, put the full seed definition in a\n JSON file and create from that file:\n\n cofactor workflow seed create --from-file seed.json\n\n Keep generated credential fields in credentialOutput, and keep exported data\n fields in outputs. Downstream workflows consume both through $var.{dataKey}.\n `--outputs-json \'<array>\'` is the inline equivalent; to change the outputs\n of an existing seed use `cofactor workflow patch <id> --seed-outputs-file\n seed-outputs.json`. The credentialOutput shape and the end-to-end recipe\n are in `cofactor workflow guide seed-users`.\n\nAFTER CREATION\n\n Add the seed body through the Flow Designer or workflow sync, test both the\n seed and teardown, then publish. If the seed can be API-backed, generate or\n attach an API sequence after the browser version is proven.\n\nREFERENCE\n\n - Seed command help: `cofactor workflow seed --help`\n - Workflow sync help: `cofactor workflow sync --help`\n';
2431
+ var cli_seeds_default = 'Use this section when you need to create or inspect seed workflows from the\nCofactor CLI. The conceptual model lives in `seed-workflows`; this section is\nonly command orientation. Prefer each command\'s `--help` output for the\ncurrent flag list.\n\nCREATE A SIMPLE SEED SHELL\n\n For simple data outputs, create the seed shell with repeatable output\n declarations:\n\n cofactor workflow seed create \\\n --name "Create Test Project" \\\n --description "Provisions a test project with default settings" \\\n --output "project_id:Project ID:The created project\'s UUID" \\\n --output "project_name:Project Name"\n\n This creates a draft seed workflow and, by default, a paired teardown\n workflow linked to the seed.\n\nCREATE A COMPLEX SEED SHELL\n\n For credential outputs or larger configs, put the full seed definition in a\n JSON file and create from that file:\n\n cofactor workflow seed create --from-file seed.json\n\n Keep generated credential fields in credentialOutput, and keep exported data\n fields in outputs. Downstream workflows consume both through $var.{dataKey}.\n `--outputs-json \'<array>\'` is the inline equivalent; to change the outputs\n of an existing seed use `cofactor workflow patch <id> --seed-outputs-file\n seed-outputs.json`. The credentialOutput shape and the end-to-end recipe\n are in `cofactor workflow guide seed-users`.\n\nAFTER CREATION\n\n Add the seed body through the Flow Designer or workflow sync, test both the\n seed and teardown, then publish. If the seed can be API-backed, generate or\n attach an API sequence after the browser version is proven.\n\n[cli >= 0.56.0]\nSET HOW OFTEN A SEED RUNS\n\n Share one seed execution across all consuming flows in a suite:\n\n cofactor workflow patch <id> --run-once-per-suite true\n\n Turn off the seed\'s forced suite scope (each consuming seed node\'s scope\n applies again):\n\n cofactor workflow patch <id> --run-once-per-suite false\n\n This flag accepts only true or false and is valid only for seed flows.\n Read the saved seedRunOncePerSuite setting with `cofactor workflow list\n --json`.\n[/cli]\n\nREFERENCE\n\n - Seed command help: `cofactor workflow seed --help`\n - Workflow sync help: `cofactor workflow sync --help`\n';
2432
2432
 
2433
2433
  // ../content/src/workflow-guide/cli-api-sequences.txt
2434
2434
  var cli_api_sequences_default = 'Use this section when you need to drive API sequence authoring from the\nCofactor CLI. The conceptual model lives in `api-sequences`; this section is\nonly command orientation. Prefer each command\'s `--help` output for the\ncurrent flag list.\n\nCOMMON FLOW\n\n 1. Inspect the API catalog for candidate endpoints.\n\n cofactor api-catalog sources list\n cofactor api-catalog endpoints list --search <keyword>\n cofactor api-catalog endpoints get <apiRequestId>\n\n 2. Compose a sequence JSON file locally. Reference endpoints by\n apiRequestId and chain steps with {{stepId.response.path}}.\n\n 3. Create an API-only workflow from the sequence file.\n\n cofactor workflow create --api-only --name "smoke: create project" \\\n --sequence-from-file seq.json\n\n The command returns the workflow, sequence, and node IDs.\n\n To attach a sequence to an existing workflow that has none, run\n `cofactor workflow api-sequence update <workflowId> --from-file seq.json`;\n it creates the sequence when absent and replaces the config otherwise.\n\n 4. Execute immediately to surface template warnings and API failures.\n\n cofactor workflow api-sequence execute <workflowId>\n\n 5. Inspect the run and iterate on the sequence file.\n\n cofactor workflow runs list <workflowId> --side api\n cofactor workflow runs get <workflowId> <runId>\n cofactor workflow runs get <workflowId> <runId> --step <runStepId> --api-call <apiStepId>\n Add --json for JSON; the flags print stored (capped) request/response bodies.\n Get <runStepId> from runs get <workflowId> <runId> --json (steps[].id).\n cofactor workflow api-sequence update <workflowId> --from-file seq.json\n\nREFERENCE\n\n - Command help: `cofactor workflow api-sequence --help`\n - Workflow creation help: `cofactor workflow create --help`\n - API catalog help: `cofactor api-catalog --help`\n';
@@ -2440,7 +2440,7 @@ var triage_results_default = "Find out what failed, whether a suite is healthy,
2440
2440
  var write_a_test_default = 'Use this section to author a test straight from the CLI: plan the journey, check\nfor duplicates, create or update the workflow, run it, and hand back its URL.\nNo spec files are needed.\n\nAUTHORING LOOP\n\n 1. Understand the requirement. Read the ticket, PRD, or the user\'s intent, and\n explore the real UI first (see `best-practices` and `cofactor session guide`).\n 2. Avoid duplicates:\n cofactor workflow list --search "<keywords>"\n If a workflow already covers the journey, update it instead of creating one.\n 3. Create it (JSON on stdin or from a file; shapes are in `node-types`):\n cofactor workflow create --from-file test.json\n cofactor workflow create --from-file test.json --tags "p0,checkout"\n Input: { "name": "...", "nodes": [{ "nodeType": "...", "configJson": {...} }] }\n A node has nodeType, configJson and, for action nodes, a required "title"\n (plus id/orderIndex when reusing). The title is short and says what the user\n does; the detail goes in configJson.actionDescription:\n { "nodeType": "action", "title": "Create a ticket",\n "configJson": { "actionDescription": "Fill in the required fields and save" } }\n The title is what the run timeline, canvas and failure reports show. An\n action node without one is rejected: add "title": "<short goal>" to it.\n To act as a persona, make the first node a login node:\n {"nodeType": "login", "configJson": {"credentialSource": "from_template",\n "credentialTemplateName": "<persona>", "performLogin": true}}\n Use credentialTemplateId instead of the name when the persona name exists on\n more than one application. Without credentialSource "from_template" a login\n node expects a stored credentialId.\n 4. Change an existing workflow:\n cofactor workflow get <workflowId> (read it first)\n cofactor workflow sync <workflowId> --from-file test.json (replace all nodes)\n cofactor workflow node patch <workflowId> <nodeId> --config \'{"params":{"statement":"..."}}\'\n cofactor workflow patch <workflowId> --name "..." --description "..."\n cofactor workflow patch <workflowId> --preflight @preflight.md --cleanup @cleanup.md\n Reuse each existing node `id` in sync to keep a minimal diff. Every new or\n edited action step needs a "title"; an untitled legacy step you leave exactly\n as it is stays accepted. `workflow get --json` lists "title": null for such steps.\n Add --propose to sync to open a proposal for review instead of applying directly.\n `node patch --config` merges configJson one level deep: name the node\'s own\n keys (a sandbox_action\'s SQL is {"params":{"statement":"..."}}; an action\'s\n text is {"actionDescription":"..."}). A nested object is replaced whole, so\n "params" must list every param to keep. Run `workflow get` first and copy the\n key path from the node\'s configJson. `--set-input key=value` writes\n configJson.inputValues, which only action, seed and call_flow nodes read, and\n only for inputs the node (or the called flow) declares. On every other node\n type the value is never used: use --config.\n[cli >= 0.55.0]\n The CLI rejects --set-input on node types that ignore inputValues, and on an\n action input the node has no data requirement for.\n For multi-line SQL or prompts, put the JSON in a file: --config @patch.json\n[/cli]\n 5. Run it and look at the result:\n cofactor workflow run <workflowId> --stream\n cofactor workflow runs list <workflowId>\n cofactor workflow debug <runId> (when a run fails)\n To run on a sandbox you claimed: `cofactor workflow run <workflowId>\n --sandbox <sandboxId>`. The run targets the sandbox\'s own environment, so\n no --environment is needed. `cofactor sandbox claims` lists your live\n claims (ids, expiry) if you lost an id; `cofactor sandbox destroy <id>`.\n 6. Return the workflow URL printed by the CLI to the user verbatim. Never\n build the URL yourself.\n\n Approving a test asserts that a human verified it checks the right thing. Do\n not pass --yes to `workflow approve` until the user has confirmed.\n\nTHE RUBRIC: EVERY TEST MUST SATISFY ALL OF THESE\n\n 1. One coherent journey per workflow\n One user objective, possibly with several verifications along the way.\n "Atomic" means coherent, not minimal. If isolating a check would force you\n to duplicate most of another test\'s setup, fold it in as an inline\n assertion on the larger journey.\n Good: create org -> invite a user -> claim invite -> land on dashboard.\n Good: submit a ticket and confirm it appears in the queue.\n Bad: submit a ticket, edit a user, generate a report (three journeys,\n three workflows).\n Split only when the setup is genuinely different or the failure modes are\n independent. A long single journey stays together; independent journeys\n become separate workflows so they run in parallel and fail independently.\n\n 2. Specific, observable expected results\n Every assertion is something a stranger could verify without judgement,\n and comes from the requirement or confirmed user intent, not incidental\n DOM details seen while exploring. Keep conditions about user-visible\n behavior; do not invent ARIA/SVG checks or pixel thresholds.\n Good: "URL ends with /dashboard." "A row named X appears in the table."\n Bad: "The page looks correct." "It works."\n Attach assertions inline to the action that produced the result (see\n `assertions`). Pick severity deliberately: fatal ends the run (gating\n preconditions), fail marks the run failed but continues (feature claims),\n warn records without failing (cosmetics). A test with no run-failing\n assertion proves nothing.\n Relative checks ("the count dropped by 2") must reference a value captured\n upstream and used via $var.<name>, never rely on memory of the earlier state.\n\n 3. Idempotent, self-owned, isolated\n - Rerunning passes without a manual reset: use macros ($EMAIL(), $UUID(),\n "QA Test $RANDOM_STRING(6)") for anything that must be unique (see `macros`).\n - The test creates the records it operates on, inline. A seed workflow is\n for data that several tests share; a seed with one consumer is overhead.\n Data the test only reads may pre-exist, but never mutate it.\n - Never modify shared resources (billing, integrations, API keys, org\n config) as a side effect.\n - Tests that depend on one another run as ordered suite layers, never by\n implicit ordering.\n - A fatal assertion halts the run before trailing cleanup. Anything that\n must be gone after a failure belongs in a seed with a teardown (see\n `seed-workflows`, `data-lifecycle`).\n\n 4. Traceable\n Link the test to the product-map capability it proves:\n cofactor product-map link <CAP-code> <workflowId>\n Traceability comes from that link, not from text: do not write capability\n codes, ticket ids or requirement references into the name or description.\n If no capability covers the test, tell the user; untraceable tests rot.\n\n 5. Risk-prioritized\n Tag each test p0, p1, or p2 with --tags / --tags-add:\n p0 happy path of the core promise; if it breaks the feature is broken\n p1 primary edge cases, validation, role variants, common error states\n p2 rare conditions, visual or agentic checks, low-frequency edges\n\n 6. Preconditions, data, and cleanup are explicit\n Auth and role: use a persona (cofactor persona list --application "<app>")\n rather than raw usernames and passwords. Shared setup: a seed node, placed\n before login. Test data: workflow inputs with macros. Anything else the\n test depends on (pre-existing data it reads, flags, third-party systems)\n goes in the workflow\'s preflight, and how its side effects get reset goes\n in its cleanup. Write each as a short markdown bullet list:\n cofactor workflow patch <workflowId> --preflight @preflight.md\n cofactor workflow patch <workflowId> --cleanup @cleanup.md\n\n 7. The description says what the test is for\n Read together with the name, the description gives the spirit of the\n test: what we are doing and why it matters, in one or two plain\n sentences. It never holds capability codes, preconditions, issue ids,\n status ("expected red", "blocked"), step narration, endpoints, payloads,\n timings or cache details. Each of those lives elsewhere: the product-map\n link, the preflight, the linked issue, the run history, the nodes.\n People read the description, preflight and cleanup in the web UI, so\n never put a raw ID in them: no UUID, UUID prefix, wf_/prule_ or other\n prefixed id, issue id or run id. Name the thing instead (the seed\n workflow\'s name, the teardown\'s name, the connection\'s name, the\n record\'s name).\n Good: name "Invite a teammate to Acme"\n description "An admin invites a new teammate by email and the\n teammate can sign in. Growing a team depends on invites\n reaching people."\n Bad: description "CAP-ORG-004. Needs seed org + admin persona. Steps:\n open /settings/team, POST /api/invites, wait 5s, check inbox.\n Expected red until ISSUE-812 ships. Cache warm."\n Bad preflight:\n Seed 5e055090 creates an empty layer. Paired teardown d9a44386\n deletes the flow.\n Good preflight:\n - The "Acme empty layer" seed creates an empty layer\n - Its paired teardown, "Acme empty layer teardown", deletes the flow\n\n 8. Written as human intent, not UI mechanics\n Action titles say what the user is trying to do ("Assign the ticket to\n Support"), not click-by-click steps. Each action is one coherent,\n assertable step: usually broader than one click, ends at a visible\n boundary (modal gone, URL changed, row appeared), and has no conditionals\n inside it. If the data might not exist, seed it instead of branching.\n\nCHOOSING VALUES\n\n - Names existing data in the target app (a property, persona, credential, a\n seeded record): do not guess. Look it up with `cofactor property list`,\n `cofactor persona list --application "<app>"`, or\n `cofactor workflow list --search <term>`, and ask the user when several\n candidates fit.\n - Synthesizable (email, UUID, throwaway string, date): use a macro and move on.\n - Auth or role: prefer a persona over a raw credential.\n\nCOVERAGE CHECKLIST\n\n Before handing work back, for each item either include a test or say why it\n does not apply: happy path (p0); primary edges and boundary inputs; negative\n paths and validation errors; permission and role variants; empty, loading,\n and error states; cross-entity side effects (does this change something\n visible elsewhere?); cleanup or undo.\n\n Aim for the smallest set that proves the feature works. Five sharp tests beat\n twenty fuzzy ones.\n';
2441
2441
 
2442
2442
  // ../content/src/workflow-guide/visual-checkpoints.txt
2443
- var visual_checkpoints_default = "Visual checkpoints capture a meaningful screen state and compare it with the\nrolling baseline for that checkpoint. Add one checkpoint at each state whose\nappearance matters to the user; avoid taking several screenshots of the same\nstate just because the journey has several steps.\n\nA pull request that changes how a screen looks needs one of these as evidence:\nsee `cofactor proofs guide`.\n\nNATIVE WORKFLOW CHECKPOINTS\n\n Checkpoints run in Sandboxes v2. Seed the sandbox into the exact state to\n compare, then capture it. A standalone visual regression test is a short\n workflow that prepares one state and takes its checkpoint. Examples include\n seeding a populated list before capturing it, or submitting invalid input and\n capturing the resulting validation error.\n\n The `target` is optional plain English describing an element, such as\n `the invoices table`; omit it to capture the whole page. Never put a CSS,\n XPath, or Playwright selector in `target`.\n\n `expected` is optional plain language describing the correct visible state.\n It guides the AI in deciding whether the current state is correct and whether\n a visual change is real or can be healed. Use a specific observable\n description, for example, \"Three invoices are listed, each marked Paid, with\n no error banner.\"\n\n `record` is the default. It records visual findings without changing the\n workflow result, and compares after the suite finishes. Choose `fail` when an\n unresolved visual change should fail this workflow step; it compares inline.\n Under either severity, a `target` that cannot be found is reported as a\n checkpoint that could not be compared, and under `fail` it fails the step.\n Both severities appear in Visual results.\n\n Dynamic text is masked automatically. If the AI heals a difference, it saves\n an element mask and its reason on the checkpoint. The review shows the heal;\n a reviewer can revert the mask or mark the change as a regression. Masks are\n automatic or healed; never author mask rules yourself.\n\nSTANDALONE VISUAL REGRESSION SPECS\n\n These complete `.cofactor.md` examples use the current spec grammar. Replace\n the illustrative seed name, persona, and routes with the published seed,\n persona, and paths used by your application. In each, prepare one meaningful\n state and capture it. Sandboxes v2 supplies the run.\n\n Seed a populated invoice list, then checkpoint it:\n\n ---\n name: Invoice list visual regression\n actor: admin\n authenticated: true\n ---\n\n ## Spirit\n > An admin can see the seeded invoices and their payment status.\n\n ## Journey\n\n **Seed** invoice-list-state\n\n **Navigate to** `/invoices`\n\n **Visual checkpoint \u2014 Populated invoice list**\n - target: the invoices table\n - expected: Three invoices are listed, each marked Paid, with no error banner.\n - severity: fail\n\n **End**.\n\n Submit invalid form data and checkpoint the validation state:\n\n ---\n name: Signup validation visual regression\n authenticated: false\n ---\n\n ## Spirit\n > A visitor sees clear validation feedback when required signup fields are missing.\n\n ## Journey\n\n **Navigate to** `/signup`\n\n **Action \u2014 Submit the empty signup form**\n > Submit without entering any values so the required-field messages appear.\n - assert (fail): A required-field validation message is visible.\n\n **Visual checkpoint \u2014 Required-field validation**\n - target: the signup form\n - expected: Required fields have clear validation messages and the form has not been submitted.\n - severity: record\n\n **End**.\n\nPLAYWRIGHT ATTACHMENT CHECKPOINTS\n\n In an external Playwright suite running on Sandboxes v2, an attached PNG is a\n visual checkpoint:\n\n await testInfo.attach(name, { body: await page.screenshot({ mask }), contentType: 'image/png' })\n\n Its identity is the spec file, test title, and attachment name. Keep masking\n in the test code with Playwright's `mask` option. The service explains and\n reports Playwright changes but never fails the Playwright test. Failure\n screenshots, trace frames, and `toHaveScreenshot` actuals are excluded. Do\n not use `toHaveScreenshot` for this feature; attach the screenshot instead.\n\nREVIEW RESULTS\n\n Review changes and comparisons on the Visual page. From the CLI, use\n `cofactor visual list` to find captures. Mark or clear a regression with\n `cofactor visual regression <captureId>` or\n `cofactor visual regression <captureId> --clear`.\n";
2443
+ var visual_checkpoints_default = "Visual checkpoints capture a meaningful screen state and compare it with the\nrolling baseline for that checkpoint. Add one checkpoint at each state whose\nappearance matters to the user; avoid taking several screenshots of the same\nstate just because the journey has several steps.\n\nA pull request that changes how a screen looks needs one of these as evidence:\nsee `cofactor proofs guide`.\n\nNATIVE WORKFLOW CHECKPOINTS\n\n First ask: where must this test run?\n - Sandbox only: a SQL fixture is fine. This is the usual case for a visual\n regression test comparing an exact state at a commit.\n - Also against staging or prod: seed only through the app's API, generic\n API-backed actions, or the test's own UI steps. Only a sandbox exposes its\n database. Keep checkpoints on regions that do not depend on exact data,\n or rely on masking (automatic/healed for native checkpoints; see below).\n A test that loads SQL is sandbox-only.\n\n Seed the chosen state first, then capture it.\n Choose among three kinds of test data:\n - Seeds: shared, thin, generic base data nearly every test needs.\n - Actions: typed, parameterized, generic operations; prefer the app's API.\n - Visual-state fixtures: one SQL file per screen state, kept beside its test.\n Use fixed, neutral values and timestamps relative to now() (for example,\n now() - interval '2 days') so relative-time text stays stable. Fixtures are\n never parameterized or shared; do not build a library of per-test seeds.\n\n SQL fixtures are the default for sandbox-only visual data, only inserting\n rows with no encryption and no side effects. For encrypted rows or side\n effects, use the app's API or a generic action that calls it. Use UI steps for\n interaction states such as an open menu or validation feedback. A visual test\n prepares one meaningful state and takes its checkpoint.\n\n The `target` is optional plain English describing an element, such as\n `the invoices table`; omit it to capture the whole page. Never put a CSS,\n XPath, or Playwright selector in `target`.\n\n `expected` is optional plain language describing the correct visible state.\n It guides the AI in deciding whether the current state is correct and whether\n a visual change is real or can be healed. Use a specific observable\n description, for example, \"Three invoices are listed, each marked Paid, with\n no error banner.\"\n\n `record` is the default. It records visual findings without changing the\n workflow result, and compares after the suite finishes. Choose `fail` when an\n unresolved visual change should fail this workflow step; it compares inline.\n Under either severity, a `target` that cannot be found is reported as a\n checkpoint that could not be compared, and under `fail` it fails the step.\n Both severities appear in Visual results.\n\n Dynamic text is masked automatically. If the AI heals a difference, it saves\n an element mask and its reason on the checkpoint. The review shows the heal;\n a reviewer can revert the mask or mark the change as a regression. Masks are\n automatic or healed; never author mask rules yourself.\n\nBEFORE YOUR FIRST VISUAL TEST\n\n For a sandbox-only SQL test, check cofactor.sandbox.yaml for a generic\n SQL-loading action. If absent, add one to the app's definition. For Postgres,\n this action loads a repo-relative .sql path from the claimed commit. Claim\n the commit containing both the test and its fixture. Install git and psql\n in the sandbox; DATABASE_URL must already name its sandbox database in env\n (or repository secrets). Use your app's database URL variable if different.\n\n actions:\n load-sql:\n description: Load a test's SQL fixture into the sandbox database.\n run: sandbox/actions/load-sql.sh\n input:\n path: { type: string, required: true, description: Repo-relative .sql file }\n output:\n ok: { type: boolean }\n\n # sandbox/actions/load-sql.sh\n #!/usr/bin/env bash\n set -euo pipefail\n git show \"HEAD:$INPUT_PATH\" | \\\n psql \"$DATABASE_URL\" -X --set=ON_ERROR_STOP=1 --single-transaction >&2\n printf '{\"ok\":true}\\n'\n\n The generic loader is shared; each screen's fixture belongs only to its test.\n SQL errors fail the action; success is the last JSON line on stdout.\n\nSTANDALONE VISUAL REGRESSION SPECS\n\n These complete `.cofactor.md` examples use the current spec grammar. Replace\n the fixture path, persona, and routes with your application's. Prepare one\n meaningful state and capture it. Sandboxes v2 supplies the run.\n\n Keep invoice-list.sql beside invoice-list.cofactor.md. It inserts three Paid\n invoices with neutral, fixed values and dates relative to now(). Load it,\n then checkpoint the populated list:\n\n ---\n name: Invoice list visual regression\n actor: admin\n authenticated: true\n ---\n\n ## Spirit\n > An admin can see three invoices and their payment status.\n\n ## Journey\n\n **Sandbox action \u2014 load-sql**\n - param.path: tests/visual/invoice-list.sql\n\n **Navigate to** `/invoices`\n\n **Visual checkpoint \u2014 Populated invoice list**\n - target: the invoices table\n - expected: Three invoices are listed, each marked Paid, with no error banner.\n - severity: fail\n\n **End**.\n\n Submit invalid form data and checkpoint the validation state:\n\n ---\n name: Signup validation visual regression\n authenticated: false\n ---\n\n ## Spirit\n > A visitor sees clear validation feedback when required signup fields are missing.\n\n ## Journey\n\n **Navigate to** `/signup`\n\n **Action \u2014 Submit the empty signup form**\n > Submit without entering any values so the required-field messages appear.\n - assert (fail): A required-field validation message is visible.\n\n **Visual checkpoint \u2014 Required-field validation**\n - target: the signup form\n - expected: Required fields have clear validation messages and the form has not been submitted.\n - severity: record\n\n **End**.\n\nPLAYWRIGHT ATTACHMENT CHECKPOINTS\n\n In an external Playwright suite running on Sandboxes v2, an attached PNG is a\n visual checkpoint:\n\n await testInfo.attach(name, { body: await page.screenshot({ mask }), contentType: 'image/png' })\n\n Its identity is the spec file, test title, and attachment name. Keep masking\n in the test code with Playwright's `mask` option. The service explains and\n reports Playwright changes but never fails the Playwright test. Failure\n screenshots, trace frames, and `toHaveScreenshot` actuals are excluded. Do\n not use `toHaveScreenshot` for this feature; attach the screenshot instead.\n\nREVIEW RESULTS\n\n Review changes and comparisons on the Visual page. From the CLI, use\n `cofactor visual list` to find captures. Mark or clear a regression with\n `cofactor visual regression <captureId>` or\n `cofactor visual regression <captureId> --clear`.\n";
2444
2444
 
2445
2445
  // ../content/src/workflow-guide/index.ts
2446
2446
  var WORKFLOW_GUIDE_CONCEPT_SECTIONS = [
@@ -2561,4 +2561,4 @@ export {
2561
2561
  SANDBOX_V2_GUIDE_SECTIONS,
2562
2562
  printServedGuide
2563
2563
  };
2564
- //# sourceMappingURL=chunk-QHRWXCON.js.map
2564
+ //# sourceMappingURL=chunk-O7B2IYCZ.js.map