Files
solution-erp/.claude/workflows/runs/2026-07-31-S164-4gd-khkk-fanout/sub-cicd-verify-k4ab.md
2026-08-01 10:37:08 +07:00

24 KiB
Raw Blame History

CICD-K4AB: PASS — 7/7 (run #438 success 6m23s · gate 621=45+576 · Mig GIỮ 71 · seed 49 menu+637 perm tam-giác-3-nguồn · A1 8+48 & A5 restart×2 đứng im · bundle JS ROTATE ×2 marker 3/3 · smoke 401/404/200 + bind-400)

cicd-monitor — verify deploy K4a+K4b SP-002 (88e7ecf) + phủ luôn F-1 (7a903cf)

Artifact chống #53: ghi TỪNG MỤC ngay khi có bằng chứng. Kết bằng END ... VERDICT=. Repo D:\Dropbox\CONG_VIEC\SOLUTION\SOLUTION_ERP · push 7a903cf..88e7ecf main · phiên S166 01/08. Khuôn kế thừa k1/k2/k3 cùng folder: tam-giác 3-nguồn ⟂ control ÂM ⟂ recycle-proof ⟂ dự-đoán-TRƯỚC-khi-đo.

§0 — Tiền đề (git, đo LIVE)

HEAD = 88e7ecf263f019f4b31d4defb9520704568042a9  2026-08-01 10:10:11 +0700
       [CLAUDE] Contract: K4a+K4b SP-002 sidebar 8 nhom x 6 leaf + noi ?group= end-to-end (0-mig)
git log origin/main..HEAD  ->  (rỗng — đã push)

git diff --name-only 7a903cf..88e7ecf = 33 file. Phân loại vs paths-ignore 7-glob:

  • CODE (13, trigger CI):
    • BE 4: ContractSigningPlansController.cs · ContractSigningPlanFeatures.cs · MenuKeys.cs · DbInitializer.cs
    • FE-admin 4: components/Layout.tsx · pages/khkk/KhkkCreatePage.tsx · pages/khkk/KhkkListPage.tsx · types/khkk.ts
    • FE-user 4: y hệt 4 file trên (cặp song sinh)
    • test 1: tests/…/Application/KhkkGroupMenuSeedTests.cs
  • IGNORED (20): docs/STATUS.md (**/*.md) + .claude/WAL.md + .claude/agent-memory/** ×10 + .claude/workflows/runs/** ×8
  • 0 file *Migrations* ⇒ K4a+K4b = 0-mig (khẳng định này đo lại độc lập ở §3).

F-1 7a903cf (run #437, chưa verify riêng — lượt này phủ luôn): git diff --name-only 15349e8..7a903cf = 41 file; CODE 5 (fe-admin/khkk/KhkkWorkflowPanel.tsx + fe-admin/types/khkk.ts + fe-user/khkk/KhkkWorkflowPanel.tsx + fe-user/types/khkk.ts + ContractSigningPlanFeatures.cs) — khớp mô tả "FE+DTO nhỏ"; phần còn lại ignored-path (CLAUDE.md, docs/**, .claude/**).

§0.1 — Snapshot PRE đo LIVE lúc run #438 CÒN running (10:12-10:14 +07)

Đây là mốc PRE MẠNH NHẤT có thể có: đo trong lúc CI đang chạy ⇒ chắc chắn chưa bị K4a/K4b ghi đè.

Bundle PRE (curl index.html, 10:12:09):

admin    /assets/index-1yiNV4VH.js  + /assets/index-Cf3iCOOv.css
eoffice  /assets/index-DoULfmdT.js  + /assets/index-DX1FRk5n.css

✔ Khớp ĐÚNG mốc đề bài lead giao (1yiNV4VH / DoULfmdT) và khớp nguồn gốc của mốc đó (sub-reviewer-gate-k4a.md:131,133 — gate-K4a đo post-#437). 2 nguồn độc lập ⇒ mốc PRE hợp lệ.

Prod DB PRE (sqlcmd localhost\SQLEXPRESS):

MENU_TOTAL          = 151
MENU_KHKK_PCT       = 7        (đúng 7 key nhóm-1 không-infix: Khkk_G1/WfView/List/Create/Pending/Approved/Deleted)
MENU_KHKK_G1_LABEL  = "1. Kế hoạch ký kết HĐ (NCC-TP)"      <- nhãn SKELETON S159, chưa backfill
PERM_TOTAL          = 1584
PERM_KHKK_PCT       = 91       (= 7 × 13, và CanRead=1 cả 91 ⇒ họ Khkk đang mở đủ)
ROLE_TOTAL          = 13       (Accounting/Admin/AuthorizedSigner/CatalogManager/CostControl/DeptManager/
                                Director/Drafter/Equipment/Finance/HrAdmin/Procurement/ProjectManager)
MIGTOP              = 20260731085624_AddKhkkApprovalGroupCatalogAndFinalizeRuntime · MIGCOUNT = 71
CONTROL ÂM nền #84  : Hrm*/Off*  rows=377  nhưng CanRead=1 chỉ **29**  (revoker đang SỐNG, giữ 348 row tắt)

⚠️ Cột PK MenuItems = [Key]; bảng quyền Permissions(Id,RoleId,**MenuKey**,CanRead,CanCreate,CanUpdate,CanDelete) — 1 ROW / cặp (role,key) với 4 cột cờ, KHÔNG phải 4 row. Bảng role = Roles (non-prefix).

🔴 ĐÍNH CHÍNH MỐC PRE TRONG ĐỀ BÀI (đo, không đoán): lead giao MENU_TOTAL PRE=143 ⇒ kỳ vọng 192. Đo LIVE ra 151. Truy PHẠM-VI-QUERY trước khi hô drift (bài #422) → giải thích TRỌN, element-wise: K3 (#436) đẻ thêm 8 menu-row AwV2_KhkkN1..N8 (đo: MENU_AwV2_ALL=10 = 2 cũ AwV2_DuyetNcc/ AwV2_DuyetNccPhuongAn + 8 mới, liệt kê đủ tên). 143 + 8 = 151 khít ⇒ 143 là mốc PRE-K3 đã lão hoá, không phải drift. Kỳ vọng đúng cho lượt này = 200, KHÔNG phải 192.

w3wp PRE: appcmd list wpWP "4876" (applicationPool:SolutionErp-Api), StartTime 01/08 09:11:51 = đúng cửa-sổ recycle của #437 (09:05:59→09:12:21). ⇒ mốc-so-sánh recycle-proof cho #438. (VPS có 6 w3wp chung VIETREPORT ⇒ map PID→pool bằng appcmd, CẤM đoán PID.)

§0.2 — DỰ ĐOÁN TỪ SOURCE, ghi ĐĨA TRƯỚC KHI ĐO POST

Đọc MenuKeys.cs:72-84 + DbInitializer.cs (vòng seed menu K4a + labelBackfill + grant site-2):

  • KhkkGroupNumbers=[2..8] (7) × (KhkkGroup(n) 1 + KhkkLeafActions 6 = WfView,List,Create,Pending,Approved,Deleted) ⇒ KhkkGroupMenuKeys = 7 × 7 = 49 key MỚI (7 group + 42 leaf).
  • Vòng seed menu-row dùng CHÍNH bộ sinh đó ⇒ MENU_TOTAL 151 → 200 · MENU_KHKK_PCT 7 → 56.
  • labelBackfill["Khkk_G1"] ⇒ Label đổi → "1. Thiết bị, máy móc, vật tư phụ (A1)" (key GIỮ, 0 row mới).
  • Grant: site-1 loop MenuKeys.All cấp Admin; site-2 KhkkKeys().Concat(KhkkGroupMenuKeys) cấp CanRead mọi role, skip-existing ⇒ hợp lại = 49 key × 13 role = +637 ROW. ⇒ PERM_TOTAL 1584 → 2221 · PERM_KHKK_PCT 91 → 728 (chéo: 728 = 56 × 13, và 72891 = 637 — hai lối tính khớp).
  • ⚠️ Số 196 trong đề bài (49×4) là POLICY (|All|×4 action, tầng authz — MenuKeys.cs:57-62 khai đúng vậy), KHÔNG phải row DB. Hai đại lượng khác nhau, đừng cộng nhầm vào PERM_TOTAL.
  • Mig: 0 file *Migrations* trong diff ⇒ GIỮ 71 / top 20260731085624.

§1 — Run Gitea Actions — PASS (2 run: #438 K4a+K4b · #437 F-1)

API .../actions/tasks?limit=8 (anon, public repo). Match theo head_sha, KHÔNG theo run_number:

id=551 | run# 438 | 88e7ecf263 | status=success | concl=None | 10:10:19 -> 10:16:42 +07   <- K4a+K4b
id=550 | run# 437 | 7a903cf108 | status=success | concl=None | 09:05:59 -> 09:12:21 +07   <- F-1 (phủ lượt này)
id=549 | run# 436 | 15349e8ee0 | status=success | concl=None | 31/07 18:21:48 -> 18:27:59  <- K3 (neo thứ tự)
  • #438 duration 6m23s · #437 duration 6m22s — in-band (#436 6m11s · #435 6m12s · #434 6m31s).
  • Control neo thứ tự 2 tầng: run kề dưới #438 = 7a903cf (F-1), kề dưới nữa = 15349e8 (K3, đã verify ở sub-cicd-verify-k3.md) ⇒ chuỗi #436→#437→#438 liền mạch, không có run lạ chen giữa, không nhầm run.
  • Tao đo LIVE lúc #438 còn running (poll 10:12→10:16:51, 3 nhịp running rồi success) ⇒ không dính bẫy false-positive "verify khi còn running" (Run #242).
  • concl=None là bình thường với endpoint tasks ⇒ tin status (gate test chạy TRƯỚC build/deploy).
  • URL: https://git.baocaogiaoduc.vn/vietreport-admin/solution-erp/actions/runs/438 (và /437).

§2 — Gate CI 621 = 45 + 576 PASS (khớp TỪNG VẾ + đo luôn F-1)

Log web-UI .../actions/runs/{438,437}/jobs/0/logs (HTTP 200; 18.665 B và 18.689 B). Đọc bằng bytes (text-mode crash cp1252); regex nới khoảng-trắng vì log dùng 2 space sau Passed!:

#438  Passed!  - Failed: 0, Passed:  45, Skipped: 0, Total:  45 — SolutionErp.Domain.Tests.dll
#438  Passed!  - Failed: 0, Passed: 576, Skipped: 0, Total: 576 — SolutionErp.Infrastructure.Tests.dll
#437  Passed!  - Failed: 0, Passed:  45, Skipped: 0, Total:  45 — SolutionErp.Domain.Tests.dll
#437  Passed!  - Failed: 0, Passed: 569, Skipped: 0, Total: 569 — SolutionErp.Infrastructure.Tests.dll
  • #438 = 45 + 576 = 621 ✔ khớp kỳ vọng TÁCH-PHẦN (45D/576I), 0 fail / 0 skip.
  • Chuỗi delta khép kín 3 mốc (đây là chỗ việc phủ-2-run trả giá trị): #436 K3 = 45+569 = 614 → #437 F-1 = 45+569 = 614 (Infra GIỮ 569, +0)#438 = 45+576 = +7. ⇒ F-1 đúng là "FE+DTO nhỏ, 0 test mới"; toàn bộ +7 thuộc về K4a+K4b, khớp KhkkGroupMenuSeedTests.cs (file test DUY NHẤT trong diff #438). Domain GIỮ 45 suốt 3 run.
  • Control ÂM: grep -c 'Failed!' = 0 trên CẢ HAI log ⇒ 2 dòng Passed! là TOÀN BỘ project, không có project thứ 3 bị nuốt (nếu fail, dotnet in Failed! - Failed: N…).

§3 — Mig GIỮ 71 PASS (2 nguồn ĐỘC LẬP repo ⟂ prod)

prod: SELECT MAX(MigrationId) -> 20260731085624_AddKhkkApprovalGroupCatalogAndFinalizeRuntime ; COUNT(*) = 71
repo: git diff --name-only 7a903cf..88e7ecf -- '*Migrations*'  ->  (rỗng)
  • MIGCOUNT 71 (§0.1 PRE) == 71 (POST) ⇒ không có mig lén chen vào; top y nguyên mốc K2.
  • ⚠️ Mục "GIỮ NGUYÊN" tự nó ít bit ⇒ răng lấy từ control DƯƠNG cùng phiên: §4 chứng minh DbInitializer ĐÃ chạy (seed 49 row + backfill 1 label) ⇒ startup-hook SỐNG. "Mig không nhích" là vì không có mig, chứ không phải vì app không khởi động lại.

§4 — Seed 49 menu-row + 637 permission-row + backfill label — PASS (TAM-GIÁC 3 NGUỒN, khớp TỪNG ĐỒNG)

Mọi số dưới đo SAU recycle (recycle-proof ở §4.1). Đối chiếu với dự đoán §0.2 đã ghi đĩa TRƯỚC khi đo:

Chỉ số PRE (§0.1) DỰ ĐOÁN §0.2 ĐO POST
MENU_TOTAL 151 200 200
MenuItems LIKE Khkk% 7 56 56
Khkk_G1.Label "1. Kế hoạch ký kết HĐ (NCC-TP)" "1. Thiết bị, máy móc, vật tư phụ (A1)" đúng chuỗi đó
PERM_TOTAL 1584 2221 2221
Permissions LIKE Khkk% 91 728 (=56×13) 728
… trong đó CanRead=1 91 728 728 (100%)
MIGCOUNT 71 71 71

3 NGUỒN ĐỘC LẬP cùng ra 49 / 637 (khuôn tam-giác K1):

  • (a) source-parse: KhkkGroupNumbers[2..8] × (1 group + KhkkLeafActions 6) = 7×7 = 49; grant 49 × 13 role = 637.
  • (b) sqlcmd prod: menu 151→200 (+49) · perm 1584→2221 (+637).
  • (c) Serilog prod (nguồn thứ 3, độc lập hoàn toàn với 2 nguồn trên):
10:16:30.458 [INF] Seeded 49 menu items
10:16:30.531 [INF] Backfilled 1 menu labels
10:16:30.711 [INF] Seeded 49 admin permissions
10:16:31.298 [INF] Seeded all-roles review perms: 588 added + 0 upgraded (139 keys × 13 roles)

🔑 Phân rã 637 khớp CHÍNH XÁC cơ chế 2-site: site-1 (loop MenuKeys.All, chỉ Admin) = 49; site-2 (KhkkKeys().Concat(KhkkGroupMenuKeys), mọi role, skip-existing nên Admin không đếm lại) = 588 = 49 × 12 role còn lại. 49 + 588 = 637 ✔. Con số 588 này KHÔNG suy ra được từ tổng — nó chỉ khớp nếu đúng cơ chế skip-existing ⇒ đây là bit thông tin thật, không phải trùng khớp số học rỗng. 0 upgraded cũng đúng: 49 key MỚI toàn đi nhánh INSERT.

SHAPE element-wise 8 nhóm (không chỉ đếm — soi từng dòng, khớp bảng nhãn owner danh-muc-sp002-transcribe.md):

Khkk_G1|ord=1|kids=6| 1. Thiết bị, máy móc, vật tư phụ (A1)          <- nhãn ĐÃ backfill
Khkk_G2|ord=2|kids=6| 2. Kết cấu, hạ tầng, phá dỡ & DV hỗ trợ thi công (A2-A3)
Khkk_G3|ord=3|kids=6| 3. Hoạt động văn phòng (A4)
Khkk_G4|ord=4|kids=6| 4. Vật tư (B1)
Khkk_G5|ord=5|kids=6| 5. Thầu phụ xây dựng (B2)
Khkk_G6|ord=6|kids=6| 6. Thầu phụ MEP (B3)
Khkk_G7|ord=7|kids=6| 7. BCH công trường (C1-C3)
Khkk_G8|ord=8|kids=6| 8. HĐ chưa ủy quyền — CEO (5.2)

8 nhóm × đúng 6 kids mỗi nhóm · Order 1..8 liền không trùng · 8/8 nhãn khớp bảng owner từng ký tự.

Control ÂM (chứng "đúng NGƯỜI, đúng TẬP", không quét bừa):

  • CTRL_OUTOFSET_G0_G9plus = 0 — không đẻ Khkk_G0/Khkk_G9+ ngoài miền 1..8.
  • Revoker #84 còn SỐNG mà KHÔNG đụng Khkk: Hrm*/Off* có 377 row nhưng CanRead=1 chỉ 29y hệt PRE (29). Nếu revoker chết thì số này bung lên 377; nếu revoker quét lố sang Khkk_* thì 728 đã tụt. Cả hai đều không xảy ra ⇒ checker còn răng VÀ phạm vi đúng (chân-kiềng iii).

§4.1 — Recycle-proof (2 nguồn) — mọi số §4 đo SAU khởi động lại

  • appcmd list wpWP "7020" (applicationPool:SolutionErp-Api); Get-Process w3wp → PID 7020 StartTime 01/08 10:16:22. KHÁC PRE (PID 4876 @ 09:11:51) ⇒ pool ĐÃ recycle, đúng cửa-sổ deploy #438. (VPS có 6 w3wp chung VIETREPORT ⇒ map PID→pool bằng appcmd, CẤM đoán PID.)
  • Nguồn 2: dòng ĐẦU TIÊN của solution-erp-20260801.log = 10:16:23.961 [INF] Applying migrations... (deploy xoá logs/ ⇒ dòng đầu = boot). Seed log 10:16:30 nằm sau đó ⇒ đúng lần boot này.

§6 — Bundle ×2 — DỰ ĐOÁN TỪ SOURCE trước khi curl (ghi đĩa trước, đo sau)

🔴 LOẠI marker lead gợi ý — nó KHÔNG CÓ RĂNG. Đề bài đề xuất Kết cấu, hạ tầng, phá dỡ (từ KHKK_APPROVAL_GROUP_LABELS) với kỳ vọng "0-hit PRE / 1-hit POST". Đo nguồn: git show 7a903cf:fe-user/src/types/khkk.ts | grep -c = 1 (và fe-admin cũng 1) ⇒ chuỗi này ĐÃ CÓ từ trước K4 (nó vào repo ở wave trước, K4 chỉ thêm comment quanh nó) ⇒ đã nằm sẵn trong bundle PRE ⇒ 0 bit. Cùng lớp sai lầm với earlyFinalizeLocked (§5 k3). Thay bằng 3 marker sau, đều là string literal (minifier giữ nguyên) và đều có transition PRE→POST đo được:

Marker Loại Nguồn PRE 7a903cf Nguồn POST 88e7ecf Kỳ vọng bundle LIVE
/khkk/list?group=1 POS admin 0 · user 0 admin 4 · user 4 >0 CẢ HAI
/khkk/list?view=deleted NEG admin 1 · user 1 admin 0 · user 0 0 CẢ HAI
workflow-matrix?type=10&group= DIFFERENTIAL admin 0 · user 0 admin 1 (trong // comment) · user 2 (code) admin 0 · user 2
  • NEG ĐỘC QUYỀN (bài "control ÂM phải độc quyền"): grep -rn toàn fe-admin/src+fe-user/src bản POST = 0 hit ⇒ vắng mặt trong bundle là vắng mặt THẬT, không phải chuỗi còn sống ở chỗ khác.
  • DIFFERENTIAL là marker mạnh nhất lượt này, 2 mũi cùng lúc: (1) chứng bất-đối-xứng CỐ Ý đã ship (fe-admin cố ý KHÔNG nới regex Khkk_G* — lead chốt 27/07), (2) tự chứng comment bị strip — admin có đúng 1 lần xuất hiện và nó nằm trong // (dòng 39) nên phải ra 0; nếu admin ra 1 thì hoặc comment lọt vào bundle hoặc tao grep nhầm file.

§6.1 — ĐO BUNDLE (sau khi dự đoán trên đã ghi đĩa) — PASS, khớp 3/3 marker

App PRE (§0.1) POST (đo 10:23) CT Size Last-Modified
admin js index-1yiNV4VH.js index-qOcoitOr.js ROTATE application/javascript 1.756.651 01/08 03:15:14 GMT = 10:15:14 +07
admin css index-Cf3iCOOv.css index-Cf3iCOOv.css FROZEN text/css 86.364 10:15:14 +07
eoffice js index-DoULfmdT.js index-D9J3BbcA.js ROTATE application/javascript 1.673.668 01/08 03:16:16 GMT = 10:16:16 +07
eoffice css index-DX1FRk5n.css index-DX1FRk5n.css FROZEN text/css 92.148 10:16:16 +07
  • JS ROTATE ×2 = ĐÚNG YÊU CẦU (cả 2 app đổi code thật: Layout + ListPage + CreatePage + types).
  • Cả 4 LM trong cửa-sổ deploy 10:10:19→10:16:42 ⇒ deploy CÓ chạm cả 4 file (#69: LM-in-window mới là ship-proof, hash-delta thì không). CSS FROZEN lành tính: K4a/K4b không thêm class Tailwind mới (chỉ đổi chuỗi route + logic), LM vẫn in-window ⇒ re-ship byte-identical (#69 mode-B), KHÔNG phải kẹt bundle cũ.
  • Control ÂM 2 tầng — hash CŨ hash BỊA cùng rơi text/html 900 B / 876 B: admin/index-1yiNV4VH.js→text/html 900 · eoffice/index-DoULfmdT.js→text/html 876 · admin/index-ZZfakeZZ.js→text/html 900. ⇒ text/html thật sự nghĩa "không tồn tại" (SPA-fallback), và bundle PRE đã biến mất khỏi đĩa = chứng ghi-đè.

Byte-marker đo trên file tải về (adm.js 1.738.624 ký tự · eof.js 1.658.019 ký tự):

Marker Kỳ vọng §6 admin eoffice
POS /khkk/list?group=1 >0 cả hai 4 4 ✔ (khớp ĐÚNG 4 = số lần trong source mỗi app — element-wise, không chỉ ">0")
NEG /khkk/list?view=deleted 0 cả hai 0 0 ✔ (chuỗi độc quyền, PRE source =1 ⇒ transition đo được)
DIFF workflow-matrix?type=10&group= admin 0 · user 2 0 2 ✔ (bất-đối-xứng cố ý ship + comment bị strip đúng như dự đoán)
(marker lead gợi ý) Kết cấu, hạ tầng, phá dỡ 1 1 🔴 =1 ở CẢ HAI ⇒ xác nhận bằng ĐO rằng nó 0-bit: có mặt bất kể K4 ship hay không

§5 — Acceptance A1 (quyền user-thường) + A5 (idempotent qua restart ×2) — PASS

A1 — SQL JOIN Permissions × Roles × MenuItems, role NON-admin:

Procurement : GROUP = 8   LEAF = 48      (CanRead=1)
Drafter     : GROUP = 8   LEAF = 48      (CanRead=1)
  • Khớp ĐÚNG kỳ vọng đề bài "8 group + 48 leaf". Toán khép: group 8 = Khkk_G1 + Khkk_G2..G8; leaf 48 = 6 (nhóm-1 không-infix) + 42 (nhóm 2..8) ⇒ tổng 56 = MENU_KHKK_PCT.
  • Toàn họ: PERM_KHKK_CANREAD1 = 728 = 56 × 13KHÔNG role nào bị hụt (nếu 1 role sót thì ≤ 715).

A1-bis — chứng ĐI TRỌN STACK (nguồn thứ 3, mạnh nhất): GET /api/menus/me materialize qua EF (http 200, 44.656 B; parse cây theo key, không grep thô):

GROUP distinct = 8      LEAF distinct = 42   per-group = {G2..G8 : 6 mỗi nhóm}
OLD nhóm-1 leaf non-infix = 6  [Khkk_Approved, Khkk_Create, Khkk_Deleted, Khkk_List, Khkk_Pending, Khkk_WfView]
TOTAL Khkk* distinct = 56
Khkk_G1 -> 1. Thiết bị, máy móc, vật tư phụ (A1)      Khkk_G5 -> 5. Thầu phụ xây dựng (B2)
Khkk_G2 -> 2. Kết cấu, hạ tầng, phá dỡ & DV hỗ trợ thi công (A2-A3)   Khkk_G6 -> 6. Thầu phụ MEP (B3)
Khkk_G3 -> 3. Hoạt động văn phòng (A4)                Khkk_G7 -> 7. BCH công trường (C1-C3)
Khkk_G4 -> 4. Vật tư (B1)                             Khkk_G8 -> 8. HĐ chưa ủy quyền — CEO (5.2)

⇒ 8 nhãn ra ĐÚNG tới dấu tiếng Việt qua đường Domain→Infra→Api (không phải chỉ có row trong bảng); per-group = 6 đều chứng không nhóm nào thiếu leaf. Đây là cái sqlcmd không nói được.

A5 — RESTART APP-POOL ×2 THẬT (appcmd recycle apppool /apppool.name:SolutionErp-Api, KHÔNG gọi seeder lẻ qua reflection — chuỗi SeedAsync đủ chỉ chạy lúc boot):

Mốc w3wp PID StartTime MENU_TOTAL Khkk menu PERM_TOTAL Khkk perm CanRead=1 A1 Proc leaf CTRL Hrm/Off CanRead=1
sau deploy 7020 10:16:22 200 56 2221 728 728 48 29
restart #1 220 10:23:55 200 56 2221 728 728 48 29
restart #2 6636 10:24:38 200 56 2221 728 728 48 29
  • Restart là THẬT, không no-op: PID đổi 3 lần (7020→220→6636) + appcmd xác nhận đúng pool SolutionErp-Api + Serilog có 3 dòng Applying migrations... (10:16:23 · 10:23:57 · 10:24:39).
  • Idempotent: sau boot 2 và 3, Serilog KHÔNG có Seeded … menu items / Seeded … admin permissions / Seeded all-roles review perms. Đây là suy luận CÓ CƠ SỞ, không phải đoán từ im-lặng — đã soi điều kiện log trong source: DbInitializer.cs:2180-2183 if (added > 0) · :2247 if (updatedLabels > 0) · :2310 if (added > 0) · :2524 if (added > 0 || upgraded > 0). ⇒ vắng dòng ⟺ 0 added / 0 upgraded. Cộng với 8 con số DB đứng im hoàn toàn ⇒ seeder hội tụ, không nhân bản row.
  • Revoker-proof (#84): Hrm*/Off* giữ 29/377 CanRead=1 qua cả 3 mốc. Đây là control ÂM có RĂNG 2 chiều: revoker vẫn CHẠY mỗi boot (nếu chết → bung lên 377) và vẫn KHÔNG chạm Khkk_* (nếu quét lố → 728 tụt). Đúng như comment K4a khai ở DbInitializer.cs ("filter chỉ bắt prefix Hrm*/Off* + Personal").
  • Nhãn Khkk_G1 giữ nguyên bản backfill qua cả 2 restart ⇒ labelBackfill không lật ngược về skeleton.

§7 — Smoke prod — PASS

GET /api/contract-signing-plans                    (no token) -> 401   authz đóng đúng
GET /api/contract-signing-plans/deleted?group=1    (no token) -> 401   route MỚI cũng đóng
GET /api/contract-signing-plans-khong-ton-tai-xyz             -> 404   \ 2 route BỊA
GET /api/route-rac-k4ab-999                                   -> 404   / ⇒ 401 ở trên CÓ NGHĨA
GET /health/ready                                             -> 200
GET /health/live                                              -> 200
GET https://admin.solutions.com.vn/                           -> 200
GET https://eoffice.solutions.com.vn/                         -> 200

🔑 401 chỉ có nghĩa vì đi kèm 404 của route bịa — nếu server nuốt mọi path thành 401 thì phép đo rỗng.

Giới hạn phép đo đã gỡ được (đề bài dự phòng đúng): ?group=abc không token → 401, tức authz chặn TRƯỚC model-binding ⇒ không đọc được bind-error. Đã lấy bearer admin re-probe để đóng lỗ này:

(có token)  ?group=abc -> 400   <- BIND-ERROR: param ĐƯỢC WIRE thật (nếu bị nuốt, ASP.NET trả 200)
(có token)  ?group=1   -> 200      ?group=8 -> 200      (không group) -> 200
(có token)  /deleted?group=1 -> 200
body ?group=1 = {"items":[],"total":0,"page":1,"pageSize":20,"totalPages":0,"hasNext":false,"hasPrev":false}

?group= nối end-to-end THẬT (chứng bằng 400 bind-error, không bằng lời khai).

⚠️ 2 quan sát KHÔNG hạ verdict, chuyển tiếp cho wave sau:

  1. Chân-lý-rỗng còn nguyên (kế thừa K2): ContractSigningPlans prod vẫn 0 dòngitems:[] với MỌI group. Phép đo ?group=n chứng được đường ống + binding, KHÔNG chứng được lọc đúng nhóm — backfill/lọc sai sẽ trông y hệt. Muốn có răng phải có ≥1 phiếu thật ở ≥2 nhóm khác nhau.
  2. ?group=999200 (không phải 400/404): biên API vẫn không chặn ngoài-miền 1..8 — y hệt K2 ghi nhận, chưa hồi quy nhưng cũng chưa đóng. Vô hại hôm nay vì bảng rỗng.

END sub-cicd-verify-k4ab — VERDICT=PASS 7/7 (1: run #438 success 6m23s match head_sha + #437 F-1 success 6m22s, chuỗi #436→#437→#438 liền mạch · 2: gate 621 = 45D + 576I tách-phần, F-1 = 614 (+0) ⇒ trọn +7 thuộc K4, control ÂM Failed!=0 cả 2 log · 3: Mig GIỮ 71 / top 20260731085624, 2 nguồn repo ⟂ prod · 4: seed 49 menu + 637 perm + backfill 1 label — TAM-GIÁC 3 nguồn (source-parse ⟂ sqlcmd ⟂ Serilog 49+588=637), shape 8 nhóm × 6 kids element-wise, recycle-proof PID 4876→7020 + Serilog dòng đầu · 5: A1 Procurement/Drafter 8-group + 48-leaf + menus/me materialize 8+42+6=56 qua EF; A5 restart ×2 THẬT (PID 7020→220→6636) 8 chỉ số ĐỨNG IM + log-condition đã soi source ⇒ 0 added; revoker Hrm/Off giữ 29 · 6: bundle JS ROTATE ×2 (1yiNV4VH→qOcoitOr · DoULfmdT→D9J3BbcA), 4 LM in-window, marker 3/3 khớp (POS 4/4 · NEG 0/0 · DIFF admin 0 / user 2); marker lead gợi ý ĐO RA 1/1 ⇒ bác vì 0-bit · 7: smoke 401/401 + 404 route bịa ×2 + 200 ×4; bind-error 400 chứng ?group= wire thật)