wal: flush 20260731T1155

This commit is contained in:
pqhuy1987
2026-07-31 11:55:53 +07:00
parent 8cb5b8220c
commit b82cf6f747
8 changed files with 592 additions and 68 deletions

View File

@ -1,36 +1,35 @@
# WAL — auto-generated, không sửa tay
updated: 2026-07-31T11:20:00+07:00 | session: S164 (L9, window 2 — /tiep; lead=Fable từ giữa phiên) | branch: main
updated: 2026-07-31T12:35:00+07:00 | session: S164 (L9, window 2 — /tiep; lead=Fable) | branch: main
goal: CHUỖI 4-GĐ SP-002 (lệnh owner B0→B4 + dry-run + master-data) — B0 xong, B1a đang chạy
goal: CHUỖI 4-GĐ SP-002 — B0 ✅ B1a ✅ B1b ✅ spec-v2 ✅ transcribe ✅ · NEXT = B2 (chờ anh liếc 6 OG, có default)
chain:
[x] /tiep trọn + DANH-SÁCH VÁ 12/12 (P3 hoãn đúng toa) + ESCALATE(i) vế cơ-chế — chi tiết log phiên trước trong _context
[x] Nạp spec SP-002 6 nhịp + 3 hình + 2 bổ sung (dry-run · master-data) → memory `sol-ccm-sp002-signing-matrix`
[x] B0 — draft tổng hợp: runs/2026-07-31-S164-4gd-khkk-fanout/draft-yeu-cau-owner.md (10 lời chốt verbatim
+ model 4GĐ + 8 nhóm duyệt + scope 5 mảnh + ràng buộc khuôn + nền kỹ thuật + 8 treo) + run.md scaffold
[x] wf: B1a att-1 wf_22e8949b-afa — 🔴 CHẾT ngược-#53 event#4-lifetime (207K tok · 35 tool-use · 0 byte đĩa ·
0 return · resume "No transcript found" — vệt S149). Ghi sự-kiện, tally family @closeout ledger
[x] wf: B1a att-2 wf_bc04352b-6a4 — kỷ-luật-ghi CỨU: A-D đầy 18.277B (return vẫn rỗng — StructuredOutput
fail ×2, nhưng đĩa sống). E/F chết dở → lead compile từ nhãn inline C + guard D (khai nguồn). Spot-check
4/4 ĐẠT → lead kết tinh spec-4gd-khkk-tong-quat-31-07-2026.md (8 verdict · K1-K8 · 3 cụm · OG-1/2/3/5)
[!] wf: B1b wf_8eb13087-5f9 — /fable-clone review 3 lane reviewer tier:opus (lens1-cov · lens2-evi ·
lens3-seq), khung-rỗng-lượt-1-2 + trần-25 + ≤3 file/lane (C2). Output sub-reviewer-clone-l{1,2,3}-b1b.md
[ ] B1blead verify sub-file → kết tinh spec-file 3-heading → /fable-clone review spec tổng quát
[ ] B2 — per cụm 3 wave: /fable-real invest sâu (spec-chi-tiết + checklist) → /fable-clone review
[ ] B3 /fable-real review chốt spec cuối
[ ] B4 — fan-out Opus 5 MAX build bám spec+checklist · B5 dry-run E2E (wave cuối)
[ ] seed diary vai (closeout §L.b — C2 checklist AB=∅) + tally garble chốt + nhãn D đổi 2 ý
[x] /tiep + VÁ 12/12 + nạp spec SP-002 (6 nhịp + 3 hình + 2 bổ sung) — chi tiết _context/log
[x] B0 draft-yeu-cau-owner.md (10 lời chốt + 8 nhóm + 5 mảnh + khuôn + 8 treo)
[x] B1a ×2 att: att-1 ngược-#53 (207K/0-byte/resume-chết) · att-2 kỷ-luật-ghi CỨU A-D 18.277B →
lead compile E/F + spot-check 4/4 → spec v1 (8 verdict · K1-K8 · 3 cụm)
[x] B1b wf_8eb13087-5f9 — 3 lane reviewer opus 3-lens, 3/3 SẠCH 0-empty (C2 khung-rỗng ăn): l1 FAIL 5H ·
l2 6/6 chịu-lực ĐÚNG + F1 rào-1-PE-1-phiếu (:342/:1201) · l3 FAIL 6H + bác-6-nghi. Hội tụ 2-lane:
admin-hide "System-subtree" GIỮ NHẦM 2 mục cần ẩn (Workflows :1795 + PeWorkflows :1798 = CON System)
[x] Lead synthesize b1b-review-synthesis.md (41 finding → 14 vá + OG-6/OG-7) → SPEC V2 10.012B
(tập-key tường-minh · ApprovalGroup K2-chủ+backfill-1 · 8-designer đo được · K7 3-nấc · acceptance răng)
[x] F-1 resolve: file đĩa = ĐÚNG SP-002.v01 (mã ở word/header1.xml) NHƯNG 6-trang KHÔNG bảng ⇒ nguồn =
7 ảnh anh. Lead TRANSCRIBE danh-muc-sp002-transcribe.md: 85+1 row · 8 nhóm map · 4 ô-chưa-chắc [OG-6]
[!] B2per cụm 3 wave (cụm-1 K1+K2+K3 · cụm-2 K4+K5+K6 · cụm-3 K7+K8): /fable-real invest sâu →
/fable-clone review. PRE: 6 OG có DEFAULT (spec v2 §④) — chạy default nếu anh không đổi; build B4 mới chặn cứng
[ ] B3 /fable-real review chốt · B4 fan-out Opus MAX · B5 dry-run E2E
[ ] closeout: seed diary vai (C2 AB=∅) · tally garble (+ngược-#53 ev#4) · nhãn D · K3-STATUS-row
next: đợi notification wf_22e8949b-afa → VERIFY sub-file (đọc ruột, không tin return — #53 ×72) → B1b.
Nếu wf chết giữa chừng: relaunch-đã-cắt-gọt theo tiep.md §4 (sub-file section-append = vật liệu đĩa).
next: đợi anh liếc 6 OG (OG-1 gộp · OG-2 roster-LIVE · OG-3 NĐUQ-tham-khảo · OG-6 SOÁT TRANSCRIBE ·
OG-7 đổi-rào-1-PE-N-phiếu · OG-5 extract-mơ-hồ) — HOẶC anh im thì B2-cụm-1 chạy default spec v2.
Files: runs/2026-07-31-S164-4gd-khkk-fanout/{draft,spec-v2,b1b-synthesis,transcribe,sub×4}
verify:
ls .claude/workflows/runs/2026-07-31-S164-4gd-khkk-fanout/ # draft + run.md (+ sub khi B1a xong)
grep -c 'END' .claude/workflows/runs/2026-07-31-S164-4gd-khkk-fanout/sub-invest-fable-b1a.md # >=1 khi xong
grep -n 'B1a' .claude/workflows/runs/2026-07-31-S164-4gd-khkk-fanout/run.md
grep -c 'END spec v2' .claude/workflows/runs/2026-07-31-S164-4gd-khkk-fanout/spec-4gd-khkk-tong-quat-31-07-2026.md
grep -c '86 row' .claude/workflows/runs/2026-07-31-S164-4gd-khkk-fanout/danh-muc-sp002-transcribe.md
ls .claude/workflows/runs/2026-07-31-S164-4gd-khkk-fanout/ | wc -l # >=9 file
notes: 🔴 RÀNG BUỘC KHUÔN owner: GIỐNG NHAUCHỈ KHÁC FORMĐỪNG CHẾ THÊM (1 khung Duyệt NCC nhân mọi GĐ).
Scope 5 mảnh: master-data danh mục ~85 dòng · sidebar 8 nhóm+6-leaf · cây mirror (GĐ2=8 nhóm, GĐ3/4=7 loại) ·
admin ẩn-chừa-HỆ-THỐNG + 8 designer · dry-run E2E. Đội hình 3 trạm QT-DN-V2-001 trong memory (verify LIVE).
8 treo draft §6 — nặng nhất cardinality N-hạng-mục→1-HĐ [OWNER-GATE dự kiến]. HMW marker ON. Lead=Fable;
/fable-real qua tier:'fable' spawn-param (H23). Chờ anh cũ: 54-62 + 42/43/44 (HANDOFF). mind-check 1 cờ giữ.
notes: RÀNG BUỘC KHUÔN: GIỐNG NHAUCHỈ KHÁC FORMĐỪNG CHẾ THÊM. 8 nhóm = HIỂN THỊ không phải authz (khai
thẳng spec §①). Đội hình 3 trạm QT-DN-V2-001 memory (verify LIVE trước K3). Sự cố phiên: ngược-#53 ev#4
(B1a att-1) + StructuredOutput-fail ×2 lần B1a (đĩa cứu) — tally @closeout. mind-check 1 cờ giữ chủ-đích.
Chờ anh cũ: 54-62+42/43/44 (HANDOFF). Lead=Fable; ensemble lane pin tier:opus (GAP#6).

View File

@ -0,0 +1,44 @@
# B1b SYNTHESIS — /fable-clone review 3-lens (lead refute + hợp nhất @S164)
> Input: `sub-reviewer-clone-l1-b1b.md` (20.715B, 15 finding 5H/9M/1L, FAIL) · `l2` (8.591B, 6/6 claim chịu-lực ĐÚNG + 6 finding 1H/4M/1L) · `l3` (19.557B, 20 finding 6H/8M/2L + 6 nghi-đã-bác, FAIL). 3/3 lane 0-empty (kỷ luật C2 khung-rỗng-trước ăn).
## VERDICT HỢP NHẤT: spec v1 FAIL — nền kỹ thuật VỮNG (lens2 6/6 chịu-lực ĐÚNG), vỏ spec THỦNG (coverage + acceptance). Vá được trọn bằng spec v2, không phải làm lại invest.
## Hội tụ cross-lane (tin cậy cao nhất)
1. **ADMIN-HIDE SAI HƯỚNG** (l1-F2 l3-F1/F2 — 2 lane độc lập cùng bắt): "chừa System subtree" GIỮ nhầm `Workflows` (:1795) + `PeWorkflows` (:1798) = 2 mục owner ra lệnh ẨN (đều con System; AwV2 :1801 cũng con System ⇒ vế "+AwV2" tố cáo tác giả tưởng nó ngoài). + l3 điểm khuôn: đảo whitelist = đổi default-behavior menu-mới (fail-closed) — VƯỢT yêu cầu "ẩn mục cũ". **Fix v2: ẨN TẬP KEY TƯỜNG MINH (blacklist mở rộng), giữ default cũ; acceptance = liệt kê đúng N mục lá còn thấy.**
2. **Trục `ApprovalGroup` mồ côi 3 mặt** (l3-F3/F5/F6 l1-F7): không chủ wave cho query-param · không backfill phiếu cũ (NULL = biến mất mọi folder — mirror bài `EvaluationType` không rỗng từ lúc tạo) · chủ cột chẻ đôi K2/K3. **Fix v2: K2 = CHỦ DUY NHẤT (cột + backfill=1 + query-param + FE param); K3 chỉ đọc; K4 tách a/b/c.**
3. **"8 designer" bốc hơi** (l1-F3 l3-F10 l2-F2): draft đòi 8, spec còn chữ "entry" không số; route designer chỉ nhận 3 code hardcode (`Layout.tsx:166-172`), leaf lạ RỚT IM LẶNG (#50). **Fix v2: K3 += 8 menu-con AwV2 + mở resolvePath + acceptance đếm 8.**
## Finding lens2 nặng nhất (mới, không lane nào khác thấy)
**l2-F1 HIGH — rào 1-PE-1-phiếu đang sống** (`ContractSigningPlanFeatures.cs:342-347` + picker `:1201-1202`): 1 PE có hạng mục thuộc >1 nhóm ⇒ phiếu nhóm 2 bị 409. "Phiếu per NHÓM" đòi đổi rào thành (PeId, ApprovalGroup). Đổi rào = đổi nghiệp vụ đã ship ⇒ **[OG-7 MỚI]** hỏi anh. FE tree ĐÃ chịu N-phiếu/PE (`usePipelineStages.ts:110-121` bucket mảng) — chỉ BE chặn.
## Lead refute (finding bị bác / resolve)
- **l1-F1 (nguồn K1 0-hit) — RESOLVED bằng đo đĩa của lead:** file `QUY_TRINH/QT TRINH KY HOP DONG TP-NCC.docx` CHÍNH LÀ SOL-CCM-SP-002.v01 (mã trong `word/header1.xml` — lane grep `document.xml` nên miss). NHƯNG bản đĩa = **6 trang, KHÔNG chứa bảng danh mục** (0 hit mọi marker; ảnh anh ghi "…/15") ⇒ nguồn K1 thật = **7 ảnh anh gửi (trang 5-9, trọn bảng 5.1+5.2)**; transcribe + **[OG-6 MỚI]** anh soát bảng seed trước land. Bài: lane đúng hiện tượng (đường dẫn draft không dùng được as-is), sai chẩn đoán (file có tồn tại).
- **l1 tự-bác 1 HIGH dự định** ("GĐ4 không có sidebar mirror" — `Hdc_*` 7 leaf có thật :1868) — giữ làm mẫu bác-có-chứng.
- **l3 bác 6 nghi** (48-leaf = nhân khuôn hợp lệ · ApprovalGroup = mirror EvaluationType · sub-folder = owner đòi · Menu eOffice trong vùng chừa · Distinct() có sẵn · ApprovedAmount có thật) — spec KHÔNG vi phạm luật khuôn ở chỗ dễ nghi nhất.
- **l2-F4**: `KhkkListItemDto` = FE type; BE thật = `ContractSigningPlanListItemDto` — sửa citation.
- **l2-F5 l1-F8**: số đúng = **42 leaf MỚI + 7 group-key mới = 49 row menu mới; 48 leaf TỔNG** — sửa mọi chỗ.
## Bảng vá spec v2 (đủ 14 mục — traceability)
| # | Nguồn | Vá |
|---|---|---|
| 1 | l1-F2/l3-F1/F2 | QĐ7 → ẩn tập-key tường minh; acceptance liệt kê N mục lá |
| 2 | l1-F3/l3-F10/l2-F2 | K3 += 8 designer menu + resolvePath + đếm 8 |
| 3 | l1-F4/l3-F7 | OG nối vào DoD: PRE-gate K1←OG-6, K2←OG-1+OG-7, K3←OG-2(+LIVE verify) |
| 4 | l1-F5/l3-F18 | K2 += bất-biến tiền (Σ PeReferenceAmount trước==sau mig) + acceptance rà 16 consumer |
| 5 | l2-F1 | K2 += đổi rào (PeId)→(PeId,ApprovalGroup) + DoD "phiếu nhóm 2 cùng PE → 201" [OG-7] |
| 6 | l3-F3/F5/F6 | ApprovalGroup: K2 chủ duy nhất (cột+backfill=1+param); K4 tách a/b/c |
| 7 | l3-F4 | Khai thẳng: 8 nhóm = HIỂN THỊ, authz vẫn 1 bộ `KeHoachKyKet.*` per-action; authz-per-nhóm = wave riêng nếu anh muốn |
| 8 | l3-F11 | K7 RE-ĐO 3 nấc: cột/index ✓ (đã đo: có) · write-site (grep gán ContractId = 0 hiện tại) · test E2E |
| 9 | l3-F12 | "diff bundle fe-user=0" → `git diff --stat <base>..<head> -- fe-user/src` = 0 file (#69) |
| 10 | l3-F13 | K4 acceptance 2 chiều + bảng kỳ-vọng 13 role (seeder hiện phủ 7 vai — 6 vai còn lại phải khai kỳ vọng) |
| 11 | l3-F14/F15/F20 | K1 đối chứng ~85±5 giải trình · test có số ≥590+k · 3-file rule K1/K2 |
| 12 | l3-F16 | K8 += 3 dòng ZZTEST/HỐ-1/notification vào acceptance |
| 13 | l3-F17 | Byte-verify #77 CHỈ wave có FE-ship; BE-only = cicd + smoke API |
| 14 | l2-F3/F6 | K4 += leaf WfView/Create mang `?group=` (WfView lọc theo workflow Code nhóm) + site-3 `KhkkKeys()` (:2185) vào danh sách sửa |
END b1b-synthesis — 3 lane · 41 finding thô → 14 vá + 2 OG mới (OG-6 soát bảng, OG-7 rào 1-PE-N-phiếu) + 1 resolve (nguồn K1 = ảnh) + 1 sửa citation.

View File

@ -0,0 +1,142 @@
# DANH MỤC HỢP ĐỒNG SOL-CCM-SP-002.v01 — TRANSCRIBE từ 7 ảnh anh gửi @S164 (trang 5-9, bảng 5.1 + 5.2)
> 🔴 [OG-6] Lead transcribe từ ẢNH (file đĩa `QUY_TRINH/QT TRINH KY HOP DONG TP-NCC.docx` = bản 6-trang KHÔNG chứa bảng — đã verify). **ANH SOÁT FILE NÀY trước khi K1 seed.** Ô mờ/nghi đánh dấu ⚠️. Mã dòng = lead tự đánh (ảnh chỉ có STT trong nhóm). **Đếm: 85 dòng danh mục + 1 dòng CEO = 86 row seed.**
## Nhóm duyệt (8) — map pattern ký
| Nhóm | Ký chính | Nháy Ô1→Ô4 |
|---|---|---|
| N1 = A1 | TP.CCM | TBP.CCM → NV.CCM → TBP.EQU → NV.EQU |
| N2 = A2+A3 | TP.CCM | TBP.CCM → PD/PM → SM → QS |
| N3 = A4 | TP.CCM | TBP.CCM → TPB/TBP → TBP/NV.PB → N/A |
| N4 = B1 | TP.PRO | TBP.PRO → NV.PRO → N/A → N/A |
| N5 = B2 | TP.PRO | TP.CCM → TBP.PRO → PD/PM/SM → QS |
| N6 = B3 | TP.PRO | TP.CCM → TBP.PRO → PD/PM/SM → MEP SM |
| N7 = C1+C2+C3 | PD/PM | TP.CCM → SM → QS → N/A |
| N8 = CEO | CEO | TP.CCM → TP.PRO → N/A → N/A |
## A — PHÒNG CCM
**A1 · Thiết bị, máy móc, vật tư phụ / Equipment, machinery, and auxiliary materials (5) → N1**
| Mã | Tên VN | Tên EN |
|---|---|---|
| A1-01 | Thuê thiết bị/máy móc/dụng cụ thi công/thiết bị an toàn lao động/thiết bị văn phòng (máy photo, máy in) | Rental of construction equipment, machinery, and tools; occupational safety equipment; and office equipment (photocopiers, printers) |
| A1-02 | Thuê container văn phòng, văn phòng BCH CT, nhà vệ sinh, nhà bảo vệ | Rental of office containers, site offices, toilets, and guardhouses |
| A1-03 | Cung cấp vật tư phụ phục vụ công tác tạm/tiện ích công trường | Supply of materials for temporary works and site utilities |
| A1-04 | Cung cấp vật tư phụ thi công: nylon bảo dưỡng, Sika, khoan cấy, … | Supply of auxiliary construction materials: curing nylon sheets, Sika products, chemical anchors, etc |
| A1-05 | Cung cấp vật tư biện pháp: sika, lưới mắt cáo, thanh trương nở, … | Supply of method-related materials: Sika products, wire mesh, expansion bars, etc |
**A2 · Kết cấu, hạ tầng, phá dỡ / Structures, infrastructure, and demolition (1) → N2**
| A2-01 | Thầu phụ hạ tầng & phá dỡ | Infrastructure and demolition subcontracting works |
**A3 · Dịch vụ hỗ trợ thi công / Construction support services (9) → N2**
| A3-01 | Dịch vụ ca cẩu | Crane rental services |
| A3-02 | Dịch vụ vận chuyển | Transportation services |
| A3-03 | Trắc đạc | Surveying services |
| A3-04 | Thí nghiệm | Testing services |
| A3-05 | Bảo vệ | Security services |
| A3-06 | Dịch vụ giấy phép, tư vấn, thiết kế (kể cả dịch vụ tư vấn MEP) | Licensing, consultancy, and design services (including MEP consultancy services) |
| A3-07 | Điện tạm | Temporary power supply |
| A3-08 | Cấp và thoát nước tạm | Temporary water supply and drainage |
| A3-09 | Cung cấp tiện ích công trường: tủ hồ sơ, bàn ghế, internet, văn phòng phẩm, thiết bị bảo hộ lao động (PPE), nước uống, dịch vụ y tế, bảng biểu an toàn, … | Supply of site utilities: filing cabinets, desks and chairs, internet, stationery, personal protective equipment (PPE), drinking water, medical services, safety signage, etc. |
**A4 · Hoạt động văn phòng / Office operations (5) → N3**
| A4-01 | Thuê văn phòng | Office leasing |
| A4-02 | Văn phòng phẩm | Stationery |
| A4-03 | Bảo hiểm cháy nổ văn phòng | Office fire and explosion insurance |
| A4-04 | Thiết bị máy tính, phần mềm | Computer equipment and software |
| A4-05 | Các hợp đồng khác liên quan đến hoạt động văn phòng | Other contracts related to office operations |
## B — PHÒNG CUNG ỨNG
**B1 · Vật tư / Materials (23) → N4**
| B1-01 | Đá | Stone |
| B1-02 | Cát | Sand |
| B1-03 | Cừ tràm | Mangrove piles |
| B1-04 | Bê tông | Concrete |
| B1-05 | Xi măng | Cement |
| B1-06 | Thép | Steel |
| B1-07 | Ván phủ phim | Film-faced plywood |
| B1-08 | Gạch ốp lát | Floor and wall tiles |
| B1-09 | Gạch xây | Bricks |
| B1-10 | Nylon | Nylon sheets |
| B1-11 | Vữa grouting | Grouting |
| B1-12 | Chất tăng cứng bề mặt | Hardener |
| B1-13 | Sơn nước | Emulsion paint |
| B1-14 | Sơn dầu | Oil-based paint |
| B1-15 | Bột matic | Matic |
| B1-16 | Vật liệu hoàn thiện sàn | Floor finishing materials |
| B1-17 | Thanh cản nước | Waterbar |
| B1-18 | Vật liệu chống thấm | Waterproofing |
| B1-19 | Đá granite | Granite stone |
| B1-20 | Thiết bị vệ sinh | Sanitary equipment |
| B1-21 | Vật liệu hoàn thiện | Finishing materials |
| B1-22 | Vật tư, thiết bị MEP (cáp điện, tủ điện, máy móc của hệ MEP) | MEP materials and equipment (electrical cables, panels, machinery for MEP systems) |
| B1-23 | Vật tư chính khác | Other main materials |
**B2 · Thầu phụ xây dựng / Civil subcontractors (14 dòng — ảnh đánh số 1-13 TRÙNG số "7" ×2 ⚠️, lead đánh lại 01-14) → N5**
| B2-01 | Ép cọc, cừ Larsen | Pile works and sheet pile |
| B2-02 | Trần thạch cao | Gypsum ceiling works |
| B2-03 | Đá granite | Granite |
| B2-04 | Kết cấu thép | Steel structure |
| B2-05 | Cửa đi, cửa sổ | Doors and windows |
| B2-06 | Cửa chống cháy, thoát hiểm | Fire-rated and emergency exit doors |
| B2-07 | Công tác kim loại (ảnh: số 7 thứ nhất) | Metal |
| B2-08 | Cảnh quan (ảnh: số 7 thứ hai ⚠️) | Landscaping |
| B2-09 | Công tác kính (ảnh: số 8) | Glazing |
| B2-10 | Rèm cửa (ảnh: 9) | Curtain |
| B2-11 | Nội thất (ảnh: 10) | Interior |
| B2-12 | Chống thấm (ảnh: 11) | Waterproofing |
| B2-13 | Hoàn thiện sàn nâng (ảnh: 12) | Raised floor finishing |
| B2-14 | Thầu phụ xây dựng khác (ảnh: 13) | Other civil subcontractors |
**B3 · Thầu phụ MEP / MEP subcontractors (15) → N6**
| B3-01 | Hệ thống điện trung thế | Medium-voltage power systems |
| B3-02 | Máy phát điện | Generator systems |
| B3-03 | Hệ thống điện hạ thế và chiếu sáng | Low-voltage power and lighting systems |
| B3-04 | Hệ thống điện nhẹ ELV | Extra Low Voltage (ELV) systems |
| B3-05 | Hệ thống lạnh (Máy lạnh, Chiller, Cooling Tower, Process cooling...) | Cooling systems (air conditioning, chillers, cooling towers, process cooling, etc.) |
| B3-06 | Hệ thống thông gió | Ventilation systems |
| B3-07 | Hệ thống cấp thoát nước | Water supply and drainage systems |
| B3-08 | Hệ thống xử lý nước | Water treatment systems |
| B3-09 | Hệ thống chữa cháy | Firefighting systems |
| B3-10 | Hệ thống báo cháy | Fire alarm systems |
| B3-11 | Hệ thống chống sét | Lightning protection systems |
| B3-12 | Hệ thống Gas | Gas systems |
| B3-13 | Hệ thống khí nén | Compressed air systems |
| B3-14 | Thiết bị bếp | Kitchen equipment systems |
| B3-15 | Thầu phụ MEP khác | Other MEP subcontractors |
## C — BAN CHỈ HUY CÔNG TRƯỜNG
**C1 · Kết cấu, công tác tạm / Structures and temporary works (6) → N7**
| C1-01 | Nhân công công tác tạm, vệ sinh công nghiệp | Labor for temporary works and industrial cleaning |
| C1-02 | Nhân công khoán đội, công nhật | Construction teams and daily workers |
| C1-03 | Nhân công thầu phụ | Subcontractor labor |
| C1-04 | Thầu phụ trọn gói | Lump-sum subcontractors |
| C1-05 | Thầu phụ giàn giáo | Scaffolding subcontractors |
| C1-06 | Cơ giới, ca máy | Machinery and equipment shifts |
**C2 · Xây tô, sơn nước, hoàn thiện, ốp lát / Masonry, painting, finishing, tiling (3) → N7**
| C2-01 | Thi công sơn nước | Painting |
| C2-02 | Thi công công tác hoàn thiện (xây tô, ốp lát) | Finishing works (masonry, plastering, tiling) |
| C2-03 | Thi công defect, bó vỉa, xoa nền, … | Defect rectification works, kerb works, floor finishing/polishing, etc. |
**C3 · Khác / Other (4) → N7**
| C3-01 | Thuê nhà ở cho BCH CT | House rental |
| C3-02 | Bảo hiểm tai nạn 24/24 | 24/7 accident insurance |
| C3-03 | Ban quản lý khu công nghiệp | Industrial Park Management Board |
| C3-04 | Mua bán điện ngoài mục đích sinh hoạt với Công ty Điện lực | Electricity supply agreement for non-residential use with EVN |
## 5.2 — CEO
| CEO-01 | Tất cả Hợp đồng chưa Ủy quyền | All Contracts Not Subject to Authorization | → N8 |
## ⚠️ Ô chưa chắc — anh soát giúp
1. **B2 trùng số "7"** (kim loại + cảnh quan) — lead đánh lại B2-07/B2-08, tổng B2 = **14 dòng** (không phải 13).
2. **A4 Ô2 "TPB/TBP" + Ô3 "TBP/NV.PB"** — dấu "/" em đọc là "hoặc" (OR-of-N như workflow V2). Đúng không?
3. **Cột NĐUQ** (giá trị ký theo Ủy quyền) — ảnh chỉ ghi chung "NĐUQ", KHÔNG có số tiền per dòng ⇒ seed để cột tham khảo trống/ghi "theo UQ" [khớp OG-3].
4. Trang 1-4 + 10-15 của bản 15-trang (phần quy trình chữ) em CHƯA có — nếu K1 cần gì ngoài bảng, anh gửi thêm file.
END transcribe — 85 dòng + 1 CEO = 86 row · 8 nhóm map đủ.

View File

@ -9,7 +9,7 @@
|---|---|---|---|
| 0 | Draft yêu cầu owner | em-main | ✅ `draft-yeu-cau-owner.md` |
| 1a | Spec tổng quát các wave | /fable-real invest | ✅ att-2 (att-1 ngược-#53 0-byte; att-2 A-D đầy 18.277B, E/F lead-compile; spot-check 4/4) |
| 1b | Review spec tổng quát — 3 lane ensemble: `WF reviewer lens1-cov s164` (phủ scope + OG-lậu) · `WF reviewer lens2-evi s164` (8 verdict vs code) · `WF reviewer lens3-seq s164` (thứ tự/khuôn/đo-được) | /fable-clone review (tier:opus ×3) | ⏳ |
| 1b | Review spec tổng quát — 3 lane `WF reviewer lens{1,2,3} s164` | /fable-clone review (tier:opus ×3) | ✅ 3/3 sạch 0-empty · 41 finding thô → synthesis 14 vá + 2 OG mới → **spec v2** |
| 2.x | Per cụm 3 wave: spec chi tiết + checklist | /fable-real invest (1 con/cụm) | ☐ |
| 2.x-r | Review spec chi tiết per cụm | /fable-clone review | ☐ |
| 3 | Chốt spec cuối | /fable-real review | ☐ |
@ -20,7 +20,7 @@
- [x] B0 — draft tổng hợp 10 lời chốt + model 4GĐ + 8 nhóm + scope 5 mảnh + ràng buộc khuôn + nền kỹ thuật + 8 treo
- [x] B1a — att-1 wf_22e8949b-afa CHẾT ngược-#53 (207K tok, 0 byte) · att-2 wf_bc04352b-6a4 A-D đầy + spec-file lead kết tinh `spec-4gd-khkk-tong-quat-31-07-2026.md` (8 verdict · 8 wave K1-K8 · 3 cụm · OG-1/2/3/5)
- [ ] B1b — fable-clone review
- [x] B1b — wf_8eb13087-5f9 3/3 lane sạch (l1 FAIL 5H · l2 6/6 chịu-lực ĐÚNG +l2-F1 rào-1-PE · l3 FAIL 6H + bác 6 nghi) → `b1b-review-synthesis.md` 14 vá + OG-6/OG-7 → spec v2 (hội tụ 2-lane: admin-hide tập-key tường minh; ApprovalGroup K2-chủ + backfill=1; 8 designer có phép đo; nguồn K1 = 7 ảnh [file đĩa 6-trang không bảng])
- [ ] B2 — cụm invest sâu + review (số cụm = theo spec B1)
- [ ] B3 — fable-real review chốt
- [ ] B4 — fan-out build

View File

@ -1,44 +1,49 @@
# SPEC TỔNG QUÁT — Chuỗi 4 GĐ theo SOL-CCM-SP-002 (B1a, lead verify + kết tinh @S164)
# SPEC TỔNG QUÁT v2 — Chuỗi 4 GĐ theo SOL-CCM-SP-002 (B1a + B1b review, lead kết tinh @S164)
> Nguồn: `sub-invest-fable-b1a.md` (engine /fable-real attempt-2, A-D đầy; E/F lane chết dở — lead compile từ nhãn inline C + cột guard D, khai rõ). Lead spot-check 4 claim chịu-lực §B: 4/4 ĐẠT (UNIQUE-filtered Line · only-ONE-active-per-type · `isAdminHidden` chỉ `Ct_` · 0 workflow type-10 seed). Yêu cầu gốc: `draft-yeu-cau-owner.md`.
> v1 = kết tinh từ `sub-invest-fable-b1a.md` (spot-check 4/4). **v2 = v1 + 14 vá từ B1b** (`b1b-review-synthesis.md` — 3 lane reviewer 41 finding thô; lens2 xác nhận 6/6 claim chịu-lực ĐÚNG ⇒ nền giữ, vỏ vá). Yêu cầu gốc: `draft-yeu-cau-owner.md`.
## ① Tính năng / đề bài
Chuỗi 4 GĐ per-gói-thầu theo quy trình SOL-CCM-SP-002: GĐ1 Duyệt NCC (PE = CHA, đã chạy) → GĐ2 KHKK = **duyệt HẠNG MỤC theo 8 NHÓM DUYỆT** (nén từ ~85 dòng danh mục SP-002; trình ký số trạm-by-trạm y Duyệt NCC; ký nháy = mực trên bản cứng GĐ4) → GĐ3 điều khoản → GĐ4 bản cứng 1-mốc. Scope 5 mảnh: **master-data danh mục** (admin CRUD + seed) · **sidebar eOffice fan-out 8 nhóm × 6 leaf** · **cây toàn trình mirror sidebar** (GĐ2 = 8 nhóm, GĐ3/4 = 7 loại HĐ) · **admin thu gọn** (chừa HỆ THỐNG + Quy trình duyệt (Mới) + 8 designer) · **dry-run E2E**. 🔴 Luật khuôn: **GIỐNG NHAU — CHỈ KHÁC FORM — ĐỪNG CHẾ THÊM** (nhân khung Duyệt NCC, đổi form).
Chuỗi 4 GĐ per-gói-thầu theo SOL-CCM-SP-002: GĐ1 Duyệt NCC (PE = CHA, đã chạy) → GĐ2 KHKK = **duyệt HẠNG MỤC theo 8 NHÓM DUYỆT** (trình ký số trạm-by-trạm y Duyệt NCC; ký nháy = mực trên bản cứng GĐ4) → GĐ3 điều khoản → GĐ4 bản cứng 1-mốc. Scope 5 mảnh: master-data danh mục · sidebar 8 nhóm × 6 leaf · cây mirror sidebar · admin thu gọn + 8 designer · dry-run E2E. 🔴 Luật khuôn: **GIỐNG NHAU — CHỈ KHÁC FORM — ĐỪNG CHẾ THÊM**. 🔸 Khai thẳng (B1b l3-F4): đợt này **8 nhóm = đơn vị HIỂN THỊ + luồng duyệt**, KHÔNG phải đơn vị authz — mọi endpoint KHKK giữ 1 bộ policy `KeHoachKyKet.{R,C,U,D}` per-action (`ContractSigningPlansController.cs:28+`); authz-per-nhóm nếu anh muốn = wave riêng sau.
## ② Cách implement
## ② Cách implement — 8 quyết định (v2)
**8 quyết định (từ 8 treo — chi tiết + evidence: sub-file §C):**
1. Cardinality "đưa vào loại HĐ" = **[OG-1]** default **GỘP theo cặp (NCC × dòng-danh-mục)** — N line trỏ chung `ContractId` (đã hợp khuôn `ContractSigningPlanLine.cs:25`, không UNIQUE); đảo được sang 1-1 cùng máy.
2. Phiếu KHKK = **per NHÓM** (1 phiếu = 1 nhóm duyệt, chứa N hạng mục); Lines repurpose per-hạng-mục (Mig thêm `CatalogEntryId` + `TenHangMuc`, đổi UNIQUE → (PlanId, SupplierId, CatalogEntryId) filtered — cardinality change ⇒ **grep MỌI consumer Lines**).
3. Workflow = **1 type-10 + 8 Code `KHKK-N1..N8`** (không mint type mới); only-ONE-active-per-type VÔ HẠI (create chỉ check type, picker lọc `isUserSelectable`); plan +cột `ApprovalGroup int` + validator line-cùng-nhóm.
4. Vai catalog = **2 tầng**: danh mục lưu chuỗi vai TEXT (hiển thị/in khối ký) ⟂ workflow V2 Levels lưu NGƯỜI THẬT đích danh (không dựng bảng role-map, không đẻ AppRole mới).
5. NĐUQ = cột THAM KHẢO text trên danh mục đợt này **[OG-3]**; máy enforce (`CeoApprovalThreshold` type-10) = wave opt-in sau (hiện 0 consumer CỐ Ý — set nay là config-lie #78).
6. Menu: **GIỮ key `Khkk_G1` + 6 leaf cũ, đổi LABEL** thành nhóm 1; nhóm 2-8 = key mới `Khkk_G{n}_*` (48 leaf; labelBackfill khuôn S155).
7. Admin ẩn = **fe-admin `filterForAdmin` đảo blacklist→whitelist** (chừa `System` subtree + AwV2) — 1 file FE, 0 BE, KHÔNG đụng `IsVisible` (ăn cả fe-user).
8. Số dòng danh mục + B2 trùng "7" + "TPB/TBP" = **extract file gốc QUY_TRINH bằng office-document = việc ĐẦU K1**; "/" đọc là OR-of-N.
1. **[OG-1]** Cardinality "đưa vào loại HĐ" default **GỘP theo cặp (NCC × dòng-danh-mục)** — N line chung `ContractId` (hợp khuôn `ContractSigningPlanLine.cs:25`, index không-unique `Configuration:26`); đảo 1-1 được cùng máy.
2. Phiếu KHKK = **per NHÓM** (1 phiếu = 1 nhóm, N hạng mục). Lines per-hạng-mục: Mig +`CatalogEntryId Guid?` +`TenHangMuc`, UNIQUE → (PlanId, SupplierId, CatalogEntryId) filtered. 🔴 **+2 vá B1b:** (a) **đổi rào 1-PE-1-phiếu** (`ContractSigningPlanFeatures.cs:342-347` + picker `:1201-1202`) → (PeId, ApprovalGroup) **[OG-7]** — FE tree đã chịu N-phiếu/PE (`usePipelineStages.ts:110-121`); (b) **bất-biến tiền**: `PeReferenceAmount` = snapshot per-NCC-toàn-phiếu (`Line.cs:6,:16-20`) — repurpose per-hạng-mục PHẢI định nghĩa lại semantics + Σ trước == Σ sau mig, acceptance rà **16 consumer** (10 BE `.Lines` + 6 file FE — danh sách l3-F18).
3. Workflow = **1 type-10 + 8 Code `KHKK-N1..N8`**; only-ONE-active VÔ HẠI (chứng 2 mắt xích l2: create chỉ check type `:209-220`; overview không lọc IsActive `:117-134`, POST không đụng IsUserSelectable `:341-353`). 🔴 Cột `ApprovalGroup` **K2 là CHỦ DUY NHẤT** (l3-F6) — K3 chỉ đọc (map Code↔group + validator phiếu-pin-đúng-nhóm).
4. Vai catalog = 2 tầng: danh mục lưu chuỗi vai TEXT (in khối ký GĐ4) ⟂ workflow Levels lưu NGƯỜI THẬT. 🔸 Khai limitation (l1-F12): TEXT không ràng buộc máy với người-duyệt-thật — chấp nhận đợt này, khai trong doc K1.
5. NĐUQ = cột THAM KHẢO text **[OG-3]**; enforce (`CeoApprovalThreshold`) = wave opt-in sau (0 consumer CỐ Ý — `WorkflowService.cs:44`).
6. Menu: GIỮ `Khkk_G1` + 6 leaf cũ, đổi LABEL = nhóm 1 (labelBackfill khuôn S155). Nhóm 2-8 = **42 leaf MỚI + 7 group-key mới (49 row menu mới; 48 leaf TỔNG)** — sửa số l1-F8/l2-F5. 🔴 Phiếu cũ: **backfill `ApprovalGroup = 1`** (l3-F5 — mirror bài `EvaluationType` không rỗng; acceptance đếm `COUNT(phiếu) == Σ COUNT 8 folder`).
7. Admin ẩn = fe-admin **`isAdminHidden` MỞ RỘNG TẬP KEY TƯỜNG MINH** (giữ blacklist — KHÔNG đảo whitelist, tránh đổi default menu-mới; vá hội tụ l1-F2 l3-F1/F2): ẩn thêm {`Dashboard?`→giữ, các root nghiệp vụ: `DuyetNcc-root keys`, `KeHoachKyKet`, HR/Office/Personal, `Contracts`(đã ẩn `Ct_*`), `HardCopies`, `Forms`, `Reports`, `Catalogs/Master`, **`Workflows` (:1795)**, **`PeWorkflows` (:1798)**} — CHỪA {`System` + 4 leaf quản trị + **`ApprovalWorkflowsV2` (:1801, con System)** + subtree}. Danh sách key CHÍNH XÁC = việc B2-cụm-2 liệt từ `DbInitializer` seed; acceptance = **liệt kê đúng N mục lá còn thấy** (l3-F2).
8. Nguồn danh mục: **file đĩa `QUY_TRINH/QT TRINH KY HOP DONG TP-NCC.docx` = ĐÚNG SOL-CCM-SP-002.v01 (mã ở word/header1.xml) NHƯNG bản 6-trang KHÔNG chứa bảng** (lead đo). Nguồn bảng = **7 ảnh anh gửi @S164 (trang 5-9, trọn 5.1 A/B/C + 5.2 CEO)** → transcribe → **[OG-6] anh soát bảng seed trước land** (hoặc anh gửi file 15-trang). "/" trong ô vai = OR-of-N.
**8 wave K1-K8, 3 cụm (bảng đầy đủ + DoD: sub-file §D):**
- **Cụm 1 (nền):** K1 master-data danh mục (Mig 70 + seed idempotent + CRUD + extract gốc) → K2 phiếu mang hạng-mục+nhóm (Mig 71 + validator + FE lines editor) → K3 8 workflow nhóm seed + picker lọc + designer entry.
- **Cụm 2 (UI — chờ `approvalGroup` từ cụm 1):** K4 sidebar 48 leaf + grant seeder (KHKK không inherit; #85 gate đúng key) → K5 cây sub-folder (GĐ2 group-by `approvalGroup`, GĐ3/4 group-by `c.type`; mirror SHA ×2 app) → K6 admin thu gọn (độc lập — kéo sớm được).
- **Cụm 3 (nối + chạy):** K7 cầu KHKK→HĐ + đường ống V2 (tái xuất W5/W6 S160 — **đầu wave RE-ĐO** đã land chưa; bridge đọc `ApprovedAmount` per-Line, gộp theo OG-1) → K8 **DRY-RUN E2E** từ `PE/2026/A/049` (ZZTEST convention, checklist S160 §4).
## ③ Wave K1-K8 (v2 — PRE-gate + acceptance có răng)
**[OWNER-GATE] (lead compile từ nhãn inline §C — lane chết trước khi gom §E):**
- **OG-1** cardinality gộp (default = gộp theo cặp NCC×dòng; anh phủ quyết được sang 1-1).
- **OG-2** roster map vai→user khi seed 8 workflow (đội hình đề xuất = danh sách sống `QT-DN-V2-001`, note 54 — cần anh gật trước khi seed).
- **OG-3** NĐUQ chỉ tham khảo đợt này, enforce = đợt sau.
- **OG-5** nếu extract file gốc vẫn mơ hồ B2 trùng-số/"TPB/TBP" → hỏi anh. *(Không có OG-4 trong sub-file — số nhảy là của lane, giữ nguyên không bịa.)*
**Cụm 1 (nền):**
- **K1 Master-data danh mục** — PRE: **[OG-6]**. BE entity+config+CQRS+controller per-action policy; Mig 70 (3-file rule) filtered-unique Code #57; seed per-Code idempotent; FE leaf Catalogs ×2 app. DoD: N dòng khớp **~85±5, lệch giải trình từng dòng** (đối chứng ảnh) · tên VN+**EN** đủ · CRUD 200 · seed ×2 không nhân row · test **≥590+k, khai k**.
- **K2 Phiếu mang hạng-mục + nhóm** — PRE: **[OG-1] + [OG-7]**. Mig 71 (3-file): plan +`ApprovalGroup int` **backfill=1**; Lines +2 cột + đổi UNIQUE; **đổi rào (PeId)→(PeId,ApprovalGroup) 2 site** `:342-347`/`:1201-1202`; list-query +param `group` (`ListContractSigningPlansQuery` `:622-625`**K2 chủ**, l3-F3) + DTO `ContractSigningPlanListItemDto:34` +`approvalGroup` (sửa citation l2-F4) + FE types. DoD: phiếu nhóm 2 CÙNG PE → **201** · line khác nhóm → 409 · phiếu cũ ở folder nhóm 1, `COUNT == Σ 8 folder` · **Σ tiền trước == sau mig** · bảng rà 16 consumer ĐÃ-XỬ/KHÔNG-ĐỔI · test ≥590+k.
- **K3 8 workflow nhóm + designer** — PRE: **[OG-2] gật roster + verify đội hình LIVE (không phải archived)**. Seed 8 workflow `KHKK-N1..N8` type-10 (khuôn `:171`) + validator pin-đúng-nhóm; **8 menu-con dưới `ApprovalWorkflowsV2`** + mở `resolvePath` fe-admin `Layout.tsx:166-172` nhận code mới (leaf lạ rớt im lặng #50) + route designer per-workflow. DoD: API trả 8 · **sidebar admin đếm được 8 designer, mở designer nhóm n load đúng `KHKK-N{n}`** (l3-F10) · phiếu nhóm 3 pin workflow nhóm 5 → 409 · duyệt trọn trạm 1 phiếu → DaDuyet · test ≥590+k.
**Rủi ro + guard (lead compile từ cột guard bảng D + draft §5):** K1 ăn #57 filtered-unique + #75/#76 seed-gate · K2 = cardinality-change class S87/S88 (grep write+read site) · K3 ăn #78 config-lie nếu port threshold sớm · K4 ăn #85/#82 (gate đúng key policy; grep controller authz) + 6-chỗ-mirror S155 · K5 mirror SHA ×2 app + #68 (chỉ tin build sạch cuối) · K6 rủi ro thấp (1 file, revert 1 commit) · K7 ăn 3 lỗ đường ống S156/S160 (`GetEligiblePhases` legacy + bridge V1-only + guard PMH 403) + Reject-trước-guard treo-có-chủ-đích · K8 ăn HỐ-1 S160 (HĐ DaPhatHanh không xóa được qua API) + notification bắn user thật.
**Cụm 2 (UI):** *(l3-F8: tách phụ thuộc thật — K4a chạy song song cụm 1 được)*
- **K4a Menu+grant** (0 phụ thuộc): 49 row menu + grant 3 SITE (`:1780` seed · `SeedKeHoachKyKetAccessAsync:2415` · **`KhkkKeys():2185` — site-3 l2-F6**, comment `:2204` "quên 1 bên = quyền lệch"). **K4b 4 leaf list + Thao tác** (chờ K2 param). **K4c leaf WfView/Create** (chờ K3): WfView mang `?group=` lọc theo workflow Code nhóm (hiện `WorkflowMatrixViewPage:52-71` chỉ lọc type — l2-F3). DoD: 48 leaf render đúng · leaf lọc đúng group **kể cả WfView/Create** · **2 chiều theo bảng kỳ-vọng 13 role** (seeder hiện phủ 7 vai `:2671-2675` — 6 vai còn lại khai kỳ vọng TRƯỚC, l3-F13) · key cũ 0 mồ côi.
- **K5 Cây sub-folder**: GĐ2 group-by `approvalGroup` · GĐ3/4 group-by `c.type` (đã có `:240`); thêm tầng `groups` vào `PipelineStage` (mở rộng khuôn — bẫy `content` phải truyền `count` tay `:74-75`). DoD: sub-folder khớp sidebar · **2 file đích danh SHA-identical ×2 app** (`usePipelineStages.ts` + `PipelineTreePanel.tsx` — l3-F19) · build ×2 PASS.
- **K6 Admin thu gọn** (độc lập NHƯNG l3-F9: kéo sớm TRƯỚC K3 thì 8 designer chưa tồn tại — nếu kéo sớm, acceptance "đếm 8 designer" dời sang sau K3): QĐ7 tập-key tường minh. DoD: **liệt kê đúng N mục lá còn thấy (đủ tên, dư/thiếu 1 = FAIL)** · fe-user KHÔNG đổi = **`git diff --stat <base>..<head> -- fe-user/src` = 0 file** (KHÔNG đo bundle-hash #69 — l3-F12).
## ③ Checklist (mỗi dòng = deliverable + acceptance ĐO ĐƯỢC)
**Cụm 3 (nối + chạy):**
- **K7 Cầu KHKK→HĐ** — RE-ĐO **3 NẤC** (l3-F11): (a) cột/index ✓ có (`Line.cs:25`+`Config:26`); (b) **write-site gán `ContractId` = 0 hiện tại** (lead xác nhận grep); (c) test E2E — ⇒ trạng thái NỬA-LAND, K7 build phần (b)+(c). Bridge đọc `ApprovedAmount` per-Line + gộp theo OG-1 + pin workflow V2 + chọn `ContractType` lúc bắc cầu (**ai chọn = câu nghiệp vụ, mặc định người bấm chọn trong dialog — l1-F15**). DoD: KHKK DaDuyet → HĐ đúng số lượng OG-1 · `giaTri == Σ ApprovedAmount` **phạm-vi-gộp khai rõ, null → chặn với message** (l1-F6) · approver V2 thấy + inbox · test bridge ≥590+k.
- **K8 DRY-RUN E2E** từ `PE/2026/A/049`. DoD (+3 dòng l3-F16): mọi bản ghi mới mang tiền tố **ZZTEST** · danh sách bản-ghi-không-xoá-được + cách sống chung (HĐ DaPhatHanh guard numeric) · **tắt/dặn-team notification trước khi chạy** · toàn trình hết trạm → HĐ · cây đủ 4 GĐ · screenshot + mã phiếu thật.
- [ ] **K1**: bảng danh mục N dòng == số extract file gốc (khai N); CRUD admin 200; seed chạy 2 lần không nhân row (test idempotent); `dotnet test` PASS.
- [ ] **K2**: tạo phiếu nhóm X + N hạng mục qua UI ×2 app; line khác nhóm → 409; phiếu cũ mở nguyên vẹn (`CatalogEntryId=NULL` = hạng mục tổng); test validator ÂM PASS.
- [ ] **K3**: `GET /approval-workflows-v2?applicableType=10` trả 8 workflow `KHKK-N1..N8`; phiếu nhóm 3 pin workflow nhóm 5 → 409; duyệt trọn trạm 1 phiếu bất kỳ → DaDuyet.
- [ ] **K4**: sidebar 8 mục + 48 leaf đúng thứ tự; leaf lọc đúng `ApprovalGroup`; 13/13 role 0 rác 403; key cũ 0 mồ côi.
- [ ] **K5**: cây hiện sub-folder GĐ2 theo nhóm + GĐ3/4 theo loại khớp sidebar; 2 file pipeline SHA-identical ×2 app; `npm run build` ×2 PASS.
- [ ] **K6**: sidebar admin chỉ còn HỆ THỐNG + Quy trình duyệt (Mới); diff bundle fe-user = 0.
- [ ] **K7**: KHKK DaDuyet → tạo HĐ đúng số lượng OG-1, `giaTri == ApprovedAmount`; approver V2 thấy + inbox HĐ; test bridge PASS.
- [ ] **K8**: toàn trình từ `PE/2026/A/049` chạy hết trạm → HĐ; cây đủ 4 GĐ có leaf; bằng chứng screenshot + mã phiếu thật.
- [ ] Mỗi wave: cicd PASS + bundle byte-verify (#77) trước khi báo xong.
**Mọi wave:** cicd PASS; **byte-verify #77 CHỈ wave có FE-ship** — wave BE-only = smoke API (l3-F17); mig = 3-file rule.
## ④ [OWNER-GATE] — 4 cũ + 2 mới (nối vào PRE từng wave ở trên)
| OG | Câu hỏi | Default nếu anh không đổi | Chặn wave |
|---|---|---|---|
| OG-1 | Gộp HĐ: N hạng-mục-đã-duyt cùng (NCC × dòng-danh-mục) → 1 HĐ? | GỘP | K2, K7 |
| OG-2 | Roster 8 workflow: map vai → đội hình LIVE `QT-DN-V2-001`? | Đội hình live (verify LIVE trước seed) | K3 |
| OG-3 | NĐUQ đợt này chỉ THAM KHẢO (text), máy chưa enforce? | Tham khảo | — |
| OG-6 | Bảng danh mục transcribe từ 7 ảnh — anh soát trước seed (hay gửi file 15-trang)? | Transcribe + anh soát | K1 |
| OG-7 | 1 PE được N phiếu KHKK (mỗi nhóm 1 phiếu) — đổi rào 1-PE-1-phiếu? | Đổi rào theo (PE, nhóm) | K2 |
| OG-5 | Extract mơ hồ (B2 trùng số "7", "TPB/TBP") → hỏi khi gặp | — | K1 |
END spec v2 — 8 wave · 8 quyết định · 6 OG · 14 vá B1b áp đủ.

View File

@ -1,9 +1,134 @@
# lens1-cov — coverage + owner-gate
Đối tượng chấm: `spec-4gd-khkk-tong-quat-31-07-2026.md` (viết tắt **spec:N**)
Yêu cầu gốc: `draft-yeu-cau-owner.md` (viết tắt **draft:N**)
Lăng kính: (Q1) phủ đủ 5 mảnh scope + 2 yêu cầu bổ sung? · (Q6) OG list đủ chưa — có chốt lậu không?
## Q1 — bảng mảnh → wave phủ → lỗ
| # | Mảnh (draft §3) | Wave spec | Trạng thái |
|---|---|---|---|
| (i) | Master data danh mục (draft:46) | K1 (spec:22, :36) | ⚠️ nguồn extract KHÔNG TỒN TẠI theo tên (F-1); acceptance chỉ đo SỐ DÒNG, 0 đo cột (F-14) |
| (ii) | eOffice sidebar 8 nhóm × 6 leaf (draft:48) | K4 (spec:23, :39) | ⚠️ thiếu deliverable FE route-map ×2 app (F-9); số 48 vs 42 + 2 dạng key (F-8) |
| (iii) | Cây toàn trình mirror sidebar (draft:50) | K5 (spec:23, :40) | ✅ nền có thật cho cả GĐ3 (`Ct_*`) lẫn GĐ4 (`Hdc_*` 7 leaf, `DbInitializer.cs:1868,:1981-1987`) |
| (iv) | Admin thu gọn (draft:52) | K6 (spec:23, :41) + K3 | ❌ **vỡ 2 nửa**: whitelist giữ đúng 2 mục owner bảo ẩn (F-2); nửa "8 designer" 0 wave 0 acceptance (F-3) |
| (v) | Dry-run E2E (draft:54) | K8 (spec:24, :43) | ⚠️ chạy prod, tác dụng phụ không xoá được + ping user thật, 0 gate (F-13) |
| bổ sung 1 | "Tạo luôn master data danh mục" (lời 10, draft:16) | K1 | = (i), xem F-1/F-14 |
| bổ sung 2 | "yêu cầu là dry-run luôn" (lời 10) | K8 | = (v), xem F-13 |
### Đối chiếu 10 lời chốt verbatim §0 draft → wave/quyết định nào ăn
| Lời | Nội dung | Ăn ở đâu | Verdict |
|---|---|---|---|
| 1 (draft:7) | PE bự → bổ hạng mục nhỏ | QĐ2 + K2 (spec:13) | ✅ |
| 2 (draft:8) | hạng mục ≠ loại HĐ ≠ 5 form; "đặt tên field tách bạch" (draft:27) | — | ⚠️ F-15 (kỷ luật đặt tên rơi; spec vẫn xài "loại HĐ" 2 nghĩa trong cùng file) |
| 3 (draft:9) | duyệt xong → **"đưa vào loại hợp đồng gì"** | QĐ1 (spec:12) chỉ chốt CARDINALITY | ⚠️ F-15: cơ chế CHỌN loại HĐ/khuôn lúc bắc cầu K7 không giao cho ai |
| 4 (draft:10) | GĐ3 chỉ điều khoản | spec:7 | ✅ (cố ý ngoài scope) |
| 5 (draft:11) | ký nháy = mực trên bản cứng; "in khối ký đúng pattern" (draft:25) | chỉ nằm trong ngoặc lý do QĐ4 (spec:15) | ❌ F-11: 0 wave, 0 checklist |
| 6 (draft:12) | fan-out A1/A2 + chú thích ngắn | K4 | ✅ (chú thích ngắn không có phép đo — LOW, gộp F-8) |
| 7 (draft:13) | "Ẩn hết… chỉ còn Quy trình duyệt (Mới)" + chừa User + "theo nhóm duyệt" | QĐ7/K6 + QĐ2 | ❌ F-2 (whitelist mâu thuẫn) + ❌ F-3 (8 designer) |
| 8 (draft:14) | thêm sub-folder + sliderbar giống nhau | K5 | ✅ |
| 9 (draft:15) | giống nhau, chỉ khác form, đừng chế | spec:7 + acceptance SHA K5 (spec:40) | ✅ |
| 10 (draft:16) | dry-run + master data | K8 + K1 | ✅ có wave (chất lượng: F-1, F-13) |
## Findings
(ĐANG LÀM)
### F-L1-1 [HIGH] — Nguồn dữ liệu của K1 không tồn tại theo cái tên spec ghi
- **Claim:** "việc ĐẦU K1" = extract file gốc `SOL-CCM-SP-002` (spec:19) trỏ vào một tên file 0 hit trên toàn cây `D:\Dropbox\CONG_VIEC\SOLUTION`.
- **Evidence:** `find . -iname "*SP-002*"`**0 hit**; control-dương cùng lệnh cùng cây `-iname "*FO-002*"`**7 hit** (`FORM/SOL-CCM-FO-002.01…07`) ⇒ không phải thước hỏng. `QUY_TRINH/` chỉ có **1** file: `QT TRINH KY HOP DONG TP-NCC.docx` (141.036 B). Draft:42 cũng ghi path `…\QUY_TRINH\SOL-CCM-SP-002…`.
- **Vì sao HIGH:** K1 là wave nền của cụm 1; K2 (CatalogEntryId), K4 (ApprovalGroup) đều phụ thuộc. OG-5 (spec:30) chỉ phủ ca "extract vẫn mơ hồ", KHÔNG phủ ca "không tìm thấy file".
- **Đề nghị:** pin TÊN FILE THẬT + kích thước vào spec trước khi K1 chạy; nếu `QT TRINH KY HOP DONG TP-NCC.docx` chính là SP-002 thì khai đính chính; acceptance K1 thêm "trích được bảng 3.5 với N dòng, N khai trước khi seed".
### F-L1-2 [HIGH] — Whitelist "System subtree" GIỮ LẠI đúng 2 mục owner ra lệnh ẩn
- **Claim:** QĐ7 "chừa `System` subtree + AwV2" (spec:18) mâu thuẫn trực tiếp draft:52 ("+ 2 mục quy trình cũ (Quy trình Duyệt NCC · Quy trình HĐ)" nằm trong danh sách ẨN) và lời 7 (draft:13 *"chỉ còn cái Quy trình duyệt (Mới) thôi"*).
- **Evidence (đĩa):** `DbInitializer.cs:1795` `(MenuKeys.Workflows, "Quy trình HĐ", MenuKeys.System, 95, …)` · `:1798` `(MenuKeys.PeWorkflows, "Quy trình Duyệt NCC", MenuKeys.System, 95, …)` · `:1801` `(MenuKeys.ApprovalWorkflowsV2, "Quy trình duyệt (Mới)", MenuKeys.System, 96, …)`. ⇒ cả 3 đều là CON của `System`; whitelist cả subtree = giữ nguyên 2 mục cũ. Vế "+ AwV2" trong QĐ7 còn **thừa** (AwV2 vốn ở trong System).
- **Acceptance không bắt được:** K6 (spec:41) "sidebar admin chỉ còn HỆ THỐNG + Quy trình duyệt (Mới)" — 2 mục cũ nằm BÊN TRONG "HỆ THỐNG" nên câu này vẫn đọc là PASS. Đây là dòng acceptance không phân biệt được đúng/sai.
- **Đề nghị:** whitelist ở mức KEY (`System, Users, Roles, Permissions, MenuVisibility, ApprovalWorkflowsV2` + con của AwV2), KHÔNG "subtree"; acceptance đổi thành liệt kê ĐỦ tên mục còn lại và ghi rõ `Workflows`/`PeWorkflows` phải VẮNG.
### F-L1-3 [HIGH] — Nửa "+= 8 designer nhóm-duyệt" của mảnh (iv) không có wave, không có acceptance
- **Claim:** draft:52 đòi *"Quy trình duyệt (Mới) **+= 8 designer nhóm-duyệt** (khuôn y Duyệt NCC (Mới): Bước—Phòng—Cấp, versioned + archived history)"*. Spec chỉ có 2 chữ "designer entry" trong mô tả K3 (spec:22) và "+ 8 designer" trong §① (spec:7) — **0 dòng checklist** đo nó; sub-file invest còn chốt "0 designer mới" (`sub-invest-fable-b1a.md:53`).
- **Evidence nền:** tiền lệ là 2 leaf con của AwV2 (`DbInitializer.cs:1802-1803` `AwV2_DuyetNcc` / `AwV2_DuyetNccPhuongAn`); route FE là allowlist HARDCODE 3 code (`fe-admin/src/components/Layout.tsx:169`: `code === 'DuyetNcc' || 'DuyetNccPhuongAn' || 'Contract'`) ⇒ 8 designer nhóm cần +8 MenuItem +8 nhánh route, không tự có.
- **Acceptance hiện tại đo trật:** K3 (spec:38) chỉ đo `GET /approval-workflows-v2?applicableType=10` trả 8 — API trả 8 mà admin UI vẫn 0 mục designer nhóm thì vẫn PASS.
- **Đề nghị:** hoặc thêm dòng checklist "8 leaf `AwV2_KHKK_N1..N8` hiện dưới Quy trình duyệt (Mới), click mở đúng designer của Code tương ứng", hoặc khai TỪ CHỐI phạm vi (1 designer type-10 dùng chung) và cho owner biết vì đó là thứ anh nhìn thấy.
### F-L1-4 [HIGH] — 4 OWNER-GATE được khai nhưng KHÔNG nối vào wave nào ⇒ không chặn được gì
- **Claim:** §② liệt OG-1/OG-2/OG-3/OG-5 (spec:26-30) nhưng §③ checklist (spec:36-44) **0 dòng** nhắc tới gate; bảng wave (spec:22-24) cũng không ghi tiền-điều-kiện.
- **Cụ thể OG-2 (roster map vai→user):** phải gật TRƯỚC **K3** (wave seed 8 workflow, spec:22) — spec nói "cần anh gật trước khi seed" (spec:28) nhưng DoD K3 (spec:38) không có dòng đó. Fan-out B4 dặn worker "bám sát spec-chi-tiết + checklist" (draft:86) ⇒ worker đọc §③, nơi gate vô hình.
- **Rơi hẳn:** cảnh báo ⚠️ của draft:52 *"verify bản LIVE không phải archived trước khi wire"* không xuất hiện ở bất kỳ đâu trong spec ⇒ nguy cơ seed roster từ bản workflow đã archive.
- **Đề nghị:** mỗi wave có gate thì DoD mở đầu bằng "[BLOCKED-BY OG-n] — chưa có gật thì không chạy"; thêm 1 dòng checklist "OG-1/2/3/5 đã có phán quyết bằng văn bản (link) trước khi cụm tương ứng khởi động"; OG-2 kèm bước verify workflow `QT-DN-V2-001` bản LIVE.
### F-L1-5 [HIGH] — Repurpose `Lines` per-hạng-mục phá nghĩa `PeReferenceAmount`, checklist 0 bất-biến tài chính
- **Claim:** QĐ2 (spec:13) đổi `Lines` từ 1-row-per-NCC sang N-row-per-(NCC × hạng mục). Entity hiện tại nói rõ **"1 row = 1 NCC (winner của PE nguồn)"** và `PeReferenceAmount` = **SNAPSHOT tổng báo giá của NCC này trên phiếu PE (SUM Quote.IsSelected)** — một con số PER-NCC-TOÀN-PHIẾU.
- **Evidence:** `src/Backend/SolutionErp.Domain/ContractSigningPlans/ContractSigningPlanLine.cs:6` (comment "1 row = 1 NCC"), `:16-20` (`PeReferenceAmount` snapshot), `:9-10` (UNIQUE (PlanId, SupplierId) filtered `WHERE IsDeleted=0`).
- **Hệ quả:** sau khi nhân dòng, mọi nơi SUM `PeReferenceAmount` theo phiếu sẽ nhân N lần (cùng NCC lặp lại trên N hạng mục). Spec tự gọi đúng tên lớp lỗi (S87/S88, spec:32) nhưng guard duy nhất là **quy trình** ("grep MỌI consumer"), còn acceptance K2 (spec:37) chỉ đo tạo phiếu / 409 / mở phiếu cũ / test validator âm — **không có một phép so tiền nào**.
- **Đề nghị:** thêm dòng acceptance đo được: "trên ≥1 phiếu KHKK có sẵn, tổng tiền hiển thị/API TRƯỚC và SAU Mig 71 bằng nhau (chụp số)"; và chốt luôn ngữ nghĩa mới của `PeReferenceAmount` ở cấp dòng (chia? lặp? NULL?).
### F-L1-6 [MED] — Acceptance K7 `giaTri == ApprovedAmount` mâu thuẫn chính default OG-1
- **Claim:** OG-1 default = **GỘP** N line → 1 HĐ (spec:12) nhưng acceptance K7 viết số ít `giaTri == ApprovedAmount` (spec:42); gộp thì phải là `SUM(ApprovedAmount)` của các line cùng (NCC × dòng-danh-mục).
- **Evidence:** `ContractSigningPlanLine.cs:24` `public decimal? ApprovedAmount`**nullable**, "CHỈ ghi tại choke-point finalize" ⇒ gộp khi có line NULL chưa được định nghĩa (bỏ qua? coi 0? chặn?).
- **Thiếu thêm:** **phạm vi gộp** không khai — trong 1 phiếu, trong 1 PE cha, hay toàn cục theo (SupplierId, CatalogEntryId)? Đọc thẳng mặt chữ "gộp theo cặp (NCC × dòng-danh-mục)" có thể gộp xuyên phiếu/xuyên dự án.
- **Đề nghị:** viết lại acceptance thành `giaTri == SUM(ApprovedAmount) các line thuộc nhóm gộp` + khai rõ khoá gộp gồm cả `ContractSigningPlanId` (hoặc PE cha) + xử lý NULL.
### F-L1-7 [MED] — Treo #6 (phiếu KHKK cũ) chỉ được trả lời nửa MENU, rơi hẳn nửa DỮ LIỆU
- **Claim:** draft:76 hỏi *"Mục '1. Kế hoạch ký kết HĐ (NCC-TP)' cũ (đã có phiếu?) → migrate/giữ/ẩn thế nào"*. QĐ6 (spec:17) trả lời phần MENU (giữ key, đổi label). Phần DỮ LIỆU: QĐ3 thêm cột `ApprovalGroup **int**` (spec:14 — mặt chữ = non-null) mà **không có backfill/default** cho phiếu đã tồn tại.
- **Hệ quả đo được:** K4 (spec:39) "leaf lọc đúng `ApprovalGroup`" ⇒ phiếu cũ mang group mặc định 0 sẽ **không thuộc leaf nào trong 8 nhóm** = biến mất khỏi điều hướng mới. K2 (spec:37) "phiếu cũ mở nguyên vẹn" chỉ nói `CatalogEntryId=NULL`, không đả động ApprovalGroup.
- **Đề nghị:** chốt `ApprovalGroup int?` (nullable) + quy tắc hiển thị phiếu chưa gán nhóm (leaf "Chưa phân nhóm" hoặc backfill về nhóm 1 kèm bằng chứng đếm row trước/sau).
### F-L1-8 [MED] — Số leaf 48 vs 42 + hai dạng key song song (hệ quả của QĐ6, không ai đo)
- **Claim:** QĐ6 (spec:17) viết "nhóm 2-8 = key mới `Khkk_G{n}_*` **(48 leaf)**" — nhóm 2-8 là **7 nhóm × 6 = 42 leaf mới**; 48 là TỔNG (khớp checklist spec:39 "8 mục + 48 leaf").
- **Evidence hình dạng thật:** `DbInitializer.cs:1780` `Khkk_G1`**GROUP** dưới root `KeHoachKyKet`; 6 leaf con là `Khkk_WfView/List/Create/Pending/Approved/Deleted` (`:1782-1787`) — **không có infix `G1_`**. Giữ key cũ ⇒ tồn tại song song 2 dạng (`Khkk_List``Khkk_G2_List`), mọi parser/route-map/seeder-grant phải xử 2 nhánh.
- **Đề nghị:** khai rõ tổng 8 group + 48 leaf, trong đó **42 leaf mới + 7 group mới = 49 MenuItem mới**; thêm acceptance "0 chỗ nào parse key bằng cách cắt cứng prefix" hoặc chấp nhận rename có backfill label (khuôn S155 đã nhắc).
### F-L1-9 [MED] — K4 thiếu deliverable "route-map FE ×2 app"; acceptance K4 không ghi ×2 app
- **Claim:** leaf không có route ⇒ **biến mất im lặng**, không lỗi. Spec K4 (spec:23, :39) không nhắc gì tới static route map.
- **Evidence:** `fe-user/src/components/Layout.tsx:59-62,:82-83``fe-admin/src/components/Layout.tsx:35-40` đều **hardcode đúng 6 key `Khkk_*`**; `fe-admin/src/components/Layout.tsx:270` `if (!path) return null` (leaf tự rụng); comment ngay tại `fe-admin/…/Layout.tsx:40`: *"[S159-đợt5 FLAG-1] thiếu là leaf drop SILENT (gotcha #50)"*; run S159 đã dính thật ("fe-admin thiếu 2 route Khkk → FIX +2 staticMap").
- **Thêm:** K2/K5 có chữ "×2 app" trong acceptance, K4 **không** ⇒ rớt 1 app vẫn PASS. (Lưu ý tương tác: nếu K6 whitelist land trước, nhánh admin của 42 leaf thành vô ích — spec nên nói rõ K4 chỉ làm fe-user hay cả 2.)
### F-L1-10 [MED] — "only-ONE-active-per-type VÔ HẠI" chưa chứng đủ, và acceptance K3 không thể phát hiện nếu sai
- **Claim:** spec:14 khẳng định vô hại vì "create chỉ check type, picker lọc `isUserSelectable`" — biện luận này phủ 2 consumer, không phủ hết.
- **Evidence:** `SolutionErp.Application/ApprovalWorkflowsV2/ApprovalWorkflowV2AdminFeatures.cs:341-343``.Where(w => w.ApplicableType == typeEnum && w.IsActive)` rồi `foreach (var old in actives) old.IsActive = false;` ⇒ mỗi lần tạo bản mới **tắt toàn bộ workflow cùng type**; sau khi seed 8 nhóm sẽ còn **1 active / 7 inactive**, và mỗi lần admin tạo version cho nhóm X thì 7 nhóm kia lại bị tắt (hazard LẶP, không chỉ lúc seed). Consumer khác vẫn đọc cờ này: `:210` `versions.FirstOrDefault(v => v.IsActive)`.
- **Acceptance mù:** K3 (spec:38) chỉ đếm `GET …?applicableType=10` trả 8 — trả 8 bản kể cả 7 bản `IsActive=false` thì vẫn PASS.
- **Đề nghị:** acceptance K3 thêm "8 bản đều `isActive=true` sau seed" **và** "tạo version mới cho nhóm 5 xong, 7 nhóm còn lại vẫn active" (đây mới là phép làm TRƯỢT nếu 'vô hại' sai).
### F-L1-11 [MED] — "App in khối ký nháy" không có chủ sở hữu
- **Claim:** lời 5 (draft:11) + draft:25 *"app in khối ký đúng pattern"*; spec chỉ để nó trong ngoặc lý do của QĐ4 ("hiển thị/in khối ký", spec:15). Không wave nào nhận, không dòng checklist nào đo. GĐ4 đã chốt 1-mốc xong từ S161 ⇒ không có wave GĐ4 để rơi vào.
- **Đề nghị:** hoặc gắn vào K1 (render khối ký từ chuỗi vai của dòng danh mục) với acceptance "in thử 1 phiếu, khối ký khớp cột chuỗi-nháy", hoặc khai defer công khai.
### F-L1-12 [MED] — QĐ4 (vai TEXT 2 tầng) là ứng viên OWNER-GATE bị tự chốt (chốt lậu ứng viên #1)
- **Claim:** QĐ4 (spec:15) quyết định rằng chuỗi vai (TBP.CCM, QS…) chỉ là **TEXT** trên danh mục, còn người duyệt thật nằm ở V2 Levels — **không có ràng buộc nào giữa 2 tầng**. Hệ quả nghiệp vụ: giấy in ra ghi "TBP.CCM ký" trong khi người bấm duyệt có thể là ai khác; hệ thống không phát hiện, không có phép đo.
- **Vì sao là quyền owner:** đây là thứ owner nhìn thấy trên bản cứng và là lý do tồn tại của SP-002 (ai được ký). 4 quyết định khác được dán nhãn OG (1/2/3/5); QĐ4 thì không, dù mức độ nhìn-thấy-được cao hơn OG-3 (NĐUQ tham khảo).
- **Đề nghị:** gán [OWNER-GATE] hoặc khai LIMITATION tường minh + 1 dòng acceptance đối chiếu (ví dụ: cảnh báo mềm khi người duyệt ở Level không khớp vai ghi trên dòng danh mục).
### F-L1-13 [MED] — K8 dry-run chạy PROD với 2 tác dụng phụ tự khai mà 0 gate
- **Claim:** spec:32 tự liệt rủi ro K8: "HỐ-1 S160 (HĐ DaPhatHanh không xóa được qua API)" + "notification bắn user thật"; acceptance K8 (spec:43) đòi "mã phiếu thật" ⇒ chạy trên dữ liệu thật. Draft:54 để ngỏ "prod/UAT". Không có OG nào cho việc tạo bản ghi **không xoá được** và ping nhân viên thật.
- **Đề nghị:** thêm [OWNER-GATE] "dry-run chạy prod hay UAT; nếu prod thì chấp nhận N bản ghi ZZTEST tồn tại vĩnh viễn + ai bị notify" — hỏi TRƯỚC K8, không hỏi sau.
### F-L1-14 [LOW] — Acceptance K1 chỉ đo số dòng ⇒ bảng thiếu cột vẫn PASS
- **Claim:** draft:46 liệt cột bắt buộc: "mã nhóm A1…C3 + tên **VN/EN** + nhóm-duyệt 1-8 + người-ký + chuỗi nháy" (+ cột NĐUQ tham khảo theo QĐ5). Acceptance K1 (spec:36) chỉ đo N dòng + CRUD 200 + seed idempotent + `dotnet test`.
- **Cụ thể rơi:** "tên EN" không xuất hiện một lần nào trong spec.
- **Đề nghị:** liệt kê tập cột tối thiểu ngay trong dòng acceptance.
### F-L1-15 [MED] — Lời 2+3 (3 trục độc lập / "đưa vào loại hợp đồng gì") chỉ được ăn một nửa
- **Claim (a):** draft:27 cảnh báo *"Chữ 'loại HĐ' trong hội thoại mang 2 nghĩa — **đặt tên field tách bạch**"*. Spec không mang cảnh báo này sang, và chính spec dùng cả "đưa vào loại HĐ" (spec:12, nghĩa = dòng danh mục SP-002) lẫn "7 loại HĐ" (spec:7, nghĩa = ContractType) trong cùng một file ⇒ đúng cái bẫy owner cảnh báo.
- **Claim (b) nặng hơn:** **cơ chế CHỌN** loại HĐ/khuôn form lúc bắc cầu KHKK→HĐ không giao cho ai. K7 (spec:24, :42) chỉ nói bridge đọc `ApprovedAmount` + gộp theo OG-1; ai/ở đâu chọn `ContractType` (7 loại, quyết cả mã HĐ RG-001 lẫn workflow pin) thì trống. Lời 3 (draft:9) đòi đúng bước này.
- **Đề nghị:** 1 dòng acceptance K7: "màn tạo HĐ từ KHKK bắt chọn ContractType (mặc định suy từ …), mã HĐ sinh đúng RG-001 theo loại đã chọn".
## Điểm CHỊU ĐƯỢC soi (khai 2 chiều, chống Smart-Friend)
- **P1 — khai thiếu OG-4 là THẬT:** spec:30 viết "Không có OG-4 trong sub-file — số nhảy là của lane, giữ nguyên không bịa". Đo lại: `grep -n "OG-" sub-invest-fable-b1a.md` → hit ở `:36 (OG-1)`, `:39 (OG-2)`, `:40 (OG-3)`, `:43 (OG-5)`, `:53 (OG-2)`, `:57 (OG-1)`**0 hit OG-4**. Khai đúng, không bịa lấp chỗ trống.
- **P2 — tôi ĐÃ ĐỊNH flag sai và tự bác:** dự định flag "GĐ4 không có sidebar 7 loại để mirror" ⇒ đo đĩa thì `Hdc_*` 7 leaf có thật (`DbInitializer.cs:1868`, nhãn `:1981-1987`) ⇒ **BỎ finding**, mảnh (iii) đứng vững.
- **P3 — QĐ7 gọi đúng tên hàm có thật:** `isAdminHidden` (`fe-admin/src/components/Layout.tsx:182-184`, hiện chỉ chặn `Ct_`) + `filterForAdmin` (`:186-190`) + sidebar admin render 100% từ menu-tree động (`:315`) ⇒ cơ chế "1 file FE, 0 BE" khả thi. Lỗi của QĐ7 nằm ở **tập whitelist** (F-2), không ở cơ chế.
- **P4 — header spec khai rõ lane E/F chết + lead compile từ nhãn inline** (spec:3) ⇒ truy được nguồn từng quyết định; đây là lý do lens này soi được §② nhanh.
## Verdict
(ĐANG LÀM)
**FAIL (spec chưa đủ chuẩn để mở fan-out B4 nguyên trạng) — 15 finding: 5 HIGH · 9 MED · 1 LOW.**
Không phải bác toàn bộ: khung 8 wave / 3 cụm và 5/7 mảnh scope có wave phủ, luật khuôn được ràng bằng acceptance SHA. Nhưng 3 thứ phải sửa TRƯỚC khi worker cầm spec:
1. **F-2 + F-3** — mảnh (iv) đang thực thi ngược lời owner (giữ 2 designer cũ) và thiếu hẳn nửa "8 designer".
2. **F-4** — 4 owner-gate không nối vào wave nào ⇒ B4 sẽ chạy xuyên gate mà không ai chặn (đặc biệt OG-2 phải gật trước K3).
3. **F-1 + F-5** — K1 không có nguồn theo tên đã ghi; K2 đổi cardinality mà 0 bất-biến tiền tệ trong checklist.
Ba dòng acceptance đang **không phân biệt được đúng/sai** (mù): K6 (spec:41 — F-2), K3 (spec:38 — F-3 và F-10), K1 (spec:36 — F-14).
END — TOTAL: 15 finding (F-L1-1 → F-L1-15), 5 HIGH / 9 MED / 1 LOW.

View File

@ -2,6 +2,68 @@
## Findings
### V-a (claim: Line per-NCC + UNIQUE(PlanId,SupplierId) filtered + ContractId per-line KHÔNG unique) = ĐÚNG
- `Domain/ContractSigningPlans/ContractSigningPlanLine.cs:14-25``ContractSigningPlanId` + `SupplierId` + `ContractId?` (`:25`).
- `Infrastructure/Persistence/Configurations/ContractSigningPlanLineConfiguration.cs:22-24``HasIndex(PlanId, SupplierId).IsUnique().HasFilter("[IsDeleted] = 0")`.
- `:26` `HasIndex(x => x.ContractId)` KHÔNG `.IsUnique()` ⇒ N-line→1-HĐ (OG-1 gộp) hợp khuôn schema hiện tại. ĐÚNG 3/3 vế.
### V-b (claim: create validator CHỈ check ApplicableType==10, không check IsActive) = ĐÚNG
- `Application/ContractSigningPlans/ContractSigningPlanFeatures.cs:209-220``EnsureWorkflowTypeAsync` chỉ `Select(w => (int?)w.ApplicableType)` (`:214`) + nhánh NotFound (`:216-217`) + so type (`:218-219`). 0 đọc `IsActive`/`IsUserSelectable`.
- Call-site: `:350` (Create) + `:460` (Update pin) — cả 2 nhánh cùng guard.
### V-c (claim: picker FE lọc isUserSelectable) = ĐÚNG
- `fe-user/src/pages/khkk/KhkkCreatePage.tsx:56-66` — GET `/approval-workflows-v2?applicableType=10``types.find(...).history``.filter(w => w.isUserSelectable)`.
- Chuỗi bổ trợ (lens2 tự đo, sub-invest KHÔNG nêu): `ApprovalWorkflowV2AdminFeatures.cs:117-134` overview KHÔNG lọc `IsActive` ⇒ history trả CẢ bản inactive; POST tạo mới `:341-343` chỉ set `IsActive=false` cho bản cũ, **KHÔNG đụng `IsUserSelectable`** (`:352-353` bản mới = true) ⇒ 8 workflow cùng type-10 vẫn hiện đủ ở picker. Verdict "only-ONE-active VÔ HẠI" ĐỨNG VỮNG cho đường picker.
### V-e (claim: KHKK service KHÔNG port CeoApprovalThreshold ⇒ set = config-lie #78) = ĐÚNG
- Grep `CeoApprovalThreshold|AllowApproverFinalize` trên toàn `*ContractSigningPlan*` (src/Backend): hit DUY NHẤT = comment `ContractSigningPlanWorkflowService.cs:44` + Designer migration (schema `ApprovalWorkflow`). 0 hit đọc-giá-trị trong code KHKK ⇒ 0 consumer THẬT (không phải chỉ comment khai).
### V-f (claim: leaf key KHÔNG infix G1) = ĐÚNG
- `DbInitializer.cs:1780-1787``Khkk_G1` (group) + 6 leaf `Khkk_WfView/List/Create/Pending/Approved/Deleted` parent = `"Khkk_G1"`, key leaf KHÔNG mang G1.
### V-d (claim: GĐ3/4 đã có `c.type`; `PipelineStage` leaves PHẲNG, muốn nhóm thì `groups?` hoặc `content`) = ĐÚNG
- `fe-user/src/components/pipeline/PipelineStageFolders.tsx:58-72``PipelineStage = {n, leaves[], locked?, failed?, loading?, content?, count?, truncated?}` — KHÔNG có `groups`.
- `:74-75` `stageCount` = `content !== undefined ? (count ?? 0) : leaves.length` ⇒ đi đường `content` thì `count` PHẢI truyền tay (bẫy đếm S162).
- `usePipelineStages.ts:240` `navigate('/hard-copies?type=' + c.type)``c.type` có trên leaf GĐ4; GĐ3 `:220-229` dùng cùng object `c` ⇒ group-by type = thuần FE. ĐÚNG.
- **BONUS ĐO ĐƯỢC (mirror claim K5):** sha256 `usePipelineStages.ts` fe-user == fe-admin = `7144eeb7…ab148`; `PipelineStageFolders.tsx` = `d449cc8d…cdc2d` — 2/2 cặp IDENTICAL, tiền-đề "mirror SHA ×2 app" ĐÚNG tại HEAD.
### F-L2-1 [HIGH] "Phiếu per NHÓM" (quyết-định ②.2) VA vào rào 1-PE-1-phiếu đang sống — K2 không liệt site này
- `ContractSigningPlanFeatures.cs:342-347` — Create chặn: `AnyAsync(p => p.PurchaseEvaluationId == pe.Id && p.Phase != TuChoi)` ⇒ 409 "Phiếu Duyệt NCC này đã có kế hoạch ký kết."
- Cặp sinh đôi ở picker: `:1201-1202` `!db.ContractSigningPlans.Any(pl => pl.PurchaseEvaluationId == e.Id && pl.Phase != TuChoi)` ⇒ PE biến mất khỏi dropdown ngay sau phiếu ĐẦU.
- Hệ quả: 1 gói thầu có hạng mục thuộc >1 nhóm duyệt ⇒ phiếu nhóm thứ 2 KHÔNG tạo được. Spec K2 (§③) chỉ liệt Mig 71 + validator + FE lines editor — 2 vị-ngữ trên KHÔNG có trong phạm vi.
- DoD K2 hiện tại ("tạo phiếu nhóm X + N hạng mục; line khác nhóm → 409") PASS mà lỗ vẫn còn ⇒ acceptance mù đúng chỗ nguy hiểm.
- Falsifier: nếu owner chốt "1 PE chỉ thuộc ĐÚNG 1 nhóm" thì finding rớt xuống LOW — nhưng khi đó "phiếu per NHÓM" == "phiếu per PE", cần khai rõ.
- Đề nghị: thêm dòng K2 "đổi 2 vị-ngữ uniqueness sang (PeId, ApprovalGroup)" + DoD "tạo phiếu nhóm 2 CÙNG PE → 201".
### F-L2-2 [MED] "8 designer entry" (K6 DoD) không khả thi với hình-dạng route hiện tại
- Deep-link designer là PER-TYPE: fe-admin staticMap literal `Khkk_WfView: '/system/approval-workflows-v2/ContractSigningPlan'` (`fe-admin/src/components/Layout.tsx:35`), route `:code` = TYPE-code.
- `resolvePath` nhánh `AwV2_` chỉ nhận 3 code `DuyetNcc|DuyetNccPhuongAn|Contract` (`fe-admin/Layout.tsx:166-172`) ⇒ leaf `AwV2_*` code mới trả `null` = leaf RỚT IM LẶNG (gotcha #50, chính comment `:40` cảnh báo).
- ⇒ muốn 8 entry designer phải (a) mở regex/whitelist + (b) route nhận per-workflow (Code/Id), không có trong K3/K6 scope.
### F-L2-3 [MED] Leaf `WfView` (và `Create`) không có đường mang `group` — K4 DoD chỉ đo leaf list
- `WorkflowMatrixViewPage.tsx:52-71` lọc DUY NHẤT theo `?type=` (`typeInt` whitelist 1|2|3|10) rồi render `summary.history` (mọi workflow `isUserSelectable` của type) ⇒ 8 leaf `Khkk_G{n}_WfView` sẽ trỏ CÙNG `/khkk/workflow-matrix?type=10` và hiện CẢ 8 nhóm.
- K4 DoD (§③) chỉ đòi "bấm từng leaf list lọc đúng ApprovalGroup" ⇒ 8/48 leaf WfView + 8/48 leaf Create nằm NGOÀI phép đo.
### F-L2-4 [LOW-MED] Citation §D K5 gọi tên BE-artifact KHÔNG tồn tại
- Sub-invest §D K5: "BE: `KhkkListItemDto` +`approvalGroup`". Grep `src/Backend`: 0 hit `KhkkListItemDto`; record thật = `ContractSigningPlanListItemDto` (`ContractSigningPlanFeatures.cs:34`). `KhkkListItemDto` là type FE (`fe-user/src/types/khkk.ts:106`).
### F-L2-5 [LOW] Số leaf trong ②.6 mâu thuẫn với §C-6
- Spec ②.6: "nhóm 2-8 = key mới `Khkk_G{n}_*` (48 leaf…)" — đọc như 48 leaf MỚI; §C-6 sub-invest ghi "48 leaf TỔNG". Đúng số: 7 nhóm mới × 6 = 42 leaf mới + 7 group-key mới = 49 row menu mới; tổng leaf = 48.
- Ảnh hưởng thật: 2 danh sách key TRONG DbInitializer phải khớp từng dòng — `KhkkKeys()` (`DbInitializer.cs:2185-2188`, 7 key) và bộ grant (`:2418-2424`) — sai số ở spec = sai 2 chỗ enumerate.
### F-L2-6 [MED] K4 liệt 2 site BE nhưng có 3 — `KhkkKeys()` bị bỏ sót (đúng lớp S155 mà spec tự trích)
- Site 1 menu seed: `DbInitializer.cs:1780-1787` (`SeedMenuTreeAsync` :1737).
- Site 2 grant KHKK: `SeedKeHoachKyKetAccessAsync` (`:2415`, key-list `:2418-2424`).
- **Site 3 KHÔNG được spec nêu:** `KhkkKeys()` `:2185-2189` (7 key hardcode) → `:2216` `.Concat(KhkkKeys())` bên trong `SeedAllRolesReviewReadPermissionsAsync` (`:2144`). Chính comment `:2204-2205` cảnh báo lớp này: "phải có mặt ở CẢ HAI danh sách permission … quên 1 bên = quyền lệch".
- Quên site 3 ⇒ 42 leaf mới không có CanRead trên đường review-scope (fresh-DB / env khác) — lỗi IM LẶNG kiểu menu-vắng.
### Ghi chú kiểm-chứng phụ (không tính là finding)
- "0 workflow type-10 seed" (§B.7) ĐÚNG 2 nguồn độc lập: grep `ContractSigningPlan` trong `DbInitializer.cs` = 0 hit ngoài `Khkk_*` menu; danh sách hàm seeder (`:171,:223,:273,:325,:373,:421,:469`) có Contract/Proposal/Leave/Ot/Travel/Vehicle — KHÔNG có ContractSigningPlan.
- Khuôn được trích tồn tại thật: `SeedRealMasterDataAsync` `:2901` (K1) · `SeedSampleApprovalWorkflowsV2Async` `:171` (K3) · `isAdminHidden` chỉ `Ct_` `fe-admin/Layout.tsx:182-184` (K6).
- FE tree ĐÃ chịu được N-phiếu/1-PE: `usePipelineStages.ts:110-121` `Map<string, KhkkListItemDto[]>` (bucket mảng + sort desc) ⇒ F-L2-1 là rào BE thuần, FE không phải sửa.
## Verdict
(ĐANG LÀM)
**LENS2: 8/11 claim ĐÚNG · 1 SAI · 2 LỆCH-NHẸ.** 6 claim chịu-lực (a)-(f) = 6/6 ĐÚNG ⇒ nền kỹ-thuật của 8 verdict §C VỮNG. Rủi ro không nằm ở evidence sai mà ở **phần spec KHÔNG nói**: F-L2-1 (HIGH, rào 1-PE-1-phiếu chặn thẳng quyết-định "phiếu per NHÓM") + F-L2-6 (MED, thiếu 1 site permission) + F-L2-2/3 (MED, designer-entry & leaf WfView/Create ngoài phép đo K4/K6).
(XONG — 6 verdict verify + 6 finding, không còn mục treo)

View File

@ -1,9 +1,156 @@
# lens3-seq — thứ tự + khuôn + đo được
Lens 3/3 ensemble B1b. Target: `spec-4gd-khkk-tong-quat-31-07-2026.md` (+ `draft-yeu-cau-owner.md` = nguồn PIN).
3 câu: Q3 thứ tự/phụ thuộc wave · Q4 chế-thêm trái luật khuôn · Q5 acceptance đo được.
## Findings
(ĐANG LÀM)
### F-L3-1 [HIGH] — Whitelist "chừa System subtree" GIỮ LẠI đúng 2 mục owner ra lệnh ẨN
- Claim: spec §② quyết-định 7 = "fe-admin `filterForAdmin` đảo blacklist→whitelist (chừa `System` subtree + AwV2)". Nhưng 2 mục owner đòi ẩn — "Quy trình Duyệt NCC" và "Quy trình HĐ" — đều là CON của `System`, nên whitelist đó giữ chúng lại.
- Evidence:
- Yêu cầu ẩn: `draft-yeu-cau-owner.md:52` — "+ 2 mục quy trình cũ (Quy trình Duyệt NCC · Quy trình HĐ)" nằm trong danh sách ẨN; chừa = "Người dùng · Vai trò · Phân quyền · Menu eOffice" + "Quy trình duyệt (Mới)".
- `src/Backend/SolutionErp.Infrastructure/Persistence/DbInitializer.cs:1795``(MenuKeys.Workflows, "Quy trình HĐ", MenuKeys.System, 95, ...)`
- `DbInitializer.cs:1798``(MenuKeys.PeWorkflows, "Quy trình Duyệt NCC", MenuKeys.System, 95, ...)`
- `DbInitializer.cs:1801``(MenuKeys.ApprovalWorkflowsV2, "Quy trình duyệt (Mới)", MenuKeys.System, 96, ...)` ⇒ "+ AwV2" trong spec là THỪA, và chính chỗ thừa đó tố cáo tác-giả tưởng AwV2 nằm NGOÀI System.
- Sev: HIGH (sai chức-năng so với lời owner, phát hiện được ngay bằng đọc seeder).
- Đề nghị: spec phải phát biểu rule theo TẬP KEY tường minh (4 leaf System + AwV2 subtree), KHÔNG theo "subtree System"; kèm acceptance liệt-kê-key.
### F-L3-2 [HIGH] — Acceptance K6 MÙ với chính lỗi F-L3-1
- Claim: `spec:41` — "sidebar admin chỉ còn HỆ THỐNG + Quy trình duyệt (Mới)". Nếu implement theo quyết-định 7, 2 mục quy trình cũ vẫn hiện — nhưng chúng hiện BÊN TRONG nhóm "HỆ THỐNG", nên người chấm đọc câu acceptance vẫn thấy "chỉ còn HỆ THỐNG + …" ⇒ PASS.
- Evidence: `spec-4gd-khkk-tong-quat-31-07-2026.md:41` vs `DbInitializer.cs:1795/1798` (2 mục là con System).
- Sev: HIGH (acceptance không có răng đúng ở trục đang sai).
- Đề nghị: acceptance = liệt-kê ĐÚNG N mục lá còn thấy trong sidebar admin (tên + số), FAIL nếu dư/thiếu 1 mục.
### F-L3-3 [HIGH] — K4 "leaf lọc đúng ApprovalGroup" cần tham-số lọc mà KHÔNG wave nào nhận nuôi
- Claim: 48 leaf lọc theo nhóm cần `ApprovalGroup` trong list-query BE; query hiện chỉ có 4 tham số, và K2/K4 không khai việc thêm.
- Evidence:
- `src/Backend/SolutionErp.Application/ContractSigningPlans/ContractSigningPlanFeatures.cs:622-625``ListContractSigningPlansQuery(Phase, ProjectId, PurchaseEvaluationId, PendingMe)` — không có group.
- `spec:23` K4 = "sidebar 48 leaf + grant seeder"; `spec:22` K2 = "Mig 71 + validator + FE lines editor" ⇒ không ai khai list-filter.
- `spec:39` acceptance K4 đòi "leaf lọc đúng `ApprovalGroup`".
- Sev: HIGH (acceptance nằm ngoài scope wave đang chấm ⇒ hoặc trượt, hoặc lấn scope im lặng).
- Đề nghị: gán rõ chủ wave cho `ApprovalGroup` (cột + query-param + FE param), 1 dòng DoD.
### F-L3-4 [HIGH] — 8 nhóm chỉ tách được ở tầng HIỂN THỊ; API là MỘT khoá chung ⇒ "0 rác 403" không đo được cái cần đo
- Claim: 48 leaf fan-out nhưng mọi endpoint KHKK dùng chung bộ policy `KeHoachKyKet.{Read,Create,Update,Delete}`; không có authz theo nhóm. Acceptance "13/13 role 0 rác 403" chỉ bắt được lỗi grant-lệch (leaf hiện mà thiếu quyền root), MÙ hoàn toàn với rò-rỉ chéo-nhóm.
- Evidence:
- `src/Backend/SolutionErp.Api/Controllers/ContractSigningPlansController.cs:28` class-level `[Authorize(Policy = "KeHoachKyKet.Read")]`; :36/:53/:59/:71/:77 Read · :84 Create · :93 Update · :103 Delete — 1 bộ khoá cho TẤT CẢ.
- `src/Backend/SolutionErp.Domain/Identity/MenuKeys.cs:42` + `:180` — chỉ `KeHoachKyKet` vào `All` ⇒ 4 policy, không có policy per-nhóm.
- Rào duy nhất hiện có là IDOR-scope theo người, không theo nhóm: `ContractSigningPlanFeatures.cs:636-640` (comment "phiếu MÌNH soạn phiếu đã rời nháp thuộc quy trình mình có chân duyệt").
- Sev: HIGH (spec bán "8 nhóm" như đơn-vị phân-quyền; thực tế là nhãn hiển thị).
- Đề nghị: spec khai thẳng "phân-tách nhóm = HIỂN THỊ, không phải authz" (và nếu owner muốn authz thì đó là wave riêng); acceptance K4 đổi thành so-khớp TẬP: với mỗi role, {leaf render} vs {Permission row `KeHoachKyKet`} + {phiếu list trả về} — đo bằng truy vấn, không bằng click tay.
### F-L3-5 [HIGH] — `ApprovalGroup` cho phiếu CŨ không được định nghĩa ⇒ phiếu cũ có thể biến mất khỏi cả sidebar lẫn cây
- Claim: K2 acceptance chỉ lo Lines (`CatalogEntryId=NULL`), không nói gì về giá trị `ApprovalGroup` của phiếu đã tồn tại; mà K4 (leaf lọc theo nhóm) + K5 (cây group-by `approvalGroup`) đều lọc theo nó. Nhóm NULL/0 không có folder ⇒ vắng mặt IM LẶNG (đúng lớp "lọc = vanish" đã gặp).
- Evidence: `spec:37` (K2 acceptance — chỉ `CatalogEntryId=NULL`) · `spec:23` (K5 group-by `approvalGroup`) · `spec:39` (K4 leaf lọc `ApprovalGroup`) · entity hiện KHÔNG có cột nhóm: `src/Backend/SolutionErp.Domain/ContractSigningPlans/ContractSigningPlan.cs:22-45`.
- Sev: HIGH.
- Đề nghị: chốt backfill tường minh (mặc định nhóm 1 vì quyết-định 6 giữ `Khkk_G1` đổi label thành nhóm 1) + acceptance đếm: `COUNT(phiếu) trước = Σ COUNT(phiếu) hiện trong 8 folder`.
### F-L3-6 [MED] — Chủ sở-hữu cột `ApprovalGroup` bị chẻ đôi giữa quyết-định 3 và wave K2
- Claim: quyết-định 3 (workflow) gói luôn "plan +cột `ApprovalGroup int` + validator line-cùng-nhóm" ⇒ thuộc K3; nhưng K2 mô tả "phiếu mang hạng-mục+nhóm (Mig 71 …)" ⇒ thuộc K2. Cụm 2 lại ghi "chờ `approvalGroup` từ cụm 1" mà không nói chờ K2 hay K3.
- Evidence: `spec:14` (quyết-định 3) vs `spec:22` (K2) vs `spec:23` (mở đầu cụm 2).
- Sev: MED (2 wave cùng đụng 1 cột ⇒ hoặc trùng migration, hoặc rơi kẽ).
- Đề nghị: 1 dòng "cột X do wave Y đẻ", các wave khác chỉ đọc.
### F-L3-7 [MED] — Cổng owner (OG-1, OG-2) nằm NGOÀI đồ thị wave, không có chốt chặn trước wave phụ thuộc
- Claim: OG-1 (cardinality) quyết hình dạng UNIQUE của K2 và luật gộp của K7; OG-2 (roster vai→user) chặn chính việc seed của K3 ("cần anh gật trước khi seed"). Nhưng K1→K2→K3 được xếp như chuỗi chạy thẳng, không wave nào có DoD "gate đã chốt".
- Evidence: `spec:27-28` (OG-1, OG-2) · `spec:22` (cụm 1 chạy thẳng) · `spec:36-38` (checklist K1/K2/K3 không có dòng gate).
- Sev: MED-HIGH (fan-out B4 sẽ hoặc đứng, hoặc tự chế roster; OG-1 lật SAU K2 = phải sửa migration đã land).
- Đề nghị: mỗi wave phụ-thuộc-gate có dòng đầu "PRE: OG-n đã chốt (dẫn chứng: …)"; OG-1 phải chốt TRƯỚC K2, OG-2 TRƯỚC K3.
### F-L3-8 [MED] — "Cụm 2 chờ cụm 1" gộp 3 phụ-thuộc KHÁC nhau vào 1 mũi tên
- Claim: trong 6 leaf/nhóm của K4, phụ thuộc thực khác nhau: (a) MenuItem row + grant seeder + thứ tự sidebar = KHÔNG phụ thuộc gì (tầng Identity, chạy song song cụm 1 được); (b) 4 leaf danh-sách (Danh sách/Đang duyệt/Đã duyệt/Đã xóa) + Thao tác = chờ `ApprovalGroup` (K2 hoặc K3, xem F-L3-6) + chờ query-param (F-L3-3); (c) leaf "Luồng duyệt" (`WfView`) = chờ K3 (8 workflow tồn tại), KHÔNG chờ K2.
- Evidence: bộ 6 leaf khuôn ở `DbInitializer.cs:1780-1787` (KHKK) và factory PE `MenuKeys.cs:142-158` (`Group/List/Create/Pending/Approved/Deleted/WfView`) · `spec:23` chỉ ghi 1 mũi tên "chờ `approvalGroup` từ cụm 1".
- Sev: MED (vừa nối-thừa gây tuần-tự-hoá phí, vừa giấu 1 phụ-thuộc THẬT là K4→K3).
- Đề nghị: tách bảng phụ-thuộc theo LEAF-GROUP, ghi rõ K4a (không phụ thuộc) / K4b (K2) / K4c (K3).
### F-L3-9 [MED] — K6 "độc lập, kéo sớm được" đúng CÓ ĐIỀU KIỆN: 8 designer của K3 phải nằm dưới `ApprovalWorkflowsV2`
- Claim: sau khi đảo sang whitelist, mọi menu MỚI mặc định BỊ ẨN ở admin (fail-closed). K3 sinh "designer entry" cho 8 nhóm; nếu chúng không phải con của `ApprovalWorkflowsV2` thì K6-kéo-sớm làm 8 designer vô hình — mà không ai báo lỗi.
- Evidence: khuôn hiện tại có đúng 2 con dưới AwV2: `DbInitializer.cs:1802-1803` (`Duyệt NCC (Mới)` / `Duyệt NCC và Giải pháp (Mới)`) · `spec:23` K6 "(độc lập — kéo sớm được)" · `spec:22` K3 "designer entry".
- Sev: MED.
- Đề nghị: ghi ràng buộc "8 designer = con của `ApprovalWorkflowsV2`" vào DoD K3, và thêm dòng acceptance K6 "sau K3, đếm được 8 designer trong sidebar admin".
### F-L3-10 [MED] — Spec nén "8 designer" của owner thành "designer entry" (số ít, không số)
- Claim: draft đòi `"Quy trình duyệt (Mới)" += 8 designer nhóm-duyệt`; spec K3 chỉ còn "designer entry", checklist K3 (`spec:38`) không có dòng nào đếm designer.
- Evidence: `draft-yeu-cau-owner.md:52` vs `spec:22` + `spec:38`.
- Sev: MED (mất số ⇒ mất phép đo; đúng lớp "nhãn ✓ không có phép đo").
- Đề nghị: checklist K3 thêm "sidebar admin hiện đúng 8 designer nhóm; mở designer nhóm n load đúng workflow `KHKK-N{n}`".
### F-L3-11 [HIGH] — K7 hỏi "W5/W6 đã land chưa" bằng câu NHỊ-PHÂN, trong khi trạng thái thật là NỬA-LAND
- Claim: cầu KHKK→HĐ hiện có ĐỦ phần schema (cột + index) nhưng KHÔNG có một chỗ ghi nào. Ai "RE-ĐO" bằng cách soi schema sẽ đọc ra "đã land" và bỏ qua đúng phần còn thiếu.
- Evidence:
- `src/Backend/SolutionErp.Domain/ContractSigningPlans/ContractSigningPlanLine.cs:25``public Guid? ContractId { get; set; } // [C6 review-schema] HĐ sinh ra từ dòng này (W5)`.
- `src/Backend/SolutionErp.Infrastructure/Persistence/Configurations/ContractSigningPlanLineConfiguration.cs:26``b.HasIndex(x => x.ContractId);`
- Grep toàn `src/Backend` (trừ Migrations) tìm phép GÁN `ContractId` cho dòng kế-hoạch: **0 kết quả** — chỉ ra 1 comment (`ContractSigningPlanFeatures.cs:1185`) và 1 khai-báo index. Không có write-site.
- Sev: HIGH.
- Đề nghị: đổi câu RE-ĐO từ nhị-phân sang 3 nấc ĐO ĐƯỢC — (a) cột/index có chưa, (b) write-site có chưa (grep phép gán), (c) test/E2E chứng đường quay ngược — và nêu xử lý cho nấc giữa.
### F-L3-12 [HIGH] — Acceptance K6 "diff bundle fe-user = 0" đo bằng thứ gotcha #69 đã tuyên bố KHÔNG ổn định
- Claim: bundle hash không deterministic và CI rebuild FE vô-điều-kiện mỗi run ⇒ "diff bundle = 0" hoặc luôn FAIL, hoặc bị lách bằng cách không build lại.
- Evidence: `docs/gotchas.md:1207` (tiêu đề #69: "FE bundle hash KHÔNG deterministic + `deploy.yml` rebuild FE vô-điều-kiện mỗi run") · `:1211` cơ chế (Vite/rolldown emit content-hash không deterministic) · `:1213` guard chính thức: "Phát-hiện FE-ship THẬT = `git diff fe-admin/src fe-user/src <range>`, KHÔNG tin hash-delta".
- Sev: HIGH (phép đo sai bản chất, đã có luật ngược trong repo).
- Đề nghị: viết lại thành `git diff --stat <base>..<head> -- fe-user/src` = 0 file.
### F-L3-13 [HIGH] — "13/13 role 0 rác 403" PASS được bằng kết quả suy biến (không cấp quyền cho ai)
- Claim: nếu grant seeder không cấp leaf KHKK cho role nào thì 0 leaf render ⇒ 0 lệnh gọi ⇒ 0 rác 403 ⇒ acceptance PASS trong khi tính năng vô hình. Thiếu ma-trận KỲ VỌNG (role nào PHẢI thấy nhóm nào) thì phép đo chỉ có 1 chiều.
- Evidence: `spec:39` · anchor con số "13" có thật (`src/Backend/SolutionErp.Domain/Identity/AppRoles.cs` = 13 `public const string`) nhưng seeder quyền hiện chỉ phủ 7 vai (`DbInitializer.cs:2671-2675`: Drafter, DeptManager, Procurement, CostControl, ProjectManager, Director, AuthorizedSigner) ⇒ 6 vai còn lại không có kỳ-vọng nào được khai.
- Sev: HIGH.
- Đề nghị: acceptance 2 chiều — (i) mọi leaf render đều gọi được (0 rác 403); (ii) số leaf render của từng vai == số kỳ-vọng khai trước trong bảng 13 dòng.
### F-L3-14 [MED] — K1 "bảng danh mục N dòng == số extract file gốc (khai N)" là mệnh đề TỰ ĐÚNG
- Claim: N do chính bước extract sinh ra rồi lại lấy N so với N ⇒ 0 bit thông tin; không có đối-chứng độc lập.
- Evidence: `spec:36` · đối-chứng độc lập DUY NHẤT nằm ở draft và spec đã bỏ mất: `draft-yeu-cau-owner.md:42` — "Tổng danh mục ~85 dòng (đếm từ ảnh trang 5-9)".
- Sev: MED.
- Đề nghị: acceptance = "N khớp ~85 (đếm ảnh trang 5-9); lệch > 5 dòng phải giải trình từng dòng lệch".
### F-L3-15 [MED] — Mọi dòng "test PASS" không có SỐ ⇒ xoá test vẫn PASS
- Claim: `dotnet test` PASS (K1), "test validator ÂM PASS" (K2), "test bridge PASS" (K7) đều không nêu baseline hay số ca thêm.
- Evidence: `spec:36`, `:37`, `:42` · baseline có thật trong draft: `draft-yeu-cau-owner.md:67` — "Test: 590 PASS (45D+545I) baseline".
- Sev: MED.
- Đề nghị: mỗi wave ghi "≥ 590 + k, khai k và tên ca".
### F-L3-16 [MED] — K8 dry-run: 3 guard nêu ở phần rủi ro KHÔNG xuống tới dòng acceptance
- Claim: quy ước ZZTEST, HỐ-1 (HĐ `DaPhatHanh` không xoá được qua API) và "notification bắn user thật" đều nằm ở §② nhưng dòng checklist K8 chỉ đòi chạy hết trạm + screenshot.
- Evidence: `spec:24` (ZZTEST) + `spec:32` (HỐ-1, notification) vs `spec:43` (acceptance K8 — không có 3 thứ đó).
- Sev: MED (dry-run trên prod để lại rác không xoá được).
- Đề nghị: K8 thêm 3 dòng đo được — tiền-tố ZZTEST trên mọi bản ghi tạo mới; danh sách bản ghi không thể xoá + cách sống chung; tắt/chuyển hướng thông báo trước khi chạy.
### F-L3-17 [MED] — Dòng chung "mỗi wave: cicd PASS + bundle byte-verify (#77)" sinh nhiễu cho wave BE-only
- Claim: K1/K2/K3 gần như thuần BE, nhưng bundle vẫn rotate mỗi run (#69) ⇒ "byte-verify" luôn thấy đổi mà không có nghĩa; đồng thời dòng này ngụ ý mỗi wave đều deploy prod, trong khi không wave nào khai bước deploy.
- Evidence: `spec:44` · `docs/gotchas.md:1211` (rebuild vô-điều-kiện + hash rotate) · `:1301` (#77 là về cache-lag sau deploy).
- Sev: MED.
- Đề nghị: tách 2 loại wave — wave có ship FE mới cần #77; wave BE-only chỉ cần cicd PASS + smoke API.
### F-L3-18 [MED] — Guard "grep MỌI consumer Lines" không có dòng acceptance nào; bề mặt đếm được là 16
- Claim: đổi cardinality của `Lines` (1 dòng/NCC → N dòng/NCC) là lớp lỗi S87/S88, nhưng checklist K2 không đo việc rà consumer.
- Evidence: `spec:13` + `spec:32` (guard) vs `spec:37` (acceptance không có) · bề mặt: 10 chỗ chạm `.Lines` ở BE (`ContractSigningPlanFeatures.cs:389/412/417/500/524/568`, `ContractSigningPlanLineConfiguration.cs:29`, `ContractSigningPlanWorkflowService.cs:111/296/303`) + 6 file FE (`fe-{user,admin}/src/pages/khkk/KhkkCreatePage.tsx`, `KhkkDetailPage.tsx`, `types/khkk.ts`).
- Sev: MED.
- Đề nghị: acceptance "liệt 16 chỗ, mỗi chỗ ghi ĐÃ-XỬ-N hoặc KHÔNG-ĐỔI kèm lý do".
### F-L3-19 [LOW] — K5 "2 file pipeline SHA-identical" thiếu tên file (bar hiện đang ĐẠT)
- Evidence: `spec:40` · đo thực tế bây giờ: `fe-{admin,user}/src/hooks/usePipelineStages.ts` cùng `7144eeb7…`; `fe-{admin,user}/src/components/pipeline/PipelineTreePanel.tsx` cùng `d70ac315…` ⇒ đây là bar hợp lệ, chỉ cần nêu đích danh 2 đường dẫn.
- Sev: LOW. Đề nghị: chép 2 đường dẫn vào dòng acceptance.
### F-L3-20 [LOW] — Checklist không có dòng 3-file rule cho Mig 70/71
- Evidence: `spec:36`/`:37` (K1/K2 đẻ migration) không nhắc; luật nằm ở `draft-yeu-cau-owner.md:67` ("mig mới = 3-file rule").
- Sev: LOW. Đề nghị: thêm "3 file migration cùng commit" vào DoD K1 + K2.
## Đã nghi rồi BÁC BỎ (nêu kèm chứng — không để nghi ngờ trôi thành cáo buộc)
- **48 leaf KHÔNG phải chế thêm.** Khuôn Duyệt NCC đúng là 1 group + 6 leaf: `MenuKeys.cs:142-158` (`…Group/List/Create/Pending/Approved/Deleted/WfView`); KHKK hiện đã dựng đúng bộ đó: `DbInitializer.cs:1780-1787` với chú thích ":1781 — đủ 6 leaf 'y chang Duyệt NCC'". 8 × 6 = 48 là NHÂN KHUÔN.
- **Cột `ApprovalGroup` cũng KHÔNG phải khái-niệm mới** — nó là bản sao vai trò của `EvaluationType` bên PE (`MenuKeys.cs:55` — "Module Duyệt NCC … 2 EvaluationType" chính là thứ sinh ra fan-out `Pe_<typeCode>_*`). Hệ quả: đã mirror thì mirror trọn — `EvaluationType` không rỗng từ lúc tạo, nên `ApprovalGroup` cũng phải có backfill (xem F-L3-5).
- **Tầng sub-folder trong cây là owner ĐÒI, không phải tự chế:** `draft-yeu-cau-owner.md:14` — "Thêm các sub folder con ở đây".
- **Nghi "whitelist bỏ rơi Menu eOffice" — SAI:** `DbInitializer.cs:1794` cho thấy Menu eOffice là con của `System`, nằm trong vùng chừa.
- **Nghi "N dòng/NCC làm vỡ tra cứu NCC" — SAI:** `ContractSigningPlanFeatures.cs:524-526` đã có `.Distinct()` trước `ToDictionaryAsync`.
- **`ApprovedAmount` per-Line có thật** (`ContractSigningPlanLine.cs:23`) và spec trích `:25` cho `ContractId` cũng đúng ⇒ K7 không đọc field ma.
## Verdict
(ĐANG LÀM)
**FAIL — spec chưa nên đem đi fan-out ở dạng hiện tại.** 6 lỗi HIGH, 8 MED, 2 LOW, 6 điểm nghi đã bác bỏ.
Ba lỗi chặn đường (phải sửa trước B2):
1. `F-L3-1` + `F-L3-2` — luật ẩn admin giữ lại đúng 2 mục owner ra lệnh ẩn, và acceptance được viết theo cách không thể phát hiện chuyện đó.
2. `F-L3-3` + `F-L3-4` + `F-L3-5` — trục `ApprovalGroup`: thiếu chủ wave cho tham số lọc, thiếu backfill cho phiếu cũ, và "8 nhóm" chỉ tồn tại ở tầng hiển thị vì mọi endpoint dùng chung một bộ khoá.
3. `F-L3-11` — câu hỏi RE-ĐO nhị-phân sẽ trả lời "đã land" trên một trạng thái mới land nửa chừng.
Về khuôn (Q4): spec **không** vi phạm luật "giống nhau — chỉ khác form" ở chỗ dễ nghi nhất (48 leaf, cột nhóm, tầng sub-folder đều là nhân khuôn hoặc do owner đòi). Chỗ lệch khuôn thật sự là cơ chế ẩn admin: đảo blacklist thành whitelist đổi luôn kiểu hỏng mặc định của menu mới (mọi menu thêm sau sẽ tự ẩn ở admin), và đó là thay đổi hành vi vượt quá yêu cầu "ẩn các mục cũ".