Initial Commit
This commit is contained in:
63
openspec/specs/health-check/spec.md
Normal file
63
openspec/specs/health-check/spec.md
Normal file
@@ -0,0 +1,63 @@
|
||||
# Health-Check — Spec Delta
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: HealthChecker registers, runs, and aggregates dependency checks
|
||||
|
||||
`HealthChecker` MUST expose `addCheck(config)`, `removeCheck(name)`, `getCheckNames()`, `runCheck(name)`, `health()`, `ready()`, `live()`, `clearCache()`, and `getUptimeMs()`. `addCheck` MUST throw `HealthCheckError(DUPLICATE_CHECK)` when the name is already registered. `runCheck` MUST throw `HealthCheckError(UNKNOWN_CHECK)` when the name is missing. `health()` MUST aggregate to `unhealthy` if any critical check is unhealthy, `degraded` if any non-critical check is unhealthy/degraded, and `healthy` otherwise.
|
||||
|
||||
**Verified by:** `health-checker.test.ts` › "rejects duplicate check registration", "throws UNKNOWN_CHECK for unregistered name", "aggregates healthy when all critical checks pass", "degrades when non-critical check fails", "aggregates degraded when a check is degraded".
|
||||
|
||||
### Requirement: HealthChecker accepts injectable Clock + TimersLike + LoggerLike
|
||||
|
||||
The `HealthChecker` constructor SHALL accept optional `clock: Clock`, `timers: TimersLike`, and `logger: LoggerLike`. Wall-clock readings (`responseTime`, `uptimeMs`, ISO `timestamp`, cache expiry) MUST come from the injected clock. Per-check timeouts MUST be scheduled via `timers.setTimer` / `timers.clearTimer`. When a check fails, the logger's `warn` method MUST be invoked with `{ name, message }`.
|
||||
|
||||
**Verified by:** `health-checker.test.ts` › "records success result + emits CHECK_PASSED" (uses injected clock), "enforces timeout via TimersLike", "records failure result + emits CHECK_FAILED + logs warn".
|
||||
|
||||
### Requirement: HealthChecker emits typed lifecycle events
|
||||
|
||||
`HealthChecker` SHALL expose `events: EventBus<HealthEventMap>` that emits `check:passed` (payload `CheckResult`) and `check:failed` (payload `CheckResult`) on every check execution, and `status:changed` (payload `{ previous, current }`) when the aggregate status flips.
|
||||
|
||||
**Verified by:** `health-checker.test.ts` › "records success result + emits CHECK_PASSED", "records failure result + emits CHECK_FAILED + logs warn", "emits STATUS_CHANGED on transition".
|
||||
|
||||
### Requirement: HealthChecker caches results when cacheTtlMs > 0
|
||||
|
||||
When `cacheTtlMs > 0`, a successful or failed `CheckResult` MUST be cached for the TTL and subsequent calls MUST return a copy with `cached: true`. Cache entries MUST expire on the next `runCheck` once the injected clock advances past `expiresAt`. `clearCache()` MUST drop every cached entry. `removeCheck(name)` MUST also drop the cached entry for that name.
|
||||
|
||||
**Verified by:** `health-checker.test.ts` › "returns cached result on second call", "clearCache forces re-run", "removeCheck clears cache + returns true".
|
||||
|
||||
### Requirement: HEALTH_CHECK_CODES registry via defineCodes
|
||||
|
||||
The package MUST publish a stable `HEALTH_CHECK_CODES` registry built with `defineCodes("HEALTH_CHECK", [...])` and an accompanying `HealthCheckError extends SuaveplanError<HealthCheckCode>` class with a public constructor delegating to `super`. Every public option-validation failure MUST throw `HealthCheckError(INVALID_OPTIONS)`. Duplicate / unknown registration paths MUST throw `DUPLICATE_CHECK` / `UNKNOWN_CHECK`. Timeout failures MUST surface as `details.timedOut === true`.
|
||||
|
||||
**Verified by:** `error-codes.test.ts` › "namespaces every code under HEALTH_CHECK.", "exposes the expected leaf names", "carries the supplied code, message, cause, and context"; `health-checker.test.ts` › "rejects empty service name", "rejects invalid defaultTimeout", "rejects invalid addCheck arguments".
|
||||
|
||||
### Requirement: createHttpCheck accepts injectable fetchImpl + clock
|
||||
|
||||
`createHttpCheck` SHALL accept `HttpCheckConfig` carrying optional `fetchImpl: typeof fetch` and an optional `{ clock }` companion. Production code MAY rely on the `globalThis.fetch` default; tests MUST be able to inject a stub. Network errors, abort errors, and unexpected status codes MUST be returned as `unhealthy` `CheckResult` values; the function MUST NOT throw at runtime for transport failures.
|
||||
|
||||
**Verified by:** `http-check.test.ts` › "returns healthy on expected status", "treats unexpected status as unhealthy", "respects expectedStatusCodes override", "classifies network errors as unhealthy", "classifies abort errors as timeouts", "falls back to non-Error stringification".
|
||||
|
||||
### Requirement: createTcpCheck accepts injectable TcpConnectFn + clock
|
||||
|
||||
`createTcpCheck` SHALL accept `TcpCheckConfig` carrying optional `connectImpl: TcpConnectFn` and an optional `{ clock }` companion. The default `connectImpl` MUST wrap `node:net.connect`. The check MUST resolve (never reject) and MUST surface timeouts via `details.timedOut === true`. Late events emitted after the promise settles MUST be ignored.
|
||||
|
||||
**Verified by:** `tcp-check.test.ts` › "returns healthy when socket connects", "returns unhealthy on socket error", "returns unhealthy on timeout", "ignores late events once settled", "uses the default node:net connector when not injected".
|
||||
|
||||
### Requirement: ResponseBuilder maps statuses to HTTP codes
|
||||
|
||||
`ResponseBuilder` SHALL expose `health()`, `ready()`, `live()`. `health()` MUST return statusCode `200` for `healthy` and `degraded` and `503` for `unhealthy`. `ready()` MUST return `200` when `ready === true` and `503` otherwise. `live()` MUST always return `200`.
|
||||
|
||||
**Verified by:** `response-builder.test.ts` › "returns 200 for healthy /health", "returns 200 for degraded /health", "returns 503 for unhealthy /health", "returns 200 for ready /ready", "returns 503 for not-ready /ready", "returns 200 for /live".
|
||||
|
||||
### Requirement: Telemetry instruments emit on every public call
|
||||
|
||||
The package MUST register an OTel meter at `@suaveplan/health-check` exposing:
|
||||
|
||||
- `health_check_runs_total` counter — incremented on every executed dependency check, tagged with `name` + `status`.
|
||||
- `health_check_aggregate_status_total` counter — incremented on every `HealthChecker.health()`, tagged with the aggregate status.
|
||||
- `health_check_duration_ms` histogram — recorded on every executed dependency check, tagged with `name`.
|
||||
|
||||
A real in-memory `MeterProvider` registered before module import MUST see metric points after public calls.
|
||||
|
||||
**Verified by:** `telemetry.test.ts` › "records check runs", "records aggregate status", "records check duration".
|
||||
Reference in New Issue
Block a user