diff --git a/fleetd/src/main/java/dev/ltms/fleet/Fleetd.java b/fleetd/src/main/java/dev/ltms/fleet/Fleetd.java index bf9a5d0..8d4cae3 100644 --- a/fleetd/src/main/java/dev/ltms/fleet/Fleetd.java +++ b/fleetd/src/main/java/dev/ltms/fleet/Fleetd.java @@ -351,7 +351,8 @@ public final class Fleetd { .map(session -> exhaustedPatternsByProfile.get(session.profile())) .orElse(null); log.info("backend-exhausted classification (CB-578 stage A): {}", - CompletionResolver.coverage(cfg.profiles().keySet(), exhaustedPatternsByProfile.keySet())); + CompletionResolver.coverage("exhaustedPattern", cfg.profiles().keySet(), + exhaustedPatternsByProfile.keySet())); // fleetd #201 Unit 5: classify a completion-fallback scrape that matches a profile's // configured backend-error refusal (a credential outage, a provider 5xx) as a backend error // rather than handing it back as a real answer. Compiled once at startup, keyed by profile @@ -371,7 +372,8 @@ public final class Fleetd { BackendErrorPatternLookup backendErrorPatterns = backendErrorPatternLookup(sessions::roster, errorPatternsByProfile); log.info("backend-error classification (fleetd #201 Unit 5): {}", - CompletionResolver.coverage(cfg.profiles().keySet(), errorPatternsByProfile.keySet())); + CompletionResolver.coverage("errorPattern", cfg.profiles().keySet(), + errorPatternsByProfile.keySet())); // CB-578 stage B: on a classification that actually wins, quarantine the exhausted profile's // CREDENTIAL — not the profile name — so a profile sharing that credential (e.g. two models // on one OpenAI account) is refused too, not just the one that happened to report it. Reads diff --git a/fleetd/src/main/java/dev/ltms/fleet/inject/CompletionResolver.java b/fleetd/src/main/java/dev/ltms/fleet/inject/CompletionResolver.java index 202eab2..c806fd8 100644 --- a/fleetd/src/main/java/dev/ltms/fleet/inject/CompletionResolver.java +++ b/fleetd/src/main/java/dev/ltms/fleet/inject/CompletionResolver.java @@ -620,9 +620,9 @@ public final class CompletionResolver implements TurnListener { * @param allProfiles every configured profile name * @param configuredProfiles the subset of {@code allProfiles} that carry an exhausted pattern */ - public static String coverage(Set allProfiles, Set configuredProfiles) { + public static String coverage(String patternKey, Set allProfiles, Set configuredProfiles) { if (configuredProfiles.isEmpty()) { - return "off (no profile has an exhaustedPattern configured; profiles: " + sorted(allProfiles) + ")"; + return "off (no profile has an " + patternKey + " configured; profiles: " + sorted(allProfiles) + ")"; } Set unconfigured = new TreeSet<>(allProfiles); unconfigured.removeAll(configuredProfiles); diff --git a/fleetd/src/test/java/dev/ltms/fleet/inject/CompletionResolverTest.java b/fleetd/src/test/java/dev/ltms/fleet/inject/CompletionResolverTest.java index ad11a37..e056b6b 100644 --- a/fleetd/src/test/java/dev/ltms/fleet/inject/CompletionResolverTest.java +++ b/fleetd/src/test/java/dev/ltms/fleet/inject/CompletionResolverTest.java @@ -768,19 +768,37 @@ class CompletionResolverTest { @Test void coverageIsOffWhenNoProfileHasAPatternConfigured() { assertEquals("off (no profile has an exhaustedPattern configured; profiles: [terra])", - CompletionResolver.coverage(Set.of("terra"), Set.of())); + CompletionResolver.coverage("exhaustedPattern", Set.of("terra"), Set.of())); } @Test void coverageIsFullWhenEveryProfileHasAPatternConfigured() { assertEquals("full (all profiles configured: [gx10, terra])", - CompletionResolver.coverage(Set.of("terra", "gx10"), Set.of("terra", "gx10"))); + CompletionResolver.coverage("exhaustedPattern", Set.of("terra", "gx10"), Set.of("terra", "gx10"))); } @Test void coverageIsPartialAndNamesWhichProfilesAreConfigured() { assertEquals("partial (configured: [terra]; not configured: [gx10])", - CompletionResolver.coverage(Set.of("terra", "gx10"), Set.of("terra"))); + CompletionResolver.coverage("exhaustedPattern", Set.of("terra", "gx10"), Set.of("terra"))); + } + + /** + * Found live on 2026-09-03, reading a real boot log rather than a test. {@code coverage} is + * shared by two call sites — CB-578's {@code exhaustedPattern} line and fleetd#201 Unit 5's + * {@code errorPattern} line — but its "off" branch hard-coded the word {@code exhaustedPattern}. + * So a daemon with no {@code errorPattern} anywhere printed "no profile has an exhaustedPattern + * configured" directly beneath a line reporting that two profiles DO have one. Both lines were + * individually defensible and together they were nonsense, and the message sent an operator to + * set the wrong key. + * + *

Every earlier test here passed the exhaustion case only, so none of them could see it. This + * one pins that the message names the key the caller actually meant. + */ + @Test + void coverageNamesTheConfigKeyItsCallerMeansRatherThanAlwaysSayingExhaustedPattern() { + assertEquals("off (no profile has an errorPattern configured; profiles: [gx10, terra])", + CompletionResolver.coverage("errorPattern", Set.of("terra", "gx10"), Set.of())); } // --- fleetd#201 Unit 1: target-keyed backend-error pattern + typed sink ----------------------