Context
The hosted web-terminal stack (#140 foundation, #136 byte-offset resume) has two server-side
correctness mechanisms that currently rest on implicit guarantees rather than tests.
Problem
1. Heartbeat reaping is untested. PtyWebSocketServer runs a protocol-level ping/pong
heartbeat (HEARTBEAT_INTERVAL_MS = 30_000, isAlive flip, client.terminate() on a missed
pong) so a browser tab that vanished without a close frame (laptop sleep, dropped wifi) is
reaped instead of lingering as a phantom subscriber. There is no test that a socket which stops
answering pings is marked isAlive=false on the next tick and terminated on the tick after.
2. The replay→subscribe invariant is undocumented and untested. On attach,
PtyWebSocketServer does, in order: getReplay(sessionId, offset) (snapshots the buffer +
computes attached{base,gap,next}) → sends size → sends attached → sends the replay binary
frame → addSubscriber(sessionId, ws) (joins the live stream). Correctness (no dropped or
duplicated bytes across the replay/live seam) depends entirely on no PTY data event being
delivered to a new subscriber between the getReplay snapshot and addSubscriber — i.e. on
Node's single-threaded synchronous execution of that block. This ordering invariant is load-bearing
but only implied by a code comment; nothing pins it.
Acceptance criteria
Refs
Foundation #140, offset-resume #136. Files: maestro-server/src/infrastructure/websocket/PtyWebSocketServer.ts,
maestro-server/src/application/services/PtyHostService.ts.
Context
The hosted web-terminal stack (#140 foundation, #136 byte-offset resume) has two server-side
correctness mechanisms that currently rest on implicit guarantees rather than tests.
Problem
1. Heartbeat reaping is untested.
PtyWebSocketServerruns a protocol-level ping/pongheartbeat (
HEARTBEAT_INTERVAL_MS = 30_000,isAliveflip,client.terminate()on a missedpong) so a browser tab that vanished without a close frame (laptop sleep, dropped wifi) is
reaped instead of lingering as a phantom subscriber. There is no test that a socket which stops
answering pings is marked
isAlive=falseon the next tick and terminated on the tick after.2. The replay→subscribe invariant is undocumented and untested. On attach,
PtyWebSocketServerdoes, in order:getReplay(sessionId, offset)(snapshots the buffer +computes
attached{base,gap,next}) → sendssize→ sendsattached→ sends the replay binaryframe →
addSubscriber(sessionId, ws)(joins the live stream). Correctness (no dropped orduplicated bytes across the replay/live seam) depends entirely on no PTY
dataevent beingdelivered to a new subscriber between the
getReplaysnapshot andaddSubscriber— i.e. onNode's single-threaded synchronous execution of that block. This ordering invariant is load-bearing
but only implied by a code comment; nothing pins it.
Acceptance criteria
PtyWebSocketServertest proves a socket that stops responding to pings is flipped toisAlive=falseon the first heartbeat tick andterminate()d on the next (fake timers).unref'd and cleared onstopHeartbeat()/ servershutdown (no leaked timer, process can exit).
datachunk immediatelyafter
getReplay, the reconnecting client receives that chunk exactly once (via live stream,not replay) with no gap and no duplication — the replay
nextoffset abuts the first live byte.attach site (why the two calls must not be separated by an
awaitor async boundary).Refs
Foundation #140, offset-resume #136. Files:
maestro-server/src/infrastructure/websocket/PtyWebSocketServer.ts,maestro-server/src/application/services/PtyHostService.ts.