Bỏ qua

Khung dự án

Nền quản trị · nhóm 0 · 6 story · 🟡 chưa viết xong · còn 2 mốc

Chưa viết xong: còn 2 mốc

Phần chưa viết hiển thị tiêu chí dự kiến từ kế hoạch (đã qua cổng PO duyệt) và mốc chờ agent làn docs.

Story Service Thứ tự Ghi chú
E-001/0.1 Khung dự án admin-be admin-be 1 cap/scaffold-admin-be · AD-24 · Maven, Spotless, JUnit 5, Testcontainers postgres:18
E-001/0.2 Khung dự án portal-be portal-be 2 cap/scaffold-portal-be · AD-24 · pom riêng hẳn; ⛔ không cấu hình Flyway (AD-14)
E-001/0.3 Khung dự án admin-fe admin-fe 3 cap/scaffold-admin-fe · AD-24 · Vite, TypeScript strict; ⛔ không phụ thuộc CDN (AD-23)
E-001/0.4 Khung dự án portal-fe portal-fe 4 cap/scaffold-portal-fe · AD-24 · dựng riêng, không dùng lại cấu hình của 0.3 (AD-13)
E-001/0.5 Dựng repo tài liệu docs docs 5 không OpenSpec · nhà của trang hướng dẫn mà khoá docs: trong link.yaml trỏ tới
E-001/0.6 Khung nghiệm thu uat uat 5b hồi tố (PO chốt 13/09/2026, OAPI-231): dựng 07–12/09 trước khi có story; AC = mô tả cái đã có ở uat@30a7b83 — compose local (AD-26) + AWS · Playwright + uat-case.sh · CI self-hosted (AD-24 mục 7) · seed idempotent · bí mật giả local-only-* (AD-27). ⛔ không OpenSpec cho CA (OAPI-212-uat-viết-ca-không-qua-change) nhưng là nhà của change hạ tầng env/ · tool/ · AWS. Thứ tự 5b = sau 0.5, không dời hàng nào (ô phi số đầu tiên, cố ý)

Vì sao làm — BRD

→ Mục tiêu và giá trị nghiệp vụ nằm ở Tổng quan epic. Ở đây chỉ ghi mỗi story góp gì vào mục tiêu đó.

E-001/0.1 Khung dự án admin-be — admin-be

Góp gì vào mục tiêu epic. Story này không giao một tính năng nghiệp vụ nào — và nói thẳng điều đó là đúng chỗ, vì cái nó giao là khả năng chứng minh những tính năng sau. Ba hình dạng được chốt ở đây, mười bảy story admin-be còn lại tựa lên:

  • Phong bì lỗi ADM.* với traceId sinh ở filter và đi qua MDC. Mỗi request có đúng một traceId, và mọi story sau mang được nó vào nhật ký mà không story nào phải tự nhớ. FR-041 (nhật ký chỉ-ghi-thêm), FR-043 (tra cứu theo TPP · người dùng · khoảng thời gian) và TT-13 (truy vấn được 3 tháng, sao lưu 1 năm) chỉ có nghĩa nếu từng bản ghi nối ngược được về một request cụ thể.
  • Nền kiểm chạy trên PostgreSQL thật (Testcontainers postgres:18), có phép thử mồi chứng minh KẾT NỐI chứ không chỉ isRunning(). E-001/1.2 (ma trận vai và quyền cơ sở dữ liệu) và E-001/1.3 (schema nhật ký bất biến — K2) không có cách kiểm nào khác. Một cái nền xanh-mà-rỗng ở đây nghĩa là hai cam kết tuân thủ nặng nhất của epic không kiểm được.
  • Cấu hình và bí mật đọc một đường duy nhất, thiếu file thì chết sớm kèm tên file. Dựng muộn thì mỗi story sau tự đẻ một kiểu đọc, và tới lúc đó không gỡ ra được nữa.

Giá trị tuân thủ trực tiếp. CI chạy trên self-hosted runner (AD-24 mục 7) không phải để tiết kiệm: runner đám mây checkout mã lên hạ tầng bên thứ ba, mà đây là hệ thống cấp độ 3 theo khoản 4 Điều 11 — đưa mã ra ngoài là quyết định một chiều.

Vị trí trong hàng đợi. Thứ tự bắt buộc 1. Nó chặn 17 story admin-be còn lại, và chặn cả làn uat: job db-migrate của môi trường tích hợp (AD-26) chính là ảnh của admin-be.

⚠️ Cái CHƯA mở khoá. Chế độ chỉ-migrate cố ý không làm ở đây (cần Flyway, thuộc E-001/1.1) ⇒ sau story này profile portal của compose vẫn chưa chạy được, chỉ profile admin chạy được.

E-001/0.2 Khung dự án portal-be — portal-be

Góp gì vào mục tiêu epic. Đây là bề mặt máy chủ mà bên thứ ba chạm vào. Story chưa mang luật nghiệp vụ nào — nộp hồ sơ, khai ứng dụng, chống lạm dụng thuộc E-004, E-005, E-007; cái nó góp là làm cho bề mặt đó kiểm được và dựng được trước khi có luật nào chạy trên nó.

  • Phong bì lỗi PRT.* — không gian mã lỗi RIÊNG, dựng ngay chứ không đợi route đầu tiên. AD-13 cấm hai back-end phụ thuộc build vào nhau, nên phong bì lỗi là văn bản chứ không phải thư viện dùng chung (AD-16). ⚠️ Vế «không chia sẻ mã» của AD-13 đã được PO bỏ 09/09/2026 — chép thì được, phụ thuộc thì không; kết luận ở đây không đổi vì nó đứng trên vế còn hiệu lực. Hai không gian mã lỗi trôi khỏi nhau là hỏng âm thầm: một lỗi trả cho bên thứ ba mà không nối ngược được về nhật ký nội bộ là một lỗ hổng trong đúng thứ FR-045 (xuất hồ sơ vòng đời một TPP) phải trả lời được.
  • ⛔ Không một dòng cấu hình Flyway nào (AD-14), kể cả cho bảng đơn mà chính service này ghi. Ý nghĩa là nghiệp vụ chứ không phải gọn gàng kỹ thuật: đúng một repo sở hữu schema, nên khi bị hỏi ai đã đổi cấu trúc dữ liệu nào thì chỉ có một dòng lịch sử để trả lời, không phải hai dòng phải đối chiếu.
  • Ảnh container chạy được, health GET /actuator/health ⇒ làn uat ghim SHA và dựng được chuỗi nghiệm thu (AD-26: cổng 8080 trong ảnh → 8082 trên host, profile portalfull).

⚠️ Khoảng trống có chủ đích, người duyệt cần biết. AD-26 mô tả portal-be từ chối khởi động khi flyway_schema_history thấp hơn mức nó cần; AC của story E-001/0.2 lại viết thẳng rằng cổng đó là story sau. Change đi theo AC của story (story là hợp đồng của change, AD là trạng thái đích) và đã ghi vênh này vào .memlog.md để làn hoạch định đối chiếu lại AD-26 với epic E-001. Hệ quả cho tới khi cổng đó tồn tại: không có gì chặn portal-be chạy trên một schema cũ hơn mức nó cần.

