89 lines
3.6 KiB
Markdown
89 lines
3.6 KiB
Markdown
# QuietDeploy
|
|
|
|
Check a deployment plan against a network-signature budget before it runs.
|
|
|
|
QuietDeploy takes the steps of a rollout (what each one contacts, how many
|
|
bytes it moves, for how long, with how many retries) and checks them against
|
|
a total-volume budget, a peak-rate ceiling and a destination allowlist. It is
|
|
pure, with no network and no clock. It sends nothing.
|
|
|
|
## What it checks and why
|
|
|
|
A deployment is observable. On a constrained or monitored link, the volume,
|
|
the destinations and the timing of what a rollout pulls are a signal in
|
|
themselves. The failures tend to be the same three.
|
|
|
|
1. The pull nobody counted. A build that "just pulls a base image" can move
|
|
hundreds of megabytes to a third-party registry. It does not show up in
|
|
the plan because nobody wrote it as a step; it is a side effect of one. A
|
|
step with `bytes: null` is NOT ASSESSED, never zero.
|
|
2. Egress outside the declared set. An allowlist exists because a deploy
|
|
that reaches an unexpected host is either a supply-chain problem or a
|
|
leak. That check runs first and stays critical however small the transfer
|
|
is. Volume is irrelevant to it.
|
|
3. A burst that fits the total and not any window. Ten targets pulling 200 MB
|
|
each inside a minute is a different signature from the same traffic over
|
|
an hour, and a total-only budget passes both. Peak rate is checked on its
|
|
own and multiplied by `concurrency`.
|
|
|
|
Retries are cost. `worstCaseBytes()` counts every attempt, because a step
|
|
budgeted at one attempt is budgeted at its best case. A plan that fits on the
|
|
first try and not with retries fails.
|
|
|
|
When any step is unmeasured, the total is reported as a lower bound, not a
|
|
total. A partial sum presented as a total is how a budget check reassures
|
|
without checking. The total still fails if the known bytes alone exceed the
|
|
budget, since unmeasured steps cannot rescue a plan that is already over.
|
|
|
|
## Usage
|
|
|
|
```ts
|
|
import { checkPlan, formatPlan, type DeployStep, type SignatureBudget } from '@extant2000/quiet-deploy'
|
|
|
|
const steps: DeployStep[] = [
|
|
{ name: 'pull-base-image', destination: 'registry.example.com', bytes: 200_000_000, durationSeconds: 60, retries: 2, where: 'ci.yml' },
|
|
{ name: 'fetch-cli', destination: 'downloads.example.net', bytes: 5_000_000, durationSeconds: 10, where: 'scripts/setup.sh' },
|
|
{ name: 'push-app-image', destination: 'registry.example.com', bytes: null, where: 'ci.yml' },
|
|
]
|
|
|
|
const budget: SignatureBudget = {
|
|
maxTotalBytes: 2e9,
|
|
maxBytesPerSecond: 5e6,
|
|
allowedDestinations: ['registry.example.com'],
|
|
concurrency: 4,
|
|
}
|
|
|
|
const report = checkPlan(steps, budget)
|
|
for (const f of report.findings) console.log(`${f.determination.padEnd(13)} ${f.id}: ${f.summary}`)
|
|
|
|
// The full evidence-record report for a terminal or CI log
|
|
console.log(formatPlan(steps, budget))
|
|
```
|
|
|
|
`checkPlan()` returns a `CapabilityReport` from `@extant2000/evidence-record`.
|
|
`worstCaseBytes()`, `peakRate()` and `formatBytes()` are exported as well.
|
|
|
|
## Sample output
|
|
|
|
Produced by running the example above (the loop over findings):
|
|
|
|
```
|
|
not-assessed total: 405.0 MB of 2.0 GB accounted for, with 1 step(s) unmeasured
|
|
fail pull-base-image: pull-base-image peaks at 13.3 MB/s
|
|
fail fetch-cli: fetch-cli contacts downloads.example.net, which is not in the declared set
|
|
not-assessed push-app-image: push-app-image moves an unknown volume to registry.example.com
|
|
```
|
|
|
|
The 405 MB total counts both attempts of the base-image pull. The pull fails
|
|
the rate check only because four run at once.
|
|
|
|
## Install
|
|
|
|
```
|
|
npm install @extant2000/quiet-deploy
|
|
```
|
|
|
|
## License
|
|
|
|
MIT. Copyright (c) 2026 Extant 2000 LLC. See [LICENSE](LICENSE).
|