Files
self-serve/README.md
T
2026-09-28 14:52:12 -04:00

86 lines
3.4 KiB
Markdown

# SelfServe
Decide whether self-service tenant provisioning is safe to run, and whether
the job queue running it is healthy.
SelfServe does not provision anything. It is the gate around a provisioning
worker you already have, for example one that claims jobs with
`FOR UPDATE SKIP LOCKED`, renders a per-tenant stack, starts it and wires
DNS. Reimplementing that worker would be duplication. The missing half is the
part that decides whether to let it run, which is the part that keeps
self-service from turning into an outage.
It is pure, with no clock and no I/O. `now` is a parameter.
## What it checks and why
1. Dormant is not proven. Built, enabled and proven in production are three
separate states. A worker that is switched off is NOT ASSESSED, never
ready, and an enabled worker that has never completed a real tenant gets
its own finding. Until one tenant has gone end to end, the first customer
is the integration test.
2. Self-service with no ceiling is a denial of service you run against
yourself. If every tenant is a stack on one host, a signup loop with no
quota fills that host, and a full disk on a shared host wedges every
container, not only the new one. Declaring no limits at all is a critical
failure: a quota that was never set was never considered.
`preflight()` also checks the host ceiling, the per-account ceiling and
the free disk left after provisioning. Unmeasured disk, or a stack with no
size estimate, is NOT ASSESSED.
3. A claimed job looks like progress. With `SKIP LOCKED` claiming, a worker
that dies mid-job leaves the job claimed forever. It is not queued, not
failed, and nothing retries it, so no dashboard alarms on it.
`stuckJobs()` finds jobs claimed for longer than a window (15 minutes by
default).
4. Provisioned is not reachable. DNS wired and the stack up is not the same
as the tenant's URL serving. Without a post-provision probe, the first
person to discover a broken tenant is the tenant.
## Usage
```ts
import { assessService, formatService, preflight, stuckJobs, type Job } from '@extant2000/self-serve'
const jobs: Job[] = [
{ id: 'j7', state: 'claimed', claimedAt: '2026-09-28T11:00:00Z', claimedBy: 'worker-1', where: 'jobs table' },
{ id: 'j8', state: 'queued', where: 'jobs table' },
]
const report = assessService(
{ workerEnabled: true, provenInProduction: false, verifiesReachability: true },
{
now: '2026-09-28T12:00:00Z',
request: { account: 'acme', tenantName: 'acme-prod', existingForAccount: 1, estimatedDiskBytes: 2e9, where: 'jobs table, j8' },
host: { tenants: 10, freeDiskBytes: null, where: 'df -P /data' },
limits: { maxTenants: 50, maxPerAccount: 3, minFreeDiskBytes: 10e9 },
jobs,
},
)
for (const f of report.findings) console.log(`${f.determination.padEnd(13)} ${f.id}: ${f.summary}`)
```
`assessService()` returns a `CapabilityReport` from
`@extant2000/evidence-record`, and `formatService()` renders it for a
terminal or CI log. `preflight()` and `stuckJobs()` can be used on their own.
## Sample output
Produced by running the example above:
```
not-assessed worker/unproven: The worker is enabled but has never completed a real tenant
not-assessed capacity/disk: Disk headroom after provisioning cannot be computed
fail job/j7: job j7 has been claimed by worker-1 since 2026-09-28T11:00:00Z
```
## Install
```
npm install @extant2000/self-serve
```
## License
MIT. Copyright (c) 2026 Extant 2000 LLC. See [LICENSE](LICENSE).