E-001/0.3 Khung dự án admin-fe — admin-fe

Góp gì vào mục tiêu epic. Đây là bề mặt nhân viên ngân hàng thật sự nhìn thấy — nơi F1 (quản trị người dùng nội bộ, phân quyền) và F3 (hàng đợi chờ duyệt, maker/checker) sẽ sống. Story này chưa dựng màn hình nào; nó dựng các cổng để những màn hình đó không mang theo lỗi lặng lẽ.

  • ⛔ Không phụ thuộc CDN, và cổng đo được bằng máy (AD-23, UX-DR10). Bề mặt quản trị không có đường vào từ Internet, kể cả qua reverse proxy ở DMZ. Một thẻ <link> trỏ ra ngoài không làm build đỏ, không làm test đỏ, và trên máy lập trình viên (có mạng) thì trông đúng — nó chỉ hỏng ở môi trường thật, lặng lẽ, dưới dạng phông thay thế. Cổng quét toàn bộ kết quả dựng biến đúng lớp lỗi đó thành việc máy bắt được thay vì việc mắt người phải bắt.
  • Vị trí back-end nằm trong ảnh, không nằm trong cấu hình (AD-26 mục 4). Giao diện gọi đường tương đối /api/...; nginx trong ảnh proxy sang http://admin-be:8080 theo tên host của mạng compose. Hệ quả cố ý: không có biến môi trường nào mang địa chỉ back-end, nên không có gì để cấu hình sai, và AD-17 (hai back-end không gọi nhau) không bị lách qua đường cấu hình của front-end.
  • Bốn cổng chất lượng fail-closed dựng TRƯỚC khi có mã nghiệp vụ — lint, định dạng, kiểm kiểu, phép thử. Dựng sau vài nghìn dòng thì lần chạy đầu là một bãi lỗi không ai đọc, và kết cục quen thuộc là cổng bị tắt đi thay vì mã được sửa.

Vị trí trong hàng đợi. Mọi story giao diện quản trị đứng sau nó — E-001/5.1, 5.3, 5.5, 5.7, rồi E-002/1.2, 1.4, 1.7 và tiếp theo. Làn uat cũng cần ảnh của nó để khai service admin-fe trong env/compose.yml (AD-26: cổng 80 trong ảnh → 3001 trên host).

E-001/0.4 Khung dự án portal-fe — portal-fe

Góp gì vào mục tiêu epic. Đây là bề mặt công khai của ngân hàng — nơi bên thứ ba đăng ký, khai ứng dụng và nhận khoá (nhóm F9, các epic E-004, E-005, E-007). Như 0.3, story này chưa dựng màn hình nghiệp vụ nào; nó chốt những ràng buộc mà một bề mặt ngân hàng ra Internet không được phép quyết định muộn.

  • Story này ĐÃ dựng RIÊNG, không dùng lại gì của admin-fe (AC của story nói thẳng «cấu hình build sinh ra từ 0.3 cũng là mã»). ⚠️ Căn cứ thứ hai lúc đó là AD-13; PO đã BỎ vế cấm chia sẻ mã ngày 09/09/2026 — nay chép thì được, chỉ phụ thuộc build là không. Đoạn này ghi việc đã làm, ⛔ không phát biểu một luật còn hiệu lực. Lý do là hồ sơ rủi ro: một bề mặt nằm trong mạng nội bộ, một bề mặt ra Internet. Dùng chung mã là mở đường cho một lỗ hổng ở bề mặt công khai kéo theo bề mặt quản trị. Hệ quả phải nói rõ để không ai «dọn dẹp» sau này: về sau thấy hai repo giống nhau thì vẫn không được rút ra gói dùng chung.
  • Bề mặt này không mang bí mật, và điều đó được KHẲNG ĐỊNH chứ không được giả định (AD-27 mục 5). Vite nhúng thẳng mọi biến tiền tố công khai vào gói tĩnh, nên mọi biến vào gói phải nằm trong danh sách biến công khai đã khai, và có cổng quét cả gói tĩnh lẫn lớp ảnh tìm giá trị bí mật. Đây đúng lớp sự cố «khoá lọt vào bundle» mà một cổng Open API không được phép mắc.
  • Ở biên thì không tin header của client. Host ghim theo upstream, X-Forwarded-For ghi đè bằng $remote_addr chứ không nối thêm giá trị client gửi. Ảnh này đang biên trong môi trường AD-26; khi có vành đai thật đứng trước thì đổi ở vành đai.

⚠️ Vênh giữa epic và spine, cần làn kiến trúc chốt. AD-23 binds admin-feadmin-be, không binds portal-fe; còn epic E-001/0.4 nói bề mặt này ra Internet. Nên lý do «tải không được» của luật không-CDN không áp được ở đây. Change vẫn giữ luật không-CDN, nhưng bằng hai lý do khác: toàn vẹn chuỗi cung ứng cho một bề mặt ngân hàng công khai, và dựng lại được tất định. Hôm nay đó là quyết định của change, chưa phải một AD.

E-001/0.5 Dựng repo tài liệu docs — docs

Góp gì vào mục tiêu epic. Bốn story trên dựng thứ để chạy; story này dựng thứ để trình được. Điều 11 và các mốc báo cáo TT-09 · TT-13 đòi hồ sơ mà người ngoài đội đọc được. Nếu tài liệu chỉ sống trong openspec/ của bốn kho mã thì bên thẩm định phải đọc bốn kho mã, và mỗi lần cấu trúc repo đổi thì đường dẫn hồ sơ đổi theo.

  • Một nơi đọc, xếp theo cách người duyệt nghĩ: hệ (Admin / Portal) → epic theo tên nghiệp vụ → nhóm việc, mỗi nhóm một trang, mỗi trang ba phần theo đúng thứ tự đọc vì sao làm → ai dùng → hệ làm gì. Nhóm chạm cả hai hệ xuất hiện ở cả hai mục nhưng là cùng một file — không cắt đôi một hành trình.
  • Duyệt là duyệt một BẢN CỤ THỂ, không phải duyệt một cái tên. Dấu duyệt ghi vào frontmatter dạng duyet: <ngày> · <ai> · <sha7>; hồ sơ ở repo service đổi sau khi duyệt thì sha7 lệch và cổng tài liệu dừng lại cho tới khi duyệt lại. Không có dòng này thì «đã duyệt» là một khẳng định không kiểm được.
  • Khung của toàn bộ 72 story đợt 1 có sẵn từ đầu. Story chưa có hồ sơ hiển thị tiêu chí dự kiến từ kế hoạch đã qua cổng PO duyệt, kèm mốc chờ. Người duyệt vì thế thấy ngay cái gì còn thiếu, thay vì thấy một mục lục ngắn và tưởng đó là toàn bộ phạm vi.

