Back to feed

sledtools/pika branch #59

pika-orch-incus-cleanup-8

Make structured pikaci lanes explicit

Target branch: master

Merge Commit: 68f927b760cbbe9cacf3acff6de202ba5299aa6c

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 #76 success

3 passed

head 0f9521c0e136a247a5d59ad0f6e1c3b91d2f4d30 · queued 2026-03-25 20:17:50 · 3 lane(s)

queued 11s · ran 29s

check-pika-followup · success check-notifications · success check-agent-contracts · success

Summary

This branch makes structured pikaci lane detection explicit by introducing a structured_pikaci_target_id column to both the branch_ci_run_lanes and nightly_run_lanes database tables. Previously, the system inferred whether a CI lane was a structured pikaci target purely by inspecting the command-line arguments at execution time (matching a known wrapper name pattern). This was fragile: any lane using a custom wrapper script would fail to be recognized as structured. The new approach stores the structured target ID as explicit metadata at lane creation time, threads it through the queue/claim/rerun lifecycle, and uses it as the authoritative signal at execution time—falling back to the old command-based heuristic only when the explicit field is absent. The branch also hardens the --output jsonl flag injection by validating pre-existing --output values and returning errors for mismatches, adds a SQL migration, updates all affected queries, and includes comprehensive tests for both branch and nightly lane paths as well as edge cases.

Tutorial Steps

Add SQL migration for structured_pikaci_target_id column

Intent: Extend the database schema so that both branch CI run lanes and nightly run lanes can persist an explicit structured pikaci target identifier, decoupling structured-lane detection from runtime command inspection.

Affected files: crates/pika-news/migrations/0024_ci_lane_structured_pikaci_target.sql, crates/pika-news/src/storage.rs

