From 49a0a9d0473f3de398a7c39a2a37195cd21858de Mon Sep 17 00:00:00 2001 From: "shoney.arickathil" Date: Sun, 30 Aug 2026 11:57:57 +0200 Subject: [PATCH] fix(db2-delta): refuse resident:keys with no WO_DATA at runtime MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - loader stopped refusing durable:true+resident:keys once UPDATE landed; nothing replaced it at runtime - rows for such a table live only in the WAL, so every read failed with a misleading "no such row" instead of naming the problem - main.c now refuses at startup, names the class, exit(2) - residency-accept.sh gains a leg: refuses without WO_DATA, still runs with it — verified failing before the fix, passing after Co-Authored-By: Claude Opus 5 (1M context) (cherry picked from commit 3ea6d6452f260d45f92045c0f300fa49faa1d810) --- runtime/src/main.c | 25 +++++++++++++++++++++++++ scripts/residency-accept.sh | 24 ++++++++++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/runtime/src/main.c b/runtime/src/main.c index a2bd783..ebeebeb 100644 --- a/runtime/src/main.c +++ b/runtime/src/main.c @@ -219,6 +219,31 @@ int main(int argc, char **argv) { VM.rt.db = &DB; DB.rt = &VM.rt; /* databasev2 2 (5c): the loop a borrow reads the WAL through */ const char *data_dir = getenv("WO_DATA"); + if (!data_dir || !data_dir[0]) { + /* databasev2 3: the loader used to refuse `resident: keys` outright; + * now it is accepted because UPDATE landed, but every row still + * lives in the log, not RAM — refuse the same way the loader's own + * durable:false+resident:keys refusal does, rather than let reads + * silently misbehave with no WAL to fold from. */ + for (uint32_t i = 0; i < mod.class_cnt; i++) { + if (!(mod.classes[i].flags & WO_CLASSF_RESIDENT_KEYS)) continue; + const char *cname = "?"; + int cnlen = 1; + uint32_t k = mod.classes[i].name; + if (k < mod.const_cnt && mod.consts[k].s) { + cname = mod.consts[k].s->data; + cnlen = (int)mod.consts[k].s->len; + } + fprintf(stderr, + "wovm: `%.*s` is declared `resident: keys` — its rows live only " + "in the write-ahead log, so it cannot run without WO_DATA.\n", + cnlen, cname); + wo_db_destroy(&DB); + wo_vm_destroy(&VM); + wo_module_free(&mod); + return 2; + } + } if (data_dir && data_dir[0]) { char wal_path[512]; snprintf(wal_path, sizeof wal_path, "%s/shard-0.wal", data_dir); diff --git a/scripts/residency-accept.sh b/scripts/residency-accept.sh index e8f90fb..c8e6d39 100755 --- a/scripts/residency-accept.sh +++ b/scripts/residency-accept.sh @@ -170,6 +170,30 @@ else bad "the doc example compiles" "see $LOG" fi +# ---- 6. resident:keys refuses to run without WO_DATA ----------------------- +# CRITICAL 1: the loader stopped refusing durable:true + resident:keys once +# UPDATE landed, and nothing replaced that refusal at runtime — a table +# declared this way ran with no WAL to fold its rows from, misreporting +# every read as "no such row" instead of naming the real problem. +printf '@table(name: "items", index: [k], durable: true, resident: keys)\nclass Items { k: Text }\nfn main() -> Int { insert Items { k: "a" }; return 0; }\n' > "$WORK/reskeys.wo" +if "$WOC" --emit "$WORK/reskeys.wo" -o "$WORK/reskeys.wob" 2>"$WORK/e"; then + out="$("$WOVM" "$WORK/reskeys.wob" 2>&1)"; rc=$? + [[ $rc -eq 2 ]] \ + && ok "resident:keys without WO_DATA exits 2" \ + || bad "resident:keys without WO_DATA exits 2" "exit=$rc" + grep -q 'Items' <<<"$out" \ + && ok "resident:keys refusal names the offending class" \ + || bad "resident:keys refusal names the class" "got: $out" + mkdir -p "$WORK/d6" + WO_DATA="$WORK/d6" "$WOVM" "$WORK/reskeys.wob" >/dev/null 2>&1 + rc2=$? + [[ $rc2 -eq 0 ]] \ + && ok "resident:keys WITH WO_DATA still runs" \ + || bad "resident:keys with WO_DATA runs" "exit=$rc2" +else + bad "resident:keys refusal fixture compiles" "$(head -1 "$WORK/e")" +fi + echo echo "residency-accept: $((pass + fail)) checks, $fail failures" [[ $fail -eq 0 ]] || exit 1