# Forge Sites Alpha v1 contract

Status: historical static contract. Generalized Forge Deploy now owns every
active build, release, activation, reconciliation, and serving decision.

## Generalization boundary

This document describes the original static Sites slice. New Forge Deploy
deployments use the public [preview guide](deploy/forge-deploy-preview.md) and
the [`forgeBuild.ts` v1 contract](deploy/manifest-v1.md). Static-only
applications continue to use the same immutable artifact and edge-serving
path.

The Sites product surface is retained as a view over generalized projects,
releases, deployments, and endpoints. It does not create or serve a separate
Site deployment. Using `sites.smol.ai` for pre-production does not itself
authorize dynamic execution, public self-service, or a production launch.

## Pre-production hostname exception

During review and pre-production validation, operators may set
`HOST_SUFFIX=.sites.smol.ai`, producing `{project}.sites.smol.ai` and
deployment-specific preview hostnames under the same suffix.

This is an explicit temporary exception to the production isolation rule below.
It does not satisfy the dedicated registrable-domain production gate and must
not be described as a production-ready hosting boundary. The compensating
controls and mandatory domain-graduation criteria are documented in the
[Forge Deploy security proposal](deploy/security.md).

The production hostname is intentionally not named in source. Operators must
set `HOST_SUFFIX` to a Forge-owned registrable domain used only for hosted
content before enabling Sites in production. The domain must not share the
Forge application origin or its registrable domain. A proxied wildcard DNS
record and Worker Route serve `*.HOST_SUFFIX`.

## Product contract

- One explicitly enabled site per public repository.
- Alpha accounts may configure and enable an eligible project after its
  repository, Terms, entitlement, quota, and safety checks pass. Repository
  administrators may redeploy, promote, roll back, and disable an enabled site.
  Platform operators may suspend or revoke Alpha serving.
- Production builds follow one configured branch. A successful Git push to
  that branch automatically queues the exact commit SHA.
- Every successful build has an immutable preview hostname. Production is only
  a pointer to one successful deployment; promotion and rollback never rebuild.
- A private, unlisted, deleted, disabled, or suspended repository or site is
  ineligible at every edge lookup. Previously uploaded immutable objects remain
  unreachable until retention cleanup.
- Repository rename, deletion, disablement, suspension, or Alpha revocation
  never releases the site slug. A later repository cannot take over the
  historical hostname, even though serving stops and retained content may be
  removed.
- Agents deploy through ordinary authorized Git pushes in v1. There is no
  special Sites token or permission.

## Build contract

Sites builds run in Forge's restricted public-repository container runner:

- Checkout is detached at the deployment's exact 40-character source SHA.
- The sandbox receives no Forge session, repository secret, customer
  Cloudflare credential, Cloudflare account ID, or delegated deployment token.
- Outbound access is limited to Forge Git and public dependency sources.
- The build command runs from the repository root.
- The output directory is a normalized relative path inside the repository.
- Deployment commands are rejected. Only static files from the configured
  output directory enter trusted ingest.

The default v1 artifact policy is versioned as `v1`:

| Limit | Value |
| --- | ---: |
| Files per deployment | 5,000 |
| Bytes per file | 25 MiB |
| Total output | 250 MiB |
| UTF-8 bytes per relative path | 512 |

Trusted ingest rejects symlinks, special files, path traversal, duplicate
normalized paths, unsupported extensions, observed file-size changes during
ingest, and limit violations. It derives MIME types from a fixed allowlist,
writes files under a deployment-specific immutable R2 prefix, and stores the
completed manifest last. The build sandbox never receives bucket credentials.
The runner uses the Sandbox SDK RPC transport so binary output can cross into
trusted ingest without a base64 or HTTP fallback. Before inspecting output, the
runner kills repository-started processes and verifies the sandbox is
quiescent so repository background processes cannot continue mutating output
during ingest.

## Serving contract

The dedicated Sites Worker:

- accepts only `GET` and `HEAD`;
- resolves exactly one hostname label as a production slug or `preview-*`
  deployment slug;
- joins the current repository, site, and deployment state on every request;
- only reads exact paths present in the trusted manifest and never lists R2;
- rejects malformed encoding, NULs, backslashes, dot segments, and traversal;
- resolves directory indexes and applies SPA fallback only to HTML navigation;
- sends allowlisted MIME, `nosniff`, restrictive frame/referrer/permissions
  headers, no platform cookies, and no broad CORS;
- revalidates production and immutable-preview responses so visibility,
  suspension, disable, and rollback changes take effect immediately.

## Static compatibility boundary

Static Sites does not itself expose server code. Forge Deploy adds one
Forge-owned application abstraction with HTTP, durable state, and bounded
realtime, while private-source builds, secrets, customer Cloudflare
credentials, broad egress, arbitrary provider bindings, and custom domains
remain outside the current Alpha.

## Enablement gates

Before any enablement under the pre-production hostname exception:

1. Configure wildcard DNS, TLS, and the Worker Route for the reviewed
   `HOST_SUFFIX`.
2. Create the `cloudforge-site-assets` R2 bucket.
3. Apply and verify migrations `0023` and `0024`.
4. Deploy the trusted container runner with its `SITE_ASSETS` binding.
5. Deploy the Sites Worker, then the API/UI.
6. Set `SITE_ADMIN_USER_IDS` and `HOST_SUFFIX`.
7. Add and test the `noindex` and shared-domain controls in the Deploy security
   proposal.
8. Complete a fixture push and verify queued, running, successful, preview,
   production, rollback, disable, unlisted, and suspension states on their
   owning surfaces.

Before production enablement, move hosted content to the separate
Forge-owned registrable domain and pass every production-graduation gate in
the Deploy security proposal.
