Filed as directed by the maintainer's 2026-08-18 ruling on #9650, which chose the transport-owned seam (Option A) and named this its principled successor, to be its own packages/spec card and explicitly not folded into that fix.
Verbatim from the ruling:
Option B (IHttpServer response-observing contract hook) is the principled successor: file it as its own spec card, ⛔ not folded into this fix.
Documented expectation: a transport that does not implement the seam reports no HTTP metrics. State this plainly in the docs/PR body rather than letting it read as coverage.
Filed unassigned; not graded by the filing seat.
The gap this closes
PR #9746 emits http_requests_total{method,route,status} from the Hono adapter, as a raw-app middleware installed at the end of HonoServerPlugin.init(). That reaches every inbound request on that transport, including the getRawApp() mounts no IHttpServer-level wrapper can see — measured, at least 14 surfaces in two classes on #9650.
What it cannot reach is another transport. packages/qa/http-conformance/src/adapter.ts:37-41 registers http.server and deliberately does not implement getRawApp(); it has no equivalent seam, so it emits nothing. Zero there means "not instrumented", never "no traffic" — and that is precisely the failure mode #9650 is about, one level up. The ruling accepts it as a documented expectation for now; this card is how it stops being one.
Why the existing use() seam is not the answer — measured, not argued
packages/runtime/src/http-metrics-inbound-coverage.hono.integration.test.ts pins it: the framework-agnostic use() middleware chain runs to completion and only then returns Hono's next(), so a middleware there sees method, path, query and headers and has no response. It cannot carry the status label, which is the label the whole operator guidance (5xx rate) is keyed on. Body is deliberately absent there too, for the reasons the use() contract states.
So the contract needs something the current one cannot express: an observation point that runs after the response exists.
Sketch, for the spec seat to accept or replace
An afterResponse style registration on IHttpServer — something an adapter invokes once per request with { method, routePattern, status, elapsedMs } — plus:
Not urgent, but not free to defer indefinitely
The label shape ships with #9650. Once dashboards are wired against it, any adapter that later joins with a different label convention is a breaking change to the operator's queries rather than to any code, which is the class of change nothing in CI can catch.
Generated by Claude Code
Filed as directed by the maintainer's 2026-08-18 ruling on #9650, which chose the transport-owned seam (Option A) and named this its principled successor, to be its own
packages/speccard and explicitly not folded into that fix.Verbatim from the ruling:
Filed unassigned; not graded by the filing seat.
The gap this closes
PR #9746 emits
http_requests_total{method,route,status}from the Hono adapter, as a raw-app middleware installed at the end ofHonoServerPlugin.init(). That reaches every inbound request on that transport, including thegetRawApp()mounts noIHttpServer-level wrapper can see — measured, at least 14 surfaces in two classes on #9650.What it cannot reach is another transport.
packages/qa/http-conformance/src/adapter.ts:37-41registershttp.serverand deliberately does not implementgetRawApp(); it has no equivalent seam, so it emits nothing. Zero there means "not instrumented", never "no traffic" — and that is precisely the failure mode #9650 is about, one level up. The ruling accepts it as a documented expectation for now; this card is how it stops being one.Why the existing
use()seam is not the answer — measured, not arguedpackages/runtime/src/http-metrics-inbound-coverage.hono.integration.test.tspins it: the framework-agnosticuse()middleware chain runs to completion and only then returns Hono'snext(), so a middleware there sees method, path, query and headers and has no response. It cannot carry thestatuslabel, which is the label the whole operator guidance (5xx rate) is keyed on. Body is deliberately absent there too, for the reasons theuse()contract states.So the contract needs something the current one cannot express: an observation point that runs after the response exists.
Sketch, for the spec seat to accept or replace
An
afterResponsestyle registration onIHttpServer— something an adapter invokes once per request with{ method, routePattern, status, elapsedMs }— plus:http_requests_totalnever sees auth routes or the REST data API — the two highest-traffic inbound surfaces are outside the only HTTP counter the docs tell operators to monitor #9650 and non-negotiable downstream:/api/v1/data/:id, not one series per record id. The contract has to make the pattern the thing an adapter is required to supply, or each adapter re-decides cardinality.getPort/getRawApp, whose optional-member erasure is the shaperuntime导出的HttpServer包装器静默丢弃IHttpServer的全部可选成员(getPort/getRawApp/setFallbackHandler) #5122 records.instrumentRouteHandler— see the sibling card on the double count.Not urgent, but not free to defer indefinitely
The label shape ships with #9650. Once dashboards are wired against it, any adapter that later joins with a different label convention is a breaking change to the operator's queries rather than to any code, which is the class of change nothing in CI can catch.
Generated by Claude Code