writeonce/docs/examples/db-actor/README.md
shoney.arickathil 296efb52ed docs(db2-ephemeral): databasev2 2 closes — task 6a contract, forks 1–7, the README sweep
- story 02: `status: done`, `review_pending` (forks 1–7 auto-approved for
  autonomy); progress rows 6a ✅, 6b ➡ databasev2 5 Phase A, 7 `a310496`;
  5c/5d rows cite the `dev` hashes (the pre-merge ones were unreachable);
  task 6a's Given/When/Then met; Info records the seven forks (sentinel over
  `:memory:`, its rules, the refusal contract, startup-only, the budget
  leaves for 5, library-owned tables bind consumers, the v8 table bit);
  History keeps the first cut that refused every class-bearing program
- database/src/CODE-LOGIC.md: "Startup refusal + WO_EPHEMERAL" — contract,
  hatch, table bit, measured blast radius, deferred items, proof; the
  dispatcher paragraph no longer says a failed commit un-applies the row
  (fatal since databasev2 4 part A; WO_T_IO unreachable from a write path)
- residency spec + plan: task 6 items annotated with the 2026-09-09
  decisions; the byte budget marked moved to databasev2 5
- README, seven example READMEs and four guides carry the one-line rule
  (durable default refuses without WO_DATA; WO_EPHEMERAL=1; durable:
  false); shop's RAM-only command sets the sentinel

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
(cherry picked from commit 2c3531998124042fe736388e8b926abda3841194)
2026-09-15 01:16:24 +02:00

3.6 KiB

db-actor — the database reached from any shard

Status: shipped — arc stage 3's acceptance gate. Run it with just db-actor. Landed 2026-08-21 with the shard-fiber arc (story 8 · plan).

The database lives on one shard — the owner, shard 0 — because RAM is authoritative and a single writer is what makes the WAL's ordering meaningful. That is a problem the moment actors are placed round-robin across cores: a spawned actor has no say in which shard it lands on, and before stage 3 a worker-shard insert trapped WO_T_DB with "database engine not initialized".

Stage 3's answer is a transparent DB actor: statements issued off the owner shard marshal to it, execute there, and materialize their replies back. The program's source says nothing about any of it — the same insert and the same from … select work wherever the actor happens to run. This sample exists to prove exactly that, which is why its acceptance criterion is placement independence rather than any particular output.

What it does

Note is a @table with a secondary index on tag. Writer is an actor: each one inserts a row, then scans the whole table and prints the sum it sees. main spawns two writers, waits, then scans once itself.

With the default shard count, round-robin placement puts at least one writer off the owner shard — so one of those inserts and one of those scans travel the RPC path under test, and the other does not. Both must produce the same shape.

just db-actor                       # the gate
woc docs/examples/db-actor/         # or build it by hand
WO_SHARDS=1 ./docs/examples/db-actor/target/db-actor   # force the local path

What the gate proves

scripts/db-actor-accept.sh, 8 checks:

Check Why it is shaped that way
multi-shard, three rounds The writer lines are asserted as a set, not a sequence — scheduling decides their order, and pinning it would be testing the scheduler, not the RPC. The main line is exact.
both WO_IO backends forced The reply park has to be plane-independent: io_uring and epoll must give the same answer, or the parking is leaking into semantics.
single shard, byte-exact The local path is untouched by stage 3. Any drift here means the RPC changed the non-RPC case.
WO_DATA restart pair A worker's insert must commit on the owner's WAL before its ack, so a restart replays it: 2 rows, then 2+2 after a second run. This is the durability claim the RPC could most easily break. A program with any durable table (the default) refuses to start without WO_DATA; WO_EPHEMERAL=1 opts into a RAM-only run, @table(durable: false) opts a table out.

Run under wovm_asan and wovm_tsan as well — cross-shard message passing is exactly where a data race would hide, and TSan covering this demo is the one place it runs.

Read it for

  • How little the source knows. Compare Writer.receive here against the same statements in employee: identical. Transparency is the feature.
  • Why main waits. main is not an actor and has no mailbox, so it sleeps rather than awaiting — the gap iteration 31's call closes for actors and iteration 24 landed 2026-08-27.

Reasoning under the engine side: database/src/CODE-LOGIC.md. Contract: plan/oop-vm/04-db-binding.md.