Phía ngân hàng¶
Onboard tổ chức · nhóm 1 · 13 story · 🟡 chưa viết xong · còn 20 mốc
Chưa viết xong: còn 20 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-004/1.1 Schema Tổ chức và hồ sơ |
admin-be | 1 | cap/tpp-onboarding · mã số thuế là khoá tự nhiên, loại pháp nhân là enum bắt buộc · ⚠️ PO chốt 11/09 (tại làn admin-be, memlog change tpp-organization-schema @ d810c85): V12 BỎ trang_thai và ngay_duyet khỏi app.tpp — không SHALL, không design, không test; V11 đã ghi «cột trạng thái là nguồn sự thật thứ hai». Vòng đời/trạng thái hồ sơ là việc của 1.3. ⇒ Câu «trạng thái, ngày duyệt» ở epics.md:485 là nguyên liệu đã hết hiệu lực (⛔G1: không sửa ngược), hàng này là nơi ghi. Cùng lượt PO chốt: 8 khối Điều 8 đi bằng noi_dung jsonb (hoãn lần hai, có nợ) · tpp_profile chỉ-thêm · portal_app không SELECT trần (lệch AD-20 lần 2, vết ở OAPI-121) · đính kèm chỉ metadata |
E-004/1.2 Thẩm định mã số thuế |
admin-be | 2 | cap/tpp-onboarding · nhập tay + checker xác nhận |
E-004/1.3 Vòng đời hồ sơ |
admin-be | 3 | cap/tpp-onboarding · qua khung phê duyệt E-001/3.1 |
E-004/1.6 Khai bảng SUBMISSION và ma trận quyền portal_app |
admin-be | 5 | cap/schema-migration · ⛔ vật mang của E-004/2.2 · AD-14: bảng portal-be GHI vẫn do admin-be KHAI |
E-004/1.7 Khai bảng phiên cho người dùng bên thứ ba |
admin-be | 6 | cap/schema-migration · ⛔ vật mang của E-004/2.8 · AD-14 · ⚠️ phạm vi chốt bằng số đo của portal-be trước khi mở change |
E-004/1.4 Màn hồ sơ tổ chức |
admin-fe | 10 | cap/admin-console-ui · ⛔ màn riêng không modal · ô lý do nhắc hiển thị ra ngoài · PO chốt 13/09/2026: M:1.3 → Đ:1.3 — FE đi trước API vòng đời hồ sơ theo tiền lệ E-001/5.12 (Đ:2.4) và E-001/5.9 (P7, Đ:E-004/2.8): dựng trên gói (OrgList · OrgDetailScreen · OrgCreateModal) + máy chủ giả lập theo nếp port, màn tự khai chỗ chưa nối (§F); ⛔ không coi là xong khi 1.3 chưa archive và chưa nối thật · hợp đồng API 1.3 chưa có (OAPI-224 mới mở) ⇒ làn admin-fe nhắn admin-be xin openapi.yaml sớm (AD-28) |
E-004/1.5 Nhập dữ liệu tổ chức đang đấu nối |
admin-be | 13 | cap/tpp-onboarding · ⚠️ BỊ CHẶN: chưa rõ có tổ chức nào |
E-004/1.8 Khai bảng phiên bản văn bản pháp lý và vết đồng ý |
admin-be | 15 | cap/schema-migration · ⛔ vật mang của E-004/2.7 · AD-14 · ⛔ không ghi đè tại chỗ: bảng phải giữ được NHIỀU phiên bản và vết ai đồng ý bản nào |
E-004/1.10 API xem người dùng bên thứ ba theo tổ chức |
admin-be | 18 | cap/tpp-onboarding · mở 11/09 tối (PO chốt P2: A3 = CHỈ XEM) · liệt kê + chi tiết app.tpp_user gom theo tổ chức (admin_app đã có SELECT từ V3) · ⛔ 2.1 (portal-be) đã archive, không đụng · ⛔ KHÔNG gán vai, KHÔNG khoá/mở trong story này — gán vai chờ nguồn vai TPP — nợ có tên OAPI-205 (openapi-platform#23; ~~E-005~~ ⛔ đo 12/09 E-005 không có story vai TPP, trỏ vào đó là trỏ chỗ trống), khoá/mở là thao tác ràng buộc (K2 + duyệt 3.1) ⇒ nợ có tên, mở story sau khi hai nguồn đó có |
E-004/1.9 Màn xem người dùng bên thứ ba (phía quản trị) |
admin-fe | 19 | cap/admin-console-ui · mở 11/09 tối (PO chốt P2: A3 = CHỈ XEM) · gói AccountList({scope:"tpp"}) (cột Tổ chức + Vai trò trong tổ chức, ⛔ không nút Tạo mới) + khối «Người dùng thuộc tổ chức» trong OrgDetailScreen · cột Vai trò trong tổ chức ⛔ chưa có nguồn (nợ vai TPP OAPI-205, openapi-platform#23) ⇒ lượt đầu bỏ, ghi nợ cùng nếp OAPI-142 · ⛔ không nút gán vai / khoá / mở (xem 1.10) |
E-004/1.11 Khai bảng đính kèm của đơn nộp và kho nội dung tệp |
admin-be | 21 | cap/schema-migration · mở 12/09 (PO chốt 8 mục — AD-30) · ⛔ vật mang của E-004/2.11 · AD-14 · app.submission_attachment (metadata, nối submission, khuôn V12 tpp_attachment: ten_file · loai_mime · kich_thuoc_byte · bam · duong_dan = khoá kho db:<uuid>) + app.tep_noi_dung (bytea, xoá được — AD-30 mục 1) · GRANT portal_app INSERT/SELECT theo hàng (nếp V5), ⛔ không UPDATE/DELETE · ⛔ hình dạng do 2.11 chốt trong design.md trước, 1.11 không tự đoán — ba nhịp như cặp 1.7/2.8 · trả nợ OAPI-149 (admin-be#53) |
E-004/1.12 Hàm lọc đọc byte kho tệp cho portal_app |
admin-be | 23 | cap/schema-migration · mở 13/09 (PO chốt OAPI-230-cô-lập-byte-kho-chung cách 1) · nhịp ①/③ thu hồi SELECT trần trên app.tep_noi_dung · hàm trả noi_dung chỉ khi khoá kho thuộc đính kèm của đơn người gọi (join submission_attachment_of_user, nếp V18) · ⛔ SECURITY INVOKER không đủ sau thu hồi ⇒ DEFINER hẹp + SET search_path + REVOKE FROM PUBLIC + GRANT EXECUTE portal_app · ⛔ chưa thu hồi gì ở đây (chiều THÊM — admin-be trước) |
E-004/1.13 Thu hồi SELECT trần của portal_app trên app.tep_noi_dung |
admin-be | 25 | cap/schema-migration · mở 13/09 (PO chốt OAPI-230 cách 1) · nhịp ③/③ · V2x REVOKE SELECT ON app.tep_noi_dung FROM portal_app + ma trận quyền + lưới · đo has_table_privilege('portal_app','app.tep_noi_dung','SELECT') = false · ⛔ TR: bản portal-be của 2.12 phải chạy trên mọi môi trường TRƯỚC khi migration này chạy — chiều THU HỒI đảo AD-14, án lệ V7/OAPI-83-dat-updated-at-không-revoke-execute |
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-004/1.1 Schema Tổ chức và hồ sơ — admin-be¶
⟨CẦN NGƯỜI VIẾT — agent làn admin-be viết khi đóng change⟩
E-004/1.2 Thẩm định mã số thuế — admin-be¶
Góp gì vào mục tiêu epic. Phụ lục 01 §1 TT64 định nghĩa định danh bên thứ ba là mã số thuế hợp lệ, còn
hoạt động (TT-05), và FR-010 đòi ngân hàng thẩm định mã đó trước khi tổ chức đi tiếp. Chưa có API tra cứu
([ASSUMPTION C7]) nên thẩm định là việc người làm bằng tay: nhân sự tra ở cổng thuế, nhập kết quả kèm bằng
chứng, và một người thứ hai xác nhận. Story này là mắt xích thứ hai của đường găng A5
(1.1 → 1.2 → 1.3 → 2.5, PO chốt 13/09 mở cả 1.2 và 1.3): không có bản ghi thẩm định thì 1.3 (vòng
đời hồ sơ) không có căn cứ để chặn duyệt tổ chức có mã ngừng hoạt động.
- Là bộ áp nội dung sản xuất đầu tiên của khung phê duyệt
E-001/3.1. Khung đã archive với ghi chú «ap_dung_lucchưa bao giờ được đặt cho tới khi có story cắm» — story này là story cắm: một lượt thẩm định = một đề xuất của khung, hai người, áp ngay khi đủ cấp. K3 (tách trách nhiệm) giữ nguyên ở đây — khác nhóm phân quyền GĐ1. - Trả ba câu hỏi treo từ
1.1bằng cấu trúc, không bằng quy ước: (a) mã số thuế đổi được ⇒ thẩm định gắn với giá trị tại lượt, đổi mã là tự mất hiệu lực; (b) bằng chứng ⇒ byte vào kho chungAD-30, siêu dữ liệu ở bảng riêng của tổ chức; (c) ai làm/ai duyệt ⇒tpp_organization.update(maker) vàtpp_organization.approve(checker) từ danh mục quyền2.4— super admin không tự có hai quyền này. - Vì sao hôm nay còn thiếu nó. Trước story này, cổng quản trị không có bề mặt HTTP nào cho tổ chức —
1.1chỉ khai bảng; đây là sáu đường đầu tiên dưới/api/v1/organizations/{id}, và cổng quyền chức năng đầu tiên cho cả tiền tố ấy (đường của1.3sau này tự bị chặn theo, không có đường «quên gắn quyền»). - Giới hạn cố ý: ⛔ không tra cứu tự động; ⛔ không quét mã độc (
AD-30mục 3, nợ có cổng); ⛔ không nhận PNG (AD-30mục 2 chốt PDF + JPEG — ảnh chụp màn hình phải lưu JPG hoặc in PDF); ⛔ không sửa đề xuất bị từ chối — mở đề xuất mới.
E-004/1.3 Vòng đời hồ sơ — admin-be¶
Góp gì vào mục tiêu epic. Sau 1.1 (bảng tổ chức/hồ sơ) và 1.2 (thẩm định mã số thuế), hồ sơ tổ chức vẫn
chưa có trạng thái (V12 cố ý bỏ cột trang_thai — «vòng đời là việc của 1.3») và chưa có bề mặt HTTP nào để
tạo tổ chức, gửi hồ sơ đi duyệt, hay trả về/duyệt/từ chối. Story này là bước thứ ba và cuối phía ngân hàng
trong đường găng A5/B3 (1.1 → 1.2 → 1.3 → 2.5): không có nó thì đơn nộp từ cổng bên thứ ba (2.5) không
có nơi tiếp nhận, và màn hồ sơ tổ chức E-004/1.4 (admin-fe, đang dựng trên máy chủ giả lập) không có gì để nối.
- Thi hành bốn điều luật một lượt:
FR-015vòng đời Nháp → Chờ duyệt → (Chờ bổ sung | Đã duyệt | Từ chối);FR-016(Điều 10.1 TT64) trả về Chờ bổ sung kèm lý do bắt buộc;FR-012(Điều 8, TT-02) thiếu khối nào thì không cho gửi duyệt;FR-019sửa hồ sơ đã duyệt phải qua hai người, bản cũ vẫn hiệu lực tới khi bản mới được duyệt. K3 (tách trách nhiệm) giữ nguyên cho hồ sơ tổ chức — đúng phạm vi lệch có chủ ý GĐ1 chỉ áp cho phân quyền. - Là bộ áp sản xuất thứ hai của khung phê duyệt
E-001/3.1(sau1.2): loạiho_so_tppcó hàng chính sách từ V11 mà chưa bộ áp nào dùng. Một đề xuất = xin duyệt một phiên bản hồ sơ; kết luận là bản ghi chỉ-thêm — thanh tra thấy cả chuỗi «gửi → trả về vì lý do X → gửi bản mới → duyệt». - Cùng nếp
1.2, không dựng lại: trạng thái suy ra (⛔ không cột), đính kèm hồ sơ đi qua kho chung (AD-30mục 1, 2) với cùng bộ kiểm tệp, cùng cổng quyềnToChucPhanQuyenFilter—1.3chỉ nới có chủ ý ánh xạ quyền. - Giới hạn cố ý: ⛔ không sửa thông tin định danh tổ chức (tên pháp lý, giấy phép, mã số thuế — chưa có story);
⛔ không «chấm dứt/ngưng hoạt động» (
FR-035, epic khác) — «từ chối» không khoá tổ chức; ⛔ không cột thật cho tám khối Điều 8 (nợOAPI-148); ⛔ không hàng đợi chờ duyệt (3.3).
E-004/1.6 Khai bảng SUBMISSION và ma trận quyền portal_app — admin-be¶
Góp gì vào mục tiêu epic. Story này không thêm gì người dùng nhìn thấy. Nó là mắt xích bảng story thiếu — và trong lúc thiếu, nó đang chặn một làn khác đứng im.
- Chỗ hổng nằm giữa hai luật đều đúng.
E-004/2.2giaoportal-beghi bảng đơn nộp; nhưng⛔AD-14cấmportal-becó dòng di trú nào, và không story nào khai bảng ấy. Spine đã trả lời sẵn câu «ai khai»: bảngportal-beghi vẫn doadmin-bekhai. ⇒ Đây không phải xung đột kiến trúc, mà là bảng story thiếu một dòng so với chính spine. Lànportal-beđã dừng hẳn công việc của mình để chờ dòng đó. - Đơn tự nộp là cửa vào của cả epic.
FR-055cho bên thứ ba có tài khoản trước khi tổ chức được duyệt,FR-056cho họ nộp hồ sơ bằng đúng bộ trường mà nghiệp vụ dùng. Không có chỗ chứa đơn thì cửa ấy không mở được, và mọi story phía sau — tiếp nhận, thẩm định, phê duyệt — không có vật để làm việc. - Nửa dễ quên nhất của story này lại là nửa có răng.
portal-benay có cổng khởi động đo quyền theo cột: một bảng mới mà thiếu quyền đúng cho vai ứng dụng của cổng thìportal-betừ chối khởi động. Đó là hành vi đúng thiết kế, nhưng triệu chứng sẽ trông như lỗi củaportal-bechứ không như thiếu sót của lượt khai bảng này — tức mất thời gian tìm ở nhầm chỗ.
Vị trí trong hàng đợi. Đứng ở thứ tự 5 của epic, ngay trước E-004/2.2 (đơn nộp hồ sơ,
portal-be) là story bị nó chặn. ⚠️ Nó chạy trước E-004/1.1 (schema Tổ chức và hồ sơ, thứ tự
1, chưa làm) — thứ tự đảo này không phải sơ suất mà là hệ quả của việc gỡ chặn, và nó tạo ra một
quyết định về hình dạng dữ liệu mà story 1.1 sẽ phải thừa hưởng hoặc nêu lý do đi khác (xem PRD).
E-004/1.7 Khai bảng phiên cho người dùng bên thứ ba — admin-be¶
⟨CẦN NGƯỜI VIẾT — agent làn admin-be viết khi đóng change⟩
E-004/1.4 Màn hồ sơ tổ chức — admin-fe¶
⟨CẦN NGƯỜI VIẾT — agent làn admin-fe viết khi đóng change⟩
E-004/1.5 Nhập dữ liệu tổ chức đang đấu nối — admin-be¶
⟨CẦN NGƯỜI VIẾT — agent làn admin-be viết khi đóng change⟩
E-004/1.8 Khai bảng phiên bản văn bản pháp lý và vết đồng ý — admin-be¶
⟨CẦN NGƯỜI VIẾT — agent làn admin-be viết khi đóng change⟩
E-004/1.10 API xem người dùng bên thứ ba theo tổ chức — admin-be¶
Góp gì vào mục tiêu epic. Màn A3 «Người dùng TPP (phía quản trị)» — PO chốt 11/09 tối là CHỈ XEM — cần một
nguồn dữ liệu, và tới trước story này admin-be không đọc app.tpp_user ở đâu cả: không repository, không route,
openapi.yaml không có /tpp-users*. Story mở ba đường chỉ đọc để E-004/1.9 (màn người dùng bên thứ ba,
admin-fe, (chưa có issue)) và khối «Người dùng thuộc tổ chức» trong màn hồ sơ tổ chức E-004/1.4
(OAPI-225, đang chạy trên máy chủ giả lập) có API thật để nối.
- Vá một lỗ dữ liệu có sẵn từ V2:
tpp_user.tpp_idchưa có khoá ngoại tớiapp.tpp(V2 hứa «thêm ở change khai TPP», V12 không thêm). Story thêm FK chỉ-thêm, không cascade (AD-4.2— xoá tổ chức không được âm thầm cắt người thật). Hôm nay mọitpp_id = NULLvì chưa story nào ghi — nên «người dùng theo tổ chức» là tập rỗng cho tới khiOAPI-242chốt ai gắn. - Quyền là dữ liệu, không thêm mã quyền: bề mặt
/tpp-usersdùnguser.read(«Xem Người dùng MSB + TPP», có sẵn từ V16); super admin không mặc nhiên có. Đường dưới tổ chức kế thừa cổngtpp_organization.readcủa1.2. - Giới hạn cố ý — đúng chữ PO «chỉ xem»: ⛔ không gán vai (chưa có nguồn vai TPP — nợ
OAPI-205); ⛔ không khoá/mở/vô hiệu (thao tác ràng buộc, K2 + duyệt3.1, story sau); ⛔ không ghitpp_id; ⛔ không họ tên/điện thoại (cột không tồn tại); ⛔ không đọc phiên hay vết đăng nhập của bên thứ ba (dữ liệu vận hành củaportal-be).
E-004/1.9 Màn xem người dùng bên thứ ba (phía quản trị) — admin-fe¶
⟨CẦN NGƯỜI VIẾT — agent làn admin-fe viết khi đóng change⟩
E-004/1.11 Khai bảng đính kèm của đơn nộp và kho nội dung tệp — admin-be¶
⟨CẦN NGƯỜI VIẾT — agent làn admin-be viết khi đóng change⟩
E-004/1.12 Hàm lọc đọc byte kho tệp cho portal_app — admin-be¶
⟨CẦN NGƯỜI VIẾT — agent làn admin-be viết khi đóng change⟩
E-004/1.13 Thu hồi SELECT trần của portal_app trên app.tep_noi_dung — admin-be¶
⟨CẦN NGƯỜI VIẾT — agent làn admin-be viết khi đóng change⟩
Ai dùng, dùng thế nào — URD¶
Một hành trình cho cả nhóm, kể từ bề mặt người dùng chạm vào.
E-004/1.4 Màn hồ sơ tổ chức — admin-fe¶
⟨CẦN NGƯỜI VIẾT — agent làn admin-fe viết khi đóng change⟩
E-004/1.9 Màn xem người dùng bên thứ ba (phía quản trị) — admin-fe¶
⟨CẦN NGƯỜI VIẾT — agent làn admin-fe viết khi đóng change⟩
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-004/1.1 Schema Tổ chức và hồ sơ — admin-be¶
Chờ hồ sơ. Tiêu chí dự kiến từ kế hoạch:
- Tổ chức: mã, tên pháp lý, mã ngắn, số giấy phép ĐKKD, mã số thuế, loại pháp nhân, trạng thái, ngày duyệt.
- Mã số thuế là khoá tự nhiên duy nhất, ràng buộc
UNIQUEđặt trênTPP(FR-008,FR-009). - Loại pháp nhân là enum bắt buộc: Ngân hàng · Tổ chức trung gian thanh toán · Tổ chức khác (
FR-011) — nó là nửa còn lại củaAD-21. - Hồ sơ có phiên bản, không ghi đè tại chỗ (
BL-03); mang tám khối nội dung Điều 8 (FR-012) và trường cấp độ an toàn hệ thống (FR-013). - Đính kèm tài liệu (
FR-014).
⟨CẦN NGƯỜI VIẾT — agent làn admin-be viết khi đóng change⟩
E-004/1.2 Thẩm định mã số thuế — admin-be¶
Nguồn hồ sơ. admin-be @ origin/main 0320cf0 — openspec/changes/archive/2026-09-13-tham-dinh-ma-so-thue/
(proposal.md · design.md D1–D10 + «Review outcome» · specs/tpp-onboarding/spec.md 7 requirement, 38 scenario ·
specs/approval-workflow/spec.md 2 requirement · specs/iam-internal-users/spec.md 1 requirement · tasks.md ·
test-cases.md · security.md); bản thảo viết từ nhánh 11a6517, bản cuối soát lại theo archive — proposal/specs/tasks
nguyên chữ, design.md chỉ thêm mục «Review outcome» sau dấu duyệt theo protocol đóng change (0 dòng xoá, không đổi
quyết định nào; sha artifact ở archive 8abd251, dấu duyệt ghim bản 0644f5b PO đã đọc). Issue OAPI-223-tham-dinh-ma-so-thue
(thangvv111/admin-be#73). Change ĐÃ đóng (archive 13/09/2026,
merge 0320cf0) ⇒ bản cuối. Vật mang: di trú app/V19 của chính change (chỉ THÊM, AD-14.1).
Yêu cầu và cách đo¶
| Yêu cầu | Ràng buộc | Đo bằng |
|---|---|---|
Một lượt thẩm định = một đề xuất của khung 3.1, mở-và-gửi trong một giao dịch |
POST /api/v1/organizations/{id}/tax-code-verification-requests (multipart: phần yeuCau JSON ≤ 16 KiB + bangChung 1–5 tệp): kiểm khuôn → băm từng tệp → mở đề xuất loại tham_dinh_ma_so_thue → ghi byte + siêu dữ liệu → gửi duyệt. Đổ ở đâu cuộn hết: ⛔ không byte mồ côi, ⛔ không đề xuất thiếu bằng chứng. 0 tệp ⇒ 400 — lời khai không bằng chứng không phải thẩm định |
Scenario nhóm «Thẩm định đi qua hai người»; ca ThamDinhMaSoThueHttpTest |
| Nội dung đề xuất mang ảnh chụp mã số thuế do máy chủ điền | {ma_so_thue (chụp từapp.tpplúc mở — ⛔ không nhận từ client), ket_qua, ten_theo_tra_cuu, nguon_tra_cuu, ghi_chu, bang_chung: [sha256…]} — chỉ dấu vân tệp vào nội dung đề xuất, ⛔ không tên tệp, ⛔ không byte (bốn bảng khung không xoá được) |
Ca kiểm thân đề xuất; bite: gửi ma_so_thue khác trong yeuCau ⇒ bị bỏ qua |
| Một đề xuất đang chờ mỗi tổ chức; bị từ chối ⛔ không sửa | Đề xuất thứ hai ⇒ 409; phép kiểm tuần tự hoá ở tầng dữ liệu (khoá hàng tổ chức FOR NO KEY UPDATE trước khi hỏi — hai lượt mở đồng thời thì lượt sau xếp hàng). Từ chối ⇒ maker mở đề xuất mới; đề xuất cũ còn nguyên với lý do |
Scenario «Mỗi tổ chức chỉ một đề xuất đang chờ»; ca hai luồng mở đồng thời |
| Kết quả là từ vựng đóng, kết quả âm vẫn là lượt thẩm định hợp lệ | con_hoat_dong · ngung_hoat_dong · khong_tim_thay (CHECK ở CSDL). Kết quả âm vẫn phải hai người xác nhận và được giữ — bằng chứng «ngân hàng có kiểm» cho thanh tra |
HinhBangV19Test; scenario kết quả âm |
| Checker quyết định; đủ cấp ⇒ áp ngay trong giao dịch | POST …/{deXuatId}/decisions {tuChoi, lyDo} — người giữ tpp_organization.approve; K3 do khung V11 thi hành (maker ≠ checker) cộng CHECK nguoi_tham_dinh_id <> nguoi_xac_nhan_id ngay trên bảng thẩm định. Bộ áp: so ảnh chụp mã số thuế với mã hiện tại — lệch ⇒ 409, cuộn cả quyết định (đề xuất giữ «chờ duyệt», checker từ chối bằng tay, ⛔ không tự từ chối hộ); băm lại toàn bộ byte bằng chứng so đa tập với dấu vân đã ký — thiếu/lệch ⇒ 409 |
Scenario «áp khi mã đã đổi» · «bằng chứng đổi sau khi mở»; bite: gỡ phép so ⇒ ca đỏ |
| Bản ghi thẩm định là chỉ-thêm, không bịa được | app.tpp_tax_verification: admin_app chỉ SELECT, INSERT theo cột; UNIQUE(approval_request_id) (một đề xuất áp đúng một lần); trigger BEFORE INSERT buộc hàng trỏ đề xuất đã áp, đúng loại, đúng tổ chức, nguoi_tham_dinh = nguoi_mo, nguoi_xac_nhan có chữ ký duyệt ở vòng hiện tại, ma_so_thue/ket_qua khớp nội dung đề xuất. Trigger chạy quyền người gọi (⛔ không SECURITY DEFINER), hàm REVOKE EXECUTE FROM PUBLIC |
HinhBangV19Test · MaTranQuyenThamDinhMstTest; bite: admin_app INSERT thẳng hàng không có đề xuất ⇒ CSDL từ chối |
Bằng chứng: byte vào kho AD-30, siêu dữ liệu chỉ-thêm gắn tổ chức + đề xuất |
app.tpp_tax_evidence (tên tệp · MIME theo magic bytes · kích thước · bam · khoá db:<uuid> · người ghi · approval_request_id); admin_app thêm INSERT (id, noi_dung) trên app.tep_noi_dung — admin-be thành bên ghi kho (AD-30 mục 1, 6). portal_app ⛔ không quyền nào trên hai bảng mới |
Ma trận quyền +4 dòng, sửa 1; MaTranArtifactTest |
| Kiểm tệp tại điểm nhận theo nội dung thật, trần là cấu hình | Magic bytes %PDF- · FF D8 FF; ⛔ không tin Content-Type/đuôi; ngoài whitelist / tệp rỗng ⇒ 400 ADM.REQUEST.VALIDATION_FAILED, ⛔ không lưu tạm. Trần 10 MB/tệp (ADMIN_ATTACHMENT_MAX_FILE_SIZE), request 60 MB, phần ≤ trần nằm trong bộ nhớ (⛔ không đĩa tạm), Tomcat nuốt hết thân bị từ chối ⇒ 413 có tên ADM.REQUEST.PAYLOAD_TOO_LARGE; thân hỏng ⇒ 400 MALFORMED, ⛔ không 500 |
Lớp miền đọc cùng knob (MockMvc không qua Tomcat) — ca dựng knob nhỏ để bite đo được; trần request đo tay trên Tomcat thật (T5.2) |
| Trạng thái thẩm định là SUY RA, ⛔ không cột; đổi mã ⇒ mất hiệu lực | GET …/tax-code-verification: không hàng ⇒ chua-tham-dinh; hàng gần nhất có ma_so_thue ≠ mã hiện tại ⇒ het-hieu-luc; khớp ⇒ con-hoat-dong · ngung-hoat-dong · khong-tim-thay; kèm thamDinhGanNhat và deXuatDangCho nếu có. ⛔ Không cột da_tham_dinh trên app.tpp (nguồn sự thật thứ hai). Lịch sử GET …/tax-code-verifications phân trang |
Scenario «đổi mã số thuế làm mất hiệu lực»; 1.3 đọc trạng thái này để chặn duyệt hồ sơ |
| Tải bằng chứng so lại dấu vân, không inline, mỗi lượt một vết | GET …/attachments/{bangChungId}: thuộc đúng đề xuất/tổ chức (khác ⇒ 404) → byte theo khoá (db: khác / đã xoá ⇒ 404) → băm lại, lệch ⇒ 409 ⛔ không trả byte, vết THAT_BAI + lech_dau_van BỀN (commit trước, ném sau) → Content-Type = MIME đã lưu · Content-Disposition: attachment (filename* mã hoá) · nosniff · no-store → vết bang_chung_mst.tai_ve cùng giao dịch (chủ thể người tải; AD-30 mục 5 «ai đã xem giấy tờ của tổ chức X»). HEAD ⇒ 405 (không ghi vết «đã tải» giả) |
Scenario «Tải bằng chứng…»; bite: sửa byte trong kho ⇒ 409 và vết lech_dau_van còn sau rollback |
| Bề mặt tổ chức đi qua quyền chức năng, hỏi mỗi lượt | ToChucPhanQuyenFilter cho mọi /api/v1/organizations/**: GET/HEAD ⇒ tpp_organization.read; mở đề xuất ⇒ .update; quyết định ⇒ .approve; phương thức ghi khác (kể cả đường chưa tồn tại) ⇒ .approve — fail-closed bằng quyền mạnh nhất. Thiếu ⇒ 403 ADM.AUTH.FORBIDDEN + details[{permission, <mã>}]. Hỏi iam qua API công khai ChuTheVaQuyen (một SELECT EXISTS mỗi lượt, ⛔ không cache — K1); lỗi CSDL trong filter ⇒ 500 fail-loud, ⛔ không nuốt thành 403. Super admin ⛔ không tự có read/update/approve |
ToChucPhanQuyenFilterTest · RanhGioiGoiToChucTest (onboarding chỉ import iam.ChuTheVaQuyen) · ChuTheVaQuyenTest |
| Vết miền, tham chiếu, cùng giao dịch | bang_chung_mst.ghi (mỗi tệp, maker) · to_chuc.tham_dinh_mst (lúc áp, chủ thể người xác nhận; mang mã số thuế — định danh tổ chức, không phải dữ liệu cá nhân) · bang_chung_mst.tai_ve; ⛔ không tên tệp, ⛔ không ghi_chu/ten_theo_tra_cuu vào vết. Bốn vết de_xuat.* của khung ghi như thường |
VetThamDinhMstTest |
| Bề mặt bên thứ ba không chạm siêu dữ liệu bằng chứng và bản ghi thẩm định | portal_app không quyền nào trên hai bảng mới — nhưng xem ⚠️ 1 dưới về byte |
Ma trận quyền; scenario tpp-onboarding cuối |
⛔ Cố ý KHÔNG có trong story này¶
⛔ Vòng đời hồ sơ / trạng thái tổ chức, API tạo·sửa·liệt kê tổ chức — E-004/1.3. ⛔ Đường đổi mã số thuế qua HTTP
(chưa story nào mở; change chỉ bảo đảm đổi mã là mất hiệu lực). ⛔ Tra cứu tự động. ⛔ Quét mã độc (AD-30 mục 3).
⛔ Hàng đợi chờ duyệt (3.3 — cần sổ ánh xạ loại đối tượng → đặc quyền duyệt; change này ghim
tham_dinh_ma_so_thue → tpp_organization.approve ở QuyenToChuc). ⛔ Sửa nội dung đề xuất đã gửi. ⛔ Rút đề xuất đang
chờ (khung 3.1 không có rut). ⛔ PNG trong whitelist. ⛔ Không đụng app.tpp_attachment (đính kèm hồ sơ Điều 8 — vật khác).
⚠️ Người duyệt cần biết — sáu chỗ¶
1. Byte bằng chứng nằm ở kho chung mà portal_app còn SELECT mức bảng (V18). Câu «bên thứ ba không chạm bằng
chứng» đúng với siêu dữ liệu, sai với byte: vai CSDL của cổng bên thứ ba đọc được byte bằng chứng nội bộ
(bản chụp có thể mang tên người) nếu có khoá kho (uuid v4 — siêu dữ liệu mang khoá thì portal_app không đọc được).
Mọi cách khép (thu hồi SELECT trần + hàm lọc · kho riêng · RLS) chạm portal-be ⇒ issue quy ước
OAPI-230 cô lập byte kho chung — PO chọn một lần,
change này không tự đổi quyền của V18.
2. Đề xuất «kẹt chờ duyệt» là chủ đích. Mã số thuế đổi sau khi mở ⇒ lúc duyệt bộ áp ném 409 và cuộn cả quyết
định; hệ thống ⛔ không tự từ chối hộ — một quyết định từ chối phải là của một người. Checker phải từ chối kèm lý
do rồi maker thẩm định lại. Không có đường rút cho maker mở nhầm — thêm rut là đổi khung, ghi Open Questions.
3. Cổng quyền là guard tầng ứng dụng; K3 mới có lớp CSDL. admin_app bị chiếm vẫn INSERT được ba bảng — lớp bù là
vết (cùng vector S8.1(d) của 2.4/2.7). Riêng K3 có lớp CSDL thật: V11 của khung + CHECK/UNIQUE/trigger sáu điều
kiện trên bảng thẩm định — một hàng thẩm định không bịa được mà không bịa trọn một đề xuất và một chữ ký duyệt.
4. Mặc định fail-closed cho 1.3. Filter khớp cả tiền tố: đường ghi mới của 1.3 mà quên ánh xạ quyền thì maker bị
403 (đòi approve) chứ ⛔ không lọt; 1.3 chỉ nới có chủ ý ở QuyenToChuc. Người duyệt 1.3 nên đọc lại bảng ánh
xạ này.
5. Văn bản tự do trong bảng chỉ-thêm không xoá được. ghi_chu (≤ 4096) và ten_theo_tra_cuu (≤ 254) sống mãi theo
K2; người nhập gõ nhầm dữ liệu cá nhân thì không xoá được — muốn xoá được phải tách bảng xoá-có-vết (cùng lớp
OAPI-206). Hiện chấp nhận với trần độ dài + thông điệp hợp đồng.
Cũng nợ: chu kỳ thẩm định lại theo thời gian (mã không đổi nhưng doanh nghiệp đã ngừng) — chưa có FR, chờ PO.
6. Hai con số là quyết định lượt thi công, người chấm bác được. Tối đa 5 tệp một đề xuất; trần 254/4096 ký tự
cho hai trường văn bản — đều ở GioiHanHoSo. Heap: một lượt mở giữ tới 5 × 10 MB, bộ áp đọc lại cùng khối lúc duyệt —
chấp nhận ở GĐ1 (tổ chức hàng chục, thẩm định hàng đơn vị), hạ knob nếu chật.
E-004/1.3 Vòng đời hồ sơ — admin-be¶
Nguồn hồ sơ. admin-be @ origin/vong-doi-ho-so b10fd66 (artifact a4024aa; bản thảo đầu từ 92ac34b, cập nhật theo lens review 13/09) — openspec/changes/vong-doi-ho-so/ (proposal.md ·
design.md D1–D9 · specs/tpp-onboarding/spec.md 5 requirement ADDED + 1 MODIFIED (quyền), 42 scenario ·
specs/approval-workflow/spec.md 1 requirement · tasks.md · test-cases.md · security.md). Issue OAPI-224-vong-doi-ho-so
(thangvv111/admin-be#74). Change ĐANG mở (artifact đẩy sớm
theo AD-25.5, đang thi công) — đây là bản thảo, soát lại theo archive khi đóng. Vật mang: di trú app/V21 của
chính change (chỉ THÊM, AD-14.1).
Change thứ hai của story — bo-sung-hop-dong-to-chuc (OAPI-258-bo-sung-hop-dong-be-mat-to-chuc,
thangvv111/admin-be#79) — đã đóng (archive 2026-09-14-bo-sung-hop-dong-to-chuc, merge main @ bec973f;
bản thảo viết từ nhánh 55cae1f, artifact 3dbcbd3), docs: khong-can. Lens review trước khi đóng thêm duyetLuc vào cả
chi tiết tổ chức và nguoiGhiHoTen ở phiên bản hồ sơ — cùng nếp *HoTen đã tả dưới, không đổi hàng nào. PO chốt 14/09 theo bốn số đo của admin-fe khi nối màn 1.4: danh sách
thêm soGiayPhepDkkd + duyetLuc; tên hiển thị *HoTen bên mã người; chi tiết gộp capDoAnToanMoiNhat + lichSuDeXuat[];
mã trạng thái nhap → ban-nhap. Spec requirement «Bề mặt HTTP tổ chức» MODIFIED (+4 scenario). Không di trú, không đổi
quyền, không đổi vết — bảng dưới đã cập nhật hai hàng tương ứng.
Yêu cầu và cách đo¶
| Yêu cầu | Ràng buộc | Đo bằng |
|---|---|---|
| Trạng thái hồ sơ SUY RA, ⛔ không cột | ban-nhap · cho-duyet · cho-bo-sung · da-duyet · tu-choi (mã nhap đổi thành ban-nhap ở change thứ hai) = trạng thái của đề xuất ho_so_tpp mới nhất đối chiếu với kết luận của nó; đề xuất đã quyết mà không có kết luận ⇒ 500 fail-loud (hàng nội bộ hỏng, ⛔ không đoán). Kèm phienBanHieuLuc (= phiên bản của kết luận da_duyet gần nhất — có thể khác phienBanMoiNhat) và deXuatDangCho. Sau da-duyet, ghi bản mới vẫn da-duyet tới khi mở đề xuất mới (FR-019: bản đã duyệt không đổi ngầm) |
Scenario nhóm «Trạng thái hồ sơ là suy ra»; ToChucHttpTest |
| Gửi duyệt = mở một đề xuất cho đúng MỘT phiên bản, một giao dịch | POST …/{id}/profile-review-requests {phienBan}: khoá hàng tổ chức → phiên bản thuộc tổ chức → cổng Điều 8 → một đề xuất chờ mỗi tổ chức (còn ⇒ 409) → mở + gửi. Nội dung đề xuất ghim tpp_profile_id · phien_ban · cap_do_an_toan · bam_noi_dung · dinh_kem[sha256…] — ⛔ không chép tám khối vào đề xuất (khung không xoá được, có thể mang tên người). ⛔ Không xin duyệt phiên bản cũ hơn hoặc đang là bản hiệu lực (409 — lùi là ghi bản mới); mỗi phiên bản đi qua đúng một đề xuất — bị trả về thì ⛔ không gửi lại cùng bản (đính kèm của bản ấy đã đóng băng); được xin duyệt bản không phải mới nhất |
Scenario «Gửi duyệt là mở một đề xuất…»; bite: gửi bản 2 khi bản 3 đã duyệt ⇒ 409 |
Cổng tám khối Điều 8 (FR-012) ở lượt gửi duyệt, ⛔ không ở lượt ghi nháp |
noi_dung phải có đủ 8 khoá không rỗng: camKetBaoMat · camKetPhamViDuLieu · thongBaoViPhamNhanSu · dichVuCungCap · phiDichVu · capDoAnToanHeThong · quyenTruyCapDuLieu · dieuKhoanChamDut (thứ tự = khoản Điều 8). Khoản 5 «phí (nếu có)» vẫn bắt buộc có khoá — «không thu phí» là giá trị hợp lệ. Thiếu ⇒ 400 ADM.PROFILE.MISSING_FIELD + details[{field, REQUIRED}] cho mọi khoá thiếu. Khoá lạ được giữ. Một lớp ứng dụng (tên khoá ghim ở KhoiDieu8, ⛔ không CHECK jsonb) — xem ⚠️ 1 |
KhoiDieu8Test; bite: gỡ cổng ⇒ ca đỏ (S8.2 ghi rõ tắt được bằng refactor) |
| Ba kết cục trên hai chiều của khung, phân biệt bằng bảng kết luận chỉ-thêm | POST …/{deXuatId}/decisions {ketQua: duyet \| cho-bo-sung \| tu-choi, lyDo}. duyet ⇒ lyDo rỗng; đủ cấp ⇒ bộ áp băm lại nội dung phiên bản + toàn bộ byte đính kèm so với dấu vân đã ký (lệch ⇒ 409, cuộn cả quyết định) rồi ghi kết luận da_duyet. cho-bo-sung/tu-choi ⇒ quyết định tu_choi của khung (vòng mới) + kết luận kèm lý do bắt buộc — CHECK đối xứng: không duyệt ⇒ lý do không rỗng (≤ 4096), duyệt ⇒ ly_do IS NULL — là FR-016/FR-021 ở CSDL (trigger đã đòi lý do khớp quyết định đã ký, CHECK là backstop). cap = số quyết định duyệt ở vòng hiện tại + 1 (⛔ không giả định 1 cấp); kết luận da_duyet neo vào chữ ký cấp cuối (cap = so_cap_can). K3 hai mặt: người quyết định ⛔ không phải người gửi (khung) và ⛔ không phải người ghi phiên bản mà đề xuất ghim (service 409 + trigger) — xem ⚠️ 6 |
Scenario «Kết luận hồ sơ là bản ghi chỉ-thêm…»; HinhBangV21Test; bite: INSERT kết luận không lý do ⇒ 23514 |
| Kết luận không bịa được | app.tpp_profile_review: UNIQUE(approval_request_id); trigger BEFORE INSERT sáu vế — đề xuất tồn tại, đúng loại, đúng tổ chức, đúng tpp_profile_id; người quyết định ≠ tpp_profile.nguoi_ghi_id; da_duyet ⇒ đề xuất đã áp + người quyết định có chữ ký duyet cấp cuối ở vòng hiện tại; cho_bo_sung/tu_choi ⇒ có chữ ký tu_choi ở vòng liền trước và ly_do khớp lý do checker gõ. admin_app chỉ SELECT, INSERT theo cột; portal_app ⛔ không quyền; hàm trigger REVOKE EXECUTE FROM PUBLIC |
MaTranQuyenHoSoTest; bite: admin_app INSERT kết luận không có đề xuất/chữ ký ⇒ CSDL từ chối |
| Sau trả về / từ chối: phiên bản mới + đề xuất mới, ⛔ không gửi lại đề xuất cũ | Đề xuất cũ giữ nguyên với lý do; tu-choi không là trạng thái chết — vẫn mở được đề xuất mới (khác cho-bo-sung chỉ ở ngữ nghĩa cho người đọc và cho 2.5/portal). Chấm dứt tổ chức là story khác |
Scenario chuỗi «trả về → bản mới → duyệt» |
| Đính kèm hồ sơ qua kho chung, chỉ gắn khi phiên bản chưa từng gửi duyệt | POST …/profile-versions/{phienBan}/attachments (một tệp/lượt, PDF/JPEG theo magic bytes, trần = knob multipart; tên tệp ⛔ ký tự điều khiển/dấu phân cách đường dẫn; tối đa 20 tài liệu mỗi phiên bản — không trần thì dinh_kem[] trong đề xuất vượt 64 KiB) → khoá hàng tổ chức (đính kèm xếp hàng sau lượt gửi duyệt đang chạy, không thì bộ áp 409 vĩnh viễn) → kho AD-30 → app.tpp_attachment → vết. Trigger V21 trên tpp_attachment từ chối INSERT khi phiên bản đã nằm trong một đề xuất (kể cả đề xuất bị trả về) — người duyệt duyệt đúng tập đính kèm đã thấy; muốn thêm tài liệu ⇒ ghi phiên bản mới, đính lại (tài liệu là của phiên bản). Tải về: so lại sha256 (lệch ⇒ 409 + vết THAT_BAI bền) · attachment · nosniff · no-store · HEAD ⇒ 405 — y hệt 1.2 D8, dùng chung TepDinhKem |
Scenario «Đính kèm hồ sơ…»; ThamDinhMaSoThueHttpTest phải xanh nguyên sau refactor TepDinhKem |
| Bề mặt HTTP tổ chức đi qua quyền chức năng | 11 thao tác dưới /api/v1/organizations: tạo (create; mã số thuế trùng ⇒ 409) · danh sách ?page&size&trangThai&loaiPhapNhan&q (read; q khớp mã số thuế chuẩn / tên pháp lý; hàng mang thêm soGiayPhepDkkd + duyetLuc — mốc kết luận da_duyet gần nhất, suy ra, ⛔ không cột) · chi tiết (read; một đường đủ cho màn: kèm thamDinhMst.trangThai của 1.2, capDoAnToanMoiNhat, lichSuDeXuat[] mọi đề xuất mới trước; bên mỗi mã người có *HoTen chỉ để hiển thị — mã nội bộ vẫn là khoá K4, người không còn ⇒ null, hỏi tên qua API công khai iam) · ghi phiên bản (update) · danh sách phiên bản phân trang ?page&size (read; số lượt ghi không trần) · đính kèm/tải (update/read) · gửi duyệt (update) · liệt kê đề xuất kèm ketLuan (read) · quyết định (approve). ⛔ Không PUT/PATCH /organizations/{id} — gọi ⇒ 403 approve trước cả 404 (fail-closed của 1.2); / cuối vẫn là biến thể lạ ⇒ approve. Spec ghi phần này là MODIFIED của requirement quyền của 1.2, không phải requirement mới |
ToChucPhanQuyenFilterTest (ánh xạ mới) · RanhGioiGoiToChucTest |
| Vết tham chiếu, cùng giao dịch | ho_so.gui_duyet (maker; de_xuat_id · phien_ban · bam_noi_dung) · ho_so.ket_luan (người quyết định; ket_qua · ket_luan_id — ⛔ không ly_do vào vết: lý do hiển thị cho bên thứ ba và có thể mang tên người, sống ở approval_decision + bảng kết luận) · dinh_kem.tai_ve. Ba vết của 1.1 nay có đường HTTP thật |
VetHoSoTest |
⛔ Cố ý KHÔNG có trong story này¶
⛔ Sửa thông tin định danh tổ chức qua HTTP (tên pháp lý · giấy phép · mã số thuế — chưa có story; đổi mã số thuế là câu
treo từ V12). ⛔ Ngưng hoạt động / chấm dứt hợp đồng (tpp_organization.disable, FR-035 — epic khác). ⛔ Lọc theo
«scope đang có» của FR-017 (chưa có scope). ⛔ Hàng đợi chờ duyệt (3.3 — cần thêm cặp ho_so_tpp →
tpp_organization.approve vào sổ ánh xạ). ⛔ Rút đề xuất (câu ngỏ từ 1.2). ⛔ Quét mã độc (AD-30 mục 3). ⛔ Cột thật
tám khối (OAPI-148). ⛔ Sửa nội dung đề xuất tại chỗ. ⛔ Xoá đính kèm (byte xoá là OAPI-206).
⚠️ Người duyệt cần biết — bảy chỗ¶
1. Cổng Điều 8 là MỘT lớp ứng dụng, tắt được bằng refactor. Hình dạng thật tám khối là nợ
OAPI-148 (chờ mẫu hợp đồng bank); tới lúc đó tên khoá ghim ở mã
(KhoiDieu8), ⛔ không CHECK ở CSDL — cố ý, vì ghim tên khoá đoán vào ràng buộc chỉ-thêm là đổi nợ mềm lấy ràng
buộc vĩnh viễn (1.1 D3). Bite trong lưới: gỡ cổng ⇒ ca đỏ. Khi OAPI-148 trả, lớp CSDL thay lớp này.
2. «Từ chối» không khoá tổ chức. Tổ chức bị từ chối vẫn mở được đề xuất mới — chủ đích: ngân hàng có thể đổi ý
sau khi bên thứ ba sửa. Chấm dứt/ngưng là FR-035 (epic khác). Người đọc màn 1.4 và 2.5 cần hiểu tu-choi là
«hồ sơ này không đạt», ⛔ không phải «tổ chức bị cấm».
3. Đính kèm hồ sơ chịu cùng biên byte kho chung như bằng chứng 1.2. portal_app còn SELECT mức bảng trên kho
(V18) ⇒ OAPI-230 cô lập byte kho chung — PO đã chốt cách 1 (thu hồi SELECT trần + hàm lọc), làn platform lo,
V2x về sau; change này không dựng thêm gì lên quyền kho.
4. Lọc theo trạng thái làm trong bộ nhớ. Trạng thái suy ra nên ?trangThai= lọc sau khi suy — đúng ở GĐ1
(tổ chức hàng chục); khi cần, đổi lối đọc sang SQL lateral không đổi hợp đồng.
5. Hai câu ngỏ, không chặn. Khoản 5 «phí (nếu có)»: change bắt buộc có khoá (giá trị chữ, kể cả «không thu
phí») — PO muốn cho vắng khoá thì bỏ một dòng ở KhoiDieu8. Chính sách ho_so_tpp đang 1 cấp từ V11 — bank đòi 2
cấp thì chỉnh hàng dữ liệu, bộ áp không giả định số cấp.
6. Nghĩa của K3 cho mọi bộ áp về sau — PO cần chốt. Change này chốt K3 theo nội dung: người ghi phiên bản ⛔ không
quyết định đề xuất của phiên bản ấy (A ghi bản, B gửi, A quyết là A ký lên nội dung A viết) — vì trục T_sod nói «việc mình
vừa tạo» và AD-9.2 nói «mọi người đã sửa nội dung»; spec 3.1 chỉ nhìn người gửi. Nếu PO muốn K3 chỉ theo người gửi,
gỡ một vế trigger + một if; nhưng đây là điểm chốt nghĩa K3 cho mọi bộ áp sau này (lens review 13/09 gọi tên). Hệ quả
UAT: ca thẩm định hồ sơ cần hai tài khoản thật sự tách vai.
7. Ba biên chạm story khác, đã ghi ở Impact. (a) Bán kính fail-loud của D1 là toàn danh sách: một tổ chức có đề xuất đã
quyết mà không kết luận làm GET /organizations 500 cho mọi người — chủ đích, và đường tạo hàng lệch duy nhất là ghi tay hoặc
E-001/3.3 gọi khung.quyetDinh thẳng cho loại ho_so_tpp ⇒ hàng đợi chung ⛔ không được làm thế, kết luận chỉ sinh qua bộ
áp của HoSoService. (b) ly_do phải hiển thị cho bên thứ ba (UX-DR7) nhưng portal_app không có quyền nào trên bảng kết luận
lẫn approval_decision ⇒ E-004/2.5 tiếp nhận đơn nộp (chưa có issue) phải khai lối đọc lý do cho portal (T_iso). (c) Kết
luận sai chiều (da_duyet cho đề xuất bị từ chối) cũng fail-loud 500 — chỉ ghi tay mới tạo được.
E-004/1.6 Khai bảng SUBMISSION và ma trận quyền portal_app — admin-be¶
Nguồn hồ sơ. admin-be @ origin/khai-bang-submission e762657 —
openspec/changes/khai-bang-submission/ (proposal.md · design.md ·
specs/schema-migration/spec.md · tasks.md · test-cases.md · security.md). Issue
OAPI-62-khai-bang-submission (thangvv111/admin-be#16). Story khắc ở
epics/E-004-onboard-to-chuc.md @ 512dea7 (PO duyệt 08/09/2026). Change CHƯA đóng (hồ sơ ở
in-review) ⇒ bản thảo; dấu duyệt ghim sha7 bản cuối.
Phạm vi: một dòng di trú vùng nghiệp vụ khai bảng đơn nộp, cộng các dòng tương ứng trong artifact ma trận quyền. Không có mã ứng dụng nào.
Yêu cầu và cách đo¶
| Yêu cầu | Ràng buộc | Đo bằng |
|---|---|---|
| Bảng đơn nộp tự chứa, không phụ thuộc tổ chức | ⛔ Không khoá ngoại tới bảng tổ chức — đơn tồn tại trước khi tổ chức tồn tại. Khoá ngoại tới tài khoản người nộp thì có và cần: đó là cột duy nhất trả lời được «đơn này của ai». ⛔ Không ON DELETE CASCADE, không SET NULL |
Ghi một đơn mang mã số thuế của tổ chức chưa tồn tại ⇒ thành công; cộng phép quét khoá ngoại cả hai chiều (đơn trỏ ra · bảng nào trỏ vào đơn) |
| Mã số thuế trên đơn ⛔ không mang ràng buộc duy nhất | Hai đơn cùng mã số thuế cùng tồn tại được — trùng là việc người thẩm định xử, không phải việc của tầng cơ sở dữ liệu (khác bảng tổ chức, nơi ràng buộc duy nhất là bắt buộc) | Chèn hai đơn cùng mã số thuế và khẳng định cả hai vào được — đo sự vắng mặt của ràng buộc bằng hành vi, không bằng cách đọc siêu dữ liệu |
| Nội dung khai lưu kèm phiên bản của chính nó | Nhãn phiên bản nội dung ⛔ không được rỗng; đọc lại một đơn cũ phải biết nó khai theo phiên bản nào | Ghi đơn với nhãn rỗng ⇒ bị từ chối ở tầng cơ sở dữ liệu |
| Loại pháp nhân nhận giá trị đóng | Chỉ ba giá trị đã khai; để trống được khi đơn còn là bản nháp. Ràng buộc viết dạng «rỗng hoặc thuộc tập» để không chặn đường lưu nháp | Giá trị ngoài tập ⇒ bị từ chối; đơn nháp chưa chọn loại ⇒ vẫn ghi được |
| Người nộp của một đơn ⛔ không đổi được sau khi ghi | Quyền sửa cấp theo từng cột, ⛔ không cấp trần trên bảng. Ba cột nằm ngoài với cả hai vai: định danh đơn · người nộp · mốc cập nhật (do trigger giữ) | Từng vai thử sửa cột định danh và cột người nộp ⇒ đều bị từ chối |
| Đơn có lối đọc lọc theo người nộp | Trả về tập đơn của người được hỏi; ⛔ không lộ đơn của người khác kể cả khi người đó có nhiều đơn | Người nộp A có hai đơn, hỏi bằng mã của B ⇒ kết quả chỉ có đơn của B |
Quyền cấp theo ma trận: đọc · thêm · sửa cho cả hai vai ứng dụng, ⛔ không xoá cho vai nào.
Một quyết định về hình dạng dữ liệu, PO chốt 08/09/2026 — và cái giá của nó¶
Bộ trường của đơn chia làm hai nhóm, không phải vì khẩu vị mà vì mức chắc chắn khác nhau:
- Lõi định danh và phân loại (mã số thuế, tên pháp lý, loại pháp nhân, cấp độ an toàn) thành cột thật — các điều khoản khai rõ từng trường, không có giả định nào treo.
- Tám khối nội dung theo Điều 8 vào một cột dữ liệu có cấu trúc, kèm nhãn phiên bản — vì
chính điều khoản quy định chúng tự khai là chưa biết ánh xạ thành bao nhiêu trường thật:
chưa có mẫu hợp đồng của ngân hàng. Và
E-004/1.1, story lẽ ra chốt hình dạng ấy, chưa làm.
⛔ Nhãn phiên bản không phải trang trí. Thiếu nó thì khi mẫu hợp đồng có thật và cấu trúc đổi, không ai đọc được đơn cũ theo luật cũ — mà đó đúng là thứ thanh tra hỏi khi soi một đơn nộp năm ngoái.
⚠️ Cái giá, ghi thẳng chứ không phát hiện sau: khi E-004/1.1 chốt hình dạng thật và cột thật ra
đời, sẽ có một quãng hai chỗ chứa cùng nội dung. Đó là giá đã biết trước của việc không đoán.
⛔ Cố ý KHÔNG có trong story này¶
⛔ Không máy trạng thái cho vòng đời đơn — tiếp nhận và thẩm định là E-004/2.5, chưa làm. Thay
vì đoán một tập trạng thái rồi buộc story đó phải theo, dùng mốc thời gian nộp: chưa có = còn
nháp, có = đã nộp. Một sự thật, không có trạng thái nào phải đồng bộ với nó.
⛔ Không bắt buộc điền trên mã số thuế và loại pháp nhân — biểu mẫu dài phải lưu nháp tự động
(E-004/2.4), và cổng bên thứ ba theo thiết kế chỉ kiểm định dạng và ràng buộc schema, không mang
luật nghiệp vụ. Luật «đủ tám khối mới cho chuyển trạng thái» sống ở hồ sơ chính thức phía
admin-be, không ở bảng đơn.
⛔ Không cấp quyền trước cho mọi bảng tương lai — làm vậy là vô hiệu hoá ma trận trước khi
bảng kịp ra đời.
⚠️ Người duyệt cần biết — ba chỗ¶
1. Ba mã giá trị của loại pháp nhân là do change này ĐẶT, chưa ai phê chuẩn. Điều khoản nguồn
khai ba giá trị bằng tiếng Việt có dấu; ba mã kỹ thuật tương ứng là cách viết của làn
admin-be. Hai repo khác (portal-be, admin-fe) sẽ phải dùng đúng ba chuỗi đó, và luật di trú
chỉ-cho-thêm biến việc đổi chúng về sau thành phá hợp đồng: ràng buộc thì sửa được, dữ liệu đã
ghi thì không. ⇒ Muốn bộ mã khác thì đổi TRƯỚC khi có dòng dữ liệu thật. Đây là chỗ đáng để
người duyệt dừng lại.
2. Lối đọc lọc theo người nộp là lớp chặn CÓ ĐIỀU KIỆN, không phải cô lập. Nó chỉ đóng ca «đọc trần cả bảng» khi bên gọi chọn dùng nó; lớp chặn thật (cô lập ở tầng dòng cộng đường truyền danh tính xuống cơ sở dữ liệu) là quyết định của một story khác. Change khai đúng mức đó thay vì khai «đã cô lập» — khai quá mức chính là thứ làm một kết luận đạt mất giá trị.
3. Hồ sơ này TỰ SOI, chưa có người soi độc lập. Chính lượt tự soi bắt được hai lỗi cùng một họ — lời khai rộng hơn phép đo — trong đó một lỗi là phiên tự làm mù cổng đo trục của mình: cổng trả về «không chạm trục nào» cho một change chạm cả trục cô lập lẫn trục phân quyền, và mã thoát đó trông y hệt kết quả lành. Cả hai đã vá bằng cách sửa phép đo, không sửa câu chữ. ⇒ Change đáng chấm lại khi có checker: nó chạm hai trục nhạy và khai một hợp đồng mà hai repo khác phải theo.
Về khoá docs: — change khai docs: khong-can, và làn docs đồng ý: story giao một dòng di trú,
không thêm màn hình hay thao tác nào người dùng cuối nhìn thấy. Đây là khai có chủ ý, khác hẳn ô
để trống ở bảy change đang nằm trong mục [D3].
E-004/1.7 Khai bảng phiên cho người dùng bên thứ ba — admin-be¶
Chờ hồ sơ. Tiêu chí dự kiến từ kế hoạch:
- ⛔ Vật mang của
E-004/2.8— cùng lý doAD-14như1.6. - ⚠️ Phạm vi chốt bằng số đo của
portal-betrước khi mở change (TK:E-004/2.8): hình dạng bảng do bên dùng nêu, bên khai không tự đoán. - Bảng phiên của người dùng TPP tách hẳn đường phiên nội bộ —
AD-20cấmportal_appchạmapp.internal_user. - Cấp
GRANTchoportal_appđúng cột thật, ⛔ không cấp ở mức bảng khi chỉ cần mức cột.
⟨CẦN NGƯỜI VIẾT — agent làn admin-be viết khi đóng change⟩
E-004/1.5 Nhập dữ liệu tổ chức đang đấu nối — admin-be¶
Chờ hồ sơ. Tiêu chí dự kiến từ kế hoạch:
- ⚠️ Story này bị chặn: chưa rõ có tổ chức nào đang đấu nối trực tiếp không (
FR-018, câuC3).
⟨CẦN NGƯỜI VIẾT — agent làn admin-be viết khi đóng change⟩
E-004/1.8 Khai bảng phiên bản văn bản pháp lý và vết đồng ý — admin-be¶
Chờ hồ sơ. Tiêu chí dự kiến từ kế hoạch:
- ⛔ Vật mang của
E-004/2.7—AD-14. - ⛔ Không ghi đè tại chỗ: bảng phải giữ được nhiều phiên bản của cùng một văn bản, và vết ai đồng ý bản nào, lúc nào.
- Đây là ràng buộc tuân thủ chứ không phải tiện lợi: khi thanh tra hỏi người dùng X đã đồng ý điều khoản nào, câu trả lời phải trỏ được đúng phiên bản tại đúng thời điểm.
⟨CẦN NGƯỜI VIẾT — agent làn admin-be viết khi đóng change⟩
E-004/1.10 API xem người dùng bên thứ ba theo tổ chức — admin-be¶
Nguồn hồ sơ. admin-be @ origin/tpp-user-read-api b63cd2e (artifact 722c437) — openspec/changes/tpp-user-read-api/
(proposal.md · design.md D1–D11 · specs/tpp-onboarding/spec.md 4 requirement, 21 scenario · tasks.md ·
test-cases.md · security.md · docs.md). Issue OAPI-240-api-xem-nguoi-dung-ben-thu-ba
(thangvv111/admin-be#77). PO đã duyệt artifact 13/09, change
đang thi công — bản thảo, soát lại theo archive khi đóng. ⚠️ epics.md không có mục AC riêng cho 1.10 — hợp đồng
hành vi sống ở specs/ của change (PO duyệt). Vật mang: di trú app/V22 (chỉ THÊM một khoá ngoại, AD-14.1).
Yêu cầu và cách đo¶
| Yêu cầu | Ràng buộc | Đo bằng |
|---|---|---|
| Ba đường chỉ đọc | GET /api/v1/tpp-users (liệt kê) · GET /api/v1/tpp-users/{id} (chi tiết = đúng hình dạng hàng liệt kê, bảng không có gì thêm để đọc) · GET /api/v1/organizations/{id}/users (cùng truy vấn, ép lọc theo tổ chức). ⛔ Không thao tác ghi nào |
Ca HTTP; OpenApiHopDongTest (hợp đồng đẩy trước mã, D10) |
| Hàng dữ liệu không lộ bí mật | id · email · xacMinhLuc · voHieuLuc · taoLuc · toChuc {id, maSoThue, tenPhapLy} \| null — ⛔ không password_hash, ⛔ không email_normalized. toChuc = null hiển thị «chưa gắn tổ chức» |
Scenario «Liệt kê mang tổ chức rỗng cho người chưa gắn»; ca khẳng định vắng hai cột bí mật |
| Lọc / tìm / phân trang ở SQL | q tìm theo email đã chuẩn hoá (so khớp chứa, ESCAPE tường minh cho %/_) · organizationId = uuid hoặc từ khoá none (chưa gắn) — giá trị khác ⇒ 400 nêu tên tham số · trangThai ∈ {hoat-dong, vo-hieu} · xacMinh ∈ {da, chua} · phân trang AD-28 (page từ 1, size ≤ 200, TrangKetQua) · sắp xếp ổn định created_at DESC, id. Hai câu SQL một lượt (đếm + trang), ⛔ không đọc cả bảng rồi cắt |
Scenario «Lọc…» · «Tìm theo thư điện tử không phân biệt hoa thường» · «Phân trang đúng hợp đồng chung»; ca T7 đo «đúng 2 câu» qua log |
| Đường dưới tổ chức: 404 khi tổ chức không có, trang rỗng khi «có mà chưa ai gắn» | Kiểm tổ chức tồn tại trước khi truy vấn; không phân biệt hai ca này là ẩn lỗi dữ liệu | Scenario «Tổ chức có nhưng chưa ai gắn» · «Tổ chức không tồn tại» |
Cổng quyền /tpp-users: user.read, hỏi mỗi lượt, super admin không mặc nhiên |
TppUserPhanQuyenFilter (mẫu ToChucPhanQuyenFilter): GET/HEAD ⇒ user.read; mọi phương thức khác ⇒ 403 mang user.read (bề mặt không có thao tác ghi để trỏ về quyền mạnh hơn; đường chưa tồn tại không được rơi về 404/405 trước cổng). Thu hồi vai ⇒ hiệu lực ở lượt kế (⛔ không cache — K1). Không phiên ⇒ 401 do PhienFilter |
Scenario «Thiếu quyền» · «Super admin không mặc nhiên có» · «Thu hồi vai có hiệu lực ở lượt kế» · «Bề mặt chỉ đọc» |
| Đường dưới tổ chức kế thừa cổng tổ chức | /organizations/{id}/users đi qua ToChucPhanQuyenFilter sẵn có ⇒ tpp_organization.read là đủ, ⛔ không cần user.read, ⛔ không viết cổng thứ hai — xem ⚠️ 2 |
Scenario «Quyền xem tổ chức là đủ, thiếu thì 403» |
Khoá ngoại tpp_user.tpp_id → app.tpp(id), chỉ-thêm, không cascade |
V22: ADD CONSTRAINT … FOREIGN KEY — ⛔ không ON DELETE CASCADE, ⛔ không SET NULL (AD-4.2); ⛔ không NOT VALID (cột toàn NULL ⇒ validate tức thời, hiệu lực ngay); ⛔ không đổi ma trận quyền (đọc đã có SELECT từ V3; portal_app không thêm gì). Cột NULL vẫn hợp lệ |
HinhBang V22; scenario «Ghi mã tổ chức không tồn tại bị từ chối» · «Xoá tổ chức đang có người bị từ chối» · «Cột rỗng vẫn hợp lệ» · «Ma trận quyền không đổi»; bite đỏ-trước có sẵn: MaTranQuyenPortalAppTest chèn tpp_id giả ở 4 chỗ phải sửa thành tổ chức thật |
| Mã lỗi tái dùng, ⛔ không mã mới | 404 ADM.REQUEST.NOT_FOUND · 400 ADM.REQUEST.VALIDATION_FAILED (details chỉ tên tham số, ⛔ không giá trị người gửi) · 403 ADM.AUTH.FORBIDDEN + details[{permission, <mã>}] · 401 ADM.AUTH.UNAUTHENTICATED |
Ca phong bì lỗi |
| Không vết cho lượt đọc | Cùng nếp liệt kê tài khoản nội bộ — xem ⚠️ 3 | T_audit N/A ở test-cases.md |
⛔ Cố ý KHÔNG có trong story này¶
⛔ Gán vai người dùng TPP (OAPI-205). ⛔ Khoá / mở / vô hiệu từ phía quản trị. ⛔ Ghi tpp_id — gắn người vào tổ
chức là OAPI-242 (story nào, có qua duyệt không — PO chốt
một lần). ⛔ Họ tên / điện thoại (cột không tồn tại — vật mang ở story khác). ⛔ Sửa E-004/2.1 (portal-be, đã archive)
theo hướng đổi cột. ⛔ Đọc tpp_session / tpp_login_attempt của portal-be. ⛔ Giao diện (1.9, OAPI-225).
⚠️ Người duyệt cần biết — bốn chỗ¶
1. Tập kết quả hôm nay là RỖNG theo tổ chức — và đó là sự thật, không phải lỗi. Chưa story nào ghi tpp_id
(portal_app bị cấm cột, admin-be không có mã ghi, 1.3 không chạm) ⇒ GET /organizations/{id}/users trả trang rỗng cho
mọi tổ chức, organizationId=none trả tất cả. Màn 1.9/1.4 phải hiển thị đúng «chưa gắn tổ chức», ⛔ không tự bịa quan
hệ. Ai gắn, ở story nào, có qua duyệt không — OAPI-242 chờ PO.
2. Người giữ tpp_organization.read xem được email thành viên mà không giữ user.read. Chấp nhận có chủ ý
(D6, ghi ở spec): «xem hồ sơ tổ chức» bao gồm xem ai thuộc tổ chức. PO siết được sau bằng đổi SHALL, không đổi hợp đồng.
3. Không vết cho lượt đọc email người dùng TPP. Email là PII; người đọc đã qua cổng quyền và phiên có vết đăng nhập,
nên change theo nếp liệt kê nội bộ. Giá: không trả lời được «ai đã xem danh sách người dùng TPP lúc nào». Nếu PO muốn,
một vết nguoi_dung_tpp.xem mức thông tin là thêm sau, không đổi hợp đồng — câu ngỏ ở security.md.
4. Số di trú V22 chốt qua tin liên phiên cùng ngày (V20 = don_vi, V21 = vong-doi-ho-so, V22 = change này) sau
án lệ V19 trùng số; tasks đo lại ls migration/app | sort -V ngay trước khi tạo file. FK làm đỏ mọi test/seed chèn
tpp_id giả — bằng chứng FK load-bearing.
E-004/1.11 Khai bảng đính kèm của đơn nộp và kho nội dung tệp — admin-be¶
Chờ hồ sơ. Tiêu chí dự kiến từ kế hoạch:
- ⛔ Vật mang của
E-004/2.11—AD-14: bảngportal-beghi vẫn doadmin-bekhai. ⛔ Hình dạng do2.11chốt trongdesign.mdtrước (TK:E-004/2.11), bên khai không tự đoán — ba nhịp như cặp1.7/2.8. - Hai bảng:
app.submission_attachment(metadata, nốisubmission, khuôn V12tpp_attachment:ten_file·loai_mime·kich_thuoc_byte·bamkhuônsha256:<hex>·duong_dan= khoá khodb:<uuid>) vàapp.tep_noi_dung(bytea,AD-30mục 1). - ⛔
tep_noi_dungxoá được — kháctpp_attachment: byte là dữ liệu cá nhân (T_pii, Nghị định 13), metadata +bamlà bằng chứng và giữ.admin_appDELETE có nhật ký; ⛔ không vai nào UPDATE byte tại chỗ. - GRANT
portal_appINSERT/SELECT theo hàng trên cả hai bảng (nếp V5submission), ⛔ không UPDATE/DELETE, ⛔ không SELECT trần; ghi vàoma-tran-quyen.tsv. Trigger giữ mốc phảiREVOKE EXECUTE … FROM PUBLIC(cổng khởi độngportal-bequét hàm). - Trả nợ
OAPI-149(admin-be#53 «kho lưu tệp chưa có chủ»): chủ vòng đời kho làadmin-be(AD-30mục 6).
⟨CẦN NGƯỜI VIẾT — agent làn admin-be viết khi đóng change⟩
E-004/1.12 Hàm lọc đọc byte kho tệp cho portal_app — admin-be¶
Chờ hồ sơ. Tiêu chí dự kiến từ kế hoạch:
- Vấn đề (
OAPI-230):V18cấpportal_appSELECTmức bảng trên khoapp.tep_noi_dung;E-004/1.2đưa byte bằng chứng thẩm định nội bộ (T_pii) vào cùng kho ⇒ vai CSDL của cổng bên thứ ba đọc/dump được byte nếu có khoá. PO chọn cách 1: thu hồiSELECTtrần, đọc byte qua hàm lọc. - Hàm trả
noi_dungcủa một khoá kho, chỉ khi khoá đó làduong_dancủa một hàng trongsubmission_attachment_of_user(p_nguoi_nop_id)(nếpV18,T_isotheo tập người dùng); không khớp ⇒ rỗng, ⛔ không lỗi tiết lộ. - ⛔
SECURITY INVOKERkhông đủ: sau1.13portal_appkhông cònSELECTtrên bảng ⇒ hàm phảiSECURITY DEFINERhẹp (chủ hàm = vai chủ bảng,SET search_path = pg_catalog, app, tham số kiểu chặt),REVOKE EXECUTE … FROM PUBLIC,GRANT EXECUTEchoportal_app(vàadmin_appnếu cần). Đây là đổi nếpV5/V18cho riêng hàm đọc byte — hàm siêu dữ liệu giữ INVOKER; commentV18đã ghi «PO quyết» — nay đã quyết. - ⛔ Chưa thu hồi gì trong story này (chiều THÊM —
admin-bedi trú trước đúngAD-14). Thu hồi ở1.13. - Xong = hàm có trên trunk + ma trận quyền/lưới
admin-bekhaiEXECUTEchoportal_app+has_table_privilege('portal_app','app.tep_noi_dung','SELECT')vẫntrue(cố ý).
⟨CẦN NGƯỜI VIẾT — agent làn admin-be viết khi đóng change⟩
E-004/1.13 Thu hồi SELECT trần của portal_app trên app.tep_noi_dung — admin-be¶
Chờ hồ sơ. Tiêu chí dự kiến từ kế hoạch:
V2x:REVOKE SELECT ON app.tep_noi_dung FROM portal_app; ma trận quyền + lướiadmin-becập nhật (portal_app:INSERTtheo cột, ⛔SELECT/UPDATE/DELETE).- Đo:
SELECT has_table_privilege('portal_app','app.tep_noi_dung','SELECT')⇒false;has_function_privilege('portal_app', '<hàm 1.12>', 'EXECUTE')⇒true. - ⛔
TR:E-004/2.12— merge tự do sau2.12, nhưng migration này chỉ được chạy trên môi trường đã chạy bảnportal-becủa2.12. Chiều THU HỒI đảoAD-14: bên MẤT quyền triển khai trước bên THU HỒI (án lệV7/OAPI-83-dat-updated-at-không-revoke-execute, 08/09).uatvà người vận hành đọc ô này. OAPI-230đóng khi story này archive và phép đo trên trảfalse.
⟨CẦN NGƯỜI VIẾT — agent làn admin-be viết khi đóng change⟩
E-004/1.4 Màn hồ sơ tổ chức — admin-fe¶
Chờ hồ sơ. Tiêu chí dự kiến từ kế hoạch:
- ⛔ Màn riêng, không modal (
UX-DR13): tám khối Điều 8 cộng tài liệu không vừa 1080px, cần URL để gửi link cho người duyệt, và chồng hộp xác nhận lên modal là chỗ dễ bấm nhầm nhất. - Ô nhập lý do trả về phải nhắc ngay tại chỗ rằng nội dung hiển thị cho bên thứ ba (
UX-DR7,KF-1). - Ghi chú nội bộ là trường riêng, không hiển thị ra ngoài.
- Danh sách tổ chức lọc theo trạng thái, loại pháp nhân (
FR-017).
⟨CẦN NGƯỜI VIẾT — agent làn admin-fe viết khi đóng change⟩
E-004/1.9 Màn xem người dùng bên thứ ba (phía quản trị) — admin-fe¶
⟨CẦN NGƯỜI VIẾT — agent làn admin-fe viết khi đóng change⟩