Repository navigation
v0.30.0
[0.30.0] - 2026-11-03
Fixed
- A resume position whose values do not have the order terms' types is
refused (issue #1623).plan_pagenow checks every value of a resume
tuple against the type it proved for the order term at that position, and
refuses a mismatch withPagePlanError::ResumeValueTypeMismatch, naming the
position, the term and both types, instead of rendering a literal of another
type into the keyset predicate
(a_resume_value_of_another_type_is_refused_at_any_position). A fixed-width
binary value of another width is a mismatch. The MCPravel_query_sqland
ravel_search_logstools answer such a cursor ascursor_invalid
(a_cursor_whose_tuple_has_another_type_is_invalid). - A MetricsBench memory-step failure no longer stops the folded read-back
(issue #2821). After an--rss-bandbreach or an OOM-killed container,
run.shlistsmemoryinstamps.jsonasdeferred_failures, runs stop,
fold, start and readback-folded, and then exits as before: 75 naming
memory, or 70 naming ingest after a failed ingest verification. A folded
step that fails after the memory failure exits 70 naming that step. - The MetricsBench start step no longer reruns the one-shot services
(issue #2821). After the fold,run.shstarts the stoppedravel-server
container withdocker compose up -d --no-deps --no-recreate ravel-server
instead ofdocker compose start ravel-server, which also started its
dependencies: on the real-S3 lanecreatebucketsreran its empty-bucket
check against Ravel's bucket, which by then held the run's objects, exited
1, and failed the step. Theravel_restartstamp and
the step's failure message name the new command. - MetricsBench's Mimir comparator can now list the cardinality profile's
1,000,000 series (issue #2821). Indeploy/metricsbench/config/mimir.yaml,
the gRPC message ceilings go from the 100 MiB default to 512 MiB: the
server pair, and every client pair on the query path between Mimir's own
components (six sections, listed in the README's frozen-limits table). At
the default, the harness read-back's/api/v1/seriesanswered 500 ("grpc:
trying to send message larger than max (133231002 vs. 104857600)"), which
made both cardinality runs of 2026-11-03 diagnostic. A response under
100 MiB is unaffected. - A declared typed attribute column whose key starts with
__ravel_is
refused (issue #3730, epic #1374). The prefix names Ravel's own
synthetic columns (__ravel_attr[k],__ravel_row_ref, and the MCP
tools'__ravel_group,__ravel_group_attrs,__ravel_depthand
__ravel_parent), and before this nothing
stopped a declaration from taking one of those names. The check is
case-sensitive, so__Ravel_xis still an ordinary key
(validation_rejects_reserved_prefix,
validation_accepts_keys_that_only_resemble_the_reserved_prefix). It
applies toravel-cli typed-attr-column set, toravel-server's
--typed-attr-columnand--typed-attr-column-tenantflags, which now
fail startup on such a key, and to every durable tenant config write
(set_tenant_config_refuses_a_new_reserved_key). A tenant config record
stored before this rule still loads and its typed attribute columns are
still served (a_stored_record_with_a_reserved_key_still_loads,
a_stored_reserved_prefix_key_is_still_applied), and a durable write that
carries such a stored key through unchanged, such as a retention change,
still succeeds, while one adding a new reserved key is refused
(set_tenant_config_keeps_a_stored_reserved_key_and_refuses_a_new_one).
ravel-cli typed-attr-column setreplaces the whole list, so it refuses
any reserved key, a stored one included. - An MCP response that a row cap stopped stays inside its byte cap
(issue #3733). The byte fit now measures the envelope with the row cap's
rows_omittedcount androw_cap_hitalready written, and under the
statusfinishgives it (ok_boundedwhenever a cap took something), so
neither can add bytes after the fit. Before, a response packed to its cap
could end up a few bytes over it once the status changed fromokto
ok_bounded. When the byte cap cuts, the kept rows or the one cut cell can
now be a few bytes smaller than before. - A PromQL read no longer holds every object's samples outside the memory
budget (issue #3747). The local sample prefetch folds each object's scalar
and histogram runs into one per-series accumulator as the object's fetch
completes, with the resultmerge_soa_runsgives in any completion order
(scalar_fold_equals_the_merge_in_any_completion_order), and reserves what
the fold holds against the engine memory budget. A series carried by many
objects is held and charged once
(a_sample_read_holds_one_copy_of_each_series_across_objects), and a read
whose merged samples do not fit is refused withFetchMemoryExhausted
against the budget's limit instead of reaching the host-memory floor
(a_sample_read_that_outgrows_the_budget_is_refused_against_it). The
charge is 24 bytes per held scalar sample, plus an overhead per series and
per distinct provenance prefix, and shows under
ravel_memory_reserved_bytes{component="fetch"}. A native-histogram sample
costs substantially more, because its spans and buckets are charged too. A
large read now either answers or is refused earlier, with "fetch memory
exhausted", against the budget itself, so a deployment with a tight
--memory-budget-bytesmay see more refusals on very large reads.
TooManySeriescan now be raised partway through a fetch, at the series
that crosses the limit. - The MCP compact text block escapes line separators, C1 controls and bidi
controls in every caller string (issue #3750). String cells, column names,
the failure message, warnings andnext_stepstext now escape U+2028,
U+2029, the bidi controls U+202A to U+202E and U+2066 to U+2069, and the
marks U+200E, U+200F and U+061C as\u{XXXX}, beside the control
characters they already escaped. Map cells,
the# sharedline and each row's diff are serialized as JSON, and each of
those characters the serializer left raw is written as JSON's own\uXXXX,
so U+007F, U+0085 and U+009B no longer reach the text raw
(no_caller_path_writes_a_hostile_character_raw) and each stays JSON that
parses back to the original map
(json_cells_parse_back_to_the_original_map). Thewarnings:
line now quotes each warning and writes its"as\", so a warning
containing;no longer reads as two
(one_warning_containing_the_separator_is_not_read_as_two). A
next_step:line quotes its action the same way, so an action containing
-is not read as a split
(one_next_step_action_containing_the_separator_is_not_read_as_a_split).
structuredContentis unchanged, but a consumer that parses the text block
sees the new escapes and quoting. - The MCP docs no longer misstate
accuracy.lower_bound_count(issue
#3751). The reference and guide now say it is reserved and alwaysfalse;
0.29.0's guide said it wastrueon aCOUNToverlogsorspans. The
reference also
says that abudget_exceedednames its counter infailure.message, with
failure.counterset only for the analyze-timeseries series cap.
Added
ravel_get_tracereports the span tree (#1382, epic #1374). Every span
carries__ravel_depth, its count of returned ancestors, and
__ravel_parent:root,present,cycle, or, for a parent id no
returned span has,missingwhen the result is complete andnot_returned
when it is not, because a row or byte cap fired or coverage is partial
(missing_parent_is_disclosed,
a_row_capped_result_never_says_missing,
get_trace_reports_the_span_tree_and_a_dropped_root). The tree is labelled
after the byte fit over the rows that survive it, and the envelope stays
inside the cap
(a_byte_capped_result_labels_after_the_fit_and_stays_inside_the_cap). A
child that starts before its parent still resolves
(a_child_that_starts_before_its_parent_is_present), a parent loop ends
the walk ascyclewith a null depth
(a_parent_cycle_ends_and_is_labelled), and a duplicated span id resolves
to its first row (a_duplicated_span_id_resolves_to_its_first_row). A
missingparent under atime_rangeadds awiden_time_rangenext step,
and anot_returnedone addsraise_max_rows
(next_steps_appear_exactly_for_an_absent_parent).include_logs: trueis
still refused asunsupported, and its message now names
ravel_search_logs(include_logs_names_search_logs).benchmarks/mcp-evalholds the MCP agent evaluation corpus and runner
(issue #1385, epic #1374). It has 24 questions across six journeys, their
expected answers and evidence, the bands, and a seeded OTLP fixture.
run.sh --checkvalidates the corpus offline,--fixtureloads the
fixture into an empty tenant, and--run baseline|fullruns every case
through$MCP_EVAL_AGENT_CMDand scores correctness, evidence, tool calls,
invalid queries, retries, store requests and bytes. No results are
recorded yet, and a run refuses an--outinside the repository.- MetricsBench counts Ravel's objects after ingest (issue #2821). The
newobjectsstep ofdeploy/metricsbench/run.shlists Ravel's bucket on
either lane and writesravel-objects.jsonwith the count and bytes of data
objects, commit records, store-qualify probes and everything else, and the
optional--ravel-objects-band MIN:MAXfails the run when the data object
count, and not the whole bucket, falls outside it. - The MCP server serves
ravel_query_sql(epic #1374). It runs one
read-onlySELECTover one table, with its rows filtered to the required
time_rangerather than only the segments listed for it
(query_sql_excludes_a_row_outside_time_range_from_an_overlapping_segment),
and pages by keyset. The statement is checked before anything is read:
two tables areinvalid_argumentwith asplit_the_statementnext step,
a write isunsupported, and a statement the page planner refuses, such
as one with its ownLIMITor a floatORDER BYterm, is
invalid_argumentnaming the reason
(statement_refusals_are_answered_before_the_backend). AnORDER BYterm
computed from other rows (an aggregate, a window function such as
count(*) OVER (), or a subquery) carries no cursor, since a row committed
between pages could move a group past it: the statement runs as written, a
capped result isok_bounded, and anorder_by_the_rows_own_columnsnext
step says that paging needs an order on the row's own columns (issue
#3749). A full page of a
statement with noORDER BYto page by endsok_boundedwith no cursor
(a_capped_statement_with_no_order_is_bounded). Under a total order
the cursor resumes after the page's last row
(query_sql_walks_a_total_order_three_pages_by_keyset); under an order that
is not total, a page ends before an open group of equal rows
(a_grouped_page_ends_before_an_open_group). A later page resolves at page
one's instant and audits at its own clock
(a_later_query_sql_page_resolves_at_the_mint_instant_and_audits_at_the_callers_clock).
A full page with no cursor position (no complete group, a boundary value
that has no cursor encoding, or a cursor over its 4 KiB bound) also ends
ok_boundedwith
no cursor and a next step
(no_complete_group_fits_is_bounded_with_no_cursor,
a_boundary_with_no_cursor_position_is_bounded_with_a_next_step). A newer
erasure expires the cursor (a_newer_erasure_expires_the_cursor), and a
cursor sent with another statement ortime_rangeiscursor_invalid
(a_cursor_for_another_query_or_time_range_is_refused).max_rowsdefaults to
200 with a ceiling of 5,000. Anevidence_refis refused asunsupported. metricsbench_ingest --readback-onlyreads one system back from a kept
ingest.jsonwithout replaying anything (#3685). It rebuilds the row's
read-back plan from the same stream through the replay's own recording
path, reads the row back with its commit tokens, and exits 0 on a match, 1
on a mismatch, a read-back error or another setup failure, and 64 on a
refused report or flag, including a stream that drifted from the kept one.- MetricsBench reads Ravel back on a folded catalog (#3685). After the
memory step,deploy/metricsbench/run.shstops ravel-server, folds the
metrics catalog with every writer stopped, starts the server again, and
repeats the Ravel read-back. It fails the run when the folded read-back
does not match, or when its catalog GETs exceed
--folded-catalog-get-floor(default 64) plus
--folded-catalog-get-max-fraction(default 0.05) of the data objects the
objects step listed; an unfolded read costs about one per object. - The MCP server serves
ravel_query_promql(epic #1374). It runs an
instant evaluation (evaluation_time) or a range evaluation
(time_rangewithstep) and answers one row per series, the series'
ts_nsandvalueas parallel arrays, withNaNand-0.0kept
(query_promql_answers_both_modes_with_exact_samples). The mode is checked
before anything is read: both modes, neither, or half of the range pair is
invalid_argument(both_modes_or_neither_is_refused_before_the_backend),
and so is a step without a unit or an empty range
(a_bad_step_or_an_empty_range_is_refused_before_the_backend). A time or
step that is not a whole millisecond is refused rather than rounded
(sub_millisecond_inputs_are_refused_not_truncated). The range is
half-open: steps run fromstart_nswhile belowend_ns, and the scope
reports the step alignment and the 5 m lookback. A result that skipped
part of its coverage is refused asunavailableunless the call sets
allow_partial_coverage: true
(partial_coverage_is_refused_without_consent_and_returned_with_it); a
partial result returned with that consent reportsaccuracy.exact: false,
with an approximation saying it omits the data the query could not reach
(issue #3751).
max_rowscounts series, defaults to 200 with a ceiling of 5,000, and a
capped result isok_boundedwith no cursor
(query_promql_max_rows_caps_series_and_is_ok_bounded). A range of more
than 11,000 steps, the engine's own limit, isinvalid_argumentbefore the
backend call, with a next step naming the smallest step that fits
(a_range_over_the_step_limit_is_refused_before_the_backend). A series
whose samples do not fitmax_response_byteskeeps a prefix of them as
parallel arrays of one length, withsamples_omittedcounting the rest,
and the result isok_bounded
(a_series_over_the_byte_cap_keeps_an_aligned_prefix,
query_promql_bounds_a_long_series_by_steps_and_bytes).ravel_log_lines
andravel_log_byteswork over a logs tenant
(query_promql_counts_log_lines_and_bytes). Anevidence_refis refused
asunsupported. ravel-cli loadwrites batches into a range of past ingest hours
(issue #3700).--ingest-hour-stride <hours> --ingest-hour-start <unix hour>writes batch i into ingest hour start + i x stride through a shifted
clock, so a fresh tenant can span many ingest hours for read-path
measurement (ADR-3692 decision 8). The hours are synthetic, so the lane is
for fresh measurement tenants only. It forces one fully acked batch per
hour (--pipeline-depth 1,--read-cursors 1,--target-bytes 1), and
refuses with exit code 2 before any write for--signal metrics, on a
tenant that already has a HEAD or commit records, when the range reaches
the current hour, or when the start is before 2020-01-01. Before each
batch it fails the load if the catalog HEAD has appeared (a fold ran
during the load) or if the batch's
event times reach past its hour plusmax_ingest_lag; a failed strided
load is not resumable. The load report names the synthetic hours.
ravel-cli catalog fold --snapshot-part-max-entries <n>overrides the
snapshot part cap for one fold, so a small cap gives hour-ranged parts.- Ingest holds at the head while the flush backlog is over a bound
(ADR-3718, issues #3720 and #3756). A background detector reads each ingest
router's queued flush count and the routers' deferred-flush-trigger counts
every 100 ms. The node counts as congested while any one router's queued
flush count is above that router's bound and at least 1 flush trigger was
deferred across all routers over the last full second. Neither signal alone
counts: a healthy node at its queue cap defers triggers, and a node with
many tenants can sit above the bound with no backlog. A router's bound is its
shard count times--max-queued-flushes(32 at the defaults), unless
--ingest-hold-queued-flushessets one bound for every router. While it is
congested, every OTLP and Remote Write request waits at the head, right after
the in-flight permit and before the tenant is resolved, for up to
--ingest-hold-max-wait(default1s, at most20s, the listener-join
share of the default--shutdown-timeout). A request whose wait ends with
the congestion cleared is admitted; one
still congested at the max wait gets 429 withRetry-After: 1on HTTP and
RESOURCE_EXHAUSTEDon gRPC. An OTAP stream holds once per
BatchArrowRecordsbatch, after that batch's own in-flight permit; its
tenant was resolved when the stream opened, and a refused batch ends the
stream. Held requests are counted onravel_ingest_head_hold_total{mode, outcome}and timed on the histogramravel_ingest_head_hold_seconds{mode};
the gaugeravel_ingest_head_congested{mode}reads 1 while the hold is on.
--ingest-hold-max-wait 0turns the hold off and spawns no detector task.
On GKE at a fixed 160k log lines/s offered on 8 cores, the hold kept two
overloaded nodes at 141k to 150k lines/s stored, where unheld collapses
settled at about 112k.
It does not make a node that locked into overload recover (#3718 comment
6610502055). - The MCP server serves
ravel_analyze_timeseries(epic #1374). It
runschange_pointorsummaryover each series of a PromQL range result
and answers one row per series, through the same service method as
POST /api/v1/analytics, so admission and theanalyticsaudit record are
the route's (analyze_timeseries_summary_is_exact_and_matches_the_http_route).
A change point lands where the series changes
(analyze_timeseries_places_a_change_point_where_it_was_built). Both ops
are reported as heuristics,accuracy.exact: falsewith the method named,
and every row carries the minimum point count and whether the series was
downsampled;change_pointbucket-averages a series over 2,000 points and
summarynever downsamples
(analyze_timeseries_reports_downsampling_and_summary_never_does). A series
under the minimum is a row markedtoo_few_points, not a failed call
(analyze_timeseries_reports_too_few_points_per_row). The range arguments
are checked by the same function asravel_query_promql's, before anything
is read, and both tools refuse the same inputs with the same message
(both_tools_refuse_the_same_bad_ranges_before_the_backend,
analyze_timeseries_refuses_bad_ranges_before_the_store). A result that
skipped part of its coverage is refused asunavailable, with a next step
to read the partial series throughravel_query_promql.max_rowscounts
series; the scan budgets and anevidence_refare refused as
unsupported. - The MCP server serves
ravel_search_logs(epic #1374), the last of the
nine tools. Typed predicates (eq,ne,in,exists) on typed attribute
columns and an indexedtrace_id, ahas_wordbody search, a minimum
severity and a trace id compile to oneSELECToverlogs, with every
caller value rendered as a typed literal (text single-quoted, bytes as
X'...')
(caller_values_reach_the_statement_only_as_literals,
search_logs_filters_and_pages_a_logs_tenant). A predicate key that is
neither a typed attribute column nortrace_idwithtrace_idindexed is
refused before any statement runs, an indexed key that is not a typed
attribute column included, with a next step namingravel_describe_data
(an_unknown_key_is_refused_before_the_statement_runs,
an_indexed_key_other_than_trace_id_is_refused_unless_declared). Results
order and page by(ts, observed_ts), and a page never ends inside a group
of rows equal on that pair (a_group_straddling_the_cap_is_not_split); a
group larger than the page comes backok_boundedwith no cursor
(a_group_larger_than_max_rows_is_bounded_with_no_cursor). Rows with the
same attribute key set form a group, and the attributes the whole group
shares are listed once in__ravel_group_attrs
(shared_attributes_are_hoisted_once_per_group). The tool's two columns,
__ravel_groupand__ravel_group_attrs, cannot collide with a declared
attribute column namedgrouporgroup_attrs
(search_logs_group_columns_cannot_collide_with_a_declared_column). An
evidence_refis refused asunsupported. All nine tools are now served
and none is only catalogued: 0.29.0 served five, and this release adds
ravel_query_sql,ravel_query_promql,ravel_analyze_timeseriesand
ravel_search_logs, and the span tree inravel_get_trace, whose logs leg
(include_logs) is still deferred.
Changed
- The MCP tools reference is generated from the tool catalog. Each tool's
title, description, annotations and arguments (JSON type and whether
required) indocs/reference/mcp.mdnow come from the catalog the server
advertises intools/list, and a test fails when the page drifts from it.
The reference and the guide now state, tool by tool, how anevidence_ref
argument is handled. - MetricsBench runs Ravel at fetch concurrency 32 (issue #2821).
deploy/metricsbench/ravel.ymlsets--fetch-concurrency 32in place of
the derived default,max(8, 2 x cores), which is 8 in the benchmark's
2-CPU container. It is disclosed in the README's settings list and in an
ADR-0927 amendment. On real S3 the cardinality read-back is bound by GETs
in flight. On the folded 1,000,000-series tenant, its/api/v1/series
listing took 264 to 277 s at 8, past the 120 s deadline, and 71 to 74 s at
32, with the same GETs and bytes. Ravel's own derivation is unchanged. - Cached log string columns hand SQL their values without a copy (issue
#3231). With the decoded-column cache on (it is off by default:
--decoded-cache-max-bytesdefaults to 0), each cached string column keeps
one Arrow values array over its own buffers, built once. A scan served
from the cache, or the scan that admits the entry, now reuses that array
instead of copying
every cell: a plain column whose block no filter narrowed and whose rows
are all present also skips the per-row key loop, and a block's own
dictionary skips its per-scan values copy. Results are unchanged. The
cache's memory reservation now lasts until the last Arrow buffer over an
entry's bytes is gone, so the memory pressure evictor no longer counts as
freed an entry a query result still holds. Each cached string column that
can carry the array is charged 512 bytes more when admitted. A ClickBench
run at an 8 GB cache measured 20% more evictions and 4% more misses per
round, which the counters attribute to the larger charge (#3326 comment
6607706943). Building the array counts
once per cache entry instrCellsValidated. An offer refused because the
column already carries a filled reservation (admitted concurrently under
another key, or evicted while something outside the cache still held it)
is counted in the newstats.decodedCache.filledCellRefusals. - The whole-segment fast path's serve-if-fits check stops early (issue
#3642). With the decoded cache on, aLIKE '%...%'scan whose decoded-set
estimate passes 0.95 of--decoded-cache-max-bytesstops reading segment
directories at that point, since the verdict can no longer change. Its
stats.decodedCache.deferredSetEstimateBytesis then the sum read so far,
a lower bound, and the newdeferredSetEstimatePartial: truesays so. A
scan that fits reads and reports exactly as before.
What's Changed
- feat(ravel-logseg): write STREAM_DIR and FIELD_DIR after PAGE_DIR by @pmoust in #3232
- perf(ravel-codec): decode integer pages in bulk by @pmoust in #3239
- feat(ravel-sql): prune logs blocks for an integer <> predicate by @pmoust in #3247
- fix(sql): keep string values charged while a downstream operator holds them by @pmoust in #3249
- fix(parquet): walk page headers before the decoder skips a corrupt list by @pmoust in #3258
- feat(server): export column-stats cache and fold drop metrics by @pmoust in #3259
- fix(load): derive the pipeline depth a quarter past the rows bound by @pmoust in #3141
- perf(sql): load column statistics only for the shapes the metadata paths answer by @pmoust in #3257
- Measure bbtree and hash-then-sort against std maps on two hot paths by @pmoust in #3264
- docs(adr-3217): withdraw decision 2 for the competing entry by @pmoust in #3255
- feat(server): size the column-stats cache from the memory budget by @pmoust in #3260
- fix(parquet): serve whole column chunks only in the scan phase by @pmoust in #3265
- Measure hash-then-collect postings on stream-sorted rows by @pmoust in #3268
- feat(ingest): expose the RLOG row-group size as a loader flag by @pmoust in #3267
- perf(catalog): cache column statistics per part and per dictionary by @pmoust in #3272
- feat(logseg): place declared columns first in the RLOG chunk order by @pmoust in #3277
- perf(codec): reuse a per-thread zstd context on read-path decodes by @pmoust in #3278
- fix(parquet): report a decoder error while scanning a file as Corrupt by @pmoust in #3266
- perf(catalog): stream the column-statistics decode one segment at a time by @pmoust in #3279
- fix(cli): run the depth flush with an explicit --target-bytes by @pmoust in #3282
- feat(metricsbench): capture each comparator's effective version and engine by @pmoust in #3286
- perf(sql): cast group keys back to dictionaries above the TopK by @pmoust in #3287
- fix(cli): scale the load ack deadline with the flush target by @pmoust in #3290
- fix(catalog): degrade a refused column-stats reservation to a scan by @pmoust in #3291
- docs(adr-0927): amend the MetricsBench contract before the first portable run by @pmoust in #3284
- chore(metricsbench): bump the Mimir comparator to 3.2.3 by @pmoust in #3285
- feat(bench): anchor and stream the metricsbench ingest replay by @pmoust in #3288
- feat(difftest): bound every PrometheusClient query by a complete-operation deadline by @pmoust in #3292
- feat(sql): push LIKE '%literal%' on a declared str column as Contains by @pmoust in #3293
- fix(ravel-sql): keep partitioned hash join inputs co-partitioned by @pmoust in #3294
- fix(guards): anchor AI attribution trailers in authorship guard by @manulpatel in #3256
- chore(metricsbench): freeze comparator ingest windows and query limits by @pmoust in #3296
- feat(ravel-bench): move MetricsBench workload and corpus to version 2 by @pmoust in #3297
- chore(release): prepare 0.27.0 by @pmoust in #3302
- chore(metricsbench): bound Mimir query memory at 2 GiB by @pmoust in #3305
- feat(bench): require the MetricsBench anchor and record stream provenance by @pmoust in #3298
- feat(bench): classify MetricsBench outcomes into the seven statuses by @pmoust in #3303
- chore(deps): clear the zstd advisories cargo deny reports by @pmoust in #3310
- feat(server): export the catalog's decoded-cache bytes by @pmoust in #3307
- feat(bench): add MetricsBench report schema version 3 and validate by @pmoust in #3315
- perf(logseg): cut the contains kernel's per-row work outside the search by @pmoust in #3308
- perf(ravel-catalog): keep each cached L0 record's derivation by @pmoust in #3311
- perf(query): place only the filter sections the reader reads by @pmoust in #3318
- perf(sql): lower skip-partial aggregation probe ratio to 0.1 by @pmoust in #3322
- docs: state the probe and filter-section bounds as the code applies them by @pmoust in #3324
- perf(cache): widen the S3-FIFO ghost window to 30% of the cache by @pmoust in #3323
- docs: correct ghost-window figures and stale ADR-0046 references by @pmoust in #3325
- feat(metricsbench): deploy Ravel in the portable lane at the comparators' envelope by @pmoust in #3309
- feat(bench): send stale markers, native histograms and Ravel's row over Remote Write by @pmoust in #3314
- docs(adr): amend ADR-0927 for warnings, Mimir limits, band 6 and acks by @pmoust in #3316
- docs: add ADR-3331 Ravel Agents durable agent runtime by @pmoust in #3332
- feat(catalog): bound the decoded caches' held bytes by @pmoust in #3365
- docs(adr): propose ADR-3326, a decoded column-block cache by @pmoust in #3329
- feat(sdk): add the TypeScript Agents SDK workspace skeleton by @pmoust in #3366
- docs(metricsbench): size the max-samples limits by what each one counts by @pmoust in #3328
- feat(server): add --max-series and --max-samples query ceilings by @pmoust in #3333
- feat(bench): read each receiver's accepted set back and enforce band 6 by @pmoust in #3327
- docs: name the region cost and the column id rule for chunk order by @pmoust in #3367
- test: pin chunk order reads through POSTINGS, NumRange and records by @pmoust in #3369
- feat(server): charge catalog decodes to the process memory budget by @pmoust in #3319
- feat(bench): run the PromQL corpus over HTTP against every engine by @pmoust in #3368
- docs: bring two #3244 changelog entries in line with the server wiring by @pmoust in #3371
- fix(cli): bound --target-bytes and give every target its allowance by @pmoust in #3372
- test(tla): add the agents protocol area by @pmoust in #3375
- feat(metricsbench): freeze Ravel's per-query ceilings in the portable lane by @pmoust in #3377
- feat(cli): warn about --chunk-order names no mapping key carries by @pmoust in #3378
- feat(bench): collapse the full-range corpus entry under corpus version 3 by @pmoust in #3374
- feat(bench): retry an unavailable oracle query before voiding the run by @pmoust in #3379
- fix(cli): name a clamped derived target and reword the target bound by @pmoust in #3381
- feat(metricsbench): drive a profile end to end and stamp what ran by @pmoust in #3384
- feat(bench): write a diagnostics sidecar with ULP gaps and oracle retries by @pmoust in #3385
- feat(agents): add ravel-agents-core contracts and skeleton crates by @pmoust in #3383
- feat(bench): let the ingest run skip its in-process row by @pmoust in #3386
- feat(metricsbench): run the large profiles without the in-process row by @pmoust in #3387
- feat(metricsbench): write the query diagnostics sidecar on every run by @pmoust in #3388
- feat(agents): add the ravel-agents-store publication skeleton by @pmoust in #3389
- test(agents-sim): add the reference machine, history checker and scheduler by @pmoust in #3390
- fix(metricsbench): repeat a short read-back until the receiver settles by @pmoust in #3391
- docs(adr): amend ADR-3326 so admission keeps a headroom margin by @pmoust in #3380
- feat(sdk): add the TypeScript SDK core for Ravel Agents by @pmoust in #3392
- feat(server): add the agents feature seam and --agents flags by @pmoust in #3394
- docs(tla): add the agents traceability table by @pmoust in #3396
- docs(adr): amend ADR-3326 so the evictor frees shortfall plus margin by @pmoust in #3395
- feat(agents-runtime): add the turn coordinator over AgentStore by @pmoust in #3397
- docs(agents): add the Ravel Agents docs, key rows and IAM grants, rename the MCP guide by @pmoust in #3400
- feat(metricsbench): keep replay requests in flight per series shard by @pmoust in #3398
- test(ravel-server): re-pin mcp_e2e completed-call cost counters by @pmoust in #3402
- feat(metricsbench): bound summation residue with ULP tolerances by @pmoust in #3405
- feat(agents-sim): milestone 1 and 2 simulation schedules by @pmoust in #3406
- docs(adr): accept ADR-3326 after Stage 0 and record the entry decision by @pmoust in #3407
- chore(deps): bump pulldown-cmark to 0.13.5 for RUSTSEC-2026-0414 by @pmoust in #3409
- ci: add agents, mcp and TypeScript SDK gate lanes by @pmoust in #3403
- feat(server): serve the agents client and worker routes under --agents by @pmoust in #3411
- feat(agents-runtime): journal tool steps and coordinate their effects by @pmoust in #3410
- fix(metricsbench): equal nofile limits, a read-back that fits 1M series by @pmoust in #3414
- perf(logseg): decode plain string pages into one buffer plus offsets by @pmoust in #3413
- feat(tla): model the agents effect journal and one agent end to end by @pmoust in #3418
- test(agents-sim): simulate steps, effects and reconcile by @pmoust in #3420
- feat(sdk): run the agents SDK end to end against ravel-server by @pmoust in #3417
- feat(agents-runtime): fire timers from HEAD and sweep wake hints by @pmoust in #3419
- fix(metricsbench): read back in day windows and keep failed reports by @pmoust in #3423
- feat(ravel-server): serve the agents step, effect and reconcile routes by @pmoust in #3422
- refactor(logseg): hold every decoded column form behind an Arc by @pmoust in #3424
- ci(metricsbench): run the nightly on a Ravel built from its own commit by @pmoust in #3426
- feat(agents-sim): run every seed batch nightly against fresh windows by @pmoust in #3427
- docs(tla): measure and band every agents configuration by @pmoust in #3428
- test(logseg): make the chunk-order prune comparison prune partially by @pmoust in #3429
- feat(sdk): scaffold a starter agent project and generate client types by @pmoust in #3430
- fix(cli): state the target bound as absolute and warn on string resource keys by @pmoust in #3432
- feat(agents): add ravel-agents-client and ravel-cli agents commands by @pmoust in #3440
- feat(agents): serve timer, sweep, event resume and inspector routes by @pmoust in #3436
- feat(metricsbench): run the stack's object store on s3s-fs by @pmoust in #3441
- fix(metricsbench): pull the AWS CLI from Docker Hub by @pmoust in #3447
- test(agents): check the store on RustFS and add an agents chaos lane by @pmoust in #3445
- feat(query): add the ADR-3326 decoded column-block cache, off by default by @pmoust in #3439
- feat(mcp): serve the Ravel Agents tools over MCP by @pmoust in #3446
- feat(agents): page HEAD indexes into bounded immutable objects by @pmoust in #3450
- test(agents-core): seed the kernel sequence proptest by @pmoust in #3455
- docs(adr): run the MetricsBench Ravel row at a 500 ms flush delay by @pmoust in #3449
- feat(metricsbench): run the Ravel row at a 500 ms flush delay by @pmoust in #3462
- docs(adr): add ADR-3456 native GCS object store backend by @pmoust in #3457
- test(metricsbench): pin the stated S3 request budget to its derivation by @pmoust in #3464
- feat(agents): report how close each agent is to its lifetime caps by @pmoust in #3465
- fix(query): keep the ghost credit when a decoded-cache admission is refused by @pmoust in #3466
- docs(adr): keep cache-served columns off the tenant ceiling by @pmoust in #3461
- feat(agents): project committed agent history into logs and traces by @pmoust in #3469
- feat(agents-sim): measure agents cost per message, turn and pass by @pmoust in #3470
- feat(object-store): add GcsStore, a read-write GCS backend by @pmoust in #3467
- fix(query): hold one copy per series in a series listing by @pmoust in #3471
- feat(server): run ravel-server on a GCS bucket with --store gcs by @pmoust in #3472
- feat(cli): accept --store gcs by @pmoust in #3474
- feat(server): run ravel-server on a GCS bucket with --store gcs by @pmoust in #3477
- feat(agents): ship agents in the published image with an operator guide by @pmoust in #3480
- docs(readme): add a Ravel Agents section by @pmoust in #3489
- chore(release): prepare 0.28.0 by @pmoust in #3488
- docs(readme): name 0.28.0 as the pin in the Agents section by @pmoust in #3491
- docs(adr): add ADR-3492 ingest CPU per log line by @pmoust in #3493
- feat(logseg): choose RLOG page encodings by estimate on request by @pmoust in #3500
- feat(codec): tokenize without per-token allocation by @pmoust in #3501
- perf(otlp): move values out of the logs request in normalize by @pmoust in #3502
- test(sql): attribute the unfolded logs count poll's GETs by @pmoust in #3503
- feat(clickbench): invalidate kit runs whose boxes differ in speed by @pmoust in #3504
- test(server): hold dead-endpoint ports so parallel tests cannot bind them by @pmoust in #3505
- feat(logseg): encode a block's column pages on a caller's executor by @pmoust in #3508
- feat(ingest): cut per-record work on the OTLP logs ingest path by @pmoust in #3509
- feat(ingest): seal logs flush column pages in parallel on the write gate by @pmoust in #3512
- docs: add ADR-3513 per-tenant record cache byte budget flag by @pmoust in #3514
- feat(sql): compact only each batch's top k rows under a TopK by @pmoust in #3516
- feat(catalog): add per-tenant record cache byte budget flag by @pmoust in #3518
- feat(sql): compile a constant regexp_replace pattern once per session by @pmoust in #3520
- perf(logseg): index block pages per column chunk in PageDir by @pmoust in #3524
- fix(query): skip the catalog of an object whose series a listing saw by @pmoust in #3507
- fix(logseg): validate plain Str columns once at decode by @pmoust in #3515
- fix(metricsbench): keep measuring every row after a read-back fails by @pmoust in #3438
- docs(adr): make LIKE-deferred columns cacheable behind frequency-aware admission by @pmoust in #3526
- fix(sql): keep NULL dictionary entries NULL in GROUP BY keys by @pmoust in #3534
- feat(logseg): refuse decoded-cache admissions that would evict a more used entry by @pmoust in #3536
- fix(agents): destroy in the runtime and null state before first turn by @pmoust in #3538
- test(logseg): pin the admission gate's production-shaped traces by @pmoust in #3542
- feat(ingest): time logs flush and strict-ack phases on /metrics by @pmoust in #3537
- fix(agents): hand over only acknowledged reconcile writes by @pmoust in #3543
- feat(sdk): record agent steps and effects on the server by @pmoust in #3544
- fix(catalog): charge the fold's previous snapshot part decode by @pmoust in #3546
- perf(logseg): build the page index per chunk on first lookup by @pmoust in #3549
- revert: drop the decoded-cache frequency admission gate by @pmoust in #3550
- fix(query): keep a listing's seen set under a shared half of the budget by @pmoust in #3551
- fix(catalog): refuse a previous snapshot part naming another tenant by @pmoust in #3553
- feat(agents): add the POST /api/v1/agents/destroy route by @pmoust in #3554
- feat(agents): add AgentsClient::destroy and ravel-cli agents destroy by @pmoust in #3545
- fix(metricsbench): say an oracle failure ends the run at query by @pmoust in #3555
- docs: add a runbook entry for a fold the memory budget refuses by @pmoust in #3558
- feat(sdk): kill worker A as a real process in kill-and-recover by @pmoust in #3560
- docs(adr): record the ADR-3492 baseline as built and the next cuts by @pmoust in #3559
- docs(adr): amend ADR-3331 to serve destroy over HTTP by @pmoust in #3527
- fix(server): set TCP_NODELAY on every accepted connection by @pmoust in #3567
- test(sql): cover every TopK pre-filter key type in the exactness property test by @pmoust in #3566
- fix(guards): let only a collidable PR make the dispatch guard refuse by @pmoust in #3570
- perf(logseg): build a chunk's page index only on its second lookup by @pmoust in #3571
- docs(adrs): serve LIKE-deferred columns only when a scan fits by @pmoust in #3562
- fix(sdk): abort a turn whose lease renewals fail past the lease by @pmoust in #3581
- fix(agents-client): print a refused request's safe ids by @pmoust in #3582
- feat(metricsbench): run s3s-fs 0.17.0-ravel.2, which sets TCP_NODELAY by @pmoust in #3573
- docs: add ADR-3541 for a subject on the principal in audit records by @pmoust in #3574
- perf(logseg): build the OTLP flush batch from rows by reference by @pmoust in #3585
- feat(tenant-resolve): give Principal an optional bounded subject by @pmoust in #3584
- docs(adrs): keep a zero-copy Arrow values array per cached Str column by @pmoust in #3587
- feat(sql): accept a table-qualified min_commit_token over Flight SQL by @pmoust in #3589
- fix(sql): refuse at mint a Flight ticket its surface refuses at decode by @pmoust in #3593
- feat(ravel-server): record the principal subject on agents audit records by @pmoust in #3595
- fix(catalog): check the tenant of every snapshot part read by @pmoust in #3592
- fix(catalog): charge snapshot part reads to the memory budget by @pmoust in #3596
- feat(audit): name the caller's subject on query audit records by @pmoust in #3597
- feat(sql): refuse table-name shadowing and run logs LIKE as logs-only by @pmoust in #3599
- fix(sql): report the TopK pre-filter's row bound and lost order by @pmoust in #3591
- test: assert the quietest window in the bloom and tokenizer alloc pins by @pmoust in #3601
- feat(auth): read the mTLS subject from --mtls-subject-header by @pmoust in #3600
- docs: sweep stale one-table and Flight statements after ADR-2904 by @pmoust in #3602
- fix(sql): close the carried multi-table review nits by @pmoust in #3604
- perf(logseg): hash internally keyed encode maps with foldhash by @pmoust in #3603
- feat(tools): add bin/agent-cli for the lookups sessions repeat by @pmoust in #3583
- fix(codec): hash bloom staging sets with SipHash again by @pmoust in #3610
- Nudge sessions toward agent-cli and measure its use by @pmoust in #3613
- feat(segment): prune the sparse chunked catalog decode by equality by @pmoust in #3615
- feat(mcp): split cursor redemption around the resolve by @pmoust in #3617
- fix(maintain): cut RSPAN L1 parts before a KEY_IDX bucket overflows by @pmoust in #3618
- fix(sql): enforce scan budgets on logs, spans, alerts and audit by @pmoust in #3619
- feat(sql): report the pre-reserve-ahead peak in the SQL stats by @pmoust in #3621
- feat(server): add QueryService::describe_data by @pmoust in #3624
- feat(sql): add refuse_several_tables, the MCP SQL one-table check by @pmoust in #3622
- fix(sql): refuse lock clauses and SELECT INTO in the read-only gate by @pmoust in #3623
- feat(mcp): return an unfinished SQL draft and its cursor pins by @pmoust in #3627
- feat(sql): add BucketedFinalAggregateExec behind a config field by @pmoust in #3628
- feat(logseg): serve LIKE-deferred columns when the scan fits the cache by @pmoust in #3630
- feat(metricsbench): add a real-S3 lane to run.sh by @pmoust in #3632
- feat(image): compile the MCP server into the published image by @pmoust in #3631
- test(sql): stop using the TopK as its own oracle over both zero signs by @pmoust in #3634
- fix(server,sql): bill a cancelled exemplars or SQL call for every attempt by @pmoust in #3636
- feat(mcp): serve ravel_explain_query and ravel_find_labels by @pmoust in #3635
- fix(server): hand /metrics the audit counters, not the pipeline by @pmoust in #3637
- feat(sql): cut each bucket to its exact top k in the bucketed final by @pmoust in #3640
- fix(tools): refuse a fleet review with no structured result by @pmoust in #3647
- perf(otlp): encode log stream attributes once per scope by @pmoust in #3650
- feat(operator): serve MCP on query pods from RavelCluster by @pmoust in #3649
- perf(logseg): stop regrowing per-row column vectors on the flush by @pmoust in #3656
- feat(mcp): serve ravel_describe_data with keyset cursor paging by @pmoust in #3651
- fix(mcp): pin a digest of the declared column set in cursors by @pmoust in #3661
- fix(deps): bump pulldown-cmark to 0.13.6 for RUSTSEC-2026-0433 by @pmoust in #3667
- docs: add ADR-3565 bucketed final aggregate for fixed-width keys by @pmoust in #3606
- fix(ingest): back off provisioning reads after a failure by @pmoust in #3665
- build(metricsbench): run s3s-fs ravel.3 on the portable stack by @pmoust in #3668
- fix(metricsbench): give every engine 8 GiB and lift VM's series cap by @pmoust in #3669
- fix(sql): memoise the fast-path decoded-set estimate per segment by @pmoust in #3660
- feat(ingest): default to eight flush slots per shard by @pmoust in #3673
- fix(server): fail ravel_describe_data on a failed metadata read by @pmoust in #3672
- feat(ingest): make the flush object backstop multiple configurable by @pmoust in #3677
- fix(mcp): report describe_data's committed row count as an upper bound by @pmoust in #3678
- fix(sql): split the SQL request clock into resolve and caller instants by @pmoust in #3680
- ci: build coverage without debug info and let a PR opt in by @pmoust in #3675
- fix(sql): page exactly across a float zero and encode resume values by @pmoust in #3684
- feat(metricsbench): count Ravel's objects by kind after ingest by @pmoust in #3686
- feat(bench): add --readback-only to metricsbench_ingest by @pmoust in #3688
- feat(mcp): serve ravel_query_sql with keyset and grouped paging by @pmoust in #3691
- feat(metricsbench): read Ravel back on a folded catalog by @pmoust in #3697
- chore(release): prepare 0.29.0 by @pmoust in #3696
- docs: add ADR-3692 selective investigations across logs and traces by @pmoust in #3698
- fix(metricsbench): run the folded read-back after a memory failure by @pmoust in #3716
- feat(mcp): serve ravel_query_promql by @pmoust in #3715
- docs: add ADR-3718 refuse ingest before the node's cores saturate by @pmoust in #3719
- fix(metricsbench): start ravel-server alone after the fold by @pmoust in #3723
- feat(cli): load batches into a range of synthetic ingest hours by @pmoust in #3726
- feat(mcp): serve ravel_analyze_timeseries by @pmoust in #3724
- feat(mcp): serve ravel_search_logs by @pmoust in #3728
- feat(ingest): refuse ingest before the node's cores saturate by @pmoust in #3729
- feat(mcp): report the span tree in ravel_get_trace by @pmoust in #3732
- test(server): pin MCP protocol, escape routes and audit rows by @pmoust in #3735
- fix(sql): check resume tuple types and pin page planner claims by @pmoust in #3736
- fix(sql): stop the fast-path fit check once the estimate is over the cap by @pmoust in #3742
- fix(metricsbench): raise Mimir's gRPC message ceilings to 512 MiB by @pmoust in #3743
- docs(mcp): render the tool reference from the catalog by @pmoust in #3746
- test(mcp): add the adversarial suite for the MCP tools by @pmoust in #3738
- feat(bench): add the MCP agent evaluation corpus and runner by @pmoust in #3748
- fix(mcp): escape every caller string in the compact text block by @pmoust in #3757
- fix(sql): give aggregate and window order terms no cursor by @pmoust in #3759
- fix(metricsbench): run Ravel at fetch concurrency 32 by @pmoust in #3758
- perf(sql): keep a zero-copy values array on each cached string column by @pmoust in #3761
- fix(mcp): report a partial PromQL answer as inexact by @pmoust in #3762
- fix(query): fold PromQL sample runs as each object completes by @pmoust in #3763
- fix(mcp): measure the row-cap fold inside the byte fit by @pmoust in #3765
- feat(ingest): hold at the head while the flush backlog is over a bound by @pmoust in #3760
- fix(catalog): refuse declared keys with the reserved _ravel prefix by @pmoust in #3766
New Contributors
- @manulpatel made their first contribution in #3256
Full Changelog: v0.26.0...v0.30.0
Downloads
The attached binaries were extracted unmodified from the signed container
images this release published, so each one is byte-identical to the file inside
the image you can pull. They are glibc-dynamic, built against Debian 12
(the runtime image is gcr.io/distroless/cc-debian12), so they need glibc 2.36
or newer. On an older distribution, run the container image instead:
docker pull ghcr.io/nofireai/ravel-server:0.30.0One binary per platform, named <binary>-<os>-<arch>:
ravel-server-linux-amd64,ravel-server-linux-arm64ravel-cli-linux-amd64,ravel-cli-linux-arm64ravel-operator-linux-amd64,ravel-operator-linux-arm64ravel-ingest-router-linux-amd64,ravel-ingest-router-linux-arm64
Verifying the downloads
SHA256SUMS covers every attached binary and symbol archive. Check the files
you downloaded, then verify SHA256SUMS itself against the signature and
certificate beside it. The signature is keyless, bound to this release
workflow's identity, so there is no key to obtain:
sha256sum --check --ignore-missing SHA256SUMS
cosign verify-blob \
--certificate SHA256SUMS.pem \
--signature SHA256SUMS.sig \
--certificate-identity 'https://github.com/NOFireAI/ravel/.github/workflows/publish-images.yml@refs/tags/v0.30.0' \
--certificate-oidc-issuer 'https://token.actions.githubusercontent.com' \
SHA256SUMSDebug symbols
The shipped binaries are stripped of debug info but keep their symbol table,
so a backtrace resolves function names with nothing extra installed. Line
numbers come from the matching symbols archive. Each binary records the
basename of its symbol file, so unpacking the archive next to the binary is
enough and no symbol-file step is needed:
tar -xzf ravel-debug-symbols-linux-amd64.tar.gz
gdb ./ravel-server-linux-amd64gdb looks for ravel-server.debug in the same directory (and in
.debug/), which is the name inside the archive. Do not rename the extracted
.debug files.