Initial Commit

This commit is contained in:
2026-05-27 23:29:53 +01:00
commit fbc70f96e6
10000 changed files with 1102152 additions and 0 deletions

View File

@@ -0,0 +1,39 @@
# Proposal — Migrate @suaveplan/health-check to genesis
## Why
`@suaveplan/health-check` is the server-tier orchestrator for `/health`, `/ready`, and `/live` endpoints with dependency checks. The upstream package at `/mnt/s/web/suaveplan/packages/server/health-check/` predates the genesis enterprise gates: it uses the upstream `EventEmitter` shape, calls global `Date.now()`/`setTimeout`/`fetch`/`node:net` directly, has no error-code registry, no OTel self-telemetry, and no Zod boundary validation. This change re-lands the package at `packages/observability/monitor/health-check/` (sibling to `logger`, `audit-logging`, `opentelemetry`) at full feature surface with the genesis contract.
## What
Land `@suaveplan/health-check@0.1.0` with the following feature modules (one per folder, 5-files-per-feature):
- `health-checker/` — `HealthChecker` orchestrator with `EventBus<HealthEventMap>` and injectable `Clock` / `TimersLike` / `LoggerLike`
- `http-check/` — `createHttpCheck` factory with injectable `fetchImpl` + `Clock`
- `tcp-check/` — `createTcpCheck` factory with injectable `TcpConnectFn` + `Clock`
- `response-builder/` — `ResponseBuilder` mapping `CheckStatus` → HTTP status codes
- `error-codes/` — `HEALTH_CHECK_CODES` via `defineCodes` + public-ctor `HealthCheckError`
- `telemetry/` — OTel meter (`health_check_runs_total`, `health_check_aggregate_status_total`, `health_check_duration_ms`)
- `types/` — shared `CheckResult`/`CheckFn`/`DependencyCheckConfig`/`HealthResponse`/`ReadinessResponse`/`LivenessResponse`/`HttpHealthResponse`/`HttpCheckConfig`/`TcpCheckConfig`/`HealthEvent`/`HealthEventMap`
## Source mapping
| Upstream file | Genesis file | Notes |
| --- | --- | --- |
| `src/checker/health-checker.ts` | `src/health-checker/health-checker.ts` | `EventEmitter` → `EventBus<HealthEventMap>`; `Clock`+`TimersLike`+`LoggerLike` injected; Zod-validated options; `HealthCheckError` on every failure |
| `src/checks/http-check.ts` | `src/http-check/http-check.ts` | `fetchImpl` injection replaces global `fetch`; Zod options |
| `src/checks/tcp-check.ts` | `src/tcp-check/tcp-check.ts` | `TcpConnectFn` injection replaces direct `node:net.connect`; settled-flag guards late events |
| `src/response/response-builder.ts` | `src/response-builder/response-builder.ts` | Same algorithm, renamed file |
| `src/types/types.ts` | `src/types/types.ts` | Adds `HealthEventMap`, `TcpConnectFn`/`TcpSocketLike`, `HttpCheckConfig.fetchImpl`, `TcpCheckConfig.connectImpl` |
| `src/index.ts` | `src/index.ts` | Explicit named re-exports only |
| (none) | `src/error-codes/error-codes.ts` | New — required by genesis rule |
| (none) | `src/telemetry/telemetry.ts` | New — required by genesis rule |
## Deviations from upstream
1. Upstream uses `@suaveplan/events`' `EventEmitter` with variadic tuple payloads. Genesis `@suaveplan/events` exposes `EventBus<TMap>` with single-payload `emit(type, payload)`. `STATUS_CHANGED` therefore emits `{ previous, current }` object instead of two positional args. Exposed as `HealthChecker.events`.
2. Upstream calls `Date.now()` and `setTimeout` directly. Genesis injects `Clock` + `TimersLike` per the universal-tier contract.
3. Upstream has no error-code registry. Genesis adds `HEALTH_CHECK_CODES` + public-ctor `HealthCheckError`.
4. Upstream HTTP / TCP checks depend on the runtime's global `fetch` and `node:net`. Genesis injects them so tests never touch real sockets.
5. Upstream emits no self-telemetry. Genesis adds a `telemetry/` module emitting per-check counters + duration histogram + aggregate-status counter.
6. Boundary validation: every public option object is now Zod-validated; failures throw `HealthCheckError(INVALID_OPTIONS)`.

