# Forge Deploy operator runbook

Status: local release runbook. Completing these checks does not itself push,
deploy Forge, attach a domain, or change production traffic.

## Authoritative records

- SmolForge Git and the exact 40-character commit SHA are the source authority.
- `deploy_projects` owns each project's root, enabled state, and environments.
- A project-scoped approved provider target owns the Cloudflare account and
  Worker name Forge is allowed to publish.
- Immutable releases and provider artifacts own build and publication
  evidence. Environment pointers own promotion and rollback.
- Cloudflare remains authoritative for Worker versions, runtime secrets,
  inherited bindings, DNS, certificates, Custom Domains, and routes.

Work is partitioned by Forge project for builds and by provider Worker for
publication, promotion, retirement, and domain reconciliation. A failure in
one project must not move another project's pointer or roll it back.

## Before enabling a private monorepo

1. Resolve the repository owner, repository ID, project ID, root directory,
   production branch, and exact SHA separately.
2. If the repository uses `.forge/config.json`, validate the exact-SHA file
   against the [repository configuration contract](repository-config.md).
   Confirm the reviewed defaults, permanent project slugs, roots, binding
   match modes, and plan digest.
3. Confirm each intended root contains its own `forgeBuild.ts`.
4. Confirm the private-source entitlement and Terms receipt are current.
5. Confirm the project's exact-source projection contains its root, root
   package-manager files, and transitive `workspace:*` dependencies, but not
   unrelated sibling applications.
   For pnpm v9, confirm the frozen lockfile importer index was accepted; an
   unsupported importer shape must take the conservative full-tree discovery
   fallback instead of omitting a workspace.
6. Confirm the connected-asset entitlement. For AIE it is 1 GiB per release;
   per-file and file-count provider limits still apply.
7. Resolve the administrator-approved Cloudflare account, Worker name, allowed
   zones, active 100% production version, and binding generation.

Never place Git clone credentials, Cloudflare credentials, secret values, or
DNS authority in the build sandbox.

## OpenNext candidate

1. Require a project-local registry `@opennextjs/cloudflare` declaration and
   an exact resolution in a supported npm, pnpm, or Bun root lockfile.
   For the vetted 1.19.11 target, require Next
   `>=15.5.18 <16 || >=16.2.6 <17`; do not publish a Next 13 project.
2. Run the frozen root dependency install before resolving the project-local
   OpenNext executable. Do not use `npx`, `dlx`, or an implicit `latest`.
3. Build in the project root.
4. Wait for build processes to stop, then bound the raw `.open-next` compiler
   input. Require `worker.js`, `assets`, and `cache`; permit only contained pnpm
   symlinks and reject special files, escapes, or compiler-input overflow.
5. Compile with Forge's credential-free Wrangler path. Inspect the generated
   provider module graph and assets, plus the supported R2 ISR cache layout.
6. Compare the compiled graph, assets, and cache with [limits](limits.md).
   Reject changed, unsafe, oversized, or unsupported output before provider
   publication.
7. Freeze source SHA,
   OpenNext version, Forge adapter version, Wrangler digest, binding
   generation, module graph, assets, cache seed, and inherited settings into
   the artifact identity.
8. Before installation, permit OpenNext artifact reuse only when the complete
   source/config/lockfile/adapter/build-environment/provider/entitlement key
   matches, ISR evidence is ready, and the immutable provider version still
   exists. Missing legacy evidence or any changed input is a normal cache miss.
9. After a full build, reuse an existing provider artifact only after its
   immutable identity and provider receipt are verified. Do not require two
   fresh OpenNext builds to be byte-identical: generated draft-mode keys and
   concurrent prerender-manifest ordering may differ without changing routes.

For ISR, the trusted helper may inherit only the exact R2 cache binding and an
ephemeral token. It must stay at zero percent, upload bounded digest-verified
objects, survive retries through its durable operation ledger, and be removed
before the application candidate is published.

## Asset adoption

1. As a repository administrator, inspect the exact primary-branch SHA with
   `GET /api/repos/:owner/:repo/sites/migration/assets?sha=<sha>` or the Sites
   adoption card. This is read-only and does not execute repository code.
2. Review the literal tree/file entries. The v1 server reports the fixed
   greater-than-50-MiB `public/**` and greater-than-5-MiB file defaults.
3. For each object with `uploaded: false`, use the standard Forge LFS
   Batch/basic HTTP API to upload and verify the exact bytes. A local `git-lfs`
   binary is not required. Inspect again before apply.
4. Apply with the exact source/head SHA, primary branch, exact object list, and
   a stable idempotency key. Never reuse the key for changed input.
5. Record the immutable receipt, derived migration branch, commit, pull request,
   and `preview_behavior: "suppressed"`. Confirm the production branch and all
   deployment pointers are unchanged.
