---
# System prepended metadata

title: Detective - 編碼標準與架構指南

---

# Detective - 編碼標準與架構指南

## 1. 核心架構原則

本專案採用 **領域驅動設計（Domain-Driven Design, DDD）** 結合 **六角架構（Hexagonal Architecture，又稱 Ports & Adapters）** 及 **命令查詢職責分離（CQRS, Command Query Responsibility Segregation）**。

-   **依賴規則**：依賴必須指向**內層**。
    -   外層（Adapters）依賴內層（Ports, Application, Domain）。
    -   內層（Domain）**絕對不可**依賴外層。
-   **依賴注入（Dependency Injection, DI）**：專案使用 `tsyringe` 進行依賴管理。
    -   每個模組（限界上下文，Bounded Context）必須包含一個 `di.ts` 檔案作為 **組合根（Composition Root）**。
    -   它將 **被驅動適配器（Driven Adapters）**（實作）綁定到 **被驅動端口（Driven Ports）**（介面）。
-   **核心隔離**：業務邏輯（Domain）必須是純 TypeScript，不依賴任何框架程式碼。
-   **就近配置（Colocation）**：相關檔案（DTOs, Mappers, Persistence DTOs）應盡可能放置在其使用處附近（功能資料夾，Feature Folders）。
-   **混合快照模式（Hybrid Snapshot Pattern）**：區分不可變的核心上下文與可變的執行邏輯。
    -   **核心上下文（Core Context）**：不可變。
    -   **格式設定（Format Settings）**：快照鎖定。
    -   **執行邏輯（Execution Logic）**：可變（執行期間需要記錄快照）。

### 1.1 限界上下文概覽（Bounded Contexts Overview）

#### 1.1.1 Libs 與 Apps 的定義

> **`libs/` 存放「業務能力 (Capability)」**：我會做什麼（純 TypeScript 邏輯，不依賴框架）  
> **`apps/` 存放「部署進入點 (Deployment Entry)」**：我在哪裡運行、如何被呼叫（含 Firebase Functions、HTTP Controller、DI 配置）

**Libs 層的特點**：

-   純 TypeScript，不 import `firebase-functions`、`firebase-admin`、`express` 等框架
-   只定義 Port（介面），如 `UserRepositoryPort.findByEmail()`
-   可單獨測試，用假的 Adapter 即可跑單元測試

**Apps 層的特點**：

-   實作 Adapter，如 `FirestoreUserAdapter implements UserRepositoryPort`
-   組裝 DI，透過 `di.ts` 把 Adapter 注入到 Port
-   處理 HTTP/Event，接收 Request、解析參數、回傳 Response
-   獨立部署單位，每個 App 可獨立部署

#### 1.1.2 🔑 核心決策：什麼該是 Lib？什麼該是 App？

| 問題                                      | 如果答案是「是」 | 如果答案是「否」 |
| :---------------------------------------- | :--------------- | :--------------- |
| **這個 BC 的型別會被其他 BC 引用嗎？**    | 必須是 Lib       | 可以只放 App 內  |
| **這個 BC 需要被外部呼叫 (HTTP/Event)？** | 必須有 App       | 不需要 App       |
| **這個 BC 的邏輯會被多處重用嗎？**        | 應該是 Lib       | 可以只放 App 內  |

**決策流程**：

```text
某個 Bounded Context...

Q1: 有沒有「其他 BC」需要 import 它的型別？
    │
    ├── 是 ──→ 必須抽成獨立 Lib (libs/xxx)
    │          因為型別需要被共享
    │
    └── 否 ──→ Q2: 它有沒有需要對外暴露的 API/Event？
                   │
                   ├── 是 ──→ 只需要 App (apps/xxx)
                   │          領域邏輯放在 apps/xxx/domain/ 內
                   │
                   └── 否 ──→ 可能根本不需要獨立存在
```

#### 1.1.3 Bounded Contexts 與 Monorepo 對應

| BC 名稱        | 類型       | 核心職責           | 需要獨立 Lib？ | 對應 App          | 理由                                       |
| :------------- | :--------- | :----------------- | :------------- | :---------------- | :----------------------------------------- |
| **Identity**   | Generic    | 使用者驗證、RBAC   | ✅ 是          | `apps/iam`        | 所有 BC 都需要 User 型別                   |
| **Brand**      | Supporting | 品牌同步、資產管理 | ✅ 是          | `apps/brand`      | Monitoring/Discovery 需要 Brand 型別       |
| **Monitoring** | Core       | 任務/階段生命週期  | ✅ 是          | `apps/monitoring` | Crawling/Discovery 需要 Task/Phase 型別    |
| **Discovery**  | Core       | 發現結果管理、審查 | ✅ 是          | `apps/discovery`  | AI Triage 需要 Finding 型別                |
| **Crawling**   | Core       | 爬蟲邏輯、ETL      | ❌ 否          | `apps/crawling`   | 無其他 BC 需要其型別（領域邏輯放 apps 內） |
| **AI Triage**  | Core       | AI 策略、LLM 呼叫  | ❌ 否          | `apps/ai-triage`  | 無其他 BC 需要其型別（領域邏輯放 apps 內） |

**Crawling 和 AI Triage 為什麼不需要獨立 Lib？**

這兩個 BC 像是「工人」：

-   **Crawling**：輸入是 `Task`/`Phase`（來自 `libs/monitoring`），輸出是原始爬蟲資料 → 寫入 Firestore → 觸發 Discovery 處理。**沒有其他 BC 需要 import Crawling 的內部型別。**
-   **AI Triage**：輸入是 `Finding`（來自 `libs/discovery`），輸出是風險分類 → 更新 Finding 的 status/risk。**沒有其他 BC 需要 import AI Triage 的策略模型。**

它們的「輸入」和「輸出」都是別人定義的型別，自己的內部模型不需要對外暴露。

#### 1.1.4 Context Mapping 分析

```text
                          ┌─────────────────┐
                          │    Identity     │
                          │   (Generic)     │
                          └────────┬────────┘
                                   │ [Open Host Service]
                                   │ 所有 BC 需要知道「當前使用者是誰」
                    ┌──────────────┼──────────────┐
                    │              │              │
                    ▼              ▼              ▼
             ┌──────────┐   ┌──────────┐   ┌──────────────┐
             │  Brand   │   │Monitoring│   │  Discovery   │
             │(Support) │   │  (Core)  │   │   (Core)     │
             └────┬─────┘   └────┬─────┘   └───────┬──────┘
                  │              │                 │
    [Customer-Supplier]    [Customer-Supplier]    [Customer-Supplier]
                  │              │                 │
                  └──────┬───────┘                 │
                         │                         │
                         ▼                         ▼
                  ┌──────────┐              ┌──────────┐
                  │ Crawling │──────────────│AI Triage │
                  │  (Core)  │  [產生發現]   │  (Core)  │
                  └──────────┘              └──────────┘
```

**Context Mapping 關係類型**：

| 上游 BC        | 下游 BC               | 關係類型                       | 說明                                            |
| :------------- | :-------------------- | :----------------------------- | :---------------------------------------------- |
| **Identity**   | 所有 BC               | Open Host Service / Conformist | Identity 提供認證協議，其他 BC 遵從其 User 模型 |
| **Brand**      | Monitoring, Discovery | Customer-Supplier              | Brand 提供品牌資料，監控任務綁定 Brand          |
| **Monitoring** | Crawling              | Customer-Supplier              | Monitoring 定義 Task/Phase，Crawling 執行       |
| **Monitoring** | Discovery             | Customer-Supplier              | Discovery 的 Finding 歸屬於 Task/Phase          |
| **Crawling**   | Discovery             | Customer-Supplier              | Crawling 產生原始資料，Discovery 接收為 Finding |
| **Discovery**  | AI Triage             | Customer-Supplier              | AI Triage 讀取 Finding 進行分類判斷             |

#### 1.1.5 Libs 依賴圖

```text
                    libs/shared
                         │
          ┌──────────────┼──────────────┐
          │              │              │
          ▼              ▼              ▼
    libs/identity   libs/brand    libs/monitoring
          │              │              │
          │              └──────┬───────┘
          │                     │
          │                     ▼
          │              libs/discovery
          │                     │
          └──────────┬──────────┘
                     │
                     ▼
              (apps/ 引用)
```

**依賴規則**：

-   `libs/shared` ← 所有 libs 都依賴（共用原始型別）
-   `libs/identity` ← 所有 libs 都依賴（User 型別）
-   `libs/brand` ← `libs/monitoring`, `libs/discovery` 依賴
-   `libs/monitoring` ← `libs/discovery` 依賴
-   **libs 之間不能循環依賴**

#### 1.1.6 最終 Monorepo 結構總覽

-   **5 個 Libs**：`shared`, `identity`, `brand`, `monitoring`, `discovery`
-   **6 個 Apps**：`iam`, `brand`, `monitoring`, `discovery`, `crawling`, `ai-triage`

> **📝 Billing 定位說明**:
> Billing **不設為獨立 Bounded Context**。透過 `executionLogs` 欄位 (`durationMs`, `apiCallsConsumed`) 預留計費統計資料。獨立 Billing BC 可於未來視需求評估。

### 1.2 資料可變性規則（Data Mutability Rules）

| 層級     | 可變屬性                                               | 鎖定屬性 (不可變)                                 | 理由                                           |
| :------- | :----------------------------------------------------- | :------------------------------------------------ | :--------------------------------------------- |
| **任務** | 任務名稱、排程表達式、客戶經理 (ownerId)               | **Brand**、**Platform Type**、**Protected Asset** | 修改核心上下文將破壞階段與發現結果的參考完整性 |
| **階段** | 階段名稱、開始/結束日期、監控範圍、AI 策略、強調關鍵字 | **參考貨幣**、**預設翻譯語言**                    | 鎖定格式設定確保聚合統計在單一階段內數學正確   |

**設計原則**：「編輯經常變動的 (策略, 範圍)；鎖定定義結構的 (Brand, Platform)。」

## 2. 戰術設計本體論（Tactical Design Ontology）

