---
status: walked + e2e-covered
coverage_depth: deep
last_walked:
  '2026-07-02 by Codex — real-dev-infra Playwright covers proxy 307/404 boundary
  decisions, aaa-creator and operator-admin passing tiers,
  contemplative/malformed/missing-token hard-blocks, the /aaa-upgrade disclosure
  and CTA telemetry, open-redirect sanitisation, mobile standalone rendering,
  real service-worker offline document replay, legacy hub fail-closed gating,
  and the /v1/entitlements/aaa BFF mirror.'
specs:
  - apps/oshun/web/e2e/aaa-upgrade-entitlement-bounce.spec.ts
  - apps/oshun/web/e2e/entitlements-aaa-bff.spec.ts
---

# Journey: AAA upgrade entitlement bounce

A signed-in customer hits an AAA-only generation surface on the contemplative
product (`/studio/isis/<aaa>`). `proxy.ts` runs `resolveStudioBoundary`; if the
user has `aaa-creator` or `operator-admin`, they are redirected with status 307
to `/aaa-upgrade?from=<orig>`; if not, they get a hard 404. This is the
cross-surface handoff from the contemplative route boundary into the Yemaya
Studio surface.

## Personas

- **Contemplative customer with AAA entitlement** — primary path; sees the
  disclosure and chooses Yemaya Studio or returns to the gallery
- **Contemplative customer without AAA** — hard-blocked at the proxy; 404 on any
  direct nav to an AAA-only segment
- **Operator-admin** — also passes the boundary (`operator-admin` is the second
  allowed tier in `resolveStudioBoundary`)
- **Non-AAA customer on a hub page** — the proxy can't see inside an embed;
  `AaaGate` plugs the leak per-embed inside hubs like
  `StudioIsisJobEnvelopeWorkspace`

## Pre-conditions

- User signed-in (`OSHUN_ACCESS_TOKEN_COOKIE_NAME = 'oshun-access'` set)
- `readCustomerStudioEntitlement()` can decode the access token (3-segment prod
  JWT or 2-segment dev token); malformed falls back to `anon`
- Target segment is in `AAA_ONLY_STUDIO_ROUTES` (frozen list in
  `libs/isis/entitlements/src/studio-boundary.ts` — includes `gpu-worker`,
  `lora-training`, `comfyui-nodes`, `model-merging`, `animatediff-lightning`,
  `rife-interpolation`, etc.)
- `resolveGenerationTier(entitlement)` returns one of `contemplative`,
  `curated-creator`, `aaa-creator`, `operator-admin`

## Steps

### 1. Customer attempts a direct nav to an AAA-only surface

- [ ] Customer follows a deep link or types `/studio/isis/gpu-worker`
- [ ] Proxy receives the request; `pathname.startsWith(STUDIO_ISIS_PREFIX)` is
      true; segment extracted via
      `pathname.slice(STUDIO_ISIS_PREFIX.length).split('/')[0]`
- [ ] `AAA_ONLY_STUDIO_ROUTES.includes(segment)` is true
- [ ] Proxy calls `resolveStudioBoundary({ routeSegment, entitlement })`

### 2. Branch A — AAA tier present, render-yemaya-cta

- [ ] Tier is `'aaa-creator'` or `'operator-admin'`
- [ ] `resolveStudioBoundary` returns
      `{ action: 'render-yemaya-cta', disclosureCopy, upgradeUrl: '/aaa-upgrade' }`
- [ ] `applyStudioBoundary` returns `{ kind: 'render-yemaya-cta', upgradeUrl }`
- [ ] `studioBoundaryResponse` builds `NextResponse.redirect(url, 307)` with
      `url.pathname = '/aaa-upgrade'` and
      `url.searchParams.set('from', request.nextUrl.pathname)`
- [ ] Headers: `X-Request-Id: <id>`, `X-Studio-Boundary: aaa-cta`
- [ ] Browser follows the 307 to `/aaa-upgrade?from=/studio/isis/gpu-worker`

### 3. Branch B — no AAA, hard-block-404

- [ ] Tier is `'contemplative'` or `'curated-creator'`
- [ ] `resolveStudioBoundary` returns `{ action: 'hard-block-404' }`
- [ ] `studioBoundaryResponse` rewrites to `/_not-found`
      (`NextResponse.rewrite(notFoundUrl, { status: 404, headers: { 'X-Request-Id', 'X-Studio-Boundary': 'hard-block' } })`)
      so the 404 ships with the root document shell (`<html lang="en">` +
      `<title>`) for WCAG document-title / html-has-lang — not a bare
      `text/plain` "Not Found" body
- [ ] No leakage of `/aaa-upgrade` existence for this segment
- [ ] **Verify**:
      [`studio/domain-bridges/isis/studio-isis-gpu-worker.md`](../studio/domain-bridges/isis/studio-isis-gpu-worker.md)
      gated state (hard-block branch)

