wal: flush 20260801T1310
This commit is contained in:
@ -44,11 +44,20 @@ chain:
|
|||||||
lead-ghi, gate-K7 = lưới review THẬT đầu tiên của diff FE). TEST DONE (engine-exit-0-delta → resume
|
lead-ghi, gate-K7 = lưới review THẬT đầu tiên của diff FE). TEST DONE (engine-exit-0-delta → resume
|
||||||
Write-trước: suite 639/0 = 45D+594I Δ+17 [12 bridge T1-T12 + 5 authz] · 1-FAIL-bàn-giao đóng bằng
|
Write-trước: suite 639/0 = 45D+594I Δ+17 [12 bridge T1-T12 + 5 authz] · 1-FAIL-bàn-giao đóng bằng
|
||||||
ngoại-lệ CreateContract #85 · 0-FLAG-production · 2-khai: vá-6-thắng-invest-T1 + seeder-grant-chỉ-đo-
|
ngoại-lệ CreateContract #85 · 0-FLAG-production · 2-khai: vá-6-thắng-invest-T1 + seeder-grant-chỉ-đo-
|
||||||
được-@restart-prod→giao-cicd — sub-test-k7.md §0-§5). Gate-K7 ĐANG nền (sub-reviewer-gate-k7.md — cửa
|
được-@restart-prod→giao-cicd — sub-test-k7.md §0-§5). Gate-K7 PASS-WITH-FLAGS 10/0-blocker (garble-
|
||||||
chết ĐỌC FILE trước, đừng spawn lại). CÒN: gate → commit ([CLAUDE] Contract: K7) → push → cicd
|
0-artifact → resume Write-trước trả CLEAN; tự đo prod 13-vai CanCreate-1/13 + chốt acceptance seeder
|
||||||
(kèm đo seeder-grant restart-proof) → GĐ3-đồng-nhất (chốt 55)
|
"0 added + 2 upgraded" — sub-reviewer-gate-k7.md 32.5KB §9-5-hành-động). F-8 FIXED trước commit (neo
|
||||||
[ ] GĐ3 cây-4-folder ĐỒNG-NHẤT — anh chốt (55) @S167 "Nên đồng nhất": áp cây 4-folder lên cụm trang
|
:2516 chết-lúc-land → neo TÊN-HÀM 3-chỗ + 13/13→12/13-đo-prod + khai-chủ-đích-CanRead + trỏ-đúng-
|
||||||
Duyệt-hợp-đồng (7 loại HĐ) ×2 app — làm SAU K7 ship (tái dùng tập ContractIds/usePipelineStages mới)
|
SeedAdminPermissionsAsync) · filter 17-test PASS · COMMIT aaed699 PUSHED (13 file +2241/−46) +
|
||||||
|
governance 628132c (3-script + STATUS + G-1-RATIFIED-stamps ×2-app SHA-pair-giữ; 4-file-agent-memory
|
||||||
|
đã vào wal:-flush trước). Nợ mang theo 0-blocker: MAJOR-race-2-request-đồng-thời (chống-trùng
|
||||||
|
in-memory — phương-án unique/RowVersion nhịp sau) · DraftData-không-carry-chưa-khai-chủ-đích ·
|
||||||
|
a11y-<a>-trong-label + O(n·m) FE (lớp K5-nợ) · CLARIFY menu-cache-re-login (UAT-NÓI-TRƯỚC).
|
||||||
|
cicd-K7 ĐANG nền (sub-cicd-verify-k7.md — ĐÃ báo vai phủ-2-run vì 628132c đẩy chồng; restart×2
|
||||||
|
= phép đo idempotent miễn phí)
|
||||||
|
[!] GĐ3 cây-4-folder ĐỒNG-NHẤT — anh (55) "Nên đồng nhất": lane FE ĐANG nền @S167 (khuôn bfc7b79 S162
|
||||||
|
3-trang + usePipelineStages-tập-contractIds K7 — sub-implfe-gd3.md, cửa chết ĐỌC FILE trước).
|
||||||
|
CÒN SAU LANE: gate → commit → cicd
|
||||||
[ ] K8-prep form-14-người fail-closed + gói SOLUTION-PE-{n} + QĐ9 đo-2-nhánh + ZZTEST + rollback-theo-ID.
|
[ ] K8-prep form-14-người fail-closed + gói SOLUTION-PE-{n} + QĐ9 đo-2-nhánh + ZZTEST + rollback-theo-ID.
|
||||||
🔴 (54) FINAL @S167 (anh, 2 lượt): "quy trình duyệt tử tế rồi ko sao" + "hiện cứ như vậy — Nếu điều
|
🔴 (54) FINAL @S167 (anh, 2 lượt): "quy trình duyệt tử tế rồi ko sao" + "hiện cứ như vậy — Nếu điều
|
||||||
chỉnh, tao sẽ tự tay điều chỉnh" ⇒ GIỮ HIỆN TRẠNG workflow/người-duyệt KHKK (8 WF N1-N8 đã seed @K3,
|
chỉnh, tao sẽ tự tay điều chỉnh" ⇒ GIỮ HIỆN TRẠNG workflow/người-duyệt KHKK (8 WF N1-N8 đã seed @K3,
|
||||||
|
|||||||
@ -2,11 +2,12 @@
|
|||||||
|
|
||||||
- **[→ archive/2026-07.md — verbatim ĐÃ VERIFY moved-not-cut 3/3 @S166-K5-curate] S166 gate K4b · K4a · vá F-1 (digest):** mỏ-neo XUYÊN FILE (quét file BỊ DỜI, không chỉ file đang sửa) · **tham-số-trang-trí** (đo AI ĐỌC từng param, đừng đo "route có tồn tại") · mỏ-neo tự-vô-hiệu ngay lúc land (đo SAU khi chèn) · **ô canonical sai đo được bằng 1 curl** · khuôn-nguồn 2 tầng ⇒ đo **GIÁ-TRỊ KHỞI-TẠO** không đọc chú-thích · **cây phình giữa review** ⇒ neo mốc + re-đo `--stat` TRƯỚC verdict · HIGH-nằm-ở-git (`git status --porcelain -- src tests | grep '^??'`) · khoá-cache định-danh REQUEST không định-danh URL · sợi-dây-1-argument 0 test.
|
- **[→ archive/2026-07.md — verbatim ĐÃ VERIFY moved-not-cut 3/3 @S166-K5-curate] S166 gate K4b · K4a · vá F-1 (digest):** mỏ-neo XUYÊN FILE (quét file BỊ DỜI, không chỉ file đang sửa) · **tham-số-trang-trí** (đo AI ĐỌC từng param, đừng đo "route có tồn tại") · mỏ-neo tự-vô-hiệu ngay lúc land (đo SAU khi chèn) · **ô canonical sai đo được bằng 1 curl** · khuôn-nguồn 2 tầng ⇒ đo **GIÁ-TRỊ KHỞI-TẠO** không đọc chú-thích · **cây phình giữa review** ⇒ neo mốc + re-đo `--stat` TRƯỚC verdict · HIGH-nằm-ở-git (`git status --porcelain -- src tests | grep '^??'`) · khoá-cache định-danh REQUEST không định-danh URL · sợi-dây-1-argument 0 test.
|
||||||
|
|
||||||
- **S166 (08-01) gate K5 cây GĐ2 mở 8 sub-folder nhóm — PASS 0 blocker (1 CLARIFY + 5 MINOR):** lõi hiếm-đúng: bất-biến Σ do **CẤU TRÚC** (1 mảng nguồn `khkkItems` → 1 phép phân hoạch if/else TOÀN PHẦN → `stage.leaves` vẫn là chỗ đếm duy nhất `stageCount():96`) nên không phụ thuộc ai nhớ cộng; route `?group=` nối đủ 3 chặng (FE `:79-80` → param `:124` → BE `Where(:757)`), **khác hẳn tham-số-trang-trí K4a**. **Bài thu:** ① 🔴 **phép-đo-rẻ-nhất nằm ở file RÁC của lane khác** — `menus.json` untracked ở gốc repo (sản phẩm curl của cicd-monitor) chính là payload `/api/menus/me` LIVE ⇒ đối chiếu nhãn cây suy-ra ⟂ nhãn sidebar thật **8/8 khớp từng ký tự + `displayLabel` đều null**, thứ mà đọc code không bao giờ chứng được. Cùng file đó là **rào commit** (`git add -A` sẽ nuốt 44KB rác) ⇒ 1 vật vừa là bằng-chứng vừa là rủi-ro, đừng chỉ báo 1 vế. ② **chân-lý-rỗng phải khai là rỗng**: curl prod `total=0` phiếu KHKK ⇒ phân bố `approvalGroup` = `{}`, KHÔNG dùng để kết luận gì; nhưng cùng con số ấy đẻ ra sự-thật dùng được cho UAT (ngày đầu mọi gói hiện **8 dòng "SẮP" rỗng** — đúng spec, dễ bị đọc thành hỏng). ③ **tương-đương-từng-vế > "cùng tập cùng thứ tự"**: vòng lặp GĐ3/4 bị viết lại ⇒ lập bảng 4 vế (tập id · thứ tự = `Set` giữ thứ-tự-chèn · khử trùng · dòng-phụ `find` = phiếu ĐẦU) mới dám phán 0-regression; thân stage4 **0 tham chiếu `p`** là lý do cấu-trúc khiến rewrite an toàn. ④ **tiền-lệ phải đo ở BASE, không đo ở cây đã sửa**: `git show 88e7ecf:<file> | grep -A6 '<summary'` cho thấy 6/6 summary cũ chỉ chứa `span`/`svg` ⇒ `<button>` lồng trong `<summary>` là **khuôn MỚI** (nested-interactive a11y), lane tưởng đang bám tiền lệ. ⑤ **"15/15 OK" chết theo phiên**: script `new Function` không commit + FE **0 hạ tầng test** (`package.json` chỉ `dev|build|lint|preview`) + chuông Σ là `import.meta.env.DEV` ⇒ prod bị strip ⇒ 0 lưới an toàn; đừng đọc số-test-ad-hoc thành đã-phủ. ⑥ 2 nghĩa của 1 dòng checklist ("GĐ3/4 by c.type") — lead và lane **cùng chọn 1 nghĩa không chứng minh nghĩa đó đúng ý owner** ⇒ ghim cách đọc vào commit. Tag `[s166, bang-chung-o-file-rac, chan-ly-rong, tuong-duong-tung-ve, tien-le-do-o-base]` **Evidence:** `runs/2026-07-31-S164-4gd-khkk-fanout/sub-reviewer-gate-k5.md`
|
- **[→ archive/2026-07.md §@S167-K7-curate — verbatim ĐÃ VERIFY moved-not-cut 2/2] S166 gate K5 cây-GĐ2-8-sub-folder · gate K4c cụm-2 (digest):** bất-biến Σ do CẤU TRÚC (1 nguồn → phân hoạch TOÀN PHẦN → 1 chỗ đếm) · **phép-đo-rẻ-nhất nằm ở file RÁC của lane khác** (`menus.json` untracked = payload prod LIVE, vừa là bằng-chứng vừa là rào-commit) · chân-lý-RỖNG phải khai là rỗng · tương-đương-TỪNG-VẾ > 'cùng tập cùng thứ tự' · tiền-lệ phải đo ở BASE không đo ở cây đã sửa · 'N/N OK' chết theo phiên khi FE 0 hạ-tầng-test · **flake chỉ lộ ở LƯỢT ĐẦU và `-v q` NUỐT thông điệp assert** (lượt đầu dùng verbosity thường) · probe RED bằng đổi TEST-INPUT hợp lệ CHỈ KHI tham số xuất hiện ĐÚNG 1 chỗ (grep trước) · `Total` là vân-tay lọc-SERVER chỉ khi `CountAsync` sau `Where` trước `Skip/Take`.
|
||||||
|
|
||||||
- **S166 (08-01) gate K4c chốt cụm-2 (FE lọc nhóm WfView + 1 Fact test) — PASS 0 blocker (1 CLARIFY + 3 MINOR):** ① 🔴 **flake chỉ lộ ở LƯỢT ĐẦU, và chính tôi giết mất chứng cớ**: chạy `dotnet test -v q` SONG SONG với `tsc` ⇒ 1 test CŨ đỏ (`Failed 1/8`) rồi 10/10 xanh sau đó + suite 622/0 ⇒ `-v q` **nuốt thông điệp assert** ⇒ có tên test mà không có lý do. Bài: lượt chạy ĐẦU dùng verbosity thường, `-v q` để dành cho lượt lặp. Truy nhiễm phải đo 3 vế mới dám nói "không do diff": fixture `:memory:` per-test · codegen **0 static** · xUnit 2.9.3 same-class = tuần tự. Báo "8/8 ở 11/12 lượt", KHÔNG báo 8/8 tuyệt đối. ② **probe RED bằng đổi TEST-INPUT (không chạm prod) là hợp lệ CHỈ KHI tham số xuất hiện ĐÚNG 1 CHỖ trong handler** — grep xong mới được chấp nhận; ≥2 chỗ là lập luận sập. ③ `Total` là dấu-vân-tay lọc-SERVER **chỉ khi** `CountAsync` chạy trên `q` SAU `Where` và TRƯỚC `Skip/Take` — đọc `ProjectPagedAsync:790-792` mới dám ký, đừng tin comment. ④ **queryKey KHÔNG thêm trục là ĐÚNG** khi BE endpoint không nhận trục đó (đọc controller: chỉ `applicableType`+`isUserSelectable`) + giá trị lọc là **dẫn xuất trong render** (không `useState`) ⇒ miễn nhiễm bẫy "đổi leaf không remount". ⑤ Guard `/^[1-8]$/` chặn `Number("03")=3`, và **đồng nhất 3 trang** KHKK — kiểm cả 3, lệch 1 chỗ là 2 luật cho 1 tham số. ⑥ Lane khai bundle `1,643.96 kB` — tôi chạy lại `npm run build`, **trùng từng chữ số** ⇒ claim đo-lường có thật. Tag `[s166, chung-co-bi-nuot-boi-verbosity, probe-1-cho-moi-hop-le, total-la-van-tay]` **Evidence:** `runs/2026-07-31-S164-4gd-khkk-fanout/sub-reviewer-gate-k4c.md`
|
|
||||||
|
|
||||||
- **S167 (08-01) gate K6 admin thu-gọn (1 file FE, tập 12 key ẩn) — PASS-WITH-FLAGS 5 (1 MAJOR + 1 CLARIFY + 3 MINOR):** ① 🔴 **cây menu PROD SỐNG là thước, seed chỉ là bản vẽ**: `curl /api/menus/me` (token admin, 44KB) → áp CHÍNH tập key **cắt bằng regex từ file đã ship** ⇒ 200 node · 166 lá → **15 lá**, trùng tên 15/15 + control-âm bỏ `KeHoachKyKet` = 63 (+48) khớp lane. Cùng payload đóng luôn FLAG "row chèn tay": **10 `AwV2*`, 0 `AwV2_Contract`**. Khai giới hạn: endpoint đã lọc-quyền ⇒ row không có Permission thì vô hình (nhưng cũng vô hình trên sidebar ⇒ không lệch C1). ② **`filterForAdmin` mới là bước MỘT**: lá còn "THẤY" phải qua tiếp `resolvePath→null ⇒ MenuLeaf drop im lặng` (#50) và **group rỗng-hoá tụt xuống render như LEAF** (`children.length>0`) — lane bỏ cả 2, tôi đo: 0 drop · 0 rỗng-hoá. ③ **typo-noop**: key ẩn sai chính tả = `Set.has` false, module vẫn hiện, tsc vẫn xanh ⇒ phải assert từng key TỒN TẠI trong cây prod (12/12 OK; control-âm 3 key bịa đều MISS). ④ **MAJOR: comment mới nhồi 3 số-đếm, 1 số SAI ngay lúc land** — "28 leaf `Ct_`" (thật 42 lá/49 key, S155 thêm WfView/Approved/Deleted), số chép từ skill `permission-matrix` stale ⇒ đúng bệnh #84(4) "CẤM số-đếm trong comment". ⑤ **CLARIFY hệ-quả không ai khai**: prod có `Hrm/Off/Personal` `isVisible=false`, fe-user lọc `isVisible!==false` còn fe-admin KHÔNG đọc cờ đó ⇒ admin là lối vào CUỐI, ẩn nốt = 3 module mất menu ở CẢ 2 app. ⑥ display-only chứng 3 tầng: diff (grep `+` chỉ hit COMMENT) < kiến-trúc (`usePermission:15` đọc cây THÔ, chỉ 2 consumer) < curl prod 200×2 endpoint đã ẩn. Tag `[s167, thuoc-la-cay-prod, filter-moi-la-buoc-mot, typo-noop, so-dem-trong-comment]` **Evidence:** `runs/2026-07-31-S164-4gd-khkk-fanout/sub-reviewer-gate-k6.md`
|
- **[→ archive/2026-07.md §@S167-K7-curate-2 — verbatim ĐÃ VERIFY moved-not-cut] S167 gate K7 cầu KHKK→HĐ — PASS-WITH-FLAGS 10, 0 blocker (digest):** **1 con số đóng 2 câu hỏi** — baseline 622 (đo ở K4c cùng ngày) + 17 `[Fact]` đếm được = 639, đo đúng **639/0** ⇒ chứng CẢ '17 test chạy thật, không skip câm' LẪN '0 test cũ vỡ'. **Mỏ-neo tự-vô-hiệu tái phát, lan 3 nơi**: chú thích trỏ `:2516` — `git show HEAD:<f>|grep -n`=2516 vs `grep -n` cây=**2523**, chính diff chèn 7 dòng đẩy xuống ⇒ phải đo **2 THẾ GIỚI (HEAD ⟂ cây)**, đo 1 thế giới là im. **Prod probe sửa con số của chính comment**: `/permissions/by-role/{id}`×13 vai → `Contracts.CanCreate` **1/13** ⇒ tiền-đề lỗ ĐÚNG, nhưng '13/13 role' sai (thật 12/13, Admin đã có row True) + đẻ acceptance sau-deploy `0 added + 2 upgraded`. **Răng phủ NỬA**: T11 double-click assert 4 vế nhưng `await` tuần tự ⇒ ca ĐỒNG THỜI vẫn hở (0 unique-index/RowVersion/transaction) — đừng đọc 'có T-idem' thành 'đã đóng'. **Ngoại lệ authz đúng cách**: map khớp TUYỆT ĐỐI + action ngoài map vẫn bị đòi prefix ⇒ tương-lai không tự miễn-trừ; kèm `BeSubsetOf` chống cấu-hình-chết. **SHA-identical KHÔNG bảo chứng route-identical** (3 path cứng `/contracts/${id}` trong file mirror 2 app ⇒ grep route CẢ 2). Tag `[s167, mot-so-dong-2-cau-hoi, moneo-tu-vo-hieu, probe-sua-so-cua-comment, rang-phu-nua, sha-khong-bao-chung-route]` **Evidence:** `runs/2026-07-31-S164-4gd-khkk-fanout/sub-reviewer-gate-k7.md`
|
||||||
|
|
||||||
|
- **[→ archive/2026-07.md §@S167-K7-curate-2 — verbatim ĐÃ VERIFY moved-not-cut] S167 gate K6 admin thu-gọn — PASS-WITH-FLAGS 5 (digest):** **cây menu PROD SỐNG là thước, seed chỉ là bản vẽ** (`curl /api/menus/me` + áp CHÍNH tập key cắt regex từ file đã ship; control-âm bỏ 1 key để chứng phép đo có phân biệt). **`filterForAdmin` mới là bước MỘT** — còn `resolvePath→null` drop im lặng (#50) + group rỗng-hoá tụt xuống render như LEAF. **typo-noop**: key ẩn sai chính tả = `Set.has` false, module vẫn hiện, tsc vẫn xanh ⇒ assert TỪNG key TỒN TẠI trong cây prod. MAJOR: comment nhồi 3 số-đếm, 1 số sai ngay lúc land (số chép từ skill stale) ⇒ CẤM số-đếm trong comment. **Evidence:** `runs/2026-07-31-S164-4gd-khkk-fanout/sub-reviewer-gate-k6.md`
|
||||||
|
|
||||||
- **[→ archive/2026-07.md — verbatim ĐÃ VERIFY moved-not-cut 4/4 @S166-curate] S164/S165 gate K3 · S164 gate K1 · S162 cây-4-folder · S161-W2 KHKK-CRUD (digest):** tham-số-chết-vì-thiếu-tầng-UI (đo TẦNG-CUỐI ai GỬI) · sweep 2-cờ bất-đối-xứng · flake ⇒ "N/0 tươi" phải khai SỐ LƯỢT · giá-trị lớn nhất ở GATE QUY-TRÌNH không ở mã · **ghost-policy** (grep policy, đừng suy từ "key đã vào All") · default-lấn-ngữ-nghĩa · im-lặng-về-độ-chính-xác-của-con-số ×3/diff · **#82 ngược chiều** (FE OR nhiều key LỎNG HƠN policy khớp-CHÍNH-XÁC-1-key ⇒ tự chuốc 403) · hợp-đồng FE↔BE VỠ 6 điểm mà tsc+build+574-test XANH (2 lỗi CHE NHAU) ⇒ so ROUTE+FIELD trên ĐĨA.
|
- **[→ archive/2026-07.md — verbatim ĐÃ VERIFY moved-not-cut 4/4 @S166-curate] S164/S165 gate K3 · S164 gate K1 · S162 cây-4-folder · S161-W2 KHKK-CRUD (digest):** tham-số-chết-vì-thiếu-tầng-UI (đo TẦNG-CUỐI ai GỬI) · sweep 2-cờ bất-đối-xứng · flake ⇒ "N/0 tươi" phải khai SỐ LƯỢT · giá-trị lớn nhất ở GATE QUY-TRÌNH không ở mã · **ghost-policy** (grep policy, đừng suy từ "key đã vào All") · default-lấn-ngữ-nghĩa · im-lặng-về-độ-chính-xác-của-con-số ×3/diff · **#82 ngược chiều** (FE OR nhiều key LỎNG HƠN policy khớp-CHÍNH-XÁC-1-key ⇒ tự chuốc 403) · hợp-đồng FE↔BE VỠ 6 điểm mà tsc+build+574-test XANH (2 lỗi CHE NHAU) ⇒ so ROUTE+FIELD trên ĐĨA.
|
||||||
|
|
||||||
@ -93,6 +94,7 @@ Adversarial pre-commit reviewer SOLUTION_ERP. Read-only verify + live curl prod
|
|||||||
---
|
---
|
||||||
|
|
||||||
## 🔄 Curate trigger
|
## 🔄 Curate trigger
|
||||||
|
- ⚠️ **@S167-K7 curate 2 pass: 22.1KB → 19.0KB** (move verbatim K5·K4c·K7·K6 → `archive/2026-07.md` §`@S167-K7-curate` + §`-curate-2`, mỗi pass probe moved-not-cut + control-âm TRƯỚC khi cắt, 4/4 pass). **CÒN NỢ ~1.9KB** để về 17.1KB — ứng viên kế: gộp khối `📁 Area memory` (nhiều dòng L2 đã stale >2 tháng). 🔴 **no-self-exempt CÓ ÁP**: entry K7 của CHÍNH TÔI là entry to nhất (3081B, phá luật "≤1.5K/entry" của chính file này) ⇒ nén trước, không miễn cho mình. 🔴 **Bài S166-K5 TÁI PHÁT NGAY trong pass này**: digest sinh bằng script đẻ chữ hỏng câm (`'ll3/13'` thay `'13/13'`) — byte-count không bắt được, chỉ ĐỌC LẠI BẰNG MẮT mới thấy. Script sinh chữ = luôn đọc lại output.
|
||||||
- ✅ **NỢ CURATE @S166-K4b ĐÃ TRẢ @S166-K5** (23.4KB→~17KB): move VERBATIM 3 entry đã hẹn (K4b·K4a·vá-F-1) → `archive/2026-07.md` §`@S166-K5-curate`. Trình tự bài S102: **append archive TRƯỚC → probe 3/3 kèm control-âm → assert `line in arc` NGAY TRONG script cắt → mới cắt → hậu-kiểm L1=0/archive=1**. Bài mới: digest sinh bằng escape `\uXXXX` **đẻ chữ hỏng câm** (byte-count không bắt được) ⇒ **script sinh chữ phải ĐỌC LẠI bằng mắt**.
|
- ✅ **NỢ CURATE @S166-K4b ĐÃ TRẢ @S166-K5** (23.4KB→~17KB): move VERBATIM 3 entry đã hẹn (K4b·K4a·vá-F-1) → `archive/2026-07.md` §`@S166-K5-curate`. Trình tự bài S102: **append archive TRƯỚC → probe 3/3 kèm control-âm → assert `line in arc` NGAY TRONG script cắt → mới cắt → hậu-kiểm L1=0/archive=1**. Bài mới: digest sinh bằng escape `\uXXXX` **đẻ chữ hỏng câm** (byte-count không bắt được) ⇒ **script sinh chữ phải ĐỌC LẠI bằng mắt**.
|
||||||
- Hook-cap **>17.1KB** (24.4KB read-limit, S109) → archive recent → L2 `archive/<period>.md` (append additive) + `_INDEX.md` pointer. Stale >3mo → remove.
|
- Hook-cap **>17.1KB** (24.4KB read-limit, S109) → archive recent → L2 `archive/<period>.md` (append additive) + `_INDEX.md` pointer. Stale >3mo → remove.
|
||||||
- **Prev curates:** S166-K5 (probe 3/3) · S162 (2/2) · S145 · S140 · S134 · S128 · **S102 recovery: cut-not-moved ×5 `f229b07` ⇒ mọi L1-shrink kèm '→archive' BẮT BUỘC grep moved-not-cut TRƯỚC khi trust** · S116·S113·S109·S80·S71·S70·S40.
|
- **Prev curates:** S166-K5 (probe 3/3) · S162 (2/2) · S145 · S140 · S134 · S128 · **S102 recovery: cut-not-moved ×5 `f229b07` ⇒ mọi L1-shrink kèm '→archive' BẮT BUỘC grep moved-not-cut TRƯỚC khi trust** · S116·S113·S109·S80·S71·S70·S40.
|
||||||
|
|||||||
@ -211,3 +211,23 @@
|
|||||||
- **S166 (08-01) gate K4a 49-row menu KHKK 8-nhóm — PWF 4/0 blocker:** lõi ĐÚNG hiếm thấy (bộ-sinh 1 nguồn feed 3 phía `All`/seed-row/grant ⇒ khớp by-construction; 8/8 nhãn khớp TỪNG KÝ TỰ; suite **620/620 tôi tự chạy**). **Bài thu:** ① 🔴 **tham-số-trang-trí** — URL leaf mang `?group=n` mà `grep "get('group')"` ×2 app = **0 hit** ⇒ 7 nhóm × 6 leaf mở ra CÙNG nội dung, `WfView` hiện toàn bộ workflow type-10; *đo "route có tồn tại" là chưa đủ, phải đo **AI ĐỌC** từng param* (đối xứng ca S164 "tham-số-chết-vì-thiếu-tầng-UI", lần này chết ở tầng ĐỌC). ② **mỏ-neo tự-vô-hiệu ngay lúc land** — 2 comment mới trỏ dòng trong CHÍNH file đang sửa, sai đúng **+34 = kích thước khối vừa chèn** (tác giả tính trên bản trước khi chèn); cùng diff đó vừa DỌN lỗi y hệt ở BE (`1893`) ⇒ **mỏ neo trỏ vào file mình đang sửa phải đo SAU khi chèn**, hoặc bỏ số dùng grep-anchor. ③ **ô canonical sai đo-được bằng 1 curl** — STATUS ghi bundle `mySTlx42`/`CZAYiWWa`, live = `1yiNV4VH`/`DoULfmdT` (2 css KHỚP ⇒ chứng đo đúng site, không phải đo nhầm). ④ **bất-biến từng-vỡ-thật lại là bất-biến 0 test** (revoker không đụng `Khkk_*`; thêm 1 chữ vào filter thì 620/620 vẫn xanh, đúng kịch bản Run #423 mà chính comment dẫn lại) — khuôn có sẵn `Revoke_DoesNotTouch_PeModule`. ⑤ test gọi seeder LẺ qua reflection ⇒ chuỗi `SeedAsync` (grant→revoke→site-3) ngoài vùng test ⇒ **restart-thật KHÔNG được coi là đã phủ**. Tag `[s166, tham-so-trang-tri, moneo-tu-vo-hieu, o-canonical-sai-1-curl]` **Evidence:** `runs/2026-07-31-S164-4gd-khkk-fanout/sub-reviewer-gate-k4a.md`
|
- **S166 (08-01) gate K4a 49-row menu KHKK 8-nhóm — PWF 4/0 blocker:** lõi ĐÚNG hiếm thấy (bộ-sinh 1 nguồn feed 3 phía `All`/seed-row/grant ⇒ khớp by-construction; 8/8 nhãn khớp TỪNG KÝ TỰ; suite **620/620 tôi tự chạy**). **Bài thu:** ① 🔴 **tham-số-trang-trí** — URL leaf mang `?group=n` mà `grep "get('group')"` ×2 app = **0 hit** ⇒ 7 nhóm × 6 leaf mở ra CÙNG nội dung, `WfView` hiện toàn bộ workflow type-10; *đo "route có tồn tại" là chưa đủ, phải đo **AI ĐỌC** từng param* (đối xứng ca S164 "tham-số-chết-vì-thiếu-tầng-UI", lần này chết ở tầng ĐỌC). ② **mỏ-neo tự-vô-hiệu ngay lúc land** — 2 comment mới trỏ dòng trong CHÍNH file đang sửa, sai đúng **+34 = kích thước khối vừa chèn** (tác giả tính trên bản trước khi chèn); cùng diff đó vừa DỌN lỗi y hệt ở BE (`1893`) ⇒ **mỏ neo trỏ vào file mình đang sửa phải đo SAU khi chèn**, hoặc bỏ số dùng grep-anchor. ③ **ô canonical sai đo-được bằng 1 curl** — STATUS ghi bundle `mySTlx42`/`CZAYiWWa`, live = `1yiNV4VH`/`DoULfmdT` (2 css KHỚP ⇒ chứng đo đúng site, không phải đo nhầm). ④ **bất-biến từng-vỡ-thật lại là bất-biến 0 test** (revoker không đụng `Khkk_*`; thêm 1 chữ vào filter thì 620/620 vẫn xanh, đúng kịch bản Run #423 mà chính comment dẫn lại) — khuôn có sẵn `Revoke_DoesNotTouch_PeModule`. ⑤ test gọi seeder LẺ qua reflection ⇒ chuỗi `SeedAsync` (grant→revoke→site-3) ngoài vùng test ⇒ **restart-thật KHÔNG được coi là đã phủ**. Tag `[s166, tham-so-trang-tri, moneo-tu-vo-hieu, o-canonical-sai-1-curl]` **Evidence:** `runs/2026-07-31-S164-4gd-khkk-fanout/sub-reviewer-gate-k4a.md`
|
||||||
|
|
||||||
- **S166 (08-01) vá F-1 ô-tích opt-out `applyLevelFinalize` KHKK — PWF 7 finding (3 MAJOR):** vá ĐÚNG kỹ thuật (mirror 2 app chứng bằng **blob-SHA in ngay trong `git diff` index-line** — rẻ hơn sha256 tay; body không rò field; cấp thường bất-biến từng bit). **Bài thu:** ① 🔴 **khuôn-nguồn có 2 tầng, chú-thích nói dối tầng dưới** — implementer trích ĐÚNG dòng JSX PE `:772-791` + nhãn "khuôn PE S96 default tick", nhưng `useState` PE `:61` = `false` (S97 owner **ĐẢO** sang opt-IN) và chính chú-thích PE `:769-771` cũng stale ⇒ **đo khuôn phải đo GIÁ-TRỊ KHỞI-TẠO, không đọc chú-thích**; tái phát S165 F-1 (đo sai tầng). ② **cây làm việc PHÌNH GIỮA LƯỢT REVIEW** — đo `git status` 2 lần cách ~40 tool-call: 5 file→7 file (+121 dòng K4a, `All` 64→113) ⇒ **verdict phải neo mốc đo + re-đo `--stat` TRƯỚC khi phát verdict**, nếu không lead commit ké code chưa qua cổng. ③ vắng-mặt-đọc-thành-sạch: `isApproved ⇒ mọi Bước 'Done'` ⇒ CEO chưa đụng vẫn "đã duyệt", dấu-vết duy nhất = THIẾU dòng "✓ ký". ④ **sợi-dây-1-argument** (projection `l.AllowApproverFinalize`) 0 test ⇒ xoá là control biến mất mà 614 test + tsc vẫn xanh. Tag `[s166, khuon-2-tang-chu-thich-noi-doi, cay-phinh-giua-review, sợi-day-1-argument]` **Evidence:** `runs/2026-07-31-S164-4gd-khkk-fanout/sub-reviewer-f1-checkbox.md`
|
- **S166 (08-01) vá F-1 ô-tích opt-out `applyLevelFinalize` KHKK — PWF 7 finding (3 MAJOR):** vá ĐÚNG kỹ thuật (mirror 2 app chứng bằng **blob-SHA in ngay trong `git diff` index-line** — rẻ hơn sha256 tay; body không rò field; cấp thường bất-biến từng bit). **Bài thu:** ① 🔴 **khuôn-nguồn có 2 tầng, chú-thích nói dối tầng dưới** — implementer trích ĐÚNG dòng JSX PE `:772-791` + nhãn "khuôn PE S96 default tick", nhưng `useState` PE `:61` = `false` (S97 owner **ĐẢO** sang opt-IN) và chính chú-thích PE `:769-771` cũng stale ⇒ **đo khuôn phải đo GIÁ-TRỊ KHỞI-TẠO, không đọc chú-thích**; tái phát S165 F-1 (đo sai tầng). ② **cây làm việc PHÌNH GIỮA LƯỢT REVIEW** — đo `git status` 2 lần cách ~40 tool-call: 5 file→7 file (+121 dòng K4a, `All` 64→113) ⇒ **verdict phải neo mốc đo + re-đo `--stat` TRƯỚC khi phát verdict**, nếu không lead commit ké code chưa qua cổng. ③ vắng-mặt-đọc-thành-sạch: `isApproved ⇒ mọi Bước 'Done'` ⇒ CEO chưa đụng vẫn "đã duyệt", dấu-vết duy nhất = THIẾU dòng "✓ ký". ④ **sợi-dây-1-argument** (projection `l.AllowApproverFinalize`) 0 test ⇒ xoá là control biến mất mà 614 test + tsc vẫn xanh. Tag `[s166, khuon-2-tang-chu-thich-noi-doi, cay-phinh-giua-review, sợi-day-1-argument]` **Evidence:** `runs/2026-07-31-S164-4gd-khkk-fanout/sub-reviewer-f1-checkbox.md`
|
||||||
|
|
||||||
|
|
||||||
|
## @S167-K7-curate — verbatim moved from L1 (reviewer MEMORY.md)
|
||||||
|
|
||||||
|
> Moved 2026-08-01 during gate K7 closeout. L1 was 22KB vs 17.1KB cap.
|
||||||
|
> Digest line kept in L1 points here.
|
||||||
|
|
||||||
|
- **S166 (08-01) gate K5 cây GĐ2 mở 8 sub-folder nhóm — PASS 0 blocker (1 CLARIFY + 5 MINOR):** lõi hiếm-đúng: bất-biến Σ do **CẤU TRÚC** (1 mảng nguồn `khkkItems` → 1 phép phân hoạch if/else TOÀN PHẦN → `stage.leaves` vẫn là chỗ đếm duy nhất `stageCount():96`) nên không phụ thuộc ai nhớ cộng; route `?group=` nối đủ 3 chặng (FE `:79-80` → param `:124` → BE `Where(:757)`), **khác hẳn tham-số-trang-trí K4a**. **Bài thu:** ① 🔴 **phép-đo-rẻ-nhất nằm ở file RÁC của lane khác** — `menus.json` untracked ở gốc repo (sản phẩm curl của cicd-monitor) chính là payload `/api/menus/me` LIVE ⇒ đối chiếu nhãn cây suy-ra ⟂ nhãn sidebar thật **8/8 khớp từng ký tự + `displayLabel` đều null**, thứ mà đọc code không bao giờ chứng được. Cùng file đó là **rào commit** (`git add -A` sẽ nuốt 44KB rác) ⇒ 1 vật vừa là bằng-chứng vừa là rủi-ro, đừng chỉ báo 1 vế. ② **chân-lý-rỗng phải khai là rỗng**: curl prod `total=0` phiếu KHKK ⇒ phân bố `approvalGroup` = `{}`, KHÔNG dùng để kết luận gì; nhưng cùng con số ấy đẻ ra sự-thật dùng được cho UAT (ngày đầu mọi gói hiện **8 dòng "SẮP" rỗng** — đúng spec, dễ bị đọc thành hỏng). ③ **tương-đương-từng-vế > "cùng tập cùng thứ tự"**: vòng lặp GĐ3/4 bị viết lại ⇒ lập bảng 4 vế (tập id · thứ tự = `Set` giữ thứ-tự-chèn · khử trùng · dòng-phụ `find` = phiếu ĐẦU) mới dám phán 0-regression; thân stage4 **0 tham chiếu `p`** là lý do cấu-trúc khiến rewrite an toàn. ④ **tiền-lệ phải đo ở BASE, không đo ở cây đã sửa**: `git show 88e7ecf:<file> | grep -A6 '<summary'` cho thấy 6/6 summary cũ chỉ chứa `span`/`svg` ⇒ `<button>` lồng trong `<summary>` là **khuôn MỚI** (nested-interactive a11y), lane tưởng đang bám tiền lệ. ⑤ **"15/15 OK" chết theo phiên**: script `new Function` không commit + FE **0 hạ tầng test** (`package.json` chỉ `dev|build|lint|preview`) + chuông Σ là `import.meta.env.DEV` ⇒ prod bị strip ⇒ 0 lưới an toàn; đừng đọc số-test-ad-hoc thành đã-phủ. ⑥ 2 nghĩa của 1 dòng checklist ("GĐ3/4 by c.type") — lead và lane **cùng chọn 1 nghĩa không chứng minh nghĩa đó đúng ý owner** ⇒ ghim cách đọc vào commit. Tag `[s166, bang-chung-o-file-rac, chan-ly-rong, tuong-duong-tung-ve, tien-le-do-o-base]` **Evidence:** `runs/2026-07-31-S164-4gd-khkk-fanout/sub-reviewer-gate-k5.md`
|
||||||
|
|
||||||
|
- **S166 (08-01) gate K4c chốt cụm-2 (FE lọc nhóm WfView + 1 Fact test) — PASS 0 blocker (1 CLARIFY + 3 MINOR):** ① 🔴 **flake chỉ lộ ở LƯỢT ĐẦU, và chính tôi giết mất chứng cớ**: chạy `dotnet test -v q` SONG SONG với `tsc` ⇒ 1 test CŨ đỏ (`Failed 1/8`) rồi 10/10 xanh sau đó + suite 622/0 ⇒ `-v q` **nuốt thông điệp assert** ⇒ có tên test mà không có lý do. Bài: lượt chạy ĐẦU dùng verbosity thường, `-v q` để dành cho lượt lặp. Truy nhiễm phải đo 3 vế mới dám nói "không do diff": fixture `:memory:` per-test · codegen **0 static** · xUnit 2.9.3 same-class = tuần tự. Báo "8/8 ở 11/12 lượt", KHÔNG báo 8/8 tuyệt đối. ② **probe RED bằng đổi TEST-INPUT (không chạm prod) là hợp lệ CHỈ KHI tham số xuất hiện ĐÚNG 1 CHỖ trong handler** — grep xong mới được chấp nhận; ≥2 chỗ là lập luận sập. ③ `Total` là dấu-vân-tay lọc-SERVER **chỉ khi** `CountAsync` chạy trên `q` SAU `Where` và TRƯỚC `Skip/Take` — đọc `ProjectPagedAsync:790-792` mới dám ký, đừng tin comment. ④ **queryKey KHÔNG thêm trục là ĐÚNG** khi BE endpoint không nhận trục đó (đọc controller: chỉ `applicableType`+`isUserSelectable`) + giá trị lọc là **dẫn xuất trong render** (không `useState`) ⇒ miễn nhiễm bẫy "đổi leaf không remount". ⑤ Guard `/^[1-8]$/` chặn `Number("03")=3`, và **đồng nhất 3 trang** KHKK — kiểm cả 3, lệch 1 chỗ là 2 luật cho 1 tham số. ⑥ Lane khai bundle `1,643.96 kB` — tôi chạy lại `npm run build`, **trùng từng chữ số** ⇒ claim đo-lường có thật. Tag `[s166, chung-co-bi-nuot-boi-verbosity, probe-1-cho-moi-hop-le, total-la-van-tay]` **Evidence:** `runs/2026-07-31-S164-4gd-khkk-fanout/sub-reviewer-gate-k4c.md`
|
||||||
|
|
||||||
|
|
||||||
|
## @S167-K7-curate-2 — verbatim moved from L1 (reviewer MEMORY.md)
|
||||||
|
|
||||||
|
> Moved 2026-08-01 at gate-K7 closeout. L1 over the 17.1KB cap; both entries also broke
|
||||||
|
> the file's own "entry <= 1.5K chars" rule. no-self-exempt: the K7 entry is my own.
|
||||||
|
|
||||||
|
- **S167 (08-01) gate K7 cầu KHKK→HĐ (13 file, 3 nhóm) — PASS-WITH-FLAGS 10 (2 MAJOR + 7 MINOR + 1 CLARIFY), 0 blocker:** ① 🔴 **1 con số đóng 2 câu hỏi**: baseline 622 (đo ở K4c CÙNG NGÀY) + 17 `[Fact]` mới (đếm `grep -c '^\s*\[Fact\]'`, không chép) = kỳ vọng 639, đo được **đúng 639/0** ⇒ chứng CẢ "17 test có chạy thật (không bị filter/skip câm)" LẪN "0 test cũ vỡ" — rẻ hơn 2 phép đo rời. ② **mỏ-neo tự-vô-hiệu TÁI PHÁT và lan 3 nơi** (K4b-class): 3 chú thích mới trỏ `DbInitializer.cs:2516`; `git show HEAD:<file> | grep -n` = 2516, `grep -n` trên cây = **2523** — CHÍNH diff này chèn 7 dòng ở `:2353-2359` đẩy nó xuống ⇒ đúng lúc viết, **sai ngay khoảnh khắc land**. Bài: phải đo ở CẢ 2 THẾ GIỚI (HEAD ⟂ cây) mới thấy, đo 1 thế giới là im. ③ **prod probe sửa được con số của chính comment**: `GET /api/permissions/by-role/{id}` ×13 vai → `Contracts.CanCreate` **1/13 true (chỉ Admin)** ⇒ tiền-đề seeder ĐÚNG (Drafter/Procurement=False, lỗ THẬT), nhưng comment "403 với 13/13 role… chỉ Admin qua nhờ bypass" sai 2 vế: thật là **12/13**, và Admin **đã có row True** chứ không chỉ bypass. Cùng probe đẻ **acceptance rẻ nhất cho sau deploy: log phải là `0 added + 2 upgraded`** — lệch = có thứ khác lật quyền. Khai giới hạn: prod CHƯA deploy ⇒ chỉ chứng TIỀN-ĐỀ, không chứng seeder chạy đúng. ④ **test có răng nhưng phủ NỬA**: T11 double-click assert 4 vế (Conflict + count=1 + line trỏ HĐ đầu + `LastSeq=1` không đốt mã) — nhưng là `await` lần 1 RỒI lần 2 ⇒ chỉ phủ ca TUẦN TỰ; ca ĐỒNG THỜI vẫn hở (`:128-129` đọc-rồi-ghi trong bộ nhớ, 0 unique-index, 0 RowVersion, 0 transaction bao ngoài — transaction duy nhất là của codegen, đã commit trước). Comment test khai đúng phạm vi ⇒ KHÔNG phải lane nói quá; rủi ro là người sau đọc "T-idem có" thành "idempotency đóng". ⑤ **ngoại lệ authz đúng cách**: map `Dictionary<action,policy>` + `Should().Be()` khớp TUYỆT ĐỐI (không `StartsWith`/skip-list) + action ngoài map vẫn rơi `else` đòi prefix module ⇒ **action tương lai không tự miễn trừ**; kèm `Keys.Should().BeSubsetOf(byName.Keys)` chống **cấu-hình-chết** — vế "vắng-mặt trông giống ổn" mà lane TỰ đặt. ⑥ **mirror byte-identical đẻ rủi ro riêng**: 3 đường dẫn cứng `/contracts/${id}` trong file dùng chung 2 app ⇒ phải grep route ở CẢ `fe-user/App.tsx:65` và `fe-admin/App.tsx:84` (đều có, không thủng) — SHA-identical KHÔNG bảo chứng route-identical. ⑦ residual: 3 `scripts/*.ps1` dirty ngoài cả 13-file-set lẫn lead-set. Tag `[s167, mot-so-dong-2-cau-hoi, moneo-tu-vo-hieu-lan-3-noi, probe-sua-so-cua-comment, rang-phu-nua, sha-khong-bao-chung-route]` **Evidence:** `runs/2026-07-31-S164-4gd-khkk-fanout/sub-reviewer-gate-k7.md`
|
||||||
|
|
||||||
|
- **S167 (08-01) gate K6 admin thu-gọn (1 file FE, tập 12 key ẩn) — PASS-WITH-FLAGS 5 (1 MAJOR + 1 CLARIFY + 3 MINOR):** ① 🔴 **cây menu PROD SỐNG là thước, seed chỉ là bản vẽ**: `curl /api/menus/me` (token admin, 44KB) → áp CHÍNH tập key **cắt bằng regex từ file đã ship** ⇒ 200 node · 166 lá → **15 lá**, trùng tên 15/15 + control-âm bỏ `KeHoachKyKet` = 63 (+48) khớp lane. Cùng payload đóng luôn FLAG "row chèn tay": **10 `AwV2*`, 0 `AwV2_Contract`**. Khai giới hạn: endpoint đã lọc-quyền ⇒ row không có Permission thì vô hình (nhưng cũng vô hình trên sidebar ⇒ không lệch C1). ② **`filterForAdmin` mới là bước MỘT**: lá còn "THẤY" phải qua tiếp `resolvePath→null ⇒ MenuLeaf drop im lặng` (#50) và **group rỗng-hoá tụt xuống render như LEAF** (`children.length>0`) — lane bỏ cả 2, tôi đo: 0 drop · 0 rỗng-hoá. ③ **typo-noop**: key ẩn sai chính tả = `Set.has` false, module vẫn hiện, tsc vẫn xanh ⇒ phải assert từng key TỒN TẠI trong cây prod (12/12 OK; control-âm 3 key bịa đều MISS). ④ **MAJOR: comment mới nhồi 3 số-đếm, 1 số SAI ngay lúc land** — "28 leaf `Ct_`" (thật 42 lá/49 key, S155 thêm WfView/Approved/Deleted), số chép từ skill `permission-matrix` stale ⇒ đúng bệnh #84(4) "CẤM số-đếm trong comment". ⑤ **CLARIFY hệ-quả không ai khai**: prod có `Hrm/Off/Personal` `isVisible=false`, fe-user lọc `isVisible!==false` còn fe-admin KHÔNG đọc cờ đó ⇒ admin là lối vào CUỐI, ẩn nốt = 3 module mất menu ở CẢ 2 app. ⑥ display-only chứng 3 tầng: diff (grep `+` chỉ hit COMMENT) < kiến-trúc (`usePermission:15` đọc cây THÔ, chỉ 2 consumer) < curl prod 200×2 endpoint đã ẩn. Tag `[s167, thuoc-la-cay-prod, filter-moi-la-buoc-mot, typo-noop, so-dem-trong-comment]` **Evidence:** `runs/2026-07-31-S164-4gd-khkk-fanout/sub-reviewer-gate-k6.md`
|
||||||
|
|||||||
@ -0,0 +1,25 @@
|
|||||||
|
CICD-K7: IN-PROGRESS (đang đo — verdict ở dòng cuối)
|
||||||
|
|
||||||
|
# sub-cicd-verify-k7 — deploy verify K7 (bridge KHKK→HĐ), S167 2026-08-01
|
||||||
|
|
||||||
|
> Artifact chống-#53: ghi TỪNG MỤC ngay khi có bằng chứng. Return chỉ là tóm tắt.
|
||||||
|
> Baseline PRE đọc từ `sub-cicd-verify-k6.md` (post-#440): admin js `Ig1R5tAh` · eoffice js `DFOCQ2ks` · gate 622=45+577 · Mig 71 · sys.tables 97.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## M0 — Push + path-filter (ĐÃ ĐO)
|
||||||
|
|
||||||
|
- HEAD = `aaed699228f418fa31ecba07414709de465502fc`
|
||||||
|
subject: `[CLAUDE] Contract: K7 SP-002 bridge KHKK->HD hoi-2-khoa + Line.ContractId + grant seeder (gate PASS-WITH-FLAGS 10, F-8 fixed)`
|
||||||
|
committed: 2026-08-01 13:04:49 +0700
|
||||||
|
- `git log origin/main..HEAD` = **RỖNG** ⇒ đã push thật.
|
||||||
|
- Range `1c6d77a..aaed699` = 7 commit (6 × `wal: flush` + 1 code).
|
||||||
|
- **File code trong range (13/13 khớp đề bài):**
|
||||||
|
- BE (4): `Api/Controllers/ContractSigningPlansController.cs` · `Application/ContractSigningPlans/ContractSigningPlanFeatures.cs` · `Application/ContractSigningPlans/CreateContractFromSigningPlanFeatures.cs` · `Infrastructure/Persistence/DbInitializer.cs`
|
||||||
|
- FE mirror ×2 app (6): `fe-{admin,user}/src/hooks/usePipelineStages.ts` · `.../pages/khkk/KhkkDetailPage.tsx` · `.../types/khkk.ts`
|
||||||
|
- Test (3): `Api/AuthorizePolicyRegressionTests.cs` · `Api/KhkkBridgeAuthzTests.cs` · `Application/CreateContractFromSigningPlanTests.cs`
|
||||||
|
- (còn lại 14 file đều `.claude/**` = paths-ignore, KHÔNG trigger)
|
||||||
|
- **M3-phần-1 (git):** `git diff --name-only 1c6d77a..aaed699 -- '*Migrations*'` = **0 file** ⇒ 0-mig chứng-minh-được từ git.
|
||||||
|
- Path-filter: có file `src/**` + `fe-*/**` ⇒ CI PHẢI chạy (không skip).
|
||||||
|
|
||||||
|
---
|
||||||
@ -0,0 +1,469 @@
|
|||||||
|
GATE-K7: PASS-WITH-FLAGS 10
|
||||||
|
|
||||||
|
# Gate K7 — adversarial review cầu KHKK → HĐ (S167, 2026-08-01)
|
||||||
|
|
||||||
|
Vai: reviewer (READ-only). Base đo: HEAD = `f5ee146` (`wal: flush 20260801T1242`), diff UNCOMMITTED.
|
||||||
|
Nguyên tắc: mọi mục dưới đây là phép đo TỰ CHẠY của reviewer, không chép claim của lane.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## §0 — Mốc cây + residual scope
|
||||||
|
|
||||||
|
Đo bằng `git status --porcelain` + `git diff --stat` (2026-08-01, đầu lượt review).
|
||||||
|
|
||||||
|
13 file thuộc phạm vi K7 — CÓ ĐỦ, đúng như spec giao:
|
||||||
|
|
||||||
|
| Nhóm | File | Trạng thái |
|
||||||
|
|---|---|---|
|
||||||
|
| BE | `src/Backend/SolutionErp.Api/Controllers/ContractSigningPlansController.cs` | M (+33) |
|
||||||
|
| BE | `src/Backend/SolutionErp.Application/ContractSigningPlans/ContractSigningPlanFeatures.cs` | M (+23/-2) |
|
||||||
|
| BE | `src/Backend/SolutionErp.Application/ContractSigningPlans/CreateContractFromSigningPlanFeatures.cs` | **?? MỚI** (252 dòng) |
|
||||||
|
| BE | `src/Backend/SolutionErp.Infrastructure/Persistence/DbInitializer.cs` | M (+86) |
|
||||||
|
| FE | `fe-{admin,user}/src/hooks/usePipelineStages.ts` | M (+61/-?) ×2 |
|
||||||
|
| FE | `fe-{admin,user}/src/pages/khkk/KhkkDetailPage.tsx` | M (+308) ×2 |
|
||||||
|
| FE | `fe-{admin,user}/src/types/khkk.ts` | M (+49) ×2 |
|
||||||
|
| Test | `tests/SolutionErp.Infrastructure.Tests/Api/AuthorizePolicyRegressionTests.cs` | M (+48/-?) |
|
||||||
|
| Test | `tests/SolutionErp.Infrastructure.Tests/Api/KhkkBridgeAuthzTests.cs` | **?? MỚI** |
|
||||||
|
| Test | `tests/SolutionErp.Infrastructure.Tests/Application/CreateContractFromSigningPlanTests.cs` | **?? MỚI** |
|
||||||
|
|
||||||
|
**FLAG RESIDUAL (F-R1, MINOR)** — 3 file dirty NGOÀI cả 13-file-set LẪN lead-set đã khai
|
||||||
|
(`.claude/*` + `docs/STATUS.md`):
|
||||||
|
|
||||||
|
- `scripts/distill-shard-probe.ps1` (+1/-1)
|
||||||
|
- `scripts/measure-agent-memory.ps1` (+1/-1)
|
||||||
|
- `scripts/memory-archive-gate.ps1` (+11/-2)
|
||||||
|
|
||||||
|
Đọc diff: cả 3 là việc **governance memory-index rotation `_INDEX-<period>.md` @S167**, KHÔNG
|
||||||
|
liên quan K7. Không phải lỗi nội dung; rủi ro là **`git add -A` sẽ nuốt chúng vào commit K7**.
|
||||||
|
Đề nghị: hoặc khai chúng vào lead-set, hoặc commit tách. Untracked trong `src`/`tests` = đúng
|
||||||
|
3 file MỚI đã khai, **0 file lạ**.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## §1 — TRỌNG TÂM 1: hội-2-khoá 2 tầng (BE ∧ FE)
|
||||||
|
|
||||||
|
### 1a. Tầng server — ĐO ĐƯỢC, ĐẠT
|
||||||
|
|
||||||
|
- Class-level: `ContractSigningPlansController.cs:29` → `[Authorize(Policy = "KeHoachKyKet.Read")]`.
|
||||||
|
- Action mới: `ContractSigningPlansController.cs:227-228` →
|
||||||
|
`[HttpPost("{id:guid}/create-contract")]` + `[Authorize(Policy = "Contracts.Create")]`.
|
||||||
|
- ASP.NET Core cộng dồn class + action theo AND ⇒ hội 2 khoá là THẬT, không phải claim.
|
||||||
|
- Policy `Contracts.Create` có tồn tại: `MenuKeys.cs:12` khai const `Contracts`, và
|
||||||
|
`MenuKeys.cs:228` đưa `Contracts` VÀO mảng `All` ⇒ `Program.cs` sinh đủ 4 policy.
|
||||||
|
(Nếu key ngoài `All` thì runtime 500 chứ không 403 — đã kiểm, không rơi vào ca đó.)
|
||||||
|
|
||||||
|
### 1b. Tầng FE — ĐỌC DIFF TỪNG DÒNG (lead ghi §3-§4 on-behalf; đây là lưới THẬT đầu tiên)
|
||||||
|
|
||||||
|
`fe-user/src/pages/khkk/KhkkDetailPage.tsx` (bản admin byte-identical, xem §3c):
|
||||||
|
|
||||||
|
- `:138-140` — `const { can } = usePermission()`; `canReadPlan = can('KeHoachKyKet','Read')`;
|
||||||
|
`canCreateContract = can('Contracts','Create')`. Đúng 2 key của server, KHÔNG OR `Khkk_*`
|
||||||
|
(gotcha #85 giữ được).
|
||||||
|
- `:299` — `showBridge = phase === KhkkPhase.DaDuyet && canReadPlan`.
|
||||||
|
- `:303-312` — `bridgeBlockReason`: nhánh ĐẦU TIÊN là `!canCreateContract` ⇒ nút render nhưng
|
||||||
|
`disabled`.
|
||||||
|
- `:352-363` — `<Button disabled={bridgeBlockReason != null} onClick={() => setBridgeOpen(true)}>`.
|
||||||
|
|
||||||
|
**Truy đường lách:** grep toàn file cho `setBridgeOpen(true)` → xuất hiện ĐÚNG 1 lần, tại
|
||||||
|
`onClick` của nút đã disabled. Dialog `CreateContractFromKhkkDialog` render có điều kiện
|
||||||
|
`{bridgeOpen && ...}` (`:781`) ⇒ **không có đường nào tới `create.mutate()` khi thiếu
|
||||||
|
`Contracts.Create`**. React không bắn `onClick` trên `<button disabled>`.
|
||||||
|
⇒ FE **KHÔNG lỏng hơn server**. ĐẠT.
|
||||||
|
|
||||||
|
**Verify hook đọc gì:** `fe-user/src/hooks/usePermission.ts` — `findNode(menu, key)` đệ quy trên
|
||||||
|
cây THÔ từ `AuthContext`, trả `false` khi không thấy node; `Create` → `node.canCreate`. Nghĩa là
|
||||||
|
gate FE phụ thuộc **row Permission của key `Contracts`**, đúng thứ seeder mới cấp.
|
||||||
|
|
||||||
|
**CLARIFY-1 (không phải lỗi mã, là điều kiện nghiệm thu):** cây menu FE nạp lúc login và cache
|
||||||
|
`localStorage` (`AuthContext.loadMenu`). Comment seeder khai "grant ăn @restart, KHÔNG phải
|
||||||
|
deploy FE" — đúng cho BE, nhưng **nút vẫn xám cho tới khi user logout/login lại** vì `can()` đọc
|
||||||
|
menu cache cũ. UAT phải nói rõ bước đăng-xuất/đăng-nhập, nếu không sẽ bị đọc thành "seeder không
|
||||||
|
ăn".
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## §2 — TRỌNG TÂM 2: seeder grant, gotcha #84 nghiêm
|
||||||
|
|
||||||
|
`DbInitializer.cs:2738-2814` = `SeedContractCreateAccessAsync`; call-site `:2361`.
|
||||||
|
|
||||||
|
### 2c. THỨ TỰ (vế nặng nhất) — ĐO BẰNG SỐ DÒNG THẬT, ĐẠT
|
||||||
|
|
||||||
|
`awk` liệt kê mọi `await Seed*/Revoke*` trong khối 2250-2420:
|
||||||
|
|
||||||
|
```
|
||||||
|
2331: await SeedAllRolesReviewReadPermissionsAsync(...)
|
||||||
|
2341: await RevokeTemporarilyHiddenModulesAsync(...) ← revoker
|
||||||
|
2347: await SeedProcurementMasterAccessAsync(...)
|
||||||
|
2354: await SeedKeHoachKyKetAccessAsync(...)
|
||||||
|
2361: await SeedContractCreateAccessAsync(...) ← MỚI, SAU revoker ✔
|
||||||
|
```
|
||||||
|
|
||||||
|
Và trong cửa sổ 2361→2520 **không còn call nào khác** ⇒ seeder mới là seeder-quyền CHẠY CUỐI,
|
||||||
|
không ai lật lại nó. ĐẠT.
|
||||||
|
|
||||||
|
Đọc thêm thân revoker `:2550-2589` để không chỉ tin thứ tự: filter là
|
||||||
|
`MenuKey.StartsWith("Hrm") || StartsWith("Off") || == Personal` — **nhánh `Contracts`/`Ct_*` đã
|
||||||
|
bị GỠ từ S159**. Vậy claim "revoker hiện không còn nhánh Contracts" là ĐÚNG, và việc giữ thứ tự
|
||||||
|
sau revoker là phòng-thủ dư (đúng chiều, không hại).
|
||||||
|
|
||||||
|
### 2b. UPGRADE-ONLY + IDEMPOTENT — ĐẠT
|
||||||
|
|
||||||
|
- Nhánh row-đã-có (`:2790-2797`): chỉ `if (!row.CanRead) row.CanRead = true` và
|
||||||
|
`if (!row.CanCreate) row.CanCreate = true`. **Không chạm `CanUpdate`/`CanDelete`, không hạ cờ
|
||||||
|
nào.** Không có `= false` ở bất kỳ đâu trong hàm.
|
||||||
|
- Nhánh row-chưa-có (`:2800-2808`): tạo `CanRead=true, CanCreate=true, CanUpdate=false,
|
||||||
|
CanDelete=false`.
|
||||||
|
- Idempotent: lượt 2 trở đi `added=0, upgraded=0` ⇒ **không gọi `SaveChangesAsync`** (`:2811`),
|
||||||
|
không log rác. ĐẠT.
|
||||||
|
- Guard role-chưa-seed (`:2779-2784`): `roles.Count == 0` → log warning + `return`, không ném.
|
||||||
|
|
||||||
|
### 2a/2d. TIỀN ĐỀ "chưa role nào có Contracts.Create" — ĐO ĐƯỢC TỪ MÃ
|
||||||
|
|
||||||
|
`DbInitializer.cs:2474` `reviewKeys = MenuKeys.All.Where(InReviewScope)…` ⇒ `Contracts` nằm
|
||||||
|
trong tập. `IsPeKey("Contracts") == false` ⇒ nhánh insert dùng `CanCreate = isPe` (`:2523`) =
|
||||||
|
**false**, và nhánh row-đã-có là **skip-existing** cho key non-Pe (`:2515`) ⇒ không ai nâng
|
||||||
|
`CanCreate` lên. Tiền đề của lane ĐÚNG.
|
||||||
|
|
||||||
|
Bộ vai: `roleNames = { Drafter, Procurement, Admin }` (`:2775`) — đúng 3 vai spec nêu, kèm comment
|
||||||
|
"câu III default, chờ anh ratify" và chỉ-dẫn sửa 1 chỗ. ĐẠT.
|
||||||
|
|
||||||
|
**MINOR F-2** — Admin thừa: `MenuPermissionHandler.cs:26-31` có Admin-bypass (`Succeed` ngay khi
|
||||||
|
role chứa `Admin`) ⇒ cấp row cho Admin **vô hại nhưng vô tác dụng**. Không đề nghị sửa; nêu để
|
||||||
|
người sau đừng đọc row Admin thành "bằng chứng Admin cần row".
|
||||||
|
|
||||||
|
**MINOR F-3** — comment `:2814` viết "Thu hồi khi cần: xoá call ở SeedAsync", nhưng call thật
|
||||||
|
nằm trong `SeedAdminPermissionsAsync` (thân 2288-2381), không phải `SeedAsync`. Chỉ-dẫn trỏ sai
|
||||||
|
tên hàm.
|
||||||
|
|
||||||
|
**MINOR F-4 (mở rộng ngoài Create)** — hàm nâng cả `CanRead`, trong khi tên hàm + comment chỉ hứa
|
||||||
|
`Contracts.Create`. Với 3 vai này `CanRead` gần như đã true từ S159 nên thực tế không đổi gì;
|
||||||
|
nhưng `reviewKeys` có ghi rõ "trên prod row false KHÔNG được nâng ở đây" ⇒ nếu prod có role nào
|
||||||
|
đang `Contracts.CanRead=false` thì hàm này **âm thầm mở lại quyền XEM**, rộng hơn nhãn.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## §3 — TRỌNG TÂM 3: chuỗi dữ liệu cây, khớp từng khớp nối
|
||||||
|
|
||||||
|
### 3a. Write-site → DTO (BE)
|
||||||
|
|
||||||
|
- Write-site: `CreateContractFromSigningPlanFeatures.cs:238` → `foreach (var l in lines) l.ContractId = contract.Id;`
|
||||||
|
Đây là gán THẬT trên entity đã tracked (`plan.Lines` nạp qua `Include` `:93`), nằm TRƯỚC
|
||||||
|
`SaveChangesAsync` `:248`. ĐẠT.
|
||||||
|
- Fallback: `:246` `if (pe is not null) pe.ContractId ??= contract.Id;` — `??=` đúng chiều
|
||||||
|
(HĐ đầu thắng, không cướp ô của cầu PE cũ).
|
||||||
|
- DTO list: `ContractSigningPlanFeatures.cs:75` `List<Guid> ContractIds` (append CUỐI record ⇒
|
||||||
|
positional-arg của các call-site cũ không lệch).
|
||||||
|
- Projection: `ContractSigningPlanFeatures.cs:836-842` — subquery
|
||||||
|
`Where(l => l.ContractSigningPlanId == p.Id && l.ContractId != null).OrderBy(CreatedAt)
|
||||||
|
.Select(l => l.ContractId!.Value).ToList()`. Filter `!= null` đứng TRƯỚC `.Value` ⇒ không NRE,
|
||||||
|
và line chưa bridge thì mảng RỖNG (không phải null-element). ĐẠT.
|
||||||
|
|
||||||
|
### 3b. DTO → FE (tên + null-safety)
|
||||||
|
|
||||||
|
- `types/khkk.ts:200` → `contractIds?: string[] | null`. JSON camelCase khớp `ContractIds`.
|
||||||
|
Khai optional dù BE non-null: đúng, vì cache React Query cũ / API chưa deploy không có field.
|
||||||
|
- Helper `types/khkk.ts:304-313` `khkkPlanContractIds(plan)`:
|
||||||
|
`for (const id of plan.contractIds ?? []) if (id) out.push(id)` +
|
||||||
|
`for (const line of plan.lines ?? []) if (line.contractId) out.push(line.contractId)`.
|
||||||
|
**Ca "line không chọn ⇒ ContractId null ⇒ mảng rỗng, không crash" = ĐẠT** — cả 2 vòng đều
|
||||||
|
`?? []` và đều lọc falsy trước khi push.
|
||||||
|
- Chữ ký `plan: KhkkListItemDto & Partial<Pick<KhkkDetailDto,'lines'>>` ⇒ truyền
|
||||||
|
`KhkkListItemDto` thuần vẫn hợp lệ (lines optional). ĐẠT.
|
||||||
|
- Union + dedupe: `usePipelineStages.ts:295-300` — `contractIdsOfPackage` là `Set<string>`, đổ
|
||||||
|
nguồn (1) `p.contractId` rồi nguồn (2) `khkkPlanContractIds(k)`. Chồng nhau ở HĐ đầu là CỐ Ý và
|
||||||
|
`Set` khử. ĐẠT.
|
||||||
|
- Dòng phụ: `usePipelineStages.ts:321-326` — `fallbackName` nay tra CẢ 2 nguồn
|
||||||
|
(`peItems.find(...)?.tenGoiThau ?? khkkItems.find(k => khkkPlanContractIds(k).includes(id))?.peTenGoiThau ?? null`).
|
||||||
|
Đo field tồn tại: `types/khkk.ts:171 peTenGoiThau: string | null`, BE `…Features.cs:39
|
||||||
|
string? PeTenGoiThau`. Khớp. ĐẠT.
|
||||||
|
|
||||||
|
### 3c. Mirror 2 app — TỰ ĐO SHA, KHÔNG TIN CLAIM
|
||||||
|
|
||||||
|
```
|
||||||
|
IDENTICAL src/types/khkk.ts 60ec8fa3a5aea94456e2180b1b80f63c1994257c55a424f4e1f7adf376cbbc07
|
||||||
|
IDENTICAL src/hooks/usePipelineStages.ts 76e1823345d4e78b87e802bc10d22ea0a42307bbb426b8787eb2d25b02e8c52f
|
||||||
|
IDENTICAL src/pages/khkk/KhkkDetailPage.tsx cf0a2272061f4bd8cc19c54b1a4992fdac331e9093665ec4ce708e119ef38123
|
||||||
|
```
|
||||||
|
|
||||||
|
3/3 byte-identical — claim của lane TÁI LẬP ĐƯỢC.
|
||||||
|
|
||||||
|
**Rủi ro riêng của mirror byte-identical (đã kiểm, KHÔNG thủng):** file dùng đường dẫn cứng
|
||||||
|
`/contracts/${id}` ở 3 chỗ (`:527` neo "✓ HĐ", `:975` neo "Đã có HĐ", `:855` `navigate`). Vì 2
|
||||||
|
app có bảng route KHÁC nhau, đây là chỗ dễ 404 một bên. Đo:
|
||||||
|
`fe-user/src/App.tsx:65` và `fe-admin/src/App.tsx:84` đều có `<Route path="/contracts/:id">`.
|
||||||
|
⇒ ĐẠT ở cả 2 app.
|
||||||
|
|
||||||
|
**MINOR F-5** — 2 neo dùng `<a href>` thay `<Link>` ⇒ full reload (mất SPA state); và neo
|
||||||
|
"Đã có HĐ" (`:975`) nằm LỒNG trong `<label>` (nested-interactive, cùng lớp a11y với finding K5 về
|
||||||
|
`<button>` trong `<summary>`). Radio của dòng đó `disabled` nên không sinh double-activate, nhưng
|
||||||
|
khuôn thì vẫn là interactive-lồng-interactive.
|
||||||
|
|
||||||
|
**MINOR F-6 (hiệu năng, không sai)** — `usePipelineStages.ts:324` gọi `khkkPlanContractIds(k)`
|
||||||
|
BÊN TRONG `find` mà `find` lại nằm trong vòng lặp theo `contractIdsOfPackage` ⇒ O(n·m) và cấp phát
|
||||||
|
mảng mới mỗi lượt. Quy mô hiện tại (list kẹp 200) vô hại.
|
||||||
|
|
||||||
|
### 3d. Chuông DEV — đọc kỹ vì dễ thành chuông-câm hoặc chuông-loạn
|
||||||
|
|
||||||
|
`usePipelineStages.ts:303-315`: chỉ chạy khi `import.meta.env.DEV`, có cờ module-level
|
||||||
|
`warnedMissingLineContractIds` (`:62`) chặn lặp; điều kiện
|
||||||
|
`approved.length > 0 && approved.every(k => k.contractIds == null)`.
|
||||||
|
`== null` bắt cả `null` lẫn `undefined` — ĐÚNG chủ đích "vắng-field ⟂ mảng-rỗng", vì sau khi BE
|
||||||
|
deploy field luôn là `[]` (non-null) ⇒ chuông tự im. Logic ĐẠT.
|
||||||
|
Lưu ý (không phải lỗi): prod strip `DEV` ⇒ đây **không phải lưới an toàn**, chỉ là đèn báo lúc dev.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## §4 — TRỌNG TÂM 4: guard 4-vế + idempotency
|
||||||
|
|
||||||
|
Đọc `CreateContractFromSigningPlanFeatures.cs` toàn file (252 dòng).
|
||||||
|
|
||||||
|
Rào theo thứ tự thực thi:
|
||||||
|
|
||||||
|
| # | Dòng | Nội dung | Nhận xét |
|
||||||
|
|---|---|---|---|
|
||||||
|
| 0 | :87-89 | `IsAuthenticated` + `UserId` | ném `UnauthorizedException` |
|
||||||
|
| 1 | :92-99 | phiếu tồn tại + `Phase == DaDuyet` | ĐẠT |
|
||||||
|
| 2 | :101-109 | dòng ∈ phiếu (đếm sau `Distinct`) | id lạ/của phiếu khác đều rơi vào đây — ĐẠT |
|
||||||
|
| 3 | :111-115 | 1 HĐ = 1 NCC | ĐẠT |
|
||||||
|
| 4 | :125-127 | `ApprovedAmount is null` → 409 | KHÔNG fallback `ProposedAmount` — ĐÚNG |
|
||||||
|
| 5 | :128-129 | `ContractId is not null` → "đã có Hợp đồng" | chống bridge-lặp |
|
||||||
|
| 6 | :134-148 | workflow pin **4 vế** | xem dưới |
|
||||||
|
|
||||||
|
**vá-3 (4 vế workflow) — ĐẠT, và ĐÚNG "throw tách":**
|
||||||
|
`:136` NotFound nếu không tồn tại · `:137-140` sai `ApplicableType` · `:143-145` `!IsActive` ném
|
||||||
|
riêng · `:146-148` `!IsUserSelectable` ném riêng. Bốn thông điệp bốn nguyên nhân — đúng yêu cầu
|
||||||
|
spec, và **rộng hơn khuôn tiền lệ** `ContractFeatures.cs:79-87` (khuôn cũ CHỈ có 2 vế đầu — tôi đã
|
||||||
|
đọc lại `ContractFeatures.cs:74-88` để xác nhận, claim của lane đúng).
|
||||||
|
|
||||||
|
**Idempotency / double-click — ĐÂY LÀ FINDING NẶNG NHẤT.** Rào (5) là kiểm tra
|
||||||
|
**đọc-rồi-ghi trong bộ nhớ**, không có khoá nào ở tầng CSDL:
|
||||||
|
|
||||||
|
- không `unique index` trên `ContractSigningPlanLines.ContractId`;
|
||||||
|
- không `RowVersion`/optimistic concurrency trên `Line`;
|
||||||
|
- không transaction bao ngoài (`SaveChangesAsync` trần `:248`; transaction duy nhất trong luồng
|
||||||
|
là transaction NỘI BỘ của `ContractCodeGenerator`, đã commit xong trước đó).
|
||||||
|
|
||||||
|
⇒ Hai request song song (double-click thật, hoặc 2 tab) đều đọc `l.ContractId == null` ở
|
||||||
|
`:128`, cùng đi tiếp, **tạo 2 Contract**, và lượt ghi sau đè `Line.ContractId`. Kết quả: 1 HĐ mồ
|
||||||
|
côi (đã tiêu 1 số sequence, đã có changelog) mà không dòng nào trỏ tới. Đây là **TOCTOU thật**,
|
||||||
|
không phải lo xa: nút "Tạo hợp đồng" chỉ chặn bằng `disabled={create.isPending}` (`:892`) — chặn
|
||||||
|
được double-click trong CÙNG tab, **không** chặn 2 tab / retry mạng.
|
||||||
|
|
||||||
|
Xếp hạng: **MAJOR F-1**, không phải blocker của gate (dữ liệu không mất, HĐ mồ côi xoá được, và
|
||||||
|
xác suất thấp vì thao tác có dialog), nhưng phải được KHAI, vì lane trình mục này như "đã đóng".
|
||||||
|
Acceptance đề nghị (không phải chỉ-dẫn sửa mã): một phép kiểm chứng minh 2 lệnh gọi đồng thời chỉ
|
||||||
|
sinh 1 HĐ — hoặc bằng ràng buộc CSDL, hoặc bằng cập-nhật-có-điều-kiện, hoặc khai công khai
|
||||||
|
"chấp nhận rủi ro, chặn ở UI".
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## §5 — TRỌNG TÂM 5: vá-6
|
||||||
|
|
||||||
|
- **Project/Supplier NotFound guard:** `:153-154` (`Project`) và `:156-157` (`Supplier`), cả 2 ném
|
||||||
|
`NotFoundException` trước khi dùng `.Code`. ĐẠT — loose-Guid không có FK đỡ lưng nên guard này
|
||||||
|
là bắt buộc.
|
||||||
|
- **Codegen TRƯỚC `Add`:** `:212` `contract.MaHopDong = await codeGenerator.GenerateAsync(contract,
|
||||||
|
project.Code, supplier.Code, ct);` rồi `:214` `db.Contracts.Add(contract);`. Đúng thứ tự bài S88
|
||||||
|
(codegen tự-commit ⇒ không được để HĐ dở trong context). Chữ ký khớp
|
||||||
|
`IContractWorkflowService.cs:29 Task<string> GenerateAsync(Contract, string, string, CancellationToken)`.
|
||||||
|
ĐẠT.
|
||||||
|
- **SlaDeadline:** `:205-206` `DateTime.UtcNow.Add(workflow.GetPhaseSla(DangSoanThao) ?? TimeSpan.FromDays(7))`
|
||||||
|
— trùng từng chữ với 2 tiền lệ (`ContractFeatures.cs:110`, bridge PE `:109-110`). ĐẠT.
|
||||||
|
- **`WorkflowDefinitionId` pin:** `:163-166` truy `WorkflowDefinitions` active theo
|
||||||
|
`request.ContractType` → `:201` gán. Ngang bridge PE cũ (đã đọc lại
|
||||||
|
`CreateContractFromEvaluationFeatures.cs:67-71` + field-map `:105-113` để đối chiếu). Set CẢ
|
||||||
|
V1 + V2 (`:202` `ApprovalWorkflowId`) là ĐÚNG — service dispatch V2 trước. ĐẠT.
|
||||||
|
|
||||||
|
**KHAI-1 của test-lane (invest T1 ghi null nhưng vá-6 thắng): XÁC NHẬN ĐÚNG — ĐÓNG, không mở lại.**
|
||||||
|
Lý do đo được: vá-6 trong spec ②:16 yêu cầu pin `WorkflowDefinitionId` theo 2 tiền lệ; mã hiện
|
||||||
|
tại làm đúng thế; "để null" là hành vi mà chính spec loại bỏ. Không có mâu thuẫn còn lại.
|
||||||
|
|
||||||
|
**MINOR F-7 (field-map thiếu so với tiền lệ, có thể CỐ Ý):** bridge PE cũ carry
|
||||||
|
`DraftData = pe.PaymentTerms` (`CreateContractFromEvaluationFeatures.cs:104`); cầu K7 **không**
|
||||||
|
carry `DraftData`, dù đã nạp `pe` sẵn ở `:170`. Cũng không có `BypassProcurementAndCCM` (cái này
|
||||||
|
hợp lý vì command K7 không nhận tham số đó). Nếu bỏ `DraftData` là chủ đích thì nên có một dòng
|
||||||
|
comment, vì mọi khoản lệch khác so với tiền lệ đều đã được chú thích.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## §6 — TRỌNG TÂM 6+7 (test có răng · comment sạch số-đếm)
|
||||||
|
|
||||||
|
### 6b. Comment mang số-đếm / mỏ-neo — CÓ LỖI, ĐO ĐƯỢC BẰNG 1 LỆNH
|
||||||
|
|
||||||
|
**MAJOR F-8 — mỏ-neo TỰ VÔ HIỆU ngay lúc land (đúng lớp S166-K4b, tái phát):**
|
||||||
|
2 comment MỚI trỏ `DbInitializer.cs:2516` cho `CanCreate = isPe`:
|
||||||
|
- `ContractSigningPlansController.cs:224` ("seeder `DbInitializer.cs:2516`")
|
||||||
|
- `DbInitializer.cs:2743` ("`SeedAllRolesReviewReadPermissionsAsync:2516`")
|
||||||
|
|
||||||
|
Phép đo (2 lệnh, cho 2 thế giới):
|
||||||
|
|
||||||
|
```
|
||||||
|
git show HEAD:…/DbInitializer.cs | grep -n "CanCreate = isPe" → 2516 (TRƯỚC diff)
|
||||||
|
grep -n "CanCreate = isPe" …/DbInitializer.cs → 2523 (SAU diff)
|
||||||
|
```
|
||||||
|
|
||||||
|
Chính diff này chèn 7 dòng ở `:2353-2359`, đẩy dòng đích xuống 2523. Nghĩa là con trỏ ĐÚNG lúc
|
||||||
|
viết và **SAI ngay khoảnh khắc commit** — không ai làm gì sai sau đó cả. Đây là lỗi loại "đo
|
||||||
|
trước khi chèn". Acceptance: hoặc cập nhật thành 2523, hoặc bỏ số dòng và trỏ bằng TÊN
|
||||||
|
(`SeedAllRolesReviewReadPermissionsAsync` + token `CanCreate = isPe` là duy nhất, grep ra ngay).
|
||||||
|
|
||||||
|
**MINOR F-9 — số-đếm trong comment tự mâu thuẫn:** `DbInitializer.cs:2743-2744` viết "nút bridge
|
||||||
|
sẽ 403 với **13/13 role** (kể cả Drafter), chỉ Admin qua được nhờ bypass". Đếm thật:
|
||||||
|
`AppRoles.cs:23-28` liệt **13** vai và Admin nằm trong đó; `MenuPermissionHandler.cs:26-31` cho
|
||||||
|
Admin bypass ⇒ con số đúng phải là **12/13**. Một câu chứa cả con số lẫn phản-ví-dụ của chính nó.
|
||||||
|
|
||||||
|
Ngoài 2 mục trên, đã quét các con số khác trong comment MỚI và chúng kiểm được:
|
||||||
|
`ContractConfiguration TenHopDong = 500` khớp `MaximumLength(500)` (`:73`) và clamp `:184`;
|
||||||
|
`ContractTypeLabel` 1..7 (`fe-user/src/types/forms.ts:13-21`) khớp ĐÚNG
|
||||||
|
`ContractType.cs:5-11` 1..7 ⇒ `IsInEnum()` không bao giờ đá dropdown.
|
||||||
|
|
||||||
|
### 6a. Ba file test — ĐỌC HẾT, CÓ RĂNG
|
||||||
|
|
||||||
|
**Đếm:** `KhkkBridgeAuthzTests` 5 `[Fact]` + `CreateContractFromSigningPlanTests` 12 `[Fact]`
|
||||||
|
= **17** — khớp đúng con số "17 test mới" spec nêu (đếm bằng `grep -c '^\s*\[Fact\]'`, không chép).
|
||||||
|
|
||||||
|
**`KhkkBridgeAuthzTests.cs` — răng thật, và đúng loại răng:**
|
||||||
|
- `:33-48` `BridgePolicies()` **đọc 2 tên policy TỪ CHÍNH CONTROLLER** bằng reflection, không
|
||||||
|
hardcode ⇒ ai sửa attribute thì test đo theo cái mới mà vẫn hỏi đúng câu. Kèm
|
||||||
|
`actionPolicy.Should().NotBe(classPolicy)` (`:45`) — chặn ca "2 vế trùng nhau thì hội-2-khoá
|
||||||
|
còn 1 khoá mà nhìn vẫn như cũ".
|
||||||
|
- Chạy **máy quyết định THẬT** `MenuPermissionHandler` (`:57-68`), không mô phỏng.
|
||||||
|
- (2) `:170-171` và (3) `:195-196` mỗi test đều assert **vế còn lại VẪN QUA** trước khi assert vế
|
||||||
|
thiếu bị chặn ⇒ không thể xanh nhờ "chặn tất cả". Đây đúng là thứ tôi định đòi.
|
||||||
|
- (5) `:235-243` phép **2 thế giới 1 biến**: cùng bộ quyền, chỉ lật `IsActive`, kết quả phải đảo.
|
||||||
|
- (4) `:214` assert `Permissions.Count == 0` trước khi kết luận Admin qua bằng bypass.
|
||||||
|
|
||||||
|
**`CreateContractFromSigningPlanTests.cs` — 12 ca, assert phân biệt được thế giới hỏng:**
|
||||||
|
- T1 `:243-245` — 3 con số CỐ Ý khác nhau (750 chốt / 999 đề xuất / 900 NS) ⇒ lấy nhầm nguồn nào
|
||||||
|
cũng ra số khác. T1 `:266` soi **RUỘT mã HĐ** `{ProjectCode}/HĐTP/SOL&{SupplierCode}/01` —
|
||||||
|
chính là bằng chứng vá-6 đã nạp 2 bảng Project/Supplier, mạnh hơn `NotBeNull`.
|
||||||
|
- T1 `:255-257` — assert pin **đúng bản V1 active của đúng loại**, và seed sẵn 1 bản NHIỄU loại
|
||||||
|
khác (`:197-201`) để `!= null` không đủ để xanh. Đây là cách đúng để đóng KHAI-1.
|
||||||
|
- T3 `:360` + T7 `:491` + T11 `:661-662` — assert **`ContractCodeSequences` không cháy**, tức đo
|
||||||
|
được thứ tự "rào đứng TRƯỚC codegen" bằng side-effect, không bằng đọc code.
|
||||||
|
- T6 `:436-440` và T7 `:496-498` — **đối chứng dương cùng lượt**, đổi đúng 1 biến thì QUA.
|
||||||
|
- T10 `:625-627` — đo **CHIỀU của `??=`**: viết `=` thì mọi assert khác vẫn xanh, chỉ vế này đỏ.
|
||||||
|
- T8 `:549-551` + `:555-562` — mối nối cầu → đường ống V2, đo bằng **hộp thư của đúng người**
|
||||||
|
(Cấp 1 thấy, Cấp 2 chưa thấy), không phải đọc cột.
|
||||||
|
|
||||||
|
**T12 vs vá-7 — ĐÚNG, đủ 3 vế spec đòi:** (a) phiếu TỰ đi tới `DaDuyet` qua service THẬT
|
||||||
|
(`:724-730`), không seed thẳng; (b) `ApprovedAmount` đông cứng `{250, 111}` với tương phản chứng
|
||||||
|
minh `??=` (dòng chốt tay giữ 111, **không bị 999 đè**) `:735-736`, và `GiaTri = 361` với lập luận
|
||||||
|
"Σ Proposed = 1249 ⇒ mọi cách sai đều ra số khác" `:744-745`; (c) sau bắc cầu, bấm duyệt lại →
|
||||||
|
`Conflict`, rồi đọc lại **cả `ContractId` lẫn `ApprovedAmount` đều không đổi** `:753-758`. ĐẠT.
|
||||||
|
|
||||||
|
**T11 (double-click) — CÓ RĂNG NHƯNG CHỈ CHO NỬA TUẦN TỰ.** Assert thật, 4 vế: Conflict + đúng
|
||||||
|
thông điệp + `Contracts.Count == 1` + `LastSeq == 1`. Nhưng `:652-655` là `await` lần 1 **rồi mới**
|
||||||
|
lần 2 (`ChangeTracker.Clear()` mô phỏng request thứ 2). Test này **không đo được và không thể đo**
|
||||||
|
ca ĐỒNG THỜI — tức F-1 vẫn hở. Bản thân comment test khai đúng phạm vi ("bấm 2 lần liên tiếp"),
|
||||||
|
nên đây KHÔNG phải lane nói quá; rủi ro là **người đọc sau tưởng "idempotency đã đóng"**.
|
||||||
|
|
||||||
|
**Ngoại lệ `CreateContract` trong `AuthorizePolicyRegressionTests` — SIẾT, không mở lỗ:**
|
||||||
|
- `:370-373` là `Dictionary<string,string>` map **1 action → 1 chuỗi policy CHÍNH XÁC**, dùng
|
||||||
|
`authz.Policy.Should().Be(expectedPolicy)` (`:419`) — so khớp tuyệt đối, **không** `StartsWith`
|
||||||
|
/ `Contains` / danh sách bỏ-qua.
|
||||||
|
- Action nào không có tên trong map vẫn rơi vào `else` đòi prefix `KeHoachKyKet` (`:428`) ⇒
|
||||||
|
**action tương lai KHÔNG được miễn trừ tự động**. Đúng yêu cầu "chỉ miễn đúng 1 action".
|
||||||
|
- `MenuKeys.All.Should().Contain(parts[0])` (`:434`) nằm NGOÀI `if/else` ⇒ vẫn chạy cho cả nhánh
|
||||||
|
ngoại lệ (chặn ca policy không tồn tại → 500).
|
||||||
|
- `:477-479` `KhkkCrossModuleGates.Keys.Should().BeSubsetOf(byName.Keys)` — chống **cấu-hình-chết**
|
||||||
|
(đổi tên/xoá action mà quên dọn map thì mục đó im lặng không ai đo). Đây là vế "vắng-mặt trông
|
||||||
|
giống ổn" mà tôi hay phải tự đòi; lane tự đặt sẵn.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## §7 — Trọng tài tươi (reviewer tự chạy, không nhận số của lane)
|
||||||
|
|
||||||
|
| Phép đo | Lệnh | Kết quả |
|
||||||
|
|---|---|---|
|
||||||
|
| Build BE | `dotnet build SolutionErp.slnx -v q` | **Build succeeded — 0 Warning, 0 Error** (62s) |
|
||||||
|
| 3 lớp test K7 | `dotnet test --filter …KhkkBridgeAuthzTests\|…CreateContractFromSigningPlanTests\|…AuthorizePolicyRegressionTests` | **39/39 passed, 0 failed** |
|
||||||
|
| Toàn suite | `dotnet test SolutionErp.slnx --no-build` | Domain **45/45** + Infrastructure **594/594** = **639 pass / 0 fail** |
|
||||||
|
| tsc fe-user | `npx tsc --noEmit` | exit 0, 0 dòng lỗi |
|
||||||
|
| tsc fe-admin | `npx tsc --noEmit` | exit 0, 0 dòng lỗi |
|
||||||
|
|
||||||
|
🔴 **Phép cộng khớp:** baseline trước đợt này là **622** (đo ở gate K4c cùng ngày) và diff thêm
|
||||||
|
**17** `[Fact]` ⇒ kỳ vọng 639. Đo được đúng **639**. Nghĩa là 17 test mới **có chạy thật** (không
|
||||||
|
bị filter/skip câm) **và** 0 test cũ bị vỡ. Đây là 1 con số đóng cả 2 câu hỏi.
|
||||||
|
|
||||||
|
### Prod probe — TIỀN ĐỀ SEEDER, ĐO ĐƯỢC, XÁC NHẬN
|
||||||
|
|
||||||
|
`GET /api/permissions/by-role/{id}` cho **cả 13 vai** trên `api.solutions.com.vn` (token admin;
|
||||||
|
lưu ý field token là `accessToken`, không phải `token` — sai tên là "login failed" giả):
|
||||||
|
|
||||||
|
```
|
||||||
|
roles on prod: 13
|
||||||
|
Accounting/AuthorizedSigner/CatalogManager/CostControl/DeptManager/Director/
|
||||||
|
Drafter/Equipment/Finance/HrAdmin/Procurement/ProjectManager → Contracts R=True C=False
|
||||||
|
Admin → Contracts R=True C=True
|
||||||
|
--- roles with Contracts.CanCreate = true: 1 / 13
|
||||||
|
```
|
||||||
|
|
||||||
|
Kết luận đo được (không suy từ code):
|
||||||
|
- Tiền đề của lane **ĐÚNG**: hôm nay **Drafter = False, Procurement = False** ⇒ nút bridge sẽ 403
|
||||||
|
cho họ nếu không có seeder. Lỗ là THẬT.
|
||||||
|
- **Sửa lại F-9 bằng số sống:** không phải "13/13 role", mà **12/13**; và Admin qua **không chỉ**
|
||||||
|
nhờ bypass — Admin **đã có sẵn row `CanCreate=True`** trên prod.
|
||||||
|
- **Củng cố F-2 bằng số sống:** vì Admin đã True, nhánh Admin của seeder mới là **no-op**.
|
||||||
|
- **Dự đoán kiểm chứng được sau deploy+restart:** seeder phải log đúng **`0 added + 2 upgraded`**
|
||||||
|
(Drafter, Procurement). Lệch con số này = có thứ khác đang lật quyền. Đây là acceptance rẻ nhất
|
||||||
|
cho nghiệm thu #75/#76 "restart THẬT rồi đếm row".
|
||||||
|
|
||||||
|
Giới hạn khai thật: prod **chưa deploy** diff này ⇒ phép đo trên chứng minh TIỀN ĐỀ, **không**
|
||||||
|
chứng minh seeder chạy đúng. Không đo được bằng con mắt người dùng thường vì
|
||||||
|
`nv.test@solutions.com.vn` đăng nhập thất bại (tài khoản không còn/đổi mật khẩu) — đã dùng đường
|
||||||
|
admin thay thế.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## §8 — Tổng hợp finding (CHỐT)
|
||||||
|
|
||||||
|
| # | Sev | Vị trí | Một câu |
|
||||||
|
|---|---|---|---|
|
||||||
|
| F-1 | MAJOR | `CreateContractFromSigningPlanFeatures.cs:128-129` + `:248` | Chống-trùng chỉ là kiểm-tra-trong-bộ-nhớ, không ràng buộc CSDL / không transaction bao ngoài ⇒ 2 request ĐỒNG THỜI tạo 2 HĐ, 1 cái mồ côi; T11 chỉ phủ ca tuần tự. |
|
||||||
|
| F-8 | MAJOR | `ContractSigningPlansController.cs:224` · `DbInitializer.cs:2743` · `KhkkBridgeAuthzTests.cs:153` | Mỏ-neo `DbInitializer.cs:2516` đúng lúc viết, **sai ngay lúc land** (dòng thật 2523 — chính diff này chèn 7 dòng ở `:2353-2359` đẩy nó xuống); **3 nơi**, không phải 2. |
|
||||||
|
| F-R1 | MINOR | `scripts/{distill-shard-probe,measure-agent-memory,memory-archive-gate}.ps1` | Dirty ngoài cả 13-file-set lẫn lead-set (việc rotate `_INDEX-<period>.md`); `git add -A` sẽ nuốt vào commit K7. |
|
||||||
|
| F-9 | MINOR | `DbInitializer.cs:2743-2744` | "403 với **13/13** role… chỉ Admin qua nhờ bypass" tự mâu thuẫn; đo prod: **12/13** có `CanCreate=False`, và Admin **đã có sẵn row True** (không chỉ bypass). |
|
||||||
|
| F-2 | MINOR | `DbInitializer.cs:2775` | Cấp row cho Admin vô hại nhưng **vô tác dụng** — có Admin-bypass (`MenuPermissionHandler.cs:26-31`) và prod đo được Admin đã `CanCreate=True`. |
|
||||||
|
| F-3 | MINOR | `DbInitializer.cs:2814` | "Thu hồi khi cần: xoá call ở SeedAsync" — call thật nằm trong `SeedAdminPermissionsAsync` (thân 2288-2381). |
|
||||||
|
| F-4 | MINOR | `DbInitializer.cs:2792` | Hàm nâng cả `CanRead` trong khi tên + comment chỉ hứa `Contracts.Create` (rộng hơn nhãn). |
|
||||||
|
| F-5 | MINOR | `KhkkDetailPage.tsx:527` · `:975` | `<a href>` thay `<Link>` (full reload, mất state SPA) + neo "Đã có HĐ" lồng trong `<label>` (interactive-lồng-interactive, cùng lớp a11y với finding K5). |
|
||||||
|
| F-6 | MINOR | `usePipelineStages.ts:324` | `khkkPlanContractIds` gọi trong `find` trong vòng lặp — O(n·m) + cấp phát mảng mỗi lượt; vô hại ở list kẹp 200. |
|
||||||
|
| F-7 | MINOR | `CreateContractFromSigningPlanFeatures.cs:186-207` | Không carry `DraftData` như bridge PE tiền lệ (`CreateContractFromEvaluationFeatures.cs:104`) dù `pe` đã nạp sẵn, và không có dòng nào nói đó là chủ đích. |
|
||||||
|
| CLARIFY-1 | — | `usePermission.ts` + `AuthContext.loadMenu` | Sau khi seeder chạy, nút vẫn xám cho tới khi user **đăng-xuất/đăng-nhập lại** (menu cache `localStorage`) — UAT phải nêu, không thì bị đọc thành "seeder không ăn". |
|
||||||
|
|
||||||
|
**Không có blocker.** F-1 và F-8 phải xử lý nhưng không chặn commit: F-1 xác suất thấp + không mất
|
||||||
|
dữ liệu (HĐ mồ côi xoá được), F-8 là con trỏ trong chú thích.
|
||||||
|
|
||||||
|
**ĐẠT (đo xong, không có finding):** hội-2-khoá đủ 2 tầng và FE không lỏng hơn server (0 đường
|
||||||
|
gọi API lách gate) · thứ tự seeder SAU revoker (2341 → 2361) và là seeder-quyền chạy CUỐI ·
|
||||||
|
upgrade-only + idempotent (0 chỗ hạ cờ, lượt 2 không `SaveChanges`) · chuỗi
|
||||||
|
`Line.ContractId` → `ContractIds` → `khkkPlanContractIds` → `Set` union null-safe từng khớp ·
|
||||||
|
mirror 3/3 byte-identical (SHA tự đo) · route `/contracts/:id` tồn tại ở CẢ 2 app · guard 4 vế
|
||||||
|
workflow ném tách từng nguyên nhân · vá-6 đủ 4 khoản (2 NotFound guard · codegen trước `Add` ·
|
||||||
|
`SlaDeadline` · pin `WorkflowDefinitionId`) · 17 test mới có răng thật · ngoại lệ regression siết
|
||||||
|
đúng 1 action + chống cấu-hình-chết · build 0/0 · 639/0 test · tsc sạch 2 app.
|
||||||
|
|
||||||
|
**KHAI-1 (test-lane: invest T1 ghi `WorkflowDefinitionId` null nhưng vá-6 thắng): XÁC NHẬN ĐÚNG —
|
||||||
|
ĐÓNG, không mở lại.** Spec `②:4` chốt "16 vá THẮNG khi xung đột với nền"; mã theo vá-6 (`:163-166`
|
||||||
|
+ `:201`); và T1 `:255-257` assert đúng bản active + có bản nhiễu để chống xanh-giả.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## §9 — Đề nghị hành động (mô tả tiêu chí, KHÔNG chỉ-dẫn sửa mã)
|
||||||
|
|
||||||
|
1. **F-8** — bỏ số dòng, trỏ bằng TÊN (`SeedAllRolesReviewReadPermissionsAsync` + token
|
||||||
|
`CanCreate = isPe` là duy nhất trong file, grep ra ngay), hoặc cập nhật cả **3** nơi thành 2523.
|
||||||
|
Tiêu chí nghiệm thu: sau khi vá, `grep -n "CanCreate = isPe"` trên cây ĐÃ COMMIT phải khớp con
|
||||||
|
số ghi trong chú thích.
|
||||||
|
2. **F-1** — hoặc dựng một phép kiểm chứng minh 2 lệnh gọi đồng thời chỉ sinh 1 HĐ, hoặc khai công
|
||||||
|
khai "chấp nhận rủi ro, chỉ chặn ở UI" ngay tại `:128-129` để người sau không đọc T11 thành
|
||||||
|
"đã đóng".
|
||||||
|
3. **F-9 / F-2** — sửa "13/13" thành "12/13" và bỏ vế "chỉ Admin qua nhờ bypass" (prod đo được
|
||||||
|
Admin có row thật).
|
||||||
|
4. **F-R1** — tách 3 file `scripts/*.ps1` khỏi commit K7, hoặc khai chúng vào lead-set trước khi
|
||||||
|
`git add`.
|
||||||
|
5. **Nghiệm thu sau deploy** — restart THẬT rồi soi log seeder: kỳ vọng **`0 added + 2 upgraded`**;
|
||||||
|
và UAT phải có bước đăng-xuất/đăng-nhập lại (CLARIFY-1).
|
||||||
|
|
||||||
|
END gate-k7 — VERDICT=PASS-WITH-FLAGS 10
|
||||||
Reference in New Issue
Block a user