# EventHub Monorepo Migration Plan

> Trạng thái: **Architecture proposal** — chưa thay đổi source code.  
> Cập nhật: 2026-10-15.  
> Phạm vi: chuyển EventHub hiện tại thành npm Workspaces gồm hai ứng dụng Nuxt độc lập: `web` và `console`.
>
> Nguồn:
> - Source EventHub hiện tại.
> - [`monorepo-migration-proposal.md`](./monorepo-migration-proposal.md).
> - [`monorepo-target-architecture.md`](./monorepo-target-architecture.md).
> - [`2026-10-13-technical-seed-next-monorepo-anatomy.md`](../research/2026-10-13-technical-seed-next-monorepo-anatomy.md).

---

## 0. Executive summary

Kiến trúc đích được đề xuất:

- Một repository, quản lý bằng **npm Workspaces**.
- `apps/web`: EventHub public hiện tại, Nuxt 3, `ssr: true`, không còn route hoặc dependency của Builder.
- `apps/console`: Event Builder nội bộ, `ssr: false`, đích cuối là Nuxt 4 và static output.
- Mỗi app sở hữu `package.json`, `nuxt.config.ts`, source, test, Docker/hosting contract và artifact riêng.
- Toàn workspace dùng **một `package-lock.json` ở root**.
- `apps/web` tạo Nitro Node artifact tại `apps/web/.output/`.
- `apps/console` chạy `nuxi generate`, tạo static artifact tại `apps/console/.output/public/`.
- Hai app có pipeline, release version, deploy target và rollback độc lập.
- Chỉ extract shared package khi cả Web và Console thực sự dùng và có ownership rõ ràng. Shared boundary đầu tiên có bằng chứng rõ nhất là event-page runtime, đặc biệt `EventPageRenderer`, model/validation và preview protocol.
- Migration đầu tiên không nâng Web lên Nuxt 4. Builder nên được tách và kiểm chứng độc lập trên Nuxt 3 trước; việc nâng riêng Console lên Nuxt 4 là một phase có quality gate riêng.

### Quyết định kiến trúc

| Chủ đề | Quyết định đề xuất |
|---|---|
| Monorepo tool | npm Workspaces; chưa cần Nx hoặc Turborepo |
| App structure | `apps/web` + `apps/console` |
| Dependency declaration | Mỗi app/package có `package.json` riêng |
| Lockfile | Một `package-lock.json` tại root |
| Web | Nuxt 3, SSR, Nitro Node server |
| Console | Tách trên Nuxt 3 trước, sau đó nâng riêng lên Nuxt 4; CSR với `ssr: false` |
| Console output | Static output bằng `nuxt generate` |
| Web output | Node artifact bằng `nuxt build` |
| Shared code | Extract theo nhu cầu thực tế, không tạo một package `common` tổng hợp ngay từ đầu |
| CI/CD | Hai pipeline release độc lập; shared package change có thể trigger cả hai |

### Điều chỉnh so với yêu cầu ban đầu

`package-lock.json` riêng cho từng app mâu thuẫn với mô hình npm Workspaces chuẩn:

- `npm ci` tại root dùng một dependency graph và một lockfile.
- Mỗi app vẫn sở hữu dependency trực tiếp qua `apps/<app>/package.json`.
- Root lockfile có thể chứa đồng thời Nuxt 3 của Web và Nuxt 4 của Console.
- Nếu mỗi app bắt buộc có lockfile riêng và cài đặt riêng, nên dùng hai repository hoặc bỏ workspace relationship; không nên gọi đó là một npm workspace thống nhất.

---

## 1. Objective & scope

### 1.1 Background

EventHub hiện là một Nuxt application duy nhất:

- Root app phục vụ public EventHub.
- `layers/collab-event` và `layers/promotion` cung cấp public routes và event runtime.
- `layers/event-builder` cung cấp route `/builder`.
- Builder chỉ được đăng ký với các `RUN_TYPE` `dev`, `dev2`, `qa2`.
- Web và Builder hiện dùng chung Nuxt config, dependency graph, plugin, middleware, runtime config và một `.output`.

Cấu trúc này làm hai product có cùng release boundary dù mục đích vận hành khác nhau:

- Web là public product cần SSR, SEO, Nitro server và độ ổn định production cao.
- Console là internal authoring tool, ưu tiên CSR, access control và release độc lập.

### 1.2 Objective

Migration phải đạt các kết quả:

1. Tạo một npm workspace cài đặt được bằng một lệnh từ root.
2. Tách Web và Console thành hai Nuxt app độc lập.
3. Mỗi app có config, dependency, build command và artifact riêng.
4. Web giữ nguyên hành vi SSR và public routes, ngoại trừ Builder.
5. Console sở hữu toàn bộ Event Builder.
6. Hai app có thể build, deploy và rollback độc lập.
7. Shared code có dependency direction rõ ràng, không import chéo giữa hai app.

### 1.3 In scope

