Danh tính và phân quyền¶
Nền quản trị · nhóm 2 · 7 story · 🟡 chưa viết xong · còn 6 mốc
Chưa viết xong: còn 6 mốc
Phần chưa viết hiển thị tiêu chí dự kiến từ kế hoạch (đã qua cổng PO duyệt) và mốc chờ agent làn docs.
| Story | Service | Thứ tự | Ghi chú |
|---|---|---|---|
E-001/2.1 Người dùng nội bộ |
admin-be | 9 | cap/iam-internal-users · AD-11 mã bất biến, chỉ vô hiệu không xoá |
E-001/2.2 Adapter xác thực và đăng nhập tự quản |
admin-be | 10 | cap/iam-internal-users · AD-1 port · nợ N1 không làm MFA |
E-001/2.3 Phiên có trạng thái |
admin-be | 11 | cap/iam-internal-users · AD-19 · hết hạn khi không hoạt động: 15 phút (PO chốt 09/09, NFR-003 vế 1) · ⚠️ vế 2 «xác thực lại để thao tác nhạy cảm» CHƯA có đáp |
E-001/2.4 Vai và quyền cấu hình được lúc chạy |
admin-be | 13 | cap/iam-internal-users · xếp SAU 3.1 vì gán vai phải qua duyệt · ✅ PO chốt 11/09 tối — thêm CRUD danh mục quyền (nguyên văn với admin-fe: «danh mục quyền là cái cấu hình được, xem thêm sửa xoá được, anh cần nó control được qua admin»): quyền có đối tượng · hành động · tên hiển thị · mô tả · lớp (chức năng / đặc quyền duyệt — K3) · trạng thái; UNIQUE(đối tượng,hành động), mã khuôn <đối tượng>.<hành động> (read · create · update · disable + đặc quyền approve, publish…); ⛔ không xoá, chỉ ngưng hoạt động (AD-11); mọi thay đổi vai · quyền · gán qua duyệt 3.1 (FR-004, AD-9). Tham chiếu cơ chế đã chạy ở TingPos permissions(resource, action, display_name) · ma trận vai×quyền gom theo resource · ⚠️ PO SỬA 12/09 theo gói design/roles/: ⛔ KHÔNG qua duyệt — chỉ super admin (iam.super_admin) gọi được các đường này, hiệu lực ngay khi lưu, mỗi thao tác ghi nhật ký 4.1 mức cảnh báo kèm tài khoản thực hiện (K3 lệch có chủ ý, K2 bù) · 13 đối tượng seed đúng mã + thứ tự gói (user · role · permission · tpp_organization · application · tpp_request · api · api_product · api_product_plan · domain · subdomain · api_mock · notification_template) · mã quyền tự sinh, không đổi sau khi tạo · hành động đổi theo lớp: chức năng read/create/update/disable, đặc quyền approve/reject/publish/approve_prod/rotate_approve; quyền lớp đặc quyền mang thêm hai trường đối tượng chịu tác động · khi duyệt thì điều gì xảy ra · vai có mã vai (in hoa/số/_/. ≤ 64), số quyền, số người dùng, updated_at · gán vai ghi granted_at, thu hồi ghi revoked_at không xoá hàng (K1) + đường đọc lịch sử cấp/thu hồi theo tài khoản · người không phải super admin: 403 phong bì AD-16 mã iam.super_admin · ⛔ điều kiện ĐÓNG là 4.1 archive — nhật ký cảnh báo là lớp bù duy nhất cho K3 lệch, ⛔ không ship «hiệu lực ngay» khi chưa có vết; mở change được ngay (4.1 đang thi công) |
E-001/2.5 Super admin có giới hạn |
admin-be | 14 | cap/iam-internal-users · FR-050 · đường mồi qua dòng di trú · ⚠️ PO SỬA 12/09: câu 1 của FR-050 («không tự phê duyệt đề xuất cấp quyền») tạm không áp ở GĐ1 — super admin là người duy nhất phân quyền và làm có hiệu lực ngay; ba câu còn lại giữ: không sửa/xoá nhật ký · mọi thao tác ghi mức cảnh báo, tách riêng · giới hạn số lượng super admin + rà soát định kỳ. Dấu «là super admin» sống ở 2.4 (vai/cờ iam.super_admin), story này làm phần giới hạn + đường mồi |
E-001/2.6 API quản trị người dùng nội bộ |
admin-be | 31 | cap/iam-internal-users · mở 11/09/2026, PO chốt — liệt kê (phân trang · tìm · lọc trạng thái) · tạo tài khoản nội bộ · vô hiệu và mở lại (disabled_at, ⛔ không xoá — AD-11). ⛔ Lý do story tồn tại: 2.1 khai BẢNG, ⛔ không khai đường; openapi.yaml @ a26b48b có 4 đường (3 phiên + schema-migration) ⇒ màn 5.11 ⛔ không có nguồn dữ liệu nào. ⛔ Gán vai ⛔ KHÔNG thuộc story này (2.4 + 5.12). ⚠️ Tạo/vô hiệu là thao tác ràng buộc ⇒ ghi nhật ký (K2) — vì thế xếp sau 4.1. ~~✅ PO chốt 11/09 tối: cả ba thao tác qua khung phê duyệt 3.1~~ ⚠️ PO SỬA 12/09 (gói design/roles/ mục 6): ⛔ KHÔNG qua duyệt — tạo · vô hiệu · mở lại do super admin làm, hiệu lực ngay, ghi nhật ký 4.1 mức cảnh báo (K3 lệch có chủ ý GĐ1). Hệ quả: (a) 5.11 bỏ trạng thái «chờ duyệt», bộ từ còn Đang hoạt động · Ngưng hoạt động · (b) đường mồi thuộc story này: migration mồi một tài khoản super admin (không còn đòi hai — AD-9 câu 3 sửa 12/09), mật khẩu buộc đổi lần đầu, tự vô hiệu sau lần đăng nhập đầu theo AD-9, ghi nhật ký. 2.5 giữ nguyên sau 2.4 · đường liệt kê trả createdAt (cột Ngày tạo của 5.11, PO chốt 11/09) · ⛔ 3.1 không còn là điều kiện |
E-001/2.7 Tài khoản nội bộ theo mô hình super admin — bỏ đường đề xuất của 2.6 |
admin-be | 37 | cap/iam-internal-users · mở 12/09/2026 (luật 09/09: 2.6 đã archive theo bản 11/09 «qua đề xuất hai người» — 2026-09-12-internal-user-admin-api, platform: 61494f7 — ⛔ không sửa ngược, thêm story mới) · làm đúng hàng 2.6 bản 12/09: tạo · vô hiệu · mở lại do super admin (iam.super_admin, cổng đã có từ 2.4) hiệu lực ngay, ghi nhật ký 4.1 mức cảnh báo; bỏ đường đề xuất/duyệt đã dựng (đường HTTP + trạng thái chờ) — ⛔ AD-14.1 chỉ-thêm: bảng/cột đã có thì ngưng dùng, không drop; mồi một super admin · trả luôn OAPI-178 (admin-be#62 — vô hiệu super admin cuối qua đường hiện chạy: ⛔ không được vô hiệu super admin đang hoạt động cuối cùng, 409) · admin-fe 5.11 nối vào đường của story này (OAPI-145) |
Vì sao làm — BRD¶
→ Mục tiêu và giá trị nghiệp vụ nằm ở Tổng quan epic. Ở đây chỉ ghi mỗi story góp gì vào mục tiêu đó.
E-001/2.1 Người dùng nội bộ — admin-be¶
⟨CẦN NGƯỜI VIẾT — agent làn admin-be viết khi đóng change⟩
E-001/2.2 Adapter xác thực và đăng nhập tự quản — admin-be¶
⟨CẦN NGƯỜI VIẾT — agent làn admin-be viết khi đóng change⟩
E-001/2.3 Phiên có trạng thái — admin-be¶
Góp gì vào mục tiêu epic. Story này biến đăng nhập từ một lượt kiểm ở cửa thành một trạng thái mà hệ thống thu hồi được. Đó là khoảng cách giữa «tài khoản đã bị khoá» và «người đó thôi thao tác được» — trên một bề mặt phê duyệt hồ sơ, khoảng cách ấy đo bằng phút và phải giải trình được.
- Trước story này, bề mặt quản trị chưa có phiên nào cả.
E-001/2.2dựng xong cổng xác thực rồi dừng đúng ở đó: nó trả về «thành công, đây là người dùng» và không ai giữ kết quả ấy. Hệ quả đo được ở hai chỗ: màn đăng nhập đã giao vẫn chạy trên ba hàm giả tiêm từ ngoài, không một lời gọi mạng nào; và story hạ tầng phiên phía giao diện — đã đóng — lại treo trên một story chưa mở, khiến cổng thứ tự báo xanh giả đúng chỗ này. - «Khoá tài khoản» chỉ có nghĩa nếu nó đuổi được người đang ngồi trong nhà. Nếu khoá chỉ chặn
lượt đăng nhập kế tiếp thì một tài khoản bị khoá vì nghi ngờ vẫn duyệt hồ sơ tiếp cho tới
khi phiên tự nguội.
FR-006viết thẳng: phiên đang mở chấm dứt ngay, ⛔ không chờ hết hạn. - Cái bẫy ở đây im lặng, và story dựng ra để đóng đúng nó. Làm theo hướng «tra mốc thu hồi của phiên là đủ» thì phiên của một tài khoản đã bị khoá vẫn sống — và ⛔ không cổng nào thấy, vì mọi phép thử về phiên đều xanh. Vì vậy story giao cả lớp cưỡng chế, không chỉ giao chỗ chứa dữ liệu: phép kiểm mỗi yêu cầu đọc trạng thái tài khoản, ⛔ không chỉ đọc mốc thu hồi.
Vị trí trong hàng đợi. Nó gỡ chặn E-001/5.8 (hạ tầng phiên cho admin-fe) và làm trọn
E-001/5.7 (màn đăng nhập, hiện chạy nguồn giả). Nó đứng trước E-001/2.4 (vai và quyền lúc
chạy): story này trả lời «ai đang gọi», ⛔ chưa trả lời «người đó được làm gì».
E-001/2.4 Vai và quyền cấu hình được lúc chạy — admin-be¶
Góp gì vào mục tiêu epic. Đây là story FR-003 gọi tên: người vận hành tạo vai, sửa tập quyền, gán và
thu hồi vai mà không triển khai lại phần mềm — các vai ở PRD là dữ liệu khởi tạo, ⛔ không phải hằng
số trong mã. Trên main của admin-be chưa có bảng vai/quyền nào; màn vai và quyền (E-001/5.12) đang
chạy trên nguồn mẫu; và không tồn tại khái niệm super admin ở tầng nào — mọi người có phiên gọi được
mọi đường (giả định tường minh của 2.6, nay hết hiệu lực).
- Nó thi hành một quyết định PO ĐẢO CHIỀU ngày 12/09 — người đọc phải biết trước khi đọc bất cứ trang
nào khác của nhóm này. Gói
design/roles/của CCS về, PO chốt bỏ maker/checker cho phân quyền GĐ1: bốn nhóm thao tác (quyền · vai · gán/thu hồi vai · tài khoản nội bộ) do super admin làm, hiệu lực ngay khi lưu, ⛔ không qua người thứ hai. Đây là lệch có chủ ý vớiK3, ghi ở spineAD-9vàbacklog-tuan-thu.md§K3, bù bằng vết nhật ký mức cảnh báo kèm tài khoản thực hiện, ⛔ không cờ bật/tắt (gói vẽ cờ, thi công không có), điều kiện trả = cổng go-live. Mọi luồng khác (hồ sơ TPP, yêu cầu đăng ký, phạm vi, xoay khoá) giữ nguyênAD-9. - Nó biến «quyền» thành dữ liệu thật, theo đúng gói: danh mục đối tượng (13 của gói +
iamẩn) và hành động (4 chức năng · 5 đặc quyền · 1 hệ thống) là hai bảng mồi bằng di trú; mã quyền<đối tượng>.<hành động>do CSDL sinh, bất biến; ngưng là mốc, ⛔ không xoá; mọi lượt cấp/thu hồi (vai↔quyền, người↔vai) là hàng có mốc (K1), lịch sử là chính bảng. - Dấu super admin sống ở dữ liệu, ⛔ không phải cờ trên tài khoản: vai mồi
SUPER_ADMINgiữ quyền hệ thốngiam.super_admin; tài khoản mồi số 1 nhận vai ấy qua di trú — super admin đầu tiên; mồi số 2 không.
Vị trí trong hàng đợi. Thứ tự 13; hàng story vẫn ghi «xếp sau 3.1 vì gán vai phải qua duyệt» — câu đó
hết hiệu lực sau 12/09, thứ tự giữ nguyên. Mở khoá: 5.12 nối API (OAPI-163), cửa gán vai của 5.11,
và 2.6 làm lại theo 12/09 (bỏ đường đề xuất, chỉ super admin, mồi một super admin — change kế tiếp, PO
chốt thứ tự). Giới hạn số lượng và rà soát super admin là 2.5 (chưa có issue).
E-001/2.5 Super admin có giới hạn — admin-be¶
⟨CẦN NGƯỜI VIẾT — agent làn admin-be viết khi đóng change⟩
E-001/2.6 API quản trị người dùng nội bộ — admin-be¶
Góp gì vào mục tiêu epic. Story E-001/2.1 khai bảng người dùng nội bộ mà ⛔ không khai
đường: hợp đồng HTTP của admin-be chỉ có các đường phiên và di trú, nên màn người dùng
(E-001/5.11) đang chạy trên dữ liệu mẫu và tự khai «chưa nối». Nặng hơn: chuỗi story kết thúc bằng
một đường đăng nhập mà không ai đăng nhập được — không dòng di trú nào mồi tài khoản, không đường
nào đặt mật khẩu. Story này là chỗ cả trang quản trị có người dùng thật đầu tiên.
- Mọi thao tác trên danh tính nội bộ đi qua phê duyệt hai người — không đường tắt. PO chốt 11/09:
tạo · vô hiệu · mở lại đều là đề xuất qua khung
E-001/3.1, maker gửi, checker duyệt, áp ngay khi đủ cấp. Đây là lần đầu khung phê duyệt có bề mặt HTTP và có một loại đối tượng nghiệp vụ thật. - Mật khẩu ban đầu không do ai gõ. Hệ sinh lúc áp, trả một lần cho checker (người trao tay), maker không bao giờ biết; tài khoản mới buộc đổi ở lần đăng nhập đầu. Cơ chế này đóng nợ «không story nào là chủ của việc đặt mật khẩu» mà lens review 09/09 từng thu quyền ghi vì thiếu chủ.
- Đường mồi ≥ 2 tài khoản thuộc story này.
K3đòi maker ≠ checker nên một tài khoản mồi là không đủ để làm được bất cứ gì; email/họ tên từ cấu hình, mật khẩu từ hai file bí mật, thiếu ⇒ từ chối khởi động, tài khoản đã có ⛔ không bị đè. - Nó cắm vết thật cho bốn thao tác (
tai_khoan.tao·vo_hieu·mo_lai·doi_mat_khau) với chủ thể thật — đóng khoảnE-001/4.1cố ý hoãn, và là lý do story xếp sau4.1.
Vị trí trong hàng đợi. Thứ tự 31, mở 11/09 (PO chốt). Đứng trên 2.1 (bảng), 2.2 (adapter xác
thực), 3.1 (khung phê duyệt), 4.1 (nhật ký). Mở khoá: 5.11 nối dữ liệu thật (OAPI-145), màn đổi
mật khẩu và chính sách ở admin-fe (OAPI-134), và trả ba nợ OAPI-98 ② · OAPI-113 · OAPI-126. Gán vai
⛔ không thuộc story này (2.4, ngoài GĐ1) — hệ quả: mọi người dùng nội bộ có phiên đều mở và duyệt được
đề xuất, engine giữ maker ≠ checker.
E-001/2.7 Tài khoản nội bộ theo mô hình super admin — bỏ đường đề xuất của 2.6 — admin-be¶
Góp gì vào mục tiêu epic. Story này tồn tại vì E-001/2.6 archive theo bản 11/09 («cả ba thao tác qua
khung phê duyệt hai người») đúng một ngày trước khi PO đảo quyết định 12/09 theo gói design/roles/: tạo · vô
hiệu · mở lại tài khoản nội bộ do super admin làm, hiệu lực ngay, ghi nhật ký mức cảnh báo (K3 lệch có
chủ ý GĐ1). Luật 09/09 «không sửa story đã ship» ⇒ story mới thay «2.6 làm lại». Người đọc: trang 2.6 là
lịch sử; trang này là bản đúng cho tài khoản nội bộ.
- Nó vá hai lỗ đang sống trên
main. (a)OAPI-178: đường vô hiệu của2.6không biết vai — vô hiệu super admin hoạt động cuối cùng là hệ mất super admin, không đường hồi phục. (b)AD-9.3«tài khoản mồi tự vô hiệu sau lần đăng nhập đầu» chưa story nào thi hành — tài khoản mồi với mật khẩu do vận hành trao tay sống mãi. - Nó gỡ hẳn ba đường đề xuất của 2.6 khỏi mã và hợp đồng — không phải «tắt» bằng cờ, không 410 Gone: mã chết
sống thêm một lớp trong khi màn
5.11nối lại ngay lượt này. Dữ liệu ở tầng lưu trữ (policy, đề xuất treo, hàm cũ) ngưng dùng, không drop (AD-14.1). - Nó dùng lại cổng super admin của
2.4(PhanQuyenFilter, view quyền hiệu lực, tài khoản mồi số 1 giữSUPER_ADMINtừ V14) — không dựng cổng thứ hai. Và nó giữ cái đúng của 2.6: mật khẩu ban đầu hệ sinh, lộ một lần, buộc đổi; credential chỉ sinh được qua hàmSECURITY DEFINER, vai ứng dụng ⛔ khôngINSERTtrần.
Vị trí trong hàng đợi. Thứ tự 37, mở 12/09. Đứng trên 2.4 (cổng super admin), 2.6 (bảng, đường mồi V13,
đổi mật khẩu trong phiên), 4.1 (nhật ký). Mở khoá: 5.11 nối lại hai đường mới (OAPI-145 đã đóng trên đường cũ —
nhắn hình dạng trước), quy trình bàn giao tài khoản mồi cho uat. Trả nợ OAPI-178 và lệch hợp đồng 409 của đường
đổi mật khẩu. Giới hạn số lượng super admin vẫn là 2.5 (chưa có issue).
Ai dùng, dùng thế nào — URD¶
Không có bề mặt người dùng
Nhóm này chỉ có phần máy chủ hoặc hạ tầng — không cần URD (khong-can, có vết ở đây). Hành trình người dùng nếu có nằm ở nhóm có màn tương ứng.
Hệ làm gì, ràng buộc gì — PRD¶
Mỗi story một mục, phần máy chủ trước, giao diện sau. Story chưa đóng hiển thị tiêu chí dự kiến từ epics.md.
E-001/2.1 Người dùng nội bộ — admin-be¶
Chờ hồ sơ. Tiêu chí dự kiến từ kế hoạch:
- Tạo và khoá tài khoản; không có đăng ký tự do (
FR-001,FR-006). - Mã người dùng nội bộ bất biến làm danh tính; email công ty là khoá đối chiếu LDAP về sau (
AD-11,FR-049). INTERNAL_USERchỉ vô hiệu hoá bằngdisabled_at, không xoá (AD-4.3).
⟨CẦN NGƯỜI VIẾT — agent làn admin-be viết khi đóng change⟩
E-001/2.2 Adapter xác thực và đăng nhập tự quản — admin-be¶
Chờ hồ sơ. Tiêu chí dự kiến từ kế hoạch:
- Xác thực đi qua một port (
AD-1); giai đoạn này chỉ hiện thực đường tự quản (FR-002). - Chính sách mật khẩu và khoá tài khoản khai thành cấu hình trong dòng di trú, không rải trong mã.
- ⚠️ Không hiện thực xác thực đa yếu tố — khoản nợ
N1, xembacklog-tuan-thu.md.
⟨CẦN NGƯỜI VIẾT — agent làn admin-be viết khi đóng change⟩
E-001/2.3 Phiên có trạng thái — admin-be¶
Nguồn hồ sơ. admin-be @ origin/main —
openspec/changes/archive/2026-09-10-stateful-admin-session/ (proposal.md · design.md ·
specs/iam-internal-users/spec.md · tasks.md), SHA artifact cuối 04bd683. Issue
OAPI-109-phien-co-trang-thai (thangvv111/admin-be#36). Change đã đóng và archive
10/09/2026, verdict PASS @ 28e643c — ⚠️ tự chấm theo knob review.enforce=self, chưa có
người soi độc lập.
Yêu cầu và cách đo¶
| Yêu cầu | Ràng buộc | Đo bằng |
|---|---|---|
| Phiên sống ở phía máy chủ | Bảng riêng, giữ băm của mã phiên ⛔ không giữ mã phiên; băm là duy nhất. Trạng thái «còn sống» biểu diễn bằng mốc thu hồi rỗng, ⛔ không có thêm cột cờ nào nói cùng sự thật. Một tài khoản giữ được nhiều phiên cùng lúc | Đọc trọn một hàng bằng vai có quyền cao nhất ⇒ ⛔ không cột nào chứa mã phiên dùng lại được; cộng ca ghi mã thô vào cột băm ⇒ bị từ chối |
| Mỗi yêu cầu kiểm lại phiên ở phía máy chủ | Đọc trạng thái hiện tại từ tầng dữ liệu ở từng yêu cầu; ⛔ kết quả một lượt kiểm không dùng lại cho lượt sau. Bốn ca hỏng — không mã phiên · mã không tồn tại · phiên đã thu hồi · phiên quá hạn — trả cùng một phản hồi, ⛔ không cho biết là ca nào | Bốn lượt gọi cho bốn phản hồi bằng nhau về mã lỗi và nội dung phong bì |
| Khoá tài khoản chấm dứt phiên ngay | Phiên thôi dùng được ở yêu cầu kế tiếp, ⛔ không chờ hết hạn và ⛔ không phụ thuộc việc phiên đã được đánh dấu thu hồi hay chưa. Cưỡng chế nằm ở phép kiểm đọc trạng thái vô hiệu của tài khoản | Ca vô hiệu hoá bằng đường chỉ đổi trạng thái tài khoản, không chạm bảng phiên ⇒ yêu cầu vẫn bị từ chối |
| Hết hạn khi không hoạt động, ngưỡng là cấu hình ở tầng dữ liệu | Ngưỡng sống ở tầng dữ liệu và đổi được ⛔ không cần triển khai lại; bị chặn cả cận dưới lẫn cận trên ở tầng cơ sở dữ liệu. Mỗi yêu cầu đi lọt đẩy mốc thấy-lần-cuối lên hiện tại. ⛔ Phiên quá hạn không được ghi mốc thu hồi | Đổi ngưỡng khi một phiên đang mở ⇒ yêu cầu kế tiếp dùng giá trị mới; cộng ca đọc lại phiên quá hạn ⇒ mốc thu hồi vẫn rỗng |
| Mốc thu hồi là một chiều | Đã đặt thì ⛔ không xoá được, ⛔ không sửa được, bởi bất kỳ vai ứng dụng nào — thi hành ở tầng cơ sở dữ liệu. Lý do thu hồi có mặt khi và chỉ khi mốc thu hồi có mặt | Bốn ca: hồi sinh · sửa mốc · thu hồi không lý do · lý do mà không thu hồi ⇒ đều bị từ chối |
| Mọi mốc thời gian do tầng dữ liệu đặt | Giá trị bên ghi tự truyền bị bỏ qua, kể cả ở lượt tạo mới. ⛔ Không phép so thời gian nào dùng đồng hồ của tiến trình ứng dụng | Ca đẩy mốc thấy-lần-cuối tới tương lai xa ⇒ bị ghi đè, nên phiên ⛔ không sống lâu hơn ngưỡng |
| Bề mặt bên thứ ba ⛔ không chạm được phiên quản trị | Vai của cổng bên thứ ba ⛔ không có một quyền nào trên bảng phiên. Vai quản trị ⛔ không xoá và ⛔ không làm rỗng được bảng — thu hồi là cách duy nhất để một phiên thôi dùng được bằng thao tác chủ động | Từng vai thử xoá và thử làm rỗng ⇒ đều bị từ chối |
| Đóng phiên là thao tác một lần | Đóng lại một phiên đã đóng ⇒ lỗi xung đột trạng thái, ⛔ không ghi đè mốc đã có | Đóng hai lần ⇒ lần hai bị từ chối và mốc vẫn là mốc lần đầu; cộng ca đóng phiên của mình ⛔ không chạm phiên người khác |
Hai quyết định đáng đọc vì chúng chống một lỗi im lặng¶
- ⛔ Không có cột «hết hạn lúc». Hết hạn do không hoạt động là một phép tính, ⛔ không phải một hàng dữ liệu — nên cũng ⛔ không cần tác vụ nền dọn phiên chết. Hệ quả trực tiếp: đổi ngưỡng có hiệu lực ngay với phiên đang sống, thay vì chỉ với phiên mở sau đó.
- Ngưỡng nằm ở tầng dữ liệu, ⛔ không nằm trong cấu hình bản dựng. Đây là thứ bên phê duyệt quan tâm: đổi ngưỡng ⛔ không phải triển khai lại phần mềm.
⛔ Cố ý KHÔNG có trong story này¶
⛔ Xác thực lại cho thao tác nhạy cảm — nửa còn lại của NFR-003, PO chốt 10/09/2026 là khai
giả định chứ ⛔ không thi công. ⛔ Vai và quyền lúc chạy (E-001/2.4): phép kiểm ở đây trả
lời «ai đang gọi», ⛔ không trả lời «được làm gì». ⛔ Nhật ký kiểm toán cho đăng nhập và đăng
xuất (FR-007): thuộc E-001/4.1, chưa mở — change này ⛔ không đẻ bảng nhật ký thứ hai.
⛔ Đổi mật khẩu trong phiên: chưa có story máy chủ. ⛔ Tác vụ nền dọn phiên chết.
⚠️ Người duyệt cần biết — bốn chỗ¶
1. Một lỗ ở TẦNG YÊU CẦU, ⛔ không phải ở mã — và nó cần PO quyết. NFR-003 ⛔ chưa định
nghĩa «hoạt động». Vì mỗi yêu cầu đi lọt đều đẩy mốc thấy-lần-cuối lên, một tab để mở tự gọi
máy chủ có thể giữ phiên sống vô hạn — tức con số «15 phút» ⛔ không bảo đảm điều người đọc tưởng
nó bảo đảm. Mã làm đúng thứ hồ sơ khai; chỗ hở nằm ở chính yêu cầu. ⇒ Trang này cố ý không
viết câu tuyệt đối kiểu «quá 15 phút không thao tác là hệ thống tự đăng xuất». Nợ đã có địa
chỉ: OAPI-115, và nó ràng cả E-001/5.8 phía giao diện.
2. Giả định về xác thực lại là một tiền đề CÓ HẠN SỬ DỤNG. Nó đứng được vì giai đoạn 1 chưa có
thao tác nào thuộc nhóm «nhạy cảm» — hai ứng viên hiển nhiên (E-001/3.1 khung phê duyệt, E-001/2.5
super admin) đều chưa mở. ⇒ ⛔ E-001/3.1 phải đọc lại giả định này trước khi đóng, vì
chính nó làm tiền đề kia sai. Giá của việc chờ là thấp và đo được (thêm cột hay thêm bảng đều là
thao tác thêm); giá của việc đoán sai hình dạng hôm nay là vĩnh viễn.
3. Đường đầu-cuối thật ⛔ CHƯA CHẠY, và triệu chứng dễ đọc nhầm. Phía giao diện còn một lỗi
đang sống trên nhánh chính (OAPI-111): một dấu / thừa trong cấu hình proxy. Khi ấy triệu chứng
là 401 khắp nơi, ⛔ không phải 404 — tức giống hệt ca phiên hỏng, nên rất dễ đổ lỗi
nhầm cho story này.
4. Verdict là TỰ CHẤM. PASS @ 28e643c do chính làn thi công chấm theo knob hiện hành, ⛔ chưa
có người soi độc lập. Change chạm trục cô lập và trục danh tính, và nó khai một hợp đồng dây
(định dạng phiên trên dây) mà repo admin-fe phải theo ⇒ đáng chấm lại khi có checker.
Về khoá docs: — change khai huong-dan-admin/quan-tri-he-thong/nguoi-dung.md, đúng hệ đặt
tên PO chốt 10/09/2026, kèm ghi chú vì sao chưa viết được: trang hướng dẫn bám UI thật, mà
giao diện chưa cắm (E-001/5.8 bị chặn bởi chính change này). ⇒ Đây là khai có chủ ý, story
này ⛔ không rơi vào mục change thiếu khoá.
E-001/2.4 Vai và quyền cấu hình được lúc chạy — admin-be¶
Nguồn hồ sơ. admin-be @ origin/main 01a0c48 —
openspec/changes/archive/2026-09-12-iam-roles-permissions/ (bản thảo viết từ nhánh f2fea8b; artifact cuối eb16923 đúng sha đã ghim) (proposal.md · design.md · specs/iam-internal-users/spec.md ·
tasks.md · test-cases.md · security.md). Issue OAPI-171-iam-roles-permissions
(thangvv111/admin-be#61). Quyết định PO 12/09 đọc ở
platform c966975 (epics/E-001 §«Gói design/roles/ 12/09», spine AD-9 khối lệch, backlog-tuan-thu.md
§K3). Change ĐÃ đóng (archive 12/09/2026, merge 01a0c48, verdict PASS @ 0d45e71 tự chấm; lens review 21 vá · 8 bác · 1 nợ) ⇒ bản cuối.
Change thứ hai của story — permission-catalog-seed (OAPI-196-permission-catalog-seed,
thangvv111/admin-be#66), origin/permission-catalog-seed @ ce08d3f
(proposal · design · specs/iam-internal-users/spec.md · tasks · test-cases) — chưa thi công; phần đánh dấu ⟪mồi⟫ dưới
đây là của change ấy. Lý do: PO vọc UAT với super admin, hộp «Tạo vai trò mới» toàn «—», 0/0 — V14 chỉ mồi đúng một quyền
iam.super_admin; PO chốt 12/09 đường (a): mồi danh mục bằng di trú, ⛔ không seed riêng UAT, ⛔ không bắt super admin tạo tay 58 quyền.
Yêu cầu và cách đo¶
| Yêu cầu | Ràng buộc | Đo bằng |
|---|---|---|
| Danh mục đối tượng và hành động là bảng dữ liệu mồi bằng di trú | permission_object 14 hàng (13 của gói đúng mã, thứ tự + iam ẩn khỏi ma trận) · permission_action 10 hàng ba lớp chuc_nang · dac_quyen · he_thong (CHECK cố ý — thêm lớp là thêm hành vi). GĐ1 ⛔ không API ghi hai danh mục; MSB thêm bằng di trú dữ liệu |
Thêm một đối tượng bằng di trú ⇒ ma trận tự có hàng, ⛔ sửa mã |
| Quyền có mã tự sinh, bất biến; lớp = lớp của hành động bằng khoá ngoại ghép | ma là cột GENERATED <đối tượng>.<hành động>, UNIQUE theo cặp; lop = dac_quyen ⇒ hai trường hệ quả NOT NULL; ⛔ không seed 52 quyền minh hoạ của gói — chỉ mồi iam.super_admin. Sửa chỉ đổi tên/mô tả/hệ quả; ngưng là mốc trong chính câu UPDATE (lặp ⇒ 409); quyền lớp hệ thống ⇒ mọi PUT 409; ngưng quyền ⛔ đụng role_permission (mở lại thì quyền về) |
Gửi doiTuong/hanhDong ở PUT ⇒ 400; trùng cặp ⇒ 409 bắt từ UNIQUE, ⛔ check-then-act |
Vai: mã in hoa/số/_/. ≤ 64, bất biến; lưu = diff tập quyền mong muốn |
Thiếu ⇒ INSERT role_permission có granted_by; thừa ⇒ ghi revoked_at/by; ⛔ không xoá hàng; một cặp một hàng hiệu lực (chỉ mục duy nhất một phần); mỗi thay đổi một vết. Tập quyen chỉ nói về hai lớp nghiệp vụ — hàng lớp hệ thống của SUPER_ADMIN ⛔ bị diff chạm; SUPER_ADMIN ⛔ ngưng (409) |
Quyền đã ngưng trong tập ⇒ 409; lớp hệ thống ⇒ 400; thu hồi rồi cấp lại ⇒ hàng mới, hàng cũ nguyên |
| Gán vai theo tập mong muốn, lịch sử là chính bảng | PUT /internal-users/{id}/roles {vaiTro: [...]} diff như trên; GET …/roles trả cả vai đã thu hồi (da-cap · da-thu-hoi · chua-cap, mốc); GET …/role-events sinh sự kiện từ hàng user_role — ⛔ không bảng lịch sử thứ hai |
Vai đã ngưng ⇒ 409; sự kiện có boi (ai cấp/thu) |
| Chỉ super admin thao tác; 403 cho người khác; hiệu lực ngay, ⛔ cache | PhanQuyenFilter sau PhienFilter, khớp tiền tố 6 nhóm đường; hỏi view internal_user_active_permission mỗi lượt (SELECT EXISTS), ⛔ đọc từ phiên; không giữ ⇒ 403 ADM.AUTH.FORBIDDEN, details [{field: permission, issue: iam.super_admin}]; controller ⛔ kiểm lại |
Thu hồi SUPER_ADMIN xong ⇒ lượt gọi kế đã 403; super admin đi lọt cả 13 đường (đối xứng) |
| Lối đọc duy nhất «người này giữ quyền gì» | View = user_role hiệu lực ⋈ role hoạt động ⋈ role_permission hiệu lực ⋈ permission hoạt động ⇒ bốn đường làm quyền biến mất (ngưng vai · thu hồi vai · ngưng quyền · thu quyền khỏi vai) đều hiệu lực lượt kế |
Ca bốn đường |
| Hệ không tự khoá | Thu hồi SUPER_ADMIN khỏi chính người gọi ⇒ 409; thu hồi khi sau đó không còn tài khoản hoạt động nào giữ nó ⇒ 409 (đếm trong cùng giao dịch; ca tới được qua HTTP: chủ thể A đã bị vô hiệu thu hồi khỏi B là tài khoản hoạt động duy nhất còn giữ vai — guard là lưới ở tầng service, hàng của B còn hiệu lực); ngưng iam.super_admin ⇒ 409; ngưng vai SUPER_ADMIN ⇒ 409 — ba đường tới «không còn ai là super admin», ba chỗ chặn |
Ba ca 409 |
| Vết mức cảnh báo cho 12 hành động, cùng giao dịch | quyen.tao/sua/ngung/mo_lai · vai.tao/sua/cap_quyen/thu_quyen/ngung/mo_lai · gan_vai.cap/thu_hoi; chủ thể = super admin thực hiện; before/after tham chiếu mã, ⛔ email/họ tên; hai khoá cố định muc: CANH_BAO · k3_lech: true trong after_state ⇒ nằm trong băm K2; thao tác đổ thì không vết, vết đổ thì thao tác không hiệu lực |
Ca mọi hành động có muc; bite gỡ helper ⇒ đỏ |
| Phiên mang mã quyền hiệu lực | ChuThe của POST /sessions · GET /sessions/current thêm quyen: [mã] từ view — để 5.10/5.12 lọc menu; ⛔ không phải nguồn của cổng |
Ca đăng nhập trả quyen |
Mồi V14: quyền hệ thống + vai SUPER_ADMIN + cấp cho tài khoản mồi số 1 |
Dùng lại placeholder ${seed_email_1} của V13; NOT EXISTS hàng hiệu lực; mồi số 2 không là super admin (ca 403 tự nhiên cho UAT); không tìm thấy mồi số 1 đang hoạt động ⇒ V14 RAISE, fail-closed |
Ca boot; ca mồi 2 ⇒ 403 |
Ma trận quyền theo cột; portal_app ⛔ quyền nào |
Hai dòng app.role · app.permission có sẵn chuyển từ mức bảng sang mức cột; sáu bảng + view; doi_tuong/hanh_dong/ma ⛔ trong cột_update của vai nào (mã bất biến là cơ chế) |
Cổng khởi động hai chiều |
⟪mồi⟫ Danh mục quyền nghiệp vụ mồi bằng di trú V16 |
52 quyền chức năng (13 đối tượng trong ma trận × read · create · update · disable, tên/mô tả theo mẫu gói) + 6 đặc quyền (tpp_organization.approve · tpp_request.approve · tpp_request.reject · application.approve_prod · api_key.rotate_approve · api_product.publish, hai trường hệ quả theo mẫu admin-fe) + đối tượng api_key («Khoá API», ngoài ma trận — nhà của đặc quyền xoay khoá). ON CONFLICT (doi_tuong, hanh_dong) DO NOTHING: hàng super admin đã tạo tay trước V16 giữ nguyên (kể cả tên khác mẫu), ⛔ hàng kép, boot hai lần không thêm gì. ⛔ Không chạm iam.super_admin, ⛔ vai mới, ⛔ gán vai, ⛔ GRANT mới. Mồi là dữ liệu ban đầu — sửa/ngưng/mở lại qua API như quyền thường. Hệ quả API (sau lens review): tạo trùng cặp đã mồi ⇒ 409 (kể cả api.create); đối tượng ngoài ma trận (iam, api_key) ⛔ nhận quyền mới qua HTTP — quyền mồi sẵn trên api_key vẫn là quyền thường |
Pin MoiSuperAdminTest: đối tượng 14 → 15, quyền 1 → 59; ca hàng tạo tay giữ nguyên; ca boot hai lần |
⛔ Cố ý KHÔNG có trong story này¶
⛔ Không cưỡng chế quyền chức năng (user.read, api.create…) trên các đường ngoài phân quyền — từng story
sở hữu đường tự cắm khi danh mục có. ⟪mồi⟫ ⚠️ Change thứ hai đảo non-goal «⛔ không seed 52 quyền từ bản vẽ» của bản đầu — có chủ ý, PO chốt; điều không đổi: quyền chức năng vẫn chưa cưỡng chế ở đâu ngoài phân quyền — mồi là để ma trận có ô, ⛔ không phải để hệ bắt đầu chặn theo quyền. ⛔ Không API ghi danh mục đối tượng/hành động. ⛔ Không giới hạn số
lượng / rà soát super admin (2.5). ⛔ Không hàng đợi chờ duyệt (3.3). ⛔ Không tên vai trong liệt kê người
dùng (2.6 làm lại). ⛔ Không maker/checker cho phân quyền — bật lại là change có tên trước go-live, ⛔
không cờ. ⛔ Không vết BI_CHAN cho lượt 403 (cân và loại: lớp bù của FR-050 là vết thành công của super
admin, không phải vết của người bị chặn). ⛔ Không rate-limit riêng (chung tình trạng repo).
⚠️ Người duyệt cần biết — sáu chỗ¶
1. K3 lệch có chủ ý — và đây là lệch nặng nhất dự án đang mang. Một super admin cấp được quyền cho bất
kỳ ai, kể cả cấp SUPER_ADMIN cho người khác, hiệu lực ngay, không ai thứ hai. Lớp bù duy nhất là vết cảnh
báo + 2.5 (sau). security.md khai ⚠️, ⛔ không ✅. Production ⛔ chạy với lệch này — điều kiện trả là cổng
go-live (PO 12/09). Người thẩm định tuân thủ nên đọc backlog-tuan-thu.md §K3 trước khi đọc trang này.
2. Ba trang khác của nhóm này ĐANG NÓI NGƯỢC — đọc theo mốc thời gian, không theo trang. E-001/5.12 (màn
vai và quyền) viết «tạo vai là Gửi duyệt, mọi thay đổi qua phê duyệt hai người» — đúng luật 11/09, sai với
12/09; E-001/2.6 (API người dùng nội bộ) đã merge với ba thao tác qua đề xuất — PO chốt làm lại theo
12/09 ở change kế; hàng story 2.4 vẫn ghi «xếp sau 3.1 vì gán vai phải qua duyệt». Cả ba sẽ được sửa khi
change tương ứng đổi; tới lúc đó, trang này là bản đúng cho phân quyền.
3. «Mức cảnh báo» của vết là QUY ƯỚC jsonb, ⛔ không phải cột có ràng buộc. Đường cột muc riêng đã cân và
loại (phải thay hàm băm 13 tham số, trigger, hai công thức băm sống song song — chạm vùng vừa archive). Hệ quả:
một bên ghi quên hai khoá thì vết vẫn vào và không «tách riêng» được; lưới là lớp giữ (ca mọi hành động, bite gỡ
helper). 4.2 lọc bằng after_state->>'muc'.
4. Mã quyền và mã vai bất biến là CƠ CHẾ, ⛔ không phải lời khai. ma là cột sinh; doi_tuong/hanh_dong
nằm ngoài cột_update của mọi vai; khoá ngoại ghép giữ «lớp của quyền = lớp của hành động» ở CSDL. Đường enum
Java + CHECK đã cân và loại: thêm đối tượng là sửa mã + đổi CHECK ở vùng chỉ-thêm, và admin-fe phải gõ cứng 13
hàng — đúng thứ gói cấm.
5. Còn MỘT đường lockout change này ⛔ chạm: super admin cuối tự vô hiệu tài khoản mình qua 2.6. Guard
«không vô hiệu super admin cuối» giao cho 2.6 làm lại — nợ có tên OAPI-178
(thangvv111/admin-be#62). Bản cuối bổ sung: guard «cuối» tuần tự hoá bằng
SELECT … FOR UPDATE trên hàng vai SUPER_ADMIN và hàng người dùng (⛔ write-skew dưới READ COMMITTED); so tiền tố đường
trên đường đã giải mã (%/; ⛔ đi vòng được). Cầu mồi V14 để hàng user_role granted_by NULL
(vết là flyway_schema_history) — 2.6 làm lại định hình mồi cuối cùng, thu hồi bằng mốc, ⛔ xoá.
6. Trang hướng dẫn phân quyền — ba câu ⛔ được viết: ⛔ «bấm Gửi duyệt» (không còn duyệt); ⛔ liệt kê danh mục quyền như cố định (danh mục là dữ liệu MSB cấu hình, gói chỉ minh hoạ); ⛔ «xoá quyền/vai» (chỉ ngưng, lịch sử giữ). Và một câu phải có: người không phải super admin mở URL màn phân quyền sẽ thấy 403 — đó là chủ đích.
E-001/2.5 Super admin có giới hạn — admin-be¶
Chờ hồ sơ. Tiêu chí dự kiến từ kế hoạch:
- Không tự phê duyệt được hồ sơ hay đề xuất cấp quyền;
FR-005áp cả với nó. - Không sửa hay xoá được nhật ký.
- Thao tác của super admin ghi nhật ký ở mức cảnh báo, tách riêng để soi được.
- Đường mồi chỉ qua dòng di trú; tài khoản mồi tự vô hiệu sau lần đăng nhập đầu (
AD-9.3).
⟨CẦN NGƯỜI VIẾT — agent làn admin-be viết khi đóng change⟩
E-001/2.6 API quản trị người dùng nội bộ — admin-be¶
Nguồn hồ sơ. admin-be @ origin/main dc8e033 —
openspec/changes/archive/2026-09-12-internal-user-admin-api/ (bản thảo viết từ nhánh 51f4df9, soát lại theo archive) (proposal.md · design.md · specs/iam-internal-users/spec.md ·
specs/approval-workflow/spec.md · tasks.md · test-cases.md · security.md). Issue
OAPI-141-internal-user-admin-api (thangvv111/admin-be#50);
closes: thêm #38 · #47.
Change ĐÃ đóng (archive 12/09/2026, merge dc8e033, verdict PASS @ 3b3c92d tự chấm) ⇒ bản cuối.
Yêu cầu và cách đo¶
| Yêu cầu | Ràng buộc | Đo bằng |
|---|---|---|
Liệt kê người dùng nội bộ cho màn 5.11 |
GET /api/v1/internal-users: phân trang từ 1 (AD-28, trần 200), tìm theo email/họ tên, lọc dang-hoat-dong · ngung-hoat-dong · cho-duyet; trả id · hoTen · email · createdAt · disabledAt · trangThai · deXuatChoDuyetId. Đề xuất tạo đang chờ hiện thành hàng ảo cho-duyet với id rỗng; gộp trong bộ nhớ (hàng thật SQL + đề xuất chờ từ API công khai của approval), sắp xếp tất định |
Ca hai người + một đề xuất tạo chờ ⇒ 3 hàng, hàng ảo trước |
| Ba thao tác đều là đề xuất qua phê duyệt | POST …/requests mở và gửi duyệt trong một giao dịch (tao · vo_hieu · mo_lai); ⛔ không «đang soạn», bị từ chối ⇒ đề xuất chết, mở lại từ đầu. Kiểm sớm cho maker (email đúng khuôn, chưa có, chưa có đề xuất chờ; ⛔ tự vô hiệu chính mình) — không phải lớp chặn; lớp chặn nằm ở lúc áp |
Ca tự vô hiệu mình ⇒ từ chối; ca hai đề xuất chờ trên cùng tài khoản ⇒ từ chối |
| Đủ cấp ⇒ áp ngay trong CÙNG giao dịch của quyết định | POST …/requests/{id}/decisions; DA_DUYET ⇒ áp: tạo (hàng + credential) · vô hiệu (kèm thu hồi phiên, FR-006) · mở lại. Áp đổ ⇒ cả quyết định cuộn lại, 409, đề xuất vẫn chờ. Mã ⛔ giả định so_cap = 1. Hệ không tự khoá (K3): checker ⛔ không duyệt được đề xuất vô hiệu chính mình (409, vẫn từ chối được); lượt áp vô hiệu để hệ còn dưới hai tài khoản hoạt động có mật khẩu cục bộ ⇒ 409, cuộn lại. Chi tiết đề xuất đã từ chối vẫn mang người từ chối và lý do |
Ca bẻ policy lên 2 cấp; ca áp đổ ⇒ đề xuất vẫn CHO_DUYET |
| Mật khẩu tạm hệ sinh, trả MỘT LẦN cho checker | SecureRandom, bảng chữ bỏ 0 O l 1 I, độ dài max(16, tối thiểu); chỉ phản hồi của quyết định làm đề xuất tao có hiệu lực mang matKhauTam (Cache-Control: no-store); mọi đường khác ⛔ trả lại; mật khẩu tạm không có hạn (thời hạn hiệu lực là nợ có tên); ⛔ vào log, ⛔ vào vết (vết ghi mat_khau_tam: true) |
GET …/requests/{id} ⛔ có mật khẩu; quét log/vết |
Credential chỉ sinh được từ đề xuất đã duyệt — giữ K3 ở tầng dữ liệu |
⛔ không GRANT INSERT credential cho admin_app; hàm SECURITY DEFINER app.cap_mat_khau_theo_de_xuat tự kiểm ở CSDL: đề xuất đúng loại, đã gửi, đã áp, đủ số duyệt ở vòng hiện tại, không từ chối, email khớp, chưa có credential |
Bite: gỡ một điều kiện trong hàm ⇒ ca đỏ |
| Buộc đổi là một MỐC, không phải cờ | Cột nguoi_dung_tu_dat_luc NULL = người khác đặt ⇒ buộc đổi; chỉ đường đổi mật khẩu đặt được. PhienFilter chặn 403 mọi đường trừ đổi mật khẩu · đọc/đóng phiên · đọc chính sách; POST /sessions và GET /sessions/current thêm phaiDoiMatKhau. Tài khoản không có credential (LDAP) ⇒ false |
Route mới thêm sau ⇒ tự bị chặn (fail-closed ở filter) |
| Đổi mật khẩu trong phiên | PUT /api/v1/sessions/current/password: kiểm hiện tại qua port xác thực (lượt sai đếm vào khoá tạm), độ dài từ app.internal_user_auth_policy (12–128, đếm theo ký tự, GET /password-policies/current), mới ≠ hiện tại, thu hồi mọi phiên khác, giữ phiên này, vết tai_khoan.doi_mat_khau — lượt sai cũng để vết thất bại có chủ thể. Chủ phiên không có credential (LDAP) ⇒ 409, ⛔ không đếm vào ngưỡng khoá |
Ca hai phiên ⇒ đổi ở một ⇒ phiên kia chết |
| Mồi ĐÚNG HAI tài khoản, fail-closed, không đè | admin.seed.users[n] (đúng 2 — đủ maker ≠ checker, email chuẩn hoá khác nhau, ⛔ chứa '); hai tài khoản mồi phải đang hoạt động sau di trú — email mồi trùng một tài khoản đã vô hiệu ⇒ di trú app dừng ở V13, tiến trình không khởi động + hai bí mật admin.seed.password.1|2 (file, AD-27); thiếu ⇒ từ chối khởi động nêu tên file. Băm Argon2id mỗi lần khởi động, placeholder Flyway; ON CONFLICT giữ tài khoản đã có, ⛔ đè mật khẩu; mồi ⇒ buộc đổi lần đầu |
Ca boot hai lần ⛔ «checksum mismatch»; ca tài khoản UAT có sẵn giữ nguyên mật khẩu |
| Vết với chủ thể thật | tai_khoan.tao/vo_hieu/mo_lai: actor = checker ra quyết định cấp cuối; before/after tham chiếu, ⛔ email/họ tên (T_pii); chuỗi ai mở/gửi/duyệt ở vết de_xuat.* của khung |
Ca đọc vết sau khi áp |
| Ranh giới gói | iam → approval qua interface KhungPheDuyet trong gói approval; approval ⛔ import iam; bộ áp nội dung sống ở iam |
Ca đọc import |
⛔ Cố ý KHÔNG có trong story này¶
⛔ Không gán vai/quyền (E-001/2.4, ngoài GĐ1) ⇒ ⛔ không lớp «vai» tạm nào — một cờ tạm là thứ
AD-9 cấm và là nợ vĩnh viễn. ⛔ Không hàng đợi chờ duyệt liên loại (3.3). ⛔ Không đặt lại mật khẩu
khi quên (không story). ⛔ Không loại ký tự · thời hạn hiệu lực · thời gian đổi tối thiểu của mật khẩu (nợ
có tên, mở khi đóng change). ⛔ Không sửa email/họ tên. ⛔ Không gửi thư mời. ⛔ Không ba cột tên
đăng nhập · đơn vị · vai trò (OAPI-142). ⛔ Không bản nháp đề xuất sửa nhiều vòng ở bề mặt này.
⚠️ Người duyệt cần biết — bảy chỗ¶
0. Đọc dấu «TỰ CHẤM» cho đúng trọng lượng. Lens review bắt ba lỗ nghiệp vụ nặng mà làn không thấy khi tự viết: hệ có thể tự khoá vĩnh viễn (vô hiệu tới mức không còn hai người đăng nhập được — không đường hồi phục ngoài sửa tay CSDL) · checker duyệt được đề xuất vô hiệu chính mình · chi tiết đề xuất đã từ chối trả rỗng người từ chối. Cả ba đã vá và có ca ở bản cuối; nhưng đây là ca cho thấy review.enforce=self để lọt đúng loại lỗi người thẩm định quan tâm nhất — CheckMate chấm lại.
1. Checker CẦM mật khẩu tạm — chủ đích, có giá đã cân. GĐ1 không gửi thư nên đây là đường rẻ nhất còn
giữ K3: cùng một người không thể vừa mở đề xuất vừa nhận mật khẩu. Cửa sổ rủi ro (người thứ hai biết mật
khẩu tới lần đăng nhập đầu) giảm bằng buộc đổi + vết + thu hồi phiên khác khi đổi. PO bác được.
2. Cho tới 2.4, MỌI người dùng nội bộ có phiên đều là maker lẫn checker. Giả định tường minh
(D11), ⛔ không phải bỏ sót; engine vẫn cấm tự duyệt (laNguoiSoan). T_authz khai [N/A] với đúng lý do
này; T_sod là ca có thật. Người duyệt cần thấy lựa chọn «không dựng vai tạm» là đúng, dù trông như thiếu.
3. Quyền UPDATE hai cột credential là quyền mức bảng cho MỌI hàng — security.md khai ⚠️, ⛔ không ✅.
CSDL không phân biệt «tự đổi» với «đặt hộ». Đường gắn phiên bằng SECURITY DEFINER đã cân và loại vì
admin_app có INSERT trên bảng phiên nên phép kiểm giả được bằng chính vai đó. Lớp chặn thật cho vector
«đặt hộ mật khẩu người khác»: guard tầng ứng dụng (chủ thể chỉ từ phiên) + vết + thu hồi phiên (chủ thật
bị đá ra và thấy).
4. Tài khoản UAT hiện có sẽ bị BUỘC ĐỔI ở lần đăng nhập kế. Hàng credential có trước V13 nhận cột mới
NULL — đúng nghĩa «người khác đặt» (làn admin-be đặt hộ). ⛔ Không phải lỗi; nói với uat/PO trước khi dựng
lại ảnh. Kèm: uat cần hai file bí mật + cấu hình mồi trong compose, thiếu là admin-be từ chối khởi động.
5. Lượt mồi ⛔ ghi được nhật ký nghiệp vụ. Dòng di trú chạy bằng flyway_app, vai bị REVOKE ALL trên
vùng audit (AD-18); cấp quyền cho nó là mở đường dòng nghiệp vụ ghi vào vùng nhật ký. Chấp nhận: vết của
mồi là flyway_schema_history + lượt đổi mật khẩu bắt buộc của chính tài khoản mồi. ⚠️ Phát hiện kèm: bảng
audit.schema_event (khai «mỗi lượt di trú một dòng») không có bên ghi nào — ngoài phạm vi, chờ PO mở
issue (trùng phát hiện của E-001/1.2).
6. Hai chỗ chạm đối tác liên repo. admin-fe chờ hình dạng: liệt kê + tạo (OAPI-145), đổi mật khẩu +
chính sách (OAPI-134), và cờ phaiDoiMatKhau để đưa thẳng vào màn đổi mật khẩu (5.8). Hai lượt sai mật
khẩu hiện tại đếm vào ngưỡng khoá tạm — trang hướng dẫn phải nói đúng thế, ⛔ không viết «đổi mật khẩu
không giới hạn số lần thử».
E-001/2.7 Tài khoản nội bộ theo mô hình super admin — bỏ đường đề xuất của 2.6 — admin-be¶
Nguồn hồ sơ. admin-be @ origin/internal-user-super-admin-api 3a64195 —
openspec/changes/internal-user-super-admin-api/ (proposal.md · design.md · specs/iam-internal-users/spec.md —
5 REMOVED · 1 MODIFIED · 9 ADDED · tasks.md · test-cases.md · security.md · .memlog.md entry 1 = bốn quyết định
hình dạng PO 12/09). Issue OAPI-179-internal-user-super-admin-api
(thangvv111/admin-be#63); closes: thêm #62 (OAPI-178).
Story mới trên platform e083ff4. Change CHƯA thi công ⇒ bản thảo; cổng docs-gate phe-duyet ở admin-be đang block.
Yêu cầu và cách đo¶
| Yêu cầu | Ràng buộc | Đo bằng |
|---|---|---|
| Gỡ hẳn ba đường đề xuất của 2.6 | POST …/requests · GET …/requests/{id} · POST …/requests/{id}/decisions biến mất khỏi openapi.yaml và mã; service viết lại không phụ thuộc approval/ (pin ranh giới đảo chiều: «iam ⛔ import approval»); TrangThaiNguoiDung còn hai giá trị. CSDL: policy, đề xuất treo, hàm cap_mat_khau_theo_de_xuat giữ nguyên, ngưng dùng |
Ca ranh giới gói; ba đường cũ ⇒ 404 (⛔ không 410) |
| Liệt kê cho mọi phiên, hai trạng thái | Bỏ hàng ảo và deXuatChoDuyetId; sắp createdAt giảm dần rồi id; lọc trạng thái lạ (kể cả cho-duyet) ⇒ 400. ⚠️ Bỏ trường là đổi hợp đồng với admin-fe |
Ca lọc cho-duyet ⇒ 400 |
| Tạo · vô hiệu · mở lại do super admin, hiệu lực ngay | POST /api/v1/internal-users {email, hoTen} → 201 kèm matKhauTam một lần (Cache-Control: no-store), trùng email ⇒ 409 bắt từ UNIQUE; PUT …/{id}/status {trangThai} → vô hiệu thu hồi mọi phiên (FR-006), mở lại; lặp ⇒ 409 (điều kiện trong UPDATE). Cả hai sau PhanQuyenFilter — đúng phương thức: GET liệt kê vẫn mở cho mọi phiên (màn Tài khoản ai cũng xem được theo gói) |
Người không giữ iam.super_admin ⇒ 403; super admin ⇒ đi lọt |
| Credential ban đầu chỉ trong cùng giao dịch tạo | Hàm SECURITY DEFINER mới app.cap_mat_khau_ban_dau(user, hash): kiểm ở CSDL — hàng tồn tại · created_at = now() (now() bất biến trong giao dịch ⇒ chỉ đúng với hàng vừa chèn cùng giao dịch) · chưa có credential; sai ⇒ RAISE ⇒ 409. ⛔ Vẫn không GRANT INSERT credential — vai bị chiếm ⛔ cấp được mật khẩu cho tài khoản có sẵn (kể cả hình dạng LDAP) |
Bite: gọi hàm ở giao dịch khác ⇒ RAISE |
Không vô hiệu super admin hoạt động cuối cùng (trả OAPI-178) + không tự vô hiệu mình |
Tự khoá mình ⇒ 409; đích giữ iam.super_admin ⇒ khoá hàng vai SUPER_ADMIN (tuần tự hoá với lượt thu hồi vai của 2.4) rồi đếm super admin hoạt động, ≤ 1 ⇒ 409 trước khi ghi. Cả hai là lớp ứng dụng, ⛔ không ràng buộc CSDL tương đương (S8.2 ⚠️) |
Ca super admin cuối ⇒ 409; ca hai lượt đua ⇒ một 409 |
Tài khoản mồi tự vô hiệu khi hệ đã có super admin KHÁC (AD-9.3, PO ①) |
V15: tai_khoan_moi (dấu mồi, ⛔ vai ứng dụng ghi được — chỉ di trú đặt, cho mồi số 1) · dang_nhap_lan_dau_luc (ghi một lần). Lượt đăng nhập đầu: ghi mốc, mở phiên. Lượt thứ hai hoặc khi đóng phiên: khoá hàng vai, đếm super admin hoạt động khác — ≥ 1 ⇒ tự vô hiệu (thu hồi phiên, vết SYSTEM tai_khoan.tu_vo_hieu mức cảnh báo) và từ chối đăng nhập; = 0 ⇒ giữ, WARN + vết tai_khoan.moi_con_dung — ⛔ không lockout. ⛔ Không cờ cấu hình |
Ca đăng nhập lần hai sau khi UAT tạo super admin thật ⇒ bị từ chối, tài khoản mồi disabled_at có mốc |
| Vết mức cảnh báo, cùng giao dịch | tai_khoan.tao/vo_hieu/mo_lai chủ thể = super admin, after_state qua VetPhanQuyen.canhBao (muc = CANH_BAO, k3_lech = true, trong băm K2); tao ghi mat_khau_tam: true, ⛔ mật khẩu; thêm tu_vo_hieu · moi_con_dung (ActorType.SYSTEM); ⛔ email/họ tên. Guard «≥ 2 tài khoản có mật khẩu» của 2.6 gỡ — K3 hai người không còn áp, lớp thay thế là guard super admin cuối |
Ca mọi hành động có muc; thao tác đổ ⇒ không vết |
| Đường mồi giữ V13 (hai tài khoản), nghĩa đổi (PO ④) | Mồi số 1 = super admin đầu tiên (V14) + tài khoản mồi (V15, tự vô hiệu). Mồi số 2 = tài khoản thường: buộc đổi mật khẩu lần đầu, không vai, ⛔ tự vô hiệu. Cấu hình ADMIN_SEED_1|2_* + hai tệp bí mật giữ |
Ca boot; ca mồi 2 đăng nhập lần hai ⇒ vẫn vào |
| Hợp đồng khai đủ mã | PUT /sessions/current/password thêm 409 (admin-fe đo 12/09: mã đã trả 409 khi không có mật khẩu cục bộ mà yaml thiếu). Mã lỗi riêng «sai mật khẩu hiện tại» là đổi hợp đồng — PO quyết riêng, ngoài change |
Cổng đối chiếu HTTP thật ↔ yaml |
⛔ Cố ý KHÔNG có trong story này¶
⛔ Không mã lỗi riêng cho «sai mật khẩu hiện tại». ⛔ Không tên vai trong liệt kê (OAPI-142, admin-fe). ⛔
Không sửa email (họ tên: mở ở lượt 2, xem dưới). ⛔ Không giới hạn số lượng super admin (2.5). ⛔ Không đổi cấu hình mồi (V13 archive cần
hai placeholder). ⛔ Không drop bảng/cột/hàm/hàng của đường đề xuất. ⛔ Không tự vô hiệu tài khoản mồi ngay lúc
2.4 cấp SUPER_ADMIN cho người khác (chạm luuVai của story đã ship, cắt phiên đang làm việc giữa chừng — đã cân, loại).
⚠️ Người duyệt cần biết — sáu chỗ¶
1. Đây là story sửa một story vừa ship hôm trước — đúng luật, nhưng người thẩm định phải đọc hai trang theo thứ
tự. 2.6 archive 12/09 sáng theo bản 11/09; PO đảo chiều 12/09 chiều; luật 09/09 cấm sửa ngược ⇒ 2.7. Trang 2.6
giữ nguyên làm hồ sơ; trang này là bản đúng. Năm requirement của 2.6 bị REMOVED có Reason/Migration trong spec.
2. K3 lệch có chủ ý — cùng lệch với 2.4, và bù bằng cùng cơ chế. Super admin tạo tài khoản, cầm mật khẩu tạm,
vô hiệu/mở lại — một người, hiệu lực ngay. Bù: vết cảnh báo cùng giao dịch, buộc đổi mật khẩu lần đầu, thu hồi phiên
khác khi đổi. security.md khai ⚠️; production ⛔ chạy với lệch này (cổng go-live).
3. Tự vô hiệu tài khoản mồi lấy nghĩa AN TOÀN, ⛔ không phải nghĩa đen AD-9.3. Nghĩa đen «lần đăng nhập thứ hai bị
từ chối bất kể» ⇒ quên tạo super admin thật là khoá hệ. Change chọn: chỉ tự vô hiệu khi đã có super admin
hoạt động khác; chưa có thì giữ và ghi vết cảnh báo mỗi lượt. Rủi ro còn lại (ghi ở Risks/S8): phiên đầu hết hạn
mà không đăng xuất và không đăng nhập lại ⇒ tài khoản mồi còn hoạt động tới lượt đăng nhập kế; super admin thật vô hiệu
nó bằng tay được (guard cho phép vì còn người khác).
4. Bỏ deXuatChoDuyetId và trạng thái cho-duyet là ĐỔI HỢP ĐỒNG với admin-fe — OAPI-145 đã đóng trên đường
cũ; admin-fe phải nối lại 5.11 vào hai đường mới, và màn 5.11 (đã ship trạng thái «Chờ duyệt» theo 11/09) phải bỏ
giá trị ấy. Nhắn hình dạng trước khi push là điều kiện của change.
5. Guard super admin cuối là LỚP ỨNG DỤNG, khoá hàng vai để tuần tự hoá. Không có ràng buộc CSDL tương đương; hai
đường tới cùng mục tiêu «hệ không còn super admin» (thu hồi vai ở 2.4, vô hiệu tài khoản ở đây) xếp hàng sau cùng một
khoá SELECT … FOR UPDATE trên hàng vai SUPER_ADMIN. Đếm trước khi ghi đủ vì đã khoá.
6. Quy trình bàn giao cho vận hành/UAT (README của change; vùng van-hanh/ trong repo docs chưa có): đăng nhập
mồi 1 → đổi mật khẩu → tạo tài khoản thật → gán SUPER_ADMIN (màn 2.4) → đăng xuất ⇒ mồi 1 tự vô hiệu. ⚠️ Trang
hướng dẫn nguoi-dung.md: ⛔ viết «gửi duyệt» / «chờ duyệt» cho tài khoản (bản 11/09 hết hiệu lực); phải nói người
tạo là super admin và mật khẩu tạm chỉ hiện một lần.
Lượt 2 — hoàn thiện API tài khoản: tạo/sửa kèm tập vai, chi tiết kèm quyền, đơn vị (13/09/2026)¶
Nguồn hồ sơ. admin-be @ origin/main 983a8d9 — openspec/changes/archive/2026-09-13-internal-user-profile-roles-api/
(proposal.md · design.md D1–D11 + «Review outcome» · specs/iam-internal-users/spec.md 4 ADDED · 3 MODIFIED, 42 scenario
↔ 42 hàng test · tasks.md 5 nhóm/20 task · security.md S1–S10 · docs.md); bản thảo viết từ nhánh 64b684d (artifact
8a18530), bản cuối soát lại theo archive — khác duy nhất: số di trú V19 → V20 (V19 đã thuộc tham-dinh-ma-so-thue
merge trước; artifact ở tip 48cfc95 = 7716a3f), và mục «Review outcome» thêm sau dấu duyệt theo protocol đóng.
Change thứ hai nối cùng story 2.7; PO chốt 13/09 trước khi soạn: «phần thêm sửa tài khoản phải cấu hình được quyền
cho người dùng. Xem tài khoản phải thấy quyền người dùng» + «đơn vị chốt hiện tại để ô text» + «làm cả 3».
Change ĐÃ đóng (archive 13/09/2026, merge 983a8d9) ⇒ bản cuối.
Số đo origin/main @ 216b940 làm điểm xuất phát: nhóm tài khoản nội bộ có đúng 3 đường (liệt kê · tạo · đổi trạng
thái); tạo chỉ nhận email + hoTen; không có chi tiết, không có sửa; hàng liệt kê không mang vai; không có cột đơn vị.
Quyền của một người chỉ đi qua vai (user_role → role_permission → permission, lối đọc
internal_user_active_permission) ⇒ «cấu hình quyền cho người dùng» = chọn tập vai; quyền là hệ quả đọc ra, ⛔ không
có cấp quyền trực tiếp người↔quyền.
Yêu cầu và cách đo¶
| Yêu cầu | Ràng buộc | Đo bằng |
|---|---|---|
| Tạo kèm tập vai + đơn vị, một giao dịch | POST /api/v1/internal-users nhận thêm vaiTro: uuid[] (tuỳ chọn, trần 500) và donVi (tuỳ chọn). Hàng tài khoản + credential tạm + mọi hàng gán vai ghi trong một giao dịch — vai đã ngưng ⇒ 409, mã vai lạ ⇒ 400, và khi ấy không có tài khoản nào được tạo. Tập chứa SUPER_ADMIN được chấp nhận (chỉ làm tăng, không chạm guard cuối). Phản hồi 201 mang vaiTro[] đã cấp + donVi |
Ca tạo kèm vai đã ngưng ⇒ 409, bảng tài khoản không thêm hàng; ca tạo không kèm vai vẫn như cũ |
| Sửa hồ sơ kèm tập vai hiệu lực (đường mới) | PUT /api/v1/internal-users/{id} {hoTen, donVi, vaiTro[]} — vaiTro là tập hiệu lực mong muốn: thiếu ⇒ cấp (mốc + người cấp), thừa ⇒ thu hồi bằng mốc trên chính hàng, ⛔ không xoá; rỗng = thu hồi hết. ⛔ Không nhận email — gửi lên bị bỏ qua (khoá đối chiếu K4/AD-11). Guard «hệ không tự khoá» của 2.4 áp nguyên: tự bỏ SUPER_ADMIN ⇒ 409; làm hệ hết super admin hoạt động ⇒ 409. Sửa được cả tài khoản đã vô hiệu (PO chốt; dữ liệu tĩnh, phiên đã thu hồi, không mở khoá gì). Đổ ở bước nào ⇒ cuộn cả lượt kể cả họ tên. Trả khuôn chi tiết (D4). Chỉ super admin |
Ca họ tên hợp lệ + vai đã ngưng ⇒ 409 và họ tên không đổi; ca gửi email khác ⇒ 200, email giữ nguyên |
| Chi tiết một tài khoản mang vai và quyền hiệu lực (đường mới) | GET /api/v1/internal-users/{id} = hồ sơ + mọi vai của hệ với da-cap · da-thu-hoi (kèm mốc) · chua-cap + quyen[] (mã · tên · lớp) đọc từ cùng lối đọc mà phiên dùng trả ChuThe.quyen. Chỉ super admin (PO chốt 13/09: chi tiết lộ tập quyền của người khác — S6.4 ⚠️ ghi finding cho lens review). id không phải UUID ⇒ 400; không có ⇒ 404 |
Ca quyen[] của chi tiết X bằng ChuThe.quyen khi X đọc phiên mình; ca thu hồi vai ⇒ lượt đọc kế mất quyền |
| Hàng liệt kê mang vai đang hiệu lực + đơn vị | Mỗi hàng thêm vaiTro[] (roleId · ma · ten, chỉ vai đang hiệu lực — đã thu hồi/đã ngưng ⛔ không hiện) và donVi. Liệt kê vẫn mở cho mọi phiên nhưng ⛔ không kèm quyen (quyền chỉ ở chi tiết). Gom vai một truy vấn cho trang hiện tại (= ANY(?)), không N+1, không đổi phân trang trong bộ nhớ (D8) |
Ca tài khoản không vai ⇒ mảng rỗng; ca quét hàng liệt kê không có trường quyen |
| Đơn vị công tác = văn bản tự do | Cột app.internal_user.don_vi text NULL — di trú V20 chỉ THÊM (AD-14.1; V18 đã dùng bởi submission-attachment-tables) + CHECK không rỗng + GRANT UPDATE (don_vi) cho admin_app, ma-tran-quyen.tsv cùng commit (cổng MaTranQuyenVerifier từ chối khởi động khi lệch). Ứng dụng strip, rỗng/chỉ khoảng trắng ⇒ null, trần 200. ⛔ Không là điều kiện lọc, không là khoá đối chiếu. Tài khoản cũ đọc ra rỗng |
Ca " " ⇒ lưu null; ca > 200 ⇒ 400 nêu trường donVi, không mang giá trị |
| Phiên mang đơn vị | ChuThe.donVi ở POST /sessions và GET /sessions/current (Topbar admin-fe hiện dòng đơn vị dưới họ tên). ChuThe vẫn ⛔ không email, ⛔ không mã phiên. Chạm trục T_pii — PO chốt 13/09 |
Ca ChuThe có donVi, không có email/mã phiên |
Vết mức cảnh báo, cùng giao dịch — thêm tai_khoan.sua |
Thao tác thứ tư trên danh tính: before/after_state = truong_doi[] + don_vi trước/sau + vai_hieu_luc (mã); muc = CANH_BAO, k3_lech = true; ⛔ không họ tên/email (họ tên chỉ xuất hiện là tên trường đã đổi). Đổi vai trong cùng lượt ⇒ vết gan_vai.cap/thu_hoi riêng từng vai; tạo kèm N vai = 1 vết tai_khoan.tao + N vết gan_vai.cap (đây là chỗ artifact đi khác issue — issue đề «một vết gộp», requirement đang chạy «mỗi thay đổi một bản ghi» thắng). Lượt không đổi gì ⇒ 200, không vết (D6); lượt bị từ chối ⇒ không vết |
Ca sửa họ tên + đơn vị ⇒ đúng một vết tai_khoan.sua, không khoá nào chứa họ tên; ca gửi lại y nguyên ⇒ không vết mới |
| Cổng phân quyền theo đường | PhanQuyenFilter thêm regex ^/api/v1/internal-users/[^/]+$ cho GET + PUT — ⛔ không bắt /internal-users trần (liệt kê mở). Hai đường mới không khai miễn trừ ở PhienFilter ⇒ chưa xác thực 401; đang buộc đổi mật khẩu ⇒ 403 PASSWORD_CHANGE_REQUIRED |
Lưới RanhGioiGoiPhanQuyenTest; ca gỡ regex ⇒ đỏ |
| Hợp đồng trước mã, không phá vỡ | openapi.yaml: 2 đường mới; schema mới SuaNguoiDungNoiBo · ChiTietNguoiDungNoiBo · VaiRutGon · QuyenHieuLuc; sửa TaoNguoiDungNoiBo · NguoiDungNoiBo · NguoiDungNoiBoMoi · ChuThe. Mọi trường mới đều là thêm; PUT /{id}/roles giữ nguyên cho hộp Gán vai riêng |
Cổng OpenApiHopDongTest đối chiếu HTTP thật ↔ yaml |
⛔ Cố ý KHÔNG có trong lượt 2¶
⛔ Không DELETE tài khoản (PO đảo 12/09: chỉ vô hiệu / mở lại; AD-4.2 cấm xoá dòng cha chạm audit — nút «Xoá» của bản
vẽ thành Ngưng hoạt động). ⛔ Không đổi email khi sửa. ⛔ Không Tên đăng nhập AD (không có cột nguồn; vẫn là nợ
OAPI-142). ⛔ Không danh mục đơn vị (ô text — làm danh mục là đẻ nghiệp vụ chưa có chủ). ⛔ Không maker/checker
cho tài khoản — cơ chế ngoại lệ K3 GĐ1 áp cho cả sửa + gán vai, trả K3 trước go-live theo gói design/roles/.
⛔ Không cấp quyền trực tiếp cho người bỏ qua vai. ⛔ Không đưa vaiTro/donVi vào lọc/tìm của liệt kê.
⛔ Không tách vết «sửa hồ sơ» và «đổi vai» thành hai giao dịch; ⛔ không thêm hàm CSDL mới. ⛔ Giao diện admin-fe —
OAPI-220, làn ấy làm.
⚠️ Người duyệt cần biết — sáu chỗ¶
1. Lượt 2 sửa lại một câu của lượt 1. Bản 12/09 ghi «⛔ không sửa email/họ tên»; nay họ tên sửa được, email vẫn ⛔. Câu ở phần trên đã đính chính tại chỗ; phần còn lại của lượt 1 (tài khoản mồi, guard super admin cuối, đường đề xuất đã gỡ) giữ nguyên hiệu lực.
2. Gán vai đi qua đúng một cửa — PhanQuyenService.luuVai của 2.4 (D1). Tạo và sửa đều gọi nó trong cùng giao
dịch ⇒ một bộ guard, một thứ tự khoá (hàng người trước, hàng vai SUPER_ADMIN sau — D2, cùng thứ tự với vô hiệu
và tài khoản mồi ⇒ không deadlock AB/BA). Thay thế bị loại: viết lại vòng cấp/thu hồi (nhân đôi guard) hoặc admin-fe gọi
hai lượt HTTP (cửa sổ tài-khoản-không-vai nếu lượt hai đổ). Giá: lượt tạo kèm 2 vai để 3 vết — người đọc nhật ký
thấy nhiều dòng cho một cú bấm — ⚠️ đo ở lượt thi công (13/09): audit.action_event không có trace_id, occurred_at = clock_timestamp() ⇒ ⛔ không gom theo cú bấm được từ nhật ký; tính nguyên tử chứng minh bằng ca cuộn (đổ bước cuối ⇒ 0 hàng, 0 vết). Gom theo cú bấm là nợ chung của nhật ký, ngoài change này (security.md S9.3, design.md Risks).
3. Chi tiết tài khoản lộ tập quyền của người khác — đóng bằng «chỉ super admin». GET /{id} là bản đồ tấn công
nếu lọt; security.md S6.4 ghi ⚠️ để lens review soi lại quyết định. Liệt kê (mở cho mọi phiên) cố ý chỉ mang tên
vai, ⛔ không quyền.
4. Sửa được tài khoản đã vô hiệu không mở lỗ (D5). Guard super admin cuối đếm tài khoản hoạt động ⇒ cấp
SUPER_ADMIN cho tài khoản vô hiệu không làm tăng số đếm. Một cạnh cần đọc đúng, ghi ở test-cases để khỏi nhầm là
lỗ: lượt sửa gửi lại tập vai cũ có vai vừa bị ngưng ở lượt khác ⇒ không 409 — luuVai chỉ kiểm vai đã ngưng ở
nhánh cấp mới, vai đã cấp từ trước giữ nguyên hàng.
5. Đơn vị mang giá trị trong vết, họ tên thì không — tự quyết của làn admin-be (D7). Lý lẽ: đơn vị là thuộc tính tổ chức, không phải danh tính cá nhân; auditor cần biết đổi từ đâu. Họ tên/email bị requirement hiện hành cấm. Người duyệt nếu coi đơn vị cũng là PII thì đây là chỗ bác.
6. donVi là chuỗi tự do ⇒ dữ liệu sẽ lệch chính tả giữa người nhập. Trần 200 + strip + CHECK không rỗng chỉ
chặn rác thô; khi có danh mục thật phải chuẩn hoá lại (expand-contract sau). ChuThe thêm trường là mỗi lần một quyết
định PO, lưới T_pii bắt. Trục nhạy cảm dự kiến T_authz · T_pii · T_audit (+ T_authn) ⇒ cần verdict phiên khác
(⛔G4) trước khi thi công. Vận hành: một bước di trú V20 ở chế độ migrate trước khi lên mã; không biến môi
trường/bí mật mới.