13 KiB
GATE-K4C: PASS — 0 blocker · 1 CLARIFY (flake 1/11 trên test CŨ, không tái lập) · 3 MINOR
GATE-K4C — DONE
Reviewer adversarial gate, wave K4c (chốt cụm-2), S166. Ghi TỪNG MỤC ngay khi đo xong (chống #53: file luôn hợp-lệ giữa chừng). Verdict CHỈ hợp-lệ khi có dòng
ENDcuối file. Chưa có END = lượt chạy chưa xong, ĐỪNG đọc verdict.
0. Bối cảnh nhận từ lead
- Diff CHƯA commit, lead khai HEAD
ce35f90, 2 file:fe-user/src/pages/pe/WorkflowMatrixViewPage.tsx(+68/−5) — WfView lọc nhóm client-sidetests/SolutionErp.Infrastructure.Tests/Application/ContractSigningPlanCrudTests.cs(+1 Fact) — MAJOR-A/deletedlọc server-side
- Lane artifacts:
sub-implfe-k4c.md(DONE, 21/21 + 2 control-âm, 2 FLAG minor) ·sub-test-k4a.md§MAJOR-A (8/8, RED→GREEN 2 chiều)
1. Đo — điền dần
1.0 Phạm vi diff (đo, không tin khai)
- HEAD thật lúc review =
2023f41(wal: flush 20260801T1045), KHÔNG phảice35f90như lead khai —ce35f90là commit code cuối (K5), sau đó có 1 commitwal:chèn vào. Không ảnh hưởng nội dung diff (2 wal-commit không đụngsrc|tests|fe-*), nhưng nhãn base trong spec lead SAI 1 nấc. git diff --numstat(working tree vs HEAD), lọc cây code:fe-user/src/pages/pe/WorkflowMatrixViewPage.tsx+68 / −5 ✅ khớp khai.tests/.../ContractSigningPlanCrudTests.cs+106 / −2 (lead khai "+1 Fact" — đúng về SỐ FACT, nhưng LOC lớn hơn vì còn sửa helperCreateAsync+ thêm helperListDeletedAsync; không phải chỉ-thêm-1-Fact thuần).
git status --porcelain -- src tests fe-user fe-admin= đúng 2 dòngMtrên, 0 dòng??⇒ cây code KHÔNG phình.??toàn repo = 1 file duy nhất: chính artifact này (sub-reviewer-gate-k4c.md). 8 fileMcòn lại đều là agent-memory / run-artifact /.session-counter.json— ngoài cây code, không tính scope drift.- fe-admin: 0 diff ✅ (không có dòng nào của
fe-admintrong git status).
1.1 SOI-1 — lọc client-side WfView
- Vị ngữ:
allWorkflows.filter(wf => wf.code === khkkGroupWorkflowCode(khkkGroup)),khkkGroupWorkflowCode = n => \KHKK-N${n}`(fe-user/src/types/khkk.ts:163). Khớp **BE**: seederDbInitializer.cs:609var code = $"KHKK-N{n}"✅ và rào (v)ContractSigningPlanFeatures.cs:420regex^KHKK-N([1-8])$` ✅. Cả 2 số dòng trích trong comment đều ĐÚNG (đã grep, không trượt). AwDefinitionDto.codetồn tại (fe-user/src/types/approvalWorkflowV2.ts:41) ✅. Lọc theocode(không theoid/name) ⇒ giữ đủ mọi version cùng nhóm — đúng như comment khai, và đúng vớihistory: AwDefinitionDto[].- Guard
/^[1-8]$/— ca"03": regex TRƯỢT (đòi đúng 1 ký tự) ⇒khkkGroup = null⇒ bỏ lọc, hiện tất cả, KHÔNG bịNumber("03")=3nuốt thành nhóm 3. Lane khai đã xử = ĐÚNG, verify độc lập xong.- Đồng nhất 3 chỗ:
KhkkListPage.tsx:80vàKhkkCreatePage.tsx:67dùng y hệt/^[1-8]$/⇒ không đẻ luật thứ 2 cho cùng một tham số. - Đường sinh link không bao giờ đẻ
"03":Layout.tsx:216lấygrouptừ capture của regex^Khkk_G([1-8])_(...)$⇒ luôn 1 chữ số.
- Đồng nhất 3 chỗ:
- Regression type ≠ 10:
typeInt === 10 &&là liên từ ĐẦU TIÊN ⇒ type 1/2/3 (PE + Contract) luôn chokhkkGroup = null⇒workflows === allWorkflows,actions = undefined(PageHeader có{actions && ...}nên render y hệt trước). Mọi consumer downstream (workflows.length === 0,workflows.map) không đổi ⇒ 0 đổi hành vi cho matrix PE/HĐ. ✅ - Rỗng-vì-lọc ⟂ rỗng-thật: tách 2 nhánh THẬT (
:157-165), nhánh lọc còn nói thêm "loại phiếu này có N quy trình ở nhóm khác" — bảo vệ đúng ca người nhóm N5 tưởng cả module chưa cấu hình. Điều kiệnallWorkflows.length > 0 &&là phép so sánh (không phải{0 && ...}) ⇒ không rò số0ra UI. ✅
1.2 SOI-2 — queryKey KHÔNG đổi: đúng hay rò cache?
Phán: ĐÚNG, không rò. 3 vế:
- Payload không phụ thuộc
group: query gọi/approval-workflows-v2?applicableType=10&isUserSelectable=true(:63-71) — BEApprovalWorkflowsV2Controller.cs:24-26chỉ nhậnapplicableType+isUserSelectable, không có trụcgroup⇒ 8 nhóm dùng chung ĐÚNG một payload. ThêmgroupvàoqueryKey= 8 lần gọi mạng cho cùng một body + 8 ô cache trùng nội dung. Bỏ ra là đúng. - Không stale khi đổi leaf:
khkkGroup/workflowslà giá trị dẫn xuất trong render từuseSearchParams(), KHÔNG phảiuseStatekhởi tạo-một-lần ⇒ điều hướng leaf→leaf (cùng route, React Router không remount) vẫn tính lại ngay. Đây đúng là cái bẫy "vá-5" đã cắnKhkkListPageở K4b, lần này tránh được by-construction, không cầnuseEffect. - Cache dùng chung là ĐIỀU MONG MUỐN: nhảy N1→N2 không refetch, lọc lại tại chỗ.
Cái giá (đã cân, chấp nhận): tải trọn bộ type-10 rồi mới cắt — với 8 workflow seed thì không đáng kể.
1.3 SOI-4 + SOI-5 — build + cây
| Phép đo | Lệnh | Kết quả |
|---|---|---|
| tsc fe-user | npx tsc -b (reviewer tự chạy, KHÔNG đọc panel IDE) |
exit 0, 0 dòng lỗi ✅ |
| tsc fe-admin | npx tsc -b --force |
exit 0 ✅ |
| fe-admin diff | git status --porcelain -- fe-admin |
rỗng ✅ |
| Cây phình | git status --porcelain -- src tests fe-user fe-admin | grep '^??' |
0 hit ✅ (control dương: cùng lệnh bỏ path-filter ra 1 hit = artifact này ⇒ phép đo KHÔNG rỗng) |
| Bundle prod fe-user | npm run build (reviewer tự chạy) |
exit 0 — index-DBtbmn5s.js 1,643.96 kB / css 92.71 kB, trùng từng chữ số với con số lane khai ⇒ claim đo-lường của lane KHÔNG phải số bịa |
| Suite BE | dotnet test SolutionErp.slnx (reviewer tự chạy) |
622 PASS / 0 FAIL (45 Domain + 577 Infra) — khớp con số lead báo |
1.4 SOI-3 — test MAJOR-A có RĂNG THẬT không?
Phán: CÓ RĂNG. Probe "đổi test-input sang null" là tương-đương HỢP LỆ ở ca này — nhưng vì lý do đo được, không phải vì nghe hợp lý.
- Kiểm tương-đương trên MÃ THẬT (không tin lập luận suông): trong
ListDeletedContractSigningPlansQueryHandler,request.ApprovalGroupchỉ xuất hiện đúng 1 chỗ —:896-897if (request.ApprovalGroup is not null) q = q.Where(p => p.ApprovalGroup == request.ApprovalGroup). Xoá đúng khối đó thìHandle(g)đồng nhấtHandle(null)với MỌIg. Vậy truyềnnulllà mô phỏng CHÍNH XÁC thế-giới-bỏ-param, không phải xấp xỉ. (Nếu tham số được dùng ở ≥2 chỗ thì lập luận này sập — nên phải grep trước khi chấp nhận, và tôi đã grep.) - Cặp (null ⇒ 2) ∧ (group=1 ⇒ 1) nằm cùng một Fact, cùng một fixture ⇒ 2 thế giới đặt cạnh nhau trong 1 lượt chạy; không cần sửa prod-code để chứng.
Totalkhông phải assert trang trí:ProjectPagedAsync:790var total = await q.CountAsync(ct)chạy trên chínhqđãWhere, trướcSkip/Take(:791-792). Nên "lọc trong bộ nhớ sau khi lấy trang" sẽ choItemsgiống hệt màTotalsai — assertTotalphân biệt được đúng thứ nó khai là phân biệt.- Đã giết các mutant hiển nhiên: "luôn trả phiếu đầu" (vế group=3 ⇒ đúng
planG3) · "không khớp thì bỏ lọc" (vế group=7 ⇒ rỗng +Total=0) · "đẩy vế lọc vào trongif (!admin)" (vế 5 Admin ⇒ vẫn lọc). Vế IDOR ở:884-893đúng là NGOÀI vế lọc như comment khai ✅. - 2 sanity trước phép đo (2 row thật
IsDeleted∧ thật khác nhóm) là thứ khiến "lọc ra 1 phiếu" không thể đúng-vì-lý-do-khác. Đúng bài chống chân-lý-rỗng. - Giới hạn còn lại (khai thật, không phải blocker): probe này KHÔNG bao mutant "lọc đúng hình dạng nhưng sai CỘT" — 2 phiếu trong fixture khác nhau ở nhiều trường (Id,
MaKeHoach, thời điểm), nên một vếWherebám trường tương quan khác cũng qua được. Mutant kiểu đó phi thực tế ở đây; ghi lại để lượt sau đừng đọc "RED→GREEN" thành "mutation-tested toàn phần". - Trích dẫn số dòng trong test (
:896-897,:884-893,:401-404,:420-424,:756-757) — grep lại từng cái, ĐÚNG 5/5, không trượt.
1.5 🔴 FLAKE quan sát được — 1 FAIL / 11 lượt chạy lớp test này
- Lượt chạy đầu tiên của tôi (
dotnet test --filter ContractSigningPlanCrudTests -v q, chạy đồng thời vớinpx tsc -b, 13 s):Failed: 1, Passed: 7— tên test đỏ =DeleteContractSigningPlan_SoftDeletes_KeepsChangelog_ThenCreateAgainSucceeds(test CŨ, không phải Fact mới). Thông điệp assert KHÔNG bắt được vì-v qnuốt mất — khai thẳng chỗ hổng của phép đo, không đoán bù. - Không tái lập: cùng lớp chạy lại 10 lượt (3 lượt rảnh + 6 lượt dưới tải
tsc -b --forcefe-admin + 1 lượt chạy riêng chính test đó) ⇒ 10/10 XANH;dotnet test SolutionErp.slnx622/0. - Truy nguyên cơ chế lây nhiễm (để biết CÓ THỂ do diff không): fixture
KhkkFixturemởSqliteConnection("DataSource=:memory:")riêng cho từng test ⇒ không có DB dùng chung;ContractSigningPlanCodeGeneratorkhông có static nào (đọc trọn file), sequence nằm trong bảng của chính DB đó; xUnit 2.9.3 ⇒ test trong CÙNG một lớp chạy TUẦN TỰ. Thay đổi của diff lên test cũ = helperCreateAsyncthêm tham số mặc địnhnull, mà handler quynull → DefaultApprovalGroup = 1(:207,:399) — đúng bằng hành vi trước diff. ⇒ không tìm được đường nào diff gây ra, nhưng cũng KHÔNG có bằng chứng lớp này vốn đã flake từ trước. - Xử trí đề nghị: không chặn commit (mọi phép đo đầy đủ đều xanh, 622/0 hai nguồn độc lập), nhưng ghi vào WAL/handoff làm mốc: nếu CI đỏ đúng test này lần nào nữa thì đã có mốc "S166 gate-K4c: 1/11" để không phải điều tra lại từ đầu. Đừng viết vào STATUS là "8/8 tuyệt đối" — con số trung thực là 8/8 ở 11/12 lượt quan sát.
2. Kết luận
VERDICT: PASS — 0 blocker. 1 CLARIFY (flake trên) + 3 MINOR dưới.
| Hạng mục | Trạng thái | Ghi chú |
|---|---|---|
| 1. Wire/feature claim | PASS | Không có // Mock / alert( / TODO-wire trong diff; lọc bám code khớp seeder BE + rào BE cùng khuôn |
| 2. Schema | N-A | 0 migration, 0 entity, 0 file Migrations/ trong diff |
| 3. Security | PASS | 0 đổi authz. Trang chỉ thu hẹp danh sách hiển thị; endpoint /deleted lọc nhóm SAU khối IDOR (:884-893) ⇒ lọc chỉ hẹp đi, không nới quyền. Không có key policy mới ⇒ gotcha #85 không đụng |
| 4. Code quality | PASS | tsc fe-user 0 · tsc fe-admin 0 · npm run build fe-user 0 · dotnet test 622/0 · 0 file ngoài phạm vi · 0 --no-verify |
| 5. Test coverage | PASS | +1 Fact có răng đúng chuẩn "bug → test-before-fix" (RED đo được trước, GREEN sau); suite 569→577 Infra |
| 6. Writing quality | N-A | Diff không có nội dung hướng ra ngoài (comment nội bộ + chuỗi UI tiếng Việt; đã đọc từng chuỗi mới: đủ dấu, câu hoàn chỉnh) |
MINOR-1 — ListDeletedAsync (ContractSigningPlanCrudTests.cs:216-224) không dispose TestApplicationDbContext (var db = f.NewDb(actor); rồi trả Task luôn), trong khi 3 helper anh em đều await using var db. Không sai kết quả (connection do fixture giữ, IDisposable của fixture đóng), nhưng lệch khuôn của chính file. Chuẩn nhận: đổi thành async + await using, hoặc ghi 1 dòng lý do cố ý.
MINOR-2 — link "Xem tất cả nhóm" dựng bằng `${pathname}?type=${typeInt}` ⇒ rơi mọi query khác ngoài type. Hôm nay vô hại (trang chỉ đọc type + group), nhưng thêm tham số thứ 3 sau này sẽ mất im lặng. Chuẩn nhận: dựng từ new URLSearchParams(searchParams) rồi delete('group').
MINOR-3 — bấm "Xem tất cả nhóm" xong URL còn ?type=10 ⇒ không leaf nào trong sidebar khớp (vì group là danh-tính điều hướng, Layout.tsx:201-221) ⇒ mất highlight menu. Hành vi chấp nhận được cho một nút "thoát lọc", nêu để không ai đọc thành lỗi.
Không có Critical. Không có Major.
Xác nhận 2 FLAG của lane là ĐÚNG, không phải cách nói giảm: (a) fe-admin không có WorkflowMatrixViewPage — git status -- fe-admin rỗng và App.tsx bên admin không có route matrix ⇒ lệch cố-ý, đã khai từ K4a; (b) Admin đổi Code trong Designer sẽ làm leaf nhóm ra trang rỗng — nhưng có báo rõ nhờ câu rỗng-vì-lọc mới, và BE cũng khoá cùng khuôn ^KHKK-N([1-8])$ (:420) nên phiếu không pin nhầm được. Hai tầng nhất quán, không phải FE tự chế luật riêng.
GATE-K4C-END