Files
solution-erp/.claude/workflows/runs/2026-07-31-S164-4gd-khkk-fanout/sub-reviewer-f1-checkbox.md
2026-08-01 09:00:14 +07:00

22 KiB
Raw Blame History

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 positional bool AllowApproverFinalize; construction site = 1 (:640, grep toàn src/Backend cho đúng 3 hit: :116 def · :132 kiểu List · :640 new) ⇒ không có site thứ 2 lỡ truyền false cứng.
  • l.AllowApproverFinalize đọc thẳng từ ApprovalWorkflowLevel của đúng workflow đã pin (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.

§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 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 (approverUserIdstring 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 true true actingLevel.AllowApproverFinalize && true = false ⇒ no-op (§4)
approve, cấp có cờ, giữ tick true true finalize (đúng ý người bấm)
approve, cấp có cờ, bỏ tick 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 ?? trueapplyLevelFinalize=true
  • sau: body {action, comment, applyLevelFinalize: true} ⇒ BE truecù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=undefinedapproverFinalizeEligible=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.)

§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 ?? true0 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:

  1. Cấp trộn cờ + Admin duyệt thay🟡 G-4 (MINOR, điều-kiện). BE lấy pendingLevelGroup.First() (IGrouping dựng trong LoadStepsAsync+ResolvePointer, Service:410-442); FE lấy currentLevels[0] (mảng dựng trong query detail ContractSigningPlanFeatures.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ùng Order do 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 — đo DbInitializer.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ÙNG finalize) ⇒ trong 1 Cấp cờ đồng nhấtFirst() 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ần ResolveActingLevel đổi, FE im lặng lệch, không có test nào kêu.

  2. 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).

  3. Sợi dây DTO đứt🟡 G-3 (MINOR): ContractSigningPlanFeatures.cs:643 l.AllowApproverFinalizeđiểm chết duy nhất của tính năng, mà 0 test chạm nó. Grep tests/: AllowApproverFinalize chỉ xuất hiện ở ContractSigningPlanApprovalTests.cs:877-904 (hành vi service) và :1065-1138 (create-definition); 0 hit trên projection của GetContractSigningPlanQuery. Xoá đúng 1 argument đó ⇒ 614 test vẫn xanh, tsc vẫn xanh (field vẫn đúng kiểu, chỉ luôn false lú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.

  4. 🟡 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 terminal Service:320-329vẫn DaDuyet ngay (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-14 supersede-1 (BE đã port + Designer đã gỡ khoá) → kiểm chứng: earlyFinalizeLocked chỉ còn ở 2 chỗ bia-mộ ApprovalWorkflowsV2Page.tsx:153 + :641 (đều là chú thích "ĐÃ BỎ HẲN"), 0 site dùng.
  • :15-18 supersede-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ận approvalFlow đã 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-134 mô 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ửi applyLevelFinalize ; :772-791 = đúng khối JSX ; ContractSigningPlanWorkflowService.cs:455-469 = đúng ResolveActingLevel . 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-1KHÔ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 đổ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** |: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-18PeWorkflowPanel.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 + :234PeWorkflowPanel.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-136Service: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:457Service: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