Files
kill-switch/README.md
T
Paul HittandClaude Opus 5.5 4c5158595e Publish to the GitLab package registry on version tags
The README's install line failed: the package was never published, and
nothing told npm where the @extant2000 scope lives. A tag like v1.2.3 now
publishes to this project's registry with CI_JOB_TOKEN, and the README
sets the scope's registry before installing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 16:41:59 -04:00

108 lines
4.2 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 straightforward. Proving that nothing remains is harder, 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.
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
The package is published to the GitLab package registry, so point the
`@extant2000` scope there first:
```
npm config set @extant2000:registry https://gitlab.com/api/v4/packages/npm/
npm install @extant2000/kill-switch
```
## License
MIT. See [LICENSE](LICENSE).