Skip to content

Tolerate missing role on user - #388

Merged
kriszyp merged 1 commit into
v5.0from
tolerate-no-roles
Apr 20, 2026
Merged

Tolerate missing role on user#388
kriszyp merged 1 commit into
v5.0from
tolerate-no-roles

Conversation

@kriszyp

@kriszyp kriszyp commented Apr 20, 2026

Copy link
Copy Markdown
Member

No description provided.

@kriszyp
kriszyp marked this pull request as ready for review April 20, 2026 18:25
@kriszyp
kriszyp requested a review from a team as a code owner April 20, 2026 18:25

@heskew heskew left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems correct, but not caught early because user is (often) any typed? User interface has the role as optional.

@kriszyp
kriszyp merged commit 54e445b into v5.0 Apr 20, 2026
22 checks passed
@kriszyp
kriszyp deleted the tolerate-no-roles branch April 20, 2026 19:24
kriszyp added a commit that referenced this pull request Jun 20, 2026
…can skip it (#429)

A truncated/incomplete UNCOMPRESSED blob file at the source threw a plain Error with no `.code`,
so the sender forwarded errorCode=undefined and the receiver classified it transient — holding
the replication resume cursor forever (the same wedge #403 fixed for ENOENT, but for a truncated
blob instead of a missing one). Confirmed live on a 5.1.6 cluster: jjillCache copy stuck behind
"Blob is incomplete" while ENOENT blobs correctly advanced past.

Stamp ERR_BLOB_INCOMPLETE (via incompleteBlobError()) on the two writer-finished READ paths
(checkCompletion and the streaming reader) so isPermanentSourceBlobErrorCode (harper-pro) advances
the resume cursor past it; the diverged record is left for proactive backfill (#388/#418).

Scoped to avoid two data-loss traps:
- Only UNCOMPRESSED blobs are coded. The read paths compare the header's uncompressed `size`
  against raw on-disk bytes, so a COMPLETE compressed blob also trips the incomplete check —
  coding it would let replication skip + unlink a healthy blob.
- The sync msgpackr-encode path has no writer-finished guard (a mismatch there can be a mid-write
  blob), so it stays uncoded -> receiver holds and retries.
The read-timeout incompleteness also stays uncoded (transient).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
kriszyp added a commit that referenced this pull request Jun 22, 2026
…can skip it (#429)

A truncated/incomplete UNCOMPRESSED blob file at the source threw a plain Error with no `.code`,
so the sender forwarded errorCode=undefined and the receiver classified it transient — holding
the replication resume cursor forever (the same wedge #403 fixed for ENOENT, but for a truncated
blob instead of a missing one). Confirmed live on a 5.1.6 cluster: jjillCache copy stuck behind
"Blob is incomplete" while ENOENT blobs correctly advanced past.

Stamp ERR_BLOB_INCOMPLETE (via incompleteBlobError()) on the two writer-finished READ paths
(checkCompletion and the streaming reader) so isPermanentSourceBlobErrorCode (harper-pro) advances
the resume cursor past it; the diverged record is left for proactive backfill (#388/#418).

Scoped to avoid two data-loss traps:
- Only UNCOMPRESSED blobs are coded. The read paths compare the header's uncompressed `size`
  against raw on-disk bytes, so a COMPLETE compressed blob also trips the incomplete check —
  coding it would let replication skip + unlink a healthy blob.
- The sync msgpackr-encode path has no writer-finished guard (a mismatch there can be a mid-write
  blob), so it stays uncoded -> receiver holds and retries.
The read-timeout incompleteness also stays uncoded (transient).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
kriszyp added a commit that referenced this pull request Jun 23, 2026
…sweep (#1387)

* feat(blob): add findIncompleteBlobRefs generator and isBlobComplete for repair sweep

Exports three new functions used by the harper-pro blob repair operation:
- `findIncompleteBlobRefs(database, databaseName?)` — async generator that walks
  the primary store looking for records with HAS_BLOBS set whose blob files are
  missing, truncated, or errored. Throttles via STORAGE_BLOBCLEANUPSPEED.
- `isBlobComplete(blob)` — checks whether a FileBackedBlob's on-disk file is
  fully written (header size == file size, no ERROR_TYPE header).
- Re-exports existing `findBlobsInObject` and `isSaving` for the repair layer.

`isBlobComplete` reads only the 8-byte header (stat + openSync/readSync/closeSync)
rather than the whole file, so it's safe to call on large blobs during a sweep.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* test(blob): add unit tests for isBlobComplete and findIncompleteBlobRefs

Both exports were flagged by the automated review as untested. Tests cover:
- isBlobComplete: native Blob → true, unsaved FileBackedBlob → false,
  fully saved → true, deleted file (ENOENT) → false
- findIncompleteBlobRefs: yields record with missing blob file, skips
  record with complete blob

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(blob): wrap entry.value decode in decodeFromDatabase in findIncompleteBlobRefs

encodeBlobsWithFilePath resets currentStore to undefined in its finally block.
When findIncompleteBlobRefs subsequently accesses entry.value from a lazy getRange
iterator, the blob unpack extension reads currentStore (undefined) and throws
'No store specified'. The catch block swallowed this silently, so records with
incomplete blobs were never yielded.

Fix: wrap the findBlobsInObject(entry.value, ...) call in decodeFromDatabase so
currentStore is set to the table's rootStore before blob decoding. This is the
same pattern used by receiveBlobs and other callers that decode blobs outside
the normal encodeBlobsWithFilePath window.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: use entry.key (not entry.id) in findIncompleteBlobRefs and catch isBlobFileComplete errors

lmdb-js getRange yields entries as {key, value, version}; `entry.id` does not
exist, so `recordId: entry.id` was always undefined. Also adds inner try-catch
around isBlobFileComplete so a path-construction error (non-ENOENT) marks the
blob as incomplete rather than being silently swallowed by the outer catch.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* test(blob): cover truncated/error-stub isBlobComplete and HAS_BLOBS skip in findIncompleteBlobRefs

Extend coverage per PR review: isBlobComplete now also asserts the truncated
(header size != file size) and error-stub (ERROR_TYPE header) cases;
findIncompleteBlobRefs asserts a record without the HAS_BLOBS metadata flag is
not yielded.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(blob): correct isBlobFileComplete for compressed blobs

isBlobFileComplete compared the header size field against fileSize -
HEADER_SIZE. For a DEFLATE blob the header size is the *uncompressed*
length (writeBlobWithStream stores deflate.bytesWritten, the input byte
count), while the on-disk body is the *compressed* stream, so a
correctly-saved compressed blob was wrongly reported incomplete. This
made findIncompleteBlobRefs (the proactive repair sweep) flag every
compressed blob as a false repair candidate. (Codex review on #1387.)

For DEFLATE blobs, verify completeness by streaming the body through
inflate and comparing the decompressed length to the header size; a
truncated stream errors (Z_BUF_ERROR) or yields fewer bytes. Streaming
(vs inflateSync on the whole buffer) keeps memory bounded during the
sweep, which may touch many large blobs. The uncompressed path keeps the
existing body-length check; an explicit UNKNOWN_SIZE placeholder check
is added. isBlobFileComplete/isBlobComplete are now async to accommodate
the streamed inflate; findIncompleteBlobRefs collects candidate blobs
synchronously then awaits the checks.

Tests: a correctly-saved compressed blob now reports complete (fails
before this fix) and a truncated compressed blob still reports
incomplete.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(blob): code the confidently-incomplete blob error so replication can skip it (#429)

A truncated/incomplete UNCOMPRESSED blob file at the source threw a plain Error with no `.code`,
so the sender forwarded errorCode=undefined and the receiver classified it transient — holding
the replication resume cursor forever (the same wedge #403 fixed for ENOENT, but for a truncated
blob instead of a missing one). Confirmed live on a 5.1.6 cluster: jjillCache copy stuck behind
"Blob is incomplete" while ENOENT blobs correctly advanced past.

Stamp ERR_BLOB_INCOMPLETE (via incompleteBlobError()) on the two writer-finished READ paths
(checkCompletion and the streaming reader) so isPermanentSourceBlobErrorCode (harper-pro) advances
the resume cursor past it; the diverged record is left for proactive backfill (#388/#418).

Scoped to avoid two data-loss traps:
- Only UNCOMPRESSED blobs are coded. The read paths compare the header's uncompressed `size`
  against raw on-disk bytes, so a COMPLETE compressed blob also trips the incomplete check —
  coding it would let replication skip + unlink a healthy blob.
- The sync msgpackr-encode path has no writer-finished guard (a mismatch there can be a mid-write
  blob), so it stays uncoded -> receiver holds and retries.
The read-timeout incompleteness also stays uncoded (transient).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor(blob): make isBlobComplete async to drop Promise.resolve wrappers

Addresses cb1kenobi review comment on #1387.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Kris Zyp <kris@harperdb.io>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants