From 9debc0de273fc4628e49d1d8cf22eb0ec49c7a69 Mon Sep 17 00:00:00 2001 From: Dai Ha Date: Fri, 4 Sep 2026 12:19:02 +0700 Subject: [PATCH] #297: render both profiles doors from one body builder, not two copies The merged change shared the QuarantineSource and OutageSource instances between fleet_profiles and GET /profiles, so the two doors read identical facts. It then rendered those facts through a character-for-character copy of the loop, in a different file. Shared inputs do not make duplicated computation safe: a later edit to the row shape lands on one door and not the other, and the two disagree about a live outage. That is what #284 was. The ticket caused this. It said 'read from the same shared instances' and 'do not change FleetMcp', and together those made copying the loop the only legal move. Extracting FleetMcp.profilesView and calling it from both is what the ticket should have asked for. --- .../java/dev/ltms/fleet/mcp/FleetMcp.java | 21 ++++++++++- .../java/dev/ltms/fleet/rest/FleetApp.java | 36 +++---------------- 2 files changed, 24 insertions(+), 33 deletions(-) diff --git a/fleetd/src/main/java/dev/ltms/fleet/mcp/FleetMcp.java b/fleetd/src/main/java/dev/ltms/fleet/mcp/FleetMcp.java index d567411..c25083d 100644 --- a/fleetd/src/main/java/dev/ltms/fleet/mcp/FleetMcp.java +++ b/fleetd/src/main/java/dev/ltms/fleet/mcp/FleetMcp.java @@ -1015,6 +1015,25 @@ public final class FleetMcp { * both maps at once when it is both exhaustion-quarantined AND cooling off. */ static McpSchema.CallToolResult profiles(PeerLauncher workers, QuarantineSource quarantine, OutageSource outage) { + return text(json(profilesView(workers, quarantine, outage))); + } + + /** + * The body both front doors answer {@code profiles} with: the configured profile names, the + * default, and the two independent outage states — {@code quarantined} (the backend reported it + * out of capacity) and {@code coolingOff} (the credential threw repeated non-exhaustion backend + * errors). Each map is present only when at least one profile is in that state, and a profile + * can appear in both at once, because the two checks are separate. + * + *

fleetd #297: extracted so {@code fleet_profiles} and {@code GET /profiles} render from ONE + * body builder rather than two copies. Passing both doors the same {@link QuarantineSource} and + * {@link OutageSource} instances is necessary but not sufficient: with the loop written out + * twice, a later edit to the row shape — a renamed key, an added field — lands on one door and + * not the other, and the two then disagree about a live outage. That is exactly what fleetd + * #284 was, where one rule computed in two places was widened in only one and a single response + * contradicted itself. Shared inputs do not make duplicated computation safe. + */ + public static Map profilesView(PeerLauncher workers, QuarantineSource quarantine, OutageSource outage) { Map result = new LinkedHashMap<>(); result.put("profiles", workers.profiles()); result.put("default", workers.defaultProfile() == null ? "" : workers.defaultProfile()); @@ -1046,7 +1065,7 @@ public final class FleetMcp { if (!coolingOff.isEmpty()) { result.put("coolingOff", coolingOff); } - return text(json(result)); + return result; } /** diff --git a/fleetd/src/main/java/dev/ltms/fleet/rest/FleetApp.java b/fleetd/src/main/java/dev/ltms/fleet/rest/FleetApp.java index 94c9420..56a36fc 100644 --- a/fleetd/src/main/java/dev/ltms/fleet/rest/FleetApp.java +++ b/fleetd/src/main/java/dev/ltms/fleet/rest/FleetApp.java @@ -427,38 +427,10 @@ public final class FleetApp { if (!allow(ctx, routeAction("GET /profiles"), null)) { return; } - Map body = new LinkedHashMap<>(); - body.put("profiles", workers.profiles()); - body.put("default", workers.defaultProfile() == null ? "" : workers.defaultProfile()); - Map quarantined = new LinkedHashMap<>(); - Map coolingOff = new LinkedHashMap<>(); - for (String profile : workers.profiles()) { - String credentialId = quarantine.credentialIdFor().apply(profile); - if (credentialId != null) { - quarantine.quarantine().remainingSeconds(credentialId).ifPresent(remaining -> { - Map row = new LinkedHashMap<>(); - row.put("credentialId", credentialId); - row.put("quarantinedForSeconds", remaining); - quarantined.put(profile, row); - }); - } - String outageCredentialId = outage.credentialIdFor().apply(profile); - if (outageCredentialId != null) { - outage.outagePolicy().remainingCoolOffSeconds(outageCredentialId).ifPresent(remaining -> { - Map row = new LinkedHashMap<>(); - row.put("credentialId", outageCredentialId); - row.put("coolingOffForSeconds", remaining); - coolingOff.put(profile, row); - }); - } - } - if (!quarantined.isEmpty()) { - body.put("quarantined", quarantined); - } - if (!coolingOff.isEmpty()) { - body.put("coolingOff", coolingOff); - } - ctx.status(200).json(body); + // fleetd #297: ONE body builder, shared with fleet_profiles. Handing both doors the same + // QuarantineSource/OutageSource instances stops them reading different facts; rendering + // through the same method stops them reporting those facts differently. Both are needed. + ctx.status(200).json(FleetMcp.profilesView(workers, quarantine, outage)); } /**