Use S2S-only OBS with isolated app-token providers across samples - #339
Use S2S-only OBS with isolated app-token providers across samples#339Krishnadheeraj (DheerajPannala) wants to merge 1 commit into
Conversation
Configure S2S OBS across sample languages, add isolated blueprint-to-agent application-token providers, preserve workload OBO, and cover routing and authentication failure paths. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Dependency ReviewThe following issues were found:
License Issuespython/crewai/sample_agent/pyproject.toml
OpenSSF ScorecardScorecard details
Scanned Files
|
| catch (Exception) | ||
| { | ||
| // No exception bodies/inner exceptions: identity SDK and HTTP failures can contain credentials. | ||
| throw new InvalidOperationException("Observability app token acquisition failed; check OBS configuration, credentials and application authorization."); | ||
| } |
| foreach (var claimName in new[] { "appid", "azp" }) | ||
| { | ||
| if (claims.TryGetProperty(claimName, out var clientId)) | ||
| { | ||
| hasClientId = true; | ||
| if (!string.Equals(clientId.GetString(), _options.AgentId, StringComparison.OrdinalIgnoreCase)) | ||
| { | ||
| throw new InvalidOperationException(); | ||
| } | ||
| } | ||
| } |
| } | ||
|
|
||
| private static string Fixture(string file) => | ||
| File.ReadAllText(Path.Combine(AppContext.BaseDirectory, "ObservabilityFixtures", file)); |
There was a problem hiding this comment.
🟡 Changes recommended
The new OBS-only token helper has an opaque error message (“not a ******”) across multiple samples and the .NET managed-identity assertion scope is likely incorrect without the /.default suffix.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
This PR standardizes Agent 365 observability (OBS) export across the repository to use S2S-only ingestion (/observabilityService), and introduces isolated app-only token providers for interactive samples so OBS authentication is kept separate from business MCP/Graph/OBO authentication.
Changes:
- Adds/updates sample-local OBS app-token resolvers (blueprint FMI → agent client_credentials) and wires them into Node.js, Python, and .NET sample observability configuration.
- Removes legacy “per-request export” and delegated-token fallback paths, and hardens token/expiry/identity validation behavior in samples and tests.
- Updates READMEs,
.env/appsettings templates, and adds regression coverage for route selection and configuration.
File summaries
| File | Description |
|---|---|
| README.md | Documents S2S-only OBS routing, token requirements, and validation commands |
| tests/observability/test_s2s_export.py | Adds mocked HTTP regression test ensuring no legacy route fallback |
| tests/e2e/Agent365.E2E.Tests.csproj | Links shared .NET OBS token provider + fixture sources into E2E test project |
| python/docs/design.md | Updates Python design guidance to use OBS-only token service + exporter options |
| python/openai/sample-agent/README.md | Documents dedicated OBS credentials and flow for OpenAI Python sample |
| python/openai/sample-agent/pyproject.toml | Raises observability core minimum to >= 1.0.0 |
| python/openai/sample-agent/host_agent_server.py | Removes delegated token exchange/caching for OBS |
| python/openai/sample-agent/docs/design.md | Updates design to use exporter_options with S2S + OBS-only resolver |
| python/openai/sample-agent/agent.py | Switches to OBS-only token resolver + exporter_options S2S configuration |
| python/openai/sample-agent/AGENT-CODE-WALKTHROUGH.md | Updates walkthrough to use exporter_options + OBS-only resolver |
| python/openai/sample-agent/.env.template | Adds AGENT365_OBS_* settings; removes legacy KAIRO flag |
| python/openai/sample-agent/observability_token_service.py | Adds sample-local OBS-only token acquisition/cache helper |
| python/observability-with-otlp/README.md | Adds optional A365 S2S export section + prerequisites |
| python/observability-with-otlp/pyproject.toml | Pins observability core minimum to >= 1.0.0 |
| python/observability-with-otlp/main.py | Uses exporter_options with S2S + OBS-only resolver; stamps AgentDetails IDs |
| python/observability-with-otlp/.env.template | Adds AGENT365_OBS_* settings for optional exporter |
| python/observability-with-otlp/observability_token_service.py | Adds sample-local OBS-only token acquisition/cache helper |
| python/observability-with-langgraph/README.md | Documents optional S2S export + removes stub-token narrative |
| python/observability-with-langgraph/pyproject.toml | Pins observability core minimum to >= 1.0.0 |
| python/observability-with-langgraph/main.py | Updates to exporter_options S2S + OBS-only resolver and newer scope APIs |
| python/observability-with-langgraph/.env.template | Adds AGENT365_OBS_* settings for optional exporter |
| python/observability-with-langgraph/observability_token_service.py | Adds sample-local OBS-only token acquisition/cache helper |
| python/observability-with-azure-monitor/README.md | Documents optional S2S export + removes stub-token narrative |
| python/observability-with-azure-monitor/pyproject.toml | Pins observability core minimum to >= 1.0.0 |
| python/observability-with-azure-monitor/main.py | Uses exporter_options S2S + adds explicit baggage context for standalone demo |
| python/observability-with-azure-monitor/.env.template | Adds AGENT365_OBS_* settings for optional exporter |
| python/observability-with-azure-monitor/observability_token_service.py | Adds sample-local OBS-only token acquisition/cache helper |
| python/google-adk/sample-agent/README.md | Documents OBS S2S auth + clarifies app-id vs agent-user attribution |
| python/google-adk/sample-agent/pyproject.toml | Raises observability core minimum to >= 1.0.0 |
| python/google-adk/sample-agent/main.py | Uses exporter_options S2S + OBS-only resolver |
| python/google-adk/sample-agent/agent.py | Uses agentic_app_id (or OBS env) instead of agent-user ID for OBS baggage |
| python/google-adk/sample-agent/.env.template | Adds AGENT365_OBS_* settings |
| python/google-adk/sample-agent/observability_token_service.py | Adds sample-local OBS-only token acquisition/cache helper |
| python/crewai/sample_agent/start_with_generic_host.py | Switches to exporter_options S2S + OBS-only resolver |
| python/crewai/sample_agent/README.md | Documents shared OBS-only resolver for both bootstraps |
| python/crewai/sample_agent/pyproject.toml | Raises observability core minimum to >= 1.0.0 |
| python/crewai/sample_agent/host_agent_server.py | Removes delegated token exchange/caching; wires exporter_options S2S + OBS-only resolver |
| python/crewai/sample_agent/.env.template | Adds AGENT365_OBS_* settings |
| python/crewai/sample_agent/observability_token_service.py | Adds sample-local OBS-only token acquisition/cache helper |
| python/claude/sample-agent/README.md | Documents dedicated OBS credentials and flow |
| python/claude/sample-agent/pyproject.toml | Raises observability core minimum to >= 1.0.0 |
| python/claude/sample-agent/observability_config.py | Uses exporter_options S2S + OBS-only resolver |
| python/claude/sample-agent/host_agent_server.py | Removes delegated token exchange/caching for OBS |
| python/claude/sample-agent/.env.template | Adds AGENT365_OBS_* settings |
| python/claude/sample-agent/observability_token_service.py | Adds sample-local OBS-only token acquisition/cache helper |
| python/agent-framework/sample-agent/README.md | Documents required OBS-only credentials when distro export is enabled |
| python/agent-framework/sample-agent/host_agent_server.py | Uses distro S2S + OBS-only resolver; removes delegated token exchange/caching |
| python/agent-framework/sample-agent/agent.py | Removes legacy cached-token resolver block from agent |
| python/agent-framework/sample-agent/AGENT-CODE-WALKTHROUGH.md | Updates observability section to distro S2S + OBS-only resolver |
| python/agent-framework/sample-agent/.env.template | Adds required AGENT365_OBS_* settings |
| python/agent-framework/sample-agent/observability_token_service.py | Adds sample-local OBS-only token acquisition/cache helper |
| python/autonomous/github-trending/observability_token_service.py | Hardens OBS token acquisition expiry validation and cache behavior |
| python/autonomous/github-trending/main.py | Fails closed when OBS token missing/expired (no empty-token fallback) |
| nodejs/docs/design.md | Updates Node.js design guidance for S2S + OBS-only token resolver patterns |
| nodejs/openai/sample-agent/src/otel.ts | Adds early OBS bootstrap: S2S enabled + token resolver + per-request export guard |
| nodejs/openai/sample-agent/src/observability-token-service.ts | Adds OBS-only blueprint FMI → agent token resolver with strict validation |
| nodejs/openai/sample-agent/src/index.ts | Imports ./otel first to ensure early OBS configuration |
| nodejs/openai/sample-agent/src/client.ts | Removes in-module manager setup; scopes now use turn context identities |
| nodejs/openai/sample-agent/src/agent.ts | Removes delegated token preloading; stamps agent/tenant baggage explicitly |
| nodejs/openai/sample-agent/README.md | Documents OBS-only app auth + legacy service route behavior |
| nodejs/openai/sample-agent/docs/design.md | Updates design docs to reference otel.ts bootstrap and resolver |
| nodejs/openai/sample-agent/AGENT-CODE-WALKTHROUGH.md | Updates walkthrough for new bootstrap + scope signature changes |
| nodejs/openai/sample-agent/.env.template | Adds AGENT365_OBS_* settings; removes custom resolver toggle |
| nodejs/langchain/sample-agent/src/observability-token-service.ts | Adds OBS-only token resolver helper |
| nodejs/langchain/sample-agent/src/index.ts | Distro S2S enabled + durable delivery replay disabled + OBS-only resolver |
| nodejs/langchain/sample-agent/src/client.ts | Uses turnContext/env tenant+agent IDs for attribution |
| nodejs/langchain/sample-agent/src/agent.ts | Removes delegated token preload logic |
| nodejs/langchain/sample-agent/README.md | Documents OBS-only app auth and S2S exporter behavior |
| nodejs/langchain/sample-agent/package.json | Bumps @microsoft/opentelemetry to ^1.4.0 |
| nodejs/langchain/sample-agent/docs/design.md | Updates design docs package references for distro usage |
| nodejs/langchain/sample-agent/Agent-Code-Walkthrough.md | Updates walkthrough imports and scope signature |
| nodejs/langchain/sample-agent/.env.example | Adds AGENT365_OBS_* settings; removes legacy resolver toggle |
| nodejs/vercel-sdk/sample-agent/src/otel.ts | Adds early OBS bootstrap for legacy SDK family + per-request export guard |
| nodejs/vercel-sdk/sample-agent/src/index.ts | Imports ./otel first |
| nodejs/vercel-sdk/sample-agent/src/client.ts | Uses turnContext identities and user details for inference scopes |
| nodejs/vercel-sdk/sample-agent/src/agent.ts | Passes turnContext into client factory for correct attribution |
| nodejs/vercel-sdk/sample-agent/README.md | Documents OBS-only app auth and legacy route selection |
| nodejs/vercel-sdk/sample-agent/docs/design.md | Pins observability package version reference to preview.125 |
| nodejs/vercel-sdk/sample-agent/.env.example | Adds AGENT365_OBS_* settings |
| nodejs/perplexity/sample-agent/src/otel.ts | Adds early OBS bootstrap for legacy SDK family + per-request export guard |
| nodejs/perplexity/sample-agent/src/index.ts | Imports ./otel first |
| nodejs/perplexity/sample-agent/README.md | Documents pinned legacy SDK family + OBS-only app auth |
| nodejs/perplexity/sample-agent/package.json | Pins preview.115 dependencies, adds Node >=22 engines, adds @opentelemetry/core |
| nodejs/perplexity/sample-agent/docs/design.md | Documents otel.ts bootstrap + OBS-only app auth settings |
| nodejs/perplexity/sample-agent/.env.template | Adds AGENT365_OBS_* settings and removes legacy flags |
| nodejs/devin/sample-agent/src/utils.ts | Adds caller details + normalizes tenant/agent ID sourcing for OBS |
| nodejs/devin/sample-agent/src/otel.ts | Adds early OBS bootstrap + per-request export guard |
| nodejs/devin/sample-agent/src/observability-token-service.ts | Adds OBS-only token resolver helper |
| nodejs/devin/sample-agent/src/index.ts | Imports ./otel first; keeps shutdown hook for ObservabilityManager |
| nodejs/devin/sample-agent/src/agent.ts | Removes in-constructor OBS init; improves scope disposal/error recording |
| nodejs/devin/sample-agent/README.md | Documents pinned legacy SDK family + OBS-only app auth |
| nodejs/devin/sample-agent/package.json | Pins preview.115 deps, adds @opentelemetry/core, updates deps |
| nodejs/devin/sample-agent/docs/design.md | Documents otel.ts bootstrap + pinned SDK family |
| nodejs/devin/sample-agent/.env.example | Adds AGENT365_OBS_* settings; removes legacy flags |
| nodejs/copilot-studio/sample-agent/src/otel.ts | Adds early OBS bootstrap + per-request export guard |
| nodejs/copilot-studio/sample-agent/src/index.ts | Imports ./otel first |
| nodejs/copilot-studio/sample-agent/src/client.ts | Refactors scope creation to include baggage + explicit disposal/error recording |
| nodejs/copilot-studio/sample-agent/src/agent.ts | Removes delegated token preload; builds baggage explicitly with IDs |
| nodejs/copilot-studio/sample-agent/README.md | Documents pinned legacy SDK family + OBS-only app auth |
| nodejs/copilot-studio/sample-agent/package.json | Pins preview.115 deps, adds Node >=22 engines, adds @opentelemetry/core |
| nodejs/copilot-studio/sample-agent/.env.template | Adds AGENT365_OBS_* settings; removes legacy resolver toggle |
| nodejs/claude/sample-agent/src/otel.ts | Enables distro S2S + uses OBS-only resolver |
| nodejs/claude/sample-agent/src/client.ts | Removes blueprint secret from subprocess env; uses turnContext/env IDs for scopes |
| nodejs/claude/sample-agent/README.md | Documents OBS-only app auth and distro configuration |
| nodejs/claude/sample-agent/docs/design.md | Updates distro snippet to include S2S + token resolver |
| nodejs/claude/sample-agent/.env.template | Adds AGENT365_OBS_* settings |
| nodejs/autonomous/github-trending/src/observability-token-service.ts | Requires real expiry for cached OBS tokens; sanitizes error bodies |
| nodejs/autonomous/github-trending/src/index.ts | Fails closed if OBS token missing/expired; removes empty-token fallback |
| dotnet/shared/Observability/ObservabilityAppTokenFactory.cs | Adds managed-identity assertion wiring for shared OBS-only token provider |
| dotnet/w365-computer-use/sample-agent/W365ComputerUseSample.csproj | Adds Azure.Identity alias and links shared Observability sources |
| dotnet/w365-computer-use/sample-agent/Telemetry/ObservabilityServiceCollectionExtensions.cs | Uses S2S endpoint + injects dedicated OBS token resolver |
| dotnet/w365-computer-use/sample-agent/Telemetry/A365OtelWrapper.cs | Removes delegated token registration/caching path for OBS |
| dotnet/w365-computer-use/sample-agent/README.md | Documents Agent365Observability config + deployment guidance |
| dotnet/w365-computer-use/sample-agent/Program.cs | Wires shared OBS-only app token provider into OpenTelemetry config |
| dotnet/w365-computer-use/sample-agent/appsettings.json | Adds BlueprintClientId/Secret + managed identity knobs |
| dotnet/w365-computer-use/sample-agent/Agent/MyAgent.cs | Removes exporter token cache dependencies from agent constructor/calls |
| dotnet/semantic-kernel/sample-agent/SemanticKernelSampleAgent.csproj | Adds Azure.Identity alias and links shared Observability sources |
| dotnet/semantic-kernel/sample-agent/README.md | Documents dedicated OBS-only credentials and S2S configuration |
| dotnet/semantic-kernel/sample-agent/Program.cs | Injects shared OBS-only resolver and enables S2S on exporter |
| dotnet/semantic-kernel/sample-agent/appsettings.json | Adds Agent365Observability configuration template |
| dotnet/docs/design.md | Updates .NET design docs for shared OBS-only provider + S2S-only exporter |
| dotnet/autonomous/github-trending/sample-agent/Program.cs | Fails closed if OBS token missing/expired (no empty-token fallback) |
| dotnet/agent-framework/sample-agent/README.md | Documents dedicated OBS-only credentials and deployment considerations |
| dotnet/agent-framework/sample-agent/Program.cs | Injects shared OBS-only resolver and enables S2S on exporter |
| dotnet/agent-framework/sample-agent/appsettings.json | Replaces legacy client ID/secret fields with OBS-only settings |
| dotnet/agent-framework/sample-agent/AgentFrameworkSampleAgent.csproj | Adds Azure.Identity alias and links shared Observability sources |
| dotnet/agent-framework/sample-agent/Agent/MyAgent.cs | Removes delegated token registration for OBS; keeps business auth separate |
| agent-platforms/salesforce/apex-observability/README.md | Marks endpoint-selection flag deprecated; OBS always uses S2S path |
| agent-platforms/salesforce/apex-observability/force-app/main/default/objects/A365_Observability_Config__mdt/fields/UseS2SEndpoint__c.field-meta.xml | Deprecates legacy route flag metadata and labeling |
| agent-platforms/salesforce/apex-observability/force-app/main/default/classes/A365TelemetryTest.cls | Adds tests asserting no legacy route fallback (including on 401) |
| agent-platforms/salesforce/apex-observability/force-app/main/default/classes/A365ObsConfig.cls | Forces S2S route selection regardless of deprecated flag |
Review details
- Files reviewed: 141/141 changed files
- Comments generated: 9
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| var assertion = await credential.GetTokenAsync( | ||
| new TokenRequestContext(["api://AzureADTokenExchange"]), | ||
| cancellationToken).ConfigureAwait(false); |
| raise ObservabilityTokenError(f"OBS {step} response has no access token.") | ||
| token_type = result.get("token_type") | ||
| if not isinstance(token_type, str) or token_type.lower() != "bearer": | ||
| raise ObservabilityTokenError(f"OBS {step} response is not a Bearer token.") |
| raise ObservabilityTokenError(f"OBS {step} response has no access token.") | ||
| token_type = result.get("token_type") | ||
| if not isinstance(token_type, str) or token_type.lower() != "bearer": | ||
| raise ObservabilityTokenError(f"OBS {step} response is not a Bearer token.") |
| raise ObservabilityTokenError(f"OBS {step} response has no access token.") | ||
| token_type = result.get("token_type") | ||
| if not isinstance(token_type, str) or token_type.lower() != "bearer": | ||
| raise ObservabilityTokenError(f"OBS {step} response is not a Bearer token.") |
| raise ObservabilityTokenError(f"OBS {step} response has no access token.") | ||
| token_type = result.get("token_type") | ||
| if not isinstance(token_type, str) or token_type.lower() != "bearer": | ||
| raise ObservabilityTokenError(f"OBS {step} response is not a Bearer token.") |
| raise ObservabilityTokenError(f"OBS {step} response has no access token.") | ||
| token_type = result.get("token_type") | ||
| if not isinstance(token_type, str) or token_type.lower() != "bearer": | ||
| raise ObservabilityTokenError(f"OBS {step} response is not a Bearer token.") |
| raise ObservabilityTokenError(f"OBS {step} response has no access token.") | ||
| token_type = result.get("token_type") | ||
| if not isinstance(token_type, str) or token_type.lower() != "bearer": | ||
| raise ObservabilityTokenError(f"OBS {step} response is not a Bearer token.") |
| raise ObservabilityTokenError(f"OBS {step} response has no access token.") | ||
| token_type = result.get("token_type") | ||
| if not isinstance(token_type, str) or token_type.lower() != "bearer": | ||
| raise ObservabilityTokenError(f"OBS {step} response is not a Bearer token.") |
| raise ObservabilityTokenError(f"OBS {step} response has no access token.") | ||
| token_type = result.get("token_type") | ||
| if not isinstance(token_type, str) or token_type.lower() != "bearer": | ||
| raise ObservabilityTokenError(f"OBS {step} response is not a Bearer token.") |
Summary
client_credentialswithfmi_path, followed by agent-instanceclient_credentialswith the exchange assertion. Keep business MCP/Graph/OBO authentication separate.Compatibility and setup
AGENT365_OBS_*settings. .NET uses dedicatedAgent365Observabilityconfiguration and supports blueprint secrets or managed identity./otlpS2S suffix. That route's authorization policy is not asserted to be equivalent to the public OTLP route.Validation