Bump the wire-protocol schema version to 2
Intent: Signal a breaking change in the IncusGuestRunRequest JSON contract so that mismatched host/guest pairs fail fast on version mismatch rather than silently misinterpreting the payload.
Affected files: crates/pika-cloud/src/lib.rs, nix/incus/pikaci-image.nix
Evidence
@@ -34,7 +34,7 @@ pub const CMD_SCHEMA_V1: &str = "agent.control.cmd.v1";
-pub const INCUS_GUEST_RUN_REQUEST_SCHEMA_VERSION: u32 = 1;
+pub const INCUS_GUEST_RUN_REQUEST_SCHEMA_VERSION: u32 = 2;
@@ -14,7 +14,7 @@ let
- INCUS_GUEST_RUN_REQUEST_SCHEMA_VERSION = 1
+ INCUS_GUEST_RUN_REQUEST_SCHEMA_VERSION = 2
The constant INCUS_GUEST_RUN_REQUEST_SCHEMA_VERSION is bumped from 1 to 2 in both the Rust shared-types crate (pika-cloud/src/lib.rs:37) and the Nix guest image script (nix/incus/pikaci-image.nix:17). Because the guest validates the incoming version before processing a request, any host still sending version-1 payloads will be cleanly rejected. This is the coordination point that makes the rest of the cleanup safe to deploy.
Remove directory-path fields from IncusGuestRunRequest struct
Intent: Eliminate the five host-supplied directory paths from the shared Rust data model, shrinking the request surface and preventing the host from influencing guest filesystem layout.
Affected files: crates/pika-cloud/src/lib.rs
Evidence
@@ -65,11 +65,6 @@ pub struct IncusGuestRunRequest {
pub command: String,
pub timeout_secs: u64,
pub run_as_root: bool,
- pub workspace_dir: String,
- pub cargo_home_dir: String,
- pub target_dir: String,
- pub xdg_state_home_dir: String,
- pub home_dir: String,
The IncusGuestRunRequest struct in crates/pika-cloud/src/lib.rs:65-70 previously carried five String fields that told the guest which directories to use for the workspace, Cargo home, build target, XDG state, and user home. These are removed entirely. The struct now contains only schema_version, command, timeout_secs, and run_as_root — the minimal information the host legitimately needs to supply. The corresponding round-trip serialization test is updated to match.
Remove host-side constants and request-building logic for the deleted fields
Intent: Clean up the orchestrator code that previously populated the now-removed fields, along with the constants they referenced.
Affected files: crates/pikaci/src/executor.rs, crates/pikaci/src/executor/incus.rs
Evidence
@@ -227,10 +227,6 @@ const REMOTE_LINUX_VM_INCUS_PROFILE_DEFAULT: &str = "pika-agent-dev";
-const REMOTE_LINUX_VM_INCUS_CARGO_HOME_DIR: &str = "/cargo-home";
-const REMOTE_LINUX_VM_INCUS_TARGET_DIR: &str = "/cargo-target";
-const REMOTE_LINUX_VM_INCUS_XDG_STATE_HOME_DIR: &str = "/run/pika-cloud/xdg-state";
-const REMOTE_LINUX_VM_INCUS_NON_ROOT_HOME_DIR: &str = "/home/pikaci";
@@ -46,15 +46,6 @@ pub(super) fn build_remote_incus_guest_request(job: &JobSpec) -> IncusGuestRunRe
- workspace_dir: REMOTE_LINUX_VM_INCUS_SNAPSHOT_MOUNT_PATH.to_string(),
- cargo_home_dir: REMOTE_LINUX_VM_INCUS_CARGO_HOME_DIR.to_string(),
- target_dir: REMOTE_LINUX_VM_INCUS_TARGET_DIR.to_string(),
- xdg_state_home_dir: REMOTE_LINUX_VM_INCUS_XDG_STATE_HOME_DIR.to_string(),
- home_dir: if run_as_root {
- "/root".to_string()
- } else {
- REMOTE_LINUX_VM_INCUS_NON_ROOT_HOME_DIR.to_string()
- },
Four host-side constants (REMOTE_LINUX_VM_INCUS_CARGO_HOME_DIR, REMOTE_LINUX_VM_INCUS_TARGET_DIR, REMOTE_LINUX_VM_INCUS_XDG_STATE_HOME_DIR, REMOTE_LINUX_VM_INCUS_NON_ROOT_HOME_DIR) are deleted from crates/pikaci/src/executor.rs:230-233.
The build_remote_incus_guest_request function in crates/pikaci/src/executor/incus.rs:46-57 no longer populates any directory fields, including the conditional home_dir logic that previously selected /root vs /home/pikaci based on run_as_root. That decision now lives exclusively inside the guest image.
Move directory ownership into the guest image Nix script
Intent: Make the guest image the single source of truth for its own filesystem layout, so paths can be changed in one place without coordinating a host release.
Affected files: nix/incus/pikaci-image.nix
Evidence
@@ -123,6 +123,12 @@ let
state_dir = pathlib.Path("/run/pika-cloud")
logs_dir = state_dir / "logs"
artifacts_dir = state_dir / "artifacts"
+ workspace_dir = pathlib.Path("/workspace/snapshot")
+ cargo_home_dir = pathlib.Path("/cargo-home")
+ target_dir = pathlib.Path("/cargo-target")
+ xdg_state_home_dir = state_dir / "xdg-state"
+ root_home_dir = pathlib.Path("/root")
+ non_root_home_dir = pathlib.Path("/home/pikaci")
@@ -147,13 +153,6 @@ let
- workspace_dir = validate_absolute_dir(request.get("workspace_dir"), "workspace_dir")
- cargo_home_dir = validate_absolute_dir(request.get("cargo_home_dir"), "cargo_home_dir")
- target_dir = validate_absolute_dir(request.get("target_dir"), "target_dir")
- xdg_state_home_dir = validate_absolute_dir(
- request.get("xdg_state_home_dir"), "xdg_state_home_dir"
- )
- home_dir = validate_absolute_dir(request.get("home_dir"), "home_dir")
@@ -199,10 +198,10 @@ let
- child_env["HOME"] = str(home_dir)
+ child_env["HOME"] = str(root_home_dir)
child_command = command_prefix
else:
- child_env["HOME"] = str(home_dir)
+ child_env["HOME"] = str(non_root_home_dir)
Six path constants are defined at the top of the guest boot script in nix/incus/pikaci-image.nix:126-131: workspace_dir, cargo_home_dir, target_dir, xdg_state_home_dir, root_home_dir, and non_root_home_dir.
The request-parsing block no longer extracts or validates these paths from the JSON payload (lines 153-159 removed). The HOME environment variable is now set from the locally defined root_home_dir or non_root_home_dir rather than the request's home_dir field. The remaining directory variables (workspace_dir, cargo_home_dir, target_dir, xdg_state_home_dir) were already referenced downstream in the script for environment setup, so they continue to work unchanged — they simply draw from local constants now instead of untrusted input.
Update and add tests to enforce the runtime contract
Intent: Ensure existing tests compile after the field removal and add a new contract test that pins the well-known runtime paths the guest image exposes.
Affected files: crates/pikaci/src/executor.rs
Evidence
@@ -2593,17 +2591,6 @@ mod tests {
assert_eq!(request.command, "bash --noprofile --norc -lc 'actionlint'");
assert_eq!(request.timeout_secs, 120);
assert!(!request.run_as_root);
- assert_eq!(
- request.workspace_dir,
- REMOTE_LINUX_VM_INCUS_SNAPSHOT_MOUNT_PATH
- );
- assert_eq!(request.cargo_home_dir, REMOTE_LINUX_VM_INCUS_CARGO_HOME_DIR);
- assert_eq!(request.target_dir, REMOTE_LINUX_VM_INCUS_TARGET_DIR);
@@ -2616,7 +2603,14 @@ mod tests {
assert!(root_request.run_as_root);
- assert_eq!(root_request.home_dir, "/root");
+ }
+
+ #[test]
+ fn remote_linux_incus_runtime_contract_paths_match_image_defaults() {
+ assert_eq!(CLOUD_GUEST_LOG_PATH, "/run/pika-cloud/logs/guest.log");
+ assert_eq!(STATUS_PATH, "/run/pika-cloud/status.json");
+ assert_eq!(EVENTS_PATH, "/run/pika-cloud/events.jsonl");
+ assert_eq!(RESULT_PATH, "/run/pika-cloud/result.json");
The existing remote_linux_incus_guest_request_round_trip test has all assertions against the removed fields deleted, keeping only the checks for command, timeout_secs, and run_as_root. The conditional home_dir assertion on root requests is also removed.
A new test remote_linux_incus_runtime_contract_paths_match_image_defaults is introduced at crates/pikaci/src/executor.rs:2608-2614. It imports CLOUD_GUEST_LOG_PATH, STATUS_PATH, EVENTS_PATH, and RESULT_PATH from pika_cloud and asserts their exact string values. This acts as a compile-time and CI-time contract: if the shared crate ever changes these paths, the test fails, alerting developers that the guest image must be updated in tandem. The imports at the top of the test module are adjusted accordingly to bring in these additional constants.