# 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 ```ts 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](LICENSE).