diff --git a/fleetd/src/main/java/dev/ltms/fleet/member/HerdrPeerLauncher.java b/fleetd/src/main/java/dev/ltms/fleet/member/HerdrPeerLauncher.java index 8087d88..40e3cf6 100644 --- a/fleetd/src/main/java/dev/ltms/fleet/member/HerdrPeerLauncher.java +++ b/fleetd/src/main/java/dev/ltms/fleet/member/HerdrPeerLauncher.java @@ -1497,10 +1497,27 @@ public abstract class HerdrPeerLauncher implements PeerLauncher { * survive {@code allowed} (including the {@code LC_*} prefix rule). Neither number is a constant: * both come from the actual derived set and the actual environment this spawn sees. Never logs a * variable NAME or VALUE — only the counts. + * + *

Under {@code memberHerdrSocket} the counts describe fleetd's own process, not the + * member's (fleetd #269 follow-up), so the message says so rather than leaving the + * reader to infer it from this javadoc, which the operator reading the log never sees. */ private void logAllowListCoverage(Set allowed) { Set hostNames = hostEnvNames.get(); long kept = hostNames.stream().filter(name -> MemberEnvAllowList.keeps(allowed, name)).count(); + if (memberHerdrSocketConfigured()) { + // fleetd #269 covered the sibling line below (logCredentialGap) and stopped there. + // This line has the same problem: read plainly, "allowed 7 of 39" is a statement about + // the member's pane, and under memberHerdrSocket it is not -- the pane is routed to a + // second herdr whose environment fleetd cannot inspect. The counts stay useful, so + // this is not a WARN and not a refusal; only the claim is narrowed to what is true. + log.info("member credentials: allowed {} of {} names in fleetd's OWN environment — " + + "memberHerdrSocket is configured, so member panes are routed to a " + + "second herdr whose environment fleetd has no channel to inspect. " + + "These counts describe fleetd's process, NOT the member pane's.", + kept, hostNames.size()); + return; + } log.info("member credentials: allowed {} of {}", kept, hostNames.size()); } diff --git a/fleetd/src/test/java/dev/ltms/fleet/member/HerdrPeerLauncherAllowListWiringTest.java b/fleetd/src/test/java/dev/ltms/fleet/member/HerdrPeerLauncherAllowListWiringTest.java index d0ab9a5..50611d3 100644 --- a/fleetd/src/test/java/dev/ltms/fleet/member/HerdrPeerLauncherAllowListWiringTest.java +++ b/fleetd/src/test/java/dev/ltms/fleet/member/HerdrPeerLauncherAllowListWiringTest.java @@ -250,6 +250,57 @@ class HerdrPeerLauncherAllowListWiringTest { + appender.list.stream().map(ILoggingEvent::getFormattedMessage).toList()); } + /** + * fleetd #269 follow-up: the same overclaim the WARN in {@code logCredentialGap} was fixed for, + * in the INFO line beside it. With {@code memberHerdrSocket} configured, member panes are routed + * to a second herdr whose environment fleetd has no channel to inspect, so the counts come from + * fleetd's OWN environment. The bare line "member credentials: allowed 1 of 3" reads as a fact + * about the member's pane, and there it is not one. + * + *

#269 reworded four sites and stopped at the sibling below; this pins the pair together so + * a future edit cannot fix one and leave the other. Real path: asserted after a real {@link + * HerdrPeerLauncher#spawn}, reading the log production actually emits. + */ + @Test + void theAllowedCountLineSaysWhoseEnvironmentItCountedWhenMemberHerdrSocketIsSet(@TempDir Path worktreeRoot) + throws IOException { + String group = currentUserGroup(); + FakeHerdr herdr = new FakeHerdr(); + Set hostEnvNames = Set.of(INJECTED, "SOME_UNRELATED_NAME", "ANOTHER_UNRELATED_NAME"); + WiringLauncher launcher = new WiringLauncher(herdr, allowList(), "/bin/bash-should-be-ignored", + () -> hostEnvNames, + () -> configWithMemberHerdrSocketRootAndGroup("/tmp/other-user-herdr.sock", "/bin/zsh", + worktreeRoot.toString(), group)); + + Logger logger = (Logger) LoggerFactory.getLogger(HerdrPeerLauncher.class); + Level original = logger.getLevel(); + logger.setLevel(Level.INFO); + ListAppender appender = new ListAppender<>(); + appender.start(); + logger.addAppender(appender); + try { + launcher.spawn(new SpawnRequest("test", null, null, null, null, MemberRole.DEV)); + } finally { + logger.detachAppender(appender); + logger.setLevel(original); + } + + List lines = appender.list.stream().map(ILoggingEvent::getFormattedMessage).toList(); + String coverage = lines.stream() + .filter(l -> l.startsWith("member credentials: allowed ")) + .findFirst() + .orElse(null); + assertNotNull(coverage, "the coverage line must still be logged — narrowing the claim must " + + "not silently delete the line: " + lines); + assertTrue(coverage.contains("fleetd's OWN environment"), + "the line must say whose environment it counted: " + coverage); + assertTrue(coverage.contains("NOT the member pane's"), + "and must say plainly that it is not the member's: " + coverage); + // The counts themselves stay real — narrowing the claim must not turn them into constants. + assertTrue(coverage.startsWith("member credentials: allowed 1 of 3"), + "the real counts must survive the rewording: " + coverage); + } + /** * Lead-review fix: on a NON-zsh shell no scrub ever runs (bash ignores {@code ZDOTDIR}), so the * "allowed N of M" line — which describes what the scrub does — must not be printed there either.