Skip to content

Commit f7eff23

Browse files
os-steveclaude
andauthored
docs(protocol): document the real checkMethod health-check contract (#11822)
The TypeScript example under "Custom Health Checks" declared `healthChecks`, a map of async functions taking `({ context })`, on the plugin default export. No such field exists: measured as an author-writable field it has 0 occurrences (the hits in packages/ are PluginHealthMonitor's own private Map plus one example reading it). The real configuration is PluginHealthCheckSchema.checkMethod — a method NAME, invoked with no arguments, whose return is read only for `false` / `{ status: 'unhealthy' }`. Rewrite the example against that contract, in the two halves it actually has: the plugin exposes a plain method, and the embedding application constructs PluginHealthMonitor and names the method in the parsed config. No declarative plugin field is shown, which keeps the section's callout ("not yet ... as a declarative plugin field") true — that callout was verified correct and is left untouched, as is the PluginHealthReport block below. Claude-Session: https://claude.ai/code/session_015ahemw8RcTgqtxrj15PEZx Co-authored-by: Claude <noreply@anthropic.com>
1 parent 0d8bac2 commit f7eff23

1 file changed

Lines changed: 47 additions & 22 deletions

File tree

‎content/docs/protocol/kernel/lifecycle.mdx‎

Lines changed: 47 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -665,34 +665,59 @@ health-monitor model below, not to this body.
665665

666666
### Custom Health Checks
667667

668-
Plugins can register custom health checks:
668+
A plugin can expose **one** custom health check, and it takes two halves: the
669+
plugin defines an ordinary method, and whoever runs the monitor names that
670+
method in the plugin's `PluginHealthCheck` config. There is no `healthChecks`
671+
field to declare — the monitor resolves the method dynamically off the plugin
672+
object (`plugin[checkMethod]`), so it is not a member of the `Plugin` interface
673+
either.
669674

670675
```typescript
671-
// Plugin registers a custom health check (internal health-monitor model)
672-
export default {
676+
import { PluginHealthMonitor } from '@objectstack/core';
677+
import { PluginHealthCheckSchema } from '@objectstack/spec/kernel';
678+
679+
// 1. The plugin side — a plain method, invoked with NO arguments.
680+
export const salesforcePlugin = {
673681
name: '@vendor/salesforce',
674682

675-
healthChecks: {
676-
salesforce_connection: async ({ context }) => {
677-
try {
678-
// Test Salesforce API
679-
const response = await salesforce.query('SELECT Id FROM Account LIMIT 1');
680-
681-
return {
682-
status: 'healthy',
683-
latency_ms: response.duration,
684-
records_synced_last_hour: await getSyncCount(),
685-
};
686-
} catch (error) {
687-
return {
688-
status: 'unhealthy',
689-
error: error.message,
690-
};
691-
}
692-
},
683+
// The name is free; `checkMethod` below is what points at it.
684+
async healthCheck() {
685+
try {
686+
await salesforce.query('SELECT Id FROM Account LIMIT 1');
687+
return true;
688+
} catch (error) {
689+
return { status: 'unhealthy', message: error.message };
690+
}
693691
},
694692
};
695-
```
693+
694+
// 2. The host side — the embedding application owns the monitor.
695+
const monitor = new PluginHealthMonitor(kernel.logger);
696+
697+
// `registerPlugin` takes the PARSED config, so parse it: the schema fills in
698+
// interval 30000, timeout 5000, failureThreshold 3, successThreshold 1,
699+
// autoRestart false, maxRestartAttempts 3, restartBackoff 'exponential'.
700+
monitor.registerPlugin(
701+
salesforcePlugin.name,
702+
PluginHealthCheckSchema.parse({ checkMethod: 'healthCheck' }),
703+
);
704+
705+
monitor.startMonitoring(salesforcePlugin.name, salesforcePlugin);
706+
```
707+
708+
`startMonitoring` runs one check immediately, then repeats every `interval`
709+
milliseconds; each run is raced against `timeout`, and the method may be
710+
synchronous or return a promise.
711+
712+
Only two returned shapes count as a failure: `false`, and an object whose
713+
`status` is exactly `'unhealthy'` (whose `message`, if any, becomes the
714+
report's). **Everything else passes** — `true`, `undefined`,
715+
`{ status: 'healthy' }` and a rich object of metrics alike, so a check has
716+
nowhere to publish latency or row counts: no key beyond `status` and `message`
717+
is read. Consecutive returned failures move the plugin to `degraded` first, and
718+
to `unhealthy` only once `failureThreshold` of them accumulate. A check that
719+
**throws** — including one that exceeds `timeout` — is the separate `failed`
720+
status, applied immediately with no threshold.
696721

697722
The monitor keeps one report per plugin rather than one aggregate document. Each
698723
round of checks builds a `PluginHealthReport` (`@objectstack/spec/kernel`,

0 commit comments

Comments
 (0)