K8-DRYRUN: ĐANG CHẠY — B0 PASS (backup 26,6MB + roster LIVE + login admin OK); B1.. đổ ruột liên tục bên dưới

# sub-k8-dryrun — K8 dry-run E2E toàn trình (S168, 2026-08-01)

> 🔴 **TÁC GIẢ = LEAD (em-main), KHÔNG phải sub-agent** — tiền tố `sub-` ở đây là **sai quy-ước tên**, giữ nguyên tên để con-trỏ đã phát ra (WAL · STATUS · session-log · commit `b41f002`/`6918fa9`) không thối. K8 chạy **lead-solo bằng script** (`k8run.py`/`k8fx.py` ở scratchpad), 0 spawn ⇒ **không có spawn-record** cho lượt này là ĐÚNG, không phải silent-miss. *(Khai theo FID-1 của `harvest-curator` @close S168 — vai từ chối tự phán tác-giả từ đĩa, đúng luật.)*

> Mandate owner @S168 (verbatim): *"Chạy dry-run hết luôn đi rồi tao giao tiếp plan khác"*.
> Kịch bản = spec-cụm3 §③ K8 + bảng B0-B16 (`sub-invest-fable-b2-cum3.md` K8.1) + **vá-9..15 spec-final THẮNG nền**.
> 🔴 Vá-12 owner-ratified @S164 *"tạo PE Test SOLUTION-PE-1,2,3… không test trên phiếu thật"* ⇒ **bộ test RIÊNG** (Project+NCC+gói ZZTEST/SOLUTION-PE-{n}), **KHÔNG đụng `PE/2026/A/049`** — câu giờ-G K8.2 của bản nền coi như ĐÃ trả.
> Người bấm mọi bước hôm nay = **admin (duyệt thay — SignedByUserId track, banner "Admin duyệt thay" by-design)**; cột "Ai (thật)" trong form = roster LIVE để buổi chạy với người thật dùng lại.

## B0 — PRE-FLIGHT (fail-closed) — ✅ PASS 16:2x-16:3x +07

