Task 4 of docs/superpowers/plans/2026-08-26-table-residency.md — the first behavioural change in the iteration. - db.c: one predicate, `table_is_durable`, gating the three EXISTING mutation sites. Kept as a function rather than an inlined condition so database/src/CODE-LOGIC.md's "nothing else may mutate storage" claim keeps holding — the choke points stayed three - the ack contract is untouched for durable tables: RAM applied, record staged, one commit before the ack, and a failed commit still removes the row - replay: a log holding records for a class the image now declares volatile is a real migration case, not corruption. apply_record returns -2 (distinct from -1), wo_wal_replay_ex reports the class id, and main.c names it and exits 2. `wo_wal_replay` stays as the NULL wrapper, so all 156 WAL unit checks are untouched - measured, not asserted: 50 inserts wrote 1500 WAL bytes into a durable table and ZERO into a volatile one. The file's SIZE proves nothing (it is fallocate'd to 1 MiB up front), so the gate measures the non-zero prefix BUG I INTRODUCED AND CAUGHT: the mismatch message first printed the class name with %s, but wo_str.data is `char data[]` with NO NUL terminator (obj.h) — a buffer over-read. Now %.*s with the explicit length, and re-verified under ASan. New gate `just residency` (8 checks), because everything above was otherwise a one-off manual measurement: restart behaviour, the zero-byte write path, the mismatch refusal (exit 2, names the class, NOT reported as corruption), and both compile-time refusals. Its own first run failed two checks for a bug in the script rather than the feature — `woc | grep` under `set -o pipefail` returns woc's exit 1 even when grep matches, since woc exits 1 whenever it reports diagnostics. Captures first now, with the reason noted inline. Also new: corpus run/table-volatile-inprocess pins that a volatile table is a FULL table in-process — same @unique enforcement, same index probe, same query surface. Only survival differs, and that is unobservable from inside one process. Gates: woc-test 557/0, 18 runtime suites 0 fail, cli_smoke OK, oop-e2e 119/0 (was 118), residency 8/0, employee 8/0, db-actor 8/0, site 21/0, ASan clean on the new replay path. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
138 lines
5.7 KiB
Bash
Executable file
138 lines
5.7 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
# scripts/residency-accept.sh — databasev2 2's gate: per-table storage.
|
|
#
|
|
# The corpus proves the in-process half (tests/corpus/run/table-volatile-inprocess,
|
|
# .../table-residency-legal, .../compile-fail/table-durable-ref-volatile). This
|
|
# script proves the half a single process cannot observe:
|
|
# * a volatile table is EMPTY after a restart while its durable sibling replays
|
|
# * volatile inserts write ZERO bytes to the WAL — measured, not asserted,
|
|
# because the file is fallocate'd to 1 MiB up front so its SIZE proves
|
|
# nothing; what is measured is the non-zero prefix actually written
|
|
# * a mode mismatch (log holds records for a table the source now declares
|
|
# volatile) REFUSES to start, exits 2, and names the class
|
|
# * the compile-time refusals still fire
|
|
set -uo pipefail
|
|
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
|
cd "$ROOT"
|
|
WOC="compiler/_build/default/bin/woc"
|
|
WOVM="runtime/wovm"
|
|
pass=0; fail=0
|
|
ok() { echo "ok $1"; pass=$((pass + 1)); }
|
|
bad() { echo "FAIL $1 -- $2"; fail=$((fail + 1)); }
|
|
|
|
if [[ ! -x "$WOC" || ! -x "$WOVM" ]]; then
|
|
echo "residency-accept: build woc and wovm first (just woc-build && just wovm-build)" >&2
|
|
exit 1
|
|
fi
|
|
|
|
WORK="$(mktemp -d "${TMPDIR:-/tmp}/residency-accept.XXXXXX")"
|
|
trap 'rm -rf "$WORK"' EXIT
|
|
|
|
# non-zero prefix of a fallocate'd WAL = bytes actually written
|
|
wal_bytes() {
|
|
python3 -c "import sys;d=open(sys.argv[1],'rb').read();print(len(d.rstrip(b'\x00')))" "$1"
|
|
}
|
|
|
|
# ---- 1. restart: durable replays, volatile does not -----------------------
|
|
cat > "$WORK/mix.wo" <<'EOF'
|
|
@table(name: "kept", index: [k])
|
|
class Kept { k: Text }
|
|
@table(name: "scratch", durable: false, index: [k])
|
|
class Scratch { k: Text }
|
|
fn main(args: multi Text) -> Int {
|
|
if len(args) > 0 and args[0] == "seed" {
|
|
insert Kept { k: "a" };
|
|
insert Scratch { k: "b" };
|
|
return 0;
|
|
}
|
|
let nk = 0;
|
|
for x in from r in Kept select r { nk = nk + 1; }
|
|
let ns = 0;
|
|
for x in from r in Scratch select r { ns = ns + 1; }
|
|
print("kept=${nk} scratch=${ns}");
|
|
return 0;
|
|
}
|
|
EOF
|
|
if "$WOC" --emit "$WORK/mix.wo" -o "$WORK/mix.wob" 2>"$WORK/e"; then
|
|
mkdir -p "$WORK/d1"
|
|
WO_DATA="$WORK/d1" "$WOVM" "$WORK/mix.wob" seed >/dev/null 2>&1
|
|
got="$(WO_DATA="$WORK/d1" "$WOVM" "$WORK/mix.wob" 2>&1)"
|
|
[[ "$got" == "kept=1 scratch=0" ]] \
|
|
&& ok "restart: durable row replays, volatile row is gone" \
|
|
|| bad "restart: durable replays, volatile gone" "got: $got"
|
|
else
|
|
bad "restart fixture compiles" "$(head -1 "$WORK/e")"
|
|
fi
|
|
|
|
# ---- 2. the write-path saving, measured ------------------------------------
|
|
cat > "$WORK/only.wo" <<'EOF'
|
|
@table(name: "dur", index: [k])
|
|
class Dur { k: Text }
|
|
@table(name: "vol", durable: false, index: [k])
|
|
class Vol { k: Text }
|
|
fn main(args: multi Text) -> Int {
|
|
let n = 0;
|
|
while n < 50 {
|
|
if args[0] == "dur" { insert Dur { k: "x" }; } else { insert Vol { k: "x" }; }
|
|
n = n + 1;
|
|
}
|
|
return 0;
|
|
}
|
|
EOF
|
|
if "$WOC" --emit "$WORK/only.wo" -o "$WORK/only.wob" 2>"$WORK/e"; then
|
|
for m in dur vol; do
|
|
mkdir -p "$WORK/w_$m"
|
|
WO_DATA="$WORK/w_$m" "$WOVM" "$WORK/only.wob" "$m" >/dev/null 2>&1
|
|
done
|
|
db="$(wal_bytes "$WORK/w_dur/shard-0.wal")"
|
|
vb="$(wal_bytes "$WORK/w_vol/shard-0.wal")"
|
|
[[ "$vb" -eq 0 ]] \
|
|
&& ok "50 volatile inserts write 0 WAL bytes (durable wrote $db)" \
|
|
|| bad "volatile inserts write nothing" "volatile wrote $vb bytes"
|
|
[[ "$db" -gt 0 ]] \
|
|
&& ok "50 durable inserts do write to the WAL ($db bytes)" \
|
|
|| bad "durable inserts still log" "durable wrote $db bytes"
|
|
else
|
|
bad "measurement fixture compiles" "$(head -1 "$WORK/e")"
|
|
fi
|
|
|
|
# ---- 3. mode mismatch refuses, exits 2, names the class --------------------
|
|
printf '@table(name: "orders", index: [k])\nclass Orders { k: Text }\nfn main() -> Int { insert Orders { k: "a" }; return 0; }\n' > "$WORK/wasdur.wo"
|
|
printf '@table(name: "orders", durable: false, index: [k])\nclass Orders { k: Text }\nfn main() -> Int { print("started"); return 0; }\n' > "$WORK/nowvol.wo"
|
|
if "$WOC" --emit "$WORK/wasdur.wo" -o "$WORK/a.wob" 2>/dev/null \
|
|
&& "$WOC" --emit "$WORK/nowvol.wo" -o "$WORK/b.wob" 2>/dev/null; then
|
|
mkdir -p "$WORK/d3"
|
|
WO_DATA="$WORK/d3" "$WOVM" "$WORK/a.wob" >/dev/null 2>&1
|
|
out="$(WO_DATA="$WORK/d3" "$WOVM" "$WORK/b.wob" 2>&1)"; rc=$?
|
|
[[ $rc -eq 2 ]] \
|
|
&& ok "mode mismatch exits 2 (refuses to start)" \
|
|
|| bad "mode mismatch exits 2" "exit=$rc"
|
|
grep -q 'Orders' <<<"$out" \
|
|
&& ok "mode mismatch names the offending class" \
|
|
|| bad "mode mismatch names the class" "got: $out"
|
|
grep -q 'corruption' <<<"$out" \
|
|
&& bad "mismatch is not reported as corruption" "got: $out" \
|
|
|| ok "mode mismatch is not misreported as corruption"
|
|
else
|
|
bad "mismatch fixtures compile" "compile failed"
|
|
fi
|
|
|
|
# ---- 4. the compile-time refusals still fire ------------------------------
|
|
printf '@table(name: "x", durable: false, resident: keys)\nclass X { k: Text }\nfn main() -> Int { return 0; }\n' > "$WORK/combo.wo"
|
|
# NOTE: capture, then grep. `woc | grep` under `set -o pipefail` returns
|
|
# woc's exit 1 (it reports diagnostics) even when grep matched, which made
|
|
# both of these checks fail while the compiler was behaving correctly.
|
|
combo_out="$("$WOC" "$WORK/combo.wo" 2>&1)"
|
|
grep -q 'WO-E102' <<<"$combo_out" \
|
|
&& ok "durable:false + resident:keys is WO-E102" \
|
|
|| bad "combination refused" "got: $combo_out"
|
|
|
|
printf '@table(name: "s", durable: false)\nclass S { t: Text }\n@table(name: "o")\nclass O { s: ref S }\nfn main() -> Int { return 0; }\n' > "$WORK/dref.wo"
|
|
dref_out="$("$WOC" "$WORK/dref.wo" 2>&1)"
|
|
grep -q 'WO-E224' <<<"$dref_out" \
|
|
&& ok "durable ref into a volatile table is WO-E224" \
|
|
|| bad "dangling ref refused" "got: $dref_out"
|
|
|
|
echo
|
|
echo "residency-accept: $((pass + fail)) checks, $fail failures"
|
|
[[ $fail -eq 0 ]] || exit 1
|