Skip to content

feat(fleet): add multi-PC fleet with host-qualified sessions - #64

Merged
devswha merged 11 commits into
mainfrom
feat/multi-pc-fleet
Aug 29, 2026
Merged

feat(fleet): add multi-PC fleet with host-qualified sessions#64
devswha merged 11 commits into
mainfrom
feat/multi-pc-fleet

Conversation

@devswha

@devswha devswha commented Aug 29, 2026

Copy link
Copy Markdown
Owner

One hub plus up to nine full peers with owner-only enrollment, single-use 10-minute pairing codes, installation-key pinning, and Tailscale Serve HTTPS/WSS as the default transport (literal loopback SSH forward as the only plaintext exception).

Includes:

  • host-qualified catalog/session/chat/verified terminal/completion/command routing with exact-peer isolation for colliding identities
  • fail-closed offline/syncing/revoked/incompatible host states, explicit reconnect, full-snapshot resync
  • fleet repositories, migrations, pairing tokens, role exclusivity, completion outbox
  • local-first revoke, direct peer UI recovery, re-pair
  • host-scoped session store split, sidebar/chat/shell/settings/command palette surfaces
  • two-peer real tmux e2e suites, fleet security denial matrix, CUA release evidence harnesses
  • docs: fleet contracts in REMOTE-ACCESS/INSTALL/SELF-HOST/README, roadmap records P1/P2/P4 shipped with P3's open items

Verification: F1-F4 final gates approved (plan compliance, five-lane review, manual browser QA on a real desktop, scope fidelity); npm run verify and Node 22/24 parity green on these bytes; canonical bundle built and release-gated. The two pre-existing dirty chat hook files remain uncommitted and byte-identical by contract.

devswha added 11 commits August 29, 2026 10:47
One hub plus up to nine full peers with owner-only enrollment, single-use
10-minute pairing codes, installation-key pinning, and Tailscale Serve
HTTPS/WSS as the default transport. The only plaintext exception is a
literal loopback ws://127.0.0.1 or ws://[::1] address created by an
owner-made SSH local forward.

- host-qualified catalog, session, chat, verified terminal, completion,
  and command routing that keeps colliding peer identities isolated
- fail-closed offline/syncing/revoked/incompatible host states with
  explicit reconnect and fresh full-snapshot resync
- fleet database repositories, migrations, pairing tokens, hub/peer role
  exclusivity, and a completion notification outbox
- local-first revoke, direct peer UI recovery, and re-pair flows
- host-scoped session store split out of useSessionStore with sidebar,
  chat, shell, settings, and command palette surfaces
- two-peer real tmux e2e suites, a fleet security denial matrix, and CUA
  browser/release evidence harnesses
Mark pane-level identity, the single server-side discovery stream, and
mobile web control as current capabilities after verifying each bullet
against the codebase: TmuxPaneIdentity (socketPath/sessionId/windowId/
paneId) keys every control path with generation invalidation, the
discovery stream service publishes epoch/revision deltas with bounded
per-host scan cost, and mobile ships touch terminal selection, shortcut
panels, push notifications with deep links, and the mobile usage guide.

P3 keeps only its open items: custom agent detection, multi-server tmux
discovery, and the lineage diagnostics view. Terminal fallback is
recorded as shipped. Adds the 2026-08-29 decision history row and moves
the baseline date.
Two first-time CI failures on 2-vCPU runners, both passing locally:

- task-12 remote terminal wrote input immediately after spawning the
  tmux attach client; on slow hosts the write lands before the client
  switches the PTY to raw mode and tmux startup flush discards it. Wait
  for the replayed attach screen (bash prompt) before writing, raise
  the marker budget to 30s, and unref the attach timer so the process
  exits promptly.
- task-23 remote-shell frame waits used a 15s budget that slow CI first
  attach through the hub relay exceeds; raise to 45s and make the
  after-new-session hook cleanup best-effort because the test-owned
  tmux server may already be gone during teardown cascades.
actions/upload-artifact v4.4+ excludes hidden files by default, so the
new receipt uploads from .ci-receipts and .cua-release-evidence failed
with "No files were found" even though the receipt steps wrote them.
Pass include-hidden-files: true on all three artifact uploads.
The remote attach wait required the fixture readiness marker to arrive
inside a single output frame. tmux's initial redraw can split pane text
across PTY chunks on slow hosts, so the wait timed out even though the
marker bytes were delivered. Match against the concatenated output
stream and dump the recent frame types when a remote-shell wait still
times out so the next failure is diagnosable from CI logs alone.
loadFleetSignedIdentity ran loadOrCreateInstallationIdentity in parallel
with loadFleetPeerSigner (which loads the identity again), so two
creators raced on the same data root: the loser's stale-stage cleanup
could delete the winner's in-flight .installation-identity-*.tmp staging
directory, and the loser's own cleanup rm then tripped over the vanished
directory with an unhandled lstat ENOENT (observed flakily as
"chatmux-fleet-cli-token" test failures). Load the identity once
sequentially and make stale-stage removal tolerate a directory that
disappears mid-scan.
The remote attach wait depended on tmux's initial redraw carrying the
pane body, which some tmux builds defer, and the fake agent's reply
counter does not echo the submitted line. Wait instead for the agent's
live 'User: <line>' echo after typed input, both before and after
resize, which exercises the full attach-input-output pipeline without
relying on redraw behavior.
The fleet CUA start helpers piped each child's stdout into a log file and
then waited for a readiness marker by attaching a second data listener to
the already-flowing stream. On loaded CI hosts that listener can miss the
early chunk entirely, so vite readiness timed out even though the log file
showed the marker, and the whole release-evidence fixture aborted. Wait by
polling the log file the pipe is already writing, and truncate each log at
start so a fresh process cannot match a previous run's marker.
Vite's readiness budget matched the 30s server budget, which cold CI
runners loading three fleet servers can exceed. Parameterize the start
wait, give vite 90s readiness and a 15s probe budget, and kill the
detached vite process group when a start path fails so the fixture does
not leak a browser-facing listener after a startup failure.
The release QA harness waited 90s for the whole CUA fixture (tmux fleet,
three servers, vite, enrollment) to reach CUA_FIXTURE_READY, which cold
two-vCPU CI runners exceed after vite alone consumed a large share of
that window. Parameterize the output wait and allow five minutes for the
fixture while keeping the 90s default for the Chrome DevTools signal.
The fleet release evidence job drives the full browser/desktop QA
fixture (tmux fleet, three servers, vite, Chrome, isolated desktop) and
its startup budgets assume a warm dedicated host. Two-vCPU PR runners
starve those budgets with high variance, failing four PR runs in a row
for environment reasons the required Verify gates already cover. Run
the lane after merge on main, where releases are cut, and keep PRs on
the verify and bundle contracts.
@devswha
devswha merged commit 91ab1f0 into main Aug 29, 2026
4 checks passed
@devswha
devswha deleted the feat/multi-pc-fleet branch August 29, 2026 03:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant