Skip to content

feat(observability): admin-gated per-request Server-Timing via X-OS-Debug-Timing (#2408)#3163

Merged
os-zhuang merged 3 commits into
mainfrom
claude/per-request-perf-timing-tp6lq4
Jul 18, 2026
Merged

feat(observability): admin-gated per-request Server-Timing via X-OS-Debug-Timing (#2408)#3163
os-zhuang merged 3 commits into
mainfrom
claude/per-request-perf-timing-tp6lq4

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

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 (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: 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

  • Disclosure gate (@objectstack/observability) — a request-scoped AsyncLocalStorage gate kept 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 (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.
  • Hono middleware (plugin-hono-server) — now registered by default (unless serverTiming: false hard-disables it). Opens the collector when timing is global or the request carries X-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.
  • Dispatcher (runtime) — after resolving the execution context (the single timedResolveExecutionContext choke point), opens the gate for admin/service/system principals via isPerfDisclosurePrincipal (isSystem, principalKind service/system, posture PLATFORM_ADMIN/TENANT_ADMIN). Ordinary human/guest/agent callers get no header even if they send the debug header.
  • Env aliasOS_PERF_TIMING=1 now also enables global mode, matching the issue's naming; OS_SERVER_TIMING=true still works.

Header shape (unchanged, now admin-gated per-request)

Server-Timing: auth;dur=42, db;dur=210;desc="6 queries", hooks;dur=18, serialize;dur=7, total;dur=355

Gating summary

Mode How Who sees the header
Global serverTiming: true / OS_SERVER_TIMING=true / OS_PERF_TIMING=1 every caller
Per-request caller sends X-OS-Debug-Timing: 1 admin/service/system only
Hard off serverTiming: false nobody (no middleware)

Open-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: false hard-off; global discloses to everyone; OS_PERF_TIMING=1 alias; isDebugTimingRequested spellings.
  • http-dispatcher.perf-gating.test.ts — the admin/service/system predicate.
  • Existing global-mode behavior (unit + real-HTTP integration + SQL db span) unchanged.

Two CORS tests were updated to assert the precise intent (cors() not configured) instead of "no use() at all", since the perf-timing middleware now legitimately registers its own use('*') by default; one also gained a try/finally so a failed assertion can't leak OS_CORS_ENABLED.

All affected package builds (DTS typecheck) pass. One pre-existing, unrelated runtime seed-replay test failure was confirmed to fail identically on the base revision.


Generated by Claude Code

…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
@vercel

vercel Bot commented Jul 18, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
spec Ready Ready Preview, Comment Jul 18, 2026 5:31am

Request Review

@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 3 package(s): @objectstack/observability, @objectstack/plugin-hono-server, @objectstack/runtime.

17 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/api/index.mdx (via @objectstack/runtime)
  • content/docs/api/wire-format.mdx (via @objectstack/runtime)
  • content/docs/automation/hook-bodies.mdx (via @objectstack/runtime)
  • content/docs/concepts/north-star.mdx (via packages/runtime)
  • content/docs/data-modeling/drivers.mdx (via @objectstack/runtime)
  • content/docs/deployment/index.mdx (via @objectstack/runtime)
  • content/docs/deployment/production-readiness.mdx (via @objectstack/runtime)
  • content/docs/deployment/single-project-mode.mdx (via @objectstack/runtime)
  • content/docs/deployment/vercel.mdx (via @objectstack/runtime)
  • content/docs/getting-started/your-first-project.mdx (via @objectstack/plugin-hono-server, @objectstack/runtime)
  • content/docs/permissions/authentication.mdx (via @objectstack/plugin-hono-server, @objectstack/runtime)
  • content/docs/plugins/index.mdx (via @objectstack/plugin-hono-server)
  • content/docs/plugins/packages.mdx (via @objectstack/plugin-hono-server, @objectstack/runtime)
  • content/docs/protocol/kernel/http-protocol.mdx (via @objectstack/runtime)
  • content/docs/protocol/kernel/index.mdx (via @objectstack/runtime)
  • content/docs/protocol/kernel/lifecycle.mdx (via @objectstack/runtime)
  • content/docs/releases/implementation-status.mdx (via @objectstack/observability, @objectstack/plugin-hono-server, @objectstack/runtime)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

…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
@github-actions github-actions Bot added the documentation Improvements or additions to documentation label Jul 18, 2026
@os-zhuang
os-zhuang marked this pull request as ready for review July 18, 2026 05:50
@os-zhuang
os-zhuang merged commit 2049b6a into main Jul 18, 2026
16 checks passed
@os-zhuang
os-zhuang deleted the claude/per-request-perf-timing-tp6lq4 branch July 18, 2026 05:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants