Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
89eed55
v2.4.0 (#327)
singaraiona Jul 11, 2026
eb66eed
feat(query): support live inserts into parted tables
singaraiona Jul 14, 2026
bbdc419
fix(core): restore total-core -c semantics
singaraiona Jul 15, 2026
9961388
fix(parse) Fix nonstring if not defined
protocolstardust Jul 16, 2026
6cac6e8
Merge pull request #330 from protocolstardust/dev
protocolstardust Jul 16, 2026
f44ab81
Merge branch 'master' into dev
singaraiona Jul 16, 2026
49d8524
fix(store): surface FlushFileBuffers failure in journal SYNC mode (#335)
belowzeroff Jul 18, 2026
cb1c6cd
fix(hnsw): reject build dims whose vector count overflows size_t (#333)
belowzeroff Jul 18, 2026
7475949
fix(store): read full link sidecar to avoid wrong-symbol truncation (…
belowzeroff Jul 18, 2026
ebadd07
fix(hnsw): reject index files whose vector count overflows size_t (#332)
belowzeroff Jul 21, 2026
fa39d9d
fix(docs): remediate F-0001 F-0005 F-0007
singaraiona Jul 22, 2026
ffd784f
fix(docs): remediate CF-0001
singaraiona Jul 22, 2026
1f27e61
fix(docs): remediate CF-0002
singaraiona Jul 22, 2026
465e58e
chore(audit): plan CF-0003 ratification
singaraiona Jul 22, 2026
8f8033c
fix(docs): remediate CF-0003
singaraiona Jul 22, 2026
3ae79f5
feat(docs): redesign website and documentation
singaraiona Jul 22, 2026
5dc8265
Merge branch 'master' into dev
singaraiona Jul 22, 2026
4d2c8f3
fix(null): avoid f64 null casts to integers (#340)
belowzeroff Jul 23, 2026
4e49626
fix(expr): avoid null truthiness casts in fallback binary ops (#339)
belowzeroff Jul 23, 2026
a05886d
ci: make Rayforce audit PR comments best-effort
singaraiona Jul 23, 2026
6ca3d8a
ci: publish Rayforce audit comments from trusted workflow
singaraiona Jul 23, 2026
04d8f3d
ci: resolve fork PRs for audit commenter
singaraiona Jul 23, 2026
6acdc2c
perf: parallelize serial stages around group-by; unify binary-agg nul…
ser-vasilich Jul 23, 2026
a53d98e
Merge branch 'master' into dev
singaraiona Jul 23, 2026
260e018
fix(aggr): preserve slice nullability in binary groups
singaraiona Jul 23, 2026
492c5db
fix(expr): avoid f64 null cast in fallback idiv integer output (#344)
belowzeroff Jul 23, 2026
d250108
fix(group): avoid f64 null read cast in dense aggs (#343)
belowzeroff Jul 23, 2026
95f0a3d
fix(ipc): preserve boxed data list args (#346)
belowzeroff Jul 24, 2026
e10d1ab
fix(group): avoid f64 null cast in DA reads (#348)
belowzeroff Jul 24, 2026
842acc0
ci: use portable march for fuzz jobs (#347)
belowzeroff Jul 24, 2026
84cea17
chore(release): merge master into dev
singaraiona Jul 24, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ jobs:
# suite each run and cached so coverage accretes across runs.
fuzz-smoke:
runs-on: ubuntu-latest
env:
# Hosted runner `-march=native` can expose transient CPU features that
# clang promotes to -Werror diagnostics; fuzzing does not need native ISA.
RAY_MARCH: x86-64
steps:
- uses: actions/checkout@v4
- name: Cache fuzz corpus
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/nightly.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ jobs:
fuzz-long:
runs-on: ubuntu-latest
env:
# Keep clang fuzz builds off hosted-runner native ISA quirks.
RAY_MARCH: x86-64
strategy:
fail-fast: false
matrix:
Expand Down
16 changes: 16 additions & 0 deletions src/core/ipc.c
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,16 @@
#include "lang/internal.h"
#include "table/sym.h"

static void mark_ipc_literal_fallbacks(ray_t* obj) {
if (!obj || RAY_IS_ERR(obj) || obj->type != RAY_LIST)
return;

obj->attrs |= RAY_EVAL_LITERAL_FALLBACK;
ray_t** elems = (ray_t**)ray_data(obj);
for (int64_t i = 0; i < ray_len(obj); i++)
mark_ipc_literal_fallbacks(elems[i]);
}

/* ===== Compression (delta + RLE) ===== */

size_t ray_ipc_compress(const uint8_t* src, size_t len,
Expand Down Expand Up @@ -506,6 +516,12 @@ static ray_t* eval_payload_core(uint8_t* payload, size_t payload_len,

ray_t* result = NULL;
if (msg && !RAY_IS_ERR(msg)) {
if (msg->type == RAY_LIST) {
ray_t** elems = (ray_t**)ray_data(msg);
for (int64_t i = 1; i < ray_len(msg); i++)
mark_ipc_literal_fallbacks(elems[i]);
}

/* Dispatch through `.ipc.on.sync` / `.ipc.on.async` hook if
* installed; otherwise fall back to v1's inline-eval default.
* The hook receives the raw deserialised payload — same shape
Expand Down
31 changes: 29 additions & 2 deletions src/lang/eval.c
Original file line number Diff line number Diff line change
Expand Up @@ -3609,8 +3609,17 @@ ray_t* ray_eval(ray_t* obj) {

/* List: evaluate first element, dispatch by type */
ray_t** elems = (ray_t**)ray_data(obj);
int literal_fallback = (obj->attrs & RAY_EVAL_LITERAL_FALLBACK) != 0;
ray_t* head = ray_eval(elems[0]);
if (RAY_IS_ERR(head)) { ret = head; goto out; }
if (RAY_IS_ERR(head)) {
const char* code = ray_err_code(head);
if (literal_fallback && code && strcmp(code, "name") == 0) {
ray_error_free(head);
ray_retain(obj);
ret = obj; goto out;
}
ret = head; goto out;
}

/* A symbol in functional position names the function to apply. A name
* head (e.g. from `(quote f)`) already resolved during head-eval above;
Expand All @@ -3619,6 +3628,11 @@ ray_t* ray_eval(ray_t* obj) {
if (head->type == -RAY_SYM) {
ray_t* fn = ray_env_resolve(head->i64);
if (!fn) {
if (literal_fallback) {
ray_release(head);
ray_retain(obj);
ret = obj; goto out;
}
ray_t* ns = ray_sym_str(head->i64);
if (ns) {
ret = ray_error("name", "'%.*s' undefined",
Expand All @@ -3632,7 +3646,16 @@ ray_t* ray_eval(ray_t* obj) {
}
/* env_resolve may also surface a real error (e.g. nyi from a
* dotted-target deref) — propagate it directly. */
if (RAY_IS_ERR(fn)) { ray_release(head); ret = fn; goto out; }
if (RAY_IS_ERR(fn)) {
const char* code = ray_err_code(fn);
if (literal_fallback && code && strcmp(code, "name") == 0) {
ray_release(head);
ray_error_free(fn);
ray_retain(obj);
ret = obj; goto out;
}
ray_release(head); ret = fn; goto out;
}
ray_release(head);
head = fn; /* env_resolve hands back an owned ref; no extra retain. */
}
Expand Down Expand Up @@ -3827,6 +3850,10 @@ ray_t* ray_eval(ray_t* obj) {
default: {
int8_t head_type = head->type;
ray_release(head);
if (obj->attrs & RAY_EVAL_LITERAL_FALLBACK) {
ray_retain(obj);
ret = obj; goto out;
}
ret = ray_error("type", "eval: head of list is not callable, got %s", ray_type_name(head_type)); goto out;
}
}
Expand Down
5 changes: 5 additions & 0 deletions src/lang/eval.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,11 @@
#define ATTR_QUOTED 0x20 /* -RAY_SYM atom with this flag SET = quoted/literal symbol;
CLEAR (default) = name reference, resolved at eval. */

/* Internal eval flag for RAY_LIST values carried as nested IPC arguments.
* The evaluator still runs callable lists normally; this only makes a
* non-callable list head fall back to returning the list as data. */
#define RAY_EVAL_LITERAL_FALLBACK 0x04

/* Function type signatures */
typedef ray_t* (*ray_unary_fn)(ray_t*);
typedef ray_t* (*ray_binary_fn)(ray_t*, ray_t*);
Expand Down
2 changes: 1 addition & 1 deletion src/ops/group.c
Original file line number Diff line number Diff line change
Expand Up @@ -5685,7 +5685,7 @@ static inline void da_read_val(const void* ptr, int8_t type, uint8_t attrs,
int64_t r, double* out_f64, int64_t* out_i64) {
if (group_fp_type(type)) {
*out_f64 = group_fp_at(ptr, type, r);
*out_i64 = ray_cast_f64_to_i64_null(*out_f64);
*out_i64 = NULL_I64;
} else {
*out_i64 = read_col_i64(ptr, r, type, attrs);
*out_f64 = (double)*out_i64;
Expand Down
124 changes: 124 additions & 0 deletions test/test_ipc.c
Original file line number Diff line number Diff line change
Expand Up @@ -485,6 +485,129 @@ static test_result_t test_ipc_send_compiled_lambda_msg(void) {
PASS();
}

static test_result_t test_ipc_send_list_dict_arg_literal(void) {
ray_ipc_server_t srv;
ray_err_t err = ray_ipc_server_init(&srv, 0);
TEST_ASSERT_EQ_I(err, RAY_OK);

uint16_t port = get_listen_port(srv.listen_fd);
TEST_ASSERT((port) > (0), "port > 0");

ray_vm_t* srv_vm = make_server_vm();
TEST_ASSERT_NOT_NULL(srv_vm);

ipc_thread_ctx_t ctx = { .srv = &srv, .vm = srv_vm };
ray_thread_t tid;
ray_thread_create(&tid, server_thread_fn, &ctx);

int64_t h = ray_ipc_connect("127.0.0.1", port, NULL, NULL, 0);
TEST_ASSERT((h) >= (0), "h >= 0");

ray_t* setup_msg = ray_str("(set echo (fn [x] x))", strlen("(set echo (fn [x] x))"));
ray_t* setup = ray_ipc_send(h, setup_msg);
ray_release(setup_msg);
TEST_ASSERT_NOT_NULL(setup);
TEST_ASSERT_FALSE(RAY_IS_ERR(setup));
ray_release(setup);

ray_t* msg = ray_eval_str("(list 'echo (list {lo: 0 hi: 10} {lo: 10 hi: 20}))");
TEST_ASSERT_NOT_NULL(msg);
TEST_ASSERT_FALSE(RAY_IS_ERR(msg));

ray_t* result = ray_ipc_send(h, msg);
ray_release(msg);

TEST_ASSERT_NOT_NULL(result);
TEST_ASSERT_FALSE(RAY_IS_ERR(result));
TEST_ASSERT_EQ_I(result->type, RAY_LIST);
TEST_ASSERT_EQ_I(result->len, 2);

ray_t** rows = (ray_t**)ray_data(result);
TEST_ASSERT_EQ_I(rows[0]->type, RAY_DICT);
TEST_ASSERT_EQ_I(rows[1]->type, RAY_DICT);

ray_t* lo_key = ray_sym(ray_sym_intern("lo", 2));
ray_t* hi_key = ray_sym(ray_sym_intern("hi", 2));
TEST_ASSERT_NOT_NULL(lo_key);
TEST_ASSERT_NOT_NULL(hi_key);

ray_t* lo0 = ray_dict_get(rows[0], lo_key);
ray_t* hi0 = ray_dict_get(rows[0], hi_key);
ray_t* lo1 = ray_dict_get(rows[1], lo_key);
ray_t* hi1 = ray_dict_get(rows[1], hi_key);
TEST_ASSERT_NOT_NULL(lo0);
TEST_ASSERT_NOT_NULL(hi0);
TEST_ASSERT_NOT_NULL(lo1);
TEST_ASSERT_NOT_NULL(hi1);
TEST_ASSERT_EQ_I(lo0->type, -RAY_I64);
TEST_ASSERT_EQ_I(hi0->type, -RAY_I64);
TEST_ASSERT_EQ_I(lo1->type, -RAY_I64);
TEST_ASSERT_EQ_I(hi1->type, -RAY_I64);
TEST_ASSERT_EQ_I(lo0->i64, 0);
TEST_ASSERT_EQ_I(hi0->i64, 10);
TEST_ASSERT_EQ_I(lo1->i64, 10);
TEST_ASSERT_EQ_I(hi1->i64, 20);

ray_release(lo0);
ray_release(hi0);
ray_release(lo1);
ray_release(hi1);
ray_release(lo_key);
ray_release(hi_key);
ray_release(result);

ray_t* hook_msg = ray_str("(set .ipc.on.sync (fn [m] (eval m)))",
strlen("(set .ipc.on.sync (fn [m] (eval m)))"));
ray_t* hook = ray_ipc_send(h, hook_msg);
ray_release(hook_msg);
TEST_ASSERT_NOT_NULL(hook);
TEST_ASSERT_FALSE(RAY_IS_ERR(hook));
ray_release(hook);

ray_t* msg2 = ray_eval_str("(list 'echo (list {lo: 0 hi: 10} {lo: 10 hi: 20}))");
TEST_ASSERT_NOT_NULL(msg2);
TEST_ASSERT_FALSE(RAY_IS_ERR(msg2));

ray_t* result2 = ray_ipc_send(h, msg2);
ray_release(msg2);

TEST_ASSERT_NOT_NULL(result2);
TEST_ASSERT_FALSE(RAY_IS_ERR(result2));
TEST_ASSERT_EQ_I(result2->type, RAY_LIST);
TEST_ASSERT_EQ_I(result2->len, 2);

ray_t** hook_rows = (ray_t**)ray_data(result2);
TEST_ASSERT_EQ_I(hook_rows[0]->type, RAY_DICT);
TEST_ASSERT_EQ_I(hook_rows[1]->type, RAY_DICT);

ray_t* hook_lo_key = ray_sym(ray_sym_intern("lo", 2));
ray_t* hook_hi_key = ray_sym(ray_sym_intern("hi", 2));
TEST_ASSERT_NOT_NULL(hook_lo_key);
TEST_ASSERT_NOT_NULL(hook_hi_key);

ray_t* hook_lo0 = ray_dict_get(hook_rows[0], hook_lo_key);
ray_t* hook_hi1 = ray_dict_get(hook_rows[1], hook_hi_key);
TEST_ASSERT_NOT_NULL(hook_lo0);
TEST_ASSERT_NOT_NULL(hook_hi1);
TEST_ASSERT_EQ_I(hook_lo0->type, -RAY_I64);
TEST_ASSERT_EQ_I(hook_hi1->type, -RAY_I64);
TEST_ASSERT_EQ_I(hook_lo0->i64, 0);
TEST_ASSERT_EQ_I(hook_hi1->i64, 20);

ray_release(hook_lo0);
ray_release(hook_hi1);
ray_release(hook_lo_key);
ray_release(hook_hi_key);
ray_release(result2);

ray_ipc_close(h);
srv.running = false;
ray_thread_join(tid);
ray_ipc_server_destroy(&srv);
ray_sys_free(srv_vm);
PASS();
}

/* ---- test_ipc_connect_fail_no_server ------------------------------------ */
/*
* ray_ipc_connect to a port with nothing listening must return -1.
Expand Down Expand Up @@ -1945,6 +2068,7 @@ const test_entry_t ipc_entries[] = {
{ "ipc/eval_non_string_msg", test_ipc_eval_non_string_msg, ipc_setup, ipc_teardown },
{ "ipc/send_list_select_msg", test_ipc_send_list_select_msg, ipc_setup, ipc_teardown },
{ "ipc/send_compiled_lambda_msg", test_ipc_send_compiled_lambda_msg, ipc_setup, ipc_teardown },
{ "ipc/send_list_dict_arg_literal", test_ipc_send_list_dict_arg_literal, ipc_setup, ipc_teardown },
{ "ipc/connect_fail_no_server", test_ipc_connect_fail_no_server, ipc_setup, ipc_teardown },
{ "ipc/connect_auth_no_user", test_ipc_connect_auth_no_user, ipc_setup, ipc_teardown },
{ "ipc/close_invalid_handle", test_ipc_close_invalid_handle, ipc_setup, ipc_teardown },
Expand Down
85 changes: 85 additions & 0 deletions test/test_lang.c
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,90 @@ static test_result_t test_eval_symbol_head_applies(void) {
PASS();
}

static test_result_t test_eval_literal_fallback_preserves_boxed_data(void) {
ray_t* setup = ray_eval_str("(set echo (fn [x] x))");
TEST_ASSERT_NOT_NULL(setup);
TEST_ASSERT_FALSE(RAY_IS_ERR(setup));
ray_release(setup);

ray_t* msg = ray_eval_str("(list 'echo (list {lo: 0 hi: 10} {lo: 10 hi: 20}))");
TEST_ASSERT_NOT_NULL(msg);
TEST_ASSERT_FALSE(RAY_IS_ERR(msg));
TEST_ASSERT_EQ_I(msg->type, RAY_LIST);

ray_t** msg_elems = (ray_t**)ray_data(msg);
msg_elems[1]->attrs |= RAY_EVAL_LITERAL_FALLBACK;

ray_t* result = ray_eval(msg);
ray_release(msg);

TEST_ASSERT_NOT_NULL(result);
TEST_ASSERT_FALSE(RAY_IS_ERR(result));
TEST_ASSERT_EQ_I(result->type, RAY_LIST);
TEST_ASSERT_EQ_I(result->len, 2);

ray_t** rows = (ray_t**)ray_data(result);
TEST_ASSERT_EQ_I(rows[0]->type, RAY_DICT);
TEST_ASSERT_EQ_I(rows[1]->type, RAY_DICT);

ray_t* lo_key = ray_sym(ray_sym_intern("lo", 2));
ray_t* hi_key = ray_sym(ray_sym_intern("hi", 2));
TEST_ASSERT_NOT_NULL(lo_key);
TEST_ASSERT_NOT_NULL(hi_key);

ray_t* lo0 = ray_dict_get(rows[0], lo_key);
ray_t* hi1 = ray_dict_get(rows[1], hi_key);
TEST_ASSERT_NOT_NULL(lo0);
TEST_ASSERT_NOT_NULL(hi1);
TEST_ASSERT_EQ_I(lo0->type, -RAY_I64);
TEST_ASSERT_EQ_I(hi1->type, -RAY_I64);
TEST_ASSERT_EQ_I(lo0->i64, 0);
TEST_ASSERT_EQ_I(hi1->i64, 20);

ray_release(lo0);
ray_release(hi1);
ray_release(lo_key);
ray_release(hi_key);
ray_release(result);

ray_t* sym_msg = ray_eval_str("(list 'echo (list 'ipc_data_tag 1))");
TEST_ASSERT_NOT_NULL(sym_msg);
TEST_ASSERT_FALSE(RAY_IS_ERR(sym_msg));
TEST_ASSERT_EQ_I(sym_msg->type, RAY_LIST);

ray_t** sym_elems = (ray_t**)ray_data(sym_msg);
sym_elems[1]->attrs |= RAY_EVAL_LITERAL_FALLBACK;

ray_t* sym_result = ray_eval(sym_msg);
ray_release(sym_msg);
TEST_ASSERT_NOT_NULL(sym_result);
TEST_ASSERT_FALSE(RAY_IS_ERR(sym_result));
TEST_ASSERT_EQ_I(sym_result->type, RAY_LIST);
TEST_ASSERT_EQ_I(sym_result->len, 2);
ray_t** sym_rows = (ray_t**)ray_data(sym_result);
TEST_ASSERT_EQ_I(sym_rows[0]->type, -RAY_SYM);
TEST_ASSERT_EQ_I(sym_rows[1]->type, -RAY_I64);
TEST_ASSERT_EQ_I(sym_rows[1]->i64, 1);
ray_release(sym_result);

ray_t* expr_msg = ray_eval_str("(list 'echo (list + 1 2))");
TEST_ASSERT_NOT_NULL(expr_msg);
TEST_ASSERT_FALSE(RAY_IS_ERR(expr_msg));
TEST_ASSERT_EQ_I(expr_msg->type, RAY_LIST);

ray_t** expr_elems = (ray_t**)ray_data(expr_msg);
expr_elems[1]->attrs |= RAY_EVAL_LITERAL_FALLBACK;

ray_t* expr_result = ray_eval(expr_msg);
ray_release(expr_msg);
TEST_ASSERT_NOT_NULL(expr_result);
TEST_ASSERT_FALSE(RAY_IS_ERR(expr_result));
TEST_ASSERT_EQ_I(expr_result->type, -RAY_I64);
TEST_ASSERT_EQ_I(expr_result->i64, 3);
ray_release(expr_result);
PASS();
}

/* ---- Test: eval if true ---- */
static test_result_t test_eval_if_true(void) {
ray_t* result = ray_eval_str("(if true 1 2)");
Expand Down Expand Up @@ -8439,6 +8523,7 @@ const test_entry_t lang_entries[] = {
{ "lang/eval/cmp", test_eval_cmp, lang_setup, lang_teardown },
{ "lang/eval/set", test_eval_set, lang_setup, lang_teardown },
{ "lang/eval/symbol_head_applies", test_eval_symbol_head_applies, lang_setup, lang_teardown },
{ "lang/eval/literal_fallback_preserves_boxed_data", test_eval_literal_fallback_preserves_boxed_data, lang_setup, lang_teardown },
{ "lang/eval/if_true", test_eval_if_true, lang_setup, lang_teardown },
{ "lang/eval/if_false", test_eval_if_false, lang_setup, lang_teardown },
{ "lang/eval/let", test_eval_let, lang_setup, lang_teardown },
Expand Down
Loading