docs/adr/0001-gcp-cloud-run-is-the-pool-at-concurrency-1.md
execute(); GCP Cloud Run is that pool at concurrency 1The sandbox pool is a pure execution function — execute({ operationType, operation, timeoutInSeconds, settings, provision }) → { engineResponse, logs } — that holds no apiClient and no connection back to the app. provision is { flowBundle?, pieces?, archiveRefs? }, where a single flowBundle reference subsumes flowVersion + piece metadata + compiled codes (piece-only jobs pass bare pieces; private-piece archives ride alongside as S3 archiveRefs). The worker is the sole Resolver: it uses its Socket.IO apiClient to resolve the flowVersion, piece metadata, and bundle/archive S3 refs (disabling the flow on a missing piece) before calling execute, so the pool only ever receives healthy, fully-materialized inputs and reaches the network only to pull the blobs named in its parameters (S3 by signed URL, npm registry for public pieces). GCP_CLOUD_RUN is then nothing but this same pool at concurrency 1 on ephemeral disk, fronted by a single /execute HTTP endpoint; the worker stays in place as Resolver + queue puller and the Cloud Run container is pure compute.
provision/run/dispose across the wire — it must be one self-contained call. Making the pool a single pure execute makes that call trivial and gives LOCAL and GCP_CLOUD_RUN an identical signature (in-process vs. over-HTTP).apiClient entirely. Cloud Run holds zero Socket.IO connections, which fits its stateless/scale-to-zero model.updateRunProgress / updateStepProgress / sendFlowResponse / uploadRunLog straight to the app over engine-token HTTP (POST /v1/engine/*), the same channel it already uses for store/files/connections — so no callback ever needs a path back through the pool host. (Shipped separately on refactor/engine-direct-run-callbacks.)apiClient to the app. Reused the worker's client verbatim, but pinned a persistent bidirectional connection onto a stateless request-scoped runtime, and kept the pool coupled to WorkerToApiContract./execute body. Self-contained, but forced an app→worker→Cloud Run double transfer of large piece archives. Chose S3 references instead (the flow bundle already works this way); the pool pulls blobs from S3 directly.WorkerToApiContract (flowProvisioning, pieceCache, flowBundleStore) moves out of sandbox-pool to the worker/Resolver; sandbox-pool stops importing WorkerToApiContract.disableFlow / missing-piece handling leaves the pool entirely — the Resolver detects it during resolution and never calls execute.execute, so the worker keeps bun/build tooling and the pool only ever consumes a ready, compiled bundle. flowBundle is therefore always a ready ref ({ url } | { inline }), never a build instruction. (Rejected: building in the pool via a pre-signed upload URL, and building at flow-lock time app-side — both viable, but the worker is already the builder today, so this is the least-change path. Revisit if cold-build load on workers becomes a problem.)