- Thay đổi layout repository.
- Tách `package.json` và Nuxt config theo app.
- Tách artifact và pipeline.
- Di chuyển Builder sang Console.
- Extract event runtime thực sự được cả hai app dùng.
- Cập nhật alias, auto-import, tests, Docker context và CI path rules.
- Thiết lập Console `ssr: false` và static output.
- Nâng riêng Console lên Nuxt 4 sau khi đã tách thành công.

### 1.4 Out of scope

- Không nâng Web lên Nuxt 4 trong migration này.
- Không refactor business logic không liên quan.
- Không đưa Nx/Turborepo vào giai đoạn đầu.
- Không chuyển toàn bộ code hiện tại thành shared packages.
- Không thay đổi API contract nếu không cần cho auth, CORS hoặc authorization của Console.
- Không gộp release version của Web và Console.

---

## 2. Current architecture

### 2.1 Cấu trúc hiện tại

```text
event-hub/
├── nuxt.config.ts
├── package.json
├── package-lock.json
├── Dockerfile
├── pages/ layouts/ components/
├── composables/ stores/ services/ apis/ utils/
├── plugins/ middleware/ server/
├── configs/
│   └── runtime/run-type/
├── layers/
│   ├── collab-event/
│   ├── promotion/
│   └── event-builder/
├── public/ markup/ tests/
└── .output/                     # một artifact cho toàn ứng dụng
```

### 2.2 Nuxt composition hiện tại

Root `nuxt.config.ts` đang:

- Cấu hình `ssr: true`.
- Luôn extend `collab-event` và `promotion`.
- Chỉ extend `event-builder` khi `RUN_TYPE` thuộc `dev`, `dev2`, `qa2`.
- Dùng chung modules, aliases, i18n, runtime config, Sentry, Vite, sitemap, robots và server config.

```text
Root Nuxt app
├── collab-event layer
├── promotion layer
└── event-builder layer           # chỉ dev/dev2/qa2
```

Vì Builder là một layer của Web, nó hiện thừa hưởng:

- Web `app.vue` và layout behavior.
- Global middleware.
- Auth/plugin của Web.
- Public runtime config.
- Root aliases.
- Toàn bộ dependency của Web.
- Cùng build artifact và release lifecycle.

### 2.3 Build và artifact hiện tại

Các build script hiện tại đều chạy tại root:

```bash
npm run build:dev
npm run build:dev2
npm run build:qa
npm run build:qa2
npm run build:sandbox
npm run build:live
```

Tất cả gọi `nuxt build` với `RUN_TYPE` tương ứng và ghi:

```text
.output/
├── public/
├── server/
│   └── index.mjs
└── nitro.json
```

Dockerfile hiện tại:

- Dùng Node `20.11.1`.
- Copy root `.output/`.
- Chạy `node server/index.mjs`.

Repository không chứa Jenkinsfile, `.gitlab-ci.yml`, Helm chart hoặc Kubernetes manifest. Pipeline Web thực tế nằm ngoài repository và phải được Infra cung cấp trước khi cutover.

### 2.4 Coupling cần xử lý

Research hiện tại ghi nhận:

- `layers/promotion`: 79 file import root qua `@/`.
- `layers/collab-event`: 14 file import root qua `@/`.
- `layers/event-builder`: 13 file import root qua `@/`.
- Builder import `@promotion/entities/event-page` với mật độ rất cao.
- Builder dùng `@promotion/shared/lib`, `@promotion/shared/ui`.
- Codegen của Builder sinh import dành cho Web.
- Preview iframe hiện trỏ tới public promotion route và giả định same-origin.

Các dependency Builder cần phân loại:

```text
Event Builder
├── Builder-only
│   ├── canvas
│   ├── inspector
│   ├── workflow
│   ├── persistence UI
│   └── code generation UI
├── Shared event runtime
│   ├── event-page model/validation
│   ├── EventPageRenderer
│   ├── preview messaging
│   ├── stage policy
│   └── event config helpers
└── Platform dependencies
    ├── useRequest / API client
    ├── auth
    ├── runtime config
    ├── alert/tooltip
    └── shared constants/types
```

### 2.5 Current deployment view

```mermaid
flowchart LR
  Source["Một source tree"] --> Build["Một Nuxt build"]
  Build --> Artifact["Một .output"]
  Artifact --> Image["Một Node image"]
  Image --> Runtime["Một EventHub instance"]
  Runtime --> Public["Public routes"]
  Runtime --> Builder["/builder ở một số RUN_TYPE"]
```

### 2.6 Những điểm phải giữ nguyên cho Web

- Public route list, trừ Builder.
- SSR behavior và hydration.
- Locale redirect.
- Runtime config theo `RUN_TYPE`.
- Sentry behavior.
- `.well-known` assets.
- API/auth behavior.
- Nitro start command.
- Existing environment matrix.
- Markup mode nếu team vẫn sử dụng.

---

## 3. Target architecture

### 3.1 Directory structure

