# Forge application runtime preview

Status: the constrained `forge-application/v1alpha1` Alpha runtime and numeric
limits are implemented for Alpha-enabled fixtures. Broader compatibility
remains proposed.

The normal Forge author declares one application in `forgeBuild.ts`:

```ts
app: {
  entrypoint: 'src/server.ts',
  assets: { directory: 'dist', fallback: 'index.html' },
}
```

Forge compiles that application into static assets, an HTTP runtime, managed
durable state, and managed realtime components as needed. `Worker`, `Durable
Object`, namespace, class, service binding, and provider script are compiled
plan vocabulary, not application choices.

Every application entrypoint receives the managed state and realtime APIs.
They are lazy: no durable storage is retained until a successful mutation, and
no realtime connection exists until a client subscribes. An assets-only
application has no code runtime and creates neither. A site administrator may
disable realtime as a spend guard without changing the module shape;
authorization then returns `realtime_refused`.

## Default semantics

- HTTP is stateless, auto-scaled, and has no affinity.
- Durable state is strictly serializable within one caller-selected scope.
- Different scopes are independent; cross-scope transactions are unsupported.
- SQLite-backed Durable Objects are the initial state implementation.
- A state mutation may emit a realtime event in the same atomic commit.
- Managed realtime is server-emit-only, ordered per `(scope, topic)`, and
  non-authoritative.
- Realtime uses hibernating WebSockets when admitted and conditional polling
  only when a socket is technically unavailable and the shared realtime
  budget admits fallback.
- Production state persists across releases. Preview state is isolated per
  deployment and expires under preview retention policy.
- Rollback changes code, routes, assets, and compiled components; it never
  rolls mutable data backward.
- Provider placement, colocation, hibernation, bindings, and lifecycle are
  Forge responsibilities.

Unsupported semantics fail planning or execution with a stable diagnostic.
Forge never silently weakens serializability, isolation, or access policy.

## Bundle contract

Forge's trusted build step produces:

- one bundled ECMAScript module from the declared JavaScript or TypeScript
  source `entrypoint`;
- an optional static asset inventory;
- an optional source map stored as a diagnostic artifact;
- no runtime package installation or mutable dependency resolution.

Rules:

- the module uses ESM syntax;
- it has one default export with an asynchronous `fetch` method;
- all dependencies are statically bundled;
- dynamic and remote imports, CommonJS entrypoints, service-worker event
  syntax, `eval`, and `new Function` are rejected;
- Node filesystem, process, child-process, raw-socket, and native-addon APIs
  are unavailable;
- repository-authored Durable Object classes and named provider handlers are
  rejected in `v1alpha1`;
- provider compatibility is pinned by the adapter; any future native escape
  hatch requires a later reviewed manifest version.

Forge invokes a pinned bundler with no repository plugins or configuration.
The repository build may prepare assets, generated source, and installed
dependencies, but it does not supply the provider-ready wrapper or deployment
artifact.

Example:

```js
export default {
  async fetch(request, env) {
    const url = new URL(request.url);
    const eventId = url.searchParams.get("event");
    const event = env.forge.state.scope(`event/${eventId}`);

    return Response.json(await event.list({ prefix: "booth/" }));
  },
};
```

## HTTP handler

```ts
declare const forgeRealtimeGrantBrand: unique symbol;

interface ForgeRealtimeGrant {
  readonly [forgeRealtimeGrantBrand]: true;
}

type ForgeHandlerResult = Response | ForgeRealtimeGrant;

interface ForgeApplicationModule {
  fetch(
    request: Request,
    env: Readonly<ForgeEnvironment>,
    context: ForgeExecutionContext,
  ): ForgeHandlerResult | Promise<ForgeHandlerResult>;
}
```

There is one HTTP handler per application in `v1alpha1`. The handler receives
the original normalized URL path selected by Forge routing. Exact `/.forge`
and `/.forge/*` requests remain platform-owned and never reach repository code.

Unsupported repository-authored handlers include scheduled events, queues,
email, tail or trace, alarms, WebSocket upgrade, raw TCP, and background
daemons. `ForgeRealtimeGrant` is an unforgeable, opaque platform value that an
authorization endpoint may return directly. Managed realtime is exposed
through the reserved platform endpoint, not an application `101` response.

## Globals and execution context

The portable baseline includes:

