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):
| Source | Destination |
crates/pikaci/src/lib.rs | crates/jerichoci/src/lib.rs |
crates/pikaci/src/model.rs | crates/jerichoci/src/model.rs |
crates/pikaci/src/run.rs | crates/jerichoci/src/run.rs |
crates/pikaci/src/snapshot.rs | crates/jerichoci/src/snapshot.rs |
crates/pikaci/src/executor.rs | crates/jerichoci/src/executor.rs |
crates/pikaci/src/executor/incus.rs | crates/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::.
PikaciRunStore → JerichociRunStore
TestPikaciRunFixture → TestJerichociRunFixture
TestPikaciJobFixture → TestJerichociJobFixture
require_pikaci_run_store → require_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 (TestPikaciRunFixture → TestJerichociRunFixture, 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:
PikaciRunStore → JerichociRunStore
TestPikaciRunFixture → TestJerichociRunFixture
TestPikaciJobFixture → TestJerichociJobFixture
pikaci::RunLifecycleEvent → jerichoci::RunLifecycleEvent
pikaci::RunStatus → jerichoci::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:
| Invariant | Change |
| PIKACI-001 | Statement now covers both crates: jerichoci may depend on pika-cloud; pikaci may depend on jerichoci. Scope adds crates/jerichoci/**. |
| PIKACI-002 | Reworded to locate the reusable engine in crates/jerichoci/**. Scope and hint updated. |
| PIKACI-003 | Scope expanded to include crates/jerichoci/**. |
| PIKACI-004 | Scope narrowed to just crates/jerichoci/** since placement/runtime modeling is purely an engine concern. |
| PIKACI-005 | Scope 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/**).