fleetd #404: report armed detection from startup map
CI / build (pull_request) Successful in 1m25s
CI / contract (pull_request) Successful in 1m20s

This commit is contained in:
Dai Ha
2026-09-10 08:56:39 +07:00
parent 7180b1aad0
commit 44c735f6f5
2 changed files with 90 additions and 5 deletions
@@ -669,11 +669,9 @@ public final class Fleetd {
var configured = config.get().profiles().get(profile);
return configured == null ? null : configured.effectiveCredentialId();
}, quarantine, profile -> {
// fleetd #395: read live off the current config, like credentialIdFor above — an
// exhaustedPattern edit takes effect on the next fleet_profiles/fleet_list call, no
// restart needed, same as the credential-id lookup it sits beside.
var configured = config.get().profiles().get(profile);
return configured != null && configured.hasExhaustedPattern();
// 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.OutageSource outageSource = new FleetMcp.OutageSource(profile -> {
var configured = config.get().profiles().get(profile);
@@ -0,0 +1,87 @@
package dev.ltms.fleet;
import dev.ltms.fleet.config.ConfigRef;
import dev.ltms.fleet.config.FleetConfig;
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.
*
* <p>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.
*/
class FleetdExhaustionDetectionArmedWiringTest {
private static final String NO_PATTERN = """
bind:
host: 127.0.0.1
port: 8765
herdrSocket: ~/.config/herdr/herdr.sock
profiles:
terra:
baseUrl: http://gx00.gw:8000
model: terra
guard:
offSubscriptionHosts:
- gx00.gw
""";
private static final String WITH_PATTERN = """
bind:
host: 127.0.0.1
port: 8765
herdrSocket: ~/.config/herdr/herdr.sock
profiles:
terra:
baseUrl: http://gx00.gw:8000
model: terra
exhaustedPattern: "usage limit"
guard:
offSubscriptionHosts:
- gx00.gw
""";
private static Map<String, Pattern> compiledPatterns(FleetConfig config) {
Map<String, Pattern> 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()")
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<String, Pattern> 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.");
}
}