Skip to content

[P1][adapters] Loop OPFS reads; fail recovery on short read instead of truncating #23

Closed

Description

@cevheri

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

  • OPFS read loops until length or EOF.
  • Recovery throws on size mismatch after read.
  • DST short-read profile updated.
  • bun run gate green.
  • Changeset required.

Agent constraints

  • node-fs reads whole file today (readFileSync) — size mismatch unlikely but kernel check still valuable.

Verification

bun run gate

Activity

  1. cevheri commented on Jul 3, 2026

    @cevheri
    MemberAuthor

    Implemented and shipped in PR #43 (merge commit 69f7161), released as v0.2.0. The PR description's comma-separated Closes list only auto-closed the first reference — closing manually. For #39/#28-style partials: the shipped scope is done; the real-browser verification half of #39 continues under #10, and the typed-error contract (#28) shipped as LibreDbError with stable codes across kernel, adapters, lenses, and CLI.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions