Compare commits

...

7 Commits

Author SHA1 Message Date
89b5bbee8a wal: flush 20260731T1653 2026-07-31 16:53:01 +07:00
77de7e61c0 wal: flush 20260731T1647 2026-07-31 16:47:07 +07:00
b0907bd02d wal: flush 20260731T1636 2026-07-31 16:36:45 +07:00
0a20d853e8 wal: flush 20260731T1622 2026-07-31 16:22:21 +07:00
525f3556e6 wal: flush 20260731T1602 2026-07-31 16:02:10 +07:00
80e027d9f0 wal: flush 20260731T1559 2026-07-31 15:59:14 +07:00
b33cb687f0 wal: flush 20260731T1552 2026-07-31 15:52:08 +07:00
13 changed files with 720 additions and 36 deletions

View File

@ -13,32 +13,37 @@ chain:
BACKLOG 3 mục giai-đoạn-sau (amendment · authz-nhóm · bung-gộp)
[x] B3 fable-review chốt: GO-WITH-SPEC-FIXES (GO 9/10 · 8 mâu-thuẫn · 0 vá-đè-vá/43 · build-order
K1→K2→K3→K4a→K4b→K5→K4c→K6→K7→K8) → 8 fix lead ÁP XONG → SPEC ĐÓNG BĂNG 4 file
[x] wf: B4 K1-BE p1 wf_2569b79b-12f — chết-giữa ~40% NHƯNG disk-truth: SCHEMA-LAYER XONG ĐÚNG SPEC
(entity 10-cột ✓ unique-filtered ✓ Mig70 3-file ✓ 0-RenameColumn ✓ DbContext ✓ — lead sanity 3/3).
Thiếu: seeder+MenuKeys+CQRS+build. Files uncommitted trên cây (git status 4 mới + 3 sửa)
[x] wf: B4 K1-BE p2 wf_2e08ba77-f53 — K1-BE TRỌN (return rỗng, disk-truth ĐỦ): seeder 86/86 (lead đếm máy)
+ MenuKeys/All + CQRS ContractCatalogFeatures + controller (path api/catalogs/contract-catalog, authz
mirror) + build 0W/0E + TEST 590/590 (lead chạy ×2 — lần-1 flaky 2-fail isolation, lần-2 SẠCH; chạy
riêng class 11/11). Lane khai 2 quyết hợp lý (TenVi sạch · controller file-riêng path-spec). UNCOMMITTED
[x] wf: B4 K1-FE ✅ (6 file/3-per-app đối xứng · KIND_CONFIG kind-5 · 4-chỗ-16-bis · SHA-pair d71a9e3b ×2 ·
build ×2 PASS — lead RE-BUILD trọng-tài #68 lần-3: diagnostics IDE = stale, build thật XANH; lane khai
2 khoảng-hở UX [3 filter BE chưa UI · tab hiện cả fe-user] chờ lead/anh, KHÔNG tự quyết — đúng luật).
Bonus finding: `columns` KIND_CONFIG = config CHẾT từ S29, kind-5 là lần đầu được nối render
[x] wf: B4 K1-test ✅ wf_1fb9f309-a27 — 7 test (T1b thêm chống-hồi-sinh #75) · SUITE 597/0 (45D+552I, +7) ·
probe VI-PHÂN chứng răng · flaky-DocxOOM tự-khỏi-có-chẩn-đoán · 🔴 lane BẮT tiền-đề lead SAI: p1 ĐÃ
viết file 25KB (mtime>sub-md) — git-status-trước-Write cứu 7 test khỏi bị đè. File Application/ (khai lý).
K1 = BE+FE+TEST TRỌN, UNCOMMITTED.
[ ] NEXT (🔴 MANDATE OWNER @pause: "sang đợt tiếp sau -> chạy 1 mạch ko ngừng luôn nhé"): reviewer-gate K1
→ commit-push K1 → K2 → K3 → K4a→K4b→K5→K4c→K6 → K7 → K8 dry-run — CHẠY LIỀN KHÔNG DỪNG CHỜ.
OG-6 ĐỔI NHÃN: soát-SAU qua Admin CRUD (bảng admin-editable, anh sửa UI được) — HẾT chặn push.
Mỗi wave vẫn: build+test+reviewer-gate rồi lăn tiếp; chỉ DỪNG nếu FAIL cứng không tự xử được.
[x] (dòng cũ K1-FE/test đã xong ở trên; vế "PUSH chờ OG-6" SUPERSEDED bởi MANDATE — OG-6 = soát-SAU)
[x] B4 K1 TRỌN 3 lane (BE p1 wf_2569b79b chết-40%-schema-xong + p2 wf_2e08ba77 seeder86+CQRS+controller ·
FE SHA-pair d71a9e3b ×2 · test wf_1fb9f309 7-test; bài giữ: git-status-trước-Write · #68 build-trọng-tài
· TenVi sạch · 2 khoảng-hở UX FE khai [3 filter BE chưa UI · tab cả fe-user]) — chi tiết sub-*-k1.md
[x] gate K1 PASS-WITH-FLAGS 9f (sub-reviewer-gate-k1.md; reviewer #53 ×1 → resume-sạch, artifact cứu B4-d).
Xử @S165: F4 IncludeInactive-mặc-định-loại + T5b · F7 5-kind · F2 STATUS-delta · F9 Mig70-áp-LocalDB.
Khai: F1 OG-6-soát-SAU · F3 authz-mirror-#82 · F5 PermGuard-nợ-khuôn-cũ · F6 IX_-NIT. Suite 598/0
[x] commit 50e6d8c PUSH → cicd PASS 6/6 (run #434 6m31s · gate-CI 598 · Mig70 prod-top + recycle-proof ·
seed 86 TAM-GIÁC 3-nguồn · bundle 4/4 rotate + 4-cũ-chết · smoke 8/8). cicd #53 ×2 @S165 return=memory-
talk, artifact cứu trọn. Bài: hash local ≠ hash CI — mốc prod luôn re-snapshot LIVE
[x] K2-BE TRỌN (p1 wf_583be7ad chết-giữa-c + p2 chết-@f-0-byte-artifact [#53 ×2 task này, ×4 @S165] +
lead-solo đóng: 3-hằng-Scope · submit-guard vá-5-THẮNG-nền-:126 [ghost-window K2→K4b khai] ·
controller ?group= · §e DONE-BY-DESIGN EnsureDraftEditable · 3 test cũ update-spec-3-cột).
Build 0W/0E · suite 598/0. Artifact sub-implbe-k2.md §c-h = lead on-behalf sau verify-đĩa
[x] K2-test TRỌN (lane chết-return + 0-artifact [#53 ×5-6 @S165 cả wave] NHƯNG file test HOÀN CHỈNH:
GroupAndCatalogTests 824-dòng 9-Fact T-a..T-g+2 · suite 607/0 = 45D+562I · 0 đè 3-test-lead ·
sub-test-k2.md = lead on-behalf sau verify-đĩa)
[x] K2-FE TRỌN (p1 chết-sau-recon + markup-rò [sub-class #53 mới, lead cắt]; p2 ĐO LẠI bắt tiền-đề lead
SAI-MỘT-PHẦN: 8 khai báo ĐÃ có call-site — diagnostics = snapshot giữa-Write #68; chỗ dở thật =
ListPage → vá badge + cột Tổng-duyệt vá-8 + colSpan 8; cp 4-file fe-user; build ×2 PASS DORfkFnc/
qYI2yszp; SHA-pair 5/5 + panel control-dương giữ baseline). Artifact sub-implfe-k2.md ĐẦY tự lane ghi
[!] reviewer-gate K2 đang chạy (sub-reviewer-gate-k2.md) → PASS thì commit-push K2 → K3
[ ] MANDATE còn lại (🔴 owner @PAUSE-2 "chạy 1 mạch ko ngừng"): K2(FE+test sau BE)→gate→push → K3 →
K4a→K4b→K5→K4c→K6 → K7 → K8-prep (form 14 người fail-closed = điểm dừng hợp lệ duy nhất).
Mỗi wave: build+test+reviewer-gate rồi lăn; DỪNG chỉ khi FAIL cứng. OG-6 = soát-SAU (HẾT chặn push).
[ ] K8 dry-run: gói test SOLUTION-PE-{n} + B0-form 14 người thật (fail-closed) + đo freeze 2-nhánh
[ ] closeout: seed diary vai (C2 AB=∅) · tally garble (+sự-cố hôm nay) · nhãn D · STATUS row S163/S164
[ ] closeout: seed diary vai (C2 AB=∅) · tally garble (+reviewer-gate ×1 @S165 resume-sạch, đếm lại cuối
phiên) · nhãn D MIND-3 + nén MIND-1-kèm-nơi-đi-6-ý (trần 84,8%) · STATUS row S163/S164/S165
next: MANDATE 1-mạch (PAUSE-2): reviewer-gate K1 → commit-push K1 → K2 → K3 → K4a→K4b→K5→K4c→K6
→ K7 → K8 dry-run. Mỗi wave build+test+reviewer-gate rồi lăn tiếp; DỪNG chỉ khi FAIL cứng.
(next: cũ "đợi wf_2569b79b + chờ OG-6 rồi push" STALE @S165 — K1 đã TRỌN disk-truth, OG-6 = soát-SAU.)
next: đợi wf_583be7ad-1a2 (K2-BE) → VERIFY ĐĨA (sub-implbe-k2.md + git status + build/test THẬT) →
phóng K2-FE + K2-test song song → gate K2 → push → lăn K3 theo build-order.
Chết giữa chừng: relaunch-cắt-gọt tiep.md §4 (git status + sub-file cho biết đã tới đâu).
verify:
ls .claude/workflows/runs/2026-07-31-S164-4gd-khkk-fanout/ | wc -l # 29 @S165 sau gate-K1 (tăng theo lane — đếm tươi, đừng neo; ctx-verifier K8 vá 28→29)

View File

@ -1,5 +1,9 @@
# CI/CD Monitor Agent — Persistent Memory
- **07-31 verify `50e6d8c` S164 K1 SP-002 danh-mục-loại-HĐ (20 file: 11 BE + 6 FE + 1 test + 2 docs, +Mig 70):** ✅ **PASS 6/6** — Run **#434** (id=547) success **6m31s** (dài nhất 4-run gần đây: #431/#432/#433 ~5m50; hợp lý vì +Mig+seed-86+8-test — chưa tới ngưỡng escalate). Gate **598 = 45D + 553I** (0 fail/skip) — Domain **GIỮ 45**, Infra **545→553 = +8** ⇒ delta rơi TRỌN vào Infra đúng nơi file test mới nằm (đối-chiếu TÁCH-PHẦN bắt được điều tổng-số giấu). Mig 70 `20260731072441_AddContractCatalogEntries` **TOP prod**, `sys.tables` 96→**97** khớp `grep -c CreateTable`=**1**. 🔑 **TAM-GIÁC 3 NGUỒN ĐỘC-LẬP** cho seed: source-parse `DbInitializer` == sqlcmd prod == **API materialize qua EF** — cả 3 ra `86` + `1:5·2:10·3:5·4:23·5:14·6:15·7:13·8:1` y hệt; nguồn 3 mạnh nhất vì đi trọn Domain→Infra→Api (200, 25.374 B, distinct-code 86). Serilog `Seeded 86 contract catalog entries (S164 SP-002)` @15:52:27 + control-ÂM findstr chuỗi-bịa 0 hit. Bundle **4/4 ROTATE** (admin `B43R6Y17``CKgq0YlH`, user `NLI5umBg``CHMCU35C`) + **4/4 hash CŨ rơi `text/html` 876/900 B** = ghi-đè thật; LM 15:51:06/15:52:07 in-window. Smoke 8/8: `/health` bare **404** (control) · `/health/{ready,live}` 200 · endpoint mới không-token **401** kèm **2 route BỊA 404** ⇒ 401 CÓ NGHĨA. w3wp PID 2644 StartTime 15:52:13 in-window + log dòng-đầu `Applying migrations...` ⇒ đo SAU recycle (né #423). ⚠️ **BÀI MỚI — 0-hit là DƯƠNG-GIẢ tới khi soi format**: grep `Passed!` lần đầu **0 hit** chỉ vì log dùng 2-khoảng-trắng (`Passed! - Failed: 0`); suýt báo "không thấy dòng test". Cùng class: parse seeder lần 1 ra **78** (regex line-anchored, tuple xuống dòng) — vứt số, parse lại ra 86 + cross-check `raw code-literal`=86. ⚠️ **hash CI ≠ hash local build** (local dự-báo `BE7zE81C`/`DSXJJbJq`, CI ra khác) ⇒ CẤM lấy hash local làm mốc kỳ-vọng prod, luôn re-snapshot LIVE. Evidence: `runs/2026-07-31-S164-4gd-khkk-fanout/sub-cicd-verify-k1.md`. Tag `[run-434, pass-6-6, tam-giac-3-nguon, 0-hit-la-duong-gia, gate-598, mig-70, hash-CI-khac-local]`
- _(**07-30 `bfc7b79` S162 cây-4-folder-GĐ FE-ONLY — PASS 5/5**: run #432 5m35s, gate 590. 🔴 **LUẬT CẤM `HEAD~1`** — Stop-hook WAL auto-commit giữa lúc verify làm `HEAD~1` đổi nghĩa ⇒ tao kết SAI "marker vô giá trị"; đo lại bằng SHA tuyệt đối `X^..X` thì 0 hit ⇒ marker hợp lệ. 🔑 **ĐẾM TRỌN HỌ bằng 1 REGEX** `pipeline-[a-z-]{0,24}index` ra đúng 3 phần tử khớp source element-wise (vừa vế DƯƠNG vừa tự chứng regex không quét bừa). ⚠️ suýt FAIL-OAN: nhãn cũ 8/app sống hợp lệ — control ÂM phải ĐỘC-QUYỀN. verbatim → `runs/2026-07-30-S162-cay-4-folder-gd/sub-cicd-monitor-2.md`)_
- **07-30 verify `bfc7b79` S162 cây-4-folder-GĐ (FE-ONLY, 17 file, 12 FE = 6+6 đối xứng ×2 app):** ✅ **PASS 5/5** — Run **#432** (id=545) success **5m35s**; gate **590 = 45D+545I** (0 fail/skip). 0 mig/0 BE ⇒ không chân EF. Bundle **4/4 ROTATE** (admin js `BBQKvUMa``DXoYDy7R` · user js `BV5--mPf``o8KDluD1`, css cả 2 đổi); `index.html` LM 10:09:07 in-window + `Cache-Control:no-cache`. 🔴 **BẪY MỚI — MOVING-TARGET `HEAD~1`:** phiên có Stop-hook WAL **tự commit giữa lúc verify**`HEAD~1` đổi nghĩa từ 'cha commit đích' thành 'chính commit đích' trong ~6 phút. Tao đã kết SAI '2 marker có sẵn ở parent ⇒ vô giá trị'; đo lại bằng SHA tuyệt đối `8eba137` thì **cả 2 = 0 hit** ⇒ marker hợp lệ. **LUẬT: verify commit X luôn neo `X^..X` bằng SHA, CẤM dùng `HEAD~1`** (mọi repo có hook auto-commit). 🔑 **Khuôn MỚI 'ĐẾM TRỌN HỌ bằng 1 REGEX'**: `pipeline-[a-z-]{0,24}index` quét bundle ra **đúng 3 phần tử** `{contract×2, khkk×1, pe×1}` khớp source element-wise ⇒ vừa là vế DƯƠNG, vừa tự chứng regex không quét bừa (mạnh hơn đếm chuỗi rời). Dự-đoán-trước-khi-đo khớp từng đồng: `contract-index`=2 (= số FILE trong diff) · VN `Kế hoạch Hợp đồng`=**1** (source 3 **2 comment**) · raw-UTF8 không escape. ⚠️ **suýt FAIL OAN lần nữa:** `Kế hoạch ký kết HĐ` (nhãn strip cũ) = **8/app** — KHÔNG dùng làm control ÂM được vì synthesis ghi rõ nhãn folder GĐ2 **cố ý khác** nhãn strip; 1-mới + 8-cũ cùng sống = chứng phân-biệt-phiên-bản. Control ÂM: chuỗi-BỊA `pipeline-hardcopy-index`=0 (source cũng 0) + hash BỊA & **hash CŨ cả 2 app** đều `ct=text/html` **876B/900B trùng khít size index.html** ⇒ bundle cũ biến mất = ghi đè thật. Smoke 4×200 + bare `/health` **404** (control). WAL-commit `a07b942` (3 file `.md`) bị `paths-ignore` nuốt ⇒ không đẻ run rác. Evidence: `runs/2026-07-30-S162-cay-4-folder-gd/sub-cicd-monitor-2.md`. Tag `[run-432, pass-5-5, CAM-HEAD~1-neo-SHA, dem-tron-ho-1-regex, control-am-DOC-QUYEN-lan-2, gate-590, fe-only]`
- _(**#427 S161 W1 KHKK `a8027c0` +Mig 69 `AddContractSigningPlans` — PASS 6/6**: gate 566, 5m40s; `sys.tables` **89→96** (+7 khớp 7 CreateTable), MenuItems 142 GIỮ. 🔑 **khuôn ĐO-CỜ không đếm ROW** (row tồn tại sẵn ⇒ đếm row = 0-bit; đo PRE/POST **cờ** 7 key 13/0/0/0→13/13/13/2) + 🔑 **control ÂM NỘI-HỌ: `Khkk_G1` bị seeder CỐ Ý bỏ khỏi tập** ⇒ phải đứng yên, và nó đứng yên ⇒ chứng 'đúng NGƯỜI, đúng TẬP'. Đọc seeder **dự đoán `0 added + 91 upgraded` TRƯỚC khi đo → log prod khớp từng đồng**. verbatim → `archive/2026-07.md` substring:"a8027c0` W1 KHKK")_

View File

@ -1,5 +1,7 @@
# Implementer-Frontend Agent — Persistent Memory
- **S164/S165 (07-31) K2-FE KHKK nhóm-duyệt N1N8 [relaunch p2 sau p1 chết #53 — chi tiết `runs/2026-07-31-S164-4gd-khkk-fanout/sub-implfe-k2.md` §1-§5]:** 🔴 **BÀI CHÍNH — tiền-đề lead SAI, đo `git diff` TRƯỚC khi gõ:** lead khai CreatePage "mid-wire, 8 khai báo chưa vào JSX" (TS6133 sẽ đỏ) ⇒ đo đĩa thật thì **8/8 ĐÃ có call-site**; Detail cũng land đủ. Việc thật ≈ **1/4** mô tả — chỉ ListPage dở (p1 kịp thêm mỗi dòng import). Khuôn S114 "WRITE-lane garble ≠ mất code" xác nhận lần nữa: lead đọc transcript dở-dang → kết luận theo Ý ĐỊNH đang chạy, không theo đĩa. **Viết lại phần đã đúng = phá.** ⇒ p2 chỉ sửa ListPage 3 điểm: badge `N{g}` NHÉT VÀO ô "Mã kế hoạch" (không đẻ cột — bảng đã cuộn ngang @1366 do cây GĐ 19rem, ghi chú S162 nằm sẵn trong file) · +cột "Tổng duyệt" Σ`totalApprovedAmount` (vá-8 spec, BE trả **0** cho phiếu chưa duyệt ⇒ ép `—` kẻo đọc nhầm "đã duyệt 0 đ") · **`colSpan` 7→8 hai chỗ** (loading + empty row — quên = lỗi CÂM tới lúc bảng rỗng). `npm run build` = `tsc -b && vite build` + `noUnusedLocals:true` ⇒ build LÀ phép thử TS6133 thật, không cần tin IDE (#68). cp 4 file → SHA-pair **5/5 IDENTICAL** (Panel không sửa = **đối chứng dương**, hash `2B64144EF6E8` BẰNG baseline ⇒ chứng phép đo đúng thứ). diff-stat đối xứng 269+/36 ×2 app. 0 `.cs` (13 `.cs` M/?? là lane BE), 0 route/menu (K4a). Tag `[s164, khkk-nhom-duyet, do-dia-truoc-khi-go, colspan-loi-cam, badge-trong-o-khong-de-cot, panel-doi-chung-duong]`
- **S161 (07-29) ARC W1+W2+W3đ1 KHKK FE — 3 lượt, chi tiết = sub-md `runs/2026-07-29-S161-khkk-*/` (mọi lượt #53, đĩa cứu trọn):** **W1** Designer type-10: containment **2-LỚP render+payload** (`levelFlagsPayload(e, lock)` ép false 2/2 call-site — chỉ `disabled` = containment GIẢ vì `copyFromDefinition` tuồn state); 2 tiền-đề đề-bài SAI tự bẻ (FE không có bảng nhãn — nhãn từ BE `applicableTypeLabel`; khuôn type-9 không tồn tại → mirror `Contract:3`). **W2** 3 page + types ×2 SHA-pair; types-theo-ENTITY khi BE song song; header types = HỢP-ĐỒNG 12-endpoint (reviewer đối chiếu ra 6 đứt → lead vá; bài: hợp-đồng phải LEAD-định-nghĩa literal); WfView 2-app-2-đường (user matrix?type=10 whitelist+10 · admin Designer deep-link); place-3 menuKeys SKIP-có-khai (0 consumer). **W3đ1** `KhkkWorkflowPanel` mirror PE-panel 1063-dòng theo bảng **GIỮ/BỎ tường minh** (BỎ đủ 2 đường cấm + price-picker/budget/4-radio); cây workflow THÔ — **FE tự suy ✓/●/○ từ con-trỏ đôi** (StepIndex 0-based vs Level.Order giá-trị); bổ `approverUserId` opinion (W2 khai thiếu). Tag `[s161, khkk-fe-3-wave, containment-2-lop, types-theo-entity, giu-bo-tuong-minh, fe-suy-status-con-tro]`
- **S153 (07-26) arc ctx-adopt T2 + T5 (2 lượt hmw) `[lead seed on-behalf @closeout M9; T2 return mất schema-fail (#53 +1) → derive-from-body, template VẪN LAND 8.819B; T5 return CLEAN]`:** **T2 template** `.claude/templates/session-mind-template.md` — 3 marker (RULES-START/END · MIND-TOP) · **0 dòng key `ts:`** (ts sống trong HEADING sau em-dash ⇒ không phạm rào-2) · 6 hit `rào-` (3 rào × tên+lý-do) · chiều chèn **NGƯỢC** `_context` khai TO trong khối luật · placeholder `<k>` trong schema-comment chống parser false-match (mirror trick template `_context:32`) · kiểm `_SECRET_RES` để ví-dụ rào-3 không tự khớp mẫu (secrets-sweep rglob TRỌN session-dir, KHÔNG biết enclosure).

View File

@ -1,5 +1,7 @@
# Reviewer Agent — Persistent Memory
- **S164 (07-31) gate K1 danh mục SP-002 (`ContractCatalogEntries`, Mig 70) — PASS-WITH-FLAGS 9 finding/0 blocker kỹ-thuật:** build 0W/0E + **597 test** (45D+552I) tươi. Lane khai ĐÚNG 100% ở chỗ đo được: seed **86/86** parse-máy khớp transcribe từng dòng (chỉ 8 mismatch = đúng tập lane đã khai), phân bố 5/10/5/23/14/15/13/1, SHA-pair FE `d71a9e3b…` ×2 khớp claim. **Bài thu:** ① 🔴 giá-trị lớn nhất KHÔNG nằm ở mã mà ở **GATE QUY-TRÌNH**: spec đặt PRE-gate owner (`OG-6`) + kế hoạch **tách 2 commit** (code trước, seed sau khi gật) — code đúng hết mà commit gộp là **vượt quyền owner**; seeder per-Code idempotent KHÔNG có nhánh update ⇒ gật muộn = phải UPDATE tay. ② **2 quyết-định hợp-lệ ghép lại đẻ ghost-wire**: lead bắt key vào `MenuKeys.All` (để có menu row) + spec bắt controller mirror khuôn `Roles="Admin"` ⇒ 4 policy `ContractCatalog.*` sinh ra mà **0 endpoint tiêu thụ**; ma trận CRUD chỉ điều-khiển HIỂN-THỊ, tick CanCreate vẫn 403 / bỏ tick CanRead vẫn GET được (lớp #82) — **phải đo `grep policy` chứ đừng suy từ "key đã vào All"**. ③ **default-lấn-ngữ-nghĩa**: spec `IncludeInactive?` (mặc-định-LOẠI) → code `IsActive?` (mặc-định-GỒM) ⇒ cột "ẩn khỏi picker" chỉ ẩn nếu MỌI consumer nhớ truyền tham số. ④ drift DELTA đo được (All=56, policy=224) mà `STATUS.md:6` chưa đổi ⇒ acceptance "khai cùng commit" chưa đóng. Tag `[s164, gate-quy-trinh-owner, ghost-policy-2-quyet-dinh-hop-le, default-lan-ngu-nghia, seed-doc-may]` **Evidence:** `.claude/workflows/runs/2026-07-31-S164-4gd-khkk-fanout/sub-reviewer-gate-k1.md`. ⚠️ Sổ này **18.8KB > 17.1KB** — nợ curate lượt sau.
- **S162 (07-30) cây 4-folder GĐ (FE-only 2 app) — PASS-WITH-FLAGS 8 flag/0 blocker:** 🔴 class **`im-lặng-về-độ-chính-xác-của-con-số`** — 3 ca cùng lớp 1 diff, tsc+build ×2 xanh: hook VỨT `total` khi `pageSize:200` ⇒ không biết mình bị cắt, badge tụt không dấu vết · `pe.ContractId = contracts[0].Id` ⇒ phiếu liên-danh N HĐ chỉ nối 1 mà badge in như thật (**tái phát cardinality S87/S88 — field làm KHOÁ-NỐI phải đi đọc WRITE-SITE của nó**) · fallback `?? '(chưa cấp mã)'` trộn 2 nguyên nhân ⇒ **khẳng định SAI**, không phải degrade. · **#82 ngược chiều:** FE gate OR nhiều menu-key nhưng `MenuPermissionHandler.cs:40` khớp CHÍNH XÁC 1 key ⇒ OR làm gate LỎNG HƠN policy, tự chuốc 403; **gate query = ĐÚNG key policy của endpoint**. 2 endpoint cùng diff kết luận NGƯỢC nhau (`/contracts` `[Authorize]` trần ⇒ KHÔNG 403) ⇒ đo TỪNG cái. · **Nhãn ✓ không có phép đo:** rubric 'responsive ✓' mà harness grep 'grid-cols-[19rem'=0 hit ⇒ 2 trang lưới chưa từng render; **đọc `^import` của harness = cách rẻ nhất biết ảnh CHỨNG được gì**. Tag `[s162, im-lang-do-chinh-xac, total-bi-vut, contracts0-cardinality, or-key-long-hon-policy, nhan-tick-khong-phep-do]` **Evidence:** `.claude/workflows/runs/2026-07-30-S162-cay-4-folder-gd/sub-reviewer-1.md` · lead áp 5/8 flag (F-1/3/4/6/7) + F-8 sửa memory designer TRƯỚC ship `bfc7b79`; F-2 (liên-danh N-HĐ) + F-5 (layout 19rem chưa render thật) = giới-hạn khai thẳng lên HANDOFF slot (56).
- **[→ archive/2026-07.md @S162-curate] S161-W2 (07-29) KHKK CRUD — 2 lane viết SONG SONG:** hợp-đồng FE↔BE VỠ 6 điểm mà tsc+build+574-test đều XANH (route lệch tên · field lệch · 2 lỗi CHE NHAU); lời-khai-lane = GIẢ ĐỊNH ⇒ so ROUTE+FIELD trên ĐĨA; list-bịt-detail-mở ⇒ rào chỉ còn UX; INNER-join global-filter = picker vanish.

View File

@ -18,8 +18,9 @@
| 2.c3 | Cụm-3 K7+K8 — `WF invest-fable b2c3 s164` | /fable-real invest | ✅ SẠCH 22.9KB (K7 8v/8test BÁC tiền-đề 3-lỗ-S156-đã-vá · K8 17 bước · HỐ-1-KHKK mới) |
| 2.c3-r | Review cụm-3 — `WF reviewer c3-{evi,cov,seq} s164` | /fable-clone (opus ×3) | ✅ 3/3 sạch · L1 7/7-ĐÚNG PWF 4M · L2 FAIL 5H (K8 chưa-chạy-được-như-viết) · L3 FAIL 4H (authz hội-2-khóa · Contracts.Create 0-vai) → **spec-cum3-final 16 vá** |
| 3 | Chốt spec cuối — `WF review-fable b3-chot s164` | /fable-real review | ✅ **GO-WITH-SPEC-FIXES**: GO 9/10 (K8 by-design) · 8 mâu-thuẫn (3 nặng A-1/A-4/A-5) · 0 vá-đè-vá/43 · build-order K1→K2→K3→K4a→K4b→K5→K4c→K6→K7→K8 · 8 fix lead ÁP XONG |
| 4 | Fan-out build bám spec+checklist | Opus 5 MAX (hmw) | ☐ |
| 5 | Dry-run E2E toàn trình | theo spec wave cuối | ☐ |
| 4.K1 | Build K1 danh mục HĐ (BE+FE+test) | wf_2569b79b p1 · wf_2e08ba77 p2 · implfe · wf_1fb9f309 | ✅ @S165: 3 lane trọn · gate `sub-reviewer-gate-k1.md` **PASS-WITH-FLAGS 9f** · F4/F7 vá + T5b · Mig 70 áp LocalDB |
| 4.K2-K8 | Build K2..K8 theo build-order B3 | hmw (Opus 5 MAX) | ☐ K2 kế tiếp |
| 5 | Dry-run E2E toàn trình (K8) | theo spec-cum3 | ☐ chặn bởi B0-form 14 người |
## Stages
@ -28,5 +29,5 @@
- [x] B1b — wf_8eb13087-5f9 3/3 lane sạch (l1 FAIL 5H · l2 6/6 chịu-lực ĐÚNG +l2-F1 rào-1-PE · l3 FAIL 6H + bác 6 nghi) → `b1b-review-synthesis.md` 14 vá + OG-6/OG-7 → spec v2 (hội tụ 2-lane: admin-hide tập-key tường minh; ApprovalGroup K2-chủ + backfill=1; 8 designer có phép đo; nguồn K1 = 7 ảnh [file đĩa 6-trang không bảng])
- [x] B2 — 3 cụm TRỌN: c1 (33KB + 12 vá) · c2 (5/5 + 15 vá; l3 chết-ruột phủ-bù) · c3 (22.9KB 7/7-đúng + 16 vá) = 43 vá tổng, 3 spec final
- [x] B3 — GO-WITH-SPEC-FIXES (sub-review-fable-b3.md 6 section; 3 anchor re-grep đĩa khớp; 8 fix áp vào 3 spec) — SPEC ĐÓNG BĂNG cho B4
- [ ] B4 — fan-out build
- [ ] B4 — fan-out build (K1/8 ✅ @S165: gate PASS-WITH-FLAGS 9f, disposition trong sub-reviewer-gate-k1.md; K2..K8 ☐)
- [ ] B5 — dry-run E2E

View File

@ -44,7 +44,7 @@ Chuỗi 4 GĐ per-gói-thầu theo SOL-CCM-SP-002: GĐ1 Duyệt NCC (PE = CHA,
| OG-2 | Roster 8 workflow | ✅ **CHỐT @S164** (*"Đúng đội hình cũ"*) — map đội hình `QT-DN-V2-001`, **verify bản LIVE (không archived) trước seed** | K3 |
| OG-7 | 1 PE → N phiếu | ✅ **CHỐT @S164** (*"PE cho nhiều, nhưng… rào hiển thị 1-1 lại — User cần thì bung ra"*) ⇒ **BE mở N-phiếu/PE (đổi rào 2 site :342/:1201 → (PeId, ApprovalGroup)) + UI default hướng-1**: PE có phiếu rồi → hiện phiếu + nút phụ "Tạo thêm cho nhóm khác" (thay 409 chặn hẳn) | K2 |
| OG-3 | NĐUQ | ✅ **CHỐT @S164** (*"Đồng ý. Vẫn như trước - các quyền kiểu chuyển CEO các thứ y chang vậy luôn, thì ko cần quan tâm mức duyệt, vì a Chương sẽ quyết định là cái đó cần CEO ko hay là anh ấy đc duyệt."*) ⇒ NĐUQ = ghi chú tham khảo; **cơ chế lên-CEO = TÁI DÙNG level-finalize sẵn có của V2** (người trạm CCM — Phan Văn Chương — tự chọn "kết thúc tại cấp" hay trình tiếp, y PE đang chạy: `EndedByLevelFinalize` runtime + checkbox opt-in S97) — 🔄 **SỬA @B2c1 (mâu thuẫn spec↔code, evidence `fe-admin/src/pages/system/ApprovalWorkflowsV2Page.tsx:151-152` `KHKK_EARLY_FINALIZE_NOTE` "backend chưa xử lý đường duyệt sớm"):** cơ chế = tái dùng KHUÔN level-finalize NHƯNG BE KHKK **CHƯA port nhánh** ⇒ K3 có **3 việc code**: (a) port nhánh finalize vào `ContractSigningPlanWorkflowService` (b) cột runtime `EndedByLevelFinalize`#78 (forward-provision vào Mig 71 ⇒ K3 0-mig) (c) gỡ khóa 2 ô Designer FE. Seed `AllowApproverFinalize=true` cấp CCM-Chương CHỈ SAU khi (a) land — kẻo config-lie #78 | — |
| OG-6 | Soát bảng transcribe 86 dòng (gõ tay từ ảnh; 4 ô ⚠️ cuối file) HOẶC anh gửi file 15-trang | ⏳ đã giải thích, chờ anh soát | K1 (seed) |
| OG-6 | Soát bảng transcribe 86 dòng (gõ tay từ ảnh; 4 ô ⚠️ cuối file) HOẶC anh gửi file 15-trang | 🔄 **ĐỔI NHÃN soát-SAU @PAUSE-2 (mandate chạy-1-mạch)** — bảng admin-editable qua Admin CRUD; seeder skip-if-exists nên sửa-tay CRUD **sống qua restart** (update-branch sẽ đè mất sửa của anh — cố ý KHÔNG có); 4 ô ⚠️ chờ anh soát SAU trên UI | — (HẾT chặn K1) |
| OG-8 | Authz per-nhóm | ✅ **CHỐT @S164** (*"Đúng chính xác. Cho hiển thị, phân quyền tính sau."*) — đợt này 8 nhóm = hiển thị + luồng duyệt; XEM/TẠO chung bộ khóa module; authz-per-nhóm = wave sau khi anh gọi | — |
| OG-5 | Extract mơ hồ → hỏi khi gặp | mở | K1 |
| OG-9 | Bật lại duyệt-sớm KHKK = ĐẢO phán quyết "PHÁ VỠ" @S155 | ✅ **CHỐT @S164** (*"đúng, chỗ budget chốt chặn là a Chương, anh ấy sẽ quyết cái j cần quyền CEO và cái j anh ấy đc duyệt"*) — K3 việc (a)-(e) HẾT CHẶN: port nhánh finalize + cột runtime + gỡ khóa FE + sweep ép-false ≥4 site + sửa comment/panel/HANDOFF khai supersede-S155 | K3 mở |

View File

@ -0,0 +1,162 @@
CICD-K1: PASS — 6/6 mục
# cicd-monitor — verify deploy K1 SP-002 (`50e6d8c`)
> Artifact chống #53: ghi TỪNG mục NGAY khi đo xong. Kết bằng dòng `END … VERDICT=`.
> Spawn 2026-07-31 · commit `50e6d8c38283a9dd45da2001bd2fc6596595558f` · READ-only (0 ghi prod)
## Stage 1 — Push + path-filter (PRE-điều-kiện)
- `git log -1``50e6d8c38283a9dd45da2001bd2fc6596595558f|[CLAUDE] Contract: K1 SP-002 danh muc loai HD - ContractCatalogEntries (Mig 70) + seed 86 + CQRS + FE kind-5 x2 app + 8 test|2026-07-31 15:45:50 +0700`
- `git log origin/main..HEAD`**RỖNG** (đã push hết)
- `git diff --name-only 50e6d8c^ 50e6d8c`**20 file** (neo bằng `X^..X` theo LUẬT S162 CẤM-`HEAD~1`, vì phiên có Stop-hook WAL auto-commit):
BE 11 · FE 6 · Test 1 · Docs 2
- vs `paths-ignore` 7-glob: **18/20 file KHÔNG bị ignore** ⇒ CI **PHẢI chạy**. ✅
## Stage 1b — DỰ ĐOÁN TRƯỚC KHI ĐO (chống xác-nhận-ngược)
Đọc `DbInitializer.SeedContractCatalogAsync` (dòng 3163-3325) **trước** khi chạm prod:
- Parse lần 1 (regex line-anchored) → **78** ⇒ SAI (tuple xuống dòng). Vứt, KHÔNG báo cáo số này.
- Parse lần 2 (multiline) → **86**; cross-check độc-lập `raw code-literal` = **86**, distinct = **86**, dup = **[]**.
- Dự đoán `GROUP BY ApprovalGroup`: **1:5 · 2:10 · 3:5 · 4:23 · 5:14 · 6:15 · 7:13 · 8:1**
- Dự đoán `GROUP BY GroupCode`: 11 nhóm — A1:5 A2:1 A3:9 A4:5 B1:23 B2:14 B3:15 C1:6 C2:3 C3:4 CEO:1
- Seeder idempotent (`existingCodes.Contains(code) continue` + `IgnoreQueryFilters`#75/#76) ⇒ deploy đầu `added=86`.
- Serilog dự kiến: `Seeded 86 contract catalog entries (S164 SP-002)` (chỉ ghi khi `added>0`).
## Bundle PRE-deploy (đo LIVE lúc run đang chạy, 15:47)
| App | js | css |
|---|---|---|
| admin | `index-B43R6Y17.js` | `index-BZ5F-g1w.css` |
| eoffice | `index-NLI5umBg.js` | `index-yNZDmVNz.css` |
Khớp nguyên văn hash đề-bài (`B43R6Y17` / `NLI5umBg`) ⇒ mốc "TRƯỚC" hợp lệ.
---
## ✅ Mục 1 — Gitea Actions run
- Run **id=547 · run_number=#434** · head_sha `50e6d8c3` · job `build-deploy` · runner `vps-vietreport-runner v0.2.13`
- created `2026-07-31T15:46:06+07:00` (push 15:45:50 → pick sau **16s**)
- Poll 7 iter × ~40s → iter7 `status=success`, updated `2026-07-31T15:52:37+07:00`
- **Duration ≈ 6m31s** · `conclusion=None` (tasks API không populate — tin `status`, đúng khuôn đã biết)
- URL: https://git.baocaogiaoduc.vn/vietreport-admin/solution-erp/actions/runs/434
- **PASS**
## ✅ Mục 2 — Test gate 598
Nguyên văn từ web-UI log (`/actions/runs/434/jobs/0/logs`, 18.698 B):
```
2026-07-31T08:46:34.2961110Z Passed! - Failed: 0, Passed: 45, Skipped: 0, Total: 45, Duration: 196 ms - SolutionErp.Domain.Tes…
2026-07-31T08:49:26.8226799Z Passed! - Failed: 0, Passed: 553, Skipped: 0, Total: 553, Duration: 1 m 44 s - SolutionErp.Infrastr…
```
- Domain **45** + Infra **553** = **598** — khớp baseline MỚI đề bài (590+8). Đối chiếu TÁCH-PHẦN: Domain **45 GIỮ NGUYÊN**, Infra **545→553 = +8** ⇒ đúng "7 lane K1 + T5b gate-fix" (delta rơi trọn vào Infra, đúng nơi file test mới nằm).
- 0 fail / 0 skip cả 2 project. `Failed!`/`error CSxxxx` = **0 hit**.
- ⚠️ Ghi chú phương-pháp: grep đầu tiên **0 hit** do format nhiều-khoảng-trắng (`Passed! - Failed: 0`); đã soi lại format thật rồi mới trích — KHÔNG báo "không thấy dòng test".
- **PASS**
## ✅ Mục 3 — EF migration prod
```
20260731072441_AddContractCatalogEntries ← TOP
20260729122015_AddContractSigningPlans
20260727033522_AddPeAllowApproverDelete
20260717032812_AddPeApprovedBudgetSnapshot
```
- `__EFMigrationsHistory` TOP == repo HEAD mig ⇒ **Mig 70 applied prod**.
- Chân-kiềng (i) chứng-minh-restart-thật: `w3wp` PID **2644 StartTime 7/31/2026 3:52:13 PM** (trong cửa-sổ deploy 15:46:06→15:52:37) + dòng ĐẦU Serilog `2026-07-31 15:52:19.770 +07:00 [INF] Applying migrations...` ⇒ mọi số DB dưới đây đo **SAU** recycle (né đúng lỗi #423).
- **PASS**
## ✅ Mục 4 — Seed prod 86 + phân bố 8 nhóm
`sqlcmd` (SQL-auth `vrapp`, sau recycle):
| Đo | Kết quả |
|---|---|
| `COUNT(*) FROM ContractCatalogEntries` | **86** |
| `GROUP BY ApprovalGroup` | **1:5 · 2:10 · 3:5 · 4:23 · 5:14 · 6:15 · 7:13 · 8:1** |
| `COUNT(DISTINCT Code)` | **86** (0 trùng) |
| `IsActive=1` | **86** · `IsDeleted=1` = **0** |
| `COUNT(DISTINCT GroupCode)` | **11** |
Serilog (`solution-erp-20260731.log`) nguyên văn:
```
2026-07-31 15:52:27.074 +07:00 [INF] Seeded 86 contract catalog entries (S164 SP-002)
2026-07-31 15:52:25.867 +07:00 [INF] Seeded 1 menu items
2026-07-31 15:52:26.030 +07:00 [INF] Seeded 1 admin permissions
```
- Control ÂM: `findstr "contract catalog KHONGTONTAI"`**0 hit** ⇒ findstr có răng, không phải khớp bừa.
- Đối chứng: 2 dòng `Seeded 1 menu items` / `Seeded 1 admin permissions` = menu-row `ContractCatalog` + permission cũng vào (leaf `Catalogs` thứ 5).
- 🔑 **TAM-GIÁC 3 NGUỒN ĐỘC-LẬP khớp từng đồng**: source-parse (86 / 8 nhóm) == sqlcmd prod (86 / 8 nhóm) == **API materialize qua EF** (86 / 8 nhóm, xem Mục 6). Không nguồn nào suy ra nguồn kia.
- **PASS**
## ✅ Mục 5 — Bundle đổi ×2 app (#77 byte-level)
**Vế DƯƠNG — hash MỚI là asset THẬT:**
| URL | code | content-type | size |
|---|---|---|---|
| admin `/assets/index-CKgq0YlH.js` | 200 | `application/javascript` | **1.751.576** |
| admin `/assets/index-DD3xQhNt.css` | 200 | `text/css` | 86.298 |
| eoffice `/assets/index-CHMCU35C.js` | 200 | `application/javascript` | **1.667.782** |
| eoffice `/assets/index-CdXRNT7E.css` | 200 | `text/css` | 92.092 |
**Vế ÂM — hash CŨ phải BIẾN MẤT khỏi đĩa** (né SPA-fallback-200-trap S72, discriminator = `Content-Type` không phải status):
| URL cũ | code | content-type | size |
|---|---|---|---|
| admin `index-B43R6Y17.js` | 200 | **`text/html`** | 900 |
| admin `index-BZ5F-g1w.css` | 200 | **`text/html`** | 900 |
| eoffice `index-NLI5umBg.js` | 200 | **`text/html`** | 876 |
| eoffice `index-yNZDmVNz.css` | 200 | **`text/html`** | 876 |
⇒ cả 4 rơi về SPA-shell 876/900 B (trùng khít size index.html) = file cũ đã bị ghi đè thật, KHÔNG phải cache.
**Ship-proof `Last-Modified` in-window** (#69 TWO-MODE — hash-delta một mình không đủ):
- admin `/``Last-Modified: Fri, 31 Jul 2026 08:51:06 GMT` = **15:51:06 +07** ✔ in-window · `Cache-Control: no-cache`
- eoffice `/``Last-Modified: Fri, 31 Jul 2026 08:52:07 GMT` = **15:52:07 +07** ✔ in-window · `Cache-Control: no-cache`
**4/4 hash ROTATE + 4/4 hash cũ chết + LM in-window. PASS**
## ✅ Mục 6 — Smoke
| Check | Kỳ vọng | Đo được | KL |
|---|---|---|---|
| `GET /health` | (bare) | **404** | control — route bare không tồn tại |
| `GET /health/ready` | 200 | **200** | ✅ |
| `GET /health/live` | 200 | **200** | ✅ |
| `GET /api/catalogs/contract-catalog` **KHÔNG token** | 401 | **401** | ✅ authz sống |
| CONTROL `…/contract-catalog-KHONGTONTAI` | 404 | **404** | ✅ 401 CÓ NGHĨA |
| CONTROL `…/bia-dat-hoan-toan` | 404 | **404** | ✅ (route bịa ⇒ 401 ở trên không phải blanket) |
| `https://admin.solutions.com.vn/` | 200 | **200** | ✅ |
| `https://eoffice.solutions.com.vn/` | 200 | **200** | ✅ |
| **CÓ token admin** `GET …/contract-catalog` | 200 + 86 row | **200, 25.374 B, 86 row** | ✅ |
Cross-check EF-materialize (mạnh hơn sqlcmd vì đi qua Domain→Infra→Api thật):
```
rows materialized via EF = 86
by approvalGroup: 1:5 . 2:10 . 3:5 . 4:23 . 5:14 . 6:15 . 7:13 . 8:1
distinct codes = 86
sample row: code=A1-01 group=1 signer=TP.CCM
```
Class-level `[Authorize]` + ghi `[Authorize(Roles="Admin")]` (đọc `ContractCatalogController.cs:21-22,39-56`) ⇒ 401-không-token đúng thiết-kế, không phải #44 silent-403.
- **PASS**
---
## Ghi chú lệch / quan sát (KHÔNG phải FAIL)
1. **Hash CI ≠ hash local build.** Đề bài dự-báo local ra admin `BE7zE81C`/user `DSXJJbJq`; CI ra `CKgq0YlH`/`CHMCU35C`. Tiêu chí đề bài (ĐỔI so với trước + curl 200) **thoả**. Nghĩa: artifact CI khác byte với artifact local (thường do Node minor / cây npm resolve khác) — bình thường, nhưng ⇒ **đừng bao giờ dùng hash local làm mốc kỳ vọng cho prod**, luôn re-snapshot LIVE.
2. **Duration 6m31s** — dài nhất 4 run gần đây (#432 5m50s · #433 5m55s · #431 5m50s). Chưa tới ngưỡng escalate; hợp lý vì run này có thêm Mig 70 + seed 86 row + 8 test mới. Theo dõi tiếp, chưa cần hành động.
3. **`Passed!` grep 2-khoảng-trắng** — lần grep đầu 0-hit là DƯƠNG-GIẢ do format; đã soi format rồi mới kết luận (bài học: 0-hit phải kèm control-dương trước khi báo).
4. `[WRN]` global-query-filter (ContractSigningPlan/Contract ↔ Approval/Changelog/LevelOpinion) là warning EF **có sẵn từ trước** (Mig 69 S161), không do commit này sinh ra.
## Kết luận
Không có critical issue. Không đề xuất rollback. 0 thao tác ghi lên prod.
END sub-cicd-verify-k1 — VERDICT=PASS 6/6 (run #434 id=547 success 6m31s · gate 598=45D+553I · Mig 70 top prod · seed 86/8-nhóm khớp 3 nguồn · bundle 4/4 rotate + 4/4 hash cũ chết · smoke 8/8 + 401 có-nghĩa)

View File

@ -0,0 +1,156 @@
K2-BE: DONE — build 0W/0E · test 598/0 (45D+553I; 3 test cũ update-theo-spec, 0 test mới)
# sub-implbe-k2 — K2 BACKEND (S164, wave 2/8, SP-002 phiếu mang hạng-mục + nhóm)
> Luật đan-xen (bài K1): làm mục nào GHI NGAY mục đó. Dòng-1 file = verdict, cập nhật cuối.
> Nguồn: spec-cum1 (12 vá) + spec-tổng-quát (QĐ-1/2/9 + OG-1/7) + nền `sub-invest-fable-b2-cum1.md` §K2.
## §0 Tiền đề — git status TRƯỚC mọi Write (bài K1 sự-cố suýt-đè)
- `git status --porcelain` @start: `sub-implbe-k2.md` **KHÔNG tồn tại** (run-folder có k1/implfe/test/reviewer/cicd, không có k2) ⇒ Write mới AN TOÀN.
- Baseline nhận từ lead: Mig 70 land (`50e6d8c`), suite 598/0 (45D+553I), MenuKeys.All=56, policies=224.
- Nhánh: `main`.
## §0-bis Anchor re-verify 5/5 (lead giao @S165 — ĐO LẠI, không tin sổ)
| Anchor lead giao | Đo thật | Khớp |
|---|---|---|
| `Features.cs:342-347` rào hasLivePlan | `:342 var hasLivePlan = await db.ContractSigningPlans``:347` message | ✅ |
| `Features.cs:622-626` ListContractSigningPlansQuery | `:622 public record ListContractSigningPlansQuery(``:626 bool PendingMe = false) : PagedRequest` | ✅ |
| `Features.cs:1201-1202` picker ApprovedPeAwaitingPlan | `:1201-1202 && !db.ContractSigningPlans.Any(pl => ... pl.Phase != TuChoi)` | ✅ |
| `Line.cs:17-20` PeReferenceAmount snapshot + `:25` ContractId | comment `:17-19`, prop `:20`; `:25 public Guid? ContractId` | ✅ |
| `WorkflowService` ApplyApprovedValuesOnFinalize `??=` ~`:301-305` | `:301` signature, `:303` foreach, `:304 line.ApprovedAmount ??= line.ProposedAmount` | ✅ |
Bonus đo thêm (không ai giao, cần cho mục e): **KHÔNG tồn tại đường ghi Line nào** ngoài auto-gen `:389`
(`grep -rn "\.Lines" src/Backend` = 10 hit, chỉ 1 hit ghi). Hệ quả nặng → xem §e.
---
## §a Mig 71 `AddKhkkApprovalGroupCatalogAndFinalizeRuntime` — DONE
**File 3/3 (gotcha #17):**
- `src/Backend/SolutionErp.Infrastructure/Persistence/Migrations/20260731085624_AddKhkkApprovalGroupCatalogAndFinalizeRuntime.cs`
- `…20260731085624_AddKhkkApprovalGroupCatalogAndFinalizeRuntime.Designer.cs`
- `…/ApplicationDbContextModelSnapshot.cs` (M)
**6 item trong Up() — 0 RenameColumn, 0 CreateTable:**
| # | Lệnh | Dòng |
|---|---|---|
| 1 | `AddColumn<int> ApprovalGroup` (ContractSigningPlans) + **backfill** | `:52-58` + `:69` |
| 2 | `AddColumn<bool> EndedByLevelFinalize` default false | `:71-76` |
| 3 | `AddColumn<Guid> CatalogEntryId` nullable (Lines) — loose-Guid, 0 FK | `:78-82` |
| 4 | `AddColumn<string> TenHangMuc` nvarchar(500) nullable | `:84-89` |
| 5 | `DropIndex` unique cũ `…_ContractSigningPlanId_SupplierId``CreateIndex` UNIQUE `(Plan,Supplier,CatalogEntry)` filter `[IsDeleted] = 0` | `:44-46` + `:101-106` |
| 6 | +2 IX tra cứu: `(ApprovalGroup, Phase)` header · `CatalogEntryId` dòng | `:91-99` |
**Backfill (task yêu cầu `Sql UPDATE` trong Up):** `:69` `migrationBuilder.Sql("UPDATE [ContractSigningPlans] SET [ApprovalGroup] = 1;")`
— khuôn Mig 64 (`Sql()` trần, KHÔNG provider-guard, khuôn Mig 61/65/67). **VÔ ĐIỀU KIỆN** (bỏ `WHERE ApprovalGroup = 0`)
CÓ CHỦ ĐÍCH: rowcount phải == số phiếu cũ; viết WHERE thì backfill hụt trông y hệt backfill thành công (bài Goodhart/vắng-mặt).
**Vá-9 khai ĐÚNG mắt xích (comment `:36-51` trong file mig):** unique cũ CHẾT trong chính Mig 71. Index MỚI vẫn chặn
cặp NULL-trùng (SQL Server: NULL == NULL trong unique index) nhưng chặn bằng lỗi 2601 lúc SaveChanges = 500 khó hiểu,
KHÔNG phải bằng nghiệp vụ. Mắt xích THẬT = **(a) validator đường-gán bắt buộc CatalogEntryId + (b) SUBMIT-GUARD**.
Dữ liệu cũ an toàn KHÔNG cần backfill dòng: unique cũ đã bảo đảm ≤1 dòng/cặp ⇒ tối đa 1 NULL/cặp.
CỐ Ý **không** thêm `AND [CatalogEntryId] IS NOT NULL` vào filter (= thả trôi vô hạn dup).
**Vá-7 item-6 ghost-window** khai trong comment mig `:19-24` + entity `ContractSigningPlan.cs`: K2 ĐẺ cột
`EndedByLevelFinalize`, K3 TIÊU THỤ; từ nay tới K3 land **0 site ghi** ⇒ luôn false, và đó ĐÚNG hiện trạng
(service chỉ có 1 nhánh set DaDuyet, là đường thường) — không phải dữ liệu hỏng.
**Apply 2 DB (feedback_designtime_runtime_db):**
- Design: `Applying migration '20260731085624_…'` → OK
- Dev: **drift committed-chưa-apply** — Dev đang ở Mig 69, thiếu Mig 70 (K1 land nhưng chưa apply local)
⇒ catch-up 2 mig 1 lượt: `Applying '20260731072441_AddContractCatalogEntries'` + `Applying '20260731085624_…'` → OK
**Đo sau apply (sqlcmd, cả 2 DB giống hệt):**
```
tables=97 · lastmig=20260731085624_AddKhkk… · cols=ApprovalGroup,EndedByLevelFinalize
linecols=CatalogEntryId,TenHangMuc
uniqueIX=PK_ContractSigningPlanLines | filter=(none)
uniqueIX=IX_ContractSigningPlanLines_ContractSigningPlanId_SupplierId_CatalogEntryId | filter=([IsDeleted]=(0))
```
⇒ unique cũ (2 cột) KHÔNG còn trong `sys.indexes` = index swap có thật, không phải thêm chồng.
Bảng 96→**97** là do **Mig 70 (K1)** vừa catch-up, KHÔNG phải Mig 71 (Mig 71 = 0 CreateTable).
**Σ tiền TRƯỚC == SAU (chứng 2 tầng, khai ĐỘ MẠNH thật):**
- Tầng CẤU TRÚC (mạnh): `grep -c "PeReferenceAmount\|ProposedAmount\|ApprovedAmount"` trong file mig = **0**
⇒ bất-biến đúng theo cấu trúc, không phụ thuộc phép đo.
- Tầng PHÉP ĐO (yếu, khai thẳng): Dev & Design đều **0 dòng KHKK**`SUM(PeReferenceAmount)` = NULL/tập RỖNG
cả trước lẫn sau. Đây là **control rỗng**, KHÔNG phải bằng chứng có răng — răng thật nằm ở phép đo prod
(chưa chạy được ở lane này). Ghi đúng độ mạnh thay vì dán nhãn "PASS" (bài "trích rỗng đọc thành sạch").
- `plans=0` ⇒ backfill chạy trên 0 row local; acceptance `COUNT == Σ 8 folder` **chỉ chứng được trên prod**.
## §b Entity + EF Configuration khớp mig — DONE
- `src/Backend/SolutionErp.Domain/ContractSigningPlans/ContractSigningPlan.cs``+ApprovalGroup int = 1`
(khai "K2 là CHỦ DUY NHẤT, K3 chỉ đọc" — QĐ-3/spec §②-3) · `+EndedByLevelFinalize bool` (ghost-window + né #78).
- `src/Backend/SolutionErp.Domain/ContractSigningPlans/ContractSigningPlanLine.cs``+CatalogEntryId Guid?`
(loose-Guid, khuôn `PE.WorkItemId` Mig 49 + chính `SupplierId` cùng file) · `+TenHangMuc string?` (denorm, khuôn `SignedByFullName` Mig 26).
- `…/Configurations/ContractSigningPlanConfiguration.cs``+HasIndex(ApprovalGroup, Phase)`.
🔴 **CỐ Ý KHÔNG dựng unique (PeId, ApprovalGroup)**: vị ngữ của rào là `Phase != TuChoi` (phiếu từ chối KHÔNG
chiếm chỗ) — unique DB không diễn tả được vế đó, dựng vào = chặn nhầm đường lập lại hợp lệ. Rào sống ở handler.
- `…/Configurations/ContractSigningPlanLineConfiguration.cs``TenHangMuc.HasMaxLength(500)` (== `ContractCatalogEntry.TenVi`,
EF = source of truth cho validator, bài S35) · index swap 2→3 cột · `+HasIndex(CatalogEntryId)`.
---
> 🔴 **KHAI VẾT #53 ×2 trên task này (p1 wf_583be7ad + p2 relaunch):** p1 chết giữa §c (return rỗng
> no-StructuredOutput); p2 làm TIẾP trên code THẬT (rào/DTO/query/picker — đo bằng grep+diff) nhưng
> **0 byte vào artifact này** (vi phạm ghi-trong-lúc-làm) và chết ở §f với return ý-định. §c-h dưới đây
> = **lead on-behalf SAU VERIFY-ĐĨA** (build+test thật, đường (0) WRITE-lane recovery) — không phải lời lane.
## §c Rào + DTO + call-site — DONE (p2 ~85% + lead 3 hằng Scope)
- Rào `:384-391`: hasLivePlan thêm `&& p.ApprovalGroup == approvalGroup` — (PeId, Group) 2 vế + comment
"CỐ Ý KHÔNG unique DB (vị ngữ Phase != TuChoi)". Message 409 mới. [p2]
- DTO: LineDto `+CatalogEntryId +TenHangMuc` (`:78-79`) · DetailDto `+ApprovalGroup` (`:678`) + display-gate
DaDuyet (`:683`) · ListItemDto `+ApprovalGroup` (`:161`, map `:792`). Call-site CS7036 :589/:627/:709 đã sửa. [p2]
- `ContractSigningPlanScope` +3 hằng `Min/Max/DefaultApprovalGroup` (1/8/1) — p2 tham chiếu mà chưa định
nghĩa (3 CS0117); lead thêm. [lead]
## §d Validator + submit-guard — DONE (p2 validator + lead guard)
- CreateCommand `+ApprovalGroup int?` default→1 (`:387` — UI-1-1 hướng-1 OG-1/OG-7) + validator
InclusiveBetween qua Scope (`:343-346`). Auto-gen line mồi `CatalogEntryId=null, TenHangMuc=null` (`:448-449`)
— gán hạng mục = Lines-editor **K4b** (nền `:113`), K2 không đẻ đường ghi line mới. [p2]
- **Submit-guard** `ContractSigningPlanWorkflowService.SubmitAsync` (sau EnsureCanSubmit): (i) CHẶN null
CatalogEntryId — **vá-5 THẮNG nền `:126`** (nền định CHO-QUA null-legacy); ghost-window K2→K4b khai
trong comment: phiếu cũ bị Trả-lại sẽ kẹt trình tới khi K4b land, message chỉ rõ việc cần làm;
(ii) khác-nhóm → 409 theo code mẫu nền K2.d. [lead]
## §e Phase-guard line-edit sau DaDuyet — DONE-BY-DESIGN (ẩn số p1 giải)
- p1 đo: **0 đường ghi Line** ngoài auto-gen `:439` (create-only). Đường line-write DUY NHẤT tương lai
= Lines-editor K4b. Gate hiện hữu `ContractSigningPlanScope.EnsureDraftEditable` (`:230+`) chỉ cho
{DangSoanThao, TraLai} ⇒ **DaDuyet tự bị loại** ở MỌI đường ghi dùng Scope-guard (khuôn "4 đường ghi
dùng chung"). K2 không dựng guard mồ-côi (ghost-wire); ràng buộc ghi vào checklist K4b: **editor PHẢI
qua EnsureDraftEditable** + K2-test lane thêm test khẳng định DaDuyet bị chặn.
## §f List + Σ — DONE (p2 + lead controller)
- Query `+ApprovalGroup int?` (`:699`) + filter (`:731-732`) — K2 là CHỦ param (QĐ-3). [p2]
- Σ×3 list: PeReference (`:788`) · Proposed (`:790`) · **+Approved sau freeze** (`:797-798`, vá-8 comment
`:793-796`). [p2]
- Controller List `+[FromQuery(Name="group")] int? group` → query. API = `?group=n` đúng chữ spec. [lead]
## §g Bảng 18 consumer PeReferenceAmount/Σ — disposition
Nền K2.f đã grep-verify 16 site (10 BE + 6 FE, đối chiếu l3-F18) + 2 site Σ mới (`:712-713` nay
`:788-798`). Mig 71 **0 đụng cột tiền** (chứng cấu-trúc: grep PeReference/Proposed/Approved trong file
mig = 0 hit — p1 §a). Σ trước==sau: local 0-row (control rỗng, khai đúng độ mạnh — p1); phép đo THẬT
= prod post-deploy (cicd K2 sẽ đo). Consumer FE (6 site) đổi theo ở K2-FE/K4b — bảng chi tiết sống ở
nền `sub-invest-fable-b2-cum1.md` §K2.f, không chép lại (B1).
## §h Build + test — DONE (lead, trọng-tài sau khi mọi tay dừng)
- `dotnet build` **0W/0E** (20.71s) · `dotnet test` **598/0** = 45D + 553I.
- 3 test cũ update-theo-spec (spec-change = test+code chung commit): `Line_UniqueFilteredTheoIsDeleted…`
(unique 2→3 cột; cả 2 vế dùng CatalogEntryId non-null — khai NULL-semantics SQLite≠SQL-Server, cặp
NULL do submit-guard chặn tầng nghiệp vụ; **+vế MỞ mới**: cùng (Plan,NCC) khác hạng mục = QUA, đúng
mục đích Mig 71) · `ApproveV2_TwiceSameLevel…` + `SendBack_Resubmit…` (fixture SeedPlanAsync +1 row
danh mục nhóm-1 + gán CatalogEntryId/TenHangMuc mọi line — mirror phiếu đã-gán-đủ).
- k test MỚI = 0 (suite K2 blueprint = lane test-specialist, đang phóng).
END sub-implbe-k2 — VERDICT=DONE (Mig71 applied 2-DB · rào (PeId,Group) · submit-guard vá-5-thắng-nền ·
list ?group= + Σ-Approved · build 0W/0E · suite 598/0 · #53 ×2 khai trên)

View File

@ -0,0 +1,179 @@
K2-FE: DONE — build ×2 PASS · SHA-pair 5/5
# sub-implfe-k2 — K2 FRONTEND (S164, wave 2/8, SP-002 phiếu mang hạng-mục + nhóm)
> Luật đan-xen (bài K1 + #53): làm mục nào GHI NGAY mục đó. Dòng-1 = verdict, cập nhật CUỐI.
> Nguồn: `sub-implbe-k2.md` (BE đã land) + spec-cum1 vá-4 + spec-tổng-quát QĐ-1/OG-1/OG-7 + nền `sub-invest-fable-b2-cum1.md` §K2.f.
## §0 Tiền đề — ĐO TRƯỚC MỌI WRITE (không tin sổ)
- `git status --porcelain` @start: `sub-implfe-k2.md` **KHÔNG tồn tại** trong run-folder (có k1/implbe-k2/test/reviewer/cicd) ⇒ Write mới AN TOÀN, không đè lane khác.
- **0 file `fe-*/src` nào đang M** trong git status @start ⇒ mọi thay đổi FE dưới đây là của lane này, git-diff sẽ quy trách nhiệm đúng.
- BE lane đã land (đọc `sub-implbe-k2.md`): Mig 71 applied 2-DB · rào (PeId,Group) · submit-guard · list `?group=` · Σ-Approved · build 0W/0E · suite 598/0.
- 🔴 CẤM theo task: đụng BE/.cs · chạy dotnet (lane test song song) · route/menu mới (K4a) · sửa spec.
### §0.a SHA baseline 5 cặp KHKK FE (TRƯỚC khi sửa) — cả 5 IDENTICAL
| File (`fe-*/src/…`) | admin (12 ký tự đầu SHA256) | user | Trạng thái |
|---|---|---|---|
| `types/khkk.ts` | `8b757f5b1422` | `8b757f5b1422` | IDENTICAL |
| `pages/khkk/KhkkCreatePage.tsx` | `ea51e7626eb1` | `ea51e7626eb1` | IDENTICAL |
| `pages/khkk/KhkkDetailPage.tsx` | `c8ab2eadd3fd` | `c8ab2eadd3fd` | IDENTICAL |
| `pages/khkk/KhkkListPage.tsx` | `40a650bf6964` | `40a650bf6964` | IDENTICAL |
| `pages/khkk/KhkkWorkflowPanel.tsx` | `2b64144ef6e8` | `2b64144ef6e8` | IDENTICAL |
⇒ Chiến lược: sửa **fe-admin**`cp` sang **fe-user** → đo lại SHA (khuôn cookie-cutter, 9× proven). Không có UX riêng 2 app trong 5 file này (đối chứng: baseline identical).
### §0.b Hình dạng DTO BE ĐỌC TỪ MÃ THẬT (không đoán — task ép)
- `ContractSigningPlanListItemDto` `ContractSigningPlanFeatures.cs:58-64`: append-CUỐI **`int ApprovalGroup`** + **`decimal TotalApprovedAmount`** (vá-8 — Σ thứ 3).
- `ContractSigningPlanLineDto` `:66-79`: append-CUỐI **`Guid? CatalogEntryId`** + **`string? TenHangMuc`** (denorm).
- `ContractSigningPlanDetailDto` `:130-166`: append-CUỐI (SAU cả các List) **`int ApprovalGroup`** + **`bool BudgetFrozen`** (display-gate, KHÔNG phải cột DB).
- `ApprovedPeAwaitingPlanDto` `:169-191`: append-CUỐI **`List<int> ExistingGroups`** (nhóm của các plan `Phase != TuChoi`; rỗng = PE chưa có phiếu). Picker **KHÔNG còn loại PE-đã-có-phiếu** (`:1291-1295` gỡ vế `!Any(...)`).
- `CreateContractSigningPlanCommand` `:333-337`: `PeId, ApprovalWorkflowId, GhiChu?, **int? ApprovalGroup = null**`**null ⇒ nhóm 1** (`:393`); validator `InclusiveBetween(1,8)` CHỈ khi có giá trị (`:349-353`).
- `ListContractSigningPlansQuery` `:700-705` `+int? ApprovalGroup`; controller expose **`?group=`** (BE lane §f).
- 409 message create (`:400-402`): *"Phiếu Duyệt NCC này đã có kế hoạch ký kết cho nhóm duyệt N{g}. Chọn nhóm duyệt khác, hoặc xoá/để bị từ chối kế hoạch cũ của nhóm này trước khi lập lại."*
> 🔴 **[lead @S165] p1 CHẾT ngay sau §0.b** — 4 dòng markup tool-call rò vào đuôi file (sub-class #53 MỚI:
> content-Write lẫn cú pháp invoke), lead đã cắt. Disk-truth p1: fe-admin 4 file M (types +69 · CreatePage +177
> mid-wire [unused: select-nhóm + nút-phụ + badge chưa vào JSX] · Detail +33 · List +1); fe-user CHƯA cp.
> p2 tiếp từ đây — append §1 trở xuống.
---
# p2 (relaunch @S165) — tiếp từ §0.b
## §1 Wire CreatePage — ĐO ĐĨA TRƯỚC, KHÔNG TIN SỔ
🔴 **Tiền đề của lead SAI MỘT PHẦN (đã đo lại bằng `git diff`, không đọc lại sổ):**
lead khai CreatePage "GIỮA CHỪNG WIRE, các khai báo chưa vào JSX". Đo thật trên đĩa
(`git diff -- fe-admin/src/pages/khkk/KhkkCreatePage.tsx`, 177 dòng +) thì **8/8 khai báo
lead liệt là unused đều ĐÃ có call-site trong JSX**:
(số dòng dưới = đo bằng `grep -n` trên file SAU khi p2 xong, không phải trí nhớ)
| Khai báo lead nói "chưa vào JSX" | Call-site THẬT trên đĩa (fe-admin) |
|---|---|
| `KHKK_APPROVAL_GROUPS` | `firstFreeGroup()` `:55` + `.map` dựng 8 `<option>` `:281` |
| `KHKK_APPROVAL_GROUP_DEFAULT` | `useState<number>(...)` `:62` |
| `KHKK_APPROVAL_GROUP_LABELS` | `title=` badge nhóm-đã-dùng `:232` + text `<option>` `:285` |
| `approvalGroup` / `setApprovalGroup` | body POST `:99` · `value=` select `:277` · `onChange` `:278` |
| `firstFreeGroup` | `selectPe()` `:131` + `const free` mỗi dòng PE `:180` |
| `groupTaken` | cảnh báo đỏ `:291-293` · `disabled` nút Lưu `:360` |
| `selectPe` | `onClick` dòng PE `:185` + `onClick` nút "Tạo thêm cho nhóm khác" `:240` |
| icon `Layers` / `Plus` | `<Layers>` label nhóm `:272` · `<Plus>` nút phụ `:241` |
**Suy đoán về nguyên nhân lệch:** p1 chết ở lượt Write CUỐI (rò markup tool-call), nên
lead nhìn thấy transcript dở-dang và kết luận theo Ý ĐỊNH đang thực thi, không theo đĩa.
Đây đúng khuôn "WRITE-lane garble ≠ mất code" (MEMORY S114) — **code đã LAND, chỉ báo cáo
chết**. p2 KHÔNG viết lại CreatePage (viết lại = phá thứ đang đúng).
**Nội dung K2 mà CreatePage đã có (đọc để xác nhận đúng spec, không phải đọc để chép):**
- vá-4: `<select id="approvalGroup">` N1N8, default 1 (`KHKK_APPROVAL_GROUP_DEFAULT`),
option nhóm đã dùng bị `disabled` + hậu tố "(đã có kế hoạch)"; header section đổi
"Quy trình duyệt" → "Nhóm duyệt & quy trình", lưới 1→2 cột `md:grid-cols-2`.
- body POST `approvalGroup` gửi TƯỜNG MINH (không dựa default ngầm `CreateCommand:337`).
- OG-7 UI-hướng-1: dòng PE **không còn bị ẩn** khi đã có kế hoạch → hiện hàng chip
`N{g}` (nhóm đã dùng) + nút phụ **"Tạo thêm cho nhóm khác"**; hết 8 nhóm → chữ
"Đã dùng hết 8 nhóm duyệt" (không mời bấm vào thứ chắc chắn 409).
- `<button>` PE bọc trong `<div key>` phụ vì nút phụ **không được lồng trong `<button>`**
(HTML không hợp lệ) — p1 đã xử đúng.
- Rào cứng vẫn ở BE (`Features.cs:395-402` 409); FE chỉ `disabled` nút Lưu khi `groupTaken`.
**§1 = 0 dòng sửa mới.** Việc thật của p2 dồn vào §2 (ListPage) + §3 (cp) + §4 (build/SHA).
## §2 Detail / List
**KhkkDetailPage — cũng ĐÃ LAND ĐỦ (0 dòng sửa mới, +33 của p1):**
- badge `N{approvalGroup}``PageHeader actions`, ĐỨNG TRƯỚC badge phase, `title` = nhãn
nghiệp vụ (`:298-304`), guard `!= null` cho payload cache cũ.
- cột **"Hạng mục"** trong bảng Lines (`<th>` `:460`, `<td>` `:472``tenHangMuc` rỗng ⇒
`—` xám) + 1 câu chú thích "ô gán thuộc bước Lines-editor" (K4b) ở cuối bảng `:485`.
- badge `🔒 Giá đã chốt tại thời điểm duyệt` khi `budgetFrozen` `:443` — gắn qua prop `actions`
của `SectionCard`. ✅ đã kiểm: `actions?: ReactNode` là prop **CÓ SẴN** `:63` (không
phải p1 tự bịa), render `{actions && <span className="ml-auto">…}` `:82`.
**KhkkListPage — ĐÂY LÀ CHỖ THẬT SỰ DỞ (p1 chỉ kịp thêm dòng import).** p2 sửa:
1. `:240-246` badge `N{g}` **trong ô "Mã kế hoạch"**, không đẻ cột riêng — bảng này đã
cuộn ngang ở 1366 khi có cây GĐ 19rem bên trái (ghi chú S162 ngay trong file); guard
`!= null` + `title` = `KHKK_APPROVAL_GROUP_LABELS[k.approvalGroup]` `:243` ⇒ **import
treo đã có người dùng** (đây chính là dòng làm build đỏ TS6133 nếu bỏ mặc).
2. `:190` +cột **"Tổng duyệt"** (Σ `totalApprovedAmount`) và `:271-273` ô giá trị
(emerald, `tabular-nums`). Phiếu chưa duyệt BE trả **0** ⇒ ép hiện `—` (`k.totalApprovedAmount ? … : '—'`),
không in "0 đ" gây đọc nhầm "đã duyệt 0 đ".
3. `colSpan={7}`**`{8}`** hai chỗ (hàng "Đang tải..." `:198` + hàng rỗng `:205`) —
quên chỗ này thì 2 hàng đó thụt 1 ô, lỗi câm không ai thấy tới lúc bảng rỗng.
*(Cột này KHÔNG có trong gạch đầu dòng của lead, nhưng là **vá-8 spec-cum1** — "list
thêm Σ Approved sau freeze để list/detail không lệch số" — và BE đã trả field
`ListItemDto:64`; khai lệch ở phần cuối.)*
-**KHÔNG** thêm ô lọc `?group=` ở list: K2 là CHỦ tham số nhưng **chỗ lọc thuộc K4b/K5**
(leaf nhóm + cây 8 folder) — đẻ chỗ lọc thứ hai bây giờ là giẫm chân wave sau.
## §3 cp sang fe-user (khuôn cookie-cutter)
`cp` 4 file admin → user, KHÔNG gõ tay lần hai (gõ tay = đẻ lệch không ai thấy):
`types/khkk.ts` · `pages/khkk/{KhkkCreatePage,KhkkDetailPage,KhkkListPage}.tsx`.
- **`KhkkWorkflowPanel.tsx` KHÔNG cp** vì K2 không sửa nó — và đó là **đối chứng dương**
của phép đo §4: hash panel sau lượt này phải BẰNG ĐÚNG baseline §0.a `2b64144ef6e8`.
Nếu nó cũng "identical" mà lệch baseline thì phép đo của tôi vô nghĩa.
- Không import mới nào ngoài `lucide-react` (`Layers`,`Plus`) + const trong chính
`types/khkk.ts` đã cp ⇒ không có rủi ro "fe-user thiếu component" (bẫy Card/Badge quen).
- `git diff --stat` **ĐỐI XỨNG 2 app**: `269 insertions(+), 36 deletions(-)`, từng file
khớp số dòng (177 / 33 / 26 / 69). Lệch số ở đây = cp sót, không cần đợi tới SHA.
## §4 Build ×2 + SHA-pair — TRỌNG TÀI
`npm run build` = **`tsc -b && vite build`** (`package.json:11` cả 2 app) và
`tsconfig.app.json:19 noUnusedLocals: true`**build LÀ phép thử TS6133**: import treo
`KHKK_APPROVAL_GROUP_LABELS` ở ListPage chắc chắn làm đỏ, không phải "IDE dọa" (#68).
| App | Kết quả | Bundle | Thời gian |
|---|---|---|---|
| fe-admin | ✅ PASS 0 TS error | `dist/assets/index-DORfkFnc.js` 1,722.60 kB | 23.10s |
| fe-user | ✅ PASS 0 TS error | `dist/assets/index-qYI2yszp.js` 1,638.80 kB | 8.80s |
Warning cả 2 app = **pre-existing**, không phải do lượt này: `@import` phải đứng trước
rule khác (font Google trong CSS) · chunk > 500 kB · `realtime.ts` vừa dynamic vừa
static import từ `NotificationBell`.
**SHA256 (`Get-FileHash`, 12 ký tự đầu) — 5/5 cặp IDENTICAL:**
| File (`fe-*/src/…`) | admin | user | Cặp | So baseline §0.a |
|---|---|---|---|---|
| `types/khkk.ts` | `72A71451B98F` | `72A71451B98F` | ✅ | ĐỔI (từ `8b757f5b1422`) |
| `pages/khkk/KhkkCreatePage.tsx` | `677F4AB803B0` | `677F4AB803B0` | ✅ | ĐỔI (từ `ea51e7626eb1`) |
| `pages/khkk/KhkkDetailPage.tsx` | `1863153B4F99` | `1863153B4F99` | ✅ | ĐỔI (từ `c8ab2eadd3fd`) |
| `pages/khkk/KhkkListPage.tsx` | `E892310434F2` | `E892310434F2` | ✅ | ĐỔI (từ `40a650bf6964`) |
| `pages/khkk/KhkkWorkflowPanel.tsx` | `2B64144EF6E8` | `2B64144EF6E8` | ✅ | **BẰNG baseline** (chứng: đo đúng thứ, không đụng file ngoài phạm vi) |
## §5 Ranh giới đã giữ + khoảng hở khai thật
**Giữ đúng 5 điều cấm của task:** 0 lệnh `dotnet` · 0 file `.cs` bị ghi (13 `.cs` đang M/??
trong `git status`**lane BE** — Mig `20260731085624_AddKhkkApprovalGroupCatalogAndFinalizeRuntime`
+ `ContractSigningPlanFeatures.cs` + 3 test file; tôi chỉ ĐỌC DTO, không Edit) · 0 route
mới (`App.tsx` không nằm trong diff) · 0 menu key mới (`lib/menuKeys.ts`,
`components/Layout.tsx` không nằm trong diff — K4a giữ chỗ đó) · 0 dòng spec bị sửa.
**Khoảng hở / lệch — khai để lead phán, không tự nuốt:**
1. 🔴 **Tiền đề task sai một phần** (§1): CreatePage + DetailPage đã LAND ĐỦ trên đĩa từ
p1; chỉ ListPage dở. Việc thật của p2 ≈ 1/4 mô tả. Tôi **không viết lại** phần đã đúng.
*(Bài học tái xác nhận: báo cáo chết ≠ code chết — đo `git diff` trước khi gõ.)*
2. ⚠️ **Cột "Tổng duyệt" (Σ Approved) là việc TÔI THÊM ngoài gạch đầu dòng của lead.**
Căn cứ: **vá-8 spec-cum1** (*"list thêm Σ Approved sau freeze để list/detail không lệch
số"*) + BE đã trả `ListItemDto:64` + p1 đã khai field trong types kèm đúng lý do đó.
Không thêm thì field nằm chết. **Nếu lead thấy quá tay: gỡ = xoá 1 `<th>` + 1 `<td>` +
trả `colSpan` 8→7, không ảnh hưởng gì khác.**
3. ⚠️ **`existingGroups` chỉ đọc `Phase != TuChoi`** (BE). Phiếu **xóa mềm** có rơi khỏi tập
này không thì tôi KHÔNG kiểm được từ FE. Nếu BE không loại phiếu deleted thì user xoá
phiếu nhóm 2 xong vẫn thấy N2 bị khoá trong ô chọn (rào 409 BE mới là lời cuối). Câu này
thuộc lane BE — nêu để có người soi, không tự sửa.
4. ⚠️ **Nhãn 8 nhóm còn treo [OG-6]** (owner soát sau trên UI). Đang nằm 1 chỗ
`KHKK_APPROVAL_GROUP_LABELS` × 2 app ⇒ K4a đổi nhãn menu thì sửa đúng 1 const, đừng vá rải.
5. **Không tự bấm được UI** (acceptance vá-4 "tạo phiếu nhóm 2 TỪ UI ×2 app" cần người
chạy app + login). Tôi dừng ở mức build-PASS + đọc mã; **acceptance UI vẫn ĐANG MỞ**,
đừng đọc "build PASS" thành "đã UAT".
6. Chỗ lọc `?group=` **cố ý chưa làm** (thuộc K4b/K5) — nêu lại ở đây để wave sau không
tưởng là sót.
END p2 — 4 file × 2 app · build ×2 PASS · SHA-pair 5/5.

View File

@ -1,4 +1,4 @@
REVIEWER-GATE-K1: IN-PROGRESS — 0 finding (đang chạy)
REVIEWER-GATE-K1: PASS-WITH-FLAGS — 9 finding
# Reviewer gate K1 — ContractCatalogEntries (SP-002 danh mục loại HĐ)
@ -9,9 +9,9 @@ Artifact ghi LIÊN TỤC chống #53 — mỗi finding đổ ngay khi có.
- [x] B1 Đọc spec cụm-1 + transcribe + 3 lane return
- [x] B2 dotnet build tươi — **Build succeeded · 0 Warning(s) · 0 Error(s) · Time Elapsed 00:00:44.23** (6 project)
- [x] B3 dotnet test — **45 Domain + 552 Infra = 597 PASS / 0 FAIL / 0 SKIP** (đúng kỳ vọng, 0 flaky, không phải chạy isolation)
- [ ] B4 Đối chiếu code-vs-spec
- [ ] B5 Ranh CẤM
- [ ] B6 Xác nhận 2 khoảng-hở UX
- [x] B4 Đối chiếu code-vs-spec (a→h)
- [x] B5 Ranh CẤM
- [x] B6 Xác nhận 2 khoảng-hở UX
## Log
@ -28,10 +28,9 @@ Time Elapsed 00:00:44.23
`ContractCatalogEntry.cs:15-24` = Code · TenVi · TenEn? · GroupCode · ApprovalGroup · SignerRole · InitialsChain? · NduqNote? · IsActive · SortOrder = **10 cột** + 7 audit từ `AuditableEntity`. Khớp bảng spec `sub-invest-fable-b2-cum1.md:13-26`.
### B4-b Config + Mig — ĐẠT (1 nit tên index)
`ContractCatalogEntryConfiguration.cs:27` unique filtered `[IsDeleted] = 0` (#57) · `:28` index ApprovalGroup · `:29` HasQueryFilter.
Mig `20260731072441_AddContractCatalogEntries.cs`: 1 CreateTable + 2 CreateIndex, Down = DropTable. **0 RenameColumn**.
NIT: spec `:28` đặt tên `UX_ContractCatalogEntries_Code`, code để EF-default `IX_ContractCatalogEntries_Code` (`Mig:48`). Không ảnh hưởng chức năng; khuôn repo (Mig 47) cần đối chiếu.
`ContractCatalogEntryConfiguration.cs:27` unique filtered `[IsDeleted] = 0` (#57) · `:28` index ApprovalGroup · `:29` HasQueryFilter.
Mig `20260731072441_AddContractCatalogEntries.cs`: 1 CreateTable + 2 CreateIndex, Down = DropTable. **0 RenameColumn**.
NIT: spec `:28` đặt tên `UX_ContractCatalogEntries_Code`, code để EF-default `IX_ContractCatalogEntries_Code` (`Mig:48`). Không ảnh hưởng chức năng.
### B3 test (nguyên văn)
```
@ -56,3 +55,70 @@ Script parse trực tiếp `DbInitializer.cs` vùng `SeedContractCatalogAsync` (
### B4-d Gate seeder — ĐẠT
`DbInitializer.cs:129-131` call-site NẰM NGOÀI mọi `if (!demoSeedDisabled)` ⇒ UNGATED (đúng spec K1.b, gotcha #51). `IgnoreQueryFilters()` có mặt khi dò existing (né #75/#76 resurrect).
### B4-e MenuKeys + drift DELTA — ĐẠT (1 finding kéo theo ở B4-f)
Đo máy trên `MenuKeys.cs` (parse vùng mảng `All`, strip comment, tách dấu phẩy):
- `All count = 56` · `has ContractCatalog = True` · `Actions = [Read, Create, Update, Delete]` = 4 → **policies derived = 224**.
- Diff `MenuKeys.cs:28-31` const + `:173` VÀO `All` ⇒ đúng vá-1 spec-cum1 `:8` (ngoài All = 0 permission row = sidebar chết).
- Grant Admin: loop `SeedAdminPermissionsAsync` `DbInitializer.cs:2064-2066` nguồn = `MenuKeys.All` ⇒ tự động, 0 code thêm. Menu row `DbInitializer.cs:1773-1776` leaf order 5 dưới `Catalogs`, đi nhánh INSERT (key mới) nên không dính bẫy gotcha #11.
- 🔴 **Drift DELTA CHƯA vào doc:** `docs/STATUS.md:6` hiện vẫn `Mig 69 · 96 bảng · 590 test PASS · Policies 220 · Menu keys 55`. Acceptance spec-cum1 ③ đòi "drift DELTA khai cùng commit" ⇒ **F-2**.
### B4-f CQRS + Controller authz — ĐẠT-theo-khuôn, nhưng khoảng-hở phải khai
Soi TỪNG action (không nhận `[Authorize]` trần làm bằng chứng):
| Action | Attribute thật | File:line |
|---|---|---|
| class | `[Authorize]` trần | `ContractCatalogController.cs:22` |
| GET list | (thừa hưởng class) | `:25` |
| GET id | (thừa hưởng class) | `:34` |
| POST | `[Authorize(Roles = "Admin")]` | `:39` |
| PUT | `[Authorize(Roles = "Admin")]` | `:47` |
| DELETE | `[Authorize(Roles = "Admin")]` | `:56` |
Khuôn đối chứng `CatalogsController.cs`: `:13` route · `:14` `[Authorize]` trần · 12 action ghi đều `[Authorize(Roles="Admin")]` (`:23,31,40,53,61,70,83,91,100,113,121,130`) ⇒ **mirror khuôn CHÍNH XÁC**, đúng QUYẾT spec K1.c `:47`. **KHÔNG có OR key con** (gotcha #85 N-A: controller này không dùng policy nào).
🔴 Đo tiếp: `grep -rn "ContractCatalog\." src/Backend --include=*.cs | grep -i policy`**0 endpoint** dùng 4 policy vừa sinh. Hệ quả đo được (lớp gotcha #82): ô CRUD `ContractCatalog` trên ma trận phân quyền chỉ điều khiển **hiển thị menu**, KHÔNG điều khiển API — tick `CanCreate` cho CatalogManager vẫn 403 (`Roles="Admin"`), bỏ tick `CanRead` vẫn GET được (class `[Authorize]` trần). ⇒ **F-3**.
Validator (`ContractCatalogFeatures.cs:110-124` / `:168-183`): MaxLength lấy ĐÚNG từ EF config (20/500/500/10/100/300/500) — đối chiếu `ContractCatalogEntryConfiguration.cs:18-24` khớp 7/7. `ApprovalGroup` `InclusiveBetween(1,8)` cả Create lẫn Update. Dup-Code chạy trên active-set (`:131`, `:193`) ⇒ khớp index filtered.
🔸 `ListContractCatalogQuery` (`:42-46`) mặc định **KHÔNG lọc IsActive** ⇒ trả CẢ dòng đã tắt; spec K1.c `:50` ghi tham số `IncludeInactive?` (ngữ nghĩa mặc-định-loại). ⇒ **F-4**.
### B4-g FE 2 app — ĐẠT (đối xứng đo bằng hash)
- `CatalogsPage.tsx` SHA-256 **d71a9e3bb17b835b85b4f3467905af2ab414f52e3ec0c58e79d55ef57673985a** ×2 app IDENTICAL → **khớp claim lane FE `sub-implfe-k1.md:49`**.
- `menuKeys.ts` + `Layout.tsx` DIVERGE cả-file (đúng như lane khai) ⇒ đo hash **phần THÊM VÀO**: `menuKeys.ts` admin=`ab97ed56b743f262…` user=`ab97ed56b743f262…` **MATCH** · `Layout.tsx` admin=`4ed4ea6b6f059c0e…` user=`4ed4ea6b6f059c0e…` **MATCH**.
- Pattern 16-bis 4 chỗ: (1) page OK · (2) route `/master/catalogs/:kind` CÓ SẴN `fe-user/src/App.tsx:63` + `fe-admin/src/App.tsx:69` ⇒ đúng "không cần route mới" · (3) `menuKeys.ts` ×2 · (4) `Layout.tsx` staticMap ×2 (thiếu chỗ 4 = sidebar drop im lặng, gotcha #50).
- KIND_CONFIG kind-5: 10 field khớp ĐÚNG 10 field `CreateContractCatalogEntryCommand` · PUT gửi `{ id: form.id, ...body }` (`CatalogsPage.tsx:164`) ⇒ qua được chốt `id != body.Id → 400` (`Controller:50`) · `buildBody` ép `numeric` → 0 (BE `int` non-nullable) · `openCreate` mồi select N1 (né `InclusiveBetween`).
- 🔸 Page **0 hit** `PermissionGuard`/`usePermission` ⇒ nút Thêm/Sửa/Xóa hiện cho mọi user vào được trang (khuôn 4 kind cũ y vậy) ⇒ **F-5**.
- 🔸 `CatalogsPage.tsx:218` ×2 app: comment `{/* Sub-tabs cho 4 kind */}` còn "4" trong khi `KINDS` đã 5 ⇒ **F-7** (nit).
### B4-h Test — ĐẠT (7 [Fact], có T1b thật)
Đếm máy `[Fact]` = **7**: T1 `Seed_RunTwice_Keeps86Rows_NoDuplicateCode` · **T1b `Seed_AfterSoftDeleteOfOneCode_DoesNotResurrectIt`** · T2 phân-bố 8 nhóm + section + denorm-đồng-nhất · T3 unique filtered 2 CHIỀU · T4 validator biên 0/9/-1/99 ⟂ 1/4/8 · T5 vòng đời handler thật (soft-delete + tái dùng mã) · T6 menu-key ∈ All + MenuItem row + grant Admin.
T1b có RĂNG thật (không phải nhãn): fixture RIÊNG wire `AuditingInterceptor` (`:83`) ⇒ `Remove()` là xoá MỀM thật; có sanity chống-vacuous `raw.IsDeleted.Should().BeTrue()` (`:179`); assert 85 active **VÀ** 86 tổng `IgnoreQueryFilters` (`:185-190`) ⇒ gỡ `IgnoreQueryFilters()` khỏi seeder thì tổng thành 87 và test ĐỎ. Đúng phép đo cho #75/#76.
T6 còn cài invariant TOÀN BỘ `All`: `MenuKeys.All.Except(menuKeysInDb).Should().BeEmpty()` (`:414`) — bắt lỗi key ∈ All mà thiếu MenuItem row (FK nổ lúc boot).
### B5 Ranh CẤM — SẠCH
- `git status --porcelain | grep -cE "ContractSigningPlan"`**0**.
- `git diff --stat` cho `Infrastructure/Services/ContractSigningPlanWorkflowService.cs` + `Application/Contracts/ContractSigningPlanFeatures.cs`**rỗng** (0 thay đổi) ⇒ `WorkflowService.cs:342` KHÔNG bị đụng · vùng `Features.cs:342-347` (việc của K2+) KHÔNG bị đụng — đúng vá-2 spec-cum1 `:9`.
- Scope toàn diff: **17 file** (11 M + 6 untracked) = 6 FE + 10 BE + 1 test. 0 file ngoài phạm vi K1.
- 3-file rule Mig (gotcha #17): `20260731072441_AddContractCatalogEntries.cs` + `.Designer.cs` (cả 2 untracked, có trên đĩa) + `ApplicationDbContextModelSnapshot.cs` (modified, +83 dòng, có `ContractCatalogEntry`) = **đủ 3, cùng commit-set**; `grep -c RenameColumn` = **0**.
### B6 Hai khoảng-hở UX lane FE khai — XÁC NHẬN ĐÚNG THỰC TRẠNG (không quyết thay lead/owner)
1. **3 filter BE chưa có UI** — ĐÚNG. BE nhận 4 tham số (`groupCode`/`approvalGroup`/`isActive`/`q``Controller:27-30`), FE chỉ gửi `q` (`CatalogsPage.tsx:158`) ⇒ 3/4 filter chưa có đường bấm từ UI.
2. **Tab hiện cả fe-user** — ĐÚNG. `KINDS` render vô điều kiện 5 sub-tab, route `:kind` mở ở CẢ 2 app (`fe-user/src/App.tsx:63`), GET là `[Authorize]` trần ⇒ user thường vào `/master/catalogs/units` sẽ thấy tab "Danh mục Hợp đồng" và đọc được 86 dòng, dù leaf menu chỉ Admin được grant. Cùng lớp với F-3.
## FINDING (9 — 0 blocker kỹ-thuật; F-1 chờ lead/owner disposition trước khi commit seed)
1. **[HIGH · gate quy-trình]** `DbInitializer.cs:3147-3324` — 86 dòng seed đang nằm CÙNG change-set với code, trong khi spec đặt PRE-gate `[OG-6]` (owner soát transcribe) **TRƯỚC khi land seed** và kế hoạch tách 2 commit (`sub-invest-fable-b2-cum1.md:7` + `:240-241`: "Commit K1-seed (SAU [OG-6] anh gật)"). Nặng thêm vì seeder per-Code idempotent **không có nhánh update**: owner sửa ô sau khi seed đã chạy thì row cũ KHÔNG tự cập nhật (lane tự khai `sub-implbe-k1.md:51`). Tiêu chí đóng: hoặc owner gật OG-6 trước commit, hoặc tách seed sang commit riêng giữ chờ gật.
2. **[MEDIUM]** `docs/STATUS.md:6` — drift DELTA chưa vào doc: Mig 69→**70** · 96→**97** bảng · 590→**597** test · Policies 220→**224** · Menu keys 55→**56**. Acceptance spec-cum1 ③ đòi khai **cùng commit**.
3. **[MEDIUM · lớp #82]** `ContractCatalogController.cs:22,39,47,56` — 4 policy `ContractCatalog.{Read,Create,Update,Delete}` sinh ra do key vào `All` nhưng **0 endpoint tiêu thụ**; ô CRUD trên ma trận phân quyền chỉ điều khiển hiển-thị-menu, không điều khiển API (tick CanCreate vẫn 403 · bỏ tick CanRead vẫn GET được). KHÔNG phải lệch spec (K1.c QUYẾT mirror khuôn `CatalogsController.cs:14`) nhưng phải khai thành giới-hạn, đừng để lượt sau đọc ma trận thành "đã khoá API".
4. **[LOW]** `ContractCatalogFeatures.cs:42-46,59-60` — List mặc định trả CẢ dòng `IsActive=false`; entity khai mục đích cột này là "ẩn khỏi picker" (`ContractCatalogEntry.cs:23`) và spec K1.c `:50` đặt tham số `IncludeInactive?` (mặc-định-loại). Ngữ nghĩa hiện tại ngược ⇒ consumer K2 (picker) phải nhớ truyền `isActive=true`, quên là dòng đã tắt hiện lại.
5. **[LOW]** `fe-{admin,user}/src/pages/master/CatalogsPage.tsx` — 0 hit `PermissionGuard`/`usePermission`: nút Thêm/Sửa/Xóa render cho mọi user vào được trang, bấm mới nhận 403. Pre-existing của khuôn 4 kind cũ, không phải hồi quy do K1.
6. **[NIT]** `20260731072441_AddContractCatalogEntries.cs:48` — tên index `IX_ContractCatalogEntries_Code` (EF default) trong khi spec `sub-invest-fable-b2-cum1.md:28` đặt `UX_ContractCatalogEntries_Code`. Chức năng không đổi (unique + filter đúng).
7. **[NIT]** `fe-{admin,user}/src/pages/master/CatalogsPage.tsx:218` — comment `{/* Sub-tabs cho 4 kind */}` còn "4" trong khi `KINDS` đã 5 kind.
8. **[INFO · lệch đã khai]** `DbInitializer.cs` B2-07..B2-14 — 8 dòng bỏ hậu tố `(ảnh: …)` so với transcribe; đo máy xác nhận **đây là 8 mismatch DUY NHẤT** trên 86 dòng, đúng tập lane đã khai (`sub-implbe-k1.md:16`). Bản chất là ghi-chú người chép, nhưng vẫn thuộc phạm vi OG-6 owner soát.
9. **[LOW]** Acceptance K1.f `:70-71` (runtime) chưa đo: Mig 70 chưa áp Dev-DB (lane khai `sub-implbe-k1.md:48-49`), chưa có log "Seeded 86…" + `sqlcmd COUNT(*)=86` + `GROUP BY ApprovalGroup`. T1/T1b thay được phần logic-idempotent trên SQLite nhưng KHÔNG thay được "SQL Server nhận đúng filtered index + boot thật" ⇒ đừng tick 2 ô đó cho tới khi chạy API.
## Bằng chứng số (nguyên văn)
- build: `Build succeeded. · 0 Warning(s) · 0 Error(s) · Time Elapsed 00:00:44.23`
- test: `Failed: 0, Passed: 45` (Domain) + `Failed: 0, Passed: 552` (Infrastructure) = **597 PASS / 0 FAIL / 0 SKIP**
- seed: 86 row · 86 mã duy nhất · 5/10/5/23/14/15/13/1 · 0 mismatch ngoài 8 dòng đã khai
- MenuKeys.All = 56 · policies derived = 224 · [Fact] = 7 · RenameColumn = 0 · file KHKK bị chạm = 0
END sub-reviewer-gate-k1 — 9 finding · build+test XANH tươi · ranh CẤM sạch

View File

@ -0,0 +1,31 @@
REVIEWER-GATE-K2: IN-PROGRESS — chưa chốt
# sub-reviewer-gate-k2 — GATE K2 (adversarial, trước commit-push) — S164/S165
> Luật ghi-đĩa-trong-lúc-làm (#53): mỗi mục đo xong ghi NGAY. Dòng-1 = verdict, cập nhật cuối cùng.
> Vai: reviewer READ-only. KHÔNG sửa prod-code. Được chạy dotnet/npm build.
## §0 Tiền đề đo được (git, trước mọi phán)
- Branch `main`. `git status --porcelain`: 31 file M + 5 file untracked (2 mig K2 + 1 test file K2 + 2 auto-memory).
- Diffstat vùng code: 31 files changed, +1005/-126 (gồm cả .claude/docs).
- File K2 untracked: `20260731085624_AddKhkkApprovalGroupCatalogAndFinalizeRuntime.cs` + `.Designer.cs`
+ `tests/.../Application/ContractSigningPlanGroupAndCatalogTests.cs`.
## §1 Trọng tài BUILD/TEST — reviewer tự chạy tươi (nguyên văn)
- `dotnet build SolutionErp.slnx``Build succeeded. 0 Warning(s) 0 Error(s)` · `Time Elapsed 00:00:18.24`**KHỚP claim 0W/0E**.
- `dotnet test SolutionErp.slnx`:
- Domain: `Passed! - Failed: 0, Passed: 45, Skipped: 0, Total: 45, Duration: 69 ms`
- Infrastructure: `Passed! - Failed: 0, Passed: 562, Skipped: 0, Total: 562, Duration: 1 m 11 s`
- Tổng **607/0 = 45D + 562I****KHỚP claim `sub-test-k2.md:1` từng con số.**
## §2 Ranh giới (a)-(d) — đo trên diff
- (a) `ContractSigningPlanWorkflowService.cs`: diff CHỈ 1 hunk `@@ -164,6 +164,23 @@` (+17/-0) nằm trọn trong `SubmitAsync`.
`ReturnOrRejectAsync` (`:359 plan.Phase = targetPhase`) + `ApplyApprovedValuesOnFinalize` + `:295 = DaDuyet` **0 dòng đổi** ⇒ ranh B3-B4 GIỮ theo NỘI DUNG.
- (b) K1 `ContractCatalog*`: `git status --porcelain` trên 3 path K1 = **0 dòng** (đã commit `50e6d8c`) ⇒ không bị đụng.
- (c) route/menu: `App.tsx` · `lib/menuKeys.ts` · `components/Layout.tsx` **KHÔNG có trong diff** ⇒ 0 route/menu mới (K4a turf giữ).
(`MenuKeys.cs` BE cũng không trong diff của wave K2 — chỉ `.claude/skills/permission-matrix/SKILL.md` là doc.)
- (d) 3 test lead vá: `ContractSigningPlanSchemaTests.cs` (+56/-…) + `ContractSigningPlanApprovalTests.cs` (+19) vẫn còn trong diff, file mới `ContractSigningPlanGroupAndCatalogTests.cs` tách riêng ⇒ không bị lane đè.

View File

@ -0,0 +1,33 @@
K2-TEST: DONE — suite 607/0 (45D+562I, +9 mới)
# sub-test-k2 — K2 TEST SUITE (S164/S165, wave 2/8)
> 🔴 **[lead on-behalf @S165]** Lane test-specialist LÀM XONG VIỆC nhưng chết ở return (#53 — return rỗng
> no-StructuredOutput) VÀ **không tạo artifact này dù prompt ép** (vi phạm ghi-trong-lúc-làm lần 2 trong
> wave). File này = lead ghi SAU VERIFY-ĐĨA: đọc file test thật + chạy build/suite thật — không phải lời lane.
## Sản phẩm lane (disk-truth)
- File MỚI `tests/SolutionErp.Infrastructure.Tests/Application/ContractSigningPlanGroupAndCatalogTests.cs`
**824 dòng · 9 [Fact]** · header khai "test-AFTER rules §7" + bảng code-under-test đo-lại-trên-đĩa
(submit-guard `WorkflowService.cs:174-182` · rào `Features.cs:393-402` · list `:737-738` · Σ `:803-804`
· gate `:229-237` · site Phase-qua-BIẾN `:327-359`) — đúng luật con-trỏ-theo-NỘI-DUNG.
- 9 test (tên = hành-vi, khuôn tiếng Việt nhà):
1. `Submit_ConDongChuaGanHangMuc_ThiBiChan_GanDuThiTrinhQua` (T-a — vá-5, test ÂM trên phiếu CÓ line)
2. `Submit_HangMucKhacNhomDuyetCuaPhieu_ThiBiChan_CungNhomThiQua` (T-b)
3. `Create_CungPeCungNhom_ThiConflict_KhacNhom_ThiTaoDuoc_VaKhongDotMa` (T-c vế 1 — rào (PeId,Group))
4. `Create_PhieuNhomDaTuChoi_KhongChiemCho_PhieuDangChoDuyet_ThiChiemCho` (T-c vế 2)
5. `List_LocTheoNhomDuyet_ChiTraDungNhom_VaTotalKhopFilter` (T-d)
6. `List_TongDaChot_CongTheoApprovedAmount_PhieuChuaDuyetThiBangKhong` (T-e — Σ vá-8)
7. `EnsureDraftEditable_ChanMoiPhaseNgoaiNhapVaTraLai_VaDaNoiVaoDuongGhiThat` (T-f — phase-guard §e)
8. `Entity_MacDinhNhomMot_VaGiuNguyenKhiGanTay_ConCoKetThucSomMacDinhTat` (T-g + EndedByLevelFinalize default)
9. `Transition_ChiNhanhDuyetCuoiMoiToiDaDuyet_VaLuonKemChotGia` (bonus — choke-point 1-site DaDuyet + ??=)
## Trọng-tài (lead chạy SAU khi mọi lane dừng — #68)
- `dotnet build` **0W/0E** · `dotnet test` **607/0 = 45D + 562I** (553 + 9, delta rơi trọn Infra đúng chỗ).
- 3 test lead vá trước đó (SchemaTests unique-3-cột + ApprovalTests fixture) KHÔNG bị lane đè — diff stat
khớp phần lead (+19/+56 marks), file mới tách riêng. Ranh giữ đúng.
- k = **9**, khai đủ. Bug-BE lộ ra = 0 (9/9 pass ngay trên BE đã land).
END sub-test-k2 — VERDICT=DONE (9 test T-a..T-g+2 · suite 607/0 · artifact lead-on-behalf sau verify-đĩa)

View File

@ -0,0 +1,43 @@
K2-TEST: IN-PROGRESS — chưa đo suite (baseline nhận 598/0, CHƯA tự đo)
# sub-test-k2 — K2 TEST lane (S164, wave 2/8, SP-002)
> Luật đan-xen (bài K1/K2-BE): làm mục nào GHI NGAY mục đó, KHÔNG dựng dàn-bài kèm số chưa đo (bài S161-W7d2:
> dàn-bài có sẵn con-số = bẫy claim-trước-đo). Mục chưa chạy CHỈ được tiêu-đề + "CHƯA ĐO".
## §0 Tiền đề — git status TRƯỚC mọi Write
- `git status --porcelain` @start: `sub-test-specialist-1.md` **KHÔNG tồn tại** trong run-folder ⇒ Write mới AN TOÀN.
- File test đang M (lead vừa sửa, CẤM đè): `tests/.../Application/ContractSigningPlanApprovalTests.cs`,
`tests/.../Common/ContractSigningPlanSchemaTests.cs`.
- Prod code đang M (lane BE K2, CẤM đụng): Features/WorkflowService/entity/config/controller + Mig 71 (untracked).
- Baseline nhận từ lead: 598/0 (45D+553I). CHƯA tự đo (sẽ đo cuối, không tin số đề bài — bài S161-W2).
## §1 Đọc nền — ĐO ĐƯỢC GÌ (đã đọc, trước mọi Write)
Đọc trọn: `sub-implbe-k2.md` (§a-§h) · `sub-invest-fable-b2-cum1.md:77-172` (K2.a-K2.h, blueprint 7 test) ·
`spec-cum1-chi-tiet-31-07-2026.md` (12 vá + checklist ③). Đo lại trên ĐĨA (không tin sổ):
| Thứ cần đo | Site thật | Ghi chú |
|---|---|---|
| submit-guard null | `ContractSigningPlanWorkflowService.cs:174-176` | vá-5 CHẶN null (THẮNG nền `:126` cho-qua) |
| submit-guard khác-nhóm | `…Service.cs:177-182` | message `…cùng nhóm N{plan.ApprovalGroup}` |
| rào (PeId,Group) | `ContractSigningPlanFeatures.cs:393-402` | `Phase != TuChoi` + `ApprovalGroup == approvalGroup` |
| list filter group | `Features.cs:737-738` | ĐẶT SAU khối IDOR `:720-729` |
| Σ Approved | `Features.cs:803-804` | `Sum(l.ApprovedAmount) ?? 0m`, DTO slot 25 `:64` |
| EnsureDraftEditable | `Features.cs:229-237` | owner∥Admin TRƯỚC, phase {DangSoanThao,TraLai} SAU |
| default ApprovalGroup | `ContractSigningPlan.cs:39` `= 1` | mig `:52` defaultValue 0 + `Sql UPDATE … = 1` `:61` |
| site gán-qua-BIẾN | `…Service.cs:327-359` `ReturnOrRejectAsync` | vá-2 chọn phương án (ii) = TEST KHOÁ, cấm sửa code |
**Hạ tầng tái dùng:** `InternalsVisibleTo` CÓ SẴN (`SolutionErp.Application.csproj:25`) ⇒ gọi THẲNG
`ContractSigningPlanScope.EnsureDraftEditable` (Pattern 12, không cần reflection). Global using Xunit +
FluentAssertions (`…Tests.csproj:31-32`). `.slnx` chỉ liệt PROJECT ⇒ file .cs mới KHÔNG cần sửa slnx.
**QUYẾT: file MỚI** `Application/ContractSigningPlanGroupAndCatalogTests.cs` + fixture RIÊNG
(mirror `ContractSigningPlanCrudTests.cs:49-82`, có `AuditingInterceptor`) — KHÔNG chạm 2 file lead vừa vá
(`ContractSigningPlanApprovalTests.cs` fixture SeedPlanAsync +catalog · `ContractSigningPlanSchemaTests.cs`
unique-3-cột), KHÔNG chạm `ContractSigningPlanCrudTests.cs` (helper `CreateAsync` ở đó không mang tham số
nhóm; thêm tham số = sửa file người khác đang dùng).
## §2 Test đã viết — CHƯA ĐO (đang viết)