Skip to Content
ReferenceServicesElasticsearch

Elasticsearch

The Elasticsearch service lets your agent talk to a mock Elasticsearch 8.x cluster during simulations, using the same APIs and query languages it uses in production. Your agent connects with elasticsearch-py, any Elastic SDK, or plain HTTP — nothing about the client code needs to change.

Scope: real Elasticsearch is a general-purpose search and analytics engine (product search, vector search, full-text documents, geospatial, APM, SIEM, and more). The Veris mock is currently targeted at log and event-shaped data — it’s backed by an event store that infers a time field (_time, @timestamp, timestamp, …) and ingests CSV/NDJSON. If your production use case is vector search, full-text document retrieval, or another non-log workload, this service will not model it faithfully today.

Two setup steps: enable the service, then upload a log dataset.

Enable the service

.veris/veris.yaml
services: - name: elastic

Requests to *.elastic-cloud.com are routed to the mock automatically via DNS interception. For self-hosted Elastic clusters, add your own hostnames under dns_aliases:

services: - name: elastic dns_aliases: - search.internal.example.com

Provide your log data

Upload a log file via the Datasets section on your Environment page in the console. Select the elastic service, choose CSV or NDJSON as the format, and upload your file.

One dataset per service per environment. Schema is inferred from the upload — no index mappings to declare. To replace an existing dataset, delete the current one first; a second upload for the same service returns a 409.

If your agent only calls cluster or index-management endpoints (creating indices, checking health, etc.), you can skip the dataset — but any call to _search, _query, or _count needs data to return anything meaningful.

Connect your app

Point your Elasticsearch client at the mock the same way you’d point it at Elastic Cloud. Example with elasticsearch-py:

.veris/veris.yaml
services: - name: elastic agent: environment: ELASTICSEARCH_URL: https://my-cluster.es.us-east-1.elastic-cloud.com

The hostname doesn’t matter — DNS interception routes any *.elastic-cloud.com request to the mock. Use whatever URL your production code already expects.

Query languages

The service supports both of Elasticsearch’s query surfaces.

Query DSL

Standard JSON queries via POST /_search or POST /{index}/_search. Compiled deterministically (no LLM in the hot path) into SQL against the uploaded dataset.

{ "query": { "bool": { "must": [ {"term": {"status": "error"}}, {"range": {"@timestamp": {"gte": "2025-01-01T00:00:00Z"}}} ] } } }

Supported query clauses:

ClauseNotes
boolmust, should, must_not, filter
term / termsExact-match on keyword fields
match / match_phraseFull-text match against indexed fields
match_allReturns everything
rangeNumeric and time ranges. Use absolute ISO-8601 timestamps — date-math expressions like now-1h are passed through as literal strings and will not match.
existsField presence check
wildcard* and ? patterns
query_stringLucene-style query strings

Supported aggregations:

AggregationNotes
termsBucket by field value
date_histogramTime-bucketed counts
avg, sum, min, maxMetric aggregations
cardinalityDistinct-value counts

Dotted field names (http.response.status_code) are resolved against the flattened column schema automatically.

If the deterministic compiler fails on a query it doesn’t recognize, the request falls through to an LLM-backed translator as a top-level safety net. Within a compiled query, however, unknown sub-clauses (e.g. prefix, fuzzy, span_*) are logged and silently dropped rather than LLM-translated — a query that mixes supported and unsupported clauses will match more broadly than intended. Stick to the clauses in the table above.

ES|QL

Text-based pipe queries via POST /_query:

{ "query": "FROM logs-* | WHERE status == \"error\" | STATS count = COUNT(*) BY host" }

ES|QL is translated via LLM rather than deterministic compilation, so expect a little more latency than Query DSL for equivalent queries.

Endpoints

Beyond search, the mock implements the endpoints most SDKs need during startup, ingest, and teardown:

EndpointPurpose
GET /Cluster info — version, cluster name, build metadata
GET /_cluster/healthCluster health (always reports green)
GET /_nodesSingle-node cluster description
GET /_resolve/index/{name}Resolve an index pattern to concrete indices
HEAD /{index}Check index existence
PUT /{index}Create index (no-op, always acknowledged)
DELETE /{index}Delete index (no-op, always acknowledged)
POST /{index}/_docIndex a single document
PUT /{index}/_doc/{id}Index a document with a known id
POST /_bulkBulk ingest. index and create write to the store; update and delete are acknowledged no-ops.
POST /_search, POST /{index}/_search, GET /{index}/_searchQuery DSL search
POST /_queryES
POST /_count, POST /{index}/_countCount documents

The cluster reports itself as Elasticsearch 8.15.0 with a single data/ingest/master node, which is enough to satisfy most client libraries’ handshakes.

Troubleshooting

Searches return zero hits. Check that a dataset is uploaded for the elastic service on this environment and that the fields your query references actually exist in the uploaded file. The service infers schema from the upload, so a typo’d field name silently matches nothing.

ES|QL queries are slow or flaky. ES|QL uses an LLM translator. For hot-path queries, prefer Query DSL, which compiles deterministically.

Unsupported Query DSL clause. Top-level unsupported shapes fall back to the LLM translator, but unknown sub-clauses inside an otherwise supported query are dropped silently — the query still runs, just without that filter. If you’re getting more hits than you expect, check that every clause you’re using is in the supported table above.

Date-math expressions match nothing. range filters using now-1h / now-1d style expressions are not translated to real timestamps — they’re treated as literal strings and compared lexicographically against your time column, which effectively matches nothing. Use absolute ISO-8601 timestamps instead.