Back to feed

sledtools/pika branch #99

pika-orch-incus-cleanup-23

Centralize payload manifest helpers

Target branch: master

Merge Commit: 9c9d1512a73ac17acf9a65a53aa5bb75e0ac24b7

branch: merged tutorial: ready ci: success
Open CI Details

Continuous Integration

CI: success

Compact status on the review page, with full logs on the CI page.

Open CI Details

Latest run #125 success

6 passed

head 941ab2cf78dfa83ca28420038731bb0157e8c4ba · queued 2026-03-26 01:49:47 · 6 lane(s)

queued 40s · ran 30s

check-notifications · success check-agent-contracts · success check-pikachat · success check-pikachat-typescript · success check-pikachat-openclaw-e2e · success check-fixture · success

Summary

This branch centralizes duplicated payload manifest helpers—path construction and JSON decoding—into the shared model module. Before this change, the magic path string share/pikaci/payload-manifest.json was hard-coded in three separate locations (incus.rs, run.rs twice), and each call-site independently called serde_json::from_slice with its own context message. The branch extracts a canonical constant (PREPARED_OUTPUT_PAYLOAD_MANIFEST_RELATIVE_PATH), a path-builder (prepared_output_payload_manifest_path), and a decode helper (decode_prepared_output_payload_manifest) into model.rs, then rewires every consumer to use them. This eliminates the risk of the path or decode logic drifting between call-sites and reduces per-site boilerplate.

Tutorial Steps

Add canonical helpers to model.rs

Intent: Introduce a single source of truth for the payload manifest relative path, a path-builder function, and a JSON decode function so that no call-site needs to duplicate this logic.

Affected files: crates/pikaci/src/model.rs

Evidence
@@ -1,3 +1,6 @@
+use std::path::{Path, PathBuf};
+
+use anyhow::Context;
 use serde::{Deserialize, Serialize};
@@ -1554,6 +1557,20 @@ pub struct PreparedOutputPayloadManifestRecord {
     pub mounts: Vec<PreparedOutputPayloadMountRecord>,
 }
 
+pub const PREPARED_OUTPUT_PAYLOAD_MANIFEST_RELATIVE_PATH: &str =
+    "share/pikaci/payload-manifest.json";
+
+pub fn prepared_output_payload_manifest_path(output_root: &Path) -> PathBuf {
+    output_root.join(PREPARED_OUTPUT_PAYLOAD_MANIFEST_RELATIVE_PATH)
+}
+
+pub fn decode_prepared_output_payload_manifest(
+    bytes: &[u8],
+    manifest_path: &Path,
+) -> anyhow::Result<PreparedOutputPayloadManifestRecord> {
+    serde_json::from_slice(bytes).with_context(|| format!("decode {}", manifest_path.display()))
+}

Three new public items are added directly below PreparedOutputPayloadManifestRecord in model.rs:

  1. PREPARED_OUTPUT_PAYLOAD_MANIFEST_RELATIVE_PATH – a &str constant holding "share/pikaci/payload-manifest.json". This was previously a local const in run.rs and an inline literal in incus.rs.
  2. prepared_output_payload_manifest_path(output_root) – joins the constant onto any output root, returning a PathBuf.
  3. decode_prepared_output_payload_manifest(bytes, manifest_path) – wraps serde_json::from_slice with an anyhow context that includes the manifest path in the error message.

New use imports for std::path::{Path, PathBuf} and anyhow::Context are added at the top of the file to support these additions.

Migrate incus.rs to the shared helpers

Intent: Replace the inline path literal and manual serde call in `load_remote_payload_manifest` with the new model helpers, removing duplicated logic from the Incus executor.

Affected files: crates/pikaci/src/executor/incus.rs

Evidence
@@ -1,5 +1,8 @@
 use super::*;