Ranh giới cố ý. ⛔ Repo này không có OpenSpec, không capability, không change (AD-25): nó là kho nội dung, không phải đơn vị thay đổi. Việc ở đây đi thẳng issue → làm → đóng issue.

E-001/0.6 Khung nghiệm thu uat — uat

⟨CẦN NGƯỜI VIẾT — agent làn uat 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-001/0.4 Khung dự án portal-fe — portal-fe

Ở nhóm này chưa có người dùng nghiệp vụ nào — và đó là một kết luận, không phải một chỗ bỏ trống. Bên thứ ba chưa có gì để làm trên cổng: đăng ký, khai ứng dụng, lấy khoá đều thuộc nhóm F9 và các epic E-004, E-005, E-007. Trang mồi trong ảnh không phải màn hình thật, và hồ sơ thi công nói rõ điều đó để không ai đọc nhầm.

Hành trình duy nhất quan sát được ở story này là của người dựng và người nghiệm thu môi trường tích hợp (AD-26, profile portal hoặc full, cổng 80 trong ảnh → 3002 trên host):

  1. Làn uat dựng môi trường theo SHA của repo: uat-env.sh up --sha portal-fe=<sha7>.
  2. Mở http://localhost:3002/ → trang hiển thị đầy đủ chữ và icon, GET / trả 200. Chính đường này là điểm health của ảnh.
  3. Cắt đường ra Internet rồi tải lại → trang vẫn hiển thị đúng phông, và không một request nào đi ra host ngoài. Đây là phép nhìn quan trọng nhất của người nghiệm thu ở story này.
  4. Tải thẳng một đường nội bộ của ứng dụng (không phải /) → vẫn ra trang giao diện, không 404. Nhưng một file tĩnh gõ sai đường thì vẫn phải 404: rơi về trang gốc cho mọi thứ sẽ biến một file JS gõ sai đường thành trang HTML trả 200 — lỗi im lặng khó truy nhất của loại ứng dụng này.
  5. Gọi /api/<đường bất kỳ> → lời gọi tới portal-be theo tên host do môi trường cấp, không qua host cứng nào.
  6. Tắt portal-beGET / vẫn 200, chỉ /api/... lỗi. Giao diện không chết theo back-end.
  7. Đặt sai biến trỏ back-end → container chết ở giây đầu kèm thông điệp, thay vì đổ thành 502 lúc có request thật.

Vì chưa có màn hình nghiệp vụ nào, story này không sinh trang hướng dẫn người dùng cuối. Làn docs xác nhận docs: khong-can cho change scaffold-portal-fe (đây là câu trả lời cho mục 6.4 còn để trống trong bảng task của change). Hướng dẫn cho bên thứ ba viết khi các story nhóm F9 đóng, từ giao diện thật trên môi trường tích hợp — không viết trước, vì viết trước là bịa.

E-001/0.3 Khung dự án admin-fe — admin-fe

Cũng chưa có người dùng nghiệp vụ nào. Nhân viên ngân hàng chưa đăng nhập được: màn đăng nhập và quản trị người dùng là E-001/5.7, và nó còn chờ E-001/2.2 (adapter xác thực và đăng nhập tự quản). Story này chưa có F1F8 nào.

Hành trình quan sát được là của người dựng và người nghiệm thu (AD-26, profile admin hoặc full, cổng 80 trong ảnh → 3001 trên host):

  1. Mở http://localhost:3001/GET / trả 200 cùng trang giao diện; đây là điểm health.
  2. Máy không có đường ra Internet. Với bề mặt này đó là điều kiện vận hành bình thường, không phải một phép thử khắc nghiệtAD-23 đặt admin-feadmin-be ngoài tầm Internet, kể cả qua reverse proxy ở DMZ. Trang phải render bằng đúng phông đã ghim trong repo, không rơi về phông thay thế.
  3. Tải thẳng một đường sâu của ứng dụng → nginx trả tài liệu gốc với 200, không 404; nhưng 404 thật của một đường dưới /api/ không được nuốt thành trang HTML.
  4. Gọi một đường dưới /api/ → chuyển tiếp sang http://admin-be:8080 theo tên host compose. Rà toàn bộ mã nguồn và cấu hình dựng không tìm thấy biến môi trường nào mang cổng hay địa chỉ back-end — đó là một khẳng định của spec, không phải một thói quen tốt.

⇒ Story này không sinh trang hướng dẫn người dùng cuối; link.yaml của change đã khai docs: khong-can. Hướng dẫn cho nhân viên ngân hàng viết khi các màn hình nghiệp vụ đóng, từ UI thật trên môi trường tích hợp.

Hệ làm gì, ràng buộc gì — PRD

Mỗi story một mục, phần máy chủ trước, giao diện sau. Story chưa đóng hiển thị tiêu chí dự kiến từ epics.md.

E-001/0.1 Khung dự án admin-be — admin-be

Nguồn hồ sơ. admin-be @ origin/main e8085da — change đã đóng; hồ sơ bản cuối ở openspec/changes/archive/2026-09-07-scaffold-admin-be/ (proposal.md · design.md · specs/scaffold-admin-be/spec.md · tasks.md · security.md · test-cases.md).

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

