This branch unifies the pikaci target and lane catalogs by eliminating the redundant staged_linux_target field from ForgeLane and instead deriving the target key directly from the lane's id. The hardcoded lane definitions in forge_lanes.rs are replaced by a new declarative TargetSpec catalog in targets.rs that serves as the single source of truth for both the CI runner (pikaci) and the forge server (pika-git). Lane IDs are renamed to match their target IDs (e.g., pika_rust becomes pre-merge-pika-rust), visibility of catalog types is widened from pub(crate) to pub, and test infrastructure gains a thread-local manifest override mechanism so integration tests no longer depend on the compiled production manifest.
Tutorial Steps
Remove the `staged_linux_target` field from `ForgeLane`
Intent: Eliminate the redundant optional field that mapped lanes to targets. Since lane IDs will now match target IDs directly, the mapping is implicit and the extra field is unnecessary.
The ForgeLane struct previously carried an Option<String> field called staged_linux_target that explicitly mapped a lane to its CI target. With the new convention that lane IDs are target IDs, this field becomes redundant.
Every ForgeLane construction site across the codebase — branch store tests, CI tests, web API tests, live-stream tests, and render tests — drops the staged_linux_target field. Where a lane previously had id: "pika" with staged_linux_target: Some("pre-merge-pika-rust"), it now simply uses id: "pre-merge-pika-rust" directly.
Derive target key from lane ID in `ci_state.rs`
Intent: Update the target-key resolution logic to use the lane's `id` field instead of the removed `staged_linux_target` field, completing the implicit-mapping convention.
The configured_target_key_for_lane function in ci_state.rs is the runtime bridge between a lane and its infrastructure target. Previously it passed lane.staged_linux_target to effective_target_key; now it passes lane.id directly. This is the key behavioral change that makes the "lane ID equals target ID" convention work at runtime.
Introduce the `TargetSpec` catalog in `targets.rs`
Intent: Create a single declarative data structure that defines every CI target — its ID, description, trigger type (branch/nightly/none), path filters, concurrency group, and entrypoint command — replacing the sprawling hand-coded lane constructors.
A new file crates/pikaci/src/targets.rs introduces the TargetSpec struct and supporting types:
CiTrigger — an enum with None, Branch, and Nightly variants, each optionally carrying a ConcurrencyGroupSpec.
ConcurrencyGroupSpec — either SameAsTarget (the concurrency group equals the target ID) or Named(String) for shared groups like apple-compile.
TargetSpec — bundles a PikaStagedLinuxTarget variant, its CiTrigger, and a filters list of glob patterns.
all_target_specs() — returns the full ordered list of specs, replacing hundreds of lines of hand-coded lane constructors.
This catalog is the new single source of truth. Both the CI runner binary and the forge server derive their lane lists from it.
Rewrite `compiled_forge_ci_manifest()` to consume `TargetSpec`s
Intent: Replace the hardcoded list of `staged_linux_lane(...)` and `pikaci_target_lane(...)` calls with a loop over `all_target_specs()`, projecting each spec into a `ForgeLane`.
The function compiled_forge_ci_manifest() in forge_lanes.rs was the most dramatic reduction: ~400 lines of hand-written lane definitions collapsed into a compact loop.
For each TargetSpec returned by all_target_specs():
If the trigger is CiTrigger::None, the target is skipped (it has no CI lane).
If Branch, a ForgeLane is created with path filters resolved via source_of_truth_paths() and pushed to branch_lanes.
If Nightly, a ForgeLane with empty paths is pushed to nightly_lanes.
The helper project_lane constructs the ForgeLane from the spec, using the target's config to populate id, title, entrypoint, command, and the resolved concurrency_group. A new constant TARGET_CATALOG_DEFINITION_PATH is also exported for use as a source-of-truth path.
Update `ci_store.rs` to use the canonical lane definition path
Intent: Replace the hardcoded `crates/pikaci/src/forge_lanes.rs` string with the `FORGE_LANE_DEFINITION_PATH` constant so the manifest source path stays consistent if renamed.
The manifest source path stored in the database during queue_branch_ci_run_for_head and record_branch_ci_failure now uses the FORGE_LANE_DEFINITION_PATH constant instead of a hardcoded string.
When inserting lane rows, the staged_linux_target column is populated with Some(lane.id.clone()) instead of lane.staged_linux_target, mirroring the convention established in ci_state.rs.
The same pattern applies to queue_nightly_run.
Update `mirror_store.rs` to use the canonical path constant
Intent: Ensure the mirror store also references the lane definition path through the constant rather than a hardcoded string.
A single-line change in mirror_store.rs replaces the hardcoded path string with crate::ci::FORGE_LANE_DEFINITION_PATH, keeping all references to the lane definition file location centralized.
Widen visibility of catalog types from `pub(crate)` to `pub`
Intent: Allow the `pika-git` crate (and potentially other consumers) to access the target catalog types that were previously crate-private to `pikaci`.
Five items in catalog.rs change from pub(crate) to pub:
PikaStagedLinuxTarget enum
PikaStagedLinuxTargetConfig struct
PikaStagedLinuxLane enum
PikaStagedLinuxLane::command_config() method
PikaStagedLinuxTarget::config() method
PikaStagedLinuxTargetInfoJson struct
The lib.rs re-export is also updated: pub mod catalog and pub mod targets are now publicly visible modules, allowing pika-git to import TargetSpec and related types.
Introduce thread-local test manifest override in `ci.rs`
Intent: Decouple integration tests from the compiled production manifest so tests can declare exactly which lanes they expect, preventing breakage when the real manifest changes.
@@ -42,6 +70,10 @@
+ #[cfg(test)]
+ if let Some(manifest) = TEST_MANIFEST_OVERRIDE.with(|slot| slot.borrow().clone()) {
+ return Ok(manifest);
+ }
A #[cfg(test)]-gated thread-local TEST_MANIFEST_OVERRIDE is added along with:
install_test_manifest_override(manifest) — sets the override and returns a RAII guard.
TestManifestOverrideGuard — clears the override on drop.
Both load_manifest_from_default_branch and load_manifest_for_head check this thread-local before falling through to the compiled manifest. This lets each test install a minimal manifest with only the lanes it cares about, eliminating coupling to production lane definitions.
Helper functions test_lane(), test_manifest(), and write_lane_definition_file() are added to the test module for ergonomic manifest construction.
Update all CI integration tests to use manifest overrides
Intent: Migrate every test that previously relied on the global compiled manifest to instead install an explicit test manifest, ensuring tests are self-contained.
Every test function that calls load_manifest_from_default_branch or spawns a thread running run_ci_pass now installs its own manifest override:
Tests with nightly lanes install a manifest containing only the specific nightly lane(s) under test.
Tests with only branch lanes install an empty manifest (test_manifest(vec![], vec![])) since the branch lanes are provided explicitly via queue_branch_ci_run_for_head.
Thread-spawned CI passes install their own override because the thread-local is per-thread.
The merged_branch_page_renders_after_source_branch_deletion render test in render.rs also gains a manifest override with a custom render_history lane.
Tests also switch from hardcoding the path crates/pikaci/src/forge_lanes.rs to using super::FORGE_LANE_DEFINITION_PATH, keeping paths consistent.
Rename lane IDs in manifest assertion tests
Intent: Update test assertions to reflect the new lane ID naming convention where IDs match target IDs (e.g., `pika_rust` → `pre-merge-pika-rust`).
The ci_manifest.rs selector tests and poller.rs integration test update their expected lane IDs:
Old ID
New ID
pika_rust
pre-merge-pika-rust
pika_followup
pre-merge-pika-followup
pikachat
pre-merge-pikachat-rust
pikachat_typescript
pre-merge-pikachat-typescript
fixture
pre-merge-fixture-rust
notifications
pre-merge-notifications
rmp
pre-merge-rmp
Apple-target lane IDs (apple_desktop_compile, apple_ios_compile) remain unchanged because they already matched their target IDs.
Update the `sample_lane_with_target` test helper in `branch_store.rs`
Intent: Adapt the helper that creates a lane associated with a specific target to use the new convention: construct the lane with the target ID as its own ID, then override just the title.
@@ -883,8 +882,8 @@
fn sample_lane_with_target(id: &str, target_id: &str) -> ForgeLane {
- let mut lane = sample_lane(id);
- lane.staged_linux_target = Some(target_id.to_string());
+ let mut lane = sample_lane(target_id);
+ lane.title = format!("{id} title");
lane
}
The sample_lane_with_target helper in branch store tests previously created a lane with id set to the logical name and staged_linux_target pointing to the infrastructure target. Now it creates the lane with id equal to target_id (so the implicit mapping works) and overrides just the title to preserve the human-readable label for test assertions.
Intent: Ensure that the target catalog in `targets.rs` stays in sync with the lane definitions and that any future edits are validated automatically.
Affected files: invariants/invariants.toml
Evidence
@@ invariants/invariants.toml changes for target catalog validation
The invariants/invariants.toml file is updated to include validation rules that tie the target catalog (targets.rs) to the rest of the CI infrastructure. This ensures that adding, removing, or renaming a target in the catalog is caught by the project's invariant checks if dependent files are not updated accordingly.