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>
104 lines
5.0 KiB
Markdown
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).
|