From b8aedeafcbab3d38c0ed789e2578e9f47742c0e5 Mon Sep 17 00:00:00 2001 From: Dai Ha Date: Thu, 10 Sep 2026 09:13:07 +0700 Subject: [PATCH] fleetd #404: test armed startup map behavior --- .../src/main/java/dev/ltms/fleet/Fleetd.java | 23 +++++++----- ...etdExhaustionDetectionArmedWiringTest.java | 35 +++++-------------- 2 files changed, 24 insertions(+), 34 deletions(-) diff --git a/fleetd/src/main/java/dev/ltms/fleet/Fleetd.java b/fleetd/src/main/java/dev/ltms/fleet/Fleetd.java index 3372f49..120c7b0 100644 --- a/fleetd/src/main/java/dev/ltms/fleet/Fleetd.java +++ b/fleetd/src/main/java/dev/ltms/fleet/Fleetd.java @@ -665,14 +665,8 @@ public final class Fleetd { // built a second time — two independently-constructed sources reading the SAME BackendQuarantine // / BackendOutagePolicy would still be able to drift (e.g. a future edit to the credentialIdFor // closure in only one of the two places), exactly the shape #284 was. - FleetMcp.QuarantineSource quarantineSource = new FleetMcp.QuarantineSource(profile -> { - var configured = config.get().profiles().get(profile); - return configured == null ? null : configured.effectiveCredentialId(); - }, quarantine, profile -> { - // fleetd #404: use the compiled startup map that CompletionResolver reads. An - // exhaustedPattern edit is deferred, so this stays aligned with detection until restart. - return exhaustedPatternsByProfile.containsKey(profile); - }); + FleetMcp.QuarantineSource quarantineSource = quarantineSource(config, quarantine, + exhaustedPatternsByProfile); FleetMcp.OutageSource outageSource = new FleetMcp.OutageSource(profile -> { var configured = config.get().profiles().get(profile); return configured == null ? null : configured.effectiveCredentialId(); @@ -806,6 +800,19 @@ public final class Fleetd { return target -> presence.isPresent(target) || leads.get().containsKey(target); } + /** + * fleetd #404: production source for quarantine reporting. Credential IDs are hot, but + * exhausted patterns are compiled once at startup for {@link CompletionResolver}, so the armed + * field must use that same compiled map until restart. + */ + static FleetMcp.QuarantineSource quarantineSource(ConfigRef config, BackendQuarantine quarantine, + Map startupExhaustedPatterns) { + return new FleetMcp.QuarantineSource(profile -> { + var configured = config.get().profiles().get(profile); + return configured == null ? null : configured.effectiveCredentialId(); + }, quarantine, profile -> startupExhaustedPatterns.containsKey(profile)); + } + /** * fleetd #248: package-private factory for the member worktree/branch lookup {@link * CompletionResolver} uses to name a fallback report's worktree and branch (fleetd#241). diff --git a/fleetd/src/test/java/dev/ltms/fleet/FleetdExhaustionDetectionArmedWiringTest.java b/fleetd/src/test/java/dev/ltms/fleet/FleetdExhaustionDetectionArmedWiringTest.java index 0d28635..b11ff05 100644 --- a/fleetd/src/test/java/dev/ltms/fleet/FleetdExhaustionDetectionArmedWiringTest.java +++ b/fleetd/src/test/java/dev/ltms/fleet/FleetdExhaustionDetectionArmedWiringTest.java @@ -2,22 +2,20 @@ package dev.ltms.fleet; import dev.ltms.fleet.config.ConfigRef; import dev.ltms.fleet.config.FleetConfig; +import dev.ltms.fleet.mcp.FleetMcp; +import dev.ltms.fleet.placement.BackendQuarantine; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; import java.nio.file.Files; import java.nio.file.Path; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.regex.Pattern; - import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; /** - * fleetd #404: {@code exhaustionDetectionArmed} must describe the startup pattern map that - * {@code CompletionResolver} actually uses, not the reloaded config snapshot. + * fleetd #404: {@code exhaustionDetectionArmed} must describe the startup pattern map, not the + * reloaded config snapshot. * *

This test needs a reload. At startup the two snapshots agree, so a test of only a newly * started daemon would not detect a live {@code config.get()} lookup in the report field. @@ -53,35 +51,20 @@ class FleetdExhaustionDetectionArmedWiringTest { - gx00.gw """; - private static Map compiledPatterns(FleetConfig config) { - Map patterns = new LinkedHashMap<>(); - config.profiles().forEach((name, profile) -> { - if (profile.hasExhaustedPattern()) { - patterns.put(name, Pattern.compile(profile.exhaustedPattern())); - } - }); - return patterns; - } - - private static String fleetdSource() throws Exception { - return Files.readString(Path.of("src/main/java/dev/ltms/fleet/Fleetd.java")); - } - @Test - @DisplayName("[SOURCE TEXT] reloading an exhaustedPattern cannot make the armed field read config.get()") + @DisplayName("reloading an exhaustedPattern does not arm the startup detection source") void reloadedPatternDoesNotChangeTheArmedFieldUntilRestart(@TempDir Path dir) throws Exception { Path file = dir.resolve("fleetd.yaml"); Files.writeString(file, NO_PATTERN); ConfigRef config = new ConfigRef(file, FleetConfig.load(file)); - Map startupPatterns = compiledPatterns(config.get()); Files.writeString(file, WITH_PATTERN); assertTrue(config.reload().applied()); assertTrue(config.get().profiles().get("terra").hasExhaustedPattern()); - assertFalse(startupPatterns.containsKey("terra")); - assertTrue(fleetdSource().contains("return exhaustedPatternsByProfile.containsKey(profile);"), - "Fleetd must derive exhaustionDetectionArmed from exhaustedPatternsByProfile, not config.get(): " - + "the compiled startup map is what CompletionResolver uses until restart."); + FleetMcp.QuarantineSource source = Fleetd.quarantineSource(config, BackendQuarantine.none(), + java.util.Map.of()); + assertFalse(source.exhaustedPatternArmed().apply("terra"), + "exhaustionDetectionArmed must use the startup pattern map, not config.get()"); } }