Add display control lock endpoints #1718

Merged
hongming merged 1 commits from feat/1686-display-control-lock into main 2026-05-23 08:08:08 +00:00
Owner

Implements the next backend-only slice for #1686: a volatile display control lock contract for future native display takeover.

What changed

  • Adds workspace_display_control_locks with TTL-backed per-workspace ownership state.
  • Adds admin-gated endpoints:
    • GET /workspaces/:id/display/control
    • POST /workspaces/:id/display/control/acquire
    • POST /workspaces/:id/display/control/release
  • Uses atomic acquire semantics: active locks can only be renewed by the same attributable actor; otherwise callers receive 409 with the current holder.
  • Keeps browser session callers observe-only for mutation until middleware exposes a stable per-user/session id. Mutation requires ADMIN_TOKEN bearer or org-token actor; force release requires ADMIN_TOKEN.
  • Validates acquire only works for workspaces with display enabled, so non-display instances cannot get control rows.
  • Emits best-effort structure_events audit rows for acquire/release started/completed/failed.

Comprehensive testing performed

  • go test ./internal/handlers -run 'TestWorkspaceDisplayControl'
  • go test ./internal/handlers ./internal/router ./internal/db
  • go test ./...
  • go build ./cmd/server

Local-postgres E2E run

Ran Stage A with isolated temp services:

  • Postgres: local initdb/pg_ctl on port 55432
  • Redis: local redis-server on port 56379
  • Server: DATABASE_URL=postgres://dev@localhost:55432/molecule?sslmode=disable REDIS_URL=redis://localhost:56379 PORT=18086 BIND_ADDR=127.0.0.1 ADMIN_TOKEN=stage-a-admin-token MOLECULE_ENV=development go run ./cmd/server

Evidence:

  • Fresh server boot applied 20260523120000_workspace_display_control_locks.up.sql with all repo migrations.
  • \d workspace_display_control_locks confirmed PK, FK cascade, controller check, controlled_by length check, and expires index.
  • Endpoint probe on a display-enabled workspace:
    • GET /display/control -> {"controller":"none"}
    • POST /display/control/acquire -> {"controller":"user","controlled_by":"admin-token","expires_at":"..."}
    • DB row showed active = true
    • POST /display/control/release -> {"controller":"none"}
    • DB row count returned 0
  • structure_events showed display.control.acquire.started, display.control.acquire.completed, display.control.release.started, and display.control.release.completed for the workspace.
  • Up/down migration pair was run on the populated temp DB; down dropped the table and up recreated it cleanly.

Docker was not running locally. The server attempted Docker provisioner auto-detection and orphan-sweeper checks returned Docker connection errors without stopping any containers.

Staging-smoke verified or pending

Pending merge/deploy. This PR adds backend coordination endpoints only; no Canvas takeover UI is wired in this slice.

Root-cause not symptom

The display roadmap needs native computer-level takeover coordination before exposing a live screen/control UI. This adds the coordination primitive without leaking DCV/session details or changing GET /workspaces/:id/display availability semantics.

Five-Axis review walked

  • Correctness: addressed reviewer findings by blocking coarse session mutation, checking RowsAffected on release, validating display-enabled workspace before acquire, and using a separate none DTO so zero timestamps are not serialized.
  • Readability/simplicity: kept implementation in one display-control handler file with explicit DTOs and no Canvas changes.
  • Architecture: stores volatile lock state outside workspaces.compute; existing display status remains backward-compatible.
  • Security: routes stay under AdminAuth; SQL is parameterized; browser sessions cannot mutate until stable identity exists; force release requires actual ADMIN_TOKEN bearer.
  • Performance: one indexed row per workspace; acquire is one atomic upsert plus conflict lookup only on contention.

No backwards-compat shim / dead code added

No compatibility shim. Existing display status endpoint behavior is unchanged. No dead code added.

Memory/saved-feedback consulted

Used the repo SOP from internal/runbooks/dev-sop.md and followed the Stage A / review checklist. No prior saved rollout memory was needed for this narrow backend slice.

