working on runtime executions

This commit is contained in:
2026-02-16 22:04:20 -06:00
parent f52320f889
commit 904ede04be
99 changed files with 6778 additions and 5929 deletions

View File

@@ -0,0 +1,114 @@
# Fix: Pack Installation Virtualenv Ordering & FK ON DELETE Constraints
**Date:** 2026-02-05
## Problems
### 1. Virtualenv Not Created at Permanent Location
When installing a Python pack (e.g., `python_example`), no virtualenv was created at the permanent storage location. Attempting to run an action yielded:
```json
{
"error": "Execution failed during preparation",
"succeeded": false
}
```
### 2. Pack Deletion Blocked by Foreign Key Constraints
Deleting a pack that had been used (with executions) failed with:
```json
{
"error": "Constraint violation: execution_action_fkey",
"code": "CONFLICT"
}
```
## Root Causes
### Virtualenv Ordering Bug
In `install_pack` (`crates/api/src/routes/packs.rs`), the operation ordering was incorrect:
1. Pack downloaded to temp directory (`/tmp/attune-pack-installs/...`)
2. `register_pack_internal(temp_path)` called — creates DB record **and sets up virtualenv at temp path**
3. `storage.install_pack()` copies pack from temp to permanent storage (`packs/{pack_ref}/`)
4. Temp directory cleaned up
Python virtualenvs are **not relocatable** — they contain hardcoded paths in shebang lines, `pyvenv.cfg`, and pip scripts. The copied `.venv` was non-functional.
### Missing ON DELETE Clauses on Foreign Keys
Several foreign key constraints in the schema had no `ON DELETE` behavior (defaulting to `RESTRICT`), which blocked cascading deletes:
- `execution.action``action(id)`**no ON DELETE** (blocks action deletion)
- `execution.parent``execution(id)`**no ON DELETE**
- `execution.enforcement``enforcement(id)`**no ON DELETE**
- `rule.action``action(id)`**no ON DELETE**, also `NOT NULL`
- `rule.trigger``trigger(id)`**no ON DELETE**, also `NOT NULL`
- `event.source``sensor(id)`**no ON DELETE**
- `workflow_execution.workflow_def``workflow_definition(id)`**no ON DELETE**
When deleting a pack, the cascade deleted actions (`action.pack ON DELETE CASCADE`), but executions referencing those actions blocked the delete.
## Fixes
### 1. Pack Installation Ordering
Restructured `install_pack` to move the pack to permanent storage **before** calling `register_pack_internal`:
1. Pack downloaded to temp directory
2. `pack.yaml` read to extract `pack_ref`
3. **Pack moved to permanent storage** (`packs/{pack_ref}/`)
4. `register_pack_internal(permanent_path)` called — virtualenv creation and dependency installation now happen at the final location
5. Temp directory cleaned up
Added error handling to clean up permanent storage if registration fails after the move.
### 2. Foreign Key ON DELETE Fixes (Merged into Original Migrations)
Fixed all missing ON DELETE behaviors directly in the original migration files (requires DB rebuild):
| Table.Column | Migration File | ON DELETE | Notes |
|---|---|---|---|
| `execution.action` | `000006_execution_system` | `SET NULL` | Already nullable; `action_ref` text preserved |
| `execution.parent` | `000006_execution_system` | `SET NULL` | Already nullable |
| `execution.enforcement` | `000006_execution_system` | `SET NULL` | Already nullable |
| `rule.action` | `000006_execution_system` | `SET NULL` | Made nullable; `action_ref` text preserved |
| `rule.trigger` | `000006_execution_system` | `SET NULL` | Made nullable; `trigger_ref` text preserved |
| `event.source` | `000004_trigger_sensor_event_rule` | `SET NULL` | Already nullable; `source_ref` preserved |
| `workflow_execution.workflow_def` | `000007_workflow_system` | `CASCADE` | Meaningless without definition |
### 3. Model & Code Updates
- **Rule model** (`crates/common/src/models.rs`): Changed `action: Id` and `trigger: Id` to `Option<Id>`
- **RuleResponse DTO** (`crates/api/src/dto/rule.rs`): Changed `action` and `trigger` to `Option<i64>`
- **Enforcement processor** (`crates/executor/src/enforcement_processor.rs`): Added guards to skip execution when a rule's action or trigger has been deleted (SET NULL)
- **Pack delete endpoint** (`crates/api/src/routes/packs.rs`): Added filesystem cleanup to remove pack directory from permanent storage on deletion
### 4. Test Updates
- `crates/common/tests/rule_repository_tests.rs`: Updated assertions to use `Some(id)` for nullable fields
- `crates/executor/src/enforcement_processor.rs` (tests): Updated test Rule construction with `Some()` wrappers
## Files Changed
- `migrations/20250101000004_trigger_sensor_event_rule.sql` — Added `ON DELETE SET NULL` to `event.source`
- `migrations/20250101000006_execution_system.sql` — Added `ON DELETE SET NULL` to `execution.action`, `.parent`, `.enforcement`; made `rule.action`/`.trigger` nullable with `ON DELETE SET NULL`
- `migrations/20250101000007_workflow_system.sql` — Added `ON DELETE CASCADE` to `workflow_execution.workflow_def`
- `crates/api/src/routes/packs.rs` — Reordered `install_pack`; added pack directory cleanup on delete
- `crates/api/src/dto/rule.rs` — Made `action`/`trigger` fields optional in `RuleResponse`
- `crates/common/src/models.rs` — Made `Rule.action`/`Rule.trigger` `Option<Id>`
- `crates/executor/src/enforcement_processor.rs` — Handle nullable action/trigger in enforcement processing
- `crates/common/tests/rule_repository_tests.rs` — Fixed test assertions
## Design Philosophy
Historical records (executions, events, enforcements) are preserved when their referenced entities are deleted. The text ref fields (`action_ref`, `trigger_ref`, `source_ref`, etc.) retain the reference for auditing, while the FK ID fields are set to NULL. Rules with deleted actions or triggers become non-functional but remain in the database for traceability.
## Verification
- `cargo check --all-targets --workspace` — zero warnings
- `cargo test --workspace --lib` — all 358 unit tests pass

