# Workflow files A workflow is a YAML file at `.tangled/workflows/.yml` (or `.yaml`); its name is the file name without the extension. It is pure data: the happy path (`steps`) and the closed set of `actions` a run may take. There are no conditionals, expressions or loops; a run changes course only when an action's [result](results.md) says so. The schema is [`run.rtw.ci.workflow`](../lexicons/ci/run.rtw.ci.workflow.json). Unknown fields are errors. `ci validate` checks every workflow in the repo. For why it works this way, see [DESIGN.md](../DESIGN.md) §6–8. ```yaml when: - event: [push] branch: [main] engine: devenv steps: [test, lint] actions: test: { command: go test ./... } lint: { command: golangci-lint run } ``` ## Top-level fields | Field | Required | Meaning | |---|---|---| | `when` | yes | Trigger conditions (below) | | `engine` | yes | Must be `devenv` | | `environment` | | Map of variable name to value, set for every action. Numbers and booleans become strings. Not for secrets: the file is in the repo | | `repos` | | Extra checkouts next to the main one (below) | | `steps` | yes | The happy path: action names, in order | | `actions` | yes | Map of action name to action (below) | ## `engine` Only `engine: devenv` workflows are run by this engine; files with any other engine are skipped (`ci validate` prints `skip`), so a repo can keep workflows for Tangled's spindle (`engine: microvm`) in the same directory. The reverse does not hold: a spindle marks a workflow whose engine it lacks as failed, so keep `devenv` workflows in repos no spindle watches. ## `when` A list of conditions; a push starts the workflow if any one matches. | Field | Meaning | |---|---| | `event` | `push` or `manual` (required). `pull_request` and `schedule` are not supported and fail validation | | `branch` | Branch globs. A push to a branch matches only if one of these matches | | `tag` | Tag globs. A push of a tag matches only if one of these matches | | `paths` | Push only: run only if a changed file matches one of these globs | Globs use `*` (within one path segment) and `**` (any number of segments), so `release/*` matches `release/1.2` and `docs/**` matches every file under `docs/`. Every list also accepts a single string: `event: push` is `event: [push]`. A push condition without `branch` or `tag` matches nothing. A manual start (the appview, `ci trigger`, `ci run`) always runs the workflow it names, whatever `when` says. ```yaml when: - event: push branch: [main, "release/**"] paths: ["**/*.go", go.mod] - event: push tag: "v*" - event: manual ``` ## `repos` Extra repositories checked out for each run, next to the main checkout. | Field | Required | Meaning | |---|---|---| | `repo` | yes | A repository name on the same forge as the main repo (`data`), or a full clone URL (`ssh://…`, `https://…`, `git@host:path`) | | `path` | yes | Where to check it out, relative to the main checkout. Must be a sibling: `../data` | | `rev` | | Revision to check out. Default: the default branch when the run starts | | `env` | | Variable that receives the checkout's absolute path, e.g. `SECRETS_DIR` | ```yaml repos: - { repo: data, path: ../data, env: SECRETS_DIR } - { repo: "https://github.com/example/tools", path: ../tools, rev: v1.4.0 } ``` A name is resolved against the main repo's `origin` (`git@git.example.org:servers` → `git@git.example.org:data`), or against `$CI_CLONE_URL` if set (`git@host:{repo}`). A URL is named by its last path element; two different URLs with the same name fail validation. The commit of every checkout is recorded in the run's events. Locally, `ci run --repo data=../data` uses a directory instead of a clean clone (see [running.md](running.md)). ## `steps` The happy path: the actions a run takes, in order, when every result is a plain success. Each step names a declared action, and a step may not appear twice (a result can route back to an earlier step instead). Actions that are not steps are reached only by a result's `next` or by hand (`manual`). ## `actions` A map of action name to action. Names use letters, digits, `.`, `_` and `-`, and `stop` is reserved. Results may route only to actions declared here, so this is the complete list of what the workflow can do, and the appview renders it as buttons. | Field | Meaning | |---|---| | `command` | Required. A bash script (see below) | | `dir` | Working directory, relative to the main checkout (`../data`). Default: the main checkout | | `label` | Button and timeline label. Default: the action name | | `gate` | Ask a person before running: `{ prompt, timeout }`, or `gate: true` for the defaults. `prompt` is the question (default "Run