diff --git a/apps/api/src/services/config/config_profile_resolver.py b/apps/api/src/services/config/config_profile_resolver.py index e1f24d3..a717de5 100644 --- a/apps/api/src/services/config/config_profile_resolver.py +++ b/apps/api/src/services/config/config_profile_resolver.py @@ -569,7 +569,7 @@ def apply_resolved_profile( expanded_target = os.path.normpath(expand_container_path(mount.target, home_dir)) mount_dir = mounts_dir / expanded_target.lstrip("/").replace("/", "_") for file_path, content in mount.files.items(): - write_canonical_file(mount_dir, file_path, content) + write_canonical_file(mount_dir, file_path, content, preserve_inode=True) volume_mounts.append( { diff --git a/apps/api/tests/unit/test_config_profile_resolver.py b/apps/api/tests/unit/test_config_profile_resolver.py index b0e94a6..f3b70e5 100644 --- a/apps/api/tests/unit/test_config_profile_resolver.py +++ b/apps/api/tests/unit/test_config_profile_resolver.py @@ -584,6 +584,37 @@ class TestApplyResolvedProfile: assert canonical_file.stat().st_ino == original_inode assert canonical_file.read_text() == "value = 2" + def test_mounted_file_update_preserves_bind_mount_inode(self, tmp_path) -> None: + """A file inside a profile directory mount must update in place.""" + profile_id = uuid.uuid4() + instance_root = tmp_path / "instances" + resolved = ResolvedProfile( + profile_id=profile_id, + profile_name="test", + mounts={ + "/etc/tool": ResolvedMount( + target="/etc/tool", mode="rw", files={"settings.toml": "value = 1"} + ) + }, + ) + + apply_resolved_profile(str(instance_root / "instance-a"), resolved) + canonical_file = ( + instance_root + / "config-profiles" + / str(profile_id) + / "mounts" + / "etc_tool" + / "settings.toml" + ) + original_inode = canonical_file.stat().st_ino + + resolved.mounts["/etc/tool"].files["settings.toml"] = "value = 2" + apply_resolved_profile(str(instance_root / "instance-a"), resolved) + + assert canonical_file.stat().st_ino == original_inode + assert canonical_file.read_text() == "value = 2" + def test_instances_share_profile_scoped_mount_sources(self, tmp_path) -> None: """Different instance paths resolve a profile to one canonical source.""" profile_id = uuid.uuid4()