Repository navigation
lifecycle.mdx's healthChecks TypeScript example does not match PluginHealthCheckSchema — the real config is checkMethod #11811
Description
Activity
- addeddocumentationImprovements or additions to documentationImprovements or additions to documentation
on Aug 24, 2026 Claim: devx lane PM seat, session
e2eac1a7-8000-5c95-9749-38aec2ace6fc, branchclaude/issue-11811-healthchecks-config-shape.Restart-when:is satisfied. PR #11812 landed ase43b18fd9, socontent/docs/protocol/kernel/lifecycle.mdxis free of in-flight work from this lane.The judgement this card turns on, unchanged
Its filer declined the in-place fix on a condition, not on effort: the callout already marks the block aspirational ("not yet … as a declarative plugin field"), so choosing between rewrite against
checkMethodand keep the intended shape and mark it unimplemented is a design question, and the bounded-exemption condition that the correct form be pinned by existing evidence fails.That reasoning stands and is why this is its own card. ⛔ Do not treat the choice as settled by the fact that a card now exists.
What is measured
healthChecksas an author-writable field: 0 occurrences (the hits inpackages/arePluginHealthMonitor's own private Map plus one example reading it). Positive controlcheckMethod: 8 — I confirmed that count independently. The real config isPluginHealthCheckSchema.checkMethod(:36-84), a method name, invoked with no arguments (health-monitor.ts:109-111), whose return is read only forfalse/{status:'unhealthy'}(:116).⚠️ Note the direction, established while accepting #11812: an author does not produce aPluginHealthReport— the monitor builds it and the author reads it back viagetHealthReport(). Whatever you write must not re-introduce that confusion one field over.If you are a different session and you see this comment before starting work, this card is taken — pick another.
Generated by Claude Code
{ "issue": 11811, "status": "done", "branch": "claude/issue-11811-healthchecks-config-shape", "pr": "https://github.com/objectstack-ai/objectstack/pull/11822", "premise_still_valid": true, "summary": "Route A (rewrite against checkMethod), with a correction the callout forced. I read the callout before choosing, as instructed: it is CORRECT, and that is what made the page self-contradictory rather than merely aspirational — it says the checks are 'not yet ... exposed as a declarative plugin field', and the block underneath demonstrated exactly such a field under the present-tense lead-in 'Plugins can register custom health checks:'. So the defect is not 'the example names the wrong field' but 'the example showed a declarative plugin field at all'. That matters, because route A as the card sketched it would have reproduced the error one field over: nothing in the kernel reads AdvancedPluginLifecycleConfig.health either (its only repo-wide references are the schema file and its own tests), and kernel.ts never mentions the health monitor — PluginHealthMonitor is constructed in exactly one place, packages/core/examples/phase2-integration.ts:53. It IS exported from @objectstack/core (index.ts:76), so the capability is real but HOST-DRIVEN, not declarative. The rewritten example therefore shows two halves — the plugin exposes a plain method; the embedding application constructs the monitor and names that method in the parsed config — and no manifest field, which keeps the callout true. Route B was rejected on evidence: healthChecks has no design record anywhere (zero hits in packages/spec, the ADRs, the roadmap, any other doc), and a repo audit note already classed it with the fictional definePlugin() (docs/audits/2026-06-handwritten-docs-accuracy-followups.md:319) — there is no published intention for B to preserve. The example does not have the author build a PluginHealthReport; the monitor builds it and getHealthReport() reads it back, per the #11812 ruling.", "tests": "Docs-only card, so the evidence is CORRESPONDENCE plus gate greens — no ablation was performed and none would be meaningful here (nothing executable changed); saying so rather than filling that field with a template. MEASUREMENTS, each zero with its positive control: `healthChecks` as an author-writable field = 0 (the 7 raw hits are PluginHealthMonitor's own private Map at health-monitor.ts:19,35,51,301, one example reading that private map at examples/phase2-integration.ts:151, the doc block itself, and the 2026-06 audit note) — positive control `checkMethod` = 8 in packages/ (schema :64, spec test :52, consumer :109,111,119,121, its TSDoc :329, core test :108), reproducing the filer's count exactly. CONTRACT verified end to end (Zone 2): declared as a method NAME in PluginHealthCheckSchema.checkMethod; invoked with NO arguments at health-monitor.ts:111; may be sync or async and is raced against `timeout` (raceCheckTimeout TSDoc :329-350); return read only for `false` / `{status:'unhealthy'}` at :116, whose `message` becomes the report's; consecutive returned failures give `degraded` first and `unhealthy` only at failureThreshold (:150-164); a THROW — including a timeout overrun — is a separate `failed`, applied immediately with no threshold (:165-170). Every element of the new example traces to a source, and the plugin shape matches the repo's OWN fixture (health-monitor.test.ts:113-122 uses a `healthCheck` method returning `true`, with checkMethod:'healthCheck' at :108, cast `as unknown as Plugin` — which is also the evidence for the prose claim that the method is not a member of the Plugin interface; core/src/types.ts:99-171 has no index signature). The seven default values quoted in the example's comment are pinned green by an existing test, plugin-lifecycle-advanced.test.ts:32-41. CONTROLS (Zone 3), by checksum over the final tree, not inspection: the PluginHealthReport block is BYTE-IDENTICAL — sha256 83a4432f7644ccb7e65f1b49dfec8b8a3485d6f1030b6d40d346a2b74dd3112f before and after, 5121 bytes from 'The monitor keeps one report per plugin' to EOF; and the adjacent already-correct claim (the callout itself, plus the GET /health prose) is byte-identical — sha256 5217096945515f3226fe6479e21ec66198fb106171b8a2e11e48ea565d004fd3, 25607 bytes from SOF through the '### Custom Health Checks' heading. `git diff -U0` shows no changed line beyond old line 696. Diff is 1 file, +47/-22. GATES: derived mechanically via `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack` with no hand-built path list (it self-derived 1 path vs merge base e43b18fd9), then RE-RUN at the shipping commit e98175b1a — 17 derived families + check:nul-bytes, ALL exit 0, quoting their own verdict lines: '✓ doc authoring guard: 389 files clean — no bare metadata literals.' / '✅ check-doc-anchors: 278 internal #fragment link(s) across 408 source file(s) all resolve to a real heading' / '✓ check:doc-formula-expressions: 22 record-scoped formula example(s) across 421 files / 1448 TS blocks judged clean by @objectstack/formula.' / '✅ 26 ObjectSchema.create example(s) in 227 marked block(s) across 237 prose file(s) in 2 root(s) carry an os validate-clean security posture' / '✓ docs-accuracy-audit scope is in sync with content/docs/: 189 hand-written doc(s).' All ran through scripts/pm/os-verify-lock.sh (VERDICT command-exit 0; one 223s queue wait behind issue-11557, waited out in-turn). NOTE a false red worth recording: check:doc-formula-expressions and check:doc-security-posture were red on the first pass ONLY because packages/lint/dist and @objectstack/formula/dist do not exist in a fresh worktree (ERR_MODULE_NOT_FOUND) — green after `pnpm --filter '@objectstack/lint...' build`; that is the documented build-the-dependency-closure trap, not a content failure. REPO-WIDE `pnpm lint` NARROWED, and the narrowing is a measurement: `eslint --no-inline-config --format json` on the changed file returns 1 result, 0 errors, sole message 'File ignored because no matching configuration was supplied.' — read from eslint's own config resolution, not from my reading of the config; the broadest `files` entry in eslint.config.mjs is line 891, '**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}', which no .mdx path matches, so the linted population of this changeset is ZERO files; no type-aware linting applies and the diff touches no eslint config, so no untouched file's verdict can move. Docs-only ⇒ no changeset; `skip-changeset` applied via the additive POST endpoint (HTTP 200) and CONFIRMED BY READ-BACK after the size labeler settled: labels are ['size/s','skip-changeset']. PR body read back from the API and verified intact (7987 bytes, first line 'Fixes #11811', session-URL footer survived).", "open_questions": [], "out_of_scope_findings": [ "filed as #11823: lifecycle.mdx:745 enumerates only two of the three check-entry names — the exception path pushes a fixed 'health-check' (health-monitor.ts:173), matching neither the configured checkMethod nor 'plugin-loaded'. Not fixed here because Zone 1 froze that block (it is #11812's verified work).", "filed as #11825: AdvancedPluginLifecycleConfig is carried in packages/spec/authorable-surface/kernel.json yet has no runtime consumer — its only repo-wide references are its own file and its own tests, and the kernel never constructs PluginHealthMonitor. The liveness ledger cannot catch it because that ledger is scoped by metadata type. Filed with three routes (wire it / retire under ADR-0049 / record as deliberate intention), none chosen, since Zone 1 put packages/spec and packages/core off-limits." ] }
Generated by Claude Code
Found while implementing #11787 (PR for the JSON block in the same section). Out of that card's scope — #11787 measured the JSON report sketch and explicitly recorded that
PluginHealthCheckParsed"was not audited against its schema". It has now been audited incidentally, and it diverges. Filed rather than fixed, because the right shape is a design question rather than a mechanical correction (see below).What was measured
content/docs/protocol/kernel/lifecycle.mdx,### Custom Health Checks, the TypeScript example immediately above the JSON block:The real configuration type is
PluginHealthCheckSchema(packages/spec/src/kernel/plugin-lifecycle-advanced.zod.ts:36-84):and its only consumer,
packages/core/src/health-monitor.ts:109-122:healthChecks, a map of named async functions on the plugin default exportcheckMethodstring naming a method on the plugin objectasync ({ context }) => ...plugin[checkMethod]()— called with no arguments; nothing supplies acontextsalesforce_connection)checkMethod(or'plugin-loaded'when none is configured){ status: 'healthy', latency_ms, records_synced_last_hour }falseand{ status: 'unhealthy' }are read;'healthy',latency_msand every other key are ignoredhealthChecksas an author-writable field: 0 occurrences. The 5 hits inpackages/arePluginHealthMonitor's own privateMap<string, PluginHealthCheckParsed>(health-monitor.ts:19,35,51,301) plus one example reading that private map (packages/core/examples/phase2-integration.ts:151) — none of them a declared plugin field. Positive control in the same sweep:checkMethod= 8 hits, spanning the schema field, the consumer, and two test files (plugin-lifecycle-advanced.test.ts:52,health-monitor.test.ts:108), so the zero is a reading rather than a dead probe.Why this is filed and not fixed
The
<Callout>above the section already says these checks are "not yet exposed as a dedicated HTTP endpoint or as a declarative plugin field" — so the example is flagged as aspirational, and a reader who reads the callout is not being told a lie abouthealthChecksexisting.That is also exactly why the correction is not mechanical. Two readings, and they teach opposite things:
checkMethod. The page then shows a shape a plugin author can actually write today: ahealthCheck()method on the plugin plus thePluginHealthCheckSchemaconfig that names it. Costs the per-check naming the current example illustrates (the real model supports exactly one check per plugin).healthChecksas the declared intent and mark it explicitly unimplemented. Defensible if the map-of-named-checks shape is the direction the model is meant to grow toward — the callout's "not yet" reads that way. Then the fix is a sharper "not implemented" marker on the block, not a rewrite.Answering that needs someone who knows whether
healthChecksis planned or abandoned, which is above the pay grade of a docs-accuracy pass.Not asserting more than was checked
Only the TypeScript example,
PluginHealthCheckSchemaandhealth-monitor.tswere compared. The JSON report block in the same section is #11787's subject and is being fixed there. No other page was examined, andraceCheckTimeout's behaviour was read only as far as "the check is invoked with no arguments".Generated by Claude Code