Skip to content

fix(runtime): derive /health and discovery version instead of a hardcoded '1.0.0' literal - #11242

Merged
os-elon merged 1 commit into
mainfrom
claude/issue-10993-health-version-derived
Aug 23, 2026
Merged

fix(runtime): derive /health and discovery version instead of a hardcoded '1.0.0' literal#11242
os-elon merged 1 commit into
mainfrom
claude/issue-10993-health-version-derived

Conversation

@os-elon

Copy link
Copy Markdown
Collaborator

Fixes#10993

Premise check

Confirmed at origin/main (dd84ddd796): packages/runtime/src/http-dispatcher.ts carried the hardcoded version: '1.0.0' literal at both sites — the /health handler and the discovery payload built by getDiscoveryInfo(). Premise holds.

Second site — measured in scope

DiscoverySchema.version (packages/spec/src/api/discovery.zod.ts:681, z.string()) sits under the schema's "System Identity" section alongside name, distinct from ServiceInfoSchema's separately-documented per-SERVICE version field ("Semantic version of the service implementation"). Three measurements:

  1. The SDK's connect() (packages/client/src/index.ts:567) logs data.version alongside apiName and services under "Connected to ObjectStack server" — read as artifact identity, not a protocol/API version.
  2. DiscoverySchema has no protocol field at all — the "protocol 17" numbering near GetDiscoveryResponseSchema is a documentation-only convention, not a contracted value.
  3. The OTHER DiscoverySchema producer, getDiscovery() in @objectstack/metadata-protocol (packages/metadata-protocol/src/protocol.ts:5095), fills the SAME field with a DIFFERENT unrelated literal ('1.0', not even '1.0.0') — itself evidence neither producer ever treated the value as a meaningful contract.

So this is the identical defect, not a separately-contracted value — fixed here, in the same file, alongside /health. Filed the third producer (a different package) as its own unassigned card: #11235.

Design

packages/runtime/src/runtime-version.ts, resolveRuntimeVersion(), called once in HttpDispatcher's constructor and stored on this.runtimeVersion:

  1. OS_RUNTIME_VERSION, if the host injects one. Not a new env varpackages/cloud-connection/src/cloud-connection-plugin.ts already reads this exact name (with a per-plugin literal fallback) to name the runtime in a device-bind approval URL; reused rather than inventing a second name for the same value (AGENTS.md [WIP] Create a new release version #9's OS_{DOMAIN}_{NAME} config-value shape — RUNTIME domain, VERSION value).
  2. @objectstack/runtime's own resolved package.json version, via createRequire(import.meta.url)('../package.json').
  3. 'unknown' only if both are unavailable — never a plausible-looking literal.

Resolved once at construction (per triage), not per-request — it names the process, which cannot change over the dispatcher's lifetime.

A build trap this surfaced

createRequire(import.meta.url) is correct for the ESM output, but esbuild EMPTIES import.meta in tsup's CJS build (measured: packages/lint's own built dist/runtime.js already carries an [empty-import-meta] warning on a bundled dependency doing exactly this, unguarded). packages/runtime/tsup.config.ts now sets shims: true, which makes tsup rewrite import.meta.url in the CJS bundle to a real __filename-derived value (its assets/cjs_shims.js). Confirmed both ways by requiring the built dist/index.cjs and importing the built dist/index.js directly and constructing a real HttpDispatcher: both correctly resolve the package version. Need-based injection — nothing else in the package references __dirname/__filename, so the ESM shim path is unused (no effect on the ESM build).

Pin design + anti-vacuity

packages/runtime/src/http-dispatcher.runtime-version.test.ts drives the real HttpDispatcher.dispatch() path (never source text or the resolver in isolation):

  • Injects a stamp with no plausible relation to '1.0.0' or the package version and asserts BOTH /health and /discovery return it verbatim — proves one derived source serves both callers.
  • No stamp injected → asserts the served value equals the real resolved package version (read the same way resolveRuntimeVersion() reads it, so the assertion tracks production code rather than an independently guessed string) — not '1.0.0', not undefined.
  • Asserts construction-time (not per-request) resolution: an env change after construction does not retroactively affect an already-built dispatcher, but does apply to a freshly constructed one.

Two pre-existing tests pinned the literal directly (http-dispatcher.root.test.ts, both via getDiscoveryInfo() on the root "" and /discovery routes) — fixed at the source: they now assert against the real resolved package version instead of '1.0.0'.

Reverse verification (fix committed first; both version: this.runtimeVersion, sites reverted in the working tree to the literal version: '1.0.0',; restored via git checkout HEAD --): the literal turned exactly the intended 6 assertions red — 2 in http-dispatcher.root.test.ts, 4 in the new pin file — each naming the mismatch (expected '1.0.0' to be '17.1.0' / the injected stamp), nothing else in the package moved. Restored, re-ran, green.

Boot + curl (real process, not a unit test)

Booted a real LiteKernel + HonoServerPlugin (port 0) + createDispatcherPlugin server against the built @objectstack/runtime and fetched over real HTTP:

[no OS_RUNTIME_VERSION (fallback)] GET /api/v1/health -> 200 {"status":"ok",...,"version":"17.1.0",...}
[no OS_RUNTIME_VERSION (fallback)] GET /api/v1/discovery -> 200 version=17.1.0
[OS_RUNTIME_VERSION=real-boot-9c2e1] GET /api/v1/health -> 200 {"status":"ok",...,"version":"real-boot-9c2e1",...}
[OS_RUNTIME_VERSION=real-boot-9c2e1] GET /api/v1/discovery -> 200 version=real-boot-9c2e1

