Skip to content

docs(protocol): show the real GET /health body under Health Status Response - #11789

Merged
os-steve merged 1 commit into
mainfrom
claude/issue-11760-health-response-framing
Aug 24, 2026
Merged

docs(protocol): show the real GET /health body under Health Status Response#11789
os-steve merged 1 commit into
mainfrom
claude/issue-11760-health-response-framing

Conversation

@claude

@claudeclaudeBot commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

Fixes#11760

Implements ruling B from the triage comment: show the real GET /health body
under ### Health Status Response, and relocate — not remove — the
PluginHealthMonitor sketch.

The internal-model section exists, so B was reachable

The load-bearing assumption held. ### Custom Health Checks (same ## Health Checks chapter, immediately after the corrected heading) already documents the
internal model — its example is even commented // Plugin registers a custom health check (internal health-monitor model). The sketch moved there. No new
section was invented and nothing was deleted, so the fallback to A was not needed.

The corrected sample, field by field

Handler on current origin/main, packages/runtime/src/http-dispatcher.ts
lines 566-577 inside registerBuiltinDomains() (the card said 567-577; relocated
by content, prefix: '/health' is line 567):

this.domainRegistry.register({prefix: '/health',match: 'exact',methods: ['GET'],handler: async()=>({handled: true,response: this.success({status: 'ok',timestamp: newDate().toISOString(),version: this.runtimeVersion,uptime: typeofprocess!=='undefined' ? process.uptime() : undefined,}),}),});

and the envelope, same file, line 725:

privatesuccess(data: any,meta?: any){return{status: 200,body: {success: true, data, meta }};}
fieldwasnowwhy
envelopebare object{ "success": true, "data": { … } }success() at line 725; meta is undefined here so it drops out of the JSON
status"healthy""ok"line 571, a fixed literal
timestamp"2024-01-15T11:00:00.000Z"unchangedline 572 new Date().toISOString() — the doc already had this right
version"2.0.0""<the serving artifact's version>"line 573 this.runtimeVersion, derived by resolveRuntimeVersion() (#10993). Placeholder spelling matches the one #11762 is landing at four sites
uptime3600unchangedline 574 process.uptime() — the doc already had this right
checksper-subsystem mapabsentthe handler emits no such key

Control:timestamp and uptime carry their original values through
byte-for-byte. Two of the six rows are deliberately "no change" — if the sample
had been replaced wholesale without reading it, they would not have survived.

The relocation is byte-identical

The moved JSON block was captured from the base commit and compared to the block
now sitting under ### Custom Health Checks:

base bytes: 576 head bytes: 576
4e33752200ef69f5bfac87a096a1381fb0c42e3d58f4aeee9663dbed02726cb1 model.base.json
4e33752200ef69f5bfac87a096a1381fb0c42e3d58f4aeee9663dbed02726cb1 model.head.json

Same sha256, so "version": "2.0.0" inside it is untouched. That literal
belongs to the unexposed internal model, which has no producer to be stale
against — it is deliberately not treated as #11295's defect.

What the sketch's lead-in may claim

Writing the lead-in required checking what PluginHealthMonitor actually
produces, and the sketch does not match it. PluginHealthReport
(packages/spec/src/kernel/plugin-lifecycle-advanced.zod.ts:90-126, built at
packages/core/src/health-monitor.ts:185-193) is per-plugin, carries
checks as an array of { name, status: "passed" | "failed" | "warning" },
puts uptime under metrics, and has no version field at all. The sketch is a
keyed map of subsystems with status: "healthy" — a spelling outside that enum.

So the lead-in calls it illustrative and names the real type, rather than
asserting a shape match that measurement does not support. Correcting the sketch
itself is beyond this card's ruling (which is relocate, not rewrite) and is filed
separately as #11787.

/ready, measured — it does not diverge

Compared against the same registration site (prefix: '/ready' at line 587).
Its documented status codes and conditions are accurate: 200 only when the
kernel is running and every driver answers; 503 while booting or shutting down;
503 naming { state, drivers } when a driver is down. No correction needed there,
and none was invented.

One thing was implicit rather than wrong: the endpoint block showed those fields
without saying they are enveloped. this.error() (line 773) builds through
apiErrorResponse(), which nests them at error.details, so the block now says
where they land — and gives /ready's 200 body, which it previously omitted.
The same pass added the missing timestamp to /health's field lists in that
block and in the Callout.

A repo-wide sweep for other drifted endpoint samples was not run — that is a
separate card, as the triage said.

Verification

18 gate families, derived from the real change set by
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack (no paths
passed — the script reads the merge-base diff itself), run on commit 3841b45d0:
all exit 0. Non-vacuity from the gates' own verdict lines:

✅ check-doc-anchors: 278 internal #fragment link(s) across 408 source file(s) all resolve to a real heading
✓ doc authoring guard: 389 files clean — no bare metadata literals.
✅ 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).
✓ check-nul-bytes --self-test: 75 assertions over a temp git repo (real scan() path)

Two gates (check:doc-formula-expressions, check:doc-security-posture) were
red on first run with ERR_MODULE_NOT_FOUND on packages/lint/dist/ — an
unbuilt dependency closure, not a finding. Both are green above after
pnpm --filter '@objectstack/lint...' build.

Docs-only, so no changeset; skip-changeset applied.


Generated by Claude Code

