Back to feed

sledtools/pika branch #133

pika-orch-incus-cleanup-23

Split jerichoci from pikaci

Target branch: master

Merge Commit: 7ee2bde7e99d2006dcad749984d3e4b504b4842b

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

Continuous Integration

CI: failed

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

Open CI Details

Latest run #168 failed

9 passed 1 failed

head 13118be2d317bbac2c76c073fb413c140bffec4b · queued 2026-03-27 00:38:06 · 10 lane(s)

queued 1m 02s · ran 17m 02s

check-pika-rust · success check-pika-followup · success check-notifications · success check-agent-contracts · success check-rmp · success check-pikachat · success check-pikachat-typescript · success check-apple-host-sanity · failed check-pikachat-openclaw-e2e · success check-fixture · success

Summary

This branch extracts the reusable CI engine core from the pikaci crate into a new standalone library crate called jerichoci. All generic modules — lib.rs, model.rs, run.rs, snapshot.rs, and the executor subsystem — are moved from crates/pikaci/src/ to crates/jerichoci/src/, while the Pika-specific frontend (catalogs, CLI, target definitions) remains in crates/pikaci/. The pikaci crate now depends on jerichoci instead of owning the engine code directly. Downstream consumers (pika-git) switch their dependency from pikaci to jerichoci, and all import paths, struct names, and store references are updated accordingly. Architectural invariants in invariants.toml are revised to codify the new two-crate boundary, including a new PIKACI-006 rule that prevents generic engine modules from migrating back into pikaci.

Tutorial Steps

Create the jerichoci library crate

Intent: Introduce a new crate that will house the generic, reusable CI engine core previously embedded inside pikaci.

Affected files: crates/jerichoci/Cargo.toml, Cargo.toml, Cargo.lock