### 4. `/aaa-upgrade` renders the handoff (Branch A continuation)

`apps/oshun/web/src/app/aaa-upgrade/page.tsx` is a server async component that
reads `searchParams.from`, sanitises it to `string | null`, and renders
inline-styled chrome (no `ShellLayout`).

- [ ] H1 "This surface lives in Yemaya AAA Studio"
- [ ] Body "Oshun keeps the contemplative product curated — raw graph editors,
      LoRA training, model merging, and the rest of the AAA-tier generation
      surfaces live in Yemaya AAA Studio. Your entitlement includes access."
- [ ] "You arrived from
      <code data-testid="aaa-upgrade-from">/studio/isis/gpu-worker</code>"
      conditional line (only when `from` is a string)
- [ ] Primary Link `data-action="open-yemaya"` → `/studio`; secondary
      `data-action="back-to-gallery"` → `/studio/generation-gallery`
- [ ] `data-testid="aaa-upgrade-page"` on `<main>`
- [ ] **Verify**:
      [`customer/13-system/aaa-upgrade.md`](../customer/13-system/aaa-upgrade.md)
      default + `?from` states; CTA labels; touch targets

### 5. Customer chooses one CTA

- [ ] "Continue in Yemaya Studio" → `/studio` (cross-surface handoff; the
      contemplative product never embeds the AAA generation surface itself)
- [ ] OR "Back to gallery" → `/studio/generation-gallery` (returns to curated
      surface)

### 6. Embedded-leak case — fail-closed legacy route plus `AaaGate` defence-in-depth

The legacy `/studio/isis/*` tree now fail-closes at the route boundary before a
non-AAA user can reach a hub that embeds an AAA workspace. `AaaGate` remains the
component-level defence for any future reachable parent surface where the proxy
cannot see inside the embed.

- [x] Route-level boundary: `/studio/isis/job-envelope` returns the same
      `hard-block` 404 for contemplative users, preserves the URL, and does not
      render an in-place blocked card
- [ ] Component tree: `StudioIsisWorkflowRegistryWorkspace` mounts
      `<AaaGate routeSegment="gpu-worker"><StudioIsisGpuWorkerWorkspace /></AaaGate>`
      (same pattern in Job Envelope, Web, Output Registry, AI Video, etc.)
- [ ] `AaaGate` is server-side; calls `readCustomerStudioEntitlement()` from
      cookies, runs `resolveGenerationTier(entitlement).tier`
- [ ] For `aaa-creator` / `operator-admin`: renders children
- [ ] For `contemplative` / `curated-creator`: renders `AaaBlockedCard`
      (`data-aaa-gate-blocked`, `data-aaa-gate-route-segment="gpu-worker"`) with
      eyebrow "AAA tier required", route-segment label, and a Link to
      `/aaa-upgrade` ("Open the upgrade page")
- [ ] Unknown segments (not in `AAA_ONLY_STUDIO_ROUTES`) render children
      (defensive default)
- [ ] BFF independently enforces entitlement — the gate is UI-only
- [ ] **Verify**:
      [`studio/domain-bridges/isis/studio-isis-gpu-worker.md`](../studio/domain-bridges/isis/studio-isis-gpu-worker.md)
      "AAA-gated parent embedding non-AAA-gated Generation API child" open
      question — captures the inverse case to this one

## Post-conditions

- AAA-tier user: at `/studio` (or back at `/studio/generation-gallery` after
  declining)
- Non-AAA user: at a 404 for direct nav and the legacy hub; any future reachable
  hub embed should show an in-place `AaaBlockedCard`; the user never sees the
  AAA surface or leaked state
- Telemetry: the `/aaa-upgrade` client emits `aaa_upgrade_viewed`,
  `aaa_upgrade_open_yemaya_clicked`, and `aaa_upgrade_back_to_gallery_clicked`;
  the proxy boundary remains observable through `X-Studio-Boundary`

## Failure modes

- **Cookie missing / malformed** — `decodeEntitlement` falls back to `anon`;
  user sees the 404 (correct posture, not the disclosure)
- **Unknown segment wrapped in `AaaGate`** — defensive default renders children
- **Segment removed from `AAA_ONLY_STUDIO_ROUTES`** — proxy + `AaaGate` both
  fall through to render-as-is; verify the bridge change is intentional
- **Open redirect via `?from=`** — `from` renders as plain `<code>` content, no
  `<a href>`; Next escapes; CTAs go to fixed destinations only
- **Cross-domain leak** — entitlement decoder is `import 'server-only'`; never
  reaches client bundles
- **BFF skew** — the gate is UI-only; if BFF rules drift from
  `AAA_ONLY_STUDIO_ROUTES`, a non-AAA user could reach data through the API —
  covered by entitlement-equivalence tests

## E2E coverage

