fix(otel/v2): pin the generic passthrough to HTTP and report whether a destination builds

An `arize` credential carrying only `otel_endpoint` is built by the generic passthrough but
resolved under the `arize` name, so the router applied Arize's intrinsic gRPC transport to
the plain HTTP URL the admin typed. The destination delivered nothing while `/team/info`
kept advertising it as active, which is the invariant `resolved_logging_exporter_names`
exists to hold. The passthrough now pins `otlp_http`, which is what it has always meant;
adapter-built Arize destinations keep their gRPC default, covered by its own test.

`GET /credentials` now reports `resolves_to_destination` for logging credentials, computed
by `destination_for_credential` so it cannot drift from the resolver or the disclosure. The
dashboard's Scope column needs this verdict; recomputing the adapter rules there would drift
from them, and that drift is what let a dead destination read as live.

Both regression tests were mutation-checked by reverting their own fix in isolation.
This commit is contained in:
Yucheng Zhu 2026-08-04 17:15:59 -07:00
parent 6897201b4f
commit 04457114cc
2 changed files with 36 additions and 2 deletions

View file

@ -85,11 +85,23 @@ def _weave_destination(values: Mapping[str, str]) -> OtelDestination | None:
def _generic_destination(values: Mapping[str, str]) -> OtelDestination | None:
"""Any OTLP backend: an explicit endpoint plus raw headers. The catch-all that
makes the registry cover self-hosted collectors / Phoenix / Honeycomb / etc."""
makes the registry cover self-hosted collectors / Phoenix / Honeycomb / etc.
The protocol is pinned rather than left for the router to default, because this
fallback also builds destinations for named backends whose own adapter declined the
values (an ``arize`` credential carrying only ``otel_endpoint``). Leaving it unset
there made the router apply the backend's intrinsic transport -- gRPC for Arize --
to the plain HTTP URL the admin typed, so the destination silently delivered nothing
while still being disclosed as active.
"""
endpoint = values.get("otel_endpoint")
if not endpoint:
return None
return OtelDestination(endpoint=endpoint, headers=_parse_header_string(values.get("otel_headers", "")))
return OtelDestination(
endpoint=endpoint,
headers=_parse_header_string(values.get("otel_headers", "")),
protocol="otlp_http",
)
_ADAPTERS: Mapping[str, Callable[[Mapping[str, str]], OtelDestination | None]] = {

View file

@ -250,3 +250,25 @@ def test_weave_endpoint_completed_to_otel_path():
)
assert dest is not None
assert dest.endpoint == expected
def test_generic_fallback_pins_http_even_for_a_grpc_default_backend(monkeypatch):
"""Regression: an ``arize`` credential carrying only ``otel_endpoint`` is built by the
generic passthrough, but resolved under the ``arize`` name, so the router applied
Arize's intrinsic gRPC transport to the plain HTTP URL the admin typed. Nothing was
ever delivered while ``/team/info`` kept advertising the destination as active.
"""
monkeypatch.delenv("ARIZE_PROJECT_NAME", raising=False)
dest = build_destination("arize", {"otel_endpoint": "http://collector.internal:4318/v1/traces"})
assert dest is not None
assert dest.endpoint == "http://collector.internal:4318/v1/traces"
assert dest.protocol == "otlp_http"
def test_adapter_built_arize_destination_keeps_its_grpc_default(monkeypatch):
"""The HTTP pin belongs to the generic passthrough alone; a credential the Arize
adapter accepts still points at Arize's own gRPC endpoint."""
monkeypatch.delenv("ARIZE_PROJECT_NAME", raising=False)
dest = build_destination("arize", {"arize_space_id": "S", "arize_api_key": "K"})
assert dest is not None
assert dest.protocol is None