Summary
recover() treats any short read (fewer bytes than file.size()) as a torn tail and truncates. The OPFS adapter's read() performs one handle.read() call and does not loop to fill the buffer — short reads are legal per the OPFS API. A transient short read at reopen can destroy fsync'd commits. The DST short-read test currently asserts this data loss as acceptable — that expectation must flip.
Audit ID: B10 (High)
Wave: 2 (adapter + recovery; pairs with #10 OPFS E2E)
Root cause
OPFS adapter:
read(offset, length) {
const buffer = new Uint8Array(length);
const read = handle.read(buffer, { at: offset });
return buffer.subarray(0, read);
}
Kernel recovery:
const log = file.read(0, file.size());
// ...
if (end > log.length) break; // treats short read as torn
Append already loops (opfs.ts:64-67); read does not.
Proposed fix
1. OPFS adapter — loop reads (mirror append)
read(offset, length) {
const buffer = new Uint8Array(length);
let filled = 0;
while (filled < length) {
const n = handle.read(buffer.subarray(filled), { at: offset + filled });
if (n === 0) break;
filled += n;
}
return buffer.subarray(0, filled);
}
2. Kernel recover() — fail loud on short read
If log.length !== file.size() after read → throw (libredb: incomplete WAL read) instead of treating as torn tail.
Distinguish:
read returned < size → IO/read error (throw)
read returned == size but last record incomplete → torn tail (truncate)
3. Fix DST short-read test
Update src/sim/dst.ts / SimFS armShortRead expectations: short read during full-file read should error, not truncate commits.
Related issues
Acceptance criteria
Agent constraints
- node-fs reads whole file today (
readFileSync) — size mismatch unlikely but kernel check still valuable.
Verification
Summary
recover()treats any short read (fewer bytes thanfile.size()) as a torn tail and truncates. The OPFS adapter'sread()performs onehandle.read()call and does not loop to fill the buffer — short reads are legal per the OPFS API. A transient short read at reopen can destroy fsync'd commits. The DST short-read test currently asserts this data loss as acceptable — that expectation must flip.Audit ID: B10 (High)
Wave: 2 (adapter + recovery; pairs with #10 OPFS E2E)
Root cause
OPFS adapter:
Kernel recovery:
Append already loops (
opfs.ts:64-67); read does not.Proposed fix
1. OPFS adapter — loop reads (mirror append)
2. Kernel
recover()— fail loud on short readIf
log.length !== file.size()after read → throw (libredb: incomplete WAL read) instead of treating as torn tail.Distinguish:
readreturned< size→ IO/read error (throw)readreturned== sizebut last record incomplete → torn tail (truncate)3. Fix DST short-read test
Update
src/sim/dst.ts/ SimFSarmShortReadexpectations: short read during full-file read should error, not truncate commits.Related issues
Acceptance criteria
bun run gategreen.Agent constraints
readFileSync) — size mismatch unlikely but kernel check still valuable.Verification