Mechanism: since 1672cf0b03 delete_profile waits for the profile to be unowned through
wait_profile_unowned -> exclusive_maintenance -> ProfileOwnership.reserve, which opens
gateway.lock O_RDWR|O_CREAT. In a NixOS-style read-only copy (0555 dirs, 0444 files) that
raises FileNotFoundError/EACCES (no lock: the 0555 home refuses the create) or
PermissionError (0444 lock), the OSError escapes the OwnershipConflict handler, and the
delete stops with "Could not remove" before _rmtree_make_writable ever runs. Main's
delete had no reservation step and removed such copies.
Fix: when the reservation cannot even be attempted (OSError), probe the lock read-only: a
missing lock, or one our own flock through an O_RDONLY descriptor wins, has no holder; a
held one still blocks the delete exactly like an OwnershipConflict (fail closed; Windows
refuses since a sharing violation is a live holder there).
Test: tests/hermes_cli/test_profile_delete_read_only_copy.py::
test_read_only_copy_is_unowned_unless_its_lock_is_held[False/True] (real 0555/0444 tree,
real flock holder subprocess); on r13-base2 both cases raise.
Review: dokterdok N106
Mechanism: delete_profile tombstones the profile and asks the multiplexer to
rescan; the multiplexer answers {"pending": true} after its 5 s wait and keeps
tearing the profile down (adapters, authority retirement, then the gateway.lock
reservation it now holds per served profile via reserve_profile_home). The delete
went straight to rmtree, removing the held gateway.lock inode and the tree under
live writers that recreate it.
Fix: before rmtree, wait (up to 60 s) until exclusive_maintenance([profile_dir])
can take the profile's lock - the same reservation every gateway owner holds -
and abort with "a running gateway still owns this profile; retry" when it cannot.
The rmtree helpers move to hermes_cli/profiles_remove.py with the new wait so
profiles.py (over the file-line cap) shrinks. Class sweep (SWEEP_profile-home-owner.md):
rename_profile's multiplexed arm has the same shape (unroute, then move the dir) and
takes the same wait before the rename.
Test: tests/hermes_cli/test_profile_delete_held_lock.py holds the profile's lock
from a real separate process through process_ownership.reserve; red on r12-base
(the directory is removed while the owner holds it); green: delete completes
once the owner releases and refuses cleanly when it never does; the rename case
is red on base too (the home moves under the owner).
Review: dokterdok N60
(cherry picked from commit 2c4afb72c2c427a772cab73886cc6a85b224f66d)