diff --git a/.claude/WAL.md b/.claude/WAL.md index cc121f0..5b5f0d4 100644 --- a/.claude/WAL.md +++ b/.claude/WAL.md @@ -27,12 +27,17 @@ chain: tam-giác · A1 8+48 · A5 restart×2-THẬT PID-7020→220→6636 đứng-im + revoker-control 29/377 · bundle ROTATE ×2 marker-differential · smoke+bind-400; treo kế thừa: bảng 0-phiếu = chân-lý-rỗng lọc-nhóm, đo lại khi K8 đẻ phiếu — sub-cicd-verify-k4ab.md) -[ ] K5→K4c→K6→K7→K8-prep theo build-order B3 + vá-số spec-cụm3 (chi tiết PAUSE-3 mục (2): K5 groups - cây 8-sub-folder + forward-provision lines[].contractId ∪ pe.contractId · K4c WfView lọc Code - [+nợ gate-K4b: MAJOR-A test /deleted+group · MAJOR-C WfView đọc group] · K6 blacklist 12-key + - fe-user-0-diff · K7 hội-2-khóa + grant Contracts.Create [câu III default Drafter+Procurement+Admin] - + Line.ContractId-ghi-luôn · K8 form-14-người fail-closed + gói SOLUTION-PE-{n} + QĐ9 đo-2-nhánh - + ZZTEST + rollback-theo-ID) +[x] K5+K4c SHIPPED @S166 — ce35f90 (cây 8-ngăn, gate PASS-0-blocker, Σ-bằng-CẤU-TRÚC) + e202393 + (WfView lọc nhóm + test MAJOR-A RED→GREEN, gate PASS; MAJOR-C đóng) PUSH 88e7ecf..e202393 · + suite 622/0 · cicd-K5K4C ĐANG nền (sub-cicd-verify-k5-k4c.md). Nợ nhẹ mang theo: gate-K4c + 3-MINOR (helper-dispose · link-mất-query · highlight) · K5 MINOR-3-"SẮP" {UX chờ anh} · + flaky-1/12 DeleteCSP_SoftDeletes (mốc cho CI) · UAT-NÓI-TRƯỚC: prod 0 phiếu ⇒ 8 ngăn "SẮP" + rỗng mọi gói = ĐÚNG SPEC +[ ] K6→K7→K8-prep theo build-order B3 + vá-số spec-cụm3 (chi tiết PAUSE-3 mục (2): K6 blacklist + 12-key fe-admin + acceptance liệt-đúng-N-lá + fe-user-0-diff + comment :179 vá-14 lineage-2-đảo · + K7 hội-2-khóa + grant Contracts.Create [câu III default Drafter+Procurement+Admin] + + Line.ContractId-ghi-luôn [nợ K5 FLAG-1 chỗ-cắm-1-dòng] · K8 form-14-người fail-closed + gói + SOLUTION-PE-{n} + QĐ9 đo-2-nhánh + ZZTEST + rollback-theo-ID) [ ] closeout-nợ: diary vai (C2 — checklist @sub-lead-gap khi nó trả) · tally #53 CHỐT (S166 tới giờ: cicd×2 + reviewer×2 + stale×2 + gap×1 + laneBE×1 = 8 lượt #53-return / 0 mất công, artifact cứu hết) · nhãn D MIND · STATUS row S163-S166 · spec-cụm1 vá-3 đính-chính PUT-Conflict · lỗi-tự-bắt 607/609 diff --git a/.claude/agent-memory/reviewer/MEMORY.md b/.claude/agent-memory/reviewer/MEMORY.md index d611a12..5b5cee2 100644 --- a/.claude/agent-memory/reviewer/MEMORY.md +++ b/.claude/agent-memory/reviewer/MEMORY.md @@ -4,6 +4,8 @@ - **S166 (08-01) gate K5 cây GĐ2 mở 8 sub-folder nhóm — PASS 0 blocker (1 CLARIFY + 5 MINOR):** lõi hiếm-đúng: bất-biến Σ do **CẤU TRÚC** (1 mảng nguồn `khkkItems` → 1 phép phân hoạch if/else TOÀN PHẦN → `stage.leaves` vẫn là chỗ đếm duy nhất `stageCount():96`) nên không phụ thuộc ai nhớ cộng; route `?group=` nối đủ 3 chặng (FE `:79-80` → param `:124` → BE `Where(:757)`), **khác hẳn tham-số-trang-trí K4a**. **Bài thu:** ① 🔴 **phép-đo-rẻ-nhất nằm ở file RÁC của lane khác** — `menus.json` untracked ở gốc repo (sản phẩm curl của cicd-monitor) chính là payload `/api/menus/me` LIVE ⇒ đối chiếu nhãn cây suy-ra ⟂ nhãn sidebar thật **8/8 khớp từng ký tự + `displayLabel` đều null**, thứ mà đọc code không bao giờ chứng được. Cùng file đó là **rào commit** (`git add -A` sẽ nuốt 44KB rác) ⇒ 1 vật vừa là bằng-chứng vừa là rủi-ro, đừng chỉ báo 1 vế. ② **chân-lý-rỗng phải khai là rỗng**: curl prod `total=0` phiếu KHKK ⇒ phân bố `approvalGroup` = `{}`, KHÔNG dùng để kết luận gì; nhưng cùng con số ấy đẻ ra sự-thật dùng được cho UAT (ngày đầu mọi gói hiện **8 dòng "SẮP" rỗng** — đúng spec, dễ bị đọc thành hỏng). ③ **tương-đương-từng-vế > "cùng tập cùng thứ tự"**: vòng lặp GĐ3/4 bị viết lại ⇒ lập bảng 4 vế (tập id · thứ tự = `Set` giữ thứ-tự-chèn · khử trùng · dòng-phụ `find` = phiếu ĐẦU) mới dám phán 0-regression; thân stage4 **0 tham chiếu `p`** là lý do cấu-trúc khiến rewrite an toàn. ④ **tiền-lệ phải đo ở BASE, không đo ở cây đã sửa**: `git show 88e7ecf: | grep -A6 '` lồng trong `` là **khuôn MỚI** (nested-interactive a11y), lane tưởng đang bám tiền lệ. ⑤ **"15/15 OK" chết theo phiên**: script `new Function` không commit + FE **0 hạ tầng test** (`package.json` chỉ `dev|build|lint|preview`) + chuông Σ là `import.meta.env.DEV` ⇒ prod bị strip ⇒ 0 lưới an toàn; đừng đọc số-test-ad-hoc thành đã-phủ. ⑥ 2 nghĩa của 1 dòng checklist ("GĐ3/4 by c.type") — lead và lane **cùng chọn 1 nghĩa không chứng minh nghĩa đó đúng ý owner** ⇒ ghim cách đọc vào commit. Tag `[s166, bang-chung-o-file-rac, chan-ly-rong, tuong-duong-tung-ve, tien-le-do-o-base]` **Evidence:** `runs/2026-07-31-S164-4gd-khkk-fanout/sub-reviewer-gate-k5.md` +- **S166 (08-01) gate K4c chốt cụm-2 (FE lọc nhóm WfView + 1 Fact test) — PASS 0 blocker (1 CLARIFY + 3 MINOR):** ① 🔴 **flake chỉ lộ ở LƯỢT ĐẦU, và chính tôi giết mất chứng cớ**: chạy `dotnet test -v q` SONG SONG với `tsc` ⇒ 1 test CŨ đỏ (`Failed 1/8`) rồi 10/10 xanh sau đó + suite 622/0 ⇒ `-v q` **nuốt thông điệp assert** ⇒ có tên test mà không có lý do. Bài: lượt chạy ĐẦU dùng verbosity thường, `-v q` để dành cho lượt lặp. Truy nhiễm phải đo 3 vế mới dám nói "không do diff": fixture `:memory:` per-test · codegen **0 static** · xUnit 2.9.3 same-class = tuần tự. Báo "8/8 ở 11/12 lượt", KHÔNG báo 8/8 tuyệt đối. ② **probe RED bằng đổi TEST-INPUT (không chạm prod) là hợp lệ CHỈ KHI tham số xuất hiện ĐÚNG 1 CHỖ trong handler** — grep xong mới được chấp nhận; ≥2 chỗ là lập luận sập. ③ `Total` là dấu-vân-tay lọc-SERVER **chỉ khi** `CountAsync` chạy trên `q` SAU `Where` và TRƯỚC `Skip/Take` — đọc `ProjectPagedAsync:790-792` mới dám ký, đừng tin comment. ④ **queryKey KHÔNG thêm trục là ĐÚNG** khi BE endpoint không nhận trục đó (đọc controller: chỉ `applicableType`+`isUserSelectable`) + giá trị lọc là **dẫn xuất trong render** (không `useState`) ⇒ miễn nhiễm bẫy "đổi leaf không remount". ⑤ Guard `/^[1-8]$/` chặn `Number("03")=3`, và **đồng nhất 3 trang** KHKK — kiểm cả 3, lệch 1 chỗ là 2 luật cho 1 tham số. ⑥ Lane khai bundle `1,643.96 kB` — tôi chạy lại `npm run build`, **trùng từng chữ số** ⇒ claim đo-lường có thật. Tag `[s166, chung-co-bi-nuot-boi-verbosity, probe-1-cho-moi-hop-le, total-la-van-tay]` **Evidence:** `runs/2026-07-31-S164-4gd-khkk-fanout/sub-reviewer-gate-k4c.md` + - **[→ archive/2026-07.md — verbatim ĐÃ VERIFY moved-not-cut 4/4 @S166-curate] S164/S165 gate K3 · S164 gate K1 · S162 cây-4-folder · S161-W2 KHKK-CRUD (digest):** tham-số-chết-vì-thiếu-tầng-UI (đo TẦNG-CUỐI ai GỬI) · sweep 2-cờ bất-đối-xứng · flake ⇒ "N/0 tươi" phải khai SỐ LƯỢT · giá-trị lớn nhất ở GATE QUY-TRÌNH không ở mã · **ghost-policy** (grep policy, đừng suy từ "key đã vào All") · default-lấn-ngữ-nghĩa · im-lặng-về-độ-chính-xác-của-con-số ×3/diff · **#82 ngược chiều** (FE OR nhiều key LỎNG HƠN policy khớp-CHÍNH-XÁC-1-key ⇒ tự chuốc 403) · hợp-đồng FE↔BE VỠ 6 điểm mà tsc+build+574-test XANH (2 lỗi CHE NHAU) ⇒ so ROUTE+FIELD trên ĐĨA. - **[→ archive/2026-07.md — verbatim, verify moved 5/5 @S166-curate] S159-đợt5 · S161-W1 · S155-đợt2 · S145-4lane · S143 · S139 · S134b · S133 · S146/S147/S152-D2 (digest gộp):** chú-thích-mang-SỐ phải chạy phép đếm · cookie-cutter soi CẢ phần bản gốc đã CHẶN · `tsc`+`noUnusedLocals` > grep cho claim 'đã cắt' · comment 'bên kia lo' = ĐI ĐỌC BÊN KIA · grep bằng TOKEN-SONG-SINH · `hợp-đồng-đứt-2-bờ` · đổi-chiều-assert ≠ nới-assert · bất-biến chia-đôi 2-axis chỉ cross-cut sweep bắt · `bằng-chứng-tự-huỷ-sau-squash` · re-measure acceptance từ git HEAD + no-self-exempt · 'mọi nhánh set X' phải grep assignment-qua-BIẾN (#81) · `ExecuteDelete` ngoài transaction = mất dữ liệu · xanh-giả bắt bằng phép 2-thế-giới · 3 THƯỚC-HỎNG (md5 CRLF · `grep -c` citation-trap · `grep -iF` MSYS 0-hit im lặng). diff --git a/.claude/workflows/runs/2026-07-31-S164-4gd-khkk-fanout/sub-reviewer-gate-k4c.md b/.claude/workflows/runs/2026-07-31-S164-4gd-khkk-fanout/sub-reviewer-gate-k4c.md index f51c1f7..0f2608f 100644 --- a/.claude/workflows/runs/2026-07-31-S164-4gd-khkk-fanout/sub-reviewer-gate-k4c.md +++ b/.claude/workflows/runs/2026-07-31-S164-4gd-khkk-fanout/sub-reviewer-gate-k4c.md @@ -1,4 +1,6 @@ -# GATE-K4C — IN-PROGRESS +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 `END` cuối file. Chưa có END = lượt chạy chưa xong, ĐỪNG đọc verdict. @@ -49,4 +51,51 @@ Cái giá (đã cân, chấp nhận): tải trọn bộ type-10 rồi mới cắ | 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.ApprovalGroup` **chỉ xuất hiện đúng 1 chỗ** — `:896-897` `if (request.ApprovalGroup is not null) q = q.Where(p => p.ApprovalGroup == request.ApprovalGroup)`. Xoá đúng khối đó thì `Handle(g)` **đồng nhất** `Handle(null)` với MỌI `g`. Vậy truyền `null` là 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. +- `Total` **không phải assert trang trí**: `ProjectPagedAsync:790` `var total = await q.CountAsync(ct)` chạy trên **chính `q` đã `Where`**, trước `Skip/Take` (`:791-792`). Nên "lọc trong bộ nhớ sau khi lấy trang" sẽ cho `Items` giống hệt mà `Total` sai — assert `Total` phâ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 trong `if (!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ế `Where` bá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ới `npx 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 q` nuố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 --force` fe-admin + 1 lượt chạy riêng chính test đó) ⇒ **10/10 XANH**; `dotnet test SolutionErp.slnx` **622/0**. +- Truy nguyên cơ chế lây nhiễm (để biết CÓ THỂ do diff không): fixture `KhkkFixture` mở `SqliteConnection("DataSource=:memory:")` **riêng cho từng test** ⇒ không có DB dùng chung; `ContractSigningPlanCodeGenerator` **khô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ũ = helper `CreateAsync` thêm tham số **mặc định `null`**, mà handler quy `null → 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 +