- `Request`, `Response`, `Headers`, `URL`, and `URLSearchParams`;
- `fetch` under the effective Forge egress policy;
- `crypto` and `crypto.subtle`;
- `TextEncoder`, `TextDecoder`, `atob`, and `btoa`;
- `ReadableStream`, `WritableStream`, and `TransformStream`;
- `AbortController` and `AbortSignal`;
- `structuredClone`;
- `console`;
- bounded timers supported by the provider runtime.

It excludes `process`, `Buffer`, `require`, filesystem and operating-system
access, raw provider bindings, provider identifiers, Forge service bindings,
storage credentials, and management APIs.

```ts
interface ForgeExecutionContext {
  waitUntil(promise: Promise<unknown>): void;
}
```

`waitUntil` shares the request's project, deployment, CPU, egress, log, and
cost attribution; it is cancelled at the post-response deadline.
`passThroughOnException` is unsupported.

## Environment and trusted wrapper

Every application entrypoint receives the same portable environment:

```ts
interface ForgeEnvironment {
  readonly forge: {
    readonly state: ForgeStateNamespace;
    readonly realtime: ForgeRealtime;
  };
}
```

The adapter does not upload the repository bundle as the top-level provider
handler. It generates a versioned trusted wrapper that imports the immutable
user module and alone receives provider bindings.

The wrapper:

- exposes only the managed Forge APIs;
- keeps raw KV, Durable Object, dispatch, service, and provider bindings in
  trusted lexical scope;
- binds every call to the current tenant, project, environment or preview
  deployment, and compiled plan;
- applies operation, value, scope, connection, and usage limits;
- translates provider failures into stable Forge errors;
- strips edge-owned access credentials before invoking repository code; and
- rejects imports of wrapper internals or provider modules.

The release records the user-bundle digest. Each environment deployment also
records a provider-artifact digest derived from the wrapper version,
user-bundle digest, application components, policy, and compatibility
settings.

## Managed durable state

Repository code selects a logical scope, not a Durable Object:

```ts
type JsonValue =
  | null
  | boolean
  | number
  | string
  | JsonValue[]
  | { [key: string]: JsonValue };

type ForgeGetResult =
  | {
      found: false;
      scopeVersion: string;
    }
  | {
      found: true;
      value: JsonValue;
      version: string;
      scopeVersion: string;
    };

interface ForgeStateEntry {
  key: string;
  value: JsonValue;
  version: string;
}

interface ForgeRealtimeCursor {
  topic: string;
  sequence: string;
  scopeVersion: string;
}

interface ForgeStateNamespace {
  scope(name: string): ForgeDurableScope;
}

interface ForgeDurableScope {
  get(key: string): Promise<ForgeGetResult>;

  list(options?: {
    prefix?: string;
    cursor?: string;
    limit?: number;
  }): Promise<{
    entries: ForgeStateEntry[];
    cursor?: string;
    complete: boolean;
    scopeVersion: string;
  }>;

  snapshot(options: {
    topic: string;
    prefix?: string;
    limit?: number;
  }): Promise<{
    entries: ForgeStateEntry[];
    complete: boolean;
    realtimeCursor: ForgeRealtimeCursor;
  }>;

  mutate(operations: ForgeMutation[]): Promise<{
    scopeVersion: string;
    results: ForgeMutationResult[];
  }>;
}
```

Initial mutation operations are:

```ts
type ForgeMutation =
  | { assertAbsent: { key: string } }
  | { assertVersion: { key: string; version: string } }
  | { put: { key: string; value: JsonValue } }
  | { delete: { key: string } }
  | { increment: { key: string; delta: number } }
  | {
      emit: {
        topic: string;
        event: JsonValue;
      };
    };

type ForgeMutationResult =
  | { type: "assertAbsent" }
  | { type: "assertVersion" }
  | { type: "put"; version: string }
  | { type: "delete"; existed: boolean }
  | { type: "increment"; value: number; version: string }
  | {
      type: "emit";
      sequence: string;
      delivery: "queued" | "suppressed";
    };
```

All operations in one `mutate` call commit atomically and serializably inside
one scope. Results are positional. A failed assertion commits nothing and
rejects with `state_conflict`. Every successful mutation advances an opaque
monotonic `scopeVersion`. Stored numbers must be finite. `increment` accepts
only safe-integer deltas, treats an absent key as zero, and rejects a
non-number or overflow.

