@runcofactor/cli 0.28.0 → 0.29.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{account-6FPITX2M.js → account-QWQP6YCY.js} +3 -3
- package/dist/{alert-PWERXHCP.js → alert-65FH7P74.js} +4 -4
- package/dist/{api-catalog-HCSSHOFY.js → api-catalog-WM6R37CA.js} +3 -3
- package/dist/{attachment-A7JAZWWH.js → attachment-FTQTZUTA.js} +3 -3
- package/dist/{augmentor-MIEBKJUK.js → augmentor-IQMT6NFX.js} +3 -3
- package/dist/{chunk-3VHY375S.js → chunk-3VIBXXNX.js} +2 -2
- package/dist/{chunk-IFZ72FZV.js → chunk-3X3TSH2B.js} +2 -2
- package/dist/{chunk-ZP3RP4IK.js → chunk-6AJLNMHB.js} +2 -2
- package/dist/{chunk-44NI5IDC.js → chunk-6QGUAITE.js} +15 -1
- package/dist/{chunk-44NI5IDC.js.map → chunk-6QGUAITE.js.map} +1 -1
- package/dist/{chunk-5N4OZPFQ.js → chunk-DECTGV4O.js} +2 -2
- package/dist/{chunk-6BAMC77N.js → chunk-E22PSTUP.js} +2 -2
- package/dist/{chunk-4ZNTGBJR.js → chunk-EHKHZ5UR.js} +23 -23
- package/dist/chunk-EHKHZ5UR.js.map +1 -0
- package/dist/{chunk-DHL4O7MG.js → chunk-MBMFYGKN.js} +2 -2
- package/dist/{chunk-YBX5BIKL.js → chunk-PK5UICGR.js} +2 -2
- package/dist/{chunk-4SV3CGNM.js → chunk-SFRUUIIC.js} +2 -2
- package/dist/{chunk-LSL2M7O6.js → chunk-WTMSRIDK.js} +2 -2
- package/dist/{credential-KSLGCJU7.js → credential-CKO4ZBMU.js} +3 -3
- package/dist/{credential-template-WECUNR4X.js → credential-template-XOZ2FJ6Z.js} +3 -3
- package/dist/{dist-5PKCAL5N.js → dist-YPK2RMUK.js} +3 -3
- package/dist/{docs-PRUI4LAD.js → docs-WZCQ6GP7.js} +3 -3
- package/dist/{environment-ATT4UQCY.js → environment-KO3JUX3V.js} +3 -3
- package/dist/{feature-flag-MWIHVJER.js → feature-flag-4YPPF27F.js} +63 -5
- package/dist/feature-flag-4YPPF27F.js.map +1 -0
- package/dist/index.js +38 -38
- package/dist/{init-QBMKWGHZ.js → init-RXWDJV5I.js} +3 -3
- package/dist/{install-BHYIXK6G.js → install-Z7EGAEEW.js} +3 -3
- package/dist/{issues-JO3OVOV7.js → issues-XOU54YKP.js} +3 -3
- package/dist/{knobs-4LLJ4LCU.js → knobs-7MB2O5SW.js} +3 -3
- package/dist/{list-JVJSIK52.js → list-5CJZY5BC.js} +3 -3
- package/dist/{local-VCZGHODB.js → local-XLBE4JIT.js} +5 -5
- package/dist/{login-UBNUPK2H.js → login-6GDATX4X.js} +3 -3
- package/dist/{mcp-2BBNDVZI.js → mcp-52UNMEF3.js} +4 -4
- package/dist/{metrics-4YOSDGIG.js → metrics-4Z5IJZ7U.js} +3 -3
- package/dist/{product-T2PVBVZ4.js → product-N2R3MWZS.js} +3 -3
- package/dist/{prompts-MI4QK2ZM.js → prompts-X6FNI6NZ.js} +3 -3
- package/dist/{property-EKASZCO7.js → property-VUKL5IFR.js} +3 -3
- package/dist/{record-Z4NY75FM.js → record-AVGKVJK7.js} +4 -4
- package/dist/{release-Z7WKFMBS.js → release-4QOZ4IP6.js} +3 -3
- package/dist/{sandbox-YOODBA5Q.js → sandbox-4ZR6XRIQ.js} +14 -3
- package/dist/sandbox-4ZR6XRIQ.js.map +1 -0
- package/dist/{scenarios-ABYCUJ4O.js → scenarios-UZBDO3XR.js} +3 -3
- package/dist/{schedule-ZRHOCT5Q.js → schedule-XCSZ6NLT.js} +4 -4
- package/dist/{session-T7BV73AC.js → session-QXHDS5GP.js} +5 -5
- package/dist/{skill-VLCWQJHG.js → skill-FXNE77RE.js} +166 -159
- package/dist/skill-FXNE77RE.js.map +1 -0
- package/dist/{spec-QPWXRPAG.js → spec-TOUOP4TC.js} +3 -3
- package/dist/{src-BH5O6Z3E.js → src-YZGG5ZSL.js} +3 -3
- package/dist/{start-5BADZ65B.js → start-JDLIIBPC.js} +3 -3
- package/dist/{tag-CIR6U5LL.js → tag-LSS3IOUA.js} +4 -4
- package/dist/{task-4YKONXJI.js → task-KCNDIT4A.js} +3 -3
- package/dist/{test-suite-PRJ2PGMN.js → test-suite-MOVN2XL5.js} +5 -5
- package/dist/{trace-XVB47RLY.js → trace-7OZNMAKV.js} +4 -4
- package/dist/{triage-L5WR65KW.js → triage-BRSUW4Z7.js} +4 -4
- package/dist/{utilities-LBKWREPU.js → utilities-TXQGGH37.js} +3 -3
- package/dist/{workflow-GCZCXPGA.js → workflow-2TVSFQXK.js} +310 -302
- package/dist/workflow-2TVSFQXK.js.map +1 -0
- package/dist/{workflow-cache-XWTLXIHE.js → workflow-cache-LRH5CAPS.js} +6 -6
- package/dist/{workflow-debug-XHR4552F.js → workflow-debug-IWXZXSL2.js} +5 -5
- package/dist/{workflow-proposals-VWLKW352.js → workflow-proposals-76D2MM6G.js} +3 -3
- package/package.json +1 -1
- package/dist/chunk-4ZNTGBJR.js.map +0 -1
- package/dist/feature-flag-MWIHVJER.js.map +0 -1
- package/dist/sandbox-YOODBA5Q.js.map +0 -1
- package/dist/skill-VLCWQJHG.js.map +0 -1
- package/dist/workflow-GCZCXPGA.js.map +0 -1
- /package/dist/{account-6FPITX2M.js.map → account-QWQP6YCY.js.map} +0 -0
- /package/dist/{alert-PWERXHCP.js.map → alert-65FH7P74.js.map} +0 -0
- /package/dist/{api-catalog-HCSSHOFY.js.map → api-catalog-WM6R37CA.js.map} +0 -0
- /package/dist/{attachment-A7JAZWWH.js.map → attachment-FTQTZUTA.js.map} +0 -0
- /package/dist/{augmentor-MIEBKJUK.js.map → augmentor-IQMT6NFX.js.map} +0 -0
- /package/dist/{chunk-3VHY375S.js.map → chunk-3VIBXXNX.js.map} +0 -0
- /package/dist/{chunk-IFZ72FZV.js.map → chunk-3X3TSH2B.js.map} +0 -0
- /package/dist/{chunk-ZP3RP4IK.js.map → chunk-6AJLNMHB.js.map} +0 -0
- /package/dist/{chunk-5N4OZPFQ.js.map → chunk-DECTGV4O.js.map} +0 -0
- /package/dist/{chunk-6BAMC77N.js.map → chunk-E22PSTUP.js.map} +0 -0
- /package/dist/{chunk-DHL4O7MG.js.map → chunk-MBMFYGKN.js.map} +0 -0
- /package/dist/{chunk-YBX5BIKL.js.map → chunk-PK5UICGR.js.map} +0 -0
- /package/dist/{chunk-4SV3CGNM.js.map → chunk-SFRUUIIC.js.map} +0 -0
- /package/dist/{chunk-LSL2M7O6.js.map → chunk-WTMSRIDK.js.map} +0 -0
- /package/dist/{credential-KSLGCJU7.js.map → credential-CKO4ZBMU.js.map} +0 -0
- /package/dist/{credential-template-WECUNR4X.js.map → credential-template-XOZ2FJ6Z.js.map} +0 -0
- /package/dist/{dist-5PKCAL5N.js.map → dist-YPK2RMUK.js.map} +0 -0
- /package/dist/{docs-PRUI4LAD.js.map → docs-WZCQ6GP7.js.map} +0 -0
- /package/dist/{environment-ATT4UQCY.js.map → environment-KO3JUX3V.js.map} +0 -0
- /package/dist/{init-QBMKWGHZ.js.map → init-RXWDJV5I.js.map} +0 -0
- /package/dist/{install-BHYIXK6G.js.map → install-Z7EGAEEW.js.map} +0 -0
- /package/dist/{issues-JO3OVOV7.js.map → issues-XOU54YKP.js.map} +0 -0
- /package/dist/{knobs-4LLJ4LCU.js.map → knobs-7MB2O5SW.js.map} +0 -0
- /package/dist/{list-JVJSIK52.js.map → list-5CJZY5BC.js.map} +0 -0
- /package/dist/{local-VCZGHODB.js.map → local-XLBE4JIT.js.map} +0 -0
- /package/dist/{login-UBNUPK2H.js.map → login-6GDATX4X.js.map} +0 -0
- /package/dist/{mcp-2BBNDVZI.js.map → mcp-52UNMEF3.js.map} +0 -0
- /package/dist/{metrics-4YOSDGIG.js.map → metrics-4Z5IJZ7U.js.map} +0 -0
- /package/dist/{product-T2PVBVZ4.js.map → product-N2R3MWZS.js.map} +0 -0
- /package/dist/{prompts-MI4QK2ZM.js.map → prompts-X6FNI6NZ.js.map} +0 -0
- /package/dist/{property-EKASZCO7.js.map → property-VUKL5IFR.js.map} +0 -0
- /package/dist/{record-Z4NY75FM.js.map → record-AVGKVJK7.js.map} +0 -0
- /package/dist/{release-Z7WKFMBS.js.map → release-4QOZ4IP6.js.map} +0 -0
- /package/dist/{scenarios-ABYCUJ4O.js.map → scenarios-UZBDO3XR.js.map} +0 -0
- /package/dist/{schedule-ZRHOCT5Q.js.map → schedule-XCSZ6NLT.js.map} +0 -0
- /package/dist/{session-T7BV73AC.js.map → session-QXHDS5GP.js.map} +0 -0
- /package/dist/{spec-QPWXRPAG.js.map → spec-TOUOP4TC.js.map} +0 -0
- /package/dist/{src-BH5O6Z3E.js.map → src-YZGG5ZSL.js.map} +0 -0
- /package/dist/{start-5BADZ65B.js.map → start-JDLIIBPC.js.map} +0 -0
- /package/dist/{tag-CIR6U5LL.js.map → tag-LSS3IOUA.js.map} +0 -0
- /package/dist/{task-4YKONXJI.js.map → task-KCNDIT4A.js.map} +0 -0
- /package/dist/{test-suite-PRJ2PGMN.js.map → test-suite-MOVN2XL5.js.map} +0 -0
- /package/dist/{trace-XVB47RLY.js.map → trace-7OZNMAKV.js.map} +0 -0
- /package/dist/{triage-L5WR65KW.js.map → triage-BRSUW4Z7.js.map} +0 -0
- /package/dist/{utilities-LBKWREPU.js.map → utilities-TXQGGH37.js.map} +0 -0
- /package/dist/{workflow-cache-XWTLXIHE.js.map → workflow-cache-LRH5CAPS.js.map} +0 -0
- /package/dist/{workflow-debug-XHR4552F.js.map → workflow-debug-IWXZXSL2.js.map} +0 -0
- /package/dist/{workflow-proposals-VWLKW352.js.map → workflow-proposals-76D2MM6G.js.map} +0 -0
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
apiRequest,
|
|
4
4
|
fetchList,
|
|
5
5
|
getTargetOrg
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-WTMSRIDK.js";
|
|
7
7
|
import "./chunk-H3LWYSKY.js";
|
|
8
8
|
import {
|
|
9
9
|
getArgValue,
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
resolveConfig
|
|
12
12
|
} from "./chunk-YBYOOXMK.js";
|
|
13
13
|
import "./chunk-S3Q7MDCS.js";
|
|
14
|
-
import "./chunk-
|
|
14
|
+
import "./chunk-EHKHZ5UR.js";
|
|
15
15
|
import "./chunk-ZSOAFMES.js";
|
|
16
16
|
import "./chunk-ICKB3EOT.js";
|
|
17
17
|
|
|
@@ -253,4 +253,4 @@ async function runAccount(argv) {
|
|
|
253
253
|
export {
|
|
254
254
|
runAccount
|
|
255
255
|
};
|
|
256
|
-
//# sourceMappingURL=account-
|
|
256
|
+
//# sourceMappingURL=account-QWQP6YCY.js.map
|
|
@@ -17,8 +17,8 @@ import {
|
|
|
17
17
|
mergeSteps,
|
|
18
18
|
parseFilters,
|
|
19
19
|
updateRule
|
|
20
|
-
} from "./chunk-
|
|
21
|
-
import "./chunk-
|
|
20
|
+
} from "./chunk-E22PSTUP.js";
|
|
21
|
+
import "./chunk-WTMSRIDK.js";
|
|
22
22
|
import "./chunk-H3LWYSKY.js";
|
|
23
23
|
import {
|
|
24
24
|
getArgValue,
|
|
@@ -26,7 +26,7 @@ import {
|
|
|
26
26
|
resolveConfig
|
|
27
27
|
} from "./chunk-YBYOOXMK.js";
|
|
28
28
|
import "./chunk-S3Q7MDCS.js";
|
|
29
|
-
import "./chunk-
|
|
29
|
+
import "./chunk-EHKHZ5UR.js";
|
|
30
30
|
import "./chunk-ZSOAFMES.js";
|
|
31
31
|
import "./chunk-ICKB3EOT.js";
|
|
32
32
|
|
|
@@ -233,4 +233,4 @@ async function runAlert(argv) {
|
|
|
233
233
|
export {
|
|
234
234
|
runAlert
|
|
235
235
|
};
|
|
236
|
-
//# sourceMappingURL=alert-
|
|
236
|
+
//# sourceMappingURL=alert-65FH7P74.js.map
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { createRequire as __cr } from "module"; import { fileURLToPath as __fup } from "url"; import { dirname as __dn } from "path"; const require = __cr(import.meta.url); const __filename = __fup(import.meta.url); const __dirname = __dn(__filename);
|
|
2
2
|
import {
|
|
3
3
|
apiRequest
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-WTMSRIDK.js";
|
|
5
5
|
import "./chunk-H3LWYSKY.js";
|
|
6
6
|
import {
|
|
7
7
|
getArgValue,
|
|
8
8
|
hasFlag
|
|
9
9
|
} from "./chunk-YBYOOXMK.js";
|
|
10
10
|
import "./chunk-S3Q7MDCS.js";
|
|
11
|
-
import "./chunk-
|
|
11
|
+
import "./chunk-EHKHZ5UR.js";
|
|
12
12
|
import "./chunk-ZSOAFMES.js";
|
|
13
13
|
import "./chunk-ICKB3EOT.js";
|
|
14
14
|
|
|
@@ -399,4 +399,4 @@ function exitErr(msg) {
|
|
|
399
399
|
export {
|
|
400
400
|
runApiCatalog
|
|
401
401
|
};
|
|
402
|
-
//# sourceMappingURL=api-catalog-
|
|
402
|
+
//# sourceMappingURL=api-catalog-WM6R37CA.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createRequire as __cr } from "module"; import { fileURLToPath as __fup } from "url"; import { dirname as __dn } from "path"; const require = __cr(import.meta.url); const __filename = __fup(import.meta.url); const __dirname = __dn(__filename);
|
|
2
2
|
import {
|
|
3
3
|
apiRequest
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-WTMSRIDK.js";
|
|
5
5
|
import "./chunk-H3LWYSKY.js";
|
|
6
6
|
import {
|
|
7
7
|
getArgValue,
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
resolveConfig
|
|
10
10
|
} from "./chunk-YBYOOXMK.js";
|
|
11
11
|
import "./chunk-S3Q7MDCS.js";
|
|
12
|
-
import "./chunk-
|
|
12
|
+
import "./chunk-EHKHZ5UR.js";
|
|
13
13
|
import "./chunk-ZSOAFMES.js";
|
|
14
14
|
import "./chunk-ICKB3EOT.js";
|
|
15
15
|
|
|
@@ -274,4 +274,4 @@ async function runAttachment(argv) {
|
|
|
274
274
|
export {
|
|
275
275
|
runAttachment
|
|
276
276
|
};
|
|
277
|
-
//# sourceMappingURL=attachment-
|
|
277
|
+
//# sourceMappingURL=attachment-FTQTZUTA.js.map
|
|
@@ -2,13 +2,13 @@ import { createRequire as __cr } from "module"; import { fileURLToPath as __fup
|
|
|
2
2
|
import {
|
|
3
3
|
apiRequest,
|
|
4
4
|
getApplicationArg
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-WTMSRIDK.js";
|
|
6
6
|
import "./chunk-H3LWYSKY.js";
|
|
7
7
|
import {
|
|
8
8
|
getArgValue
|
|
9
9
|
} from "./chunk-YBYOOXMK.js";
|
|
10
10
|
import "./chunk-S3Q7MDCS.js";
|
|
11
|
-
import "./chunk-
|
|
11
|
+
import "./chunk-EHKHZ5UR.js";
|
|
12
12
|
import "./chunk-ZSOAFMES.js";
|
|
13
13
|
import "./chunk-ICKB3EOT.js";
|
|
14
14
|
|
|
@@ -195,4 +195,4 @@ async function runAugmentor(argv, apiUrl, token) {
|
|
|
195
195
|
export {
|
|
196
196
|
runAugmentor
|
|
197
197
|
};
|
|
198
|
-
//# sourceMappingURL=augmentor-
|
|
198
|
+
//# sourceMappingURL=augmentor-IQMT6NFX.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createRequire as __cr } from "module"; import { fileURLToPath as __fup } from "url"; import { dirname as __dn } from "path"; const require = __cr(import.meta.url); const __filename = __fup(import.meta.url); const __dirname = __dn(__filename);
|
|
2
2
|
import {
|
|
3
3
|
isTriageEnabledForExecution
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-6AJLNMHB.js";
|
|
5
5
|
import {
|
|
6
6
|
resolveConfig
|
|
7
7
|
} from "./chunk-YBYOOXMK.js";
|
|
@@ -401,4 +401,4 @@ async function runRemoteTest(argv) {
|
|
|
401
401
|
export {
|
|
402
402
|
runRemoteTest
|
|
403
403
|
};
|
|
404
|
-
//# sourceMappingURL=chunk-
|
|
404
|
+
//# sourceMappingURL=chunk-3VIBXXNX.js.map
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
} from "./chunk-S3Q7MDCS.js";
|
|
5
5
|
import {
|
|
6
6
|
isVolatileIdentifier
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-EHKHZ5UR.js";
|
|
8
8
|
import {
|
|
9
9
|
require_src
|
|
10
10
|
} from "./chunk-2EHYDUHX.js";
|
|
@@ -309039,4 +309039,4 @@ playwright-extra/dist/index.esm.js:
|
|
|
309039
309039
|
* @license MIT
|
|
309040
309040
|
*)
|
|
309041
309041
|
*/
|
|
309042
|
-
//# sourceMappingURL=chunk-
|
|
309042
|
+
//# sourceMappingURL=chunk-3X3TSH2B.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createRequire as __cr } from "module"; import { fileURLToPath as __fup } from "url"; import { dirname as __dn } from "path"; const require = __cr(import.meta.url); const __filename = __fup(import.meta.url); const __dirname = __dn(__filename);
|
|
2
2
|
import {
|
|
3
3
|
apiRequest
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-WTMSRIDK.js";
|
|
5
5
|
import {
|
|
6
6
|
getArgValue,
|
|
7
7
|
hasFlag,
|
|
@@ -268,4 +268,4 @@ export {
|
|
|
268
268
|
isTriageEnabledForExecution,
|
|
269
269
|
runTriage
|
|
270
270
|
};
|
|
271
|
-
//# sourceMappingURL=chunk-
|
|
271
|
+
//# sourceMappingURL=chunk-6AJLNMHB.js.map
|
|
@@ -871,6 +871,20 @@ PREFER SEED WORKFLOWS FOR DATA
|
|
|
871
871
|
|
|
872
872
|
See the Seed Workflows section for how to build and use them.
|
|
873
873
|
|
|
874
|
+
SANDBOX BASE-SEED DATA ($var.SEED.*)
|
|
875
|
+
|
|
876
|
+
On a sandbox environment, core baseline data (the application/property, a
|
|
877
|
+
credential, sometimes a starter workflow) is provisioned automatically by a
|
|
878
|
+
base seed and exposed as $var.SEED.{key} \u2014 e.g. $var.SEED.property_id.
|
|
879
|
+
|
|
880
|
+
Prefer these over hardcoded names when you need a core entity that already
|
|
881
|
+
exists on every tenant:
|
|
882
|
+
Good: "Open the property $var.SEED.property_id"
|
|
883
|
+
|
|
884
|
+
Base seeds are core-only. Anything test-specific (an issue, a notification,
|
|
885
|
+
run history) is NOT in the base seed \u2014 create it yourself (see below) or via
|
|
886
|
+
a per-test seed/custom action.
|
|
887
|
+
|
|
874
888
|
THE DATA OWNERSHIP PRINCIPLE
|
|
875
889
|
|
|
876
890
|
Own your data:
|
|
@@ -1123,4 +1137,4 @@ export {
|
|
|
1123
1137
|
WORKFLOW_GUIDE_CONCEPT_SECTIONS,
|
|
1124
1138
|
WORKFLOW_GUIDE_SECTIONS
|
|
1125
1139
|
};
|
|
1126
|
-
//# sourceMappingURL=chunk-
|
|
1140
|
+
//# sourceMappingURL=chunk-6QGUAITE.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../content/src/workflow-guide/overview.txt","../../content/src/workflow-guide/node-types.txt","../../content/src/workflow-guide/action-nodes.txt","../../content/src/workflow-guide/playbook.txt","../../content/src/workflow-guide/best-practices.txt","../../content/src/workflow-guide/macros.txt","../../content/src/workflow-guide/seed-workflows.txt","../../content/src/workflow-guide/data-lifecycle.txt","../../content/src/workflow-guide/assertions.txt","../../content/src/workflow-guide/api-sequences.txt","../../content/src/workflow-guide/cli-seeds.txt","../../content/src/workflow-guide/cli-api-sequences.txt","../../content/src/workflow-guide/index.ts"],"sourcesContent":["Workflows are visual test sequences that model user journeys through your application. Each workflow is a directed graph of nodes — login, navigate, action, assertion, wait, condition, setup, and more — connected to form a testable path.\n\nWhen to use workflows:\n\n- Critical paths: signup, checkout, billing, onboarding\n- High-risk areas: features that regress often or have complex logic\n- Multi-step journeys: flows spanning multiple pages or user roles\n\nFLOW TYPES\n\n standard The default. Models a single user journey end-to-end.\n Login → Navigate → Actions → Assertions → Cleanup.\n\n setup A prerequisite workflow that runs before other workflows.\n Use for shared data creation or environment preparation.\n Any published workflow can be referenced as a setup step.\n\n seed A data-seeding workflow that generates test data and exports\n variables for downstream workflows. Seed outputs are declared\n in the workflow definition and available as $var.{dataKey}\n in any workflow that references the seed.\n\n teardown A cleanup workflow that runs after a seed workflow completes.\n Automatically invoked to remove data the seed created.\n\nEXECUTION MODEL\n\n 1. Login node provisions an authenticated browser session\n 2. Nodes execute in sequence (or branch at condition nodes)\n 3. Each action node interacts with the live application\n 4. Assertions verify expected outcomes\n 5. Additional login nodes switch to a different user — each starts a\n new segment with a fresh session (great for multi-role testing)\n 6. Wait nodes pause execution (minutes, hours, days) — also creating\n segments. After a wait, a login re-establishes the session.\n 7. On completion, the run is marked success or failure\n\nVERSION HISTORY\n\n Workflows maintain version history. Publishing creates a versioned\n snapshot. Previous versions can be previewed or restored to draft.\n Setup nodes reference a specific published version of another workflow.","Every workflow is built from nodes. Each node has a type, a title, a description,\nand type-specific configuration.\n\nSTARTING NODE — pick the first node in this order\n The first node of a flow follows a strict hierarchy. Getting this wrong is the\n single most common authoring mistake (most often: opening with a navigate node).\n\n 1. SEED first — if the flow depends on pre-existing data, a `seed` node comes\n first. It provisions data in its own isolated context and exports\n $var.{dataKey} outputs the rest of the flow consumes.\n 2. Otherwise LOGIN first — for any authenticated flow, the first node is a\n `login` node that declares the ACTOR (who the journey runs as). This is the\n \"acting as\" step. Do NOT open an authenticated flow with a navigate node.\n 3. Navigate comes AFTER login — a navigate jumps to the test's entry point, but\n it follows the login; it is never the opening node of an authenticated flow.\n\n The only flows that may start with a navigate are UNauthenticated ones — signup,\n password reset, a public/marketing page, or a test of the login experience\n itself. For those, set the login node's `performLogin: false` (or omit the login\n node) and author the steps as the journey.\n\n LOGIN SUBSUMES LOGGING IN. A login node already provisions an authenticated\n session — it IS the act of logging in. Never emit a login node and then a\n \"navigate to /login\" → \"enter email/password\" → \"click Sign in\" sequence. Start\n with the login node and go straight to the first real action. The one exception\n is a flow whose explicit purpose is to test login (start logged out and make the\n login steps the journey).\n\nLOGIN\n Sets the user context. Select a credential to act as that user.\n The login node provisions an authenticated browser session — subsequent\n nodes start with the browser already logged in.\n\n Config:\n credentialId UUID of the credential to log in as. REQUIRED for the\n node to bind to a real credential. Without it the\n Flow Designer shows a red \"Select a credential\"\n warning even if credentialName is set.\n credentialName Human-readable name, used as the node title. Optional\n but recommended alongside credentialId.\n performLogin When true, the browser is authenticated automatically.\n\n Finding the credentialId:\n canary credential list [--env prod|dev]\n → prints \"ID: <uuid>\" under each credential name.\n Pick the ID whose property matches the environment the flow runs in.\n\n Ordering: login nodes should typically follow any seed nodes, not\n precede them. Seeds export variables (e.g. $var.workflow_id) that\n later nodes consume, so a seed-first / login-second order avoids\n wasted browser segments and lets login/navigate URLs reference\n seed outputs. See the SEED node type and the Node Ordering section\n of best-practices.\n\n - Workflows support multiple login nodes for multi-role testing.\n Each login node starts a new segment with a fresh browser session.\n Example: Login as Customer → place order → Login as Sales Rep → verify order.\n Great for two-sided apps, permission checks (member vs admin), and\n cross-role journeys.\n - Login nodes also appear after Wait nodes to re-establish the session.\n - A navigate node after login is optional but useful for jumping\n directly to the test's entry point.\n\nNAVIGATE\n Directs the browser to a specific path.\n\n Prefer relative paths (e.g. \"/flows\", \"/flows/$var.workflow_id\")\n over hard-coded absolute URLs. Relative paths let the same workflow\n run unchanged across environments (prod, dev, local, preview) — the\n base URL comes from the property/environment the run targets. A\n workflow that hard-codes \"https://app.dev.trycanary.ai/...\" will\n silently drive traffic against dev even when run against prod.\n\n Use an absolute URL only when the destination is genuinely off the\n application being tested (external identity provider, third-party\n checkout, docs site, etc.).\n\n - A navigate node after login is a great way to jump straight to the\n page where the test begins.\n - Select from discovered pages or enter a custom URL.\n\nACTION\n Performs a user interaction on the current page. Describe what the user\n should do (\"Create a new ticket\") and optionally provide input data via\n formFields.\n\n Every action node must have a title (customActionTitle). The title shows\n in the canvas, run timeline, failure reports, and issue tickets — an\n untitled action is nearly impossible to reason about after the fact.\n Keep titles short and intent-oriented: name the goal (and, where it helps,\n the purpose), e.g. \"Create a ticket\", \"Publish the flow\", or \"Click Start to\n open the run-config modal\". A title that names only the mechanics with no\n intent (\"Click the button\") is the thing to avoid.\n\n Keep actions focused on a single user goal — usually broader than one click,\n but a lone action that genuinely stands alone in the flow is a fine node:\n Good: \"Create a ticket with required fields\"\n Good: \"Click Start to open the run-config modal\" (single action — but it\n stands on its own and names its purpose)\n Bad: \"Click the button\" (mechanics only, no intent)\n Bad: \"Create a ticket, assign it, comment, upload, and verify\" (too stuffed)\n\n Aim for ~3–8 playbook steps per node; if a single outcome needs more, split it\n into sequential action nodes. See the Action Nodes section for the full\n altitude guidance (coherent step, assertion boundary, no hidden conditionals).\n\n Rule of thumb: if an action crosses multiple screens or changes multiple\n entities, split it. If it's a single user goal on a single screen, keep it.\n\n Actions can include:\n - customActionTitle: short intent-oriented title (required)\n - formFields: input data the action needs (name, email, etc.)\n - inlineAssertions: checks verified after the action completes (preferred\n over standalone assertion nodes — see Assertions section)\n - declaredOutputs: values to extract from the page for downstream use\n - customInstructions: additional guidance for the agent executing the step\n\nASSERTION\n Verifies expected outcomes. Define a condition to check.\n What happens on failure depends on the severity.\n\n Prefer inline assertions on action nodes over standalone assertion nodes.\n Inline assertions keep the test on track and produce cleaner failure\n reports. If verifying something requires significant interaction first,\n that's better modeled as an action node with inline assertions.\n\n Use standalone assertion nodes only when the check doesn't belong to\n any action — e.g., verifying a page state after navigation, or\n confirming data absence after a sequence of steps.\n\n Severity levels:\n fatal The test must end now — the run halts at this step.\n Use for preconditions later steps depend on.\n fail The test may continue — the run finishes marked failed,\n but later steps still run and produce signal.\n warn The test should continue — logged as a warning, run passes.\n\n Agentic assertions: for visual/layout checks that can't be mechanically\n validated, enable agent evaluation. These require the AI agent during\n replay and cannot be cached. Use for:\n - Timestamps or dates\n - Real-time data feeds\n - Content that changes between runs\n\nCONDITION\n Branches the workflow based on a runtime condition. Define a condition\n to evaluate, then connect separate paths for true and false outcomes.\n The condition is deterministic when it compares a $var or the acting-as\n identity (evaluated without the agent); otherwise the agent evaluates it\n against the live page.\n\n Reach for a condition node only when the branch is genuine. Conditional\n logic baked INTO an action node — \"if it isn't set to New York, set it\" /\n \"if patient X is missing, create X\" — is usually a sign the data wasn't\n staged, and it caches poorly. Prefer, in order: seed the precondition so\n the branch disappears; run an idempotent sequence unconditionally when the\n same steps reach the goal from any starting state; else a formal condition\n node. If a conditional must live inside an action node, set\n `alwaysUseAgent: true` on that node so the branch is reasoned about live and\n never mis-cached. See the Action Nodes section.\n\nWAIT\n Pauses execution for a specified duration (minutes, hours, or days).\n Creates a segmented flow — execution pauses, then resumes automatically.\n Add a Login node after the Wait to re-establish the browser session.\n\nSETUP\n References another published workflow to run as a prerequisite.\n\n Execution modes:\n Inline (default) Runs before each workflow\n Suite-level Runs once per suite, shared across workflows\n\n When to use: creating users, projects, or records that multiple tests\n depend on. Login is handled separately — setup nodes are for data creation.\n\nSEED\n Injects outputs from a seed workflow as variables. Seed workflows\n generate test data and export named values. Downstream workflows\n access these as $var.{dataKey}.\n\n Ordering: seed nodes should almost always come FIRST — before the\n login node. Seeds run in their own isolated context (usually via\n API sequence) and export variables into the workflow. Placing a\n login before the seed wastes a browser session, and any\n login/navigate node that references $var.{seed_output} must come\n after the seed that produces it.\n\n Typical pattern:\n Seed → Login → Navigate → ... action/assertion nodes\n\n Config:\n seedWorkflowId UUID of the seed workflow (required)\n seedWorkflowName Human-readable name (used as the node title)\n\n Seed workflows are the preferred way to provision test data. See the\n Seed Workflows section for details on building and using them.\n\nAPI SEQUENCE\n Executes multiple API calls in sequence without a browser.\n Use for backend-only operations, webhooks, or API testing.\n\n API sequences are also used inside seed workflows — start with a\n browser-based seed to discover the right API calls, then convert\n to an API sequence for speed and reliability.\n\nSANDBOX ACTION\n Executes a sandbox action — a server-side script defined on the\n environment template — directly on the run environment's sandbox\n instance. No browser involved. The action's structured output is\n registered as workflow variables: each output key becomes\n $var.{key} for downstream steps.\n\n Config:\n actionSlug Slug of the action (required; discover via\n list_sandbox_actions)\n params Input values keyed by the action's inputSchema\n property names; values may use $var.{name} / macros\n outputKeys Output variable keys (defaults to the action's\n outputSchema keys)\n\n Output registration is allowlisted by declared intent: the action's\n outputSchema keys, else the node's outputKeys. If neither declares\n anything, NO result keys register — declare outputs explicitly for\n schema-less actions.\n\n Environment constraint: the action runs on the environment's\n backing sandbox instance. On a non-sandbox environment (static\n staging/prod) the node SKIPS with a warning and registers no\n variables — so workflows containing one are effectively\n sandbox-only.\n\n Seeds: a seed workflow whose body is a single sandbox action node\n is the fastest way to provision data — it runs entirely\n server-side (no browser session), and its outputs satisfy the\n seed's declared outputs like any captured value.\n\nNOTIFICATION\n Sends notifications (Slack, email, etc.) at a point in the workflow.\n\nEND\n Explicitly terminates the workflow. Use when a condition branch\n should stop execution rather than continue.","WHAT A STRONG ACTION NODE LOOKS LIKE\n\n Action nodes are the workhorse of a flow — and they are also the unit the\n cache stores and replays. Sizing them well is what makes a flow both fast\n (cacheable sooner) and durable (resistant to benign UI churn). Four things\n separate a strong action node from a weak one.\n\n 1. IT READS AS ONE COHERENT STEP\n\n A good node has a title you could read aloud as a single instruction to a\n teammate: \"Create a ticket\", \"Fill out the subscription form\", \"Click Start\n to open the run-config modal\". The title names a goal — and, where it helps,\n the purpose of the step — not raw mechanics.\n\n A node is usually broader than a single click, but not always: a lone action\n that genuinely stands on its own in the flow is a fine node (\"Click Start to\n open the modal\"). The extremes to avoid are a purely mechanical title with no\n intent (\"Click the button\") and a node stuffed with several unrelated\n outcomes (\"Create a ticket, assign it, comment, and upload\"). When in doubt,\n err toward clearer, more discrete steps over one overloaded node.\n\n 2. IT HAS ROUGHLY 3–8 PLAYBOOK STEPS\n\n Each playbook step is one meaningful action or tool call — a click, a select,\n a form fill, a submit. A healthy node lands around 3–8 of them:\n\n click → fill_form → click (good — open, fill, submit)\n click → click → fill_form → click (good)\n 27 clicks (bad — that's several outcomes)\n\n \"Meaningful\" is the operative word. Incidental events collapse into the\n nearest step (a focus-click before a fill is part of the fill), and a whole\n form-fill is ONE fill_form step, not one step per field. If a single outcome\n would need more than ~8 steps, that's the signal to split it into sequential\n action nodes (\"Fill customer details\" → \"Fill billing details\" → \"Review &\n submit\") rather than grow one megastep.\n\n This ~3–8 is the STEP count inside one action node — not a cap on how many\n NODES a flow has. A flow is as long as its coherent journey needs (see Best\n Practices on flow scope).\n\n 3. IT HAS A CLEAR ASSERTION BOUNDARY\n\n A strong node ends at a checkpoint you can assert on: a modal disappeared,\n the URL changed, a row appeared, a toast fired. Put that check inline on the\n node (see the Assertions section) so the node's success is observable and the\n run gets a real progress marker at each step.\n\n Inline assertions verify the END STATE after the action. They can be relative\n or temporal (\"the row count dropped by 2\"), but only if the baseline was\n captured upstream — write `current row count = $var.captured_row_count - 2`,\n never \"the count dropped by 2\". The reason: an action may replay from cache\n while its assertion is judged live by the agent, which does NOT have the\n action's execution context. A self-contained assertion (absolute values, or\n values bound to captured variables) survives that split; one that silently\n assumes \"before\" state does not.\n\n 4. IT IS NOT CONDITIONAL\n\n Avoid baking branching into an action node — \"if the location isn't New York,\n set it to New York\", \"if patient X isn't in the table, create patient X\". A\n conditional in a node usually means the data wasn't staged properly, and it\n caches poorly (the cached path and the live path diverge).\n\n Prefer, in order:\n - Seed the precondition so the branch never exists (\"patient X always\n present\" → seed patient X). See Seed Workflows.\n - If the action is idempotent — the same sequence reaches the desired state\n regardless of where it started (re-selecting \"New York\" works whether or\n not it was already set) — just run the sequence unconditionally. No branch\n needed.\n - If the branch is genuine, use a formal CONDITION node (deterministic when\n the check is a $var / identity comparison; agentic otherwise), which keeps\n the branch explicit and cacheable per path.\n\n If a conditional truly must live inside a node, set `alwaysUseAgent: true` on\n it so the branch is reasoned about live every run and never mis-cached.\n\n WHY THIS MATTERS\n\n The node is the atomic cache unit. The smaller and more reliable each node is,\n the sooner the flow caches and the faster it replays. But because the node is\n also the unit of specificity, the more a node pins to incidental detail (exact\n button text, a precise field order), the more a benign change breaks it.\n Nailing the altitude — coherent goal, ~3–8 meaningful steps, a clean assertion\n boundary, no hidden branches — is what buys both speed and reliability at once.\n","A playbook is a sequence of human-readable steps that describe the actions\ntaken during a workflow node's execution. Playbooks bridge the gap between\nhigh-level node descriptions (\"Create a ticket\") and the low-level browser\ninteractions that fulfill them.\n\nWHAT A PLAYBOOK CONTAINS\n\n Each playbook is an ordered list of PlaybookSteps:\n\n order Step sequence number\n action What to do: click, fill, fill_form, select, navigate,\n wait, scroll, hover, upload, verify, handle_native_dialog,\n toggle, press_key, focus, drag, resize, capture,\n extract_table, python_execute, verify_download,\n verify_download_content, screenshot, agent_loop\n target The element or area being acted on\n params Key-value parameters for the action\n note Human-readable description of the step\n\n Example playbook for \"Create a ticket\":\n 1. click \"New Ticket\" button\n 2. fill_form Title: \"QA Test — Billing Bug\", Priority: \"High\"\n 3. click \"Submit\" button\n 4. verify Success toast appears\n\nHOW PLAYBOOKS ENABLE CACHE REPLAY\n\n When a workflow runs for the first time, the AI agent explores the\n application, performs actions, and generates a playbook for each node.\n The raw browser actions (clicks, keystrokes, waits) are cached alongside\n the playbook steps.\n\n On subsequent runs, the system uses playbook-step-driven replay:\n\n 1. Load the playbook steps from the node config\n 2. For each step, look up the cached browser actions\n 3. Replay the cached actions directly (no AI agent needed)\n 4. If a step's cache misses or fails, the AI agent heals that step\n\n This means most runs execute instantly via cache, with the agent only\n invoked for steps that have changed or broken.\n\nCOMPILED ENTRIES\n\n The cache maps playbook steps to cached action indices:\n\n cached Playbook step has matching cached actions — replayed directly\n gap No cache for this step — agent performs it fresh\n\n A fully cached node replays in seconds. A node with gaps falls back to\n agent execution only for the missing steps.\n\nPLAYBOOK HEALTH\n\n Each cached node tracks playbook health:\n\n verified Cache is working correctly across recent runs\n degraded Some steps have failures — cache still used but monitored\n stale Cache is no longer reliable — full agent re-execution needed\n\n Step-level failure counts track which specific steps break most often,\n guiding targeted cache healing.\n\nWHY PLAYBOOKS MATTER FOR AUTHORING\n\n When you build a workflow, the quality of your node descriptions directly\n affects playbook quality. Clear, intent-focused descriptions produce\n better playbooks, which produce more reliable cache replay.\n\n Good: \"Create a ticket with title 'QA Test' and priority 'High'\"\n → Clean playbook: fill_form with clear fields, click submit\n\n Bad: \"Do the thing on the page\"\n → Vague playbook: agent guesses at actions, cache breaks easily\n\n The playbook is the semantic contract between your intent and the\n execution engine. Write nodes that describe what a human would do,\n and the playbook will faithfully capture and replay it.\n","SCOPE: ONE COHERENT USER JOURNEY\n\n A workflow should test one job-to-be-done, not multiple unrelated tasks.\n Many steps are fine if they're all part of the same flow (creating,\n editing, verifying one entity). Stop when the goal is accomplished.\n\n Good — coherent journey:\n Login → Navigate to projects → Create project → Edit name →\n Assert name updated → Cleanup: delete project\n\n Good — multi-role journey:\n Login as Customer → Place an order →\n Login as Sales Rep → Verify the order appears in the queue\n\n Good — read-only flow:\n Login → Navigate to dashboard → Assert widgets visible\n\n Bad — multiple unrelated journeys:\n Dashboard → Create item → Manage users → Generate report\n (These are separate jobs — create separate workflows)\n\n Rule: \"Is this step dependent on or naturally connected to the previous\n steps?\" If no, you're starting a new journey — stop.\n\n A coherent flow can be long or short — there is no target node count. If a\n flow grows very long, ask whether it is really several INDEPENDENT journeys:\n splitting them into separate tests lets them run in parallel and fail\n independently. Keep a single coherent journey together — don't split mid-flow.\n\nWRITE STEPS AS HUMAN INTENT\n\n A workflow should read like instructions you'd explain to a teammate:\n - \"Create a ticket assigned to Support.\"\n - \"Submit the ticket and confirm it appears in the list.\"\n - \"Change the ticket status to Closed.\"\n\n Capture the \"why\" and the \"what\" — not UI mechanics.\n\nCHOOSE THE RIGHT GRANULARITY\n\n Too granular:\n - \"Click New\"\n - \"Type title\"\n - \"Type description\"\n - \"Click Save\"\n\n Too stuffed:\n - \"Create a ticket, assign it, comment, upload, and verify in three views\"\n\n Good:\n - \"Create a ticket with required fields\"\n - \"Assign the ticket to Support\"\n - \"Verify the ticket appears in the queue\"\n\n Sizing rule: a node is a single coherent goal of roughly 3–8 playbook steps\n (one meaningful action each — a whole form-fill is ONE fill_form step). The\n \"too granular\" list above is about splitting one goal into a node per keystroke\n or writing mechanics-only titles — NOT a ban on single-action nodes: a lone\n action that stands on its own (\"Click Start to open the modal\") is fine. This\n ~3–8 is per action node (its playbook STEPS); it is NOT a cap on how many NODES\n a flow has — flow length follows the journey (see SCOPE above). See the Action\n Nodes section.\n\n If an action crosses multiple screens or changes multiple entities, split it.\n If it's a single user goal on a single screen, keep it as one action.\n\nMAKE EACH TEST SELF-SUFFICIENT\n\n Tests should not depend on what another test created or on long-lived\n shared state. If a workflow needs data, provision it:\n\n - Use a seed workflow (preferred) to generate data and export variables\n - Use a setup node to reference a published workflow for shared data\n - As a last resort, add data creation steps at the start of the workflow\n - Always use macros ($EMAIL, $UUID, etc.) so reruns don't collide\n\n This keeps runs deterministic and prevents cascading failures.\n\nVERIFY EVERY SIGNIFICANT ACTION\n\n Before recording a step, verify it worked:\n\n - Form submissions: confirm acceptance (success message, redirect, new item in list)\n - Data creation: navigate to where the record should appear and confirm\n - Data modification: reload and confirm the change persisted\n - Deletions: confirm the item is gone (a toast alone is not enough)\n - Navigation: confirm the expected page loaded\n\n If verification fails, debug the issue — don't record and move on.\n\nKEEP DESCRIPTIONS CLEAR\n\n Node titles should be short and action-oriented:\n Good: \"Create a support ticket\"\n Bad: \"Step 3 - ticket creation process\"\n\n Node descriptions should add detail the title can't carry:\n Good: \"Fill the form with a QA Test subject and High priority, then submit\"\n Bad: \"Do the thing\"\n\nNODE ORDERING\n\n The start-of-workflow order is a strict hierarchy (see \"STARTING NODE\"\n in the Node Types guide):\n\n Seed (if needed) → Login → Navigate → ...\n\n The first node is the ACTOR, not a navigate: an authenticated flow opens\n with a `login` node (or a `seed`, if it needs data first) — never with a\n navigate node. A navigate jumps to the test's entry point AFTER login.\n Only unauthenticated flows (signup, password reset, public pages, or a\n login test) may start with a navigate.\n\n Seed nodes should come first. Seeds run in their own isolated context\n (usually via API sequence) and export variables the rest of the flow\n consumes. Putting a login before the seed wastes a browser segment,\n and any login/navigate that references $var.{seed_output} must come\n after the seed that produced it.\n\n Only skip the seed-first rule if the seed itself genuinely depends on\n a browser session created earlier in the same workflow (rare — in\n that case, prefer a separate browser-based seed workflow and reference\n it here).\n\nLOGIN BEST PRACTICES\n\n - Most workflows start with a login node. Workflows can also start\n with a seed node or API sequence when no browser session is needed yet.\n - Login SUBSUMES logging in. A login node already provisions an\n authenticated session — it IS the act of logging in. Never follow a\n login node with \"navigate to /login\" / \"enter email + password\" /\n \"click Sign in\" steps. Start with the login node and go straight to\n the first real action. (Only exception: a flow whose purpose is to\n test login — start logged out and make the login steps the journey.)\n - Login nodes require a credentialId (UUID), not just a credentialName.\n Without a credentialId the Flow Designer flags the node as invalid.\n Find credential IDs with: canary credential list [--env prod|dev]\n - Use multiple login nodes for multi-role testing — each login starts\n a new segment with a fresh session as that user\n - Don't test login itself (unless that's the explicit goal)\n - A navigate node after login is a great way to jump directly to the\n test's entry point\n\nPREFER INLINE ASSERTIONS\n\n Attach assertions to action nodes using inlineAssertions rather than\n adding standalone assertion nodes. Inline assertions keep the test\n focused and produce clearer failure reports. See the Assertions section\n for details.","Macros generate fresh, unique data every time a workflow runs. Variables let\nyou reference shared values across nodes. Both use the $ prefix.\n\nAVAILABLE MACROS\n\n $EMAIL() Unique test email test-a1b2@example-test.com\n $FIRST_NAME() Random first name Jordan\n $LAST_NAME() Random last name Chen\n $PHONE() Random US phone +1-555-382-7491\n $UUID() Random UUID v4 a1b2c3d4-e5f6-...\n $NOW() ISO 8601 timestamp 2026-02-19T14:30:00.000Z\n $TODAY() Current date 2026-02-19\n $DATE(N) Date offset by N days $DATE(7) = next week\n $TIMESTAMP() Unix timestamp (ms) 1771520400000\n $RANDOM_INT(min,max) Random integer $RANDOM_INT(1,100) = 42\n $RANDOM_STRING(len) Random alphanumeric $RANDOM_STRING(6) = xK9mQ4\n $SEQUENCE(name) Auto-incrementing $SEQUENCE(order) = 1, 2, 3...\n\nSYNTAX\n\n Macros follow the format $MACRO_NAME() or $MACRO_NAME(args).\n They can appear standalone or inline within a larger string:\n\n Standalone: $EMAIL()\n Inline: user-$UUID()@corp.com\n Escaped: \\$EMAIL() produces the literal text $EMAIL()\n\n Each run resolves macros independently — every execution gets fresh values.\n\nVARIABLES\n\n Variables reference values set during a workflow run. When an action has\n input fields or extracts data, those become $var.{name} references.\n\n Sources:\n Input A value entered in an action's field $var.company\n Macro A macro expression in a field $var.email\n Extracted Data extracted from the page $var.order_id\n Credential Values from the login credential $var.username\n\n Use $var.{name} in subsequent step formFields and descriptions.\n Don't re-hardcode values — reference the variable from the earlier step.\n\nHOW VARIABLES GET BOUND\n\n Two distinct paths create $var.{name} bindings. Picking the wrong one is a\n common foot-gun.\n\n 1. Typed input (auto-promote)\n\n Add an entry to inputValues on an action node:\n\n inputValues: { company: \"$RANDOM_STRING(8)\" }\n\n The key \"company\" auto-promotes to $var.company for every downstream\n node. Do NOT also list it in declaredOutputs — the input itself IS the\n binding.\n\n 2. DOM capture (declaredOutputs + playbook capture)\n\n For values that only exist after the page renders (URL fragment, badge\n text, generated ID in the breadcrumb), declare them upfront and capture\n them inside the playbook:\n\n declaredOutputs: [\"order_id\"]\n playbookSteps: [\n { order: N, action: \"capture\", target: \"<selector or rule>\",\n outputKey: \"order_id\" }\n ]\n\n The capture step writes into $var.order_id at runtime. Without\n declaredOutputs the downstream resolver won't recognize the binding.\n\n Rule of thumb: if you can type the value into a field before the run, it's\n an input. If the value only exists after something happens in the browser,\n it's a captured output.\n\nSEED VARIABLES\n\n When seed workflows are configured, their outputs are available as\n $var.{dataKey} references. If a seed provides data you need (a project ID,\n a user record), use the seed variable — don't create that data again.\n\nCOMMON PATTERNS\n\n Unique user registration:\n Email: $EMAIL()\n Name: $FIRST_NAME() $LAST_NAME()\n\n Numbered items:\n $SEQUENCE(\"orders\") → 1, 2, 3... (resets each run, independent per name)\n\n Date-relative testing:\n $DATE(30) for 30 days out, $DATE(-7) for last week, $TODAY() for now\n\n Unique identifiers:\n $UUID() for globally unique, $RANDOM_STRING(8) for shorter strings\n Combine with text: order-$RANDOM_STRING(6)\n\nBEST PRACTICES\n\n - Use $EMAIL() instead of hardcoded addresses for uniqueness\n - Use $SEQUENCE(\"name\") when you need predictable ordering within a run\n - Prefer $DATE(days) over fixed dates so tests stay valid over time\n - Always include a value for every formField — don't leave fields empty","Seed workflows are the preferred way to provision test data. A seed\nworkflow creates data your test needs and exports named variables that\ndownstream workflows can reference.\n\nWHY USE SEEDS\n\n - Reusable: one seed can serve many test workflows\n - Fast: seeds can use API sequences instead of slow browser interactions\n - Clean: paired teardown workflows handle cleanup automatically\n - Isolated: each run gets its own fresh data via macros\n\nHOW SEEDS WORK\n\n 1. Create a workflow with flowType \"seed\"\n 2. Define seedOutputs — the named values the seed exports\n (e.g., project_id, user_email, order_number)\n 3. The seed creates data and captures those values during execution\n 4. Downstream workflows add a Seed node that references this seed\n 5. The seed's outputs are available as $var.{dataKey} in the test\n\n Example:\n Seed \"Create Test Project\" exports: project_id, project_name\n Test workflow references the seed, then uses:\n Navigate to $var.project_id\n Assert page title contains $var.project_name\n\nCREATING SEEDS\n\n A seed workflow shell needs a name, declared outputs, and usually a paired\n teardown. Outputs are the contract downstream workflows depend on; define\n them before building the seed body so every node captures the right values.\n\n Simple data outputs need:\n - dataKey: stable variable name, used as $var.{dataKey}\n - name: human label\n - description: optional context for builders and reviewers\n\n Credential outputs additionally define credentialOutput, including provider\n type, generated field mapping, inbox behavior, and loginUrl.\n\n After creating the shell, add nodes through the Flow Designer, workflow sync,\n or API, then publish once the seed and paired teardown both pass.\n\n CLI users: see `canary workflow guide cli-seeds` for command usage.\n\nBUILDING A SEED: START WITH THE BROWSER\n\n The easiest way to build a seed is to start with browser-based steps:\n\n 1. Create a seed workflow (flowType: seed)\n 2. Add a login node with credentials that have permission to create data\n 3. Add action nodes that create the data through the UI\n - Use macros for all names and identifiers ($EMAIL, $RANDOM_STRING, etc.)\n 4. Add declaredOutputs on action nodes to capture generated values\n (IDs, URLs, confirmation numbers)\n 5. Define seedOutputs on the workflow to export those captured values\n 6. Test and publish the seed\n\n This approach lets you discover exactly what steps and data are needed\n by interacting with the real application.\n\nCONVERTING TO API SEQUENCES\n\n Once a browser-based seed works reliably, convert it to an API sequence\n for speed and reliability. Canary can do this automatically.\n\n Auto-conversion from a workflow run:\n 1. Open the seed workflow in the Flow Designer\n 2. Add an api_sequence node\n 3. In the node config, choose \"Generate from workflow run\"\n 4. Select a completed run of the browser-based seed\n 5. The AI agent analyzes the network requests captured during\n that run and generates an equivalent API sequence automatically\n 6. The agent tests each step against the live API to verify it works\n 7. Review the generated sequence and publish\n\n The AI agent has access to your application's API catalog and can:\n - Search for endpoints by keyword\n - Inspect request/response schemas\n - Test individual API calls to verify they work\n - Compose a multi-step sequence with variable passing between steps\n - Validate the full sequence end-to-end before finalizing\n\n You can also generate an API sequence from a prompt — describe what\n data to create and the agent will find the right endpoints and build\n the sequence.\n\n API sequences support two kinds of dynamic values:\n - Step references: {{stepId.response.path.to.value}} to pass data\n between steps (e.g., use the project ID from step 1 in step 2)\n - Macros: $EMAIL(), $UUID(), $RANDOM_STRING(), etc. for fresh\n unique data each run\n\n Exports map response fields to seed output variables:\n - stepId: which step to extract from\n - responsePath: dot notation path (e.g., \"data.id\")\n - variableName: the $var.{name} downstream workflows will use\n\nEXECUTION MODES\n\n Seeds automatically choose the best execution mode:\n\n browser Runs through the browser like a normal workflow\n api Runs the API sequence directly — no browser needed\n recommended Uses the API sequence if one exists, otherwise browser\n\n Once you've added an API sequence to a seed, it will automatically\n run via API — faster, cheaper, and more reliable than browser execution.\n\n Not all seeds can be converted — some data creation flows require\n browser interaction (file uploads, complex wizards, third-party\n integrations). Keep those as browser-based seeds.\n\nAPI CALL TIMEOUT\n\n When a seed runs in API mode (executionMode \"api\", or \"recommended\"\n resolving to API), each HTTP call uses a 2-minute (120s) timeout by\n default. Seeds/teardowns that hit slow provisioning endpoints can raise\n this per seed node via apiTimeoutSeconds — it applies to both the seed\n and its paired teardown. Ignored for browser-mode seeds.\n\n Set it three equivalent ways:\n - Flow Designer: the \"API call timeout (seconds)\" field on the Seed node\n - .canary.md: a \"- apiTimeoutSeconds: 300\" bullet under the Seed node\n - workflow JSON (create / sync): on the seed node's configJson, e.g.\n { \"nodeType\": \"seed\",\n \"configJson\": { \"seedExecutionMode\": \"api\", \"apiTimeoutSeconds\": 300 } }\n\n Leave it unset to keep the 120s default; an invalid value falls back to\n the default rather than erroring.\n\nTEARDOWN WORKFLOWS\n\n Every seed can have a paired teardown workflow that cleans up the\n data it created. Teardowns run automatically after the seed's\n downstream workflows complete.\n\n - Set flowType to \"teardown\" and link it to the seed\n - The teardown receives the same $var.{dataKey} variables the seed exported\n - Use those variables to find and delete the created data\n\n Teardowns are the cleanest way to handle cleanup — they keep cleanup\n logic separate from test logic and run automatically.\n\nSANDBOX-ACTION-BACKED SEEDS\n\n When the property has a verified sandbox template with actions, a seed\n workflow can provision data by running a SANDBOX ACTION node instead of\n driving the UI or composing API calls. The seed's body is a single\n sandbox_action node (plus End); the action's outputs register as\n $var.{key} and satisfy the seed's declared outputs directly.\n\n - Fastest seed form: runs entirely server-side, no browser session\n - Discover actions with list_sandbox_actions; outputs come from the\n action's outputSchema\n - Constraint: consuming workflows become sandbox-only — on a\n non-sandbox environment the action node skips with a warning and\n produces no variables\n - Teardown is usually unnecessary: sandbox environments are destroyed\n after the suite, taking provisioned data with them\n\nSEED BEST PRACTICES\n\n - Use macros in all created data ($EMAIL, $RANDOM_STRING, etc.)\n so each run gets unique, non-conflicting data\n - Keep seeds focused — one seed per logical dataset\n (e.g., \"Create Project with Members\" not \"Create Everything\")\n - Export only what downstream workflows need — don't over-export\n - Start with browser-based seeds, then use auto-conversion to generate\n an API sequence from a completed run\n - For sandbox-backed properties, prefer a sandbox-action-backed seed\n (see above) — server-side, fast, and reliable\n - Pair seeds with teardown workflows for automatic cleanup\n - Use suite-level setup nodes to share seed data across multiple\n workflows in the same test suite\n","Test data discipline keeps workflows reliable and concurrent-safe.\n\nPREFER SEED WORKFLOWS FOR DATA\n\n Seed workflows are the best way to provision test data. They run before\n your test workflow, create the data it needs, and export variables that\n downstream workflows can reference as $var.{dataKey}.\n\n Benefits of seeds over inline data creation:\n - Reusable across multiple workflows\n - Faster — can use API sequences instead of browser interactions\n - Cleaner separation of setup and test logic\n - Paired teardown workflows handle cleanup automatically\n\n See the Seed Workflows section for how to build and use them.\n\nTHE DATA OWNERSHIP PRINCIPLE\n\n Own your data:\n - Data you create → clean it up when the workflow finishes\n - Settings you modify → revert them when the workflow finishes\n - Data that was already there → never delete it\n\n It's fine to depend on pre-existing data (existing integrations, org\n settings, reference data). Only create what doesn't already exist or\n what the test specifically needs to own.\n\nCREATING TEST DATA\n\n If the test operates on a record (edit, delete, archive), create that\n record as an early step — unless it already exists and the test is meant\n to operate on it.\n\n Never reference \"any\" resource:\n Bad: \"Pick a project from the list\" (which one? may not exist)\n Good: \"Create a project named 'QA Test — $RANDOM_STRING(6)'\"\n Good: \"Use the seed output $var.project_id\"\n\n Always use macros in names to ensure uniqueness across parallel runs:\n Bad: \"Test Item\" (generic, will collide with other runs)\n Bad: \"QA Test Project\" (still collides if two runs overlap)\n Good: \"QA Test — $RANDOM_STRING(6)\" (unique every run)\n Good: \"QA Test $FIRST_NAME() $LAST_NAME()\" (unique and readable)\n\n After creating data, verify it exists before continuing.\n\nNAMING CONVENTION\n\n All test data should follow this pattern:\n\n Name/Title: \"QA Test $FIRST_NAME() $LAST_NAME()\"\n Email: \"$EMAIL()\"\n Phone: \"$PHONE()\"\n Project/Item: \"QA Test — $RANDOM_STRING(6)\"\n Unique ID: \"QA$TIMESTAMP()\"\n\n The \"QA Test\" prefix makes test data instantly recognizable. The macro\n suffix ensures every run creates unique data that won't collide with\n other runs or leftover data from previous executions.\n\nCLEANING UP (STRONGLY RECOMMENDED)\n\n Workflows that create data should include cleanup steps at the end.\n Workflows that modify settings should revert them.\n\n This isn't strictly required, but it's strongly recommended:\n - Cleanup prevents data accumulation over time\n - It keeps the test environment clean for other workflows\n - The \"QA Test\" naming convention makes orphaned data identifiable\n even when cleanup is skipped or fails\n\n Don't over-engineer cleanup — a simple \"navigate to the item and\n delete it\" is sufficient. For seed workflows, use a paired teardown\n workflow instead.\n\nWHAT NOT TO TOUCH\n\n - Don't create users, roles, or org-level config unless that's the goal\n - Don't modify shared resources (billing, integrations, API keys) as a side effect\n - Don't delete or modify data not owned by this workflow\n\nAVOIDING COLLISIONS WITH MACROS\n\n The best way to avoid data collisions is to use macros for every\n name, email, and identifier. When every run generates unique values,\n there's no risk of conflicting with parallel runs or leftover data:\n\n $EMAIL() Unique email every run\n $RANDOM_STRING(6) Short unique suffix for names\n $UUID() Globally unique identifier\n $TIMESTAMP() Unique-per-millisecond numeric ID\n\n Combined with the \"QA Test\" prefix, this means you never need to\n worry about whether data from a previous run still exists. Each run\n creates its own isolated dataset.\n\nREAD-ONLY FLOWS\n\n Workflows that only read data (verify dashboard loads, check reports)\n don't need creation or cleanup steps. Not every workflow needs to\n create data — only create what the test specifically requires.","Assertions verify that the application behaves as expected. There are two\nways to add assertions: inline on action nodes, or as standalone assertion\nnodes. Inline assertions are strongly preferred.\n\nINLINE ASSERTIONS (PREFERRED)\n\n Attach assertions directly to action nodes using the inlineAssertions\n field. These are verified immediately after the action completes and\n cached for fast replay.\n\n Inline assertions are the best way to verify outcomes because they:\n - Keep the test on track — verification happens right where the action is\n - Produce clean failure reports — each assertion is tied to the step that caused it\n - Are cacheable — fast replay on subsequent runs\n\n Example — after submitting a form:\n Action: \"Submit the support ticket\"\n Inline assertions:\n - \"Success toast appears\" (severity: warn)\n - \"Ticket appears in the ticket list\" (severity: fail)\n - \"Form is no longer visible\" (severity: fatal)\n\n Severity levels — what a failure means for the run:\n fatal The test must end now. The run halts at this step.\n Use when continuing is pointless: login succeeded, the page\n loaded, a precondition every later step depends on.\n Example: \"User is logged in and the dashboard is visible\"\n fail The test may continue. The step is marked failed and the run\n finishes marked failed, but later steps still execute and\n produce signal.\n Use for independent checks: data saved, expected row present,\n error absent.\n Example: \"The new record appears in the table\"\n warn The test should continue. The failure is recorded as a warning\n and the run still passes.\n Use for cosmetic or non-critical checks: toast appeared,\n animation completed.\n Example: \"Success toast appears\"\n\n Choosing a severity: ask \"if this is wrong, is there any value in\n running the rest of the test?\" No → fatal. \"Does this being wrong mean\n the feature is broken?\" Yes → fail. Otherwise → warn. When unsure,\n prefer fatal for preconditions and fail for feature claims. Always set\n a severity explicitly — don't leave it unspecified.\n\n If verifying something requires significant interaction (scrolling,\n navigating, expanding sections), model that as an action node with\n inline assertions — not a detailed standalone assertion node.\n\nSTANDALONE ASSERTION NODES (USE SPARINGLY)\n\n Use dedicated assertion nodes only when the check doesn't belong to\n any action:\n\n - Verifying a final page state after navigation (no action to attach to)\n - Checking that data was NOT modified (absence checks after a sequence)\n - Confirming a state visible only after a wait or page transition\n\n A typical workflow has 0-1 standalone assertion nodes. If you find\n yourself writing a complex assertion node that requires multiple steps\n to verify, consider whether it should be an action node with inline\n assertions instead.\n\nAGENTIC ASSERTIONS\n\n For visual or layout checks that can't be mechanically validated,\n enable agent evaluation. These require the AI agent during replay\n and cannot be cached.\n\n Use for:\n - Screenshots that should \"look correct\"\n - Charts or graphs rendering properly\n - Dynamic content that changes between runs\n - Timestamps or real-time data\n\n Standard assertions are preferred — only use agentic when the\n check is inherently subjective or dynamic.\n\nWRITING GOOD CONDITIONS\n\n Assertions must be specific and observable:\n\n Good:\n - \"The status badge shows 'Active'\"\n - \"The table contains a row with name 'Test Record'\"\n - \"Error message is not visible\"\n - \"The page title is 'Dashboard'\"\n\n Bad:\n - \"The page looks correct\"\n - \"The action was successful\"\n - \"Everything works\"\n\n Start with what's observable: \"The page displays...\", \"The table\n contains...\", \"The field shows...\". Include the specific value\n you expect.\n\nASSERTION BOUNDARIES & SELF-CONTAINED CHECKS\n\n Each action node should end at a checkpoint you can assert on — a modal\n disappeared, the URL changed, a row appeared, a toast fired. These boundaries\n double as the run's progress markers, so prefer a concrete end-state check\n to a vague one.\n\n Make every assertion SELF-CONTAINED. An action may replay from cache while\n its assertion is judged live by the agent, which does not have the action's\n execution context — so an assertion that silently assumes \"before\" state is\n unreliable. Relative or temporal checks are fine, but only when the baseline\n was captured upstream and referenced by variable:\n\n Good: \"current row count = $var.captured_row_count - 2\"\n Bad: \"the row count dropped by 2\" (no captured baseline — the live\n evaluator can't know the old count)\n\n Capture the baseline with declaredOutputs / a capture on the upstream node,\n then reference it as $var.<name> in the assertion.\n\nASSERTION PLACEMENT\n\n Place assertions at critical verification points:\n\n 1. After data creation: verify the record exists (inline on the create action)\n 2. After data modification: verify the change persisted (inline on the edit action)\n 3. After deletion: verify the item is gone (inline on the delete action)\n 4. At the goal completion point: verify the journey succeeded\n\n Don't assert everything — focus on the claims that matter.\n A focused assertion that proves the test's core claim is better\n than a dozen checks on every UI element.","API Sequences are the API-only counterpart to browser workflows. Instead of\ndriving a browser node-by-node, an API sequence is a single node\n(nodeType: \"api_sequence\") inside an api-only workflow that executes an\nordered list of HTTP calls, chaining outputs from one step into the inputs\nof the next.\n\nWHEN TO USE\n\n - Exercise an API without a UI (integration tests, fixture setup).\n - Seed or tear down state via API before/after a browser run.\n - Fast, deterministic smoke test of a critical flow.\n\nCONFIG SHAPE (ApiSequenceConfig)\n\n {\n \"steps\": [ ... ], // ordered; required\n \"description\": \"...\", // optional\n \"stopOnFailure\": true, // default: true\n \"exports\": [ ... ] // optional; see below\n }\n\nSTEP SHAPE (ApiSequenceStep)\n\n The authoritative type is in packages/types/src/api-sequence.ts. Key fields:\n\n {\n \"stepId\": \"createProject\", // optional; defaults to \"step{index}\"\n \"apiRequestId\": \"<catalog id>\", // required — points to api_requests row\n \"credentialId\": \"<uuid>\" | null, // null = unauthenticated\n \"credentialTemplateId\": \"<uuid>\" | null, // auth by persona/template (per-env); wins over credentialId\n \"credentialTemplateName\": \"<persona>\", // author-friendly alias for the above; resolved to the id on save\n \"method\": \"POST\", // optional; falls back to catalog default\n \"urlTemplate\": \"/projects/{{createProject.response.id}}\",\n \"queryParams\": [ { \"key\": \"limit\", \"value\": \"10\" } ],\n \"bodyTemplate\": \"{\\\"name\\\": \\\"$EMAIL()\\\"}\", // STRING (JSON), not object\n \"headersTemplate\": { \"X-Trace\": \"$UUID()\" }, // optional\n \"responseVariableName\": \"createProject\", // optional alias for stepId\n \"expectedStatusCodes\": [200, 201], // step fails if status is outside\n \"description\": \"...\", // optional UI label\n \"validations\": [ ... ] // assertions against the response\n }\n\n CREDENTIAL ROLES — `credentialId` is overloaded; two distinct uses exist:\n\n - Step-level `credentialId` (and a top-level/default credential on the\n sequence) is AUTH: it's what the api-sequence runner uses to\n authenticate each HTTP call. Editing this changes who is calling.\n\n - Step-level `credentialTemplateId` is AUTH-by-persona: instead of a fixed\n credential id, name a credential template/persona and the runner resolves\n it to the credential bound for the run's environment (mirrors a login\n node's from_template). Takes precedence over `credentialId`. Use this when\n the same sequence must run across environments (e.g. Dev + a sandbox that\n mints its own per-instance credential) rather than a single static id.\n\n - `credentialId` appearing INSIDE a step's `bodyTemplate` JSON string is\n DATA: it gets written into the request body and becomes part of the\n provisioned resource (e.g. the credential a created workflow logs in\n with). Editing this changes what the call creates.\n\n The two are independent. Swapping the auth credential won't change what\n the call provisions; swapping the body credential won't change auth.\n Mixing them up silently produces confusing test failures.\n\n `apiRequestId` references a row in the `api_requests` table. Discover IDs\n from the API catalog for the target property. If `urlTemplate`, `method`,\n or `bodyTemplate` are omitted, they are backfilled from the catalog entry's\n defaults at execution time.\n\n `bodyTemplate` is a JSON *string*, not a JSON object — templates and macros\n are interpolated into it as raw text before parsing.\n\nTEMPLATE SYNTAX\n\n Three ways to inject dynamic values. All three can appear in the same\n string (urlTemplate, bodyTemplate, headersTemplate values, or queryParams\n values).\n\n 1. STEP REFERENCES — outputs of prior steps.\n\n {{stepId.response.path.to.value}}\n\n The `.response.` segment is REQUIRED. This is the most common foot-gun:\n\n ✓ {{createProject.response.data.id}}\n ✗ {{createProject.data.id}} — missing .response., will warn at\n runtime and resolve to undefined\n ✗ {{projectId}} — no step reference at all\n\n The executor logs a warning for malformed references but does NOT\n block execution — a malformed template resolves to an empty string and\n the request goes out anyway. Always run once and check the run log.\n\n 2. MACROS — dynamic data generated fresh per run.\n\n $UUID() Random UUID v4\n $NOW() ISO 8601 timestamp (UTC)\n $TODAY() YYYY-MM-DD\n $DATE(N) Date offset by N days\n $TIMESTAMP() Unix ms\n $RANDOM_INT(min,max) Random integer in range\n $RANDOM_STRING(N) Alphanumeric string of length N\n $EMAIL() test-<rand>@example-test.com\n $FIRST_NAME() Random first name\n $LAST_NAME() Random last name\n $PHONE() +1-555-...\n $SEQUENCE(\"name\") Per-run auto-increment counter\n\n Each run resolves macros independently. $SEQUENCE counters persist\n across steps within a single run.\n\n 3. WORKFLOW VARIABLES — values injected by the workflow runtime.\n\n $var.<name>\n\n These come from two places:\n\n a) Upstream credential variables — pre-provisioned before the\n sequence runs. Typical names: `$var.credential.username`,\n `$var.credential.password`, `$var.credential.email`. If the\n workflow has a credential, USE these for the matching fields\n instead of `$EMAIL()` / `$RANDOM_STRING()` — the values must\n match the already-provisioned account.\n\n b) Upstream exports — values exported by earlier nodes in the same\n workflow (e.g. a seed step produced `$var.project_id`).\n\n Naming convention for exported variables: `resource_field` with\n underscores (e.g. `project_id`, `user_email`).\n\nEXPORTS\n\n After all steps finish, the sequence can register values from the final\n state into the workflow variable registry for downstream nodes:\n\n {\n \"exports\": [\n {\n \"variableName\": \"project_id\", // appears as $var.project_id\n \"stepId\": \"createProject\",\n \"responsePath\": \"data.id\",\n \"source\": \"response\", // \"response\" (default) | \"request\"\n \"description\": \"...\" // optional UI label\n }\n ]\n }\n\n Only export fields downstream nodes actually need. Use the\n `resource_field` naming convention.\n\nVALIDATIONS\n\n Per-step assertions against the response. Each validation:\n\n {\n \"left\": \"createProject.response.data.status\", // \"stepId.path\" form\n \"operator\": \"equals\", // see list\n \"right\": \"active\", // omit for is_set/is_not_set\n \"description\": \"...\"\n }\n\n Operators: equals, not_equals, gt, lt, gte, lte, contains, not_contains,\n is_set, is_not_set.\n\nWHAT IS VALIDATED AT COMMIT TIME\n\n Very little. The save endpoint accepts anything matching\n { steps: any[], description?, stopOnFailure?, exports? }. It does NOT\n verify that apiRequestId exists in the catalog, that stepIds are unique,\n that {{stepId.response.path}} references a real step, that exports'\n stepId resolves, or that macro names are known.\n\n The only template sanity check is `validateStepTemplates()` in\n apps/api/src/workflows/execution/api-sequence-executor.ts, which runs\n at EXECUTE time, only flags `{{stepId.X}}` missing `.response.`, and only\n LOGS A WARNING — the sequence still runs. Always smoke-test the sequence\n before committing it to a workflow.\n\n CLI users: see `canary workflow guide cli-api-sequences` for command usage.\n\nEXAMPLE (3-step sequence, correct shape)\n\n {\n \"description\": \"Create an item, fetch it, delete it\",\n \"stopOnFailure\": true,\n \"steps\": [\n {\n \"stepId\": \"create\",\n \"apiRequestId\": \"<POST /items id>\",\n \"credentialId\": null,\n \"method\": \"POST\",\n \"bodyTemplate\": \"{\\\"name\\\": \\\"item-$RANDOM_STRING(6)\\\"}\",\n \"expectedStatusCodes\": [201]\n },\n {\n \"stepId\": \"fetch\",\n \"apiRequestId\": \"<GET /items/:id id>\",\n \"credentialId\": null,\n \"method\": \"GET\",\n \"urlTemplate\": \"/items/{{create.response.id}}\",\n \"expectedStatusCodes\": [200],\n \"validations\": [\n { \"left\": \"fetch.response.name\", \"operator\": \"is_set\" }\n ]\n },\n {\n \"stepId\": \"remove\",\n \"apiRequestId\": \"<DELETE /items/:id id>\",\n \"credentialId\": null,\n \"method\": \"DELETE\",\n \"urlTemplate\": \"/items/{{create.response.id}}\",\n \"expectedStatusCodes\": [200, 204]\n }\n ],\n \"exports\": [\n { \"variableName\": \"item_id\", \"stepId\": \"create\", \"responsePath\": \"id\" }\n ]\n }\n\nTIPS\n\n - Give every step a short, stable `stepId`. Later steps and exports\n reference it; the default `stepN` label is brittle to reordering.\n - Always include `.response.` in step references. The runtime won't\n error out, but values will silently resolve to empty strings.\n - Prefer `$EMAIL()`, `$UUID()`, `$RANDOM_STRING()` for fields that must\n be unique per run — unless the workflow has an upstream credential,\n in which case use `$var.credential.username` / `.password` / `.email`.\n - Set `expectedStatusCodes` on every step so unexpected 4xx/5xx fails\n the step instead of silently flowing into the next template.\n - `bodyTemplate` is a JSON STRING. Escape inner quotes.\n - `workflow api-sequence generate` can AI-draft a config from a prompt\n or a prior browser run; refine with `update`. The generated output is\n just JSON — review it before committing.\n","Use this section when you need to create or inspect seed workflows from the\nCanary CLI. The conceptual model lives in `seed-workflows`; this section is\nonly command orientation. Prefer each command's `--help` output for the\ncurrent flag list.\n\nCREATE A SIMPLE SEED SHELL\n\n For simple data outputs, create the seed shell with repeatable output\n declarations:\n\n canary workflow seed create \\\n --name \"Create Test Project\" \\\n --description \"Provisions a test project with default settings\" \\\n --output \"project_id:Project ID:The created project's UUID\" \\\n --output \"project_name:Project Name\"\n\n This creates a draft seed workflow and, by default, a paired teardown\n workflow linked to the seed.\n\nCREATE A COMPLEX SEED SHELL\n\n For credential outputs or larger configs, put the full seed definition in a\n JSON file and create from that file:\n\n canary workflow seed create --from-file seed.json\n\n Keep generated credential fields in credentialOutput, and keep exported data\n fields in outputs. Downstream workflows consume both through $var.{dataKey}.\n\nAFTER CREATION\n\n Add the seed body through the Flow Designer or workflow sync, test both the\n seed and teardown, then publish. If the seed can be API-backed, generate or\n attach an API sequence after the browser version is proven.\n\nREFERENCE\n\n - Seed command help: `canary workflow seed --help`\n - Workflow sync help: `canary workflow sync --help`\n","Use this section when you need to drive API sequence authoring from the\nCanary CLI. The conceptual model lives in `api-sequences`; this section is\nonly command orientation. Prefer each command's `--help` output for the\ncurrent flag list.\n\nCOMMON FLOW\n\n 1. Inspect the API catalog for candidate endpoints.\n\n canary api-catalog sources list\n canary api-catalog endpoints list --search <keyword>\n canary api-catalog endpoints get <apiRequestId>\n\n 2. Compose a sequence JSON file locally. Reference endpoints by\n apiRequestId and chain steps with {{stepId.response.path}}.\n\n 3. Create an API-only workflow from the sequence file.\n\n canary workflow create --api-only --name \"smoke: create project\" \\\n --sequence-from-file seq.json\n\n The command returns the workflow, sequence, and node IDs.\n\n 4. Execute immediately to surface template warnings and API failures.\n\n canary workflow api-sequence execute <workflowId>\n\n 5. Inspect the run and iterate on the sequence file.\n\n canary workflow runs list <workflowId> --side api\n canary workflow runs get <workflowId> <runId>\n canary workflow api-sequence update <workflowId> --from-file seq.json\n\nREFERENCE\n\n - Command help: `canary workflow api-sequence --help`\n - Workflow creation help: `canary workflow create --help`\n - API catalog help: `canary api-catalog --help`\n","import type { GuideSection } from '../types.js';\n\nimport overview from './overview.txt';\nimport nodeTypes from './node-types.txt';\nimport actionNodes from './action-nodes.txt';\nimport playbook from './playbook.txt';\nimport bestPractices from './best-practices.txt';\nimport macros from './macros.txt';\nimport seedWorkflows from './seed-workflows.txt';\nimport dataLifecycle from './data-lifecycle.txt';\nimport assertions from './assertions.txt';\nimport apiSequences from './api-sequences.txt';\nimport cliSeeds from './cli-seeds.txt';\nimport cliApiSequences from './cli-api-sequences.txt';\n\nexport const WORKFLOW_GUIDE_CONCEPT_SECTIONS: GuideSection[] = [\n { title: 'Workflow Overview', slug: 'overview', content: overview },\n { title: 'Node Types', slug: 'node-types', content: nodeTypes },\n { title: 'Action Nodes', slug: 'action-nodes', content: actionNodes },\n { title: 'Playbooks & Cache', slug: 'playbook', content: playbook },\n { title: 'Best Practices', slug: 'best-practices', content: bestPractices },\n { title: 'Macros & Variables', slug: 'macros', content: macros },\n { title: 'Seed Workflows', slug: 'seed-workflows', content: seedWorkflows },\n { title: 'Test Data Lifecycle', slug: 'data-lifecycle', content: dataLifecycle },\n { title: 'Assertions', slug: 'assertions', content: assertions },\n { title: 'API Sequences', slug: 'api-sequences', content: apiSequences },\n];\n\nexport const WORKFLOW_GUIDE_CLI_SECTIONS: GuideSection[] = [\n { title: 'CLI: Seed Workflows', slug: 'cli-seeds', content: cliSeeds },\n { title: 'CLI: API Sequences', slug: 'cli-api-sequences', content: cliApiSequences },\n];\n\nexport const WORKFLOW_GUIDE_SECTIONS: GuideSection[] = [\n ...WORKFLOW_GUIDE_CONCEPT_SECTIONS,\n ...WORKFLOW_GUIDE_CLI_SECTIONS,\n];\n"],"mappings":";;;AAAA;;;ACAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA;;;ACAA;;;ACAA;;;ACeO,IAAM,kCAAkD;AAAA,EAC7D,EAAE,OAAO,qBAAqB,MAAM,YAAY,SAAS,iBAAS;AAAA,EAClE,EAAE,OAAO,cAAc,MAAM,cAAc,SAAS,mBAAU;AAAA,EAC9D,EAAE,OAAO,gBAAgB,MAAM,gBAAgB,SAAS,qBAAY;AAAA,EACpE,EAAE,OAAO,qBAAqB,MAAM,YAAY,SAAS,iBAAS;AAAA,EAClE,EAAE,OAAO,kBAAkB,MAAM,kBAAkB,SAAS,uBAAc;AAAA,EAC1E,EAAE,OAAO,sBAAsB,MAAM,UAAU,SAAS,eAAO;AAAA,EAC/D,EAAE,OAAO,kBAAkB,MAAM,kBAAkB,SAAS,uBAAc;AAAA,EAC1E,EAAE,OAAO,uBAAuB,MAAM,kBAAkB,SAAS,uBAAc;AAAA,EAC/E,EAAE,OAAO,cAAc,MAAM,cAAc,SAAS,mBAAW;AAAA,EAC/D,EAAE,OAAO,iBAAiB,MAAM,iBAAiB,SAAS,sBAAa;AACzE;AAEO,IAAM,8BAA8C;AAAA,EACzD,EAAE,OAAO,uBAAuB,MAAM,aAAa,SAAS,kBAAS;AAAA,EACrE,EAAE,OAAO,sBAAsB,MAAM,qBAAqB,SAAS,0BAAgB;AACrF;AAEO,IAAM,0BAA0C;AAAA,EACrD,GAAG;AAAA,EACH,GAAG;AACL;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../content/src/workflow-guide/overview.txt","../../content/src/workflow-guide/node-types.txt","../../content/src/workflow-guide/action-nodes.txt","../../content/src/workflow-guide/playbook.txt","../../content/src/workflow-guide/best-practices.txt","../../content/src/workflow-guide/macros.txt","../../content/src/workflow-guide/seed-workflows.txt","../../content/src/workflow-guide/data-lifecycle.txt","../../content/src/workflow-guide/assertions.txt","../../content/src/workflow-guide/api-sequences.txt","../../content/src/workflow-guide/cli-seeds.txt","../../content/src/workflow-guide/cli-api-sequences.txt","../../content/src/workflow-guide/index.ts"],"sourcesContent":["Workflows are visual test sequences that model user journeys through your application. Each workflow is a directed graph of nodes — login, navigate, action, assertion, wait, condition, setup, and more — connected to form a testable path.\n\nWhen to use workflows:\n\n- Critical paths: signup, checkout, billing, onboarding\n- High-risk areas: features that regress often or have complex logic\n- Multi-step journeys: flows spanning multiple pages or user roles\n\nFLOW TYPES\n\n standard The default. Models a single user journey end-to-end.\n Login → Navigate → Actions → Assertions → Cleanup.\n\n setup A prerequisite workflow that runs before other workflows.\n Use for shared data creation or environment preparation.\n Any published workflow can be referenced as a setup step.\n\n seed A data-seeding workflow that generates test data and exports\n variables for downstream workflows. Seed outputs are declared\n in the workflow definition and available as $var.{dataKey}\n in any workflow that references the seed.\n\n teardown A cleanup workflow that runs after a seed workflow completes.\n Automatically invoked to remove data the seed created.\n\nEXECUTION MODEL\n\n 1. Login node provisions an authenticated browser session\n 2. Nodes execute in sequence (or branch at condition nodes)\n 3. Each action node interacts with the live application\n 4. Assertions verify expected outcomes\n 5. Additional login nodes switch to a different user — each starts a\n new segment with a fresh session (great for multi-role testing)\n 6. Wait nodes pause execution (minutes, hours, days) — also creating\n segments. After a wait, a login re-establishes the session.\n 7. On completion, the run is marked success or failure\n\nVERSION HISTORY\n\n Workflows maintain version history. Publishing creates a versioned\n snapshot. Previous versions can be previewed or restored to draft.\n Setup nodes reference a specific published version of another workflow.","Every workflow is built from nodes. Each node has a type, a title, a description,\nand type-specific configuration.\n\nSTARTING NODE — pick the first node in this order\n The first node of a flow follows a strict hierarchy. Getting this wrong is the\n single most common authoring mistake (most often: opening with a navigate node).\n\n 1. SEED first — if the flow depends on pre-existing data, a `seed` node comes\n first. It provisions data in its own isolated context and exports\n $var.{dataKey} outputs the rest of the flow consumes.\n 2. Otherwise LOGIN first — for any authenticated flow, the first node is a\n `login` node that declares the ACTOR (who the journey runs as). This is the\n \"acting as\" step. Do NOT open an authenticated flow with a navigate node.\n 3. Navigate comes AFTER login — a navigate jumps to the test's entry point, but\n it follows the login; it is never the opening node of an authenticated flow.\n\n The only flows that may start with a navigate are UNauthenticated ones — signup,\n password reset, a public/marketing page, or a test of the login experience\n itself. For those, set the login node's `performLogin: false` (or omit the login\n node) and author the steps as the journey.\n\n LOGIN SUBSUMES LOGGING IN. A login node already provisions an authenticated\n session — it IS the act of logging in. Never emit a login node and then a\n \"navigate to /login\" → \"enter email/password\" → \"click Sign in\" sequence. Start\n with the login node and go straight to the first real action. The one exception\n is a flow whose explicit purpose is to test login (start logged out and make the\n login steps the journey).\n\nLOGIN\n Sets the user context. Select a credential to act as that user.\n The login node provisions an authenticated browser session — subsequent\n nodes start with the browser already logged in.\n\n Config:\n credentialId UUID of the credential to log in as. REQUIRED for the\n node to bind to a real credential. Without it the\n Flow Designer shows a red \"Select a credential\"\n warning even if credentialName is set.\n credentialName Human-readable name, used as the node title. Optional\n but recommended alongside credentialId.\n performLogin When true, the browser is authenticated automatically.\n\n Finding the credentialId:\n canary credential list [--env prod|dev]\n → prints \"ID: <uuid>\" under each credential name.\n Pick the ID whose property matches the environment the flow runs in.\n\n Ordering: login nodes should typically follow any seed nodes, not\n precede them. Seeds export variables (e.g. $var.workflow_id) that\n later nodes consume, so a seed-first / login-second order avoids\n wasted browser segments and lets login/navigate URLs reference\n seed outputs. See the SEED node type and the Node Ordering section\n of best-practices.\n\n - Workflows support multiple login nodes for multi-role testing.\n Each login node starts a new segment with a fresh browser session.\n Example: Login as Customer → place order → Login as Sales Rep → verify order.\n Great for two-sided apps, permission checks (member vs admin), and\n cross-role journeys.\n - Login nodes also appear after Wait nodes to re-establish the session.\n - A navigate node after login is optional but useful for jumping\n directly to the test's entry point.\n\nNAVIGATE\n Directs the browser to a specific path.\n\n Prefer relative paths (e.g. \"/flows\", \"/flows/$var.workflow_id\")\n over hard-coded absolute URLs. Relative paths let the same workflow\n run unchanged across environments (prod, dev, local, preview) — the\n base URL comes from the property/environment the run targets. A\n workflow that hard-codes \"https://app.dev.trycanary.ai/...\" will\n silently drive traffic against dev even when run against prod.\n\n Use an absolute URL only when the destination is genuinely off the\n application being tested (external identity provider, third-party\n checkout, docs site, etc.).\n\n - A navigate node after login is a great way to jump straight to the\n page where the test begins.\n - Select from discovered pages or enter a custom URL.\n\nACTION\n Performs a user interaction on the current page. Describe what the user\n should do (\"Create a new ticket\") and optionally provide input data via\n formFields.\n\n Every action node must have a title (customActionTitle). The title shows\n in the canvas, run timeline, failure reports, and issue tickets — an\n untitled action is nearly impossible to reason about after the fact.\n Keep titles short and intent-oriented: name the goal (and, where it helps,\n the purpose), e.g. \"Create a ticket\", \"Publish the flow\", or \"Click Start to\n open the run-config modal\". A title that names only the mechanics with no\n intent (\"Click the button\") is the thing to avoid.\n\n Keep actions focused on a single user goal — usually broader than one click,\n but a lone action that genuinely stands alone in the flow is a fine node:\n Good: \"Create a ticket with required fields\"\n Good: \"Click Start to open the run-config modal\" (single action — but it\n stands on its own and names its purpose)\n Bad: \"Click the button\" (mechanics only, no intent)\n Bad: \"Create a ticket, assign it, comment, upload, and verify\" (too stuffed)\n\n Aim for ~3–8 playbook steps per node; if a single outcome needs more, split it\n into sequential action nodes. See the Action Nodes section for the full\n altitude guidance (coherent step, assertion boundary, no hidden conditionals).\n\n Rule of thumb: if an action crosses multiple screens or changes multiple\n entities, split it. If it's a single user goal on a single screen, keep it.\n\n Actions can include:\n - customActionTitle: short intent-oriented title (required)\n - formFields: input data the action needs (name, email, etc.)\n - inlineAssertions: checks verified after the action completes (preferred\n over standalone assertion nodes — see Assertions section)\n - declaredOutputs: values to extract from the page for downstream use\n - customInstructions: additional guidance for the agent executing the step\n\nASSERTION\n Verifies expected outcomes. Define a condition to check.\n What happens on failure depends on the severity.\n\n Prefer inline assertions on action nodes over standalone assertion nodes.\n Inline assertions keep the test on track and produce cleaner failure\n reports. If verifying something requires significant interaction first,\n that's better modeled as an action node with inline assertions.\n\n Use standalone assertion nodes only when the check doesn't belong to\n any action — e.g., verifying a page state after navigation, or\n confirming data absence after a sequence of steps.\n\n Severity levels:\n fatal The test must end now — the run halts at this step.\n Use for preconditions later steps depend on.\n fail The test may continue — the run finishes marked failed,\n but later steps still run and produce signal.\n warn The test should continue — logged as a warning, run passes.\n\n Agentic assertions: for visual/layout checks that can't be mechanically\n validated, enable agent evaluation. These require the AI agent during\n replay and cannot be cached. Use for:\n - Timestamps or dates\n - Real-time data feeds\n - Content that changes between runs\n\nCONDITION\n Branches the workflow based on a runtime condition. Define a condition\n to evaluate, then connect separate paths for true and false outcomes.\n The condition is deterministic when it compares a $var or the acting-as\n identity (evaluated without the agent); otherwise the agent evaluates it\n against the live page.\n\n Reach for a condition node only when the branch is genuine. Conditional\n logic baked INTO an action node — \"if it isn't set to New York, set it\" /\n \"if patient X is missing, create X\" — is usually a sign the data wasn't\n staged, and it caches poorly. Prefer, in order: seed the precondition so\n the branch disappears; run an idempotent sequence unconditionally when the\n same steps reach the goal from any starting state; else a formal condition\n node. If a conditional must live inside an action node, set\n `alwaysUseAgent: true` on that node so the branch is reasoned about live and\n never mis-cached. See the Action Nodes section.\n\nWAIT\n Pauses execution for a specified duration (minutes, hours, or days).\n Creates a segmented flow — execution pauses, then resumes automatically.\n Add a Login node after the Wait to re-establish the browser session.\n\nSETUP\n References another published workflow to run as a prerequisite.\n\n Execution modes:\n Inline (default) Runs before each workflow\n Suite-level Runs once per suite, shared across workflows\n\n When to use: creating users, projects, or records that multiple tests\n depend on. Login is handled separately — setup nodes are for data creation.\n\nSEED\n Injects outputs from a seed workflow as variables. Seed workflows\n generate test data and export named values. Downstream workflows\n access these as $var.{dataKey}.\n\n Ordering: seed nodes should almost always come FIRST — before the\n login node. Seeds run in their own isolated context (usually via\n API sequence) and export variables into the workflow. Placing a\n login before the seed wastes a browser session, and any\n login/navigate node that references $var.{seed_output} must come\n after the seed that produces it.\n\n Typical pattern:\n Seed → Login → Navigate → ... action/assertion nodes\n\n Config:\n seedWorkflowId UUID of the seed workflow (required)\n seedWorkflowName Human-readable name (used as the node title)\n\n Seed workflows are the preferred way to provision test data. See the\n Seed Workflows section for details on building and using them.\n\nAPI SEQUENCE\n Executes multiple API calls in sequence without a browser.\n Use for backend-only operations, webhooks, or API testing.\n\n API sequences are also used inside seed workflows — start with a\n browser-based seed to discover the right API calls, then convert\n to an API sequence for speed and reliability.\n\nSANDBOX ACTION\n Executes a sandbox action — a server-side script defined on the\n environment template — directly on the run environment's sandbox\n instance. No browser involved. The action's structured output is\n registered as workflow variables: each output key becomes\n $var.{key} for downstream steps.\n\n Config:\n actionSlug Slug of the action (required; discover via\n list_sandbox_actions)\n params Input values keyed by the action's inputSchema\n property names; values may use $var.{name} / macros\n outputKeys Output variable keys (defaults to the action's\n outputSchema keys)\n\n Output registration is allowlisted by declared intent: the action's\n outputSchema keys, else the node's outputKeys. If neither declares\n anything, NO result keys register — declare outputs explicitly for\n schema-less actions.\n\n Environment constraint: the action runs on the environment's\n backing sandbox instance. On a non-sandbox environment (static\n staging/prod) the node SKIPS with a warning and registers no\n variables — so workflows containing one are effectively\n sandbox-only.\n\n Seeds: a seed workflow whose body is a single sandbox action node\n is the fastest way to provision data — it runs entirely\n server-side (no browser session), and its outputs satisfy the\n seed's declared outputs like any captured value.\n\nNOTIFICATION\n Sends notifications (Slack, email, etc.) at a point in the workflow.\n\nEND\n Explicitly terminates the workflow. Use when a condition branch\n should stop execution rather than continue.","WHAT A STRONG ACTION NODE LOOKS LIKE\n\n Action nodes are the workhorse of a flow — and they are also the unit the\n cache stores and replays. Sizing them well is what makes a flow both fast\n (cacheable sooner) and durable (resistant to benign UI churn). Four things\n separate a strong action node from a weak one.\n\n 1. IT READS AS ONE COHERENT STEP\n\n A good node has a title you could read aloud as a single instruction to a\n teammate: \"Create a ticket\", \"Fill out the subscription form\", \"Click Start\n to open the run-config modal\". The title names a goal — and, where it helps,\n the purpose of the step — not raw mechanics.\n\n A node is usually broader than a single click, but not always: a lone action\n that genuinely stands on its own in the flow is a fine node (\"Click Start to\n open the modal\"). The extremes to avoid are a purely mechanical title with no\n intent (\"Click the button\") and a node stuffed with several unrelated\n outcomes (\"Create a ticket, assign it, comment, and upload\"). When in doubt,\n err toward clearer, more discrete steps over one overloaded node.\n\n 2. IT HAS ROUGHLY 3–8 PLAYBOOK STEPS\n\n Each playbook step is one meaningful action or tool call — a click, a select,\n a form fill, a submit. A healthy node lands around 3–8 of them:\n\n click → fill_form → click (good — open, fill, submit)\n click → click → fill_form → click (good)\n 27 clicks (bad — that's several outcomes)\n\n \"Meaningful\" is the operative word. Incidental events collapse into the\n nearest step (a focus-click before a fill is part of the fill), and a whole\n form-fill is ONE fill_form step, not one step per field. If a single outcome\n would need more than ~8 steps, that's the signal to split it into sequential\n action nodes (\"Fill customer details\" → \"Fill billing details\" → \"Review &\n submit\") rather than grow one megastep.\n\n This ~3–8 is the STEP count inside one action node — not a cap on how many\n NODES a flow has. A flow is as long as its coherent journey needs (see Best\n Practices on flow scope).\n\n 3. IT HAS A CLEAR ASSERTION BOUNDARY\n\n A strong node ends at a checkpoint you can assert on: a modal disappeared,\n the URL changed, a row appeared, a toast fired. Put that check inline on the\n node (see the Assertions section) so the node's success is observable and the\n run gets a real progress marker at each step.\n\n Inline assertions verify the END STATE after the action. They can be relative\n or temporal (\"the row count dropped by 2\"), but only if the baseline was\n captured upstream — write `current row count = $var.captured_row_count - 2`,\n never \"the count dropped by 2\". The reason: an action may replay from cache\n while its assertion is judged live by the agent, which does NOT have the\n action's execution context. A self-contained assertion (absolute values, or\n values bound to captured variables) survives that split; one that silently\n assumes \"before\" state does not.\n\n 4. IT IS NOT CONDITIONAL\n\n Avoid baking branching into an action node — \"if the location isn't New York,\n set it to New York\", \"if patient X isn't in the table, create patient X\". A\n conditional in a node usually means the data wasn't staged properly, and it\n caches poorly (the cached path and the live path diverge).\n\n Prefer, in order:\n - Seed the precondition so the branch never exists (\"patient X always\n present\" → seed patient X). See Seed Workflows.\n - If the action is idempotent — the same sequence reaches the desired state\n regardless of where it started (re-selecting \"New York\" works whether or\n not it was already set) — just run the sequence unconditionally. No branch\n needed.\n - If the branch is genuine, use a formal CONDITION node (deterministic when\n the check is a $var / identity comparison; agentic otherwise), which keeps\n the branch explicit and cacheable per path.\n\n If a conditional truly must live inside a node, set `alwaysUseAgent: true` on\n it so the branch is reasoned about live every run and never mis-cached.\n\n WHY THIS MATTERS\n\n The node is the atomic cache unit. The smaller and more reliable each node is,\n the sooner the flow caches and the faster it replays. But because the node is\n also the unit of specificity, the more a node pins to incidental detail (exact\n button text, a precise field order), the more a benign change breaks it.\n Nailing the altitude — coherent goal, ~3–8 meaningful steps, a clean assertion\n boundary, no hidden branches — is what buys both speed and reliability at once.\n","A playbook is a sequence of human-readable steps that describe the actions\ntaken during a workflow node's execution. Playbooks bridge the gap between\nhigh-level node descriptions (\"Create a ticket\") and the low-level browser\ninteractions that fulfill them.\n\nWHAT A PLAYBOOK CONTAINS\n\n Each playbook is an ordered list of PlaybookSteps:\n\n order Step sequence number\n action What to do: click, fill, fill_form, select, navigate,\n wait, scroll, hover, upload, verify, handle_native_dialog,\n toggle, press_key, focus, drag, resize, capture,\n extract_table, python_execute, verify_download,\n verify_download_content, screenshot, agent_loop\n target The element or area being acted on\n params Key-value parameters for the action\n note Human-readable description of the step\n\n Example playbook for \"Create a ticket\":\n 1. click \"New Ticket\" button\n 2. fill_form Title: \"QA Test — Billing Bug\", Priority: \"High\"\n 3. click \"Submit\" button\n 4. verify Success toast appears\n\nHOW PLAYBOOKS ENABLE CACHE REPLAY\n\n When a workflow runs for the first time, the AI agent explores the\n application, performs actions, and generates a playbook for each node.\n The raw browser actions (clicks, keystrokes, waits) are cached alongside\n the playbook steps.\n\n On subsequent runs, the system uses playbook-step-driven replay:\n\n 1. Load the playbook steps from the node config\n 2. For each step, look up the cached browser actions\n 3. Replay the cached actions directly (no AI agent needed)\n 4. If a step's cache misses or fails, the AI agent heals that step\n\n This means most runs execute instantly via cache, with the agent only\n invoked for steps that have changed or broken.\n\nCOMPILED ENTRIES\n\n The cache maps playbook steps to cached action indices:\n\n cached Playbook step has matching cached actions — replayed directly\n gap No cache for this step — agent performs it fresh\n\n A fully cached node replays in seconds. A node with gaps falls back to\n agent execution only for the missing steps.\n\nPLAYBOOK HEALTH\n\n Each cached node tracks playbook health:\n\n verified Cache is working correctly across recent runs\n degraded Some steps have failures — cache still used but monitored\n stale Cache is no longer reliable — full agent re-execution needed\n\n Step-level failure counts track which specific steps break most often,\n guiding targeted cache healing.\n\nWHY PLAYBOOKS MATTER FOR AUTHORING\n\n When you build a workflow, the quality of your node descriptions directly\n affects playbook quality. Clear, intent-focused descriptions produce\n better playbooks, which produce more reliable cache replay.\n\n Good: \"Create a ticket with title 'QA Test' and priority 'High'\"\n → Clean playbook: fill_form with clear fields, click submit\n\n Bad: \"Do the thing on the page\"\n → Vague playbook: agent guesses at actions, cache breaks easily\n\n The playbook is the semantic contract between your intent and the\n execution engine. Write nodes that describe what a human would do,\n and the playbook will faithfully capture and replay it.\n","SCOPE: ONE COHERENT USER JOURNEY\n\n A workflow should test one job-to-be-done, not multiple unrelated tasks.\n Many steps are fine if they're all part of the same flow (creating,\n editing, verifying one entity). Stop when the goal is accomplished.\n\n Good — coherent journey:\n Login → Navigate to projects → Create project → Edit name →\n Assert name updated → Cleanup: delete project\n\n Good — multi-role journey:\n Login as Customer → Place an order →\n Login as Sales Rep → Verify the order appears in the queue\n\n Good — read-only flow:\n Login → Navigate to dashboard → Assert widgets visible\n\n Bad — multiple unrelated journeys:\n Dashboard → Create item → Manage users → Generate report\n (These are separate jobs — create separate workflows)\n\n Rule: \"Is this step dependent on or naturally connected to the previous\n steps?\" If no, you're starting a new journey — stop.\n\n A coherent flow can be long or short — there is no target node count. If a\n flow grows very long, ask whether it is really several INDEPENDENT journeys:\n splitting them into separate tests lets them run in parallel and fail\n independently. Keep a single coherent journey together — don't split mid-flow.\n\nWRITE STEPS AS HUMAN INTENT\n\n A workflow should read like instructions you'd explain to a teammate:\n - \"Create a ticket assigned to Support.\"\n - \"Submit the ticket and confirm it appears in the list.\"\n - \"Change the ticket status to Closed.\"\n\n Capture the \"why\" and the \"what\" — not UI mechanics.\n\nCHOOSE THE RIGHT GRANULARITY\n\n Too granular:\n - \"Click New\"\n - \"Type title\"\n - \"Type description\"\n - \"Click Save\"\n\n Too stuffed:\n - \"Create a ticket, assign it, comment, upload, and verify in three views\"\n\n Good:\n - \"Create a ticket with required fields\"\n - \"Assign the ticket to Support\"\n - \"Verify the ticket appears in the queue\"\n\n Sizing rule: a node is a single coherent goal of roughly 3–8 playbook steps\n (one meaningful action each — a whole form-fill is ONE fill_form step). The\n \"too granular\" list above is about splitting one goal into a node per keystroke\n or writing mechanics-only titles — NOT a ban on single-action nodes: a lone\n action that stands on its own (\"Click Start to open the modal\") is fine. This\n ~3–8 is per action node (its playbook STEPS); it is NOT a cap on how many NODES\n a flow has — flow length follows the journey (see SCOPE above). See the Action\n Nodes section.\n\n If an action crosses multiple screens or changes multiple entities, split it.\n If it's a single user goal on a single screen, keep it as one action.\n\nMAKE EACH TEST SELF-SUFFICIENT\n\n Tests should not depend on what another test created or on long-lived\n shared state. If a workflow needs data, provision it:\n\n - Use a seed workflow (preferred) to generate data and export variables\n - Use a setup node to reference a published workflow for shared data\n - As a last resort, add data creation steps at the start of the workflow\n - Always use macros ($EMAIL, $UUID, etc.) so reruns don't collide\n\n This keeps runs deterministic and prevents cascading failures.\n\nVERIFY EVERY SIGNIFICANT ACTION\n\n Before recording a step, verify it worked:\n\n - Form submissions: confirm acceptance (success message, redirect, new item in list)\n - Data creation: navigate to where the record should appear and confirm\n - Data modification: reload and confirm the change persisted\n - Deletions: confirm the item is gone (a toast alone is not enough)\n - Navigation: confirm the expected page loaded\n\n If verification fails, debug the issue — don't record and move on.\n\nKEEP DESCRIPTIONS CLEAR\n\n Node titles should be short and action-oriented:\n Good: \"Create a support ticket\"\n Bad: \"Step 3 - ticket creation process\"\n\n Node descriptions should add detail the title can't carry:\n Good: \"Fill the form with a QA Test subject and High priority, then submit\"\n Bad: \"Do the thing\"\n\nNODE ORDERING\n\n The start-of-workflow order is a strict hierarchy (see \"STARTING NODE\"\n in the Node Types guide):\n\n Seed (if needed) → Login → Navigate → ...\n\n The first node is the ACTOR, not a navigate: an authenticated flow opens\n with a `login` node (or a `seed`, if it needs data first) — never with a\n navigate node. A navigate jumps to the test's entry point AFTER login.\n Only unauthenticated flows (signup, password reset, public pages, or a\n login test) may start with a navigate.\n\n Seed nodes should come first. Seeds run in their own isolated context\n (usually via API sequence) and export variables the rest of the flow\n consumes. Putting a login before the seed wastes a browser segment,\n and any login/navigate that references $var.{seed_output} must come\n after the seed that produced it.\n\n Only skip the seed-first rule if the seed itself genuinely depends on\n a browser session created earlier in the same workflow (rare — in\n that case, prefer a separate browser-based seed workflow and reference\n it here).\n\nLOGIN BEST PRACTICES\n\n - Most workflows start with a login node. Workflows can also start\n with a seed node or API sequence when no browser session is needed yet.\n - Login SUBSUMES logging in. A login node already provisions an\n authenticated session — it IS the act of logging in. Never follow a\n login node with \"navigate to /login\" / \"enter email + password\" /\n \"click Sign in\" steps. Start with the login node and go straight to\n the first real action. (Only exception: a flow whose purpose is to\n test login — start logged out and make the login steps the journey.)\n - Login nodes require a credentialId (UUID), not just a credentialName.\n Without a credentialId the Flow Designer flags the node as invalid.\n Find credential IDs with: canary credential list [--env prod|dev]\n - Use multiple login nodes for multi-role testing — each login starts\n a new segment with a fresh session as that user\n - Don't test login itself (unless that's the explicit goal)\n - A navigate node after login is a great way to jump directly to the\n test's entry point\n\nPREFER INLINE ASSERTIONS\n\n Attach assertions to action nodes using inlineAssertions rather than\n adding standalone assertion nodes. Inline assertions keep the test\n focused and produce clearer failure reports. See the Assertions section\n for details.","Macros generate fresh, unique data every time a workflow runs. Variables let\nyou reference shared values across nodes. Both use the $ prefix.\n\nAVAILABLE MACROS\n\n $EMAIL() Unique test email test-a1b2@example-test.com\n $FIRST_NAME() Random first name Jordan\n $LAST_NAME() Random last name Chen\n $PHONE() Random US phone +1-555-382-7491\n $UUID() Random UUID v4 a1b2c3d4-e5f6-...\n $NOW() ISO 8601 timestamp 2026-02-19T14:30:00.000Z\n $TODAY() Current date 2026-02-19\n $DATE(N) Date offset by N days $DATE(7) = next week\n $TIMESTAMP() Unix timestamp (ms) 1771520400000\n $RANDOM_INT(min,max) Random integer $RANDOM_INT(1,100) = 42\n $RANDOM_STRING(len) Random alphanumeric $RANDOM_STRING(6) = xK9mQ4\n $SEQUENCE(name) Auto-incrementing $SEQUENCE(order) = 1, 2, 3...\n\nSYNTAX\n\n Macros follow the format $MACRO_NAME() or $MACRO_NAME(args).\n They can appear standalone or inline within a larger string:\n\n Standalone: $EMAIL()\n Inline: user-$UUID()@corp.com\n Escaped: \\$EMAIL() produces the literal text $EMAIL()\n\n Each run resolves macros independently — every execution gets fresh values.\n\nVARIABLES\n\n Variables reference values set during a workflow run. When an action has\n input fields or extracts data, those become $var.{name} references.\n\n Sources:\n Input A value entered in an action's field $var.company\n Macro A macro expression in a field $var.email\n Extracted Data extracted from the page $var.order_id\n Credential Values from the login credential $var.username\n\n Use $var.{name} in subsequent step formFields and descriptions.\n Don't re-hardcode values — reference the variable from the earlier step.\n\nHOW VARIABLES GET BOUND\n\n Two distinct paths create $var.{name} bindings. Picking the wrong one is a\n common foot-gun.\n\n 1. Typed input (auto-promote)\n\n Add an entry to inputValues on an action node:\n\n inputValues: { company: \"$RANDOM_STRING(8)\" }\n\n The key \"company\" auto-promotes to $var.company for every downstream\n node. Do NOT also list it in declaredOutputs — the input itself IS the\n binding.\n\n 2. DOM capture (declaredOutputs + playbook capture)\n\n For values that only exist after the page renders (URL fragment, badge\n text, generated ID in the breadcrumb), declare them upfront and capture\n them inside the playbook:\n\n declaredOutputs: [\"order_id\"]\n playbookSteps: [\n { order: N, action: \"capture\", target: \"<selector or rule>\",\n outputKey: \"order_id\" }\n ]\n\n The capture step writes into $var.order_id at runtime. Without\n declaredOutputs the downstream resolver won't recognize the binding.\n\n Rule of thumb: if you can type the value into a field before the run, it's\n an input. If the value only exists after something happens in the browser,\n it's a captured output.\n\nSEED VARIABLES\n\n When seed workflows are configured, their outputs are available as\n $var.{dataKey} references. If a seed provides data you need (a project ID,\n a user record), use the seed variable — don't create that data again.\n\nCOMMON PATTERNS\n\n Unique user registration:\n Email: $EMAIL()\n Name: $FIRST_NAME() $LAST_NAME()\n\n Numbered items:\n $SEQUENCE(\"orders\") → 1, 2, 3... (resets each run, independent per name)\n\n Date-relative testing:\n $DATE(30) for 30 days out, $DATE(-7) for last week, $TODAY() for now\n\n Unique identifiers:\n $UUID() for globally unique, $RANDOM_STRING(8) for shorter strings\n Combine with text: order-$RANDOM_STRING(6)\n\nBEST PRACTICES\n\n - Use $EMAIL() instead of hardcoded addresses for uniqueness\n - Use $SEQUENCE(\"name\") when you need predictable ordering within a run\n - Prefer $DATE(days) over fixed dates so tests stay valid over time\n - Always include a value for every formField — don't leave fields empty","Seed workflows are the preferred way to provision test data. A seed\nworkflow creates data your test needs and exports named variables that\ndownstream workflows can reference.\n\nWHY USE SEEDS\n\n - Reusable: one seed can serve many test workflows\n - Fast: seeds can use API sequences instead of slow browser interactions\n - Clean: paired teardown workflows handle cleanup automatically\n - Isolated: each run gets its own fresh data via macros\n\nHOW SEEDS WORK\n\n 1. Create a workflow with flowType \"seed\"\n 2. Define seedOutputs — the named values the seed exports\n (e.g., project_id, user_email, order_number)\n 3. The seed creates data and captures those values during execution\n 4. Downstream workflows add a Seed node that references this seed\n 5. The seed's outputs are available as $var.{dataKey} in the test\n\n Example:\n Seed \"Create Test Project\" exports: project_id, project_name\n Test workflow references the seed, then uses:\n Navigate to $var.project_id\n Assert page title contains $var.project_name\n\nCREATING SEEDS\n\n A seed workflow shell needs a name, declared outputs, and usually a paired\n teardown. Outputs are the contract downstream workflows depend on; define\n them before building the seed body so every node captures the right values.\n\n Simple data outputs need:\n - dataKey: stable variable name, used as $var.{dataKey}\n - name: human label\n - description: optional context for builders and reviewers\n\n Credential outputs additionally define credentialOutput, including provider\n type, generated field mapping, inbox behavior, and loginUrl.\n\n After creating the shell, add nodes through the Flow Designer, workflow sync,\n or API, then publish once the seed and paired teardown both pass.\n\n CLI users: see `canary workflow guide cli-seeds` for command usage.\n\nBUILDING A SEED: START WITH THE BROWSER\n\n The easiest way to build a seed is to start with browser-based steps:\n\n 1. Create a seed workflow (flowType: seed)\n 2. Add a login node with credentials that have permission to create data\n 3. Add action nodes that create the data through the UI\n - Use macros for all names and identifiers ($EMAIL, $RANDOM_STRING, etc.)\n 4. Add declaredOutputs on action nodes to capture generated values\n (IDs, URLs, confirmation numbers)\n 5. Define seedOutputs on the workflow to export those captured values\n 6. Test and publish the seed\n\n This approach lets you discover exactly what steps and data are needed\n by interacting with the real application.\n\nCONVERTING TO API SEQUENCES\n\n Once a browser-based seed works reliably, convert it to an API sequence\n for speed and reliability. Canary can do this automatically.\n\n Auto-conversion from a workflow run:\n 1. Open the seed workflow in the Flow Designer\n 2. Add an api_sequence node\n 3. In the node config, choose \"Generate from workflow run\"\n 4. Select a completed run of the browser-based seed\n 5. The AI agent analyzes the network requests captured during\n that run and generates an equivalent API sequence automatically\n 6. The agent tests each step against the live API to verify it works\n 7. Review the generated sequence and publish\n\n The AI agent has access to your application's API catalog and can:\n - Search for endpoints by keyword\n - Inspect request/response schemas\n - Test individual API calls to verify they work\n - Compose a multi-step sequence with variable passing between steps\n - Validate the full sequence end-to-end before finalizing\n\n You can also generate an API sequence from a prompt — describe what\n data to create and the agent will find the right endpoints and build\n the sequence.\n\n API sequences support two kinds of dynamic values:\n - Step references: {{stepId.response.path.to.value}} to pass data\n between steps (e.g., use the project ID from step 1 in step 2)\n - Macros: $EMAIL(), $UUID(), $RANDOM_STRING(), etc. for fresh\n unique data each run\n\n Exports map response fields to seed output variables:\n - stepId: which step to extract from\n - responsePath: dot notation path (e.g., \"data.id\")\n - variableName: the $var.{name} downstream workflows will use\n\nEXECUTION MODES\n\n Seeds automatically choose the best execution mode:\n\n browser Runs through the browser like a normal workflow\n api Runs the API sequence directly — no browser needed\n recommended Uses the API sequence if one exists, otherwise browser\n\n Once you've added an API sequence to a seed, it will automatically\n run via API — faster, cheaper, and more reliable than browser execution.\n\n Not all seeds can be converted — some data creation flows require\n browser interaction (file uploads, complex wizards, third-party\n integrations). Keep those as browser-based seeds.\n\nAPI CALL TIMEOUT\n\n When a seed runs in API mode (executionMode \"api\", or \"recommended\"\n resolving to API), each HTTP call uses a 2-minute (120s) timeout by\n default. Seeds/teardowns that hit slow provisioning endpoints can raise\n this per seed node via apiTimeoutSeconds — it applies to both the seed\n and its paired teardown. Ignored for browser-mode seeds.\n\n Set it three equivalent ways:\n - Flow Designer: the \"API call timeout (seconds)\" field on the Seed node\n - .canary.md: a \"- apiTimeoutSeconds: 300\" bullet under the Seed node\n - workflow JSON (create / sync): on the seed node's configJson, e.g.\n { \"nodeType\": \"seed\",\n \"configJson\": { \"seedExecutionMode\": \"api\", \"apiTimeoutSeconds\": 300 } }\n\n Leave it unset to keep the 120s default; an invalid value falls back to\n the default rather than erroring.\n\nTEARDOWN WORKFLOWS\n\n Every seed can have a paired teardown workflow that cleans up the\n data it created. Teardowns run automatically after the seed's\n downstream workflows complete.\n\n - Set flowType to \"teardown\" and link it to the seed\n - The teardown receives the same $var.{dataKey} variables the seed exported\n - Use those variables to find and delete the created data\n\n Teardowns are the cleanest way to handle cleanup — they keep cleanup\n logic separate from test logic and run automatically.\n\nSANDBOX-ACTION-BACKED SEEDS\n\n When the property has a verified sandbox template with actions, a seed\n workflow can provision data by running a SANDBOX ACTION node instead of\n driving the UI or composing API calls. The seed's body is a single\n sandbox_action node (plus End); the action's outputs register as\n $var.{key} and satisfy the seed's declared outputs directly.\n\n - Fastest seed form: runs entirely server-side, no browser session\n - Discover actions with list_sandbox_actions; outputs come from the\n action's outputSchema\n - Constraint: consuming workflows become sandbox-only — on a\n non-sandbox environment the action node skips with a warning and\n produces no variables\n - Teardown is usually unnecessary: sandbox environments are destroyed\n after the suite, taking provisioned data with them\n\nSEED BEST PRACTICES\n\n - Use macros in all created data ($EMAIL, $RANDOM_STRING, etc.)\n so each run gets unique, non-conflicting data\n - Keep seeds focused — one seed per logical dataset\n (e.g., \"Create Project with Members\" not \"Create Everything\")\n - Export only what downstream workflows need — don't over-export\n - Start with browser-based seeds, then use auto-conversion to generate\n an API sequence from a completed run\n - For sandbox-backed properties, prefer a sandbox-action-backed seed\n (see above) — server-side, fast, and reliable\n - Pair seeds with teardown workflows for automatic cleanup\n - Use suite-level setup nodes to share seed data across multiple\n workflows in the same test suite\n","Test data discipline keeps workflows reliable and concurrent-safe.\n\nPREFER SEED WORKFLOWS FOR DATA\n\n Seed workflows are the best way to provision test data. They run before\n your test workflow, create the data it needs, and export variables that\n downstream workflows can reference as $var.{dataKey}.\n\n Benefits of seeds over inline data creation:\n - Reusable across multiple workflows\n - Faster — can use API sequences instead of browser interactions\n - Cleaner separation of setup and test logic\n - Paired teardown workflows handle cleanup automatically\n\n See the Seed Workflows section for how to build and use them.\n\nSANDBOX BASE-SEED DATA ($var.SEED.*)\n\n On a sandbox environment, core baseline data (the application/property, a\n credential, sometimes a starter workflow) is provisioned automatically by a\n base seed and exposed as $var.SEED.{key} — e.g. $var.SEED.property_id.\n\n Prefer these over hardcoded names when you need a core entity that already\n exists on every tenant:\n Good: \"Open the property $var.SEED.property_id\"\n\n Base seeds are core-only. Anything test-specific (an issue, a notification,\n run history) is NOT in the base seed — create it yourself (see below) or via\n a per-test seed/custom action.\n\nTHE DATA OWNERSHIP PRINCIPLE\n\n Own your data:\n - Data you create → clean it up when the workflow finishes\n - Settings you modify → revert them when the workflow finishes\n - Data that was already there → never delete it\n\n It's fine to depend on pre-existing data (existing integrations, org\n settings, reference data). Only create what doesn't already exist or\n what the test specifically needs to own.\n\nCREATING TEST DATA\n\n If the test operates on a record (edit, delete, archive), create that\n record as an early step — unless it already exists and the test is meant\n to operate on it.\n\n Never reference \"any\" resource:\n Bad: \"Pick a project from the list\" (which one? may not exist)\n Good: \"Create a project named 'QA Test — $RANDOM_STRING(6)'\"\n Good: \"Use the seed output $var.project_id\"\n\n Always use macros in names to ensure uniqueness across parallel runs:\n Bad: \"Test Item\" (generic, will collide with other runs)\n Bad: \"QA Test Project\" (still collides if two runs overlap)\n Good: \"QA Test — $RANDOM_STRING(6)\" (unique every run)\n Good: \"QA Test $FIRST_NAME() $LAST_NAME()\" (unique and readable)\n\n After creating data, verify it exists before continuing.\n\nNAMING CONVENTION\n\n All test data should follow this pattern:\n\n Name/Title: \"QA Test $FIRST_NAME() $LAST_NAME()\"\n Email: \"$EMAIL()\"\n Phone: \"$PHONE()\"\n Project/Item: \"QA Test — $RANDOM_STRING(6)\"\n Unique ID: \"QA$TIMESTAMP()\"\n\n The \"QA Test\" prefix makes test data instantly recognizable. The macro\n suffix ensures every run creates unique data that won't collide with\n other runs or leftover data from previous executions.\n\nCLEANING UP (STRONGLY RECOMMENDED)\n\n Workflows that create data should include cleanup steps at the end.\n Workflows that modify settings should revert them.\n\n This isn't strictly required, but it's strongly recommended:\n - Cleanup prevents data accumulation over time\n - It keeps the test environment clean for other workflows\n - The \"QA Test\" naming convention makes orphaned data identifiable\n even when cleanup is skipped or fails\n\n Don't over-engineer cleanup — a simple \"navigate to the item and\n delete it\" is sufficient. For seed workflows, use a paired teardown\n workflow instead.\n\nWHAT NOT TO TOUCH\n\n - Don't create users, roles, or org-level config unless that's the goal\n - Don't modify shared resources (billing, integrations, API keys) as a side effect\n - Don't delete or modify data not owned by this workflow\n\nAVOIDING COLLISIONS WITH MACROS\n\n The best way to avoid data collisions is to use macros for every\n name, email, and identifier. When every run generates unique values,\n there's no risk of conflicting with parallel runs or leftover data:\n\n $EMAIL() Unique email every run\n $RANDOM_STRING(6) Short unique suffix for names\n $UUID() Globally unique identifier\n $TIMESTAMP() Unique-per-millisecond numeric ID\n\n Combined with the \"QA Test\" prefix, this means you never need to\n worry about whether data from a previous run still exists. Each run\n creates its own isolated dataset.\n\nREAD-ONLY FLOWS\n\n Workflows that only read data (verify dashboard loads, check reports)\n don't need creation or cleanup steps. Not every workflow needs to\n create data — only create what the test specifically requires.","Assertions verify that the application behaves as expected. There are two\nways to add assertions: inline on action nodes, or as standalone assertion\nnodes. Inline assertions are strongly preferred.\n\nINLINE ASSERTIONS (PREFERRED)\n\n Attach assertions directly to action nodes using the inlineAssertions\n field. These are verified immediately after the action completes and\n cached for fast replay.\n\n Inline assertions are the best way to verify outcomes because they:\n - Keep the test on track — verification happens right where the action is\n - Produce clean failure reports — each assertion is tied to the step that caused it\n - Are cacheable — fast replay on subsequent runs\n\n Example — after submitting a form:\n Action: \"Submit the support ticket\"\n Inline assertions:\n - \"Success toast appears\" (severity: warn)\n - \"Ticket appears in the ticket list\" (severity: fail)\n - \"Form is no longer visible\" (severity: fatal)\n\n Severity levels — what a failure means for the run:\n fatal The test must end now. The run halts at this step.\n Use when continuing is pointless: login succeeded, the page\n loaded, a precondition every later step depends on.\n Example: \"User is logged in and the dashboard is visible\"\n fail The test may continue. The step is marked failed and the run\n finishes marked failed, but later steps still execute and\n produce signal.\n Use for independent checks: data saved, expected row present,\n error absent.\n Example: \"The new record appears in the table\"\n warn The test should continue. The failure is recorded as a warning\n and the run still passes.\n Use for cosmetic or non-critical checks: toast appeared,\n animation completed.\n Example: \"Success toast appears\"\n\n Choosing a severity: ask \"if this is wrong, is there any value in\n running the rest of the test?\" No → fatal. \"Does this being wrong mean\n the feature is broken?\" Yes → fail. Otherwise → warn. When unsure,\n prefer fatal for preconditions and fail for feature claims. Always set\n a severity explicitly — don't leave it unspecified.\n\n If verifying something requires significant interaction (scrolling,\n navigating, expanding sections), model that as an action node with\n inline assertions — not a detailed standalone assertion node.\n\nSTANDALONE ASSERTION NODES (USE SPARINGLY)\n\n Use dedicated assertion nodes only when the check doesn't belong to\n any action:\n\n - Verifying a final page state after navigation (no action to attach to)\n - Checking that data was NOT modified (absence checks after a sequence)\n - Confirming a state visible only after a wait or page transition\n\n A typical workflow has 0-1 standalone assertion nodes. If you find\n yourself writing a complex assertion node that requires multiple steps\n to verify, consider whether it should be an action node with inline\n assertions instead.\n\nAGENTIC ASSERTIONS\n\n For visual or layout checks that can't be mechanically validated,\n enable agent evaluation. These require the AI agent during replay\n and cannot be cached.\n\n Use for:\n - Screenshots that should \"look correct\"\n - Charts or graphs rendering properly\n - Dynamic content that changes between runs\n - Timestamps or real-time data\n\n Standard assertions are preferred — only use agentic when the\n check is inherently subjective or dynamic.\n\nWRITING GOOD CONDITIONS\n\n Assertions must be specific and observable:\n\n Good:\n - \"The status badge shows 'Active'\"\n - \"The table contains a row with name 'Test Record'\"\n - \"Error message is not visible\"\n - \"The page title is 'Dashboard'\"\n\n Bad:\n - \"The page looks correct\"\n - \"The action was successful\"\n - \"Everything works\"\n\n Start with what's observable: \"The page displays...\", \"The table\n contains...\", \"The field shows...\". Include the specific value\n you expect.\n\nASSERTION BOUNDARIES & SELF-CONTAINED CHECKS\n\n Each action node should end at a checkpoint you can assert on — a modal\n disappeared, the URL changed, a row appeared, a toast fired. These boundaries\n double as the run's progress markers, so prefer a concrete end-state check\n to a vague one.\n\n Make every assertion SELF-CONTAINED. An action may replay from cache while\n its assertion is judged live by the agent, which does not have the action's\n execution context — so an assertion that silently assumes \"before\" state is\n unreliable. Relative or temporal checks are fine, but only when the baseline\n was captured upstream and referenced by variable:\n\n Good: \"current row count = $var.captured_row_count - 2\"\n Bad: \"the row count dropped by 2\" (no captured baseline — the live\n evaluator can't know the old count)\n\n Capture the baseline with declaredOutputs / a capture on the upstream node,\n then reference it as $var.<name> in the assertion.\n\nASSERTION PLACEMENT\n\n Place assertions at critical verification points:\n\n 1. After data creation: verify the record exists (inline on the create action)\n 2. After data modification: verify the change persisted (inline on the edit action)\n 3. After deletion: verify the item is gone (inline on the delete action)\n 4. At the goal completion point: verify the journey succeeded\n\n Don't assert everything — focus on the claims that matter.\n A focused assertion that proves the test's core claim is better\n than a dozen checks on every UI element.","API Sequences are the API-only counterpart to browser workflows. Instead of\ndriving a browser node-by-node, an API sequence is a single node\n(nodeType: \"api_sequence\") inside an api-only workflow that executes an\nordered list of HTTP calls, chaining outputs from one step into the inputs\nof the next.\n\nWHEN TO USE\n\n - Exercise an API without a UI (integration tests, fixture setup).\n - Seed or tear down state via API before/after a browser run.\n - Fast, deterministic smoke test of a critical flow.\n\nCONFIG SHAPE (ApiSequenceConfig)\n\n {\n \"steps\": [ ... ], // ordered; required\n \"description\": \"...\", // optional\n \"stopOnFailure\": true, // default: true\n \"exports\": [ ... ] // optional; see below\n }\n\nSTEP SHAPE (ApiSequenceStep)\n\n The authoritative type is in packages/types/src/api-sequence.ts. Key fields:\n\n {\n \"stepId\": \"createProject\", // optional; defaults to \"step{index}\"\n \"apiRequestId\": \"<catalog id>\", // required — points to api_requests row\n \"credentialId\": \"<uuid>\" | null, // null = unauthenticated\n \"credentialTemplateId\": \"<uuid>\" | null, // auth by persona/template (per-env); wins over credentialId\n \"credentialTemplateName\": \"<persona>\", // author-friendly alias for the above; resolved to the id on save\n \"method\": \"POST\", // optional; falls back to catalog default\n \"urlTemplate\": \"/projects/{{createProject.response.id}}\",\n \"queryParams\": [ { \"key\": \"limit\", \"value\": \"10\" } ],\n \"bodyTemplate\": \"{\\\"name\\\": \\\"$EMAIL()\\\"}\", // STRING (JSON), not object\n \"headersTemplate\": { \"X-Trace\": \"$UUID()\" }, // optional\n \"responseVariableName\": \"createProject\", // optional alias for stepId\n \"expectedStatusCodes\": [200, 201], // step fails if status is outside\n \"description\": \"...\", // optional UI label\n \"validations\": [ ... ] // assertions against the response\n }\n\n CREDENTIAL ROLES — `credentialId` is overloaded; two distinct uses exist:\n\n - Step-level `credentialId` (and a top-level/default credential on the\n sequence) is AUTH: it's what the api-sequence runner uses to\n authenticate each HTTP call. Editing this changes who is calling.\n\n - Step-level `credentialTemplateId` is AUTH-by-persona: instead of a fixed\n credential id, name a credential template/persona and the runner resolves\n it to the credential bound for the run's environment (mirrors a login\n node's from_template). Takes precedence over `credentialId`. Use this when\n the same sequence must run across environments (e.g. Dev + a sandbox that\n mints its own per-instance credential) rather than a single static id.\n\n - `credentialId` appearing INSIDE a step's `bodyTemplate` JSON string is\n DATA: it gets written into the request body and becomes part of the\n provisioned resource (e.g. the credential a created workflow logs in\n with). Editing this changes what the call creates.\n\n The two are independent. Swapping the auth credential won't change what\n the call provisions; swapping the body credential won't change auth.\n Mixing them up silently produces confusing test failures.\n\n `apiRequestId` references a row in the `api_requests` table. Discover IDs\n from the API catalog for the target property. If `urlTemplate`, `method`,\n or `bodyTemplate` are omitted, they are backfilled from the catalog entry's\n defaults at execution time.\n\n `bodyTemplate` is a JSON *string*, not a JSON object — templates and macros\n are interpolated into it as raw text before parsing.\n\nTEMPLATE SYNTAX\n\n Three ways to inject dynamic values. All three can appear in the same\n string (urlTemplate, bodyTemplate, headersTemplate values, or queryParams\n values).\n\n 1. STEP REFERENCES — outputs of prior steps.\n\n {{stepId.response.path.to.value}}\n\n The `.response.` segment is REQUIRED. This is the most common foot-gun:\n\n ✓ {{createProject.response.data.id}}\n ✗ {{createProject.data.id}} — missing .response., will warn at\n runtime and resolve to undefined\n ✗ {{projectId}} — no step reference at all\n\n The executor logs a warning for malformed references but does NOT\n block execution — a malformed template resolves to an empty string and\n the request goes out anyway. Always run once and check the run log.\n\n 2. MACROS — dynamic data generated fresh per run.\n\n $UUID() Random UUID v4\n $NOW() ISO 8601 timestamp (UTC)\n $TODAY() YYYY-MM-DD\n $DATE(N) Date offset by N days\n $TIMESTAMP() Unix ms\n $RANDOM_INT(min,max) Random integer in range\n $RANDOM_STRING(N) Alphanumeric string of length N\n $EMAIL() test-<rand>@example-test.com\n $FIRST_NAME() Random first name\n $LAST_NAME() Random last name\n $PHONE() +1-555-...\n $SEQUENCE(\"name\") Per-run auto-increment counter\n\n Each run resolves macros independently. $SEQUENCE counters persist\n across steps within a single run.\n\n 3. WORKFLOW VARIABLES — values injected by the workflow runtime.\n\n $var.<name>\n\n These come from two places:\n\n a) Upstream credential variables — pre-provisioned before the\n sequence runs. Typical names: `$var.credential.username`,\n `$var.credential.password`, `$var.credential.email`. If the\n workflow has a credential, USE these for the matching fields\n instead of `$EMAIL()` / `$RANDOM_STRING()` — the values must\n match the already-provisioned account.\n\n b) Upstream exports — values exported by earlier nodes in the same\n workflow (e.g. a seed step produced `$var.project_id`).\n\n Naming convention for exported variables: `resource_field` with\n underscores (e.g. `project_id`, `user_email`).\n\nEXPORTS\n\n After all steps finish, the sequence can register values from the final\n state into the workflow variable registry for downstream nodes:\n\n {\n \"exports\": [\n {\n \"variableName\": \"project_id\", // appears as $var.project_id\n \"stepId\": \"createProject\",\n \"responsePath\": \"data.id\",\n \"source\": \"response\", // \"response\" (default) | \"request\"\n \"description\": \"...\" // optional UI label\n }\n ]\n }\n\n Only export fields downstream nodes actually need. Use the\n `resource_field` naming convention.\n\nVALIDATIONS\n\n Per-step assertions against the response. Each validation:\n\n {\n \"left\": \"createProject.response.data.status\", // \"stepId.path\" form\n \"operator\": \"equals\", // see list\n \"right\": \"active\", // omit for is_set/is_not_set\n \"description\": \"...\"\n }\n\n Operators: equals, not_equals, gt, lt, gte, lte, contains, not_contains,\n is_set, is_not_set.\n\nWHAT IS VALIDATED AT COMMIT TIME\n\n Very little. The save endpoint accepts anything matching\n { steps: any[], description?, stopOnFailure?, exports? }. It does NOT\n verify that apiRequestId exists in the catalog, that stepIds are unique,\n that {{stepId.response.path}} references a real step, that exports'\n stepId resolves, or that macro names are known.\n\n The only template sanity check is `validateStepTemplates()` in\n apps/api/src/workflows/execution/api-sequence-executor.ts, which runs\n at EXECUTE time, only flags `{{stepId.X}}` missing `.response.`, and only\n LOGS A WARNING — the sequence still runs. Always smoke-test the sequence\n before committing it to a workflow.\n\n CLI users: see `canary workflow guide cli-api-sequences` for command usage.\n\nEXAMPLE (3-step sequence, correct shape)\n\n {\n \"description\": \"Create an item, fetch it, delete it\",\n \"stopOnFailure\": true,\n \"steps\": [\n {\n \"stepId\": \"create\",\n \"apiRequestId\": \"<POST /items id>\",\n \"credentialId\": null,\n \"method\": \"POST\",\n \"bodyTemplate\": \"{\\\"name\\\": \\\"item-$RANDOM_STRING(6)\\\"}\",\n \"expectedStatusCodes\": [201]\n },\n {\n \"stepId\": \"fetch\",\n \"apiRequestId\": \"<GET /items/:id id>\",\n \"credentialId\": null,\n \"method\": \"GET\",\n \"urlTemplate\": \"/items/{{create.response.id}}\",\n \"expectedStatusCodes\": [200],\n \"validations\": [\n { \"left\": \"fetch.response.name\", \"operator\": \"is_set\" }\n ]\n },\n {\n \"stepId\": \"remove\",\n \"apiRequestId\": \"<DELETE /items/:id id>\",\n \"credentialId\": null,\n \"method\": \"DELETE\",\n \"urlTemplate\": \"/items/{{create.response.id}}\",\n \"expectedStatusCodes\": [200, 204]\n }\n ],\n \"exports\": [\n { \"variableName\": \"item_id\", \"stepId\": \"create\", \"responsePath\": \"id\" }\n ]\n }\n\nTIPS\n\n - Give every step a short, stable `stepId`. Later steps and exports\n reference it; the default `stepN` label is brittle to reordering.\n - Always include `.response.` in step references. The runtime won't\n error out, but values will silently resolve to empty strings.\n - Prefer `$EMAIL()`, `$UUID()`, `$RANDOM_STRING()` for fields that must\n be unique per run — unless the workflow has an upstream credential,\n in which case use `$var.credential.username` / `.password` / `.email`.\n - Set `expectedStatusCodes` on every step so unexpected 4xx/5xx fails\n the step instead of silently flowing into the next template.\n - `bodyTemplate` is a JSON STRING. Escape inner quotes.\n - `workflow api-sequence generate` can AI-draft a config from a prompt\n or a prior browser run; refine with `update`. The generated output is\n just JSON — review it before committing.\n","Use this section when you need to create or inspect seed workflows from the\nCanary CLI. The conceptual model lives in `seed-workflows`; this section is\nonly command orientation. Prefer each command's `--help` output for the\ncurrent flag list.\n\nCREATE A SIMPLE SEED SHELL\n\n For simple data outputs, create the seed shell with repeatable output\n declarations:\n\n canary workflow seed create \\\n --name \"Create Test Project\" \\\n --description \"Provisions a test project with default settings\" \\\n --output \"project_id:Project ID:The created project's UUID\" \\\n --output \"project_name:Project Name\"\n\n This creates a draft seed workflow and, by default, a paired teardown\n workflow linked to the seed.\n\nCREATE A COMPLEX SEED SHELL\n\n For credential outputs or larger configs, put the full seed definition in a\n JSON file and create from that file:\n\n canary workflow seed create --from-file seed.json\n\n Keep generated credential fields in credentialOutput, and keep exported data\n fields in outputs. Downstream workflows consume both through $var.{dataKey}.\n\nAFTER CREATION\n\n Add the seed body through the Flow Designer or workflow sync, test both the\n seed and teardown, then publish. If the seed can be API-backed, generate or\n attach an API sequence after the browser version is proven.\n\nREFERENCE\n\n - Seed command help: `canary workflow seed --help`\n - Workflow sync help: `canary workflow sync --help`\n","Use this section when you need to drive API sequence authoring from the\nCanary CLI. The conceptual model lives in `api-sequences`; this section is\nonly command orientation. Prefer each command's `--help` output for the\ncurrent flag list.\n\nCOMMON FLOW\n\n 1. Inspect the API catalog for candidate endpoints.\n\n canary api-catalog sources list\n canary api-catalog endpoints list --search <keyword>\n canary api-catalog endpoints get <apiRequestId>\n\n 2. Compose a sequence JSON file locally. Reference endpoints by\n apiRequestId and chain steps with {{stepId.response.path}}.\n\n 3. Create an API-only workflow from the sequence file.\n\n canary workflow create --api-only --name \"smoke: create project\" \\\n --sequence-from-file seq.json\n\n The command returns the workflow, sequence, and node IDs.\n\n 4. Execute immediately to surface template warnings and API failures.\n\n canary workflow api-sequence execute <workflowId>\n\n 5. Inspect the run and iterate on the sequence file.\n\n canary workflow runs list <workflowId> --side api\n canary workflow runs get <workflowId> <runId>\n canary workflow api-sequence update <workflowId> --from-file seq.json\n\nREFERENCE\n\n - Command help: `canary workflow api-sequence --help`\n - Workflow creation help: `canary workflow create --help`\n - API catalog help: `canary api-catalog --help`\n","import type { GuideSection } from '../types.js';\n\nimport overview from './overview.txt';\nimport nodeTypes from './node-types.txt';\nimport actionNodes from './action-nodes.txt';\nimport playbook from './playbook.txt';\nimport bestPractices from './best-practices.txt';\nimport macros from './macros.txt';\nimport seedWorkflows from './seed-workflows.txt';\nimport dataLifecycle from './data-lifecycle.txt';\nimport assertions from './assertions.txt';\nimport apiSequences from './api-sequences.txt';\nimport cliSeeds from './cli-seeds.txt';\nimport cliApiSequences from './cli-api-sequences.txt';\n\nexport const WORKFLOW_GUIDE_CONCEPT_SECTIONS: GuideSection[] = [\n { title: 'Workflow Overview', slug: 'overview', content: overview },\n { title: 'Node Types', slug: 'node-types', content: nodeTypes },\n { title: 'Action Nodes', slug: 'action-nodes', content: actionNodes },\n { title: 'Playbooks & Cache', slug: 'playbook', content: playbook },\n { title: 'Best Practices', slug: 'best-practices', content: bestPractices },\n { title: 'Macros & Variables', slug: 'macros', content: macros },\n { title: 'Seed Workflows', slug: 'seed-workflows', content: seedWorkflows },\n { title: 'Test Data Lifecycle', slug: 'data-lifecycle', content: dataLifecycle },\n { title: 'Assertions', slug: 'assertions', content: assertions },\n { title: 'API Sequences', slug: 'api-sequences', content: apiSequences },\n];\n\nexport const WORKFLOW_GUIDE_CLI_SECTIONS: GuideSection[] = [\n { title: 'CLI: Seed Workflows', slug: 'cli-seeds', content: cliSeeds },\n { title: 'CLI: API Sequences', slug: 'cli-api-sequences', content: cliApiSequences },\n];\n\nexport const WORKFLOW_GUIDE_SECTIONS: GuideSection[] = [\n ...WORKFLOW_GUIDE_CONCEPT_SECTIONS,\n ...WORKFLOW_GUIDE_CLI_SECTIONS,\n];\n"],"mappings":";;;AAAA;;;ACAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA;;;ACAA;;;ACAA;;;ACeO,IAAM,kCAAkD;AAAA,EAC7D,EAAE,OAAO,qBAAqB,MAAM,YAAY,SAAS,iBAAS;AAAA,EAClE,EAAE,OAAO,cAAc,MAAM,cAAc,SAAS,mBAAU;AAAA,EAC9D,EAAE,OAAO,gBAAgB,MAAM,gBAAgB,SAAS,qBAAY;AAAA,EACpE,EAAE,OAAO,qBAAqB,MAAM,YAAY,SAAS,iBAAS;AAAA,EAClE,EAAE,OAAO,kBAAkB,MAAM,kBAAkB,SAAS,uBAAc;AAAA,EAC1E,EAAE,OAAO,sBAAsB,MAAM,UAAU,SAAS,eAAO;AAAA,EAC/D,EAAE,OAAO,kBAAkB,MAAM,kBAAkB,SAAS,uBAAc;AAAA,EAC1E,EAAE,OAAO,uBAAuB,MAAM,kBAAkB,SAAS,uBAAc;AAAA,EAC/E,EAAE,OAAO,cAAc,MAAM,cAAc,SAAS,mBAAW;AAAA,EAC/D,EAAE,OAAO,iBAAiB,MAAM,iBAAiB,SAAS,sBAAa;AACzE;AAEO,IAAM,8BAA8C;AAAA,EACzD,EAAE,OAAO,uBAAuB,MAAM,aAAa,SAAS,kBAAS;AAAA,EACrE,EAAE,OAAO,sBAAsB,MAAM,qBAAqB,SAAS,0BAAgB;AACrF;AAEO,IAAM,0BAA0C;AAAA,EACrD,GAAG;AAAA,EACH,GAAG;AACL;","names":[]}
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
} from "./chunk-HI2UUGRK.js";
|
|
5
5
|
import {
|
|
6
6
|
InlineAssertionSchema
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-EHKHZ5UR.js";
|
|
8
8
|
|
|
9
9
|
// ../canary-spec/src/parse.ts
|
|
10
10
|
import yaml from "yaml";
|
|
@@ -2658,4 +2658,4 @@ export {
|
|
|
2658
2658
|
writeWorkflowId,
|
|
2659
2659
|
writeNodeIds
|
|
2660
2660
|
};
|
|
2661
|
-
//# sourceMappingURL=chunk-
|
|
2661
|
+
//# sourceMappingURL=chunk-DECTGV4O.js.map
|
|
@@ -2,7 +2,7 @@ import { createRequire as __cr } from "module"; import { fileURLToPath as __fup
|
|
|
2
2
|
import {
|
|
3
3
|
apiRequest,
|
|
4
4
|
fetchList
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-WTMSRIDK.js";
|
|
6
6
|
import {
|
|
7
7
|
getArgValue,
|
|
8
8
|
hasFlag
|
|
@@ -390,4 +390,4 @@ export {
|
|
|
390
390
|
createRule,
|
|
391
391
|
updateRule
|
|
392
392
|
};
|
|
393
|
-
//# sourceMappingURL=chunk-
|
|
393
|
+
//# sourceMappingURL=chunk-E22PSTUP.js.map
|