2026-09-28 14:49:02 -04:00
2026-09-28 14:49:02 -04:00
2026-09-28 14:49:02 -04:00
2026-09-28 14:49:02 -04:00
2026-09-28 14:49:02 -04:00
2026-09-28 14:49:02 -04:00
2026-09-28 14:49:02 -04:00
2026-09-28 14:53:50 -04:00
2026-09-28 14:49:02 -04:00
2026-09-28 14:49:02 -04:00
2026-09-28 14:49:02 -04:00
2026-09-28 14:49:02 -04:00

RolloutRail

Verify a deployment target by target, by the artifact each one is actually serving.

RolloutRail takes what you observed about each target in a rollout and decides whether the deployment can be evidenced. It deploys nothing. Its only runtime dependency is @extant2000/evidence-record.

What it checks and why

Three common traps share one shape: a signal that is easy to collect stands in for the one that matters.

  1. A green pipeline is not a deployed artifact. Suppose an unpinned linter starts failing, the deploy job's dependency is no longer satisfied, and the deploy job is skipped. A skipped job does not fail a pipeline, so the pipeline stays green while nothing ships. The job list is the only place that shows it, so jobsRun is a first-class field. A successful pipeline whose deploy job is missing from jobsRun is a critical failure.
  2. A 200 is not your build. The old container answering healthily returns the same 200 as the new one, and a loose smoke test can even pass on HTTP 500. Only the served artifact (a version, a commit SHA, a build id) tells them apart.
  3. An untouched target reports its previous success. In a staged rollout, pipeline status for a repo that has not been merged yet returns the pre-rollout run, which is green. A monitor that polls status reports "all green" while half the targets have not started. started: false reports NOT ASSESSED.

The only pass is an artifact-probe whose observed value matches expected. Everything else is NOT ASSESSED and names the weaker signal it settled for (pipeline-status, http-status or none). A target that is up, healthy and serving the previous build is a critical failure.

safeToProceed() gates the next wave of a staged rollout, and it is strict: NOT ASSESSED blocks exactly as a failure does. The next wave depends on the previous one having worked, and "we did not check" is not evidence that it did.

Usage

import { verifyRollout, safeToProceed, formatRollout, type Target } from '@extant2000/rollout-rail'

const targets: Target[] = [
  { name: 'api', expected: 'a1b2c3d', observed: 'a1b2c3d', via: 'artifact-probe', where: 'https://api.example.com/__version' },
  { name: 'billing', expected: 'a1b2c3d', observed: '9f8e7d6', via: 'artifact-probe', where: 'https://billing.example.com/__version' },
  { name: 'web', expected: 'a1b2c3d', observed: 'a1b2c3d', via: 'artifact-probe', where: 'https://web.example.com/__version',
    pipelineStatus: 'success', jobsRun: ['build', 'test', 'image-lint'] },
  { name: 'worker', expected: 'a1b2c3d', observed: 'up', via: 'http-status', httpStatus: 200, where: 'https://worker.example.com/healthz' },
  { name: 'search', expected: 'a1b2c3d', observed: 'success', via: 'pipeline-status', pipelineStatus: 'success', where: 'pipeline 41', started: false },
]

const { ok, reason } = safeToProceed(targets)
console.log(formatRollout(targets))

verifyRollout() returns a CapabilityReport from @extant2000/evidence-record. If your deploy job has another name, pass { deployJob: 'ship' }.

Sample output

Produced by running the example above. safeToProceed() returns ok: false with the reason 2 target(s) failed: web/skipped-deploy, billing. The report, without its title line:

  NOT MET  ·  5 items examined  ·  6 findings
  2 of 5 target(s) confirmed serving the intended artifact by independent probe

✗ CRITICAL billing is serving 9f8e7d6, expected a1b2c3d   [NOT MET]
             The target is up, healthy, and running the previous build. This is the failure a status check cannot see.
             evidence: https://billing.example.com/__version
✗ CRITICAL web: pipeline is green and "deploy" never ran   [NOT MET]
             A skipped job does not fail a pipeline. Jobs that ran: build, test, image-lint. Nothing shipped, and the only place that is visible is the job list.
             evidence: $ pipeline jobs for web → exit 0
✗ HIGH     worker: answered HTTP 200, artifact not checked   [NOT ASSESSED]
             The previous build returns the same status as the new one. A response code identifies neither the version nor, with a loose smoke test, even the health: a check that accepts any response passes on HTTP 500.
             evidence: https://worker.example.com/healthz
· MEDIUM   search has not started   [NOT ASSESSED]
             Any status read from this target right now describes the previous rollout, not this one. It is green because nothing has happened yet.
             evidence: pipeline 41
◦ info     api is serving a1b2c3d   [MET]
             evidence: https://api.example.com/__version
◦ info     web is serving a1b2c3d   [MET]
             evidence: https://web.example.com/__version

Install

npm install @extant2000/rollout-rail

License

MIT. Copyright (c) 2026 Extant 2000 LLC. See LICENSE.

S
Description
Checks that each target in a rollout is serving the new build.
Readme MIT
50 KiB
Languages
TypeScript 100%