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,85 @@
# `heartbeat` — ping/pong policy + evaluator
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
Every WebSocket connection that the Suaveplan API host accepts is policed
by a heartbeat: the engine pings at a fixed cadence and forcibly closes the
connection once a documented number of consecutive pongs are missed. This
module owns the abstract policy and the stateless evaluator that concrete
engines call from their heartbeat tick. The defaults — 30000ms cadence,
two missed pongs to abort — track design doc §11.4.
The evaluator is intentionally stateless: it consumes scalar measurements
the engine already tracks and returns the next action. State lives with
the engine; the contract lives here.
## Features
- `HeartbeatPolicy` interface (`heartbeatMs`, `maxMissedPongs`).
- `DEFAULT_HEARTBEAT_POLICY` — frozen design-doc defaults.
- `evaluateHeartbeat(msSinceLastPong, consecutiveMissedPongs, policy?)` →
`"ok" | "ping" | "abort"`.
- `raiseHeartbeatFailed(connectionId)` throws the canonical `WsError`.
## Usage: Basic
```ts
import { evaluateHeartbeat } from "@suaveplan/api-ws/heartbeat";
const action = evaluateHeartbeat(msSinceLastPong, missedPongs);
if (action === "ping") sendPing();
if (action === "abort") closeAbnormal();
```
## Usage: Advanced
```ts
import {
evaluateHeartbeat,
raiseHeartbeatFailed,
type HeartbeatPolicy,
} from "@suaveplan/api-ws/heartbeat";
class HeartbeatTicker {
private missed = 0;
private lastPong = Date.now();
constructor(private readonly policy: HeartbeatPolicy) {}
tick(now: number, connectionId: string): void {
const action = evaluateHeartbeat(now - this.lastPong, this.missed, this.policy);
if (action === "ping") {
this.missed += 1;
} else if (action === "abort") {
raiseHeartbeatFailed(connectionId);
}
}
onPong(now: number): void {
this.missed = 0;
this.lastPong = now;
}
}
```
## API Reference
- `interface HeartbeatPolicy { heartbeatMs: number; maxMissedPongs: number }`.
- `const DEFAULT_HEARTBEAT_POLICY: HeartbeatPolicy` (frozen).
- `type HeartbeatAction = "ok" | "ping" | "abort"`.
- `evaluateHeartbeat(msSinceLastPong, consecutiveMissedPongs, policy?): HeartbeatAction`.
- `raiseHeartbeatFailed(connectionId): never`.
## Implementation Notes
- The evaluator checks `consecutiveMissedPongs` before `msSinceLastPong`
so an already-failed connection aborts immediately on the next tick
even if the engine's clock briefly stalls.
- `consecutiveMissedPongs` is the engine's counter — every ping increments
it; every pong resets it. The evaluator does not own that state.
- `raiseHeartbeatFailed` is `never` so callers can use it as a terminal
branch without an explicit unreachable assertion.