wal: flush 20260803T0948
This commit is contained in:
@ -0,0 +1,457 @@
|
||||
# sub-harvest-curator — /sleep-recovery-memory-l2 Phase 1 (GATHER + DEDUP) — batch A/2 — S170
|
||||
|
||||
> **Vai:** harvest-curator (H2) · **PROPOSE-ONLY** — file này là kênh run-trace B4/B6, KHÔNG phải ghi trí-nhớ.
|
||||
> **Batch A** = `cicd-monitor` (5 verbatim) + `implementer-backend` (4 verbatim) = 9 file gen-0.
|
||||
> **Phương-pháp:** đóng-kín TỪNG file (chống cross-file bleed) — cụm-dedup + importance-tag + bản-nén-nháp 4-field SE + danh-sách token.
|
||||
> **Khung 4-field SE:** VIỆC · KẾT-LUẬN(+commit/file:line) · BÀI-HỌC · BẤT-NGỜ — rút gọn câu chữ, KHÔNG bỏ field.
|
||||
> **Token-list** = nguyên-liệu coverage-diff của lead: {surprise · guard · file:line · root-cause · gotcha#}. Thiếu = gate FAIL.
|
||||
> **Lưu ý đọc:** mọi token dưới đây trích NGUYÊN VĂN từ verbatim gen-0; bản-nén là ĐỀ-XUẤT, chưa phải bản chốt.
|
||||
|
||||
---
|
||||
|
||||
## cicd-monitor/2026-05-q2 (3.877 B — "Archive Recent Runs Q2, Plan B detail")
|
||||
|
||||
**Đặc-điểm file:** 1 entry DUY NHẤT (Run #231) + header-rule. Archive S32 em-main proxy curate, lý-do = MEMORY.md 27KB → target ~24KB.
|
||||
|
||||
### Cụm-dedup
|
||||
|
||||
| # | Cụm | Thành-phần gộp | Tag |
|
||||
|---|---|---|---|
|
||||
| A1 | **Run #231 Plan B Contract V2 wire — VERDICT=PARTIAL** | deploy PASS (3m30s, bundle 2/2 rotate, Mig 32+33 applied, 5/5 smoke, V1 compat) + seed PARTIAL | **vừa** |
|
||||
| A2 | **DISCOVERY #6 — DemoSeed flag nuốt seed V2 → gotcha #51** | phát-hiện + log-evidence + 2 fix-option (a) admin-tạo-tay / (b) carve-out + escalate + **Resolution Run #232 `38f1c4d` chọn (b)** | **cao** |
|
||||
| A3 | **Test-gap: ~150 LOC `ApproveV2Async` 0 test** (đúng khuôn gotcha #48 high-risk) | test baseline 111 unchanged + khuyến-nghị Plan B+1 | **cao** |
|
||||
| A4 | **SlaExpiryJob ERR cluster 5 entry — nhiễu prod CÓ TRƯỚC, KHÔNG liên-quan Plan B** | 4 transition V1 fail + phán-định unrelated + escalate riêng | **thấp** |
|
||||
|
||||
> Ghi-chú dedup: A2 vốn nằm rải 2 đoạn (phát-hiện ở thân + Resolution ở dòng cuối) — gộp 1 cụm vì cùng một sự-kiện đóng-mở.
|
||||
|
||||
### Bản-nén-nháp 4-field
|
||||
|
||||
**[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.
|
||||
|
||||
**[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.
|
||||
|
||||
### Token-list (verbatim, cicd-monitor/2026-05-q2)
|
||||
|
||||
- **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
|
||||
|
||||
---
|
||||
|
||||
## cicd-monitor/2026-05-q3 (3.949 B — "Archive Q3, S34 curate")
|
||||
|
||||
**Đặc-điểm file:** 3 entry — 1 cặp FAIL→PASS (dày), 1 verify cumulative, 1 setup-baseline.
|
||||
|
||||
### Cụm-dedup
|
||||
|
||||
| # | Cụm | Thành-phần gộp | Tag |
|
||||
|---|---|---|---|
|
||||
| B1 | **Cặp Run #215 FAIL → #216 PASS = gotcha #48 SQLite tie-break** | 2 test fail cùng root-cause + phân-tích tie-break + fix `.Where(Summary Contains)` + 8 phút turnaround + nhận-định "UAT skip-test rủi-ro cho refactor" | **cao** |
|
||||
| B2 | **Verify S22 chốt cuối + Discovery #3 `**/*.md` glob nuốt CI** | 12 commit PASS/104 test/Mig 30 + giả-thuyết spec BỊ BÁC + gotcha #47 hạ xuống vai-trò PREVENTIVE | **cao** |
|
||||
| B3 | **Setup baseline 2026-05-12** | khởi-tạo agent, 44 gotcha + 5-stage + 3 skill, 0 run | **thấp** |
|
||||
|
||||
> Ghi-chú dedup: B1 vốn là 2 mục Run riêng nhưng verbatim đã tự gộp title "catch+fix pair" — giữ 1 cụm, KHÔNG tách (tách sẽ mất mạch nhân-quả).
|
||||
|
||||
### Bản-nén-nháp 4-field
|
||||
|
||||
**[cao] B1 — Run #215 FAIL → #216 PASS (gotcha #48)**
|
||||
- **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).
|
||||
|
||||
**[cao] B2 — Verify S22 cumulative + Discovery #3 (glob `**/*.md`)**
|
||||
- **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/`.
|
||||
|
||||
**[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.)
|
||||
|
||||
### Token-list (verbatim, cicd-monitor/2026-05-q3)
|
||||
|
||||
- **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
|
||||
|
||||
---
|
||||
|
||||
## cicd-monitor/2026-05-q4 (10.576 B — "late May, S32-S34 verbose Run entries")
|
||||
|
||||
**Đặc-điểm file:** 7 entry — 3 Run deploy (#238, #237, #350) + 3 snapshot phi-deploy (S33 startup, S32 wrap, S32 startup) + 1 cumulative-Run S26. Archive S36 startup curate.
|
||||
|
||||
> 🔴 **Coverage-note cho lead:** header (dòng 3) khai archive gồm "**Discovery #8** NEW collection naming" nhưng **THÂN FILE 0 hit `Discovery #8`** — nội-dung nằm ở entry S35/S36 KHÔNG thuộc file này. Đây là **con-trỏ mồ-côi trong header**, không phải mất dữ-liệu; khi nén đừng chép nhãn #8 vào bản-nén của file này (sẽ thành cite-bịa).
|
||||
|
||||
### Cụm-dedup
|
||||
|
||||
| # | Cụm | Thành-phần gộp | Tag |
|
||||
|---|---|---|---|
|
||||
| C1 | **Chuỗi 3 Run module-mới S33→S34 (#350 → #237 → #238)** — Hrm schema → Hrm wire → Office directory | 3 entry Run, cùng khuôn: ALL-PASS 5 stage + Stage 4/4.6 sqlcmd + menu-seed verify + Pattern 12-bis cookie-cutter; bundle-chain nối liền 3 mốc | **cao** |
|
||||
| C2 | **Snapshot phi-deploy S32×2 + S33×1** (startup/wrap/health-check) | trùng nặng: cert notAfter · 3-endpoint smoke 200 · bundle baseline · "foundation entries CONFIRMED preserved" · docs-only skip #41 · MEMORY size-watch | **vừa** |
|
||||
| C3 | **DISCOVERY #7 — `paths-ignore` thiếu `eval/**` ⇒ deploy lãng-phí** | tách khỏi C2 vì là phát-hiện có hành-động, không phải snapshot | **cao** |
|
||||
| C4 | **S26 Run #222-#227 — khuôn "hybrid verify" (spawn 1× đầu Phase, self-verify polish)** | 1 entry, nhưng là bài-học KINH-TẾ spawn (ROI), giá-trị dài-hạn | **cao** |
|
||||
| C5 | **3 ca "gotcha KHÔNG xảy ra" + 1 ca "test bắt được 0"** (rải trong C1) | #1 MediatR / #44 silent-403 / #51-áp-đúng / BW1-7 caught NOTHING | **vừa** |
|
||||
|
||||
> Ghi-chú dedup: C2 gộp 3 entry vì **cùng nội-dung tái-khẳng-định** (cert 2026-07-23, smoke 200, foundation preserved) — chỉ 3 dữ-kiện là RIÊNG: MEMORY 24.9KB/221 dòng (S32-start), MEMORY 27→24.2KB + agent-id (S32-wrap), Mig TOP 5 + last-5-Run (S33-start). C5 cố-ý tách vì "âm-tính" dễ bị nén mất mà lại là bằng-chứng khuôn đã ổn-định.
|
||||
|
||||
### Bản-nén-nháp 4-field
|
||||
|
||||
**[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ò.
|
||||
|
||||
**[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.
|
||||
|
||||
**[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.
|
||||
|
||||
**[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.
|
||||
|
||||
**[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.
|
||||
|
||||
### Token-list (verbatim, cicd-monitor/2026-05-q4)
|
||||
|
||||
- **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)
|
||||
- **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`
|
||||
|
||||
---
|
||||
|
||||
## cicd-monitor/2026-05-runs (59.848 B — "Archive Runs Q2, Run #186 → #221 / S21 t3 → S25 Plan AF")
|
||||
|
||||
**Đặc-điểm file:** file LỚN NHẤT batch A. 3 phần: (a) **"Last curate logs"** 6 mục tóm-tắt · (b) **"Run entries archived"** 20 mục Run đầy-đủ · (c) **"Key patterns extracted (cumulative S21-S25)"** 8 mục = **lớp chưng-cất có sẵn**. Header khai nguồn MEMORY.md ~72KB/~211 dòng/~14 entry → mục-tiêu ~25-30KB.
|
||||
|
||||
> 🔴 **3 cảnh-báo cho lead TRƯỚC khi nén:**
|
||||
> **(1) TRÙNG NỘI-FILE:** phần (a) và phần (b) **chồng nhau 4 lần** — #202 (dòng 16 vs 88) · #203 (20 vs 109) · #210 (24 vs 145) · #219 (36 vs 168). Nhưng **#215 (dòng 28) và #217 (dòng 32) CHỈ có ở phần (a)** — nếu nén bằng cách "bỏ phần tóm-tắt, giữ phần đầy-đủ" thì **mất trắng Run #217** (Plan AC, nguồn duy nhất của pattern "capture pre-call mutation state" + Discovery #5).
|
||||
> **(2) TRÙNG LIÊN-FILE (chỉ báo, KHÔNG gộp — giữ đóng-kín):** mục "Verify S22 chốt cuối cumulative" có ở **cả** file này (dòng 54, bản DÀY) **lẫn** `2026-05-q3.md` (dòng 22, bản MỎNG); Run #215 có ở cả hai (dòng 28 mỏng ở đây / q3 dòng 10 dày). ⇒ khi lead hợp-nhất phải chọn **bản DÀY hơn**, đừng chọn theo thứ-tự file.
|
||||
> **(3) 🔐 BÍ-MẬT LỘ TRONG FILE (ưu-tiên cao, ngoài phạm-vi nén):** **dòng 48** chứa **mật-khẩu DB prod dạng thô** — `appsettings.Production.json` ở `C:\inetpub\solution-erp\api\`, `user=vrapp` + `pwd=<24 ký-tự, CỐ Ý KHÔNG chép lại ở đây>`. File này **git-tracked**. Đề-xuất em-main: (i) khi nén **redact** chuỗi đó, (ii) cân nhắc xoay mật-khẩu vì đã nằm trong lịch-sử git. Vai này KHÔNG tự sửa (propose-only).
|
||||
|
||||
### Cụm-dedup
|
||||
|
||||
| # | Cụm | Thành-phần gộp (Run) | Tag |
|
||||
|---|---|---|---|
|
||||
| D1 | **Chuỗi hotfix per-NV lookup discrimination → checklist 10 điểm** | #194 PARTIAL (K3 DTO thiếu field) → #195 (K10, chốt 8 điểm) → #201 (Plan N, điểm 9) → #202 (Plan O, 4 site cascade) → #203 (Plan P, điểm 10) | **cao** |
|
||||
| D2 | **Saga Discovery #3 — `paths-ignore` xét TRỌN push-range** | #188 (Discovery #2) → S22-chốt (**bác** giả-thuyết) → #200 (bất-thường) → #201 (lần 2) → #202 (lần 3, chốt) → #210 (lần 4, không áp-dụng) → Key-pattern 1 | **cao** |
|
||||
| D3 | **Chuỗi audit-trail S25 (Plan AB→AF)** | #215 FAIL → #217 (AC) → #218 (AC2) → #219 (AD) → #220 (AE) → #221 (AF) | **cao** |
|
||||
| D4 | **Chuỗi schema Mig 28→31 + backfill** | #186 (Mig 28) · #187 (Mig 29 + backfill 48/48 & 0/13) · #188 (Plan C/D/E) · S22-chốt (Mig 30) · #194 (Mig 31) | **vừa** |
|
||||
| D5 | **Plan T — tắt DemoSeed + CHỨNG bằng restart thật** | #207 (flag) · #208 (T5 xoá + T6 restart chứng-minh) | **cao** |
|
||||
| D6 | **Đo-đạc / vận-hành (7 phát-hiện công-cụ)** | #186 endpoint Gitea · #187 `updated_at` trễ · #188 rate-limit 429 + cache token · #217 Discovery #5 · #203 Discovery #4 · #221 503 + route/`accessToken` · #187 credential prod | **cao** |
|
||||
| D7 | **Phình MEMORY 35→72KB, 8 lần cảnh-báo leo thang** | rải ở cuối mọi mục phần (a) + #218/#219/#220/#221 | **cao** |
|
||||
| D8 | **Đánh-bóng UX FE + 1 ca revert-trong-phiên** | #204 (banner `mx-5`) · #209 (Plan U truncate) · #210 (Plan AA widen + **revert Plan U**) · #214 (wrap fix) | **thấp** |
|
||||
| D9 | **"Key patterns extracted" 8 mục — lớp chưng-cất SẴN CÓ** | phần (c) dòng 182-201 | **cao** |
|
||||
| D10 | **Tác-dụng-phụ UAT tự khai + giới-hạn phép đo tự khai** | #202 caveat #2 (PE/025 10→98) · #203 side-effect + "Test 2 409 = không đổi state" · #203 "NV Test không khả-thi (quét 20 PE)" · #194 "presumed ×4" · #219 sqlcmd bỏ qua (thiếu `PROD_DB_PASSWORD`) | **cao** |
|
||||
|
||||
> Ghi-chú dedup: D1 và D2 **đan nhau về mặt Run** (#201/#202 nằm ở cả hai) nhưng **khác trục** (một bên là lỗi CODE, một bên là hành-vi CI) ⇒ giữ 2 cụm, khi nén phải viết 2 dòng riêng cho cùng Run. D8 gộp 4 Run vì nội-dung là CSS, giá-trị dài-hạn thấp — **trừ** chi-tiết "Plan U bị Plan AA revert cùng phiên" (giữ, vì đó là bài về đọc bundle-hash tích-luỹ).
|
||||
|
||||
### Bản-nén-nháp 4-field
|
||||
|
||||
**[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.
|
||||
|
||||
**[cao] D2 — Saga Discovery #3 (`paths-ignore` xét trọn push-range)**
|
||||
- **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.
|
||||
|
||||
**[cao] D3 — Chuỗi audit-trail S25 (Plan AB → AF): 5 khuôn phục-hồi lịch-sử**
|
||||
- **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.
|
||||
|
||||
**[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.
|
||||
|
||||
**[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.
|
||||
|
||||
**[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 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ế.
|
||||
|
||||
**[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**.
|
||||
|
||||
**[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`).
|
||||
|
||||
**[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.
|
||||
|
||||
**[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.
|
||||
|
||||
### Token-list (verbatim, cicd-monitor/2026-05-runs)
|
||||
|
||||
- **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 (KHÔNG chép giá-trị):** dòng **48** — `appsettings.Production.json` tại `C:\inetpub\solution-erp\api\`, `user=vrapp`, mật-khẩu 24 ký-tự dạng thô → **đề-xuất redact + xoay khoá**
|
||||
- **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`
|
||||
|
||||
---
|
||||
|
||||
## cicd-monitor/2026-08 (5.912 B — "L2 archive, moved byte-exact từ L1 khi vượt soft-cap")
|
||||
|
||||
**Đặc-điểm file:** 4 entry (S166-S167, 08-01) — **KHÔNG phải văn-xuôi verbose như 4 file trên**. Đây là **L2 đã chưng-cất sẵn**: 2 entry dạng đầy-đủ + 2 entry dạng `_( … )_` in-nghiêng cực-nén, cả 4 đều **trỏ verbatim ra `runs/…/sub-cicd-verify-*.md`**.
|
||||
|
||||
> 🔴 **KHUYẾN-NGHỊ NGƯỢC cho lead — ĐỀ NGHỊ KHÔNG NÉN FILE NÀY:**
|
||||
> (a) mật-độ đã ở mức chưng-cất (5.9KB chứa ~20 bài-học phân-biệt được); nén tiếp là **mất lớp chưng-cất**, đúng khuôn hỏng đã ghi ở S162/F-02.
|
||||
> (b) file này **mới nhất trong batch A** (mtime 08-01, cùng phiên đang chạy) — giá-trị truy-hồi còn nóng, khác hẳn 8 file 2026-05.
|
||||
> (c) 🟢 **Đã VERIFY con-trỏ, cả 4 đều SỐNG** (đo trên đĩa hôm nay): `sub-cicd-verify-k7.md` 23.745 B · `k6.md` 12.886 B · `k5-k4c.md` 10.869 B · `k4ab.md` 24.451 B — đều ở `.claude/workflows/runs/2026-07-31-S164-4gd-khkk-fanout/`. ⇒ Rút gọn thêm **được phép về mặt an-toàn** (nguyên-liệu còn), nhưng **không nên** vì (a)+(b).
|
||||
> Nếu buộc phải chạm: chỉ đề-xuất **gỡ 2 entry `_( … )_`** (K5/K4c + K4a/K4b) vì đã có verbatim sống, **GIỮ NGUYÊN** 2 entry K7 + K6.
|
||||
|
||||
### Cụm-dedup
|
||||
|
||||
| # | Cụm | Thành-phần | Tag |
|
||||
|---|---|---|---|
|
||||
| E1 | **S167 K7 `aaed699` — PUSH-ĐÈ NUỐT DEPLOY (đề-xuất gotcha #86)** | run #441 CANCELLED + ship nhờ MAY + luật ancestry + 4 kỹ-thuật phụ (tiền-đề marker · control-dương tiền-commit · admin-idempotent · appcmd map PID) + nv.test 401 | **cao** |
|
||||
| E2 | **S167 K6 `1c6d77a` — 0-hit GIẢ 2 lần liên-tiếp do cơ-chế regex** | backtick-minify + shell nuốt backtick → luật `grep -F` + trích byte · COUNT-DELTA · log-CI nhân-chứng-4 · frozen-lành-tính · 2-tầng #82 đo bằng số | **cao** |
|
||||
| E3 | **S166 K5+K4c `e202393` — marker differential + 2 bẫy đo** | 4/4 element-wise · recipe đếm Mig **false-negative** · bar "FE route 200" **không có răng** (SPA-fallback) | **cao** |
|
||||
| E4 | **S166 K4a+K4b `88e7ecf` — phủ 2 run 1 lượt + mốc PRE lão-hoá** | delta khép kín · đề-bài SAI bắt bằng đo (143⇒192 vs LIVE 151) · perm-row ≠ policy · soi điều-kiện log trước khi đọc im-lặng · marker lead **bị bác** | **cao** |
|
||||
| E5 | **META xuyên 4/4 entry: "vắng-mặt / 0-hit KHÔNG được đọc thành kết-luận"** | E1 tiền-đề-marker · E2 0-hit-giả ×2 · E3 false-negative + POS≡NEG · E4 marker-lead-bị-bác | **cao** |
|
||||
|
||||
> Ghi-chú dedup: E5 **không phải entry riêng** mà là trục chung rút từ cả 4 — nếu lead nén, E5 là thứ **phải sống sót nguyên vẹn**, vì nó là bài lặp 4/4 lần (bằng-chứng mạnh nhất trong file).
|
||||
|
||||
### Bản-nén-nháp 4-field
|
||||
|
||||
**[cao] E1 — K7: push-đè nuốt deploy**
|
||||
- **VIỆC:** verify `aaed699` (bridge KHKK→HĐ, 13 file cross-stack 4 BE + 6 FE×2 + 3 test, 0-mig).
|
||||
- **KẾT-LUẬN:** **PARTIAL 4/6+1** (gate **639 = 45+594**, Δ+17; Mig giữ **71**; bảng **97**; bundle `Ig1R5tAh→RgvHZkPH` + `DFOCQ2ks→D65FJPGd`). Run **#441 bị CANCELLED ở 3m02s** vì lead push `628132c` đè lên (Gitea cancel-in-progress); code lên được prod **chỉ nhờ may** — `628132c` tình cờ chạm `fe-*/src/**` nên đẻ run #442.
|
||||
- **BÀI-HỌC:** sau **2 push liên tiếp**, bắt buộc verify hai điều: run cuối có `head_sha == TIP` **và** TIP chứa commit code (`git merge-base --is-ancestor`). **Cấm** đọc "bảng Actions có run xanh" là xong. Kèm 3 kỹ-thuật rẻ: kiểm **tiền-đề của marker** trước khi tin 0-hit (nghi `\u`-escape và nghi code-split — **cả hai đều sai, nhưng phải ĐO mới có quyền kết luận**: `grep -c -F 'Hợp đồng'`=4 trên bundle PRE; index.html chỉ 2 asset); **control dương = chuỗi bản tiền-commit của chính file đang sửa** (4/4); **admin-seeder = control idempotent miễn phí** (3 role báo **2 upgraded**, không phải 3 ⇒ nhánh no-op đã chạy thật).
|
||||
- **BẤT-NGỜ:** kịch-bản chết-người là kịch-bản **KHÔNG xảy ra lần này**: nếu commit-2 rơi trọn vào `paths-ignore` (docs/memory-only) thì run-1 bị huỷ + run-2 bị skip ⇒ **code không bao giờ lên prod mà mọi bảng đều trông lành**. Và `appcmd list wp` cứu một hiểu-nhầm: PID 7680 khởi cùng cửa-sổ nhưng thuộc `vietreport-admin` — **dự án khác**.
|
||||
|
||||
**[cao] E2 — K6: 0-hit giả hai lần, cùng một cơ-chế regex**
|
||||
- **VIỆC:** verify `1c6d77a` (`ADMIN_HIDDEN_KEYS` 12 key, FE-admin 1 file, 0-mig).
|
||||
- **KẾT-LUẬN:** **PASS 5/5** (#440 6m29s; gate **622 = 45+577** delta 0; Mig 71; bảng 97; menu 200 / perm 2221 đứng im). Nhưng marker báo 0-hit **sai 2 lần liên-tiếp**: (a) rolldown/Vite 8 minify chuỗi thành **backtick** (`new Set([\`PurchaseEvaluations\`,…])`) nên regex neo `["']` **không đời nào khớp**; (b) vá xong lại truyền pattern-chứa-backtick qua biến trong `"…"`, shell nuốt ⇒ `grep -cE` gãy (`Unmatched (`) và trả **0 gần như im lặng** trong vòng lặp.
|
||||
- **BÀI-HỌC:** marker chuỗi trên bundle phải dùng **`grep -F` fixed-string** và **luôn trích byte thật `.{90}X.{90}`** trước khi kết-luận vắng-mặt. Thêm **COUNT-DELTA**: khi literal đã tồn-tại từ trước thì *có/không* = **0 bit**, nhưng *số lần xuất hiện* PRE→POST vẫn đo được (**12/12 key đều +1**).
|
||||
- **BẤT-NGỜ:** ngoại-lệ duy nhất `Workflows` **+2** lại **giải-thích được** (là substring của `PeWorkflows`) ⇒ chỗ lệch biến thành **kiểm-tra nhất-quán miễn phí**. Và log CI **in thẳng `dist/assets/index-<hash>.js <size>`** = nhân-chứng **thứ 4** độc-lập với curl, khớp LM prod tới từng giây ⇒ phân-biệt được **FROZEN lành-tính** (eoffice LM 11:14→**12:00:41**, log in **cùng hash `DFOCQ2ks`** = rebuild tất-định) với bundle kẹt thật.
|
||||
|
||||
**[cao] E3 — K5+K4c: marker differential + 2 bẫy đo**
|
||||
- **VIỆC:** verify `e202393` (FE-only).
|
||||
- **KẾT-LUẬN:** **PASS 5/5** (#439 6m16s; gate **622 = 45+577**, +1 đúng bằng 1 `[Fact]` mới). Marker differential **4/4 element-wise** (K5 vào **cả 2** app · K4c **chỉ** eoffice) + Δsize cùng chiều = nhân-chứng vuông-góc với grep.
|
||||
- **BÀI-HỌC:** recipe đếm Mig đang nằm trong MEMORY có **false-negative**: `grep -v Snapshot` ăn nhầm `AddPeApprovedBudgetSnapshot` (Mig 67) ⇒ phải neo đuôi `Designer\.cs$|ModelSnapshot\.cs$`, và ưu-tiên **set-diff repo↔prod 2 chiều** thay vì khớp tổng.
|
||||
- **BẤT-NGỜ:** thước "FE route trả 200" **không có răng** với SPA-fallback — ca dương và ca âm **giống nhau tới từng byte (876 B)**; tức một thước đã dùng lâu nay chứng-minh được **0 bit**.
|
||||
|
||||
**[cao] E4 — K4a+K4b: phủ 2 run một lượt, mốc PRE lão-hoá**
|
||||
- **VIỆC:** verify `88e7ecf`.
|
||||
- **KẾT-LUẬN:** **PASS 7/7** (#438; gate 621 = 45+576; menu **151→200**; perm **1584→2221**). Phủ **2 run trong 1 lượt** ⇒ delta khép kín, không hở khoảng giữa. Phân rã **49+588** khớp cơ-chế 2-site skip-existing (**588 = 49 × 12 role**).
|
||||
- **BÀI-HỌC:** **mốc PRE lão-hoá sau MỖI deploy** — đề-bài của lead ghi 143⇒192, đo LIVE ra **151**; xử đúng là **truy phạm-vi**, không hô "drift". Và **perm-row ≠ policy** (đếm row không nói được quyền). Trước khi đọc log im-lặng thành hồi-quy, phải **soi điều-kiện in log trong source** (`if (added>0)`).
|
||||
- **BẤT-NGỜ:** marker do chính lead gợi ý **bị bác bằng đo** — chữ `Kết cấu` vốn đã có sẵn nên 1/1 = **0 bit**; người ra đề cũng là nguồn sai, và cách chữa là đo chứ không phải cãi.
|
||||
|
||||
**[cao] E5 — META (xuyên 4/4 entry): vắng-mặt không phải kết-luận**
|
||||
- **VIỆC:** trục lặp lại ở cả 4 entry của file.
|
||||
- **KẾT-LUẬN:** 4 dạng dương-giả/âm-giả khác nhau, cùng một bệnh: 0-hit do **regex sai cơ-chế** (E2 ×2) · 0-hit do **chưa kiểm tiền-đề** (E1) · thước **0 bit** vì literal có sẵn (E4) hoặc vì POS≡NEG (E3) · recipe đếm có **false-negative** (E3).
|
||||
- **BÀI-HỌC:** mỗi phép đo phải kèm **control dương** (thứ chắc-chắn phải khớp) và **trích byte thật**; "không thấy" chỉ có nghĩa khi đã chứng được thước còn răng.
|
||||
- **BẤT-NGỜ:** trong cùng một file, **suýt FAIL-OAN 3 lần** (K6 ×2 + K7 ×1) — nghĩa là kỷ-luật này không phải lý-thuyết mà là thứ vừa cứu 3 bàn thua.
|
||||
|
||||
### Token-list (verbatim, cicd-monitor/2026-08)
|
||||
|
||||
- **gotcha#:** **`#86` (ĐỀ-XUẤT MỚI — push-đè nuốt deploy)** · `#82` (2 tầng display ⟂ API — đo bằng số ở K6, **chưa đo được tầng-2 Drafter** ở K7) · Mig `67 AddPeApprovedBudgetSnapshot` (nạn-nhân của recipe false-negative) · K6 nhắc `K4a`
|
||||
- **surprise:** `PUSH-ĐÈ NUỐT DEPLOY` · "K7 ship được **CHỈ NHỜ MAY**" · "code KHÔNG BAO GIỜ lên prod, mà bảng Actions trông vẫn lành + `git log` đã push" · "**0-hit marker SAI 2 LẦN LIÊN TIẾP do CƠ-CHẾ REGEX** ... suýt FAIL-OAN" · "suýt FAIL-OAN **lần 3**" · "Cả 2 nghi đều SAI nhưng **phải đo mới được quyền kết luận**" · "ngoại lệ duy nhất `Workflows` **+2** giải-thích-được (substring ⊂ `PeWorkflows`)" · "**bar 'FE route 200' KHÔNG CÓ RĂNG**" (POS ≡ NEG byte-identical **876B**) · "**recipe đếm mig trong MEMORY có FALSE-NEGATIVE**" · "**MỐC PRE ĐỀ BÀI SAI, bắt bằng ĐO**" · "**marker lead gợi ý BỊ BÁC BẰNG ĐO** (`Kết cấu` có sẵn ⇒ 1/1 = 0 bit)" · "**PERM row ≠ policy**" · "log lần-2 sẽ **IM LẶNG** — đừng đọc thành hồi-quy" · "PID 7680 ... là `vietreport-admin` (**dự án KHÁC**)" · "**CẤM lấy Admin-qua-được để tuyên grant đã ăn**"
|
||||
- **guard:** `git merge-base --is-ancestor` + `head_sha == TIP` (luật 2-push) · `grep -F` fixed-string + trích byte `.{90}X.{90}` · **control dương** = chuỗi bản tiền-commit của chính file đang sửa (4/4) · `if(changed) upgraded++` · `if(added>0||upgraded>0)` (guard làm log im) · `if (added>0)` · `appcmd list wp` map PID→pool · neo đuôi `Designer\.cs$|ModelSnapshot\.cs$` + **set-diff repo↔prod 2 chiều** · marker-ÂM display-only (route `/purchase-evaluations` **68→68**) · COUNT-DELTA PRE→POST
|
||||
- **root-cause:** Gitea **cancel-in-progress** khi push mới đè ⇒ run-1 huỷ; nếu commit-2 trọn `paths-ignore` thì run-2 skip ⇒ **cửa sổ mất deploy im-lặng** · rolldown/Vite 8 minify chuỗi thành **backtick** ⇒ regex neo `["']` không khớp · pattern-chứa-backtick truyền qua biến trong `"…"` ⇒ **shell nuốt**, `grep -cE` gãy `Unmatched (` trả 0 im-lặng · `grep -v Snapshot` khớp nhầm tên Mig chứa chữ *Snapshot* · **SPA-fallback** trả 200 cho mọi route ⇒ thước route mất răng · literal đã tồn-tại từ commit trước ⇒ marker *có/không* = 0 bit
|
||||
- **file:line / định-danh:** sha `aaed699` (K7) · `628132c` (commit đè) · `1c6d77a` (K6) · `e202393` (K5+K4c) · `88e7ecf` (K4a+K4b) · run **#438 / #439 6m16s / #440 6m29s / #441 3m02s CANCELLED / #442** · gate `639=45+594` (Δ+17) · `622=45+577` (delta 0) · `622=45+577` (+1) · `621=45+576` · Mig **71** · tables **97** · bundle `Ig1R5tAh→RgvHZkPH` + `DFOCQ2ks→D65FJPGd` · hash lặp `DFOCQ2ks` (rebuild tất-định) · `ADMIN_HIDDEN_KEYS` **12-key** · `new Set([\`PurchaseEvaluations\`,…])` · `grep -c -F 'Hợp đồng'` = **4** · `dist/assets/index-<hash>.js <size>` (log CI) · LM `11:14 → 12:00:41` · menu **151→200** · perm **1584→2221** · phân rã **49+588 = 49×12 role** · mốc PRE đề-bài `143⇒192` vs LIVE **151** · `nv.test` login **401** · PID **7680** / pool `vietreport-admin` · seeder **3 role → 2 upgraded**
|
||||
- **evidence-pointer (ĐÃ ĐO, 4/4 SỐNG):** `runs/2026-07-31-S164-4gd-khkk-fanout/sub-cicd-verify-k7.md` **23.745 B** · `…/sub-cicd-verify-k6.md` **12.886 B** · `…/sub-cicd-verify-k5-k4c.md` **10.869 B** · `…/sub-cicd-verify-k4ab.md` **24.451 B**
|
||||
- **tag gốc:** `[run-442, push-de-nuot-deploy, ancestry-proof, tien-de-marker, control-duong-tien-commit, admin-control-idempotent, appcmd-map-pid]` · `[run-440, 0-hit-gia-2-lan, grep-F, count-delta, frozen-lanh-tinh, log-CI-nhan-chung-4]`
|
||||
|
||||
---
|
||||
|
||||
## implementer-backend/2026-05-q1 (18.512 B — "Archive Recent Activity Q1, S21 t3 → S24 Plan AA, 12 entry")
|
||||
|
||||
**Đặc-điểm file:** 12 entry. Chia rõ 2 loại: **4 entry "REFUSE"** (vai từ-chối nhận việc, em-main tự làm) + **8 entry "PASS"** (vai làm thật). Header khai nguồn MEMORY.md **38.8 KB**, giữ lại Pattern 1-19.
|
||||
|
||||
### Cụm-dedup
|
||||
|
||||
| # | Cụm | Thành-phần | Tag |
|
||||
|---|---|---|---|
|
||||
| F1 | **Nhật-ký TỪ-CHỐI + 7 tiêu-chí phạm-vi** | S21 t3-t5 (REFUSE 3/3) · S22 (REFUSE 100%) · S23 t4-t11 (**8 plan liên-tiếp, 0 spawn**) · S24 wrap (REFUSE 4/4, ghi rõ tiêu-chí từng ca) | **cao** |
|
||||
| F2 | **Chuỗi K của Mig 31 (pre-A → K1 → K3 → K5 → K7)** | UI label · schema swap Users→Levels · checkbox thứ 7 · dọn zombie · test hồi-quy | **cao** |
|
||||
| F3 | **4 "Mismatch" tự khai ở S22** | per-NV opt-in vs default · `PE.changelogs` vắng · Dialog `size="xl"` · `accessToken` ≠ `token` | **cao** |
|
||||
| F4 | **Plan AA (Chunk B + wrap) — trang matrix read-only cho fe-user** | 3 file mới · thiếu shadcn Card/Badge · Pattern 13/14/15 mới | **vừa** |
|
||||
| F5 | **Plan M (M2 test + M3 đổi nhãn FE)** | mở-rộng helper không clone · kỷ-luật phạm-vi hẹp + ngưỡng chống-nghịch | **vừa** |
|
||||
|
||||
> Ghi-chú dedup: F1 gộp 4 entry nằm rải khắp file vì **cùng một loại nội-dung** (quyết-định không-làm + lý-do theo tiêu-chí); gộp lại mới thấy được tỉ-lệ. F2 giữ **5 mắt-xích riêng** trong 1 cụm vì chúng là **một lần refactor bị chẻ nhỏ có chủ-đích** (mỗi chunk 1 commit, K5 dọn rác của K1).
|
||||
|
||||
### Bản-nén-nháp 4-field
|
||||
|
||||
**[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ó.
|
||||
|
||||
**[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.
|
||||
|
||||
**[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**.
|
||||
|
||||
**[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.
|
||||
|
||||
**[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.
|
||||
|
||||
### Token-list (verbatim, implementer-backend/2026-05-q1)
|
||||
|
||||
- **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`
|
||||
|
||||
---
|
||||
|
||||
## implementer-backend/2026-05-q2 (12.442 B — "S25 Plan AB → S29 Plan B Chunk D, 5 entry verbose")
|
||||
|
||||
**Đặc-điểm file:** 5 entry, header khai 38.4KB → mục-tiêu ~21KB. Đậm đặc **pattern-number** và **SHA256 mirror-verify**.
|
||||
|
||||
> 🔴 **VA-CHẠM SỐ HIỆU PATTERN (fidelity-flag, KHÔNG tự phán — đề-nghị lead định-đoạt):** trong **cùng file này**, nhãn **"Pattern 16 NEW"** được gán cho **hai thứ khác nhau**:
|
||||
> · dòng 18 (S25 wrap): *"**Pattern 16 NEW**: Preventive systemic batch fix khi audit phát hiện 9 sites cùng bug pattern"*
|
||||
> · dòng 26 (S27 Plan CA): *"**Pattern 16 NEW** — byte-identical mirror admin → user khi parity confirmed"*
|
||||
> Chứng-cứ liên-quan (file q4, dòng 53) tự khai đã biết chỗ mờ: *"Pattern 16 baseline **implied** trong recent activity S27 **chưa numbered explicit**"*. ⇒ Khi nén, **cấm gộp 2 mục này thành một** chỉ vì trùng số.
|
||||
|
||||
### Cụm-dedup
|
||||
|
||||
| # | Cụm | Thành-phần | Tag |
|
||||
|---|---|---|---|
|
||||
| G1 | **Plan AB Chunk A (`cdfd542`) — mô-tả 2 LẦN trong file** | entry "S25 Plan AB Chunk A" (dòng 14, dày) + phần đầu entry "S25 wrap" (dòng 18, tóm) — trùng ~60% | **cao** |
|
||||
| G2 | **S26 Plan AG — cây thư-mục kiểu Outlook cho PE List (Pattern 19)** | 3 chunk gộp 1 commit `0bf6c7e` | **cao** |
|
||||
| G3 | **S27 Plan CA Chunk B — mirror byte-identical + bẫy PowerShell** | 4 trang master admin→user + gotcha `$_` bị nuốt | **cao** |
|
||||
| G4 | **S29 Plan B Chunk D — dropdown V2 cho ContractCreatePage** | mirror từ PE, kiểm 4-chỗ Pattern 16-bis | **vừa** |
|
||||
| G5 | **META: kỷ-luật "chứng mirror bằng HASH, không bằng mắt"** | G2 (`21001E90…`) · G3 (4 SHA256) · G4 (git diff stat) · bộ đếm Pattern 5 **7th → 8th → 9th** | **cao** |
|
||||
|
||||
> Ghi-chú dedup: G1 là ca trùng **rõ nhất batch A** — 2 entry cùng tả một commit. Phần **chỉ có ở entry wrap** mà phải giữ: 6 plan em-main tự làm sau đó (AC→AF) + **3 Pattern mới 16/17/18** + lý-do vì sao vai sẽ từ-chối (tiêu-chí #4). Phần **chỉ có ở entry dày**: số dòng cụ-thể, chuỗi `modeName` switch, cách resolve `actorName`, thời-gian build.
|
||||
|
||||
### Bản-nén-nháp 4-field
|
||||
|
||||
**[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ố.
|
||||
|
||||
**[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.
|
||||
|
||||
**[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.
|
||||
|
||||
**[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`).
|
||||
|
||||
**[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".
|
||||
|
||||
### Token-list (verbatim, implementer-backend/2026-05-q2)
|
||||
|
||||
- **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,432 @@
|
||||
# sub-harvest-curator — /sleep-recovery-memory-l2 Phase 1 (GATHER + DEDUP) — batch B/2 — S170
|
||||
|
||||
> **Vai:** harvest-curator (H2) · **PROPOSE-ONLY** — file này là kênh run-trace B4/B6, KHÔNG phải ghi trí-nhớ. 0 chạm `.claude/agent-memory/**`.
|
||||
> **Batch B** = `investigator-codebase` (4 verbatim) + `reviewer` (2) + `test-specialist` (1) + `implementer-frontend` (1) = **8 file gen-0**.
|
||||
> **Phương-pháp:** đóng-kín TỪNG file (chống cross-file bleed) — cụm-dedup + importance-tag + bản-nén-nháp 4-field SE + danh-sách token.
|
||||
> **Khung 4-field SE:** VIỆC · KẾT-LUẬN(+commit/file:line) · BÀI-HỌC · BẤT-NGỜ — rút gọn câu chữ, KHÔNG bỏ field.
|
||||
> **Token-list** = nguyên-liệu coverage-diff của lead: {surprise · guard · file:line · root-cause · gotcha#}. Thiếu = gate FAIL.
|
||||
> **2 file đặc-biệt (theo lệnh lead):** `implementer-frontend/2026-H2` = nội-dung moved-not-cut đợt drain 08-01 ⇒ MỌI mục giữ danh-tính riêng, mục auto-refuse (dòng 78) tag **cao** · `test-specialist/baseline-history` = chuỗi số baseline là RUỘT ⇒ nén = gom giai-đoạn, giữ đủ mốc đầu/cuối mỗi giai-đoạn.
|
||||
> **Lưu ý đọc:** mọi token dưới đây trích NGUYÊN VĂN từ verbatim gen-0; bản-nén là ĐỀ-XUẤT, chưa phải bản chốt.
|
||||
|
||||
---
|
||||
|
||||
## investigator-codebase/2026-05-q1 (20.699 B — "Archive Recent Activity Q1 2026-05, S21-S24")
|
||||
|
||||
**Đặc-điểm file:** 10 entry verbose FIFO S21→S24 Plan AA, archive 2026-05-22 (nguồn MEMORY.md 34.9 KB → giữ <25KB). Header khai rõ **§6.5 KHÔNG cắt narrative** + danh-sách KEEP. Đây là file dày file:line nhất batch B (bản-đồ mã PE V2 thời Mig 28-31).
|
||||
|
||||
### Cụm-dedup
|
||||
|
||||
| # | Cụm | Thành-phần gộp | Tag |
|
||||
|---|---|---|---|
|
||||
| B1-A | **Chuỗi bug per-NV Allow* (F1-F4) — 3 lượt audit, 2 bug + 1 no-bug** | S23 t2 spawn L2 (F4 FE readOnly short-circuit) + S23 t3 spawn (BE lookup `:765` sai row) + S23 t2 spawn M0 (semantic ChoDuyet — KHÔNG bug, disconnect mental-model) | **cao** |
|
||||
| B1-B | **Cross-stack contract drop — Controller body ≠ Command record** | S23 t6 spawn Plan P FE-wire audit (xác nhận scope BE-only) | **cao** |
|
||||
| B1-C | **Pre-flight Plan K (Mig 31) — bản-đồ 20+ điểm neo + GAP fe-user + drift Dev/prod** | S23 t1 spawn K0 | **vừa** |
|
||||
| B1-D | **Pre-flight Plan R — cleanup destructive prod** | S23 t8 spawn (4 sqlcmd, FK Restrict, SQL Express limit, ~620 row cascade, 3 Option → chốt A) | **vừa** |
|
||||
| B1-E | **Plan AA (User Workflow Matrix + sidebar widen) — 5Q audit + wrap 7 commit** | S24 t1 spawn Pre-A + S24 t1-t4 em-main solo 4 polish (gộp: cùng 1 Plan, phần wrap chính là hậu-nghiệm của 5Q) | **vừa** |
|
||||
| B1-F | **Phát-hiện rời S22 (6 mục) — hạ-tầng/identity/script** | password ≥12 · rename atomic 4 field · `accessToken` ≠ `token` · PS 5.1 ASCII · Plan F drop V1 ABORTED · admin opt-in 2× | **vừa** |
|
||||
| B1-G | **Ảnh chụp trạng-thái no-spawn (setup + S21 + S22 cumulative count)** | 2026-05-11 setup baseline + S21 t3-t5 + phần đầu S22 | **thấp** |
|
||||
|
||||
> Ghi-chú dedup: B1-A gộp 3 entry rời vì **cùng một mặt-cắt tính-năng** (7 cờ Allow* per-slot sau Mig 29/30/31); giữ nguyên 3 verdict phân-biệt (BUG FE · BUG BE · NO-BUG) — gộp verdict là mất danh-tính. B1-E gộp 2 entry cùng Plan AA. B1-G là ảnh-chụp số đếm, đã bị các mốc sau ghi đè ⇒ tag thấp.
|
||||
|
||||
### Bản-nén-nháp 4-field
|
||||
|
||||
**[cao] B1-A — chuỗi per-NV Allow* (F1-F4): 2 bug tách lớp FE/BE + 1 báo-động-giả**
|
||||
- **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 nào; (iii) rà 4 BE + 4 FE × 2 app xem F1/F2/F3/F4 có tự đẩy phiếu về "nháp" không.
|
||||
- **KẾT-LUẬN:** (i) **F3 ĐÚNG, F4 HỎNG** — `PeDetailTabs.tsx:118` `itemsReadOnly = readOnly && !approverEditMode` override được, còn `: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ế thành `isAdmin || (!readOnly && isDrafter && isDrafterPhase) || isApproverChoDuyet` (đã land, thấy lại ở `:977`). (ii) **BE lấy nhầm hàng**: `PurchaseEvaluationFeatures.cs:765` `Levels.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` ⇒ handler mù currentUser; fix 1 dòng `&& l.ApproverUserId == currentUser.UserId` + fallback row-đầu cho admin. (iii) **KHÔNG bug**: F1.OneLevel `:285-312` / F1.OneStep `:314-333` / F1.Assignee `:335-360` / F2 `:483-524` / F3 `PurchaseEvaluationDetailFeatures.cs:42-99` / F4 `PurchaseEvaluationFeatures.cs:272-329` đề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 một tính-năng hỏng theo **hai kiểu khác nhau** ⇒ đọc verdict lớp này không suy ra lớp kia. Và: refactor cardinality (1-row-per-Cấp → 1-row-per-NV) làm **mọi phép `FirstOrDefault` theo khoá cũ** thành 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ó ẩn suốt vì 13 user UAT cũ hầu hết nằm ở row đầu của Cấp và admin tick toàn FALSE ⇒ mọi row hành-xử giống nhau; chỉ lộ khi tick CHỌN LỌC per-NV. (b) route `/inbox` (`InboxPage.tsx`) đẩy sang DetailPage mobile mặc-định `readOnly=false`, chỉ màn Danh sách `?pendingMe=1` desktop 3-panel mới 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**: chữ "Trả lại" bị gọi lẫn thành "draft", trong khi `TraLai=98` ≠ `DangSoanThao=1`.
|
||||
|
||||
**[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` `api.post(/transitions, body)` gửi ĐỦ (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 nằm ở **ranh giới kiểu dữ-liệu** (record thiếu field = mất dữ-liệu không lỗi, không log). Đối-chiếu **đế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 nó.
|
||||
|
||||
**[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 `PurchaseEvaluationWorkflowService.cs:119-161` (:125) · nhánh APPROVE `~:393-525` · `PurchaseEvaluationFeatures.cs:393-402` · `PurchaseEvaluationDtos.cs:86-92` · bundle DTO `:214`/`:217` · `UsersController` `:91-98`/`:105` · `UserFeatures.cs:332` · Designer `:873` nhãn slot + `:853-933` panel 6 checkbox · `UsersPage.tsx:306-318`/`:365-372`/`:181-186` · PeDetailTabs `:287-297` admin / `:294-304` user). 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; phải 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 có **2 user / 0 flagged**, prod có **33 user / 4 flagged** — cả hai đều KHÔNG khớp con số spec (S22+2) ⇒ 4 user sẽ 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.
|
||||
|
||||
**[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 được 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, và index filtered (từ Mig 29) đòi `SET QUOTED_IDENTIFIER ON` mới chạy được sqlcmd.
|
||||
|
||||
**[vừa] B1-E — Plan AA: 5Q audit → 7 commit `a1a910f..ee0902a`**
|
||||
- **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ỉ cần 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ờ, nhãn VI `:892-948`; sidebar `Layout.tsx:325` (user) + `:218` (admin) `w-60 xl:w-72` → `w-72 xl:w-80` an-toàn @1280px, `w-80 xl:w-96` là ngưỡng rủi-ro; enum `ApprovalWorkflow.cs:45-50` {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 ⇒ bảng màu STEP_PALETTE/LEVEL_PALETTE phải liệt nguyên chuỗi (tái dùng được cho mọi cây menu phân-cấp). Bỏ `truncate` KHÔNG đủ để nhãn dài xuống dòng: phải đổ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 — `feedback_responsive_laptop_breakpoint` khai "PE Workspace 3-panel", đo lại là **2-panel** (`[260px_1fr] xl:[320px_1fr]`); và chiến-lược Order=2 cho "Luồng duyệt" buộc phải **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.
|
||||
|
||||
**[vừa] B1-F — 6 phát-hiện rời S22 (hạ-tầng/identity/script)**
|
||||
- **VIỆC:** phiên em-main solo, investigator ghi lại các va-chạm khi seed 20 user role-based lên prod (13→33 user).
|
||||
- **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 là **4 trường nguyên-tử** (Email + NormalizedEmail + UserName + NormalizedUserName + FullName — xác nhận **gotcha #38**), sqlcmd cần `SET QUOTED_IDENTIFIER ON`; login trả `accessToken`/`refreshToken`/`user` — **KHÔNG có field `token`** (ví-dụ Bash trong spec 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 củng-cố lần 2 (Mig 30 F4 lặp lại Mig 29 F1+F3) sau khi anh sửa lưng em-main: mặc-định là **KHÔNG mở rộng quyền**, muốn mở thì tick.
|
||||
- **BẤT-NGỜ:** Plan F (drop V1) bị **HUỶ ngay ở pre-flight** vì sqlcmd lộ ra Contract còn HOÀN TOÀN V1 (chưa có cột `ApprovalWorkflowId`) + 4 PE V1-only + 19 PE lai V1+V2 ⇒ phạm-vi drop phải tính cả thực-thể liên-đới, không chỉ PE.
|
||||
|
||||
**[thấp] B1-G — ảnh-chụp trạng-thái no-spawn**
|
||||
- **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 — nghĩa là diary vai này từng ghi cả **những phiên nó không làm gì**, đây là nguồn nhiễu khi đếm coverage sau này.
|
||||
|
||||
### Token-list (verbatim, investigator-codebase/2026-05-q1)
|
||||
|
||||
- **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)
|
||||
|
||||
---
|
||||
|
||||
## investigator-codebase/2026-05-q2 (9.062 B — "Archive Recent Activity Q2 2026-05, S25-S29 verbose audit")
|
||||
|
||||
**Đặc-điểm file:** 4 entry verbose, archive 2026-05-26 S32 (MEMORY.md 27.7KB → ~22KB). Đây là file DUY NHẤT batch B có **0 token gotcha#** — mọi phát-hiện đều dạng schema-gap / nghiên-cứu ngoài repo.
|
||||
|
||||
### Cụm-dedup
|
||||
|
||||
| # | Cụm | Thành-phần gộp | Tag |
|
||||
|---|---|---|---|
|
||||
| B2-A | **2 bug UAT "Lịch sử thay đổi" trống — 2 root-cause KHÁC NHAU** | S25 t1 spawn 5Q (Bug1 Budget-Adjust: code ĐÚNG, nghi FE filter · Bug2 Return-mode: handler KHÔNG log) | **cao** |
|
||||
| B2-B | **Pre-flight Plan B Contract V2 — 5Q + tái-chia 6 chunk** | 2026-05-22 (Q1-Q5 + Bonus re-chunk + 3 surprise) | **cao** |
|
||||
| B2-C | **Plan AG — PE List cây thư-mục kiểu Outlook** | S26 spawn 5Q (scale · Project entity · 3-panel · GAP shadcn · đặt tên Mig 32) | **vừa** |
|
||||
| B2-D | **Plan AI — nghiên-cứu phân-phối RAG, 4 case công-nghiệp** | S26 spawn 7Q (Cursor/Cline/Continue/Sourcegraph + multi-tenant + chi-phí + chốt Pattern C) | **vừa** |
|
||||
|
||||
> Ghi-chú dedup: 4 entry = 4 chủ-đề rời, KHÔNG có cặp trùng ⇒ dedup ở đây = giữ nguyên 4, chỉ nén câu chữ.
|
||||
|
||||
### Bản-nén-nháp 4-field
|
||||
|
||||
**[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 theo 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, không log tác-dụng-phụ của mode. 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 thêm enum Kind 30 LOC / B mở EntityType 20 LOC / C nới filter FE 10 LOC).
|
||||
- **BÀI-HỌC:** cùng một triệu-chứng "không thấy lịch-sử" nhưng **một cái là ghi-thiếu, một cái là đọc-thiếu** — phải tách hai trục ghi/đọc trước khi đặt giả-thiết chung; và `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).
|
||||
|
||||
**[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 cả 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 cái đ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 `:484-495` · UPSERT opinion `:522-546` · advance `:605-633` · skipToFinal `:561-602`), đ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 việc "nối module thứ 2" nằm ở **Service + terminal-state khác nhau**, không ở migration — 7 cờ `Allow*` trên `ApprovalWorkflowLevels:86-114` Contract **thừa-hưởng miễn phí**.
|
||||
- **BẤT-NGỜ:** (a) prod có **0 row** `ApplicableType=3` ⇒ nối xong mà không seed thì dropdown rỗng, phải gài seed vào chunk A2 (đúng thứ sau này thành sự-cố Run #231 ở lane cicd); (b) `RejectedAtStepIndex` bên 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.
|
||||
|
||||
**[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 sàng 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 được việc — `<details>/<summary>` native + badge `<div>` là đường vòng đã kiểm-chứng ở Plan AA; chọn Phase-1-FE-only giữ được 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õ.
|
||||
|
||||
**[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 — phải 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".
|
||||
|
||||
### Token-list (verbatim, investigator-codebase/2026-05-q2)
|
||||
|
||||
- **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**
|
||||
|
||||
---
|
||||
|
||||
## investigator-codebase/2026-05-q3 (3.089 B — "Archive Q3 2026-05, S34 curate")
|
||||
|
||||
**Đặc-điểm file:** 4 mục ngắn, trong đó **2 mục là bản sao/ghi-chú housekeeping**. Mục S27 chứa **flag tự-nghi-ngờ về chính lịch-sử spawn của vai** — đắt nhất file, và là loại token mà harvest-curator phải giữ.
|
||||
|
||||
### Cụm-dedup
|
||||
|
||||
| # | Cụm | Thành-phần gộp | Tag |
|
||||
|---|---|---|---|
|
||||
| B3-A | **S27 — 2 bài: (i) pre-flight hạ-tầng BẮT BUỘC spawn (ii) NGHI-VẤN lịch-sử spawn của chính vai** | 2026-05-22 S27 wrap retrospective (2 đoạn) | **cao** |
|
||||
| B3-B | **S25 wrap Plan AB — bản rút gọn của entry S25 ở q2 + kết-quả CI** | 2026-05-19 (root-cause 2 bug + 7 commit + run #215 FAIL → #216-#221 PASS + gotcha #48) | **vừa** |
|
||||
| B3-C | **Housekeeping: bản-ghi phiên curate S29-era** | 2026-05-22 (archive 10 entry → q1, 34.9KB → ~20-22KB) | **thấp** |
|
||||
| B3-D | **Trùng nguyên-văn: setup baseline 2026-05-11** | y-hệt đoạn mở đầu file q1 | **thấp** |
|
||||
|
||||
> Ghi-chú dedup: **B3-D trùng 100% với entry đầu của q1** (cùng 44 gotcha/14 memory/6 skill/27 mig/81 test) — đề-xuất nén còn 1 dòng trỏ, KHÔNG chép lại. B3-B là bản NGẮN của cùng sự-kiện với entry S25 ở q2 (bản q2 dài hơn, có 5Q); **KHÔNG gộp xuyên file** theo lệnh đóng-kín, chỉ FLAG cho lead ở §Ghi-chú liên-file cuối bài.
|
||||
|
||||
### Bản-nén-nháp 4-field
|
||||
|
||||
**[cao] B3-A — S27: hai bài học, một trong đó 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 được "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 phiên S27, spawn agent kiểu `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 các 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) phải là **việc của lượt pre-flight**, không phải việc của người dùng cuối; và khi 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 vào diary không chứng-minh được danh-tính agent đã chạy. Đây là 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.
|
||||
|
||||
**[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) — tức 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.
|
||||
|
||||
**[thấp] B3-C + B3-D — housekeeping & bản sao**
|
||||
- **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 (đúng chỗ), không nên chiếm slot trong 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 một vai ⇒ bản sao được nhân lên bởi chính quy-trình curate, không phải bởi việc thật.
|
||||
|
||||
### Token-list (verbatim, investigator-codebase/2026-05-q3)
|
||||
|
||||
- **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
|
||||
|
||||
---
|
||||
|
||||
## investigator-codebase/2026-05-q4 (2.770 B — "Archive Q4 2026-05, S39-S40 era")
|
||||
|
||||
**Đặc-điểm file:** 3 bullet cô-đọng (đã ở dạng gần-nén sẵn) + **header mang một sự-kiện integrity**: file này từng được MEMORY.md trỏ tới nhưng **VẮNG trên đĩa**, nội-dung chỉ còn trong git `d2f52ba`, dựng lại ở S69. Đây là ca con-trỏ-chết đã được vá — giữ nguyên, đừng nén mất.
|
||||
|
||||
### Cụm-dedup
|
||||
|
||||
| # | Cụm | Thành-phần gộp | Tag |
|
||||
|---|---|---|---|
|
||||
| B4-A | **META: con-trỏ chết đã phục-hồi (file archive vắng đĩa, nội-dung git-only `d2f52ba`)** | header file (dựng S69) | **cao** |
|
||||
| B4-B | **S40 STATE GROUNDING — 7 metric, 4/7 lệch docs** | 2026-05-29 | **cao** |
|
||||
| B4-C | **P11-A WorkflowApps pre-flight — mirror Proposal + 2 mâu-thuẫn spec** | 2026-05-30 | **cao** |
|
||||
| B4-D | **S39 BVAAU 7-agent extract (port khuôn từ dự-án khác)** | 2026-05-29 | **vừa** |
|
||||
|
||||
> Ghi-chú dedup: 3 bullet rời chủ-đề, không trùng nhau; cụm B4-A tách ra từ header vì nó là **sự-kiện về chính hồ-sơ**, không phải nội-dung việc.
|
||||
|
||||
### Bản-nén-nháp 4-field
|
||||
|
||||
**[cao] B4-A — con-trỏ chết: MEMORY trỏ file không tồn-tại**
|
||||
- **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, còn 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**, vì đọ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 — nghĩa là còn một nửa dữ-liệu chỉ tra được nếu biết đúng commit.
|
||||
|
||||
**[cao] B4-B — S40: đ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 cái tên ("số bảng") có nhiều phép đếm cho ra 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".
|
||||
|
||||
**[cao] B4-C — P11-A: khuôn có sẵn, nhưng 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); phần 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` (làm phẳng `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). Còn 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 được gắn cờ ngay: (a) `ApprovalWorkflow.cs:72` khai Level **KHÔNG OR-of-N** (1 ApproverUserId/Level) — **NGƯỢC** với 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à migration `ExtendApplicableTypeForWorkflowApps` có `Up/Down` **RỖNG** (enum-only, không đụng DB).
|
||||
|
||||
**[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; và "VIPIX guide" được dẫn nhưng **không verify được vì file miss**.
|
||||
|
||||
### Token-list (verbatim, investigator-codebase/2026-05-q4)
|
||||
|
||||
- **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]`
|
||||
|
||||
---
|
||||
|
||||
## reviewer/2026-05-q1 (18.707 B — "Archive Recent Activity Q1 2026-05, S21-S24")
|
||||
|
||||
**Đặc-điểm file:** 7 entry, archive 2026-05-22 (MEMORY.md 34.5 KB). Ba entry là **self-review em-main không spawn** (chế-độ UAT), bốn entry là review thật. Xuyên suốt có 1 sợi chỉ: **"Smart Friend guard active"** lặp ở cuối mọi entry — dấu hiệu vai tự nhắc chuẩn không được hạ.
|
||||
|
||||
### Cụm-dedup
|
||||
|
||||
| # | Cụm | Thành-phần gộp | Tag |
|
||||
|---|---|---|---|
|
||||
| B5-A | **Plan K1+K2 — PASS kèm 2 Major: "khuôn sentinel tạm" đẻ endpoint zombie** | S23 t1 spawn | **cao** |
|
||||
| B5-B | **Plan N→U (8 kế-hoạch self-review) — 3 phản-mẫu, gồm "vá theo báo-cáo lỗi" và vòng lặp re-seed** | S23 t4-t11 | **cao** |
|
||||
| B5-C | **Plan M — PASS, phạm-vi hẹp có chủ-đích + lệch chữ xuyên module** | S23 t3 spawn | **vừa** |
|
||||
| B5-D | **Plan AA — pre-commit PASS 0 issue + hậu-nghiệm ROI spawn** | S24 pre-commit spawn + S24 post-wrap (gộp: cùng Plan, phần sau là kết-luận chi-phí của phần trước) | **vừa** |
|
||||
| B5-E | **Chế-độ UAT không spawn (S21/S22) + 3 phản-mẫu + gotcha #47 treo** | S21 t3-t5 + S22 | **vừa** |
|
||||
|
||||
> Ghi-chú dedup: B5-D gộp 2 entry cùng Plan AA (một trước commit, một sau khi anh UAT xong 4 vòng polish) vì kết-luận thật nằm ở chỗ nối hai phần: **spawn 1 lần cho chunk nặng thì lãi, spawn cho polish thì lỗ**.
|
||||
|
||||
### Bản-nén-nháp 4-field
|
||||
|
||||
**[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), 3 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 khi UPSERT opinion `:441-468`, TRƯỚC khi đẩy con-trỏ `:502` ⇒ chữ ký người duyệt được ghi trước khi nhảy terminal; guard `skipToFinal` cho V1 đặt ở **người gọi** (`:147-149` trong TransitionAsync) nên V1 giữ nguyên 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 như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:** đặt **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 thì phải có **chunk dọn được đặt tên trước**, đứng trước cả chunk sửa test.
|
||||
- **BẤT-NGỜ:** cùng một lượt review vừa xác nhận wire ĐÚNG ở chỗ khó (thứ tự UPSERT-trước-skip) vừa bắt được lỗi ở chỗ dễ (endpoint quên xoá) — rủi-ro không tập trung ở phần phức-tạp mà ở **phần bị coi là đã xong**.
|
||||
|
||||
**[cao] B5-B — Plan N→U: 8 kế-hoạch, 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` — **đây là 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 với 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á thì phải **liệt kê TOÀN BỘ site tra-cứu cùng khuôn** rồi vá một lượt. Và bản ghi 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 nằm ở lệnh xoá mà ở chỗ seeder không có công-tắc — Plan T mới thật sự đóng.
|
||||
|
||||
**[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 tiếp 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 của test. Drafter mode `:268-275` giữ nguyên 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 (DocxRenderer có sẵn), **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, đổi 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** và 2 module anh em (`contracts.ts:29`, `budget.ts:20`) vẫn `98:'Trả lại'` ⇒ nếu deploy chung thì người dùng thấy 3 module gọi khác nhau cho cùng một trạng-thái.
|
||||
|
||||
**[vừa] B5-D — Plan AA: PASS sạch + bài toán ROI của việc spawn**
|
||||
- **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`. Ghi chú rủi-ro thấp: người không phải admin có thể truyền `isUserSelectable=false` để xem workflow chưa ghim — dữ-liệu không nhạy-cảm nên **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 lại review một lượt cho chunk nặng (wire BE / migration / xuyên tầng), bỏ qua với việc polish <30 phút (CSS/màu/layout)** — 4 lần spawn polish ≈ 100K token đổi lấy gần như không phát-hiện.
|
||||
- **BẤT-NGỜ:** "Smart Friend guard" được khai là **vẫn bật** ngay trong lượt PASS-0-issue, kèm 3 ghi-chú rủi-ro treo lại — tức PASS ở đây không có nghĩa "không còn gì", mà là "không có gì đáng chặn"; và bất đối-xứng cấu-trúc giữa 2 app (fe-user có StaticLeaf "Hộp thư", fe-admin không) **được chấp-nhận** thay vì ép mirror.
|
||||
|
||||
**[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 được 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) rồi siết lại 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, phải 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** trong 2 phiên liên-tiếp mà vẫn viết entry đầy-đủ, và ghi nhận **CICD Monitor đứng thay vai reviewer** sau deploy; đồng thời 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 vào docs.
|
||||
|
||||
### Token-list (verbatim, reviewer/2026-05-q1)
|
||||
|
||||
- **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
|
||||
|
||||
---
|
||||
|
||||
## reviewer/2026-05-q2 (3.526 B — "Archive Q2 2026-05, S34 curate")
|
||||
|
||||
**Đặc-điểm file:** 3 mục ngắn, mật-độ bài-học cao/byte. Mục S28 là **bài về ranh-giới thẩm-quyền** — sinh ra hẳn một hạng-mục kiểm mới (Cat 6), và tự khai **một luật cũ bị BỎ**.
|
||||
|
||||
### Cụm-dedup
|
||||
|
||||
| # | Cụm | Thành-phần gộp | Tag |
|
||||
|---|---|---|---|
|
||||
| B6-A | **S28 — tự-cấp-phép luật xuyên dự-án ⇒ sinh Cat 6 "authority boundary" + huỷ luật cũ** | 2026-05-22 | **cao** |
|
||||
| B6-B | **S25 Plan AB — gotcha #48 (SQLite đồng-hồ đóng băng) + bổ checklist Cat 5** | 2026-05-19 | **vừa** |
|
||||
| B6-C | **S26 Plan AG — 12 phép kiểm đối-kháng PASS + củng-cố luật ROI spawn** | 2026-05-21 | **vừa** |
|
||||
|
||||
### Bản-nén-nháp 4-field
|
||||
|
||||
**[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>]` với phase ∈ {phase-9, phase-9plus, phase-10} và 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à vai reviewer phát-hiện được nó **theo lối hồi-cố**, tức lúc xảy ra thì không có ai chặn.
|
||||
|
||||
**[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), nên chỉ lộ ra khi test đỏ ngẫu-nhiên.
|
||||
|
||||
**[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" được nâng lên **so SHA256 nguyên file** thay vì đọc diff — rẻ và không cãi được; đồng thời cả 6 commit của S26 giữ nguyên baseline 111 test, tức thay đổi UI thuần **không mua thêm rủi-ro hồi-quy**.
|
||||
|
||||
### Token-list (verbatim, reviewer/2026-05-q2)
|
||||
|
||||
- **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}
|
||||
|
||||
---
|
||||
|
||||
## test-specialist/baseline-history (32.069 B — "Baseline count history (verbatim) + aged FIFO")
|
||||
|
||||
**Đặc-điểm file:** file LỚN nhất batch B, có frontmatter YAML (`name: baseline-history` · `type: project`), 1 chuỗi số một dòng + **25 entry** newest-first (S155 → S54). Tách khỏi `MEMORY.md` 2026-06-26 vì chỉ mục vượt trần đọc 24KB.
|
||||
**⚠️ Ràng buộc nén (lệnh lead):** chuỗi số là RUỘT ⇒ **giữ đủ mốc đầu/cuối mỗi giai-đoạn**, chỉ được gom giai-đoạn, KHÔNG được rút gọn thành "254 → 561".
|
||||
|
||||
### Cụm-dedup
|
||||
|
||||
| # | Cụm | Thành-phần gộp | Tag |
|
||||
|---|---|---|---|
|
||||
| TS-A | **CHUỖI SỐ BASELINE (ruột) — chuỗi gốc + hậu-chuỗi rải trong entry + 4 lỗ + 2 mâu-thuẫn nội-tại** | dòng "Count progression" + mọi mốc `X→Y` trong 25 entry | **cao** |
|
||||
| TS-B | **S155 — 1 FAIL GIỮ ĐỎ = bug prod thật + fixture đầu tiên wire `AuditingInterceptor`** | S155 (+10 → 561) | **cao** |
|
||||
| TS-C | **S153 — fault-inject máy `mind-check` (KHÔNG phải test C#), bắt 1 GAP + bài fixture-hỏng-cho-xanh** | S153 (18/18, baseline C# không đổi) | **cao** |
|
||||
| TS-D | **S133 budget-freeze RED→GREEN (INV-A T0 + INV-C T1-T9) + va-chạm sub-file 2 invocation** | S133 INV-A + S133 INV-C (gộp: cùng feature, 1 đỏ 1 xanh) | **cao** |
|
||||
| TS-E | **S134 — lũy kế TẠM TÍNH: khuôn "2 hàm cùng seed, khớp số CHÍNH XÁC"** | S134 (+8 → 528) | **cao** |
|
||||
| TS-F | **Cụm SECURITY/authz regression (reflection harness Pattern 10 + contrast chống vacuous)** | S118b (+4→509) · S118 (+4→505) · S92 (+3→434) · S69 Office (+6) · S54 ItTicket (+13) | **vừa** |
|
||||
| TS-G | **Cụm FINANCIAL / critical-algo (giá chốt, số âm, ngưỡng, multi-winner)** | S117 (+5→500) · S116 (+9→495) · S72 (+28) · S69b (+14) · D2 (+6→419) · S87 (+7) | **cao** |
|
||||
| TS-H | **Cụm Supplier Excel-import (case-collation + 1 BUG NRE báo-không-sửa)** | D3 (+10→450) · S113 (+19→477) | **vừa** |
|
||||
| TS-I | **Cụm bug-fix / spec-change / compile-fix arity** | D4 (+8→458) · S97 (+0) · S86 (+3) · S85b (+3) · S85 (+5) · S77c (rewrite 12→11) · S77 (+7) · S76 (+5) · S74 (+5) | **vừa** |
|
||||
| TS-J | **2 ghi-chú kỹ-thuật lẻ giữ lại (S57bis / S56)** | mục "S57bis / S56 (notes kept)" | **thấp** |
|
||||
|
||||
> Ghi-chú dedup: TS-D gộp 2 entry S133 vì là **hai nửa của một chu-trình test-before** (INV-A dựng đỏ, INV-C đẩy xanh) — gộp KHÔNG mất danh-tính vì giữ đủ 2 verdict. TS-F/TS-G/TS-H/TS-I gộp theo **loại rủi-ro** (security / financial / import / bug-fix) chứ không theo thời-gian, mỗi entry trong cụm vẫn giữ 1 dòng riêng có số delta.
|
||||
|
||||
### Bản-nén-nháp 4-field
|
||||
|
||||
**[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 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) → 374 (S85b +3) → 377-395 (S86-S86d) → 402 (S87 +7) → 419 (D2 +6 Mig58 multi-winner + translation-smoke)**. Hậu-chuỗi nằm 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 này 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ì chỗ đó 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 (4 lỗ + 2 mâu-thuẫn nội-tại, trích nguyên văn):** ① lỗ **419 → 431** (+12 không có mốc) · ② lỗ **434 → 440** (+6) · ③ lỗ **477 → 486** (+9) · ④ lỗ **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ó. **Tôi 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.
|
||||
|
||||
**[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 ở trạng-thái `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: **đây là 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:** hạ-tầng mới `SoftDeleteFixture` (~90 LOC, ngay trong file test) là **fixture ĐẦU TIÊN của repo** có 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 — nếu để 0 thì "bất động" và "rỗng" nhìn giống hệt nhau.
|
||||
|
||||
**[cao] TS-C — S153: tiêm lỗi vào máy `mind-check`, và 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**. Bắt **1 GAP THẬT**: ở chế-độ closed, **xoá con-trỏ → cờ/exit 0** trong khi **giữ con-trỏ đang-chờ một cách trung thực → trượt/exit 1** ⇒ đường rẻ nhất để xanh là đường SAI (chữ luật cấm, nhưng 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 vào `## MIND-1` sau khi `renum(...,2)` đã đổi heading ⇒ `str.find` = −1 ⇒ chèn **GIỮA** heading ⇒ `ts:` không nằm đầ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** ⇒ phải 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 đem dùng lại — (i) **cặp CÙNG-RUỘT khác đúng 1 biến** (cap=32 vs cap=1 trên cùng tệp = chứng đọc config LIVE; `_end` giả `_pause=9` vs không = chứng nguồn đếm là ĐĨA, dùng `diff` chứng 2 output BYTE-IDENTICAL); (ii) **đo wall-clock phải đi nhánh ĐẮT** — lệnh literal rơi vào nhánh thoát-sớm (repo chưa có `_mind`) là tự đo nhánh rẻ rồi gọi đó là chi-phí thật; đo 55-64ms/lượt so với ước 140-300ms ⇒ **lệch 2.2-5.4×**, trả lead điền lại.
|
||||
|
||||
**[cao] TS-D — S133 budget-freeze: dựng ĐỎ rồi đẩy XANH, và 2 lượt cùng vai đè file của nhau**
|
||||
- **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** (vì sau khi vá thì snapshot chỉ được đặt qua transition); đổi cặp giá trị 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 sót 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** — 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ã).
|
||||
|
||||
**[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 thì 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" là **kiểu C6 — số chính xác, 2 hàm, 1 seed**; (3) FluentAssertions `ContainInOrder(params T[])` đụng overload với chuỗ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 trong DB **sau `TransitionAsync`** — nếu assert sớm sẽ thấy "không có row" và kết luận sai về tính-năng.
|
||||
|
||||
**[vừa] TS-F — cụm SECURITY/authz: khuôn reflection + đối-chứng chống vacuous**
|
||||
- **VIỆC:** 5 đợt khoá quyền bằng test hồi-quy: S118b Reports (+4 → **509**) · S118 Procurement master-access (+4 → **505**) · S92 admin-only revoke (+3 → **434**) · S69 Office golive seed (+6) · S54 ItTicket reassign (+13).
|
||||
- **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")]`, còn `MyDashboard` **cố ý để trần** — T4 assert `(attr?.Policy).Should().BeNull()` để **cấm mọi policy** (thêm policy = khoá luôn dashboard cá-nhân của role không-Admin); csproj đã ref Api từ S45 nên không vướng. S118 `ProcurementMasterAccessSeedTests.cs` 4 [Fact], **T2 ⭐ScopeIsolation** là lõi: DeptManager đứng riêng, pre-grant → revoke → seed ⇒ 9 key VẪN all-false (row còn giữ vết) = cấp quyền cho 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 ở chỗ 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"; và authz theo dữ-liệu (handler) khác hẳn authz tĩnh (`[Authorize]` đọc bằng reflection) — hai điểm thực-thi khác nhau, phải 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 một dòng ghi chú đầu mỗi file.
|
||||
|
||||
**[cao] TS-G — cụm FINANCIAL / critical-algo: giá chốt, số âm, ngưỡng, đa người thắng**
|
||||
- **VIỆC:** 6 đợt test cho phần dính tiền: S117 ProMinMax (+5 → **500**) · S116 số âm (+9 → **495**) · S72 Mig 54 (+28 → **334**) · S69b ngưỡng CCM + urgent (+14 → **306**) · D2 multi-winner (+6 → **419**) · S87 CCM budget-period (+7 → **402**).
|
||||
- **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** (phát-sinh-giảm/hoàn tiền) đi qua; ranh-giới quyết-định là ca **net==0 nhưng từng dòng ≠ 0** `[+500k, −500k] → ChoDuyet`, và ngưỡng CCM tính trên **NET** (`[+1.2tỷ, −0.3tỷ]=0.9tỷ` không chạm ngưỡng ⟂ đố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).
|
||||
- **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 — đổi method prod sang 5 tham số khiến `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, phải test bằng unit + reflection, và ghi rõ đó là OBSERVATION. ③ D2 — `PurchaseEvaluationQuote.PESupplierId` có **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 trong cùng một luồng. ④ S87 — `Changelog.UserId` là `Guid?` **không FK** nên Guid ngẫu-nhiên vô-hại.
|
||||
|
||||
**[vừa] TS-H — cụm Supplier import: lỗi so-sánh hoa/thường và một NRE báo-mà-không-sửa**
|
||||
- **VIỆC:** D3 (+10 → **450**) test-before cho import Excel; S113 (+19 → **477**) 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, còn prod SQL Server (CI) thì bắt — nên `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, và **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 vẫn 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**), trong khi 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. Và 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`, sẽ tự lật true khi hằng được sửa.
|
||||
|
||||
**[vừa] TS-I — cụm bug-fix / spec-change / compile-fix arity**
|
||||
- **VIỆC:** 9 đợt nhỏ: D4 sign-off A1 (+8 → **458**) · S97 cờ runtime (+0) · S86 loại người tạo khỏi chuông (+3) · S85b giữ workflow khi lưu nháp (+3) · S85 người khai tự gỡ cờ gấp (+5) · S77c đối-xứng → **BẤT** đối-xứng (12→11) · S77 note giá đề-xuất (+7) · S76 tách cột PRO (+5) · S74 CcmNote (+5).
|
||||
- **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ũ được **đổi tên** vì tên cũ gây hiểu nhầm. S97 = spec-change: đọc thẳng cờ `EndedByLevelFinalize` thay vì 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 là **đả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 (vì actor ở Cấp 1 còn 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`, nên người vừa là Drafter vừa là Procurement-không-DM vẫn gỡ được **cả 2 cờ**; S77c ghi thẳng "test theo CODE" (không đụng prod).
|
||||
|
||||
**[thấp] TS-J — 2 ghi-chú kỹ-thuật lẻ**
|
||||
- **VIỆC:** giữ lại 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 vào FK-guard ở create-handler (phòng-thủ nhiều lớp, không phải bug prod). S56 — `ExecuteUpdateAsync` **đi vòng qua change tracker** ⇒ bản đọc đang tracked bị cũ, phải `.AsNoTracking()` đọc lại; transaction đã ship ở mức `IsolationLevel.Serializable`.
|
||||
- **BÀI-HỌC:** hai ca đều thuộc 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 thì mới viết được assert đúng.
|
||||
- **BẤT-NGỜ:** cả hai ghi-chú **sống sót qua mọi vòng curate** dù không kèm test nào — tức giá-trị của chúng nằm ở chỗ **chặn một giả-định sai**, không ở số lượng test.
|
||||
|
||||
### Token-list (verbatim, test-specialist/baseline-history)
|
||||
|
||||
- **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
|
||||
|
||||
<!-- CONT -->
|
||||
Reference in New Issue
Block a user