@runcofactor/cli 0.54.0 → 0.55.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -1
- package/dist/{account-6YJAU53I.js → account-RHHS7HT4.js} +5 -5
- package/dist/{alert-Z7V2ZUEW.js → alert-B5WEOKHP.js} +8 -8
- package/dist/{api-catalog-UEDTRM6E.js → api-catalog-UXONX3BR.js} +5 -5
- package/dist/{api-key-V5GEPRRG.js → api-key-J3A4TJKN.js} +5 -5
- package/dist/{attachment-M7VZ6GSE.js → attachment-ZQW7DR4G.js} +5 -5
- package/dist/{auth-DVTMWJKF.js → auth-HQGKUZBS.js} +3 -3
- package/dist/{benchmark-KZJZYUTQ.js → benchmark-TIKJA32J.js} +5 -5
- package/dist/{blackcat-QJXYDE66.js → blackcat-Z5LNV2GS.js} +3 -3
- package/dist/{chunk-7MRZWPVC.js → chunk-3KBYI6RR.js} +3 -3
- package/dist/{chunk-LPQTXBJF.js → chunk-3TZABC7D.js} +7 -7
- package/dist/{chunk-ZOPVBINJ.js → chunk-5OKCT6LF.js} +5 -5
- package/dist/{chunk-ZO5MSM4Q.js → chunk-635IQV6B.js} +2 -2
- package/dist/{chunk-4EWSONHO.js → chunk-6X6ZTDXP.js} +4 -4
- package/dist/{chunk-ZFN5FWN2.js → chunk-BJHAVM7Z.js} +2 -2
- package/dist/{chunk-Q7UAEFW5.js → chunk-CQSQNDMV.js} +4 -4
- package/dist/{chunk-AQOHNAEN.js → chunk-CZDQVQ57.js} +5 -5
- package/dist/{chunk-CV22445U.js → chunk-EGWVL5XA.js} +2 -2
- package/dist/{chunk-FLDIN6VX.js → chunk-JQG3V5E3.js} +2 -2
- package/dist/{chunk-KNWKOKM7.js → chunk-KL2ARGH6.js} +20 -11
- package/dist/chunk-KL2ARGH6.js.map +1 -0
- package/dist/{chunk-T6EQFICG.js → chunk-L4JQ236V.js} +4 -4
- package/dist/{chunk-2M4PZK6Q.js → chunk-MB4FS35J.js} +8 -8
- package/dist/{chunk-3YNR6U6Q.js → chunk-OU4I3MEH.js} +3 -3
- package/dist/{chunk-O7MBDOMO.js → chunk-QHRWXCON.js} +25 -9
- package/dist/chunk-QHRWXCON.js.map +1 -0
- package/dist/{chunk-HGEJHS45.js → chunk-QVMGXMP5.js} +5 -5
- package/dist/{chunk-G2SCV2CY.js → chunk-R2CRH6JQ.js} +3 -3
- package/dist/{chunk-HESV3F6U.js → chunk-RV7FZ4ID.js} +3 -3
- package/dist/{chunk-7VBX2M4F.js → chunk-SCQVBLNR.js} +3 -3
- package/dist/{chunk-74TU6PGO.js → chunk-TGI2AAOZ.js} +4 -4
- package/dist/{chunk-R2CVV7QT.js → chunk-U3MRXQZS.js} +5 -1
- package/dist/chunk-U3MRXQZS.js.map +1 -0
- package/dist/{chunk-L4Y5PZUE.js → chunk-W6DL4IP6.js} +2 -2
- package/dist/{chunk-TRC63RSK.js → chunk-WWUEKQLR.js} +3 -3
- package/dist/{chunk-MQQCUU5S.js → chunk-XM3WTJ7Y.js} +2 -2
- package/dist/{chunk-YACDH6MJ.js → chunk-XWJJYZQC.js} +2 -2
- package/dist/{chunk-QGP67JOK.js → chunk-YEH7UIUY.js} +4 -4
- package/dist/{chunk-RA2NRAWU.js → chunk-YTXZUWMN.js} +6 -6
- package/dist/{chunk-JAIMLQOU.js → chunk-Z72GWGH2.js} +6 -6
- package/dist/{chunk-QYHJUHL7.js → chunk-ZH7HWVEE.js} +2 -2
- package/dist/{chunk-IVWWYKAM.js → chunk-ZZZ33KTO.js} +3 -3
- package/dist/{credential-546ER7WQ.js → credential-2IEZDTH3.js} +5 -5
- package/dist/{credential-template-BWMEBGBN.js → credential-template-S5WSOI3P.js} +5 -5
- package/dist/{digest-NIVBDDI2.js → digest-HQGL7H6E.js} +8 -8
- package/dist/{docs-AK4MDP6K.js → docs-CJQZVERZ.js} +5 -5
- package/dist/{doctor-NCLA47EL.js → doctor-EGK5AB77.js} +3 -3
- package/dist/{environment-X3PV5IN2.js → environment-MWVXYEDU.js} +7 -7
- package/dist/{eval-5FZKYZ6O.js → eval-F4NORBQI.js} +9 -9
- package/dist/{exploration-2RVGIUKT.js → exploration-JC7R7KTJ.js} +5 -5
- package/dist/{exploratory-test-SJ5HFI3L.js → exploratory-test-BHK5FTMJ.js} +13 -5
- package/dist/exploratory-test-BHK5FTMJ.js.map +1 -0
- package/dist/{external-run-4NDVAHQB.js → external-run-UL5CKG54.js} +9 -9
- package/dist/{feature-flag-C4IRE724.js → feature-flag-NMO25TZN.js} +5 -5
- package/dist/{feedback-337OZCKC.js → feedback-3JNPSUXS.js} +6 -6
- package/dist/{flow-D7ZLBKCH.js → flow-AYE4RTZN.js} +5 -5
- package/dist/{github-LKXYIALH.js → github-DM2DCXRY.js} +6 -6
- package/dist/index.js +76 -76
- package/dist/{init-GKEC6LPH.js → init-CPDT5XAU.js} +5 -5
- package/dist/{install-K45SAT2V.js → install-JZRNCHMO.js} +5 -5
- package/dist/{issues-2HMTQYWR.js → issues-GRFJB52C.js} +8 -8
- package/dist/{knobs-KUCGLO5Z.js → knobs-3Y4QXWAD.js} +5 -5
- package/dist/{landing-KAET2I73.js → landing-ABYL5WVG.js} +7 -7
- package/dist/{list-GDV4UYWV.js → list-WG5QB4NK.js} +5 -5
- package/dist/{local-NFOSJLLV.js → local-KX24OR5Z.js} +7 -7
- package/dist/{login-Y77HWROY.js → login-MLJQAIFK.js} +5 -5
- package/dist/{mcp-CPCFMU6B.js → mcp-DDTO54XD.js} +10 -10
- package/dist/{metrics-Y245ST63.js → metrics-BJLLOSPQ.js} +5 -5
- package/dist/{monitor-BUDWSC3S.js → monitor-E2CKGHPM.js} +5 -5
- package/dist/{perf-3JOAXPHP.js → perf-IX7JOMUE.js} +11 -11
- package/dist/{product-CXL3YTMD.js → product-2ECWM2JN.js} +5 -5
- package/dist/{product-map-656BTU74.js → product-map-O542LZQL.js} +7 -7
- package/dist/{prompts-3SZ7O5E6.js → prompts-Y7O4XJ5P.js} +5 -5
- package/dist/{proofs-4CSCCSNL.js → proofs-TNOBLSRP.js} +9 -9
- package/dist/{property-IEKBXRVY.js → property-TZCGOZQG.js} +6 -6
- package/dist/{psql-M4CM6TSI.js → psql-M66YSUXW.js} +5 -5
- package/dist/{record-QP4TYZBS.js → record-3JINBRAR.js} +6 -6
- package/dist/{redis-KG47IGJV.js → redis-FOIBINYA.js} +5 -5
- package/dist/{release-QHPSQULK.js → release-B4VWMVEI.js} +5 -5
- package/dist/{runbook-YVBG2QK3.js → runbook-EYMSE37E.js} +5 -5
- package/dist/{sandbox-ZXDZTCCN.js → sandbox-R6EMGOKR.js} +16 -13
- package/dist/sandbox-R6EMGOKR.js.map +1 -0
- package/dist/{scenarios-2SX2NUCD.js → scenarios-W7YNLBVO.js} +5 -5
- package/dist/{schedule-P6F7MLXX.js → schedule-EDHQJUUZ.js} +8 -8
- package/dist/{session-SZMCZTPE.js → session-4N62APNQ.js} +9 -9
- package/dist/{skill-D4XFW6QI.js → skill-XFGMNOVM.js} +107 -9
- package/dist/skill-XFGMNOVM.js.map +1 -0
- package/dist/skills/cofactor-debug-run/SKILL.md +5 -1
- package/dist/skills/cofactor-debug-run/history/2.0.9.md +319 -0
- package/dist/skills/cofactor-debug-run/version.json +1 -1
- package/dist/{slack-UDXTPOSW.js → slack-RUAXG6TB.js} +6 -6
- package/dist/{spec-YCBBIV27.js → spec-ZZKMW7ER.js} +5 -5
- package/dist/{src-ZKVNVLXY.js → src-CQ2YAW7F.js} +3 -3
- package/dist/{src-NG7DRE5A.js → src-DGIVA22K.js} +3 -3
- package/dist/{src-DEGBASCD.js → src-ECZWYNSV.js} +5 -5
- package/dist/{src-DY7GLRO2.js → src-OKSAEHQS.js} +4 -4
- package/dist/{start-ZYZSORPY.js → start-OCZGILRL.js} +5 -5
- package/dist/{status-ESIG6A7W.js → status-YTJ6YITG.js} +5 -5
- package/dist/{tag-3K5Y25AR.js → tag-KBG4SWRL.js} +6 -6
- package/dist/{task-JLNX7O3M.js → task-O6XRMHYE.js} +6 -6
- package/dist/{teams-F4IU3EXE.js → teams-NFUHS7XI.js} +7 -7
- package/dist/{test-suite-QHDUW5W5.js → test-suite-6BH3DESN.js} +12 -12
- package/dist/{tickets-NBL5WACL.js → tickets-JU2STK4B.js} +5 -5
- package/dist/{trace-ZKYAI5JF.js → trace-NMSGKASJ.js} +8 -8
- package/dist/{triage-5NWCREUM.js → triage-JGECJJU5.js} +6 -6
- package/dist/{trigger-KXYYY5IR.js → trigger-OSPEVCCQ.js} +9 -9
- package/dist/{update-check-Q2URVJLC.js → update-check-FNP6RFZO.js} +4 -4
- package/dist/{upgrade-4XSASC7U.js → upgrade-3PPBXFAT.js} +5 -5
- package/dist/{usage-UY7VZYVY.js → usage-QMCU2JIO.js} +7 -7
- package/dist/{utilities-53NXNLA7.js → utilities-7VHWTSJT.js} +6 -6
- package/dist/{visual-IJAT7X7D.js → visual-WIZSPTVK.js} +5 -5
- package/dist/{workflow-OQNH2FIS.js → workflow-23HKTJLS.js} +62 -33
- package/dist/workflow-23HKTJLS.js.map +1 -0
- package/dist/{workflow-cache-FP6MKLVR.js → workflow-cache-ACGM3BOI.js} +16 -16
- package/dist/{workflow-cache-convergence-resolve-HS5KO7US.js → workflow-cache-convergence-resolve-KEQDQMKL.js} +5 -5
- package/dist/{workflow-debug-EM55H2QE.js → workflow-debug-TVLBNRCB.js} +11 -11
- package/dist/{workflow-feedback-GNWNWVHH.js → workflow-feedback-GB5VPGE2.js} +5 -5
- package/dist/{workflow-fork-M5AQWST7.js → workflow-fork-2IPJNR76.js} +5 -5
- package/dist/{workflow-promotion-HYULZDYL.js → workflow-promotion-QEQUXVUT.js} +5 -5
- package/dist/{workflow-proposals-EN327FSE.js → workflow-proposals-KHGTPMD4.js} +5 -5
- package/dist/{workflow-revisions-ZMTVNWRW.js → workflow-revisions-F5KQEEXB.js} +5 -5
- package/dist/{workflow-status-FNAJG34W.js → workflow-status-66HC5MEA.js} +5 -5
- package/package.json +1 -1
- package/dist/chunk-KNWKOKM7.js.map +0 -1
- package/dist/chunk-O7MBDOMO.js.map +0 -1
- package/dist/chunk-R2CVV7QT.js.map +0 -1
- package/dist/exploratory-test-SJ5HFI3L.js.map +0 -1
- package/dist/sandbox-ZXDZTCCN.js.map +0 -1
- package/dist/skill-D4XFW6QI.js.map +0 -1
- package/dist/workflow-OQNH2FIS.js.map +0 -1
- /package/dist/{account-6YJAU53I.js.map → account-RHHS7HT4.js.map} +0 -0
- /package/dist/{alert-Z7V2ZUEW.js.map → alert-B5WEOKHP.js.map} +0 -0
- /package/dist/{api-catalog-UEDTRM6E.js.map → api-catalog-UXONX3BR.js.map} +0 -0
- /package/dist/{api-key-V5GEPRRG.js.map → api-key-J3A4TJKN.js.map} +0 -0
- /package/dist/{attachment-M7VZ6GSE.js.map → attachment-ZQW7DR4G.js.map} +0 -0
- /package/dist/{auth-DVTMWJKF.js.map → auth-HQGKUZBS.js.map} +0 -0
- /package/dist/{benchmark-KZJZYUTQ.js.map → benchmark-TIKJA32J.js.map} +0 -0
- /package/dist/{blackcat-QJXYDE66.js.map → blackcat-Z5LNV2GS.js.map} +0 -0
- /package/dist/{chunk-7MRZWPVC.js.map → chunk-3KBYI6RR.js.map} +0 -0
- /package/dist/{chunk-LPQTXBJF.js.map → chunk-3TZABC7D.js.map} +0 -0
- /package/dist/{chunk-ZOPVBINJ.js.map → chunk-5OKCT6LF.js.map} +0 -0
- /package/dist/{chunk-ZO5MSM4Q.js.map → chunk-635IQV6B.js.map} +0 -0
- /package/dist/{chunk-4EWSONHO.js.map → chunk-6X6ZTDXP.js.map} +0 -0
- /package/dist/{chunk-ZFN5FWN2.js.map → chunk-BJHAVM7Z.js.map} +0 -0
- /package/dist/{chunk-Q7UAEFW5.js.map → chunk-CQSQNDMV.js.map} +0 -0
- /package/dist/{chunk-AQOHNAEN.js.map → chunk-CZDQVQ57.js.map} +0 -0
- /package/dist/{chunk-CV22445U.js.map → chunk-EGWVL5XA.js.map} +0 -0
- /package/dist/{chunk-FLDIN6VX.js.map → chunk-JQG3V5E3.js.map} +0 -0
- /package/dist/{chunk-T6EQFICG.js.map → chunk-L4JQ236V.js.map} +0 -0
- /package/dist/{chunk-2M4PZK6Q.js.map → chunk-MB4FS35J.js.map} +0 -0
- /package/dist/{chunk-3YNR6U6Q.js.map → chunk-OU4I3MEH.js.map} +0 -0
- /package/dist/{chunk-HGEJHS45.js.map → chunk-QVMGXMP5.js.map} +0 -0
- /package/dist/{chunk-G2SCV2CY.js.map → chunk-R2CRH6JQ.js.map} +0 -0
- /package/dist/{chunk-HESV3F6U.js.map → chunk-RV7FZ4ID.js.map} +0 -0
- /package/dist/{chunk-7VBX2M4F.js.map → chunk-SCQVBLNR.js.map} +0 -0
- /package/dist/{chunk-74TU6PGO.js.map → chunk-TGI2AAOZ.js.map} +0 -0
- /package/dist/{chunk-L4Y5PZUE.js.map → chunk-W6DL4IP6.js.map} +0 -0
- /package/dist/{chunk-TRC63RSK.js.map → chunk-WWUEKQLR.js.map} +0 -0
- /package/dist/{chunk-MQQCUU5S.js.map → chunk-XM3WTJ7Y.js.map} +0 -0
- /package/dist/{chunk-YACDH6MJ.js.map → chunk-XWJJYZQC.js.map} +0 -0
- /package/dist/{chunk-QGP67JOK.js.map → chunk-YEH7UIUY.js.map} +0 -0
- /package/dist/{chunk-RA2NRAWU.js.map → chunk-YTXZUWMN.js.map} +0 -0
- /package/dist/{chunk-JAIMLQOU.js.map → chunk-Z72GWGH2.js.map} +0 -0
- /package/dist/{chunk-QYHJUHL7.js.map → chunk-ZH7HWVEE.js.map} +0 -0
- /package/dist/{chunk-IVWWYKAM.js.map → chunk-ZZZ33KTO.js.map} +0 -0
- /package/dist/{credential-546ER7WQ.js.map → credential-2IEZDTH3.js.map} +0 -0
- /package/dist/{credential-template-BWMEBGBN.js.map → credential-template-S5WSOI3P.js.map} +0 -0
- /package/dist/{digest-NIVBDDI2.js.map → digest-HQGL7H6E.js.map} +0 -0
- /package/dist/{docs-AK4MDP6K.js.map → docs-CJQZVERZ.js.map} +0 -0
- /package/dist/{doctor-NCLA47EL.js.map → doctor-EGK5AB77.js.map} +0 -0
- /package/dist/{environment-X3PV5IN2.js.map → environment-MWVXYEDU.js.map} +0 -0
- /package/dist/{eval-5FZKYZ6O.js.map → eval-F4NORBQI.js.map} +0 -0
- /package/dist/{exploration-2RVGIUKT.js.map → exploration-JC7R7KTJ.js.map} +0 -0
- /package/dist/{external-run-4NDVAHQB.js.map → external-run-UL5CKG54.js.map} +0 -0
- /package/dist/{feature-flag-C4IRE724.js.map → feature-flag-NMO25TZN.js.map} +0 -0
- /package/dist/{feedback-337OZCKC.js.map → feedback-3JNPSUXS.js.map} +0 -0
- /package/dist/{flow-D7ZLBKCH.js.map → flow-AYE4RTZN.js.map} +0 -0
- /package/dist/{github-LKXYIALH.js.map → github-DM2DCXRY.js.map} +0 -0
- /package/dist/{init-GKEC6LPH.js.map → init-CPDT5XAU.js.map} +0 -0
- /package/dist/{install-K45SAT2V.js.map → install-JZRNCHMO.js.map} +0 -0
- /package/dist/{issues-2HMTQYWR.js.map → issues-GRFJB52C.js.map} +0 -0
- /package/dist/{knobs-KUCGLO5Z.js.map → knobs-3Y4QXWAD.js.map} +0 -0
- /package/dist/{landing-KAET2I73.js.map → landing-ABYL5WVG.js.map} +0 -0
- /package/dist/{list-GDV4UYWV.js.map → list-WG5QB4NK.js.map} +0 -0
- /package/dist/{local-NFOSJLLV.js.map → local-KX24OR5Z.js.map} +0 -0
- /package/dist/{login-Y77HWROY.js.map → login-MLJQAIFK.js.map} +0 -0
- /package/dist/{mcp-CPCFMU6B.js.map → mcp-DDTO54XD.js.map} +0 -0
- /package/dist/{metrics-Y245ST63.js.map → metrics-BJLLOSPQ.js.map} +0 -0
- /package/dist/{monitor-BUDWSC3S.js.map → monitor-E2CKGHPM.js.map} +0 -0
- /package/dist/{perf-3JOAXPHP.js.map → perf-IX7JOMUE.js.map} +0 -0
- /package/dist/{product-CXL3YTMD.js.map → product-2ECWM2JN.js.map} +0 -0
- /package/dist/{product-map-656BTU74.js.map → product-map-O542LZQL.js.map} +0 -0
- /package/dist/{prompts-3SZ7O5E6.js.map → prompts-Y7O4XJ5P.js.map} +0 -0
- /package/dist/{proofs-4CSCCSNL.js.map → proofs-TNOBLSRP.js.map} +0 -0
- /package/dist/{property-IEKBXRVY.js.map → property-TZCGOZQG.js.map} +0 -0
- /package/dist/{psql-M4CM6TSI.js.map → psql-M66YSUXW.js.map} +0 -0
- /package/dist/{record-QP4TYZBS.js.map → record-3JINBRAR.js.map} +0 -0
- /package/dist/{redis-KG47IGJV.js.map → redis-FOIBINYA.js.map} +0 -0
- /package/dist/{release-QHPSQULK.js.map → release-B4VWMVEI.js.map} +0 -0
- /package/dist/{runbook-YVBG2QK3.js.map → runbook-EYMSE37E.js.map} +0 -0
- /package/dist/{scenarios-2SX2NUCD.js.map → scenarios-W7YNLBVO.js.map} +0 -0
- /package/dist/{schedule-P6F7MLXX.js.map → schedule-EDHQJUUZ.js.map} +0 -0
- /package/dist/{session-SZMCZTPE.js.map → session-4N62APNQ.js.map} +0 -0
- /package/dist/{slack-UDXTPOSW.js.map → slack-RUAXG6TB.js.map} +0 -0
- /package/dist/{spec-YCBBIV27.js.map → spec-ZZKMW7ER.js.map} +0 -0
- /package/dist/{src-ZKVNVLXY.js.map → src-CQ2YAW7F.js.map} +0 -0
- /package/dist/{src-DEGBASCD.js.map → src-DGIVA22K.js.map} +0 -0
- /package/dist/{src-DY7GLRO2.js.map → src-ECZWYNSV.js.map} +0 -0
- /package/dist/{src-NG7DRE5A.js.map → src-OKSAEHQS.js.map} +0 -0
- /package/dist/{start-ZYZSORPY.js.map → start-OCZGILRL.js.map} +0 -0
- /package/dist/{status-ESIG6A7W.js.map → status-YTJ6YITG.js.map} +0 -0
- /package/dist/{tag-3K5Y25AR.js.map → tag-KBG4SWRL.js.map} +0 -0
- /package/dist/{task-JLNX7O3M.js.map → task-O6XRMHYE.js.map} +0 -0
- /package/dist/{teams-F4IU3EXE.js.map → teams-NFUHS7XI.js.map} +0 -0
- /package/dist/{test-suite-QHDUW5W5.js.map → test-suite-6BH3DESN.js.map} +0 -0
- /package/dist/{tickets-NBL5WACL.js.map → tickets-JU2STK4B.js.map} +0 -0
- /package/dist/{trace-ZKYAI5JF.js.map → trace-NMSGKASJ.js.map} +0 -0
- /package/dist/{triage-5NWCREUM.js.map → triage-JGECJJU5.js.map} +0 -0
- /package/dist/{trigger-KXYYY5IR.js.map → trigger-OSPEVCCQ.js.map} +0 -0
- /package/dist/{update-check-Q2URVJLC.js.map → update-check-FNP6RFZO.js.map} +0 -0
- /package/dist/{upgrade-4XSASC7U.js.map → upgrade-3PPBXFAT.js.map} +0 -0
- /package/dist/{usage-UY7VZYVY.js.map → usage-QMCU2JIO.js.map} +0 -0
- /package/dist/{utilities-53NXNLA7.js.map → utilities-7VHWTSJT.js.map} +0 -0
- /package/dist/{visual-IJAT7X7D.js.map → visual-WIZSPTVK.js.map} +0 -0
- /package/dist/{workflow-cache-FP6MKLVR.js.map → workflow-cache-ACGM3BOI.js.map} +0 -0
- /package/dist/{workflow-cache-convergence-resolve-HS5KO7US.js.map → workflow-cache-convergence-resolve-KEQDQMKL.js.map} +0 -0
- /package/dist/{workflow-debug-EM55H2QE.js.map → workflow-debug-TVLBNRCB.js.map} +0 -0
- /package/dist/{workflow-feedback-GNWNWVHH.js.map → workflow-feedback-GB5VPGE2.js.map} +0 -0
- /package/dist/{workflow-fork-M5AQWST7.js.map → workflow-fork-2IPJNR76.js.map} +0 -0
- /package/dist/{workflow-promotion-HYULZDYL.js.map → workflow-promotion-QEQUXVUT.js.map} +0 -0
- /package/dist/{workflow-proposals-EN327FSE.js.map → workflow-proposals-KHGTPMD4.js.map} +0 -0
- /package/dist/{workflow-revisions-ZMTVNWRW.js.map → workflow-revisions-F5KQEEXB.js.map} +0 -0
- /package/dist/{workflow-status-FNAJG34W.js.map → workflow-status-66HC5MEA.js.map} +0 -0
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../content/src/sandbox-guide/build-caching.txt","../../content/src/sandbox-guide.ts","../../content/src/external-run-guide/overview.txt","../../content/src/external-run-guide/materialize.txt","../../content/src/external-run-guide/results.txt","../../content/src/external-run-guide/classification.txt","../../content/src/external-run-guide/history.txt","../../content/src/external-run-guide/skips.txt","../../content/src/external-run-guide/lifecycle.txt","../../content/src/external-run-guide/reruns.txt","../../content/src/external-run-guide/reporting.txt","../../content/src/external-run-guide/ai-findings.txt","../../content/src/external-run-guide/index.ts","../../content/src/issues-guide/overview.txt","../../content/src/issues-guide/dedupe-and-close.txt","../../content/src/issues-guide/logs.txt","../../content/src/issues-guide/handoff.txt","../../content/src/issues-guide/index.ts","../../content/src/proofs-guide/overview.txt","../../content/src/proofs-guide/index.ts","../../content/src/perf-guide/overview.txt","../../content/src/perf-guide/index.ts","../../content/src/eval-guide/overview.txt","../../content/src/eval-guide/index.ts","../../content/src/session-guide/start.txt","../../content/src/session-guide/interact.txt","../../content/src/session-guide/sign-in.txt","../../content/src/session-guide/element-discovery.txt","../../content/src/session-guide/troubleshooting.txt","../../content/src/session-guide/index.ts","../../content/src/control-characters.ts","../src/brand.ts"],"sourcesContent":["A cold sandbox launch otherwise reruns the slow build work on every provision:\nthe Docker image build (the browser/toolchain install layer), `bun install`, and\nthe production web build. The platform removes that cost by capturing known-good\noutputs at VERIFICATION time and restoring them on every later provision. Most of\nit is automatic; two things are your job: opt one-shot build services in with\nlabels, and keep their outputs instance-independent.\n\nWHAT THE PLATFORM DOES FOR YOU (no action needed)\n - Prebuilt image: when a template verifies, the built image is pushed to a\n registry and PULLED on later provisions instead of rebuilt. Your services keep\n both `build:` and an injected `image:` — `--build` is skipped on a hit and a\n pull miss falls back to building. Nothing to declare.\n\nWHAT YOU DECLARE — cache labels on one-shot build/dep services\n Same convention as the sandbox.route/sandbox.port labels. The platform snapshots\n the declared volumes/paths at verify and restores them at provision. How a HIT is\n reused depends on the service's REUSE MODE (auto-detected, override with\n sandbox.cache-reuse):\n - prime (dependency installs — a service caching a named VOLUME): the restore\n WARMS the install, but the command STILL RUNS. This is required: a package\n manager in a workspace monorepo writes a node_modules into EVERY package\n (apps/web/node_modules, packages/*/node_modules) that lives in the workspace,\n not in the one cached volume — so skipping the install on a hit leaves the tree\n incomplete and the app crashes. Re-running install warm rebuilds the COMPLETE\n tree. It is fast: the platform auto-provisions and caches the package manager's\n global store for you (see below).\n - skip (build outputs — a service caching only PATHS): the cache fully captures\n the output, so on a hit a marker file makes the one-shot a no-op (skips the\n rebuild). A miss runs cold and the next verify re-captures.\n\n services:\n deps: # dependency install → cache the volume (prime)\n labels:\n sandbox.cache: \"true\"\n sandbox.cache-key: \"lockfiles\" # invalidate when a lockfile changes\n sandbox.cache-volumes: \"node_modules\" # named volume(s) to snapshot\n web-build: # production build → cache the output dirs (skip)\n labels:\n sandbox.cache: \"true\"\n sandbox.cache-key: \"inputs\"\n sandbox.cache-inputs: \"apps/web,packages/ui,bun.lock\" # source the build reads\n sandbox.cache-paths: \"apps/web/dist,packages/ui/dist\" # output dirs to snapshot\n\n Labels:\n sandbox.cache: \"true\" Opt this service in.\n sandbox.cache-key How the cache is invalidated:\n \"lockfiles\" — hash of the repo's lock manifests (bun.lock, package-lock.json,\n yarn.lock, pnpm-lock.yaml). Use for dependency installs.\n \"inputs\" — content hash of the sandbox.cache-inputs paths (node_modules\n and dist excluded). Use for BUILDS. This is what makes the\n cache useful on a branch that moves: a commit that does not\n touch the build's inputs keeps the key → hit; one that does →\n miss → rebuild. (Default: inputs when cache-inputs is set,\n else lockfiles.)\n sandbox.cache-inputs Comma-separated source paths the build consumes\n (required for \"inputs\"). List the app dir plus every\n workspace package it builds, plus the lockfile.\n sandbox.cache-volumes Comma-separated named volumes to snapshot/restore.\n sandbox.cache-paths Comma-separated workspace-relative output dirs to\n snapshot/restore.\n sandbox.cache-reuse \"prime\" | \"skip\". Optional — defaults to prime for a\n service that caches a volume (install) and skip for a\n paths-only service (build output). Only override if\n the auto-detection is wrong for your service.\n All path/volume values must be plain, relative, traversal-free names — absolute\n paths, \"..\"/empty segments, or shell metacharacters are rejected (fail-closed).\n\n NEVER key a build on the git commit: a moving branch would invalidate it on every\n unrelated push (an api-only or docs commit) and the cache would never hit. Key on\n the build's actual INPUTS.\n\nPACKAGE-MANAGER STORE IS AUTOMATIC (do not configure it)\n For a prime install service the platform sets the cache/store env vars for every\n common package manager (bun, npm, yarn, pnpm) to a shared, cached directory\n co-located with node_modules, and snapshots it. So the warm re-run of your install\n is offline and fast for ANY package manager with nothing to declare. Do NOT set\n BUN_INSTALL_CACHE_DIR / npm_config_cache / YARN_CACHE_FOLDER / store-dir yourself —\n the platform owns them. Just label the install service sandbox.cache: \"true\".\n\nKEEP A SKIP SERVICE'S COMMAND ON ONE LINE\n For a skip (build-output) service the platform wraps its `command:` in a marker\n gate so a hit skips the build. It rewrites a single-line command — scalar\n (`command: bun run build`) or flow array (`command: [\"bash\",\"-c\",\"...\"]`). A\n multi-line block command is left ungated (still gets capture/restore but always\n runs). Keep it one line. (Prime install commands always run, so this does not\n apply to them.)\n\nINSTANCE-INDEPENDENCE (required for cached build outputs)\n A cached `dist` is reused across DIFFERENT sandboxes, so it must not bake any\n per-sandbox value. The common trap is baking the API URL at build time\n (VITE_API_URL=${SANDBOX_URL}/api). Instead resolve the API origin at RUNTIME from\n the browser location — web and api are the same origin behind the sandbox nginx\n proxy, so a relative `/api` works on every sandbox. If you must bake a value, bake\n a fixed placeholder and rewrite it at provision; never bake ${SANDBOX_URL} into a\n cached output. Outputs that embed a per-sandbox value cannot be shared and will\n serve the wrong sandbox's URL.\n\nDIAGNOSING A MISS\n Every provision logs one line per cacheable service:\n [CACHE] restore svc=web-build outcome=miss reason=miss-path key=a1b2c3d4 ms=140\n outcome is hit / prime / miss; reason names what stopped it (no-key, miss-path,\n miss-volume, extract-failed, no-mountpoint); key is the cache key that provision\n computed. The\n capture side logs the key it WROTE under ([CACHE] capture svc=... key=...), so two\n different keys means the inputs moved between capture and restore — usually a\n cache-inputs path listing something that changes on every commit.\n\nREFRESHING THE CACHE\n Re-verify the template to re-capture (`canary sandbox verify <template-id>`).\n Editing artifacts already forces a re-verify. A miss is always safe — it just\n builds cold that one time.\n","// Sandbox authoring guide — printed by `cofactor sandbox guide v1 [section]`\n// and also rendered (in part) in the web UI on the sandbox detail page.\n\nimport type { GuideSection } from './types.js';\nimport buildCaching from './sandbox-guide/build-caching.txt';\n\nconst GOAL: GuideSection = {\n title: 'Goal & Tools',\n slug: 'goal',\n content: `\nYour goal is to turn a git repository into a running, containerized development\nenvironment. The end state is a sandbox URL that loads the app in a browser.\n\nWORKFLOW:\n 1. canary sandbox create <property> Create a template + authoring instance\n 2. Research the app Read the codebase to understand what it needs\n 3. Set config values API keys, secrets → pushed to instance as .canary/.env\n 4. Upload artifacts Dockerfile, docker-compose, setup script, etc. → pushed to instance\n 5. canary sandbox build <instance-id> Build images, start containers, generate nginx\n 6. Check diagnostics & logs Verify the app is running\n 7. Fix artifacts, re-upload, re-build Iterate until the app loads in browser\n 8. Mark checklist items complete Record what you did\n\nEach artifact upload and config set immediately pushes the file to the instance.\nThe \"build\" command triggers the actual rebuild and restart.\nBatch your changes (upload all artifacts, set all config), then build once.\n\nYOUR TOOLS:\n\n Config — set environment variables (pushed to instance .canary/.env immediately):\n canary sandbox config set <template-id> <key> <value>\n canary sandbox config list <template-id>\n\n SANDBOX_URL is auto-injected — use it in compose for browser-facing URLs.\n SANDBOX_LLM_PROXY_URL + SANDBOX_LLM_PROXY_TOKEN are auto-injected too: point an\n OpenAI-compatible client at \\${SANDBOX_LLM_PROXY_URL}/azure/v1 with the token as\n its API key and the app shares the platform's model account — no vendor key in config.\n\n Artifacts — files that define the sandbox (pushed to instance on upload):\n canary sandbox artifact create <template-id> <type> <file>\n canary sandbox artifact get <template-id> <type>\n\n Types: dockerfile, docker-compose, setup-script, startup-script, seed-script, file\n\n Build — rebuild and start services:\n canary sandbox build <instance-id>\n canary sandbox build <instance-id> --no-wait\n\n Generates nginx routing, injects Docker networking, writes file artifacts,\n runs setup script, then runs compose up, then runs startup script.\n Call this after uploading artifacts.\n\n First builds on large projects may exceed gateway timeouts. The CLI handles\n this automatically — it detects the timeout and polls diagnostics until\n containers are healthy. Use --no-wait to fire-and-forget and poll manually.\n\n Diagnostics & logs — check container health and read service output:\n canary sandbox diagnostics <instance-id>\n canary sandbox logs <instance-id> --service <name>\n\n Run-command — escape hatch for debugging and one-off commands:\n canary sandbox run-command <instance-id> \"<command>\"\n\n Checklist — record your progress:\n canary sandbox checklist <template-id>\n canary sandbox checklist complete <template-id> <item-id> [options]\n canary sandbox checklist skip <template-id> <item-id>\n\nPRINCIPLES:\n - Artifacts are the source of truth. Every fix must update the artifact file and re-upload.\n - Never patch source files. Use Docker env vars, CMD overrides, setup/startup scripts.\n - Code is volume-mounted at /workspace, not baked into the image.\n - The platform handles nginx routing and Docker networking — you don't need to.\n`.trim(),\n};\n\nconst RESEARCH: GuideSection = {\n title: 'Research the App',\n slug: 'research',\n content: `\nBefore writing any artifacts, research the application thoroughly.\n\n1. ENV VAR VALIDATION\n Find the app's environment validation schema (Zod, Joi, dotenv, etc.).\n List ALL required (non-optional) variables. Many apps fail fast on missing vars.\n\n Common locations:\n - src/env.ts, src/config.ts, src/environment.ts\n - .env.example, .env.sample\n\n Classify each var:\n - AUTO-WIRED: Internal service URLs (DATABASE_URL, REDIS_URL) — hardcode in compose\n - PLACEHOLDER: OAuth creds, S3 buckets — use dummy values, app boots without them\n - SENSITIVE: API keys (ANTHROPIC_API_KEY) — goes in template configSchema\n - DISABLED: Worker flags, schedulers — set to false in compose\n\n2. REQUIRED SERVICES\n What databases, caches, queues does the app need?\n Check: docker-compose.yml, package.json deps, infra/ configs, README\n Common: PostgreSQL, Redis, MongoDB, RabbitMQ, Elasticsearch\n\n3. MIGRATION STRATEGY\n Decide whether migrations can run BEFORE the app server boots.\n\n Prefer a first-class \\`migrate\\` compose service when the migration command\n can run in the app image without the app server already running:\n - Depend on the DB healthcheck and any deps/install one-shot.\n - Run the project-local ORM CLI with the same env/volumes as the app.\n - Set restart: \"no\".\n - Gate schema-reading app services on migrate with\n service_completed_successfully.\n\n This is the safest pattern for apps that read schema or knobs during boot:\n the API starts once, after the DB is already migrated.\n\n Use the startup script fallback only when the migration command must exec into\n an already-running app container or depends on runtime state that only exists\n after compose up:\n - Drizzle: sb-compose exec -T app-api bash -c 'cd /app/packages/db && bun run drizzle-kit migrate'\n - Prisma: sb-compose exec -T app-api bash -c 'bun run prisma migrate deploy'\n - Rails: sb-compose exec -T app-api rails db:migrate\n - Django: sb-compose exec -T app-api python manage.py migrate\n\n Do NOT use bunx for JS migrations — it downloads a fresh copy. Use \"bun run\" instead.\n\n4. PACKAGE BUILD ORDER (monorepos)\n Which workspace packages export from dist/ and must be pre-built?\n Check package.json \"main\" or \"exports\" fields for \"./dist/\" references.\n Prefer a one-shot build service (for example web-build) and gate services\n that import those dist packages on service_completed_successfully. Use\n startup.sh only as a fallback when the build must happen after compose up.\n Do NOT bake these outputs into the Dockerfile; source is volume-mounted.\n When running scripts via sb-compose exec, cd to the package directory where\n the dependency is declared. Bun workspace module resolution won't find deps\n from the monorepo root.\n\n5. PRODUCTION BUILD STRATEGY (frontends)\n Sandboxes serve a PRODUCTION build of the frontend, not a dev server.\n This is faster and far more responsive, ships compressed/hashed/cacheable\n assets, and sidesteps dev-server host-allowlist headaches entirely (a static\n file server has no notion of \"allowed hosts\"). HMR / hot-reload is\n intentionally dropped — a sandbox demos and QAs a BUILT app; it is not a\n live-edit environment.\n\n Identify, from package.json scripts and the framework:\n - The production build command (vite build, next build, ng build,\n webpack --mode production, etc.) and its output directory\n (commonly dist/, build/, or .next/).\n - Whether the framework produces a STATIC bundle (Vite, CRA, Angular,\n plain Webpack → a dist/ of static files) or needs a NODE SERVER at\n runtime (Next.js server mode, Remix, Nuxt SSR). Static bundles are\n served by nginx; server-rendered apps run their production start\n command (next start, etc.) as a normal routed service.\n\n BUILD-TIME ENV VARS:\n Bundlers BAKE build-time public vars into the output, so they must be set\n in the environment BEFORE the build runs — setting them only in compose\n at runtime is too late for a static bundle. Find every build-time-public\n var (VITE_*, NEXT_PUBLIC_*, REACT_APP_*, ng-injected config, etc.),\n especially the browser-facing API base URL, and set them just before the\n build using \\${SANDBOX_URL} for any URL the browser must reach\n (e.g. VITE_API_URL=\\${SANDBOX_URL}/api). See Artifacts → PRODUCTION BUILD\n for where the build runs.\n\n COST: production builds are slower and more memory-hungry than starting a\n dev server. Vite/webpack builds can OOM the default Node heap on large\n apps — be ready to raise it (NODE_OPTIONS=--max-old-space-size=8192) and\n expect the build to add minutes to the build step.\n\n6. WORKERS & DAEMONS TO DISABLE\n Sandbox typically only needs the API server + web frontend.\n Disable: job runners, schedulers, stall detectors, cron, coordinators.\n Look for env flags like WORKER_ENABLED, SCHEDULER_ENABLED, etc.\n`.trim(),\n};\n\nconst ARTIFACTS: GuideSection = {\n title: 'Artifacts',\n slug: 'artifacts',\n content: `\nArtifacts are the files that define your sandbox. Write them locally, then upload:\n canary sandbox artifact create <template-id> <type> <local-file>\n\nIMPORTANT — ARTIFACT SCOPE:\n Artifact uploads are TEMPLATE-level, not instance-level. Uploading an artifact\n updates the template definition and pushes the file to the AUTHORING instance\n only. Running instances provisioned from the template are NOT updated — they\n use whatever was baked in at provision time. To apply artifact changes to an\n existing non-authoring instance, you must rebuild it:\n canary sandbox build <instance-id>\n\nPLATFORM ARCHITECTURE:\n The sandbox filesystem is split into two areas:\n - /workspace checked-out repo and user-owned files\n - /workspace/.canary platform-managed compose, config, and helper scripts\n\n Your compose file is saved as /workspace/.canary/docker-compose.override.yml and\n merged with the platform's base compose at /workspace/.canary/docker-compose.yml,\n which provides:\n - nginx-proxy: reverse proxy on port 80 (routes based on sandbox.route labels)\n - sandbox-agent: platform agent on port 8080\n - minio: S3-compatible object store at http://minio:9000 (creds:\n minioadmin / minioadmin), with a sandbox-data bucket. See S3 BUCKET\n below — the platform doesn't wire the endpoint into your services.\n\n You do NOT need to declare any of these in your docker-compose.yml — the\n platform injects them. Defining a service with one of these reserved\n names is rejected at artifact-upload time with code\n SANDBOX_RESERVED_SERVICE_NAME. The full reserved list is:\n nginx-proxy\n sandbox-agent\n minio\n docker-socket-proxy\n\n CONTAINER PATHS — the bind mount maps the repo root to /app inside\n containers, while file artifacts are uploaded under /workspace/.canary\n on the host. So the same file has two paths:\n /workspace/.canary/foo.ts ← the path you upload to from the CLI\n /app/.canary/foo.ts ← the path your compose command/services\n reference inside the container\n Reference the in-container path in any compose \\`command\\` array, exec\n call, or script file path that runs inside a service. The host-side\n /workspace path only appears in startup.sh / setup.sh (which run on\n the host, not inside a container).\n\n S3 BUCKET (S3 emulation):\n The platform runs an S3-compatible server as the \"minio\" service at\n http://minio:9000, and the deploy waits until the sandbox-data bucket\n exists before it starts your services. After a restart, when both come up\n together, retry the first S3 call on connection errors. It does NOT inject\n AWS_ENDPOINT_URL_S3 into your containers, so:\n\n 1. Wire the endpoint into your service env in compose:\n AWS_ENDPOINT_URL_S3: http://minio:9000\n AWS_ACCESS_KEY_ID: minioadmin\n AWS_SECRET_ACCESS_KEY: minioadmin\n AWS_REGION: us-east-1\n S3_BUCKET_NAME: sandbox-data\n\n 2. If your app needs a bucket with another name, create it through the\n S3 API (CreateBucket) from your app's own startup or seed code. No\n extra compose service is needed for bucket setup.\n\n Most AWS SDK v3 clients honor AWS_ENDPOINT_URL_S3 natively. If your\n app passes a custom S3 client, also set forcePathStyle: true (the\n sandbox S3 server doesn't support virtual-host-style bucket addressing).\n\n Compose still runs with /workspace as the project directory. That means user\n compose content keeps the same repo-root-relative behavior for build contexts,\n bind mounts, and referenced files.\n\n COMPOSE INVOCATION: use the pre-installed \"sb-compose\" wrapper on the host.\n It expands to docker compose with the canonical flag set:\n docker compose --project-directory /workspace \\\\\n --env-file /workspace/.canary/.env \\\\\n -f /workspace/.canary/docker-compose.yml \\\\\n -f /workspace/.canary/docker-compose.override.yml \\\\\n \"$@\"\n Call it from startup scripts, action scripts, and run-command the same way:\n sb-compose exec -T app-api bash -c 'bun run build'\n sb-compose ps\n sb-compose logs --tail 50 app-api\n The wrapper is available as /usr/local/bin/sb-compose (symlink) and\n /workspace/.canary/sb-compose. If you invoke raw \"docker compose\" yourself\n you must pass the same flags — otherwise paths and variable interpolation\n (\\${SANDBOX_URL} et al.) will resolve incorrectly.\n\n The platform injects a shared Docker network. On \"build\", the sequence is:\n 1. Write file artifacts to their destination paths\n 2. Run setup script (if present)\n 3. docker compose down (cleanup previous containers)\n 4. docker compose build + up\n 5. Reload nginx\n 6. Wait for long-running services to accept \"sb-compose exec\" before continuing\n 7. Run startup script (if present)\n\n Step 6 is automatic. You don't need to add your own \"wait for postgres\"\n loops at the top of startup.sh — by the time startup.sh runs, every\n non-one-shot service is exec-able. Service healthchecks inside the\n containers (e.g. postgres pg_isready) are still your responsibility if\n your app needs to wait on them.\n\n Do NOT define services named \"nginx-proxy\", \"sandbox-agent\", or \"minio\"\n in your compose.\n\nDOCKERFILE (type: dockerfile)\n A base image that provides the runtime. Source code is volume-mounted, NOT copied in.\n Written to /workspace/.canary/Dockerfile.sandbox (avoids conflicting with the repo's own Dockerfile).\n Reference it in compose as: build: { context: ., dockerfile: .canary/Dockerfile.sandbox }\n\n Structure:\n FROM <runtime-base>\n RUN apt-get update && apt-get upgrade -y && apt-get install -y <system-deps>\n RUN npm install -g <global-tools>\n WORKDIR /app\n\n Rules:\n - Always \"apt-get upgrade -y\" before installing — base images ship OS\n packages with known CVEs; the upgrade patches them at build time\n - NO \"COPY . .\" — code comes via volume mount at /workspace\n - NO \"RUN npm build\" — the production build runs on the instance as a\n compose service (see PRODUCTION BUILD), where \\${SANDBOX_URL} and\n build-time env are available. Baking it into the image would freeze\n build-time env to image-build values.\n - Include ALL system deps (chromium, ffmpeg, python, etc.)\n - For browser automation: use mcr.microsoft.com/playwright as base\n - Do NOT use ENTRYPOINT — it breaks one-shot services (deps, migrate) whose\n command gets passed as args to the entrypoint instead of running directly\n\nDOCKER COMPOSE (type: docker-compose)\n Defines all services. Platform handles networking and nginx routing.\n\n Typical services:\n deps (one-shot) — installs dependencies into named volume, restart: \"no\"\n database — official image with healthcheck (postgres, mysql, etc.)\n cache — redis/memcached with healthcheck\n web-build (one-shot) — runs the production frontend build into the bind-mounted\n repo tree, restart: \"no\" (see PRODUCTION BUILD)\n app-api — application server, PRODUCTION command (NODE_ENV=production,\n no --hot / --watch)\n app-web — static frontend served by nginx:alpine from the build\n output (see PRODUCTION BUILD). SSR frameworks (Next.js\n server mode, etc.) run their production start command\n instead of an nginx static server.\n\n MIGRATIONS: prefer a first-class \\`migrate\\` one-shot compose service when\n migrations can run before the app boots. Gate it on the DB healthcheck and\n dependency install/build services it needs, then gate app-api on migrate with\n service_completed_successfully. This prevents pre-migration boot crashes and\n avoids a post-hoc \"restart app-api\" recovery step.\n\n migrate:\n build: { context: ., dockerfile: .canary/Dockerfile.sandbox }\n working_dir: /app\n volumes:\n - .:/app\n - node_modules:/app/node_modules\n command: [\"bash\",\"-c\",\"cd /app/packages/db && bun run drizzle-kit migrate\"]\n depends_on:\n deps:\n condition: service_completed_successfully\n postgres:\n condition: service_healthy\n restart: \"no\"\n\n app-api:\n depends_on:\n migrate:\n condition: service_completed_successfully\n\n If a migrate service fails because the command/path/env is wrong, fix the\n artifact and run a fresh sandbox build so compose recreates the one-shot.\n Use startup-script migrations only for stacks that truly require an already\n running app container.\n\n ROUTING LABELS — declare how nginx should route to your services:\n services:\n app-api:\n labels:\n sandbox.route: \"/api\"\n sandbox.port: \"3000\"\n app-web:\n labels:\n sandbox.route: \"/\"\n sandbox.port: \"80\"\n\n The platform reads these labels and generates nginx config on \"build\".\n If no labels, all traffic routes to the first non-infrastructure service.\n\n PREFIX STRIPPING: the generated nginx location uses a trailing-slash\n proxy_pass (proxy_pass http://<service>:<port>/), so the matched prefix\n is stripped from the upstream request. With sandbox.route: \"/api\", a\n browser hit to \"/api/health\" is delivered to the upstream as \"/health\".\n Pick VITE_API_URL, OAuth redirect URIs, etc. accordingly.\n\n !!! GET /health MUST RETURN 200 !!!\n Whatever service carries sandbox.route: \"/\" has to answer GET /health with\n a 200. The provision health gate curls localhost:80/health and FAILS THE\n WHOLE PROVISION (after ~3 minutes of retries) if it doesn't — the instance\n is marked failed even though every container came up clean.\n\n An app server usually has this already. A STATIC service does not: an\n nginx:alpine serving a built SPA 404s /health unless you give it one.\n\n Two ways, and note that BOTH need the file to reach the served document\n root — uploading an artifact somewhere under .canary/ does nothing on its\n own, because nginx serves its root, not the host filesystem:\n\n 1. An explicit location block in your nginx conf (no extra mount needed if\n you already mount a conf):\n\n location = /health { return 200 'ok'; add_header Content-Type text/plain; }\n\n 2. A file artifact that you then MOUNT into the document root:\n\n cofactor sandbox artifact create <template-id> file health \\\\\n --path /workspace/.canary/health\n\n and in compose:\n\n volumes:\n - ./.canary/health:/usr/share/nginx/html/health:ro\n\n Keep the artifact destination a single filename directly under\n .canary/ — nested destinations conflict with the provision payload\n validator.\n\n This bites hardest on sandboxes that serve nothing interesting on \"/\" — a\n test-runner box, a worker-only template — where the routed service exists\n purely to satisfy the gate. It still needs /health.\n\n ENV VAR STRATEGY — use \\${VAR} syntax, values come from config + .env:\n - Auto-wired (hardcode): DATABASE_URL=postgres://user:pass@postgres:5432/db\n - From config: ANTHROPIC_API_KEY=\\${ANTHROPIC_API_KEY}\n - Platform-injected: VITE_API_URL=\\${SANDBOX_URL}/api\n - Platform model account: OPENAI_BASE_URL=\\${SANDBOX_LLM_PROXY_URL}/azure/v1\n OPENAI_API_KEY=\\${SANDBOX_LLM_PROXY_TOKEN}\n - Disabled: WORKER_ENABLED=false\n\n SANDBOX_URL is auto-injected by the platform (e.g. https://<id>.env.runcofactor.com).\n Use it for any URL a browser needs to reach the sandbox.\n SANDBOX_LLM_PROXY_URL / SANDBOX_LLM_PROXY_TOKEN are the per-instance LLM proxy: an\n OpenAI-compatible client pointed at \\${SANDBOX_LLM_PROXY_URL}/azure/v1 with the token\n as its key is rewritten onto the platform's funded Azure account. Prefer this over\n asking the user for a vendor API key.\n\n Rules:\n - Use named volumes for data persistence (postgres-data, redis-data, etc.)\n - Bind-mount the repo (.:/app) rather than using named volumes for\n node_modules. Per-package node_modules named volumes silently shadow the\n bind mount on any service that doesn't mount them — a common monorepo\n trap where \"bun run build\" fails because tsup is nowhere to be found.\n The sandbox host is Linux EC2; bind mounts are fast.\n - DO NOT define networks — the platform handles networking\n - DO NOT expose ports to the host — nginx handles routing\n - Keep relative paths repo-root-relative; compose runs from /workspace even\n though the platform-managed compose files live in /workspace/.canary\n - Escape shell variables in \"command:\" strings with $$ so compose leaves\n them for the shell (e.g. command: [\"bash\", \"-c\", \"exec foo $$BAR\"]).\n Unescaped \\${VAR} is substituted at compose-time from the env file.\n\nPRODUCTION BUILD (static frontends)\n A production build served by nginx is the RECOMMENDED default — faster,\n cacheable, and free of dev-server host-allowlist headaches. It is NOT mandatory,\n though: a dev server (Vite/webpack) with a permissive host allowlist (e.g. Vite\n \\`allowedHosts: true\\`) also works and is simpler to stand up. Reach for the\n production build when the dev bundle is large enough to render blank on a remote\n sandbox (hundreds of uncompressed modules) or when you want a demo-grade,\n responsive UI. The production build has two pieces:\n\n 1. BUILD as a one-shot compose service — in its OWN container, NOT inside the\n api/app container. A production build is CPU + disk-I/O heavy; if it runs\n inside the api container it starves the api's /health endpoint and the\n provision health gate fails the whole sandbox (nginx 502). The setup script\n can't do it either (it runs on the HOST, where app runtimes don't exist, and\n before deps installs). So: a dedicated one-shot service that depends on deps.\n\n Build the RIGHT set of packages. In a monorepo, build EVERY workspace\n package that exports from dist/ that is imported by BOTH the frontend AND\n the BACKEND — not just the UI. If the api imports a dist-exporting package\n whose dist is missing, the api crash-loops \"Cannot find module @scope/pkg\"\n and never answers /health. Find them: grep the api's imports for @scope/*\n and cross-check each package.json \"main\"/\"exports\" for \"./dist/\". Build the\n leaf packages first, then the app:\n web-build:\n build: { context: ., dockerfile: .canary/Dockerfile.sandbox }\n working_dir: /app\n volumes:\n - .:/app\n environment:\n VITE_API_URL: \\${SANDBOX_URL}/api # build-time public vars FIRST\n NODE_OPTIONS: --max-old-space-size=8192 # raise heap if the build OOMs\n # nice -n 19 keeps the api responsive if anything overlaps. Build the\n # api's dist-dependencies AND the ui, then the app bundle.\n command: [\"bash\",\"-c\",\"nice -n 19 bash -c 'cd /app/packages/foo && bun run build && cd /app/packages/bar && bun run build && cd /app/apps/web && bun run build'\"]\n depends_on:\n deps:\n condition: service_completed_successfully\n restart: \"no\"\n Compose interpolates \\${SANDBOX_URL} at \"up\" time, so the bundle bakes the\n correct browser-facing API URL. The output (dist/) lands in the\n bind-mounted repo tree, where the nginx service below mounts it.\n\n CACHEABILITY: if you want this build cached across sandboxes (see Build\n Caching), the output must NOT bake \\${SANDBOX_URL} — a cached dist is reused on\n other sandboxes. Resolve the API origin at runtime from the browser location\n (web and api are same-origin behind the proxy, so a relative \"/api\" works\n everywhere) instead of baking VITE_API_URL. Then label web-build with\n sandbox.cache + sandbox.cache-inputs/paths.\n\n SERIALIZE the build BEFORE the health gate: give nginx and any app service\n that imports dist-exporting packages a depends_on web-build\n (service_completed_successfully). That makes \"docker compose up\" block\n until the build finishes, so the api boots once with all dist packages\n present. Do not rely on startup.sh to restart app-api solely to recover\n from missing build outputs.\n\n 2. SERVE the output with a lightweight nginx:alpine service that depends on\n the build completing. Mount the PARENT of the output dir (e.g. apps/web),\n NOT the dist dir directly, and point nginx root at the dist SUBPATH:\n app-web:\n image: nginx:alpine\n volumes:\n - ./apps/web:/srv:ro # mount the PARENT, root is /srv/dist\n - ./.canary/nginx-spa.conf:/etc/nginx/conf.d/default.conf:ro\n depends_on:\n web-build:\n condition: service_completed_successfully\n labels:\n sandbox.route: \"/\"\n sandbox.port: \"80\"\n\n WHY mount the parent: bundlers (vite, webpack) DELETE and recreate the\n output dir on build, giving it a NEW inode. Docker creates an empty\n dist bind-source at compose-up time; a direct \"dist:/usr/share/nginx/html\"\n bind pins nginx to that stale empty inode and it serves 403 \"directory\n index forbidden\" forever. Mounting the parent (a stable inode) and letting\n nginx resolve root/<uri> per request always sees the current dist.\n\n Provide an SPA-fallback nginx config as a file artifact so client-side\n routes (deep links / hard refresh) don't 404 — unknown paths serve\n index.html and the client router takes over:\n canary sandbox artifact create <template-id> file nginx-spa.conf \\\\\n --path /workspace/.canary/nginx-spa.conf\n\n nginx-spa.conf:\n server {\n listen 80;\n root /srv/dist; # the dist SUBPATH of the mounted parent\n index index.html;\n gzip on;\n gzip_types text/css application/javascript application/json image/svg+xml;\n location /assets/ {\n try_files $uri =404;\n expires 1y;\n add_header Cache-Control \"public, immutable\";\n }\n location / {\n try_files $uri $uri/ /index.html;\n add_header Cache-Control \"no-cache\";\n }\n }\n\n (Match root to your framework's output dir under the mounted parent — e.g.\n /srv/build for a build/ output. The hashed-asset location block assumes a\n /assets/ dir — adjust to your bundler's asset path.)\n\n WHY: nginx serving static files has no host allowlist (no \"This host is not\n allowed\"), ships compressed/hashed/cacheable assets, and is far more\n responsive than a dev server streaming hundreds of uncompressed modules. The\n trade-off is no hot-reload and a longer, heavier build step — acceptable\n because sandboxes demo a BUILT app.\n\n SSR / server-rendered frameworks (Next.js server mode, Remix, Nuxt SSR):\n skip the nginx static server. Build in the one-shot service, then run the\n framework's production start command (e.g. next start) as a normal routed\n service with sandbox.route \"/\" and its listen port.\n\nSETUP SCRIPT (type: setup-script)\n Runs BEFORE docker compose up on every build. Runs on the HOST, not in a container.\n Written to /workspace/.canary/setup.sh.\n\n IMPORTANT: The host is a bare EC2 instance with only docker and basic linux tools.\n App runtimes (node, bun, python, ruby) are NOT available on the host — they live\n inside your Docker images. Do NOT run app commands directly here.\n\n Script authoring rules:\n - Treat /workspace as the repo root for any file access or repo command\n - Do not assume the current directory matches the script's own path\n - Use explicit paths or start with \"cd /workspace\" before repo-relative work\n\n Use for host-level preparation only:\n - Installing system packages (apt-get, yum)\n - Downloading external tools or binaries\n - Generating config files programmatically (prefer file artifacts for static files)\n\n For most apps, you won't need a setup script — file artifacts and the startup\n script cover the common cases. Skip this if you don't need host-level prep.\n\n Rules:\n - Must be idempotent — safe to run multiple times\n - Never patch source files\n - Non-zero exit is logged as a warning but does not block the build\n\nSTARTUP SCRIPT (type: startup-script)\n Runs AFTER docker compose up on every build. Runs on the HOST, not in a container.\n Written to /workspace/.canary/startup.sh.\n\n This is where most app setup happens. Containers are running, so you can use\n \"sb-compose exec\" to run commands inside them with the app runtime.\n\n Script authoring rules:\n - Start with \"cd /workspace\" or use explicit /workspace/... paths\n - Do not assume the script runs from /workspace/.canary\n - Prefer sb-compose exec for anything that needs the app runtime\n - Use -T with exec (no TTY allocation — startup runs headless)\n\n Use for:\n - DB migrations via ORM CLI only when a pre-boot migrate service is not viable\n (use \"bun run\", NOT bunx — bunx downloads fresh)\n - Building workspace packages that export from dist/ only when a pre-boot\n build service is not viable\n - Waiting for services to be healthy\n - Triggering initial data loads\n\n WHEN STARTUP-SCRIPT MIGRATIONS ARE USED:\n ORMs that introspect the schema at boot (Drizzle/effect-sql, Prisma with\n prepared statements, SQLAlchemy with autoload, etc.) can cache the\n pre-migration schema on the first query. If migrations run after the API has\n already booted, restart schema-reading services after the migration or\n you'll see errors like:\n relation \"<table>\" does not exist\n even though psql confirms the table is there.\n\n When migrations run as a first-class compose service and app-api depends on\n it, this restart is unnecessary: app-api has not booted yet.\n\n Example (Bun monorepo with pre-boot build + migrate services):\n #!/bin/bash\n set -e\n # web-build and migrate already completed before app-api started via\n # depends_on: service_completed_successfully.\n # Restart only services whose runtime config or mounted files changed here.\n sb-compose restart app-web\n # Wait for API to be healthy\n for i in $(seq 1 30); do\n sb-compose exec -T app-api curl -sf http://localhost:3000/health && break\n sleep 2\n done\n\n Rules:\n - Must be idempotent — safe to run multiple times\n - Never patch source files\n - Don't pre-wait for services to START — the platform handles that before\n startup.sh runs (see Compose Architecture section). Do wait for service\n *readiness* (app health, schema migrations applied) when your app\n depends on it.\n\nFILE (type: file)\n Arbitrary sandbox-owned files written to the instance at a specific path.\n Use for config files that need to exist before services start (e.g. Vite\n wrapper configs, one-shot scripts that need app dependencies).\n\n !!! File artifacts MUST be written under /workspace/.canary/ !!!\n The repo tree (everything outside .canary/) is user-owned. Sandbox\n helpers live in .canary/ so they never collide with real files and\n are never committed back to the repo. The API will reject any\n --path value outside /workspace/.canary/ with\n SANDBOX_FILE_ARTIFACT_OUTSIDE_CANARY.\n\n If your script needs app dependencies (e.g. \\`postgres\\` from the api\n package), put a tiny package.json next to it in .canary/scripts/ and\n \\`bun install\\` in the setup-script, OR invoke it via\n \\`sb-compose exec -T <svc> ...\\` so it runs inside a container that\n already has them.\n\n Upload with --path to specify the destination:\n canary sandbox artifact create <template-id> file <local-file> --path /workspace/.canary/<dest>\n\n Example — SPA fallback config for the static web server (see PRODUCTION BUILD):\n canary sandbox artifact create <template-id> file nginx-spa.conf \\\\\n --path /workspace/.canary/nginx-spa.conf\n\n ONE ROW PER FILENAME. File artifacts are keyed by (template, filename).\n Uploading a file with the same filename as an earlier upload REPLACES\n the earlier row. The CLI prints \"(replaced)\" on such uploads, and the\n next build removes the previous destination path so the instance\n filesystem mirrors the template set exactly.\n\n If you want to retire a file, just upload something else with a\n different filename — the stale file is cleaned up on next build via\n .canary/.artifact-paths (a manifest the platform maintains).\n\n File artifacts are written before the setup script runs, so they're\n available for both setup and compose services.\n\nSEED SCRIPT (type: seed-script)\n Runs once on first provision to populate demo data. Optional.\n Written to /workspace/.canary/seed.sh. Runs on the HOST after startup.sh.\n Skipped on subsequent builds (checks /workspace/.canary/.seeded).\n\n For more than a trivial insert — a real demo dataset, a multi-script seed\n pipeline, or wiring login personas — see the SEED & PERSONAS section\n (\"cofactor sandbox guide v1 seed-personas\"). Two things bite there: a long seed must\n run DETACHED (it far outlasts the provision/verify window), and app-defined\n \"demo personas\" usually land in a DEFAULT workspace, not the one you seeded.\n\nCONFIG SCHEMA\n Define which env vars need user input. Set when completing the docker-compose checklist item:\n canary sandbox checklist complete <template-id> docker-compose-created \\\\\n --status success \\\\\n --summary \"Compose with postgres, redis, api, web\" \\\\\n --config-schema '[\n {\"key\": \"ANTHROPIC_API_KEY\", \"required\": true, \"sensitive\": true, \"description\": \"Anthropic API key\"},\n {\"key\": \"OPENAI_API_KEY\", \"required\": false, \"sensitive\": true, \"description\": \"OpenAI API key\"}\n ]'\n\n Set defaults:\n canary sandbox config set <template-id> <key> <value>\n canary sandbox config list <template-id>\n\n ⚠️ The provision/verify pre-check FAILS (\"Missing environment variables: X\") for\n any config-schema key — even required:false — that has no value AND is referenced\n as \\${X} in compose. So do not wire an unset optional var into compose: give it a\n config default, or drop the \\${X} reference until a value is provided.\n`.trim(),\n};\n\nconst VERIFICATION: GuideSection = {\n title: 'Verification & Iteration',\n slug: 'verify',\n content: `\nAfter running \"canary sandbox build\", verify the sandbox is working. The iteration loop:\n upload artifacts → build → check diagnostics → read logs → fix → re-upload → rebuild\n\nCOMMANDS:\n # Check all container statuses at a glance\n canary sandbox diagnostics <instance-id>\n\n # Read logs for a specific service\n canary sandbox logs <instance-id> --service app-api\n canary sandbox logs <instance-id> --service app-web\n canary sandbox logs <instance-id> --service postgres\n\n # Run arbitrary commands on the instance (runs on the host VM by default)\n canary sandbox run-command <instance-id> \"docker compose ps\"\n canary sandbox run-command <instance-id> \"echo hello\"\n\n # Run commands inside a specific Docker container\n canary sandbox run-command <instance-id> --service app-api \"bun --version\"\n canary sandbox run-command <instance-id> --service app-api \"bun test\"\n\n # Test health endpoints. localhost:80/health is the one the PROVISION GATE\n # curls — a non-200 here fails the whole provision, so check it before you\n # verify (see \"GET /health MUST RETURN 200\" in the artifacts section).\n canary sandbox run-command <instance-id> \"curl -s -o /dev/null -w '%{http_code}' http://localhost:80/health\"\n canary sandbox run-command <instance-id> \"curl -s http://localhost:3000/health\"\n canary sandbox run-command <instance-id> \"curl -s -o /dev/null -w '%{http_code}' http://localhost:80/\"\n\n # Test with sandbox hostname (for host-restriction issues)\n canary sandbox run-command <instance-id> \\\\\n \"curl -s -H 'Host: <instance-id>.env.runcofactor.com' http://localhost:80/\"\n\nSANDBOX URL:\n https://<instance-id>.env.runcofactor.com\n\nIN-BOX BROWSER VERIFICATION (sandbox agent):\n The agent running inside the sandbox has a headless Chromium and can drive the\n running app directly to confirm setup — no external tester needed. The app is\n in a sibling docker-compose project; target it over the compose network.\n\n docker compose ps # find app service + port\n cofactor session start --headless --url http://<svc>:<port> [--credential <n>]\n cofactor session snapshot # page accessibility tree\n cofactor session click --ref <ref> # act on a ref\n cofactor session type --ref <ref> --text \"...\" --submit\n cofactor session wait --text \"<expected copy>\" # assert it loaded\n cofactor session console --only-errors # surface JS errors\n cofactor session stop\n\n The session daemon idle-exits after ~60s; just start again if a command\n reports no active session. Run this AFTER the app is healthy (get_status /\n diagnostics), then record the result via update_checklist.\n\nIF SOMETHING FAILS:\n 1. Read the logs for the failing service\n 2. Diagnose the root cause (missing env var, failed build, wrong port, etc.)\n 3. Fix the ARTIFACT — not just the running container\n 4. Re-upload: canary sandbox artifact create <template-id> <type> <file>\n 5. Rebuild: canary sandbox build <instance-id>\n 6. Verify again\n\nWHEN A CLI COMMAND TIMES OUT AT 30s:\n Long sandbox operations (build, edit, artifact create on first push,\n occasionally diagnostics) can exceed the gateway window even though\n the work is still running server-side. The CLI prints:\n Error: Request to https://api.trycanary.ai/... timed out after 30000ms.\n\n Don't retry blindly — a retry can spawn duplicate work (e.g. a second\n authoring instance). Instead, poll for the actual state:\n\n canary sandbox template <template-id> # status: provisioning/authoring/verified\n canary sandbox diagnostics <instance-id> # container/app health\n canary sandbox list # running instances\n\n Only retry once you've confirmed via polling that the prior call\n truly didn't take effect.\n\nWHEN VERIFY HANGS:\n \\`canary sandbox verify\\` now streams container state and failing-service\n log tails every ~30s while the probe's app is unreachable. If you see:\n\n >> nginx-proxy is restart-looping because upstream '<service>' (...) is exited.\n Read that service's logs above, not nginx's.\n\n ignore nginx entirely and read the upstream's tail — nginx is the symptom,\n not the cause. Common underlying causes:\n - the upstream service crashed at startup (bad env var, missing file,\n import error)\n - a file artifact was lost because you re-uploaded over its filename\n (shouldn't happen any more — the build-time reconcile handles this —\n but watch for it if you're on an old template)\n\n On failure, the probe instance is preserved by default so you can\n \\`canary sandbox run-command <id>\\` into it. Pass\n --auto-terminate-on-failure for CI.\n\nCOMMON ISSUES:\n\n \"Invalid environment\" / env validation fails\n App has strict env var validation. Read the env schema. Add ALL required vars to compose.\n\n Migration fails with \"SST links not active\" / cloud SDK error\n Migrate script depends on cloud tooling. Use the project-local ORM CLI\n directly. Prefer a first-class migrate service gated on Postgres/deps and\n gate app-api on it; if the stack truly requires an already-running app\n container, use startup.sh with sb-compose exec instead:\n sb-compose exec -T app-api bash -c 'bun run drizzle-kit migrate'\n DATABASE_URL should be hardcoded in the compose environment section.\n\n Migration/DB script fails with a garbled URL or \"server does not support SSL\"\n Some migration runners resolve the DB HOST to an IP and string-replace it\n across the whole URL, then append sslmode=require. A host literally named\n \"postgres\" then corrupts the \"postgresql://\" scheme (postgres→IP inside\n postgresql) AND forces SSL that local Postgres rejects. Fix: resolve the\n container IP yourself and pass an IP-based DATABASE_URL so the rewrite is a\n no-op (an IP host == its own resolution, so the runner skips the rewrite):\n PG_IP=$(sb-compose exec -T app-api getent ahostsv4 postgres | head -1 | awk '{print $1}')\n sb-compose exec -T app-api bash -c \"DATABASE_URL=postgresql://u:p@$PG_IP:5432/db ./scripts/migrate.sh\"\n\n \"Cannot find module\" / package not built\n Monorepo package exports from dist/ but hasn't been built.\n Add the package to the pre-boot build service and gate any importing app\n service on that build with service_completed_successfully. Use startup.sh\n only if the build truly cannot run before compose up.\n\n \"Cannot find package 'X'\" from a file-artifact script\n Bun resolves node_modules starting from the script's directory. File\n artifacts live under /workspace/.canary/ which has no node_modules by\n default. Two fixes:\n (a) add a /workspace/.canary/scripts/package.json declaring the\n dep (e.g. \"postgres\") and run \\`bun install\\` from the\n setup-script / inside a sb-compose exec.\n (b) don't run the script on the host at all — invoke it via\n \\`sb-compose exec -T <app-service> bun /app/.canary/scripts/foo.ts\\`\n so it resolves against the workspace's own node_modules. Replace\n <app-service> with your template's actual service name.\n\n Blank page — frontend never renders (served by a dev server)\n The web service is a dev server (Vite/webpack-dev-server) shipping hundreds\n of uncompressed, no-cache modules. On a remote sandbox these trickle in too\n slowly for the SPA to mount, so the page stays blank. Confirm with:\n curl -s <SANDBOX_URL>/ | grep '<script'\n Dev mode references /@vite/client or /src/main.tsx; a production build\n references hashed /assets/*.js. Fix: serve a production build (see\n Artifacts → PRODUCTION BUILD), not a dev server.\n\n Deep links / page refresh 404 (SPA routing)\n nginx serves static files but a client-side route (e.g. /settings) or a\n hard refresh returns 404 — the static server has no SPA fallback. Add\n \"try_files $uri $uri/ /index.html;\" to the nginx config (see Artifacts →\n PRODUCTION BUILD) so unknown paths serve index.html and the client router\n takes over.\n\n Frontend loads but API calls hit the wrong URL / localhost\n The browser-facing API URL was not set as a BUILD-TIME var before the\n build, so the bundle baked a default (often localhost:3000). Static\n bundles read build-time vars at BUILD time, not runtime — setting them\n only in the app-web/runtime compose env is too late. Set the public var on\n the web-build service env (or before the build command), using\n \\${SANDBOX_URL}, then rebuild:\n VITE_API_URL: \\${SANDBOX_URL}/api # or NEXT_PUBLIC_*, REACT_APP_*, etc.\n\n API 502 / \"Cannot find module '@scope/pkg'\" crash-loop after going prod\n The api imports a workspace package that exports from dist/, but that\n package's dist was never built — so the api crash-loops and the health gate\n fails the whole provision with a 502. Easy to miss because the FRONTEND\n build (ui) works. Build EVERY dist-exporting package the api imports in the\n web-build service (see Artifacts → PRODUCTION BUILD), not just the ui ones.\n A warm AMI can hide this (it baked the dist in); a fresh provision exposes it.\n\n 403 \"directory index forbidden\" from the static server\n nginx is bound to the stale empty dist inode the bundler replaced. Mount the\n PARENT dir and set root to the dist subpath (see Artifacts → PRODUCTION\n BUILD), or restart the nginx service after the build.\n\n Build is killed / \"JavaScript heap out of memory\"\n The production build exhausted the Node heap (common on large Vite/webpack\n builds). Raise it on the web-build service before the build:\n NODE_OPTIONS: --max-old-space-size=8192\n If it still OOMs, bump the instance size (canary sandbox set-size) and/or\n the heap further.\n\n Stale page after a rebuild (old dist served)\n nginx is serving a previous build because the build step didn't re-run or\n its output dir wasn't refreshed. Confirm the web-build service ran in this\n build cycle (canary sandbox logs <id> --service web-build), that it wrote\n to the dir nginx mounts, and — if you ever build via the startup script\n instead — that nginx was restarted (sb-compose restart app-web). Hashed\n asset filenames change per build; if index.html still references old\n hashes, the build didn't land.\n\n run-command times out\n Long-running operation exceeds the 55s SSM command timeout. Write a script file and run in background:\n canary sandbox run-command <id> \"cat > /tmp/run.sh << 'SCRIPT'\n #!/bin/bash\n <commands>\n SCRIPT\n chmod +x /tmp/run.sh && nohup /tmp/run.sh > /tmp/run.log 2>&1 & echo started\"\n Then check: canary sandbox run-command <id> \"tail -20 /tmp/run.log\"\n\n \"Resolving dependencies\" hangs in migration container\n bunx/npx downloads a fresh copy instead of using project deps.\n Use \"bun run drizzle-kit migrate\" instead of \"bunx drizzle-kit migrate\".\n\n Containers can't resolve each other by name\n Don't use docker run — use docker compose for everything.\n\nRUNNING TESTS:\n Two approaches depending on your needs:\n\n Option 1 — Install runtime on host (no Docker needed):\n Use the setup script to install the runtime on the host VM, then run\n tests directly. Good for unit tests that don't need services like a database.\n\n Example setup-script artifact:\n #!/bin/bash\n curl -fsSL https://bun.sh/install | bash\n export PATH=\"$HOME/.bun/bin:$PATH\"\n\n Then: canary sandbox run-command <instance-id> \"bun test\"\n\n Option 2 — Run inside a container (--service flag):\n Route commands into a running Docker container. Good for integration\n tests that need access to the database or other services.\n\n canary sandbox run-command <instance-id> --service app-api \"bun test\"\n canary sandbox run-command <instance-id> --service app-api \"bun test:integration\"\n\n \\${SANDBOX_URL} is blank / env vars from config not in containers\n The \"build\" command writes /workspace/.canary/.env (with SANDBOX_URL and all config values)\n before running docker compose up. If the .env is missing or stale, re-run:\n canary sandbox build <instance-id>\n You can also trigger a .env refresh by setting any config value:\n canary sandbox config set <template-id> <key> <value>\n\n .env values look empty when I cat the file\n Values are written single-quoted (KEY='value'). Short values can look like\n KEY='' at a glance even when they have content — it's just the quotes. Run\n \"wc -c\" on a line, or \"docker compose exec app-api env | grep KEY\", to\n confirm the actual value landed.\n`.trim(),\n};\n\nconst CHECKLIST: GuideSection = {\n title: 'Checklist',\n slug: 'checklist',\n content: `\nThe checklist tracks your progress toward a working sandbox. View it with:\n canary sandbox checklist <template-id>\n\nEach item has a status: pending, completed, skipped, or failed.\nItems marked with * are actionable (their dependencies are met).\n\nCOMPLETING ITEMS:\n canary sandbox checklist complete <template-id> <item-id> \\\\\n --status <success|failure> \\\\\n --summary \"What you did\"\n\n Example:\n canary sandbox checklist complete <template-id> environment-analyzed \\\\\n --status success \\\\\n --summary \"Bun monorepo: Postgres, Redis. Vite production build served by nginx, API on 3000.\"\n\nSKIPPING ITEMS:\n canary sandbox checklist skip <template-id> <item-id>\n\n Use when an item doesn't apply (e.g., no seed data needed, no integration tests).\n\nSPECIAL: docker-compose-created accepts --config-schema to define user-configurable env vars:\n canary sandbox checklist complete <template-id> docker-compose-created \\\\\n --status success \\\\\n --summary \"Compose with postgres, redis, api, web\" \\\\\n --config-schema '[{\"key\": \"ANTHROPIC_API_KEY\", \"required\": true, \"sensitive\": true, \"description\": \"API key\"}]'\n\nTYPICAL CHECKLIST ORDER:\n 1. Provisioned — done automatically when you create the template\n 2. Code Cloned — done automatically\n 3. Environment Analyzed — complete after your research phase\n 4. Setup Script Created — complete after uploading setup-script artifact\n 5. Dockerfile Created — complete after uploading dockerfile artifact\n 6. Docker Compose Created — complete after uploading docker-compose artifact (with config-schema)\n 7. Unit Tests Runnable — complete or skip. Install runtime on host via setup script,\n or use --service to run inside a container\n 8. Integration Tests Runnable — complete or skip. Use --service to run inside a\n container with access to database and other services\n 9. Seed Script Created — complete or skip\n 10. Server Running — complete after API responds to health checks\n 11. Server Accessible — complete after nginx routes to the app successfully\n 12. Template Verified — provision a fresh instance and verify it comes up healthy\n\nTEMPLATE VERIFICATION (final step):\n Run one command — it provisions a fresh instance, waits for the app to\n come up, marks template-verified, and terminates the instance:\n canary sandbox verify <template-id>\n\n Pass --keep-instance to leave the instance running afterwards (useful if\n you want to poke at it).\n\n This catches issues that authoring hides:\n - Config values set manually but not in configDefaults\n - SANDBOX_URL or other platform vars not properly referenced\n - Startup scripts that assume manual setup already happened\n\n Template status flow:\n provisioning → authoring → verified\n\n 'verify' is the one transition from authoring to verified, and it is one-way\n — there is no re-authoring path back to 'authoring' from 'verified'. To make\n an AI-assisted edit to a verified template's artifacts, provision a sandbox\n with the agent attached:\n\n canary sandbox provision <template-id> --with-agent\n\n then iterate via in-sandbox agent chat or 'canary sandbox artifact create',\n and re-run 'canary sandbox verify' against the same template id when you're\n happy with the changes. To start over from scratch, use 'regenerate' to\n delete existing artifacts, or create a new template.\n`.trim(),\n};\n\nconst ACTIONS: GuideSection = {\n title: 'Actions',\n slug: 'actions',\n content: `\nActions are template-defined, parameterized operations that run against sandbox\ninstances. Unlike run-command (arbitrary shell), actions have typed input/output\nschemas, are reusable across instances, and produce an auditable execution log.\n\nIN-BOX AGENT: the sandbox agent defines actions with the publish_action tool,\nlists them with list_actions, and RUNS them with execute_action (by slug, with\nparams) — all scoped to its own instance, no instance id needed. The CLI\nequivalents below are for external operators.\n\nUSE CASES:\n - Provision a user credential (username, password, role)\n - Seed demo data for a specific scenario\n - Run database migrations\n - Reset application state\n - Toggle feature flags\n\nMANAGING ACTIONS:\n\n List actions on a template:\n canary sandbox action list <template-id>\n\n View an action definition (including script):\n canary sandbox action get <template-id> <slug>\n\n Create or update an action:\n canary sandbox action create <template-id> <slug> \\\\\n --label \"Provision Credential\" \\\\\n --description \"Create a user account in the sandbox app\" \\\\\n --icon UserPlus \\\\\n --script ./scripts/provision-user.sh \\\\\n --input-schema ./schemas/provision-user-input.json \\\\\n --credential-output-config ./schemas/provision-user-credential.json\n\n --credential-output-config is optional. When set, a successful execution\n also creates/updates a credential in the property's vault — see \"Linking\n actions to Canary credentials\" below.\n\n Delete an action:\n canary sandbox action delete <template-id> <slug>\n\nEXECUTING ACTIONS:\n\n Execute against a running instance:\n canary sandbox action execute <instance-id> <slug> \\\\\n --param username=testuser \\\\\n --param password=s3cret \\\\\n --param role=admin\n\n Or pass all params from a JSON file:\n canary sandbox action execute <instance-id> <slug> \\\\\n --params-file ./params.json\n\n View execution history:\n canary sandbox action executions <instance-id>\n\nSLUG NAMING:\n Slugs must be lowercase alphanumeric with hyphens, starting with a letter:\n provision-credential ✓\n seed-demo-data ✓\n reset-state ✓\n 123-bad ✗\n UPPER_CASE ✗\n\nINPUT SCHEMA:\n A JSON Schema file defining the action's parameters. The schema drives\n validation, CLI prompting, and UI form generation.\n\n Example (provision-user-input.json):\n {\n \"type\": \"object\",\n \"properties\": {\n \"username\": {\n \"type\": \"string\",\n \"description\": \"Username to create\",\n \"minLength\": 1\n },\n \"password\": {\n \"type\": \"string\",\n \"description\": \"Password for the user\",\n \"sensitive\": true\n },\n \"role\": {\n \"type\": \"string\",\n \"description\": \"Role to assign\",\n \"enum\": [\"admin\", \"user\", \"viewer\"],\n \"default\": \"user\"\n }\n },\n \"required\": [\"username\", \"password\"]\n }\n\n The \"sensitive\" extension marks params that should be masked in UI and\n injected securely (via tempfile, not env var) on the instance.\n\nSCRIPT CONVENTIONS:\n Action scripts are bash, executed on the instance via SSM. Parameters are\n injected as INPUT_<KEY> environment variables, where <KEY> is the param\n name uppercased with non-alphanumeric chars replaced by '_'. There is NO\n camelCase → snake_case conversion. Examples:\n orgName → INPUT_ORGNAME (NOT INPUT_ORG_NAME)\n org_name → INPUT_ORG_NAME\n api-key → INPUT_API_KEY\n If you want INPUT_FOO_BAR, name the input schema key 'foo_bar' or 'foo-bar'.\n\n The last line of stdout that is valid JSON is parsed as the structured result.\n\n EXECUTION ENVIRONMENT — same architecture as startup scripts:\n Scripts run on the HOST (bare EC2 instance). The host has docker, curl,\n and basic linux tools — but NO app runtimes (node, bun, python, ruby).\n Those live inside your Docker containers.\n\n Use \"docker compose exec\" to run commands that need the app runtime:\n sb-compose exec -T app-api node -e \"...\" # Node.js\n sb-compose exec -T app-api bun run seed.ts # Bun\n sb-compose exec -T app-db psql -U app -d db # Database CLI\n\n Use bare host commands for docker/system operations:\n sb-compose restart app-api # Restart a service\n curl -s http://localhost:3000/health # Health check\n cat /workspace/.canary/.env # Read config\n\n The -T flag is required for docker compose exec in non-interactive (SSM)\n contexts. Without it, exec tries to allocate a TTY and hangs.\n\n Working directory is /workspace (the mounted repo root). Keep script logic\n repo-root-relative by starting with \"cd /workspace\" when needed.\n\n AVOID INLINE CODE IN ACTION SCRIPTS:\n Never embed application-language code (JS, Python, etc.) directly in the\n bash script via \"bun -e\" or \"node -e\". Shell escaping of quotes, template\n literals, and variable interpolation is fragile and hard to debug.\n\n Instead, write complex logic as a separate file artifact and execute it:\n 1. Write a script file (e.g. provision-user.ts)\n 2. Upload as a file artifact:\n canary sandbox artifact create <template-id> file provision-user.ts \\\\\n --path /workspace/scripts/provision-user.ts\n 3. Reference it from the action script:\n sb-compose exec -T app-api bun /app/scripts/provision-user.ts\n\n Parameters are available in the action script as INPUT_<KEY> env vars.\n Pass them to the file artifact via env or args:\n sb-compose exec -T -e EMAIL=\"$INPUT_EMAIL\" app-api bun /app/scripts/provision-user.ts\n\n Example — provision a user (two-file pattern):\n\n Place the .ts file under the workspace whose node_modules has the deps\n (e.g. apps/api when only apps/api depends on \\`postgres\\`). Bun resolves\n modules from the script's directory walking up — if the dep isn't hoisted\n to the repo-root node_modules, a script under /workspace/scripts/ will\n fail with \"Cannot find package 'postgres'\". Use \\`-w\\` to set cwd inside\n the right workspace.\n\n Action script (provision-user.sh):\n #!/bin/bash\n set -euo pipefail\n cd /workspace\n sb-compose exec -T \\\\\n -w /app/apps/api \\\\\n -e EMAIL=\"$INPUT_EMAIL\" \\\\\n -e PASSWORD=\"$INPUT_PASSWORD\" \\\\\n -e NAME=\"$INPUT_NAME\" \\\\\n app-api bun /app/apps/api/scripts/provision-user.ts\n\n File artifact (uploaded to /workspace/apps/api/scripts/provision-user.ts):\n import postgres from \"postgres\";\n const sql = postgres(process.env.DATABASE_URL!);\n const hash = await Bun.password.hash(process.env.PASSWORD!, { algorithm: \"argon2id\" });\n const email = process.env.EMAIL!.toLowerCase();\n const [org] = await sql\\`INSERT INTO organizations (name) VALUES (\\${email + \"'s Org\"}) RETURNING id\\`;\n const [user] = await sql\\`\n INSERT INTO users (auth_provider, provider_id, primary_email, password_hash, email_verified_at)\n VALUES ('email', \\${email}, \\${email}, \\${hash}, now()) RETURNING id\\`;\n await sql\\`INSERT INTO org_memberships (user_id, org_id, role) VALUES (\\${user.id}, \\${org.id}, 'admin')\\`;\n const loginUrl = \\`\\${process.env.SANDBOX_PUBLIC_URL ?? ''}/login\\`;\n // Echo password back so credentialOutputConfig (below) can resolve $output.password.\n // Output keys referenced by credentialOutputConfig.fieldMapping are auto-redacted\n // from stored stdout/logs by the platform — safe to include.\n console.log(JSON.stringify({ ok: true, email, password: process.env.PASSWORD, userId: user.id, orgId: org.id, loginUrl }));\n await sql.end();\n\nLINKING ACTIONS TO CANARY CREDENTIALS:\n\n When you want a successful action to also produce a credential in the\n property's vault (so it can drive browser logins, seed workflows, etc.),\n attach a credentialOutputConfig. The platform resolves \\`$output.<key>\\`\n tokens against the action's last-line JSON stdout, then upserts a\n credential scoped to (sandboxActionId, environmentId) — re-running the\n action replaces the credential rather than duplicating it.\n\n Provide it with --credential-output-config on action create/update:\n\n canary sandbox action create <template-id> provision-credential \\\\\n --label \"Provision Credential\" \\\\\n --script ./scripts/provision-user.sh \\\\\n --input-schema ./schemas/provision-user-input.json \\\\\n --credential-output-config ./schemas/provision-user-credential.json\n\n Example provision-user-credential.json (matches the script above):\n {\n \"fieldMapping\": {\n \"username\": \"$output.email\",\n \"password\": \"$output.password\"\n },\n \"loginUrl\": \"$output.loginUrl\",\n \"loginInstructions\": \"Sign in via the email/password form.\"\n }\n\n Field-mapping values are either literal strings or \\`$output.<key>\\` tokens\n resolved from the action's parsed JSON output. Output keys referenced by\n fieldMapping/loginUrl are automatically masked in stored stdout and result.\n\n After execution, \\`canary sandbox action execute\\` prints the linked\n credentialId, and \\`canary credential list --property <name>\\` shows the\n vault entry. \\`canary credential test <credId>\\` runs the browser login\n against the sandbox URL.\n\n Example — simple SQL-only action (inline is fine for short commands):\n #!/bin/bash\n set -euo pipefail\n cd /workspace\n sb-compose exec -T app-db psql -U app -d appdb -t --csv -c \\\\\n \"INSERT INTO users (username, password_hash, role)\n VALUES ('$INPUT_USERNAME', crypt('$INPUT_PASSWORD', gen_salt('bf')), '$INPUT_ROLE')\n RETURNING id\" | while read -r user_id; do\n echo \"{\\\\\"userId\\\\\":\\\\\"$user_id\\\\\",\\\\\"username\\\\\":\\\\\"$INPUT_USERNAME\\\\\",\\\\\"loginUrl\\\\\":\\\\\"https://$SANDBOX_PUBLIC_URL/login\\\\\"}\"\n done\n\n Example — seed data via app CLI:\n #!/bin/bash\n set -euo pipefail\n cd /workspace\n sb-compose exec -T app-api bun run scripts/seed.ts \\\\\n --scenario \"$INPUT_SCENARIO\" \\\\\n --count \"$INPUT_COUNT\"\n\n Example — run migrations (no params needed):\n #!/bin/bash\n set -euo pipefail\n cd /workspace\n sb-compose exec -T app-api bash -c \\\\\n 'cd /app/packages/db && bun run drizzle-kit migrate'\n echo '{\"status\":\"completed\"}'\n\n Available context variables:\n SANDBOX_PUBLIC_URL — the instance's public URL\n SANDBOX_INSTANCE_ID — the instance UUID\n\nPERSONAS (credential templates) & ACTION FLAGS:\n A \"persona\" (the customer-facing term for a credential template — \"canary\n persona\", alias \"credential-template\") is the object a credential belongs to.\n Credentials LINK to a persona and fill its declared variables ($var.USER.<NAME>).\n Wire a login action to a persona so every instance auto-provisions its credential\n INTO the persona:\n --credential-template <personaId> Link this action's credential to a persona;\n login nodes using that persona auto-provision.\n --provisioning-mode shared|unique shared = provision once & reuse (default);\n unique = one credential per run (script must\n take a runId input param).\n --run-on-provision Run automatically once per provisioned\n instance (a base seed, NOT login-triggered).\n Its --output-schema keys are exposed to flows\n as $var.SEED.<key>.\n Manage personas directly:\n canary persona create --application <app> --name <name> [--variable NAME[:sensitive]]\n canary persona link --application <app> <personaId> --credential <credId>\n\n Heads-up on \"canary credential create\": it binds to the application's DEFAULT\n environment (usually Production) and has no CLI delete. For a SANDBOX-scoped\n credential, use an action's credentialOutputConfig (scoped to the environment the\n action runs in) — see the SEED & PERSONAS section.\n\nAGENT AUTHORING:\n During template creation, the sandbox agent can publish actions alongside\n artifacts using the publish_action tool. The agent analyzes the codebase\n and generates action definitions for common operations (user creation,\n data seeding, migrations, etc.). These are stored on the template and\n available on all instances provisioned from it.\n\n publish_action accepts an optional credentialOutputConfig — for any action\n that provisions login material (e.g. a user with email/password), the agent\n should set this so the resulting credential lands in the property vault and\n can be exercised by browser automation.\n`.trim(),\n};\n\nconst EXTERNAL_TEST_RUNNER: GuideSection = {\n title: 'External Test Runner Actions',\n slug: 'external-test-runner',\n content: `\nAn external test suite (a customer's own Selenium/Playwright/etc. repo) is run\nby executing a normal sandbox ACTION on a leased instance — see the ACTIONS\nsection above for the general action contract. There is no platform-published\nrunner image and no container the platform launches on your behalf: the action\nruns the CUSTOMER'S OWN suite, however the customer's own repo already runs it\n(their own \\`docker compose\\`, their own toolchain, already checked out onto the\ninstance by however the template or the action itself gets the code there).\nThe platform's only job is to run your script and then upload whatever it\nfinds at paths you declared. This section documents the ADDITIONAL contract\nthat applies only when an action is configured as a suite's runner: a small\nset of reserved params the platform injects on every launch, and the OUTPUT\nCONFIG that tells the platform what to upload when your script finishes. Get\neither wrong and the attempt fails with a thin diagnostic trail, which is why\nthis gets its own section rather than a footnote on ACTIONS.\n\nRESERVED LAUNCH PARAMS:\n The platform supplies these four params on every launch of a runner action,\n as INPUT_<KEY> env vars, IN ADDITION TO whatever params the suite's own\n config supplies:\n runner_command → INPUT_RUNNER_COMMAND the shell command that runs the\n suite (e.g. what the suite's\n config says to run)\n runner_launch_key → INPUT_RUNNER_LAUNCH_KEY unique per attempt; name any\n container you start after this\n runner_env_json → INPUT_RUNNER_ENV_JSON JSON object of env vars the\n suite's config wants visible\n to the test process (target\n URL, capture flags, …)\n runner_timeout_ms → INPUT_RUNNER_TIMEOUT_MS wall-clock budget in ms\n\n Do NOT declare any of these four in the action's --input-schema. They are on\n a platform allowlist and are injected regardless of what the schema says —\n an action used as a suite runner may declare its OWN additional params (the\n suite fills those in from its own config), but redeclaring a reserved name\n is at best redundant and at worst risks fighting the platform-supplied\n value.\n\n A runner action also must not:\n - declare any \"required\" param the suite does not itself set (the launch\n will fail validation before the script ever starts), or\n - declare a \"sensitive\" param — the suite's stored action params are kept\n as plaintext, and a sensitive param is rejected at suite-save time.\n\nOUTPUT CONFIG — how results get back to the platform WITHOUT a curl:\n Your script does not upload anything itself. Instead the action DECLARES,\n in its outputConfig, one or more slots naming a file path your script is\n expected to leave something at when it finishes. The platform appends a\n generated EPILOGUE to your script — code you never write — that gzips\n whatever exists at each declared path and uploads it for you, minting its\n own presigned URL at the moment it uploads (so a multi-hour suite's upload\n is never signed against credentials that expired an hour into the run).\n\n Two output kinds matter for a runner:\n test-results kind: \"test-results\", format: \"mochawesome-json\"\n source: { from: \"file\", path: \"/workspace/.canary/<name>\" }\n The parsed report the platform folds into the run's\n catalog of pass/fail verdicts. Required for a runner\n action to mean anything.\n artifacts-archive kind: \"artifacts-archive\", format: \"tar-gz\"\n source: { from: \"file\", path: \"/workspace/.canary/<name>\" }\n A gzipped tar of whatever else is worth keeping —\n capture video, traces, per-test logs. Optional; a\n suite with no capture producer has nothing to put here.\n\n Every declared path MUST be under /workspace/.canary/ — that constraint is\n enforced server-side (validateOutputSlot), not a style preference, and a\n path outside it is refused at save time, not at launch.\n\n Set outputConfig the same way --credential-output-config sets a credential\n slot: a JSON body on the action's create/update call, e.g.\n \"outputConfig\": [\n {\n \"slot\": \"test-results\",\n \"kind\": \"test-results\",\n \"config\": { \"format\": \"mochawesome-json\" },\n \"source\": { \"from\": \"file\", \"path\": \"/workspace/.canary/output/mochawesome.json\" }\n },\n {\n \"slot\": \"artifacts-archive\",\n \"kind\": \"artifacts-archive\",\n \"config\": { \"format\": \"tar-gz\" },\n \"source\": { \"from\": \"file\", \"path\": \"/workspace/.canary/artifacts.tar.gz\" }\n }\n ]\n\n Note the archive path is OUTSIDE /workspace/.canary/output/. You build it by\n taring that directory, so writing it inside the directory tars the archive\n into itself — a recursion that looks fine on a small run and blows up on a\n real one.\n\nLIVE PROGRESS — two host paths the platform reads WHILE your suite runs:\n\n /workspace/.canary/output/capture/boundaries.jsonl\n One JSON line per finished test. The platform tails this every ~30s and\n appends results to the run as they happen, so a suite that is killed before\n it finishes still reports everything it got through. Without it, a run that\n never reaches its epilogue reports NOTHING — the report is written once, at\n the very end, so there is nothing left behind to recover from.\n\n On mocha you get the records for free: the platform stages a hook at\n /workspace/.canary/mocha-boundary.cjs before your action starts. Your script\n has to do two things or it stays completely inert —\n - \"require\" that file from the mocha config you pass, and\n - export COFACTOR_CAPTURE_DIR to the directory it should write into.\n\n If your suite runs inside a container, that directory is inside the\n container, so publish it to the host path above on a timer (a background\n loop copying it out every ~15s). The platform deliberately never reaches\n into your containers — it only reads the host path. Copy to a temp file and\n mv it into place, so a reader cannot catch a half-written file.\n\n /workspace/.canary/output/run.log\n Tee your suite's output here. SSM returns stdout only once the invocation\n COMPLETES and caps it around 24KB, and \"docker logs\" shows nothing when the\n suite runs under \"docker exec\" — so without this a run is a black box for\n its whole duration. Take the exit code from PIPESTATUS[0], not $?: with\n pipefail set, a failing tee would otherwise be reported as a failing suite.\n\nTHE FAILURE MODE — read this before you write the script:\n There is no bootstrap handshake to get wrong anymore, but there is a\n quieter failure this substrate makes MORE likely, not less: your script can\n exit 0 having never actually run the suite (a compose file that doesn't\n exist at the path you assumed, a toolchain not installed on this template,\n a typo in the run command), and the platform has no way to distinguish that\n from a suite that legitimately produced zero tests. If the declared\n test-results path has nothing at it when your script exits, the attempt is\n recorded as \"unable to verify\" and salvage runs — it does NOT read as a\n passing run, but it also does not tell you WHY nothing landed.\n\n When that happens, the action's own stdout/stderr (its SSM execution\n transcript) is the ONLY evidence that exists anywhere; the platform\n automatically splices it into the attempt precisely because this failure\n mode produces nothing else to look at. So: prefer a script that fails\n LOUDLY (non-zero exit, a clear stderr line) over one that silently does\n nothing and reports success.\n\n Name any container you start after $INPUT_RUNNER_LAUNCH_KEY. That gives\n your script the same idempotency the platform's own adoption check gets\n elsewhere, so a re-delivered launch can't start a second copy of the suite\n even if that check were somehow bypassed.\n\nREFERENCE SCRIPT:\n\n #!/bin/bash\n set -euo pipefail\n\n # Fail loudly rather than silently: these four are supplied by the platform\n # on every runner launch and are never declared in this action's input schema.\n : \"\\${INPUT_RUNNER_COMMAND:?missing}\"\n : \"\\${INPUT_RUNNER_LAUNCH_KEY:?missing}\"\n : \"\\${INPUT_RUNNER_ENV_JSON:?missing}\"\n : \"\\${INPUT_RUNNER_TIMEOUT_MS:?missing}\"\n\n OUT_DIR=/workspace/.canary/output\n mkdir -p \"$OUT_DIR\"\n\n # The entrypoint enforces its own budget first; this is the shell-level\n # backstop so a wedged suite can't outlive the attempt's timeout.\n timeout_s=$(( (INPUT_RUNNER_TIMEOUT_MS + 999) / 1000 ))\n\n echo \"runner: running the suite's own compose file (timeout \\${timeout_s}s)\" >&2\n\n # The suite is the customer's own — this repo's own docker-compose.yml, its\n # own toolchain, already checked out onto this template. This action does\n # not pull or run a platform-published image; it runs what is already here.\n # THE MOUNT IS LOAD-BEARING. Without it this script silently hands back\n # nothing: $INPUT_RUNNER_COMMAND writes its report to $OUT inside the\n # container (/work/output), the container is --rm, and the host never sees\n # the file — so the path outputConfig declares stays empty. Bind the host\n # handover directory onto that path and the suite writes straight through.\n #\n # If your suite instead runs in an ALREADY-RUNNING compose service (via\n # \"sb-compose exec\"), you cannot add a mount after the fact. Copy it out\n # afterwards, and do so regardless of exit code — a red suite is when the\n # report matters most:\n # sb-compose cp <service>:<in-container-report> \"$OUT_DIR/mochawesome.json\"\n timeout \"\\${timeout_s}s\" docker compose run --rm \\\\\n -v \"$OUT_DIR:/work/output\" test-runner \\\\\n sh -c \"$INPUT_RUNNER_COMMAND\" \\\\\n || echo \"runner: suite exited non-zero (this may just mean tests failed)\" >&2\n\n # No upload code below this line. The platform's generated epilogue reads\n # $OUT_DIR/mochawesome.json and $OUT_DIR/artifacts.tar.gz — whichever paths\n # this action's outputConfig declared — gzips them, and uploads them after\n # your script returns. If mochawesome.json is missing here, that is exactly\n # what the platform will see too: report the paths your outputConfig names\n # so a debugging session does not have to guess.\n echo \"runner: script complete, handing off to the platform's upload epilogue\" >&2\n\n Note what is absent: no $INPUT_RUNNER_CALLBACK_URL, no bootstrap token, no\n curl to an upload-url endpoint, no \"\\${status: launched}\" line the platform\n parses. The epilogue is appended automatically and runs after this script\n returns — writing the two files above is the entire contract.\n\nDIAGNOSING A DEAD RUNNER ATTEMPT:\n If an attempt on this substrate stops abnormally, the platform logs a remedy\n alongside the stop code:\n failed-to-start → the action produced no exit code; check the script\n itself for a syntax error or an unset reserved var\n oom → the suite exhausted the instance's memory; raise the\n template's instanceSize\n host-terminated → the sandbox host went away mid-run; check the TTL\n reaper and any scale-in activity against the lease\n unknown → the attempt ended without an observed outcome; check\n whether the host was terminated before the epilogue\n could upload anything\n Pair that remedy with the spliced action transcript. If the transcript looks\n clean but the attempt still shows no report, the near-certain cause is a\n declared outputConfig path the script never actually wrote to — check the\n path in outputConfig against the path the suite's own reporter is\n configured to write.\n`.trim(),\n};\n\nconst SEED_PERSONAS: GuideSection = {\n title: 'Seed & Personas',\n slug: 'seed-personas',\n content: `\nPopulate a demo dataset on boot and wire login personas so a sandbox is worth\ndemoing. Three moving parts: the seed pipeline, persona homing, and persona-linked\ncredentials.\n\nSEED PIPELINE (base → enrichment)\n Real seed systems ENRICH an already-populated workspace rather than invent core\n entities: a BASE seed creates the org / workspace / users / tickets, then\n ENRICHMENT layers feature data on top (enrichment fails if the base workspace is\n missing). Chain them in one script. Two guards you will hit:\n - Local-only guards: seeders refuse a non-local DB. A compose service host is\n NOT \"localhost\" — set the app's escape hatch (e.g. SEED_ALLOW_NONLOCAL_HOST=true)\n and keep ENVIRONMENT=development.\n - Seeded-rows boot guard: apps that block booting with seeded rows need a flag\n (e.g. VEDAYA_ALLOW_SEEDED_ROWS=true) in the api compose env, or a later reboot\n of the api refuses to start.\n\nRUN IT ON BOOT — detached\n Put the pipeline in the SEED SCRIPT (runs once per fresh provision, guarded by\n .canary/.seeded). A full demo seed takes minutes — far longer than the\n provision/verify health gate — so run it DETACHED and let it finish in the\n background; provision/verify only gate on app health, not on data being present:\n PG_IP=$(sb-compose exec -T app-api getent ahostsv4 postgres | head -1 | awk '{print $1}')\n sb-compose exec -d -e DATABASE_URL=postgresql://u:p@$PG_IP:5432/db app-api \\\\\n bash -c '(./scripts/demo-seed.sh; \\\\\n psql \"$DATABASE_URL\" -f /app/.canary/home-personas.sql; \\\\\n echo SEED_DONE) > /app/.canary/seed.log 2>&1'\n Tail /workspace/.canary/seed.log for your DONE marker. (Alternative: an ACTION\n with --run-on-provision runs once per instance and exposes $var.SEED.* to flows.)\n\nPERSONA HOMING — the workspace-mismatch trap\n Apps often ship dev-login \"personas\" (quick-login buttons) whose accounts get\n homed in a DEFAULT workspace — NOT the workspace your demo seed populated. So the\n persona buttons log into an EMPTY workspace. Fix: after seeding, re-home the\n persona accounts (users.workspace_id + org membership + role assignment) into the\n seeded workspace with a small idempotent SQL file (home-personas.sql above).\n dev-login typically PRESERVES an existing home workspace, so the re-home sticks\n across logins. Verify: the persona's workspace should equal the workspace holding\n the demo rows (e.g. tickets).\n\nPERSONA-LINKED CREDENTIALS (sandbox-scoped)\n Make each persona a Cofactor credential bound to THIS sandbox so flows/tests can\n log in as it:\n 1. Create the persona (credential template):\n canary persona create --application <app> --name \"IT Admin\"\n 2. Add a login ACTION per persona whose script dev-logs-in the persona and\n echoes {\"email\": ..., \"loginUrl\": \"\\${SANDBOX_PUBLIC_URL}/login\"} on its\n LAST stdout line.\n 3. Attach BOTH:\n --credential-output-config (fieldMapping username=$output.email,\n loginUrl=$output.loginUrl, loginInstructions describing the login —\n often passwordless / persona-button, so instruction-driven)\n --credential-template <personaId> (auto-links the provisioned credential\n INTO the persona on every instance)\n 4. Execute the action against the instance → a credential scoped to that\n instance's environment and linked to the persona. Re-run on a new instance\n to rebind. Verify with: canary credential test <credId>\n Why not \"canary credential create\": it binds to the app's DEFAULT (Production)\n environment and cannot be deleted from the CLI. The action route above is the\n sandbox-scoped, re-runnable path.\n`.trim(),\n};\n\nconst CACHING: GuideSection = {\n title: 'Build Caching (advanced)',\n slug: 'caching',\n content: buildCaching.trim(),\n};\n\nexport const SANDBOX_GUIDE_SECTIONS: GuideSection[] = [\n GOAL,\n RESEARCH,\n ARTIFACTS,\n ACTIONS,\n EXTERNAL_TEST_RUNNER,\n SEED_PERSONAS,\n VERIFICATION,\n CHECKLIST,\n CACHING,\n];\n","External-run diagnosis is a customer-accessible workflow. It requires a normal\nCofactor login with external-run read access; it does not require psql, Sentry,\nAWS, Redis, or superadmin commands.\n\nStart with:\n\n cofactor external-run get <run-id>\n cofactor external-run failures <run-id> [--area <label|index>]\n cofactor external-run triage <run-id> [--area <label|index>]\n cofactor external-run skips <run-id>\n cofactor external-run debug overview <run-id>\n\nUse the result rows to answer what the test framework observed. Use lifecycle\ntelemetry to answer whether the harness timed out, retried, or recovered. Use\nartifacts and a focused rerun to distinguish an application bug from a test or\nharness issue.\n\nCofactor's own AI findings are readable too: `triage` groups a run's failures\ninto ranked findings, and `diagnosis --item` shows one test's root-cause\ndiagnosis (see the `ai-findings` section). Treat both as leads to verify\nagainst the evidence, not as conclusions. A developer or local coding agent can\ninspect the materialized workspace alongside the customer's test source and\napplication source.\n","Create a bounded local workspace:\n\n cofactor external-run debug materialize <run-id>\n\nThe default downloads run logs plus first-failure screenshots and DOM evidence\nfor failed/flaky scenarios. Video is excluded by default because it is large:\n\n --include-video\n --artifacts none|failures|all\n --out <directory>\n\nThe output directory must be new or empty; the command refuses to overwrite an\nexisting workspace.\n\nNarrow the workspace when one scenario or one area is under investigation:\n\n cofactor external-run debug materialize <run-id> --item <item-id-or-text>\n cofactor external-run debug materialize <run-id> --area <label|index>\n\nEvery selected scenario with a Playwright trace gets a full diagnostic\nworkspace under traces/<scenario>/: timeline/test-steps.jsonl (the test's step\ntree with times, source locations and the failing step's error),\nbrowser/actions.jsonl with a DOM, screenshot and accessibility snapshot after\neach call, browser/error-context.md (Playwright's page snapshot at the failure),\nlogs/test-output.jsonl (stdout/stderr), source/, and the raw browser/trace.zip.\n\nFor one test's trace alone:\n\n cofactor external-run trace <run-id> --item <item-id-or-text> [--out <dir>]\n\nOnly retried attempts are traced unless the suite records every attempt, so a\ntest that failed without retrying has no trace; work from its error, steps,\nlogs and video.\n\nThe workspace contains summary.md, manifest.json, JSONL result rows, folded\nscenarios, skip explanations, lifecycle telemetry, scenario histories,\ndiagnostics, and an artifact manifest. Downloads are best-effort: a retained row\nwhose artifact has expired is recorded as unavailable instead of aborting the\nwhole materialization.\n\nA scenario whose test reported its own stdout, stderr, or a text attachment\n(tags and source location included) gets output/<scenario>.txt — Playwright's\nprimary-source capture of what the test itself printed, distinct from the\ncontainer's run.log under logs/.\n","Rows are individual framework tests and attempts. Scenarios are the customer-\nmeaningful ticket/spec groups reconstructed from test keys.\n\nAttempt folding is important:\n\n failed only -> failed\n passed only -> passed\n failed then passed -> flaky\n skipped only -> skipped\n\nThe latest attempt supplies the retained row/evidence, but its verdict reflects\nall attempts. A successful retry therefore does not erase the original failure.\nScenario verdicts are worst-step-wins: failed, then flaky, then passed, then\nskipped. `resultsTruncated: true` means tests that never started may have no row;\ndo not read those counts as a complete suite.\n","Classify only after comparing the failed assertion, captured application state,\nsource, history, and a focused rerun.\n\nApplication bug:\n The expected behavior is correct, the app state contradicts it, and the same\n scenario fails reproducibly without lifecycle disruption.\n\nTest issue:\n The assertion/selector/test data is stale or incorrect, or source review shows\n the test expects behavior the product no longer promises.\n\nHarness issue:\n The runner never reached the assertion reliably: timeout, bootstrap/collection\n failure, missing/truncated results, environment unavailability, or a failure\n that disappears when the identical scenario reruns after infrastructure\n recovery.\n\nInconclusive:\n Evidence is missing or conflicting. State what is known, preserve the\n workspace, and run the smallest experiment that can separate the remaining\n hypotheses. Never convert “passed on retry” directly into “test issue”; it may\n be application flakiness or environmental instability.\n","Scenario history answers whether this is a regression, chronic flake, or one-off.\nMaterialization fetches recent history for the selected failed/flaky scenarios.\n\nCompare:\n\n - the last green revision and timestamp;\n - whether the same step failed previously;\n - whether failures alternate with passes (flakiness);\n - whether a failure began after a product or test-source change;\n - whether historical failures coincide with lifecycle timeouts.\n\nHistory is supporting evidence, not proof. A previously green test can still\ncontain a latent test bug, and a newly failing test can expose a real regression.\n","Run:\n\n cofactor external-run skips <run-id>\n\nThe CLI distinguishes only what retained data proves:\n\n filtered_by_selection\n The recorded --grep expression does not match the provider's full test\n title. This is attributable to the run selection.\n\n reported_skipped_unknown\n The provider reported a skip, but the result format does not preserve\n whether it came from an explicit .skip, a hook, conditional test code, or a\n harness decision.\n\nA skipped count can be expected for a targeted run. For an unfiltered run,\ninspect the test source and provider report before assigning intent. If\nresultsTruncated is true, remember that never-started tests are absent rather\nthan represented as skipped.\n","Run:\n\n cofactor external-run debug overview <run-id>\n\nThe overview shows the customer-safe orchestration timeline: run state, parent\nand shard job phases, attempts, timeouts, reschedules, launch completion, and\nwhether a later attempt recovered. It deliberately excludes job payloads,\narbitrary metadata, launch commands, environment variables, substrate handles,\nand cloud resource identifiers.\n\n`recovered_after_retry` means at least one shard attempt timed out and a later\nattempt for that shard completed before the run sealed. That is a real harness\nevent, but not automatically the explanation for every failed assertion in the\nrun. Correlate timestamps and scenario attempts.\n\n`collecting` is not by itself stuck. Check current phases/runAt timestamps and\npoll again. A sealed run with recovered retries completed; report the transient\ntimeout and recovery rather than calling it permanently stuck.\n","Reruns always create a new run; they never mutate historical evidence.\n\nFor a sandbox-backed suite, each shard attempt asks the configured template pool\nfor a healthy idle instance first, so pre-warmed capacity is reused when it is\navailable; otherwise the pool provisions one. The instance that ran the previous\nattempt is not reused: customer test code has executed on it, so release uses the\nterminate policy instead of returning that dirty host to the idle pool. A\nredelivered launch within the same attempt adopts its existing action by launch\nkey and does not start a duplicate sandbox.\n\nRepeat the prior selection and stream progress:\n\n cofactor external-run rerun <run-id>\n\nOverride one or both filters:\n\n --spec <path>\n --grep <provider-title-regex>\n\nClear the previous selection and run the configured suite scope:\n\n --clear-selection\n\nRe-run one area, or only what did not pass (failed, flaky and skipped tests),\neach in the area that ran it:\n\n cofactor external-run rerun <run-id> --area <label|index>\n cofactor external-run rerun <run-id> --failed\n cofactor external-run rerun <run-id> --failed --area <label|index>\n\n`--failed` needs a finished run. Tests in an area turned off since are left out.\n\nA plain `rerun <run-id>` of a run limited to some areas re-runs every area.\n`--area` re-runs the whole area and drops the earlier title filter.\n\nUse a focused rerun to test a hypothesis, not to make the original failure\ndisappear. Record the new run ID, exact selection, revision, and whether the\nbehavior reproduced. `--no-stream` is available for automation that only needs\nthe newly created run record.\n","A useful report separates observation from conclusion:\n\n 1. Run identity, revision, selection, and completeness.\n 2. Folded scenario verdicts and the first failing step/error.\n 3. Skip counts grouped by proven provenance.\n 4. Lifecycle facts: timeout/retry/recovery and affected shard attempts.\n 5. Relevant artifacts and scenario history.\n 6. Focused rerun result, if performed.\n 7. Classification: application bug, test issue, harness issue, or inconclusive.\n 8. Confidence and the next discriminating check.\n\nShare summary.md and manifest.json from the workspace. Do not paste credentials,\nraw environment variables, or private artifact URLs into tickets. The diagnostic\nAPI already omits control-plane secrets, but customer test logs may still contain\napplication data and should follow the customer's normal handling policy.\n\nFor one test's complete recorded attempt timeline, use\n`cofactor external-run get <run-id> --item <id-or-text>`. Each attempt is shown\nnewest first with its own Started, Ended and Total Time. Times are UTC; missing\nwindows say `Not recorded`, and a provider-only duration stays labeled\n`Provider Duration`. BDD reports include feature/scenario context, effective\ntags, and the authored step arguments (doc strings or data tables) retained for\nthat attempt. `--json` returns the focused item detail with its typed attempt,\nstep, and BDD fields.\n\nFor a native workflow run, `cofactor workflow runs get <workflow-id> <run-id>`\nshows Started, Ended, and Total Time from that run's recorded timestamps. An\nactive run shows Ended as `Running` and measures elapsed time from its recorded\nstart; missing historical timestamps remain `Not recorded`.\n","Cofactor diagnoses failures on its own; read what it concluded before\nrepeating the work.\n\nRun triage groups every failure of a finished suite run into ranked findings\n(Grouped failures, Real bugs, Suite maintenance), each citing the failing\nitems:\n\n cofactor external-run triage <run-id>\n cofactor external-run triage <run-id> --area <label|index> # findings citing that area\n cofactor external-run triage <run-id> --json\n\nA test's diagnosis is the root-cause analysis on its page: category, smoking\ngun, failure points, evidence and a recommendation.\n\n cofactor external-run diagnosis <run-id> --item <item-id-or-text>\n cofactor external-run diagnosis <run-id> --item <item> --thoughts # the agent's reasoning\n cofactor external-run diagnosis <run-id> --item <item> --retrigger # diagnose again\n\nBoth are model output. Confirm each claim against the trace, logs and source\nbefore repeating it, and say where you disagree.\n","import type { GuideSection } from '../types.js';\n\nimport overview from './overview.txt';\nimport materialize from './materialize.txt';\nimport results from './results.txt';\nimport classification from './classification.txt';\nimport history from './history.txt';\nimport skips from './skips.txt';\nimport lifecycle from './lifecycle.txt';\nimport reruns from './reruns.txt';\nimport reporting from './reporting.txt';\nimport aiFindings from './ai-findings.txt';\n\nexport const EXTERNAL_RUN_GUIDE_SECTIONS: GuideSection[] = [\n { title: 'External run diagnosis overview', slug: 'overview', content: overview },\n { title: 'Materialize a diagnostic workspace', slug: 'materialize', content: materialize },\n { title: 'Read results and attempts', slug: 'results', content: results },\n { title: \"Read Cofactor's triage and diagnoses\", slug: 'ai-findings', content: aiFindings },\n { title: 'Classify failures', slug: 'classification', content: classification },\n { title: 'Use scenario history', slug: 'history', content: history },\n { title: 'Explain skipped tests', slug: 'skips', content: skips },\n { title: 'Read lifecycle, retries, and timeouts', slug: 'lifecycle', content: lifecycle },\n { title: 'Run controlled reruns', slug: 'reruns', content: reruns },\n { title: 'Write an evidence-backed report', slug: 'reporting', content: reporting },\n];\n","Use these commands to find, read, and prioritize issues Cofactor has detected.\n\nLIST AND FILTER\n\n cofactor issues list\n cofactor issues list --status open --severity high --format markdown\n cofactor issues list --search \"timeout\" --application-id <uuid>\n cofactor issues list --tag checkout,regression --status open,closed\n cofactor issues list --page 2 --page-size 50\n\n Filters: --search, --severity (low|medium|high|unknown), --status\n (open|closed|not_a_bug, comma-separated), --tag (any of these tag names),\n --application-id (alias --property-id). Add --json for compact JSON, or\n --json --full for the unmodified response.\n\nREAD ONE ISSUE\n\n cofactor issues get <issueId>\n cofactor issues get <issueId> --format markdown\n cofactor issues get <issueId> --json --include console,network,repro,diagnostic-full\n cofactor issues get <issueId> --json --download ./evidence\n\n The detail view carries the diagnosis, the latest occurrence (URL, console\n and network failures, repro steps), occurrence count, first and last seen\n times, assignee, and the workflow run that surfaced it. Note the timestamps,\n URLs, error text, and HTTP status codes; you need them to confirm a root cause.\n\n It also lists the evidence items of each of the 10 newest occurrences,\n grouped by occurrence. Evidence stays on the occurrence that produced it, so\n a later sighting often has none while an earlier, diagnosed one does. Each\n item has a number, kind, description, optional detail, and a signed link\n (valid about an hour; do not paste it publicly) for each screenshot or clip.\n --download <dir> saves those files as <occurrenceId>-evidence-<n>-frame.jpg,\n -clip.mp4 and so on, so you can open the screenshots and look at them.\n\nTRIAGE ORDER\n\n 1. Start with open issues, severity high first, then by occurrence count and\n most recent last-seen time. A recurring issue outranks a one-off.\n 2. Open each candidate and read the diagnosis before judging it.\n 3. Decide one of: real and new (keep open, tag it); duplicate (merge it);\n intended behavior or noise (close as not-a-bug with a reason); fixed (close).\n 4. See `dedupe-and-close` for the commands, `logs` to find root cause, and\n `handoff` to pass a confirmed bug to someone who will fix it.\n\nFILE A NEW ISSUE\n\n Search first so you do not create a duplicate:\n cofactor issues create --check --title \"<title>\" --application-id <uuid>\n Then:\n cofactor issues create --title \"<title>\" --application-id <uuid> \\\n --url <page-url> --severity medium --body \"<markdown>\"\n Link evidence with --from-run <runId>, --code-ref <path[:line]>,\n --sentry <id|url>, or --log-query <query>. Add your own evidence items with\n --evidence <kind>:<description>, each followed by an optional\n --detail <text> and --screenshot <file> (uploaded and shown with the item):\n cofactor issues bump <id> --url <page-url> \\\n --evidence \"visual:The Pay button stays disabled\" --screenshot ./pay.png\n\nEDIT AN ISSUE\n\n cofactor issues update <id> --summary \"<markdown>\" --repro-steps \"<steps>\"\n cofactor issues update <id> --occurrence <occurrenceId> \\\n --replace-evidence \"1:visual:<corrected description>\" --screenshot ./shot.png \\\n --remove-evidence 2 --evidence \"timeline_event:<what happened>\"\n\n Edits apply to the latest occurrence unless --occurrence names another;\n `issues get` lists evidence by occurrence with the numbers these flags use.\n A replaced item is replaced whole, so pass --screenshot again to keep one.\n Evidence cannot be edited while the occurrence's diagnosis is running or\n after it failed, and re-running a diagnosis replaces hand-edited evidence.\n Every edit is recorded in the issue's audit history.\n\nLINK AN ISSUE TO THE PRODUCT MAP\n\n cofactor issues link <id> --capability CAP-FLW-003\n cofactor issues link <id> --capability CAP-FLW-003 --rule prule_<id>\n cofactor issues unlink <id> --capability CAP-FLW-003 [--rule prule_<id>]\n cofactor issues create ... --capability CAP-FLW-003 [--rule prule_<id>]\n\n A link says which capability (and, with --rule, which of its rules) the bug\n breaks. Codes resolve within the issue's application; rule ids come from\n `cofactor product-map show <code>`. `issues get` lists the links, and\n `product-map show` lists the open issues on the capability and under each\n rule. Link only behaviour the bug actually breaks: an open linked issue is\n what `product-map coverage` reads to show a capability as failing on a\n known issue (its flows red) or blocked by the issue (no flow yet), and a\n linked issue that closes flags the capability's flows for a rerun.\n","Keep the issue list honest: one issue per real problem, closed when resolved.\n\nCLOSE\n\n cofactor issues close <issueId>\n cofactor issues close <issueId> --not-a-bug --reason \"<why this is intended>\"\n\n --not-a-bug requires --reason. Cofactor records the reason as intended\n behavior so the same behavior is not reported again; write it as a clear\n statement of the rule (\"Archived projects intentionally hide the Edit\n button\"), not as an apology.\n\nREOPEN\n\n cofactor issues reopen <issueId>\n\n Use when a closed issue recurs. This also clears a duplicate link.\n\nMERGE DUPLICATES\n\n cofactor issues merge <dupId>[,<dupId>...] --into <canonicalId>\n\n The duplicates are closed, their tags are copied to the canonical issue, it\n gains a comment, and later sightings land on the canonical issue. Pick the\n oldest or best-documented issue as canonical. Confirm they share a root\n cause, not just a symptom, before merging.\n\nADD A SIGHTING\n\n cofactor issues bump <issueId> --url <page-url> [--from-run <runId>] [--body \"<note>\"]\n\n Records that the problem was seen again, attaching the run's video and trace,\n instead of filing a duplicate.\n\nTAG\n\n cofactor issues tag <issueId> --add regression,checkout\n cofactor issues tag <issueId> --remove checkout\n cofactor issues tag <issueId> --set regression\n\n Tags are referenced by name. Create a missing tag first:\n cofactor tag create --name <name> --color <color>\n\nRULES\n\n - Search (`issues list --search`, or `issues create --check`) before filing\n or closing.\n - Never close as not-a-bug without a reason you could defend to the person\n who owns the feature.\n - Add --json to any command for scripting.\n","Cross-reference an issue with your own server logs to find the root cause.\nCofactor sees the symptom in the browser; your logs show the cause.\n\n1. Get the evidence from the issue\n cofactor issues get <issueId> --json --include console,network,repro\n Write down: when it was detected (timestamp), the URL and API routes\n involved, visible error text, failed requests and their HTTP status codes,\n and any request or correlation IDs.\n\n2. Query your logs for the same window\n Use whatever your team uses (CloudWatch, Datadog, kubectl logs, a local log\n file, Sentry). Search a few seconds either side of the detection time,\n filtered by the route and the error text. Cofactor does not hold your server\n logs; ask the user where they live if you do not know.\n\n3. Correlate\n Match on timestamp, error message, HTTP status (4xx/5xx for the failed\n request), URL or route, stack trace, and request ID. A request ID lets you\n follow one request end to end.\n\n4. Report\n - State match confidence: clear, likely, or circumstantial.\n - Lay out a single timeline across the browser evidence and the logs.\n - Separate the symptom (what Cofactor caught) from the cause (what logs show).\n - Point at the specific code, service, or config to fix.\n - Say plainly when logs are missing or incomplete; do not speculate.\n\n5. Record what you found\n cofactor issues create --check ... (file only if no issue exists)\n cofactor issues bump <issueId> --url <url> --log-query \"<query>\" --log-system cloudwatch\n Attach the query with --log-query (repeatable; --log-system is one of\n sentry, cloudwatch, datadog, other), and --sentry <id|url> and\n --code-ref <path[:line]> for a Sentry issue or the defective code location.\n","Hand a confirmed bug to whoever will fix it, with everything they need.\n\nA good handoff is self-contained. Put it in the issue itself:\n\n cofactor issues bump <issueId> --url <page-url> \\\n --body \"<root cause, evidence, suggested fix>\" \\\n --code-ref src/orders/submit.ts:88 \\\n --log-query \"<the query that shows it>\" --log-system datadog\n\nInclude:\n - What is wrong and who is affected (the user-visible symptom).\n - How to reproduce it (`cofactor issues get <id> --format markdown` has the\n repro steps; link the run with --from-run <runId>).\n - The root cause if known, with --code-ref paths and --log-query evidence.\n - How you will know it is fixed: the workflow that should pass\n (`cofactor workflow run <workflowId> --stream`).\n\nThen tag the issue so it can be found (`cofactor issues tag <id> --add needs-fix`),\nand close it once the fixing change is deployed and the workflow passes. If it\nrecurs, `cofactor issues reopen <id>`.\n","import type { GuideSection } from '../types.js';\n\nimport overview from './overview.txt';\nimport dedupeAndClose from './dedupe-and-close.txt';\nimport logs from './logs.txt';\nimport handoff from './handoff.txt';\n\nexport const ISSUES_GUIDE_SECTIONS: GuideSection[] = [\n { title: 'Find and triage issues', slug: 'overview', content: overview },\n { title: 'Dedupe, close, and tag', slug: 'dedupe-and-close', content: dedupeAndClose },\n { title: 'Cross-reference an issue with server logs', slug: 'logs', content: logs },\n { title: 'Hand an issue off for a fix', slug: 'handoff', content: handoff },\n];\n","PROVE A CHANGE WITH TESTS\n\nUse this guide for a pull request that changes user-visible behavior. The pull\nrequest connects numbered acceptance criteria to passing Workflow evidence.\nPass `--org <org>` on every write to select the organization.\n\nCHOOSING PROOFS\n\nPick the proof that fits the change; one change can need several.\n\n- Functional change: a Workflow run that passes at the pushed commit. The\n procedure below covers it.\n- Performance-sensitive change: a perf run. Read `cofactor perf guide`. A\n query change, for example, needs both a functional proof and a perf proof.\n- UI change: a Workflow with `visual_checkpoint` nodes. Read\n `cofactor workflow guide visual-checkpoints`, and step 7 below for how\n attaching runs the test at the PR base and shows the checkpoint diffs.\n[cli >= 0.54.0]\n- Agent behavior: evals. Read `cofactor eval guide`, then attach a full passing\n eval run with `--eval-run` (see step 6).\n[/cli]\n\nTHE PROCEDURE\n\n1. Find coverage before writing a Workflow:\n cofactor workflow list --search \"<words>\"\n cofactor workflow get <wf_...> --format markdown\n Read the candidate assertions. If a test already walks the journey, use it.\n Never add near-duplicates or a new test for a changed filter, column or field.\n\n2. Choose the Workflow:\n - If an approved Workflow already proves the criterion without changes (the\n journey must still work, for example after a query or refactor change),\n run it as is and attach its run. It is cited as an existing test: it\n proves the journey still works, it is not new coverage, and it is never\n promoted (it is already in its suites). It cannot prove new behavior: a\n new filter, column or field needs a fork that asserts it. An approved\n fork is still treated as a fork; to cite an existing test, attach the\n approved test that is not a fork.\n - Fork an approved Workflow with\n `cofactor workflow fork fork <wf_...> --org <org>` and edit the fork.\n Never edit the approved original.\n - Create a new draft only when no existing journey fits or it needs its own\n data or role (see `cofactor workflow guide write-a-test`).\n\n3. Configure promotion on the fork or new draft before attaching evidence:\n cofactor workflow set-promotion <workflowId> --promote-on merge|never|deploy:<stage> [--target-suite <suite>]... --org <org>\n Repeat `--target-suite` for multiple suites. The command replaces the target\n suite list; omit it to clear extra suites. Promotion adds target suites and\n the default regression suite when present. An org without one can use the\n **Create default suite** button. Do not choose a layer.\n `never` archives the test at merge instead of adding it to any suite, and\n takes no `--target-suite`; use it when the proof is about the change itself\n (see CURATING THE SUITE). It has no effect on an already-approved test.\n\n4. Prove the change at the exact pushed commit. Start from a clean checkout,\n commit and push the application change, then use the pushed commit from\n `git rev-parse HEAD` as `<sha>` in `--at <sha>`. Claim that SHA and sync it:\n cofactor sandbox claim --application <app> --at <sha> --wait --org <org>\n cofactor sandbox sync <sandboxId> [--build] --org <org>\n cofactor workflow run <workflowId> --sandbox <sandboxId> --stream --org <org>\n Iterate until it passes, then run it once more after the last application or\n Workflow test change and use that passing run ID. An existing test is run the\n same way, at the pushed commit and on its current revision. For application edits,\n commit and push, then sync and rerun. Leave the checkout clean. Attach\n refuses dirty checkouts or missing commit evidence. If the run records a\n synced tree, it must equal `HEAD^{tree}`; otherwise its recorded source SHA\n must equal `HEAD`. The run must exist, belong to the cited Workflow, pass,\n and use its current revision. Attach requires a GitHub-delivered application;\n upload-delivered applications cannot supply this proof.\n\n5. After proving, commit any remaining changes and push the branch. If those\n commits change the application or Workflow tests, return to step 4 and\n prove them before proceeding. Open a DRAFT pull request and note its number\n `<n>`; attach needs that PR number.\n\n6. Write concise numbered acceptance criteria. State observable outcomes\n plainly. One run may support several criteria; multiple runs may support one\n criterion. For each evidence item, use the most specific node or assertion\n ID available and state what it proves as `Asserted that …`. Omit optional\n `--node` only when there is no specific ID.\n A performance run can prove a criterion too, and one criterion can mix\n kinds: for a query change, attach a Workflow run for the behavior and a\n performance run for the speed on the same criterion number. Run it at the pushed head SHA\n and attach it with `--perf-run <prun_...>`; one criterion can carry several\n runs, and every attached item must pass. A performance run must be completed,\n compare different base and head commits, and be at the commit you attach; a\n slower result is refused. Attach a new run at the current head, then detach\n an older stale item when the criterion should become proved again.\n [cli >= 0.54.0]\n An agent eval run can prove a criterion as well (a change to an agent's\n behavior): attach it with `--eval-run <aerun_...>`. It must be a full run\n (every case at its repeats), completed, passing its pass bar, and measured at\n the commit you attach; a partial, failing or other-commit run is refused.\n One criterion can carry any number of eval runs, each with its own model, and\n every one must pass.\n [/cli]\n\n7. After the draft PR exists, attach evidence. The server refuses a new\n criterion number without both `--text` and `--detail`; reuse the same text\n and detail for additional evidence on that number:\n cofactor proofs attach --pr <n> --application <app> --criterion <n> --text \"<criterion>\" --detail \"<why this matters>\" --test <wf_...> --run <wrun_...> [--node <most-specific-id>] [--base <sha>] --asserted \"Asserted that …\" --org <org>\n For performance evidence, use `--perf-run` instead of `--test` and `--run`:\n cofactor proofs attach --pr <n> --application <app> --criterion <n> --perf-run <prun_...> --asserted \"Asserted that …\" --org <org>\n [cli >= 0.54.0]\n For an eval run, use `--eval-run` in the same place:\n cofactor proofs attach --pr <n> --application <app> --criterion <n> --eval-run <aerun_...> --asserted \"Asserted that …\" --org <org>\n [/cli]\n An approved Workflow that is not a fork is attached the same way; the\n result says `\"existing\": true`.\n Attach exactly one evidence subject per command. A test containing any\n `visual_checkpoint` node is a UI test, including a test that also has other\n steps. Attaching it runs the same test at the PR base commit and the proof\n page shows each checkpoint's base, head, and diff. When `--base` is omitted,\n the CLI uses `git merge-base HEAD origin/<default>` when that ref is available.\n Put checkpoints before\n steps that depend on the new behavior so the base run can reach them.\n\n8. Push first, then render the criteria block:\n cofactor proofs render --pr <n> --org <org>\n The output already includes the markers. Paste the entire output into the\n PR description, verbatim, replacing any existing proof block. Never\n hand-format or edit it, and do not add a prose \"Proof notes\" section that\n restates it. It looks like this:\n\n <!-- cofactor:proof -->\n\n **Proof of acceptance criteria** · [View all proofs in Cofactor](<proof page>)\n\n 🟢 1 passed · 🟡 1 stale · ⚪ 1 manual · as of `a1b2c3d`\n\n 🟢 **1. The saved filter stays selected after a reload** · Passed\n - **Tested:** Filter bar keeps its selection · E2E run in a sandbox · [View the run](<run page>)\n - Asserted that the Status filter still shows \"Open\" after a reload.\n\n 🟡 **2. The list query is no slower** · Stale (proved at 9f8e7d6)\n - **Tested:** Performance run (prod) · [See the comparison](<perf run page>)\n - Asserted that the list query is no slower than base.\n\n ⚪ **3. The desktop client shows the badge** · Manual\n - **Tested:** Manual check, not provable with Cofactor · [See the proof](<proof page>)\n - Opened the desktop build and checked the badge.\n\n Each criterion leads with a stoplight: 🟢 passed, 🟡 passed at an earlier\n commit (stale), 🔴 failed, ⚪ shown manually because Cofactor could not prove\n it. Under it, each evidence item names the test (its user goal), how it\n ran, what it asserted, and links to it. The block is a snapshot as of the\n commit in its header: it goes stale after a push, and the live verdict is\n the `cofactor / proofs` check and the proof page. After every push, attach,\n or detach, render again and replace the block. Context for a reviewer goes\n in the criterion's `--detail` and in the `Asserted that …` line (the proof\n page shows both), not in a paragraph in the PR body.\n\n9. Inspect or revise the proof:\n cofactor proofs status --repo <owner/repo> --pr <n> --org <org>\n The repo is inferred from `GITHUB_REPOSITORY` or `git remote get-url origin`\n and can be overridden with `--repo`.\n Detaching removes a criterion and all evidence attached to it:\n cofactor proofs detach --pr <n> --criterion <n> --org <org>\n Remove just one item with `--test <wf_...>`, `--perf-run <prun_...>` or\n `--manual`:\n cofactor proofs detach --pr <n> --criterion <n> --perf-run <prun_...> --org <org>\n [cli >= 0.54.0]\n `--eval-run <aerun_...>` removes one eval run the same way.\n [/cli]\n Render again and replace the PR proof block after every attach or detach.\n\nWHEN COFACTOR CANNOT PROVE A CRITERION\n\nManual evidence is for a criterion Cofactor cannot exercise at all (for example\nit needs a desktop client, real hardware or a third-party console). If the\ncriterion is plainly outside a browser or a sandbox, go straight to it;\notherwise try a Cofactor test first. A failing, flaky or hard-to-write test is\nnever a reason for manual evidence: fix the test or the change, and never detach\nfailing evidence to replace it with manual evidence.\n\nSay what you did and why Cofactor could not prove it, from a clean checkout at\nthe pushed commit:\n cofactor proofs attach --pr <n> --application <app> --criterion <n> --text \"<criterion>\" --detail \"<why this matters>\" --manual --notes <text|@file> [--artifact <https-url>]... --org <org>\n Notes (up to 10,000 characters) and links (up to 20) are shown to everyone\n who can read the proof: keep notes to what you did and why, and redact\n secrets, tokens and customer data from anything you link. Links must be\n https URLs reviewers can open, never signed or tokenized links and never\n with credentials (an image attached to the pull request works; where your\n organization has Cofactor utilities, `cofactor utilities object upload\n <file> --public --json` returns one).\n A criterion holds one manual item, and attaching again replaces its notes\n and links, so pass the same `--notes` and every `--artifact` again. A\n criterion whose only evidence is manual passes the check with `📝` and\n `[manual]`, renders ⚪ Manual in the block, and shows a Manual badge with\n your notes on the proof page; a criterion that mixes manual with other kinds\n keeps its stoplight and is decided by its other items. Manual evidence never promotes\n anything and reads stale after a push; attach it again at the new head.\n\nCHECKS AND STALE EVIDENCE\n\nThe App publishes `cofactor / proofs`; the Actions job reports the same server\nverdict. Diagnose with a body file:\n cofactor proofs check --pr <n> --sha <head> --body-file <file> --org <org>\n Or pass `--body -` for stdin. The App queues a refreshed check after attach\nor detach. In the Actions path, rerun the check job after attaching or\ndetaching. Successful stale evidence remains successful and is\nmarked stale. Use Re-run on the Cofactor proof page for stale E2E evidence at\nthe PR head. For stale performance evidence, run it at the new head, attach\nthat run and detach the old item. Cofactor does not rerun automatically. Server and CI own promotion\ntriggers; `cofactor trigger` is not your job.\n\nNo block means no proof check; a block with no criteria fails. When the pull\nrequest has no observable acceptance criterion at all (for example a\ndependency bump or a docs-only change), print the neutral block and paste it\nin place of any proof block:\n cofactor proofs render --none \"<why this pull request has no criterion to prove>\"\nIt reports a neutral result. A criterion that only Cofactor cannot exercise\ngets manual evidence instead (above).\n\nCURATING THE SUITE\n\nEvery promoted test runs from then on, so keep the suite worth running:\n - Reuse first: attach the run of an existing test or eval that already proves\n the criterion before creating one.\n - Extend an existing test instead of duplicating its journey with a new one.\n - One test per user goal, named for that goal (what the user accomplishes,\n not the component or the ticket).\n - Promote lasting behaviour whose regression would matter.\n - Set a Workflow to `--promote-on never` when its proof is about the change\n itself: a one-time migration, backfill, redirect or flag rollout step,\n behaviour about to be removed, or a one-off visual before/after of a\n redesign.\n - Perf and eval runs never join a suite through proofs: a perf A/B or an\n eval experiment for one decision stays a run. Save a perf test only for a\n hot path (see the perf guide).\n - Don't keep a test nobody would miss if it disappeared.\n\nWHAT GOES IN THE PULL REQUEST\n\nPaste the output of `proofs render` and nothing that restates it: no \"Proof\nnotes\" section. If a reviewer needs to know why a Workflow was forked or what\nstays uncovered, say it once in the criterion's `--detail`, or make it a\ncriterion with manual evidence.\n\nUse neutral example names and never include customer names, domains, or\ncredentials. A human approves Workflows by merging the pull request; do not\napprove them yourself.\n","import type { GuideSection } from '../types.js';\n\nimport overview from './overview.txt';\n\nexport const PROOFS_GUIDE_SECTIONS: GuideSection[] = [\n { title: 'Prove a Change with Tests', slug: 'overview', content: overview },\n];\n","Use a perf test to compare two commits of the same Application in a sandbox:\nLighthouse for page loading, k6 for HTTP load, and server spans when the app\nexports OpenTelemetry (OTel). Choose a target metric and a goal before iterating.\n\nWRITE perf.yaml\n\n rounds: 7\n lighthouse:\n formFactor: desktop\n pages:\n - { name: dashboard, path: /dashboard, as: admin }\n k6:\n script: perf/dashboard.k6.js\n vus: 10\n durationSeconds: 30\n as: admin\n\n - rounds: integer 5..15, default 7. Each arm (base or head) gets that many\n measured rounds after a discarded warm-up: one Lighthouse load per page\n and a 10-second k6 pass. Measured rounds alternate base/head then head/base;\n each arm runs all Lighthouse pages before k6.\n - Configure lighthouse, k6, or both. Omit a block to skip that flavor.\n - lighthouse.formFactor: required desktop or mobile, with Lighthouse's stock\n simulated throttling. pages: 1..10 entries, each with a required name and\n path. name is 1..40 lowercase letters, digits or hyphens. path starts with\n /, cannot start with //, and cannot contain backslashes.\n - k6.script: required path relative to perf.yaml; the CLI inlines its contents.\n vus: required integer 1..200. durationSeconds: required integer 10..300.\n - as: optional persona name, per Lighthouse page or for k6. Omit for anonymous\n access. Use a persona configured in the Application's sandbox definition.\n - Configs whose estimated time for both arms exceeds 90 minutes are rejected;\n reduce rounds, pages or k6 duration.\n\nWRITE THE k6 SCRIPT\n\n Put one default function in perf/dashboard.k6.js:\n\n import http from 'k6/http';\n\n export default function () {\n http.get(`${__ENV.BASE_URL}/api/projects`, {\n headers: { Cookie: __ENV.COOKIE },\n });\n }\n\n The runner supplies BASE_URL and COOKIE as environment variables, read through\n __ENV. COOKIE contains the persona's cookie header (empty for anonymous).\n Send it on your requests as above; setting as does not add the header to your\n script. Load duration and VUs come from perf.yaml.\n\n Lighthouse runs inside the sandbox against the app's PUBLIC port URL, with\n the page persona's cookies installed in Chrome. It must finish on the requested\n path with a 2xx document status; a login redirect fails the run. k6 uses that\n same public sandbox base URL and sends the persona cookie through your script.\n\nENABLE SERVER SPANS (OPTIONAL)\n\n Instrument your app with OTel and add these values to the sandbox definition's\n top-level env: block:\n\n env:\n OTEL_EXPORTER_OTLP_ENDPOINT: http://127.0.0.1:4318\n OTEL_SERVICE_NAME: acme\n OTEL_EXPORTER_OTLP_PROTOCOL: http/protobuf\n\n Environment variables configure an instrumented app; they do not instrument it.\n Apps without OTel lose nothing from Lighthouse or k6: leave the endpoint\n undeclared and the result has otel: null and no otel rows. Declaring the endpoint\n but receiving zero spans on either arm fails the run.\n\nRUN AND READ THE VERDICT\n\n cofactor perf run --config perf.yaml --application acme \\\n --environment preview --base <base-sha> --head <head-sha> --org acme\n\n --head is required. --head and --base take what `cofactor sandbox claim --at`\n takes: for an Application built from GitHub, a full 40-character lowercase\n commit SHA; for an Application pushed as an image, a push id or a tag (for\n example nightly). Omit --base to compare against the environment's\n default-branch head (GitHub) or against the ready push before --head (pushed\n image). Omit --environment to use the Application's default environment.\n Both arms use the same Application and environment. The CLI waits for\n completion.\n\n The table shows base/head medians, delta, 95% CI and verdict. Relative rows\n compare median(head) / median(base) - 1 with a 95% bootstrap confidence interval\n (10,000 resamples). For a gated row, slower requires the entire CI above\n +10% (minEffect) AND the absolute median difference beyond its floor; faster\n requires the entire CI below -10% and the same floor. Otherwise: no_change.\n Higher requests/s is better: JSON flips its delta sign so positive means worse;\n the printed table shows throughput gains as positive.\n\n Gated rows and absolute floors:\n Lighthouse LCP: 100 ms; TBT: 50 ms, per page.\n k6 p95 latency: 5 ms; requests/s: no extra floor; error rate: 1 percentage point.\n OTel endpoint p95: 5 ms, only with data in every measured round on both arms\n and at least 30 spans per arm for that endpoint.\n\n Error-rate rows (unit ratio) and rows with any zero base-round value use an\n absolute difference instead of a ratio. Their CI must clear zero and the\n difference must exceed the floor; the 10% minEffect does not apply. Error-rate\n deltas print as +x.x pt (percentage points), zero-base timing deltas as +x ms;\n their CIs use the same units. JSON deltaPct and ci95 also hold those absolute\n units for these rows, despite the field name.\n\n FCP, CLS, speed index, bytes, p50s and insufficiently sampled OTel endpoints\n are reported, never gated. Ungated rows always have verdict no_change.\n Rule: never trust a single `faster` row of an ungated metric.\n\n The run takes the worst gated verdict: any slower wins, otherwise any faster\n wins, otherwise no_change. The table prints SLOWER for regressions; most\n ungated rows are omitted (use --json for all rows). A slower run with OTel\n includes slowest head spans and new or slower spans. Incomplete measurements,\n failed Lighthouse navigation or any k6 round above 50% errors fail the run\n instead of producing a verdict.\n\n Exit codes: 0 = faster or no_change; 3 = slower; 1 = failed run, timeout or error.\n Handle exit 3 in a shell loop. --json flattens the result into run metadata:\n id, status, verdict, baseSha, headSha, rounds, rows[], otel and tools are at the\n top level. Each row has gated, verdict, base/head median and values, deltaPct\n and ci95. For example, count regressions after capturing a completed run:\n\n cofactor perf run --config perf.yaml --application acme --head <head-sha> \\\n --org acme --json > perf-result.json\n jq '[.rows[] | select(.gated and .verdict==\"slower\")] | length' perf-result.json\n\nSAVE A DURABLE TEST\n\n cofactor perf save --config perf.yaml --name dashboard --application acme \\\n --environment preview --cron \"0 9 * * 1-5\" --timezone UTC --org acme\n cofactor perf list --org acme\n cofactor perf get dashboard --org acme\n cofactor perf run --test dashboard --head <head-sha> --org acme\n cofactor perf runs dashboard --limit 20 --org acme\n cofactor perf get <prun_id> --json --org acme\n cofactor perf delete dashboard --org acme\n\n A scheduled run measures the default branch head against its first parent\n commit (GitHub), or the default-tag push head against the previous ready push\n (pushed image), and skips a head that already has a run; \"Run now\" is never\n skipped.\n\n Save a GitHub test with --on-merge to run it when a pull request or merged SHA\n reaches the repository's default branch:\n\n cofactor perf save --config perf.yaml --name dashboard \\\n --application ExampleApp --on-merge --org ExampleOrg\n\n Merge runs fire only when a merge reaches the default branch and compare the\n merge commit with its first parent. A root commit with no first parent is\n skipped. Repeated deliveries for a test and head SHA create at most one run.\n Upload-delivered applications cannot use --on-merge. The trigger works\n without a proof record; proofs promotion and perf measurement are independent.\n\n A perf run attached as proof joins nothing at merge. Save a perf test (on a\n schedule or --on-merge) only for a hot journey or endpoint: one ongoing perf\n test each. An A/B for one change stays an ad hoc run.\n\n save replaces the saved config, schedule, and on-merge marker by name. Omit\n --on-merge to clear that marker. Without timing flags and --on-merge, the test\n is manual. For a schedule, supply exactly one timing flag: --schedule \"every weekday at 9am\",\n --cron \"0 9 * * 1-5\", --at 09:00, or --every 2h. --timezone defaults to UTC.\n --test uses the saved Application, environment and inlined script; do not pass\n --application or --environment with it. get accepts a test name, ptest_ id or\n prun_ id. runs lists newest first (--limit 1..100, default 20). delete removes\n the saved test, its automation rules and its runs.\n\n Open the Performance page (/performance) to browse saved tests and recent\n ad hoc runs, inspect a test's history and metric confidence intervals, or open\n a run's verdict table, tool versions and server spans for both arms.\n\nTHE hill-climb LOOP\n\n 0. Every new SHA or push needs a snapshot and a perf layer before its sandboxes are\n ready. Builds take minutes (up to 90), so batch changes per iteration.\n 1. Run once on the unchanged branch. If the verdict surprises you, do an A/A\n sanity check: set --base and --head to the same full SHA.\n 2. Read the worst gated row. If otel is present, inspect its endpoint rows and\n slow spans (otel.topSlowSpans.base/head in --json).\n 3. Change code, commit and push.\n 4. Measure the new commit, keeping the same base for the comparison:\n cofactor perf run --config perf.yaml --application acme \\\n --base <base-sha> --head <new-sha> --org acme\n 5. Keep the commit if faster, or no_change with a better target median. Revert\n it if slower. Lower latency is better; higher requests/s is better.\n 6. Stop when no gated row is slower and the target row is under its goal.\n\nREFERENCE\n\n - Current flags: `cofactor perf --help`\n - Sandbox definitions and personas: `cofactor sandbox guide v2`\n","import type { GuideSection } from '../types.js';\n\nimport overview from './overview.txt';\n\nexport const PERF_GUIDE_SECTIONS: GuideSection[] = [\n { title: 'Performance Tests', slug: 'overview', content: overview },\n];\n","Use an eval to measure an AI agent your product ships: run its command against a\nset of cases at a commit, grade every attempt, and get a pass rate, cost and\ntime per task. Run, grade and compare models with the CLI; no GitHub setup is\nneeded beyond the commit you evaluate. Pass `--org <org>` on every write.\n\nWHEN TO WRITE AN EVAL\n\n- An agent's behavior is the thing that can regress: a prompt, a tool set, a\n model change, a retrieval change.\n- A workflow test cannot decide it, because the output is free text or a\n judgment (a diagnosis, a summary, a support reply), not a click path.\n- You can name real cases and what a good answer looks like. Without cases,\n write a workflow test (`cofactor workflow guide write-a-test`) instead.\n- Do not eval deterministic code; unit-test it.\n\nSTART\n\n cofactor eval init <name> --application <app> --capability CAP-XXX-001\n\nThis writes evals/<name>.eval.yaml, evals/<name>.runbook.md and\nevals/.gitignore, and refuses to overwrite. Edit them, commit them with the\nagent code, then push:\n\n cofactor eval push evals/<name>.eval.yaml --application <app> --org <org>\n\nPush validates the template locally, stores it with its runbook and is\nidempotent. `cofactor eval show <name>` reads it back.\n\nTHE TEMPLATE\n\n version: 1\n name: diag-smoke # 2 to 40 lowercase letters, digits, dashes\n description: One sentence on what the agent must get right.\n proves:\n capabilities: [CAP-XXX-001] # product-map codes in the pushed application\n runbook: diag-smoke.runbook.md # path relative to the YAML\n mode: single-turn # or multi-turn (needs simulator)\n run: ./scripts/run-agent.sh # shell command at the repo root of the commit\n input:\n description: What the files in a case's input directory contain.\n require: [input.json] # every case bundle must contain these paths\n agent:\n models:\n sonnet: { provider: anthropic, model: claude-sonnet-4-6 }\n default: sonnet # aliases are what --model takes\n providers: # optional: only when the agent reads non-standard\n azure: # names (see CONFIGURE THE AGENT'S MODEL below)\n env: { apiKey: LLM_API_KEY, baseUrl: LLM_BASE_URL, model: LLM_MODEL }\n graders: # at least one\n - { id: answer-matches, kind: script, command: ./scripts/grade.sh }\n - id: quality\n kind: ai-judge\n rubric: \"At least 20 characters: what each score means.\"\n scale: 1-5 # or binary\n passAt: 4 # required for 1-5, 2 to 5\n judge: { provider: anthropic, model: claude-opus-4-8 }\n repeats: 3 # attempts per case, 1 to 7\n passAtLeast: 2 # a case passes when this many attempts pass\n passBar: 0.5 # share of cases that must pass, 0.1 to 1\n attemptSeconds: 600 # per attempt, 30 to 1700\n\nUnknown fields are rejected. Set passBar from a measured baseline and treat it\nas a regression ratchet, not an aspiration.\n\nTHE STANDARD ENVIRONMENT\n\nEvery attempt runs `run` in the repository root of the evaluated commit, in an\nisolated sandbox, with:\n\n EVAL_INPUT the case's input directory (read only what you need)\n EVAL_OUTPUT the directory to write the answer to\n EVAL_MODEL the model id for this run\n EVAL_PROVIDER anthropic or azure\n EVAL_CASE the case key\n EVAL_ATTEMPT the attempt index\n EVAL_RUN_ID the run id\n EVAL_ATTEMPT_SECONDS the time limit\n\nplus, for the selected model's provider only, a key, a base URL that routes\nmodel calls through Cofactor (which meters cost and latency) and the model id.\nBy default they use the standard names:\n\n anthropic: ANTHROPIC_API_KEY, ANTHROPIC_AUTH_TOKEN, ANTHROPIC_BASE_URL,\n ANTHROPIC_MODEL\n azure: OPENAI_API_KEY, AZURE_OPENAI_API_KEY, OPENAI_BASE_URL,\n AZURE_OPENAI_BASE_URL, OPENAI_MODEL\n\nThe key holds a short-lived token for that proxy, never a real provider key.\nThe base URL speaks the provider's own wire: the Anthropic Messages API for\nanthropic, the OpenAI v1 API (chat completions and responses) for azure.\nEvery other provider-key secret your sandbox declares (the other provider's,\nGemini's and Google's, and with an agent.providers entry also the selected\nprovider's standard names) arrives empty (set, not absent), so a call to a\nprovider you did not select fails. Plain `env:` values in your sandbox\ndefinition (not secrets) reach the attempt unchanged. Do not\nhard-code a key, a URL or a model id; read them from the environment. The case's\nexpected value is withheld from the agent and only reaches the graders.\n\nCONFIGURE THE AGENT'S MODEL\n\nMost agents need nothing here. Add an `agent.providers` entry only for a\nprovider whose agent reads names other than the standard ones (an entry for a\nprovider no model uses is an error). An entry replaces all of that provider's\nstandard names: the attempt then sets exactly these three, and nothing else.\n\n agent:\n providers:\n azure:\n env:\n apiKey: LLM_API_KEY # gets the proxy token\n baseUrl: LLM_BASE_URL # gets the proxy URL\n model: LLM_MODEL # gets the model id\n\nNames are upper case, distinct, and never EVAL_*. A mapped key name that your\nsandbox also declares as a secret is replaced for the whole attempt (with the\ntoken, or emptied when another provider runs), so never map onto a secret the\napplication itself needs. Fill the entry from the\nagent's own config code, not by guessing: find where it builds its model client\nand copy the names it reads. For example, if the agent has\n\n createAzure({\n apiKey: process.env.LLM_API_KEY,\n baseURL: process.env.LLM_BASE_URL,\n })\n const model = process.env.LLM_MODEL\n\nthen the entry above is right. Map baseUrl to the name the client really reads:\na client that ignores it calls the vendor directly with the proxy token, and\nthe call fails. If the client takes no base URL setting, add one first.\n\nWRITE THE ENTRYPOINT\n\nRead the case from `$EVAL_INPUT`, call the agent exactly as production does,\nand write the final answer to `$EVAL_OUTPUT/answer.<ext>` (answer.json for\nstructured output, answer.txt for prose). The first `answer.*` file in sorted\norder is graded (so write exactly one), up to 256 KiB. No answer file is a\nfailed attempt. Anything the script prints goes to stdout.log in the output\ndirectory, which helps when debugging; do not print secrets or dump the\nenvironment.\n\n #!/usr/bin/env bash\n set -euo pipefail\n bun run scripts/my-agent.ts \\\n --input \"$EVAL_INPUT/input.json\" --out \"$EVAL_OUTPUT/answer.json\"\n\nRun the real code path, not a mock of it. Exit non-zero when the agent breaks\n(it is recorded for debugging; the answer file decides the grade).\n\nCHOOSE GRADERS\n\nScript first. Use a judge only for what code cannot decide. A case passes an\nattempt only when every grader passes.\n\n- Script grader: runs after the agent exits, with EVAL_INPUT, EVAL_OUTPUT and\n EVAL_EXPECTED (the case's expected value as a JSON file), in the repo root,\n with a fixed 120 second limit. Its last stdout line must be\n {\"pass\": true, \"score\": 1, \"reason\": \"why\"}. Anything else is a grader\n error, which is reported as an error, never as a pass or a fail. Use scripts\n for categories, ids, exact fields, schema checks and state.\n- ai-judge grader: scores a rubric against the answer and the expected value.\n Use binary for a fact (\"names the same cause: yes or no\") and 1 to 5 for a\n quality (completeness, tone), with the meaning of each score in the rubric\n and `passAt` set. Always pin the judge model; never leave it to a default.\n Judge the content, not the wording.\n\nBUILD CASES\n\nEach case is an input directory, an expected JSON value, and a provenance.\n\n cofactor eval add-case <name> --key <k> --input <dir|file.tar.gz> \\\n --expected <file|-> --source \"<provenance>\" [--title <t>] --org <org>\n\n- Read the runbook first: `cofactor eval runbook <name>` works with no checkout.\n- One idea per case. A case that tests three things hides which one broke.\n- Prefer real inputs. Sanitize them before upload: no customer names, domains,\n people, tokens or ids. Use neutral names (Acme, example.com). Customer data\n stays in the database, never in a case.\n- Label from something other than the agent's own output (a human decision, a\n fix that landed, a recorded outcome). Say what corroborates it in `--source`.\n- When no real case exists, mutate a frozen real input and mark it synthetic in\n `--source`.\n- Add every failing case you find, as you find it. The set should grow from\n real misses.\n- `--input` takes a directory (tarred deterministically) or a .tar.gz; the\n template's `input.require` paths are checked first. `--expected -` reads\n stdin. A bundle already stored is deduplicated.\n- `cofactor eval cases <name>` lists them; `cofactor eval rm-case <name> <key>`\n removes one no run has used.\n\nMULTI-TURN\n\nSet `mode: multi-turn` and add `simulator: { provider, model }` (pinned). `run`\nis then the agent process, not a one-shot command. It speaks JSONL on stdin and\nstdout: read one line per user message, answer with one line.\n\n stdin {\"user\": \"message\"}\n stdout {\"reply\": \"answer\"}\n either side may end it with {\"done\": true}\n\nNo answer file is needed in multi-turn mode; the graded input is the\nconversation. stdout belongs to the protocol, so any other line you print on\nstdout (a debug message, a log) is a protocol error that errors the attempt;\nlog to stderr. An agent that exits before replying, or writes an over-long line,\nis also a protocol error. As in single-turn mode, a multi-turn attempt that runs\nout of time is not an error: it is graded on the turns completed so far, and\ncounts as a fail if none completed. Multi-turn output also lands in\nstdout.log. A protocol error is not retried.\n\nEach case needs a conversation: `--conversation <file.json>`, either\n`{\"kind\":\"scripted\",\"turns\":[\"first message\", \"second message\"]}` or\n`{\"kind\":\"simulated\",\"persona\":\"…\",\"goal\":\"…\",\"facts\":[\"…\"],\"stopWhen\":\"…\",\n\"maxTurns\":8}`. A simulated user is another model playing the persona, and the\ngoal and facts never reach the agent. Prefer a script grader that inspects the\nresulting state; use a judge over the transcript for process rules. Read a\ntranscript with `cofactor eval get <run-id> --case <key> --transcript`.\n\nRUN\n\n cofactor eval run <name> [--model <alias>]... [--environment <env>]\n [--at <sha>] [--case <key>]... [--repeats N]\n [--parallel N] [--no-wait] --org <org>\n\nOne run per `--model` (none means the default). Attempts run `--parallel` at a\ntime; raise it before cutting cases when a large set times out. Cost and time\ngrow with cases x repeats x models. It waits, then prints a\nsection per model and a comparison grid. `--at <sha>` evaluates a commit; push\nthe commit first. `--case` (a subset of cases) and a `--repeats` that differs\nfrom the template make a partial run, useful for iterating, which never counts\nas coverage or proof. Ids are\nprinted to stderr first; `--no-wait` prints them and exits 0.\n\nEXIT CODES (run)\n\n 0 every run's verdict is pass\n 3 at least one run's verdict is fail (the agent missed the pass bar)\n 1 a failed or cancelled run, a polling timeout, or any error\nExit 1 wins over 3: if any run failed or was cancelled, the code is 1 even when\nanother run's verdict is fail. A polling timeout leaves the runs going; read\nthem later with `cofactor eval get`. All other sub-commands exit 0 on success\nand 1 on error. Gate on 0.\n\nREAD RESULTS\n\n cofactor eval get <run-id> summary, cases, grader reasons\n cofactor eval get <run-id> --case <key> one case, every attempt\n cofactor eval runs <name> [--limit N] recent runs\n cofactor eval list evals and their latest verdicts\n\nA case passes when `passAtLeast` of its `repeats` attempts pass; the eval\npasses when the share of passing cases reaches `passBar`. Read the grader\n`reason` before changing anything.\n\nA single-turn attempt is graded on its answer file. The agent command's exit code\nand a timeout are recorded but do not by themselves fail the attempt: a crash\nor a timeout that left no `answer.*` is a counted fail (\"no answer written\"),\nand one that left a good answer can still pass. An attempt is errored, not\nfailed, when a grader failed (a script grader printed no parsable last line or\nran past its limit, since its own exit code is ignored and only its last line\ncounts; or an ai-judge call failed), the answer file is over 256 KiB, a\nmulti-turn protocol error occurred, or the sandbox failed (infrastructure\nfailures are retried once). One errored attempt makes the whole run `failed`\nwith no verdict, however many cases passed, and `run` exits 1, not 3. Fix the\ngrader, answer size or infrastructure problem and run again; the error text\nnames the case and attempt. The summary counts errored attempts as\n`infraFailures`.\n\nCOMPARE MODELS\n\n cofactor eval run <name> --model sonnet --model haiku --org <org>\n cofactor eval compare <run-id> <run-id>\n cofactor eval compare --batch <id>\n\nOne grid, one row per case, one column per model, with pass rate, cost per task\nand time per task. Compare on the same commit and the same cases; the output\nshows the commit and a digest of the case set. Only a full run of the\ntemplate's default model alias counts as coverage; runs of other aliases are\nfor comparing.\n\nLINK WHAT IT PROVES\n\n- Capabilities: `proves.capabilities` in the template links them when you push.\n Add or remove links later with\n `cofactor product-map capability link-eval <CODE|id> --eval <name> --application <app> --org <org>`\n (and `unlink-eval`), but `eval push` makes `proves.capabilities` the source\n of truth: the next push unlinks any capability not listed there. Edit the\n template, not the link.\n- Rules: `cofactor product-map rule link-eval <prule_…> --eval <name> --org <org>`\n ties an eval to a rule in a capability (and `unlink-eval`). The rule must be\n active, its capability must sit in an area, and that capability and area must\n belong to the eval's application. Pushing does not touch rule links.\n- An agent as a product-map area, for what an agent can do and evals prove:\n `cofactor product-map area create <title> --kind agent --application <app> --org <org>`.\n- A pull request: attach a full run at the PR head commit with\n `cofactor proofs attach --pr <n> --application <app> --criterion <n> --eval-run <aerun_…> --asserted \"Asserted that …\" --org <org>`.\n The run must be completed, at the commit you pushed, full (not partial) and\n passing its pass bar. The default-alias rule above is about product-map\n coverage only; it does not apply to attachment: no model is privileged, and a\n full passing run of any model alias proves the criterion (each item shows its\n model). Attach as many runs as you have; every one must pass. Read\n `cofactor proofs guide` for the whole procedure.\n\nTHE HILL-CLIMB LOOP\n\nThe hill-climb loop uses `--json` to climb on measured cases, not on feel.\n\n1. Run the eval at the current commit and note the pass rate.\n cofactor eval run <name> --json --org <org> > run.json\n2. List the failing cases (run --json is an array, one entry per --model;\n .[0] is the first model. A run whose status is failed has a null verdict:\n read .[0].error, and the attempts in .[0].cases[].attempts[] whose status is\n error. A run that never produced a result prints only id, status, verdict and\n error, with no `cases`):\n jq '[.[0].cases[] | select(.passed|not) | .key]' run.json\n For a run id, `cofactor eval get <run-id> --json` returns the result\n directly: jq '[.cases[] | select(.passed|not) | .key]'\n3. Read one case: `cofactor eval get <run-id> --case <key>`.\n4. Make one change to the agent that explains the failures, commit and push it.\n5. Iterate on the failing cases only (`--case <key>`), then run the full set at\n the new head. Keep the change only if the full pass rate goes up and cost or\n time did not regress. When it does not, revert and try another idea.\n\nNever tune `passBar`, a rubric or a grader to turn a failing run green; fix the\nagent, or fix a wrong label and say why in the commit. Never trust a pass\nfrom a partial run. Add a case for each new failure you meet.\n\nTHE RUNBOOK FORMAT\n\nEvery eval has a runbook at evals/<name>.runbook.md, stored with the eval and\nprinted by `cofactor eval runbook <name>`. Its headings are fixed so an agent\ncan rely on them. `cofactor eval init` writes all of them, each starting with a\n`<!-- cofactor:todo -->` line and a hint:\n\n # <eval name> runbook\n ## What this eval proves the agent's job in one paragraph and the capabilities it backs\n ## Anatomy of a case the files in $EVAL_INPUT, what expected holds, what a good case teaches\n ## Finding real data where in prod the inputs live: exact cofactor commands, ids, how to pick varied cases\n ## Labeling how expected is decided and what must corroborate it; never the agent's own output alone\n ## Sanitizing what to strip or replace before upload; the check to run; neutral names to use\n ## Synthesizing scenarios how to make a case when no real one exists, and how to mark it synthetic in --source\n ## Adding the case the exact eval add-case command shape for this eval\n ## Known traps what has gone wrong before\n\nWrite each section, then delete its marker line. `eval push` refuses a runbook\nwhere any section still has the marker, warns when a heading is missing, and\nerrors when \"Finding real data\" or \"Sanitizing\" is empty. Keep the runbook true:\nadd a trap when a case goes wrong.\n\nSCOPES AND ACCESS\n\nThe sandbox-v2 organization flag must be on. API keys need explicit scopes:\n\n run: sandbox:create and sandbox:exec, named\n explicitly (sandbox:* does not grant exec)\n push, add-case, rm-case: sandbox:edit\n show, list, cases, runbook, get,\n runs, compare: sandbox:read\n\n`cofactor eval --help` lists every flag. `cofactor eval guide` needs no login.\n","import type { GuideSection } from '../types.js';\n\nimport overview from './overview.txt';\n\nexport const EVAL_GUIDE_SECTIONS: GuideSection[] = [\n { title: 'Agent Evals', slug: 'overview', content: overview },\n];\n","Drive a real browser from the CLI to explore or verify a web app. You work\nthrough snapshots (an accessibility tree plus a screenshot) and issue commands\nagainst element refs.\n\nSTART A SESSION\n\n cofactor session start --url https://app.example.com\n cofactor session start --credential (interactive picker)\n cofactor session start --credential \"Admin User\" --url https://app.example.com\n cofactor session start --storage-state ./auth.json --url <url>\n cofactor session start --headless --url <url>\n\n Credentials belong to an application and carry saved login state, so a\n session started with --credential is already signed in. Load one into a\n running session with `cofactor session load-credential`.\n\n Credentials only work for the deployed app they were saved against. Their\n cookies are bound to that domain. For localhost or 127.0.0.1, start without\n --credential, ask the user to sign in in the browser window, and wait for\n them to confirm before continuing (or pass --storage-state with a local file).\n\nMANAGE SESSIONS\n\n cofactor session list\n cofactor session status\n cofactor session stop (or: stop --all)\n cofactor session snapshot --session s1 (target one when several exist)\n\n Always stop sessions you started when you finish.\n\nTHE LOOP\n\n snapshot -> read -> act -> snapshot -> verify\n Never assume an action worked; look at the page afterwards.\n Run `cofactor session --help` for the full command list.\n","Look at the page, act on it by ref, then check the result.\n\nSNAPSHOT\n\n cofactor session snapshot tree + screenshot\n cofactor session snapshot --search \"Submit\" find elements fast\n cofactor session snapshot --search \"Email|Password\"\n cofactor session snapshot --expand e50 open a collapsed section\n cofactor session snapshot --mode tree tree only\n cofactor session snapshot --mode screenshot image only\n\n The tree lists elements with refs like [ref=e15]. Use those refs in every\n command below. --search is case-insensitive and OR-matched; use it to locate\n elements, not to judge visual state (the highlight can hide subtle changes).\n Collapsed regions show \"(127 elements - use expand=e50 to see content)\".\n\nACT\n\n cofactor session click --ref e15 --element \"Submit button\"\n cofactor session type --ref e7 --text \"hello\" --element \"Search box\" --submit\n cofactor session fill --fields '[{\"ref\":\"e3\",\"value\":\"John\"},{\"ref\":\"e4\",\"value\":\"j@example.com\"}]'\n cofactor session select --ref e12 --value admin --element \"Role dropdown\"\n cofactor session press --key Enter\n cofactor session hover --ref e8 --element \"User menu\"\n cofactor session scroll --direction down --amount 5\n cofactor session click --x 450 --y 300 --element \"Canvas\" (last resort)\n\nNAVIGATE AND WAIT\n\n cofactor session navigate --url https://app.example.com/settings\n cofactor session back\n cofactor session wait --text \"Saved\" wait for text to appear\n cofactor session wait --text \"Loading...\" wait for text to disappear\n cofactor session tabs --action list|new|select|close [--index n]\n\n Prefer waiting on expected text over fixed delays.\n\nCAPTURE AND INSPECT\n\n cofactor session screenshot --label before-submit [--full-page]\n cofactor session console --only-errors\n cofactor session network\n cofactor session evaluate --js \"() => document.title\"\n cofactor session downloads / read-download\n\n `evaluate` reads page state only. Use `run-code` (driver-side Playwright)\n when you need real click and keyboard events.\n\nVERIFY\n\n After each action: snapshot, search for the success or error message\n (\"Success|saved\", \"Error|failed|invalid\"), check console and network for\n failed requests, and report exactly what you saw, including refs and error\n text.\n","SIGNING IN WHEN THE SESSION IS LOGGED OUT\n\n If a snapshot shows a login page, never ask for or look up the password. Put\n a credential placeholder where the value goes. The CLI resolves it just\n before typing, and the password is redacted from all output of that session\n for its lifetime, so it stays out of your context:\n\n cofactor session fill --fields '[{\"ref\":\"e3\",\"value\":\"$CREDENTIAL(\\\"Admin User\\\").username\"},{\"ref\":\"e4\",\"value\":\"$CREDENTIAL(\\\"Admin User\\\").password\"}]'\n cofactor session type --ref e4 --text '$CREDENTIAL(\"Admin User\").password' --submit\n\n The name is the credential's name or id; add --application <name|id> if two\n credentials share a name. This works in any session, whether or not it was\n started with --credential. If you work across organizations, add --org <org>.\n\n Afterwards, `cofactor session save-credential` stores the fresh login for the\n next session.\n\n Limits:\n - Usernames are shown, since they identify the account you are signed in as.\n - Redaction prevents accidental exposure; it does not stop an agent that\n deliberately runs `cofactor credential get`. Do not do that.\n - Screenshots are not redacted.\n - Use the placeholder only on the credential's own login page. Typing it\n anywhere else sends the password to that page.\n - \"[credential redacted]\" may appear in unrelated text, because matching is\n prefix-based.\n","Use this when a snapshot contains the expected word but the control you expect\ncannot be found or clicked.\n\nSTRATEGY: WEAK ASSUMPTIONS TO STRONG EVIDENCE\n\n 1. Search the shortest distinctive term across every element.\n 2. Rank matches by direct evidence: aria-label, title, short visible text,\n then data-testid.\n 3. Inspect only the matching element's local neighborhood.\n 4. Check both directions: the nearest interactive ancestor and any\n interactive descendants.\n 5. Add role, tag, and uniqueness constraints only after observing structure.\n\n Do not search for \"button\", \"input\", or another presumed tag first.\n Components often put labels or tooltips on wrappers and nest an icon-only\n control inside.\n\nLIVE DOM\n\n Start with the semantic term:\n cofactor session snapshot --search \"Delete\"\n\n Find direct DOM signals without assuming a tag (keep text matches short so\n large containers do not dominate):\n cofactor session evaluate --js \"() => {\n const needle = /delete/i;\n return [...document.querySelectorAll('*')].flatMap((el) => {\n const text = el.children.length === 0 ? el.textContent?.trim() : '';\n const signals = {\n ariaLabel: el.getAttribute('aria-label'),\n title: el.getAttribute('title'),\n text: text && text.length <= 120 ? text : null,\n testId: el.getAttribute('data-testid'),\n };\n return Object.values(signals).some((v) => v && needle.test(v))\n ? [{ tag: el.tagName, signals, html: el.outerHTML.slice(0, 1200) }]\n : [];\n });\n }\"\n\n Then inspect the match in both directions, because the label may sit outside\n the clickable control:\n cofactor session evaluate --js \"() => {\n const match = document.querySelector('[aria-label*=\\\"Delete\\\" i]');\n const interactive = 'button, a, input, select, textarea, [role=\\\"button\\\"], [tabindex]';\n return {\n match: match?.outerHTML,\n parent: match?.parentElement?.outerHTML.slice(0, 1600),\n interactiveAncestor: match?.closest(interactive)?.outerHTML,\n interactiveDescendants: [...(match?.querySelectorAll(interactive) ?? [])]\n .map((el) => el.outerHTML),\n };\n }\"\n\n Choose the interaction from what comes back:\n label on the button: getByRole('button', { name: /delete/i })\n label on a wrapper with a button: locator('[aria-label*=\"Delete\" i]').locator('button')\n text inside a button: getByRole('button').filter({ hasText: /delete/i })\n Perform real interactions with `session run-code`; use `evaluate` only to\n inspect.\n\nNEGATIVE-EVIDENCE GATE\n\n Do not report a control as missing unless all of these hold in the expected\n UI state:\n - The screenshot does not show it.\n - The snapshot has no matching term, icon description, tooltip, or nearby\n control.\n - The live DOM has no matching label, title, short text, test ID, or state\n transition.\n If the term exists but the expected role does not, report a locator or\n accessibility-structure mismatch, not missing functionality.\n","COMMON PROBLEMS\n\n Element not in the tree, or several look identical\n Take a screenshot-only and a tree-only snapshot and compare. If you can see\n it but the tree cannot, run the element-discovery steps (`session guide\n element-discovery`). Report the gap as a testability suggestion: missing\n aria-label, clickable div instead of button, unlabeled input, repeated\n generic rows without a test ID. Group suggestions at the end of your report.\n\n Click did nothing\n Snapshot again; an overlay, dialog, or stale ref is the usual cause. Try\n `session dismiss`, `session dialog --action accept|dismiss`, or scroll the\n element into view. Refs change after the page changes: re-snapshot before\n reusing one. Coordinates are the last resort.\n\n Page looks stuck\n A slow page is not a broken page. `session wait --text \"<expected>\"\n --timeout 30000`, then check `session network` for pending or failed\n requests and `session console --only-errors` for script errors.\n\n Logged out or wrong user\n Reload credentials with `session load-credential`, or sign in with a\n $CREDENTIAL placeholder (`session guide sign-in`). For localhost, ask the\n user to sign in.\n\n Multiple sessions\n Pass --session <id|name> on every command, or `session stop --all` and\n start clean.\n\n Screenshots cannot be read automatically\n `cofactor session allow-screenshots` grants read access to the screenshot\n folder (Claude Code).\n\nRULES\n\n - Never print, request, or write down a password.\n - Snapshot after every action.\n - Stop the sessions you start.\n","import type { GuideSection } from '../types.js';\n\nimport start from './start.txt';\nimport interact from './interact.txt';\nimport signIn from './sign-in.txt';\nimport elementDiscovery from './element-discovery.txt';\nimport troubleshooting from './troubleshooting.txt';\n\nexport const SESSION_GUIDE_SECTIONS: GuideSection[] = [\n { title: 'Start and manage a browser session', slug: 'start', content: start },\n { title: 'Snapshot, act, and verify', slug: 'interact', content: interact },\n { title: 'Sign in with credential placeholders', slug: 'sign-in', content: signIn },\n { title: 'Element discovery', slug: 'element-discovery', content: elementDiscovery },\n { title: 'Troubleshooting', slug: 'troubleshooting', content: troubleshooting },\n];\n","/**\n * Strip what makes untrusted text act on its reader instead of being read:\n * terminal escape sequences (CSI, terminated OSC, other ESC), C0/C1 controls\n * except tab and newline, bidi and zero-width characters that make text read\n * differently to a person than to a model, and Unicode tag characters, which\n * hide ASCII a model reads and a person cannot see.\n *\n * Customer text reaches a terminal through the CLI and a coding agent through\n * copied prompts; an `ESC[201~` in a Playwright error could end a bracketed\n * paste early and turn the rest of a prompt into keystrokes.\n */\nconst CONTROL_CHARACTERS = new RegExp(\n [\n // CSI, OSC with its terminator, and two-byte escapes. An unterminated OSC\n // falls to the two-byte branch: it loses its `ESC ]` introducer, not the\n // rest of the text.\n '\\\\u001b(?:\\\\[[0-?]*[ -/]*[@-~]|\\\\][^\\\\u0007\\\\u001b]*(?:\\\\u0007|\\\\u001b\\\\\\\\)|[@-Z\\\\\\\\-_])',\n // C0 except tab/newline, DEL and C1.\n '[\\\\u0000-\\\\u0008\\\\u000b-\\\\u001f\\\\u007f-\\\\u009f]',\n // Soft hyphen, Arabic letter mark, Mongolian vowel separator.\n '[\\\\u00ad\\\\u061c\\\\u180e]',\n // Zero-width space/joiners and LRM/RLM; bidi embeddings and overrides;\n // word joiner, invisible operators and isolates; BOM.\n '[\\\\u200b-\\\\u200f\\\\u202a-\\\\u202e\\\\u2060-\\\\u2064\\\\u2066-\\\\u2069\\\\ufeff]',\n // Tag characters U+E0000–E007F (surrogate pairs; the regex has no `u` flag).\n '\\\\udb40[\\\\udc00-\\\\udc7f]',\n ].join('|'),\n 'g'\n);\n\nexport function stripControlCharacters(value: string): string {\n return value.replace(CONTROL_CHARACTERS, '');\n}\n","/**\n * Brand resolution for the dual-published CLI.\n *\n * We ship a single build to two npm packages:\n * - `@canaryai/cli` → binary `canary` (legacy brand)\n * - `@runcofactor/cli` → binary `cofactor` (new brand)\n *\n * Both packages contain the identical artifact; they differ only in their\n * `name` and `bin` map (rewritten at publish time — see\n * `.github/workflows/publish-canary-cli.yml`). Everything brand-specific is\n * therefore derived at runtime:\n *\n * - `command` comes from the invoked binary name (`basename(process.argv[1])`),\n * so output reflects how the user called it (`brandText` rewrites legacy\n * `canary` display strings to the active brand).\n * - `packageName` is always `@runcofactor/cli`: the only package self-upgrade\n * and the update check should ever target. It is a constant, not read from\n * the installed package.json, so no install quirk can point `cofactor\n * upgrade` at the legacy package. A `canary` invocation prints a rename\n * notice (see `rename-notice.ts`) and `canary upgrade` installs this package.\n *\n * The legacy `canary` brand is the default for every non-`cofactor` invocation\n * (`node dist/index.js`, dev scripts, tests), which keeps existing behaviour\n * byte-for-byte unchanged.\n *\n * @module\n */\n\nimport { createRequire } from 'node:module';\nimport path from 'node:path';\nimport process from 'node:process';\nimport { stripControlCharacters } from '@chatsdet/content';\n\nconst require = createRequire(import.meta.url);\nconst pkg = require('../package.json') as { version: string };\n\nexport type Brand = 'canary' | 'cofactor';\n\n/**\n * Resolve the brand from the invoked binary path (`process.argv[1]`). The\n * global-install shim is named `canary` or `cofactor` (npm/bun/pnpm bin link),\n * so the basename is the brand. Everything else (`node dist/index.js`, dev\n * scripts, tests) defaults to the legacy `canary` brand. Pure for testing.\n */\nexport function resolveBrandFromArgv(argv1: string | undefined): Brand {\n if (argv1) {\n const base = path.basename(argv1).replace(/\\.[^./\\\\]+$/, '').toLowerCase();\n if (base.includes('cofactor')) return 'cofactor';\n }\n return 'canary';\n}\n\n/** The invoked command / binary name, e.g. `canary` or `cofactor`. */\nexport const command: Brand = resolveBrandFromArgv(process.argv[1]);\n\n/** The npm package to install, upgrade and check for updates, whichever binary ran. */\nexport const packageName = '@runcofactor/cli';\n\n/** CLI version, read from package.json. */\nexport const version: string = pkg.version;\n\n/**\n * Rewrite display strings authored against the legacy `canary` brand to the\n * active brand. Identity for the `canary` brand (so legacy output never\n * changes). Two precise, word-anchored rules:\n *\n * 1. The invocation token `canary ` followed by a lowercase letter (i.e. a\n * sub-command or `canary v1.2.3`) or `<` (a usage placeholder like\n * `canary <command> --help`). Anchored on a non-word, non-hyphen\n * boundary so it never touches `trycanary.ai`, `x-canary-request-id`,\n * `canary-cli`, or `CANARY_API_TOKEN`.\n * 2. The prose noun `Canary` (capitalized, standalone word) → display name.\n */\nconst INVOCATION_RE = /(^|[^\\w-])canary(?= [a-z<])/g;\nconst PROSE_RE = /\\bCanary\\b/g;\n\n/** Pure brand rewrite for a given target brand. Identity for `canary`. */\nexport function brandTextFor(text: string, brand: Brand): string {\n if (brand === 'canary') return text;\n const display = brand === 'cofactor' ? 'Cofactor' : 'Canary';\n return text\n .replace(INVOCATION_RE, (_m, prefix: string) => `${prefix}${brand}`)\n .replace(PROSE_RE, display);\n}\n\nfunction brandText(text: string): string {\n return brandTextFor(text, command);\n}\n\n/**\n * Rewrite a bundled skill template's content to the target brand for the\n * `canary skill install` / `canary skill <name>` output. Builds on\n * {@link brandTextFor} (invocation tokens + the prose noun) and additionally\n * maps the brand-specific identifiers that {@link brandTextFor} deliberately\n * leaves intact for console output — but which a distributed skill file must\n * carry per brand:\n *\n * - inline binary: `` `canary` `` → `` `cofactor` `` (a backtick-wrapped bare\n * binary reference, e.g. \"the `canary` CLI\" — `brandTextFor` only rewrites\n * `canary <subcommand>` invocations, not the standalone binary name)\n * - npm package: `@canaryai/cli` → `@runcofactor/cli`\n * - env prefix: `CANARY_*` → `COFACTOR_*`\n * - domains: `*.trycanary.ai` → `*.runcofactor.com`\n *\n * Identity for the `canary` brand, so legacy skill text installs byte-for-byte\n * unchanged. The skill's frontmatter `name:` is never an invocation/prose/token\n * match (e.g. `cofactor-write-test`, hyphenated), so the install directory key is\n * preserved. Trigger keywords in prose (e.g. \"mentions canary, canary CLI\")\n * intentionally keep the legacy term so the skill still activates for users who\n * say \"canary\". Pure (brand injectable).\n */\nexport function brandSkillContentFor(content: string, brand: Brand): string {\n const base = brandTextFor(content, brand);\n if (brand !== 'cofactor') return base;\n return base\n .replace(/`canary`/g, '`cofactor`')\n .replace(/@canaryai\\/cli/g, '@runcofactor/cli')\n .replace(/\\bCANARY_/g, 'COFACTOR_')\n .replace(/trycanary\\.ai/g, 'runcofactor.com');\n}\n\n/**\n * Read a brand-prefixed environment variable, accepting EITHER brand's prefix.\n *\n * The two published brands historically diverged: the binary only ever read the\n * legacy `CANARY_*` names, but the cofactor docs tell users to set `COFACTOR_*`\n * (e.g. `COFACTOR_API_TOKEN`). This bridges that gap — a value set under either\n * prefix is honoured regardless of which binary (`canary` / `cofactor`) was\n * invoked. When both are set, the active brand's prefix wins.\n *\n * `canaryName` is the legacy `CANARY_`-prefixed name; the `COFACTOR_` variant is\n * derived from it. Empty strings are treated as unset so a blank value can't\n * shadow a real one set under the other prefix. Pure (brand + env injectable).\n */\nexport function readBrandEnvFor(\n canaryName: string,\n brand: Brand,\n env: NodeJS.ProcessEnv = process.env,\n): string | undefined {\n const cofactorName = canaryName.replace(/^CANARY_/, 'COFACTOR_');\n const order = brand === 'cofactor' ? [cofactorName, canaryName] : [canaryName, cofactorName];\n for (const name of order) {\n const value = env[name];\n if (value !== null && value !== undefined && value !== '') return value;\n }\n return undefined;\n}\n\n/** Read a brand-prefixed env var for the active brand. See {@link readBrandEnvFor}. */\nexport function readBrandEnv(\n canaryName: string,\n env: NodeJS.ProcessEnv = process.env,\n): string | undefined {\n return readBrandEnvFor(canaryName, command, env);\n}\n\n/**\n * Workflow spec file convention — brand-aware, backwards compatible.\n *\n * The spec extension and default directory follow the active brand: the\n * `cofactor` binary writes `.cofactor.md` into `cofactor/specs`, the legacy\n * `canary` binary writes `.canary.md` into `canary/specs` (byte-for-byte\n * unchanged). Reading/discovery always accepts BOTH — {@link SPEC_EXTENSIONS}\n * and {@link SPEC_DIRS} list every recognized variant (newest brand first), so\n * an existing `.canary.md` repo keeps working under either binary.\n */\nexport const SPEC_EXTENSIONS = ['.cofactor.md', '.canary.md'] as const;\n\n/**\n * Default spec-directory roots, newest brand first. Each is used as\n * `<root>/specs`. The ordering is a precedence contract: consumers that merge\n * across roots (e.g. `pull --all`) must treat earlier entries as higher\n * priority, so the active/new brand's copy wins over a legacy one.\n */\nexport const SPEC_DIRS = ['cofactor', 'canary'] as const;\n\n/** Spec-file extension to WRITE for a given brand. See {@link SPEC_EXTENSIONS}. */\nexport function specExtensionFor(brand: Brand): string {\n return brand === 'cofactor' ? '.cofactor.md' : '.canary.md';\n}\n\n/** Default spec directory (`<root>/specs`) to WRITE into for a given brand. */\nexport function specDirFor(brand: Brand): string {\n return path.join(brand === 'cofactor' ? 'cofactor' : 'canary', 'specs');\n}\n\n/** Spec-file extension to WRITE for the active brand. See {@link specExtensionFor}. */\nexport const specExtension: string = specExtensionFor(command);\n\n/** Default spec directory to WRITE into for the active brand. See {@link specDirFor}. */\nexport const specDir: string = specDirFor(command);\n\n/**\n * Every spec directory to READ from, highest precedence first — `<root>/specs`\n * for each {@link SPEC_DIRS} root. This is the single source of the spec-dir\n * convention for discovery; consumers merge across these in order (first-seen\n * wins) so the active brand's copy beats a legacy one. Brand-independent: a\n * `cofactor` run still discovers an existing `canary/specs` repo, and vice\n * versa.\n */\nexport function readableSpecDirs(): string[] {\n return SPEC_DIRS.map((root) => path.join(root, 'specs'));\n}\n\n/** True if `name` ends with any recognized spec extension (either brand). */\nexport function isSpecFile(name: string): boolean {\n return SPEC_EXTENSIONS.some((ext) => name.endsWith(ext));\n}\n\n/**\n * Split a recognized spec extension off `name`, returning `{ base, ext }` for\n * the first matching {@link SPEC_EXTENSIONS} entry, or `null` if none match.\n * Lets callers derive sibling names (e.g. `.stamped`) without assuming a brand.\n */\nexport function splitSpecExtension(name: string): { base: string; ext: string } | null {\n for (const ext of SPEC_EXTENSIONS) {\n if (name.endsWith(ext)) return { base: name.slice(0, -ext.length), ext };\n }\n return null;\n}\n\n/**\n * Print opaque data — anything whose bytes came from a datastore, a server\n * response, or user/customer-controlled input — on a path that branding can\n * never touch.\n *\n * `installConsoleBranding` rewrites the prose noun `Canary` in every string\n * passed to `console.*`. That is correct for authored UI copy and catastrophic\n * for values: a customer record named \"Quinn Canary\" printed through\n * `console.log` becomes \"Quinn Cofactor\", and anything parsing that output\n * (e.g. `scripts/copy-flow-to-local.ts`, which reads `psql`'s table form)\n * silently ingests the corrupted value.\n *\n * Gating that risk per-command was the earlier design and was wrong in both\n * directions — it missed data surfaces outside the listed commands (`workflow\n * list` prints workflow names) while disabling branding for authored prose\n * inside them. Provenance is the correct criterion, not the sub-command: if a\n * byte did not originate in this repo's source, render it here.\n *\n * For a mixed line, brand the authored fragment and compose it around the raw\n * value — never hand the composed string to `console.log`.\n */\nexport function writeRawOutput(value: string): void {\n process.stdout.write(`${value}\\n`);\n}\n\n/**\n * Strip terminal control sequences from text that originated outside this\n * repo and outside the operator's control: AI summaries of a customer's app,\n * captured error messages. An escape sequence quoted from a page could\n * otherwise move the cursor, retitle the window or forge a hyperlink.\n */\nexport function stripTerminalControls(value: string): string {\n return stripControlCharacters(value);\n}\n\n/** {@link writeRawOutput} for untrusted prose: no branding, no control sequences. */\nexport function writeUntrustedText(value: string): void {\n writeRawOutput(stripTerminalControls(value));\n}\n\n/**\n * Whether console output for this invocation should be brand-rewritten.\n *\n * False only for genuinely machine-oriented invocations: the `mcp` JSON-RPC\n * transport and `--json`/`--format` output, where a rewrite could corrupt a\n * structured payload.\n *\n * Deliberately NOT false for any particular sub-command. Data safety is the\n * job of {@link writeRawOutput} at the render site, because a command-level\n * exemption conceals unsafe render sites rather than fixing them. Pure so the\n * policy is testable without touching `console`.\n */\nexport function shouldBrandConsoleOutput(argv: string[]): boolean {\n if (argv[0] === 'mcp') return false;\n return !argv.includes('--json') && !argv.includes('--format');\n}\n\n/**\n * Install brand rewriting over the `console` methods used for human-readable\n * output. No-op for the `canary` brand. The caller gates this on\n * {@link shouldBrandConsoleOutput}, which excludes machine output, the `mcp`\n * JSON-RPC transport, and raw data-query commands — display branding only.\n */\nexport function installConsoleBranding(): void {\n if (command === 'canary') return;\n const methods = ['log', 'error', 'warn', 'info'] as const;\n for (const method of methods) {\n const original = console[method].bind(console);\n console[method] = (...args: unknown[]) => {\n original(...args.map((arg) => (typeof arg === 'string' ? brandText(arg) : arg)));\n };\n }\n}\n"],"mappings":";;;AAAA;;;ACMA,IAAM,OAAqB;AAAA,EACzB,OAAO;AAAA,EACP,MAAM;AAAA,EACN,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAgET,KAAK;AACP;AAEA,IAAM,WAAyB;AAAA,EAC7B,OAAO;AAAA,EACP,MAAM;AAAA,EACN,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EA8FT,KAAK;AACP;AAEA,IAAM,YAA0B;AAAA,EAC9B,OAAO;AAAA,EACP,MAAM;AAAA,EACN,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA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","names":["overview_default","overview_default","overview_default","overview_default","overview_default","overview_default","overview_default","overview_default","require"]}
|