Compare commits

...

2 Commits

Author SHA1 Message Date
ceeebf3c3b wal: flush 20260729T1425 2026-07-29 14:25:17 +07:00
0ac6e361f8 wal: flush 20260729T1352 2026-07-29 13:52:45 +07:00
8 changed files with 361 additions and 21 deletions

View File

@ -1,26 +1,23 @@
# WAL — auto-generated, không sửa tay
updated: 2026-07-29 | session: S159 (phiên-LOGIC L8, window 1) | branch: main
goal: arc-2 MỞ LẠI ĐỢT-4 (anh khoanh đỏ khối dưới hero): BỎ 5 KPI + 2 list "gần đây" → danh sách phiếu CHI TIẾT (tab 4-GĐ mang count + bảng full-width + cột Hành-trình 4-chấm per-row + phân trang thật) — designer đang chạy nền; pipeline sau đó: build → reviewer (>50 LOC) → commit → cicd. Đợt 1-3 ĐÃ ĐÓNG restart-proof (giữ nguyên khối chain dưới).
goal: arc-2 UI toàn-trình — đợt 1-4 SHIPPED (4 commit, cicd 3×PASS + 1 FAIL-bắt-revoker); đợt-5 CODE XONG chờ reviewer → commit → push → cicd → /pause (LỆNH ANH: "xong thì /pause")
chain:
- [x] bookend @open 5 vòng + (49)-(53) — run `2026-07-29-S159-bookend-open`
- [x] arc-2 ĐÓNG — run `2026-07-29-S159-tong-quan-pipeline-menu`:
Đ1 `b5799fc` Tổng quan pipeline (PASS 9/9) · Đ2 `1a47a61` tab strip + menu 4-GĐ + 01-07 (8/8 sạch, mục-9 lộ REVOKER)
· Đ3 `2a72695` gỡ nhánh S92 + skeleton GĐ2/GĐ4 + ComingSoonPage ×2 + 3 test spec-change (PASS 9/9, **restart-proof 494/494 + control âm 3 chân kiềng**)
- [x] sự cố revoker xử trọn: regrant=447 tức thì + code-fix + REGRESSION-GUARD test + verify-by-restart-THẬT (khuôn S91)
- [x] dọn dư-lượng ×3 lần trong phiên (stray 0 · empty 0 · agent-memory 23 nguyên)
- [ ] CLOSEOUT còn nợ (làm ở /session-end hoặc lượt kế):
· HANDOFF: chép verbatim ❹-risk (owner chấp nhận mở HĐ khi authz trần) + sự cố revoker + bộ tên 4-GĐ chốt cuối (Kế hoạch ký kết HĐ — supersede nhãn ❶) + baseline bundle mới
· STATUS CURRENT: bổ sung arc-2 (3 commit + revoker + skeleton) + row bundle baseline `CC1pGE1F`/`DWDbm5As`/`DeAg7MWm`/`BQIZ-rUe`
· gotchas.md: +#84 candidate "owner-supersede-owner: revoker ungated thi hành quyết cũ lật data-change tay — audit seeder CẢ HAI CHIỀU (re-add ⟂ revoke) trước mọi grant/hide SQL; nghiệm thu = restart THẬT + control âm"
· spec KHKK: ghi chú Wave-1 phải TÁI DÙNG/thay root `KeHoachKyKet` + skeleton keys Khkk_*
· session-log S159 + seed diary cicd-monitor đã tự ghi (nó khai), verify lúc closeout
· mind: chèn MIND-2 @close (mục E bổ sung 5 lượt arc-2: designer/reviewer/cicd×3-lượt + 2 resume)
- [x] Đ1 `b5799fc` Tổng quan pipeline — PASS 9/9 · Đ2 `1a47a61` tab strip + menu 4GĐ + 01-07 — 8/8 + mục-9 lộ REVOKER · Đ3 `2a72695` gỡ nhánh S92 + skeleton — PASS 9/9 restart-proof · Đ4 `a2bbcb9` list chi tiết + 7 FLAG + "Bảng cứng" — PASS 9/9 (durability ×2 restart)
- [x] Đ5 CODE XONG (build fe-user ✓521ms · test 45+517=562 ✓ · grep mồ-côi 0):
· BE: `ListDeletedContractsQuery` (ContractFeatures, chỗ-thứ-2 IgnoreQueryFilters + Where(IsDeleted) + IDOR mirror :297-302) + GET `/contracts/deleted` policy Contracts.Read + D3 note PE cập nhật
· Seeder: Ct ×7 nhóm +3 leaf (WfView/Approved/Deleted) + "Đang duyệt" backfill + Khkk 6 leaf + Dashboard→"Tổng quan quy trình" + reviewKeys +23
· FE fe-user: matrix +type=3 (HĐ chung 1 bộ V2) + route alias `/contracts/workflow-matrix` + Ct regex +3 action + staticMap Khkk ×2 + MyContractsPage (phase/deleted + banner + ẩn Tạo) + MenuLeaf depth-0 UPPERCASE + UserDashboardPage BỎ HERO + title "Tổng quan quy trình"
· fe-admin: leaf mới drop-silent CỐ Ý (tiền lệ WfView-PE)
- [!] reviewer đợt-5 ĐANG CHẠY (`sub-reviewer-diff-dot5.md`) — 6 trục, nặng nhất: endpoint vs khuôn PE + detail HĐ-đã-xóa 404? (trục 4)
- [ ] SAU verdict: xử FLAG → commit `[CLAUDE] Contract+FE-User: dot-5 ...` → push → message cicd verify (chạy NỀN, kết quả đọc phiên sau) → /pause
next: /session-end (đóng L8 window 1) — hoặc anh giao tiếp việc mới thì closeout dồn cuối phiên
next: (phiên sau /tiep) đọc verdict cicd đợt-5 (`cicd-verify-<sha>.md`) + closeout sổ bền (HANDOFF: ❹-risk verbatim + revoker + 4 đợt + tên-4-GĐ chốt cuối "Kế hoạch ký kết HĐ"(GĐ2)/"Duyệt hợp đồng"/"Hợp đồng cứng" + "Tổng quan quy trình"; STATUS CURRENT arc-2; gotchas +#84 revoker-2-chiều; spec KHKK note W1 tái dùng root/skeleton Khkk_*; session-log; MIND-2 @close)
verify:
- prod state chốt: 38-key 13/13 · Khkk 5 · Hdc 7 · root 12 · 3 root ẩn · Pe% 197/185 · nhãn 01-07 ×2 nhóm (Ct_+Hdc_ =14, đúng thiết kế)
- ⚠️ seeder grant = skip-existing KHÔNG nâng row false — prod rơi false thì re-grant TAY
- 🔴 lỗi lead phiên này để RCA closeout: grant SQL không audit revoker (bài S91 áp thiếu 1 chiều) — guard đã dựng bằng test
- prod hiện hành: menu 4-GĐ + 01-07 "- Bảng cứng" + 38-key 13/13 (bền ×2 restart) · bundle user `CLfAftby`/`B6bUSFBt` admin `CC1pGE1F`/`DWDbm5As`
- Đ5 CHƯA push — mọi thứ Đ5 mới ở local (kể cả seeder; menu leaf mới CHƯA có trên prod — deploy Đ5 seeder tự insert)
- 🔴 3 bài máy phiên này: cut-code = LINE-SCAN so CẢ DÒNG (bracket-count vỡ vì destructure/string; substring ' })' khớp sớm) · python heredoc bash vỡ → Write file script + PowerShell · script chết trước save = file NGUYÊN (đọc "cut X" in ra ≠ đã ghi đĩa)
- 🔴 diagnostics IDE stale ×3 lần hôm nay — chỉ tin build sạch chạy SAU (#68); grep 0-hit đối chứng

View File

@ -1,10 +1,12 @@
# CI/CD Monitor Agent — Persistent Memory
- **07-29 verify `a2bbcb9` ĐỢT-4 (danh sách toàn trình: 7 FLAG reviewer fix + nhãn 'Bảng cứng') [S159, 2 file code: FE-user 1043/337 + DbInitializer label-only 9/1, 0 mig]:** ✅ **PASS 9/9** — Run **#425** (id=538) success **5m40s**; gate **562**. User bundle rotate `DeAg7MWm``CLfAftby` (+css `BQIZ-rUe``B6bUSFBt`). 🔑 **ADMIN bundle FROZEN mà vẫn chứng minh được là LÀNH TÍNH (#69 mode-B) — khuôn dùng lại:** `fe-admin` 0 file trong diff ⇒ hash không đổi; nhưng `Last-Modified` của admin js = **13:55:31 NẰM TRONG cửa-sổ deploy** (13:51:15→13:56:55) + size trùng khít đợt-3 ⇒ file **ĐÃ ghi lại**, chỉ là Vite ra byte y hệt. ⟹ 'frozen' + LM-in-window = **re-ship giống hệt**, KHÁC hoàn toàn 'bundle cũ kẹt lại' (cái sau LM sẽ cũ). Đừng phán hash-frozen là fail. 🔑 **Comment-strip DỰ ĐOÁN TRƯỚC rồi mới đo (tiến bộ so đợt-1 là phát hiện SAU):** control âm `chưa tới` grep source ra **2** — soi ra cả 2 nằm trong COMMENT L343-344 giải thích chính FLAG-2 ⇒ **dự đoán bundle=0**, đo xong đúng **0**. Nếu đọc grep-source thô là FAIL oan một control vốn đúng. ⚠️ Ngược lại `Không tìm thấy` bundle=**10** > source=3 (chuỗi phổ thông, có ở module khác) ⇒ marker DƯƠNG chỉ dùng '≥1', **đừng lấy nó làm phép đếm-chính-xác**; chọn chuỗi ĐẶC-THÙ mới đếm-khớp được. **Durability lần 2 (khuôn 3 chân kiềng đợt-3, chạy lại rẻ):** restart thật (log mở lại **13:56:39 `Applying migrations...`** + w3wp 13:56:33) → 38-key **min=13 max=13 keys=38 totcr=494/494 GIỮ** · skeleton 156 · `Hdc_` nhãn `%-%`=7 · root 12 · 3 root vis=0 · **control âm Hrm/Off/Personal non-Admin=0 (revoker còn sống)** · `Pe%` 197/185. ⟹ **2 restart liên tiếp (#424, #425) đều giữ ⇒ fix bền, không phải may.** Evidence: `runs/2026-07-29-S159-tong-quan-pipeline-menu/cicd-verify-a2bbcb9.md`. Tag `[run-425, pass-9-9, admin-frozen-LM-in-window-proof, comment-strip-du-doan-truoc, marker-pho-thong-khong-dem-chinh-xac, durability-restart-2, gate-562]`
- **07-29 verify `2a72695` ĐỢT-3 (gỡ nhánh S92 khỏi revoker + skeleton menu GĐ2/GĐ4 + ComingSoonPage×2) [S159, 9 file code, 0 mig]:** ✅ **PASS 9/9 — ĐÓNG regression tao bắt ở đợt-2.** Run **#424** (id=537) success **5m56s**; gate **562** giữ nguyên (3 test SỬA theo spec-change, không thêm ⇒ tổng không đổi). Bundle rotate **4/4** (admin `D6Pz7nfV``CC1pGE1F`/`B1ErASuI``DWDbm5As` · user `Bpb8WEuY``DeAg7MWm`/`7oAYFlzM``BQIZ-rUe`); marker `Phân hệ đang được triển khai`=1 cả 2 bundle + `Quay lại`=4; hash CŨ→`text/html` = chứng ghi-đè. Menu-tree: Khkk=5 · Hdc=7 · root=12 · DUPKEYS=0 · 3 root vis=0. 🔑 **PHÉP DURABILITY LÀM ĐÚNG (sửa lỗi đo-sớm đợt-2) — khuôn 3 CHÂN KIỀNG, tái dùng cho mọi ca 'fix có bền không':** (i) **CHỨNG MINH có restart THẬT trước khi đo**`w3wp` PID StartTime 12:39:56 trong cửa-sổ deploy + **dòng ĐẦU log = 12:40:03 `Applying migrations...`** (dùng được vì deploy xoá `logs/` ⇒ log mở lại = boot mới) + DLL mtime; (ii) đo CÁI CẦN: 38-key **min=13 max=13 keys=38, tổng 494/494** SAU restart (đợt-2 chính phép này ra 47/494); (iii) **control ÂM chống Goodhart**: `Hrm/Off/Personal` non-Admin vẫn **=0** ⇒ revoker CÒN SỐNG, nên (ii) xanh KHÔNG phải do vô hiệu hoá cả hàm. **Thiếu (iii) thì 'fix' bằng cách xoá luôn checker cũng xanh y hệt.** Bổ sung: `Revoked` **0 dòng** trong log = ĐÚNG (hàm chỉ log khi `rows.Count>0`; nay 0 row cần lật, so 447 ở đợt-2) — **vắng-mặt phải giải-trình bằng cơ-chế, đừng đọc thành thiếu dữ liệu**. Guard mới có RĂNG 2 vế: `StillHiddenKeys` phải false + `ReopenedKeys` phải TRUE ⇒ re-add nhánh S92 là CI đỏ. ⚠️ Bẫy số: nhãn `01.-07.` đếm ra **14** (kỳ vọng 7) — tách prefix ra `Ct_`=7 + `Hdc_`=7 + other=0, đúng thiết kế 'GĐ4 tái dùng nhãn đã đánh số', KHÔNG phải nhân đôi. ⚠️ seeder grant là **skip-existing** ⇒ không tự nâng row `false` sẵn có trên prod; prod phải re-grant SQL + revoker thôi lật. Evidence: `runs/2026-07-29-S159-tong-quan-pipeline-menu/cicd-verify-2a72695.md`. Tag `[run-424, pass-9-9, durability-3-chan-kieng, restart-proof-w3wp-log-head, control-am-chong-goodhart, gate-562]`
- **07-29 verify `1a47a61` ĐỢT-2 (tab strip 4 GĐ trên phiếu PE + menu 4-GĐ + đánh số 01-07 nhóm HĐ) [S159, 8 file code 190+/18, FE×2 + seeder-only BE]:** **PARTIAL 8/9** — Run **#423** (id=536) success **5m44s**; gate **562 EXACT** (45+517). 0 migration (BE chỉ `MenuKeys.cs` const + `DbInitializer.cs` seeder). Bundle rotate **4/4** (admin js `BHodkPNV``D6Pz7nfV` css `DiNJjaoo``B1ErASuI` — admin-css rotate ĐÚNG vì fe-admin có sửa, khác đợt-1 frozen; user js `0GO8mgrG``Bpb8WEuY` css `BARVqkP-``7oAYFlzM`). Mirror `PePipelineStrip.tsx` 2 app **md5 IDENTICAL** `ef7b7824…`. 🔑 **Quy luật đếm marker (nối tiếp bài comment-strip): bundle-count = SỐ MODULE chứa chuỗi** (bundler pool hằng-số trong 1 module ⇒ nhiều occurrence cùng file gộp còn 1). USER=2 module→2 · ADMIN=1 module→1, đúng **6/6** (3 marker × 2 app) = 'đúng-BỘI-SỐ'. 2 control ÂM (`Đề xuất ký kết hợp đồng`, `Ký cứng & phát hành`) =0 cả 2 bundle + grep repo-wide 0-hit. Menu-data 5/5: **root=12 (0 double-insert) · DUPKEYS=0** · 2 root mới `KeHoachKyKet` ord26 / `HopDongCung` ord32 vis=1 (13 rows/13 canread mỗi key) · 3 root vẫn vis=0 · rename prefix `Duy…` ×2 · `Pe%` 197/185. 🔴 **FAIL #9 — BẮT ĐƯỢC REGRESSION NGOÀI CHECKLIST:** Serilog `Revoked 9 permission rows … Hợp đồng/Danh mục khỏi non-Admin` ⇒ đo lại grant-38-key của đợt-1: **47/494 canread, 0 key còn 13** (Admin=38 · Procurement=9 · 11 role còn lại=0, mất 447). Gốc = `DbInitializer.cs:2203-2236` **`RevokeTemporarilyHiddenModulesAsync` UNGATED chạy MỖI startup**, tập revoke TRÙNG KHỚP grant-set ⇒ **grant SQL tay không bao giờ bền, phải sửa CODE**. ⚠️⚠️ **BÀI HỌC LỚN NHẤT — bẫy ĐO-TRƯỚC-RECYCLE: pool restart nằm ở CUỐI deploy-window, nên đo DB lúc run còn `running` = đo trạng thái CŨ, KHÔNG chứng minh được 'sống sót recycle'. Mọi mục durability PHẢI đo SAU `status=success`.** (đợt-1 tao đo 11:46 vs restart 11:49 → kết luận sai). ⚠️ deploy `Remove-Item …pi\*` **xoá luôn `logs/`** ⇒ Serilog chỉ còn từ lần deploy cuối, không truy ngược được restart trước. ⚠️ prod Identity table = **`Roles`** không `AspNetRoles` (#472 tái xác nhận). Đợt-2 tự nó ship ĐÚNG — KHÔNG rollback. Evidence: `runs/2026-07-29-S159-tong-quan-pipeline-menu/cicd-verify-1a47a61.md`. Tag `[run-423, partial-8-9, do-truoc-recycle-trap, revoker-ungated-durability, marker-count-per-module, gate-562]`
- **07-29 verify `b5799fc` ĐỢT-1 (FE-User: Tổng quan tất cả luồng duyệt) [S159 L8]:** PASS 9/9 — Run **#422** success 5m59s, gate **562** (45+517), 0 mig, user js `DptYR4wL``0GO8mgrG`. 🔑 **marker PHÂN-BIỆT-PHIÊN-BẢN** (mạnh hơn marker-hiện-diện): designer gốc ghi `Pipeline 4 giai đoạn`, bản sau review-fix ghi `Toàn trình 4 giai đoạn` ⇒ bundle có `Toàn trình`=1 & `Pipeline`=0 ⟹ chứng prod chạy bản ĐÃ-FIX. Dùng khi review sửa CHỮ: chọn cặp chuỗi cũ/mới làm phép phân-biệt. ⚠️ **bẫy comment-strip**: `grep -c` source ra 2 mà bundle=1 — occurrence dư ở COMMENT đầu file (minifier strip) ⇒ so bundle với occurrence-CODE. ⚠️ **bẫy hình-dạng-truy-vấn**: control `Pe` ra 182/182 vs kỳ vọng 197/185 vì tao dùng `LEFT(MenuKey,3)='Pe_'` còn gốc là `LIKE 'Pe%'` (+`Permissions`/`Personal`/`PeWorkflows`=+15/+3) ⇒ **LỆCH-số phải truy PHẠM-VI-QUERY trước khi hô drift**. 🔴 **M7 của lượt này SAI**: đo 11:46 tức TRƯỚC pool-restart ~11:49 ⇒ không chứng minh được durability; phần 38-key sau đó mất thật (đợt-2 bắt) → đóng ở đợt-3. Evidence: `runs/2026-07-29-S159-…/cicd-verify-b5799fc.md`. Tag `[run-422, marker-phan-biet-phien-ban, comment-strip-trap, query-shape-not-drift]`
- _(**07-29 `b5799fc` ĐỢT-1 (Tổng quan tất cả luồng duyệt) PASS 9/9** — Run #422 5m59s, gate 562, 0 mig, user js `DptYR4wL``0GO8mgrG`. 🔑 **marker PHÂN-BIỆT-PHIÊN-BẢN**: designer gốc `Pipeline 4 giai đoạn` vs bản-sau-review `Toàn trình 4 giai đoạn` ⇒ bundle có `Toàn trình`=1 & `Pipeline`=0 ⟹ chứng prod chạy bản ĐÃ-FIX. Dùng khi review sửa CHỮ: chọn cặp chuỗi cũ/mới làm phép phân-biệt. ⚠️ **bẫy hình-dạng-truy-vấn**: control `Pe` ra 182/182 vs kỳ vọng 197/185 vì tao dùng `LEFT(MenuKey,3)='Pe_'` còn gốc là `LIKE 'Pe%'` (+`Permissions`/`Personal`/`PeWorkflows`=+15/+3) ⇒ **LỆCH-số phải truy PHẠM-VI-QUERY trước khi hô drift**. 🔴 M7 lượt này SAI (đo 11:46 TRƯỚC restart ~11:49) → regression lộ ở đợt-2, đóng ở đợt-3/4. Evidence `cicd-verify-b5799fc.md`)_
- _(**S157 #419 `a52a91e` PE cảnh-báo amber ô Địa-điểm (FE 2-app) GO 9/9** — gate 562 [45+517, khớp canonical STATUS cả tách-phần]; NO-Mig; 🔑 byte-marker **count=2/app = khớp SỐ FILE trong diff** ["đúng-BỘI-SỐ" > "tìm thấy ≥1"]; js-rotate ×2 css-FROZEN ×2; control âm 2 tầng [fake-hash + **hash CŨ** đều `ct=text/html` ⇒ bundle cũ biến mất = chứng ghi-đè]; self-correct on-disk "6 file"→13 commit/26 file · verbatim → `archive/2026-07.md` @S159-curate)_
- _(**#416 S155 `b1bae77` Workflow duyệt-TẠI-CHỖ +Mig 68 APPLIED-PROD 6/6** [gate 551; FLAG 'mig rớt khỏi commit' loại-trừ 3 lớp — mạnh nhất `GET /approval-workflows-v2` trả cột ×54 **materialize qua EF từ DB thật**] · **#407 S135 `4510cea` mã-dự-án normalize 5/5** [gate 532] · **#399 S134 `6664a61` PE lũy-kế 0-mig 5/5** [gate 528; 🔑 `ct=text/html` discriminator SẠCH HƠN size-919B vì shell-size drift] · **#398 S133 `d4fbced` +Mig 67 budget-freeze 5/5** [gate 520; backfill 18/18; python text=True crash cp1252 → dùng bytes] · verbatim → `archive/2026-07.md` via `_INDEX`)_
@ -35,7 +37,7 @@ cicd-lane đôi khi trả nhầm slice transcript (memory-curation) thay verdict
- **#25 IIS WebSocket** `notification-hub/negotiate` 401/404 prod. Fix: WebSocket module enable `web.config` site api (skill `iis-deploy-runbook`).
- **#48 SQLite tie-break** `OrderByDescending(CreatedAt).First()` pick wrong khi 2+ `.Add()` cùng frozen-clock. Fix: discriminator filter BEFORE OrderBy.
- **Bundle hash unchanged = ship FAIL** push+action success nhưng prod không đổi. Verify via INDEX.HTML ref (`curl -s https://admin.solutions.com.vn/ | grep -oE '/assets/index-[A-Za-z0-9_-]+\.(js|css)'`), NOT by GETting a hash-named asset directly. verify MUST sau status=success (Run #242 false-positive: check khi "running" stale hash).
- **🔴 #69 TWO-MODE (S72 #312 + S83 #333) FE hash vừa thể ROTATE vừa thể FROZEN.** deploy.yml copy `dist\*` UNCONDITIONALLY mọi run (path-filter gate cả workflow, không per-step) 0-FE-change vẫn rotate được (mode-A); ngược lại src y hệt Vite ra byte-identical hash FROZEN (mode-B). **Cả hai lành tính ⟹ hash-delta KHÔNG phải ship-proof.** Ship-proof THẬT = `Last-Modified` in deploy-window + real-size + byte-marker. Muốn biết FE đổi thật diff `fe-*/src` trong range.
- **🔴 #69 TWO-MODE (S72 #312 + S83 #333) FE hash vừa thể ROTATE vừa thể FROZEN.** deploy.yml copy `dist\*` UNCONDITIONALLY mọi run (path-filter gate cả workflow, không per-step) 0-FE-change vẫn rotate được (mode-A); ngược lại src y hệt Vite ra byte-identical hash FROZEN (mode-B). **Cả hai lành tính ⟹ hash-delta KHÔNG phải ship-proof.** Ship-proof THẬT = `Last-Modified` in deploy-window + real-size + byte-marker. Muốn biết FE đổi thật diff `fe-*/src` trong range. 🔑 **Phân biệt FROZEN-lành-tính vs bundle-cũ-kẹt: xem `Last-Modified`** frozen + LM **trong** cửa-sổ deploy = re-ship byte-identical (OK); frozen + LM **cũ** = deploy không chạm file (cờ đỏ). S159 #425 admin frozen, LM in-window lành tính.
- **⚠ SPA-fallback 200 trap (S72):** server rewrite `/*``index.html` GET `/assets/index-<BỊA>.js` vẫn **200**. Discriminator sạch = **`Content-Type`** (`text/html` ~876-919B = trap · `application/javascript` ~1,6MB = thật); size drift per-build nên đừng neo số byte. **Hash CŨ rơi về text/html = bundle cũ biến mất khỏi đĩa = chứng ghi-đè (control âm tầng-2).**
- **🔴 Durability: đo SAU recycle + 3 CHÂN KIỀNG** pool restart CUỐI deploy-window query DB lúc `running` = trạng thái (S159 đợt-2 tao sai đúng chỗ này). Khuôn đúng: (i) **chứng minh restart thật** (`w3wp` StartTime in-window + dòng ĐẦU log `Applying migrations...` hợp lệ deploy xoá `logs/`), (ii) đo chỉ-số đích SAU mốc đó, (iii) **control ÂM chứng minh checker còn sống** (thiếu (iii) thì 'fix' kiểu xoá checker cũng xanh). Ca gốc: `RevokeTemporarilyHiddenModulesAsync` ungated lật 447/494 row grant SQL tay (#423) đợt-3 #424 gỡ nhánh S92 + guard test 2 vế 494/494 sống sót. Data-change ngoài CI luôn phải re-verify SAU status=success.
- **Migration drift prod vs repo** compare `ls .../Persistence/Migrations/*.cs` vs `sqlcmd __EFMigrationsHistory`. Fix: check `Program.cs`/DbInitializer `app.MigrateDatabase()` + app pool recycle. Mig-applied proof = `__EFMigrationsHistory` top==repo-HEAD; stuck-old pool didn't recycle FAIL even if status=success+bundle-rotated.

View File

@ -1,6 +1,7 @@
# Reviewer Agent — Persistent Memory
- **S155-đợt2 (07-27) PE delete-approver DIFF-review #2 (12 FLAG 2H/4M/6L · ĐỪNG-DEPLOY):** 🔴 class MỚI **`hợp-đồng-đứt-giữa-2-bờ`** — FE type chép tay khai `allowApproverDelete: boolean`, BE record chỉ 8-member KHÔNG có cờ ⇒ nút CHẾT mà `tsc` + `dotnet build` + 561-test đều XANH (mọi thước đo đo chỗ khác) ⇒ **phép rẻ nhất = đếm member DTO vs khoá FE + `grep -c <cờ>` ở file dựng DTO**. · policy-gate `X.Delete` chặn ĐÚNG nhóm cần dùng (đo DB: 11/13 vai `CanDelete=0`, 2 seeder KHÔNG BAO GIỜ nâng) ⇒ owner gỡ policy; **verify "gỡ authz" = truy TỪNG dòng chặn thành bảng + grep `.First()` fallback + grep `Admin` trong chính file** (gỡ policy sinh LOW mới: endpoint thành máy-dò 4-loại-phản-hồi cho mọi tài khoản). · **mã ĐỔI giữa lượt soi ⇒ re-đo đĩa TRƯỚC verdict**; test đổi chiều (`==X``==null`) = **ĐỔI-SPEC hợp lệ ≠ nới-assert** vì 2 mệnh đề loại trừ nhau (nới = mệnh đề mi SUY RA được từ cũ). · cascade `Add con → Remove cha → 1 SaveChanges`: chứng "site duy nhất" bằng liệt-kê-vét-cạn 34 `Remove` → lọc 8 → xét cha-con từng cái.
- **S159 (07-29) dashboard "toàn trình" đợt-4 FE-only (fe-user 984+/337, PWF 7 FLAG 0H/2M/5L):** 🔴 **`tsc` XANH đo ÍT hơn vẻ ngoài** — `fe-user/tsconfig*.json` KHÔNG có `strict`/`strictNullChecks` (chỉ `noUnusedLocals`) ⇒ **tra cờ tsconfig TRƯỚC khi trích tsc làm bằng chứng**; ngược lại `noUnusedLocals`+pass = bằng chứng MẠNH HƠN grep cho claim "đã gỡ khối X" (compiler chặn nếu sót import). · class **`nhãn khẳng định chiều-ngược`**: dot-map `none='chưa tới'` — không-suy-diễn "đã xong" là đúng, nhưng "chưa tới" CŨNG là khẳng định ⇒ sai cho GĐ ĐỨNG TRƯỚC + sai khi `contractId != null` **có sẵn trong payload**. · pager tính `data.page±1` (keepPreviousData ⇒ stale) thay vì state ⇒ double-click = no-op. · **verify đích điều hướng phải đọc CẢ breakpoint trang đích** (panel `hidden lg:block` ⇒ ngõ cụt mobile; trang đích tự có nhánh `matchMedia` mà caller mới không copy). · regression đo HEAD⟂work bằng `grep -c` từng token: count 5→2 mà KHÔNG mất nhãn (inline-prop → `STAGES[i].title`) **đếm lệch phải truy CẤU TRÚC trước khi kêu hồi quy**. · tự tính lại 4 tỉ-số contrast + tra `@theme` mi `var(--color-*)` (0 dòng `--color-*: initial` ⇒ nấc mặc định còn) — 3/3 số designer ĐÚNG. · lời khai sub-file "766→1.130 dòng" SAI (đĩa 1.413 = 766337+984). [→ `runs/2026-07-29-S159-tong-quan-pipeline-menu/sub-reviewer-diff-dot4.md` 28,5KB]
- **[→ archive/2026-07.md @S159-curate] S155-đợt2 (07-27) PE delete-approver DIFF-review #2 (12 FLAG 2H/4M/6L · ĐỪNG-DEPLOY):** class **`hợp-đồng-đứt-giữa-2-bờ`** (FE type khai cờ, BE DTO không có ⇒ nút chết mà tsc+build+561-test đều xanh) · policy-gate đo DB 11/13 vai `CanDelete=0` · mã ĐỔI giữa lượt soi ⇒ re-đo đĩa TRƯỚC verdict · đổi-chiều-assert ≠ nới-assert.
- **[→ archive/2026-07.md @S155-curate] S155 ×2 (07-27) PE delete-approver — DIFF-review đợt 1 tiền-deploy (14 FLAG 4H/6M/3L/1I, DEPLOY-CÓ-RỦI-RO) + SPEC-review (PWF 4H/8M/5L):** 🔴 class MỚI **`HIGH nằm ở git chứ không ở mã`** — 2/3 file migration UNTRACKED mà snapshot ĐÃ tracked ⇒ `commit -a` nạp model-có-cột nhưng bỏ migration ⇒ `Invalid column name` cả 7 module, build+test vẫn xanh ⇒ **gate deploy phải chấm `git status ^??` cho `Migrations/`** · `ExecuteDelete` ngoài transaction TRƯỚC `SaveChanges` = mất dữ liệu không hoàn tác · purge xoá CỨNG chữ ký phiếu XOÁ MỀM ⇒ **test đang KHOÁ chiều ngược, vá xong test đỏ ≠ hồi quy, CẤM nới assert** · ~600 dòng BE = mã chết (`grep api.put`=0) · **xanh-giả bắt bằng phép 2-thế-giới** (còn-gate ⟂ bỏ-gate cùng cho 0 ⇒ 0 thông tin) · CLEAN chứng bằng **liệt kê vét cạn** write-site + sweep gián-tiếp (#81-EXT) · 2 lỗi TỰ BẮT: trích line-number **từ diff** (diff không mang số dòng file) · `grep -v Snapshot` ăn nhầm tên migration · **file bị sửa TRONG LÚC soi ⇒ neo bằng NỘI DUNG DÒNG + ghi bản-đồ-trôi**.
- **[→ archive/2026-07.md @S155-curate] S146·S147·S152-D2 (digest):** S146 lead sửa 1 phép quét 4 lần/phiên vì mã-hoá ý-định NGỮ-NGHĨA vào regex ⇒ nấc đúng = lưới-soát-cho-người + allowlist CÓ TÊN + use⟂mention · S147 **decouple 1 gate ⇒ truy xem prop dùng chung đã mang carve-out chưa** (`itemsReadOnly` ≠ raw `readOnly`); "same fix" cho surface anh-em có thể trúng gate KHÁC · S152-D2 census thay spot 307/307; **3 THƯỚC-HỎNG** (md5 disk vs `git show` = FAIL giả do CRLF⟂LF · `grep -c` dính citation-trap · `grep -iF` MSYS trả 0-hit IM LẶNG trên UTF-8) + máy-trích mù đuôi `{.yml,.json,.sql}`.
- **[→ archive/2026-07.md @S145-curate] S145 4-lane (Axis-A/D/E + S145b GATE) — digest:** bất-biến chia-đôi giữa 2 axis nằm ĐÚNG SEAM ⇒ chỉ cross-cut sweep bắt (hook 3→4 path orphan; `session_ctx_kb` ghost-wire #H18) · 'vai-có-sổ' acceptance ⇒ MUST `ls agent-memory/<role>/` (folder≠sổ, ring1-audit RỖNG) · stale-fix phải verify caller-TRÊN-ĐĨA — grep-"4 surface"=0-hit là ANTI-TEETH Goodhart; 2-script-same-suffix = conflation-vector · đếm VALID_ROLES = per-file grep bidirectional KHÔNG regex (comment lừa parser).

View File

@ -156,3 +156,9 @@
- **Tự bắt 2 lỗi của CHÍNH mình trước khi báo:** "Mig 47/50/42/46 MISS" = artifact chuẩn-hoá của tao (`Mig42` liền không dấu cách, có ĐỦ 2 phía) · context-regex `.{70}` trả RỖNG đọc nhầm thành sạch → phải `.{0,70}`. ⇒ **LUẬT: mọi MISS phải literal-grep XÁC-NHẬN 2 phía bằng công-cụ THỨ HAI trước khi vào report.**
- D re-ground từ đĩa: `docs/gotchas.md` count=83/max=83 ⇒ 0 nhãn `gotcha #N>83`; soi NGƯỢC 24 dòng gotcha ≤83 tìm mirror-error (run-number đội lốt gotcha) → 0 ca, mọi #N đều là gotcha thật trong ngữ-cảnh nguồn. Tag `[s152, d2-fidelity-gate, coverage-diff, extractor-ext-blindspot, thuoc-hong-x3]`
---
## @S159-curate — moved from L1 (S155-đợt2 verbatim, moved-not-cut)
- **S155-đợt2 (07-27) PE delete-approver DIFF-review #2 (12 FLAG 2H/4M/6L · ĐỪNG-DEPLOY):** 🔴 class MỚI **`hợp-đồng-đứt-giữa-2-bờ`** — FE type chép tay khai `allowApproverDelete: boolean`, BE record chỉ 8-member KHÔNG có cờ ⇒ nút CHẾT mà `tsc` + `dotnet build` + 561-test đều XANH (mọi thước đo đo chỗ khác) ⇒ **phép rẻ nhất = đếm member DTO vs khoá FE + `grep -c <cờ>` ở file dựng DTO**. · policy-gate `X.Delete` chặn ĐÚNG nhóm cần dùng (đo DB: 11/13 vai `CanDelete=0`, 2 seeder KHÔNG BAO GIỜ nâng) ⇒ owner gỡ policy; **verify "gỡ authz" = truy TỪNG dòng chặn thành bảng + grep `.First()` fallback + grep `Admin` trong chính file** (gỡ policy sinh LOW mới: endpoint thành máy-dò 4-loại-phản-hồi cho mọi tài khoản). · **mã ĐỔI giữa lượt soi ⇒ re-đo đĩa TRƯỚC verdict**; test đổi chiều (`==X``==null`) = **ĐỔI-SPEC hợp lệ ≠ nới-assert** vì 2 mệnh đề loại trừ nhau (nới = mệnh đề mới SUY RA được từ cũ). · cascade `Add con → Remove cha → 1 SaveChanges`: chứng "site duy nhất" bằng liệt-kê-vét-cạn 34 `Remove` → lọc 8 → xét cha-con từng cái. Tag `[s155-dot2, hop-dong-dut-2-bo, policy-gate-do-DB, re-do-dia-truoc-verdict]`

View File

@ -0,0 +1,190 @@
# cicd-verify `a2bbcb9` — S159 ĐỢT-4 "danh sách toàn trình: 7 FLAG reviewer fix + nhãn 'Bảng cứng'"
> Ghi-TỪNG-MỤC-trong-lúc-làm (chống #53). File = SẢN PHẨM CHÍNH; return chỉ tóm tắt.
> Đợt-1 `b5799fc` PASS 9/9 · đợt-2 `1a47a61` PARTIAL 8/9 · đợt-3 `2a72695` PASS 9/9 — 3 file cũ KHÔNG chạm.
> Bắt đầu: 2026-07-29 ~13:52+07 · cicd-monitor (READ-only)
---
## M1 — Push + phạm vi · ✅ PASS
```
git log -1 → a2bbcb91c37de3d79a61809fd903d918e7d9f105
[CLAUDE] FE-User: dot-4 danh sach toan trinh - 7 FLAG reviewer fix
+ nhan 'Bang cung' (owner ve) · 2026-07-29 13:51:07 +0700
git log origin/main..HEAD → EMPTY (đã push)
```
Range `2a72695..a2bbcb9` = 9 file; **2 file CODE** (7 file còn lại `.claude/**`, ignored):
| File code | +/ | ghi chú |
|---|---|---|
| `fe-user/src/pages/UserDashboardPage.tsx` | **+1043 / 337** | redesign list toàn trình + 7 FLAG fix |
| `src/Backend/…/Persistence/DbInitializer.cs` | **+9 / 1** | **label-only** |
⚠️ Tự-đính-chính nhẹ so với brief: brief ghi "1050+/338", số thật `--numstat` = **1043/337**.
Không đổi bản chất (FE-user 1 file + BE label-only).
**`fe-admin` KHÔNG có file nào trong diff** ⇒ admin bundle có thể rotate HOẶC frozen — cả hai
đều lành tính (#69 TWO-MODE). Sẽ **ĐO rồi mới nói**, không phán trước.
**DbInitializer đúng là label-only** (đọc diff):
```csharp
- tree.Add(("Hdc_" + code, label, MenuKeys.HopDongCung, hdcOrder++, "FileText"));
+ tree.Add(("Hdc_" + code, label + " - Bảng cứng", MenuKeys.HopDongCung, hdcOrder++, "FileText"));
+ ["Hdc_ThauPhu"] = "01. HĐ Thầu phụ - Bảng cứng", ×7 (labelBackfill)
```
Không đụng revoker / reviewKeys / grant ⇒ **không có rủi ro mới cho durability**, nhưng vẫn sẽ đo lại (M6).
## M2 — Migration VẮNG là ĐÚNG · ✅ PASS
```
git diff --name-only 2a72695..a2bbcb9 -- '*Migrations*' → 0
```
Mig giữ **68** / 89 bảng.
## 📌 BASELINE TRƯỚC RESTART (đo 13:52+07, run #425 đang `running`, pool CHƯA recycle)
| Phép đo | TRƯỚC restart |
|---|---|
| 38-key set | **min=13 max=13 keys=38** |
| Skeleton `Khkk_`+`Hdc_` permission rows | **156** |
| `Hdc_*` MenuItems có hậu tố (`Label LIKE '%-%'`) | **7** (prod đã SQL cùng ngày) |
Bundle trước deploy: admin js `CC1pGE1F` css `DWDbm5As` · user js `DeAg7MWm` css `BQIZ-rUe`
— khớp CHÍNH XÁC baseline hậu-đợt-3 ⇒ baseline hợp lệ.
## 🔎 M3-pre — Marker kỳ vọng, đếm trên SOURCE (chốt trước khi soi bundle)
`UserDashboardPage.tsx` (63.124 B):
| Chuỗi | source | vai trò | dự đoán bundle |
|---|---|---|---|
| `chưa có thông tin ở giai đoạn này` | 1 | dương | 1 |
| `đã sinh hợp đồng` | 1 | dương | 1 |
| `Không tìm thấy` | 3 | dương | ≥1 |
| `HĐ ĐANG SOẠN` | **0** | ÂM (KPI cũ bỏ) | 0 |
| `Tổng giá trị nháp` | **0** | ÂM (KPI cũ bỏ) | 0 |
| `chưa tới` | **2** ⚠️ | ÂM (DOT_TEXT cũ đã thay) | **0** |
⚠️ **BẪY ĐÃ GỠ TRƯỚC (nếu không soi sẽ báo FAIL oan):** `chưa tới` grep ra **2** trên source,
nhưng cả 2 đều nằm trong **COMMENT** giải thích lý do FLAG-2:
```
L343: // [FLAG-2 reviewer đợt-4] 'chưa tới' là lời KHẲNG ĐỊNH sai với chấm PHÍA TRƯỚC
L344: // giai đoạn hiện tại (HĐ ở GĐ3 thì GĐ1/GĐ2 là quá khứ, không phải "chưa tới").
L345: none: 'chưa có thông tin ở giai đoạn này', ← chuỗi THAY THẾ (code thật)
```
⇒ occurrence-**CODE** = **0**; minifier strip comment ⇒ bundle phải = **0**.
(Đúng mẫu comment-strip đã học ở đợt-1 — so bundle với occurrence-CODE, không với grep-source thô.)
## M4 — Poll terminal · ✅ PASS
- **id=538 · run_number=425 · head_sha `a2bbcb91`** · created 13:51:15 → **`status=success` 13:56:55**
- **Duration 5m40s** (đợt-1 5m59s · đợt-2 5m44s · đợt-3 5m56s) — ổn định, dưới ngưỡng.
### M4b — Test gate · ✅ 562 EXACT
```
Passed! - Failed: 0, Passed: 45, Skipped: 0, Total: 45, 159 ms - SolutionErp.Domain.Tests.dll
Passed! - Failed: 0, Passed: 517, Skipped: 0, Total: 517, 1m27s - SolutionErp.Infrastructure.Tests.dll
```
`Failed!` ×0 ⇒ **562**. Giữ nguyên — đúng kỳ vọng (FE + label BE, không đụng test).
Regression-guard đợt-3 (`StillHiddenKeys` / `ReopenedKeys`) nằm trong 517 này và **vẫn xanh**.
## M5 — Bundle · ✅ PASS (đo 13:57:23, sau status=success)
| App | asset | TRƯỚC | SAU | KQ |
|---|---|---|---|---|
| **user** | js | `DeAg7MWm` | **`CLfAftby`** | ✅ **ROTATE** (điều kiện bắt buộc) |
| **user** | css | `BQIZ-rUe` | **`B6bUSFBt`** | ✅ ROTATE |
| admin | js | `CC1pGE1F` | `CC1pGE1F` | **FROZEN** — lành tính, xem dưới |
| admin | css | `DWDbm5As` | `DWDbm5As` | **FROZEN** — lành tính |
🔑 **Admin FROZEN = #69 mode-B, KHÔNG phải deploy hỏng — và tao chứng minh được, không đoán:**
```
ADMIN index-CC1pGE1F.js ct=application/javascript size=1.678.733 LM=13:55:31+07
```
`Last-Modified` **13:55:31 nằm TRONG cửa sổ deploy** (13:51:15→13:56:55) ⇒ file **ĐÃ được ghi lại**
trong lần deploy này (deploy copy `dist\*` vô điều kiện), chỉ là Vite dựng ra **byte y hệt**
`fe-admin` 0-file-thay-đổi ⇒ content-hash không đổi. Size trùng khít đợt-3 (1.678.733).
⇒ Đây là "re-ship nội dung giống hệt", **không phải** "bundle cũ kẹt lại". Brief dặn "ghi nhận,
không phán sai" — kết luận: **lành tính, nhất quán với diff**.
```
USER index-CLfAftby.js ct=application/javascript size=1.601.698 LM=13:56:28+07 (in-window)
```
Control âm 2 tầng: hash CŨ `DeAg7MWm` **và** hash bịa đều trả 200 nhưng `ct=text/html` 876 B
⇒ bundle đợt-3 đã biến mất khỏi đĩa = chứng ghi-đè thật.
## M3 — Byte-marker bundle USER · ✅ PASS (3 dương / 3 âm)
| Chuỗi | bundle | kỳ vọng | KQ |
|---|---|---|---|
| `chưa có thông tin ở giai đoạn này` | **1** | dương | ✅ |
| `đã sinh hợp đồng` | **1** | dương | ✅ |
| `Không tìm thấy` | **10** | dương | ✅ |
| `chưa tới` | **0** | **ÂM** | ✅ |
| `HĐ ĐANG SOẠN` | **0** | **ÂM** | ✅ |
| `Tổng giá trị nháp` | **0** | **ÂM** | ✅ |
🔑 **Dự đoán comment-strip ĐÚNG:** source có `chưa tới` ×2 (đều trong comment L343-344), bundle ra
**0** — xác nhận lại quy tắc "so bundle với occurrence-CODE, không với grep-source thô".
Nếu đọc grep-source thô sẽ báo FAIL oan cho một control vốn ĐÚNG.
⚠️ `Không tìm thấy` bundle=10 > source=3: chuỗi phổ thông, xuất hiện ở module khác nữa
(empty-state dùng chung). Là marker DƯƠNG nên "≥1" đủ kết luận; không dùng nó làm phép đếm-chính-xác.
## M6 — RESTART-DURABILITY lần 2 · ✅ PASS (chứng bền qua ≥2 restart)
**Bước 0 — chứng minh có restart THẬT (giữ khuôn đợt-3):**
| Bằng chứng | Giá trị |
|---|---|
| Dòng ĐẦU log `solution-erp-20260729.log` | **`13:56:39.193 +07:00 [INF] Applying migrations...`** |
| `w3wp` StartTime (mới nhất trong cửa sổ) | **13:56:33** |
Deploy xoá `logs/` ⇒ log mở lại lúc 13:56:39 = **boot mới**, DbInitializer (seeder + revoker) đã chạy.
Mọi số dưới đo SAU mốc này.
| Phép đo | TRƯỚC restart (13:52) | **SAU restart (13:57)** | KQ |
|---|---|---|---|
| **38-key set** | 13/13/38 | **min=13 max=13 keys=38 · totcr=494/494** | ✅ **GIỮ** |
| Skeleton `Khkk_`+`Hdc_` perms | 156 | **156** | ✅ |
| `Hdc_*` nhãn có hậu tố `%-%` | 7 | **7** | ✅ |
| Root menu | — | **12** | ✅ |
| 3 root ẩn `Hrm`/`Off`/`Personal` `IsVisible` | — | **0 / 0 / 0** | ✅ |
| **Control ÂM** `Hrm%`/`Off%`/`Personal` non-Admin canread | 0 | **0** | ✅ revoker CÒN SỐNG |
| Control `Pe%` | — | **197 / 185** | ✅ |
🎯 **Đây là restart thứ 2 liên tiếp sau khi gỡ nhánh S92** (#424 rồi #425). Grant 38-key giữ
**494/494** cả hai lần ⇒ không phải "may một lần". Control âm vẫn 0 ⇒ fix không phải do
vô hiệu hoá cả revoker (chống Goodhart, giữ đủ 3 chân kiềng của đợt-3).
## M7 — Smoke · ✅ PASS 4/4
```
/health/live 200 Healthy · /health/ready 200 Healthy · admin 200 · eoffice 200
```
---
## VERDICT: **PASS — 9/9**
| # | Mục | KQ |
|---|---|---|
| 1 | Push + range 2 file code (1043/337 + 9/1 label-only) | ✅ |
| 2 | Migration VẮNG là ĐÚNG | ✅ |
| 3 | Run #425 success 5m40s | ✅ |
| 4 | Test gate **562** (45+517, 0 fail) | ✅ |
| 5 | Bundle **user rotate** `DeAg7MWm``CLfAftby` (+css) | ✅ |
| 6 | Admin FROZEN — chứng minh lành tính bằng **LM in-window + size trùng** (#69 mode-B) | ✅ |
| 7 | 3 marker DƯƠNG trong bundle user | ✅ |
| 8 | 3 control ÂM VẮNG (`chưa tới` / `HĐ ĐANG SOẠN` / `Tổng giá trị nháp`) + hash-cũ `text/html` | ✅ |
| 9 | **Durability lần 2: 38-key 494/494 · skeleton 156 · Hdc nhãn 7 · root 12 · control âm 0 · Pe 197/185** | ✅ |
**Không có cờ đỏ. Không khuyến nghị rollback.**
Baseline mới: **user js `CLfAftby` / css `B6bUSFBt`** · admin **giữ** js `CC1pGE1F` / css `DWDbm5As`
(frozen hợp lệ) · gate 562 · Mig 68 / 89 bảng · menu root 12 · 38-key 13/13 · skeleton 156.
### Ghi cho arc S159 (4 đợt)
đợt-1 PASS 9/9 → đợt-2 PARTIAL 8/9 (bắt regression revoker ăn 447/494 row) → đợt-3 PASS 9/9
(đóng regression, chứng bằng 3 chân kiềng) → **đợt-4 PASS 9/9 (tái xác nhận durability qua restart thứ 2)**.
<!-- END cicd-verify-a2bbcb9 · VERDICT=PASS 9/9 -->

View File

@ -76,7 +76,20 @@
· ⚠️ **Lưu ý còn hiệu lực:** nhánh grant seeder = **skip-existing, KHÔNG tự nâng row false** — prod đúng nhờ re-grant SQL + revoker thôi lật; prod nào sau này rơi false thì re-grant tay, đừng chờ seeder.
· Baseline mới: admin js `CC1pGE1F`/css `DWDbm5As` · user js `DeAg7MWm`/css `BQIZ-rUe` · 38-key 13/13.
<!-- ARC-2 ĐÓNG @S159 12:5x — 3 đợt · 3 commit (`b5799fc`·`1a47a61`·`2a72695`) · 3 cicd (PASS 9/9 · FAIL 8/9 bắt revoker · PASS 9/9 restart-proof) · prod-data 5 lượt SQL (backup+verify đủ) -->
<!-- moc: 3 đợt đầu đóng @12:5x — arc MỞ LẠI đợt-4 theo 3 lệnh mid-turn anh -->
## ĐỢT-4 — "bỏ hết mấy cái này, hiện list chi tiết đàng hoàng" + tab strip menu + "Bảng cứng"
**Owner verbatim:***"bỏ hết mấy cái này đi, hiện theo list chi tiết đàng hoàng luôn. Nghĩ cách bố trí sao cho nó có danh sách phiếu mà User nhìn vào vẫn thấy đc tổng quan liên kết."* (ảnh khoanh 5 KPI + 2 list gần-đây) ② *"Thêm chữ bảng cứng vào nhé"* (ảnh: `01. HĐ Thầu phụ - Bảng cứng`) ③ *"vậy là tròn 1 quy trình của Solution rồi đấy."* (ghi nhận, không phải lệnh).
- [x] **Đ4-a — designer redesign** (`sub-frontend-designer-dot4.md`, §5 lead on-behalf): BỎ đúng khối khoanh · THÊM tab 4-GĐ mang count + 2 bảng chi tiết (PE 7 cột + HĐ) + cột **Hành trình 4-chấm** per-row + phân trang + tìm kiếm (param BE verify thật §2) · trang **59,8 KB**, build PASS · 🔴 nỗi lo CSS-token của vai (chết #53 đúng lúc verify): **lead verify hộ SẠCH** — 0 class ghép động, 3 lookup-map tĩnh
- [x] **Đ4-b — reviewer** `PASS_WITH_FLAGS — 7 FLAG (0H/2M/5L)` (`sub-reviewer-diff-dot4.md` 28.510B; vì designer chưa kịp tự-review FD4 ⇒ reviewer là lớp duy nhất, trục 6 a11y đo 10/10 mục). **Disposition TỪNG DÒNG:**
· F1[M] mobile HĐ ngõ-cụt → **FIX** mirror matchMedia trang đích · F2[M] dot "chưa tới" nói chắc điều không biết (3 ca — ca-3: HĐ DangDongDau/DaPhatHanh = GĐ4 thật mà chấm GĐ3) → **FIX** per-dot states + `linked` (contractId THẬT) + `contractDots` suy diễn tuyến-tính-CÙNG-module + aria chỉ kể chấm CÓ thông tin · F3[L] pager race → **FIX** page-state · F4[L] aria-live biến mất khi 0 kết quả → **FIX** role=status sr-only · F5[L] Hành trình mất trên mobile → **FIX** 4-chấm vào sub-line · F7[L] nút X chờ debounce → **FIX** clearSearch() · **F6[L] tab-count vs search → PARK** (chờ UAT; 🔴 **ERRATA commit-message `a2bbcb9`**: câu "co chu thich o code" cho F6 là **SAI** — không có chú thích nào được thêm; disposition thật = PARK. Khai tại đây, không amend)
- [x] **Đ4-c — nhãn "Bảng cứng"**: prod `suffix=7` (UTF-16 file, idempotent NOT-LIKE guard) + seeder tree.Add + labelBackfill 7 entry. 🔸 Theo VERBATIM anh vẽ "Bảng cứng" (nếu ý anh là "Bản cứng" — 1 lệnh đổi được, guard NOT-LIKE vẫn idempotent)
- [x] **Đ4-d — build PASS 495ms · Infra 517 PASS · commit `a2bbcb9` push** (2 file, 1052+/338)
- [!] **Đ4-e — cicd verify `a2bbcb9`** đang chạy (marker bản mới + control âm "chưa tới"/"HĐ ĐANG SOẠN" vắng + restart-durability lần 2 của 38-key)
<!-- ARC-2: 4 đợt · 4 commit (`b5799fc`·`1a47a61`·`2a72695`·`a2bbcb9`) · prod-data 7 lượt SQL -->
- [x] **T2 — Prod menu data****LIVE + VERIFY TRỌN** (`prod-backup-pre-apply.txt` → apply `hide=3 upd=99 ins=376``prod-verify-post-apply.txt`):
· Inspect lộ thực-trạng ≠ giả định: Master subtree có matrix 13-row/key (canread 2/13) nhưng **`Ct_*` 28 key = 0 ROW** (chưa từng seed), `Contracts` root 1 row ⇒ INSERT 376 + UPDATE 99, số khớp toán từng đồng (9×11=99 · 28×13+12=376)

View File

@ -100,3 +100,106 @@ Nền so sánh: `HEAD` = 766 dòng và ĐÃ chứa hero + các bản vá vòng-1
`fe-user/src/pages/UserDashboardPage.tsx:606` đặt `aria-live="polite"` lên dòng "Hiển thị xy trong N", nhưng chính khối chứa nó bị gỡ khỏi cây khi không có kết quả (`:601` — `if (!data || data.total === 0) return null`). Nghĩa là đúng lúc cần thông báo nhất — gõ một từ khoá không khớp gì — vùng thông báo biến mất, và khối trạng thái rỗng thay thế nó (`:704-731`) thì không nằm trong bất kỳ vùng `aria-live` nào. Người dùng bàn phím/trình đọc gõ xong sẽ không nghe gì cả và không biết bảng vừa đổi.
**Fix đề nghị:** giữ vùng `aria-live="polite"` luôn tồn tại (khi rỗng thì thông báo "Không tìm thấy phiếu nào khớp"), hoặc gắn `role="status"` cho khối trạng thái rỗng.
### FLAG-5 [L] — tính năng chủ đạo của đợt-4 biến mất trên điện thoại
Cột "Hành trình" — thứ mà chính đầu bài gọi là tầng liên kết thứ hai, và là câu trả lời cho yêu cầu "nhìn vào vẫn thấy được tổng quan liên kết" — được đặt `hidden … md:table-cell` ở cả hai bảng (`fe-user/src/pages/UserDashboardPage.tsx:672` và `:866`, kèm ô dữ liệu `:812` và `:977`). Dưới 768px cột này không tồn tại, nên trên điện thoại người dùng chỉ còn dải hero ở trên, không còn thông tin "từng phiếu đang đứng đâu".
Chọn ẩn cột trên màn hẹp là quyết định hợp lý về mặt bố cục — tôi không đòi hiện nguyên bảng 7 cột trên điện thoại. Điều đáng ghi là **phần bị hy sinh lại đúng là phần được giao làm**, và dòng phụ gộp cho mobile (`:765-767`) chỉ gộp Dự án + Ngày, không gộp hành trình.
**Fix đề nghị:** đưa cụm 4 chấm vào dòng phụ trên mobile (cùng chỗ với Dự án · Ngày), hoặc đổi ngưỡng ẩn của cột Người soạn/Ngày để nhường chỗ cho Hành trình.
### FLAG-6 [L] — hai con số đếm cùng một thứ, hiện cạnh nhau, không khớp nhau khi đang tìm kiếm
Số trên thẻ tab lấy từ `peTotalQuery` / `contractTotalQuery` (`:1333`) — đây là tổng **không kèm từ khoá**. Bảng ngay bên dưới lại lọc theo `search` (`:1147`, `:1159`), và dòng phân trang in tổng **có kèm từ khoá** (`:608`). Gõ một từ khoá xong, cùng một màn hình sẽ có thẻ tab ghi "Giai đoạn 1 · 57" trong khi dòng dưới ghi "Hiển thị 13 trong 3 phiếu".
Không sai dữ liệu — hai số đo hai thứ khác nhau — nhưng đặt cạnh nhau mà không chú thích thì người đọc phải tự đoán.
**Fix đề nghị:** khi `search` khác rỗng thì cho thẻ tab đang chọn lấy số từ chính kết quả danh sách (`peList.data.total`), hoặc thêm một chữ nhỏ "trên tổng 57" cạnh dòng phân trang.
### FLAG-7 [L] — hai nút cùng nghĩa "xoá tìm kiếm" nhưng phản hồi khác nhau
Nút chữ X trong ô tìm (`:556-558`) chỉ gọi `onChange('')`, tức chỉ đổi `searchInput`, rồi phải chờ hết 300ms gỡ nhịp mới thực sự bỏ lọc. Nút "Xoá tìm kiếm" ở khối trạng thái rỗng (`:717`, `:911`) gọi `clearSearch()` (`:1136-1140`) đặt lại cả `searchInput`, `search` và `page` **ngay lập tức**. Cùng một hành động, một đường có độ trễ, một đường không.
**Fix đề nghị:** cho nút X gọi thẳng `clearSearch()`.
---
## TRỤC 6 — bảng có tử tế với bàn phím / trình đọc không, và mỗi tab có đủ 3 trạng thái không
Đo trên mã, từng mục:
| Mục | Có / Không | Chỗ đo |
|---|---|---|
| `<caption>` cho bảng | CÓ, cả 2 bảng, `sr-only`, câu nói rõ cột Hành trình dùng để làm gì | `:660-663`, `:854-857` |
| `scope="col"` mọi ô tiêu đề | CÓ, 7/7 mỗi bảng | `:666-672`, `:860-866` |
| `scope="row"` cho ô đầu dòng | CÓ (ô Mã phiếu / Mã HĐ là `<th scope="row">`) | `:740`, `:934` |
| Bấm dòng bằng bàn phím | CÓ — `<tr onClick>` chỉ là tiện cho chuột; điểm dừng bàn phím THẬT là `<button>` trong ô Mã, có `stopPropagation` nên không kích hoạt hai lần | `:735-751`, `:929-945` |
| Không lồng nút trong nút | ĐÚNG — `PeUrgentChips` và `PeFinalizeChip` chỉ dựng `<span>` + `title`, không có phần tử bấm được | `components/pe/PeUrgentChips.tsx`, `components/pe/PeFinalizeChip.tsx` |
| Trạng thái đang tải, theo từng tab | CÓ — `TableSkeleton` dùng một ô `colSpan` nên không lệch khi cột bị ẩn theo bề rộng | `:574-590`, dùng ở `:677`, `:871` |
| Trạng thái lỗi, theo từng tab | CÓ — khối lỗi riêng + nút "Thử lại" gọi đúng `refetch()` của query đó, KHÔNG gộp vào băng lỗi của hero | `:686-702`, `:880-896`, nối ở `:1366`, `:1394` |
| Trạng thái rỗng, theo từng tab | CÓ, và tách hai ca: rỗng vì chưa có dữ liệu (nút tạo mới) ⟂ rỗng vì tìm không ra (nút xoá tìm kiếm) | `:704-731`, `:898-925` |
| Đang tải lại thì báo gì | `aria-busy` trên `<tbody>` + làm mờ 60%, có tôn trọng `motion-reduce` | `:680-684`, `:874-878` |
| Thanh tab đúng khuôn WAI-ARIA | CÓ — `role="tablist"` + 4 `role="tab"` mang `id`/`aria-selected`/`aria-controls`, tabIndex luân phiên, panel `role="tabpanel"` + `aria-labelledby`; phím ←/→ cuộn vòng, Home/End nhảy biên | `:1323-1327`, `:466-480`, `:1346`, `:1123-1134` |
| Vùng bấm đủ lớn | CÓ — nút X 24×24, dòng số liệu hero cao ~32px, đều có chú thích lý do trong mã | `:560`, `:266` |
| Tương phản chữ nền | ĐẠT — tôi **tự tính lại** từ mã màu trong `index.css`, không lấy số của người làm: `brand-700 #1b6aa3` + trắng = **5,77:1**; `teal-700 #0a7170` + trắng = **5,82:1**; `brand-800 #175685` trên trắng = **7,76:1**; chữ `text-white/90` trên nền `brand-700` = **5,01:1**. Cả bốn đều qua ngưỡng AA 4,5:1, và ba số đầu khớp đúng con số người làm khai. |
| Màu dùng có thật trong bảng màu | CÓ — mọi `var(--color-…)` gọi trong `dotVisual`/`STAGE_TONE` (`brand-500/600`, `teal-500/600`, `amberx-500`, `accent-500`) đều được khai trong `@theme` (`fe-user/src/index.css:5-52`), và file **không** có dòng `--color-*: initial` nào nên các nấc mặc định của Tailwind (`slate`, `red`, `emerald`, `violet-800`…) vẫn còn. Không có màu ma. |
Một khoản nhỏ không tính FLAG: khung cuộn ngang của bảng (`:658`, `:852` — `overflow-x-auto`) không nhận được tiêu điểm bàn phím, nên người chỉ dùng bàn phím không cuộn ngang được bằng phím mũi tên. Vì mọi cột tràn đều đã bị ẩn theo bề rộng nên gần như không xảy ra; ghi lại cho đủ.
---
## TRỤC 1 — khối bị owner khoanh đã biến hẳn chưa
Quét cây làm việc bằng các chuỗi nhận dạng của khối cũ (`Việc của tôi`, `gần đây`, `StatCard`, `STAT_TONE`, `SlaTimer`, `SectionTitle`, `Tổng giá trị nháp`, `đang soạn`, `Sắp quá hạn`, `my-contracts-recent`, `my-pe-recent`): chỉ còn **hai dòng chú thích** kể lại việc đã gỡ (`:12`, `:1034`) và ba trường của kiểu `MyDashboard` (`:94`, `:96`, `:98`) mô tả hình dạng dữ liệu backend trả về. **Không còn một dòng giao diện nào.** Đây là phân biệt dùng ⟂ nhắc-đến, không phải bỏ nửa vời.
Bằng chứng phụ mạnh hơn cả grep: `fe-user/tsconfig.app.json` bật `noUnusedLocals` và `noUnusedParameters`, mà `npx tsc --noEmit -p tsconfig.app.json` chạy sạch ⇒ **không còn import hay biến mồ côi nào** của khối đã gỡ (`StatCard`, `SlaTimer`, `SectionTitle`, các icon `Pencil`/`Inbox`/`Clock`/`Coins`). Nếu sót, trình biên dịch đã chặn.
**Hero còn nguyên:** 4 thẻ giai đoạn, mũi tên nối, 6 dòng số liệu, băng lỗi + nút "Thử lại", và toàn bộ 8 đích điều hướng cũ — đối chiếu HEAD ⟂ cây làm việc đều khớp. Thay đổi duy nhất là nhãn/biểu tượng nay đọc từ mảng `STAGES` thay vì viết tay trong JSX.
## TRỤC 4 — bản đồ trạng thái sang chấm có ca nào rơi không
Liệt kê vét cạn, không lấy mẫu:
- **Phiếu (`peDot`, `:401-408`)** đi qua `getPeDisplayStatus` (`types/purchaseEvaluation.ts:104-110`). Toàn bộ 10 giá trị phase khai trong `PurchaseEvaluationPhase` (`:21-32`) đều có chỗ đáp: `1` → nháp; `7` → xong; `98` → bị trả lại; `99` → dừng; `2,3,4,5,6,10` → đang chạy. **Không ca nào rơi**, nhánh mặc định là "đang chạy" — an toàn, vì mọi phase còn lại đều thật sự là phase trung gian.
- **Hợp đồng (`contractDot`, `:410-416`)** so thẳng với `ContractPhase` (`types/contracts.ts:1-14`): `9` → xong; `99` → dừng; `98` → bị trả lại; `1,2` → nháp; `3,4,5,6,7,8,10` → đang chạy. **Không ca nào rơi.**
Trục này ĐẠT về mặt "không rơi ca". Phần sai nằm ở chỗ khác — xem FLAG-2 (chấm của các giai đoạn KHÔNG phải giai đoạn của dòng đó bị gán nghĩa "chưa tới").
## Quan sát KHÔNG tính FLAG (ghi để khỏi bị đào lại)
- **Không có dấu vết giả lập.** Rà toàn bộ dòng thêm mới: 0 lần `// Mock`, 0 `alert(`, 0 `TODO/FIXME`, 0 `console.`. Bảy lần khớp chữ `placeholder` đều là thuộc tính ô tìm kiếm (dùng thật), không phải đánh dấu chỗ làm dối.
- **An toàn.** Không `dangerouslySetInnerHTML`, không `eval`, không địa chỉ máy chủ hay khoá cứng trong mã. Dữ liệu hai bảng lấy từ hai endpoint đã lọc theo người dùng ở tầng backend (`ContractFeatures.cs:297-303` và khối IDOR trong `PurchaseEvaluationFeatures.cs`) ⇒ bảng mới **không** mở rộng phạm vi dữ liệu người dùng được thấy so với trước, chỉ hiển thị nhiều dòng hơn của cùng phạm vi đó.
- **Tật cũ từ vòng-1 chưa xử (không tính là mới):** hai ô của giai đoạn 3 vẫn lấy số từ `/reports/my-dashboard` nhưng dẫn sang `/inbox` — hai nơi đếm theo hai luật khác nhau (vòng-1 FLAG-4). Owner đã biết, để lại là lựa chọn của owner.
- **Ba trường `draftsInProgress` / `dueSoon` / `draftsTotalValue`** trong kiểu `MyDashboard` không còn nơi nào đọc. Vô hại (nó tả hình dạng dữ liệu backend trả về, không phải mã chết chạy được). Nhưng đáng nói một câu: "Sắp quá hạn" nay không còn xuất hiện ở đâu trên trang chủ — đúng ý owner đã yêu cầu bỏ, chỉ ghi lại để owner biết mình đã mất cảnh báo đó.
## Chỗ chịu được soi (ghi lại vì có giá trị đối chứng lần sau)
- **Gom nhãn 4 giai đoạn về một mảng `STAGES`** khiến hero và thanh tab không thể lệch tên nhau nữa — đây là cách chữa đúng gốc cho một thứ đã trôi qua ba đợt sửa, không phải chép tay cẩn thận hơn.
- **Cách dùng lại `['pe-inbox']`**: giữ nguyên khoá và hàm tải, chỉ đếm bằng `select` ở phía quan sát. Đúng chỗ đúng cách — nếu đổi hàm tải thành trả `.length` dưới cùng khoá thì đã phá cache của trang Hộp thư. Trong mã có ghi rõ lý do cấm làm vậy (`:1066-1069`).
- **Bốn con số tương phản và toàn bộ mã màu**: tôi tự tính lại và tự tra `@theme`, cả hai đều đứng.
- **Khung xương khi đang tải dùng một ô `colSpan`** thay vì dựng đủ 7 ô — tránh được lỗi lệch cột khi cột bị ẩn theo bề rộng màn hình.
## Sai lệch giữa lời khai và đĩa (không phải lỗi mã, nhưng phải nói)
`sub-frontend-designer-dot4.md` §4 khai *"766 → **1.130 dòng**"*. Đo lại trên đĩa: **1.413 dòng** (`wc -l`), và phép cộng từ diff cũng ra đúng số đó: 766 337 + 984 = 1.413. Con số 1.130 lệch 283 dòng. Không ảnh hưởng gì tới mã, nhưng ai đọc sổ sau này mà trích lại con số đó thì trích phải số sai.
## Giới hạn của lượt soi này — khai thẳng
1. **Không kiểm chứng trực tiếp trên máy chủ.** Chưa triển khai, `/dashboard` lại nằm sau đăng nhập. Mọi kết luận về bố cục, tương phản thực tế, hành vi khi bấm đều là **suy luận từ mã**. Chưa có một khung hình nào được nhìn bằng mắt.
2. **Bản chạy `tsc` sạch của tôi yếu hơn vẻ ngoài của nó.** `fe-user/tsconfig.app.json` **không** bật `strict` cũng không bật `strictNullChecks` (tôi đã tra cả `tsconfig.json` gốc — chỉ có `references`, không có cờ nào). Nên "biên dịch sạch" ở đây chứng minh được: không import thừa, không biến mồ côi, tên thuộc tính có thật. Nó **không** chứng minh được an toàn null. Đây là cấu hình sẵn có của dự án, không phải chuyện của diff này.
3. **Hai điểm tôi không bác được bằng cách soi tĩnh:** mẹo `w-full max-w-0` để cắt chữ trong ô bảng có ăn đúng ở mọi bề rộng không, và dải 4 chấm có đọc ra "hành trình" bằng mắt thường không. Cần người nhìn màn hình thật.
---
## KẾT LUẬN
**Verdict: PASS_WITH_FLAGS — 7 FLAG (0 Nặng / 2 Vừa / 5 Nhẹ).**
Nói rõ vì sao không có mục nào xếp mức Nặng, để không ai hiểu là tôi hạ chuẩn cho hợp ý: tôi đã tìm đúng bốn lớp lỗi thuộc mức đó và không thấy lớp nào — dữ liệu hiển thị sai (đã đối chiếu từng tham số với controller và từng trường với DTO), lỗ hổng phân quyền (không có endpoint mới; hai endpoint đang dùng đều lọc theo người dùng ở backend), vỡ biên dịch (tự chạy `tsc`, sạch), và hồi quy so với HEAD (đã đo từng bản vá của vòng-1, còn nguyên cả sáu). Hai mục mức Vừa đều là chuyện đúng-sai về hành vi và câu chữ, sửa được trong vài dòng, và một trong hai là tật kế thừa chứ không phải do đợt này gây ra.
**Thứ tự nên sửa:** FLAG-1 (ngõ cụt trên điện thoại) → FLAG-2 (bỏ khẳng định "chưa tới") → FLAG-3 (phân trang tính từ state) → còn lại tuỳ owner.
**Bốn yêu cầu của đầu bài, đối chiếu từng cái:** khối bị khoanh đã bỏ hết (không sót dòng giao diện nào) · hero giữ nguyên (8 đích điều hướng khớp HEAD) · bảng chi tiết có đủ 7 cột cho giai đoạn 1 gồm cột Hành trình, có bảng hợp đồng cho giai đoạn 3 kèm nút "Tạo HĐ mới" ở trạng thái rỗng, giai đoạn 2 và 4 dẫn sang `/coming-soon?stage=N` (route có thật, và `STAGE_INFO` có đủ cả khoá `'2'` lẫn `'4'` nên không rơi về nhầm giai đoạn) · phân trang và tìm kiếm đều dựa trên tham số backend CÓ THẬT, không phải làm cho có.
<!-- END sub-reviewer-diff-dot4 · TOTAL=7 FLAG (0H/2M/5L) · verdict=PASS_WITH_FLAGS -->

View File

@ -0,0 +1,28 @@
# sub-reviewer — soi đối kháng diff đợt-5 (S159 L8)
**Vật soi:** `git diff` working-tree vs HEAD (`bbc39be`), 9 file trong scope (BE 4 + FE 5).
**Chế độ:** tĩnh (không live-verify, theo lệnh lead).
**Ghi từng FLAG NGAY khi chốt (chống #53).**
---
## Bản đồ file trong scope (đo từ `git diff --stat`)
| File | +/- |
|---|---|
| `src/Backend/SolutionErp.Api/Controllers/ContractsController.cs` | +12 |
| `src/Backend/SolutionErp.Application/Contracts/ContractFeatures.cs` | +55 |
| `src/Backend/SolutionErp.Application/PurchaseEvaluations/PeSoftDeleteFeatures.cs` | +1/-1 (comment) |
| `src/Backend/SolutionErp.Infrastructure/Persistence/DbInitializer.cs` | +33/-3 |
| `fe-user/src/App.tsx` | +2 |
| `fe-user/src/components/Layout.tsx` | +15/-3 |
| `fe-user/src/pages/UserDashboardPage.tsx` | +18/-339 |
| `fe-user/src/pages/contracts/MyContractsPage.tsx` | +32/-7 |
| `fe-user/src/pages/pe/WorkflowMatrixViewPage.tsx` | +4/-1 |
Ngoài scope lead khai (KHÔNG soi, chỉ ghi nhận có mặt trong working-tree): `.claude/agent-memory/cicd-monitor/MEMORY.md`, `.claude/commands/session-{start,end}.md`, `.claude/governance/.session-counter.json`, `docs/HANDOFF.md`, `docs/STATUS.md`, `scripts/governance-detectors.ps1`, `scripts/session_ctx.py`.
---
## FLAG (ghi dần)