# KẾ HOẠCH API DANH SÁCH THÀNH VIÊN & PHÒNG BAN + ĐĂNG NHẬP (CSM → aom.vuphong.com)

> **Trạng thái tổng quát (cập nhật 10/07/2026): ĐÃ TRIỂN KHAI XONG & LIVE trên production (chế độ strict).** CSM login + directory đã bật trên `aom.vuphong.com`; 13 user được cấp grant (map từ role AOM cũ); menu "Quản trị & Người dùng" đã gỡ; 2064 tham chiếu user đã migrate sang `csmUserId` (có backup). Còn lại: vài mục tuỳ chọn ở §9.7/§9.8 (permissions bổ sung, rút ngắn hạn JWT, test browser enforceGrant/CSM-down) và 3 user AOM cũ chưa có tài khoản CSM. Không tích khống: mọi ô `[x]` đã đối chiếu code/thao tác thật.

## 📌 PHÂN ĐỊNH: PHẦN NÀO CODE Ở ĐÂU?

Tài liệu này có 2 phần code **tách biệt hoàn toàn**, do 2 repo khác nhau triển khai:

| | **PHẦN A — code trên CSM** (`csm.vuphong.vn`, repo CRM) | **PHẦN B — code trên AOM** (`aom.vuphong.com`, repo này `E:\WWW\www\AMOM-VPEG`) |
|---|---|---|
| Vai trò | Cung cấp API (bên phục vụ) | Gọi API CSM để kế thừa dữ liệu (bên tiêu thụ) |
| Nội dung | Model `IntegrationClient`, middleware xác thực, route `/api/integrations/{members,departments,auth/verify}`, màn hình cấp/thu hồi API key + grant | Form đăng nhập AOM gọi hộ CSM, gọi API lấy danh sách thành viên/phòng ban, **thôi tự lưu user/mật khẩu nội bộ** |
| Mục nào trong tài liệu | **Mục 2 → 8** (spec + checklist CSM) | **Mục 9** (checklist code thực tế phía AOM) |
| Ai đọc | Đội backend CSM | Đội build AOM |

Mục 3–7 là **hợp đồng API (spec)** dùng chung — AOM đọc để biết cách gọi, **không code lại**. Việc code cụ thể của AOM nằm gọn trong **mục 9**.

> ⚠️ **Đừng nhầm với tích hợp CSM hiện có của AOM.** AOM đã có sẵn 1 tích hợp CSM **khác** (đồng bộ dữ liệu nghiệp vụ): route **inbound** `/api/integrations/csm/*` (projects, assets, contracts, tickets, work-orders, warranty) trong `backend/src/routes/integrations.routes.ts`, bảo vệ bằng `serviceAuth` (`CSM_SERVICE_TOKEN`), + webhook **outbound** `backend/src/services/csmWebhookClient.ts` đẩy trạng thái ngược về CSM. Tài liệu này nói về **một luồng riêng biệt**: AOM là **client** gọi sang CSM để lấy **danh bạ nhân sự** và **xác thực đăng nhập**. Hai luồng dùng 2 secret khác nhau (xem mục 7).

---

## 1. MỤC TIÊU

CSM (`csm.vuphong.vn`) là nơi lưu **danh sách thành viên** (`SettingsUser`) và **phòng ban** (`Department`) đầy đủ, mới nhất của công ty. Hiện tại AOM **tự quản lý user riêng**:

- Bảng `users` + mật khẩu bcrypt riêng (`backend/src/models/User.ts`),
- Màn hình CRUD user riêng (`pages/Governance.tsx` → `/api/users`),
- Đăng nhập nội bộ riêng (`POST /api/auth/login`, so bcrypt, tự ký JWT).

Mục tiêu là AOM **kế thừa danh tính + đăng nhập từ CSM** giống MTL/PROJECT đã làm, để nhân sự công ty **dùng 1 tài khoản CSM duy nhất** cho mọi webtool. Cụ thể AOM cần:

