diff --git a/strix/interface/utils.py b/strix/interface/utils.py index e051a1856..a61f90a69 100644 --- a/strix/interface/utils.py +++ b/strix/interface/utils.py @@ -1323,6 +1323,7 @@ def collect_local_sources(targets_info: list[dict[str, Any]]) -> list[dict[str, "source_path": details["target_path"], "workspace_subdir": workspace_subdir, "protect_metadata": True, + "read_only": bool(details.get("read_only")), } ) diff --git a/strix/runtime/session_manager.py b/strix/runtime/session_manager.py index d908c33db..a8447ebbe 100644 --- a/strix/runtime/session_manager.py +++ b/strix/runtime/session_manager.py @@ -70,8 +70,9 @@ def build_bind_mounts(local_sources: list[dict[str, Any]]) -> list[dict[str, Any continue resolved = Path(host_path).expanduser().resolve() target = f"{_WORKSPACE_ROOT}/{ws_subdir}" - bind_mounts.append({"source": str(resolved), "target": target, "read_only": False}) - if src.get("protect_metadata"): + read_only = bool(src.get("read_only")) + bind_mounts.append({"source": str(resolved), "target": target, "read_only": read_only}) + if src.get("protect_metadata") and not read_only: bind_mounts.extend(_metadata_mounts(resolved, target)) return bind_mounts diff --git a/tests/test_local_sources.py b/tests/test_local_sources.py index 9984bef6a..a799192de 100644 --- a/tests/test_local_sources.py +++ b/tests/test_local_sources.py @@ -30,7 +30,26 @@ def _local_target(target_path: str) -> dict[str, Any]: def test_collect_local_sources_protects_the_users_own_git() -> None: sources = collect_local_sources([_local_target("/code")]) assert sources == [ - {"source_path": "/code", "workspace_subdir": "repo", "protect_metadata": True} + { + "source_path": "/code", + "workspace_subdir": "repo", + "protect_metadata": True, + "read_only": False, + } + ] + + +def test_collect_local_sources_forwards_read_only() -> None: + target = _local_target("/layout") + target["details"]["read_only"] = True + sources = collect_local_sources([target]) + assert sources == [ + { + "source_path": "/layout", + "workspace_subdir": "repo", + "protect_metadata": True, + "read_only": True, + } ] diff --git a/tests/test_session_entries.py b/tests/test_session_entries.py index 43becfe3e..a15e259b8 100644 --- a/tests/test_session_entries.py +++ b/tests/test_session_entries.py @@ -27,8 +27,15 @@ from strix.runtime.session_manager import ( ) -def _source(subdir: str, path: str, *, protect_metadata: bool = False) -> dict[str, Any]: - return {"source_path": path, "workspace_subdir": subdir, "protect_metadata": protect_metadata} +def _source( + subdir: str, path: str, *, protect_metadata: bool = False, read_only: bool = False +) -> dict[str, Any]: + return { + "source_path": path, + "workspace_subdir": subdir, + "protect_metadata": protect_metadata, + "read_only": read_only, + } def test_source_becomes_writable_bind_mount(tmp_path: Path) -> None: @@ -124,6 +131,16 @@ def test_clone_keeps_its_git_writable(tmp_path: Path) -> None: assert [m["target"] for m in mounts] == ["/workspace/clone"] +def test_read_only_source_is_one_read_only_mount(tmp_path: Path) -> None: + (tmp_path / ".git").mkdir() + mounts = build_bind_mounts( + [_source("image", str(tmp_path), protect_metadata=True, read_only=True)] + ) + assert mounts == [ + {"source": str(tmp_path.resolve()), "target": "/workspace/image", "read_only": True} + ] + + def test_multiple_sources_each_get_a_mount(tmp_path: Path) -> None: first = tmp_path / "first" second = tmp_path / "second"