```text
event-hub/
├── apps/
│   ├── web/                                  # @event-hub/web
│   │   ├── package.json
│   │   ├── nuxt.config.ts                    # Nuxt 3, ssr:true
│   │   ├── app.vue
│   │   ├── error.vue
│   │   ├── configs/
│   │   ├── pages/
│   │   ├── layouts/
│   │   ├── components/
│   │   ├── composables/
│   │   ├── stores/
│   │   ├── services/
│   │   ├── apis/
│   │   ├── plugins/
│   │   ├── middleware/
│   │   ├── server/
│   │   ├── public/
│   │   ├── markup/
│   │   ├── layers/
│   │   │   ├── promotion/                    # Web-only local layer
│   │   │   └── collab-event/                 # Web-only local layer
│   │   ├── tests/
│   │   └── Dockerfile                        # Node/Nitro image
│   │
│   └── console/                              # @event-hub/console
│       ├── package.json
│       ├── nuxt.config.ts                    # ssr:false
│       ├── app.vue
│       ├── configs/
│       ├── pages/
│       │   ├── index.vue
│       │   ├── builder/index.vue
│       │   └── preview/[slug].vue
│       ├── layouts/
│       ├── plugins/
│       ├── middleware/
│       ├── layers/
│       │   └── event-builder/                # Console-only local layer
│       ├── public/
│       ├── tests/
│       ├── deploy/
│       │   └── nginx.conf
│       └── Dockerfile                        # static image, nếu deploy bằng container
│
├── packages/
│   ├── nuxt-layers/
│   │   └── event-runtime/                    # chỉ code thực sự dùng bởi cả hai app
│   │       ├── package.json
│   │       ├── nuxt.config.ts
│   │       ├── entities/event-page/
│   │       ├── widgets/event-renderer/
│   │       ├── shared/lib/
│   │       ├── shared/ui/
│   │       └── config/events/
│   ├── event-contract/                       # optional: TS thuần sau khi boundary ổn định
│   │   ├── package.json
│   │   └── src/
│   └── configs/                              # optional: eslint/tsconfig/tailwind khi có lợi ích rõ
│
├── scripts/
│   └── ci/
├── docs/
├── patches/
├── package.json                              # workspace orchestration
├── package-lock.json                         # lockfile duy nhất
├── .npmrc
├── .nvmrc
└── .gitlab-ci.yml                            # nếu Console dùng GitLab CI
```

### 3.2 Ownership boundary

#### `apps/web`

Sở hữu:

- Public pages và layouts.
- `promotion` và `collab-event`.
- Web-only plugins: launcher, mobile SDK, ads, SEO, sitemap, robots.
- Nitro server, `.well-known`, Sentry server.
- Public Web Docker image và deployment.

Không được:

- Import source từ `apps/console`.
- Đăng ký `event-builder`.
- Chứa route `/builder`.

#### `apps/console`

Sở hữu:

- Builder page, layout, canvas, inspector, workflow và persistence UX.
- Console auth guard và access policy.
- Console runtime config.
- Same-origin preview shell.
- Static hosting config và Console release.

Không được:

- Import source từ `apps/web`.
- Kế thừa Web app hoặc Web-only plugins.
- Dựa vào route guard phía client như lớp authorization duy nhất.

#### `packages/nuxt-layers/event-runtime`

Chỉ chứa code có ít nhất hai consumer:

- `EventPageRenderer`.
- Event page model, validation và stage policy.
- Preview messaging contract.
- Helper/UI primitive gắn trực tiếp với event runtime.
- Event config dùng cho render/preview.

Không dùng `@/` hoặc `~/`, vì các alias này trỏ về app consumer. Import nội bộ phải dùng package name hoặc alias riêng của layer.

#### `packages/event-contract`

Chỉ tạo khi model thuần đã tách khỏi Nuxt/Vue:

- TypeScript types.
- Validation.
- Factory.
- Stage policy.
- Preview message types.

Không chứa component, composable, `useRuntimeConfig` hoặc Nuxt auto-import.

### 3.3 Vì sao chọn cấu trúc này

#### So với giữ Builder trong Web

- Loại Builder khỏi public artifact.
- Không buộc Console theo release cadence của Web.
- Giảm blast radius khi thay đổi authoring tool.
- Có thể nâng Console lên Nuxt 4 mà không chạm Web.
- Rõ trách nhiệm vận hành và access control.

#### So với tách thành repository riêng

- Có thể thay đổi event contract và hai consumer trong cùng một MR.
- Một lockfile giúp dependency graph có thể tái tạo.
- Dùng lại code runtime mà không phải publish package lên registry.
- CI có thể xác định app bị ảnh hưởng từ dependency graph.
- Dễ thực hiện migration từng phase hơn.

#### Vì sao chưa cần Nx/Turborepo

- Chỉ có hai app và một số ít shared packages.
- npm Workspaces đã giải quyết install và package linking.
- Build của mỗi app đã độc lập.
- Path-based CI đủ cho giai đoạn đầu.
- Có thể bổ sung task runner sau nếu build time, caching hoặc graph orchestration trở thành vấn đề thực tế.

### 3.4 Dependency direction

```mermaid
flowchart TD
  Web["@event-hub/web<br/>Nuxt 3 SSR"]
  Console["@event-hub/console<br/>Nuxt 4 CSR"]
  Runtime["@event-hub/event-runtime<br/>Nuxt layer"]
  Contract["@event-hub/event-contract<br/>TS thuần, optional"]

  Web --> Runtime
  Console --> Runtime
  Runtime --> Contract
```