Evidence
@@ -0,0 +1,5 @@
+ALTER TABLE branch_ci_run_lanes
+ADD COLUMN structured_pikaci_target_id TEXT;
+
+ALTER TABLE nightly_run_lanes
+ADD COLUMN structured_pikaci_target_id TEXT;
@@ -2790,6 +2790,11 @@ fn migrations() -> Vec<Migration> {
+        Migration {
+            version: 24,
+            name: "0024_ci_lane_structured_pikaci_target",
+            sql: include_str!("../migrations/0024_ci_lane_structured_pikaci_target.sql"),
+        },

A new migration (0024) adds a nullable TEXT column structured_pikaci_target_id to both branch_ci_run_lanes and nightly_run_lanes. The column is nullable because not every lane is a structured pikaci lane—legacy lanes and lanes that don't use pikaci remain unaffected.

The migration is registered in storage.rs as version 24 via the standard Migration struct, ensuring it runs automatically on startup.

Thread structured_pikaci_target_id through domain structs

Intent: Add the new field to all Rust structs that represent lane jobs in the queue/claim/rerun lifecycle, ensuring the value is available wherever lane metadata is read or written.

Affected files: crates/pika-news/src/branch_store.rs

Evidence
@@ -143,6 +143,7 @@ pub struct PendingBranchCiLaneJob {
+    pub structured_pikaci_target_id: Option<String>,
@@ -204,6 +205,7 @@ pub struct PendingNightlyLaneJob {
+    pub structured_pikaci_target_id: Option<String>,
@@ -258,6 +260,7 @@ struct BranchLaneRerunSource {
+    structured_pikaci_target_id: Option<String>,
@@ -282,6 +285,7 @@ struct NightlyLaneRerunSource {
+    structured_pikaci_target_id: Option<String>,

Four structs gain structured_pikaci_target_id: Option<String>:

  • PendingBranchCiLaneJob — the struct returned when claiming a queued branch CI lane.
  • PendingNightlyLaneJob — the struct returned when claiming a queued nightly lane.
  • BranchLaneRerunSource — the struct used to read a lane's data before creating a rerun.
  • NightlyLaneRerunSource — the nightly equivalent of the rerun source.

All four use Option<String> to maintain backward compatibility with lanes that predate the migration.

Persist and read structured_pikaci_target_id in all SQL paths

Intent: Update every INSERT and SELECT statement that touches lane rows—initial queue, rerun creation, rerun source reads, and job claiming—to include the new column, keeping column indices consistent.

Affected files: crates/pika-news/src/branch_store.rs

Evidence
@@ -771,17 +775,19 @@ impl Store {
+                        structured_pikaci_target_id,
                         concurrency_group,
-                     ) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, 'queued', 'queued')",
+                     ) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, 'queued', 'queued')",
+                        lane.staged_linux_target,
@@ -970,17 +976,19 @@ impl Store {
+                        structured_pikaci_target_id,
-                     ) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, 'queued', 'queued')",
+                     ) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, 'queued', 'queued')",
+                        lane.staged_linux_target,
@@ -2147,7 +2163,7 @@ impl Store {
-                        "SELECT lane.id, lane.claim_token, lane.branch_ci_run_id, suite.branch_id, suite.source_head_sha, lane.lane_id, lane.title, lane.entrypoint, lane.command_json, lane.concurrency_group, lane.ci_target_key
+                        "SELECT lane.id, lane.claim_token, lane.branch_ci_run_id, suite.branch_id, suite.source_head_sha, lane.lane_id, lane.title, lane.entrypoint, lane.command_json, lane.structured_pikaci_target_id, lane.concurrency_group, lane.ci_target_key
@@ -2393,7 +2410,7 @@ impl Store {
-                        "SELECT lane.id, lane.claim_token, lane.nightly_run_id, nightly.source_head_sha, lane.lane_id, lane.command_json, lane.concurrency_group, lane.ci_target_key
+                        "SELECT lane.id, lane.claim_token, lane.nightly_run_id, nightly.source_head_sha, lane.lane_id, lane.command_json, lane.structured_pikaci_target_id, lane.concurrency_group, lane.ci_target_key

This is the bulk of the mechanical change. Every SQL statement that touches lane rows is updated:

Initial queue (branch + nightly)

The INSERT INTO branch_ci_run_lanes and INSERT INTO nightly_run_lanes statements in queue_branch_ci_run_for_head and queue_nightly_run now include structured_pikaci_target_id as a bound parameter. The value comes from lane.staged_linux_target on the input lane config.

Rerun source reads

The SELECT queries in the rerun paths (rerun_branch_ci_lane and rerun_nightly_lane) now fetch structured_pikaci_target_id and map it into the BranchLaneRerunSource / NightlyLaneRerunSource structs. The rerun INSERT statements then copy the value forward so reruns inherit the structured target metadata.

Job claiming

The claim queries (claim_pending_branch_ci_lane_runs and claim_pending_nightly_lane_runs) add the column to their SELECT lists. All subsequent row.get(N) indices are shifted by one to accommodate the new column, and the new field is populated on PendingBranchCiLaneJob / PendingNightlyLaneJob.

Use explicit target ID at execution time with command-based fallback

Intent: Make the CI executor prefer the explicit structured_pikaci_target_id from the database over inferring it from the command array, while preserving backward compatibility for lanes that predate the migration.

Affected files: crates/pika-news/src/ci.rs, crates/pika-news/src/forge.rs

Evidence
@@ -397,6 +397,7 @@ fn execute_branch_job(
+        job.structured_pikaci_target_id.as_deref(),
@@ -482,6 +483,7 @@ fn execute_nightly_job(
+        job.structured_pikaci_target_id.as_deref(),
@@ -328,6 +328,7 @@ pub fn run_ci_command_for_head_with_heartbeat<F, G>(
+    structured_pikaci_target_id: Option<&str>,
@@ -339,12 +340,14 @@ where
-    let structured_pikaci_target = staged_pikaci_target_from_command(command);
+    let structured_pikaci_target = structured_pikaci_target_id
+        .map(ToOwned::to_owned)
+        .or_else(|| staged_pikaci_target_from_command(command));

The core execution function run_ci_command_for_head_with_heartbeat in forge.rs gains a new parameter structured_pikaci_target_id: Option<&str>. The resolution logic becomes:

  1. If the explicit target ID is Some, use it directly.
  2. Otherwise, fall back to staged_pikaci_target_from_command(command) which inspects the command array for known wrapper patterns.

This means lanes configured with a staged_linux_target in their lane definition will always be treated as structured, regardless of their wrapper script name. The fallback ensures pre-existing lanes without the DB column populated continue to work.

Both execute_branch_job and execute_nightly_job in ci.rs pass job.structured_pikaci_target_id.as_deref() through to this function.

Harden --output jsonl flag validation

Intent: Prevent silent misconfiguration by validating that structured pikaci commands use exactly `--output jsonl`, rejecting other output formats and bare `--output` flags without values.

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

Evidence
@@ -1134,14 +1137,22 @@ fn staged_pikaci_target_from_command(command: &[String]) -> Option<String> {
-fn ensure_structured_pikaci_output(command: &[String]) -> Vec<String> {
-    if command.iter().any(|arg| arg == "--output") {
-        return command.to_vec();
+fn ensure_structured_pikaci_output(command: &[String]) -> anyhow::Result<Vec<String>> {
+    if let Some(output_index) = command.iter().position(|arg| arg == "--output") {
+        let Some(output_value) = command.get(output_index + 1) else {
+            bail!("structured pikaci command passed `--output` without a value");
+        };
+        if output_value != "jsonl" {
+            bail!(
+                "structured pikaci command must use `--output jsonl`, got `--output {output_value}`"
+            );
+        }
+        return Ok(command.to_vec());
     }
@@ -339,12 +340,14 @@ where
     let effective_command = if structured_pikaci_target.is_some() {
-        ensure_structured_pikaci_output(command)
+        ensure_structured_pikaci_output(command)?

Previously, ensure_structured_pikaci_output silently accepted any --output value and returned the command as-is. Now it:

  1. Returns anyhow::Result<Vec<String>> instead of bare Vec<String>.
  2. If --output is present, validates the next argument exists and equals "jsonl". If the value is missing, it bails with a clear error. If the value is something other than jsonl (e.g., json), it bails with a descriptive message.
  3. If --output is absent, appends --output jsonl as before.

The caller in run_ci_command_for_head_with_heartbeat now uses ? to propagate the error, failing the lane early instead of running with incorrect output format.

Add tests for explicit structured pikaci target propagation

Intent: Verify that the structured_pikaci_target_id survives the full queue → claim lifecycle for both branch and nightly lanes, and that the execution layer correctly uses it independent of wrapper script naming.

Affected files: crates/pika-news/src/branch_store.rs, crates/pika-news/src/forge.rs

Evidence
@@ -3711,6 +3729,33 @@ mod tests {
+    #[test]
+    fn claimed_branch_lane_keeps_structured_pikaci_target_hint() {
@@ -3829,6 +3874,43 @@ mod tests {
+    #[test]
+    fn claimed_nightly_lane_keeps_structured_pikaci_target_hint() {
@@ -1604,6 +1617,101 @@ mod tests {
+    #[test]
+    fn explicit_structured_pikaci_target_does_not_depend_on_wrapper_name() {
@@ -1604,6 +1617,101 @@ mod tests {
+    #[test]
+    fn explicit_structured_pikaci_target_rejects_non_jsonl_output() {

Four new tests cover the critical behaviors:

claimed_branch_lane_keeps_structured_pikaci_target_hint

Queues a branch CI lane with sample_lane_with_target("pika_rust", "pre-merge-pika-rust"), claims it, and asserts that job.structured_pikaci_target_id is Some("pre-merge-pika-rust"). This validates the full DB round-trip for branch lanes.

claimed_nightly_lane_keeps_structured_pikaci_target_hint

Same pattern for the nightly lane path: queue → claim → assert the target ID is preserved.

explicit_structured_pikaci_target_does_not_depend_on_wrapper_name

This is the key integration test. It creates a custom wrapper script (scripts/custom-structured-lane.sh) whose name does not match the legacy pikaci naming convention. When executed with structured_pikaci_target_id: Some("pre-merge-pika-rust"), the lane is still correctly treated as structured—verifying that state root, run metadata, and log collection all work without depending on command-name heuristics.

explicit_structured_pikaci_target_rejects_non_jsonl_output

Passes --output json (not jsonl) with an explicit target ID and asserts the function returns an error containing "structured pikaci command must use \--output jsonl`". This validates the new validation logic in ensure_structured_pikaci_output`.

Add orchestration skill documentation

Intent: Codify the multi-step orchestration workflow, delegation patterns, and project-specific safety rules as a reusable agent skill, capturing lessons learned from this and previous cleanup programs.

Affected files: .agents/skills/orchestrate/SKILL.md

Evidence
@@ -0,0 +1,106 @@
+---
+name: orchestrate
+description: Orchestrate multi-step cleanup, migration, or refactor work across pika worktrees

A new SKILL.md file documents the orchestration pattern used for multi-chunk implementation programs in the pika project. Key sections include:

  • Default shape: One integration branch, one implementation worker on the critical path, one explorer/reviewer in parallel.
  • Worktree rules: Run ./scripts/agent-brief in new sessions, use dedicated branches per chunk, separate worktrees for conflicting chunks.
  • Staged Linux remote safety: Warnings about the shared /var/tmp/pikaci-prepared-output root causing collisions in parallel runs.
  • Delegation pattern: Orchestrator → Worker → Explorer/reviewer roles with clear write-set ownership.
  • Landing pattern: Forge-native flow, checkpoint before risk, push dedicated branches.
  • Project-specific heuristics: Lessons distilled from prior work—prefer Nix-declared contracts over bash assembly, treat migration-only knobs as debt, inspect consumer seams before inventing new protocols, use persistent state roots for temp-worktree CI, and validate remote-authoritative paths.

This skill document serves as institutional memory, preventing the team from re-learning collision and state-root issues that surfaced during earlier pikaci cleanup iterations.

Diff