Computer Use Runtime Hardening

This follow-up addresses lifecycle gaps found during review of PR #892.

Problems

  • clearSession() did not create a stop tombstone when no session-state record existed yet, so a first queued invocation could activate after cleanup.
  • Read-only host actions did not acquire a session lease and could continue after user_stopped.
  • Later lifecycle events could overwrite blocked_url or user_stopped.
  • A process-wide executor queue blocked unrelated windows, while a first per-session replacement allowed two sessions to interleave snapshot/validation/dispatch against the same window.
  • The ambiguity gate included ephemeral element IDs even when a stable element identity was available.
  • cursor_position formatting existed without a production backend result.
  • Service release reasons did not distinguish a session-only notification from a real child-generation release, so clearSession(A) could discard retained observations and keyboard ownership for unrelated session B.

Root Cause

The Runtime treated observation and mutation leases as the only operations that needed lifecycle fencing. Cleanup also mutated only an already-created state record, while terminal transitions shared the same unrestricted transition helper as recoverable states.

The executor queue was scoped to the caller rather than the resource being validated. Ambiguity signatures also mixed stable identity with snapshot-local IDs, and the cursor result test used only a fake backend.

The backend also inferred generation loss from the release reason. The no-in-flight clearSession() path emits session_cleared without stopping the child, so the reason alone cannot establish that shared executor state changed.

Fix

  • Create the same-turn stop tombstone unconditionally during clearSession().
  • Require an observation lease for every host-reading or waiting action.
  • Make blocked_url and user_stopped absorb later lifecycle events.
  • Serialize mutations by bound PID/window, with one conservative queue for unbound mutations; unrelated bound windows may still progress independently.
  • Ignore ephemeral element IDs when a stable element identity is present.
  • Read get_cursor_position through the pinned cua-driver and return the resolved point without moving the pointer.
  • Carry generationReleased as an explicit service release fact. Session-only releases invalidate only their listed sessions; actual child exits sweep all retained session observations and keyboard targets.

A new turn still creates a fresh Computer Use session state, preserving the existing explicit recovery boundary.

Verification

  • npm --workspace @maka/runtime run typecheck
  • npm --workspace @maka/runtime test
  • npm --workspace @maka/computer-use test
  • git diff --check