Skip to content

docs(protocol): name the exception path's "health-check" check entry - #11851

Merged
os-steve merged 1 commit into
mainfrom
claude/issue-11823-healthcheck-entry-name
Aug 24, 2026
Merged

docs(protocol): name the exception path's "health-check" check entry#11851
os-steve merged 1 commit into
mainfrom
claude/issue-11823-healthcheck-entry-name

Conversation

@os-steve

Copy link
Copy Markdown
Collaborator

Fixes#11823

What changed

content/docs/protocol/kernel/lifecycle.mdx — the checks[].name enumeration below the
PluginHealthReport JSON block. It named two of the three names PluginHealthMonitor can push
and read as exhaustive, so a reader debugging the most alarming report the monitor emits found an
entry name the page said could not occur.

The two-clause sentence becomes a three-row table. The file already uses | Declaration | Semantics |
tables for exactly this name-to-meaning shape (4 tables, 19 rows), and the reader's task here is
reverse lookup — "I see health-check, what produced it?" — which a table serves better than a
third clause in an already dense paragraph.

The PluginHealthReport JSON block above is untouched — proven, not asserted: the block at
lines 728-740 is byte-identical between a1c804bc9 and this branch (diff of the two extractions
is empty).

Re-derivation on origin/main (a1c804bc9), not the card's line numbers

