feat(tests): add production-shape local harness (Phase 1) - #2401
Conversation
The harness brings up the SaaS tenant topology on localhost using the SAME workspace-server/Dockerfile.tenant image that ships to production. Tests run against http://harness-tenant.localhost:8080 and exercise the same code path a real tenant takes: client → cf-proxy (nginx; CF tunnel + LB header rewrites) → tenant (Dockerfile.tenant — combined platform + canvas) → cp-stub (minimal Go CP stand-in for /cp/* paths) → postgres + redis Why this exists: bugs that survive `go run ./cmd/server` and ship to prod almost always live in env-gated middleware (TenantGuard, /cp/* proxy, canvas proxy), header rewrites, or the strict-auth / live-token mode. The harness activates ALL of them locally so #2395 + #2397-class bugs can be reproduced before deploy. Phase 1 surface: - cp-stub/main.go: minimal CP stand-in. /cp/auth/me, redeploy-fleet, /__stub/{peers,mode,state} for replay scripts. Catch-all returns 501 with a clear message when a new CP route appears. - cf-proxy/nginx.conf: rewrites Host to <slug>.localhost, injects X-Forwarded-*, disables buffering to mirror CF tunnel streaming semantics. - compose.yml: one service per topology layer; tenant builds from the actual production Dockerfile.tenant. - up.sh / down.sh / seed.sh: lifecycle scripts. - replays/peer-discovery-404.sh: reproduces #2397 + asserts the diagnostic helper from PR #2399 surfaces "404" + "registered". - replays/buildinfo-stale-image.sh: reproduces #2395 + asserts /buildinfo wire shape + GIT_SHA injection from PR #2398. - README.md: topology, quickstart, what the harness does NOT cover. Phases 2-3 (separate PRs): - Phase 2: convert tests/e2e/test_api.sh to target the harness URL instead of localhost; make harness-based replays a required CI gate. - Phase 3: config-coherence lint that diffs harness env list against production CP's env list, fails CI on drift. Verification: - cp-stub builds (go build ./...). - cp-stub responds to all stubbed endpoints (smoke-tested locally). - compose.yml passes `docker compose config --quiet`. - All shell scripts pass `bash -n` syntax check. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
HongmingWang-Rabbit
left a comment
There was a problem hiding this comment.
Five-axis pass. Substantive infra addition (production-shape harness, 11 new files, +772/-0) → comment-only per loop policy.
Correctness. The harness wires up the exact prod topology with minimum-viable stand-ins:
tenantbuilds fromworkspace-server/Dockerfile.tenant— same image CI publishes, notgo run ./cmd/server. This is the load-bearing claim and it lands cleanly via build-context../..+args: GIT_SHA.cp-stub's catch-all returns 501 with an actionable hint when a new/cp/*path appears — drift surfaces fast instead of silently 404'ing the test.cf-proxynginx mirrors CF tunnel semantics (Hostrewrite,X-Forwarded-*injection, buffering disabled for streaming).__stub/{peers,mode,state}toggles let replay scripts seed scenarios deterministically;atomic.Valuestorage keeps the stub race-free under concurrent test traffic.- Replay scripts each cite a specific shipped bug (#2395, #2397) — the regression bar is "this shipped to prod despite green local E2E."
Readability. Top-of-file docstrings explain why this exists, not just what. The cp-stub/main.go header makes the role explicit ("plays that role on localhost so we can exercise the SAME code path"); compose.yml calls out the load-bearing image choice; replays are named by bug class. README is provided. Each service comment is tied to a concrete production behavior (TenantGuard activation, strict-auth mode, CF tunnel header rewrite).
Architecture. Right scope split:
- Phase 1 (this PR) — bring up the topology + two replay scripts
- Phase 2 — convert
tests/e2e/test_api.shto target the harness URL + make replays a required CI gate - Phase 3 — config-coherence lint that diffs harness env vs prod CP env
Aligns with the harness-pair pattern from RFC #2251 (same-named scripts on each side of the OSS/SaaS boundary, each owning the surface it tests). Mirrors feedback_chase_verification_to_staging discipline at the local-CI layer: the gap that lets bugs ship is exactly the gap this closes.
Security.
ADMIN_TOKEN: "harness-admin-token"— hardcoded local test token, not a real credential. No leaked-secret regex match (sk-, gh_, AKIA, sk-ant-, sk-cp- — none).POSTGRES_PASSWORD: harness— local docker-compose convention, no real DB exposed.cp-stubis bound to theharness-netdocker network only — nothing exposed beyond compose.- No production code modified.
- No
.github/workflows/*changes (harness will become a CI gate in Phase 2 — separate PR). - No deleted tests; this is pure test infrastructure addition.
Performance. Test infra only; no prod hot-path impact. Healthcheck intervals 2s with 10-retry budget is reasonable for compose boot ordering. cp-stub's atomic.Int64 for redeployFleetCalls and atomic.Value for state are cheap.
One nit, not blocking: CP_STUB_PEERS_MODE: "timeout" documented as "hang for 60s" — make sure the replay's per-call timeout is long enough to actually hit the 60s vs. just getting cut off by a default httpx 10s timeout (the actual scenario from a2a_client.py uses timeout=10.0). The diagnostic helper from #2399 will return network-exception with that timeout, which is the correct branch for the replay assertion — so this is fine, but worth confirming the replay docs match.
LGTM. CI is minimal (this is pure test infra) — once green, mergeable.
Three findings from re-reviewing PR #2401 with fresh eyes: 1. Critical — port binding to 0.0.0.0 compose.yml's cf-proxy bound 8080:8080 (default 0.0.0.0). The harness uses a hardcoded ADMIN_TOKEN so anyone on the local network or VPN could hit /workspaces with admin privileges. Switch to 127.0.0.1:8080 so admin access is loopback-only — safe for E2E and prevents the known-token leak. 2. Required — dead code in cp-stub peersFailureMode + __stub/mode + __stub/peers were declared with atomic.Value setters but no handler ever READ from them. CP doesn't host /registry/peers (the tenant does), so the toggles couldn't drive responses. Removed the dead vars + handlers; kept redeployFleetCalls counter and __stub/state since those have a real consumer in the buildinfo replay. 3. Required — replay's auth-context dependency peer-discovery-404.sh's Python eval ran a2a_client.get_peers_with_ diagnostic() against the live tenant. Without a workspace token file, auth_headers() yields empty headers — so the helper might exercise a 401 branch instead of the 404 branch the replay claims to test. Split the assertion into (a) WIRE — direct curl proves the platform returns 404 from /registry/<unregistered>/peers — and (b) PARSE — feed the helper a mocked 404 via httpx patches, no network/auth. Each branch tests exactly what it claims. Also added a graceful skip when the workspace runtime in the current checkout pre-dates #2399 (no get_peers_with_diagnostic yet) — replay falls back to wire-only verification with a clear message instead of an opaque AttributeError. After #2399 lands on staging, both branches will run. cp-stub still builds clean. compose.yml validates. Replay's bash syntax + Python eval both verified locally. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
HongmingWang-Rabbit
left a comment
There was a problem hiding this comment.
Five-axis pass with two findings — one Critical, several Optional.
Critical: harness won't boot as-written
compose.yml sets MOLECULE_ENV: "production" but doesn't set SECRETS_ENCRYPTION_KEY. Tracing the boot path:
crypto.InitStrict()—aes.go:114-118:isProdEnv()returnstruefor"prod"or"production".aes.go:76-80: whenisProdEnv() && !IsEnabled(), returnsErrEncryptionKeyMissingwith message"SECRETS_ENCRYPTION_KEY is required in production...".cmd/server/main.go:60-62:log.Fatalf("Secrets encryption: %v", err)→ process exits non-zero → container exits →up.sh --waitblocks until the healthcheck retries exhaust.
The entrypoint-tenant.sh doesn't paper over this (verified — no SECRETS_ENCRYPTION_KEY handling there). The container has no baked-in .env (Dockerfile doesn't COPY one and .env is gitignored).
This squares with the PR's verification list — the four checked items are syntax/build only (go build, docker compose config --quiet, bash -n); the four Test-Plan boxes that actually exercise up.sh are unchecked. The harness as written would fail at up.sh --wait.
Two ways to fix:
- (a) Add
SECRETS_ENCRYPTION_KEYtocompose.ymlenv. Use a non-secret-shaped fixed value (e.g. a known-test 32-byte hex like0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef) and document in the README why it's safe (loopback-only bind + no real secrets ever encrypted with it). This preserves the prod-shape aspiration. - (b) Set
MOLECULE_ENV: "harness"instead of"production". TripsisProdEnv() == false, the strict gate is bypassed, encryption disabled with a warning. Caveat: this is the least-prod-shape choice; per the PR's own thesis ("localgo runskips ... strict-auth mode"), bypassing the strict path defeats some of the harness's purpose — butADMIN_TOKENstrict-auth is independent of encryption gating, so the canvas-bootstrap / TenantGuard / CP-proxy tests would still fire.
I'd lean (a) — it keeps the harness behaving identically to prod for everything except the encrypted-secrets surface (which the replays don't currently exercise anyway). Add a follow-up replay if/when secrets-encrypted code paths need coverage.
Other findings (non-blocking)
Optional / Consider — env var name inconsistency in the buildinfo replay:
compose.yml:67reads${GIT_SHA:-harness}replays/buildinfo-stale-image.sh:36reads${HARNESS_GIT_SHA:-harness}- These should be the same name. The current shape produces a false-positive WARN ("Image may be cached...") whenever an operator overrides
GIT_SHAbecause the replay is reading a different env var.
Optional — seed.sh creates ALPHA_ID + BETA_ID but neither replay actually consumes them. peer-discovery-404.sh uses a fresh ROGUE_ID for the unregistered case. Either drop the seed.sh invocation from the replay, or add a success-path replay that exercises tool_list_peers against the seeded pair.
Optional — seed.sh:37 POSTs \"id\":\"$ALPHA_ID\" to /workspaces. The platform's typical create handler generates the ID server-side; if it ignores the client value, .seed.env would persist a bogus ID. Worth a one-line check: grep "id.*=.*payload" workspace_crud.go to confirm the field is honored.
FYI — cp-stub/ has its own go.mod. Top-level go test ./... won't catch syntax errors in cp-stub. The Dockerfile build will, but only when compose runs. Phase 2 should add a make ci-harness target so cp-stub stays buildable.
FYI — the loopback-bind comment in compose.yml:115-119 is right next to the port, which is the best location for it. A future contributor changing 127.0.0.1:8080:8080 to 8080:8080 would silently expose harness-admin-token to the LAN/VPN. Optional follow-up: add a one-line shell test in CI that greps compose.yml for ^[^#]*ports: blocks and asserts 127.0.0.1: prefix on the admin-bound services.
Other axes
Readability ✓ — README + per-file commentary are exemplary; every WHY is captured. Replay scripts have step-numbered phases.
Architecture ✓ — clean module boundaries (cp-stub isolated go.mod, nginx config in its own dir, service-per-layer compose). Reuse of the actual Dockerfile.tenant is the right call.
Security ✓ — hardcoded admin token is intentional and bound to loopback. No real-data exposure surface.
Performance ✓ — no concerns; healthcheck cadence reasonable.
Verdict
Request changes — the Critical boot bug needs to land before this is useful. Once that's fixed and you've actually run the four Test-Plan steps end-to-end (so the boxes turn checked), this is a strong addition to the test infrastructure.
…tenant boots Found via the first run of the harness-replays-required-check workflow (#2410): the tenant container failed its healthcheck after 100s with "refusing to boot without encryption in production". This is the deferred CRITICAL flagged on PR #2401 — `crypto.InitStrict()` requires SECRETS_ENCRYPTION_KEY when MOLECULE_ENV=production, and the harness sets prod-mode but never seeded a key. Fix: add a clearly-test 32-byte base64 value (encoding the literal string "harness-test-only-not-for-prod!!") inline. Keeping MOLECULE_ENV=production preserves the harness's value as a production- shape replay surface — it now exercises the full encryption boot path including the strict check, rather than skirting it via dev-mode. Why inline rather than .env: - The harness compose file is meant to be self-contained and reproducible from a clean clone. An external .env would split the config across two files for one synthetic value. - The value is intentionally a sentinel; there's no operator decision here to gate behind a per-deployment file. After this lands the harness boots clean and `run-all-replays.sh` can exercise the buildinfo + peer-discovery replays as designed. The required-check workflow itself (#2410) needs no change.
Summary
Phase 1 of the production-shape local harness — addresses the topology gap that lets bugs like #2395 (silent stale image) and #2397 (silent peer-discovery failure) ship to production despite green local E2E.
The harness brings up the SaaS tenant topology on localhost using the SAME
workspace-server/Dockerfile.tenantimage that ships to production:Tests run against
http://harness-tenant.localhost:8080. They exercise:TenantGuardmiddleware (MOLECULE_ORG_IDset)/cp/*reverse proxy (CP_UPSTREAM_URL=http://cp-stub:9090)CANVAS_PROXY_URLfrom entrypoint-tenant.sh)ADMIN_TOKEN)Hostrewrite +X-Forwarded-*injectionAll env-gated middleware and request-shape rewrites that local
go runskips.How
cp-stub/main.go: minimal CP stand-in. Serves/cp/auth/me,/cp/admin/tenants/redeploy-fleet, plus/__stub/{peers,mode,state}toggles for replay scripts. Catch-all/cp/*returns 501 with a clear hint when a new CP route appears so drift surfaces fast.cf-proxy/nginx.conf: rewritesHostto<slug>.localhost, injectsX-Forwarded-*, disables buffering to mirror CF tunnel streaming semantics.compose.yml: one service per topology layer; tenant builds fromworkspace-server/Dockerfile.tenant.up.sh / down.sh / seed.sh: lifecycle scripts.replays/peer-discovery-404.sh: reproduces list_peers + get_peers swallow non-200 + exceptions, leaving agent + user blind to root cause #2397 + asserts the diagnostic helper from PR feat(workspace): surface peer-discovery failure reason instead of "may be isolated" #2399 surfaces "404" + "registered".replays/buildinfo-stale-image.sh: reproduces redeploy-fleet + tenant provision serve stale workspace-server images even with fresh :latest #2395 + asserts/buildinfowire shape and GIT_SHA injection from PR feat(deploy): verify each tenant /buildinfo matches published SHA after redeploy #2398.Bar for adding replays
Each replay script reproduces a real bug class against the harness so fixes can be verified locally before deploy. The bar is "this bug shipped to production despite local E2E being green" — the script becomes the regression gate that closes the gap.
Verification
cp-stubbuilds (go build ./...).cp-stubresponds to all stubbed endpoints (smoke-tested locally — see commit message).compose.ymlpassesdocker compose config --quiet.bash -nsyntax check.Out of scope (Phase 2-3, separate PRs)
tests/e2e/test_api.shto target the harness URL instead of localhost. Make harness-based replays a required CI gate.Test plan
cd tests/harness && ./up.sh && ./seed.sh— bring up harness, seed sample workspaces../replays/buildinfo-stale-image.sh— proves /buildinfo shape works post feat(deploy): verify each tenant /buildinfo matches published SHA after redeploy #2398../replays/peer-discovery-404.sh— proves diagnostic surfacing works post feat(workspace): surface peer-discovery failure reason instead of "may be isolated" #2399../down.sh— clean up.🤖 Generated with Claude Code