docs/RELIABILITY.md
RELIABILITY.md
Tranzfer Reliability Contract
A transfer is not fragile.
Once Tranzfer has successfully moved bytes, ordinary failure should not make the user move those bytes again.
If Tranzfer has already moved 220 GB of a 350 GB project, the default outcome after failure is:
continue from roughly 220 GB.
Not:
start from zero.
This document is the reliability contract for Tranzfer.
SOUL.md explains what kind of product Tranzfer is.
STACK.md explains which tools we use.
This file explains what must remain true when everything goes wrong.
Any code touching uploads, downloads, transfer persistence, multipart state, recovery, verification, cleanup, or resumability must preserve the invariants in this document.
Reliability is not a later hardening phase.
Reliability is the product.
1. The promise
Tranzfer exists to move enormous files between people without making them babysit transport.
The user is allowed to have an unreliable environment.
Their Wi-Fi can disappear. Their ISP can flap. Their router can reboot. Their laptop can sleep. Their browser can refresh. Their tab can close. Their browser can restart. Their machine can reboot. A signed URL can expire. A multipart part can fail. An API can briefly return an error. A renderer can crash. The UI can disappear. A transfer can be 63% complete when any of this happens.
Tranzfer is responsible for making ordinary failure boring.
The product should behave as though the transfer is a durable job that happens to be observed by a UI.
The UI is not the job.
The browser tab is not the job.
The Electron window is not the job.
The current JavaScript process is not the job.
The transfer is the job.
2. The prime directive
Never make the user upload bytes again if Tranzfer can prove those bytes already exist correctly on the remote side.
This does not mean blindly trusting stale local state.
It means:
- persist enough identity to recover the transfer;
- ask the remote system what actually exists;
- verify the local file is still the same file;
- reconcile the two;
- continue only the missing work.
If correctness and convenience conflict, correctness wins.
A resume that silently corrupts a file is worse than a restart.
3. Hierarchy of truth
For browser uploads:
| 1 | Cloudflare R2 multipart state |
| 2 | ↑ |
| 3 | │ remote truth |
| 4 | │ |
| 5 | IndexedDB durable session metadata |
| 6 | ↑ |
| 7 | │ local durable recovery state |
| 8 | │ |
| 9 | Uppy runtime |
| 10 | ↑ |
| 11 | │ current process/session |
| 12 | │ |
| 13 | Solid 2 UI |
For future desktop uploads:
| 1 | Cloudflare R2 multipart state |
| 2 | ↑ |
| 3 | SQLite durable transfer state |
| 4 | ↑ |
| 5 | Background transfer engine |
| 6 | ↑ |
| 7 | Electron + Solid UI |
The closer a layer is to the actual bytes, the more authoritative it is.
The UI must never become durable truth.
A signal, store, memo, query cache, or renderer state must never be the only place where critical recovery information exists.
4. Reliability invariants
These are product invariants, not implementation suggestions.
4.1 Remote completed parts are authoritative
If R2 reports that part 842 exists for the current multipart upload, that is stronger evidence than a stale local progress bar claiming only 841 parts completed.
Local progress can be wrong.
Remote multipart state is the source of truth for uploaded parts.
4.2 Durable metadata must be sufficient to recover
An in-progress transfer must persist enough information to reconstruct its identity after process loss.
Conceptually:
| 1 | type DurableUploadSession = { |
| 2 | transferId: string; |
| 3 | objectKey: string; |
| 4 | multipartUploadId: string; |
| 5 | |
| 6 | fileName: string; |
| 7 | fileSize: number; |
| 8 | lastModified: number | null; |
| 9 | fingerprint: string; |
| 10 | |
| 11 | chunkSize: number; |
| 12 | |
| 13 | createdAt: number; |
| 14 | updatedAt: number; |
| 15 | |
| 16 | status: "preparing" | "uploading" | "paused" | "recovering" | "finalizing"; |
| 17 | }; |
The shape may evolve.
The invariant does not.
4.3 Never resume against an unverified local file
If the user replaces Episode42.mov after 180 GB has already uploaded, Tranzfer must not blindly combine the old first 180 GB with the new remainder.
Before resuming after restoration, verify the local file using multiple signals:
| 1 | name |
| 2 | size |
| 3 | last modified time where meaningful |
| 4 | stable fingerprint |
Do not resume when identity is uncertain.
4.4 Filename is not identity
Two files can share a name, extension, or size.
Identity must be stronger than any one of those.
4.5 Retry only failed work
A failed part must not cause successful parts to be uploaded again.
A temporary signing failure must not reset the multipart upload.
A temporary API failure must not destroy the transfer.
4.6 Expired authorization is recoverable
Expired signed URLs or temporary credentials are expected.
The behavior should be:
| 1 | authorization expires |
| 2 | ↓ |
| 3 | obtain fresh authorization |
| 4 | ↓ |
| 5 | retry the affected operation |
Not:
| 1 | authorization expires |
| 2 | ↓ |
| 3 | restart 300 GB file |
4.7 Network disappearance is a pause
A lost connection is ordinary behavior.
The product should say:
Connection lost. Waiting to continue.
When connectivity returns, continue automatically wherever safe.
4.8 Completion is idempotent
This must be safe:
| 1 | all parts uploaded |
| 2 | ↓ |
| 3 | CompleteMultipartUpload sent |
| 4 | ↓ |
| 5 | remote object completes |
| 6 | ↓ |
| 7 | client crashes before response arrives |
| 8 | ↓ |
| 9 | client restarts |
Recovery must converge toward the same correct completed state.
4.9 Abort is explicit
Do not silently abort a valid multipart upload because:
| 1 | tab closed |
| 2 | window closed |
| 3 | renderer crashed |
| 4 | network disappeared |
Those are not cancellation.
Cancellation is a deliberate product action.
4.10 Recovery reconciles; it does not assume
Compare durable local state with remote multipart state.
Remote state wins disagreements about uploaded parts.
4.11 Transfers outlive processes
Browser runtime dies: transfer survives.
Renderer dies: transfer survives.
Desktop UI closes: transfer survives.
Machine reboots: transfer survives if the platform can reconnect to the same local file.
4.12 Progress reflects real work
Do not fake progress.
Do not report complete merely because the last part upload finished.
A transfer is complete only after remote completion and Tranzfer state safely reconcile.
4.13 Reliability degrades honestly
If automatic file access cannot be restored, ask the user to reselect the original file and continue.
Never pretend a guarantee exists when the platform cannot provide it.
5. What “resume” means
Do not use the word loosely.
Level 0 — retry
A single request failed. Retry it.
Level 1 — network resume
Connectivity disappears while the runtime remains alive. Continue when it returns.
Level 2 — runtime resume
Refresh or renderer reload. Reconstruct and continue.
Level 3 — browser-session resume
Browser/tab closes. Restore durable session metadata and recover file access where possible.
Level 4 — machine-reboot resume
The OS restarts.
On web:
| 1 | restore durable session |
| 2 | ↓ |
| 3 | restore persistent file handle if possible |
| 4 | ↓ |
| 5 | otherwise reselect same file |
| 6 | ↓ |
| 7 | verify identity |
| 8 | ↓ |
| 9 | ListParts |
| 10 | ↓ |
| 11 | resume missing work |
On desktop:
| 1 | restore local path |
| 2 | verify file |
| 3 | reconcile remote parts |
| 4 | continue |
Level 5 — unattended desktop resume
Future target:
| 1 | machine reboots |
| 2 | ↓ |
| 3 | background agent starts |
| 4 | ↓ |
| 5 | unfinished transfer found |
| 6 | ↓ |
| 7 | file still matches |
| 8 | ↓ |
| 9 | remote multipart state reconciled |
| 10 | ↓ |
| 11 | transfer continues without opening the UI |
Do not promise a level before we have actually tortured it.
6. Browser reliability contract
Browser uploads live under browser security constraints.
Do not pretend otherwise.
6.1 Durable browser state
Persist unfinished transfer metadata in IndexedDB or equivalent durable browser storage.
Persist:
| 1 | transfer identity |
| 2 | multipart upload identity |
| 3 | remote object key |
| 4 | file identity metadata |
| 5 | fingerprint |
| 6 | chunk policy |
| 7 | recovery status |
| 8 | timestamps |
| 9 | file-system handle when supported |
| 10 | schema version |
Solid signals are not persistence.
Uppy runtime state is not persistence.
Local variables are not persistence.
6.2 Persistent file handles
Where supported, store a file-system handle durably.
On restoration:
| 1 | restore handle |
| 2 | ↓ |
| 3 | check permission |
| 4 | ↓ |
| 5 | request permission through user gesture if required |
| 6 | ↓ |
| 7 | obtain File |
| 8 | ↓ |
| 9 | verify identity |
| 10 | ↓ |
| 11 | reconcile multipart state |
| 12 | ↓ |
| 13 | resume |
A permission prompt is not a transfer failure.
6.3 Reselect fallback
If file access cannot be restored:
| 1 | unfinished transfer detected |
| 2 | ↓ |
| 3 | show expected file |
| 4 | ↓ |
| 5 | user selects local file |
| 6 | ↓ |
| 7 | verify identity |
| 8 | ↓ |
| 9 | resume existing multipart upload |
The user should not restart merely because browser file access was lost.
6.4 Golden Retriever
Uppy Golden Retriever may be used as an extra recovery layer for:
| 1 | selected file recovery |
| 2 | runtime state restoration |
| 3 | accidental refresh |
| 4 | tab closure |
It is not the entire durability design.
Our own durable transfer identity must exist independently.
6.5 Capability tiers
Internally classify browser recovery capability.
Example:
| 1 | Tier A |
| 2 | persistent file handle available |
| 3 | strongest automatic recovery |
| 4 | |
| 5 | Tier B |
| 6 | durable session available |
| 7 | file must be reselected |
| 8 | |
| 9 | Tier C |
| 10 | limited browser APIs |
| 11 | best-effort recovery with explicit limitations |
Never silently downgrade.
7. Desktop reliability contract
The desktop app exists partly because native processes can provide stronger long-running guarantees.
7.1 Electron is the shell
Assume Electron unless product reality changes it.
Electron owns:
| 1 | window lifecycle |
| 2 | tray |
| 3 | menus |
| 4 | native notifications |
| 5 | updater |
| 6 | OS integration |
| 7 | IPC bridge |
Electron does not own the transfer engine.
7.2 Background transfer engine
Initial assumption:
| 1 | compiled Bun sidecar process |
Responsibilities:
| 1 | filesystem reads |
| 2 | multipart upload |
| 3 | durable queue |
| 4 | SQLite |
| 5 | retries/backoff |
| 6 | network recovery |
| 7 | bandwidth policy |
| 8 | sleep/wake recovery |
| 9 | remote reconciliation |
| 10 | finalization |
The renderer may disappear without affecting upload correctness.
7.3 Renderer independence
Ideal:
| 1 | user closes Tranzfer window |
| 2 | ↓ |
| 3 | renderer dies |
| 4 | ↓ |
| 5 | minimal Electron shell remains |
| 6 | ↓ |
| 7 | Bun uploader keeps transferring |
When the UI returns:
| 1 | renderer starts |
| 2 | ↓ |
| 3 | requests current snapshot |
| 4 | ↓ |
| 5 | projects daemon state |
7.4 Desktop durable database
Conceptually:
| 1 | uploads |
| 2 | ------- |
| 3 | id |
| 4 | transfer_id |
| 5 | local_path |
| 6 | file_name |
| 7 | file_size |
| 8 | last_modified |
| 9 | fingerprint |
| 10 | remote_key |
| 11 | multipart_upload_id |
| 12 | chunk_size |
| 13 | status |
| 14 | created_at |
| 15 | updated_at |
and optionally:
| 1 | parts |
| 2 | ----- |
| 3 | upload_id |
| 4 | part_number |
| 5 | etag |
| 6 | size |
| 7 | completed_at |
Exact schema may change.
The recovery capability may not.
7.5 Remote truth still wins
SQLite accelerates recovery.
R2 confirms reality.
7.6 Bun is not sacred
If profiling shows unacceptable RAM, CPU, battery use, missing OS behavior, or reliability problems, replace only the daemon boundary.
A future Rust daemon must not require rewriting the renderer, API, contracts, or transfer UX.
8. Multipart design
Multipart is the basis of giant-file resumability.
8.1 Huge files use multipart
Do not upload 300 GB as one enormous request.
8.2 Chunk size is derived
Never hardcode a chunk size that can exceed the provider's multipart part-count limit.
A reasonable starting policy may be approximately:
| 1 | large files ~64 MiB |
| 2 | hundreds of GB ~128 MiB |
| 3 | very large files ~256 MiB |
Benchmark before canonizing values.
8.3 Trade-off
Smaller parts:
| 1 | + less retransmission on failure |
| 2 | + finer resume granularity |
| 3 | - more requests |
| 4 | - more signing operations |
| 5 | - more bookkeeping |
Larger parts:
| 1 | + fewer requests |
| 2 | + less control-plane chatter |
| 3 | - more retransmission per failed part |
| 4 | - coarser recovery |
8.4 No presigning the whole future
Do not generate thousands of part URLs at transfer start.
Long-running transfers can outlive them.
Sign lazily or use another safe short-lived credential strategy.
8.5 Stable object identity
Once multipart creation fixes the remote object identity, resumed operations must continue against that same object.
8.6 ListParts is core infrastructure
Use it after uncertain interruption.
Use it when local and remote state may disagree.
Use it before deciding what to resend.
9. File identity and fingerprinting
Resuming the wrong file is corruption.
Treat file identity as a first-class domain concept.
Persist:
| 1 | name |
| 2 | size |
| 3 | lastModified when available |
| 4 | fingerprint |
| 5 | fingerprint algorithm version |
The fingerprint should detect accidental replacement without requiring a complete 350 GB reread before every resume.
A possible sampled strategy:
| 1 | hash( |
| 2 | metadata |
| 3 | + |
| 4 | beginning sample |
| 5 | + |
| 6 | deterministic interior samples |
| 7 | + |
| 8 | end sample |
| 9 | ) |
The exact algorithm may evolve.
A full checksum may be computed incrementally while bytes are already being read.
Do not repeatedly re-hash giant files without reason.
10. State machines, not boolean soup
Do not model upload lifecycle as unrelated booleans that can contradict one another.
Prefer explicit tagged states.
Example:
| 1 | type TransferState = |
| 2 | | { tag: "preparing" } |
| 3 | | { tag: "waiting-for-file-access" } |
| 4 | | { tag: "verifying-local-file" } |
| 5 | | { tag: "creating-multipart" } |
| 6 | | { tag: "uploading"; progress: Progress } |
| 7 | | { tag: "offline"; progress: Progress } |
| 8 | | { tag: "paused"; progress: Progress } |
| 9 | | { tag: "retrying"; attempt: number; progress: Progress } |
| 10 | | { tag: "recovering" } |
| 11 | | { tag: "reconciling-parts" } |
| 12 | | { tag: "finalizing" } |
| 13 | | { tag: "verifying-remote-object" } |
| 14 | | { tag: "complete"; transferId: string } |
| 15 | | { tag: "cancelled" } |
| 16 | | { tag: "failed"; error: TransferError }; |
Use Effect Match / tagged enums where exhaustive matching improves clarity.
11. Recovery state machine
Recovery is not one boolean.
Conceptually:
| 1 | load durable session |
| 2 | ↓ |
| 3 | does transfer still exist? |
| 4 | ↓ |
| 5 | can local file be opened? |
| 6 | ↓ |
| 7 | does local file still match? |
| 8 | ↓ |
| 9 | does multipart upload still exist? |
| 10 | ↓ |
| 11 | ListParts |
| 12 | ↓ |
| 13 | reconcile |
| 14 | ↓ |
| 15 | resume |
Possible states:
| 1 | type RecoveryState = |
| 2 | | { tag: "loading-session" } |
| 3 | | { tag: "needs-permission" } |
| 4 | | { tag: "needs-file-reselection" } |
| 5 | | { tag: "verifying-file" } |
| 6 | | { tag: "remote-upload-expired" } |
| 7 | | { tag: "reconciling-parts" } |
| 8 | | { tag: "ready-to-resume" } |
| 9 | | { tag: "resuming" } |
| 10 | | { tag: "file-changed" } |
| 11 | | { tag: "already-complete" } |
| 12 | | { tag: "failed"; error: RecoveryError }; |
Solid projects these states.
Solid does not invent them independently.
12. Failure taxonomy
Not all failures are equal.
Retryable transport failure
Examples:
| 1 | temporary network loss |
| 2 | timeout |
| 3 | 429 |
| 4 | 5xx |
| 5 | temporary storage issue |
Behavior:
| 1 | retry with bounded backoff |
| 2 | preserve session |
| 3 | do not restart whole file |
Authorization expiry
Behavior:
| 1 | refresh authorization |
| 2 | retry operation |
User attention required
Examples:
| 1 | file permission lost |
| 2 | file handle unavailable |
| 3 | local path moved |
| 4 | external drive disconnected |
Behavior:
| 1 | pause safely |
| 2 | preserve remote progress |
| 3 | ask for the smallest possible user action |
Local file mismatch
Behavior:
| 1 | do not resume |
| 2 | explain why |
| 3 | offer safe restart |
Remote multipart expired
Behavior:
| 1 | do not pretend resume remains possible |
| 2 | explain clearly |
| 3 | start fresh only through explicit product logic |
Finalization uncertainty
Behavior:
| 1 | inspect remote object |
| 2 | reconcile transfer status |
| 3 | converge idempotently |
Fatal invariant failure
Behavior:
| 1 | capture diagnostics |
| 2 | fail loudly |
| 3 | do not continue with questionable data |
13. Progress, speed, and ETA
The progress bar is part of trust.
Progress must represent confirmed work.
Speed should be smoothed enough to be useful.
ETA should be honest, not absurdly precise.
Prefer:
| 1 | ~18 min remaining |
over:
| 1 | 17m 42.381s |
If bytes are done but finalization is not:
| 1 | Uploading 100% |
| 2 | Finalizing... |
Do not call the transfer complete early.
14. Persistence rules
Persist valuable identity before proceeding into work that would be difficult to rediscover.
If multipart upload creation succeeds, do not rely on volatile memory as the only place that knows its identity.
Prefer small, atomic durable checkpoints.
Persist domain recovery state, not giant runtime blobs.
Version persisted schemas:
| 1 | { |
| 2 | schemaVersion: 1, |
| 3 | ... |
| 4 | } |
A user may begin a transfer on frontend version N and resume on version N+1.
Do not casually make deployed in-progress transfers unreadable.
15. Cleanup
Incomplete multipart uploads consume remote resources.
Stale sessions must eventually be aborted.
But cleanup must respect the advertised resume window.
Never promise:
Resume within 7 days
while maintenance aborts the session after 24 hours.
Product semantics and lifecycle policy must agree.
Closing the UI is not cleanup.
Losing the network is not cleanup.
Explicit cancellation, expiry, and confirmed abandonment are cleanup events.
16. Cancellation
Cancel is destructive.
A true cancellation may:
| 1 | stop active work |
| 2 | abort multipart upload |
| 3 | free transfer capacity |
| 4 | remove durable recovery metadata |
| 5 | mark transfer cancelled |
Do not trigger this sequence because a renderer disappears.
17. Security during resume
Resume is not an authorization bypass.
The API must still verify:
| 1 | identity |
| 2 | transfer ownership |
| 3 | workspace membership |
| 4 | entitlement |
| 5 | transfer status |
| 6 | object-key ownership |
| 7 | multipart-upload association |
A client must never be able to present an arbitrary uploadId and receive signed access to arbitrary storage objects.
18. Integrity after completion
Completion is more than one successful API response.
At minimum reconcile:
| 1 | object exists |
| 2 | expected size |
| 3 | transfer record matches object |
| 4 | upload session is complete |
| 5 | capacity/accounting is correct |
| 6 | recipient availability is correct |
Where stronger checksums are useful and affordable, add them.
19. Download reliability
Uploads come first, but the philosophy applies in reverse.
A future desktop receiver should support:
| 1 | background download |
| 2 | resume |
| 3 | bandwidth limits |
| 4 | destination rules |
| 5 | disk-space checks |
| 6 | checksum verification |
| 7 | post-download remote cleanup |
The invariant remains:
already downloaded bytes should not be needlessly downloaded again.
20. Resource safety
Do not buffer giant files in memory.
Memory usage should scale with:
| 1 | concurrency × bounded part size |
not:
| 1 | total file size |
Respect backpressure.
Do not leak file descriptors over multi-hour transfers.
Do not repeatedly reread or rehash hundreds of gigabytes without a clear reason.
21. Sleep, wake, and network changes
Sleep is normal laptop behavior.
On wake:
| 1 | check connectivity |
| 2 | refresh authorization if needed |
| 3 | reconcile uncertain work |
| 4 | continue |
Network transitions are normal too:
| 1 | Ethernet → Wi‑Fi |
| 2 | Wi‑Fi → hotspot |
| 3 | VPN on/off |
| 4 | home → office |
Temporary transition may pause throughput.
It should not require restarting the transfer.
22. Uppy responsibilities
Uppy owns browser upload mechanics.
That includes the mechanics around:
| 1 | multipart orchestration |
| 2 | part uploads |
| 3 | retry/backoff |
| 4 | pause/resume |
| 5 | progress |
| 6 | remote part listing |
| 7 | completion |
| 8 | abort |
Tranzfer owns:
| 1 | transfer identity |
| 2 | quota |
| 3 | durable session identity |
| 4 | same-file verification |
| 5 | recovery UX |
| 6 | authorization |
| 7 | business lifecycle |
| 8 | telemetry |
| 9 | final verification |
| 10 | abuse controls |
| 11 | acceptance testing |
Do not reimplement Uppy for sport.
Do not assume Uppy automatically solves every product-level recovery requirement.
23. Solid 2 responsibilities
Solid owns reactive presentation and orchestration.
Solid does not own durable correctness.
Good:
| 1 | durable recovery service |
| 2 | ↓ |
| 3 | reactive state |
| 4 | ↓ |
| 5 | Solid UI |
Bad:
| 1 | Solid signal |
| 2 | ↓ |
| 3 | effect |
| 4 | ↓ |
| 5 | hope it persisted |
Derive whenever possible.
Effects are for external side effects, not synchronizing multiple copies of the same domain state.
Component destruction must always be safe.
24. Desktop renderer responsibilities
The desktop renderer may:
| 1 | display progress |
| 2 | display speed |
| 3 | display ETA |
| 4 | display errors |
| 5 | request pause |
| 6 | request resume |
| 7 | request cancellation |
| 8 | show recovery prompts |
It must not be the only owner of:
| 1 | local file path |
| 2 | multipart uploadId |
| 3 | durable queue |
| 4 | retry policy |
| 5 | remote reconciliation |
25. Background daemon responsibilities
The background transfer engine must be reconstructible from:
| 1 | local durable database |
| 2 | + |
| 3 | local filesystem |
| 4 | + |
| 5 | remote storage state |
| 6 | + |
| 7 | Tranzfer API |
It must not require the renderer to explain what it was doing before a crash.
26. Observability
Record meaningful events such as:
| 1 | transfer_created |
| 2 | multipart_created |
| 3 | part_retry |
| 4 | network_offline |
| 5 | network_restored |
| 6 | auth_refreshed |
| 7 | tab_recovery_started |
| 8 | file_permission_required |
| 9 | file_reselected |
| 10 | file_identity_mismatch |
| 11 | parts_reconciled |
| 12 | resume_started |
| 13 | resume_succeeded |
| 14 | resume_failed |
| 15 | finalization_started |
| 16 | finalization_retried |
| 17 | transfer_completed |
| 18 | transfer_cancelled |
| 19 | multipart_expired |
Never log raw credentials, signed URLs, private tokens, or file contents.
27. Reliability metrics
Eventually measure:
| 1 | successful transfer rate |
| 2 | successful resume rate |
| 3 | manual-intervention rate |
| 4 | avoidable bytes re-uploaded |
| 5 | average retries per large transfer |
| 6 | finalization failure rate |
| 7 | reselection frequency |
| 8 | file mismatch frequency |
| 9 | multipart expiry frequency |
| 10 | time from recovery to resumed throughput |
A particularly important metric is:
avoidable re-uploaded bytes
If a user successfully moved 217 GB and Tranzfer unnecessarily makes them resend 217 GB, that is a serious product failure.
28. Recovery UX
Recovery should feel calm.
Example:
| 1 | Episode 42 |
| 2 | 347 GB |
| 3 | |
| 4 | Found unfinished transfer |
| 5 | 217 GB already uploaded |
| 6 | |
| 7 | [Continue] |
If permission is required:
| 1 | 217 GB is already uploaded. |
| 2 | |
| 3 | Allow access to the original file to continue. |
| 4 | |
| 5 | [Allow & continue] |
If reselection is required:
| 1 | Select the original Episode42.mov to continue. |
| 2 | Already uploaded data will not be sent again. |
| 3 | |
| 4 | [Choose file] |
If the file changed:
| 1 | This file no longer matches the upload that started earlier. |
| 2 | |
| 3 | To protect your footage, Tranzfer won't combine two different file versions. |
| 4 | |
| 5 | [Start a new transfer] |
Do not expose ListMultipartUploadParts, SigV4, IndexedDB, ETags, or other infrastructure jargon to users.
29. No fake heroics
If we cannot prove the local file is the same file: stop.
If the remote multipart session no longer exists: say so.
If storage returns a state we do not understand: fail safely.
Reliability means trustworthy continuation, not aggressive continuation at all costs.
30. Torture testing is product development
Mocked tests are necessary.
They are not sufficient.
The product's value is surviving real failure, so we must create real failure.
Required baseline file sizes:
| 1 | 10 GB |
| 2 | 100 GB |
| 3 | 350 GB |
Later add 500 GB and 1 TB when real usage and plan limits justify it.
31. Browser torture matrix
For a serious 350 GB test:
Connectivity
- disconnect Wi‑Fi mid-part
- reconnect after 30 seconds
- reconnect after 10 minutes
- switch networks mid-transfer
- restart router
- temporary DNS failure
- connection becomes dramatically slower
Request failures
- force a part to return 429
- force a part to return 500
- force several consecutive retryable failures
- signing endpoint 500
- signing endpoint timeout
- signed authorization expiry
- failure immediately before finalization
- lost response immediately after finalization
Browser lifecycle
- refresh at 1%
- refresh at 40%
- refresh at 99%
- close tab at 30%
- close tab at 70%
- reopen site
- close browser
- reopen browser
- browser crash if reproducible
- PC reboot
- sleep laptop
- wake laptop
File access
- persistent file handle remains valid
- permission must be requested again
- handle unavailable
- reselect original file
- reselect wrong file
- reselect same-name different file
- original file moved
- original file deleted
- original file modified
Remote reconciliation
- local state misses a completed remote part
- local state claims completion but remote does not
- stale local progress
- multipart session expired
- final object already completed
- cleanup races with recovery
Completion
- crash before completion request
- crash during completion request
- remote completes but response is lost
- recovery sees already-completed object
- duplicate finalization attempt
- final object size verification
32. Desktop torture matrix
When desktop exists:
- close renderer while upload continues
- destroy BrowserWindow entirely
- Electron shell restart
- Bun daemon restart
- kill daemon mid-part
- OS reboot
- auto-start after reboot
- local file path still valid
- local file moved
- local file changed
- external drive unplugged
- external drive reconnected
- machine sleep/wake
- network switch
- tray-only operation for hours
- update lifecycle does not destroy active transfers
- renderer and daemon can reconnect after update
- incoming download checks free disk space
33. Acceptance standard
A torture test does not pass because the UI looks okay.
It passes only if:
| 1 | final remote object is correct |
| 2 | + |
| 3 | already uploaded valid parts were not needlessly resent |
| 4 | + |
| 5 | transfer state converged correctly |
| 6 | + |
| 7 | the user could understand what happened |
34. Release gates
Gate A — internal
| 1 | 10 GB |
| 2 | network drop |
| 3 | part retry |
| 4 | refresh |
Gate B — serious beta
| 1 | 100 GB |
| 2 | tab close |
| 3 | sleep/wake |
| 4 | authorization expiry |
| 5 | recovery |
Gate C — Tranzfer reliability promise
| 1 | 350 GB |
| 2 | router restart |
| 3 | browser restart |
| 4 | file reselection recovery |
| 5 | remote reconciliation |
| 6 | completion verification |
Gate D — reboot promise
| 1 | 350 GB |
| 2 | machine reboot |
| 3 | durable restore |
| 4 | same-file verification |
| 5 | continue existing multipart upload |
Do not market Gate D before Gate D passes.
35. Regression severity
P0
| 1 | silent file corruption |
| 2 | security boundary bypass |
| 3 | wrong user's object resumed |
P1
| 1 | recoverable huge upload restarts from zero |
| 2 | completed transfer becomes inaccessible |
| 3 | remote object and product state diverge badly |
P2
| 1 | manual action required where automatic recovery should work |
| 2 | progress incorrect after recovery |
| 3 | resume requires unnecessary extra steps |
P3
| 1 | confusing recovery copy |
| 2 | weird ETA |
| 3 | cosmetic state issue |
36. Agent rules
Every coding agent touching transfer code must know:
| 1 | R2 remote state is authoritative for uploaded parts. |
| 2 | Durable metadata outlives UI processes. |
| 3 | Local file identity must be verified. |
| 4 | Recovery reconciles; it does not assume. |
| 5 | Component lifetime must not determine correctness. |
| 6 | Uppy owns browser upload mechanics. |
| 7 | Solid owns presentation, not durable transfer truth. |
Reject code that:
- stores critical upload identity only in a component;
- creates a second upload state machine next to Uppy;
- aborts uploads on page/window disappearance;
- assumes unload means cancellation;
- clears durable state before completion is known;
- resumes based only on filename;
- trusts stale local completed-part lists without reconciliation;
- treats every error as fatal;
- restarts a whole file after one failed part;
- pre-signs thousands of future part URLs;
- hardcodes chunk size without part-limit reasoning;
- marks complete before finalization;
- swallows completion uncertainty;
- logs signed URLs or credentials.
37. Code-review questions
Every upload/recovery PR should answer:
- What durable state does this change create?
- What happens if the process dies immediately afterward?
- What if the remote operation succeeded but the response is lost?
- What if local and remote state disagree?
- What if the local file changed?
- Can the user be forced to resend already-confirmed bytes?
- Does cancellation remain explicit?
- Can this survive refresh?
- Can this survive browser restart?
- Can this survive machine reboot?
- Does it preserve future desktop compatibility?
- What torture test proves it?
38. Definition of done: browser uploader
The browser uploader is not done because a 1 GB happy path reaches R2.
It is done when:
- 10 GB works
- 100 GB works
- 350 GB works
- chunk policy respects multipart limits
- progress is accurate
- retryable errors retry
- network loss recovers
- refresh recovers
- tab close recovers
- browser restart has a recovery path
- PC reboot has a recovery path
- file permission loss has a recovery path
- original file reselection safely resumes
- changed file is rejected
- remote parts reconcile
- authorization can refresh
- finalization is safe
- final object is verified
- cancellation cleans up intentionally
- stale sessions eventually clean up
39. Definition of done: desktop uploader
Desktop upload is not done because Electron can upload a file.
It is done when:
- renderer may disappear without stopping upload
- daemon state survives daemon restart
- machine reboot resumes
- local file is verified
- changed file is rejected
- remote state reconciles
- tray-only transfer works for hours
- sleep/wake works
- network switch works
- external-drive loss is handled safely
- update lifecycle does not destroy active transfers
- resource usage is acceptable
40. What we explicitly do not build yet
Reliability is not permission to build a storage operating system.
Do not build yet:
| 1 | mounted filesystem |
| 2 | Dropbox-style sync |
| 3 | folder mirroring |
| 4 | distributed block deduplication |
| 5 | peer-to-peer transport |
| 6 | custom TCP protocol |
| 7 | custom congestion control |
| 8 | global accelerator network |
| 9 | bespoke multipart protocol |
| 10 | enterprise transfer appliance |
Use existing infrastructure.
S3-compatible multipart already solves the core remote resumability primitive.
Uppy already solves much of browser transfer mechanics.
R2 already stores the parts.
We build the product semantics and recovery experience around those primitives.
41. Anti-overengineering rule
When deciding between:
| 1 | beautiful generic transfer abstraction |
and:
| 1 | 350 GB upload survives router restart |
choose the second one.
When deciding between:
| 1 | five-backend portable storage adapter |
and:
| 1 | R2 resume works after browser restart |
choose the second one.
When deciding between:
| 1 | perfect event-sourced upload architecture |
and:
| 1 | real editor resumes a failed upload |
choose the second one.
42. Anti-underengineering rule
“Don't overengineer” is not permission to ship fragile uploads.
These are not optional polish:
| 1 | multipart |
| 2 | retry |
| 3 | durable upload identity |
| 4 | reconciliation |
| 5 | same-file verification |
| 6 | safe finalization |
| 7 | real torture testing |
For Tranzfer, these are the minimum product.
43. The 350 GB rule
Every upload architecture proposal must explain this scenario:
| 1 | A creator has a 350 GB project. |
| 2 | |
| 3 | They start uploading at night. |
| 4 | |
| 5 | At 63%: |
| 6 | the router restarts. |
| 7 | |
| 8 | Ten minutes later: |
| 9 | the network returns. |
| 10 | |
| 11 | At 71%: |
| 12 | the laptop sleeps. |
| 13 | |
| 14 | In the morning: |
| 15 | the laptop wakes. |
| 16 | |
| 17 | At 79%: |
| 18 | the browser is accidentally closed. |
| 19 | |
| 20 | Later: |
| 21 | the computer reboots for an update. |
| 22 | |
| 23 | The creator opens Tranzfer again. |
| 24 | |
| 25 | Tranzfer finds the unfinished transfer. |
| 26 | |
| 27 | It restores or requests access to the original file. |
| 28 | |
| 29 | It verifies the file is unchanged. |
| 30 | |
| 31 | It asks R2 which parts already exist. |
| 32 | |
| 33 | It continues from the missing work. |
| 34 | |
| 35 | The creator does not resend 276 GB. |
| 36 | |
| 37 | The editor eventually receives one correct file. |
If a design cannot explain exactly how this works, the design is incomplete.
44. The already-moved-bytes rule
Already moved bytes are an asset. Preserve them.
Every retry strategy, persistence decision, cleanup policy, and UX flow should respect that.
45. The one-file rule
A multipart object must correspond to one immutable logical source-file version.
Do not combine versions.
Do not guess.
One logical transfer.
One logical source file.
One final object.
46. The remote-truth rule
When recovering:
| 1 | local says 72% |
| 2 | remote says 74% |
remote wins.
When:
| 1 | local says part 1200 done |
| 2 | remote does not have part 1200 |
upload part 1200 again.
When:
| 1 | local says finalizing |
| 2 | remote object already exists correctly |
converge toward complete.
47. The calm-software rule
Failures should not make the product scream.
Prefer:
| 1 | Connection lost. |
| 2 | We'll continue when you're back online. |
or:
| 1 | We need access to the original file to continue. |
| 2 | 217 GB is already uploaded. |
The product should communicate confidence because the system actually has a recovery plan.
48. Reliability ladder
| 1 | happy-path upload |
| 2 | ↓ |
| 3 | part retry |
| 4 | ↓ |
| 5 | network recovery |
| 6 | ↓ |
| 7 | refresh recovery |
| 8 | ↓ |
| 9 | tab/browser recovery |
| 10 | ↓ |
| 11 | machine-reboot recovery |
| 12 | ↓ |
| 13 | background desktop transfer |
| 14 | ↓ |
| 15 | unattended recurring delivery |
Do not skip lower rungs.
49. Reliability before automation
Automation amplifies whatever reliability exists underneath.
If transfer is fragile:
| 1 | automation = automated failure |
Therefore:
| 1 | reliable transfer |
| 2 | ↓ |
| 3 | background transfer |
| 4 | ↓ |
| 5 | trusted relationships |
| 6 | ↓ |
| 7 | automatic delivery |
50. Reliability before everything else
Review features are irrelevant if the file never arrives.
Pricing sophistication is irrelevant if a 300 GB transfer restarts from zero.
Desktop architecture is irrelevant if browser reliability has not taught us the actual failure modes.
Scale is irrelevant before ten users trust us with real footage.
Reliability comes first.
51. Product copy constraints
Do not claim:
Never fails.
Nothing never fails.
Claim things we can prove.
Examples:
| 1 | Resumes interrupted transfers. |
| 2 | |
| 3 | Already uploaded parts stay uploaded. |
| 4 | |
| 5 | Lose Wi‑Fi. Pick up where you left off. |
| 6 | |
| 7 | Reopen Tranzfer and continue. |
| 8 | |
| 9 | No starting a 300 GB project from zero because your connection blinked. |
Only market stronger claims after the corresponding release gate passes.
52. The customer's mental model
The customer should think:
Tranzfer remembers.
Not:
I hope the tab stays open.
53. The engineering mental model
Engineers and agents should think:
A transfer is a durable distributed job with a local source file and remote multipart state.
Not:
A transfer is a component with a progress bar.
54. The founder mental model
Do not turn reliability into another way to avoid customers.
The purpose of this document is to make Tranzfer trustworthy enough for real people to use.
It is not permission to spend three months perfecting theoretical failure modes nobody has encountered.
The sequence remains:
| 1 | build |
| 2 | break |
| 3 | fix |
| 4 | real human |
| 5 | repeat |
| 6 | ask for money |
55. Final standard
Tranzfer should eventually be the software someone chooses when the file is too important and too large to casually try again.
The connection may fail.
The browser may disappear.
The process may die.
The machine may reboot.
The transfer should remain understandable, recoverable, and safe.
The best reliability feature is not a clever retry algorithm.
It is the user's belief, earned by repeated evidence, that:
I can leave this alone. Tranzfer will get it there.
Until that is true:
make the next transfer more reliable than the last one.