@runcofactor/cli 0.46.0 → 0.47.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.
@@ -440,7 +440,10 @@ var action_nodes_default = `WHAT A STRONG ACTION NODE LOOKS LIKE
440
440
  A strong node ends at a checkpoint you can assert on: a modal disappeared,
441
441
  the URL changed, a row appeared, a toast fired. Put that check inline on the
442
442
  node (see the Assertions section) so the node's success is observable and the
443
- run gets a real progress marker at each step.
443
+ run gets a real progress marker at each step. State the behavior promised
444
+ by the requirement, not DOM attributes, SVG details, or an arbitrary
445
+ measurement threshold discovered during exploration. Keep observation
446
+ mechanics in executable code and chosen interaction inputs in the action.
444
447
 
445
448
  Inline assertions verify the END STATE after the action. They can be relative
446
449
  or temporal ("the row count dropped by 2"), but only if the baseline was
@@ -477,11 +480,12 @@ var action_nodes_default = `WHAT A STRONG ACTION NODE LOOKS LIKE
477
480
  The description is what a tester would say out loud \u2014 two to four plain
478
481
  sentences: what to click, what should happen, what state to leave behind.
479
482
  Playbook targets name controls by their visible text ("'Create copy' menu
480
- item", "E2E_Commissions_21 row checkbox"). Exact strings belong in the
481
- assertions, not the description.
483
+ item", "QA Policy row checkbox"). Exact strings belong in the
484
+ assertions only when they are required by the spec, not merely locator
485
+ details. The readable condition must remain about the product behavior.
482
486
 
483
- Good: "Search for E2E_Commissions_9, tick the E2E_Commissions_94 and
484
- E2E_Commissions_95 row checkboxes, and click the 'Create policies'
487
+ Good: "Search for QA Carrier, tick the QA Policy A and
488
+ QA Policy B row checkboxes, and click the 'Create policies'
485
489
  toolbar icon. Review the preview, then click 'Cancel'. Leave both
486
490
  rows selected."
487
491
  Bad: a paragraph that restates hover/retry mechanics, quotes request
@@ -619,7 +623,20 @@ WHY PLAYBOOKS MATTER FOR AUTHORING
619
623
  `;
620
624
 
621
625
  // ../content/src/workflow-guide/best-practices.txt
622
- var best_practices_default = `SCOPE: ONE COHERENT USER JOURNEY
626
+ var best_practices_default = `EXPLORE BEFORE AUTHORING
627
+
628
+ Read the intended journey and relevant product-map capability. Use a
629
+ cofactor session with the intended credential and application environment
630
+ to explore the real UI before choosing targets. Confirm the signed-in user
631
+ and role during this preflight, not as an extra runtime requirement unless
632
+ authentication or identity is what the test covers.
633
+
634
+ Discover named data candidates yourself, then confirm with the user which
635
+ existing fixtures are reliable for repeated tests. Ask about genuinely
636
+ ambiguous behavior or data stability, not labels or URLs you can discover.
637
+ If shared data is unsuitable, agree on test-owned setup.
638
+
639
+ SCOPE: ONE COHERENT USER JOURNEY
623
640
 
624
641
  A workflow should test one job-to-be-done, not multiple unrelated tasks.
625
642
  Many steps are fine if they're all part of the same flow (an action and
@@ -740,9 +757,16 @@ MAKE EACH TEST SELF-SUFFICIENT
740
757
 
741
758
  This keeps runs deterministic and prevents cascading failures.
742
759
 
743
- VERIFY EVERY SIGNIFICANT ACTION
760
+ VERIFY THE SPECIFIED BEHAVIOR
761
+
762
+ Verify each meaningful product claim, without adding implementation-only
763
+ checks. Assertions express the required outcome; replay code supplies DOM
764
+ evidence and measurements. Chosen scroll distances and viewport sizes are
765
+ action/setup inputs, not acceptance thresholds unless the spec says so.
766
+ Do not assert ARIA values, SVG paths, or a signed-in user merely because
767
+ exploration exposed them.
744
768
 
745
- Before recording a step, verify it worked:
769
+ Before recording a step, verify its expected outcome:
746
770
 
747
771
  - Form submissions: confirm acceptance (success message, redirect, new item in list)
748
772
  - Data creation: navigate to where the record should appear and confirm
@@ -1374,7 +1398,8 @@ STRONG TESTS: DEFAULTS AND THEIR WORKAROUNDS
1374
1398
  overhead, not hygiene. (Setup nodes are deprecated; do not use them.)
1375
1399
  Workaround: depending on data that already exists on the environment
1376
1400
  is fine AS LONG AS the test only reads it and never mutates it \u2014
1377
- declare it in ## Preflight ("Property 'Acme Demo' exists with \u22651
1401
+ confirm with the user that the discovered fixture is reliable for
1402
+ repeated runs, then declare it in ## Preflight ("Property 'Acme Demo' exists with \u22651
1378
1403
  issue"). A test that must mutate pre-existing data creates its own
1379
1404
  copy first, or states in Preflight why it can't.
1380
1405
 
@@ -1453,7 +1478,7 @@ CREATING TEST DATA
1453
1478
  Good: "Create a project named 'QA Test \u2014 $RANDOM_STRING(6)'"
1454
1479
  Good: "Use the seed output $var.project_id"
1455
1480
 
1456
- Always use macros in names to ensure uniqueness across parallel runs:
1481
+ Use macros in newly created names to ensure uniqueness across parallel runs:
1457
1482
  Bad: "Test Item" (generic, will collide with other runs)
1458
1483
  Bad: "QA Test Project" (still collides if two runs overlap)
1459
1484
  Good: "QA Test \u2014 $RANDOM_STRING(6)" (unique every run)
@@ -1500,8 +1525,8 @@ WHAT NOT TO TOUCH
1500
1525
 
1501
1526
  AVOIDING COLLISIONS WITH MACROS
1502
1527
 
1503
- The best way to avoid data collisions is to use macros for every
1504
- name, email, and identifier. When every run generates unique values,
1528
+ Use macros for newly created test names, emails, and identifiers. Keep
1529
+ confirmed existing reference names and captured identities intact. When every run generates unique values,
1505
1530
  there's no risk of conflicting with parallel runs or leftover data:
1506
1531
 
1507
1532
  $EMAIL() Unique email every run
@@ -1516,10 +1541,11 @@ AVOIDING COLLISIONS WITH MACROS
1516
1541
  READ-ONLY FLOWS
1517
1542
 
1518
1543
  Workflows that only read data (verify dashboard loads, check reports)
1519
- don't need creation or cleanup steps. This is for pages that have no
1520
- writes: a page with create/edit/delete controls is tested by its
1521
- lifecycle (create \u2192 verify \u2192 edit \u2192 verify \u2192 delete \u2192 verify gone),
1522
- not by a browse. Only create what the test specifically requires.`;
1544
+ don't need record creation or deletion. Restore any view state the journey
1545
+ changes. The presence of unrelated create/edit/delete controls does not
1546
+ expand the test's scope: exercise the specified behavior. A journey that
1547
+ tests writes owns those records and verifies their lifecycle. Only create
1548
+ what the test specifically requires.`;
1523
1549
 
1524
1550
  // ../content/src/workflow-guide/assertions.txt
1525
1551
  var assertions_default = `Assertions verify that the application behaves as expected. There are two
@@ -1599,14 +1625,31 @@ AGENTIC ASSERTIONS
1599
1625
  Use for:
1600
1626
  - Screenshots that should "look correct"
1601
1627
  - Charts or graphs rendering properly
1602
- - Dynamic content that changes between runs
1603
- - Timestamps or real-time data
1628
+ Changing content, timestamps, and measurable layout can still be checked
1629
+ deterministically against current-run inputs and captured baselines.
1630
+ Cached assertions replay verification code, never an earlier verdict.
1604
1631
 
1605
1632
  Standard assertions are preferred \u2014 only use agentic when the
1606
- check is inherently subjective or dynamic.
1633
+ check needs subjective judgment.
1607
1634
 
1608
1635
  WRITING GOOD CONDITIONS
1609
1636
 
1637
+ Assertions describe codified, user-visible behavior, not the mechanics
1638
+ used to observe it. Derive the expected result from the specification,
1639
+ product-map requirement, or confirmed user intent. Browser exploration
1640
+ discovers how to exercise and measure the behavior; it does not invent
1641
+ new acceptance requirements.
1642
+
1643
+ Good: "The named column stays fixed while level columns scroll."
1644
+ Good: "After unpinning, the named column scrolls with the grid."
1645
+ Bad: "aria-pressed=false and the SVG has an outlined path."
1646
+ Bad: "Horizontal scroll increases at least 80 pixels." (unless required)
1647
+
1648
+ A scroll distance or viewport size chosen for the test belongs in setup
1649
+ or the action. Measurements and comparison tolerances belong in replay
1650
+ code. Confirm the intended user during browser preflight; do not add a
1651
+ signed-in-user assertion unless identity/authentication is the journey.
1652
+
1610
1653
  Assertions must be specific and observable:
1611
1654
 
1612
1655
  Good:
@@ -1622,7 +1665,8 @@ WRITING GOOD CONDITIONS
1622
1665
 
1623
1666
  Start with what's observable: "The page displays...", "The table
1624
1667
  contains...", "The field shows...". Include the specific value
1625
- you expect.
1668
+ you expect when the requirement specifies it. Do not assert exact
1669
+ incidental copy, attributes, or dimensions simply because they were observed.
1626
1670
 
1627
1671
  ASSERTION BOUNDARIES & SELF-CONTAINED CHECKS
1628
1672
 
@@ -1698,4 +1742,4 @@ export {
1698
1742
  WORKFLOW_GUIDE_CONCEPT_SECTIONS,
1699
1743
  WORKFLOW_GUIDE_SECTIONS
1700
1744
  };
1701
- //# sourceMappingURL=chunk-IJTIWAY6.js.map
1745
+ //# sourceMappingURL=chunk-BYKQ5N5L.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../content/src/workflow-guide/overview.txt","../../content/src/workflow-guide/node-types.txt","../../content/src/workflow-guide/spec-grammar.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/seed-users.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/cli-chat-evals.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, seed, 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 Deprecated. Formerly a prerequisite workflow referenced by a\n setup node; use a seed workflow for shared data and suite\n layers for ordering instead.\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\n eval A chat eval: one chat_scenario node holding a scripted\n conversation against a chat target, each reply graded by an\n LLM judge. Runs browserless. See the CHAT SCENARIO node type.\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 Seed nodes reference a specific published version of the seed workflow.","Every workflow is built from nodes. Each node has a type, a title, a description,\nand type-specific configuration.\n\nDEACTIVATING A NODE (any type except condition)\n Set `deactivated: true` in the node's config to skip it during runs — the\n step records status `skipped` and execution continues with the next node.\n Useful for converging a flow node-by-node from the front, or parking a\n chronically failing step without deleting it. Toggle via the CLI\n (`workflow node deactivate|activate <workflowId> <nodeId>`) or the node's\n three-dot menu in the Flow Designer. Two rules: a run is blocked while a\n later step still references a `$var` that only a deactivated step produces,\n and condition nodes cannot be deactivated (skipping the branch decision\n would run both branches — deactivate the branch nodes instead).\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 (DEPRECATED)\n Referenced another published workflow as a prerequisite. Do not author\n new setup nodes. For shared data use a SEED node (below); for workflows\n that must run in order use suite layers (cofactor test-suite --layer).\n Existing specs that carry a setup node still parse.\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 provision data that several tests share; data unique to\n one test is created inline. See the Seed Workflows section.\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\nMCP\n Drives an MCP-server application — an application (property) with type\n \"mcp\" — by letting an agent accomplish a plain-language goal through the\n server's tools. No browser involved. Use it to test an MCP server the org\n has registered as an application: exercising its tool surface, creating or\n mutating data through MCP tools, and capturing returned values as\n workflow variables.\n\n When to use: the step's target is the MCP server itself. To verify the\n EFFECT of the MCP work in the product UI, keep the MCP interaction in the\n mcp node and put the browser verification in SUBSEQUENT action/assertion\n nodes — an mcp node never drives the browser.\n\n Authoring fields (create_node / update_node):\n mcpApplicationName Name of the MCP application (required; discover via\n list_mcp_applications). Resolved to the stored\n application id at authoring time.\n mcpInstructions Plain-language goal (falls back to description).\n State ONE verifiable outcome and name the values to\n capture — \"Create a project named Acme via the\n create_project tool and capture its id as\n project_id\", not \"try out the project tools\".\n mcpPersona Persona (credential template name) supplying\n authentication, resolved against the application.\n Preferred over pinning a raw credential.\n mcpOutputs Variable names the node must produce; each becomes\n $var.{name} for downstream steps.\n mcpTools Optional allowlist restricting which server tools\n the agent may call; omit to allow all.\n\n Like action nodes, mcp nodes replay from cache first with agent fallback\n (set alwaysUseAgent in config to skip replay). Requires the mcp.testing\n feature flag for the organization.\n\nCHAT SCENARIO\n Runs a scripted conversation against a chat target — a saved description\n of how to talk to an AI chat — and grades each reply with an LLM judge.\n No browser involved. This is the node a chat eval is built around: a flow\n of flowType \"eval\" holds the conversation in one chat_scenario node, and\n may chain other nodes either side of it — an api_sequence before it to\n establish ground truth a rubric compares against, a call_flow after it to\n have a browser flow verify what the assistant claims it did.\n\n When to use: the thing under test is what an assistant SAYS. It suits\n binary invariants (\"declines a cross-org request\", \"says a capability is\n unavailable rather than inventing data\"), not scoring or benchmarking.\n\n Each turn sends one user message and judges the reply against that turn's\n assertions plus every global invariant declared on the chat target — one\n batched judge call per reply. An assertion is a plain-language rubric and\n the verdict is binary; severity decides whether a failure fails the step\n (\"fail\") or only warns (\"warn\").\n\n Config:\n chatTargetId Chat target to talk to (required). Manage targets under\n Test Data -> Applications -> <app> -> Chat.\n turns Ordered list, at least one. Each turn is\n { id, message, assertions: [{ id, rubric, severity }] }.\n\n Messages AND rubrics interpolate $var.{name}, so an upstream api_sequence\n node can establish ground truth and a rubric can compare against it —\n \"Reports a status matching $var.expected_status\". Rubrics resolve against\n non-credential variables only, so $var.credential.* can never reach the\n judge. A rubric still holding an unresolved $var after expansion is\n withheld from the judge and recorded as a failure naming the missing\n variable, rather than being graded as literal template text.\n\n One asymmetry: a message is expanded on the way OUT, but the transcript\n and the judge see it as authored — a bubble can read \"…order\n $var.order_id\" though the target got the value. The judge grades the\n RESPONSE, so this is usually cosmetic; when a rubric compares the reply\n against the message, put the $var in the rubric too.\n\n Chat scenarios are start-node eligible and run browserless, like mcp and\n api_sequence, so a [chat_scenario, call_flow] flow boots no browser of its\n own while the called child still drives a real one.\n\n The evals feature flag gates chat targets — creating, listing and editing\n them — so in practice an org needs it before a chat scenario has anything\n to point at.\n\nCALL FLOW\n Runs another flow from inside this one, passing arguments in and getting\n that flow's declared outputs back as variables. The child executes as its\n own nested run with its own environment, credentials and browser session,\n so a browserless parent can call a browser child and still watch it.\n\n When to use: a check you want to reuse. Unlike seed nodes —\n which are hoisted out of the chain and run before everything else — a\n call_flow node runs exactly where you put it, which is what makes it\n usable AFTER a chat_scenario or an action node.\n\n The flow picker in the designer lists only approved-and-promoted (or\n eligible) flows, so a draft is not offered — but that is a picker filter,\n not a runtime rule. Authoring a call_flow node directly against a draft's\n id, as `workflow create` can, is not blocked today; the handler only\n checks that the caller may read the flow.\n\n Arguments map to the child's declared value inputs by name, and an\n argument the child no longer declares fails the step rather than being\n silently dropped. Only outputs the child DECLARES cross the boundary.\n\n Config:\n flowId Flow to run (required). A flow cannot call itself.\n flowName Cached child name, used as the node title.\n snapshotVersion Pinned published version, or null for the live definition.\n inputValues Map of the child's declared input names to values.\n $var.{name} is interpolated here; macros pass through\n unresolved so the child's own macro context expands\n them and per-run uniqueness is preserved.\n environmentId Explicit environment override, or null to let the\n parent's environment apply when the child is eligible\n for it, else the child's own resolution.\n executionMode recommended | api | browser\n onFailure block (default) — a failed child fails this step;\n warn — a child that ran to completion and failed only\n warns. A child this step could not START — bad flowId,\n depth cap hit, an argument the child does not declare —\n fails either way: misconfiguration is never softened.\n\n Declared outputs arrive as both a bare $var.{key} and a namespaced\n $var.{Child_Flow_Name}.{key}, where the child's name is slugified —\n \"Verify checkout totals\" becomes $var.Verify_checkout_totals.{key}. Use\n the namespaced form when two called flows export the same key.\n\n Nesting is capped at 5 levels. Each level is a full run with its own\n browser session, so cost compounds.\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.","This is the exact syntax of a `.canary.md` spec file — what the parser actually matches, not\na loose description. A spec that reads as good prose but drifts from this grammar does NOT\nerror at parse time in most cases: the parser drops the line/bullet it can't match and reports\nonly a warn-severity issue. A spec can validate as \"ok\" while silently missing content the\nauthor wrote. When in doubt, match the grammar below character-for-character rather than\nwriting what merely reads naturally.\n\nFRONTMATTER\n\n A `---`-fenced YAML block, first thing in the file:\n\n ---\n name: Org onboarding — invite a member\n workflowId: <uuid> # present only once compiled; NEVER remove or edit it by hand\n status: draft # draft | approved\n flowType: standard # standard | seed | teardown | eval (setup is deprecated)\n actor: admin # persona name (or \"persona <role>\"), \"credential <name>\"\n # for one fixed account, or \"seed-output <dataKey>\" for a\n # seed's credential output — see ACTOR FORMS below\n authenticated: true\n tags: [smoke, p0]\n requirementRef: LIN-4421\n ---\n\n `workflowId` binds the file to a live workflow: present → `spec_compile` diffs/updates that\n workflow; absent → `spec_compile` creates a new one. If it's present, treat it as immutable —\n removing it turns your next compile into a duplicate-creating CREATE instead of an UPDATE.\n\nSECTION HEADINGS (H2, in this order)\n\n ## Spirit — one blockquote paragraph: the user-visible promise this flow proves.\n ## Preflight — plain prose or bullets: non-seedable preconditions the environment must\n already satisfy, including pre-existing data the test reads but does not\n own, and external systems (flags, payments, third parties) it relies on.\n ## Cleanup — plain prose or bullets: how the run's side effects get reset.\n ## Inputs — bullets: `- <name>: <defaultValue>` or `- credential <name>: <value>`.\n ## Declared Outputs — seed flows only: `- <dataKey>` bullets with optional indented\n `- label:` / `- property:` / `- description:` sub-bullets. A\n credentialOutput block is NOT authorable here — it is preserved from\n the live seed (rendered as an HTML comment on pull) and set via the\n Flow Designer or `workflow seed create --from-file` / `workflow patch\n --seed-outputs-file` (see `workflow guide seed-users`).\n ## Scenarios — H3 sub-blocks per scenario: `### <Name> *(default)*` or `*(skip: <reason>)*`,\n with `- values: {...}` / `- credentials: {...}` YAML underneath.\n ## Journey — the linear sequence of nodes described below. Required — a spec with no\n Journey nodes fails to parse.\n\nJOURNEY LEAD-INS\n\n Every Journey line is either a bold lead-in (`**...**`) or a blockquote/bullet attached to\n the preceding lead-in. Blank lines between a lead-in, its description, and its bullets are\n fine — the parser skips them.\n\n **Navigate to** /path/here\n **Action — Fill out the signup form**\n **Assert** the confirmation toast is visible\n **Wait** 30s (also accepts 5m, 2h)\n **End** (add \"fail\" anywhere in the line for a failing End)\n **Seed** seed-workflow-name\n **Setup** setup-workflow-name (deprecated — still parses; author **Seed** instead)\n **Switch role to** admin (or **Switch role to admin** — both forms work)\n **Sandbox action — provision-org**\n **Api sequence — Create via API**\n **If** $var.status is \"blocked\"\n **Then:**\n <nested nodes, indented 2 spaces>\n **Else:**\n <nested nodes, indented 2 spaces>\n\n CRITICAL: for `Action`, `Sandbox action`, and `Api sequence`, the title goes AFTER an em-dash\n (`—`) or hyphen INSIDE the bold span: `**Action — Title**`. Writing `**Action** Title` (title\n outside the bold) used to silently lose the title; it is now tolerated as a fallback, but the\n em-dash-inside-bold form is canonical — use it. Do not invent a form for a lead-in not listed\n above (e.g. there is no `**Click**` or `**Type**` lead-in) — express it as an Action instead.\n\nACTOR FORMS (frontmatter `actor:` and `**Switch role to** <ref>` take the same forms)\n\n actor: Admin persona named \"Admin\" (default form)\n actor: persona Admin same, explicit\n actor: credential admin-bob one specific stored credential by name\n actor: seed-output new_user the credential output `new_user` of a **Seed** node\n earlier in the Journey (credentialSource: from_seed)\n\n `template <name>` is a legacy alias for `persona <name>`. `seed-output` binds to the\n nearest **Seed** node that precedes the login, so the Seed must come first in the\n Journey (for `actor:`, as the leading node); compile fails if no Seed precedes it.\n\nDESCRIPTION (optional, any lead-in)\n\n A blockquote line immediately after the lead-in (or after a blank line) becomes the node's\n description — free prose is fine and expected here:\n\n **Action — Create the org**\n > Fill the org form with orgName and plan, then submit.\n - field name: $orgName\n\nACTION BULLET GRAMMAR (exact — only these forms parse; everything else is DROPPED)\n\n - field <name> [(type)]: <value> e.g. - field plan (select): $plan\n - capture: <name> e.g. - capture: createdOrgId\n - assert (warn|fail|fatal[, agentic]): <condition>\n e.g. - assert (fail): URL ends with `/dashboard`\n e.g. - assert (warn, agentic): chart looks right\n - alwaysUseAgent: true\n - captureAudio: true\n - captureLabel: <label>\n - captureExtraMs: <number>\n\n A standalone `**Assert**` node takes its own kv option bullet the same way:\n\n - severity: warn|fail|fatal (assert nodes only; NOT `- severity: warn — text`,\n which is an unparseable kv bullet, not an assert)\n\n `Seed`/`Setup` bullets: `- version: latest`, `- mode: ...`, `- onFailure: block|warn`,\n `- input.<name>: <value>`. `Sandbox action` bullets: `- label: ...`, `- outputs: a, b`,\n `- param.<name>: <value>`.\n\nCOMPLETE MINIMAL EXAMPLE\n\n ---\n name: Login smoke\n actor: admin\n authenticated: true\n requirementRef: LIN-101\n ---\n\n ## Spirit\n > A returning admin can sign in and reach the dashboard.\n\n ## Inputs\n - adminEmail: $EMAIL\n\n ## Journey\n\n **Navigate to** `/login`.\n\n **Action — Sign in**\n > Enter email and password, submit.\n - field email: $adminEmail\n - field password: $CREDENTIAL\n - assert (fail): URL ends with `/dashboard`\n\n **End**.\n\nCOMMON MISTAKES (each of these compiles into an empty or half-empty node — no parse error)\n\n - A free-text bullet (\"- Should reach the dashboard eventually\") is not `field`/`capture`/\n `assert (...)`/a known kv key — it is DROPPED. Put prose in the `>` description instead of\n inventing a bullet shape.\n - `- severity: warn — should show a toast` is a kv bullet with key `severity`; it is not a\n recognized action option, so it is DROPPED. Use `- assert (warn): should show a toast`.\n - `**Action** Do the thing` (title outside the bold) is tolerated but non-canonical — prefer\n `**Action — Do the thing**`.\n - Any Journey line that doesn't start with a recognized bold lead-in is DROPPED with only a\n warn-severity \"Unexpected line in Journey\" issue — it will not error your compile.\n - After authoring or editing a spec, always run `spec_validate` and inspect its\n `droppedContent` field. Non-empty `droppedContent` means real content is about to be lost —\n treat it as a blocking error, not a warning, even though its underlying severity is `warn`.\n","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). Five 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 atomic intent executed in one tool call, bounded\n by stable page states. Fuse actions coupled through ephemeral UI state — an\n opener and a target that exists only while its menu is open — into that\n call. Split unrelated intents and every stable intermediate. A healthy node\n lands around 3–8 steps:\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 Interaction recipes are property-scoped, reusable interaction mechanics.\n A recipe invocation appears as one `call` step for its whole transaction,\n not as separate steps for the browser actions inside it.\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. State the behavior promised\n by the requirement, not DOM attributes, SVG details, or an arbitrary\n measurement threshold discovered during exploration. Keep observation\n mechanics in executable code and chosen interaction inputs in the action.\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 5. ITS PROSE IS SHORT\n\n The description is what a tester would say out loud — two to four plain\n sentences: what to click, what should happen, what state to leave behind.\n Playbook targets name controls by their visible text (\"'Create copy' menu\n item\", \"QA Policy row checkbox\"). Exact strings belong in the\n assertions only when they are required by the spec, not merely locator\n details. The readable condition must remain about the product behavior.\n\n Good: \"Search for QA Carrier, tick the QA Policy A and\n QA Policy B row checkboxes, and click the 'Create policies'\n toolbar icon. Review the preview, then click 'Cancel'. Leave both\n rows selected.\"\n Bad: a paragraph that restates hover/retry mechanics, quotes request\n URLs and response bodies, or narrates a bug with its ticket id\n and repro steps.\n\n Do not restate the same mechanics in every node (\"hover the row so its\n controls appear — they render only while hovered — then click …\"); say it\n once in a short phrase or leave it to the agent. Known failures go in a\n warn-severity assertion that cites the ticket id in one clause, with the\n ticket attached to the flow (status event / comment), never as an essay\n inside the node. If a description needs more than four sentences, the node\n is doing more than one thing — split it (point 1).\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, short prose — is what buys both speed and\n 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, call\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 Each step is one atomic intent executed in one tool call, bounded by stable\n page states. Fuse actions coupled through ephemeral UI state — opening a\n transient menu and choosing its item, for example — into that call. Split\n unrelated intents and every stable intermediate into separate steps.\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\nVERIFY STEPS & ASSERTIONS\n\n A `verify` (or `verify_download`/`verify_download_content`) step names the\n inline assertion it proves via `assertionId` — the assertion's UUID from\n the node's inlineAssertions, or `'completion'` for the step that closes\n out the node's job-to-be-done. The step sits at the position in the flow\n where the check actually happened, not appended at the end.\n\n A verify step whose assertionId matches a recorded assertion compiles into\n the cache at that position, alongside the other steps. An assertion the\n agent didn't place in the flow gets a synthesized trailing verify step\n instead.\n\n Agentic assertions (agent-evaluated, not mechanically checkable) never get\n a verify step — they can't compile and are judged live during replay.\n\n See assertions.txt for how inline assertions are authored, severities, and\n when to use agentic evaluation.\n\nINTERACTION RECIPES\n\n Interaction recipes are property-scoped, reusable interaction mechanics.\n Calling one executes its transaction atomically and appears in the playbook\n as one `call` step, with a human-readable target and the recipe arguments.\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","EXPLORE BEFORE AUTHORING\n\n Read the intended journey and relevant product-map capability. Use a\n cofactor session with the intended credential and application environment\n to explore the real UI before choosing targets. Confirm the signed-in user\n and role during this preflight, not as an extra runtime requirement unless\n authentication or identity is what the test covers.\n\n Discover named data candidates yourself, then confirm with the user which\n existing fixtures are reliable for repeated tests. Ask about genuinely\n ambiguous behavior or data stability, not labels or URLs you can discover.\n If shared data is unsuitable, agree on test-owned setup.\n\nSCOPE: 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 (an action and\n everything it is meant to cause). Stop when the goal is accomplished.\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 — single-entity journey:\n Login → Navigate to projects → Create project → Edit name →\n Assert name updated → Cleanup: delete project\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\nTEST THE SEAMS: FOLLOW THE ACTION TO ITS EFFECT\n\n CRUD rarely breaks on its own. The seams between features break: the place\n where one action is supposed to cause something somewhere else. Creating a\n ticket is not the point. The point is what the ticket does: notify the\n assignee, land in Support's queue, bump a count on the dashboard, sync to\n an integration.\n\n Before writing a flow, ask: \"When this action succeeds, what is supposed to\n happen next, and where would a user see it?\" When there's an answer, the\n stronger journey follows the action to that effect:\n\n Weaker — stops at the write:\n Login → Create ticket assigned to Support → Assert ticket in my list\n\n Stronger — crosses the seam:\n Login as Customer → Create ticket assigned to Support →\n Login as Support → Confirm the assignment email in Support's inbox →\n Verify the ticket is in the Support queue\n\n Common seams to look for:\n - Another screen or view (a list, dashboard, report, activity feed)\n - Another role or user (a queue, an approval, a shared record)\n - A message (email, in-app notification; for email, see EXTERNAL\n DEPENDENCIES in cofactor workflow guide data-lifecycle)\n - A downstream state change (status, count, permission)\n\n Create → edit → delete is mostly data management: the test makes what it\n needs and cleans it up (see MAKE EACH TEST SELF-SUFFICIENT). It is a fine\n flow for a page whose writes have no effect elsewhere. But when the action\n is meant to trigger something, the effect is what's worth proving.\n\n This is a preference, not a requirement. Some seams can't be observed from\n the test (a third-party system with no readable surface, a side effect with\n no UI). Assert what you can see and move on. When authoring from a\n recording, apply this only within what was captured — don't add steps\n the recording never performed.\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 A strong test is idempotent, controls its own data, and is isolated. The\n full rules and their sanctioned workarounds are the \"STRONG TESTS\" block of\n the Test Data Lifecycle guide (cofactor workflow guide data-lifecycle).\n In short:\n\n - Create the data the test operates on, inline, with macros ($EMAIL,\n $UUID, etc.) so reruns don't collide\n - Use a seed workflow only for data that several tests share\n - Pre-existing data the test only READS is fine — declare it in\n ## Preflight and never mutate it\n - Tests that must run in order go in suite layers, never rely on\n implicit ordering\n\n This keeps runs deterministic and prevents cascading failures.\n\nVERIFY THE SPECIFIED BEHAVIOR\n\n Verify each meaningful product claim, without adding implementation-only\n checks. Assertions express the required outcome; replay code supplies DOM\n evidence and measurements. Chosen scroll distances and viewport sizes are\n action/setup inputs, not acceptance thresholds unless the spec says so.\n Do not assert ARIA values, SVG paths, or a signed-in user merely because\n exploration exposed them.\n\n Before recording a step, verify its expected outcome:\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 - Side effects: where an action is meant to cause something elsewhere,\n check it there when you can (see TEST THE SEAMS)\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 $NOW(+5m) Shifted ISO timestamp $NOW(-2h), $NOW(30s), $NOW(1d)\n $NOW(+5m, \"fmt\") Shifted time, UTC fmt $NOW(+5m, \"m H * * *\") = 35 14 * * *\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\n$NOW FORMATS\n\n The optional second argument renders the time in UTC. Tokens: YYYY MM DD\n HH mm ss (zero-padded) and H m (unpadded hour and minute); everything else\n is literal, except that a bare H or m is always a token (there is no escape). Use $NOW(, \"fmt\") to format the current time with no shift.\n A format cannot contain commas or parentheses. Quote it with single or\n double quotes (or not at all); inside a JSON string such as an API\n sequence bodyTemplate use single quotes.\n\n $NOW(, \"YYYY-MM-DD HH:mm\") 2026-02-19 14:30\n $NOW(+5m, \"m H * * *\") daily cron that first fires in ~5 minutes\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 provision test data that several tests share. A seed\nworkflow creates data and exports named variables that downstream\nworkflows can reference. Data unique to one test is created inline in\nthat test, not seeded (see the Test Data Lifecycle section).\n\nWHY USE SEEDS\n\n - Reusable — the qualifying condition: one seed serves many test workflows\n - Fast: seeds can use API sequences instead of slow browser interactions\n - Clean: paired teardown workflows handle cleanup automatically, on\n pass and fail\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 — the field\n mapping (\"$generate.email\" for a minted address), inbox behavior, and\n loginUrl. The full recipe for a seeded user, from declaration to the\n consumer's from_seed login and teardown, is the Seeding a Test User\n section (`canary workflow guide seed-users`).\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 — whether they passed or failed.\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 - To share seed data across several workflows in one test suite,\n reference the same seed from each; order dependent workflows with\n suite layers (setup nodes are deprecated)\n","A seeded user is a brand-new account minted for one run, created in the\napplication by a seed workflow, and handed to the tests that consume that\nseed as a login credential plus $var values. This section is the recipe for\nconfiguring one. The general seed model (outputs, execution modes,\nteardowns) is in the Seed Workflows section; this only covers the user case.\n\nWHEN TO SEED A USER (AND WHEN NOT TO)\n\n Use a persona (actor: Admin) when the test only needs SOME logged-in\n account of a role. Personas resolve per environment, need no seed, and are\n the default for every authenticated test.\n\n Seed a user when the test needs a NEW account it owns:\n - signup, invite acceptance, onboarding, first-login flows\n - anything that mutates or deletes the account itself (profile, MFA\n enrolment, password change, leave org, delete account)\n - a fresh inbox: verification codes, invite links, magic links\n - a user with no history, so counts/empty states are deterministic\n\n The seed-only-when-reused rule still applies: one seed serves every test\n that needs \"a fresh user\", so this is exactly the case a seed is for. Do\n not create a per-test copy — reference the same seed from each test.\n\n On sandbox environments the account can come from a sandbox action bound\n to a persona instead (see SANDBOX ALTERNATIVE at the end). This section\n covers the environment-agnostic seed-workflow path first.\n\nHOW IT WORKS (PLATFORM SIDE)\n\n A seed output with a credentialOutput block is a CREDENTIAL output. At the\n start of every run that references the seed, before the seed body runs,\n the platform:\n\n 1. Mints an email address from emailPrefix + the run's execution id:\n <emailPrefix>+<8 hex chars>@<test-user inbox domain>\n e.g. testuser+3fa92c1b@testuser.runcofactor.com\n 2. Resolves fieldMapping — \"$generate.email\" becomes that address,\n \"$generate.phone\" reserves an SMS number, any other value is a\n literal — and creates an ephemeral credential\n (\"[Ephemeral] <dataKey> (<first 8 chars of exec id>)\") on the\n credentialOutput's property, with the mapped fields as its login data.\n 3. Creates an inbox recipient for the address when\n createInboxRecipient is true, so the seed and its consumers can read\n mail sent to it.\n 4. Injects the mapped fields into the seed body as\n $var.<dataKey>.<field> e.g. $var.new_user.username\n $var.credential.<field> (alias; only unambiguous with one\n credential output)\n 5. Binds the credential to any downstream login node that references\n the seed output (credentialSource: from_seed), and deletes the\n credential after the suite finishes — or on seed failure — with a\n 24-hour age sweep as backstop.\n\n Minting the credential does NOT create the account in your application.\n That is the seed body's job (next section). A credential output with an\n empty seed body is a login that will fail.\n\nSTEP 1 — DECLARE THE CREDENTIAL OUTPUT\n\n Create the seed shell with the outputs declared up front; the\n credentialOutput block is only settable through JSON (CLI --from-file /\n --outputs-json, workflow patch --seed-outputs-file, or the Flow Designer\n output editor — .canary.md's ## Declared Outputs cannot author it, only\n preserve it on round-trip).\n\n {\n \"name\": \"Create invited user\",\n \"description\": \"Fresh org + admin invite for a seed-minted email\",\n \"outputs\": [\n {\n \"dataKey\": \"new_user\",\n \"name\": \"Invited User\",\n \"credentialOutput\": {\n \"propertyId\": \"<uuid of the application under test>\",\n \"loginUrl\": \"https://app.example.com/login\",\n \"emailPrefix\": \"qa-invite\",\n \"fieldMapping\": {\n \"username\": \"$generate.email\",\n \"password\": \"QaTest-Passw0rd!\"\n },\n \"createInboxRecipient\": true,\n \"loginInstructions\": \"Email/password form at /login.\"\n }\n },\n { \"dataKey\": \"invite_token\", \"name\": \"Invite token\" },\n { \"dataKey\": \"organization_id\", \"name\": \"Organization ID\" }\n ]\n }\n\n cofactor workflow seed create --from-file seed.json\n\n credentialOutput fields:\n propertyId REQUIRED. The application the credential belongs\n to; without it the output is skipped with a\n warning and downstream logins fail.\n fieldMapping REQUIRED. Credential field → value. Use\n \"$generate.email\" for the minted address (usually\n username, and email too if the login form wants\n both). Every other value is a literal stored\n verbatim — macros are NOT expanded here — so the\n password is exactly what you write; pick one your\n app's password policy accepts. Uniqueness comes\n from the email, not the password.\n emailPrefix Local-part prefix; defaults to \"testuser\". Use\n something that names the seed so inbox traffic is\n attributable.\n createInboxRecipient true whenever the flow must read a message sent to\n the user (verification code, invite link, magic\n link). Without it the address exists but nothing\n captures its mail.\n provisionPhoneNumber true (or a \"$generate.phone\" mapping) to reserve an\n SMS-capable number for OTP flows.\n loginUrl Where the login agent goes to sign in as this user.\n loginInstructions Extra guidance for the login agent (form location,\n SSO button to avoid, post-login landing).\n provisioningInstructions Only if the account needs a manual step before\n first login; marks the credential as not yet\n provisioned.\n\n Keep plain data outputs alongside the credential output for every id the\n consumers or the teardown will need (org id, user id, invite token). Declare\n ONLY what the seed body actually exports — a declared-but-unexported output\n shows up as \"missing\" on every run.\n\nSTEP 2 — MAKE THE SEED BODY CREATE THE ACCOUNT\n\n The body turns the minted email into a real account. Prefer an API\n sequence; fall back to browser nodes only when no API exists.\n\n Typical shapes:\n - Invite: admin/superadmin creates (or picks) an org → POST invite for\n $var.new_user.username → export the invite token. The consumer\n test then completes signup through the invite link.\n - Direct: POST /users or /signup with $var.new_user.username and\n $var.new_user.password → export the user id. The account is\n ready to log in.\n - Verify: if the app emails a verification code on signup, the seed can\n read it too (the inbox exists already) — but usually leave the\n verification step to the consumer test when that step IS what\n the test proves.\n\n Reference: the cofactor org's \"Create org & copy invite\" seed — an\n api_sequence of createOrg → switchOrg → createInvite (body\n {\"email\": \"$var.credential.username\", \"role\": \"admin\"}) exporting\n organization_id and invite_key, consumed by \"Create & Delete Org Property\".\n\n Rules for the api_sequence steps:\n - Authenticate as a persona (credentialTemplateId), never a hardcoded\n credentialId — the persona resolves per environment and is minted\n just-in-time on a fresh sandbox.\n - Target $var.ENV.api_url/..., never a baked host, so the same seed runs\n on dev, prod, and every sandbox.\n - Use $var.<dataKey>.username for the email. $var.credential.username\n also works but stops being unambiguous the moment a second credential\n output is added.\n - Export every id the teardown needs.\n\nSTEP 3 — CONSUME THE USER IN A TEST\n\n In the consuming workflow:\n\n 1. Add a Seed node referencing the seed (the seed's outputs become\n $var.<key> and $var.<Seed_Name>.<key>).\n 2. Point the login node at the seed's credential output:\n credentialSource: \"from_seed\"\n seedCredentialRef: { \"seedWorkflowId\": \"<seed uuid>\",\n \"dataKey\": \"new_user\" }\n performLogin: false when the test performs signup/first login\n itself (invite acceptance, onboarding)\n performLogin: true when the account is ready and the test starts\n logged in\n Both fields are required at run time, but you rarely write\n seedWorkflowId yourself: the Flow Designer's \"Acting as → from seed\"\n picker sets both, and the compile path (create-with-nodes, which\n .canary.md specs and raw JSON both go through) fills it from the\n nearest preceding Seed node when only dataKey is given. In a spec, put\n the **Seed** node first and write `actor: seed-output new_user` (or\n `**Switch role to** seed-output new_user` mid-journey).\n 3. Use the credential's fields in steps as $var.new_user.username and\n $var.new_user.password (signup forms, \"confirm password\", etc.), and\n the plain outputs as $var.invite_token etc.\n 4. Email steps: describe what to read — \"open the verification email\n sent to $var.new_user.username and follow the accept-invite link\".\n The agent has an inbox tool scoped to the credential's recipient. Do\n NOT hardcode a code value in the node's inputs; it is different every\n run.\n\n Several tests can reference the same seed; each run mints its own user, so\n they run in parallel without colliding. Order tests that must reuse ONE\n user across flows as suite layers instead.\n\nSTEP 4 — TEARDOWN\n\n Pair the seed with a teardown (created by default by `workflow seed\n create`). The teardown receives the same $var values and should delete the\n account through the API — the org if the seed created one, otherwise the\n user — using the exported ids. Same rules: persona auth, $var.ENV.api_url.\n The ephemeral credential and inbox recipient are removed by the platform;\n only your application's rows need the teardown.\n\n A seed that runs daily without a working teardown leaves one orphan user\n (or org) per run. Check once after publishing that the object is gone.\n\nCHECKLIST\n\n [ ] credentialOutput.propertyId set; fieldMapping has \"$generate.email\"\n [ ] createInboxRecipient: true if any step reads mail\n [ ] seed body creates the account and exports every id the teardown needs\n [ ] api_sequence steps use credentialTemplateId + $var.ENV.api_url\n [ ] declared outputs == exported outputs (no \"missing\" on the run)\n [ ] consumer login: from_seed with dataKey, and a Seed node before it\n (seedWorkflowId auto-fills from that Seed node at compile)\n [ ] no hardcoded verification codes in consumer inputs\n [ ] teardown deletes the app-side account/org; verified once by hand\n [ ] seed + teardown published (approved) before consumers are promoted\n\nSANDBOX ALTERNATIVE — ACTION-MINTED PERSONA\n\n On a sandbox template, a sandbox action with a credentialOutputConfig and\n a credentialTemplateId mints the account server-side and binds it to a\n PERSONA (e.g. provision-tenant → \"Admin\"). Tests then just say\n actor: Admin; the persona is provisioned just-in-time on each fresh\n instance and no seed node is needed. A second human member for \"act on\n someone else\" tests comes from a follow-up action (e.g. add-org-member,\n keyed to $var.USER.org_id). Constraint: consuming tests become\n sandbox-only, and no teardown is needed because the instance is destroyed.\n Prefer this on sandbox-backed properties; use the seed-workflow path\n everywhere else.\n","Test data discipline keeps workflows reliable and concurrent-safe.\n\nSTRONG TESTS: DEFAULTS AND THEIR WORKAROUNDS\n\n These are strong defaults, not absolutes. A test may deviate when the\n application makes a rule impossible — but it defaults to the rule, uses the\n workaround named here, and says so in ## Preflight / ## Cleanup so the\n deviation is visible, never silent.\n\n 1. IDEMPOTENT — running the test again, including after a FAILED run,\n passes without a manual reset. Unique-name macros (\"QA Test —\n $RANDOM_STRING(6)\", $EMAIL()) make this true even when cleanup never\n happened.\n Workaround: if the app forbids duplicates (a singleton setting, one\n config record), revert the change in ## Cleanup and say so.\n\n 2. CONTROLS ITS OWN DATA — the test creates every record it operates on.\n Inline creation is the normal case: if the data is unique to this test,\n create it in the test. Use a seed workflow only when the data is (or\n should be) reused across several tests — a seed with one consumer is\n overhead, not hygiene. (Setup nodes are deprecated; do not use them.)\n Workaround: depending on data that already exists on the environment\n is fine AS LONG AS the test only reads it and never mutates it —\n confirm with the user that the discovered fixture is reliable for\n repeated runs, then declare it in ## Preflight (\"Property 'Acme Demo' exists with ≥1\n issue\"). A test that must mutate pre-existing data creates its own\n copy first, or states in Preflight why it can't.\n\n 3. ISOLATED — the test works only on data it created and never modifies\n shared resources as a side effect (billing, integrations, API keys, org\n config). Match the isolation level to the blast radius:\n record-level unique-id macros in a shared tenant — ordinary CRUD\n tenant/org-level a seeded org — the test mutates org-wide state\n application-level an ephemeral env / sandbox — it mutates global state\n Workaround: tests that genuinely depend on one another run as ordered\n LAYERS in a test suite (cofactor test-suite ... --layer, optionally\n --failure-mode stop_on_layer_failure). Layers run sequentially; members\n within a layer run in parallel. Never rely on implicit ordering between\n standalone flows.\n\n 4. CLEANUP SURVIVES FAILURE — a fatal assertion halts the run, so trailing\n inline cleanup nodes do NOT run. Seed-paired teardown workflows run on\n both pass and fail. Inline cleanup is therefore best-effort and pairs\n with unique naming (\"QA Test\" prefix + macro) so orphans are harmless\n and identifiable. When a record MUST be removed even after a failure\n (quota-bound, billed, visible to real users), that is the reason to move\n it into a seed + teardown pair.\n\n 5. EXTERNAL DEPENDENCIES ARE DECLARED OR CAPTURED — email delivery: give a\n seed credential output \"Create email inbox\" (the Email Inbox feature,\n /settings/inbox) so the flow can read the message. Payments, third-party\n APIs, feature flags: declare them in ## Preflight (cofactor feature-flag\n list --env <env> for exact flag names). Never assume an external\n system's state silently.\n\nSEED ONLY WHAT IS REUSED\n\n A seed workflow runs before your test, creates data, and exports variables\n downstream workflows reference as $var.{dataKey}. Reach for one when the\n same data serves several tests. When it does, you also get:\n - Faster setup — API sequences instead of browser interactions\n - Cleaner separation of setup and test logic\n - Paired teardown that runs on pass AND fail\n\n If only this test needs the data, create it inline (see CREATING TEST\n DATA below). See the Seed Workflows section for how to build seeds.\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) as long as the test only reads it — declare\n it in ## Preflight (rule 2 above). Create everything the test mutates.\n\nCREATING TEST DATA\n\n If the test operates on a record (edit, delete, archive), create that\n record as an early step. Operating on a record that already existed is\n a documented deviation, not the default (rule 2 above).\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 Use macros in newly created 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 Inline cleanup is best-effort: a fatal assertion halts the run before the\n cleanup nodes execute. Seed-paired teardowns run on pass and fail. Don't\n over-engineer inline cleanup — \"navigate to the item and delete it\" is\n enough; if a record must be gone even after a failure, provision it from a\n seed and let its teardown remove it (rule 4 above).\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 Use macros for newly created test names, emails, and identifiers. Keep\n confirmed existing reference names and captured identities intact. 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 record creation or deletion. Restore any view state the journey\n changes. The presence of unrelated create/edit/delete controls does not\n expand the test's scope: exercise the specified behavior. A journey that\n tests writes owns those records and verifies their lifecycle. Only create\n 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 Each non-agentic inline assertion is proved by exactly one `verify` step in\n the playbook, bound to it by the assertion's id (see playbook.txt). That\n binding is what lets the check compile and replay at the point in the flow\n where it was actually verified, instead of trailing at the end of the node.\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 Changing content, timestamps, and measurable layout can still be checked\n deterministically against current-run inputs and captured baselines.\n Cached assertions replay verification code, never an earlier verdict.\n\n Standard assertions are preferred — only use agentic when the\n check needs subjective judgment.\n\nWRITING GOOD CONDITIONS\n\n Assertions describe codified, user-visible behavior, not the mechanics\n used to observe it. Derive the expected result from the specification,\n product-map requirement, or confirmed user intent. Browser exploration\n discovers how to exercise and measure the behavior; it does not invent\n new acceptance requirements.\n\n Good: \"The named column stays fixed while level columns scroll.\"\n Good: \"After unpinning, the named column scrolls with the grid.\"\n Bad: \"aria-pressed=false and the SVG has an outlined path.\"\n Bad: \"Horizontal scroll increases at least 80 pixels.\" (unless required)\n\n A scroll distance or viewport size chosen for the test belongs in setup\n or the action. Measurements and comparison tolerances belong in replay\n code. Confirm the intended user during browser preflight; do not add a\n signed-in-user assertion unless identity/authentication is the journey.\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 when the requirement specifies it. Do not assert exact\n incidental copy, attributes, or dimensions simply because they were observed.\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\n MULTIPART UPLOADS — to send multipart/form-data instead of a raw body, set\n `formParts` on the step and omit `bodyTemplate`. The two are mutually\n exclusive; there is no separate mode field, a step is multipart exactly when\n `formParts` is present. Each part is either text or file:\n\n \"formParts\": [\n { \"kind\": \"text\", \"name\": \"projectId\", \"value\": \"$var.project_id\" },\n { \"kind\": \"file\", \"name\": \"file\", \"attachmentId\": \"<uuid>\" }\n ]\n\n A text part's `value` supports the same macro/variable/step-reference syntax\n as `bodyTemplate`. A file part names an attachment by UUID; add `filename`\n to override the name sent in Content-Disposition, otherwise the\n attachment's own filename is used. Don't set a `Content-Type` header on a\n multipart step: the transport generates its own boundary-bearing header at\n request time, so any author-supplied `Content-Type` is stripped rather than\n allowed to shadow it with a stale or missing boundary.\n\n API LOGIN CREDENTIALS — use these for token exchanges that precede the\n sequence request. POST bodies support application/json,\n application/x-www-form-urlencoded, and text/xml. With no body template,\n form fields are URL-encoded automatically. JSON token responses use normal\n dot paths such as access_token; XML/SOAP responses are parsed with namespace\n prefixes removed, so a Salesforce session path is\n Envelope.Body.loginResponse.result.sessionId. Keep returned tokens inside\n the Credential boundary — do not export them as workflow variables. Token\n request headers (for example Salesforce SOAPAction=login) are encrypted with\n the Credential and apply only to the token exchange.\n\nTEMPLATE SYNTAX\n\n Three ways to inject dynamic values. All three can appear in the same\n string (urlTemplate, bodyTemplate, headersTemplate values, or queryParams\n values).\n\n 1. STEP REFERENCES — 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 $NOW(+5m) ISO timestamp shifted by +5m / -2h / 30s / 1d\n $NOW(+5m, 'm H * * *') Shifted time rendered in UTC (see macros guide);\n single-quote the format inside a JSON bodyTemplate\n $TODAY() YYYY-MM-DD\n $DATE(N) Date offset by N days\n $TIMESTAMP() Unix ms\n $RANDOM_INT(min,max) Random integer in range\n $RANDOM_STRING(N) Alphanumeric string of length N\n $EMAIL() test-<rand>@example-test.com\n $FIRST_NAME() Random first name\n $LAST_NAME() Random last name\n $PHONE() +1-555-...\n $SEQUENCE(\"name\") Per-run auto-increment counter\n\n Each run resolves macros independently. $SEQUENCE counters persist\n across steps within a single run.\n\n 3. WORKFLOW VARIABLES — 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 `--outputs-json '<array>'` is the inline equivalent; to change the outputs\n of an existing seed use `canary workflow patch <id> --seed-outputs-file\n seed-outputs.json`. The credentialOutput shape and the end-to-end recipe\n are in `canary workflow guide seed-users`.\n\nAFTER CREATION\n\n Add the seed body through the Flow Designer or workflow sync, test both the\n seed and teardown, then publish. If the seed can be API-backed, generate or\n attach an API sequence after the browser version is proven.\n\nREFERENCE\n\n - Seed command help: `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","Use this section to author a chat eval from the Canary CLI. The conceptual\nmodel lives in the CHAT SCENARIO and CALL FLOW node types; this section is only\ncommand orientation. Prefer each command's `--help` output for the current flag\nlist.\n\nAn eval is a workflow of flowType \"eval\" built around one chat_scenario node.\nIt needs a chat target first — the saved description of how to talk to the AI\nchat under test. Both require the `evals` feature flag.\n\nCREATE THE CHAT TARGET\n\n A chat target is application-scoped like a credential, and describes one\n endpoint: how to send a message, how to read the reply, who to send it as.\n\n canary chat-target create \\\n --application \"Acme App\" --environment \"Staging\" \\\n --credential \"Support Agent\" \\\n --name \"Acme support assistant\" \\\n --config ./target.json \\\n --invariant \"No cross-org data=Never reveals another organization's data.\"\n\n `--config` takes a file or `-` for stdin, holding `message` (method,\n urlTemplate, historyMode, bodyTemplate, optional headersTemplate) and\n `response` (transport `ai-sdk-v5`, or `sse` with deltaPath, or `json` with\n textPath).\n\n Only three slots are substituted per turn: {{message}}, {{messages}} and\n {{conversationId}}. There is NO {{baseUrl}} slot — write the endpoint as a\n literal URL.\n\n `$var.ENV.<name>` also resolves in a urlTemplate, which lets one target\n follow whichever environment a run picks. But it resolves only during a\n RUN: the live-chat path behind `chat-target chat` supplies no workflow\n variables, so a target written that way cannot be smoke-tested with the\n command below. Prefer a literal URL unless you need one target across\n several environments, and know the tradeoff if you do.\n\n `--invariant` is repeatable and declares a rubric judged against EVERY reply\n in every scenario run against this target — the cheapest coverage available.\n Severity defaults to fail; append `:warn` to the name to soften one.\n\nPROVE THE TARGET BEFORE BUILDING ON IT\n\n canary chat-target chat \"Acme support assistant\" --message \"What can you do?\"\n\n This sends one real turn down the same executor path a scenario uses, so a\n reply here is evidence the config, credential binding and transport all work.\n Expect 10-30s for a credentialed target — each message resolves an\n authenticated session server-side. A failed turn exits non-zero.\n\nAUTHOR AND RUN THE EVAL\n\n Author it like any other flow — `canary workflow create --from-file eval.json`\n with `\"flowType\": \"eval\"` and one `chat_scenario` node whose configJson holds\n `chatTargetId` and `turns` (see the CHAT SCENARIO entry for the shape).\n\n Put an api_sequence node BEFORE the chat node to establish ground truth and\n reference its exports from a rubric — \"Reports a count matching\n $var.expected_count\". Put a call_flow node AFTER it to have a browser flow\n verify something the assistant claims it did.\n\n Watch the two path forms in an api_sequence: a `validations` entry's `left`\n is step-qualified (`myStep.response.items.0.name`), while an `exports`\n entry's `responsePath` is relative to that step's response body\n (`items.0.name`) because it already names its `stepId`. Carrying the\n `response.` prefix into an export silently yields nothing — the step still\n passes, and the failure only surfaces later as a rubric that references a\n variable no step produced.\n\n Give the ground-truth step a `validations` entry as well as an export. An\n export that resolves to nothing does not fail the step, so without a\n validation the first sign of trouble is a withheld rubric further down.\n\n canary workflow run <workflowId>\n canary workflow runs get <runId>\n\n Per-turn judge verdicts persist on the chat_scenario step as\n `metadataJson.chatTranscript`, so the graded transcript is readable without\n opening the browser.\n\nREFERENCE\n\n - Chat target help: `canary chat-target --help`\n - Node config shapes: `canary workflow guide node-types`\n","import type { GuideSection } from '../types.js';\n\nimport overview from './overview.txt';\nimport nodeTypes from './node-types.txt';\nimport specGrammar from './spec-grammar.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 seedUsers from './seed-users.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';\nimport cliChatEvals from './cli-chat-evals.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: 'Spec file grammar (.canary.md)', slug: 'spec-grammar', content: specGrammar },\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: 'Seeding a Test User', slug: 'seed-users', content: seedUsers },\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 { title: 'CLI: Chat Evals', slug: 'cli-chat-evals', content: cliChatEvals },\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;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;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;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;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;;;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;;;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;;;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;;;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;;;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;;;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;;;ACAA;;;ACAA;;;ACAA;;;ACAA;;;ACkBO,IAAM,kCAAkD;AAAA,EAC7D,EAAE,OAAO,qBAAqB,MAAM,YAAY,SAAS,iBAAS;AAAA,EAClE,EAAE,OAAO,cAAc,MAAM,cAAc,SAAS,mBAAU;AAAA,EAC9D,EAAE,OAAO,kCAAkC,MAAM,gBAAgB,SAAS,qBAAY;AAAA,EACtF,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,cAAc,SAAS,mBAAU;AAAA,EACvE,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;AAAA,EACnF,EAAE,OAAO,mBAAmB,MAAM,kBAAkB,SAAS,uBAAa;AAC5E;AAEO,IAAM,0BAA0C;AAAA,EACrD,GAAG;AAAA,EACH,GAAG;AACL;","names":[]}
@@ -50,7 +50,7 @@ function printHelp({
50
50
  lines.push(
51
51
  " canary application <sub-command> List and create applications, view linked repos (alias: property)",
52
52
  " canary environment <sub-command> List, create, and default environments (alias: env)",
53
- " canary sandbox <sub-command> Manage sandbox templates and instances",
53
+ " canary sandbox <sub-command> Sandboxes v2: push, claim, run app copies (+ legacy v1)",
54
54
  " canary attachment <sub-command> List, upload, and manage attachments",
55
55
  " canary tag <sub-command> List, create, and assign tags",
56
56
  " canary triage <sub-command> Read AI triage results for a test suite execution",
@@ -280,4 +280,4 @@ export {
280
280
  resolveIsSuperadmin,
281
281
  showGlobalHelp
282
282
  };
283
- //# sourceMappingURL=chunk-E6K4PAID.js.map
283
+ //# sourceMappingURL=chunk-SQXLD7QP.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/help.ts"],"sourcesContent":["import { readAllStoredTokens, resolveOptionalConfig } from './auth';\nimport { version, readBrandEnv, specExtension } from './brand.js';\nimport { isSuperadminToken } from './jwt';\n\nexport function printVersion() {\n console.log(`canary v${version}`);\n}\n\nexport function printHelp({\n isSuperadmin,\n features = {},\n}: {\n isSuperadmin: boolean;\n features?: Record<string, boolean>;\n}) {\n const lines: string[] = [\n `canary v${version}: Testing and QA CLI`,\n '',\n 'Usage:',\n ' canary test-suite <sub-command> Run and query test suite results',\n ' canary external-run <sub-command> Diagnose external suite runs (no superadmin access)',\n ' canary mcp Start MCP server (stdio)',\n ' canary install [--target <id>] Install MCP server into Claude, Cursor, etc.',\n ' canary login [--org <name>] [--app-url https://app.runcofactor.com] [--no-open]',\n ' canary orgs [members] List organizations + your logged-in sessions',\n ' canary org use <name|id> Set the active org for bare commands',\n ' canary record [options] Record browser interactions',\n ' canary local <sub-command> Set up and manage local testing',\n ' canary session <sub-command> Manage local browser sessions',\n ' canary issues <sub-command> Search and view issues',\n ' canary credential <sub-command> List, create, update, and test credentials',\n ' canary persona <sub-command> Manage per-application personas',\n ' canary chat-target <sub-command> Manage AI chat targets for chat evals',\n ' canary workflow <sub-command> Inspect workflow definitions',\n ` canary spec <sub-command> Author workflows from ${specExtension} spec files`,\n ' canary api-catalog <sub-command> Manage API catalog sources and endpoints',\n ' canary skill <name> Install an AI agent skill template (--name to rename)',\n ' canary prompts <list|get> Curated starter prompts for coding agents',\n ' canary digest <list|get> Read daily/weekly QA digests',\n ];\n\n lines.push(' canary release <sub-command> Release QA gate (CI/CD)');\n lines.push(' canary runbook <sub-command> Run repo-declared operational commands (deploy, rollback)');\n\n lines.push(\n ' canary application <sub-command> List and create applications, view linked repos (alias: property)',\n ' canary environment <sub-command> List, create, and default environments (alias: env)',\n ' canary sandbox <sub-command> Sandboxes v2: push, claim, run app copies (+ legacy v1)',\n ' canary attachment <sub-command> List, upload, and manage attachments',\n ' canary tag <sub-command> List, create, and assign tags',\n ' canary triage <sub-command> Read AI triage results for a test suite execution',\n ' canary schedule suite <sub-command> Schedule recurring test-suite runs',\n ' canary alert <sub-command> Manage notify-only alerts (Slack/email)'\n );\n\n // Feature-gated agent commands — only listed when the signed-in org has the\n // backing feature flag (resolved from /me). Hidden otherwise so help never\n // advertises a command that returns 403.\n if (features.agentTasks) {\n lines.push(' canary task <sub-command> Manage agent tasks');\n }\n if (features.utilities) {\n lines.push(\n ' canary utilities <group> <sub> Object storage, gists, email/slack (agent toolkit)'\n );\n }\n lines.push(\n ' canary product-map <sub-command> Areas, capabilities, flow links, coverage',\n ' canary exploration <sub-command> Run exploration cycles now (--cycles N), inspect them'\n );\n\n if (isSuperadmin) {\n lines.push(\n ' canary debug-session [--env dev|local] [--json] Create browser debug session',\n ' canary psql <query> [--json] Execute read-only SQL',\n ' canary redis <command> [--json] Execute read-only Redis commands',\n ' canary docs <sub-command> Manage documentation pages',\n ' canary landing <sub-command> Manage runcofactor.com/lp/<slug> landing pages',\n ' canary feature-flag <sub-command> Manage feature flags',\n ' canary knobs <sub-command> Manage knobs (global config)',\n ' canary product <sub-command> Manage the Product board (cards, fields, flag links)',\n ' canary metrics <sub-command> Manage product metrics (pull/review health metrics)',\n ' canary benchmark <sub-command> Run/inspect ad-hoc A/B experiments (models, knobs, flags)',\n ' canary account <sub-command> Manage the Accounts board (accounts, org links)',\n ' canary status <sub-command> Publish incidents to the status page'\n );\n }\n\n lines.push(\n ' canary auth doctor [--env <env>] Diagnose auth / corporate-proxy issues',\n ' canary upgrade Upgrade CLI to latest version',\n ' canary version Show version',\n ' canary help',\n '',\n 'Test suite sub-commands:',\n ' run [options] Trigger a test suite and stream results',\n ' list [options] List recent test suites',\n ' get <id> [options] Show suite detail with runs',\n ' diagnostics <workflowId> <runId> Get failure diagnostic',\n ' steps <workflowId> <runId> Get workflow run steps',\n '',\n 'Test suite run options:',\n ' --token <key> API key (or set CANARY_API_TOKEN)',\n ' --api-url <url> API URL (default: https://api.runcofactor.com)',\n ' --application <name|id> Target a specific application (alias: --property)',\n ' --environment <name|id> Target a specific environment',\n ' --tag <tag> Filter workflows by tag',\n ' --name-pattern <pat> Filter workflows by name pattern',\n ' --verbose, -v Show all events',\n '',\n 'Login options:',\n ' --org <name> Select organization by name or ID (for multi-org users)',\n '',\n 'Working across orgs (act on behalf of customers):',\n ' Stay logged into several orgs at once and pick which one any command runs as.',\n ' (--org goes AFTER the command, like --env: `canary <command> ... --org <name>`.)',\n ' canary <command> --org <name|id> Run one command as one of your logged-in orgs',\n ' CANARY_ORG=<name|id> Persistent default org (like AWS_PROFILE)',\n ' canary login --org <name> Add / refresh a session (side by side per env)',\n ' canary org use <name> Set the active org for bare (no --org) commands',\n ' canary orgs See your logged-in orgs, their env, token expiry',\n ' (Superadmin cross-org commands target a different org with --target-org.)',\n '',\n 'Login environments:',\n ' Production: canary login',\n ' Dev: canary login --app-url https://app.dev.trycanary.ai --api-url https://api.dev.trycanary.ai',\n ' Local: canary login --app-url http://localhost:5173 --api-url http://localhost:3000',\n '',\n ' Or set CANARY_API_URL env var for non-production environments:',\n ' export CANARY_API_URL=http://localhost:3000'\n );\n\n lines.push(\n '',\n 'Debug options:',\n ' --to-step <N> Stop after step N (1-based, default: all)',\n ' --env <env> Environment (local, dev, prod)',\n ' --verbose, -v Show all SSE events',\n '',\n 'Issues sub-commands:',\n ' list [options] List and search issues',\n ' get <issueId> [options] Get issue detail with diagnostics',\n '',\n 'Issues options:',\n ' --search <query> Full-text search',\n ' --severity <level> Filter: low, medium, high, unknown',\n ' --status <statuses> Filter: open, closed, not_a_bug (comma-separated)',\n ' --application-id <uuid> Filter by application (alias: --property-id)',\n ' --page <n> Page number (default: 1)',\n ' --page-size <n> Page size (default: 25)',\n ' --json Output raw JSON',\n ' --format markdown Output as markdown',\n '',\n 'Release sub-commands:',\n ' trigger --application-id <uuid> Trigger a Release QA run (alias: --property-id)',\n ' status <run-id> [--json] Check run status',\n ' run --application-id <uuid> [--timeout N] Trigger and poll until complete',\n '',\n 'Record options:',\n ' --credential <id> Credential ID (skip interactive selection)',\n ' --url <startUrl> URL to navigate to after launch',\n ' --output <dir> Local output directory (default: temp dir)',\n ' --env <env> Environment (local, dev, prod)',\n ' --no-upload Save locally only, skip API upload',\n ''\n );\n\n if (isSuperadmin) {\n lines.push(\n '',\n 'PSQL options:',\n ' --json Output results as JSON',\n ' --query <sql> SQL query (alternative to positional)',\n '',\n 'Redis options:',\n ' --json Output results as JSON',\n '',\n 'Feature flag sub-commands:',\n ' list List all flags',\n ' create <name> [--description <text>] Create a flag',\n ' delete <name> Delete a flag and its gates',\n ' enable <name> --target-org <orgId> Enable for an org',\n ' disable <name> --target-org <orgId> Disable for an org',\n ' lifecycle <name> --stage <stage> [--final-value true|false]',\n ' Set lifecycle metadata',\n '',\n 'Knobs sub-commands:',\n ' list List all knobs',\n ' get <key> Get a knob value',\n ' set <key> <value> --type <type> Set a knob value',\n ' delete <key> Delete a knob',\n ' toggle <key> Toggle a boolean knob',\n ' lifecycle <key> --stage <stage> [--final-value <value>]',\n ' Set lifecycle metadata',\n '',\n 'Product board sub-commands (org you are logged into):',\n ' list [--search <t>] [--priority p0,p1] [--include-deprecated] List items',\n ' create <title> [--description <t>] [--stage <s>] [--priority <p0-p3>] Create item',\n ' set <id> [--title <t>] [--description <t>] [--priority <p0-p3|null>] Update fields',\n ' move <id> --stage <s> [--before <id>] [--after <id>] Move card to a column',\n ' delete <id> Delete item',\n ' customer add <id> (--account-card <id> | --target-org <orgId>) [--name <t>] Link customer',\n ' customer remove <id> <customerId> Unlink customer',\n ' link-flag <cardId> --flag <name> | --flag <name> --clear Link/unlink a feature flag',\n ' flags <cardId> List flags linked to a card',\n '',\n 'Account board sub-commands (org you are logged into):',\n ' list [--search <t>] [--stage <s>] List accounts',\n ' create <name> [--description <t>] [--stage <s>] Create account (default stage=prospect)',\n ' set <id> [--title <t>] [--description <t>] [--stage <s>] Update fields',\n ' delete <id> Delete account',\n ' link-org <id> --target-org <customerOrgId> Bind account to a customer org',\n ' unlink-org <id> Remove org link',\n ' linked-org <id> Show linked org'\n );\n }\n\n lines.push('', 'Flags:', ' -h, --help Show help', ' -V, --version Show version');\n\n console.log(lines.join('\\n'));\n}\n\nexport function printTestHelp() {\n console.log(\n [\n `canary v${version}: Test command`,\n '',\n '\\x1b[33m⚠ `canary test` is deprecated. Use `canary test-suite run` instead.\\x1b[0m',\n '',\n 'Usage:',\n ' canary test-suite run [options] Trigger a test suite and stream results',\n ' canary test-suite list List recent test suites',\n ' canary test-suite get <id> Show suite detail with runs',\n '',\n 'Run options:',\n ' --token <key> API key (or set CANARY_API_TOKEN)',\n ' --api-url <url> API URL (default: https://api.runcofactor.com)',\n ' --application <name|id> Target a specific application (alias: --property)',\n ' --environment <name|id> Target a specific environment',\n ' --tag <tag> Filter workflows by tag',\n ' --name-pattern <pat> Filter workflows by name pattern',\n ' --verbose, -v Show all events',\n '',\n 'Examples:',\n ' canary test-suite run Run all workflow tests',\n ' canary test-suite run --tag smoke Run smoke-tagged workflows',\n ' canary test-suite run --property \"My App\" --environment staging',\n ].join('\\n')\n );\n}\n\n/** Check if any stored profile (or env token) has superadmin privileges */\nexport async function resolveIsSuperadmin(): Promise<boolean> {\n const envToken = readBrandEnv('CANARY_API_TOKEN');\n if (envToken) return isSuperadminToken(envToken);\n\n const tokens = await readAllStoredTokens();\n return tokens.some((t) => isSuperadminToken(t));\n}\n\n/**\n * Best-effort fetch of the signed-in org's enabled feature flags (the same\n * `user.features` map the web app uses), so the global help can hide commands\n * for features the org doesn't have. Fail-closed: any error, timeout, or\n * missing auth yields `{}`, which hides feature-gated commands rather than\n * advertising something the org can't use. Never throws.\n */\nexport async function resolveOrgFeatures(): Promise<Record<string, boolean>> {\n try {\n const config = await resolveOptionalConfig([]);\n if (!config) return {};\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), 3000);\n try {\n const res = await fetch(`${config.apiUrl}/me`, {\n headers: { Authorization: `Bearer ${config.token}` },\n signal: controller.signal,\n });\n if (!res.ok) return {};\n const body = (await res.json()) as { user?: { features?: Record<string, boolean> } };\n return body.user?.features ?? {};\n } finally {\n clearTimeout(timer);\n }\n } catch {\n return {};\n }\n}\n\n/** Resolve superadmin + org features in parallel, then print the global help. */\nexport async function showGlobalHelp(): Promise<void> {\n const [isSuperadmin, features] = await Promise.all([resolveIsSuperadmin(), resolveOrgFeatures()]);\n printHelp({ isSuperadmin, features });\n}\n"],"mappings":";;;;;;;;;;;;;;;AAIO,SAAS,eAAe;AAC7B,UAAQ,IAAI,WAAW,OAAO,EAAE;AAClC;AAEO,SAAS,UAAU;AAAA,EACxB;AAAA,EACA,WAAW,CAAC;AACd,GAGG;AACD,QAAM,QAAkB;AAAA,IACtB,WAAW,OAAO;AAAA,IAClB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,kEAAkE,aAAa;AAAA,IAC/E;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAEA,QAAM,KAAK,mEAAmE;AAC9E,QAAM,KAAK,qGAAqG;AAEhH,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAKA,MAAI,SAAS,YAAY;AACvB,UAAM,KAAK,8DAA8D;AAAA,EAC3E;AACA,MAAI,SAAS,WAAW;AACtB,UAAM;AAAA,MACJ;AAAA,IACF;AAAA,EACF;AACA,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,EACF;AAEA,MAAI,cAAc;AAChB,UAAM;AAAA,MACJ;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAEA,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAEA,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAEA,MAAI,cAAc;AAChB,UAAM;AAAA,MACJ;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAEA,QAAM,KAAK,IAAI,UAAU,8BAA8B,+BAA+B;AAEtF,UAAQ,IAAI,MAAM,KAAK,IAAI,CAAC;AAC9B;AAEO,SAAS,gBAAgB;AAC9B,UAAQ;AAAA,IACN;AAAA,MACE,WAAW,OAAO;AAAA,MAClB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF,EAAE,KAAK,IAAI;AAAA,EACb;AACF;AAGA,eAAsB,sBAAwC;AAC5D,QAAM,WAAW,aAAa,kBAAkB;AAChD,MAAI,SAAU,QAAO,kBAAkB,QAAQ;AAE/C,QAAM,SAAS,MAAM,oBAAoB;AACzC,SAAO,OAAO,KAAK,CAAC,MAAM,kBAAkB,CAAC,CAAC;AAChD;AASA,eAAsB,qBAAuD;AAC3E,MAAI;AACF,UAAM,SAAS,MAAM,sBAAsB,CAAC,CAAC;AAC7C,QAAI,CAAC,OAAQ,QAAO,CAAC;AACrB,UAAM,aAAa,IAAI,gBAAgB;AACvC,UAAM,QAAQ,WAAW,MAAM,WAAW,MAAM,GAAG,GAAI;AACvD,QAAI;AACF,YAAM,MAAM,MAAM,MAAM,GAAG,OAAO,MAAM,OAAO;AAAA,QAC7C,SAAS,EAAE,eAAe,UAAU,OAAO,KAAK,GAAG;AAAA,QACnD,QAAQ,WAAW;AAAA,MACrB,CAAC;AACD,UAAI,CAAC,IAAI,GAAI,QAAO,CAAC;AACrB,YAAM,OAAQ,MAAM,IAAI,KAAK;AAC7B,aAAO,KAAK,MAAM,YAAY,CAAC;AAAA,IACjC,UAAE;AACA,mBAAa,KAAK;AAAA,IACpB;AAAA,EACF,QAAQ;AACN,WAAO,CAAC;AAAA,EACV;AACF;AAGA,eAAsB,iBAAgC;AACpD,QAAM,CAAC,cAAc,QAAQ,IAAI,MAAM,QAAQ,IAAI,CAAC,oBAAoB,GAAG,mBAAmB,CAAC,CAAC;AAChG,YAAU,EAAE,cAAc,SAAS,CAAC;AACtC;","names":[]}
package/dist/index.js CHANGED
@@ -7,7 +7,7 @@ import {
7
7
  printVersion,
8
8
  resolveIsSuperadmin,
9
9
  showGlobalHelp
10
- } from "./chunk-E6K4PAID.js";
10
+ } from "./chunk-SQXLD7QP.js";
11
11
  import {
12
12
  runLogin
13
13
  } from "./chunk-Y7ATOIGI.js";
@@ -498,7 +498,7 @@ var COMMAND_DEFINITIONS = [
498
498
  {
499
499
  name: "landing",
500
500
  run: async (rest) => {
501
- const { runLanding } = await import("./landing-NJZS5JDT.js");
501
+ const { runLanding } = await import("./landing-3TY7TLVZ.js");
502
502
  await runLanding(rest);
503
503
  }
504
504
  },
@@ -613,7 +613,7 @@ var COMMAND_DEFINITIONS = [
613
613
  updateCheck: true,
614
614
  handlesHelp: true,
615
615
  run: async (rest) => {
616
- const { runWorkflow } = await import("./workflow-NTSXBZR3.js");
616
+ const { runWorkflow } = await import("./workflow-QRCK7U6G.js");
617
617
  await runWorkflow(rest);
618
618
  }
619
619
  },
@@ -633,7 +633,7 @@ var COMMAND_DEFINITIONS = [
633
633
  updateCheck: ["skill", "skills"],
634
634
  handlesHelp: ["skill", "skills"],
635
635
  run: async (rest) => {
636
- const { runSkill } = await import("./skill-G2654GLA.js");
636
+ const { runSkill } = await import("./skill-A4MN7UIT.js");
637
637
  await runSkill(rest);
638
638
  }
639
639
  },
@@ -712,7 +712,7 @@ var COMMAND_DEFINITIONS = [
712
712
  updateCheck: true,
713
713
  handlesHelp: true,
714
714
  run: async (rest) => {
715
- const { runSandbox } = await import("./sandbox-OIC6CDHG.js");
715
+ const { runSandbox } = await import("./sandbox-5Y4HALKW.js");
716
716
  await runSandbox(rest);
717
717
  }
718
718
  },
@@ -2,7 +2,7 @@ import { createRequire as __cr } from "module"; import { fileURLToPath as __fup
2
2
  import {
3
3
  resolveIsSuperadmin,
4
4
  showGlobalHelp
5
- } from "./chunk-E6K4PAID.js";
5
+ } from "./chunk-SQXLD7QP.js";
6
6
  import {
7
7
  apiRequest
8
8
  } from "./chunk-XVXKETBT.js";
@@ -621,4 +621,4 @@ export {
621
621
  renderStatsTable,
622
622
  runLanding
623
623
  };
624
- //# sourceMappingURL=landing-NJZS5JDT.js.map
624
+ //# sourceMappingURL=landing-3TY7TLVZ.js.map