1. **Lấy danh sách thành viên + phòng ban** từ CSM để hiển thị/gán việc/lọc (dropdown người phụ trách ở phiếu O&M/work order, danh bạ ở Governance...) — **không lưu bản sao, gọi API CSM trực tiếp mỗi khi cần** (mục 5.3).
2. Cho phép người dùng **đăng nhập AOM bằng đúng email/mật khẩu CSM** — AOM giữ **form đăng nhập riêng**, nhưng backend AOM gửi email/password lên CSM để xác thực hộ (mục 6.1), **không tự so mật khẩu**.
3. **Kế thừa phân quyền**: AOM đã có sẵn hệ RBAC tốt (`Role` collection + `requirePermission`). Ta **giữ nguyên bộ máy RBAC đó**, chỉ đổi **nguồn gán vai trò**: thay vì admin AOM gán role trong Governance, vai trò AOM đến từ **grant do CSM cấp** (`webtool.roles`) — hoặc suy từ role/phòng ban CSM khi chưa cấp (mục 9.6–9.7).

CSM **là hệ thống chủ (source of truth) duy nhất** cho danh tính + mật khẩu. AOM **thôi CRUD user/mật khẩu**, chỉ **đọc** danh bạ và **xác thực nhờ** CSM.

> **Nguyên tắc chuyển tiếp an toàn:** toàn bộ tính năng này **mặc định TẮT** (gate `isCsmEnabled()` = có đủ `CSM_API_BASE_URL` + `CSM_INTEGRATION_KEY` trong `.env`). Khi chưa bật key, AOM chạy y như hiện tại (login nội bộ + Governance CRUD). Bật/tắt hoặc revert chỉ bằng thêm/xóa 2 biến `.env` + `pm2 restart aom-backend --update-env`. Giữ `users` cũ làm **cold fallback**, không xóa.

---

## 2. HIỆN TRẠNG (căn cứ code AOM hiện có)

- **User model** — `backend/src/models/User.ts`: `email (unique, lowercase, trim), password (bcrypt, select:false), name, role (string), department, status (Active|Suspended|Disabled), companyId, scope[], avatar`. Pre-save hash password + validate `role` tồn tại trong `Role`.
- **Role model** — `backend/src/models/Role.ts`: `name, code (uppercase, unique), permissions (Record<string,boolean>), isSystem, isActive`. Role hệ thống (seed `backend/src/scripts/seed-roles.ts`): `ADMIN, OM_MANAGER, TECHNICIAN, HSE_OFFICER, ASSET_OWNER`. Permission keys đang dùng: `view_dashboard, manage_assets, manage_users, view_finance, approve_tickets, config_system`.
- **Đăng nhập nội bộ** — `backend/src/controllers/auth.controller.ts`:
  - `login`: so `bcrypt` với `User.password`, ký JWT `{ userId, email }` bằng `JWT_CONFIG` (`backend/src/config/jwt.ts`).
  - `getMe`: `GET /api/auth/me` — trả user + populate company/scope + `attachUserRoleFields`.
  - `logout`.
- **Middleware** — `backend/src/middleware/auth.middleware.ts`: `authenticate` (verify JWT → nạp `User` theo email/`_id` → gắn `req.user`), `requirePermission(key)` (đọc `Role.permissions`, `code==='ADMIN'` bỏ qua), `authorize`, `allowSelfOrManageUsers`, `requireAnyPermission`.
- **CRUD user** — `backend/src/routes/users.routes.ts` + `users.controller.ts`, guard `manage_users`. Frontend: `pages/Governance.tsx`, `services/dataService.ts`, `services/authService.ts`, `services/apiClient.ts`.
- **Tích hợp CSM data-sync đã có** (KHÔNG phải phạm vi tài liệu này, chỉ để phân biệt): `integrations.routes.ts` mount `/api/integrations/csm/*` (serviceAuth), `csmWebhookClient.ts` đẩy webhook. Env đã có: `CSM_BASE_URL, CSM_SERVICE_TOKEN, CSM_PROJECT_SYNC_TOKEN, CSM_WEBHOOK_URL, CSM_WEBHOOK_TOKEN` (`.env.example`).
- **Hạ tầng deploy**: pm2 `aom-backend` (port 2123), Apache serve `dist_new/` cho `https://aom.vuphong.com/`, proxy `/api/` → `127.0.0.1:2123`. Deploy bằng `deploy.ps1` (build FE + BE + `pm2 restart aom-backend`).

---

## 3. KIẾN TRÚC TỔNG QUAN

AOM (bên tiêu thụ) gọi **server-to-server** sang CSM. **Không bao giờ** gọi thẳng từ browser người dùng cuối — tránh CORS và không lộ API key.

