ObjectStackObjectStack

Plugin Lifecycle Advanced — Kernel Protocol

Declares the INPUT contracts of the host-driven lifecycle classes exported by @objectstack/core — PluginHealthMonitor.

Advanced Plugin Lifecycle — host-driven library vocabularies

Declares the INPUT contracts of the host-driven lifecycle classes exported by @objectstack/core — PluginHealthMonitor (reads PluginHealthCheck, emits PluginHealthStatus / PluginHealthReport) and HotReloadManager (reads HotReloadConfig, snapshots via PluginStateSnapshot). The kernel does not construct either class: a HOST composes them and passes these shapes directly (content/docs/protocol/kernel/lifecycle.mdx, the #11811 examples, is the supported usage).

This module deliberately declares NO authorable configuration surface — see the #11825 retirement record below.

Source: packages/spec/src/kernel/plugin-lifecycle-advanced.zod.ts

TypeScript Usage

import { HotReloadConfigSchema, PluginHealthCheckSchema, PluginHealthReportSchema, PluginHealthStatusSchema, PluginStateSnapshotSchema } from '@objectstack/spec/kernel';
import type { HotReloadConfig, PluginHealthCheck, PluginHealthReport, PluginHealthStatus, PluginStateSnapshot } from '@objectstack/spec/kernel';

// Validate data
const result = HotReloadConfigSchema.parse(data);

HotReloadConfig

Properties

PropertyTypeRequiredDescription
enabledbooleanoptional (default: false)
watchPatternsneveroptional[REMOVED] HotReloadConfig.watchPatterns was removed in @objectstack/spec 17.3.0 (ADR-0049 enforce-or-remove) — nothing ever read it. Its only two uses were log lines in HotReloadManager, and one of them announced 'File watching started' at INFO level while no watcher was ever constructed: startWatching held a placeholder, and watchHandles was read, deleted, iterated and cleared but never set. So an author could declare a glob and no file change could ever trigger a reload. Delete the key. File watching is the HOST's job in this host-driven library: run your own watcher, declare your globs wherever that watcher reads them, and call HotReloadManager.scheduleReload(pluginName, reloadFn) when one matches — the debounced integration point this class does implement, and which is unchanged.
debounceDelayMsintegeroptional (default: 1000)Wait time after change detection before reload, in milliseconds
debounceDelayneveroptional[REMOVED] HotReloadConfig.debounceDelay was renamed to debounceDelayMs in @objectstack/spec 17 — the unit of a duration-shaped number lives in the key name, not only in the describe prose. Its unit (milliseconds) lived in a source JSDoc only and the published describe named none, so the reference-page reader got a bare 1000. Rename the key to debounceDelayMs; the value (milliseconds) and the 1000 default are unchanged.
preserveStatebooleanoptional (default: true)Keep plugin state across reloads
stateStrategyEnum<'memory' | 'none'>optional (default: "memory")How to preserve state during reload
shutdownTimeoutintegeroptional (default: 30000)Maximum time to wait for graceful shutdown, in milliseconds
beforeReloadstring[]optionalHook names to call before reload
afterReloadstring[]optionalHook names to call after reload

PluginHealthCheck

Properties

PropertyTypeRequiredDescription
intervalMsintegeroptional (default: 30000)How often to perform health checks, in milliseconds
timeoutMsintegeroptional (default: 5000)Maximum time to wait for health check response, in milliseconds
failureThresholdintegeroptional (default: 3)Consecutive failures needed to mark unhealthy
successThresholdintegeroptional (default: 1)Consecutive successes needed to mark healthy
checkMethodstringoptionalMethod name to call for health check
autoRestartneveroptional[REMOVED] PluginHealthCheck.autoRestart was removed in @objectstack/spec 17.3.0 (ADR-0049 enforce-or-remove) — it never restarted a plugin.A PluginHealthMonitor never restarted anything. attemptRestart called plugin.destroy() and stopped there — the in-source comment said "Call destroy and init to restart", but init appeared in health-monitor.ts ONLY inside that comment. What a plugin actually got was: destroy, a log line reading 'Plugin restarted', status recovering, and periodic health checks continuing against the destroyed instance — which the default check ({ name: 'plugin-loaded', status: 'passed' }, used whenever no checkMethod resolves) passes forever, so the terminal report on a destroyed, never-re-initialised plugin was healthy. Delete the key. Restarting a plugin is the HOST's job in this host-driven library, and the monitor could not do it even in principle: Plugin.init(ctx) needs a PluginContext, which only the kernel constructs and which it exposes to nobody (ObjectKernel.context is private; KernelBase.createContext is protected). Poll getHealthStatus(pluginName) / getHealthReport(pluginName) and act on unhealthy / failed at the level that owns the plugin's lifetime — recreate the kernel, or let your supervisor restart the process. The monitor reports; it does not act.
maxRestartAttemptsneveroptional[REMOVED] PluginHealthCheck.maxRestartAttempts was removed in @objectstack/spec 17.3.0 (ADR-0049 enforce-or-remove) — it capped a restart that never happened.A PluginHealthMonitor never restarted anything. attemptRestart called plugin.destroy() and stopped there — the in-source comment said "Call destroy and init to restart", but init appeared in health-monitor.ts ONLY inside that comment. What a plugin actually got was: destroy, a log line reading 'Plugin restarted', status recovering, and periodic health checks continuing against the destroyed instance — which the default check ({ name: 'plugin-loaded', status: 'passed' }, used whenever no checkMethod resolves) passes forever, so the terminal report on a destroyed, never-re-initialised plugin was healthy. The cap counted destroy calls, so raising it only scheduled further "restarts" of a plugin that was never brought back up. Delete the key. Restarting a plugin is the HOST's job in this host-driven library, and the monitor could not do it even in principle: Plugin.init(ctx) needs a PluginContext, which only the kernel constructs and which it exposes to nobody (ObjectKernel.context is private; KernelBase.createContext is protected). Poll getHealthStatus(pluginName) / getHealthReport(pluginName) and act on unhealthy / failed at the level that owns the plugin's lifetime — recreate the kernel, or let your supervisor restart the process. The monitor reports; it does not act.
restartBackoffneveroptional[REMOVED] PluginHealthCheck.restartBackoff was removed in @objectstack/spec 17.3.0 (ADR-0049 enforce-or-remove) — it delayed a restart that never happened.A PluginHealthMonitor never restarted anything. attemptRestart called plugin.destroy() and stopped there — the in-source comment said "Call destroy and init to restart", but init appeared in health-monitor.ts ONLY inside that comment. What a plugin actually got was: destroy, a log line reading 'Plugin restarted', status recovering, and periodic health checks continuing against the destroyed instance — which the default check ({ name: 'plugin-loaded', status: 'passed' }, used whenever no checkMethod resolves) passes forever, so the terminal report on a destroyed, never-re-initialised plugin was healthy. The chosen strategy only moved when the destroy landed. Delete the key. Restarting a plugin is the HOST's job in this host-driven library, and the monitor could not do it even in principle: Plugin.init(ctx) needs a PluginContext, which only the kernel constructs and which it exposes to nobody (ObjectKernel.context is private; KernelBase.createContext is protected). Poll getHealthStatus(pluginName) / getHealthReport(pluginName) and act on unhealthy / failed at the level that owns the plugin's lifetime — recreate the kernel, or let your supervisor restart the process. The monitor reports; it does not act.
intervalneveroptional[REMOVED] PluginHealthCheck.interval was renamed to intervalMs in @objectstack/spec 17 — the unit of a duration-shaped number lives in the key name, not only in the describe prose. Its unit (milliseconds) lived in a source JSDoc only, and the published describe named no unit: its one unit-shaped token was the parenthetical "(default: 30s)", which names SECONDS for a value carried in milliseconds. Rename the key to intervalMs; the value (milliseconds) and the 30000 default are unchanged.
timeoutneveroptional[REMOVED] PluginHealthCheck.timeout was renamed to timeoutMs in @objectstack/spec 17 — the unit of a duration-shaped number lives in the key name, not only in the describe prose. Its unit (milliseconds) lived in a source JSDoc only and the published describe named none, so the reference-page reader got a bare 5000. Rename the key to timeoutMs; the value (milliseconds) and the 5000 default are unchanged.

PluginHealthReport

Properties

PropertyTypeRequiredDescription
statusEnum<'healthy' | 'degraded' | 'unhealthy' | 'failed' | 'recovering' | 'unknown'>✅Current health status of the plugin
timestampstring✅
messagestringoptional
metrics{ uptimeMs?: number; memoryUsage?: number; cpuUsage?: number; activeConnections?: number; … }optional
checks{ name: string; status: Enum<'passed' | 'failed' | 'warning'>; message?: string; data?: Record<string, any> }[]optional
dependencies{ pluginId: string; status: Enum<'healthy' | 'degraded' | 'unhealthy' | 'failed' | 'recovering' | 'unknown'>; message?: string }[]optional

Nested Shape: PluginHealthReport.metrics

PropertyTypeRequiredDescription
uptimeMsnumberoptionalPlugin uptime in milliseconds
memoryUsagenumberoptionalMemory usage in bytes
cpuUsagenumberoptionalCPU usage percentage
activeConnectionsnumberoptionalNumber of active connections
errorRatenumberoptionalError rate (errors per minute)
responseTimeMsnumberoptionalAverage response time in ms
uptimeneveroptional[REMOVED] PluginHealthReport.metrics.uptime was renamed to uptimeMs in @objectstack/spec 17 — the unit of a duration-shaped number lives in the key name, not only in the describe prose, and this platform already spells a SECONDS-valued uptime with the same bare name on GET /health. Rename the key to uptimeMs; the value (milliseconds, Date.now() - startTime) is unchanged.
responseTimeneveroptional[REMOVED] PluginHealthReport.metrics.responseTime was renamed to responseTimeMs in @objectstack/spec 17 — the unit of a duration-shaped number lives in the key name, not only in the describe prose. Rename the key to responseTimeMs; the value (milliseconds) is unchanged.

Nested Shape: PluginHealthReport.checks[number]

PropertyTypeRequiredDescription
namestring✅Check name
statusEnum<'passed' | 'failed' | 'warning'>✅
messagestringoptional
dataRecord<string, any>optional

Nested Shape: PluginHealthReport.dependencies[number]

PropertyTypeRequiredDescription
pluginIdstring✅
statusEnum<'healthy' | 'degraded' | 'unhealthy' | 'failed' | 'recovering' | 'unknown'>✅Current health status of the plugin
messagestringoptional

PluginHealthStatus

Current health status of the plugin

Allowed Values

  • healthy
  • degraded
  • unhealthy
  • failed
  • recovering
  • unknown

PluginStateSnapshot

Properties

PropertyTypeRequiredDescription
pluginIdstring✅
versionstring✅
timestampstring✅
stateRecord<string, any>✅
metadata{ checksum?: string; compressed?: boolean; encryption?: string }optional

Nested Shape: PluginStateSnapshot.metadata

PropertyTypeRequiredDescription
checksumstringoptionalState checksum for verification
compressedbooleanoptional (default: false)
encryptionstringoptionalEncryption algorithm if encrypted

On this page