26 KiB
reviewer — LĂNG-KÍNH 2/3: KHẢ-THI KỸ-THUẬT + SỰ-THẬT DỮ-LIỆU
Lane 2/3 của ENSEMBLE
/fable-cloneS171. Chấm bảnsub-investigator-codebase-invest-s171.md. PROPOSE-ONLY / READ-ONLY. Mọi con số dưới đây do lane này TỰ đo, không chép từ bản invest.
0. Tái-dựng số load-bearing (bắt buộc ≥2 — tôi làm 4)
| # | Số bản invest nêu | Lệnh tôi chạy | Đo được | Phán |
|---|---|---|---|---|
| R1 | WorkflowService.cs:534 ghi ContractSigningPlanApprovals |
grep -n "ContractSigningPlanApprovals\.Add" |
:534 đúng y |
KHỚP |
| R2 | changelog ghi ≥9 chỗ (liệt kê 8: :547 + Features.cs:497,562,639,1078,1116,1224,1304) |
grep -n "ContractSigningPlanChangelogs\.Add" src/Backend |
10 chỗ: WorkflowService.cs:546 · Features.cs:497,562,639,1078,1116,1224,1304,1372 · CreateContractFromSigningPlanFeatures.cs:226 |
KHỚP về mệnh-đề (10 ≥ 9) — nhưng liệt-kê thiếu 2 (xem §7-B3) |
| R3 | BuildStagesOptions chỉ stage1Content/stage1Count (:163) |
đọc usePipelineStages.ts:161-165 |
đúng 2 field, đúng dòng | KHỚP |
| R4 | DetailDto ở Features.cs:145-181 |
đọc :145-181 |
record đóng đúng tại :181 (bool BudgetFrozen);), 37 tham-số |
KHỚP |
⇒ 4/4 con-số load-bearing của bản invest tái-dựng được. Bản đó không bịa số. Chỗ nó vỡ là suy-luận từ số, không phải số. Ghi rõ để lead đừng hạ tín-nhiệm nhầm chỗ.
1. D-2 — ContractSigningPlanWorkflowService.cs:534 ghi Approvals: LUÔN hay 1-NHÁNH?
1.0 Đính-chính ĐƯỜNG-DẪN (bản invest ghi thiếu, người sau sẽ mở trượt)
File KHÔNG nằm ở Application/ContractSigningPlans/. Đường-dẫn thật:
src/Backend/SolutionErp.Infrastructure/Services/ContractSigningPlanWorkflowService.cs
(find src -iname "*ContractSigningPlan*" → chỉ 1 file mang tên đó). Bản invest trích ContractSigningPlanWorkflowService.cs:534 trần, không path; ai đoán theo hàng-xóm ContractSigningPlanFeatures.cs (Application) sẽ tìm không ra. Lỗi nhẹ, nhưng A-1/A-2 là checklist thi-công nên phải đúng path.
1.1 🟢 Giả-thuyết "chỉ ghi 1 nhánh" của lane tôi: SAI. Claim D-2 HELD.
Tôi vào săn kẽ này và không có kẽ. Đo bằng grep -n "LogSigningPlanTransitionAsync" — 6 call-site, phủ TRỌN mọi transition:
| call-site | Ngữ-cảnh | Decision truyền vào |
|---|---|---|
:204 |
Gửi duyệt / gửi lại sau Trả lại | ApprovalDecision.Pending |
:283 |
Duyệt + level-finalize (kết thúc sớm) | Approve |
:296 |
Duyệt → chuyển Cấp kế | Approve |
:311 |
Duyệt → hoàn tất Bước, sang Bước kế | Approve |
:325 |
Duyệt Cấp cuối → DaDuyet |
Approve |
:393 |
Trả lại VÀ Từ chối — targetPhase động, comment, isReturn ? … : … chọn summary |
ApprovalDecision.Reject |
:534 db.ContractSigningPlanApprovals.Add(...) nằm thẳng trong thân LogSigningPlanTransitionAsync (:521-544), không có if nào bọc — mọi tham-số quyết-định (fromPhase/toPhase/decision/actingLevelId/summary) đều là đối-số, không phải hằng.
⇒ Entry "Trả lại → Lùi về Cấp 2" của Ảnh 2 SẼ CÓ. Kẽ mà lệnh của lead nghi ngờ không tồn tại; ước-lượng chi-phí D-2 không bị lật.
🔸 Ghi rõ để lead khỏi mất công: đây là chỗ bản invest đúng mà owner/lead có thể tưởng sai — nó đúng vì tác-giả LogSigningPlanTransitionAsync đã cố-ý gom 2 bảng vào một unit-of-work chung (docstring :515-520), chứ không phải vì may.
1.2 Nhưng có 1 điều D-2 không nói mà ảnh-hưởng đến hình hiển-thị
Approvals cũng chứa dòng Decision = Pending của hành-vi GỬI duyệt (:204-207), tức "Lịch sử duyệt (N)" của KHKK sẽ có nhiều dòng hơn số lần ai đó thật sự bấm Duyệt/Trả/Từ-chối. PE xử việc này bằng lớp merge + dedupe 5s-bucket + synthetic-reject (PeDetailTabs.tsx:3152-3192, bản invest có ghi ở §1.3 mục 10 nhưng không kéo hệ-quả sang §4/§7). ⇒ FE KHKK phải quyết: hiện dòng Pending (nhật-ký đầy-đủ) hay lọc (giống PE hơn). Đây là việc không có trong bảng D, và nó nằm trên đường tới A-8d.
1.3 Tái-dựng số load-bearing #1: KHỚP tuyệt-đối
:534 = db.ContractSigningPlanApprovals.Add(...) ✅ · :547 = db.ContractSigningPlanChangelogs.Add(...) lệch 1 dòng — đo thật là :546 (:547 là {). Sai-số 1 dòng, vô-hại, ghi ra cho đủ.
Còn ResolveActorFullNameAsync bản invest ghi WorkflowService.cs:552: đo thật trong file KHKK là call-site :485 và :554; định-nghĩa hàm nằm ở file KHÁC (PurchaseEvaluationWorkflowService.cs:1301, ContractWorkflowService.cs:446). :552 không trỏ vào cái gì cả → trích-dẫn hỏng (dạng nhẹ). Nội-dung claim ("BE đã có sẵn helper") vẫn HELD.
2. D-3 — changelog ≥9 chỗ ghi + UserName denorm + 0 endpoint đọc
Cả 3 vế: HELD.
- ≥9 chỗ ghi → HELD (thật 10). Xem R2 §0.
UserNameđã denorm → HELD.ContractSigningPlanWorkflowService.cs:554:UserName = Truncate(await ResolveActorFullNameAsync(actorId, ct), 200)— tên ghi thẳng vào hàng changelog, không cần join lúc đọc. Đúng như bản invest nói.- 0 endpoint đọc → HELD.
grep -in "changelog"trênsrc/Backend/SolutionErp.Api/Controllers/ContractSigningPlansController.cs→ 0 hit. Liệt-kê đủ 18 route (HttpGet/Post/Put/Deletetại:36,54,60,73,79,86,95,108,117,129,140,152,162,182,191,205,222,243), không route nào làchangelogs. Cửa đọc thật sự chưa có.
Kèm 1 dữ-kiện làm A-13 vỡ (xem §6): đếm thật trên controller — 19 dòng [Authorize(Policy = …)] (1 class-level :29 + 18 action) / 18 route ⇒ tỷ-lệ 19 ≥ 18 và 0 [Authorize] trần THẬT. Tức hôm nay file này sạch theo gotcha #82; nhưng thước A-13 mà bản invest viết lại không đo được điều đó (§6-G1).
3. D-9 / D-8 — usePipelineStages.ts:150-320 (lỗ hổng bản invest TỰ KHAI)
3.1 Trả lời DỨT câu hỏi D-9 (unknown → known)
Đã đọc fe-admin/src/hooks/usePipelineStages.ts:120-350. Trả lời:
Phiếu KHKK cho folder GĐ2 lấy từ QUERY ĐÃ CÓ SẴN — KHÔNG phải thêm fetch mới.
- Hook
usePipelineLinkage()đã chạy querykhkkIndexvà trảkhkkByPeId: Map<peId, KhkkListItemDto[]>(usePipelineStages.ts:123-134,:147). buildStagesgom phiếu bằngpeItems.flatMap(p => linkage.khkkByPeId.get(p.id) ?? [])(:204) — đúng tập phiếu KHKK của gói thầu, kiểuKhkkListItemDto(đủmaKeHoachpeTenGoiThauphaseapprovalGroupcontractIds).toKhkkLeaf(:205-212) đã dựng sẵn leaf.stage2(:265-273) đã cóleaves+groups+locked/failed/loading/truncated. ⇒ Chặn-cửa A-0 GỠ ĐƯỢC NGAY. Không cần query mới, không cần endpoint mới cho panel 1. Đây là điểm bản invest bỏ ngỏ và nó rơi về phía RẺ HƠN ước-lượng.
3.2 D-8 — claim BuildStagesOptions chỉ có stage1: HELD (tự đo)
usePipelineStages.ts:161-165:
export type BuildStagesOptions = {
/** GĐ1 tự vẽ (trang Duyệt NCC truyền khối leaf phiếu CŨ vào đây). */
stage1Content?: ReactNode
stage1Count?: number
}
Đúng 2 field, đúng số dòng bản invest nêu (:163). HELD.
3.3 🔴 D-8 claim "sửa 1 hook, 0 đụng component trình-bày": BROKE — bản invest bỏ sót groups
Đây là chỗ vỡ thật, không phải bắt bẻ chữ:
- Stage 1 (PE) là folder PHẲNG:
{ n:1, leaves:[], content, count }(:183-186) — không cógroups. Cho nênstage1Contentthay chỗleaveslà an-toàn. - Stage 2 (KHKK) KHÔNG PHẲNG: nó có
groups: khkkGroups= 8 ngăn cố định N1-N8 + ngăn "(chưa phân nhóm)" (:220-252,:268), là feature K5 S166 cố ý "HIỆN ĐỦ 8 NGĂN KỂ CẢ RỖNG" (comment:232-234), mỗi ngăn cóonOpenAll → /khkk/list?group=nkhớp URL leaf sidebar (:239-241), và có bất-biến Σ cảnh báo DEV khi tổng ngăn ≠ tổng folder (:254-263). - ⇒ Nhét
stage2Contenttheo khuôn đối-xứng stage1 (contentthayleaves) sẽ đặt card phiếu KHKK và 8 ngăn nhóm vào tranh-chấp cùng một ô. Hoặc mất 8 ngăn, hoặc phải quyết-định thứ-tự content ⟂ groups — đó là đụng vàoPipelineStageFolders, và đụng vào một bất-biến có chuông báo. - Đối-xứng "stage1 ↔ stage2" mà bản invest dựa vào là đối-xứng giả: hai stage khác CARDINALITY (phẳng vs 2 tầng). Đây đúng lớp bài học
feedback_cardinality_change_grep_consumers.
Hệ quả chi-phí: D-8 không phải "sửa 1 hook". Tối thiểu là 1 hook + 1 quyết-định trình-bày (giữ 8 ngăn hay bỏ) + đo lại bất-biến Σ. Vẫn RẺ, nhưng không phải 0 đụng component.
3.3-bis 🔴 BẰNG-CHỨNG CỨNG: content THẮNG groups — 8 ngăn bị nuốt IM LẶNG
fe-admin/src/components/pipeline/PipelineStageFolders.tsx:285-295 (tự đo, grep -n -C4):
285: ) : stage.content !== undefined ? (
286: stage.content
287: ) : stage.groups !== undefined ? (
...
292: {stage.groups.map(g => (
content đứng TRƯỚC groups trong chuỗi tam-nguyên ⇒ hễ truyền stage2Content thì nhánh groups không bao giờ chạy. 8 ngăn N1-N8 + ngăn "(chưa phân nhóm)" biến mất, không lỗi, không cảnh-báo.
Và stageCount (:95-97) khi có content thì đọc count ?? 0 ⇒ quên truyền stage2Count là badge đếm về 0 trong khi folder đầy phiếu — lại một dạng "vắng-mặt trông giống ổn".
⇒ Claim D-8 "0 đụng component trình-bày": BROKE (2/2 bằng-chứng tại :285-287 và :95-97).
🔸 Đường ra rẻ: đừng dùng content cho stage 2. Panel 1 KHKK có thể giữ nguyên leaves/groups sẵn có và chỉ làm giàu PipelineLeaf (thêm badge Σ tiền / người soạn), hoặc thêm ô groupContent per-ngăn. Cả hai đều KHÔNG giẫm lên nhánh content của GĐ1.
3.4 Sai địa-chỉ dòng của A-0 (nhỏ nhưng làm người sau đọc trượt)
A-0 và D-9 bảo đọc :150-320 và mô-tả "stage 2 dựng bằng leaves (link) :265". Đúng dòng :265 là const stage2: PipelineStage = {, nhưng nguồn dữ-liệu thật nằm ở :204 (khkkByPeId) và :123-134 (map dựng trong usePipelineLinkage) — tức NGOÀI khoảng :150-320 mà A-0 chỉ định (:123-134 nằm trước :150). Ai đọc đúng khoảng A-0 vẫn có thể không thấy query nguồn. Sửa A-0 thành :100-320.
4. Chi-phí BE thật (ContractSigningPlanFeatures.cs:145-181) + D-6 join Users
4.1 Chỗ RẺ HƠN ước-lượng (nói ra cho công bằng)
grep -n "new ContractSigningPlanDetailDto" → đúng 1 call-site: ContractSigningPlanFeatures.cs:784. Record positional 37 tham-số ⇒ thêm field bình thường sẽ vỡ mọi call-site, nhưng ở đây chỉ có một. ⇒ Chi-phí "append field vào DetailDto" thật sự thấp, đúng như bản invest hàm-ý. HELD.
4.2 D-6 "entity chỉ có ApprovedByUserId, không có tên ⇒ phải join Users": HELD
Đọc thân ContractSigningPlanApproval được khởi-tạo tại WorkflowService.cs:534-544: 8 field ContractSigningPlanId · FromPhase · ToPhase · Decision · Comment · ApprovalWorkflowLevelId · ApprovedByUserId · ApprovedAt — không có field tên. Đối-chiếu: hàng changelog ngay dưới (:546-557) CÓ UserName. Hai bảng ghi cùng lúc, một denorm tên một không ⇒ D-6 đúng, và đúng vì lý-do cấu-trúc chứ không phải suy đoán.
🔸 Gợi-ý rẻ hơn "join Users" mà bản invest không nêu: Approvals và Changelogs ghi cặp 1-1 trong cùng unit-of-work (:534 + :546, cùng now) ⇒ có thể lấy tên từ changelog cùng transition, hoặc denorm thêm cột tên vào Approval cho khớp anh-em. Cả hai đều tránh join lúc đọc.
4.3 🔴 BROKE — D-1 "+3 field (append cuối)" ước-lượng THIẾU phần đắt nhất
ContractSigningPlan.cs:51 chỉ có public bool EndedByLevelFinalize — một cờ boolean. Không có cột nào lưu Bước nào / Cấp nào đã kết-thúc.
⇒ FinalizeStepName và FinalizeLevelName không phải "field append", chúng là giá-trị phải DẪN XUẤT. PE làm việc đó bằng subquery where lv.AllowApproverFinalize && lv.Step!.ApprovalWorkflowId == … lặp 3 lần trong projection (PurchaseEvaluationFeatures.cs:665-683), cộng nhánh 3-ngả phase == DaDuyet ? runtime-flag : suy-config (:665-673).
⇒ D-1 thật sự = 1 field append (EndedByLevelFinalize) + port ~20 dòng projection có 3 subquery. Vẫn làm được, nhưng gọi nó là "+3 field" là ước-lượng sai chiều RẺ — đúng loại sai mà §8-2 của chính bản invest cảnh-báo ("dễ ước-lượng sai đắt nhất theo cả hai chiều"). Nó cảnh-báo đúng rồi tự dính chiều còn lại.
4.4 Chỗ phải sửa mà bản invest không đếm (BE)
| # | Việc bị bỏ khỏi bảng D / A-1 | Bằng-chứng |
|---|---|---|
| U-1 | ContractSigningPlanApprovalDto CHƯA TỒN TẠI — phải tạo record mới + projection |
grep -n "ContractSigningPlanApprovalDto" src/Backend → 0 hit |
| U-2 | Projection Approvals phải .Include/join ApprovalWorkflowLevels nếu muốn hiện "Cấp nào duyệt" (Ảnh 2 có "Lùi về Cấp 2") — ApprovalWorkflowLevelId là Guid trần |
WorkflowService.cs:541 |
| U-3 | types/khkk.ts phải mirror ×2 app cho mọi field mới; A-9 có phủ, nhưng A-1 (BE) không nhắc ⇒ dễ làm 1 app rồi tick A-1 xong |
R-7 của chính bản invest |
| U-4 | Hàng Decision = Pending (hành-vi gửi duyệt, :204-207) sẽ nằm trong Approvals ⇒ phải quyết lọc hay không trước khi dựng "Lịch sử duyệt (N)" |
:204-207 |
5. §5 Phương-án A vs B — lý-do bác B có đứng vững?
5.1 Nửa DỮ-KIỆN: HELD (tôi tự mở 2 file)
fe-admin/src/components/pe/PeWorkflowPanel.tsx:384—const stepIcon = step.status === 'Done' ? '✓' : step.status === 'Current' ? '●' : '○'⇒ PE đọcstep.statuscó sẵn trên DTO. ✅fe-admin/src/pages/khkk/KhkkWorkflowPanel.tsx:106-120—stepStatus(idx)/levelStatus(idx, order)tự tính từcurrentWorkflowStepIndex+currentApprovalLevelOrder. ✅ ⇒ "shape KHÁC NHAU THẬT" là sự-thật đo được, không phải cái cớ. HELD.
5.2 Nửa LẬP-LUẬN: BROKE — nhị-phân giả (false dilemma)
Bản invest viết: "Generic hoá = phải hoặc (i) ép BE KHKK precompute, hoặc (ii) nhồi adapter 2 chiều. Cả hai đều đụng vào đường duyệt của PE đang chạy prod."
Hai lỗi:
- Có đường (iii) mà nó không xét: KHKK đã có sẵn hàm suy status — 15 dòng thuần-tuý, không side-effect (
:106-120). Chỉ cần KHKK tự chuẩn-hoá cây thô → shape cóstatusngay trước khi truyền vào component generic. Đó là adapter MỘT chiều, nằm TRỌN trong nhánh KHKK, và 0 dòng PE bị đọc lại hay sửa. - Câu "cả hai đều đụng vào đường duyệt PE" SAI với (ii): adapter ở phía KHKK không chạm PE. Câu này gán rủi-ro của (i) cho cả (ii) rồi dùng nó để đóng cửa B.
5.3 Nhưng KẾT-LUẬN chọn A vẫn ĐỨNG — vì một lý-do khác, mạnh hơn
Lý-do thật sự để bác B không phải shape, mà là: trích StepOpinionsBox ra khỏi PeDetailTabs.tsx:705-774 buộc phải sửa file trong components/pe/, mà chính bản invest đặt A-10 = "git diff --name-only → 0 đường-dẫn khớp components/pe/". ⇒ B mâu-thuẫn trực-tiếp với acceptance của chính nó. Cộng thêm ≥6 nhánh riêng của PE (đúng, và đây là lý-do có sức nặng).
🔸 Khuyến-nghị lead: giữ quyết-định A, thay lý-do. Lý-do hiện tại sai kỹ-thuật; nếu owner (hoặc reviewer sau) bẻ được lý-do, quyết-định đúng sẽ bị lật oan. Đây là dạng "kết-luận đúng, chứng-minh hỏng".
6. §7 Checklist — mục nào GOODHART-able?
G1 🔴 A-13 VỠ CẢ HAI ĐẦU (nghiêm nhất — nó là mục canh quyền)
Thước viết: grep -rn "Authorize" …Controller.cs | grep -c "Policy" phải ≥ số route, và 0 dòng [Authorize] trần.
- Đầu 1 — tử-số bơm bằng COMMENT. Dòng
:15là chú-thích:// tầng 1 = class `[Authorize(Policy = "KeHoachKyKet.Read")]` (mọi endpoint tối thiểu Read)— chứa cả "Authorize" lẫn "Policy" ⇒ được đếm. Viết thêm vài dòng chú-thích là con-số tự tăng. Ai xoá 1[Authorize(Policy=…)]thật rồi thêm 1 dòng comment ⇒ A-13 vẫn PASS, API mở toang. - Đầu 2 — vế "0 dòng
[Authorize]trần" FAIL NGAY HÔM NAY.:17và:241chứa literal[Authorize]` trầntrong chú-thích cảnh-báo. Đo thật: file KHÔNG có[Authorize]trần nào (19 dòng Policy / 18 route, §2). ⇒ thước báo đỏ trên một file đang sạch — đây đúng lớp bẫyfeedback_citation_trap_selfreference(doc/nguồn nói VỀ anti-pattern bị matcher bắt như là anti-pattern). - Thước thay thế (đề-xuất): bỏ grep chuỗi, đếm trên AST-lite bằng cách chỉ soi dòng bắt đầu bằng
[sau khi cắt comment:Neogrep -nE '^\s*\[(Http(Get|Post|Put|Delete))' <file> | wc -l # R = số route grep -nE '^\s*\[Authorize\(Policy' <file> | wc -l # P grep -nE '^\s*\[Authorize\]\s*$' <file> | wc -l # T phải = 0^\s*\[loại sạch comment//. Chứng thước có RĂNG: thêm tạm 1 action[Authorize]trần vào cây tạm ⇒Tphải nhảy lên 1 (fault-injection,feedback_faultinjection_proves_teeth).
G2 🔴 A-10 đo SAI TRỤC — đo đường-dẫn chạm, không đo hành-vi đổi
A-10: "git diff --name-only → 0 path khớp components/pe/ · pages/pe/ · components/contracts/ · pages/contracts/".
Đo thật, buildStages có 3 consumer trong fe-admin:
components/pipeline/PipelineTreePanel.tsx:309 stages={buildStages(wg.items)}
pages/pe/PurchaseEvaluationsListPage.tsx:524 stages={buildStages(wg.items, { stage1Content: … })}
(và PipelineTreePanel lại là thứ KHKK dùng ở panel-1 lẫn GĐ3 dùng ở panel-3).
⇒ Việc D-8 sửa hooks/usePipelineStages.ts hoặc components/pipeline/PipelineStageFolders.tsx — hai path đều KHÔNG nằm trong danh-sách A-10 — có thể làm cây của trang PE mất 8 ngăn GĐ2 (§3.3-bis: content thắng groups) mà A-10 vẫn PASS 100%. Hồi-quy đi vòng qua cửa mà thước không canh.
Thước thay thế: thêm 2 path vào A-10 (hooks/usePipelineStages.ts, components/pipeline/) với ý nghĩa "chạm ⇒ phải mở trang PE và trang HĐ đếm lại folder GĐ2 bằng mắt", chứ không cấm chạm.
G3 A-7d Goodhart-able trực-diện
grep -c "Ý kiến cấp duyệt" …KhkkWorkflowPanel.tsx = 0. Đổi tiêu-đề thành "Ý kiến duyệt" / "Ý kiến các cấp" ⇒ PASS trong khi khối vẫn nằm nguyên panel 3. Thước đo chuỗi chữ, mục-tiêu là vị-trí khối.
Thay bằng: grep -c "opinions" …KhkkWorkflowPanel.tsx = 0 và grep -c "levelOpinions" …KhkkDetailContent.tsx ≥ 1 — đo luồng dữ-liệu, thứ không đổi được bằng cách sửa nhãn.
G4 A-7c / A-8a / A-8b / A-8c / A-8d — cùng một lớp, nhẹ hơn
Đều là grep -c "<cụm tiếng Việt>" ≥1. Gõ đúng cụm chữ vào JSX chết (khối render sau return null, hoặc điều-kiện luôn false) vẫn PASS. Không cần thay hết — nhưng A-8d ("Lịch sử duyệt" + "Lịch sử thay đổi") nên buộc thêm 1 dòng gọi API: grep -c "/changelogs" … ≥1, vì khối đó vô nghĩa nếu không fetch.
G5 A-4 regex sai cú-pháp — sẽ FAIL trên chính bố-cục nó đề-xuất
A-4 đòi khớp ^.*lg:grid-cols-\[[0-9]+px_1fr_[0-9]+px\]. Nhưng §5 "Bố-cục đích" ghi Panel 1 = 400px … trong khi khuôn nó bảo chép là ContractsListPage.tsx:125 dùng lg:grid-cols-[340px_1fr_360px] — khớp. Rủi ro thật: nếu ai dùng rem (như KHKK đang dùng 19rem/21rem, KhkkListPage.tsx:197) thì regex [0-9]+px FAIL dù bố-cục đúng. Nới thành \[[^]]*_1fr_[^]]*\].
7. Bỏ-sót lane này bắt thêm (thách-CLEAN)
B1 🔴 content nuốt groups — 8 nhóm N1-N8 biến mất im lặng. §3.3-bis. Đây là món nặng nhất: nó vừa lật claim D-8, vừa là hồi-quy chéo sang trang PE, vừa không có thước nào trong §7 bắt được (A-10 miss, A-11 build vẫn xanh, A-14 chụp màn KHKK chứ không chụp PE).
B2 ContractSigningPlanApprovalDto chưa tồn tại (0 hit) — A-1 viết như thể chỉ cần "thêm field", thực tế phải đẻ record + projection + mirror types ×2 app. (U-1 §4.4)
B3 D-3 liệt-kê thiếu 2 chỗ ghi changelog, một trong đó có nghĩa nghiệp-vụ. Đo được 10, bản invest kể 8. Chỗ bị bỏ đáng kể nhất: CreateContractFromSigningPlanFeatures.cs:226 — đó là bắc-cầu KHKK→HĐ (K7). Nghĩa là "Lịch sử thay đổi" của KHKK sẽ hiện entry tạo Hợp đồng, một loại dòng mà spec §7 không hề nhắc và FE chưa có nhãn cho nó. Thiếu ⇒ hoặc hiện chuỗi thô, hoặc rơi im lặng.
B4 Dòng Decision = Pending (gửi duyệt) vào thẳng "Lịch sử duyệt (N)". PE chống bằng merge + dedupe 5s + synthetic-reject (PeDetailTabs.tsx:3152-3192); bản invest có mô-tả lớp đó ở §1.3 nhưng không kéo hệ-quả vào §4/§7 ⇒ số (N) của KHKK sẽ không cùng nghĩa với số (N) của PE, trong khi cả đề-bài là "cho đồng nhất".
B5 A-0 chỉ sai khoảng dòng. Nguồn dữ-liệu thật ở :123-134 + :204, nằm ngoài :150-320 mà A-0 bắt đọc (§3.4). Người làm đúng theo A-0 vẫn có thể không thấy query nguồn ⇒ chặn-cửa tự vô-hiệu.
B6 Path ContractSigningPlanWorkflowService.cs ghi thiếu lớp. Nó ở Infrastructure/Services/, không phải Application/ContractSigningPlans/ như ngữ-cảnh xung quanh gợi ý (§1.0). Và :552 (ResolveActorFullNameAsync) không trỏ vào gì — call-site thật :485/:554, định-nghĩa ở file khác.
8. Chỗ bản invest ĐÚNG mà owner có thể tưởng sai
- 🟢 "Lịch sử duyệt của KHKK ĐÃ được ghi đầy đủ, kể cả Trả lại" — tôi vào để lật và không lật được. 6/6 call-site phủ trọn Pending/Approve/Reject-Return (§1.1). Entry
Trả lại → Lùi về Cấp 2ở Ảnh 2 sẽ có dữ-liệu. Đừng cắt ngân-sách mục này vì nghi ngờ. - 🟢 "Không ép BE KHKK precompute
status" (P3-3) — đúng. Hàm suy FE:106-120là 15 dòng thuần-tuý, đọccurrentWorkflowStepIndex/currentApprovalLevelOrderđã có trên DTO (Features.cs:162-163). Ép BE precompute là đổi hợp-đồng API để lấy về một thứ FE tính được miễn phí. - 🟢 Đ-2 (GĐ3 đã 3-panel ⇒ GĐ2 là mắt-xích lệch duy nhất) — tôi không kiểm trực-tiếp
ContractsListPage.tsx:125(ngoài lăng-kính, lane 1/3 nên chấm), nhưng gián-tiếp KHỚP:PipelineTreePanelđược cả KHKK và GĐ3 dùng, vàbuildStageschỉ có 3 consumer đúng như bố-cục đó hàm-ý. - 🟢 Chi-phí append DetailDto thật sự RẺ — 1 call-site duy nhất
:784(§4.1). Owner có thể nghĩ "sửa record 37 tham-số = đụng khắp nơi"; không phải.
9. Chưa đo được / lệnh cần chạy
| # | Chưa đo | Lệnh cần chạy |
|---|---|---|
| C-1 | Toàn bộ đo của tôi chỉ trên fe-admin/. Không xác-nhận fe-user/ identical |
for f in hooks/usePipelineStages.ts components/pipeline/PipelineStageFolders.tsx pages/khkk/KhkkWorkflowPanel.tsx; do sha256sum fe-admin/src/$f fe-user/src/$f; done |
| C-2 | A-2b (tên người trong Approvals ≠ null) — cần gọi API thật |
curl -H "Authorization: Bearer …" https://api…/api/contract-signing-plans/{id} trên 1 phiếu DaDuyet |
| C-3 | hideEmpty tương-tác thế nào với content/groups khi lọc bật (PE truyền hideEmpty={filterActive}) |
đọc PipelineStageFolders.tsx:230-311 |
| C-4 | Số phiếu KHKK thật ở prod (bản invest nói PE 45 phiếu, không nói KHKK) — quyết-định trần 200 (R-11) có cắn không | đếm SELECT COUNT(*) FROM ContractSigningPlans WHERE IsDeleted=0 |
| C-5 | ContractsListPage.tsx (khuôn được đề-xuất chép) — tôi không đọc, để lane khác chấm |
sed -n '80,260p' fe-admin/src/pages/contracts/ContractsListPage.tsx |
END reviewer-lens-feasibility-s171 — VERDICT=8 HELD / 6 BROKE / 6 bỏ-sót-bắt-thêm