```
Browser AOM (form login / các trang cần danh bạ)
        │  gọi API nội bộ AOM như bình thường (Bearer JWT của AOM)
        ▼
Backend AOM (aom-backend, port 2123)   ← nơi giữ CSM_INTEGRATION_KEY
        │  Header: X-Integration-Key: <key riêng của AOM>
        ▼
CSM  https://csm.vuphong.vn/api/integrations/*   (đã tồn tại, KHÔNG qua phiên đăng nhập CSM)
        ├─ GET  /members            → Directory Sync API (đọc danh sách)
        ├─ GET  /departments        → Directory Sync API (đọc danh sách)
        └─ POST /auth/verify        → Auth Delegation API ("email/password này đúng không + được vào AOM không")
```

Biến môi trường AOM cần thêm (backend):

```
CSM_API_BASE_URL=https://csm.vuphong.vn/api/integrations
CSM_INTEGRATION_KEY=<key CSM cấp riêng cho AOM>
```

> Phân biệt secret: `CSM_SERVICE_TOKEN` (đã có) = CSM gọi **vào** AOM (inbound data-sync). `CSM_INTEGRATION_KEY` (mới) = AOM gọi **ra** CSM (outbound directory/auth). Hai chiều, hai key, không dùng lẫn.

---

## 4. HẠ TẦNG PHÍA CSM (đã có sẵn — AOM không phải code)

Model `IntegrationClient` + middleware `requireIntegrationAuth(scope)` + 3 route + `WebtoolAccessGrant` **đã được CSM xây dựng và deploy** (phục vụ MTL/PROJECT). Chi tiết spec xem `E:\WWW\www\MTL-VuPhong\docs\plan-api-member-csm.md` mục 4. Với AOM, phía CSM chỉ cần **cấp phát**, không code lại (xem checklist mục 8):

- Tạo 1 `IntegrationClient` tên **"aom"** với scope `members:read`, `departments:read`, `auth:verify` → sinh `rawKey` (hiển thị 1 lần) giao cho đội AOM.
- Bổ sung danh mục vai trò/quyền của AOM vào `WEBTOOL_ROLE_CATALOG` để CSM chọn khi cấp grant (mục 8bis.13).
- (Khi sẵn sàng chặt) tạo `WebtoolAccessGrant` cho từng user AOM + bật `enforceGrant`.

---

## 5. NHÓM API 1 — DIRECTORY SYNC (đọc danh sách thành viên/phòng ban)

Spec response giống hệt MTL (contract dùng chung). Tóm tắt:

### 5.1. `GET {CSM_API_BASE_URL}/departments` — scope `departments:read`
- Query tuỳ chọn: `updatedSince=<ISO>`.
- Trả mảng `{ id, name, code, parentId, managerId, status, updatedAt }`.

### 5.2. `GET {CSM_API_BASE_URL}/members` — scope `members:read`
- Query tuỳ chọn: `updatedSince`, `status` (mặc định `Active`, `status=all` để lấy cả inactive), `ids=id1,id2` (resolve đúng vài user theo `csmUserId` đã lưu).
- **Không bao giờ** trả `passwordHash/failedAttempts/lockUntil` (CSM đã lọc ở tầng query).
- Trả mảng `{ id, name, email, phone, role, roles[], department:{id,name,code}, position, status, updatedAt }`.

### 5.3. Không mirror — AOM gọi CSM trực tiếp (đề xuất chốt như MTL)
AOM **không** tạo bảng `members`/`departments` riêng. Màn hình cần danh bạ gọi thẳng qua backend AOM tại thời điểm cần. Hệ quả khi build:
- **Quan hệ dữ liệu**: record nghiệp vụ AOM (ticket, work order, assignment...) chỉ lưu **`csmUserId`** (chính là `SettingsUser._id` bên CSM) làm khoá tham chiếu, **không copy** `name/email` vào record — hiển thị thì gọi lại `/members?ids=...` để luôn đúng dữ liệu mới nhất.
- **Hiệu năng**: cache in-memory TTL ngắn (30–60s) trong backend AOM cho danh sách hay lặp (dropdown phòng ban) — **không lưu DB**, tự hết hạn.
- **Phụ thuộc uptime CSM**: màn hình liên quan danh bạ sẽ lỗi/chậm nếu CSM down — chấp nhận đánh đổi theo hướng "luôn mới nhất". AOM đã có sẵn nỗi đau tương tự với `/auth/me`, cần xử lý lỗi rõ ràng (không crash trang).
- **Lưu ý riêng AOM**: các trang O&M hiện lấy danh bạ qua `GET /api/users/directory` (`getUserDirectory`) và `GET /api/users`. Khi bật CSM, các endpoint này của AOM sẽ **proxy sang CSM `/members`** thay vì đọc `users` nội bộ (mục 9.3), để không phải sửa từng màn hình frontend.