- [`apps/oshun/web/e2e/aaa-upgrade-entitlement-bounce.spec.ts`](../../apps/oshun/web/e2e/aaa-upgrade-entitlement-bounce.spec.ts)
  — steps 1–6: asserts the 307 `render-yemaya-cta` (with
  `X-Studio-Boundary: aaa-cta` and `from=`), the contemplative hard-block 404
  (URL preserved, WCAG doc shell, no `/aaa-upgrade` leak), the disclosure page +
  both CTA destinations, `aaa_upgrade_viewed`, both CTA click telemetry events,
  the fail-closed legacy `/studio/isis/job-envelope` hub denial, and the
  `?from=` open-redirect failure mode (echoed as inert `<code>`, never an
  `<a>`), plus mobile standalone rendering with safe source propagation/no
  overflow/≥44px CTAs and real service-worker offline document replay of the
  warmed `/aaa-upgrade` page.
- [`apps/oshun/web/e2e/entitlements-aaa-bff.spec.ts`](../../apps/oshun/web/e2e/entitlements-aaa-bff.spec.ts)
  — the BFF entitlement resolver **behind** the proxy bounce (the journey's Open
  question #4) over **real HTTP**: `GET /v1/entitlements/aaa` returns entitled
  for `aaa-creator`, bounces `contemplative` toward `/aaa-upgrade`, and `401`s
  without a bearer token — so a drift between the `proxy.ts` boundary and the
  BFF's own enforcement is now detectable.
- **Coverage depth**: deep — see [`coverage.md`](./coverage.md).
- **Uncovered**: proxy-side structured access-log telemetry for redirect / 404
  decisions remains a backend observability gap; the browser `aaa_upgrade_*`
  telemetry is covered. The `operator-admin` second-tier path is asserted via
  tier-equivalence in the boundary unit tests rather than re-driven here (same
  `render-yemaya-cta` outcome as `aaa-creator`).

## Per-view files touched

- [`customer/13-system/aaa-upgrade.md`](../customer/13-system/aaa-upgrade.md)
- [`studio/domain-bridges/isis/studio-isis-gpu-worker.md`](../studio/domain-bridges/isis/studio-isis-gpu-worker.md)

## Cross-references

- `V1/features.md` § Crypto Payments — Non-Custodial Entitlement Settlement
  (upstream path that grants the AAA tag in the first place)
- `V1/ARCHITECTURE.md` § Security, Privacy, and Compliance (entitlements
  interlock with `DomainAuthPolicy.scopes`)
- `apps/oshun/web/src/proxy.ts` — `applyStudioBoundary`,
  `studioBoundaryResponse`
- `apps/oshun/web/src/lib/server/studio-entitlement.ts` —
  `readCustomerStudioEntitlement` (server-only token decoder)
- `libs/isis/entitlements/src/studio-boundary.ts` — `AAA_ONLY_STUDIO_ROUTES`,
  `isAaaOnlyRoute`, `resolveStudioBoundary`, `StudioBoundaryOutcome`
- `apps/oshun/web/src/components/studio/AaaGate.tsx` — defence-in-depth embed
  gate
- `apps/oshun/web/src/app/aaa-upgrade/page.tsx` — disclosure page source
- Sibling journeys: [`sign-up-and-pay-crypto.md`](./sign-up-and-pay-crypto.md)
  (how the user got the AAA tag),
  [`nyx-to-tara-bridge.md`](./nyx-to-tara-bridge.md) (sibling cross-domain
  handoff pattern)

## Open questions / known gaps

- [x] `/aaa-upgrade` emits `aaa_upgrade_viewed`,
      `aaa_upgrade_open_yemaya_clicked`, and
      `aaa_upgrade_back_to_gallery_clicked`; the Playwright journey captures all
      three events and verifies their safe source/destination payloads
- [ ] Proxy has no telemetry on the redirect / 404 path itself; response headers
      (`X-Studio-Boundary`) are the only signal; consider a structured
      access-log event
- [x] Page chrome uses raw inline styles, not Lilith design-system primitives;
      keyboard focus outlines, ≥44px CTA targets, reduced-motion static state,
      mobile standalone containment, and offline replay are now covered by the
      route-level Playwright assertions. The design-system primitive migration
      remains a visual-design gap, not an unverified interaction.
- [x] Document the BFF-side entitlement check that mirrors
      `AAA_ONLY_STUDIO_ROUTES`, so a drift between proxy + BFF is detectable
      (the comment in `AaaGate.tsx` says "The BFF still independently enforces
      entitlement on every request" — `entitlements-aaa-bff.spec.ts` captures
      the real HTTP resolver)
- [x] Per-view file flags "AAA-gated parent embedding non-AAA-gated Generation
      API child — confirm intended UX"; the embedded-leak `AaaGate` pattern is
      the inverse (non-AAA parent embedding AAA child). The journey now
      documents the fail-closed legacy hub behavior and the real spec asserts no
      embedded blocked card leaks after route-level denial.
