wal: flush 20260727T0903

This commit is contained in:
pqhuy1987
2026-07-27 09:03:41 +07:00
parent a3a6d66cff
commit 0db0c6a3ee
3 changed files with 135 additions and 2 deletions

View File

@ -1,7 +1,7 @@
# WAL — auto-generated, không sửa tay
updated: 2026-07-27T01:01+07:00 | session: S155 (phiên-LOGIC L7, window 2) | branch: main
goal: Bootstrap L7 XONG (bookend @open + trio + Phase 3.5). Còn 22 FLAG/finding chưa disposition — CHẶN bởi 3 số chờ anh (42)(43)(44).
goal: [A-product, ĐANG CHẠY] PE nút XÓA phiếu ở màn DUYỆT — pipeline anh lệnh: 2 Invest → file-chi-tiết+checklist → review spec → hmw Opus 5 MAX. || [B-governance, TREO] 22 FLAG/finding chưa disposition — CHẶN bởi (42)(43)(44). Hai mạch SONG SONG, A không đóng thay B.
chain:
[x] Sàn-3 sạch 5/5 + WAL trống — `git log --format='%s' origin/main..HEAD | grep -v '^wal:'` = rỗng
@ -22,7 +22,10 @@ chain:
[ ] @MIND-2 (block kế) mục C — đính chính 2 số bất-biến trong `_context-s-7.md` (FLOW CẤM sửa tại chỗ): `:62` "counter 27→28" nay là **29** · `:67` "12 file" nay là **13** (máy-derive + đĩa đều 13)
[ ] @MIND-2 mục D — xử 3 nhãn ctx-verifier nêu: "Phép thử tự-soi H24" đã định-đoạt ⇒ RỜI D sang con-trỏ · "Guard post-wave assertion" {gần-chốt} nhưng VÔ-GIA-CƯ (0 dòng bản-đồ) ⇒ +1 dòng chain · "Bắt con-đo khai mẫu-số" có ca độc-lập thứ 4 (10→9 của chính lead @S155), 0 ca ngược ⇒ đề {gần-chốt}
next: CHỜ anh trả lời 3 số (42) canonRows · (43) carry con-trỏ-vs-slug · (44) END-line-thành-luật; có số rồi mới chạy disposition 22 FLAG.
[!] wf: A-product PE-delete-approver — run `.claude/workflows/runs/2026-07-27-S155-pe-delete-approver/` · S1 2 Invest ĐANG CHẠY nền (sub-invest-be-1 · sub-invest-fe-2) → S2 spec+checklist → S3 reviewer → S4 hmw
[ ] A-S2: sau khi 2 Invest về → tổng hợp `spec-pe-delete-approver.md` + checklist; HỎI anh các điểm quyết (ai được xóa · soft-delete vs thu-hồi-về-Nháp · phiếu đã có lượt duyệt có xóa được không)
next: [A] chờ 2 Invest về → viết file chi tiết + checklist → hỏi anh điểm quyết → reviewer → hmw. || [B] CHỜ anh 3 số (42) canonRows · (43) carry con-trỏ-vs-slug · (44) END-line-thành-luật + (E2) harvest-curator 111,1% trần.
verify:
python scripts/session_ctx.py mind-check --session 7

View File

