Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
652 changes: 652 additions & 0 deletions docs/LCORE-Strategic-Path-Forward.md

Large diffs are not rendered by default.

37 changes: 37 additions & 0 deletions docs/devel_doc/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -6753,6 +6753,14 @@
}
],
"name": "lightspeed-stack",
"observability": {
"otel": {
"OTEL_EXPORTER_OTLP_ENDPOINT": "",
"OTEL_EXPORTER_OTLP_PROTOCOL": "",
"OTEL_SDK_DISABLED": "true",
"OTEL_SERVICE_NAME": ""
}
},
"quota_handlers": {
"enable_token_history": false,
"limiters": [],
Expand Down Expand Up @@ -12367,6 +12375,11 @@
"title": "Splunk configuration",
"description": "Splunk HEC configuration for sending telemetry events."
},
"observability": {
"$ref": "#/components/schemas/ObservabilityConfiguration",
"title": "Observability configuration",
"description": "OpenTelemetry and observability configuration collected from OTEL_* environment variables."
},
"deployment_environment": {
"type": "string",
"title": "Deployment environment",
Expand Down Expand Up @@ -12463,6 +12476,14 @@
}
],
"name": "lightspeed-stack",
"observability": {
"otel": {
"OTEL_EXPORTER_OTLP_ENDPOINT": "",
"OTEL_EXPORTER_OTLP_PROTOCOL": "",
"OTEL_SDK_DISABLED": "true",
"OTEL_SERVICE_NAME": ""
}
},
"quota_handlers": {
"enable_token_history": false,
"limiters": [],
Expand Down Expand Up @@ -15107,6 +15128,22 @@
"title": "OAuthFlows",
"description": "Defines the configuration for the supported OAuth 2.0 flows."
},
"ObservabilityConfiguration": {
"properties": {
"otel": {
"additionalProperties": {
"type": "string"
},
"type": "object",
"title": "OpenTelemetry configuration",
"description": "Active OpenTelemetry configuration from OTEL_* environment variables"
}
},
"additionalProperties": false,
"type": "object",
"title": "ObservabilityConfiguration",
"description": "OpenTelemetry observability configuration.\n\nThis configuration is automatically populated from OTEL_* environment variables\nto provide visibility into the active tracing setup.\n\nAttributes:\n otel: Dictionary of OTEL_* environment variables (excluding secrets)."
},
"OkpConfiguration": {
"properties": {
"rhokp_url": {
Expand Down
8 changes: 8 additions & 0 deletions src/models/api/responses/successful/configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,14 @@ class ConfigurationResponse(AbstractSuccessfulResponse):
"scheduler": {"period": 1},
"enable_token_history": False,
},
"observability": {
"otel": {
"OTEL_SDK_DISABLED": "true",
"OTEL_EXPORTER_OTLP_ENDPOINT": "",
"OTEL_EXPORTER_OTLP_PROTOCOL": "",
"OTEL_SERVICE_NAME": "",
}
},
}
}
]
Expand Down
40 changes: 40 additions & 0 deletions src/models/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

# pylint: disable=too-many-lines

import os
import re
from enum import Enum
from functools import cached_property
Expand Down Expand Up @@ -2830,6 +2831,38 @@ def compiled_patterns(self) -> CompiledPatterns:
return list(self._compiled_patterns)


class ObservabilityConfiguration(ConfigurationBase):
"""OpenTelemetry observability configuration.

This configuration is automatically populated from OTEL_* environment variables
to provide visibility into the active tracing setup.

Attributes:
otel: Dictionary of OTEL_* environment variables (excluding secrets).
"""

otel: dict[str, str] = Field(
default_factory=dict,
title="OpenTelemetry configuration",
description="Active OpenTelemetry configuration from OTEL_* environment variables",
)

@classmethod
def from_environment(cls) -> "ObservabilityConfiguration":
"""Collect all OTEL_* environment variables from the environment.

Returns:
ObservabilityConfiguration with otel dict populated from environment.
"""
otel_vars = {}
for key, value in os.environ.items():
if key.startswith("OTEL_"):
# Exclude sensitive variables that might contain tokens or keys
# Currently no secret OTEL_ vars are used, but this is future-proof
otel_vars[key] = value
return cls(otel=otel_vars)
Comment thread
coderabbitai[bot] marked this conversation as resolved.


class Configuration(ConfigurationBase):
"""Global service configuration."""

Expand Down Expand Up @@ -2991,6 +3024,13 @@ class Configuration(ConfigurationBase):
description="Splunk HEC configuration for sending telemetry events.",
)

observability: ObservabilityConfiguration = Field(
default_factory=ObservabilityConfiguration.from_environment,
title="Observability configuration",
description="OpenTelemetry and observability configuration collected "
"from OTEL_* environment variables.",
)

