docs(db2-7): contract + CODE-LOGIC — the WO_DATA file form

- docs/plan/oop-vm/04-db-binding.md, WAL section, "Where the log lives":
  WO_DATA is always a path; directory form (existing dir or trailing `/`
  → `<dir>/shard-0.wal`, pre-7 bytes incl. the `//`), file form (the path
  IS the log, created only under an existing parent), the two refusal
  lines verbatim, too-long refused not truncated, one file at any core
  count, compaction/migration temps + parent fsync derived from the log
  path never from WO_DATA, the two pinning tests named.
- database/src/CODE-LOGIC.md, `wal.c — durability`: the resolver's three
  codes and main.c's wording, why no mkdir -p, trailing slash on a missing
  dir kept as the pre-7 `cannot open` on purpose, `parent_dir_of` shared
  by the boot check and the post-rename fsync.
- Both paragraphs sit in regions untouched by the uncommitted 6a/12 doc
  work in the same files; no other docs touched (story/board are pm's).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
(cherry picked from commit f1985bae5d110ed773159393bd82c32b73bfb672)
This commit is contained in:
shoney.arickathil 2026-09-10 14:20:51 +02:00
parent 587991124d
commit ea66761c4e
2 changed files with 53 additions and 0 deletions

View file

@ -58,6 +58,28 @@ tear). The crash battery in `runtime/test/test_wal.c` is the module's
meaning proven: acked-over-a-pipe after commit, SIGKILL mid-stream, replay, meaning proven: acked-over-a-pipe after commit, SIGKILL mid-stream, replay,
zero acked-but-missing. zero acked-but-missing.
**Where the log lives (databasev2 7, 2026-09-10).** `wo_wal_resolve_data_path`
turns `WO_DATA` into the log path before main.c opens anything: an existing
directory or a trailing `/` → `<dir>/shard-0.wal` byte for byte (the pre-7
form, `//` after a trailing slash included); anything else IS the log —
opened if a regular file, created by `wo_wal_open` if absent. Two refusals,
exit 2, one stderr line each, worded in main.c from the resolver's codes:
`WO_WAL_PATH_NO_PARENT` (the parent comes back in `out`, so the line names
the path AND the parent; no `mkdir -p` — a typo must not plant a store
somewhere unexpected, the operator creates directories, the runtime never
does) and `WO_WAL_PATH_NOT_A_FILE` (fifo, socket, device).
`WO_WAL_PATH_TOO_LONG` refuses what the old 512-byte `snprintf` silently
truncated. A trailing slash on a MISSING directory is still the directory
form and still fails at `wo_wal_open` (`cannot open`), unchanged on purpose.
Nothing below main.c knows which form was used: compaction and migration
build `<log path>.compact` and fsync `parent_dir_of(log path)` — the same
static helper the resolver's parent check uses, so the directory checked at
boot is the directory synced after every rename. Tests:
`test_resolve_data_path` (every arm of the rule, fifo via `mkfifo`) and
`test_file_form_temps_beside_log` (a directory planted at `<file>.compact`
makes compaction and migration refuse with the log untouched; removed, both
succeed and the file is the only artifact beside a decoy sibling directory).
## db.c — statement executors (iteration 9, Task 3) ## db.c — statement executors (iteration 9, Task 3)
One dispatcher, the builtin contract (0 ok, else WO_T_* + msg). The engine One dispatcher, the builtin contract (0 ok, else WO_T_* + msg). The engine

View file

@ -107,6 +107,37 @@ intact record count and prefix end — the crash battery's verifier
(`runtime/test/test_wal.c`: five rounds of insert/commit/ack-over-pipe with (`runtime/test/test_wal.c`: five rounds of insert/commit/ack-over-pipe with
SIGKILL mid-stream; every acked row present and exact after replay). SIGKILL mid-stream; every acked row present and exact after replay).
**Where the log lives (databasev2 7, 2026-09-10).** `WO_DATA` is always a
path, never a sentinel (ephemerality is `WO_EPHEMERAL=1`, databasev2 2 task
6a), and it names the store in one of two forms, resolved by
`wo_wal_resolve_data_path` (`wal.{c,h}`) before anything is opened:
- **Directory form** — an existing directory, or any path ending in `/`: the
log is `<dir>/shard-0.wal`, byte for byte what every deployment and gate
before this iteration used (a trailing slash still yields the `//` the
pre-7 driver produced, and a trailing slash on a missing directory still
fails at open: `wovm: cannot open <dir>//shard-0.wal`, exit 2).
- **File form** — anything else: the path IS the log (`app.db`, `store.wo.db`
— the name is the operator's). An existing regular file is opened; an
absent path is created by `wo_wal_open`, but only when its parent directory
already exists. Two refusals, each exit 2 and ONE stderr line: a parent
that is not an existing directory — `wovm: WO_DATA=<path> — its parent
<parent> is not an existing directory; create it first (wovm never runs
mkdir -p).` — because a typo must not plant a store somewhere unexpected;
and a path that exists but is neither a regular file nor a directory
(fifo, socket, device). A result longer than the driver's path buffer is
refused as well, never truncated.
One file is the whole store at any core count (shard 0 is the only WAL
writer since arc stage 3). Compaction (databasev2 3) and schema migration
(databasev2 12) rewrite through `<log path>.compact` beside the log and
fsync the log's parent after the `rename` — both derive that from the log
path, never from `WO_DATA`, so the file form inherits their crash safety
unchanged; the boot-time parent check and the post-rename fsync share one
derivation (`parent_dir_of`). `WO_EPHEMERAL` set together with either form
refuses exactly as 6a says. Pinned by `runtime/test/test_wal.c`
`test_resolve_data_path` and `test_file_form_temps_beside_log`.
## Insert (Task 3) — builtin 61, `database/src/db.c` ## Insert (Task 3) — builtin 61, `database/src/db.c`
`insert Class { field: expr, … }` is a typed expression (statement position `insert Class { field: expr, … }` is a typed expression (statement position