Files
kill-switch/README.md
T
2026-09-28 14:49:01 -04:00

106 lines
4.1 KiB
Markdown

# KillSwitch
Checks that a terminated deployment is actually gone, resource class by
resource class, including the billing that tends to outlive it.
## What it does and why
Deleting is the easy half. The hard half is proving nothing remains, and the
two failures that matter are both invisible to the delete path itself.
### Billing outliving compute
The servers stop, the DNS record goes, and the subscription keeps charging.
Each half looks correct on its own: the teardown succeeded, and the
subscription is in a normal active state. Only the combination is wrong, and
the customer is the one who finds out. No per-resource check can see this,
so KillSwitch reports it as a separate critical finding and prints it first.
### A delete response taken as a verification
A `DELETE` that returns 200 means the API accepted the request. It does not
mean the resource is gone. A teardown whose only evidence is its own success
response has verified nothing, so an `absent` state backed only by
`delete-response` is reported as not assessed. That is the difference
between terminated and verifiably terminated.
KillSwitch never deletes anything. It takes an inventory you enumerated and
decides whether the removal can be evidenced. There is no network access and
no cloud SDK.
| `state` and `verifiedBy` | Result |
|---|---|
| `absent`, `independent-probe` | Met. The only real pass. |
| `absent`, `delete-response` | Not assessed. The API accepted a request. |
| `absent`, `assumed` or unset | Not assessed. An assumption is not a verification. |
| `unknown` | Not assessed. Nobody looked. |
| `present` with `retainedBy` | Not applicable, and still printed. |
| `present` | Not met. This is residue. |
Surviving `billing`, `secret` and `database` resources are critical, because
they cost money or can still authenticate. Other classes are high.
## Retention is an outcome, not a leak
A backup kept after termination under a stated policy is correct behaviour.
Reporting it as residue would teach people to ignore the report. Setting
`retainedBy` makes it not applicable, but it is still printed, because a
retention nobody can see looks the same as a leak.
## Coverage gaps
`coverageGaps()` lists the resource classes the teardown never mentioned. It
answers a different question from "is this resource gone". A checklist that
never mentions billing passes every check it makes. `formatTermination()`
adds these gaps to the report as a NOT COVERED note.
## Usage
```ts
import { verifyTermination, formatTermination, coverageGaps, type Termination } from '@extant2000/kill-switch'
const termination: Termination = {
target: 'customer example-co',
resources: [
{ id: 'web-1', class: 'compute', where: 'cloud console: instances',
state: 'absent', verifiedBy: 'independent-probe' },
{ id: 'sub_123', class: 'billing', where: 'billing API: subscriptions',
state: 'present', verifiedBy: 'independent-probe' },
{ id: 'data-vol', class: 'storage', where: 'DELETE /volumes/data-vol',
state: 'absent', verifiedBy: 'delete-response' },
{ id: 'nightly-backup', class: 'backup', where: 's3://backups/example-co',
state: 'present', retainedBy: '90-day retention, contractual' },
],
}
const report = verifyTermination(termination) // evidence-record report
const gaps = coverageGaps(termination) // ['dns', 'route', 'database', 'mailbox', 'secret']
console.log(formatTermination(termination))
```
Output of the example above, with the header, notes and detail lines
omitted:
```
NOT MET · 4 items examined · 5 findings
✗ CRITICAL Compute is gone and billing is still active [NOT MET]
✗ CRITICAL billing sub_123 still present after termination [NOT MET]
· MEDIUM storage data-vol reported absent by the delete call itself [NOT ASSESSED]
◦ info compute web-1 confirmed gone by independent probe [MET]
```
Resource classes are `compute`, `storage`, `dns`, `route`, `database`,
`billing`, `mailbox`, `secret` and `backup`. An empty inventory is reported
as not assessed: an unenumerated resource is not a deleted one.
## Install
```
npm install @extant2000/kill-switch
```
## License
MIT. See [LICENSE](LICENSE).