View File

@@ -0,0 +1,74 @@
# Work Summary: Pack Installation Fixes (2026-02-13)
## Problem
The `/packs/install` web UI page was completely non-functional when attempting to install packs from git repositories. Multiple cascading issues prevented successful pack installation via the API.
## Issues Fixed
### 1. `git` binary missing from API container
**Error:** `Failed to execute git clone: No such file or directory (os error 2)`
The `install_from_git` method in `PackInstaller` runs `Command::new("git")` to clone repositories, but the runtime Docker image (`debian:bookworm-slim`) did not include `git`.
**Fix:** Added `git` to the runtime stage's `apt-get install` in `docker/Dockerfile.optimized`.
### 2. Pack tests ran before pack files existed at expected location
**Error:** `Pack directory not found: /opt/attune/packs/python_example`
The `execute_and_store_pack_tests` function always constructed the pack path as `packs_base_dir/pack_ref`, but during installation the pack files were still in a temp directory. The move to permanent storage happened *after* test execution.
**Fix:**
- Added `execute_pack_tests_at(pack_dir, ...)` method to `TestExecutor` that accepts an explicit directory path
- Added `pack_dir_override: Option<&std::path::Path>` parameter to `execute_and_store_pack_tests`
- `register_pack_internal` now passes the actual pack path through to tests
### 3. Missing test config treated as installation failure
**Error:** `No testing configuration found in pack.yaml for pack 'python_example'`
Packs without a `testing` section in `pack.yaml` could not be installed without `force=true`, because the absence of test config was returned as an error.
**Fix:** Changed `execute_and_store_pack_tests` return type from `Result<PackTestResult>` to `Option<Result<PackTestResult>>`. Returns `None` when no testing config exists or testing is disabled, which the caller treats as "no tests to run" (success). All `?` operators were replaced with explicit `match`/`return` to work with the `Option<Result<...>>` return type.
### 4. Packs volume mounted read-only on API container
**Error:** `Read-only file system (os error 30)`
The `packs_data` volume was mounted as `:ro` on the API container, and files were owned by root (written by `init-packs` running as root). The API service (running as user `attune`, uid 1000) could not write.
**Fix:**
- Changed volume mount from `packs_data:/opt/attune/packs:ro` to `:rw` in `docker-compose.yaml`
- Added `chown -R 1000:1000 "$TARGET_PACKS_DIR"` to `docker/init-packs.sh` (runs after initial pack copy and again after all packs loaded)
### 5. Pack components not loaded into database
**Symptom:** Pack installed successfully but actions, triggers, and sensors not visible in the UI.
The `register_pack_internal` function only created the `pack` table record and synced workflows. It never loaded the pack's individual components (actions, triggers, sensors) from their YAML definition files. This was previously only handled by the Python `load_core_pack.py` script during `init-packs`.
**Fix:** Created `PackComponentLoader` in `crates/common/src/pack_registry/loader.rs` — a Rust-native pack component loader that:
- Reads `triggers/*.yaml` and creates trigger records via `TriggerRepository`
- Reads `actions/*.yaml` and creates action records with full field support (parameter_delivery, parameter_format, output_format) via direct SQL
- Reads `sensors/*.yaml` and creates sensor records via `SensorRepository`, resolving trigger and runtime references
- Loads in dependency order: triggers → actions → sensors
- Skips components that already exist (idempotent)
- Resolves runtime IDs by looking up common ref patterns (e.g., `shell``core.action.shell`)
Integrated into `register_pack_internal` so both the `/packs/install` and `/packs/register` endpoints load components.
### 6. Pack stored with version suffix in directory name
**Symptom:** Pack stored at `python_example-1.0.0` but workers/sensors look for `python_example`.
`PackStorage::install_pack` was called with `Some(&pack.version)`, creating a versioned directory name. The rest of the system expects `packs_base_dir/pack_ref` without version.
**Fix:** Changed to `install_pack(&installed.path, &pack.r#ref, None)` to match the system convention.
## Files Changed
| File | Change |
|------|--------|
| `docker/Dockerfile.optimized` | Added `git` to runtime dependencies |
| `docker/init-packs.sh` | Added `chown -R 1000:1000` for attune user write access |
| `docker-compose.yaml` | Changed packs volume mount from `:ro` to `:rw` on API |
| `crates/common/src/test_executor.rs` | Added `execute_pack_tests_at` method |
| `crates/common/src/pack_registry/loader.rs` | **New file**`PackComponentLoader` |
| `crates/common/src/pack_registry/mod.rs` | Added `loader` module and re-exports |
| `crates/api/src/routes/packs.rs` | Fixed test execution path, no-test-config handling, component loading, storage path |

