feat(observability): admin-gated per-request Server-Timing via X-OS-Debug-Timing (#2408)#3163
Merged
Merged
Conversation
…ebug-Timing (#2408) Adds the per-request gating from the perf-tuning design so an operator can pull a single request's `Server-Timing` breakdown on a live environment without turning the header on for every user. Until now perf-tuning was global-only (`serverTiming` option / `OS_SERVER_TIMING`), which discloses internal phase durations — a mild backend-fingerprinting surface — to every caller. This wires up the second gating path the issue describes: - **Disclosure gate** (`@objectstack/observability`): a request-scoped `AsyncLocalStorage` gate, separate from the pure `PerfTiming` collector so the collector keeps its "only measures, never decides to emit" invariant. Pinned to a `Symbol.for` registry key like the collector store so the middleware (which seeds it) and the dispatcher (which opens it) share one store across ESM/CJS module copies. - **Hono middleware**: always registered unless `serverTiming: false`. Runs the collector when timing is global OR the request carries `X-OS-Debug-Timing: 1`; emits the header only when the gate is open. Zero collector overhead (one header read) when neither applies. - **Dispatcher**: after resolving the execution context, opens the gate for admin/service/system principals (`isSystem`, `principalKind` service/system, posture PLATFORM_ADMIN/TENANT_ADMIN). Ordinary callers get no header even if they send the debug header. - **Env alias**: `OS_PERF_TIMING=1` now also enables global mode, matching the issue's naming; `OS_SERVER_TIMING=true` still works. Tests cover the gate primitive, the middleware's per-request/global/hard-off paths, and the admin predicate. Existing global-mode behavior is unchanged. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011upHdyr5AnNc6dWAu63qxD
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Contributor
📓 Docs Drift CheckThis PR changes 3 package(s): 17 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
…ng path (#2408) Updates the Server-Timing section for the new gating: the global mode (`serverTiming` / `OS_SERVER_TIMING` / `OS_PERF_TIMING`) plus the per-request `X-OS-Debug-Timing: 1` path that returns timing only to an admin/service identity, and the `serverTiming: false` hard-off. Keeps the go-live checklist in sync. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011upHdyr5AnNc6dWAu63qxD
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011upHdyr5AnNc6dWAu63qxD
os-zhuang
marked this pull request as ready for review
July 18, 2026 05:50
This was referenced Jul 18, 2026
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What & why
Closes the gating part of #2408's perf-tuning design. The Server-Timing mechanism (auth/db/hooks/serialize/total spans, request-scoped collector, SQL query counting) already landed in #3107, but perf-tuning was global-only (
serverTimingoption /OS_SERVER_TIMING), which discloses internal phase durations — a mild backend-fingerprinting surface — to every caller.This wires up the second gating path the issue describes: an operator (or the cloud console) can pull one request's timing breakdown on a live environment by sending
X-OS-Debug-Timing: 1, and it is returned only to an admin/service identity — without flipping timing on for everyone.How
@objectstack/observability) — a request-scopedAsyncLocalStoragegate kept separate from the purePerfTimingcollector, so the collector keeps its "only measures, never decides to emit" invariant. Pinned to aSymbol.forregistry key (same reason as the collector store) so the middleware that seeds it and the dispatcher that opens it share one store across ESM/CJS module copies. New API:runWithPerfDisclosure,allowPerfDisclosure,isPerfDisclosureAllowed,PerfDisclosureGate.plugin-hono-server) — now registered by default (unlessserverTiming: falsehard-disables it). Opens the collector when timing is global or the request carriesX-OS-Debug-Timing: 1; emits the header only when the gate is open. When neither applies it's a single header read then straight through — zero collector overhead.runtime) — after resolving the execution context (the singletimedResolveExecutionContextchoke point), opens the gate for admin/service/system principals viaisPerfDisclosurePrincipal(isSystem,principalKindservice/system, posturePLATFORM_ADMIN/TENANT_ADMIN). Ordinary human/guest/agent callers get no header even if they send the debug header.OS_PERF_TIMING=1now also enables global mode, matching the issue's naming;OS_SERVER_TIMING=truestill works.Header shape (unchanged, now admin-gated per-request)
Gating summary
serverTiming: true/OS_SERVER_TIMING=true/OS_PERF_TIMING=1X-OS-Debug-Timing: 1serverTiming: falseOpen-core boundary
Pure mechanism (the request path, driver, engine). Policy — who counts as "admin", whether it surfaces as a cloud "perf panel" — stays cloud-side, mirroring the existing observability split.
Tests
perf-timing.test.ts— disclosure-gate primitive (seed state, open-after-await, independence from the collector scope, shared-symbol store).server-timing.test.ts— per-request path: debug header alone withholds; header + proven admin identity emits; opt-in only (no header without the debug header);serverTiming: falsehard-off; global discloses to everyone;OS_PERF_TIMING=1alias;isDebugTimingRequestedspellings.http-dispatcher.perf-gating.test.ts— the admin/service/system predicate.dbspan) unchanged.Two CORS tests were updated to assert the precise intent (
cors()not configured) instead of "nouse()at all", since the perf-timing middleware now legitimately registers its ownuse('*')by default; one also gained atry/finallyso a failed assertion can't leakOS_CORS_ENABLED.All affected package builds (DTS typecheck) pass. One pre-existing, unrelated
runtimeseed-replay test failure was confirmed to fail identically on the base revision.Generated by Claude Code