我們使用以下標準詞彙和檔案後綴來定義系統元件：

### 2.1 領域層 - 核心業務

**職責**：定義業務規則、狀態驗證和不變條件。

| 元件                         | 定義                                                 | 檔案後綴        | 範例               |
| :--------------------------- | :--------------------------------------------------- | :-------------- | :----------------- |
| **聚合根（Aggregate Root）** | 一致性的守護者。具有 ID。保護內部狀態。              | `.aggregate.ts` | `monitoring-task`  |
| **實體（Entity）**           | 具有 ID 且屬於聚合的物件。                           | `.entity.ts`    | `phase`            |
| **值物件（Value Object）**   | 由屬性定義，無 ID。必須是**不可變的（Immutable）**。 | `.vo.ts`        | `monitoring-scope` |
| **領域事件（Domain Event）** | 表示已經發生的業務事實。                             | `.event.ts`     | `task-created`     |

### 2.2 應用層 - 流程編排

**職責**：編排領域層並處理功能流程。

| 元件                | 定義                               | 檔案後綴      | 範例                          |
| :------------------ | :--------------------------------- | :------------ | :---------------------------- |
| **命令（Command）** | 處理「寫入/狀態變更」意圖。        | `.command.ts` | `create-task`                 |
| **查詢（Query）**   | 處理「讀取」意圖（CQS）。          | `.query.ts`   | `get-task-list`               |
| **策略（Policy）**  | 監聽領域事件並觸發副作用（Saga）。 | `.policy.ts`  | `scheduled-scan-orchestrator` |

### 2.3 端口層 - 介面契約

**職責**：定義應用層的「進入」和「退出」介面。

| 元件                          | 定義                                  | 檔案後綴           | 範例              |
| :---------------------------- | :------------------------------------ | :----------------- | :---------------- |
| **驅動端口（Driving Port）**  | 定義命令（用例）的簽名介面。          | `.command.port.ts` | `create-task`     |
| **被驅動端口（Driven Port）** | 定義外部依賴介面（Repository, RPC）。 | `.port.ts`         | `task-repository` |

### 2.4 適配器層 - 技術實作

**職責**：實作介面、處理 HTTP、資料庫存取和 DI 配置。

| 元件                               | 定義                            | 檔案後綴            | 範例             |
| :--------------------------------- | :------------------------------ | :------------------ | :--------------- |
| **控制器（Controller）**           | 驅動適配器。處理 HTTP 請求。    | `.controller.ts`    | `task`           |
| **被驅動適配器（Driven Adapter）** | 實作被驅動端口。                | `.adapter.ts`       | `firestore-task` |
| **請求 DTO（Request DTO）**        | API 輸入資料傳輸物件。          | `.req.dto.ts`       | `create-task`    |
| **回應 DTO（Response DTO）**       | API 輸出資料傳輸物件。          | `.res.dto.ts`       | `task-list`      |
| **持久化 DTO（Persistence DTO）**  | 資料庫層的資料結構。            | `.firestore.dto.ts` | `task`           |
| **DI 配置（DI Config）**           | `tsyringe` 容器註冊（組合根）。 | `di.ts`             | `di`             |

---

## 3. 專案目錄結構

### 3.0 完整 Monorepo 結構總覽

