Release flow
In classic CI, each commit spawns a throwaway pipeline. In Pipemesh, a pipeline is long-lived and revisions flow through it.
Pipelines and workflows
- A pipeline (under
pipemesh.pipelines) stays put. Each commit on the branch it watches becomes a revision that promotes job by job through the stages. Each job tracks the revision it last ran, so the board shows what's deployed right now, not whether commit X passed. - A workflow runs a whole DAG once per trigger (a pull request head,
a schedule, a tag, a manual click, or a
job_type: workflowjob). Each run,run #N, is one revision with one verdict.
Why Pipemesh explains when to use each.
Approval and promotion
Each pipeline job keeps an approval cursor: the highest revision it
vouches for, by running it or by a proven skip. A
job starts a revision only once every job in its needs: and
consumes: has approved it.
A failed job holds its cursor, and everything downstream holds too. Green means approved, never "didn't run".
A revision is a snapshot, not a delta: a docs-only commit on a broken build is held too, because it contains the broken code. Independent deliverables (own checkout, no dependency on the broken job) still flow. Revert or fix, and everything queued ships together.
Trustworthy skips
A job skips when its fingerprint matches its last successful run, not the previous commit. So a skip proves nothing it depends on changed since then, and it's safe to promote through.
- An unbuilt change is never skipped past. After a failed build, even an unrelated commit differs from the last success, so the job runs (and fails visibly) instead of deploying a broken pair.
- A job that has never succeeded never skips.
- A skipped job keeps showing the revision that actually ran.
- Narrow
checkout:to skip more. Withcheckout: true(a build's default), any changed file changes the fingerprint.
What's in a fingerprint
The job's definition (script, image, variables, outputs, components),
its parameters, the digests of what it consumes, the files it checks out
(plus those Pipemesh adds for its cache key and publish:), and the
versions, never the values, of the secrets and Settings variables it
declares.
Edit a job and it runs. Each job decides by its own fingerprint: it never skips just because the job before it did. When both skip, it's because the entry it consumes is part of its fingerprint.
Skip policies
skip: picks which earlier fingerprint lets a job skip. Each
job type has a default; a workflow job takes
built when all its jobs reuse, never otherwise.
skip: |
The job skips when its fingerprint matches… | Status | Default for |
|---|---|---|---|
unchanged |
its last successful run's | no changes | deploy, pipeline |
built |
an earlier successful run's, whose outputs it reuses | reused | build |
never |
nothing: it always runs | — | task |
unchangedfits side effects, like deploys. After a failed or cancelled run, the job runs in full until one succeeds (a successful rollback counts): an unfinished run's effects are unknown.builtfits builds, images and hermetic tests. Consumers see the reused run's outputs and digests. Only passes with unexpired artifacts are reused. Default-branch, tag and pipeline runs reuse only each other's; a pull request reuses those first, then its own, never another pull request's.neverfits jobs whose value is running now: smoke tests, notifications.
A revert shows the difference. After A → B → A, an
unchangeddeploy sees A differ from B and runs, putting A back. Abuiltbuild finds A in the store and reuses its outputs.
An example
jobs:
build:
job_type: build # skip: built
stage: build
checkout:
- services/api
script: services/api/build.sh
produces:
jar: dist/api.jar
deploy_staging:
job_type: deploy # skip: unchanged
production: false
stage: staging
consumes:
- build/jar
checkout:
- deploy
script: deploy/deploy.sh staging
smoke:
job_type: task # skip: never
stage: staging
needs:
- deploy_staging
checkout:
- smoke
script: smoke/run.sh https://staging.example.com
| The change touches | build | deploy_staging | smoke |
|---|---|---|---|
services/api/handler.go |
runs | runs (new jar) | runs |
a comment in handler.go |
runs | skips (same jar) | runs |
README.md |
reused | skips | runs |
deploy_staging reads only the jar and deploy/, so it skips an
identical jar even when the build ran. smoke, a task, runs on every
revision that reaches it.
Each job keeps its own memory. Revision 10 changes services/api/:
build passes, deploy_staging fails. On revision 11 (README.md
only), build is reused, but deploy_staging compares with revision 9,
its last success, and runs: revision 10's change still reaches staging.
Consolidation and supersede
When revisions queue behind a busy or failed job, it runs once, at the
newest eligible revision. That covers the ones in between, so
they're superseded; run history shows supersedes rM–rN. A failed
attempt is superseded as soon as a newer revision is fully eligible.
Retries and cancellation
- Auto-retry:
retry: Nretries a failed job in a workflow run up to N times (notjob_type: workflowjobs). Pipeline jobs never retry on their own. - By hand: a maintainer retries a failed pipeline job from its panel. A settled workflow run reopens with Retry (all failed jobs, or one), and so does GitHub's Re-run on a failed check. Jobs skipped downstream are decided again from the retry's outcome.
- Cancel a run, a revision, or one running job (from its log panel).
A
workfloworpipelinejob's child is cancelled with it.
Changing the definition
Safe changes apply live. Unsafe ones wait for in-flight work to finish, then switch. A new job starts at the current revision: history is never replayed through it.
A failed release doesn't block a change. Once nothing is moving or can start, the change applies and its release supersedes the failure, so a fix that is itself a definition change ships on its own.
Holding promotions by hand
A maintainer can Disable promotions… in a job's drawer, with a reason: to keep a load test on staging from being overwritten, say, or to hold production during an incident.
Revisions queue at the job's door and consolidate as usual. The board shows a red stop sign on the door and gated with the reason, and the catalog badges the pipeline GATED. Enable promotions lets them all flow at once.
Both are audited with who and when, and neither changes the definition.
Rolling back
Rollback… in a job's drawer re-deploys a revision this job already deployed successfully: only those have the artifacts it needs. It's for maintainers, and audited.
- Target: by default the last accepted revision (the success before the deployed one, or the last success if the latest attempt failed or was rejected). Any earlier success works: the dropdown has the last ten, the run history all of them.
- The preview names the target (revision, commit, message) and what runs: the definition version it deployed with (not the current one), its frozen inputs and artifacts, and its pinned variables and secrets. The runner image is resolved live, as for any run.
- Not offered when the target's artifacts are gone (expired, or a superseded build; the preview says which) or the job has a run in flight. From the run history, the confirmation re-checks and asks before cancelling a run in progress.
The job is at that revision again, as a new attempt marked rollback: its card shows the target's number and colour, with newer revisions ahead.
A rollback gates the job ("rolled back to revision 41 by …"). Newer revisions build and queue until someone enables promotions. Then the head flows: the fix if one landed, or the same revision again as a fresh attempt.
Release metrics
Each pipeline page has a health strip with the four DORA metrics over
7, 30 or 90 days, from runs it already stores: nothing to instrument.
Production means deploys with production: true.
| Metric | What it counts |
|---|---|
| Deploys | successful production runs, with the weekly rate and how many revisions reached production (a run that consolidates several counts each) |
| Lead time | median from commit to production (p90 in the tooltip) |
| Change failure | share of production runs that failed or were rejected |
| Time to restore | median from a failed production run to the next success |
Hover a tile for its numbers. GET /api/workloads/{id}/metrics?days=30
(API) returns them with a per-day series.