---

## 6. NHÓM API 2 — AUTH DELEGATION (đăng nhập AOM bằng tài khoản CSM)

### 6.1. Cách hoạt động

```
Người dùng nhập email + password trên FORM ĐĂNG NHẬP CỦA AOM (không đổi UI)
        ▼
Backend AOM (server-to-server, KHÔNG phải browser)
   POST {CSM_API_BASE_URL}/auth/verify
   Header: X-Integration-Key: <key riêng AOM>
   Body:   { "email": "...", "password": "..." }
        ▼
CSM: so bcrypt với passwordHash của SettingsUser + tra WebtoolAccessGrant(user, aom)
   → 200 { valid:true, access:true,  user:{...}, webtool:{ roles:[...], permissions:[...] } }
   → 200 { valid:true, access:false, reason:'no_webtool_access', user:{ id,name,email } }   (đúng mật khẩu nhưng chưa được cấp quyền — chỉ khi enforceGrant=true)
   → 401 { valid:false }                                                                     (sai mật khẩu)
        ▼
Backend AOM tự ký JWT RIÊNG CỦA AOM (giữ nguyên cơ chế hiện tại), gán role AOM theo webtool.roles / map (mục 9.6–9.7)
```

- CSM **không** phát sinh token/phiên CSM, không ghi LoginLog CSM. Ghi `AuditLog` CSM với action `external_auth_verify`.
- **Rate limit riêng** + dùng chung cơ chế khoá tài khoản (`lockUntil`/`failedAttempts`) của CSM.
- AOM **không lưu** password/passwordHash dưới bất kỳ hình thức nào — chỉ forward qua HTTPS rồi bỏ.

### 6.2. Vì sao "verify hộ" thay vì tự so mật khẩu
- Password không rời khỏi kết nối HTTPS server-to-server; AOM không giữ mật khẩu.
- CSM đổi hash/thêm 2FA/khoá tài khoản → AOM không cần đổi gì.

### 6.3. Giai đoạn 2 (chưa làm) — SSO JWT trung tâm
Ghi nhận hướng mở rộng nếu ≥3–4 webtool cần "đăng nhập 1 lần dùng chung". AOM đợt này dùng form riêng + verify hộ, **không** làm SSO redirect.

---

## 7. BẢO MẬT & VẬN HÀNH (bắt buộc)

- [x] Toàn bộ gọi CSM chạy trên **HTTPS**, chỉ từ **backend AOM** (không từ browser).
- [x] `CSM_INTEGRATION_KEY` chỉ nằm trong `.env` backend AOM, **không** đưa ra frontend/bundle.
- [x] Không nhầm với `CSM_SERVICE_TOKEN` (inbound) — 2 secret riêng, 2 chiều riêng.
- [x] Áp `scopes` tối thiểu: AOM cần cả 3 (`members:read`, `departments:read`, `auth:verify`).
- [x] Không log password ở bất kỳ đâu (kể cả tạm/debug) trong luồng `/auth/verify`.
- [x] Gate `isCsmEnabled()` — thiếu key thì tính năng TẮT hẳn, AOM về login nội bộ.
- [x] Khi CSM bật: **tắt CRUD user + đổi mật khẩu nội bộ** ở AOM (UI read-only, backend trả 409/410) để tránh 2 nguồn chân lý.
- [x] Giữ `users` cũ làm cold fallback (khi CSM down / khi tắt key). **Không** drop collection.
- [x] Xử lý lỗi CSM down: login báo lỗi rõ ("CSM tạm thời không phản hồi"), trang danh bạ không crash.

---

## 8. 🔵 PHẦN A — CHECKLIST PHÍA CSM (repo CRM) cho AOM

Hạ tầng đã có (mục 4); phần này chỉ là **cấp phát cho AOM**:

