molecule-core/canvas
Hongming Wang b375252dc8 feat(external): credential rotation + re-show instruction modal (#319)
External workspaces (runtime=external) lose their workspace_auth_token
the moment the create modal closes — the token is unrecoverable from
any later DB read. Operators who lost their copy or want to respond to
a suspected leak had no recovery path short of recreating the workspace
(which also breaks cross-workspace delegation links + memory namespace).

This PR adds two endpoints + a Config-tab section that surfaces them:

  POST /workspaces/:id/external/rotate
    Revokes any prior live tokens, mints a fresh one, returns the same
    ExternalConnectionInfo payload Create returns. Old credentials stop
    working immediately — the previously-paired agent will fail auth on
    its next heartbeat (~20s).

  GET /workspaces/:id/external/connection
    Returns the connect block with auth_token="". For the operator who
    just needs to re-find PLATFORM_URL / WORKSPACE_ID / one of the
    snippets without invalidating the live agent.

Both reject runtime ≠ external with 400 + a hint pointing at /restart
for non-external runtimes (which mints AND injects into the container).

## Why a flag isn't needed

The endpoints are purely additive — Create's behavior is unchanged.
Existing external workspaces don't see anything different until an
operator clicks the new buttons.

## DRY refactor

Extracted BuildExternalConnectionPayload() in external_connection.go
as the single source of truth for the connect payload shape. Create,
Rotate, and GetExternalConnection all call it. Adds a snippet once →
all three endpoints emit it. Trims trailing slash on platform_url so
no double-slash sneaks into registry_endpoint.

## Canvas

ExternalConnectionSection mounts in ConfigTab when runtime=external.
Two buttons:
  - "Show connection info" (cosmetic) — fetches GET /external/connection
  - "Rotate credentials" (destructive) — confirm dialog explains the
    impact, then POST /external/rotate

Both reuse the existing ExternalConnectModal so operators don't learn
a second snippet UX.

## Coverage

10 Go tests:
  - Rotate happy path (revoke + mint order, payload shape, broadcast event)
  - Rotate refuses non-external runtimes (400 with restart hint)
  - Rotate 404 on unknown workspace + 400 on empty id
  - GetExternalConnection happy path (auth_token="", same payload shape)
  - GetExternalConnection refuses non-external + 404 on unknown
  - BuildExternalConnectionPayload — placeholder substitution + trailing
    slash trimming + blank-token contract

6 canvas tests:
  - both action buttons render
  - "Show" calls GET /external/connection and opens modal
  - "Rotate" opens confirm dialog before firing POST
  - Cancel dismisses without rotating
  - Confirm POSTs and opens modal with returned token
  - API failures surface as visible error chips

Migration: existing external workspaces gain new abilities; no data
migration. The DRY refactor preserves byte-identical Create response
shape (8 ConfigTab tests + all existing handler tests still pass).

Closes #319.
2026-05-05 01:55:27 -07:00
..
e2e canvas/e2e: surface admin-orgs row + workspace body on failure 2026-05-03 14:01:50 -07:00
public chore: replace brand icon and add HANDOFF.md 2026-04-13 13:03:40 -07:00
src feat(external): credential rotation + re-show instruction modal (#319) 2026-05-05 01:55:27 -07:00
.env.example fix(canvas): close 4 gaps in WS status indicator (env, toast, tests) 2026-04-14 08:26:38 +00:00
.gitignore feat(canvas): SaaS cross-origin — slug header + cookie credentials (Phase F) 2026-04-14 20:08:39 -07:00
components.json fix(canvas): restore text-white on saturated buttons + close zinc gaps 2026-05-03 02:04:20 -07:00
Dockerfile chore(canvas): upgrade node:20-alpine → node:22-alpine 2026-04-24 18:54:30 +00:00
next.config.ts fix(canvas,dotenv): review-driven hardening of fit gate + parser parity 2026-04-24 22:23:51 -07:00
package-lock.json fix(canvas): regenerate lockfile with cross-platform optional deps 2026-05-03 01:52:42 -07:00
package.json feat(canvas): warm-paper theme + Tailwind v4 migration 2026-05-03 01:43:55 -07:00
playwright.config.ts initial commit — Molecule AI platform 2026-04-13 11:55:37 -07:00
playwright.staging.config.ts feat(e2e): canary + canvas Playwright workflows; delegation mechanics 2026-04-21 04:15:10 -07:00
postcss.config.js feat(canvas): warm-paper theme + Tailwind v4 migration 2026-05-03 01:43:55 -07:00
tsconfig.json initial commit — Molecule AI platform 2026-04-13 11:55:37 -07:00
vitest.config.ts feat(canvas): vitest coverage instrumentation (#1815, no CI gate yet) 2026-04-26 23:44:07 -07:00