Skip to content

Latest commit

 

History

History
779 lines (681 loc) · 57.5 KB

SERVER_API_REFERENCE.md

File metadata and controls

779 lines (681 loc) · 57.5 KB

Rust signal server API reference

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.

Binaries and launch contract

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.

Complete route inventory

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.

Storage and compression series

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.

OpenTelemetry exporter health

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.

Sampled HTTP request spans

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.

Metrics requests

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/from and end/to are epoch seconds and default to the previous hour and now.
  • Native range requires metric; accepts the same bounds, a positive whole-second step (default 60), and aggregate=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, optional time, and optional lookback_delta. Range accepts query, start, end, step, and optional lookback_delta.
  • MetricsQL instant additionally accepts step and max_lookback; range additionally accepts max_lookback.
  • Discovery accepts repeated match[] or match. Native label values and series may also use metric; the Prometheus series alias requires at least one match[].

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.

Logs requests

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.

Trace requests

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.

Runtime environment

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.

Cardinality and query limits

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_POINTS for a store whose chunks have grown large and whose readers go back through time; raise ..._MAX_WORK_POINTS only 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__/values build 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 0 to make them unbounded and rely on the request deadline and ..._MAX_RESPONSE_BYTES instead.
  • Memory. Discovery rows stream lazily: timeless_series builds one row per xNext, 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_CONNECTIONS readers 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 and admission

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/status

timeless-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, and stale_auth_version;
  • identity/authority: wrong_issuer, wrong_audience, wrong_tenant, wrong_signal, insufficient_scope, scope_policy_exceeded, and claim_limits_exceeded;
  • limits/admission: request_bytes_exceeded, response_bytes_exceeded, invalid_query_limit, query_rows_exceeded, request_time_exceeded, queue_unavailable, and queue_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, ownership, and shutdown

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.

Flush, backup, restore, and WAL

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.

Error envelopes

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.

Platform and artifact boundary

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.