From b6b9e689464c2add93a2f94c0351dee86936abf5 Mon Sep 17 00:00:00 2001 From: pqhuy1987 Date: Wed, 29 Jul 2026 12:47:37 +0700 Subject: [PATCH] wal: flush 20260729T1247 --- .claude/WAL.md | 30 ++- .claude/agent-memory/cicd-monitor/MEMORY.md | 6 +- .../cicd-verify-2a72695.md | 229 ++++++++++++++++++ .../run.md | 9 +- 4 files changed, 260 insertions(+), 14 deletions(-) create mode 100644 .claude/workflows/runs/2026-07-29-S159-tong-quan-pipeline-menu/cicd-verify-2a72695.md diff --git a/.claude/WAL.md b/.claude/WAL.md index d753ca2..311d7b6 100644 --- a/.claude/WAL.md +++ b/.claude/WAL.md @@ -1,18 +1,26 @@ # 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 UI 4-giai-đoạn (3 đợt) — đợt-1/2 SHIPPED; đợt-3 đang CI #424 (fix revoker + skeleton GĐ2/GĐ4) +goal: ✅ arc-2 UI 4-giai-đoạn ĐÓNG TRỌN (3 đợt, 3 commit, restart-proof) — còn closeout sổ bền chain: -- [x] bookend @open 5 vòng + (49)-(53) — XONG (run `bookend-open`) -- [x] Đ1 `b5799fc`: Tổng quan pipeline 4 GĐ — cicd **PASS 9/9**, bundle user `0GO8mgrG` -- [x] Đ2 `1a47a61`: tab strip PE (mirror md5) + menu 4-GĐ + 01-07 + đồng bộ nhãn — cicd **FAIL 8/9**: 8 mục đợt-2 sạch, mục 9 = phát hiện REVOKER -- [x] 🔴 SỰ CỐ: `RevokeTemporarilyHiddenModulesAsync` (nhánh [S92] "chỉ Admin thấy") UNGATED mỗi startup LẬT grant 38-key ở restart #423 (447/494 rơi, 11/13 role = 0). Owner ❹ hôm nay supersede S92 mà code còn thi hành quyết cũ. Chiều-ngược gotcha #75/#76. LỖI LEAD: grant SQL không audit revoker. -- [x] xử: re-grant SQL `regrant=447` (user sống lại ngay) + gỡ nhánh S92 khỏi revoker + reviewKeys grant bền + skeleton 12 row/perm 156 + ComingSoonPage ×2 + 3 test spec-change (có REGRESSION-GUARD) → 517/517 PASS → commit `2a72695` push -- [!] cicd verify `2a72695` — MỤC CHÍNH: restart-durability 38-key GIỮ 13/13/38 SAU success (nếu tụt lần nữa = còn nhánh revoke khác chưa tìm ra, FAIL to) +- [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) + +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: chờ cicd đợt-3 → nếu PASS: báo anh + closeout (HANDOFF chép ❹-risk verbatim + sự cố revoker + gotcha-mới candidate #84; STATUS CURRENT bổ sung arc-2; gotchas.md +1) → /session-end verify: -- restart-durability = phép PASS/FAIL của arc (khuôn S91 verify-by-restart-THẬT; đo SAU status=success — bài M7 cicd tự đính chính) -- 3 nhãn phiên-LOGIC: đừng chép "cicd FAIL 8/9" thành "đợt-2 hỏng" — 8 mục đợt-2 sạch, FAIL là regression NGOÀI phạm vi do restart lộ ra -- dọn dư-lượng spawn cuối wave: find stray .claude + empty-dir +- 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 diff --git a/.claude/agent-memory/cicd-monitor/MEMORY.md b/.claude/agent-memory/cicd-monitor/MEMORY.md index 5d16d04..f8c85ad 100644 --- a/.claude/agent-memory/cicd-monitor/MEMORY.md +++ b/.claude/agent-memory/cicd-monitor/MEMORY.md @@ -1,8 +1,10 @@ # CI/CD Monitor Agent — Persistent Memory +- **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` (FE-User: Tổng quan tất cả luồng duyệt — pipeline 4 GĐ) [S159 L8, FE-USER-ONLY 1 file-code, ghi-đĩa-suốt-lượt]:** **PASS 9/9** — Run **#422** (id=535) success **5m59s** (11:43:34→11:49:33+07); gate **562 EXACT** (45 D + **517** I, `Failed!`×0 toàn log). NO-Mig đúng kỳ vọng (diff `src/Backend/*`+`*Migrations*`=0 ⇒ KHÔNG query DB; Mig 68/89-table). Range **8 commit/37 file** (7 `wal:`) — chỉ `fe-user/src/pages/UserDashboardPage.tsx` thoát 7-glob ⇒ CI fire đúng. 🔑 **KỸ THUẬT MỚI — marker PHÂN-BIỆT-PHIÊN-BẢN (mạnh hơn marker-hiện-diện):** bả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 prod có **`Toàn trình`=1 & `Pipeline`=0** ⟹ chứng prod chạy bản ĐÃ-FIX, không chỉ 'có nội dung mới'. 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ư nằm ở COMMENT đầu file (minifier strip) ⇒ so bundle với **occurrence-CODE**, đừng so grep-source thô. ⚠️ **Bẫy hình-dạng-truy-vấn (suýt hô cờ đỏ):** control `Pe` ra 182/182 vs kỳ vọng 197/185 — do tao dùng `LEFT(MenuKey,3)='Pe_'` còn gốc là `LIKE 'Pe%'` (+`Permissions`1/1+`Personal`13/1+`PeWorkflows`1/1=+15/+3). **LỆCH-số ⇒ truy PHẠM-VI-QUERY trước khi kết luận drift.** Bundle: user js `DptYR4wL`→**`0GO8mgrG`** css `BRCvEHvC`→`BARVqkP-` (1.584.514B, LM 11:49:05 in-window); admin js `8-QYcMOg`→`BHodkPNV` css `DiNJjaoo` FROZEN (#69 mode-A + REFINE). Control 2 tầng: fake-hash & **hash CŨ** đều `ct=text/html` 876B ⇒ bundle cũ biến mất = chứng ghi-đè. Smoke health live+ready 200 + 2 FE 200. **M7 menu-data: 3 root IsVisible=0 · 38-key grant min=max=13 · `Pe%` 197/185 — ⚠️ ĐÍNH CHÍNH (đợt-2 bắt): đo lúc 11:46 tức TRƯỚC pool-restart ~11:49 ⇒ KHÔNG chứng minh được 'sống sót recycle'; phần 38-key sau đó ĐÃ MẤT (xem entry `1a47a61`).** Evidence: `runs/2026-07-29-S159-tong-quan-pipeline-menu/cicd-verify-b5799fc.md`. Tag `[run-422, pass-9-9, marker-phan-biet-phien-ban, comment-strip-trap, query-shape-not-drift, 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]` - _(**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 - **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 có thể ROTATE vừa có 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 có đổi thật → diff `fe-*/src` trong range. - **⚠️ SPA-fallback 200 trap (S72):** server rewrite `/*`→`index.html` ⇒ GET `/assets/index-.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, không đo trước** — app-pool restart ở CUỐI deploy-window ⇒ query DB lúc `running` = trạng thái CŨ. Và `RevokeTemporarilyHiddenModulesAsync` (`DbInitializer.cs`) UNGATED mỗi startup revoke non-Admin trên `Contracts`/`Ct_*`/`Master`/`Suppliers`/`Projects`/`Departments`/`Catalog*`/`Hrm*`/`Off*`/`Personal` ⇒ **mọi grant SQL tay trên các key này CHẾT ở deploy kế** (S159 #423: 494→47 canread). Data-change ngoài CI phải re-verify SAU status=success. +- **🔴 Durability: đo SAU recycle + 3 CHÂN KIỀNG** — pool restart ở CUỐI deploy-window ⇒ query DB lúc `running` = trạng thái CŨ (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ệ vì 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. --- diff --git a/.claude/workflows/runs/2026-07-29-S159-tong-quan-pipeline-menu/cicd-verify-2a72695.md b/.claude/workflows/runs/2026-07-29-S159-tong-quan-pipeline-menu/cicd-verify-2a72695.md new file mode 100644 index 0000000..4ce1738 --- /dev/null +++ b/.claude/workflows/runs/2026-07-29-S159-tong-quan-pipeline-menu/cicd-verify-2a72695.md @@ -0,0 +1,229 @@ +# cicd-verify `2a72695` — S159 ĐỢT-3 "gỡ nhánh S92 khỏi revoker + skeleton menu GĐ2/GĐ4 + ComingSoonPage" + +> 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, FAIL mục-9 durability) — 2 file cũ KHÔNG chạm. +> Bắt đầu: 2026-07-29 ~12:35+07 · cicd-monitor (READ-only) +> +> 🎯 **Mục 6(a) là phép PASS/FAIL của cả arc**: grant 38-key có SỐNG SÓT restart không. +> Đúng bài tao tự đính chính đợt-2: **phải đo SAU `status=success` VÀ SAU thời điểm pool recycle**. + +--- + +## M1 — Push + phạm vi · ✅ PASS + +``` +git log -1 → 2a7269581c96bd0d3ce939b4dceaeb847df76a89 + [CLAUDE] Infra: go nhanh S92 khoi revoker (owner supersede: hien thi het) + + skeleton menu GD2/GD4 + ComingSoonPage x2 · 2026-07-29 12:34:15 +0700 +git log origin/main..HEAD → EMPTY (đã push) +``` +Range `1a47a61..2a72695` = **15 file, 466+/45−**. Trong đó **9 file CODE**: + +| File | vai trò | +|---|---| +| `src/Backend/…/Persistence/DbInitializer.cs` | 🔴 gỡ nhánh [S92] khỏi revoker + grant bền vào reviewKeys | +| `tests/…/Application/AdminOnlyModulesRevokeTests.cs` | REGRESSION-GUARD mới | +| `tests/…/Application/ProcurementMasterAccessSeedTests.cs` | cập nhật theo spec-change | +| `fe-admin/src/pages/ComingSoonPage.tsx` · `fe-user/…/ComingSoonPage.tsx` | **MỚI** ×2 | +| `fe-admin/src/App.tsx` · `Layout.tsx` · `fe-user/src/App.tsx` · `Layout.tsx` | route + menu 2 app | + +6 file còn lại = `.claude/**` (memory + run-trace, ignored bởi 7-glob). +**Cả 2 app FE đều sửa ⇒ bundle 2 app đều phải rotate.** + +## M2 — Migration VẮNG là ĐÚNG · ✅ PASS +``` +git diff --name-only 1a47a61..2a72695 -- '*Migrations*' → 0 +``` +BE-change = seeder + revoker logic (runtime), không sinh schema. Mig giữ **68** / 89 bảng. + +## 📌 BASELINE TRƯỚC RESTART (đo 12:35:47+07, run #424 vừa start — pool CHƯA recycle) + +Đây là "vế trước" của phép durability. Ghi lại để đối chứng sau restart: + +| Phép đo | Giá trị TRƯỚC restart | +|---|---| +| 38-key set (per-key canread) | **min=13 max=13 keys=38** ✅ (lead re-grant 447 đã ăn) | +| `Khkk_%` permission rows | **65** (= 5 key × 13 role) | +| `Hdc_%` permission rows | **91** (= 7 key × 13 role) | +| Tổng skeleton | **156** ✅ khớp con số lead báo | +| Control âm `Hrm%`/`Off%`/`Personal` non-Admin canread | **0** | + +Bundle trước deploy: admin js `D6Pz7nfV` css `B1ErASuI` · user js `Bpb8WEuY` css `7oAYFlzM` +— khớp CHÍNH XÁC baseline hậu-đợt-2 ⇒ baseline hợp lệ, đợt-3 chưa land. + +## M3 — Poll terminal · ✅ PASS +- **id=537 · run_number=424 · head_sha `2a726958`** · created 12:34:22 → **`status=success` 12:40:18** +- **Duration 5m56s** (đợt-1 5m59s · đợt-2 5m44s) — ổn định. + +### M3b — Test gate · ✅ 562 EXACT +``` +Passed! - Failed: 0, Passed: 45, Skipped: 0, Total: 45, 200 ms - SolutionErp.Domain.Tests.dll +Passed! - Failed: 0, Passed: 517, Skipped: 0, Total: 517, 1m36s - SolutionErp.Infrastructure.Tests.dll +``` +`Failed!` ×0. **45+517 = 562** — giữ nguyên, đúng kỳ vọng: 3 test được **SỬA theo spec-change** +(không thêm test mới) ⇒ tổng không đổi. Regression-guard nằm TRONG test cũ. + +**Nội dung guard (đọc diff, không suy đoán)** — `AdminOnlyModulesRevokeTests.cs` tách 2 vế: +- `StillHiddenKeys` = `Hrm` / `Off` / `Personal` → sau revoke **PHẢI** `CanRead=false` +- `ReopenedKeys` = `Contracts`/`Ct_ThauPhu`/`Master`/`Suppliers`/`Catalogs`/`CatalogWorkItems` + → sau revoke **PHẢI GIỮ** `CanRead=true` +⇒ ai re-add nhánh `[S92]` vào predicate là **test ĐỎ ngay**. Guard có RĂNG thật (assert 2 chiều). + +Và `DbInitializer.cs`: nhánh `|| p.MenuKey == MenuKeys.Contracts || p.MenuKey.StartsWith("Ct_") +|| Master/Suppliers/Projects/Departments/Catalog*` đã **GỠ khỏi predicate revoker**; reviewKeys +`+ContractMenuKeys() +MasterMenuKeys() +KhkkKeys() +HdcKeys()`. +⚠️ Ghi chú code nêu rõ nhánh grant là **skip-existing** ⇒ trên prod row `false` KHÔNG tự nâng; +prod dựa vào re-grant SQL tay (đã chạy) **+** revoker không lật nữa. Đúng: phép 6(a) dưới đây kiểm chính điều đó. + +## M4 — Bundle rotate · ✅ PASS 4/4 (đo 12:40:45+07, sau status=success) + +| App | asset | TRƯỚC | SAU | KQ | +|---|---|---|---|---| +| admin | js | `D6Pz7nfV` | **`CC1pGE1F`** | ✅ | +| admin | css | `B1ErASuI` | **`DWDbm5As`** | ✅ | +| user | js | `Bpb8WEuY` | **`DeAg7MWm`** | ✅ | +| user | css | `BQIZ-rUe` ← `7oAYFlzM` | **`BQIZ-rUe`** | ✅ | + +Cả 2 app đều sửa (App.tsx + Layout.tsx + ComingSoonPage.tsx mới) ⇒ rotate 4/4 nhất quán với diff. + +--- + +# 🔴 M6 — RESTART-DURABILITY (mục CHÍNH của cả arc) · ✅ **PASS** + +## Bước 0 — CHỨNG MINH pool ĐÃ recycle THẬT (không đo sớm như đợt-2) + +| Bằng chứng | Giá trị | +|---|---| +| `w3wp` PID **5508** StartTime | **12:39:56** — nằm trong cửa sổ deploy (12:34:22→12:40:18) | +| `SolutionErp.Api.dll` LastWriteTime | **12:37:38** — binary mới đã copy | +| Dòng ĐẦU của `logs\solution-erp-20260729.log` | **`2026-07-29 12:40:03.239 +07:00 [INF] Applying migrations...`** | +| Log LastWriteTime | 12:40:37 | + +🔑 Vì step deploy `Remove-Item …\api\*` **xoá cả `logs/`**, việc dòng đầu log là **12:40:03** chứng minh +file được tạo lại SAU deploy ⇒ đây đúng là **lần khởi động mới**, và `DbInitializer` (seeder + revoker) +đã chạy trong lần đó. **Mọi số dưới đây đo SAU thời điểm này** — sửa đúng lỗi đo-sớm của đợt-2. + +## (a) 38-key set — PHÉP PASS/FAIL CỦA CẢ ARC · ✅ **PASS** + +``` +A_POST38 min=13 max=13 keys=38 totcr=494 +``` + +| Mốc | min/max | tổng canread | +|---|---|---| +| Đợt-2 SAU restart (#423) — **hỏng** | 1 / 2 | **47** / 494 | +| Đợt-3 TRƯỚC restart (12:35:47, sau re-grant tay) | 13 / 13 | 494 / 494 | +| **Đợt-3 SAU restart (#424, đo ~12:41)** | **13 / 13** | **494 / 494** ✅ | + +⇒ **Grant 38-key SỐNG SÓT app-pool recycle.** Chính phép đo này ở đợt-2 cho ra 0 key còn 13; +nay giữ nguyên 38/38 key ở 13/13. **Fix của lead ăn thật, không phải trùng hợp** — vì: +(i) đã chứng minh có restart thật ở Bước 0, (ii) predicate revoker đã gỡ nhánh S92, (iii) control (c) cho thấy +hàm revoker **vẫn chạy** chứ không chết. + +## (b) Skeleton GĐ2/GĐ4 · ✅ PASS +``` +B_SKEL Khkk=65 Hdc=91 tot=156 (Permissions rows) +B2_SKELCR min=13 max=13 keys=12 (mỗi key đủ 13 role canread) +``` +65 = 5 key × 13 · 91 = 7 key × 13 · **tổng 156** ✅ khớp con số lead báo; **12 key** × 13 = 156 nhất quán. + +## (c) Control ÂM — revoker CÒN SỐNG · ✅ PASS +``` +C_CTRL hrmoff_nonadmin_cr = 0 +``` +`Hrm%`/`Off%`/`Personal` với role ≠ Admin vẫn **canread = 0** ⇒ gỡ nhánh S92 **KHÔNG làm chết cả hàm**; +vế `StillHiddenKeys` vẫn thực thi đúng. (Đây là phép chống "Goodhart": nếu ai đó vô hiệu hoá luôn +revoker thì (a) cũng xanh — nhưng (c) sẽ đỏ. (a) xanh **và** (c) xanh mới là fix ĐÚNG.) + +## (d) Control `Pe_%` · ✅ PASS +``` +D_PE rows=197 cr=185 (dùng LIKE 'Pe%' — đúng hình-dạng query gốc) +``` + +## M7 — Log seeder sau restart · ✅ PASS (khai đầy đủ) +`findstr "Revoked"` trên log mới ⇒ **KHÔNG có dòng nào**. Đây là kết quả ĐÚNG chứ không phải thiếu dữ liệu: +`RevokeTemporarilyHiddenModulesAsync` chỉ ghi log khi `rows.Count > 0`; nay tập ẩn chỉ còn +Hrm/Off/Personal và chúng **đã ở canread=0 từ trước** ⇒ 0 row cần lật ⇒ im lặng. Khớp control (c)=0. +**So đợt-2: 447 row bị lật → nay 0.** + +--- + +## M5 — Byte-marker ComingSoonPage trên 2 bundle · ✅ PASS + +Source `ComingSoonPage.tsx` **2 app đều 2.553 B** (mirror): chứa `Phân hệ đang được triển khai` ×1 ++ `Quay lại` ×1; **không** chứa `Sắp triển khai`. + +| Chuỗi | USER bundle | ADMIN bundle | KQ | +|---|---|---|---| +| `Phân hệ đang được triển khai` | **1** | **1** | ✅ dương cả 2 (chính là ComingSoonPage mới) | +| `Quay lại` | **4** | **4** | ✅ dương cả 2 | +| `Sắp triển khai` | 2 | 1 | ✅ dương cả 2 (nhãn GĐ có sẵn, không phải ComingSoonPage) | + +Artifact thật + in-window: +``` +USER index-DeAg7MWm.js ct=application/javascript size=1.587.996 LM=12:39:51+07 +ADMIN index-CC1pGE1F.js ct=application/javascript size=1.678.733 LM=12:38:56+07 +``` +Control âm: hash CŨ (`Bpb8WEuY` / `D6Pz7nfV`) đều rơi về `ct=text/html` 876 B / 900 B +⇒ bundle đợt-2 đã biến mất khỏi đĩa = chứng ghi-đè thật. + +## M8 — Smoke · ✅ PASS 4/4 +``` +/health/live 200 Healthy · /health/ready 200 Healthy · admin 200 · eoffice 200 +``` + +## M9 — Menu-tree skeleton · ✅ PASS 5/5 + +``` +MENU Khkk=5 Hdc=7 (MenuItems rows) +ROOT=12 DUPKEYS=0 +Hrm=0 Off=0 Personal=0 (IsVisible) +NUM(label '01.'-'07.')=14 → NUMSPLIT ct=7 hdc=7 other=0 +``` + +Cấu trúc `Khkk_*` đúng phân cấp thiết kế: +``` +KeHoachKyKet (root) +└── Khkk_G1 ord1 "1. Kế hoạch ký kết HĐ (NCC-TP)" + ├── Khkk_List ord1 + ├── Khkk_Create ord2 + ├── Khkk_Pending ord3 + └── Khkk_Approved ord4 +``` + +🔎 **Giải-trình con số 14 (đừng đọc nhầm thành drift):** kỳ vọng ban đầu là 7 nhãn `01.`-`07.`, +đo ra **14**. Tách theo prefix key: **`Ct_`=7 + `Hdc_`=7 + other=0**. Đúng thiết kế — diff ghi rõ +GĐ4 *"tái dùng đúng nhãn đã đánh số 01.-07."* (`tree.Add(("Hdc_"+code, label, …))` dùng chung +`typeLabels`). Vậy 14 = 7 gốc + 7 mirror, **không** phải nhân đôi nhầm (DUPKEYS=0, root vẫn 12). + +--- + +## VERDICT: **PASS — 9/9** + +| # | Mục | KQ | +|---|---|---| +| 1 | Push + range 15 file (9 code), CI fire đúng | ✅ | +| 2 | Migration VẮNG là ĐÚNG (0 file `*Migrations*`) | ✅ | +| 3 | Run #424 success 5m56s | ✅ | +| 4 | Test gate **562** (45+517, 0 fail) + regression-guard 2 vế có RĂNG | ✅ | +| 5 | Bundle rotate **4/4** (2 app đều sửa) + control hash-cũ `text/html` | ✅ | +| 6 | Marker ComingSoonPage dương **cả 2 bundle** + `Quay lại` ×4 | ✅ | +| 7 | Smoke health ×2 + FE ×2 | ✅ | +| 8 | Menu-tree: Khkk=5 · Hdc=7 · root=12 · 3 root vis=0 · nhãn 7+7 · DUPKEYS=0 | ✅ | +| 9 | 🔴 **RESTART-DURABILITY: 38-key = 13/13/38 (494/494) SAU restart THẬT** | ✅ **PASS** | +| | ├ (b) skeleton 156 perm, 12 key × 13 | ✅ | +| | ├ (c) control âm Hrm/Off/Personal non-Admin = **0** (revoker còn sống) | ✅ | +| | └ (d) `Pe%` 197/185 | ✅ | + +### 🎯 Kết luận arc 3 đợt +Regression tao bắt ở đợt-2 (mục 9, 447/494 row rơi) **đã đóng**. Bằng chứng đóng KHÔNG dựa vào +"CI xanh" mà dựa vào **3 chân kiềng**: (i) chứng minh có restart THẬT (w3wp 12:39:56 + log mở lại +12:40:03), (ii) 38-key giữ **13/13/38** sau restart đó, (iii) control âm (c) chứng minh revoker +**vẫn chạy** — nên (ii) không phải do vô hiệu hoá cả hàm. Kèm regression-guard trong test: +re-add nhánh `[S92]` ⇒ CI đỏ, không cần trông vào trí nhớ. + +Baseline mới: admin js `CC1pGE1F` / css `DWDbm5As` · user js `DeAg7MWm` / css `BQIZ-rUe` · +gate 562 · Mig 68 / 89 bảng · menu root 12 · 38-key 13/13. + + + diff --git a/.claude/workflows/runs/2026-07-29-S159-tong-quan-pipeline-menu/run.md b/.claude/workflows/runs/2026-07-29-S159-tong-quan-pipeline-menu/run.md index f42ff93..a4868ac 100644 --- a/.claude/workflows/runs/2026-07-29-S159-tong-quan-pipeline-menu/run.md +++ b/.claude/workflows/runs/2026-07-29-S159-tong-quan-pipeline-menu/run.md @@ -70,7 +70,14 @@ - [x] **Đ3-c — spec-change 3 test** (S92→S159, *"update test cũ + code chung commit"*): `AdminOnlyModulesRevokeTests` tách `StillHiddenKeys`(3) ⟂ `ReopenedKeys`(8) + ⭐ **REGRESSION-GUARD**: re-add nhánh S92 vào predicate ⇒ test đỏ ngay · `ProcurementMasterAccessSeedTests` ×2 đọc lại isolation bằng **CỜ CAO** (CanRead all-role giữ true). **517/517 PASS lại.** - [x] **Đ3-d — commit `2a72695` push** (9 file, 234+/27−) -- [!] **Đ3-e — cicd verify `2a72695`**: mục CHÍNH = **restart-durability 6(a)** — 38-key phải GIỮ 13/13/38 **SAU** `status=success` (đúng bài M7); control âm 6(c) Hrm/Off/Personal vẫn 0. +- [x] **Đ3-e — cicd verify `2a72695`: PASS 9/9 — ARC ĐÓNG** (`cicd-verify-2a72695.md`). 🔴 **Mục 6(a) phép PASS/FAIL của arc: ĐÓNG bằng 3 chân kiềng** (cicd tự dựng, không nhận-vì-xanh): ① restart THẬT có chứng (`w3wp` PID 5508 StartTime **12:39:56** trong cửa sổ deploy + log boot-mới `12:40:03` — dùng được vì deploy xoá `logs/` ⇒ log mở lại = boot mới + DLL mtime 12:37:38) ② đo SAU mốc → **494/494 canread, 38 key min=max=13** (đợt-2 chính truy vấn này = 47/494) ③ **control âm chống-Goodhart**: Hrm/Off/Personal non-Admin vẫn **= 0** ⇒ revoker CÒN SỐNG — xanh không phải do giết cả hàm (*"fix kiểu xoá luôn checker cũng xanh y hệt"*). + · Run #424 success 5m56s · gate 562 · bundle 4/4 rotate + hash cũ rơi text/html · marker ComingSoonPage 2/2 bundle · Khkk=5 · Hdc=7 · root=12 · DUPKEYS=0 · Pe% 197/185. + · 2 số tự-giải-trình (không phải drift): nhãn `01.-07.` = **14** = `Ct_`7 + `Hdc_`7 đúng thiết kế tái dùng · log **0 dòng `Revoked`** = đúng (hàm chỉ log khi count>0; đợt-2 là 447). + · ⚠️ **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. + + + - [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) · Verify: **38/38 key min=max=13 canread** · 3 root `IsVisible=0` · control âm **Pe_* 197/185 NGUYÊN** (❹ "để như cũ") · `Hrm_Config*` canread=7 nguyên (vùng đang-làm loại khỏi grant, đúng ý ❸)