Skip to content

Delivery and GitOps

Everything that runs is declared in a repository, and everything declared is applied by a pipeline. There is no manual deploy path that is considered acceptable, and the emergency one that exists is defined as drift the next pipeline run will revert — which is the point: it forces the fix back into Git within the hour.

  • 97CI/CD pipelines, 76 of them deploying
  • 2,377deployments in 28 days
  • 6.3 minmedian commit to live
  • 5.2%of deployments fail

The delivery path, and where each gate sits

Section titled “The delivery path, and where each gate sits”
redgreenPost-deployAcceptance on the artifacta user receivesService test planPre-deploy gates, all fail-closedLint, syntax, schemaSecret scanArtifact assertionon the built bytesTrigger economybatch: truecoalesce a burstPath-scopedcross-repo triggersManifest short-circuitskip when output is identicalLocal, before pushEvery gate that readsthe changed pathsEnumerated from thepipeline definitionChange authoredin a worktreePush to mainBuild on theself-hosted poolDeployPrevious version stays liveChange is done
redgreenPost-deployAcceptance on the artifacta user receivesService test planPre-deploy gates, all fail-closedLint, syntax, schemaSecret scanArtifact assertionon the built bytesTrigger economybatch: truecoalesce a burstPath-scopedcross-repo triggersManifest short-circuitskip when output is identicalLocal, before pushEvery gate that readsthe changed pathsEnumerated from thepipeline definitionChange authoredin a worktreePush to mainBuild on theself-hosted poolDeployPrevious version stays liveChange is done

The pipeline is a confirmation, not a test loop

Section titled “The pipeline is a confirmation, not a test loop”

The self-hosted pool is finite and the runs take minutes, so “push and see if it goes red” is the most expensive way to discover a mistake. The working rule is to be near-certain before pushing: enumerate every gate that reads the paths in the diff from the pipeline definition, subtract what already ran, and run the remainder locally.

That phrasing is precise for a reason, and the reason is a specific failure that reads as success. A pre-push hook ran four real validators and passed. The push went out, and the shared documentation pipeline went red for half an hour — blocking every concurrent session’s publish — because the gate that fails on that file was not one of the four the hook runs.

The hook was not broken. It was a subset, and a passing check that is not the right check is indistinguishable from the right check passing. The question is never did my checks pass? but which gates read the files I changed, and have I run each one? Momentum is the tell: the push you feel surest about is the one you have checked least.

Gates ship with their own proof that they can fail

Section titled “Gates ship with their own proof that they can fail”

A gate is only worth having if it would actually go red on the breakage it exists to catch, and the only way to know that is to have seen it. So the house rule is that every build hook and pipeline gate carries a self-test that drives the real entry point over a poisoned fixture per pattern and asserts it reds, then over a clean set and asserts it greens — and CI runs the self-test immediately before running the gate.

That is ADR-0141, and it is the single highest-value convention in this estate. A gate proven only on clean input has never been observed to work; its silence carries no information at all.

The companion rule closes the other half: a gate that aggregates over a discovered set must distinguish found nothing from everything passed. Both produce zero findings and one of them is a green light with nothing behind it — ADR-0382.

Trigger economy is a real cost, and it is managed as one

Section titled “Trigger economy is a real cost, and it is managed as one”

Ninety-nine pipelines on a two-runner pool means trigger design is capacity design. Three mechanisms, each with a stated trade-off:

MechanismWhat it buysWhat it costs
batch: true on every pipelineA burst of pushes coalesces into one run. Safe because every deploy here is a full, idempotent, last-write-wins applyA run may cover several commits, so attributing a red to one commit needs the run’s own log
Path-scoped cross-repo triggersA change in the documentation corpus publishes the site without anyone running anything by handA path filter cannot express the page carried the publish flag, so it over-triggers
A manifest short-circuitThe over-triggering is absorbed: the run compares what it would publish against what the live site is already serving, and skips when they are byte-identicalIt must fail open — any doubt at all builds — because a wrong skip is a site that silently stops updating

That is ADR-0311 and ADR-0054. The measured number behind it: three hundred and thirty commits touched the documentation tree in one seven-day window. Unfiltered, that is hours of pool time publishing nothing.

The runners are cattle, and a parity gate keeps them that way

Section titled “The runners are cattle, and a parity gate keeps them that way”

The CI pool is two interchangeable agents built from one role. The moment they are nearly identical, every pipeline becomes a coin flip — a gate present on one agent and absent on the other is a red that reproduces half the time. So a parity gate asserts two distinct properties, because they fail differently: conformance (each runner matches its declaration) and parity (each runner matches the other, including on things the declaration does not pin).

It also catches a third shape both of those are blind to: two places in the repository pinning the same tool, where the runners agree with each other perfectly and are simply alternately wrong.

And unreachable is not a pass. A runner that cannot be inspected exits unverified, never zero. “I could not look” and “I looked and it was fine” must never share an exit code. The full design is in the CI runner pool case study; the portability rule that stops a pipeline being pinned to one agent is ADR-0337.

Delivery performance, measured rather than asserted

Section titled “Delivery performance, measured rather than asserted”

Delivery performance — the four DORA keys, 27.64 days

Snapshot taken — build-time, not live

Deployment frequency

81.3

successful deployments per day

Eliten = 2,248

How this is derived

Successful runs of deploy-class pipelines, divided by the observed window. Deploy-class is imported from the estate's own pipeline-metrics collector, not restated here: pipelines that put something on a host, with guard, lint and validation pipelines excluded.

Lead time for changes

6.3

minutes, median (p90 20.6)

Eliten = 2,248

How this is derived

CI trigger to deployment complete, for successful CI-triggered deploy-class runs. The trigger fires within seconds of the push, so this is commit-to-live to within webhook latency. It is stated as that rather than dressed up as exact: resolving each commit's author date misreports a rebase or an old branch merge far more badly than this does.

Change failure rate

5.2

% of deployments that failed

Highn = 2,372

How this is derived

Deploy-class runs that ended `failed`, over those that ended `failed` or `succeeded`. This is the rate at which a change failed to reach production, which is not identical to DORA's 'caused a degradation in service' — a red gate usually stops the change before it lands. It is reported as what it measures rather than relabelled as what it resembles.

Failed-deployment recovery time

26.8

minutes, median (p90 403.4)

Eliten = 92

How this is derived

For each failed deploy-class run, the wall-clock until that same pipeline next succeeded. Failures still unrecovered at the end of the window are counted separately (2 in this window) and are not folded in as zero.

Computed from 4,669 completed Azure DevOps pipeline runs over the 28-day window ending , of which 2,377 were deploy-class. The deploy classification is not restated here — it is imported from ado_pipeline_metrics.py (ADR-0331), the estate's single pipeline-metrics collector, so the public figures and the internal queue-time SLO can never quietly disagree about which pipelines count as a deployment. Elite / High / Medium / Low are the DORA State of DevOps report's own thresholds, not thresholds derived from this estate. 2 failures were still unrecovered at the end of the window and are excluded from the recovery-time median rather than folded in as zero.