Each `(scope, topic)` also has a contiguous unsigned 64-bit event sequence,
encoded as a decimal string. An `emit` advances that sequence and records the
event intent in the same SQLite transaction as its state mutation. It becomes
visible only after the state commit succeeds. State is authoritative;
realtime events are change notifications that prompt clients to apply a
versioned update or refetch a snapshot.

`delivery: "queued"` means admitted for best-effort live fan-out, not
guaranteed delivery. Realtime disablement, event-rate exhaustion, or a full
short-lived outbox returns `"suppressed"` without rolling back the state
mutation. The sequence still advances, and Forge closes or marks affected live
subscriptions for resync. Realtime cost controls can therefore degrade live
updates without taking down authoritative state.

`snapshot` reads the selected entries, `scopeVersion`, and topic sequence in
one transaction. Its initial `limit` ceiling is 100 and `complete: false` is
not valid for a browser snapshot endpoint; applications with larger snapshots
must expose a different bounded projection or wait for a future contract.

The managed implementation:

- uses a small stable set of Forge-owned SQLite-backed Durable Object classes;
- hashes scopes over 16 fixed production shards per project and four fixed
  shards per preview deployment;
- records the shard count with environment policy and never remaps retained
  state through an ordinary deployment;
- stores many logical scopes in each shard rather than creating one provider
  object per caller-selected name;
- compacts delivered outbox entries and retains undelivered entries for at
  most 60 seconds and 1 MiB per shard, with no subscriber replay API;
- retains scope storage only after a successful mutation;
- never runs repository code inside a Durable Object;
- cannot make user-directed network requests;
- exposes no class, namespace, object ID, provider stub, or migration control;
- retains production scopes across application releases; and
- isolates preview scopes by deployment and removes them only under recorded
  retention policy.

Custom Durable Object code, alarms, arbitrary methods, schema migrations,
outbound connections, and provider bindings require a future
`cloudflare-native` capability.

## Managed realtime

The managed realtime interface is always present but creates no connection
until the Forge browser client subscribes:

```ts
interface ForgeSnapshotEnvelope<T extends JsonValue = JsonValue> {
  type: "forge-snapshot";
  data: T;
  cursor: ForgeRealtimeCursor;
}

interface ForgeRealtime {
  authorize(
    request: Request,
    subscription: {
      scope: string;
      topic: string;
    },
  ): Promise<ForgeRealtimeGrant>;

  snapshotResponse<T extends JsonValue>(
    request: Request,
    snapshot: ForgeSnapshotEnvelope<T>,
  ): Response;
}
```

Every application event in `v1alpha1` is an `emit` inside `state.mutate`.
Standalone publishing, transient events unrelated to state, and browser
publishing are deferred.

Browser use is through the versioned Forge client:

```ts
const subscription = forge.realtime.subscribe({
  authorize: `/api/events/${eventId}/subscribe`,
  snapshot: `/api/events/${eventId}/availability`,
  onSnapshot(value) {
    render(value);
  },
  onEvent(event) {
    applyVersionedEvent(event);
  },
});
```

The application's subscribe endpoint authenticates and authorizes the ordinary
HTTP request, then calls:

```js
return env.forge.realtime.authorize(request, {
  scope: `event/${eventId}`,
  topic: "availability",
});
```

The return value has no enumerable or readable data. The trusted wrapper keeps
its brand and ticket bytes in private lexical state, recognizes it only as the
handler's final result, and serializes the wire response after repository code
has returned. Repository code cannot inspect, clone, stringify, or log the
ticket. The SDK calls this endpoint for the first connection and every
reconnect. For intentionally public data, the application endpoint may
authorize an anonymous request. Forge still applies project, origin,
deployment, generation, and quota policy.

The snapshot endpoint reads state and the realtime sequence atomically, builds
the application projection, and uses `snapshotResponse`:

```js
const snapshot = await event.snapshot({
  topic: "availability",
  prefix: "booth/",
});

if (!snapshot.complete) {
  return Response.json({ error: "snapshot_too_large" }, { status: 413 });
}

return env.forge.realtime.snapshotResponse(request, {
  type: "forge-snapshot",
  data: Object.fromEntries(
    snapshot.entries.map(({ key, value }) => [key, value]),
  ),
  cursor: snapshot.realtimeCursor,
});
```

`snapshotResponse` emits the normative JSON envelope and an `ETag` derived
from both scope version and topic sequence. It returns `304` for a matching
`If-None-Match`. Arbitrary JSON snapshot responses are not valid SDK inputs.
Every mutation that affects a topic's snapshot must include an `emit` for that
topic; Forge does not infer application-level dependencies.

