Add workspace-lifecycle real-infra staginge2e (core#2332 P1.10) #2338

Merged
claude-ceo-assistant merged 1 commits from core2332-p110-workspace-lifecycle-staginge2e into main 2026-06-06 05:20:55 +00:00
Member

What

Close the workspace-lifecycle coverage gap (core#2332 P1.10): soft-restart / pause / resume / hibernate were only unit-tested (httptest in workspace-server/internal/handlers/*_test.go) and never proven against a real container.

New Go suite workspace-server/internal/staginge2e (//go:build staging_e2e), mirroring the cp internal/staginge2e harness idioms (cp#386): STAGING_E2E=1 gate, CP_ADMIN_API_TOKEN admin surface, provision → wait-online → assert, t.Cleanup teardown. molecule-core has no CP client packages, so the harness is HTTP-only and self-contained (no go.moleculesai.app/controlplane/... imports).

What each transition asserts (observable state, not just HTTP 200)

TestWorkspaceLifecycle_Staging provisions a real throwaway staging tenant + workspace, then:

Endpoint Asserts
restart POST /restart body {"status":"provisioning"}; then live GET /workspaces/:idonline + routable url; post-restart A2A serve probe returns 2xx (container actually came back, not just a row flip)
pause POST /pause → paused; url cleared; A2A no longer serves — the genuinely-stopped signal (a flag-only handler would still be reachable)
resume POST /resume paused → provisioning → online + routable; serveable again
hibernate POST /hibernate?force=true → hibernated (settled, not stuck mid-hibernating); url cleared; unserveable
wake next A2A message auto-wakes hibernated → online (Resume only handles paused); serveable again

Status is read from the live DB-backed GET /workspaces/:id — the lifecycle POST body could lie; the GET proves the row. The restart provisioning window is observed non-fatally (a fast box can race back to online before the first poll); the load-bearing assertions are eventual online+routable + a successful serve probe.

Honest limit (precise TODO left in code)

The strongest "container stopped" signal is the EC2/Docker power-state, which is only observable CP-side (AWS/SSM) and not reachable from the core ws-server module without importing the CP client surface. assertNotServing asserts the strongest signal available here (url cleared + immediate non-serve) with a TODO(core#2332) to tighten to instance power-state if a CP admin endpoint ever surfaces it to the tenant API. Nothing was weakened to make this pass.

CI

New workflow e2e-workspace-lifecycle.yml:

  • PR path = cheap, honest compile+skip gate: go vet -tags staging_e2e + assert the suite SKIPs LOUD without creds. NOT a fake-green mask (a broken test file fails at PR time). Non-required.
  • Real run = workflow_dispatch / schedule (daily 08:00 UTC, offset from e2e-staging-saas 07:00 + peer-visibility 07:30) with CP_BASE_URL + CP_STAGING_ADMIN_API_TOKEN. Teardown via test t.Cleanup; the age-guarded sweep-stale-e2e-orgs (30-min floor, e2e- prefix) is the final net.

Advisory-by-infra: this suite needs a live staging tenant, so it is intentionally NOT a merge-blocking required check. Promote-to-required is a separate CTO decision (mirrors cp#386 and the peer-visibility flip-to-required pattern, molecule-core#1296).

Validation

go vet -tags staging_e2e ./internal/staginge2e/...        # clean
go test -tags staging_e2e ./internal/staginge2e/ \
  -run TestWorkspaceLifecycle -count=1                     # compiles + SKIPs loud (no creds)
gofmt -l ...                                               # clean
go test ./...                                              # package excluded (tag-gated)

No self-merge — routed to agent-reviewer-cr2 + agent-researcher.

🤖 Generated with Claude Code

## What Close the workspace-lifecycle coverage gap (**core#2332 P1.10**): soft-restart / pause / resume / hibernate were only unit-tested (httptest in `workspace-server/internal/handlers/*_test.go`) and never proven against a real container. New Go suite **`workspace-server/internal/staginge2e`** (`//go:build staging_e2e`), mirroring the cp `internal/staginge2e` harness idioms (cp#386): `STAGING_E2E=1` gate, `CP_ADMIN_API_TOKEN` admin surface, provision → wait-online → assert, `t.Cleanup` teardown. molecule-core has no CP client packages, so the harness is **HTTP-only and self-contained** (no `go.moleculesai.app/controlplane/...` imports). ## What each transition asserts (observable state, not just HTTP 200) `TestWorkspaceLifecycle_Staging` provisions a real throwaway staging tenant + workspace, then: | Endpoint | Asserts | |---|---| | **restart** `POST /restart` | body `{"status":"provisioning"}`; then live `GET /workspaces/:id` → `online` + routable `url`; **post-restart A2A serve probe returns 2xx** (container actually came back, not just a row flip) | | **pause** `POST /pause` | `→ paused`; **`url` cleared**; **A2A no longer serves** — the genuinely-stopped signal (a flag-only handler would still be reachable) | | **resume** `POST /resume` | `paused → provisioning → online` + routable; **serveable again** | | **hibernate** `POST /hibernate?force=true` | `→ hibernated` (settled, not stuck mid-`hibernating`); `url` cleared; unserveable | | **wake** | next A2A message auto-wakes `hibernated → online` (Resume only handles `paused`); serveable again | Status is read from the live DB-backed `GET /workspaces/:id` — the lifecycle POST body could lie; the GET proves the row. The restart provisioning window is observed **non-fatally** (a fast box can race back to online before the first poll); the load-bearing assertions are eventual online+routable + a successful serve probe. ### Honest limit (precise TODO left in code) The strongest "container stopped" signal is the EC2/Docker power-state, which is only observable **CP-side** (AWS/SSM) and not reachable from the core ws-server module without importing the CP client surface. `assertNotServing` asserts the strongest signal available here (`url` cleared + immediate non-serve) with a `TODO(core#2332)` to tighten to instance power-state if a CP admin endpoint ever surfaces it to the tenant API. **Nothing was weakened to make this pass.** ## CI New workflow `e2e-workspace-lifecycle.yml`: - **PR path** = cheap, honest **compile+skip** gate: `go vet -tags staging_e2e` + assert the suite SKIPs LOUD without creds. NOT a fake-green mask (a broken test file fails at PR time). **Non-required.** - **Real run** = `workflow_dispatch` / `schedule` (daily 08:00 UTC, offset from e2e-staging-saas 07:00 + peer-visibility 07:30) with `CP_BASE_URL` + `CP_STAGING_ADMIN_API_TOKEN`. Teardown via test `t.Cleanup`; the age-guarded `sweep-stale-e2e-orgs` (30-min floor, `e2e-` prefix) is the final net. **Advisory-by-infra:** this suite needs a live staging tenant, so it is intentionally NOT a merge-blocking required check. **Promote-to-required is a separate CTO decision** (mirrors cp#386 and the peer-visibility flip-to-required pattern, molecule-core#1296). ## Validation ``` go vet -tags staging_e2e ./internal/staginge2e/... # clean go test -tags staging_e2e ./internal/staginge2e/ \ -run TestWorkspaceLifecycle -count=1 # compiles + SKIPs loud (no creds) gofmt -l ... # clean go test ./... # package excluded (tag-gated) ``` No self-merge — routed to agent-reviewer-cr2 + agent-researcher. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
hongming-codex-laptop added 1 commit 2026-06-06 04:47:16 +00:00
Add workspace-lifecycle real-infra staginge2e (core#2332 P1.10)
ci-arm64-advisory / fast-checks (pull_request) Waiting to run
Lint shellcheck (arm64 pilot) / shellcheck-arm64 (pilot) (pull_request) Successful in 2s
Block internal-flavored paths / Block forbidden paths (pull_request) Successful in 8s
CI / Python Lint & Test (pull_request) Successful in 10s
E2E Workspace Lifecycle (staginge2e) / E2E Workspace Lifecycle (staging) (pull_request) Has been skipped
CI / Detect changes (pull_request) Successful in 15s
E2E API Smoke Test / detect-changes (pull_request) Successful in 13s
E2E Staging Canvas (Playwright) / detect-changes (pull_request) Successful in 14s
Handlers Postgres Integration / detect-changes (pull_request) Successful in 13s
E2E Chat / detect-changes (pull_request) Successful in 21s
Harness Replays / detect-changes (pull_request) Successful in 12s
Lint curl status-code capture / Scan workflows for curl status-capture pollution (pull_request) Successful in 7s
Lint forbidden tenant-env keys / Scan workspace_secrets writers for forbidden env keys (pull_request) Successful in 6s
Lint forbidden tenant-env keys / Scan for repo-host token write into tenant workspace surface (pull_request) Successful in 11s
E2E Workspace Lifecycle (staginge2e) / E2E Workspace Lifecycle (compile+skip) (pull_request) Successful in 27s
Secret scan / Scan diff for credential-shaped strings (pull_request) Successful in 7s
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)
security-review / approved (pull_request_target) Failing after 8s
sop-checklist / all-items-acked (pull_request_target) Successful in 7s
sop-checklist / review-refire (pull_request_target) Has been skipped
gate-check-v3 / gate-check (pull_request_target) Successful in 19s
lint-required-workflows-docker-host-pinned / Lint docker-host pin on docker-touching workflows (pull_request) Successful in 21s
sop-tier-check / tier-check (pull_request_target) Failing after 9s
qa-review / approved (pull_request_target) Failing after 31s
lint-continue-on-error-tracking / lint-continue-on-error-tracking (pull_request) Successful in 1m18s
Lint pre-flip continue-on-error / Verify continue-on-error flips have run-log proof (pull_request) Successful in 1m10s
Lint workflow YAML (Gitea-1.22.6-hostile shapes) / Lint workflow YAML for Gitea-1.22.6-hostile shapes (pull_request) Successful in 1m13s
lint-required-no-paths / lint-required-no-paths (pull_request) Successful in 1m26s
lint-required-context-exists-in-bp / lint-required-context-exists-in-bp (pull_request) Failing after 1m35s
CI / Shellcheck (E2E scripts) (pull_request) Successful in 2s
CI / Canvas (Next.js) (pull_request) Successful in 8s
E2E Staging Canvas (Playwright) / Canvas tabs E2E (pull_request) Successful in 2s
Harness Replays / Harness Replays (pull_request) Successful in 2s
Handlers Postgres Integration / Handlers Postgres Integration (pull_request) Successful in 11s
E2E Chat / E2E Chat (pull_request) Successful in 10s
CI / Canvas Deploy Status (pull_request) Has been skipped
E2E API Smoke Test / E2E API Smoke Test (pull_request) Successful in 1m4s
CI / Platform (Go) (pull_request) Successful in 4m16s
CI / all-required (pull_request) Successful in 5s
audit-force-merge / audit (pull_request_target) Successful in 6s
7e313d1c77
Close the workspace-lifecycle coverage gap: soft-restart / pause / resume
/ hibernate were only unit-tested (httptest in
workspace-server/internal/handlers/*_test.go) and never proven against a
real container.

New Go suite workspace-server/internal/staginge2e (build tag
//go:build staging_e2e), mirroring the cp internal/staginge2e idioms
(cp#386): STAGING_E2E=1 gate, CP_ADMIN_API_TOKEN admin surface,
provision -> wait-online -> assert, t.Cleanup teardown. Core has no CP
client packages, so the harness is HTTP-only and self-contained.

TestWorkspaceLifecycle_Staging provisions a real throwaway staging tenant
+ workspace, then drives each lifecycle endpoint and asserts OBSERVABLE
state (not just HTTP 200):

- restart  -> body provisioning, then GET status -> online+routable, and a
             post-restart A2A serve probe succeeds (container actually back).
- pause    -> status paused + url cleared + workspace no longer serves A2A
             (the genuinely-stopped signal: a flag-only handler would still
             serve). resume -> online + serveable again.
- hibernate-> status hibernated + url cleared + unserveable; wake via the
             next A2A message -> online + serveable (auto-wake-on-message;
             Resume only handles paused).

Status is read from the live DB-backed GET /workspaces/:id (the lifecycle
POST body could lie; the GET proves the row). The restart provisioning
window is observed non-fatally (a fast box can race back to online before
the first poll) — the load-bearing assertions are eventual online+routable
and a successful serve probe.

The strongest "container stopped" signal is EC2/Docker power-state, only
observable CP-side (AWS/SSM) and not reachable from the core ws-server
module; assertNotServing asserts the strongest signal available here
(url cleared + immediate non-serve) with a precise TODO(core#2332).

Advisory-by-infra: the real run needs a live staging tenant, so the new
workflow e2e-workspace-lifecycle.yml runs it on workflow_dispatch /
schedule only (daily 08:00 UTC, offset from the other staging e2es). The
PR path is a cheap honest compile+skip gate (vet under the tag + assert it
SKIPs LOUD without creds) — NOT required. Promote-to-required is a
separate CTO decision (mirrors cp#386 / the peer-visibility flip pattern,
molecule-core#1296).

Validation: go vet -tags staging_e2e ./internal/staginge2e/... (clean);
go test -tags staging_e2e ./internal/staginge2e/ -run TestWorkspaceLifecycle
-count=1 compiles and SKIPs loud without creds; gofmt clean; default
`go test ./...` excludes the package (tag-gated).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
hongming-codex-laptop requested review from agent-reviewer-cr2 2026-06-06 04:47:53 +00:00
hongming-codex-laptop requested review from agent-researcher 2026-06-06 04:47:54 +00:00
claude-ceo-assistant merged commit a85d4c8f89 into main 2026-06-06 05:20:55 +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#2338