Quy tắc:

- `web` và `console` không import lẫn nhau.
- Package shared không import app.
- Builder là Console-only, nên không cần biến thành shared package.
- Promotion và Collab là Web-only, nên ở trong `apps/web/layers`.
- Dependency phải được khai báo trong `package.json` của đúng consumer; không dựa vào npm hoisting.

### 3.5 Nuxt version strategy

Đích cuối:

- Web: giữ Nuxt 3.
- Console: Nuxt 4.

Thứ tự migration được khuyến nghị:

1. Tạo Console tối thiểu và tách Builder trên cùng Nuxt major với Web.
2. Chứng minh Builder build/chạy độc lập.
3. Cắt Builder khỏi Web.
4. Nâng riêng Console lên Nuxt 4.

Lý do không nâng Console đồng thời với lần move Builder:

- Nếu build hỏng, có thể phân biệt lỗi do move source với lỗi do Nuxt major.
- Shared layer có thời gian để loại bỏ `@/`, auto-import ngầm và Nuxt 3-only behavior.
- Rollback đơn giản hơn.

Nếu team quyết định tạo Console trực tiếp bằng Nuxt 4, phải có một compatibility spike trước khi migrate Builder và không được tính spike là bằng chứng production readiness.

### 3.6 Lockfile và version policy

Root workspace:

```jsonc
{
  "name": "event-hub-monorepo",
  "private": true,
  "workspaces": [
    "apps/*",
    "packages/*",
    "packages/nuxt-layers/*",
    "packages/configs/*"
  ]
}
```

Quy tắc:

- Một `package-lock.json` ở root.
- `npm ci` chỉ chạy từ root.
- Mỗi app pin exact Nuxt major/version của chính nó trong app `package.json`.
- Không dùng root `overrides` để ép Nuxt 3 và Nuxt 4 thành một version.
- Shared package không khai báo Nuxt/Vue là runtime dependency nếu không cần; dùng peer dependency có range đã được kiểm thử.
- Sau khi Console lên Nuxt 4, CI phải build shared runtime với cả hai app.
- `patch-package` của Nuxt 3 cần một CI assertion riêng vì npm có thể hoist/nest khác sau khi thêm Nuxt 4.

---

## 4. Build, artifact & runtime

### 4.1 Root commands

Root `package.json` chỉ orchestration:

```jsonc
{
  "scripts": {
    "dev:web": "npm run serve --workspace=@event-hub/web",
    "dev:console": "npm run serve --workspace=@event-hub/console",

    "build:web:dev": "npm run build:dev --workspace=@event-hub/web",
    "build:web:qa": "npm run build:qa --workspace=@event-hub/web",
    "build:web:sandbox": "npm run build:sandbox --workspace=@event-hub/web",
    "build:web:live": "npm run build:live --workspace=@event-hub/web",

    "build:console:dev": "npm run build:dev --workspace=@event-hub/console",
    "build:console:qa": "npm run build:qa --workspace=@event-hub/console",
    "build:console:live": "npm run build:live --workspace=@event-hub/console",

    "build:all:dev": "npm run build:web:dev && npm run build:console:dev",
    "lint": "npm run lint --workspaces --if-present",
    "typecheck": "npm run typecheck --workspaces --if-present",
    "test": "npm run test --workspaces --if-present"
  }
}
```

`build:all:*` chỉ dùng cho local validation hoặc quality gate. Release pipeline phải gọi build của từng app riêng.

### 4.2 Web build

`apps/web/package.json` giữ script hiện tại:

```jsonc
{
  "name": "@event-hub/web",
  "private": true,
  "scripts": {
    "serve": "cross-env RUN_TYPE=dev nuxt dev",
    "build:dev": "cross-env NODE_OPTIONS=--max-old-space-size=4096 BUILD_VERSION=$BUILD_VERSION RUN_TYPE=dev SENTRY=off nuxt build",
    "build:qa": "cross-env NODE_OPTIONS=--max-old-space-size=4096 BUILD_VERSION=$BUILD_VERSION RUN_TYPE=qa SENTRY=off nuxt build",
    "build:sandbox": "cross-env NODE_OPTIONS=--max-old-space-size=4096 BUILD_VERSION=$BUILD_VERSION RUN_TYPE=sandbox SENTRY=on nuxt build",
    "build:live": "cross-env NODE_OPTIONS=--max-old-space-size=4096 BUILD_VERSION=$BUILD_VERSION RUN_TYPE=live SENTRY=on nuxt build"
  }
}
```

Build:

```bash
npm ci
npm run build:web:live
```

Artifact:

```text
apps/web/.output/
├── nitro.json
├── public/
└── server/
    └── index.mjs
```

Runtime:

```bash
node apps/web/.output/server/index.mjs
```

### 4.3 Console build

`apps/console/package.json`:

```jsonc
{
  "name": "@event-hub/console",
  "private": true,
  "scripts": {
    "serve": "cross-env RUN_TYPE=dev nuxt dev",
    "build:dev": "cross-env RUN_TYPE=dev nuxi generate",
    "build:qa": "cross-env RUN_TYPE=qa nuxi generate",
    "build:live": "cross-env RUN_TYPE=live nuxi generate"
  }
}
```