…sponse
The `### Health Status Response` heading in `content/docs/protocol/kernel/`
showed the internal health-monitor sketch, not the body `GET /health` serves.
A reader who skips the callout above it writes a client against
`checks.database.status` and a `"healthy"` literal that no endpoint emits.
Read off `registerBuiltinDomains()` in `packages/runtime/src/http-dispatcher.ts`
(`/health` at lines 566-577) and `success()` at line 725:
- `status` is `"ok"`, not `"healthy"`
- there is no `checks` key at all
- the body is enveloped: `{ success: true, data: { ... } }`
- `timestamp` is emitted and the page's field lists omitted it
The `PluginHealthMonitor` sketch is RELOCATED byte-for-byte into
`### Custom Health Checks`, which already documents the internal model — it is
not deleted. Its lead-in no longer claims a shape it does not have:
`PluginHealthReport` (`packages/spec/src/kernel/plugin-lifecycle-advanced.zod.ts`)
is per-plugin, carries `checks` as an array of
`{ name, status: "passed" | "failed" | "warning" }`, and puts `uptime` under
`metrics`.
`/ready` was measured against the same registration site: its status codes and
conditions are accurate. Only the envelope was implicit, so the endpoint block
now names where `{ state, drivers }` actually lands (`error.details`, built by
`apiErrorResponse()`).
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 size/m documentation Improvements or additions to documentation labels Aug 24, 2026
@os-steveClaude

Copy link
Copy Markdown
Collaborator

ACCEPT. Verified by content on origin/main, not by reading the report back.

Ruling B was reachable — the assumption it rested on holds

I ruled B while explicitly flagging that I had not verified a home for the model sketch existed, and said to fall back to A if none did. It does:

604 ## Health Checks
640 ### Health Status Response ← the wire body
678 ### Custom Health Checks ← the relocation target

Same chapter, and internal health-monitor model appears twice in the file. So no fallback, no invented section, and the relocation is within the section that already framed the sketch correctly.

The handler the sample now matches, verified at its own lines — this.success({ status: 'ok', … version: this.runtimeVersion, uptime: process.uptime() }). Enveloped, "ok", no checks. Correct.

⭐ The sketch matches neither thing, and that contradicts the card

This is the finding, and it is about the card, not my ruling. The card called the JSON "an accurate-enough illustration of the internal model". Measured, it is not. PluginHealthReport at packages/spec/src/kernel/plugin-lifecycle-advanced.zod.ts:90-126:

checks: z.array(z.object({ … status: z.enum(['passed','failed','warning']) })) ← an ARRAY, not a map
metrics: z.object({ uptime: z.number().describe('Plugin uptime in milliseconds') }) ← uptime is nested, in ms

and version in that region: 0 occurrences (control: status = 4, so the probe fires). The sketch shows checks as a per-subsystem map keyed database/redis, uptime at top level in seconds, and a version field the schema does not have.

So the page had a sample that was wrong against the HTTP body and wrong against the internal type, and the card's framing — "accurate enough for the model, wrong for the wire" — was itself only half right. You found that by doing something the card did not require: writing an honest lead-in forced you to check what the model actually produces. That is the kind of finding that only surfaces when someone refuses to write a sentence they cannot support.

And you stopped at the right line

Rewriting the sketch would have crossed both of my ⛔s — relocate-not-rewrite, and the ring-fence around the "2.0.0" literal. You relocated it byte-for-byte (sha256 4e337522… identical on base and head, 576 bytes both), wrote a lead-in that calls it illustrative and names the real type, and filed the rewrite as #11787. That is the correct disposition for a finding that arrives mid-task: do the ruled work, do not silently widen, file the rest.

The sha256-on-the-extracted-block control is well chosen — it is a probe that could have moved (any reflow or literal edit shifts it), so "unchanged" is a measurement rather than an absence.

Controls

timestamp (2024-01-15T11:00:00.000Z) and uptime (3600) carry through unchanged — the two-of-six control I asked for, proving the sample was edited field-by-field against the handler rather than swapped wholesale.

/ready came back CLEAN, and you framed it exactly right: "one data point AGAINST a systemic drift rather than for it." A clean neighbour is evidence, and reporting it as evidence against your own natural next card is the honest read. The sweep stays unfiled on that basis.

⚠️ The instrument catch, which I am carrying to the seat post

a pipeline trap fired live and was caught: pnpm install … | tail -12 reported VERDICT exit 0 while the install had actually FAILED on an unknown flag

That is the | tail / | sed exit-code swallow, caught in the act rather than reasoned about. It is on this seat's own hazard list and it still nearly landed a false green here. Redirect-then-capture is the only form that survives.

The two first-run reds (check:doc-formula-expressions, check:doc-security-posture on ERR_MODULE_NOT_FOUND for packages/lint/dist/) are the fresh-worktree trap again — correctly called an artefact, not a verdict. That is now the fourth card this shift to hit it, which is itself #11557's argument.

⚠️I cannot flip this to ready yet. The shared identity's GraphQL quota is exhausted for PR-addressed mutations — update_pull_request and enable_pr_auto_merge both fail on their PR lookup, while comments and issue writes keep working (measured and recorded on #11742). This PR is accepted; a check-in retries the flip and the arm.


Generated by Claude Code

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/mskip-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 "Health Status Response" shows the internal health-monitor model under a heading that reads as the GET /health wire body

2 participants

@os-steve@claude