From e7f282fca77839567122398512abd883d07a643b Mon Sep 17 00:00:00 2001 From: pqhuy1987 Date: Mon, 10 Aug 2026 19:46:32 +0700 Subject: [PATCH] wal: flush 20260810 S188 dot-2 run-trace W3-W6 --- .claude/WAL.md | 16 +-- .../sub-cicd-dot1.md | 116 ++++++++++++++++++ .../sub-implementer-backend-w3.md | 28 +++++ .../sub-implementer-backend-w4.md | 79 ++++++++++++ .../sub-implementer-frontend-w5.md | 100 +++++++++++++++ .../sub-implementer-frontend-w6.md | 102 +++++++++++++++ .../sub-reviewer-cum.md | 51 ++++++++ .../sub-test-specialist-w3.md | 77 ++++++++++++ 8 files changed, 562 insertions(+), 7 deletions(-) create mode 100644 .claude/workflows/runs/2026-08-10-S187-duyet-hd-tu-khkk/sub-cicd-dot1.md create mode 100644 .claude/workflows/runs/2026-08-10-S187-duyet-hd-tu-khkk/sub-implementer-backend-w3.md create mode 100644 .claude/workflows/runs/2026-08-10-S187-duyet-hd-tu-khkk/sub-implementer-backend-w4.md create mode 100644 .claude/workflows/runs/2026-08-10-S187-duyet-hd-tu-khkk/sub-implementer-frontend-w5.md create mode 100644 .claude/workflows/runs/2026-08-10-S187-duyet-hd-tu-khkk/sub-implementer-frontend-w6.md create mode 100644 .claude/workflows/runs/2026-08-10-S187-duyet-hd-tu-khkk/sub-reviewer-cum.md create mode 100644 .claude/workflows/runs/2026-08-10-S187-duyet-hd-tu-khkk/sub-test-specialist-w3.md diff --git a/.claude/WAL.md b/.claude/WAL.md index edc2726..ca7acb1 100644 --- a/.claude/WAL.md +++ b/.claude/WAL.md @@ -21,18 +21,20 @@ chain: [!] wf: hmw đợt-1 wf_58d6f783-395 XONG — t0 CLEAN (4 test W1a/W1b, 3 ĐỎ-hành-vi + 1 control, +295/-0) · t1 code LAND (+89 Features +76 Dtos, 0-mig ✓, return rỗng + KHÔNG sub-file) · t2 PARTIAL (types ×2 + fe-user MyContracts xong; fe-admin mirror + InboxPage ×2 THIẾU). - W7-FINISH VỀ PASS (17:5x): admin=ContractsListPage áp logic + vá 3 lỗi client-filter-sau- - pagination; InboxPage BLOCKED ĐÚNG (BE GetMyInboxQuery 0 param + Take(100)) ⇒ NỢ MỚI - [carry:w7b-inbox-param] (BE thêm ?type= inbox rồi FE gỡ client-filter — xếp đợt sau, hiện - client-filter vẫn chạy); types-pair lệch-baseline-có-chủ-đích, khối THÊM identical ×2 ✓; - build ×2 EXIT 0. CÒN BAY: W1-GREEN. Sau đó: build+test full → reviewer → commit+push → cicd. +[x] ĐỢT-1 SHIPPED + VERIFIED: gate 672/672 · reviewer PASS_WITH_FLAGS-8 (F-1/F-2 vá ngay ×2app; + F-4→spec + [carry:w7b-inbox-param] (+F-5 ContractCreatePage); F-3 ActiveOthers treo W6) · + push bc309c55..0149f106 (5 commit) · POST 4/4: run #481 success · bundle rotate 2 app + (DeBPqp_c/CSIiQrhk) · smoke 200 · /api/contracts?type=1 → 401. cicd-trace đứt giữa (PRE+dự- + đoán đủ, sub-cicd-dot1.md) — POST lead tự đo. #53 lượt này ×5 (2 impl + w1green + reviewer + + cicd), đĩa cứu 5/5. NỢ closeout: STATUS mốc bundle lão-hoá (PRE đã ≠ S164) + baseline 672. [ ] W3→W4→W5(+ruột-88)→W6→W10 — MỞ HẾT theo YC-024; W8 HOÃN · W9 HỦY; W3 roster sao khuôn KHKK (implementer đo seeder `SeedKhkkGroupWorkflowsAsync` thật, không đoán) [ ] chờ anh: (74) rotate + (76)(77)(78)(79) + tồn (22)(67)(72) + lệnh "ổn rồi" mở W8-authz. (73) + (80)-(88) ĐÓNG @S188 -next: fan-out đợt-1 W1→W2→W7 (hmw Opus MAX, args.run=2026-08-10-S187-duyet-hd-tu-khkk) theo - spec §2/§3, TỪNG wave gate; xong ship thì W3→W4→W5(+88)→W6→W10. +next: YC-025 (anh @S188 "làm tiếp hết, review 1 lần"): W3(seed 7 HD-* + 7 key, 2 lane đang bay) + → W4(guard 4-cửa + fault-inject) → W5(card F1 + ruột-88) → W6(panel 2-nút + Đ2 + quyết F-3) + → gate + reviewer TOÀN CỤM → push 1 LƯỢT → cicd → W10 dry-run Dev → báo anh review. verify: run: ls .claude/workflows/runs/2026-08-10-S187-duyet-hd-tu-khkk/ # run.md + 4 sub + spec diff --git a/.claude/workflows/runs/2026-08-10-S187-duyet-hd-tu-khkk/sub-cicd-dot1.md b/.claude/workflows/runs/2026-08-10-S187-duyet-hd-tu-khkk/sub-cicd-dot1.md new file mode 100644 index 0000000..bc67594 --- /dev/null +++ b/.claude/workflows/runs/2026-08-10-S187-duyet-hd-tu-khkk/sub-cicd-dot1.md @@ -0,0 +1,116 @@ +# sub-cicd-dot1 — Verify deploy đợt-1 YC-023 (range `bc309c55..0149f106`) + +> Vai: cicd-monitor (READ-only). Ghi TRONG LÚC LÀM (chống #53). Kết bằng `== HẾT ==`. +> Spawn: 2026-08-10 ~18:21. Push: 18:20:41. + +--- + +## CHÂN 1 — Push + path-filter (Stage 1) + +``` +$ git log -1 --format='%H %ci %s' +0149f106f98dcea51669c49028201d410b0a32ed 2026-08-10 18:20:41 +0700 [CLAUDE] Docs: YC-024 — 9 đáp án (80)-(88)… +$ git log origin/main..HEAD --oneline → (RỖNG) ⇒ đã push hết +``` + +Range 5 commit (khớp đề bài): + +| sha | subject | +|---|---| +| `5a77f4f1` | wal: flush 20260810 S188 dot-1 run-trace + governance state | +| `7bad4ae1` | Workflow: W1 gỡ 2 bom per-type type-3 — deactivate per-Code + overview family; test-before RED→GREEN **+4 test (672/672)** | +| `01a7c0b8` | Contract: W2 BE-DTO nền GĐ3 — `ContractSourceDto` reverse-join KHKK + CurrentWorkflowStepIndex + WorkflowSteps | +| `502f4d2e` | Contract: W7 server-filter per-type — ContractsListPage (+3 fix badge/pagination) + MyContractsPage | +| `0149f106` | Docs: YC-024 — 9 đáp án vào sổ + HANDOFF + spec đợt-1 | + +**Path-filter (gotcha #41, 7-glob):** 74 file đổi. File **non-ignored** có mặt ⇒ CI PHẢI chạy: +- `src/Backend/SolutionErp.Application/{ApprovalWorkflowsV2/ApprovalWorkflowV2AdminFeatures.cs, Contracts/ContractFeatures.cs, Contracts/Dtos/ContractDtos.cs}` +- `fe-admin/src/pages/contracts/ContractsListPage.tsx` · `fe-admin/src/types/contracts.ts` +- `fe-user/src/pages/{InboxPage.tsx, contracts/MyContractsPage.tsx}` · `fe-user/src/types/contracts.ts` +- `tests/SolutionErp.Infrastructure.Tests/Application/UpdateAwDefinitionTests.cs` + +⇒ **KHÔNG docs-only.** ✅ CHÂN 1 PASS. + +--- + +## PRE-SNAPSHOT (chụp kịp giờ — bài học S168) + +Spawn 18:21 / push 18:20:41 / run `status=running` lúc 18:22 ⇒ bước `deploy` (cuối pipeline, ~5-6 phút) **CHƯA chạm đĩa**. Đo LIVE lúc **18:22:29**: + +``` +$ curl -s https://admin.solutions.com.vn/ | grep -oE '/assets/index-[A-Za-z0-9_-]+\.(js|css)' +/assets/index-BNiYPCoJ.js +/assets/index-Desb-yZr.css +$ curl -s https://eoffice.solutions.com.vn/ | grep -oE '/assets/index-[A-Za-z0-9_-]+\.(js|css)' +/assets/index-BYLXK11b.js +/assets/index-BGUiinrQ.css +``` + +🔴 **Mốc PRE của đề bài đã LÃO HOÁ** — đề bài + `STATUS.md:9` ghi mốc **@S164**: admin `B43R6Y17` · user `NLI5umBg`. Đo live: admin **`BNiYPCoJ`** · user **`BYLXK11b`** ⇒ đã có ≥1 deploy giữa S164 và giờ mà STATUS chưa lật. **Dùng mốc ĐO (`BNiYPCoJ`/`BYLXK11b`) làm PRE, KHÔNG dùng mốc đề bài** (bài học #438: mốc PRE lão hoá sau MỖI deploy). +*(Ghi chú: `STATUS.md:479` mà đề bài trỏ hiện là row `Sub-agents`, không phải row bundle — con-trỏ-số-dòng đã trôi.)* + +--- + +## CHÂN 2 — Gitea run (Stage 2/3) + +``` +$ curl -s ".../api/v1/repos/vietreport-admin/solution-erp/actions/tasks?limit=6" +id=594 run_number=481 head_sha=0149f106f98dcea… status=running workflow=deploy.yml +``` +Khớp `head_sha` = HEAD (không khớp theo run_number — gotcha cancelled-shift). Run cuối của range ⇒ đúng nếp gotcha #86. + +_(đang poll — ghi tiếp bên dưới)_ + +--- + +## CHỌN MARKER + HIỆU CHUẨN THƯỚC (làm TRƯỚC khi deploy land) + +### FE — 3 ứng viên, chỉ 1 sống + +Diff FE **không có literal chuỗi mới nào** (toàn comment + logic). Comment bị Vite strip, tên biến local (`parsedType`/`typeFilter`) bị minify ⇒ **0 bit** (bài học #436). Phải soi kỹ: + +| ứng viên | đo trên bundle PRE | phán | +|---|---|---| +| `approvedAmountTotal` (đề bài gợi ý) | — | ❌ **LOẠI trước khi đo**: chỉ nằm trong `types/contracts.ts` dạng `export type` ⇒ **type-only, TS xoá sạch lúc compile**, không phải tree-shake | +| `Number.NaN` | admin **0** · user **0** | ❌ **DEAD MARKER** — minifier viết lại `Number.NaN` → `NaN` trần. *Nếu không hiệu chuẩn mà cứ đo, POST cũng 0 ⇒ báo FAIL OAN.* | +| `Number.isFinite` | admin **4** · user **4** | ✅ **SỐNG** — truy cập property built-in, minifier không đổi được | + +**Chứng ContractsListPage nằm TRONG chunk `index-*.js`** (không bị code-split ra chunk khác — nếu bị thì count sẽ đứng im và ta kết luận sai): +``` +$ grep -o -F 'Tìm mã / tên / NCC' pre-admin.js | wc -l → 1 (literal của CHÍNH ContractsListPage) +$ grep -o -F 'Tìm theo mã / tên / NCC' pre-user.js | wc -l → 3 +$ grep -rn "React.lazy\|lazy(" fe-admin/src/App.tsx fe-admin/src/main.tsx → 0 hit +``` + +**Vị trí marker trong source (neo SHA, không dùng `HEAD~n`):** +``` +base bc309c55 → fe-admin: KhkkDetailContent.tsx:666 + HardCopiesPage.tsx:68 = 2 +head 0149f106 → fe-admin: + ContractsListPage.tsx:50 = 3 (Δ+1) +base bc309c55 → fe-user : KhkkDetailContent.tsx:666 + HardCopiesPage.tsx:68 = 2 +head 0149f106 → fe-user : + MyContractsPage.tsx:36 = 3 (Δ+1) +``` + +🎯 **DỰ ĐOÁN KHAI TRƯỚC KHI ĐO (#436):** bundle POST phải là **admin 5 · user 5** (4+1 mỗi app). Sai số nào khác = phải điều tra, không được vuốt. + +### BE — probe DLL 2 khung UTF-8 ⟂ UTF-16 (nếp S177) + +Script `dllprobe.ps1` scp lên VPS rồi `powershell -File` (KHÔNG nhét inline — SSH→PS nuốt `$`/`"`). Đọc `SolutionErp.Application.dll` ra byte → decode Latin1 giữ nguyên byte → đếm 2 khung. + +**ĐO PRE lúc 18:25:18** (`LWT=2026-08-10 12:55:16`, `LEN=2454528` — bản deploy TRƯỚC đó): + +``` +MARK ActiveOthers utf8=0 utf16=0 ← control-ÂM độc quyền (W1) +MARK PerCodeDeactivationTypes utf8=0 utf16=0 ← control-ÂM độc quyền (W1) +MARK ContractSourceDto utf8=0 utf16=0 ← control-ÂM độc quyền (W2) +MARK ApprovedAmountTotal utf8=0 utf16=0 ← control-ÂM độc quyền (W2) +MARK ContractWorkflowStepDto utf8=0 utf16=0 ← control-ÂM độc quyền (W2) +MARK TenHangMucs utf8=0 utf16=0 ← control-ÂM độc quyền (W2) +MARK AwDefinitionDto utf8=1 utf16=1 ← ✅ CONTROL-DƯƠNG (có từ trước) +MARK AllowApproverFinalize utf8=3 utf16=1 ← ✅ CONTROL-DƯƠNG (có từ trước) +MARK CurrentWorkflowStepIndex utf8=5 utf16=3 ← ✅ CONTROL-DƯƠNG (tên trùng phía KHKK) +``` + +⇒ **Thước SỐNG** (control-dương ≥1 cả 2 khung) **và** 6 marker mới **vắng thật** trước deploy. Đây là differential ĐÚNG NGHĨA: 0 → ≥1, đo trên cùng một thước, không phải "0-hit rồi đoán". + +🎯 **DỰ ĐOÁN:** POST cả 6 marker phải **≥1 ở khung utf8**; `LWT` phải rơi trong cửa-sổ deploy hôm nay ~18:2x; `LEN` phải đổi. + diff --git a/.claude/workflows/runs/2026-08-10-S187-duyet-hd-tu-khkk/sub-implementer-backend-w3.md b/.claude/workflows/runs/2026-08-10-S187-duyet-hd-tu-khkk/sub-implementer-backend-w3.md new file mode 100644 index 0000000..5d6c575 --- /dev/null +++ b/.claude/workflows/runs/2026-08-10-S187-duyet-hd-tu-khkk/sub-implementer-backend-w3.md @@ -0,0 +1,28 @@ +# sub-implementer-backend-w3 — W3 seed họ workflow type-3 + 7 key Designer (BE data-op, 0-mig) + +> Run `2026-08-10-S187-duyet-hd-tu-khkk` · vai `implementer-backend` · Opus 5 MAX · 2026-08-10. +> Đáp án owner đã chốt (YC-024): (80) trục **7-LOẠI** · (81) **PA-A** mỗi loại 1 mã · (83) **roster SAO KHUÔN KHKK**, anh tự chỉnh Admin Designer sau. +> Ghi TỪNG MỤC trong lúc làm (chống #53). File này = chân lý; return chỉ là tóm. + +## §0 — Ground-truth ĐO TƯƠI (không chép từ brief) + +| # | Đo gì | Lệnh / nguồn | Kết quả | +|---|---|---|---| +| 1 | Cấu trúc trạm/người THẬT của 8 workflow KHKK trên Dev | `sqlcmd -S "(localdb)\MSSQLLocalDB" -d SolutionErp_Dev -E` — chữ ký MD5 per-Code trên tập `(StepOrder/Dept/StepName/LevelOrder/Email/Finalize)` | **8/8 GIỐNG HỆT**: cùng hash `7EB6CD219E89AFA4C2DE55DE63377107`, 9 level-row/workflow ⇒ **dùng KHUÔN CHUNG** (không phải "lấy N1 + khai") | +| 2 | Khuôn chung đó là gì | cùng truy vấn, `KHKK-N1` | Bước 1 `Phòng Cung Ứng`/PRO → Cấp 1 OR-of-3 (duy·long·phuong) + Cấp 2 (tra.bui) · Bước 2 `P. Kiểm soát Chi phí`/CCM → Cấp 1 OR-of-3 (binh.le·luu.tran·nguyen.ho) + Cấp 2 (chuong.phan, `Finalize=1`) · Bước 3 `Ban Giám đốc`/BOD → Cấp 1 (truong.nguyen) | +| 3 | Roster CODE (11 email) vs DB Dev (9 slot) | `DbInitializer.cs:554-585` vs đo (1) | Dev thiếu 2 user (`binh.lethanh@`, `truong.letrandang@`) ⇒ **nhánh "bỏ slot + warning" đã chạy THẬT trên Dev** — không phải đường lý thuyết | +| 4 | Type-3 hiện có gì | `SELECT ... WHERE ApplicableType=3` | **1** workflow `QT-HD-V2-001` v01 (sample UAT S29), 1 Bước. Dev **0** workflow type-1/2 (khớp Surprise #7 BE) | +| 5 | `\|MenuKeys.All\|` trước sửa | đếm tay `MenuKeys.cs:222-250` + đối chiếu STATUS `:474` | **113** (khớp canonical) | +| 6 | Engine type-3 có hiểu `AllowApproverFinalize` không | `grep -n "Finalize" ContractWorkflowService.cs` | **0 hit** (chỉ `skipToFinal` = cờ KHÁC). Control dương: PE `:870` CÓ · KHKK `ContractSigningPlanWorkflowService.cs:286` CÓ ⇒ **thước sống**, 0-hit là vắng THẬT | + +## §1 — 1 quyết kỹ thuật CÓ KHAI: cờ `AllowApproverFinalize` KHÔNG sao nguyên xi + +Roster KHKK bật `Level2Finalize=true` ở trạm CCM (a Chương được kết thúc, không lên CEO). Đo (6) cho thấy **máy duyệt type-3 KHÔNG có nhánh finalize** ⇒ seed `true` cho họ HĐ = **config-lie #78**: Designer/UI khoe "cấp này được kết thúc" mà engine vẫn đi tiếp BGĐ. + +⇒ Seed HĐ giữ **NGUYÊN trạm/người/thứ tự/OR-of-N** (đúng "sao khuôn" theo (83)), riêng cờ finalize = `false` + ghi vết ngay tại chỗ. Owner bật lại 1 click trong Designer khi máy type-3 có nhánh finalize (wave sau). + +## §2 — 4 mảnh land (điền dần) + +_(đang thi công — mục này cập nhật sau mỗi mảnh)_ + +== ĐANG VIẾT == diff --git a/.claude/workflows/runs/2026-08-10-S187-duyet-hd-tu-khkk/sub-implementer-backend-w4.md b/.claude/workflows/runs/2026-08-10-S187-duyet-hd-tu-khkk/sub-implementer-backend-w4.md new file mode 100644 index 0000000..3327687 --- /dev/null +++ b/.claude/workflows/runs/2026-08-10-S187-duyet-hd-tu-khkk/sub-implementer-backend-w4.md @@ -0,0 +1,79 @@ +# W4 — guard 4-cửa pin/re-pin quy trình HĐ (BE, 0-migration) + +> vai: implementer-backend · run `2026-08-10-S187-duyet-hd-tu-khkk` · đề bài: lead W4 +> đáp án owner neo vào: (86) KHOÁ CỨNG không đổi chéo loại (mirror KHKK/Duyệt NCC) · (80) trục 7-LOẠI · (81) PA-A + +## 0. Đo TRƯỚC khi sửa (không nhận tiền-đề từ đề bài) + +### 0.1 Nhà 7 mã ĐÃ LAND (dùng, cấm gõ lại chuỗi) + +`src/Backend/SolutionErp.Domain/Contracts/ContractApprovalWorkflowFamily.cs` — có trên đĩa (untracked, W3 đang land), +`CodePrefix = "HD-"` · `Members` 7 bộ ba (Type, Code, ShortName) · `CodeFor(type)` · `KeyToken(code)`. +Consumer hiện có: `DbInitializer.cs:756/:782` (seed 7 workflow) + `MenuKeys.cs:155-156` (7 key Designer) + test W3. +⇒ W4 là consumer thứ 3, gọi `CodeFor`, KHÔNG khai lại mã. + +### 0.2 Dev DB — item 3 của đề bài ("có HĐ nào pin workflow ngoài họ không?") + +``` +sqlcmd -S "(localdb)\MSSQLLocalDB" -d SolutionErp_Dev -E +SELECT COUNT(*) , SUM(CASE WHEN ApprovalWorkflowId IS NOT NULL THEN 1 ELSE 0 END) FROM Contracts; + → 7 | 0 ← 7 HĐ tồn tại (control dương: bảng KHÔNG rỗng) · 0 HĐ pin V2 +SELECT Code,Version,ApplicableType,IsActive,IsUserSelectable FROM ApprovalWorkflows WHERE ApplicableType=3; + → QT-HD-V2-001 | 1 | 3 | 1 | 1 ← DUY NHẤT, và mã NGOÀI họ HD-* +SELECT ApplicableType, COUNT(*) ... GROUP BY ApplicableType; + → 3:1 · 4:1 · 5:1 · 6:1 · 7:1 · 9:1 · 10:8 ← control dương: type-10 có 8 (họ KHKK đã seed) ⇒ thước sống +``` + +**Kết luận đo:** Dev **0 HĐ pin quy trình ngoài họ** (vì 0 HĐ pin V2 nào cả — 7/7 đều `NULL`). +Quy trình type-3 DUY NHẤT đang sống trên Dev lại là `QT-HD-V2-001` (mã ngoài họ) — W3 seeder `HD-*` chưa chạy trên Dev +(code có trên đĩa, chưa restart API). ⇒ Ngay hôm nay, người dùng mở màn tạo HĐ chỉ thấy đúng 1 lựa chọn: `QT-HD-V2-001`. + +### 0.3 🔴 ĐỌC ASSERT TRƯỚC KHI CHỌN HÌNH RÀO (bài S187 W1-GREEN) + +Đề bài viết: *"`Code` phải == `CodeFor(contractType)`; workflow mã NGOÀI họ/khác loại ⇒ lỗi"* (dạng **bằng-nhau-tuyệt-đối**). +Đo trên đĩa thì hình đó **ĐỎ HÀNG LOẠT** ở tests/** — mà tests/** tôi **CẤM sửa**: + +| Test (không được sửa) | Pin mã gì | Hình bằng-nhau-tuyệt-đối cho kết quả | +|---|---|---| +| `CreateContractFromSigningPlanTests` T1-T11 (`:186` `QT-HD-V2-K7-{tag}`, `TestType=HopDongThauPhu` ⇒ họ đòi `HD-TP`) | ngoài họ | **ĐỎ** cả 11 ca, kể cả ĐỐI CHỨNG DƯƠNG `:496` | +| T8 `:543` — HĐ do cầu đẻ đem TRÌNH qua `TransitionAsync` | ngoài họ | **ĐỎ** nếu cửa TRÌNH cũng bằng-nhau-tuyệt-đối | +| `ContractV2PipelineTests` `:609` `QT-CT-T7` + `:624` submit | ngoài họ | **ĐỎ** | +| `ContractWorkflowServiceApproveV2Tests` `QT-CT-TERMINAL` / `QT-CT-F2` | ngoài họ | ĐỎ nếu siết cả đường duyệt | + +Và chính chữ owner (86) là **"mirror KHKK/Duyệt NCC"** — khuôn KHKK +(`ContractSigningPlanWorkflowGuards.cs:56-64`) khai TƯỜNG MINH semantics: *mã TRONG họ phải khớp · mã NGOÀI họ CHO QUA +có chủ đích ("không mang thông tin nhóm ⇒ không có gì để đối chiếu, chặn nó là bịa ràng buộc")*. + +⇒ **Hình đã chọn = KHOÁ THEO KHÔNG-GIAN-TÊN `HD-`** (chặt hơn KHKK một nấc, vẫn khớp disk + khớp chữ owner): + +``` +code bắt đầu bằng "HD-" ⇒ PHẢI == CodeFor(type) (sai ⇒ 409 "gắn chéo loại") +code NGOÀI "HD-" ⇒ CHO QUA (QT-HD-V2-001, QT-CT-*, quy trình admin tự đặt) +``` + +Nó bắt ĐÚNG cái owner sợ (pin `HD-MB` lên HĐ Thầu phụ = đổi chéo loại) **và** bắt thêm ca gõ nhầm trong chính +không-gian-tên họ (`HD-TPX`, `HD-TP-V2` — sai thì nổ, không im lặng), mà **0 test ĐỎ** (0 mã test nào bắt đầu bằng `HD-`; +đã grep toàn tests/: các mã là `QT-*`, `K7-*`, `BTBM`, `FLOCK01`). +Lệch đề bài đúng 1 nấc này khai ở §4 để lead bác nếu muốn siết tiếp (siết = phải sửa tests/, việc của test-specialist). + +### 0.4 Loại exception — đề bài nói "ValidationException", đĩa nói KHÔNG + +- `Common/Exceptions/ApplicationException.cs:10-25`: `ValidationException` **KHÔNG có ctor nhận message** + (message cố định "Có lỗi trong dữ liệu gửi lên.", chỉ mang `Errors` dict) ⇒ "ValidationException tiếng Việt rõ ràng" + không dựng được nếu không sửa file exception dùng chung. +- `tests/.../CreateContractCommandApplicableTypeTests.cs:88-89` assert **`ConflictException`** + `"*ApplicableType=Contract*"`, + và chú thích `:22-24` nói thẳng "spec mention ValidationException — thực tế throw ConflictException". +- Khuôn KHKK `EnsureWorkflowGroupMatchAsync` cũng `ConflictException`. +⇒ **Dùng `ConflictException` (409) + message tiếng Việt rõ ràng**; `NotFoundException` giữ cho nhánh id-không-tồn-tại +(T7-a `:470` assert đúng loại này). + +### 0.5 Ràng buộc chữ MESSAGE (2 test assert 2 chuỗi KHÁC NHAU cho CÙNG một vế) + +Vế "sai ApplicableType" nay do 1 nhà chung phát ngôn, mà hai test đòi hai chuỗi: +- `CreateContractCommandApplicableTypeTests.cs:89` → `*ApplicableType=Contract*` +- `CreateContractFromSigningPlanTests.cs:476` (T7-b) → `*không khớp với Hợp đồng*` +⇒ message hợp nhất PHẢI chứa CẢ HAI: +`Quy trình '{Code}' áp dụng cho {ApplicableType}, không khớp với Hợp đồng (cần ApplicableType=Contract).` +3 message còn lại giữ nguyên văn bản bridge (`*ngừng hoạt động*` `:482` · `*chưa được ghim*` `:488`). + + diff --git a/.claude/workflows/runs/2026-08-10-S187-duyet-hd-tu-khkk/sub-implementer-frontend-w5.md b/.claude/workflows/runs/2026-08-10-S187-duyet-hd-tu-khkk/sub-implementer-frontend-w5.md new file mode 100644 index 0000000..247063d --- /dev/null +++ b/.claude/workflows/runs/2026-08-10-S187-duyet-hd-tu-khkk/sub-implementer-frontend-w5.md @@ -0,0 +1,100 @@ +# sub-implementer-frontend-w5 — W5 card "Nguồn gốc KHKK" (F1) + vế (88) ruột pre-fill + +> Run `2026-08-10-S187-duyet-hd-tu-khkk` · vai `implementer-frontend` (WRITE, FE 2 app) · 2026-08-10. +> Đề bài: (1) card Nguồn gốc KHKK trong màn detail HĐ ×2 app; (2) ĐO vế (88) ruột pre-fill — làm TỐI THIỂU, 0 BE/0 types/0 schema; (3) mirror SHA-pair + `npm run build` ×2. +> Ghi TỪNG MỤC trong lúc làm (chống #53). File này là chân lý, return chỉ là tóm. + +## §0 — ĐO ĐĨA TRƯỚC KHI GÕ (ground-truth, không chép đề bài) + +**0a. Shape `ContractSource` — ĐỌC TYPES LÀM CHUẨN (đề bài dặn, xác nhận đúng):** +`fe-admin/src/types/contracts.ts:196-208` = `maKeHoach: string|null` · `planId: string` · `approvalGroup?: number|null` · +`tenHangMucs: string[]` · `approvedAmountTotal?: number|null` · `purchaseEvaluationId?: string|null`. +Cắm vào `ContractDetail` ở `:182` dạng `source?: ContractSource | null` — comment `:174-181` khai rõ **2 nghĩa +KHÁC NHAU**: `undefined` = payload/cache CHƯA có W2; `null` = BE nói "HĐ này không có nguồn KHKK". +⇒ Điều kiện render phải là `c.source != null` (bắt CẢ 2), KHÔNG dùng `c.source !== null`. + +**0b. Vỏ trang + nơi cắm card:** +- `components/contracts/ContractDetailContent.tsx` (257 dòng) = ruột dùng chung, **5 host**: admin + `ContractDetailPage:28` + `ContractsListPage:254`; user `ContractDetailPage:28` + `MyContractsPage:231` + `InboxPage:347`. + Sửa 1 file ⇒ card hiện ở cả 5 chỗ, 0 nhân bản. +- Khối cùng trang KHÔNG dùng shadcn `Card` (2 app **đều không có** `components/ui/Card.tsx` — đo `ls ui/`): + khuôn thật = `
` + `

