Steps and approvals
Jobs form a graph of steps inside each rollout phase, ordered by depends_on, with block steps that wait for a person in your environments.
Every job on deploy, seed or start is a step. Phases run in a fixed order. Inside a phase, steps run in parallel unless depends_on orders them. A block step runs nothing and waits for someone to approve it. The keys are in the jobs reference.
jobs:
init-db: { label: Create the database, service: web, command: npm run db:init, on: deploy }
migrate: { service: web, command: npm run migrate, depends_on: [init-db] }
assets: { service: web, runner: tools, command: ./scripts/upload-assets.sh, on: deploy }
release:
block: Release to customers
prompt: Check the canary dashboard and #support first.
depends_on: [migrate, assets]
environments: [production]
seed: { service: web, command: npm run seed, on: seed }init-db and assets start together. migrate starts when init-db succeeds. release waits for both branches. seed starts after the whole deploy phase.
Phases
Phases are barriers. A phase starts only when every step of the one before it has succeeded.
| Phase | Steps | Runs |
|---|---|---|
deploy | Jobs with on: deploy | When a sandbox is first set up, and on every rollout after |
seed | Jobs with on: seed and not deploy | Once, when a sandbox is first set up |
start | Jobs with on: start | Once per sandbox, after deploy and seed |
In your environments, a rollout runs the deploy steps; see Builds. Jobs on a schedule, on destroy or with no on are not steps.
Order steps with depends_on
depends_on names jobs and services in one list. A job name makes the step run after that job succeeds. A service name makes the job wait for that service, as before.
- Parallel by default. Steps with nothing between them run at the same time, up to 8 per sandbox. Past 8, the rest start in file order as slots free up.
- Inherited phase. A job without
onthat depends on jobs runs in the latest phase among them.migrateabove runs ondeploy. A job withoutonthat depends only on services stays on demand. - Same or earlier phase. A step can depend on steps of its own phase or an earlier one. A
seedstep can list adeploystep; adeploystep can't list astartstep. - No cycles. Parity names the cycle.
- One name, one meaning. A name that is both a service and a job is an error. Rename one.
Labels
label is the name the console and CLI show for a step. The job's name stays its key: depends_on, parity run and the API use the key. A label is one line, at most 200 characters.
Block steps
A block step gates the rollout. It runs nothing: no service, command, runner or service depends_on.
release:
block: Release to customers # or: block: true, with label
prompt: Check the canary dashboard and #support first.
depends_on: [migrate, assets]
environments: [production]| Key | Meaning |
|---|---|
block | block: <label>, or block: true with an optional label. Not both a block label and label. |
prompt | Optional plain text, up to 1,000 characters, shown when someone approves. Block steps only. |
environments | Names of declared environments. Omitted means every environment. Block steps only. |
depends_on | Jobs only. A block runs on deploy, so every job it lists must be a deploy step. |
Sandboxes pass blocks. A sandbox belongs to an agent, and agents never approve. The block records as succeeded the moment its dependencies are done, so parity up never stalls on one.
Environments wait. A block holds the rollout until a person approves or rejects it. That takes environments.deploy, which Developers have. Approve lets the steps after it run. Reject fails the rollout, and the current revision keeps serving. Agent tokens can't approve.
Jobs that run something can't vary by environment. What a sandbox tested is what every environment runs. environments exists only on blocks because a block runs nothing.
When a step fails
| What | Result |
|---|---|
| The failed step | failed, with the end of its log in log_tail. A job pod gets 3 attempts before the step fails. |
| Steps that depend on it, directly or not | skipped. They never start. |
| Steps already running | Run to completion. |
| Later phases | Never start. |
| The sandbox | Fails once nothing is still running. parity up prints the log tail. |
Rerunning parity up retries the failed step. Steps that succeeded on seed or start don't run again; deploy steps run on every rollout.
Parity runs one copy of a job at a time. A rollout that lands while an earlier run of migrate is still going waits for it, so two migrations never meet one database.
Read status
Before you deploy, parity check prints each step with its label, phase and what it runs after:
jobs
init-db (Create the database) in web, deploy: when a sandbox is set up and on every rollout
migrate in web, deploy: when a sandbox is set up and on every rollout, after init-db
assets in web, deploy: when a sandbox is set up and on every rollout
release (Release to customers) block, waits for a person in staging and production (sandboxes pass it), deploy: when a sandbox is set up and on every rollout, after migrate, assets
seed in web, seed: once, when a sandbox is first set up, after the deploy jobsWhile it runs, parity status, the sandbox_status MCP tool and GET /v0/sandboxes/{id} return each step under jobs, in run order:
[
{ "name": "init-db", "label": "Create the database", "event": "deploy", "status": "succeeded", "attempts": 1 },
{ "name": "migrate", "event": "deploy", "status": "running", "needs": ["init-db"], "attempts": 1 },
{ "name": "assets", "event": "deploy", "status": "failed", "attempts": 3, "log_tail": "…" },
{ "name": "release", "label": "Release to customers", "event": "deploy", "status": "skipped", "needs": ["migrate", "assets"], "block": true, "attempts": 0 }
]| Field | Meaning |
|---|---|
status | pending, running, succeeded, failed or skipped |
label | The step's label, when it has one |
needs | The jobs it runs after |
block | true for a block step |
event | The phase the run was for. On a sandbox's first setup, deploy steps report seed. |
Validation
parity check and parity up refuse a manifest with any of these, and list them all at once.
| Problem | Message |
|---|---|
| Depends on itself | jobs.<job>.depends_on: a job can't depend on itself |
| A cycle | jobs.a.depends_on: a cycle, a → b → a |
| Depends on a later phase | jobs.migrate.depends_on: warm runs on start, after this job's deploy; depend on jobs of the same or an earlier phase |
| Depends on a job that isn't a step | jobs.<job>.depends_on: <dep> runs on no seed, deploy or start, so it is never a step to wait for |
| A non-step depends on a job | jobs.<job>.depends_on: <dep> is a job; only steps (jobs on seed, deploy or start) run after other jobs |
| Name is both a service and a job | jobs.<job>.depends_on: "<name>" is both a service and a job; rename one of them |
| A block that runs something | jobs.<job>: a block step waits for a person and runs nothing; drop service, command, runner and service depends_on |
A block off deploy | jobs.<job>.on: a block step runs on deploy, not start (it gates a rollout) |
Block label and label | jobs.<job>: block and label both name the step; keep one (block: true with label, or block: <label>) |
Empty or non-text block | block must be a label, such as "Release to customers", or true |
prompt on a job | jobs.<job>.prompt: only block steps have a prompt; add block: true, or drop prompt |
environments on a job | jobs.<job>.environments: only block steps vary by environment (a job that runs something runs the same everywhere, so a sandbox tests it) |
| An undeclared environment | jobs.<job>.environments: environment "<name>" is not declared (sandboxes pass block steps) |
| Long text | jobs.<job>.prompt: at most 1000 characters, got <n> and jobs.<job>.label: at most 200 characters, got <n> |
| Multi-line label | jobs.<job>.label: one line; put longer text in prompt |
Migrate from file order
Jobs of one phase used to run one at a time, in file order. They now run in parallel. If a job relied on the job above it, list that job in depends_on.
jobs:
init-db: { service: web, command: npm run db:init, on: deploy }
migrate: { service: web, command: npm run migrate, on: deploy }jobs:
init-db: { service: web, command: npm run db:init, on: deploy }
migrate: { service: web, command: npm run migrate, on: deploy, depends_on: [init-db] }Order between phases is unchanged. A seed job still runs after every deploy job and needs no depends_on for that. Run parity check to see each step's after list before you push.