fleetd #444: test that pins the place()-to-spawn() window PlacementDecision closes #447
Reference in New Issue
Block a user
Delete Branch "worker/444-placement-window-feb56a-2"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
fleetd #444 — pin the
place()-to-spawn()windowPlacementDecisionexists to closeWhat was missing
PlacementDecision's whole point is that a profile chosen byplace()is the profile actuallyspawned on, even if fleet state changes in between.
CompositePeerLauncher.spawn(req, decision)routes
decision.profile()straight to its owning adapter and never re-runs the explicit-profileenforce*checks (enforceNotQuarantined,enforceNotCoolingOff,enforceMaxLoad,enforceModelEnabled). The alternative body — re-enteringspawn(req.withProfile(...))— lands inthe throwing explicit-profile branch instead.
Nothing in the suite distinguished the two, because after fleetd #435 every
enforce*check tests acondition
place()'s own routing already filtered out — the two paths only disagree when placementstate moves between the
place()call and thespawn()call, and no existing test moved thatstate.
What this PR adds
One test in
CompositePeerLauncherTest:spawnHonorsAPlacementDecisionEvenAfterItsProfileIsQuarantinedInTheWindowAfterPlacecomposite.place(MemberRole.DEV)while nothing is quarantined —sol(definition-orderfirst,
fixedpolicy) is chosen.sol's credential (shared-openai) — moving placement state in the window theticket names.
composite.spawn(req, decision)against the held decision and asserts it stillsucceeds, lands on
sol(handle.profile()), and that the request that reached the delegateactually carried
sol(adapter.spawnCount("sol") == 1,adapter.spawnCount("b") == 0).Also updated
PeerLauncher's defaultspawn(req, decision)javadoc: a launcher that routes acrossmore than one profile must override this method, naming the four
enforce*checks the default'sre-entry into the single-argument
spawnwould re-apply. The default itself is unchanged — kept perthe ticket's own decision, since
HerdrPeerLauncher(single-profile) is correctly served by it.Round 2 — fixture fix (review finding)
Reviewer found the fixture's own weakness:
StubLauncherwas built with"sol"as its ownfallback default (
new StubLauncher("claude", herdr, profiles, "sol", Set.of())).StubLauncher.spawnfalls back to that field whenever
req.profileName()is blank:Since that fallback was also
"sol"— the same profileplace()decides — an unstamped request(one routed but never actually given
req.withProfile("sol")) would land onspawnCount("sol") == 1by coincidence. Reviewer proved it: dropping the stamping (
SpawnRequest routedReq = req;) whilekeeping the routing left the test green.
Fix: the adapter's own fallback default is now
"b"instead of"sol", so an unstamped requestcounts against
"b", not"sol"— only an actually-stamped request can producespawnCount("sol") == 1now. Confirmedplace(MemberRole.DEV)still resolves"sol"after thischange (composite's own
poolFor/candidatesnever reads the adapter's internal default; thatfield only feeds
StubLauncher.spawn's own fallback).Evidence
Test alone, unmutated —
mvn test -Dtest=CompositePeerLauncherTest#spawnHonorsAPlacementDecisionEvenAfterItsProfileIsQuarantinedInTheWindowAfterPlace:Mutation 2 (this round) — drop the stamping, keep the routing —
CompositePeerLauncher's 2-argspawn:Test alone under this mutation:
Reverted; confirmed the diff on
CompositePeerLauncher.javawas empty again (git diff --stat—no output) before moving to mutation 1.
Mutation 1 (M1, from round 1) — re-enter the explicit-profile branch:
Test alone under this mutation:
Both mutations kill the same test — no trade-off between them. Reverted;
src/maindiff is emptyagain (
git diff --stat -- src/main/java/dev/ltms/fleet/member/CompositePeerLauncher.java— nooutput, confirming no net change to
src/main).Full build —
mvn clean install, full unpiped log, no-q:(
CompositePeerLauncherTest:Tests run: 76, Failures: 0, Errors: 0.)Scope note
This round touches only
CompositePeerLauncherTest.java.src/mainis untouched (verified viagit diff --stat) —PeerLauncher.java's javadoc from round 1 stands as-is, per the reviewer'sinstruction not to change it.
Review found the fixture's StubLauncher fell back to 'sol' too — the same profile place() decides — so an UNSTAMPED request could land on spawnCount('sol') by coincidence, and the assertion's claim that the request 'actually carried sol' was unproven. Dropping the stamping (SpawnRequest routedReq = req) while keeping the routing survived the test unchanged. Fix: give the adapter 'b' as its own fallback default instead, so an unstamped request counts against 'b', not 'sol'. Verified both mutations against the single test in isolation: - drop-stamping (routedReq = req): RED, expected <sol> but was <b> - re-entering (return spawn(req.withProfile(decision.profile()))) i.e. M1 from the first round: still RED, PlacementException naming the now-quarantined 'sol' Restored both; full suite green at 1575 tests. No changes to src/main — PeerLauncher's javadoc from the first round is unchanged.