The client:

1. fetches an opaque, one-use grant from the application authorization
   endpoint;
2. opens the same-origin `/.forge/realtime` endpoint;
3. after `ready`, buffers events without invoking application callbacks;
4. fetches the authoritative snapshot;
5. discards buffered events at or before the snapshot sequence;
6. applies later buffered events in sequence and enters live delivery;
7. deduplicates repeated sequences and refetches on a sequence gap,
   activation change, or reconnect; and
8. may fall back to conditional snapshot polling, initially every 15 seconds
   and doubling with jitter to five minutes, only while the page is visible
   and only when the grant permits fallback after a technical failure.

The SDK buffers at most 256 events or 1 MiB during bootstrap or resync.
Overflow discards the buffer and starts a fresh snapshot. There is no partial
replay.

Wire events use:

```ts
interface ForgeRealtimeEvent<T extends JsonValue = JsonValue> {
  type: "event";
  sequence: string;
  scopeVersion: string;
  event: T;
}
```

`authorize` internally creates a single-use, 60-second handshake ticket scoped
to tenant, project, environment or preview deployment, normalized public
origin, scope, topic, deployment generation, access epoch, and issuing
release. The expected origin is `new URL(request.url).origin` after trusted
host normalization; an incoming `Origin`, when present, must match it exactly.

The SDK offers subprotocols
`forge.realtime.v1, forge-ticket.<base64url-ticket>`. The server echoes only
`forge.realtime.v1`, atomically consumes the ticket immediately before a
successful upgrade, and redacts both protocol fields from platform and
provider logs. Tickets never appear in a URL. Reconnect always requires a new
opaque grant from the application endpoint.

The grant wire response carries an SDK-only `fallbackAllowed` decision. Policy,
access, suspension, and exhausted-budget denials issue no grant. A later
`1008`/`4403` access close or `4429` budget close forbids polling; a transient
`4503` or abnormal network failure permits it only when the grant did.
Polling uses the snapshot endpoint, its ETag, the same access and generation
checks, and the same realtime budget. `401`, `403`, `410`, or `429` stops
polling.

Realtime guarantees:

- hibernating server-side WebSockets are mandatory;
- endpoint access policy is checked at handshake and throughout the session;
- repository and share credentials are consumed by the edge, never exposed to
  application code;
- connections are pinned to one deployment and managed state scope;
- only server-to-client application events are supported initially;
- ordering and contiguous sequence are per `(scope, topic)`;
- replay, presence, client publish, and cross-scope ordering are unsupported;
- events contain no platform credentials or provider metadata;
- application correctness cannot depend on receiving every event.

Activation, rollback, disablement, access changes, and suspension close
affected sockets with a platform code. Activation and rollback stop new
old-generation grants immediately, drain for at most 30 seconds, and close
remaining sockets with `1012` (`Service Restart`). The SDK applies 0–30
seconds of jitter. A one-use reconnect lease tied to the prior connection
exempts one attempt within two minutes from the connection-attempt rate limit,
but not from authorization, concurrency, request, bandwidth, or spend limits.

Suspension revokes grants, increments the access epoch, makes every socket
inert, and closes sockets with `1008` (`Policy Violation`). The client does not
reconnect automatically. The fixed shard pool gives the control plane a
bounded set of hibernating objects to wake, enumerate, close, and require
acknowledgment from. Inertness is the safety guarantee; physical closure has a
ten-second objective while the provider control path is available. A missed
notification is retried and cannot permit state mutation or event delivery.

## Booth reservation mapping

The booth application uses one scope per event:

```js
const event = env.forge.state.scope(`event/${eventId}`);

await event.mutate([
  { assertAbsent: { key: `booth/${boothId}` } },
  { put: { key: `booth/${boothId}`, value: reservation } },
  {
    emit: {
      topic: "availability",
      event: { boothId, status: "reserved" },
    },
  },
]);
```

Concurrent claim attempts serialize inside the event scope. Exactly one
assertion succeeds; losers receive `state_conflict`, which the HTTP handler
maps to `409`. Availability updates commit atomically with the reservation.
Clients see live changes when admitted to realtime and bounded conditional
polling otherwise. No D1 database, companion Worker, raw Durable Object class,
Cloudflare token, or additional CI target is application-visible.