`apps/console/nuxt.config.ts`:

```ts
export default defineNuxtConfig({
  ssr: false,
  extends: ['./layers/event-builder', '@event-hub/event-runtime'],
  app: {
    head: {
      meta: [{ name: 'robots', content: 'noindex, nofollow' }]
    }
  }
});
```

Build:

```bash
npm ci
npm run build:console:dev
```

Artifact:

```text
apps/console/.output/public/
├── index.html
├── 200.html
├── 404.html
├── _nuxt/
└── assets copied from public/
```

Console không có Node/Nitro runtime sau deploy. Static server phải có SPA fallback:

```nginx
location / {
  try_files $uri $uri/ /200.html;
}
```

### 4.4 Hai instance, hai artifact

```mermaid
flowchart LR
  Install["npm ci<br/>root package-lock"] --> WebBuild["build @event-hub/web"]
  Install --> ConsoleBuild["build @event-hub/console"]

  WebBuild --> WebArtifact["apps/web/.output<br/>Nitro Node artifact"]
  ConsoleBuild --> ConsoleArtifact["apps/console/.output/public<br/>static artifact"]

  WebArtifact --> WebImage["Web Node image"]
  ConsoleArtifact --> ConsoleImage["Console Nginx/static image"]

  WebImage --> WebDeploy["Public Web deployment"]
  ConsoleImage --> ConsoleDeploy["Internal Console deployment"]
```

Mỗi artifact phải có:

- App name.
- Commit SHA.
- Build environment.
- Build timestamp.
- Version/release tag.
- Sentry release nếu áp dụng.

### 4.5 Temporary Jenkins compatibility

Trong phase đầu, có thể giữ nguyên Jenkins command hiện tại:

```bash
npm run build:live
```

Root script proxy sang Web rồi copy artifact về root:

```jsonc
{
  "scripts": {
    "build:live": "npm run build:live --workspace=@event-hub/web && node scripts/ci/sync-web-output.mjs"
  }
}
```

`sync-web-output.mjs` phải copy bảo toàn symlink:

```text
apps/web/.output/ → ./.output/
```

Sau một release cycle ổn định, Jenkins chuyển sang native:

```bash
npm ci
npm run build:live --workspace=@event-hub/web
docker build -f apps/web/Dockerfile apps/web
```

Khi đó bỏ root `.output`, compatibility script và root Dockerfile.

---

## 5. Migration strategy

### Phase 0 — Baseline và quyết định

Mục tiêu: có bằng chứng trước khi move source.

Actions:

1. Lấy Jenkins job/pipeline thật từ Infra.
2. Ghi lại install command, build command, Docker context, runtime env và probe.
3. Build baseline cho `dev`, `qa`, `sandbox`, `live`.
4. Lưu route list, `nitro.json`, bundle/chunk list và kích thước artifact.
5. Chốt Console domain, auth, CORS, preview và static hosting.
6. Chốt Nuxt 4 timing cho Console.
7. Commit các script verify cần dùng; hiện `scripts/` đang bị `.gitignore`.

Exit criteria:

- Baseline có thể chạy lại.
- Infra xác nhận pipeline hiện tại.
- Các quyết định security/runtime của Console có owner.

### Phase 1 — Setup npm workspace

Mục tiêu: đổi repository layout nhưng chưa migrate Builder behavior.

Actions:

1. Thêm root workspaces.
2. Tạo `apps/web/package.json`.
3. Move root Nuxt app vào `apps/web`.
4. Move `promotion` và `collab-event` vào `apps/web/layers`.
5. Giữ Builder tạm thời ở vị trí có thể được Web consume trong transition.
6. Tạo `apps/console` tối thiểu, chưa có Builder.
7. Tạo lại root `package-lock.json`.
8. Pin dependency để tránh nâng version ngoài ý muốn.
9. Thêm root build proxy để Jenkins chưa phải đổi.

Exit criteria:

- `npm ci` tại root thành công.
- Web artifact parity với baseline.
- Console tối thiểu build static độc lập.
- Jenkins Web chạy mà không đổi deployment behavior.

Rollback:

- Revert workspace/move MRs.
- Jenkins tiếp tục dùng layout cũ.

### Phase 2 — Independent build validation

Mục tiêu: chứng minh hai app có build boundary thật.

Actions:

1. Chạy Web build cho toàn bộ `RUN_TYPE`.
2. Chạy Console build từ clean install.
3. Kiểm tra output path không đè nhau.
4. Kiểm tra dependency của Console không xuất hiện trong Web artifact.
5. Kiểm tra build Web không cần source Console.
6. Kiểm tra Docker build context mới.
7. Thêm quality gate cho package boundaries.

Exit criteria:

- Hai build có thể chạy riêng và chạy đồng thời.
- Xóa `.output` của app này không ảnh hưởng app kia.
- Web SSR smoke test pass.
- Console static hosting smoke test pass với deep-link fallback.

### Phase 3 — Extract shared event runtime

