diff --git a/AGENTS.md b/AGENTS.md index 49401a2..fcd365d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,118 +1,202 @@ -## Project Overview +# Splunk Agent Observability Python SDK Agent Guide -Splunk Agent Observability Python SDK (`splunk-ao` on PyPI) — the official Python client for Splunk Agent Observability. Instrument LLM/agent apps, send traces and metrics, manage projects, datasets, experiments, and prompts. +This guide covers the entire repository. All paths are repository-root relative. Read `README.md` for supported user +workflows and `ARCHITECTURE.md` before changing telemetry, configuration, lifecycle, or integration behavior. -Successor to [`galileo-python`](https://github.com/rungalileo/galileo-python); migration notes in `splunk-ao-migration-tool/README.md`. +## Permission Model -SDK code lives under `src/splunk_ao/`. Do not edit `galileo-core` or `src/splunk_ao/resources/` (auto-generated). +- Read-only discovery is allowed without approval: file reads, `rg`, `git status`, and `git diff`. +- Before running any project command, show the exact command, explain its scope, and ask. This includes tests, lint, + formatting, type checks, builds, installs, lock updates, scripts, code generation, and documentation generation. +- A requested implementation authorizes scoped file edits, not unrelated cleanup or expansion. +- Never stage or commit changes unless explicitly requested. Never push, publish, release, or bump a version without an + explicit request and separate confirmation of the exact action. +- Do not dispatch, rerun, cancel, or otherwise operate a GitHub Actions workflow unless the task explicitly requests it. + You may identify a relevant workflow as an optional next step, but do not seek approval to run it unless the user asks + to proceed. When execution is requested, show the exact workflow, ref, inputs, and command and wait for approval. +- Never expose credentials, tokens, `.env` contents, customer payloads, or private/internal planning material. -### Deployment Modes +## Subagents -| Mode | Auth | Notes | -|------|------|-------| -| **O11y Cloud** | `SPLUNK_AO_REALM` + `SPLUNK_AO_SF_TOKEN` | Do not set `SPLUNK_AO_CONSOLE_URL` / `SPLUNK_AO_API_URL` | -| **Standalone** | `SPLUNK_AO_API_KEY` + `SPLUNK_AO_CONSOLE_URL` | Self-hosted or legacy AO | +- Use subagents only for concrete, bounded, independent work where parallelism materially improves speed or quality. + Prefer read-heavy exploration, review, triage, and independent package analysis. +- Do not delegate trivial, tightly coupled, or sequential work. Avoid concurrent edits to the same files. +- The main agent owns scope, architectural decisions, integration, and final review. It must read task-defining + instructions itself rather than outsourcing its understanding. +- Give each subagent an explicit scope, relevant paths, constraints, and expected output. Require a concise, + evidence-backed handoff. +- Subagents inherit every permission boundary in this guide. Delegation must never bypass approval for project commands, + GitHub workflows, staging, commits, releases, or publishing. +- When subagents edit files, assign non-overlapping ownership and review the combined diff before completion. -Detection: `src/splunk_ao/deployment.py::resolve_deployment()`. Never mix o11y and standalone env vars. +## Commands (Reference Only—Ask Before Running) -Optional defaults: `SPLUNK_AO_PROJECT`, `SPLUNK_AO_AGENT_STREAM` (deprecated alias: `SPLUNK_AO_LOG_STREAM`). - -`SplunkAOConfig` bridges `SPLUNK_AO_*` → `GALILEO_*` for `galileo-core` (see `config.py::_BRIDGE`). - -## Build & Development +Root SDK (`splunk-ao`, Poetry 2.4.1): ```bash -poetry install --all-extras --no-root # or: inv setup -poetry run pytest # single file: poetry run pytest tests/test_foo.py -inv test # with coverage -inv type-check # mypy -poetry run ruff check --fix src/ # lint + format +poetry install --all-extras --no-root +poetry run pytest tests/test_deployment.py -n 0 # targeted, deterministic +poetry run pytest # full unit suite +poetry run invoke test # full suite with terminal coverage +poetry run invoke type-check # configured mypy run +poetry run ruff check --no-fix src tests # non-mutating lint +poetry run ruff format --check src tests # non-mutating format check +poetry build ``` -CI: mypy + pytest on Python 3.11–3.14 × Linux/macOS/Windows. Pre-commit: ruff + mypy. +A2A and ADK packages use uv/Hatch. Each block starts independently from the repository root. -## Architecture +A2A: -``` -src/splunk_ao/ -├── project.py, dataset.py, experiment.py, prompt.py # Object-centric API (import via splunk_ao.__future__) -├── logger/ # SplunkAOLogger — trace/span management -├── handlers/ # LangChain, CrewAI, OpenAI Agents integrations -├── openai/ # Drop-in OpenAI client wrapper -├── resources/ # Auto-generated API client — DO NOT EDIT -├── decorator.py # @log, splunk_ao_context -├── config.py # SplunkAOConfig -└── deployment.py # O11y vs standalone detection -``` - -**Regenerate API client:** ```bash -./scripts/import-openapi-yaml.sh https://api.galileo.ai/client -./scripts/auto-generate-api-client.sh +cd splunk-ao-a2a +uv sync --dev +uv run pytest +uv run mypy src/ +uv run ruff check src tests +uv build ``` -Uses OpenAPI **Client API** (`/client`), not the main API (`/docs`). +ADK: -Depends on `galileo-core` for shared schemas and helpers; ongoing work to reduce this. - -## Key Patterns - -**Object-centric API** (`__future__`): -```python -from splunk_ao.__future__ import Project -project = Project.get(name="my-project") # retrieve -project = Project(name="new").create() # create -agent_streams = project.list_agent_streams() +```bash +cd splunk-ao-adk +uv sync --dev +uv run pytest +uv run mypy src/ +uv run ruff check src tests +uv build ``` -**Service layer** (procedural): -```python -from splunk_ao.datasets import create_dataset -from splunk_ao.experiments import run_experiment -``` +Potentially mutating commands require approval and a clean diff first: -**Logging:** -```python -from splunk_ao import log, splunk_ao_context - -@log -def my_workflow(): ... - -with splunk_ao_context(project="my-project", agent_stream="prod"): - my_workflow() +```bash +poetry run ruff check --fix +poetry run ruff format +poetry run pre-commit run --files +poetry run python scripts/create_docs.py ``` -**Handlers:** `splunk_ao.handlers.langchain` (`SplunkAOCallback`), `splunk_ao.handlers.crewai` (`CrewAIEventListener`), `splunk_ao.openai` (drop-in wrapper). - -## Testing +Regenerate the low-level API client only when the task explicitly changes the Client OpenAPI contract, and still ask: -Fixtures in `tests/conftest.py`: `mock_request`, `mock_healthcheck`, `mock_login_api_key`. Tests use `--disable-socket`; env vars set in conftest for pytest-xdist. - -CrewAI wraps stdout/stderr at import time. In tests, patch `_crewai_imports_resolved` / `CREWAI_AVAILABLE`, mock `AgentStreams`/`Projects`/`Traces`, and pass a mock `SplunkAOLogger` (see `tests/test_crewai_handler.py`). +```bash +./scripts/import-openapi-yaml.sh https://api.galileo.ai/client +./scripts/auto-generate-api-client.sh +``` -Use Given/When/Then comments in tests (`# Given: …`, `# When: …`, `# Then: …`). +The generator replaces `src/splunk_ao/resources/`; review the complete generated diff. It uses the Client API (`/client`), +not the main API documentation contract. + +## Stack and Package Map + +| Area | Stack and responsibility | +|---|---| +| `src/splunk_ao/` | Python 3.11–3.14, Pydantic v2, OpenTelemetry 1.38, `galileo-core` 4.x; public SDK | +| `tests/` | pytest 9, xdist, respx, socket blocking, timeout and coverage plugins | +| `splunk-ao-a2a/` | Independently released native-OTel A2A instrumentation; uv/Hatch | +| `splunk-ao-adk/` | Independently released Google ADK handler integration; uv/Hatch | +| `splunk-ao-migration-tool/` | Migration documentation and examples, not a buildable package | +| `src/splunk_ao/resources/` | OpenAPI-generated transport client; never hand-edit | + +The three buildable packages have independent versions, lockfiles, CI, and release workflows. Validate every package a +change touches. CI supports Python 3.11–3.14; root CI also spans Linux, macOS, and Windows. + +## Architecture and Public Surfaces + +- `src/splunk_ao/__init__.py` defines supported root imports. `src/splunk_ao/__future__/` is a compatibility/re-export + surface; do not assume object APIs are available only there. +- Singular modules (`project.py`, `dataset.py`, `experiment.py`, and peers) implement stateful object APIs. Plural modules + (`projects.py`, `datasets.py`, `experiments.py`, and peers) implement procedural/service APIs. Preserve both. +- `logger/`, `decorator.py`, `handlers/`, and `openai/` instrument applications. `otel.py` is the native OTel entry point. +- `exporter/` owns deployment-aware OTLP export, span normalization, lifecycle, and diagnostics. +- `config.py` bridges selected `SPLUNK_AO_*` variables to legacy `GALILEO_*` inputs used by `galileo-core`. +- `galileo-core` is an external dependency. Do not edit or vendor it here; adapt at this repository's boundary. +- See `ARCHITECTURE.md` for telemetry paths, ownership rules, and a change-impact map. + +## Configuration and Routing Invariants + +| Deployment | Required authentication | +|---|---| +| O11y Cloud | `SPLUNK_AO_REALM` plus `SPLUNK_AO_O11Y_TOKEN`; that token may serve CRUD when permitted, or use a dedicated `SPLUNK_AO_O11Y_API_TOKEN` | +| Standalone | `SPLUNK_AO_API_KEY` plus `SPLUNK_AO_CONSOLE_URL`; `SPLUNK_AO_API_URL` is optional | + +- Detection lives in `deployment.py::resolve_deployment()`. Never mix O11y and standalone variable sets. +- Project/Agent Stream selection is name XOR ID. Precedence is explicit argument, active context, environment, then + deployment defaults. Routing must agree in OTLP headers and Resource attributes. +- `OTEL_RESOURCE_ATTRIBUTES` is not an SDK routing override. Remove reserved routing keys before merging it. +- Configuration is stateful across `Configuration`, environment variables, and `SplunkAOConfig`; tests that change it + must reset all affected state and singleton instances. +- Never log auth headers, tokens, raw prompts, completions, embeddings, or large payloads. + +## Telemetry and Error Boundaries + +- Handler/decorator/OpenAI/ADK telemetry uses the internal logged-step path and converts completed steps to immutable + OTel spans. The internal trace envelope is never exported as a span. +- `start_splunk_ao_span()` is SDK-native OTel. `add_splunk_ao_span_processor()` and A2A instrument caller-owned OTel. +- Never replace the process-global tracer provider. Register processors on the provided provider; respect ownership. +- Treat ended `ReadableSpan` objects as immutable. Normalize by copying at export, never by mutating private fields. +- Completed spans enqueue immediately. `flush()` drains completed work without ending active work; `terminate()` drains, + shuts down SDK-owned resources, and discards unfinished state. Caller-owned providers use `shutdown()`. +- CRUD/resource operations raise useful failures. Telemetry infrastructure failures must not break instrumented business + code; sanitize and rate-limit diagnostics. +- Preserve standard `gen_ai.*` attributes. New SDK-owned attributes use `splunk_ao.*`; do not introduce new proprietary + `galileo.*` wire attributes. +- Changes to propagation, IDs, parents, content schemas, or routing need coverage across every affected telemetry path. ## Code Style -- Line length 120; ruff + mypy; numpy docstrings -- Conventional commits: `type(scope): description` -- Imports at module level (exception: lazy imports for optional deps like crewai) -- Duration vars need units: `timeout_seconds`, `delay_ms` -- Use `logging.getLogger(__name__)`; never log secrets or large payloads - -**Error handling:** Resource ops (`create_project`, `get_dataset`, …) raise on failure. Telemetry/ingestion (`ingest_traces`, `flush`, `@log`) swallows infra errors — observability should not break user code. +- Line length 120; Ruff for lint/format; mypy for typing; NumPy-style public docstrings. +- Keep imports at module scope except intentional lazy imports for optional integrations. +- Use `logging.getLogger(__name__)`, typed signatures, and unit-bearing names such as `timeout_seconds` or `delay_ms`. +- Prefer the smallest compatible change. Do not combine feature work with drive-by formatting or generated diffs. +- Maintain sync, async, generator, and async-generator semantics where an API supports them. -## Known Issues +Tests should show intent explicitly: -1. **galileo-core dependency** — private package, contributor friction -2. **Config state** — split across `Configuration`, `os.environ`, `SplunkAOConfig`; `connect()` must be called explicitly -3. **Dataset versions** — API is 1-based, not 0-based -4. **Experiment vs Playground** — SDK `Experiment` conflates two API concepts -5. **Metadata** — SDK stringifies values in handlers; Trace vs Dataset APIs behave differently - -## References +```python +def test_flush_does_not_end_active_trace(mock_request) -> None: + # Given: an active trace with one completed child span + # When: completed telemetry is flushed + # Then: the child is exported and the active trace remains open + ... +``` -- PyPI: https://pypi.org/project/splunk-ao/ -- GitHub: https://github.com/splunk/splunk-ao-python -- Migration: `splunk-ao-migration-tool/README.md` -- Contributing: `CONTRIBUTING.md` +## Testing Rules + +- Add the closest focused regression test first; ask before running it. Run broader suites only after targeted confidence. +- Root tests inherit `-n auto`, network blocking, a 120-second timeout, and fake standalone credentials from pytest config. + Use `-n 0` for deterministic focused debugging. +- Set test environment variables before importing `splunk_ao`; xdist workers and Python 3.14 expose import-order leaks. +- Reuse `tests/conftest.py` fixtures such as `mock_request`, `mock_healthcheck`, and `mock_login_api_key`. Mock all network. +- Reset global OTel context, providers/processors, SDK configuration, loggers, and background resources after tests. +- Exercise success, exceptions, cancellation/early generator close, and cleanup for lifecycle-sensitive instrumentation. +- CrewAI is optional and excluded on Python 3.14. Preserve lazy imports and test both installed/unavailable behavior. +- Dataset version numbers are API-facing and 1-based. + +## Change Workflow and Git + +1. Read the public API, implementation, adjacent tests, and relevant architecture section before editing. +2. Identify ownership: public wrapper, integration, converter, exporter, generated client, or external dependency. +3. Preserve compatibility unless the task explicitly authorizes a breaking change. Update exports, docstrings, README usage, + tests, and `CHANGELOG.md` when public behavior changes. +4. Ask before running the exact validation commands. Report what ran, what did not run, and why. +5. Review `git diff` for secrets, unrelated rewrites, generated churn, and platform-specific assumptions. + +Use conventional commit subjects (`type(scope): description`) only when a commit is explicitly requested. Do not edit +versions, release workflows, or lockfiles as incidental cleanup. + +## Hard Boundaries + +- Do not hand-edit `src/splunk_ao/resources/`, generated reference docs, or generated lock content. +- Do not change release/publish configuration, dependency pins, or public compatibility aliases without task scope. +- Do not silently add network calls, global state, import-time side effects, unbounded queues, or non-daemon threads. +- Do not make tests depend on real credentials, live services, ordering, timing luck, or another test's state. +- Do not document unavailable internal context. Repository documentation must stand alone for public contributors. + +## Progressive References + +- `README.md`: installation, authentication, supported APIs, and integration examples. +- `ARCHITECTURE.md`: package boundaries, telemetry data flow, lifecycle, and change-impact routing. +- `CONTRIBUTING.md`: contribution setup and generated-client workflow. +- `src/splunk_ao/README_API_CLIENT.md`: generated client's capabilities and limitations. +- `splunk-ao-migration-tool/README.md`: migration guidance from `galileo-python`. diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md new file mode 100644 index 0000000..4b46a41 --- /dev/null +++ b/ARCHITECTURE.md @@ -0,0 +1,228 @@ +# Splunk Agent Observability Python SDK Architecture + +This document is the progressive-disclosure companion to `AGENTS.md`. It describes stable, public repository +architecture rather than plans or release sequencing. Read only the sections relevant to the change, then inspect the +referenced code and tests; code remains authoritative. + +## Repository Topology + +The repository contains three independently built and released packages: + +| Package | Source | Purpose | Tooling | +|---|---|---|---| +| `splunk-ao` | `src/splunk_ao/` | Core API, logging, integrations, CRUD, OTLP export | Poetry | +| `splunk-ao-a2a` | `splunk-ao-a2a/src/splunk_ao_a2a/` | A2A client/server native OTel instrumentation | uv/Hatch | +| `splunk-ao-adk` | `splunk-ao-adk/src/splunk_ao_adk/` | Google ADK handler/plugin integration | uv/Hatch | + +`splunk-ao-migration-tool/` currently contains migration documentation and examples. `docs/` contains repository +documentation; generated API references are produced by `scripts/create_docs.py`. + +## Core SDK Layers + +| Layer | Primary locations | Responsibility | +|---|---|---| +| Public facade | `__init__.py`, `__future__/` | Stable exports and compatibility aliases | +| Object API | singular resource modules | Stateful create/get/update/delete workflows | +| Service API | plural resource modules | Procedural operations and orchestration | +| Generated transport | `resources/` | OpenAPI models and HTTP calls; generated as one unit | +| Instrumentation | `logger/`, `decorator.py`, `handlers/`, `openai/` | Capture application operations and content | +| Native OTel | `otel.py` | Span creation and processor registration for OTel users | +| Conversion | `attribute_mapping.py`, `span_converter.py` | Internal completed steps to OTel `ReadableSpan` data | +| Export | `exporter/` | Routing, immutable normalization, OTLP transport, diagnostics | +| Configuration | `config.py`, `configuration.py`, `deployment.py` | Deployment detection, compatibility bridge, endpoint/auth selection | + +Object resources use lifecycle states through `StateManagementMixin` (local, synced, modified, failed, deleted). Keep +object behavior and procedural helpers consistent. The generated transport is an implementation detail; public callers +should normally enter through the SDK APIs. + +## Telemetry Data Flow + +There are three supported ingress paths. They converge at export but have different ownership and conversion rules. + +```text +Path 1: decorator / logger / handlers / OpenAI / ADK + -> internal LoggedTrace + completed steps + -> SpanConverter + -> OTel ReadableSpan copies + +Path 2: start_splunk_ao_span() + -> SDK-created OTel spans + +Path 3: external OTel/OpenInference + A2A + -> caller TracerProvider + -> add_splunk_ao_span_processor(provider) + +All paths -> NormalizingSpanExporter -> deployment-aware OTLP exporter -> backend +``` + +### Path 1: internal step model + +`SplunkAOLogger`, `@log`, LangChain, CrewAI, OpenAI Agents, the drop-in OpenAI wrapper, and ADK build the established +internal trace/step model. Stable OTel context is associated with actual operations. Completed steps are converted to +OTel spans; the `LoggedTrace` envelope remains an internal lifecycle container and must not become a wire span. + +Do not bypass this path when extending an existing handler. It centralizes lifecycle behavior, content conversion, +session handling, and compatibility hooks. Conversion changes must cover workflow, agent, LLM, tool, retriever, and +Agent Control kinds that they affect. + +### Path 2: SDK-native OTel + +`start_splunk_ao_span()` creates spans through SDK OTel support and applies SDK semantic attributes at completion. It is +useful when a caller wants explicit OTel spans without the internal step model. Do not mutate an ended span; downstream +normalization receives an immutable copy. + +### Path 3: caller-owned OTel + +`add_splunk_ao_span_processor(provider)` attaches export to a provider supplied by the application. Standard OTel and +OpenInference instrumentations can then flow directly to the backend. A2A follows this path and manages A2A-specific +client/server wrapping and message-context propagation. + +The SDK may add processors, but it must never silently replace the global tracer provider. The application owns the +provider and calls `shutdown()`; SDK-owned logger/export resources use their own termination path. + +## Span Lifecycle and Export Ownership + +`SpanSink` owns the SDK's private provider and `BatchSpanProcessor` for internal telemetry. A completed operation is +enqueued immediately, allowing scheduled export without an explicit flush. + +- `flush()` / `async_flush()` drain completed work and do not end an active trace. +- `terminate()` drains completed work, shuts down SDK-owned telemetry resources, and releases unfinished state. +- Caller-owned OTel providers are not terminated by the SDK; their owner calls `shutdown()`. +- Completion, emission, and state release are separate operations. Preserve idempotency under retries and cleanup. +- Telemetry failures are diagnostic events, not reasons to fail the instrumented application. + +OTel `ReadableSpan` data is effectively immutable after end. `exporter/span_transform.py` creates normalized copies; +never change private span fields in place. This protects concurrent exporters and caller-owned processors. + +## Attributes, Content, and Routing + +Standard GenAI attributes and structured content are the interoperability contract. Preserve upstream `gen_ai.*` +values unless a documented SDK rule fills or sanitizes them. SDK-specific wire attributes belong under `splunk_ao.*`. + +Routing is transport metadata, not an ordinary per-span override: + +1. Resolve Project and Agent Stream by name XOR ID. +2. Apply precedence: explicit call, active `splunk_ao_context`, environment, deployment default. +3. Capture routing when the exporter is built. +4. Stamp the authoritative selection in both request headers and OTel Resource attributes. +5. Ignore/remove conflicting reserved keys from `OTEL_RESOURCE_ATTRIBUTES`. + +Changing routing requires tests for precedence, name/ID exclusivity, headers, Resource attributes, and both deployment +modes. It also requires attention to when exporters/configuration singletons are constructed and reset. + +## Deployment and Configuration + +`deployment.py::resolve_deployment()` selects a mode by environment-variable presence: + +| Mode | CRUD auth | Telemetry auth/endpoints | +|---|---|---| +| O11y Cloud | `SPLUNK_AO_O11Y_TOKEN` when permitted, or a dedicated `SPLUNK_AO_O11Y_API_TOKEN` | realm-derived endpoint plus `SPLUNK_AO_O11Y_TOKEN` | +| Standalone | `SPLUNK_AO_API_KEY` | console/API configuration and deployment-derived OTLP endpoint | + +O11y also requires `SPLUNK_AO_REALM`; standalone requires `SPLUNK_AO_CONSOLE_URL`. The primary O11y token can authorize +both telemetry and CRUD when it has the required permissions; the API token provides separate CRUD credentials when +needed. Do not combine deployment modes. + +Configuration has compatibility state in more than one layer: + +- `Configuration` is the user-facing facade and synchronizes supported settings with the environment. +- `SplunkAOConfig` extends the `galileo-core` configuration and bridges selected `SPLUNK_AO_*` inputs. +- exporter and API-client instances may capture resolved settings. + +Tests that mutate configuration must establish environment state before importing the SDK, then reset the environment, +configuration facades, cached singletons, and exporter state. Never read or print real developer credentials in tests. + +## Error Model + +The SDK has two intentional failure policies: + +| Operation | Expected behavior | +|---|---| +| CRUD/resource operations | Raise actionable errors; callers requested the operation and need its result | +| Telemetry capture/export/flush | Contain infrastructure failures so observability does not break business code | + +Containment does not mean silence. Exporters log sanitized transport/auth/rejection diagnostics, rate-limit repetitive +receiver failures, and expose bounded acknowledgement health. Do not log secrets or raw application content. + +## Integration Boundaries + +### LangChain, CrewAI, and OpenAI Agents + +These handlers adapt framework events into the internal Path 1 model. Keep optional dependencies lazy and imports free +of surprising side effects. CrewAI is excluded on Python 3.14; code and tests must support the unavailable path. + +### OpenAI wrapper + +`src/splunk_ao/openai/` is a drop-in wrapper. Preserve the upstream caller experience and sync/async response semantics. +Telemetry failures must not change an OpenAI call's application-visible outcome. + +### A2A + +A2A is independently released and native OTel (Path 3). Instrumentation and uninstrumentation must be idempotent. +Streaming, early close/cancellation, client/server parentage, and message metadata context all need focused coverage. + +### ADK + +ADK is independently released but handler-based (Path 1). Its plugin, observer, span tracker, manager, trace builder, and +data conversion layers share session and lifecycle state; changes must preserve concurrency and cleanup behavior. + +### Agent Control + +Agent Control bridges control execution into the core telemetry model. Classification attributes and input/output fields +are a backend contract. Test its public helpers and its final OTLP representation when changing the bridge. + +## Generated Client Boundary + +`openapi.yaml` and the scripts under `scripts/` define regeneration. The generated `resources/` tree is replaced as a +unit and post-processed by repository scripts/templates. Never patch an individual generated file to fix a public API; +change the source contract, template, or post-processing step and regenerate only with explicit approval. + +The generated client is not a deployment-aware public facade. Public operations should select deployment/auth through +SDK configuration, then call the appropriate generated transport internally. + +## Test Architecture + +Root pytest configuration enables xdist, disables non-local sockets, injects fake credentials, and sets timeouts. +`tests/conftest.py` provides HTTP/auth fixtures, global reset logic, a fast configuration-validation fixture, and a +legacy ingestion-hook capture for compatibility tests. + +For telemetry changes, select tests by path and lifecycle rather than testing only the changed function: + +- Conversion: internal schema -> converter -> final OTel attributes/content. +- SDK-native OTel: start/end, exceptions, context nesting, immutable export copy. +- Caller-owned OTel/A2A: processor registration, provider ownership, propagation, streaming cleanup. +- Logger/decorator: sync, async, generator, async generator, nesting, flush, termination. +- Configuration: both deployments, precedence, invalid mixed state, singleton reset, sanitized diagnostics. +- Integrations: installed and missing optional dependency behavior; no real service calls. + +Use Given/When/Then comments, deterministic IDs/clocks where needed, and explicit cleanup. A passing focused test is not +sufficient if the change crosses a package or telemetry-path boundary. + +## Change-Impact Map + +| Change | Inspect together | Minimum evidence to seek approval to run | +|---|---|---| +| Auth, realm, endpoints | `deployment.py`, config facades, `exporter/o11y.py`, `exporter/standalone.py` | both modes; invalid/mixed env; no secret logging | +| Routing/context | context APIs, config, exporter builder, Resource transform | precedence; name/ID; header/Resource agreement | +| Span attributes/content | schemas, `attribute_mapping.py`, converter, span transform | Path 1 plus any affected native/external path | +| IDs or propagation | `tracing.py`, logger context, middleware, A2A metadata | local/nested/distributed parentage; malformed input | +| Flush/termination | logger, sink, processors/exporters | active work, completed work, failures, repeated cleanup | +| Object/service API | singular and plural modules, exports, generated transport | state transitions; sync/async; public import tests | +| Handler integration | handler, logger/converter, optional dependency tests | success/error/streaming; installed/unavailable modes | +| Generated API | `openapi.yaml`, scripts, templates, complete `resources/` diff | regeneration plus public wrapper regression tests | +| A2A or ADK | package source, package tests, package pyproject/CI | that package's targeted test, mypy, lint; core tests if shared | + +## Public Compatibility Checklist + +Before calling a change complete, inspect whether it changes: + +- root or `__future__` imports; +- method signatures, defaults, enum values, or return types; +- environment variables, auth headers, routing, or endpoint construction; +- OTel span names, kinds, parents, attributes, events, status, or structured content; +- logger/provider ownership, flushing, shutdown, or background work; +- supported Python/framework versions or optional dependency behavior; +- examples, README guidance, generated docs, migration guidance, or changelog entries. + +Public behavior changes should be documented and tested in the same change. Compatibility aliases and deprecated +parameters are still public surfaces until their removal is explicitly authorized. diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..517439e --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,5 @@ +@AGENTS.md + +## Claude Code + +