Skip to main content

Parity

What this service promises against Elastic's own output, what it deliberately leaves alone, and how the claim is measured. The code map it sits beside is architecture.md, and the mechanics of the measurement are compat.md and compat-policy.md.

Goal: Reliable 1:1 (or better) parity with Elastic ingest pipeline output for all supported Beats and Elastic Agent integrations.

This is NOT reverse engineering Elasticsearch. We are building an independent transform service that produces the same normalised ECS output as the Elastic ingest pipeline would. Customers sending Beats/Agent data through DFE should get identical field mapping, type coercion, and enrichment as if the data went directly to Elasticsearch.

"Or better" means: Where Elastic pipelines have known limitations (regex-only parsing, single-threaded Painless execution, per-document GeoIP lookups), we can exceed their performance while maintaining output compatibility.


What we replicate​

The Elastic ingest pipeline is the primary transform layer. Data arrives from Beats/Agent as JSON events, and the ingest pipeline applies processors sequentially to normalise, enrich, and route the data.

Elastic Componentdfe-transform-elastic EquivalentStatus
Ingest processors (27 used)Rust processor implementations, one per Elastic processor typeDone
Painless scriptsA hand-transcribed runner from crates/dfe-painless/src/bespoke/ where one is registered for that script, otherwise pattern-matched against params.rs and the ladder in common.rs; unrecognised scripts are skippedsee Painless coverage -- the figure is a corpus-run output, not a constant, so re-derive it rather than quoting one from here
Foreach processorPer-event loop inside the transform functionOkta, O365
Pipeline chainingA nested pipeline is INLINED into the calling module between // Begin nested pipeline / // End nested pipeline markers, so there is no call and no dispatch on the hot pathDone. rg -c "Begin nested pipeline" crates/dfe-transforms/src/ counts today's call sites
GeoIP enrichmentGlobal MMDB enricher, auto-detected at startup, LRU-cachedDone (DB-IP Lite)
User Agent parsingRegex-based parserDone (minor diffs from Elastic UA parser)
Community IDHash-based network flow IDDone
Conditional evaluationNative Rust if/match expressions per condition patternNearly. A condition the transpiler cannot read emits if false, making the processor it guards dead code -- skipped_processors in tests/ratchets.json is the live count, with a per-source table beside it
On-failure handlersWrapped around EVERY processor by the generator, alongside if and ignore_failure, so a new processor cannot quietly omit them. A raised error runs the handlers and records _ingest.on_failure_message and _ingest.on_failure_processor_typeDone

What we do not replicate​

Elastic ComponentWhy NotOur Approach
Beat/Agent collectionWe receive from Kafka, not from endpointsUpstream responsibility
Transport parsing (syslog, CEF)Done by Beat/Agent, or by dfe-receiver for the syslog envelopeUpstream responsibility
Agent-side processors (add_host_metadata, add_cloud_metadata)Run on source machineFields arrive pre-populated in event
@custom pipelinesUser-specific, not part of integrationNot applicable
final_pipelineElasticsearch-specific routingNot applicable
enrich processorRequires Elasticsearch enrich indexAlternative enrichment via dfe-loader
inference processorML model executionNot applicable for DFE
reroute processorElasticsearch index routingKafka topic routing instead

The Beat or Agent collects raw data, adds host/cloud metadata, applies its own local processors and JSON-encodes the result before it ever reaches Kafka (see System context for the full flow). Everything from there is this service's job: JSON parse, ingest pipeline logic, enrichment, ECS normalisation.


How parity is verified​

Parity is measured ONLY against the compat corpus. crates/dfe-transforms/tests/compat_corpus.rs compares per FIELD against output captured from a real Elasticsearch and ratchets tests/compat-baseline.json, which holds every per-source score. compat.md and compat-policy.md are the full account: how the corpus is generated, what tests/compare-policy.yaml excludes and why, and how the ratchet refuses to be lowered.

The .log / -expected.json fixtures copied from Elastic predate the current pipelines and disagree with what Elasticsearch emits now, so their parity assertions are retired. They are Elastic-licensed and kept outside this repository, where a CI job runs integration/ and untested_sources.rs over them as panic, error and enrichment floors. On a fresh clone of this repository, where neither they nor the corpus exist, the floors that hold are unencumbered.rs and the public cases in untested_sources.rs, over the licence-clean samples in tests/fixtures/unencumbered/.

Per-processor runtime status and code pattern: see Processor taxonomy.