If one event later becomes a hotspot, an explicitly reviewed application
change may partition state by booth while preserving event-level notifications
through a managed durable outbox. Forge does not silently change partitioning
or consistency.

## Data lifecycle and rollback

- Production scopes are keyed to the stable production environment, not a
  release or source SHA.
- Preview scopes are keyed to one preview deployment and have a TTL.
- Preview data is never promoted into production.
- Code that stops accessing a scope, other code changes, and rollback never
  destroy state.
- Rollback changes application code and routes while retaining current mutable
  data.
- Explicit deletion, export, migration, jurisdiction change, or retention
  change is a separate authorized and audited operation.
- Stateful rollback requires compatibility confirmation and cannot claim to
  restore data.

## Network

User-authored outbound traffic originates only from the application HTTP
handler.

`fetch()`:

- accepts only `https:` by default;
- applies the effective allowlist or entitled broad-public policy;
- blocks private, loopback, link-local, metadata, internal, Forge, storage,
  credential-proxy, and provider-management targets;
- validates DNS at connection time and every redirect;
- applies safe ports, byte, duration, concurrency, and subrequest limits; and
- never attaches Forge or provider credentials.

Managed durable state and realtime make no user-directed outbound requests.
Cloudflare Outbound Workers do not intercept Durable Object fetches, which is
why custom Durable Object code and outbound sockets are not part of the
automatic runtime.

## Requests and response headers

- Forge accepts HTTP and HTTPS selected by trusted hostname and path routing.
- Application HTTP handlers may return a valid `Response` within limits or an
  opaque `ForgeRealtimeGrant` as their final value.
- Streaming bodies remain subject to byte, duration, cancellation, and
  metering limits.
- Application status `101` is rejected; only `/.forge/realtime` may upgrade.
- Exceptions become sanitized `500` responses.
- Platform routing, access, suspension, realtime, and diagnostic headers are
  reserved.
- Edge-owned access cookies and authentication headers are consumed before the
  application handler receives a request.

The trusted edge:

- rejects malformed or oversized headers;
- removes hop-by-hop fields and computes response framing;
- removes provider fields, `CF-*`, and `X-Forge-*`;
- applies baseline security, framing, and shared-domain `noindex` policy;
- overrides dynamic responses to `Cache-Control: no-store`;
- permits application CORS only on public endpoints;
- rejects cookies with a `Domain` attribute and reserves
  `__Host-forge-*`;
- blocks application service workers in `v1alpha1`; and
- permits other bounded end-to-end headers that do not weaken platform policy.

Static assets retain the immutable-asset response policy.

## Logging and errors

Application, state, and realtime logs are attributed to tenant, project,
environment, release, deployment, component, scope digest, request or
connection, and provider operation. Raw state values, preview credentials,
share tokens, cookies, realtime tickets, and provider secrets are not logged.

Every managed runtime method rejects with the same typed error:

```ts
type ForgeErrorCode =
  | "binding_unavailable"
  | "invalid_scope"
  | "scope_limit_exceeded"
  | "invalid_key"
  | "invalid_number"
  | "value_too_large"
  | "state_conflict"
  | "quota_exceeded"
  | "rate_limited"
  | "realtime_refused"
  | "resync_required"
  | "egress_denied"
  | "request_cancelled"
  | "runtime_limit_exceeded"
  | "unsupported_runtime_feature";

class ForgeRuntimeError extends Error {
  readonly code: ForgeErrorCode;
  readonly retryable: boolean;
  readonly retryAfterMs?: number;
}
```

Application code may catch and map it to a domain response. If it does not, the
trusted wrapper returns a bounded JSON error with no provider detail:

```json
{
  "error": {
    "code": "state_conflict",
    "retryable": false
  }
}
```

Invalid input uses `400` or `413`, state conflicts use `409`, policy denials
use `403`, quota and rate limits use `429` with `Retry-After`, unavailable
dependencies use `503`, and unsupported runtime features use `501`. Other
uncaught application exceptions remain sanitized `500` responses.

## Proposed alpha limits

These are Forge ceilings, not provider promises. A repository may request
lower limits. Higher limits require a project entitlement and budget review.

### HTTP

| Limit | Default |
| --- | ---: |
| Application bundle, uncompressed | 5 MiB |
| Request body | 10 MiB |
| Response body | 25 MiB |
| CPU per request | 50 ms |
| Wall time per request | 30 s |
| Outbound subrequests | 20 |
| Outbound response bytes | 10 MiB |
| `waitUntil` promises | 5 |
| Post-response `waitUntil` | 10 s |
| Log bytes per request | 64 KiB |