Mục tiêu: tạo shared boundary tối thiểu đủ để Builder và Web cùng render event.

Actions:

1. Tạo `packages/nuxt-layers/event-runtime`.
2. Move `EventPageRenderer`.
3. Move event-page model, validation, stage policy và preview contract cần cho cả hai.
4. Đổi import khỏi `@promotion/*` sang package name.
5. Loại `@/` và `~/` khỏi shared package.
6. Chỉ tạo `event-contract` nếu phần model đã thực sự framework-agnostic.
7. Giữ Web behavior không đổi.

Exit criteria:

- Web render public event qua shared runtime.
- Console preview render cùng runtime.
- Shared package không import app.
- Shared package build được trong cả Web và Console.

### Phase 4 — Migrate Builder sang Console

Mục tiêu: Console sở hữu Builder nhưng Web vẫn có fallback tạm thời nếu cần.

Actions:

1. Move `layers/event-builder` vào `apps/console/layers/event-builder`.
2. Tách Console plugins, config và auth.
3. Sửa alias và auto-import.
4. Di chuyển Builder tests.
5. Thêm same-origin preview route trong Console.
6. Cập nhật codegen contract.
7. Deploy Console dev/QA.

Exit criteria:

- Builder load/list/create/save/promote theo acceptance flow.
- Preview desktop/mobile hoạt động.
- Không import source từ `apps/web`.
- Console artifact chỉ là static output.

### Phase 5 — Upgrade Console lên Nuxt 4

Mục tiêu: nâng riêng Console, không thay đổi Web.

Actions:

1. Pin Nuxt 4 và dependency tương thích trong Console.
2. Cập nhật directory semantics hoặc `srcDir` tường minh.
3. Kiểm tra shared event runtime trên Nuxt 3 và Nuxt 4.
4. Sửa API deprecated và auto-import khác biệt.
5. Regenerate root lockfile.
6. Chạy full Builder acceptance test.

Exit criteria:

- Web vẫn build bằng Nuxt 3.
- Console build static bằng Nuxt 4.
- Root install chứa đúng hai dependency trees.
- Shared runtime pass test trên cả hai app.

Rollback:

- Revert riêng Console dependency/config về Nuxt 3.
- Không rollback Web.

### Phase 6 — Cut Builder khỏi Web

Actions:

1. Xóa `SUPPORTED_BUILDER_ENABLED_RUN_TYPES`.
2. Xóa Builder khỏi Web `extends`.
3. Xóa alias và dependency Builder khỏi Web.
4. Assert Web route manifest không có `/builder`.
5. Thông báo URL Console mới cho Product/QA/Content.

Exit criteria:

- Web artifact không còn Builder source/chunk/route.
- Console đã được dùng ổn định ít nhất một sprint hoặc release window đã thống nhất.

### Phase 7 — Native CI/CD và cleanup

Actions:

1. Jenkins build trực tiếp `apps/web`.
2. Console dùng pipeline riêng.
3. Xóa compatibility artifact copy.
4. Thêm affected path rules.
5. Cập nhật AGENTS/CLAUDE/rules/docs theo layout mới.
6. Thiết lập release tag riêng, ví dụ `web@x.y.z`, `console@x.y.z`.

Exit criteria:

- Hai pipeline release độc lập.
- Mỗi app rollback độc lập.
- Shared change trigger đúng consumer.

---

## 6. Impact & risk assessment

### 6.1 Source code impact

Mức ảnh hưởng: **cao trong migration, thấp sau khi ổn định**.

Thay đổi:

- Hầu hết root path chuyển xuống `apps/web`.
- Relative path trong Nuxt config fragments phải cập nhật.
- `process.cwd()` trong build hooks sẽ thay đổi thành app directory.
- Alias `@/` trong layers không còn an toàn.
- Tests, fixtures và generated output path phải cập nhật.
- i18n, OpenAPI generation và `.well-known` copy phải chạy với đúng app cwd.

Giảm thiểu:

- Move bằng MR riêng với sửa behavior.
- Baseline artifact trước move.
- Build mọi `RUN_TYPE`.
- Cấm cross-app import bằng ESLint.

### 6.2 Dependency impact

Mức ảnh hưởng: **cao**.

Rủi ro:

- Regenerate root lockfile có thể resolve version mới.
- Nuxt 3 và Nuxt 4 có thể được hoist/nest khác dự kiến.
- `patch-package` có thể patch sai Nuxt location.
- Phantom dependency có thể chạy local nhưng fail trong CI.
- Shared Vue component có thể gặp khác biệt compiler/runtime.

Giảm thiểu:

- Pin exact version trong từng app khi migration.
- So sánh `npm ls --all` trước/sau.
- CI clean install.
- Mỗi package khai báo dependency trực tiếp.
- Test shared runtime bằng cả hai build.

### 6.3 Web runtime impact

Mức ảnh hưởng: **trung bình**, nhưng blast radius production cao.

Các vùng nhạy cảm:

- SSR và hydration.
- Locale redirect.
- Server plugins.
- Auth cookie.
- Sentry.
- Sitemap/robots.
- `.well-known`.
- Healthcheck.

Lưu ý hiện tại:

