Files
rollout-rail/README.md
T
Paul HittandClaude Opus 5.5 3be766f112 Publish to the GitLab package registry on version tags
The README's install line failed: the package was never published, and
nothing told npm where the @extant2000 scope lives. A tag like v1.2.3 now
publishes to this project's registry with CI_JOB_TOKEN, and the README
sets the scope's registry before installing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 16:42:25 -04:00

104 lines
5.0 KiB
Markdown

# 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
The package is published to the GitLab package registry, so point the
`@extant2000` scope there first:
```
npm config set @extant2000:registry https://gitlab.com/api/v4/packages/npm/
npm install @extant2000/rollout-rail
```
## License
MIT. Copyright (c) 2026 Extant 2000 LLC. See [LICENSE](LICENSE).