-use crate::model::{PreparedOutputPayloadManifestRecord, PreparedOutputPayloadMountRecord};
+use crate::model::{
+    PreparedOutputPayloadManifestRecord, PreparedOutputPayloadMountRecord,
+    decode_prepared_output_payload_manifest, prepared_output_payload_manifest_path,
+};
@@ -523,7 +526,7 @@ fn load_remote_payload_manifest(
-    let manifest_path = output_root.join("share/pikaci/payload-manifest.json");
+    let manifest_path = prepared_output_payload_manifest_path(output_root);
@@ -545,9 +548,10 @@ fn load_remote_payload_manifest(
-    let manifest = serde_json::from_slice(&output.stdout)
-        .with_context(|| format!("decode remote payload manifest {}", manifest_path.display()))?;
-    Ok(Some(manifest))
+    Ok(Some(decode_prepared_output_payload_manifest(
+        &output.stdout,
+        &manifest_path,
+    )?))

In crates/pikaci/src/executor/incus.rs, two changes are made inside load_remote_payload_manifest:

  • Path construction (incus.rs:529): The hard-coded output_root.join("share/pikaci/payload-manifest.json") is replaced by prepared_output_payload_manifest_path(output_root), delegating to the shared helper.
  • Deserialization (incus.rs:551-554): The inline serde_json::from_slice(&output.stdout).with_context(…) block is replaced by a call to decode_prepared_output_payload_manifest(&output.stdout, &manifest_path). Note that the previous context string included the word "remote"; the centralized helper uses a shorter "decode {path}" format. This is an intentional normalization of error messages across all call-sites.

The import block at the top of the file is expanded to bring decode_prepared_output_payload_manifest and prepared_output_payload_manifest_path into scope.

Migrate run.rs to the shared helpers and remove the local constant

Intent: Delete the duplicated local constant and inline deserialization calls in run.rs, replacing them with imports from model.rs.

Affected files: crates/pikaci/src/run.rs

Evidence
@@ -22,7 +22,8 @@ use crate::model::{
-    ExecuteNode, JobLogMetadata, JobRecord, JobSpec, PlanExecutorKind, PlanNodeRecord, PlanScope,
+    ExecuteNode, JobLogMetadata, JobRecord, JobSpec,
+    PREPARED_OUTPUT_PAYLOAD_MANIFEST_RELATIVE_PATH, PlanExecutorKind, PlanNodeRecord, PlanScope,
@@ -33,7 +34,8 @@ use crate::model::{
-    StagedLinuxRustTarget,
+    StagedLinuxRustTarget, decode_prepared_output_payload_manifest,
+    prepared_output_payload_manifest_path,
@@ -933,19 +935,17 @@ pub fn load_run_bundle(state_root: &Path, run_id: &str) -> anyhow::Result<RunBun
-const PREPARED_OUTPUT_PAYLOAD_MANIFEST_RELATIVE_PATH: &str = "share/pikaci/payload-manifest.json";
-
 fn load_prepared_output_payload_manifest(
     realized_path: &Path,
 ) -> anyhow::Result<Option<PreparedOutputPayloadManifestRecord>> {
-    let path = realized_path.join(PREPARED_OUTPUT_PAYLOAD_MANIFEST_RELATIVE_PATH);
+    let path = prepared_output_payload_manifest_path(realized_path);
@@ -1006,9 +1006,10 @@ fn load_remote_prepared_output_payload_manifest(
-    let manifest = serde_json::from_slice(&output.stdout)
-        .with_context(|| format!("decode remote {}", manifest_path.display()))?;
-    Ok(Some(manifest))
+    Ok(Some(decode_prepared_output_payload_manifest(
+        &output.stdout,
+        &manifest_path,
+    )?))

run.rs had two functions that duplicated the same pattern: load_prepared_output_payload_manifest (local filesystem) and load_remote_prepared_output_payload_manifest (SSH). Both are updated:

  1. Deleted local constant (run.rs, previously line 936-937): The file-level const PREPARED_OUTPUT_PAYLOAD_MANIFEST_RELATIVE_PATH is removed entirely. The constant now lives in model.rs and is imported where still needed (it is used elsewhere in run.rs for building SSH cat commands, so the import remains).

  2. load_prepared_output_payload_manifest (run.rs:938): Path construction switches from a manual .join(…) to prepared_output_payload_manifest_path(realized_path). The inline serde_json::from_slice + .with_context is replaced by decode_prepared_output_payload_manifest(&bytes, &path).

  3. load_remote_prepared_output_payload_manifest (run.rs:1009-1012): Same decode replacement. The previous error context included the word "remote"; the centralized helper normalizes this to "decode {path}", which still contains the full remote path and is unambiguous.

The import block at the top of run.rs is updated to bring in PREPARED_OUTPUT_PAYLOAD_MANIFEST_RELATIVE_PATH, decode_prepared_output_payload_manifest, and prepared_output_payload_manifest_path from crate::model.

Diff