deployment_environment: str = Field(
"development",
title="Deployment environment",
Expand Down
77 changes: 77 additions & 0 deletions tests/integration/endpoints/test_config_integration.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Integration tests for the /config endpoint."""

import os
from typing import cast

import pytest
Expand Down Expand Up @@ -88,3 +89,79 @@ async def test_config_endpoint_fails_without_configuration(
assert "response" in exc_info.value.detail
detail = cast(dict[str, str], exc_info.value.detail)
assert "configuration is not loaded" in detail["response"].lower()


@pytest.mark.asyncio
async def test_config_endpoint_includes_observability(
current_config: AppConfig, # pylint: disable=unused-argument
test_request: Request,
test_auth: AuthTuple,
) -> None:
"""Test that config endpoint includes observability configuration.

This integration test verifies:
- Observability field is present in the configuration response
- OTEL environment variables are correctly collected
- Response structure includes observability.otel block

Parameters:
----------
current_config (AppConfig): Loads root configuration
test_request (Request): FastAPI request
test_auth (AuthTuple): noop authentication tuple
"""
response = await config_endpoint_handler(auth=test_auth, request=test_request)

# Verify observability field exists
assert hasattr(response.configuration, "observability")
assert response.configuration.observability is not None

# Verify otel field exists
assert hasattr(response.configuration.observability, "otel")
assert isinstance(response.configuration.observability.otel, dict)


@pytest.mark.asyncio
async def test_config_endpoint_observability_collects_otel_vars(
current_config: AppConfig, # pylint: disable=unused-argument
test_request: Request, # pylint: disable=unused-argument
test_auth: AuthTuple, # pylint: disable=unused-argument
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""Test that observability config collects OTEL_* environment variables.

This integration test verifies:
- OTEL_* environment variables are collected into observability.otel
- Non-OTEL variables are not included
- Environment variables set at runtime are reflected in config

Parameters:
----------
current_config (AppConfig): Loads root configuration
test_request (Request): FastAPI request
test_auth (AuthTuple): noop authentication tuple
monkeypatch (pytest.MonkeyPatch): Fixture to modify environment variables
"""
# pylint: disable=import-outside-toplevel
from models.config import ObservabilityConfiguration

# Set OTEL environment variables
monkeypatch.setenv("OTEL_SDK_DISABLED", "true")
monkeypatch.setenv("OTEL_SERVICE_NAME", "test-service")
monkeypatch.setenv("OTEL_EXPORTER_OTLP_ENDPOINT", "http://localhost:4317")

# Reload configuration to pick up new env vars
updated_observability = ObservabilityConfiguration.from_environment()

# Verify OTEL vars are present in the environment
# (Note: The config is loaded at startup, so these may not be in the response
# unless we reload the entire config. This test verifies the mechanism works.)
assert "OTEL_SDK_DISABLED" in os.environ
assert "OTEL_SERVICE_NAME" in os.environ
assert "OTEL_EXPORTER_OTLP_ENDPOINT" in os.environ

# Verify that when we call from_environment(), it picks up the vars
assert "OTEL_SDK_DISABLED" in updated_observability.otel
assert updated_observability.otel["OTEL_SDK_DISABLED"] == "true"
assert "OTEL_SERVICE_NAME" in updated_observability.otel
assert updated_observability.otel["OTEL_SERVICE_NAME"] == "test-service"
Comment on lines +125 to +167

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Assert the mutated values through the endpoint.

This test never invokes config_endpoint_handler after setting the variables; it only re-tests ObservabilityConfiguration.from_environment(). Set the environment before rebuilding the app/config fixture, call the endpoint, and assert the OTEL values in response.configuration.observability.otel. Otherwise removal of the Configuration default factory can still pass this integration test.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/integration/endpoints/test_config_integration.py` around lines 125 -
167, Update test_config_endpoint_observability_collects_otel_vars to set the
OTEL environment variables before rebuilding the app/config fixture, then invoke
config_endpoint_handler and assert the values through
response.configuration.observability.otel. Remove the direct
ObservabilityConfiguration.from_environment() assertions so the test validates
endpoint behavior and catches configuration default-factory regressions.

Comment on lines +133 to +167

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Assert the complete environment contract.

Add an assertion that OTEL_EXPORTER_OTLP_ENDPOINT appears in updated_observability.otel, and set/assert a non-OTEL variable is excluded. The current test verifies only setup state for those cases.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/integration/endpoints/test_config_integration.py` around lines 133 -
167, Extend the test around ObservabilityConfiguration.from_environment() to
assert OTEL_EXPORTER_OTLP_ENDPOINT is present in updated_observability.otel with
its configured value, then set a non-OTEL environment variable and assert it is
excluded from updated_observability.otel.

14 changes: 14 additions & 0 deletions tests/unit/models/config/test_dump_configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@
"max_content_length": constants.SAVED_PROMPTS_DEFAULT_MAX_CONTENT_LENGTH,
}