View File

@@ -0,0 +1,99 @@
# 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

View File

@@ -0,0 +1,98 @@
# Runtime Type Removal, YAML Loading & Ref Format Cleanup
**Date:** 2026-02-13
## Problem
Running a Python action failed with:
```
Runtime not found: No runtime found for action: python_example.hello (available: shell)
```
The worker only had the Shell runtime registered. Investigation revealed four interrelated bugs:
1. **Runtime YAML files were never loaded into the database.** `PackComponentLoader::load_all()` loaded triggers, actions, and sensors but completely ignored the `runtimes/` directory. Files like `packs/core/runtimes/python.yaml` were dead weight.
2. **`load_core_pack.py` only created Shell + Sensor Builtin runtimes** via hardcoded `ensure_shell_runtime()` and `ensure_sensor_runtime()` methods instead of reading from YAML files.
3. **The worker filtered by `runtime_type == "action"`**, but this distinction (action vs sensor) was meaningless — a Python runtime should be usable for both actions and sensors.
4. **Runtime ref naming mismatch.** The Python YAML uses `ref: core.python`, but `resolve_runtime_id("python")` only looked for `core.action.python` and `python` — neither matched.
## Root Cause Analysis: `runtime_type` Was Meaningless
The `runtime_type` column (`action` | `sensor`) conflated *what the runtime is used for* with *what the runtime is*. Analysis of all usages showed:
- **Worker filter**: Only behavioral use — could be replaced by checking if `execution_config` has an interpreter configured.
- **`RuntimeRepository::find_by_type` / `find_action_runtimes`**: Never called anywhere in the codebase. Tests for them were already commented out.
- **`runtime_detection.rs`**: Was filtering by ref pattern (`NOT LIKE '%.sensor.builtin'`), not by `runtime_type`.
- **`DependencyManager::runtime_type()`**: Completely unrelated concept (returns "python"/"nodejs" for language identification).
The real distinction is whether a runtime has an `execution_config` with an interpreter — data that already exists. The column was redundant.
## Changes
### Column Removal: `runtime_type`
| File | Change |
|------|--------|
| `migrations/20250101000002_pack_system.sql` | Removed `runtime_type` column, its CHECK constraint, and its index |
| `crates/common/src/models.rs` | Removed `runtime_type` field from `Runtime` struct |
| `crates/common/src/repositories/runtime.rs` | Removed `runtime_type` from `CreateRuntimeInput`, `UpdateRuntimeInput`, all SELECT/INSERT/UPDATE queries; removed `find_by_type()` and `find_action_runtimes()` |
| `crates/worker/src/service.rs` | Replaced `runtime_type` filter with `execution_config` check (skip runtimes with empty config) |
| `crates/worker/src/executor.rs` | Removed `runtime_type` from runtime SELECT query |
| `crates/common/src/pack_environment.rs` | Removed `runtime_type` from runtime SELECT query |
| `crates/common/src/runtime_detection.rs` | Removed `runtime_type` from runtime SELECT query |
| `crates/common/tests/helpers.rs` | Removed `runtime_type` from `RuntimeFixture` |
| `crates/common/tests/repository_runtime_tests.rs` | Removed `runtime_type` from test fixtures |
| `crates/common/tests/repository_worker_tests.rs` | Removed `runtime_type` from test fixture |
| `crates/common/tests/migration_tests.rs` | Removed stale `runtime_type_enum` from expected enums list |
| `crates/executor/tests/fifo_ordering_integration_test.rs` | Removed `runtime_type` from test fixture |
| `crates/executor/tests/policy_enforcer_tests.rs` | Removed `runtime_type` from test fixture |
| `scripts/load_core_pack.py` | Removed `runtime_type` from INSERT/UPDATE queries |
### Runtime Ref Format Cleanup
Runtime refs now use a clean 2-part `{pack_ref}.{name}` format (e.g., `core.python`, `core.shell`, `core.builtin`). The old 3-part format with `action` or `sensor` segments (e.g., `core.action.shell`, `core.sensor.builtin`) is eliminated.
| File | Change |
|------|--------|
| `packs/core/runtimes/sensor_builtin.yaml` | Renamed ref from `core.sensor.builtin` to `core.builtin` |
| `crates/common/src/schema.rs` | Updated `validate_runtime_ref` to enforce 2-part `pack.name` format; updated tests |
| `crates/common/src/runtime_detection.rs` | Removed `WHERE ref NOT LIKE '%.sensor.builtin'` filter — no ref-based filtering needed |
| `crates/common/src/pack_registry/loader.rs` | Updated hardcoded sensor runtime ref to `core.builtin`; cleaned `resolve_runtime_id()` to use only `core.{name}` patterns (removed legacy `core.action.*` fallbacks) |
| `scripts/load_core_pack.py` | Updated `core.sensor.builtin` references to `core.builtin` |
| `crates/common/tests/repository_runtime_tests.rs` | Updated test refs from 3-part to 2-part format |
| `crates/common/tests/repository_worker_tests.rs` | Updated test ref from 3-part to 2-part format |
### Runtime YAML Loading
| File | Change |
|------|--------|
| `crates/common/src/pack_registry/loader.rs` | Added `load_runtimes()` method to read `runtimes/*.yaml` and insert into DB; added `runtimes_loaded`/`runtimes_skipped` to `PackLoadResult` |
| `crates/api/src/routes/packs.rs` | Updated log message to include runtime count |
| `scripts/load_core_pack.py` | Replaced hardcoded `ensure_shell_runtime()`/`ensure_sensor_runtime()` with `upsert_runtimes()` that reads all YAML files from `runtimes/` directory; added `resolve_action_runtime()` for smart runtime resolution |
### Error Reporting Improvement
| File | Change |
|------|--------|
| `crates/worker/src/executor.rs` | `handle_execution_failure` now accepts an `error_message` parameter. Actual error messages from `prepare_execution_context` and `execute_action` failures are stored in the execution result instead of the generic "Execution failed during preparation". |
## Component Loading Order
`PackComponentLoader::load_all()` now loads in dependency order:
1. **Runtimes** (no dependencies)
2. **Triggers** (no dependencies)
3. **Actions** (depend on runtimes)
4. **Sensors** (depend on triggers and runtimes)
## Deployment
Requires database reset (`docker compose down -v && docker compose up -d`) since the migration changed (column removed).
## Validation
- Zero compiler errors, zero warnings
- All 76 unit tests pass
- Integration test failures are pre-existing (missing `attune_test` database)

