Edit this page on GitHub

Health indicators — classification audit

Every ctx.health.register(...) call in packages/*/src, classified. This is the M70c audit deliverable (plan §3.9): it stops the “hardcoded up next to live data” pattern recurring by making every indicator’s status source explicit, and it is enforced by test/health-indicator-audit.test.ts, which fails if a ctx.health.register site is added (or moved) without a matching row here.

The CLI schematic at packages/cli/src/schematics/health-indicator.ts mentions ctx.health.register inside a template string and is not a registration site.

There are 27 registration sites. static-plugin registers the same static-files indicator from two branches (the no-fs arm and the fs arm), so it contributes two rows; only one is active at a time.

What each status means

The health plugin projects every indicator to { status, data } (M70c, fixing X3-7) and aggregates: /ready is 200 only when every indicator is up; /health is 503 only when any is down (degraded is 200). The gRPC bridge maps the aggregate to grpc.health.v1.Health/Check — since M70c, degraded maps to NOT_SERVING, agreeing with /ready (X7-8).

ClassificationMeaning
live-stateThe status reflects a live fact about the backend or a live probe: a reachability probe (isHealthy()), a readiness flag that tracks a real connection, or an actual I/O check. The status can change at runtime as the backend changes.
justified-literalThe status is a fixed literal, but the justification is documented and the literal is the correct signal for that plugin (e.g. a platform binding whose presence is known at registration).
configuration-literalThe status is a fixed literal (or a lifecycle flag that only ever reports “started / stopped”) while the data payload is live. The status says nothing about the backend’s health. These are the X8-5 class of defect — the indicator’s status is decorative.

The six M70c in-scope packages (messaging, realtime-backplane, storage, mail, queue, service-discovery) are now live-state: each reports both signals — isReady() (lifecycle) and isHealthy() (reachability) — and the indicator maps them (see the per-package READMEs for the status tables). M90b added cache and database; the audit, notification and worker-pool rows followed, closing the last three H-70c defects this audit recorded, so no ctx.health.register site outside the defensible in-process set is configuration-literal any more.

The register

#IndicatorPackageSiteClassificationStatus source and note
1messaging (token)messaging-pluginpackages/messaging-plugin/src/plugin/messaging-plugin.ts:436live-stateM70c: isReady() lifecycle + isHealthy() reachability; down when not ready or unreachable, degraded when ready but unreachable and a reconnect is in flight. M90b: every broker probe is cached 5 s and bounded 2 s; the Service Bus transport probes the namespace (getNamespaceProperties(), 401/403 counts as reachable).
2realtime-backplanerealtime-backplane-pluginpackages/realtime-backplane-plugin/src/plugin/realtime-backplane-plugin.ts:88live-stateM70c: transport isHealthy(); a fan-out failure is degraded (local delivery still works), never down.
3storagestorage-pluginpackages/storage-plugin/src/plugin/storage-plugin.ts:175live-stateM70c: provider isHealthy() probe (HEAD/list) + lifecycle; down when unreachable.
4mailmail-pluginpackages/mail-plugin/src/plugin/mail-plugin.ts:140live-stateM70c: provider isHealthy() (SMTP verify?, SES isHealthy?) + lifecycle. H-70c-4: read through MailService.isHealthy() — now public on IMailer — so the indicator and any holder of the capability answer through one implementation.
5queue (token)queue-pluginpackages/queue-plugin/src/plugin/queue-plugin.ts:272live-stateM70c: adapter isHealthy() (PING / connection-fault / GetQueueAttributes) + lifecycle; data.reachable distinguishes up/down/unknown. M70k (X8-4): data.queues adds per-name ready/processing/dead depths where the adapter can count them cheaply, omitted where it cannot.
6service-discoveryservice-discovery-pluginpackages/service-discovery-plugin/src/plugin/service-discovery-plugin.ts:122live-stateM70c: #everResolved + provider probe (Consul /v1/status/leader, Kubernetes limit=1 EndpointSlice LIST); never-resolved-and-unreachable is down (X10-3 fix), stale-cache is degraded.
7grpcgrpc-pluginpackages/grpc-plugin/src/plugin/grpc-plugin.ts:79configuration-literalstatus: 'up' beside live data.available/serviceCount. The transport’s real health is the Health/Check RPC (mapped from the health plugin’s aggregate), so the plugin’s own indicator only reports that the plugin registered.
8schedulerscheduler-pluginpackages/scheduler-plugin/src/plugin/scheduler-plugin.ts:192configuration-literalcreateHealthIndicator() reports #connected (lifecycle) only — no reachability probe.
9auditaudit-pluginpackages/audit-plugin/src/plugin/audit-plugin.ts:149live-stateCLOSES H-70c-1: isReady() lifecycle + a cached, bounded sink probe — database select on a sentinel key (never an insert, which would write a fabricated record into the trail), file stat plus the last append’s outcome; memory/log report lifecycle truth.
10cache (token)cache-pluginpackages/cache-plugin/src/plugin/cache-plugin.ts:120live-stateM90b (closes H-70c-2): isReady() lifecycle + a cached, bounded reachability probe — Redis ping(); memory/noop report lifecycle truth; no probe is reachable: 'unknown', never a false true.
11database (token)database-pluginpackages/database-plugin/src/plugin/database-plugin.ts:161live-stateM90b (closes H-70c-3): uncached lifecycle-only gate (DatabaseService.isClosed, reaching no adapter), then adapter readiness through a cached, bounded probe; a Drizzle poolStats callback publishes data.capacity — data, not a threshold.
12notificationnotification-pluginpackages/notification-plugin/src/plugin/notification-plugin.ts:105live-stateCLOSES H-70c-4: per-channel reachability in data.reachable; one contacted-and-unreachable channel takes the indicator down. Email delegates to IMailer.isHealthy; the send-only transports report 'unknown', because probing them would deliver a real notification.
13worker-poolworker-pool-pluginpackages/worker-pool-plugin/src/plugin/worker-pool-plugin.ts:99live-stateCLOSES H-70c-5: the status derives from data.available — a runtime with no worker host rejects every run(), so it reports degraded with a reason rather than up. degraded not down because M45 registers this plugin on Workers deliberately, and /ready must stay 200 there. M70k’s data.exitDetection still reports whether a worker’s thread ending is observable. The pool counters stay data, never a threshold.
14cloudflarecloudflare-pluginpackages/cloudflare-plugin/src/plugin/cloudflare-plugin.ts:229justified-literalReports the billed binding read — which arms are present is a platform fact known at registration, not a backend to probe. The status is up/down on the presence of the bindings the arms need.
15feature-flagsfeature-flags-pluginpackages/feature-flags-plugin/src/plugin/feature-flags-plugin.ts:136live-stateQueries the provider’s status(); a provider that reports degraded (stale cache, fallback mode) is surfaced as degraded with the detail.
16sessionsession-pluginpackages/session-plugin/src/plugin/session-plugin.ts:131live-stateservice.storeHealth() — a store that reports unhealthy is down (a session store failure is the one invisible from outside).
17static-filesstatic-pluginpackages/static-plugin/src/plugin/static-plugin.ts:56live-stateThe no-fs runtime arm: degraded, justified — the plugin cannot serve files without a filesystem.
18static-filesstatic-pluginpackages/static-plugin/src/plugin/static-plugin.ts:85live-stateThe fs arm: a real fs.stat(root); down when the root is missing or not a directory.
19ssesse-pluginpackages/sse-plugin/src/plugin/sse-plugin.ts:78configuration-literalstatus: 'up' beside live data.connections. The SSE service has no backend to probe; the status is decorative.
20websocketwebsocket-pluginpackages/websocket-plugin/src/plugin/websocket-plugin.ts:154configuration-literalstatus: 'up' beside live data.available/connections/rooms/routes. No backend to probe.
21cqrscqrs-pluginpackages/cqrs-plugin/src/plugin/cqrs-plugin.ts:149configuration-literalstatus: 'up' beside live data.commands/queries handler counts. In-process bus, no backend.
22eventsevents-pluginpackages/events-plugin/src/plugin/events-plugin.ts:159configuration-literalstatus: 'up' beside live data.handlers. In-process bus, no backend.
23graphqlgraphql-pluginpackages/graphql-plugin/src/plugin/graphql-plugin.ts:210configuration-literalstatus: 'up' beside live data.endpoint/cachedDocuments/subscriptions. The endpoint is in-process.
24multi-tenancymulti-tenancy-pluginpackages/multi-tenancy-plugin/src/plugin/multi-tenancy-plugin.ts:292configuration-literalstatus: 'up' beside live data.resolver/strategy/store. In-process middleware, no backend.
25react-routerreact-router-pluginpackages/react-router-plugin/src/plugin/react-router-plugin.ts:245configuration-literalstatus: 'up' beside live data.mode/serverBuildPath. Stateless handler (the source notes there is no socket, pool, timer, or subscription to close).
26secretssecrets-pluginpackages/secrets-plugin/src/plugin/secrets-plugin.ts:143live-stateM90b: isReady() lifecycle + a cached, bounded reachability probe — Vault /v1/sys/health (no secret read, no token); env reports lifecycle truth; a cloud facade without the optional isHealthy() member is reachable: 'unknown'.
27viewview-pluginpackages/view-plugin/src/plugin/view-plugin.ts:77justified-literalM92: rendering is stateless and touches no backend — there is nothing to probe — so up is the correct signal, and the live data carries the real fact: the selected engine (hono-jsx, hono-html, or custom).

Out-of-scope defects recorded (not changed on this branch)

Per plan §3.9, the configuration-literal sites outside the six in-scope packages that nonetheless hide a real backend behind a decorative status were recorded in the smoke register (smoke/DEFECTS.md, which is deliberately not tracked in git — hence a plain reference rather than a link) rather than fixed on that branch. All five are now closed, and the table is kept as the record of what the audit found:

Defect IDPackageIssue
H-70c-1audit-pluginCLOSED — the audit indicator now probes the sink (database sentinel read, file stat + last append outcome).
H-70c-2cache-pluginCLOSED in M90b — the cache indicator now probes reachability (Redis ping(); unknown, never a false true).
H-70c-3database-pluginCLOSED in M90b — the database indicator gates on uncached lifecycle behind a bounded probe and publishes capacity.
H-70c-4notification-pluginCLOSED — per-channel reachability; email delegates to IMailer.isHealthy, send-only transports report 'unknown'.
H-70c-5worker-pool-pluginCLOSED — the status derives from data.available; a runtime with no worker host reports degraded with a reason.

The remaining configuration-literal rows (7, 8, 19–25) are defensible: the plugin has no external backend — the status says “this in-process capability is registered”, and the live data carries the useful part. They are recorded here so a future milestone can decide whether to reclassify them, but they are not defects in the X8-5 sense (no backend is being hidden).