Summary
When a sender offers files whose transfer paths already exist in the receiver output directory, the receiver currently errors before the offer can be accepted. This happens even when the receiver is configured with ConflictPolicy::Rename, so the configured conflict policy is effectively ignored for normal destination conflicts.
Related: #27
Severity
Medium
Current behavior
If the receiver output directory already contains a file with the same relative path as an incoming transfer item, the receiver fails while building the offer:
- The receiver receives the sender manifest.
- Core calls
build_expected_files(...) before the user decision phase.
build_expected_files(...) resolves each destination path under the receiver output directory.
- It calls
ensure_destination_available(...).
ensure_destination_available(...) returns TransferPathError::DestinationExists as soon as the destination exists.
- The receiver sends a decline/error back to the sender and reports a file conflict.
The user sees a transfer failure instead of a renamed output path such as report (1).txt.
Expected behavior
The receiver's configured conflict policy should control this case:
Reject: fail before transfer and report a clear file-conflict error.
Rename: resolve the destination to the next available filename and continue.
Overwrite: remain unsupported for now if we do not want overwrite semantics yet, but it should be rejected explicitly at configuration time as it is today.
For the app's current default behavior, existing receiver files should not cause an error if the configured policy is Rename.
Root cause from investigation
The app and CLI expose ConflictPolicy, but it does not reach the core receiver planning path.
Relevant code:
crates/app/src/receiver/actor.rs: spawn_listener_task(...) accepts conflict_policy, rejects Overwrite, then drops the value.
crates/app/src/receiver/session.rs: creates CoreReceiverRequest with only device_name, device_type, and out_dir.
crates/core/src/transfer/receiver.rs: ReceiverRequest has no conflict_policy field.
crates/core/src/transfer/receiver.rs: build_expected_files(...) calls ensure_destination_available(...) directly and does not call ConflictPolicy::resolve(...).
crates/core/src/transfer/receiver.rs: TransferRecord::new(...) hard-codes ConflictPolicy::Rename, but that happens after destination validation, so it is too late to affect conflicts.
crates/core/src/fs_plan/conflict.rs: ConflictPolicy::resolve(...) already has the intended rename behavior, but receiver planning does not use it.
Important nuance
Existing destinations are currently allowed only for files already exported by a resume record. That path is covered by resumed_export_allows_destinations_for_already_exported_files and should remain valid.
So the desired fix should preserve resume behavior while applying conflict policy to ordinary destination conflicts.
Suggested fix shape
- Add
conflict_policy to CoreReceiverRequest.
- Pass
ReceiverConfig.conflict_policy from app runtime/listener/session into core.
- Change
build_expected_files(...) to take the policy.
- Keep the existing resume-record exception for already exported files.
- For non-resume conflicts, use
ConflictPolicy::resolve(...) or equivalent shared destination-planning logic.
- Persist the actual policy and resolved destinations in
TransferRecord as appropriate.
- Add focused tests for:
Rename resolves file.txt to file (1).txt when file.txt already exists.
Reject still fails with a file-conflict error.
- Resume records can still reuse already-exported destinations.
- App-level receiver config actually reaches core planning.
Verification idea
A regression test can construct a manifest containing report.txt, create report.txt in the receiver output directory, configure the receiver with ConflictPolicy::Rename, and assert that expected files resolve to report (1).txt rather than returning DestinationExists.
Summary
When a sender offers files whose transfer paths already exist in the receiver output directory, the receiver currently errors before the offer can be accepted. This happens even when the receiver is configured with
ConflictPolicy::Rename, so the configured conflict policy is effectively ignored for normal destination conflicts.Related: #27
Severity
Medium
Current behavior
If the receiver output directory already contains a file with the same relative path as an incoming transfer item, the receiver fails while building the offer:
build_expected_files(...)before the user decision phase.build_expected_files(...)resolves each destination path under the receiver output directory.ensure_destination_available(...).ensure_destination_available(...)returnsTransferPathError::DestinationExistsas soon as the destination exists.The user sees a transfer failure instead of a renamed output path such as
report (1).txt.Expected behavior
The receiver's configured conflict policy should control this case:
Reject: fail before transfer and report a clear file-conflict error.Rename: resolve the destination to the next available filename and continue.Overwrite: remain unsupported for now if we do not want overwrite semantics yet, but it should be rejected explicitly at configuration time as it is today.For the app's current default behavior, existing receiver files should not cause an error if the configured policy is
Rename.Root cause from investigation
The app and CLI expose
ConflictPolicy, but it does not reach the core receiver planning path.Relevant code:
crates/app/src/receiver/actor.rs:spawn_listener_task(...)acceptsconflict_policy, rejectsOverwrite, then drops the value.crates/app/src/receiver/session.rs: createsCoreReceiverRequestwith onlydevice_name,device_type, andout_dir.crates/core/src/transfer/receiver.rs:ReceiverRequesthas noconflict_policyfield.crates/core/src/transfer/receiver.rs:build_expected_files(...)callsensure_destination_available(...)directly and does not callConflictPolicy::resolve(...).crates/core/src/transfer/receiver.rs:TransferRecord::new(...)hard-codesConflictPolicy::Rename, but that happens after destination validation, so it is too late to affect conflicts.crates/core/src/fs_plan/conflict.rs:ConflictPolicy::resolve(...)already has the intended rename behavior, but receiver planning does not use it.Important nuance
Existing destinations are currently allowed only for files already exported by a resume record. That path is covered by
resumed_export_allows_destinations_for_already_exported_filesand should remain valid.So the desired fix should preserve resume behavior while applying conflict policy to ordinary destination conflicts.
Suggested fix shape
conflict_policytoCoreReceiverRequest.ReceiverConfig.conflict_policyfrom app runtime/listener/session into core.build_expected_files(...)to take the policy.ConflictPolicy::resolve(...)or equivalent shared destination-planning logic.TransferRecordas appropriate.Renameresolvesfile.txttofile (1).txtwhenfile.txtalready exists.Rejectstill fails with a file-conflict error.Verification idea
A regression test can construct a manifest containing
report.txt, createreport.txtin the receiver output directory, configure the receiver withConflictPolicy::Rename, and assert that expected files resolve toreport (1).txtrather than returningDestinationExists.