View File

@@ -0,0 +1,57 @@
# Worker On-Demand Virtualenv Creation
**Date:** 2026-02-14
## Problem
When installing Python packs via the API in Docker deployments, virtualenvs were never created. The API service attempted to run `python3 -m venv` during pack registration, but the API container (built from `debian:bookworm-slim`) does not have Python installed. The command failed silently (logged as a warning), and pack registration succeeded without a virtualenv.
When the worker later tried to execute a Python action, it fell back to the system Python interpreter instead of using an isolated virtualenv with the pack's dependencies installed.
## Root Cause
The architecture had a fundamental mismatch: environment setup (virtualenv creation, dependency installation) was performed in the **API service** during pack registration, but the API container lacks runtime interpreters like Python. The **worker service** — which has Python installed — had no mechanism to create environments on demand.
## Solution
Moved the primary responsibility for runtime environment creation from the API service to the worker service:
### Worker-Side: On-Demand Environment Creation
**File:** `crates/worker/src/runtime/process.rs`
Added environment setup at the beginning of `ProcessRuntime::execute()`. Before executing any action, the worker now checks if the runtime has environment configuration and ensures the environment exists:
- Calls `setup_pack_environment()` which is idempotent — it creates the virtualenv only if it doesn't already exist
- On failure, logs a warning and falls back to the system interpreter (graceful degradation)
- The virtualenv is created at `{pack_dir}/.venv` inside the shared packs volume, making it accessible across container restarts
### API-Side: Best-Effort with Clear Logging
**File:** `crates/api/src/routes/packs.rs`
Updated the API's environment setup to be explicitly best-effort:
- Changed log levels from `warn` to `info` for expected failures (missing interpreter)
- Added clear messaging that the worker will handle environment creation on first execution
- Added guard to skip dependency installation when the environment directory doesn't exist (i.e., venv creation failed)
- Preserved the setup attempt for non-Docker (bare-metal) deployments where the API host may have Python available
## How It Works
1. **Pack installation** (API): Registers pack in database, loads components, attempts environment setup (best-effort)
2. **First execution** (Worker): Detects missing `.venv`, creates it via `python3 -m venv`, installs dependencies from `requirements.txt`
3. **Subsequent executions** (Worker): `.venv` already exists, skips setup, resolves interpreter to `.venv/bin/python3`
## Files Changed
| File | Change |
|------|--------|
| `crates/worker/src/runtime/process.rs` | Added on-demand environment setup in `execute()` |
| `crates/api/src/routes/packs.rs` | Updated logging and made environment setup explicitly best-effort |
## Testing
- All 75 worker unit tests pass
- All 23 ProcessRuntime tests pass
- Zero compiler warnings