- (i) **Backup prod:** `C:\Backups\SolutionErp\SolutionErp_preK8_S168.bak` — 27.877.888 B @ 2026-08-01 16:28 (BACKUP DATABASE 3393 pages OK). *(Script `scripts/backup-sql.ps1` default `D:\` — VPS KHÔNG có ổ D ⇒ dùng `C:\Backups`; default ĐÃ VÁ `C:\` @S168 cùng lượt.)*
- (ii) **Workflow LIVE (sqlcmd -E; ⚠️ `$env:PROD_DB_PASSWORD` KHÔNG tồn tại cả 3 scope trên VPS — persona-note stale cùng lớp F-9):**
  - type-1 PE: `QT-DN-V2-001` active+selectable · 3 trạm (P1: 4NV L1 + tra.bui L2[SkipToFinal,Delete] · P2: 4NV L1 + **chuong.phan L2 [Finalize=1,SkipToFinal,Delete]** · P3: truong.nguyen [SkipToFinal]).
  - type-10 KHKK: **đủ 8 nhóm N1-N8 active**; `KHKK-N4` 3 trạm 11 người (Cung ứng: 4NV+tra.bui · Kiểm soát CP: 4NV+**chuong.phan [Finalize=1]** · BGĐ: truong.nguyen) — khớp anchor OG-3/OG-9.
  - type-3 HĐ: `QT-HD-V2-001` active — **1 trạm 1 người: binh.le (Bước 1 - Phòng CCM)**.
- (ii-b) Catalog: `ContractCatalogEntries` = **86** dòng; nhóm-4 mẫu B1-01 Đá · B1-02 Cát · B1-03 Cọc tràm · B1-04 Bê tông · **B1-05 Xi măng (chọn cho line test)** · B1-06 Thép.
- (iii) Dữ liệu test sẵn có: Projects ZZTEST = **0** · Suppliers TEST = **0** ⇒ TẠO MỚI qua API (GĐ0 dưới). Contracts prod = **0** (HĐ dry-run sẽ là HĐ ĐẦU TIÊN trên prod — seq cháy thuộc cặp prefix TEST, vô hại data thật).
- (iv) **Dặn team:** em KHÔNG có kênh Zalo/email — thay bằng ZZTEST-prefix mọi bản ghi + mục này để anh nhắn team *"notification phiếu ZZTEST trong hôm nay — bỏ qua"*. KHAI trade-off, không im.
- (v) Login API prod: `POST /api/auth/login` admin OK (accessToken 468c — field `accessToken` đúng bài S160).

## B0-FORM 14 dòng {bước · người THẬT (roster LIVE) · tài khoản · ai bấm hôm nay} — vá-9 fail-closed

| B | Ai (thật, theo roster LIVE) | Tài khoản | Hôm nay |
|---|---|---|---|
| B1-B3 | BCH drafter (đại diện Cung ứng: Nguyễn Văn Duy) | duy.nguyen@solutions.com.vn | admin (tạo thay) |
| B4 | Trạm-1 PE/KHKK — 1 trong {duy.nguyen · binh.lethanh · long.chau · phuong.nguyen} + L2 tra.bui | (4+1 tài khoản trên) | admin duyệt thay |
| B5 | Trạm-2 — 1 trong {luu.tran · nguyen.ho · binh.le · truong.letrandang} | (4 tài khoản) | admin duyệt thay |
| B6 | **TP.CCM Phan Văn Chương (Finalize)** | chuong.phan@solutions.com.vn | admin duyệt thay (tick finalize) |
| B7 | CEO Nguyễn Văn Trường (nhánh B) | truong.nguyen@solutions.com.vn | admin duyệt thay |
| B8 | BCH/Admin kiểm khóa | — | admin |
| B9 | Người có `Contracts.Create` (câu III default: Drafter/Procurement/Admin — seeder K7 CanCreate=3) | — | admin |
| B10 | Người bấm B9 | — | admin |
| B11 | Approver HĐ: **Lê Văn Bình** (QT-HD-V2-001 trạm duy nhất) | binh.le@solutions.com.vn | admin duyệt thay |
| B12 | HRA/CCM (HardCopyActorRoles) | — | admin |
| B13 | Bất kỳ + Admin (mở cây) | — | admin |
| B14 | Lead chốt sổ | — | lead |

⇒ 14/14 dòng CÓ CHỦ — fail-closed ĐẠT (không dòng nào trống).

## Nhật ký bước (đổ ruột liên tục — ID ghi TẠI CHỖ cho rollback vá-11/13)

### 🔴 PHÁT HIỆN TRƯỚC GIỜ CHẠY — BLOCKER toàn trình: Lines-editor KHÔNG TỒN TẠI (vá tại chỗ @S168)

Truy vết trước khi bấm B1 (đúng tinh thần "K8 lộ wave thiếu"):
- `ContractSigningPlanFeatures.cs` auto-gen line mồi `CatalogEntryId = null` + comment HẸN *"Người soạn gán sau qua `UpdateContractSigningPlanLineCommand`"* — **command đó chỉ tồn tại TRONG COMMENT** (grep toàn src + FE = 0 impl, 0 route, 0 call).
- `ContractSigningPlanWorkflowService.SubmitAsync` (vá-5) **CHẶN THẬT** line null: `"Còn dòng chưa gán hạng mục…"` + đếm-so-khớp nhóm.
- FE `KhkkDetailPage` cột Hạng mục tự khai *"Đường GÁN (Lines-editor) = K4b"* — nhưng K4b THẬT ship việc khác (`88e7ecf` = nối `?group=` sidebar, đo `git show --name-only`). ⇒ mảnh rơi giữa build-order.
- Hệ quả nếu không vá: **MỌI phiếu KHKK tạo sau K2 kẹt Nháp vĩnh viễn** — dry-run chết ở B3. Đây chính là chỗ cicd-K2 khai *"mục-4 chân-lý-rỗng — đo lại khi K8 đẻ phiếu thật"*.

**Vá @S168 (BE + FE ×2 app + 3 test):** `UpdateContractSigningPlanLineCommand(PlanId, LineId, CatalogEntryId)` handler (EnsureDraftEditable + fail-fast entry sống/đúng-nhóm 409 + denorm `TenHangMuc` + changelog Line) · route `PUT /contract-signing-plans/{id}/lines/{lineId}` policy `KeHoachKyKet.Update` · FE select gán tại bảng (Nháp/TraLai, khoá dòng đã bắc cầu HĐ, options lọc `?approvalGroup=`) mirror SHA-pair `21e0ca0507d4` ×2 app · 3 Fact (dương gán→submit-QUA · âm khác-nhóm 409 · âm phase). Gate + commit riêng trước khi chạy chuỗi.

### GĐ0 — dựng bộ test — ✅ PASS (script `k8run.py gd0`, state `k8-state.json` scratchpad)

| Vật | ID |
|---|---|
| Project `ZZTEST-K8` "ZZTEST Du an dry-run K8 (SOLUTION-PE)" | `9c6caa7a-22b9-4482-bc74-2ef36fbebc54` |
| Supplier `ZZTEST-NCC-K8` | `10b60f58-1bb0-4164-b191-3cb9af3896fe` |
| WorkItem `ZZTEST-WI-K8` | `a29c7932-411c-4763-9ca6-5546798c3d5c` |
| Catalog line-test | B1-05 Xi măng `69a6db3a-30b1-4289-878b-a364a1fd1eab` |
| Workflow ids | PE `8a0aa922…` · KHKK-N4 `bf90b962…` · HĐ `12aee3bf…` |

### GĐ1 — PE 2 nhánh — ✅ TRỌN (B1-B7 phần PE)

- **PE-1 nhánh A = `PE/2026/A/052`** (`4e4ad413-30cb-4eda-b5cf-31f2936e8ec2`; supplier-row `512f6b41` · detail `55285053` · quote 110tr-VAT isSelected · Bảng-so-sánh att `50bbcf46`): submit → 3 lượt trạm → lượt 4 **giá-chốt NCC 110tr + finalize tại CCM (chuong.phan)** ⇒ **DaDuyet KHÔNG qua CEO** (OG-9 nhánh A chứng ✓) → winner ZZTEST-NCC ✓.
- **PE-2 nhánh B = `PE/2026/A/053`** (`a53db8b9-df60-42d0-92c0-e2ca4c80f2e5`; row `9e3d8060` · detail `1f98f925` · att `4a79abfd`): submit → **5 lượt ĐỦ đường** với `applyLevelFinalize=false` ở mọi lượt (checkbox opt-out F-1 `7a903cf` chứng chạy thật) ⇒ qua **CEO Trường** → DaDuyet → winner ✓.
- Sự-cố script tự bắt + sửa dọc đường (đều là THUỐC THỬ tốt cho form B0 buổi người-thật): ① PE detail đòi `GroupCode/GroupName` required · ② `QuoteBody.BgVat/ChuaVat` = decimal non-null · ③ submit-guard đòi đính kèm **Bảng so sánh** (purpose=4) · ④ cấp cuối đòi **chọn giá chốt** (Ncc/ProMin/ProMax/Ccm/ProMinMax — Mig 66) · ⑤ auth rate-limit **429** khi login mỗi-stage ⇒ cache token (JWT 1h) vào state · ⑥ 1 phiếu nháp orphan (script fail giữa) ⇒ **DELETE 204 dọn liền** — allow-list Nháp xoá được, đúng thiết kế.

### GĐ2+ (KHKK → bridge → HĐ → bản-cứng → QĐ9) — CHỜ deploy line-editor (commit + push + cicd) rồi chạy stages `khkk1…qd9-2`

### GĐ2→GĐ4 + QĐ9 — ✅ TRỌN CẢ 2 NHÁNH (sau deploy #444 line-editor live)

| Bước | Nhánh A | Nhánh B |
|---|---|---|
| KHKK tạo + **gán hạng mục qua endpoint MỚI** | `KHKK/2026/001` (`9cc64a5e`), line `776faa92` ← B1-05 ✓ | `KHKK/2026/002` (`3b11f7d3`), line `0a9e67a0` ✓ |
| Duyệt | **4 lượt — finalize tại Chương (KHÔNG qua BGĐ)** ✓ OG-9 | **5 lượt — QUA ĐỦ BGĐ/CEO** (`applyLevelFinalize=false` mọi lượt — checkbox opt-out F-1 sống) ✓ |
| DaDuyet | phase=3, approvedTổng **110.000.000** chốt tại choke-point | phase=3, approvedTổng 110.000.000 |
| Bridge K7 → HĐ | HĐ `47b453d3` — mã RG-001 tự sinh **`ZZTEST-K8/NCC/SOL&ZZTEST-NCC-K8/01`**, `giaTri == ApprovedAmount` = 110tr ✓ | (không bridge — spec chỉ cần 1 nhánh) |
| Duyệt HĐ | submit → 1 lượt (binh.le trạm duy nhất) → **DaPhatHanh (9)** — HĐ ĐẦU TIÊN trên prod, là ZZTEST đúng chủ đích | — |
| GĐ4 bản cứng | upload SealedCopy purpose=3 `78be7327` → **LIST `hasSealedCopy=True`** ✓ (field là EXISTS tầng LIST — detail đọc `attachments[]`) | — |
| B13 mối nối cây | `khkk.line.contractId → 47b453d3` ✓ · `pe.contractId → 47b453d3` (forward-provision K5) ✓ · `hasSealedCopy` ✓ — đúng 3 nguồn `usePipelineStages:293-299` đọc | line.contractId = null (đúng — chưa bridge) |
| **QĐ9 freeze (đo 2 vế/nhánh)** | (a) sửa quote nguồn PE DaDuyet → **409 "không thể chỉnh sửa Section 2"** = nguồn khoá từ tầng dưới · (b) catalog-rename `Xi măng→Xi măng (ZZTEST QD9)` 204 → **line GIỮ 'Xi măng'** (denorm thắng join-ngược — bit TRƯỢT-ĐƯỢC thật) → restore 204 nguyên trạng | (a) 409 y hệt · (b) rename→GIỮ→restore ✓ |

🔸 Khai đo: **B13 đo ở tầng API-mối-nối** (3 con trỏ cây đọc), KHÔNG chụp UI — dry-run kỹ thuật admin-duyệt-thay; buổi người-thật theo form B0 sẽ chụp `ss-k8-*` + bấm trên UI thật. QĐ9 nguyên bản ("đổi nguồn → số không đổi") nâng thành bằng chứng MẠNH HƠN ở vế (a): cửa đổi nguồn bị khoá hẳn 409.

### B15 — SỔ BẢN-GHI-VĨNH-VIỄN + seq cháy (rollback theo ID, vá-11/13)

- **BẤT TỬ by-design (sống chung, nhận diện ZZTEST/SOLUTION-PE):** PE `PE/2026/A/052` + `PE/2026/A/053` (DaDuyet) · KHKK `KHKK/2026/001` + `/002` (DaDuyet — HỐ-1-KHKK: chiếm slot (PE,N4); PE là test nên vô hại) · HĐ `ZZTEST-K8/NCC/SOL&ZZTEST-NCC-K8/01` DaPhatHanh (HỐ-1 numeric-guard chặn xóa; muốn dọn = sqlcmd soft-delete CHỜ ANH GẬT — lệnh: `UPDATE Contracts SET IsDeleted=1 WHERE Id='47b453d3-…'` + NULL lại `Line.ContractId`/`pe.ContractId` nếu gỡ).
- 🔴 **Seq cháy KHÔNG thu hồi:** `KHKK/2026` seq ăn **001+002** (global-per-year — phiếu THẬT đầu tiên sẽ là 003, anh biết trước kẻo thắc mắc) · PE `2026/A` seq ăn 052+053 · mã HĐ seq 01 thuộc CẶP PREFIX TEST (vô hại prefix thật).
- Master test GIỮ làm bộ tái dùng (đúng tinh thần SOLUTION-PE-{n} owner ratify): Project `ZZTEST-K8` · Supplier `ZZTEST-NCC-K8` · WorkItem `ZZTEST-WI-K8`.
- Đã dọn trong-phiên: phiếu nháp orphan `0430d99f` DELETE 204 · catalog B1-05 restore nguyên trạng ×2 lần đo.
- Notification đã bắn tới roster thật trong lúc duyệt-thay — mọi tên mang ZZTEST; **anh nhắn team "phiếu ZZTEST hôm nay bỏ qua"** (B0.iv khai: em không có kênh Zalo/email).
- B16 rollback: KHÔNG kích hoạt (0 lần hỏng-phải-dừng).

END sub-k8-dryrun — VERDICT=**K8 DRY-RUN PASS TOÀN TRÌNH 2 NHÁNH** (GĐ1 PE 2 phiếu · GĐ2 KHKK 2 phiếu qua endpoint line MỚI · K7-bridge → GĐ3 HĐ DaPhatHanh · GĐ4 bản-cứng · QĐ9 4/4 vế · B15 sổ-ID đủ) — 1 blocker THẬT phát hiện + vá + ship trong phiên (Lines-editor) · 8 thuốc-thử guard ghi lại cho buổi người-thật

---

## BỘ FIXTURE TEST (owner lệnh @S168 sau dry-run: "để là ADMIN_TEST… tạo luôn master data test… 1 bộ User test… chữ test trước… mỗi bộ 2-3 thôi")

🔴 **LUẬT chốt từ lệnh này: mọi test từ nay chạy trên FIXTURE TEST — CẤM đụng master data / user thật.** (Dry-run K8 đã theo đúng — bộ ZZTEST.)

**Users (password chung `Test@12345678` — policy prod đòi ≥12 ký tự; đổi sau buổi test nếu anh muốn):**
| FullName | Email | Roles | Id |
|---|---|---|---|
| **ADMIN_TEST** | admin_test@solutions.com.vn | Admin | `f0151cc7-32ad-4ec0-5da7-08deefbe0ee9` |
| TEST Nhân viên Soạn thảo | test.drafter@solutions.com.vn | Drafter + Procurement | `79de0ef1-4658-41cd-5da8-08deefbe0ee9` |
| TEST Người duyệt | test.approver@solutions.com.vn | CostControl + DeptManager | `f4d02ccb-7466-4d7e-5da9-08deefbe0ee9` |

Verify: login ADMIN_TEST OK (token 471c) · 3/3 hiện `GET /users?search=TEST`. ⚠️ 2 user TEST muốn **DUYỆT thật** thì phải được gắn vào workflow qua **Designer — ANH TỰ TAY** ((54)-FINAL, em không đụng workflow); role hiện tại đủ THẤY màn.

**Master data test (cộng bộ ZZTEST sẵn có từ GĐ0):**
| Bộ | Thành viên (2-3/bộ) |
|---|---|
| Department (1) | TEST Phòng thử nghiệm `ZZTEST-PB` — `53044453…` (3 user TEST gắn vào đây) |
| Projects (2) | `ZZTEST-K8` (GĐ0) · `ZZTEST-K8-2` "TEST Dự án thử nghiệm 2" — `b1a77d48…` |
| Suppliers (3) | `ZZTEST-NCC-K8` (NCC, GĐ0) · `ZZTEST-NTP-K8` (Thầu phụ) — `3c41bd4c…` · `ZZTEST-TD-K8` (Tổ đội) — `517f62c9…` |
| WorkItems (2) | `ZZTEST-WI-K8` (GĐ0) · `ZZTEST-WI-K8-2` — `54ced5c2…` |

Không bơm test vào `ContractCatalogEntries` (danh mục SP-002 chuẩn 86 dòng dùng chung — QĐ9 đã đo bằng rename+restore, không để vết).

END-FIXTURE — 1 dept + 3 users + 3 suppliers + 2 projects + 2 workitems, tất cả prefix TEST/ZZTEST, verify sống 2 phép

## BỘ DỮ LIỆU TEST TOÀN TRÌNH fx3 (owner lệnh @S168: "tạo 1 bộ dữ liệu test từ PE -> Hợp đồng luôn") — ✅ TRỌN, danh tính TEST 100%

Khác dry-run trước: **soạn = `test.drafter` · duyệt-thay = `ADMIN_TEST`** (0 lượt nào dùng admin thật) · master data = bộ fixture (`ZZTEST-K8-2` + TEST Phòng thử nghiệm + WI-2).

| GĐ | Vật | Người bấm |
|---|---|---|
| GĐ1 | **`PE/2026/A/054`** `3dcbc65d` — "TEST SOLUTION-PE-3 — Bộ dữ liệu toàn trình PE→HĐ" · quote 110tr · Bảng-so-sánh `23261bcb` · DaDuyet finalize-CCM + winner | tạo+submit: test.drafter · duyệt ×4 + winner: ADMIN_TEST |
| GĐ2 | **`KHKK/2026/003`** `6ed7c100` — line `f07efb1e` gán B1-05 qua endpoint Lines-editor · DaDuyet | tạo+gán+submit: **test.drafter** (KeHoachKyKet.Create có cho Drafter ✓) · duyệt ×4: ADMIN_TEST |
| GĐ3 | **HĐ `ZZTEST-K8-2/NCC/SOL&ZZTEST-NCC-K8/01`** `1a025b78` — "TEST Hợp đồng toàn trình (SOLUTION-PE-3)" · DaPhatHanh | **bridge: test.drafter** (hội-2-khóa KeHoachKyKet.Read + Contracts.Create MỞ ĐÚNG cho Drafter — seeder K7 chứng lần 2) · submit+duyệt: ADMIN_TEST |
| GĐ4 | Bản cứng `5bc991e8` → `hasSealedCopy=True` | ADMIN_TEST |

Prod hiện có **2 HĐ, cả 2 ZZTEST** (đều phase 9 + bản cứng). Seq cháy thêm: PE A/054 · KHKK/2026/003 (phiếu thật đầu = **004**) · HĐ seq cặp prefix (ZZTEST-K8-2, ZZTEST-NCC-K8). Bug script tự bắt: main() save-đè-mất token cache (merge-trước-save đã vá — bài "2 writer 1 file").

END-FX3 — chuỗi PE→KHKK→HĐ→bản-cứng đủ 4 GĐ trên cây, dữ liệu + danh tính đều TEST
