StackShip starts as six independently deployed Workers in one pnpm/Turborepo monorepo.
| Workspace | Runtime | Domain |
|---|---|---|
apps/web |
Next.js 16 through OpenNext | stackship.run |
apps/api |
Hono | api.stackship.run |
apps/mcp |
Hono and the MCP TypeScript SDK | mcp.stackship.run |
apps/docs |
Nimbus on Astro | docs.stackship.run |
apps/orchestrator |
Cloudflare Workflows | internal service binding |
apps/credential-broker |
Worker and Durable Objects | internal service binding |
The separate public analytics surface, r.stackship.run, is a
PostHog-managed US reverse proxy rather than a seventh StackShip Worker. The
dashboard’s shared product-event helper allows only the documented event and
property schemas. The managed proxy forwards the PostHog protocol and is not a
StackShip validation boundary. Its root is not a health endpoint; release
checks use PostHog’s proxied SDK asset and project-configuration paths.
Request flow
The dashboard calls the native API through a same-origin
/api/control-plane boundary. That boundary admits only methods and paths in
the generated v1 contract, rewrites the host-only dashboard session into the
API’s Better Auth session cookie, rejects unsafe cross-origin mutations, and
forwards logs and artifacts without buffering their complete bodies.
Authenticated server-side API fetches use Worker’s supported manual redirect
mode. The boundary never follows a redirect, and the generated JSON client
rejects any redirect response instead of forwarding the session cookie.
Better Auth cookies remain host-only. Browser JavaScript never receives or forwards the API session cookie directly.
The API owns the Better Auth handler at /api/auth. OAuth login and consent
routes redirect to dashboard screens while preserving Better Auth’s signed
OAuth query. The MCP Worker publishes RFC 9728 protected-resource metadata,
points clients to the API authorization server, and verifies JWT access tokens
against the API’s JWKS. Its tools hold no business logic: each one forwards
the caller’s token to the native v1 API, which independently re-authenticates
the credential and confines it to the published MCP operation surface.
Data plane
PlanetScale PostgreSQL is reached from the API through the Cloudflare Hyperdrive
binding 397048f06dbc43c1a921c92258384a40. Drizzle owns application and Better
Auth schema. Application traffic uses a least-privileged PlanetScale role;
schema migrations use a separate direct administrative connection.
The identity Hyperdrive configuration has query caching disabled. Hyperdrive does not expose a supported per-query cache hint, and Better Auth owns its SQL, so sessions, permissions, organizations, and identity reads need the cache-disabled connection. When product reads can tolerate staleness, add a second cache-enabled Hyperdrive configuration and route only those reads through it.
R2 is the object plane for state-related artifacts, policy source and bundle artifacts, plans, run logs, and exports. Workers Analytics Engine is for high-cardinality operational events, while PostHog is reserved for product behavior and funnels.
Configuration archives, policy source archives, lock files, state, and run
artifacts stream through Workers and R2. The API does not aggregate an artifact
in Worker memory; the launch limit is 100 MB and stream readers enforce bounded
read-ahead. When the plaintext length is known, encryption produces an
exact-length FixedLengthStream for R2. Unknown-length bodies use sequential
multipart upload with one 5 MiB part buffered at a time and at most 10,000
parts; failures abort the multipart upload. Neither path buffers the complete
artifact.
The OpenTofu Cloud backend is the sole state writer for managed runs. Its
attempt token exposes the run-pinned predecessor through the TFE-compatible
API, so the runner neither receives a predecessor-state artifact nor writes one
into the working directory. During apply, OpenTofu creates the successor
version through /api/v2, streams the raw state through the Worker into its
final encrypted R2 identity, and finalizes the metadata and outputs before the
command exits. There is no runner upload, second object copy, or in-memory
state handoff.
After a zero-exit apply, the session-bound runner asks for publication metadata, never the state body. The API returns an id, checksum, and serial only when the current version and its published upload have the exact managed-run principal, attempt, sandbox, predecessor, lock, fence, object identities, and active writer reservation. The runner binds its terminal result digest to that checksum. The commit transaction independently rechecks the same immutable publication before recording the apply’s resulting state and releasing the exact principal’s lock. An exact callback replay uses the persisted resulting state pointer after release; changed replay bindings still fail closed.
OpenTofu writes errored.tfstate only when it cannot persist state through the
Cloud backend. The runner can retain that file as recovery evidence, but it
never treats an ordinary root terraform.tfstate as a resulting-state copy.
Ordinary Cloud plans and successful applies produce no attempt-scoped state or
output copy.
A nonzero apply may still have published partial state through the Cloud backend. StackShip keeps the exact managed-run writer authority and enters recovery instead of calling that apply successful. Recovery may retain the current pointer only when the state version and its published upload prove the same run, attempt, sandbox, predecessor, lock, and fence. Unrelated pointer movement remains a hard conflict.
Artifact deletion removes the logical metadata reference before the physical R2 object. A metadata failure therefore leaves the committed object readable; an R2 deletion failure leaves an inaccessible, unreferenced object for the orphan sweep instead of a metadata row that points to missing ciphertext.
Final encrypted objects are written before their PlanetScale metadata commits.
A daily ArtifactOrphanSweepWorkflowV1 instance walks R2 in pages of at most
1,000 objects through an authenticated internal API. It deletes only
StackShip-shaped keys older than 24 hours that have no exact committed
artifacts.r2_object_key; current uploads, committed objects, and unknown key
shapes are retained. The opaque R2 cursor is persisted between Workflow steps,
so neither Worker accumulates a bucket-wide key inventory.
Native v1 and TFE-compatible JSON control documents are the deliberate exception: ordinary control documents are capped at exactly 65,536 bytes (64 KiB) before parsing. Variable mutation envelopes have a separate 131,072-byte (128 KiB) cap so they can carry one supported 65,536-byte value plus bounded JSON metadata. Credential-profile mutation documents have a 100,000-byte cap so a supported 64 KiB canonical generic-secret environment map fits inside its bounded version envelope. A declared oversize is rejected without pulling the body, while an unknown-length stream is canceled as soon as it crosses the applicable limit. Artifact bodies never pass through the control-document reader.
The orchestrator persists run control flow in Cloudflare Workflows, but durable step outputs contain only bounded identifiers and digests. Raw capability handles, bootstrap tokens, execution nonces, callback nonces, and bearer credentials never enter Workflow history. A runner-start step reacquires its attempt-bound bootstrap handle through authenticated service bindings and keeps the handle and token only in Worker memory while starting the exact sandbox process.
The credential broker stores each handle in a Durable Object and binds it to one attempt, phase, sandbox, and immutable input facts. An identical bootstrap issue retry may reuse the original handle without extending its expiry even when the caller refreshed the requested expiry; every non-expiry binding must still match exactly. Changed bindings fail closed. Apply-start capabilities retain an expiry-bounded exact redemption result so an identical retry can recover a lost response without accepting a changed request. Bootstrap tokens additionally bind the final manifest and callback-nonce digests. Secret handles identify an encrypted variable version; neither the broker nor Workflow state persists its plaintext.
Managed execution uses private, ephemeral sandboxes created from Daytona’s
standard daytona-medium base. StackShip does not build or publish a custom
runner image or snapshot. The orchestrator build embeds generated main-runner
and policy-runner bundles; after sandbox creation it uploads those bundles,
downloads the manifest-selected OpenTofu and pinned OPA releases, verifies
their committed SHA-256 values, and starts the selected bundle. Sandbox egress
is unrestricted because both this bootstrap and customer operations require
public endpoints; it is not a security boundary.
Policy-version publication uses its own Cloudflare Workflow and a dedicated
Daytona runner mode on that same standard base. The browser, dashboard proxy,
API, and R2 preserve backpressure while a customer .tar.gz source archive is
uploaded; the deterministic encrypted object identity is bound to the tenant,
build workspace, policy set, and idempotency key. Workflow state contains only
tenant, version, attempt, sandbox, and digest identities. The API streams
encrypted R2 source and output artifacts, verifies the database-derived digest
binding, and performs the atomic pending-to-published or pending-to-failed
transition. The signed policy bootstrap token and deterministic execution
nonce exist only inside service-bound mint and start callbacks; durable results
project safe sandbox and command identities. Rego validation, tests, and bundle
creation never run in a Worker.
Fixed OPA capabilities, sanitized input, and the absence of customer cloud
credentials constrain policy execution; the sandbox network does not.
Failure posture
Public health and version endpoints do not require the database. Authentication and protected product operations fail closed when Hyperdrive or required secrets are absent. The MCP endpoint never falls back to anonymous access.
Native v1 mutations atomically persist their domain change, audit evidence, exact idempotency result, and any required Workflow outbox entry. External dispatch occurs only after commit. Run, source-ingestion, and policy-publication Workflow IDs are deterministic, so delivery can reconcile a lost create response without starting a second logical execution. The orchestrator’s five-minute schedule calls an authenticated internal reconciler that leases at most 25 pending dispatches per tick; organization, dispatch key, and lease owner constrain every completion or retry.
Launch and operational controls
PlanetScale is the source of truth for one versioned production launch-control
snapshot. It contains the default-disabled public launch gate, a bounded
dark-canary allowlist of exact organization-and-workspace pairs, independent
new-run and apply switches, federation-provider and Daytona-region switches,
resource ceilings, logical runner and policy release allowlists, and the exact
Workflow v1 routing. Workspace public IDs are organization-local: no launch
path may authorize a dark canary from a ws_ ID alone. Organization and
workspace suspensions are tenant-scoped rows. Every mutation uses an optimistic
expected version and an idempotency key and writes immutable actor, request,
reason, before, and after evidence in the same transaction.
Control reads fail closed. When the public gate is false, only an exact
organization-and-workspace dark-canary pair may bypass that one gate; missing
or malformed controls are never an allow. New managed runs then require the
new-run switch and recheck suspension, the pinned region, resource ceilings,
and logical runner release. Sandbox creation rechecks the stored region
immediately before Daytona and never chooses another region. The current
Daytona organization supports the required container class only in us, so
the eu launch switch remains disabled and an EU workspace never falls back
to US. Apply authorization and the runner start intent independently recheck
the public and apply gates. These execution checks do not replace product
admission and do not independently gate reads, cancellation, cleanup, or
recovery.
Product admission is separate from authentication and execution admission. The
native cloud GET /api/v2/ping route remains public. Every other TFE-compatible
route first validates its cloud credential or bearer capability, resolves a
bounded ownership projection, and then admits only the exact owning
organization-and-workspace pair while launch is closed. Organization-wide,
project-wide, unknown, malformed, missing, and ambiguous ownership all fail
closed. Configuration-upload, state-upload, and log capabilities carry no
admission bypass; their verified resource ownership must resolve to the same
allowed pair.
A closed or unresolved native request returns the same non-disclosing JSON:API
503 service_unavailable response with Retry-After: 60, a request ID, and
private no-store caching. Invalid cloud credentials remain 401 invalid_token.
The response never reveals whether a resource exists or which pair is on the
allowlist. Product admission happens before a handler claims an upload
capability or persists a mutation. Separately, a failed new-run execution
check uses the same retryable status and never persists a partial run.
Dashboard product admission
Dashboard authentication and product admission are separate boundaries. The
authenticated root layout verifies the host-only Better Auth session and
active organization, but it does not render the product shell. Organization-
wide route groups first call GET /v1/launch-access; a launch-availability 503
fails the render at the dashboard error boundary before the full navigation
rail is rendered, without loading or implying project, workspace, run, or
state data.
Exact workspace and run route groups do not use the general probe. They call
GET /v1/launch-access/workspaces/:workspaceId or
GET /v1/launch-access/runs/:runId on the server. Public launch renders the
normal organization shell. A dark-canary admission renders only the exact
resource surface: desktop and mobile organization navigation, project links,
and organization-wide run links are absent. Nested links may remain only when
they stay inside the admitted workspace, or move from an admitted run to its
exact owning workspace. Unknown, malformed, closed, and non-allowlisted probes
fail closed rather than falling back to general access.
The same-origin dashboard API boundary preserves an upstream denial’s status,
request ID, Retry-After, and private no-store cache policy while normalizing
the error envelope. It streams request and response bodies and never buffers an
artifact to implement admission.
Playwright exercises these server probe paths against only the explicit
loopback stackship_test database. Its launch-control fixture requires a
hard-coded local-E2E marker, snapshots the exact prior production control row
or its absence, and restores that exact snapshot during teardown and after
each closed or dark-canary case. The fixture refuses remote hosts, another
database name, or a missing marker; local PostgreSQL availability is therefore
an E2E prerequisite, not a reason to weaken an admission assertion.
Each sandbox create intent atomically acquires a durable regional capacity lease in the same PlanetScale transaction. Active leases occupy uniquely indexed regional slots, so simultaneous contenders cannot exceed the current ceiling and an attempt replay cannot reserve twice. The provider identity is bound to that lease after creation, and successful cleanup releases it in the same transaction that terminates the attempt. Ambiguous provider outcomes keep the lease active until exact-label reconciliation proves cleanup.
Workers Analytics Engine receives only strict operational event names, opaque
IDs, bounded enums, durations, counts, and byte sizes. PostHog receives only
the allowlisted launch funnel; automatic page, element, and session capture are
disabled. Repository names, cloud identifiers, resource addresses, email
addresses, secrets, and artifact content are rejected at the telemetry
boundary. Unhandled execution and runner control-plane failures emit only a
fixed error or non_error classification; caught error messages, attached
provider context, request bodies, and authorization material never cross the
logging boundary.