[CLAUDE] Docs: cicd-monitor memory S198 — verify 92c78f17 PASS 4/4 (run #493)

Entry S198: probe pair 404/409 client==log, control duong 3 hit, discriminator
thu-tu-2-dong-dao; ha co bay Serilog-500-gia xuong DA VA. Baseline nv.test
CHET tren prod (0 row Users, DemoSeed:Disabled) -> Stage-4 bo nhanh bearer
nv.test toi khi owner cap tai khoan non-admin that. Lead verify roi accept
(B3) — residual write cua sub, noi dung khop evidence byte-exact.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
pqhuy1987
2026-08-14 20:42:24 +07:00
parent 92c78f172a
commit 52c2dd9e47

View File

@ -1,7 +1,10 @@
# CI/CD Monitor Agent — Persistent Memory # CI/CD Monitor Agent — Persistent Memory
- **S198 (08-14) `92c78f17` đảo Serilog ra vòng NGOÀI GlobalExceptionMiddleware — PASS 4/4 (run #493, 9m39s).** Vá ĐÚNG nợ quan-sát tao bắt @S197. 🔑 **KHUÔN CHỨNG "VẮNG-MẶT" 3 NHỊP, dùng lại được cho mọi ca "lỗi X hết xuất hiện":** (1) **control dương TRƯỚC** — grep `responded 500` trên log CŨ ra **3 hit** (đúng cặp probe S197) ⇒ thước sống; (2) **tìm THẤY dòng request của probe** (khớp path + guid riêng) TRƯỚC; (3) **rồi mới** đọc status của CHÍNH dòng đó. Bỏ nhịp (2) thì "không thấy 500" chỉ là thước hỏng. Probe A `GET /api/contracts/5555…` client **404** + log `[INF] … responded 404` · Probe B `POST /api/contracts` forged client **409** + log `[INF] … responded 409`; 0 hit `responded 500`, **0 dòng `[ERR]`**, `Contracts` 2 rows / **0 row tạo mới** (gate ném ở dòng đầu handler — stack-trace chứng). 🔑 **DISCRIMINATOR CƠ-HỌC MỚI (mạnh hơn đọc level): THỨ TỰ 2 DÒNG ĐẢO.** Serilog-TRONG: `[ERR] responded 500` (19:53:56.539) **rồi** `[WRN] Handled exception` (.542). Serilog-NGOÀI: `[WRN] Handled exception` (20:33:30.480) **rồi** `[INF] responded 404` (.502). Thứ tự = vị-trí-vòng, không giả được. Bonus: stack-trace giờ **vắng khung Serilog**, đáy là `GlobalExceptionMiddleware.InvokeAsync` ⇒ Serilog không còn thấy exception. Gate **697+6 skip** EXACT (45+652), GUARD-92 ×2, `Deploy done. App pool started.` 13:26:56Z == w3wp pid 6188 start 13:26:56Z. Tag `[s198, vang-mat-3-nhip, thu-tu-dao-discriminator, serilog-500-artifact-DA-VA]`
- 🔴 **BASELINE CHẾT — `nv.test@solutions.com.vn` KHÔNG TỒN TẠI trên prod** (`Users` where email = **0 row**; `nv.%` = **0**; total 21 user; admin còn). Login 401 với CẢ 2 mật khẩu (`TestUser@123456` lẫn `User@1234567` mà seeder tự in). Ăn khớp `DemoSeed:Disabled=true` + 2 dòng `[WRN] SeedItDepartmentStaffAsync: nv.cao/nv.truong not found — skip`. ⇒ **bỏ nhánh "smoke với bearer nv.test" trong Stage 4 / gotcha #44** cho tới khi có tài khoản non-admin THẬT; đừng đọc 401 này thành lỗi authz. 🔑 Bắt được vì **tự nghi thước trước**: query đầu `AspNetUsers` ra `Invalid object name` — prod dùng Identity **non-prefix** `Users`/`Roles` (#472), suýt đọc thành "bảng trống".
- **S197 (08-14) `55a01643` YC-040 chặn tạo HĐ ngoài KHKK — PASS 8/8 (run #492, 9m59s).** 🔑 **KỸ THUẬT MỚI, DÙNG LẠI ĐƯỢC: differential-probe KHÔNG-GHI chứng BE runtime.** Gate đứng dòng ĐẦU handler, TRƯỚC mọi load ⇒ payload **qua validator nhưng SupplierId/PeId bịa** cho: PRE=**404** ("Supplier … không tồn tại") → POST=**409** đúng câu YC-040, **2/2 write-site**, **0 row ghi vào prod**. Mạnh hơn hẳn byte-match: chứng **code MỚI đang CHẠY**, không chỉ "file đã tới". Neo: gate-trước-NotFound ⇒ đảo 404→409 chỉ xảy ra nếu gate thật sự nạp. 🔑 Hash CI==local **4/4 lần 2 liên tiếp** (`erVPHB3N`1.848.933·`CgELGQXw`91.502·`DuAbkPD_`1.768.774·`CL0cCjp0`97.389) — npm ci deterministic (#92) đứng vững; **CSS FROZEN mà ĐÚNG** (hash==local, #69 mode-C). LM 12:49:14/12:50:23Z rơi trong run 12:41:06-12:51:05Z + control âm hash-cũ→`text/html` 900B/876B. Gate **697 pass + 6 Skip/0 fail** (45 Domain+652 Infra, total 703) — Skip ĐÚNG 6 test `CreateContractFromEvaluationMultiWinnerTests` **gọi tên trong log**, khớp commit-msg. GUARD-92 OK ×2. Mig **74** no-advance (0 file mig trong diff). Tag `[s197, differential-probe-404to409, khong-ghi-prod, hash-CI==local-x2, 697+6skip, serilog-500-artifact]` - **S197 (08-14) `55a01643` YC-040 chặn tạo HĐ ngoài KHKK — PASS 8/8 (run #492, 9m59s).** 🔑 **KỸ THUẬT MỚI, DÙNG LẠI ĐƯỢC: differential-probe KHÔNG-GHI chứng BE runtime.** Gate đứng dòng ĐẦU handler, TRƯỚC mọi load ⇒ payload **qua validator nhưng SupplierId/PeId bịa** cho: PRE=**404** ("Supplier … không tồn tại") → POST=**409** đúng câu YC-040, **2/2 write-site**, **0 row ghi vào prod**. Mạnh hơn hẳn byte-match: chứng **code MỚI đang CHẠY**, không chỉ "file đã tới". Neo: gate-trước-NotFound ⇒ đảo 404→409 chỉ xảy ra nếu gate thật sự nạp. 🔑 Hash CI==local **4/4 lần 2 liên tiếp** (`erVPHB3N`1.848.933·`CgELGQXw`91.502·`DuAbkPD_`1.768.774·`CL0cCjp0`97.389) — npm ci deterministic (#92) đứng vững; **CSS FROZEN mà ĐÚNG** (hash==local, #69 mode-C). LM 12:49:14/12:50:23Z rơi trong run 12:41:06-12:51:05Z + control âm hash-cũ→`text/html` 900B/876B. Gate **697 pass + 6 Skip/0 fail** (45 Domain+652 Infra, total 703) — Skip ĐÚNG 6 test `CreateContractFromEvaluationMultiWinnerTests` **gọi tên trong log**, khớp commit-msg. GUARD-92 OK ×2. Mig **74** no-advance (0 file mig trong diff). Tag `[s197, differential-probe-404to409, khong-ghi-prod, hash-CI==local-x2, 697+6skip, serilog-500-artifact]`
- 🔴 **BẪY ĐỌC LOG MỚI — `[ERR] responded 500` trong Serilog KHI CLIENT NHẬN 404/409.** `Program.cs:170` `UseMiddleware<GlobalExceptionMiddleware>()` **TRƯỚC** `:171 UseSerilogRequestLogging()` ⇒ Serilog nằm TRONG, thấy exception thoát ra và ghi 500; GlobalException (vòng ngoài) mới map lại 409/404 cho client. **Tiền-tồn, KHÔNG phải regression** — chứng bằng **control dương**: `GET /api/contracts/{guid-bịa}` client=**404** mà log cũng `[ERR] responded 500` + `[WRN] Handled exception: NotFoundException`. ⚠️ Suýt báo oan deploy này. **LUẬT: thấy 500 trong log thì đối chiếu mã client curl đo được TRƯỚC**; log 500 ≠ prod lỗi. Kèm: **deploy xoá `logs/` ⇒ không so được lịch sử ⇒ phải TỰ SINH control cùng lớp.** - 🧊 _(BẪY `[ERR] responded 500` giả khi client nhận 404/409 — Serilog vòng-TRONG: **ĐÃ VÁ @S198** `92c78f17`, verify sạch. **LUẬT còn sống, tổng-quát hoá:** thấy status lạ trong log thì **đối chiếu mã client đo được TRƯỚC** log ≠ sự-thật-client. Kèm: **deploy XOÁ `logs/`**muốn control dương phải **kéo log về đĩa TRƯỚC khi deploy step chạy**, không thì mất vĩnh viễn.)_
- **S191 (08-13) `305b573f` vá GỐC #92 (CI-config-only: `deploy.yml`+2 lockfile) — PASS 6/6 + 1 caveat tự khai.** Run **#488** success 10m24s. 🔑 **PHÉP ĐO QUYẾT ĐỊNH: hash CI == local 4/4** (`CR538vZx`1.700.709·`DNE0buTg`94.665·`C10ta-wB`1.780.807·`CTIZKsjE`88.858) **DÙ runner node v24.15.0/npm11 ⟂ local v22.13.0/npm10** ⇒ nghi-vấn 'chênh node/npm = nguồn bất định thứ 2' **BỊ BÁC BẰNG ĐO**. GUARD-92 **2/2 app** `react don ban = 19.2.5`, 0 FAIL. Gate **697** Δ0. Mig **73** no-advance. 🔑 **BẪY MỚI — hash-delta VÔ DỤNG khi PRE đã trùng hash kỳ vọng** (prod đang chạy bản-cứu chép tay) ⇒ ship-proof chuyển sang **Last-Modified nhảy vào cửa sổ run** (18:10:28→18:23:17 · 18:10:37→18:21:58; run 18:13:51-18:24:15Z) + control âm hash-cũ→`text/html` 876B. Bổ sung #69: **mode-C FROZEN-nhưng-ĐÚNG**. 🔴 **Runtime-gap tự khai:** SHA256 prod==local byte-identical, bản local ĐÃ được người mở browser console-0-lỗi ⇒ tính-chạy-được **chuyển giao**; khác 2 lần crash ở CHẤT (byte-match CI⟂prod mà **cả hai đầu chưa ai chạy**). Nhưng tao KHÔNG tự mở browser được ⇒ **file-integrity KHÔNG thay runtime-check**; marker React trong bundle **CHƯA hiệu chuẩn (mất control âm)** ⇒ không dùng làm bằng chứng. Evidence `runs/2026-08-12-S190-soltuon-dieu-chinh-hd/sub-cicd-va-goc-92.md`. Tag `[s191, gotcha-92-va-goc, npm-ci-deterministic, hash-CI==local, guard92-2/2, LM-thay-hash-delta, runtime-gap-tu-khai]` - **S191 (08-13) `305b573f` vá GỐC #92 (CI-config-only: `deploy.yml`+2 lockfile) — PASS 6/6 + 1 caveat tự khai.** Run **#488** success 10m24s. 🔑 **PHÉP ĐO QUYẾT ĐỊNH: hash CI == local 4/4** (`CR538vZx`1.700.709·`DNE0buTg`94.665·`C10ta-wB`1.780.807·`CTIZKsjE`88.858) **DÙ runner node v24.15.0/npm11 ⟂ local v22.13.0/npm10** ⇒ nghi-vấn 'chênh node/npm = nguồn bất định thứ 2' **BỊ BÁC BẰNG ĐO**. GUARD-92 **2/2 app** `react don ban = 19.2.5`, 0 FAIL. Gate **697** Δ0. Mig **73** no-advance. 🔑 **BẪY MỚI — hash-delta VÔ DỤNG khi PRE đã trùng hash kỳ vọng** (prod đang chạy bản-cứu chép tay) ⇒ ship-proof chuyển sang **Last-Modified nhảy vào cửa sổ run** (18:10:28→18:23:17 · 18:10:37→18:21:58; run 18:13:51-18:24:15Z) + control âm hash-cũ→`text/html` 876B. Bổ sung #69: **mode-C FROZEN-nhưng-ĐÚNG**. 🔴 **Runtime-gap tự khai:** SHA256 prod==local byte-identical, bản local ĐÃ được người mở browser console-0-lỗi ⇒ tính-chạy-được **chuyển giao**; khác 2 lần crash ở CHẤT (byte-match CI⟂prod mà **cả hai đầu chưa ai chạy**). Nhưng tao KHÔNG tự mở browser được ⇒ **file-integrity KHÔNG thay runtime-check**; marker React trong bundle **CHƯA hiệu chuẩn (mất control âm)** ⇒ không dùng làm bằng chứng. Evidence `runs/2026-08-12-S190-soltuon-dieu-chinh-hd/sub-cicd-va-goc-92.md`. Tag `[s191, gotcha-92-va-goc, npm-ci-deterministic, hash-CI==local, guard92-2/2, LM-thay-hash-delta, runtime-gap-tu-khai]`
@ -51,7 +54,7 @@ cicd-lane trả nhầm slice transcript thay verdict — **tao TÁI PHẠM dai n
- **Stage 1 Push+filter:** `git log -1 --format='%H %s'` + `git log origin/main..HEAD` empty + diff vs paths-ignore (docs-only SKIPPED-DOCS return). - **Stage 1 Push+filter:** `git log -1 --format='%H %s'` + `git log origin/main..HEAD` empty + diff vs paths-ignore (docs-only SKIPPED-DOCS return).
- **Stage 2 Gitea poll** (max 10 iter × 60s): API `.../actions/tasks?limit=5` (NOT `/runs` 404). Match `head_sha` (NOT run_number cancelled runs shift numbering). `updated_at` stale ~2min (gotcha #46) cross-check VPS mtime. Foreground-sleep BLOCKED Windows Monitor-style busy-wait-curl-spin ~30s/iter. - **Stage 2 Gitea poll** (max 10 iter × 60s): API `.../actions/tasks?limit=5` (NOT `/runs` 404). Match `head_sha` (NOT run_number cancelled runs shift numbering). `updated_at` stale ~2min (gotcha #46) cross-check VPS mtime. Foreground-sleep BLOCKED Windows Monitor-style busy-wait-curl-spin ~30s/iter.
- **Stage 3 Test gate:** baseline **697 PASS + 6 SKIP** (45 Domain + 652 Infra; run #492 `55a01643` 08-14; Skipfail xem row Tests; climb 697/680/645/644/639/622/614/609/598/590/566/551/532/509). CI chạy cả 2 proj TRƯỚC build/deploy `status:success` = gate passed (`tasks` KHÔNG populate `conclusion` tin `status`). Local grep undercount (Theory/InlineData). - **Stage 3 Test gate:** baseline **697 PASS + 6 SKIP** (45 Domain + 652 Infra; run #492 `55a01643` 08-14; Skipfail xem row Tests; climb 697/680/645/644/639/622/614/609/598/590/566/551/532/509). CI chạy cả 2 proj TRƯỚC build/deploy `status:success` = gate passed (`tasks` KHÔNG populate `conclusion` tin `status`). Local grep undercount (Theory/InlineData).
- **Stage 4 Post-deploy** (if SUCCESS): login bearer (admin + nv.test #44; token=`accessToken`) smoke 2XX (**NEW-endpoint bodyless-POST = 411 pre-auth NOT 404**; re-probe WITH body 401 = đã wire; 415 cùng lớp) bundle 2 app (#69: real-size, KHÔNG hash-delta) SignalR (#25) mig prod==repo. - **Stage 4 Post-deploy** (if SUCCESS): login bearer (**admin only** nv.test chết @S198; token=`accessToken`) smoke 2XX (**NEW-endpoint bodyless-POST = 411 pre-auth NOT 404**; re-probe WITH body 401 = đã wire; 415 cùng lớp) bundle 2 app (#69: real-size, KHÔNG hash-delta) SignalR (#25) mig prod==repo.
- **Stage 4.6 (S29):** sqlcmd seed verify post-deploy (NOT chỉ schema) seed = runtime-row-insert (history/tables KHÔNG nhích) verify bằng DB-query, không bằng bundle-frozen. - **Stage 4.6 (S29):** sqlcmd seed verify post-deploy (NOT chỉ schema) seed = runtime-row-insert (history/tables KHÔNG nhích) verify bằng DB-query, không bằng bundle-frozen.
- enum cần input SỐ (no `JsonStringEnumConverter`). INFRASTRUCTURE seed ungated / DEMO seed gated #51. - enum cần input SỐ (no `JsonStringEnumConverter`). INFRASTRUCTURE seed ungated / DEMO seed gated #51.
- **Stage 5 Report** PASS/FAIL + evidence + MEMORY update. - **Stage 5 Report** PASS/FAIL + evidence + MEMORY update.
@ -72,7 +75,7 @@ cicd-lane trả nhầm slice transcript thay verdict — **tao TÁI PHẠM dai n
- **Mig latest repo: Mig 74 `AddContractSealedAt`** (`20260813204332`, @S194 `88bdd054`) verify prod @S197 #492 = **74** (history top khớp · MIGCOUNT prod 74 == repo 74 đếm-neo-đuôi · 0 file mig trong diff = no-advance ĐÚNG). 🔴 Đếm phải **NEO ĐUÔI** `grep -vE 'Designer\.cs$|ModelSnapshot\.cs$'`. Mạnh hơn khớp-tổng = **set-diff repo↔prod 2 chiều** (`comm -23`/`-13` cùng rỗng). Prod: `sqlcmd -S .\SQLEXPRESS -d SolutionErp -E` (⚠ `$env:PROD_DB_PASSWORD` KHÔNG trên VPS). **NO-Mig proof = history NOT-advance + empty `git diff <base>..<head> -- '*Migrations*'`.** - **Mig latest repo: Mig 74 `AddContractSealedAt`** (`20260813204332`, @S194 `88bdd054`) verify prod @S197 #492 = **74** (history top khớp · MIGCOUNT prod 74 == repo 74 đếm-neo-đuôi · 0 file mig trong diff = no-advance ĐÚNG). 🔴 Đếm phải **NEO ĐUÔI** `grep -vE 'Designer\.cs$|ModelSnapshot\.cs$'`. Mạnh hơn khớp-tổng = **set-diff repo↔prod 2 chiều** (`comm -23`/`-13` cùng rỗng). Prod: `sqlcmd -S .\SQLEXPRESS -d SolutionErp -E` (⚠ `$env:PROD_DB_PASSWORD` KHÔNG trên VPS). **NO-Mig proof = history NOT-advance + empty `git diff <base>..<head> -- '*Migrations*'`.**
- **Bundle hash live:** baseline **#492 (08-14, `55a01643`)** admin `erVPHB3N`/`CgELGQXw` (JS 1.848.933 · CSS 91.502) · eoffice `DuAbkPD_`/`CL0cCjp0` (JS 1.768.774 · CSS 97.389) **trùng ĐÚNG dist local 4/4**, khớp CI log tới byte. 🧊 prev #488 admin `C10ta-wB`/`CTIZKsjE` · eoffice `CR538vZx`/`DNE0buTg`. 🧊 prev #600 admin `D2FHMRPX` · eoffice `BIN1bEGo` (1.819.492/1.803.881 bản này crash `useState null`, đã thay). 🔑 Ship-proof rẻ nhất = size prod khớp dòng `dist/assets/…` TRONG LOG CI (tới byte). marker minify ra **BACKTICK** `grep -F`/python. **re-snapshot LIVE sau status=success** (#69). id run_number (key theo head_sha). - **Bundle hash live:** baseline **#492 (08-14, `55a01643`)** admin `erVPHB3N`/`CgELGQXw` (JS 1.848.933 · CSS 91.502) · eoffice `DuAbkPD_`/`CL0cCjp0` (JS 1.768.774 · CSS 97.389) **trùng ĐÚNG dist local 4/4**, khớp CI log tới byte. 🧊 prev #488 admin `C10ta-wB`/`CTIZKsjE` · eoffice `CR538vZx`/`DNE0buTg`. 🧊 prev #600 admin `D2FHMRPX` · eoffice `BIN1bEGo` (1.819.492/1.803.881 bản này crash `useState null`, đã thay). 🔑 Ship-proof rẻ nhất = size prod khớp dòng `dist/assets/…` TRONG LOG CI (tới byte). marker minify ra **BACKTICK** `grep -F`/python. **re-snapshot LIVE sau status=success** (#69). id run_number (key theo head_sha).
- **Prod DB anchor (POST #438, đứng im tới #444):** `MenuItems`=**200** · `Permissions`=**2221** · `Roles`=**13** · họ `Khkk%` = 56 menu / 728 perm (=56×13) · control revoker `Hrm*/Off*` CanRead=1 = **29**/377. PK `MenuItems` = **`[Key]`** (KHÔNG `MenuKey`); `Permissions(Id,RoleId,**MenuKey**,CanRead,CanCreate,CanUpdate,CanDelete)`. - **Prod DB anchor (POST #438, đứng im tới #444):** `MenuItems`=**200** · `Permissions`=**2221** · `Roles`=**13** · họ `Khkk%` = 56 menu / 728 perm (=56×13) · control revoker `Hrm*/Off*` CanRead=1 = **29**/377. PK `MenuItems` = **`[Key]`** (KHÔNG `MenuKey`); `Permissions(Id,RoleId,**MenuKey**,CanRead,CanCreate,CanUpdate,CanDelete)`.
- **Bearer:** admin `admin@solutions.com.vn/Admin@123456` (full) · UAT `nv.test@solutions.com.vn/TestUser@123456` (Drafter CCM, gotcha #44 check). - **Bearer:** admin `admin@solutions.com.vn/Admin@123456` (full) **CHỈ CÒN ĐƯỜNG NÀY**. 🔴 UAT `nv.test@…` **ĐÃ CHẾT @S198** (0 row `Users`, `nv.%`=0) gotcha #44 (403 silent non-admin) **tạm KHÔNG probe được**; muốn probe phải xin owner 1 tài khoản non-admin thật, đừng báo 401 của thành lỗi.
- **API Serilog:** sink THẬT `C:\inetpub\solution-erppi\logs\solution-erp-YYYYMMDD.log` (skill-runbook `…\Logs\log-*.txt` STALE). Level=Information (seed/revoke lines ghi). deploy xoá `logs/` chỉ còn từ deploy cuối. - **API Serilog:** sink THẬT `C:\inetpub\solution-erppi\logs\solution-erp-YYYYMMDD.log` (skill-runbook `…\Logs\log-*.txt` STALE). Level=Information (seed/revoke lines ghi). deploy xoá `logs/` chỉ còn từ deploy cuối.
## 🔑 Critical config (flag commit nếu tái xuất) ## 🔑 Critical config (flag commit nếu tái xuất)