CB-554: weight <= 0 excludes a profile from automatic placement, not 1.0
CI / contract (pull_request) Successful in 44s
CI / build (pull_request) Successful in 52s

This commit is contained in:
Dai Ha
2026-08-15 12:54:02 +02:00
parent 72d3481de3
commit a1f4dc365a
8 changed files with 267 additions and 20 deletions
@@ -1221,6 +1221,42 @@ class BridgedConfigTest {
assertNull(w.maxLoad(), "absent maxLoad defaults to unlimited (null)");
}
@Test
void explicitWeightZeroStaysZeroInsteadOfCoercingToOne(@TempDir Path dir) throws Exception {
// CB-554: weight: 0 used to be normalised to 1.0 by this same compact constructor, which
// made it read as "never auto-select" while behaving as "select like anyone else."
Path f = dir.resolve("weight-zero.yaml");
Files.writeString(f, """
bind:
port: 8080
profiles:
opus:
baseUrl: http://gx10.gw:8000
weight: 0
""");
BridgedConfig cfg = BridgedConfig.load(f);
BridgedConfig.Profile w = cfg.profiles().get("opus");
assertEquals(0.0f, w.weight(), 0.0001f, "an explicit weight: 0 must stay 0, not coerce to 1.0");
}
@Test
void negativeWeightNormalisesToZeroNotOne(@TempDir Path dir) throws Exception {
Path f = dir.resolve("weight-negative.yaml");
Files.writeString(f, """
bind:
port: 8080
profiles:
gx10:
baseUrl: http://gx10.gw:8000
weight: -3.0
""");
BridgedConfig cfg = BridgedConfig.load(f);
BridgedConfig.Profile w = cfg.profiles().get("gx10");
assertEquals(0.0f, w.weight(), 0.0001f, "a negative weight behaves as excluded (0), not as an error and not as 1.0");
}
@Test
void subscriptionFlagBindsAndDefaultsFalse(@TempDir Path dir) throws Exception {
Path f = dir.resolve("subscription.yaml");
@@ -336,6 +336,42 @@ class CompositePeerLauncherTest {
assertEquals(10, b);
}
@Test
void weightedPolicySkipsWeightZeroProfileOnUnqualifiedSpawn() {
// CB-554: weight: 0 must exclude a profile from automatic placement, not coerce to 1.0.
FakeHerdr herdr = new FakeHerdr();
Map<String, BridgedConfig.Profile> profiles = ordered(
"a", stubWorker("a", 0.0f, null),
"b", stubWorker("b", 1.0f, null));
StubLauncher adapter = new StubLauncher("claude", herdr, profiles, "a", Set.of());
CompositePeerLauncher composite = new CompositePeerLauncher(
List.of(adapter), "a", profiles, PlacementPolicies.weighted(), _ -> 0);
for (int i = 0; i < 5; i++) {
PeerHandle h = composite.spawn(new SpawnRequest(null, null, null));
assertEquals("b", h.profile(), "profile a has weight 0, so every unqualified spawn must land on b");
}
assertEquals(0, adapter.spawnCount("a"), "a is never chosen automatically");
}
@Test
void explicitSpawnStillSucceedsOnWeightZeroProfile() {
// CB-554: weight: 0 excludes a profile from AUTOMATIC selection only — an explicit
// bridge_spawn{profile:"a"} must still work exactly as today (e.g. `opus` on the
// operator's own subscription, kept weight-0 so it is never picked automatically).
FakeHerdr herdr = new FakeHerdr();
Map<String, BridgedConfig.Profile> profiles = ordered(
"a", stubWorker("a", 0.0f, null),
"b", stubWorker("b", 1.0f, null));
StubLauncher adapter = new StubLauncher("claude", herdr, profiles, "b", Set.of());
CompositePeerLauncher composite = new CompositePeerLauncher(
List.of(adapter), "b", profiles, PlacementPolicies.weighted(), _ -> 0);
PeerHandle h = composite.spawn(new SpawnRequest("a", null, null));
assertEquals("a", h.profile(), "naming a weight-0 profile explicitly bypasses placement and still spawns it");
assertEquals(1, adapter.spawnCount("a"));
}
@Test
void failoverRetriesNextCandidateWhenProfileIsUnreachable() {
FakeHerdr herdr = new FakeHerdr();
@@ -237,6 +237,105 @@ class PlacementPolicyTest {
assertTrue(e.getMessage().contains("1 unreachable"), e.getMessage());
}
// --- CB-554: weight <= 0 excludes a candidate from automatic selection ------------------
@Test
void weightedSkipsWeightZeroProfile() {
PlacementPolicy policy = PlacementPolicies.weighted();
List<PlacementCandidate> candidates = List.of(
PlacementCandidate.profile("a", 0.0f, null),
PlacementCandidate.profile("b", 1.0f, null));
for (int i = 0; i < 5; i++) {
assertEquals("b", policy.select(ctx(candidates, noSessions())).profile(),
"a has weight 0, so every pick lands on b");
}
}
@Test
void weightedThrowsWhenAllWeightZero() {
PlacementPolicy policy = PlacementPolicies.weighted();
List<PlacementCandidate> candidates = List.of(
PlacementCandidate.profile("a", 0.0f, null),
PlacementCandidate.profile("b", 0.0f, null));
PlacementException e = assertThrows(PlacementException.class,
() -> policy.select(ctx(candidates, noSessions())));
assertTrue(e.getMessage().contains("weight 0"), e.getMessage());
}
@Test
void weightedTreatsNegativeWeightAsExcludedNotError() {
PlacementPolicy policy = PlacementPolicies.weighted();
List<PlacementCandidate> candidates = List.of(
PlacementCandidate.profile("a", -1.0f, null),
PlacementCandidate.profile("b", 1.0f, null));
for (int i = 0; i < 5; i++) {
assertEquals("b", policy.select(ctx(candidates, noSessions())).profile(),
"a has a negative weight, so it is excluded like weight 0 — never picked, never an error");
}
}
@Test
void weightZeroAndQuarantinedIsNotDoubleCounted() {
PlacementPolicy policy = PlacementPolicies.weighted();
List<PlacementCandidate> candidates = List.of(
PlacementCandidate.profile("a", 0.0f, null),
PlacementCandidate.profile("b", 1.0f, null),
PlacementCandidate.profile("c", 1.0f, null));
// a is BOTH weight-0 and quarantined; b and c are quarantined only. If a were counted
// into both buckets, "quarantined" would read 3 (or the arithmetic would go negative)
// instead of the true count of 2 candidates actually quarantined.
PlacementContext mixedCtx = ctx(candidates, noSessions(), Set.of(), Set.of("a", "b", "c"));
PlacementException e = assertThrows(PlacementException.class, () -> policy.select(mixedCtx));
assertTrue(e.getMessage().contains("1 weight-0"), e.getMessage());
assertTrue(e.getMessage().contains("2 quarantined"), e.getMessage());
assertTrue(e.getMessage().contains("0 remaining"), e.getMessage());
}
@Test
void roundRobinSkipsWeightZeroProfiles() {
PlacementPolicy policy = PlacementPolicies.roundRobin();
List<PlacementCandidate> candidates = List.of(
PlacementCandidate.profile("a", 0.0f, null),
PlacementCandidate.profile("b", 1.0f, null));
for (int i = 0; i < 5; i++) {
assertEquals("b", policy.select(ctx(candidates, noSessions())).profile(),
"a has weight 0, so every pick lands on b");
}
}
@Test
void roundRobinThrowsWhenAllWeightZero() {
PlacementPolicy policy = PlacementPolicies.roundRobin();
List<PlacementCandidate> candidates = List.of(
PlacementCandidate.profile("a", 0.0f, null),
PlacementCandidate.profile("b", 0.0f, null));
PlacementException e = assertThrows(PlacementException.class,
() -> policy.select(ctx(candidates, noSessions())));
assertTrue(e.getMessage().contains("weight 0"), e.getMessage());
}
@Test
void fixedSkipsWeightZeroDefault() {
PlacementPolicy policy = PlacementPolicies.fixed();
PlacementContext ctx = new PlacementContext("b",
List.of(PlacementCandidate.profile("a", 1.0f, null),
PlacementCandidate.profile("b", 0.0f, null)),
noSessions(), Set.of(), Set.of());
assertEquals("a", policy.select(ctx).profile(),
"the default 'b' has weight 0, so fixed falls through to the first available candidate");
}
@Test
void fixedThrowsWhenDefaultAndEveryCandidateWeightZero() {
PlacementPolicy policy = PlacementPolicies.fixed();
PlacementContext ctx = new PlacementContext("b",
List.of(PlacementCandidate.profile("a", 0.0f, null),
PlacementCandidate.profile("b", 0.0f, null)),
noSessions(), Set.of(), Set.of());
PlacementException e = assertThrows(PlacementException.class, () -> policy.select(ctx));
assertTrue(e.getMessage().contains("weight 0"), e.getMessage());
}
@Test
void unknownPolicyNameThrows() {
assertThrows(IllegalArgumentException.class, () -> PlacementPolicies.fromName("random"));