> ## Documentation Index
> Fetch the complete documentation index at: https://docs.tuturuuu.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Playground isolation contract

> Behaviour and isolation invariants the Playground app must keep across its TanStack Start and Cloudflare migration, each linked to its contract test.

## Scope

This page records the current behaviour of `apps/playground` (Next.js 16.4) that
the TanStack Start + Void + Vite+ + Cloudflare migration must preserve. It is the
first Playground PR from the
[migration inventory](https://docs.tuturuuu.com/build/devops/tanstack-cloudflare-migration-inventory.md):
contracts only, with no framework or hosting change.

The contract tests live in `apps/playground` and run with `bun run test` from
that directory. They use module fakes for the API proxy guard, the AI provider
and satellite staff-session dependencies, so they make no network calls and need no credentials.

### What Playground does not own

`apps/playground` is an internal experiment surface. It does not evaluate
user-supplied code. Code-execution playgrounds (sandboxed runtimes, previews and
checkpoints) are owned by the devbox sandbox in `packages/sdk` and by Web routes
under `/api/v1/users/me/playgrounds/**`, and are accepted by
`playground-runtime-acceptance.yaml`. The migration must not move execution,
preview or sandbox responsibilities into this app. Adding any of them is a new
trust boundary that needs its own design, review and contract tests first.

## Current shape

| Area | Current behaviour |
| - | - |
| Pages | `/` and `/tool-calling`, both client components that fetch a same-origin `/api/ai/*` route |
| Local route handlers | `GET /api/ai/object-generation`, `GET /api/ai/tool-calling`, `GET /api/build-info` |
| Proxy | `src/proxy.ts` matches pages and API paths, excluding framework assets and dotted paths; API requests use the shared guard with the `proxy:playground:api` prefix; signed-out pages redirect to Web sign-in with a return URL |
| Web API | Unmatched `/api/*` paths reach Web through a Next `fallback` rewrite, after local routes and pages |
| Headers | Shared `createTuturuuuNextConfig`: no `X-Powered-By`, anti-framing on every path outside the platform framable list, explicit private `X-Robots-Tag` from `next.config.ts` on every response; unlisted from shared SEO policy and `robots.txt` disallows all crawling |
| Session | Pages and AI routes require a satellite session with a verified staff Auth identity and root-workspace membership; AI routes return 401 when signed out and 403 for non-staff or non-root-members; the root layout rechecks access |
| i18n | English-only request config with an empty catalog |
| Storage | No app-owned storage, database writes or bindings; AI routes write no AI memory or usage under the root workspace or a placeholder user |

## Invariants and tests

Paths are relative to `apps/playground`.

| # | Invariant | Test |
| -: | - | - |
| 1 | Pages are exactly `/` and `/tool-calling` | `src/contracts/surface.test.ts` → serves exactly the inventoried pages |
| 2 | Local route handlers are exactly the three inventoried `/api` routes | `src/contracts/surface.test.ts` → serves exactly the inventoried local route handlers |
| 3 | One root layout plus the shared error and global error boundaries | `src/contracts/surface.test.ts` → keeps one root layout |
| 4 | No dynamic or catch-all segments that could shadow proxied `/api` paths | `src/contracts/surface.test.ts` → has no catch-all or dynamic segments |
| 5 | No server actions | `src/contracts/surface.test.ts` → declares no server actions |
| 6 | No execution or cross-origin messaging primitives: iframes, `srcdoc`, `postMessage`, message listeners, workers, service workers, `eval`, `new Function`, string timers, `dangerouslySetInnerHTML`, `child_process`, `vm`, `window.open`, `document.domain` | `src/contracts/surface.test.ts` → isolation: no execution surface |
| 7 | Client code fetches only same-origin relative `/api` paths | `src/contracts/surface.test.ts` → only fetches same-origin relative /api paths |
| 8 | Client code reads no non-public environment variables | `src/contracts/surface.test.ts` → reads no server secrets from client code |
| 9 | No `X-Powered-By` header | `src/contracts/next-config.test.ts` → hides the framework fingerprint header |
| 10 | `frame-ancestors 'none'` and `X-Frame-Options: DENY` on pages, local routes and proxied API paths | `src/contracts/next-config.test.ts` → denies framing of every playground path |
| 11 | No app-specific framable exceptions beyond the platform list | `src/contracts/next-config.test.ts` → adds no app-specific framable exceptions |
| 12 | Every response carries the private `X-Robots-Tag` explicitly from `next.config.ts`; Playground is unlisted and crawling is disallowed | `src/contracts/next-config.test.ts` → sets the private robots header; `src/contracts/seo.test.ts` → stays out of public SEO surfaces |
| 13 | Only unmatched `/api/*` proxies to the configured Web origin, after local routes; no `beforeFiles` or `afterFiles` rewrites | `src/contracts/next-config.test.ts` → proxies only unmatched /api paths to web |
| 14 | Deployed builds default the Web origin to `https://tuturuuu.com` | `src/contracts/next-config.test.ts` → defaults the proxied web origin |
| 15 | Remote image optimization is https-only | `src/contracts/next-config.test.ts` → only optimizes remote images over https |
| 16 | No custom redirects | `src/contracts/next-config.test.ts` → declares no custom redirects |
| 17 | The proxy matches pages and API paths while excluding framework assets and dotted paths | `src/proxy.test.ts` → matches pages and api but not framework assets |
| 18 | Local and proxied API requests pass the shared guard with the `proxy:playground:api` prefix | `src/proxy.test.ts` → guards local and proxied API requests |
| 19 | A guard rejection is returned unchanged | `src/proxy.test.ts` → returns the guard rejection unchanged |
| 20 | Signed-out pages redirect to Web sign-in with the original return URL; supported session cookies pass to the server staff gate | `src/proxy.test.ts` → redirects signed-out %s to web sign-in; lets a %s through to the page |
| 21 | A guard failure is raised, never treated as a pass | `src/proxy.test.ts` → propagates guard failures |
| 22 | Every local route handler exposes only `GET` | `src/app/api/routes.test.ts` → exposes only GET |
| 23 | `/api/ai/tool-calling` requires verified root staff and returns `{}` with no model call; both AI routes return 401 signed out and 403 for non-staff or missing root membership | `src/app/api/routes.test.ts` → tool-calling returns an empty JSON object for staff; `src/app/api/ai/routes.test.ts` → returns 401 / 403 |
| 24 | `/api/build-info` returns uncached JSON | `src/app/api/routes.test.ts` → build-info returns uncached JSON |
| 25 | `/api/ai/object-generation` reads no request input, uses a fixed server prompt, the raw pinned model for verified root staff without AI memory writes, and passes no tools | `src/app/api/routes.test.ts` → object-generation takes no request input |
| 26 | Object generation output is validated against the task schema | `src/app/api/routes.test.ts` → validates model output against the task schema |
| 27 | Provider failures surface as errors | `src/app/api/routes.test.ts` → surfaces provider failures as errors |
| 28 | i18n is English-only with an empty catalog | `src/i18n/request.test.ts` → is English-only with no message catalog |

## Migration rules

* Port these tests with the app; keep each invariant green on the Start/Worker
  runtime before any cutover. Changing an invariant is a reviewed behaviour change,
  not a migration detail.
* Header invariants (9–12) must be reproduced by the Worker response path, using
  the shared framable-path list from `@tuturuuu/utils/framable-paths` as the
  start-bridge Worker does.
* The `/api` ordering in invariant 13 must hold on Cloudflare: local handlers
  answer first, then Web. Do not add a catch-all route that captures `/api/*`.
* Keep the API proxy guard (17–21) in front of local and proxied `/api` traffic.
* Keep API ownership with Web; do not copy Web handlers into this app.
* Keep the current host until the app's explicit cutover, per the
  [active runtime](https://docs.tuturuuu.com/build/devops/active-runtime.md) rules.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.