From 4b10d02207af11620a05226772463884a50f0667 Mon Sep 17 00:00:00 2001 From: Dai Ha Date: Thu, 10 Sep 2026 14:24:14 +0700 Subject: [PATCH] fleetd #425 rework round 4: mutation-pinning test for the dropped PlacementDecision MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SessionManager.acquireWithWorktree's unqualified branch must carry the PlacementDecision it already resolved via launcher.place() into launcher.spawn(spawnReq, decision) rather than re-deriving it through a blank-profile launcher.spawn(spawnReq). Every existing test in this file uses PlacementPolicies.fixed(), which answers select() the same way on every call, so dropping the decision (handle = launcher.spawn(spawnReq);) was invisible: 186 tests stayed green under that mutation. acquireWithWorktreeSpawnsOnTheSameProfileItProvisionedTheWorktreeForUnderARotatingPolicy uses PlacementPolicies.roundRobin() instead — deterministic AND stateful, so two select() calls on the same policy instance disagree (index 0 then index 1 across a two-profile pool). It asserts AGREEMENT between the profile the worktree's parity overlay was provisioned for and the profile the member actually spawned on, never a hardcoded expected profile name. Verified as a real mutation, not a no-op: applying the exact mutation (handle = launcher.spawn(spawnReq);) turns it red — expected [b.mcp.json] but was [a.mcp.json] — and reverting turns it green again. Full build: 1572 tests, 0 failures, 0 errors, BUILD SUCCESS. --- .../fleet/session/SessionManagerTest.java | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/fleetd/src/test/java/dev/ltms/fleet/session/SessionManagerTest.java b/fleetd/src/test/java/dev/ltms/fleet/session/SessionManagerTest.java index 3f99ffb..2daf2fc 100644 --- a/fleetd/src/test/java/dev/ltms/fleet/session/SessionManagerTest.java +++ b/fleetd/src/test/java/dev/ltms/fleet/session/SessionManagerTest.java @@ -2219,6 +2219,64 @@ class SessionManagerTest { + "on the worktree path alone (fleetd #425 rework, round 2)"); } + /** + * fleetd #425 rework, round 4: the exact regression a mutation test found that 186 green tests + * missed — {@code acquireWithWorktree} dropping the {@link + * dev.ltms.fleet.placement.PlacementDecision} it already resolved via {@code launcher.place}, + * and letting the unqualified spawn re-run placement a second time (a blank-profile {@code + * launcher.spawn(spawnReq)}) instead of carrying that decision forward via {@code + * launcher.spawn(spawnReq, decision)}. Every earlier test in this file uses {@code + * PlacementPolicies.fixed()}, which returns the same answer on every {@code select()} call, so + * dropping the decision is invisible under it — two {@code select()} calls simply agree by + * accident. {@code PlacementPolicies.roundRobin()} is deterministic AND stateful: its {@code + * select()} advances an internal index on every call, so two consecutive calls for the SAME + * spawn (one from {@code place()} to provision the worktree, a second from a dropped-decision + * blank-profile {@code spawn(spawnReq)}) land on DIFFERENT profiles from a two-profile pool — + * index 0 ("a"), then index 1 ("b"). + * + *

This test does not hardcode which profile wins — asserting one specific name would pass + * for the wrong reason the moment the rotation order changes (round-4 brief invariant 3). It + * asserts AGREEMENT instead: whichever profile the worktree's parity overlay was provisioned + * for must be the SAME profile the member actually spawned on. Each profile's overlay list is + * named after the profile itself ({@code "a.mcp.json"}/{@code "b.mcp.json"}), so comparing the + * recorded overlay against {@code s.profile() + ".mcp.json"} checks agreement without ever + * naming an expected winner. + */ + @Test + void acquireWithWorktreeSpawnsOnTheSameProfileItProvisionedTheWorktreeForUnderARotatingPolicy() { + Map profiles = new LinkedHashMap<>(); + profiles.put("a", new FleetConfig.Profile("a", "http://gx00.gw:8000", "coder-a", null, + "FLEETD_WORKER_TOKEN", List.of("claude"), "tab", "fleetd-workers", + "worker: {profile} #{n}", null, "/repo/a", List.of("a.mcp.json"))); + profiles.put("b", new FleetConfig.Profile("b", "http://gx00.gw:8000", "coder-b", null, + "FLEETD_WORKER_TOKEN", List.of("claude"), "tab", "fleetd-workers", + "worker: {profile} #{n}", null, "/repo/b", List.of("b.mcp.json"))); + FakeHerdr herdr = new FakeHerdr(); + ClaudeCodeLauncher adapter = new ClaudeCodeLauncher(new AgentControl(herdr), new WorkspaceControl(herdr), + new SubscriptionGuard(Set.of("gx00.gw")), profiles, "a", _ -> null); + // roundRobin is deterministic AND stateful: the first select() call picks index 0 ("a"), + // and the SAME policy instance's second select() call (reached only if the + // PlacementDecision is dropped) picks index 1 ("b") — the two-call disagreement this test + // needs to make a dropped decision observable, rather than merely probable. + PeerLauncher launcher = new CompositePeerLauncher(List.of(adapter), "a", profiles, + PlacementPolicies.roundRobin(), _ -> 0); + + FakeWorktrees worktrees = new FakeWorktrees(); + SessionManager sessions = new SessionManager(launcher, worktrees, () -> 0L); + + MemberSession s = sessions.acquire(null, null, "/caller", + null, new WorktreeRequest("fleetd-425-round4", null)); + + FakeWorktrees.OverlayCall overlay = worktrees.lastOverlay(); + assertNotNull(overlay, "overlayParity must have been called"); + assertEquals(List.of(s.profile() + ".mcp.json"), overlay.requested(), + "the worktree must be provisioned for the SAME profile the member actually spawned " + + "on — under a rotating policy, dropping the PlacementDecision makes the " + + "second, spawn-time select() call disagree with the first, place()-time " + + "call, so the member ends up on a profile whose worktree (repoRoot/parity " + + "overlay) was built for a DIFFERENT profile (fleetd #425 rework, round 4)"); + } + /** * Reduce one {@code acquire(...)} attempt to a value comparable across the with-worktree and * without-worktree paths: the spawned profile name on success, or the thrown exception's class