# Shell: Routing and layouts

Source: `apps/oshun/admin/src/middleware.ts`,
`apps/oshun/admin/src/app/error.tsx`, `apps/oshun/admin/src/app/loading.tsx`,
`apps/oshun/admin/src/app/not-found.tsx`,
`apps/oshun/admin/src/lib/session-cookie.ts`

How the admin app handles routes, public paths, rate limiting, and the
request-id stamp that flows from middleware into every page and audit entry.

## Middleware composition

`apps/oshun/admin/src/middleware.ts` runs on every request matched by:

```
/((?!_next/static|_next/image|favicon\.ico|.*\.(?:svg|png|jpg|jpeg|gif|webp|ico|woff|woff2|ttf|eot)$).*)
```

Order of operations:

1. Generate a `requestId` (timestamp + random suffix)
2. Resolve client IP (`x-forwarded-for` first hop, then `x-real-ip`, fallback
   `127.0.0.1`)
3. Rate-limit check (production only): 120 requests / 60 s window / IP
4. Public-path check
5. Session-cookie check (for non-public paths)
6. Forward request with `x-request-id` header; attach `X-Request-Id` to response

## Public paths

```ts
const PUBLIC_PATHS = new Set<string>(['/unauthorized', '/handoff']);
const PUBLIC_PREFIXES = ['/_next', '/api', '/icons', '/images', '/favicon'];
```

- [ ] `/unauthorized` — auth-denial UI; takes `reason` and `returnTo` query
      params
- [ ] `/handoff` — privileged handoff from consumer to admin session
- [ ] `/_next/*`, `/api/*`, `/icons/*`, `/images/*`, `/favicon*` — never
      auth-gated

Any other path requires `OSHUN_ADMIN_SESSION_COOKIE_NAME` cookie that parses
cleanly via `parseAdminSessionToken`. Missing or invalid → redirect to
`/unauthorized?reason=<missing-session|invalid-session>&returnTo=<original>`;
invalid token also `cookies.delete(OSHUN_ADMIN_SESSION_COOKIE_NAME)` on the
response.

## Rate limiting

In-memory `rateLimitMap` keyed by IP.

- [ ] **Threshold** — 120 requests per 60 s
- [ ] **Local dev exemption** —
      `process.env.NODE_ENV !== 'production' ||     hostname === 'localhost' || hostname === '127.0.0.1'`
- [ ] **Response when limited** — `429 Too Many Requests` with `Retry-After: 60`
      and `X-Request-Id` header
- [ ] **Window reset** — first request after `resetAt` starts new window

In-memory store note: rate-limit state is per-instance. In multi-replica
deploys, this is local to the replica. Open question worth flagging in ops.

## Request ID

Every request gets `req_<timestamp>_<random8>`. The ID flows three places:

- [ ] Stamped on the response (`X-Request-Id` header)
- [ ] Forwarded to the page via `x-request-id` request header (read with
      `headers()` in server components — see `handoff/page.tsx` line:
      `hdrs.get('x-request-id')`)
- [ ] Logged (verify telemetry pipeline)

Used for log correlation, audit trail joining, and on-screen support references.

## Route groups

The admin app does not use Next.js route groups — every segment is a real URL
segment.

## Catch-all routes

None. Every route is explicit. Unknown paths → `not-found.tsx` (default Next 404
behavior).

## Special files

- [ ] `app/error.tsx` — segment-level error boundary; `'use client'`; shows
      heading "Something broke inside the admin shell", body with
      `error.digest`, "Retry workspace" button calls `reset()`
- [ ] `app/loading.tsx` — global loading; `<main role="main">` with
      `aria-live="polite"` "Loading operations cockpit…"; no skeleton
- [ ] `app/not-found.tsx` — workspace-not-found UI; h1 "Workspace not found";
      link back to `/`; `metadata.title = 'Workspace not found'`
- [ ] No `app/global-error.tsx` — `error.tsx` is the only error UI

## Sub-route patterns

Some routes have a single child:

| Parent            | Child(ren)                                                                                                                                                                                  |
| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `/review`         | `/review/[reviewId]`                                                                                                                                                                        |
| `/trust-safety`   | `/trust-safety/voice-abuse`                                                                                                                                                                 |
| `/isis`           | `/isis/civitai-intake`, `/isis/comfy-nodes`, `/isis/lora-training`, `/isis/model-merging`, `/isis/output-gallery`, `/isis/runpod-endpoints`, `/isis/voice-cloning`, `/isis/workflow-editor` |
| `/messaging`      | `/messaging/telegram-channels`                                                                                                                                                              |
| `/tenant-console` | `/tenant-console/living-scenes`                                                                                                                                                             |
| `/__test`         | `/__test/v1-aweb-104`                                                                                                                                                                       |

No parent index pages for `/isis`, `/messaging`, `/tenant-console`, `/__test` —
only the named children. Direct visits to those parents hit `not-found.tsx`.

## States

- [ ] **Anonymous on a gated path** — middleware redirects to
      `/unauthorized?reason=missing-session&returnTo=<path>`
- [ ] **Invalid session token** — middleware deletes cookie and redirects to
      `/unauthorized?reason=invalid-session&returnTo=<path>`
- [ ] **Rate limited** — 429 page with `Retry-After: 60` and the request ID
- [ ] **Valid session** — request continues to the page; `x-request-id` header
      propagates
- [ ] **Public path (`/unauthorized`, `/handoff`)** — no session check; page
      renders for anyone (with its own internal logic re: customer session for
      `/handoff`)
- [ ] **404 on unknown route** — `not-found.tsx`; preserves admin shell chrome?
      No — global not-found.tsx; verify visually
- [ ] **Error in page** — `error.tsx` catches; "Retry workspace" button

## Cross-references

- [01-app-shell.md](./01-app-shell.md) — AdminShell mounts inside each page
  after middleware passes
- [03-auth-session.md](./03-auth-session.md) — the session cookie this
  middleware validates
- Session cookie module: `apps/oshun/admin/src/lib/session-cookie.ts`

## Open questions / known gaps

- [ ] Document the exact format of admin session tokens (the
      `parseAdminSessionToken` return shape lives in `lib/session-cookie.ts`)
- [ ] Confirm rate-limit storage strategy in production — in-memory per-replica
      is a known limitation
- [ ] Verify `not-found.tsx` doesn't leak the admin shell to unauthenticated
      visitors (it's not in PUBLIC_PATHS, so middleware should bounce them
      first)
- [ ] Document the `x-request-id` propagation — which BFF calls forward it for
      audit-trail joining?