Yêu cầu Ràng buộc Đo bằng
Phong bì lỗi thống nhất Đúng một phong bì code · message · traceId · timestamp · details tuỳ chọn; mã mang tiền tố ADM.; timestamp là chuỗi ISO-8601, và hình dạng đó ép tại chính hợp đồng chứ không dựa cấu hình tuần tự hoá toàn cục Một phép thử đi qua luồng HTTP thật, không phải qua ObjectMapper tự dựng
Dữ liệu vào không hợp lệ bị chặn Request vi phạm ràng buộc khai báo bị từ chối bằng phong bì ADM.*, và hành vi nghiệp vụ phía sau không chạy spring-boot-starter-validation khai tường minh; ./mvnw dependency:tree phải thấy validator
Cấu hình và bí mật đọc một đường Mỗi bí mật một file trong thư mục cấu hình dạng cây; cùng một cơ chế ở mọi môi trường, chỉ nguồn thư mục đổi Đọc mã, không có đường đọc thứ hai
Thiếu bí mật thì chết sớm Tiến trình dừng với mã thoát khác 0 và in đích danh tên file còn thiếu Xoá một file bắt buộc rồi khởi động
Bí mật không rò Không lộ qua năm vector: endpoint quản trị · log ứng dụng và log khởi động · thông điệp lỗi · lớp ảnh Docker · biến môi trường tiến trình Năm cổng riêng: ActuatorExposureVerifier, SecretMaskingConverter, GlobalExceptionHandler, hai bước Docker trong CI, HopDongRepoTest
Tín hiệu sống GET /actuator/health trả 200 khi tiến trình sẵn sàng; chỉ health được bật Khẳng định theo tập cho phép, không theo danh sách cấm
Nền kiểm trên PostgreSQL thật Container postgres:18; ⛔ không cơ sở dữ liệu nhúng; phép thử mồi phải mở kết nối và truy vấn thật SELECT 1 qua DriverManager với getJdbcUrl() — ⛔ không phải assertTrue(container.isRunning())

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

  • Ghim bằng BOM spring-boot-dependencies 3.5.16, ⛔ không <parent> (AD-24 mục 1). Cái phải trả được nói thẳng: bỏ spring-boot-starter-parentmất toàn bộ phiên bản plugin mặc định, nên mọi plugin trong <build> phải khai <version> tường minh — thiếu là bản dựng trôi theo môi trường. Bảng đã tra thật từ Maven Central ngày 06/09/2026: maven-compiler-plugin 3.16.0 · maven-surefire-plugin 3.6.0 · spotless-maven-plugin 3.10.2 · google-java-format 1.36.1 · Maven wrapper 3.3.4 tải Maven 3.9.16.
  • Bất biến an ninh phải là MÃ, không phải một dòng cấu hình. Đo được trên container thật: AD-27 mục 5 từng chỉ là một dòng YAML, và đặt MANAGEMENT_ENDPOINTS_WEB_EXPOSURE_INCLUDE=* lúc chạy mở lại env · configprops · beans · loggers · threaddump, trả 200, mà không cổng nào đỏ. Cùng tinh thần với ràng buộc tách trách nhiệm: thứ không được phép tắt thì không được để ở tầng cấu hình.
  • Kiểm theo TẬP CHO PHÉP, không theo danh sách CẤM. Phép thử cũ khẳng định envconfigprops không tồn tại, nên thêm beans vẫn lọt. Spec nói «không endpoint nào khác được bật» — đó là một khẳng định về tập.
  • Một traceId cho mỗi request, sinh ở filter và đi qua MDC — không sinh trong từng handler. Nhờ vậy nhật ký nghiệp vụ của mọi story sau mang được id mà không story nào phải tự nhớ.
  • Phong bì lỗi ép hình dạng tại chính hợp đồng. Đo được ở portal-be ngày 06/09: phép thử đơn vị tự dựng ObjectMapper cho Instant ra chuỗi ISO và xanh, nhưng qua luồng Spring thật cùng đối tượng ra 1.788635051521968E9. ⇒ ép shape = STRING thẳng trên trường timestamp, ít nhất một phép thử đi qua luồng HTTP thật.

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

  • Chế độ chỉ-migrate (job db-migrate của AD-26): cần Flyway và một dòng di trú, thuộc E-001/1.1. Hệ quả phải nói thẳng: profile portal của compose chưa chạy được sau story này, chỉ profile admin chạy được.
  • Bất kỳ DataSource nào ở đường chạy chính — Testcontainers chỉ sống trong phạm vi test.
  • Flyway và schema (E-001/1.1); xác thực, phân quyền, nhật ký nghiệp vụ (E-001/2.x, 3.x, 4.x).

