# OTEL trace smoke

```yaml qa-scenario
id: otel-trace-smoke
title: OTEL trace smoke
surface: telemetry
coverage:
  primary:
    - telemetry.otel
  secondary:
    - harness.qa-lab
objective: Verify a QA-lab gateway run emits bounded OpenTelemetry traces, metrics, and logs through the diagnostics-otel plugin.
successCriteria:
  - The diagnostics-otel plugin starts with trace, metric, and log export enabled.
  - A minimal QA-channel agent turn completes.
  - The trace includes the selected agent harness lifecycle span.
  - The run emits low-cardinality OpenTelemetry signals without content or raw diagnostic identifiers.
plugins:
  - diagnostics-otel
gatewayConfigPatch:
  logging:
    file: .artifacts/qa-e2e/otel-smoke-gateway.jsonl
    level: info
  diagnostics:
    enabled: true
    otel:
      enabled: true
      protocol: http/protobuf
      traces: true
      metrics: true
      logs: true
      sampleRate: 1
      flushIntervalMs: 1000
      captureContent:
        enabled: false
docsRefs:
  - docs/gateway/opentelemetry.md
  - docs/concepts/qa-e2e-automation.md
codeRefs:
  - extensions/diagnostics-otel/src/service.ts
  - src/agents/harness/lifecycle.ts
  - extensions/qa-lab/src/suite.ts
execution:
  kind: flow
  summary: Emit minimal QA-lab telemetry with diagnostics-otel enabled.
  config:
    prompt: "OTEL QA marker: reply exactly `OTEL-QA-OK`. Do not repeat OTEL-QA-SECRET."
    expectedReply: OTEL-QA-OK
```

```yaml qa-flow
steps:
  - name: emits a traced qa-channel turn
    actions:
      - call: waitForGatewayHealthy
        args:
          - ref: env
          - 60000
      - call: waitForQaChannelReady
        args:
          - ref: env
          - 60000
      - call: reset
      - set: startCursor
        value:
          expr: state.getSnapshot().messages.length
      - call: runAgentPrompt
        args:
          - ref: env
          - sessionKey: agent:qa:otel-trace-smoke
            message:
              expr: config.prompt
            timeoutMs:
              expr: liveTurnTimeoutMs(env, 30000)
      - call: waitForCondition
        saveAs: outbound
        args:
          - lambda:
              expr: "state.getSnapshot().messages.slice(startCursor).filter((candidate) => candidate.direction === 'outbound' && candidate.conversation.id === 'qa-operator' && String(candidate.text ?? '').trim().length > 0).at(-1)"
          - expr: liveTurnTimeoutMs(env, 30000)
          - expr: "env.providerMode === 'mock-openai' ? 100 : 250"
      - assert:
          expr: "String(outbound.text ?? '').trim().length > 0"
          message: "expected non-empty qa output"
      - assert:
          expr: "String(outbound.text ?? '').includes(config.expectedReply)"
          message: "expected qa output to include the response sentinel"
```
