First public release
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,12 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
@@ -0,0 +1,9 @@
|
||||
node_modules/
|
||||
dist/
|
||||
*.log
|
||||
.DS_Store
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
.npmrc
|
||||
dist-cjs/
|
||||
@@ -0,0 +1,6 @@
|
||||
stages: [test]
|
||||
|
||||
test:
|
||||
stage: test
|
||||
image: node:22-alpine
|
||||
script: [npm ci, npm run build, npm test]
|
||||
@@ -0,0 +1,16 @@
|
||||
# Changelog
|
||||
|
||||
## 0.1.0 - 2026-09-28
|
||||
|
||||
First public release under MIT.
|
||||
|
||||
- `assessService()` and `formatService()` assess whether self-service
|
||||
provisioning is safe to run. The library gates provisioning; it does not
|
||||
perform it.
|
||||
- A disabled worker is NOT ASSESSED, and an enabled worker that has never
|
||||
completed a real tenant gets its own finding.
|
||||
- `preflight()` fails when no capacity limits are declared, and checks the
|
||||
host ceiling, the per-account ceiling and a post-provision disk floor.
|
||||
Unmeasured disk is NOT ASSESSED.
|
||||
- `stuckJobs()` finds jobs claimed past a time window.
|
||||
- Unverified reachability is NOT ASSESSED.
|
||||
@@ -0,0 +1,21 @@
|
||||
# Contributing
|
||||
|
||||
Issues and merge requests are welcome at
|
||||
https://gitlab.com/extant2000/self-serve.
|
||||
|
||||
## Ground rules
|
||||
|
||||
- A test that cannot fail proves nothing. If you fix a bug, add a test and
|
||||
check that it fails against the unfixed code before you submit.
|
||||
- Measure, don't assume. Two modules with the same line count can still be
|
||||
different programs.
|
||||
- Keep dependencies minimal. Every new runtime dependency needs a reason.
|
||||
- Explain why in comments, not what. The what is already in the code.
|
||||
- A breaking change needs a major version bump and a note in CHANGELOG.md.
|
||||
|
||||
## Before you open a merge request
|
||||
|
||||
Run both of these and make sure they pass:
|
||||
|
||||
npm run build
|
||||
npm test
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2026 Extant 2000 LLC
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,86 @@
|
||||
# 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 is not unlimited, it is unconsidered.
|
||||
`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. An unmeasured resource is not a plentiful
|
||||
one.
|
||||
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).
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
# Security policy
|
||||
|
||||
## Reporting a vulnerability
|
||||
|
||||
Please report vulnerabilities privately by email to security@extant2000.com.
|
||||
Do not open a public issue or merge request for a security problem.
|
||||
|
||||
Include the affected version, a description of the issue, and steps or a test
|
||||
that reproduce it if you have them.
|
||||
|
||||
We aim to acknowledge every report within 5 business days.
|
||||
Generated
+1504
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"name": "@extant2000/self-serve",
|
||||
"version": "0.1.0",
|
||||
"private": false,
|
||||
"description": "Partner-initiated provisioning with no engineer in the loop.",
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
"main": "./dist-cjs/index.js",
|
||||
"files": [
|
||||
"dist",
|
||||
"dist-cjs",
|
||||
"src",
|
||||
"README.md",
|
||||
"LICENSE"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://gitlab.com/extant2000/self-serve.git"
|
||||
},
|
||||
"module": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"import": "./dist/index.js",
|
||||
"require": "./dist-cjs/index.js"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc && tsc -p tsconfig.cjs.json && node -e \"require('fs').writeFileSync('dist-cjs/package.json', JSON.stringify({type:'commonjs'}))\"",
|
||||
"test": "vitest run",
|
||||
"prepublishOnly": "npm run build"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5.6.0",
|
||||
"vitest": "^2.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@extant2000/evidence-record": "^0.1.2"
|
||||
},
|
||||
"author": "Extant 2000 LLC",
|
||||
"homepage": "https://gitlab.com/extant2000/self-serve",
|
||||
"bugs": {
|
||||
"url": "https://gitlab.com/extant2000/self-serve/-/issues"
|
||||
}
|
||||
}
|
||||
+282
@@ -0,0 +1,282 @@
|
||||
/**
|
||||
* SelfServe — decide whether partner-initiated provisioning is safe to run,
|
||||
* and whether the queue running it is actually healthy.
|
||||
*
|
||||
* **This does not provision anything.** It assumes a provisioning worker
|
||||
* that already exists (one that claims jobs via `FOR UPDATE SKIP LOCKED`,
|
||||
* renders a per-tenant compose stack, brings it up and wires DNS).
|
||||
* Reimplementing that here would be duplication. What is missing is the half that decides whether to let it
|
||||
* run — which is the half that keeps self-service from being an outage.
|
||||
*
|
||||
* Four checks, each from a property of that kind of deployment:
|
||||
*
|
||||
* 1. **Dormant is not proven.** The worker ships behind
|
||||
* `DISABLE_PROVISION_WORKER=1`. Built, enabled, and proven in production
|
||||
* are three states, and reporting a dormant capability as ready is how
|
||||
* the first real tenant becomes the test.
|
||||
*
|
||||
* 2. **Self-serve without a ceiling is a denial of service you run against
|
||||
* yourself.** Every tenant is a compose stack on one host. A signup loop
|
||||
* with no quota fills the host, and on a shared host a full disk wedges
|
||||
* every container, not only the new one.
|
||||
*
|
||||
* 3. **A claimed job looks like progress.** `SKIP LOCKED` means a worker
|
||||
* that dies mid-job leaves that job claimed forever. Not queued, not
|
||||
* failed, nothing retrying it — the one state no dashboard alarms on.
|
||||
*
|
||||
* 4. **Provisioned is not reachable.** DNS wired and the stack up is not
|
||||
* the same as the tenant's URL serving.
|
||||
*
|
||||
* Pure: no clock, no I/O. `now` is a parameter.
|
||||
*/
|
||||
|
||||
import {
|
||||
type CapabilityReport,
|
||||
type Finding,
|
||||
type FormatOptions,
|
||||
evidence,
|
||||
formatReport as renderReport,
|
||||
} from '@extant2000/evidence-record'
|
||||
|
||||
export interface CapacityLimits {
|
||||
/** Hard ceiling on tenants for this host. */
|
||||
maxTenants?: number
|
||||
/** Ceiling per account, so one account cannot take the host. */
|
||||
maxPerAccount?: number
|
||||
/** Free disk that must REMAIN after provisioning. */
|
||||
minFreeDiskBytes?: number
|
||||
}
|
||||
|
||||
export interface HostState {
|
||||
tenants: number
|
||||
/** `null` when not measured — never treated as plentiful. */
|
||||
freeDiskBytes: number | null
|
||||
/** Where these readings came from. */
|
||||
where: string
|
||||
}
|
||||
|
||||
export interface ProvisioningRequest {
|
||||
account: string
|
||||
tenantName: string
|
||||
/** Tenants this account already has. */
|
||||
existingForAccount: number
|
||||
/** Estimated footprint of the new stack. `null` when unknown. */
|
||||
estimatedDiskBytes: number | null
|
||||
where: string
|
||||
}
|
||||
|
||||
export type JobState = 'queued' | 'claimed' | 'done' | 'failed'
|
||||
|
||||
export interface Job {
|
||||
id: string
|
||||
state: JobState
|
||||
/** ISO 8601, when a worker claimed it. */
|
||||
claimedAt?: string
|
||||
claimedBy?: string
|
||||
where: string
|
||||
}
|
||||
|
||||
export interface ServiceState {
|
||||
/** False when the worker ships dormant behind a kill flag. */
|
||||
workerEnabled: boolean
|
||||
/** True once a real tenant has gone end to end in production. */
|
||||
provenInProduction?: boolean
|
||||
/** Seconds a job may stay claimed before it is presumed abandoned. */
|
||||
maxClaimSeconds?: number
|
||||
/** Whether provisioned tenants are verified reachable afterwards. */
|
||||
verifiesReachability?: boolean
|
||||
}
|
||||
|
||||
const DEFAULT_MAX_CLAIM = 900
|
||||
|
||||
const fmt = (n: number): string =>
|
||||
n >= 1e9 ? `${(n / 1e9).toFixed(1)} GB` : n >= 1e6 ? `${(n / 1e6).toFixed(0)} MB` : `${n} B`
|
||||
|
||||
/**
|
||||
* Whether the service may accept a new provisioning request.
|
||||
*
|
||||
* The absence of a limit is itself a finding. A quota that was never set is
|
||||
* not an unlimited quota, it is an unconsidered one — and "unlimited" on a
|
||||
* single host is not a decision anyone would make deliberately.
|
||||
*/
|
||||
export function preflight(
|
||||
request: ProvisioningRequest,
|
||||
host: HostState,
|
||||
limits: CapacityLimits = {},
|
||||
): Finding[] {
|
||||
const out: Finding[] = []
|
||||
const cite = evidence.file(host.where, undefined,
|
||||
`${host.tenants} tenants, ${host.freeDiskBytes === null ? 'disk unmeasured' : `${fmt(host.freeDiskBytes)} free`}`)
|
||||
|
||||
const noLimits = limits.maxTenants === undefined
|
||||
&& limits.maxPerAccount === undefined
|
||||
&& limits.minFreeDiskBytes === undefined
|
||||
|
||||
if (noLimits) {
|
||||
out.push({
|
||||
id: 'capacity/no-limits',
|
||||
summary: 'No capacity limits are declared',
|
||||
determination: 'fail',
|
||||
severity: 'critical',
|
||||
detail: 'Self-serve provisioning with no ceiling is a denial of service you run against yourself. Every tenant is a stack on one host, and a signup loop fills it — a quota that was never set is not unlimited, it is unconsidered.',
|
||||
evidence: [cite],
|
||||
})
|
||||
}
|
||||
|
||||
if (limits.maxTenants !== undefined && host.tenants + 1 > limits.maxTenants) {
|
||||
out.push({
|
||||
id: 'capacity/host',
|
||||
summary: `Host is at ${host.tenants} of ${limits.maxTenants} tenants`,
|
||||
determination: 'fail',
|
||||
severity: 'high',
|
||||
detail: 'Provisioning would exceed the host ceiling.',
|
||||
evidence: [cite],
|
||||
})
|
||||
}
|
||||
|
||||
if (limits.maxPerAccount !== undefined && request.existingForAccount + 1 > limits.maxPerAccount) {
|
||||
out.push({
|
||||
id: 'capacity/account',
|
||||
summary: `${request.account} has ${request.existingForAccount} of ${limits.maxPerAccount} allowed tenants`,
|
||||
determination: 'fail',
|
||||
severity: 'high',
|
||||
detail: 'A per-account ceiling is what stops one account taking the host.',
|
||||
evidence: [evidence.file(request.where, undefined, `account ${request.account}`)],
|
||||
})
|
||||
}
|
||||
|
||||
if (limits.minFreeDiskBytes !== undefined) {
|
||||
if (host.freeDiskBytes === null || request.estimatedDiskBytes === null) {
|
||||
out.push({
|
||||
id: 'capacity/disk',
|
||||
summary: 'Disk headroom after provisioning cannot be computed',
|
||||
determination: 'not-assessed',
|
||||
severity: 'high',
|
||||
detail: `${host.freeDiskBytes === null ? 'Free disk was not measured' : 'The new stack has no size estimate'}. An unmeasured resource is not a plentiful one, and a full disk on this host wedges every container, not only the new tenant.`,
|
||||
evidence: [cite],
|
||||
})
|
||||
} else {
|
||||
const after = host.freeDiskBytes - request.estimatedDiskBytes
|
||||
if (after < limits.minFreeDiskBytes) {
|
||||
out.push({
|
||||
id: 'capacity/disk',
|
||||
summary: `${fmt(after)} free after provisioning, below the ${fmt(limits.minFreeDiskBytes)} floor`,
|
||||
determination: 'fail',
|
||||
severity: 'critical',
|
||||
detail: 'A full disk on this host wedges every container, not only the new tenant.',
|
||||
evidence: [cite],
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (out.length === 0) {
|
||||
out.push({
|
||||
id: 'capacity',
|
||||
summary: `Capacity available for ${request.tenantName}`,
|
||||
determination: 'pass',
|
||||
severity: 'info',
|
||||
evidence: [cite],
|
||||
})
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
/**
|
||||
* Jobs claimed and never finished.
|
||||
*
|
||||
* `SKIP LOCKED` claiming means a worker that dies mid-job leaves that job
|
||||
* claimed forever: not queued, not failed, nothing retries it. It is the one
|
||||
* state that looks like progress, which is why nothing alarms on it.
|
||||
*/
|
||||
export function stuckJobs(jobs: Job[], now: string, maxClaimSeconds = DEFAULT_MAX_CLAIM): Job[] {
|
||||
const n = Date.parse(now)
|
||||
return (jobs ?? []).filter(j => {
|
||||
if (j.state !== 'claimed' || !j.claimedAt) return false
|
||||
const t = Date.parse(j.claimedAt)
|
||||
if (!Number.isFinite(t) || !Number.isFinite(n)) return false
|
||||
return (n - t) / 1000 > maxClaimSeconds
|
||||
})
|
||||
}
|
||||
|
||||
export interface AssessOptions {
|
||||
/** ISO 8601. Injected, never read from the clock. */
|
||||
now: string
|
||||
request?: ProvisioningRequest
|
||||
host?: HostState
|
||||
limits?: CapacityLimits
|
||||
jobs?: Job[]
|
||||
}
|
||||
|
||||
/** Assess whether self-service provisioning is safe to run right now. */
|
||||
export function assessService(service: ServiceState, opts: AssessOptions): CapabilityReport {
|
||||
const findings: Finding[] = []
|
||||
const maxClaim = service.maxClaimSeconds ?? DEFAULT_MAX_CLAIM
|
||||
|
||||
if (!service.workerEnabled) {
|
||||
findings.push({
|
||||
id: 'worker/dormant',
|
||||
summary: 'The provisioning worker is disabled',
|
||||
determination: 'not-assessed',
|
||||
severity: 'high',
|
||||
detail: 'Built, enabled and proven in production are three states. A dormant capability reported as ready is how the first real tenant becomes the test.',
|
||||
evidence: [evidence.document('DISABLE_PROVISION_WORKER', undefined, undefined, 'the worker ships dormant behind this flag')],
|
||||
})
|
||||
} else if (!service.provenInProduction) {
|
||||
findings.push({
|
||||
id: 'worker/unproven',
|
||||
summary: 'The worker is enabled but has never completed a real tenant',
|
||||
determination: 'not-assessed',
|
||||
severity: 'high',
|
||||
detail: 'Enabled is not proven. Until one tenant has gone end to end in production, the first customer is the integration test.',
|
||||
evidence: [],
|
||||
})
|
||||
}
|
||||
|
||||
if (opts.request && opts.host) {
|
||||
findings.push(...preflight(opts.request, opts.host, opts.limits ?? {}))
|
||||
}
|
||||
|
||||
const jobs = opts.jobs ?? []
|
||||
const stuck = stuckJobs(jobs, opts.now, maxClaim)
|
||||
for (const j of stuck) {
|
||||
findings.push({
|
||||
id: `job/${j.id}`,
|
||||
summary: `job ${j.id} has been claimed by ${j.claimedBy ?? 'a worker'} since ${j.claimedAt}`,
|
||||
determination: 'fail',
|
||||
severity: 'critical',
|
||||
detail: `Claimed longer than ${Math.round(maxClaim / 60)} minutes. With SKIP LOCKED claiming, a worker that died mid-job leaves it claimed forever — not queued, not failed, nothing retrying it. It is the one state that looks like progress.`,
|
||||
evidence: [evidence.record('provisioning_jobs', j.id, 'state', 'claimed', j.where)],
|
||||
})
|
||||
}
|
||||
|
||||
if (service.verifiesReachability !== true) {
|
||||
findings.push({
|
||||
id: 'verify/reachability',
|
||||
summary: 'Provisioned tenants are not verified reachable',
|
||||
determination: 'not-assessed',
|
||||
severity: 'high',
|
||||
detail: "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.",
|
||||
evidence: [],
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
capability: 'SelfServe',
|
||||
scope: `worker ${service.workerEnabled ? 'enabled' : 'dormant'}, ${jobs.length} job(s) in flight`,
|
||||
examined: 1 + (opts.request ? 1 : 0) + jobs.length,
|
||||
findings,
|
||||
notes: [
|
||||
`${stuck.length} stuck job(s) of ${jobs.filter(j => j.state === 'claimed').length} claimed`,
|
||||
'This library gates provisioning; it does not perform it — the worker already exists.',
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
/** Render a self-service assessment for a terminal, CI log or review. */
|
||||
export function formatService(service: ServiceState, opts: AssessOptions & FormatOptions): string {
|
||||
return renderReport(assessService(service, opts), opts)
|
||||
}
|
||||
|
||||
export { DEFAULT_MAX_CLAIM, fmt as formatBytes }
|
||||
@@ -0,0 +1,159 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { overall, validateReport } from '@extant2000/evidence-record'
|
||||
import {
|
||||
assessService,
|
||||
formatService,
|
||||
preflight,
|
||||
stuckJobs,
|
||||
type HostState,
|
||||
type Job,
|
||||
type ProvisioningRequest,
|
||||
type ServiceState,
|
||||
} from '../src/index.js'
|
||||
|
||||
const NOW = '2026-08-01T12:00:00Z'
|
||||
|
||||
const healthy: ServiceState = {
|
||||
workerEnabled: true,
|
||||
provenInProduction: true,
|
||||
verifiesReachability: true,
|
||||
}
|
||||
|
||||
const host: HostState = { tenants: 10, freeDiskBytes: 50e9, where: 'df -P /data' }
|
||||
const request: ProvisioningRequest = {
|
||||
account: 'acme', tenantName: 'acme-prod', existingForAccount: 1,
|
||||
estimatedDiskBytes: 2e9, where: 'provisioning_jobs#j1',
|
||||
}
|
||||
const limits = { maxTenants: 50, maxPerAccount: 3, minFreeDiskBytes: 10e9 }
|
||||
|
||||
describe('dormant is not proven', () => {
|
||||
it('reports a disabled worker as not-assessed, never ready', () => {
|
||||
// Built, enabled and proven in production are three states.
|
||||
const r = assessService({ ...healthy, workerEnabled: false }, { now: NOW })
|
||||
const f = r.findings.find(x => x.id === 'worker/dormant')!
|
||||
expect(f.determination).toBe('not-assessed')
|
||||
expect(f.detail).toContain('the first real tenant becomes the test')
|
||||
expect(overall(r)).toBe('not-assessed')
|
||||
})
|
||||
|
||||
it('distinguishes enabled from proven', () => {
|
||||
const r = assessService({ ...healthy, provenInProduction: false }, { now: NOW })
|
||||
const f = r.findings.find(x => x.id === 'worker/unproven')!
|
||||
expect(f.determination).toBe('not-assessed')
|
||||
expect(f.detail).toContain('the first customer is the integration test')
|
||||
})
|
||||
|
||||
it('says nothing about the worker when it is enabled and proven', () => {
|
||||
const r = assessService(healthy, { now: NOW })
|
||||
expect(r.findings.some(f => f.id.startsWith('worker/'))).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('no ceiling is a denial of service you run against yourself', () => {
|
||||
it('fails when no limits are declared at all', () => {
|
||||
// A quota that was never set is not unlimited, it is unconsidered.
|
||||
const f = preflight(request, host, {})[0]!
|
||||
expect(f.determination).toBe('fail')
|
||||
expect(f.severity).toBe('critical')
|
||||
expect(f.detail).toContain('unlimited, it is unconsidered')
|
||||
})
|
||||
|
||||
it('fails at the host ceiling', () => {
|
||||
expect(preflight(request, { ...host, tenants: 50 }, limits)
|
||||
.some(f => f.id === 'capacity/host' && f.determination === 'fail')).toBe(true)
|
||||
})
|
||||
|
||||
it('fails at the per-account ceiling — one account cannot take the host', () => {
|
||||
expect(preflight({ ...request, existingForAccount: 3 }, host, limits)
|
||||
.some(f => f.id === 'capacity/account' && f.determination === 'fail')).toBe(true)
|
||||
})
|
||||
|
||||
it('fails when provisioning would drop below the disk floor', () => {
|
||||
const f = preflight({ ...request, estimatedDiskBytes: 45e9 }, host, limits)
|
||||
.find(x => x.id === 'capacity/disk')!
|
||||
expect(f.determination).toBe('fail')
|
||||
expect(f.detail).toContain('wedges every container')
|
||||
})
|
||||
|
||||
it('reports unmeasured disk as not-assessed, never plentiful', () => {
|
||||
const f = preflight(request, { ...host, freeDiskBytes: null }, limits)
|
||||
.find(x => x.id === 'capacity/disk')!
|
||||
expect(f.determination).toBe('not-assessed')
|
||||
expect(f.detail).toContain('An unmeasured resource is not a plentiful one')
|
||||
})
|
||||
|
||||
it('reports an unestimated stack the same way', () => {
|
||||
const f = preflight({ ...request, estimatedDiskBytes: null }, host, limits)
|
||||
.find(x => x.id === 'capacity/disk')!
|
||||
expect(f.determination).toBe('not-assessed')
|
||||
})
|
||||
|
||||
it('passes when every limit is satisfied', () => {
|
||||
const out = preflight(request, host, limits)
|
||||
expect(out).toHaveLength(1)
|
||||
expect(out[0]!.determination).toBe('pass')
|
||||
})
|
||||
})
|
||||
|
||||
describe('a claimed job looks like progress', () => {
|
||||
const job = (p: Partial<Job> = {}): Job =>
|
||||
({ id: 'j1', state: 'claimed', claimedAt: '2026-08-01T11:00:00Z', claimedBy: 'worker-1', where: 'db', ...p })
|
||||
|
||||
it('finds a job claimed past the limit', () => {
|
||||
// SKIP LOCKED means a dead worker leaves it claimed forever: not queued,
|
||||
// not failed, nothing retrying it.
|
||||
expect(stuckJobs([job()], NOW, 900)).toHaveLength(1)
|
||||
const r = assessService(healthy, { now: NOW, jobs: [job()] })
|
||||
const f = r.findings.find(x => x.id === 'job/j1')!
|
||||
expect(f.determination).toBe('fail')
|
||||
expect(f.severity).toBe('critical')
|
||||
expect(f.detail).toContain('the one state that looks like progress')
|
||||
})
|
||||
|
||||
it('leaves a recently claimed job alone', () => {
|
||||
expect(stuckJobs([job({ claimedAt: '2026-08-01T11:55:00Z' })], NOW, 900)).toHaveLength(0)
|
||||
})
|
||||
|
||||
it('ignores queued, done and failed jobs', () => {
|
||||
for (const state of ['queued', 'done', 'failed'] as const) {
|
||||
expect(stuckJobs([job({ state })], NOW, 900)).toHaveLength(0)
|
||||
}
|
||||
})
|
||||
|
||||
it('honours a custom claim window', () => {
|
||||
expect(stuckJobs([job()], NOW, 7200)).toHaveLength(0)
|
||||
expect(stuckJobs([job()], NOW, 600)).toHaveLength(1)
|
||||
})
|
||||
})
|
||||
|
||||
describe('provisioned is not reachable', () => {
|
||||
it('reports unverified reachability as not-assessed', () => {
|
||||
const r = assessService({ ...healthy, verifiesReachability: false }, { now: NOW })
|
||||
const f = r.findings.find(x => x.id === 'verify/reachability')!
|
||||
expect(f.determination).toBe('not-assessed')
|
||||
expect(f.detail).toContain('the first person to discover a broken tenant is the tenant')
|
||||
})
|
||||
|
||||
it('says nothing when reachability is verified', () => {
|
||||
expect(assessService(healthy, { now: NOW }).findings.some(f => f.id === 'verify/reachability')).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('conformance with the evidence-record standard', () => {
|
||||
it('a fully healthy service with a valid request passes', () => {
|
||||
const r = assessService(healthy, { now: NOW, request, host, limits })
|
||||
expect(overall(r)).toBe('pass')
|
||||
})
|
||||
|
||||
it('every conclusion carries a citation', () => {
|
||||
expect(validateReport(assessService(
|
||||
{ ...healthy, workerEnabled: false },
|
||||
{ now: NOW, request, host, limits, jobs: [{ id: 'j1', state: 'claimed', claimedAt: '2026-08-01T09:00:00Z', where: 'db' }] },
|
||||
))).toEqual([])
|
||||
})
|
||||
|
||||
it('says it gates rather than provisions', () => {
|
||||
expect(formatService(healthy, { now: NOW }))
|
||||
.toContain('gates provisioning; it does not perform it')
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"module": "CommonJS",
|
||||
"moduleResolution": "Node",
|
||||
"outDir": "dist-cjs",
|
||||
"declaration": false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022", "module": "ES2022", "moduleResolution": "bundler",
|
||||
"declaration": true, "outDir": "dist", "rootDir": "src",
|
||||
"strict": true, "skipLibCheck": true
|
||||
},
|
||||
"include": ["src/**/*.ts"]
|
||||
}
|
||||
Reference in New Issue
Block a user