Skip to content

a missing manifest or unreadable chunk on the upload walk is a loss, not a refusal - #20

Merged
LucaCappelletti94 merged 1 commit into
mainfrom
fix/no-manifest-is-a-loss
Sep 15, 2026
Merged

LucaCappelletti94 merged 1 commit into
mainfrom
fix/no-manifest-is-a-loss

Conversation

@LucaCappelletti94

@LucaCappelletti94 LucaCappelletti94 commented Sep 14, 2026

Copy link
Copy Markdown
Owner

A permanent upload failure used to have a single fate. Whatever the cause, the outbox entry was marked refused and left in place, so a file whose bytes this device no longer holds would sit unsent forever, retried on request only to fail the same way. The boot integrity pass already knew better and retired a file whose manifest had gone, so the two passes disagreed about the very same fact.

This gives the upload walk the rule we settled. A permanent outcome is now one of two events, chosen by where the fact came from. A refusal is an answer from the server, an over-budget or malformed request or a reply this client cannot read, and it keeps the entry marked so a later budget or quota change can still let the same upload through, and the application is told the upload was refused. A loss is a fact about this device, a manifest the outbox no longer holds or a chunk the store cannot read, and it retires the entry the way the boot pass does and tells the application the bytes are gone. A store that is merely unavailable stays ambiguous and keeps the entry, so being offline is never mistaken for a loss.

The change lives entirely in shared code, so the native client and the browser worker inherit the one fix, and the event an application receives for a given fact is the same whichever pass reaches it first. New tests cover the classification itself, the agreement between the two passes on a missing manifest, an unreadable chunk met on the walk, and that a genuine server refusal still keeps its entry in the outbox.

Summary by Sourcery

Distinguish upload refusals from local data loss so the outbox preserves recoverable failures and retires bytes that are gone.

Bug Fixes:

  • Classify permanent upload failures as server refusals or device data losses, retiring lost files and preserving refused entries.
  • Treat definitively unreadable chunks as lost while keeping entries when the underlying store is merely unavailable.
  • Align upload-walk handling with boot integrity scanning and emit the appropriate refusal or bytes-lost event.

Enhancements:

  • Expose a shared upload-attempt outcome classification for native and browser clients.

Tests:

  • Add coverage for failure classification, missing-manifest agreement between upload and boot passes, unreadable chunks, and retained server refusals.

@coderabbitai

coderabbitai Bot commented Sep 14, 2026

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: Repository: LucaCappelletti94/coderabbit/.coderabbit.yaml

Review profile: ASSERTIVE

Plan: Advanced

Run ID: baf237fe-f3fe-4039-b8e5-7d0ae826184f


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Sorry @LucaCappelletti94, you've used your own review budget of 250,000 diff characters for the last 7 days.

You can request another review in 8 hours and 45 minutes by commenting @sourcery-ai review. Upgrade to get a review now.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 14, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-14T20:09:22.097081Z 748578d PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@sourcery-ai

sourcery-ai Bot commented Sep 14, 2026

Copy link
Copy Markdown

Reviewer's Guide

The shared upload and worker paths now distinguish retryable conditions, server refusals, and device-side data loss: unavailable storage remains deferred, refusals stay marked for later retry, and missing manifests or definitively unreadable chunks are retired and reported as lost, with tests confirming consistent behavior across upload and boot integrity paths.

Sequence diagram for unreadable chunk handling

sequenceDiagram
    participant Walk as UploadWalk
    participant Store as ChunkStore
    participant Error as ContentError
    participant DB as OutboxDB
    participant App as Application

    Walk->>Store: read_chunk(hash)
    alt store unavailable
        Store-->>Walk: ambiguous error
        Walk->>Error: outcome()
        Error-->>Walk: Retry
        Walk->>App: UploadDeferred
    else chunk definitively unreadable
        Store-->>Walk: permanent read error
        Walk->>Error: outcome()
        Error-->>Walk: Lost
        Walk->>DB: retire(file_id)
        Walk->>App: BytesLost
    end
Loading

Flow diagram for upload failure classification

flowchart TD
    A[Upload attempt fails] --> B{"ContentError.outcome()"}
    B -->|Retry| C[Keep outbox entry]
    C --> D[Emit UploadDeferred]
    B -->|Refused| E[Mark entry refused]
    E --> F[Emit UploadRefused]
    B -->|Lost| G{"unreadable_chunks(file_id)"}
    G -->|None| C
    G -->|Some unreadable data| H[Retire outbox entry]
    H --> I[Emit BytesLost]
Loading

Flow diagram for unified loss handling across upload and boot passes

flowchart LR
    A[Missing manifest] --> B[AttemptOutcome::Lost]
    B --> C[Upload walk]
    B --> D[Boot integrity scan]
    C --> E["retire(file_id)"]
    D --> E
    E --> F[Bytes are gone]
Loading

File-Level Changes

Change Details Files
Classify upload failures by outcome source and apply distinct outbox and event behavior.
  • Added AttemptOutcome classification for retryable failures, server refusals, and device losses.
  • Kept retryable entries deferred, persisted refusals, and retired losses with BytesLost notifications.
  • Replaced the previous retryability API and re-exported the new outcome type.
crates/connetto-file-client/src/error.rs
crates/connetto-file-client/src/client.rs
crates/connetto-file-client/src/lib.rs
crates/connetto-file-client/src/worker.rs
Detect definite chunk loss during upload while preserving ambiguity for unavailable storage.
  • Introduced LostChunk for chunks that are definitively unreadable.
  • Used store-provided ambiguity checks to distinguish offline/unavailable storage from permanent data loss.
  • Propagated the distinction through encrypted stores.
crates/connetto-file-client/src/upload.rs
crates/connetto-file-core/src/encrypt.rs
Add coverage for consistent loss/refusal semantics across upload and integrity passes.
  • Tested classification of loss, refusal, and retry outcomes.
  • Verified missing manifests retire entries consistently in both passes.
  • Verified missing chunks emit BytesLost and are retired, while malformed grants remain refused in the outbox.
crates/connetto-file-client/src/error.rs
crates/connetto-file-client/src/worker.rs
crates/connetto-file-client/tests/it/negotiation.rs

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sonarqubecloud

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 748578d127

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +662 to +663
AttemptOutcome::Lost => {
retire(connection.conn(), file_id)?;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Revalidate lost chunks before retiring worker uploads

When ContentUpload::transfer returns LostChunk in the browser relay, finish_content_upload may already have serviced a concurrent HubEvent::Import and restored that chunk before this bookkeeping runs. This branch nevertheless dequeues the file and records it as retired without the fresh store read performed by ContentClient::flush_outbox and the integrity scan, permanently preventing a now-valid outbox entry from uploading. Recheck the current manifest and chunks before retiring this outcome.

Useful? React with 👍 / 👎.

@LucaCappelletti94
LucaCappelletti94 merged commit d6a9205 into main Sep 15, 2026
52 checks passed
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.

1 participant