You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The Factory needs to create and destroy isolated environments on demand to verify features. Follow the repo's existing port + provider seam (src/ports/fleet.ts with RelayFleetClient/InternalFleetClient implementations) so the environment substrate is swappable and testable. The primary provider is Cloudflare (the org's substrate — see #146); K8s/Daytona are optional providers behind the same port for stacks that can't run on Cloudflare.
What
Add an EnvironmentProvider port (src/ports/environment.ts) and a Cloudflare implementation (src/environments/cloudflare-provider.ts):
status(id) — readiness of the namespace's Workers/Containers.
endpoints(id) — resolve the environment's reachable URLs (Worker preview URL / Container ingress) so callers (E2E driver, load harness) can hit them.
destroy(id) — delete the dispatch namespace + Container instances; idempotent.
TTL reaper — mirror src/orchestrator/reaper.ts (identity-checked) to delete environments past TTL or whose owning run died, so namespaces/Containers can't leak.
Config schema entry (mirror src/config/schema.ts) for the Cloudflare account/API-token (read via a Resource.*-style secret, not process.env), default limits, TTL.
Keep the Cloudflare API/wrangler dependency isolated behind the provider (the port stays substrate-agnostic). Leave a seam for an optional KubernetesEnvironmentProvider / DaytonaEnvironmentProvider implementing the same port later.
Export from src/index.ts.
Acceptance
EnvironmentProvider port with a Cloudflare provider; unit tests with a faked Cloudflare API/miniflare.
Provision creates a dispatch namespace + per-env bindings; destroy is idempotent (namespace + Containers gone).
Reaper deletes past-TTL / orphaned environments.
Exported from src/index.ts; config schema covers account/token + limits + TTL; port leaves room for K8s/Daytona providers.
"Compiles" is not done. Run the provider against a REAL Cloudflare dispatch namespace and observe an environment come up, be reachable, and get reaped. CI uses miniflare for the fast path and a real dispatch namespace for the E2E.
Run it
npm run build
npm test# unit (faked Cloudflare API / miniflare)# E2E against a real dispatch namespace (creds from factory-test-infra)
CLOUDFLARE_API_TOKEN=... npx tsx test/e2e/environment-provider.e2e.ts
The E2E must (against real Cloudflare):
provision() → assert the dispatch namespace exists, per-env bindings are set, and status() reports ready.
Deploy a trivial Worker (or Container) into it and assert endpoints() resolves to a reachable URL (curl returns 200).
destroy() → assert the namespace + Container instances are gone.
Provision with a short TTL, run the reaper, assert the environment is deleted without an explicit destroy (proves leaks are cleaned).
Deliverable
test/e2e/environment-provider.e2e.ts running provision→reachable→destroy→reap against a real dispatch namespace in CI, one command.
Fails if provisioning, endpoint resolution, or reaping is broken (no false green).
Anchor files
src/ports/fleet.ts (port pattern) → new src/ports/environment.ts
src/fleet/relay-fleet-client.ts (provider-impl pattern) → new src/environments/cloudflare-provider.ts (+ later optional kubernetes-provider.ts / daytona-provider.ts)
Part of Epic: Factory Verification Environments (#141). This is the core provisioning primitive.
Why
The Factory needs to create and destroy isolated environments on demand to verify features. Follow the repo's existing port + provider seam (
src/ports/fleet.tswithRelayFleetClient/InternalFleetClientimplementations) so the environment substrate is swappable and testable. The primary provider is Cloudflare (the org's substrate — see #146); K8s/Daytona are optional providers behind the same port for stacks that can't run on Cloudflare.What
Add an
EnvironmentProviderport (src/ports/environment.ts) and a Cloudflare implementation (src/environments/cloudflare-provider.ts):provision(spec): Promise<Environment>— create a Workers for Platforms dispatch namespace for the environment, wire per-env bindings/secrets, and (per the stack descriptor Verification-stack descriptor contract + deployer: repos declare their full stack, Factory brings it up #143) deploy the environment's Workers and Cloudflare Containers into it.Environmentcarries{ id, dispatchNamespace, endpoints, bindings, status, createdAt, ttl }.status(id)— readiness of the namespace's Workers/Containers.endpoints(id)— resolve the environment's reachable URLs (Worker preview URL / Container ingress) so callers (E2E driver, load harness) can hit them.destroy(id)— delete the dispatch namespace + Container instances; idempotent.src/orchestrator/reaper.ts(identity-checked) to delete environments past TTL or whose owning run died, so namespaces/Containers can't leak.src/config/schema.ts) for the Cloudflare account/API-token (read via aResource.*-style secret, notprocess.env), default limits, TTL.KubernetesEnvironmentProvider/DaytonaEnvironmentProviderimplementing the same port later.src/index.ts.Acceptance
EnvironmentProviderport with a Cloudflare provider; unit tests with a faked Cloudflare API/miniflare.destroyis idempotent (namespace + Containers gone).src/index.ts; config schema covers account/token + limits + TTL; port leaves room for K8s/Daytona providers.✅ End-to-end verification — Factory success criteria (REQUIRED)
"Compiles" is not done. Run the provider against a REAL Cloudflare dispatch namespace and observe an environment come up, be reachable, and get reaped. CI uses miniflare for the fast path and a real dispatch namespace for the E2E.
Run it
The E2E must (against real Cloudflare):
provision()→ assert the dispatch namespace exists, per-env bindings are set, andstatus()reports ready.endpoints()resolves to a reachable URL (curl returns 200).destroy()→ assert the namespace + Container instances are gone.destroy(proves leaks are cleaned).Deliverable
test/e2e/environment-provider.e2e.tsrunning provision→reachable→destroy→reap against a real dispatch namespace in CI, one command.Anchor files
src/ports/fleet.ts(port pattern) → newsrc/ports/environment.tssrc/fleet/relay-fleet-client.ts(provider-impl pattern) → newsrc/environments/cloudflare-provider.ts(+ later optionalkubernetes-provider.ts/daytona-provider.ts)src/orchestrator/reaper.ts(reaper pattern),src/config/schema.ts,src/index.tsrelaycast-cloud, cloud'sminiflare-sage+test:integration