* feat(shell): fs.mergeVolumes deletes source needles after filer update
Before this change, mergeVolumes only copied chunks to the destination
volume and updated the filer — the source needle sat untouched on its
original volume as a silent orphan. Operators had to run a separate
volume.fsck + volume.vacuum pass to actually reclaim the space, and
#9116 (comment 4282692876) showed how that pipeline can look exactly
like "mergeVolumes did nothing": the source volume keeps reporting its
original size even though every chunk has been logically moved out.
Clean up the source inline. For each entry, track the pre-move fids as
they're captured, and after the UpdateEntry RPC commits, issue
BatchDelete on every replica of each source volume. Key invariants:
- Source fids are only deleted AFTER UpdateEntry succeeds; if the
filer write fails we skip the cleanup for that entry so we never
delete data the filer still references.
- rewriteManifestChunk grew a fourth return value so nested manifest
and sub-chunk moves propagate their moved-source list back to the
top-level callsite. The outer manifest itself is recorded at the
callsite, since only the callsite sees the pre-rewrite fid.
- deleteMovedSourceNeedles logs errors but never returns them.
Propagating would abort TraverseBfs mid-merge, stranding remaining
entries; logging leaves the fallback path (fsck reconciles later)
intact.
- StatusNotModified from the volume server is expected whenever a
concurrent fsck purge beat us to the delete or a replica already
reconciled — don't warn on it.
Readonly source volumes are already rejected up front by
createMergePlan, so by the time we reach the delete the source is
writable. If a replica's readonly bit has flipped since then the
delete will fail and get logged; the user can re-run once they've
fixed the replica (same failure mode as today's fsck purge).
Fixes the space-not-reclaimed half of #9116.
Related design discussion: #8589.
* address review: cast r.Status to int in StatusNotModified compare
http.StatusNotModified is an untyped constant so the compare works as
written, but the int32/int mixed-type signal trips static analyzers
and PR tooling. Cast explicitly and note why.