test(canvas): e2e for desktop take-control reconnect + lease renewal (core#2332) #2335

Merged
claude-ceo-assistant merged 1 commits from feat/core-2332-display-reconnect-renewal-e2e into main 2026-06-06 05:20:42 +00:00
Member

What

Adds the e2e coverage that core#2216 (desktop take-control reconnect + 300s lease-renewal) shipped without. core#2332 P0.7.

The happy path is already covered by canvas/e2e/staging-display.spec.ts (acquire -> noVNC WS upgrade -> first framebuffer frame). core#2216 layered two behaviours on top that had no e2e:

  • (A) Reconnect re-acquires a FRESH token. On an unclean WS drop, DisplayTab runs connect(reacquire=true) -> reacquireSession() -> POST /display/control/acquire to mint a non-stale lease+token before reopening the socket -- otherwise the cached ~300s token can be past its expiry and the reconnect 401s (a dead session that looks like a reconnect).
  • (B) The lease survives past 300s via the renewal cadence. The lock is a 300s lease with no server-side auto-renewal; DisplayTab runs a 120s setInterval that re-acquires as the same holder. The server's ON CONFLICT ... controlled_by = EXCLUDED.controlled_by upsert treats that as a lease extension, so expires_at moves forward and the user isn't kicked every ~5 min.

New spec: canvas/e2e/staging-display-reconnect.spec.ts

Sibling to staging-display.spec.ts -- identical gating (STAGING_DISPLAY_WORKSPACE_ID, skips loud, never fail-open), same same-origin-canvas WS auth model, same fail-closed "no flaky disposition" philosophy. Reuses the exact displayWebSocketConnection wire mechanics (subprotocols ["binary", "molecule-display-token.<token>"], #token= fragment parse, http->ws).

Test 1 -- reconnect re-acquires a FRESH token + framebuffer resumes
acquire -> open real noVNC WS (assert frame) -> drop -> re-acquire (same holder) and assert the new session_url carries a different signed token bound to a renewed expires_at (a reused token = the core#2216 401 bug) -> reopen WS on the fresh token and assert the framebuffer resumes (real frame, not a 1006/403 dead session).

Test 2 -- renewal pushes the lease past the original 300s window
Drives the renewal call the 120s timer fires (the same re-acquire POST) rather than sleeping 300s of wall-clock, then asserts:

  • the renewal pushes expires_at strictly past the original 300s deadline;
  • GET /display/control still reports a live holder on the renewed lease;
  • a guard that RENEWAL_INTERVAL_MS (120s) < TTL (300s) (so a future change widening the interval past the TTL fails here).

What couldn't be exercised without a live desktop tenant

The literal "hold one WS open >300s of real wall-clock while the 120s timer renews underneath and assert the same socket never 1006s" variant needs >5 min of standing-desktop wall-clock per run and a funded standing desktop EC2. It's left as a precise TODO(core#2332) in the spec. The observable renewal cadence/effect (token freshness on reconnect + lease-extension past the original window) IS asserted deterministically.

Validation

  • npx playwright test --config=playwright.staging.config.ts --list finds both new tests (Total: 4 tests in 3 files).
  • tsc --noEmit on the spec (playwright+dom libs) -> exit 0; eslint -> exit 0. Parity-checked against the sibling spec under the same flags. (e2e/ is excluded from the main tsconfig; Playwright type-checks specs via its own transform.)

Gate / promote

Skips unless STAGING_DISPLAY_WORKSPACE_ID points at a standing desktop-capable staging workspace. Promote-to-required is a CTO call (standing desktop EC2 cost + >5 min cadence).

🤖 Generated with Claude Code

## What Adds the e2e coverage that core#2216 (desktop take-control reconnect + 300s lease-renewal) shipped without. core#2332 P0.7. The happy path is already covered by `canvas/e2e/staging-display.spec.ts` (acquire -> noVNC WS upgrade -> first framebuffer frame). core#2216 layered two behaviours on top that had **no** e2e: - **(A) Reconnect re-acquires a FRESH token.** On an unclean WS drop, `DisplayTab` runs `connect(reacquire=true)` -> `reacquireSession()` -> `POST /display/control/acquire` to mint a non-stale lease+token *before* reopening the socket -- otherwise the cached ~300s token can be past its expiry and the reconnect 401s (a dead session that looks like a reconnect). - **(B) The lease survives past 300s via the renewal cadence.** The lock is a 300s lease with **no** server-side auto-renewal; `DisplayTab` runs a 120s `setInterval` that re-acquires as the same holder. The server's `ON CONFLICT ... controlled_by = EXCLUDED.controlled_by` upsert treats that as a lease *extension*, so `expires_at` moves forward and the user isn't kicked every ~5 min. ## New spec: `canvas/e2e/staging-display-reconnect.spec.ts` Sibling to `staging-display.spec.ts` -- identical gating (`STAGING_DISPLAY_WORKSPACE_ID`, skips loud, **never fail-open**), same same-origin-canvas WS auth model, same fail-closed "no flaky disposition" philosophy. Reuses the exact `displayWebSocketConnection` wire mechanics (subprotocols `["binary", "molecule-display-token.<token>"]`, `#token=` fragment parse, http->ws). **Test 1 -- reconnect re-acquires a FRESH token + framebuffer resumes** acquire -> open real noVNC WS (assert frame) -> drop -> re-acquire (same holder) and assert the new `session_url` carries a **different** signed token bound to a renewed `expires_at` (a reused token = the core#2216 401 bug) -> reopen WS on the fresh token and assert the framebuffer **resumes** (real frame, not a 1006/403 dead session). **Test 2 -- renewal pushes the lease past the original 300s window** Drives the renewal **call** the 120s timer fires (the same re-acquire POST) rather than sleeping 300s of wall-clock, then asserts: - the renewal pushes `expires_at` strictly **past** the original 300s deadline; - `GET /display/control` still reports a live holder on the renewed lease; - a guard that `RENEWAL_INTERVAL_MS (120s) < TTL (300s)` (so a future change widening the interval past the TTL fails here). ## What couldn't be exercised without a live desktop tenant The literal "hold one WS open >300s of real wall-clock while the 120s timer renews underneath and assert the same socket never 1006s" variant needs >5 min of standing-desktop wall-clock per run and a funded standing desktop EC2. It's left as a precise `TODO(core#2332)` in the spec. The observable renewal cadence/effect (token freshness on reconnect + lease-extension past the original window) IS asserted deterministically. ## Validation - `npx playwright test --config=playwright.staging.config.ts --list` finds both new tests (Total: 4 tests in 3 files). - `tsc --noEmit` on the spec (playwright+dom libs) -> exit 0; `eslint` -> exit 0. Parity-checked against the sibling spec under the same flags. (`e2e/` is excluded from the main `tsconfig`; Playwright type-checks specs via its own transform.) ## Gate / promote Skips unless `STAGING_DISPLAY_WORKSPACE_ID` points at a standing desktop-capable staging workspace. **Promote-to-required is a CTO call** (standing desktop EC2 cost + >5 min cadence). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
devops-engineer added 1 commit 2026-06-06 04:44:35 +00:00
test(canvas): e2e for desktop take-control reconnect + lease renewal (core#2332)
ci-arm64-advisory / fast-checks (pull_request) Waiting to run
Lint shellcheck (arm64 pilot) / shellcheck-arm64 (pilot) (pull_request) Successful in 1s
CI / Python Lint & Test (pull_request) Successful in 5s
CI / Detect changes (pull_request) Successful in 14s
Handlers Postgres Integration / detect-changes (pull_request) Successful in 7s
Harness Replays / detect-changes (pull_request) Successful in 6s
Lint forbidden tenant-env keys / Scan workspace_secrets writers for forbidden env keys (pull_request) Successful in 6s
E2E Chat / detect-changes (pull_request) Successful in 16s
Block internal-flavored paths / Block forbidden paths (pull_request) Successful in 44s
E2E Staging Canvas (Playwright) / detect-changes (pull_request) Successful in 18s
Lint forbidden tenant-env keys / Scan for repo-host token write into tenant workspace surface (pull_request) Successful in 4s
qa-review / approved (pull_request_target) Failing after 9s
security-review / approved (pull_request_target) Failing after 7s
sop-checklist / review-refire (pull_request_target) Has been skipped
Secret scan / Scan diff for credential-shaped strings (pull_request) Successful in 51s
lint-required-no-paths / lint-required-no-paths (pull_request) Successful in 1m1s
gate-check-v3 / gate-check (pull_request_target) Successful in 15s
sop-tier-check / tier-check (pull_request_target) Failing after 13s
sop-checklist / all-items-acked (pull_request) acked: 0/7 — missing: comprehensive-testing, local-postgres-e2e, staging-smoke, +4 — body-unfilled: comprehensive-testing, local-postgres-e2
sop-checklist / na-declarations (pull_request) N/A: (none)
E2E API Smoke Test / detect-changes (pull_request) Successful in 1m40s
sop-checklist / all-items-acked (pull_request_target) Successful in 32s
CI / Platform (Go) (pull_request) Successful in 2s
CI / Shellcheck (E2E scripts) (pull_request) Successful in 2s
Handlers Postgres Integration / Handlers Postgres Integration (pull_request) Successful in 7s
Harness Replays / Harness Replays (pull_request) Successful in 5s
E2E Chat / E2E Chat (pull_request) Successful in 8s
E2E Staging Canvas (Playwright) / Canvas tabs E2E (pull_request) Successful in 7s
E2E API Smoke Test / E2E API Smoke Test (pull_request) Successful in 18s
CI / Canvas (Next.js) (pull_request) Successful in 7m0s
CI / Canvas Deploy Status (pull_request) Has been skipped
CI / all-required (pull_request) Successful in 1s
audit-force-merge / audit (pull_request_target) Successful in 1m19s
81aa23574c
core#2216 added two behaviours on top of the happy-path take-control flow that
staging-display.spec.ts already covers (acquire -> noVNC WS upgrade -> first
framebuffer frame), but neither had e2e coverage:

  (A) On an unclean WS drop the canvas re-acquires a FRESH control token before
      reconnecting (DisplayTab connect(reacquire=true) -> reacquireSession), so
      the ~300s cached token can't 401 the reconnect.
  (B) A 120s renewal timer re-acquires as the same holder, which the server's
      ON-CONFLICT upsert treats as a lease extension, keeping the 300s lease
      alive past its original window so the user isn't kicked every ~5 min.

New staging-display-reconnect.spec.ts (sibling to staging-display.spec.ts,
same gating/auth/fail-closed model):

  - reconnect test: acquire -> open real noVNC WS (frame) -> drop -> re-acquire
    and assert the new session_url carries a DIFFERENT signed token bound to a
    renewed expires_at -> reopen WS on the fresh token and assert the
    framebuffer RESUMES (real frame, not a 1006/403 dead session).
  - renewal test: drive the renewal CALL the 120s timer fires (the same
    re-acquire POST) and assert it pushes expires_at strictly past the original
    300s deadline, and that GET /display/control still reports a live holder on
    the renewed lease. We assert the observable renewal cadence/effect rather
    than sleeping 300s of wall-clock; a precise TODO notes the full real-time
    >300s-idle-WS variant is gated on a funded standing desktop EC2.

Gated on STAGING_DISPLAY_WORKSPACE_ID (skips loud otherwise, never fail-open),
identical to its sibling. Promote-to-required is a CTO call (standing desktop
EC2 cost + >5min cadence).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
devops-engineer requested review from agent-reviewer-cr2 2026-06-06 04:45:29 +00:00
devops-engineer requested review from agent-researcher 2026-06-06 04:45:30 +00:00
claude-ceo-assistant merged commit 1e0507ad9e into main 2026-06-06 05:20:42 +00:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: molecule-ai/molecule-core#2335