View 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".

View File

@@ -0,0 +1,47 @@
# Tasks — Migrate @suaveplan/health-check to genesis
## 0. Pre-flight (non-negotiable)
- [x] 0.1 `git status` clean inside `.claude/worktrees/agent-ae4f1a1bd61f6b11d`
- [x] 0.2 Account for every worktree returned by `git worktree list` (this agent's worktree + main checkout + other live agent)
- [x] 0.3 `git merge-base --is-ancestor 5942a30eeb82f16a92479e34d04c1c6d4513f041 HEAD` reports true after `git fetch /home/paul/web/genesis genesis && git merge FETCH_HEAD --no-edit`
## 1. Spec authoring
- [x] 1.1 Read upstream `/mnt/s/web/suaveplan/packages/server/health-check/` end-to-end
- [x] 1.2 Read `openspec/AGENTS.md` Sections 13 and 14
- [x] 1.3 Draft `proposal.md` with `## Source mapping` table
- [x] 1.4 Draft `specs/health-check/spec.md` with `## ADDED Requirements` + `**Verified by:**` lines
## 2. Implementation
- [x] 2.1 `src/types/` — shared types incl. `HealthEventMap`, `TcpConnectFn`, `TcpSocketLike`, injectable fetch/connect on configs
- [x] 2.2 `src/error-codes/` — `HEALTH_CHECK_CODES` + public-ctor `HealthCheckError`
- [x] 2.3 `src/telemetry/` — OTel meter (counter + counter + histogram)
- [x] 2.4 `src/health-checker/` — `HealthChecker` with injectable `Clock`/`TimersLike`/`LoggerLike` + `EventBus<HealthEventMap>`
- [x] 2.5 `src/http-check/` — `createHttpCheck` with injectable `fetchImpl`
- [x] 2.6 `src/tcp-check/` — `createTcpCheck` with injectable `TcpConnectFn`
- [x] 2.7 `src/response-builder/` — `ResponseBuilder` mapping statuses to HTTP codes
- [x] 2.8 `src/index.ts` — explicit named re-exports only
## 3. Tests
- [x] 3.1 Unit tests co-located at `src/<feature>/<feature>.test.ts`
- [x] 3.2 100/100/100/100 coverage gate met (istanbul)
- [x] 3.3 Every `it(...)` carries `{ timeout: 5000 }`
- [x] 3.4 All tests import from `@suaveplan/testing/runner`
## 4. Documentation
- [x] 4.1 Sibling `.md` for every non-exempt implementation file (≥200 words, six sections)
- [x] 4.2 Root `README.md` with module list + install + usage
## 5. Verification
- [x] 5.1 `bunx turbo lint typecheck test --filter=@suaveplan/health-check` green
- [x] 5.2 `bunx turbo test:coverage --filter=@suaveplan/health-check` ≥ 100/100/100/100
- [x] 5.3 `bunx turbo build --filter=@suaveplan/health-check` green
- [x] 5.4 `grep -rnE "from ['\"](vitest|bun:test)['\"]" packages/observability/monitor/health-check/src/` empty
- [x] 5.5 `grep -rn "biome-ignore\|TODO\|FIXME\|XXX" packages/observability/monitor/health-check/src/` empty
- [x] 5.6 `bun scripts/lint/check-feature-layout.ts | grep health-check` empty
- [x] 5.7 `bun scripts/folder-reorg/generate-manifest.ts` refreshed `MANIFEST.md`