#185: refuse an unowned paneId when more than one herdr daemon could own it #187
Reference in New Issue
Block a user
Delete Branch "worker/cb185-paneids-992586-2"
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?
Part of #185. Opened on behalf of the implementing member, whose authenticated POST was blocked by its command classifier.
Why
Members are going to run on a second herdr daemon under a different OS user, so they cannot read the operator's credentials (#184). I measured what two live herdr daemons do to ids:
Workspace, tab and pane ids are per-daemon sequential counters. Both daemons held
w1:p1at once, pointing at different panes owned by different users. Collision is guaranteed, not unlucky.fleet_stop{paneId}takes a paneId, so today's fallback — "no recorded owner, route to the first adapter" — would close a pane on an arbitrary daemon.What changed
CompositePeerLauncher.stopkeeps its spawn-time paneId→adapter record. When there is no record it now counts distinct herdr daemons and refuses the id if there is more than one, instead of guessing.The count is over
HerdrClientidentity, deliberately: two adapter kinds sharing one client are one daemon, and identity keeps that true even if a client ever gains value equality.The bug this went through first
The first revision guarded on
delegates.size() != 1. That conflates adapter kinds with daemons — the live Mac fleet runssonnet/opus/local/local-directasclaude-codeandgx/sol/terra/xfasopencode, so it holds 2 delegates on 1 daemon. That revision would have thrown on the current fleet for any pane with no recorded owner, most obviously after a restart. Caught in review; the added test now pins the two-adapters-one-daemon case.Compatibility
Single-daemon behaviour is unchanged, which matters because a live fleet is running against this.
fleet_listoutput andfleet_stopinput are untouched — no id format change, no new required prefix. Today the throw is unreachable: nothing can yet supply a second client. It becomes reachable on its own once theHerdrRouter(#186) does, with no flag to remember.Known follow-up, not addressed here
spawnedByis in-memory, so it is empty after a restart. Once two-daemon mode is real, every surviving pane is unowned and everyfleet_stopon one will refuse. Recovering pane ownership at boot is a separate unit. Refusing is still strictly safer than the old behaviour of closing a pane on an arbitrary daemon.CompositePeerLauncher.list()also still deduplicates by raw paneId, which assumes global uniqueness — reported by the implementer, deliberately not fixed here.Tests
Tests run: 990, Failures: 0, Errors: 0, Skipped: 0—BUILD SUCCESS.stopKeepsSameHerdrPaneIdSeparateByOwningAdapterstopAllowsALegacyBarePaneIdWithOneDaemonstopRejectsAnUnownedPaneIdWhenMultipleDaemonsCouldOwnItstopAllowsAnUnownedPaneIdWithTwoAdaptersSharingOneDaemon— the live Mac shapeLead review — approved with three fixes, pushed as
31d5516I read the diff myself and ran one reviewer against it. Both found real problems. I fixed them here
rather than send a 3-line change back.
1.
stop()lost the owner when the delegate refused (reviewer)spawnedBy.remove(id)ran befored.stop(id).HerdrPeerLauncher.stoprethrows anypane.closeerror that is not "already gone", so a delegate that threw left the pane alive with itsowner forgotten. The retry then fell into the new ambiguous branch and refused the id for good — the
pane became unstoppable. The record is now dropped only after the delegate accepted the stop.
Test:
stopKeepsTheOwnerRecordWhenTheDelegateRefusesTheStop.2.
list()deduplicated on the raw pane id (reviewer, rated high)The PR body listed this as a known follow-up. I fixed it here instead, because #186 is what makes it
reachable and the two would land together. Pane ids are per-daemon counters, so two daemons can each
hold
w1:p1on different panes; keying on the pane id alone silently dropped one of two real agentsfrom
fleet_listand from every status view built on it. The key is now (owning daemon, pane id).Delegates that share one daemon still collapse — that is what the dedupe was for, and it still works.
Tests:
listKeepsBothPanesWhenTwoDaemonsShareAPaneIdandlistStillDeduplicatesTwoAdaptersSharingOneDaemon.3. The class javadoc still asserted the old premise (mine)
The "Fleet-wide" bullet still said
listis deduplicated by pane id "because every herdr-backeddelegate shares one herdr connection" — stated as fact, directly under the bullet this PR had just
corrected for
stop. Fixed there too. Also dropped a redundantly qualifiedjava.util.Collections.What I checked and did not change
herdrDaemonCount()counts distinctHerdrClientidentities, not delegates. That is the rightcount, and identity is the right comparison — the live Mac fleet is 2 adapter kinds on 1 daemon,
and the first revision of this PR would have thrown on it.
fleet_listoutput andfleet_stopinput are untouched.spawnedBystill does not survive a restart. Still a real follow-up, still strictly safer than theold "close a pane on an arbitrary daemon".
HerdrPeerLauncher.stophas the same shape one level down:paneByAgentId.remove(idOrPane)alsoruns before the close. A failed stop there drops to the raw-pane fallback rather than refusing, so
it is not the same severity. Not fixed here — noted so it is not lost.
AgentControl.herdr()/HerdrPeerLauncher.herdr()are wider than strictly needed. Left asis:
list()now needs the client for keying too, so a narrowersharesHerdr(...)no longer coversthe use.
Build — run by me, not piped
mvn -f fleetd/pom.xml clean installin a clean worktree at31d5516:Tests run: 993, Failures: 0, Errors: 0—BUILD SUCCESS.(990 before, plus the three tests above.)
Merging.