Refs #1686

Implements the next backend-only slice for #1686: a volatile display control lock contract for future native display takeover. ## What changed - Adds `workspace_display_control_locks` with TTL-backed per-workspace ownership state. - Adds admin-gated endpoints: - `GET /workspaces/:id/display/control` - `POST /workspaces/:id/display/control/acquire` - `POST /workspaces/:id/display/control/release` - Uses atomic acquire semantics: active locks can only be renewed by the same attributable actor; otherwise callers receive `409` with the current holder. - Keeps browser session callers observe-only for mutation until middleware exposes a stable per-user/session id. Mutation requires `ADMIN_TOKEN` bearer or org-token actor; force release requires `ADMIN_TOKEN`. - Validates acquire only works for workspaces with display enabled, so non-display instances cannot get control rows. - Emits best-effort `structure_events` audit rows for acquire/release started/completed/failed. ## Comprehensive testing performed - `go test ./internal/handlers -run 'TestWorkspaceDisplayControl'` - `go test ./internal/handlers ./internal/router ./internal/db` - `go test ./...` - `go build ./cmd/server` ## Local-postgres E2E run Ran Stage A with isolated temp services: - Postgres: local `initdb`/`pg_ctl` on port `55432` - Redis: local `redis-server` on port `56379` - Server: `DATABASE_URL=postgres://dev@localhost:55432/molecule?sslmode=disable REDIS_URL=redis://localhost:56379 PORT=18086 BIND_ADDR=127.0.0.1 ADMIN_TOKEN=stage-a-admin-token MOLECULE_ENV=development go run ./cmd/server` Evidence: - Fresh server boot applied `20260523120000_workspace_display_control_locks.up.sql` with all repo migrations. - `\d workspace_display_control_locks` confirmed PK, FK cascade, controller check, controlled_by length check, and expires index. - Endpoint probe on a display-enabled workspace: - `GET /display/control` -> `{"controller":"none"}` - `POST /display/control/acquire` -> `{"controller":"user","controlled_by":"admin-token","expires_at":"..."}` - DB row showed `active = true` - `POST /display/control/release` -> `{"controller":"none"}` - DB row count returned `0` - `structure_events` showed `display.control.acquire.started`, `display.control.acquire.completed`, `display.control.release.started`, and `display.control.release.completed` for the workspace. - Up/down migration pair was run on the populated temp DB; down dropped the table and up recreated it cleanly. Docker was not running locally. The server attempted Docker provisioner auto-detection and orphan-sweeper checks returned Docker connection errors without stopping any containers. ## Staging-smoke verified or pending Pending merge/deploy. This PR adds backend coordination endpoints only; no Canvas takeover UI is wired in this slice. ## Root-cause not symptom The display roadmap needs native computer-level takeover coordination before exposing a live screen/control UI. This adds the coordination primitive without leaking DCV/session details or changing `GET /workspaces/:id/display` availability semantics. ## Five-Axis review walked - Correctness: addressed reviewer findings by blocking coarse session mutation, checking `RowsAffected` on release, validating display-enabled workspace before acquire, and using a separate `none` DTO so zero timestamps are not serialized. - Readability/simplicity: kept implementation in one display-control handler file with explicit DTOs and no Canvas changes. - Architecture: stores volatile lock state outside `workspaces.compute`; existing display status remains backward-compatible. - Security: routes stay under `AdminAuth`; SQL is parameterized; browser sessions cannot mutate until stable identity exists; force release requires actual `ADMIN_TOKEN` bearer. - Performance: one indexed row per workspace; acquire is one atomic upsert plus conflict lookup only on contention. ## No backwards-compat shim / dead code added No compatibility shim. Existing display status endpoint behavior is unchanged. No dead code added. ## Memory/saved-feedback consulted Used the repo SOP from `internal/runbooks/dev-sop.md` and followed the Stage A / review checklist. No prior saved rollout memory was needed for this narrow backend slice. Refs #1686
hongming added 1 commit 2026-05-23 07:44:29 +00:00
Add display control lock endpoints
CI / Canvas Deploy Reminder (pull_request) Blocked by required conditions
Lint shellcheck (arm64 pilot) / shellcheck-arm64 (pilot) (pull_request) Waiting to run
Block internal-flavored paths / Block forbidden paths (pull_request) Successful in 4s
Check migration collisions / Migration version collision check (pull_request) Successful in 16s
CI / Detect changes (pull_request) Successful in 9s
CI / Python Lint & Test (pull_request) Successful in 4s
E2E Chat / detect-changes (pull_request) Successful in 10s
E2E Staging Canvas (Playwright) / detect-changes (pull_request) Successful in 10s
E2E API Smoke Test / detect-changes (pull_request) Successful in 10s
Handlers Postgres Integration / detect-changes (pull_request) Successful in 4s
Harness Replays / detect-changes (pull_request) Successful in 4s
Lint forbidden tenant-env keys / Scan workspace_secrets writers for forbidden env keys (pull_request) Successful in 4s
Lint no tenant GITEA or GITHUB token write / Scan for repo-host token write into tenant workspace surface (pull_request) Successful in 4s
Secret scan / Scan diff for credential-shaped strings (pull_request) Successful in 5s
gate-check-v3 / gate-check (pull_request) Successful in 7s
qa-review / approved (pull_request) Successful in 3s
security-review / approved (pull_request) Successful in 4s
sop-checklist / review-refire (pull_request) Has been skipped
sop-tier-check / tier-check (pull_request) Successful in 4s
lint-required-no-paths / lint-required-no-paths (pull_request) Successful in 1m5s
sop-checklist / all-items-acked (pull_request) acked: 0/7 — missing: comprehensive-testing, local-postgres-e2e, staging-smoke, +4
sop-checklist / na-declarations (pull_request) N/A: (none)
E2E Staging External Runtime / E2E Staging External Runtime (pull_request) Successful in 5m26s
CI / Canvas (Next.js) (pull_request) Successful in 10s
CI / Shellcheck (E2E scripts) (pull_request) Successful in 16s
E2E Chat / E2E Chat (pull_request) Successful in 18s
E2E Staging Canvas (Playwright) / Canvas tabs E2E (pull_request) Successful in 5s
Harness Replays / Harness Replays (pull_request) Successful in 23s
E2E API Smoke Test / E2E API Smoke Test (pull_request) Successful in 1m58s
Handlers Postgres Integration / Handlers Postgres Integration (pull_request) Successful in 2m27s
CI / Platform (Go) (pull_request) Successful in 4m53s
CI / all-required (pull_request) Successful in 15m16s
audit-force-merge / audit (pull_request) Successful in 8s
08ca29fdad
hongming added the securitytier:medium labels 2026-05-23 07:44:57 +00:00
hongming requested review from core-qa 2026-05-23 07:44:58 +00:00
hongming requested review from core-security 2026-05-23 07:44:58 +00:00
core-qa approved these changes 2026-05-23 07:45:38 +00:00
core-qa left a comment
Member

QA review: approved. Local handler/router/db tests and full workspace-server suite are green; Stage A endpoint probe against temp Postgres/Redis verified acquire/release and audit rows.

QA review: approved. Local handler/router/db tests and full workspace-server suite are green; Stage A endpoint probe against temp Postgres/Redis verified acquire/release and audit rows.
core-security approved these changes 2026-05-23 07:45:39 +00:00
core-security left a comment
Member

Security review: approved. New control mutation endpoints stay under AdminAuth; SQL is parameterized; browser sessions are observe-only until stable identity exists; force release requires ADMIN_TOKEN.

Security review: approved. New control mutation endpoints stay under AdminAuth; SQL is parameterized; browser sessions are observe-only until stable identity exists; force release requires ADMIN_TOKEN.
hongming merged commit 665f0a2405 into main 2026-05-23 08:08:08 +00:00
hongming deleted branch feat/1686-display-control-lock 2026-05-23 08:08:08 +00:00
Sign in to join this conversation.
3 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: molecule-ai/molecule-core#1718