diff --git a/.claude/agent-memory/test-specialist/MEMORY.md b/.claude/agent-memory/test-specialist/MEMORY.md index 3aea1c8..a54849e 100644 --- a/.claude/agent-memory/test-specialist/MEMORY.md +++ b/.claude/agent-memory/test-specialist/MEMORY.md @@ -1,5 +1,7 @@ # Test-Specialist Agent — Persistent Memory +- **S164-K3 (07-31) KHKK level-finalize + pin-đúng-nhóm — 5 test, return SẠCH `[baseline đo tươi 609/0 = 45D+564I KHỚP đề bài · sau: 614/0, Δ+5, 0 hồi quy]`:** +4 vào `ContractSigningPlanApprovalTests.cs` (T-a/b/c finalize + T-e giữ-cờ), +1 vào `ContractSigningPlanGroupAndCatalogTests.cs` (T-d rào (v)) — chọn file thứ 3 thay "file mới" để KHÔNG nhân bản 70 LOC `SeedPeForCreateAsync`+`CreateAsync` đã có sẵn ở đó. **Kỹ thuật chính = MA TRẬN cùng-ruột-khác-1-biến thay cho fault-inject** (không được chạm prod, mà prod đang mang diff lane khác): 1 `SeedFinalizeScenarioAsync` → T-a (cờ+apply=true ⇒ DaDuyet+chốt giá) / T-b (**cùng seed, đổi ĐÚNG `applyLevelFinalize:false`** ⇒ (2,1) advance thường) / T-c (Cấp không cờ + default true ⇒ (0,2)). Bỏ qua cờ ⇒ T-a đỏ · bỏ qua param ⇒ T-b đỏ · `||` thay `&&` ⇒ T-c đỏ. **Điều kiện SỐNG CÒN: đặt cờ ở Bước-2-Cấp-2 của quy trình 3 Bước** — cờ ở cấp cuối thì "kết thúc" xảy ra ở CẢ HAI thế giới ⇒ T-a xanh mà chứng minh 0 (assert `StepCountAsync==3` để chốt). T-d: **CÙNG workflow `KHKK-N5` cho 2 kết quả trái ngược** (phiếu nhóm 3 = 409 · nhóm 5 = qua) + không-đốt-mã (rào (v) `:409-420` đứng TRƯỚC codegen `:428`) có control-dương 001/002/003 tăng thật. Kiểm dữ-liệu chứ không chỉ đọc regex: `DbInitializer.cs:609` sinh đúng `$"KHKK-N{n}"` ⇒ rào neo thật. T-e: 3 vế (tick⇒true · không-tick⇒false · **đối chứng type-1 cũng true** ⇒ ECHO chứ không phải đổi ép-false thành ép-true). **CỐ Ý KHÔNG pin `AllowApproverSkipToFinal==true`** dù mã đang echo: service KHKK 0 lần đọc cờ đó (`:47` khai "không port") ⇒ đó là ứng-viên config-lie #78 CÒN SÓT; pin nó = biến khả-năng-lỗi thành yêu cầu → REPORT lead thay vì test. 0 bug prod. Tag `[s164-k3, level-finalize, ma-tran-cung-ruot-1-bien, co-o-cap-cuoi-lam-rong-phep-do, khong-pin-config-lie]` + - **S161-W2 (07-29) KHKK CRUD 8 test `[hmw wf_7490179a-70b · chết #53 sau khi file LAND (sub-md dừng §0.1) · lead chạy suite tươi 574/0 = 566+8, cả 8 tên đúng]`:** `Application/ContractSigningPlanCrudTests.cs` (7) + extend `Api/AuthorizePolicyRegressionTests.cs` (PIN reflection). 2 PIN spec + 6 acceptance: happy-path Lines=winners + SUM IsSelected đúng NCC (bẫy `w.Id` vs `w.SupplierId`) · second-create 409 **`_AndDoesNotBurnCode`** (mã không đốt khi Conflict — phép đo sequence side-effect) · workflow-guard 2 nhánh Conflict/NotFound · no-winner · **soft-delete-then-recreate + changelog giữ** · owner/phase guard. Phát hiện đắt (sub-md §0.1): **TestApplicationDbContext KHÔNG wire AuditingInterceptor ⇒ `Remove()` = xoá CỨNG ⇒ đo "xoá-tạo-lại" trên xoá-cứng = ĐO NHẦM CƠ CHẾ** (rào 1-PE-1-KH dựa global-filter che row soft-deleted, không phải row biến mất) → dựng fixture riêng CÓ interceptor cho nhóm delete-test. Baseline đo tươi trước khi viết (không tin số đề bài). Tag `[s161-w2, khkk-8-test, khong-dot-ma-khi-409, fixture-interceptor-cho-soft-delete, do-nham-co-che-xoa-cung]` - **S161 (07-29) W1 KHKK: 4 test schema+codegen `[hmw wf_a95c9909-14b · chết #53 LÚC ĐỢI full-suite (việc thật đã xong) → sub-md 10KB ghi-từng-mục cứu trọn; lead chạy full-suite tươi 566/0 + điền §2/§4 on-behalf]`:** File MỚI `Common/ContractSigningPlanSchemaTests.cs` — 4 test: (1) PIN-spec `BayBang_MoiBang_CoAuditFields_VaQueryFilter` (quét EF model theo NAMESPACE → `BeEquivalentTo` đúng bộ 7 tên [bảng #8 lọt = ĐỎ] + audit-fields + soft-delete assert 2-CHIỀU [BaseEntity phải KHÔNG có IsDeleted] + seed IsDeleted=true chứng filter là BIỂU-THỨC-THẬT); (2) PIN `SigningPlanCodeGenerator_Format_KhkkYearSeqD3` — regex + tuyệt-đối 001→002 + row LastSeq==2 + **đẩy đồng hồ 2027 → KHKK/2027/001 reset** (thiếu vế này `\d{4}` xanh cả khi năm hardcode); (3) #57 xoá-mềm-thêm-lại KHÔNG nổ UNIQUE + vế đối chứng 2-dòng-sống PHẢI DbUpdateException (không có = xanh cả khi index chưa từng tạo); (4) pin slot 10 enum. **Fault-inject 2 chiều trên bảng-kỳ-vọng rồi revert** — chứng `GetDeclaredQueryFilters()` phân biệt 2 thế giới. Learned: (1) **prompt lead ≠ đĩa** — LevelOpinion là AuditableEntity nhưng CỐ Ý KHÔNG filter (lý-do ghi 2 chỗ: site orphan-purge phải thấy row soft-deleted) ⇒ test theo CODE + assert cái-cố-ý thành invariant có răng; (2) EF Core 10: `GetQueryFilter()` obsolete-prone → `(IReadOnlyEntityType).GetDeclaredQueryFilters()`; (3) Serializable trên SQLite = graceful-map, tiền lệ `SqliteDbFixture.cs:36-42` + PE codegen test — KHÔNG chế fixture mới; (4) KHÔNG `Remove()` đo xoá mềm (TestCtx không wire AuditingInterceptor → xoá CỨNG, bài S155). Tag `[s161, w1-4-test, faultinject-2-chieu, test-theo-code-khong-theo-prompt, clock-2027-reset, 53-luc-doi-suite]` diff --git a/.claude/workflows/runs/2026-07-31-S164-4gd-khkk-fanout/sub-reviewer-gate-k3.md b/.claude/workflows/runs/2026-07-31-S164-4gd-khkk-fanout/sub-reviewer-gate-k3.md new file mode 100644 index 0000000..25c079c --- /dev/null +++ b/.claude/workflows/runs/2026-07-31-S164-4gd-khkk-fanout/sub-reviewer-gate-k3.md @@ -0,0 +1,22 @@ +REVIEWER-GATE-K3: IN-PROGRESS — 0 finding (đang chạy) + +# Reviewer gate K3 — 8 workflow nhóm + port finalize + designer + +Ghi-đĩa TRONG LÚC LÀM (chống #53). Dòng-1 sẽ được cập nhật khi có verdict. + +## 0. Trạng thái khởi động (đo từ đĩa) + +- Branch `main`, HEAD `2967c43` (wal: flush 20260731T1743). +- `git diff --stat`: 25 file M, +908 / -102. +- Untracked: chỉ 2 file `.claude/auto-memory/*` (memory, không phải prod-code). +- 0 file untracked trong `src/` hoặc `tests/` → **0 migration mới** (kiểm lại ở §4). + +## 1. Build / test (đang chạy) + +## 2. Code-vs-spec + +## 3. Phán 3 điểm lead tự-quyết + +## 4. Ranh giới + +## 5. Test lane diff --git a/.claude/workflows/runs/2026-07-31-S164-4gd-khkk-fanout/sub-test-k3.md b/.claude/workflows/runs/2026-07-31-S164-4gd-khkk-fanout/sub-test-k3.md index 973c3a2..d44997a 100644 --- a/.claude/workflows/runs/2026-07-31-S164-4gd-khkk-fanout/sub-test-k3.md +++ b/.claude/workflows/runs/2026-07-31-S164-4gd-khkk-fanout/sub-test-k3.md @@ -1,4 +1,4 @@ -K3-TEST: IN-PROGRESS — chưa đo +K3-TEST: DONE — suite 614/0 (+5) # K3-TEST — level-finalize + rào pin-đúng-nhóm (S164) @@ -11,4 +11,100 @@ K3-TEST: IN-PROGRESS — chưa đo `ReadPlanAsync` · `ReadOpinionsAsync` · `LevelIdOfAsync` · `InboxAsync` · `ShouldBeAt` · `ResultShouldMirror`. File hiện có 8 [Fact] (2 gate-K2 + T1..T6). git status: file đang `M` (K2 đã sửa) — KHÔNG đè. - [t0] Baseline đề bài: 609/0 (45D + 564I). SẼ ĐO LẠI TƯƠI (không tin số đề bài — bài S161-W2). +- [t1] Đọc `ContractSigningPlanWorkflowService.cs` — nhánh MỚI `:276-289`: + `if (actingLevel.AllowApproverFinalize && applyLevelFinalize)` → `EndedByLevelFinalize=true` · + `ApplyApprovedValuesOnFinalize(plan)` (TRƯỚC gán Phase) · `Phase=DaDuyet` · 2 con-trỏ = null · log · return. + Đứng NGAY SAU `UpsertLevelOpinionAsync` ⇒ ý kiến vẫn ghi. Advance thường ở `:293-318`. + `TransitionAsync(planId, action, comment, bool applyLevelFinalize = true, CancellationToken ct = default)`. +- [t1] Đọc rào (v) `ContractSigningPlanFeatures.cs:409-420`: regex `^KHKK-N([1-8])$` trên `w.Code`, + ĐỨNG SAU `EnsureWorkflowTypeAsync` (:407) và **TRƯỚC** `codeGen.GenerateAsync` (:428) + ⇒ 409 ở rào (v) KHÔNG được đốt mã (đo được — khuôn S161-W2 `_AndDoesNotBurnCode`). +- [t1] `ApprovalWorkflowV2AdminFeatures.cs:384-386` — ép-false type-10 ĐÃ GỠ ở **POST tạo version** + (echo `l.AllowApproverSkipToFinal` + `l.AllowApproverFinalize`). PUT sửa-tại-chỗ VẪN chặn + (`:795` không gán, `:813-814` dòng THÊM luôn false) — CỐ Ý theo luật versioning ⇒ KHÔNG test PUT. + Handler `CreateAwDefinitionCommandHandler(IApplicationDbContext db)` — 1 dep ⇒ arrange NHẸ, + làm test HÀNH-VI được (không phải rơi xuống phương án "khẳng định không còn biểu thức"). +- [t1] Chỗ đặt test (khai lệch so với đề bài): + * T-a/T-b/T-c/T-e → **append CUỐI** `ContractSigningPlanApprovalTests.cs` (dùng `KhkkWorkflowFixture`). + * T-d → **append CUỐI** `ContractSigningPlanGroupAndCatalogTests.cs`: rào (v) cần + `SeedPeForCreateAsync` (:749) + `CreateAsync` (:814) đã có sẵn ở ĐÓ và là `private`. + Đề bài cho "ApprovalTests hoặc file mới"; chọn file thứ 3 để KHÔNG nhân bản ~70 LOC seed PE. + KHÔNG đè test cũ (chỉ chèn trước dấu `}` cuối class). +- [t2] **BASELINE ĐO TƯƠI (không lấy số đề bài):** `dotnet test SolutionErp.slnx` → + Domain **45**/0 + Infra **564**/0 = **609**/0. KHỚP số đề bài. +- [t3] Đã viết 5 test + 6 helper. Build test-project: **succeeded, 0 warning 0 error**. +- [t4] Chạy LỌC 5 test mới (`--filter`): **6 matched, 6 PASS, 0 fail** (6 = 5 của tôi + 1 test + nền `Entity_MacDinhNhomMot_...ConCoKetThucSomMacDinhTat` dính chữ "KetThuc"). + `--list-tests` xác nhận ĐỦ 5 TÊN trên đĩa (chống claim-mạnh-hơn-việc-đã-làm): + ApproveV2_CapMangCoKetThuc_ChotPhieuNgay_KhongTrinhTiepBuocSau (T-a) + ApproveV2_CapMangCoKetThuc_NhungBoTick_ThiVanTrinhTiepBuocSau (T-b) + ApproveV2_CapKhongMangCo_ThiKhongBaoGioKetThucSom (T-c) + Create_PinQuyTrinhKhacNhomDuyet_ThiBiChan_VaKhongDotMa (T-d) + CreateAwDefinition_LoaiKhkk_GiuNguyenCoKetThucTaiCap_KhongConEpFalse (T-e) + +## §1 Vì sao 5 lưới này KHÔNG rỗng (fault-injection = CẤU TRÚC, không cần sửa mã production) + +Ma trận T-a/T-b/T-c dùng **CÙNG MỘT bộ seed** (`SeedFinalizeScenarioAsync`), khác đúng 1 biến: +- cài đặt bỏ qua `AllowApproverFinalize` (không bao giờ finalize) ⇒ **T-a ĐỎ** +- cài đặt bỏ qua tham số `applyLevelFinalize` (cờ một mình quyết) ⇒ **T-b ĐỎ** (phiếu ra DaDuyet + thay vì Bước 3) — tương đương phép "gọi handler THẬT với param=null" của S164-W7d2 +- điều kiện viết `||` thay `&&` (mặc định true ⇒ mọi lượt duyệt đều chốt) ⇒ **T-c ĐỎ** +- nhánh finalize quên gọi `ApplyApprovedValuesOnFinalize` ⇒ **T-a ĐỎ** ở khối chốt giá +- finalize quên vế "chỉ phiếu này" ⇒ **T-a ĐỎ** ở phiếu chứng nhân song sinh + +Điều kiện để phép đo có nghĩa (đã assert trong test, không chỉ nói mồm): Cấp mang cờ là +**Bước 2 Cấp 2 / quy trình 3 Bước** ⇒ đường THƯỜNG từ (1,2) đi tới (2,1) **chứ không** tới +DaDuyet ⇒ `DaDuyet` của T-a không thể tới từ đường thường. `StepCountAsync == 3` chốt vế này. + +T-d: **cùng một quy trình `KHKK-N5`** cho 2 kết quả trái ngược khi chỉ đổi nhóm phiếu (3 = chặn, +5 = qua) ⇒ rào-chặn-tuốt và rào-chưa-bao-giờ-chạy đều bị bắt. Vế "không đốt mã" có +**control dương**: 001/002/003 tăng thật ⇒ codegen sống, nên "001 ngay sau lần 409" là phép đo +thật chứ không phải hằng số. + +T-e: 3 vế — Cấp tick ⇒ true · Cấp không tick ⇒ false (chống "bật bừa") · **đối chứng type-1 +`DuyetNcc` cũng true** ⇒ chứng cơ chế là ECHO tham số, phân biệt "đã gỡ ép-false" với +"đổi ép-false thành ép-true". + +## §2 KẾT QUẢ ĐO (full suite, chạy SAU khi 5 test land) + +``` +Domain.Tests Failed: 0, Passed: 45 (không đổi) +Infrastructure.Tests Failed: 0, Passed: 569 (564 + 5) +TỔNG 614 / 0 fail (baseline đo tươi 609 ⇒ Δ = +5) +``` +`dotnet build` test-project: 0 warning · 0 error. **0 hồi quy** — 564 test Infra cũ nguyên vẹn. + +Phạm vi file đã ghi (`git status`, chỉ 3 dòng, KHÔNG chạm production): +``` + M tests/.../Application/ContractSigningPlanApprovalTests.cs +346 −1 + M tests/.../Application/ContractSigningPlanGroupAndCatalogTests.cs + 81 −1 + M .claude/workflows/runs/2026-07-31-S164-4gd-khkk-fanout/sub-test-k3.md +``` +(2 dòng `−1` = dấu `}` cuối class bị thay khi chèn thêm; không test cũ nào bị sửa/xoá.) + +## §3 BUG PRODUCTION: **KHÔNG CÓ** — 5/5 xanh ngay lượt đầu, mã BE đúng như spec K3 mô tả + +## §4 QUAN SÁT giao lead (KHÔNG tự sửa, KHÔNG pin thành test) + +**(O-1) Ứng viên config-lie #78 còn sót: `AllowApproverSkipToFinal` cho type-10.** +Khối ép-false vừa gỡ ở `ApprovalWorkflowV2AdminFeatures.cs:376-385` bao **CẢ HAI** cờ, nên nay +`:384` echo `AllowApproverSkipToFinal` cho MỌI type kể cả 10. Nhưng +`ContractSigningPlanWorkflowService.cs:47` khai rõ **"`AllowApproverSkipToFinal` giữ nguyên +không port"**, và grep toàn `src/Backend` xác nhận service KHKK **0 lần đọc** cờ này (chỉ PE +`:810` + Contract `:335` đọc). ⇒ Admin tick "Cấp này nhảy thẳng Cấp cuối" trên quy trình KHKK: +DB lưu `true`, máy KHÔNG làm gì — đúng hình config-lie mà chính lane này vừa trả giá để đóng +cho `AllowApproverFinalize`. Lựa chọn: (a) ép false RIÊNG `SkipToFinal` cho type-10, hoặc +(b) ẩn ô tick đó trên Designer khi type=10, hoặc (c) port nốt nhánh. **CỐ Ý KHÔNG viết test +pin `SkipToFinal == true`** — pin nó là biến một khả-năng-lỗi thành "yêu cầu". + +**(O-2) Rào (v) neo vào mã CHÍNH XÁC — đã đối chiếu dữ liệu, KHÔNG chỉ đọc regex.** +`^KHKK-N([1-8])$` neo hai đầu ⇒ `QT-KHKK-N5` (có tiền tố) sẽ **lọt rào trong im lặng**. +Đã kiểm nguồn dữ liệu thật: `DbInitializer.cs:609` sinh `var code = $"KHKK-N{n}";` — KHỚP +tuyệt đối ⇒ rào SỐNG trên 8 workflow seed. Rủi ro còn lại thuần đặt-tên: ai tạo tay quy trình +nhóm mà gõ tiền tố sẽ mất rào mà không có tín hiệu nào. (Không đổi mã — chỉ ghi để lead cân +nhắc đưa mã nhóm vào hằng số dùng chung thay vì literal 2 nơi.) + +**(O-3) PUT sửa-tại-chỗ CỐ Ý vẫn chặn đổi cờ** (`:795` không gán · `:813-814` dòng thêm luôn +false) — spec vá-3 xếp nhầm vào "cụm ép-false", lead đã khai lệch trong `sub-implbe-k3.md`. +KHÔNG viết test cho PUT theo đúng quyết định đó.