Move docs/ package mirror into document-navigator plugin

Relocate the ~6k-file docs tree from the repo root into
plugins/document-navigator/docs/ so the plugin is self-contained and
portable. docs/superpowers/ (specs and plans) stays at the repo root.

Moved via cp+rm rather than git mv because directory rename() fails with
EPERM on this WSL /mnt DrvFs mount; git rename-detection pairs the
identical-content files at commit time.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-30 01:10:00 +01:00
parent fe3fedf42e
commit 9bf56a806b
6022 changed files with 12437 additions and 0 deletions

View File

@@ -0,0 +1,75 @@
# `PeriodicHealthChecker`
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
`PeriodicHealthChecker` races each worker's `getHealth()` against a configurable timeout, tracks consecutive failure and success counts, flips workers between `ONLINE`/`BUSY` and `UNHEALTHY` only after sustained badness or recovery, and optionally caches recent results so back-to-back probes don't hammer the underlying transport. It implements the `WorkerHealthChecker` interface and is the default health checker that `WorkerManager` constructs when no other is supplied.
## Features
- Promise.race between `worker.getHealth()` and a `setTimeout`-driven timeout (default 5 s).
- `unhealthyThreshold` (default 3) — consecutive failures required to flip to `UNHEALTHY`.
- `healthyThreshold` (default 2) — consecutive successes required to recover.
- Optional caching (`cacheResults`, `cacheTimeMs`; both default-on at 5 s) — backed by the injected `Clock`.
- OTel `queue_dist.health.check` span + `queue_dist.health_check.duration_ms` histogram.
- `getHealthCheckCounts(id)`, `resetHealthCheckCounts(id)`, `clearCache()` for inspection / reset.
- Recovery transitions to `BUSY` when `activeTasks > 0`, else `ONLINE`.
- Injectable `Clock` and `LoggerLike`; defaults are `Date.now` and `NOOP_LOGGER`.
## Usage: Basic Example
```ts
import { PeriodicHealthChecker } from "@suaveplan/queue-distribution/health-checker/health-checker";
const checker = new PeriodicHealthChecker({
unhealthyThreshold: 3,
healthyThreshold: 2,
});
const health = await checker.checkHealth(worker);
if (!health.isHealthy) {
console.warn(`Worker ${worker.id} unhealthy`);
}
```
## Usage: Advanced Example
Combine with a `WorkerPool` to fan-out checks and react to status transitions.
```ts
import { PeriodicHealthChecker } from "@suaveplan/queue-distribution/health-checker/health-checker";
import { WorkerStatus } from "@suaveplan/queue-distribution/worker/worker";
const checker = new PeriodicHealthChecker(
{ unhealthyThreshold: 2, healthyThreshold: 1, cacheResults: false },
{ logger: console as never },
);
async function probeAll(workers: readonly Worker[]): Promise<void> {
const ids = await Promise.all(
workers.map(async (worker) => {
const isHealthy = await checker.isHealthy(worker);
return { id: worker.id, status: worker.status, isHealthy };
}),
);
for (const row of ids) {
if (row.status === WorkerStatus.UNHEALTHY) {
console.error(`Worker ${row.id} is unhealthy`);
}
}
}
```
## API Reference
- `interface WorkerHealthChecker` — `checkHealth(worker, signal?)`, `isHealthy(worker, signal?)`.
- `interface HealthCheckerOptions` — `checkIntervalMs?`, `unhealthyThreshold?`, `healthyThreshold?`, `healthCheckTimeoutMs?`, `cacheResults?`, `cacheTimeMs?`.
- `interface HealthCheckerDeps` — `clock?`, `logger?`.
- `class PeriodicHealthChecker implements WorkerHealthChecker`.
- `getCheckInterval(): number`, `checkHealth(worker, signal?): Promise<WorkerHealth>`, `isHealthy(worker, signal?): Promise<boolean>`.
- `getHealthCheckCounts(workerId): { failures, successes } | null`, `resetHealthCheckCounts(workerId): void`, `clearCache(): void`.
## Implementation Notes
The class flips status both ways without holding a lock; concurrent calls into `checkHealth` against the same worker can theoretically race the threshold check. In practice that means a brief window where status oscillates — acceptable because both `BUSY` and `ONLINE` are eligible for dispatch and the threshold acts as a debouncer over a longer window. The cache is keyed by `worker.id` so removing and re-adding a worker with the same id continues from the old cache; call `clearCache()` after `pool.removeWorker` if isolation matters. The Promise.race uses `setTimeout` rather than the injected `Clock` so the timeout fires under real time even when consumers mock the clock — vitest's `vi.useFakeTimers()` does work because it patches `setTimeout` globally.