# Runtime Environments Externalization **Date:** 2026-02-13 ## Summary Completed the refactoring to externalize runtime environments (virtualenvs, node_modules, etc.) from pack directories to a dedicated `runtime_envs_dir`. This ensures pack directories remain clean and read-only while isolated runtime environments are managed at a configurable external location. ## Problem Previously, runtime environments (e.g., Python virtualenvs) were created inside pack directories at `{pack_dir}/.venv`. This had several issues: 1. **Docker incompatibility**: Pack volumes are mounted read-only (`:ro`) in worker containers, preventing environment creation 2. **API service failures**: The API container doesn't have Python installed, so `python3 -m venv` failed silently during pack registration 3. **Dirty pack directories**: Mixing generated environments with pack source files 4. **Missing `runtime_envs_dir` parameter**: `ProcessRuntime::new()` was updated to accept 4 arguments but callers were still passing 3, causing compile errors ## Changes ### Compile Fixes - **`crates/worker/src/service.rs`**: Added `runtime_envs_dir` from config and passed as 4th argument to `ProcessRuntime::new()` - **`crates/worker/src/runtime/local.rs`**: Added `PathBuf::from("/opt/attune/runtime_envs")` as 4th argument to `ProcessRuntime::new()` in `LocalRuntime::new()` - **`crates/worker/src/runtime/process.rs`**: Suppressed `dead_code` warning on `resolve_pack_dir` (tested utility method kept for API completeness) ### Configuration - **`config.docker.yaml`**: Added `runtime_envs_dir: /opt/attune/runtime_envs` - **`config.development.yaml`**: Added `runtime_envs_dir: ./runtime_envs` - **`config.test.yaml`**: Added `runtime_envs_dir: /tmp/attune-test-runtime-envs` - **`config.example.yaml`**: Added documented `runtime_envs_dir` setting with explanation - **`crates/common/src/config.rs`**: Added `runtime_envs_dir` field to test `Config` struct initializers ### Docker Compose (`docker-compose.yaml`) - Added `runtime_envs` named volume - Mounted `runtime_envs` volume at `/opt/attune/runtime_envs` in: - `api` (for best-effort bare-metal env setup) - `worker-shell`, `worker-python`, `worker-node`, `worker-full` (for on-demand env creation) ### API Pack Registration (`crates/api/src/routes/packs.rs`) Updated the best-effort environment setup during pack registration to use external paths: - Environment directory computed as `{runtime_envs_dir}/{pack_ref}/{runtime_name}` instead of `{pack_dir}/.venv` - Uses `build_template_vars_with_env()` for proper template variable resolution with external env_dir - Creates parent directories before attempting environment creation - Checks `env_dir.exists()` directly instead of legacy `resolve_env_dir()` for dependency installation ### ProcessRuntime `can_execute` Fix (`crates/worker/src/runtime/process.rs`) Fixed a pre-existing logic issue where `can_execute` would fall through from a non-matching runtime_name to extension-based matching. When an explicit `runtime_name` is specified in the execution context, it is now treated as authoritative — the method returns the result of the name comparison directly without falling through to extension matching. ### Test Updates - **`crates/worker/tests/dependency_isolation_test.rs`**: Full rewrite to use external `runtime_envs_dir`. All 17 tests pass. Key changes: - Separate `packs_base_dir` and `runtime_envs_dir` temp directories - `env_dir` computed as `runtime_envs_dir.join(pack_ref).join(runtime_name)` - `setup_pack_environment(&pack_dir, &env_dir)` — now takes 2 arguments - `environment_exists("pack_ref")` — now takes pack_ref string - Assertions verify environments are created at external locations AND that pack directories remain clean - **`crates/worker/tests/security_tests.rs`**: Added 4th `runtime_envs_dir` argument to all `ProcessRuntime::new()` calls - **`crates/worker/tests/log_truncation_test.rs`**: Added 4th `runtime_envs_dir` argument to all `ProcessRuntime::new()` calls - **`crates/worker/src/runtime/process.rs`** (unit test): Added 4th argument to `test_working_dir_set_to_pack_dir` ## Environment Path Pattern ``` {runtime_envs_dir}/{pack_ref}/{runtime_name} ``` Examples: - `/opt/attune/runtime_envs/python_example/python` (Docker) - `./runtime_envs/python_example/python` (development) - `/tmp/attune-test-runtime-envs/testpack/python` (tests) ## Architecture Summary | Component | Old Behavior | New Behavior | |-----------|-------------|-------------| | Env location | `{pack_dir}/.venv` | `{runtime_envs_dir}/{pack_ref}/{runtime}` | | Pack directory | Modified by venv | Remains clean/read-only | | API setup | Pack-relative `build_template_vars` | External `build_template_vars_with_env` | | Worker setup | Did not create venv | Creates venv on-demand before first execution | | Docker volumes | Only `packs_data` | `packs_data` (ro) + `runtime_envs` (rw) | | Config | No `runtime_envs_dir` | Configurable with default `/opt/attune/runtime_envs` | ## Test Results - **attune-common**: 125 passed, 0 failed - **attune-worker unit tests**: 76 passed, 0 failed, 4 ignored - **dependency_isolation_test**: 17 passed, 0 failed - **log_truncation_test**: 8 passed, 0 failed - **security_tests**: 5 passed, 2 failed (pre-existing, unrelated to this work) - **Workspace**: Zero compiler warnings ## Pre-existing Issues (Not Addressed) - `test_shell_secrets_not_in_environ`: Shell secret delivery mechanism issue - `test_python_secrets_isolated_between_actions`: Python stdin secret reading doesn't match delivery mechanism