From 77c723b07a9277169df094df49aac5f8a400a74d Mon Sep 17 00:00:00 2001 From: pqhuy1987 Date: Wed, 29 Jul 2026 20:23:52 +0700 Subject: [PATCH] wal: flush 20260729T2023 --- .claude/WAL.md | 4 +- .claude/agent-memory/cicd-monitor/MEMORY.md | 4 +- .../cicd-verify-a8027c0.md | 277 ++++++++++++++++++ .../2026-07-29-S161-khkk-w1-schema/run.md | 4 +- .../w1-synthesis.md | 32 ++ .../runs/2026-07-29-S161-khkk-w2-crud/run.md | 26 ++ 6 files changed, 344 insertions(+), 3 deletions(-) create mode 100644 .claude/workflows/runs/2026-07-29-S161-khkk-w1-schema/cicd-verify-a8027c0.md create mode 100644 .claude/workflows/runs/2026-07-29-S161-khkk-w1-schema/w1-synthesis.md create mode 100644 .claude/workflows/runs/2026-07-29-S161-khkk-w2-crud/run.md diff --git a/.claude/WAL.md b/.claude/WAL.md index a6cf2c5..121974f 100644 --- a/.claude/WAL.md +++ b/.claude/WAL.md @@ -14,7 +14,9 @@ chain: - [x] wf ĐỢT-2 `wf_a95c9909-14b`: database-agent **PASS 8/8+DB11 0-blocker** (parity 2 DB, Down script-proof) · test-specialist **4 test + fault-inject 2-chiều** (chết #53 lúc đợi suite — sub-md cứu, lead chạy tươi **566/0**); harvest 2 diary xong. Tally #53 wave này: 3/5 lượt spawn - [x] reviewer PWF 10-issue 0-chặn (lượt-1 garble #53 → resume sạch): 8 fix cùng commit + 2 giữ; build+suite giữ 0W/0E · 566/0; residual-write MEMORY reviewer ACCEPT verified-KEPT (AS-10) - [x] commit `a8027c0` (30 file +8.987/−22, F-0 gate ĐẠT) + PUSH origin/main -- [!] wf: cicd-monitor verify `a8027c0` ĐANG CHẠY nền — 6 phép (CI 566 · mig 69 prod · menu 142 · marker `QT-KHKK-V2-001` · đo-CỜ F-04 2 chiều · smoke+label); về PASS ⇒ W1 ĐÓNG, còn nghiệm thu tay Designer type-10 3-Bước (deep-link `/system/approval-workflows-v2/ContractSigningPlan`) + dựng WF trước W5 +- [x] **W1 ĐÓNG — cicd VERDICT=PASS 6/6** (Run #427 5m40s · 566 · Mig 69 prod 96 bảng · menu 142 GIỮ · marker 3-tầng · đo-CỜ 13/13/13/2 + control-âm Khkk_G1 + log `0 added + 91 upgraded` khớp dự-đoán · label type-10 VN sống). Synthesis `runs/2026-07-29-S161-khkk-w1-schema/w1-synthesis.md`. Tồn: setup WF tay trước W5 · UAT non-Admin (nv.test KHÔNG tồn tại prod) · 51-bis GẤP (cicd _INDEX 99,2%/167B) · F-4 +- [x] gate 5-anchor W2 PASS 5/5 (A4 line-drift content-found; A5 KeHoachKyKet∈All:180 THẬT; phân xử lệch: mã sinh @CREATE per spec, entity-comment W1 sai → worker sửa) +- [!] wf: hmw W2 đợt-1 `wf_6f0aad26-229` ĐANG CHẠY — 2 lane {implementer-backend: mega-file ContractSigningPlanFeatures CHỈ-CRUD (cấm copy phần duyệt, bẫy flatten :427-439) + Controller authz-2-tầng ≥4 policy · implementer-frontend: pages/khkk 3-page ×2 app + 4-place re-point 6 leaf khỏi coming-soon + SHA-pair} — run-folder `runs/2026-07-29-S161-khkk-w2-crud/` (taskList 6 stage) - [ ] đợt-2 SAU khi wf về: test-specialist + database-agent (song song) → lead acceptance §③-B (build·test·sqlcmd 96·Down sạch·đo CỜ) → reviewer → commit/push → cicd-monitor 3-chân-kiềng - [ ] setup thủ công TRƯỚC W5 (SAU W1 vì type=10 cần mig 69): dựng workflow type=3 + type=10 3-trạm qua Designer (seed chỉ 1 trạm) - [ ] housekeeping @pause/closeout kế: nén MIND-1 5.447B (trần `_mind` 83,2% — MIND-5 chắc vượt) · re-carry 2 ý D rơi (vá-GỐC-Phase-3.5-refresh {mới-nêu} CÒN SỐNG + khuôn-5-đợt→sổ bền {gần-chốt}) · STATUS:6 bổ arc dry-run S160 (mù có lý do — land 15:27 trước pipeline) · hạ nhãn DR/WF-3-trạm đã-định-đoạt · E đếm 7 sub-file (không 6) · mở lại slot-45 trần `_mind` (tiền-đề lật: file SỐNG 83,2%) · quyết bookkeeping OVERDUE:light (cặp H24 chạy @S159 bookend mà `last_audit` đứng 27) diff --git a/.claude/agent-memory/cicd-monitor/MEMORY.md b/.claude/agent-memory/cicd-monitor/MEMORY.md index 247c2b9..a056de8 100644 --- a/.claude/agent-memory/cicd-monitor/MEMORY.md +++ b/.claude/agent-memory/cicd-monitor/MEMORY.md @@ -1,6 +1,8 @@ # CI/CD Monitor Agent — Persistent Memory -- **07-29 verify `ea962b1` ĐỢT-5 (menu 6-leaf y-chang-Duyệt-NCC + `GET /contracts/deleted` + bỏ hero) [S159, 10 file 173+/371−, 0 mig]:** ✅ **PASS 9/9** — Run **#426** (id=539) success **5m38s**; gate **562**. JS rotate cả 2 app (admin `CC1pGE1F`→`D0sXA0fe` · user `CLfAftby`→`Y6dW_5CM` +css `B6bUSFBt`→`6YIAufJR`); admin-css FROZEN nhưng **LM 14:44:29 in-window** ⇒ lành tính (#69 mode-B, khuôn đợt-4 dùng lại được ngay). 🔑 **CỨU FALSE-FAIL — luật MỚI về control ÂM:** brief bảo `Toàn trình 4 giai đoạn` phải VẮNG; tao grep repo-wide TRƯỚC khi đo → chuỗi vẫn sống hợp lệ ở `PePipelineStrip.tsx:24` **`aria-label`** (cả 2 app, có từ đợt-2) ⇒ **dự đoán bundle=1 chứ không 0**, đo đúng 1. Áp máy móc là FAIL OAN. Thay bằng `Quy trình mua sắm` (eyebrow hero) — `grep -rc` 2 app = **0 hit mọi file** ⇒ ĐỘC QUYỀN ⇒ bundle=0 = chứng hero đã gỡ (kèm `UserDashboardPage` 63.124→50.142 B). ⟹ **LUẬT: trước khi nhận 1 chuỗi làm control ÂM phải grep repo-wide xác nhận nó ĐỘC QUYỀN cho thứ mình muốn chứng vắng; chuỗi dùng-chung chỉ làm marker DƯƠNG.** 🔑 **SEEDER-INSERT delta khớp TOÁN (khuôn mạnh, tái dùng):** đo baseline TRƯỚC deploy rồi so SAU restart — `Ct_%_WfView` 0→**7** · `_Approved` 0→**7** · `_Deleted` 0→**7** · `Khkk_%` 5→**7** · nhãn `Ct_%_Pending`=`Đang%` 0→**7** · Dashboard `%quy tr%` 0→**1** · **MenuItems 119→142 = +23 = 7+7+7+2 khớp từng đồng** · root 12 · DUPKEYS 0 · **21 key mới đủ 13/13 canread** (nhánh reviewKeys skip-existing ăn vì key HOÀN TOÀN MỚI — khác đợt-2/3 nơi row `false` sẵn có không được nâng). **Endpoint mới:** `GET /api/contracts/deleted` → **401** + **control route bịa → 404** ⇒ 401 CÓ NGHĨA, không phải catch-all (đừng chỉ đo 401 trần). ⚠️ brief ghi `/contracts/deleted` thiếu prefix — route thật có `[Route("api/contracts")]`. **Durability lần 3:** 38-key **494/494** · control âm Hrm/Off/Personal=0 · `Pe%` 197/185 · `Hdc_` nhãn 7 · 3 root ẩn. ⚠️ **Bẫy phạm-vi mới: `Ct_*` nay đẻ thêm 21 key ⇒ `LEFT(MenuKey,3)='Ct_'` cho ra 59 key, KHÔNG so được với đợt trước — phải NOT LIKE 3 hậu tố mới để giữ đúng tập 38 gốc.** Evidence: `runs/2026-07-29-S159-tong-quan-pipeline-menu/cicd-verify-ea962b1.md`. Tag `[run-426, pass-9-9, control-am-phai-DOC-QUYEN, seeder-delta-khop-toan, 401-voi-control-404, durability-restart-3, query-scope-re-anchor, gate-562]` +- **07-29 verify `a8027c0` W1 KHKK — Mig 69 `AddContractSigningPlans` [S161, 30 file +8987/−22]:** ✅ **PASS 6/6** — Run **#427** (id=540) success **5m40s**; gate **566 = 45D+521I** (0 fail/skip, +4 test W1). Mig 69 APPLIED-PROD: MIGTOP `20260729122015_AddContractSigningPlans` · MIGCOUNT 68→**69** · `sys.tables` **89→96 (+7 khớp 7 CreateTable)** · 7 bảng `ContractSigningPlan{s,Lines,DossierItems,LevelOpinions,Attachments,Changelogs,Approvals}`. MenuItems **142→142 GIỮ** (policy-key ⟂ menu-key). 🔑 **KHUÔN ĐO-CỜ (không đếm ROW) — tái dùng cho mọi seeder UPGRADE-if-exists:** row đã tồn tại CanRead-only ⇒ đếm row = 0-bit; đo PRE/POST **cờ** 8 key: 7 key đích 13/0/0/0 → **13/13/13/2**, vế ÂM liệt-kê-toàn-bộ `CanDelete=1` = **đúng 14 row = {Admin,Drafter}×7** (11 role kia D=0), Drafter (non-Admin) C/U/D=1. 🔑 **Control ÂM NỘI-HỌ mạnh nhất từ trước tới nay: `Khkk_G1` bị seeder CỐ Ý bỏ khỏi danh sách 7 key** ⇒ nó phải đứng yên 13/**0/0/0** — và nó đứng yên ⟹ chứng "đúng NGƯỜI làm, đúng TẬP", không phải thứ gì lật bừa cả họ. **Đọc seeder DỰ ĐOÁN `0 added + 91 upgraded` (91=7×13) TRƯỚC khi đo → log prod khớp từng đồng** (`added=0` = đúng đường upgrade, F-S1 đóng). Marker `QT-KHKK-V2-001` grep repo TRƯỚC ⇒ độc quyền 1 site fe-admin ⇒ dự đoán admin=1/user=0, đo đúng; **control ÂM 3 TẦNG**: hash CŨ `D0sXA0fe` → `ct=text/html` 900B **trùng khít hash BỊA** ⇒ 200 là SPA-fallback, bundle cũ đã bị ghi đè. admin js+css rotate (`M8SDA7Ps`/`C3xzT0H9`); user FROZEN nhưng **LM 20:10:00 in-window** ⇒ #69 mode-B lành tính. Restart chứng bằng **w3wp pid=4980 StartTime 20:10:05 TRÙNG dòng CI `App pool started`** + DLL mtime 20:07:50. Label F-7 sống: `?applicableType=10` → `"applicableTypeLabel":"Kế hoạch ký kết HĐ"`, `active:null/history:[]` + sqlcmd `ApplicableType=10`=0 ⇒ chưa dựng WF (đúng W1). Smoke 4 endpoint 200 + **route bịa 404** (chứng 200 CÓ NGHĨA). Durability `Ct_` **637/637** CanRead, root 12, DUPKEYS 0. Evidence: `runs/2026-07-29-S161-khkk-w1-schema/cicd-verify-a8027c0.md`. Tag `[run-427, pass-6-6, do-CO-khong-dem-ROW, control-am-noi-ho-key-bi-bo-sot, du-doan-91-upgraded-khop, control-am-3-tang-bundle, mig69-96-bang, gate-566]` + +- _(**#426 S159 đợt-5 `ea962b1` menu 6-leaf + `GET /contracts/deleted` + bỏ hero — PASS 9/9**: gate 562, 5m38s. JS rotate cả 2 app (admin `CC1pGE1F`→`D0sXA0fe` · user `CLfAftby`→`Y6dW_5CM` +css `B6bUSFBt`→`6YIAufJR`); admin-css FROZEN nhưng **LM 14:44:29 in-window** ⇒ lành tính (#69 mode-B, khuôn đợt-4 dùng lại được ngay). 🔑 **CỨU FALSE-FAIL — luật MỚI về control ÂM:** brief bảo `Toàn trình 4 giai đoạn` phải VẮNG; tao grep repo-wide TRƯỚC khi đo → chuỗi vẫn sống hợp lệ ở `PePipelineStrip.tsx:24` **`aria-label`** (cả 2 app, có từ đợt-2) ⇒ **dự đoán bundle=1 chứ không 0**, đo đúng 1. Áp máy móc là FAIL OAN. Thay bằng `Quy trình mua sắm` (eyebrow hero) — `grep -rc` 2 app = **0 hit mọi file** ⇒ ĐỘC QUYỀN ⇒ bundle=0 = chứng hero đã gỡ (kèm `UserDashboardPage` 63.124→50.142 B). ⟹ **LUẬT: trước khi nhận 1 chuỗi làm control ÂM phải grep repo-wide xác nhận nó ĐỘC QUYỀN cho thứ mình muốn chứng vắng; chuỗi dùng-chung chỉ làm marker DƯƠNG.** 🔑 **SEEDER-INSERT delta khớp TOÁN (khuôn mạnh, tái dùng):** đo baseline TRƯỚC deploy rồi so SAU restart — `Ct_%_WfView` 0→**7** · `_Approved` 0→**7** · `_Deleted` 0→**7** · `Khkk_%` 5→**7** · nhãn `Ct_%_Pending`=`Đang%` 0→**7** · Dashboard `%quy tr%` 0→**1** · **MenuItems 119→142 = +23 = 7+7+7+2 khớp từng đồng** · root 12 · DUPKEYS 0 · **21 key mới đủ 13/13 canread** (nhánh reviewKeys skip-existing ăn vì key HOÀN TOÀN MỚI — khác đợt-2/3 nơi row `false` sẵn có không được nâng). **Endpoint mới:** `GET /api/contracts/deleted` → **401** + **control route bịa → 404** ⇒ 401 CÓ NGHĨA, không phải catch-all (đừng chỉ đo 401 trần). ⚠️ brief ghi `/contracts/deleted` thiếu prefix — route thật có `[Route("api/contracts")]`. **Durability lần 3:** 38-key **494/494** · control âm Hrm/Off/Personal=0 · `Pe%` 197/185 · `Hdc_` nhãn 7 · 3 root ẩn. ⚠️ **Bẫy phạm-vi mới: `Ct_*` nay đẻ thêm 21 key ⇒ `LEFT(MenuKey,3)='Ct_'` cho ra 59 key, KHÔNG so được với đợt trước — phải NOT LIKE 3 hậu tố mới để giữ đúng tập 38 gốc.** Evidence: `runs/2026-07-29-S159-tong-quan-pipeline-menu/cicd-verify-ea962b1.md`. Tag `[run-426, pass-9-9, control-am-phai-DOC-QUYEN, seeder-delta-khop-toan, 401-voi-control-404, durability-restart-3, query-scope-re-anchor, gate-562]` - _(**#425 S159 đợt-4 `a2bbcb9` 7-FLAG fix + nhãn 'Bảng cứng' — PASS 9/9**: gate 562, user js `DeAg7MWm`→`CLfAftby`. 🔑 **admin FROZEN vẫn chứng được lành tính**: LM 13:55:31 **in-window** + size trùng ⇒ re-ship byte-identical (#69 mode-B). 🔑 **comment-strip DỰ ĐOÁN TRƯỚC rồi mới đo**: `chưa tới` source=2 (cả 2 trong comment) → dự đoán bundle=0 → đúng 0. ⚠️ `Không tìm thấy` bundle 10 > source 3 ⇒ marker phổ-thông chỉ dùng '≥1'. **Durability lần 2**: 38-key 494/494 giữ + control âm 0)_ diff --git a/.claude/workflows/runs/2026-07-29-S161-khkk-w1-schema/cicd-verify-a8027c0.md b/.claude/workflows/runs/2026-07-29-S161-khkk-w1-schema/cicd-verify-a8027c0.md new file mode 100644 index 0000000..76acb57 --- /dev/null +++ b/.claude/workflows/runs/2026-07-29-S161-khkk-w1-schema/cicd-verify-a8027c0.md @@ -0,0 +1,277 @@ +# cicd-monitor — verify deploy `a8027c0` (W1 KHKK, Mig 69 ContractSigningPlans) + +> Agent: cicd-monitor (READ-only) · S161 · 2026-07-29 +> Spec acceptance: `.claude/workflows/runs/2026-07-29-S160-khkk-dryrun-plan/spec-wave-w1-schema-designer-29-07-2026.md` §③-B +> **Ghi-đĩa-TRONG-lúc-làm** (chống #53). Mỗi mục ghi NGAY khi có bằng chứng. Kết bằng dòng `END … VERDICT=`. + +--- + +## CHÂN 0 — Push + path-filter (pre-condition) + +| Mục | Giá trị | +|---|---| +| Commit | `a8027c04fe929750998f5c20e23854f9d0e7a765` | +| Subject | `[CLAUDE] Domain+App+Infra+Admin: W1 KHKK — Mig 69 ContractSigningPlans 7 bang + policy KeHoachKyKet + seeder UPGRADE-if-exists + CodeGen KHKK + Designer type-10` | +| Commit time | 2026-07-29 20:04:39 +0700 | +| `git log origin/main..HEAD` | **rỗng** ⇒ đã push | +| Files changed | **30 file, +8987 / −22** | +| Path-filter (#41) | 29/30 file NON-ignored (`src/Backend/**`, `fe-admin/src/**`, `tests/**`); chỉ `docs/STATUS.md` bị ignore ⇒ **CI PHẢI trigger** ✅ | + +**Mig 69 on-disk (3-file rule OK):** +- `20260729122015_AddContractSigningPlans.cs` (372 dòng) + `.Designer.cs` (6960) + `ApplicationDbContextModelSnapshot.cs` (+550) +- `grep -c migrationBuilder.CreateTable` = **7** — đúng 7 bảng, 0 ALTER bảng cũ: + `ContractSigningPlans` · `…Lines` · `…DossierItems` · `…LevelOpinions` · `…Attachments` · `…Changelogs` · `…Approvals` + +**Byte-marker (chân 4) — kiểm ĐỘC QUYỀN TRƯỚC khi đo (luật S159):** +- `rg QT-KHKK-V2-001` toàn repo: **1 hit code** = `fe-admin/src/pages/system/ApprovalWorkflowsV2Page.tsx:170` + (`[TYPE_INT_CONTRACT_SIGNING_PLAN]: 'QT-KHKK-V2-001',` trong `DEFAULT_CODE_BY_TYPE`) +- `fe-user/src` = **0 hit** ⇒ chuỗi ĐỘC QUYỀN cho fe-admin, sinh mới @S161 +- ⟹ **Dự đoán TRƯỚC khi đo:** admin bundle mới = **1** occurrence · user bundle = **0** · admin bundle CŨ (`D0sXA0fe`) = 0. + +--- + +## BASELINE PRE-DEPLOY (đo 20:08:31 +0700, TRƯỚC khi run #427 xong) + +| Chỉ số | Giá trị pre-deploy | +|---|---| +| `sys.tables WHERE is_ms_shipped=0` | **89** | +| `MAX(MigrationId)` | **`20260727033522_AddPeAllowApproverDelete`** (Mig 68) | +| `COUNT(*) MenuItems` | **142** | +| bảng `ContractSigningPlan%` | **0** | +| admin bundle | js `index-D0sXA0fe.js` · css `index-DWDbm5As.css` | +| user bundle | js `index-Y6dW_5CM.js` · css `index-6YIAufJR.css` | + +Kỳ vọng post-deploy: tables **89 → 96** (+7) · MIGTOP → `20260729122015_AddContractSigningPlans` · MenuItems **142 GIỮ** · CSP tables **0 → 7**. + +--- + +### Baseline CỜ policy (chân 5) — đo 20:09, TRƯỚC deploy + +13 role prod. 8 key họ KHKK, **tất cả** = `rows=13 · R=13 · C=0 · U=0 · D=0`: +`KeHoachKyKet` · `Khkk_G1` · `Khkk_WfView` · `Khkk_List` · `Khkk_Create` · `Khkk_Pending` · `Khkk_Approved` · `Khkk_Deleted` +⟹ xác nhận tiền-đề F-S1: **row ĐÃ có sẵn CanRead-only** ⇒ đếm ROW = 0-bit, chỉ CỜ mới chứng UPGRADE ăn. + +### DỰ ĐOÁN TRƯỚC KHI ĐO (đọc seeder `DbInitializer.SeedKeHoachKyKetAccessAsync` trong diff, KHÔNG đọc kết quả) + +Tập key seeder đụng = **7** (`:2405+`): `KeHoachKyKet` + `Khkk_{WfView,List,Create,Pending,Approved,Deleted}`. +🔑 **`Khkk_G1` CỐ Ý NGOÀI tập** (comment: "group điều hướng — KHÔNG cần cờ ghi") ⟹ **control ÂM nội-họ**: nếu `Khkk_G1` cũng bật C/U thì phép đo của tao vô nghĩa (chứng tỏ có thứ khác lật bừa cả họ). + +| Đại lượng | Dự đoán | +|---|---| +| 7 key đích: `SUM(CanCreate)` | **13** mỗi key (13/13 role) | +| 7 key đích: `SUM(CanUpdate)` | **13** mỗi key | +| 7 key đích: `SUM(CanDelete)` | **2** mỗi key (CHỈ Admin + Drafter) — chiều ÂM: 11 role còn lại phải =0 | +| `Khkk_G1` (control ÂM) | **C=0 · U=0 · D=0 GIỮ NGUYÊN** | +| Log seeder | `0 added + 91 upgraded` (91 = 7 key × 13 role, mọi row tồn tại sẵn) | +| MenuItems | **142 GIỮ** (seeder không thêm menu-row; `KeHoachKyKet` menu-row đã có từ S159) | + +--- + +## CHÂN 1 — CI run + +| Mục | Giá trị | +|---|---| +| Run | **#427** (task id **540**) | +| head_sha | `a8027c04` ✅ khớp (key theo sha, KHÔNG theo run_number) | +| created_at | 2026-07-29T20:04:48+07:00 | +| **Kết luận** | **`status=success`** @2026-07-29T20:10:28+07:00 | +| Duration | **5m40s** (20:04:48 → 20:10:28) — trong dải ~5m30-6m gần đây | +| Run URL | https://git.baocaogiaoduc.vn/vietreport-admin/solution-erp/actions/runs/427 | + +**Test gate (log web-UI `…/actions/runs/427/jobs/0/logs`, 17.936 B):** 2 dòng `Passed!`, đối chiếu TÁCH-PHẦN: + +``` +Passed! - Failed: 0, Passed: 45, Skipped: 0, Total: 45 <- SolutionErp.Domain.Tests +Passed! - Failed: 0, Passed: 521, Skipped: 0, Total: 521 <- SolutionErp.Infrastructure.Tests +``` + +⟹ **566 PASS (45 D + 521 I), 0 fail, 0 skip** = ĐÚNG con số spec chờ (562 → 566, +4 test mới W1). ✅ + +**Dòng deploy (cuối log, giờ UTC trong log = +07 −7h):** +``` +2026-07-29T13:10:05.8595528Z Deploy done. App pool started. +2026-07-29T13:10:19.7741482Z API /health/live -> 200 +``` + +### ✅ CHÂN 1 = **PASS** + +--- + +## CHỨNG MINH RESTART THẬT (điều kiện tiên quyết của chân 2/3/5 — luật 3-chân-kiềng) + +| Bằng chứng | Giá trị | +|---|---| +| `w3wp` process mới | **pid 4980 · StartTime = 2026-07-29 20:10:05** — TRÙNG KHỚP dòng CI `App pool started` 20:10:05 | +| `SolutionErp.Api.dll` mtime | **2026-07-29 20:07:50** — trong cửa-sổ deploy (20:04:48–20:10:28) ⇒ file BE thật sự bị ghi đè | + +⟹ Mọi số đo DB dưới đây lấy lúc **20:11+**, tức **SAU** mốc recycle 20:10:05 ⇒ hợp lệ (tránh bẫy đo-trước-recycle S159 đợt-2). + +--- + +## CHÂN 2 — Mig 69 áp prod + +| Chỉ số | Pre | Post | Kỳ vọng | Kết quả | +|---|---|---|---|---| +| `MAX(MigrationId)` | `20260727033522_AddPeAllowApproverDelete` | **`20260729122015_AddContractSigningPlans`** | = Mig 69 | ✅ | +| `COUNT(*) __EFMigrationsHistory` | 68 | **69** | 69 | ✅ | +| `sys.tables WHERE is_ms_shipped=0` | 89 | **96** | 89→96 (+7) | ✅ khớp TOÁN | +| bảng `ContractSigningPlan%` | 0 | **7** | 7 | ✅ | + +7 bảng đếm đích danh (khớp 1:1 với 7 `CreateTable` trong file mig): +`ContractSigningPlans` · `…Lines` · `…DossierItems` · `…LevelOpinions` · `…Attachments` · `…Changelogs` · `…Approvals` + +### ✅ CHÂN 2 = **PASS** (bằng chứng sqlcmd prod trực tiếp, không phải suy từ CI-log) + +--- + +## CHÂN 3 — Menu KHÔNG đổi + +`COUNT(*) MenuItems`: pre **142** → post **142** ⇒ **GIỮ NGUYÊN, 0 row thêm** ✅ +(W1 chỉ đụng policy-key `KeHoachKyKet` trong `MenuKeys.All`, menu-row `Khkk_*` đã seed từ S159 — đúng Δ-1 của spec.) + +### ✅ CHÂN 3 = **PASS** + +--- + +## CHÂN 6 (phần 1) — 3 domain sống + +| URL | HTTP | +|---|---| +| `https://api.solutions.com.vn/health/live` | **200** ✅ | +| `https://admin.solutions.com.vn/` | **200** ✅ | +| `https://eoffice.solutions.com.vn/` | **200** ✅ | + +_(đo 20:11:30; lưu ý `/health` trần = 404 UNMAPPED — đường thật là `/health/live` + `/health/ready`)_ + +--- + +## CHÂN 4 — Bundle byte-marker (#77) + 3 tầng control ÂM + +| App | Pre-deploy | Post-deploy | Trạng thái | +|---|---|---|---| +| admin js | `index-D0sXA0fe.js` | **`index-M8SDA7Ps.js`** | 🔄 ROTATE | +| admin css | `index-DWDbm5As.css` | **`index-C3xzT0H9.css`** | 🔄 ROTATE | +| user js | `index-Y6dW_5CM.js` | `index-Y6dW_5CM.js` | ⏸️ FROZEN — **lành tính** (xem dưới) | +| user css | `index-6YIAufJR.css` | `index-6YIAufJR.css` | ⏸️ FROZEN — lành tính | + +**Ship-proof THẬT (không dựa hash-delta — #69 TWO-MODE):** + +| Bằng chứng | admin js MỚI | user js (frozen) | +|---|---|---| +| `Content-Type` | `application/javascript` | `application/javascript` | +| Size thật | **1.679.452 B** | 1.595.577 B | +| `Last-Modified` | **Wed, 29 Jul 2026 13:09:05 GMT** = 20:09:05 +07 | **13:10:00 GMT = 20:10:00 +07** | +| Trong cửa-sổ deploy (20:04:48–20:10:28)? | ✅ | ✅ | + +⟹ user FROZEN + **LM TRONG cửa-sổ** = re-ship byte-identical (#69 mode-B), KHÔNG phải bundle-cũ-kẹt. Đúng kỳ vọng: commit chỉ đụng `fe-admin/src`, `fe-user` 0 file. + +**Byte-marker `QT-KHKK-V2-001` — đo SAU khi đã chứng ĐỘC QUYỀN:** + +| Phép | Dự đoán (ghi trước) | Đo được | KL | +|---|---|---|---| +| admin bundle MỚI | **1** | **1** | ✅ khớp SỐ FILE nguồn (1 site `ApprovalWorkflowsV2Page.tsx:170`) | +| user bundle (control ÂM cross-app) | **0** | **0** | ✅ chứng marker không "khớp bừa" | +| admin bundle **CŨ** `D0sXA0fe` (control ÂM tầng-2) | biến mất | `http=200` **`ct=text/html` 900 B** | ✅ bundle cũ đã bị GHI ĐÈ khỏi đĩa | +| hash BỊA `ZZZZZZZZ` (mốc so trap SPA-fallback) | text/html | `http=200` **`ct=text/html` 900 B** | ✅ trùng khít mốc ⇒ 200 của hash-cũ là fallback, KHÔNG phải file thật | + +### ✅ CHÂN 4 = **PASS** (3 tầng control ÂM đều nói cùng một chuyện) + +--- + +## CHÂN 5 — ĐO CỜ policy-grant (F-04 / F-1 — vế acceptance đang treo) + +**Đo SAU recycle 20:10:05.** Đối chiếu PRE → POST, mọi key 13 role: + +| MenuKey | PRE (R/C/U/D) | POST (R/C/U/D) | Dự đoán | KL | +|---|---|---|---|---| +| `KeHoachKyKet` | 13/0/0/0 | **13/13/13/2** | 13/13/2 | ✅ | +| `Khkk_WfView` | 13/0/0/0 | **13/13/13/2** | 13/13/2 | ✅ | +| `Khkk_List` | 13/0/0/0 | **13/13/13/2** | 13/13/2 | ✅ | +| `Khkk_Create` | 13/0/0/0 | **13/13/13/2** | 13/13/2 | ✅ | +| `Khkk_Pending` | 13/0/0/0 | **13/13/13/2** | 13/13/2 | ✅ | +| `Khkk_Approved` | 13/0/0/0 | **13/13/13/2** | 13/13/2 | ✅ | +| `Khkk_Deleted` | 13/0/0/0 | **13/13/13/2** | 13/13/2 | ✅ | +| 🔑 `Khkk_G1` **(control ÂM nội-họ)** | 13/0/0/0 | **13/0/0/0** | **KHÔNG đổi** | ✅ | + +**Vế DƯƠNG (role non-Admin):** `Drafter` trên cả 7 key đích = **C=1 · U=1 · D=1** ⇒ thoả acceptance "≥1 role non-Admin có CanCreate=1 ∧ CanUpdate=1". + +**Vế ÂM (CanDelete chỉ Drafter/Admin):** liệt kê TOÀN BỘ row có `CanDelete=1` trong họ KHKK = **đúng 14 row = {Admin, Drafter} × 7 key**, không thừa 1 role nào ⇒ 11 role còn lại D=0. ✅ + +🔑 **Vì sao `Khkk_G1` là control ÂM có RĂNG:** seeder liệt kê tay 7 key và **cố ý bỏ `Khkk_G1`** ("group điều hướng — KHÔNG cần cờ ghi"). Nếu `Khkk_G1` cũng bật C/U thì phép đo vô nghĩa (chứng tỏ có thứ khác lật bừa cả họ). Nó đứng yên ⇒ đúng NGƯỜI làm, đúng TẬP. + +**Chứng độc lập từ log API prod** (`C:\inetpub\solution-erp\api\logs\solution-erp-20260729.log`), khớp con số tao ghi TRƯỚC khi đo: + +``` +2026-07-29 20:10:18.902 +07:00 [INF] Seeded KeHoachKyKet access: 0 added + 91 upgraded + (W1 GD2 golive - 13/13 role C+U, Delete=Drafter/Admin; UPGRADE-if-exists F-S1) +``` + +`0 added + 91 upgraded` — **91 = 7 key × 13 role**, khớp từng đồng dự đoán. `added=0` ⇒ đúng đường UPGRADE-if-exists, KHÔNG insert (F-S1 đóng: nhánh insert-NO-OP đã bị tránh). Timestamp 20:10:18 nằm SAU restart 20:10:05 ⇒ seeder chạy trong lần khởi động này, và chạy SAU revoke (đúng thứ tự `SeedAsync`). + +### ✅ CHÂN 5 = **PASS** — đo CỜ (không đếm ROW), đủ 2 chiều dương/âm + control ÂM + +--- + +## CHÂN 6 (phần 2) — Bearer + label type-10 + +Login `admin@solutions.com.vn` → `accessToken` dài 468 ⇒ OK. + +| Endpoint (admin bearer) | HTTP | +|---|---| +| `GET /api/contracts` | 200 ✅ | +| `GET /api/purchase-evaluations` | 200 ✅ | +| `GET /api/menus` | 200 ✅ | +| `GET /api/approval-workflows-v2` | 200 ✅ | +| **control: `GET /api/khong-ton-tai-abc`** | **404** ⇒ 200 ở trên CÓ NGHĨA, không phải catch-all | + +**F-7 label BE ăn prod** — `GET /api/approval-workflows-v2?applicableType=10` trả nguyên văn: + +```json +{"types":[{"applicableType":10,"applicableTypeLabel":"Kế hoạch ký kết HĐ","active":null,"history":[]}]} +``` + +⟹ type **10** SỐNG trên prod với nhãn VN **"Kế hoạch ký kết HĐ"** (không rơi fallback `ToString()` tiếng Anh). `active:null` + `history:[]` = **chưa có quy trình type-10 nào** — ĐÚNG kỳ vọng W1 (admin sẽ dựng tay), xác nhận chéo bằng sqlcmd `COUNT(*) ApprovalWorkflows WHERE ApplicableType=10` = **0**. + +### ✅ CHÂN 6 = **PASS** + +--- + +## Kiểm tra phụ (không nằm trong 6 chân — chống hồi-quy) + +| Phép | Kết quả | Nhận định | +|---|---|---| +| Họ `Ct_` (S159 durability) | **637/637 row CanRead**, 49 distinct key | ✅ 100%, revoker KHÔNG lật gì | +| MenuItems root (`ParentKey IS NULL`) | **12** | ✅ khớp mốc S159 | +| `MenuItems` trùng key | **0** | ✅ | +| Menu-row họ KHKK | **8** (`KeHoachKyKet` + 7 `Khkk_*`) | ✅ không đổi | +| `ContractSigningPlans` rows | **0** | ✅ đúng — W1 schema-only, chưa có dữ liệu | + +⚠️ **Đính chính trí-nhớ của tao (không phải lỗi deploy):** ghi chú S159 nói họ `Ct_` có "59 key ⇒ tập gốc 38". Đo thật hôm nay: **49 distinct key**, trong đó 21 key mang 3 hậu tố mới ⇒ tập lọc còn **28 key × 13 role = 364**, nên `364/364` chứ không phải `494/494`. Denominator lệch là do **PHẠM-VI QUERY + số cũ ghi sai**, KHÔNG phải mất quyền — bằng chứng mạnh hơn: đo TOÀN họ `Ct_` được **637/637**. + +--- + +## ⚠️ 2 phát hiện phụ cần báo (KHÔNG do commit này gây ra, KHÔNG chặn W1) + +1. **Tài khoản UAT `nv.test@solutions.com.vn` KHÔNG TỒN TẠI trên prod.** + `POST /api/auth/login` → **401** `"Email hoặc mật khẩu không đúng."`; sqlcmd `SELECT COUNT(*) FROM Users WHERE Email LIKE 'nv.test%'` = **0** (tổng 18 user). + ⇒ Phép thử 2-bearer (gotcha #44 — soi 403 im lặng cho role non-Admin) **hiện KHÔNG chạy được trên prod**. Commit `a8027c0` không đụng Users ⇒ không phải hồi-quy của lượt này (nhiều khả năng demo-seed bị gate — gotcha #51). **Đề xuất em-main:** chọn 1 tài khoản non-Admin CÓ THẬT làm mốc UAT mới, hoặc bỏ vế 2-bearer khỏi checklist cho tới khi có tài khoản. +2. **Đường log API trong memory của tao đã STALE.** `C:\inetpub\solution-erppi\logs` = **không tồn tại**; đường THẬT = **`C:\inetpub\solution-erp\api\logs\solution-erp-YYYYMMDD.log`**. Đã sửa trong MEMORY. + +--- + +## TỔNG KẾT + +| Chân | Nội dung | Verdict | +|---|---|---| +| 1 | CI run #427 PASS + test gate **566 (45D+521I)** | ✅ **PASS** | +| 2 | Mig 69 áp prod (MIGTOP + 96 bảng + 7 bảng CSP) | ✅ **PASS** | +| 3 | MenuItems **142 GIỮ NGUYÊN** | ✅ **PASS** | +| 4 | Byte-marker `QT-KHKK-V2-001` + 3 tầng control ÂM | ✅ **PASS** | +| 5 | ĐO CỜ policy-grant 2 chiều + control ÂM `Khkk_G1` | ✅ **PASS** | +| 6 | Smoke 3 domain + bearer + label type-10 | ✅ **PASS** | + +**Không có mục nào FAIL. Không đề xuất rollback.** + +END cicd-verify a8027c0 — **VERDICT=PASS 6/6** + + diff --git a/.claude/workflows/runs/2026-07-29-S161-khkk-w1-schema/run.md b/.claude/workflows/runs/2026-07-29-S161-khkk-w1-schema/run.md index b0fb4a5..637b783 100644 --- a/.claude/workflows/runs/2026-07-29-S161-khkk-w1-schema/run.md +++ b/.claude/workflows/runs/2026-07-29-S161-khkk-w1-schema/run.md @@ -15,7 +15,9 @@ | 4 | Đợt-2b: design-review Mig 69 TRƯỚC deploy (reversible Down · snapshot drift · FK/index) | hmw `database-agent` | [x] cùng wf — **PASS 8/8 + DB11, 0 blocker** (đo sys.foreign_keys/filter_definition; parity 2 DB tuyệt đối; Down script atomic sạch; IX superset khuôn PE; 4 advisory wave-sau) — `sub-database-agent-1.md` 21KB | | 5 | Lead: build ×2 + `dotnet test` + sqlcmd 89→96 + Down-script sạch + acceptance §③-B sweep | lead | [x] build BE 0W/0E 47s + FE build tươi PASS · test **566/0** (562+4 ≥ sàn 564) · sys.tables 96 ×2 DB · Down script-proof · CÒN 2 vế prod-side @stage-7: đo-CỜ policy sau seed prod + Designer tạo type-10 3-Bước | | 6 | Reviewer adversarial diff (schema mig >50 LOC — bắt buộc trước commit) | `reviewer` | [x] **PASS_WITH_FLAGS 10 issue (1H/3M/4m/2L), 0 chặn logic** — lượt-1 garble #53 giữa Trục-1 → SendMessage-resume về SẠCH đủ 6 trục. Disposition TỪNG DÒNG: 8 FIX cùng commit (F-0 git-add-tường-minh gate · F-7 label VN type-10 vào AwLabels · F-8 ép-false 2 cờ tầng BE · F-2 STATUS 55/220 · F-5 comment 7→8 · F-3 comment 2 chỗ · F-9 neo-tên-thay-số-dòng · F-6b khai giới hạn changelog) + 2 GIỮ (F-1 đo-CỜ chờ prod @stage-7 · F-4 kế thừa y PE prod). Sau fix: build 0W/0E + suite 566/0 GIỮ. Residual-write MEMORY tự-curate → verify ĐẠT, ACCEPT verified-KEPT (AS-10) — `sub-reviewer-2.md` | -| 7 | Commit + push + cicd-monitor 3-chân-kiềng (CI PASS · mig 69 prod · menu 142 giữ) | lead + `cicd-monitor` | [!] commit **`a8027c0`** (30 file, +8.987/−22, mig 3-file đủ, F-0 gate porcelain-??-rỗng ĐẠT) đã PUSH → CI đang chạy; `cicd-monitor` spawn nền — 6 phép {CI+test-gate 566 · mig 69 prod · menu 142 GIỮ · byte-marker `QT-KHKK-V2-001` độc-quyền · **đo CỜ F-04** (Create/Update 13/13 + Delete chỉ Drafter/Admin cả chiều âm) · smoke 3 domain + label type-10} | +| 7 | Commit + push + cicd-monitor 3-chân-kiềng (CI PASS · mig 69 prod · menu 142 giữ) | lead + `cicd-monitor` | [x] commit **`a8027c0`** PUSH → CI Run **#427 success 5m40s** · cicd **VERDICT=PASS 6/6** (`cicd-verify-a8027c0.md` 277 dòng END-line đủ; return garble #53 lần 5 — đĩa trọn): gate 566 · Mig 69 prod 96 bảng · menu **142 GIỮ** · marker 3-tầng-control-âm · **đo CỜ 13/13/13/2 + `Khkk_G1` control-âm-nội-họ đứng yên + log `0 added + 91 upgraded` khớp dự-đoán-ghi-trước** · label type-10 VN sống prod. Residual MEMORY cicd ACCEPT verified-KEPT; ⚠️ `_INDEX` cicd nay **99,2%** (167B) — slot 51-bis GẤP | + +**⇒ W1 ĐÓNG — synthesis: `w1-synthesis.md` (VERIFIED). Tồn sang wave sau: setup WF tay trước W5 · tài khoản UAT non-Admin (nv.test không tồn tại prod) · slot 51-bis · F-4.** ## Ràng buộc mang theo - 🔴 Seeder = **UPGRADE-if-exists khuôn `SeedProcurementMasterAccessAsync:2367-2374` + mở rộng vế CanDelete**, chạy SAU revoke (mirror `:2098-2101`) — KHÔNG insert (F-S1, 403 giết dry-run) · CẤM SQL tay (#84). diff --git a/.claude/workflows/runs/2026-07-29-S161-khkk-w1-schema/w1-synthesis.md b/.claude/workflows/runs/2026-07-29-S161-khkk-w1-schema/w1-synthesis.md new file mode 100644 index 0000000..33829e3 --- /dev/null +++ b/.claude/workflows/runs/2026-07-29-S161-khkk-w1-schema/w1-synthesis.md @@ -0,0 +1,32 @@ +# W1-SYNTHESIS — Wave W1 KHKK: Schema Mig 69 + Permission + Designer type-10 (S161, 2026-07-29) — VERIFIED + +> Lead-verified synthesis (đóng run — dual-accept dạng-1). Chi tiết per-lane: 4 sub-md + cicd-verify cùng cấp. + +## KẾT QUẢ: W1 SHIP PROD — cicd VERDICT=PASS 6/6, commit `a8027c0` (30 file, +8.987/−22) + +- **Schema:** Mig 69 `20260729122015_AddContractSigningPlans` — 7 bảng + 20 index, 0 ALTER bảng cũ; prod 89→**96 bảng**, MIGTOP khớp; database-agent design-review **PASS 8/8 + DB11 0-blocker** (parity 2 DB local tuyệt đối, Down script-proof atomic). +- **Permission:** `MenuKeys.All += KeHoachKyKet` (54→55 key ⇒ policy 216→**220**) + seeder `SeedKeHoachKyKetAccessAsync` UPGRADE-if-exists (+vế CanDelete): prod đo CỜ PRE 13/0/0/0 → POST **13/13/13/2** cả 7 key, control ÂM nội-họ `Khkk_G1` đứng yên, log `0 added + 91 upgraded` khớp dự-đoán-ghi-trước. Menu-row **142 GIỮ** (policy-key ⟂ menu-key). +- **CodeGen:** `ContractSigningPlanCodeGenerator` — `KHKK/{YYYY}/{Seq:D3}` (anh OK @S161), SERIALIZABLE, `WorkflowAppCodeSequences` dùng chung. +- **Designer type-10:** deep-link `/system/approval-workflows-v2/ContractSigningPlan` + label VN từ BE (`applicableTypeLabel` sống prod) + 2 checkbox early-finalize bị rào **3 tầng** (render + payload FE + ép-false BE create-path — 2 đường anh phán "PHÁ VỠ" S155). Prod `ApplicableType=10` = 0 workflow (đúng — dựng tay trước W5). +- **Test:** 562 → **566 PASS/0 fail** (4 test mới, fault-inject 2 chiều chứng răng; gồm phép đo mã phiếu A6 với vế clock-2027 chống năm-hardcode). +- **Review:** reviewer PWF 10 issue (1H/3M/4m/2L) → **disposition từng dòng: 8 fix cùng commit + 2 giữ-có-lý-do** (F-1 đo-CỜ → ĐÃ ĐÓNG bởi cicd chân-5; F-4 UTC-year/D3-tràn kế thừa y PE prod). + +## VẬN HÀNH — #53 cắn 5/7 lượt spawn, THIỆT HẠI 0 BYTE + +| Lượt | Vai | Return | Đĩa | +|---|---|---|---| +| wf_7d6fb81b lane BE | implementer-backend | chết (empty) | code sống 40% (Domain trọn), 0 sub-md → relaunch-cắt-gọt | +| wf_7d6fb81b lane FE | implementer-frontend | CLEAN | sub-md 7.9KB + build tươi verify | +| wf_f8c806d2 lane BE | implementer-backend | chết (empty) | **sub-md 6.2KB ghi-từng-mục cứu TRỌN** (tới hết Mig 69 apply 2 DB) → lead solo đuôi | +| wf_a95c9909 lane test | test-specialist | chết (empty) | sub-md 10KB — chết LÚC ĐỢI full-suite, việc thật xong | +| wf_a95c9909 lane DB | database-agent | CLEAN | sub-md 21KB | +| reviewer | reviewer | garble → **SendMessage-resume về SẠCH** | sub-md đủ 6 trục | +| cicd-monitor | cicd-monitor | garble | **cicd-verify 15KB đủ END+VERDICT-line** | + +Bài đắt nhất wave: **"ghi-đĩa-TỪNG-MỤC trong lúc làm" là biến số quyết định** — lượt BE-1 dồn cuối mất trắng narrative; mọi lượt sau ép ghi-từng-mục đều cứu trọn. Xác nhận lần thứ N bài S123/S158: chống #53 = thiết kế ARTIFACT, không phải prompt. + +## TỒN SANG WAVE SAU +1. **Setup thủ công TRƯỚC W5** (sau W2/W3 cũng được, type-10 đã sống): dựng workflow type=3 3-trạm + type=10 3-trạm (PMH→CCM→CEO) qua Designer — type-10 vào bằng deep-link (menu cố ý không đổi). Acceptance "tạo + xoá Level chưa chữ ký" chấm lúc đó. +2. **Tài khoản UAT `nv.test@` KHÔNG tồn tại prod** (cicd phát hiện — demo-seed gated #51/#75): phép 2-bearer gotcha #44 không chạy được ⇒ W2+ cần chọn 1 tài khoản non-Admin THẬT làm mốc, hoặc anh cho tạo user test. +3. `cicd-monitor/archive/_INDEX.md` **99,2% cap (167B còn)** — slot (51-bis) của anh giờ GẤP thật. +4. F-4 (UTC-year + D3 tràn 1000) ghi nhận — xử chung với PE khi làm mốc đổi năm. diff --git a/.claude/workflows/runs/2026-07-29-S161-khkk-w2-crud/run.md b/.claude/workflows/runs/2026-07-29-S161-khkk-w2-crud/run.md new file mode 100644 index 0000000..7c8b00f --- /dev/null +++ b/.claude/workflows/runs/2026-07-29-S161-khkk-w2-crud/run.md @@ -0,0 +1,26 @@ +# RUN — W2 KHKK: CRUD phiếu nháp + căn cứ b.8-9 + FE 3 page ×2 app (S161, 2026-07-29) + +> Wave 2/6 dry-run toàn trình. Spec: `../2026-07-29-S160-khkk-dryrun-plan/spec-wave-w2-crud-nhap-29-07-2026.md`. W1 ĐÃ SHIP (`a8027c0`, cicd PASS 6/6 — synthesis `../2026-07-29-S161-khkk-w1-schema/w1-synthesis.md`). +> **Gate 5-anchor: PASS 5/5 @S161** (flatten-trap :427-439 · guard+snapshot :53-62 · guard-type-trọn :258-267 · staticMap-comment content-found @:2215 [drift từ 2207 do commit W1 — neo content] · `KeHoachKyKet ∈ All:180` THẬT). +> ⚠️ Lệch phát hiện @gate: entity comment W1 "gen mã lúc Submit" ≠ spec W2 "gen lúc CREATE" (acceptance POST trả maKeHoach) — **SPEC THẮNG**, worker sửa comment entity cùng lượt. + +## taskList snapshot + +| # | Stage | Vai / cách | Trạng thái | +|---|---|---|---| +| 1 | Đợt-1a BE: mega-file `ContractSigningPlanFeatures.cs` (Create+auto-Lines+MaKeHoach@CREATE · UpdateDraft · Get/List/pendingMe+inbox-precompute · Dossier UPSERT/DELETE · Attachments · DELETE allow-list · picker PE-chờ-lập-KH) + `ContractSigningPlansController` authz 2-tầng per-action | hmw `implementer-backend` | [ ] | +| 2 | Đợt-1b FE: `pages/khkk/` 3 page + types ×2 app + 4-place mirror (routes + staticMap re-point 6 leaf khỏi coming-soon + menuKeys) + build ×2 + SHA-pair | hmw `implementer-frontend` | [ ] | +| 3 | Đợt-2: test-specialist (≥2 test PIN: `ContractSigningPlansController_EveryWriteEndpoint_HasAuthorizePolicy` + `CreateContractSigningPlan_FromNonApprovedPe_Throws`) | hmw `test-specialist` | [ ] | +| 4 | Lead: build ×2 + suite + acceptance §③-B sweep | lead | [ ] | +| 5 | Reviewer adversarial (CRUD endpoints POST/PUT/DELETE — bắt buộc) | `reviewer` | [ ] | +| 6 | Commit + push + cicd (3-chân-kiềng + 2-bearer NẾU có tài khoản non-Admin — nv.test KHÔNG tồn tại prod, xem w1-synthesis tồn-2) | lead + `cicd-monitor` | [ ] | + +## Ràng buộc mang theo +- 🔴 CẤM copy phần DUYỆT của Proposal (bẫy flatten :427-439 — OR-of-N thành AND; duyệt = W3 con-trỏ-đôi mirror PE). +- 🔴 Guard workflow-type copy TRỌN :258-267 (cả nhánh NotFound), đổi đích `ContractSigningPlan=10`. +- 🔴 Authz 2 tầng (#82): class `[Authorize]` + per-action `Policy="KeHoachKyKet.{Action}"` — grep ≥4. +- 🔴 409-khoá-vĩnh-viễn: DELETE allow-list mở NGAY W2 (M6) + tạo-lại-sau-TuChoi/xóa được (#57). +- 🔴 FE 4-place ×2 app SHA-pair; 6 leaf hết coming-soon (grep = 0 cả 2 app). + +## Stages +(cập nhật theo tiến độ)