fleetd #505: refuse (not promote) a caller whose pane scan errored #508
Reference in New Issue
Block a user
Delete Branch "worker/505-03f8b2-1"
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?
fleetd #505 — a herdr error during the pane scan must not resolve a worker as the primary
The defect
PaneLocator.paneOwnsAnyOfcaughtHerdrExceptionfrompane.process_infoand returnedfalse— "this pane does not own the pid" — for both a pane that genuinely vanished mid-scanand a pane that failed for a transient reason while it actually did own the caller's pid.
If the failing pane was the caller's own, the scan finished with a clean-looking
nullterminal on a resolved (real) pid — exactly the shape
CallerResolver's loopback-trustfallback reads as the primary. That is a worker→primary privilege escalation through a door
fleetd #317 did not close: #317 guards a failed lsof lookup (
Caller.resolved()), not afailed herdr pane scan.
The fix — the third-state shape, and why
Per the ticket,
Caller.resolved()is not widened — it stays exactlypid > 0, centralisednext to the lsof
-1sentinel it tests (ConnectionIdentity.java:48-59).Instead,
PaneLocator.terminalForPidnow returns a record,Lookup(String terminal, boolean complete), rather than a bareString. I chose the record shape (the ticket's second option)over a three-valued
paneOwnsAnyOfpropagated all the way up, because the record cleanlyseparates "the answer" from "how much of the scan is behind that answer" at every level
(
paneOwnsAnyOf→Ownershipenum internally,scan→Lookupper herdr client,terminalForPid→Lookupacross every client) without needing a second return channel.paneOwnsAnyOfnow returns a privateOwnershipenum:OWNS/DOES_NOT_OWN/UNKNOWN.UNKNOWNis the new case — a herdr error, not a confirmed non-match.scan(one herdr client) returnsLookup(terminal, complete): a match short-circuitsimmediately as
complete = trueregardless of any other pane's earlier failure — apane that genuinely vanished mid-scan but was never the caller's own must not turn into a
refusal (the ticket's second acceptance test).
terminalForPid(across every searched herdr client, CB-185's lead/member split) returns thefirst definite match found on any client, else
Lookup(null, complete)wherecompleteisthe AND of every client's completeness — one daemon's error must not read as a clean negative
for the whole scan.
ConnectionIdentity.Callergains a third component,scanComplete, carried straight fromLookup.complete().resolved()is untouched.CallerResolver's loopback-trust fallback now requires bothc.resolved()andc.scanComplete()before grantingPrincipal.primary(...); an incomplete scan resolvesPrincipal.anonymous()— failing toward the recoverable error, as the ticket asks: a refusedprimary retries loudly, a promoted worker would not.
warnnaming the pane id and which herdr client (of how many) failed, so theincomplete-scan path is diagnosable rather than silent (#317's own lesson, per the ticket).
LsofPeerPidLookup.javaandLsofProcessCwdLookup.javawere not touched, per the ticket.Tests
PaneLocatorTest: the discriminating case —pane.process_infofails for exactly the panethat owns the caller's pid (
w2:p7,term_a) — assertsterminal() == nullandcomplete() == false. Companion test: a different, non-owning pane fails(
w2:p9) — asserts the real match is still found (term_a) andcomplete() == true.ConnectionIdentityTest: propagates the same scenario throughCaller—resolved()true,terminal()null,scanComplete()false; distinguished from the existing "real primary, noherdr error" test which now also asserts
scanComplete()true.CallerResolverTest: the acceptance-level pair — an error on the owning pane resolvesRole.ANONYMOUS(notPRIMARY); an error on a non-owning pane still resolvesRole.WORKERwith the right terminal. All pre-existing #317 tests (failed lsof lookup, real primary, token
mode, leads/architects) stay green untouched.
FakeHerdrgainedprocessInfoFailsForPane(paneId, code), mirroring the existingpaneCloseFailsForPane/tabCloseFailsForTabper-target-failure pattern.PaneLocatorContractTestupdated for theLookupreturn type only (.terminalForPid(pid)→.terminalForPid(pid).terminal()); not run here (needs a live herdr socket —assumeTrueskips without one, consistent with the addendum's herdr-socket-test carve-out).
Mutation proof (self-verified; the lead will re-run)
PaneLocatorTest15/15,ConnectionIdentityTest8/8,CallerResolverTest40/40 green (full-suite run:Tests run: 1694, Failures: 0, Errors: 0).Lookup/OwnershipAPI, so tests still compile):src/main/java/dev/ltms/fleet/herdr/PaneLocator.java:183,return Ownership.UNKNOWN;→return Ownership.DOES_NOT_OWN;(pre-fix behaviour: a herdr error reads as a cleannon-match again).
mvn test -Dtest=PaneLocatorTest,ConnectionIdentityTest,CallerResolverTest:BUILD FAILURE, exactlyTests run: 63, Failures: 3, Errors: 0:CallerResolverTest.aHerdrErrorOnTheOwningPaneDuringTheScanIsRefusedNotPromotedToPrimary—expected: <ANONYMOUS> but was: <PRIMARY>ConnectionIdentityTest.scanIsIncompleteWhenHerdrErrorsOnThePaneThatOwnsThePid—expected: <false> but was: <true>PaneLocatorTest.anErrorOnThePaneThatOwnsThePidMakesTheScanIncompleteNotAClearNegative—expected: <false> but was: <true>All 60 other tests in those 3 classes (including the "vanished pane, still resolves" and
"real primary" companions) stayed green — the mutant is caught by exactly the 3 tests meant
to pin it, nothing else.
comment (present), one matching the original
return Ownership.UNKNOWN;line (absent,grep exit 1).
shasum -a 256onPaneLocator.javamatched the pre-mutation hashbyte-for-byte (
81b797a6cfb823e489e938b215956271d5741cce49dc9732f779c7ed64a3b811).mvn clean install, unpiped —Tests run: 1694, Failures: 0, Errors: 0, Skipped: 0,BUILD SUCCESS.Build
cd fleetd && mvn clean install(unpiped):Tests run: 1694, Failures: 0, Errors: 0, Skipped: 0,BUILD SUCCESS.Out of scope, noted only
LsofPeerPidLookup.java/LsofProcessCwdLookup.java— left untouched per the ticket's explicitinstruction; not the same defect.