`. + ⇒ card mới bám ĐÚNG khuôn đó, không đẻ primitive mới. + +**0c. Route đích (grep `App.tsx` ×2 app) — CÓ SẴN cả 2:** +- `/khkk/:id` (admin `:110` · user `:84`) → `KhkkDetailPage`. +- `/purchase-evaluations/:id` (admin `:91` · user `:76`) → `PurchaseEvaluationDetailPage`. +- ✅ `?group=` KHÔNG phải param chết: `KhkkDetailPage.tsx:50-52` ×2 app đọc `searchParams.get('group')`, + validate **REGEX CHUỖI** `/^[1-8]$/` (không `Number()`) rồi lấy làm `backToList` = `/khkk/list?group=n` + ⇒ giữ chỗ đứng + sidebar sáng đúng leaf nhóm. Truyền `?group=` là CÓ NGƯỜI TIÊU THỤ. + +**0d. Nhãn nhóm — KHÔNG đẻ map thứ 2 (bài học S166-K4a):** `KHKK_APPROVAL_GROUP_LABELS` +(`types/khkk.ts:140-149`) đã tồn tại và **SHA-pair identical ×2 app** (`29aa5633…`) ⇒ import lại, không chép chuỗi. + +**0e. Palette — đo trước khi gõ class:** `index.css` SHA-pair identical (`62c26bf6…`); `@theme` chỉ định nghĩa +`violet` ở stop **50/100/500/600/700** (`:50-55`), KHÔNG có 300/800. `PipelineStageFolders.tsx:41-46` chốt +tông theo GĐ: **GĐ1 PE = brand · GĐ2 KHKK = violet · GĐ3 HĐ = amberx · GĐ4 = teal**. +⇒ card nguồn-gốc dùng tông **violet** (đúng GĐ2), link PE dùng **brand** (đúng GĐ1), và **chỉ xài stop có thật**. +🔸 Ghi nhận đi ngang (KHÔNG sửa, ngoài scope): `KhkkDetailContent.tsx:477` dùng `violet-300`/`violet-800` — +2 stop này rơi về violet MẶC ĐỊNH của Tailwind (khác hệ màu `@theme`), lệch tông nhẹ; pre-existing. + +**0f. Baseline hash 2 file sẽ chạm (trước khi sửa) — cả 2 vốn đã IDENTICAL ×2 app:** +`ContractDetailContent.tsx` = `cd5ce88f211d1a801cce0626e5f6a339233cadc4` (admin ≡ user) +`pages/contracts/ContractCreatePage.tsx` = `5ec906195be1d605b16f61ada8b3113bae8ab799` (admin ≡ user) +🔸 `types/contracts.ts` KHÁC nhau ×2 app (`66b2bd54` ⟂ `3004a6e7`) — diff = **bảng màu badge phase** +(fe-user brand-tinted `-800`, fe-admin `-700`, chú thích 2026-06-16 của anh). Lệch CÓ CHỦ ĐÍCH, **không phải +việc của W5** và đề bài CẤM đụng types ⇒ không chạm. + +## §1 — Card "Nguồn gốc Kế hoạch ký kết" (F1) + +**Landed:** `components/contracts/ContractDetailContent.tsx` ×2 app — 1 file, 5 host (admin +`ContractDetailPage`+`ContractsListPage`; user `ContractDetailPage`+`MyContractsPage`+`InboxPage`). +Vị trí: NGAY SAU `
` "Thông tin HĐ", TRƯỚC "Góp ý" (người mở HĐ cần biết nó đẻ từ đâu +trước khi đọc góp ý/chi tiết). +3 import: `Link` (react-router-dom) · `GitBranch` (lucide, đã +dùng sẵn ở `WorkflowSummaryCard.tsx:1` nên chắc chắn có trong version đang cài) · +`KHKK_APPROVAL_GROUP_LABELS` (`@/types/khkk`). + +**Ruột card (5 mảnh, đúng đề bài):** +1. Mã `src.maKeHoach` → ``. +2. Badge nhóm `Nhóm N{g} · {LABELS[g]}` khi `approvalGroup != null` — **import map, 0 chuỗi chép**. +3. `tenHangMucs` → chip list + **đếm ngay trên nhãn** `Hạng mục đưa vào HĐ (n)`. +4. `Σ approvedAmountTotal` qua **chính `fmtMoney` của file** (`v.toLocaleString('vi-VN') + ' VND'`) + — không đẻ formatter thứ 2. +5. Link "Mở phiếu Duyệt NCC →" `/purchase-evaluations/{peId}` khi có; không có ⇒ `—` (không giấu ô). + +**3 quyết định có bằng chứng (không phải khẩu vị):** +- **Điều kiện render `src != null`** (loose) — bắt CẢ `undefined`; `types/contracts.ts:174-182` + khai 2 nghĩa khác nhau, cả 2 đều không có gì để vẽ. +- 🔴 **KHÔNG link khi `maKeHoach === null`** (lệch 1 chữ so với đề bài — xem FLAG-1 §5). + Đo: BE lấy mã qua query CÓ global filter (`ContractFeatures.cs:738-740`) ⇒ `maKeHoach === null` + ⇔ phiếu cha **đã xoá mềm**; mà màn `/khkk/:id` đọc bằng `GetContractSigningPlanQuery` + (`ContractSigningPlanFeatures.cs:957-962`) **KHÔNG** `IgnoreQueryFilters` (grep cả folder: hit + `IgnoreQueryFilters` DUY NHẤT ở `:1383` = màn "Đã xoá") ⇒ link đó **chắc chắn 404**. Thay bằng + chữ NHÌN THẤY ĐƯỢC "(phiếu đã xoá) — không mở được phiếu gốc", **không** giấu sau tooltip + (bài S182: thông tin PHÂN BIỆT không được nằm sau hover). +- **Tông màu:** violet = GĐ2 (KHKK), brand = GĐ1 (PE) theo `PipelineStageFolders.tsx:41-46`; + chỉ dùng stop violet CÓ THẬT trong `@theme` (50/100/500/600/700). `violet-300/800` ở + `KhkkDetailContent.tsx:477` = pre-existing, KHÔNG sửa (ngoài scope). + +**Đối chứng shape (không tin types một mình):** đọc thẳng BE `ContractSourceDto` +(`Contracts/Dtos/ContractDtos.cs`) = `PlanId · MaKeHoach · ApprovalGroup · PurchaseEvaluationId · +TenHangMucs · ApprovedAmountTotal` ⇒ camelCase khớp TỪNG TÊN với `ContractSource` FE. BE **có +điền thật** (`ContractFeatures.cs:720-748` + `Source: sourceDto` `:839`), không phải field chết. +🔸 Lệch nhỏ khai luôn: BE `ApprovedAmountTotal` là `decimal` **non-null**, FE khai `?: number|null` +(siêu-tập phòng thủ) ⇒ card vẫn null-check, hiện `—` nếu vắng. + +## §2 — Vế (88): ĐO bridge-map ⟂ màn edit Nháp + +(đang ghi…) + +## §3 — Mirror ×2 app + SHA-pair + +(đang ghi…) + +## §4 — Build ×2 + control-âm + +(đang ghi…) + +## §5 — FLAG / nợ treo + +(đang ghi…) diff --git a/.claude/workflows/runs/2026-08-10-S187-duyet-hd-tu-khkk/sub-implementer-frontend-w6.md b/.claude/workflows/runs/2026-08-10-S187-duyet-hd-tu-khkk/sub-implementer-frontend-w6.md new file mode 100644 index 0000000..b1f9e64 --- /dev/null +++ b/.claude/workflows/runs/2026-08-10-S187-duyet-hd-tu-khkk/sub-implementer-frontend-w6.md @@ -0,0 +1,102 @@ +# sub-implementer-frontend-w6 — W6-FE màn duyệt V2-trạm HĐ + Designer type-3 family-slice + +> Lane: implementer-frontend (WRITE, FE-only). Run S187 YC-023 GĐ3. +> Đề bài: (1) `ContractWorkflowPanel` ×2 app mirror `KhkkWorkflowPanel` owner-trim 2 nút · +> (2) wire vào `ContractDetailContent.tsx` ×2 app dual-render V1/V2 · (3) Designer type-3 +> family-slice 7 mã `HD-*` khuôn KHKK nhóm · (4) SHA-pair + `npm run build` ×2. +> Đáp án owner (84) = **2 nút Duyệt / Trả lại**, KHÔNG render Từ chối (máy BE giữ). + +## §0 — ĐO ĐĨA TRƯỚC KHI GÕ (tiền-đề đề bài vs thực tế) + +Baseline SHA trước khi chạm (đo lúc mở lane): + +| file | SHA256 (12 đầu) | ghi chú | +|---|---|---| +| `fe-{admin,user}/src/components/contracts/ContractDetailContent.tsx` | `112ccbcae77d` | **ĐANG PAIR** (W5 vừa land, cả 2 `M` trong git status) | +| `fe-{admin,user}/src/components/contracts/WorkflowHistoryPanel.tsx` | `efd01a3936b3` | pair, KHÔNG chạm wave này | +| `fe-{admin,user}/src/components/khkk/KhkkWorkflowPanel.tsx` | `e7b294c9dbad` | khuôn nguồn, pair ⇒ primitives 2 app parity | +| `fe-admin/src/types/contracts.ts` | `3d2da3bd2993` | 🔴 **KHÔNG pair** với fe-user (`1d4d649f87ae`) | +| `fe-user/src/types/contracts.ts` | `1d4d649f87ae` | lệch PRE-EXISTING: bảng `ContractPhaseColor` khác tông/app (admin -700, user -800 + brand). Đề bài CẤM chạm file này ⇒ giữ nguyên, KHÔNG "vá" thành pair. | + +### Đ0.1 — Wire THẬT của "endpoint duyệt V2" (đề bài nói "grep approve/return endpoints") + +🔴 **KHÔNG có endpoint V2 riêng.** `ContractsController.cs` chỉ có **một** cửa transition: +`POST /api/contracts/{id}/transitions`, body `TransitionContractBody(TargetPhase, Decision, Comment)` +(`ContractsController.cs:64-69` + `:244`). `ApproveV2Async` là **private** trong +`ContractWorkflowService.cs:230`, tới được bằng DISPATCH: `TransitionAsync` `:105` bắt +`fromPhase == ChoDuyet && decision == Approve`, rồi `:111` `if (contract.ApprovalWorkflowId is Guid awId)` +→ V2; else → V1 legacy. ⇒ FE **không** gọi đường mới, chỉ gọi đúng cửa cũ với đúng cặp +(targetPhase, decision): + +- **Duyệt** → `{ targetPhase: 10 (ChoDuyet), decision: 1 (Approve), comment }`. + Nhánh approve `:105` KHÔNG đọc `targetPhase` (BE tự tính phase kế: lên Cấp / lên Bước / + `DaPhatHanh` + gen mã). Vẫn phải gửi 1 giá trị hợp lệ vì validator `IsInEnum()` + (`ContractFeatures.cs:210`) ⇒ gửi `ChoDuyet` = trung tính, không hứa phase đích. +- **Trả lại** → `{ targetPhase: 98 (TraLai), decision: 2 (Reject), comment }`. + Nhánh reject `:49-66`: `targetPhase == TuChoi` ⇒ TuChoi, **else ⇒ TraLai**. Gửi 98 tường minh + thay vì "số nào cũng được miễn khác 99". + +### Đ0.2 — 3 mảnh của khuôn KHKK KHÔNG port được (đo ra, không phải chọn cho gọn) + +1. ❌ **Ô-tích "Cấp này KẾT THÚC"** — `TransitionContractBody` có ĐÚNG 3 field; `skipToFinal` + là tham số C# default (`TransitionAsync(..., bool skipToFinal = false)`) **không nằm trên + API surface** ⇒ render ô-tích = control chết, bấm xong không đổi gì. +2. ❌ **Badge "⚑ Duyệt thay CEO"** trên Cấp có `allowApproverFinalize` — 🔴 `ApproveV2Async` + của HĐ **KHÔNG ĐỌC** `AllowApproverFinalize` một lần nào (`ContractWorkflowService.cs:230-407`; + cờ duy nhất nó đọc là `AllowApproverSkipToFinal` `:335`, cho đường skip mà FE không gọi được). + DTO có trả field (`ContractDtos.cs:138`) và chú thích của chính W2 nói "dùng hay không là + quyết của W6". ⇒ Vẽ badge nói "duyệt ở đây là KẾT THÚC" là **nói dối**: duyệt xong vẫn chạy + tiếp Cấp sau. Không render. +3. ❌ **Banner kết-thúc-sớm + meta `{code} v{NN} · {name}`** — `ContractDetail` **0 field** + `endedByLevelFinalize` / `workflowCode` / `workflowVersion` / `workflowName` (đo cả type + `types/contracts.ts:141-191`). Lấy `c.workflow.policyName` thế vào là ghép nhãn của + **workflow policy V1** (`WorkflowSummary`) vào quy trình V2 — 2 vật khác nhau. Không render. + (Cây thô đã inline sẵn trong detail ⇒ cũng KHÔNG gọi thêm `/approval-workflows-v2/{id}`, + đúng lời W2: "KHÔNG đẻ đường thứ 3".) + +### Đ0.3 — 2 khối KHKK cố ý bỏ vì TRÙNG chỗ đã có trên cùng trang + +`ContractDetailPage` render `ContractDetailContent` **và** `WorkflowHistoryPanel` +(`WorkflowHistoryPanel.tsx:38-65` đã có "Lịch sử duyệt (N)" + `:67-73` "Lịch sử điều chỉnh"). +⇒ panel mới KHÔNG đẻ bản thứ 2 của 2 khối đó. Khối "📎 File đính kèm khi duyệt" cũng bỏ: +HĐ không có đường `approval-attachments` riêng (đính kèm đi qua +`ContractAttachmentsSection` đã render sẵn trong `ContractDetailContent`). +⇒ Panel W6 = khối **1 (header) + 3 (stepper) + banner lượt-ai + 4 (2 nút + dialog)**. + +### Đ0.4 — FLAG-BE (không chạm, báo lead) + +🔴 **Nhánh Reject `ContractWorkflowService.cs:48-66` KHÔNG có guard đúng-trạm.** Approve V2 có +(`:268-279` match `ApproverUserId` ∈ Cấp đang chờ, Admin bypass); Reject thì **bất kỳ user đã +đăng nhập nào** POST `{decision:2}` là HĐ về `TraLai`. Panel W6 có `disabled` nút Trả lại cho +người ngoài Cấp, nhưng **đó là trang trí** (gotcha #82 — 2 tầng độc lập). Đúng vế "Đ2" mà +`sub-plan-review §7` đã treo cho W6 (phần BE `ContractWorkflowService.cs:48-66`). **Ngoài lane +FE ⇒ KHÔNG sửa, báo lead.** + +--- + +## §1 — Việc đã làm + +### 1.1 — Nối lượt chết: ĐO LẠI ĐĨA trước khi gõ (3/4 tiền-đề của lệnh nối SAI) + +Lệnh nối nói *"import & cờ `isV2` đã khai ×2 app (unused — wire dở)"*. Đo tươi lúc mở lượt: + +| đo | lệnh | kết quả | +|---|---|---| +| `ContractWorkflowPanel.tsx` ×2 | `git hash-object` | `48b72391` **=** `48b72391` — pair, đúng như brief | +| `ContractDetailContent.tsx` ×2 | `git hash-object` | admin `3148173d` ⟂ user `42009bd1` — **KHÔNG pair** | +| nội dung lệch | `git diff --no-index` | **+33/−1, đúng 3 khối W6** | +| `grep -n "ContractWorkflowPanel\|isV2"` ×2 app | ripgrep | **fe-user: 6 hit** (import + `isV2` + `!isV2 &&` header + `{isV2 && }`) · **fe-admin: 0 hit** | + +⇒ Sự thật khác brief: **fe-user KHÔNG "dở"** — wire đã TRỌN và không có biến unused nào (`isV2` được đọc 2 chỗ, +`ContractWorkflowPanel` được render). Cái chết ở lượt trước là **fe-admin chưa hề chạm**, tức lệch **MIRROR**, +không phải lệch *unused-vars*. Vá đúng bệnh: `cp` fe-user → fe-admin (giữ trọn 33 dòng W6 + mọi thứ khác +byte-y-hệt), KHÔNG gõ lại tay — gõ lại là mời sai lệch vào đúng cặp file phải identical. + +**Kết quả:** `git hash-object` ×2 = `42009bd1` **=** `42009bd1` ✅ · `git diff --no-index` **rc=0** (0 dòng lệch). + +3 khối wire (nay có ở CẢ 2 app): (a) import + ghi chú V1-không-tải-nhánh-này; (b) `const isV2 = +c.approvalWorkflowId != null` + khối ghi chú "một cờ ba nơi đọc"; (c) `!isV2 &&` chặn 2 nút header V1 + +`{isV2 &&
}` đặt NGAY SAU card "Nguồn gốc KHKK". +🔴 Nhánh V1 legacy **KHÔNG đổi một byte** — chỉ bọc thêm điều kiện `!isV2` ở JSX bao ngoài; `openAction`, +`transition`, `Dialog` chọn-phase, `availableTargets` giữ nguyên hoàn toàn. + diff --git a/.claude/workflows/runs/2026-08-10-S187-duyet-hd-tu-khkk/sub-reviewer-cum.md b/.claude/workflows/runs/2026-08-10-S187-duyet-hd-tu-khkk/sub-reviewer-cum.md new file mode 100644 index 0000000..92d3276 --- /dev/null +++ b/.claude/workflows/runs/2026-08-10-S187-duyet-hd-tu-khkk/sub-reviewer-cum.md @@ -0,0 +1,51 @@ +# sub-reviewer-cum — Review ADVERSARIAL cụm W3+W4+W5+W6 (YC-024/025) TRƯỚC push + +**Run:** `2026-08-10-S187-duyet-hd-tu-khkk` · **Vai:** reviewer (adversarial, verdict-only, KHÔNG sửa code) +**Ngày:** 2026-08-10 · **Relaunch** (lượt trước chết 0-trace) + +## Đề bài tóm + +Soi diff `git diff` + untracked (20 file) trước push. Trọng tài đã xanh: full test 680/680, build FE ×2 OK, 0 Migrations mới. + +Trục rủi ro lead giao: +1. 🔴 Seeder W3 chạy thẳng prod (#51): `SeedContractTypeWorkflowsAsync` idempotent per-Code; roster builder `applyLevel2Finalize:false` cho họ HD; KHKK sau refactor GIỮ NGUYÊN (`true`); menu-row 7 key seed được trên DB prod CŨ hay chỉ DB mới? +2. 🔴 Guard W4: 4 cửa đủ; backward-compat (V1 + mã-ngoài-họ chỉ chặn pin MỚI); message giữ 2 chuỗi test cũ; UpdateDraft field cuối; vì sao `ContractSigningPlansController.cs` đổi. +3. 🔴 Đ2 W6-BE: V2 sai-trạm bị chặn thật; V1 giữ nguyên; DaPhatHanh bất khả xâm phạm. +4. FE: card Nguồn-gốc null-safe; panel 2-nút gating OR-of-N; dual-render V1; Designer type-3 slice; `contractWorkflowFamily.ts` nguồn-đôi 7 mã vs BE; SHA-pair; unused/CS8019. +5. STATUS 2 row đúng số đếm; 0 secret; tiếng Việt. + +**Diff scope đo được:** 15 file modified (+1223 / −137) + 6 file untracked. + +--- + +## Nhật ký đo (append theo mục) + +### 🔴 C-1 (BLOCKER) — 7 leaf menu `AwV2_Hd*` DROP IM LẶNG khỏi sidebar (gotcha #50, Pattern 16-bis "chỗ thứ 4") + +**Đo:** +- `git status --porcelain fe-admin/src/components/Layout.tsx` → **RỖNG** (file KHÔNG đổi trong cụm này). +- `grep -n "contractWorkflowFamily|findContractWfByTypeCode|Hd" fe-admin/src/components/Layout.tsx` → **0 hit**. +- Control dương cùng lệnh: `grep -n "AwV2_"` cùng file → hit `:175` `key.match(/^AwV2_(.+)$/)` + `:187` `/^KhkkN[1-8]$/` ⇒ thước sống, 0-hit là **vắng THẬT**. +- `Layout.tsx:30` `resolvePath` nhánh `AwV2_`: chỉ nhận `DuyetNcc` · `DuyetNccPhuongAn` · `Contract` · `/^KhkkN[1-8]$/`. `AwV2_HdTP` → rơi xuống `return null`. +- `Layout.tsx:331-335` `MenuLeaf`: `const path = resolvePath(node.key); if (!path) return null` ⇒ leaf biến mất **không báo lỗi**. + +**Hệ quả:** BE seed 7 menu-row + 28 policy vào DB (kể cả prod, seeder ungated); admin thấy… **không gì cả** dưới "Quy trình duyệt (Mới)". Panel Designer 7 loại (đã viết ở `ApprovalWorkflowsV2Page.tsx:310-420`) **chỉ tới được bằng gõ tay URL** `/system/approval-workflows-v2/HdTP`. Đây đúng lớp "máy dựng mà không ai gọi". + +**Bằng chứng lời-hứa-chưa-land (file MỚI tự khai):** `fe-admin/src/lib/contractWorkflowFamily.ts:6-11` viết *"Có ĐÚNG HAI chỗ đọc … 2. `components/Layout.tsx` `resolvePath` — đổi menu-key `AwV2_Hd*` thành đường dẫn ("chỗ thứ 4" Pattern 16-bis; thiếu là leaf DROP IM LẶNG khỏi sidebar — gotcha #50)"*. `grep -rn "contractWorkflowFamily" fe-admin/src fe-user/src` → **chỉ 1 importer** (`ApprovalWorkflowsV2Page.tsx:39`). Consumer #2 mà chính file khai = **chưa tồn tại**. + +**AC để đóng:** thêm nhánh vào `resolvePath` (khuôn `/^KhkkN[1-8]$/` `:187`) dùng `findContractWfByTypeCode(code) !== null` ⇒ `return '/system/approval-workflows-v2/' + code`; kiểm bằng đếm 7 `` con dưới root `ApprovalWorkflowsV2` sau khi seed (control âm: `HdXX` bịa phải vẫn null). + +### C-2 (MAJOR) — mirror `menuKeys.ts` ×2 app thiếu 7 key mới + +`grep -rn "AwV2_" fe-admin/src/lib/menuKeys.ts fe-user/src/lib/menuKeys.ts` → có đủ 10 key cũ (`AwV2_DuyetNcc`, `AwV2_DuyetNccPhuongAn`, `AwV2_KhkkN1..N8`) nhưng **0 key `AwV2_Hd*`**. Tiền lệ ngay trong file (`fe-admin/src/lib/menuKeys.ts:34-38`) khai rõ lý do phải chép cả key chưa có call-site: *"chép vào để bảng đối chiếu FE↔BE không khuyết, tránh lượt sau đọc chỗ này rồi kết luận 'BE chưa có key nhóm'"*. Bỏ 7 key = tái lập đúng cái bẫy mà chú thích đó dựng để chặn. Không chặn build (chỉ là bảng tra), nên MAJOR không BLOCKER. + +### ✅ SHA-pair 3 cặp FE mirror — ĐẠT (đo `sha256sum`, 16 ký tự đầu) + +| Cặp | sha (16) | KQ | +|---|---|---| +| `ContractWorkflowPanel.tsx` admin/user | `9cdf9ffe649dbc5d` | ✅ identical | +| `ContractDetailContent.tsx` admin/user | `cd81508f97cb79fc` | ✅ identical | +| `ContractCreatePage.tsx` admin/user | `17edc625ddc9654b` | ✅ identical | + +`contractWorkflowFamily.ts` **fe-admin only** — KHÔNG-CẶP có chủ đích, khai tại `:16-18` (fe-user 0 route Designer). Đo xác nhận: `ls fe-user/src/lib/contractWorkflowFamily.ts` → không tồn tại. Hợp lệ. + diff --git a/.claude/workflows/runs/2026-08-10-S187-duyet-hd-tu-khkk/sub-test-specialist-w3.md b/.claude/workflows/runs/2026-08-10-S187-duyet-hd-tu-khkk/sub-test-specialist-w3.md new file mode 100644 index 0000000..bffdda0 --- /dev/null +++ b/.claude/workflows/runs/2026-08-10-S187-duyet-hd-tu-khkk/sub-test-specialist-w3.md @@ -0,0 +1,77 @@ +# sub-test-specialist-w3 — khoá drift menu-key/policy cho +7 key Designer `AwV2_Hd*` (W3) + +> Run `2026-08-10-S187-duyet-hd-tu-khkk` · vai `test-specialist` (WRITE độc quyền `tests/**`) · 2026-08-10. +> Spec-change theo `docs/rules.md §7`: test cũ update + test mới CÙNG wave với code. Lane +> `implementer-backend` land `MenuKeys.cs` + `SeedMenuTreeAsync` SONG SONG ⇒ ĐỎ-chờ-lane là trạng +> thái HỢP LỆ, nhưng phải chứng ĐỎ vì "key chưa có" chứ không phải vì test sai. +> Ghi TỪNG MỤC trong lúc làm (chống #53). Mục chưa chạy chỉ có tiêu đề + "CHƯA ĐO". + +## §0 — Ground-truth ĐO TƯƠI trước khi viết + +| Thứ đo | Lệnh | Kết quả | +|---|---|---| +| Cây làm việc | `git status --porcelain` | CHỈ `M docs/governance/so-yeu-cau-chu-du-an.md` (không phải của tôi). **`tests/**` SẠCH · `src/**` SẠCH** ⇒ lane impl W3 CHƯA land lúc tôi bắt đầu | +| HEAD | `git log --oneline -1` | `0f4bc184 wal: flush 20260810T1840` | +| Baseline file đích | `dotnet test --filter "~KhkkGroupMenuSeedTests\|~ContractCatalogTests"` | **Passed: 15, Failed: 0** (3 s) — build 0 error | +| `MenuKeys.All` hôm nay | đọc mã `MenuKeys.cs:222-250` + số neo trong test | **113** (= `docs/STATUS.md` row `Menu keys`) · `Policies` = 113×4 = **452** | +| 7 mã quy trình đích | đề bài lead + `UpdateAwDefinitionTests.cs:1574` (lane t0 đã ghim làm DỮ LIỆU) | `HD-TP · HD-GK · HD-NCC · HD-DV · HD-MB · HD-NT-NCC · HD-NT-DV` | + +🔴 **KHÔNG đo full-suite** ở lượt này: lane impl có thể sửa `src/**` bất cứ lúc nào ⇒ số full-suite đo +giữa chừng là snapshot dở (#68) và không phải số của tôi. Số tôi chịu trách nhiệm = filter theo file đích. +Phán quyết cuối = gate lead chạy sau khi mọi lane dừng tay. + +## §1 — Tra CHỒNG LẤN trước khi viết (bài S166: bước đắt nhất) + +`grep -rn "MenuKeys.All" tests/` → 4 file, 3 nhóm mệnh đề đã có: + +| Đã có ở đâu | Đo cái gì | Có phủ 7 key mới không | +|---|---|---| +| `ContractCatalogTests.cs:445` (T6 lane K1) | `MenuKeys.All.Except(menuKeysInDb).BeEmpty()` sau `SeedMenuTreeAsync` — **mọi** key ∈ `All` phải có menu-row (thiếu row = Permission FK nổ lúc boot) | ✅ CÓ (tự động, by-construction) ⇒ **KHÔNG nhân bản** invariant này | +| `KhkkGroupMenuSeedTests.cs:131` (T2 lane K4a) | `MenuKeys.All.HaveCount(113)` — số neo duy nhất trong toàn `tests/` cho 2 row canonical STATUS | ⚠️ CÓ nhưng SỐ CŨ ⇒ **phải update 113→120** (đây chính là "khoá drift" của đề bài) | +| `AuthorizePolicyRegressionTests.cs:139/:432` | policy trên controller phải trỏ key ∈ `All` | Không liên quan (chưa có controller nào dùng `AwV2_Hd*`) | + +**Lỗ hổng đo được:** `grep -rn "KhkkN\|AwV2" tests/` → **0 hit** ⇒ họ 10 leaf Designer `AwV2_*` (2 PE + 8 +KhkkN) hôm nay **KHÔNG có test nào** về hình cây/menu-row/grant. Tức khuôn mà W3 đang mirror chưa từng +được khoá. File mới bên dưới khoá CẢ họ cũ (control dương, xanh hôm nay) LẪN họ mới (đỏ tới khi land). + +## §2 — Quyết định thiết kế: vì sao KHÔNG viết tên key ra chữ + +🔴 Rủi ro lớn nhất của lượt này: spec (`spec-duyet-hd-10-08-2026.md:10` + `sub-plan-review §5`) chốt +**"CÓ sinh 7 key `AwV2_Hd*`"** nhưng **KHÔNG chốt chuỗi ký tự cụ thể** (`AwV2_HdTp`? `AwV2_HdThauPhu`? +theo `ContractTypeCodes` hay theo token mã `HD-TP`?). Nếu tôi đoán tên rồi viết `MenuKeys.ApprovalWorkflowHdTp`: +- đoán trúng → xanh; +- đoán trượt → **lỗi BIÊN DỊCH** ⇒ sập cả test-project ⇒ ĐỎ vì "test sai", đúng thứ đề bài cấm. + +⇒ Luật tự đặt cho file này: **0 tham chiếu tới symbol C# chưa tồn tại**. Họ key mới được PHÁT HIỆN bằng +phép trừ trên `MenuKeys.All` (đã tồn tại): `All.Where(StartsWith "AwV2_")` **trừ** 10 key cũ (tham chiếu +qua const có thật). Nhờ vậy: +- hôm nay: tập rỗng ⇒ đỏ tại vế tiền-đề với thông điệp "chưa có key nào" (= "key chưa có", đúng lý do); +- sau khi land với BẤT KỲ hậu tố nào (`AwV2_HdTp` / `AwV2_HdThauPhu` / …): xanh, không phải sửa test; +- nếu land LỆCH KHUÔN tên (vd `AwV2_HD_TP` hoa/`AwV2_ContractTp`): vẫn phát hiện được và báo đúng chỗ + bằng vế "mọi key họ mới phải bắt đầu bằng `AwV2_Hd`" — đó là mệnh đề SPEC, không phải đoán mò. + +**Cố ý KHÔNG viết (khai để không ai tưởng đã phủ):** +- Ánh xạ 1-1 `AwV2_Hd{X}` ↔ mã quy trình `HD-{X}`: không dựng được phép đo không-tautology khi chưa + biết luật biến hình token (`NT-NCC` → `NtNcc`? `NguyenTacNcc`?). Ai chốt tên thì bổ vế này sau — ghi + vào §6 làm nợ. +- Số workflow `HD-*` do seeder W3 tạo: thuộc lane seed (t0 đã ghim `ContractFamilyCodes` ở + `UpdateAwDefinitionTests.cs:1574` như DỮ LIỆU). File này chỉ đo MENU/POLICY. +- `RevokeTemporarilyHiddenModulesAsync`: đọc mã `:2568-2571` — bộ lọc là `StartsWith("Hrm") || + StartsWith("Off") || == Personal` ⇒ `AwV2_*` nằm ngoài; hơn nữa site-2 `:2413` CỐ Ý loại `AwV2_*` + (admin-only). Không có ca thật ⇒ không viết test trang trí. + +## §3 — Đã sửa/thêm gì + +CHƯA ĐO + +## §4 — Chạy: đỏ có đúng lý-do "key chưa có" không + +CHƯA ĐO + +## §5 — Hồi quy + phạm vi file + +CHƯA ĐO + +## §6 — Nợ / bàn giao cho gate W3 + +CHƯA ĐO