This is the canonical reference for the three signal-specific Rust data-plane
binaries shipped from the servers workspace. The binaries own HTTP parsing,
bounded queues, language evaluation, SQLite connections, and process
lifecycle. They do not implement a second storage engine: every durable write,
query, and maintenance operation uses the public surfaces of
libtimeless_ext documented in the
SQLite extension API reference.
The servers are independently usable. Phoenix is a control plane that may supervise them and issue policy files and tokens, but no request requires BEAM, Elixir, a NIF, or a fallback process. PromQL, MetricsQL, and LogsQL support is defined by the query feature maps. TraceQL is not implemented.
| Binary | Default TCP listener | Storage table | Required extension batch capability |
|---|---|---|---|
timeless-metrics-api |
127.0.0.1:19439 |
metric_samples for a fresh database; legacy public metrics vtab remains readable |
named-v0 |
timeless-logs-api |
127.0.0.1:19429 |
logs with epoch-microsecond timestamps |
rich-v1 |
timeless-traces-api |
127.0.0.1:19449 |
traces |
rich-span-v2 |
All three use the same positional command line:
timeless-<signal>-api <extension-path> <database> [listen-address]
The extension and database paths are required. The listener is the optional
third positional argument. An extra or malformed argument exits with status
2. --version, supplied by itself, prints a JSON object containing the binary
name, Cargo version, source commit, target triple, and build profile.
Use libtimeless_ext.so on Linux or libtimeless_ext.dylib on macOS. The
launch path may omit the suffix: SQLite resolves libtimeless_ext on either
platform. Source-build examples are in the
metrics,
logs, and
traces READMEs.
TCP is the implemented transport. Loopback is the safe default. An operator
who has separately secured a container or host network may set
TIMELESS_ALLOW_NON_LOOPBACK=1 and pass a non-loopback address. There is no
Unix-socket listener in this release.
Authentication is opt-in at every level, and the library and binaries agree:
Config::default() and an unconfigured binary both start open. Set
TIMELESS_AUTH_MODE=required with TIMELESS_AUTH_POLICY_FILE to enable
token verification; a bad policy still fails closed before the listener
binds.
The following marked inventory is checked against every production Axum
.route(...) registration, including its exact method set.
The "Required scope" column applies only when auth is enabled
(TIMELESS_AUTH_MODE=required); an open server enforces no scopes.
| Signal | Methods | Path | Required scope | Contract |
|---|---|---|---|---|
metrics |
GET |
/live |
none | Process liveness only; does not touch SQLite. |
metrics |
GET |
/ready |
metrics:stats |
Constant-time process readiness plus build identity; does not scan storage. |
metrics |
GET |
/health |
metrics:stats |
Readiness plus storage, queue, rollup, and file accounting. |
metrics |
GET |
/metrics |
none | Prometheus text exposition of the plane's own operational stats plus timeless_build_info; unauthenticated like the probe endpoints. |
metrics |
GET |
/select/metrics/stats |
metrics:stats |
Complete serialized StorageStats. |
metrics |
POST |
/api/v1/flush |
metrics:maintenance |
Ordered writer completion, extension flush, and durability barrier. |
metrics |
POST |
/api/v1/backup |
metrics:maintenance |
Flush, compact/roll up, checkpoint, and verified SQLite backup. |
metrics |
POST |
/api/v1/import |
metrics:write |
VictoriaMetrics JSON-line import. |
metrics |
POST |
/api/v1/import/prometheus |
metrics:write |
Prometheus text exposition import through the public extension parser. |
metrics |
GET, PUT |
/api/v1/scrape/targets |
metrics:read for GET; metrics:write for PUT |
Read or atomically replace the process-local scrape target set. |
metrics |
GET, POST |
/api/v1/query |
metrics:read |
Native exact-latest query with metric=, or PromQL instant query with query=. |
metrics |
GET |
/api/v1/export |
metrics:read |
VictoriaMetrics JSON-line raw export by metric= or match[]. |
metrics |
GET, POST |
/api/v1/query_range |
metrics:read |
Native range query with metric=, or PromQL range query with query=. |
metrics |
GET |
/api/v1/labels |
metrics:read |
Native label-name discovery. |
metrics |
GET |
/api/v1/label/{name}/values |
metrics:read |
Native label-value discovery. |
metrics |
GET |
/api/v1/series |
metrics:read |
Native series discovery; optional inclusive start/end. |
metrics |
GET |
/prometheus/api/v1/labels |
metrics:read |
Prometheus-compatible label-name alias. |
metrics |
GET |
/prometheus/api/v1/label/{name}/values |
metrics:read |
Prometheus-compatible label-value alias. |
metrics |
GET |
/prometheus/api/v1/series |
metrics:read |
Prometheus-compatible series discovery; requires match[]; optional inclusive start/end. |
metrics |
GET, POST |
/prometheus/api/v1/query |
metrics:read |
Stable PromQL instant endpoint. |
metrics |
GET, POST |
/prometheus/api/v1/query_range |
metrics:read |
Stable PromQL range endpoint. |
metrics |
GET, POST |
/metricsql/api/v1/query |
metrics:read |
Explicit MetricsQL instant compatibility tier. |
metrics |
GET, POST |
/metricsql/api/v1/query_range |
metrics:read |
Explicit MetricsQL range compatibility tier. |
logs |
GET |
/live |
none | Process liveness only; does not touch SQLite. |
logs |
GET |
/ready |
logs:stats |
Constant-time process readiness plus build identity; does not scan storage. |
logs |
GET |
/health |
logs:stats |
Readiness plus storage and queue accounting. |
logs |
GET |
/metrics |
none | Prometheus text exposition of the plane's own operational stats plus timeless_build_info; unauthenticated like the probe endpoints. |
logs |
POST |
/insert/jsonline |
logs:write |
NDJSON ingestion into one public rich-log batch per request. |
logs |
GET, POST |
/select/logsql/query |
logs:read |
LogsQL compatibility grammar by GET (query=) or POST; native parameter query on GET otherwise. |
logs |
GET |
/select/logsql/field_values |
logs:read |
Bounded discovery for service, host, path, or status. |
logs |
GET |
/select/logsql/stats |
logs:stats |
Complete serialized StorageStats. |
logs |
GET, POST |
/select/logsql/tail |
logs:read |
Live tail: streams admitted entries matching one LogsQL filter expression as NDJSON; pipelines are rejected; slow consumers drop (counted in stats). |
logs |
POST |
/api/v1/flush |
logs:maintenance |
Ordered writer completion and extension durability barrier. |
logs |
POST |
/api/v1/backup |
logs:maintenance |
Flush, optimize, checkpoint, and verified SQLite backup. |
traces |
GET |
/live |
none | Process liveness only; does not touch SQLite. |
traces |
GET |
/ready |
traces:stats |
Constant-time process readiness plus build identity; does not scan storage. |
traces |
GET |
/health |
traces:stats |
Readiness plus negotiated rich-span capability, storage, and queue watermarks. |
traces |
GET |
/metrics |
none | Prometheus text exposition of the plane's own operational stats plus timeless_build_info; unauthenticated like the probe endpoints. |
traces |
GET |
/select/traces/stats |
traces:stats |
Complete serialized StorageStats. |
traces |
GET |
/select/jaeger/api/services |
traces:read |
Sorted Jaeger service discovery. |
traces |
GET |
/select/jaeger/api/services/{service}/operations |
traces:read |
Sorted operations for one service. |
traces |
GET |
/select/jaeger/api/traces |
traces:read |
Jaeger trace search. |
traces |
GET |
/select/jaeger/api/traces/{trace_id} |
traces:read |
One Jaeger trace with all matching spans. |
traces |
GET |
/select/timeless/api/spans |
traces:read |
Native rich-span search for the Timeless dashboard contract. |
traces |
GET, POST |
/select/timeless/api/spans/tail |
traces:read |
Live tail: streams admitted spans as NDJSON, filtered by the search surface's live-matchable parameters (service, name, kind, status) plus an attributes JSON object of exact scalar matches; invalid values are rejected; slow consumers drop spans (counted in stats). |
traces |
GET |
/select/timeless/api/traces/{trace_id} |
traces:read |
One native trace with complete rich-span fields. |
traces |
POST |
/api/v1/flush |
traces:maintenance |
Ordered writer completion, extension flush, and durability barrier. |
traces |
POST |
/api/v1/backup |
traces:maintenance |
Flush, optimize, checkpoint, and verified SQLite backup. |
A backup occupies the signal's single writer for its whole duration — flush,
the entire optimize backlog, a WAL checkpoint, then the copy — so queued
ingest waits behind it. At most one runs per process at a time: an
overlapping POST /api/v1/backup is answered 409 Conflict with
{"error":"conflict","reason":"backup_in_progress"} rather than
queued, so repeating the call costs the writer nothing. Retry after the
in-flight backup completes.
| traces | POST | /insert/opentelemetry/v1/traces | traces:write | OTLP JSON or protobuf ingestion, optionally gzip-compressed. |
Both series-discovery routes accept optional inclusive start and end in
Unix seconds (including fractions) or RFC 3339. For example,
/api/v1/series?metric=cpu&start=1790000000&end=1790000030 returns series with
at least one raw sample in that window. A missing endpoint is unbounded;
omitting both preserves the existing catalog response. Invalid timestamps and
reversed windows return a query-validation error. Storage has whole-second
timestamps, so fractional bounds include only stored seconds inside the range.
Catalog extrema prove activity without payload decoding whenever an endpoint
sample lies inside the window. Historical windows strictly between the first
and last sample require exact, series-id-constrained timeless_latest probes
to exclude gaps. Those probes conservatively reserve each ambiguous series'
full catalog point count against the request's shared
TIMELESS_METRICS_PROMQL_MAX_STORAGE_POINTS; exhaustion returns an explicit
limit error. Ordinary recent-window discovery does not use that decode budget
or charge sample counts against MAX_WORK_POINTS. Catalog, selector, result
and response limits still apply. The probes use the existing bounded public
SQL capability, so this server change requires no new storage format.
An unregistered route always returns HTTP 422 with
{"error":"unsupported_capability","reason":"unsupported_route"}. The
servers never forward it to Rocket, Phoenix, another process, or another
storage owner.
The three serialized StorageStats responses obtain extension-owned tier,
block, and optimizer-source accounting only from the public
timeless_stats TVF. Servers never query implementation-owned shadow tables.
Whole-database page/freelist and file/WAL/SHM sizes still come from public
SQLite PRAGMAs and filesystem metadata. Exact per-index allocation would
require a complete dbstat walk, so index_bytes is unavailable at the SQL
boundary and the server's sqlite_index_bytes compatibility field is zero;
all logical counts remain available from durable counters.
Trace responses additionally pass through the cumulative public
attribute_index_fields, attribute_bloom_rows, and
attribute_bloom_bytes storage values, plus the cumulative public
query_decoded_columns, query_decoded_column_bytes,
query_materialized_values, and query_materialized_rich_values values as
extension_query_* fields; they remain aggregate diagnostics rather than
request-local attribution when readers overlap.
Every plane's /metrics exposition publishes the honest storage split as
separate families: timeless_<plane>_storage_bytes (the engine's
bytes_on_disk — data-block payload only), timeless_<plane>_index_bytes,
timeless_<plane>_wal_bytes, timeless_<plane>_freelist_bytes, and
timeless_<plane>_database_file_bytes. A compression ratio is
raw-vs-storage; index, WAL, freelist, and whole-file bytes are operational
series and are never part of one.
Payload bytes and logical storage counts are maintained transactionally in
table metadata for all three signals, so routine health and scrape requests do
not aggregate complete block, chunk, or posting-list tables. Exact per-index
bytes would require a complete dbstat walk and are therefore reported as
unavailable by the extension (the server compatibility gauges are 0);
page, freelist, WAL, and database-file gauges remain available.
The raw side per plane: timeless_metrics_raw_ingested_bytes is
16 × total_points (8-byte timestamp + 8-byte value per sample — the
standard raw comparator; series identity is the amortized catalog), and is
lifetime-accurate because it derives from durable point counts.
timeless_logs_raw_ingested_bytes_total and
timeless_traces_raw_ingested_bytes_total surface the extension's
persisted ingest_raw_bytes_total counter: logical row bytes (log
timestamp + level + message + metadata; span ids + kind/status + timings +
string fields) counted once when rows become durable, monotonic under
optimize and prune, restart-safe. Rows still buffered in memory are not
yet counted, so the ratio briefly understates after bursts and
self-corrects on flush; pre-upgrade databases start the counter at their
next flush. The paired
timeless_logs|traces_compression_input|output_bytes_total counters are
exported alongside. The same values appear in the serialized StorageStats
responses; each plane README documents its exact series list.
Every StorageStats response carries an otel_traces object and every
/health response carries otel_traces_state, so each server's optional
maintenance-span exporter (metrics compaction; logs and traces optimize) is
observable without exporting telemetry about itself. state is
one of disabled (no endpoint), starting (enabled, nothing attempted or
dropped yet), healthy (last export succeeded, nothing dropped since),
dropping (exports succeed but the bounded queue overflowed since the last
success), or failing (the last export failed or timed out). The object also
reports the configured sample_ratio, queue_capacity_spans, batch_spans,
export_delay_ms, and export_timeout_ms; the live queued_spans and
queue_high_water_spans; cumulative enqueued_spans, dropped_spans,
dropped_since_last_success, export_attempts, export_successes,
export_failures, exported_spans, and failed_spans; and
last_success_unix_ms, last_failure_unix_ms, and a bounded last_error
text (retained after a later success so its timestamp dates it). The
Prometheus exposition renders the state as
timeless_<signal>_otel_traces_state{state="..."} plus the
timeless_<signal>_otel_traces_queued_spans, ..._queue_capacity_spans,
..._enqueued_spans_total, ..._dropped_spans_total,
..._export_successes_total, ..._export_failures_total, and
..._exported_spans_total series, where <signal> is metrics, logs, or
traces.
Span vocabulary shared by the three servers: the span is
timeless.<signal>.<operation> (compaction for metrics, optimize for logs
and traces) with timeless.signal, timeless.maintenance.operation,
timeless.maintenance.slow_steps, and timeless.maintenance.result
(ok/error, plus error.message), and one <span>.slow_step event per
committed step of at least 50 ms carrying timeless.maintenance.step,
.step.elapsed_ns, and .step.continues. Logs and traces sweeps add
timeless.optimize.* attributes: skipped, elapsed_ns, budget_entries,
budget_limited, the backlog.* counts seen before the pass, the
remaining.* counts after it, blocks_removed, blocks_written, and the
raw.* / merge.* group, block, entry, byte, and elapsed-time deltas.
With ..._REQUEST_SAMPLE_RATIO set, each server records a Server span
named <METHOD> <route template> (for example GET /api/v1/query_range, or
GET unmatched for a 404 on no route) for every request that fails with a
5xx, takes at least the slow threshold, or carries a sampled W3C
traceparent, plus the configured fraction of the rest. The incoming
traceparent becomes the span's parent, so an upstream trace continues
through the API; an unsampled parent (-00) suppresses the span unless the
request errs or is slow. The attribute set is fixed and low-cardinality:
timeless.signal, http.request.method, http.route (the template, never
the raw path or query string), http.response.status_code,
timeless.request.duration_ns, timeless.request.sampled_reason
(error, slow, parent, or ratio), and timeless.response.result_rows
when the response carried x-timeless-result-rows. No request or response
headers, bodies, credentials, identities, client addresses, metric names,
label values, log text, or span attributes are ever attached. The layer
wraps the auth layer, so rejected requests are visible by status. /live,
/ready, /health, /metrics, and the OTLP ingest route
/insert/opentelemetry/v1/traces are never traced, so a traces server can
receive its own or another server's spans without recursion. The decision
path for an unsampled request adds about 3 µs on a debug-build loopback
microbenchmark; per-request storage contention (admission, queue, and
writer-gate waits) is not yet attached to these spans and remains available
only as the aggregate api_*_wait_ns and retry counters in StorageStats.
GET and POST query endpoints consume URL-encoded parameters. On POST, form body values follow URL values and therefore win for a duplicated singleton parameter. Unknown Prometheus/MetricsQL parameters fail explicitly.
- Native latest requires
metric; other non-reserved parameters are exact label matchers. - Native export requires
metric;start/fromandend/toare epoch seconds and default to the previous hour and now. - Native range requires
metric; accepts the same bounds, a positive whole-secondstep(default 60), andaggregate=avg|min|max|sum|count|last|first|rate. Label values beginning with=~are regular expressions. Unsupported dashboard composition parameters fail rather than being ignored. - PromQL instant accepts
query, optionaltime, and optionallookback_delta. Range acceptsquery,start,end,step, and optionallookback_delta. - MetricsQL instant additionally accepts
stepandmax_lookback; range additionally acceptsmax_lookback. - Discovery accepts repeated
match[]ormatch. Native label values and series may also usemetric; the Prometheus series alias requires at least onematch[].
The exact expression compatibility and response behavior are in the
PromQL matrix and
query cookbook.
Prometheus-family success and error envelopes follow the documented
status, data, errorType, error, warnings, and infos contract.
POST /api/v1/import accepts VictoriaMetrics JSON lines. Valid rows from a
partially malformed request are admitted and parse failures are counted.
POST /api/v1/import/prometheus accepts Prometheus text exposition and gives
the complete body directly to the extension's public parser. A successful
204 means bounded writer-queue admission, not an implicit extension flush.
Scrape target replacement accepts:
{
"version": 1,
"targets": [{
"id": 1,
"job_name": "self",
"scheme": "http",
"address": "127.0.0.1:4000",
"metrics_path": "/metrics",
"scrape_interval_secs": 15,
"scrape_timeout_secs": 5,
"labels": {"instance": "local"},
"auth": null,
"enabled": true
}]
}The set is process-local control-plane state and must be supplied again after restart. Versions cannot move backward. A set has at most 10,000 targets; IDs are unique and positive; job and address are nonempty; intervals are 1–86,400 seconds; timeouts are 1–300 seconds and no longer than the interval; label names contain only ASCII letters, digits, and underscore. HTTP and HTTPS are supported. Each response is capped at 10 MiB. Bearer auth takes precedence over optional basic-auth username/password. Configured labels do not overwrite labels already present in a sample.
POST /insert/jsonline accepts one JSON object per line. _msg_field and
_time_field query parameters select alternate source keys (defaults _msg
and _time). New server tables use epoch microseconds by default;
TIMELESS_LOGS_TIMESTAMP_UNIT=ms supports existing millisecond SQL tables.
The setting must match the persisted unit and never converts stored timestamps.
Every other retained
field remains typed/nested metadata, and all eight severities remain exact.
A fully valid request returns 204; a partially valid request returns 200 with
accepted/error counts. That response means admission to the bounded writer
queue, not durability; call the flush route for the ordered barrier.
A GET /select/logsql/query that carries query is LogsQL, exactly as the
POST form below. Any other native GET /select/logsql/query rejects unknown
parameters and accepts
level, message, service, host, path, status, start, end,
limit, offset, and order. order is asc or desc; bounds accept the
documented native time forms. An invalid/overflowing bound or unsupported
order returns HTTP 400 with invalid_query_parameter; it cannot silently
widen the query. Field discovery requires
field=service|host|path|status, accepts the same filters except offset/order,
and defaults to 1,000 values, clamped to the deployment's
TIMELESS_LOGS_LOGSQL_MAX_RESULT_ROWS. An explicit value above that ceiling is
rejected rather than silently clamped.
POST /select/logsql/query uses an URL-encoded form with required query and
optional allow_partial_response, start, end, and limit, as VictoriaLogs
documents them. start and end bound the outer selection and intersect any
_time filter in the query; they take unix timestamps, RFC 3339, now, or a
duration such as 15m meaning that long before now, and cannot be combined
with a query time_offset. A positive limit caps the result as a final
| limit would (0 is no limit). allow_partial_response=false is the
complete fail-closed mode; true fails explicitly because one authoritative
SQLite owner cannot produce an honest distributed partial result. Other form
fields, including _time and time, fail with
unsupported_query_parameters. Query syntax errors return
invalid_query, bounded work exhaustion returns query_limit, retryable
storage contention returns HTTP 503 plus Retry-After, and an unexpected
executor fault returns internal with reason query_execution while retaining
its sensitive detail only in the server log. The complete shipped grammar,
strict errors, rich values, ordering, and intentional VictoriaLogs differences
are in the LogsQL matrix and
query cookbook.
POST /insert/opentelemetry/v1/traces accepts OTLP JSON by default and OTLP
protobuf for Content-Type: application/x-protobuf. Both encodings support
Content-Encoding: gzip (or uncompressed identity); other content encodings
return HTTP 415. The handler validates the complete request,
encodes one public rich-span-v2 batch, and waits for its SQLite insertion
statement before returning a full-success export response: {} for JSON or
an empty protobuf message, with the matching response content type. Empty
exports succeed. Errors use google.rpc.Status in the same encoding, including
pre-handler authentication and body-limit failures. That statement may leave
spans in the extension's authoritative 8,192-span buffer; call flush when a
durability barrier is required. There is no server-owned span buffer.
Jaeger search accepts service, operation, microsecond start and end,
non-negative limit (default 100), and minDuration/maxDuration duration
strings. Its compatibility limit counts selected spans before trace grouping,
so a limited trace may be incomplete. Unknown parameters return explicit 422.
Native dashboard span search accepts name, service,
kind=internal|server|client|producer|consumer,
status=unset|ok|error, nanosecond since/until, limit from 1 through
100 (default 100), non-negative offset, and order=asc|desc (default desc).
It returns the stored status description, typed attributes, events, resource,
and instrumentation scope rather than blanking fields for a lower-fidelity
wire format.
The marked inventory below is checked against production source before its test modules. Unless a row says otherwise, numeric values are positive base-10 integers and an invalid value stops startup with status 2.
| Variable | Applies to | Default | Meaning and constraints |
|---|---|---|---|
TIMELESS_ALLOW_NON_LOOPBACK |
all | unset | Only 1, true, or TRUE permits an explicitly supplied non-loopback TCP listener. |
TIMELESS_AUTH_MODE |
all | disabled |
Set required to enable token verification; auth is opt-in. |
TIMELESS_AUTH_POLICY_FILE |
all | none | Readable policy-v1 JSON path; required when TIMELESS_AUTH_MODE=required. |
TIMELESS_TENANT |
all | default |
Exact tenant required in both policy and token. |
TIMELESS_BACKUP_DIR |
all | backups/ beside the database file |
Directory backups are confined to; destinations that canonicalize outside it are rejected. Relative destinations resolve inside it. |
TIMELESS_ADMIN_KEY |
all | unset | When set, /api/v1/scrape/targets, /api/v1/backup, /api/v1/flush, and /api/v1/optimize additionally require it (header x-timeless-admin-key), independent of TIMELESS_AUTH_MODE. Unset means open. Header-only by design: a query-string key leaks into access/proxy logs and browser history. Keys must be visible ASCII (an HTTP header-value constraint). |
TIMELESS_METRICS_READER_CONNECTIONS |
metrics | 2 |
Independent bounded SQLite readers. |
TIMELESS_METRICS_COMMAND_QUEUE_BATCHES |
metrics | 256 |
Writer-command queue capacity in admitted request batches. |
TIMELESS_METRICS_QUEUE_BYTES |
metrics | 134217728 |
Queued-payload admission gate in bytes; admissions wait while in-flight ingest bytes exceed it (a batch larger than the gate is admitted alone). |
TIMELESS_METRICS_FLUSH_INTERVAL_SECS |
metrics | 10 |
Cadence of the ordered flush command. A metrics write is stored when its batch commits, so this now only applies retention on that cadence. |
TIMELESS_METRICS_COMPACT_INTERVAL_SECS |
metrics | 300 |
Public compact/rollup sweep cadence. Each sweep is planned for the chunks there when it began, a stretch of series at a time as it goes, and stepped through in resumable transactions capped at 64 metrics series, 262,144 source points, 4 MiB of encoded metrics input, and 64 rollup groups, with a 10 ms pause between commits in which queued writes are taken. Reads do not wait for a step and a step does not wait for a read. What is written while a sweep runs waits for the next. |
TIMELESS_METRICS_OTEL_TRACES_ENDPOINT |
metrics | unset | Full OTLP/HTTP traces endpoint, for example http://127.0.0.1:19449/insert/opentelemetry/v1/traces or an https:// collector. Unset disables export and no exporter, queue, or span cost exists. The scope is one scheduled compact/rollup sweep span with raw/merge step, point, byte, and elapsed-time totals plus events for steps taking at least 50 ms; it does not trace requests, samples, or other services. Credentials in the URL are rejected; use ..._HEADERS for authentication. |
TIMELESS_METRICS_OTEL_TRACES_HEADERS |
metrics | unset | Extra OTLP request headers as comma-separated name=value entries (the OTEL_EXPORTER_OTLP_HEADERS convention; values may be percent-encoded). Values are secrets: they are never logged, and validation errors name only the header. content-type, content-length, host, and transfer-encoding are set by the exporter and rejected here. Requires the endpoint. |
TIMELESS_METRICS_OTEL_TRACES_HEADERS_FILE |
metrics | unset | Path to a file with the same name=value entries, one per line or comma-separated, for deployments that mount secrets as files. Mutually exclusive with ..._HEADERS. |
TIMELESS_METRICS_OTEL_TRACES_CA_CERT |
metrics | unset | PEM file holding the CA certificate(s) that sign an https:// collector's certificate; it is read and parsed at startup. Unset uses the built-in WebPKI roots. Rejected with an http:// endpoint. |
TIMELESS_METRICS_OTEL_TRACES_SAMPLE_RATIO |
metrics | 1.0 |
Fraction of sweeps traced, 0.0–1.0. Sampling is decided when the sweep starts, so an unsampled sweep records no attributes and touches no queue. |
TIMELESS_METRICS_OTEL_TRACES_QUEUE_SPANS |
metrics | 256 |
Capacity of the drop-on-full export queue, 1–65,536. A full queue drops the newest span and counts it; ending a span never blocks maintenance. |
TIMELESS_METRICS_OTEL_TRACES_BATCH_SPANS |
metrics | 64 |
Maximum spans per export request, 1–queue size. |
TIMELESS_METRICS_OTEL_TRACES_EXPORT_DELAY_MS |
metrics | 1000 |
Longest a queued span waits before an export attempt, 1–60,000 ms. |
TIMELESS_METRICS_OTEL_TRACES_EXPORT_TIMEOUT_MS |
metrics | 2000 |
Per-request HTTP connect and response timeout, 1–60,000 ms. Shutdown flushes the queue for at most twice this budget, then discards and counts what is left. |
TIMELESS_METRICS_OTEL_TRACES_REQUEST_SAMPLE_RATIO |
metrics | unset | Unset leaves HTTP request tracing off even when the endpoint is set. A value in 0.0–1.0 turns it on: every request that fails with a 5xx, takes at least ..._REQUEST_SLOW_MS, or carries a sampled W3C traceparent is recorded, plus this fraction of the rest (0.0 means errors, slow requests, and sampled parents only). Spans share the exporter queue and health. |
TIMELESS_METRICS_OTEL_TRACES_REQUEST_SLOW_MS |
metrics | 1000 |
A request at least this slow is always recorded when request tracing is on, 1–60,000 ms. |
TIMELESS_METRICS_INDEX_CACHE |
metrics | unset | The metrics table's index_cache budget: unbounded, or a size such as 64MB or 0 (see index_cache in the SQL reference). It bounds the cache of series identities and remembered selections; the catalog and chunk index are read from the database whatever it says. Unset keeps an existing database's setting. A changed setting is applied during startup. The first start of 0.9.0 or later on a database that an earlier version held its indexes in memory for builds them (about four seconds for 700,000 series and 7.9 million chunks). |
TIMELESS_METRICS_RETENTION_INTERVAL_SECS |
metrics | 3600 |
Cadence of the optional additional wall-clock raw-retention prune. Inert when TIMELESS_METRICS_RAW_RETENTION_SECS=0. |
TIMELESS_METRICS_RAW_RETENTION_SECS |
metrics | 0 |
Additional wall-clock expiry for raw metrics, in seconds (0 disables it; maximum i64::MAX). Positive values explicitly prune raw chunks older than now minus this window. Unset/zero preserves the table's declared data-time retention without adding wall-clock expiry; a fresh table without retention keeps its data. This setting cannot lengthen or replace a shorter table policy and never changes rollup tiers. |
TIMELESS_METRICS_ROLLUPS |
metrics | unset (none for new databases) |
Persisted rollup ladder such as 1h@30d,1d@365d. Unset preserves an existing database's ladder; explicit none disables future rollup production and lets scheduled compact maintenance drain old rollup rows through the same 64-row transaction budget. The HTTP API does not require persisted rollups. |
TIMELESS_METRICS_PROMQL_MAX_POINTS_PER_SERIES |
metrics | 11000 |
Native and PromQL range evaluation-grid points per series; valid range 1–11,000. Raw export uses the total result-point limit. |
TIMELESS_METRICS_PROMQL_MAX_RESULT_POINTS |
metrics | 100000 |
Final native and PromQL result points; discovery counts returned names, values, or series. |
TIMELESS_METRICS_PROMQL_MAX_WORK_POINTS |
metrics | 100000 |
Points a query keeps as it evaluates: the points of the windows it reads, and every intermediate vector. This is what a query's memory depends on. Discovery selector evaluations are bounded separately by this value. Catalog reads use the dedicated catalog limits below. |
TIMELESS_METRICS_PROMQL_MAX_STORAGE_POINTS |
metrics | 5000000 |
Stored points decoded to answer a query, checked from the chunk index before any payload is read. A chunk is decoded whole, so a query about one moment inside a chunk of 16,384 points decodes all of them for the few it keeps. Measured, decoding costs about 5 ms a million points: this is a bound on CPU, not on memory, and is sized apart from ..._MAX_WORK_POINTS so that a query that reads much and keeps little is not refused for what it does not keep. Native reads bound their storage work by this value too. |
TIMELESS_METRICS_PROMQL_MAX_CATALOG_SERIES |
metrics | 1000000 |
Series examined while building a metric catalog for native discovery and the PromQL selector catalog. 0 disables the ceiling. Catalog reads are deliberately not charged to MAX_WORK_POINTS; a small discovery response can still require reading a large catalog. Raise this (and ..._MAX_CATALOG_BYTES) with cardinality. |
TIMELESS_METRICS_PROMQL_MAX_CATALOG_BYTES |
metrics | 268435456 |
Catalog metadata bytes (metric name plus label keys and values) read while building a catalog. 0 disables the ceiling. Deliberately not tied to MAX_RESPONSE_BYTES; raise it independently for high-cardinality stores. |
TIMELESS_METRICS_PROMQL_MAX_RESPONSE_BYTES |
metrics | 16777216 |
Serialized native and PromQL response bytes. This bounds the response body only; catalog reads use the dedicated catalog limits above. |
TIMELESS_METRICS_PROMQL_DEFAULT_SUBQUERY_STEP_MS |
metrics | 15000 |
Omitted PromQL subquery resolution in milliseconds. |
TIMELESS_METRICS_PROMQL_DEADLINE_MS |
metrics | 30000 |
Hard execution deadline for native metrics, PromQL/MetricsQL, and discovery reads. |
TIMELESS_LOGS_READER_CONNECTIONS |
logs | 2 |
Independent bounded SQLite readers. |
TIMELESS_LOGS_COMMAND_QUEUE_BATCHES |
logs | 256 |
Writer-command queue capacity in admitted request batches. |
TIMELESS_LOGS_QUEUE_BYTES |
logs | 134217728 |
Queued-payload admission gate in bytes; admissions wait while in-flight ingest bytes exceed it (a batch larger than the gate is admitted alone). |
TIMELESS_LOGS_FLUSH_INTERVAL_SECS |
logs | 1 |
Ordered public extension flush cadence. |
TIMELESS_LOGS_OPTIMIZE_INTERVAL_SECS |
logs | 30 |
Bounded public optimize cadence. |
TIMELESS_LOGS_OTEL_TRACES_ENDPOINT |
logs | unset | Full OTLP/HTTP traces endpoint. Unset disables export with no exporter, queue, or span cost. Enabled, the logs server emits one timeless.logs.optimize span per scheduled optimize/retention sweep with the backlog it saw, the budget it chose, the extension's before/after block, entry, byte, and elapsed-time deltas, and an event only when the pass takes at least 50 ms. Nothing per request, entry, span, block, or ingest is traced. |
TIMELESS_LOGS_OTEL_TRACES_HEADERS, ..._HEADERS_FILE, ..._CA_CERT, ..._SAMPLE_RATIO, ..._QUEUE_SPANS, ..._BATCH_SPANS, ..._EXPORT_DELAY_MS, ..._EXPORT_TIMEOUT_MS, ..._REQUEST_SAMPLE_RATIO, ..._REQUEST_SLOW_MS |
logs | as for metrics | Same meanings, defaults, bounds, and secret handling as the TIMELESS_METRICS_OTEL_TRACES_* settings above. |
TIMELESS_LOGS_LOGSQL_MAX_RESULT_ROWS |
logs | 100000 |
Final rows; valid range 1–100,000. |
TIMELESS_LOGS_LOGSQL_MAX_WORK_ROWS |
logs | 100000 |
Cumulative decoded/examined rows and bounded state items. |
TIMELESS_LOGS_LOGSQL_MAX_RESPONSE_BYTES |
logs | 16777216 |
Response and bounded pipeline-state bytes. |
TIMELESS_LOGS_LOGSQL_DEADLINE_MS |
logs | 30000 |
Hard LogsQL/native-query deadline in milliseconds. |
TIMELESS_LOGS_TIMESTAMP_UNIT |
logs | us |
Stored log timestamp unit, ms or us. Must match an existing table; choose ms for the default direct-SQL table. Validated before policy changes, writer PRAGMAs, or schema-ledger writes. No timestamp conversion is performed. |
TIMELESS_LOGS_INDEX_KEYS |
logs | absent/inherit | Comma-separated indexed-metadata allowlist (default for new stores service,path,status,host). New stores are created with it; an existing store whose persisted allowlist differs is reindexed once at startup (postings rewritten for every block). Absent preserves the store's current allowlist. An exact string match on any listed key (kind:="recording") reads only the blocks that hold the value; on an unlisted key it reads the whole time range, so list the fields that queries select rare values by. |
TIMELESS_LOGS_RETENTION |
logs | absent/inherit | Retention window as <n> plus a required unit suffix s, m, h, or d. Applied to new and existing stores at startup; enforcement remains at flush/optimize boundaries. Absent preserves the store's current window. |
TIMELESS_TRACES_READER_CONNECTIONS |
traces | 2 |
Independent bounded SQLite readers. |
TIMELESS_TRACES_COMMAND_QUEUE_BATCHES |
traces | 256 |
Writer-command queue capacity in admitted requests. |
TIMELESS_TRACES_QUEUE_BYTES |
traces | 134217728 |
Queued-payload admission gate in bytes; admissions wait while in-flight ingest bytes exceed it (a batch larger than the gate is admitted alone). |
TIMELESS_TRACES_RETENTION_SECS |
traces | absent/inherit | If absent, preserve the vtab's stored retention (a fresh table has none); positive sets and enforces it; 0 explicitly disables it. |
TIMELESS_TRACES_FLUSH_INTERVAL_SECS |
traces | 1 |
Ordered public extension flush cadence. |
TIMELESS_TRACES_OPTIMIZE_INTERVAL_SECS |
traces | 30 |
Bounded public optimize cadence. |
TIMELESS_TRACES_OTEL_TRACES_ENDPOINT |
traces | unset | Full OTLP/HTTP traces endpoint. Unset disables export with no exporter, queue, or span cost. Enabled, the traces server emits one timeless.traces.optimize span per scheduled optimize/retention sweep with the backlog it saw, the budget it chose, the extension's before/after block, entry, byte, and elapsed-time deltas, and an event only when the pass takes at least 50 ms. Nothing per request, entry, span, block, or ingest is traced; a traces server may export into its own OTLP ingest without recursing. |
TIMELESS_TRACES_OTEL_TRACES_HEADERS, ..._HEADERS_FILE, ..._CA_CERT, ..._SAMPLE_RATIO, ..._QUEUE_SPANS, ..._BATCH_SPANS, ..._EXPORT_DELAY_MS, ..._EXPORT_TIMEOUT_MS, ..._REQUEST_SAMPLE_RATIO, ..._REQUEST_SLOW_MS |
traces | as for metrics | Same meanings, defaults, bounds, and secret handling as the TIMELESS_METRICS_OTEL_TRACES_* settings above. |
TIMELESS_BUILD_COMMIT is a build-time override, not a runtime setting. If
unset, the build script records the current Git commit when available. Cargo
supplies target and profile. These values are returned by --version and
health/readiness build identity.
A large deployment must size the metrics read limits before it grows into them. Every limit below is a hard, per-request ceiling; a request that exceeds one returns a client error naming the limit, and the setting that raised it, without partially rendering. All limits can be raised per deployment, and the documented defaults are the ship-time values.
| concern | default | setting |
|---|---|---|
| Catalog series examined (discovery and PromQL selector expansion) | 1,000,000 |
TIMELESS_METRICS_PROMQL_MAX_CATALOG_SERIES (0 = unbounded) |
| Catalog metadata bytes read | 256 MiB |
TIMELESS_METRICS_PROMQL_MAX_CATALOG_BYTES (0 = unbounded) |
| Result points returned | 100,000 |
TIMELESS_METRICS_PROMQL_MAX_RESULT_POINTS |
| Points kept while evaluating | 100,000 |
TIMELESS_METRICS_PROMQL_MAX_WORK_POINTS |
| Stored points decoded | 5,000,000 |
TIMELESS_METRICS_PROMQL_MAX_STORAGE_POINTS |
| Response body bytes | 16 MiB |
TIMELESS_METRICS_PROMQL_MAX_RESPONSE_BYTES |
| Evaluation-grid points per series | 11,000 |
TIMELESS_METRICS_PROMQL_MAX_POINTS_PER_SERIES |
| Execution deadline | 30 s |
TIMELESS_METRICS_PROMQL_DEADLINE_MS |
Guidance:
- Decoding and keeping are bounded apart. A ranking or a sum at one
moment inside a large chunk decodes the whole chunk of every series it
matches and keeps a point or two of each. Raise
..._MAX_STORAGE_POINTSfor a store whose chunks have grown large and whose readers go back through time; raise..._MAX_WORK_POINTSonly for queries that hold many points at once, such as long ranges over many series. - Catalog limits are independent of response size. Discovery routes such as
/api/v1/label/__name__/valuesbuild a full catalog but return a small body; the catalog bytes they read are governed by..._MAX_CATALOG_BYTES, not..._MAX_RESPONSE_BYTES. Raise the catalog limits first when discovery fails on a high-cardinality store. - Scaling. Catalog bytes grow with the number of series and the size of
their label sets; a store with roughly 50,000 DOCSIS-style series already
builds a catalog over 20 MiB. Size the ceilings to at least twice the
projected population, or set
0to make them unbounded and rely on the request deadline and..._MAX_RESPONSE_BYTESinstead. - Memory. Discovery rows stream lazily:
timeless_seriesbuilds one row perxNext, and the label-name/value routes accumulate only distinct strings, so peak memory is O(distinct output) rather than O(catalog). A broad PromQL selector still collects the series it matches (execution needs them), so the catalog ceilings remain the backstop there;TIMELESS_METRICS_READER_CONNECTIONSreaders may run concurrently. Leave the ceilings finite unless the host has headroom, and prefer narrowing selectors over raising them. - PromQL gotcha. A regex or negative matcher (
{__name__=~".+"},{job!="x"}) cannot use a name index and therefore builds the full catalog. Exact metric names (node_load1,{__name__="node_load1"}) do not. See PromQL matrix.
Authentication is opt-in: servers start open, and verification engages
only under TIMELESS_AUTH_MODE=required with a policy file. For running the
binaries standalone — including non-loopback binding and sharing a database
with an embedding application — see
Running standalone.
Enabling it takes four commands — no Elixir control plane required:
timeless-authctl keygen --out ./auth
timeless-authctl policy init --signal metrics --key "$(cat ./auth/timeless-auth.pub)" \
--out ./auth/policy.json
TIMELESS_AUTH_MODE=required TIMELESS_AUTH_POLICY_FILE=./auth/policy.json ./timeless-metrics-api &
curl -H "Authorization: Bearer $(timeless-authctl token mint --key ./auth/timeless-auth.key \
--policy ./auth/policy.json --subject default --signal metrics --ttl 1h)" \
http://127.0.0.1:19439/api/v1/statustimeless-authctl policy add-subject narrows scopes per caller, and
timeless-authctl token inspect decodes a token without verifying it. For
operators who want ingest and query open but administration closed without
any token machinery, TIMELESS_ADMIN_KEY independently gates the
administrative routes (see the environment table). When enabled,
every route except the probe endpoints /live, /ready, and /health,
and the /metrics self-metrics endpoint (exact-match exemptions, so
container probes, load balancers, and Prometheus scrapers need no
credentials) requires an exact Authorization: Bearer <token>. Tokens are compact JWS/JWT values signed with
Ed25519 (alg=EdDSA); typ, if present, must be JWT. The policy contains
base64url-no-padding 32-byte public keys. Rust validates tokens but never
issues them.
Policy version 1 contains issuer, audience, tenant,
minimum_auth_version, positive max_token_seconds, maximum_limits, a
nonempty subjects object, a nonempty keys array, and optional
revoked_jtis. A subject contains exact allowed scopes, auth_version,
maximum_limits, and optional enabled (default true). A key contains
kid, public_key, not_before, expires_at, and optional revoked.
Audience convention: timeless-<signal> — timeless-metrics,
timeless-logs, timeless-traces. authctl policy init writes this, and
authctl mint copies the policy's audience into the token's aud, so an
authctl-managed deployment satisfies it automatically. Hand-written policies
and third-party minters should follow it.
The verifier does not enforce the shape: it requires only that the token's
aud equals the policy's audience (any matching pair is accepted), and it
separately rejects a token whose signal does not match the server's. A
per-signal audience is therefore defence in depth rather than the primary
control — it keeps a policy minted for one signal from being repurposed for
another by editing the server it points at.
Token claims are iss, aud, sub, jti, tenant, signal, scopes,
auth_version, iat, nbf, exp, and limits. The signal is exactly
metrics, logs, or traces. The seven positive limit fields are:
| Claim limit | Enforcement |
|---|---|
max_request_bytes |
Auth middleware buffers and caps the request before route execution. The hard server body cap remains 10 MiB. |
max_decompressed_bytes |
Caps OTLP JSON and protobuf after decompression, and uncompressed OTLP bodies. |
max_response_bytes |
Auth middleware caps the final body; signal query limits may be tighter. |
max_query_rows |
Prechecks limit, max_rows, max_points, and the LogsQL limit pipeline stage; then verifies the handler's internal exact result-row header. |
max_request_ms |
Deadline for read and stats routes. Writes are not response-cancelled because queued durability would make timeout ambiguous. |
max_concurrent_requests |
Per-subject active request cap. |
max_queue_ms |
Per-subject admission wait before HTTP 429. |
Claim limits cannot exceed either policy-wide or subject-wide maxima, and auth cannot raise the signal server's hard query/body limits. A token may not claim a scope absent from its subject even if the current route needs only one of the claimed scopes. Policy files are at most 1 MiB; bearer tokens are at most 32 KiB; not-before and issued-at checks allow 30 seconds of positive clock skew. Policy replacements are noticed by size, modification time, and file identity so atomic same-size rewrites reload correctly.
Scope derivation is deterministic: readiness, health, and paths ending in
/stats use <signal>:stats; paths ending in /flush, /optimize, or
/backup use maintenance; remaining GET/HEAD and query/discovery routes use
read; other routes use write. The exact result is listed beside every route
above.
Backpressure rejections carry Retry-After, in whole seconds, derived from
the subject's own max_queue_ms — exactly how long admission was willing to
hold the request — rounded up to a 1-second floor (RFC 9110 has no sub-second
form). It is sent only for queue_wait_exceeded (429) and queue_unavailable
(503): rejections a retry cannot fix, such as bad credentials, a wrong tenant,
or an oversized payload, deliberately carry no hint.
RateLimit-* headers are deliberately not sent. Those describe a request
quota over a time window; the limit enforced here is max_concurrent_requests,
a concurrency cap with no window, so populating them would misreport what the
server actually enforces.
Authorization errors use
{"error":"data_plane_authorization","reason":"<code>"}. Stable reason
codes are:
- credential/token/policy:
missing_credentials,invalid_credentials,invalid_token,invalid_key,invalid_signature,unknown_key,unknown_subject,authorization_policy_unavailable,authorization_policy_invalid,clock_unavailable,expired_token,token_lifetime_exceeded,token_not_yet_valid,revoked_key,revoked_token, andstale_auth_version; - identity/authority:
wrong_issuer,wrong_audience,wrong_tenant,wrong_signal,insufficient_scope,scope_policy_exceeded, andclaim_limits_exceeded; - limits/admission:
request_bytes_exceeded,response_bytes_exceeded,invalid_query_limit,query_rows_exceeded,request_time_exceeded,queue_unavailable, andqueue_wait_exceeded.
Credential failures are HTTP 401, authority failures HTTP 403, malformed limit parameters HTTP 400, byte caps HTTP 413, row caps HTTP 422, admission exhaustion HTTP 429/503, and read deadlines HTTP 504.
Startup fails closed before binding the listener if configuration, auth
policy, extension loading, capability negotiation, data ABI, minimum version,
signal batch support, public vtab schema, stored retention, or database schema
is incompatible. The current server data-schema ledger version and extension
data ABI are both 1. A database written by a newer schema version is not
mutated. A pre-ledger database receives the additive, idempotent
_timeless_schema_migrations v1 row on the writer connection.
Each server holds an advisory exclusive
<canonical-database>.timeless-api.lock lease, shared across signals, together
with all three legacy .timeless-<signal>-api.lock files for compatibility.
Existing file symlinks and directory aliases resolve before leasing and opening
SQLite; a new database uses its canonical parent directory. Dangling file
symlinks fail with instructions to create the target first. Linux and macOS
reject hard-linked database files because they can create conflicting WAL paths.
Do not rename or replace the database, add hard links, or remove lease files
while a server owns it. This fences another conforming
server owner. It cannot protect against an unrelated SQLite process that
ignores the lease; an operator must not give another writer the database while
the server owns it.
The writer is a bounded FIFO. Scheduled flush/compact/optimize/retention work
uses the same ordered public command path as explicit maintenance. On SIGINT
or SIGTERM, Axum stops new admission and drains accepted HTTP requests. The
server stops periodic maintenance, closes readers, places a final flush behind
all admitted writer commands, checkpoints the WAL with TRUNCATE, joins its
workers, and releases the owner lease. Metrics also stop all scrape tasks.
Shutdown returns failure if the final flush or complete checkpoint fails.
SIGKILL cannot run this sequence. Committed SQLite transactions remain recoverable, which for metrics is every acknowledged write; a logs or traces extension-buffer tail is intentionally not claimed durable.
Explicit flush is the durability boundary for logs and traces. A metrics
write is durable when the request that made it is answered; its flush route
remains the ordered barrier behind every admitted write. Metrics and traces
return exact admitted/completed/failed/queued/in-flight watermarks; logs
returns {"status":"ok"} after the same ordered writer barrier.
Every backup route accepts exactly:
{"destination":"/absolute/path/to/new-backup.db"}The parent directory must already exist. The destination must be absolute,
must name a file, and must not exist. Backup runs on the established sole
writer connection: metrics flushes and compacts/rolls up; logs and traces
flush and drain actionable optimize backlog. The writer then requires a
complete WAL TRUNCATE checkpoint, copies pages with SQLite's online-backup
API, runs PRAGMA quick_check, validates the signal schema ledger, fsyncs the
staging file, and publishes without overwrite in the destination directory.
An error removes the staging name and does not label a partial copy complete.
The success report contains signal, final destination, bytes, copied pages, schema version, WAL/checkpoint frame counts, completion Unix seconds, and elapsed nanoseconds. Restore is deliberately offline: stop the owner, retain the current database and WAL/SHM as rollback material, copy or rename the verified backup into the configured database path, and start the same or a compatible newer server. Startup performs the capability/schema check before serving. Do not replace a live database file behind an owner process.
The auth envelope is documented above. Timeless-native query, discovery,
administration, and maintenance handlers use native JSON error contract
v1. Every unsuccessful JSON body in that boundary contains stable,
machine-readable error and reason strings:
{"error":"invalid_query","reason":"query_validation","message":"metric is required"}message is optional and contains only client-safe diagnostics. Internal
storage, SQLite, schema, and filesystem detail is logged server-side and is
never returned. Families may add a documented parameter, limit, or
deadline_ms value. Contract v1 is the documentation identifier rather than
an extra wire field; an incompatible envelope change requires a newly named
contract and a compatibility-line change.
| Family | Status | Shape |
|---|---|---|
| Unsupported route/parameter/capability | 422 | {"error":"unsupported_capability","reason":"..."}; LogsQL may add message. |
| Malformed LogsQL | 400 | {"error":"invalid_query","reason":"malformed_logsql","message":"..."} |
| Invalid native logs query parameter | 400 | {"error":"invalid_query","reason":"invalid_query_parameter","parameter":"start|end|order"} |
| Other native query validation | 400 | {"error":"invalid_query","reason":"query_validation","message":"..."} |
| Invalid administration JSON | 400 | {"error":"invalid_request","reason":"invalid_json_body"} |
| Invalid scrape-target set | 400 | {"error":"invalid_request","reason":"scrape_target_validation","message":"..."} |
| LogsQL execution conflict | 422 | {"error":"query_execution","reason":"field_conflict","message":"..."} |
| Native metrics query limit | 400 | {"error":"invalid_query","reason":"query_validation","message":"..."} naming the exceeded budget. |
| Native logs query limit | 422 | {"error":"query_limit",...} with reason max_result_rows, max_work_rows, or max_response_bytes and numeric limit. |
| Native query timeout | 504 | {"error":"timeout","reason":"query_deadline","deadline_ms":N} |
| Retryable native storage contention | 503 | {"error":"temporarily_unavailable","reason":"storage_busy"} plus Retry-After. |
| Overlapping backup | 409 | {"error":"conflict","reason":"backup_in_progress"} |
| Internal native handler fault | 500 | {"error":"internal","reason":"query_execution|stats_execution|flush_execution|backup_execution"} |
Compatibility routes retain their established protocol shapes and are not silently migrated to native contract v1:
| Compatibility boundary | Error contract |
|---|---|
| Prometheus and MetricsQL query/discovery routes | {"status":"error","errorType":"...","error":"..."}; parse is 400/bad_data, execution or limit is 422/execution, timeout is 504/timeout, and retryable storage contention is 503/unavailable plus Retry-After. |
| Jaeger query/discovery routes | Existing Jaeger handler bodies, including bare client diagnostics and {"status":"error","error":"internal"} for internal faults. |
| OTLP trace ingest | OTLP export response on success (JSON {} or empty protobuf), matching request encoding; failures use google.rpc.Status. |
VictoriaMetrics/VictoriaLogs-compatible ingest and Prometheus text /metrics |
Existing ingestion error bodies and text exposition behavior. |
| Liveness/readiness/health probe state | Existing probe status bodies; internal health-stat collection faults use native stats_execution. |
Unknown parameters and unsupported language constructs fail explicitly; no server silently delegates to Elixir or another query implementation. Detailed language diagnostics and intentional compatibility differences are versioned with their matrix rows.
The intended release matrix covers x86-64 and AArch64 Linux GNU and macOS.
Linux loadable extensions use .so; macOS uses .dylib. A complete archive
contains the telemetry extension and all three signal-specific binaries.
dbhealth is a separate extension artifact and is not registered by these
servers.
v0.4.1 passed all four intended native package jobs and the complete outer
checksum gate. The archives are retained as authenticated GitHub Actions
artifacts until 2026-11-06; the workflow did not attach them to a permanent
GitHub Release. Other targets may compile from source but are not claimed as
release artifacts. Exact archive paths, current channel status, checksums,
manifest/SBOM content, install layout, and removal behavior are in the
artifact inventory.
The binaries use TCP, a local SQLite/libSQL-compatible database file, WAL
mode, and the loadable extension API. A direct Rust embedding that does not
need HTTP should use the mutually exclusive embedded crate feature,
timeless_ext::register_telemetry, and the
embedded guide instead of launching a signal server.
Before replacing either side of the binary/extension pair, follow the compatibility statement and upgrade and rollback guide.