From 21b26ed6b5ea7038b6e3feb4793b4c4762ea82a1 Mon Sep 17 00:00:00 2001 From: "Nikhil Chitlur Navakiran (from Dev Box)" Date: Tue, 21 Apr 2026 19:38:01 +0530 Subject: [PATCH 1/2] update scope value to support new exporter path --- CLAUDE.md | 12 ++ .../runtime/environment_utils.py | 4 +- .../core/test_export_config_consistency.py | 109 ++++++++++++++++++ 3 files changed, 124 insertions(+), 1 deletion(-) create mode 100644 tests/observability/core/test_export_config_consistency.py diff --git a/CLAUDE.md b/CLAUDE.md index 17536b03..69f0f2ab 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -219,6 +219,18 @@ Place it before imports with one blank line after. - **Never** use the keyword "Kairo" in code - it's a legacy reference that must be removed/replaced - If found during code review, flag for removal +### Observability Export Configuration — Coordinated Review Required + +The following three constants must stay in sync. If a PR changes **any one** of them, the reviewer (human or Copilot) **must** ask the author to confirm the other two are still correct: + +| Constant | Location | +|---|---| +| `PROD_OBSERVABILITY_SCOPE` | `libraries/microsoft-agents-a365-runtime/microsoft_agents_a365/runtime/environment_utils.py` | +| `DEFAULT_ENDPOINT_URL` | `libraries/microsoft-agents-a365-observability-core/microsoft_agents_a365/observability/core/exporters/agent365_exporter.py` | +| Export URL path pattern | `build_export_url()` in `libraries/microsoft-agents-a365-observability-core/microsoft_agents_a365/observability/core/exporters/utils.py` | + +Snapshot tests in `tests/observability/core/test_export_config_consistency.py` will fail if any value drifts, but the developer must also verify the values are correct for the target environment — the tests only catch accidental drift, not intentional-but-incomplete updates. + ### Python Conventions - Type hints required on all function parameters and return types diff --git a/libraries/microsoft-agents-a365-runtime/microsoft_agents_a365/runtime/environment_utils.py b/libraries/microsoft-agents-a365-runtime/microsoft_agents_a365/runtime/environment_utils.py index 3eb6463b..bbb240ac 100644 --- a/libraries/microsoft-agents-a365-runtime/microsoft_agents_a365/runtime/environment_utils.py +++ b/libraries/microsoft-agents-a365-runtime/microsoft_agents_a365/runtime/environment_utils.py @@ -8,7 +8,9 @@ import os # Authentication scopes for different environments -PROD_OBSERVABILITY_SCOPE = "https://api.powerplatform.com/.default" +PROD_OBSERVABILITY_SCOPE = ( + "api://9b975845-388f-4429-889e-eab1ef63949c/Agent365.Observability.OtelWrite" +) # Cluster categories for different environments PROD_OBSERVABILITY_CLUSTER_CATEGORY = "prod" diff --git a/tests/observability/core/test_export_config_consistency.py b/tests/observability/core/test_export_config_consistency.py new file mode 100644 index 00000000..fef52cc9 --- /dev/null +++ b/tests/observability/core/test_export_config_consistency.py @@ -0,0 +1,109 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +""" +Snapshot tests for observability export configuration. + +These tests pin the production export URL path and authentication scope together. +If the export URL path changes (e.g. from a backend migration), the scope must +be reviewed and updated in lockstep — and vice versa. A failure here is a +reminder to verify both values are consistent with the deployed backend. +""" + +import unittest + +from microsoft_agents_a365.observability.core.exporters.agent365_exporter import ( + DEFAULT_ENDPOINT_URL, +) +from microsoft_agents_a365.observability.core.exporters.utils import build_export_url +from microsoft_agents_a365.runtime.environment_utils import ( + PROD_OBSERVABILITY_SCOPE, + get_observability_authentication_scope, +) + + +class TestExportConfigConsistency(unittest.TestCase): + """Ensure export URL, endpoint, and auth scope stay in sync. + + These are intentionally pinned snapshot values. If any of the three + production constants change (endpoint, scope, URL path), **all three + tests below will likely need updating together**. That forced review is + the whole point — it prevents one value from drifting without the others. + """ + + # ---- pinned production values ---- + + EXPECTED_ENDPOINT = "https://agent365.svc.cloud.microsoft" + EXPECTED_SCOPE = ( + "api://9b975845-388f-4429-889e-eab1ef63949c/Agent365.Observability.OtelWrite" + ) + EXPECTED_STANDARD_PATH = "/observability/tenants/{tid}/otlp/agents/{aid}/traces" + EXPECTED_S2S_PATH = "/observabilityService/tenants/{tid}/otlp/agents/{aid}/traces" + + # ---- snapshot assertions ---- + + def test_default_endpoint_url(self): + """DEFAULT_ENDPOINT_URL must match the expected production endpoint.""" + self.assertEqual( + DEFAULT_ENDPOINT_URL, + self.EXPECTED_ENDPOINT, + "DEFAULT_ENDPOINT_URL changed — also review PROD_OBSERVABILITY_SCOPE " + "and build_export_url() path. All three must stay in sync.", + ) + + def test_prod_observability_scope_value(self): + """PROD_OBSERVABILITY_SCOPE must match the expected production scope.""" + self.assertEqual( + PROD_OBSERVABILITY_SCOPE, + self.EXPECTED_SCOPE, + "PROD_OBSERVABILITY_SCOPE changed — also review DEFAULT_ENDPOINT_URL " + "and build_export_url() path. All three must stay in sync.", + ) + + def test_export_url_standard_path_structure(self): + """Standard export URL must use the pinned path pattern.""" + url = build_export_url(self.EXPECTED_ENDPOINT, "a1", "t1") + expected = ( + f"{self.EXPECTED_ENDPOINT}" + f"{self.EXPECTED_STANDARD_PATH.format(tid='t1', aid='a1')}?api-version=1" + ) + self.assertEqual( + url, + expected, + "Standard export URL path changed — also review PROD_OBSERVABILITY_SCOPE " + "and DEFAULT_ENDPOINT_URL. All three must stay in sync.", + ) + + def test_export_url_s2s_path_structure(self): + """S2S export URL must use the pinned path pattern.""" + url = build_export_url(self.EXPECTED_ENDPOINT, "a1", "t1", use_s2s_endpoint=True) + expected = ( + f"{self.EXPECTED_ENDPOINT}" + f"{self.EXPECTED_S2S_PATH.format(tid='t1', aid='a1')}?api-version=1" + ) + self.assertEqual( + url, + expected, + "S2S export URL path changed — also review PROD_OBSERVABILITY_SCOPE " + "and DEFAULT_ENDPOINT_URL. All three must stay in sync.", + ) + + def test_scope_and_endpoint_are_coherent(self): + """Auth scope and endpoint must both target the agent365 service. + + This is a coarse sanity check: if the endpoint domain changes away + from 'agent365' but the scope still references the old AAD app, or + vice versa, something is likely wrong. + """ + scopes = get_observability_authentication_scope() + self.assertEqual(len(scopes), 1) + scope = scopes[0] + + # Scope should reference the Agent365 Observability permission + self.assertIn("Agent365.Observability", scope) + # Endpoint should be the agent365 service + self.assertIn("agent365", DEFAULT_ENDPOINT_URL) + + +if __name__ == "__main__": + unittest.main() From 880c00a55946266830d25bc91cc76fd6578ee55a Mon Sep 17 00:00:00 2001 From: "Nikhil Chitlur Navakiran (from Dev Box)" Date: Tue, 21 Apr 2026 19:47:41 +0530 Subject: [PATCH 2/2] fix formatting --- tests/observability/core/test_export_config_consistency.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/observability/core/test_export_config_consistency.py b/tests/observability/core/test_export_config_consistency.py index fef52cc9..443aa5d0 100644 --- a/tests/observability/core/test_export_config_consistency.py +++ b/tests/observability/core/test_export_config_consistency.py @@ -34,9 +34,7 @@ class TestExportConfigConsistency(unittest.TestCase): # ---- pinned production values ---- EXPECTED_ENDPOINT = "https://agent365.svc.cloud.microsoft" - EXPECTED_SCOPE = ( - "api://9b975845-388f-4429-889e-eab1ef63949c/Agent365.Observability.OtelWrite" - ) + EXPECTED_SCOPE = "api://9b975845-388f-4429-889e-eab1ef63949c/Agent365.Observability.OtelWrite" EXPECTED_STANDARD_PATH = "/observability/tenants/{tid}/otlp/agents/{aid}/traces" EXPECTED_S2S_PATH = "/observabilityService/tenants/{tid}/otlp/agents/{aid}/traces"