6. Review and merge or close the pull request through the ordinary repository
   workflow. Creating the migration PR is not a deployment or production
   cutover.

## Promotion and rollback

1. Re-read project, environment, release, target, and provider state under the
   relevant fence.
2. Prove the candidate is ready and belongs to the same project and provider
   target as the environment.
3. For manual production migration, change only the selected project's active
   pointer and record the provider receipt.
4. Verify the immutable preview, provider version, Forge production pointer,
   and representative HTTP routes independently.
5. Rollback moves one project's pointer to a prior ready release. It does not
   rebuild and does not rewind Cloudflare resources or application data.

### Automatic connected publication

For an enabled connected project whose project and production-environment
publication modes are both `auto`, Forge reconciles the newest ready release
for the current production ref through the same provider-activation operation
used by an explicit promotion. The activation must still prove the exact head
SHA, ready artifact, approved target, binding generation, Forge environment
generation, active deployment, and frozen provider base. A failed build or a
stale candidate never reaches this reconciler. Push releases also freeze the
matching Action run IDs and wait until every recorded run has completed
successfully. A scheduled pass repairs an interrupted reconciliation
idempotently.

An ordinary Action for a repository with an approved connected target may run
tests, builds, and read-only verification, but it must not activate a plain
Worker version with `wrangler deploy`. The runner rejects that production
mutation because it would bypass the release-scoped private bindings and the
Forge deployment ledger. Remove the raw deploy step from such a workflow and
let Automatic publication activate the connected release, or use Forge's
explicit promotion operation when the project is configured for manual
publication.

Treat these as separate evidence: the repository push, its exact-SHA Action,
the ready connected artifact, the succeeded provider activation and receipt,
the Forge production pointer, and the live hostname. No one record implies the
others.

## Domains and path routes

Repository builds receive no DNS authority. An authenticated project
administrator records desired state, then uses a separate explicit apply
action.

A repository may propose this desired state in `.forge/config.json`. Treat it
as exact-SHA, statically validated intent only. Inspect first, review the plan
and digests, then explicitly import. New projects remain disabled. Existing
projects preserve their enabled or disabled state while reviewed roots,
production branches, and publication modes are updated. A missing or disabled
target stays unresolved; an already-approved target that does not allow the
declared zone blocks import. Import never applies provider state, and an
omitted entry never means detach. If an independent project partially imports
before a later failure, repair the contract, inspect again, and approve the new
plan digest before retrying.

Before apply, prove the hostname is in an approved owned zone, has no competing
Forge claim or conflicting CNAME, and the path route has unambiguous
precedence. Record the desired generation before calling Cloudflare. Persist
the provider receipt, certificate/DNS observations, and any retryable error.

A `path_route` is one logical wildcard-free path with one of two match modes.
`exact_and_descendants` is the default and preserves lexical siblings: Forge
reconciles the exact path plus its `/` descendants. `reserved_prefix` is an
explicit namespace reservation: Forge uses a broad provider pattern such as
`host/archive*`, which covers the query-bearing root and descendants but also
captures siblings such as `/archive-old`. Approve it only when one project
intentionally owns that whole lexical prefix. Apply and detach inspect every
expected provider pattern and fail closed on unexpected overlaps or ownership
drift.

A main Worker Custom Domain and more-specific archive path bindings may share
one hostname. Cloudflare evaluates the matching Worker Route before the Custom
Domain, so Forge does not model the main Worker as a `/*` path route.

One Cloudflare matching limit remains an explicit application gate. Archive
prefixes do not capture Next Pages Router data URLs such as
`/_next/data/<build-id>/archive.json`; reviewed document navigation can avoid
those client data requests. The explicit `reserved_prefix` mode solves the
query-bearing archive root by reserving the broader lexical prefix; its sibling
capture is intentional and must be reviewed before apply. Cloudflare does not
support arbitrary infix Worker Route wildcards.

Detach is reversible desired state. Keep the claim until Cloudflare proves the
domain or route is detached. Never delete an active domain without a confirmed
replacement or an explicit detach.

## Cleanup

Retention protects the active deployment and the configured rollback
candidates. Provider-version cleanup is serialized with promotion, rechecks
current deployment membership, and never deletes a 100% production version.
An interrupted or provider-rejected cleanup retains its receipt and returns to
retryable reconciliation.

## Release proof

Report these as separate facts:

1. implemented locally;
2. focused and full tests passing;
3. phase commits created;
4. branch pushed;
5. D1 migrations applied;
6. provider targets and entitlements provisioned;
7. Worker versions uploaded;
8. traffic or environment pointers switched;
9. DNS and certificate active;
10. live HTTP and browser routes verified.
