This is an automated email from the ASF dual-hosted git repository. luigidemasi pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/camel-kit.git
commit e1566e2efeba8d3e92aebbe5914a08dacf6f8163 Author: Jinal Sarvaiya <[email protected]> AuthorDate: Fri Oct 30 22:39:31 2026 +0000 fix #100: add Pi execution and run-state compatibility verification --- CHANGELOG.md | 4 +++ .../camel/kit/command/ship/ShipCommandTest.java | 41 ++++++++++++++++++++-- .../kit/generator/ShippedAssetStructureTest.java | 10 ++++++ .../kit/ship/controller/ShipCoordinatorTest.java | 14 ++++++++ .../kit/ship/controller/ShipRunStoreTest.java | 15 ++++++++ 5 files changed, 82 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4da43c01..07a3943b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,6 +37,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 while preserving Codex CLI sandbox, approval policies and existing session permissions. Host-enforced tool restriction depends on the Codex CLI version. Fresh and regenerated workspaces receive the native skill and worker; runs retain their recorded host. +- **Pi execution and run-state compatibility verification (#100)** — focused non-regression tests confirm + that Pi runs reject native backends on resume, round-trip `executionMode: PI` through the store, preserve + Pi execution mode across the full coordinator lifecycle (run, resume, abort, recovery, interruption), and + that the Pi Ship skill contains no native dispatch language. ### Changed diff --git a/kit/camel-kit-core/src/test/java/org/apache/camel/kit/command/ship/ShipCommandTest.java b/kit/camel-kit-core/src/test/java/org/apache/camel/kit/command/ship/ShipCommandTest.java index 10592418..d4771ea3 100644 --- a/kit/camel-kit-core/src/test/java/org/apache/camel/kit/command/ship/ShipCommandTest.java +++ b/kit/camel-kit-core/src/test/java/org/apache/camel/kit/command/ship/ShipCommandTest.java @@ -199,6 +199,40 @@ class ShipCommandTest { assertNotEquals(task.path("taskId"), freshTask.path("taskId")); } + @Test + void piRunRejectsNativeBackendOnResumeAndSubmit() throws Exception { + Path project = Files.createDirectory(tempDir.resolve("project")); + ShipController controller = controller("state"); + RecordingLauncher launcher = RecordingLauncher.passThrough(controller); + + RunResult started = run(controller, launcher, "--project-dir", project.toString()); + assertEquals(0, started.exitCode(), started.error()); + String id = runId(started.output()); + assertEquals(ShipRun.ExecutionMode.PI, controller.status(id).executionMode()); + + RunResult mismatch = run(controller, rejectingLauncher(), + "--project-dir", project.toString(), + "--resume", id, "--backend", "bob2-native"); + assertEquals(1, mismatch.exitCode()); + assertTrue(mismatch.error().contains("execution-mode-mismatch"), mismatch.error()); + + RunResult mismatchClaude = run(controller, rejectingLauncher(), + "--project-dir", project.toString(), + "--resume", id, "--backend", "claude-native"); + assertEquals(1, mismatchClaude.exitCode()); + assertTrue(mismatchClaude.error().contains("execution-mode-mismatch"), mismatchClaude.error()); + + Path fakeResult = Files.writeString(tempDir.resolve("fake.json"), "{}"); + RunResult submitted = run(controller, RecordingLauncher.passThrough(controller), + "--project-dir", project.toString(), + "--submit", id, "--result", fakeResult.toString()); + assertEquals(1, submitted.exitCode()); + assertTrue(submitted.error().contains("execution-mode-mismatch") + || submitted.error().contains("no-pending-task") + || submitted.error().contains("requires a native backend"), + "Pi run must reject --submit: " + submitted.error()); + } + @Test void continuationCommandsCarryTheProjectDirectoryOfTheProjectLocalStore() throws Exception { Assumptions.assumeTrue(System.getenv("CAMEL_KIT_SHIP_STATE_HOME") == null, @@ -243,12 +277,15 @@ class ShipCommandTest { assertEquals(0, result.exitCode(), result.error()); assertEquals("", result.error()); - assertEquals(summary(id, "RUNNING", "DISCOVERY", "SMART"), result.output()); - assertEquals(List.of("run:" + id), launcher.invocations); ShipRun run = controller.status(id); + assertEquals(ShipRun.ExecutionMode.PI, run.executionMode(), + "A bare start without --backend must default to PI"); assertEquals(project.toAbsolutePath().normalize().toString(), run.projectDirectory()); assertTrue(run.context().sources().isEmpty()); + + assertEquals(summary(id, "RUNNING", "DISCOVERY", "SMART"), result.output()); + assertEquals(List.of("run:" + id), launcher.invocations); } @Test diff --git a/kit/camel-kit-core/src/test/java/org/apache/camel/kit/generator/ShippedAssetStructureTest.java b/kit/camel-kit-core/src/test/java/org/apache/camel/kit/generator/ShippedAssetStructureTest.java index 003b5f76..facde7a0 100644 --- a/kit/camel-kit-core/src/test/java/org/apache/camel/kit/generator/ShippedAssetStructureTest.java +++ b/kit/camel-kit-core/src/test/java/org/apache/camel/kit/generator/ShippedAssetStructureTest.java @@ -886,6 +886,16 @@ class ShippedAssetStructureTest { assertFalse(skillContent.contains(guide), agentName + " Ship skill must not reference retired guide " + guide); } + if ("pi".equals(agentName)) { + assertFalse(skillContent.contains("--backend pi-native"), + "Pi Ship skill must not reference a pi-native backend"); + assertFalse(skillContent.contains("spawn_subagent"), + "Pi Ship skill must not dispatch native subagents"); + assertFalse(skillContent.contains("invoke_subagent"), + "Pi Ship skill must not dispatch native subagents"); + assertFalse(skillContent.contains("fork_context"), + "Pi Ship skill must not reference fork_context"); + } if (!ctx.agent().generatesCommandStubs()) { return; diff --git a/kit/camel-kit-core/src/test/java/org/apache/camel/kit/ship/controller/ShipCoordinatorTest.java b/kit/camel-kit-core/src/test/java/org/apache/camel/kit/ship/controller/ShipCoordinatorTest.java index 5f8f6a5a..89bb2e93 100644 --- a/kit/camel-kit-core/src/test/java/org/apache/camel/kit/ship/controller/ShipCoordinatorTest.java +++ b/kit/camel-kit-core/src/test/java/org/apache/camel/kit/ship/controller/ShipCoordinatorTest.java @@ -302,6 +302,8 @@ class ShipCoordinatorTest { ShipRun completed = coordinator.run(run.id()); + assertEquals(ShipRun.ExecutionMode.PI, completed.executionMode(), + "Pi coordinator must never switch to a native execution mode"); assertDesignPaused(completed, 1); assertEquals( List.of(sessionId(run)), @@ -318,6 +320,8 @@ class ShipCoordinatorTest { ShipRun completed = coordinator.resume(run.id(), List.of()); + assertEquals(ShipRun.ExecutionMode.PI, completed.executionMode(), + "Pi coordinator must never switch to a native execution mode"); assertDesignPaused(completed, 1); assertEquals( List.of(sessionId(run)), @@ -341,6 +345,8 @@ class ShipCoordinatorTest { ShipRun failed = coordinator.run(run.id()); + assertEquals(ShipRun.ExecutionMode.PI, failed.executionMode(), + "Pi coordinator must never switch to a native execution mode"); assertEquals(RunStatus.FAILED, failed.status()); assertEquals(StageStatus.FAILED, failed.stage(Stage.DESIGN).status()); @@ -394,6 +400,8 @@ class ShipCoordinatorTest { Files.writeString(fixture.resolve("mode"), "nonzero\n"); ShipRun completed = coordinator.resume(run.id(), List.of()); + assertEquals(ShipRun.ExecutionMode.PI, completed.executionMode(), + "Pi coordinator must never switch to a native execution mode"); assertDesignPaused(completed, 1); assertEquals( List.of(sessionId(run)), @@ -507,6 +515,8 @@ class ShipCoordinatorTest { Files.writeString(fixture.resolve("mode"), "success\n"); ShipRun completed = coordinator.resume(run.id(), List.of()); + assertEquals(ShipRun.ExecutionMode.PI, completed.executionMode(), + "Pi coordinator must never switch to a native execution mode"); assertDesignPaused(completed, 2); String sessionId = sessionId(run); assertEquals( @@ -567,6 +577,8 @@ class ShipCoordinatorTest { assertFalse(invocation.isAlive()); assertNull(failure.get()); assertTrue(interrupted.get()); + assertEquals(ShipRun.ExecutionMode.PI, aborted.executionMode(), + "Pi coordinator must never switch to a native execution mode"); assertEquals(aborted, controller.status(run.id())); assertEquals(RunStatus.ABORTED, aborted.status()); assertEquals(StageStatus.ABORTED, @@ -713,6 +725,8 @@ class ShipCoordinatorTest { ShipRun completed = coordinator.resume(run.id(), List.of()); + assertEquals(ShipRun.ExecutionMode.PI, completed.executionMode(), + "Pi coordinator must never switch to a native execution mode"); assertEquals(RunStatus.PAUSED, completed.status()); assertEquals(Stage.DESIGN, completed.currentStage()); assertEquals(StageStatus.COMPLETED, diff --git a/kit/camel-kit-core/src/test/java/org/apache/camel/kit/ship/controller/ShipRunStoreTest.java b/kit/camel-kit-core/src/test/java/org/apache/camel/kit/ship/controller/ShipRunStoreTest.java index dd1d65a7..b454f537 100644 --- a/kit/camel-kit-core/src/test/java/org/apache/camel/kit/ship/controller/ShipRunStoreTest.java +++ b/kit/camel-kit-core/src/test/java/org/apache/camel/kit/ship/controller/ShipRunStoreTest.java @@ -20,6 +20,7 @@ import org.junit.jupiter.api.io.TempDir; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -51,6 +52,20 @@ class ShipRunStoreTest { assertCode("state-corrupt", () -> store.read(RUN_ID)); } + @Test + void piRunRetainsExecutionModeAfterRoundTrip() throws Exception { + ShipRunStore store = store(); + ShipRun pi = run(RUN_ID); + assertEquals(ShipRun.ExecutionMode.PI, pi.executionMode()); + store.create(pi); + ShipRun read = store.read(RUN_ID); + assertEquals(ShipRun.ExecutionMode.PI, read.executionMode()); + for (ShipRun.StageRecord stage : read.stages()) { + assertNull(stage.nativeEvidence(), + "PI stage " + stage.stage() + " must not carry native evidence"); + } + } + @Test void createsReadsAndAtomicallyReplacesRunState() throws Exception { ShipRunStore store = store();