Liveness contract — unchanged

/health still checks nothing beyond "this process is executing code" (framework#3756) — only the VALUE of one existing field changed. No schema widening: DiscoverySchema.version: z.string() was already declared.

Gate verdicts (all local, quoted verbatim)

  • pnpm --filter '@objectstack/runtime' typecheck — exit 0, clean.
  • pnpm --filter '@objectstack/runtime' build — ESM + CJS + DTS all succeed, no empty-import-meta warning on our own code.
  • pnpm --filter '@objectstack/runtime' test (full package) — Test Files 184 passed (184) / Tests 2710 passed (2710).
  • pnpm check:changeset-gate-self-tests — 3 self-tests OK.
  • pnpm check:cross-package-test-inputsOK: 13 package(s) read outside themselves, all declared, and turbo.json hashes every declared glob.
  • pnpm check:objectui-changeset — self-tests OK.
  • pnpm check:published-files69 publishable package(s) of 78 workspace member(s) declare a files whitelist...
  • pnpm check:slot-lookupslot-lookup ratchet holds: 107 unswept site(s) in 25 file(s), none new... baseline key set verified against dd84ddd: no files added.
  • pnpm check:test-source-aliascheck-test-source-alias OK — 72 packages with tests scanned...
  • pnpm check:type-source-resolutioncheck-type-source-resolution OK — 77 packages with a tsconfig.json scanned...
  • node scripts/check-ci-filter-parity.mjsOK: all 86 declared cross-package glob(s) (74 unique) are covered...
  • node scripts/check-plugin-teardown-shape.mjs63 Plugin implementation(s) across 4503 source(s)... 0 known-unreached, SHRINK-ONLY, baseline fully burned down.
  • node scripts/docs-audit/check-affected-docs.mjsaffected-docs self-test: 339 cases pass.
  • pnpm check:query-options-erasurequery-options-erasure ratchet holds: 67 unswept non-test site(s) in 17 file(s), none new... baseline key set verified against dd84ddd: no files added.
  • pnpm check:engine-double-contractOK — 384 pinned, 133 in the DEBT ledger, 2 exempt.
  • pnpm check:where-matcherwhere-matcher conformance holds: 282 matcher(s) discovered, 282 answer the combinator battery correctly or refuse it loudly... 0 silently-wrong and 0 unjudged... baseline key set verified against dd84ddd: no files added.
  • pnpm check:nul-bytescheck-nul-bytes: OK (scanned 6513 text file(s) -- 6513 tracked, 0 untracked-not-ignored; skipped 5 binary; no raw ASCII control bytes).
  • pnpm check:type-check-debt (real re-measure, full workspace closure built first via pnpm exec turbo run build --filter='./packages/*' --filter='./packages/*/*', 70/70 tasks) — check-type-check-coverage --re-measure: OK — 33 ledger entr(ies) re-measured in 254.0s, 1897 raw tsc error(s) total, none above its recorded number.@objectstack/runtime's own TEST_DEBT entry went DOWN (227 recorded → 226 measured), not up.

All of the above were run at 10a5a453d2 (this PR's current head).

Out of scope


Generated by Claude Code

…, not a hardcoded '1.0.0' literal (#10993)
Resolve `version` once at HttpDispatcher construction from an injected
OS_RUNTIME_VERSION stamp, falling back to the resolved @objectstack/runtime
package version -- reusing the env var cloud-connection-plugin.ts already
reads for the same purpose. Fixes the identical defect on both the /health
handler and the discovery payload (getDiscoveryInfo()), which share the
same "System Identity" field in DiscoverySchema.
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/runtime, touching 7 documentable anchor(s).

4 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/api/environment-routing.mdx(via HttpDispatcher (symbol))
  • content/docs/automation/webhooks.mdx(via HttpDispatcher (symbol))
  • content/docs/kernel/cluster.mdx(via HttpDispatcher (symbol))
  • content/docs/plugins/packages.mdx(via HttpDispatcher (symbol))
What this run could not see
  • 1 changed file(s) yielded no anchor (packages/runtime/tsup.config.ts) — pages documenting those are invisible to this run
  • 1 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 45 of 221 client-bound route-ledger rows — the other 176 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run: node scripts/docs-audit/affected-docs.mjs --bridge-coverage

Coarse fallback — 23 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json df287e652862f92cd1f7e40846a494bb031aaac1packageMentionDocs.

Which tree this was computed on

This run read content/docs from cff0c051ce549614a25adabd77b4d02b321382fb — the merge of head 10a5a453d21db07afde4cc17dd8d863e1e36134f into base df287e652862f92cd1f7e40846a494bb031aaac1, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin cff0c051ce549614a25adabd77b4d02b321382fb && git checkout cff0c051ce549614a25adabd77b4d02b321382fb
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin df287e652862f92cd1f7e40846a494bb031aaac1 10a5a453d21db07afde4cc17dd8d863e1e36134f && git checkout -B drift-repro df287e652862f92cd1f7e40846a494bb031aaac1 && git merge --no-ff 10a5a453d21db07afde4cc17dd8d863e1e36134f
node scripts/docs-audit/affected-docs.mjs --json df287e652862f92cd1f7e40846a494bb031aaac1

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs df287e652862f92cd1f7e40846a494bb031aaac1 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

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

Labels

documentationImprovements or additions to documentationsize/mteststooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[finding] /api/v1/health reports a hardcoded version: '1.0.0' — a field that exists and lies, so no consumer can use it for artifact identity

2 participants

@os-elon@claude