.ankra/pipeline.yaml is the one file an Ankra pipeline is planned from. It sits beside .ankra/ankra.yaml in the repository, and its parser, validator and planner live in exactly one place on the platform, so the dry run you get from ankra pipeline validate is the run.
The document
- Unknown keys are ignored. A single scalar stands in for a one-element list (
branches: main), a quoted number reads as a number (retention_days: "30"), and a flag acceptstrue/false,yes/no,on/offand1/0. YAML anchors are resolved, so an anchoredservicesordefaultsblock behaves like an inline one. - An empty document, a document that is not YAML, an
apiVersionother thanankra.io/v1, akindother thanPipeline, or astagesblock that is not a list is fatal: the pipeline cannot be planned. metadata.nameand at least one stage are required.
.ankra/manifests/ is refused by the manifest gate, because the file carries a kind and an apiVersion and would otherwise be applied to a cluster.
Top-level keys
Stage names must be unique across
stages, on_failure and finally, because the three share one run and one output namespace.
Triggers
A manual input has a
name (letters, digits and underscores, so it can be addressed as ${{ inputs.<name> }}), a type (string, the default, boolean, number or choice), an optional default, an enum (required for choice, and the default must be one of its values), required and description. A dispatch whose inputs do not match is refused with the planner’s diagnostics and records nothing.
A path filter is decided on the event’s changed files, and never excludes a run when the diff could not be read or was truncated - a file the planner could not see is not a file that did not change; the run records that it passed on incomplete information.
Stages
Every stage has aname (lower-case letters, digits, dashes and underscores, starting with a letter or digit - it becomes an expression key, a Job name component and a check-run title) and a kind. The kinds are a closed vocabulary; an unknown kind is fatal rather than a stage the planner skips.
A stage of a platform-settled kind is left exactly as the dispatcher found it; a run containing one does not conclude until that kind’s evaluator ships, and
ankra pipeline cancel is how to release its concurrency group in the meantime.
Fields common to every stage
Kind-specific blocks
What every step carries
A step’s container mounts the run’s workspace at/workspace and runs with these variables set: ANKRA_RUN_ID, ANKRA_STEP_ID, ANKRA_STEP_KEY, ANKRA_STEP_KIND, ANKRA_ORGANISATION_ID, ANKRA_CLUSTER_ID, ANKRA_REPOSITORY_ID, ANKRA_APPLICATION_ID, ANKRA_HEAD_SHA, ANKRA_REF, ANKRA_IS_FORK and ANKRA_WORKSPACE. ANKRA_OUTPUT names a file on a memory-backed volume: append key=value lines to it (at most 64 KiB) and they are recorded as the step’s outputs when the script exits, whether it succeeded or not. Declared secrets are delivered as files under /run/agent-secrets or as environment variables once secret delivery ships.
Expressions
Any string field may embed expressions between${{ and }}; the text inside is CEL. Evaluation is deterministic and performs no I/O: everything an expression can reach arrives in its context, and hashFiles is served by the agent, which has the workspace.
Functions:
hashFiles(pattern, …), contains(haystack, needle) (substring or list membership), startsWith, endsWith, fromJSON, toJSON (compact, keys sorted), format(template, args…) with GitHub-style {0} placeholders, join(list) and join(list, separator), and the status functions always(), success(), failure(), cancelled() and skipped(). The CEL standard library - size, matches, the bounded all/exists/map/filter macros, arithmetic and comparisons - is available on top.
Rules worth knowing:
- A stage condition is compiled at plan time against a schema without
steps.*, so a reference to a step that has not run is reported as a diagnostic rather than resolving to nothing; the dispatcher evaluates the same definition with the concluded steps in scope. has()is refused oninputs,matrix,vars,env,steps,needsandsecrets: an absent key on those roots resolves to the zero value, so a convertedenv.DEPLOY == 'true'keeps its meaning when the variable is unset. Compare the value instead.has()still works onankraandevent, whose paths are fixed.- Converted GitHub expressions keep working - single-quoted strings,
==,!=,&&,||,!, the functions above, and thesteps,matrix,inputs,envandneedsreferences map one to one. A reference to thegithub,runner,joborstrategycontext is a diagnostic naming the Ankra equivalent (github.shaisankra.sha,github.event_nameisevent.kind). Two deliberate departures:toJSONemits compact JSON, and comparisons follow the typed rules of CEL, so'1' == 1is a type error rather than true. - A template is at most 8,192 bytes with at most 64 expressions; an expression is at most 2,048 bytes and 24 levels deep; the evaluation cost is capped. Each bound is refused with a fixed sentence at validation time.
Triggers and reasons
Every event that reaches the trigger is decided in order, and the answer is recorded as one of these tokens. Where a run row is written,ankra pipeline list shows it; where none is, the reason is in the platform’s logs and metrics.
Dispatches (
manual, api, agent, rerun) are exempt from the recent-run deduplication: a person who presses run twice asked for two runs.
Inside a run, a stage that is not planned carries a skip reason: event_filter, branch_filter, path_filter, schedule_subset, condition_false, condition_unreadable (a condition nobody can read is not a condition that passed), fork_policy or dependency_skipped. A run refused as a whole carries one fixed sentence, for example “Every stage this pipeline declares is filtered out for this event, so the run would do nothing.” or “A stage fans out over more matrix legs than the number one run may plan.”
Validation
.ankra/pipeline.yaml (or the file you name), validates it, and plans it for a synthetic push to the default branch and a synthetic pull request, printing for each the steps that would run, the stages that would be skipped with their reason, and every diagnostic. Its exit code is non-zero when the severity is fatal. Without a file and without a stored definition there is nothing to validate. The same validator answers POST …/pipeline/validate on the API and the cicd_validate_pipeline_yaml tool in chat, which needs no repository at all.
A result has a severity - ok, warn (the pipeline runs, but not the way its author probably meant; reported on the pull request, never blocking) or fatal (the pipeline cannot be planned) - and a list of violations, each prefixed with a fixed key:
The planner adds diagnostics with
plan_ keys (plan_trigger:, plan_paths:, plan_matrix:, plan_condition:, plan_fork:, plan_concurrency:, plan_interpolation:, plan_timeout:, plan_needs:, plan_spec:), and the step renderer records what it decided on incomplete information: image_policy_open when the organisation set no image policy, resources_defaulted, service_without_readiness, service_undeclared, build_without_registry_auth, registry_push_withheld, build_namespace_workspace and multi_platform_build. A step the renderer refuses outright - an image outside the policy, more compute than one step may have, a missing timeout, a services tier without services, host privilege - is a fixed sentence naming the field, and the same refusal at dispatch time is what a step that never started shows.
A complete example
This is the pipeline Ankra’s own documentation repository is written against - a bare repository with nothing to build, which is the case that proves pipelines are repository-scoped with an optional application link.run stages execute in the cluster; checkout and the verify probe are dispatched but wait on runner images that have not shipped; the agent triage and the webhook notification are platform-settled kinds that stay pending. The schedule is stored but not fired. The definition validates as ok today, which is the point of the closed vocabulary: the file describes the pipeline the platform will run, and says nothing it will not do.
Related
Ankra Pipelines
How a run happens, the security model, failure handling and limits.
Migrate from GitHub Actions
Convert an existing definition and review the notes.