From a314103ac3eb66d9c99eb6e92202a235a8b6a659 Mon Sep 17 00:00:00 2001 From: pqhuy1987 Date: Wed, 29 Jul 2026 12:36:26 +0700 Subject: [PATCH] wal: flush 20260729T1236 --- .claude/WAL.md | 23 +- .claude/agent-memory/cicd-monitor/MEMORY.md | 13 +- .../cicd-verify-1a47a61.md | 231 ++++++++++++++++++ .../run.md | 21 +- 4 files changed, 270 insertions(+), 18 deletions(-) create mode 100644 .claude/workflows/runs/2026-07-29-S159-tong-quan-pipeline-menu/cicd-verify-1a47a61.md diff --git a/.claude/WAL.md b/.claude/WAL.md index 976eeb1..d753ca2 100644 --- a/.claude/WAL.md +++ b/.claude/WAL.md @@ -1,19 +1,18 @@ # 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 — Tổng quan pipeline 4 GĐ + menu eOffice (anh chốt 4 quyết, run `2026-07-29-S159-tong-quan-pipeline-menu`) +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) chain: -- [x] bookend @open 5 vòng + vá spec KHKK + (49)-(53) — XONG (run `2026-07-29-S159-bookend-open`, xem WAL cũ trong git) -- [x] anh giao arc-2 + đổi lead → Fable 5; em đọc lại đĩa (S156 21-bước + spec KHKK) → báo → anh chốt 4 câu (❶ ranh GĐ ❷ pipeline 4 GĐ ❸ ẩn 3 nhóm ❹ mở HĐ+DM không vá authz — verbatim trong run.md) -- [x] T2 prod LIVE: hide=3 upd=99 ins=376 · verify 38/38=13 canread · Pe_* 197/185 NGUYÊN · Hrm_Config* nguyên -- [x] T3 spec đổi nhãn "Đề xuất ký kết hợp đồng" + ranh GĐ + Q8-ĐÓNG (2 site) -- [!] T1 frontend-designer đang chạy background (redesign UserDashboardPage → pipeline 4 GĐ, fe-user only) -- [ ] T4: designer về → đọc đĩa sub-file → build fe-user → reviewer diff → commit/push → cicd verify - -next: chờ T1 return → verify RUỘT (chống skeleton) → build → reviewer (diff >50 LOC) → commit [CLAUDE] FE-User → push → cicd-monitor +- [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) +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: -- prod menu đảo được: `UPDATE MenuItems SET IsVisible=1 WHERE [Key] IN ('Hrm','Off','Personal')` + revoke = xoá row INSERT (backup pre-apply có số liệu gốc) -- 🔴 ❹ = rủi-ro CÓ Ý THỨC owner (mở HĐ khi authz trần) — ghi run.md, PHẢI chép vào HANDOFF lúc closeout -- dọn dư-lượng spawn sau wave: `find . -mindepth 2 -type d -name .claude` + `find .claude -type d -empty` +- 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 diff --git a/.claude/agent-memory/cicd-monitor/MEMORY.md b/.claude/agent-memory/cicd-monitor/MEMORY.md index 497d1a9..5d16d04 100644 --- a/.claude/agent-memory/cicd-monitor/MEMORY.md +++ b/.claude/agent-memory/cicd-monitor/MEMORY.md @@ -1,6 +1,8 @@ # CI/CD Monitor Agent — Persistent Memory -- **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 (data-change NGOÀI CI, verify sống sót recycle): 3 root IsVisible=0 · 38-key grant min=max=13 · `Pe%` 197/185.** 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 `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]` - _(**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`)_ @@ -33,6 +35,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. - **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. --- @@ -61,10 +64,10 @@ cicd-lane đôi khi trả nhầm slice transcript (memory-curation) thay verdict - **Prod:** api/admin/eoffice `.solutions.com.vn` · SSH `ssh vietreport-vps` (Administrator, id_ed25519) · IIS phys paths: API `C:\inetpub\solution-erp\api` · admin `\fe-admin` · user `\fe-user`. DB `.\SQLEXPRESS`/`SolutionErp`/`vrapp` SQL-auth. **Conn key = `ConnectionStrings.Default` (NOT `DefaultConnection`)**. DB pw = `buKL3TGBkD0wDDbYVw65QeX9` (User Id=`vrapp`; ⚠️ Password field has NO `vrapp/` prefix — S89 login-failed when prefix included; read verbatim from prod `appsettings.Production.json` ConnectionStrings.Default when `$env:PROD_DB_PASSWORD` empty). ⚠️ skill-doc path `C:\inetpub\apps\SolutionErp\Api` STALE. - **SSH→PS quoting (S42):** nested bash→ssh→powershell mangles `$var`/`\"` → use `iconv UTF-16LE | base64` → `powershell -EncodedCommand $B64`; OR write ps1-file + `powershell -File $(cygpath -w)`. sqlcmd over SSH direct `-W -h -1` pw-inline. sys-catalog string-concat → `COLLATE DATABASE_DEFAULT` (collation conflict). - **Tests baseline:** **562 PASS** (45 Domain + 517 Infra · 0 fail/skip; run #422 `b5799fc` 07-29; khớp canonical `docs/STATUS.md`). 🔑 **Số cụ thể lấy ở web-UI log `https://git.baocaogiaoduc.vn/vietreport-admin/solution-erp/actions/runs/{run_number}/jobs/0/logs` (~18KB, KHÔNG có `/api/v1/` — api/v1 runs+tasks logs đều 404)** → mỗi project 1 dòng `Passed! - Failed: 0, Passed: N, Skipped: 0`; đối chiếu TÁCH-PHẦN (45/517) chứ đừng chỉ khớp tổng. Delta test mới = so run trước. -- **Mig latest repo: Mig 68 `AddPeAllowApproverDelete`** (`20260727033522`, S155 `b1bae77` — AddColumn `AllowApproverDelete` bit NOT NULL def-0 trên **`ApprovalWorkflowLevels`**, no-new-table; VERIFIED-APPLIED-PROD history-top + col-present + sys.tables **89**). Prev Mig 67 `AddPeApprovedBudgetSnapshot` / 66 `AddPeApprovedPriceRange` / 65 backfill-invariant / 62 `ExpandSupplierFields` (full table → docs/CLAUDE.md). Path `src/Backend/SolutionErp.Infrastructure/Persistence/Migrations/` (68 `.cs` excl Designer/Snapshot). Prod check `sqlcmd __EFMigrationsHistory ORDER BY MigrationId DESC`. ⚠️ **`sys.tables`(is_ms_shipped=0)=89** — AddColumn/data-only/enum-additive don't change count; no-new-table commit ⟹ 89 correct, don't FAIL. **NO-Mig proof = history NOT-advance + empty `git diff .. -- '*Migrations*'`.** -- **Bundle hash live:** ship-baseline = run #422 (07-29) admin js `BHodkPNV`/css `DiNJjaoo` · eoffice js `0GO8mgrG`/css `BARVqkP-` (user real-size 1.584.514B, LM 11:49:05+07). ⚠️ Đây là **SNAPSHOT-per-run, KHÔNG phải mốc ổn định** (#69 TWO-MODE) → LUÔN re-snapshot LIVE sau status=success; ship-proof = LM-in-window + real-size + marker, KHÔNG phải hash-delta. ⚠️ id ≠ run_number (key theo head_sha). +- **Mig latest repo: Mig 68 `AddPeAllowApproverDelete`** (`20260727033522`, S155 — AddColumn bit trên `ApprovalWorkflowLevels`, no-new-table; VERIFIED-APPLIED-PROD). Path `src/Backend/SolutionErp.Infrastructure/Persistence/Migrations/` (68 `.cs` excl Designer/Snapshot). Prod check `sqlcmd __EFMigrationsHistory ORDER BY MigrationId DESC`. ⚠️ **`sys.tables`(is_ms_shipped=0)=89** — AddColumn/data-only/enum-additive KHÔNG đổi số; no-new-table commit ⟹ 89 đúng, đừng FAIL. **NO-Mig proof = history NOT-advance + empty `git diff .. -- '*Migrations*'`.** Bảng mig đầy đủ → skill `ef-core-migration` + `docs/STATUS.md`. +- **Bundle hash live:** ship-baseline = run #423 (07-29 đợt-2) admin js `D6Pz7nfV`/css `B1ErASuI` · eoffice js `Bpb8WEuY`/css `7oAYFlzM`. ⚠️ **SNAPSHOT-per-run, KHÔNG phải mốc ổn định** (#69 TWO-MODE) → LUÔN re-snapshot LIVE sau status=success; ship-proof = LM-in-window + real-size + marker, KHÔNG phải hash-delta. ⚠️ id ≠ run_number (key theo head_sha). - **Bearer:** admin `admin@solutions.com.vn/Admin@123456` (full) · UAT `nv.test@solutions.com.vn/TestUser@123456` (Drafter CCM, gotcha #44 check). -- **API Serilog log (S118 correction):** real sink = `C:\inetpub\solution-erp\api\logs\solution-erp-YYYYMMDD.log` (lowercase `logs`, rolling daily). ⚠️ skill-runbook path `…\Logs\log-YYYYMMDD.txt` STALE → empty grep. MinimumLevel Default=Information (DbInitializer `LogInformation` seed/revoke lines DO persist) — Microsoft/System overridden Warning (no "Now listening" INFO). **Startup seed-race audit pattern (durability deploys):** grep this log for revoke-line THEN seeder-line same-second → "N added + M upgraded" proves seeder actively flipped rows revoke knocked down (0-upgraded = seeder no-op = FAIL flag). +- **API Serilog log:** sink THẬT = `C:\inetpub\solution-erppi\logs\solution-erp-YYYYMMDD.log` (lowercase `logs`; skill-runbook path `…\Logs\log-*.txt` STALE → grep rỗng). Default=Information (DbInitializer seed/revoke lines CÓ ghi). ⚠️ **deploy `Remove-Item …pi\*` xoá luôn `logs/`** ⇒ chỉ còn từ lần deploy cuối, không truy ngược restart trước. Dùng grep revoke-line + seeder-line cùng-giây để audit seed-race. ## 🔑 Critical config (flag commit nếu tái xuất) Node CI `20.x` (`feedback_node_cicd`) · MediatR `12.4.1` (gotcha #1, flag `Version="14`) · Swashbuckle `6.9.0` (gotcha #2) · act_runner manual checkout (#39) · npm cache DISABLED (#40, flag `cache: npm`) @@ -82,7 +85,7 @@ BE (test+build) ~90s · FE × 2 ~60s/app · deploy ~30s · **total ~3min code / ## 📅 Recent runs (compressed — verbatim → archive via `_INDEX.md`) - _(**S98-S132 #383-#397/#485-#496 — GOVERNANCE/FE/SECURITY streak, all GO** (gate leo 440→509): 🔑 bài-học còn dùng — **AS-6 #41 soi TỪNG file vs 7-glob** (`.ps1`/`.json` thoát `scripts/**.md` ⇒ governance non-.md VẪN trigger CI; memory-push thì HẾT trigger từ #495) · `conclusion:None` dù success ⇒ tin `status` · **411-refine**: endpoint MỚI bodyless-POST trả **411** (IIS Length-Required, pre-auth) KHÔNG phải 404 → re-probe CÓ body, 401/415 ⇒ đã wire · bare `/health`=404 UNMAPPED, thật là `/health/live`+`/ready` (đừng FAIL nhầm) · **LM mới hơn cửa-sổ đã verify = detector 'có run mình bỏ sót'** · Mig 63/64/65/66 + Mig 67 đều APPLIED-PROD, 89-table giữ · verbatim → `archive/2026-07.md` via `_INDEX`)_ -- _(**PASS-pattern library S69b→S104 #307-#494** all GO — 🔑 reusable verify-recipes: **SCHEMA-ADD+DATA-UN-CRAM** [#488 Supplier 9→27 Mig62: AddColumn×18-exact + 89-invariant + un-cram 4-NCC + DTO-live] · **DATA-FIX-BACKFILL** [#484 Mig61: Mig-top==HEAD + target-rows-flip-EXACT + control-untouched + derived-API-FLIP-live] · **DATA-SEED-PASS** [#477: pre/post-DB-row-delta + DLL-mtime + w3wp-recycle]; #482 Mig60 EF-ternary-SQL 200≠500; #472 PROD Identity NON-prefix `Roles`; #470 no-resurrect DB-count-diff; Mig54-62 applied-prod; #348 411→401 · verbatim → git + `2026-06/07.md`)_ +- _(**PASS-pattern library S69b→S104 #307-#494** all GO — recipe tái dùng: **SCHEMA-ADD+DATA-UN-CRAM** [#488 Supplier 9→27 Mig62: AddColumn×18-exact + 89-invariant + DTO-live] · **DATA-FIX-BACKFILL** [#484 Mig61: Mig-top==HEAD + target-rows-flip-EXACT + control-untouched + derived-API-FLIP-live] · **DATA-SEED-PASS** [#477: pre/post-DB-row-delta + DLL-mtime + w3wp-recycle]; #482 Mig60 EF-ternary-SQL 200≠500; **#472 PROD Identity table NON-prefix `Roles`** (không `AspNetRoles`); Mig54-62 applied-prod; #348 411→401 · verbatim → git + `2026-06/07.md`)_ - **Older (S83 #325 ← S29 #232) → git `d2f52ba` + `archive/2026-06.md`** (incl #291 06-16 FAIL [gotcha #65]); pre-S38 → `archive/2026-05-*`. --- diff --git a/.claude/workflows/runs/2026-07-29-S159-tong-quan-pipeline-menu/cicd-verify-1a47a61.md b/.claude/workflows/runs/2026-07-29-S159-tong-quan-pipeline-menu/cicd-verify-1a47a61.md new file mode 100644 index 0000000..81a9717 --- /dev/null +++ b/.claude/workflows/runs/2026-07-29-S159-tong-quan-pipeline-menu/cicd-verify-1a47a61.md @@ -0,0 +1,231 @@ +# cicd-verify `1a47a61` — S159 ĐỢT-2 "tab strip 4 GĐ + menu 4-GĐ + đánh số 01-07 nhóm HĐ" + +> 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) ở file `cicd-verify-b5799fc.md` — KHÔNG chạm. +> Bắt đầu: 2026-07-29 ~12:07+07 · cicd-monitor (READ-only) + +--- + +## M1 — Push + phạm-vi commit · ✅ PASS + +``` +git log -1 → 1a47a61c21b62329c76bdef14a5daa9630c66db3 + [CLAUDE] FE-User: tab strip 4 giai doan tren phieu PE + menu 4-GD + + danh so 01-07 nhom HD (UAT anh Kiet) · 2026-07-29 12:06:19 +0700 +git log origin/main..HEAD → EMPTY (đã push) +``` + +Range `b5799fc..1a47a61` = **2 commit / 10 file**; trong đó 2 file là +`.claude/workflows/runs/**` (ignored) ⇒ **8 file CODE, 190+/18−** — khớp CHÍNH XÁC brief. + +| File code | ± | loại | +|---|---|---| +| `fe-admin/src/components/pe/PePipelineStrip.tsx` | +65 | **MỚI** | +| `fe-user/src/components/pe/PePipelineStrip.tsx` | +65 | **MỚI** (mirror) | +| `fe-admin/src/components/pe/PeDetailTabs.tsx` | +3 | wire strip | +| `fe-user/src/components/pe/PeDetailTabs.tsx` | +3 | wire strip | +| `fe-user/src/components/Layout.tsx` | +5 | menu 4-GĐ | +| `fe-user/src/pages/UserDashboardPage.tsx` | 18 ± | đổi nhãn GĐ2/GĐ3 | +| `src/Backend/SolutionErp.Domain/Identity/MenuKeys.cs` | +7 | key mới | +| `src/Backend/SolutionErp.Infrastructure/Persistence/DbInitializer.cs` | +42/− | **seeder-only** | + +⇒ BE có đụng nhưng **0 controller/service, 0 migration** — chỉ hằng-số key + seeder. +`fe-admin` CÓ sửa ⇒ **admin bundle PHẢI rotate** (khác đợt-1 nơi admin rotate là do #69 mode-A). + +**Mirror md5 (kiểm chứng "mirror md5-identical" trong brief):** +``` +ef7b782409d02b5a6a2f1bf4c2e97b42 fe-admin/src/components/pe/PePipelineStrip.tsx +ef7b782409d02b5a6a2f1bf4c2e97b42 fe-user/src/components/pe/PePipelineStrip.tsx +``` +✅ **BYTE-IDENTICAL** — 2 app dùng đúng 1 nội dung, không lệch bản. + +## M2 — Migration VẮNG là ĐÚNG · ✅ PASS + +``` +git diff --name-only b5799fc..1a47a61 -- '*Migrations*' → 0 dòng +``` +BE-change chỉ là `MenuKeys.cs` (const) + `DbInitializer.cs` (seeder runtime) ⇒ **không sinh schema**. +Mig prod kỳ vọng đứng nguyên **68** / 89 bảng. Thay đổi menu đi qua **SEEDER lúc app khởi động**, +nên rủi ro thật nằm ở M7 (double-insert / đè nhãn), KHÔNG ở `__EFMigrationsHistory`. + +## Pre-deploy bundle snapshot (chốt TRƯỚC khi deploy land) + +Đo **2026-07-29 12:07:19+07** (run #423 vừa start, chưa tới step deploy): + +| App | js | css | HTTP | +|---|---|---|---| +| admin | `index-BHodkPNV.js` | `index-DiNJjaoo.css` | 200 | +| user | `index-0GO8mgrG.js` | `index-BARVqkP-.css` | 200 | + +✅ Khớp CHÍNH XÁC baseline hậu-đợt-1 ⇒ baseline hợp lệ, đợt-2 chưa land. + +## M-src — Marker kỳ vọng, đếm trên SOURCE (chốt trước khi soi bundle) + +Đếm BYTES utf-8. (Bài học đợt-1: occurrence trong COMMENT bị minifier strip ⇒ đối chiếu occurrence-CODE.) + +| Chuỗi | UserDashboardPage | PePipelineStrip (user) | PePipelineStrip (admin) | vai trò | +|---|---|---|---|---| +| `Kế hoạch ký kết HĐ` | 2 | 2 | 2 | dương | +| `Hợp đồng cứng` | 2 | 3 | 3 | dương | +| `Toàn trình 4 giai đoạn` | 1 | 1 | 1 | dương (giữ từ đợt-1) | +| `Đề xuất ký kết hợp đồng` | **0** | **0** | **0** | ÂM — nhãn cũ đợt-1 đã đổi | +| `Ký cứng & phát hành` | **0** | **0** | **0** | ÂM | + +## M3 — Poll terminal · ✅ PASS + +- **id=536 · run_number=423 · head_sha `1a47a61c`** · created 12:06:25 → **`status=success` 12:12:09** +- **Duration 5m44s** (đợt-1 5m59s) — trong dải bình thường. +- kề trên: #422 id=535 `b5799fc` success (đợt-1) ⇒ hai run tách bạch, không nhầm. + +### M3b — Test gate · ✅ 562 EXACT +`…/actions/runs/423/jobs/0/logs` (17.737 ký tự): +``` +Passed! - Failed: 0, Passed: 45, Skipped: 0, Total: 45, 167 ms - SolutionErp.Domain.Tests.dll +Passed! - Failed: 0, Passed: 517, Skipped: 0, Total: 517, 1m26s - SolutionErp.Infrastructure.Tests.dll +``` +`Failed!` ×0. **45+517 = 562**, khớp cả TỔNG lẫn TÁCH-PHẦN. Delta 0 — đúng cho commit không kèm test mới. + +## M4 — Bundle rotate · ✅ PASS 4/4 (đo 12:12:38+07) + +| App | asset | TRƯỚC (hậu đợt-1) | SAU | KQ | +|---|---|---|---|---| +| **admin** | js | `BHodkPNV` | **`D6Pz7nfV`** | ✅ **ROTATE** (bắt buộc — `fe-admin` CÓ sửa) | +| admin | css | `DiNJjaoo` | **`B1ErASuI`** | ✅ ROTATE (utility mới của strip) | +| **user** | js | `0GO8mgrG` | **`Bpb8WEuY`** | ✅ **ROTATE** | +| user | css | `BARVqkP-` | **`7oAYFlzM`** | ✅ ROTATE | + +⚠️ Khác đợt-1 ở chỗ: đợt-1 admin-css FROZEN vì 0 admin-src; đợt-2 admin-css rotate vì strip mang class mới +⇒ hình-dạng rotate **nhất quán với nội dung diff**, không phải nhiễu #69. + +Artifact thật: +``` +USER index-Bpb8WEuY.js ct=application/javascript size=1.585.946 LM=12:11:43+07 (in-window) +ADMIN index-D6Pz7nfV.js ct=application/javascript size=1.676.677 LM=12:10:45+07 (in-window) +``` + +## M5 — Byte-marker 2 bundle · ✅ PASS (6/6 dương EXACT · 4/4 âm VẮNG) + +| Chuỗi | USER bundle | ADMIN bundle | Kỳ vọng | +|---|---|---|---| +| `Kế hoạch ký kết HĐ` | **2** | **1** | dương ✅ | +| `Hợp đồng cứng` | **2** | **1** | dương ✅ | +| `Toàn trình 4 giai đoạn` | **2** | **1** | dương ✅ | +| `Đề xuất ký kết hợp đồng` | **0** | **0** | ÂM ✅ (nhãn đợt-1 đã đổi) | +| `Ký cứng & phát hành` | **0** | **0** | ÂM ✅ | + +🔑 **Quy luật đếm rút ra (nối tiếp bài học comment-strip đợt-1): bundle-count = SỐ MODULE chứa chuỗi** +(bundler pool hằng-số chuỗi trong 1 module → nhiều occurrence cùng file gộp còn 1): +- USER = 2 module chứa marker (`PePipelineStrip.tsx` + `UserDashboardPage.tsx`) ⇒ **2** ✅ +- ADMIN = 1 module (`PePipelineStrip.tsx`; `Layout.tsx` chỉ thêm route `'/dashboard'`, KHÔNG chứa nhãn) ⇒ **1** ✅ + +Đúng cho **cả 3 marker × 2 app = 6/6**, tức "đúng-BỘI-SỐ" chứ không chỉ "tìm thấy ≥1". +Kiểm chứng phụ: `grep -rn` 2 chuỗi ÂM trên toàn `fe-user/src` + `fe-admin/src` = **0 hit** ⇒ marker âm vắng ở bundle là do mã đã gỡ, không phải do đọc sai. + +**Control ÂM 2 tầng (cả 2 app):** hash CŨ (`0GO8mgrG` / `BHodkPNV`) **và** hash bịa đều trả 200 nhưng +`ct=text/html` 876 B (user) / 900 B (admin) ⇒ bundle cũ **biến mất khỏi đĩa** = chứng ghi-đè thật. + +## M6 — Smoke · ✅ PASS 4/4 +``` +/health/live 200 Healthy · /health/ready 200 Healthy · admin 200 · eoffice 200 +``` + +## M7 — Menu-data prod sau app-pool recycle · ✅ 5/5 mục được giao + +**(a) Root count — chống double-insert:** `SELECT COUNT(*) FROM MenuItems WHERE ParentKey IS NULL` = **12** ✅ (không 13/14). +Bổ sung: **`DUPKEYS=0`** (không key nào lặp) · `MENUTOTAL=107`. + +**(b) 2 root mới đúng vị trí:** +``` +KeHoachKyKet ord=26 IsVisible=1 ✅ +HopDongCung ord=32 IsVisible=1 ✅ +``` +Permission 2 root mới: mỗi key **13 rows / 13 canread** — đúng 13 role × 1, **không nhân đôi** ✅ + +**(c) 3 root vẫn ẩn:** `Hrm` ord28 vis=**0** · `Off` ord29 vis=**0** · `Personal` ord30 vis=**0** ✅ + +**(d) 2 root đã rename** (đối chiếu prefix, KHÔNG so byte VN qua console): `PurchaseEvaluations` và +`Contracts` đều bắt đầu `Duy…` (= "Duyệt NCC" / "Duyệt hợp đồng") ✅ + +**(e) 7 nhãn đánh số CÒN NGUYÊN** (backfill silent vì equal): +``` +Ct_ThauPhu 01. · Ct_GiaoKhoan 02. · Ct_NhaCungCap 03. · Ct_DichVu 04. +Ct_MuaBan 05. · Ct_NguyenTacNcc 06. · Ct_NguyenTacDv 07. +``` +**(f) Control ÂM `Pe%`:** rows=**197** canread=**185** ✅ EXACT (seeder không đụng vùng PE — đúng comment code). + +--- + +## 🔴 M8 — PHÁT HIỆN NGOÀI CHECKLIST: grant 38-key của ĐỢT-1 ĐÃ BỊ REVOKE · ❌ FAIL + +Trong lúc soi Serilog để chứng minh seeder có chạy thật, bắt được dòng: +``` +2026-07-29 12:12:00.028 +07:00 [INF] Revoked 9 permission rows — … HRM/Văn phòng số/Cá nhân/ + Hợp đồng/Danh mục khỏi non-Admin (chỉ Admin thấy — S58 + S92) +``` +⇒ đi đo lại grant-set 38 key mà đợt-1 đã áp tay lúc 11:15: + +| Thời điểm | Đo được | +|---|---| +| **11:46** (đợt-1, **TRƯỚC** khi pool restart) | 38 key · min=max=**13/13** · tổng 494 canread | +| **12:14** (sau restart đợt-2) | 38 key · **min=1 max=2** · tổng **47/494** — **0 key nào còn 13** | + +Phân bố còn lại: **`Admin`=38 · `Procurement`=9 · 11 role còn lại = 0.** ⇒ mất **447/494** canread. + +**Nguyên nhân (đọc code, không suy đoán)** — `DbInitializer.cs:2203-2236` +`RevokeTemporarilyHiddenModulesAsync`, chạy **mỗi lần app khởi động, KHÔNG có cờ gate**: +```csharp +.Where(p => p.RoleId != adminRoleId + && (p.MenuKey.StartsWith("Hrm") || p.MenuKey.StartsWith("Off") + || p.MenuKey == MenuKeys.Personal + || p.MenuKey == MenuKeys.Contracts || p.MenuKey.StartsWith("Ct_") + || p.MenuKey == MenuKeys.Master || p.MenuKey == MenuKeys.Suppliers + || p.MenuKey == MenuKeys.Projects|| p.MenuKey == MenuKeys.Departments + || p.MenuKey.StartsWith("Catalog")) + && (p.CanRead || p.CanCreate || p.CanUpdate || p.CanDelete)) +… row.CanRead = false; row.CanCreate = false; row.CanUpdate = false; row.CanDelete = false; +``` +Tập revoke **TRÙNG KHỚP CHÍNH XÁC** grant-set 38 key (Contracts + `Ct_*` + Master/Suppliers/Projects/ +Departments/`Catalog*`). ⇒ **Grant tay 11:15 về bản chất KHÔNG BỀN: mỗi lần app restart là bị gỡ lại.** +Đây đúng mẫu `feedback_wipe_durability_check_reseed` — nhưng CHIỀU NGƯỢC: data-change thủ công bị +**seeder/revoker ungated** ăn mất, thay vì seed ungated tự mọc lại. + +**⚠️ Tự đính chính đợt-1 (KHÔNG sửa file đợt-1 theo yêu cầu):** mục M7 file `cicd-verify-b5799fc.md` +đo lúc **11:46**, mà pool restart của run #422 xảy ra ~**11:49** ⇒ phép đo đó nằm **TRƯỚC** recycle, +nên câu "App pool recycle không hề đụng menu-data" trong file đợt-1 **chưa được chứng minh** và nay +đã bị bác. 3 root ẩn + `Pe%` 197/185 vẫn đúng (nằm ngoài / miễn nhiễm), **chỉ phần grant 38-key sai.** +(`Hrm/Off/Personal` bị revoke cũng vô hại vì chủ đích chính là ẩn chúng.) + +**Vì sao log chỉ có 1 dòng `Revoked`:** step deploy `Remove-Item …\api\* ` xoá luôn thư mục `logs`, +nên log hiện chỉ chứa từ 12:12 trở đi ⇒ **không thể dùng log để quy trách nhiệm cho restart 11:49 hay 12:12**; +điều chắc chắn là revoker ungated và trạng thái HIỆN TẠI đã mất grant. + +**Khuyến nghị (KHÔNG tự sửa — READ-only, escalate em main):** +1. Grant 38-key muốn BỀN thì phải sửa **code** `RevokeTemporarilyHiddenModulesAsync` (thu hẹp tập, hoặc + thêm env-flag gate), **không** áp SQL tay — SQL tay sẽ chết ở lần deploy kế. +2. Sau khi sửa, nghiệm thu bắt buộc bằng **restart THẬT** rồi mới đo (không đo trước recycle). +3. Không rollback `1a47a61` — regression này **không do đợt-2 gây ra**, đợt-2 ship đúng. + +--- + +## VERDICT: **PARTIAL — 8/9** + +| # | Mục | KQ | +|---|---|---| +| 1 | Push + range 8 file code 190+/18− + mirror md5 identical | ✅ | +| 2 | Migration VẮNG là ĐÚNG (0 file `*Migrations*`) | ✅ | +| 3 | Run #423 success 5m44s | ✅ | +| 4 | Test gate **562** (45+517, 0 fail) | ✅ | +| 5 | Bundle rotate 4/4 (admin js+css **bắt buộc** đổi, user js+css) | ✅ | +| 6 | 3 marker dương USER + 3 dương ADMIN, count = số-module EXACT | ✅ | +| 7 | 2 control ÂM vắng ở cả 2 bundle + control hash-cũ/bịa `text/html` | ✅ | +| 8 | Smoke health ×2 + FE ×2 · menu-data 5/5 mục được giao · 0 double-insert (root=12, dupkeys=0) | ✅ | +| 9 | **Grant 38-key đợt-1 sống sót recycle** | ❌ **FAIL — còn 47/494, mất 447** | + +**Đợt-2 (`1a47a61`) tự nó SHIP ĐÚNG HOÀN TOÀN — không khuyến nghị rollback.** +Cờ đỏ số 9 là **hệ quả cấu trúc của revoker ungated có sẵn**, chỉ lộ ra khi app restart; cần fix bằng CODE. + +Baseline mới sau đợt-2: admin js `D6Pz7nfV` / css `B1ErASuI` · user js `Bpb8WEuY` / css `7oAYFlzM`; +gate 562; Mig 68 / 89 bảng (không đụng); menu root = 12. + + + 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 ca9be64..f42ff93 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 @@ -51,7 +51,26 @@ - [x] **Đ2-e — build FE ×2 PASS + test 562 PASS** (seeder đụng ⇒ chạy đủ) → **commit `1a47a61` push** (8 file, 190+/18−) - [!] **Đ2-f — cicd verify `1a47a61`** đang chạy (kèm check seeder-re-run không double-insert: 12 root, 7 nhãn `01.` nguyên) -🔸 fe-admin sidebar: 2 root placeholder sẽ **drop silent** (không thêm staticMap admin — cố ý, admin không cần placeholder; ghi để khỏi đọc thành bug) +🔸 fe-admin sidebar: ~~2 root placeholder drop silent~~ → **đổi @đợt-3**: admin CÓ staticMap + ComingSoonPage (skeleton có con ⇒ group rỗng bên admin sẽ xấu; mirror luôn cho sạch). + +## ĐỢT-3 — ảnh thứ 2 của anh + 🔴 SỰ CỐ REVOKER (phát hiện đắt nhất arc) + +**Owner verbatim (ảnh 2, ~11:4x):** ① *"Cho viết hoa - Bên trong cũng phân các list ra tương tự như duyệt NCC"* (→ Kế hoạch ký kết HĐ) ② *"Thêm Sub và đánh số như Duyệt Hợp đồng, viết hoa và các danh sách tương tự như Duyệt NCC"* (→ Hợp đồng cứng). + +- [x] **Đ3-a — skeleton menu prod**: 12 row (`Khkk_G1`+4 leaves khuôn Duyệt-NCC · `Hdc_×7` tái dùng nhãn 01.-07.) + `perm=156` (=12×13). Root có con ⇒ **uppercase tự động** (Layout `:247`). Đo: khkk_con=5 · hdc_con=7 ✅ +- [x] **Đ3-b — `ComingSoonPage` mirror ×2** (md5 `6aaa7f87…`): PePipelineStrip current=stage + mô tả + banner "cấu trúc menu hiển thị trước để mọi người góp ý" + Quay-lại `navigate(-1)` (không phụ thuộc route riêng app) · route `/coming-soon` ×2 · staticMap **11 leaf** ×2 (query riêng từng mục giữ active-state — gotcha #50) + +### 🔴🔴 SỰ CỐ — grant 38-key đợt-1 BỊ REVOKE ở app-restart #423 (cicd đợt-2 mục 9, FAIL 8/9) + +- **Đo:** còn 47/494 canread (Admin=38 · Procurement=9 · **11 role = 0**) — mất **447 dòng**. Gốc: `DbInitializer.cs:2203-2236` `RevokeTemporarilyHiddenModulesAsync` **UNGATED mỗi startup**, tập revoke (nhánh **[S92 2026-06-29]** *"anh chốt chỉ Admin thấy"*) **trùng khớp chính xác** grant-set 38 key. +- **Bản chất:** owner hôm nay (❹ *"hiển thị hết để mọi người góp ý"*) **SUPERSEDE chính quyết định S92 của owner** — code còn thi hành quyết cũ. Đúng **chiều-ngược gotcha #75/#76** (`wipe-durability` S91): seeder ungated thắng data-change tay; đổi trạng thái bền = đổi CODE, nghiệm thu = **restart THẬT**. +- 🔴 **Lỗi quy trình của LEAD (nhận):** grant SQL đợt-1 mà **không audit seeder cho revoker ngược** — memory `feedback_wipe_durability_check_reseed` có sẵn bài này, em áp chiều re-add mà không soi chiều revoke. Máy không bắt được; `cicd-monitor` bắt nhờ **đo-sau-restart** + tự đính chính M7 đợt-1 của chính nó (*"đo lúc 11:46, pool recycle ~11:49 ⇒ claim durability chưa từng được chứng minh"*). +- **Xử 2 nhịp:** ① re-grant SQL ngay `regrant=447` → 38/38 = 13/13 (user mất menu ~vài phút quanh #423) ② **gỡ nhánh [S92] khỏi revoker** (giữ Hrm/Off/Personal — ý ❸) + grant bền `reviewKeys += ContractMenuKeys()+MasterMenuKeys()+KhkkKeys()+HdcKeys()` (fresh-env hội tụ; prod đi đường SQL vì nhánh grant key-thường là skip-existing không nâng row false). +- **Đối chứng thuận:** `Khkk_/Hdc_` (156 perms) **không match prefix revoker** ⇒ sống qua #423 — giải thích vì sao menu 4-GĐ + nhãn 01-07 nguyên mà chỉ bộ 38-key rơi. + +- [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] **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 ý ❸)