Two startup refusals give a reason that #669 Unit D made false: the roster is consulted before every tab map, so a live member is no longer read back as a lead #711

Closed
opened 2026-10-04 06:47:08 +02:00 by ltms · 1 comment
Owner

The claim, and why it is now false

Two FleetConfig validators refuse to start and both give the same reason: a member landing in a
lead's or collaborator's labelled tab would be read back as that identity and granted its
authority
.

  • FleetConfig.java:2874-2914 — validatePanePlacementAgainstLeadTabs, javadoc at :2880 and
    the throw message at :2911.
  • FleetConfig.java:2776-2781 — the tabLabel/tabPrefix collision refusal, throw message at
    :2779.

#669 Unit D changed that. CallerResolver now consults the spawned-member roster first, ahead of
every tab map
, and says so in its own comment (CallerResolver.java:294-299):

MemberRole spawnedRole = spawnedMemberRole.apply(c.terminal());
if (spawnedRole != null) {
    // A live spawned member occupies this pane. Its identity is its own, whatever a tab
    // … so a tab label can never override a roster entry for the same terminal.

So for a live, registered member the stated consequence cannot happen. The member resolves as
itself.

The refusals are still right — for a different reason

Do not delete either check. A member pane is only protected while it is in the roster, and there
are windows where it is not:

  • #702 — release() removes the registry entry, then shells out to git before stopping the
    pane. In that window the pane is alive and unregistered.
  • A pane that outlives a daemon restart. FleetMcp.java:1303 names this case: "a worker the
    registry has no record of — one that outlived a daemon restart".

In either window the tab map is the only thing left, and a member pane sitting in a lead's
labelled tab is read back as that lead. That is the hazard the refusal still prevents, and it is
a narrower and more precise claim than the one written today.

Scope

Re-key the reason in both validators, and in everything that repeats it. The same sentence is
written in 7 places:

File Lines
fleetd/src/main/java/dev/ltms/fleet/config/FleetConfig.java 2779, 2880, 2911
fleetd/src/test/java/dev/ltms/fleet/config/FleetConfigTest.java 853, 1091
wiki/11-Features.md 697, 708

The wiki is a submodule and is the lead's to push — it is listed so the count is honest, not as
part of the code change.

Not measured

Nobody has driven a live member pane into a lead tab during either window. The reachability
argument above is read from the source: CallerResolver.java:294-299 for the precedence,
SessionManager.release() for the teardown window, FleetMcp.java:1303 for the restart case.

## The claim, and why it is now false Two `FleetConfig` validators refuse to start and both give the same reason: a member landing in a lead's or collaborator's labelled tab **would be read back as that identity and granted its authority**. - `FleetConfig.java:2874-2914` — `validatePanePlacementAgainstLeadTabs`, javadoc at `:2880` and the throw message at `:2911`. - `FleetConfig.java:2776-2781` — the `tabLabel`/`tabPrefix` collision refusal, throw message at `:2779`. #669 Unit D changed that. `CallerResolver` now consults the spawned-member roster **first, ahead of every tab map**, and says so in its own comment (`CallerResolver.java:294-299`): ```java MemberRole spawnedRole = spawnedMemberRole.apply(c.terminal()); if (spawnedRole != null) { // A live spawned member occupies this pane. Its identity is its own, whatever a tab // … so a tab label can never override a roster entry for the same terminal. ``` So for a **live, registered** member the stated consequence cannot happen. The member resolves as itself. ## The refusals are still right — for a different reason Do not delete either check. A member pane is only protected while it is in the roster, and there are windows where it is not: - **#702** — `release()` removes the registry entry, then shells out to `git` before stopping the pane. In that window the pane is alive and unregistered. - **A pane that outlives a daemon restart.** `FleetMcp.java:1303` names this case: "a worker the registry has no record of — one that outlived a daemon restart". In either window the tab map is the only thing left, and a member pane sitting in a lead's labelled tab *is* read back as that lead. That is the hazard the refusal still prevents, and it is a narrower and more precise claim than the one written today. ## Scope Re-key the reason in both validators, and in everything that repeats it. The same sentence is written in 7 places: | File | Lines | |---|---| | `fleetd/src/main/java/dev/ltms/fleet/config/FleetConfig.java` | 2779, 2880, 2911 | | `fleetd/src/test/java/dev/ltms/fleet/config/FleetConfigTest.java` | 853, 1091 | | `wiki/11-Features.md` | 697, 708 | The wiki is a submodule and is the lead's to push — it is listed so the count is honest, not as part of the code change. ## Not measured Nobody has driven a live member pane into a lead tab during either window. The reachability argument above is read from the source: `CallerResolver.java:294-299` for the precedence, `SessionManager.release()` for the teardown window, `FleetMcp.java:1303` for the restart case.
Author
Owner

Done and verified in main — closing

Landed as PR #714, implementation commit 7d497aa, merge c468953. I confirmed c468953 is an
ancestor of origin/main and then read the text out of main itself rather than out of the PR:

$ git show origin/main:fleetd/src/main/java/dev/ltms/fleet/config/FleetConfig.java \
    | grep -n 'spawned-member roster'
2780:  + "spawned-member roster, is read back as a lead or collaborator and granted "
2881:  * there, while its pane carries no entry in the spawned-member roster, is read back as that
2914:  + "carries no entry in the spawned-member roster, it is read back as the lead or "

Both refusals now state the condition that is still real — the pane is alive and carries no
entry in the spawned-member roster — instead of the one #669 Unit D made false.

One correction to PR #714's own description, repeated here because it is the lasting record

The PR description claimed a spawn-time window where a pane is alive but not yet registered.
That window does not exist. idle precedes the MCP connect, so registration follows within
microseconds. I corrected this on the PR. The two windows that are real:

  1. #702 — release() removes the roster entry, then shells out to git before stopping the
    pane, so the resolver goes blind while the pane is still alive. Still open.
  2. A pane that outlived a daemon restart — the roster is in-memory, so a restart empties it
    while the panes survive.

That second one is why #705 option 1 (the OBSERVER floor) must ship in the same commit as
the presence split: a floor alone would make a restart-surviving pane permanently undeliverable.
Tracked on #705, not here.

## Done and verified in `main` — closing Landed as PR #714, implementation commit `7d497aa`, merge `c468953`. I confirmed `c468953` is an ancestor of `origin/main` and then read the text out of `main` itself rather than out of the PR: ``` $ git show origin/main:fleetd/src/main/java/dev/ltms/fleet/config/FleetConfig.java \ | grep -n 'spawned-member roster' 2780: + "spawned-member roster, is read back as a lead or collaborator and granted " 2881: * there, while its pane carries no entry in the spawned-member roster, is read back as that 2914: + "carries no entry in the spawned-member roster, it is read back as the lead or " ``` Both refusals now state the condition that is still real — the pane is alive **and** carries no entry in the spawned-member roster — instead of the one #669 Unit D made false. ### One correction to PR #714's own description, repeated here because it is the lasting record The PR description claimed a spawn-time window where a pane is alive but not yet registered. **That window does not exist.** `idle` precedes the MCP connect, so registration follows within microseconds. I corrected this on the PR. The two windows that are real: 1. **#702** — `release()` removes the roster entry, then shells out to git before stopping the pane, so the resolver goes blind while the pane is still alive. Still open. 2. **A pane that outlived a daemon restart** — the roster is in-memory, so a restart empties it while the panes survive. That second one is why #705 option 1 (the `OBSERVER` floor) must ship **in the same commit** as the presence split: a floor alone would make a restart-surviving pane permanently undeliverable. Tracked on #705, not here.
ltms closed this issue 2026-10-04 07:58:33 +02:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: fleet/fleetd#711