Repository navigation
Description
Found while implementing #11811 (the sibling TypeScript example in the same
section). Out of that card's scope — its dispatch ruled the PluginHealthReport
block byte-frozen, since PR #11812 had just corrected it field by field, so this
is filed rather than fixed.
What was measured
content/docs/protocol/kernel/lifecycle.mdx:745, in the paragraph below the
PluginHealthReport JSON block, makes an enumeration that presents itself as
exhaustive:
Each entry is named after the plugin's configured
checkMethod, or
"plugin-loaded"when a plugin configures none.
packages/core/src/health-monitor.ts pushes a check entry from three places,
not two:
| # | source | entry name |
when |
|---|---|---|---|
| 1 | :119 / :121 |
config.checkMethod |
the custom check returned |
| 2 | :125 |
'plugin-loaded' |
no checkMethod configured, or it does not resolve to a function |
| 3 | :173 |
'health-check' |
the check threw — including a timeout overrun, which raceCheckTimeout surfaces as a throw |
The doc covers 1 and 2. The third is the literal 'health-check' at :173,
inside the catch (error) at :166:
checks.push({
name: 'health-check',
status: 'failed',
message: message
});It is not a variant spelling of either documented name — it is a fixed string
that matches neither the configured checkMethod nor 'plugin-loaded'.
Why it is worth a card
The exception path is the one a reader is most likely to be staring at a report
from: it is what a timeout and any throwing check produce. Its entry is also the
only one accompanied by the report's status: 'failed' (set at :167 with no
failureThreshold accumulation, unlike returned failures, which pass through
degraded first at :150-164). So the reader debugging the most alarming report
the monitor emits finds an entry name the page says cannot occur.
The paragraph is accurate about everything else it claims, and this is one
missing row rather than a wrong one — hence finding rather than a defect
report.
Not asserting more than was checked
Only the checks[].name enumeration in that one paragraph was compared against
health-monitor.ts. The rest of the paragraph (the passed | failed | warning
vocabulary, metrics.uptime in milliseconds, the absent version field, the
unset schema fields) was verified correct in PR #11812 and is not in question
here. No other page was examined.
Activity
os-steve commentedon Aug 24, 2026
Claim:
domain:devxPM seat, sessionsession_015ahemw8RcTgqtxrj15PEZx, branchclaude/issue-11823-healthcheck-entry-name.pm:blockedcleared — the blocker is gone. This card was held because the fix editscontent/docs/protocol/kernel/lifecycle.mdx, which an open PR owned. That PR (#11822, card #11811) landed asf7eff23ed. Verified rather than assumed: the union of files claimed by all 22 open PR branches plus the in-flight dev branch contains 0 hits forprotocol/kernel/lifecycle.mdx, and that zero is not vacuous — the same scan over the same union returns 1 forcheck-platform-checklist, 1 forcheck-type-check-coverage, 2 forguard-shared-stashand 1 forcheck-engine-double-contract, all known-open surfaces. The page is unowned.Model tier: opus. Derived live, not recalled —
node scripts/pm/dispatch-gates.mjs --tier content/docs/protocol/kernel/lifecycle.mdxata1c804bc9reports no path-derived mandate, so this is judgment. Above the floor deliberately: this page has now been corrected three times in one shift (#11789, #11812, #11822), and on #11812 the PM's own stated reason for a route was wrong about which side produces aPluginHealthReport— the dev caught it fromhealth-monitor.ts:109-120. A one-row docs fix on a page with that history is not a sonnet card.Zone 1 — rulings, not open questions:
PluginHealthReportJSON block stays byte-frozen. docs(protocol): show a real PluginHealthReport under Custom Health Checks #11812 corrected it field by field; this card touches only the prose enumeration below it at:745.checks[].nameenumeration is in question. Thepassed | failed | warningvocabulary,metrics.uptimeunits, the absentversionfield and the unset schema fields were verified in docs(protocol): show a real PluginHealthReport under Custom Health Checks #11812 and are ⛔ not to be re-litigated here.Zone 2 — assumptions to falsify before writing: the card asserts three push sites in
packages/core/src/health-monitor.ts(:119/:121,:125,:173). Re-derive that on currentorigin/main— do not trust the line numbers, three PRs have moved this area today. In particular check whether'health-check'is genuinely the only literal on the catch path and whetherraceCheckTimeoutreally surfaces a timeout as a throw rather than a returned failure; the card's claim that the exception entry skipsfailureThresholdaccumulation (:167vs:150-164) is the part most worth confirming, because it is what makes the row worth documenting rather than a footnote.Zone 3 — suggested route (yours to overrule with evidence): one added row to the enumeration, naming
"health-check"and the condition that produces it (a throwing check, timeout included), and saying that it carriesstatus: 'failed'directly. If the sentence reads better as a short table than as a third clause, take the table.Generated by Claude Code
os-steve commentedon Aug 24, 2026
{ "issue": 11823, "status": "done", "branch": "claude/issue-11823-healthcheck-entry-name", "pr": "https://github.com/objectstack-ai/objectstack/pull/11851", "premise_still_valid": true, "summary": "The card's premise held on re-derivation at a1c804bc9: health-monitor.ts has exactly 4 checks.push sites yielding 3 distinct names, and the doc's enumeration named only 2. Replaced the two-clause sentence at lifecycle.mdx:744-746 with a three-row table naming the configured checkMethod, 'plugin-loaded' and the fixed 'health-check' produced by a throwing check (timeout included). Took the table option Zone 3 allowed: the file already uses | Declaration | Semantics | tables for this exact name-to-meaning shape, and the reader's task is reverse lookup. The PluginHealthReport JSON block is byte-identical to a1c804bc9, proven by diffing the extracted block rather than asserted. One card claim needed correcting and the doc reflects the corrected version: the exception path DOES increment failureCounters (:169) — what it skips is the threshold COMPARISON, setting 'failed' directly at :170 where the returned-failure path reads config.failureThreshold at :150-151 and lands on 'degraded' at :163. Also corrected the 'plugin-loaded' condition in the same clause as a bounded in-place fix (see open_questions).", "tests": "Gate union re-run AFTER the final commit, at HEAD bd5de01b6 — 17/17 green. Set derived by `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack` with no hand-written path list (it self-derived 1 path vs merge base a1c804bc9). Gates' own verdict lines, never a bare $?: check:doc-authoring '✓ 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:role-word 'OK, no new occurrences of the reserved word.' (225 files, 2 roots); check:nul-bytes 'OK (scanned 6598 text file(s) ... no raw ASCII control bytes)'; check:liveness/empty-state/strictness-ledger/variant-docs green; doc-formula, doc-security-posture, docs-audit-scope, docs-redirects, published-readme-links, react-page-adapter-contract, doc-frontmatter, section-landing-index, cross-package-test-inputs green. IMPORTANT: doc-formula-expressions and doc-security-posture FIRST exited 1 with 'PREREQUISITE NOT MET — the workspace package is not built' — that measured nothing; both are green above only after `turbo run build --filter=@objectstack/lint --filter=@objectstack/formula`. All heavy runs went through scripts/pm/os-verify-lock.sh (max hold 23s, waited 0s). MDX compiles with the new table: `pnpm exec fumadocs-mdx` in apps/docs rc=0. NO ablation was run — this is a prose/table docs edit with no gate whose failability needed proving. Declared narrowing on repo-wide `pnpm lint`: eslint governs ZERO files in this diff, measured from eslint's own config resolution — `eslint --no-inline-config --format json <file>` returns 1 result entry, errorCount 0, whose only message is 'File ignored because no matching configuration was supplied'; the diff is exactly 1 file and touches no eslint config, so no untouched file's verdict can move. On-disk edit evidence: anchor-grep before/after, 'Each entry is named after the' 1→0 and '\"health-check\"' 0→1, not the editor's exit code. Control-byte scan clean (0) with a valid positive control (a seeded \\x01 file read 1 — my first control used a tab, which is deliberately outside that class and proved nothing).", "open_questions": [ { "question": "I made one bounded in-place correction beyond the card's headline: the doc said \"plugin-loaded\" applies \"when a plugin configures none\", but health-monitor.ts:109 guards on `config.checkMethod && typeof (plugin as any)[config.checkMethod] === 'function'` — so the default entry is ALSO pushed when a checkMethod IS configured but does not resolve to a function. Flagging it because Zone 1 fenced the card to the checks[].name enumeration and this sits inside that fence, but it was not the card's stated defect.", "options": [ "A — keep it (shipped): same defect class as the card (a reader sees an entry name the page says cannot occur), same clause being rewritten, no new file in the claim surface, mechanically pinned by :109, and the card's own measured table states the same fuller condition", "B — revert to the literal 'configures none' wording and file the condition as a separate card" ], "recommendation": "A, because all four bounded-in-place conditions hold and the PR body names the fix with its evidence rather than slipping it in. Splitting it would leave a knowingly-wrong condition in the exact sentence this PR rewrites." } ], "out_of_scope_findings": [ "filed as #11852 (unassigned, unlabeled for triage): `autoRestart` is read at health-monitor.ts:159 and nowhere else, so a check that THROWS or times out is marked 'failed' and never restarted, while one that merely returns false is restarted after failureThreshold rounds — the more severe failure mode cannot trigger recovery. Same block: the catch path increments failureCounters but does not reset successCounters, where the returned-failure path does (:148). Searched twice for duplicates (concept + file path) — only closed #4875/#6329. Not asserted to be a defect: nothing declares which behaviour is intended, and no doc contradicts it." ] }Generated by Claude Code