@ -0,0 +1,44 @@
# run — PE: nút XÓA phiếu ở màn DUYỆT (approver-side)
- **run-id:** `2026-07-27-S155-pe-delete-approver`
- **phiên:** S155 (phiên-LOGIC L7, window 2)
- **pipeline anh lệnh:** 2 Invest → file-chi-tiết + checklist → review spec → hmw Opus 5 MAX
- **mode:** RUN-TRACE (≥3 task) — sub ghi full-detail vào CHỈ `sub-<role>-<i>.md` của mình
## Nguồn yêu cầu (ảnh chat anh gửi — UAT thật, không phải giả định)
Phiếu mẫu: `PE/2026/A/046` · Duyệt NCC · dự án **FLOCK03** · hạng mục **MAT-16 "16 Mat Khác"** · gói thầu **"16 Mat - Băng cản nước"** · trạng thái **Đã gửi duyệt** · có cờ **GẤP (PRO)**.
Workflow đang chạy: Bước 1 (Phòng Cung ứng) — Cấp 2, NV duyệt **Bùi Lê Thủy Trà**, "Đến lượt bạn duyệt". Bước 2 có Cấp 2 "Duyệt thay CEO". Bước 3 Ban Giám đốc, kết thúc tại Cấp 2. `Lịch sử duyệt (1)`.
Khối **HÀNH ĐỘNG** hiện chỉ có: `✓ Duyệt` · `← Trả lại`**thiếu Xóa**.
Banner đang hiện: *"Bạn được phép chỉnh sửa Hạng mục / NCC / Báo giá (workflow bật mode Approver edit)"*.
**Lời user verbatim (Tra Sol):**
- "@Kenny Chỗ duyệt - tạo giúp em nút xóa với"
- "Vì mấy bạn bắt sai cái gói thầu" → "nên phải cho quyền xóa cái phiếu đó luôn"
- "chứ lỡ bấm sai cái gói thầu / quay lại không được / **phải xóa thì nó mới ko có lũy kế lên**"
**Lời anh (owner) verbatim:** "Hic hôm trước có cái chỗ cho huy / mọi người nói là ko cần cái đó / giờ lại cần"
⇒ 🔴 giả-thuyết cần KIỂM: đường xóa/thu-hồi **đã từng tồn tại rồi bị gỡ**. Nếu đúng → khôi phục rẻ hơn dựng mới, và phải tìm ra **lý do gỡ** để không tái phạm.
## Vì sao đây là lỗi SỐ LIỆU, không phải UX
CLAUDE.md S134: `ComputePendingAsync` complementary — **phiếu ChoDuyet có winner được tính vào lũy kế TẠM TÍNH**. Phiếu bấm sai gói thầu đang treo ở bước duyệt ⇒ **đang ăn ngân sách hạng mục sai**. "Trả lại" đẩy về `TraLai=98` chứ không triệt tiêu ⇒ số tạm tính vẫn lệch. Đó chính là điều Tra Sol mô tả bằng chữ "lũy kế lên".
## Stages
- [ ] **S1 — 2 Invest song song** (BE-slice + FE/authz-slice) → `sub-invest-be-1.md` · `sub-invest-fe-2.md`
- [ ] **S2 — file chi tiết + checklist** (lead tổng hợp) → `spec-pe-delete-approver.md`
- [ ] **S3 — review spec** (reviewer, adversarial)
- [ ] **S4 — hmw Opus 5 MAX** thực thi
## Ràng buộc mang theo mọi stage
- Governance L7 **vẫn treo** 22 FLAG chờ (42)(43)(44) — việc này **song song**, KHÔNG đóng thay.
- Soft delete là quy ước sẵn có (`AuditableEntity`: `IsDeleted`/`DeletedAt`/`DeletedBy`) — spec phải nói rõ dùng cái này hay thu-hồi-về-Nháp.
- Quyền = **2 tầng độc lập** (bài học S118 / gotcha #82): display-layer menu `CanRead` ⟂ API-authz `[Authorize(Policy)]`. Ẩn nút ≠ đóng API.
- Xóa phải có **vết** (changelog/audit) — phiếu đã có `Lịch sử duyệt (1)`.
## taskList snapshot
1. invest BE: entity/state-machine/endpoint-xóa/authz/lũy-kế/changelog + git-khảo-cổ "chỗ cho huy"
2. invest FE: màn duyệt (component khối HÀNH ĐỘNG) + permission matrix `Pe_*` + nơi PE đang xóa được (nếu có) + dấu vết UI đã gỡ

View File

@ -0,0 +1,86 @@
# sub-invest-be-1 — ĐIỀU TRA BACKEND: nút XÓA phiếu PE ở màn DUYỆT
- **run-id:** `2026-07-27-S155-pe-delete-approver`
- **vai:** investigator-codebase (INTERNAL, read-only)
- **quy ước:** mọi dòng `[CODE]` = đọc thẳng từ file trên đĩa · `[SUY LUẬN]` = tôi suy ra, chưa có dòng mã nào nói · `[CHƯA XÁC MINH]` = không chứng được
- **trạng thái file:** ĐANG GHI DẦN (ghi ngay khi tìm ra từng mục — chống #53)
---
## Q1 — Đường XÓA hiện có tới đâu?
### F1. CÓ endpoint DELETE PE — nhưng **không có policy authz** `[CODE]`
`src/Backend/SolutionErp.Api/Controllers/PurchaseEvaluationsController.cs:146-151`
```csharp
[HttpDelete("{id:guid}")]
public async Task<IActionResult> Delete(Guid id, CancellationToken ct)
{
await mediator.Send(new DeletePurchaseEvaluationCommand(id), ct);
return NoContent();
}
```
- Route đầy đủ: `DELETE /api/purchase-evaluations/{id}` (`:14` `[Route("api/purchase-evaluations")]`).
- Authz = **CHỈ class-level `[Authorize]` TRẦN** (`:15`) — **KHÔNG có `[Authorize(Policy = "Pe_*.Delete")]`** ở action.
⇒ Bất kỳ user **đã đăng nhập nào** cũng gọi được endpoint này (chỉ bị chặn bởi guard phase ở handler, xem F3).
⇒ Trùng khớp gotcha #82 / bài học S118: menu-ẩn ≠ API-đóng. Đây là **2 tầng độc lập** và tầng API hiện **mở**.
- Tôi đã grep toàn controller: **không một action nào** trong `PurchaseEvaluationsController.cs``[Authorize(Policy=...)]` (0 hit) — cả file dựa hoàn toàn vào guard trong handler. Đây là **thiết kế cố ý đã có từ trước** (comment `:69-71`, `:88-90`, `:120` nói rõ "Class [Authorize] any-auth; handler fine-grained Forbidden").
### F2. CÓ Command/Handler `DeletePurchaseEvaluationCommand` `[CODE]`
`src/Backend/SolutionErp.Application/PurchaseEvaluations/PurchaseEvaluationFeatures.cs:1392-1411`
```csharp
// ========== DELETE ==========
public record DeletePurchaseEvaluationCommand(Guid Id) : IRequest;
public class DeletePurchaseEvaluationCommandHandler(
IApplicationDbContext db) : IRequestHandler<DeletePurchaseEvaluationCommand>
{
public async Task Handle(DeletePurchaseEvaluationCommand request, CancellationToken ct)
{
var entity = await db.PurchaseEvaluations.FirstOrDefaultAsync(x => x.Id == request.Id, ct)
?? throw new NotFoundException("PurchaseEvaluation", request.Id);
if (entity.Phase != PurchaseEvaluationPhase.DangSoanThao
&& entity.Phase != PurchaseEvaluationPhase.TuChoi)
throw new ConflictException("Chỉ xóa được phiếu ở phase Soạn thảo hoặc Từ chối.");
db.PurchaseEvaluations.Remove(entity);
await db.SaveChangesAsync(ct);
}
}
```
Ghi chú quan trọng về handler này:
- Handler **KHÔNG inject `ICurrentUser`****không check ai là người xóa**: không check Drafter-owner, không check role, không check "đến lượt bạn". Guard duy nhất là **phase**.
- Handler **KHÔNG ghi changelog** (đối chiếu Q4 — các thao tác khác đều ghi).
- Handler **KHÔNG đụng bảng con** (xem Q5).
### F3. XÓA hiện tại là **SOFT delete** — nhưng gián tiếp qua interceptor `[CODE]`
`db.PurchaseEvaluations.Remove(entity)` **trông như** hard-delete, nhưng bị `AuditingInterceptor` chặn lại và đổi thành soft:
`src/Backend/SolutionErp.Infrastructure/Persistence/Interceptors/AuditingInterceptor.cs:54-63`
```csharp
foreach (var entry in context.ChangeTracker.Entries<AuditableEntity>())
{
if (entry.State == EntityState.Deleted)
{
entry.State = EntityState.Modified;
entry.Entity.IsDeleted = true;
entry.Entity.DeletedAt = now;
entry.Entity.DeletedBy = userId;
}
}
```
⇒ Vì `PurchaseEvaluation : AuditableEntity`, mọi `.Remove()` trên PE = **UPDATE IsDeleted=1 + DeletedAt + DeletedBy** (DeletedBy lấy từ `ICurrentUser.UserId` — nên **vết "ai xóa" CÓ**, dù handler không tự ghi).
🔴 Đây là **indirect-assignment** giống hệt lớp bẫy gotcha #81-EXT: đọc handler thì tưởng hard-delete, sự thật là soft.
---
*(file đang được ghi tiếp — các mục Q2..Q7 bên dưới)*