diff --git a/fleetd/src/main/java/dev/ltms/fleet/mcp/ConnectionIdentity.java b/fleetd/src/main/java/dev/ltms/fleet/mcp/ConnectionIdentity.java
index 01a88a6..7ebb602 100644
--- a/fleetd/src/main/java/dev/ltms/fleet/mcp/ConnectionIdentity.java
+++ b/fleetd/src/main/java/dev/ltms/fleet/mcp/ConnectionIdentity.java
@@ -31,6 +31,20 @@ public final class ConnectionIdentity {
this.cwds = cwds;
}
+ /**
+ * The {@link PaneLocator} this identity resolves callers against — fleetd #612 CB-185: lets a
+ * test drive the exact {@link PaneLocator} a real assembly wired up (e.g. {@code
+ * FleetdAssembly}'s {@code new ConnectionIdentity(new PaneLocator(herdr, memberHerdr), ...)})
+ * directly with a chosen pid, bypassing the OS-dependent {@link PeerPidLookup} that {@link
+ * #resolve} otherwise goes through. A full HTTP round trip cannot exercise this: {@code
+ * LsofPeerPidLookup} excludes its own pid, and an in-process test client and server share one
+ * JVM pid, so {@code pidForLocalPort} always returns {@code -1} and {@link PaneLocator} never
+ * gets called at all.
+ */
+ public PaneLocator panes() {
+ return panes;
+ }
+
/**
* The caller resolved from the connection: its worker {@code terminal} (or {@code null} for the
* primary / an off-host client), its {@code pid} (or {@code -1} if not resolvable), and whether
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 556d6b2..2231e70 100644
--- a/fleetd/src/main/java/dev/ltms/fleet/mcp/FleetMcp.java
+++ b/fleetd/src/main/java/dev/ltms/fleet/mcp/FleetMcp.java
@@ -107,6 +107,13 @@ public final class FleetMcp {
* untested identity heuristic.
*/
private final boolean authorizationEnforced;
+ /**
+ * fleetd #612 CB-185: kept as a field (rather than only captured by the {@code
+ * contextExtractor} closure built in the constructor) so a test can reach the exact {@link
+ * ConnectionIdentity} — and, through {@link ConnectionIdentity#panes()}, the exact {@link
+ * dev.ltms.fleet.herdr.PaneLocator} — that a real assembly wired up. See {@link #identity()}.
+ */
+ private final ConnectionIdentity identity;
private final Metrics metrics; // CB-502: null → auth failures not counted
private final CapacitySource capacity;
private final HealthCoverageSource healthCoverage;
@@ -396,6 +403,7 @@ public final class FleetMcp {
Objects.requireNonNull(callers, "callers");
this.authorizationEnforced = Objects.requireNonNull(authorizationMode, "authorizationMode")
== AuthorizationMode.ENFORCED;
+ this.identity = identity;
this.leadChannel = leadChannel;
this.peers = peers == null ? List.of() : List.copyOf(peers);
this.capacity = capacity;
@@ -755,6 +763,15 @@ public final class FleetMcp {
return transport;
}
+ /**
+ * The {@link ConnectionIdentity} this server resolves every caller against — fleetd #612
+ * CB-185: lets a test reach the exact {@link dev.ltms.fleet.herdr.PaneLocator} a real assembly
+ * wired up (via {@link ConnectionIdentity#panes()}), rather than a copy built for the test.
+ */
+ public ConnectionIdentity identity() {
+ return identity;
+ }
+
/** Mark a connected spawned member available for the injector readiness gate. */
static void markSpawnedMemberPresent(Principal caller, MemberPresence presence) {
if (caller.isSpawnedMember()) {
diff --git a/fleetd/src/test/java/dev/ltms/fleet/FleetdAssemblyConnectionIdentityTest.java b/fleetd/src/test/java/dev/ltms/fleet/FleetdAssemblyConnectionIdentityTest.java
new file mode 100644
index 0000000..df5afdf
--- /dev/null
+++ b/fleetd/src/test/java/dev/ltms/fleet/FleetdAssemblyConnectionIdentityTest.java
@@ -0,0 +1,222 @@
+package dev.ltms.fleet;
+
+import dev.ltms.fleet.config.ConfigRef;
+import dev.ltms.fleet.config.FleetConfig;
+import dev.ltms.fleet.guard.SubscriptionGuard;
+import dev.ltms.fleet.herdr.FakeHerdr;
+import dev.ltms.fleet.herdr.HerdrClient;
+import dev.ltms.fleet.herdr.PaneLocator;
+import io.javalin.Javalin;
+import org.junit.jupiter.api.AfterEach;
+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.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.function.LongSupplier;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+/**
+ * fleetd #612 step 2, unit B2 (CB-185, identity half). Replaces the deleted
+ * {@code FleetdConnectionIdentityConstructionTest}, which pinned this claim by reading {@code
+ * Fleetd.java}'s source text for {@code "new PaneLocator(herdr, memberHerdr)"}. That claim moved
+ * to {@code FleetdAssembly.java} (fleetd #612 Unit A) and is pinned here instead, by driving the
+ * real {@link ConnectionIdentity} — via {@code runtime.mcp().identity()}, not a copy — that {@link
+ * FleetdAssembly#assembleAndStart} built.
+ *
+ *
What this guards against (from the deleted test's own javadoc): pinning
+ * {@code PaneLocator} to {@code memberHerdr} alone leaves every LEAD's own MCP connection
+ * unresolvable ({@code callerTerminal == null}) the moment {@code memberHerdrSocket} names a
+ * second daemon, which breaks {@code fleet_reply}/{@code fleet_ask}/{@code fleet_whoami} for a
+ * lead. {@code PaneLocatorTest} already proves {@link PaneLocator} itself can search two clients
+ * given two — the gap this pins is that the assembly actually passes it two, and in the right
+ * order (lead first).
+ *
+ *
Why this cannot be driven through a real MCP/HTTP round trip. The natural
+ * way to observe {@code ConnectionIdentity} would be a real {@code fleet_whoami} call over the
+ * built {@code FleetMcp}, the way {@code FleetMcpContextExtractorTest} drives its own
+ * hand-built one. That does not work for the REAL assembly, because {@code FleetdAssembly} wires
+ * {@code ConnectionIdentity} with a hardcoded {@code new LsofPeerPidLookup()} (see {@code
+ * FleetdAssembly.java:444}), and {@code LsofPeerPidLookup} explicitly excludes its own PID — see
+ * its javadoc: "we exclude our own PID and take the other end". In a JUnit test the HTTP client
+ * and the daemon under test run in the very same JVM, so the "client" and "server" ends of the
+ * loopback connection ARE the same PID, and {@code pidForLocalPort} always returns {@code -1}
+ * before {@link PaneLocator} is ever reached — proving nothing about which daemon(s) got searched.
+ * This test instead reaches the real {@link PaneLocator} the assembly built (through {@link
+ * ConnectionIdentity#panes()}, added for exactly this) and drives it with a chosen pid directly,
+ * bypassing the OS-dependent PID lookup entirely — a legitimate substitute, since the pid lookup
+ * is not what CB-185 is about.
+ */
+class FleetdAssemblyConnectionIdentityTest {
+
+ /** Same shape as {@code FleetdAssemblyLifecycleTest}'s fake, but keys {@code connectHerdr} by
+ * socket path so the lead and member daemons can be two DIFFERENT {@link FakeHerdr}s. */
+ private static final class TwoHerdrResourcePorts implements ResourcePorts {
+
+ final Map herdrsBySocket = new LinkedHashMap<>();
+ final CopyOnWriteArrayList schedulers = new CopyOnWriteArrayList<>();
+ Runnable shutdownHook;
+
+ @Override
+ public Map environment() {
+ return Map.of();
+ }
+
+ @Override
+ public HerdrClient connectHerdr(Path socketPath) {
+ HerdrClient client = herdrsBySocket.get(socketPath);
+ if (client == null) {
+ throw new IllegalStateException("no fake herdr registered for socket " + socketPath);
+ }
+ return client;
+ }
+
+ @Override
+ public Fleetd.AmqpOpener replyInboxOpener() {
+ return (uri, prefetch) -> new dev.ltms.fleet.msg.InMemoryReplyInbox();
+ }
+
+ @Override
+ public Fleetd.LeadMailboxOpener leadMailboxOpener() {
+ return (uri, selfCoordId, prefetch) -> {
+ throw new UnsupportedOperationException(
+ "leadMailboxOpener must not be called — no coordinator: block is configured");
+ };
+ }
+
+ @Override
+ public LongSupplier nanoClock() {
+ return System::nanoTime;
+ }
+
+ @Override
+ public LongSupplier wallClockNanos() {
+ return System::nanoTime;
+ }
+
+ @Override
+ public ScheduledExecutorService newScheduler(String purpose) {
+ ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
+ schedulers.add(scheduler);
+ return scheduler;
+ }
+
+ @Override
+ public void addShutdownHook(Runnable hook) {
+ this.shutdownHook = hook;
+ }
+
+ @Override
+ public void startHttp(Javalin app, String host, int port) {
+ // Deliberately never bind — this test never issues a real HTTP request.
+ }
+ }
+
+ private FleetdRuntime runtime;
+ private TwoHerdrResourcePorts ports;
+
+ @AfterEach
+ void tearDown() {
+ if (ports != null && ports.shutdownHook != null) {
+ ports.shutdownHook.run();
+ }
+ }
+
+ private static final Path LEAD_SOCKET = Path.of("/fake/lead-herdr.sock");
+ private static final Path MEMBER_SOCKET = Path.of("/fake/member-herdr.sock");
+
+ private static FleetConfig writeConfig(Path dir) throws Exception {
+ Path f = dir.resolve("fleetd.yaml");
+ Files.writeString(f, """
+ bind:
+ host: 127.0.0.1
+ port: 8765
+ herdrSocket: "%s"
+ memberHerdrSocket: "%s"
+ lifecycle:
+ idleTtlSeconds: 600
+ health:
+ enabled: false
+ broker:
+ uri: "amqp://fake-test-broker/vh"
+ """.formatted(LEAD_SOCKET, MEMBER_SOCKET));
+ return FleetConfig.load(f);
+ }
+
+ private FleetdRuntime assemble(Path dir, FakeHerdr lead, FakeHerdr member) throws Exception {
+ FleetConfig cfg = writeConfig(dir);
+ ConfigRef config = new ConfigRef(dir.resolve("fleetd.yaml"), cfg);
+ SubscriptionGuard guard = new SubscriptionGuard(cfg.guard().hostSet());
+ ports = new TwoHerdrResourcePorts();
+ ports.herdrsBySocket.put(LEAD_SOCKET, lead);
+ ports.herdrsBySocket.put(MEMBER_SOCKET, member);
+ runtime = FleetdAssembly.assembleAndStart(new AssemblyInputs(cfg, config, guard), ports);
+ return runtime;
+ }
+
+ /**
+ * The pin. {@code lead} carries the one pane {@link FakeHerdr}'s canned {@code
+ * pane.process_info} ties to {@link FakeHerdr#WORKER_PID} (pane {@code w2:p7}); {@code member}
+ * reports NO panes at all ({@link FakeHerdr#withNoPanes()}) — modelling a second daemon that
+ * simply does not host the caller's pane, exactly the CB-185 javadoc's scenario for a lead's
+ * own connection. If {@code PaneLocator} only ever searches the member daemon (the bug), this
+ * pid resolves to nothing, because the pane that owns it lives on the LEAD daemon the bug
+ * skips.
+ */
+ @Test
+ void connectionIdentitySearchesTheLeadDaemonNotJustTheMemberOne(@TempDir Path dir) throws Exception {
+ FakeHerdr lead = new FakeHerdr();
+ FakeHerdr member = new FakeHerdr().withNoPanes();
+
+ assemble(dir, lead, member);
+
+ PaneLocator panes = runtime.mcp().identity().panes();
+ PaneLocator.Lookup lookup = panes.terminalForPid(FakeHerdr.WORKER_PID);
+
+ assertEquals("term_a", lookup.terminal(),
+ "the pane owning WORKER_PID lives on the LEAD daemon only (the member fake reports "
+ + "no panes) — PaneLocator must still find it, which is only possible if it "
+ + "searches the lead client and not just the member one");
+ }
+
+ /**
+ * The mirror control: when the pane instead lives ONLY on the member daemon (the lead reports
+ * no panes), the lookup must still find it — proving the member client is genuinely searched
+ * too, not merely tolerated as a second, always-losing argument.
+ */
+ @Test
+ void connectionIdentityAlsoSearchesTheMemberDaemon(@TempDir Path dir) throws Exception {
+ FakeHerdr lead = new FakeHerdr().withNoPanes();
+ FakeHerdr member = new FakeHerdr();
+
+ assemble(dir, lead, member);
+
+ PaneLocator panes = runtime.mcp().identity().panes();
+ PaneLocator.Lookup lookup = panes.terminalForPid(FakeHerdr.WORKER_PID);
+
+ assertEquals("term_a", lookup.terminal(),
+ "the pane owning WORKER_PID lives on the MEMBER daemon only — PaneLocator must "
+ + "find it there too");
+ }
+
+ /** Sanity control: a pid nobody owns resolves to nothing on either daemon. */
+ @Test
+ void aPidNoPaneOwnsResolvesToNoTerminalOnEitherDaemon(@TempDir Path dir) throws Exception {
+ FakeHerdr lead = new FakeHerdr();
+ FakeHerdr member = new FakeHerdr();
+
+ assemble(dir, lead, member);
+
+ PaneLocator panes = runtime.mcp().identity().panes();
+ PaneLocator.Lookup lookup = panes.terminalForPid(999_999L);
+
+ assertNull(lookup.terminal());
+ }
+}
diff --git a/fleetd/src/test/java/dev/ltms/fleet/FleetdAssemblyFleetAppTest.java b/fleetd/src/test/java/dev/ltms/fleet/FleetdAssemblyFleetAppTest.java
new file mode 100644
index 0000000..1372c22
--- /dev/null
+++ b/fleetd/src/test/java/dev/ltms/fleet/FleetdAssemblyFleetAppTest.java
@@ -0,0 +1,209 @@
+package dev.ltms.fleet;
+
+import dev.ltms.fleet.config.ConfigRef;
+import dev.ltms.fleet.config.FleetConfig;
+import dev.ltms.fleet.guard.SubscriptionGuard;
+import dev.ltms.fleet.herdr.FakeHerdr;
+import dev.ltms.fleet.herdr.HerdrClient;
+import io.javalin.Javalin;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.function.LongSupplier;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ * fleetd #612 step 2, unit B2 (CB-185, {@code FleetApp} half). Replaces the deleted {@code
+ * FleetdFleetAppConstructionTest}, which pinned this claim by reading {@code Fleetd.java}'s
+ * source text for {@code "new FleetApp(herdr, memberHerdr, workers,"}. That claim moved to {@code
+ * FleetdAssembly.java} (fleetd #612 Unit A) and is pinned here instead, by driving the real {@code
+ * Javalin} app — via {@code runtime.app()}, not a copy — that {@link
+ * FleetdAssembly#assembleAndStart} built and handed to {@link FleetdRuntime}.
+ *
+ * What this guards against (from the deleted test's own javadoc): constructing
+ * {@code FleetApp} with the lead-only {@code herdr} client (dropping {@code memberHerdr}) makes
+ * {@code GET /healthz} report green while the MEMBER daemon is down — so every spawn fails
+ * invisibly — and silently drops every member workspace from {@code GET /sessions}. {@code
+ * FleetAppTwoDaemonTest} already proves {@code FleetApp} itself merges/gates correctly given two
+ * clients; the gap this pins is that the assembly actually passes it two.
+ *
+ *
Unlike the {@code ConnectionIdentity} half of CB-185 ({@code
+ * FleetdAssemblyConnectionIdentityTest}), {@code /healthz} needs no caller identity at all, so
+ * this test can bind {@link FleetdRuntime#app()} to a REAL ephemeral port (exactly {@code
+ * FleetAppTwoDaemonTest} does for its own hand-built {@code FleetApp}) and drive it with a real
+ * {@code HttpClient} — no accessor needed for this half.
+ *
+ *
{@code GET /sessions} could not be driven the same way, so this class does
+ * not pin the merge half of the deleted test's javadoc. {@code /sessions} requires
+ * {@code Authz.Action.READ}, which — through the REAL assembly's real {@code
+ * CallerResolver}/{@code ConnectionIdentity} (built with a hardcoded {@code
+ * new LsofPeerPidLookup()}) — needs {@code Caller.resolved()}, i.e. a real positive pid from
+ * {@code lsof}. {@code LsofPeerPidLookup} excludes its own pid (see its javadoc), and a JUnit
+ * test's HTTP client and the daemon under test share one JVM pid, so the resolved pid is always
+ * {@code -1} and every such request is refused as {@code ANONYMOUS} (fleetd #317's fail-closed
+ * rule) before the route handler — and its {@code memberHerdr} merge — is ever reached. Verified
+ * directly: driving {@code GET /sessions} here returns {@code 401 unauthenticated}, not the
+ * merged body. {@code FleetAppTwoDaemonTest} avoids this because it builds {@code FleetApp} with
+ * {@code callers: null}, which is not what the real assembly passes. The {@code /healthz} pin
+ * below is what this class relies on for CB-185's {@code FleetApp} half; {@code
+ * FleetAppTwoDaemonTest} remains the full behavioural proof that {@code FleetApp} itself merges
+ * {@code /sessions} correctly once handed two clients.
+ */
+class FleetdAssemblyFleetAppTest {
+
+ private static final class TwoHerdrResourcePorts implements ResourcePorts {
+
+ final Map herdrsBySocket = new LinkedHashMap<>();
+ final CopyOnWriteArrayList schedulers = new CopyOnWriteArrayList<>();
+ Runnable shutdownHook;
+
+ @Override
+ public Map environment() {
+ return Map.of();
+ }
+
+ @Override
+ public HerdrClient connectHerdr(Path socketPath) {
+ HerdrClient client = herdrsBySocket.get(socketPath);
+ if (client == null) {
+ throw new IllegalStateException("no fake herdr registered for socket " + socketPath);
+ }
+ return client;
+ }
+
+ @Override
+ public Fleetd.AmqpOpener replyInboxOpener() {
+ return (uri, prefetch) -> new dev.ltms.fleet.msg.InMemoryReplyInbox();
+ }
+
+ @Override
+ public Fleetd.LeadMailboxOpener leadMailboxOpener() {
+ return (uri, selfCoordId, prefetch) -> {
+ throw new UnsupportedOperationException(
+ "leadMailboxOpener must not be called — no coordinator: block is configured");
+ };
+ }
+
+ @Override
+ public LongSupplier nanoClock() {
+ return System::nanoTime;
+ }
+
+ @Override
+ public LongSupplier wallClockNanos() {
+ return System::nanoTime;
+ }
+
+ @Override
+ public ScheduledExecutorService newScheduler(String purpose) {
+ ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
+ schedulers.add(scheduler);
+ return scheduler;
+ }
+
+ @Override
+ public void addShutdownHook(Runnable hook) {
+ this.shutdownHook = hook;
+ }
+
+ @Override
+ public void startHttp(Javalin app, String host, int port) {
+ // Deliberately never bind here — this test binds runtime.app() itself, for real, below.
+ }
+ }
+
+ private static final Path LEAD_SOCKET = Path.of("/fake/lead-herdr.sock");
+ private static final Path MEMBER_SOCKET = Path.of("/fake/member-herdr.sock");
+
+ private final HttpClient http = HttpClient.newHttpClient();
+ private FleetdRuntime runtime;
+ private TwoHerdrResourcePorts ports;
+ private Javalin boundApp;
+
+ @AfterEach
+ void tearDown() {
+ if (boundApp != null) {
+ boundApp.stop();
+ }
+ if (ports != null && ports.shutdownHook != null) {
+ ports.shutdownHook.run();
+ }
+ }
+
+ private static FleetConfig writeConfig(Path dir) throws Exception {
+ Path f = dir.resolve("fleetd.yaml");
+ Files.writeString(f, """
+ bind:
+ host: 127.0.0.1
+ port: 8765
+ herdrSocket: "%s"
+ memberHerdrSocket: "%s"
+ lifecycle:
+ idleTtlSeconds: 600
+ health:
+ enabled: false
+ broker:
+ uri: "amqp://fake-test-broker/vh"
+ """.formatted(LEAD_SOCKET, MEMBER_SOCKET));
+ return FleetConfig.load(f);
+ }
+
+ /** Assembles the real graph, then binds the real {@code Javalin app} to an ephemeral port. */
+ private int assembleAndBind(Path dir, FakeHerdr lead, FakeHerdr member) throws Exception {
+ FleetConfig cfg = writeConfig(dir);
+ ConfigRef config = new ConfigRef(dir.resolve("fleetd.yaml"), cfg);
+ SubscriptionGuard guard = new SubscriptionGuard(cfg.guard().hostSet());
+ ports = new TwoHerdrResourcePorts();
+ ports.herdrsBySocket.put(LEAD_SOCKET, lead);
+ ports.herdrsBySocket.put(MEMBER_SOCKET, member);
+ runtime = FleetdAssembly.assembleAndStart(new AssemblyInputs(cfg, config, guard), ports);
+ boundApp = runtime.app().start("127.0.0.1", 0);
+ return boundApp.port();
+ }
+
+ private HttpResponse get(int port, String path) throws Exception {
+ HttpRequest req = HttpRequest.newBuilder(URI.create("http://127.0.0.1:" + port + path)).GET().build();
+ return http.send(req, HttpResponse.BodyHandlers.ofString());
+ }
+
+ /**
+ * The pin. The MEMBER daemon is down; the LEAD daemon is healthy. If the assembly built
+ * {@code FleetApp} with only the lead client (the bug: passing {@code herdr} where {@code
+ * memberHerdr} is expected), the down member is invisible and {@code /healthz} stays 200.
+ */
+ @Test
+ void healthzGoesRedWhenTheMemberDaemonIsDownEvenThoughTheLeadIsUp(@TempDir Path dir) throws Exception {
+ FakeHerdr lead = new FakeHerdr();
+ FakeHerdr member = new FakeHerdr().healthy(false);
+
+ int port = assembleAndBind(dir, lead, member);
+
+ HttpResponse res = get(port, "/healthz");
+ assertEquals(503, res.statusCode(),
+ "a down MEMBER daemon must not be masked by a healthy lead: " + res.body());
+ }
+
+ /** Sanity control: both daemons healthy must still be green through the real assembly. */
+ @Test
+ void healthzIsGreenWhenBothDaemonsAreUp(@TempDir Path dir) throws Exception {
+ FakeHerdr lead = new FakeHerdr();
+ FakeHerdr member = new FakeHerdr();
+
+ int port = assembleAndBind(dir, lead, member);
+
+ assertEquals(200, get(port, "/healthz").statusCode());
+ }
+}
diff --git a/fleetd/src/test/java/dev/ltms/fleet/FleetdConnectionIdentityConstructionTest.java b/fleetd/src/test/java/dev/ltms/fleet/FleetdConnectionIdentityConstructionTest.java
deleted file mode 100644
index 7274264..0000000
--- a/fleetd/src/test/java/dev/ltms/fleet/FleetdConnectionIdentityConstructionTest.java
+++ /dev/null
@@ -1,31 +0,0 @@
-package dev.ltms.fleet;
-
-import java.nio.file.Files;
-import java.nio.file.Path;
-import org.junit.jupiter.api.Test;
-
-import static org.junit.jupiter.api.Assertions.assertFalse;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-
-/**
- * CB-185: {@code ConnectionIdentity} must resolve a caller's pane on EITHER herdr daemon (a
- * lead's MCP connection resolves against the lead daemon; a member's against the member daemon).
- * Pinning {@code PaneLocator} to {@code memberHerdr} alone — the bug this guards against — leaves
- * every lead's own connection unresolvable ({@code callerTerminal == null}) the moment
- * {@code memberHerdrSocket} names a second daemon, which breaks {@code fleet_reply}/{@code
- * fleet_ask} and {@code fleet_whoami} for a lead. A unit test on {@link
- * dev.ltms.fleet.herdr.PaneLocator} alone (see {@code PaneLocatorTest}) proves the class CAN
- * search two clients, but not that {@code Fleetd.main} actually wires it that way — hence this
- * source-level assertion, the same technique {@code FleetdHerdrControlConstructionTest} uses.
- */
-class FleetdConnectionIdentityConstructionTest {
- @Test
- void connectionIdentitySearchesBothDaemonsNotJustTheMemberOne() throws Exception {
- String source = Files.readString(Path.of("src/main/java/dev/ltms/fleet/Fleetd.java"));
- assertFalse(source.contains("new PaneLocator(memberHerdr)"),
- "PaneLocator must not be pinned to the member daemon alone — a lead's own "
- + "connection resolves against the LEAD daemon and would never be found");
- assertTrue(source.contains("new PaneLocator(herdr, memberHerdr)"),
- "PaneLocator must search the lead daemon first, then the member daemon");
- }
-}
diff --git a/fleetd/src/test/java/dev/ltms/fleet/FleetdFleetAppConstructionTest.java b/fleetd/src/test/java/dev/ltms/fleet/FleetdFleetAppConstructionTest.java
deleted file mode 100644
index a7e8482..0000000
--- a/fleetd/src/test/java/dev/ltms/fleet/FleetdFleetAppConstructionTest.java
+++ /dev/null
@@ -1,29 +0,0 @@
-package dev.ltms.fleet;
-
-import java.nio.file.Files;
-import java.nio.file.Path;
-import org.junit.jupiter.api.Test;
-
-import static org.junit.jupiter.api.Assertions.assertFalse;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-
-/**
- * CB-185: {@code FleetApp} must be constructed with BOTH herdr clients (the lead's and the
- * member's), never the raw lead-only {@code herdr}. Passing only {@code herdr} — the bug this
- * guards against — makes {@code GET /healthz} green while the member daemon is down (so every
- * spawn fails invisibly) and silently drops every member workspace from {@code GET /sessions}.
- * A behavioural test on {@code FleetApp} alone (see {@code FleetAppTwoDaemonTest}) proves the
- * class merges/gates correctly when given two clients, but not that {@code Fleetd.main} actually
- * passes it two — hence this source-level assertion, mirroring
- * {@code FleetdHerdrControlConstructionTest}.
- */
-class FleetdFleetAppConstructionTest {
- @Test
- void fleetAppIsConstructedWithBothHerdrDaemons() throws Exception {
- String source = Files.readString(Path.of("src/main/java/dev/ltms/fleet/Fleetd.java"));
- assertFalse(source.contains("new FleetApp(herdr, workers,"),
- "FleetApp must not be constructed with the lead-only herdr client");
- assertTrue(source.contains("new FleetApp(herdr, memberHerdr, workers,"),
- "FleetApp must be constructed with both the lead and the member herdr client");
- }
-}