@runcofactor/cli 0.52.0 → 0.53.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 -0
- package/dist/{account-3ZG24MOE.js → account-MRZ54P5P.js} +5 -5
- package/dist/{alert-5G7PTAX2.js → alert-XBHELHEI.js} +8 -8
- package/dist/{api-catalog-IBYAFKKQ.js → api-catalog-GWYRX53L.js} +5 -5
- package/dist/{api-key-UXC44BDR.js → api-key-FH6JN447.js} +5 -5
- package/dist/{attachment-T6IRFQW3.js → attachment-J7GRVT5F.js} +5 -5
- package/dist/{auth-SHOCJE6Z.js → auth-VZZQXRDR.js} +3 -3
- package/dist/{benchmark-NJ2U6VTC.js → benchmark-A3SNDARF.js} +5 -5
- package/dist/{blackcat-RQJVFGXQ.js → blackcat-UM6TBQBN.js} +3 -3
- package/dist/{chunk-DHAGRM2F.js → chunk-33U56YGD.js} +14 -5
- package/dist/{chunk-DHAGRM2F.js.map → chunk-33U56YGD.js.map} +1 -1
- package/dist/{chunk-M752EUK3.js → chunk-3JU4UYYN.js} +5 -5
- package/dist/{chunk-G6BCST3G.js → chunk-3ZOUNCA5.js} +7 -7
- package/dist/{chunk-76Z6E7XX.js → chunk-4UZN6TVT.js} +74 -131
- package/dist/chunk-4UZN6TVT.js.map +1 -0
- package/dist/{chunk-ABT3BLRC.js → chunk-5TKXHS3X.js} +3 -3
- package/dist/{chunk-QCYSONXI.js → chunk-CRYHWO4M.js} +2 -2
- package/dist/{chunk-QTYUM4ZB.js → chunk-EJ3ZPH5U.js} +23 -187
- package/dist/chunk-EJ3ZPH5U.js.map +1 -0
- package/dist/{chunk-I5RHTCPY.js → chunk-EL6KJ4PO.js} +4 -4
- package/dist/{chunk-ZL4O6C42.js → chunk-HXBK3AGD.js} +2 -2
- package/dist/{chunk-PRERH4DN.js → chunk-I2SOHQ2C.js} +3 -3
- package/dist/{chunk-CYSPCGO6.js → chunk-JEHHEMUL.js} +5 -5
- package/dist/{chunk-LIEADTEF.js → chunk-LJFVRJCA.js} +2 -2
- package/dist/{chunk-UEJ2HTKU.js → chunk-MJVUMVLO.js} +19 -1
- package/dist/chunk-MJVUMVLO.js.map +1 -0
- package/dist/chunk-N55RKCGR.js +24 -0
- package/dist/chunk-N55RKCGR.js.map +1 -0
- package/dist/{chunk-PHNH3CEU.js → chunk-O4Z44BR7.js} +5 -5
- package/dist/{chunk-QA37WVF6.js → chunk-OQFCZERZ.js} +6 -6
- package/dist/{chunk-EUVB6Y3C.js → chunk-P7L5KELH.js} +8 -8
- package/dist/chunk-P7L5KELH.js.map +1 -0
- package/dist/{chunk-Z3MDQSZG.js → chunk-PJFFGQSB.js} +2 -2
- package/dist/{chunk-IY3ZK46I.js → chunk-QOVJUSSL.js} +2 -2
- package/dist/{chunk-RPJMQBB5.js → chunk-QXU4G4YN.js} +3 -3
- package/dist/{chunk-SZSTJIYU.js → chunk-RRSUKARV.js} +4 -4
- package/dist/{chunk-775FI436.js → chunk-RVD6J6IZ.js} +2 -2
- package/dist/{chunk-5UUIBY6E.js → chunk-TAVBFLCR.js} +3 -3
- package/dist/{chunk-ROX7N3RK.js → chunk-V7HB5LAU.js} +10 -7
- package/dist/chunk-V7HB5LAU.js.map +1 -0
- package/dist/{chunk-LL6TSQWV.js → chunk-VKZOHJCE.js} +3 -3
- package/dist/chunk-VKZOHJCE.js.map +1 -0
- package/dist/{chunk-3HI6QC4V.js → chunk-WRBU6TX7.js} +2 -2
- package/dist/{proofs-TXJGAP5F.js → chunk-X7SUSIY3.js} +118 -20
- package/dist/chunk-X7SUSIY3.js.map +1 -0
- package/dist/{chunk-BF3IPGMP.js → chunk-XSSZMG65.js} +4 -4
- package/dist/{credential-TWSWV64F.js → credential-4RFRU6OH.js} +5 -5
- package/dist/{credential-template-XO5D5AI3.js → credential-template-36PFUTBN.js} +5 -5
- package/dist/{digest-SLEFOLPV.js → digest-THMKFMP7.js} +8 -8
- package/dist/{docs-IDC4V4WQ.js → docs-ZGM53L67.js} +5 -5
- package/dist/{doctor-SDAYEA6C.js → doctor-Z2XBGBM3.js} +3 -3
- package/dist/{environment-VBUIBQ6O.js → environment-5YVZXBN7.js} +7 -7
- package/dist/eval-SO5TPD37.js +1177 -0
- package/dist/eval-SO5TPD37.js.map +1 -0
- package/dist/{exploration-IREIR5PO.js → exploration-GZ4WUJJH.js} +5 -5
- package/dist/{exploratory-test-4Q7BMEFT.js → exploratory-test-Y7P4ZDLD.js} +5 -5
- package/dist/{external-run-S7LULKPT.js → external-run-IJGGZOGI.js} +7 -7
- package/dist/{feature-flag-IASHTUGU.js → feature-flag-UH6Z7774.js} +5 -5
- package/dist/{flow-IBEZFZTU.js → flow-MRWAQYZZ.js} +5 -5
- package/dist/{github-HAEZRULP.js → github-VDJU3PU3.js} +6 -6
- package/dist/index.js +101 -82
- package/dist/index.js.map +1 -1
- package/dist/{init-LT2TZMQY.js → init-CROSTPA6.js} +5 -5
- package/dist/{install-RTEYH6TK.js → install-ECR7OLAY.js} +5 -5
- package/dist/{issues-5GE277R2.js → issues-K5TL2S4Z.js} +7 -7
- package/dist/{knobs-AMIWGYUZ.js → knobs-OKQFBQLK.js} +5 -5
- package/dist/{landing-3VLVVQL5.js → landing-XANIBPVM.js} +7 -7
- package/dist/{list-GDJR6UY6.js → list-64GX47Q4.js} +5 -5
- package/dist/{local-4R6GVW7U.js → local-QMQY2ZUX.js} +7 -7
- package/dist/{login-IXVAWTLO.js → login-I7UQ3GTM.js} +5 -5
- package/dist/{mcp-BM2MJWX4.js → mcp-XZV63DX5.js} +11 -11
- package/dist/{mcp-BM2MJWX4.js.map → mcp-XZV63DX5.js.map} +1 -1
- package/dist/{metrics-SSIP5CEB.js → metrics-KBTQ3KGZ.js} +5 -5
- package/dist/{monitor-ESZYHXT2.js → monitor-UGWY43IU.js} +5 -5
- package/dist/perf-CCHWHCSR.js +961 -0
- package/dist/perf-CCHWHCSR.js.map +1 -0
- package/dist/{product-VCGUV5TH.js → product-KLG2ZTHC.js} +5 -5
- package/dist/{product-map-AXKZNQDM.js → product-map-AMEZ76EQ.js} +5 -5
- package/dist/{prompts-FVPSON2N.js → prompts-ZVQNGL7X.js} +5 -5
- package/dist/proofs-QCNSQAPX.js +20 -0
- package/dist/{property-XEPCC7KQ.js → property-PJDQFHAL.js} +6 -6
- package/dist/{psql-JKF3OYYJ.js → psql-AURAN27C.js} +5 -5
- package/dist/{record-REQ37S54.js → record-SYWBTUOB.js} +6 -6
- package/dist/{redis-WGHT2GJB.js → redis-2H752SKO.js} +5 -5
- package/dist/{release-SNDIVIA6.js → release-OSIYUXSC.js} +5 -5
- package/dist/{runbook-XFJG2LFC.js → runbook-7LVHF6KV.js} +5 -5
- package/dist/{sandbox-DYCKLCMY.js → sandbox-TJQPMRGS.js} +18 -30
- package/dist/sandbox-TJQPMRGS.js.map +1 -0
- package/dist/{scenarios-PVHCKP55.js → scenarios-P2ABU4HK.js} +5 -5
- package/dist/{schedule-DGC7CX23.js → schedule-XLS56NP5.js} +8 -8
- package/dist/{session-ERGUFDDI.js → session-ACSFC3UB.js} +8 -8
- package/dist/{skill-6QO2E3TB.js → skill-F7I5P6AI.js} +43 -28
- package/dist/{skill-6QO2E3TB.js.map → skill-F7I5P6AI.js.map} +1 -1
- package/dist/{slack-WE7THLLH.js → slack-ZEK4GRND.js} +6 -6
- package/dist/{spec-SEOY7N3L.js → spec-XPIRRSV6.js} +5 -5
- package/dist/{src-CCK3TKBX.js → src-N7ZCYCXU.js} +4 -4
- package/dist/{src-AEWV5HYH.js → src-XSBBMVGW.js} +3 -3
- package/dist/{start-EPGLHTND.js → start-DKWLOQI6.js} +5 -5
- package/dist/{status-DTE2GXM2.js → status-K7KH5DI3.js} +5 -5
- package/dist/{tag-GIFEWGBL.js → tag-2Y3OMQQC.js} +6 -6
- package/dist/{task-VLJCHSFB.js → task-4PIOC4QV.js} +6 -6
- package/dist/{teams-J2OOJBL4.js → teams-KUM56BE2.js} +83 -47
- package/dist/teams-KUM56BE2.js.map +1 -0
- package/dist/{test-suite-CTACGIAZ.js → test-suite-SW25YGJV.js} +138 -96
- package/dist/test-suite-SW25YGJV.js.map +1 -0
- package/dist/{trace-RWQDYL4K.js → trace-QPXHYBLA.js} +7 -7
- package/dist/{triage-6UAXLCGU.js → triage-EZABRZZM.js} +6 -6
- package/dist/trigger-7C7KX5KH.js +137 -0
- package/dist/trigger-7C7KX5KH.js.map +1 -0
- package/dist/{update-check-QRREAM3N.js → update-check-E3X7UIY6.js} +3 -3
- package/dist/{upgrade-5Y655TQ6.js → upgrade-EJGTMW44.js} +4 -4
- package/dist/{usage-KCXFY46V.js → usage-GZCQDULF.js} +7 -7
- package/dist/{utilities-SUON2KZK.js → utilities-RIITD53A.js} +6 -6
- package/dist/{visual-GJZVJC32.js → visual-FH67OKS2.js} +5 -5
- package/dist/{workflow-R5QAXWBX.js → workflow-34YJZ2QQ.js} +38 -86
- package/dist/workflow-34YJZ2QQ.js.map +1 -0
- package/dist/{workflow-cache-PFBTWLGM.js → workflow-cache-YQ3BGJIG.js} +17 -17
- package/dist/{workflow-cache-convergence-resolve-UZGRPC3B.js → workflow-cache-convergence-resolve-SPLBRSNK.js} +5 -5
- package/dist/{workflow-debug-RACNWWOL.js → workflow-debug-HZB7TJGK.js} +10 -10
- package/dist/workflow-debug-HZB7TJGK.js.map +1 -0
- package/dist/{workflow-feedback-RJIQ5R3R.js → workflow-feedback-6UOTIWIQ.js} +5 -5
- package/dist/{workflow-fork-LINDCYJ3.js → workflow-fork-IDYHAHCX.js} +5 -5
- package/dist/workflow-promotion-U6WYFY5U.js +62 -0
- package/dist/workflow-promotion-U6WYFY5U.js.map +1 -0
- package/dist/{workflow-proposals-E4EXKAGM.js → workflow-proposals-MMVNIQT3.js} +5 -5
- package/dist/{workflow-revisions-5M6APN6R.js → workflow-revisions-BPUOLCZB.js} +5 -5
- package/dist/{workflow-status-7KL73WS2.js → workflow-status-RA3IEMEW.js} +5 -5
- package/package.json +3 -2
- package/dist/chat-target-IZ7YLLU3.js +0 -502
- package/dist/chat-target-IZ7YLLU3.js.map +0 -1
- package/dist/chunk-76Z6E7XX.js.map +0 -1
- package/dist/chunk-EUVB6Y3C.js.map +0 -1
- package/dist/chunk-LL6TSQWV.js.map +0 -1
- package/dist/chunk-QTYUM4ZB.js.map +0 -1
- package/dist/chunk-ROX7N3RK.js.map +0 -1
- package/dist/chunk-UEJ2HTKU.js.map +0 -1
- package/dist/proofs-TXJGAP5F.js.map +0 -1
- package/dist/sandbox-DYCKLCMY.js.map +0 -1
- package/dist/teams-J2OOJBL4.js.map +0 -1
- package/dist/test-suite-CTACGIAZ.js.map +0 -1
- package/dist/workflow-R5QAXWBX.js.map +0 -1
- /package/dist/{account-3ZG24MOE.js.map → account-MRZ54P5P.js.map} +0 -0
- /package/dist/{alert-5G7PTAX2.js.map → alert-XBHELHEI.js.map} +0 -0
- /package/dist/{api-catalog-IBYAFKKQ.js.map → api-catalog-GWYRX53L.js.map} +0 -0
- /package/dist/{api-key-UXC44BDR.js.map → api-key-FH6JN447.js.map} +0 -0
- /package/dist/{attachment-T6IRFQW3.js.map → attachment-J7GRVT5F.js.map} +0 -0
- /package/dist/{auth-SHOCJE6Z.js.map → auth-VZZQXRDR.js.map} +0 -0
- /package/dist/{benchmark-NJ2U6VTC.js.map → benchmark-A3SNDARF.js.map} +0 -0
- /package/dist/{blackcat-RQJVFGXQ.js.map → blackcat-UM6TBQBN.js.map} +0 -0
- /package/dist/{chunk-M752EUK3.js.map → chunk-3JU4UYYN.js.map} +0 -0
- /package/dist/{chunk-G6BCST3G.js.map → chunk-3ZOUNCA5.js.map} +0 -0
- /package/dist/{chunk-ABT3BLRC.js.map → chunk-5TKXHS3X.js.map} +0 -0
- /package/dist/{chunk-QCYSONXI.js.map → chunk-CRYHWO4M.js.map} +0 -0
- /package/dist/{chunk-I5RHTCPY.js.map → chunk-EL6KJ4PO.js.map} +0 -0
- /package/dist/{chunk-ZL4O6C42.js.map → chunk-HXBK3AGD.js.map} +0 -0
- /package/dist/{chunk-PRERH4DN.js.map → chunk-I2SOHQ2C.js.map} +0 -0
- /package/dist/{chunk-CYSPCGO6.js.map → chunk-JEHHEMUL.js.map} +0 -0
- /package/dist/{chunk-LIEADTEF.js.map → chunk-LJFVRJCA.js.map} +0 -0
- /package/dist/{chunk-PHNH3CEU.js.map → chunk-O4Z44BR7.js.map} +0 -0
- /package/dist/{chunk-QA37WVF6.js.map → chunk-OQFCZERZ.js.map} +0 -0
- /package/dist/{chunk-Z3MDQSZG.js.map → chunk-PJFFGQSB.js.map} +0 -0
- /package/dist/{chunk-IY3ZK46I.js.map → chunk-QOVJUSSL.js.map} +0 -0
- /package/dist/{chunk-RPJMQBB5.js.map → chunk-QXU4G4YN.js.map} +0 -0
- /package/dist/{chunk-SZSTJIYU.js.map → chunk-RRSUKARV.js.map} +0 -0
- /package/dist/{chunk-775FI436.js.map → chunk-RVD6J6IZ.js.map} +0 -0
- /package/dist/{chunk-5UUIBY6E.js.map → chunk-TAVBFLCR.js.map} +0 -0
- /package/dist/{chunk-3HI6QC4V.js.map → chunk-WRBU6TX7.js.map} +0 -0
- /package/dist/{chunk-BF3IPGMP.js.map → chunk-XSSZMG65.js.map} +0 -0
- /package/dist/{credential-TWSWV64F.js.map → credential-4RFRU6OH.js.map} +0 -0
- /package/dist/{credential-template-XO5D5AI3.js.map → credential-template-36PFUTBN.js.map} +0 -0
- /package/dist/{digest-SLEFOLPV.js.map → digest-THMKFMP7.js.map} +0 -0
- /package/dist/{docs-IDC4V4WQ.js.map → docs-ZGM53L67.js.map} +0 -0
- /package/dist/{doctor-SDAYEA6C.js.map → doctor-Z2XBGBM3.js.map} +0 -0
- /package/dist/{environment-VBUIBQ6O.js.map → environment-5YVZXBN7.js.map} +0 -0
- /package/dist/{exploration-IREIR5PO.js.map → exploration-GZ4WUJJH.js.map} +0 -0
- /package/dist/{exploratory-test-4Q7BMEFT.js.map → exploratory-test-Y7P4ZDLD.js.map} +0 -0
- /package/dist/{external-run-S7LULKPT.js.map → external-run-IJGGZOGI.js.map} +0 -0
- /package/dist/{feature-flag-IASHTUGU.js.map → feature-flag-UH6Z7774.js.map} +0 -0
- /package/dist/{flow-IBEZFZTU.js.map → flow-MRWAQYZZ.js.map} +0 -0
- /package/dist/{github-HAEZRULP.js.map → github-VDJU3PU3.js.map} +0 -0
- /package/dist/{init-LT2TZMQY.js.map → init-CROSTPA6.js.map} +0 -0
- /package/dist/{install-RTEYH6TK.js.map → install-ECR7OLAY.js.map} +0 -0
- /package/dist/{issues-5GE277R2.js.map → issues-K5TL2S4Z.js.map} +0 -0
- /package/dist/{knobs-AMIWGYUZ.js.map → knobs-OKQFBQLK.js.map} +0 -0
- /package/dist/{landing-3VLVVQL5.js.map → landing-XANIBPVM.js.map} +0 -0
- /package/dist/{list-GDJR6UY6.js.map → list-64GX47Q4.js.map} +0 -0
- /package/dist/{local-4R6GVW7U.js.map → local-QMQY2ZUX.js.map} +0 -0
- /package/dist/{login-IXVAWTLO.js.map → login-I7UQ3GTM.js.map} +0 -0
- /package/dist/{metrics-SSIP5CEB.js.map → metrics-KBTQ3KGZ.js.map} +0 -0
- /package/dist/{monitor-ESZYHXT2.js.map → monitor-UGWY43IU.js.map} +0 -0
- /package/dist/{product-VCGUV5TH.js.map → product-KLG2ZTHC.js.map} +0 -0
- /package/dist/{product-map-AXKZNQDM.js.map → product-map-AMEZ76EQ.js.map} +0 -0
- /package/dist/{prompts-FVPSON2N.js.map → prompts-ZVQNGL7X.js.map} +0 -0
- /package/dist/{src-AEWV5HYH.js.map → proofs-QCNSQAPX.js.map} +0 -0
- /package/dist/{property-XEPCC7KQ.js.map → property-PJDQFHAL.js.map} +0 -0
- /package/dist/{psql-JKF3OYYJ.js.map → psql-AURAN27C.js.map} +0 -0
- /package/dist/{record-REQ37S54.js.map → record-SYWBTUOB.js.map} +0 -0
- /package/dist/{redis-WGHT2GJB.js.map → redis-2H752SKO.js.map} +0 -0
- /package/dist/{release-SNDIVIA6.js.map → release-OSIYUXSC.js.map} +0 -0
- /package/dist/{runbook-XFJG2LFC.js.map → runbook-7LVHF6KV.js.map} +0 -0
- /package/dist/{scenarios-PVHCKP55.js.map → scenarios-P2ABU4HK.js.map} +0 -0
- /package/dist/{schedule-DGC7CX23.js.map → schedule-XLS56NP5.js.map} +0 -0
- /package/dist/{session-ERGUFDDI.js.map → session-ACSFC3UB.js.map} +0 -0
- /package/dist/{slack-WE7THLLH.js.map → slack-ZEK4GRND.js.map} +0 -0
- /package/dist/{spec-SEOY7N3L.js.map → spec-XPIRRSV6.js.map} +0 -0
- /package/dist/{src-CCK3TKBX.js.map → src-N7ZCYCXU.js.map} +0 -0
- /package/dist/{tag-GIFEWGBL.js.map → src-XSBBMVGW.js.map} +0 -0
- /package/dist/{start-EPGLHTND.js.map → start-DKWLOQI6.js.map} +0 -0
- /package/dist/{status-DTE2GXM2.js.map → status-K7KH5DI3.js.map} +0 -0
- /package/dist/{trace-RWQDYL4K.js.map → tag-2Y3OMQQC.js.map} +0 -0
- /package/dist/{task-VLJCHSFB.js.map → task-4PIOC4QV.js.map} +0 -0
- /package/dist/{triage-6UAXLCGU.js.map → trace-QPXHYBLA.js.map} +0 -0
- /package/dist/{update-check-QRREAM3N.js.map → triage-EZABRZZM.js.map} +0 -0
- /package/dist/{upgrade-5Y655TQ6.js.map → update-check-E3X7UIY6.js.map} +0 -0
- /package/dist/{utilities-SUON2KZK.js.map → upgrade-EJGTMW44.js.map} +0 -0
- /package/dist/{usage-KCXFY46V.js.map → usage-GZCQDULF.js.map} +0 -0
- /package/dist/{workflow-debug-RACNWWOL.js.map → utilities-RIITD53A.js.map} +0 -0
- /package/dist/{visual-GJZVJC32.js.map → visual-FH67OKS2.js.map} +0 -0
- /package/dist/{workflow-cache-PFBTWLGM.js.map → workflow-cache-YQ3BGJIG.js.map} +0 -0
- /package/dist/{workflow-cache-convergence-resolve-UZGRPC3B.js.map → workflow-cache-convergence-resolve-SPLBRSNK.js.map} +0 -0
- /package/dist/{workflow-feedback-RJIQ5R3R.js.map → workflow-feedback-6UOTIWIQ.js.map} +0 -0
- /package/dist/{workflow-fork-LINDCYJ3.js.map → workflow-fork-IDYHAHCX.js.map} +0 -0
- /package/dist/{workflow-proposals-E4EXKAGM.js.map → workflow-proposals-MMVNIQT3.js.map} +0 -0
- /package/dist/{workflow-revisions-5M6APN6R.js.map → workflow-revisions-BPUOLCZB.js.map} +0 -0
- /package/dist/{workflow-status-7KL73WS2.js.map → workflow-status-RA3IEMEW.js.map} +0 -0
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/help.ts"],"sourcesContent":["import { readAllStoredTokens, resolveOptionalConfig } from './auth';\nimport { version, readBrandEnv, specExtension } from './brand.js';\nimport { isSuperadminToken } from './jwt';\n\nexport function printVersion() {\n console.log(`canary v${version}`);\n}\n\nexport function printHelp({\n isSuperadmin,\n features = {},\n}: {\n isSuperadmin: boolean;\n features?: Record<string, boolean>;\n}) {\n const lines: string[] = [\n `canary v${version}: Testing and QA CLI`,\n '',\n 'Usage:',\n ' canary test-suite <sub-command> Run and query test suite results',\n ' canary visual <sub-command> Review visual checkpoints and mark regressions',\n ' canary external-run <sub-command> Diagnose external suite runs (no superadmin access)',\n ' canary mcp Start MCP server (stdio)',\n ' canary install [--target <id>] Install MCP server into Claude, Cursor, etc.',\n ' canary login [--org <name>] [--app-url https://app.runcofactor.com] [--no-open]',\n ' canary orgs [members] List organizations + your logged-in sessions',\n ' canary org invite <email> [--role] Invite a teammate (org invites: pending invitations)',\n ' canary api-key <sub-command> Create, list and revoke API keys (CI: COFACTOR_API_TOKEN)',\n ' canary org settings get|set ... Read or set organization-wide external-test concurrency',\n ' canary org use <name|id> Set the active org for bare commands',\n ' canary record [options] Record browser interactions',\n ' canary local <sub-command> Set up and manage local testing',\n ' canary session <sub-command> Manage local browser sessions',\n ' canary issues <sub-command> Search and view issues',\n ' canary credential <sub-command> List, create, update, and test credentials',\n ' canary persona <sub-command> Manage per-application personas',\n ' canary chat-target <sub-command> Manage AI chat targets for chat evals',\n ' canary workflow <sub-command> Inspect workflow definitions',\n ` canary spec <sub-command> Author workflows from ${specExtension} spec files`,\n ' canary api-catalog <sub-command> Manage API catalog sources and endpoints',\n ' canary skill <name> Install an AI agent skill template (--name to rename)',\n ' canary prompts <list|get> Curated starter prompts for coding agents',\n ' canary digest <list|get> Read daily/weekly QA digests',\n \" canary github connect Connect the organization's GitHub App installation from the terminal\",\n ' canary github bind Superadmin: bind an existing GitHub App installation to an org',\n \" canary slack <connect|disconnect> Connect or disconnect the organization's Slack workspace\",\n ' canary teams <sub-command> Manage the Microsoft Teams connection',\n ];\n\n lines.push(' canary release <sub-command> Release QA gate (CI/CD)');\n lines.push(' canary runbook <sub-command> Run repo-declared operational commands (deploy, rollback)');\n\n lines.push(\n ' canary application <sub-command> List and create applications, view linked repos (alias: property)',\n ' canary environment <sub-command> List, create, default environments; set variables (alias: env)',\n ' canary sandbox <sub-command> Sandboxes v2: push, claim, run app copies (+ legacy v1)',\n ' canary attachment <sub-command> List, upload, and manage attachments',\n ' canary tag <sub-command> List, create, and assign tags',\n ' canary triage <sub-command> Read AI triage results for a test suite execution',\n ' canary schedule <target> <sub-command> Schedule recurring suite runs or exploratory tests',\n ' canary alert <sub-command> Manage notify-only alerts (Slack/Teams/email)'\n );\n\n // Feature-gated agent commands — only listed when the signed-in org has the\n // backing feature flag (resolved from /me). Hidden otherwise so help never\n // advertises a command that returns 403.\n if (features.agentTasks) {\n lines.push(' canary task <sub-command> Manage agent tasks');\n }\n if (features.utilities) {\n lines.push(\n ' canary utilities <group> <sub> Object storage, gists, email/slack (agent toolkit)'\n );\n }\n lines.push(\n ' canary product-map <sub-command> Areas, capabilities, stages, flow links, coverage',\n ' canary proofs <sub-command> The tests a pull request lists as proof (block, status)',\n ' canary exploration <sub-command> Run exploration cycles now (--cycles N), inspect them',\n ' canary exploratory-test <sub-command> Start and inspect exploratory tests (run, get, list)'\n );\n\n if (isSuperadmin) {\n lines.push(\n ' canary org create --name <n> [--owner-email <e>] Create an org for someone and invite its owner',\n ' canary debug-session [--env dev|local] [--json] Create browser debug session',\n ' canary psql <query> [--json] Execute read-only SQL',\n ' canary redis <command> [--json] Execute read-only Redis commands',\n ' canary docs <sub-command> Manage documentation pages',\n ' canary landing <sub-command> Manage runcofactor.com/lp/<slug> landing pages',\n ' canary feature-flag <sub-command> Manage feature flags',\n ' canary knobs <sub-command> Manage knobs (global config)',\n ' canary product <sub-command> Manage the Product board (cards, fields, flag links)',\n ' canary metrics <sub-command> Manage product metrics (pull/review health metrics)',\n ' canary benchmark <sub-command> Run/inspect ad-hoc A/B experiments (models, knobs, flags)',\n ' canary account <sub-command> Manage the Accounts board (accounts, org links)',\n ' canary status <sub-command> Publish incidents to the status page'\n );\n }\n\n lines.push(\n ' canary auth doctor [--env <env>] Diagnose auth / corporate-proxy issues',\n ' canary upgrade Upgrade CLI to latest version',\n ' canary version Show version',\n ' canary help',\n '',\n 'Test suite sub-commands:',\n ' run [options] Trigger a test suite and stream results',\n ' list [options] List recent test suites',\n ' get <id> [options] Show suite detail with runs',\n ' diagnostics <workflowId> <runId> Get failure diagnostic',\n ' steps <workflowId> <runId> Get workflow run steps',\n '',\n 'Test suite run options:',\n ' --token <key> API key (or set CANARY_API_TOKEN)',\n ' --api-url <url> API URL (default: https://api.runcofactor.com)',\n ' --application <name|id> Target a specific application (alias: --property)',\n ' --environment <name|id> Target a specific environment',\n ' --tag <tag> Filter workflows by tag',\n ' --name-pattern <pat> Filter workflows by name pattern',\n ' --suite <name|id> Run a named suite (layered)',\n ' --at <sha> With --suite: run its Sandboxes v2 environments at this full commit sha',\n ' instead of the default branch head. A pinned run never feeds visual',\n ' baselines.',\n ' --verbose, -v Show all events',\n '',\n 'Login options:',\n ' --org <name> Select organization by name or ID (for multi-org users)',\n '',\n 'Working across orgs (act on behalf of customers):',\n ' Stay logged into several orgs at once and pick which one any command runs as.',\n ' (--org goes AFTER the command, like --env: `canary <command> ... --org <name>`.)',\n ' canary <command> --org <name|id> Run one command as one of your logged-in orgs',\n ' CANARY_ORG=<name|id> Persistent default org (like AWS_PROFILE)',\n ' canary login --org <name> Add / refresh a session (side by side per env)',\n ' canary org use <name> Set the active org for bare (no --org) commands',\n ' canary orgs See your logged-in orgs, their env, token expiry',\n ' (Superadmin cross-org commands target a different org with --target-org.)',\n '',\n 'Login environments:',\n ' Production: canary login',\n ' Dev: canary login --app-url https://app.dev.trycanary.ai --api-url https://api.dev.trycanary.ai',\n ' Local: canary login --app-url http://localhost:5173 --api-url http://localhost:3000',\n '',\n ' Or set CANARY_API_URL env var for non-production environments:',\n ' export CANARY_API_URL=http://localhost:3000'\n );\n\n lines.push(\n '',\n 'Debug options:',\n ' --to-step <N> Stop after step N (1-based, default: all)',\n ' --env <env> Environment (local, dev, prod)',\n ' --verbose, -v Show all SSE events',\n '',\n 'Issues sub-commands:',\n ' list [options] List and search issues',\n ' get <issueId> [options] Get issue detail with diagnostics',\n '',\n 'Issues options:',\n ' --search <query> Full-text search',\n ' --severity <level> Filter: low, medium, high, unknown',\n ' --status <statuses> Filter: open, closed, not_a_bug (comma-separated)',\n ' --application-id <uuid> Filter by application (alias: --property-id)',\n ' --page <n> Page number (default: 1)',\n ' --page-size <n> Page size (default: 25)',\n ' --json Output raw JSON',\n ' --format markdown Output as markdown',\n '',\n 'Release sub-commands:',\n ' trigger --application-id <uuid> Trigger a Release QA run (alias: --property-id)',\n ' status <run-id> [--json] Check run status',\n ' run --application-id <uuid> [--timeout N] Trigger and poll until complete',\n '',\n 'Record options:',\n ' --credential <id> Credential ID (skip interactive selection)',\n ' --url <startUrl> URL to navigate to after launch',\n ' --output <dir> Local output directory (default: temp dir)',\n ' --env <env> Environment (local, dev, prod)',\n ' --no-upload Save locally only, skip API upload',\n ''\n );\n\n if (isSuperadmin) {\n lines.push(\n '',\n 'PSQL options:',\n ' --json Output results as JSON',\n ' --query <sql> SQL query (alternative to positional)',\n '',\n 'Redis options:',\n ' --json Output results as JSON',\n '',\n 'Feature flag sub-commands:',\n ' list List all flags',\n ' create <name> [--description <text>] Create a flag',\n ' delete <name> Delete a flag and its gates',\n ' enable <name> --target-org <orgId> Enable for an org',\n ' disable <name> --target-org <orgId> Disable for an org',\n ' lifecycle <name> --stage <stage> [--final-value true|false]',\n ' Set lifecycle metadata',\n '',\n 'Knobs sub-commands:',\n ' list List all knobs',\n ' get <key> Get a knob value',\n ' set <key> <value> --type <type> Set a knob value',\n ' delete <key> Delete a knob',\n ' toggle <key> Toggle a boolean knob',\n ' lifecycle <key> --stage <stage> [--final-value <value>]',\n ' Set lifecycle metadata',\n '',\n 'Product board sub-commands (org you are logged into):',\n ' list [--search <t>] [--priority p0,p1] [--include-deprecated] List items',\n ' create <title> [--description <t>] [--stage <s>] [--priority <p0-p3>] Create item',\n ' set <id> [--title <t>] [--description <t>] [--priority <p0-p3|null>] Update fields',\n ' move <id> --stage <s> [--before <id>] [--after <id>] Move card to a column',\n ' delete <id> Delete item',\n ' customer add <id> (--account-card <id> | --target-org <orgId>) [--name <t>] Link customer',\n ' customer remove <id> <customerId> Unlink customer',\n ' link-flag <cardId> --flag <name> | --flag <name> --clear Link/unlink a feature flag',\n ' flags <cardId> List flags linked to a card',\n '',\n 'Account board sub-commands (org you are logged into):',\n ' list [--search <t>] [--stage <s>] List accounts',\n ' create <name> [--description <t>] [--stage <s>] Create account (default stage=prospect)',\n ' set <id> [--title <t>] [--description <t>] [--stage <s>] Update fields',\n ' delete <id> Delete account',\n ' link-org <id> --target-org <customerOrgId> Bind account to a customer org',\n ' unlink-org <id> Remove org link',\n ' linked-org <id> Show linked org'\n );\n }\n\n lines.push('', 'Flags:', ' -h, --help Show help', ' -V, --version Show version');\n\n console.log(lines.join('\\n'));\n}\n\nexport function printTestHelp() {\n console.log(\n [\n `canary v${version}: Test command`,\n '',\n '\\x1b[33m⚠ `canary test` is deprecated. Use `canary test-suite run` instead.\\x1b[0m',\n '',\n 'Usage:',\n ' canary test-suite run [options] Trigger a test suite and stream results',\n ' canary test-suite list List recent test suites',\n ' canary test-suite get <id> Show suite detail with runs',\n '',\n 'Run options:',\n ' --token <key> API key (or set CANARY_API_TOKEN)',\n ' --api-url <url> API URL (default: https://api.runcofactor.com)',\n ' --application <name|id> Target a specific application (alias: --property)',\n ' --environment <name|id> Target a specific environment',\n ' --tag <tag> Filter workflows by tag',\n ' --name-pattern <pat> Filter workflows by name pattern',\n ' --verbose, -v Show all events',\n '',\n 'Examples:',\n ' canary test-suite run Run all workflow tests',\n ' canary test-suite run --tag smoke Run smoke-tagged workflows',\n ' canary test-suite run --property \"My App\" --environment staging',\n ].join('\\n')\n );\n}\n\n/** Check if any stored profile (or env token) has superadmin privileges */\nexport async function resolveIsSuperadmin(): Promise<boolean> {\n const envToken = readBrandEnv('CANARY_API_TOKEN');\n if (envToken) return isSuperadminToken(envToken);\n\n const tokens = await readAllStoredTokens();\n return tokens.some((t) => isSuperadminToken(t));\n}\n\n/**\n * Best-effort fetch of the signed-in org's enabled feature flags (the same\n * `user.features` map the web app uses), so the global help can hide commands\n * for features the org doesn't have. Fail-closed: any error, timeout, or\n * missing auth yields `{}`, which hides feature-gated commands rather than\n * advertising something the org can't use. Never throws.\n */\nexport async function resolveOrgFeatures(): Promise<Record<string, boolean>> {\n try {\n const config = await resolveOptionalConfig([]);\n if (!config) return {};\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), 3000);\n try {\n const res = await fetch(`${config.apiUrl}/me`, {\n headers: { Authorization: `Bearer ${config.token}` },\n signal: controller.signal,\n });\n if (!res.ok) return {};\n const body = (await res.json()) as { user?: { features?: Record<string, boolean> } };\n return body.user?.features ?? {};\n } finally {\n clearTimeout(timer);\n }\n } catch {\n return {};\n }\n}\n\n/** Resolve superadmin + org features in parallel, then print the global help. */\nexport async function showGlobalHelp(): Promise<void> {\n const [isSuperadmin, features] = await Promise.all([resolveIsSuperadmin(), resolveOrgFeatures()]);\n printHelp({ isSuperadmin, features });\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../content/src/print-guide.ts","../../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/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":["import type { GuideSection } from './types.js';\n\nexport function printGuide(\n allSections: GuideSection[],\n section?: string,\n json?: boolean,\n): void {\n const sections = section\n ? allSections.filter((s) => s.slug === section)\n : allSections;\n\n if (section && sections.length === 0) {\n console.error(\n `Unknown guide section: \"${section}\". Available: ${allSections.map((s) => s.slug).join(', ')}`,\n );\n process.exit(1);\n }\n\n if (json) {\n console.log(JSON.stringify(sections, null, 2));\n return;\n }\n\n for (const s of sections) {\n console.log(`\\n${'='.repeat(72)}`);\n console.log(` ${s.title}`);\n console.log(`${'='.repeat(72)}\\n`);\n console.log(s.content);\n console.log('');\n }\n}\n","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 `canary sandbox guide [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 (\"canary sandbox guide 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","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":"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;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA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;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA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","names":["overview_default","overview_default","process","require","process"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/proofs.ts"],"sourcesContent":["/**\n * `cofactor proofs`: the Cofactor tests a pull request lists as proving it.\n *\n * - `block <wf…> | --none \"<reason>\"` validates the tests (a new draft, or a\n * fork of an approved test) and prints the block to paste into the pull\n * request description, so nobody hand-formats one.\n * - `status --application <app> --pr <n>` shows what the pull request lists.\n *\n * Backed by `/proofs/*` in `apps/api/src/proofs/routes.ts`.\n *\n * @module proofs\n */\n\nimport process from 'node:process';\nimport { execFileSync } from 'node:child_process';\nimport { appendFile, readFile } from 'node:fs/promises';\nimport { formatProofBlock, type ClassifiedProof, type EvaluatedProof, type ProofVerdict } from '@chatsdet/types';\nimport { getArgValue, hasFlag, resolveConfig } from './auth.js';\nimport { apiRequest, readStdin } from './cli-helpers.js';\nimport { writeRawOutput } from './brand.js';\nimport { resolveApplicationId } from './environment.js';\n\ntype ApiResult<T> = { ok: boolean; error?: string; data?: T };\ntype ProofStatusView = {\n proof: { headSha: string };\n verdict: ProofVerdict;\n};\n\n/** Flags `resolveConfig` reads, whose values are not positional. */\nconst CONNECTION_FLAGS = new Set(['--api-url', '--token', '--org', '--env']);\n\nconst HELP = [\n 'Usage: cofactor proofs <sub-command>',\n '',\n ' block <wf_…> <wrun_…> [<wf_…> <wrun_…> …] Check each test and the run of it you made, and print the proofs block for the pull request description',\n ' block --none \"<reason>\" The block for a pull request no test can prove: say why, and what would make a test possible',\n ' status --application <app>|--repo <owner/repo> --pr <n> [--json] Proofs attached to the pull request',\n ' attach --pr <n> --application <app> --criterion <n> --test <wf_…> --run <wrun_…> --asserted <text> [--text <text> --detail <text>] [--node <id>] [--sha <sha> --tree <tree>]',\n ' render --pr <n> Print the pull request proof block',\n ' detach --pr <n> --criterion <n> Remove a criterion and its evidence',\n ' check --pr <n> --sha <sha> [--body-file <path>|--body -] Evaluate the pull request proofs',\n].join('\\n');\n\nexport async function runProofs(argv: string[]): Promise<void> {\n const [subCommand, ...rest] = argv;\n if (!subCommand || subCommand === 'help' || hasFlag(argv, '--help', '-h')) {\n console.log(HELP);\n return;\n }\n const none = getArgValue(rest, '--none');\n // `block --none` needs no server: the reason is the block.\n if (subCommand === 'block' && hasFlag(rest, '--none')) {\n if (rest.some((arg, i) => !arg.startsWith('-') && rest[i - 1] !== '--none' && !CONNECTION_FLAGS.has(rest[i - 1] ?? ''))) {\n console.error('Error: --none is for a pull request no test can prove; list tests or give --none, not both (quote a reason that has several words).');\n process.exit(1);\n }\n if (!none?.trim()) {\n console.error('Error: --none takes a reason: say why no test is possible, and what would make one possible.');\n process.exit(1);\n }\n writeRawOutput(formatProofBlock({ kind: 'none', reason: none.trim() }));\n return;\n }\n if (subCommand === 'block' && (positional(rest).length === 0 || positional(rest).length % 2 !== 0)) {\n console.error('Error: list each test (wf_…) with the run of it that passed (wrun_…), or pass --none \"<reason>\".');\n process.exit(1);\n }\n const { apiUrl, token } = await resolveConfig(argv);\n switch (subCommand) {\n case 'block':\n return handleBlock(rest, apiUrl, token);\n case 'status':\n return handleStatus(rest, apiUrl, token);\n case 'attach':\n return handleAttach(rest, apiUrl, token);\n case 'render':\n return handleRender(rest, apiUrl, token);\n case 'detach':\n return handleDetach(rest, apiUrl, token);\n case 'check':\n return handleCheck(rest, apiUrl, token);\n default:\n console.error(`Unknown proofs sub-command: ${subCommand}\\n\\n${HELP}`);\n process.exit(1);\n }\n}\n\n/** The words that are not flags or flag values: test ids, each followed by its run id. */\nconst positional = (argv: string[]) => argv.filter((arg, i) => !arg.startsWith('-') && !CONNECTION_FLAGS.has(argv[i - 1] ?? ''));\n\nconst describe = (proof: ClassifiedProof) =>\n 'refusal' in proof\n ? `refused: ${proof.message}`\n : proof.kind === 'fork'\n ? `fork of ${proof.originName}`\n : 'new';\n\nasync function handleBlock(argv: string[], apiUrl: string, token: string): Promise<void> {\n const ids = positional(argv);\n const tests = Array.from({ length: ids.length / 2 }, (_, i) => ({ workflowId: ids[2 * i]!, runId: ids[2 * i + 1]! }));\n const res = await apiRequest<ApiResult<{ items: EvaluatedProof[] }>>(apiUrl, token, 'POST', '/proofs/classify', { tests });\n if (!res.ok || !res.data) {\n console.error(`Error: ${res.error ?? 'could not check the tests'}`);\n process.exit(1);\n }\n const listed: { workflowId: string; runId: string; description: string }[] = [];\n let bad = false;\n for (const { test, proof, runProblem } of res.data.items) {\n const problem = 'refusal' in proof ? describe(proof) : runProblem;\n if (problem) {\n bad = true;\n console.error(`${test.workflowId} ${problem}`);\n } else if (!('refusal' in proof)) {\n listed.push({ workflowId: test.workflowId, runId: test.runId ?? '', description: `${proof.name} (${describe(proof)})` });\n }\n }\n if (bad) process.exit(1);\n writeRawOutput(formatProofBlock({ kind: 'workflows', tests: listed }));\n}\n\nasync function handleStatus(argv: string[], apiUrl: string, token: string): Promise<void> {\n const pr = getArgValue(argv, '--pr');\n if (!pr) {\n console.error('Usage: cofactor proofs status --application <app>|--repo <owner/repo> --pr <number> [--json]');\n process.exit(1);\n }\n if (getArgValue(argv, '--application')) {\n const application = await resolveApplicationId(argv, apiUrl, token);\n const query = new URLSearchParams({ application, pr });\n const legacy = await apiRequest<ApiResult<{ prNumber: number; items: ClassifiedProof[] }>>(\n apiUrl,\n token,\n 'GET',\n `/proofs/status?${query}`\n );\n if (!legacy.ok || !legacy.data) return fail(legacy.error ?? 'could not read the pull request proofs');\n if (hasFlag(argv, '--json')) {\n writeRawOutput(JSON.stringify(legacy.data, null, 2));\n return;\n }\n const { items } = legacy.data;\n if (items.length === 0) {\n console.log(`#${pr} lists no tests.`);\n return;\n }\n console.log(`#${pr} lists ${items.length} test${items.length === 1 ? '' : 's'}:`);\n for (const proof of items) console.log(` ${proof.id} ${proof.name ?? ''} ${describe(proof)}`);\n return;\n }\n const repo = resolveRepo(argv);\n const query = new URLSearchParams({ repo, pr });\n const res = await apiRequest<ApiResult<{ proof: ProofStatusView | null }>>(\n apiUrl,\n token,\n 'GET',\n `/proofs/status?${query}`\n );\n if (!res.ok || !res.data) {\n console.error(`Error: ${res.error ?? 'could not read the pull request proofs'}`);\n process.exit(1);\n }\n if (hasFlag(argv, '--json')) {\n writeRawOutput(JSON.stringify(res.data, null, 2));\n return;\n }\n const proof = res.data.proof;\n if (!proof) {\n console.log(`#${pr} has no attached proof criteria.`);\n return;\n }\n console.log(`#${pr} proof status at ${proof.proof.headSha}:`);\n writeRawOutput(proof.verdict.summaryMarkdown);\n}\n\nasync function handleAttach(argv: string[], apiUrl: string, token: string): Promise<void> {\n const pr = requiredNumber(argv, '--pr');\n const criterion = requiredNumber(argv, '--criterion');\n const application = getArgValue(argv, '--application');\n const test = getArgValue(argv, '--test');\n const run = getArgValue(argv, '--run');\n const asserted = getArgValue(argv, '--asserted');\n if (!application || !test || !run || !asserted) {\n usageError('Usage: cofactor proofs attach --pr <n> --application <app> --criterion <n> --test <wf_…> --run <wrun_…> --asserted <text> [--text <text> --detail <text>]');\n }\n const applicationId = await resolveApplicationId(argv, apiUrl, token);\n const repo = resolveRepo(argv);\n const { sha, tree } = resolveCheckoutCommit(argv);\n const body: Record<string, unknown> = { repo, application: applicationId, pr, criterion, test, run, asserted, sha, tree };\n const text = getArgValue(argv, '--text');\n const detail = getArgValue(argv, '--detail');\n const node = getArgValue(argv, '--node');\n if (text !== undefined) body.text = text;\n if (detail !== undefined) body.detail = detail;\n if (node !== undefined) body.node = node;\n const res = await apiRequest<ApiResult<{ proofId: string; criterion: unknown; evidence: unknown }>>(\n apiUrl,\n token,\n 'POST',\n '/proofs/attach',\n body\n );\n if (!res.ok || !res.data) return fail(res.error ?? 'could not attach proof evidence');\n writeRawOutput(JSON.stringify(res.data, null, 2));\n}\n\nasync function handleRender(argv: string[], apiUrl: string, token: string): Promise<void> {\n const pr = requiredNumber(argv, '--pr');\n const repo = resolveRepo(argv);\n const query = new URLSearchParams({ repo, pr: String(pr) });\n const res = await apiRequest<ApiResult<{ body: string }>>(\n apiUrl,\n token,\n 'GET',\n `/proofs/render?${query}`\n );\n if (!res.ok || !res.data) return fail(res.error ?? 'could not render pull request proofs');\n writeRawOutput(res.data.body);\n}\n\nasync function handleDetach(argv: string[], apiUrl: string, token: string): Promise<void> {\n const pr = requiredNumber(argv, '--pr');\n const criterion = requiredNumber(argv, '--criterion');\n const repo = resolveRepo(argv);\n const res = await apiRequest<ApiResult<{ deleted: true }>>(\n apiUrl,\n token,\n 'POST',\n '/proofs/detach',\n { repo, pr, criterion }\n );\n if (!res.ok || !res.data) return fail(res.error ?? 'could not detach proof criterion');\n writeRawOutput(JSON.stringify(res.data, null, 2));\n}\n\nasync function handleCheck(argv: string[], apiUrl: string, token: string): Promise<void> {\n const pr = requiredNumber(argv, '--pr');\n const sha = getArgValue(argv, '--sha');\n if (!sha) usageError('Usage: cofactor proofs check --pr <n> --sha <sha> [--body-file <path>|--body -]');\n const repo = resolveRepo(argv);\n const bodyInput = getArgValue(argv, '--body');\n const bodyFile = getArgValue(argv, '--body-file');\n if (bodyInput !== undefined && (bodyInput !== '-' || bodyFile !== undefined)) {\n usageError('Use --body - for stdin or --body-file <path>.');\n }\n if (bodyInput === undefined && bodyFile === undefined) {\n usageError('Pass the pull request body with --body - or --body-file <path>.');\n }\n let body: string;\n try {\n body = bodyInput === '-' ? await readStdin() : await readFile(bodyFile!, 'utf8');\n } catch (error) {\n return fail(`could not read pull request body: ${error instanceof Error ? error.message : String(error)}`);\n }\n const res = await apiRequest<ApiResult<ProofVerdict | null>>(\n apiUrl,\n token,\n 'POST',\n '/proofs/check',\n { repo, pr, sha, body }\n );\n if (!res.ok) return fail(res.error ?? 'could not check pull request proofs');\n const verdict = res.data;\n if (!verdict) return;\n if (verdict.summaryMarkdown) {\n writeRawOutput(verdict.summaryMarkdown);\n const summaryFile = process.env.GITHUB_STEP_SUMMARY;\n if (summaryFile) {\n try {\n await appendFile(summaryFile, `${verdict.summaryMarkdown}\\n`, 'utf8');\n } catch (error) {\n return fail(`could not append GitHub step summary: ${error instanceof Error ? error.message : String(error)}`);\n }\n }\n }\n if (verdict.conclusion === 'failure') process.exit(1);\n}\n\n/** Resolve an explicit repo, Actions context, or the checkout's origin remote. */\nexport function resolveRepo(argv: string[]): string {\n const explicit = getArgValue(argv, '--repo');\n const candidate = explicit ?? process.env.GITHUB_REPOSITORY ?? originRepository();\n const repo = parseRepository(candidate);\n if (!repo) usageError('Could not determine a GitHub repository. Pass --repo <owner/repo> or set GITHUB_REPOSITORY.');\n return repo;\n}\n\nexport function parseRepository(value: string): string | null {\n const normalized = value.trim();\n const bare = normalized.match(/^([^/\\s:]+)\\/([^/\\s:]+?)(?:\\.git)?$/i);\n if (bare) return `${bare[1]}/${bare[2]}`.toLowerCase();\n const scp = normalized.match(/^git@github\\.com:([^/\\s:]+)\\/([^/\\s:]+?)(?:\\.git)?$/i);\n if (scp) return `${scp[1]}/${scp[2]}`.toLowerCase();\n try {\n const url = new URL(normalized);\n if (!['http:', 'https:', 'ssh:'].includes(url.protocol) || url.hostname.toLowerCase() !== 'github.com') return null;\n const path = url.pathname.match(/^\\/([^/\\s]+)\\/([^/\\s]+?)(?:\\.git)?\\/?$/i);\n return path ? `${path[1]}/${path[2]}`.toLowerCase() : null;\n } catch {\n return null;\n }\n}\n\nfunction originRepository(): string {\n try {\n return execFileSync('git', ['remote', 'get-url', 'origin'], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim();\n } catch {\n return '';\n }\n}\n\nfunction resolveCheckoutCommit(argv: string[]): { sha: string; tree: string } {\n const explicitSha = getArgValue(argv, '--sha');\n const explicitTree = getArgValue(argv, '--tree');\n if (explicitSha !== undefined && explicitTree !== undefined) {\n return { sha: explicitSha, tree: explicitTree };\n }\n if (gitOutput(['status', '--porcelain'])) {\n return fail('the working tree is dirty; commit and push, sync, and run again');\n }\n const sha = explicitSha ?? gitRevision('HEAD');\n const tree = explicitTree ?? gitRevision('HEAD^{tree}');\n return { sha, tree };\n}\n\nfunction gitOutput(args: string[]): string {\n try {\n return execFileSync('git', args, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim();\n } catch {\n return fail('could not inspect the working tree; run this command from a git checkout');\n }\n}\n\nfunction gitRevision(revision: string): string {\n try {\n return execFileSync('git', ['rev-parse', revision], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim();\n } catch {\n return fail(`could not resolve ${revision}; run this command from a git checkout`);\n }\n}\n\nfunction requiredNumber(argv: string[], flag: string): number {\n const value = getArgValue(argv, flag);\n const parsed = value ? Number(value) : Number.NaN;\n if (!Number.isSafeInteger(parsed) || parsed < 1) usageError(`${flag} must be a positive integer.`);\n return parsed;\n}\n\nfunction usageError(message: string): never {\n console.error(`Error: ${message}`);\n process.exit(1);\n}\n\nfunction fail(message: string): never {\n console.error(`Error: ${message}`);\n process.exit(1);\n}\n"],"mappings":"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","names":["query","proof"]}
|