First public release
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
# 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).
|
||||
Reference in New Issue
Block a user