71 lines
18 KiB
Markdown
71 lines
18 KiB
Markdown
# Investigator-Codebase Agent — Persistent Memory
|
||
|
||
> **Persistent diary cross-session.** Auto-injected first ~200 lines at spawn (L1 HOT). Update BEFORE every stop; keep entries to 1 line (gotcha #53 truncation — index hit 28KB @S148, hard-cap 24.4KB). Detail → `archive/2026-07.md` + `archive/_INDEX.md` (sha-keyed substring, Ctrl-F) + git `d2f52ba` + `archive/2026-0{5,6}.md` + `runs/*/sub-*.md`. Renamed S39: investigator → investigator-codebase (internal; external → investigator-api).
|
||
|
||
## 🎯 Role baseline
|
||
Read-only INTERNAL audit SOLUTION_ERP. Tools: Read/Grep/Glob/Bash + 5 RAG MCP. Output: concise <500w + file:line. Skills: contract-workflow + permission-matrix + ef-core-migration.
|
||
- ✅ MINE: sqlcmd schema scan, EF/grep, controller/authz audit, migration diff, count grounding
|
||
- ❌ NOT: external docs/CVE/lib → investigator-api · write → implementer · test → test-specialist · architecture decision → em main
|
||
|
||
## 📌 Recent invest (1-line digest; full detail → archive/runs/git)
|
||
- **S155 (07-27) PE xóa-phiếu màn DUYỆT [24 finding, `runs/2026-07-27-S155-pe-delete-approver/sub-invest-be-1.md`]:** DELETE `/api/purchase-evaluations/{id}` ĐÃ CÓ (`PurchaseEvaluationsController.cs:146`, authz = class `[Authorize]` TRẦN, 0 policy action-level toàn file) nhưng guard allow-list `{DangSoanThao,TuChoi}` (`PurchaseEvaluationFeatures.cs:1404`) chặn ChoDuyet. `Remove()`=**SOFT** gián tiếp qua `AuditingInterceptor.cs:56-62` (Deleted→Modified+IsDeleted/DeletedAt/DeletedBy) — lớp #81-EXT indirect. Lũy kế: `PeBudgetAccumulator.cs` 2 hàm/4 call-site DUY NHẤT (0 read-site khác, Reports 0-hit PE); ChoDuyet ăn số ở **2 chỗ** — `ComputeAsync:54` PrevSubmitted(ChoDuyet+DaDuyet) + `ComputePendingAsync:114` PendingSelected(∉{Nháp,DaDuyet,TuChoi}+winner); `TraLai` KHÔNG triệt tiêu; `TuChoi` rơi khỏi 100% phép cộng. Không dòng `IsDeleted` nào trong query → dựa global `HasQueryFilter` (`PurchaseEvaluationConfiguration.cs:84`), `IgnoreQueryFilters`=0-hit ⇒ soft-del TỰ rớt, không sửa accumulator. Snapshot Mig 67 KHÔNG re-compute. 🔴 Cascade 6 FK (`:75-80`) **không bao giờ chạy** vì soft ⇒ con `BaseEntity` (no IsDeleted) ở lại — rác ẩn, chưa lộ vì mọi read-site join từ PE root. **Git khảo cổ TRÚNG:** "chỗ cho hủy" = nút Từ chối, gỡ `6db195d` 12-06-2026 S60 (anh Kiệt "Duyệt hoặc Trả về thôi"); guard `PurchaseEvaluationWorkflowService.cs:101-106` còn sống + tự trỏ "Xóa phiếu khi còn Bản nháp" = **chính cái lỗ** S155 phải vá. Q7: 0 tiền lệ hủy chứng-từ in-flight; khuôn gần nhất `CancelMeetingBookingHandler` (`MeetingFeatures.cs:457`, Status=Cancelled NOT IsDeleted + owner-or-admin check). Tag `[s155, pe-delete-approver, s60-tuchoi-removal-hole, soft-delete-no-cascade]`
|
||
- **S155 (07-27) PE xóa-phiếu FE/authz-slice [18 finding, `runs/2026-07-27-S155-pe-delete-approver/sub-invest-fe-2.md`]:** Khối HÀNH ĐỘNG = `components/pe/PeWorkflowPanel.tsx:458-506` **byte-identical 2 app** (md5 `02671be6…`, cùng PeDetailTabs/PeListPanel/ListPage) ⇒ sửa-1-bên-là-sót. Gate 3 tầng: `?pendingMe=1`→`readOnly={!pendingMe}` (`ListPage:590`/`:676`) · `next=workflow.nextPhases` BE-source (`:257`) · `blockedByV2Level` (`:99-103` isAdmin ∥ actor∈currentApproval.approvers). 🔴 Plumbing xóa ĐÃ NỐI SẴN vào màn duyệt: `onDelete={del.mutate}` truyền ở `ListPage:573`+`:668` (`api.delete('/purchase-evaluations/:id')` `:90`) nhưng nút chết vì `PeDetailTabs:445` đòi `mode==='workspace' && canEditPhase && !readOnly`; nút hiện chỉ ở `:460` `phase===DangSoanThao`. **Authz 2 tầng:** PE vùng FE = **0-hit** `usePermission|PermissionGuard` (guard chỉ sống ở Master+Users, khuôn `DepartmentsPage:101 action="Delete"`); `menuKeys.ts` KHÔNG có const `Pe_*` (chỉ root `:23`) — `Pe_*` sinh BE `MenuKeys.cs:134-141`, seed `DbInitializer:1870-1877`, FE khớp bằng regex `Layout.tsx:120` (fe-admin `:107` **LỆCH**, thiếu WfView). 🔴 `Pe_*` **KHÔNG ∈ `MenuKeys.All`** ⇒ `Program.cs:82-89` không đăng-ký policy `Pe_*.{action}` (0 dynamic provider) ⇒ dùng `[Authorize(Policy="Pe_…")]` = policy-chưa-tồn-tại; **dùng được ngay = `PurchaseEvaluations.Delete`** (root ∈ All, + `GetMyMenuTreeQuery:66-72` cho `Pe_*` KẾ THỪA cờ CRUD từ root ⇒ 0 key mới, 0 migration). Ma trận enumerate `db.MenuItems` (`PermissionFeatures.cs:20`) nên ô Delete đã hiện sẵn. Q6: thêm-nút KHÔNG kích 4-place (0 route/page/staticMap `Layout.tsx:56-106`); `Pe_*` resolve nhánh regex `:119-134` KHÔNG qua staticMap. Tag `[s155, pe-delete-fe-authz, two-app-byte-identical, pe-keys-not-in-All]`
|
||
- **S155 (07-27) PE menu/trash/dept-head [21 finding, `runs/2026-07-27-S155-pe-delete-approver/sub-invest-menu-3.md`]:** 🔴 Menu = **SEED DB** không hard-code FE — 4 mục con `Pe_<Code>` khai `DbInitializer.cs:1874-1877` (`"Duyệt"`=`:1877`); thêm mục = restart API, **0 migration** (upsert `:1888-1914` chỉ sửa `Order`, **KHÔNG đụng Label** ⇒ rename phải qua `labelBackfill:1918-1924` HOẶC `DisplayLabel` admin-UI mà `MenuDtos.cs:14` ghi rõ **CHỈ ăn fe-user**, fe-admin luôn Label gốc). 6-chỗ-mirror (không phải 4): +2 seed permission SONG SONG `:2092-2096`/`:2492-2496`; `menuKeys.ts` FE **0-hit `Pe_*`** (sinh regex `Layout.tsx:120`, fe-admin `:107` thiếu `WfView` = lệch sẵn). Đổi KEY = mồ côi **13 row/key × 10 key** (Permissions Dev) ⇒ giữ key chỉ đổi label. **`Đã duyệt` chạy được HÔM NAY**: `phase=` đã đủ 3 tầng (`Controller:23`→`Features:565`→`:620`), DaDuyet=7, FE `:39/:75/:395` sẵn ⇒ `?type=1&phase=7`, 0 route mới. `pendingMe` KHÔNG phải param BE — FE rẽ `/inbox` (`ListPage:61-68`) rồi lọc DaGuiDuyet client-side `:182`. Tự verify: `IgnoreQueryFilters` **0** src/Backend nhưng **15 hit tests** (khuôn `Suppliers.IgnoreQueryFilters().CountAsync()` `SupplierImportV2DedupTests`); `b.HasQueryFilter(`=12. An-toàn accumulator = **cấu trúc**: `PeBudgetAccumulator.cs:42`/`:100` dựng `peers` FRESH mỗi lần ⇒ chỉ rò nếu helper-dùng-chung/sửa `:84`/tái-dùng-đường-snapshot. 🎁 Changelog `: BaseEntity` (no IsDeleted) + query lọc CHỈ `PurchaseEvaluationId` (`:1424`) ⇒ lịch sử phiếu-xóa đọc được MIỄN PHÍ; cái chôn là DETAIL bundle (chỗ `IgnoreQueryFilters` thứ 2). 0 tiền lệ thùng-rác/restore (`IsDeleted=false` 0-hit; hit "khôi phục" duy nhất = STRING `EmployeeFeatures.cs:162` = ghost-wire). 🔴 **"Trưởng phòng" CÓ TÊN, CHƯA CÓ ĐỜI**: `Department.ManagerUserId` (`Department.cs:9` cmt "TPB") = **0/10 Dev** + **2 FE hardcode `managerUserId: null` ở payload SỬA** (`DepartmentsPage.tsx:43`/`:57`) ⇒ tự-xoá sau mỗi lần sửa phòng; 5 hit BE toàn DTO-passthrough. `Level.Order`=trình-tự KHÔNG thứ-bậc (`ApprovalWorkflow.cs:86`); "Kết thúc tại Cấp 2"=`AllowApproverFinalize` F5. `PositionLevel.TruongPhong` **33/33 NULL** (V1-legacy, V2 không đọc); role `DeptManager` 8 user nhưng không nói PHÒNG. 3 lối: ManagerUserId(0-mig,vá-2-FE+nhập-tay) · cờ `AllowApproverDelete` per-Level (1 AddColumn, **khớp khuôn F2/F3/F4/F5 4× cùng bảng**) · role∧DeptId(có-data,nghĩa-lỏng). Tag `[s155, menu-seed-db, phase-param-exists, changelog-survives-softdelete, dept-head-name-only]`
|
||
- **S153 (07-26) ctx soft-memory spec [fable-real]:** thư `0df10df4` sàn-7 → spec 9-task/24-check (`runs/2026-07-26-S153-ctx-adopt/sub-fable-invest-S153.md`). Findings đắt: SE **0 content-ts-reader sống** (session_ctx.py = git-ts+mtime; C10 tự khai thiếu nguồn LATENESS) ⇒ rào-2 canonical `_TS_KEY_RE` đặt session_ctx.py, máy-7-phép = EXTEND python (tái dụng `_SECRET_RES` 1-nguồn) KHÔNG .ps1 mới (2-sự-thật); `secrets_sweep` rglob TRỌN session-dir ⇒ `_mind` tự vào lưới @snapshot §3; `_mind` mới-nhất-ở-TRÊN ⟂ `_context` FLOW ở-CUỐI (bẫy chép-khuôn); (36)(b) máy-$0/pause + vai @2-bookend ⇒ O-1 carve-out KHỎI CẦN (pause §1 NGỪNG-spawn giữ nguyên); C11(b) filter `sub-*.md`+`^sub-<role>[-.]` ⇒ đặt tên `sub-ctx-audit-*` lọt lưới ngay (kẽ trio `harness-*-return` đã queue #8 fix); roster 21 > hmw_width 20 = owner-note; (35)-(38) vết OWNER-DELEGATED khuôn `hmw_width._ratified_by`. Tag `[s153, ctx-mind-spec, ts-reader-absent, one-source-regex]`
|
||
- **S148 (07-23) PE tree DUP project by-space:** `FLOCK 01`/`FLOCK01` = **path-A TWO real Project rows** diff-by-space. Tree groups on `p.projectId` GUID (`PurchaseEvaluationsListPage.tsx:192-197`, display projectCode); PE→Project FK `ProjectId` (`PurchaseEvaluation.cs:15`) JOIN-resolved (`PeFeatures.cs:579/:647`) — NO denorm string. CRUD zero-trim (`ProjectFeatures.cs:80` validator, `:95/:138` exact `Code==` dupe-check). Both seeds exact-Code key emit ONLY no-space: `SeedRealMasterDataAsync:2776` (UNGATED :128) + `SeedDemoMasterDataAsync:2681` (gated :117 OFF-prod). Space=legacy/manual. Blast=3 tables ProjectId (Contracts/PeWorkItemBudgets/PurchaseEvaluations); only `PeWorkItemBudgets` UNIQUE(ProjectId,WorkItemId) filtered [IsDeleted]=0 (`PeWorkItemBudgetConfiguration.cs:27`). Direction: KEEP no-space (in seed→idempotent), RETIRE space (not-in-seed); soft-del survivor→seed RE-CREATES (#75/#76). Dev only FLOCK 01 pair (FLOCK03/CAL01 space-twins prod-only); prod UNVERIFIED (SSH dies on SQL-client, S134). Tag `[s148, pe-tree-dup-project-by-space, path-A-two-rows, no-normalize-crud]`
|
||
- **S147 (07-23) PE attach TraLai edit-block:** FE-ONLY block, BE zero phase-guard (`PurchaseEvaluationsController.cs:15` class `[Authorize]` only; handlers check exists only `PurchaseEvaluationAttachmentFeatures.cs:66/:160`). Gate = raw `readOnly` PROP not phase (`GeneralAttachmentsSection` :3555/:3577 hide-when-readOnly); List detail HARDCODE `readOnly=true` (:574/:669) ⇒ TraLai loại khỏi SCREEN không khỏi editable-SET (workspace `PeListPanel.tsx:88` isEditablePhase GỒM TraLai). fe-admin MIRROR. BE no-guard CỐ Ý (S78). Fix FE: `attachEditable=isDrafter&&isEditablePhase`. Tag `[s147, pe-attach-tralai, fe-only-screen-gate]`
|
||
- **S141 (07-20) adap đợt-9/10 5-lane [fable-clone-ensemble, 5/5 clean]:** (ii)-nới-có-vết đủ đóng dangling-head (3/3 squash-benign, `.session-counter.json`); tick-head=wal-commit. byte-safe⟂tolerant-EXTRACTION 2-lỗ tách → pin regex `\"?([0-9a-fA-F]{8,64})\"?`. Roster-count 3-tầng (prose/glob/pointer). refine⟂lead-omission = discriminator WHOSE-memory. Detail → `runs/…S141…`.
|
||
- **S138 (07-17) adap 3-bản 17-07 [fable-real]:** 3/3 FIT; delta hẹp hub PHẢI grep landing-site trong luật SỐNG (SE chép 4-nhãn lỗi vào `reviewer.md:46` = adopt-verbatim kế-thừa lỗi nguồn). Detail → `runs/…S138…`.
|
||
- **S135/S134/S133 (07-17) PE budget invest [fable-real, #53→sub-file cứu trọn]:** S134 lũy-kế lệch-tiền-đề row1 (`PeBudgetAccumulator.cs:42-48` PrevSubmitted ĐÃ gồm ChoDuyet; DaDuyet-only chỉ row2 :50-60) predicate 1-bản helper Features:962+WorkflowService:1018. S133 freeze-at-DaDuyet 4-assignment (`PurchaseEvaluationWorkflowService.cs:861/:899/:931/:1053`, Mig67). S135 tick H24 single-entry `session-start:181-206`. Detail → archive + runs.
|
||
- **S128 (07-16) adap-16-07 [fable-real, garble→on-behalf]:** RAG config in-repo (`AI_INFRA\claude-rag\lib\projects.py:39` owner-final, `rag.json:26` override); nhãn-đo "calibrated" chỉ C1; ghi-đĩa-trong-lúc-làm cứu draft khi garble. Detail → runs.
|
||
- **S126 (07-16) worker-pin + all-inherit sweep [opus×2]:** 14/14 opus+max (no-tier lane resolve opus DÙ lead=Fable); B1 284→15 floor-sống; mega-line blow Read-cap → perl-window. `[s126, h23-precedence]`
|
||
- **S125 (07-16) adap-review [fable-real]:** auto-memory 50 tracked/0 porcelain; 14/14 opus+effort:max; residue all-inherit floor-sống 2 (`fable-real.md:37`+`fable-clone.md:43`) vá; verifier-suspect-first.
|
||
- **S122 (07-15) retro-harvest [6/6 clean]:** 🔴 0/7 mất 7/7 XONG synthesis-sai-chỗ (orphan đo TÊN không NỘI-DUNG); lane-RỖNG≠mất; lane tự truy git đừng tin brief.
|
||
- **S116/S117 (07-13) PE negative-quote + giá-chốt RANGE [fable]:** neg-quote FE-ONLY (BE/DB nhận âm; quote sống `details[].quotes` KHÔNG `suppliers[].quotes`). S117 ApprovedPrice WRITE 1-site `ApplyApprovedPriceOnFinalize`; HĐ-gen KHÔNG đọc ApprovedPrice (GiaTri=SUM IsSelected). endsBeforeCeo=config-lie #78 (detail :1152 / list :647).
|
||
- **[archived → archive/2026-07.md + git]** S87-S114 PE recon (multi-winner IsSelected per-hạng-mục Mig65 · WinnerQuoteTotal DERIVED · budget gate PURE-ROLE · V2 routing=ApproverUserId LINEAR) · Supplier-import-v2 (ClosedXML, Mã NCC=Code, IsPublic fail-closed, import authz-mismatch) · S105 BVAAU auth RCA (relative-`/api` prod-404) · S104 30-field · S98-S101 harness · Office/HRM/Governance recon. Ctrl-F `_INDEX.md`.
|
||
|
||
## 📋 Patterns proven
|
||
### Schema scan sqlcmd
|
||
```
|
||
sqlcmd -S "(localdb)\MSSQLLocalDB" -d SolutionErp_Dev -Q "..." # runtime API (primary)
|
||
sqlcmd -S "(localdb)\MSSQLLocalDB" -d SolutionErp_Design -Q "..." # ef tooling
|
||
```
|
||
Prod: `ssh vietreport-vps` powershell-wrapper `sqlcmd -S '.\SQLEXPRESS' -d SolutionErp -E …` — ⚠️ **SSH session DIES whenever SQL-client loads** (S134/S148 re-confirm); prod-DB effectively UNVERIFIABLE this path, flag + give query for implementer. **2 LocalDB distinct**: `_Dev`=runtime · `_Design`=`dotnet ef` default. Queries: `sys.columns/indexes/tables`, `__EFMigrationsHistory`, dup-detector `REPLACE(Code,' ','')`.
|
||
|
||
### Controller / wire-claim audit
|
||
- Grep `\[Route\("api/[a-z]+"\)\]` controllers · `\[Authorize(Policy="…"` per-action (gotcha #44 silent-403; class-level `[Authorize]` trần = no phase-guard, S147)
|
||
- Grep `// Mock` / `alert(` / `setEditing(null) // close UI` — wire bugs
|
||
|
||
### Memory cross-reference
|
||
30 user-memory `C:\Users\pqhuy\.claude\projects\D--…\memory\MEMORY.md`. Key: designtime_runtime_db · workflow_fanout_reliability · canonical_spec_over_broadcast · root_cause_over_symptom · cardinality_change_grep_consumers.
|
||
|
||
## ⚠️ Anti-patterns
|
||
❌ Skip MEMORY update · ❌ Vague "seems/probably" · ❌ Missing file:line · ❌ >500 words · ❌ Scope drift to architecture rec (em main decides) · ❌ Multi-line MEMORY entries (index truncates >24.4KB)
|
||
|
||
## 🧠 SOLUTION_ERP context (numbers canonical → docs/STATUS.md; re-ground don't trust)
|
||
- **DB:** Dev `SolutionErp_Dev` · Design `SolutionErp_Design` (distinct) · Prod `.\SQLEXPRESS`/`SolutionErp`/`vrapp` via SSH `vietreport-vps`
|
||
- **Migration path:** `src/Backend/SolutionErp.Infrastructure/Persistence/Migrations/*.cs` (NOT root). Last-schema Mig 67 `AddPeApprovedBudgetSnapshot` (S133); ~67 mig.
|
||
- **Counts (docs/STATUS.md canonical):** ~88 SQL tables (count `.ToTable()` snapshot NOT DbSet) · ~253 endpoints · 68 FE pages · 54 menu keys (`MenuKeys.cs`) · ~354 test · ~71+ gotchas · 14 sub-agents (worker-tier-pin S124)
|
||
- **Tech:** .NET 10 Clean Arch (Api→Application←Domain + Infra) + CQRS MediatR + EF Core 10 + 2 React Vite TS (fe-admin :8082 + fe-user :8080) + SQL Server + Gitea CI + IIS
|
||
- **Prod:** api/admin/eoffice.solutions.com.vn · Gitea `git.baocaogiaoduc.vn/…/solution-erp` (Actions API `/actions/tasks` NOT `/runs`, cache stale ~2min #46)
|
||
- **Auth:** `admin@solutions.com.vn/Admin@123456` (full) / `nv.test@solutions.com.vn/TestUser@123456` (Drafter). Password ≥12.
|
||
|
||
## 🔄 Active workflow schemas (V1 + V2 coexist post-S17)
|
||
- **V1 Mig 21 flat** — `WorkflowDefinition` pin cũ, match Dept+PositionLevel.
|
||
- **V2 Mig 22-31** — `ApprovalWorkflow` pin PE/Contract, match `ApproverUserId` 1-1 OR-of-N cùng Cấp. Steps(Phòng)>Levels(Cấp). Proposal V2 Mig38, WorkflowApps Mig39. (Mig25 IsUserSelectable · 26 LevelOpinions UPSERT · 30 AllowApproverEditBudget · 31 SkipToFinal→ApproverLevel · 53 cờ-gấp+CeoThreshold · 54 giá-đề-xuất · 58 IsWinner multi + AllowApproverFinalize · 65 Quote.IsSelected per-hạng-mục · 66 ApprovedPrice Min-Max · 67 budget-freeze snapshot).
|
||
- **State PE 5:** Nháp / Đã gửi duyệt / Trả lại (TraLai=98) / Từ chối (TuChoi, REMOVED-path S60) / Đã duyệt
|
||
- **Mode Trả lại 4:** OneLevel / OneStep / Assignee / Drafter (Phase=TraLai). 3 đầu giữ ChoDuyet lùi pointer. Admin bypass `level.Allow*`.
|
||
- **Seeds gating:** `SeedDemoMasterDataAsync` gated `DemoSeed:Disabled` (OFF prod) · `SeedRealMasterDataAsync` UNGATED (62 dự án + 71 hạng mục Excel, per-code idempotent, exact-Code key #75/#76 resurrect risk).
|
||
|
||
## 🔄 Curate log
|
||
- **S148 (07-23):** hard-cap rewrite 28.2→~14KB — collapsed multi-line S133-S148 → 1-liner, dropped stale pointer-list (S87-S114 → grouped 1-line, verbatim in archive/git). Single-writer inv-cb (hook @28KB).
|
||
- **Prev:** S125 (23.9→~15KB, 14 lines→archive/2026-07.md) · S71/S70/S40. Verbatim byte-exact in `archive/`.
|
||
|
||
## S149 (2026-07-24→25) — 10 invocation gộp 1 entry [LEAD GHI ON-BEHALF @S150 — M9/B3 theo H2-F3 Coverage-miss (10 lượt → 0 delta sổ, ngoài lưới C11(b) by-design); nguồn: sub-file trong `runs/2026-07-24-S149-{dong-du-5-vong,hoi-tu-bookend,owner-gated-3-khoan}/` + `runs/2026-07-25-S149-adap-backlog/sub-invest-backlog.md`]
|
||
|
||
- 10 lượt invest @S149: chẩn-đoán **"SE = rules-đủ, máy-thiếu"** (0 script GHI `.session-counter.json` → chuỗi "đo rồi như chưa đo": backfill S146 · tally-rơi S148 · ×27≠33) — ĐỨNG qua gate reviewer-Fable, thành spec hội-tụ-bookend M1-M9. Backlog adap phân-loại **A-12/B-6/C-1/D-3** ("nợ REPORT không phải nợ ADOPT") → wave 6-lane land 23/23. O-2 nguồn thật = depth-axis → **posture-A**.
|
||
- #53 trong 10 lượt (ghi sổ garble @S150): L1 **ngược-#53** (return OK, ĐĨA không — vớt journal) · L5 kinh-điển (đĩa cứu) · L7 mất-cả-hai (bù deep-pass). Sub-class mới: ghi-đĩa-trong-lúc-làm = CẦN, KHÔNG ĐỦ.
|
||
|
||
## S152 (2026-07-25) — R1 retention-note không-fabricate [M9 on-behalf lead]
|
||
- **KHÔNG fabricate — uncertain thì nói "uncertain" + reason:** finding không chứng được từ đĩa/DB phải khai uncertain kèm lý-do thiếu-chứng, KHÔNG lấp bằng suy-đoán (tiền lệ tốt: A/018 S117 — verify prod-DB `Phase=ChoDuyet` trước khi phán "lũy kế đúng", thay vì đoán theo lời UAT). Claim ≠ chứng: mọi số kèm lệnh. Tag `[s152, r1-retention, no-fabricate-uncertain-reason]`
|