# jouleclaw-otel-genai **Scope**: Typed reference of the OTel GenAI `gen_ai.*` semantic conventions + JouleClaw `jouleclaw.*` extensions. **Data types only**. ## Relation to `jouleclaw-otel` This crate and `jouleclaw-otel` overlap by design: | Crate | Layer | Surface | |---|---|---| | **`jouleclaw-otel-genai`** (this crate) | Typed-attribute reference | `GenAiOperation`, `GenAiSystem`, `GenAiSpanAttrs`, `JouleclawSpanAttrs`, `OperationSpan` | | **`jouleclaw-otel`** | Projection layer | `receipt_to_span(...)`, `verdict_to_span(...)`, emits `SpanData` with the same attribute names as raw strings | `jouleclaw-otel` predates this crate and is the *projection authority* — it knows how to walk every JouleClaw receipt shape and emit a span. This crate is the *namespace authority* — upstream callers (e.g. [`jouleclaw-openinference`](../jouleclaw-openinference)) who need typed safety on `gen_ai.*` keys use this crate's enums to avoid string typos. Both can coexist because their consumers are different: - A direct OTel emitter consumes `jouleclaw-otel` and never sees typed `GenAiOperation`. - An OpenInference / Arize / Phoenix superset emitter consumes this crate's types and emits its own attribute pairs. Neither crate should be deleted; the duplication risk is the attribute *names* drifting between the two. The convention: when you add a new `gen_ai.*` key, add it to this crate's enums first, then mirror it as a string in `jouleclaw-otel`. ## Honest scope This crate ships: - `GenAiOperation { Chat | TextCompletion | Embeddings | ToolCall | Code | ImageGen | Speech }` - `GenAiSystem { OpenAi | Anthropic | Google | Cohere | Mistral | Meta | Jouleclaw | Other(String) }` - `GenAiSpanAttrs` with builder + `to_otel_attrs() -> Vec<(String, Value)>` - `JouleclawSpanAttrs` (tier, joules_uj, energy_provenance, receipt.schema, receipt.content_hash, oracle.verdict, cascade.depth, cache.hit) - `OperationSpan` combining both namespaces All tests pass.