# Pull request deploy previews

Forge can build an enrolled repository whenever a same-repository pull request
opens or its source branch advances. The result appears inside the pull request
as an exact-SHA check with two links:

- the stable pull request URL, which follows only the latest successful head;
- an immutable deployment URL, which always identifies one deployment.

Pull request previews use the ordinary Forge Deploy pipeline. They do not create
a separate Cloudflare Pages project, bypass `forgeBuild.ts`, or grant a pull
request permission to publish production.

## Turn previews on

1. Add and validate the repository's root `forgeBuild.ts`.
2. Enroll the repository from its **Sites** tab.
3. In **Pull request previews**, leave **Automatic** enabled.
4. Open a pull request from a branch in the same Forge repository.

Automatic pull request previews are enabled by default for newly enrolled
sites. An administrator can turn them off without disabling the production
site. The initial release does not build pull requests from forks.

The stable hostname has this shape:

```text
preview-pr-<pull-number>-<random>.sites.smol.ai
```

The random component makes the URL unlisted, not private. Anyone who obtains
the URL can open it.

## What Forge guarantees

The pull request head is frozen before a build starts. A durable intent records
the site, pull request, exact commit, policy generation, and desired generation.
Duplicate ref events coalesce into one intent. A retry creates a new desired
generation even when it rebuilds the same commit.

Creating a pull request requires repository write access. Read access to a
public repository is not enough to schedule preview work or consume its build
allowance.

Build completion does not automatically move the stable URL. Forge compares the
result with the pull request's current desired generation, exact head SHA, and
policy generation. A late or superseded build keeps its immutable URL but
cannot move the stable alias backwards. Closing or merging the pull request
revokes the stable alias immediately. The edge also rechecks that automatic
previews remain enabled and that the source branch still exists at the desired
exact SHA, so a partial policy update or deleted branch fails closed.

```mermaid
flowchart LR
  PR["Open PR or new head SHA"] --> Intent["Durable exact-SHA intent"]
  Intent --> Build["Ordinary Forge Deploy build"]
  Build --> Immutable["Immutable preview"]
  Immutable --> CAS{"Still the desired generation?"}
  CAS -->|Yes| Alias["Stable PR alias"]
  CAS -->|No| Superseded["Immutable only"]
  Close["Close or merge"] --> Revoke["Revoke stable alias"]
```

The pull request panel is a view of this control-plane state. Workflow logs
remain available in **Actions**, while build configuration and preview policy
remain in **Sites**.

## Initial safety policy

Automatic previews deliberately support a smaller contract than production:

| Concern | Initial behavior |
| --- | --- |
| Source | Same Forge repository only |
| Access | Public and unlisted |
| Secrets | None |
| Durable state | Isolated by immutable preview deployment |
| Runtime egress | Deny all |
| Realtime | Preview quotas |
| Production activation | Never |
| Retention | Seven days |

Repository code may request lower limits but cannot raise these ceilings.
Deployments that import a connected Cloudflare Worker with
`shareProductionResources: true` are blocked before automatic preview dispatch,
because those bindings could mutate production D1, KV, R2, Durable Objects,
secrets, or services. Such projects remain eligible for deliberate production
deployment but not this safe automatic path.

## Statuses and recovery

- **Queued** means a durable intent is waiting for dispatch.
- **Building** links to the ordinary Forge workflow logs.
- **Ready** exposes both the stable and immutable URLs.
- **Failed** retains a sanitized error and permits a repository writer or
  administrator to retry the exact current SHA.
- **Superseded** means a newer head or generation now owns the stable alias.
- **Blocked** explains a policy or configuration failure.
- **Disabled**, **expired**, or a closed pull request cannot resolve through the
  stable alias.

Retries are idempotent at the deployment boundary and generation-scoped at the
pull request boundary. They do not reuse an older successful result merely
because a branch returned to the same Git SHA.

Runner handoff remains retryable until the exact queued workflow accepts it.
Forge also reclaims a bounded fast-path build after its five-minute claim lease
expires while the workflow remains queued or in progress. This repairs an
interrupted runner request without creating a second deployment identity.

## Authentication limitations

The first preview release intentionally excludes preview-specific application
secrets and external OAuth auto-registration. Many OAuth providers require
exact registered callback origins; wildcard preview hostnames are not a safe
portable assumption. Google requires authorized JavaScript origins and redirect
URIs to match registered values, while a GitHub OAuth App has one configured
callback URL.

Applications that need sign-in should initially use a stable Forge-owned auth
broker callback and app-scoped session exchange, or wait for a later
authenticated-preview mode. Do not put production OAuth client secrets into
automatic pull request builds.

Provider references:

- [Google server-side OAuth flow](https://developers.google.com/identity/sign-in/web/server-side-flow)
- [Google JavaScript OAuth reference](https://developers.google.com/identity/oauth2/web/reference/js-reference)
- [GitHub OAuth App creation](https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/creating-an-oauth-app)

## Not included yet

- fork pull request builds;
- authenticated or organization-only preview access;
- preview-scoped secrets;
- automatic OAuth callback registration;
- sharing production databases, objects, or service bindings;
- preview comments or check-run integration outside Forge;
- production promotion from the pull request panel.

These require distinct trust, identity, or provider-registration work. They do
not weaken the exact-SHA and stable-alias contract described above.
