Báo cáo sai lệch — batch/job-daily-warning-out-date — vòng 2
0. Kết luận nhanh
- Điều kiện đạt:
G1 ✅ G2 ✅ G3 ✅ G4 — G5 ✅ G6 ✅→ CHỜ KIỂM SONAR - Trạng thái: SẴN SÀNG CHO NGƯỜI XEM
- Tầng rủi ro: A → người cần đọc: spec + toàn bộ diff + chạy GM-01
- Số quy tắc: 8 · PASS 4 (R-03, R-04, R-05, R-08) · DEVIATION có chủ đích 2 (R-01 D-01, R-02 D-06/D-07) · DEVIATION KHÔNG chủ đích 0 · UNVERIFIED 2 (R-06 ngưỡng IN D-08, R-07 giờ +7/
KeyHoldertrên MSSQL) - Coverage thấp nhất: F-01 95.2% dòng / 90.0% nhánh
- Finding logic: vòng 1 MINOR 2; vòng 2 (thu hẹp) MINOR 2 (vẫn là 2 finding cũ) — không cần xác minh
- Nghi vấn bug code cũ (mục 3c): 4 (CAO 0) · Quyết định tự chốt cần xác nhận (mục 3d): 8
- Chạy bằng:
/mig-auto· Số vòng: 2 / lần sửa 1/3
1. Điều kiện đạt
| Mã | Mục tiêu | Kết quả | Bằng chứng |
|---|---|---|---|
| G1 | mvnw verify |
✅ | verify/round-2/build.md (716 test) |
| G2 | Test khớp test-plan | ✅ | verify/round-2/tc-map.md (14/14), oracle.md |
| G3 | Coverage | ✅ | verify/round-2/coverage.md |
| G4 | Sonar | CHỜ NGƯỜI | sonar-ide.md |
| G5 | Finding CRITICAL/MAJOR | ✅ | verify/round-2/review-logic.md (thu hẹp, 0 CRITICAL/MAJOR) |
| G6 | Lách cổng | ✅ | verify/round-2/gates.md |
Không chặn nhưng cần người: Integration test CHƯA CHẠY · Golden master CHƯA CHẠY (GM-01 — SQL native, khoá Redis, KeyHolder, giờ ghi chỉ khoá bằng mock, K-13)
2. Ma trận truy vết
| Quy tắc | Tầng | Code mới | Test | Golden | Kết luận |
|---|---|---|---|---|---|
| R-01 | A | BatchExpiryWarningScheduler, application.yml |
TC-01 | — | DEVIATION có chủ đích (D-01: cờ riêng mặc định tắt) |
| R-02 | A | BatchExpiryWarningScheduler |
TC-02, 03 | — | DEVIATION có chủ đích (D-06 bỏ whitelist IP; D-07 nhánh race theo starter) |
| R-03 | A | BatchExpiryWarningService, BatchExpiryReader |
TC-04, 06 | GM-01 | PASS (giữ D-03); SQL mock |
| R-04 | A | BatchExpiryReader |
TC-05 | GM-01 | PASS; SQL mock |
| R-05 | A | BatchExpiryWarningService |
TC-06 | — | PASS |
| R-06 | A | BatchExpiryWarningService, CompanyUserRelationalAdapter |
TC-07, 08 | GM-01 | PASS (giữ D-04, D-08); UNVERIFIED ngưỡng IN |
| R-07 | A | BatchExpiryNotificationPlan, NotificationRelationalAdapter |
TC-09..12 | GM-01 | PASS (giữ D-05); UNVERIFIED giờ +7/KeyHolder trên MSSQL; F-logic-2 |
| R-08 | A | BatchExpiryWarningService.warn @Transactional |
TC-13, 14 (+EXTRA 1, 2) | — | PASS |
3. Người cần xem
| # | Vị trí | Vì sao cần người xem |
|---|---|---|
| 1 | Toàn bộ diff (20 file, verify/round-2/changed-files.md) |
Tầng A: job ghi dữ liệu hằng ngày cho mọi công ty |
| 2 | NotificationRelationalAdapter |
Ghi thẳng bảng notification/notification_user của monolith bằng JDBC (D-02); giờ Timestamp.valueOf(giờ +7), KeyHolder.getKey() trên mssql-jdbc — UNVERIFIED, chạy GM-01 |
| 3 | application.yml app.scheduler.batch-expiry-warning.enabled |
Mặc định tắt; chỉ bật khi đã tắt job bên monolith, hoặc khi hai bên chung Redis (cùng khoá job-lock-daily-warning) — khác Redis thì sinh thông báo trùng |
| 4 | F-logic-1 (MINOR) | D-08 mô tả sai: code mới cũng qua Hibernate có in_clause_parameter_padding → ngưỡng lỗi IN như cũ (không phải "mới chạy được 2049–2099 công ty") |
| 5 | F-logic-2 (MINOR) | Một now cho mọi dòng, lấy trước các truy vấn — giờ ghi sớm hơn cũ vài giây; TC-10 đang khoá điều này |
| 6 | G4 | Chưa kiểm Sonar |
3b. Phân tích luồng sau chuyển
Chi tiết: verify/round-1/flow-analysis.md (vòng 2: verify/round-2/flow-analysis.md, không đổi)
| Luồng | Tên | Số bước KHỚP / KHÁC / CHỈ CÓ Ở CŨ / CHỈ CÓ Ở MỚI | Lệch luồng đã duyệt (spec mục 6) | Điểm dev cần chú ý |
|---|---|---|---|---|
| F-01 | Cảnh báo lô sắp/đã hết hạn | 17 bước: KHỚP 12 (gồm bước 3 khớp về kết quả và bước 12 khớp nhưng D-08 mô tả sai, F-logic-1) / KHÁC có chủ đích 3 (D-01, D-06, D-02) / KHÁC một phần 2 (bước 6: nhánh race theo D-07; bước 16: giờ ghi, F-logic-2) / CHỈ CÓ Ở CŨ 1 (whitelist clientCanDoSchedule, D-06 — bước 4 và 5 cùng thuộc D-06) / CHỈ CÓ Ở MỚI 0 |
không | JDBC phải dùng DataSource @Primary để chung transaction; KeyHolder.getKey() trên mssql-jdbc; giờ +7 ghi bằng Timestamp.valueOf (UNVERIFIED); một now cho mọi dòng; phải bật cả hai cờ; dùng chung khoá Redis với monolith (khác Redis thì sinh thông báo trùng) |
3c. Nghi vấn bug / sai ở code cũ (đã GIỮ nguyên khi chuyển)
B-01 Một câu IN cho mọi công ty — nhiều công ty thì cả job lỗi
- Mức nghi ngờ: TRUNG BÌNH
- Ở đâu:
backend:repository/CompanyUserRepository.java:49,BMS:492→CompanyUserRelationalAdapter.findByCompanyIds - Code cũ đang làm gì: đọc
company_usercủa mọi công ty có lô cần cảnh báo bằng một câuIN, không chia lô. - Ví dụ cụ thể: 2500 công ty có lô sắp/đã hết hạn → vượt 2100 tham số SQL Server → lỗi → rollback, không công ty nào nhận thông báo, ngày nào cũng vậy. Có padding lũy thừa 2 nên từ khoảng 2049 công ty đã lỗi (cả cũ và mới — F-logic-1).
- Vì sao nghi là sai: số khách hàng tăng thì job lặng lẽ ngừng hoạt động.
- Ảnh hưởng nếu đúng là bug: mất toàn bộ cảnh báo hết hạn.
- Đã xử lý khi chuyển: GIỮ. Test khoá:
TC-08(CompanyUserRelationalAdapterTest#findByCompanyIds). - Nếu muốn sửa: chia
companyIdsthành lô ≤ 1000 trong adapter (K-27); đổi TC-08. - Liên quan: D-08, R-06
B-02 Gửi cho cả user đã ngừng hoạt động
- Mức nghi ngờ: THẤP
- Ở đâu:
backend:repository/CompanyUserRepository.java:49→CompanyUserRelationalAdapter - Code cũ đang làm gì: lấy mọi dòng
company_usercủa công ty, không lọcstatus. - Ví dụ cụ thể: công ty 10 có 3 user, 1 đã nghỉ → cả 3 nhận thông báo mỗi loại.
- Vì sao nghi là sai: thông báo cho người không còn làm việc.
- Ảnh hưởng nếu đúng là bug: dữ liệu thừa, không ai đọc.
- Đã xử lý khi chuyển: GIỮ. Test khoá:
TC-08,TC-07. - Nếu muốn sửa: thêm điều kiện
statushoạt động vào SQL adapter; đổi TC-08. - Liên quan: D-04, R-06
B-03 Thông báo lặp lại mỗi ngày, mỗi user một bản ghi
- Mức nghi ngờ: THẤP
- Ở đâu:
backend:service/impl/NotificationManagementServiceImpl.java:166-188→BatchExpiryNotificationPlan,NotificationRelationalAdapter - Code cũ đang làm gì: mỗi ngày tạo lại thông báo, không chống trùng; mỗi user một dòng
notificationriêng. - Ví dụ cụ thể: lô hết hạn còn tồn 30 ngày, công ty 50 user → mỗi user 30 thông báo "đã hết hạn"; mỗi ngày 50 dòng
notificationgiống nhau. - Vì sao nghi là sai: phình bảng, làm phiền.
- Ảnh hưởng nếu đúng là bug: hiệu năng bảng
notification, trải nghiệm. - Đã xử lý khi chuyển: GIỮ. Test khoá:
TC-09,TC-12. - Nếu muốn sửa: cần quyết định nghiệp vụ (một
notification+ nhiềunotification_user, hoặc chống trùng theo ngày). - Liên quan: D-05, R-07
B-04 "Hôm nay" theo giờ JVM nhưng mốc cảnh báo theo giờ DB
- Mức nghi ngờ: THẤP
- Ở đâu:
backend:repository/BatchesRepository.java:81-122→BatchExpiryReader,BatchExpiryWarningService - Code cũ đang làm gì:
?1 = today(JVM) so vớiexp_date; điều kiện mốc cảnh báo dùngGETDATE()của DB. - Ví dụ cụ thể: JVM +7, DB UTC; lô HSD
2026-11-10 08:00, cảnh báo 4 ngày (mốc06/11 08:00). Job 06/11 08:07 (+7),GETDATE()= 01:07 → chưa qua mốc, tới 07/11 mới cảnh báo; DB +7 thì cảnh báo ngay 06/11. - Vì sao nghi là sai: hai nguồn giờ; kết quả phụ thuộc múi giờ server DB.
- Ảnh hưởng nếu đúng là bug: cảnh báo trễ một ngày.
- Đã xử lý khi chuyển: GIỮ (giữ
GETDATE()trong SQL). Test khoá:TC-04,TC-06. - Nếu muốn sửa: truyền mốc thời gian từ
ClockthayGETDATE(); đổi TC-04. - Liên quan: D-03, R-03
3d. Quyết định AI tự chốt và thay đổi tự động (cần người xác nhận)
| # | Nguồn | AI đã làm gì | Khác code cũ ở điểm nào (input → cũ / mới) | Người cần xác nhận gì |
|---|---|---|---|---|
| 1 | D-01 (ADR BẮT KHÁC) | Cờ riêng app.scheduler.batch-expiry-warning.enabled mặc định false |
deploy với SCHEDULER_ENABLED=true → cũ chạy / mới không chạy cho tới khi bật cờ riêng |
Khi nào bật; chung Redis với monolith hay tắt job bên monolith |
| 2 | D-02 (PHỤ THUỘC NGOÀI) | Port NotificationPort, CompanyUserPort + adapter SQL native ghi/đọc bảng monolith |
cùng cột/giá trị; thứ tự id giữa các công ty có thể khác (HashMap → LinkedHashMap) | Chấp nhận ghi thẳng bảng của context khác |
| 3 | D-06 (PHỤ THUỘC NGOÀI) | Bỏ whitelist IP clientCanDoSchedule |
node không có trong whitelist → cũ không chạy / mới tranh khoá | Chấp nhận |
| 4 | D-07 (MƠ HỒ) | Không nhả khoá; nhánh race (SETNX hỏng + GET null) theo starter | khoá hết hạn đúng lúc → cũ chạy / mới không chạy | Chấp nhận (UNVERIFIED) |
| 5 | F-logic-1 (MINOR) — D-08 mô tả sai | Không sửa code; không sửa decisions.md (sau bước kiểm spec không được sửa) |
không khác: cả hai cùng padding IN, cùng ngưỡng lỗi | Sửa câu cuối "Ví dụ cụ thể" của D-08 khi rà soát |
| 6 | F-logic-2 (MINOR) | Một now từ Clock đầu warn() cho mọi dòng |
create_time cũ ≈ sau các truy vấn, mỗi dòng khác vài ms / mới cùng một giá trị, sớm hơn vài giây |
Chấp nhận, hoặc chỉ thị lấy now ngay trước createAll |
| 7 | spec-review | Tầng A: 2 lần review (lần 1 có CRITICAL F-spec-1 giờ ghi +7); đổi R-01, R-03, R-04, R-06, R-07, D-01, D-03, D-08 (mới), mục 3/5/7 | — | Đọc kỹ R-07 (giờ +7) và D-08 |
| 8 | Hạ tầng | NotificationRelationalAdapter tự tạo NamedParameterJdbcTemplate từ DataSource @Primary (chung transaction JPA) |
— | Xem diff |
4. Finding đã xác nhận
Không có CRITICAL/MAJOR. MINOR F-logic-1, F-logic-2 — ở mục 3/3d.
5. Finding bị loại
Không có.
6. Xử lý của người
| Vấn đề | Cổng | Chỉ thị |
|---|---|---|
| Chưa kiểm Sonar | G4 |
7. So với vòng trước
| Vấn đề | Vòng trước | Vòng này |
|---|---|---|
| TC-10 nới (G2) | MỚI | ĐÃ HẾT |
| F-logic-1 (MINOR) D-08 mô tả sai | MỚI | CÒN |
F-logic-2 (MINOR) một now |
MỚI | CÒN |
8. Thay đổi trong repo đích
20 file (verify/round-2/changed-files.md): mới scheduler, service, plan, port company/notification, BatchExpiryReader, 2 adapter + 6 test; sửa BatchRepository, BatchRelationalRepository, application.yml.
9. Đề xuất bổ sung kinh nghiệm
| Đề xuất | Đưa vào đâu |
|---|---|
Job ghi bảng monolith bằng JDBC: Timestamp.valueOf(giờ treo tường +7) thay cho jdbc.time_zone của Hibernate; NamedParameterJdbcTemplate tạo từ DataSource @Primary để chung transaction |
target-cheatsheet.md; lessons.md 1b |
Repo đích cũng bật in_clause_parameter_padding → native query qua EntityManager cũng độn IN như monolith |
legacy-facts.md / target-cheatsheet.md |
Test service: assert toàn bộ danh sách (containsExactly) thay vì hasSize + vài trường — G2 bắt "nới" |
lessons.md 1c |