### Durable state

| Limit | Default |
| --- | ---: |
| Fixed production shards per project | 16 |
| Fixed shards per preview deployment | 4 |
| Production scopes per project | 1,000 |
| Preview scopes per deployment | 100 |
| New production scopes | 100/hour |
| New preview scopes | 20/hour |
| Distinct production scope names addressed | 200/hour |
| Distinct preview scope names addressed | 50/hour |
| Scope-name bytes | 256 |
| Keys per list page | 100 |
| Operations per HTTP request | 50 |
| Operations per atomic mutation | 32 |
| Key bytes | 512 |
| Value bytes | 256 KiB |
| Stored bytes per scope | 10 MiB |
| Production stored bytes per project | 100 MiB |
| Stored bytes per preview deployment | 10 MiB |
| Stored bytes across live previews | 50 MiB |

Distinct-scope limits count the first address of each normalized scope name in
a rolling hour, including misses and rejected mutations, and are enforced
before a provider-object call. All state rates are keyed by tenant, project,
and production environment or exact preview deployment. Fixed sharding bounds
provider-object cardinality even when code rotates unretained scope names.

### Managed realtime

| Limit | Default |
| --- | ---: |
| Fixed production shards per project | 16 |
| Fixed shards per preview deployment | 4 |
| Active topics per project | 100 |
| Active topics per preview | 20 |
| New topics per project | 50/hour |
| New topics per preview | 10/hour |
| Concurrent connections per project, all environments | 200 |
| Concurrent connections per preview | 20 |
| Concurrent connections per scope | 50 |
| Concurrent connections per browser session | 4 |
| Soft concurrent connections per source IP | 20 |
| New non-platform connection attempts | 60/minute/project |
| Scheduled reconnect | randomized between 45 and 60 minutes |
| Hard session lifetime | 75 minutes |
| Visible idle lifetime | no timeout; hibernation required |
| Hidden-page close | after 10 minutes |
| Event bytes | 8 KiB |
| Events per scope | 30/minute |
| Events per project | 300/minute |
| Recipients per event | 50 |
| Short-lived outbox retention | 60 seconds |
| Short-lived outbox per shard | 1 MiB |
| Managed realtime bandwidth | 50 MiB/day/project |
| Bootstrap/resync buffer | 256 events or 1 MiB |
| Polling fallback | 15 seconds to 5 minutes with backoff |

Topic creation and connection-attempt windows are rolling and keyed by tenant,
project, and environment. The one-use platform reconnect lease described
above is the only attempt-rate exemption.

Socket connections, fallback polls, messages, deliveries, fan-out, and bytes
consume one shared realtime budget. The platform additionally enforces
project, tenant, provider-account, and global request, active-duration,
storage, bandwidth, and spend circuit breakers. Approaching a realtime budget
first refuses new subscriptions and lengthens fallback backoff. Reaching the
budget disables both sockets and polling for that project; it never converts a
socket denial into extra polling. It does not disable authoritative HTTP or
durable state unless their own safety limits are exceeded.

## Conformance and launch gate

The adapter fixture must verify:

1. exact artifact digest, module format, and trusted-wrapper isolation;
2. documented globals and rejection of excluded APIs;
3. production and preview state separation;
4. scope serialization, atomic assertion/mutation/emit, and cross-project
   denial;
5. fixed sharding, cold-scope and cardinality limits, retention, and explicit
   deletion;
6. opaque-grant isolation, exact origin, deployment pinning, hibernation,
   connect-buffer-snapshot ordering, duplicate/gap resync, and polling
   fallback;
7. connection, message, fanout, bandwidth, duration, and spend limits;
8. activation, rollback, disablement, access revocation, and suspension while
   sockets are connected;
9. allowlist and broad-public egress, redirects, DNS rebinding, private and
   metadata targets, ports, bytes, and revocation;
10. `waitUntil`, cancellation, streaming, logging, CPU, wall, and subrequest
    limits;
11. response sanitization and reserved `/.forge/*` routing;
12. code-only rollback against retained mutable state; and
13. provider retry, stale-generation fencing, cleanup, and reconciliation.

Until this fixture passes, the manifest parser may recognize `application`,
but planning must return `runtime_not_enabled`.
