CB-185: fix connection-identity/status/health gaps a second herdr daemon exposes #188
Reference in New Issue
Block a user
Delete Branch "worker/cb185-router-routing-gaps-9e9d33-3"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Fixes the three routing gaps named in the CB-185 review, each invisible today because memberHerdrSocket is unset (both clients are the same object) and each wrong the moment a second daemon is configured.
StatusRefiner.refine(target, raw, control); the poller now refines through the SAME AgentControl the raw status was sampled from (router.agentsFor(target)), not a control fixed at construction./healthzstayed green while the member daemon was down (every spawn then fails invisibly) andGET /sessionssilently dropped every member workspace. Fixed: FleetApp now takes both clients; healthz requires both to answer, sessions merges workspaces from both. Legacy single-client constructors kept (memberHerdr defaults to herdr).All three collapse to byte-for-byte today's behaviour when memberHerdrSocket is unset (herdr == memberHerdr) — verified by tests that assert exactly one herdr call is made in that case.
Tests, each proven to fail without its fix (reverted the production change, reran, restored):
FleetdConnectionIdentityConstructionTest/FleetdFleetAppConstructionTest— source-level assertions against the realFleetd.javawiring (same technique asFleetdHerdrControlConstructionTest), since a unit test on the class alone doesn't prove the daemon actually wires it that way.PaneLocatorTest— 4 new cases: fallback lead→member, member→lead, neither matches, and same-object collapse (asserts exactly onepane.listcall).StatusRefinerTest— 2 new cases for therefine(target, raw, control)overload.StatusPollerRoutingTest— exercises the realStatusPoller(HerdrRouter, ...)+Injector(HerdrRouter, ...)wired together (not a hand-built graph): a lead target only unwedges and delivers when refined against the LEAD daemon's pane content, never the member's.FleetAppTwoDaemonTest— realFleetAppagainst two fake herdr clients: healthz 503 when either is down, 200 when both are up, sessions merges workspaces from both, and both make exactly one call when lead==member.Build:
mvn -f fleetd/pom.xml clean install— BUILD SUCCESS, Tests run: 1005, Failures: 0, Errors: 0, Skipped: 0.Base branch is worker/cb185-router-d6436d-3 (this PR builds on that branch's HerdrRouter/memberHerdrSocket work), not main.
Lead review — approved, merging
All three findings are properly fixed, and the "fails without the fix" step was actually done rather
than claimed. I checked each one against the diff.
1.
PaneLocator— the two-client constructor searches lead first, then member, and collapses toone client on object identity.
Fleetd.javanow passesnew PaneLocator(herdr, memberHerdr). Theone-client constructor is untouched. Lead-first is also the cheap order in practice: the lead daemon
holds few panes, and
terminalForPidis onepane.listplus apane.process_infoper pane.2.
StatusRefiner— the per-callcontroloverload is the right call over a refiner per side.The routing decision already lives in exactly one place (
HerdrRouter.agentsFor), andStatusPolleralready computes it each iteration for the
status()call; reusing that same value forrefine()removes the seam instead of copying the lead/member split into a second class.
3.
FleetApp— both endpoints gated and merged, legacy constructors preserved and delegatingwith
memberHerdr = herdr. The single-daemon path makes exactly one call each, and that is assertedwith call counts rather than argued by inspection.
The caveat you raised — resolved, nothing to do
You left
agents()andlistMembers()alone as out of scope. That was the right instinct, and theyturn out to be safe anyway:
workers.list(), which isCompositePeerLauncher.list(). #187 landed in themeantime (
a237fbf) and now keys that dedupe by (owning daemon, pane id) instead of the raw paneid, so a second daemon's panes no longer disappear there.
listMembersjoins onterminalId, not the pane coordinate. Terminal ids are timestamp+random,not per-daemon counters, so they do not collide the way
w1:p1does.On the source-text tests
FleetdConnectionIdentityConstructionTestandFleetdFleetAppConstructionTestare brittle by nature— a rename breaks them. I am accepting them anyway, for the reason your javadoc gives: a unit test on
PaneLocatorproves the class can search two clients and says nothing about whetherFleetd.maindoes. Breaking noisily on a rename is the correct failure mode here.
StatusPollerRoutingTestis thestrongest of the set, because it wires the real
StatusPollerandInjectortogether and showsdelivery only happens when the right daemon's content is read.
Verification — mine, not yours
I merged this branch onto current
main(which now carries #187) and built the result:Tests run: 1012, Failures: 0, Errors: 0—BUILD SUCCESS. No conflicts.One follow-up, not blocking
/healthznow requires both daemons to answer, but the 200 body still reports only the LEADdaemon's
versionandprotocol. In two-daemon mode it is the MEMBER daemon's protocol that decideswhether spawns work — which is exactly what
scripts/redeploy-fleetd.shwarns about. Reporting bothbelongs in a later unit; I have added it to the list on #185.