From 350012655aec56a32350f2130f40630039f0b0be Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 24 Aug 2026 17:37:44 +0000 Subject: [PATCH] docs(protocol): show a real PluginHealthReport under Custom Health Checks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The JSON block under `### Custom Health Checks` matched neither an HTTP body nor the internal type its lead-in named. `PluginHealthReport` (`packages/spec/src/kernel/plugin-lifecycle-advanced.zod.ts:90-136`, built at `packages/core/src/health-monitor.ts:185-193`) is per-plugin, and the sketch diverged on every structural axis: - `checks` is an array of `{ name, status: 'passed' | 'failed' | 'warning' }` (schema:121-126), not a keyed map of subsystems whose entries read `"healthy"` - `uptime` lives under `metrics` in milliseconds (schema:109-116, `Date.now() - startTime`), not at top level in seconds - no `version` field exists (0 occurrences in schema:90-136, against a control of 5 for `status` in the same region) The example is now a report the monitor actually builds: `status` from `PluginHealthStatusSchema`, `metrics.uptime` in ms, and a `checks` array whose entry is named after the configured `checkMethod` (health-monitor.ts:119-125). `"2.0.0"` is absent because the schema has no `version` field — not because the literal was edited to something else. Two fields that were already correct are unchanged: `status: "healthy"` (a real `PluginHealthStatusSchema` member) and the `timestamp` literal. The `### Health Status Response` sample above is untouched. That one is the `GET /health` wire body and a different thing entirely; conflating the two is the defect this change closes. The callout's "richer per-subsystem report" became "per-plugin health report", since the per-subsystem block it pointed at no longer exists. Not audited, filed separately: the TypeScript example above the block declares `healthChecks` as a map of async functions, while the real config is `PluginHealthCheckSchema.checkMethod` (a method name on the plugin). Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_015ahemw8RcTgqtxrj15PEZx --- content/docs/protocol/kernel/lifecycle.mdx | 62 +++++++++------------- 1 file changed, 25 insertions(+), 37 deletions(-) diff --git a/content/docs/protocol/kernel/lifecycle.mdx b/content/docs/protocol/kernel/lifecycle.mdx index 93b126ed4e..3f277a923a 100644 --- a/content/docs/protocol/kernel/lifecycle.mdx +++ b/content/docs/protocol/kernel/lifecycle.mdx @@ -633,7 +633,7 @@ rather than black-holing a working deployment. `GET /health` returns a compact liveness body (`status`, `timestamp`, `version`, -`uptime`) and `GET /ready` returns readiness. The richer per-subsystem report +`uptime`) and `GET /ready` returns readiness. The per-plugin health report and the plugin-declared custom checks below describe the internal health-monitor model (`PluginHealthMonitor`), which covers **plugins, not driver connections** — they are **not yet** exposed as a dedicated HTTP endpoint or as a declarative plugin @@ -694,50 +694,38 @@ export default { }; ``` -The richer per-subsystem view the callout above refers to is sketched below. It -is **illustrative only**: no endpoint serves this shape, and it is not the -monitor's own serialized type either — `PluginHealthReport` -(`@objectstack/spec/kernel`) is per-plugin, carries `checks` as an array of -`{ name, status: "passed" | "failed" | "warning" }`, and reports `uptime` under -`metrics`. Read the sketch as a picture of what per-subsystem health reporting -would carry, not as a contract. +The monitor keeps one report per plugin rather than one aggregate document. Each +round of checks builds a `PluginHealthReport` (`@objectstack/spec/kernel`, +constructed in `packages/core/src/health-monitor.ts`) and stores it under the +plugin's name, where `getHealthReport(pluginName)` reads it back. Nothing serves +this shape over HTTP — it is an in-process model, not a wire body. ```json { "status": "healthy", - "uptime": 3600, - "version": "2.0.0", "timestamp": "2024-01-15T11:00:00.000Z", - "checks": { - "database": { - "status": "healthy", - "latency_ms": 5, - "connections": { - "active": 8, - "idle": 2, - "max": 10 - } - }, - "redis": { - "status": "healthy", - "latency_ms": 2 - }, - "plugins": { - "status": "healthy", - "loaded": 3, - "enabled": 3, - "failed": 0 - }, - "jobs": { - "status": "healthy", - "pending": 5, - "running": 2, - "failed": 0 - } - } + "metrics": { + "uptime": 3600000 + }, + "checks": [ + { "name": "healthCheck", "status": "passed" } + ] } ``` +`checks` is an **array**, and a check's `status` is `"passed" | "failed" | +"warning"` — the six-value `"healthy" | "degraded" | "unhealthy" | "failed" | +"recovering" | "unknown"` vocabulary belongs to the report's own top-level +`status`, never to an entry inside `checks`. Each entry is named after the +plugin's configured `checkMethod`, or `"plugin-loaded"` when a plugin configures +none. `metrics.uptime` is in **milliseconds** (`Date.now() - startTime`), unlike +the seconds-valued `uptime` of `GET /health` above, and the report carries no +`version` field — it identifies its plugin by the key it is stored under. The +optional `message` is set only when a check fails; the schema's remaining +`metrics` fields (`memoryUsage`, `cpuUsage`, `activeConnections`, `errorRate`, +`responseTime`) and its `dependencies` array are declared but left unset by the +monitor today. + ## Shutdown Sequence Graceful shutdown ensures in-flight requests complete before process exits.