Sheet 9 · System Messages, Security & API Reference
Phụ lục & Hệ thống
Tài liệu tra cứu kỹ thuật toàn diện cho CTC ERP: Từ điển thông điệp hệ thống chuẩn hoá (IEM/EM/SM/CM), danh mục 60+ API Endpoints trong sync-server, quy tắc bảo mật Whitelist relay, và cơ chế đồng bộ realtime SSE dạng Revision Blob.
1. Từ điển Thông điệp Hệ thống (System Messages)
Toàn bộ thông điệp giao diện và lỗi nghiệp vụ được định danh theo mã chuẩn: IEM (Lỗi inline tại trường dữ liệu) · EM (Lỗi thao tác nghiệp vụ) · SM (Thông báo thành công) · CM (Hộp thoại xác nhận thao tác).
1.1. IEM — Inline Error Messages (Lỗi trường nhập liệu)
Mã
Quy trình
Nội dung thông điệp tiếng Việt
Tiêu chí kích hoạt
IEM1
Tạo Task
Tiêu đề công việc không được để trống hoặc chỉ chứa khoảng trắng.
Độ dài chuỗi title.trim().length === 0
IEM2
Tạo Task / Dự án
Hạn hoàn thành (Due date) phải bằng hoặc sau ngày bắt đầu.
dueDate < startDate
IEM3
Toàn hệ thống (*)
Trường này là bắt buộc, vui lòng không bỏ trống.
Thiếu trường bắt buộc (Mandatory) lúc blur/submit
IEM4
Hoá đơn / Đối tác
Mã số thuế không đúng định dạng (10 số hoặc 13 số).
Regex kiểm tra MST doanh nghiệp Việt Nam
IEM5
Chốt Deal
Giá trị hợp đồng (dealValue) phải là số dương lớn hơn 0.
dealValue <= 0 khi chuyển sang won
IEM6
Đổi mật khẩu
Mật khẩu mới phải có ít nhất 8 ký tự, bao gồm cả chữ và số.
Validation mật khẩu tài khoản cá nhân
1.2. EM — Operation Error Messages (Lỗi nghiệp vụ)
Mã
Quy trình
Nội dung thông điệp tiếng Việt
Tiêu chí kích hoạt
EM1
Lưu dữ liệu (*)
Không thể lưu — vui lòng kiểm tra lại các trường dữ liệu được đánh dấu đỏ.
Client validation thất bại trước khi gửi request
EM2
Đồng bộ Realtime
Xung đột phiên bản dữ liệu (Optimistic Lock). Đang tự động tải lại bản mới nhất...
Mã lỗi 409 Conflict khi clientRev !== serverRev
EM3
Phân quyền RBAC
Bạn không có quyền truy cập module này. Vui lòng liên hệ Admin cấp quyền.
Tài khoản member không có mã module trong modules[]
EM4
Duyệt chi NCC
Khoản chi vượt quá 5.000.000đ yêu cầu phải có phê duyệt từ Ban Giám đốc.
Tài khoản không phải Admin duyệt chi lớn
EM5
Relay Webhook
Domain webhook không nằm trong danh sách an toàn (Whitelist). Gửi thất bại.
Domain không khớp whitelist bảo mật của server
1.3. SM — Success Messages (Thông báo thành công)
Mã
Quy trình
Nội dung thông điệp tiếng Việt
Ghi chú
SM1
Tạo Task
Đã tạo công việc <Mã task> thành công.
Hiển thị toast và focus thẻ task mới
SM2
Chốt Deal
Chúc mừng! Đã chuyển Deal sang trạng thái THẮNG (Won) và khởi tạo Dự án.
Kích hoạt workflow thông báo Telegram
SM3
Xuất Hoá đơn
Đã phát hành hoá đơn <Số HĐ> thành công. Đã cập nhật công nợ AR.
Ghi nhận vào sổ hoá đơn GTGT
SM4
Ghi nhận tiền
Đã ghi nhận thanh toán <Số tiền> vào sổ tài chính công ty.
Tự động sinh Transaction income
SM5
Review Thread
Đã đánh dấu hoàn tất (Resolved) phiên thảo luận review.
Ẩn pin khỏi màn hình active
1.4. CM — Confirmation Messages (Hộp thoại xác nhận)
Mã
Quy trình
Nội dung câu hỏi xác nhận
Mục đích
CM1
Xoá bản ghi
Bạn có chắc chắn muốn xoá bản ghi này? Thao tác này sẽ được ghi vào nhật ký hệ thống.
Chống bấm nhầm mất dữ liệu
CM2
Đóng Dự án
Xác nhận nghiệm thu và đóng dự án <Tên DA>? Hồ sơ sẽ chuyển sang chế độ Chỉ-đọc.
Khoá chỉnh sửa sau nghiệm thu
CM3
Duyệt chi > 5tr
Xác nhận phê duyệt thanh toán <Số tiền> cho NCC <Tên Vendor>?
Kiểm soát dòng tiền của BOD
CM4
Duyệt Dev Request
Phê duyệt yêu cầu phát triển <Mã YC> và chuyển sang trạng thái triển khai?
Admin phê duyệt tính năng
1.5. Thông điệp MÁY CHỦ trả về — bảng tra khi gặp lỗi
Khác các bảng 1.1–1.4 ở trên: những bảng đó là đặc tả thông điệp giao diện (mã IEM/EM/SM/CM, dùng khi thiết kế và kiểm thử). Bảng này là chuỗi thật trích nguyên văn từ sync-server/*.js ngày 03/09/2026 — thứ người dùng thấy khi có lỗi, và thứ tra được khi cần biết vì sao. Cột "Xử lý" là việc cần làm, không phải mô tả lỗi.
Phân quyền & quyền chủ sở hữu
Thông điệp
Mã HTTP
Nguyên nhân & xử lý
Chỉ chủ sở hữu hệ thống (Owner) mới được thay đổi phân quyền người dùng.
403
Tài khoản là admin nhưng không nằm trong ERP_OWNERS. Quản trị và chủ hệ thống là hai tầng khác nhau — xem trang Vai trò.
Không đổi được tên đăng nhập của tài khoản chủ sở hữu — quyền tối cao gắn với tên này.
400
Quyền owner suy từ username khớp ERP_OWNERS, nên đổi tên là mất quyền. Muốn đổi thì sửa env trước.
Không hạ quyền hoặc khoá tài khoản chủ sở hữu.
400
Chốt chống tự khoá mình ra ngoài hệ thống.
Không xoá được tài khoản chủ sở hữu hệ thống.
400
Như trên.
phải còn ít nhất 1 admin · phải còn ít nhất 1 admin đang hoạt động
400
Không thể hạ hoặc khoá người quản trị cuối cùng.
thiếu quyền module <tên>
403
Tài khoản chưa được cấp phân hệ đó. Cấp ở Cài đặt → Người dùng & phân quyền.
bạn chỉ được xem kỹ năng này
403
Được chia sẻ ở mức Xem, không phải mức Sửa. Nhờ chủ sở hữu nâng mức, hoặc nhân bản thành của mình.
chỉ người tạo được sửa · chỉ người tạo được xoá
403
Quy trình và kỹ năng đi theo chủ sở hữu, kể cả quản trị cũng không sửa hộ.
Đường trợ lý ngoài (/api/agent/*)
Thông điệp
Mã HTTP
Nguyên nhân & xử lý
khoá dịch vụ không hợp lệ
401
Thông điệp hay gây hiểu sai nhất. Gần như luôn là $ERP_API_KEYrỗng, không phải khoá hỏng — mỗi lệnh shell là một tiến trình riêng, nạp khoá ở lệnh này rồi dùng biến ở lệnh sau là mất. Kiểm bằng echo ${#ERP_API_KEY}: ra 71 là khoá có, ra 0 là workspace chưa có khoá.
ERP chưa cấu hình ERP_AGENT_KEY
503
Khoá dịch vụ chung không đặt trên production (có chủ đích). Dùng khoá theo người dùng, cấp ở Cài đặt → Khoá API.
Hành động "<tên>" không có. Dùng: viec, tim, help, them, xong, trangthai, han, tao_ky_nang, tao_quy_trinh.
400
Bảng hành động là allowlist. Thông điệp tự liệt kê tên hợp lệ nên trợ lý sửa được ngay mà không cần tra tài liệu.
Tài khoản <tên> chưa được cấp phân hệ "<phân hệ>" nên không dùng được lệnh "<lệnh>".
403
Kèm trường missingModule để trợ lý báo lại đúng việc cần xin — thay vì im lặng trả danh sách rỗng khiến người dùng tưởng mình không có việc nào.
Đã tạo quá 40 bản nháp trong 24 giờ. Thử lại ngày mai.
429
Hạn tính theo người, không theo khoá — một người nhiều khoá vẫn là một người.
rate_limited
429
Quá 120 lượt/phút trên một khoá, hoặc 300 lượt/phút từ một IP. Đợi rồi thử lại, đừng lặp ngay.
Bản nháp không phải JSON hợp lệ · Bản nháp phải là một object JSON · Bản nháp quá lớn (giới hạn 32KB)
200 (ok:false)
Tham số arg của tao_ky_nang/tao_quy_trinh phải là chuỗi JSON, không phải object.
Bản nháp thiếu trường "name".
200 (ok:false)
Trường bắt buộc duy nhất. Thiếu trường khác thì bỏ qua — bản ghi là nháp, người dùng sửa tiếp.
Mã không đúng hoặc đã dùng · Mã đã hết hạn (sống 10 phút) · Thử quá nhiều lần, đợi 10 phút.
200 / 429
Liên kết Telegram bằng mã 6 số: sống 10 phút, 5 lần thử mỗi 10 phút theo cặp (khoá, tgUserId).
Telegram này chưa liên kết tài khoản ERP
200 (needLink)
Chỉ xảy ra ở đường khoá dịch vụ chung. Khoá theo người dùng đã mang sẵn danh tính nên không cần liên kết.
Giới hạn kích thước & hạn mức
Thông điệp
Mã HTTP
Ngưỡng thật
ảnh quá lớn (giới hạn 8MB)
413
Hình nền (/api/wallpaper) — ảnh điện thoại thường 3–6MB nên nắp thấp hơn là chặn oan.
ảnh quá lớn (>3MB sau khi nén)
413
Ảnh đính vào Yêu cầu phát triển.
ảnh đại diện quá lớn (>64KB)
413
Ảnh hồ sơ, lưu thẳng trong bản ghi nên phải nhỏ.
File vượt quá giới hạn 25MB
413
Tệp Thư viện.
Đã vượt hạn mức lưu trữ 2GB của tài khoản. Hãy xoá bớt file cũ.
413
Tổng dung lượng Thư viện mỗi người.
nội dung quá lớn (>200KB) · quy trình quá lớn · nội dung quá lớn
413
Lần lượt: nội dung chung, def quy trình (64KB), def kỹ năng (128KB).
Quá 60 lượt tải lên trong 1 giờ · Bạn đã tải quá nhiều ảnh trong 1 giờ · Quá 10 lần tạo mã trong 1 giờ
429
Hạn theo người theo giờ, giữ trong bộ nhớ tiến trình.
Một câu cho cả hai trường hợp — không tiết lộ username nào tồn tại.
Quá nhiều lần thử, đợi vài phút.
429
Chống dò mật khẩu.
Mật khẩu hiện tại không đúng · Mật khẩu mới tối thiểu 8 ký tự
400
Đổi mật khẩu: 5 lần / 10 phút.
tài khoản ERP không hợp lệ hoặc đang bị khoá
200 (ok:false)
Tài khoản có active=0. Cũng là lý do quy trình tự tắt nếu chủ sở hữu bị khoá.
Hai thông điệp còn trong mã nhưng người dùng không còn đường tới:"Chỉ quản trị viên (Admin) mới có quyền truy cập cài đặt AI Gateway" và "Chỉ chủ sở hữu hệ thống (Owner) mới được đổi cài đặt AI Gateway". Endpoint /api/ai-gateway vẫn tồn tại vì aiProvider(t) đọc cấu hình khoá HTTP từ đó cho các endpoint nội dung bất động sản, nhưng tab "Cài đặt AI" đã gỡ khỏi giao diện 03/09/2026 — nên hai câu này chỉ xuất hiện nếu ai đó gọi API trực tiếp.
2. Danh mục API Endpoints (sync-server)
Tất cả API chạy trên Node.js backend port 4114, xác thực qua Cookie phiên ode_sess (HMAC SHA-256, khoá ký dẫn xuất theo tenant nên cookie tenant A không dùng được ở tenant B) và cách ly theo Tenant Slug. Riêng nhóm /api/agent/* xác thực bằng header X-Agent-Key, không dùng cookie.
Về con số: server.js có 103 chỗ kiểm req.method === '…', nhưng một path thường được kiểm nhiều lần cho nhiều method nên đó KHÔNG phải số endpoint riêng biệt. Bản tài liệu trước ghi "60+" theo một cách đếm khác — nay nêu rõ cách đếm thay vì đưa một con số dễ hiểu sai.
Method
Endpoint Path
Quyền hạn
Mô tả chức năng
POST
/api/login
Public
Đăng nhập hệ thống, cấp cookie ode_sess có chữ ký HMAC SHA-256.
POST
/api/logout
Public
Đăng xuất, xoá cookie phiên làm việc.
GET
/api/me
Public / Auth
Lấy thông tin tài khoản hiện tại, vai trò và danh sách modules[] được cấp.
GET
/api/auth/verify
Public
Endpoint cho Caddy forward_auth kiểm tra hợp lệ của phiên đăng nhập.
POST
/api/me/password
User
Người dùng tự đổi mật khẩu tài khoản cá nhân.
GET
/api/state
User
Lấy toàn bộ JSON snapshot state mới nhất của tenant (theo rev hiện tại).
PUT
/api/state
User
Ghi đè snapshot state mới, tăng rev, tự động diff changelog và broadcast SSE.
GET
/api/events
User
Kênh Server-Sent Events (SSE) đẩy sự kiện realtime (state_update, mention...).
GET
/api/changes
User
Truy vấn lịch sử các thay đổi dữ liệu giữa các revision (phục vụ System Log).
Lưu dữ liệu per-user (giới hạn dung lượng ≤ 256KB/key).
GET
/api/workflows
User
Lấy danh sách các workflow tự động hoá thuộc quyền sở hữu hoặc được chia sẻ.
POST
/api/workflows
User
Tạo mới quy trình tự động hoá (React Flow JSON canvas).
PUT
/api/workflows/:id
Owner/Admin
Cập nhật cấu hình trigger, condition và action của workflow.
DELETE
/api/workflows/:id
Owner/Admin
Xoá quy trình tự động hoá.
GET
/api/workflow-runs
User
Xem lịch sử các lần chạy tự động hoá và log thực thi.
GET
/api/skills
User
Lấy danh sách sổ tay kỹ năng và SOP chuẩn hoá.
POST
/api/skills
User
Tạo kỹ năng mới hoặc import từ file Markdown có sẵn.
PUT
/api/skills/:id
Owner/Admin
Cập nhật nội dung kỹ năng, checklist và nâng version.
DELETE
/api/skills/:id
Owner/Admin
Xoá kỹ năng.
GET
/api/requests
User
Xem danh sách đề xuất phát triển kỹ năng/quy trình mới.
POST
/api/requests
User
Gửi yêu cầu phát triển mới.
POST
/api/requests/review
Admin
Admin duyệt/từ chối yêu cầu qua 5 trạng thái new→reviewing→approved→released.
POST
/api/lead-ingest
Public / Token
Tiếp nhận Lead từ bên ngoài (HubSpot, Form, Landing Page).
POST
/api/me/relay/telegram
User
Gửi tin nhắn thông báo ra bot Telegram (kiểm tra token an toàn).
POST
/api/me/relay/gws
User
Gửi webhook sang Google Workspace / Zapier (kiểm tra Whitelist).
POST
/api/viber/webhook
Public
Tiếp nhận webhook tin nhắn từ kênh Viber/Zalo.
GET
/api/library/items
User
Lấy danh sách tài liệu, liên kết và tệp trong Thư viện chung.
POST
/api/library/items
User
Tạo liên kết mới (tự bóc metadata link) hoặc upload tệp đính kèm (≤ 25MB).
GET
/api/library/boards
User
Lấy danh sách các Board phân loại tài liệu.
POST
/api/library/boards
Admin
Tạo Board chia sẻ tài nguyên mới.
Nhóm mới / thay đổi trong tháng 9/2026
Method
Endpoint
Quyền
Mô tả
POST
/api/agent/whoami
X-Agent-Key
Khoá này đại diện cho ai, còn hiệu lực không
POST
/api/agent/context
X-Agent-Key
Trả system + context (đã lọc hai tầng) + actions + keySkills
POST
/api/agent/action
X-Agent-Key
Thực thi 1 trong 9 hành động allowlist; tên ngoài danh sách → 400
POST
/api/agent/link
X-Agent-Key
Liên kết Telegram với tài khoản ERP bằng mã 6 số (sống 10 phút, 5 lần thử/10 phút)
GET / POST
/api/api-keys
Đăng nhập; cấp cho người khác cần owner
Danh sách và cấp khoá API. Bản rõ trả về một lần duy nhất
POST
/api/api-keys/revoke
Chủ khoá hoặc owner
Thu hồi khoá, hiệu lực tức thì
GET / POST
/api/api-keys/by-skill
Chủ sở hữu kỹ năng
Phát hành / gỡ kỹ năng khỏi skill_ids của từng khoá
POST
/api/me/sessions/revoke-all
Owner
Tăng session_ver toàn tenant → mọi phiên đang có mất hiệu lực
POST
/api/wallpaper
Đăng nhập
Tải ảnh nền từ máy (multipart, ≤8MB, jpg/png/webp/gif/avif)
GET
/api/wallpaper/<tệp>
Chỉ chính chủ ảnh
Xem ảnh nền; tên tệp mang id người tải, admin cũng không xem được ảnh người khác
POST
/api/skills
Chủ sở hữu, hoặc người được chia sẻ mức Sửa
Người mức Sửa ghi được nội dung; sharedWith/sharedEdit/published họ gửi lên bị bỏ qua
POST
/api/workflows
Chủ sở hữu
Vá 03/09:active chỉ đổi khi người gọi thực sự gửi trường đó
Đã gỡ 03/09/2026:POST /api/ai-draft (sinh bản nháp bằng CLI mô hình), khối /api/bots/ai-models, POST /api/bots/inbound. Xem trang Quy trình mục 5b để biết lý do.
3. Quy tắc Bảo mật Whitelist Relay & An toàn Dữ liệu
Bảo mật Tích hợp Ngoài (Relay Whitelist):
Để ngăn ngừa tấn công SSRF (Server-Side Request Forgery), endpoint /api/me/relay/gws chỉ chấp nhận chuyển tiếp request tới các domain trong danh sách Whitelist cứng:
script.google.com & *.googleusercontent.com (Google Apps Script Web Apps)
chat.googleapis.com (Google Chat Webhooks)
hooks.zapier.com (Zapier Integration)
api.telegram.org (Telegram Bot API)
Mọi URL trỏ tới dải IP private (127.0.0.1, 10.x, 192.168.x) hoặc domain lạ đều bị từ chối ngay lập tức với mã lỗi HTTP 403 Forbidden.
4. Cơ chế Đồng bộ Realtime SSE & Revision Blob
Hệ thống áp dụng kiến trúc Optimistic State Sync kết hợp Server-Sent Events (SSE):
Single Source of Truth: Toàn bộ state nghiệp vụ của 1 tenant được gói trong 1 JSON document lưu trong bảng revisions (rev, state_json, author_id, created_at).
Optimistic Locking: Khi client gửi PUT /api/state kèm expectedRev:
Nếu expectedRev === currentRev: Server ghi snapshot mới với rev = currentRev + 1, diff ra danh sách bản ghi thay đổi và broadcast SSE sự kiện state_update tới tất cả các client khác.
Nếu expectedRev < currentRev: Server trả về mã lỗi 409 Conflict. Client nhận lỗi sẽ tự động kéo state mới về, thực hiện 3-way merge và thông báo cho người dùng nếu có xung đột trường.
Materialized Views: Để phục vụ truy vấn báo cáo và REST API nhanh, server tự động giải nén JSON blob thành các dòng trong 9 bảng SQLite (tasks, contacts, activities, projects, events, invoices, transactions, notifications, users). Nguồn sự thật tuyệt đối vẫn là blob trong bảng revisions.
Giới hạn Lịch sử: Bảng revisions lưu tối đa 1.000 phiên bản gần nhất để tối ưu dung lượng ổ đĩa; định kỳ hàng tuần server chạy lệnh VACUUM INTO để nén file SQLite dự phòng an toàn.
5. Hạ tầng, triển khai & kiểm thử (cập nhật 03/09/2026)
Toàn bộ mục này là mới — hệ thống đã chuyển sang VPS riêng trong ngày 03/09/2026 và cách triển khai không còn giống bản tài liệu trước.
5.1 Máy chủ và đường dẫn thật
Hạng mục
Giá trị
SSH host
ctc-erp (unix user erp). Ngoài ra ctc = root, ctc-smarttech, ctc-kazuki cho các user khác trên cùng máy.
/home/erp/erp-platform/logs/erp-crm-sync-out.log. Lọc nhanh: grep '[agent]' cho đường trợ lý, grep '[workflows]' cho việc tạo/sửa quy trình.
Tên miền
Production ctc.erp.smarttechctc.io · Thử nghiệm staging-erp.smarttechctc.io · Tài liệu erp-docs.smarttechctc.io
5.2 Triển khai — rsync, không phải git
Dự án KHÔNG có remote git. Repo chỉ tồn tại local; mọi đường triển khai đi bằng rsync qua SSH. Không có git clone, không có git pull trên máy đích.
# 1. Đẩy mã nguồn (loại node_modules, dữ liệu và bí mật — xem deploy/rsync-exclude.txt)
./deploy/sync-to-vps.sh ctc-erp erp-platform # production
./deploy/sync-to-vps.sh ctc-erp erp-staging # thử nghiệm
DRY=1 ./deploy/sync-to-vps.sh ctc-erp # xem trước, không ghi gì
# 2. Trên máy đích: build web (tự hoán đổi thư mục build và kiểm BUILD_ID)
cd erp-platform/frontend-next && ./deploy.sh
ERP_WEB_APP=erp-staging-web ./deploy.sh # bản thử nghiệm
# 3. Sau khi sửa backend
pm2 restart erp-crm-sync
pm2 restart --update-env KHÔNG nạp env mới từ ecosystem — phải pm2 delete rồi pm2 start. Rollback web: rm -rf .next && mv .next-prev .next && pm2 restart erp-next.
5.3 Bộ công cụ trong deploy/
Tệp
Việc
bootstrap.sh <base-domain>
Dựng từ một VPS trắng
sync-to-vps.sh + rsync-exclude.txt
Đẩy mã nguồn. Danh sách loại trừ có bẫy: nó từng loại *.png nên nuốt luôn logo trang đăng nhập — nay giữ lại tường minh frontend-next/public/** và frontend-next/src/assets/**.
sanitize-tenant.js
Làm sạch một DB trước khi chuyển máy. Chế độ --minimal giữ mật khẩu và khoá API, chỉ xoá bí mật cấu hình và token chia sẻ.
scan-secrets.js
Quét 10 mẫu khoá trên mọi bảng và cả trong blob state; chỉ in vị trí và vân tay, không in giá trị.
caddy-*.example
Mẫu cấu hình Caddy cho từng tenant và cho site tài liệu
5.4 Kiểm thử
20 tệp trong sync-server/test/, 84 test case, chạy bằng node --test sync-server/test/*.test.js — không cần framework ngoài. Nhóm khoá chặt các lỗ hổng đã vá: rbac-escalation, rbac-settings, rbac-endpoints, rbac-data, session-ver, session-salt, tenant-isolation, invoice-number, skill-share (chia sẻ ba mức), agent-draft (trợ lý dựng nháp — gồm một ca ép hai lớp kiểm phân hệ phải khớp nhau), codes (ép hai bản quy tắc mã khớp nhau).
5.5 Ràng buộc phải giữ
Ràng buộc
Vì sao
CLIENT_SCHEMA = 14 — không bump
Toàn bộ state client nằm trong một chuỗi JSON lồng {version:14, state:{…}}. Bump là mọi client cũ mất dữ liệu.
shadcn trên Base UI, không Radix
Component viết theo API Base UI (vd ToggleGroup luôn nhận mảng giá trị kể cả khi chọn một). Lẫn sang Radix là vỡ kiểu.
Hai bản quy tắc mã phải khớp
frontend-next/src/erp/lib/codes.ts và bản chép cho script sync-server/migrate-codes.js. Sửa một bên phải sửa bên kia — test/codes.test.js ép điều đó.
Hai tầng lọc ở mọi đường đọc
scopeStateForUser(filterStateForUser(inner, actor), actor). Thiếu tầng hai là rò dữ liệu cả công ty.