Compare commits
4 Commits
e202393256
...
1c6d77a6cc
| Author | SHA1 | Date | |
|---|---|---|---|
| 1c6d77a6cc | |||
| d82c5a8175 | |||
| b6db92abc7 | |||
| dea76d605a |
100
.claude/WAL.md
100
.claude/WAL.md
@ -1,57 +1,59 @@
|
||||
# WAL — auto-generated, không sửa tay
|
||||
updated: 2026-07-31T21:52:00+07:00 | session: S165 (L9, window 3 — /pause) | branch: main
|
||||
updated: 2026-08-01T11:40:00+07:00 | session: S167 (L9, window 5 — /tiep) | branch: main
|
||||
|
||||
goal: CHUỖI SP-002 B4 — K1+K2+K3 SHIP (3/8 wave); K4a→K8 + checkbox-opt-out = lần /tiep sau (mandate PAUSE-3)
|
||||
goal: CHUỖI SP-002 B4 — K1→K5+K4c SHIP + cicd PHỦ HẾT (6/8 wave); K6 lane-DONE chờ gate+commit; K7 → K8-prep + closeout-nợ còn
|
||||
|
||||
chain:
|
||||
[x] K1 danh mục 86 dòng — gate 9f · commit 50e6d8c · cicd PASS 6/6 (sub-cicd-verify-k1.md; seed 86
|
||||
TAM-GIÁC 3-nguồn prod)
|
||||
[x] K2 phiếu +nhóm +hạng-mục Mig71 — gate 10f (F-1 guard đếm-so-khớp + 2 test bịt) · commit 0779f2d ·
|
||||
cicd PARTIAL 5/6 deploy-khỏe (mục-4 chân-lý-rỗng, đo lại khi K8 đẻ phiếu — sub-cicd-verify-k2.md)
|
||||
[x] K3 8-workflow +finalize +designer 0-mig — chẻ-nhỏ sau 2 engine-exit (bài: task-hẹp-Write-sớm);
|
||||
roster LIVE lead-verify (instance SQLEXPRESS — sub-implbe-k3.md §0); gate 8f/0-blocker (3/3 phán
|
||||
lead ĐÚNG) · commit 15349e8 PUSHED · suite 614/0 (45D+569I) · +8 key AwV2_KhkkN* (All=64, policy=256)
|
||||
[x] cicd-K3 PASS 6/6 @S166 — re-spawn vai đo §1-§5 (run#436 success · gate 614 tách-phần · mig GIỮ 71 ·
|
||||
seed 8-wf shape-từng-email + finalize 2+8=10 + recycle-proof 5364→1900 · bundle admin ROTATE/eoffice
|
||||
FROZEN-giải-thích-trọn) rồi chết lần 2 → §6 smoke lead đo on-behalf 401/404/200/200 + END-line
|
||||
[x] 🔴 ĐẦU VIỆC #1 F-1 SHIPPED @S166: commit 7a903cf PUSHED (5 file đích danh — G-6). Reviewer
|
||||
PASS-WITH-FLAGS 7 (3 MAJOR/4 MINOR, sub-reviewer-f1-checkbox.md): G-1 default→OPT-IN PE-live-S97
|
||||
ÁP (ratify treo chờ-anh) · G-5 bỏ-(CEO) ÁP · G-6 tách-commit ÁP · nợ nhịp-K4: G-2 sơ-đồ-CEO-đã-duyệt
|
||||
(badge nửa-rẻ + F-7 EndedByLevelFinalize-ra-DTO) + G-3 1-Fact-projection-cờ + G-4 mirror-drift-
|
||||
Designer-trộn-cờ {D} · G-7 STATUS-rows-55/220 → commit K4a. Suite 614/0 · SHA b37cd292
|
||||
[x] K4a+K4b SHIPPED @S166 — commit 88e7ecf PUSHED (14 file +876/−59, gộp-CÓ-KHAI vì Layout dính 2 wave;
|
||||
gate-K4a PWF-4 + gate-K4b PWF-4M/5m 0-chặn, cả 2 artifact + disposition từng dòng). 49-key bộ-sinh
|
||||
All=113/Policies=452 (T2 khoá) · nhãn N2 "phá dỡ" · ?group= end-to-end (list+create+deleted lead-fix
|
||||
3-chỗ BE) · staticMap G1 group=1 · test +7 (T7 revoker-#84 probe-vi-phân) suite 621/0 · STATUS
|
||||
canonical 5-row đồng bộ. cicd-K4ab **PASS 7/7** (run #438 6m23s · 621 · seed 49-menu+637-perm
|
||||
tam-giác · A1 8+48 · A5 restart×2-THẬT PID-7020→220→6636 đứng-im + revoker-control 29/377 ·
|
||||
bundle ROTATE ×2 marker-differential · smoke+bind-400; treo kế thừa: bảng 0-phiếu = chân-lý-rỗng
|
||||
lọc-nhóm, đo lại khi K8 đẻ phiếu — sub-cicd-verify-k4ab.md)
|
||||
[ ] K5→K4c→K6→K7→K8-prep theo build-order B3 + vá-số spec-cụm3 (chi tiết PAUSE-3 mục (2): K5 groups
|
||||
cây 8-sub-folder + forward-provision lines[].contractId ∪ pe.contractId · K4c WfView lọc Code
|
||||
[+nợ gate-K4b: MAJOR-A test /deleted+group · MAJOR-C WfView đọc group] · K6 blacklist 12-key +
|
||||
fe-user-0-diff · K7 hội-2-khóa + grant Contracts.Create [câu III default Drafter+Procurement+Admin]
|
||||
+ Line.ContractId-ghi-luôn · K8 form-14-người fail-closed + gói SOLUTION-PE-{n} + QĐ9 đo-2-nhánh
|
||||
+ ZZTEST + rollback-theo-ID)
|
||||
[ ] closeout-nợ: diary vai (C2 — checklist @sub-lead-gap khi nó trả) · tally #53 CHỐT (S166 tới giờ:
|
||||
cicd×2 + reviewer×2 + stale×2 + gap×1 + laneBE×1 = 8 lượt #53-return / 0 mất công, artifact cứu hết)
|
||||
· nhãn D MIND · STATUS row S163-S166 · spec-cụm1 vá-3 đính-chính PUT-Conflict · lỗi-tự-bắt 607/609
|
||||
· 🆕 H24-DEEP 11-FLAG-SÀN resolve (sub-lead-stale-deep-S166.md): F-1 STATUS-canonical-5-row +
|
||||
canonical-poison :465-472 (detector đòi sửa 97-đúng→96-sai!) · F-2 bundle :479 · F-7 slot(57)
|
||||
_INDEX ĐÃ-VỠ 101,23% · F-8 slot(59) ring2-BÁC-một-phần: số HANDOFF:19 stale THẬT (71.492→120.633B) nhưng "180.649B
|
||||
đã-đạt-dải" = stale đo SAI phạm-vi (nuốt 7 segment cũ) — đúng phạm-vi ~30-40K VẪN DƯỚI sàn ⇒
|
||||
slot (59) CÒN SỐNG, chỉ cập-nhật số; bài: lead chép claim vai-đo TRƯỚC vòng-kiểm = suýt đóng
|
||||
owner-decision ngược chiều (ring2 16Đ/1T cứu) · F-4 CLAUDE-mig-70/71
|
||||
· F-5/F-6/F-9/F-10/F-11 · deep-đã-trả: set last_audit light=deep=40 + ring2 sau cặp
|
||||
[x] K1+K2+K3 SHIP trọn vòng — gate 9f/10f/8f · commit 50e6d8c/0779f2d/15349e8 PUSHED · cicd 6/6 · 5/6-khỏe
|
||||
(mục-4 chân-lý-rỗng, đo lại khi K8 đẻ phiếu) · 6/6-@S166 — chi tiết _context PAUSE-3 + sub-*-k{1,2,3}.md
|
||||
[x] ĐẦU VIỆC #1 F-1 checkbox opt-out SHIPPED @S166 — 7a903cf PUSHED · reviewer PASS-WITH-FLAGS 7 (G-1
|
||||
default→OPT-IN ratify TREO CHỜ-ANH · nợ-nhịp-K4 G-2/3/4/7 đã vào K4a) — sub-reviewer-f1-checkbox.md
|
||||
[x] K4a+K4b SHIPPED @S166 — 88e7ecf PUSHED (gate PWF-4 + PWF-4M/5m, 0-chặn) · 49-key All=113/Policies=452
|
||||
· nhãn N2 "phá dỡ" · ?group= end-to-end · test +7 suite 621/0 · cicd PASS 7/7 run #438 (treo kế thừa:
|
||||
bảng 0-phiếu = chân-lý-rỗng lọc-nhóm, đo lại khi K8 đẻ phiếu) — sub-cicd-verify-k4ab.md
|
||||
[x] K5+K4c SHIPPED @S166 — ce35f90 (cây 8-ngăn, gate PASS-0-blocker) + e202393 (WfView lọc nhóm + test
|
||||
MAJOR-A RED→GREEN) PUSHED · suite 622/0 re-verify @S167 · cicd-K5K4C PASS 5/5 (run #439 6m16s · gate
|
||||
622 tách-phần 45D+577I · mig GIỮ 71 set-diff-2-chiều-rỗng · bundle rotate ×2 + marker differential 4/4
|
||||
K5-cả-2-app/K4c-chỉ-eoffice + control 2 tầng · smoke 4/4 + 2-control-âm; vai chết-return, ARTIFACT TRỌN
|
||||
— sub-cicd-verify-k5-k4c.md; bundle baseline MỚI admin C2FhHCNT · eoffice DFOCQ2ks). Nợ nhẹ mang theo:
|
||||
gate-K4c 3-MINOR (helper-dispose · link-mất-query · highlight) · K5 MINOR-3-"SẮP" {UX chờ anh} ·
|
||||
flaky-1/12 DeleteCSP_SoftDeletes (mốc: #439 + S167-local đều xanh) · UAT-NÓI-TRƯỚC: prod 0 phiếu ⇒
|
||||
8 ngăn "SẮP" rỗng mọi gói = ĐÚNG SPEC
|
||||
[!] K6 admin-thu-gọn: lane FE DONE trên đĩa @S166, CHƯA gate CHƯA commit — sub-implfe-k6.md (ADMIN_HIDDEN_KEYS
|
||||
12-key nguyên-văn spec + comment lineage-2-ĐẢO S29→S57→QĐ7 + display-only #82; C1 15-lá LIỆT TÊN 16/16
|
||||
assert + CONTROL-ÂM bỏ-KeHoachKyKet 15→63 · C2 fe-user 0-diff · C4 0-route/0-guard · build PASS 1961
|
||||
modules; Layout.tsx +52/−19 UNCOMMITTED; 3 FLAG MINOR: N=15 chốt-số-spec [spec quên +8 designer] ·
|
||||
prod-menu-row cần SELECT AwV2% đóng-tuyệt-đối · resolvePath nhánh-chết CỐ-Ý-không-dọn [đảo 2 lần rồi])
|
||||
→ gate-K6 ĐANG CHẠY @S167 (sub-reviewer-gate-k6.md ghi liên-tục — cửa chết thì ĐỌC FILE ĐÓ, đừng
|
||||
spawn lại từ đầu) → commit → push → cicd
|
||||
[ ] K7 hội-2-khóa + grant Contracts.Create [câu III default Drafter+Procurement+Admin] +
|
||||
Line.ContractId-ghi-luôn [nợ K5 FLAG-1 chỗ-cắm-1-dòng] (spec chi tiết _context PAUSE-3 mục (2))
|
||||
[ ] K8-prep form-14-người fail-closed + gói SOLUTION-PE-{n} + QĐ9 đo-2-nhánh + ZZTEST + rollback-theo-ID
|
||||
[ ] closeout-nợ: diary vai (C2) · tally #53 CHỐT derive-from-body (S166 nháp 8 lượt + cicd-K5K4C chết-return
|
||||
artifact-trọn = 9-cận-dưới / 0 mất công) · nhãn D MIND · STATUS row S163-S167 · spec-cụm1 vá-3 đính-chính
|
||||
PUT-Conflict · lỗi-tự-bắt 607/609 · H24-DEEP 11-FLAG-SÀN resolve (sub-lead-stale-deep-S166.md: F-1
|
||||
STATUS-canonical-5-row + canonical-poison :465-472 · F-2 bundle :479 · F-7 slot(57) _INDEX 101,23% ·
|
||||
F-8 slot(59) ring2-BÁC-một-phần số-mới-120.633B-vẫn-dưới-sàn · F-4 CLAUDE-mig-70/71 · F-5/6/9/10/11;
|
||||
deep-đã-trả: last_audit light=deep=40 + ring2 sau cặp) · orphan-4-folder (S159×2 = nợ-ngủ anh chốt
|
||||
"bỏ mạch" + S163-bookend-open + S166-h24-deep chưa-synthesis)
|
||||
|
||||
next: /tiep → đọc sub-cicd-verify-k3.md dòng-1 + re-verify 6 mục → checkbox opt-out (đầu việc #1)
|
||||
→ K4a theo build-order.
|
||||
next: gate-K6 reviewer (diff Layout.tsx vs spec-cụm2 K6 @sub-invest-fable-b2-cum2.md:67-100 + 3 FLAG lane)
|
||||
→ commit K6 → push → cicd phủ → K7.
|
||||
|
||||
verify:
|
||||
head -1 .claude/workflows/runs/2026-07-31-S164-4gd-khkk-fanout/sub-cicd-verify-k3.md
|
||||
git log --format='%h %s' -3 # 15349e8 K3 · 0779f2d K2 · 50e6d8c K1 (đều pushed)
|
||||
dotnet test SolutionErp.slnx --nologo # 614/0 = 45D + 569I
|
||||
head -1 .claude/workflows/runs/2026-07-31-S164-4gd-khkk-fanout/sub-cicd-verify-k5-k4c.md # CICD-K5K4C: PASS — 5/5
|
||||
head -1 .claude/workflows/runs/2026-07-31-S164-4gd-khkk-fanout/sub-implfe-k6.md # IMPLFE-K6: DONE
|
||||
head -1 .claude/workflows/runs/2026-07-31-S164-4gd-khkk-fanout/sub-reviewer-gate-k6.md # GATE-K6: <verdict — IN-PROGRESS nếu chết giữa>
|
||||
git status --porcelain fe-admin/src/components/Layout.tsx # M = K6 uncommitted; RỖNG SAU commit K6
|
||||
git log --format='%h %s' -2 # dea76d6 wal · e202393 K4c (pushed)
|
||||
dotnet test SolutionErp.slnx --nologo # 622/0 = 45D + 577I
|
||||
|
||||
notes: MANDATE PAUSE-3 (3 tin anh verbatim ở _context): xong-K3-thì-pause · K4a→K8 lần sau · sổ nhớ kỹ
|
||||
từng đầu việc đã review. Treo-không-chặn: (I)(II)(III) · OG-5 · OG-6-soát-4-ô-UI · 2-hở-UX-K1-FE ·
|
||||
F-5-PermGuard-khuôn-cũ · O-2-literal-2-nơi. _mind 93,3% ⇒ pause kế NÉN MIND-2 (khuôn nén = MIND-1).
|
||||
notes: MANDATE PAUSE-3 sống (sổ nhớ kỹ từng đầu việc đã review — rà OG từng dòng trước khi tuyên xong).
|
||||
S166 chết-vì-limit GIỮA /pause-4: draft MIND-4 7Đ/0T CHƯA chèn (ctx-curator chết) ⇒ /pause kế (ctx-verifier
|
||||
@S167 11 cờ): PAUSE-4 khai 2-WINDOW (④ _context mù trọn window-4) · NÉN MIND-2 SÂU ≤1,5KB (⑤ số-học thiếu
|
||||
≥700B dù nén khuôn cũ) + di-trú nhãn TỪNG Ý 22-ý-D + 4 con-trỏ vào TRƯỚC (PARK-2/PARK-5 cấp nhà ⑨ ·
|
||||
ESCALATE(i) disposition ⑩ · slot(57) _INDEX đo tươi 104,8% TRÀN 988B ⑥ · slot(58) ruột "65,7%" đã lật ⑦)
|
||||
· chuẩn-hoá nhãn `{UX chờ anh}`→`{treo-chờ-anh}` khi di-trú (⑧ ngoài bộ-4 = trượt-cấu-trúc) · MIND-4 ≤6.331B
|
||||
· diary ctx-verifier S167 = lead seed on-behalf (vai nhịn Write giữ rào R0 gate-K6). run.md ledger tụt hậu ③
|
||||
(row 4 "☐ lần /tiep sau" + 0 row F-1) = sửa cùng closeout. Treo-không-chặn: (I)(II)(III) · OG-5 ·
|
||||
OG-6-soát-4-ô-UI · 2-hở-UX-K1-FE · F-5-PermGuard-khuôn-cũ · O-2-literal-2-nơi · G-1-ratify-OPT-IN.
|
||||
|
||||
@ -21,11 +21,14 @@
|
||||
10. **Mirror ×2 app: cái KHÔNG đổi cũng là bằng chứng.** File không sửa mà hash BẰNG baseline = **đối chứng dương** cho phép đo hash. Lệch có chủ đích (fe-admin không có `WorkflowMatrixViewPage`) phải KHAI tại chỗ, không mirror mù.
|
||||
11. **Validate query-param bằng REGEX CHUỖI, không `Number()`.** `Number("03")=3` lọt lưới; `/^[1-8]$/` chặn. Và chốt chặn kép khi param chỉ có nghĩa với 1 ngữ cảnh (`type===10 && regex`).
|
||||
12. **Lọc client-side thì KHÔNG thêm trục vào `queryKey`** nếu payload đã tải trọn — thêm = N lần gọi mạng cho cùng một payload + N ô cache trùng. Ngược lại, trục làm ĐỔI request mà thiếu = cache dùng chung sai CÂM.
|
||||
13. **Muốn ĐẾM cái UI hiển thị thì DỰNG LẠI dữ liệu thật rồi chạy chính bộ lọc đã ship** — đừng đếm bằng mắt/suy luận. Và cho phép đo **tự kiểm**: cây menu dựng lại được đối chứng bằng invariant T6 (mọi key `MenuKeys.All` phải có menu-row) — dựng sai thì chỉ số đó vỡ trước.
|
||||
14. **Route-resolver có nhánh ≠ thứ đó tồn tại.** `resolvePath` nhận code `'Contract'` không có nghĩa menu-row `AwV2_Contract` tồn tại (0 hit repo). Cùng họ với "bịa field": đọc nhánh code rồi suy ra dữ liệu là suy ngược.
|
||||
|
||||
---
|
||||
|
||||
## 📌 Nhật ký (móc 1 dòng — verbatim → `archive/2026-H2.md`, cũ hơn → `archive/2026-H1.md` + `_INDEX.md`)
|
||||
|
||||
- **S166 (08-01) K6** admin thu gọn về vai NƠI-CONFIG (fe-admin ONLY 1 file, fe-user 0-diff). `filterForAdmin` lọc ĐỆ QUY ⇒ chỉ liệt 12 ROOT; **dựng lại cây seed 200 node rồi chạy bộ lọc đã ship ⇒ 166 lá → 15 lá liệt đủ tên**, tự-kiểm bằng invariant T6 (113 key `All` → 0 thiếu row). Comment vá lineage **2 lần đảo**. `[s166, k6-admin-thu-gon, route-resolver-khac-menu-row]`
|
||||
- **S166 (08-01) K4c** WfView lọc nhóm (chốt cụm-2, fe-user ONLY 1 file, fe-admin 0-diff). Lọc `wf.code === khkkGroupWorkflowCode(n)` client-side ⇒ **không đổi queryKey** (khai rõ); rỗng-vì-lọc tách câu riêng. 21/21 proof + 2 control-âm. `[s166, khkk-k4c-wfview-loc-nhom]`
|
||||
- **S166 (08-01) K5** cây toàn-trình GĐ2 mở **8 sub-folder nhóm** + forward-provision B3-A5. `groups` chia lại `leaves` phẳng ⇒ Σ theo cấu trúc; hiện đủ 8 kể cả rỗng + "(chưa phân nhóm)"; `lines[].contractId` KHÔNG tồn tại → union 1-nguồn + FLAG. 15/15 proof (CA4 tiêm lỗi). SHA 4/4 pair (2 đổi + **2 đứng yên = đối chứng dương**). `[s166, khkk-k5-8-sub-folder]`
|
||||
- **S166 (08-01) K4b** nối `?group=` + vá-3/5/6. Override 3-trạng-thái `chipPhase` + reset-trong-render theo `navKey` (KHÔNG useEffect); queryKey +`group`; staticMap G1 user 6/6 · admin 5/6. **Harness đo nhầm render bị vứt.** `[s166, khkk-k4b-group-wire, harness-do-nham-render-bi-vut]`
|
||||
|
||||
@ -5,6 +5,13 @@
|
||||
|
||||
---
|
||||
|
||||
## S166 (2026-08-01) — K6-FE admin thu gọn về vai NƠI-CONFIG `[s166, k6-admin-thu-gon, dung-lai-cay-seed-de-dem, route-resolver-khac-menu-row]`
|
||||
Artifact `runs/2026-07-31-S164-4gd-khkk-fanout/sub-implfe-k6.md`. fe-admin ONLY, **1 file** (+52/−19), fe-user 0-diff.
|
||||
**Giải ẩn-số spec để mở acceptance:** spec treo "N = 7 hay 8 tuỳ `AwV2_Contract` có tồn tại không". Đo: **0 HIT toàn repo** ⇒ 2 seed leaf + 8 designer. 🔴 **Bẫy suýt lẫn: `resolvePath:173` CÓ nhánh nhận code `'Contract'` — nhưng route-resolver có nhánh ≠ menu-row tồn tại.** Chốt **N = 1 Dashboard + 4 System-leaf + 2 AwV2 + 8 designer = 15** (spec tự quên cộng 8 designer vào công thức của chính nó).
|
||||
**Khuôn giữ lại — ĐẾM BẰNG CÁCH DỰNG LẠI CÂY THẬT rồi chạy bộ lọc đã ship:** parse 72 tuple literal từ `DbInitializer` + sinh 128 key từ vòng lặp (hằng đọc thẳng `MenuKeys.cs`) = 200 node → chạy `ADMIN_HIDDEN_KEYS`+`isAdminHidden`+`filterForAdmin` cắt từ file → **166 lá → 15 lá**, liệt đủ tên. **Đối chứng tính đúng của cây dựng lại: 113 key trong `MenuKeys.All` → 0 key thiếu menu-row (invariant T6)** — cây dựng sai thì chỉ số này vỡ ngay, tức phép đo tự kiểm chính nó. **CONTROL-ÂM:** bỏ `'KeHoachKyKet'` khỏi Set ⇒ 15→**63** (+48 = đúng 8 nhóm × 6 leaf).
|
||||
**Cơ chế đáng nhớ:** `filterForAdmin` lọc **ĐỆ QUY** ⇒ ẩn 1 ROOT là cả cây con chết theo ⇒ tập chỉ liệt 12 ROOT (+`Workflows`/`PeWorkflows` vì chúng là CON của `System`, không chết theo root nào khác). Liệt từng leaf = thừa + phải bảo trì mỗi lần BE thêm key. `System`/`ApprovalWorkflowsV2` là GROUP nên không tính vào số LÁ.
|
||||
**Vá-14 lineage 2 LẦN ĐẢO** (S29 ẩn → S57 bỏ-ẩn → QĐ7 S164 ẩn LẠI rộng hơn): comment cũ S57 đang nói NGƯỢC luật đang chạy ⇒ gỡ + viết lại kèm câu "cả hai đều từng đúng, mốc sau thắng mốc trước — đừng khôi phục S57 vì tưởng gặp bug". **Display-only #82** khai rõ: route+quyền còn nguyên, deep-link vẫn 200, thu gọn = bớt lối đi menu KHÔNG phải thu hồi quyền. Build fe-admin PASS 1961mod `index-B0OrpamE` 620ms.
|
||||
|
||||
## S166 (2026-08-01) — K4c-FE WfView lọc theo nhóm (chốt cụm-2) `[s166, khkk-k4c-wfview-loc-nhom, rong-vi-loc-khac-rong-that]`
|
||||
Artifact `runs/2026-07-31-S164-4gd-khkk-fanout/sub-implfe-k4c.md`. fe-user ONLY, **1 file** (+68/−5), `git diff -- fe-admin` = RỖNG.
|
||||
Đóng FLAG-3 lượt K4b (`?group=` trên workflow-matrix chưa có consumer ⇒ 8 leaf mở ra 8 trang hệt nhau).
|
||||
|
||||
@ -4,6 +4,8 @@
|
||||
|
||||
- **S166 (08-01) gate K5 cây GĐ2 mở 8 sub-folder nhóm — PASS 0 blocker (1 CLARIFY + 5 MINOR):** lõi hiếm-đúng: bất-biến Σ do **CẤU TRÚC** (1 mảng nguồn `khkkItems` → 1 phép phân hoạch if/else TOÀN PHẦN → `stage.leaves` vẫn là chỗ đếm duy nhất `stageCount():96`) nên không phụ thuộc ai nhớ cộng; route `?group=` nối đủ 3 chặng (FE `:79-80` → param `:124` → BE `Where(:757)`), **khác hẳn tham-số-trang-trí K4a**. **Bài thu:** ① 🔴 **phép-đo-rẻ-nhất nằm ở file RÁC của lane khác** — `menus.json` untracked ở gốc repo (sản phẩm curl của cicd-monitor) chính là payload `/api/menus/me` LIVE ⇒ đối chiếu nhãn cây suy-ra ⟂ nhãn sidebar thật **8/8 khớp từng ký tự + `displayLabel` đều null**, thứ mà đọc code không bao giờ chứng được. Cùng file đó là **rào commit** (`git add -A` sẽ nuốt 44KB rác) ⇒ 1 vật vừa là bằng-chứng vừa là rủi-ro, đừng chỉ báo 1 vế. ② **chân-lý-rỗng phải khai là rỗng**: curl prod `total=0` phiếu KHKK ⇒ phân bố `approvalGroup` = `{}`, KHÔNG dùng để kết luận gì; nhưng cùng con số ấy đẻ ra sự-thật dùng được cho UAT (ngày đầu mọi gói hiện **8 dòng "SẮP" rỗng** — đúng spec, dễ bị đọc thành hỏng). ③ **tương-đương-từng-vế > "cùng tập cùng thứ tự"**: vòng lặp GĐ3/4 bị viết lại ⇒ lập bảng 4 vế (tập id · thứ tự = `Set` giữ thứ-tự-chèn · khử trùng · dòng-phụ `find` = phiếu ĐẦU) mới dám phán 0-regression; thân stage4 **0 tham chiếu `p`** là lý do cấu-trúc khiến rewrite an toàn. ④ **tiền-lệ phải đo ở BASE, không đo ở cây đã sửa**: `git show 88e7ecf:<file> | grep -A6 '<summary'` cho thấy 6/6 summary cũ chỉ chứa `span`/`svg` ⇒ `<button>` lồng trong `<summary>` là **khuôn MỚI** (nested-interactive a11y), lane tưởng đang bám tiền lệ. ⑤ **"15/15 OK" chết theo phiên**: script `new Function` không commit + FE **0 hạ tầng test** (`package.json` chỉ `dev|build|lint|preview`) + chuông Σ là `import.meta.env.DEV` ⇒ prod bị strip ⇒ 0 lưới an toàn; đừng đọc số-test-ad-hoc thành đã-phủ. ⑥ 2 nghĩa của 1 dòng checklist ("GĐ3/4 by c.type") — lead và lane **cùng chọn 1 nghĩa không chứng minh nghĩa đó đúng ý owner** ⇒ ghim cách đọc vào commit. Tag `[s166, bang-chung-o-file-rac, chan-ly-rong, tuong-duong-tung-ve, tien-le-do-o-base]` **Evidence:** `runs/2026-07-31-S164-4gd-khkk-fanout/sub-reviewer-gate-k5.md`
|
||||
|
||||
- **S166 (08-01) gate K4c chốt cụm-2 (FE lọc nhóm WfView + 1 Fact test) — PASS 0 blocker (1 CLARIFY + 3 MINOR):** ① 🔴 **flake chỉ lộ ở LƯỢT ĐẦU, và chính tôi giết mất chứng cớ**: chạy `dotnet test -v q` SONG SONG với `tsc` ⇒ 1 test CŨ đỏ (`Failed 1/8`) rồi 10/10 xanh sau đó + suite 622/0 ⇒ `-v q` **nuốt thông điệp assert** ⇒ có tên test mà không có lý do. Bài: lượt chạy ĐẦU dùng verbosity thường, `-v q` để dành cho lượt lặp. Truy nhiễm phải đo 3 vế mới dám nói "không do diff": fixture `:memory:` per-test · codegen **0 static** · xUnit 2.9.3 same-class = tuần tự. Báo "8/8 ở 11/12 lượt", KHÔNG báo 8/8 tuyệt đối. ② **probe RED bằng đổi TEST-INPUT (không chạm prod) là hợp lệ CHỈ KHI tham số xuất hiện ĐÚNG 1 CHỖ trong handler** — grep xong mới được chấp nhận; ≥2 chỗ là lập luận sập. ③ `Total` là dấu-vân-tay lọc-SERVER **chỉ khi** `CountAsync` chạy trên `q` SAU `Where` và TRƯỚC `Skip/Take` — đọc `ProjectPagedAsync:790-792` mới dám ký, đừng tin comment. ④ **queryKey KHÔNG thêm trục là ĐÚNG** khi BE endpoint không nhận trục đó (đọc controller: chỉ `applicableType`+`isUserSelectable`) + giá trị lọc là **dẫn xuất trong render** (không `useState`) ⇒ miễn nhiễm bẫy "đổi leaf không remount". ⑤ Guard `/^[1-8]$/` chặn `Number("03")=3`, và **đồng nhất 3 trang** KHKK — kiểm cả 3, lệch 1 chỗ là 2 luật cho 1 tham số. ⑥ Lane khai bundle `1,643.96 kB` — tôi chạy lại `npm run build`, **trùng từng chữ số** ⇒ claim đo-lường có thật. Tag `[s166, chung-co-bi-nuot-boi-verbosity, probe-1-cho-moi-hop-le, total-la-van-tay]` **Evidence:** `runs/2026-07-31-S164-4gd-khkk-fanout/sub-reviewer-gate-k4c.md`
|
||||
|
||||
- **[→ archive/2026-07.md — verbatim ĐÃ VERIFY moved-not-cut 4/4 @S166-curate] S164/S165 gate K3 · S164 gate K1 · S162 cây-4-folder · S161-W2 KHKK-CRUD (digest):** tham-số-chết-vì-thiếu-tầng-UI (đo TẦNG-CUỐI ai GỬI) · sweep 2-cờ bất-đối-xứng · flake ⇒ "N/0 tươi" phải khai SỐ LƯỢT · giá-trị lớn nhất ở GATE QUY-TRÌNH không ở mã · **ghost-policy** (grep policy, đừng suy từ "key đã vào All") · default-lấn-ngữ-nghĩa · im-lặng-về-độ-chính-xác-của-con-số ×3/diff · **#82 ngược chiều** (FE OR nhiều key LỎNG HƠN policy khớp-CHÍNH-XÁC-1-key ⇒ tự chuốc 403) · hợp-đồng FE↔BE VỠ 6 điểm mà tsc+build+574-test XANH (2 lỗi CHE NHAU) ⇒ so ROUTE+FIELD trên ĐĨA.
|
||||
|
||||
- **[→ archive/2026-07.md — verbatim, verify moved 5/5 @S166-curate] S159-đợt5 · S161-W1 · S155-đợt2 · S145-4lane · S143 · S139 · S134b · S133 · S146/S147/S152-D2 (digest gộp):** chú-thích-mang-SỐ phải chạy phép đếm · cookie-cutter soi CẢ phần bản gốc đã CHẶN · `tsc`+`noUnusedLocals` > grep cho claim 'đã cắt' · comment 'bên kia lo' = ĐI ĐỌC BÊN KIA · grep bằng TOKEN-SONG-SINH · `hợp-đồng-đứt-2-bờ` · đổi-chiều-assert ≠ nới-assert · bất-biến chia-đôi 2-axis chỉ cross-cut sweep bắt · `bằng-chứng-tự-huỷ-sau-squash` · re-measure acceptance từ git HEAD + no-self-exempt · 'mọi nhánh set X' phải grep assignment-qua-BIẾN (#81) · `ExecuteDelete` ngoài transaction = mất dữ liệu · xanh-giả bắt bằng phép 2-thế-giới · 3 THƯỚC-HỎNG (md5 CRLF · `grep -c` citation-trap · `grep -iF` MSYS 0-hit im lặng).
|
||||
|
||||
5
.claude/sessions/session-9/_tiep-4.md
Normal file
5
.claude/sessions/session-9/_tiep-4.md
Normal file
@ -0,0 +1,5 @@
|
||||
ts: 2026-08-01T11:33:07+07:00
|
||||
head-sha: dea76d605a64a61a4baf9db91b03e0ac552816ed
|
||||
window-ordinal: 5
|
||||
jsonl-hint: D--Dropbox-CONG-VIEC-SOLUTION-SOLUTION-ERP/c7c954cc-b5a1-43c8-be9d-c333aeaf139a
|
||||
account-label: none
|
||||
@ -0,0 +1,130 @@
|
||||
CICD-K5K4C: PASS — 5/5
|
||||
|
||||
# cicd-monitor — verify deploy K5 + K4c (S166, 2026-08-01)
|
||||
|
||||
Push range `88e7ecf..e202393`. FE-ONLY, 0-mig cả 2 commit. Run **#439** (id 552) — **success**, 11:08:17 → 11:14:33 (**6m16s**).
|
||||
Artifact ghi TỪNG MỤC ngay khi có bằng chứng (luật #53: ghi-đĩa-TRONG-lúc-làm; return chỉ là tóm tắt).
|
||||
|
||||
---
|
||||
|
||||
## M0 — Mốc PRE (tự đo LIVE, KHÔNG tin số spec) — ĐO LÚC RUN CÒN `running`
|
||||
|
||||
Cửa sổ vàng: bắt được #439 ở trạng thái `running` lúc **11:09:15** ⇒ bundle LIVE lúc đó = PRE thật (không phải số chép lại).
|
||||
|
||||
| App | index-*.js | index-*.css | size (B) | Last-Modified | Content-Type |
|
||||
|---|---|---|---|---|---|
|
||||
| admin | `index-qOcoitOr.js` | `index-Cf3iCOOv.css` | 1.756.651 | 01 Aug 03:15:14 GMT (=10:15:14 +07) | application/javascript |
|
||||
| eoffice | `index-D9J3BbcA.js` | `index-DX1FRk5n.css` | 1.673.668 | 01 Aug 03:16:16 GMT (=10:16:16 +07) | application/javascript |
|
||||
|
||||
🔑 **PRE tự chứng là bundle #438 bằng LM, không chỉ bằng hash**: LM 10:15:14 / 10:16:16 nằm TRỌN trong cửa-sổ deploy #438 (10:10:19 → 10:16:42). Kết quả trùng baseline MEMORY — nhưng trùng đó là KẾT QUẢ ĐO, không phải giả-định đầu vào. (Bài "mốc PRE lão hoá sau MỖI deploy".)
|
||||
Content-Type `application/javascript` + ~1,7MB ⇒ không dính bẫy SPA-fallback (`text/html` ~900B).
|
||||
|
||||
## M0-bis — Chọn byte-marker CÓ RĂNG (verify 0-hit PRE TRƯỚC khi dùng)
|
||||
|
||||
**M-K5 (kỳ vọng CẢ 2 app)** — `Chưa có phiếu ở nhóm này`
|
||||
- PRE eoffice **0** · PRE admin **0**
|
||||
- control-DƯƠNG cùng file/cùng pipeline grep: `Kết cấu` = **1** ở CẢ 2 ⇒ 0-hit là ĐO THẬT, không phải grep chết / encoding hỏng.
|
||||
- src@`88e7ecf`: `git grep` exit=1 (0 hit) ⇒ marker **chưa tồn tại ở mốc PRE** — không dính bài "marker đã có sẵn từ deploy trước = 0 bit" (`Kết cấu` ở K4ab).
|
||||
- src@HEAD: có ở **cả** `fe-admin/src/components/pipeline/PipelineStageFolders.tsx:200` **và** `fe-user/.../PipelineStageFolders.tsx:200`, là JSX text (không phải comment).
|
||||
|
||||
**M-K4c (kỳ vọng CHỈ eoffice — differential)** — `Xem tất cả nhóm` · `quy trình ở nhóm khác` · `được Admin ghim cho nhóm`
|
||||
- cả 3: PRE eoffice **0**, PRE admin **0**; src@HEAD **chỉ** ở `fe-user/src/pages/pe/WorkflowMatrixViewPage.tsx` (dòng 134/161), 0 hit fe-admin.
|
||||
- ⇒ bất-đối-xứng CỐ Ý: POST phải eoffice ≥1 / admin **0**. Differential này tự chứng comment-strip + tự chứng ship đúng app.
|
||||
|
||||
❌ **LOẠI marker `xem tất cả`** (chữ thường): 3/3 hit trong repo đều nằm trong `//` comment (`Layout.tsx` ×2 + `WorkflowMatrixViewPage.tsx:55`) ⇒ Vite strip ⇒ **0 bit**. Soi ĐIỀU KIỆN trong source TRƯỚC khi phong làm marker (bài K3 `earlyFinalizeLocked`).
|
||||
|
||||
---
|
||||
|
||||
## Mục 1 — Run Gitea cho `e202393` ✅ PASS
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| head_sha | `e2023932568e731c5a6f8b7dd4ccd4ff4b3c6130` (khớp HEAD; `git log origin/main..HEAD` **rỗng**) |
|
||||
| run | **#439** / task id **552** / `status=success` |
|
||||
| thời gian | created 11:08:17 → updated 11:14:33 = **6m16s** (dải quen 5m3x–6m3x) |
|
||||
| tail log | `Deploy done. App pool started.` → `API /health/live -> 200` → `🏁 Job succeeded` |
|
||||
|
||||
**Range phủ `ce35f90`:** `88e7ecf..e202393` = **9 commit** — 2 code (`ce35f90` K5 10:44:32, `e202393` K4c 11:08:09) + **7** `wal:` commit (`ff4d4dc`,`13f7f82`,`284233f`,`80b8cf5`,`2023f41`,`d163f6d`,`fa999d8`).
|
||||
⚠️ Đề bài ghi "2 wal-commit kèm" — **đo được 7**. Không đổi kết luận (Gitea build cả *range*, K5 nằm trong range ⇒ được phủ), nhưng ghi lại vì đây lại là một mốc-đề-bài lệch với đĩa.
|
||||
`paths-ignore` (7-glob): `.claude/agent-memory/**` + `.claude/workflows/runs/**` bị bỏ qua, **nhưng** `fe-admin/**`, `fe-user/**`, `tests/**` KHÔNG ⇒ CI đúng ra phải chạy, và nó đã chạy.
|
||||
|
||||
## Mục 2 — Gate CI **622 = 45 D + 577 I** ✅ PASS (tách-phần, khớp từng vế)
|
||||
|
||||
Đo ở web-UI log `…/actions/runs/439/jobs/0/logs` (18.674 B):
|
||||
|
||||
| Project | dòng log | Failed | Passed | Skipped |
|
||||
|---|---|---|---|---|
|
||||
| `SolutionErp.Domain.Tests.dll` | L29 | **0** | **45** | 0 |
|
||||
| `SolutionErp.Infrastructure.Tests.dll` | L47 | **0** | **577** | 0 |
|
||||
| **Tổng** | | **0** | **622** | 0 |
|
||||
|
||||
- Khớp **dự đoán TRƯỚC khi đo**: 621 → 622, delta **+1** rơi TRỌN vào Infra (45 Domain đứng im) — đúng 1 `[Fact]` mới `ListDeletedContractSigningPlans_FiltersByApprovalGroup_ServerSide` trong `ContractSigningPlanCrudTests.cs` (file test DUY NHẤT trong range).
|
||||
- **control ÂM:** `grep -c 'Failed!'` = **0**; pattern lỗi build/test = **1 hit duy nhất** và là dòng vô hại của runner `Failed to setup Pty Unsupported` (L12, có ở mọi run) — không phải lỗi build/test.
|
||||
- **Flaky đã khai** `DeleteContractSigningPlan_SoftDeletes` (1/12 theo commit-msg K4c): run này **KHÔNG đỏ** (Failed:0; tên test 0-hit trong log vì log chỉ in dòng tổng-kết). Ghi mốc: #439 xanh sạch, chưa cần re-run.
|
||||
|
||||
## Mục 3 — Mig GIỮ 71 + `sys.tables` 97 ✅ PASS (2 nguồn)
|
||||
|
||||
- **Nguồn 1 — git:** `git diff 88e7ecf..e202393 -- '*Migrations*'` = **rỗng** ⇒ 0-mig ở phía code.
|
||||
- **Nguồn 2 — sqlcmd prod (đo SAU `status=success`, tức sau recycle):**
|
||||
`MIGTOP=20260731085624_AddKhkkApprovalGroupCatalogAndFinalizeRuntime` (= Mig 71, GIỮ) · `MIGCOUNT=71` · `SYSTABLES=97` (is_ms_shipped=0).
|
||||
- 🔑 **Mạnh hơn khớp-tổng — set-diff 2 chiều:** so DANH SÁCH `MigrationId` prod với danh sách file mig repo → **IN-PROD-NOT-IN-REPO = rỗng** và **IN-REPO-NOT-IN-PROD = rỗng**. Không drift chiều nào.
|
||||
|
||||
🔴 **Bắt được bug trong CHÍNH công thức đếm của tao (MEMORY).** Lần đếm đầu ra **70** ≠ prod 71 → suýt hô drift. Truy ra: recipe cũ `ls *.cs | grep -v Designer | grep -v Snapshot` **ăn nhầm** `20260717032812_AddPeApprovedBudgetSnapshot.cs` (Mig 67 có chữ "Snapshot" trong TÊN). Filter đúng phải neo đuôi: `grep -vE 'Designer\.cs$|ModelSnapshot\.cs$'` → **71**, khớp prod. Đúng lớp "vắng-mặt trông giống ổn": một mig biến mất im lặng khỏi phép đếm, và nếu prod cũng lệch 1 thì hai cái sai sẽ triệt tiêu nhau thành "xanh".
|
||||
|
||||
## Mục 4 — Bundle rotate ×2 app + byte-marker ✅ PASS
|
||||
|
||||
| App | PRE js | POST js | size PRE→POST | Δ | LM POST (=+07) | trong cửa-sổ deploy? |
|
||||
|---|---|---|---|---|---|---|
|
||||
| admin | `qOcoitOr` | **`C2FhHCNT`** | 1.756.651 → 1.758.685 | **+2.034** | 04:13:05 GMT = 11:13:05 | ✅ (11:08:17–11:14:33) |
|
||||
| eoffice | `D9J3BbcA` | **`DFOCQ2ks`** | 1.673.668 → 1.676.752 | **+3.084** | 04:14:05 GMT = 11:14:05 | ✅ |
|
||||
|
||||
CSS cũng xoay: admin `Cf3iCOOv`→`DqXm5Hpn` · eoffice `DX1FRk5n`→`DlN3yZ5L`.
|
||||
|
||||
**Byte-marker POST (điểm gắt nhất):**
|
||||
|
||||
| Marker | eoffice | admin | kỳ vọng | |
|
||||
|---|---|---|---|---|
|
||||
| `Chưa có phiếu ở nhóm này` (K5, ×2 app) | **1** | **1** | 1 / 1 | ✅ |
|
||||
| `Xem tất cả nhóm` (K4c) | **1** | **0** | 1 / 0 | ✅ |
|
||||
| `quy trình ở nhóm khác` (K4c) | **1** | **0** | 1 / 0 | ✅ |
|
||||
| `được Admin ghim cho nhóm` (K4c) | **1** | **0** | 1 / 0 | ✅ |
|
||||
| control-DƯƠNG `Kết cấu` | 1 | 1 | >0 cả 2 | ✅ grep còn sống |
|
||||
| control-ÂM chuỗi bịa `…nàyZZZ` | 0 | 0 | 0 cả 2 | ✅ grep không khớp-bừa |
|
||||
|
||||
🔑 **Differential khớp element-wise 4/4**: K5 vào CẢ HAI app, K4c CHỈ vào eoffice — đúng y hình dạng của diff (`fe-admin` chỉ bị K5 chạm; `WorkflowMatrixViewPage.tsx` chỉ có ở `fe-user`). Δ size cũng cùng chiều: eoffice +3.084 > admin +2.034 vì eoffice gánh K5+K4c còn admin chỉ K5 — một nhân-chứng ĐỘC LẬP với grep.
|
||||
|
||||
🔑 **Control âm tầng-2 (chứng ghi-đè thật, không phải thêm file):** GET hash CŨ sau deploy → `index-D9J3BbcA.js` = **200 `text/html` 876B**, `index-qOcoitOr.js` = **200 `text/html` 900B** ⇒ rơi về SPA-fallback ⇒ **bundle cũ đã biến mất khỏi đĩa**. (200 ở đây KHÔNG phải "còn sống" — discriminator là Content-Type, không phải status.)
|
||||
|
||||
## Mục 5 — Smoke 4 phép + spot route ✅ PASS (spot: đạt-bar nhưng 0-bit, xem caveat)
|
||||
|
||||
Bearer admin: `POST /api/auth/login` → `accessToken` len **468** ✅
|
||||
|
||||
| Endpoint | code |
|
||||
|---|---|
|
||||
| `GET /api/contracts` | **200** |
|
||||
| `GET /api/purchase-evaluations` | **200** |
|
||||
| `GET /api/menus` | **200** |
|
||||
| `GET /api/contract-signing-plans` | **200** |
|
||||
| control-ÂM `GET /api/khong-ton-tai-zzz` (có bearer) | **404** ✅ |
|
||||
| control-ÂM `GET /api/contracts` KHÔNG token | **401** ✅ |
|
||||
|
||||
**Spot `/khkk/workflow-matrix?type=10&group=3` (eoffice):** → **200** `text/html` **876B**.
|
||||
⚠️ **CAVEAT — con 200 này 0 bit.** Control-ÂM cùng dạng `/khkk/duong-dan-bia-zzz?type=10&group=3` cũng trả **200 `text/html` 876B**, **byte y hệt**. Đây là SPA-fallback rewrite `/*`→`index.html`: mọi đường dẫn đều 200. Bar đề bài ("chỉ cần app 200") ĐẠT, nhưng nó không phân biệt được route có thật hay không ⇒ không đọc thành "route chạy đúng".
|
||||
**Bằng chứng THẬT cho route** nằm ở 2 chỗ khác, và cả 2 đều dương:
|
||||
1. route đăng ký trong `fe-user/src/App.tsx:83` → `<Route path="/khkk/workflow-matrix" element={<WorkflowMatrixViewPage />} />`; leaf sidebar `Layout.tsx:86` `Khkk_WfView: '/khkk/workflow-matrix?type=10&group=1'`.
|
||||
2. logic lọc `?group=` của K4c đã có mặt **trong bundle eoffice đã ship** (3/3 marker = 1), và **không** có ở admin (0/3).
|
||||
|
||||
---
|
||||
|
||||
## Verdict
|
||||
|
||||
**PASS 5/5.** Run #439 `e202393` success 6m16s · gate **622 = 45 D + 577 I** (Failed 0, delta +1 khớp đúng 1 Fact mới) · Mig GIỮ **71** + `sys.tables` **97**, set-diff repo↔prod rỗng 2 chiều · bundle rotate ×2 app, LM in-window, marker differential 4/4 element-wise + 2 tầng control · smoke 4/4 + 2 control-âm.
|
||||
|
||||
**Treo / khuyến nghị (không chặn):**
|
||||
1. Bar "FE route 200" **không có răng** với SPA-fallback (POS ≡ NEG byte-identical). Lần sau muốn đo route: đo bằng byte-marker trong bundle, hoặc gọi API mà route đó dùng, đừng đo bằng status code của đường dẫn client.
|
||||
2. Recipe đếm mig trong MEMORY có **false-negative** (ăn `AddPeApprovedBudgetSnapshot`) — đã sửa sang filter neo-đuôi + ưu tiên set-diff thay khớp-tổng.
|
||||
3. Flaky `DeleteContractSigningPlan_SoftDeletes` (khai 1/12) chưa cắn ở #439; nếu đỏ ở run sau, đối chiếu mốc này trước khi coi là hồi-quy mới.
|
||||
|
||||
**Bundle baseline MỚI (post-#439):** admin `C2FhHCNT`/`DqXm5Hpn` · eoffice `DFOCQ2ks`/`DlN3yZ5L`. *(Snapshot-per-run — sẽ lão hoá ngay ở deploy kế.)*
|
||||
|
||||
END — CICD-K5K4C VERDICT=PASS 5/5
|
||||
@ -0,0 +1,67 @@
|
||||
IMPLFE-K6: DONE
|
||||
|
||||
# Lane FE K6 — admin thu gọn (S166, 2026-08-01)
|
||||
|
||||
SPEC PIN: spec-cụm2 K6 mục 1-4 + acceptance C1-C4 @`sub-invest-fable-b2-cum2.md:67-100`.
|
||||
Phạm vi: `fe-admin/src/components/Layout.tsx` **ONLY**.
|
||||
|
||||
## §0 Kế hoạch đo trước khi gõ
|
||||
- [ ] Đọc lại khối spec `:67-100` (12-key set viết sẵn — dùng NGUYÊN VĂN, không tự chế)
|
||||
- [ ] Đếm THẬT cây menu seed từ `DbInitializer.cs` (không đoán): `AwV2%` là 2 hay 3?
|
||||
- [ ] Xác định chính xác tập lá còn thấy sau lọc + LIỆT TÊN (C1)
|
||||
- [ ] Kiểm `filterForAdmin` lọc theo cây (cha ẩn ⇒ con biến mất theo?) — quyết định set có cần liệt con hay không
|
||||
|
||||
## §1 ĐO ĐĨA (ground truth) — TRƯỚC khi gõ
|
||||
|
||||
### 1.1 🔴 GIẢI xong ẩn-số spec để mở C1: `AwV2_Contract` **KHÔNG TỒN TẠI**
|
||||
Spec `:C1` treo: *"row `AwV2_Contract` có thể tồn tại DB ngoài seed… chốt N: 7 (nếu 2 AwV2) / 8 (nếu 3)"*.
|
||||
Đo: `grep -rn "AwV2_Contract" src/Backend fe-admin/src fe-user/src` → **0 HIT toàn repo**. `MenuKeys.cs:115-117` chỉ có root `ApprovalWorkflowsV2` + **2** leaf (`AwV2_DuyetNcc`, `AwV2_DuyetNccPhuongAn`); `:127-134` thêm 8 `AwV2_KhkkN1..8`. Seed rows: `DbInitializer.cs:1981-1983` (root + 2) + `:1988-1995` (8 designer).
|
||||
⚠️ Chú ý phân biệt: fe-admin `resolvePath:173` CÓ nhận code `'Contract'` → `/system/approval-workflows-v2/Contract`, nhưng đó là **bộ giải route chờ sẵn**, KHÔNG phải menu-row. Route-resolver có nhánh ≠ menu tồn tại. ⇒ **|AwV2 leaf| = 2 (seed) + 8 (designer)**.
|
||||
⇒ **N = 1 (Dashboard) + 4 (System-leaf) + 2 + 8 = 15.** (Dư-lượng thành thật: nếu ai đó INSERT tay `AwV2_Contract` thẳng vào DB prod thì +1; tôi không có đường đo prod trong rào lượt này — khai ở FLAG-2.)
|
||||
|
||||
### 1.2 Cơ chế lọc ⇒ chỉ cần liệt ROOT
|
||||
`filterForAdmin` lọc **ĐỆ QUY** mọi tầng (`.filter(...).map(n => ({...n, children: filterForAdmin(n.children)}))`) ⇒ ẩn 1 root là cả cây con chết theo. Tập 12 key chỉ liệt ROOT + 2 con của `System` (`Workflows`, `PeWorkflows` — parent = `System` nên KHÔNG chết theo root nào khác, phải liệt riêng). Liệt từng leaf vừa thừa vừa phải bảo trì lại mỗi lần BE thêm key.
|
||||
|
||||
### 1.3 `System` không phải lá
|
||||
`System` có 6 con (`Users/Roles/Permissions/MenuVisibility/Workflows/PeWorkflows`) + `ApprovalWorkflowsV2` ⇒ render `MenuGroup`, **không tính vào số LÁ**. Tương tự `ApprovalWorkflowsV2` là group của 10 leaf.
|
||||
|
||||
## §2 THỰC THI — 1 file `fe-admin/src/components/Layout.tsx` (+52 / −19)
|
||||
|
||||
1. **`ADMIN_HIDDEN_KEYS`** = Set 12 key **ĐÚNG NGUYÊN VĂN spec** (PurchaseEvaluations · KeHoachKyKet · Contracts · HopDongCung · Forms · Reports · Master · Hrm · Off · Personal · Workflows · PeWorkflows); `isAdminHidden = ADMIN_HIDDEN_KEYS.has(key) || key.startsWith('Ct_')`. Giữ luật prefix `Ct_` (thừa so với ẩn root `Contracts`, nhưng giữ để nếu mở lại root thì 28 leaf không ùa về theo — khai tại chỗ).
|
||||
2. **Vá-14 — viết lại TRỌN khối comment**: khai lineage **2 LẦN ĐẢO** (S29 ẩn → **S57 bỏ-ẩn** → **QĐ7 S164 ẩn LẠI rộng hơn**, "Admin thu về vai NƠI-CONFIG"), kèm câu chốt *"cả hai đều từng đúng, mốc sau thắng mốc trước — đừng khôi phục S57 vì tưởng gặp bug"*. Comment S57 cũ đã gỡ (nó đang nói ngược luật đang chạy).
|
||||
3. **Display-only #82** khai trong comment: route + quyền admin còn nguyên, deep-link `/purchase-evaluations` vẫn 200; **thu gọn = bớt lối đi trên menu, KHÔNG phải thu hồi quyền**; muốn chặn thật phải làm ở BE `[Authorize(Policy=…)]`.
|
||||
4. **Ghi chú móc nối K4a**: 42 leaf `Khkk_G{2..8}_*` nay bị cắt từ TẦNG CHA nên chuyện `resolvePath` fe-admin thiếu nhánh `Khkk_G*` hết là vấn đề (không còn "rơi im lặng" ở tầng leaf); kèm cảnh báo ngược: gỡ `KeHoachKyKet` khỏi Set thì PHẢI nới regex TRƯỚC + quyết `WfView` admin trỏ đâu.
|
||||
|
||||
## §3 CHỨNG
|
||||
|
||||
**C1 — đếm + LIỆT TÊN (dư/thiếu 1 = FAIL).** Dựng lại cây menu seed từ BE (72 tuple literal parse từ `DbInitializer` + 128 key sinh từ vòng lặp, hằng đọc thẳng `MenuKeys.cs`: `ContractTypeCodes`/`PurchaseEvaluationTypeCodes`/`KhkkGroupNumbers`/`KhkkLeafActions`) = **200 node**, rồi chạy **CHÍNH `ADMIN_HIDDEN_KEYS` + `isAdminHidden` + `filterForAdmin` đã ship** (cắt từ file bằng `indexOf`, chỉ bỏ annotation TS, có assert "còn annotation thì tự chết").
|
||||
> **Đối chứng tính đúng của cây dựng lại:** 113 key trong `MenuKeys.All` → **0 key thiếu menu-row** (invariant T6). Cây dựng sai thì chỉ số này vỡ ngay.
|
||||
|
||||
**166 lá TRƯỚC lọc → 15 lá SAU lọc**, đúng tên từng mục:
|
||||
```
|
||||
1 Dashboard 6 AwV2_DuyetNcc 9 AwV2_KhkkN2 12 AwV2_KhkkN5 15 AwV2_KhkkN8
|
||||
2 Users 7 AwV2_DuyetNccPhuongAn 10 AwV2_KhkkN3 13 AwV2_KhkkN6
|
||||
3 Roles 8 AwV2_KhkkN1 11 AwV2_KhkkN4 14 AwV2_KhkkN7
|
||||
4 Permissions
|
||||
5 MenuVisibility
|
||||
```
|
||||
16/16 assert OK: số = 15 · tập tên khớp CHÍNH XÁC (thừa=[] thiếu=[]) · AwV2 seed-leaf = 2 · 8 designer có mặt · 11 mục kiểm-ẩn đều biến mất (`Khkk_List`, `Khkk_G5_List`, `Pe_DuyetNcc_List`, `Ct_ThauPhu_List`, `Hdc_ThauPhu`, `Suppliers`, `CatalogUnits`, `Wf_ThauPhu`, `PeWf_DuyetNcc`, `Off_DanhBa`, `Hrm_HoSo`).
|
||||
**CONTROL-ÂM:** bỏ `'KeHoachKyKet'` khỏi Set ⇒ **15 → 63 lá (+48)** — đúng 8 nhóm × 6 leaf, chứng phép đo phân biệt được chứ không phải luôn ra 15.
|
||||
|
||||
**C2 — fe-user KHÔNG đổi:** `git diff --stat -- fe-user/src` = **RỖNG (0 file)** ✅ (đo diff, không đo bundle-hash — đúng #69/l3-F12).
|
||||
**C3** — mục "đếm 8 designer trong sidebar" thuộc acceptance K3, không đo ở đây; tuy vậy 8 `AwV2_KhkkN*` có mặt trong danh sách C1 ở trên nên coi như phụ-chứng.
|
||||
**C4 — display-only:** 0 dòng route, 0 dòng `usePermission`/`PermissionGuard` trong diff (diff chỉ chạm `ADMIN_HIDDEN_KEYS`/`isAdminHidden` + comment) ⇒ deep-link admin tới route bị ẩn menu vẫn render và API vẫn 200; rào THẬT ở BE authz.
|
||||
|
||||
**Build:** `npx tsc -b` exit 0; `npm run build` fe-admin PASS **1961 modules**, `dist/assets/index-B0OrpamE.js` 1,726.07 kB (gzip 429.28) · `index-AgG72YRZ.css` 86.93 kB · 620ms. Warning >500KB + `INEFFECTIVE_DYNAMIC_IMPORT` = PRE-EXISTING.
|
||||
**Rào:** `git diff --stat` = **đúng 1 file** `fe-admin/src/components/Layout.tsx` (52+/19−). 0 fe-user, 0 BE, **0 commit**.
|
||||
|
||||
## §4 FLAG cho lead
|
||||
|
||||
**FLAG-1 (MINOR — chốt số cho spec):** ẩn-số C1 đã giải: **N = 15** (không phải 7/8 như spec dự phòng — spec quên cộng 8 designer K3 vào công thức của chính nó, chỉ nhắc ở vế "SAU K3 = N+8"). Con số ăn khớp: 1 + 4 + 2 + 8 = 15.
|
||||
|
||||
**FLAG-2 (MINOR — dư-lượng thành thật):** tôi đo từ **SEED**, đúng cho DB dựng từ seeder. Nếu prod có menu-row được INSERT tay ngoài seeder (spec nghi `AwV2_Contract`) thì N thật = 15 + số row đó. Đo dứt điểm cần 1 câu `SELECT [Key] FROM MenuItems WHERE [Key] LIKE 'AwV2%'` trên prod — ngoài rào lượt này (không đụng BE/DB), nhờ lane có quyền chạy giúp nếu muốn đóng tuyệt đối.
|
||||
|
||||
**FLAG-3 (MINOR — quan sát, không tự sửa):** sau K6, `resolvePath` fe-admin còn **một loạt nhánh chết** (staticMap `Khkk_*`, `Hdc_*`, `Suppliers`, `Off_*`, `Hrm_*`… + regex `Pe_*`) — key không bao giờ tới nữa vì đã cắt ở tầng cha. Vô hại (chỉ là mã không chạy tới), và **cố ý không dọn** lượt này: dọn = xoá đúng thứ phải khôi phục nếu QĐ7 lại đảo lần 3, mà lịch sử file này đã đảo 2 lần. Nếu lead muốn dọn thì nói, tôi tách lượt riêng.
|
||||
|
||||
IMPLFE-K6-END
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
# GATE-K4C — IN-PROGRESS
|
||||
GATE-K4C: PASS — 0 blocker · 1 CLARIFY (flake 1/11 trên test CŨ, không tái lập) · 3 MINOR
|
||||
|
||||
# GATE-K4C — DONE
|
||||
|
||||
> Reviewer adversarial gate, wave K4c (chốt cụm-2), S166. Ghi TỪNG MỤC ngay khi đo xong (chống #53: file luôn hợp-lệ giữa chừng).
|
||||
> Verdict CHỈ hợp-lệ khi có dòng `END` cuối file. Chưa có END = lượt chạy chưa xong, ĐỪNG đọc verdict.
|
||||
@ -49,4 +51,51 @@ Cái giá (đã cân, chấp nhận): tải trọn bộ type-10 rồi mới cắ
|
||||
| tsc fe-admin | `npx tsc -b --force` | **exit 0** ✅ |
|
||||
| fe-admin diff | `git status --porcelain -- fe-admin` | **rỗng** ✅ |
|
||||
| Cây phình | `git status --porcelain -- src tests fe-user fe-admin \| grep '^??'` | **0 hit** ✅ (control dương: cùng lệnh bỏ path-filter ra 1 hit = artifact này ⇒ phép đo KHÔNG rỗng) |
|
||||
| Bundle prod fe-user | `npm run build` (reviewer tự chạy) | exit 0 — `index-DBtbmn5s.js` **1,643.96 kB** / css **92.71 kB**, **trùng từng chữ số** với con số lane khai ⇒ claim đo-lường của lane KHÔNG phải số bịa |
|
||||
| Suite BE | `dotnet test SolutionErp.slnx` (reviewer tự chạy) | **622 PASS / 0 FAIL** (45 Domain + 577 Infra) — khớp con số lead báo |
|
||||
|
||||
### 1.4 SOI-3 — test MAJOR-A có RĂNG THẬT không?
|
||||
|
||||
**Phán: CÓ RĂNG. Probe "đổi test-input sang null" là tương-đương HỢP LỆ ở ca này — nhưng vì lý do đo được, không phải vì nghe hợp lý.**
|
||||
|
||||
- Kiểm tương-đương trên MÃ THẬT (không tin lập luận suông): trong `ListDeletedContractSigningPlansQueryHandler`, `request.ApprovalGroup` **chỉ xuất hiện đúng 1 chỗ** — `:896-897` `if (request.ApprovalGroup is not null) q = q.Where(p => p.ApprovalGroup == request.ApprovalGroup)`. Xoá đúng khối đó thì `Handle(g)` **đồng nhất** `Handle(null)` với MỌI `g`. Vậy truyền `null` là mô phỏng CHÍNH XÁC thế-giới-bỏ-param, không phải xấp xỉ. (Nếu tham số được dùng ở ≥2 chỗ thì lập luận này sập — nên phải grep trước khi chấp nhận, và tôi đã grep.)
|
||||
- Cặp (null ⇒ 2) ∧ (group=1 ⇒ 1) nằm **cùng một Fact, cùng một fixture** ⇒ 2 thế giới đặt cạnh nhau trong 1 lượt chạy; không cần sửa prod-code để chứng.
|
||||
- `Total` **không phải assert trang trí**: `ProjectPagedAsync:790` `var total = await q.CountAsync(ct)` chạy trên **chính `q` đã `Where`**, trước `Skip/Take` (`:791-792`). Nên "lọc trong bộ nhớ sau khi lấy trang" sẽ cho `Items` giống hệt mà `Total` sai — assert `Total` phân biệt được đúng thứ nó khai là phân biệt.
|
||||
- Đã giết các mutant hiển nhiên: "luôn trả phiếu đầu" (vế group=3 ⇒ đúng `planG3`) · "không khớp thì bỏ lọc" (vế group=7 ⇒ rỗng + `Total`=0) · "đẩy vế lọc vào trong `if (!admin)`" (vế 5 Admin ⇒ vẫn lọc). Vế IDOR ở `:884-893` đúng là NGOÀI vế lọc như comment khai ✅.
|
||||
- 2 sanity trước phép đo (2 row thật `IsDeleted` ∧ thật khác nhóm) là thứ khiến "lọc ra 1 phiếu" không thể đúng-vì-lý-do-khác. Đúng bài chống chân-lý-rỗng.
|
||||
- **Giới hạn còn lại (khai thật, không phải blocker):** probe này KHÔNG bao mutant "lọc đúng hình dạng nhưng sai CỘT" — 2 phiếu trong fixture khác nhau ở nhiều trường (Id, `MaKeHoach`, thời điểm), nên một vế `Where` bám trường tương quan khác cũng qua được. Mutant kiểu đó phi thực tế ở đây; ghi lại để lượt sau đừng đọc "RED→GREEN" thành "mutation-tested toàn phần".
|
||||
- Trích dẫn số dòng trong test (`:896-897`, `:884-893`, `:401-404`, `:420-424`, `:756-757`) — **grep lại từng cái, ĐÚNG 5/5**, không trượt.
|
||||
|
||||
### 1.5 🔴 FLAKE quan sát được — 1 FAIL / 11 lượt chạy lớp test này
|
||||
|
||||
- Lượt chạy **đầu tiên** của tôi (`dotnet test --filter ContractSigningPlanCrudTests -v q`, chạy **đồng thời** với `npx tsc -b`, 13 s): `Failed: 1, Passed: 7` — tên test đỏ = **`DeleteContractSigningPlan_SoftDeletes_KeepsChangelog_ThenCreateAgainSucceeds`** (test CŨ, không phải Fact mới). **Thông điệp assert KHÔNG bắt được** vì `-v q` nuốt mất — khai thẳng chỗ hổng của phép đo, không đoán bù.
|
||||
- Không tái lập: cùng lớp chạy lại **10 lượt** (3 lượt rảnh + 6 lượt dưới tải `tsc -b --force` fe-admin + 1 lượt chạy riêng chính test đó) ⇒ **10/10 XANH**; `dotnet test SolutionErp.slnx` **622/0**.
|
||||
- Truy nguyên cơ chế lây nhiễm (để biết CÓ THỂ do diff không): fixture `KhkkFixture` mở `SqliteConnection("DataSource=:memory:")` **riêng cho từng test** ⇒ không có DB dùng chung; `ContractSigningPlanCodeGenerator` **không có static** nào (đọc trọn file), sequence nằm trong bảng của chính DB đó; xUnit **2.9.3** ⇒ test trong CÙNG một lớp chạy TUẦN TỰ. Thay đổi của diff lên test cũ = helper `CreateAsync` thêm tham số **mặc định `null`**, mà handler quy `null → DefaultApprovalGroup = 1` (`:207`, `:399`) — **đúng bằng hành vi trước diff**. ⇒ **không tìm được đường nào diff gây ra**, nhưng cũng KHÔNG có bằng chứng lớp này vốn đã flake từ trước.
|
||||
- Xử trí đề nghị: **không chặn commit** (mọi phép đo đầy đủ đều xanh, 622/0 hai nguồn độc lập), nhưng **ghi vào WAL/handoff làm mốc**: nếu CI đỏ đúng test này lần nào nữa thì đã có mốc "S166 gate-K4c: 1/11" để không phải điều tra lại từ đầu. Đừng viết vào STATUS là "8/8 tuyệt đối" — con số trung thực là **8/8 ở 11/12 lượt quan sát**.
|
||||
|
||||
## 2. Kết luận
|
||||
|
||||
**VERDICT: PASS — 0 blocker.** 1 CLARIFY (flake trên) + 3 MINOR dưới.
|
||||
|
||||
| Hạng mục | Trạng thái | Ghi chú |
|
||||
|---|---|---|
|
||||
| 1. Wire/feature claim | PASS | Không có `// Mock` / `alert(` / TODO-wire trong diff; lọc bám `code` khớp seeder BE + rào BE cùng khuôn |
|
||||
| 2. Schema | N-A | 0 migration, 0 entity, 0 file `Migrations/` trong diff |
|
||||
| 3. Security | PASS | 0 đổi authz. Trang chỉ **thu hẹp** danh sách hiển thị; endpoint `/deleted` lọc nhóm **SAU** khối IDOR (`:884-893`) ⇒ lọc chỉ hẹp đi, không nới quyền. Không có key policy mới ⇒ gotcha #85 không đụng |
|
||||
| 4. Code quality | PASS | tsc fe-user 0 · tsc fe-admin 0 · `npm run build` fe-user 0 · `dotnet test` 622/0 · 0 file ngoài phạm vi · 0 `--no-verify` |
|
||||
| 5. Test coverage | PASS | +1 Fact có răng đúng chuẩn "bug → test-before-fix" (RED đo được trước, GREEN sau); suite 569→577 Infra |
|
||||
| 6. Writing quality | N-A | Diff không có nội dung hướng ra ngoài (comment nội bộ + chuỗi UI tiếng Việt; đã đọc từng chuỗi mới: đủ dấu, câu hoàn chỉnh) |
|
||||
|
||||
**MINOR-1** — `ListDeletedAsync` (`ContractSigningPlanCrudTests.cs:216-224`) **không dispose** `TestApplicationDbContext` (`var db = f.NewDb(actor);` rồi trả `Task` luôn), trong khi 3 helper anh em đều `await using var db`. Không sai kết quả (connection do fixture giữ, `IDisposable` của fixture đóng), nhưng lệch khuôn của chính file. Chuẩn nhận: đổi thành `async` + `await using`, hoặc ghi 1 dòng lý do cố ý.
|
||||
|
||||
**MINOR-2** — link "Xem tất cả nhóm" dựng bằng `` `${pathname}?type=${typeInt}` `` ⇒ **rơi mọi query khác** ngoài `type`. Hôm nay vô hại (trang chỉ đọc `type` + `group`), nhưng thêm tham số thứ 3 sau này sẽ mất im lặng. Chuẩn nhận: dựng từ `new URLSearchParams(searchParams)` rồi `delete('group')`.
|
||||
|
||||
**MINOR-3** — bấm "Xem tất cả nhóm" xong URL còn `?type=10` ⇒ **không leaf nào trong sidebar khớp** (vì `group` là danh-tính điều hướng, `Layout.tsx:201-221`) ⇒ mất highlight menu. Hành vi chấp nhận được cho một nút "thoát lọc", nêu để không ai đọc thành lỗi.
|
||||
|
||||
**Không có Critical. Không có Major.**
|
||||
|
||||
Xác nhận 2 FLAG của lane là ĐÚNG, không phải cách nói giảm: (a) fe-admin không có `WorkflowMatrixViewPage` — `git status -- fe-admin` rỗng và `App.tsx` bên admin không có route matrix ⇒ lệch cố-ý, đã khai từ K4a; (b) Admin đổi `Code` trong Designer sẽ làm leaf nhóm ra trang rỗng — **nhưng có báo rõ** nhờ câu rỗng-vì-lọc mới, và BE cũng khoá cùng khuôn `^KHKK-N([1-8])$` (`:420`) nên phiếu không pin nhầm được. Hai tầng nhất quán, không phải FE tự chế luật riêng.
|
||||
|
||||
GATE-K4C-END
|
||||
|
||||
|
||||
|
||||
@ -0,0 +1,29 @@
|
||||
GATE-K6: IN-PROGRESS — đang đo, chưa chốt verdict (dòng-1 sẽ được ghi đè ở lượt cuối).
|
||||
|
||||
# Gate K6 — reviewer adversarial (S167, 2026-08-01)
|
||||
|
||||
Đối tượng: diff UNCOMMITTED `fe-admin/src/components/Layout.tsx` (+52/−19).
|
||||
Spec pin: `sub-invest-fable-b2-cum2.md:67-100` (K6 mục 1-4 + acceptance C1-C4).
|
||||
Lane artifact: `sub-implfe-k6.md`.
|
||||
|
||||
## R0 — Rào residual-write (đo TRƯỚC)
|
||||
|
||||
`git status --porcelain`:
|
||||
```
|
||||
M .claude/WAL.md
|
||||
M .claude/agent-memory/implementer-frontend/MEMORY.md
|
||||
M .claude/agent-memory/implementer-frontend/archive/2026-H2.md
|
||||
M .claude/auto-memory/MEMORY.md
|
||||
M .claude/auto-memory/feedback_claim_stronger_than_work.md
|
||||
M .claude/auto-memory/feedback_return_terminates_agent.md
|
||||
M .claude/governance/.session-counter.json
|
||||
M fe-admin/src/components/Layout.tsx
|
||||
?? .claude/sessions/session-9/_tiep-4.md
|
||||
?? .claude/workflows/runs/.../sub-cicd-verify-k5-k4c.md
|
||||
?? .claude/workflows/runs/.../sub-implfe-k6.md
|
||||
```
|
||||
⇒ Ngoài `.claude/*` (governance, ngoài phạm vi gate) chỉ có **đúng 1 file code**: `fe-admin/src/components/Layout.tsx`.
|
||||
**0 residual-write.** `git diff --stat` khớp lane khai: `71 +++++-----` = 52 thêm / 19 bớt.
|
||||
(Ghi chú: lane khai "đúng 1 file" trong `git diff --stat` — thực tế `git diff --stat` trần cho 8 file vì `.claude/*` cũng dirty; câu của lane chỉ đúng khi hiểu ngầm là "1 file CODE". Không phải lỗi thực chất, ghi để không ai đọc thành 0-dirty.)
|
||||
|
||||
## R1 — 12-key: so TỪNG CHỮ vs spec (đang đo)
|
||||
@ -191,26 +191,61 @@ function resolvePath(key: string): string | null {
|
||||
return null
|
||||
}
|
||||
|
||||
// Admin side: hide the per-ContractType contract submenu (Ct_*) — that's a
|
||||
// user-app concern. Keep Wf_* workflow-admin leaves.
|
||||
// [S57] BỎ ẩn "Danh mục" (Master/Catalogs + Cấu hình HRM re-parent vào đây) — gom
|
||||
// master data về 1 chỗ cho CẢ admin lẫn nhân viên (đảo S29 hide). Admin nay quản
|
||||
// master trực tiếp trên fe-admin; write vẫn khóa Admin/CatalogManager ở BE controller.
|
||||
// 🔴 [K4a — S166, vá-8] RÀNG BUỘC NGƯỢC với `resolvePath` bên trên — đọc trước khi
|
||||
// sửa bộ lọc này. fe-user có nhánh regex `^Khkk_G([1-8])_(WfView|List|Create|Pending|
|
||||
// Approved|Deleted)$`; fe-admin CỐ Ý KHÔNG có (admin không có `WorkflowMatrixViewPage`
|
||||
// — lead chốt 2026-07-27, giữ lệch). Hệ quả: 42 leaf `Khkk_G{2..8}_*` mà BE seed sẽ
|
||||
// rơi vào `resolvePath → null` ⇒ MenuLeaf trả null ⇒ DROP IM LẶNG (gotcha #50).
|
||||
// ⚠️ ĐO ĐĨA 2026-08-01: hàm này CHỈ ẩn `Ct_*` — `KeHoachKyKet` KHÔNG hề nằm trong tập
|
||||
// ẩn, tức ràng buộc trên KHÔNG phải chuyện tương lai mà ĐANG hiệu lực: sidebar admin
|
||||
// hiện root KHKK, 7 nhóm con, và 42 leaf của nhóm 2..8 im lặng biến mất (nhóm 1 sống
|
||||
// nhờ 6 dòng `Khkk_*` không-infix trong `staticMap`). Đây là trạng thái ĐÃ KHAI, chấp
|
||||
// nhận có chủ đích ở lượt K4a — KHÔNG phải bug mới phát sinh.
|
||||
// ⇒ Ai muốn admin thấy đủ 8 nhóm (hoặc ngược lại, thêm `KeHoachKyKet` vào tập ẩn rồi
|
||||
// sau đó gỡ ra) thì PHẢI nới regex `Khkk_G*` trong `resolvePath` TRƯỚC — và nới thì
|
||||
// phải quyết luôn `WfView` bên admin trỏ đâu, vì Designer không nhận `?group=`.
|
||||
// ============================================================================
|
||||
// [K6 — S166 2026-08-01, QĐ7] ADMIN THU VỀ VAI "NƠI-CONFIG"
|
||||
// ============================================================================
|
||||
// Sidebar fe-admin chỉ còn: Tổng quan · Hệ thống (Người dùng / Vai trò / Phân
|
||||
// quyền / Menu eOffice, cùng nhánh con "Quy trình duyệt (Mới)" + toàn bộ Designer).
|
||||
// Mọi module NGHIỆP VỤ (4 giai đoạn HĐ, Duyệt NCC, Danh mục, HRM, Văn phòng…)
|
||||
// làm việc ở fe-user; admin không phải là chỗ chạy nghiệp vụ nữa.
|
||||
//
|
||||
// 🔴 LỊCH SỬ ĐÃ ĐẢO 2 LẦN — đọc kỹ trước khi "sửa cho đúng":
|
||||
// · S29 : ẩn "Danh mục" khỏi admin.
|
||||
// · S57 : BỎ ẩn — gom master data về 1 chỗ cho cả admin lẫn nhân viên, admin
|
||||
// quản master trực tiếp trên fe-admin. (Đảo lần 1.)
|
||||
// · S164 : QĐ7 ẩn LẠI, lần này ẩn rộng hơn hẳn — admin thu về vai NƠI-CONFIG.
|
||||
// (Đảo lần 2, chính là luật đang chạy dưới đây.)
|
||||
// ⇒ Comment cũ của S57 nay KHÔNG còn hiệu lực. Ai đọc lịch sử git thấy hai
|
||||
// lời ngược nhau thì đây là chỗ giải thích: cả hai đều từng đúng, mốc sau
|
||||
// thắng mốc trước. Đừng "khôi phục" S57 vì tưởng gặp bug.
|
||||
//
|
||||
// 🔴 ĐÂY LÀ LỚP HIỂN THỊ, KHÔNG PHẢI LỚP QUYỀN (2 tầng độc lập — gotcha #82):
|
||||
// Route fe-admin còn nguyên, quyền của tài khoản admin còn nguyên ⇒ deep-link
|
||||
// thẳng `/purchase-evaluations`, `/khkk/list`… VẪN vào được và API vẫn trả 200.
|
||||
// Thu gọn ở đây = bớt lối đi trên menu, KHÔNG phải thu hồi quyền. Muốn CHẶN
|
||||
// thật thì phải làm ở BE authz (`[Authorize(Policy=…)]`), không phải ở file này.
|
||||
//
|
||||
// 🔴 CƠ CHẾ: `filterForAdmin` lọc ĐỆ QUY mọi tầng ⇒ ẩn 1 root là cả cây con chết
|
||||
// theo. Vì vậy tập dưới chỉ liệt ROOT (+2 nhánh con của `System`), KHÔNG liệt
|
||||
// từng leaf — liệt leaf vừa thừa vừa phải bảo trì lại mỗi lần BE thêm key.
|
||||
// Hệ quả phụ (tốt): các leaf `Khkk_G{2..8}_*` không còn được render bên admin
|
||||
// nên chuyện `resolvePath` thiếu nhánh `Khkk_G*` (khai ở K4a) hết là vấn đề —
|
||||
// nay chúng bị cắt từ tầng cha chứ không "rơi im lặng" ở tầng leaf nữa.
|
||||
// ⚠️ Nếu sau này gỡ `KeHoachKyKet` khỏi tập này thì PHẢI nới regex `Khkk_G*`
|
||||
// trong `resolvePath` TRƯỚC, kèm quyết định `WfView` admin trỏ đâu (admin
|
||||
// không có `WorkflowMatrixViewPage`; Designer không nhận `?group=`).
|
||||
const ADMIN_HIDDEN_KEYS = new Set<string>([
|
||||
'PurchaseEvaluations', // GĐ1 root — Pe_* chết theo
|
||||
'KeHoachKyKet', // GĐ2 root — toàn bộ nhánh Khkk_G* chết theo
|
||||
'Contracts', // GĐ3 root — Ct_* vốn đã có luật prefix, nay ẩn cả root
|
||||
'HopDongCung', // GĐ4 root — Hdc_* chết theo
|
||||
'Forms',
|
||||
'Reports',
|
||||
'Master', // Danh mục root — Suppliers/Projects/Departments/Catalogs + Cấu hình HRM chết theo
|
||||
'Hrm',
|
||||
'Off',
|
||||
'Personal',
|
||||
'Workflows', // CON của System ⇒ phải liệt riêng (Wf_* chết theo)
|
||||
'PeWorkflows', // CON của System ⇒ phải liệt riêng (PeWf_* chết theo)
|
||||
])
|
||||
|
||||
function isAdminHidden(key: string): boolean {
|
||||
return key.startsWith('Ct_')
|
||||
// `Ct_*` giữ luật prefix cũ: leaf hợp đồng theo loại là chuyện của app người
|
||||
// dùng. Thừa so với việc ẩn root `Contracts` ở trên, nhưng giữ để một ngày
|
||||
// nào đó mở lại root thì đám leaf `Ct_*` không ùa về theo. (KHÔNG ghi số lá
|
||||
// ở đây — số-đếm trong comment thối ngay khi BE thêm key [bài #84]; cần số
|
||||
// thì grep `Ct_` trong MenuKeys.cs phía BE.)
|
||||
return ADMIN_HIDDEN_KEYS.has(key) || key.startsWith('Ct_')
|
||||
}
|
||||
|
||||
function filterForAdmin(nodes: MenuNode[]): MenuNode[] {
|
||||
|
||||
Reference in New Issue
Block a user