# S57bis (2026-06-11) — PE gắn Hạng mục công việc (Mig 49) + mở quyền all-role + menu "Cá nhân" + khóa demo user + Harness-4 runtime-VERIFIED

> **Bối cảnh:** sếp chốt qua Zalo 11:02-11:17 (deadline **15:00 cùng ngày**): (1) kiểm tra mapping master data ngoài eoffice; (2) phân quyền TẤT CẢ user thấy Duyệt NCC + cấu hình master data; (3) flow tạo phiếu: *chọn quy trình → chọn dự án → chọn hạng mục công việc → chọn/nhập NCC/TP → chuyển duyệt*, phiếu dạng **"Dự án (năm) – Hạng mục công việc"**; (4) clear dữ liệu cũ. Anh chốt scope qua AskUserQuestion: xóa demo = **CHỈ khóa user sample** · quyền PE = **Xem + Tạo** · hạng mục = **header phiếu, 1 phiếu 1 hạng mục**.

## Việc đã làm

### A — Đối chiếu Excel (3) vs master data S55: ✅ NO-CHANGE
- Python openpyxl dump 7 sheet → diff vs `scripts/master-import-data.generated.md`: 62 Projects + 71 WorkItems (16VT/30TP/9MEP/16TB) + 3 Suppliers **identical 100%**. 2 sheet hợp đồng = catalog tham khảo, không thuộc DB. Không patch gì.

### C — PE gắn Hạng mục công việc (Mig 49 `AddWorkItemToPurchaseEvaluation`)
- **Design (🔵 database-agent introspect LocalDB):** `PurchaseEvaluations.WorkItemId Guid?` **scalar loose-Guid + index, KHÔNG FK vật lý** — nhất quán convention PE (ProjectId/SelectedSupplierId đều loose; duy nhất ApprovalWorkflowId có FK). Guard = validator + handler (mirror S43 FK-invariant). WorkItems = catalog GLOBAL (không ProjectId) → 2 dropdown độc lập; "Dự án (năm) – Hạng mục" = chuỗi ghép hiển thị. KHÔNG đụng CodeSequences (mã phiếu giữ `PE/{YYYY}/{A|B}/{Seq}`).
- **BE:** entity + config index + Mig 49 3-file (AddColumn nullable + CreateIndex, Down reversible) + Create/UpdateDraft command `WorkItemId` + validator `NotEmpty` (create bắt buộc; DB nullable backward-compat 4 phiếu cũ) + FK-guard `AnyAsync(w.Id==x && w.IsActive)` → Conflict + **UpdateDraft null-safe** (`if (request.WorkItemId is not null)` — client không gửi → GIỮ, chống null-hóa bug-class S42) + 3 projection ListItemDto (List/Inbox/CreateContractFrom) LEFT-join WorkItems.
- **FE ×2 app (logic-identical, PeHeaderForm SHA256 IDENTICAL):** `PeWorkspaceCreateView` select "c. Hạng mục công việc *" sau Dự án (option `[Category] Code — Name`, canSubmit require) + `PeHeaderForm` (select + load existing + PUT/POST gửi workItemId) + `PeDetailTabs` (header subtitle "Dự án **–** Hạng mục" + FormRow display + inline-edit khóa) + types +3 field. Route reuse `/catalogs/work-items` (không endpoint mới).
- **🟪 Test +12 (`PeWorkItemGuardTests`): 228→240 PASS.** Validator 3 + create-FK-guard 4 + **update-null-safe 5**. Finding: `NotEmpty()` trên `Guid?` không chặn `Guid.Empty` → FK-guard handler bắt (defense-in-depth, locked 2 test).

### B — Mở quyền all-role (extend S57-WIP `SeedAllRolesReviewReadPermissionsAsync`)
- **Pe_* (11 key: root + 5 leaf × 2 type, build qua factory vì Pe_* leaf KHÔNG nằm `MenuKeys.All` — recon catch):** CanRead+**CanCreate**=true mọi role, **upgrade-only** (row cũ 7 role nâng cờ false→true, KHÔNG hạ, KHÔNG đụng Update/Delete). PeWf_*/AwV2/PeWorkflows GIỮ Admin (prefix `Pe_` không match — ký tự thứ 3).
- HRM/Office/Personal/Master/Catalogs: CanRead-only skip-existing (S57 giữ nguyên). PE controller `[Authorize]` class-level → mở menu không silent-403 (gotcha #44 không áp).

### D — Khóa 14/16 demo sample user (`LockDemoSampleUsersAsync`)
- Ungated idempotent NGAY trong DbInitializer (sau SeedDemoUsers + SeedItDepartmentStaff) → tự áp prod khi deploy, bền mọi startup. `IsActive=0 + LockoutEnabled + LockoutEnd=Max + SecurityStamp rotate`.
- **GIỮ ACTIVE có chủ đích:** `nv.cao` + `nv.truong` (IT helpdesk round-robin pool S52 — khóa nốt SAU khi anh gán user thật vào dept IT, ops-pending S56) + `catalog.manager` (account chức năng).

### S57-WIP ship kèm (từ 06-10, đã nằm working tree)
- Menu nhóm **"Cá nhân"** (Personal root order 30, mirror Puro) + Chấm công re-parent Off→Personal + HrmConfig re-parent Hrm→Master + HrmDashboard order 1 + Contracts 30→31 + `parentBackfill` idempotent + admin bỏ ẩn Master (đảo S29) + Master write-lock `[Authorize(Roles="Admin,CatalogManager")]` ×3 controller.

### Harness-4 closeout (governance, commit riêng)
- **Spawn-test 2 chiều post-restart PASS:** H1 tooling-auditor (demote pin) self-report `claude-opus-4-8[1m]` + H2 harvest-curator (promote inherit) self-report `claude-fable-5[1m]` → nấc **RUNTIME-VERIFIED 06-11** (adap-report §2/§5 + STATUS row promoted; `[1m]` 1M-resolve SE tự verify). Email update `2026-06-11-se-to-ai_infra-harness-4-runtime-verified` (honest n=1/chiều; hmw.js executed-file giữ). Lesson env: **CCD cache agent frontmatter — restart CLI mới ăn** (proved 2 data-point 06-10/06-11).

## Multi-agent & lessons
- **HMW-mode ON** carry từ S55. Fan-out: 2 monitor (kiêm spawn-test) + 2 recon (investigator-codebase catch **Pe_*-leaf-not-in-All** + database-agent design Mig 49) + 2 builder + test-specialist + reviewer ×2.
- ⚠️ **2 builder return-truncated giữa task** (gotcha #53 — BE chết trước projection-3/migration, FE chết giữa mirror fe-admin) → **em main solo vá cross-stack** (disk/git truth, không tin return): fix CS7036 + CS8019 + Mig 49 + null-safe + mirror 7 edits PeHeaderForm + 3 edits PeDetailTabs ×2 app. Reviewer email-gate đầu cũng mất tích → gộp 1 gate trọn cuối.
- Excel (3) đối chiếu bằng raw-dump trước parse (tránh lặp data-quality trap S55 MEP-col).