Evidence
@@ -0,0 +1,16 @@
+[package]
+name = "jerichoci"
+version = "0.1.0"
+edition = "2024"
+
+[dependencies]
+anyhow = { workspace = true }
+chrono = { workspace = true }
+fs2 = { workspace = true }
+serde = { workspace = true }
+serde_json = { workspace = true }
+sha2 = { workspace = true }
+hex = { workspace = true }
+toml = { workspace = true }
+pika-cloud = { path = "../pika-cloud" }
+uuid = { workspace = true }
@@ -4,6 +4,7 @@ members = [
   "rust",
   "uniffi-bindgen",
   "cli",
+  "crates/jerichoci",
@@ -33,6 +34,7 @@ mdk-sqlite-storage = ...
+jerichoci = { path = "crates/jerichoci" }

A new crate crates/jerichoci is created with its own Cargo.toml. It carries the full set of dependencies that the engine core needs (anyhow, chrono, fs2, sha2, hex, serde, serde_json, toml, pika-cloud, uuid). The workspace root Cargo.toml registers jerichoci both as a workspace member and as a workspace-level dependency so other crates can reference it with { workspace = true }.

Move generic engine modules from pikaci to jerichoci

Intent: Relocate all reusable CI engine source files — the library entry point, data model, run orchestration, snapshot logic, and executor backends — into the new jerichoci crate without modifying their contents.

Affected files: crates/jerichoci/src/lib.rs, crates/jerichoci/src/model.rs, crates/jerichoci/src/run.rs, crates/jerichoci/src/snapshot.rs, crates/jerichoci/src/executor.rs, crates/jerichoci/src/executor/incus.rs

Evidence
rename from crates/pikaci/src/lib.rs
rename to crates/jerichoci/src/lib.rs
rename from crates/pikaci/src/model.rs
rename to crates/jerichoci/src/model.rs
rename from crates/pikaci/src/run.rs
rename to crates/jerichoci/src/run.rs
rename from crates/pikaci/src/snapshot.rs
rename to crates/jerichoci/src/snapshot.rs
rename from crates/pikaci/src/executor.rs
rename to crates/jerichoci/src/executor.rs
rename from crates/pikaci/src/executor/incus.rs
rename to crates/jerichoci/src/executor/incus.rs

Six files are moved via pure renames (100% similarity):

SourceDestination
crates/pikaci/src/lib.rscrates/jerichoci/src/lib.rs
crates/pikaci/src/model.rscrates/jerichoci/src/model.rs
crates/pikaci/src/run.rscrates/jerichoci/src/run.rs
crates/pikaci/src/snapshot.rscrates/jerichoci/src/snapshot.rs
crates/pikaci/src/executor.rscrates/jerichoci/src/executor.rs
crates/pikaci/src/executor/incus.rscrates/jerichoci/src/executor/incus.rs

No content changes are needed; the files are byte-identical after the move. This is the core of the split — everything that is generic CI machinery now lives under jerichoci.

Slim down pikaci's dependencies and add jerichoci

Intent: Remove the dependencies that moved with the engine core from pikaci's Cargo.toml and replace them with a single dependency on jerichoci.

Affected files: crates/pikaci/Cargo.toml

Evidence
@@ -5,13 +5,9 @@ edition = "2024"
 
 [dependencies]
 anyhow = { workspace = true }
-chrono = { workspace = true }
 clap = { workspace = true, features = ["derive"] }
-fs2 = { workspace = true }
 serde = { workspace = true }
 serde_json = { workspace = true }
-sha2 = { workspace = true }
-hex = { workspace = true }
 toml = { workspace = true }
-pika-cloud = { path = "../pika-cloud" }
 uuid = { workspace = true }
+jerichoci = { workspace = true }

The pikaci crate sheds five direct dependencies (chrono, fs2, sha2, hex, pika-cloud) that were only needed by the engine core. In their place, a single jerichoci = { workspace = true } dependency is added. The crate retains clap (CLI framework), serde, serde_json, toml, uuid, and anyhow — the dependencies needed by its Pika-specific catalog, CLI, and target-selection code.

Update pikaci source files to import from jerichoci

Intent: Rewrite all use-statements in pikaci's remaining source files so they reference types from the jerichoci crate instead of the now-removed local modules.

Affected files: crates/pikaci/src/main.rs, crates/pikaci/src/catalog.rs, crates/pikaci/src/bin/pikaci-fulfill-prepared-output.rs, crates/pikaci/src/bin/pikaci-launch-fulfill-prepared-output.rs

Evidence
@@ -5,7 +5,7 @@ use anyhow::{Context, anyhow, bail};
-use pikaci::{
+use jerichoci::{
     GuestCommand, HostProcessRuntimeConfig, IncusRuntimeConfig, JobExecutionConfig,
@@ -1,6 +1,6 @@
 use serde::Serialize;
 
-use pikaci::{
+use jerichoci::{
     StagedLinuxCommandConfig, StagedLinuxRustPayloadRole,
@@ -24,14 +24,14 @@ fn main() -> anyhow::Result<()> {
     let result =
-        pikaci::fulfill_prepared_output_request_result(std::path::Path::new(&request_path));
+        jerichoci::fulfill_prepared_output_request_result(std::path::Path::new(&request_path));
@@ -15,7 +15,7 @@ fn main() -> anyhow::Result<()> {
     let launch_request =
-        pikaci::load_prepared_output_fulfillment_launch_request(Path::new(&request_path))
+        jerichoci::load_prepared_output_fulfillment_launch_request(Path::new(&request_path))

Every use pikaci::{ ... } and qualified path like pikaci::fulfill_prepared_output_request_result in the pikaci binary and library sources is changed to jerichoci::. This is a mechanical search-and-replace; no logic changes are involved.

Key files affected:

  • main.rs — the largest file, with dozens of qualified pikaci:: paths in match arms (e.g., pikaci::PreparedOutputConsumerKind) rewritten to jerichoci::. Function signatures that returned pikaci::RunRecord now return jerichoci::RunRecord.
  • catalog.rs — imports staged-linux config types from jerichoci instead of pikaci.
  • pikaci-fulfill-prepared-output.rs and pikaci-launch-fulfill-prepared-output.rs — helper binaries that call into the engine's prepared-output fulfillment APIs.

Switch pika-git from pikaci to jerichoci

Intent: Update the pika-git crate (the forge/git server) to depend on jerichoci for CI engine types, since it never needed pikaci's Pika-specific catalog layer.

Affected files: crates/pika-git/Cargo.toml, crates/pika-git/src/main.rs, crates/pika-git/src/forge.rs, crates/pika-git/src/web.rs, crates/pika-git/src/web/api.rs

Evidence
@@ -21,11 +21,11 @@ globset = "0.4"
+jerichoci = { workspace = true }
 nostr = { workspace = true }
 pika-forge-model = { path = "../pika-forge-model" }
-pikaci = { path = "../pikaci" }
@@ -11,12 +11,12 @@ mod forge;
+mod jerichoci_store;
 mod live;
-mod pikaci_store;
@@ -14,11 +14,11 @@ use anyhow::{anyhow, bail, Context};
-use pikaci::{RunLifecycleEvent, RunStatus};
+use jerichoci::{RunLifecycleEvent, RunStatus};
@@ -16,6 +16,7 @@ use futures::stream;
+use jerichoci::{LogKind, PreparedOutputsRecord, RunLogsMetadata, RunRecord};
-use pikaci::{LogKind, PreparedOutputsRecord, RunLogsMetadata, RunRecord};

The pika-git crate's Cargo.toml replaces pikaci = { path = "../pikaci" } with jerichoci = { workspace = true }. This is the correct dependency because pika-git only uses engine-level types (RunRecord, RunStatus, LogKind, etc.) — it has no need for Pika-specific catalogs.

In the module declaration (main.rs), mod pikaci_store becomes mod jerichoci_store. The forge and web modules update their import paths accordingly. In web/api.rs, the four API handler functions that load CI run data (api_forge_pikaci_run_handler, api_forge_pikaci_logs_handler, etc.) now call require_jerichoci_run_store(state.jerichoci_run_store.as_ref()) instead of the old require_pikaci_run_store.

Rename the pikaci_store module to jerichoci_store

Intent: Rename the on-disk run-store abstraction in pika-git to reflect that it now wraps jerichoci types, and update all struct/function names for consistency.

Affected files: crates/pika-git/src/jerichoci_store.rs

Evidence
rename from crates/pika-git/src/pikaci_store.rs
rename to crates/pika-git/src/jerichoci_store.rs
@@ -1,10 +1,10 @@
-use pikaci::{load_logs, load_run_bundle, load_run_record, LogKind, Logs, RunBundle, RunRecord};
+use jerichoci::{load_logs, load_run_bundle, load_run_record, LogKind, Logs, RunBundle, RunRecord};
@@ -14,13 +14,13 @@
-pub struct PikaciRunStore {
+pub struct JerichociRunStore {
@@ -291,7 +291,9 @@
-pub fn require_pikaci_run_store(store: Option<&PikaciRunStore>) -> Result<&PikaciRunStore> {
+pub fn require_jerichoci_run_store(
+    store: Option<&JerichociRunStore>,
+) -> Result<&JerichociRunStore> {

The file crates/pika-git/src/pikaci_store.rs is renamed to jerichoci_store.rs. Inside it:

  • All use pikaci:: imports become use jerichoci::.
  • PikaciRunStoreJerichociRunStore
  • TestPikaciRunFixtureTestJerichociRunFixture
  • TestPikaciJobFixtureTestJerichociJobFixture
  • require_pikaci_run_storerequire_jerichoci_run_store

The store's behavior is unchanged — it still reads run records, logs, and bundles from the same directory layout. Only the naming reflects the new crate boundary.

Update pika-git tests for the new naming

Intent: Ensure all test files in pika-git compile and reference the renamed store types and jerichoci imports.

Affected files: crates/pika-git/src/web/tests.rs, crates/pika-git/src/web/tests/api.rs

Evidence
@@ -10,7 +10,7 @@
-use crate::pikaci_store::{PikaciRunStore, TestPikaciJobFixture, TestPikaciRunFixture};
+use crate::jerichoci_store::{JerichociRunStore, TestJerichociJobFixture, TestJerichociRunFixture};
@@ -126,16 +126,16 @@ impl WebTestContext {
 fn write_pikaci_run_fixture(config: &Config, run_id: &str) {
-    let run_store = PikaciRunStore::from_config(config).expect("pikaci run store");
-    let mut fixture = TestPikaciRunFixture::passed(
+    let run_store = JerichociRunStore::from_config(config).expect("pikaci run store");
+    let mut fixture = TestJerichociRunFixture::passed(
@@ -474,7 +474,7 @@
-    let prepared_outputs_path = PikaciRunStore::from_config(&config)
+    let prepared_outputs_path = JerichociRunStore::from_config(&config)

The web test harness and API integration tests are updated:

  • tests.rs rewrites imports from crate::pikaci_store to crate::jerichoci_store and renames all fixture types (TestPikaciRunFixtureTestJerichociRunFixture, etc.).
  • The write_pikaci_run_fixture helper function internally switches to JerichociRunStore and TestJerichociRunFixture, and uses jerichoci::RemoteLinuxVmExecutionRecord instead of pikaci::RemoteLinuxVmExecutionRecord.
  • The AppState construction in test_state_with_live_buffer uses jerichoci_run_store as the field name.
  • tests/api.rs updates the prepared-outputs corruption test to use JerichociRunStore::from_config.

Update forge.rs tests for renamed fixtures

Intent: Ensure the forge module's integration tests use the jerichoci-namespaced fixtures and event constructors.

Affected files: crates/pika-git/src/forge.rs

Evidence
@@ -1228,7 +1228,7 @@ mod tests {
-    use pikaci::RunStatus;
+    use jerichoci::RunStatus;
@@ -1238,7 +1238,9 @@
-    use crate::pikaci_store::{PikaciRunStore, TestPikaciJobFixture, TestPikaciRunFixture};
+    use crate::jerichoci_store::{
+        JerichociRunStore, TestJerichociJobFixture, TestJerichociRunFixture,
+    };
@@ -1254,7 +1256,7 @@
-    fn event_line(event: pikaci::RunLifecycleEvent) -> String {
+    fn event_line(event: jerichoci::RunLifecycleEvent) -> String {
@@ -1529,15 +1531,17 @@
-        let run_store = PikaciRunStore::from_forge_repo(&forge_repo);
-        let mut fixture = TestPikaciRunFixture::passed(
+        let run_store = JerichociRunStore::from_forge_repo(&forge_repo);
+        let mut fixture = TestJerichociRunFixture::passed(

The forge.rs test module is one of the heaviest consumers of CI run fixtures. All references are updated:

  • PikaciRunStoreJerichociRunStore
  • TestPikaciRunFixtureTestJerichociRunFixture
  • TestPikaciJobFixtureTestJerichociJobFixture
  • pikaci::RunLifecycleEventjerichoci::RunLifecycleEvent
  • pikaci::RunStatusjerichoci::RunStatus

The event_line helper, staged_pikaci_lane_reports_run_id_and_human_log_summary, explicit_structured_pikaci_target_does_not_depend_on_wrapper_name, and structured_pikaci_failure_log_keeps_job_and_run_messages tests all receive these mechanical renames. No test logic changes.

Revise architectural invariants for the two-crate model

Intent: Update the project's machine-checked invariants to reflect and enforce the new jerichoci/pikaci boundary, and add a new invariant preventing regression.

Affected files: invariants/invariants.toml, scripts/test_check_invariants.py

Evidence
@@ -5,8 +5,9 @@
 id = "PIKACI-001"
-statement = "pikaci may depend on pika-cloud."
+statement = "jerichoci may depend on pika-cloud, and pikaci may depend on jerichoci."
 scope = [
+  "crates/jerichoci/**",
@@ -15,11 +16,12 @@
 id = "PIKACI-002"
-statement = "The reusable pikaci library layer does not define the Pika staged-lane catalog..."
+statement = "The reusable CI engine lives in crates/jerichoci/** and does not define the Pika staged-lane catalog..."
@@ -37,9 +40,9 @@
 id = "PIKACI-004"
-statement = "pikaci models execution placement separately from runtime backend."
+statement = "jerichoci models execution placement separately from runtime backend."
 scope = [
-  "crates/pikaci/**",
+  "crates/jerichoci/**",
@@ +50,12 @@
+[[invariant]]
+id = "PIKACI-006"
+area = "pikaci"
+kind = "must"
+statement = "crates/pikaci/** is a Pika-specific frontend layer on top of jerichoci. It may define catalogs, path filters, and CLI commands, but generic run/executor/snapshot core modules live in crates/jerichoci/**."
+hint = "Look for generic CI engine modules being reintroduced under crates/pikaci/src, such as reusable run records, executor backends, snapshotting, or lifecycle core logic."

Five existing invariants (PIKACI-001 through PIKACI-005) are updated:

InvariantChange
PIKACI-001Statement now covers both crates: jerichoci may depend on pika-cloud; pikaci may depend on jerichoci. Scope adds crates/jerichoci/**.
PIKACI-002Reworded to locate the reusable engine in crates/jerichoci/**. Scope and hint updated.
PIKACI-003Scope expanded to include crates/jerichoci/**.
PIKACI-004Scope narrowed to just crates/jerichoci/** since placement/runtime modeling is purely an engine concern.
PIKACI-005Scope expanded; statement specifies "production jerichoci code".

A new PIKACI-006 invariant is introduced to guard the boundary going forward: it explicitly states that crates/pikaci/** is a Pika-specific frontend and that generic engine modules must not migrate back from jerichoci.

The invariant-checking test in scripts/test_check_invariants.py is updated so the expected scope string in test_build_prompt_includes_all_fields matches the new three-element scope array (crates/jerichoci/**, crates/pikaci/**, ci/**).

Diff