Bỏ qua

Cổng bên thứ ba

Onboard tổ chức · nhóm 2 · 12 story · 🟡 chưa viết xong · còn 19 mốc

Chưa viết xong: còn 19 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/2.1 Tài khoản người dùng của bên thứ ba portal-be 4 cap/tpp-self-registration · tách hẳn khỏi người dùng nội bộ
E-004/2.8 Xác thực và phiên cho người dùng bên thứ ba portal-be 7 cap/tpp-self-registration · AD-19 phiên có trạng thái · khoá tạm 3 lần / 15 phút (cấu hình, ⛔ không hằng số) · quên mật khẩu câu trung lập · đổi mật khẩu trong phiên · ⛔ tách hẳn đường xác thực nội bộ (AD-20) · chặn tới khi 1.7 xong
E-004/2.2 Đơn nộp hồ sơ portal-be 8 cap/tpp-self-registration · SUBMISSION tự chứa, không khoá ngoại tới TPP · ✅ 1.6 đã xong (admin-be 2d0909b, V5__khai_bang_submission.sql) · nay chặn bởi 2.8: nộp hồ sơ là hành động của người đã đăng nhập
E-004/2.3 Chống lạm dụng đường đăng ký công khai portal-be 9 cap/abuse-protection · căn cứ FR-059, KHÔNG phải Điều 11.9
E-004/2.4 Màn đăng ký tài khoản portal-fe 11 cap/tpp-portal-ui · PO tách 11/09 tối (P4): CHỈ còn đăng ký tài khoản (3 bước theo gói «Đăng ký tài khoản»), nộp hồ sơ sang 2.9 · ô đồng ý hai văn bản dẫn sang 2.6 · ⛔ §F UU-TIEN-GD1: đăng ký tạm tự động kích hoạt, bước xác minh email để //TODO đúng chỗ + màn tự khai, nợ có điều kiện trả (cổng gửi thư) — đường BE là change mới có tênportal-be, ⛔ không vá 2.1 đã archive · ✅ hết chặn: 2.15.6 đều archive
E-004/2.5 Tiếp nhận đơn nộp admin-be 12 cap/tpp-onboarding · ⛔ đơn KHÔNG đi thẳng tới checker
E-004/2.6 Khung hai văn bản pháp lý portal-fe 14 cap/tpp-portal-ui · điều khoản 9 mục + chính sách dữ liệu 8 mục · bảng bốn cột chuyển thẻ dưới 768px
E-004/2.7 Nội dung pháp lý có phiên bản và vết đồng ý portal-be 16 cap/tpp-self-registration · ⛔ không ghi đè tại chỗ · ghi ai đồng ý PHIÊN BẢN NÀO, lúc nào
E-004/2.9 Màn nộp hồ sơ portal-fe 17 cap/tpp-portal-ui · tách từ 2.4 11/09 tối (P4) · 8 khối theo Điều 8 · lưu nháp tự động · là hành động của người đã đăng nhập (2.8) · tuyến «Hồ sơ đăng ký» hiện là màn tự khai chưa dựng
E-004/2.10 Giới hạn tốc độ ngoài đường đăng nhập và độ dài tối thiểu khi đổi mật khẩu portal-be 20 cap/abuse-protection · mở 11/09 tối (PO chốt — luật nợ sau archive, E-001 §Bổ sung 11/09) · nhà của issue có sẵn OAPI-131 (portal-be#28), nợ phát hiện sau khi 2.8 archive · đổi trailer **Story:** trên body issue #28 từ E-004/2.8 thành E-004/2.10 (mối nối thật; link.yaml cũ không chứa #28 — số đo làn portal-be 11/09)
E-004/2.11 Điểm cuối nhận tệp đính kèm cho đơn nộp và trả tệp cho chính người nộp portal-be 22 cap/tpp-self-registration · mở 12/09 (PO chốt 8 mục — AD-30) · multipart, PDF/JPG theo magic bytes, ≤ 10 MB là cấu hình (gói design màn nộp hồ sơ khối 2: 3 tài liệu, 2 bắt buộc) · bam = sha256: tính khi nhận · trả tệp Content-Disposition: attachment + nosniff, ⛔ không inline (AD-30 mục 3) · T_iso: chỉ tệp của đơn thuộc tổ chức mình, 404 vô hình · ⛔ không quét mã độc trong story này — nợ có cổng của AD-30 · nhịp 1: chốt hình dạng bảng gửi admin-be (ghi vào issue của 1.11) trước khi viết mã nối · trả nợ OAPI-151 (portal-be#31); mở đường cho OAPI-176 (portal-fe#26)
E-004/2.12 Đọc byte qua hàm lọc, bỏ SELECT trần khỏi hợp đồng portal-be 24 cap/tpp-self-registration · mở 13/09 (PO chốt OAPI-230 cách 1) · nhịp ②/③ · AttachmentJdbcRepository.FIND_CONTENT (SELECT noi_dung FROM app.tep_noi_dung) → gọi hàm của 1.12 · DatabaseContract: bỏ SELECT khỏi tập bắt buộc của app.tep_noi_dung (giữ INSERT theo cột), khai EXECUTE hàm — cổng hai chiều, THỪA cũng đỏ · T_iso giữ nguyên: 404 vô hình

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/2.1 Tài khoản người dùng của bên thứ ba — portal-be

Góp gì vào mục tiêu epic. Đây là cửa vào đầu tiên của cổng bên thứ ba: chưa có tài khoản thì không ai nộp được gì (FR-054, FR-055), nên mọi story còn lại của E-004 — và cả E-005, E-007 — đứng sau nó.

  • Danh tính là mã nội bộ BẤT BIẾN, không phải email (AD-11, K4). Cùng một lập luận đã áp cho người dùng nội bộ ở FR-049: email đổi được — đổi tên, đổi họ, hoặc đổi tên miền sau một đợt sáp nhập — và một khoá đổi được thì không phải khoá bền. Nhật ký trỏ thẳng vào email nghĩa là một năm sau, khi TT-13 đòi tra lại, bản ghi cũ mất đường nối về chủ thể thật.
  • Tách hẳn khỏi người dùng nội bộ (AD-20): không dùng chung bảng, không dùng chung đường xác thực. Ý nghĩa là rủi ro chứ không phải gọn gàng: một lỗ hổng ở bề mặt công khai không được phép trở thành một lỗ hổng ở kho tài khoản nội bộ của ngân hàng. Dùng chung bảng là nối hai hồ sơ rủi ro rất khác nhau vào một điểm hỏng.
  • Xác minh email trước khi được nộp. Đây là chốt chặn rẻ nhất giữa một địa chỉ có thật và một hồ sơ sẽ đi vào luồng phê duyệt của ngân hàng — nếu đơn vào hàng đợi trước khi biết người nộp có thật, thì maker/checker đang duyệt trên một dữ kiện chưa kiểm.
  • Mật khẩu băm bằng Argon2id với đúng bộ tham số AD-12 đã chốt cho toàn nền tảng. AD-12 viết cho client_secret, nhưng lý do áp nguyên: hồ sơ cấp độ 3 cần một con số, không phải một lựa chọn — mỗi nơi tự chọn tham số là mỗi nơi một mức bảo vệ mà không ai đối chiếu được.

Nó còn gánh nền hợp đồng API cho cả repo. AD-28 vào spine ngày 07/09 và tự khai bốn ca lưới «bắt buộc ở story khung», nhưng story khung của portal-be viết trước AD-28, đã merge, và không có bề mặt HTTP nghiệp vụ nào để áp. Change này là bề mặt HTTP đầu tiên của repo ⇒ nó dựng nền. Không làm ở đây thì mỗi story sau tự nghĩ một kiểu — đúng loại phân kỳ AD-28 sinh ra để chặn.

⚠️ Một phần của story bị CHẶN bởi repo khác. AD-14 giao toàn bộ dòng di trú cho admin-be, kể cả bảng mà portal-be ghi. Đo 07/09: origin/main của admin-be chưa có dòng di trú nào. Yêu cầu đã mở đúng lối AD-14 chỉ — OAPI-14 (thangvv111/admin-be#5) — và portal-be không tự khai bảng, kể cả trong test. Xem phần PRD bên dưới để biết cái đó để lại khoản nợ gì.

E-004/2.8 Xác thực và phiên cho người dùng bên thứ ba — portal-be

Góp gì vào mục tiêu epic. Cổng bên thứ ba hiện không có một đường đăng nhập nào. Đo trên nhánh chính của portal-be: không lớp bảo mật, không chủ thể, không bộ giải mã phiên — 0 dòng; bề mặt HTTP có đúng ba thao tác đăng ký và xác minh email, ⛔ không thao tác nào là đăng nhập.

  • Hệ quả không nằm ở một màn — nó chặn cả một nhánh của epic. Nộp hồ sơ là hành động của người đã đăng nhập, nên E-004/2.2 đứng chờ; màn đăng nhập của cổng chờ; và ngay cả story khai bảng phiênadmin-be cũng đang đứng chờ hình dạng do story này chốt. Ba làn dừng vì cùng một chỗ trống.
  • Đây là nửa back-end mà bảng story từng KHÔNG có ai phủ. Trước 08/09/2026 không story nào nhận việc này — nó lộ ra khi xếp thứ tự ship theo màn, đúng loại lỗ mà một bảng story chỉ nhìn theo tính năng sẽ không thấy.
  • Cùng ba ràng buộc phiên với bề mặt quản trị, nhưng ⛔ KHÔNG dùng chung gì. Phiên có trạng thái, kiểm thu hồi mỗi yêu cầu, khoá tài khoản là phiên chấm dứt ngay — cùng luật. Nhưng đây là tài khoản của người ngoài ngân hàng, tách hẳn khỏi người dùng nội bộ, và hai bề mặt ⛔ không dùng chung bảng tài khoản.

Vị trí trong hàng đợi.nhịp 1 của ba nhịp đã ghi ở epic: story này chốt hình dạng bảng phiên rồi giao thẳng sang admin-be khai (AD-14 giao trọn dòng di trú cho bên đó); admin-be merge trước, rồi repo này mới nối. ⇒ Nó gỡ chặn E-004/2.2 (đơn nộp hồ sơ), màn đăng nhập của cổng, và trả xong phần chờ của story khai bảng phiên.

E-004/2.2 Đơn nộp hồ sơ — portal-be

Góp gì vào mục tiêu epic. Cổng bên thứ ba hiện không có đường nào để nộp hồ sơ. Đo trên nhánh chính của portal-be (c324f48): gói submission/ chỉ có một tệp khai gói; hợp đồng HTTP không thao tác nào chạm /submissions; bảng app.submission đã nằm trong hợp đồng CSDL từ OAPI-51 nhưng không adapter nào đọc hay ghi nó — bảng có, đường vào không.

  • Đây là nút chuyển từ «có tài khoản» sang «có hồ sơ». Hai story đứng chờ ngay sau nó: màn nộp hồ sơ E-004/2.9 (portal-fe) cần một bề mặt để lưu nháp tự động và nộp; tiếp nhận đơn E-004/2.5 (admin-be) — không có đơn thì không có gì để tiếp nhận. Cả nhánh «đơn → thẩm định → tổ chức» của epic bắt đầu từ đây.
  • Nó từng bị trả về hoạch định — và lý do đó nay đã hết. 08/09 PO đóng băng story này vì portal-be không có danh tính đã xác thực để điền người nộp: cột nguoi_nop_id của bảng là vật mang duy nhất của trục cô lập theo người nộp, mà lấy nó từ thân yêu cầu là tự mở lỗ «nộp đơn thay người khác». Lỗ nằm ở bảng story (không epic nào giao xác thực bên thứ ba cho back-end), E-004/2.8 được mở để vá, và đã archive ⇒ 11/09 story này hết chặn thật.
  • Giá trị nằm ở chỗ nó KHÔNG làm. Ranh giới AD-3 ghim từ bảng story: repo này chỉ kiểm định dạng và ràng buộc schema, ⛔ không mang luật nghiệp vụ; đơn tự chứa, ⛔ không khoá ngoại tới tổ chức; ⛔ không UNIQUE mã số thuế — hai tổ chức nộp trùng là việc người thẩm định xử; đơn ⛔ không đi thẳng tới người phê duyệt. Tức là nó dựng hạ tầng nhận đơn mà không tự đẻ ra một bộ luật thẩm định thứ hai bên ngoài admin-be.

Vị trí trong hàng đợi. Thứ tự 8 trong bảng story; là màn B1 của lộ trình ưu tiên giai đoạn 1 (§E): portal-be đứng ở nhánh chính sạch ⇒ đây là việc kế tiếp (PO chốt 11/09 tối). Mở đường cho E-004/2.9 đọc hợp đồng HTTP từ nhánh này và cho E-004/2.5 có đầu vào.

E-004/2.3 Chống lạm dụng đường đăng ký công khai — portal-be

⟨CẦN NGƯỜI VIẾT — agent làn portal-be viết khi đóng change⟩

E-004/2.4 Màn đăng ký tài khoản — portal-fe

⟨CẦN NGƯỜI VIẾT — agent làn portal-fe viết khi đóng change⟩

E-004/2.5 Tiếp nhận đơn nộp — admin-be

Góp gì vào mục tiêu epic. Đây là mắt xích còn thiếu giữa hai thứ đã có: bên thứ ba nộp được đơn (E-004/2.2 đơn nộp hồ sơ — OAPI-49, archive 11/09) và hồ sơ tổ chức có vòng đời gửi duyệt → checker (E-004/1.3 vòng đời hồ sơ — OAPI-224, archive 13/09). Đo trên admin-be @ origin/main bf6e00e (14/09): vai quản trị đọc được bảng đơn, nhưng không một đường HTTP, dịch vụ hay bản ghi nào nối một đơn đã nộp với một tổ chức. Đơn nộp xong thì nằm đó — không ai ở ngân hàng có chỗ để nhìn, và không có cách nào biến nó thành hồ sơ chính thức.

  • Vì sao phải có một người ở giữa, không cho đơn chạy thẳng tới checker. FR-057 ghim: hồ sơ TPP tự nộp là đầu vào, không phải nửa đầu của cặp maker/checker; khoản 10 Điều 11 TT64 đặt nghĩa vụ thẩm định lên ngân hàng. Nếu để bên thứ ba đóng vai maker thì «hai người» thành bên thứ ba cộng checker — ngân hàng chưa thẩm định gì cả. Story này dựng đúng vai người tiếp nhận (maker): đọc đơn, quyết một trong hai kết cục — tạo hồ sơ hoặc trả về kèm lý do.
  • Trả về kèm lý do là nửa đầu của FR-058. Bên thứ ba phải đọc được vì sao đơn bị trả, còn ghi chú nội bộ của ngân hàng thì không. Story này ghi cả hai vào một bản ghi, và mở một lối đọc có lọc cho cổng bên thứ ba chỉ trả bốn cột (mã đơn · kết cục · lý do · mốc). Nửa sau — bên thứ ba đọc trạng thái hồ sơ (chờ duyệt / đã duyệt) và bổ sung vào tổ chức sẵn có — là lỗ ở bảng story, PO mở issue quy ước OAPI-263-lỗ-story-FR-058 (openapi-platform#41), ⛔ không nhét vào đây.
  • Đây là đầu chuỗi dài nhất của giai đoạn 1. 2.5E-005/1.5 khai bảng đơn khai ứng dụng (chưa có issue)E-005/1.2 đơn khai ứng dụng — OAPI-261E-005/1.4 màn Ứng dụng — OAPI-262. Hai làn portal đang rảnh chờ nó (UU-TIEN-GD1 §L).

Ba câu PO đã chốt (14/09/2026, tại làn admin-be slot b). ① kết cục trả về làm ngay ở change này, không tách; ② quyền tiếp nhận dùng lại tpp_organization.read / tpp_organization.create, ⛔ không thêm đối tượng quyền — giá là màn phân quyền không tách được «xem đơn» khỏi «xem tổ chức», chấp nhận ở giai đoạn 1; ③ lỗ FR-058 ⇒ issue quy ước OAPI-263.

Vị trí trong hàng đợi. Thứ tự 12 trong bảng story. Hai điều kiện chặn đã thoả trên trunk (2.21.3 đều archive); PO chốt 14/09 «đẩy e004/2.5 cho admin be slot b». Change tiep-nhan-don-nopOAPI-260 đã đóng 14/09/2026 (archive 2026-09-14-tiep-nhan-don-nop, merge main @ 79e89d5).

E-004/2.6 Khung hai văn bản pháp lý — portal-fe

⟨CẦN NGƯỜI VIẾT — agent làn portal-fe viết khi đóng change⟩

E-004/2.7 Nội dung pháp lý có phiên bản và vết đồng ý — portal-be

⟨CẦN NGƯỜI VIẾT — agent làn portal-be viết khi đóng change⟩

E-004/2.9 Màn nộp hồ sơ — portal-fe

⟨CẦN NGƯỜI VIẾT — agent làn portal-fe viết khi đóng change⟩

E-004/2.10 Giới hạn tốc độ ngoài đường đăng nhập và độ dài tối thiểu khi đổi mật khẩu — portal-be

⟨CẦN NGƯỜI VIẾT — agent làn portal-be viết khi đóng change⟩

E-004/2.11 Điểm cuối nhận tệp đính kèm cho đơn nộp và trả tệp cho chính người nộp — portal-be

Góp gì vào mục tiêu epic. Hồ sơ Điều 8 TT64 đòi tài liệu chứng minh (khoản 8.6 — cấp độ an toàn hệ thống; FR-013/FR-014), và gói design màn nộp hồ sơ vẽ khối 2 với ba ô tài liệu. Tới trước story này, cổng bên thứ ba không có đường nào để gửi tệp: đo trên trunk portal-be d7cdd61 — 9 đường HTTP, không đường nào nhận multipart; bảng app.tpp_attachment (V12) có nhưng REVOKE ALL … FROM portal_app. Đơn nộp được (2.2) mà tài liệu đi kèm thì không — đó là lỗ tuân thủ có tên, đã ghi thành nợ OAPI-151 khi đóng 2.2.

  • Nó là điều kiện của hai story đứng chờ. Màn nộp hồ sơ E-004/2.9 (portal-fe) đã archive với nợ OAPI-176 «trả khi portal-be có điểm cuối nhận tệp»; tiếp nhận đơn E-004/2.5 (admin-be, (chưa có issue)) cần đọc được tệp hiện hành của đơn.
  • Vì sao bây giờ mới làm được. 2.2 để lại nợ vì «bảng không có cột, spine không có AD về lưu tệp». AD-30 mục 1, 2 (PO chốt 12/09/2026) và vật mang E-004/1.11 (admin-be, archive 13/09 — hai bảng app.submission_attachment + app.tep_noi_dung) xoá đúng hai điều kiện chặn đó. Story này đi ba nhịp: chốt hình dạng bảng gửi admin-beadmin-be khai + GRANT → viết mã nối; ⛔ không khai bảng ở đây (AD-14).
  • Giới hạn cố ý của giai đoạn này: ⛔ chưa quét mã độc — bù bằng trả tệp dạng attachment + nosniff (AD-30 mục 3); ⛔ không xoá từ phía bên thứ ba — xoá là thao tác quản trị có nhật ký (AD-30 mục 1); ⛔ không URL tải về công khai (AD-30 mục 5).

E-004/2.12 Đọc byte qua hàm lọc, bỏ SELECT trần khỏi hợp đồng — portal-be

⟨CẦN NGƯỜI VIẾT — agent làn portal-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. Nhóm này có hai bề mặt — bên thứ ba và ngân hàng — nên hành trình đi xuyên cả hai.

E-004/2.4 Màn đăng ký tài khoản — portal-fe

⟨CẦN NGƯỜI VIẾT — agent làn portal-fe viết khi đóng change⟩

E-004/2.6 Khung hai văn bản pháp lý — portal-fe

⟨CẦN NGƯỜI VIẾT — agent làn portal-fe viết khi đóng change⟩

E-004/2.9 Màn nộp hồ sơ — portal-fe

⟨CẦN NGƯỜI VIẾT — agent làn portal-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/2.5 Tiếp nhận đơn nộp — admin-be

Nguồn hồ sơ. admin-be @ origin/main 79e89d5openspec/changes/archive/2026-09-14-tiep-nhan-don-nop/ (proposal.md · design.md D1–D5 · specs/tpp-onboarding/spec.md 7 requirement / 32 scenario · tasks.md · test-cases.md 76 ca · security.md · docs.md). Issue OAPI-260-tiếp-nhận-đơn-nộp (thangvv111/admin-be#80). Change ĐÃ đóng (verdict PASS @ 4c7d17d tự chấm; lens review 14/09 bốn lớp: 32 finding — 24 patch · 4 defer thành issue OAPI-269 nợ loại đính kèm hồ sơ · OAPI-270 quy ước trần + bất biến đơn đã nộp · OAPI-271 nợ khuôn thời gian AD-28 · 4 reject có lý do) ⇒ bản cuối, soát lại từ bản thảo @ f495f2e — bốn chỗ đổi chữ sau lens review đã cập nhật tại chỗ bên dưới.

Yêu cầu và cách đo

Yêu cầu Ràng buộc Đo bằng
Mỗi đơn đúng một kết cục, ghi thành bản ghi chỉ-thêm Bản ghi tiếp nhận mang: đơn · người tiếp nhận · kết cục ∈ {tạo hồ sơ, trả về} · tổ chức (bắt buộc khi tạo hồ sơ) · lý do (bắt buộc, không rỗng khi trả về) · ghi chú nội bộ (tuỳ chọn) · mốc do CSDL đặt. UNIQUE theo đơn; vế bắt buộc kiểm đối xứng ở CSDL (CHECK); ⛔ không vai ứng dụng nào sửa/xoá. Chỉ đơn đã nộp mới tiếp nhận được — trigger ở CSDL, ⛔ không chỉ ở mã Hai người cùng lúc tiếp nhận một đơn ⇒ đúng một 201, một 409, kho có một bản ghi; ghi thẳng bản ghi cho đơn nháp / kết cục lạ / tạo hồ sơ mà có lý do ⇒ CSDL từ chối
Trạng thái đơn suy ra, ⛔ không cột nháp (chưa mốc nộp) · đã nộp (có mốc, chưa bản ghi) · đã tạo hồ sơ · trả về (theo kết cục). Bảng đơn app.submission không thêm cột — V5 ghim «một sự thật», và portal_app đang có UPDATE theo cột trên bảng ấy Đọc tập cột bảng đơn ⇒ không có cột trạng thái; đọc đơn đã nộp chưa tiếp nhận ⇒ đã nộp
Tạo hồ sơ = tổ chức + phiên bản 1 + đính kèm trong một giao dịch, đi qua đúng ba phương thức sẵn có của 1.1 Tổ chức mới từ mã số thuế · tên pháp lý · loại pháp nhân của đơn (ánh xạ từ vựng: KHACto_chuc_khac, hai giá trị kia hạ chữ thường); tên ngắn, số giấy phép do người tiếp nhận nhập thêm, trống được. Phiên bản hồ sơ 1: cấp độ an toàn của đơn, nội dung chép nguyên vẹn jsonb, người ghi = người tiếp nhận. Mỗi đính kèm hiện hành của đơn (bản mới nhất của từng loại — tệp đã bị thay ⛔ không đi theo; lens review F1) ⇒ một đính kèm hồ sơ cùng khoá kho, cùng dấu vân — ⛔ không sao chép byte (kho byte chung AD-30 mục 1). Tổ chức ở nháp, không đề xuất nào chờ — ⛔ không tự gửi duyệt (FR-057: thẩm định là của người; người tiếp nhận sửa nếu cần rồi gửi duyệt bằng đường 1.3). Bước nào đổ ⇒ cuộn cả giao dịch Tiếp nhận đơn có hai đính kèm ⇒ tổ chức đúng bốn trường, phiên bản 1 nội dung bằng đúng khối nội dung đơn, hai đính kèm hồ sơ cùng khoá kho/dấu vân/tên/kiểu/kích thước, kho byte không thêm hàng, chi tiết tổ chức = nháp; ép bước ghi đính kèm đổ ⇒ không tổ chức, không phiên bản, đơn vẫn đã nộp
Đơn thiếu trường hoặc trùng mã số thuế thì không tạo Thiếu một trong bốn trường định kiểu (mã số thuế · tên pháp lý · loại pháp nhân · cấp độ an toàn) hoặc khối nội dung rỗng ⇒ 422 ADM.SUBMISSION.INCOMPLETE, details[] {field, issue} — tên trường theo quy ước admin-be (maSoThue · tenPhapLy · loaiPhapNhan · capDoAnToan · noiDung · dinhKem), issueREQUIRED (thiếu) · INVALID (loại pháp nhân ngoài từ vựng, tên tệp đính kèm hỏng) · TOO_LONG (tên pháp lý quá trần) · TOO_MANY (đính kèm quá số). Lỗi dữ liệu đơn luôn 422 nêu trường, ⛔ không rơi thành 400 trơ. ⛔ Không kiểm tám khối Điều 8 ở đây — cổng ấy là của lượt gửi duyệt 1.3. Mã số thuế đã thuộc tổ chức khác (khác hoa/thường, khoảng trắng vẫn là trùng) ⇒ 409 mã riêng ADM.SUBMISSION.TAX_CODE_EXISTS, không tạo gì, không ghi bản ghi — người tiếp nhận trả về kèm lý do hoặc xử tay ở màn tổ chức Đơn trống cấp độ an toàn ⇒ 422 có capDoAnToan; tên pháp lý 255 ký tự ⇒ 422 tenPhapLy TOO_LONG; đơn trùng MST ⇒ 409 mã riêng, đọc lại đơn vẫn đã nộp; hai đơn khác nhau cùng MST tiếp nhận đồng thời ⇒ lượt sau 409 TAX_CODE_EXISTS (UNIQUE của bảng tổ chức chặn)
Trả về kèm lý do cho bên thứ ba; ghi chú nội bộ ⛔ không lọt Lý do bắt buộc, không toàn khoảng trắng, trần 4096; ghi chú nội bộ tuỳ chọn cùng trần; sai ⇒ 400 details[] {field: reason, issue: REQUIRED\|TOO_LONG}. Trả về ⛔ không tạo tổ chức, ⛔ không sửa đơn. Bên thứ ba nộp lại bằng đơn mới (portal-be không đổi). Lối đọc cho portal_app: hàm app.submission_receipt_of_user(uuid)SECURITY DEFINER hẹp, search_path khoá — trả đúng bốn cột (mã đơn · kết cục · lý do · mốc), ⛔ không ghi chú nội bộ, ⛔ không mã tổ chức, ⛔ không người tiếp nhận; tham số trống ⇒ rỗng. portal_app không quyền nào trên bảng, chỉ EXECUTE hàm; PUBLIC không thực thi được Trả về hợp lệ ⇒ 201, đọc lại đơn trả về kèm lý do, mốc nộp và nội dung không đổi; hỏi pg_proc ⇒ hàm trả đúng 4 cột; hỏi quyền portal_app trên bảng ⇒ không quyền nào, has_function_privilege ⇒ đúng; gọi hàm với người thứ nhất ⇒ không thấy đơn người thứ hai
Bề mặt HTTP /api/v1/submissions (tag don-nop), ba thao tác GET /submissions?page&size&trangThai&q — chỉ đơn đã nộp (⛔ không nháp), mới nộp trước, phân trang AD-28; trangThai ∈ {da-nop, da-tao-ho-so, tra-ve} (lạ và nhap ⇒ 400); q ≤ 254, tìm nghĩa đen trên mã số thuế/tên pháp lý (%/_ không phải mẫu); lọc và phân trang trong SQL. GET /submissions/{id} — bốn trường + nhãn phiên bản + nội dung + mốc nộp + người nộp (mã, email) + đính kèm (siêu dữ liệu, ⛔ không byte, ⛔ không khoá kho) + trạng thái + bản ghi tiếp nhận nếu có. POST /submissions/{id}/receipts {ketQua: tao-ho-so, tenNgan?, soGiayPhepDkkd?, ghiChuNoiBo?} hoặc {ketQua: tra-ve, lyDo, ghiChuNoiBo?} ⇒ 201 {id, ketQua, tppId?}. Mã không tồn tại / là nháp ⇒ 404 cùng thân (trừ traceId, timestamp); mã sai khuôn UUID ⇒ 400 ADM.REQUEST.MALFORMED (quy ước chung của repo, không tiết lộ gì) Kho có một nháp + hai đã nộp ⇒ danh sách đúng hai; page=0 ⇒ 400; q=% ⇒ chỉ đơn có ký tự %; đọc/tiếp nhận đơn nháp ⇒ 404 giống đơn không tồn tại
Quyền hỏi ở mỗi lượt, dùng lại quyền tổ chức (PO ②) Đọc ⇒ tpp_organization.read; tiếp nhận ⇒ tpp_organization.create; mọi phương thức ghi khác (kể cả PUT, DELETE, POST lạ, / cuối) ⇒ create — fail-closed. Cổng riêng DonNopPhanQuyenFilter cùng khuôn/cùng bậc ToChucPhanQuyenFilter. Không phiên ⇒ 401; thiếu quyền ⇒ 403 kèm mã quyền; super admin ⛔ không mặc nhiên; thu hồi vai ⇒ 403 ở lượt kế, không cần đăng nhập lại Phiên chỉ có read gọi POST …/receipts ⇒ 403 nêu tpp_organization.create, không bản ghi; DELETE bất kỳ ⇒ 403 create; super admin không gán quyền ⇒ 403
Vết nhật ký, không mang dữ liệu cá nhân Mỗi lượt thành công ⇒ một vết don_nop.tiep_nhan (chủ thể người tiếp nhận; tham chiếu kết cục · mã đơn · mã tổ chức khi có · mã bản ghi); tạo hồ sơ để thêm ba vết to_chuc.tao · ho_so.ghi_phien_ban · dinh_kem.ghi của 1.1 cùng chủ thể. ⛔ Lý do, ghi chú nội bộ, tên tệp, email ⛔ không vào vết, không vào log, không vào thông điệp lỗi. Vết ghi cùng giao dịch ⇒ lượt đổ không để vết Tiếp nhận đơn một đính kèm ⇒ đúng 4 vết cùng chủ thể; trả về ⇒ tham chiếu vết không chứa lý do; 409 trùng MST ⇒ không vết
Ma trận quyền khớp quyền thật ma-tran-quyen.tsv thêm hai hàng (admin_app: SELECT + INSERT theo cột, ⛔ không id/created_at; portal_app: không quyền bảng, ô đọc_qua = hàm); MaTranQuyenVerifier đối chiếu ở khởi động. Di trú V23 chỉ thêm (bảng · hai trigger · hàm · GRANT/REVOKE), không backfill — đơn nộp trước V23 đều đã nộp, đúng sự thật admin_app ghi bản ghi kèm id tự đặt ⇒ từ chối; cổng khởi động xanh với hai hàng mới

⛔ Cố ý KHÔNG có trong story này

Không gắn đơn vào tổ chức sẵn có (vòng «bổ sung khi Chờ bổ sung», nửa sau FR-058) — story riêng theo OAPI-263. ⛔ Không lối đọc trạng thái hồ sơ (chờ duyệt / đã duyệt) cho bên thứ ba — chạm bảng đề xuất, story riêng. ⛔ Không bề mặt portal-be đọc trạng thái đơn (hàm đã có, portal-be gọi khi có story), ⛔ không màn admin-fe. ⛔ Không bước «nhận việc» (claim) trước khi kết luận — UNIQUE theo đơn đã chặn hai người cùng tiếp nhận. ⛔ Không gộp/đối chiếu hai đơn trùng mã số thuế, ⛔ không xoá byte kho, ⛔ không RLS, ⛔ không sửa ToChucService · HoSoService · QuyenToChuc, ⛔ không đổi hình dạng app.submission / submission_attachment.

⚠️ Người duyệt cần biết — sáu chỗ

1. Mã HTTP 422 xuất hiện lần đầu ở admin-be — và lệch với 1.3. Đơn thiếu trường ⇒ 422 ADM.SUBMISSION.INCOMPLETE theo spine AD-28 mục 4 («khuôn đúng nhưng vi phạm luật nghiệp vụ»); trong khi 1.3 đã ship dùng 400 cho thiếu khối Điều 8. Change ghi nhận lệch ở memlog, ⛔ không sửa ngược hợp đồng đã ship. Người duyệt gật hoặc bác đúng chỗ này — admin-fe sẽ hiển thị hai thông điệp khác mã cho hai lỗi «thiếu» cùng bản chất.

2. Không đi qua khung phê duyệt, không cột trạng thái — hai phương án bị loại có lý do. Ép tiếp nhận qua một đề xuất tiep_nhan_don là đẻ hai lượt duyệt cho một hồ sơ, trái FR-057 (tiếp nhận là việc maker). Thêm cột trang_thai lên bảng đơn là thêm chỗ phải chứng minh «portal_app không ghi được» — vai ấy đang có UPDATE theo cột trên bảng đó. Giá của lựa chọn: một bảng + hai trigger; đơn «treo» ở đã nộp không ai nhắc, không có hạn xử lý — màn admin-fe lọc da-nop là chỗ nhìn, ngoài change.

3. Lối đọc cho bên thứ ba là hàm SECURITY DEFINER — cùng hướng PO đã chốt cho kho byte. Bảng mang ghi_chu_noi_boFR-058 cấm bên thứ ba thấy; cấp SELECT trần là vi phạm ở tầng quyền, còn SELECT theo cột thì ma trận/cổng chưa có ô. Hàm DEFINER chạy bằng quyền chủ bảng ⇒ thân một câu SQL, search_path khoá, STABLE, lưới ghim đúng 4 cột. ⚠️ Cô lập theo người nộp là có điều kiện theo tham số portal-be truyền vào — cùng mức khai của submission_of_user (V5), ⛔ không khai «đã cô lập». MaTranQuyenVerifier chưa kiểm EXECUTE — lưới HinhBangV23Test hỏi has_function_privilege bù; mở rộng cổng là việc riêng.

4. Byte đính kèm dùng chung hai bảng tham chiếu. Sau tiếp nhận, submission_attachmenttpp_attachment cùng trỏ một khoá kho. Hôm nay chưa ai xoá byte; khi OAPI-206-xoá-byte-đính-kèm (admin-be#69) làm, phải xét cả hai bảng — change ghi vào issue đó khi đóng. Tải đính kèm hồ sơ (1.3) so lại dấu vân nên byte mất/đổi bị phát hiện, không im lặng.

5. Từ vựng loại pháp nhân lệch giữa hai bảng là vĩnh viễn. Đơn (V5) dùng NGAN_HANG · TRUNG_GIAN_THANH_TOAN · KHAC, tổ chức (V12) dùng ngan_hang · trung_gian_thanh_toan · to_chuc_khacAD-14.1 làm cả hai thành vĩnh viễn. Ánh xạ ghim ở một chỗ (LoaiPhapNhan.tuMaDon) kèm lưới ba giá trị; portal thêm giá trị mới ⇒ tiếp nhận 422 loaiPhapNhan INVALID, ⛔ không tự đoán. Cấp độ an toàn ('1'..'5') chép nguyên chuỗi sang tpp_profile.cap_do_an_toan1.1/1.3 chưa ràng buộc cột này; sau chốt từ vựng khác thì là di trú dữ liệu của họ.

6. Chi tiết đơn trả email người nộp ra bề mặt quản trị. Cùng mức lộ với E-004/1.10 đọc người dùng TPP — OAPI-240 đã duyệt; ⛔ không vào vết, không vào log. Số di trú V23 cần soát với slot a trước khi hoà (án lệ trùng số 13/09).

E-004/2.1 Tài khoản người dùng của bên thứ ba — portal-be

Nguồn hồ sơ. portal-be @ origin/tpp-user-account f1360adopenspec/changes/tpp-user-account/ (proposal.md · design.md · specs/tpp-self-registration/spec.md · tasks.md). Issue OAPI-10-tai-khoan-nguoi-dung-ben-thu-ba (thangvv111/portal-be#3). Change CHƯA đóng — bảng task 24/28; đây là bản thảo giữa chừng, không phải hồ sơ bản cuối.

Yêu cầu và cách đo

Nền hợp đồng API (AD-28) — dùng cho mọi story sau của portal-be

Yêu cầu Ràng buộc Đo bằng
Hình đường dẫn và đánh phiên bản Dưới /api/v1/, danh từ số nhiều, kebab-case, lồng tối đa hai mức; thay đổi phá vỡ mở v2 song song, ⛔ không sửa v1 tại chỗ Đường đăng ký nằm đúng không gian
Phân trang đánh số từ 1 page < 1 bị từ chối bằng 400 theo phong bì PRT.*, ⛔ không âm thầm trả trang đầu; phản hồi mang pageCount · pageNumber · pageSize Hai ca ngược chiều: page=0 ⇒ 400 không kèm dữ liệu · page=1 ⇒ trang đầu, pageNumber = 1
Dấu vết tương quan Request-ID Nhận nếu người gọi gửi, sinh nếu không có, trả lại trong response header, traceId của phong bì lỗi Hai ca: có gửi ⇒ giữ nguyên đúng giá trị ở cả header lẫn traceId · không gửi ⇒ tự sinh, hai nơi bằng nhau
Thân JSON camelCase ⛔ Tên cột cơ sở dữ liệu không lộ ra bề mặt API Đọc thân JSON: không tên nào dạng snake_case
Mô tả OpenAPI là hiện vật trong repo openapi.yaml trong repo; cổng kiểm đỏ khi file lệch với mã Đổi bề mặt mà chưa cập nhật file ⇒ cổng thoát khác 0, nêu chỗ lệch

Tài khoản bên thứ ba

Yêu cầu Ràng buộc Đo bằng
Đăng ký tài khoản Email + mật khẩu; trùng email bị từ chối; sai khuôn ⇒ 400, vi phạm luật nghiệp vụ ⇒ 422 Đăng ký hợp lệ ⇒ tài khoản ở trạng thái chưa xác minh, phản hồi không chứa mật khẩu dưới bất kỳ dạng nào
Xác minh email trước khi được nộp Mã xác minh có hạn; mã sai/hết hạn bị từ chối và không đổi trạng thái Ba ca: mã đúng còn hạn ⇒ chuyển sang đã xác minh · mã hết hạn ⇒ vẫn chưa xác minh · chưa xác minh thử nộp ⇒ bị chặn
Danh tính bất biến, tách khỏi người dùng nội bộ Danh tính là mã nội bộ, ⛔ không phải email Đổi email ⇒ mã định danh không đổi; rà mã miền ⇒ không tham chiếu nào tới nơi lưu người dùng nội bộ
Mật khẩu không tồn tại ở dạng đọc lại được Argon2id, bộ tham số AD-12; ⛔ không xuất hiện trong phản hồi, nhật ký, hay thông điệp lỗi Đăng ký rồi đọc cả phản hồi lẫn nhật ký của chính lượt đó

Quyết định và cái phải trả

  • Chặn page=0 ở MÃ, không chỉ đặt thuộc tính. Thuộc tính một-gốc của Spring Data là một dòng cấu hình — ghi đè được lúc chạy bằng biến môi trường, đúng lỗ hổng Actuator đã đo được ở lượt trước. Nên ngoài thuộc tính còn một lớp kiểm ở mã trả 400, cộng ca lưới đo hành vi thật qua HTTP. ⇒ Cùng một bất biến được giữ ở hai tầng, và tầng cưỡng chế là mã.
  • Request-ID phải nối vào traceId, không làm rời. Phong bì lỗi đang tự sinh traceId riêng; không nối thì tồn tại hai mã tương quan song song và nhật ký không nối được với response mà người gọi cầm trên tay — đúng lúc cần truy một sự cố thì mỗi bên có một mã khác nhau.
  • openapi.yaml sinh từ mã rồi so với file đã commit; lệch ⇒ CI đỏ. ⛔ Cố ý không dùng một endpoint tài liệu: AD-13 cấm phụ thuộc build nên portal-fe ở repo khác chỉ đọc được hợp đồng như một file trong git, mà một endpoint thì không diff được trong review.
  • Lưu trữ nằm sau một PORT; adapter thật là nợ có mã theo dõi.Không dựng DDL trong test để "tạm có bảng" — nếu admin-be khai khác hình thì test xanh mà production hỏng, đúng loại xanh-giả mà story khung của repo này đã tìm ra ba lần. Test dùng adapter trong bộ nhớ và khai rõ đó là test double, không phải bằng chứng về lưu trữ.

⚠️ Người duyệt cần biết — bản thảo này chưa xong, và phần chưa xong là phần ghi nợ

Bảng task còn 4 mục, đều thuộc chặng hồ sơ:

Mục Nội dung
6.1 Điền phần xác minh bằng lệnh thật và mã thoát thật, không qua pipe
6.26.3 Rà trục nhạy cảm (đủ tám dòng, mỗi dòng có phép thử hoặc [N/A] kèm lý do) và hồ sơ an ninh
6.4 Ghi phần bị chặn vào mục «chưa xong» kèm mã issue nợ OAPI-14

⇒ Hai điều người duyệt cần cân, tài liệu này chỉ ghi lại chứ không kết luận:

  1. Khoản nợ về lưu trữ chưa được ghi vào hồ sơ change. Mục 6.4 còn để trống, và em đã dò toàn bộ tệp của change ở f1360ad: không tệp nào có mục «chưa xong». Tức khoản nợ hiện chỉ sống trong issue OAPI-14-bang-va-vai-cho-tai-khoan-ben-thu-ba (thangvv111/admin-be#5), không sống trong hồ sơ mà người duyệt cầm trên tay.
  2. admin-be chưa có dòng di trú, mọi bằng chứng về hành vi lưu trữ trong change này đều đến từ test double. Hồ sơ khai rõ điều đó và cố ý không dựng DDL trong test — nhưng khi duyệt thì phải duyệt với đúng nhận thức ấy: cái được chứng minh là luật nghiệp vụ, không phải hình dạng dữ liệu đã lưu.

Cổng hợp đồng API đã xong trong lúc bản thảo này được viết: các mục 4.14.35.3 (sinh openapi.yaml, cổng so file với mã, nối vào lệnh kiểm để CI gánh, ca lưới đo hai chiều) đã tickf1360ad.

  • Tài liệu hướng dẫn: khong-can — quyết định ghi Ở ĐÂY, không ở link.yaml. Change 2026-09-07-tpp-user-account ở repo admin-be (phần dữ liệu của story: hai bảng trong schema app, khoá chính bất biến, UNIQUE email chuẩn hoá — không màn hình nào) không sinh trang hướng dẫn người dùng cuối. Làn docs quyết sau khi đọc hồ sơ; PO duyệt 07/09/2026.

    ⚠️ Khoá docs: trong link.yaml của change này để trống, và sẽ ở nguyên như vậy: hồ sơ đã archive, mà PO chốt 07/09/2026 — archive là BẤT BIẾN. Hệ quả nói thẳng để không ai dò lại: inbox --role docs mục [D3] sẽ kê change này vĩnh viễn. Đó là lỗ thật đã có người quyết, không phải việc còn tồn — và dòng này là vết của quyết định đó.

E-004/2.8 Xác thực và phiên cho người dùng bên thứ ba — portal-be

Nguồn hồ sơ. portal-be @ origin/xac-thuc-va-phien-tpp 42c88e9openspec/changes/xac-thuc-va-phien-tpp/ (proposal.md · design.md · specs/tpp-self-registration/spec.md · specs/scaffold-portal-be/spec.md · tasks.md · test-cases.md · security.md · review.md · docs.md · uat.md). Issue OAPI-71-xac-thuc-va-phien-tpp (thangvv111/portal-be#19). Change CHƯA đóng ⇒ bản thảo; dấu duyệt ghim sha7 bản cuối.

Yêu cầu và cách đo

Yêu cầu Ràng buộc Đo bằng
Đăng nhập bằng email và mật khẩu, qua một lớp adapter Xác thực đi qua một cổng miền để thay nguồn danh tính mà ⛔ không đụng phần còn lại; giai đoạn này chỉ hiện thực đường tự quản. Khoá định danh bền là email công ty Rà bề mặt: đúng một cổng miền cho xác thực
Phiên có trạng thái, kiểm thu hồi ở mỗi yêu cầu Hết phiên khi không hoạt động 15 phút; khoá tài khoản ⇒ phiên đang mở chấm dứt ngay, ⛔ không chờ hết hạn Ca gọi sau khi tài khoản bị khoá ⇒ bị từ chối ngay lượt kế tiếp
Đăng xuất huỷ ĐÚNG MỘT phiên ⛔ Không huỷ mọi phiên của tài khoản — người dùng đăng nhập ở hai nơi thì đăng xuất một nơi ⛔ không đá nơi kia ra Ca hai phiên cùng sống, đăng xuất một ⇒ phiên kia vẫn dùng được
Khoá tạm sau nhiều lần sai — 3 lần / 15 phút ⛔ Ngưỡng là cấu hình, ⛔ không phải hằng số rải trong mã; kèm guard khởi động kiểm lại. ⛔ Đếm theo cặp ⟨tài khoản, IP⟩ Guard khởi động: cấu hình sai ⇒ tiến trình từ chối khởi động
Đặt lại mật khẩu ⛔ không tiết lộ email nào đã đăng ký Mã đặt lại có hạn, dùng một lần; phản hồi dùng câu trung lập cho cả email có thật lẫn email không tồn tại. Đặt lại xong ⇒ mọi phiên cũ chấm dứt Hai lượt hỏi — email có thật và email không có — cho cùng một phản hồi
Đổi mật khẩu trong phiên Làm back-end trước, gói design chưa có màn (PO chốt 09/09) Bề mặt HTTP có điểm cuối; ⛔ chưa có màn để nghiệm thu bằng mắt
Vết nhật ký cho bốn sự kiện xác thực → năm (change khai-audit-action-event, archive 11/09) Đăng nhập thành công · thất bại · bị chặn vì khoá tạm (tách khỏi thất bại) · đăng xuất · đổi mật khẩu. Một cổng miền; bản hiện thực nay là sink JDBC vào audit.action_event (nhật ký chỉ ghi thêm dùng chung hai bề mặt, K2 · AD-4): INSERT theo 9 cột, ⛔ không SELECT/UPDATE, cùng giao dịch với thao tác; thất bại/bị chặn ⛔ mang email, mã chủ thể, IP. App-log giữ làm vết vận hành (email che + IP), ngoài K2 Vai portal_app chèn được / ⛔ đọc / ⛔ sửa; thao tác đổ ⇒ vết không tồn tại; đọc kiểm bằng superuser
Ma trận quyền của vai đang nối khớp cả hai chiều Hợp đồng CSDL phải khai đúng thứ vai thật có, và ngược lại Đối chiếu hai chiều, ⛔ không chỉ chiều khai-có

⛔ Cố ý KHÔNG có trong story này

Không khai bảng nàoAD-14 giao trọn dòng di trú cho admin-be; story này chốt hình dạng rồi giao sang, bên kia khai và merge trước. ⛔ Không đường xác thực nội bộ và ⛔ không dùng chung bảng tài khoản với bề mặt quản trị. ⛔ Không xác thực đa yếu tố — hoãn sang đợt LDAP. ⛔ Không dựng bảng nhật ký nghiệp vụ: story này chỉ dựng đường nối tới kho nhật ký sẽ có sau. ⛔ Không màn «quản lý phiên đang mở» — chưa có trong gói design.

⚠️ Người duyệt cần biết — bốn chỗ

1. Một hệ quả VẬN HÀNH, ⛔ không phải hệ quả giao diện — và nó làm cụm không khởi động lại được. Sau change này portal-be cần bảng phiên có mặt trong CSDL; cụm nào chưa di trú tới mức đó sẽ từ chối khởi động. Đó là chủ đích của cổng fail-closed, ⛔ không phải sự cố — nhưng nó buộc một thứ tự triển khai: bản admin-be mang dòng di trú bảng phiên phải lên trước. Đọc nhầm chỗ này là đi tìm lỗi ở portal-be trong khi thiếu sót nằm ở lượt triển khai.

2. Khoá tạm đếm theo cặp ⟨tài khoản, IP⟩ — chủ đích, ⛔ không phải thiếu sót. Đếm theo riêng tài khoản sẽ cho một người lạ khoá tài khoản của người khác chỉ bằng cách gõ sai vài lần. Cái giá đã biết: người dùng đổi mạng có thể thấy hành vi khác nhau. ⚠️ Trang hướng dẫn ⛔ không được mô tả nó thành «sai 3 lần là khoá», vì câu đó sai với người vừa đổi mạng.

3. Hai bề mặt chọn HAI định dạng phiên trên dây khác nhau. Cổng bên thứ ba khai mã đục đi ở header, không ký; bề mặt quản trị (E-001/2.3) chốt cookie. Có lý do — hai bề mặt khác mức phơi và khác đường vào — nhưng ⛔ đây là hai hợp đồng, không phải một; ai đọc lướt rất dễ tưởng dự án có một chuẩn phiên chung.

4. Nợ K2 sinh ra cùng change, đã khai chứ ⛔ không giấu — và ĐÃ TRẢ. Bốn sự kiện xác thực ban đầu ghi ra nhật ký ứng dụng — thứ xoay vòng và xoá được; yêu cầu vết không sửa được chưa thoả tới khi story nhật ký của E-001 lên trunk. Mốc đã tới: change khai-audit-action-event (OAPI-119, thangvv111/portal-be#23, archive 11/09, 162bd3c) đưa năm sự kiện vào audit.action_event cùng giao dịch. ⚠️ Hai điều người duyệt cần biết từ change ấy: (a) hợp đồng CSDL của portal-be nay đòi bảng nhật ký có mặt — cụm chưa di trú audit/V5 không khởi động (chủ đích, thứ tự triển khai: trunk → uat di trú); (b) cổng khởi động cũ mù với quyền mức cột — tiền đề «main cũ bị chặn» trong proposal là sai và đã đính chính sau đo; chính change này sửa cái mù ấy. Nợ còn lại có tên: tpp_id chưa vào jsonb vết (miền TppUser của repo không mang nó, trả ở E-005).

⚠️ Về khoá docs: — chỗ này KHÁC mọi story trước, người duyệt nên biết. link.yaml đang để trống, và lần này ⛔ khong-can không còn là lựa chọn hợp lệ: luật đổi 09/09/2026 buộc change đổi hành vi vận hành phải trỏ vào vùng van-hanh/. Chính docs.md của change đã tự khai đủ ba thứ người vận hành gặp thật — điểm cuối mới, cấu hình mới có guard khởi động, và một phụ thuộc di trú mới. ⚠️ Nhưng vùng van-hanh/ chưa tồn tại trong repo docs (đang là việc OAPI-79) ⇒ đây là change đầu tiên bị chặn thật bởi chỗ trống đó, chứ không còn là chuyện dự phòng.

E-004/2.2 Đơn nộp hồ sơ — portal-be

Nguồn hồ sơ. portal-be @ origin/main 6fc108aopenspec/changes/archive/2026-09-11-don-nop-ho-so/ (proposal.md · design.md · specs/tpp-self-registration/spec.md · tasks.md · test-cases.md · security.md; bản thảo viết từ nhánh 17249c5, bản cuối soát lại theo archive). Issue OAPI-49-don-nop-ho-so (thangvv111/portal-be#12) — hai comment «Đối chiếu» và «PO trả lời hai câu» (11/09) là nguồn của hai quyết định PO-1, PO-2 dưới đây. Change ĐÃ đóng (archive 11/09/2026, merge 6fc108a, verdict PASS @ 8d2fd41 tự chấm) ⇒ bản cuối. Change thứ hai của story — cap-do-an-toan-tap-dong (OAPI-174-cap-do-an-toan-tap-dong, thangvv111/portal-be#34), origin/cap-do-an-toan-tap-dong @ 2e21580 (proposal · design · specs/tpp-self-registration/spec.md · tasks · test-cases · security) — chưa thi công, chốt hai câu PO treo 12/09: cấp độ an toàn tập đóng 1–5, nội dung 8 ô tự do — phần đánh dấu ⟪12/09⟫ dưới đây là của change ấy.

Yêu cầu và cách đo

Yêu cầu Ràng buộc Đo bằng
Đơn là tài nguyên của người đã đăng nhập Mọi thao tác đòi phiên bên thứ ba; thiếu/hết/thu hồi ⇒ 401 cùng một mã như mọi lỗi xác thực, ⛔ không rò đơn có tồn tại. Danh tính người nộp lấy từ phiên; thân request ⛔ không có trường nào đặt được người nộp Gọi cả năm thao tác không phiên ⇒ 401 PRT.0006; thân mang trường «người nộp» ⇒ 400 (thuộc tính lạ bị từ chối)
Mỗi người tối đa MỘT nháp mở, kể cả khi hai yêu cầu tạo đến đồng thời Nháp thứ hai ⇒ 409, ⛔ không tạo; nộp xong mới mở được nháp mới (mã khác) Ca hai luồng tạo cùng lúc ⇒ đúng một 201, kho có đúng một nháp
Ghi nháp bằng toàn bộ thân, lặp lại được PUT toàn thân: trường vắng = trường xoá, ⛔ không trộn với bản trước; nháp lưu được khi thiếu bất kỳ trường nào (kể cả loại pháp nhân) — nháp là biểu mẫu nhập dở Ghi cùng thân ba lần ⇒ ba lần 200, đọc lại một bản; lần sau vắng mã số thuế ⇒ đọc lại thấy rỗng
Chỉ kiểm định dạng và ràng buộc schema Mã số thuế khuôn 10 hoặc 10-3 chữ số (chỉ khi có giá trị) · loại pháp nhân ∈ 3 giá trị đóng · ⟪12/09⟫ cấp độ an toàn ∈ tập đóng 15 (Điều 8.6 TT64 — cấp độ theo quy định Chính phủ, FR-013; hết trần 100 chuỗi tự do; 0 · 6 · 03 · 3.0 · «cấp độ 3» đều 400; vắng vẫn là nháp hợp lệ) · trần độ dài (tên pháp lý 500 · mã số thuế 14) · khối nội dung là JSON object, ≤ 64 KiB, sâu ≤ 8. Sai ⇒ 400, details[] {field, issue: INVALID} nêu tên trường (phong bì AD-16), ⛔ không dội giá trị bị từ chối. Thuộc tính lạ trong thân ⇒ 400 — với PUT thay toàn thân, «bỏ qua» là để một tên trường gõ nhầm xoá im lặng dữ liệu đã lưu. ⛔ Không kiểm «đủ 8 khối Điều 8», ⛔ không kiểm mã số thuế đã tồn tại Ghi 12AB ⇒ 400, thông điệp có tên trường và không chứa 12AB; hai người cùng mã số thuế đều nộp được, phản hồi không khác nhau
Nộp là thao tác riêng, có điều kiện nộp (PO-1) POST /{id}/submit, ⛔ không phải một trường trong thân ghi nháp — để một lần lưu tự động ⛔ không vô tình nộp. Điều kiện: bốn trường định kiểu có giá trị + khối nội dung không rỗng. Thiếu ⇒ 422, details[] {field, issue: REQUIRED} liệt kê tên các trường thiếu (message là chuỗi chung), đơn vẫn là nháp. Predicate điều kiện nộp nằm ngay ở câu UPDATE đánh dấu nộp — để một PUT xoá trường không lọt giữa lượt đọc và ghi của nộp Nộp nháp thiếu cấp độ an toàn ⇒ 422 có tên trường, submittedAt vẫn rỗng; nộp nháp có {} ⇒ 422
Đơn đã nộp là bất biến Ghi hay nộp lại ⇒ 409, ⛔ không đổi trường nào kể cả mốc nộp — ⛔ không âm thầm 200; không vai nào xoá được (vai CSDL không có quyền DELETE) Nộp lần hai ⇒ 409, mốc nộp vẫn là mốc lần đầu
Đơn chỉ nhìn thấy và ghi được bởi chính người nộp Tập đơn chỉ gồm đơn của người gọi; đọc/ghi/nộp đơn của người khác ⇒ 404 giống hệt đơn không tồn tại (trừ traceId, timestamp), ⛔ không 403; mọi lệnh ghi mang điều kiện chủ sở hữu ngay trong câu SQL. Phân trang theo khuôn chung, đơn mới trước Gỡ điều kiện chủ sở hữu khỏi lệnh ghi ⇒ ca «ghi đơn của người khác» phải đỏ (mutation); A ghi đơn của B ⇒ 404 và updatedAt của B không đổi
Nhãn phiên bản nội dung do hệ thống đặt ⟪12/09⟫ dieu-8/8-o-tu-do-v1 cho đơn tạo từ change thứ hai — nghĩa: nội dung là object 8 khoá văn bản tự do đúng 8 khối Điều 8 (bao_mat · pham_vi_du_lieu · thong_bao_vi_pham · dich_vu · phi · cap_do_an_toan · quyen_truy_cap · cham_dut), BE ⛔ không kiểm sự có mặt hay kiểu từng khoá (AD-3; «đủ 8 khối» là việc admin-be khi tiếp nhận). Đơn cũ giữ nhãn cũ dieu-8/chua-co-mau-hop-dong — nhãn là thuộc tính của đơn, ⛔ không phải của phiên bản phần mềm. Đặt khi tạo, trả về ở mọi phản hồi; trường ⛔ không có trong request; gửi ⇒ 400 (thuộc tính lạ) Gửi nhãn khác ⇒ 400, đọc lại vẫn là nhãn hệ thống
Nộp để lại vết, không chứa nội dung đơn Vết ghi mã đơn + mã người nộp; ⛔ không mã số thuế, tên pháp lý, mã phiên. Ban đầu ghi sau commit (vết cho giao dịch rollback là vết sai K2 cấm xoá); từ khai-audit-action-event (E-004/2.8, archive 11/09) vết vào nhật ký chỉ ghi thêm audit.action_event, là câu ghi cuối trong cùng giao dịch — nộp đổ thì vết không tồn tại, bảng làm hộ điều afterCommit từng làm. Nợ K2 đã trả Nộp đơn có mã số thuế ⇒ vết có hai mã, không có mã số thuế

⛔ Cố ý KHÔNG có trong story này

Không đính kèm tệp (PO-2): bảng không có cột, spine chưa có quyết định nào về lưu tệpnợ có tên, trả khi spine có quyết định và admin-be khai vật mang. ⛔ Không trạng thái đơn, ⛔ không «trả về bổ sung» — vòng đời đơn là E-004/2.5. ⛔ Không giới hạn tần suất nộp — E-004/2.3. ⛔ Không khai bảng, ⛔ không sửa hợp đồng CSDL — đã có từ OAPI-51. ⛔ Không UNIQUE mã số thuế, ⛔ không tra cứu mã số thuế đã tồn tại — đó là kênh dò. ⛔ Không máy trạng thái (chỉ «mốc nộp rỗng / có giá trị»), ⛔ không xoá hay «khoá mềm» nháp.

⚠️ Người duyệt cần biết — năm chỗ

1. Điều kiện nộp trả 422, ⛔ không phải 400 — và chữ nghĩa mâu thuẫn với AD-3. AD-3 nói repo này không mang luật nghiệp vụ; mã PRT.0003 lại mang chữ «vi phạm quy tắc nghiệp vụ». Change đọc điều kiện nộp là kiểm bắt buộc ở mức schema — thứ AD-3 cho phép — chứ không phải luật thẩm định; và chọn 422 vì request nộp không có thân và đúng khuôn, cái chưa đủ là trạng thái của nháp. Dùng 400 là bắt giao diện hiểu «tôi gửi sai» trong khi họ không gửi gì. Mâu thuẫn được giải bằng chú thích tại chỗ và memlog, ⛔ không đẻ mã lỗi mới cho một ca. Người duyệt nên gật hoặc bác đúng chỗ này, vì trang hướng dẫn sau này sẽ tả thông điệp đó.

2. Một thay đổi bề mặt CHUNG đi kèm. Trần đọc JSON (tài liệu 256 KiB, sâu 32) đặt ở tầng đọc để chặn thân nhồi trước khi dựng cây — trần của khối nội dung (64 KiB / sâu 8) chỉ đo được sau khi đã đọc, một mình nó là cửa nhồi bộ nhớ. Trần này áp cho mọi điểm cuối, kể cả bốn thao tác đã có. Thân của chúng nhỏ hơn trần hàng trăm lần nên không đổi hành vi, nhưng đây là thay đổi toàn cục đã khai, ⛔ không phải tác dụng phụ.

3. «Một nháp mở» giữ bằng khoá giao dịch, ⛔ không phải ràng buộc CSDL — có điều kiện dừng. Không thêm được chỉ mục duy nhất vì bảng thuộc admin-be (AD-14). Change chọn khoá tư vấn giao dịch theo người nộp và đo trước quyền gọi hàm của vai ứng dụng; đo xanh. Bản cuối sau lens review: khoá hai tham số (mã riêng của repo + băm mã người nộp — không gian một tham số là của cả CSDL dùng chung), ném khi không có giao dịch (khoá ngoài giao dịch thả ngay mà không ai thấy), và áp cho cả ba đường tạo · ghi · nộp. @Transactional của bean là load-bearing — gỡ là 18 ca đỏ; ⛔ đừng «dọn» guard đòi giao dịch.

4. Hai điều trông như lỗ nhưng là CHỦ ĐÍCH. Đơn «rỗng nghĩa» (đủ bốn trường + khối nội dung có một khoá vô nghĩa) nộp được — đó đúng là ranh «định dạng / nghiệp vụ» mà AD-3 kẻ. Hai người cùng mã số thuế đều nộp được — không UNIQUE, không tra cứu, vì tra cứu là kênh dò tổ chức đã đăng ký. Cả hai là việc của thẩm định (E-004/2.5), ca lưới khẳng định chứ không chỉ cho phép.

5. Hai nợ đi kèm, đã khai. Đính kèm tệp (PO-2) — nợ ở tầng hoạch định, không mở được từ làn này. Vết «nộp» ghi ra nhật ký ứng dụng — thứ xoay vòng và xoá được — gộp vào nợ K2 hiện có (OAPI-119, portal-be#23), ⛔ không mở nợ mới. Nộp là mốc pháp lý (đơn thành đầu vào thẩm định), nên có vết dù chưa bất biến vẫn hơn không có.

E-004/2.3 Chống lạm dụng đường đăng ký công khai — portal-be

Chờ hồ sơ. Tiêu chí dự kiến từ kế hoạch:

  • Giới hạn tần suất tạo tài khoản và nộp hồ sơ theo nguồn (FR-059).
  • Không tiết lộ một mã số thuế đã tồn tại trong hệ thống hay chưa — đó là kênh dò.
  • Căn cứ là FR-059 bảo vệ hệ thống, không phải Điều 11.9 (AD-8).

⟨CẦN NGƯỜI VIẾT — agent làn portal-be viết khi đóng change⟩

E-004/2.7 Nội dung pháp lý có phiên bản và vết đồng ý — portal-be

Chờ hồ sơ. Tiêu chí dự kiến từ kế hoạch:

  • Phục vụ nội dung hai văn bản theo phiên bản, ⛔ không ghi đè tại chỗ.
  • Ghi vết ai đồng ý PHIÊN BẢN NÀO, lúc nào — đối chiếu được về sau.
  • Chặn bởi E-004/1.8 (vật mang bảng).

⟨CẦN NGƯỜI VIẾT — agent làn portal-be viết khi đóng change⟩

E-004/2.10 Giới hạn tốc độ ngoài đường đăng nhập và độ dài tối thiểu khi đổi mật khẩu — portal-be

⟨CẦN NGƯỜI VIẾT — agent làn portal-be viết khi đóng change⟩

E-004/2.11 Điểm cuối nhận tệp đính kèm cho đơn nộp và trả tệp cho chính người nộp — portal-be

Nguồn hồ sơ. portal-be @ origin/main 24e5076openspec/changes/archive/2026-09-13-tep-dinh-kem-don-nop/ (proposal.md · design.md D1–D12 + «Review outcome» · specs/tpp-self-registration/spec.md 5 requirement ADDED, 27 scenario · tasks.md 5 nhóm · test-cases.md · security.md) + openapi.yaml:579–770; bản thảo viết từ nhánh e958edb, bản cuối soát lại theo archive — proposal/specs không đổi, design.md chỉ thêm mục «Review outcome» (verdict + 10 bất biến, không đổi quyết định nào). Issue OAPI-203-diem-cuoi-nhan-tep-dinh-kem (thangvv111/portal-be#40). Change ĐÃ đóng (archive 13/09/2026, merge 24e5076) ⇒ bản cuối. Vật mang: E-004/1.11 (admin-be V18, archive 13/09 @ 216b940).

Yêu cầu và cách đo

Yêu cầu Ràng buộc Đo bằng
Ba thao tác dưới đơn nộp, đều đòi phiên bên thứ ba POST /api/v1/submissions/{id}/attachments (multipart/form-data, đúng hai phần kind + file) ⇒ 201 siêu dữ liệu · GET …/attachments ⇒ danh sách hiện hành (≤ 3) · GET …/attachments/{attachmentId} ⇒ byte. ⛔ Không DELETE, không PUT. Thiếu phiên ⇒ 401 trước khi xét thân (thân không multipart vẫn 401) Ca HTTP T26.x trên server thật; ca đối chiếu openapi.yaml ↔ phản hồi thật (T27.4): mọi mã + Content-Type nằm trong YAML đã commit
Loại tài liệu là tập đóng ba giá trị GIAY_CHUNG_NHAN_DKDN · GIAY_PHEP_TGTT · CHUNG_NHAN_ATTT — ứng ba ô của gói design khối 2. Ngoài tập ⇒ 400 {field: "kind", issue: INVALID} Scenario «Loại tài liệu ngoài tập đóng»
Siêu dữ liệu trả về không lộ kho id · kind · fileName · mimeType · sizeBytes · digest · createdAt; ⛔ không có khoá kho db:<uuid> (lộ là cam kết hình dạng kho với client). Tên trường tiếng Anh camelCase, ⛔ không phản ánh tên cột (AD-28.5) Thân 201 ⊆ schema SubmissionAttachmentItem; ca lưới khẳng định vắng khoá kho
Chỉ gắn vào đơn của mìnhcòn nháp Đơn của người khác / không tồn tại ⇒ 404 giống hệt (PRT.0002), ⛔ không 403. Đơn đã nộp ⇒ 409 PRT.0004, không ghi gì — kể cả khi nộp xảy ra giữa lúc đính kèm (khoá lockDraftsOf cùng khoá với nộp). Điều kiện chủ sở hữu + còn nháp nằm ở câu lệnh INSERT thứ hai, ⛔ không chỉ ở tầng dịch vụ Scenario «Đính kèm vào đơn của người khác» · «Đua với nộp đơn»; mutation-bite: gỡ điều kiện ở câu lệnh ⇒ ca đỏ
Kiểm nội dung thật tại điểm nhận, byte chưa qua kiểm không chạm đĩa, không chạm CSDL Whitelist theo magic bytes: PDF %PDF- · JPEG FF D8 FF; ⛔ không tin Content-Type của phần, ⛔ không tin phần mở rộng. Thứ tự: loại → có tệp, không rỗng → kích thước → magic bytes → mới băm. Phần nằm trong bộ nhớ (ngưỡng lưu tạm = trần), ⛔ không tệp tạm Scenario «Kiểu nội dung khai gian» · «JPEG khai là text»; unit T24.x 8 ca validator
Trần kích thước là cấu hình một nguồn portal.attachment.max-size mặc định 10 MB (⛔ không hằng số Java); guard khởi động (B1) buộc năm núm khớp nhau (max-file-size · max-request-size · file-size-threshold · max-swallow-size · resolve-lazily=true) — lệch ⇒ ứng dụng không khởi động Scenario «Trần cấu hình lệch giữa hai lớp»; guard 7 ca T27.x
Vượt trần / sai loại ⇒ 400, ⛔ không 413/415 PRT.0001 với details[]: {file, TOO_LARGE} · {file, UNSUPPORTED_TYPE} · {file, INVALID} (thân multipart hỏng khuôn) · phần lạ / trùng tên ⇒ 400. Chỉ một 415: request không phải multipart/form-data Bảng mã lỗi D8 ↔ T26.x
Vân tay sha256:<hex> tính khi nhận, kiểm lại khi đọc Cùng giao dịch với siêu dữ liệu; khi tải về băm lại byte, lệch ⇒ 500 PRT.0000 + nhật ký ERROR mang attachmentId (⛔ không tên tệp) Scenario «Vân tay khớp nội dung» · «Vân tay lệch» (sửa byte trong CSDL ⇒ tải về đỏ)
Thay tệp = hàng mới, hiện hành tính ra portal_app chỉ INSERT/SELECT (AD-30 mục 1) ⇒ đổi tệp cùng loại là INSERT hàng mới; hiện hành = created_at lớn nhất (tiebreak id); danh sách chỉ trả hiện hành. Byte cũ nằm lại tới lượt dọn quản trị Scenario «Thay tệp cùng loại» · «Ba loại, ba tệp» · «Không có đường xoá»
Tải về không render inline, header suy từ siêu dữ liệu đã kiểm Content-Type = mimeType đã kiểm (⛔ không từ tên tệp) · Content-Disposition: attachment kèm filename ASCII an toàn và filename* RFC 5987 (tên tệp mã hoá lại, ⛔ không nối chuỗi thô) · X-Content-Type-Options: nosniff · Cache-Control: no-store · ⛔ không ETag. Đơn đã nộp vẫn tải về được (chỉ nháp mới đính kèm thêm) Scenario «Tải về mang header chống hiển thị inline» · «Tên tệp chứa ký tự đặc biệt»; hai lớp chống chèn CR/LF đo riêng từng lớp
Cô lập đọc: 404 vô hình Tệp của đơn người khác ⇒ 404; byte đã bị quản trị xoá ⇒ 404 (siêu dữ liệu vẫn liệt kê — chỉ-thêm). Đọc qua hàm submission_attachment_of_user (SECURITY INVOKER) ⇒ lớp chặn có điều kiện, khai đúng mức ở security.md S8 Scenario «Tải tệp của đơn người khác» · «Nội dung đã bị quản trị xoá»; bite thay hàm bằng SELECT trần ⇒ đỏ
Đính kèm để lại vết cùng giao dịch, không nội dung tệp attachmentAdded(submissionId, nguoiNopId, attachmentId, kind) ghi audit.action_event trong giao dịch (K2 thoả): ghi đổ ⇒ vết cuộn; ⛔ không tên tệp, không băm. Tải về phía TPP không vết (AD-30 mục 5 chỉ đòi phía quản trị) Scenario «Vết sau khi ghi thành công» · «Không vết khi rollback»
Giới hạn tần suất theo người nộp, cả hai chiều Tải lên 20 lượt / 1 phút · tải về 60 lượt / 1 phút (hai nhóm riêng, cấu hình portal.abuse.*); danh sách không chịu ngưỡng. Vượt ⇒ 429 PRT.0009 + Retry-After. Lượt bị chặn không bóc phần tệp vào bộ nhớ (resolve-lazily) Scenario «Vượt ngưỡng đính kèm» · «Lượt bị chặn không bóc phần tệp» (tệp quá trần + vượt ngưỡng ⇒ 429, không 400) · «Tải về vượt ngưỡng»

⛔ Cố ý KHÔNG có trong story này

Không quét mã độc — nợ có cổng của AD-30 mục 3, điều kiện mở bên thứ ba thật (nhà admin-be/DevOps). ⛔ Không URL tải về công khai/có chữ ký (AD-30 mục 5). ⛔ Không mã hoá tầng ứng dụng (mục 4 — điều kiện triển khai). ⛔ Không xoá từ phía bên thứ ba — vai CSDL không có DELETE. ⛔ Không khai bảng, không GRANTE-004/1.11. ⛔ Không kiểm «đủ 2 tài liệu bắt buộc mới cho nộp» — điều kiện nộp đã chốt ở 2.2 (PO-1), đổi nó là đổi story đã ship (xem ⚠️ 1 dưới). ⛔ Không sao chép sang tpp_attachment khi tiếp nhận (2.5). ⛔ Không streaming/chunked, không dedup theo vân tay, không thumbnail, không nhúng danh sách tệp vào GET /submissions/{id}.

⚠️ Người duyệt cần biết — sáu chỗ

1. Một vế AC của story còn TREO. AC hoạch định ghi «3 tài liệu (2 bắt buộc, 1 nếu có)»; change này nhận được ba loại nhưng không bắt đủ 2 bắt buộc lúc nộp (non-goal, vì điều kiện nộp thuộc 2.2 đã archive — ⛔ PO 09/09 không sửa hồi tố). Lens review 13/09 đã nêu: story sẽ đóng với một vế AC chưa có chủ. Cần PO chốt: mở story/issue nợ «điều kiện nộp đếm tài liệu», hay đóng vế đó. lockDraftsOf đã dọn sẵn chỗ để submit đếm tệp hiện hành trong cùng khoá.

2. 400 thay vì 413/415 cho nội dung tệp — và lời hứa có phạm vi. AD-28.4 cố định 400 = «sai khuôn», nên tệp quá lớn / sai loại là 400 với issue phân biệt; 413 của Tomcat còn kèm đóng kết nối khi thân chưa đọc hết (client thấy connection reset thay vì phong bì). max-swallow-size chỉ giữ lời hứa «đọc hết rồi trả 400» tới ≈ max-request-size + max-swallow-size (≈ 23 MB); tệp 30 MB vẫn bị đóng kết nối — khai ở description của thao tác, ⛔ không hứa tuyệt đối.

3. Bộ nhớ là giá thật của «không lưu tạm». Mỗi request đang xử giữ tới 10 MB, và bản sao/mã hoá pgjdbc nhân lên ⇒ trần ≈ 2–3 × 10 MB × số luồng Tomcat (mặc định 200 ⇒ 4–6 GB lý thuyết). Giới hạn tần suất theo người là lớp chặn chính; DevOps ghim maxThreads/heap theo số này (security.md S10). Băm/ghi theo luồng là bước sau nếu số đo thật đòi.

4. Cô lập là «có điều kiện», ⛔ không phải «đã cô lập». Hàm lọc là SECURITY INVOKER (nếp V5): chỉ chặn khi mã chọn đi qua hàm. Bite bắt buộc trong lưới: thay hàm bằng SELECT trần ⇒ ca «đọc tệp đơn người khác» phải đỏ. «Tổ chức mình» hiện = nguoi_nop_id (một người nộp — một tổ chức); khi story thành viên tổ chức có vật mang, chỉ thân hàm đổi.

5. Byte của tệp bị thay nằm lại — nợ có địa chỉ, không phải của change này. Không UPDATE/DELETE ⇒ mọi lượt «đổi tệp» để lại byte cũ (dữ liệu cá nhân, T_pii) trong tep_noi_dung. Hàng bị thay tính ra được (không phải hiện hành) nên chủ kho dọn định kỳ không cần cột cờ — ghi lên OAPI-202 cho admin-be. Số lượt thay bị chặn bằng tần suất, ⛔ không bằng đếm hàng.

6. Whitelist PDF chặt hơn chuẩn. Chỉ nhận %PDF-byte 0; chuẩn PDF cho phép header nằm trong 1024 byte đầu, một số công cụ xuất thêm byte trước ⇒ tệp thật có thể bị UNSUPPORTED_TYPE. Nới (quét 1024 byte đầu) là một dòng mã khi UAT gặp — PO biết giá, chưa đổi. Thêm định dạng = sửa mã, whitelist là hợp đồng.

E-004/2.12 Đọc byte qua hàm lọc, bỏ SELECT trần khỏi hợp đồng — portal-be

Chờ hồ sơ. Tiêu chí dự kiến từ kế hoạch:

  • AttachmentJdbcRepository.FIND_CONTENT (SELECT noi_dung FROM app.tep_noi_dung WHERE id = ?, dòng 77 @ main 13/09) → gọi hàm của 1.12 với nguoi_nop_id từ phiên; hành vi bề mặt không đổi (2.11): tệp không thuộc tổ chức mình ⇒ 404 vô hình.
  • DatabaseContract (app.tep_noi_dung, dòng 439 @ main 13/09): bỏ SELECT khỏi tập quyền bắt buộc, giữ INSERT theo cột (id, noi_dung); khai EXECUTE hàm lọc vào hợp đồng hàm. Cổng khởi động hai chiều: SELECT còn ⇒ THỪA đỏ · hàm thiếu ⇒ THIẾU đỏ.
  • ⚠️ Chặn merge M:1.12; triển khai story này TRƯỚC 1.13 trên mọi môi trường — nếu không portal-be từ chối khởi động (án lệ OAPI-89-bỏ-dat-updated-at-khỏi-hợp-đồng).
  • Xong = suite xanh trên CSDL còn SELECT trần (hôm nay) trên CSDL đã REVOKE (lab 1.13) — hai fixture, một mã.

⟨CẦN NGƯỜI VIẾT — agent làn portal-be viết khi đóng change⟩

E-004/2.4 Màn đăng ký tài khoản — portal-fe

Chờ hồ sơ. Tiêu chí dự kiến từ kế hoạch:

  • Biểu mẫu dài lưu nháp tự động — tám khối Điều 8, nhập nửa chừng mà mất là mất cả buổi.
  • Theo dõi trạng thái; lý do trả về hiển thị cho bên thứ ba (FR-058).

⟨CẦN NGƯỜI VIẾT — agent làn portal-fe viết khi đóng change⟩

E-004/2.6 Khung hai văn bản pháp lý — portal-fe

Chờ hồ sơ. Tiêu chí dự kiến từ kế hoạch:

  • Hai màn: Điều khoản sử dụng 9 mụcChính sách dữ liệu 8 mục — dựng theo LegalScreen của gói design.
  • Bảng bốn cột chuyển sang thẻ dưới 768px (UX-DR4).
  • ⛔ Khung là của story này; câu chữ pháp lý thì chờ Pháp chế và Tuân thủ — nợ đã ghi ở issue, ⛔ làn không tự viết nội dung pháp lý.
  • Chỉ chặn bởi E-001/5.4, ⛔ không chờ back-end: nội dung có phiên bản là việc của E-004/2.7.

⟨CẦN NGƯỜI VIẾT — agent làn portal-fe viết khi đóng change⟩

E-004/2.9 Màn nộp hồ sơ — portal-fe

⟨CẦN NGƯỜI VIẾT — agent làn portal-fe viết khi đóng change⟩