| Hạng mục | Trạng thái |
|----------|-----------|
| 8.1 Tạo `IntegrationClient` tên "aom" (3 scope) + giao `rawKey` an toàn | ✅ Xong (prefix 2cf6a3cd) |
| 8.2 Bổ sung danh mục role/permission AOM vào `WEBTOOL_ROLE_CATALOG` | ✅ Xong (CRM deploy 10/07) |
| 8.3 (Chế độ open ban đầu) để `enforceGrant=false` cho client AOM | ⏭️ Bỏ qua — đi thẳng strict |
| 8.4 Tạo `WebtoolAccessGrant` cho user AOM rồi bật `enforceGrant=true` | ✅ Xong (13 grant + strict) |
| 8.5 Test `curl` 3 endpoint bằng key AOM (200 + ẩn field nhạy cảm) | ✅ Xong (members/departments/auth-verify) |

### 8bis.13. Danh mục vai trò/quyền của AOM để CSM hiển thị khi cấp grant
- [x] Cung cấp cho CSM danh mục **vai trò AOM** = đúng `code` của `Role` đang active: `ADMIN, OM_MANAGER, TECHNICIAN, HSE_OFFICER, ASSET_OWNER` (thêm vào `WEBTOOL_ROLE_CATALOG` dạng lowercase `admin, om_manager, technician, hse_officer, asset_owner`).
- [x] Danh mục **permission (scope)** AOM (khớp `Role.permissions` keys): `view_dashboard, manage_assets, manage_users, view_finance, approve_tickets, config_system`.
- [x] CSM lưu `webtool.roles` = 1 trong các `code` trên; AOM map `code` → `Role.name` để gán cho phiên (mục 9.7). Ưu điểm: **tái dùng nguyên bộ RBAC AOM**, chỉ đổi nguồn gán role.

---

## 9. 🟢 PHẦN B — CHECKLIST CODE TRÊN AOM (repo này) — **ĐÃ TRIỂN KHAI + LIVE (strict)**

Toàn bộ mục 9 làm trong `E:\WWW\www\AMOM-VPEG`. AOM chỉ **gọi API** đã có ở CSM. Module mới `backend/src/csm/{config,client,auth,directory,mapUser}.ts`.

> **Cập nhật 10/07/2026 — ĐÃ BẬT PRODUCTION (strict/enforceGrant=true).** CSM login live trên `aom.vuphong.com`: key trong `backend/.env` (`isCsmEnabled()`), `/api/auth/config`→`{csmEnabled:true}`. Đã cấp 13 grant (map từ role AOM cũ), gỡ menu "Quản trị & Người dùng", migrate 2064 tham chiếu user (`users._id`→`csmUserId`) kèm backup. Revert = comment 2 key `.env` + `.\deploy.ps1 -BEOnly`.

### 9.1. Xin cấp API key & cấu hình gate
- [x] Nhận key từ CSM (`IntegrationClient` "aom", prefix 2cf6a3cd), 3 scope đủ.
- [x] Thêm `CSM_API_BASE_URL` + `CSM_INTEGRATION_KEY` vào `backend/.env` (+ `.env.example`).
- [x] `isCsmEnabled()` trong `backend/src/csm/config.ts`.
- [x] Client HTTP `backend/src/csm/client.ts`: `fetch` + `X-Integration-Key`, timeout, `CsmError`, `unwrap()`.

### 9.2. Đăng nhập — verify hộ qua CSM (`auth.controller.ts`)
- [x] Giữ nguyên form login AOM — không đổi UI.
- [x] `login` rẽ nhánh `loginViaCsm` khi `isCsmEnabled()`; tắt CSM → giữ bcrypt nội bộ.
- [x] `valid:true,access:true` → ký JWT AOM mang `csmUserId`+`role`(AOM code)+`csm:true`; role theo `webtool.roles`.
- [x] `valid:true,access:false` → 403 đúng thông báo. `valid:false`/401 → sai mật khẩu. CSM down → 503.
- [x] Không lưu/log password.
- [x] `getMe` + `authenticate` middleware: khi `csm:true` dựng danh tính từ JWT (không tra `users`).

### 9.3. Danh bạ — proxy `/api/users` & `/api/users/directory` sang CSM
- [x] `getUsers`/`getUserDirectory` proxy CSM `/members` khi bật; tắt → `User.find` nội bộ.
- [x] Hỗ trợ `?ids=` (resolve theo csmUserId).
- [x] Cache in-memory TTL ~45s (`directory.ts`).
- [x] Chỉ gọi từ backend AOM.

### 9.4. Tắt CRUD user + đổi mật khẩu nội bộ khi CSM bật
- [x] `createUser/updateUser/deleteUser/resetPassword/changePassword` → 409 khi bật.
- [x] Frontend `Governance` read-only qua `dataService.isCsmManaged()` (`GET /api/auth/config`). *(Menu đã gỡ hẳn ở lượt sau — `constants.ts` + `App.tsx`.)*