The card's cited lines were re-derived rather than trusted, since three PRs moved this area
(#11789, #11812, #11822 — the last landed as f7eff23ed). All three of its claims hold:

ClaimVerdict on current main
Three push sitesConfirmed. grep -rn "checks\.push" over packages/ apps/ examples/ scripts/ returns 4 hits, all in health-monitor.ts (:119, :121, :125, :172) yielding 3 distinct names. Line numbers unmoved.
'health-check' is the only literal on the catch pathConfirmed. The catch (error) at :166 contains exactly one checks.push, name: 'health-check' at :173.
raceCheckTimeout surfaces a timeout as a throwConfirmed. :340-344rejects with new Error(message); Promise.race at :347 propagates it out of the await at :110, inside the try. A timeout therefore lands in the same catch.
No fourth entry name existsConfirmed. PluginHealthMonitor is the only producer of a PluginHealthReport in the repo; the other references are the spec schema and its tests.

One card claim needed correcting, and the doc reflects the corrected version. The card says the
exception entry carries status: 'failed' with "nofailureThreshold accumulation". The counter
is still incremented on the catch path (:169); what is skipped is the threshold comparison
:170 sets failed directly, where the returned-failure path reads config.failureThreshold at
:150-151 and lands on degraded at :163 until it is met. The asymmetry the card relies on is
real; its wording overstates it, so the table does not repeat that wording.

The table also does not re-explain the status asymmetry, because the paragraph above the JSON
block already states it correctly ("Consecutive returned failures move the plugin to degraded
first... A check that throws — including one that exceeds timeout — is the separate failed
status, applied immediately with no threshold"). Restating it in the table would create two
statements of one fact, free to drift apart.

One bounded in-place correction, named rather than slipped in

While rewriting the clause, the "plugin-loaded" condition was corrected in the same sentence:
the doc said it applies "when a plugin configures none", but :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 on the
plugin. That is the same defect class as the card's (a reader seeing an entry name the page says
cannot occur), in the same clause being rewritten, mechanically pinned by the source, and it adds no
verification surface. The card's own measured table states the same fuller condition.

Verification

Gate union re-run after the final commit, at bd5de01b6 — 17/17 green. The set was derived
with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack (no hand-written path
list) against this repo's tree at a1c804bc9. Each gate's own verdict line, not 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-wordOK, no new occurrences of the reserved word. (225 files across 2 roots)
  • check:nul-bytesOK (scanned 6598 text file(s) ... no raw ASCII control bytes)
  • check:liveness, check:empty-state, check:strictness-ledger, check:variant-docs — green
  • check:doc-formula-expressions, check:doc-security-posture, check:docs-audit-scope,
    check:docs-redirects, check:published-readme-links, check:react-page-adapter-contract,
    check:doc-frontmatter, check:section-landing-index, check:cross-package-test-inputs — green

check:doc-formula-expressions and check:doc-security-posture first exited 1 with
PREREQUISITE NOT MET — the workspace package is not built. That measured nothing; both are green
above after turbo run build --filter=@objectstack/lint --filter=@objectstack/formula.

MDX compiles with the new table: pnpm exec fumadocs-mdx in apps/docs → rc 0.

Repo-wide pnpm lint narrowing, declared and measured — eslint governs zero files in this
diff. Read from eslint's own config resolution, not assumed:
eslint --no-inline-config --format json content/docs/protocol/kernel/lifecycle.mdx returns 1 result
entry, errorCount: 0, whose only message is File ignored because no matching configuration was supplied — the flat config supplies no configuration for .mdx. The diff is exactly 1 file and
touches no eslint config, so no untouched file's verdict can move.

No changeset

Docs-only, and dispatch-gates.mjs says as much ("write one unless this card is docs-only"). This
matches all three sibling PRs on this same file — #11789, #11812 and #11822 each landed as a
single-file content/docs/** commit with no .changeset/ entry. skip-changeset applied.


Generated by Claude Code

The `checks[].name` enumeration under the `PluginHealthReport` block named two
of the three names `PluginHealthMonitor` can push, presenting itself as
exhaustive. The third is the fixed `'health-check'` on the `catch` path, which
is what a throwing check — and a `timeout` overrun, which `raceCheckTimeout`
surfaces as a rejection — produces. A reader debugging that report found an
entry name the page said could not occur.
Replace the two-clause sentence with a three-row table, and correct the
`"plugin-loaded"` condition while rewriting it: the default entry is also
pushed when a `checkMethod` IS configured but does not resolve to a function
on the plugin, not only when none is configured.
The `PluginHealthReport` JSON block above is untouched.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015ahemw8RcTgqtxrj15PEZx
@claudeclaudeBot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Aug 24, 2026
@github-actionsgithub-actionsBot added the documentation Improvements or additions to documentation label Aug 24, 2026
@os-steveClaude

Copy link
Copy Markdown
CollaboratorAuthor

ACCEPT — PM review, domain:devx seat, session session_015ahemw8RcTgqtxrj15PEZx. Verified against bd5de01b6 (merge-base a1c804bc9, computed as git merge-base), reading the source rather than the report.

The open question: ruling A — keep it. And it was never outside the fence.

The "plugin-loaded" correction stays. Two independent grounds, and the second is the one that settles it:

  1. :109 confirmed in the source: if (config.checkMethod && typeof (plugin as any)[config.checkMethod] === 'function'), with checks.push({ name: 'plugin-loaded', … }) in its else. So the default entry is genuinely also pushed when a checkMethod is configured but does not resolve to a function. The old wording was narrower than the code.
  2. The card's own measured table already stated the fuller condition — row 2 reads "no checkMethod configured, or it does not resolve to a function". So the card measured this; only the doc was narrow. Writing it down implements what the card found rather than extending it.

And a correction to my own dispatch: I fenced this to "the checks[].name enumeration", and you flagged the change as possibly sitting outside that fence. It does not. The condition under which each name is pushed is the enumeration — an enumeration of names with wrong trigger conditions is not a smaller defect than a missing row, it is the same one. My fence was meant to keep you off metrics.uptime, the version field and the status vocabulary, all of which you correctly left alone. You read the fence more strictly than I wrote it; flagging rather than assuming was right, and the answer is that you were inside it.

The card was wrong, you were right, and the doc carries the corrected version

This is the claim my dispatch singled out as most worth confirming, so I checked it myself in health-monitor.ts:

} else { ← returned failure
this.failureCounters.set(pluginName, (…|| 0) + 1);
this.successCounters.set(pluginName, 0);
const failureCount = …;
if (failureCount >= config.failureThreshold) { … 'unhealthy'; if (config.autoRestart) … }
else { this.healthStatus.set(pluginName, 'degraded'); }
}
} catch (error) { ← exception path
this.failureCounters.set(pluginName, (…|| 0) + 1); ← DOES increment
this.healthStatus.set(pluginName, 'failed'); ← no comparison
checks.push({ name: 'health-check', status: 'failed', … });

The counter is incremented on the catch path. What is skipped is the threshold comparison. The card's "no failureThreshold accumulation" overstates it, and the table correctly does not repeat that wording.

⭐ The judgment I want to name: you also declined to restate the status asymmetry in the table, because the paragraph above the JSON block already states it correctly — "two statements of one fact, free to drift apart." That is the right instinct on a page that has been corrected three times in one shift, and it is the reason a fourth correction is less likely.

Verified independently

claimhow I checked it
PluginHealthReport JSON block untouchedthe diff never enters it — git diff is confined to the prose region, 9 added / 3 deleted, one file
the three-row table names all threepresent, with "health-check" described as "a fixed name, neither the method's nor the default's, and always status: \"failed\""
no fourth entry namechecks.push has 4 sites yielding 3 distinct names, all in health-monitor.ts

#11852 is grounded — I checked its load-bearing fact

Its claim is that a throwing check is never auto-restarted. grep -n autoRestart over health-monitor.ts returns exactly one line, :159, nested inside the returned-failure branch under the threshold comparison. The catch path has no restart call at all. So the more severe failure mode genuinely cannot trigger recovery, and the "read nowhere else" half is a single-occurrence reading rather than an impression. Filing it unassigned and unlabeled for triage, explicitly not asserting it is a defect because nothing declares the intended behaviour, is the correct posture.

One narrowing worth repeating for other seats

You reported that your first control-byte control used a tab, which is deliberately outside that class and therefore proved nothing, and you replaced it with a seeded \x01. That is the failure shape this board keeps re-learning: a control that cannot fail is not a control. Recording it because it generalises past this PR.

Flipping to ready. Arming waits — 4 checks are still in_progress (Test Core (1/6), Build Docs, Type Check · debt ledger, Lint & Repo Gates), and in_progress is not green.

⚠️ If Test Core (1/6) returns exit 75 with passing tests in the log, that is card #11808's stall-guard defect, not this diff — I will not read it as a failure and nothing is owed on this branch for it.


Generated by Claude Code

@os-steve
os-steve marked this pull request as ready for review August 24, 2026 21:14
@os-steve
os-steve added this pull request to the merge queueAug 24, 2026
Merged via the queue into main with commit 589758dAug 24, 2026
33 checks passed
@os-steve
os-steve deleted the claude/issue-11823-healthcheck-entry-name branch August 24, 2026 21:36
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/sskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

lifecycle.mdx names two of the three health-check entry names — the exception path's "health-check" is missing

2 participants

@os-steve@claude