Where genai-interlingua sits

It is a Collector processor, a CLI, and a config generator for two processors that ship with the stock Collector. Those are three different answers to "where in the pipeline", and the design exists to make all three the same decision.

Three places it can run

The interesting part is the third one. -emit ottl prints a transform config and -emit schema-file prints a Telemetry Schema File, so the mapping can run inside a Collector that never heard of this binary.

A  In the path app + SDK any dialect CUSTOM COLLECTOR BUILD receiver genaiinterlingua exporter Honeycomb OTLP B  Beside the path OTLP JSON captured interlingua CLI normalized JSON + interlingua.* fixtures, replay, conformance tests C  Not in the path at all interlingua -emit ottl | schema-file config text STOCK otelcol-contrib transform | schema processor loaded by The marked boxes are this repository's code. In row C none of it runs in production.
The mapping travels even when the binary does not. Row C is why "you would have to run my fork of the Collector" is not an objection: the OTTL config runs in the Collector the customer already has.

The type model

Six dialects implement one three-method interface. A dialect scores a span before it is allowed to parse it, and the comment on Score is the design rule: detection is a claim of responsibility, not a guess.

«interface» Dialect + Name() Name + Score(Span) int + Parse(Span) Parsed implements openllmetry openinference vercel litellm braintrust raw «fallback» Span Attrs map[string]Value Name string what the emitter wrote, read-only Parsed Usage, Messages, Origin, Accounting Lossy []Loss logical fields, not keys Loss Key string Reason Reason no_field, unstructured, flattened, ambiguous Score / Parse read Parse returns 0..n
Adding a dialect is one file: it registers itself from its own init, so nothing central needs editing. raw is marked a fallback so it is only consulted when no dialect claims the span, which keeps it from eating the winner's confidence margin.

Why one decision serves three deployments

normalize.Span does not mutate anything. It returns a description of an edit, and the caller applies it to whatever representation it happens to be holding. That is the hinge the whole design turns on.

dialect.Span one span, as sent Options Target: 1.41.0 | main keep | dedupe | prune Result Dialect, Confidence Set map[string]Value Remove []string PriorLossy []string Sources map[string]string a description, not a mutation normalize.Span collector processor applies it to pdata CLI applies it to OTLP JSON emit/ottl, emit/schemafile renders it as config text Three appliers, one decision. A disagreement between them would be a bug, not a configuration difference. Remove is empty under the default (keep), so the safe mode is also the one you get by forgetting to choose.
Because the decision is data, the same mapping can be unit tested, golden tested, rendered as someone else's config, and applied in two runtimes without a second implementation drifting away from the first.

What lands on the span

AttributeWhat it records
interlingua.dialectWhich emitter claimed the span
interlingua.dialect.confidenceThe winner's margin over the runner-up. Zero means a tie, or a fallback that was the only thing left
interlingua.lossyThe keys this span is no longer a faithful carrier of
interlingua.lossy.countWritten even when zero, so "nothing was lost" is a fact you can query rather than an absence you have to trust
interlingua.replaced.<key>Where a value the emitter stated was changed
interlingua.hops, .mappingHow many normalizations this span has been through, and by what

Read in source, 17 September 2026. internal/dialect/dialect.go (interface, Detect, Scores), internal/normalize/normalize.go (Options, Result, Span), internal/emit/, processor/genaiinterlingua/ (its own Go module), cmd/interlingua/main.go.