### 9.5. Quan hệ dữ liệu — chỉ lưu khoá tham chiếu
- [x] Read-path resolve tên qua CSM khi bật: `resolveCsmUserNames()` (`csm/directory.ts`); `tickets.controller` — hoạt động (`performedBy`) resolve qua CSM, guard populate `reportedBy` (tránh null). Các field khác (assignedTo, workorder, managerId, createdBy, alerts.userId, auditlogs) resolve client-side qua directory.
- [x] Migrate dữ liệu cũ: `backend/scripts/migrate-user-refs-to-csm.mjs` (dry-run/`--apply`, match theo email, backup từng collection). Đã apply **2064** giá trị: tickets(reportedBy 33, assignedTo 13), workOrders(56+20+40), projects.managerId 37, reports.createdBy 11, alerts.userId 571, auditlogs(userId 1082, targetId 201). Backup `*_backup_csm_20260710052249`.
  - Còn lại **không remap** (đúng ý): giá trị là tên/chuỗi demo (Khánh Lê Duy, u3...) và id của user AOM không có tài khoản CSM (3 user chưa match) → giữ nguyên.

### 9.6. Phân quyền khi CHƯA có grant (chế độ open / fallback)
- [x] `mapCsmRoleToAomCode()` (`csm/mapUser.ts`): webtool.roles → AOM code; fallback heuristic role/dept; default `TECHNICIAN`.
- [x] Tái dùng RBAC: `req.user.role` = AOM code, `requirePermission`/`resolveRoleByUserField` chạy như cũ.

### 9.7. Tiêu thụ "quyền truy cập webtool" do CSM cấp (cặp mục 8bis)
- [x] `webtool.roles` không rỗng → dùng role đó; rỗng → fallback heuristic.
- [ ] (Tuỳ chọn, chưa làm) cộng thêm `webtool.permissions` ngoài `Role.permissions` — hiện chỉ dùng role, đủ dùng.
- [x] Chặn login khi `access:false` (403).
- [ ] (Tuỳ chọn, chưa làm) **Độ trễ thu hồi**: role đóng băng trong JWT tới khi hết hạn (`JWT_EXPIRES_IN`, mặc định 7d); cân nhắc rút ngắn/re-verify.

### 9.8. Kiểm thử
- [x] Login đúng/sai mật khẩu qua CSM (user test OK; sai mật khẩu → 401 sạch).
- [x] Danh bạ/dropdown hiển thị từ CSM (members/departments verify).
- [x] `tsc` sạch + build FE + deploy `deploy.ps1`; `/api/auth/config`=true.
- [ ] (Nên làm) Test enforceGrant: user không grant → 403; có grant → đúng role — kiểm bằng tài khoản CSM thật trên browser.
- [ ] (Nên làm) Test CSM down (timeout/5xx) → login báo lỗi rõ; và revert (tắt key) → login nội bộ.

### 9.9. Thứ tự triển khai đề xuất (giảm rủi ro)
1. 9.1 (key + gate + client) — chưa đổi hành vi.
2. 9.6 + 9.2 (login verify hộ, chế độ open) — bật thử trên 1 tài khoản.
3. 9.3 (proxy danh bạ) + 9.4 (read-only Governance).
4. 9.5 (khoá tham chiếu / migrate nếu cần).
5. 9.7 + phối hợp CSM cấp grant → bật `enforceGrant` (chặt).

---

> **Ghi chú deploy (AOM):** mọi thay đổi phía AOM đẩy live bằng `E:\WWW\www\AMOM-VPEG\deploy.ps1` (build FE→`dist_new`, build BE→`backend/dist`, `pm2 restart aom-backend`). Revert CSM nhanh: xóa `CSM_API_BASE_URL` + `CSM_INTEGRATION_KEY` khỏi `backend/.env` rồi `pm2 restart aom-backend --update-env` → AOM về login nội bộ + Governance CRUD như cũ.

> **Tham chiếu:** spec chi tiết Directory/Auth API (dùng chung) xem `E:\WWW\www\MTL-VuPhong\docs\plan-api-member-csm.md`. Tiền lệ tiêu thụ phía consumer (login verify hộ, map role, proxy danh bạ, migrate) xem repo PROJECT (`E:\DEV\user-001\project`, `backend-api/src/csm/*`).
