# src/target_sync/
Copies compiled content from `.mars/` to configured target directories and performs
per-target orphan cleanup. Ownership rules live in `src/surface_ownership.rs` and
are shared with native reconcile (`src/compiler/mod.rs`) and `mars link`.
## Contracts
### Per-target lock ownership
Lock v3 `OutputRecord` entries carry `target_root` + `dest_path` plus an explicit
lifecycle state. Mars may delete or remove-on-`Removed` only when the previous
lock contains a matching record for that exact pair. Overwriting an existing path
without `--force` additionally requires that record to be `Installed`.
**Invariant:** A path tracked only under `.mars` does **not** authorize mutation
under `.cursor`, `.claude`, or any other target root.
Use `LockFile::contains_output(target_root, dest_path)` for deletion authority and
`LockFile::installed_checksum_for_output(target_root, dest_path)` for replacement
authority.
Orphan cleanup scopes to `output_dest_paths_for_target(target_root)` — never
`all_output_dest_paths()` joined against every target.
### `surface_ownership` — shared gating
| `may_delete(old_lock, target_root, dest_path)` | Orphan cleanup, Removed deletes, native reconcile deletes |
| `copy_decision(...)` | Copy/install when dest already exists |
| `warn_no_installed_claim_collision(...)` | Preserve a collision without an installed claim; hint correct `--force` command |
| `warn_no_installed_claim_adopted(...)` | `--force` adopted the path; lock will record an installed target output |
`CollisionAdoptHint` selects the diagnostic hint:
- `SyncForce` → suggests `mars sync --force`
- `LinkForce` → suggests `mars link <target> --force`
### Copy/install collision semantics
When dest exists on disk but the lock has no installed-content claim for
`(target_root, dest_path)` (either no record or `PendingDeletion`):
| `mars sync` | Preserve local file; emit `target-unmanaged-collision` | Overwrite/adopt; emit `target-unmanaged-adopted`; record lock |
| `mars link` | Fail (exit 2); emit `target-unmanaged-collision` | Adopt; persist linked-target outputs in lock |
### Lock finalization after target sync
`sync_managed_targets` returns `synced_outputs` and `removed_dest_paths` per target.
`finalize()` merges these via `apply_target_sync_outputs` and
`apply_compiled_native_outputs` so linked-target records persist in `mars.lock`.
`lock.canonical_flat_items()` is for `.mars`-only views (e.g. `mars link` seed
state). Do not use bare `dest_path` iterators for linked-target ownership checks.
## Diagnostic codes
| `target-unmanaged-collision` | Existing path without an installed claim preserved; run suggested `--force` to adopt |
| `target-unmanaged-adopted` | `--force` adopted a path without an installed claim; lock updated |
## Consumers
```
surface_ownership.rs ←── single rule set
↑
target_sync/mod.rs ← orphan cleanup, Removed, copy/install
compiler/mod.rs ← reconcile_native_agent_surfaces, dual_surface_compile
cli/link.rs ← link collision fail/adopt + lock persist
sync/mod.rs ← passes old_lock + CollisionAdoptHint::SyncForce
```
## Regression tests
- `sync_preserves_handwritten_cursor_agents_when_lock_only_tracks_mars` (integration)
- `sync_preserves_handwritten_collision_when_lock_only_tracks_mars` (unit)
- `link_fails_on_unmanaged_collision_without_force` / `link_force_adopts_unmanaged_collision_and_records_lock` (integration)
Manual smoke: `tests/smoke/manual/target-scoped-linked-targets.md`