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