Skip to content

Commit 98bd798

Browse files
feat(spec)!: the three kernel/plugin-lifecycle-advanced.zod.ts duration keys carry their unit in the key name (#17986)
Fixes #17780 Clause-②: yes Executes director-seat **ruling A on #15939** (2026-09-11, maintainer 「同意」, decision batch #115) for `packages/spec/src/kernel/plugin-lifecycle-advanced.zod.ts` — 3 rows of the 21-row delta, following the #15678 / #15679 shape. The declaration above reads the affirmative: the rename puts three spellings on a published payload no author could write before. `check-clause2-carriers --pair 17986` reads **exit 0** — declaration readable in the fixed spelling, both carriers agree. ## The three rows, re-located by symbol Each key was confirmed to be the **only** key-position declaration of that name in the file (`interval` 1, `timeout` 1, `debounceDelay` 1; lit control `z.number` 12, dark control 0), and each JSDoc was read rather than inferred from the default: | key | JSDoc, as read | published `.describe()`, before | new spelling | |:--|:--|:--|:--| | `PluginHealthCheck.interval` | "Health check interval in **milliseconds**" | "How often to perform health checks (default: 30s)" | `intervalMs` | | `PluginHealthCheck.timeout` | "Timeout for health check in **milliseconds**" | "Maximum time to wait for health check response" | `timeoutMs` | | `HotReloadConfig.debounceDelay` | "Debounce delay before reloading (**milliseconds**)" | "Wait time after change detection before reload" | `debounceDelayMs` | All three are milliseconds. The `check:duration-unit-keys` census reads all three `[name: -] [prose: -]` on `origin/main` — no unit in the name and none the gate recognises in the published prose. `interval` is the sharpest: its one unit-shaped token was the parenthetical `(default: 30s)`, naming SECONDS for a value the schema bounds at `min(1000)` and defaults to `30000` milliseconds. ## Why these spellings Counted on this tree, not assumed from the card: 100 key-position `*Ms` declarations across `packages/spec/src`, of which `timeoutMs` is 29 and `intervalMs` is 3 — both renames land on names the surface already uses. The analogous question to the sibling round's `Ttl`-versus-`TTL` was asked for `debounceDelay` and answered the other way: it is the only debounce-shaped key spelling in the repo (5 key-position occurrences, all this key and its fixtures; no `debounceMs` variant anywhere), while the Delay-plus-`Ms` pairing is already attested (`maxDelayMs`, `initialDelayMs`, `retryDelayMs`, `delayMs`). There was no competing family spelling to choose between, so it takes the plain suffix. ## The kit - `retiredKey()` tombstone on each old spelling. Neither `PluginHealthCheckSchema` nor `HotReloadConfigSchema` is `.strict()`, so a bare deletion would be a silent strip — and here the stripped value lands on a `setInterval` period, a race deadline and a `setTimeout` delay. - ADR-0087 D3 semantic entry `kernel-health-check-and-hot-reload-durations-unit-in-key` **and** three `RETIRED_KEYS_BY_MAJOR[18]` rows — both, per the PM's settlement on #15939. No D2 conversion: neither def is an authorable surface (both are library parameters a host passes to `PluginHealthMonitor` / `HotReloadManager` in TypeScript), the same disposition `plugin-auto-restart-never-reinitialised` and `hot-reload-watch-placeholder-retired` recorded for keys on these two defs. `registry.ts` is **generated** — `gen:migration-registry`, never hand-merged. - `@objectstack/core` moves with the rename (it is the only in-repo reader): both classes read the suffixed keys, and each registration-time refusal table gains a row so a host still passing an old spelling gets an ADR-0112 `VALIDATION_ERROR` / 400 naming the rename instead of `undefined` where a duration belongs. Those two runtime strings carry **no tracker id** — a runtime string reaches operators who cannot resolve one; the anchor sits in an adjacent `//` comment (`check:doc-authoring`). - Generated artifacts regenerated, never hand-edited: `authorable-surface/kernel.json`, `authorable-defaults/kernel.json`, `content/docs/references/kernel/plugin-lifecycle-advanced.mdx`. `pnpm --filter @objectstack/spec check:generated` reports 15 of 15 up to date. - Hand-written `content/docs/protocol/kernel/lifecycle.mdx` updated where it names the keys (+8 / −6). ## The authorable-surface ratchet MOVES here, and that is correct `authorable-surface/kernel.json` records **top-level keys per def**. All three of these keys are top level, so the rename must move the ledger — three `[RETIRED]` markers added beside three new suffixed rows, plus the matching `authorable-defaults` moves. This is the opposite reading from sibling PR #17954, whose key was **nested** (`performance.schemaCacheTTL`) and correctly moved nothing: different readings, same rule, both right. Regenerated by `gen:schema`, never hand-edited. ## Pin tests replaced, not deleted Two minimum-bound pins are the ones worth calling out. Spelled through the bare keys, `PluginHealthCheckSchema.parse({ interval: 500 })` would have kept throwing — off the **tombstone's refusal**, not the `min(1000)` bound, a pin that can no longer fail. They now assert the `too_small` issue code on the suffixed keys plus an accepting control at the boundary. The default and fixture pins were re-pointed at the new spellings with `not.toHaveProperty` on the old ones; nothing was deleted, weakened or skipped. New pins: the refusal carries the rename prescription (asserting the issue is not `unrecognized_keys`), the suffixed keys parse at the magnitude the retired ones carried with the same defaults, the describes publish the unit, and both core classes refuse an old spelling at `registerPlugin` before the plugin is stored, with an accepting control beside each. ## Consumer sweep — `@objectstack/core` is the only reader | subject, outside `packages/spec` and `packages/core` | occurrences | |:--|--:| | `PluginHealthCheckParsed` / `HotReloadConfigParsed` | 0 / 0 | | `PluginHealthCheck` · `HotReloadConfig` · `PluginHealthMonitor` · `HotReloadManager` | 4 · 6 · 2 · 7 — **all prose**: changesets, the generated upgrade guide, a `//` comment in a `packages/rest` pin test, and two CHANGELOG lines. Zero code readers. | | the three keys in a health-check / hot-reload context | 0 | Lit controls on the same corpus: `@objectstack/spec` 14580, `PluginContext` 973, `ObjectKernel` 588. Dark control `zzqqxx` 0. ## Pinned objectui: 0, with controls `.objectui-sha` = `53ded82bf7a494f54e344e19099dbf00854b8694` — this IS the pin we build against (checked here). Grepped at that sha across its 6409 tracked files: all **thirteen** exports of `plugin-lifecycle-advanced.zod.ts` occur 0 times, and `debounceDelay` occurs 0 times. Lit controls on the same corpus: `objectstack` 10171, `@objectstack/spec` 3479, `timeout` 832, `interval` 156. Dark control `zzqqxx` 0. Console Pin Gate is unaffected. ## Verification | command | verdict | |:--|:--| | `pnpm --filter @objectstack/spec build` | `VERDICT command-exit 0` | | `pnpm --filter @objectstack/spec check:generated` | exit 0 — 15 of 15 artifacts current | | `pnpm --filter '@objectstack/core^...' build` | `VERDICT command-exit 0` | | `pnpm --filter @objectstack/core build` | `VERDICT command-exit 0` | | `pnpm --filter @objectstack/core test` | `VERDICT command-exit 0` — 51 files, 1316 tests | | `pnpm --filter @objectstack/spec test` | `VERDICT command-exit 0` — 475 files, 13517 tests | | `pnpm --filter @objectstack/spec --filter @objectstack/core typecheck` | `VERDICT command-exit 0` | | `dispatch-gates --commands` → all 110, reconciled with `--ran … :: exit N` | **104 exit 0 · 0 red · 6 exit 3** | The six exit-3s are `PREREQUISITE NOT MET` on unbuilt sibling workspace packages outside this change's build closure (`@objectstack/lint`, `@objectstack/formula`) — NOT MEASURED by the gates' own classification, and built by CI. `check:skill-examples` initially refused with **exit 1** on an unbuilt `@objectstack/client-react` — the refusal shape that a `--ran` reconciler counts as run; it was measured properly after building that closure and reads exit 0 (258 prose examples across 3 surfaces). ## Acceptance notes - **`HotReloadConfig.shutdownTimeout` is deliberately not renamed with these three.** Its JSDoc reads "Graceful shutdown timeout" and names no unit anywhere — it is the unit-nowhere shape the #14478 gate leaves outside its verdict by measurement, and it is not one of the 21 rows PR #17635 enumerates. Noted, not filed: the gate's own `--list` census already keeps it visible, and its header records that closing that class is a describe-by-describe decision rather than a defect. - `check-widening-tells` raising T1 on the `retiredKey()` tombstone lines is the known inverted false positive (#17955) — the lines it fires on make the accept set strictly narrower. The diff is not reshaped and the checker is not weakened. - **Docs Drift Check named six pages; five are false positives.** Its anchor is a bare `timeoutMs` string literal inside the new retirement constant, so it listed every page using that generic word: `automation/jobs.mdx`, `automation/webhooks.mdx`, `automation/flows.mdx`, `deployment/environment-variables.mdx` and `automation/hook-bodies.mdx` — job scheduling, webhook retry backoff and the ADR-0102 sandbox CPU budget, none of them `PluginHealthCheck` or `HotReloadConfig` (0 plugin-health mentions each; `hook-bodies.mdx`'s two "hot-reload" hits are Studio in-browser editing). The sixth, `protocol/kernel/lifecycle.mdx`, is the genuine hit and is edited here. The three `content/docs/releases/` pages are release-owned and untouched. Recorded because the four remaining rename cards in this epic will trip the same anchor. ⛔ Draft on purpose: the in-seat clause-② review at tier is owed first, and landing is the PM's step. --- _Generated by [Claude Code](https://claude.ai/code/session_015c5G6TmpMKgnusmTpD7Ntt)_ Co-authored-by: Claude <noreply@anthropic.com>
1 parent c45ea8c commit 98bd798

16 files changed

Lines changed: 683 additions & 76 deletions
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
---
2+
"@objectstack/spec": minor
3+
"@objectstack/core": minor
4+
---
5+
6+
feat(spec)!: the three `kernel/plugin-lifecycle-advanced.zod.ts` duration keys carry their unit in the key name (#17780, ruling A on #15939)
7+
8+
<!-- adr-0087: registered kernel-health-check-and-hot-reload-durations-unit-in-key -->
9+
10+
**BREAKING** — the health-check period, the health-check deadline and the hot-reload debounce
11+
now carry `Ms` in the key name.
12+
13+
| | before | after |
14+
|:--|:--|:--|
15+
| `PluginHealthCheck` | `interval: 30000` | `intervalMs: 30000` |
16+
| `PluginHealthCheck` | `timeout: 5000` | `timeoutMs: 5000` |
17+
| `HotReloadConfig` | `debounceDelay: 1000` | `debounceDelayMs: 1000` |
18+
| values, defaults, min bounds | ms; 30000 / 5000 / 1000; min 1000 / 100 / 0 | **unchanged** |
19+
20+
## Migration
21+
22+
```diff
23+
const health = PluginHealthCheckSchema.parse({
24+
- interval: 30000,
25+
- timeout: 5000,
26+
+ intervalMs: 30000,
27+
+ timeoutMs: 5000,
28+
});
29+
30+
hotReload.registerPlugin('my-plugin', {
31+
- debounceDelay: 1000,
32+
+ debounceDelayMs: 1000,
33+
});
34+
```
35+
36+
Rename the keys. Every value is the same number of milliseconds it always was, and the
37+
30000 / 5000 / 1000 defaults are unchanged; nothing else on either def moves.
38+
39+
## Why
40+
41+
Each key named milliseconds in a source JSDoc — "Health check interval in milliseconds",
42+
"Timeout for health check in milliseconds", "Debounce delay before reloading (milliseconds)" —
43+
and the JSDoc above a key is not what `content/docs/references/**` renders; `.describe()` is.
44+
Measured by the `check:duration-unit-keys` census on this tree, all three read
45+
`[name: -] [prose: -]`: no unit in the name and none in the published prose either.
46+
`interval` was the sharpest of the three — its describe carried one unit-shaped token, the
47+
parenthetical "(default: 30s)", naming SECONDS for a value the schema bounds and defaults in
48+
MILLISECONDS. Executes director-seat ruling A on #15939 (2026-09-11, maintainer 「同意」,
49+
decision batch #115), the per-file remediation of the #14478 rule.
50+
51+
The suffix is the family's own spelling, counted on this tree: 100 key-position `*Ms`
52+
declarations across `packages/spec`, `timeoutMs` 29 of them and `intervalMs` 3.
53+
`debounceDelay` takes the plain suffix rather than a shortened form because it is the only
54+
debounce-shaped key spelling in the repo (no `debounceMs` variant anywhere) while the
55+
Delay-plus-`Ms` pairing is already attested (`maxDelayMs`, `initialDelayMs`, `retryDelayMs`,
56+
`delayMs`) — so unlike the `Ttl`-versus-`TTL` question the sibling round settled, there was no
57+
competing family spelling to choose between.
58+
59+
## The kit
60+
61+
- a `retiredKey()` tombstone on each old spelling, so `tsc` types it `never` and a value
62+
reaching the parse raises the rename prescription instead of being silently stripped —
63+
neither `PluginHealthCheckSchema` nor `HotReloadConfigSchema` is `.strict()`, and here the
64+
stripped value would land on a `setInterval` period, a race deadline and a `setTimeout` delay
65+
- the ADR-0087 D3 semantic entry `kernel-health-check-and-hot-reload-durations-unit-in-key` and
66+
three `RETIRED_KEYS_BY_MAJOR[18]` rows. No D2 conversion: neither def is an authorable
67+
surface — both are library parameters a host passes to `PluginHealthMonitor` /
68+
`HotReloadManager` in TypeScript — so the chain has no seam that runs on them, the same
69+
reading `plugin-auto-restart-never-reinitialised` and `hot-reload-watch-placeholder-retired`
70+
recorded for keys on these two defs
71+
- `@objectstack/core` moves with the rename: `PluginHealthMonitor` and `HotReloadManager` read
72+
the suffixed keys, and each class's registration-time refusal table gains a row so a host
73+
still passing an old spelling is answered with an ADR-0112 `VALIDATION_ERROR` / 400 naming
74+
the rename, rather than getting `undefined` where a duration belongs
75+
- pin tests on both schemas and both classes: the refusal carries the rename prescription, the
76+
suffixed keys parse at the magnitude the retired ones carried with the same defaults, and the
77+
describes publish the unit. The two minimum-bound pins were rewritten rather than left: spelled
78+
through the bare keys they would have stayed green off the tombstone's refusal instead of the
79+
bound, so they now assert the `too_small` issue code on the suffixed keys
80+
- `HotReloadConfig.shutdownTimeout` is deliberately NOT renamed with them — its JSDoc reads
81+
"Graceful shutdown timeout" and names no unit anywhere, so it is the unit-nowhere shape the
82+
#14478 gate leaves outside its verdict, not part of this row set

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

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -693,7 +693,7 @@ export const salesforcePlugin = {
693693
const monitor = new PluginHealthMonitor(kernel.logger);
694694

695695
// `registerPlugin` takes the PARSED config, so parse it: the schema fills in
696-
// interval 30000, timeout 5000, failureThreshold 3 and successThreshold 1.
696+
// intervalMs 30000, timeoutMs 5000, failureThreshold 3 and successThreshold 1.
697697
monitor.registerPlugin(
698698
salesforcePlugin.name,
699699
PluginHealthCheckSchema.parse({ checkMethod: 'healthCheck' }),
@@ -702,9 +702,11 @@ monitor.registerPlugin(
702702
monitor.startMonitoring(salesforcePlugin.name, salesforcePlugin);
703703
```
704704

705-
`startMonitoring` runs one check immediately, then repeats every `interval`
706-
milliseconds; each run is raced against `timeout`, and the method may be
707-
synchronous or return a promise.
705+
`startMonitoring` runs one check immediately, then repeats on the
706+
`intervalMs` period; each run is raced against `timeoutMs`, and the method may
707+
be synchronous or return a promise. Both keys carry their unit in the name —
708+
they were renamed from `interval` / `timeout` in @objectstack/spec 17, and the
709+
old spellings are refused at `registerPlugin` with the rename.
708710

709711
Only two returned shapes count as a failure: `false`, and an object whose
710712
`status` is exactly `'unhealthy'` (whose `message`, if any, becomes the
@@ -713,7 +715,7 @@ report's). **Everything else passes** — `true`, `undefined`,
713715
nowhere to publish latency or row counts: no key beyond `status` and `message`
714716
is read. Consecutive returned failures move the plugin to `degraded` first, and
715717
to `unhealthy` only once `failureThreshold` of them accumulate. A check that
716-
**throws** — including one that exceeds `timeout` — is the separate `failed`
718+
**throws** — including one that exceeds `timeoutMs` — is the separate `failed`
717719
status, applied immediately with no threshold.
718720

719721
Recovery is the mirror of that half, and `successThreshold` is its counter: the
@@ -787,7 +789,7 @@ this shape over HTTP — it is an in-process model, not a wire body.
787789
| :--- | :--- |
788790
| the plugin's configured `checkMethod` | the custom check ran and returned — `"passed"`, or `"failed"` for the two failing shapes above |
789791
| `"plugin-loaded"` | no `checkMethod` is configured, **or** the configured name does not resolve to a function on the plugin |
790-
| `"health-check"` | the check **threw** — a `timeout` overrun included, since the race surfaces it as a rejection. A fixed name, neither the method's nor the default's, and always `status: "failed"` |
792+
| `"health-check"` | the check **threw** — a `timeoutMs` overrun included, since the race surfaces it as a rejection. A fixed name, neither the method's nor the default's, and always `status: "failed"` |
791793

792794
`metrics.uptimeMs` is in **milliseconds** (`Date.now() - startTime`), unlike
793795
the seconds-valued `uptime` of `GET /health` above — which is the very

‎content/docs/references/kernel/plugin-lifecycle-advanced.mdx‎

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@ const result = HotReloadConfigSchema.parse(data);
4242
| :--- | :--- | :--- | :--- |
4343
| **enabled** | `boolean` | optional (default: `false`) | |
4444
| **watchPatterns** | `never` | optional | [REMOVED] `HotReloadConfig.watchPatterns` was removed in @objectstack/spec 18 (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. |
45-
| **debounceDelay** | `integer` | optional (default: `1000`) | Wait time after change detection before reload |
45+
| **debounceDelayMs** | `integer` | optional (default: `1000`) | Wait time after change detection before reload, in milliseconds |
46+
| **debounceDelay** | `never` | optional | [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. |
4647
| **preserveState** | `boolean` | optional (default: `true`) | Keep plugin state across reloads |
4748
| **stateStrategy** | `Enum<'memory' \| 'none'>` | optional (default: `"memory"`) | How to preserve state during reload |
4849
| **shutdownTimeout** | `integer` | optional (default: `30000`) | Maximum time to wait for graceful shutdown |
@@ -58,14 +59,16 @@ const result = HotReloadConfigSchema.parse(data);
5859

5960
| Property | Type | Required | Description |
6061
| :--- | :--- | :--- | :--- |
61-
| **interval** | `integer` | optional (default: `30000`) | How often to perform health checks (default: 30s) |
62-
| **timeout** | `integer` | optional (default: `5000`) | Maximum time to wait for health check response |
62+
| **intervalMs** | `integer` | optional (default: `30000`) | How often to perform health checks, in milliseconds |
63+
| **timeoutMs** | `integer` | optional (default: `5000`) | Maximum time to wait for health check response, in milliseconds |
6364
| **failureThreshold** | `integer` | optional (default: `3`) | Consecutive failures needed to mark unhealthy |
6465
| **successThreshold** | `integer` | optional (default: `1`) | Consecutive successes needed to mark healthy |
6566
| **checkMethod** | `string` | optional | Method name to call for health check |
6667
| **autoRestart** | `never` | optional | [REMOVED] `PluginHealthCheck.autoRestart` was removed in @objectstack/spec 18 (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. |
6768
| **maxRestartAttempts** | `never` | optional | [REMOVED] `PluginHealthCheck.maxRestartAttempts` was removed in @objectstack/spec 18 (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. |
6869
| **restartBackoff** | `never` | optional | [REMOVED] `PluginHealthCheck.restartBackoff` was removed in @objectstack/spec 18 (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. |
70+
| **interval** | `never` | optional | [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. |
71+
| **timeout** | `never` | optional | [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. |
6972

7073

7174
---

0 commit comments

Comments
 (0)