Skip to content

feat(observability): admin-only per-request timing detail via X-OS-Debug-Timing: json (#2408)#3169

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

feat(observability): admin-only per-request timing detail via X-OS-Debug-Timing: json (#2408)#3169
os-zhuang merged 1 commit into
mainfrom
claude/per-request-perf-timing-tp6lq4

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

What & why

Follow-up to #3163, completing the optional richer JSON diagnostic from #2408. The basic Server-Timing header tells you how many queries ran and their total time — but not which ones. This adds an admin-only per-query breakdown: an admin/service caller sends X-OS-Debug-Timing: json and gets back the slowest SQL statements (by shape) in a separate X-OS-Debug-Timing-Detail response header.

Crucially, the detail is admin-only even under global mode — an ordinary caller never sees SQL shapes, and even an admin only ever sees the parametrized statement (knex's ? placeholders), never the bindings, so no literal row value leaves the server.

How

  • @objectstack/observabilityPerfTiming gains opt-in per-event detail capture (enableDetail / recordDetail / details) plus the ambient recordServerTimingDetail. Detail is off by default (zero allocation on the hot path) and bounded by a cap. The disclosure gate gains a second level, privileged (set by allowPerfDisclosure, read via isPerfDisclosurePrivileged), so the richer detail is gated independently of the basic header.
  • @objectstack/driver-sql — when detail capture is on, the knex query listener additionally records each query's parametrized statement + duration. Free when off (one boolean check).
  • @objectstack/plugin-hono-serverX-OS-Debug-Timing: json enables detail capture; the middleware emits X-OS-Debug-Timing-Detail (the slowest queries, capped and sanitized to header-safe printable ASCII) only when the principal is a proven admin (gate.privileged).

Detail payload shape

{"db":{"count":6,"totalMs":210.3,"slowest":{"sql":"select * from widgets where id = ?","dur":88.1},"queries":[{"sql":"select * from widgets where id = ?","dur":88.1}, ]}}

Disclosure matrix

Request Global off Global on
no debug header nothing basic header
X-OS-Debug-Timing: 1, non-admin nothing basic header
X-OS-Debug-Timing: 1, admin basic header basic header
X-OS-Debug-Timing: json, non-admin nothing basic header, no detail
X-OS-Debug-Timing: json, admin basic header + detail basic header + detail

Tests / verification

  • Unit: detail collector (capture/off/cap/categories), the privileged gate level, the driver's parametrized-SQL capture (asserts the literal binding never appears), and the middleware's json/basic/global paths incl. the "never leak detail under global mode" invariant.
  • Real-HTTP end-to-end (server-timing.integration.test.ts): boots a real Hono server + real SQLite driver, and over a genuine socket confirms an admin sending X-OS-Debug-Timing: json gets the X-OS-Debug-Timing-Detail header with the actual query shapes, while a non-admin never does — even under global mode. This is the "does it work against a running server?" check that a browser dogfood would do, made deterministic.
  • docs/OBSERVABILITY.md + a minor changeset updated.

All affected package builds (DTS typecheck) pass. Basic and global behavior are unchanged — json is purely additive.

Note on the open-core boundary

This is the last framework-side piece of #2408. The remaining item — surfacing this as a cloud console "perf panel" and defining the cloud's own "who is admin" policy — lives in the cloud repo (mechanism here, policy there), consistent with the existing observability split.


Generated by Claude Code

…bug-Timing: json (#2408)

Completes the optional "richer JSON" diagnostic from #2408. On top of the basic
Server-Timing header, an admin/service caller can request a per-query breakdown
— the slowest SQL statements + a query count — with `X-OS-Debug-Timing: json`,
returned in a separate `X-OS-Debug-Timing-Detail` header (compact JSON). It is
admin-only **even under global mode** — an ordinary caller never sees SQL shapes.

- observability: `PerfTiming` gains opt-in per-event detail capture
  (`enableDetail` / `recordDetail` / `details`) + ambient
  `recordServerTimingDetail`. The disclosure gate gains a `privileged` level
  (set by `allowPerfDisclosure`, read via `isPerfDisclosurePrivileged`) so the
  richer detail is gated independently of the basic header.
- driver-sql: with detail on, the query listener also records each query's
  PARAMETRIZED statement (knex `q.sql`, `?` placeholders) — never the bindings,
  so no literal row value enters the collector. Free when detail is off.
- plugin-hono-server: `X-OS-Debug-Timing: json` enables capture; the middleware
  emits `X-OS-Debug-Timing-Detail` (slowest queries, capped + sanitized to
  header-safe ASCII) only for a proven admin.

Tests: unit coverage for the detail collector, the privileged gate level, the
driver's parametrized-SQL capture, the middleware's json/basic/global paths, and
a real-HTTP end-to-end test (admin gets the detail header over a genuine socket
running real SQL; a non-admin never does, even under global mode). Docs +
changeset updated. Basic/global behavior unchanged; `json` is purely additive.

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 6:17am

Request Review

@github-actions github-actions Bot added documentation Improvements or additions to documentation tests tooling labels Jul 18, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

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

19 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/driver-sql, @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/glossary.mdx (via @objectstack/driver-sql)
  • 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/anatomy.mdx (via @objectstack/driver-sql)
  • content/docs/plugins/index.mdx (via @objectstack/plugin-hono-server)
  • content/docs/plugins/packages.mdx (via @objectstack/driver-sql, @objectstack/plugin-hono-server, @objectstack/runtime)
  • content/docs/protocol/kernel/http-protocol.mdx (via @objectstack/runtime)
  • content/docs/protocol/kernel/index.mdx (via @objectstack/driver-sql, @objectstack/runtime)
  • content/docs/protocol/kernel/lifecycle.mdx (via @objectstack/driver-sql, @objectstack/runtime)
  • content/docs/releases/implementation-status.mdx (via @objectstack/observability, @objectstack/driver-sql, @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.

@os-zhuang
os-zhuang marked this pull request as ready for review July 18, 2026 12:22
@os-zhuang
os-zhuang merged commit efbcfe1 into main Jul 18, 2026
16 checks passed
@os-zhuang
os-zhuang deleted the claude/per-request-perf-timing-tp6lq4 branch July 18, 2026 12:23
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/l tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants