wal: flush 20260803T1014
This commit is contained in:
@ -0,0 +1,34 @@
|
||||
# cicd-monitor/2026-05-q2 — gist
|
||||
distill-gen: 1
|
||||
source-verbatim: .claude/agent-memory/cicd-monitor/archive/2026-05-q2.md (1 entry + header-rule → 4 cụm)
|
||||
pointer-style: substring
|
||||
|
||||
## TỔNG-HỢP (3-5 ý)
|
||||
1. **Deploy-xanh ≠ feature-dùng-được.** Run #231 PASS đủ mọi lớp máy (CI 3m30s, bundle 2/2 rotate, Mig 32/33 applied prod, smoke 5/5) nhưng seed V2 = 0 row ⇒ VERDICT PARTIAL — lớp verify quyết-định là DỮ-LIỆU SEED (sqlcmd đếm row theo `ApplicableType`), không phải bundle-hash + smoke-200.
|
||||
2. **Hai chủ-đích đúng va nhau, không phải bug:** cờ `DemoSeed:Disabled=true` (thiết-kế Plan T cho UAT sạch) nuốt seed hạ-tầng của Plan B ⇒ sinh gotcha #51; đóng bằng Run #232 `38f1c4d` carve-out option (b). Monitor từ-chối tự quyết fix-option — escalate em-main.
|
||||
3. **Refactor ~150 LOC `ApproveV2Async` + 0 test** = đúng khuôn rủi-ro gotcha #48 — UAT-skip-test không áp cho scope refactor.
|
||||
4. **Log-ERR trùng cửa-sổ deploy ≠ do deploy:** SlaExpiryJob 5 entry ERR là nhiễu V1 có trước — quy trách bằng enum/phase trong message, không bằng thời-điểm.
|
||||
|
||||
## Entries
|
||||
|
||||
**[cao] A2+A3 — Run #231/#232 Plan B Contract V2 (PARTIAL → đóng)**
|
||||
- **VIỆC:** Verify Run #231 (id=345) push `6eec8d7..3e92584` 11 commit — Plan B wire Contract V2 (Mig 32/33 + `ApproveV2Async` + 6 FE × 2 app).
|
||||
- **KẾT-LUẬN:** VERDICT=**PARTIAL** — deploy PASS (CI 3m30s; bundle 2/2 rotate `leEMWFLU→BBADl46y` + `Dgn1iU9E→DA_VI3zO`; Mig 32 `20260522051059_AddApprovalWorkflowToContract` + Mig 33 `20260522052240_AddContractLevelOpinions` applied prod; API 200 + 5/5 smoke; 7 HĐ V1 nguyên-vẹn) nhưng SEED PARTIAL: `QT-HD-V2-001` 0 row (ApplicableType=3) ⇒ dropdown FE rỗng ⇒ V2 KHÔNG test được E2E. Đóng bằng Run #232 `38f1c4d` carve `SeedSampleContractWorkflowV2` ra khỏi DemoSeed-gate (option b) + **gotcha #51**.
|
||||
- **BÀI-HỌC:** deploy-xanh ≠ feature-dùng-được — phải verify **DỮ-LIỆU SEED** (sqlcmd đếm row đúng `ApplicableType`), không dừng ở bundle-hash + smoke-200. Và: refactor >100 LOC (`ApproveV2Async` ~150 LOC) đi kèm **0 test** = đúng khuôn rủi-ro gotcha #48, UAT-skip-test không áp cho scope refactor.
|
||||
- **BẤT-NGỜ:** thứ chặn feature lại là **feature-flag CỐ Ý** (`DemoSeed:Disabled=true`, thiết-kế Plan T S23 t10 để UAT sạch), không phải bug — 2 chủ-đích đúng đắn va nhau; và CICD-monitor **từ chối tự quyết** (a) hay (b), escalate em-main.
|
||||
→ substring:"38f1c4d"
|
||||
|
||||
**[thấp] A4 — SlaExpiryJob ERR cluster**
|
||||
- **VIỆC:** phân-loại 5 entry ERR 12:58:16 nổi lên cùng lúc deploy.
|
||||
- **KẾT-LUẬN:** pre-existing V1 noise — `ConflictException Transition X→Y không hỗ trợ` trên 4 cặp phase (`DangInKy→DangKiemTraCCM`, `DangSoanThao→DangGopY`, `DangGopY→DangDamPhan`, `DangTrinhKy→DongDau`); 0 dính V2; 0 regression.
|
||||
- **BÀI-HỌC:** log-ERR trùng cửa-sổ deploy ≠ do deploy — phải soi enum/phase trong message để quy trách.
|
||||
- **BẤT-NGỜ:** job auto-approve V1 vẫn đang fail âm-thầm trên HĐ legacy, tách hẳn thành việc điều-tra riêng.
|
||||
→ substring:"SlaExpiryJob"
|
||||
|
||||
## Phụ-lục token (coverage)
|
||||
|
||||
- **gotcha#:** `#48` (dẫn — refactor >100 LOC Service chạm changelog/audit) · `#51` (SINH MỚI — DemoSeed gate) · "44 gotchas cross-ref" (không ở file này, xem q3)
|
||||
- **surprise:** `CRITICAL DISCOVERY #6 (NEW gotcha to add)` · "seed gap is feature-flag design choice not deploy failure" · "Decision NOT for CICD Monitor — escalating" · "0 new tests added Plan B (~150 LOC ApproveV2Async NO test cover)" · "bro UAT ... V2 wire E2E NOT testable UAT mode"
|
||||
- **guard:** `DemoSeed:Disabled=true` feature-flag gate · `Reviewer ApplicableType=Contract guard` (hotfix commit trong range) · `paths-ignore`/test-gate 111 baseline · `feedback_uat_skip_verify` (rule được viện-dẫn) · Rule `§6.5` "KHÔNG cắt narrative"
|
||||
- **root-cause:** "DemoSeed feature flag gates Plan B Chunk A2 sample V2 workflow seed" ⇒ `QT-HD-V2-001 NOT seeded in prod (0 rows ApplicableType=3)` ⇒ V2 endpoint trả `{"active":null,"history":[]}` ⇒ dropdown Create rỗng
|
||||
- **file:line / định-danh:** log `2026-05-22 12:57:44 [INF] DemoSeed:Disabled=true → skip workflow + contracts + PE + sample V2 seed (Plan T S23 t10 + Plan B Chunk A2 Contract V2)` · `SeedSampleContractWorkflowV2` · `ApproveV2Async` (~150 LOC) · Run #231 id=345 · Run #232 sha=`38f1c4d` · push range `6eec8d7..3e92584` (11 commit) · Mig 32 `20260522051059_AddApprovalWorkflowToContract` · Mig 33 `20260522052240_AddContractLevelOpinions` · cột `ApprovalWorkflowId` + `CurrentApprovalLevelOrder` · bảng `ContractLevelOpinions` (0 row) · bundle `leEMWFLU→BBADl46y` (admin) / `Dgn1iU9E→DA_VI3zO` (user) · `QT-HD-V2-001` (ApplicableType=3) / `QT-DN-V2-001` v1+v2 (ApplicableType=1) · CI 3m30s 12:54:24→12:57:54 · `SlaExpiryJob` 5 ERR @12:58:16 · 4 transition `DangInKy→DangKiemTraCCM` / `DangSoanThao→DangGopY` / `DangGopY→DangDamPhan` / `DangTrinhKy→DongDau` · archive-header: S32 curate, 27KB→~24KB, ngưỡng 25KB
|
||||
@ -0,0 +1,43 @@
|
||||
# cicd-monitor/2026-05-q3 — gist
|
||||
distill-gen: 1
|
||||
source-verbatim: .claude/agent-memory/cicd-monitor/archive/2026-05-q3.md (3 entry → 3 cụm)
|
||||
pointer-style: substring
|
||||
|
||||
## TỔNG-HỢP (3-5 ý)
|
||||
1. **Test đỏ mà code sản-phẩm đúng — lỗi ở thước đo.** Cặp Run #215 FAIL → #216 PASS (8 phút turnaround): SQLite + clock đóng-băng ⇒ 2 changelog cùng `CreatedAt` ⇒ tie-break `OrderByDescending` bất-định (gotcha #48); fix = lọc theo NỘI-DUNG (`Summary`), code Chunk A không revert.
|
||||
2. **Giả-thuyết chết bằng quan-sát, không bằng suy-luận:** Discovery #3 — glob `**/*.md` khớp `.md` ở MỌI độ sâu nên commit `cc8a7d3` (docs + 4 agent MEMORY.md) vẫn bị CI SKIP; gotcha #47 không chết mà đổi vai PREVENTIVE cho file non-`.md` tương-lai.
|
||||
3. **CI test-gate chặn TRƯỚC deploy = lưới cứu chế-độ UAT-skip-test;** refactor BE >100 LOC nên `dotnet test` local trước push.
|
||||
4. **Baseline 2026-05-12:** vai khởi-động bằng NỀN tri-thức (44 gotchas cross-ref + checklist 5-stage + 3 skill), không bắt đầu bằng run.
|
||||
|
||||
## Entries
|
||||
|
||||
**[cao] B1 — Run #215 FAIL → #216 PASS (gotcha #48)**
|
||||
*(trùng liên-file #215: bản dày = file này; cicd-monitor--2026-05-runs giữ bản mỏng ở phần tóm-tắt)*
|
||||
- **VIỆC:** Verify Plan AB Chunk A (Changelog visibility: Bug 1 Budget Adjust + Bug 2 Return Mode) — 1 commit 3 file, BE `PurchaseEvaluationWorkflowService.cs` +207/-95 + 2 FE `PeDetailTabs.tsx`.
|
||||
- **KẾT-LUẬN:** #215 (id=329, `cdfd542`, push `e23f51c..cdfd542`) **FAIL** ở test_infra 51/53 — 2 test ReturnMode báo `Expected changelog.ContextNote not to be <null>`; deploy KHÔNG chạy (bundle giữ nguyên Run #214, Mig 31 TOP 1 y cũ). #216 (id=330, `8c05947`) **PASS** — chỉ sửa TEST (+7/-2) thêm `.Where(c => c.Summary!.Contains("Chuyển phase"))` trước `OrderByDescending(CreatedAt).First()`; code Chunk A **không revert**; 58/58 + 53/53, bundle 2/2 rotate, 10:13→10:21 = 8 phút.
|
||||
- **BÀI-HỌC:** SQLite + **clock đóng-băng** ⇒ 2 changelog cùng `CreatedAt` ⇒ `OrderByDescending` tie-break trả nhầm bản-ghi ⇒ test "lấy entry mới nhất" là **thước không xác-định**; phải lọc theo NỘI-DUNG (Summary) chứ đừng dựa thứ-tự thời-gian. Refactor BE >100 LOC nên `dotnet test` local TRƯỚC push — UAT skip-test không đủ.
|
||||
- **BẤT-NGỜ:** test đỏ mà **code sản-phẩm đúng** — lỗi nằm ở thước đo; và CI test-gate chặn đúng lúc nên bro tránh được audit-trail vỡ trên prod (fail lại hoá may).
|
||||
→ substring:"8c05947"
|
||||
|
||||
**[cao] B2 — Verify S22 cumulative + Discovery #3 (glob `**/*.md`)**
|
||||
*(trùng liên-file "Verify S22 chốt": bản dày hơn ở cicd-monitor--2026-05-runs — file này giữ bản của mình, đóng-kín per-file)*
|
||||
- **VIỆC:** Verify chốt S22, push `3d725c4..cc8a7d3` 12 commit (Plan C/D/E + Mig 30 F4 per-NV Approver edit Budget).
|
||||
- **KẾT-LUẬN:** VERDICT=PASS — 33 user active prod, bundle 2/2 rotate, **104/104** test (+1 so baseline S21=103), Mig 30 xác-nhận prod. **Discovery #3:** commit `cc8a7d3` gồm docs + 4 agent `MEMORY.md` vẫn bị CI **SKIP** vì glob `**/*.md` khớp `.md` ở MỌI độ sâu.
|
||||
- **BÀI-HỌC:** glob `**/*.md` trong `paths-ignore` khớp cả file `.md` nằm sâu trong `.claude/agent-memory/` ⇒ giả-thuyết "thư-mục đó sẽ trigger CI" **bị bác bằng quan-sát**, không bằng suy-luận.
|
||||
- **BẤT-NGỜ:** gotcha #47 KHÔNG chết mà đổi vai — vẫn đúng như biện-pháp PHÒNG cho file state **non-`.md`** tương-lai dưới `.claude/agent-memory/`.
|
||||
→ substring:"cc8a7d3"
|
||||
|
||||
**[thấp] B3 — Setup baseline**
|
||||
- **VIỆC:** khởi-tạo CI/CD Monitor 2026-05-12.
|
||||
- **KẾT-LUẬN:** nạp nền: 44 gotcha cross-ref + checklist 5-stage + 3 skill preload + khuôn verify bundle-hash; chưa monitor run nào.
|
||||
- **BÀI-HỌC:** vai bắt đầu bằng NỀN tri-thức + checklist, không bắt đầu bằng run.
|
||||
- **BẤT-NGỜ:** (không có — entry mốc; giữ vì là gốc truy-vết baseline 44 gotcha / 5-stage.)
|
||||
→ substring:"44 gotchas"
|
||||
|
||||
## Phụ-lục token (coverage)
|
||||
|
||||
- **gotcha#:** `#48` (SQLite tie-break — cặp catch+fix) · `#47` (đổi vai → PREVENTIVE) · dải nền `#39-#52` · "44 gotchas cross-ref" (baseline 05-12)
|
||||
- **surprise:** `Discovery #3 first surfaced` + "Spec hypothesis ... **disproven** for this commit" · "Side benefit: CI test gate caught BEFORE prod deploy — bro UAT spared broken Plan M edge case audit trail" · "Plan AB Chunk A code `cdfd542` KHÔNG bị revert" · "Demonstrates test-after UAT mode CAN tolerate edge case bug if next chunk lands within minutes — **but** ... UAT skip-test rule risky for refactor scope" · "Deploy NOT shipped ... bro UAT screenshot **pre-deploy stale**"
|
||||
- **guard:** test-gate CI chặn trước deploy (test_domain + test_infra) · `.Where(c => c.Summary!.Contains("Chuyển phase"))` (fix-guard chọn đúng entry) · `paths-ignore` glob `**/*.md` · Stage 4.6 sqlcmd seed verify (header) · 5-stage checklist + 10-surface-point per-NV (header)
|
||||
- **root-cause:** `ApplyReturnModeAsync` thêm Changelog MỚI (`EntityType=Workflow + Action=Update + Summary`, **không** có `ContextNote`) ở cuối, cùng `SaveChangesAsync` với `LogTransitionAsync` (có `ContextNote=comment`) ⇒ SQLite + frozen test clock ⇒ **cùng `CreatedAt`** ⇒ `.OrderByDescending(c => c.CreatedAt).FirstAsync()` tie-break trả nhầm entry Workflow · Discovery #3 root-cause: glob `**/*.md` khớp `.md` ở mọi độ sâu
|
||||
- **file:line / định-danh:** `PurchaseEvaluationWorkflowService.cs` +207/-95 · `ApplyReturnModeAsync` **line 403-412** · `LogTransitionAsync` **line 100** · `PeDetailTabs.tsx` (2 FE mirror filter) · test `PurchaseEvaluationWorkflowServiceReturnModeTests.ApplyReturnMode_OneStep_AtStep1_ResetsToBuoc1Cap1_KeepsChoDuyet` · test `...ApplyReturnMode_OneLevel_AtStep1Level1_ResetsToBuoc1Cap1_KeepsChoDuyet` · lỗi `Expected changelog.ContextNote not to be <null>` · Run #215 id=329 `cdfd542` (1m06s) / Run #216 id=330 `8c05947` (+7/-2) · push `e23f51c..cdfd542` · Run #214 (baseline bundle) · Mig 31 TOP 1 · Run S22: push `3d725c4..cc8a7d3` 12 commit, Mig 30 F4, 33 user, 104/104 (baseline S21=103) · commit `cc8a7d3` (docs + 4 agent MEMORY.md → CI SKIPPED) · PE `c6e9` · 51/53 → 58/58 + 53/53 · 10:13→10:21 (8 phút) · setup 2026-05-12
|
||||
@ -0,0 +1,63 @@
|
||||
# cicd-monitor/2026-05-q4 — gist
|
||||
distill-gen: 1
|
||||
source-verbatim: .claude/agent-memory/cicd-monitor/archive/2026-05-q4.md (7 entry → 5 cụm)
|
||||
pointer-style: substring
|
||||
|
||||
## TỔNG-HỢP (3-5 ý)
|
||||
1. **Chuỗi 3 Run module-mới S33→S34 (#350 → #237 → #238) ALL-PASS theo khuôn Pattern 12-bis cookie-cutter** (PE→Hrm→Office, MediatR auto-discover namespace mới không đăng-ký tay); verify module-mới = **3 lớp**: build/test (120/120) → bundle-rotate → sqlcmd seed + menu-key + permission-row (EmployeeProfiles 33 + `LastSeq=33` khớp nguyên-tử).
|
||||
2. **Path-filter phải rà theo thư-mục MỚI sinh ra:** DISCOVERY #7 — `paths-ignore` thiếu `eval/**` ⇒ commit telemetry RAG (Run #234 `b223466`) kích deploy ~3m30s vô-ích; cùng một filter vừa đúng (#41 skip 4 commit docs) vừa thủng trong cùng cửa-sổ — sai-sót là VẮNG-MẶT một dòng.
|
||||
3. **Kinh-tế spawn (S26, Run #222-#227):** spawn CICD-monitor 1× đầu Phase-wire là đủ, chunk polish tự verify — ranh-giới "được tự verify" = có/không đổi hợp-đồng dữ-liệu, không phải kích-thước diff.
|
||||
4. **Phiên không-deploy vẫn có việc:** canh cert (`2026-07-23` notAfter), drift Mig TOP 5, MEMORY sát ngưỡng 25KB, RAG rerank 0.906 + ghi baseline bundle-hash; `/healthz` 404 — canonical là `/health/live`.
|
||||
5. **Ghi cả kết-quả ÂM-TÍNH có chủ-đích** (gotcha #1/#44 KHÔNG dính, #51 áp đúng chiều, bộ test BW1-BW7 caught NOTHING) — nếu chỉ ghi lỗi, lần sau không ai biết bẫy đã được kiểm hay bị bỏ quên.
|
||||
|
||||
## Entries
|
||||
|
||||
**[cao] C1 — Chuỗi deploy 3 Run module-mới (Hrm → Office), ALL PASS**
|
||||
- **VIỆC:** Verify 3 Run liên-tiếp: **#350** `48a99e1` (Mig 34 EmployeeProfile 17 entity + Plan C test BW1-BW7) → **#237** `79a8343` (Hrm CQRS endpoint + FE 2 app + menu seed) → **#238** `ea440da` (Office `/api/directory` Danh bạ nội bộ BE+FE 2 app).
|
||||
- **KẾT-LUẬN:** 3/3 **PASS** (3m38s / 3m50s / ~3m30s). Test 58+62=**120/120** giữ nguyên từ #350 trở đi (62 = 53 baseline + 9 BW). Bundle-chain khớp liền mạch: admin `BUTKoqRP`(#350 giữ)→`CqGMUMOr`(#237)→`ChA9_vP5`(#238); user `CMHv2GS4`→`C_HKyxBe`→`DCpX7akt`. Mig 34 `20260526110207_AddEmployeeProfiles` TOP 1 xuyên cả 3. Seed verify sqlcmd: EmployeeProfiles **33 row** (16 demo + 14 Solutions thật + 3 admin/test) + `EmployeeCodeSequences` NV/2026 `LastSeq=33` khớp nguyên-tử; menu key `Hrm`+`Hrm_HoSo` rồi `Off`+`Off_DanhBa` (+2 row Permission auto-grant); `/api/directory` trả 13.6KB/34 dòng phủ đủ 9 phòng ban.
|
||||
- **BÀI-HỌC:** khuôn **Pattern 12-bis cookie-cutter mirror** (PE→Hrm→Office) chạy end-to-end không cần đăng-ký thủ-công: namespace mới `SolutionErp.Application.Office` được MediatR auto-discover. Verify module-mới phải gồm **3 lớp**: build/test → bundle-rotate → **sqlcmd seed + menu-key + permission-row** (bundle xanh không nói gì về seed).
|
||||
- **BẤT-NGỜ:** cả 3 Run **không bắt được lỗi nào** — kể cả bộ test BW1-BW7 vừa thêm cũng "caught NOTHING, all PASS first try"; và `LastSeq=33` khớp đúng 33 row là bằng-chứng codegen nguyên-tử không rò.
|
||||
→ substring:"20260526110207_AddEmployeeProfiles"
|
||||
|
||||
**[cao] C3 — DISCOVERY #7: `eval/**` ngoài `paths-ignore`**
|
||||
- **VIỆC:** rà vì sao Run #234 `b223466` (3m52s) chạy dù commit chỉ là docs + `eval/*.json` của S31 RAG.
|
||||
- **KẾT-LUẬN:** `paths-ignore` **thiếu `eval/**`** ⇒ mọi commit telemetry RAG kích deploy ~3m30s vô-ích (0 thay-đổi code); đề-xuất em-main cân thêm `'eval/**'` nếu tần-suất tăng. (Đối-chứng: `b832f43..5400983` 4 commit docs-only SKIP đúng theo #41.)
|
||||
- **BÀI-HỌC:** path-filter phải rà theo **thư-mục MỚI sinh ra** (eval/, telemetry) — filter đúng hôm qua thành thủng hôm nay khi repo mọc thư-mục mới.
|
||||
- **BẤT-NGỜ:** cùng một filter vừa **đúng** (skip 4 commit docs) vừa **thủng** (chạy `eval/**`) trong cùng cửa-sổ vài ngày — sai-sót là VẮNG-MẶT một dòng, không phải dòng sai.
|
||||
→ substring:"b223466"
|
||||
|
||||
**[cao] C4 — Khuôn "hybrid verify" (S26, Run #222-#227)**
|
||||
- **VIỆC:** Plan AG series (PE List tree-view) 6 Run liên-tiếp — chọn cách verify tiết-kiệm.
|
||||
- **KẾT-LUẬN:** spawn CICD-Monitor **1 lần** ở Run #222 `0bf6c7e` (bundle 2/2 rotate `C8TvDy7r→CWHIdoFo` / `BvcWrq2z→Bg2FNeIz`, smoke 5/5 200, PE List API giữ 9 field, test-gate 111, Mig 31 y cũ); #223-#227 (AG2-AG6) em-main **tự verify** (bundle mắt-thường + push OK + Gitea auto-trigger 3-4 phút). AG4 cross-stack (DTO +4 field Drafter*/Department* + 3 projection JOIN) chạy `dotnet test` 111/111 local trước push. 0 regression toàn cụm.
|
||||
- **BÀI-HỌC:** ROI spawn: **1× đầu Phase-wire** là đủ cho kịch-bản 1 dev solo lặp nhanh; chunk polish (CSS/UX/copy) tự verify — re-spawn `~150K × N` là phí.
|
||||
- **BẤT-NGỜ:** cách rẻ hơn lại **không giảm an-toàn** — vì chunk polish không đụng shape API; ranh-giới "được tự verify" là **có/không đổi hợp-đồng dữ-liệu**, chứ không phải kích-thước diff.
|
||||
→ substring:"0bf6c7e"
|
||||
|
||||
**[vừa] C2 — Snapshot phi-deploy S32/S33 (hạ-tầng + tự-kiểm)**
|
||||
- **VIỆC:** 3 lượt spawn read-only không có deploy nào để soi (S32 startup, S32 wrap, S33 health-check).
|
||||
- **KẾT-LUẬN:** VERDICT=HEALTHY — 0 unpushed (HEAD=`5400983`), 5 Run gần nhất SUCCESS (#235/#234/#233/#232/#231), 3 endpoint smoke 200 (0.23s/0.29s/0.31s), Mig prod TOP 5 khớp repo không drift (33→32→31→30→29), cert `api.solutions.com.vn` notAfter `Jul 23 01:58:16 2026 GMT` (~58 ngày, auto-renew ~2026-06-23 cửa-sổ win-acme 30d). Tự-kiểm: MEMORY 24.9KB/221 dòng sát ngưỡng 25KB → sau curate còn 24.2KB; RAG 2949 chunk, `search_memory` top rerank **0.906**.
|
||||
- **BÀI-HỌC:** phiên không-deploy vẫn có việc: canh **cert · drift Mig · kích-thước MEMORY · sức-khoẻ RAG** — và ghi lại **baseline bundle-hash** để lượt sau có mốc so.
|
||||
- **BẤT-NGỜ:** `/healthz` trả **404** — canonical là `/health/live`; và Gitea API gọi **unauth** (token rỗng) vẫn đọc được lịch-sử Run.
|
||||
→ substring:"Jul 23 01:58:16 2026 GMT"
|
||||
|
||||
**[vừa] C5 — Bốn ca "âm-tính" đáng giữ**
|
||||
- **VIỆC:** ghi nhận các bẫy ĐÃ BIẾT nhưng KHÔNG tái-diễn trong 3 Run.
|
||||
- **KẾT-LUẬN:** gotcha **#1** không dính (namespace `Application.Office` mới vẫn được MediatR auto-discover); gotcha **#44** silent-403 không thấy (`[Authorize]` mức class trên `DirectoryController` cho mọi user đã đăng-nhập); gotcha **#51** được **áp đúng chiều** (`SeedDemoEmployeeProfilesAsync` cố-ý KHÔNG gate `DemoSeed:Disabled` vì là seed HẠ-TẦNG); EmployeeProfiles 33 không tăng ở #237 = **guard idempotent** chạy.
|
||||
- **BÀI-HỌC:** ghi cả kết-quả ÂM-TÍNH có chủ-đích — nếu chỉ ghi lỗi, lần sau không ai biết bẫy đã được kiểm hay bị bỏ quên.
|
||||
- **BẤT-NGỜ:** bài-học từ #51 (seed bị gate nhầm) đã đảo thành **tiêu-chí phân-loại** DEMO-seed ⟂ INFRASTRUCTURE-seed và được viện-dẫn chủ-động ở Run sau.
|
||||
→ substring:"caught NOTHING"
|
||||
|
||||
## Phụ-lục token (coverage)
|
||||
|
||||
- **gotcha#:** `#1` (MediatR auto-discovery — **KHÔNG** dính) · `#41` (path filter docs-only skip — dẫn 3 lần) · `#44` (silent 403 — **NOT observed**) · `#48` (SQLite tie-break — foundation preserved ×2) · `#51` (INFRASTRUCTURE vs DEMO seed — dẫn + áp)
|
||||
- **Discovery#:** `#5` (sqlcmd pattern — "UPDATED from") · `#6` (INFRASTRUCTURE vs DEMO seed Stage 4.6) · `#7` (**NEW** — `paths-ignore` thiếu `eval/**`) · `#8` (chỉ có ở HEADER dòng 3 "NEW collection naming", **0 hit trong thân** — con-trỏ mồ-côi; nội-dung thuộc entry S35/S36 NGOÀI file này — KHÔNG cite như content của file)
|
||||
- **surprise:** `DISCOVERY #7 NEW: path filter paths-ignore MISSING eval/** → ... triggered ~3m30s deploy wastefully (no code change)` · `Gotcha #44 silent 403 NOT observed` · `Application/Office namespace mới auto-discovered MediatR — KHÔNG gotcha #1` · `Plan C BW1-BW7 ROI: test gate caught NOTHING this run (all PASS first try)` · `SeedDemoEmployeeProfilesAsync correctly NOT gated DemoSeed:Disabled per gotcha #51 lesson` · `EmployeeProfiles total=33 (unchanged Run #350 baseline — idempotent guard skip)` · `/healthz 404 N/A — /health/live canonical` · `Gitea API unauth (token empty OK)` · `Pattern saved: CICD Monitor spawn 1× đầu Phase wire ROI tốt ... re-spawn ~150K × N wasteful` · `bundle hash 2/2 UNCHANGED (... expected NO FE)` · `Pattern 12-bis cross-module mirror reaffirmed strong`
|
||||
- **guard:** class-level `[Authorize]` trên `DirectoryController` · idempotent guard skip (`EmployeeProfiles` 33 không đổi) · `DemoSeed:Disabled` gate (và ca **cố-ý không** gate) · `paths-ignore` + glob `**/*.md` · Stage 4 + **Stage 4.6 sqlcmd seed verify** · test-gate CI + "Implementer Reviewer pre-commit gate strong" · MEMORY ngưỡng **25KB** · win-acme cửa-sổ renew **30d** · `EmployeeCodeSequences` LastSeq nguyên-tử · BW-guard: BW4 `ForbiddenException` outsider · BW5 wrong `ApplicableType` · BW6a UNIQUE composite · BW6b UPSERT 1 row · BW6c Cascade delete · BW7 V1 fallback `ConflictException`
|
||||
- **root-cause:** `eval/**` KHÔNG có trong `paths-ignore` ⇒ commit `eval/*.json` (S31 RAG) kích deploy vô-ích · `**/*.md` khớp mọi độ sâu ⇒ commit docs+MEMORY bị SKIP đúng thiết-kế · seed hạ-tầng từng bị nhốt nhầm sau `DemoSeed` (bài #51) ⇒ tiêu-chí tách DEMO ⟂ INFRASTRUCTURE
|
||||
- **file:line / định-danh:** **Run #238** `ea440da` push `edba4ae..ea440da` 2 commit/23 file (`7b0781b` curate 4 agent MEMORY = 8 file MD) · `DirectoryFeatures.cs` · `DirectoryController.cs` route `/api/directory` · `MenuKeys.cs` (+`Off`, `Off_DanhBa`) · `DbInitializer.cs` · FE ×2 app: `types/directory.ts`, `pages/office/InternalDirectoryPage.tsx`, `App.tsx`, `Layout.tsx`, `menuKeys.ts` · directory **13.6KB / 34 row / 9 phòng ban** · bundle `CqGMUMOr→ChA9_vP5` + `C_HKyxBe→DCpX7akt` · namespace `SolutionErp.Application.Office` · ~25K token
|
||||
· **Run #237** `79a8343` 3m50s push `48a99e1..79a8343` 3 commit/18 file (`0e191de` Task 4+6 · `9616ae2` Task 5 FE ×2 · `79a8343` MEMORY 3 agent) · `EmployeesController` + `EmployeeDtos` + `EmployeeFeatures` · `/api/employees` GET list + paged `page=1&pageSize=5` · bundle `BUTKoqRP→CqGMUMOr` + `CMHv2GS4→C_HKyxBe` · key `Hrm` + `Hrm_HoSo` · mẫu `NV/2026/0007` (BOD 1 — Director, Ban Giám đốc, hireDate 2021-08-01) · ~30K token
|
||||
· **Run #350** `48a99e1` 3m38s push `5400983..48a99e1` 4 commit/34 file (`1bc6b70` docs 3 MD · `b3444a3` 3 MEMORY · `0605f19` Plan C +4 test file +9 `[Fact]`/Theory · `48a99e1` Mig 34 + 17 entity/config new + 7 modified) · test_infra **62/62** = 53 + 9 BW (BW1 happy Cấp 1→2 · BW2 terminal `DaPhatHanh` + gen mã · BW3 skipToFinal F2 admin opt-in · BW4-BW7 xem mục guard) · build_be **+6555 LOC** · Mig 34 `20260526110207_AddEmployeeProfiles` · `SeedDemoEmployeeProfilesAsync` · EmployeeProfiles **33** (16+14+3) · `NV/2026/0001-0003` `EmployeeStatus=1` Active `Nationality="Việt Nam"` · ~30K token
|
||||
· **S33 startup:** HEAD=`5400983` · 5 Run: #235 `1e1c9a2` 3m38s / #234 `b223466` 3m52s / #233 `e199603` / #232 `38f1c4d` / #231 `3e92584` · skip range `b832f43..5400983` · smoke `api/health/live` 0.23s + admin 0.29s + eoffice 0.31s · sqlcmd `ssh vietreport-vps "powershell ... '.\\SQLEXPRESS' -E"` · Mig TOP 5: `AddContractLevelOpinions`(33) → `AddApprovalWorkflowToContract`(32) → `RefactorSkipToFinalToApproverLevel`(31) → `AddAllowApproverEditBudgetToLevels`(30) → `RefactorAdvancedOptionsToPerLevelAndDrafterUser`(29) · cert `api.solutions.com.vn` notAfter `Jul 23 01:58:16 2026 GMT` · bundle baseline `index-BUTKoqRP.js` / `index-CMHv2GS4.js` · ~12K token
|
||||
· **S32 wrap:** agent id `a505a02d84fc1fabe` · MEMORY 27→24.2KB · 5 commit docs-only · cert `2026-07-23` (openssl s_client) · Plan G 11 module backlog · BW1-BW7 spec ready · 3 trigger chờ S33 (a) G-H1 Hồ sơ NS · (b) BW test bundle · (c) UAT smoke V2 contract · ~3K token · Tag `[wrap, phase-9-to-phase-10, cicd]`
|
||||
· **S32 startup:** MEMORY **24.9KB / 221 lines** · `search_memory` query "Run 232 Plan B Hotfix CICD SeedSampleContractWorkflowV2" rerank **0.906** / 0.828 / 0.816 · RAG **2949 chunk** · last push `f938bf5` (S31 docs patch `cicd-monitor.md`) · ~10K token
|
||||
· **S26:** Run #222 `0bf6c7e` ~12K · bundle `C8TvDy7r→CWHIdoFo` + `BvcWrq2z→Bg2FNeIz` · smoke 5/5 200 · PE List API **9 field** · test-gate **111** · Mig 31 unchanged · Run #223-#227 = Plan AG2-AG6 · AG4 DTO +4 field `DrafterUserId`/`DrafterName`/`DepartmentId`/`DepartmentName` + 3 projection JOIN `Users`+`Departments` · `dotnet test` 111/111 local pre-push · `~150K × N wasteful`
|
||||
@ -0,0 +1,101 @@
|
||||
# cicd-monitor/2026-05-runs — gist
|
||||
distill-gen: 1
|
||||
source-verbatim: .claude/agent-memory/cicd-monitor/archive/2026-05-runs.md (6 tóm-tắt + 20 Run + 8 key-pattern → 10 cụm)
|
||||
pointer-style: substring
|
||||
|
||||
## TỔNG-HỢP (3-5 ý)
|
||||
1. **Chuỗi per-NV lookup discrimination (Plan K→P, Mig 29/30/31):** bug `FirstOrDefault` không lọc actor sống 2-3 ngày prod, "em main + Reviewer + Implementer all missed across 3 plan", checklist phình 6→8→9→10 điểm — với schema OR-of-N, mọi `FirstOrDefault(Order==)` là điểm mù (quét TOÀN kho, không chỉ handler lộ bệnh); record body Controller phải soi-gương param với Command (thiếu field ⇒ ASP.NET `default` im-lặng).
|
||||
2. **Saga Discovery #3 chốt sau 5 lần quan-sát:** Gitea xét `paths-ignore` trên TRỌN push-range — muốn loại một biến phải tìm ca VẮNG biến mà hiện-tượng vẫn còn (#202 then-chốt); kết-luận là "lỗi" nhưng "Anomaly is BENEFICIAL".
|
||||
3. **Chuỗi audit-trail S25 (#215 FAIL → #221):** phát hiện "field X không set ở N chỗ" thì vá cả N chỗ một lượt (9 site UserName); dữ-liệu cũ hỏng cứu ở FE bằng `userMap` từ dữ-liệu nhúng sẵn; Run #217 `a734bf2` = nguồn duy nhất Discovery #5 + pattern "capture pre-call mutation state".
|
||||
4. **Plan T tắt DemoSeed:** cờ chống re-seed chỉ tin sau restart THẬT (`Restart-WebAppPool` + sqlcmd đếm 0); `appsettings.Production.json` bị `.gitignore` ⇒ default phải đặt ở `appsettings.json`; chính cờ này về sau nuốt seed hạ-tầng Plan B (gotcha #51 — xem gist q2).
|
||||
5. **Lệnh đo sai trả về IM-LẶNG** (7 phát-hiện công-cụ: `/actions/tasks`≠runs · `updated_at` trễ ~2 phút · 429 ~5 req/phút → cache token · sqlcmd 4-backslash Discovery #5 · enum cần số Discovery #4 · 503 recycle lành · route `/api/auth/login` + field `accessToken`) — mỗi phép đo cần ca đối-chứng dương. MEMORY tự phình 35→72KB qua 8 nấc cảnh-báo leo thang không tự dừng ⇒ cần cổng ngoài. 🔐 dòng 48 verbatim: password DB prod ĐÃ redact bởi lead — ROTATE REQUIRED.
|
||||
|
||||
## Entries
|
||||
|
||||
> **Ghi-chú hợp-nhất nội-file:** phần (a) tóm-tắt ∩ phần (b) đầy-đủ của verbatim chồng 4 Run (#202 · #203 · #210 · #219) — cụm dưới đây chọn bản DÀY; riêng **#215 + #217 CHỈ có ở phần (a)** — ĐÃ GIỮ khi hợp-nhất (KHÔNG mất Run #217).
|
||||
|
||||
**[cao] D1 — Chuỗi per-NV lookup discrimination (Plan K → P), sinh checklist 10 điểm**
|
||||
- **VIỆC:** wire cờ opt-in per-NV (Allow* theo từng slot Approver) xuyên Mig 29/30/31, rồi vá 5 lượt liên-tiếp khi lộ ra thiếu điểm nối.
|
||||
- **KẾT-LUẬN:** #194 `098baa6` **PARTIAL** — schema + FE checkbox thứ 7 đã ship nhưng `AwLevelDto` (`ApprovalWorkflowV2AdminFeatures.cs:23-38`) **thiếu hẳn param** `AllowApproverSkipToFinal` (chỉ tồn-tại trong **comment dòng 60**) + `ToDto:156-158` thiếu ⇒ checkbox đọc/ghi vào hư-vô. #195 `0062fcb` vá 15 LOC ⇒ 13 key round-trip PASS, đúc **checklist 8 điểm**. #201 `fb3c22c` (Plan N) vá `PurchaseEvaluationFeatures.cs:765` thêm `&& l.ApproverUserId == currentUser.UserId` + fallback admin ⇒ **điểm 9**. #202 `a1c8386` (Plan O) quét ra **4 site cùng khuôn** (`...WorkflowService.cs:204` + `:258-260`, `...DetailFeatures.cs:75-76`, `...Features.cs:314-315`) + 3 test hồi-quy (111/111). #203 `1727bd5` (Plan P) vá Controller `TransitionPeBody` từ 3 → **7 param** ⇒ **điểm 10**.
|
||||
- **BÀI-HỌC:** với schema **OR-of-N** (nhiều NV cùng một Cấp), mọi `FirstOrDefault(... Order == ...)` là **điểm mù**: phải quét TOÀN kho (`grep -n "FirstOrDefault.*Order.*=="`) chứ không chỉ sửa handler lộ bệnh. Và **record body của Controller phải soi-gương số param với Command** — thiếu field thì ASP.NET **không báo lỗi**, chỉ lặng-lẽ `default`.
|
||||
- **BẤT-NGỜ:** bug sống trên prod **2-3 ngày** và **3 lượt refactor liên-tiếp (Mig 29/30/31) đều trượt** — verbatim tự khai "em main + Reviewer + Implementer **all missed across 3 plan**"; đồng-thời checklist phình **6 → 8 → 9 → 10 điểm**, tức mỗi lần "đã đủ" đều sai.
|
||||
→ substring:"098baa6"
|
||||
|
||||
**[cao] D2 — Saga Discovery #3 (`paths-ignore` xét trọn push-range)**
|
||||
*(trùng liên-file "Verify S22 chốt": bản dày = file này; cicd-monitor--2026-05-q3 B2 giữ bản mỏng — đóng-kín per-file, không xoá bên nào)*
|
||||
- **VIỆC:** truy vì sao commit docs-only vẫn kích CI (ngược gotcha #41), qua 5 lần quan-sát.
|
||||
- **KẾT-LUẬN:** giả-thuyết đầu (Discovery #2, Run #188): "`.claude/agent-memory/**` không nằm trong `paths-ignore` nên kích CI" → **BỊ BÁC** ở S22-chốt vì `**/*.md` khớp mọi độ sâu (`cc8a7d3` SKIP đúng). Chốt sau 3 lần củng-cố (#200 `f4055a1`, #201 `fb3c22c`, #202 `a1c8386` — lần 3 **0 file agent-memory** nên loại hẳn biến này): **Gitea xét paths-ignore trên TRỌN push-range**, chỉ cần 1 commit trung-gian có file không-bị-ignore là cả range chạy. #210 mixed-diff nên **không tính** là bằng-chứng.
|
||||
- **BÀI-HỌC:** muốn loại một biến thì phải tìm ca **VẮNG biến đó mà hiện-tượng vẫn còn** (#202 là ca then-chốt) — thêm ca có-biến chỉ củng-cố giả-thuyết sai. Ghi rõ ca nào **không** tính là bằng-chứng (#210) cũng quan-trọng ngang ca tính.
|
||||
- **BẤT-NGỜ:** kết-luận cuối là "lỗi" nhưng verbatim đánh giá **"Anomaly is BENEFICIAL"** — CI chạy dư lại cứu được gate verify trên commit docs; và gotcha #47 tuy bị bác vẫn **giữ giá-trị phòng-ngừa** cho file non-`.md` tương-lai.
|
||||
→ substring:"f4055a1"
|
||||
|
||||
**[cao] D3 — Chuỗi audit-trail S25 (Plan AB → AF): 5 khuôn phục-hồi lịch-sử**
|
||||
*(trùng liên-file #215: bản dày hơn ở cicd-monitor--2026-05-q3 B1; #215+#217 trong verbatim file này chỉ có ở phần tóm-tắt (a) — Run #217 = nguồn duy nhất Discovery #5 + pattern "capture pre-call mutation state", ĐÃ GIỮ)*
|
||||
- **VIỆC:** làm "Lịch sử duyệt" hiển-thị đúng + cứu dữ-liệu lịch-sử đã ghi thiếu.
|
||||
- **KẾT-LUẬN:** #215 **FAIL** (tie-break, xem q3) → #217 `a734bf2` (AC) thêm Approval-row nhánh Reject `Service:75-103` + prefix `[Duyệt vượt cấp tới Cấp cuối]` `:472`, xác-minh 6/6 approval có đủ 8 key → #218 `25837b6` (AC2) FE dựng **synthetic Reject row** từ Changelog `entityType=5` (20 entry, 8 Workflow-entry, khớp keyword "Trả về") → #219 `0aaf2df` (AD) **bỏ badge from/to** + `extractNextTargetHint()` parse `\[Bước (\d+) — Cấp (\d+)\]` → #220 `9ea62be` (AE) vá **9 site** `UserName = currentUser.FullName ?? currentUser.Email` (4 Features + 5 DetailFeatures) → #221 `506cada` (AF) FE `userMap` useMemo dựng từ 5 nguồn nhúng sẵn (drafter/approvals/levelOpinions/departmentOpinions/approvalFlow.steps), 3 nấc: tin `entry.userName` → tra map → "Hệ thống". Cumulative 7 commit, 1 catch + 6 PASS, 0 regression.
|
||||
- **BÀI-HỌC:** khi phát-hiện "field X không set ở N chỗ giống nhau", **vá cả N chỗ một lượt** thay vì chỗ báo lỗi (AE: 1 lộ + 8 phòng-xa). Dữ-liệu cũ đã hỏng thì **cứu ở FE bằng dữ-liệu đã nhúng sẵn trong bundle** — không cần đổi hợp-đồng API, né luôn ràng-buộc `/api/users` chỉ-admin.
|
||||
- **BẤT-NGỜ:** bằng-chứng chốt Bug 1 là **một dòng dữ-liệu cũ còn sót**: entry Budget Adjust `02:31:39` `entityType=1` có `userName=""` — tức prod **tự lưu giữ vật-chứng tiền-sửa** để đối-chứng sau deploy; và AD **bỏ bớt** thông-tin (badge from/to) lại làm UI đúng hơn vì phần lớn là `ChoDuyet→ChoDuyet` tự-lặp.
|
||||
→ substring:"a734bf2"
|
||||
|
||||
**[cao] D5 — Plan T tắt DemoSeed + chứng bằng restart THẬT**
|
||||
- **VIỆC:** chặn seed demo tự mọc lại trên prod (#207 `0b97840`, #208 `7b7b28f`).
|
||||
- **KẾT-LUẬN:** bọc 5 method demo trong `if (!demoSeedDisabled)` (V1 workflow ×2, demo Contract, demo PE, sample AW V2); **GIỮ** 8 nhóm seed hạ-tầng (Roles/Admin/30 user UAT/master/template/catalog/backfill). T5 xoá 4 PE + 1 V2 + 2 V1 + cascade; **T6 ép `Restart-WebAppPool SolutionErp-Api`** rồi sqlcmd: PE=0, V2=0, V1=0, master còn nguyên (Users 33 · Suppliers 19 · Projects 9 · Contracts 7). Tổng R+S+T ≈ **720 dòng** bị xoá, cờ tồn-tại vĩnh-viễn.
|
||||
- **BÀI-HỌC:** cờ chống re-seed chỉ được tin sau khi **khởi-động lại thật** rồi đếm lại — không tin vào việc đọc code. Và vì `appsettings.Production.json` bị `.gitignore`, giá-trị mặc-định phải đặt ở `appsettings.json` (thứ đi qua git) mới tới được prod.
|
||||
- **BẤT-NGỜ:** chính cái cờ chứng-minh-tốt này **về sau nuốt luôn seed hạ-tầng** của Plan B (Run #231/#232, gotcha #51) — biện-pháp đúng ở lớp này thành bẫy ở lớp khác.
|
||||
→ substring:"Restart-WebAppPool"
|
||||
|
||||
**[cao] D6 — 7 phát-hiện về CÔNG-CỤ ĐO (dễ mất nhất khi nén)**
|
||||
- **VIỆC:** gom các bài học về chính phép đo, rải rác trong 6 Run.
|
||||
- **KẾT-LUẬN:** (1) API liệt-kê Run của Gitea là `/api/v1/repos/.../actions/**tasks**`, `/actions/runs` trả **404**; đọc **không cần auth**. (2) `updated_at` trong bảng task **trễ ~2 phút** — phải đối-chiếu `LastWriteTime` của `.dll/.html` trên VPS. (3) đăng-nhập **~5 request/phút là 429** ⇒ cache token ra `%TEMP%\*_token.txt`, 2 login + 8 lệnh dùng lại (Run #188 dính 429, S22-chốt hết dính). (4) **Discovery #5:** sqlcmd Windows-auth qua ssh cần escape **4 dấu `\\\\SQLEXPRESS`** — 2 dấu ra **rỗng KHÔNG báo lỗi**. (5) **Discovery #4:** ASP.NET Core 10 record + enum đòi **số**, không có `JsonStringEnumConverter` (grep `Program.cs` = 0 hit) ⇒ payload mẫu `"Drafter"` trong brief là **sai, fail 400**. (6) 503 giữa lúc IIS recycle (~5-15s) là **bình-thường, tự khỏi**. (7) route thật là `/api/auth/login` (không `/api/v1/...`), field token là `accessToken` (không `token`); `/api/me` + `/api/menu-keys` **404 vì không tồn-tại**, không phải regression.
|
||||
- **BÀI-HỌC:** **lệnh đo sai trả về IM-LẶNG** (2-backslash ra rỗng; enum string ra 400; endpoint sai ra 404) — mỗi phép đo phải có ca đối-chứng dương, và phải phân-biệt "0 kết-quả" với "đo hỏng".
|
||||
- **BẤT-NGỜ:** brief do người ra lệnh viết lại chính là nguồn sai (payload `"Drafter"`) — và verbatim ghi thẳng "brief example payload was misleading", không đổ cho hệ-thống.
|
||||
→ substring:"JsonStringEnumConverter"
|
||||
|
||||
**[cao] D7 — MEMORY phình 35→72KB: 8 lần cảnh-báo leo thang mà không ai dừng**
|
||||
- **VIỆC:** vai tự theo-dõi kích-thước diary của chính mình sau mỗi entry.
|
||||
- **KẾT-LUẬN:** chuỗi tự khai: ~35KB ("approaching") → ~40 → ~43 ("over 25KB trigger, under 50KB hard") → ~50 ("at hard threshold") → ~58 ("**REQUIRED** ARCHIVE ... BEFORE next entry") → ~60 ("**MANDATORY**") → ~62 ("**DEDICATED CURATION SESSION PRIORITY NEXT**") → ~66→69 ("priority MAX") → ~72 ("**PRIORITY MAX REINFORCED**"). Chốt chỉ xảy ra khi có **phiên curate riêng** (chính file archive này), kéo về mục-tiêu ~25-30KB.
|
||||
- **BÀI-HỌC:** cảnh-báo do **chính kẻ đang phình** phát ra thì không tự dừng được — cần **cổng ngoài** (ngưỡng cứng chặn ghi) hoặc **việc riêng có chủ**; và ngưỡng "mềm 25KB / cứng 50KB" bị vượt **gần 3 lần** vẫn chỉ sinh thêm chữ in-đậm.
|
||||
- **BẤT-NGỜ:** mức độ khẩn-cấp trong câu chữ leo thang **8 nấc** (approaching → REQUIRED → MANDATORY → PRIORITY MAX → REINFORCED) — bản thân nó là dữ-liệu: leo-thang ngôn-từ **không** thay cho cơ-chế.
|
||||
→ substring:"PRIORITY MAX REINFORCED"
|
||||
|
||||
**[cao] D10 — Tự khai tác-dụng-phụ và giới-hạn phép đo**
|
||||
- **VIỆC:** ghi lại thứ phép kiểm-chứng làm hỏng + thứ nó KHÔNG chứng được.
|
||||
- **KẾT-LUẬN:** kiểm-chứng bằng POST thật đã **đổi state prod**: PE/2026/A/025 phase 10→98 (admin bypass qua `if (isAdmin) return` **dòng 186**), khai rõ "admin bypass là thiết-kế, không phải regression", đề bro tự chọn revert hay giữ; PE/2026/A/021 trả 409 ⇒ **không đổi state (atomic)** — có kiểm lại chứ không đoán. Giới-hạn tự khai: NV Test **không đóng vai approver** (đã quét 20 PE) nên biến-thể actor **không khả-thi**; #194 dùng chữ **"presumed" 4 lần** cho phần không đo được; #219 **bỏ qua sqlcmd** vì thiếu `PROD_DB_PASSWORD`, chấp-nhận verify gián-tiếp và nói rõ là gián-tiếp.
|
||||
- **BÀI-HỌC:** báo-cáo verify phải tách 3 mức: **đo trực-tiếp** / **suy ra** (`inferred`, `presumed`) / **không đo được** — và ghi luôn phép đo đã làm bẩn dữ-liệu.
|
||||
- **BẤT-NGỜ:** ca 409 lại là **bằng-chứng dương mạnh nhất** cho Plan O: lỗi 409 "mode mismatch" chứng-tỏ đã qua được cửa actor-check (403 cũ), tức **thất-bại đúng chỗ = thành-công**.
|
||||
→ substring:"no state change (atomic)"
|
||||
|
||||
**[vừa] D4 — Chuỗi Mig 28→31 + backfill có kiểm số**
|
||||
- **VIỆC:** 5 lượt deploy schema per-NV/per-Level.
|
||||
- **KẾT-LUẬN:** Mig 28 `AddAdvancedOptionsToApprovalWorkflows` (#186, 6 cờ Allow* mức workflow, `allowReturnToDrafter=true` mặc-định + 5 false) → Mig 29 `RefactorAdvancedOptionsToPerLevelAndDrafterUser` (#187): Levels **+5** Allow*, Users **+1**, ApprovalWorkflows **−6**; backfill **48/48** Levels=1 và **0/13** Users=1 (đúng vì nguồn `false`) → Plan D/C/E (#188): `/api/users` +`allowDrafterSkipToFinal`, PATCH admin 204 / nv.test **403**, scope V2 chặt (nv.test 8 PE < admin 17) → Mig 30 `AddAllowApproverEditBudgetToLevels` (S22-chốt): `currentLevelOptions` 6 key, `AwLevelDto` 12 key → Mig 31 `RefactorSkipToFinalToApproverLevel` (#194): Levels +1, Users −1, endpoint zombie `/allow-skip-final` trả **404** đúng ý.
|
||||
- **BÀI-HỌC:** backfill phải kiểm **cả hai chiều** — "48/48 = 1" và "**0/13** = 1" đều là kết-quả ĐÚNG cần xác-nhận; số 0 ở đây là bằng-chứng, không phải thiếu-sót. Dọn field cũ thì phải kiểm **cả endpoint lẫn key trong DTO đã biến mất**.
|
||||
- **BẤT-NGỜ:** cùng một tính-năng bị dời chỗ 3 lần (workflow → user → level) trong ~2 ngày; test-gate suy-ra từ "deploy chạy được" chứ không đọc trực-tiếp (`test gate inferred PASS`).
|
||||
→ substring:"AddAdvancedOptionsToApprovalWorkflows"
|
||||
|
||||
**[thấp] D8 — Đánh-bóng UX + 1 ca revert trong phiên**
|
||||
- **VIỆC:** 4 Run FE thuần: #204 banner `mx-5`→`mb-3`; #209 sidebar `truncate` (`min-w-0 flex-1` + `shrink-0` + `title=` tooltip); #210 widen `w-60 → w-72 xl:w-80` **kèm revert Plan U**; #214 wrap nhãn dài.
|
||||
- **KẾT-LUẬN:** 4/4 PASS, bundle rotate đúng kỳ vọng mỗi lần; menu `Pe_DuyetNcc_WfView` Order=2 + họ `Pe_DuyetNccPhuongAn` Order 6-10, tổng **1-10 liền mạch không hở/không chồng** (seed idempotent).
|
||||
- **BÀI-HỌC:** đổi FE-thuần vẫn verify được gián-tiếp bằng **bundle-hash làm proxy**; nhưng khi một chunk bị revert trong cùng phiên thì hash phản-ánh **trạng-thái tích-luỹ**, không phải diff của chunk ⇒ đừng đọc hash như chữ-ký của một thay-đổi đơn lẻ.
|
||||
- **BẤT-NGỜ:** phép đo bundle của Plan P **bắt nhầm** thay-đổi của Run #204 đang bay cùng lúc (`QZIPWD-g`/`DaLTMGcx`) — vẫn kết-luận đúng nhờ đối-chiếu **diff có 0 file FE**, tức dùng nguồn thứ hai để cứu phép đo bị nhiễu.
|
||||
→ substring:"ac2c859"
|
||||
|
||||
**[cao] D9 — "Key patterns extracted S21-S25" (giữ gần như nguyên)**
|
||||
- **VIỆC:** phần (c) đã là bản chưng-cất 8 mục do chính vai viết.
|
||||
- **KẾT-LUẬN:** 8 mục = Discovery #3 · Discovery #4 · Discovery #5 · **checklist 10 điểm per-NV** (liệt-kê đủ 10) · gotcha #48 + cách vá · "CI test-gate bắt được lỗi mà UAT-skip-test bỏ sót" · FE merge synthetic + userMap · capture pre-call mutation state.
|
||||
- **BÀI-HỌC:** khi nguồn đã có lớp chưng-cất, việc nén nên **giữ lớp đó gần như nguyên văn** và cắt ở lớp Run bên dưới — nén lại cái đã nén là chỗ dễ mất mã-số (#48, điểm 9/10) nhất.
|
||||
- **BẤT-NGỜ:** checklist 10 điểm ở đây là **bản duy nhất liệt-kê đủ 10** trong toàn batch A — mọi chỗ khác chỉ nhắc số "8"/"9"/"10 surface points" mà không kèm nội-dung.
|
||||
→ substring:"Key patterns extracted"
|
||||
|
||||
## Phụ-lục token (coverage)
|
||||
|
||||
- **gotcha#:** `#41` (path filter — dẫn ≥6 lần) · `#44` (silent 403 — "fix confirmed" ở #210) · `#45` (fix Trả lại, Run #186) · `#47` (agent-memory — **disproven**, giữ vai PREVENTIVE) · `#48` (**NEW** multi-`Changelog.Add` + SQLite frozen-clock tie-break)
|
||||
- **Discovery#:** `#1` rate-limit 429 ~5 req/phút · `#2` `.claude/agent-memory/**` không nằm trong paths-ignore (**về sau bị bác**) · `#3` Gitea xét TRỌN push-range (củng-cố 4 lần: #200/#201/#202 + #210 "NOT applicable") · `#4` ASP.NET Core 10 record+enum cần **số** · `#5` sqlcmd cần `\\\\SQLEXPRESS` **4 backslash** · *(không đánh số)* `/actions/tasks` ≠ `/actions/runs` (#186) · `updated_at` trễ ~2 phút (#187) · "Discovery 1" 503 giữa deploy + "Discovery 2" `/api/auth/login` + field `accessToken` (#221)
|
||||
- **surprise:** `CRITICAL FAIL — K3 DTO mirror INCOMPLETE` · "`AllowApproverSkipToFinal` mentioned **only in comment line 60**, NOT as actual record param" · "**em main + Reviewer + Implementer all missed across 3 plan**" · "Bug present **2 days prod**" / "**3-day prod bug latency**" · "Spec hypothesis ... **disproven for this commit**" · "**Anomaly is BENEFICIAL** — catches verify gate even on docs-only tip" · "PROVES ACTOR VALIDATION SUCCESS ... 409 mode mismatch NOT 403" · "First attempt with **string enum** `\"Drafter\"` → HTTP 400 ... Brief example payload was **misleading**" · "Test 2 HTTP 409 = handler threw exception → **no state change (atomic)**" · "NV Test actor variant **not feasible** (scanned 20 PEs)" · "the Budget Adjust entry ... has `userName=\"\"` empty — this is the **EXACT pre-fix Bug 1 evidence**" · "**Side effect logged:** Admin transition on PE/2026/A/025 mutated phase 10→98" · "Run #190 ... **CANCELLED** by concurrency ... normal not a fail" · "`/api/me` + `/api/menu-keys` returned 404 (route names different) — **Not regression**" · "Memory size ... **PRIORITY MAX REINFORCED**" · "8 surface points checklist (**NOT 6**)" · "prophylactic codebase scan is needed"
|
||||
- **guard:** `if (isAdmin) return` (**line 186**, admin bypass CỐ Ý) · `if (!isAdmin && currentLevel is not null)` (**line 264**) · `&& l.ApproverUserId == actorId` + `throw if null` (4 site Plan O) · fallback `?? curStep?.Levels.FirstOrDefault(...)` cho admin/non-approver (Plan N) · `[Authorize]` class-level + `GetWorkflowsForUserAsync` auth filter (fix #44) · `if (!demoSeedDisabled)` bọc 5 seed demo (Plan T) · `.Where(c => c.Summary!.Contains("Chuyển phase"))` (vá #48) · `HasDefaultValue(false)` (điểm 2 checklist) · **Migration 3-file rule** (Up + Designer + Snapshot — điểm 3) · `[FromBody]` record param count = Command param count (điểm 10) · seed Order shift **idempotent** (1-10 liền mạch) · token-cache `%TEMP%\*_token.txt` chống 429 · test-gate CI chặn trước deploy
|
||||
- **root-cause:** `FirstOrDefault(l => l.Order == curLevelOrder)` **không lọc actor** ⇒ luôn lấy `Levels[0]` theo thứ-tự DB ⇒ cờ per-NV admin tick bị bỏ qua (4 site cùng bệnh) · Controller `TransitionPeBody` 3 field ⟂ Command 7 field ⇒ ASP.NET `default` **im-lặng** 3 field ⇒ F1/F2 không tới handler suốt 2 ngày · `AwLevelDto` thiếu param + `ToDto` thiếu ctor-arg ⇒ cột DB có mà API không trả · nhiều `Changelog.Add()` cùng `SaveChangesAsync` + SQLite frozen-clock ⇒ `OrderByDescending(CreatedAt)` tie-break bất-định · Gitea xét paths-ignore trên trọn push-range ⇒ tip docs-only vẫn chạy · thiếu `JsonStringEnumConverter` ⇒ enum dạng chuỗi fail 400 · `\\SQLEXPRESS` (2 backslash) ⇒ **0 output, im-lặng** · `UserName` không set ở 9 site `Changelog.Add()` ⇒ cột người-dùng rỗng
|
||||
- **file:line / định-danh (BE):** `ApprovalWorkflowV2AdminFeatures.cs` **:23-38** (`AwLevelDto` record), **comment :60**, `ToDto` **:156-158**, vùng **:184-238** · `PurchaseEvaluationFeatures.cs` **:765** (per-NV lookup), **:314-315** (AdjustBudget) · `PurchaseEvaluationWorkflowService.cs` **:204** (`EnsureCanRejectV2Async`), **:258-260** (`ApplyReturnModeAsync`), **:264** (guard), **:186** (admin bypass), **:75-103** (nhánh Reject Plan AC), **:472** (prefix `[Duyệt vượt cấp tới Cấp cuối]`), **:287-333** (F1 edge Bước 1), **:268-275** (F1.Drafter giữ TraLai) · `PurchaseEvaluationDetailFeatures.cs` **:75-76** (`EnsureEditableForDetailsAsync`) · Controller `TransitionPeBody` **:267** (3 field) → **:280-286** (7 param), `mediator.Send` **:76-78**, marker Plan P **:71-75** · `TransitionPurchaseEvaluationCommand` **:395** · `Program.cs` (0 hit `JsonStringEnumConverter`/`AddJsonOptions`) · `DbInitializer.cs` · `MenuKeys.cs` · `appsettings.json` / `appsettings.Development.json` / `appsettings.Production.json` (**bị `.gitignore`**)
|
||||
- **file:line / định-danh (test + FE):** test `ApplyReturnMode_OneStep_AtStep1` **line 350** · `ApplyReturnMode_OneLevel_AtStep1Level1` **line 308** · `GetPurchaseEvaluationCurrentLevelOptionsTests.cs` (2 method) · `PurchaseEvaluationPerNvLookupRegressionTests.cs` (3 test) · `PeDetailTabs.tsx` (fe-user + fe-admin, mirror **§3.9**) · `PeWorkflowPanel.tsx` · `types/purchaseEvaluation.ts` (`WorkflowReturnMode = { OneLevel:1, OneStep:2, Assignee:3, Drafter:4 }`) · `Layout.tsx` (`w-60 → w-72 xl:w-80`; `min-w-0 flex-1` + `shrink-0` + `truncate` + `title=`) · `WorkflowMatrixViewPage.tsx` · `approvalWorkflowV2.ts` · `App.tsx` · helper `extractNextTargetHint(comment, decision)` · regex `\[Bước (\d+) — Cấp (\d+)\]` · `resolveActorName`/`resolveUserName` + `userMap` useMemo
|
||||
- **Run / sha / bundle:** #186 `eea86fd` (id=300) · #187 `c0af9e0` (301) · #188 `a74e671` (302) · #189 `40f64c6` · #190 `8185070` **CANCELLED** · #191 `0e70789` · #192 `30d51c8` · #193 `b04a11a` (307) · #194 `098baa6` (308) **PARTIAL** · #195 `0062fcb` (309) · #199 `da30e27` · #200 `f4055a1` (314) · #201 `fb3c22c` (315) · #202 `a1c8386` (316) · #203 `1727bd5` (317) · #204 `108268a` · #207 `0b97840` · #208 `7b7b28f` · #209 `86d8806` · #210 `ac2c859` (324) · #214 `ee0902a` (328) · #217 `a734bf2` · #218 `25837b6` (332) · #219 `0aaf2df` (333) · #220 `9ea62be` (334) · #221 `506cada` (335) · commit lẻ: `c2042ef`/`4dd6f9c`/`508b17a` (Plan M) · `0326458` (N1+N2) · `ae01ca5` (O1-O5) · `ee776d5`/`c667802` (AA) · `b079b27` (BE Mig 30) · `cc8a7d3` (SKIP) · `3d725c4..cc8a7d3` · `e23f51c..506cada` · bundle-chain admin `CzesdXLh→D5l49-70→Cclc8Uwu→CpI5OL8n→CRsX6cFo→D_JENTBi→QZIPWD-g→Dmk--X6w→CZdXQ2eo→CrHpBYFM→B5iZMa7g→CDVnRDe6→DR95zKWg→C8TvDy7r`; user `DP-gH4LW→B6N5hq3d→d064StNa→X7qb4Zl4→COJhbRxy→DaLTMGcx→Bd4gh3Tp→DCwhhey2→C3bCWZ90→CHJDH3M2→gAFN3NVx→BAj_Yaj5→BvcWrq2z`
|
||||
- **Mig / dữ-liệu prod:** Mig 28 `20260513114505_AddAdvancedOptionsToApprovalWorkflows` · Mig 29 `RefactorAdvancedOptionsToPerLevelAndDrafterUser` (backfill **48/48** Levels · **0/13** Users) · Mig 30 `20260513160703_AddAllowApproverEditBudgetToLevels` · Mig 31 `20260514160124_RefactorSkipToFinalToApproverLevel` · test-count chuỗi **84 → 103 → 104 → 106 → 108 → 111** · Users **33** · Suppliers 19 · Projects 9 · Contracts 7 · PE-ID `3248f2f9-c6e9-43ff-a4ca-067ffecf9f36` (PE/2026/A/032) · `98736f06-...` (A/025) · `59176ad5-...` (A/027) · `6148ba7a-...` (A/026) · `f9476dad-...` (A/021) · workflow `bf31f120` · token len admin **468** / nv.test **477** · `~720 rows wiped`
|
||||
- **🔐 secret (đã xử-lý):** dòng **48** verbatim — `appsettings.Production.json` tại `C:\inetpub\solution-erp\api\`, `user=vrapp`, password prod **ĐÃ ĐƯỢC LEAD REDACT** thành nhãn `pwd=[đã che 71b7740a — ROTATE REQUIRED]` — gist chép NHÃN CHE này, TUYỆT ĐỐI không tra/chép giá-trị gốc từ git history; **ROTATE REQUIRED** vẫn treo (mật-khẩu từng nằm trong lịch-sử git)
|
||||
- **meta / size-trend:** `~35KB → ~40 → ~43 → ~50 → ~58 → ~60 → ~62 → ~66 → ~69 → ~72KB` · ngưỡng mềm **25KB** / cứng **50KB** · FIFO `~8 → ~9 → ~10 → ~11 → ~12 → ~13 entry` · token-cost mỗi lượt `~10k-28k` · `feedback_md_compact_narrative.md` (§6.5) · `feedback_uat_skip_verify` · `feedback_per_nv_permission_scope.md`
|
||||
@ -0,0 +1,59 @@
|
||||
# implementer-backend/2026-05-q1 — gist
|
||||
distill-gen: 1
|
||||
source-verbatim: .claude/agent-memory/implementer-backend/archive/2026-05-q1.md (12 entry → 5 cụm)
|
||||
pointer-style: substring
|
||||
|
||||
## TỔNG-HỢP (3-5 ý)
|
||||
1. **Tỉ-lệ TỪ-CHỐI gần tuyệt-đối cả quý** (S21 t3-t5: 3/3 · S22: 100% · S23 t4→t11: 8 plan liên-tiếp 0 spawn · S24: 4/4) theo 7 tiêu-chí phạm-vi — verbatim tự đối-chiếu khớp cảnh-báo Anthropic "tightly interdependent coding"; ranh-giới đo bằng độ ràng-buộc chéo lớp (tiêu-chí #3/#4), không bằng kích-thước diff, và mỗi lần từ-chối ghi kèm tiêu-chí thì tỉ-lệ mới đọc được về sau.
|
||||
2. **Chuỗi K Mig 31 (pre-A → K1 → K3 → K5 → K7):** refactor đổi chỗ lưu cờ Users→Levels có chunk dọn-rác riêng (K5) + grep-về-ZERO 5 biến-thể tên + test 3 xoá/3 thêm triệt-tiêu giữ baseline 104; mất dữ-liệu prod CÓ CHỦ-ĐÍCH (Option A không backfill, khai đích danh 4 tài-khoản); nợ kỹ-thuật được ĐẶT LỊCH (test vỡ để lại cho K7), không bỏ quên.
|
||||
3. **Mục "Mismatches discovered" tự khai 4 chỗ đoán sai** (per-NV mặc-định vs cờ admin bật riêng → Mig 30 · `PE.changelogs` vắng ⇒ TS2339 · Dialog `size="xl"` không tồn-tại · `accessToken` ≠ `token` ⇒ 401) — giả-định hình-dạng API/thư-viện phải kiểm bằng đọc, không bằng trí nhớ; bẫy `accessToken` cắn 2 vai khác nhau (tái-xuất ở cicd-monitor Run #221).
|
||||
4. **3 pattern FE mới 13/14/15 từ Plan AA** (read-only mirror Designer · Tailwind JIT full-class-string · rowSpan flat-row builder); shadcn fe-user là subset thiếu hẳn `Card`/`Badge` — 2 app tưởng song sinh nhưng kho UI lệch nhau.
|
||||
|
||||
## Entries
|
||||
|
||||
**[cao] F1 — Nhật-ký TỪ-CHỐI: vai này ghi cả việc mình KHÔNG làm**
|
||||
- **VIỆC:** phân-loại việc được giao theo 7 tiêu-chí phạm-vi, trả lại việc không hợp.
|
||||
- **KẾT-LUẬN:** S21 t3-t5 **từ-chối 3/3**; S22 **từ-chối 100%**; S23 t4→t11 **8 plan liên-tiếp em-main tự làm, 0 lần spawn** (N+O 5 site lookup · P Controller ~6 LOC · Q CSS 8 LOC · R+S+T5 xoá dữ-liệu prod ~720 dòng qua 4 file `.sql` · T cờ DemoSeed ~25 LOC · U sidebar 25 LOC); S24 **4/4 từ-chối đúng**, mỗi ca ghi rõ commit + tiêu-chí (`da218f1` #6 vặt <30 phút · `4d60598` #2 luồng UX · `fbbd361` #2 · `ee0902a` #6). Lý-do chính: tiêu-chí **#3** (xuyên >2 lớp) + **#4** (chuỗi suy-luận sửa lỗi).
|
||||
- **BÀI-HỌC:** ranh-giới "được giao" phải quyết theo **độ ràng-buộc chéo lớp**, không theo kích-thước diff — và mỗi lần từ-chối phải **ghi lại kèm tiêu-chí** thì tỉ-lệ mới đọc được về sau.
|
||||
- **BẤT-NGỜ:** cả một quý mà tỉ-lệ từ-chối gần như tuyệt-đối; verbatim tự đối-chiếu: "REFUSE rate 100% **match Anthropic warning 'tightly interdependent coding'**" — tức vai tự xác-nhận giới-hạn của chính mô-hình chia việc, chứ không cố nhận cho có.
|
||||
→ substring:"tightly interdependent coding"
|
||||
|
||||
**[cao] F2 — Chuỗi K Mig 31: đổi chỗ lưu cờ F2 từ `Users` sang `ApprovalWorkflowLevels`**
|
||||
- **VIỆC:** 5 chunk nối tiếp trong S23 t1 (pre-A → K1 → K3 → K5 → K7).
|
||||
- **KẾT-LUẬN:** **pre-A** đổi nhãn slot Designer (`ApprovalWorkflowsV2Page.tsx:873`, tra `fullName` từ `usersList` đã có sẵn ~`:500`, khuôn `.find` đã dùng ở `:535`). **K1** Mig 31 ADD-DROP **không BACKFILL** (Option A) — 6 file BE, cắt **-37 LOC** nhánh Drafter SUBMIT (`PurchaseEvaluationWorkflowService.cs:121-157`), 4 chỗ vỡ biên-dịch vá bằng sentinel `false` + marker K2, giữ tham-số thứ 8 `bool skipToFinal` để K2 dùng lại. **K3** checkbox thứ 7 = 7 tiểu-mục nguyên-tử (type → default → propagate `?? false` → vị-trí cuối nhóm → viết lại banner `~:623` → body mutation). **K5** dọn zombie: xoá endpoint `PATCH /allow-skip-final`, xoá field DTO/Command/Handler, viết lại 6 chỗ chú-thích lỗi-thời, xoá cột "Skip cuối" FE — rồi **grep 5 từ-khoá ra ZERO** toàn `src/Backend` + `fe-admin/src` (+42/-94 LOC / 9 file). **K7** xoá 3 test Drafter đã hết nghĩa + thêm 3 test Approver, helper mới `SeedApproverF2WorkflowAsync` (2 Bước × 2 Cấp để chứng skip **không** rơi sang Bước kế) ⇒ **104 PASS, baseline không đổi vì 3 xoá + 3 thêm triệt tiêu nhau**.
|
||||
- **BÀI-HỌC:** refactor đổi chỗ lưu-trữ phải có **chunk dọn rác riêng** (K5) và kết bằng **grep-về-0 trên nhiều biến-thể tên** (PascalCase / kebab / camel / nhãn tiếng Việt / tên icon) — không thì cờ chết vẫn còn dấu vết khắp nơi. Test cũng phải **xoá thứ đã hết nghĩa**, đừng chỉ thêm.
|
||||
- **BẤT-NGỜ:** (i) chấp-nhận **mất dữ-liệu prod có chủ-đích** — Option A không backfill, khai đích danh 4 tài-khoản mất giá-trị (`fin.pp`, `pm.nv`, `nv.test`, `truong.nguyen`); (ii) `UserConfiguration.cs` **không tồn-tại** — `User` được cấu-hình inline trong `ApplicationDbContext.OnModelCreating` ~`:86` nên không có `HasDefaultValue`, EF vẫn tự nhận đổi prop; (iii) test hỏng do K1 được **cố-ý để lại** cho K7 (`...ReturnModeTests.cs:253`), tức nợ kỹ-thuật được **đặt lịch** chứ không bỏ quên.
|
||||
→ substring:"SeedApproverF2WorkflowAsync"
|
||||
|
||||
**[cao] F3 — Bốn "Mismatch" tự khai ở S22**
|
||||
- **VIỆC:** ghi lại 4 chỗ hiểu sai giữa ý bro và cách vai định làm.
|
||||
- **KẾT-LUẬN:** (1) "người điều chỉnh cũng là người duyệt" — vai định cho **mặc-định** Approver luôn được phép, bro chỉnh thành **cờ admin bật riêng** (thành Mig 30); (2) `PE.changelogs` **không có** trong `PeDetailBundle` ⇒ dựng lịch-sử trong `BudgetAdjustSection` **fail TS2339**, phải bỏ và hoãn sang S23 bằng endpoint fetch riêng; (3) `Dialog size="xl"` **không tồn-tại** (chỉ `sm|md|lg`) ⇒ dùng `lg` cho iframe xem trước; (4) field đăng-nhập là **`accessToken`**, không phải `token` ⇒ `seed-test-users-prod.ps1` **401** ở lần đầu, sửa thành `$authResp.accessToken`.
|
||||
- **BÀI-HỌC:** phải **chốt "mặc-định vs admin bật riêng" TRƯỚC** khi mở rộng phạm-vi quyền — đây là loại nhầm không lộ ra ở compile mà lộ ở nghiệm-thu. Ba cái còn lại chung một gốc: **giả-định về hình-dạng API/thư-viện phải kiểm bằng đọc, không bằng trí nhớ**.
|
||||
- **BẤT-NGỜ:** vai chủ-động mở mục "Mismatches discovered" để ghi **cái mình đoán sai** — và mục này về sau là phần giá-trị nhất của entry; nhầm về `accessToken` còn tái-xuất ở cicd-monitor (Run #221) ⇒ cùng một bẫy cắn **hai vai khác nhau**.
|
||||
→ substring:"Mismatches discovered"
|
||||
|
||||
**[vừa] F4 — Plan AA: trang matrix chỉ-đọc cho fe-user**
|
||||
- **VIỆC:** dựng bản xem quy-trình V2 đã ghim cho phía user (3 file: types ~55 LOC + page ~215 LOC + route).
|
||||
- **KẾT-LUẬN:** PASS — lọc **ở BE** bằng `?isUserSelectable=true` thay vì lọc ở FE; bảng 10 cột (3 meta + 7 cờ Allow*), rowSpan theo Bước, đủ 3 trạng-thái Loading/Error/Empty, route đặt **trước** `/workspace`. Đúc 3 pattern mới: **13** (mirror trang Designer ở dạng chỉ-đọc = bỏ mutation + tái-dùng DTO dạng subset), **14** (Tailwind JIT phải viết **chuỗi class đầy đủ** trong mảng), **15** (helper dựng hàng phẳng cho rowSpan).
|
||||
- **BÀI-HỌC:** làm bản chỉ-đọc thì **đẩy điều-kiện lọc về BE** — vừa gọn FE vừa tránh lộ dữ-liệu không được xem. Và phải **kiểm kho `components/ui/` của đúng app TRƯỚC khi import**.
|
||||
- **BẤT-NGỜ:** `shadcn` của **fe-user thiếu hẳn `Card`/`Badge`** (chỉ có 6 thành-phần) — 2 app tưởng song sinh nhưng kho UI lệch nhau, phải nội-suy bằng `<div>`/`<span>` thuần cho khớp mắt với Designer bên admin.
|
||||
→ substring:"WorkflowMatrixViewPage"
|
||||
|
||||
**[vừa] F5 — Plan M: mở-rộng helper + đổi nhãn có kỷ-luật**
|
||||
- **VIỆC:** M2 thêm 2 test ca-biên "Bước 1 không lùi được"; M3 đổi nhãn hiển-thị Phase 98 "Trả lại" → "Cần chỉnh sửa lại".
|
||||
- **KẾT-LUẬN:** M2 **mở-rộng** `SeedWorkflowAsync` thêm 2 tham-số tuỳ-chọn mặc-định `false` (**không clone helper**, 4 test cũ không vỡ) ⇒ **106/106**; khẳng-định assert đúng chỗ: `Summary` là chuỗi cố-định `"Chuyển phase {from} → {to}"` nên câu "không lùi được" phải tìm ở **`ContextNote`**. M3 đổi đúng **8 dòng / 4 file × 2 app**, và liệt-kê rõ **4 nhóm KHÔNG đụng** (nút hành-động `← Trả lại` là **động-từ**, nhãn 4 mode, chú-thích lý-do, `types/contracts.ts` + `types/budget.ts` ngoài phạm-vi).
|
||||
- **BÀI-HỌC:** phân-biệt **"nhãn trạng-thái" ⟂ "động-từ hành-động"** khi đổi chữ — cùng một chuỗi "Trả lại" mà một chỗ phải đổi, một chỗ không. Danh-sách **KHÔNG-đụng** có giá-trị ngang danh-sách đã-đụng.
|
||||
- **BẤT-NGỜ:** vai tự đặt **ngưỡng chống-nghịch: "anti-fiddle <20% LOC"** và tự biện-hộ vì sao 2 chuỗi inline ngoài spec vẫn được sửa (cùng ngữ-nghĩa "tham-chiếu trạng-thái") — tự ràng mình bằng con-số trước khi bị hỏi.
|
||||
→ substring:"Cần chỉnh sửa lại"
|
||||
|
||||
## Phụ-lục token (coverage)
|
||||
|
||||
- **gotcha#:** `#45` (bug Trả lại — lý-do REFUSE) · `#44` (Authorize policy — Pattern 10, 5 test ~50 LOC) · "45 gotcha" (mốc S21) / "46 gotchas unchanged" (mốc S22)
|
||||
- **criteria# (7 tiêu-chí phạm-vi):** `#2` luồng UX · `#3` xuyên >2 lớp · `#4` chuỗi suy-luận sửa lỗi · `#6` vặt <30 phút
|
||||
- **Pattern#:** `2` EF migration 3-file rule · `3` audit-reuse **EXTEND không clone helper** · `5` mirror 2 app strict · `7` per-NV admin opt-in · `8` tách endpoint narrow scope · `9` defense-in-depth FE+BE guard pair · `10` Reflection-based Authorize regression · `11` test-infra helper cookie-cutter · `12` `InternalsVisibleTo` · **`13`** read-only mirror Designer (MỚI) · **`14`** Tailwind JIT full class string (MỚI) · **`15`** rowSpan flat row builder (MỚI) · `16-18` (S25 wrap, chỉ nhắc ở header) · `19` (S26 Plan AG, chỉ nhắc ở header)
|
||||
- **surprise:** "**Surprise:** shadcn fe-user KHÔNG có `Card`/`Badge`" · "`UserConfiguration.cs` **file không tồn tại** — User entity configured inline `ApplicationDbContext.OnModelCreating` ~line 86" · "Dialog `size=\"xl\"` **NOT supported**" · "API auth field `accessToken` **không phải** `token` ... FAIL 401" · "`PE.changelogs` **KHÔNG có** trong PeDetailBundle ... build **FAIL TS2339**" · "accept **lose 4 prod user value** `fin.pp` + `pm.nv` + `nv.test` + `truong.nguyen`" · "3 deleted + 3 added **cancel out**, baseline preserved" · "REFUSE rate 100% **match Anthropic warning 'tightly interdependent coding'**" · "Grep ... **ZERO results**" · "Test compile error ... **left for K7 chunk** (spec exclude test scope)" · "**Anti-fiddle threshold <20% LOC respected**" · "**Cookie-cutter 0 (lần đầu pattern)**" · "2 pre-existing **DocxRenderer** warn" (lặp 3 lần)
|
||||
- **guard:** `HasDefaultValue(false)` (`ApprovalWorkflowConfiguration.cs`) · sentinel `false` + marker comment K2 tại 4 site vỡ biên-dịch · tham-số tuỳ-chọn mặc-định `false` giữ tương-thích 4 test cũ · grep-về-ZERO 5 biến-thể (`AllowDrafterSkipToFinal` · `allow-skip-final` · `allowDrafterSkipToFinal` · `Skip cuối` · `FastForward`) · `InternalsVisibleTo` (Pattern 12) · guard đôi FE+BE (Pattern 9) · `dotnet build` 0 err + `npm run build` 0 TS err trước khi khai PASS · ngưỡng **anti-fiddle <20% LOC** · route đặt **trước** `/workspace`
|
||||
- **root-cause:** `PeDetailBundle` không phơi `changelogs` ⇒ TS2339 khi dựng lịch-sử tại chỗ · Mig 31 chuyển chỗ lưu ⇒ 4 site Application vỡ biên-dịch + 1 test tham-chiếu prop đã xoá (`:253`) · `EditLevelEntry` không có `approverFullName` (chỉ `LevelDto` BE có `approverUserName`) ⇒ phải tra từ `usersList` · shadcn fe-user là **subset tối-thiểu** ⇒ thiếu Card/Badge · `Summary` là chuỗi cố-định ⇒ nội-dung động phải nằm ở `ContextNote` (`service:96-99` → `LogTransition`)
|
||||
- **file:line / định-danh (BE):** `ApprovalWorkflow.cs` (+`AllowApproverSkipToFinal`; narrative **:78-80**; docstring **:108**) · `User.cs` (−1 prop) · `ApprovalWorkflowConfiguration.cs` (**:22-24** narrative, **:87** storage-move comment) · `PurchaseEvaluationWorkflowService.cs` **:121-157** (−37 LOC), **:287-333** (F1 edge), **:96-99** (comment → ContextNote) · `UserFeatures.cs` · `SetUserAllowDrafterSkipToFinalCommand(+Handler)` · `UsersController.cs` + `SetAllowDrafterSkipToFinalBody` · `PurchaseEvaluationFeatures.cs` **:401** · `ApprovalWorkflowV2AdminFeatures.cs` **:58** · `IPurchaseEvaluationWorkflowService.cs` · `PurchaseEvaluationDtos.cs` · `ApplicationDbContext.OnModelCreating` **~:86** · `DbInitializer.cs` (cờ DemoSeed ~25 LOC)
|
||||
- **file:line / định-danh (test + FE):** `PurchaseEvaluationWorkflowServiceReturnModeTests.cs` **:253** (vỡ, để cho K7), **:241** (chèn 2 `[Fact]` mới) · helper `SeedWorkflowAsync` / `SeedApproversAsync` / **`SeedApproverF2WorkflowAsync`** (2 Bước × 2 Cấp) / `BuildPeAtLevel2` · test xoá: `SkipToFinal_DrafterAllowed_SetsPointerToFinalLevel` · `SkipToFinal_DrafterDenied_NonAdmin_Throws` · `SkipToFinal_AdminBypass_Succeeds` · test thêm: `ApproveV2_SkipToFinal_AdminTickFlag_SetsPhaseDaDuyet` · `ApproveV2_SkipToFinal_FlagOff_NonAdmin_ThrowsConflictException` · `ApproveV2_SkipToFinal_FlagOff_Admin_BypassesFlagCheck` · `ApplyReturnMode_OneLevel_AtStep1Level1_ResetsToBuoc1Cap1_KeepsChoDuyet` · `ApplyReturnMode_OneStep_AtStep1_ResetsToBuoc1Cap1_KeepsChoDuyet` · `ApprovalWorkflowsV2Page.tsx` **:873**, **~:500**, **:535**, banner **~:623**, stale **:75** + **:504** (hoãn K6) · `UsersPage.tsx` + `types/users.ts` · `types/purchaseEvaluation.ts` (`PurchaseEvaluationPhaseLabel[98]`, `PeDisplayStatusLabel.TraLai`) · `PeWorkflowPanel.tsx` · `fe-user/src/types/approvalWorkflowV2.ts` (~55 LOC, 5 record type `AwLevelDto`/`AwStepDto`/`AwDefinitionDto`/`AwTypeSummaryDto`/`AwAdminOverviewDto`) · `fe-user/src/pages/pe/WorkflowMatrixViewPage.tsx` (~215 LOC) · `fe-user/src/App.tsx` route `/purchase-evaluations/workflow-matrix` · `seed-test-users-prod.ps1` → `$authResp.accessToken` · `scripts/plan-r-*.sql` + `plan-s-*.sql` + `plan-t5-*.sql` + `plan-t-backup.sql`
|
||||
- **số-đo:** test **84 → 104 → 106** · mig **29 → 30 → 31** · endpoint ~**146** (+3) · user prod **33** (13 cũ + 20 mới) · diff K5 **+42/-94** (9 file) · K7 **+175/-92** (1 file) · K3 **+26/-7** · M2 **+94** · M3 **+4/-4 ×2 = 8 LOC** · bundle fe-admin **1395 / 1395.74 KB** · fe-user **1275 / 1282 KB** (1907 module, 2.61s) · commit S24 **7** (`a1a910f..ee0902a`, 1 spawn + 6 solo) · token/chunk ~5k-14k · MEMORY nguồn **38.8 KB** · `~720 rows wiped`
|
||||
@ -0,0 +1,59 @@
|
||||
# implementer-backend/2026-05-q2 — gist
|
||||
distill-gen: 1
|
||||
source-verbatim: .claude/agent-memory/implementer-backend/archive/2026-05-q2.md (5 entry → 5 cụm)
|
||||
pointer-style: substring
|
||||
|
||||
## TỔNG-HỢP (3-5 ý)
|
||||
1. **Mirror phải chứng bằng máy, không bằng mắt:** SHA256 sau khi ghi (AG `21001E90…`; Plan CA 4 hash) hoặc `git diff --stat` byte-similar; Pattern 5 (mirror 2 app §3.9) tự đếm tích-luỹ **7th → 8th → 9th** — kinh-nghiệm biến thành con-số kiểm được.
|
||||
2. **Gộp N nhánh log về MỘT đường chung** (Plan AB `cdfd542`: gỡ `return` sớm nhánh Drafter → 1 `Changelog.Add()` duy nhất cuối phủ cả 4 mode); FE lọc bằng **substring của summary** (động-từ tiếng Việt) thay enum cứng ⇒ thêm luật lọc mới không cần migrate BE.
|
||||
3. **Pattern 19 — thiếu thư-viện thì dùng thẻ HTML gốc:** `<details>/<summary>` + Tailwind named-group + `localStorage` (fe-user không có Accordion); anti-pattern nested `group` không đặt tên ⇒ con kế-thừa trạng-thái cha, cả hai chevron xoay cùng lúc.
|
||||
4. **VA-CHẠM số-hiệu Pattern 16** (2 nghĩa khác nhau trong cùng file — CẤM gộp khi nén) + **bẫy công-cụ**: PowerShell `$_` trong `ForEach-Object` bị Bash tool nuốt khi escape ⇒ đổi sang `Get-FileHash` dạng liệt-kê — chính cách gọi shell làm hỏng phép kiểm, không phải lệnh sai.
|
||||
|
||||
## Entries
|
||||
|
||||
> ⚠️ **VA-CHẠM Pattern 16 (fidelity — giữ nguyên từ curator, lead định-đoạt):** trong verbatim, nhãn "Pattern 16 NEW" được gán cho **HAI thứ khác nhau**: (S25 wrap) *"Preventive systemic batch fix khi audit phát hiện 9 sites cùng bug pattern"* ⟂ (S27 Plan CA) *"byte-identical mirror admin → user khi parity confirmed"*. Verbatim q4 tự khai chỗ mờ: "Pattern 16 baseline **implied** trong recent activity S27 **chưa numbered explicit**". ⇒ **CẤM gộp 2 mục này thành một chỉ vì trùng số.**
|
||||
|
||||
**[cao] G1 — Plan AB Chunk A: gộp 4 nhánh về một chỗ ghi log**
|
||||
- **VIỆC:** sửa 2 lỗi UAT về nhật-ký duyệt (`cdfd542`, 3 file, +146/-95).
|
||||
- **KẾT-LUẬN:** BE viết lại `ApplyReturnModeAsync` (`:215-378`): bỏ `return` sớm của nhánh Drafter (`~:282-287`) → chuyển thành `if/else` **một đường chung**, để **một `Changelog.Add()` duy nhất ở cuối** phủ cả 4 mode (`Summary=$"Trả lại ({modeName}): {summary}"`, `modeName` switch 4 giá-trị tiếng Việt); `actorName` lấy qua `userManager.FindByIdAsync` **theo đúng khuôn `LogTransition` `~:660-667`**; **không** gọi `SaveChangesAsync` mới vì caller `TransitionAsync:100` đã lưu. FE 2 app nới bộ lọc: theo `summary` chứa "Trả lại" (Workflow) và `entityType === PE_ENTITY_HEADER(=1)` + chứa "ngân sách". Test **bỏ qua** theo chế-độ UAT (giữ mốc 111).
|
||||
- **BÀI-HỌC:** khi N nhánh cùng phải ghi một loại log, **gỡ return sớm để có đường chung** rồi ghi **một lần** — thay vì rải N lời gọi. Và lọc ở FE bằng **chuỗi con của summary** (động-từ tiếng Việt) thay vì enum cứng: FE thêm luật lọc mới **không cần migrate BE**.
|
||||
- **BẤT-NGỜ:** cùng một đoạn được tả 2 lần trong cùng file với **số dòng lệch nhau** (`282-287` ở entry dày vs `280-287` ở entry wrap) ⇒ khi hợp-nhất phải chọn bản dày, đừng trộn số.
|
||||
→ substring:"cdfd542"
|
||||
|
||||
**[cao] G2 — Plan AG: cây 2 tầng bằng HTML thuần (Pattern 19)**
|
||||
- **VIỆC:** bro phản-hồi danh-sách PE phẳng là "đám rừng" → dựng cây Dự-án ▸ Gói-thầu kiểu thư-mục Outlook (`0bf6c7e`, 2 file, +346/-116).
|
||||
- **KẾT-LUẬN:** gom bằng `useMemo` 2 tầng (`ProjectGroup` / `GoiThauGroup`, khoá chuẩn-hoá `trim`+`toLowerCase`, hiển-thị bản gốc đầu tiên), có nhánh dự-phòng "(Dự án đã xoá)" / "(Chưa phân loại)", sắp theo locale `vi`, **lọc trước khi gom** để trạng-thái rỗng đúng. UI dùng **`<details>/<summary>` thuần trình-duyệt** (fe-user không có Accordion của shadcn) + Tailwind **named group** `group/proj` · `group/gt` + `[&::-webkit-details-marker]:hidden`. Ghi nhớ trạng-thái mở bằng `localStorage` (`pe_list_expanded_groups`, khoá `${projectId}::${normalizedGoiThau}`, mặc-định đóng hết) có `try/catch` phòng chế-độ ẩn-danh. 2 app **giống nhau tới SHA256** (`21001E90…`).
|
||||
- **BÀI-HỌC:** thiếu thư-viện thì **dùng thẻ HTML gốc**: `<details>` cho sẵn trạng-thái đóng/mở, bàn-phím Space/Enter, **0 dòng JS quản trạng-thái từng nút**. Đổi thước đếm cũng phải đổi đồng-bộ (`rows.length` → `projectGroups.length`), nếu không trạng-thái rỗng sẽ nói dối.
|
||||
- **BẤT-NGỜ:** anti-pattern được ghi rõ: nếu **hai group lồng nhau trùng tên** `group` (không đặt tên) thì con **kế thừa trạng-thái cha ⇒ cả hai xoay chevron cùng lúc** — một lỗi UI chỉ lộ khi lồng, và named-group là cách chữa.
|
||||
→ substring:"pe_list_expanded_groups"
|
||||
|
||||
**[cao] G3 — Plan CA Chunk B: mirror nguyên-si + bẫy PowerShell**
|
||||
- **VIỆC:** dời 4 trang danh-mục từ fe-admin sang fe-user (`06a441c`, 6 file, **+962 LOC**).
|
||||
- **KẾT-LUẬN:** vì Investigator đã xác-nhận 2 app **ngang bằng** (DataTable/PageHeader/PermissionGuard/usePermission/6 shadcn/types), nên **chép nguyên file không sửa**, rồi **verify SHA256 cả 4** (`C1760788…`/`BDF0529E…`/`68213D62…`/`6F482614…`) ⇒ an-toàn vì mã bên admin đã qua UAT. Đúc `pattern_master_page_mirror.md`.
|
||||
- **BÀI-HỌC:** "chép y nguyên" chỉ hợp-lệ khi **đã chứng ngang-bằng trước**; và bằng-chứng phải là **hash sau khi ghi**, không phải cảm-giác đã-copy-đúng.
|
||||
- **BẤT-NGỜ:** **bẫy công-cụ:** biến `$_` của PowerShell trong khối `ForEach-Object` **bị Bash tool nuốt khi escape** ⇒ phải đổi sang dạng liệt-kê `Get-FileHash file1, file2, … | Format-Table` thay vì lặp qua pipeline. Tức chính **cách gọi shell** làm hỏng phép kiểm, không phải lệnh sai.
|
||||
→ substring:"06a441c"
|
||||
|
||||
**[vừa] G4 — Plan B Chunk D: dropdown V2 cho ContractCreatePage**
|
||||
- **VIỆC:** mirror ô chọn quy-trình V2 từ PE sang HĐ (`62b50d1`, 2 file, +44 LOC mỗi app).
|
||||
- **KẾT-LUẬN:** dựa nguyên bản `PeWorkspaceCreateView.tsx` (`:80-89` useQuery + `:152-172` UI Select), đổi **duy nhất** trường phân-biệt `ApplicableType=3`; kiểm **tiền-đề BE trước khi wire**: `CreateContractCommand` (`ContractFeatures.cs:17-36`) **đã có** `Guid? ApprovalWorkflowId = null` kèm marker của em-main ⇒ không lệch DTO. Áp **Pattern 16-bis 4 chỗ**: trang ×2 **DONE**, còn Routes / menuKeys / Layout staticMap đều **N/A có lý-do**.
|
||||
- **BÀI-HỌC:** mirror xuyên module thì **liệt-kê đủ 4 chỗ rồi ghi N/A kèm lý-do**, đừng im lặng bỏ qua — "N/A" có lý-do mới chứng-minh là đã xét.
|
||||
- **BẤT-NGỜ:** vai **kiểm tiền-đề phía BE trước khi sửa FE** (đọc record của commit em-main làm trước) — nhờ vậy không đẻ ra lỗi lệch hợp-đồng dữ-liệu; và tự khai phần **cố-ý không làm** (chỉ `ContractHeaderForm`, không đụng `ContractEditForm`).
|
||||
→ substring:"62b50d1"
|
||||
|
||||
**[cao] G5 — META: mirror phải chứng bằng hash**
|
||||
- **VIỆC:** trục lặp ở 3/5 entry.
|
||||
- **KẾT-LUẬN:** Pattern 5 (mirror 2 app §3.9) được đếm tích-luỹ **lần 7 → 8 → 9**; mỗi lần đều kèm bằng-chứng máy: SHA256 khớp (AG, CA) hoặc `git diff --stat` byte-similar (Chunk D). Verbatim còn đề-xuất biến thành công-cụ: *"recommend tooling `git diff fe-admin/X fe-user/X` after every multi-file edit batch"*.
|
||||
- **BÀI-HỌC:** với công-việc nhân-bản, **đối-chứng máy rẻ hơn review mắt** và phát-hiện được thứ mắt không thấy (khác 1 ký-tự).
|
||||
- **BẤT-NGỜ:** vai tự **đếm số lần áp-dụng** một pattern (7/8/9) — biến "kinh-nghiệm" thành con-số kiểm được, thay vì nói "đã dùng nhiều lần".
|
||||
→ substring:"git diff fe-admin"
|
||||
|
||||
## Phụ-lục token (coverage)
|
||||
|
||||
- **Pattern#:** `4` `feedback_service_hook_vs_endpoint` (log trong service hook, KHÔNG endpoint `/return-changelog` rời) · `5` mirror 2 app §3.9 (**7th/8th/9th cumulative**) · **`16` (VA-CHẠM — 2 nghĩa, xem cảnh-báo trên)** · **`16-bis`** kiểm 4 chỗ cross-app · **`17`** FE merge synthetic row · **`18`** FE userMap fallback từ dữ-liệu nhúng · **`19`** `<details>`+named group+localStorage · **`12-bis`** cookie-cutter xuyên module · Pattern 7 (per-NV opt-in, nhắc lại)
|
||||
- **criteria#:** `#4` (tight coupling BE+FE ⇒ vai sẽ REFUSE Plan AC trở đi)
|
||||
- **surprise:** "**Gotcha S27**: PowerShell `$_` variable in `ForEach-Object` block **bị Bash tool shell-escape eaten**" · "fe-user **no shadcn Accordion** → native browser disclosure widget **free**" · "**Anti-pattern:** nested same-name `group` would inherit parent state → **both rotate sync**" · "Mirror 2 app §3.9 **IDENTICAL post-edit (SHA256 verify match `21001E90...`)**" · "**stash em main WIP** ... race condition trick" (nhắc chéo) · "KHÔNG ops git push (em main verify Reviewer rồi mới push)" (lặp 3 entry) · "FE filter qua **substring summary keyword chứ KHÔNG enum field strict**" · "recommend tooling `git diff fe-admin/X fe-user/X` after every multi-file edit batch" · số dòng lệch `282-287` vs `280-287` cho cùng đoạn
|
||||
- **guard:** `try/catch` quanh `localStorage` (quota / ẩn-danh) · lọc `pendingMe` **TRƯỚC** khi gom nhóm · `projectGroups.length === 0` thay `rows.length === 0` · SHA256 sau khi ghi (4 file) · `git diff --stat` byte-similar · kiểm **tiền-đề BE** (`Guid? ApprovalWorkflowId = null`) trước khi wire FE · `[&::-webkit-details-marker]:hidden` · named group `group/proj` ⟂ `group` mặc-định · **không** gọi `SaveChangesAsync` mới (caller đã lưu)
|
||||
- **root-cause:** `return` sớm ở nhánh Drafter ⇒ không có đường chung ⇒ phải rải nhiều lời-gọi log ⇒ gom về `if/else` + 1 `Changelog.Add()` · fe-user thiếu Accordion ⇒ chuyển sang `<details>` gốc · `group` không đặt tên khi lồng ⇒ con kế-thừa trạng-thái cha · Bash tool escape nuốt `$_` ⇒ `ForEach-Object` gãy
|
||||
- **file:line / định-danh:** `PurchaseEvaluationWorkflowService.cs` `ApplyReturnModeAsync` **:215-378**, Drafter early-return **:282-287** (wrap ghi `:280-287`), `userManager.FindByIdAsync` mirror **:660-667**, caller `TransitionAsync` **:100** · `PeDetailTabs.tsx` ×2 (filter + comment 3-nguồn) · `PE_ENTITY_HEADER` **= 1** · `Summary=$"Trả lại ({modeName}): {summary}"` (`"Người soạn thảo"`/`"1 Cấp"`/`"1 Bước"`/`"Người chỉ định"`) · `ProjectGroup{projectId, projectName, goiThauList[], totalCount}` · `GoiThauGroup{displayName, normalizedKey, items[]}` · `pe_list_expanded_groups` · `__no_project__` · `${projectId}::${normalizedGoiThau}` · `group/proj` · `group/gt` · `group-open/proj:rotate-90` · `PeWorkspaceCreateView.tsx` **:80-89** + **:152-172** · `ContractCreatePage.tsx` ×2 · `CreateContractCommand` @ `ContractFeatures.cs` **:17-36** (`Guid? ApprovalWorkflowId = null`, marker `[Plan B S29 2026-05-22 Chunk E1]`) · `ApplicableType=3` · Mig 25 `IsUserSelectable` · `pattern_master_page_mirror.md` · `feedback_uat_skip_verify`
|
||||
- **sha / số-đo:** `cdfd542` (3 file +146/-95; BE +83/-49; FE +14/-6 mỗi app) · `0bf6c7e` (2 file +346/-116 = +115 mỗi app) · `06a441c` (6 file, **+962**) · `62b50d1` (2 file, +44 ×2 = **+88**) · SHA256 `21001E90…` · `C1760788…` / `BDF0529E…` / `68213D62…` / `6F482614…` · build: BE 20.27s · fe-user 1907 module 16.62s / 16.05s / 14.14s / 8.84s · fe-admin 1926 module 6.98s / 6.86s / 16.07s · bundle fe-user **1291.33 KB** (gzip 337.00) / 1.32MB (gzip 343KB) · fe-admin **1402.68 KB** (gzip 357.51) / 1.40MB (gzip 358KB) · test mốc **111** (bỏ qua theo UAT) · token ~10k-16k · warn nền: CSS `@import` + `INEFFECTIVE_DYNAMIC_IMPORT realtime.ts` (lặp 3 entry) · tag `[pattern, phase-9, frontend]`
|
||||
@ -0,0 +1,53 @@
|
||||
# implementer-backend/2026-05-q3 — gist
|
||||
distill-gen: 1
|
||||
source-verbatim: .claude/agent-memory/implementer-backend/archive/2026-05-q3.md (6 entry → 4 cụm)
|
||||
pointer-style: substring
|
||||
|
||||
## TỔNG-HỢP (3-5 ý)
|
||||
1. **Nhận/từ-chối việc là bài học 2 chiều đối-xứng:** H1 ca nhận-rồi-vỡ (E3 tự dừng giữa chừng đúng chỗ judgment call; phải `stash` WIP em-main vì song-song cùng lớp BE) ⟂ H2 ca lẽ-ra-nhận-mà-không-được (2/8 task hợp-lệ nhưng registry rỗng ⇒ em-main forced solo, thiệt ~30 phút) — ngưỡng giao việc = **độ mơ-hồ spec >20%**, không phải số file.
|
||||
2. **Song-song trên cùng một lớp code đẻ chi-phí ẩn** ⇒ luật: chunk **tuần-tự A→B→C** khi cùng chạm BE, KHÔNG parallel.
|
||||
3. **Governance S28:** cấm tự-cho-phép đặt luật xuyên dự-án (bro bắt tại trận); luật cũ phải **khai tử công-khai (ABANDONED)** thay vì để trôi — luật zombie hại hơn không có luật; pattern proven ≥2× mới đề-cử Layer B.
|
||||
4. ⚠️ **FIDELITY-FLAG: 4 tên cờ 0-hit src/Backend @S170 — chờ reviewer chấm, đừng cite như fact** (claim Pattern 7 trong verbatim nêu `AllowDrafterEdit`/`AllowApproverEditSection1`/`AllowEarlyApprove`/`AllowDelegate` — nghi trôi từ-vựng UI thay tên code; ý an-toàn để giữ: "Pattern 7 đã áp 4 lần qua Mig 29/30/31" bỏ tên cờ).
|
||||
|
||||
## Entries
|
||||
|
||||
> ⚠️ **FIDELITY-FLAG (bắt buộc mang theo mọi trích-dẫn Pattern 7 từ file này):** verbatim (S29 wrap) khai *"Pattern 7 per-NV admin opt-in flag (Mig 29 AllowDrafterEdit + Mig 30 AllowApproverEditSection1 + Mig 31 AllowEarlyApprove + AllowDelegate) đã proven 4×"* — **4 tên cờ `AllowDrafterEdit` / `AllowApproverEditSection1` / `AllowEarlyApprove` / `AllowDelegate` = 0-hit src/Backend @S170** (control dương `AllowApproverSkipToFinal` = 54 file · control âm tên bịa = 0 — thước còn răng) — **chờ reviewer chấm, đừng cite như fact**. Giả-thuyết lành: trôi từ-vựng UI-section thay tên code (lặp có hệ-thống, cả ở q1); riêng `AllowEarlyApprove`+`AllowDelegate` chưa có đối-ứng nào ở Mig 31 (thật = `AllowApproverSkipToFinal`). Tên Mig thật: `20260513114505_AddAdvancedOptionsToApprovalWorkflows` (28) · `20260513130144_RefactorAdvancedOptionsToPerLevelAndDrafterUser` (29) · `20260513160703_AddAllowApproverEditBudgetToLevels` (30) · `20260514160124_RefactorSkipToFinalToApproverLevel` (31).
|
||||
|
||||
**[cao] H1 — S29 wrap: 5 lần được gọi, và bài học về chạy song-song**
|
||||
- **VIỆC:** phiên bận nhất — Plan CA Chunk B (~10K) + Plan B 4 chunk (A2 Mig 32 ~25K · C Mig 33 ~25K · D FE ~12K · **E3 dừng giữa chừng**).
|
||||
- **KẾT-LUẬN:** A2 phải **`stash` phần đang làm dở của em-main** (`ContractWorkflowService.cs`) mới build sạch được — vì em-main và vai **cùng sửa BE song song**. E3 (FE Section 5 V2) **tự dừng** đúng chỗ phải phán-đoán ("check `ContractDetail` type") và trả lại cho em-main. Đúc 3 luật: (1) khi cùng chạm BE thì chunk phải **tuần-tự A→B→C, không song-song**; (2) tính-năng FE phức-tạp cần mở rộng type + thêm component thì **em-main tự làm đáng tin hơn** khi độ mơ-hồ của spec **>20%**; (3) Pattern 16-bis được củng-cố lần 2.
|
||||
- **BÀI-HỌC:** song-song trên cùng một lớp code **đẻ ra chi-phí ẩn** (mẹo `stash`) — nhanh trên giấy, chậm khi chạy. Và ngưỡng giao việc nên tính bằng **độ mơ-hồ của spec**, không bằng số file.
|
||||
- **BẤT-NGỜ:** vai **tự dừng giữa nhiệm-vụ** thay vì đoán bừa — biến "không đủ dữ-kiện" thành hành-động hợp-lệ, và ghi lại đúng chỗ dừng để người sau nối tiếp.
|
||||
→ substring:"stash em main WIP"
|
||||
|
||||
**[cao] H2 — S27 hồi-cứu: tự chấm điểm việc mình KHÔNG được gọi**
|
||||
- **VIỆC:** dựng bảng 8 task của S27, đối chiếu từng dòng với tiêu-chí nhận/từ-chối.
|
||||
- **KẾT-LUẬN:** 6/8 xác-nhận "em-main tự làm là ĐÚNG" (curate MEMORY = phán-đoán §6.5 giữ-hay-cắt → #1 · dashboard = #7 chưa có tiền-lệ + #2 · viết docs 421 dòng = #2 · sửa lỗi Qdrant = #4). **2/8 lẽ ra giao được**: A3.1 viết 5 script PS (Case 2) và F2 sửa `model:inherit` ở 4 file agent (Case 1) — nhưng **registry không nạp được** nên em-main buộc phải tự làm; thiệt-hại ước ~30 phút + mất kỷ-luật mirror. Đúc **Pattern 20** (bộ 5 script PS start/stop/status/dashboard/boot: cùng kỷ-luật ASCII, cùng cấu-trúc `Write-Host`, cùng khuôn `try-catch`).
|
||||
- **BÀI-HỌC:** hồi-cứu phải chấm **từng dòng**, không chấm tổng — nhờ vậy mới tách được "quyết đúng" khỏi "quyết đúng nhưng vì lý do sai" (2 ca này em-main đúng kết-quả nhưng nguyên-nhân là **hạ-tầng hỏng**, không phải phán-đoán).
|
||||
- **BẤT-NGỜ:** cơ-hội bị mất **không do ai quyết sai** mà do **registry rỗng** — tức lỗi hạ-tầng lặng-lẽ biến thành lỗi phân-công, và chỉ lộ ra khi có bảng hồi-cứu.
|
||||
→ substring:"Net loss"
|
||||
|
||||
**[vừa] H3 — S28: 5 điều tự áp sau khi bị bắt lỗi**
|
||||
- **VIỆC:** ghi lại governance Layer A dưới góc nhìn của vai.
|
||||
- **KẾT-LUẬN:** (1) pattern đã chứng **≥2 lần** thì đủ tư-cách đề-cử lên Layer B; (2) tag bắt buộc `[pattern, phase-<N>, <bc>]`; (3) quy-ước `source_path` = `solution_erp/pattern/<topic>-<date>`; (4) **cấm tự cho phép mình đặt luật xuyên dự-án** — đúc từ ca t4 bro bắt tại trận; (5) luật cũ "mọi tương-tác đều phải lưu" **BỊ BỎ**.
|
||||
- **BÀI-HỌC:** phạm-vi quyền của một vai phải **thu về chính dự-án của nó**; và luật cũ nên **khai tử công-khai** ("ABANDONED") thay vì để trôi — luật zombie còn hại hơn không có luật.
|
||||
- **BẤT-NGỜ:** chính vai này ghi nhận mình **quên mất luật cũ** rồi mới phát-hiện luật đó đã bị bỏ — quên đúng thứ đáng quên, nhưng chỉ biết là "đúng" sau khi tra lại.
|
||||
→ substring:"ABANDONED"
|
||||
|
||||
**[thấp] H4 — Log curate + baseline**
|
||||
- **VIỆC:** ghi mốc: dồn 12 entry S21→S24 sang `archive/2026-05-q1.md`; và mốc khởi-tạo 2026-05-11.
|
||||
- **KẾT-LUẬN:** MEMORY **38.8 KB → mục-tiêu ~22-24 KB**, giữ nguyên khối Pattern 1-19; lúc khởi-tạo vai đã nạp sẵn 5 (thực tế liệt-kê 6) pattern nền tích-luỹ S1-S20, chưa làm gì, **đã bật sẵn cơ-chế tự từ-chối theo phạm-vi**.
|
||||
- **BÀI-HỌC:** mỗi lần dồn kho phải ghi **nguồn → đích + kích-thước trước/sau + thứ giữ lại** thì lần sau mới truy được.
|
||||
- **BẤT-NGỜ:** vai **bật quy-tắc từ-chối ngay từ lúc chưa làm việc nào** — ranh-giới được cài trước, không phải rút ra sau khi vỡ.
|
||||
→ substring:"2026-05-q1"
|
||||
|
||||
## Phụ-lục token (coverage)
|
||||
|
||||
- **Pattern#:** `7` per-NV admin opt-in (**tên cờ SAI — xem FIDELITY-FLAG**) · `12-bis` cross-module cookie-cutter · `16-bis` 4-place mirror (**reinforced 2×**) · **`20` MỚI** (bộ 5 script PS mirror) · nền "5 patterns S1-S20" (liệt-kê **6** mục)
|
||||
- **criteria#:** `#1` phán-đoán giữ-hay-cắt (§6.5) · `#2` thiết-kế UX / viết docs · `#4` chuỗi suy-luận sửa lỗi · `#7` lần đầu, chưa tiền-lệ · Case 1 (n file sửa cơ-học giống nhau) · Case 2 (cookie-cutter mirror)
|
||||
- **surprise:** "**E3 stopped mid-task** ... STOPPED at 'check ContractDetail type' judgment call" · "**stash em main WIP `ContractWorkflowService.cs`** để build verify clean (em main + Implementer **parallel touch BE → race condition trick**)" · "Forward **SEQUENTIAL** chunks A→B→C khi cùng touch BE, **NOT parallel**" · "em main solo **more reliable** than Implementer khi **spec ambiguity > 20%**" · "**2/8 task lẽ ra delegate được** ... nhưng **registry empty → em main forced solo**" · "Net loss **~30 phút**" · "registry KHÔNG load (per pitfall **VIPIX #1+#2**)" · "bro **caught self-authorize cross-project rule mistake**" · "Quên rule cũ 'mọi tương tác mandatory store' — **ABANDONED**" · "Implementer = **busiest agent S29** với 5 spawn"
|
||||
- **guard:** `git stash` phần WIP trước khi build verify · chunk **tuần-tự** khi cùng chạm BE · ngưỡng **spec ambiguity 20%** để chuyển về em-main · tự-dừng khi gặp judgment call · tag schema bắt buộc `[pattern, phase-<N>, <bc>]` · **cấm tự-cho-phép luật liên-dự-án** · pattern phải proven **≥2×** mới đề-cử Layer B
|
||||
- **root-cause:** em-main + vai **cùng sửa BE song song** ⇒ cây làm việc bẩn ⇒ build verify không sạch ⇒ phải `stash` · **registry không nạp** ⇒ không spawn được ⇒ 2 việc hợp-lệ bị dồn cho em-main · từ-vựng **UI-section** dùng thay tên cờ trong code ⇒ 4 tên cờ 0-hit (xem FIDELITY-FLAG)
|
||||
- **file:line / định-danh:** `ContractWorkflowService.cs` (WIP bị stash) · Mig 32 (+column FK **Restrict** + IX + Configuration + `DbInitializer` `SeedSampleContractWorkflowV2`) · Mig 33 `ContractLevelOpinions` (entity + Mig + Config + DbSet + `Contract.cs` +`LevelOpinions` nav) · `ContractCreatePage.tsx` ×2 (88 LOC) · 4 master page + `App.tsx` (+5 route) + `menuKeys.ts` (+5 key `Catalogs*`) · `pattern_master_page_mirror.md` · `archive/2026-05-q1.md` · script PS `start/stop/status/dashboard/boot`
|
||||
- **số-đo:** 5 spawn S29 · ~10K + ~25K + ~25K + ~12K token · **948 LOC** mirror / 6 file · MEMORY **38.8 KB → ~22-24 KB** · 12 entry dồn kho · setup **2026-05-11**
|
||||
- **🔬 disk-check hôm nay (S170, có 2 control):** `AllowApproverSkipToFinal` = **54 file** (control dương) · `AllowXyzKhongTonTai` = **0** (control âm) · `AllowDrafterEdit` / `AllowApproverEditSection1` / `AllowEarlyApprove` / `AllowDelegate` = **0 / 0 / 0 / 0** · Mig thật: `20260513114505_AddAdvancedOptionsToApprovalWorkflows` · `20260513130144_RefactorAdvancedOptionsToPerLevelAndDrafterUser` · `20260513160703_AddAllowApproverEditBudgetToLevels` · `20260514160124_RefactorSkipToFinalToApproverLevel`
|
||||
@ -0,0 +1,49 @@
|
||||
# implementer-backend/2026-05-q4 — gist
|
||||
distill-gen: 1
|
||||
source-verbatim: .claude/agent-memory/implementer-backend/archive/2026-05-q4.md (5 khối → 4 cụm)
|
||||
pointer-style: substring
|
||||
|
||||
## TỔNG-HỢP (3-5 ý)
|
||||
1. **Scaffold ULTRA-MINIMAL S33 Task 5** (14 file, 3 file mới SHA256 IDENTICAL ×2 app, test 120/120, "Ambiguities encountered: 0"): phạm-vi hẹp phải kèm danh-sách **"cố-ý không làm"** khai minh-bạch — không thì người đọc sau tưởng là thiếu sót. Entry này bị CHẺ ĐÔI trong verbatim (2 digest S65 FIFO trim chèn giữa) — gist đã NỐI lại.
|
||||
2. **Số đếm không khớp danh-sách:** cả 2 pattern ghi "4th time" nhưng chỉ liệt-kê 3 ca (×2 chỗ), cùng lớp lệch với "5 patterns/6 mục" ở q3 ⇒ khi nén **giữ danh-sách, bỏ số thứ-tự**.
|
||||
3. **LOC thô không đo công sức:** +4265 LOC nhưng Designer autogen 95%, tay chỉ ~232; HRM không có `HasQueryFilter` ⇒ phải `.Where(!IsDeleted)` tay; MaxLength lấy **EF làm nguồn chân-lý** (`Code=50` not spec 20).
|
||||
4. **Phát-hiện ⟂ xử-lý:** tự đo MEMORY 36.2KB vượt ngưỡng 25KB ~45% → gắn cờ cho em-main nhưng **cố-ý KHÔNG tự dọn** (giữ-hay-cắt = phán-đoán §6.5 của em-main); và **REFUSE forward** trước cả khi được giao (nhận scaffold theo spec, KHÔNG nhận schema design Mig 34).
|
||||
|
||||
## Entries
|
||||
|
||||
**[cao] I1 — S33 Task 5: scaffold HRM cho 2 app, phạm-vi cực hẹp** *(nối từ 2 mảnh verbatim — đuôi "Verify all clean / Files touched / Out-of-scope respected" nằm sau 2 digest S65 FIFO trim chèn giữa; đối-chiếu verbatim xác nhận đuôi thuộc Task 5: toàn file HRM + tag `[mirror-page, phase-10.1, frontend]`, KHÔNG thuộc S29 Chunk C)*
|
||||
- **VIỆC:** dựng 3 trang/type HRM (Phase 1 chỉ-đọc) mirror sang cả 2 app — tổng **14 file** (3 file MỚI ×2 app + 4 file sửa ×2).
|
||||
- **KẾT-LUẬN:** ACCEPT vì spec **xác-định 100%**, kiểu **ULTRA-MINIMAL** (không tách component DetailTabs, 6 mục dùng `<details>` nội-tuyến, không CRUD vệ-tinh). 3 file mới **giống hệt tới SHA256**: `CCFC70666568` · `DC859C897C5C` · `C796F25D01AC`. Kiểm sạch toàn bộ: fe-admin 21.4s / fe-user 9.2s / `dotnet build` 1.59s **0 warn 0 err** / `dotnet test` **120/120** (58 Domain + 62 Infra) — và **"Ambiguities encountered: 0"**. Pattern 16-bis + 12-bis đều được ghi là **lần thứ 4**.
|
||||
- **BÀI-HỌC:** phạm-vi hẹp phải ghi kèm **danh-sách "cố-ý không làm"** (không form vệ-tinh · không sửa nội-tuyến Header · không wire `PermissionGuard` vì em-main giữ Task 6 · không thêm `Bg_*`/`Catalog*`) — nếu không, người đọc sau sẽ tưởng là thiếu sót.
|
||||
- **BẤT-NGỜ:** cả hai "lần thứ 4" đều **chỉ liệt-kê 3 lần trước** (16-bis: CA HF1 + Chunk D + Task 5; 12-bis: Chunk C + Task 4 + Task 5) ⇒ **số đếm không khớp danh-sách**; cùng loại lệch với "5 patterns/6 mục" ở q3 ⇒ khi nén nên **giữ danh-sách, bỏ số thứ-tự**.
|
||||
→ substring:"CCFC70666568"
|
||||
|
||||
**[cao] I4 — S32 startup: tự đo kho rồi TỪ CHỐI tự dọn**
|
||||
- **VIỆC:** lượt spawn chỉ để kiểm bối-cảnh phiên 32.
|
||||
- **KẾT-LUẬN:** đo `Get-Item Length` = **36.207 byte** (36.2KB) ⇒ **vượt ngưỡng 25KB ~45%** → **gắn cờ cho em-main**, và **cố-ý KHÔNG tự dọn** vì việc giữ-hay-cắt là phán-đoán §6.5 thuộc em-main (viện dẫn chính bài học C1-C4 của S27). Xác-nhận còn đủ **17 pattern đánh số** (1-12 + 12-bis + 13-15 + 16-bis + 17-19). RAG sống: 2 tool MCP hiện diện, truy-vấn thử trả 3 kết-quả rerank **0.824 / 0.801 / 0.793** — đều **> ngưỡng 0.7**.
|
||||
- **BÀI-HỌC:** đo bằng **byte thật của hệ-điều-hành** rồi mới phán "quá ngưỡng"; và **phát-hiện ⟂ xử-lý** — vai được quyền gắn cờ nhưng không được quyền cắt, đúng ranh-giới đã chốt từ ca trước.
|
||||
- **BẤT-NGỜ:** tự-kiềm-chế được ghi kèm **lý-do có dẫn nguồn** (bài học S27), không phải "ngại làm" — và chính cái cờ này về sau thành đợt curate S32 (36.2 → 27.5KB).
|
||||
→ substring:"Length=36207"
|
||||
|
||||
**[vừa] I2 — Hai digest "archived S65 FIFO trim"**
|
||||
- **VIỆC:** 2 việc BE cũ được nén còn vài dòng ở đợt dồn kho S65.
|
||||
- **KẾT-LUẬN:** **S35 G-H2**: CRUD 4 danh-mục HRM (`HrmConfigFeatures.cs` 372 LOC + Controller 134 LOC, **16 endpoint** = 4 tài-nguyên × 4 động-từ), giữ mốc **130 test**, spec rõ 95%. **S29 Chunk C**: mirror Contract V2 (Mig 33), **8 file +4265 LOC** nhưng **Designer tự sinh 95%**, tay chỉ **~232 LOC**.
|
||||
- **BÀI-HỌC:** 2 điểm kỹ-thuật đắt nhất nằm ở S35: HRM **không có `HasQueryFilter`** ⇒ phải tự viết `.Where(!IsDeleted)`; và **độ dài MaxLength lấy EF làm chuẩn**, không lấy spec (`Code=50`, spec ghi 20). *(Đây là lớp chưng-cất sẵn — giữ gần nguyên, nén tiếp sẽ mất mã-số kỹ-thuật.)*
|
||||
- **BẤT-NGỜ:** "+4265 LOC" nghe khổng-lồ nhưng **95% là mã tự sinh** — con-số LOC thô **không đo được công sức**, phải tách phần tay viết (~232) mới so được.
|
||||
→ substring:"HrmConfigFeatures.cs 372 LOC"
|
||||
|
||||
**[vừa] I3 — S32 wrap: dựng hàng chờ cho phiên sau**
|
||||
- **VIỆC:** đóng phiên 32, lập backlog.
|
||||
- **KẾT-LUẬN:** ghi **11 module Plan G** thành **10 sprint nguyên-tử** trong `migration-todos`; nêu 4 việc chờ phiên 33 kèm **phân loại sẵn** (a) G-H1 scaffold hồ-sơ NS · (b) bộ test BW1-BW7 mirror khuôn test PE · (c) BookingCalendar (FullCalendar = **thư-viện mới**) · (d) G-O3..G-O6 mirror 4 module Workflow Apps. Kèm **REFUSE trước**: sẽ nhận scaffold theo spec xác-định, **không nhận thiết-kế schema** (Mig 34 để em-main).
|
||||
- **BÀI-HỌC:** backlog nên **kèm sẵn nhãn nhận/từ-chối** cho từng mục — quyết định phân-công xong từ lúc lập kế-hoạch, không phải lúc bị gọi.
|
||||
- **BẤT-NGỜ:** vai **từ-chối trước cả khi được giao** ("REFUSE forward") — ranh-giới được tuyên trước, giúp em-main khỏi phí một lượt spawn để nhận về lời từ-chối.
|
||||
→ substring:"FullCalendar lib new dep"
|
||||
|
||||
## Phụ-lục token (coverage)
|
||||
|
||||
- **Pattern#:** `16-bis` 4-place mirror (**"4th time"**, liệt-kê 3 ca: S29 Plan CA **HF1** + S29 Plan B Chunk D + S33 Task 5) · `12-bis` cross-module (**"4th time"**, liệt-kê 3 ca: Chunk C Mig 33 + G-H1 Task 4 BE + Task 5 FE; riêng S35 ghi **"3rd application catalog-mega"**) · "**17 numbered patterns**" (1-12 + 12-bis + 13-15 + 16-bis + 17-19) · ghi chú `Pattern 16 baseline **implied** ... **chưa numbered explicit**`
|
||||
- **surprise:** "**Ambiguities encountered: 0** — spec deterministic 100%" · "**HRM no `HasQueryFilter`** → `.Where(!IsDeleted)` manual" · "**Validator MaxLength = EF source-of-truth** (`Code=50` **not spec 20**)" · "8 file **+4265 LOC** (Designer **autogen 95%**, handcraft **~232 LOC**)" · "MEMORY size **36.2KB** ... **OVER 25KB threshold ~45% bigger**" · "**KHÔNG self-curate** vì ... reserve cho em main solo judgment call §6.5" · "**KHÔNG commit** (em main commits)" · "**REFUSE forward:** ... **KHÔNG schema design**" · "FullCalendar lib **new dep**" · "3 NEW file **SHA256 verified IDENTICAL**" · số "4th time" nhưng chỉ liệt-kê **3** ca (×2 chỗ)
|
||||
- **guard:** SHA256 xác-nhận sau khi ghi (3 file) · `.Where(!IsDeleted)` thủ-công (không có query filter toàn-cục) · MaxLength lấy **EF làm nguồn chân-lý** · ngưỡng MEMORY **25KB** + đo bằng `Get-Item Length` · ngưỡng rerank **> 0.7** · ranh-giới **phát-hiện ⟂ tự-dọn** (§6.5 KEEP vs CUT thuộc em-main) · danh-sách **out-of-scope** khai minh-bạch
|
||||
- **root-cause:** đợt dồn kho **S65 FIFO trim** chèn 2 digest **vào giữa** entry S33 Task 5 ⇒ thân và đuôi bị tách rời (gist này đã NỐI lại — xem ghi chú I1) · HRM thiếu `HasQueryFilter` ⇒ mọi truy-vấn phải tự lọc soft-delete · spec ghi MaxLength lệch EF ⇒ validator sai nếu tin spec
|
||||
- **file:line / định-danh:** `types/employee.ts` (**NEW ~283 LOC**, SHA256 `CCFC70666568`) · `pages/hrm/EmployeesListPage.tsx` (**~417 LOC**, `DC859C897C5C`) · `pages/hrm/EmployeeCreatePage.tsx` (**~178 LOC**, `C796F25D01AC`) · `menuKeys.ts` (+3 LOC `Hrm`+`HrmHoSo`) · `Layout.tsx` (+4 LOC staticMap **`Hrm_HoSo`**) · `App.tsx` (+5 LOC) · 6 file fe-user mirror · `HrmConfigFeatures.cs` **372 LOC** + Controller **134 LOC** (**16 endpoint**) · Mig 33 `ContractLevelOpinions` · Mig 34 (em-main giữ) · `migration-todos` (11 module / 10 sprint) · `PurchaseEvaluationWorkflowServiceReturnModeTests.cs` → `ContractWorkflowServiceApproveV2Tests.cs` (7 kịch-bản, spec D-Bis) · `mcp__rag-unified__search_memory` + `mcp__rag-unified__cross_project_search` · "Pattern curate trigger rule **line 364**" · agent id `ab23cc322b5d495c0`
|
||||
- **số-đo:** 14 file (S33 Task 5) · build fe-admin **21.4s / 1429KB** · fe-user **9.2s / 1345KB** · `dotnet build` **1.59s 0 warn 0 err** · `dotnet test` **120/120** (58 + 62) · test mốc S35 **130** · MEMORY **36.2KB** (`Length=36207`) → curate **27.5KB** · rerank **0.824 / 0.801 / 0.793** (ngưỡng 0.7) · token ~25k (Task 5) / ~5K (wrap) / ~5k (startup) · tag `[mirror-page, phase-10.1, frontend]` · `[s35, be-crud, hrm, 12-bis-3x]` · `[s29, plan-b, 12-bis]` · `[wrap, phase-9-to-phase-10, frontend+backend]` · `[verify, phase-9, infra]`
|
||||
@ -0,0 +1,72 @@
|
||||
# investigator-codebase/2026-05-q1 — gist
|
||||
distill-gen: 1
|
||||
source-verbatim: .claude/agent-memory/investigator-codebase/archive/2026-05-q1.md (11 entry [10 FIFO S21→S24 + 1 setup] → 7 cụm)
|
||||
pointer-style: substring
|
||||
|
||||
## TỔNG-HỢP (3-5 ý)
|
||||
- Refactor cardinality Mig 29 (1-row-per-Cấp → 1-row-per-NV) làm mọi phép `FirstOrDefault` theo khoá cũ sai âm-thầm: bug BE `PurchaseEvaluationFeatures.cs:765` ẩn TỪ NGÀY DEPLOY, chỉ lộ khi tick chọn-lọc per-NV; cùng bộ 7 cờ `Allow*` hỏng 2 kiểu KHÁC nhau ở 2 lớp (F4 FE đoản-mạch readOnly ⟂ BE lấy nhầm hàng) + 1 báo-động-giả (mental-model "Trả lại"≠"nháp").
|
||||
- Ranh giới kiểu dữ-liệu = điểm gãy câm: record `TransitionPeBody` 3 field nuốt im-lặng 3 field FE gửi đủ — phép thử rẻ = đếm field body ↔ đếm field command; 1 lượt audit FE thuần BÁC được giả-thiết, chặn em-main sửa mù xuyên tầng.
|
||||
- Pre-flight có bản-đồ đổi được quyết-định: Plan K neo 20+ điểm + GAP fe-user (phá luật "mirror 2 app"); Plan R 4 sqlcmd → chốt Option A; Plan F HUỶ ngay ở pre-flight vì Contract còn hoàn-toàn V1.
|
||||
- Trí-nhớ/docs tự sai, phải re-ground: "3-panel" đo lại là 2-panel; Dev DB 2 user vs prod 33 user đều KHÔNG khớp spec; entry đếm-cumulative (B1-G) hết hạn nhanh — giá-trị còn lại chỉ là mốc thời-gian.
|
||||
- Tailwind JIT đòi chuỗi class ĐẦY ĐỦ trong mảng (STEP_PALETTE/LEVEL_PALETTE liệt nguyên chuỗi); sidebar widen an-toàn `w-72 xl:w-80` @1280px, ngưỡng rủi-ro `w-80 xl:w-96`.
|
||||
|
||||
## Entries
|
||||
|
||||
**[cao] B1-A — chuỗi per-NV Allow* (F1-F4): 2 bug tách lớp FE/BE + 1 báo-động-giả** *(gộp 3 entry: S23 t2 L2 + S23 t3 + S23 t2 M0 — giữ 3 verdict phân-biệt)*
|
||||
- VIỆC: 3 lượt audit UAT liên-tiếp trên cùng bộ 7 cờ `Allow*` per-slot (`ApprovalWorkflowLevels`) sau Mig 29/30/31: (i) admin tick F3+F4 mà menu "Duyệt" vẫn read-only; (ii) admin tick 7 cờ cho riêng 1 NV mà FE không hiện checkbox; (iii) rà 4 BE + 4 FE × 2 app xem F1-F4 có tự đẩy phiếu về "nháp".
|
||||
- KẾT-LUẬN: (i) F3 ĐÚNG, F4 HỎNG — `PeDetailTabs.tsx:118` `itemsReadOnly` override được, nhưng `:245` truyền `readOnly={readOnly}` xuống BudgetAdjustSection và `:973` `canAdjust = !readOnly && (...)` đoản-mạch TRƯỚC khi F4 kịp xét ⇒ nút bút chì ẩn `:1030`; fix ~3-5 LOC 1 file: đảo vế `isAdmin || (!readOnly && isDrafter && isDrafterPhase) || isApproverChoDuyet` (land, thấy lại `:977`). (ii) BE lấy nhầm hàng: `PurchaseEvaluationFeatures.cs:765` `FirstOrDefault(l => l.Order == curLevelOrder)` — sau Mig 29 tách 1 Level-row PER ApproverUser thì `Order` trùng nhau trong cùng Cấp ⇒ EF trả row đầu theo PK; 2 token (admin + nv.test) gọi API ra CÙNG `currentLevelOptions`; fix 1 dòng `&& l.ApproverUserId == currentUser.UserId` + fallback row-đầu cho admin. (iii) KHÔNG bug: F1.OneLevel/OneStep/Assignee + F2 + F3 + F4 đều giữ Phase=ChoDuyet; chỉ F1.Drafter `:268-275` đặt `Phase=TraLai=98` — đúng thiết-kế.
|
||||
- BÀI-HỌC: hai lớp cùng tính-năng hỏng theo hai kiểu khác nhau ⇒ verdict lớp này không suy ra lớp kia. Refactor cardinality làm mọi phép `FirstOrDefault` theo khoá cũ sai âm-thầm — phải rà toàn bộ site tra-cứu cùng khoá, không vá theo từng báo-cáo lỗi.
|
||||
- BẤT-NGỜ: (a) bug BE CÓ TỪ NGÀY deploy Mig 29, không phải hồi-quy S23 — ẩn vì 13 user UAT cũ hầu hết ở row đầu của Cấp + admin tick toàn FALSE. (b) route `/inbox` đẩy DetailPage mobile mặc-định `readOnly=false`, còn Danh sách `?pendingMe=1` desktop hard-code `readOnly={true}` ⇒ CÙNG phiếu, 2 lối vào, 2 hành-vi. (c) lượt (iii) chốt "code đã đúng" — cái sai là mô-hình trong đầu người dùng (`TraLai=98` ≠ `DangSoanThao=1`).
|
||||
→ substring:"PurchaseEvaluationFeatures.cs:765"
|
||||
|
||||
**[cao] B1-B — Plan P: Controller record nuốt 3 field, ASP.NET im lặng**
|
||||
- VIỆC: em-main giả-thiết Plan P chỉ đụng BE; investigator đối-chiếu FE × 2 app xác nhận trước khi sửa.
|
||||
- KẾT-LUẬN: FE `PeWorkflowPanel.tsx:113-124` gửi ĐỦ 6 field (TargetPhase + Decision + Comment + ReturnMode + ReturnTargetUserId + SkipToFinal, object literal không typed-service); BE `PurchaseEvaluationsController.cs:267` record `TransitionPeBody` chỉ 3 field ⇒ model-binder BỎ IM LẶNG 3 field còn lại. Verdict: Plan P = BE Controller ONLY ~6 LOC, không cần test mới (Mig 28/31 Domain test đã phủ handler).
|
||||
- BÀI-HỌC: "FE gửi rồi" và "BE nhận được" là hai mệnh-đề khác nhau — điểm gãy ở ranh giới kiểu dữ-liệu (record thiếu field = mất dữ-liệu không lỗi, không log). Đếm field body ↔ đếm field command là phép thử rẻ.
|
||||
- BẤT-NGỜ: một lượt audit FE thuần chặn được em-main sửa mù xuyên tầng — giá trị của spawn nằm ở chỗ nó BÁC BỎ giả-thiết, không phải xác nhận.
|
||||
→ substring:"TransitionPeBody"
|
||||
|
||||
**[vừa] B1-C — Pre-flight Plan K (Mig 31): bản-đồ neo + GAP fe-user + lệch Dev/prod**
|
||||
- VIỆC: rà trạng-thái trước khi chuyển `Users.AllowDrafterSkipToFinal` → `ApprovalWorkflowLevels.AllowApproverSkipToFinal` (đổi cả ngữ-nghĩa Drafter-Nháp → Approver-ChoDuyet).
|
||||
- KẾT-LUẬN: neo đủ 20+ điểm (`User.cs:38` · `ApprovalWorkflow.cs:86-105` 6 cờ · nhánh Drafter/APPROVE service · DTO + bundle · UsersController · Designer panel 6 checkbox · UsersPage · PeDetailTabs admin/user — chi tiết file:line ở phụ-lục). Path migration thật = `Persistence/Migrations/` (KHÔNG phải `Migrations/`).
|
||||
- BÀI-HỌC: GAP fe-user (không có UsersPage/Designer — quản-trị chỉ ở fe-admin) ⇒ "mirror 2 app" KHÔNG phải luật phổ-quát; xác định trước app nào có bề-mặt nào rồi mới khoanh phạm-vi UI.
|
||||
- BẤT-NGỜ: Dev DB 2 user / 0 flagged, prod 33 user / 4 flagged — cả hai đều KHÔNG khớp spec (S22+2) ⇒ 4 user mất giá-trị khi DROP cột, chấp-nhận được vì ngữ-nghĩa mới làm nó vô-nghĩa.
|
||||
→ substring:"User.cs:38"
|
||||
|
||||
**[vừa] B1-D — Pre-flight Plan R: xoá dữ-liệu prod, 3 phương-án**
|
||||
- VIỆC: 4 truy-vấn sqlcmd đo trước khi xoá: 35 PE (28 active + 7 soft) · 17 V2 (15 chưa-ghim + 2 ghim) · 4 V1 (2 active + 2 inactive).
|
||||
- KẾT-LUẬN: `PE.ApprovalWorkflowId` FK Restrict + `ApprovalWorkflow` kế-thừa `BaseEntity` (KHÔNG soft-delete) ⇒ buộc hard-DELETE; `LevelOpinion → ApprovalWorkflowLevel` Restrict chặn cascade; ước ~620 row con (446 PE-children + ~140 V2 + ~37 V1). Chốt Option A (hard-DELETE PE + bỏ V2 chưa-ghim + V1 inactive; GIỮ V2 ghim + V1 active).
|
||||
- BÀI-HỌC: tiền-lệ Plan F nhắc lại đúng lúc — KHÔNG drop V1 active vì còn phiếu pin vào, drop là BE crash lúc khởi-động.
|
||||
- BẤT-NGỜ: hạ-tầng ra điều-kiện: SQL Express không có COMPRESSION, `RESTORE VERIFYONLY` đòi sysadmin, index filtered (Mig 29) đòi `SET QUOTED_IDENTIFIER ON` mới chạy sqlcmd.
|
||||
→ substring:"RESTORE VERIFYONLY"
|
||||
|
||||
**[vừa] B1-E — Plan AA: 5Q audit → 7 commit `a1a910f..ee0902a`** *(gộp 2 entry cùng Plan: Pre-A spawn + wrap 4 polish)*
|
||||
- VIỆC: audit 5 câu hỏi mở đường (endpoint filter · menu seed · Designer · sidebar widen · enum) rồi em-main tự làm 4 vòng polish theo UAT.
|
||||
- KẾT-LUẬN: `ApprovalWorkflowsV2Controller.cs:16-19` đã `[Authorize]` trần từ S18 (gotcha #44 vá vĩnh-viễn) ⇒ chỉ thêm param filter `IsUserSelectable`; menu seed `DbInitializer.cs:1429-1437` + quyền `:1541-1547` cho 7 role (Accounting KHÔNG có, admin cấp tay); Designer `ApprovalWorkflowsV2Page.tsx` 975 dòng, 13 field DTO / 7 cờ; sidebar `w-60 xl:w-72` → `w-72 xl:w-80` an-toàn @1280px (`w-80 xl:w-96` = ngưỡng rủi-ro); enum {DuyetNcc=1, DuyetNccPhuongAn=2, Contract=3}. 7 commit: `ee776d5` BE+Layout · `c667802` FE Page · `ac2c859` docs · 4 polish `da218f1`/`4d60598`/`fbbd361`/`ee0902a`.
|
||||
- BÀI-HỌC: Tailwind JIT đòi chuỗi class ĐẦY ĐỦ trong mảng — `bg-${color}-100` bị purge ⇒ STEP_PALETTE/LEVEL_PALETTE phải liệt nguyên chuỗi (tái dùng cho mọi cây menu phân-cấp). Bỏ `truncate` KHÔNG đủ cho nhãn dài xuống dòng: đổi cả cấu-trúc NavLink (flex → block + inline) + `text-[12px] leading-snug`.
|
||||
- BẤT-NGỜ: trí-nhớ dự-án SAI về chính UI của mình — memory khai "PE Workspace 3-panel", đo lại là 2-panel (`[260px_1fr] xl:[320px_1fr]`); chiến-lược Order=2 cho "Luồng duyệt" buộc UPDATE Order các leaf cũ, không thể chỉ INSERT-if-not-exists; `Contract=3` khai trong enum nhưng FE chưa nối.
|
||||
→ substring:"STEP_PALETTE"
|
||||
|
||||
**[vừa] B1-F — 6 phát-hiện rời S22 (hạ-tầng/identity/script)**
|
||||
- VIỆC: phiên em-main solo seed 20 user role-based lên prod (13→33 user); investigator ghi lại các va-chạm.
|
||||
- KẾT-LUẬN: mật-khẩu Identity đòi ≥12 ký-tự (`User@123456` 11 ký-tự FAIL 400, `TestUser@2026` PASS); đổi tên user = 4 trường nguyên-tử (Email + NormalizedEmail + UserName + NormalizedUserName + FullName — gotcha #38); sqlcmd cần `SET QUOTED_IDENTIFIER ON`; login trả `accessToken`/`refreshToken`/`user` — KHÔNG có field `token` (ví-dụ Bash dùng `.token` sẽ fail); script PS 5.1 phải ASCII-only (gotcha #30).
|
||||
- BÀI-HỌC: khuôn "admin tick opt-in TỪNG slot" củng-cố lần 2 (Mig 30 F4 lặp Mig 29 F1+F3) sau khi anh sửa lưng em-main: mặc-định là KHÔNG mở rộng quyền.
|
||||
- BẤT-NGỜ: Plan F (drop V1) HUỶ ngay ở pre-flight vì sqlcmd lộ Contract còn HOÀN TOÀN V1 (chưa có cột `ApprovalWorkflowId`) + 4 PE V1-only + 19 PE lai ⇒ phạm-vi drop phải tính cả thực-thể liên-đới.
|
||||
→ substring:"TestUser@2026"
|
||||
|
||||
**[thấp] B1-G — ảnh-chụp trạng-thái no-spawn (3 mốc)**
|
||||
- VIỆC: 3 mốc không spawn: 2026-05-11 khởi-tạo agent · S21 t3-t5 · S22.
|
||||
- KẾT-LUẬN: baseline 2026-05-11 = 44 gotcha + 14 memory + 6 skill + 27 mig + 81 test; S21 = 84 test / 29 mig / 45 gotcha / 19 memory; S22 = 104 test (+20) / 30 mig / 46 gotcha / ~146 endpoint (+3) / 33 user prod.
|
||||
- BÀI-HỌC: entry loại "đếm cumulative" hết hạn nhanh — giá-trị còn lại chỉ là mốc thời-gian, không phải con số.
|
||||
- BẤT-NGỜ: khai thẳng "Investigator KHÔNG spawn" mà vẫn ghi entry — diary vai từng ghi cả phiên nó không làm gì = nguồn nhiễu khi đếm coverage.
|
||||
→ substring:"Awaiting first SendMessage"
|
||||
|
||||
## Phụ-lục token (coverage)
|
||||
*(chép TRỌN token-list section investigator-codebase/2026-05-q1 của proposal curator — lưới coverage-diff)*
|
||||
|
||||
- **gotcha#:** `#45` (bug fix S21 t3) · `#38` (Identity rename atomic 4 field) · `#30` (PS 5.1 ASCII-only script discipline) · `#44` (class-level `[Authorize]` bare fixed permanent từ S18 2026-05-08)
|
||||
- **surprise (nhãn nguyên-văn):** "Surprise: Inbox `/inbox` route (InboxPage.tsx) navigate sang `/purchase-evaluations/:id` (mobile DetailPage route, default readOnly=false)" · "**Surprise**: 'Trả lại' trong UI memory docs đôi khi gọi 'draft' colloquial — bro confuse 2 khái niệm" · "Surprise: bug PRESENT từ deploy Mig 29 (S21 t5 2026-05-13) — không phải regression S23" · "Surprises: (1) memory responsive breakpoint stale '3-panel' (2) Order strategy shift +1 (3) Contract=3 chưa wire FE" · "**Memory drift confirmed**: … S24 verify ACTUAL **2-panel**" · "**Plan AA color palette success** … Tailwind JIT yêu cầu full class strings array" · "**Drift Dev DB**: Total=2 user … NOT match 33-user prod seed" · "**Prod actual**: Total=33 / Flagged=4 (NOT 2 per S22+2 spec)" · "**GAP fe-user**: KHÔNG có UsersPage + ApprovalWorkflowsV2Page"
|
||||
- **guard:** `PurchaseEvaluationDraftGuard.EnsureEditableForDetailsAsync` (`PurchaseEvaluationDetailFeatures.cs:42`, 8 callsite) · guard line 485 `ConflictException` non-admin + flag off (F2 skipToFinal) · `ApplyReturnModeAsync` gate per-slot per-mode `PurchaseEvaluationWorkflowService.cs:252-265` (Admin bypass :252) · entry guard `:760-762` (pointer null khi DaDuyet/TraLai/TuChoi) · BE budget-adjust `ConflictException` nếu `Allow=false` · FK **Restrict** `PE.ApprovalWorkflowId` + `LevelOpinion → ApprovalWorkflowLevel` · `SET QUOTED_IDENTIFIER ON` (filtered index Mig 29+) · class-level `[Authorize]` bare (gotcha #44) · Designer KHÔNG có `usePermission`/`PermissionGuard` (route guard là đủ)
|
||||
- **root-cause:** ① "F4 = BROKEN at readOnly short-circuit" — `canAdjust = !readOnly && (…)` đoản-mạch TRƯỚC khi `isApproverChoDuyet` được xét (`PeDetailTabs.tsx:973`, nút ẩn `:1030`) ② "BE line 765 lookup semantic broken sau Mig 29" — `Level.Order` trùng nhau sau khi tách 1 row/ApproverUser ⇒ `FirstOrDefault(Order==X)` lấy row đầu theo PK (`PurchaseEvaluationFeatures.cs:765`) ③ Plan P — `TransitionPeBody` record 3 field ⇒ **ASP.NET silent DROP** 3 field còn lại (`PurchaseEvaluationsController.cs:267`) ④ Plan M0 — KHÔNG có root-cause code, "DISCONNECT bro mental model vs code reality post-Mig 28/29/30/31"
|
||||
- **file:line:** `User.cs:38` · `ApprovalWorkflow.cs:86-105` · `ApprovalWorkflow.cs:45-50` · `PurchaseEvaluationWorkflowService.cs:119-161` (:125) · `~:393-525` · `:268-275` · `:285-312` · `:303-310` · `:314-333` · `:335-360` · `:364` · `:441-468` · `:483-524` (:485) · `:252-265` (:252) · `PurchaseEvaluationFeatures.cs:393-402` · `:272-329` (:283-290 / :291-323) · `:735-770` · `:765` · `:281-329` · `PurchaseEvaluationDtos.cs:86-92` · `PurchaseEvaluationDetailBundleDto` `:214` + `:217` · `UsersController` `:91-98` + `:105` · `UserFeatures.cs:332` · `PurchaseEvaluationDetailFeatures.cs:42` + `:42-99` (:49-51 / :54-94) · `PurchaseEvaluationsController.cs:267` · `ApprovalWorkflowsV2Controller.cs:16-19` · `DbInitializer.cs:1429-1437` + `:1541-1547` · `system/ApprovalWorkflowsV2Page.tsx:873` + `:853-933` + `:889-946` + `:892-948` (975 dòng) · `system/UsersPage.tsx:306-318` + `:365-372` + `:181-186` · `PeDetailTabs.tsx:113-118` + `:118` + `:224` + `:245` + `:957-973` + `:973` + `:977` + `:1030` + `:287-297`(admin) / `:294-304`(user) + `:109-115` + `:967-979` · `PeWorkflowPanel.tsx:51` + `:60-68` + `:113-124` + `:331-422` (:343-396, :343/:357/:371/:397) + `:425-442` (:425) · `fe-user/src/pages/pe/PurchaseEvaluationsListPage.tsx:256-261` · `fe-user Layout.tsx:325` + `fe-admin Layout.tsx:218` · `Persistence/Migrations/` (path thật) · mig `20260513160703_AddAllowApproverEditBudgetToLevels`
|
||||
- **định-danh khác:** commit `a1a910f..ee0902a` (7): `ee776d5` · `c667802` · `ac2c859` · `da218f1` · `4d60598` · `fbbd361` · `ee0902a` · bug UAT prod `409a967` · phiếu `PE/2026/A/026` · WF `QT-DN-V2-001 v12` · user `nv.test@solutions.com.vn` · `TransitionAsync` param `skipToFinal` thứ 8 (position 47:47) · `w-60 xl:w-72` → `w-72 xl:w-80` (ngưỡng rủi-ro `w-80 xl:w-96`) · grid `[260px_1fr] xl:[320px_1fr]` · `STEP_PALETTE`/`LEVEL_PALETTE` 5 màu · token-cost 32k (Pre-A)
|
||||
@ -0,0 +1,51 @@
|
||||
# investigator-codebase/2026-05-q2 — gist
|
||||
distill-gen: 1
|
||||
source-verbatim: .claude/agent-memory/investigator-codebase/archive/2026-05-q2.md (4 entry → 4 cụm, 0 cặp trùng — dedup = nén câu chữ)
|
||||
pointer-style: substring
|
||||
|
||||
## TỔNG-HỢP (3-5 ý)
|
||||
- Cùng triệu-chứng "Lịch sử thay đổi trống" nhưng 2 root-cause KHÁC trục: một bug GHI-thiếu (`ApplyReturnModeAsync` ZERO dòng log) ⟂ một bug nghi ĐỌC-thiếu (handler ĐÃ log, nghi FE lọc) — phải tách trục ghi/đọc trước khi đặt giả-thiết chung.
|
||||
- Nối module thứ 2 vào schema duyệt dùng-chung: chi-phí thật nằm ở Service + terminal-state khác nhau (`DaPhatHanh` ≠ `DaDuyet`), KHÔNG ở migration — Contract thừa-hưởng 7 cờ `Allow*` miễn phí; nhưng prod 7 HĐ 100% pin V1 (6 đang giữa quy-trình) ⇒ BẮT BUỘC cùng-tồn V1+V2, KHÔNG drop V1.
|
||||
- Ghost-wire dạng schema lộ 2 lần: `EntityType.Workflow=5` khai trong enum nhưng 0 code ghi suốt 4+ migration; prod 0 row `ApplicableType=3` ⇒ nối xong không seed = dropdown rỗng.
|
||||
- Thiếu component thư-viện KHÔNG chặn việc: fe-user GAP Accordion/Collapsible/Tree/Card/Badge → đường vòng `<details>/<summary>` native; quyết-định kiến-trúc đứng trên số ƯỚC (~50-200 dự-án) phải khai rõ (SSH prod fail auth).
|
||||
- Khuôn hạ-tầng phải quy-chiếu đúng hình-thái người dùng: 1 người 5 dự-án ≠ team 5 dev ⇒ chốt Pattern C (MCP global localhost); Voyage AI 200M token/tháng free làm đổ luôn phép so chi-phí cũ.
|
||||
|
||||
## Entries
|
||||
|
||||
**[cao] B2-A — "Lịch sử thay đổi" trống: 1 bug hiển-thị + 1 bug thiếu-ghi**
|
||||
- VIỆC: 2 báo lỗi UAT gộp 1 lượt audit 5Q: (1) chỉnh ngân-sách 2 lần → lịch-sử không hiện 2 dòng; (2) trả-lại mode "Người chỉ định" → lịch-sử không hiện.
|
||||
- KẾT-LUẬN: Bug 1 KHÔNG phải lỗi ghi: `PurchaseEvaluationFeatures.cs:379-387` ĐÃ log changelog (EntityType=Header + Action=Update + diff) ⇒ nghi FE lọc bỏ Header, hoặc DB UAT cũ. Bug 2 thiếu ghi thật: `PurchaseEvaluationWorkflowService.cs:215-378 ApplyReturnModeAsync` ZERO dòng log — 4 nhánh chỉ đổi con-trỏ rồi trả summary; người gọi `TransitionAsync:100 LogTransitionAsync` chỉ log chuyển-phase. Query handler `ListPurchaseEvaluationChangelogsQueryHandler:1050-1064` không lọc gì ⇒ nếu mất thì mất ở FE. Đường vá: B2 trước (thêm `Changelogs.Add()` mỗi mode, 15-25 LOC 1 file), B1 sau (Option A enum Kind 30 LOC / B mở EntityType 20 LOC / C nới filter FE 10 LOC).
|
||||
- BÀI-HỌC: cùng triệu-chứng nhưng một cái ghi-thiếu, một cái đọc-thiếu — tách hai trục trước khi đặt giả-thiết chung; `EntityType` quá thô (Header/Update dùng chung cho "sửa ngân sách" lẫn "sửa mục 2") nên không phân-biệt được ở tầng hiển-thị.
|
||||
- BẤT-NGỜ: `EntityType.Workflow=5` tồn-tại trong enum nhưng KHÔNG dòng code nào ghi vào suốt 4+ migration — schema khai một khả-năng chưa bao giờ dùng (khai-mà-không-nối, dạng ghost-wire).
|
||||
→ substring:"1050-1064"
|
||||
|
||||
**[cao] B2-B — Plan B Contract V2: 80% schema dùng lại, chốt cùng-tồn V1+V2**
|
||||
- VIỆC: pre-flight 5Q + tái-chia chunk cho việc nối Contract vào bộ máy duyệt V2 (đang chỉ có PE).
|
||||
- KẾT-LUẬN: `Contract.cs:8-58` 25 field, sẵn V1 (`WorkflowDefinitionId:22`, `CurrentWorkflowStepIndex:39`, `Phase:12`, `RejectedAtStepIndex:40`) thiếu `ApprovalWorkflowId` + `CurrentApprovalLevelOrder` ⇒ Mig 32 chỉ ADD COLUMN vì Mig 22 (`20260508053749_AddApprovalWorkflowsV2.cs:14-127`) đã tạo 3 bảng DÙNG CHUNG cho PE lẫn Contract; `ContractPhase.cs:14-28` đã có ChoDuyet=10 / TraLai=98 / TuChoi=99 soi gương PE; `ContractWorkflowService.cs:23-219` 220 LOC chỉ V1, sinh mã HĐ `:148-155` ở bước cuối → `DaPhatHanh` (terminal KHÁC PE `DaDuyet`). Prod: 7 HĐ 100% pin V1, 6 đang giữa quy-trình (Phase 2-8) ⇒ BẮT BUỘC cùng-tồn, KHÔNG drop V1. Mẫu clone = `ApproveV2Async` `PurchaseEvaluationWorkflowService.cs:446-634` (189 LOC: match actor → UPSERT opinion → advance → skipToFinal), điểm rẽ nhánh `:167-171`. Chia 6 chunk A1/A2/B/C/D/E theo tay-nghề (cookie-cutter → implementer, xuyên-tầng → em-main).
|
||||
- BÀI-HỌC: khi schema đã tách sẵn dùng-chung, chi-phí thật của "nối module thứ 2" nằm ở Service + terminal-state, không ở migration — 7 cờ `Allow*` trên `ApprovalWorkflowLevels:86-114` Contract thừa-hưởng miễn phí.
|
||||
- BẤT-NGỜ: (a) prod 0 row `ApplicableType=3` ⇒ nối xong mà không seed thì dropdown rỗng, phải gài seed vào chunk A2 *(chú-thích curator S170, không thuộc verbatim: đúng thứ sau này thành sự-cố Run #231 ở lane cicd)*; (b) `RejectedAtStepIndex` Contract `:40` đã chết mà chưa đánh dấu obsolete, lệch với PE `:50`; (c) 7 `ContractType` đối đầu 1 `ApplicableType=3` chung — có thể phải thêm `ContractTypes int[]` sau.
|
||||
→ substring:"V1Pinned"
|
||||
|
||||
**[vừa] B2-C — Plan AG: PE List "đám rừng" → cây theo dự-án**
|
||||
- VIỆC: anh gửi ảnh chụp phàn nàn danh sách phẳng; audit 5Q để chọn hướng.
|
||||
- KẾT-LUẬN: `PurchaseEvaluationsListPage.tsx:133` 3-panel `lg:grid-cols-[340px_1fr_360px]`, thân phẳng `<ul><li>` `:199-252`, pageSize 50; `Master/Project.cs:5-14` thiếu MaxLength + thiếu navigation sang PE ⇒ sẵn cho bảng ProjectPackage sau; fe-user THIẾU Accordion/Collapsible/Tree/Card/Badge (chỉ có Button/Dialog/Input/Label/Select/Textarea). Chốt Approach C: Phase 1 FE-only ~160 LOC × 2 app, Phase 2 schema hoãn tới sau UAT.
|
||||
- BÀI-HỌC: thiếu component thư-viện KHÔNG chặn việc — `<details>/<summary>` native + badge `<div>` là đường vòng đã kiểm-chứng ở Plan AA; chọn Phase-1-FE-only giữ quyền đổi ý về schema.
|
||||
- BẤT-NGỜ: không đo được quy-mô dữ-liệu thật (SSH prod fail auth) nên phải ƯỚC ~50-200 dự-án × 5-15 phiếu — quyết-định kiến-trúc đứng trên con số ước, có khai rõ.
|
||||
→ substring:"340px_1fr_360px"
|
||||
|
||||
**[vừa] B2-D — Plan AI: 4 case công-nghiệp cho hạ-tầng RAG 5 dự-án**
|
||||
- VIỆC: nghiên-cứu 7Q cách chia-sẻ RAG cho 5 dự-án cùng máy.
|
||||
- KẾT-LUẬN: 4 case — Cursor (Merkle tree + Turbopuffer + tái-dùng 92% similarity, khoá dẫn-xuất từ commit hash) · Cline Memory Bank (markdown + JIT + git-native) · Continue.dev Hub (YAML slug) · Sourcegraph Cody (phản-mẫu: BỎ embedding về Search API + graph IR cho 100K+ repo). 3 khuôn multi-tenant (pool / silo / hybrid base+delta). Chốt Pattern C — MCP global cấp user, 1 server localhost phục-vụ 5 dự-án, KHÔNG theo khuôn VPS-team.
|
||||
- BÀI-HỌC: khuôn tốt nhất cho team 5 dev KHÔNG phải khuôn tốt nhất cho 1 người 5 dự-án — quy-chiếu về đúng hình-thái người dùng trước khi bê kiến-trúc về.
|
||||
- BẤT-NGỜ: Voyage AI cho 200M token/tháng miễn phí, đủ cho cả 5 dev ⇒ toàn bộ bài toán chi-phí thu về còn tiền VPS ($15/tháng), làm đổ luôn phép so "$20 vs Qdrant cloud $50".
|
||||
→ substring:"Turbopuffer"
|
||||
|
||||
## Phụ-lục token (coverage)
|
||||
*(chép TRỌN token-list section investigator-codebase/2026-05-q2 của proposal curator — lưới coverage-diff)*
|
||||
|
||||
- **gotcha#:** **0 token** (file không dẫn số gotcha nào — chỉ dẫn chéo "pattern memory: S23 t3 lookup bug")
|
||||
- **surprise (nhãn nguyên-văn):** "Surprise: `EntityType.Workflow=5` enum value design-only, unused 4+ mig history" · "**Surprise:** Voyage AI **200M tokens/month free tier** cover 5 devs comfortably" · "**Surprises:** (a) ApprovalWorkflows Prod ZERO ApplicableType=3 (b) `RejectedAtStepIndex` Contract line 40 DEPRECATED chưa marked obsolete drift với PE line 50 (c) ContractType 7 variants vs ApprovalWorkflow generic ApplicableType=3" · "Q1-DB state Prod SSH fail (auth), fallback code inspect" (×2 entry) · "**MISSING MaxLength + navigation tới PE**" · "shadcn fe-user component GAP **THIẾU Accordion/Collapsible/Tree/Card/Badge**"
|
||||
- **guard:** `ConflictException` (Contract V2 chưa có nhánh — nêu dạng thiếu) · nhánh rẽ `if (evaluation.ApprovalWorkflowId is Guid awId) ApproveV2Async else ApproveV1Legacy` (`:167-171`) · "MUST coexist V1+V2, KHÔNG drop V1" (rào tiền-lệ Plan F) · Auth JWT RS256 + role scope + document-level ACL (thiết-kế RAG) · Risk LOW / Risk MEDIUM (ContractType discriminator)
|
||||
- **root-cause:** ① Bug1 — "FE filter skip Header updates OR Schema gap (Header.Update collision: budget vs section 2 edit)" ② Bug2 — "Handler **intentionally skip logging** (no companion audit table like PurchaseEvaluationApprovals for return history)"
|
||||
- **file:line:** `PurchaseEvaluationFeatures.cs:379-387` · `PurchaseEvaluationWorkflowService.cs:215-378` (ApplyReturnModeAsync) · `TransitionAsync:100` (LogTransitionAsync) · `PurchaseEvaluationChangelog.cs` (EntityType=Workflow(5)) · `ListPurchaseEvaluationChangelogsQueryHandler:1050-1064` · `Master/Project.cs:5-14` · `PurchaseEvaluationsListPage.tsx:133` + `:199-252` · `Contract.cs:8-58` (:12 Phase, :22 WorkflowDefinitionId, :39 CurrentWorkflowStepIndex, :40 RejectedAtStepIndex) · `ContractPhase.cs:14-28` · `ContractWorkflowService.cs:23-219` (:113-126 match Dept+PositionLevel, :148-155 gen mã HĐ) · `20260508053749_AddApprovalWorkflowsV2.cs:14-127` · `20260508072821_AddApprovalWorkflowIdToPurchaseEvaluation.cs:14-31` · `20260508074937_AddCurrentApprovalLevelOrderToPe.cs:14-17` · `PurchaseEvaluationWorkflowService.cs:446-634` (:484-495, :522-546, :561-602, :605-633) + `:167-171` · `ApprovalWorkflowLevels` entity `:86-114` · PE `RejectedAtStepIndex:50`
|
||||
- **định-danh khác:** tag schema `[audit, phase-9, contract]` · `source_path: solution_erp/audit/investigator-plan-b-contract-v2-preflight-2026-05-22` · token-cost 28k/30k/40k/25K · prod 35 PE·17 V2·4 V1 (entry q1) vs "7 Contracts 100% V1Pinned" (entry này) · Mig 32 tên đề-xuất `AddProjectPackageTable` (Plan AG) vs Mig 32 thực-dụng cho Contract (Plan B) — **2 nghĩa của "Mig 32" trong CÙNG file** (xem `_lienfile-notes-batchB.md` #4)
|
||||
@ -0,0 +1,43 @@
|
||||
# investigator-codebase/2026-05-q3 — gist
|
||||
distill-gen: 1
|
||||
source-verbatim: .claude/agent-memory/investigator-codebase/archive/2026-05-q3.md (4 entry → 4 cụm, 2 cụm là housekeeping/bản-sao)
|
||||
pointer-style: substring
|
||||
|
||||
## TỔNG-HỢP (3-5 ý)
|
||||
- Diary khai việc ≠ bằng-chứng công-cụ: các entry cũ ghi "spawn Investigator" kèm chi-phí 28-40K token, nhưng trong S27 spawn agent type `investigator` NOT FOUND ⇒ hồ-sơ spawn của chính vai có thể sai — tạm tin + GẮN CỜ nghi-vấn (token loại Fidelity, giữ nguyên không tự phán).
|
||||
- Pre-flight hạ-tầng PHẢI spawn: miss CRITICAL "Qdrant Web UI static thiếu" bị người dùng cuối (anh) phát-hiện qua trình duyệt thay vì lượt pre-flight — tuyên "X hoạt động" trong docs thì phải có ai đó đã CHẠM vào X.
|
||||
- Bug 1 changelog chốt là lỗi bộ lọc FE (HistoryTab lọc cứng TraLai-only): lượt audit trước đoán đúng nhưng phải sang phiên sau mới cầm được bằng-chứng FE; CI run #215 FAIL (SQLite tie-break tái phát) → #216-#221 PASS liền chuỗi; gotcha #48 còn nợ docs.
|
||||
- Quy-trình curate tự nhân bản sao: baseline 2026-05-11 xuất hiện trong 2 file archive khác nhau của cùng vai — entry housekeeping nên nằm ở header file, không chiếm slot dòng thời-gian.
|
||||
|
||||
## Entries
|
||||
|
||||
**[cao] B3-A — S27: hai bài học, một là tự nghi-ngờ hồ-sơ của chính mình**
|
||||
- VIỆC: hồi-cố phiên S27 (em-main solo, không spawn) + rà lại các entry S20-S26 từng khai "spawn Investigator".
|
||||
- KẾT-LUẬN: (i) miss CRITICAL: nếu có spawn pre-Plan A.3 thì đã bắt "Qdrant Web UI static thiếu" TRƯỚC khi em-main viết link `localhost:6333/dashboard` vào `rag-dashboard.ps1` — anh phát-hiện qua trình duyệt rồi báo ngược. (ii) Trong S27, spawn agent type `investigator` NOT FOUND ⇒ registry chưa từng nạp trong phiên đó; nhưng diary S23 t1 K0 / S25 t1 / S26 Plan AG / Plan AI đều ghi "spawn Investigator" kèm chi-phí 28-40K token. Hai khả-năng: (a) phiên trước nạp được (bản Claude Code cũ / định-dạng khác / hot-reload sau khi mở UI `/agents`), hoặc (b) em-main quy-sai, thực ra spawn `general-purpose`. Kết: tạm tin entry cũ nhưng GẮN CỜ nghi-vấn cho lần điều-tra sau.
|
||||
- BÀI-HỌC: kiểm-tra phụ-thuộc-ngoài (binary ≠ web UI ở repo khác) là việc của lượt pre-flight, không phải của người dùng cuối; tuyên "X hoạt động" trong tài-liệu thì phải có ai đó đã CHẠM vào X.
|
||||
- BẤT-NGỜ: vai tự phát-hiện hồ-sơ spawn của chính nó có thể sai — chi-phí token ghi trong diary không chứng-minh danh-tính agent đã chạy. Ca "diary khai việc mà không khai bằng-chứng công-cụ" — đúng loại rủi-ro Fidelity mà H2 phải soi.
|
||||
→ substring:"rag-dashboard.ps1"
|
||||
|
||||
**[vừa] B3-B — S25 wrap: chốt root-cause 2 bug + chuỗi CI hồi phục**
|
||||
- VIỆC: khép phiên S25 sau audit ~28K: xác nhận nguyên-nhân 2 bug rồi em-main tự làm 6 kế-hoạch tiếp.
|
||||
- KẾT-LUẬN: Bug 1 = handler ĐÃ log (Header+Update) nhưng HistoryTab lọc cứng chỉ TraLai nên loại mất; Bug 2 = `ApplyReturnModeAsync` 4 mode không `Changelog.Add()`, chỉ người gọi log chuyển-phase. Đường vá: BE thêm log + FE nới filter + tách badge Decision. 7 commit `e23f51c..506cada`; run #215 FAIL (test Plan M vỡ vì SQLite tie-break tái phát) → #216-#221 PASS liền chuỗi; gotcha #48 còn nợ docs.
|
||||
- BÀI-HỌC: "em-main solo từ Plan AC" vì chuỗi lý-luận xuyên tầng — implementer sẽ TỪ CHỐI, nên phân việc phải xét độ-xuyên-tầng trước khi xét khối-lượng.
|
||||
- BẤT-NGỜ: bản này chốt Bug 1 là lỗi bộ lọc FE (giả-thiết ở lượt audit) — lượt audit trước đoán đúng nhưng chưa cầm được bằng-chứng FE, phải sang phiên sau mới xác nhận.
|
||||
→ substring:"e23f51c"
|
||||
|
||||
**[thấp] B3-C + B3-D — housekeeping & bản sao** *(rút narrative — token sống ở phụ-lục)*
|
||||
- VIỆC: ghi lại phiên curate S29-era + chép lại baseline khởi-tạo agent 2026-05-11.
|
||||
- KẾT-LUẬN: curate: 10 entry S21→S24 chuyển sang `archive/2026-05-q1.md`, KEEP S25/S26/setup, MEMORY 34.9 KB → mục-tiêu 20-22 KB; baseline: 44 gotcha + 14 memory + 6 skill + 27 mig + 81 test, "No investigations performed yet".
|
||||
- BÀI-HỌC: entry housekeeping nên nằm ở header file archive, không chiếm slot dòng thời-gian hoạt-động — chính nó gây trùng khi curate lần sau.
|
||||
- BẤT-NGỜ: baseline 2026-05-11 xuất-hiện 2 lần trong 2 file archive khác nhau của cùng vai ⇒ bản sao nhân lên bởi chính quy-trình curate, không phải bởi việc thật (trùng 100% với entry đầu q1 — xem `_lienfile-notes-batchB.md` #1).
|
||||
→ substring:"Awaiting first SendMessage"
|
||||
|
||||
## Phụ-lục token (coverage)
|
||||
*(chép TRỌN token-list section investigator-codebase/2026-05-q3 của proposal curator — lưới coverage-diff)*
|
||||
|
||||
- **gotcha#:** `#48` (SQLite tie-break — "pending docs")
|
||||
- **surprise (nhãn nguyên-văn):** không có nhãn "Surprise:" — token cùng loại: "Investigator pre-flight CRITICAL miss" · "spawn `investigator` agent type NOT FOUND trong session S27" · "**For now: trust prior memory entries but FLAG uncertainty cho future investigation.**" · "Possibility: (a) … hoặc (b) Em main misattribute - actually spawn `general-purpose` agent default" · "runs #215 FAIL Plan M tests SQLite tie-break re-emerge"
|
||||
- **guard:** "**pre-flight infrastructure audit MUST spawn Investigator** trước khi em main claim 'X work' trong docs" · "cross-stack reasoning Implementer would REFUSE" · Rule §6.5 (trong header các file chị em; ở file này là "Patterns proven + Active workflow schemas foundation preserved untouched")
|
||||
- **root-cause:** ① "Bug 1 Budget Adjust Handler ĐÃ log Changelog (Header+Update) nhưng FE HistoryTab filter strict TraLai-only loại" ② "Bug 2 ApplyReturnModeAsync 4 mode KHÔNG add Changelog.Add() — chỉ caller LogTransitionAsync log phase transition" ③ (meta) "registry chưa bao giờ load trong session này"
|
||||
- **file:line:** **0 token file:line** trong toàn file (chỉ tên file `rag-dashboard.ps1` + URL `localhost:6333/dashboard`)
|
||||
- **định-danh khác:** commit range `e23f51c..506cada` (7 commit) · CI run `#215` FAIL → `#216`-`#221` PASS · MEMORY 34.9 KB → 20-22 KB · baseline "44 gotchas + 14 memory entries + 6 skills + 27 mig + 81 test pass cumulative" (trùng q1) · token-cost ~28K
|
||||
@ -0,0 +1,50 @@
|
||||
# investigator-codebase/2026-05-q4 — gist
|
||||
distill-gen: 1
|
||||
source-verbatim: .claude/agent-memory/investigator-codebase/archive/2026-05-q4.md (3 entry + 1 header-sự-kiện integrity → 4 cụm)
|
||||
pointer-style: substring
|
||||
|
||||
## TỔNG-HỢP (3-5 ý)
|
||||
- Trỏ-tới ≠ tồn-tại: MEMORY.md từng trỏ file archive này khi nó VẮNG trên đĩa — nội-dung chỉ còn trong git `d2f52ba`, dựng lại S69; lô S29-S37 vẫn khai là chỉ nằm trong git chưa kéo về đĩa. Chỉ mục trí-nhớ phải kiểm bằng phép CHẠM ĐĨA.
|
||||
- Mỗi chỉ-số phải khai kèm CÁCH ĐẾM: DbSet=77 nhưng bảng SQL=84 (đếm `.ToTable()` trong ModelSnapshot, KHÔNG đếm DbSet — sót 7 Identity); docs sai CẢ HAI CHIỀU trong cùng lượt đo (FE pages 53<65 under-count, menu key 85>53 over-count); 3 số tin được nhất = mig / gotcha / git.
|
||||
- Khuôn tốt nhất khi có module anh em đã chạy thật = đọc thẳng handler của nó (`ProposalFeatures.cs:403-486`) rồi liệt phần thiếu; nhưng spec tự mâu-thuẫn phải gắn cờ ngay: `ApprovalWorkflow.cs:72` khai Level KHÔNG OR-of-N (ngược trí-nhớ cũ), enum `ApplicableType` THIẾU Travel + mig `ExtendApplicableTypeForWorkflowApps` Up/Down RỖNG.
|
||||
- Port khuôn phải soi hình-thái repo đích: BVAAU 7-agent là bản vẽ Phase 0 codebase RỖNG chưa thực-chiến — SOLUTION_ERP chọn split backend/frontend (thay domain/infra) cho hợp 2 FE.
|
||||
|
||||
## Entries
|
||||
|
||||
**[cao] B4-A — META: con-trỏ chết đã phục-hồi (file archive vắng đĩa, nội-dung git-only)**
|
||||
- VIỆC: S69 phát-hiện `MEMORY.md` dẫn `archive/2026-05-q4.md` (kèm stub curate S40 + footer) mà đĩa không có file.
|
||||
- KẾT-LUẬN: nội-dung thực nằm git-only ở `d2f52ba`; file được dựng lại, chứa 3 entry FIFO tháng 5 tràn ra; lô S29-S37 VẪN nằm nguyên trong git `d2f52ba` chứ chưa kéo về đĩa.
|
||||
- BÀI-HỌC: trỏ-tới ≠ tồn-tại. Chỉ mục trí-nhớ phải được kiểm bằng phép chạm đĩa — đọc chỉ mục thấy "có mục" là cảm-giác đủ giả.
|
||||
- BẤT-NGỜ: hai phần của cùng một lô archive kết-thúc ở HAI NƠI khác nhau (đĩa + git) mà chỉ mục chỉ khai một — còn một nửa dữ-liệu chỉ tra được nếu biết đúng commit (đầu mối cho lead — xem `_lienfile-notes-batchB.md` #6).
|
||||
→ substring:"d2f52ba"
|
||||
|
||||
**[cao] B4-B — S40 STATE GROUNDING: đo lại 7 con số, docs sai 4**
|
||||
- VIỆC: đối-chiếu 7 chỉ-số trạng-thái giữa docs và mã nguồn.
|
||||
- KẾT-LUẬN: khớp: Migrations=40 (`.../Persistence/Migrations/*.cs`, cuối = `AddAttendances`) · Gotchas=55 (đếm `### N.`) · git clean. Lệch: DbSet=77 nhưng bảng SQL=84 (77 + 7 Identity — docs 84 ĐÚNG, phép đếm DbSet sai −7) · Endpoints=211 (docs ~223) · FE pages 36+29=65 (docs 53, đếm thiếu) · Menu key=53 hằng (docs 85, đếm thừa). 3 số tin được nhất = mig / gotcha / git.
|
||||
- BÀI-HỌC: đếm bảng phải đếm `.ToTable()` trong ModelSnapshot, KHÔNG đếm DbSet — DbSet bỏ sót bảng Identity. Rộng hơn: mỗi chỉ-số cần khai kèm cách đếm, vì cùng một tên ("số bảng") có nhiều phép đếm cho nhiều số.
|
||||
- BẤT-NGỜ: docs SAI theo cả hai chiều trong cùng một lượt đo (under-count FE pages 53<65, over-count menu key 85>53) ⇒ không thể suy "docs luôn lạc-hậu theo hướng thiếu".
|
||||
→ substring:"AddAttendances"
|
||||
|
||||
**[cao] B4-C — P11-A WorkflowApps pre-flight: khuôn có sẵn, spec tự mâu-thuẫn 2 chỗ**
|
||||
- VIỆC: pre-flight nối 4 module Leave/OT/Travel/Vehicle vào bộ máy duyệt.
|
||||
- KẾT-LUẬN: schema pin sẵn từ Mig 39 (`Office/{Module}.cs` đều có `ApprovalWorkflowId?` + `CurrentApprovalLevelOrder?` + `WorkflowAppStatus` 5-state khớp ProposalStatus); code mới là SKELETON `Application/Office/WorkflowAppsFeatures.cs:11-15` (chỉ Create+List). Mẫu soi gương HOÀN HẢO = `ProposalFeatures.cs:403-486` (flatten `Steps.OrderBy(Order).SelectMany(Levels.OrderBy(Order))` → chỉ số cấp toàn-cục → `allLevels[CurrentApprovalLevelOrder-1]` → khớp actor → UPSERT opinion → tăng con-trỏ/DaDuyet). Thiếu: 4 bảng `{Module}LevelOpinion` (Mig 41+), 3 route/controller, 4 seed WF, FE Detail+Opinion.
|
||||
- BÀI-HỌC: khi có module anh em đã chạy thật, khuôn tốt nhất là đọc thẳng handler của nó rồi liệt phần thiếu — rẻ hơn thiết-kế lại từ spec.
|
||||
- BẤT-NGỜ: 2 mâu-thuẫn gắn cờ ngay: (a) `ApprovalWorkflow.cs:72` khai Level KHÔNG OR-of-N (1 ApproverUserId/Level) — NGƯỢC trí-nhớ cũ "OR-of-N", đòi verify lại; (b) enum `ApplicableType` THIẾU Travel (có Leave=5/OT=6/Vehicle=7/ItTicket=8) và mig `ExtendApplicableTypeForWorkflowApps` có Up/Down RỖNG (enum-only, không đụng DB).
|
||||
→ substring:"ExtendApplicableTypeForWorkflowApps"
|
||||
|
||||
**[vừa] B4-D — S39: rút khuôn 7-agent từ BVAAU**
|
||||
- VIỆC: đọc 8 file `.claude/agents/` của BVAAU (~22K) để tách trục vai.
|
||||
- KẾT-LUẬN: 4→7 trục research(2)/implement(2)/quality(3); ranh giới: repo interface = domain, EF config = infra, test = test-specialist; cả 7 vai đều có 5 RAG MCP (+`search_code` BM25 +`store_memory` +`list_projects`). SOLUTION_ERP giữ 6 skill và chọn split backend/frontend (thay domain/infra) cho hợp 2 FE.
|
||||
- BÀI-HỌC: port khuôn phải soi hình-thái repo đích — trục chia của dự-án nguồn không tự đúng ở đích.
|
||||
- BẤT-NGỜ: BVAAU Phase 0 codebase RỖNG ⇒ toàn bộ khuôn 7-agent là bản vẽ chưa qua thực-chiến; "VIPIX guide" được dẫn nhưng không verify được vì file miss.
|
||||
→ substring:"BVAAU"
|
||||
|
||||
## Phụ-lục token (coverage)
|
||||
*(chép TRỌN token-list section investigator-codebase/2026-05-q4 của proposal curator — lưới coverage-diff)*
|
||||
|
||||
- **gotcha#:** **0 token dạng `#N`** — chỉ có phép đếm "Gotchas=**55** (`### N.`)"
|
||||
- **surprise (nhãn ⚠️/đối-lập nguyên-văn):** "⚠️ `ApprovalWorkflow.cs:72` nói Level **KHÔNG OR-of-N** (1 ApproverUserId/Level) — KHÁC memory cũ 'OR-of-N', verify lại" · "⚠️ enum `ApplicableType` THIẾU Travel (có Leave=5/OT=6/Vehicle=7/ItTicket=8); `ExtendApplicableTypeForWorkflowApps` mig empty Up/Down (enum-only)" · "DbSet=77 nhưng **SQL tables=84** … DbSet count sai −7" · "Endpoints=**211** (docs ~223)" · "FE pages … =**65** (docs 53 under-count)" · "Menu keys=**53** const (docs 85 over-count)" · "BVAAU Phase 0 codebase RỖNG → aspirational template chưa battle-test" · "VIPIX guide claim KHÔNG verify được (file miss)" · header: "the file was absent on disk (content was git-only `d2f52ba`)"
|
||||
- **guard:** "Boundary: repo interface=domain, EF config=infra, test=test-specialist" · "Verbatim — byte-exact, no reflow" (rào chép archive) · "3 số tin cậy nhất = mig/gotcha/git" (rào tin-cậy chỉ-số)
|
||||
- **root-cause:** "tables phải count ToTable KHÔNG DbSet" (phép đếm sai nguồn ⇒ lệch −7 bảng Identity)
|
||||
- **file:line:** `Application/Office/WorkflowAppsFeatures.cs:11-15` · `ProposalFeatures.cs:403-486` · `ApprovalWorkflow.cs:72` · `Office/{Module}.cs` (không số dòng) · `.../Persistence/Migrations/*.cs` · `.claude/agents/` (8 file ~22K)
|
||||
- **định-danh khác:** commit `d2f52ba` (nguồn git-only) · Mig 39 · Mig 41+ (dự-kiến) · mig `AddAttendances` (mới nhất) · mig `ExtendApplicableTypeForWorkflowApps` (Up/Down rỗng) · `WorkflowAppStatus` 5-state ↔ `ProposalStatus` · tag `[cross-project, bvaau, port]` · `[state-grounding, docs-drift, s40]` · `[pre-flight, p11-a, workflowapps]`
|
||||
@ -0,0 +1,58 @@
|
||||
# reviewer/2026-05-q1 — gist
|
||||
distill-gen: 1
|
||||
source-verbatim: .claude/agent-memory/reviewer/archive/2026-05-q1.md (7 entry [4 review thật + 3 self-review no-spawn] → 5 cụm)
|
||||
pointer-style: substring
|
||||
|
||||
## TỔNG-HỢP (3-5 ý)
|
||||
- Rủi-ro không tập trung ở phần phức-tạp mà ở phần bị coi là ĐÃ XONG: cùng lượt review Plan K vừa xác nhận wire đúng chỗ khó (UPSERT-trước-skip) vừa bắt endpoint zombie thân rỗng `Task.CompletedTask`; sentinel tạm chỉ được dùng khi có chunk dọn ĐẶT TÊN TRƯỚC.
|
||||
- Vá theo báo-cáo lỗi = vá 1/5: Plan N sửa 1 site, Plan O phải cascade 4 site còn lại cùng khuôn + 3 test hồi-quy; record Controller phải soi gương record Command — lệch field là gốc của 2 ngày lỗi prod F1+F2.
|
||||
- ROI spawn đo được: 1 lượt ~25K cho chunk nặng xuyên tầng thì lãi; 4 lượt polish ≈ 100K token đổi gần như 0 phát-hiện ⇒ luật: spawn cho wire BE/migration/xuyên-tầng, bỏ qua polish <30 phút. Chuỗi test giữ xanh 84 → 104 → 106/106 → 111.
|
||||
- Dọn prod xong IIS recycle làm seeder chạy lại thành vòng lặp — gốc không ở lệnh xoá mà ở seeder không công-tắc; Plan T cờ `DemoSeed:Disabled` mới thật sự đóng.
|
||||
- PASS ≠ hết việc: "Smart Friend guard" bật cả trong lượt PASS-0-issue, rủi-ro thấp treo lại CÓ KHAI; gotcha #47 phát-hiện nhưng treo chờ anh quyết — không tự thêm vào docs.
|
||||
|
||||
## Entries
|
||||
|
||||
**[cao] B5-A — Plan K: PASS nhưng để lại endpoint xác sống**
|
||||
- VIỆC: review đối-kháng trước K3 cho Mig 31 (đổi chỗ + đổi nghĩa cờ skip-to-final) — 11 file BE, +4093/−83 LOC (Designer migration chiếm 3938 dòng), commit `eb106f2..56868bf..db66253..364aef6`.
|
||||
- KẾT-LUẬN: PASS với 2 Major + 2 Minor. Wire đúng: nhánh Approver F2 đặt `:477` — SAU UPSERT opinion `:441-468`, TRƯỚC đẩy con-trỏ `:502` ⇒ chữ ký người duyệt ghi trước khi nhảy terminal; guard `skipToFinal` V1 đặt ở NGƯỜI GỌI (`:147-149` TransitionAsync) nên V1 giữ chữ ký cũ. sqlcmd Dev xác nhận 7 cột `Allow*` + `Users.AllowDrafterSkipToFinal` drop (count=0), snapshot regen sạch, `HasDefaultValue(false)` có nối, Mig 31 `Up()` đảo tay ADD→DROP đúng khuôn. Major-1: endpoint zombie `PATCH /api/users/{id}/allow-skip-final` còn sống, thân rỗng (`Task.CompletedTask`) — admin tick, BE nuốt im ⇒ đòi chunk K5 dọn. Major-2: comment cũ `ApprovalWorkflow.cs:78` vẫn nói cờ "đã move sang Users…" trong khi `:107-113` là prop mới. Minor: `PurchaseEvaluationFeatures.cs:401` còn chữ "Drafter"; `ApprovalWorkflowConfiguration.cs:22` chưa ghi Mig 31.
|
||||
- BÀI-HỌC: sentinel tạm kèm lời hứa "chunk sau dọn" là bẫy — chunk sau đổi phạm-vi thì sentinel hoá xác sống. Muốn dùng phải có chunk dọn ĐẶT TÊN TRƯỚC, đứng trước cả chunk sửa test.
|
||||
- BẤT-NGỜ: cùng lượt review vừa xác nhận wire ĐÚNG chỗ khó (thứ tự UPSERT-trước-skip) vừa bắt lỗi chỗ dễ (endpoint quên xoá) — rủi-ro không nằm ở phần phức-tạp mà ở phần bị coi là đã xong.
|
||||
→ substring:"Task.CompletedTask"
|
||||
|
||||
**[cao] B5-B — Plan N→U: 8 kế-hoạch self-review, 3 phản-mẫu đắt**
|
||||
- VIỆC: em-main tự review liên-tiếp 8 kế-hoạch (N sửa lookup per-NV, O cascade 4 site, P Controller body, Q CSS, R+S+T5 xoá prod, T cờ DemoSeed, U sidebar).
|
||||
- KẾT-LUẬN: Plan N vá `PurchaseEvaluationFeatures.cs:765`; Plan O phát-hiện 4 site còn lại cùng khuôn (`EnsureCanRejectV2Async:201` · `ApplyReturnModeAsync:248` · `EnsureEditableForDetailsAsync:72` · `AdjustBudgetCommandHandler:311`) + 3 test hồi-quy `PurchaseEvaluationPerNvLookupRegressionTests.cs`; Plan P thêm 3 field vào `TransitionPeBody:267` — gốc của 2 ngày lỗi prod F1+F2; Plan R+S+T5 xoá ~720 row prod mà BE vẫn khoẻ nhờ giữ V1 active; Plan T thêm cờ `DemoSeed:Disabled` tắt 5 hàm seed demo. Tổng: 111 test (+7 so 104), 31 mig, 47 gotcha.
|
||||
- BÀI-HỌC: vá theo báo-cáo lỗi = vá 1/5; refactor đổi hình-dạng khoá phải LIỆT KÊ TOÀN BỘ site tra-cứu cùng khuôn rồi vá một lượt. Record ở Controller phải soi gương record Command — lệch field là mất dữ-liệu câm.
|
||||
- BẤT-NGỜ: dọn prod xong thì IIS recycle làm seeder chạy lại ⇒ dữ-liệu demo mọc lại thành vòng lặp; gốc không ở lệnh xoá mà ở seeder không công-tắc — Plan T mới thật sự đóng.
|
||||
→ substring:"PerNvLookupRegressionTests"
|
||||
|
||||
**[vừa] B5-C — Plan M: PASS, hẹp có chủ-đích, còn lệch chữ xuyên module**
|
||||
- VIỆC: review trước push 3 commit `c2afef2..508b17a..4dd6f9c` — sửa ca biên "đang ở Bước 1" + đổi nhãn hiển-thị Phase=TraLai.
|
||||
- KẾT-LUẬN: PASS. Hot path `Service.cs:287-333`: thay "đặt TraLai + xoá con-trỏ + return" bằng "đặt con-trỏ (0,1) + summary 'không lùi được' → rơi xuống reset SLA `:364` → GIỮ ChoDuyet"; người gọi `:94-100` truyền Phase mới vào log ⇒ ContextNote khớp assert test. Drafter mode `:268-275` giữ nghĩa; Assignee `:335-360` vẫn ném nếu không khớp; F2/F3/F4 KHÔNG đụng. Build 0 err 2 warn, test 106/106 (58 Domain + 48 Infra, +2), npm build 2 app sạch TS, không `--no-verify`. 2 test mới phủ đúng 2 ca biên.
|
||||
- BÀI-HỌC: phân-biệt động-từ hành-động ("Trả lại" trên nút) với nhãn kết-quả ("Cần chỉnh sửa lại" cho Phase=98) — đổi tên phải chọn đúng lớp, nhầm lớp thì UI mâu-thuẫn.
|
||||
- BẤT-NGỜ: phạm-vi hẹp để lại 8 chỗ chữ "Trả lại" chưa đổi + 2 module anh em (`contracts.ts:29`, `budget.ts:20`) vẫn `98:'Trả lại'` ⇒ deploy chung thì 3 module gọi khác nhau cho cùng một trạng-thái.
|
||||
→ substring:"c2afef2"
|
||||
|
||||
**[vừa] B5-D — Plan AA: PASS sạch + bài toán ROI của việc spawn** *(gộp 2 entry cùng Plan: pre-commit + post-wrap)*
|
||||
- VIỆC: review trước commit chunk A (BE+Layout) + chunk B (FE trang ma-trận, implementer làm), rồi ghi nhận 4 vòng polish sau đó KHÔNG spawn.
|
||||
- KẾT-LUẬN: PASS 0 critical / 0 major / 0 minor; wire xuyên suốt Controller `:21-26` → Handler `:114-117` (`request.IsUserSelectable is bool ius → Where(...)`) → FE `:29-33` gọi thật (không mock) → route `App.tsx:38` + `Layout.tsx resolvePath:81-86`; 0 migration (cột có từ Mig 25); seed menu idempotent (PK trên Key ⇒ ToDictionary an toàn); `[Authorize]` trần `:18` được GIỮ (gotcha #44); quyền 7 role qua `SeedPurchaseEvaluationPermissionDefaultsAsync:1573`. Rủi-ro thấp ghi nhận: non-admin truyền `isUserSelectable=false` xem được workflow chưa ghim — dữ-liệu không nhạy-cảm, không chặn commit. Plan AA tổng ~620 LOC / 7 commit `a1a910f..ee0902a`, spawn 1 lần ~25K.
|
||||
- BÀI-HỌC: gom review một lượt cho chunk nặng (wire BE / migration / xuyên tầng), bỏ qua với polish <30 phút (CSS/màu/layout) — 4 lần spawn polish ≈ 100K token đổi gần như không phát-hiện.
|
||||
- BẤT-NGỜ: "Smart Friend guard" khai VẪN BẬT ngay trong lượt PASS-0-issue, kèm 3 ghi-chú rủi-ro treo — PASS nghĩa là "không có gì đáng chặn", không phải "không còn gì"; bất đối-xứng cấu-trúc 2 app (fe-user có StaticLeaf "Hộp thư", fe-admin không) được CHẤP-NHẬN thay vì ép mirror.
|
||||
→ substring:"a1a910f"
|
||||
|
||||
**[vừa] B5-E — chế-độ UAT không spawn (S21/S22): 3 phản-mẫu + 1 gotcha treo**
|
||||
- VIỆC: 2 phiên em-main tự review theo `feedback_uat_skip_verify` (bỏ `dotnet test` mỗi chunk nhưng vẫn build + npm build 2 app); 11-12 commit cộng dồn.
|
||||
- KẾT-LUẬN: 4 điểm xác nhận: phân quyền V2 siết theo `actor.UserId` (bỏ mệnh-đề lỏng `|| ApprovalWorkflowId != null`); helper `EnsureCanRejectV2Async` chặn request giả mạo `PATCH /transitions decision=Reject` (phòng-thủ 2 lớp FE+BE); handler chỉnh ngân-sách 3 tầng (Drafter Nháp/Trả-lại · Approver ChoDuyet · Admin) siết theo cờ `AllowApproverEditBudget`; mật-khẩu ≥12 ký-tự. Trạng-thái: 104 test / 30 mig / 46 gotcha / ~146 endpoint / 33 user.
|
||||
- BÀI-HỌC: 3 phản-mẫu — (a) mở rộng quyền theo mặc-định (S22+4) phải sửa thành admin-tick-từng-slot (S22+5); (b) giả-định field tồn-tại khi render (`PeDetailBundle.changelogs`) → TS2339, xem kiểu trước; (c) script PS 5.1 có dấu tiếng Việt → parser lỗi (gotcha #30).
|
||||
- BẤT-NGỜ: vai reviewer tự khai KHÔNG spawn 2 phiên liên-tiếp mà vẫn viết entry đầy-đủ; ghi nhận CICD Monitor đứng thay vai reviewer sau deploy; phát-hiện gotcha #47 (`paths-ignore` bỏ sót `agent-memory`) nhưng TREO chờ anh quyết, không tự thêm docs.
|
||||
→ substring:"paths-ignore"
|
||||
|
||||
## Phụ-lục token (coverage)
|
||||
*(chép TRỌN token-list section reviewer/2026-05-q1 của proposal curator — lưới coverage-diff)*
|
||||
|
||||
- **gotcha#:** `#45` (fix + 3 test hồi-quy, test-before §7) · `#44` (class-level `[Authorize]` bare — "protect", nhắc 2 lần) · `#47` ("paths-ignore agent-memory gap … pending bro decide" — **New gotcha discovered**) · `#30` (PS 5.1 Vietnamese diacritics)
|
||||
- **surprise (nhãn nguyên-văn dạng Anti-pattern/Pattern caught/Discovery):** "**Pattern caught: 'Transient sentinel pattern'** — đặt sentinel + comment commit chunk khác cleanup nhưng chunk đó scope SHIFT → zombie state" · "**Anti-patterns observed:** (a) Default scope expansion mistake S22+4 → S22+5 fix (b) History display field assumption … FAIL TS2339 (c) PS 5.1 Vietnamese diacritics gotcha #30 reinforced" · "**Plan N point 9 chỉ catch 1/5 sites**" · "**Plan O caveat #1 surfaced Plan P pre-existing bug**" · "**DbInitializer auto re-seed** loop — Plan R+S clean → IIS recycle → re-seed loop" · "**Anti-pattern observed S24 polish chunks**: UI/UX iteration thuần CSS/layout KHÔNG cần Reviewer spawn mỗi chunk" · "**Discovery #3 anomaly re-tested S24** … KHÔNG reinforce docs-only anomaly hypothesis" · "**Low note S24**: BE filter `IsUserSelectable=false` non-admin leak workflow chưa ghim" · "Non-admin pass `isUserSelectable=false` BE không block → workflow chưa ghim leak risk — Low severity"
|
||||
- **guard:** **"Smart Friend guard"** (bật/nhắc ở 6/7 entry; "KHÔNG lower bar") · `EnsureCanRejectV2Async` (BE helper chặn forge Reject, defense-in-depth FE+BE) · entry guard `:75-81` (đòi ChoDuyet mới Reject) · Reject guard `:81` · Admin bypass `:252-265` · anti-fiddle audit (scope drift 0%) · "No `--no-verify` bypass" (×2) · class-level `[Authorize]` bare `:18` giữ nguyên · `HasDefaultValue(false)` EF config · `MenuItemConfiguration HasKey(Key)` PK unique ⇒ ToDictionary an toàn · `PermissionGuard`/`usePermission` KHÔNG có (route guard đủ) · Cat-checklist 5 mục (Wire BE + Schema + Security + Code quality + Test) · `feedback_uat_skip_verify` (rào chế-độ) · `feedback_ef_migration_backfill_reorder` (rào ADD→DROP)
|
||||
- **root-cause:** ① "Transient sentinel pattern" ⇒ orphan endpoint `PATCH /api/users/{id}/allow-skip-final` silent NoOp (`Task.CompletedTask`) ② "Plan P Controller `TransitionPeBody:267` record +3 fields mirror `TransitionPurchaseEvaluationCommand` schema — **root cause 2 ngày prod bug F1+F2 wire fail**" ③ "DbInitializer auto re-seed loop … Plan T flag fix root cause" ④ "Plan N point 9 chỉ catch 1/5 sites → Plan O cascade fix"
|
||||
- **file:line:** `ApprovalWorkflow.cs:78` + `:107-113` · `PurchaseEvaluationFeatures.cs:401` · `:765` · `ApprovalWorkflowConfiguration.cs:22` · K2 F2 branch `:477` / UPSERT `:441-468` / advance `:502` / caller guard `:147-149` · test ref `:253` (K7-pending) · `Service.cs:287-333` · `:364` · `:268-275` · `:335-360` · caller `:94-100` · entry guard `:75-81` · `:81` · `:252-265` · `:105-132` · `EnsureCanRejectV2Async:201` · `ApplyReturnModeAsync:248` · `EnsureEditableForDetailsAsync:72` · `AdjustBudgetCommandHandler:311` · `TransitionPeBody:267` · Controller `:21-26` · Handler `:114-117` · `WorkflowMatrixViewPage :29-33` · `App.tsx:38` · `Layout.tsx resolvePath:81-86` · `SeedPurchaseEvaluationPermissionDefaultsAsync:1573` · `[Authorize]` bare `:18` · `PeListPanel.tsx:112` · `PeWorkflowPanel.tsx:270` / `:272` / `:315` / `:342` · `PeDetailTabs.tsx:152` / `:287` · `ApprovalWorkflowsV2Page.tsx:639` · `contracts.ts:29` · `budget.ts:20` · file test `PurchaseEvaluationPerNvLookupRegressionTests.cs`
|
||||
- **định-danh khác:** commit `eb106f2..56868bf..db66253..364aef6` · `c2afef2..508b17a..4dd6f9c` · `a1a910f..ee0902a` (`da218f1`/`4d60598`/`fbbd361`/`ee0902a`) · Run #210 sha=`ac2c859` (4/4 wire + bundle 2 app rotate) · diff 11 file BE +4093/−83 (Designer 3938 dòng) · test 84 → 104 → **106/106** (58 Domain + 48 Infra) → **111** · mig 29 → 30 → 31 · gotcha 45 → 46 → 47 · ~146 endpoint · 33 user · 2 test mới `ApplyReturnMode_OneLevel_AtStep1Level1_ResetsToBuoc1Cap1_KeepsChoDuyet` + `ApplyReturnMode_OneStep_AtStep1_ResetsToBuoc1Cap1_KeepsChoDuyet` · fe-admin npm build 1926 modules / 740ms / 0 TS err · ~720 row prod bị xoá (Plan R+S+T5) · spawn-cost ~25K/lượt vs ~100K nếu spawn 4 polish
|
||||
@ -0,0 +1,42 @@
|
||||
# reviewer/2026-05-q2 — gist
|
||||
distill-gen: 1
|
||||
source-verbatim: .claude/agent-memory/reviewer/archive/2026-05-q2.md (3 entry → 3 cụm)
|
||||
pointer-style: substring
|
||||
|
||||
## TỔNG-HỢP (3-5 ý)
|
||||
- Bề mặt lỗi là PHÉP ĐỌC NGÔN-NGỮ: "chú ý X" (anh gợi ý) bị đọc thành "BẮT BUỘC X" (chính sách) — em-main t2 tự ban luật xuyên dự-án không consent, anh bắt ở t4, luật ABANDONED; sinh hạng-mục kiểm MỚI Cat 6 authority-boundary (phân-biệt gợi-ý ⟂ mệnh-lệnh, gắn cờ mọi "BẮT BUỘC xuyên dự-án" nguồn tự-quyết).
|
||||
- Gotcha #48: SQLite frozen clock — nhiều `Changelog.Add()` cùng một `SaveChangesAsync` nhận CÙNG timestamp ⇒ `OrderByDescending(CreatedAt).FirstAsync()` không tất-định; bổ Cat 5: bộ lọc test phải phân-biệt bằng thứ khác ngoài mốc thời-gian (EntityType + Summary keyword). Bài về test bắt nguồn từ đặc-tính đồng-hồ của engine DB test — không xuất-hiện ở prod.
|
||||
- Phép chứng mirror 2 app nâng lên so SHA256 nguyên file (`21001E90...`) — rẻ, không cãi được; 6 commit S26 giữ nguyên baseline 111 test = UI thuần không mua thêm rủi-ro hồi-quy; củng-cố luật ROI: spawn cho việc nặng xuyên tầng, em-main tự lo polish 50-100 LOC.
|
||||
|
||||
## Entries
|
||||
|
||||
**[cao] B6-A — S28: "chú ý X" bị đọc thành "BẮT BUỘC X"**
|
||||
- VIỆC: ghi lại quỹ-đạo phiên S28 dưới góc reviewer (thực tế em-main solo, KHÔNG có việc review nào).
|
||||
- KẾT-LUẬN: t1 kết-luận ROI của RAG "ngắn hạn không đáng, dài hạn đổi chất" → t2 em-main TỰ BAN luật xuyên dự-án "ghi RAG mọi tương tác" khi chưa có đồng-ý của anh → t4 anh bắt lỗi, thu phạm-vi về kỷ-luật nội-bộ SOLUTION_ERP → t5 chốt quản-trị 3 lớp (4 nhóm mặc-định + danh-sách bỏ qua + tag schema bắt buộc `[lesson, phase-<N>, <bc>]`, phase ∈ {phase-9, phase-9plus, phase-10}, BC ∈ {contract, pe, budget, workflow, identity, form, infra}). Luật t2 ABANDONED.
|
||||
- BÀI-HỌC: thêm Cat 6 — kiểm ranh-giới thẩm-quyền: phân-biệt "anh gợi ý phương án X" (tham-khảo) với "anh chốt X" (mệnh-lệnh); gắn cờ mọi thứ dạng "BẮT BUỘC … xuyên dự-án" mà nguồn là em-main tự quyết. 5 nhóm kiểm cũ giữ nguyên, Cat 6 cộng thêm từ S28.
|
||||
- BẤT-NGỜ: sai-phạm không nằm ở kỹ-thuật mà ở phép đọc ngôn-ngữ — một chữ "chú ý" biến thành chính sách; và reviewer phát-hiện theo lối HỒI-CỐ, tức lúc xảy ra không có ai chặn.
|
||||
→ substring:"authority boundary"
|
||||
|
||||
**[vừa] B6-B — S25 Plan AB: đồng-hồ đóng băng của SQLite**
|
||||
- VIỆC: ghi từ-khoá đúc kết sau đợt vá changelog (`cdfd542` — PE chỉnh ngân-sách + trả-lại Người-chỉ-định có log).
|
||||
- KẾT-LUẬN: gotcha #48 — nhiều `Changelog.Add()` trong CÙNG một `SaveChangesAsync` nhận cùng dấu thời-gian ⇒ `OrderByDescending(CreatedAt).FirstAsync()` cho kết-quả không tất-định trên SQLite; kèm rủi-ro lặp lại của chế-độ UAT bỏ `dotnet test` khi refactor BE > 100 LOC.
|
||||
- BÀI-HỌC: bổ vào Cat 5: bộ lọc trong test phải phân-biệt bằng thứ khác ngoài mốc thời-gian (EntityType + từ khoá trong Summary).
|
||||
- BẤT-NGỜ: một bài về test lại bắt nguồn từ đặc-tính đồng-hồ của engine DB dùng trong test — thứ không xuất-hiện ở prod (SQL Server), chỉ lộ khi test đỏ ngẫu-nhiên.
|
||||
→ substring:"cdfd542"
|
||||
|
||||
**[vừa] B6-C — S26 Plan AG: PASS 0 issue, và bài ROI lặp lại**
|
||||
- VIỆC: review trước commit 3 chunk A+B+C (~25K token, 12 phép kiểm đối-kháng).
|
||||
- KẾT-LUẬN: PASS 0 issue; commit `0bf6c7e` 2 file +346/−116 LOC, hai bản mirror SHA256 IDENTICAL (`21001E90...`); wire = useMemo gom nhóm lồng + `<details>/<summary>` 2 tầng + lưu tập mở/đóng vào localStorage; 0 migration. 5 vòng polish AG2-AG6 sau đó em-main tự verify (SHA256 giống nhau + npm build 2 app + `dotnet test 111/111`), không spawn lại.
|
||||
- BÀI-HỌC: củng-cố luật phân-bổ: spawn reviewer cho việc nặng xuyên tầng (~370 LOC + 4 sub-agent phối hợp), em-main tự lo vòng polish 50-100 LOC.
|
||||
- BẤT-NGỜ: phép chứng "2 app giống nhau" nâng lên so SHA256 nguyên file thay vì đọc diff — rẻ và không cãi được; cả 6 commit S26 giữ nguyên baseline 111 test = thay đổi UI thuần không mua thêm rủi-ro hồi-quy.
|
||||
→ substring:"21001E90"
|
||||
|
||||
## Phụ-lục token (coverage)
|
||||
*(chép TRỌN token-list section reviewer/2026-05-q2 của proposal curator — lưới coverage-diff)*
|
||||
|
||||
- **gotcha#:** `#48` (SQLite **frozen clock** tie-break — mô-tả đầy đủ: "Multi-Changelog.Add same SaveChangesAsync transaction non-deterministic `OrderByDescending(CreatedAt).FirstAsync()`") · dải nền "Foundation gotchas **#17-#48**" (header)
|
||||
- **surprise (nhãn nguyên-văn):** "**Rule cũ ABANDONED**: 'RAG ghi mọi tương tác mandatory' S28 t2 over-reach" · "t4 bro caught mistake scope-down về SOLUTION_ERP self-discipline" · "em main t2 implicit interpret 'chú ý X' (bro suggestion) AS 'MANDATORY X' (em policy decision)" · "Reviewer perspective về S28 trajectory (em main solo, **KHÔNG actual review work**)" · "UAT skip `dotnet test` recurring risk khi BE refactor > 100 LOC"
|
||||
- **guard:** **Smart Friend guard** ("active S28+ cho Plan B Contract V2 wire pre-commit spawn (mandatory heavy diff > 50 LOC cross-stack)") · **Cat 6 — Authority boundary check** (MỚI) · Cat 5 bổ-sung "test filter discriminator beyond timestamp (EntityType + Summary keyword)" · 5-category checklist baseline (Wire BE + Schema + Security + Code quality + Test) UNCHANGED · tag schema **mandatory** `[lesson, phase-<N>, <bc>]` · "Cross-module security validation mirror pattern preserved" (header)
|
||||
- **root-cause:** "scope creep từ project-local → cross-project KHÔNG bro consent là **authority boundary violation**" ⇐ "implicit consent ('chú ý' / 'có thể') KHÔNG = explicit mandate ('BẮT BUỘC' / 'mandatory')"
|
||||
- **file:line:** **0 token file:line** (file này chỉ dẫn tên hàm/commit: `ApplyReturnModeAsync`, `cdfd542`)
|
||||
- **định-danh khác:** commit `0bf6c7e` (2 file +346/−116) · SHA256 mirror `21001E90...` · commit `cdfd542` · `dotnet test 111/111` (baseline 111 preserved, 6 commit S26, 0 prod regression) · spawn ~25K · phase enum {phase-9, phase-9plus, phase-10} · BC enum {contract, pe, budget, workflow, identity, form, infra} · gotcha `#48` chú-giải KHÁC batch A — xem `_lienfile-notes-batchB.md` #5
|
||||
@ -0,0 +1,95 @@
|
||||
# test-specialist/baseline-history — gist
|
||||
distill-gen: 1
|
||||
source-verbatim: .claude/agent-memory/test-specialist/archive/baseline-history.md (29 entry-heading + 1 dòng chuỗi → 10 cụm; curator khai 25 entry — chênh ghi ở phụ-lục "nguồn-đếm")
|
||||
pointer-style: substring
|
||||
|
||||
## TỔNG-HỢP (3-5 ý)
|
||||
- Chuỗi baseline 254 (S60) → 561 (S155) là RUỘT file: chỉ có giá-trị khi mỗi mốc kèm delta + lý-do; giữ NGUYÊN 16 mốc chuỗi gốc + 12 mốc hậu-chuỗi + **4 LỖ** (419→431 · 434→440 · 477→486 · 500→501) + **2 MÂU-THUẪN nội-tại** — ⚠️ mâu-thuẫn nội-tại văn-bản — KHÔNG tự sửa số @S170.
|
||||
- Bẫy vacuous là chủ-đề lặp xuyên file: fixture không wire `AuditingInterceptor` ⇒ assert đúng ở CẢ HAI thế-giới (TS-B); fixture-hỏng-cho-xanh trông Y HỆT máy-đúng-cho-xanh (TS-C); phép chống mạnh nhất = số CHÍNH XÁC, 2 hàm, 1 seed (C6, TS-E) + mỗi assert "cho qua" của phần tiền kèm 1 seed "chặn lại" (TS-G).
|
||||
- Test tìm được cả bug prod lẫn lỗi tài-liệu: T26 GIỮ ĐỎ = bug prod thật (cascade Immediate → dependent `Added`→`Detached` ⇒ 0 row changelog, TS-B); spec ghi sai tên query `GetPurchaseEvaluationDetailQuery` — test bắt, lead sửa spec (TS-D). Cả hai đều REPORT, KHÔNG tự sửa.
|
||||
- Test xanh KHÔNG chứng đường chạy thật còn sống: S92 reflection gọi method cô-lập vẫn xanh dù prod đã TẮT lời gọi seed (TS-F); dedup collation nằm ở SERVICE `OrdinalIgnoreCase` — fixture SQLite BINARY không bắt được ca prod SQL Server bắt (TS-H).
|
||||
- Hai ghi-chú "API trông như có kiểm mà không kiểm" sống sót mọi vòng curate: `NotEmpty()` trên `Guid?` không bắt `Guid.Empty`; `ExecuteUpdateAsync` đi vòng change tracker (TS-J).
|
||||
|
||||
## Entries
|
||||
|
||||
**[cao] TS-A — chuỗi baseline: giữ nguyên mốc, gắn cờ 4 lỗ + 2 mâu-thuẫn**
|
||||
- VIỆC: duy-trì một chuỗi số test-pass xuyên ~20 phiên làm bằng-chứng "không hồi-quy".
|
||||
- KẾT-LUẬN (giữ NGUYÊN VĂN mốc): **254 (S60) → 263 (S61: +22 PeWorkItemBudget, −14 BudgetPolicy Domain drop) → 286 (S67 +23 HRM) → 292 (S69 +6 Office) → 306 (S69b +14 PE) → 334 (S72 +28 PE Mig54) → 339 (S74 +5 CcmNote) → 344 (S76 +5 PRO-split) → 351 (S77 +7 suggested-price NOTE) → 354 (S77b symmetric, superseded) → 366 (S78-S84, MEMORY chưa ghi từng delta) → 371 (S85 +5 urgent drafter-unset) → 374 (S85b +3 UpdateDraft WF-preserve) → 377-395 (S86-S86d) → 402 (S87 +7 CCM-budget-period) → 419 (D2 +6 PE Mig58 multi-winner + translation-smoke)**. Hậu-chuỗi rải trong entry (chuỗi một-dòng KHÔNG được cập nhật tiếp): **431→434 (S92 +3)** · **440 (S97, +0 extend in-place)** · **440→450 (D3 +10)** · **450→458 (D4 +8)** · **458→477 (S113 +19)** · **486→495 (S116 +9)** · **495→500 (S117 +5)** · **501→505 (S118 +4)** · **505→509 (S118b +4)** · **→519 (S133, Domain 45 + Infra 474)** · **→528 (S134, 45D+483I)** · **→561 (S155 +10; 560 PASS / 1 FAIL; 45D + 516I)**.
|
||||
- BÀI-HỌC: chuỗi chỉ có giá-trị khi mỗi mốc kèm delta + lý-do; đoạn "366 (S78-S84, MEMORY chưa ghi từng delta)" và "377-395 (S86-S86d)" cho thấy chỗ nào bỏ ghi thì vĩnh-viễn không truy được nữa. Khi nén, mốc đầu/cuối mỗi giai-đoạn là thứ KHÔNG được phép mất.
|
||||
- BẤT-NGỜ / CỜ CHO LEAD — ⚠️ mâu-thuẫn nội-tại văn-bản — KHÔNG tự sửa số @S170: **4 LỖ:** ① 419 → 431 (+12 không có mốc) · ② 434 → 440 (+6) · ③ 477 → 486 (+9) · ④ 500 → 501 (+1). **Mâu-thuẫn (a):** S133 tiêu-đề ghi "+10 -> 519" nhưng thân ghi "+9 test vào PeBudgetFreezeTests" — hoà-giải KHẢ DĨ, chưa xác-minh: T0 của INV-A (+1) + T1-T9 của INV-C (+9) = +10, khớp "464+10". **Mâu-thuẫn (b):** S153 (2026-07-26) ghi "baseline 509 KHÔNG đổi" trong khi S134 (2026-07-17, TRƯỚC đó) đã ghi 528 và S155 (2026-07-27, NGAY SAU) ghi 551→561 ⇒ con số 509 ở S153 không nằm cùng thang với hai mốc kẹp nó. Curator CHỈ đọc file, KHÔNG chạy `dotnet test` — đây là mâu-thuẫn TRONG VERBATIM, không phải phán-quyết về baseline thật.
|
||||
→ substring:"Count progression"
|
||||
|
||||
**[cao] TS-B — S155: giữ nguyên 1 test ĐỎ vì đó là bug prod**
|
||||
- VIỆC: đợt 2 tính-năng "xoá phiếu khi đang duyệt" + màn "Đã xoá" — +10 test `PeDeleteByApproverTests.cs` (T21 lũy-kế 4 hướng · T22/b/c màn Đã xoá · T23/T24/b cờ+lượt · T25 authz reflection · T25b chiều thuận · T26 changelog).
|
||||
- KẾT-LUẬN: 561 = 560 PASS / 1 FAIL (45 Domain + 516 Infra), 0 hồi-quy (506 Infra cũ nguyên vẹn). T26 GIỮ ĐỎ vì là BUG PROD thật: `PeSoftDeleteFeatures.cs` thêm changelog `:126` rồi `Remove(pe)` `:142`; `CascadeDeleteTiming.Immediate` cascade NGAY TẠI `Remove`, thực-thể con đang `Added` bị đẩy sang `Detached` (không phải `Deleted`) ⇒ 0 row; `AuditingInterceptor` chạy ở `SavingChanges` = SAU điểm mất nên không cứu được. Đo bằng chẩn-đoán `ChangeTracker` (`Added→Detached`, `rowsInDb=0`), KHÔNG suy đoán. Grep cùng lớp: site DUY NHẤT có hình "Add con → Remove cha → 1 SaveChanges". Báo lead, KHÔNG tự sửa.
|
||||
- BÀI-HỌC: `SoftDeleteFixture` (~90 LOC, ngay trong file test) là fixture ĐẦU TIÊN của repo wire `AuditingInterceptor` — bắt buộc khi đo `Remove()` nghĩa là xoá-mềm, vì `TestApplicationDbContext` không wire ⇒ mặc-định XOÁ CỨNG ⇒ màn "Đã xoá" rỗng ⇒ assert đúng ở CẢ HAI thế-giới = đúng bẫy vacuous.
|
||||
- BẤT-NGỜ: số học T21 phải seed thêm phiếu hàng-xóm (DaDuyet + TraLai) để hai đại-lượng "không đổi" (PrevSelected, PendingSubmitted) khác 0 ở cả hai phía — để 0 thì "bất động" và "rỗng" nhìn giống hệt nhau.
|
||||
→ substring:"PeDeleteByApproverTests"
|
||||
|
||||
**[cao] TS-C — S153: tiêm lỗi vào máy `mind-check`, bắt được chính fixture của mình nói dối**
|
||||
- VIỆC: fault-inject #8-#17 + #21 lên `scripts/session_ctx.py mind-check` (KHÔNG phải test C#), mọi đột-biến ở cây tạm qua seam `--root`, repo read-only.
|
||||
- KẾT-LUẬN: 18/18 PASS 0 FAIL, baseline C# KHÔNG đổi (con số "509" khai kèm — xem mâu-thuẫn (b) ở TS-A). Bắt 1 GAP THẬT: ở chế-độ closed, xoá con-trỏ → cờ/exit 0 trong khi giữ con-trỏ đang-chờ trung thực → trượt/exit 1 ⇒ đường rẻ nhất để xanh là đường SAI (chữ luật cấm, gradient thưởng). Báo em-main, KHÔNG tự sửa.
|
||||
- BÀI-HỌC: fixture `draft-ts` MALFORMED vẫn cho exit 0 — `inject_A` neo `## MIND-1` sau khi `renum(...,2)` đã đổi heading ⇒ `str.find` = −1 ⇒ chèn GIỮA heading ⇒ `ts:` không đầu dòng ⇒ máy ĐÚNG mà vẫn xanh. Fixture-hỏng-cho-xanh trông Y HỆT máy-đúng-cho-xanh ⇒ soi RUỘT fixture (`cat -A` / `repr` từng dòng), không đọc mỗi exit code.
|
||||
- BẤT-NGỜ: 2 kỹ-thuật tái dùng — (i) cặp CÙNG-RUỘT khác đúng 1 biến (cap=32 vs cap=1 cùng tệp = chứng đọc config LIVE; `_end` giả `_pause=9` vs không = chứng nguồn đếm là ĐĨA, `diff` chứng 2 output BYTE-IDENTICAL); (ii) đo wall-clock phải đi nhánh ĐẮT — lệnh literal rơi nhánh thoát-sớm là tự đo nhánh rẻ; đo 55-64ms/lượt vs ước 140-300ms ⇒ lệch 2.2-5.4×, trả lead điền lại.
|
||||
→ substring:"inject_A"
|
||||
|
||||
**[cao] TS-D — S133 budget-freeze: dựng ĐỎ rồi đẩy XANH + 2 lượt cùng vai đè file nhau** *(gộp 2 entry INV-A + INV-C — hai nửa một chu-trình test-before, giữ đủ 2 verdict)*
|
||||
- VIỆC: INV-A dựng test tái-hiện lỗi (test-before) → INV-C viết T1-T9 phủ tính-năng đóng băng ngân-sách phiếu đã duyệt.
|
||||
- KẾT-LUẬN: INV-A RED thật `Failed 1/1` — kỳ vọng ProInitial 100tr, thấy 999tr @`PeBudgetFreezeTests.cs:184`; luồng chuẩn = đưa phiếu tới DaDuyet QUA `svc.TransitionAsync` (V2 1-Bước-1-Cấp `AllowApproverFinalize=true` + giá chốt), KHÔNG seed thẳng Phase (sau vá thì snapshot chỉ đặt qua transition); đổi giá qua `UpdatePeBudgetPro/Ccm` (2 handler không chặn theo phase = đúng cơ-chế lỗi); 2 sanity-assert chống vacuous (Phase==DaDuyet + bản ghi live đổi thật) đặt TRƯỚC assert chính. INV-C +9 test (`:426` T1 đủ-11-cột · `:480` T2 CCM-threshold · `:524` T3 all-steps · `:555` T4 V1-legacy · `:638` T5 guard CCM+Admin · `:687` T6 AdjustBudget · `:730` T7 live-vs-frozen · `:783` T8 null-safe · `:821` T9 overwrite+refresh) → lead chạy full exit-0: Domain 45 + Infra 474 = 519.
|
||||
- BÀI-HỌC: INV-C ghi đè `sub-test-specialist-0.md` của INV-A — cùng vai + cùng chỉ-số task qua 2 lần gọi trong cùng run-folder = va-chạm tên sub-file; nội-dung INV-A sống nhờ journal + diary. Nhiều lần gọi trong một run PHẢI có nhãn/chỉ-số phân biệt.
|
||||
- BẤT-NGỜ: spec ghi SAI tên truy-vấn — thật là `GetPurchaseEvaluationQuery` (bundle `.BudgetSummary`), spec ghi `GetPurchaseEvaluationDetailQuery`; test-specialist bắt được và lead sửa spec (test soi ra lỗi tài-liệu, không phải lỗi mã).
|
||||
→ substring:"wf_d9639faa"
|
||||
|
||||
**[cao] TS-E — S134: hai hàm, một seed, khớp số CHÍNH XÁC**
|
||||
- VIỆC: +8 test `PePendingAccumulationTests.cs` (C1-C8) cho lũy-kế TẠM TÍNH (mirror seed `PeBudgetFreezeTests:277-336`).
|
||||
- KẾT-LUẬN: C1 ChoDuyet+winner → delta2+PriorPes · C2 TuChoi/DaDuyet loại sạch · C3 nháp chỉ-liệt-kê / TraLai delta1 · C4 cửa-sổ `CreatedAt` · C5 delta âm có dấu · C6 bổ-sung-không-đếm-hai-lần: gọi CẢ `ComputeAsync` + `ComputePendingAsync` trên CÙNG seed, assert SỐ CHÍNH XÁC hai phía (PrevSelected 90tr/1 bất động ⟂ PendingSelected 50tr/1) · C7 frozen→0/null qua query handler · C8 changelog D4 dương + đối-chứng (lọc `ContextNote.Contains("[S134 pending-prior]")`, gotcha #48). Full 528/0 (45D+483I).
|
||||
- BÀI-HỌC: (1) gọi thẳng accumulator chỉ cần `curPeId` tổng-hợp + `CreatedAt` muộn, không cần row phiếu đang xét (chỉ lọc `Id!=` / `CreatedAt<`); (2) phép chống hồi-quy mạnh nhất cho đại-lượng "bất động" = kiểu C6 — số chính xác, 2 hàm, 1 seed; (3) FluentAssertions `ContainInOrder(params T[])` đụng overload với `because` khi T=Guid ⇒ bọc `new[]{...}`.
|
||||
- BẤT-NGỜ: helper D4 không gọi SaveChanges ⇒ row chỉ thực sự nằm DB SAU `TransitionAsync` — assert sớm sẽ thấy "không có row" và kết luận sai về tính-năng.
|
||||
→ substring:"PePendingAccumulationTests"
|
||||
|
||||
**[vừa] TS-F — cụm SECURITY/authz: khuôn reflection + đối-chứng chống vacuous** *(gộp 5 entry theo loại rủi-ro: S118b +4→509 · S118 +4→505 · S92 +3→434 · S69 Office +6 · S54 ItTicket +13)*
|
||||
- VIỆC: 5 đợt khoá quyền bằng test hồi-quy security.
|
||||
- KẾT-LUẬN: S118b mở rộng `Api/AuthorizePolicyRegressionTests.cs` (Pattern 10 reflection, file 15→19 [Fact]): `Dashboard` + `ExportContracts` nay `[Authorize(Policy="Reports.Read")]`, `MyDashboard` CỐ Ý để trần — T4 assert `(attr?.Policy).Should().BeNull()` để CẤM mọi policy (thêm policy = khoá dashboard cá-nhân role không-Admin); csproj đã ref Api từ S45. S118 `ProcurementMasterAccessSeedTests.cs` 4 [Fact], T2 ⭐ScopeIsolation là lõi: DeptManager pre-grant → revoke → seed ⇒ 9 key VẪN all-false (row còn giữ vết) = cấp quyền role này không rò sang role khác, kèm đối-chứng Procurement thật sự được nâng. S92 `AdminOnlyModulesRevokeTests.cs` 3 test giữ bất-biến "PE KHÔNG bị revoke". S69 Office khác HRM: cấp cả Read AND Create trên 16 key. S54 ItTicket = authz theo dữ-liệu (2 phòng ban + ma-trận role/dept).
|
||||
- BÀI-HỌC: khuôn chung = reflection vào private static + seed FK đúng thứ tự (MenuItem → Role → Permission) + luôn có nhánh đối-chứng dương để test không "xanh vì chẳng có gì xảy ra"; authz theo dữ-liệu (handler) khác hẳn authz tĩnh (`[Authorize]` đọc reflection) — hai điểm thực-thi, test khác nhau.
|
||||
- BẤT-NGỜ: S92 — `OfficeModulePermissionSeedTests` + `HrmProfilePermissionSeedTests` VẪN xanh dù prod đã TẮT 2 lời gọi cấp quyền, vì test gọi thẳng method cô-lập qua reflection, không chạy chuỗi `SeedAsync` ⇒ test xanh không chứng minh đường chạy thật còn sống; đã thêm dòng ghi chú đầu mỗi file.
|
||||
→ substring:"ProcurementMasterAccessSeedTests"
|
||||
|
||||
**[cao] TS-G — cụm FINANCIAL / critical-algo: giá chốt, số âm, ngưỡng, đa người thắng** *(gộp 6 entry: S117 +5→500 · S116 +9→495 · S72 +28 · S69b +14 · D2 +6→419 · S87 +7)*
|
||||
- VIỆC: 6 đợt test cho phần dính tiền.
|
||||
- KẾT-LUẬN: S117 khoá bất-biến `Max != null ⟺ Source=="ProMinMax"` + ca biên min==max PHẢI OK (revert `>` thành `>=` là đỏ). S116 đổi guard nộp phiếu từ `Sum()<=0` sang `Count==0` / `All(x==0)` cho báo giá ÂM đi qua; ranh-giới = ca net==0 nhưng từng dòng ≠ 0 `[+500k, −500k] → ChoDuyet`; ngưỡng CCM tính trên NET (`[+1.2tỷ, −0.3tỷ]=0.9tỷ` không chạm ⟂ đối-chứng `[+1.2tỷ]` một mình thì chạm). S72 chuyển CCM-finalize từ TỰ-ĐỘNG sang CHỌN-THAM-GIA + thứ tự fail-closed (threshold-null → Conflict / sai role → Forbidden / vượt ngưỡng → Conflict). D2 chuyển 1→N hợp-đồng cho N đơn-vị thắng, `GiaTri` per-winner = tổng ThanhTien báo giá của ĐÚNG winner (140/260) ⟂ ca single dùng legacy `ThanhTienNganSach` (800). S87 CCM budget-period setter authz (+7).
|
||||
- BÀI-HỌC: với phần tiền, đối-chứng ngược luôn đi kèm — mỗi assert "cho qua" phải có một seed "chặn lại" cạnh bên, nếu không thì không phân-biệt được "luật đúng" với "luật không chạy".
|
||||
- BẤT-NGỜ: ① S117 — method prod đổi sang 5 tham số: `MethodInfo.Invoke` không tự bind optional param ⇒ giữ 4 arg là `TargetParameterCountException` phá 3 test cũ (tự gây, tự tránh). ② S72 — nhánh `isSystem` KHÔNG với tới được qua `ApproveV2Async` công-khai (PE không có job SLA) ⇒ phòng-thủ/chết, test bằng unit + reflection, ghi rõ OBSERVATION. ③ D2 — `PurchaseEvaluationQuote.PESupplierId` FK CỨNG → Supplier Restrict (`cfg:135`) nên Guid ngẫu-nhiên làm SQLite ném lỗi 19, phải seed row thật; trong khi `Contract.SupplierId/ProjectId` KHÔNG có FK vật lý — hai quy-ước ngược nhau cùng một luồng. ④ S87 — `Changelog.UserId` là `Guid?` không FK nên Guid ngẫu-nhiên vô-hại.
|
||||
→ substring:"TargetParameterCountException"
|
||||
|
||||
**[vừa] TS-H — cụm Supplier import: lỗi so-sánh hoa/thường + 1 NRE báo-không-sửa** *(gộp 2 entry: D3 +10→450 test-before · S113 +19→477 test-after)*
|
||||
- VIỆC: D3 test-before cho import Excel; S113 test-after cho dedup v2 + guard công-bố.
|
||||
- KẾT-LUẬN: lỗi lõi = collation: dedup nằm ở SERVICE với `OrdinalIgnoreCase`, không dựa DB ⇒ fixture SQLite (BINARY) KHÔNG bắt trùng khác-hoa-thường, prod SQL Server (CI) thì bắt — `truonggiang` vs `TRUONGGIANG` ra Inserted=0/Updated=1; nếu service dùng `Ordinal` thì prod nổ 500 cả lô. Thứ tự ưu-tiên dedup: existing-MST → existing-Code → batch-MST → batch-Code → New; MST thắng, không âm thầm ghi đè (FillNulls bỏ qua Code). Guard công-bố: `PublishSupplierCommand` (KHÔNG đi qua UpdateSupplier — #73), chặn Code rỗng, chặn trùng chính-xác.
|
||||
- BÀI-HỌC: khuôn dựng header xlsx qua reflection từ `ExpectedHeaderTokens` ⇒ test tự đồng-bộ khi hằng đổi; ClosedXML in-memory gán `.Value = string` GIỮ NGUYÊN chữ (0.105 không bị ép về số ⇒ mã có số 0 đứng đầu an toàn).
|
||||
- BẤT-NGỜ: S113 tìm ra BUG thật và KHÔNG sửa: `NewSupplier` `Code = r.Code!.Trim()` → NRE khi `row.Code == NULL` (đường R4 nháp: parser sinh null cho ô rỗng, preview phân loại New không báo lỗi ⇒ tới confirm mới nổ ⇒ 500 cả lô, dbCount=0), chuỗi rỗng `""` thì OK; viết characterization test chốt đúng hành-vi NRE hiện tại để khi vá null-safe thì test tự lật sang NotThrow. D3 khai `ExpectedHeaderTokens` chỉ là PHỎNG ĐOÁN ≠ 30 token thật dòng 4 ⇒ Case 9 so hai chiều động, hiện `LayoutValid=false`, tự lật true khi hằng được sửa.
|
||||
→ substring:"truonggiang"
|
||||
|
||||
**[vừa] TS-I — cụm bug-fix / spec-change / compile-fix arity** *(gộp 9 entry: D4 +8→458 · S97 +0 · S86 +3 · S85b +3 · S85 +5 · S77c rewrite 12→11 · S77 +7 · S76 +5 · S74 +5)*
|
||||
- VIỆC: 9 đợt nhỏ quanh bug-fix + spec-change + đổi arity record command.
|
||||
- KẾT-LUẬN: D4 đổi `ApplyDrafterBypassOnSubmitAsync` sang A1 — bypass CHỈ các cấp dưới thuần (Order < minOwn), con-trỏ DỪNG tại minOwn để người soạn ký TAY; 4 test cũ ĐỔI TÊN vì tên cũ gây hiểu nhầm. S97 = spec-change: đọc thẳng cờ `EndedByLevelFinalize` thay suy từ config, test cũ thêm param + ca tái-hiện mới `peFinButWentCeo` (cùng config nhưng cờ FALSE). S86 chứng RED-trên-code-cũ bằng `git stash` rồi khôi phục nguyên trạng. S77c = đảo chiều luật giữa chừng: SET = đủ role chức-năng / UNSET = role AND DeptManager.
|
||||
- BÀI-HỌC: đổi arity của record command thì compile-fix là MỘT PHẦN của việc test (S76 vá 5 call-site + đổi assert `.ProEstimateAmount` → `.ProInitialAmount`; S74 vá 3 call-site thêm `null`) — bỏ qua thì không phải "test đỏ" mà là "không build được".
|
||||
- BẤT-NGỜ: S86 — phép loại-trừ theo actor KHÔNG che được bug (actor ở Cấp 1, người tạo ở Cấp 2) nên test mới thật sự có răng; S85 — nhánh `isDrafter` ưu tiên và độc lập với `isProTp`, người vừa Drafter vừa Procurement-không-DM vẫn gỡ được CẢ 2 cờ; S77c ghi thẳng "test theo CODE" (không đụng prod).
|
||||
→ substring:"ApplyDrafterBypassOnSubmitAsync"
|
||||
|
||||
**[thấp] TS-J — 2 ghi-chú kỹ-thuật lẻ (S57bis / S56)** *(rút narrative — token sống ở phụ-lục)*
|
||||
- VIỆC: giữ 2 quan-sát không gắn với đợt test nào.
|
||||
- KẾT-LUẬN: S57bis — `NotEmpty()` trên `Guid?` KHÔNG bắt `Guid.Empty` (FluentValidation 7.2 coi `default(Guid?)` == null) ⇒ dựa FK-guard ở create-handler (phòng-thủ nhiều lớp, không phải bug prod). S56 — `ExecuteUpdateAsync` đi vòng change tracker ⇒ bản đọc đang tracked bị cũ, phải `.AsNoTracking()` đọc lại; transaction đã ship `IsolationLevel.Serializable`.
|
||||
- BÀI-HỌC: hai ca đều loại "API trông như có kiểm mà không kiểm" — validator im lặng và lệnh update im lặng; phải biết trước mới viết được assert đúng.
|
||||
- BẤT-NGỜ: cả hai sống sót qua mọi vòng curate dù không kèm test nào — giá-trị nằm ở chỗ CHẶN một giả-định sai, không ở số lượng test.
|
||||
→ substring:"ExecuteUpdateAsync"
|
||||
|
||||
## Phụ-lục token (coverage)
|
||||
*(chép TRỌN token-list section test-specialist/baseline-history của proposal curator — lưới coverage-diff)*
|
||||
|
||||
- **gotcha#:** `#48` (S134 — lọc `ContextNote.Contains("[S134 pending-prior]")`) · `#73` (S87 "null=clear (pre-seed 500tr, gotcha #73)"; S113 "`PublishSupplierCommand` (KHÔNG UpdateSupplier #73)") · **các ⚠️/"Gotcha" không đánh số:** S117 "⚠️ Gotcha (self-inflicted-avoided)" · S92 "**Gotcha:**" (test xanh dù prod tắt) · S56 "gotcha:" (`ExecuteUpdateAsync`)
|
||||
- **surprise (nhãn nguyên-văn):** "Surprise: D4 no-SaveChanges-in-helper → row chỉ persist sau TransitionAsync" (S134) · "⚠️ Lesson mới: INV-C **ghi đè** `sub-test-specialist-0.md` của INV-A … sub-file collision" (S133) · "**Bài học mới quan trọng nhất:** fixture `draft-ts` MALFORMED ra exit 0 … **Fixture-hỏng cho-xanh trông Y HỆT máy-đúng cho-xanh**" (S153) · "đo 55-64ms/lượt vs ước 140-300ms ⇒ lệch 2.2-5.4×" (S153) · "**1 FAIL = T26 GIỮ ĐỎ = BUG PROD**" (S155) · "🔴 1 BUG-FOUND (REPORTED em-main, NOT fixed)" (S113) · "**Spec-drift FLAGGED (không phải bug)**: `ExpectedHeaderTokens` = BEST-GUESS ≠ 30 real row-4 token" (D3) · "⚠️**SEED-FK: PurchaseEvaluationQuote HARD FK PESupplierId→Supplier Restrict (cfg:135)**" (D2) · "⚠️OBSERVATION: isSystem KHÔNG reachable qua public ApproveV2Async … defensive/dead" (S72) · "⚠️`ApprovalWorkflowId` FK Restrict → PE seed PHẢI ref WF thật" (S85b) · "⚠️**actor-exclusion KHÔNG che bug**" (S86) · "⚠️Changelog.UserId Guid? no FK User → random-Guid an toàn" (S87) · "⚠️GUARD-FIRST: bypass-test PHẢI dựng PE đủ 4 ĐK" (S60) · "Bắt được lỗi spec: query THẬT = `GetPurchaseEvaluationQuery` … spec ghi nhầm" (S133 INV-A) · "[superseded by S77c for urgent SET/UNSET asymmetry]" (S69b) · "(1 FAIL GIU DO = bug prod)" (tiêu-đề S155) · "**baseline 509 KHÔNG đổi**" (S153 — xem mâu-thuẫn ở TS-A)
|
||||
- **guard:** `SoftDeleteFixture` (fixture ĐẦU TIÊN wire `AuditingInterceptor`) · "2 sanity-assert chống vacuous (Phase==DaDuyet + liveRec đổi thật) TRƯỚC assert chính" (S133) · "+CONTRAST assert … (chống vacuous-pass)" (S118) · "CONTROL/đối-chứng" ở S116 (`[+1.2tỷ]` alone) · "Forbidden red-able BY-CONTRAST (non-IT vs IT-staff identical-setup)" (S54) · submit-guard `:202-208` (S116) · publish-guard (S113) · "guard R2-revised" (S116) · "wire-bug-guard" (S117) · "fail-closed order (832-851)" (S72) · "role-gate 152-157 TRƯỚC side-effect" (S74) · "GUARD-FIRST" (S60) · `PurchaseEvaluationDraftGuard`-tương-đương: "T5 guard CCM+Admin Conflict + ChoDuyet-allow" (S133 `:638`) · "authz-capability … CanReassign flag + Forbidden/Conflict guard + empty-staff 0-leak + no-mutation" (S54) · "Pattern 10 reflection static `[Authorize]`" (S118b) · "T4 `(attr?.Policy).Should().BeNull()`" (S118b — rào NGƯỢC: cấm thêm policy) · "invariant T6"-tương-đương: "Pe-module-NOT-revoked invariant" (S92) · "REPORT lead, KHÔNG tự sửa" (S155, S153) · "repo read-only … mọi mutation trong cây tạm qua seam `--root`" (S153) · "No prod touch — verify qua stash, restore exact" (S86) · "KHÔNG touch prod" (S113/S116/S117/S118/S118b)
|
||||
- **root-cause:** ① S155 — "`Remove(pe)` (:142) → `CascadeDeleteTiming.Immediate` cascade **ngay tại Remove**; dependent đang `Added` bị cascade ⇒ **Detached** ⇒ 0 row; `AuditingInterceptor` chạy ở SavingChanges = SAU điểm mất" ② S153 — "`inject_A` neo `## MIND-1` sau khi `renum(...,2)` đã đổi heading ⇒ `str.find`=-1 ⇒ chèn GIỮA heading ⇒ `ts:` không ở đầu dòng" ③ S113 — "`NewSupplier` `Code=r.Code!.Trim()` → NRE khi `row.Code==NULL`" ④ D3/S113 — "dedup ở SERVICE OrdinalIgnoreCase, KHÔNG dựa DB — SQLite fixture BINARY collation KHÔNG bắt CI-dup, prod SQL Server CI thì có" ⑤ S117 — "optional param KHÔNG auto-bind qua `MethodInfo.Invoke`" ⑥ S92 — "test invoke method-CÔ-LẬP qua reflection … KHÔNG chạy SeedAsync chain"
|
||||
- **file:line:** `PeSoftDeleteFeatures.cs:126` + `:142` · `PeBudgetFreezeTests.cs:184` · `:426` `:480` `:524` `:555` `:638` `:687` `:730` `:783` `:821` + seed `:277-336` · `CreateContractFromEvaluationFeatures.cs:196` · `PurchaseEvaluationFeatures.cs:285` (UpdateDraft) + `:849-864` (FullAmount) · `PurchaseEvaluationWorkflowService.LogTransitionAsync (~:1083-1135)` · submit-guard `:202-208` · `ApproveV2Async 816-854` (strict-`<` @838) + `832-851` · role-gate `152-157` · Quote FK `cfg:135` · call-site `PeWorkItemBudgetTests 388/407/421` · `PurchaseEvaluationUrgentFeatures.cs:49-55` · `Api/AuthorizePolicyRegressionTests.cs` (15→19 [Fact]) · csproj `line 25` · `scripts/session_ctx.py mind-check`
|
||||
- **tên file test (định-danh):** `PeDeleteByApproverTests.cs` · `PePendingAccumulationTests.cs` · `PeBudgetFreezeTests.cs` · `SupplierImportV2DedupTests.cs` · `SupplierPublishAndListTests.cs` · `Services/SupplierExcelImportServiceTests.cs` · `ProcurementMasterAccessSeedTests.cs` · `AdminOnlyModulesRevokeTests.cs` · `OfficeModulePermissionSeedTests.cs` · `HrmProfilePermissionSeedTests.cs` · `PeSubmitGuardAndBypassTests.cs` · `PeCcmThresholdFinalizeTests.cs` · `PeApprovedPriceFinalizeTests.cs` · `PeSuggestedPriceSetterAuthzTests.cs` · `PeUrgentToggleAuthzTests.cs` · `PeCcmBudgetPeriodSetterAuthzTests.cs` · `PeApproverNotifyExcludesDrafterTests.cs` · `PeUpdateDraftWorkflowPreserveTests.cs` · `PeMultiWinnerTests` · `PeWorkItemBudgetTests` · `PeFinalizeProjectionTests` · `PeApproverFinalizeTests` · `CreateContractFromEvaluationMultiWinnerTests.cs` · `PeListWinnerNamesProjectionTests.cs` · `ItTicketReassignAuthzTests.cs` · `SqliteDbFixture` / `TestApplicationDbContext` / `IdentityFixture` / `CapturingNotificationService`
|
||||
- **định-danh run/engine:** `run 2026-07-27-S155-pe-delete-approver · sub-d2-test-4` (diary 13.9KB) · `run 2026-07-26-S153-ctx-adopt` `[engine: opus-5]` · `wf_97d6a582-b7e` `[engine: opus-worker]` (S134) · `wf_75c6bfd3-754` (S133 INV-C, **#53 return-fail lần 3**) · `wf_d9639faa-97a` (S133 INV-A, return sạch) · Mig 54/55/56/57/58/59/60/61/65/66/67/68 được dẫn rải rác
|
||||
- **nguồn-đếm (ghi-chú drafter @S170):** đĩa đo 30 heading `##` = 1 dòng "Count progression" + 29 entry-heading (S155 → S54, gồm cả S60 và heading gộp "S57bis / S56 (notes kept)"); proposal curator khai "25 entry". Entry S60 KHÔNG thuộc cụm narrative nào của curator — nó sống ở mốc mở chuỗi "254 (S60)" (TS-A) + token GUARD-FIRST ở phụ-lục này. Chi tiết → `_lienfile-notes-batchB.md` §bổ-sung.
|
||||
Reference in New Issue
Block a user