Pipeline definitions: current implementation status¶
The pipeline TOML is read and executed, but is not a public contract yet
A pipeline run does now fetch, parse, and persist the TOML. On every
run, mark_pipeline_run_running reads the document at the pipeline's
toml_path, parses it with the one canonical reader, reconciles Job and
Step rows from it, and records the document's SHA-256 digest and declared
schema version on the run.
What is still missing before this can be documented as a contract you build automation against: the CLI does not validate with the server's rules, a manual run without a commit fetches a moving branch, triggers are not reconciled from the document, and matrices, expressions and outputs have no end-to-end execution tests. See Requirements for a stable native DSL for the current state of each.
Until those close, prefer the dashboard/API-managed objects for anything you cannot afford to have change under you.
This page records the behavior that exists today and the boundary around the
unfinished declarative interface. There is no astro pipeline validate; the
reader is backend/astrolift_ci_convert/common/toml_reader.py in astrolift-app,
and a run's recorded definition_digest is the record of what it read.
Pipeline execution model¶
A pipeline belongs to one organization and may optionally be associated with a registered app. Its stored definition consists of:
| Object | Current fields |
|---|---|
Pipeline |
name, repository URL, default branch, TOML path, optional registered app |
Job |
job ID, display name, runner selector, container image, dependency job IDs |
Step |
position, optional step ID, exactly one action reference or shell command, environment, action parameters |
Trigger |
push, pull request, schedule, manual, or webhook plus kind-specific JSON configuration |
PipelineRun |
run number, trigger source, source ref/actor, Temporal workflow ID, status and timestamps |
JobRun / StepRun |
execution status, assigned runner, output, logs, timing, and failure data |
Jobs form a directed acyclic graph through needs. The Temporal
PipelineRunWorkflow runs a topological tier in parallel and waits before
starting dependent jobs. Circular or missing dependencies are invalid.
A step can either:
- invoke a registered built-in action through
uses; or - execute a shell command through
run.
Those alternatives are mutually exclusive at the definition boundary, even though the database permits partial rows while a definition is being built.
Runners¶
A job's runs_on selects where it runs. The default, astrolift/default,
and any selector naming astrolift or an OS/architecture pair run as an
unprivileged Kubernetes Job on one of the organization's clusters whose node
OS, architectures and labels satisfy it; with no matching cluster the job
fails to schedule and says so. Because a cluster job has no Docker daemon and
cannot escalate, uses = "astrolift/docker-build" is refused there at render
time; build on a self-hosted runner or in your own CI.
self-hosted and macos select a self-hosted runner and are never spawned
on a cluster. Self-hosted runners poll the controller API for matching work.
A runner declares its operating system, architecture, and arbitrary labels,
and runs_on is matched against those. Registration authenticates with the
installation's runner registration token, configured by the operator; with no
token configured, registration is disabled rather than open. Registration
returns the long-lived runner credential. Only an idle runner can claim a job.
Runner endpoints are organization-scoped and validate the runner credential on every claim, heartbeat, log, and completion call. Do not reuse an Astrolift user or deploy token as a runner credential.
Runs, logs, and cancellation¶
The supported CLI surface is discoverable from the installed binary:
astro pipeline --help
astro pipeline list
astro pipeline run <pipeline> --request-file start.json --yes
astro pipeline runs --pipeline <pipeline>
astro pipeline show <run-guid>
astro pipeline logs <run-id>
astro pipeline cancel <run-id> --yes
astro pipeline reconcile --request-file start.json
run is a reviewed start: it binds the run to the pipeline's current version
and keeps a private request file so an interrupted start can be read back
with reconcile instead of resubmitted. See
Reviewed starts. Run identifiers and flags can
change between CLI releases; use the command's --help before scripting it.
--json is the stable choice for machine-readable output.
Cancellation marks the run cancelled and signals its Temporal workflow. The workflow then stops scheduling remaining work and attempts to cancel active jobs. A runner may take a short time to observe cancellation, so callers should wait for terminal job states instead of assuming the CLI return means every child process has already exited.
Triggers and source authentication¶
Pipeline triggers are persisted objects. Push and pull-request triggers are routed from source-provider webhooks, schedules are synchronized to the durable scheduler, and manual runs can be started from the dashboard, API, or CLI.
GitHub and GitLab webhook paths validate provider signatures and delivery metadata before creating a run. Generic outbound application webhooks are a different feature; their signing contract is documented in Webhook signature verification.
Trigger filters belong to the trigger's configuration, not an undocumented TOML table. Inspect the API schema or dashboard generated by the installed release for the accepted JSON shape.
Secrets and expressions¶
Pipeline secrets are resolved for the organization and optional app at dispatch time and reach only the job that references them, as a Kubernetes Secret that lives as long as the job pod. A run from a fork gets none. Secret values must not be put in job environment records, logs, trigger JSON, or repository metadata.
The execution layer supports context expansion for selected values, including source information and outputs from completed jobs. An output reference fails closed when the job or key does not exist; it is not silently replaced with an empty string. Treat values derived from untrusted source events as untrusted input to shell commands.
The reader accepts env and outputs tables, but no production path
evaluates ${...} expressions, if conditionals or job outputs when a job is
spawned; helper code for those exists without a caller. Do not build on them.
Concurrency behavior¶
The codebase contains a concurrency-policy hook and two rate limits. The one
that is enforced is the webhook receiver's: 300 deliveries per organization
per minute, checked after the signature, and a delivery over it is answered
429. The 60-runs-per-minute run limit and the concurrency hook have no
caller on any trigger path.
The proposed TOML block below is not active configuration today:
get_concurrency_config() currently returns no group and
cancel_in_progress = false; it does not parse the pipeline's repository file,
and nothing calls the policy it feeds. If you need "new build cancels old
build" now, enforce it in the source provider's workflow (for example GitHub
Actions concurrency).
The repository TOML path¶
Pipeline.toml_path defaults to:
When a run starts, the document is fetched at the run's commit SHA, falling back to the trigger ref and then the pipeline's default branch when no commit was recorded (a manual run started from a branch). GitHub and GitLab repositories are read through the organization's source connection; any other Git host fails the run with a clear error. A fetch or parse failure fails the run rather than reporting an empty one as green.
Editing or pushing the file does not create or change the Pipeline or its
Trigger rows; those are made through the dashboard, API or CLI, and the
document is read only when a run of that pipeline starts.
Similarly, the converter can render GitHub Actions or GitLab CI input into an intermediate TOML representation. Conversion output is not automatically a valid, deployable native pipeline definition. Treat conversion as preview or migration assistance until server-side validation and import are wired to the same schema.
Requirements for a stable native DSL¶
The declarative pipeline feature should not be considered supported until all of these are true in one release. Current state:
| # | Requirement | State |
|---|---|---|
| 1 | One published schema defines the accepted document and version field | Done. schema_version is declared on every written document and validated on read. A document with no field is read as version 1, so files written before the field existed keep working. An unrecognised version is refused with the accepted set in the message. |
| 2 | The CLI validates with the same rules as the server | Not done. astro workflow validate --server does this for workflow TOML; there is no pipeline equivalent. |
| 3 | Registration/sync fetches a file at an immutable source ref | Mostly. A webhook-started run fetches at its commit SHA. A manual run started from a branch records no commit and fetches the branch, which moves. |
| 4 | Parsing reconciles Pipeline, Job, Step and Trigger rows and reports structured path/line errors |
Mostly. Job and Step are reconciled per run, and every parse failure carries the offending key path. Trigger rows are not reconciled from the document. |
| 5 | A pipeline run records the source ref and definition digest it executed | Done. PipelineRun.definition_digest (SHA-256 of the document text) and definition_schema_version, both stamped only after the definition persists, so a run never claims a definition that failed to load. Empty and 0 mean "no document behind this run", which is a real state for an API-defined pipeline. |
| 6 | Round-trip and backward-compatibility tests cover every documented field | Partly. Round-trip is now byte-idempotent (write → read → write is identical) and compatibility of unversioned documents is pinned. Per-field coverage is not complete. |
| 7 | Repository webhooks state whether a push only syncs configuration or also starts a run | Not done. |
| 8 | Concurrency, matrices, expressions, outputs, secrets, and action inputs have end-to-end execution tests rather than isolated helper tests | Not done. Secrets have an end-to-end path; the rest do not. |
Document shape as read¶
The reader accepts these keys and refuses anything it cannot type, naming the
offending path. on is read into the parsed definition but is not reconciled
into Trigger rows (requirement 4), and env and outputs are stored
without being evaluated (see Secrets and expressions).
schema_version = 1
name = "orders-api"
[on.push]
branches = ["main"]
tags = ["v*"]
[on.pull_request]
branches = ["main"]
[[on.schedule]]
cron = "0 3 * * *"
[on.workflow_dispatch]
[env]
CI = "true"
[jobs.test]
name = "Test"
runs_on = "astrolift/default"
container = "python:3.12"
needs = []
[[jobs.test.steps]]
uses = "astrolift/git-checkout@v1"
[[jobs.test.steps]]
name = "Unit tests"
run = "make test"
[jobs.test.steps.env]
PYTHONDONTWRITEBYTECODE = "1"
| Key | Meaning |
|---|---|
schema_version |
Integer; 1 is the only accepted value and the default when absent |
name |
Required |
on.push.branches, on.push.tags, on.pull_request.branches |
String arrays |
on.schedule |
Array of tables, each with a cron |
on.workflow_dispatch |
Presence enables manual starts |
env |
String map; an unquoted number is read as its string |
jobs.<id> |
name (defaults to the id), runs_on (default astrolift/default), container, needs, env, outputs, steps |
jobs.<id>.steps[] |
name, exactly one of uses or run, env, with (action parameters) |
Jobs are read in sorted id order; needs carries the real ordering. The
built-in actions are astrolift/git-checkout@v1, astrolift/docker-build@v1,
astrolift/kubectl-apply@v1 and astrolift/astrolift-deploy@v1; the
unversioned name resolves to @v1. An unknown action, or a missing required
input, fails the job before its pod is created.
Composition¶
A pipeline document is a single file, and a repository holds one per pipeline
at .astrolift/pipelines/<name>.toml. There is no include, extends, or
import: the top-level keys are schema_version, name, on, env and
jobs, and the only reuse primitive is a step's uses reference to a
registered action.
Splitting a large definition across files is therefore a directory
convention, not a language feature, the same shape workflow TOML already
uses, where every workflows/**/*.toml file is discovered in deterministic
order and parsed independently. Adding true composition would need a
documented merge order, cycle detection, and an immutable ref for any remote
include, and would make requirement 5 mandatory rather than useful: with N
files, "what did this run execute" has no answer without the digest.
Until then, use the dashboard/API-managed objects and the current astro
pipeline run-management commands. Use astro workflow for the separate,
implemented workflow-definition TOML contract used to compose agent and human
stages.