diff --git a/docs/features/environment-variables.md b/docs/features/environment-variables.md index 9bbbac7..7798e83 100644 --- a/docs/features/environment-variables.md +++ b/docs/features/environment-variables.md @@ -101,14 +101,16 @@ SDKs may read only variables explicitly marked with `env_braintrust: true` in The current `.env.braintrust` allowlist is: -| Variable | Purpose | -| ----------------------- | ------------------------------------------------ | -| `BRAINTRUST_API_KEY` | API key for authentication with Braintrust. | -| `BRAINTRUST_APP_URL` | Base URL for the Braintrust web application. | -| `BRAINTRUST_API_URL` | Base URL for the Braintrust API. | -| `BRAINTRUST_PROJECT` | Project name for Braintrust logging and tracing. | -| `BRAINTRUST_PROJECT_ID` | Project ID for logging spans. | -| `BRAINTRUST_ORG_NAME` | Organization name to use when logging in. | +| Variable | Purpose | +| ------------------------------ | ------------------------------------------------ | +| `BRAINTRUST_API_KEY` | API key for authentication with Braintrust. | +| `BRAINTRUST_APP_URL` | Base URL for the Braintrust web application. | +| `BRAINTRUST_API_URL` | Base URL for the Braintrust API. | +| `BRAINTRUST_PROJECT` | Project name for Braintrust logging and tracing. | +| `BRAINTRUST_PROJECT_ID` | Project ID for logging spans. | +| `BRAINTRUST_ORG_NAME` | Organization name to use when logging in. | +| `BRAINTRUST_ENVIRONMENT_TYPE` | Explicit span-origin environment type. | +| `BRAINTRUST_ENVIRONMENT_NAME` | Explicit span-origin environment name. | If `.env.braintrust` contains other names, SDKs must ignore them. They must not expose ignored values through SDK config APIs or process-environment helpers. @@ -165,6 +167,66 @@ SDKs may start candidate file reads in parallel, but must still preserve nearest-file-wins semantics. A higher parent may only win after all closer candidates are known to be missing. +## Span origin environment + +SDKs MAY populate `context.span_origin.environment` to identify the operating +environment where a span was captured. This is provenance about the caller's +runtime, not the Braintrust API or app URL environment. + +SDKs SHOULD support explicit caller options equivalent to: + +```json +{ + "type": "ci", + "name": "github_actions" +} +``` + +SDKs SHOULD also support environment-variable overrides through the normal +Braintrust configuration precedence, including `.env.braintrust` fallback: + +| Variable | Purpose | +| ------------------------------ | ------------------------------------------------------ | +| `BRAINTRUST_ENVIRONMENT_TYPE` | Explicit `context.span_origin.environment.type` value. | +| `BRAINTRUST_ENVIRONMENT_NAME` | Explicit `context.span_origin.environment.name` value. | + +The environment type SHOULD be one of `ci`, `server`, `local`, or `gateway`, +but SDK type definitions SHOULD allow future string values. Environment names +SHOULD be normalized lower-snake-case labels. + +Use this precedence: + +1. A caller-provided SDK option wins over all ambient configuration. +2. `BRAINTRUST_ENVIRONMENT_TYPE` and `BRAINTRUST_ENVIRONMENT_NAME`, resolved + through process environment and `.env.braintrust` fallback, win over + automatic detection. +3. Trusted Braintrust Gateway/internal code may set `gateway`. +4. CI provider detection wins over server and language/framework detection. +5. Server/platform detection wins over language/framework detection. +6. Language/framework deployment-mode detection is a fallback. +7. If no reliable positive signal is present, omit `span_origin.environment`. + +SDKs must not infer `local` from the absence of CI or server signals. +`gateway` is reserved for spans captured by Braintrust Gateway/internal code and +must not be inferred automatically by SDKs or from `metadata.provider`. + +Reliable CI signals include `GITHUB_ACTIONS`, `GITLAB_CI`, `CIRCLECI`, +`BUILDKITE`, `JENKINS_URL`, `JENKINS_HOME`, `TF_BUILD`, `TEAMCITY_VERSION`, +`TRAVIS`, and `BITBUCKET_BUILD_NUMBER`. If no provider-specific signal is +present but `CI` is truthy, SDKs may emit type `ci` and name `ci`. + +Reliable server/platform signals include `VERCEL`, `NETLIFY`, +`AWS_LAMBDA_FUNCTION_NAME`, Lambda `AWS_EXECUTION_ENV`, `K_SERVICE`, +`FUNCTION_TARGET`, `KUBERNETES_SERVICE_HOST`, `ECS_CONTAINER_METADATA_URI`, +`ECS_CONTAINER_METADATA_URI_V4`, `DYNO`, `FLY_APP_NAME`, +`RAILWAY_ENVIRONMENT`, and `RENDER_SERVICE_NAME`. + +Language/framework variables such as `NODE_ENV`, `RAILS_ENV`, `RACK_ENV`, +`ASPNETCORE_ENVIRONMENT`, and `DOTNET_ENVIRONMENT` are weak fallback signals. +Map `production`, `prod`, `staging`, and `stage` to type `server`; map +`development`, `dev`, and `local` to type `local`; ignore `test` unless a +CI signal already classified the environment. + ### Dotenv parsing SDKs should use their runtime's standard dotenv parser when one is already diff --git a/docs/instrumentation-guide.md b/docs/instrumentation-guide.md index ea00df4..b5152a8 100644 --- a/docs/instrumentation-guide.md +++ b/docs/instrumentation-guide.md @@ -87,14 +87,16 @@ The `metrics` field is an object of string keys to numeric values. Instrumentati The `context` field is an object containing textual information about the code and systems that produced the span. SDKs MUST preserve the existing caller-location fields when available; caller-location fields are optional because not every runtime or instrumentation path can determine them. -| Field | Type | Description | -| ---------------------------------- | --------------- | ----------------------------------------------------------------------------------- | -| `caller_functionname` | string optional | Function or method that created the span | -| `caller_filename` | string optional | File where the span was created | -| `caller_lineno` | number optional | Line number where the span was created | -| `span_origin.name` | string optional | SDK, integration, Braintrust service, or exporter that emitted or exported the span | -| `span_origin.version` | string optional | Version of the SDK, integration, Braintrust service, or exporter when known | -| `span_origin.instrumentation.name` | string optional | Stable module, package, plugin, or OTel instrumentation scope that created the span | +| Field | Type | Description | +| ---------------------------------- | --------------- | -------------------------------------------------------------------------------------------- | +| `caller_functionname` | string optional | Function or method that created the span | +| `caller_filename` | string optional | File where the span was created | +| `caller_lineno` | number optional | Line number where the span was created | +| `span_origin.name` | string optional | SDK, integration, Braintrust service, or exporter that emitted or exported the span | +| `span_origin.version` | string optional | Version of the SDK, integration, Braintrust service, or exporter when known | +| `span_origin.instrumentation.name` | string optional | Stable module, package, plugin, or OTel instrumentation scope that created the span | +| `span_origin.environment.type` | string optional | Operating environment type where the span was captured: `ci`, `server`, `local`, or `gateway` | +| `span_origin.environment.name` | string optional | Normalized operating environment name, such as `github_actions`, `vercel`, or `development` | Braintrust-created spans MUST also include span-origin provenance. Omit fields whose values are unknown. @@ -104,10 +106,14 @@ Braintrust-created spans MUST also include span-origin provenance. Omit fields w "caller_filename": "app.py", "caller_lineno": 42, "span_origin": { - "name": "braintrust.javascript", + "name": "braintrust.sdk.javascript", "version": "1.2.3", "instrumentation": { "name": "openai-auto" + }, + "environment": { + "type": "ci", + "name": "github_actions" } } } @@ -115,10 +121,26 @@ Braintrust-created spans MUST also include span-origin provenance. Omit fields w OTLP ingestion will map standard OTel code attributes into caller-location context when explicit context is not provided: `code.function.name` → `context.caller_functionname`, `code.file.path` → `context.caller_filename`, and `code.line.number` → `context.caller_lineno`. -`context.span_origin` identifies the SDK, integration, Braintrust service, or exporter that emitted or exported the span. For external OTLP spans that do not include Braintrust span-origin provenance, ingestion SHOULD fall back to OTel `telemetry.sdk.name` and `telemetry.sdk.version` resource attributes. +`context.span_origin` identifies the SDK, integration, service, or exporter that emitted or exported the span. For OTLP spans that do not include explicit Braintrust span-origin provenance, ingestion SHOULD set `context.span_origin.name` to `opentelemetry` and SHOULD fill `context.span_origin.version` from the OTel `telemetry.sdk.version` resource attribute when present. + +Braintrust-maintained SDKs, plugins, services, and exporters SHOULD use stable `span_origin.name` values under the reserved `braintrust.` prefix when they provide explicit Braintrust span-origin provenance. Braintrust SDKs SHOULD use `braintrust.sdk.` names, such as `braintrust.sdk.javascript`; Braintrust plugins SHOULD use `braintrust.plugin.` names, such as `braintrust.plugin.codex`. User code and third-party integrations SHOULD NOT use the `braintrust.` prefix for caller-provided origin names. `context.span_origin.instrumentation.name` identifies the stable module, package, plugin, or OTel instrumentation scope that directly created the span. Provider/client SDKs such as `openai` or `anthropic` are not the span origin and SHOULD continue to appear in `metadata.provider` when provider metadata is available. +`context.span_origin.environment` identifies the operating environment where the span was captured. The `type` field SHOULD be one of `ci`, `server`, `local`, or `gateway`; SDK type definitions SHOULD allow future string values. The `name` field is optional and SHOULD be a normalized lower-snake-case label. Emit this object only when the value comes from an explicit override or a reliable positive signal; do not infer `local` from the absence of CI or server signals. SDKs MUST NOT automatically classify spans as `gateway`; `gateway` is reserved for spans captured by Braintrust Gateway/internal code and MUST NOT be inferred from `metadata.provider`. + +When SDKs and Braintrust-internal emitters resolve `context.span_origin.environment`, they SHOULD apply this precedence: + +1. Caller-provided SDK option. +2. `BRAINTRUST_ENVIRONMENT_TYPE` / `BRAINTRUST_ENVIRONMENT_NAME`, resolved through process environment and `.env.braintrust` fallback. +3. Trusted Braintrust Gateway/internal configuration. +4. CI provider environment variables or generic `CI`. +5. Server/platform environment variables. +6. Language or framework deployment-mode variables. +7. Omit `environment`. + +SDKs SHOULD use provider-specific CI variables when present, such as `GITHUB_ACTIONS`, `GITLAB_CI`, `CIRCLECI`, `BUILDKITE`, `JENKINS_URL`, `JENKINS_HOME`, `TF_BUILD`, `TEAMCITY_VERSION`, `TRAVIS`, or `BITBUCKET_BUILD_NUMBER`; if only generic `CI` is present, use `type: "ci"` and `name: "ci"`. SDKs MAY identify server environments from explicit platform variables such as `VERCEL`, `NETLIFY`, `AWS_LAMBDA_FUNCTION_NAME`, Lambda `AWS_EXECUTION_ENV`, `K_SERVICE`, `FUNCTION_TARGET`, `KUBERNETES_SERVICE_HOST`, `ECS_CONTAINER_METADATA_URI`, `ECS_CONTAINER_METADATA_URI_V4`, `DYNO`, `FLY_APP_NAME`, `RAILWAY_ENVIRONMENT`, or `RENDER_SERVICE_NAME`. SDKs MAY use language/framework variables such as `NODE_ENV`, `RAILS_ENV`, `RACK_ENV`, `ASPNETCORE_ENVIRONMENT`, or `DOTNET_ENVIRONMENT` only as weaker fallback signals: production/staging values map to `server`, development/local values map to `local`, and test values SHOULD be ignored unless CI was already detected. + --- ## General Principles @@ -1025,11 +1047,17 @@ Braintrust also consumes the standard OTel code attributes for caller location: | Attribute | Type | Location | Description | | ------------------------- | ------ | -------------- | ------------------------------------------------------------------------------------------------------------------------------------ | | `braintrust.context_json` | string | Span attribute | JSON-encoded context object. Extracted into the `context` field of the log row and deep-merged with backend-derived OTel provenance. | +| `braintrust.environment.type` | string | Resource or span attr | Operating environment type where the span was captured. Extracted into `context.span_origin.environment.type`. | +| `braintrust.environment.name` | string | Resource or span attr | Normalized operating environment name. Extracted into `context.span_origin.environment.name`. | -SDKs and exporters SHOULD encode Braintrust span-origin provenance in `braintrust.context_json` as `context.span_origin`. Ingestion MUST use those explicit `context.span_origin` values when present. When explicit Braintrust span-origin provenance is not provided, ingestion SHOULD derive `context.span_origin.name` and `context.span_origin.version` from OTel `telemetry.sdk.name` and `telemetry.sdk.version` resource attributes. +SDKs and exporters SHOULD encode Braintrust span-origin provenance in `braintrust.context_json` as `context.span_origin`. Ingestion MUST use those explicit `context.span_origin` values when present. When explicit Braintrust span-origin provenance is not provided on an OTLP span, ingestion SHOULD set `context.span_origin.name` to `opentelemetry` and SHOULD derive `context.span_origin.version` from the OTel `telemetry.sdk.version` resource attribute when present. OTLP ingestion SHOULD derive `context.span_origin.instrumentation.name` from the OTLP instrumentation scope name. +OTLP ingestion SHOULD derive `context.span_origin.environment.type` and `context.span_origin.environment.name` from `braintrust.environment.type` and `braintrust.environment.name` resource attributes when present. If the SDK or instrumentation cannot set resource attributes, it MAY set the same attributes on individual spans. Explicit values in `braintrust.context_json` MUST win over backend-derived environment values. + +Braintrust-internal OTLP ingest MAY derive `context.span_origin.environment.type = "gateway"` and `context.span_origin.environment.name = "braintrust_gateway"` from trusted Braintrust Gateway resource identity, such as the gateway service resource. SDKs and general OTLP ingest MUST NOT infer this gateway classification from public span attributes, routes, model/provider metadata, or `metadata.provider`. + ### Span content | Attribute | Type | Description | diff --git a/semconv/envar.yaml b/semconv/envar.yaml index ecd1d9c..0e39382 100644 --- a/semconv/envar.yaml +++ b/semconv/envar.yaml @@ -36,3 +36,13 @@ config_envars: required: false env_braintrust: true documentation: Organization name to use when logging in. Useful when account has multiple organizations. + + - name: BRAINTRUST_ENVIRONMENT_TYPE + required: false + env_braintrust: true + documentation: Optional explicit span origin environment type. When set, SDKs use this value for context.span_origin.environment.type instead of automatic environment detection. + + - name: BRAINTRUST_ENVIRONMENT_NAME + required: false + env_braintrust: true + documentation: Optional explicit normalized span origin environment name. When set, SDKs use this value for context.span_origin.environment.name instead of automatic environment detection. diff --git a/semconv/span_attributes.yaml b/semconv/span_attributes.yaml index 492ff17..31448a3 100644 --- a/semconv/span_attributes.yaml +++ b/semconv/span_attributes.yaml @@ -5,6 +5,10 @@ span_attributes: documentation: Serialized parent reference used to attach the span to a Braintrust project, experiment, dataset, prompt session, or parent span. - name: braintrust.context_json documentation: JSON-encoded context object extracted into the Braintrust row context field. Used for caller location and span-origin provenance. + - name: braintrust.environment.type + documentation: Operating environment type where the span was captured. Intended for OTel resource attributes when possible, span attributes otherwise, and extracted into context.span_origin.environment.type. + - name: braintrust.environment.name + documentation: Normalized operating environment name where the span was captured. Intended for OTel resource attributes when possible, span attributes otherwise, and extracted into context.span_origin.environment.name. - name: braintrust.metrics documentation: JSON-encoded metric annotations extracted into the Braintrust row metrics field. diff --git a/test/IMPLEMENTATION-GUIDE.md b/test/IMPLEMENTATION-GUIDE.md index d31532f..2c8ebb1 100644 --- a/test/IMPLEMENTATION-GUIDE.md +++ b/test/IMPLEMENTATION-GUIDE.md @@ -360,6 +360,7 @@ Every expected LLM span asserts span-origin provenance in `context`: - `context.span_origin.name` and `context.span_origin.version` MUST be non-empty strings. - `context.span_origin.instrumentation.name` MUST identify the stable instrumentation module, package, plugin, or OTel instrumentation scope that created the span. +- `context.span_origin.environment` is optional. When present, `type` SHOULD identify the operating environment where the span was captured (`ci`, `server`, `local`, or `gateway`), and `name` SHOULD be a normalized lower-snake-case label. Implementations MAY include additional context fields, including caller-location fields; recursive validation ignores extra keys.