writeonce/docs/examples/db-actor/README.md
shoney.arickathil 62d29d6a77 docs(24): T10 closeout — stories done, board, graph, ledger, CODE-LOGIC
Iteration 24 closes, absorbing 31 and 34. No code in this commit.

- stories 24, 31, 34 -> `status: done`, each with a landing banner. 24's
  records the gate numbers and BOTH disclosed deviations: monitor takes
  three arguments (the caller may be `main`, which has no mailbox) and a
  v1 `call` reply is a typed scalar (which is what let the agreement be
  checked at compile time, WO-E226). 31's notes it landed INSIDE 24 and
  that a fifth mechanism it never anticipated came out of proving the
  gate — the drain guarantee (40). 34's names the gap it did NOT close:
  still no RNG, so CSRF/sessions stay blocked
- board: in-progress row cleared, marker doc deleted (convention), the
  standup entry in the six-question shape, chain note — next link is
  databasev2 4 (io_uring group-commit, chain 5)
- graph: PUBSUB2 (pub/sub + WebSockets, "rejected until here") -> done
- porch ledger: a WebSocket/pub-sub row added; the cancellation row now
  says what it actually waits on rather than repeating "the arc"; the
  README's "no WebSockets/SSE" limitation was stale — WebSockets are
  supported, SSE and chunked encoding are not
- CODE-LOGIC: runtime/src gains the actor-lifecycle section (call, death,
  the cap counter's sender/home-thread split, the monitor walk, the timer
  list), the drain guarantee, and the digest section; docs/examples/chat
  gains its own — actor topology, WHY two actors per connection, fd
  ownership, and the shutdown choreography

Battery after the doc edits: wovm-test 36 suites 0 fail, woc-test exit 0,
oop-e2e 119/0, chat 11/0, web-app 46/0, linkcheck clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-28 00:06:31 +02:00

3.4 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.

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.