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ớitraceIdsinh ở filter và đi qua MDC. Mỗi request có đúng mộttraceId, 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-13cấ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ủaAD-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ànuatghim SHA và dựng được chuỗi nghiệm thu (AD-26: cổng 8080 trong ảnh → 8082 trên host, profileportalvàfull).
⚠️ 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-26mục 4). Giao diện gọi đường tương đối/api/...;nginxtrong ảnh proxy sanghttp://admin-be:8080theo 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-27mụ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.
Hostghim theo upstream,X-Forwarded-Forghi đè bằng$remote_addrchứ không nối thêm giá trị client gửi. Ảnh này đang là biên trong môi trườngAD-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-fe và admin-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ìsha7lệ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):
- Làn
uatdựng môi trường theo SHA của repo:uat-env.sh up --sha portal-fe=<sha7>. - 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. - 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.
- 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ông404. Nhưng một file tĩnh gõ sai đường thì vẫn phải404: 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. - Gọi
/api/<đường bất kỳ>→ lời gọi tớiportal-betheo tên host do môi trường cấp, không qua host cứng nào. - Tắt
portal-be→GET /vẫn200, chỉ/api/...lỗi. Giao diện không chết theo back-end. - Đặt sai biến trỏ back-end → container chết ở giây đầu kèm thông điệp, thay vì đổ thành
502lú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ó F1–F8 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):
- Mở
http://localhost:3001/→GET /trả200cùng trang giao diện; đây là điểm health. - 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ệt —
AD-23đặtadmin-fevàadmin-bengoà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ế. - Tải thẳng một đường sâu của ứng dụng →
nginxtrả tài liệu gốc với200, không404; nhưng404thật của một đường dưới/api/không được nuốt thành trang HTML. - Gọi một đường dưới
/api/→ chuyển tiếp sanghttp://admin-be:8080theo 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-dependencies3.5.16, ⛔ không<parent>(AD-24mục 1). Cái phải trả được nói thẳng: bỏspring-boot-starter-parentlà mấ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-plugin3.16.0 ·maven-surefire-plugin3.6.0 ·spotless-maven-plugin3.10.2 ·google-java-format1.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-27mục 5 từng chỉ là một dòng YAML, và đặtMANAGEMENT_ENDPOINTS_WEB_EXPOSURE_INCLUDE=*lúc chạy mở lạienv·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
envvàconfigpropskhông tồn tại, nên thêmbeansvẫ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
traceIdcho 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-bengày 06/09: phép thử đơn vị tự dựngObjectMapperchoInstantra chuỗi ISO và xanh, nhưng qua luồng Spring thật cùng đối tượng ra1.788635051521968E9. ⇒ épshape = STRINGthẳng trên trườngtimestamp, và í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-migratecủaAD-26): cần Flyway và một dòng di trú, thuộcE-001/1.1. Hệ quả phải nói thẳng: profileportalcủa compose chưa chạy được sau story này, chỉ profileadminchạy được. - Bất kỳ
DataSourcenà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ụcT_secrettrong sổ đăng ký nói vềclient_secretcủa ứng dụng bên thứ ba lộ đúng một lần (AD-12·FR-030, thuộcE-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.shtrả 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ênaxes-gatemù đúng chỗ nó nên sáng. TrụcT_opsecretđã vàoregistry/axes.tsvngà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 mangOAPI-6do lỗi cấp mã của gói (đã vá ở 2.20.0); khi đánh số lại,#1thànhOAPI-16,#3thànhOAPI-17, còn#2giữOAPI-6làm bia mộ. ⇒ Người đọc lần theodesign.mdtrongopenspec/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-12chố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. -.gitattributescủ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. Change2026-09-07-scaffold-admin-be(dựng khungadmin-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àndocsquyết sau khi đọc hồ sơ; PO duyệt 07/09/2026.⚠️ Khoá
docs:tronglink.yamlcủ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 docsmụ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 ca2216a —
openspec/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 |
Rà pom.xml và toàn bộ file cấu hình |
Quyết định và cái phải trả¶
Dockerfilenằ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)» vàAD-26/AD-27đã ghim đủ tham số. Để nó ra ngoài thì lànuatkhô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-27muốn chặn. Nên dùngconfigtree:khôngoptional:, 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-24mụ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-betừ chối khởi động khiflyway_schema_historythấp hơn mức cần).AD-26có 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ếuAD-26với epicE-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ộcE-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/ và 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-25khô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ằngRENDER_CMDtrong.bmad/docs.conf. - Ai viết đã đổi — PO chốt 06/09/2026 (
AD-25.5bản sửa). Ban đầu làn thi công viết tài liệu của mình. Nay làndocsviết cả bốn vùng — vùng thứ tưvan-hanh/(runbook) thêm ngày 09/09/2026 theoAD-25mụ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 docsvàdocs-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àndocs; 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. Frontmatterduyet:và mọi khoá lạ cũng được giữ; chỉtitle:và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ớidocs-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-008–E-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-profilecủa repo này đang ở 2.17.0, canon ởE:\Projects\bmad-profile-canonlà 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 9ab5f5b —
openspec/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 và 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) |
Rà Dockerfile |
Ghim đã đo, không phải chọn theo cảm tính¶
.nvmrc=22,engines.node=>=22.17.0 <23,.npmrcđặtengine-strict=truenên bản sai làmnpm cidừ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 kiemchạy đúng chuỗi cổng:lint→format:check→typecheck→build→test. Trong CI,buildchạy TRƯỚCtest— cố ý: cổng tự chứa quét thư mụcdistthậ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-slim— glibc, không phải alpine/musl: lock sinh trên Windows không ghi trườnglibc, nên trong alpinenpmcà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ỉCOPYthư mụcdistvànginx.conf— không Node, khôngnode_modules, không mã nguồn. try_filesrơi vềindex.htmlchỉ NGOÀI/api/, để404thậ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-24mụ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ểusrc/{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ênAD-24đểportal-fekhông phải đoán lại —AD-13cấ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.confghim cứngadmin-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-26mụ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 —
F1–F8thuộccap/admin-console-ui, story khác. - Xác thực; gọi API thật.
/apimớ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-fe— story riêngE-001/0.4— và khung củaadmin-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ẫnAD-13là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.3không nhắcDockerfile; nó đến từAD-26(bảng môi trường tích hợp) và từ yêu cầu của lànuat. Gộp vào đây vìDockerfilechỉ 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
.nvmrccộ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ớiportal-fe: xem mụcE-001/0.4ngay dưới.
E-001/0.4 Khung dự án portal-fe — portal-fe¶
Nguồn hồ sơ. portal-fe @ origin/scaffold-portal-fe 5c9bc04 —
openspec/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 docs là
khong-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ĩnh mà AD-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
nginxlà khuôn có biến, thay giá trị khi container lên, mặc địnhportal-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-26mục 4 cấm. location ^~ /api/— dấu^~là bắt buộc, không phải trang trí.nginxcho 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.json→404trong khi/api/bat-ky→204. Với một cổng Open API thì/api/v1/openapi.jsonlà đườ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— ảnhnginxsource file.envshnênexit 1làm container chết ngay. Đểnginxtự 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.
nginxchỉ kế thừaadd_headerxuống khối con khi khối đó không cóadd_headernà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_filesrơ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.htmlcho 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.npmlà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/yarnbị loại ở quy mô này.- Rủi ro đã nêu tên: khuôn
nginxthay 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/apitới đúng đích. Trang mồi có thể bị hiểu là màn hình thật ⇒ nói rõ trong mã và trongAGENTS.mdrằ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: .nvmrc — tự 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-fephụ 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ủaportal-fekhuyến khích — làm CI củaadmin-feđỏ, và đỏ ở bướcnpm cichứ 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
uattrước khi có story nào nhận; 6/7 change archive để trốngstory:⇒epic-status.shvà trang tiến độ mù vớiuat. Story này là nhà cho chúng và cho mọi change hạ tầnguatvề sau (OAPI-212-uat-viết-ca-không-qua-change mục 2: ca viết không qua change, hạ tầngenv/·tool/· AWS giữ change). - AC = mô tả cái đã có ở
thangvv111/uatorigin/main@30a7b83(đo 13/09/2026), ⛔ không đòi thêm gì: env/compose.yml— bảngAD-26thành cấu hình:db·db-sandbox·db-migrate·db-seed·db-seed-uat· bốn service;tool/uat-env.sh upchờ health theoenv/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.shgen/check/record. - CI self-hosted
.github/workflows/ci.ymltênCI(AD-24mục 7; runneroapi-uat-01trên Windows) +issue-sync.yml. env/seed/*.sqlidempotent 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.shkiểm cặp khớp (AD-27).- ⛔ Không sửa hồ sơ 7 change đã archive; việc gắn
story: E-001/0.6vào 6link.yaml(một dòng metadata, không chạm nội dung) là của lànuat—OAPI-231mục 2, PO chốt được sửa khoá này. - Xong = hàng
0.6có trong bảng,check-thu-tu.shxanh, và sau mục 2 củauat:bash tool/epic-status.sh E-001liệtE-001/0.6 uatvới các change archive.
⟨CẦN NGƯỜI VIẾT — agent làn uat viết khi đóng change⟩