check_run:
  id: OBS-006
  status: partial
  evidence_collected: 6
  evidence_required: 3
  findings:
    - "SDK present and wired: pyproject.toml:38-41 declares opentelemetry-api/sdk/exporter-otlp/instrumentation-httpx as runtime (not optional) dependencies; src/swisstopo_mcp/observability.py:58-84 builds a TracerProvider with a Resource carrying service.name, adds BatchSpanProcessor(OTLPSpanExporter()) and calls HTTPXClientInstrumentor().instrument(). setup_tracing() runs first in the lifespan at src/swisstopo_mcp/server.py:36, before create_shared_client() at server.py:37 — the ordering the httpx patching requires."
    - "RUNTIME VERIFIED — the no-op claim holds. With OTEL_EXPORTER_OTLP_ENDPOINT unset: setup_tracing() → False, tracing_enabled() → False, get_tracer() → None, httpx.AsyncClient.send is NOT patched, and the global provider stays the inert ProxyTracerProvider. A @log_tool_call-decorated handler still returned its result unchanged. Driving the real stdio server with SWISSTOPO_LOG_LEVEL=DEBUG produced {\"reason\": \"OTEL_EXPORTER_OTLP_ENDPOINT unset\", \"event\": \"tracing_disabled\"} and nothing else tracing-related. Guard at observability.py:53-56; an all-whitespace value also counts as unset (.strip())."
    - "RUNTIME VERIFIED — the tool span itself excludes arguments. With a real TracerProvider + InMemorySpanExporter and a handler invoked as handler(search_text=\"Seilergraben 76, Zürich\", lat=47.3769), the emitted span `mcp.tool/swisstopo_geocode` carried exactly {'mcp.tool.name': 'swisstopo_geocode', 'mcp.tool.result.is_error': False} — no argument values, no extra keys. Implementation at src/swisstopo_mcp/logging_config.py:88-115."
    - "RUNTIME REFUTED — the argument-exclusion claim fails end to end. Set OTEL_EXPORTER_OTLP_ENDPOINT, ran setup_tracing() (which returned True and instrumented httpx), then issued a real geo_admin_request through the respx-mocked client. The httpx auto-instrumentation emitted a child span `GET` with http.url = `https://api3.geo.admin.ch/rest/services/ech/SearchServer?searchText=Seilergraben+76%2C+Z%C3%BCrich&lat=47.3769`. The user's address and coordinates land in the observability backend verbatim as a span attribute — via the very instrumentation observability.py:79 enables. The exclusion is enforced on the parent span only; the child span defeats it."
    - "Handled errors are read from the envelope rather than inferred: logging_config.py:110-115 sets mcp.tool.result.is_error from getattr(result, 'is_error', False), and exceptions are recorded at logging_config.py:99-101. Both covered by tests/test_observability.py:81-102."
    - "OTLP configuration is env-driven and documented, not hardcoded: deploy/kubernetes.yaml:51-56 sets OTEL_SERVICE_NAME, OTEL_EXPORTER_OTLP_ENDPOINT (empty = off) and OTEL_RESOURCE_ATTRIBUTES=deployment.environment=production; docs/deployment.md:58-60 documents all three."
  gaps:
    - "The httpx child spans carry full request URLs including query strings. Every tool argument that becomes a query parameter — geocoding search text, coordinates, canton, PLZ, layer and feature IDs, the Overpass area — is exported to the tracing backend. Needs a span processor or an httpx-instrumentation url-sanitising hook that strips or hashes the query string before export."
    - "tests/test_observability.py:104-117 asserts argument exclusion on the tool span only. It never enables the httpx instrumentation, so the test passes while the actual leak path is untested — the test's own claim (\"must not land in a tracing backend\") is broader than what it verifies."
    - "No mcp.user.id attribute. Defensible here (auth_model=none, no identity exists), but the check lists it as a per-call span requirement, so the user-behaviour-analysis workflow the check motivates is unavailable."
  evaluator_notes: |
    Two of the three claims survive contact with a running interpreter: the
    no-op behaviour is exactly as advertised (verified four ways, including
    against the real stdio server), and the tool span carries only the tool
    name and the error flag. The third does not. "Tool arguments never become
    span attributes" is true of the span the module writes and false of the
    system the module configures: enabling httpx auto-instrumentation is what
    puts `?searchText=Seilergraben+76,+Zürich&lat=47.3769` into an exported
    span attribute. Since this only bites when tracing is switched on — i.e.
    precisely in the cloud deployment this check applies to — it is a real
    defect rather than a documentation nit. Partial.