_DEFAULT_OBSERVABILITY_DUMP: dict[str, dict[str, str]] = {
"otel": {},
}

Comment on lines +47 to +50

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Isolate OTEL environment state in dump tests.

Because Configuration derives observability from OTEL_*, these tests can fail whenever the runner already has an OTEL variable. Clear OTEL_* via a fixture or pass an explicit empty ObservabilityConfiguration before asserting the fixed dump.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/unit/models/config/test_dump_configuration.py` around lines 47 - 50,
Isolate OTEL environment state in the dump tests by adding a fixture that clears
all OTEL_* variables, or by supplying an explicit empty
ObservabilityConfiguration when constructing Configuration. Ensure assertions
against _DEFAULT_OBSERVABILITY_DUMP remain deterministic regardless of the test
runner environment.

_MCP_SERVER_DUMP_DEFAULTS: dict[str, Any] = {
"authorization_headers": {},
"headers": [],
Expand Down Expand Up @@ -232,6 +236,7 @@ def test_dump_configuration_minimal_cfg(tmp_path: Path) -> None:
"quota_subject": None,
},
"splunk": None,
"observability": _DEFAULT_OBSERVABILITY_DUMP,
"deployment_environment": "development",
"reranker": {
"enabled": False,
Expand Down Expand Up @@ -459,6 +464,7 @@ def test_dump_configuration_valid_values(tmp_path: Path) -> None:
"quota_subject": None,
},
"splunk": None,
"observability": _DEFAULT_OBSERVABILITY_DUMP,
"deployment_environment": "development",
"reranker": {
"enabled": False,
Expand Down Expand Up @@ -837,6 +843,7 @@ def test_dump_configuration_with_quota_limiters(tmp_path: Path) -> None:
"quota_subject": None,
},
"splunk": None,
"observability": _DEFAULT_OBSERVABILITY_DUMP,
"deployment_environment": "development",
"reranker": {
"enabled": False,
Expand Down Expand Up @@ -1099,6 +1106,7 @@ def test_dump_configuration_with_quota_limiters_different_values(
"quota_subject": None,
},
"splunk": None,
"observability": _DEFAULT_OBSERVABILITY_DUMP,
"deployment_environment": "development",
"reranker": {
"enabled": False,
Expand Down Expand Up @@ -1394,6 +1402,7 @@ def test_dump_configuration_byok(tmp_path: Path) -> None:
"quota_subject": None,
},
"splunk": None,
"observability": _DEFAULT_OBSERVABILITY_DUMP,
"deployment_environment": "development",
"reranker": {
"enabled": False,
Expand Down Expand Up @@ -1616,6 +1625,7 @@ def test_dump_configuration_pg_namespace(tmp_path: Path) -> None:
"quota_subject": None,
},
"splunk": None,
"observability": _DEFAULT_OBSERVABILITY_DUMP,
"deployment_environment": "development",
"reranker": {
"enabled": False,
Expand Down Expand Up @@ -1998,6 +2008,7 @@ def test_dump_configuration_allow_degraded_mode(tmp_path: Path) -> None:
"quota_subject": None,
},
"splunk": None,
"observability": _DEFAULT_OBSERVABILITY_DUMP,
"deployment_environment": "development",
"reranker": {
"enabled": False,
Expand Down Expand Up @@ -2226,6 +2237,7 @@ def test_dump_configuration_max_retries_settings(tmp_path: Path) -> None:
"quota_subject": None,
},
"splunk": None,
"observability": _DEFAULT_OBSERVABILITY_DUMP,
"deployment_environment": "development",
"reranker": {
"enabled": False,
Expand Down Expand Up @@ -2454,6 +2466,7 @@ def test_dump_configuration_retry_count_settings(tmp_path: Path) -> None:
"quota_subject": None,
},
"splunk": None,
"observability": _DEFAULT_OBSERVABILITY_DUMP,
"deployment_environment": "development",
"reranker": {
"enabled": False,
Expand Down Expand Up @@ -2689,6 +2702,7 @@ def test_dump_configuration_specific_compaction_values(tmp_path: Path) -> None:
"quota_subject": None,
},
"splunk": None,
"observability": _DEFAULT_OBSERVABILITY_DUMP,
"deployment_environment": "development",
"reranker": {
"enabled": False,
Expand Down
Loading
Loading