- Có route `/api/healthz`.
- Probe `/healthcheck/_check.html` cần Infra xác nhận vì không phải explicit route trong source.
- `RUN_TYPE`, `SENTRY`, `BUILD_VERSION` phải được bảo toàn.

### 6.4 Console runtime impact

Mức ảnh hưởng: **cao**, vì từ một layer thành một product.

Static Console không có Nitro server, do đó:

- Không thể tự tạo BFF/proxy bằng server routes.
- Runtime config public bị bake tại build time.
- Mỗi environment cần artifact riêng nếu endpoint khác nhau.
- Healthcheck do Nginx/ingress/static host cung cấp.
- Auth/CORS phải giải quyết ở API gateway hoặc edge.

### 6.5 Preview impact

Preview hiện giả định same-origin với public promotion route.

Khuyến nghị:

- Console có route `/preview/[slug]`.
- Route này render `EventPageRenderer` từ shared runtime.
- Iframe và parent cùng Console origin.
- Thêm action “Open on Web” để QA kiểm tra public shell thật.

Nếu bắt buộc iframe Web thật:

- Phải cấu hình target origin allowlist.
- Web phải cho phép Console origin qua CSP `frame-ancestors`.
- `postMessage` phải validate `origin` và `source`.
- Preview bị phụ thuộc vào version Web đang deploy.

### 6.6 Security impact

“Internal” không thay thế authentication và authorization.

Phải xác nhận:

- Console domain có nằm dưới `.onstove.com` hay không.
- Cookie của `seed-core` có đọc được trên domain Console hay không.
- Backend có role check cho API ghi event page hay không.
- CORS allowlist có Console origin hay không.
- Có VPN, corporate network, IP allowlist hoặc SSO ở edge hay không.
- Static bundle không chứa secret.

Nếu Console ở domain không thuộc `.onstove.com`, cookie `.onstove.com` không thể được browser gửi như same-site app cookie. Cần corporate SSO, edge proxy hoặc backend admin API phù hợp; không thể sửa bằng Nuxt route guard.

---

## 7. CI/CD and infrastructure changes

### 7.1 Web pipeline

#### Giai đoạn compatibility

- Install vẫn là `npm ci` tại root.
- Command Jenkins hiện tại có thể giữ nguyên qua root proxy.
- Root `.output` được tạo tạm từ `apps/web/.output`.
- Root Dockerfile chưa đổi.

#### Giai đoạn native

- Build command:

```bash
npm ci
npm run build:live --workspace=@event-hub/web
```

- Docker command:

```bash
docker build -f apps/web/Dockerfile apps/web
```

- Artifact path:

```text
apps/web/.output/
```

Infra phải cập nhật:

- Jenkins workspace path.
- Docker build context.
- Artifact collection path.
- Sentry sourcemap path.
- Healthcheck route nếu cần.
- Cache key dựa trên root `package-lock.json`.

### 7.2 Console pipeline

Build command:

```bash
npm ci
npm run build:live --workspace=@event-hub/console
```

Artifact path:

```text
apps/console/.output/public/
```

Deploy options:

1. Nginx container.
2. Internal static hosting.
3. Object storage + internal CDN, nếu access policy hỗ trợ.

Infra phải cung cấp:

- DNS/domain.
- TLS certificate.
- Internal/external ingress decision.
- VPN/IP allowlist/SSO policy.
- Static host hoặc Nginx image.
- SPA fallback.
- `/healthz` tại edge.
- Cache policy: HTML no-cache, hashed `/_nuxt/*` immutable.
- CSP, `X-Robots-Tag: noindex, nofollow`.
- CORS/reverse proxy strategy.
- Registry, namespace và deployment credentials.

### 7.3 Pipeline independence

Path rule ban đầu:

```text
apps/web/**                         → Web
apps/console/**                     → Console
packages/nuxt-layers/event-runtime/** → Web + Console
packages/event-contract/**          → Web + Console
package.json                        → Web + Console
package-lock.json                   → Web + Console
patches/**                          → app liên quan; mặc định cả hai nếu chưa xác định
```

Release build `sandbox/live` không nên bị skip chỉ vì path filter; khi được trigger release, app đó phải build đầy đủ.

### 7.4 Artifact contract

| Contract | Web | Console |
|---|---|---|
| Build command | `nuxt build` | `nuxi generate` |
| Render | SSR | CSR |
| Output | `apps/web/.output` | `apps/console/.output/public` |
| Runtime | Node/Nitro | Static host/Nginx |
| Health | Nitro route | Edge/Nginx route |
| Config | Build-time + server runtime hiện tại | Build-time public config |
| Rollback | Previous Node image | Previous static image/bundle |

---

## 8. Cross-team dependencies

### Frontend

- Thực hiện repository move và package boundaries.
- Tách Builder và event runtime.
- Bảo toàn Web SSR behavior.
- Xây Console preview.
- Viết build/artifact verification.
- Cập nhật documentation và developer commands.

### Infrastructure / DevOps

- Cung cấp pipeline Web hiện tại.
- Cập nhật workspace/build context.
- Tạo Console hosting, DNS, TLS, ingress và healthcheck.
- Tạo pipeline/image/release cho Console.
- Thiết lập independent rollback.
- Xác nhận CDN/cache behavior.

