106 lines
4.1 KiB
Markdown
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).
|