基於 [1.1 限界上下文概覽](#11-限界上下文概覽bounded-contexts-overview) 的分析，Monorepo 結構如下：

```text
detective-v3/
├── libs/                           # 共用領域邏輯（被其他 BC 引用的型別）
│   ├── shared/                     # 共用原始型別
│   │   └── src/
│   │       ├── types/
│   │       │   └── primitives.ts   # MicroTimestamp, UUID, etc.
│   │       └── index.ts
│   │
│   ├── identity/                   # Identity BC 領域（所有 BC 需要 User 型別）
│   │   └── src/
│   │       ├── domain/
│   │       ├── application/
│   │       ├── ports/
│   │       └── index.ts
│   │
│   ├── brand/                      # Brand BC 領域（Monitoring/Discovery 需要 Brand）
│   ├── monitoring/                 # Monitoring BC 領域（Crawling/Discovery 需要 Task/Phase）
│   └── discovery/                  # Discovery BC 領域（AI Triage 需要 Finding）
│
├── apps/                           # 部署進入點
│   ├── iam/                        # Identity API（使用 libs/identity）
│   │   └── src/
│   │       ├── adapters/
│   │       │   ├── driving/http/   # HTTP Controllers
│   │       │   └── driven/firestore/ # Firestore Implementations
│   │       ├── di.ts               # DI 組裝
│   │       └── index.ts            # Firebase Functions 進入點
│   │
│   ├── brand/                      # Brand API（使用 libs/brand）
│   ├── monitoring/                 # Monitoring API（使用 libs/monitoring）
│   ├── discovery/                  # Discovery API（使用 libs/discovery）
│   │
│   ├── crawling/                   # Crawling Workers（無獨立 Lib，領域邏輯放內部）
│   │   └── src/
│   │       ├── domain/             # 內部領域，不共享
│   │       ├── application/
│   │       ├── ports/
│   │       ├── adapters/
│   │       ├── di.ts
│   │       └── index.ts
│   │
│   └── ai-triage/                  # AI Triage Workers（無獨立 Lib，領域邏輯放內部）
│       └── src/
│           ├── domain/             # 內部領域，不共享
│           ├── application/
│           ├── ports/
│           ├── adapters/
│           ├── di.ts
│           └── index.ts
│
├── firebase.json
├── pnpm-workspace.yaml
└── tsconfig.base.json
```

> **📌 注意**：`apps/crawling` 和 `apps/ai-triage` 沒有對應的 `libs/`，因為它們的領域模型不被其他 BC 引用。這兩個 App 的 `domain/`、`application/`、`ports/` 直接放在 `apps/` 內部。

### 3.1 身份上下文（Identity Context）(`libs/identity`)

**範疇**：身份驗證、使用者帳號、RBAC。

#### 驅動適配器 - HTTP

**身份驗證（Auth）**

| 位置                          | 後綴             | 名稱         | 業務理由                                |
| :---------------------------- | :--------------- | :----------- | :-------------------------------------- |
| `adapters/driving/http/auth/` | `.controller.ts` | `auth`       | 進入點：接收登入/2FA 的 HTTP 請求。     |
| `adapters/driving/http/auth/` | `.req.dto.ts`    | `login`      | 輸入：驗證登入憑證（email、password）。 |
| `adapters/driving/http/auth/` | `.req.dto.ts`    | `verify-2fa` | 輸入：驗證 2FA 驗證碼。                 |

**使用者（Users）**

| 位置                           | 後綴             | 名稱          | 業務理由                       |
| :----------------------------- | :--------------- | :------------ | :----------------------------- |
| `adapters/driving/http/users/` | `.controller.ts` | `user`        | 進入點：處理使用者管理操作。   |
| `adapters/driving/http/users/` | `.req.dto.ts`    | `update-user` | 輸入：使用者個人資料更新資料。 |
| `adapters/driving/http/users/` | `.res.dto.ts`    | `user-list`   | 輸出：返回分頁的使用者列表。   |

**角色（Roles）**

| 位置                           | 後綴             | 名稱          | 業務理由                     |
| :----------------------------- | :--------------- | :------------ | :--------------------------- |
| `adapters/driving/http/roles/` | `.controller.ts` | `role`        | 進入點：管理 RBAC 角色配置。 |
| `adapters/driving/http/roles/` | `.req.dto.ts`    | `create-role` | 輸入：包含權限樹的角色建立。 |

#### 被驅動適配器 - Firestore

**使用者（Users）**

| 位置                               | 後綴                | 名稱             | 業務理由                               |
| :--------------------------------- | :------------------ | :--------------- | :------------------------------------- |
| `adapters/driven/firestore/users/` | `.adapter.ts`       | `firestore-user` | 持久化：使用者的 Repository 實作。     |
| `adapters/driven/firestore/users/` | `.firestore.dto.ts` | `user`           | 資料庫結構：Firestore 使用者文件結構。 |

**角色（Roles）**

| 位置                               | 後綴                | 名稱             | 業務理由                             |
| :--------------------------------- | :------------------ | :--------------- | :----------------------------------- |
| `adapters/driven/firestore/roles/` | `.adapter.ts`       | `firestore-role` | 持久化：儲存 RBAC 角色定義。         |
| `adapters/driven/firestore/roles/` | `.firestore.dto.ts` | `role`           | 資料庫結構：Firestore 角色文件結構。 |

**稽核日誌（Audit Logs）**

| 位置                                    | 後綴          | 名稱                  | 業務理由                                 |
| :-------------------------------------- | :------------ | :-------------------- | :--------------------------------------- |
| `adapters/driven/firestore/audit-logs/` | `.adapter.ts` | `firestore-audit-log` | 持久化：記錄所有使用者操作以符合合規性。 |

#### 被驅動適配器 - 外部身份提供者（External IDP）

**LDAP**

| 位置                            | 後綴          | 名稱        | 業務理由                          |
| :------------------------------ | :------------ | :---------- | :-------------------------------- |
| `adapters/driven/external-idp/` | `.adapter.ts` | `ldap-sync` | 整合：從外部 LDAP/AD 同步使用者。 |

#### 驅動端口

| 位置             | 後綴               | 名稱                 | 業務理由                   |
| :--------------- | :----------------- | :------------------- | :------------------------- |
| `ports/driving/` | `.command.port.ts` | `login`              | 介面：登入命令簽名。       |
| `ports/driving/` | `.command.port.ts` | `verify-2fa`         | 介面：2FA 驗證命令簽名。   |
| `ports/driving/` | `.command.port.ts` | `sync-user-accounts` | 介面：使用者同步命令簽名。 |
| `ports/driving/` | `.command.port.ts` | `update-user`        | 介面：使用者更新命令簽名。 |
| `ports/driving/` | `.command.port.ts` | `create-role`        | 介面：角色建立命令簽名。   |

#### 被驅動端口

| 位置 | 後綴 | 名稱 | 業務理由 |
| `:--- | :--- | :--- | :--- |
| `ports/driven/`|`.port.ts`|`user-repository`| 介面：使用者持久化契約。 |
|`ports/driven/`|`.port.ts`|`role-repository`| 介面：角色持久化契約。 |
|`ports/driven/`|`.port.ts`|`audit-log-repository`| 介面：稽核日誌持久化契約。 |
|`ports/driven/`|`.port.ts`|`external-identity-provider` | 介面：外部身份提供者整合契約。 |

#### 應用層

**命令（Commands）**

| 位置                    | 後綴          | 名稱                 | 業務理由                             |
| :---------------------- | :------------ | :------------------- | :----------------------------------- |
| `application/commands/` | `.command.ts` | `login`              | 流程：驗證使用者，若啟用則觸發 2FA。 |
| `application/commands/` | `.command.ts` | `verify-2fa`         | 流程：驗證 2FA 代碼。                |
| `application/commands/` | `.command.ts` | `sync-user-accounts` | 整合：從 Domainarium 同步使用者。    |
| `application/commands/` | `.command.ts` | `update-user`        | 流程：更新使用者個人資料和權限。     |
| `application/commands/` | `.command.ts` | `create-role`        | 流程：建立新的 RBAC 角色。           |

**查詢（Queries）**

| 位置                   | 後綴        | 名稱                     | 業務理由                   |
| :--------------------- | :---------- | :----------------------- | :------------------------- |
| `application/queries/` | `.query.ts` | `get-user-list`          | 讀取：取得分頁使用者列表。 |
| `application/queries/` | `.query.ts` | `get-risk-category-list` | 讀取：返回可用的風險類別。 |
| `application/queries/` | `.query.ts` | `get-system-audit-log`   | 讀取：檢索稽核追蹤。       |

#### 領域層

**聚合（Aggregates）**

| 位置                 | 後綴            | 名稱           | 業務理由                                        |
| :------------------- | :-------------- | :------------- | :---------------------------------------------- |
| `domain/aggregates/` | `.aggregate.ts` | `user-account` | 核心規則：管理 Active/Inactive 狀態、鎖定邏輯。 |
| `domain/aggregates/` | `.aggregate.ts` | `role`         | 核心規則：定義 RBAC 權限樹。                    |

**事件（Events）**

| 位置             | 後綴        | 名稱                   | 業務理由                       |
| :--------------- | :---------- | :--------------------- | :----------------------------- |
| `domain/events/` | `.event.ts` | `user-logged-in`       | 事實：記錄成功登入。           |
| `domain/events/` | `.event.ts` | `user-locked`          | 事實：因失敗嘗試次數鎖定帳號。 |
| `domain/events/` | `.event.ts` | `two-factor-verified`  | 事實：2FA 驗證完成。           |
| `domain/events/` | `.event.ts` | `user-accounts-synced` | 事實：外部使用者同步完成。     |
| `domain/events/` | `.event.ts` | `user-updated`         | 事實：使用者個人資料已變更。   |
| `domain/events/` | `.event.ts` | `role-updated`         | 事實：角色權限已變更。         |

#### 基礎設施層

| 位置 | 後綴 | 名稱 | 業務理由 |
| :--- | :--- | :--- | :------- |

---

### 3.2 品牌上下文（Brand Context）(`libs/brand`)

**範疇**：品牌管理、受保護資產管理、白牌設定、Domainarium 同步。

#### 驅動適配器 - HTTP (`apps/brand`)

**品牌（Brands）**

| 位置                            | 後綴             | 名稱                 | 業務理由                     |
| :------------------------------ | :--------------- | :------------------- | :--------------------------- |
| `adapters/driving/http/brands/` | `.controller.ts` | `brand`              | 進入點：處理品牌 CRUD 操作。 |
| `adapters/driving/http/brands/` | `.req.dto.ts`    | `sync-brands`        | 輸入：批次同步所有品牌。     |
| `adapters/driving/http/brands/` | `.req.dto.ts`    | `update-brand`       | 輸入：更新品牌基本資訊。     |
| `adapters/driving/http/brands/` | `.req.dto.ts`    | `update-white-label` | 輸入：更新白牌設定。         |
| `adapters/driving/http/brands/` | `.res.dto.ts`    | `brand-list`         | 輸出：返回分頁的品牌列表。   |

**受保護資產（Assets）**

| 位置                            | 後綴             | 名稱           | 業務理由                     |
| :------------------------------ | :--------------- | :------------- | :--------------------------- |
| `adapters/driving/http/assets/` | `.controller.ts` | `asset`        | 進入點：處理資產 CRUD 操作。 |
| `adapters/driving/http/assets/` | `.req.dto.ts`    | `create-asset` | 輸入：建立受保護資產。       |
| `adapters/driving/http/assets/` | `.req.dto.ts`    | `update-asset` | 輸入：更新資產資訊。         |
| `adapters/driving/http/assets/` | `.res.dto.ts`    | `asset-list`   | 輸出：返回分頁的資產列表。   |

#### 被驅動適配器 - Firestore (`apps/brand`)

| 位置                                | 後綴                | 名稱              | 業務理由                         |
| :---------------------------------- | :------------------ | :---------------- | :------------------------------- |
| `adapters/driven/firestore/brands/` | `.adapter.ts`       | `firestore-brand` | 持久化：品牌的 Repository 實作。 |
| `adapters/driven/firestore/brands/` | `.firestore.dto.ts` | `brand`           | 資料庫結構：Firestore 品牌文件。 |
| `adapters/driven/firestore/assets/` | `.adapter.ts`       | `firestore-asset` | 持久化：資產的 Repository 實作。 |
| `adapters/driven/firestore/assets/` | `.firestore.dto.ts` | `asset`           | 資料庫結構：Firestore 資產文件。 |

#### 被驅動適配器 - Domainarium (`apps/brand`)

| 位置                           | 後綴          | 名稱                | 業務理由                            |
| :----------------------------- | :------------ | :------------------ | :---------------------------------- |
| `adapters/driven/domainarium/` | `.adapter.ts` | `domainarium-brand` | 整合：從 Domainarium 同步品牌資料。 |

#### 驅動端口 (`libs/brand`)

| 位置             | 後綴               | 名稱                    | 業務理由                     |
| :--------------- | :----------------- | :---------------------- | :--------------------------- |
| `ports/driving/` | `.command.port.ts` | `sync-brands`           | 介面：批次同步品牌命令簽名。 |
| `ports/driving/` | `.command.port.ts` | `sync-brand`            | 介面：單一品牌同步命令簽名。 |
| `ports/driving/` | `.command.port.ts` | `update-brand`          | 介面：更新品牌命令簽名。     |
| `ports/driving/` | `.command.port.ts` | `configure-white-label` | 介面：設定白牌命令簽名。     |
| `ports/driving/` | `.command.port.ts` | `create-asset`          | 介面：建立資產命令簽名。     |
| `ports/driving/` | `.command.port.ts` | `update-asset`          | 介面：更新資產命令簽名。     |
| `ports/driving/` | `.command.port.ts` | `delete-asset`          | 介面：刪除資產命令簽名。     |

#### 被驅動端口 (`libs/brand`)

| 位置            | 後綴       | 名稱                      | 業務理由                               |
| :-------------- | :--------- | :------------------------ | :------------------------------------- |
| `ports/driven/` | `.port.ts` | `brand-repository`        | 介面：品牌持久化契約。                 |
| `ports/driven/` | `.port.ts` | `asset-repository`        | 介面：資產持久化契約。                 |
| `ports/driven/` | `.port.ts` | `external-brand-provider` | 介面：外部品牌資料來源 (Domainarium)。 |

#### 應用層 (`libs/brand`)

**命令（Commands）**

| 位置                    | 後綴          | 名稱                    | 業務理由                             |
| :---------------------- | :------------ | :---------------------- | :----------------------------------- |
| `application/commands/` | `.command.ts` | `sync-brands`           | 流程：從 Domainarium 批次同步。      |
| `application/commands/` | `.command.ts` | `sync-brand`            | 流程：同步單一品牌。                 |
| `application/commands/` | `.command.ts` | `update-brand`          | 流程：更新品牌基本資訊。             |
| `application/commands/` | `.command.ts` | `configure-white-label` | 流程：設定白牌外觀。                 |
| `application/commands/` | `.command.ts` | `create-asset`          | 流程：建立受保護資產。               |
| `application/commands/` | `.command.ts` | `update-asset`          | 流程：更新資產資訊。                 |
| `application/commands/` | `.command.ts` | `delete-asset`          | 流程：刪除資產（含參考完整性檢查）。 |

**查詢（Queries）**

| 位置                   | 後綴        | 名稱             | 業務理由                 |
| :--------------------- | :---------- | :--------------- | :----------------------- |
| `application/queries/` | `.query.ts` | `get-brand-list` | 讀取：取得分頁品牌列表。 |
| `application/queries/` | `.query.ts` | `get-brand`      | 讀取：取得單一品牌詳情。 |
| `application/queries/` | `.query.ts` | `get-asset-list` | 讀取：取得分頁資產列表。 |
| `application/queries/` | `.query.ts` | `get-asset`      | 讀取：取得單一資產詳情。 |

#### 領域層 (`libs/brand`)

**聚合（Aggregates）**

| 位置                 | 後綴            | 名稱              | 業務理由                           |
| :------------------- | :-------------- | :---------------- | :--------------------------------- |
| `domain/aggregates/` | `.aggregate.ts` | `brand`           | 核心規則：品牌狀態管理、白牌設定。 |
| `domain/aggregates/` | `.aggregate.ts` | `protected-asset` | 核心規則：資產與任務的參考完整性。 |

**事件（Events）**

| 位置             | 後綴        | 名稱                     | 業務理由                        |
| :--------------- | :---------- | :----------------------- | :------------------------------ |
| `domain/events/` | `.event.ts` | `brand-synced`           | 事實：品牌從 Domainarium 同步。 |
| `domain/events/` | `.event.ts` | `brand-updated`          | 事實：品牌資訊已更新。          |
| `domain/events/` | `.event.ts` | `white-label-configured` | 事實：白牌設定已變更。          |
| `domain/events/` | `.event.ts` | `asset-created`          | 事實：新資產已建立。            |
| `domain/events/` | `.event.ts` | `asset-updated`          | 事實：資產資訊已更新。          |
| `domain/events/` | `.event.ts` | `asset-deleted`          | 事實：資產已刪除。              |

---

### 3.3 監控上下文（Monitoring Context）(`libs/monitoring`)

**範疇**：監控任務管理、階段管理、標籤管理、範圍設定、AI 策略設定。

#### 驅動適配器 - HTTP (`apps/monitoring`)

**任務（Tasks）**

| 位置                           | 後綴             | 名稱             | 業務理由                           |
| :----------------------------- | :--------------- | :--------------- | :--------------------------------- |
| `adapters/driving/http/tasks/` | `.controller.ts` | `task`           | 進入點：處理任務 CRUD 與狀態變更。 |
| `adapters/driving/http/tasks/` | `.req.dto.ts`    | `create-task`    | 輸入：建立任務（含第一個階段）。   |
| `adapters/driving/http/tasks/` | `.req.dto.ts`    | `configure-task` | 輸入：更新任務可變設定。           |
| `adapters/driving/http/tasks/` | `.res.dto.ts`    | `task-list`      | 輸出：返回分頁的任務列表。         |
| `adapters/driving/http/tasks/` | `.res.dto.ts`    | `task-detail`    | 輸出：任務詳情（含階段摘要）。     |

**階段（Phases）**

| 位置                            | 後綴             | 名稱           | 業務理由                         |
| :------------------------------ | :--------------- | :------------- | :------------------------------- |
| `adapters/driving/http/phases/` | `.controller.ts` | `phase`        | 進入點：處理階段 CRUD 操作。     |
| `adapters/driving/http/phases/` | `.req.dto.ts`    | `create-phase` | 輸入：建立新階段。               |
| `adapters/driving/http/phases/` | `.req.dto.ts`    | `edit-phase`   | 輸入：編輯階段可變邏輯。         |
| `adapters/driving/http/phases/` | `.res.dto.ts`    | `phase-detail` | 輸出：階段詳情（含範圍與策略）。 |

**範圍（Scopes）**

| 位置                            | 後綴             | 名稱                  | 業務理由                |
| :------------------------------ | :--------------- | :-------------------- | :---------------------- |
| `adapters/driving/http/scopes/` | `.controller.ts` | `scope`               | 進入點：處理範圍 CRUD。 |
| `adapters/driving/http/scopes/` | `.req.dto.ts`    | `create-scope`        | 輸入：新增監控範圍。    |
| `adapters/driving/http/scopes/` | `.req.dto.ts`    | `configure-scan-tier` | 輸入：設定掃描層級。    |

**策略（Strategies）**

| 位置                                | 後綴             | 名稱               | 業務理由                    |
| :---------------------------------- | :--------------- | :----------------- | :-------------------------- |
| `adapters/driving/http/strategies/` | `.controller.ts` | `strategy`         | 進入點：處理 AI 策略 CRUD。 |
| `adapters/driving/http/strategies/` | `.req.dto.ts`    | `create-strategy`  | 輸入：新增 AI 分流策略。    |
| `adapters/driving/http/strategies/` | `.req.dto.ts`    | `reorder-strategy` | 輸入：重新排序策略優先級。  |

**標籤（Labels）**

| 位置                            | 後綴             | 名稱           | 業務理由                   |
| :------------------------------ | :--------------- | :------------- | :------------------------- |
| `adapters/driving/http/labels/` | `.controller.ts` | `label`        | 進入點：處理標籤 CRUD。    |
| `adapters/driving/http/labels/` | `.req.dto.ts`    | `create-label` | 輸入：新增標籤。           |
| `adapters/driving/http/labels/` | `.req.dto.ts`    | `update-label` | 輸入：更新標籤名稱或顏色。 |

#### 被驅動適配器 - Firestore (`apps/monitoring`)

| 位置                                | 後綴                | 名稱              | 業務理由                         |
| :---------------------------------- | :------------------ | :---------------- | :------------------------------- |
| `adapters/driven/firestore/tasks/`  | `.adapter.ts`       | `firestore-task`  | 持久化：任務的 Repository 實作。 |
| `adapters/driven/firestore/tasks/`  | `.firestore.dto.ts` | `task`            | 資料庫結構：Firestore 任務文件。 |
| `adapters/driven/firestore/phases/` | `.adapter.ts`       | `firestore-phase` | 持久化：階段的 Repository 實作。 |
| `adapters/driven/firestore/phases/` | `.firestore.dto.ts` | `phase`           | 資料庫結構：Firestore 階段文件。 |
| `adapters/driven/firestore/labels/` | `.adapter.ts`       | `firestore-label` | 持久化：標籤的 Repository 實作。 |

#### 驅動端口 (`libs/monitoring`)

| 位置             | 後綴               | 名稱             | 業務理由                 |
| :--------------- | :----------------- | :--------------- | :----------------------- |
| `ports/driving/` | `.command.port.ts` | `create-task`    | 介面：建立任務命令簽名。 |
| `ports/driving/` | `.command.port.ts` | `configure-task` | 介面：設定任務命令簽名。 |
| `ports/driving/` | `.command.port.ts` | `pause-task`     | 介面：暫停任務命令簽名。 |
| `ports/driving/` | `.command.port.ts` | `resume-task`    | 介面：恢復任務命令簽名。 |
| `ports/driving/` | `.command.port.ts` | `archive-task`   | 介面：封存任務命令簽名。 |
| `ports/driving/` | `.command.port.ts` | `delete-task`    | 介面：刪除任務命令簽名。 |
| `ports/driving/` | `.command.port.ts` | `create-phase`   | 介面：建立階段命令簽名。 |
| `ports/driving/` | `.command.port.ts` | `edit-phase`     | 介面：編輯階段命令簽名。 |
| `ports/driving/` | `.command.port.ts` | `create-label`   | 介面：建立標籤命令簽名。 |
| `ports/driving/` | `.command.port.ts` | `delete-label`   | 介面：刪除標籤命令簽名。 |

#### 被驅動端口 (`libs/monitoring`)

| 位置            | 後綴       | 名稱                | 業務理由               |
| :-------------- | :--------- | :------------------ | :--------------------- |
| `ports/driven/` | `.port.ts` | `task-repository`   | 介面：任務持久化契約。 |
| `ports/driven/` | `.port.ts` | `phase-repository`  | 介面：階段持久化契約。 |
| `ports/driven/` | `.port.ts` | `label-repository`  | 介面：標籤持久化契約。 |
| `ports/driven/` | `.port.ts` | `scheduler-service` | 介面：排程器服務契約。 |

#### 應用層 (`libs/monitoring`)

**命令（Commands）**

| 位置                    | 後綴          | 名稱                  | 業務理由                           |
| :---------------------- | :------------ | :-------------------- | :--------------------------------- |
| `application/commands/` | `.command.ts` | `create-task`         | 流程：建立任務及初始階段。         |
| `application/commands/` | `.command.ts` | `configure-task`      | 流程：更新任務可變設定。           |
| `application/commands/` | `.command.ts` | `pause-task`          | 流程：暫停任務排程。               |
| `application/commands/` | `.command.ts` | `resume-task`         | 流程：恢復任務排程。               |
| `application/commands/` | `.command.ts` | `archive-task`        | 流程：封存任務（終態）。           |
| `application/commands/` | `.command.ts` | `delete-task`         | 流程：刪除任務及所有相關資料。     |
| `application/commands/` | `.command.ts` | `create-phase`        | 流程：建立新階段。                 |
| `application/commands/` | `.command.ts` | `edit-phase`          | 流程：編輯階段可變邏輯。           |
| `application/commands/` | `.command.ts` | `create-scope`        | 流程：新增監控範圍。               |
| `application/commands/` | `.command.ts` | `configure-scan-tier` | 流程：設定範圍掃描層級。           |
| `application/commands/` | `.command.ts` | `create-strategy`     | 流程：新增 AI 分流策略。           |
| `application/commands/` | `.command.ts` | `reorder-strategies`  | 流程：重新排序策略。               |
| `application/commands/` | `.command.ts` | `create-label`        | 流程：建立標籤。                   |
| `application/commands/` | `.command.ts` | `update-label`        | 流程：更新標籤。                   |
| `application/commands/` | `.command.ts` | `delete-label`        | 流程：刪除標籤（含歷史發現影響）。 |

**查詢（Queries）**

| 位置                   | 後綴        | 名稱               | 業務理由                       |
| :--------------------- | :---------- | :----------------- | :----------------------------- |
| `application/queries/` | `.query.ts` | `get-task-list`    | 讀取：取得分頁任務列表。       |
| `application/queries/` | `.query.ts` | `get-task-detail`  | 讀取：取得任務詳情含階段摘要。 |
| `application/queries/` | `.query.ts` | `get-phase-list`   | 讀取：取得任務的所有階段。     |
| `application/queries/` | `.query.ts` | `get-phase-detail` | 讀取：取得階段詳情含範圍策略。 |
| `application/queries/` | `.query.ts` | `get-label-list`   | 讀取：取得任務的標籤庫。       |

**策略（Policies）**

| 位置                    | 後綴         | 名稱                            | 業務理由                               |
| :---------------------- | :----------- | :------------------------------ | :------------------------------------- |
| `application/policies/` | `.policy.ts` | `on-task-created-init-schedule` | Saga：任務建立後初始化排程器。         |
| `application/policies/` | `.policy.ts` | `on-zero-findings-pause-phase`  | Saga：殭屍任務自動降級（連續無發現）。 |

#### 領域層 (`libs/monitoring`)

**聚合（Aggregates）**

| 位置                 | 後綴            | 名稱              | 業務理由                                   |
| :------------------- | :-------------- | :---------------- | :----------------------------------------- |
| `domain/aggregates/` | `.aggregate.ts` | `monitoring-task` | 核心規則：任務生命週期、不可變上下文鎖定。 |

**實體（Entities）**

| 位置               | 後綴         | 名稱    | 業務理由                       |
| :----------------- | :----------- | :------ | :----------------------------- |
| `domain/entities/` | `.entity.ts` | `phase` | 實體：階段生命週期、快照鎖定。 |

**值物件（Value Objects）**

| 位置                    | 後綴     | 名稱               | 業務理由                                |
| :---------------------- | :------- | :----------------- | :-------------------------------------- |
| `domain/value-objects/` | `.vo.ts` | `monitoring-scope` | 值物件：監控範圍定義（關鍵字 × 平台）。 |
| `domain/value-objects/` | `.vo.ts` | `ai-strategy`      | 值物件：AI 分流策略定義。               |
| `domain/value-objects/` | `.vo.ts` | `label`            | 值物件：標籤定義。                      |

**事件（Events）**

| 位置             | 後綴        | 名稱                         | 業務理由                   |
| :--------------- | :---------- | :--------------------------- | :------------------------- |
| `domain/events/` | `.event.ts` | `task-created`               | 事實：新任務已建立。       |
| `domain/events/` | `.event.ts` | `task-configured`            | 事實：任務設定已變更。     |
| `domain/events/` | `.event.ts` | `task-paused`                | 事實：任務已暫停。         |
| `domain/events/` | `.event.ts` | `task-resumed`               | 事實：任務已恢復。         |
| `domain/events/` | `.event.ts` | `task-archived`              | 事實：任務已封存。         |
| `domain/events/` | `.event.ts` | `task-deleted`               | 事實：任務已刪除。         |
| `domain/events/` | `.event.ts` | `phase-created`              | 事實：新階段已建立。       |
| `domain/events/` | `.event.ts` | `phase-updated`              | 事實：階段設定已更新。     |
| `domain/events/` | `.event.ts` | `scope-scan-tier-configured` | 事實：範圍掃描層級已設定。 |
| `domain/events/` | `.event.ts` | `strategy-reordered`         | 事實：策略順序已變更。     |
| `domain/events/` | `.event.ts` | `label-created`              | 事實：新標籤已建立。       |
| `domain/events/` | `.event.ts` | `label-deleted`              | 事實：標籤已刪除。         |

---

### 3.4 發現上下文（Discovery Context）(`libs/discovery`)

**範疇**：發現管理、審查分類、評論系統、洞察分析、報告匯出。

#### 驅動適配器 - HTTP (`apps/discovery`)

**發現（Findings）**

| 位置                              | 後綴             | 名稱             | 業務理由                        |
| :-------------------------------- | :--------------- | :--------------- | :------------------------------ |
| `adapters/driving/http/findings/` | `.controller.ts` | `finding`        | 進入點：處理發現查詢與操作。    |
| `adapters/driving/http/findings/` | `.req.dto.ts`    | `assign-risk`    | 輸入：指派風險類別。            |
| `adapters/driving/http/findings/` | `.req.dto.ts`    | `change-status`  | 輸入：變更發現狀態。            |
| `adapters/driving/http/findings/` | `.req.dto.ts`    | `batch-action`   | 輸入：批次操作（移動/標籤等）。 |
| `adapters/driving/http/findings/` | `.res.dto.ts`    | `finding-list`   | 輸出：分頁發現列表。            |
| `adapters/driving/http/findings/` | `.res.dto.ts`    | `finding-detail` | 輸出：發現詳情（含評論）。      |

**評論（Comments）**

| 位置                              | 後綴             | 名稱          | 業務理由                   |
| :-------------------------------- | :--------------- | :------------ | :------------------------- |
| `adapters/driving/http/comments/` | `.controller.ts` | `comment`     | 進入點：處理評論 CRUD。    |
| `adapters/driving/http/comments/` | `.req.dto.ts`    | `add-comment` | 輸入：新增評論或系統註解。 |

**洞察（Insights）**

| 位置                              | 後綴             | 名稱                 | 業務理由                   |
| :-------------------------------- | :--------------- | :------------------- | :------------------------- |
| `adapters/driving/http/insights/` | `.controller.ts` | `insight`            | 進入點：處理統計分析查詢。 |
| `adapters/driving/http/insights/` | `.res.dto.ts`    | `insight-summary`    | 輸出：洞察統計摘要。       |
| `adapters/driving/http/insights/` | `.res.dto.ts`    | `insight-trends`     | 輸出：趨勢圖表資料。       |
| `adapters/driving/http/insights/` | `.res.dto.ts`    | `insight-comparison` | 輸出：跨階段比較資料。     |

**匯出（Export）**

| 位置                            | 後綴             | 名稱              | 業務理由                 |
| :------------------------------ | :--------------- | :---------------- | :----------------------- |
| `adapters/driving/http/export/` | `.controller.ts` | `export`          | 進入點：處理匯出作業。   |
| `adapters/driving/http/export/` | `.req.dto.ts`    | `export-findings` | 輸入：匯出發現清單請求。 |
| `adapters/driving/http/export/` | `.req.dto.ts`    | `export-stats`    | 輸入：匯出統計報告請求。 |

#### 被驅動適配器 - Firestore/Algolia (`apps/discovery`)

| 位置                                  | 後綴                | 名稱                | 業務理由                            |
| :------------------------------------ | :------------------ | :------------------ | :---------------------------------- |
| `adapters/driven/firestore/findings/` | `.adapter.ts`       | `firestore-finding` | 持久化：發現的 Repository 實作。    |
| `adapters/driven/firestore/findings/` | `.firestore.dto.ts` | `finding`           | 資料庫結構：Firestore 發現文件。    |
| `adapters/driven/firestore/comments/` | `.adapter.ts`       | `firestore-comment` | 持久化：評論的 Repository 實作。    |
| `adapters/driven/algolia/`            | `.adapter.ts`       | `algolia-finding`   | 搜尋：Algolia 發現索引實作 (CQRS)。 |

#### 驅動端口 (`libs/discovery`)

| 位置             | 後綴               | 名稱                | 業務理由                     |
| :--------------- | :----------------- | :------------------ | :--------------------------- |
| `ports/driving/` | `.command.port.ts` | `assign-risk`       | 介面：指派風險命令簽名。     |
| `ports/driving/` | `.command.port.ts` | `change-status`     | 介面：變更狀態命令簽名。     |
| `ports/driving/` | `.command.port.ts` | `batch-move-status` | 介面：批次移動狀態命令簽名。 |
| `ports/driving/` | `.command.port.ts` | `assign-labels`     | 介面：指派標籤命令簽名。     |
| `ports/driving/` | `.command.port.ts` | `add-comment`       | 介面：新增評論命令簽名。     |
| `ports/driving/` | `.command.port.ts` | `export-findings`   | 介面：匯出發現命令簽名。     |

#### 被驅動端口 (`libs/discovery`)

| 位置            | 後綴       | 名稱                 | 業務理由                       |
| :-------------- | :--------- | :------------------- | :----------------------------- |
| `ports/driven/` | `.port.ts` | `finding-repository` | 介面：發現持久化契約。         |
| `ports/driven/` | `.port.ts` | `finding-search`     | 介面：發現搜尋契約 (Algolia)。 |
| `ports/driven/` | `.port.ts` | `comment-repository` | 介面：評論持久化契約。         |
| `ports/driven/` | `.port.ts` | `export-service`     | 介面：匯出服務契約。           |

#### 應用層 (`libs/discovery`)

**命令（Commands）**

| 位置                    | 後綴          | 名稱                | 業務理由                   |
| :---------------------- | :------------ | :------------------ | :------------------------- |
| `application/commands/` | `.command.ts` | `assign-risk`       | 流程：指派風險類別。       |
| `application/commands/` | `.command.ts` | `change-status`     | 流程：變更發現狀態。       |
| `application/commands/` | `.command.ts` | `batch-move-status` | 流程：批次移動狀態。       |
| `application/commands/` | `.command.ts` | `assign-labels`     | 流程：指派標籤。           |
| `application/commands/` | `.command.ts` | `add-comment`       | 流程：新增評論。           |
| `application/commands/` | `.command.ts` | `export-findings`   | 流程：非同步產生匯出檔案。 |
| `application/commands/` | `.command.ts` | `export-stats`      | 流程：產生統計報告。       |

**查詢（Queries）**

| 位置                   | 後綴        | 名稱                     | 業務理由                           |
| :--------------------- | :---------- | :----------------------- | :--------------------------------- |
| `application/queries/` | `.query.ts` | `get-finding-list`       | 讀取：取得分頁發現列表（含篩選）。 |
| `application/queries/` | `.query.ts` | `get-finding-detail`     | 讀取：取得發現詳情。               |
| `application/queries/` | `.query.ts` | `get-comment-list`       | 讀取：取得發現的評論列表。         |
| `application/queries/` | `.query.ts` | `get-insight-summary`    | 讀取：取得洞察統計摘要。           |
| `application/queries/` | `.query.ts` | `get-insight-trends`     | 讀取：取得趨勢圖表資料。           |
| `application/queries/` | `.query.ts` | `get-insight-comparison` | 讀取：取得跨階段比較資料。         |
| `application/queries/` | `.query.ts` | `get-export-status`      | 讀取：查詢匯出作業狀態。           |

**策略（Policies）**

| 位置                    | 後綴         | 名稱                        | 業務理由                      |
| :---------------------- | :----------- | :-------------------------- | :---------------------------- |
| `application/policies/` | `.policy.ts` | `on-ai-triaged-add-comment` | Saga：AI 分流後新增系統註解。 |

#### 領域層 (`libs/discovery`)

**聚合（Aggregates）**

| 位置                 | 後綴            | 名稱      | 業務理由                                     |
| :------------------- | :-------------- | :-------- | :------------------------------------------- |
| `domain/aggregates/` | `.aggregate.ts` | `finding` | 核心規則：發現生命週期、狀態流轉、標籤管理。 |

**值物件（Value Objects）**

| 位置                    | 後綴     | 名稱            | 業務理由                     |
| :---------------------- | :------- | :-------------- | :--------------------------- |
| `domain/value-objects/` | `.vo.ts` | `risk-category` | 值物件：風險類別定義。       |
| `domain/value-objects/` | `.vo.ts` | `comment`       | 值物件：評論（含系統註解）。 |

**事件（Events）**

| 位置             | 後綴        | 名稱                     | 業務理由                   |
| :--------------- | :---------- | :----------------------- | :------------------------- |
| `domain/events/` | `.event.ts` | `finding-created`        | 事實：新發現已建立。       |
| `domain/events/` | `.event.ts` | `finding-status-changed` | 事實：發現狀態已變更。     |
| `domain/events/` | `.event.ts` | `finding-risk-assigned`  | 事實：發現已指派風險類別。 |
| `domain/events/` | `.event.ts` | `finding-labels-updated` | 事實：發現標籤已更新。     |
| `domain/events/` | `.event.ts` | `finding-ai-triaged`     | 事實：發現已完成 AI 分流。 |
| `domain/events/` | `.event.ts` | `comment-added`          | 事實：評論已新增。         |

---

### 3.5 爬取上下文（Crawling Context）(`apps/crawling`)

**範疇**：爬蟲執行、掃描單元分解、ETL 資料清洗、手動留存。

> **注意**：Crawling 沒有獨立的 `libs/`，領域邏輯直接放在 `apps/crawling/` 內部。

#### 驅動適配器 - HTTP/Event (`apps/crawling`)

**手動留存（Manual Retain）**

| 位置                            | 後綴             | 名稱            | 業務理由                   |
| :------------------------------ | :--------------- | :-------------- | :------------------------- |
| `adapters/driving/http/retain/` | `.controller.ts` | `retain`        | 進入點：處理手動留存請求。 |
| `adapters/driving/http/retain/` | `.req.dto.ts`    | `manual-retain` | 輸入：提交 URL 進行抓取。  |
| `adapters/driving/http/retain/` | `.res.dto.ts`    | `retain-status` | 輸出：留存作業狀態。       |

**排程觸發（Scheduled Scan）**

| 位置                      | 後綴          | 名稱             | 業務理由                         |
| :------------------------ | :------------ | :--------------- | :------------------------------- |
| `adapters/driving/event/` | `.trigger.ts` | `scheduled-scan` | 事件觸發：Cloud Scheduler 觸發。 |

#### 被驅動適配器 (`apps/crawling`)

| 位置                         | 後綴          | 名稱                  | 業務理由                        |
| :--------------------------- | :------------ | :-------------------- | :------------------------------ |
| `adapters/driven/firestore/` | `.adapter.ts` | `firestore-execution` | 持久化：執行日誌 Repository。   |
| `adapters/driven/crawler/`   | `.adapter.ts` | `external-crawler`    | 整合：外部爬蟲服務呼叫。        |
| `adapters/driven/pubsub/`    | `.adapter.ts` | `finding-publisher`   | 整合：發布發現到 Discovery BC。 |

#### 領域層 (`apps/crawling/domain/`)

**聚合（Aggregates）**

| 位置                 | 後綴            | 名稱       | 業務理由                     |
| :------------------- | :-------------- | :--------- | :--------------------------- |
| `domain/aggregates/` | `.aggregate.ts` | `scan-job` | 核心規則：掃描作業狀態管理。 |

**值物件（Value Objects）**

| 位置                    | 後綴     | 名稱        | 業務理由                            |
| :---------------------- | :------- | :---------- | :---------------------------------- |
| `domain/value-objects/` | `.vo.ts` | `scan-unit` | 值物件：掃描單元（關鍵字 × 平台）。 |

**事件（Events）**

| 位置             | 後綴        | 名稱                 | 業務理由               |
| :--------------- | :---------- | :------------------- | :--------------------- |
| `domain/events/` | `.event.ts` | `scan-started`       | 事實：掃描作業已開始。 |
| `domain/events/` | `.event.ts` | `scan-completed`     | 事實：掃描作業已完成。 |
| `domain/events/` | `.event.ts` | `scan-failed`        | 事實：掃描作業失敗。   |
| `domain/events/` | `.event.ts` | `raw-data-extracted` | 事實：原始資料已擷取。 |

#### 應用層 (`apps/crawling/application/`)

**命令（Commands）**

| 位置                    | 後綴          | 名稱            | 業務理由                  |
| :---------------------- | :------------ | :-------------- | :------------------------ |
| `application/commands/` | `.command.ts` | `execute-scan`  | 流程：執行掃描作業。      |
| `application/commands/` | `.command.ts` | `manual-retain` | 流程：手動提交 URL 抓取。 |

**策略（Policies / Sagas）**

| 位置                    | 後綴         | 名稱                              | 業務理由                               |
| :---------------------- | :----------- | :-------------------------------- | :------------------------------------- |
| `application/policies/` | `.policy.ts` | `scheduled-scan-orchestrator`     | Saga：排程掃描編排器（分解掃描單元）。 |
| `application/policies/` | `.policy.ts` | `on-scan-completed-emit-findings` | Saga：掃描完成後發布發現到 Discovery。 |

---

### 3.6 AI 分流上下文（AI Triage Context）(`apps/ai-triage`)

**範疇**：AI 策略執行、LLM 呼叫、推論記錄、系統註解產生。

> **注意**：AI Triage 沒有獨立的 `libs/`，領域邏輯直接放在 `apps/ai-triage/` 內部。

#### 驅動適配器 - HTTP (`apps/ai-triage`)

| 位置                            | 後綴             | 名稱            | 業務理由                       |
| :------------------------------ | :--------------- | :-------------- | :----------------------------- |
| `adapters/driving/http/triage/` | `.controller.ts` | `triage`        | 進入點：處理 AI 分流請求。     |
| `adapters/driving/http/triage/` | `.req.dto.ts`    | `triage`        | 輸入：單筆/批次分流請求。      |
| `adapters/driving/http/triage/` | `.res.dto.ts`    | `triage-result` | 輸出：分流結果（含推論理由）。 |

#### 被驅動適配器 (`apps/ai-triage`)

| 位置                         | 後綴          | 名稱                | 業務理由                          |
| :--------------------------- | :------------ | :------------------ | :-------------------------------- |
| `adapters/driven/gemini/`    | `.adapter.ts` | `gemini-llm`        | 整合：Gemini 2.5 Flash LLM 呼叫。 |
| `adapters/driven/firestore/` | `.adapter.ts` | `firestore-finding` | 讀取：從 Firestore 讀取 Finding。 |
| `adapters/driven/pubsub/`    | `.adapter.ts` | `comment-publisher` | 整合：發布系統註解到 Discovery。  |

#### 領域層 (`apps/ai-triage/domain/`)

**聚合（Aggregates）**

| 位置                 | 後綴            | 名稱         | 業務理由                               |
| :------------------- | :-------------- | :----------- | :------------------------------------- |
| `domain/aggregates/` | `.aggregate.ts` | `triage-job` | 核心規則：分流作業狀態、策略匹配邏輯。 |

**值物件（Value Objects）**

| 位置                    | 後綴     | 名稱            | 業務理由                        |
| :---------------------- | :------- | :-------------- | :------------------------------ |
| `domain/value-objects/` | `.vo.ts` | `triage-result` | 值物件：分流結果（動作+推論）。 |
| `domain/value-objects/` | `.vo.ts` | `llm-prompt`    | 值物件：LLM 提示詞組裝。        |

**事件（Events）**

| 位置             | 後綴        | 名稱                  | 業務理由               |
| :--------------- | :---------- | :-------------------- | :--------------------- |
| `domain/events/` | `.event.ts` | `triage-started`      | 事實：分流作業已開始。 |
| `domain/events/` | `.event.ts` | `triage-completed`    | 事實：分流作業已完成。 |
| `domain/events/` | `.event.ts` | `strategy-matched`    | 事實：策略已匹配。     |
| `domain/events/` | `.event.ts` | `no-strategy-matched` | 事實：無策略匹配。     |

#### 應用層 (`apps/ai-triage/application/`)

**命令（Commands）**

| 位置                    | 後綴          | 名稱             | 業務理由                 |
| :---------------------- | :------------ | :--------------- | :----------------------- |
| `application/commands/` | `.command.ts` | `execute-triage` | 流程：執行單筆 AI 分流。 |
| `application/commands/` | `.command.ts` | `batch-triage`   | 流程：執行批次 AI 分流。 |

**策略（Policies）**

| 位置                    | 後綴         | 名稱                              | 業務理由                       |
| :---------------------- | :----------- | :-------------------------------- | :----------------------------- |
| `application/policies/` | `.policy.ts` | `on-triage-completed-add-comment` | Saga：分流完成後發布系統註解。 |

---

在實作功能時，根據以下情境選擇實作路徑：

### 情境 A：處理狀態變更（寫入）- 命令流程

_範例：`CreateTask`、`EditPhase`、`PauseTask`、`ManualRetain`、`AssignRisk`_

1. **定義輸入（Adapter）**：在 `adapters/driving/http/<feature>/` 中建立 **ReqDto**。
    - 使用 `class-validator` 進行驗證規則（例如：`@IsString`、`@IsNotEmpty`）。
2. **定義介面（Port）**：在 `ports/driving/` 中定義命令介面。
    - 通常包含 `execute(payload): Promise<void>` 方法。
3. **實作流程（Application）**：建立實作端口的 **Command** 類別並標記為 `@injectable`。
    - 透過建構函數使用 `@inject` 注入 Repository。
    - 呼叫 Repository `findById` 載入聚合。
    - 呼叫聚合業務方法（例如：`task.pause()`）。
    - 呼叫 Repository `save`。
    - 發布領域事件。
    - ⛔ **禁止**在此處撰寫業務規則邏輯。
4. **實作邏輯（Domain）**：在聚合內部撰寫驗證邏輯和狀態變更。
    - 驗證失敗時拋出 `DomainError`。
5. **連接 API（Adapter）**：建立 **Controller**，注入命令，並處理 HTTP 請求。
6. **註冊 DI（Infrastructure）**：在 `di.ts` 中註冊命令和 Repository 綁定。

### 情境 B：處理資料檢索（讀取）- 查詢流程

_範例：`GetTaskList`、`ViewDetails`、`ExportReport`_

1. **定義輸出（Adapter）**：在 `adapters/driving/http/<feature>/` 中建立 **ResDto**。
2. **實作查詢（Application）**：建立 **Query** 類別並定義篩選參數。
    - ⛔ **注意**：不要載入聚合。
3. **實作存取（Adapter）**：在被驅動適配器中實作讀取邏輯（例如：`FirestoreTaskAdapter`）。
    - 直接使用 Firestore/Algolia SDK 讀取資料。
    - 直接將原始資料映射到 `ResDto` 並返回。
4. **註冊 DI（Infrastructure）**：在 `di.ts` 中註冊查詢。

### 情境 C：處理副作用與複雜流程（Sagas）

_範例：`ScheduledScan`、`AITriageAudit`_

1. **發布事件（Domain）**：確認聚合操作已產生 `DomainEvent`。
2. **監聽事件（Application）**：在 `application/policies/` 中建立 **Policy**。
3. **執行動作**：
    - **簡單**：策略直接呼叫服務。
    - **複雜（Saga）**：策略編排多個步驟。
4. **記錄**：確保記錄 `Execution Log` 或 `System Comment`。

## 5. 關鍵實作規則（應做與禁止）

### 5.1 領域層

-   ✅ **應做**：在聚合方法內撰寫所有業務規則。
-   ✅ **應做**：使用 `private` 方法封裝複雜驗證。
-   ⛔ **禁止**：匯入任何適配器或應用層物件。
-   ⛔ **禁止**：使用公開的 Setters。

### 5.2 應用層

-   ✅ **應做**：保持命令精簡；它們僅負責流程控制。
-   ✅ **應做**：將 Saga/長時間執行的流程邏輯放在策略中。
-   ⛔ **禁止**：在命令內撰寫業務邏輯（例如：`if (status === 'ARCHIVED')`）。

### 5.3 適配器層

-   ✅ **應做**：將 DTOs、Mappers 和 Controllers **就近放置**在同一個**功能資料夾**中（例如：`http/tasks/`）。
-   ✅ **應做**：區分驅動 DTO（`.req.dto.ts` / `.res.dto.ts`）和持久化 DTO（`.firestore.dto.ts`）。
-   ✅ **應做**：依照 **基礎設施 > 業務上下文** 組織被驅動適配器（例如：`driven/firestore/users/`）。
-   ⛔ **禁止**：使用「Repository」作為檔案名稱；改用「Adapter」。

## 6. 命名慣例

為確保專案一致性，所有檔案必須使用以下後綴：

| 層級                      | 檔案類型         | 後綴                 |
| :------------------------ | :--------------- | :------------------- |
| **領域層（Domain）**      | 聚合             | `*.aggregate.ts`     |
|                           | 實體             | `*.entity.ts`        |
|                           | 值物件           | `*.vo.ts`            |
|                           | 事件             | `*.event.ts`         |
| **應用層（Application）** | 命令             | `*.command.ts`       |
|                           | 查詢             | `*.query.ts`         |
|                           | 策略（含 Sagas） | `*.policy.ts`        |
| **端口層（Ports）**       | 命令介面         | `*.command.port.ts`  |
|                           | Repository 介面  | `*.port.ts`          |
| **適配器層（Adapters）**  | 控制器           | `*.controller.ts`    |
|                           | 被驅動適配器     | `*.adapter.ts`       |
|                           | 請求 DTO         | `*.req.dto.ts`       |
|                           | 回應 DTO         | `*.res.dto.ts`       |
|                           | 持久化 DTO       | `*.firestore.dto.ts` |
| **基礎設施層（Infra）**   | DI 配置          | `di.ts`              |

## 7. Domainarium 整合模式 (Domainarium Integration Patterns)

### 7.1 Authentication Bridge Pattern

Detective 3 採用「驗證橋接模式」與 Domainarium 整合：

-   **Domainarium 角色**: Identity Provider (IdP) — 負責帳號密碼驗證、2FA
-   **Firebase 角色**: Session Manager — 負責 Session Token 管理與 Firestore Security Rules
-   **核心原則**: Detective 3 不儲存密碼，僅在登入時呼叫 Domainarium API
-   **整合原則**: **Inbound Read-Only** — 從 Domainarium 讀取，不寫回

**登入流程**:

1.  使用者輸入帳密 → Detective 3 轉發至 Domainarium
2.  Domainarium 驗證成功 → 回傳 User Info + Portfolios
3.  Detective 3 Mint Firebase Custom Token (含 Claims)
4.  後續操作使用 Firebase Token，不再呼叫 Domainarium

### 7.2 Token 有效期策略

| Token 類型        | 有效期  | 說明                                     |
| :---------------- | :------ | :--------------------------------------- |
| Domainarium Token | 28 分鐘 | Safety Buffer (Server 30min - 2min 緩衝) |
| Firebase Session  | 1 小時  | 標準設定                                 |

**刷新策略**: 僅在需要「寫回 Domainarium」時檢查 Token，若過期則背景刷新。

### 7.3 Hybrid Authorization (大型權限清單處理)

針對 Firebase Custom Claims 1KB 限制：

| 情境            | 儲存位置                                             | 驗證方式               |
| :-------------- | :--------------------------------------------------- | :--------------------- |
| < 50 portfolios | Token Claims                                         | Security Rules 直讀    |
| ≥ 50 portfolios | Firestore `/users/{uid}/private_profile/permissions` | Security Rules `get()` |

**Token 標記**: 超過 50 個 portfolio 時，Claims 僅存 `has_large_portfolio: true`。

### 7.4 Domainarium API 關鍵端點

| 端點             | 用途             | 使用情境                       |
| :--------------- | :--------------- | :----------------------------- |
| `validate_user`  | 登入驗證         | 登入 + 2FA 驗證                |
| `get_user`       | 使用者資訊       | 取得使用者詳細資料             |
| `get_group_list` | Portfolio 列表   | 品牌同步 (`SyncBrandsCommand`) |
| `get_user_list`  | Portfolio 使用者 | 權限同步                       |
| `get_sub_groups` | 子群組列表       | 權限範圍設定                   |

**角色對應 (Role Mapping)**:

| Domainarium `user_group_id` | Detective Role  |
| :-------------------------- | :-------------- |
| 1                           | SystemAdmin     |
| 2                           | ServiceManager  |
| 3                           | ClientAccount   |
| 4                           | ExternalPartner |

> **建議**: Phase 1 使用 Config 檔案 (`config/role-mappings.json`) 即可，Phase 2+ 若需動態調整再改為 Firestore。

---

## 附錄 A. RBAC 權限字典（RBAC Permissions Dictionary）

### A.1 系統角色

| 角色                                | 說明                              |
| :---------------------------------- | :-------------------------------- |
| **系統管理員 (System Admin)**       | 最高權限，管理使用者與系統設定    |
| **客戶經理 (Account Manager)**      | IP Twins 員工，管理客戶品牌與任務 |
| **客戶帳號 (Client Account)**       | 品牌端員工，審查發現與匯出報告    |
| **外部合作夥伴 (External Partner)** | 受限存取，僅可審查指定品牌的發現  |

### A.2 權限清單 (Scope-based)

#### 身份與存取管理

-   `iam:users:create` - 建立使用者，指派角色
-   `iam:settings:global` - 全域系統設定
-   `iam:audit:export` - 匯出系統稽核日誌

#### 品牌上下文

-   `brand:sync` - 同步品牌資料
-   `brand:assets:read` - 檢視受保護資產
-   `brand:assets:write` - 建立/編輯資產
-   `brand:assets:delete` - 刪除資產

#### 監控

-   `monitoring:tasks:read` - 檢視任務與洞察報告
-   `monitoring:tasks:write` - 建立/編輯任務、管理標籤
-   `monitoring:tasks:control` - 暫停/恢復/封存任務
-   `monitoring:tasks:delete` - 刪除任務
-   `monitoring:phases:write` - 編輯階段、範圍與 AI 策略

#### 爬取

-   `crawling:retain:manual` - 手動保留

#### 發現

-   `discovery:findings:read` - 檢視發現列表
-   `discovery:findings:triage` - 指派風險、變更狀態、註解、套用標籤
-   `discovery:takedown:request` - 請求下架
-   `discovery:export` - 匯出列表或統計報告

#### AI Triage

-   `ai:triage:execute` - 執行 AI 自動 Triage 策略

---

## 附錄 B. 資料命名與格式規範（Data Naming & Format Standards）

### B.1 命名慣例

| 類型                | 格式       | 範例                               |
| :------------------ | :--------- | :--------------------------------- |
| **欄位名稱**        | camelCase  | `platformType`, `usageCount`       |
| **Enum 值**         | PascalCase | `'Marketplace'`, `'ToBeProcessed'` |
| **Collection 名稱** | camelCase  | `userAccounts`, `monitoringTasks`  |
| **檔案名稱**        | kebab-case | `create-task.command.ts`           |

### B.2 ID 產生策略

-   **根命名空間**: `UUID v5(Firebase Project Domain, DNS_NS)`
-   **集合 UUID**: `UUID v5(Collection Name + Parent UUID, Root Namespace)`
-   **文件 ID**: `UUID v5(UUID v1, Collection UUID)`

### B.3 時間戳記

-   所有時間戳記使用 **MicroTimestamp**（自 epoch 以來的微秒數）
-   型別定義：`type MicroTimestamp = number`

### B.4 共用原始類型

```typescript
type MicroTimestamp = number // 微秒時間戳記
type UUID = string // 通用唯一識別碼
type UrlString = string // URL 位址
type HtmlString = string // HTML 格式內容
type TailwindClass = string // Tailwind CSS 類別
```

## 8. Monorepo 開發指引 (pnpm + tsup + Firebase Functions)

本章節說明如何使用 **pnpm** 作為套件管理器、**tsup** 作為打包工具，建構 Firebase Functions 的 Monorepo 專案。

### 8.1 目錄結構概覽

```text
├── Makefile
├── apps
│   └── <function-name>           # 每個 Firebase Function 為獨立 app
│       ├── Makefile
│       ├── package.json
│       ├── src
│       │   └── index.ts
│       ├── tsconfig.json
│       └── tsup.config.ts
├── firebase.json
├── libs
│   └── <library-name>            # 共享程式庫
│       ├── package.json
│       ├── src
│       │   └── index.ts
│       └── tsconfig.json
├── package.json
├── pnpm-lock.yaml
├── pnpm-workspace.yaml
└── tsconfig.base.json
```

### 8.2 基礎骨架設定

#### 8.2.1 初始化專案

```bash
git init
pnpm init
```

#### 8.2.2 建立 `pnpm-workspace.yaml`

```yaml
packages:
    - "libs/*"
    - "apps/*"
```

#### 8.2.3 建立 `tsconfig.base.json`

```json
{
    "compilerOptions": {
        "module": "commonjs",
        "target": "ES2020",
        "lib": ["ES2020"],
        "strict": true,
        "noImplicitReturns": true,
        "noUnusedLocals": true,
        "skipLibCheck": true,
        "esModuleInterop": true
    }
}
```

#### 8.2.4 Firebase 初始化

```bash
firebase init functions
# 選擇 TypeScript
# 選擇 No ESLint
# 選擇 No Overwrite (如有詢問)
```

#### 8.2.5 移動目錄並修改 `firebase.json`

```bash
mv functions apps/<function-name>
```

修改 `firebase.json`：

```diff
{
  "functions": [
    {
-     "source": "functions",
+     "source": "apps/<function-name>/dist",
-     "codebase": "default",
+     "codebase": "<function-name>",
    }
  ]
}
```

### 8.3 建立共享程式庫 (`libs/`)

以 Logger 為例：

```bash
cd libs/logger
pnpm init
pnpm add pino
pnpm add -D typescript @types/node
```

#### 8.3.1 修改 `package.json`

```diff
- "name": "logger",
+ "name": "@detective/logger",
```

> **命名慣例**：使用 `@detective/` 作為 Scope 前綴。

#### 8.3.2 建立 `tsconfig.json`

```json
{
    "extends": "../../tsconfig.base.json",
    "compilerOptions": {
        "declaration": true,
        "declarationMap": true,
        "outDir": "dist",
        "rootDir": "src"
    },
    "include": ["src"],
    "exclude": ["node_modules", "dist"]
}
```

#### 8.3.3 建立 `src/index.ts`

```typescript
import pino from "pino"

const pinoInstance = pino({
    level: "info",
    base: { service: "detective" },
    formatters: {
        level: label => {
            return { severity: label.toUpperCase() }
        },
    },
})

export const logger = {
    info: (msg: string, data?: object) => pinoInstance.info(data, msg),
    warn: (msg: string, data?: object) => pinoInstance.warn(data, msg),
    error: (msg: string, err?: any) => pinoInstance.error({ err }, msg),
}
```

### 8.4 完善 Function App (`apps/`)

#### 8.4.1 安裝依賴

```bash
# 專案根目錄
pnpm add -D -w tsup typescript rimraf

# apps/<function-name> 目錄
pnpm add -D @types/node
pnpm add @detective/logger --workspace
```

#### 8.4.2 建立 `tsup.config.ts`

這是核心打包邏輯，負責產生部署用的 `dist/package.json`：

```typescript
import { defineConfig, Options } from "tsup"
import {
    writeFile,
    symlink,
    unlink,
    copyFile,
    access,
    readFile,
} from "fs/promises"
import { existsSync } from "fs"
import path from "node:path"

const CWD = __dirname
const DIST_DIR = path.join(CWD, "dist")
const FIREBASE_FUNCTIONS_DEFAULT_VERSION = "^12.7.0"
const FIREBASE_ADMIN_DEFAULT_VERSION = "^3.4.1"

const createPackageJson = async () => {
    const fileContent = await readFile(path.join(CWD, "package.json"), "utf8")
    const pkg = JSON.parse(fileContent) as any

    const dependencies = pkg.dependencies || {}
    const functionsVersion =
        typeof dependencies["firebase-functions"] === "string" &&
        dependencies["firebase-functions"].length > 0
            ? dependencies["firebase-functions"]
            : FIREBASE_FUNCTIONS_DEFAULT_VERSION

    const adminVersion =
        typeof dependencies["firebase-admin"] === "string" &&
        dependencies["firebase-admin"].length > 0
            ? dependencies["firebase-admin"]
            : FIREBASE_ADMIN_DEFAULT_VERSION

    const cloudDeps = {
        "firebase-functions": functionsVersion,
        "firebase-admin": adminVersion,
        // 如有其他 runtime 依賴，請在此新增
    }

    const packageJson = JSON.stringify(
        {
            name: pkg.name,
            main: "index.js",
            engines: { node: "20" },
            type: "commonjs",
            dependencies: cloudDeps,
        },
        null,
        2
    )

    const pathSavedTo = path.join(DIST_DIR, "package.json")
    console.log(`📦 [tsup] Saved deployment package.json to ${pathSavedTo}`)
    await writeFile(pathSavedTo, packageJson)
}

const symlinkNodeModules = async () => {
    const target = path.join(CWD, "node_modules")
    const link = path.join(DIST_DIR, "node_modules")
    try {
        await access(target)
    } catch (e) {
        console.warn(
            "⚠️ [tsup] Target node_modules not found. Skipping symlink."
        )
        return
    }

    try {
        if (existsSync(link)) await unlink(link)
        await symlink(target, link, "junction")
        console.log(
            "🔗 [tsup] Symlink created: dist/node_modules -> ../node_modules"
        )
    } catch (error) {
        console.warn(
            "⚠️ [tsup] Failed to create symlink:",
            (error as Error).message
        )
    }
}

const copyEnvToDist = async () => {
    const envFiles = [".env", ".env.local"]
    let copiedCount = 0

    for (const filename of envFiles) {
        const sourcePath = path.join(CWD, filename)
        const destPath = path.join(DIST_DIR, filename)

        try {
            await access(sourcePath)
            await copyFile(sourcePath, destPath)
            console.log(`📋 [tsup] Copied ${filename} to dist/`)
            copiedCount++
        } catch (error) {}
    }

    if (copiedCount !== 0) return
    console.log(
        "⚠️ [tsup] No .env files found/copied. (If using Firebase config, ignore this warning)"
    )
}

export default defineConfig((options: Options) => ({
    entry: ["src/index.ts"],
    format: ["cjs"],
    minify: !options.watch,
    clean: false,
    outDir: "dist",
    sourcemap: true,
    external: ["firebase-functions", "firebase-admin"],

    onSuccess: async () => {
        await Promise.all([
            createPackageJson(),
            copyEnvToDist(),
            symlinkNodeModules(),
        ])
        console.log("✅ [tsup] Build and deployment preparation completed.")
    },
}))
```

#### 8.4.3 修改 `package.json`

```diff
{
- "name": "functions",
+ "name": "@detective/<function-name>",
- "main": "lib/index.js",
+ "main": "dist/index.js",
- "engines": { "node": "22" },
+ "engines": { "node": "20" },
}
```

#### 8.4.4 修改 `tsconfig.json`

```json
{
    "extends": "../../tsconfig.base.json",
    "compilerOptions": {
        "moduleResolution": "node",
        "outDir": "dist",
        "sourceMap": true,
        "resolveJsonModule": true,
        "baseUrl": ".",
        "paths": {
            "@detective/logger": ["../../libs/logger/src"]
        }
    },
    "include": ["src", "tsup.config.ts"],
    "exclude": ["node_modules", "dist"]
}
```

> **說明**：
>
> -   `paths` 須包含所有引用的共享程式庫。
> -   `include: ["tsup.config.ts"]` 避免 IDE 開啟時報錯。
> -   `exclude: ["node_modules", "dist"]` 提升編譯效能。

### 8.5 Makefile 自動化

#### 8.5.1 `apps/<function-name>/Makefile`

```makefile
.PHONY: install build watch serve deploy clean

.ONESHELL:

install:
	@echo "📦 Installing dependencies from root..."
	@cd ../.. && pnpm install

build:
	@echo "🔨 Building function..."
	@pnpm exec tsup
	@echo "✅ Build complete."

watch:
	@echo "🔨 Building function..."
	@pnpm exec tsup --watch
	@echo "✅ Build complete."

serve:
	@echo "🚀 Starting emulator..."
	@firebase emulators:start --only functions

deploy:
	@echo "☁️  Deploying to Firebase..."
	@firebase deploy --only functions

clean:
	@echo "🧹 Cleaning dist..."
	@pnpm exec rimraf dist
```

### 8.6 HTTP Trigger 範例

`apps/<function-name>/src/index.ts`：

```typescript
import { setGlobalOptions } from "firebase-functions/v2"
import { onRequest } from "firebase-functions/v2/https"
import { logger } from "@detective/logger"

setGlobalOptions({ maxInstances: 10 })

export const helloWorld = onRequest((request, response) => {
    logger.info("hello world", {
        structuredData: true,
        target: "Moriarty",
        evidence_count: 5,
    })
    response.json({
        status: "ok",
        message: "Hello from Monorepo!",
        timestamp: new Date().toISOString(),
    })
})
```

### 8.7 常用指令

| 指令           | 說明                             |
| :------------- | :------------------------------- |
| `make install` | 從根目錄安裝所有依賴             |
| `make build`   | 打包 Function 至 `dist/`         |
| `make watch`   | 開發模式，檔案變更時自動重新打包 |
| `make serve`   | 啟動 Firebase Emulator           |
| `make deploy`  | 部署至 Firebase                  |
| `make clean`   | 清除 `dist/` 目錄                |
