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