SIGN IN SIGN UP

feat(obs): a server span that carries the template, and a test that can tell

Dimension 6.1, and a new crate `afd_observability` holding the attribute
vocabulary. The exporter arrives with the transport at boot; what lands here is
everything it plugs into.

## The dependency points away from HTTP

`afd_observability` knows nothing about axum. A route template is a fact in the
router and a string anywhere else, so the layer that puts one on a span lives in
`afd_api` and takes the vocabulary from here. That also leaves this crate usable
by the runner binary, which serves no HTTP at all.

## The test that nearly proved nothing

Four of the five tests here passed against a layer emitting `uri().path()`.
Every route this binary mounts is static, so for all of them the template and
the raw path are the SAME STRING — a whole dimension's worth of assertions
satisfied by the implementation it exists to forbid.

`test_a_parameterised_route_reports_its_template` is the one that discriminates:
it mounts the layer over `/v1/workspaces/{workspace_id}/secrets/{secret_name}`
and asserts neither the workspace id nor the secret's name reaches the span.
Verified by inverting the implementation and watching only that test fail. The
layer is `pub` for this reason — the property is unprovable against the two
static routes this binary serves, and it is what §7 mounts anyway.

## Two field names cannot be constants

`tracing`'s macros take field names as SYNTAX, so `semconv::ATTR_HTTP_ROUTE`
cannot be substituted into `info_span!`. `Span::record` does take a value, so
the status field names its constant directly; the other two are literals pinned
by a test, which is the closest available to what the compiler gives the third.

## Spans are collected through a Layer, not from formatted output

Asserting on rendered log lines tests the formatter as much as the
instrumentation, and cannot distinguish an ABSENT field from one that rendered
empty. The negative assertions here need fields as values.

An unmatched request opens no span at all — `route_layer`, so it never runs.
A 404 has no template, and the alternatives are a span carrying the raw path
(the leak) or one carrying a placeholder (a series that groups nothing).
K
Kishore Kumar committed
a7df206816e6263f8279d8ac829fe3805f8a4f9a
Parent: 66bea03