22 KiB
REVIEW-F1: PASS-WITH-FLAGS 7 — 3 MAJOR (G-1 default ngược PE-live · G-2 sơ đồ nói CEO "đã duyệt" · G-6 cây phình 5→7 file, K4a bị commit ké) / 4 MINOR; 0 blocker kỹ-thuật trên 5 file F-1, 1 blocker quy-trình (G-6: tách commit trước khi push)
Reviewer F-1 — ô-tích opt-out applyLevelFinalize panel duyệt KHKK
Ghi-đĩa TRONG LÚC SOI (chống #53). Đo từ ĐĨA, HEAD 24ea71e, diff CHƯA commit.
Phạm vi: git diff -- src fe-user fe-admin = 5 file M, +137 / −14.
§0 Mirror 2 app — chứng bằng blob-SHA của git (KHÔNG tin lời khai)
git diff in ra index-line của cả 2 app:
| Cặp file | blob TRƯỚC | blob SAU |
|---|---|---|
fe-{admin,user}/src/pages/khkk/KhkkWorkflowPanel.tsx |
e2fa3bc (cả hai) |
6913182 (cả hai) |
fe-{admin,user}/src/types/khkk.ts |
a99658c (cả hai) |
1ffd79b (cả hai) |
⇒ 2 cặp byte-identical cả trước lẫn sau (git blob = sha1 nội dung) — mirror §3.9 ĐẠT, và không có nhánh "sửa 1 app quên app kia". ✅
§1 Ranh giới BE — cờ có tới FE bằng ĐÚNG một đường
ContractSigningPlanWorkflowLevelDto(ContractSigningPlanFeatures.cs:116) +1 positionalbool AllowApproverFinalize; construction site = 1 (:640, grep toànsrc/Backendcho đúng 3 hit::116def ·:132kiểu List ·:640new) ⇒ không có site thứ 2 lỡ truyềnfalsecứng.l.AllowApproverFinalizeđọc thẳng từApprovalWorkflowLevelcủa đúng workflow đã pin (cùng bảng, cùng row màResolveActingLevelsẽ chấm lúc duyệt) ⇒ không có nguồn thứ hai.- 0 đổi logic BE:
git difftrênsrcchỉ 2 hunk, cả 2 nằm trong DTO + projection. ✅
§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}⇒ BEtrue⇒ cùng một giá trị đi vào cùng một biểu thứcif (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ửitruecho 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ò:
currentLevelschỉ khác[]khiisWaiting(:118-121) ⇒ DaDuyet/TraLai/TuChoi/Nháp ⇒actingLevel=undefined⇒approverFinalizeEligible=false.- Nút "Duyệt" (đường DUY NHẤT đặt
action=Approve) chỉ render trong{isWaiting && (…)}(:312-335). - 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.) ✅
§5 (đề bài 4) Khuôn-PE fidelity — lệch CỐ Ý vs SÓT
| # | Trục | PE (live) | KHKK (diff) | Phán |
|---|---|---|---|---|
| a | JSX ô-tích | :772-791 |
:443-462 — cùng khung, cùng nhãn "Cấp này KẾT THÚC quy trình" |
✅ trung thành |
| b | Câu "Nhớ chọn 1 giá chốt" | có (:786) |
bỏ | ✅ CỐ Ý — KHKK 0 price-picker, BE tự chốt ở ApplyApprovedValuesOnFinalize |
| c | Gửi field | luôn gửi, !isReject && (...) (:259) |
chỉ gửi khi approve | ✅ tương đương (3 nhánh kia BE không đọc) |
| d | Reset state | onSuccess |
mở-dialog | ✅ KHKK chặt hơn |
| e | Default ô-tích | useState(false) = OPT-IN (:61) |
useState(true) = OPT-OUT (:85) |
🔴 G-1 — lệch NGƯỢC khuôn LIVE |
| f | Nguồn eligibility | BE precompute currentLevelOptions chọn level theo actor (PurchaseEvaluationFeatures.cs:1083-1085) |
FE tự mirror ResolveActingLevel |
🟡 G-4 — nợ drift |
| g | Badge cấp mang cờ trong sơ đồ | có (:441 lv.allowApproverFinalize &&) + làm mờ Bước/Cấp SAU (:161-170) |
không có | 🔴 G-2 — SÓT |
🔴 G-1 (MAJOR) — default TICK ngược chiều khuôn PE ĐANG SỐNG; nhãn "khuôn PE S96" trích bản ĐÃ BỊ ĐẢO
fe-*/src/pages/khkk/KhkkWorkflowPanel.tsx:15-18 (header) + :83-85 (useState(true)).
Đo tại chỗ: fe-*/src/components/pe/PeWorkflowPanel.tsx:61 = useState(false), chú thích ngay trên nó:
"[S97 2026-07-01 — anh Kiệt FDC] … = ô-tick OPT-IN: default UNCHECKED → trình tiếp CEO/cấp sau; TÍCH mới KẾT THÚC tại cấp này. Đảo opt-out S96."
⇒ PE đã đi qua đúng ngã ba này và owner ĐẢO sang opt-IN một phiên sau S96. KHKK vừa nhặt lại đúng vế PE đã bỏ.
Vì sao lọt: chú-thích PE :769-771 (ngay trên khối JSX được trích) vẫn viết "NAY là ô-tích BỎ được (opt-out): default tick" — tự nó STALE so với :61 cùng file ⇒ implementer trích đúng dòng nhưng trúng chú-thích chết. Đây là tái phát lớp đối-chứng-khuôn-nguồn (S165 F-1): đo khuôn ở tầng CHÚ-THÍCH thay vì tầng giá trị khởi tạo.
Hệ quả hành vi: Chương bấm "Duyệt" → "Xác nhận" mà không đọc ⇒ phiếu DaDuyet + chốt giá, CEO không bao giờ thấy. Hướng-sai-lệch nghiêng về ÍT phê duyệt hơn ở đúng cổng tiền; chiều ngược lại chỉ tốn thêm 1 trạm và cứu được.
Vế bào chữa PHẢI ghi cùng: default=true giữ nguyên hành vi BE ?? true ⇒ 0 regression so với trước diff, và acceptance F-1 của gate K3 chỉ đòi "mirror PeWorkflowPanel.tsx:778" (JSX) — implementer làm đúng chữ.
⇒ Đây là quyết định của owner, không phải bug. Nhưng KHÔNG được để nhãn "khuôn PE S96" nói rằng PE đang làm thế.
Acceptance (1 trong 2): (i) owner ratify default=tick + sửa nhãn thành "ĐẢO NGƯỢC PE-live (PE S97 = opt-IN) — owner chốt @S166, lý do: …"; HOẶC (ii) đổi useState(true)→useState(false) + đổi 2 câu mô tả cho khớp.
🔴 G-2 (MAJOR — carry-over, KHÔNG do diff này đẻ) — sơ đồ nói CEO "đã duyệt" trong khi CEO chưa đụng
KhkkWorkflowPanel.tsx:100-106 stepStatus: if (isApproved) return 'Done' cho MỌI Bước; :108-114 levelStatus trả thẳng s khi s !== 'Current' ⇒ mọi Cấp = 'Done'; :234 render ls === 'Done' && "đã duyệt".
⇒ Phiếu kết thúc ở CCM Cấp 2 ⇒ Bước 3 «Ban Giám đốc» + cấp của truong.nguyen hiện ✓ emerald "đã duyệt". Dấu vết phân biệt duy nhất = vắng dòng "✓ ký {giờ}" (:238-242) — bắt người đọc suy từ sự vắng mặt.
PE đã giải đúng bài này: badge allowApproverFinalize trên chính Cấp (:441) + làm mờ Bước/Cấp SAU (:161-170), gate bằng runtime endsBeforeCeo (chú thích tại chỗ: "bug 'CEO duyệt mà mờ'").
KHKK làm được nửa rẻ ngay bây giờ (badge theo allowApproverFinalize — cờ vừa được chính diff này đưa vào DTO); nửa còn lại cần EndedByLevelFinalize ra DTO = F-7 gate K3 vẫn treo.
Ghi thật: lỗi có từ K3 (nhánh BE + seed cờ đã land ở 15349e8), diff này không đẻ ra nó — nhưng diff này biến đường kết-thúc-sớm thành đường mặc định có UI, nên tần suất từ hiếm → thường.
§6 (đề bài 5) Gotcha #44 — có ca nào CÓ quyền mà ô-tích KHÔNG hiện?
Quét đủ 3 đường có thể giấu control khỏi đúng người:
-
Cấp trộn cờ + Admin duyệt thay — 🟡 G-4 (MINOR, điều-kiện). BE lấy
pendingLevelGroup.First()(IGrouping dựng trongLoadStepsAsync+ResolvePointer,Service:410-442); FE lấycurrentLevels[0](mảng dựng trong query detailContractSigningPlanFeatures.cs:621-643). Hai truy vấn khác nhau, cả hai chỉOrderBy(l => l.Order)— không có khoá phụ ⇒ thứ tự các row cùngOrderdo DB quyết, không có gì bảo đảm 2 lượt trả giống nhau. Lệch ⇒ FE ẩn ô-tích trong khi BE finalize = mất opt-out im lặng (đúng hình #44). Vô hại trên 8 workflow seed — đoDbInitializer.cs:626-660: cờ gán theo CẤP (foreach (var (emails, finalize) in new[] { (r.Level1, false), (r.Level2, r.Level2Finalize) })→ mọi approver trong cùng Cấp nhận CÙNGfinalize) ⇒ trong 1 Cấp cờ đồng nhất ⇒First()chọn ai cũng ra cùng cờ; thêm nữa Cấp mang cờ chỉ có 1 người (chuong.phan,:576-580). Có hại khi admin dựng Cấp trộn cờ qua Designer — schema cho phép (cờ là per-slot), và chính PE đã ghi thành chú thích cảnh báo:PurchaseEvaluationFeatures.cs:1072-1075"OR-of-N approvers cùng Cấp = N rows cùng Order, mỗi row có Allow riêng. Lookup KHÔNG discriminate → luôn lấy row đầu DB"*. Đối chiếu thiết kế: PE không mirror ở FE — BE trảcurrentLevelOptionsđã chọn level theo actor. KHKK chọn mirror FE ⇒ mỗi lầnResolveActingLevelđổi, FE im lặng lệch, không có test nào kêu. -
Cấp CÓ cờ mà actor là người khác trong Cấp — KHÔNG phải lỗi: BE cũng chấm
own(không cờ) ⇒ 2 bờ cùng nói "không finalize" (§2 hàng 2). ✅ -
Sợi dây DTO đứt — 🟡 G-3 (MINOR):
ContractSigningPlanFeatures.cs:643l.AllowApproverFinalizelà điểm chết duy nhất của tính năng, mà 0 test chạm nó. Greptests/:AllowApproverFinalizechỉ xuất hiện ởContractSigningPlanApprovalTests.cs:877-904(hành vi service) và:1065-1138(create-definition); 0 hit trên projection củaGetContractSigningPlanQuery. Xoá đúng 1 argument đó ⇒ 614 test vẫn xanh,tscvẫn xanh (field vẫn đúng kiểu, chỉ luônfalselúc chạy) ⇒ ô-tích biến mất im lặng với đúng người có quyền. Đúng lớp S161-W2 (hợp-đồng FE↔BE vỡ mà build+test đều xanh). Phase-9 test-after cho phép hoãn — nhưng phải hoãn có ý thức, vì đây là sợi dây duy nhất. -
🟡 G-5 (MINOR) — chữ "(CEO)" là suy đoán theo seed, không theo cấu hình.
:457"duyệt xong kế hoạch TRÌNH TIẾP lên cấp sau (CEO)". Đúng với 8 workflow seed (Bước 3 = Ban Giám đốc). Nếu admin cắm cờ ở Cấp cuối của Bước cuối: bỏ tick ⇒ BE rơi xuống nhánh terminalService:320-329⇒ vẫnDaDuyetngay (khác duy nhất:EndedByLevelFinalize=false+ câu changelog) ⇒ ô-tích hứa "trình tiếp" mà phiếu đóng luôn. Test T-a có sanity chặn "cấp mang cờ = cấp cuối" trong test, nhưng BE không có validator cấm cấu hình đó ở Designer.
§6b (đề bài 6) Header-comment — còn tự mâu thuẫn chỗ nào?
Quét toàn khối :1-27 sau khi supersede:
:6-8"KHÔNG price-picker, KHÔNG ngân sách, KHÔNG file-khi-duyệt, KHÔNG 4 return-mode" → vẫn ĐÚNG với diff (ô-tích không phải price-picker). ✅:10-14supersede-1 (BE đã port + Designer đã gỡ khoá) → kiểm chứng:earlyFinalizeLockedchỉ còn ở 2 chỗ bia-mộApprovalWorkflowsV2Page.tsx:153+:641(đều là chú thích "ĐÃ BỎ HẲN"), 0 site dùng. ✅:15-18supersede-2 → nội dung mô tả ĐÚNG việc diff làm, nhưng nhãn "khuôn PE S96" sai với PE-live ⇒ G-1.:20-27"KHÁC PE Ở CHỖ ĐẮT NHẤT: PE nhậnapprovalFlowđã cóstatus… KHKK BE trả CÂY THÔ" → vẫn đúng, và giờ thiếu một vế: từ diff này KHKK còn phải tự suy cả level-của-actor (thứ PE cũng nhận sẵn từ BE) — chính là G-4. Khối "KHÁC PE" nên nêu thêm vế đó, nếu không người sau đọc sẽ tưởng chỉ lệch ởstatus.- Chú thích
:132-134mô tả mirror đúng 2 vế BE, nhưng không khai rằng thứ tự nhóm đến từ 2 truy vấn khác nhau (G-4). - Citation kiểm từng cái:
PeWorkflowPanel.tsx:259= đúng dòng gửiapplyLevelFinalize✅;:772-791= đúng khối JSX ✅;ContractSigningPlanWorkflowService.cs:455-469= đúngResolveActingLevel✅. Số dòng không bịa. ⇒ 0 mâu thuẫn logic còn sót; 2 chỗ thiếu-khai (nhãn khuôn PE · vế mirror mới) đã tính vào G-1/G-4.
§6c NGOÀI ĐỀ BÀI — cây làm việc PHÌNH GIỮA LƯỢT REVIEW (đo 2 lần, cách nhau ~40 tool-call)
| Mốc đo | git status --porcelain -- src fe-user fe-admin tests |
git diff --stat |
|---|---|---|
| Lúc spawn (§0) | 5 file | +137 / −14 |
| Đo lại lúc viết §6c | 7 file (+Domain/Identity/MenuKeys.cs, +Infrastructure/Persistence/DbInitializer.cs) |
+253 / −19 |
🔴 G-6 (MAJOR — quy trình, chặn commit): 2 file mới (+42 / +79 dòng) KHÔNG thuộc spec F-1 và
KHÔNG nằm trong phạm vi tôi soi. Nội dung là K4a (khuôn (b) menu-key nhóm): MenuKeys.cs:81-84
thêm KhkkGroupMenuKeys rồi spread .. KhkkGroupMenuKeys vào All (:244).
Đo hệ quả canonical: All hiện gồm 64 định danh tĩnh + 1 spread; spread = KhkkGroupNumbers (7 số
[2..8]) × (1 group + 6 KhkkLeafActions) = 49 key — chính chú thích MenuKeys.cs:79 cũng ghi "49 key"
⇒ |MenuKeys.All| = 113, policies DERIVED = 452.
⇒ Nếu commit "vá F-1" ngay bây giờ, nó nuốt trọn 121 dòng chưa qua cổng nào và đổi 2 con số canonical
(gấp ~1,8 lần). Đây đúng lớp đã ăn đòn ở gate K1 cùng chuỗi (commit gộp = vượt quyền owner).
Acceptance: tách 2 commit — (1) F-1 đúng 5 file; (2) K4a riêng, có gate riêng + cập nhật STATUS.
(Ghi thật: tôi KHÔNG soi nội dung 2 file đó; con số 113/452 là DERIVE từ hình dạng mã + chú thích tại chỗ,
chưa chạy initializer — người ship phải tự đếm lại bằng máy trước khi ghi vào STATUS.)
🟡 G-7 (MINOR — carry-over K3 F-3, mới vá NỬA): docs/STATUS.md:6 (văn xuôi CURRENT) đã sửa
"Policies 256 · Menu keys 64", nhưng 2 row canonical vẫn ở số S161: :470 | Policies | **220** |
và :471 | Menu keys | **55** |. Đó lại đúng nơi máy đọc: scripts/governance-detectors.ps1:175-181
Get-StatusValue khớp ^\|\s*<label>\s*\|\s*\*\*(\d+), :202-208 map 'menu'→'Menu keys',
'policy'→'Policies' ⇒ detector đang resolve canonical 55 / 220 trong khi văn xuôi nói 64 / 256 và máy
(sau K4a) nói 113 / 452 — ba nguồn, ba số. Vá prose mà bỏ row = fix áp NỬA spec (bài S160).
§7 VERDICT
PASS-WITH-FLAGS — 7 finding (3 MAJOR / 4 MINOR), 0 blocker KỸ THUẬT trên 5 file F-1, 1 blocker QUY TRÌNH (G-6).
Bản vá F-1 làm đúng việc nó nhận: sợi dây BE→FE nối bằng đúng 1 site (§1), mirror 2 app chứng bằng
blob-SHA (§0), actingLevel mirror ResolveActingLevel đúng 6/7 ca gồm cả OR-of-N cờ-ở-người-khác và
currentLevels rỗng (§2), body không rò field sang 3 action kia và hội tụ absent/null/true (§3), cấp
thường bất biến từng bit đường đi (§4), ô-tích không rò sang phase khác nhờ 3 lớp chặn độc lập (§4).
| # | Mức | Một dòng | Địa chỉ |
|---|---|---|---|
| G-1 | MAJOR | default TICK ngược chiều PE-live (PE S97 = opt-IN); nhãn "khuôn PE S96" trích bản đã bị owner ĐẢO — cần owner ratify hoặc lật default | KhkkWorkflowPanel.tsx:85 + :15-18 ⟂ PeWorkflowPanel.tsx:61 |
| G-2 | MAJOR (carry-over K3) | phiếu kết-thúc-sớm ⇒ sơ đồ hiện Bước «Ban Giám đốc» ✓ "đã duyệt" dù CEO chưa đụng; PE đã giải bằng badge + làm mờ | KhkkWorkflowPanel.tsx:100-114 + :234 ⟂ PeWorkflowPanel.tsx:441 :161-170 |
| G-6 | MAJOR (quy trình) | cây phình 5→7 file giữa lượt review; K4a (+121 dòng, All 64→113, policies →452) sẽ bị commit ké |
MenuKeys.cs:81-84 :244 · DbInitializer.cs |
| G-3 | MINOR | 0 test chạm l.AllowApproverFinalize trong projection — xoá 1 argument là ô-tích biến mất mà 614 test + tsc vẫn xanh |
ContractSigningPlanFeatures.cs:643 |
| G-4 | MINOR | FE tự mirror ResolveActingLevel (PE để BE precompute); Admin-fallback so First() giữa 2 truy vấn không khoá phụ — vô hại trên seed (cờ đồng nhất trong Cấp), hại khi Designer trộn cờ |
KhkkWorkflowPanel.tsx:135-136 ⟂ Service:463-465 |
| G-5 | MINOR | chữ "(CEO)" suy theo seed; cắm cờ ở Cấp cuối Bước cuối ⇒ bỏ tick vẫn DaDuyet ngay, ô-tích hứa sai; BE không có validator cấm |
KhkkWorkflowPanel.tsx:457 ⟂ Service:320-329 |
| G-7 | MINOR (carry-over K3 F-3) | STATUS mới vá văn xuôi, 2 row canonical detector đọc vẫn 55/220 | docs/STATUS.md:470-471 |
Khuyến nghị thứ tự: (1) tách commit theo G-6 → (2) hỏi owner G-1 (1 câu: "Chương bấm Duyệt mà không đọc thì mặc định KẾT THÚC hay LÊN CEO?") → (3) ship F-1 → (4) G-2/G-3 vào nhịp K4 → (5) G-7 kèm lần đụng STATUS kế.
END sub-reviewer-f1-checkbox — TOTAL: 7 finding (3 MAJOR / 4 MINOR) · 0 blocker kỹ-thuật · 1 blocker quy-trình (G-6) · verdict PASS-WITH-FLAGS