Người duyệt cần biết

  • Change này KHÔNG qua cổng review độc lập — và lý do đó tự nó là kết quả của một lượt review. Lượt đầu ghi request lên board với trục T_secret. Lens review bác chính cơ sở đó: trục T_secret trong sổ đăng ký nói về client_secret của ứng dụng bên thứ ba lộ đúng một lần (AD-12 · FR-030, thuộc E-005) — actor sai, và change khung không có cơ chế đó. Cái thật sự được kiểm là bí mật cấu hình. Sau khi đánh lại [N/A] đúng bản chất, tool/axes-gate.sh trả rc 0 ⇒ theo luật board, change không chạm trục thì làn tự đóng. PO chốt phương án này ngày 07/09/2026.
  • Việc đã chuyển ra ngoài repo — nay ĐÃ XONG: OAPI-16-so-truc-thieu-truc-bi-mat-van-hanh (thangvv111/openapi-platform#1, đã đóng). Lúc change này đóng, sổ trục không có trục nào phủ bí mật vận hành nên axes-gate mù đúng chỗ nó nên sáng. Trục T_opsecret đã vào registry/axes.tsv ngày 07/09/2026 (sổ nay 12 trục), phủ đủ năm đường rò — endpoint quản trị · log · thông điệp lỗi · lớp ảnh · biến môi trường — cộng ràng buộc thiếu bí mật bắt buộc thì tiến trình từ chối khởi động và in tên file thiếu.

    ⚠️ Hồ sơ gốc của change ghi mã CŨ là OAPI-6, và mã đó nay trỏ sang một issue KHÁC. Ba issue trên repo hoạch định từng cùng mang OAPI-6 do lỗi cấp mã của gói (đã vá ở 2.20.0); khi đánh số lại, #1 thành OAPI-16, #3 thành OAPI-17, còn #2 giữ OAPI-6 làm bia mộ. ⇒ Người đọc lần theo design.md trong openspec/changes/archive/ sẽ hạ cánh im lặng xuống một issue thật nhưng sai (#2, về quy ước dựng khung front-end) chứ không gặp lỗi. Hồ sơ archive là bất biến nên không sửa; trang này là chỗ duy nhất sửa được, nên nó ghi cả hai mã. - Nơi đặt kho bí mật production vẫn hoãn (AD-12 chốt phải có, chưa chốt cái nào). Story này không bị chặn bởi điều đó — cơ chế đọc từ thư mục, còn nguồn của thư mục là chuyện của môi trường. - .gitattributes của repo không phủ openspec/ (lỗi đang mở, đo ở portal-be). Change này thêm file .java, .xml, Dockerfile; việc phủ luôn chạm mọi repo nên cố ý không gộp vào đây.

  • Tài liệu hướng dẫn: khong-can — quyết định ghi Ở ĐÂY, không ở link.yaml. Change 2026-09-07-scaffold-admin-be (dựng khung admin-be, chưa một dòng mã nghiệp vụ, không bề mặt nào người dùng chạm) 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-001/0.2 Khung dự án portal-be — portal-be

Nguồn hồ sơ. portal-be @ origin/scaffold-portal-be ca2216aopenspec/changes/scaffold-portal-be/ (proposal.md · design.md · specs/scaffold-portal-be/spec.md · tasks.md, 26/26 mục đã tick). Change CHƯA đóng ⇒ đây là bản thảo; số chốt lại khi archive, và dấu duyệt phải ghim sha7 của bản cuối.

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

Yêu cầu Ràng buộc Đo bằng
Công cụ build ghim trong repo Maven Wrapper trong repo, <java.version> = 21; pom.xml độc lập, ⛔ không <parent> trỏ pom của repo khác; phiên bản thư viện ghim bằng BOM spring-boot-dependencies ./mvnw chạy được trên máy không có mvn trên PATH; hỏi cấu trúc chứ không đếm chuỗi con
Bố cục module theo miền Gốc package vn.com.msb.openapi.portal, đúng bảy thư mục tppauth · submission · apprequest · browse · selfservice · abuse · shared — đặt theo miền, ⛔ không theo tầng Liệt kê thư mục con: không có controller / service / repository ở mức đó
Cổng định dạng fail-closed spotless:check với google-java-format, đỏ khi lệch Làm lệch một file rồi chạy: thoát khác 0 và nêu tên file
Kiểm trên PostgreSQL thật Testcontainers postgres:18; ⛔ không H2, HSQLDB, Derby hay CSDL nhúng nào Phép thử mồi mở kết nối JDBC và truy vấn trả kết quả
Bề mặt tình trạng tối thiểu GET /actuator/health trả 200 khi sống; ⛔ không endpoint Actuator nào khác GET /actuator/env phải trả 404
Một cách đọc bí mật, thiếu file thì từ chối khởi động spring.config.import=configtree:/run/secrets/ — cách đọc duy nhất ở mọi môi trường; thiếu file bắt buộc thì dừng, nêu tên file Xoá một file trong /run/secrets/ rồi up → đỏ kèm tên file
Ảnh container chạy được, không mang bí mật Nghe cổng 8080 trong container, health GET /actuator/health; ⛔ không file nào lấy từ env/secrets/; chạy bằng user không phải root Build rồi chạy với bí mật mount vào /run/secrets/
Phong bì lỗi PRT.* Lỗi ra ngoài theo phong bì chuẩn, mã bắt đầu bằng PRT., có định danh truy vết Một request chạm lỗi không xử lý riêng vẫn đúng phong bì
⛔ Không có cấu hình Flyway Không phụ thuộc flyway-core, không khoá spring.flyway.*, không thư mục db/migration pom.xml và toàn bộ file cấu hình

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

  • Dockerfile nằm trong change này dù AC của story không liệt kê nó. UAT-HUONG-DAN.md §3 ghi chủ sở hữu là «làn service (story khung)»AD-26/AD-27 đã ghim đủ tham số. Để nó ra ngoài thì làn uat không có gì để kéo, mà mở một change riêng chỉ cho một file là chia đôi một việc không chia được.
  • Bí mật: chỉ một đường đọc, và thiếu file thì CHẾT SỚM. optional:configtree: sẽ im lặng khi thiếu file — đúng thứ AD-27 muốn chặn. Nên dùng configtree: không optional:, cộng một kiểm tra khởi động nêu tên file còn thiếu.
  • Actuator dùng allowlist (include=health), không dùng denylist. Thêm endpoint sau này là một hành động có chủ đích, không phải một lần quên đóng.
  • Ảnh nhiều tầng, tầng chạy dùng JRE chứ không JDK, chạy bằng user không phải root. AD-23 đặt bề mặt này ngoài tầm Internet, nhưng đó là biện pháp mạng — nó không thay được biện pháp trong ảnh.
  • Testcontainers cần Docker ở mọi nơi chạy test, kể cả runner. Docker chết ⇒ lane test ĐỎ, không phải «bỏ qua». Đây là cái giá AD-24 mục 4 đã chấp nhận có ý thức: một bộ kiểm xanh mà không kiểm gì thì tệ hơn không có bộ kiểm.

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

  • Cổng kiểm phiên bản schema tối thiểu (portal-be từ chối khởi động khi flyway_schema_history thấp hơn mức cần). AD-26 có mô tả hành vi này, nhưng AC của story viết thẳng rằng đó là story sau. Vênh đã ghi .memlog.md để làn hoạch định đối chiếu AD-26 với epic E-001.
  • Mọi luật nghiệp vụ của portal-be — tài khoản TPP, đơn nộp, đơn khai ứng dụng, chống lạm dụng — thuộc E-004, E-005, E-007.
  • Bất kỳ cấu hình Flyway nào (AD-14); xác thực, phân quyền, phiên.

⚠️ Người duyệt cần biết — một lệch xuyên repo đã xảy ra, đã vá, nhưng lớp lỗi vẫn mở

PO chốt 06/09/2026: dòng Spring Boot của dự án là 3.5.16, không phải 4.1.1. Cả hai dòng đều đang GA khi chọn; chọn 3.5.16 vì hệ sinh thái tích hợp đã chín (OAuth2, JWT, driver, SDK bên thứ ba), và một hệ thống cấp độ 3 phải nộp hồ sơ thì ít ẩn số đáng giá hơn vòng đời dài — Spring Boot 4 kéo theo Spring Framework 7 có breaking change, dồn rủi ro về các story tích hợp sau chứ không lộ ra ở khung.

Bản thảo trước của portal-be ghim 4.1.1, tức lệch cả major với admin-be. Đã vá ở ca2216a (07/09): spring-boot.version = 3.5.16, khớp admin-be. Cùng lớp lệch, đã vá trước đó ở c04f6fd (06/09): google-java-format từng để trống ở portal-be (chạy 1.30.0) trong khi admin-be ghim 1.36.1 — hai back-end định dạng bằng hai formatter khác nhau mà CI mỗi bên vẫn xanh.

Cái không được quên sau khi vá. AD-24 mục 1 ghim CÁCH ghim (BOM import) chứ không ghim SỐ, và AD-13 cấm hai back-end phụ thuộc build vào nhau nên không có phép thử xuyên repo. ⇒ Hai back-end lệch được mà không cổng máy nào bắt. Cả hai lần trên đều tìm ra bằng mắt người đối chiếu hai pom.xml, không phải bằng một cổng đỏ. Đây là rủi ro thường trực của topology bốn repo chứ không phải một sự cố đã đóng: đổi số ở một repo thì phải đổi ở cả hai, và hôm nay thứ nhắc điều đó chỉ là chú thích trong pom.xml.

E-001/0.5 Dựng repo tài liệu docs — docs

Nguồn hồ sơ. Repo docs @ origin/main 64fbcae. ⛔ Story này không có change OpenSpec (AD-25): hợp đồng của nó là AC trong _bmad-output/planning-artifacts/epics.md của repo hoạch định, và bằng chứng là chính repo này. Không có proposal.md / spec.md để trích, nên phần dưới đối chiếu AC với trạng thái đo được trên origin/main.

AC ↔ trạng thái đo được

AC của story Trạng thái
Repo private, đặt cùng workspace tại E:\Projects\openapi\docs
⛔ Không OpenSpec, không capability ✅ không có openspec/; việc đi thẳng issue → làm → đóng issue
Engine MkDocs Material, tiếng Việt, khớp nếp đã có để mượn tooling mkdocs.yml; xem trước bằng Docker squidfunk/mkdocs-material
Vùng máy sinh src/phe-duyet/ render đúng và ra rỗng không lỗi khi archive còn rỗng tool/render-phe-duyet.sh; hiện có index vùng, 7 trang epic và 15 trang nhóm
Vùng viết tay theo khuôn sáu mục src/huong-dan-admin/src/huong-dan-portal/, gồm hai trang tong-quan.md
mkdocs.yml khai nav đủ; trang thêm sau phải khai nav ✅ trừ vùng máy sinh — tool tự ghi giữa mốc RENDER-NAV
⛔ Không viết OAuth, sandbox, mẫu request ✅ data-plane đã hoãn

Cái story chốt, và đã đổi so với AC ban đầu

  • Hình dạng vùng kỹ thuật đã đổi sau khi story mở. AC gốc viết src/phe-duyet/<epic>/<story>/{brd,urd,prd}.md — một story ba file. Bản đang chạy là một trang mỗi NHÓM VIỆC (feature = nhóm story), BRD ở tầng epic, URD một hành trình cho cả nhóm, PRD mỗi story một mục và phần máy chủ trước, giao diện sau. Lý do: ba file cho mỗi story buộc người duyệt mở hàng trăm file để ráp lại một tính năng. AD-25 không chốt cấu trúc dưới mức vùng, nên đây là quyết định của dự án, khai bằng RENDER_CMD trong .bmad/docs.conf.
  • Ai viết đã đổi — PO chốt 06/09/2026 (AD-25.5 bản sửa). Ban đầu làn thi công viết tài liệu của mình. Nay làn docs viết cả bốn vùng — vùng thứ tư van-hanh/ (runbook) thêm ngày 09/09/2026 theo AD-25 mục 5.1; làn service không viết. Mốc chờ viết trong khung giữ nguyên chuỗi gốc vì gói, inbox --role docsdocs-gate đều đếm đúng chuỗi đó — nên thân trang không được trích chuỗi ấy nguyên văn, kể cả để giải thích: trích một lần là trang tự đánh dấu mình chưa xong, và render lần sau sẽ đè lại chính khối vừa viết. «Người viết» ở đây là agent của làn docs; trang còn mốc là trang chưa xong.
  • Render lại KHÔNG đè phần đã viết. Mỗi phần nằm giữa một cặp chú thích HTML VIET:BEGIN / VIET:END; khối không còn mốc = đã viết ⇒ giữ nguyên. Frontmatter duyet: và mọi khoá lạ cũng được giữ; chỉ title:stories: do máy sinh lại. Không có hợp đồng này thì mỗi lần render là một lần xoá công viết.
  • stories: trong frontmatter là hợp đồng với docs-gate ở repo service — nó là cách cổng tìm ra trang «của» một story. ⛔ Không sửa tay.

⛔ Cố ý KHÔNG có

  • Trang OAuth, sandbox, mẫu request — data-plane đã hoãn; viết trước là bịa.
  • Đợt 2 (E-008E-011) để trống cho tới khi chia story.
  • Ảnh chụp màn hình trong vùng hướng dẫn: luật của repo buộc bám UI thật, mà chưa màn hình nghiệp vụ nào tồn tại.

Người duyệt cần biết

  • Cổng đo được của story là: build xanh và render ra đúng, không đòi nội dung. Nội dung là việc của các story sau — chính trang này là một ví dụ.
  • Gói bmad-profile của repo này đang ở 2.17.0, canon ở E:\Projects\bmad-profile-canon là 2.19.0. Chênh này chưa được đo chiều. Nó chạm hợp đồng giữa renderer và docs-gate, nên nên chốt trước khi ghi dấu duyệt đầu tiên lên vùng kỹ thuật.

E-001/0.3 Khung dự án admin-fe — admin-fe

Nguồn hồ sơ. admin-fe @ origin/scaffold-admin-fe 9ab5f5bopenspec/changes/scaffold-admin-fe/ (proposal.md · design.md · specs/scaffold-admin-fe/spec.md · tasks.md, 28/28 mục đã tick). Change CHƯA đóng ⇒ đây là bản thảo; dấu duyệt phải ghim sha7 của bản cuối. link.yaml đã khai docs: khong-can.

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

Yêu cầu Ràng buộc Đo bằng
Toolchain ghim thành file trong repo Bản Node ghim ở cả .nvmrc engines.node, hai nơi không được mâu thuẫn; TypeScript · Vite · Vitest · ESLint · Prettier mỗi thứ ghim đúng một bản Cài bằng Node ngoài khoảng ⇒ dừng với lỗi nêu bản yêu cầu, không cài tiếp (engine-strict=true); npm ci trên máy sạch cho bản do package-lock.json quyết định
Bốn cổng chất lượng fail-closed lint · định dạng · kiểm kiểu · phép thử, mỗi lệnh thoát khác 0 khi vi phạm; CI chạy đủ bốn trên self-hosted runner và đỏ khi bất kỳ lệnh nào đỏ Bốn ca vi phạm dựng sẵn; lỗi kiểu dưới strict còn phải làm bản phát hành không cho ra kết quả
Ứng dụng tự chứa, không phụ thuộc mạng ngoài Kết quả dựng không chứa tham chiếu nào tới tài nguyên mạng ngoài (font, icon, script, stylesheet); font và icon nằm trong repo, ghim phiên bản Phép quét toàn bộ dist/, thoát khác 0 nếu tìm thấy; và mở trên máy không có Internet phải render đúng phông, không rơi về phông thay thế
Ảnh Docker chạy được Dockerfile ở gốc, dựng tĩnh → nginx nghe cổng 80 trong container; GET / trả 200 Chạy ảnh và gọi; đường sâu của ứng dụng một trang trả tài liệu gốc 200, không 404
Vị trí back-end nằm trong ảnh, không nằm trong cấu hình Mọi yêu cầu dưới /api/ chuyển sang back-end quản trị theo tên host compose; mã front-end gọi đường tương đối và ⛔ không đọc cổng hay địa chỉ back-end từ biến môi trường Rà toàn bộ mã nguồn và cấu hình dựng: không có biến nào như vậy
Ảnh không mang bí mật ⛔ Không COPY gì từ thư mục bí mật của môi trường tích hợp; ⛔ không ARG/ENV nào nhận giá trị bí mật (AD-27 mục 5) Dockerfile

Ghim đã đo, không phải chọn theo cảm tính

  • .nvmrc = 22, engines.node = >=22.17.0 <23, .npmrc đặt engine-strict=true nên bản sai làm npm ci dừng chứ không chỉ cảnh báo. Spine viết «bản LTS đang hiệu lực» và cố ý không ghim số — số thuộc file build của repo. Đo trên chính máy chạy runner self-hosted: node v22.17.0, npm 10.9.2.
  • React 19.2.8 · Vite 8.2.2 · TypeScript 6.0.3 · Vitest 5.0.0 · ESLint 10.10.0 · Prettier 3.9.6.
  • Lệnh gộp npm run kiem chạy đúng chuỗi cổng: lintformat:checktypecheckbuildtest. Trong CI, build chạy TRƯỚC test — cố ý: cổng tự chứa quét thư mục dist thật, đảo thứ tự thì nó không có gì để quét.

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

  • Ảnh hai tầng, tầng chạy không có Node. Tầng dựng dùng node:22-slimglibc, không phải alpine/musl: lock sinh trên Windows không ghi trường libc, nên trong alpine npm cài bản glibc còn runtime lại đòi bản musl và build gãy ở lightningcss. Tầng chạy là nginx:alpine, chỉ COPY thư mục distnginx.conf — không Node, không node_modules, không mã nguồn.
  • try_files rơi về index.html chỉ NGOÀI /api/, để 404 thật của API không bị nuốt thành một trang HTML.
  • Cấu trúc thư mục front-end là KHOẢNG TRỐNG của spine, không phải quy ước đã chốt. AD-24 mục 5 khai gốc package và cây theo miền cho hai back-end Java; Structural Seed không có cây tương ứng cho front-end. Change chọn tối thiểu src/{app,shared,assets}, miền nghiệp vụ mọc cạnh chúng khi story giao diện đầu tiên tới. Kèm đề nghị làn kiến trúc ghim cây này lên AD-24 để portal-fe không phải đoán lại — AD-13 cấm phụ thuộc build, còn quy ước thì đúng ra phải giống nhau (⚠️ vế cấm dùng lại mã PO đã bỏ 09/09/2026). Change này không tự sửa spine.
  • Cổng «không CDN» quét dist/ bằng chuỗi ⇒ lọt được nếu URL được ghép động lúc chạy. Chấp nhận ở mức khung (change này chưa có mã gọi mạng nào), nhưng story giao diện phải giữ cổng này khi thêm mã.
  • nginx.conf ghim cứng admin-be:8080 ⇒ ngoài compose (dev trên máy) tên host đó không phân giải được. Xử lý bằng proxy của Vite dev server, cấu hình riêng — ⛔ không thêm biến môi trường, vì AD-26 mục 4 nói front-end không cần biết cổng back-end.

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

  • Màn hình nghiệp vụ nào — F1F8 thuộc cap/admin-console-ui, story khác.
  • Xác thực; gọi API thật. /api mới chỉ được cấu hình đường đi, chưa ai gọi nó.
  • Thư viện giao diện / quản lý trạng thái — chọn ở story giao diện đầu tiên, không phải ở khung.
  • Khung của portal-festory riêng E-001/0.4 — và khung của admin-be. Căn cứ là phạm vi story: mỗi repo một story, một change. ⚠️ Trước 09/09/2026 chỗ này còn dẫn AD-13 làm căn cứ thứ hai; vế cấm chia sẻ mã đã được PO bỏ, nên ⛔ đừng đọc dòng này thành một điều cấm.

Người duyệt cần biết

  • Phạm vi rộng hơn AC của story một mục, và hồ sơ nói rõ để reviewer bác được nếu sai: story E-001/0.3 không nhắc Dockerfile; nó đến từ AD-26 (bảng môi trường tích hợp) và từ yêu cầu của làn uat. Gộp vào đây vì Dockerfile chỉ có nghĩa khi đã có thứ để build.
  • Ghim Node 22 khi 24 cũng đã LTS là lựa chọn tường minh, không phải bỏ sót: chọn 24 thì runner phải được cài Node 24 trước, nếu không CI đỏ ngay lượt đầu. Đảo ngược được bằng một dòng .nvmrc cộng một lần cài trên runner — không phải quyết định một chiều. ⚠️ Nhưng nó tạo một lệch với portal-fe: xem mục E-001/0.4 ngay dưới.

E-001/0.4 Khung dự án portal-fe — portal-fe

Nguồn hồ sơ. portal-fe @ origin/scaffold-portal-fe 5c9bc04openspec/changes/scaffold-portal-fe/ (proposal.md · design.md · specs/scaffold-portal-fe/spec.md · tasks.md). Change CHƯA đóng ⇒ bản thảo. Bảng task 28/29: mục còn lại là 6.4 — điền docs: trong link.yaml; câu trả lời của làn docskhong-can (lý do ở phần URD phía trên).

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

Yêu cầu Ràng buộc Đo bằng
Bộ lệnh kiểm fail-closed Định dạng · lint · kiểm kiểu · thử đơn vị · dựng gói, chạy được từ cây sạch; mỗi lệnh thoát khác 0 khi vi phạm và 0 khi không; ⛔ không lệnh nào cảnh báo rồi vẫn thoát 0 npm run check — chuỗi 9 bước, gồm bốn cổng viết riêng cho story: check:node-pin, check:external-refs, check:secrets, check:public-env
Gói tĩnh không phụ thuộc mạng ngoài Không tải font, icon hay tài sản nào từ host khác; phông hoặc nằm trong repo hoặc là phông sẵn có của hệ điều hành — ⛔ không có đường thứ ba Quét đầu ra tìm tham chiếu tuyệt đối ra host khác; phục vụ trong môi trường cắt mạng: đủ chữ và icon, không một request nào đi ra ngoài
Ảnh chạy được, chuyển tiếp đúng chỗ Cổng 80 trong ảnh, GET / trả 200 làm health; /api/... chuyển sang back-end theo tên host do môi trường cấp; mã giao diện gọi đường tương đối; ⛔ cổng back-end không nhúng vào gói tĩnh lúc build Smoke chạy trên container thật
Giao diện sống độc lập back-end Back-end chưa lên ⇒ GET / vẫn 200, chỉ /api/... lỗi
Đường trong ứng dụng tải thẳng được Đường nội bộ (không phải /) trả trang giao diện, không 404
Bề mặt này không mang bí mật Gói tĩnh và các lớp ảnh không chứa bí mật; mọi biến nhúng vào gói là công khai có chủ đích, nằm trong danh sách đã khai và được tài liệu nói rõ là đọc được từ trình duyệt check:secrets quét gói tĩnh và lớp ảnh; check:public-env đối chiếu danh sách
Môi trường chạy được ghim Đúng một bản Node, CI và ảnh dựng dùng cùng bản; bản sai thì nói ra, không im lặng đi tiếp check:node-pin so ba nơi, chạy trong CI

Ghim đã đo

.nvmrc = 24, engines.node = ^24. React 19.2.8 · Vite 8.2.2 · Vitest 5.0.0 · ESLint 10.10.0 · Prettier 3.9.6 · TypeScript 6.0.3. ⛔ Không lấy TypeScript 7.0.2 (bản latest): typescript-eslint 8.69.0 khai peer typescript >=4.8.4 <6.1.0 — lấy 7 là mất cổng lint kiểu tĩnhAD-24 bắt phải có.

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

  • Tên host back-end đọc lúc KHỞI ĐỘNG ảnh, không phải lúc build. Cấu hình nginxkhuôn có biến, thay giá trị khi container lên, mặc định portal-be:8080. Nhúng địa chỉ vào gói tĩnh lúc build bị loại vì nó buộc dựng lại ảnh cho mỗi môi trường — đúng thứ AD-26 mục 4 cấm.
  • location ^~ /api/ — dấu ^~ là bắt buộc, không phải trang trí. nginx cho location dạng regex thắng location tiền tố; thiếu ^~ thì khối regex tài sản tĩnh nuốt mọi đường API có đuôi và trả 404. Đo được: /api/v1/spec.json404 trong khi /api/bat-ky204. Với một cổng Open API thì /api/v1/openapi.json là đường gần như chắc chắn tồn tại.
  • Cấu hình sai phải đổ ở giây đầu tiên, không đổ thành 502. Biến trỏ back-end được kiểm dạng trong /docker-entrypoint.d/05-kiem-cau-hinh.envsh — ảnh nginx source file .envsh nên exit 1 làm container chết ngay. Để nginx tự vỡ lúc có request là biến một lỗi cấu hình thành một lỗi vận hành khó truy.
  • Ba header an ninh LẶP LẠI tường minh trong từng khối con. nginx chỉ kế thừa add_header xuống khối con khi khối đó không có add_header nào của riêng nó — nghĩa là một dòng header thêm vào khối tài sản tĩnh sẽ gỡ sạch ba header an ninh mà không ai thấy. Chép lại là dư thừa có chủ đích, và smoke có khẳng định canh đúng chỗ đó.
  • try_files rơi về trang gốc chỉ cho request ĐIỀU HƯỚNG; tài sản tĩnh không tìm thấy vẫn trả 404. Rơi về index.html cho mọi thứ biến một file JS gõ sai đường thành trang HTML trả 200 — lỗi im lặng khó truy nhất của loại ứng dụng này.
  • npm làm trình quản lý gói — đi kèm Node nên không đẻ thêm một bản phải ghim ở ba nơi (CI, ảnh, máy dev). pnpm/yarn bị loại ở quy mô này.
  • Rủi ro đã nêu tên: khuôn nginx thay biến lúc khởi động thì biến thiếu sẽ trỏ vào hư không một cách im lặng ⇒ có giá trị mặc định và một phép thử chứng minh /api tới đúng đích. Trang mồi có thể bị hiểu là màn hình thật ⇒ nói rõ trong mã và trong AGENTS.md rằng đây là khung.

⛔ Cố ý KHÔNG có — nêu tên để không ai tưởng là bỏ sót

CSP đầy đủ · cổng trợ năng (a11y) · i18n · ngưỡng độ phủ · ngân sách kích thước gói · ghim digest ảnh nền · cổng rà lỗ hổng phụ thuộc (audit/SCA) · quét bí mật trên lớp ảnh đã dựng ở mức sâu hơn. Mỗi món hoặc cần một màn hình thật để không phải đoán, hoặc là một quyết định vận hành vượt quyền của một change dựng khung. Chúng đi thành issue, không nằm im trong file.

Ngoài ra: không màn hình nghiệp vụ nào (nhóm F9 là story sau, trang mồi chỉ đủ chứng minh khung chạy) · không xác thực · không chọn thư viện giao diện, quản lý trạng thái hay khung định tuyến ngoài mức tối thiểu · không đóng gói lên registry.

⚠️ Người duyệt cần biết — hai front-end đang lệch dòng Node, và CI hai bên chọn Node theo hai cách

Đo trên hồ sơ ngày 07/09/2026:

Repo Nguồn .nvmrc engines.node CI chọn Node thế nào Lý do ghi trong hồ sơ
admin-fe origin/scaffold-admin-fe 9ab5f5b 22 >=22.17.0 <23 dùng Node sẵn có của runner — workflow không gọi setup-node; engine-strict=true làm npm ci đỏ nếu lệch ghim theo bản đo được trên runner self-hosted (node v22.17.0) để cổng CI có nghĩa từ ngày đầu
portal-fe origin/scaffold-portal-fe 5c9bc04 24 ^24 actions/setup-node@v4 với node-version-file: .nvmrctự cài bản đã ghim Node 22 vào maintenance, hết hỗ trợ 04/2027 — cùng loại rủi ro spine đã bác khi loại dòng cũ

Hai lý do đều đúng trong phạm vi của mình, và AD-13 chỉ cấm phụ thuộc build chứ không buộc hai repo cùng bản công cụ. Nhưng hệ quả thì có thật và không cổng nào bắt được:

  • Hai bề mặt của cùng một dự án đang chạy trên hai dòng Node lớn khác nhau, với hai lập luận ngược chiều nhau cùng nằm trong hồ sơ đã nộp để duyệt.
  • admin-fe phụ thuộc vào việc Node sẵn có trên runner giữ nguyên ở 22.x. Một lần nâng Node trên runner — chính là hành động mà lập luận của portal-fe khuyến khích — làm CI của admin-fe đỏ, và đỏ ở bước npm ci chứ không ở một cổng chất lượng nào.

Điểm mở cho làn kiến trúc và bên vận hành runner, không phải kết luận của tài liệu này: chốt một dòng Node cho cả hai bề mặt, hoặc chốt tường minh rằng chúng được phép lệch và ghi cách runner phục vụ cả hai.

E-001/0.6 Khung nghiệm thu uat — uat

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

  • Story hồi tố: khung dựng 07–12/09/2026 qua 7 change ở repo uat trước khi có story nào nhận; 6/7 change archive để trống story:epic-status.sh và trang tiến độ mù với uat. Story này là nhà cho chúng và cho mọi change hạ tầng uat về sau (OAPI-212-uat-viết-ca-không-qua-change mục 2: ca viết không qua change, hạ tầng env/ · tool/ · AWS giữ change).
  • AC = mô tả cái đã cóthangvv111/uat origin/main @ 30a7b83 (đo 13/09/2026), ⛔ không đòi thêm gì:
  • env/compose.yml — bảng AD-26 thành cấu hình: db · db-sandbox · db-migrate · db-seed · db-seed-uat · bốn service; tool/uat-env.sh up chờ health theo env/uat-env.conf.
  • env/aws/compose.aws.yml · Caddyfile · cai-dat.sh · len.sh · sinh-bi-mat.sh · dan-token.sh — môi trường UAT trên AWS Lightsail (OAPI-160-môi-trường-uat-aws).
  • Khung Playwright: playwright.config.ts · tests/E-xxx/N.M.spec.ts · tests/_chung/ · cases/E-xxx/N.M.md · results/ — ca theo story, tool/uat-case.sh gen/check/record.
  • CI self-hosted .github/workflows/ci.yml tên CI (AD-24 mục 7; runner oapi-uat-01 trên Windows) + issue-sync.yml.
  • env/seed/*.sql idempotent chạy bằng vai ứng dụng; env/seed/uat/ tài khoản UAT giả chỉ có ở UAT.
  • env/secrets/ bí mật local giả local-only-*, mỗi bí mật một file, env/kiem-secrets.sh kiểm cặp khớp (AD-27).
  • ⛔ Không sửa hồ sơ 7 change đã archive; việc gắn story: E-001/0.6 vào 6 link.yaml (một dòng metadata, không chạm nội dung) là của làn uatOAPI-231 mục 2, PO chốt được sửa khoá này.
  • Xong = hàng 0.6 có trong bảng, check-thu-tu.sh xanh, và sau mục 2 của uat: bash tool/epic-status.sh E-001 liệt E-001/0.6 uat với các change archive.

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