writeonce/docs/superpowers
shoney.arickathil 18a56f24ec feat(db): wo_wal_read_row_at — materialise a row from a log offset
Task 5b of docs/superpowers/plans/2026-08-26-table-residency.md, whose Task 5
is now split 5a-5d (plan updated in this commit).

- the offset twin of wo_row_read (table.c:721): same out-gate contract —
  every value handed back is a FRESH VM allocation — but resolved from a file
  position instead of the id hash
- fits entirely in wal.c because everything it needs was already public:
  scan_record and dec_val are local, and wo_val_decode_vm / wo_db_val_free
  are exported at table.h:183-187. Two decode stages, since the record and
  the VM speak different dialects: dec_val -> engine slots -> VM copies, with
  the engine slots freed as scratch on every path
- ZERO storage change. Nothing calls it yet; that is the point of separating
  it from 5c, so the read path can be proven before the slabs are touched
- refuses rather than guessing, each case distinguishable: no intact record
  at the offset, a malformed header, a decode failure, trailing bytes, and a
  REMOVE tombstone. That last one matters most — handing a tombstone back as
  a row would read a deleted row as live

Tested by deep field comparison, not by "it parsed": 24 rows with a nil Text
every third row, each read back BY OFFSET and compared field by field,
including the string bytes. Plus all three refusal paths — tombstone, a
mid-record offset (the silent-wrong-row failure this guards), and past the
intact prefix.

The free-on-every-path claim is VERIFIED, not assumed: removing the free
produced 3 LeakSanitizer reports; restoring it returns to 0. Worth doing
because "ASan is clean" only means something if the harness would have
complained.

PLAN SPLIT: Task 5's storage half was written as if it were plumbing. Measured
instead: wo_row_ptr returns a db_row* into a slab with 11 call sites, table.c
has 37 slab references, db.c:105-181 scans slabs directly, enc_val serialises
FROM the slab, and no operation exists that drops a payload while keeping
index entries. Note this is the OPPOSITE half from the earlier retraction —
the record FORMAT needed nothing, the record STORAGE genuinely is deep. 5c
(id->offset map + drop-payload-keep-index) and 5d (rewiring the call sites,
scans, @unique/FK across the boundary) get their own write-ups.

Gates: test_wal 3654/0 (was 3428), all 18 runtime suites 0 fail under
ASan+UBSan, oop-e2e 119/0, residency 8/0, employee 8/0, db-actor 8/0.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 10:27:40 +02:00
..
plans feat(db): wo_wal_read_row_at — materialise a row from a log offset 2026-08-27 10:27:40 +02:00
specs docs: retract the row-encoding rewrite — the flat record format already exists 2026-08-26 23:00:18 +02:00