wal: flush 20260801T0855
This commit is contained in:
@ -26,4 +26,72 @@ không có nhánh "sửa 1 app quên app kia". ✅
|
||||
(cùng bảng, cùng row mà `ResolveActingLevel` sẽ chấm lúc duyệt) ⇒ không có nguồn thứ hai.
|
||||
- 0 đổi logic BE: `git diff` trên `src` chỉ 2 hunk, cả 2 nằm trong DTO + projection. ✅
|
||||
|
||||
(các mục dưới điền dần)
|
||||
## §2 (đề bài 1) `actingLevel` FE có mirror ĐÚNG `ResolveActingLevel` không
|
||||
|
||||
BE (`ContractSigningPlanWorkflowService.cs:463-468`):
|
||||
`own = pendingLevelGroup.FirstOrDefault(l => l.ApproverUserId == actorId)` → `if (own is not null) return own;`
|
||||
→ `if (isAdmin) return pendingLevelGroup.First();` → còn lại **ném Forbidden**.
|
||||
FE (`KhkkWorkflowPanel.tsx:135-137`):
|
||||
`currentLevels.find(l => l.approverUserId === user?.id) ?? (isAdmin ? currentLevels[0] : undefined)`.
|
||||
|
||||
| Ca | BE chấm level nào | FE chấm level nào | Khớp? |
|
||||
|---|---|---|---|
|
||||
| Actor ∈ Cấp, cấp CÓ cờ | own (có cờ) | own (có cờ) → hiện ô-tích | ✅ |
|
||||
| Actor ∈ Cấp OR-of-N, **cờ ở NGƯỜI KHÁC cùng Cấp** | own (KHÔNG cờ) ⇒ không finalize | own (KHÔNG cờ) ⇒ ẩn ô-tích, gửi `true` = no-op | ✅ (ca đề bài hỏi kỹ — không rò) |
|
||||
| Admin **có** slot riêng trong Cấp | own | own (vì `find` chạy TRƯỚC nhánh isAdmin) | ✅ |
|
||||
| Admin **không** có slot | `pendingLevelGroup.First()` | `currentLevels[0]` | ⚠️ xem §6 (thứ tự 2 truy vấn) |
|
||||
| Không phải approver, không Admin | **Forbidden 403** | `undefined` ⇒ ẩn ô-tích | ✅ (và nút Duyệt đã `disabled` bởi `blockedByLevel` `:130`) |
|
||||
| `currentLevels` **RỖNG** (phase ≠ ChoDuyet, hoặc con-trỏ null, hoặc idx ngoài biên) | không tới `ApproveV2Async` (409 `:250-251`) | `find`→undefined, `currentLevels[0]`→`undefined` ⇒ eligible=false | ✅ **không nổ** (index-out-of-range trên mảng rỗng trong JS = `undefined`, không throw) |
|
||||
| `user` chưa nạp (`user?.id` undefined) | — | `find` không khớp (`approverUserId` là `string` non-null) ⇒ undefined; `isAdmin`=false ⇒ nút Duyệt disabled | ✅ |
|
||||
|
||||
Vế **so-khớp GUID dạng chuỗi**: `user.id` sinh từ `res.data.user` (`AuthContext.tsx:53-56`, DTO login
|
||||
serialize `Guid` → chữ thường có gạch), `approverUserId` cũng là `Guid` serialize cùng kiểu ⇒ `===`
|
||||
hợp lệ; và đây là **cùng phép so đã sống từ W3** (`:127` `actorIsCurrentApprover`), không phải phép mới.
|
||||
|
||||
⇒ **Mirror ĐÚNG 6/7 ca**; ca thứ 7 (Admin duyệt-thay trên Cấp **trộn cờ**) là finding §6/G-4.
|
||||
|
||||
## §3 (đề bài 2) Body — rò field ở action khác? khớp hợp-đồng `bool?`?
|
||||
|
||||
`KhkkWorkflowPanel.tsx:151-157`:
|
||||
`{ action, comment, ...(a === Approve ? { applyLevelFinalize: eligible ? state : true } : {}) }`
|
||||
|
||||
| Action | Key có trong JSON? | Giá trị | BE nhận | Ảnh hưởng |
|
||||
|---|---|---|---|---|
|
||||
| `submit` | **KHÔNG** | — | `ApplyLevelFinalize = null` → `?? true` (`Controller:213`) | `SubmitAsync` **không nhận tham số** (`Service:122`) ⇒ vô hại |
|
||||
| `return` / `reject` | **KHÔNG** | — | như trên | `ReturnOrRejectAsync` **không nhận tham số** (`:130-136`) ⇒ vô hại |
|
||||
| `approve`, cấp thường | CÓ | `true` | `true` | `actingLevel.AllowApproverFinalize && true` = **false** ⇒ no-op (§4) |
|
||||
| `approve`, cấp có cờ, giữ tick | CÓ | `true` | `true` | finalize (đúng ý người bấm) |
|
||||
| `approve`, cấp có cờ, bỏ tick | CÓ | `false` | `false` | rơi xuống advance thường ⇒ trình tiếp (đúng mục tiêu F-1) |
|
||||
|
||||
⇒ **0 rò field** sang action khác; và ngay cả khi rò cũng không có chỗ đọc ở BE (3 nhánh kia không có tham số).
|
||||
|
||||
**absent vs null vs true** — hợp-đồng `ContractSigningPlanTransitionBody(string Action, string? Comment = null, bool? ApplyLevelFinalize = null)`:
|
||||
key vắng ⇒ System.Text.Json để `bool?` = `null` (bằng ĐÚNG giá trị mặc định khai báo, nên **không phụ thuộc**
|
||||
vào chuyện STJ có tôn trọng default-parameter-value của positional record hay không — cả hai đường đều ra `null`)
|
||||
⇒ `?? true`. FE **không bao giờ** gửi `null` tường minh (spread bỏ hẳn key, không set `undefined`).
|
||||
⇒ 3 trạng thái absent/null/true hội tụ về CÙNG một hành vi. ✅ Khớp hợp-đồng.
|
||||
|
||||
## §4 (đề bài 3) Regression cấp thường + rò ô-tích ở phase khác
|
||||
|
||||
**Cấp thường (không cờ), trước ⟂ sau vá:**
|
||||
- trước: body `{action, comment}` ⇒ BE `?? true` ⇒ `applyLevelFinalize=true`
|
||||
- sau: body `{action, comment, applyLevelFinalize: true}` ⇒ BE `true`
|
||||
⇒ **cùng một giá trị đi vào cùng một biểu thức** `if (actingLevel.AllowApproverFinalize && applyLevelFinalize)`
|
||||
(`Service:276`), mà vế trái = `false` ⇒ nhánh không vào ở CẢ HAI thế giới. Hành vi **y hệt**, không phải
|
||||
"giống về mặt cảm tính". ✅
|
||||
(Điểm yếu còn lại là của thiết kế BE, không phải của diff: no-op này chỉ đúng vì BE **AND** với cờ cấp —
|
||||
FE gửi `true` cho người không có quyền finalize là dữ liệu thừa, không phải quyền thừa.)
|
||||
|
||||
**Rò ô-tích ở phase khác — 3 lớp chặn ĐỘC LẬP, hỏng 1 lớp vẫn không rò:**
|
||||
1. `currentLevels` chỉ khác `[]` khi `isWaiting` (`:118-121`) ⇒ DaDuyet/TraLai/TuChoi/Nháp ⇒ `actingLevel=undefined` ⇒ `approverFinalizeEligible=false`.
|
||||
2. Nút "Duyệt" (đường DUY NHẤT đặt `action=Approve`) chỉ render trong `{isWaiting && (…)}` (`:312-335`).
|
||||
3. Chính JSX ô-tích đòi `action === Approve` (`:443`).
|
||||
⇒ **0 rò**. Kiểm chéo: `KhkkTransitionAction.Approve` được gán ở đúng 1 site (`:323`), grep 2 app cho thấy
|
||||
không có site thứ hai. ✅
|
||||
|
||||
**Reset trạng thái:** `setApplyLevelFinalize(true)` đặt ở `onClick` mở dialog (`:322`) ⇒ bỏ tick rồi **Huỷ**
|
||||
rồi mở lại vẫn về mặc định. (PE chỉ reset trong `onSuccess` `:288` ⇒ PE **giữ** tick sau khi Huỷ — chỗ này
|
||||
KHKK **chặt hơn** khuôn.) ✅
|
||||
|
||||
|
||||
|
||||
|
||||
@ -42,3 +42,33 @@ Wave K1→K3 (`50e6d8c`/`0779f2d`/`15349e8`, S164-S165) cập-nhật **dòng :6*
|
||||
**resolve:** re-ground 5 ô `:465/:466/:470/:471/:472` về 71 / 97 / 256 / 64 / 614 (bao gồm sub-số `45D+569I`), rồi chạy lại detector — kỳ vọng FLAG `SKILL.md:93` biến mất và TOTAL đổi.
|
||||
|
||||
---
|
||||
|
||||
## FLAG-2 — `view-stale-count` — HIGH
|
||||
|
||||
**view:** `docs/STATUS.md:479` — chuỗi THẬT: `**Bundle hash live (prod):** admin **\`B43R6Y17\`** · user **\`NLI5umBg\`** … **Đo LIVE bằng curl 2026-07-31 @S164**`
|
||||
**source:** `.claude/workflows/runs/2026-07-31-S164-4gd-khkk-fanout/sub-cicd-verify-k3.md:86,88` — POST-deploy đo LIVE 31/07 18:26-18:27 +07: admin `index-mySTlx42.js` **ROTATE** · eoffice `index-CZAYiWWa.js` **FROZEN**
|
||||
|
||||
Lệch: view nói `B43R6Y17`/`NLI5umBg`, source nói `mySTlx42`/`CZAYiWWa`. **0/2 khớp.**
|
||||
|
||||
🔴 Nặng gấp đôi vì hai lẽ:
|
||||
1. `docs/STATUS.md:6` cố ý **KHÔNG chép** số bundle mà trỏ *"canonical ở `:479`"* (đúng B1) ⇒ ai tuân B1 sẽ đáp thẳng vào ô sai. Đây là **con trỏ tốt tới nội-dung hỏng** — nguy hơn số rời rạc vì nó được cả hệ tin.
|
||||
2. Chính vai cicd **đã nói ra** rồi mà chưa ai lật sổ: `sub-cicd-verify-k3.md:93-94` viết nguyên văn *"Đính chính mốc PRE trong đề bài: spec giao việc ghi baseline `admin B43R6Y17 · user NLI5umBg (@S164)` — KHÔNG khớp gì đo được"*. Phát-hiện đã tồn tại **trên đĩa**; khoảng cách nằm ở chỗ nó chưa đi từ run-folder về sổ owner-facing (đúng lớp bài S162: *nơi lead làm việc ≠ nơi anh đọc*).
|
||||
|
||||
Ghi chú danh-pháp (đừng lẫn khi vá): `:479` gọi app thứ hai là **user**, cicd gọi **eoffice** — cùng site, khác tên. Nếu vá thì thống nhất một tên, kẻo sinh vocab-fork mới.
|
||||
|
||||
**resolve:** `:479` = admin `mySTlx42` · user/eoffice `CZAYiWWa` (FROZEN, không phải ship-fail — cicd §5 dự-đoán TRƯỚC khi đo), kèm mốc 2026-07-31 18:26 +07 sau run #436. Hết flag khi hash ở `:479` khớp lần curl mới nhất.
|
||||
|
||||
---
|
||||
|
||||
## FLAG-3 — `view-stale-count` — LOW
|
||||
|
||||
**view:** `docs/STATUS.md:6` — chuỗi THẬT: `counter **39**`
|
||||
**source:** `.claude/governance/.session-counter.json` → `counter = 40` (`last_ticked_head` = `24ea71e2…` = HEAD hiện tại, session S166; detector `H24-5` xác nhận `[OK] OK-reachable`)
|
||||
|
||||
Lệch 39 vs 40 (−1). SEV LOW **có điều kiện**: đây là stale **biết-trước** giữa phiên (S166 vừa tick, STATUS được re-stamp ở closeout). Ghi FLAG để tally by-class không mất mẫu, KHÔNG phải để đòi vá ngay.
|
||||
|
||||
🔴 Nhưng đừng đọc thành vô hại: `counter` là **đầu vào của chính nhịp H24** (`h24_cadence.deep_every`). Một sổ owner-facing trình sai counter làm owner không thấy được món nợ `deep` 15/15 mà chính lượt này đang trả. Nếu closeout quên bump, LOW này lặng lẽ sống tiếp.
|
||||
|
||||
**resolve:** re-stamp `counter 40` ở closeout S166; hoặc bỏ hẳn con số khỏi `:6` và trỏ khoá `.session-counter.json` (kiểu B1 mà `:6` đã dùng đúng cho bundle).
|
||||
|
||||
---
|
||||
|
||||
Reference in New Issue
Block a user