### Backend / API gateway

- Xác nhận API Builder và authorization.
- Cấu hình CORS cho Console origin hoặc cung cấp reverse proxy.
- Xác nhận cookie/token flow.
- Cung cấp admin API nếu public API hiện tại không có role boundary phù hợp.
- Xác nhận audit log cho create/update/promote.

### Security / IT

- Chọn access model: corporate network, VPN, IP allowlist hoặc SSO.
- Review CSP/frame policy.
- Review static bundle exposure.
- Xác nhận authorization không dựa vào client.
- Review secrets và environment configuration.

### QA

- Baseline public route regression.
- SSR/hydration smoke test.
- Builder flow test trên Console.
- Preview parity desktop/mobile.
- Deep-link/static fallback.
- Independent deploy/rollback verification.

### Product / Content operations

- Chấp nhận URL Builder mới.
- Xác nhận thời điểm cutover.
- Xác nhận preview behavior.
- Xác nhận thời gian fallback `/builder` trên Web.

---

## 9. Acceptance criteria & rollback

### 9.1 Repository

- `npm ci` từ clean checkout thành công.
- Chỉ có một root `package-lock.json`.
- Mỗi app có `package.json` và `nuxt.config.ts`.
- Không có import từ `apps/web` sang `apps/console` hoặc ngược lại.
- Shared packages khai báo đầy đủ dependencies.

### 9.2 Web

- Build pass cho `dev`, `dev2`, `qa`, `qa2`, `sandbox`, `live`.
- `apps/web/.output/server/index.mjs` tồn tại.
- Public route parity pass, ngoại trừ `/builder`.
- SSR response và hydration pass.
- `.well-known`, auth, locale, Sentry và API smoke test pass.
- Final artifact không chứa Builder route/chunk/source marker.

### 9.3 Console

- Build pass độc lập.
- `apps/console/.output/public/index.html` tồn tại.
- Final artifact không cần Node runtime.
- Direct access tới nested route trả SPA shell.
- Builder list/load/create/save/promote pass.
- Preview desktop/mobile pass.
- Unauthorized user không thể ghi dữ liệu.
- `noindex` và access restriction hoạt động.

### 9.4 CI/CD

- Web-only change không release Console.
- Console-only change không release Web.
- Shared runtime change verify cả hai.
- Mỗi app có version, artifact và rollback độc lập.
- Clean CI install không dựa vào local `node_modules`.

### 9.5 Rollback

Theo phase:

- P1–P2: revert layout/workspace MR; dùng pipeline root cũ.
- P3: revert shared extraction; trả event runtime về Promotion.
- P4: giữ `/builder` fallback trên Web cho đến khi Console ổn định.
- P5: hạ riêng Console về Nuxt 3.
- P6–P7: redeploy image Web/Console trước; không rollback đồng thời nếu chỉ một app lỗi.

Không xóa Web Builder fallback trước khi:

- Console đã deploy ổn định.
- Auth/API/preview đã pass.
- Product và QA xác nhận cutover.

---

## 10. Open decisions & next actions

### Cần approval trước khi triển khai

1. Console domain thật là gì?
2. Console dùng VPN/internal ingress, IP allowlist hay corporate SSO?
3. Cookie/token hiện tại có dùng được trên domain đó không?
4. Backend đã có role authorization cho create/update/promote chưa?
5. Preview dùng same-origin Console renderer hay iframe Web thật?
6. Console deploy bằng Nginx container, static host hay object storage/CDN?
7. Console cần những environment nào: dev, qa, sandbox, live?
8. Jenkins Web hiện chạy command, Docker context và healthcheck nào?
9. Console dùng GitLab CI hay Jenkins pipeline riêng?
10. Nuxt 4 được thực hiện sau khi tách ổn định, hay team chấp nhận spike trực tiếp?

### Next actions

1. FE + Infra hoàn thành Phase 0 baseline.
2. Security + Backend chốt auth/CORS/authorization.
3. FE tạo MR workspace skeleton, chưa migrate Builder.
4. Build và deploy Web từ layout mới với compatibility pipeline.
5. Build Console tối thiểu và kiểm chứng static artifact.
6. Sau khi hai build boundary pass, bắt đầu extract event runtime và migrate Builder.

---

## 11. Final recommendation

Nên triển khai theo thứ tự:

```text
npm workspace skeleton
→ move EventHub hiện tại vào apps/web
→ tạo apps/console tối thiểu
→ kiểm chứng hai artifact độc lập
→ extract event runtime có hai consumer
→ migrate Builder sang Console
→ nâng riêng Console lên Nuxt 4
→ cắt Builder khỏi Web
→ chuyển CI/CD sang app-native paths
```

Đây là hướng ít rủi ro nhất vì không trộn ba thay đổi lớn trong cùng một bước: repository move, Builder extraction và Nuxt major upgrade. Web production được giữ nguyên Nuxt 3/SSR, trong khi Console vẫn đạt kiến trúc đích Nuxt 4/CSR/static với release độc lập.
