Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/e2e-contract-cli.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@truefoundry/trueforge': patch
---

Add a local end-to-end test command (`pnpm test:e2e`) that starts its own stack, runs session checks, and tears everything down.
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
- CI package path filters, matrix package ids, and root scripts `test:*` in `.github/workflows/ci.yml` and root `package.json` MUST stay synchronized when a workspace package is added, renamed, or moved; the `store` filter sync rule lives in `packages/trueforge-core/src/agent-session/store/AGENTS.md`.
- Release wiring MUST keep dist-free host development, `pnpm smoke`, and packed CJS/ESM consumers of `@truefoundry/trueforge-core` working without changes.
- PRs that change published-package code (`packages/trueforge-core`, `packages/trueforge`, `packages/trueforge-ui`, `packages/trueforge-sdk`) or `packages/frontend` (ships inside `@truefoundry/trueforge`) MUST include a new `.changeset/*.md` file (`pnpm changeset`). Docs, CI/workflows, charts, and docker-compose changes do not. SDK regeneration already adds `@truefoundry/trueforge-sdk` via `pnpm changeset:sdk-regen`.
- Shared Postgres/Redis settings in `docker-compose.yml` and `docker-compose.dev.yml` (image versions, health checks, `env_file`) MUST stay synchronized; intentional differences (app services, data paths, project `name`, host ports, in-network `POSTGRES_HOST` / `REDIS_URL`) MUST stay explicit. `packages/trueforge/.env` is the host-dev + secrets source; `docker-compose.yml` may read it but MUST override container connectivity so host-dev localhost values are not used inside the smoke-test stack.
- Shared Postgres/Redis settings in `docker-compose.yml`, `docker-compose.dev.yml`, and `docker-compose.e2e.yml` (image versions, health checks) MUST stay synchronized; intentional differences (app services, data paths, project `name`, host ports, `env_file`, in-network `POSTGRES_HOST` / `REDIS_URL`) MUST stay explicit. Smoke and E2E server services MUST set `HOST: 0.0.0.0` so published ports and in-container `/healthz` probes reach the process. `packages/trueforge/.env` is the host-dev + secrets source; `packages/trueforge/e2e/.env` is the E2E stack + CLI source. Compose files MUST override container connectivity so host-dev localhost values are not used inside smoke or E2E stacks.
Comment thread
cursor[bot] marked this conversation as resolved.
- Changes to types or schemas MUST keep `packages/trueforge-core`, `packages/frontend`, `packages/trueforge`, and `patches` synchronized; they MUST NOT update only one affected layer.
- TypeScript code MUST NOT use assertion escapes such as `as T`, `as unknown as T`, non-null `!`, or `as never` to silence type errors; implementations MUST use sound contracts, guards, or corrected types.
- When catching an error and throwing another, the new error MUST set `{ cause: caught }` so the original failure is preserved for logs and debugging.
Expand Down
90 changes: 90 additions & 0 deletions docker-compose.e2e.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# Full-stack E2E contract. Same topology as docker-compose.yml (smoke); isolated
# project name, host ports, and env_file so it does not collide with host-dev
# (8790/5432/6379) or smoke (8791/5433/6380). Postgres has no host bind mount so
# `scripts/e2e.sh` `down -v` discards the DB. Keep image versions and healthchecks
# in sync with docker-compose.yml / docker-compose.dev.yml.
name: trueforge-e2e

services:
postgres:
image: postgres:17
env_file:
- path: packages/trueforge/e2e/.env
required: true
# Host 5434 avoids conflict with docker-compose.dev.yml on 5432 and smoke on 5433.
ports:
- '5434:5432'
healthcheck:
test: ['CMD-SHELL', 'pg_isready -U "$$POSTGRES_USER" -d "$$POSTGRES_DB"']
interval: 5s
timeout: 5s
retries: 10
restart: unless-stopped

# Shared by all server replicas: carries executor peering (cross-replica
# turn cancel via request-reply).
redis:
image: redis:7-alpine
# Host 6381 avoids conflict with docker-compose.dev.yml on 6379 and smoke on 6380.
ports:
- '6381:6379'
healthcheck:
test: ['CMD', 'redis-cli', 'ping']
interval: 5s
timeout: 3s
retries: 5
restart: unless-stopped

# Serves both the API and the UI on one port.
server:
build:
context: .
# From-source image; root Dockerfile is the npm-install prod recipe.
dockerfile: Dockerfile.dev
image: truefoundry-server:latest
ports:
# Host 8792 avoids conflict with host `pnpm dev` on 8790 and smoke on 8791.
- '8792:8790'
environment:
# Wins over env_file / image defaults so a host .env cannot flip this.
NODE_ENV: production
# Inside the container; host mapping above is 8792.
PORT: 8790
# Wins over env_file / Node default `localhost` (::1 vs healthcheck 127.0.0.1).
HOST: 0.0.0.0
# Host-facing origin for MCP OAuth callbacks (mapped port, not container PORT).
PUBLIC_BASE_URL: http://localhost:8792
# Compose network hosts — do not use packages/trueforge/e2e/.env localhost values.
POSTGRES_HOST: postgres
POSTGRES_PORT: 5432
# Compose runs one replica but keeps peering on so the peered cancel path
# stays exercised. STANDALONE=false ⇒ Postgres + Redis.
STANDALONE: 'false'
REDIS_URL: redis://redis:6379
# Postgres credentials (POSTGRES_USER, …) and later E2E secrets.
# `environment` above overrides any colliding keys from this file.
env_file:
- path: packages/trueforge/e2e/.env
required: true
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
# Probes GET /healthz (plain text OK!); node is already in the image.
healthcheck:
test:
[
'CMD',
'node',
'-e',
"fetch('http://127.0.0.1:8790/healthz').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))",
]
interval: 5s
timeout: 5s
retries: 10
start_period: 15s
# Above the app's default GRACEFUL_TIMEOUT_SECONDS (30) so SIGKILL does not
# cut off turn drain.
stop_grace_period: 35s
restart: unless-stopped
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
"test:frontend": "pnpm --filter frontend test",
"test:trueforge-core": "pnpm --filter @truefoundry/trueforge-core test",
"test:trueforge": "pnpm --filter @truefoundry/trueforge test",
"test:e2e": "bash scripts/e2e.sh",
"test:local-sandbox:contract": "pnpm --filter @truefoundry/trueforge test:local-sandbox:contract",
"smoke:local-sandbox": "pnpm --filter @truefoundry/trueforge smoke:local-sandbox",
"smoke:local-sandbox:lima": "pnpm --filter @truefoundry/trueforge smoke:local-sandbox:lima",
Expand Down
17 changes: 17 additions & 0 deletions packages/trueforge/e2e/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Copy to `.env` (git-ignored). Used by docker-compose.e2e.yml (postgres + server)
# and by the E2E CLI. Do not point this file at host-dev ports.
#
# Compose maps the API to http://127.0.0.1:8792 (not 8790 / 8791).

# --- Postgres (required for the e2e compose postgres service) ---
POSTGRES_USER=trueforge
POSTGRES_PASSWORD=trueforge
POSTGRES_DB=trueforge

# --- CLI (model + Daytona) ---
# Model FQN `provider/model`, e.g. openai/gpt-4.1-mini
MODEL=openai/gpt-4.1-mini
MODEL_API_KEY=
DAYTONA_API_KEY=
# Per-turn wait ceiling in milliseconds (default 180000 when unset).
TEST_TIMEOUT_MS=180000
Loading