wal: flush 20260810 S188 dot-1 run-trace + governance state
This commit is contained in:
@ -0,0 +1,86 @@
|
||||
# bookend @open — SYNTHESIS — S186 / phiên-LOGIC L14 (2026-08-10)
|
||||
|
||||
**ONE-SHOT bookstart (YC-018): BƯỚC 0 → Phase 3.5 chạy trọn MỘT LƯỢT, 0 cửa chen giữa.**
|
||||
9 vai · 13 artifact · 12 lượt máy · 668 test PASS · counter 59→60 · Sàn-3 SẠCH.
|
||||
|
||||
---
|
||||
|
||||
## 1. Verdict từng vòng
|
||||
|
||||
| vòng | vai | verdict |
|
||||
|---|---|---|
|
||||
| V1 tooling | `tooling-auditor` | `PASS_WITH_FLAGS 7` |
|
||||
| V1 harvest | `harvest-curator` | `GATE-FAIL-6` |
|
||||
| V1 KIỂM | `ring1-audit` | `21Đ·3T·4KC` + lượt nối H2 `28Đ·1T·4KC` |
|
||||
| V2 soi-lead | `lead-stale-auditor` | light **6 FLAG** · deep **2 FLAG** |
|
||||
| V2 soi-lead | `lead-gap-auditor` | light **6 FLAG** · deep **6 FLAG** |
|
||||
| V2 KIỂM | `ring2-audit` | `18Đ·0T·2KC` |
|
||||
| V3 trio | `harness-eval` → `harness-refine` → `harness-audit` | `MIXED` → `4 action·16 BÁC` → `34Đ·9T·3KC` |
|
||||
| V4 sleep | máy | `pending 6/15 ⇒ IM` — skip **CÓ KHAI** |
|
||||
| V5 floor | máy | TOTAL 58 / INFORM 18 |
|
||||
| Ctx | `ctx-audit` | `TRUOT — 5 FLAG` |
|
||||
|
||||
---
|
||||
|
||||
## 2. Ba việc đã LÀM XONG trong cửa (không hoãn)
|
||||
|
||||
1. **Trả nợ `OVERDUE:deep`.** Lead chạy `nhip-no-probe` **trước** tick ⇒ đọc hàng cũ ⇒ suýt bỏ nấc deep. `lead-gap-auditor` bắt → lead đo lại xác nhận → **chạy bù cùng cửa**. `light_at=deep_at=60`, và tally ghi **light TRƯỚC deep** ⇒ `signal_last_kind=deep` — **trả đúng lời hứa `bookend-close-synthesis.md:33`** đến hạn đúng lượt này.
|
||||
2. **Biên nhận 4 thư hub** (đã `executed` từ S179/S181/S185 nhưng chưa có dòng sổ + chưa có copy). Hash **4/4 byte-identical**, `pull-cach 5 → 0`. Khai rõ đường phán **P6 thắng `check-email.md:67`** + ghi nợ chưa sửa lệnh.
|
||||
3. **Vá 3 bề mặt bị sót** — `session-start.md:142` (nhãn `Phase 11` chết, FLAG `ring2` bắt trong vùng mù) · `migration-todos.md` header (cảnh báo đóng băng) · `run.md` (stale, `ctx-audit` FLAG-3 bắt).
|
||||
|
||||
---
|
||||
|
||||
## 3. Hai con số LÁI HÀNH ĐỘNG bị lật — và lật đúng
|
||||
|
||||
### (a) `ring2-audit` over-cap → **CHẾT**
|
||||
Lead đưa tiền-đề *"ring2 30.781 B, A5 bất-khả-thi"* vào brief. **Bị bác 3 nguồn độc lập**: `lead-gap-auditor` đo tươi 24.859 · `harvest-curator` xác nhận + trỏ commit `c2f45231` · `ring1-audit` đóng bằng **2 nguồn khác lớp** (`git cat-file -s` tại chính SHA = `stat` đĩa = 24.859).
|
||||
⇒ Nợ trong brief **đã trả ~10h trước phiên**. Bài: **tiền-đề trong brief của lead cũng phải bị đo**, không được miễn.
|
||||
|
||||
### (b) `OVERCAP-HARM` 7 vai → **2 vai**, sai **2 tầng độc lập**
|
||||
- **Tầng số học:** `ring2` 24.859 **< 24.986** ⇒ "7" thật ra là **6** ngay trong đơn-vị của chính con-ĐO.
|
||||
- **Tầng đơn-vị:** governor đếm **BYTE**, hook cắt đếm **KÝ-TỰ**; tỉ số VN **1,0349–1,2285** ⇒ **2 vai** (`reviewer` 108,1% · `harvest-curator` 101,4%).
|
||||
|
||||
🔑 **Cái KHÔNG mất đi khi con số nhỏ lại:** `soft_cap_bytes` 30.720 B → **25.006–29.684 ký-tự** ⇒ **nằm TRÊN đường cắt với 29/29 vai** = governor **NGUY HIỂM**; `autoinject_cap` an toàn **chỉ biên 1,0%**. ⇒ Vấn đề **không phải chỉnh số** mà là **đơn-vị sai từ gốc** → slot **(76)** chờ anh.
|
||||
|
||||
**4 đường độc lập cùng chỉ về KÝ-TỰ:** cửa-sổ-render banner (chỉ char/1024 lọt, 3 cách kia trượt) · `reviewer/MEMORY.md:97` phát biểu thẳng · **phép vi-sai-biệt** của `harness-audit` (byte đòi 3 banner, quan sát 1) · **lập luận đơn-điệu-git** của lead (nhân chứng 3: byte ≥ 26.803 lúc banner khai 24.9 ⇒ **byte-hypothesis mâu thuẫn**).
|
||||
🟡 Hook nằm NGOÀI repo ⇒ **chưa ai đọc nguồn chân lý**; **"2 vai" là CẬN DƯỚI**. **E1 (probe đếm ký-tự) chưa dựng.**
|
||||
|
||||
---
|
||||
|
||||
## 4. 🔑 Bài học lớn nhất — **6 thước hỏng, CẢ 6 thuộc NGƯỜI KIỂM**
|
||||
|
||||
| thước hỏng | của ai | tưởng bắt được gì | thật |
|
||||
|---|---|---|---|
|
||||
| `grep -c Off` → "Off**ice**" | ring1 | H1 sai | H1 đúng |
|
||||
| regex không nuốt `**` (`Mig **71**`) | ring1 | H1 bịa | H1 đúng |
|
||||
| `find -maxdepth 1` che `harvest/` lồng | ring1 | H2 dương-giả | H2 đúng |
|
||||
| `tail -1` gặp 4 dòng trắng đuôi | lead-stale-deep | lane2 cụt ruột | lane2 **có** dòng chốt |
|
||||
| strict probe 0-hit vì **bold marker** | harness-audit | refine bịa verbatim | refine đúng, neo `:97` |
|
||||
| `grep -c "treo-chờ-anh"`=5 (1 hit ở dòng LUẬT) | ctx-audit | — | tự trừ còn 4 |
|
||||
|
||||
➕ **5 lần vai-kiểm tự phá cáo buộc của CHÍNH MÌNH** (`ring2` ×3 · `refine` ×1 · `audit` ×2 — trong đó **1 phép tự khai INCONCLUSIVE và TỪ CHỐI tính vào chứng cứ**).
|
||||
|
||||
> **Luật `ring1` tự rút và tự áp vào mình cùng lượt: *"2 engine cãi nhau thì đi tìm engine HỎNG, đừng chọn con SỐ."***
|
||||
> **Không lần nào người bị kiểm sai. Sai luôn nằm ở dụng cụ đo.**
|
||||
|
||||
---
|
||||
|
||||
## 5. `#53` — 8/9 garble, mất 0 byte
|
||||
|
||||
**8/9 vai garble ở lượt trả ĐẦU**; ngoại lệ duy nhất = `harness-refine`. **Recover 100%** bằng SendMessage resume + **ép form nén**.
|
||||
- **Mẩu garble 2 lần CHỨA FINDING** (`ring2`: *"Re-derivation is stronger than either vai claimed"* · `harness-audit`: *"false 0-hit (bold markers); lives at `:97` not `:98`"*) ⇒ **đừng vứt**.
|
||||
- **Nấc tin cậy bản lượt-trả-2** (`ring1` đo): **số khớp 3/3 tuyệt đối, NHÃN trôi** — bắt được **1 tên ma** (`Puro`, đọc từ comment *"mirror layout Puro"*) + **1 neo lệch dòng**. ⇒ **`verified-number / unverified-label`**: số dùng được sau spot-check, **mọi TÊN RIÊNG phải chạm đĩa** mới vào báo cáo.
|
||||
- `ctx-audit` xác nhận **§E của `_mind` KHÔNG dính lớp đó** — 12/12 verdict byte-exact, 12/12 con-trỏ giải được.
|
||||
|
||||
---
|
||||
|
||||
## 6. Nợ mở — đã có nhà
|
||||
|
||||
**Chờ anh:** `(76)` đơn-vị governor · `(77)` `migration-todos` dựng lại hay khai tử · `(78)` `check-email.md:67` · `(79)` `YC-002`. *(+ `(73)`(74) tồn từ S185; `(22)` `(67)` `(72)` còn sống.)*
|
||||
**NEXT-em:** nén 2 vai · `ctx-audit` A2(a) TRƯỚC A2(b) · chạy `measure-agent-memory.ps1` · 3 vai vắng row strike-ledger · `_mind` §D back-propagate @MIND-1 · 2 stale skill · `agents/README:27`+`:264`.
|
||||
|
||||
**Post-wave assertion SẠCH:** mọi thay đổi cây làm việc đều của lead (single-writer). Sub chạm đúng **1 file** — `.mfe-state.json`, do `mfe-eval.ps1` ghi theo `read_only_scope` của chính nó, **`harness-eval` khai trước**; nội dung append-only, **số không đổi** (`strikes 25 / rca 16` cả 3 mốc) ⇒ **verified-KEPT** (AS-10).
|
||||
|
||||
## 7. Mồi-ngầm @close (`ctx-audit` gieo)
|
||||
Ý §D *`soft_cap_bytes` nguy hiểm / con số là quyền anh* — **nhà DUY NHẤT lúc gieo = `_mind` §D**. Lead **đã cấp nhà durable = slot (76) HANDOFF** trong cùng cửa. **@close vai chấm nó còn sống hay rơi im lặng.**
|
||||
🔑 Vai chọn ý này vì **ngược với mồi S185**: món này **có người phán rõ ràng là anh** ⇒ nếu vẫn rơi thì hỏng ở **ĐƯỜNG DẪN**, không ở chỗ thiếu chủ.
|
||||
@ -0,0 +1,98 @@
|
||||
# harness-audit — trio nấc 3/3 (con KIỂM) — S186
|
||||
|
||||
> Lượt-trả-1 dính **#53** (31 tool-call); mẩu garble **chứa 2 finding** (thước bold-marker hỏng + neo `:97` không phải `:98`) ⇒ giữ. Dưới = lượt-trả-2.
|
||||
|
||||
**VERDICT: `HARNESS-AUDIT: 34Đ / 9T / 3KC`** — **~90 ô đối chứng, 87 khớp byte-exact**; 3 lệch **đều truy được cơ-chế**.
|
||||
|
||||
## 🔴 `DON-VI-PHAN-XU` — **KÝ-TỰ. 2 vai bị cắt thật.** con-ĐO SAI, con-ĐỀ-XUẤT ĐÚNG
|
||||
|
||||
`reviewer` **27.013 ch = 108,1%** · `harvest-curator` **25.348 ch = 101,4%**.
|
||||
**Nền chứng 3 chân:**
|
||||
1. Chỉ **char/1024** rơi vào cửa sổ render "24.8KB"; **3 cách kia trượt**.
|
||||
2. `reviewer/MEMORY.md:97` phát biểu thẳng: *"hook đếm **KÝ TỰ** còn `wc -c` đếm **BYTE** — tiếng Việt đa-byte ⇒ 2 số lệch nhau là bình thường"*.
|
||||
3. 🔑 **Phép VI-SAI-BIỆT của chính audit (không nấc nào chạy):** byte-hypothesis dự đoán **3 vai spawn** phải bannered (harvest 28.071 · lead-gap 25.454 · ring1 25.261 B); char-hypothesis dự đoán **1**. **Quan sát = 1.** lead-gap spawn 2 lượt + ring1 spawn 2 lượt = **4 cơ hội im lặng**.
|
||||
|
||||
🟡 **Chỗ audit KHÔNG chắc, khai thẳng:** hook nằm **NGOÀI repo** (0 hit `scripts/`+config) ⇒ **không ai đọc được source chân lý**; ngưỡng nằm đâu đó **[24.986 … 25.000] ký-tự**. Nhưng **dân-số 2 BỀN** với cả khoảng đó lẫn cả 2 quy-ước KB — vai thứ 3 (`ctx-audit` 23.055) cách mép **1.931 ký-tự**.
|
||||
🔴 **"2" là CẬN DƯỚI:** `impl-be` 22.633 + `lead-gap` 22.226 chỉ cần **+2.350 ký-tự** là qua vạch.
|
||||
|
||||
## TRƯỢT — con ĐO (4)
|
||||
| claim | thật |
|
||||
|---|---|
|
||||
| `.archive-strikes.json` "26 row = 0" | **24 row = 0** (26 TỔNG − 2 row nó vừa kể) — **off-by-2 đúng 2 dòng nó vừa đặt tên** = lỗi meta-count lớp S173/S119. Substantive claim vẫn đúng |
|
||||
| ngầm định ledger phủ hết roster | ledger **26 key / 29 vai** ⇒ **3 vai (`score-counter`·`score-count-auditor`·`ring5-audit`) KHÔNG CÓ ROW** ⇒ **vắng-mặt đọc thành strike=0**. 🔴 **Không nấc nào nêu** |
|
||||
| "`ring2` 24.859 … đều > ~24.986" | **24.859 < 24.986** — 🔴 **SAI SỐ HỌC ngay trong đơn-vị của chính nó** ⇒ "7 vai" là **6** kể cả khi chấp nhận byte |
|
||||
| headline `OVERCAP-HARM` = 7/29 | **sai 2 tầng ĐỘC LẬP**: 7→6 (số học) rồi 6→2 (đơn-vị) |
|
||||
> 🟢 `eval` **KHÔNG bịa số nào** — 7/7 byte tái dựng chính xác. Nó sai **ĐƠN-VỊ** và sai **SỐ HỌC** — hai lỗi khác hẳn bịa.
|
||||
|
||||
## TRƯỢT — con ĐỀ-XUẤT (5, đều minor/truy được)
|
||||
| claim | thật |
|
||||
|---|---|
|
||||
| tỉ số byte/char "**corpus** VN 1,107–1,16" | đó là range của **7 vai nó đo**. Corpus 29 vai = **1,0349–1,2285** ⇒ **lệch hướng AN TOÀN** (range rộng hơn ⇒ luận điểm *"không byte-cap cố-định nào đúng"* **MẠNH HƠN nó tưởng**) |
|
||||
| "`autoinject_cap` 25.600 B = AN TOÀN" | 🔴 **tự mâu thuẫn trong cùng đoạn** (2 dòng sau tuyên "không byte-cap nào đúng"). Số: break-even ratio **1,0246**; min quan sát **1,0349** ⇒ biên chỉ **1,0%**. File thuần-ASCII ở đúng cap = 25.600 ký-tự ⇒ **BỊ CẮT**. ⇒ **an toàn HÔM NAY, không an toàn về CẤU TRÚC** |
|
||||
| "≈26.500 ký-tự" | là **một ĐIỂM** ứng ratio 1,159 (của reviewer), không phải khoảng. Thật = **25.006–29.684**. Kết luận không đổi |
|
||||
| "Grep **toàn repo**" | không phải — sót `docs/gotchas.md:965` + `sessions/2026-05-28-s35-*`. 🔑 **Bỏ sót này làm YẾU chính nó**: `gotchas.md:965` là **nhân-chứng ĐỘC LẬP THỨ 2** (*"MEMORY size warning 24.6KB exceeds 24.4KB"*) |
|
||||
| neo `reviewer/MEMORY.md:98` | trích **nguyên văn ĐÚNG**, neo **SAI 1 dòng** — sống ở **`:97`**. Lớp `unverified-label`: **số đúng, neo trôi** |
|
||||
> ✅ `soft_cap_bytes` 30.720 **NGUY HIỂM — ĐÚNG và bền hơn refine trình**: 30.720 B → **25.006–29.684 ký-tự qua CẢ 4 tỉ số quan sát** ⇒ **TRÊN đường cắt với 29/29 vai**. Break-even 1,2295 vs max quan sát 1,2285 — **hở đúng 20 ký-tự**.
|
||||
|
||||
## RE-CHAY — 5 số / 4 instrument độc lập
|
||||
1. `memory-archive-gate.ps1` full → ctx-audit 26695/3 entry/WARN keep-floor · harvest ~27224 · reviewer ~30982 · **A7 429/429/0** — **KHỚP eval 100%**
|
||||
2. `measure-agent-memory.ps1` → 29 vai, sum **450.467** — KHỚP
|
||||
3. python decode-UTF8 codepoint **29 vai** → **KHỚP refine 7/7 BYTE-EXACT**
|
||||
4. `.archive-strikes.json` → khớp phần thực chất, **LỆCH "26 row = 0"** (thật 24)
|
||||
5. A1 stub 6 dòng → **7.511 B** vs refine **7.505** — **LỆCH 6 B**, truy về quy-ước `\n` (refine dùng **2 quy-ước trong 1 return**: không-newline ở A1, có-newline ở A2). **Decision-impact NIL**
|
||||
|
||||
## FALSIFY — **2 BROKE vào chính audit**
|
||||
- 🔴 **BROKE (tự)** — *"grep `đếm KÝ TỰ` = 0 hit ⇒ refine BỊA verbatim"*: chuỗi thật là `đếm **KÝ TỰ**`, **bold marker chen giữa ⇒ 0-hit GIẢ**. Probe lỏng thứ 2 bắt ra `:97` ngay. 🔑 **Suýt kết tội bịa-đặt oan bằng chính lỗi `.{50}x` đã ghi trong auto-memory.**
|
||||
- 🔴 **BROKE (tự)** — citation-test: token bắt được **cũng nằm ở HEAD <24.986 B** ⇒ **haystack tự-khớp, chứng minh 0**; token DUY-NHẤT-ở-đuôi = **0 hit toàn run-folder** ⇒ phép **INCONCLUSIVE**, audit **KHÔNG tính vào chứng cứ**.
|
||||
- **HELD** — *"24.8KB trùng char chỉ là trùng hợp"*: byte 28.071 lệch char **10,7%**, cửa sổ render 24.8 chỉ rộng **±0,2%**; liệt cả 4 cách diễn giải, **đúng 1 trúng** ⇒ **không trùng hợp**.
|
||||
- **HELD** — *"banner đo thứ KHÁC"*: `harvest-curator/` chỉ có MEMORY.md + `archive/`; **không file nào và không tổ hợp nào** rơi vào [25.344, 25.446) B.
|
||||
- **HELD** — *"file đổi sau khi banner in"*: mtime **08-09 22:17**, spawn 08-10 13:22 ⇒ **đứng yên xuyên banner**.
|
||||
- **HELD** — *"eval bịa số"* / *"refine bịa 2.733 B"* / *"verdict tổng gộp che TRƯỢT lẻ"* — cả 3 bác được (liệt **9 TRƯỢT + 3 KC từng dòng**, không gộp).
|
||||
|
||||
## 🔴 `A2-BAY-NHAN` — **XÁC NHẬN byte-exact, bẫy nguy hiểm nhất phiên**
|
||||
`## S159` ở **dòng 52**, file hết **dòng 64**, **0 heading chen giữa** ⇒ span nuốt trọn **dòng 62 = S182 (961 B)** + **dòng 63 = S185 (1.770 B)**. `961+1+1.770+1 = 2.733 B` — **khớp refine TUYỆT ĐỐI**. mtime `ctx-audit/MEMORY.md` = **2026-08-10 04:04 = HÔM NAY**.
|
||||
⇒ Ai (người **hay máy**) drain *"entry S159 cũ từ 2026-07-29"* sẽ **archive MẤT bản ghi S182 + S185 của hôm nay**.
|
||||
🔴🔴 **Nguy hiểm GẤP ĐÔI:** `keep_floor` bảo *"giữ 5 entry mới nhất"* nhưng máy **chỉ THẤY 3** ⇒ **cơ chế an toàn theo tuổi bị VÔ HIỆU bởi chính lỗi cấu trúc này**.
|
||||
⇒ **A2(a) (gắn `##`) PHẢI làm TRƯỚC A2(b). Không được đảo.**
|
||||
|
||||
## `A1-MAT-0-TT` — **ĐẠT, nền chắc hơn refine trình**
|
||||
Control-âm `@S999-zzz` = **0/0** ✓ · `@S182|@S180` = **8 dòng** ✓ · `@S166|@S167|@S168` = **5 dòng**, refine khai **4** ⇒ **lệch 1 hướng BẢO THỦ** (khai ít hơn bằng chứng nó có). ⚠️ refine **không nêu pattern đã dùng** ⇒ probe **không tái lập được** = **khuyết tật thật**.
|
||||
🔑 **Chỗ dựa MẠNH HƠN mà refine bỏ lỡ:** **A7 gate máy đã resolve `reviewer` 57 pointer / 57 / 0 failed** — **chứng MÁY, không phải grep tay**.
|
||||
⚠️ Trình-tự `reviewer:96` (**append archive TRƯỚC → probe token lấy từ THÂN BÀI → mới cắt**) là **BẮT BUỘC** — tiền lệ cut-not-moved **×5**.
|
||||
|
||||
## `TU-BAC-THAT-HAY-DIEN` — **THẬT, 4 dấu hiệu không diễn được**
|
||||
1. Nó **VỨT urgency** (*"vai đang bị cắt"* — thứ duy nhất làm action nghe kêu) và giữ lý do **KHÔ HƠN**; **diễn thì làm ngược**.
|
||||
2. Số tự-bác **kiểm được và đúng** (23.055 < 24.986 ✓ audit tái dựng).
|
||||
3. Nó tự-bác bằng **CHÍNH cái thước vừa dùng để lật con-ĐO** ⇒ **áp luật lên mình cùng lượt**, không phải luật một chiều.
|
||||
4. Lý do thay vào **ĐẮT HƠN để dựng** (phải đọc source `memory-archive-gate.ps1:143` + đếm byte trên heading) — **diễn thì không ai chọn đường tốn hơn**.
|
||||
⚠️ 1 vết: không hạ nhãn ưu-tiên A2 sau khi urgency chết — nhưng trigger mới **tự đứng được**, không đổi verdict.
|
||||
|
||||
## KC (3)
|
||||
hook cắt là code **NGOÀI repo** ⇒ không đọc được nguồn chân lý · crystallized/hotload **nhường `lead-gap-auditor`** theo N1 · `mfe-eval.ps1` **không re-chạy** lượt này (**KHÔNG suy ĐẠT**).
|
||||
|
||||
---
|
||||
|
||||
# 🔬 LEAD chạy tiếp `GIT-RECONSTRUCTION` (audit để KC + khuyến nghị chạy ngay)
|
||||
|
||||
**Kết quả: đường audit đề xuất ĐÓNG VỀ CẤU TRÚC — nhưng phân xử được bằng đường KHÁC.**
|
||||
|
||||
`git show d8c107c8~1:` → **26.803 B / 24.265 ch**: byte/1024 = 26,17 · byte/1000 = 26,80 · char/1024 = 23,70 · char/1000 = 24,27 ⇒ **KHÔNG cách nào trúng cửa sổ 24.9**.
|
||||
|
||||
Truy lịch sử size (12 commit) giải thích vì sao:
|
||||
| commit | ngày | byte | char |
|
||||
|---|---|---|---|
|
||||
| `9c3b87d3` | 08-05 22:16 | **26.803** | **24.265** |
|
||||
| `d8c107c8` | 08-07 13:57 | **12.131** | 11.210 |
|
||||
⇒ rewrite xảy ra ở `d8c107c8` (khớp *"rewrite <17.1KB"*), và **giữa 2 commit KHÔNG có commit nào khác chạm file** ⇒ **đỉnh thật lúc banner nổ KHÔNG BAO GIỜ được commit** (bị viết đè trước turn-boundary kế).
|
||||
🔴 ⇒ *"phép rẻ nhất còn lại"* **đóng về CẤU TRÚC**, không phải xui một lần: **trạng thái kích hoạt banner CHÍNH LÀ trạng thái bị rewrite trước khi có commit.**
|
||||
|
||||
**Nhưng lập luận ĐƠN-ĐIỆU phân xử được:** giữa 2 commit **không có lần curate nào** (curate = chính cú rewrite) ⇒ lúc banner nổ **byte ≥ 26.803**. Banner khai **24.9 KB**:
|
||||
|
||||
| giả thuyết | 24.9 KB quy ra | file lúc đó | verdict |
|
||||
|---|---|---|---|
|
||||
| **BYTE** | 25.498 B (/1024) · 24.900 B (/1000) | **≥ 26.803 B** | 🔴 **MÂU THUẪN — BÁC** |
|
||||
| **KÝ TỰ** | 25.498 ch · 24.900 ch | 24.265 ch, cần mọc **+635…+1.233** | ✅ **NHẤT QUÁN** |
|
||||
|
||||
⇒ **Giả thuyết BYTE bị BÁC ở nhân-chứng thứ 3; KÝ-TỰ đứng.** Cộng 4 đường độc lập: cửa-sổ-render (harvest) · `reviewer:97` · vi-sai-biệt (audit) · đơn-điệu-git (lead).
|
||||
|
||||
🟡 **Giả định phải khai:** lập luận dựa vào *"file chỉ mọc giữa 2 lần curate"*. Có lần co không để lại commit thì hở. **KHÔNG nâng thành "đã chứng minh"** — **E1 vẫn cần** để hết cận-dưới.
|
||||
@ -0,0 +1,64 @@
|
||||
# harness-eval — trio nấc 1/3 (con ĐO) — S186, giữa 2 bookend
|
||||
|
||||
> Lượt-trả-1 dính **#53** (22 tool-call). Recover bằng resume. Dưới = lượt-trả-2.
|
||||
|
||||
**VERDICT: `HARNESS-EVAL: MIXED`**
|
||||
|
||||
## Trục đo
|
||||
|
||||
| trục | số tươi | baseline | delta | verdict |
|
||||
|---|---|---|---|---|
|
||||
| l1_hot > `autoinject_cap` 25.600 | **3/29**: `reviewer` 31.341 · `harvest-curator` 28.071 · `ctx-audit` 26.695 | `measured` 19 row (mốc S107/S138/S141) | reviewer **+56,2%** vs 20.071 · harvest **+55,2%** vs 18.082 · **ctx-audit KHÔNG CÓ ROW** | **REGRESSION** |
|
||||
| l1_hot > `soft_cap` 30.720 | **1/29** = `reviewer` 31.341 | — | — | **REGRESSION** |
|
||||
| l1_hot cận-cap 97-100% | 4 vai: `lead-gap` 25.454 · `impl-be` 25.280 · `ring1` 25.261 · `ring2` 24.859 | — | cách cap **146-741 B** | MIXED (ranh sạch nhưng **mỏng**) |
|
||||
| roster | **29** | S185 diary 28 | +1 | INFORM (mẫu-số đổi ⇒ chỉ so số TUYỆT ĐỐI) |
|
||||
| sum l1 toàn roster | **450.467 B** | — | — | INFORM |
|
||||
| archive-gate A6 strike | 3 vai over-cap đều **strike=1** (DRY, cần 2 để PROPOSE); persisted `.archive-strikes.json` = `ring2-audit` 1 · `investigator-codebase` 1 · 26 row = 0 | S185 A7 429/429 | 🔴 **strike ĐẢO CHIỀU** | **REGRESSION** |
|
||||
| archive-gate khả-năng tự-vá | **3/3 over-cap KHÔNG auto-drain được**: `ctx-audit` keep-floor hit (5) ⇒ giảm **0 B** · `harvest-curator` value-floor ⇒ after-est 27.224 **VẪN over** · `reviewer` value-floor ⇒ after-est 30.982 **VẪN over CẢ 2 cap** | — | — | **REGRESSION** |
|
||||
| A7 archive-integrity | **PASS** — 429 pointer / 429 resolved / 0 failed, 10 vai có `_INDEX` | S185 429/429 | 0 | HEALTHY |
|
||||
| crystallized headroom | **131.496** tok (hotload 745.511 B · RANGE [186.378..248.504] · cap 380.000) | S185 **134.573** (đọc TỪ ĐĨA, không từ trí-nhớ) | **−3.077** | **REGRESSION** |
|
||||
| crystallized vs target owner | headroom **131.496 < target 210.000** ⇒ `expected_backfill = 131.496` *"headroom binds"* | 210.000 | **−78.504** | **REGRESSION** |
|
||||
| MFE FIT | `FIT = PASS` span 30.945 B ~7.736-10.315 tok / cap 380.000 = **2,71%** worst-case | — | — | HEALTHY |
|
||||
| MFE denominator | 38 items (marks 11 + AS 15 + net-guards 5 + net-recurring 7) | — | — | INFORM |
|
||||
| MFE Goodhart anchor | `strikes_total=25 RCA=16 AS-classes=15`; warn-baseline 25 = last_run 25 | 25 | **0** | HEALTHY (không tăng) |
|
||||
| MFE per-role coverage | 23 vai có số: **21 vai 100%** · `impl-be` 17/18=94% · `impl-fe` 12/13=92% | — | — | MIXED |
|
||||
| **MFE mù 6 vai** | `back-end/database/front-end-reviewer-style` · `ring5-audit` · `score-counter` · `score-count-auditor` = **KHÔNG đo được** (role-file 0 block trích được) — 🔴 **KHÔNG phải "sạch"** | — | — | MIXED |
|
||||
| NHIP-NO | `light 0/6 deep 0/1 ok \| tran-bo-nho 3 \| l2idx 0 \| run-chua-gom 1 \| pull-cach 0 \| so-yc treo 1 / nhac2+ 2` exit 0 | lead in `pull-cach 5` | 5→0 | HEALTHY |
|
||||
| l2_index `cicd-monitor` | MAIN `_INDEX.md` **9.682 B / cap 20.480 = 47,3%**; rotated `_INDEX-2026-H1.md` 13.906; TỔNG 23.588 = 115,2% | `split_rule_S167` | — | HEALTHY theo luật — 🔴 probe đo MAIN **by design**, **CẤM đọc 115,2% thành regression** |
|
||||
| độ-giữ diary S186 | **0/29** diary chứa "S186"; control-dương "S1" = **29/29** ⇒ glob SỐNG, corpus không rỗng; "S185" = 16/29 | — | — | MIXED |
|
||||
| độ-giữ đo bằng **mtime** | **5/6 vai spawn phiên này CÓ ghi hôm nay** (lead-gap 04:04 · ring1 04:04 · ring2 02:47 · lead-stale 02:32 · tooling 01:54); `harvest-curator` mtime **2026-08-09 22:17 = DUY NHẤT không ghi** | — | — | MIXED — 🔑 **0/29 là gap NHÃN, KHÔNG phải gap GHI** |
|
||||
|
||||
## SIGNAL (cho con ĐỀ-XUẤT)
|
||||
- `reviewer` 31.341 vượt **CẢ 2 trần**; planner after-est ~30.982 ⇒ **máy tự-vá không kéo nổi xuống dưới 25.600, kể cả 30.720**.
|
||||
- `ctx-audit` 26.695 chỉ có **3 entry** mà `keep-floor = 5` ⇒ **pool drain RỖNG theo CẤU TRÚC**, after-est = đúng số hiện tại.
|
||||
- 🔴 **Strike ledger trỏ SAI vai:** 2 strike nằm ở `ring2-audit` + `investigator-codebase` (**cả hai DƯỚI cap**), 3 vai over-cap thật **đều 0** ⇒ **ngưỡng A6 "2 lần liên tiếp" chưa vai over-cap nào chạm tới**. `.archive-strikes.json` mtime 2026-08-07 (chỉ đổi khi chạy `-Apply`).
|
||||
- 4 vai nằm trong **146-741 B cuối** trước cap ⇒ **một entry diary nữa là qua vạch**.
|
||||
- headroom xuống **4 kỳ liên tiếp có số trên đĩa**; bước này −3.077. Nhánh sinh ra: hotload 745.511 B, riêng **`docs/gotchas.md` 122.892** + **`agents/README.md` 61.912**.
|
||||
- 6 vai MFE không đo được ⇒ **coverage 100% của 21 vai kia đang che một tập KHÔNG CÓ THƯỚC**.
|
||||
- 2 vai duy nhất dưới 100% MFE: `impl-be` 17/18 · `impl-fe` 12/13.
|
||||
|
||||
## 🔴 `MEASURED-LECH` — thước đang nói dối về đầu vào
|
||||
|
||||
- Khối `measured` có **19 row / 29 vai** ⇒ **10 vai KHÔNG CÓ ROW NÀO** (`back-end-reviewer-style`, `ctx-audit`, `ctx-curator`, `ctx-verifier`, `database-reviewer-style`, `front-end-reviewer-style`, `ring1-audit`, `ring2-audit`, `ring4-audit`, `ring5-audit`) — với 10 vai này **"delta 0" là VÔ NGHĨA, chưa từng đo**.
|
||||
- Trong 19 row có mặt: **18 lệch đĩa, 1 đúng** — row DUY NHẤT khớp = `office-document` 3.323 = 3.323 (**vai không chạy**).
|
||||
- Tệ nhất theo tỉ số: **`harness-audit` 1.513 → 18.001 = 11,9× (+1.089,8%)** · `harness-eval` 10,8× · `harness-refine` 9,8× · `lead-stale-auditor` 6,9× · **`lead-gap-auditor` 3.691 → 25.454 = 6,9× (khớp ĐỘC LẬP số chính vai đó tố)** · `test-specialist` 2,1× · `reviewer` +56,2% · `harvest-curator` +55,2%.
|
||||
- 2 row seed ghi **0** (`score-counter`, `score-count-auditor`) nay có byte thật 2.366/2.330. 2 row lệch **chiều ÂM**: `frontend-designer` −7,6%, `tooling-auditor` −11,1%.
|
||||
- Lời hứa *"full re-sync bằng script @monthly drift-audit 2026-08-01"* ghi trong `measured._note` ⇒ **quá hạn 9 ngày**.
|
||||
|
||||
## 🔴🔴 `OVERCAP-HARM` — **NGƯỠNG CẮT THẬT THẤP HƠN MỌI GOVERNOR**
|
||||
|
||||
Ngưỡng cắt THẬT do `harvest-curator` **tự khai** = **24,4 KB ≈ 24.986 B** (banner: *"24.8KB > limit 24.4KB, only part loaded"*) — **THẤP HƠN CẢ `autoinject_cap_bytes` 25.600 LẪN `soft_cap_bytes` 30.720**.
|
||||
|
||||
Chiếu ngưỡng đo-được đó lên số máy vừa in:
|
||||
- `reviewer` 31.341 = **125,4%** ngưỡng · `ctx-audit` 26.695 = **106,8%** ⇒ **cùng nguy cơ cắt**, reviewer nặng nhất.
|
||||
- 🔴 **Nghiêm trọng hơn:** ngưỡng thật kéo thêm **4 vai đang được CẢ HAI governor báo "ok"** vào vùng rủi ro — `lead-gap` 25.454 · `impl-be` 25.280 · `ring1` 25.261 · `ring2` 24.859 **đều > ~24.986**.
|
||||
- ⇒ **7 vai / 29 nằm TRÊN ngưỡng cắt thật, trong khi mọi thước hiện có chỉ báo 3.**
|
||||
|
||||
🟡 **Caveat trung thực của vai:** 24,4 KB là con số **vai TỰ KHAI**, chưa có instrument độc lập tái-lập; **không script nào trong 5 instrument đọc ngưỡng này**.
|
||||
|
||||
## `LECH-VS-LEAD` — **1 lệch, và vai tự chẩn đúng nguyên nhân**
|
||||
`pull-cach`: lead in `5 ngay`, máy vai in `0 ngay`. **Không phải ai sai** — 4 dòng INBOUND đề 2026-08-10 được thêm vào `_index.md` **trong CHÍNH phiên này** ⇒ **số trôi giữa phiên**, đúng lớp S159/S173.
|
||||
7 trục còn lại khớp **byte**: light 0/6 · deep 0/1 · tran-bo-nho 3 · l2idx 0 · run-chua-gom 1 · so-yc treo 1/nhac2+ 2 · crystallized (cap 380.000 / hotload 745.511 / RANGE / headroom 131.496 / target 210.000). 3 số byte-exact vai over-cap khớp **tuyệt đối** với `ring1-audit`; `ring2` 24.859 DƯỚI cap ⇒ **tiền-đề cũ "ring2 over-cap" đã chết** (nguồn thứ 3 xác nhận).
|
||||
|
||||
## Containment — vai KHAI THẲNG thay vì im
|
||||
`mfe-eval.ps1` **GHI đúng 1 file** `.claude/agent-memory/.mfe-state.json` theo `read_only_scope` của chính nó — vai chạy **có chủ đích** (2 kỳ trước khai N/A **bị chứng là sai loại**) và **khai ở đây thay vì im lặng bỏ**; file đã có mtime 2026-08-10 12:58 **TRƯỚC** lượt vai. **KC:** `distill-shard-probe` + `memory-selfimprove-audit` (không nằm trong 5 instrument được giao).
|
||||
@ -0,0 +1,93 @@
|
||||
# harness-refine — trio nấc 2/3 (con ĐỀ-XUẤT) — S186
|
||||
|
||||
> 🟢 **Lượt-trả-1 SẠCH, không #53** — vai đầu tiên trong phiên (8 vai) trả trọn ngay lần đầu.
|
||||
|
||||
**VERDICT: `HARNESS-REFINE: 4 action / 16 BÁC`** (0① / 1② / 2③ / 1④) — propose-only, single-writer = lead
|
||||
|
||||
## 🔴🔴 ĐÍNH-CHÍNH CON-ĐO — lật headline `OVERCAP-HARM`
|
||||
|
||||
**Đúng CHIỀU, sai ĐƠN-VỊ và sai DÂN-SỐ.** Governor đo **BYTE**; hook cắt đếm **KÝ-TỰ**. Tỉ số byte/char corpus VN đo tươi = **1,107–1,16** (phụ thuộc nội dung).
|
||||
|
||||
**Bằng chứng SỐ HỌC, không phải suy luận:**
|
||||
- `harvest-curator` banner tự khai **"24.8KB"** → char-count = **25.348 = 24,75 KB ⇒ in ra 24.8 KB, KHỚP 1 chữ số thập phân**.
|
||||
- byte-count **28.071 = 27,4 KB ⇒ KHÔNG khớp**.
|
||||
- Cộng thêm: `reviewer/MEMORY.md:98` **đã tự học bài này từ S182** (*"hook đếm KÝ TỰ còn `wc -c` đếm BYTE"*).
|
||||
|
||||
⇒ **Ngưỡng cắt = 24.986 KÝ-TỰ.**
|
||||
|
||||
**Chiếu lại — 2 vai / 29 bị cắt thật, KHÔNG phải 7, cũng không phải 3:**
|
||||
| vai | ký-tự | % ngưỡng |
|
||||
|---|---|---|
|
||||
| `reviewer` | **27.013** | **108,1%** |
|
||||
| `harvest-curator` | **25.348** | **101,4%** |
|
||||
| ctx-audit | 23.055 | dưới |
|
||||
| lead-gap | 22.226 | dưới |
|
||||
| impl-be | 22.633 | dưới |
|
||||
| ring1 | 21.865 | dưới |
|
||||
| ring2 | 21.568 | dưới |
|
||||
> Kết luận **bền với cả hai quy-ước KB** (24,4×1024 hay ×1000 đều ra 2 vai).
|
||||
|
||||
🔴 **Cái KHÔNG mất đi:** `autoinject_cap` 25.600 B = **AN TOÀN** (chặt hơn hook), nhưng **`soft_cap_bytes` 30.720 B ≈ 26.500 ký-tự = governor NGUY-HIỂM** (nằm **TRÊN** đường cắt). Và vì tỉ số **đổi theo nội dung**, **KHÔNG byte-cap cố-định nào đúng được** — đó mới là lý do phải có phép đo riêng.
|
||||
📊 Grep toàn repo: `24.4KB` chỉ sống trong **4 diary / 3 vai**, **0 script · 0 khoá config** (control-dương `autoinject_cap_bytes` = 5 file / 4 script ⇒ grep SỐNG).
|
||||
|
||||
## 4 Action
|
||||
|
||||
**`[④] agent-memory/reviewer/MEMORY.md`** dòng 17·19·25·27·31·110 — 6 stub `[→ archive/…]` = **7.505 B** → nén còn ~720 B
|
||||
- **trigger:** 27.013 ký-tự = **108,1% ngưỡng cắt thật** + 31.341 B vượt **CẢ 2** byte-cap + after-est máy 30.982 (**vẫn over ⇒ máy bó tay**)
|
||||
- **chốt-dừng:** attempt-1. Khai lineage: S173-X5 BÁC *"86,1% trình"*, S185-#2 BÁC *"vừa harvest 15' trước"* — 🔴 **cả 2 lý do NAY VÔ HIỆU**, file đã tăng 20.292 → 31.341. Còn >24.986 ký-tự sau khi làm → **leo-thang, CẤM lần 2**
|
||||
- **Kết quả:** **24.556 B / ~21.163 ký-tự — dưới cả 3 ngưỡng**. **Mất 0 thông tin**: verbatim đã ở archive, xác minh **8 hit `@S182|@S180`** + **4 hit `@S166/@S167/@S168`**, **control-âm `@S999-zzz` = 0/0**. 5/6 stub 0 token `value_protect`; **dòng 17 chứa `[guard]` ⇒ GIỮ NGUYÊN mệnh-đề guard**.
|
||||
- 🔸 Chính `reviewer:98` **đã tự đăng-ký việc này** và **2 phiên không ai làm**.
|
||||
- ⚠️ A4-hysteresis đòi <21.760 B — A1 **chưa tới** (24.556); muốn đủ thì cắt thêm 3 bullet S180-lane chưa archive (2.979 B) → 21.577 B.
|
||||
- ⚠️ **Bắt buộc theo trình-tự `reviewer:96`:** append archive **TRƯỚC** → probe moved-not-cut bằng token lấy từ **THÂN BÀI** (không lấy từ digest) + control-âm → **mới cắt**. *(Sự-cố cut-not-moved ×5, `f229b07`.)*
|
||||
|
||||
**`[③+④] agent-memory/ctx-audit/MEMORY.md`** — (a) gắn `##` cho 6 bullet mồ-côi (dòng 3·5·7·9·10·11) · (b) move `S153` + span `## S154` + `## S158` = **7.138 B** → tạo `ctx-audit/archive/2026-07.md` + `_INDEX.md`
|
||||
- **trigger:** 26.695 B = 104,3% autoinject_cap + 🔴 **16.007 B = 60,0% file nằm TRÊN heading `##` đầu ⇒ VÔ HÌNH với parser** (`memory-archive-gate.ps1:143` — *"LOGICAL entries = HEADING markers ONLY"*) ⇒ máy đếm **3 entry**, `keep_floor` **5 > 3** ⇒ **drain 0 B theo CẤU-TRÚC, không phải theo GIÁ-TRỊ**
|
||||
- **chốt-dừng:** attempt-1 (S185-#7 vai tự hoãn *"ghi tên vòng sau"* — đây là vòng sau)
|
||||
- **Kết quả:** 19.557 B < low-water 21.760 ✓ A4
|
||||
- 🔴🔴 **Phần an-toàn quan trọng nhất:** **2.733 B = S182 + S185 (2 bản ghi MỚI NHẤT, một viết HÔM NAY 04:04) đang nằm LỌT trong span nhãn `## S159`** ⇒ **bất kỳ ai drain theo nhãn sẽ archive MẤT bản ghi hôm nay**. Đúng luật vai đã promote @S185, **nổ lần thứ 2**.
|
||||
- 🔑 Sau (a), entry **3→9**, `keep_floor` hết chặn ⇒ **máy tự đề-xuất được từ vòng sau** — **mở đường tới cửa cho guard**, thay vì cắt tay mãi.
|
||||
|
||||
**`[③] agent-memory/harvest-curator/MEMORY.md`** — rút bản ghi cũ nhất, cần ≥**362 ký-tự**, lấy ≥**1.000** để có biên
|
||||
- **trigger:** **25.348 ký-tự = 101,4% ngưỡng — vai TỰ KHAI đang bị cắt**, khớp số học với char-count; after-est máy 27.224 **vẫn over**
|
||||
- **chốt-dừng:** attempt-1 (S185-#4 BÁC *"sổ của vai vừa harvest"* — nay vai **KHÔNG ghi hôm nay**, mtime 08-09 22:17 ⇒ **lý do hết hiệu-lực**)
|
||||
- Hạ-tầng sẵn: `archive/2026-06|07.md` + `_INDEX.md` 8.325 B. Vai tự đặt self-target **≤17,1 KB** (`:15`) ⇒ đang gấp **~1,5×**
|
||||
|
||||
**`[②] agent-memory/harness-refine/MEMORY.md`** — promote **LUẬT ĐƠN-VỊ**:
|
||||
> *"Ngưỡng cắt thật đếm KÝ-TỰ, mọi governor/instrument đếm BYTE; tỉ số 1,107–1,16 đổi theo nội-dung ⇒ KHÔNG byte-cap cố-định nào đúng. Trước khi neo action vào 'over-cap', ĐỔI SANG KÝ-TỰ. `soft_cap_bytes` 30.720 nằm TRÊN đường cắt."*
|
||||
- **trigger:** lặp ≥2 độc-lập (`reviewer:98` @S182 · `investigator-codebase` archive-header @S180) **+ vừa đẻ lỗi THẬT: con-ĐO báo 7 vai, thật 2**
|
||||
- **chốt-dừng:** attempt-1; vòng sau còn ai neo bằng byte → leo-thang (**luật-vào-diary-không-đổi-hành-vi = build-gap**)
|
||||
|
||||
## ① reinject = **0 — CÓ LÝ DO 3 LỚP** (không phải bỏ trống)
|
||||
1. `S186` 0/29 là gap **NHÃN** không phải gap **GHI** (5/6 vai spawn có mtime hôm nay) ⇒ **0 floor-rot**.
|
||||
2. 2 mục MFE MISS đã được ledger S185 phân loại **B3 = BUILD-GAP**; điều-kiện lật (*"vai thực sự từ chối 1 task bằng đúng tiêu-chí"*) **chưa nổ**.
|
||||
3. 6 vai MFE mù **không có thước** ⇒ không dựng được vế *"đã-từng-có ∩ RỚT"* của phép thử B3 ⇒ reinject ở đó = **BỊA**.
|
||||
> Ledger CG-1 còn **0 dòng** ⇒ **0 quota bị tiêu**.
|
||||
|
||||
## BÁC (16) — trích các mục load-bearing
|
||||
|
||||
1. 🔴 **TỰ BÁC NHÁP CỦA CHÍNH MÌNH:** đã dựng xong claim *"ctx-audit đang bị cắt mất bản ghi S185 viết hôm nay"* — mở char-count: **23.055 < 24.986 ⇒ KHÔNG bị cắt**. **Giữ A2 nhưng THAY HẲN LÝ DO** (mù-parser + bẫy nhãn-span), **vứt urgency giả**.
|
||||
2. Con-ĐO *"7 vai/29"* — BÁC, đơn-vị sai; đúng = **2**. Nhưng **KHÔNG rút về 0**: `soft_cap` 30.720 vẫn nằm trên đường cắt.
|
||||
3. `implementer-backend` — **S185-A1 ĐÃ LAND** (đề 431 B, thật **430 B**, lệch 1 B do `\n`) và **ĐẠT** trigger (25.710→25.280). 22.633 ký-tự ⇒ dưới đường cắt. **0 action, KHÔNG attempt-2**, chốt-dừng nguyên vẹn.
|
||||
4. Nhóm "146-741 B cuối" (`lead-gap` · `ring1` · `ring2`) — quy ra ký-tự **21.568-22.226, dưới đường cắt** ⇒ **cận-cap là chuyện của byte-governor, không phải thiệt-hại**.
|
||||
5. `cicd-monitor/_INDEX` 115,2% — **2 lý do độc lập**: con-ĐO tự cấm đọc thành regression + S153 đã **attempt-2 → escalated ⇒ attempt-3 CẤM**.
|
||||
7. Reinject 2 mục MFE MISS — ledger = build-gap, nhồi vào là **Goodhart xoá chứng-nhân**.
|
||||
10. Promote lại bài *"guard dựng mà không ai gọi"* — **đã có** trong diary + 2 file auto-memory ⇒ promote nữa = **nghi-thức rỗng**. 🔑 **Thuốc lần này là CẮM DÂY ĐO, không phải ghi nhớ thêm.**
|
||||
11. 7 file memory rời trong `reviewer/` (~12,5 KB) — **không tính vào l1_hot**; gộp vào MEMORY.md = **làm nặng thêm đúng thứ đang cắt**.
|
||||
12. `harness-audit` measured 11,9× — sửa tay bị **chính `_note` của file CẤM** ⇒ dồn vào E2.
|
||||
16. Chỉnh `soft_cap_bytes`/`autoinject_cap_bytes` cho khớp ngưỡng thật — **số governor = quyền OWNER**; và **nâng-ngưỡng-để-tắt-đèn = đúng bài Goodhart vai đã BÁC @S162**.
|
||||
|
||||
## ESCALATE (ngoài 4 lớp — lead/owner quyết)
|
||||
|
||||
- **E1 — dựng phép đo ngưỡng thật.** Probe phải đếm **KÝ-TỰ** (`len(read())`), **KHÔNG** `wc -c`; ngưỡng 24,4 KB; in **cả 2 cột byte/char + tỉ số per-role**. 🔴 **Điều-kiện lật giả-thuyết ký-tự:** nếu một vai **>25.000 ký-tự mà KHÔNG bị cắt** ⇒ **giả-thuyết chết, quay lại đo byte**. Hiện trạng: **0/5 instrument đọc ngưỡng này**.
|
||||
- **E2 — `measured` 19/29 row, 18/19 lệch, quá hạn 9 ngày.** File **tự cấm sửa tay** và **tự chỉ đường**: `scripts/measure-agent-memory.ps1`. Đây là **chạy instrument** = việc nấc-1/lead. 🔴 **Chừng nào chưa chạy, mọi cột `delta` — kể cả cột con-ĐO đưa — là SO VỚI SỐ BỊA.**
|
||||
- **E3 — A6 strike không bao giờ với tới cửa.** Cơ-chế thuộc lane `tooling-auditor`. Phần vai chạm được **đã gói trong A2(a)** — **sửa corpus cho máy NHÌN THẤY, thay vì sửa máy**.
|
||||
|
||||
## NHƯỜNG (turf sạch)
|
||||
- **N1 → `lead-gap-auditor`:** `docs/gotchas.md` 122.892 B · `agents/README.md` 61.912 B · hotload 745.511 B · headroom −3.077 · target 210.000 — **6/6 nguồn là hot-feed của LEAD, không phải agent-memory corpus**.
|
||||
- **N2 → `harness-eval`:** mọi phép đo lại (vai chỉ đo 7 vai vừa đủ để bác/đứng claim).
|
||||
- **N3 → `harness-audit`:** chấm 4 action + **cú lật đơn-vị này**.
|
||||
- **N4 → `tooling-auditor`:** strike-state, `.archive-strikes.json`, `-Apply`.
|
||||
- **N5 → ngoài corpus:** 6 vai MFE mù — object là `.claude/agents/*.md`, **không phải agent-memory corpus** ⇒ hình dạng giống ② nhưng **sai lớp đối-tượng** ⇒ không nhận.
|
||||
|
||||
## Caveat vai khai thẳng
|
||||
Có `Bash` = kênh-ghi vẫn mở, đã dùng **read-only** (0 Write/Edit/store_memory) — lead soát `git status`. Ngưỡng ký-tự là **đo GIÁN-TIẾP qua 2 nguồn độc lập** (banner tự khai + `reviewer:98`), **chưa có instrument** ⇒ **E1 là thứ biến nó thành số đo được**; đến lúc đó, **"2 vai bị cắt" là CẬN DƯỚI, không phải chốt**.
|
||||
69
.claude/workflows/runs/2026-08-10-S186-bookend-open/run.md
Normal file
69
.claude/workflows/runs/2026-08-10-S186-bookend-open/run.md
Normal file
@ -0,0 +1,69 @@
|
||||
# run — 2026-08-10 S186 bookend @open (ONE-SHOT bookstart)
|
||||
|
||||
- **session-label:** S186 · **phiên-LOGIC:** L14 (`.claude/sessions/session-14/`)
|
||||
- **HEAD @open:** `bc309c55` · counter **59 → 60**
|
||||
- **mode:** HMW-mode ON · bookend @open vô-điều-kiện (hình B) · **+ DEEP** (`deep_every=1`, owner YC-019(5) @S185)
|
||||
|
||||
> 🔄 **Bản này refresh @14:2x sau `ctx-audit` FLAG-3.** Bản trước đóng băng 13:35 (trước trio) nên stale 4 chỗ: V3 còn `[ ]` · ring2 ghi "(đang chạy)" dù xong 13:39 · Artifact 7/12 · headline `#53` khai "6/6" trong khi thật **8/9**. 🔑 Vai bắt đúng **chiều lệch NGƯỢC**: `_mind` ĐÚNG, **bản-đồ SAI** ⇒ áp máy móc *"tin sổ máy"* ở đây sẽ **giết bản ghi đúng** (cùng hình F-3 @S173).
|
||||
|
||||
## Máy chạy trước vai (10 lượt)
|
||||
| máy | kết quả |
|
||||
|---|---|
|
||||
| `session-scaffold.ps1 -New` | `session-14` (max 13), exit 0 |
|
||||
| `session-counter-tick.ps1 -Session S186` | clean, **59 → 60**, HEAD `bc309c5` |
|
||||
| `nhip-no-probe.ps1` **(sau tick)** | `light 1/6 · deep 1/1 **OVERDUE:deep** · tran-bo-nho 3 · run-chua-gom 1 · pull-cach 5 · so-yc treo 1 / nhac2+ 2` |
|
||||
| `distill-shard-probe.ps1` | `pending=6 / nguong=15 ⇒ **IM**` |
|
||||
| `governance-detectors.ps1` | **TOTAL 58** / INFORM-only 18 |
|
||||
| `memory-selfimprove-audit.ps1` | GAP duy nhất `C-spectrum:ctx-audit:MED` |
|
||||
| `crystallized-backfill.ps1` | cap 380K · hotload 745.511 B · headroom **131.496** · target 210K ⇒ headroom BÓ |
|
||||
| `dotnet test SolutionErp.slnx` | **668 PASS / 0 fail** (45 Domain + 623 Infra) |
|
||||
| Sàn-3 (`tiep.md §0`) | **SẠCH** — 5/5 tín-hiệu MẠNH im + ORPHAN-L 0 |
|
||||
| RAG `list_projects` | service UP · `solution_erp` **2450** chunk · `last_indexed_at` **2026-05-29** (STALE) |
|
||||
| `session_ctx.py mind-check --session 14` | **dat=11 TRUOT=0 exit 0** (sau 1 vòng sửa con-trỏ E) |
|
||||
| `h24-signal-write.ps1` ×2 | **light TRƯỚC deep** — 9 class FIRE · `gap-owner-specifics` reset 7→0 · `gap-underfill` CoCa chặn reset · `signal_last_kind=deep` |
|
||||
|
||||
🔴 **Lỗi lead tự ghi:** chạy `nhip-no-probe` **TRƯỚC** tick ⇒ đọc hàng cũ (`deep 0/1 ok`) ⇒ **suýt bỏ nấc deep**. Thứ tự đúng `session-start §2.1.8` = (a) TICK → (c) OVERDUE → (e) probe. `lead-gap-auditor` bắt (light FLAG-2), lead đo lại xác nhận, **chạy bù trong cùng cửa**. Nợ trả xong: `light_at=deep_at=60`.
|
||||
|
||||
## Stages — TẤT CẢ ĐÓNG
|
||||
- [x] **V1 tooling/harvest** — `tooling-auditor` `PASS_WITH_FLAGS 7` · `harvest-curator` `GATE-FAIL-6` → `ring1-audit` **21Đ·3T·4KC** + lượt nối H2 **28Đ·1T·4KC**
|
||||
- [x] **V2 soi-lead H24** — light: stale **6** · gap **6**; deep: stale **2** · gap **6** → `ring2-audit` **18Đ·0T·2KC**
|
||||
- [x] **V3 trio-memory** — `harness-eval` **MIXED** → `harness-refine` **4 action·16 BÁC** → `harness-audit` **34Đ·9T·3KC**
|
||||
- [x] **V4 sleep-check** — `pending=6/15 ⇒ IM` ⇒ **skip CÓ KHAI**
|
||||
- [x] **V5 floor/detector** — TOTAL 58 / INFORM 18
|
||||
- [x] **Ctx @open** — `_mind-s-14.md` block-0 + `mind-check` 11/0 + `ctx-audit` **TRUOT 5 FLAG**
|
||||
- [x] **tally H24** — ghi **light TRƯỚC deep** (vá lời hứa `bookend-close-synthesis.md:33`), SAU verdict ring2
|
||||
- [x] **BƯỚC 0.9** — biên nhận 4 thư, hash 4/4 byte-identical, `pull-cach 5 → 0`
|
||||
|
||||
## Artifact — 12/12
|
||||
| tệp | nội dung |
|
||||
|---|---|
|
||||
| `sub-tooling-auditor-open.md` | H1 — 7 finding + DIFF vs S185 + RING5-SYNC + SKILL-STALE-VERDICT |
|
||||
| `sub-harvest-curator-open.md` | H2 — GATE-FAIL-6 + 4 câu đích danh; vai tự khai **L1 bị cắt** lúc spawn |
|
||||
| `sub-lead-stale-open.md` | H24 stale **light** 6 FLAG + ROUTE-GAP + INFORM |
|
||||
| `sub-lead-stale-deep.md` | H24 stale **deep** 2 FLAG + VERIFIED-CLEAN (+ INFORM lane2 **đã bị bác, annotate**) |
|
||||
| `sub-lead-gap-open.md` | H24 gap **light** 6 FLAG + 3 câu đích danh |
|
||||
| `sub-lead-gap-deep.md` | H24 gap **deep** 6 FLAG + YC-002-NHA + DEEP-VS-LIGHT |
|
||||
| `sub-ring1-audit-open.md` | KIỂM tooling/harvest — nấc `verified-number / unverified-label` |
|
||||
| `sub-ring1-audit-h2.md` | KIỂM H2 (lượt nối) — H2 đứng vững toàn bộ; TRƯỢT duy nhất thuộc `lead-stale-deep` |
|
||||
| `sub-ring2-audit-open.md` | KIỂM H24 — 0 TRƯỢT + bắt 1 FLAG cả 2 vai H24 đều sót |
|
||||
| `harness-eval-return.md` | trio 1/3 — MIXED |
|
||||
| `harness-refine-return.md` | trio 2/3 — lật headline con-ĐO (đơn-vị BYTE ⟂ KÝ-TỰ) |
|
||||
| `harness-audit-return.md` | trio 3/3 — phân xử **KÝ-TỰ, 2 vai bị cắt** + lead chạy nốt git-reconstruction |
|
||||
| `sub-ctx-audit-open.md` | vai-3 vòng Ctx — TRUOT 5 FLAG + mồi-ngầm @close |
|
||||
|
||||
## 🔴 #53 — datum phiên này
|
||||
**8/9 vai dính garble ở lượt trả ĐẦU** (tooling 34 tool-call · harvest 31 · stale 29 · gap 28 · ring1 24 · ring2 28 · eval 22 · audit 31). **Ngoại lệ DUY NHẤT = `harness-refine`** (trả trọn ngay lần đầu). **100% recover bằng SendMessage resume + ép form nén, mất 0 byte.**
|
||||
- Mẩu garble **2 lần CHỨA FINDING**: `ring2` (*"Re-derivation is stronger than either vai claimed"*) · `harness-audit` (*"strict probe gave a false 0-hit (bold markers); lives at `:97` not `:98`"*).
|
||||
- `ring1` đo nấc tin cậy bản lượt-trả-2: **số khớp 3/3 tuyệt đối, NHÃN trôi** (`Puro` = tên ma; neo `README:2` thật `:3`) ⇒ **`verified-number / unverified-label`**.
|
||||
- `ctx-audit` xác nhận **§E của `_mind` KHÔNG dính lớp đó** — 12/12 verdict khớp byte-exact, 12/12 con-trỏ giải được.
|
||||
|
||||
## 🔑 6 thước hỏng — **CẢ 6 thuộc về NGƯỜI KIỂM**, claim gốc lần nào cũng đứng
|
||||
| thước | của ai |
|
||||
|---|---|
|
||||
| `grep -c Off` bắt "Off**ice**" | ring1 |
|
||||
| regex không nuốt `**` trong `Mig **71**` | ring1 |
|
||||
| `find -maxdepth 1` che synthesis lồng `harvest/` | ring1 |
|
||||
| `tail -1` gặp 4 dòng trắng đuôi | lead-stale-deep |
|
||||
| strict probe 0-hit giả vì **bold marker** | harness-audit |
|
||||
| `grep -c "treo-chờ-anh"` = 5, thật 4 (1 hit ở dòng LUẬT) | ctx-audit |
|
||||
+ **5 lần vai-kiểm tự phá cáo buộc của CHÍNH MÌNH** (ring2 ×3 · refine ×1 · audit ×2 — trong đó 1 phép tự khai INCONCLUSIVE và **từ chối tính vào chứng cứ**).
|
||||
@ -0,0 +1,56 @@
|
||||
# sub-ctx-audit — vai-3 vòng Ctx — bookend **@open** S186 / L14
|
||||
|
||||
> 🟢 **Trả trọn ngay lượt đầu — không #53** (vai thứ 2 trong phiên, sau `harness-refine`).
|
||||
> Chạy **fallback hồi-tố đầy đủ**: phiên **0 `/pause` · 0 `/tiep`** ⇒ `ctx-curator` + `ctx-verifier` chưa chạy lần nào, **không có vết vai-1/vai-2 để kiểm** ⇒ vai-3 tự làm trọn.
|
||||
|
||||
**VERDICT: `CTX-AUDIT: TRUOT — @open — 5 FLAG`** (1 HIGH · 2 MED · 1 LOW-MED · 1 LOW)
|
||||
|
||||
## FLAG-1 (HIGH) — trục LIỀN-MẠCH, **KHÔNG mất việc**
|
||||
**0/6 món `carry:` + `pending-anh:` của `.claude/sessions/session-13/_end` có mặt trong block-0 L14.** 0-hit toàn bộ (`rider-jump` · `W3b` · `LEAD-PHAI-NHAC-LAI` · `nhip-refresh` · `36,7` · `vrapp` · `(73)` · `(74)` · `day-go-that`); control-dương cùng lệnh = `ring2` 2 · `harvest-curator` 3 · `ctx-audit` 3 ⇒ **thước sống**.
|
||||
🟢 **Nhưng cả 6 đều CÓ NHÀ ở `docs/HANDOFF.md`** (vrapp 2 · restart CLI 13 · (73) 3 · (74) 2 · rider-jump 2 · W3b 4 · LEAD-PHAI-NHAC-LAI 1 · 36,7 1) ⇒ **không mất việc, mất LIỀN MẠCH**.
|
||||
🔴 **Defect thật:** §D `_mind-s-14.md` có **4** `treo-chờ-anh` và **KHÔNG cái nào** thuộc hàng đợi owner mà HANDOFF đang giữ (slot (70)…(76) sống) ⇒ **HAI TẬP RỜI NHAU**. §D nạp **100% từ cửa này, 0% kéo tới từ L13**.
|
||||
⇒ Carry `mind-D-khong-back-propagate` (vai lập @S172) **TÁI PHÁT LẦN 3**, dạng sắc nhất: ở block-0 phiên mới, **chiều ghi vẫn MỘT chiều** (lead → HANDOFF, không quay về §D). Lệch **theo hướng TRẤN-AN**, trên đúng bề mặt cửa-sổ mới đọc **trước** §E.
|
||||
|
||||
## FLAG-2 (MED)
|
||||
`(74) rotate mật-khẩu prod vrapp` — chính món vai báo **HIGH @S173** vì *"rời khỏi MỌI bề mặt sống"*. Bản vá **ĐÃ GIỮ** (nay có nhà HANDOFF + `_end` s13). Nhưng ở **biên phiên** nó lại rụng khỏi lớp mềm ⇒ **lớp mềm vẫn là bề mặt buông nó ĐẦU TIÊN**. MED không HIGH — có nhà thì không phải mất.
|
||||
|
||||
## 🔴 FLAG-3 (MED) — **lệch NGƯỢC: `_mind` ĐÚNG, bản-đồ SAI**
|
||||
`run.md` stale so `_mind`: V3 trio còn `[ ]` trong khi 3 return đã land (13:47 / 14:00 / 14:15) · ring2 ghi "(đang chạy)" nhưng xong 13:39 · bảng Artifact **7/12** · headline `#53` khai **"6/6 vai"** (đóng băng 13:35, **trước trio**) trong khi `_mind` §C ghi **8/9**.
|
||||
🔴 **Áp máy móc *"tin sổ máy / bản-đồ"* ở đây sẽ GIẾT BẢN GHI ĐÚNG** — cùng hình với F-3 @S173 (WAL sai, lớp mềm đúng). **Việc sửa thuộc `run.md`, không thuộc `_mind`.**
|
||||
> ✅ **Lead đã sửa `run.md` ngay trong cửa này** (refresh 12/12 artifact, 8/9 #53, stages đóng hết).
|
||||
|
||||
## FLAG-4 (LOW-MED) — §E thiếu 2 lane
|
||||
- **(a) 0 dòng máy** — `run.md` liệt **10 lượt máy**, trong khi `_mind-s-13.md:101` (MIND-4, **10 giờ trước**) **CÓ** dòng máy trong §E ⇒ **tiền lệ vừa gãy**.
|
||||
- **(b) 0 dòng cho `ctx-audit` đang chạy** — tiền lệ vá-S173 (*"§E có dòng cho vai ĐANG chạy"*) được chính `_mind-s-13.md:140` viện dẫn.
|
||||
- 🟡 **Khai thẳng:** theo **CHỮ** heading (*"spawn/engine-run"*) máy **không bắt buộc** máy-run phải vào §E ⇒ đây là lệch **schema ⟂ tập quán**, **không phải vi phạm chữ**.
|
||||
|
||||
## FLAG-5 (LOW) = **MỒI-NGẦM @close**
|
||||
`soft_cap_bytes` + `autoinject_cap` (§D, `{treo-chờ-anh}`, *"con số là quyền anh"*) = **0 nhà durable** — HANDOFF 0 / STATUS 0 / sổ-YC 0; control `ctx-audit`=11 chứng grep sống. **Một món chờ anh phán mà CHỈ sống trong lớp mềm.**
|
||||
|
||||
## ĐẠT (8)
|
||||
- 🟢 **block-0 có NỘI-DUNG THẬT** — vai **tự đối chứng 1 ý §D** (bẫy span `## S159`): đúng, **diary của chính nó** có heading `## S159` ở dòng 52 và **2 bản ghi mới nhất (S182, S185) nằm dưới span đó** ⇒ drain theo nhãn **sẽ xoá thật**.
|
||||
- 🟢 **Neo xuất-xứ ĐÚNG CẢ HAI TRƯỜNG — lần đầu trong lịch sử vai**: heading `14:01:11 @ f9297486` khớp `git log -1 --format=%cI f9297486` **từng giây**, và `f9297486` **là HEAD lúc ghi** (mtime 14:18:32, commit kế `9d3bd0c7` 14:19:32). *S169 + S172 đều sai cả hai trường ⇒ lớp này nay sạch.*
|
||||
- 🟢 **§E verdict 12/12 khớp BYTE-EXACT** dòng-1 artifact ⇒ **KHÔNG dính `verified-number / unverified-label`**; **12/12 con-trỏ giải được trên đĩa**.
|
||||
- 🟢 **Lời tự khai của lead KHỚP vật chứng:** `sub-lead-gap-open.md` FLAG-2 chở đủ 2 số probe, `run.md` ghi lại, và **nợ đã trả chứng BẰNG MÁY** (`deep_at_counter=60`, `signal_last_kind="deep"`) ⇒ đóng luôn ý `{mới-nêu}` của `_mind-s-13.md:94` **bằng vật, không bằng lời**.
|
||||
- 3 rào tự soi SẠCH (0 verbatim · 0 ts-key đầu dòng · control-dương 33 bullet dưới marker) · bất-biến chuỗi tự đếm ĐĨA **p=0 · |block|=1** khớp máy `[0,3]`; chưa có `_end` ⇒ cross-check bỏ qua **có khai** · §B ⟂ WAL đồng bộ · nhãn D **9/9** đủ.
|
||||
|
||||
## Tự bắt thước (vai khai)
|
||||
`grep -c "treo-chờ-anh"` ra **5**, thật **4** — 1 hit nằm ở **dòng LUẬT liệt kê tập nhãn**. Cùng lớp `## MIND-` nuốt schema-placeholder @S185. **Số dùng ở FLAG-1 là 4, đã trừ.**
|
||||
> 🔑 Đây là **thước hỏng thứ 6** của phiên, và **cả 6 đều thuộc về NGƯỜI KIỂM**.
|
||||
|
||||
## Máy — vai TỰ CHẠY LẠI, không tin số pin
|
||||
`python scripts/session_ctx.py mind-check --session 14` → `dat=11 TRUOT=0 co=0 exit 0`, **khớp pin của lead**. 🟢 **Lần đầu trong 4 lần số pin còn tươi** (S162/S169/S172 đều hết hạn).
|
||||
⇒ **Cả 5 FLAG nằm NGOÀI TẦM MÁY** — đúng chỗ vai này tồn tại.
|
||||
|
||||
## 🎣 MỒI-NGẦM @close (vai gieo, lead scribe trước cửa `/pause` đầu tiên)
|
||||
Ý §D `_mind-s-14.md` — *`soft_cap_bytes` là governor NGUY HIỂM (29/29 vai trên đường cắt) vs `autoinject_cap` chỉ biên 1,0%; con số là quyền anh* — `{treo-chờ-anh}`, **nhà DUY NHẤT = `_mind` §D** (0/0/0 trên HANDOFF/STATUS/sổ-YC).
|
||||
**@close vai chấm:** nó lên được **block mới / `_tiep` / WAL / slot HANDOFF**, hay **rơi im lặng**.
|
||||
🔑 **Chọn ý này vì nó NGƯỢC với mồi S185** (ring1-persona rơi vì *không ai có nghĩa vụ sửa*): món này **có người phán rõ ràng là anh** ⇒ nếu vẫn rơi thì hỏng nằm ở **ĐƯỜNG DẪN**, không ở chỗ thiếu chủ.
|
||||
|
||||
## `CARRY-L13`
|
||||
**6/6 món `carry:`+`pending-anh:` của `_end` s13 KHÔNG được mang sang block-0 L14** — `rider-jump-chua-co-chu (C-1 W3b)` · 3 món thiết-kế ctx (`nhịp-refresh-_mind` · `LEAD-PHAI-NHAC-LAI` 0-hit · trần đối-chứng 36,7%) · `day-go-that-lan-day-sach-toi` · `(73) restart CLI` · `(74) rotate vrapp`. Chỉ `over-cap-3-vai` sang được (§B, **đã cập nhật đúng thành 2 vai bị cắt THẬT**).
|
||||
⇒ **Cả 6 còn nhà ở HANDOFF ⇒ KHÔNG mất việc, mất liền-mạch** — và §D L14 giữ 4 `treo-chờ-anh` **hoàn toàn RỜI** khỏi hàng đợi owner (70)-(76).
|
||||
|
||||
## `E-DAY-DU`
|
||||
🟢 **ĐỦ trên trục spawn: 12/12 artifact · 9/9 vai · verdict khớp byte-exact · 12/12 con-trỏ giải được** — **không sót lượt nào**, kể cả lượt nối `ring1-audit` H2.
|
||||
🟡 **Thiếu 2 lane ngoài trục spawn:** 0 dòng cho **10 lượt máy** · 0 dòng cho **`ctx-audit` đang chạy**.
|
||||
@ -0,0 +1,34 @@
|
||||
# sub-harvest-curator (H2) — bookend @open S186 (2026-08-10)
|
||||
|
||||
> Lượt-trả-1 dính **#53** (31 tool-call; câu về = *"HEAD moved while I measured…"* — vai **nhận ra HEAD dời là ĐÚNG**, chỉ mất đường trả). Recover bằng **SendMessage resume**. Dưới = lượt-trả-2.
|
||||
> 🔴 **Vai tự khai L1 BỊ CẮT lúc spawn**: banner báo `24.8KB > limit 24.4KB, only part loaded` ⇒ nó chạy với bộ-nhớ thiếu — chính nó là 1 trong 3 vai over-cap.
|
||||
|
||||
**VERDICT: `HARVEST: GATE-FAIL-6`**
|
||||
|
||||
## Finding
|
||||
|
||||
| sev | trục | vật | delta |
|
||||
|---|---|---|---|
|
||||
| HIGH | Coverage | `runs/2026-08-09-S185-bookend-close/sub-reviewer-yc021.md` (**23.280 B**) | **TRỌN artifact chưa về `agent-memory/reviewer/`.** Verdict `PASS_WITH_FIXES 8MAJ/6m` (14 finding; **R6 = mục DUY NHẤT đề FAIL** vì 3 dòng owner-SỐ sai **trước khi trình anh**: F-11 key ma `archive_gate.soft_cap_bytes` · F-12 lệch 45% · F-13 `mind_ctx_kb` stale) + 4 phản-chứng tự-bác + **2 thước-hỏng-của-chính-nó** (`s.index(']')` cắt trúng `]` trong comment ⇒ VALID_ROLES **18** thay 29, hụt 38%; `grep -rn` không glob treo >120s). 🔴 **Vô hình 4 tầng:** 0 row `run.md` · 0 mention `bookend-close-synthesis.md` · 0 hit WAL · 0 hit reviewer-folder (kể cả `archive/` + 7 topic-file). **Chỉ 1 sổ giữ vết:** `.claude/sessions/session-13/_mind-s-13.md:99` |
|
||||
| HIGH | Coverage | `runs/2026-08-09-S185-bookend-close/sub-harvest-curator.md` (43.771 B) | 🔴 **ĐỪNG gom lại 43.771 B — sản-phẩm ĐÃ về nhà** (13 sổ @`93173099`). Thiếu đúng **self-record**: END-line `HARVEST-GOM: 11 vai/16 entry + 7 tệp + 7 lane / 698.109 B — GATE FAIL 2 trục…` không có trong `agent-memory/harvest-curator/`. **Điểm-mù tự-quy-chiếu S173 F-01 tái phát, lần này ở tầng LEAD** |
|
||||
| MED | Placement | `runs/2026-08-09-S185-bookend-close/run.md` | **Cơ-chế truy được:** seed on-behalf lấy danh-sách từ **BẢNG** `run.md` (không có row `reviewer`) chứ không từ **FOLDER** — đúng bệnh S173 đã kê thuốc mà **chưa bind**. **mtime-chain là chứng:** artifact 08-10 03:48 ⟂ `reviewer/MEMORY.md` chốt 08-09 22:16 và không bao giờ chạm lại, trong khi lead vẫn seed tiếp tới 04:04 ⇒ **KHÔNG phải hết giờ — là không có tên trong bảng** |
|
||||
| MED | Completeness | commit `93173099` vs 2 sổ | **Cùng 1 sự-kiện APPEND mang 3 tally khác nhau:** sub END-line "11 vai/16 entry" · synthesis "17 khối/11 sổ" · **git thật 13 `MEMORY.md` + 7 topic-file mới**. Tái-lập: `git show --name-only --format="" 93173099 -- .claude/agent-memory/ \| grep -c "MEMORY.md"` → **13**. Phân rã: 5 sổ nhận lane + 8 sổ nhận harvest. **Cùng hình-dạng ring1 đã bắt @S185 ("bảng tổng đếm hụt phần liệt-kê của mình") — lần 3** |
|
||||
| LOW | Corruption | `runs/…S185-bookend-close/sub-ring1-audit.md:150` | Census tươi **25 occurrence / 23 line / 12 file** trên 1.169 md (perl byte-mode `\xEF\xBF\xBD`, control dương=1 + control âm=0 chạy TRƯỚC). Baseline carry "24/11" **không khai đơn-vị** ⇒ nêu cả 3 số. Hit mới duy nhất = ring1 **trích** mangle làm bằng chứng ⇒ **use⟂mention, LÀNH** |
|
||||
| LOW | Placement | 7 lane `sub-opus-lane{1..7}` trong `runs/…S185-email-adap-pipeline/` | **7/7 ĐÃ placed** (verify từng file, không đo tên) — nhưng lane = worker ad-hoc "Opus 5 MAX", **không có nhà `agent-memory`** ⇒ ánh-xạ thuần phán-đoán lead, **không tái-lập được kỳ sau**. Đề: ghi luật ánh-xạ lane→sổ |
|
||||
|
||||
## 4 câu đích danh
|
||||
|
||||
**`ORPHAN-DOC-LAP:` 0 orphan — KHỚP lead, nhưng mẫu-số KHÁC nên khai rõ.** Universe **93 dir**, **90 có `run.md`**, 3 loại trừ **có tên** (`S117-pe-approve-minmax-range`, `S117-pe-luyke-finalize` spec-only + `S144-bootstrap` có `bootstrap-synthesis.md`). Lưới `find "$d" -name "*synthesis*.md" -size +0c` bắt đúng 1 candidate = **`2026-08-10-S186-bookend-open/` — ĐANG CHẠY** (chính phiên này), không tính orphan nhưng ghi nhận theo anti-pattern #3. **0 lệch folder nào.**
|
||||
|
||||
**`S185-2-FOLDER:` CÓ 2 delta chưa về.** `bookend-close` **thủng 2** (reviewer-yc021 trọn 23.280 B + self-record harvest-curator); `email-adap-pipeline` **SẠCH 12/12**. 🔴 **Nợ THẬT chưa-APPEND = 23.280 B + 1 self-record, KHÔNG phải 67.051 B** — kể kiểu tổng là **đẩy người sau đi gom thứ đã ở nhà** (bài S180 F-04).
|
||||
|
||||
**`OVER-CAP:` BÁC premise của brief, XÁC NHẬN đo của `lead-gap-auditor`.** `ring2-audit` = **24.859 B DƯỚI cap**, nén tại `c2f45231` 2026-08-10 02:49, disk==HEAD==24.859, archive `ring2-audit/archive/2026-08.md` 8.771 B tồn ⇒ **nợ trong brief đã trả ~10h trước phiên này**. 3 vai over-cap THẬT (>25.600):
|
||||
- **`reviewer` 31.341** (+5.741, **vượt CẢ soft-L1 30.720** — nặng nhất, vừa nhận 7 topic-file)
|
||||
- **`harvest-curator` 28.071** (+2.471 — **chính vai này**; chứng ngôi-thứ-nhất: banner spawn báo L1 chỉ nạp một phần)
|
||||
- **`ctx-audit` 26.695** (+1.095)
|
||||
🔴 Danh-sách trong synthesis S185 `{ring2, reviewer, tooling}` nay **sai 2/3**: `tooling-auditor` = **15.039 B** (tự-compact 31.696→14.852 @S185).
|
||||
|
||||
**`ZERO-BYTE:` 0 ca thật.** `find .claude -type f -size 0` = 10 file, **TẤT CẢ `.gitkeep`** trong 5 run-folder cũ 06-18, khớp baseline S153. 0 `MEMORY.md` 0-byte, 0 synthesis 0-byte. Kèm: `find .claude -type d -empty` = **0** ⇒ lớp sandbox-rỗng (S169) **không tái phát**; git mù chỗ này nên phải `find` mới thấy.
|
||||
|
||||
## Containment (G-015, khai đúng giới hạn)
|
||||
0 Write/Edit trong repo · 0 `store_memory` · 2 file control chỉ ở `/tmp` ngoài repo. Dirty duy nhất `.session-counter.json` = **BOM-strip của hook**, không phải vai. Vai giữ `Bash` ⇒ **KHÔNG tuyên "read-only enforced"**. Mẫu-số: HEAD `bc309c55`; HEAD dời sang `69f9db80` giữa lúc đo nhưng `git diff --stat` chỉ đụng `S186-bookend-open/run.md` (+13 dòng) ⇒ **mọi số còn hiệu lực**.
|
||||
@ -0,0 +1,39 @@
|
||||
# sub-lead-gap-auditor — bookend @open S186 — nấc **DEEP** (trả nợ `OVERDUE:deep`)
|
||||
|
||||
> Chạy vì `deep_every=1` (owner YC-019(5) @S185) và `counter=60 − deep_at=59 = 1 ≥ 1`. Lead xếp thiếu ở lượt đầu, vai chính nó bắt ra (light FLAG-1/FLAG-2), rồi chạy bù trong cùng cửa.
|
||||
|
||||
**VERDICT: `LEAD-GAP-DEEP: 6 FLAG`**
|
||||
|
||||
## FLAG
|
||||
|
||||
| # | sev | class | thiếu gì | nguồn |
|
||||
|---|---|---|---|---|
|
||||
| 1 | HIGH | `gap-carry-dropped` | 🔴 **`migration-todos.md` ĐÓNG BĂNG @S168.** grep S169/S172/S173/S179/S180/S181/S182/S183/S185/S186 = **0/0/0/0/0/0/0/0/0/0**. ~17 nhãn phiên việc **ĐÃ SHIP** (KHKK 8-panel, guard tay-cầm-phiếu, YC-013→022, 4 wave) **chưa bao giờ VÀO tracker**, chưa 1 ô nào được tick. **Nặng thêm:** header tự trỏ mạch sống về `.claude/WAL.md` — mà WAL **reset TRẮNG @S185** ⇒ **con-trỏ CHẾT ở CẢ 2 ĐẦU**. File này là **#4/5 file đọc-trước-tiên** (CLAUDE.md) + **1/9 nguồn `tiep_reload`** | `migration-todos.md:4` vs `HANDOFF:5` (S182→S185) vs `WAL.md` (rỗng 182 B) |
|
||||
| 2 | HIGH | `gap-decision-sunk` | Lời hứa *"full re-sync bằng script @monthly drift-audit 2026-08-01"* nằm **5 bản sao** trong khối `measured`. Drift-audit **ĐÃ CHẠY 2026-08-07/S179** (PASS_WITH_FLAGS-8) — **re-sync KHÔNG làm, KHÔNG dời hẹn, 0 hit trên cả 6 sổ bền**. 🔴 **Nhân chứng thứ 2 cùng nợ:** `E2` synthesis:28 (*"baseline `measured` = phép CHÉP chưa ai chép, 16/17 rữa"*) KHÔNG lên HANDOFF, trong khi `E1` **cùng dòng cùng file** thì CÓ ⇒ **bỏ CHỌN LỌC**, loại được bào-chữa "bỏ cả khối". **Chứng độ lệch:** row `lead-gap-auditor` `l1_hot=3691 B`, đĩa = **25.454 B** = **6,9×** | `memory-budget.json measured._note` vs `CLAUDE.md` audit-định-kỳ vs `bookend-close-synthesis.md:28` vs `HANDOFF:7` |
|
||||
| 3 | HIGH | `gap-carry-dropped` | Lead **tự chẩn 1 khuyết-tật vận-hành + tự hứa vá** ngay trong synthesis S185: *"`signal_last_kind` cuối = light (em gọi light-stamp SAU deep — lần sau gọi light TRƯỚC deep)"* — **0 hit trên cả 6 sổ bền**, sống DUY NHẤT trong tệp run. 🔴 **Đến hạn ĐÚNG lượt này:** với `deep_every=1` thì **mỗi bookend bắn cả 2 kind** ⇒ field ghi sai **MỖI PHIÊN**, không phải 1 lần | `bookend-close-synthesis.md:33` vs grep 2 chuỗi = 0/6 sổ |
|
||||
| 4 | MED | `gap-carry-dropped` | Mark Active-High `RC-pqhuy1987-07-08-2026-21-42-10` khai NGOÀI-phạm-vi-ký **đúng 2 món trong 1 câu**: **W3b + W5b(ii)**, kèm lệnh *"cả hai KHÔNG được đọc là đã niêm phong"*. W3b **có nhà** (`[carry:w3b-cam-ma-thoat]` + `HANDOFF:14`). **W5b(ii) có 0 nhà** — hit HANDOFF duy nhất nằm **bên trong chính câu lineage đó**, không phải mục việc. 🔸 **Song sinh cùng câu = control-dương sẵn có** | `ACTIVE-MARKS.md:27` vs HANDOFF (W5b=1 hit lineage, W3b=3 hit có slug) vs STATUS/WAL/error-ledger/migration-todos = 0 |
|
||||
| 5 | MED | `gap-decision-sunk` | `score-counter` + `score-count-auditor` (luật chấm-điểm 2 tầng **owner duyệt @S181/W4b**) có **0 cửa gọi** trong toàn bộ `.claude/commands/*.md` (cmd-files 0/0) + 0 hit trên HANDOFF·WAL·error-ledger·ACTIVE-MARKS·migration-todos — **chỉ tồn tại ở dòng roster STATUS**. 🔴 **Không phải vai chết yểu:** nó **ĐÃ bắt lỗi đếm của lead** (3 mục vs 1 mục), ghi verbatim ngay trong sổ YC ⇒ **vai có giá trị chứng minh được, chạy bằng trí nhớ lead, không ai theo dõi việc cắm cửa**. Cùng lớp: 3 vai `*-reviewer-style` cũng cmd-files=0 — nhưng chúng **ít nhất có slot (73)** | `.claude/agents/{score-counter,score-count-auditor}.md` vs grep trong `.claude/commands/*.md` = 0 vs `so-yeu-cau:59` |
|
||||
| 6 | MED | `gap-carry-aged` | Mark `RC-pqhuy1987-15-07-2026-15-32-23` **khoản (6)** đặt *"RE-STAMP carry MỖI PHIÊN"* thành **nghĩa-vụ Active-High**, nêu đích danh chế-độ hỏng *"không re-stamp ⇒ streak≡1 ⇒ 0 fire vĩnh-viễn"*. Khối mới nhất **vẫn nhãn @S181** (S185 chỉ vá ADDITIVE). **Không máy nào kiểm "phiên này đã re-stamp chưa"** — 3 hit "re-stamp" trong `scripts/` đều là chữ gợi-ý-sửa, **không phải phép đo** ⇒ **nghĩa-vụ mark KHÔNG có cửa thi hành, và chế-độ hỏng nó cảnh báo đang SỐNG** | `ACTIVE-MARKS.md:23` khoản (6) vs `HANDOFF:15` + `HANDOFF:18` (tự tố) vs grep scripts |
|
||||
|
||||
## `YC-002-NHA`
|
||||
|
||||
Verbatim cột 2 = ***"OK cứ làm dry-run luôn"***; ý thật (cột 7) = **thi hành trọn plan adapt** của gói 7-GĐ.
|
||||
🔴 Nhà ĐÚNG **không phải HANDOFF-carry** — **sổ YC đã là nhà chuẩn của một REQUEST**; cái thiếu là **disposition có bằng chứng**. Và phần dư của nó **CÓ TÊN, không mơ hồ**: mark `21-42-10` khai đúng 2 khoản chưa niêm phong của **chính gói đó** = **W3b + W5b(ii)** ⇒ YC-002 = *"đóng phần đã ký + treo đúng 2 khoản này"*.
|
||||
**Đề xuất:** lật YC-002 → **đóng**, con-trỏ neo **mốc BỀN** (`d2918790` / `runs/2026-08-07-S180-adap-upgrade-pack-phased/`, **KHÔNG neo WAL** vì WAL ghi-đè), rồi cấp cho **W5b(ii)** một `[carry:*]` như W3b đã có (F-4). *Nếu owner thấy còn wave khác chưa xong thì đó là dữ-kiện mới — hiện 0 bề mặt nào nêu.*
|
||||
|
||||
## `DEEP-VS-LIGHT` — vai tự trả lời THẲNG, không nống
|
||||
|
||||
🔴 **CÓ, nhưng theo trục BỀ RỘNG chứ không phải ĐỘ NẶNG.** 3 FLAG nặng nhất phiên này (deep_every=1 không được thi hành · probe đo trước tick · slug `h24-deep-overdue` bị đóng) **là của lượt LIGHT — deep không vượt được**.
|
||||
Cái deep bắt được mà light **về cấu trúc** không thể:
|
||||
1. **Set-difference trên toàn bộ 57 slug DỰNG LẠI TỪ NGUỒN** — light chỉ đọc khối mới nhất nên nó đọc đúng cái *danh-sách-được-chép*, tức **mù đúng chỗ `HANDOFF:18` tự tố**.
|
||||
2. **Chuỗi con-trỏ chết** cần **2 tệp + 1 sự-kiện reset** mới thấy (migration-todos → WAL).
|
||||
3. **Khuếch-tán run-artifact → sổ bền** (12 tệp, ~250 KB) — light không mở tệp run.
|
||||
4. **Ma trận 29 persona × 11 tệp lệnh** để lộ vai không có cửa gọi.
|
||||
|
||||
## `COVERAGE-DEEP` — 5/5 mục tiêu có chạm, khai đúng nấc
|
||||
|
||||
- **(i)** Trong **19 slug vắng** khỏi khối mới nhất, vai chỉ **phân định được 3** (2 đóng có chứng đĩa @S179 · 1 đổi tên có ghi); **16 slug ở segment đông cứng (dòng ≥185) CHƯA phân định** — không mở từng cái ⇒ **đây là CẬN DƯỚI**. 🟢 Kèm tin lành: **3/3 slug trong segment SỐNG đều có disposition** ⇒ re-stamp S185 **không rơi thêm** trong vùng sống.
|
||||
- **(ii)** 12 tệp run S185: đọc synthesis + run.md + grep đối chiếu; **KHÔNG đọc thân 5 sub-file lớn (~135 KB)** ⇒ nợ nằm trong thân chúng có thể sót.
|
||||
- **(iii)** Phép "cửa gọi" = **grep TÊN vai**, KHÔNG phải phép thử spawn ⇒ **thủng 2 chiều** (gọi bằng văn xuôi không nêu tên ⇒ dương-giả; nêu tên mà không spawn được ⇒ âm-giả).
|
||||
- **(iv)** `gap-carry-aged` **VẪN KHÔNG ĐO ĐƯỢC** ở lượt deep: không có artifact H24-2, persona cấm tự tính streak ⇒ **F-6 nói về NGHĨA-VỤ + CỬA THI HÀNH, không phải về con số**.
|
||||
- **(v)** `gap-underfill` **không đo lượt này** — không chạy `crystallized-backfill`; số đã biết (target 210.000 > headroom 134.573 @S185) có nhà ở `[carry:underfill-mau-so]` nên không phải gap, **nhưng đó là số CŨ chứ không phải số vai đo**.
|
||||
@ -0,0 +1,30 @@
|
||||
# sub-lead-gap-auditor — bookend @open S186 (2026-08-10) — nấc LIGHT
|
||||
|
||||
> Lượt-trả-1 dính **#53** (28 tool-call). Recover bằng **SendMessage resume**. Dưới = lượt-trả-2.
|
||||
|
||||
**VERDICT: `LEAD-GAP: 6 FLAG`**
|
||||
|
||||
## FLAG
|
||||
|
||||
| # | sev | class | thiếu gì | nguồn đối chứng |
|
||||
|---|---|---|---|---|
|
||||
| 1 | HIGH | `gap-decision-sunk` | Owner **YC-019(5)** @S185 (*"OK kéo audit luôn giờ cứ end/start là cứ chạy"*) đã đổi `deep_every` **15→1** = MỌI bookend phải chạy **DEEP**. S186 @open là **cơ hội ĐẦU TIÊN** sau khi đổi — và **không có deep-sweep nào được xếp** | `memory-budget.json _deep_every_owner_S185` + sổ YC-019 `:54` vs `runs/…S186-bookend-open/run.md:9` (V2 chỉ liệt light + ring2) + brief lead |
|
||||
| 2 | HIGH | `view-claim-broader-than-sample` | Số probe lead phát cho vai đo là **số ĐO TRƯỚC TICK** ⇒ đúng ô quyết định deep bị đọc thành "ok". Vai **tự chạy lại cùng lệnh read-only**: lead đưa `light 0/6 deep 0/1 ok / run-chua-gom 0`; đo lại ra `light 1/6 deep 1/1 OVERDUE:deep / run-chua-gom 1` | `nhip-no-probe.ps1:96-109` vs `.session-counter.json` (counter 60, deep_at 59) vs `session-start.md §2.1.8(a)/(c)/(e)` thứ tự TICK→OVERDUE→probe |
|
||||
| 3 | HIGH | `gap-carry-dropped` | `[carry:h24-deep-overdue]` bị **ĐÓNG @S185** (`HANDOFF:27`) trong **CHÍNH closeout đặt `deep_every=1`**. Slug duy nhất theo dõi nợ-deep bị retire **đúng lúc nợ-deep thành thường-trực** ⇒ `OVERDUE:deep` hiện **không slug nào gánh**, không vào tally `class_repeat` | `HANDOFF:27` vs `h24_cadence.deep_every=1` |
|
||||
| 4 | MED | `gap-decision-sunk` | **`YC-002`** = yêu-cầu owner **DUY NHẤT còn mở**, nhắc **4 lần** — **0 hit trên TOÀN BỘ 9 bề mặt bền** (HANDOFF · STATUS · WAL · error-ledger · migration-todos · ACTIVE-MARKS · `_end` · `_context-s-13` · `_mind-s-13`). Bất-đối-xứng: 8 mã ĐÃ ĐÓNG (YC-013..022) đều có mặt ở HANDOFF; control-dương YC-019 = 1 hit HANDOFF + 1 hit `_mind`. **Mục MỞ là mục duy nhất biến mất** | `so-yeu-cau:35` vs grep 9 surface = 0/9 |
|
||||
| 5 | MED | `gap-carry-dropped` | **4 nợ MỚI sinh @S185** chỉ sống dạng văn xuôi ở `HANDOFF:7`/`:14`, **không được cấp `[carry:*]` slug nào** ⇒ nằm NGOÀI tập mà re-stamp + detector H24-2 đo. Đo: `nhịp-refresh-_mind` 0 hit ngoài HANDOFF · `36,7` 1 hit · `day-gõ-thật` 1 hit · `rider-jump` 2 hit (đều chỉ HANDOFF). WAL rỗng, migration-todos 0 | `_end:3` + `HANDOFF:7,:14` vs **57 slug** `[carry:*]` trong HANDOFF |
|
||||
| 6 | MED | `gap-carry-aged` | **Số streak KHÔNG ĐỌC ĐƯỢC lượt này**: lead không phát output H24-2 kèm brief, persona cấm vai tự tính lại. Khối RE-STAMP mới nhất vẫn nhãn **@S181** (`HANDOFF:15`) dù segment là S185 — **đúng hình dạng mà H24-2 dùng L7-bolt để WITHHOLD số** (anchor-gap) ⇒ 🔴 **ô này KHÔNG được đọc thành 0/lành** | `governance-detectors.ps1:1035-1054` vs `HANDOFF:15` |
|
||||
|
||||
## 3 câu đích danh
|
||||
|
||||
**`WAL-RESET:` KHÔNG mất việc.** `_end:3` liệt 4 carry (over-cap-3-vai · rider-jump C-1 W3b · 3-món-thiết-kế-ctx · day-gõ-thật) và **cả 4 ĐỀU có ở `HANDOFF:7`/`:14`**; `_end:4` liệt (73)(74) và cả 2 có ở `HANDOFF:6`/`:49-50`. ⇒ mang sang **ĐỦ về NỘI DUNG**; cái thiếu là **HÌNH THỨC** — 4 món không được cấp slug nên rơi ra ngoài máy đo (**đã tính thành FLAG-5, không đếm hai lần**).
|
||||
|
||||
**`NO-DE-TRACKER:`**
|
||||
- (a) `ring2-audit` — 🔴 **tiền-đề của lead ĐÃ LỖI THỜI**: đo tươi **24.859 B < cap 25.600**, **không còn over-cap**; owner đã phán **C-6** ("Role baseline + Runs", YC-019(2)) và S185 đã nén xong. Over-cap thật = **3 vai** (`reviewer` 31.341 · `harvest-curator` 28.071 · `ctx-audit` 26.695), và 3 vai này **CÓ tracker** (`HANDOFF:7` + probe `tran-bo-nho 3`) ⇒ **không phải gap**.
|
||||
- (b) `tien-kiem-cua.ps1` luôn exit 0 — **CÓ tracker**: `[carry:w3b-cam-ma-thoat]` (`HANDOFF:16`) + `HANDOFF:14` ghi rõ "W3b mã-thoát = CHỜ rider-jump được DỰNG"; lý do sống ngay trong script `:3,:14-15` ("soak thực tế = 0 phiên") ⇒ **INFORM, không phải `gap-*`**. Cái KHÔNG có tracker là **vế đi kèm `rider-jump`** (đã tính ở FLAG-5).
|
||||
|
||||
**`SO-YC-17:`** Mẫu số **17 mục** khớp luật đếm (22 dòng − 5 dòng `nhắc-lại-của`). **Không tìm thấy ca lead diễn-giải làm mất nguyên-văn** ở lượt này — ngược lại sổ giữ verbatim cả câu gõ sai/lặp (YC-002 *"OK cứ làm dry-run luôn"*, YC-019 nguyên chuỗi 6 vế, YC-020 giữ nguyên lỗi gõ *"đông ý"*), cột 7 tách đúng luật chống-bẫy-A1, và ca thu-hẹp cũ (`hmw_width` @S179) đã bị chính sổ ghi tố ở YC-012. Vấn đề của YC-002 **không phải mất nguyên-văn** mà là **không có nhà trên bề mặt công việc** (FLAG-4).
|
||||
|
||||
## Honest-zero CÓ LẬP LUẬN (`CON LAI: 0 FLAG sev thấp`)
|
||||
- `gap-owner-specifics` = **0** — verbatim 6 dòng @S179 đã vá vào `HANDOFF:33-43` dạng bảng; specifics YC-019/020 chép nguyên chuỗi.
|
||||
- `gap-incident-unrecorded` = **0** — sự cố disk/NTFS-compress + Gitea push 502/unpacker S185 **CÓ nhà bền**: STATUS 1 hit + gotchas 1 hit nêu đích danh "502 + unpacker error", dù WAL đã reset trắng.
|
||||
@ -0,0 +1,49 @@
|
||||
# sub-lead-stale-auditor — bookend @open S186 — nấc **DEEP** (trả nợ `OVERDUE:deep`)
|
||||
|
||||
**VERDICT: `LEAD-STALE-DEEP: 2 FLAG`** — vai khai **2 = SÀN**, độ phủ lệch mạnh về **trục số-học**; trục ngữ-nghĩa gần như chưa mở.
|
||||
|
||||
## FLAG
|
||||
|
||||
| # | sev | class | vật | view → source |
|
||||
|---|---|---|---|---|
|
||||
| 1 | HIGH | `view-stale-status` | `docs/governance/harness-11-engine.md:470` | Hàng 5 bảng §O ghi *"lời chủ nguyên văn ở lane DISPATCH … (chủ dự án **phán** P7 vế (ii) @S181)"* + *"✅ **MỚI CÓ @W5b/S181**"* → sổ quyết-định `owner-decisions-07-08-2026-dot-2.md:17` ghi **NGƯỢC**: *"vế (ii): **KHÔNG có phán nội dung** — lead thi hành theo bậc-1 tự-trị"*, `:46` nhắc lại; mark **ĐÃ KÝ** `ACTIVE-MARKS.md:27` **loại W5b vế (ii) khỏi phạm-vi niêm phong**; `expensive-runs-view.md:61` + `bang-goc.md:50` đều còn ghi *"chờ P7 vế (ii)"*. 🔴 **Site thứ 4 của đúng ca bắt @S181** — lần đó lead hạ chữ **3 site**, dòng này **sống sót**. Ai đọc bảng §O sẽ kết luận *"owner đã phán + đã niêm phong"* — **sai CẢ HAI vế** |
|
||||
| 2 | HIGH | `view-stale-status` | `docs/changelog/migration-todos.md:4` | *"**Active work (@S168)** … **GĐ3 đồng-nhất BLOCKED chờ owner slot (63)**, K8-prep còn"* → `STATUS:9`: *"@S168 owner **chốt (63)(b)=D**+'mở rộng': GĐ3-D **SHIP `081557b`** … K8 **DRY-RUN PASS TOÀN TRÌNH 2 NHÁNH**"*; mốc sống `STATUS:6` = S185/L13. **Cửa chặn đã mở 17 nhãn phiên / 4 phiên-LOGIC trước.** Đắt vì `CLAUDE.md:8` xếp file này vào **5 file đọc-trước-tiên** *và* ra lệnh *"active-work đọc TRONG file"* ⇒ **con-trỏ khởi-động trỏ thẳng vào ô đã chết** |
|
||||
|
||||
> 🔗 **Chứng thực chéo:** FLAG-2 trùng vật với `gap-deep` FLAG-1 nhưng **khác trục** — gap nói *file chưa bao giờ được tick từ S168* (thiếu), stale nói *nội dung nó đang chủ động dẫn sai* (lệch). Hai vai độc lập, hai lăng kính, cùng chỉ vào một tệp ⇒ nâng độ tin.
|
||||
|
||||
## VERIFIED-CLEAN (có lệnh bác, KHÔNG phải "không thấy gì")
|
||||
|
||||
- `ACTIVE-MARKS.md` — 8 mark Active-High + §LT3 + 2 khối Active/Medium: **0 flag**. Số dẫn-xuất trong thân mark (vd `RC-…-20-42-01` "roster 12") **cố ý không sửa** theo P4/P8 — delta sống ở chú-thích `:45-49` đã có và đã đúng; flag chúng = **dẫm bẫy #3**. Phạm-vi-ký W6 `:27` đối chiếu 3 nơi: **chỉ `harness-11-engine:470` đọc rộng hơn** (đã lên FLAG-1).
|
||||
- `docs/gotchas.md` — `grep -c "^### [0-9]"` = **88**, max = `### 88.` = `STATUS:471` **88**. **Khớp 3 chiều.**
|
||||
- 🟢 **Control dương chứng THƯỚC ĐO ĐƯỢC:** `HANDOFF:7` "3 over-cap: reviewer 31.341 · harvest-curator 28.071 · ctx-audit 26.695" → `wc -c` từng `agent-memory/*/MEMORY.md` > 25.600: **đúng 3 vai, khớp BYTE-EXACT 3/3** ⇒ 2 FLAG trên **không phải nhiễu thước**.
|
||||
- `harness-11-engine.md:92` + `so-yeu-cau:51` khai bảng thẩm-quyền "10 số + 12 luật" → đếm hàng thật: cột 1 = **10**, cột 2 = **12**. Khớp.
|
||||
- 🟢 **Nhịp: 0 site chép digit `deep_every`** trong **29 persona + `session-start`/`session-end`** — tất cả **trỏ khoá**. ⇒ đợt đổi **15→1 @S185 KHÔNG đẻ site thối nào** — đúng thứ **B1 dựng ra để chặn**.
|
||||
- Ghost-enum: `lead-gap-auditor.md:29` "## 6 class" = đúng 6 nhánh `gap-*`; heading persona của chính vai **không còn số** (vá @S180 giữ). **Không tái phát.**
|
||||
- `.claude/WAL.md` rỗng sạch — khớp điểm-dừng-SẠCH S185, không mâu thuẫn `/day`.
|
||||
- `runs/2026-08-09-S185-*`: `run.md:17-20` bảng P1-P4 toàn `[x]`, 0 ô `[!]` chọi `sub-*`. `sub-reviewer-yc021.md:19` còn trỏ `bang-tham-quyen-owner-DRAFT.md` (đĩa nay bỏ `-DRAFT`) — **CỐ Ý KHÔNG flag: run-folder phiên đã đóng = record đóng-băng**.
|
||||
|
||||
## ĐÃ CÂN NHẮC → BÁC (ghi để lần sau khỏi đo lại)
|
||||
|
||||
`agents/README.md:228` suy từ **9 vai đo @S143** ra *"mọi câu read-only là ý-định"* trong khi nay **25/29 persona** khai không Write/Edit — **đúng hình dạng `view-claim-broader-than-sample` về mặt số**, nhưng chiều suy rộng là chiều **BỎ bảo-đảm an-toàn** (làm hệ **dè chừng hơn**), không phải chiều tự cấp bảo-đảm. **Fire nó = phạt đúng cái nết thận trọng ⇒ bác.**
|
||||
|
||||
## INFORM — 🔴 **ĐÃ BỊ BÁC, GỠ** (annotate, không xoá — record integrity)
|
||||
|
||||
> 🧊 **Nguyên văn INFORM của vai (giữ làm vết):** *"`runs/2026-08-09-S185-email-adap-pipeline/sub-opus-lane2-tienkiem.md` kết thúc bằng **dòng trắng, không có dòng chốt/`END`** như 8 sub cùng folder ⇒ **nghi cụt ruột**."*
|
||||
|
||||
❌ **`ring1-audit` BÁC bằng đo trực tiếp** (`sub-ring1-audit-h2.md` → `LANE2-CUT`): tệp **19.289 B** (nằm giữa dải 17.662–27.979 B của 8 lane anh em) và **CÓ dòng chốt `**END — sub-opus-lane2-tienkiem**`** — chỉ bị **4 dòng trắng đuôi** đẩy xuống. Thước đọc `tail -1` thấy chuỗi rỗng rồi kết luận "cụt".
|
||||
|
||||
🔑 **Cùng lớp với 3 lần thước-hỏng của chính `ring1` phiên này** (`Off`-substring · regex không nuốt `**` · `-maxdepth` che synthesis lồng): **vắng-mặt do THƯỚC CẮT SAI CHỖ, không phải vắng-mặt thật.**
|
||||
⇒ Vị-từ đúng để đo "sub có dòng chốt" = `tail -c 400 | grep -c 'END'`, **KHÔNG dùng `tail -1`**.
|
||||
⇒ Hệ quả: `SẠCH 12/12` của H2 **đứng nguyên**; 2 vai **không còn chọi nhau**.
|
||||
|
||||
## `COVERAGE-DEEP` — chạm 6/5 mục giao + 1 tự thêm; **2 = SÀN**
|
||||
|
||||
| | trạng thái |
|
||||
|---|---|
|
||||
| `ACTIVE-MARKS.md` | ✅ **TRỌN 76 dòng** (gồm §LT3 + khối SUPERSEDED) |
|
||||
| `migration-todos.md` | ⚠️ chỉ **header `:1-14`**; ~700 dòng thân phase **CHƯA đọc** |
|
||||
| `gotchas.md` | ⚠️ chỉ **trục số-học** (đếm heading + max); **KHÔNG đọc ruột 88 mục** ⇒ sai-ngữ-nghĩa không mang số còn sót |
|
||||
| `runs/2026-08-09-S185-*` | ⚠️ trọn `run.md` + `checklist-final` + **4 sub**; **9 sub còn lại chỉ đọc 3 dòng cuối** |
|
||||
| **29/29 persona** | ⚠️ chạm nhưng **CHỈ BẰNG GREP theo mẫu** (roster · cadence-digit · class-count · nấc-restart · read-only) — **0 file đọc trọn**; persona 13-20 KB/file ⇒ **lệch-ngữ-nghĩa-không-số là vùng mù còn nguyên** |
|
||||
| tự thêm | `bang-tham-quyen-owner.md` + `so-yeu-cau` (chỉ bảng trạng-thái) + WAL |
|
||||
| **CHƯA chạm hẳn** | `docs/rules.md` · `architecture.md` · `PROJECT-MAP.md` · `error-ledger.md` · `ritual-labels.md` · `naming-standard.md` · **20+ command** (chỉ mở 3) · `runs/**` phiên khác |
|
||||
@ -0,0 +1,36 @@
|
||||
# sub-lead-stale-auditor — bookend @open S186 (2026-08-10)
|
||||
|
||||
> Lượt-trả-1 dính **#53** (chỉ về câu mở đầu, 29 tool-call). Recover bằng **SendMessage resume** + ép form nén. Nội dung dưới = lượt-trả-2, nguyên văn.
|
||||
|
||||
**VERDICT: `LEAD-STALE: 6 FLAG`** — vai tự khai **6 = SÀN, KHÔNG phải TỔNG**.
|
||||
|
||||
## FLAG
|
||||
|
||||
| # | sev | class | vật | view ghi gì → source thật |
|
||||
|---|---|---|---|---|
|
||||
| 1 | HIGH | `view-stale-role-desc` | `.claude/agents/README.md:27` | "Roster THẬT (28 vai — đếm phần-tử)" + liệt đúng 28 TÊN, **VẮNG `ring5-audit`** → đĩa = **29**; canonical `STATUS.md:479` = **29**; `hmw.js:43` ĐÃ có |
|
||||
| 2 | HIGH | `view-stale-status` | `.claude/agents/README.md:217` | `ring5-audit` gắn nhãn *(verified-pending-restart @S185)* → `STATUS.md:479` khai **verified-runtime @S185 CÙNG PHIÊN** (hot-reload agent-MỚI + spawn-probe ALIVE, KHÔNG cần đợi restart (73)); `HANDOFF.md:6` cũng nói ring5 "đã tự sống nhờ hot-reload" |
|
||||
| 3 | HIGH | `view-stale-header` | `docs/STATUS.md:9` | "🔥 CURRENT (S161→S168 … phiên-LOGIC **L9**" → mốc sống ở CÙNG file `:6`/`:7` = **S185 / L13 window 1-4**. Dư-lượng lượt vá @S179 (`:695` tự khai lúc đó `:6` kẹt L9/S168 — họ vá `:6`/`:7` mà **bỏ `:9`**) |
|
||||
| 4 | MED | `view-stale-count` | `docs/STATUS.md:479` (mega-line, trích "ĐÓNG @S181: cap 30, roster 23 ≤ 30") | "roster **23**" SAI **cả 2 thời điểm**: tại S181 = **28** (`HANDOFF.md:20` ghi đúng; `memory-budget.json _cap_raised_S181` ghi 26+2), hôm nay = **29**. 🔴 Chính row này ra luật *"số vai = cột giữa của row này — KHÔNG chép vào narrative"* rồi **tự vi phạm** |
|
||||
| 5 | MED | `view-residual-asym` | `.claude/agents/README.md:264` (khối VALID_ROLES "ĐỦ roster") | enum TÊN dừng ở đội STYLE S176, **VẮNG `ring5-audit`** → `hmw.js:43` đã thêm @S185. Sửa 1/2 bề-mặt độc-lập mà **chính `:266` cảnh báo phải sửa đồng thời** |
|
||||
| 6 | LOW-MED | `view-residual-asym` | `docs/HANDOFF.md:6` | "(73) restart CLI giờ chỉ phục vụ **3 vai STYLE**" → `agents/README.md:67` liệt **5** vai chờ restart (`score-counter` · `score-count-auditor` + 3 style); 2 vai tầng-2/tầng-3 seed @S181 chưa được trừ ra, cũng chưa chứng đã sống ở đâu |
|
||||
|
||||
## ROUTE-GAP → chuyển `lead-gap-auditor`
|
||||
|
||||
4 broadcast (`08-05 phan-1`/`phan-2` · `08-07 nghi-thuc-hai-dau` · `08-08 quy-trinh-hai-dau-phien`) có **3 adap-report nấc `executed`** trên đĩa nhưng **0 dòng INBOUND** (`broadcasts/_index.md` dừng ở 2026-08-05 cho lô 08-04) + **0 copy** `inbox/ai_infra/` (file mới nhất 2026-08-04) ⇒ **cái BỊ THIẾU, không phải view lệch**.
|
||||
|
||||
🔴 **2 lưu ý kèm — PHẢI giải trước khi gọi là nợ:**
|
||||
1. `check-email.md:67` khai thư kênh `all` **KHÔNG vào `_index`**, trong khi `_index.md:7` khai **P6 @S180 đã mở INBOUND cho CẢ HAI kênh** và `:87-91` đã "bù dòng @S180 (P6)" cho đúng lô `all` ⇒ **hai lệnh nói ngược nhau**.
|
||||
2. `_index.md:23` **đã tự khai đúng điểm mù này** ("thư đã áp mà chưa từng có dòng trong sổ thì cột `áp` không thấy được").
|
||||
|
||||
## INFORM — không xếp được vào enum ĐÃ NIÊM PHONG (`_sealed_P3B_S181`); nhà đúng = `error-ledger.md`, vai KHÔNG tự chế class
|
||||
|
||||
`docs/STATUS.md:466` row Migrations ghi **72** — **số ĐÚNG** (145 .cs − 72 Designer − 1 ModelSnapshot = 72) — nhưng **công-thức nó tự dán kèm thì SAI**: chạy đúng chữ *"`ls Persistence/Migrations/*.cs` trừ Designer+Snapshot"* ra **71**, vì bộ lọc `Snapshot` **nuốt luôn migration thật** `20260717032812_AddPeApprovedBudgetSnapshot.cs`. Vai tự khai đã trúng bẫy ở lượt đo đầu, phải đo lại mới thấy. Đúng lớp `memory-budget.json:50` đã đặt tên **"Snapshot-nuốt-migration"** — lớp lỗi ĐÃ BIẾT mà công-thức trong canonical vẫn còn nguyên ⇒ người/máy đo sau ra 71 rồi tưởng canonical thối.
|
||||
|
||||
> 🔸 Lead đối chứng: phép đo của lead dùng `grep -v ModelSnapshot` (khớp CHÍNH XÁC tên file snapshot) nên **ra 72, né được bẫy** — trùng số với vai nhưng bằng đường khác.
|
||||
|
||||
## Coverage vai TỰ KHAI (⇒ 6 = SÀN)
|
||||
|
||||
- **Đã chạm:** `STATUS` §CURRENT STATE + `:6`/`:7`/`:9` · `HANDOFF:1-45` · `agents/README` dòng roster/VALID_ROLES/nấc · `hmw.js` VALID_ROLES+WIDTH_CAP · `memory-budget.json` trọn · `.session-counter.json` (counter **60** · last_ticked **S186** · `deep_at=59` khớp `deep_every=1` đổi @S185) · `broadcasts/_index`+`inbox`+adap-reports · migrations trên đĩa.
|
||||
- **CHƯA chạm:** `ACTIVE-MARKS.md` (bề mặt 4 lead giao — hết lượt trước khi mở) · `migration-todos.md` · `gotchas.md` · `runs/2026-08-09-S185-*` (mới 3/12 tệp) · **27/29 persona**.
|
||||
- **Chạm mà KHÔNG ra flag:** `CLAUDE.md:128` audit "ĐÃ CHẠY 2026-08-07 (S179) · lần kế 2026-09-01" — hôm nay 08-10, ô ngày **còn hiệu lực**.
|
||||
@ -0,0 +1,56 @@
|
||||
# sub-ring1-audit — lượt NỐI: KIỂM H2 (phần bị cắt ở lượt trước) — bookend @open S186
|
||||
|
||||
**VERDICT: `RING1-H2: 28Đ / 1T / 4KC`** — 🔴 **cái TRƯỢT duy nhất là của `lead-stale-deep`, KHÔNG phải H2.**
|
||||
|
||||
## H2 `GATE-FAIL-6` — ĐỨNG VỮNG TOÀN BỘ
|
||||
|
||||
**Orphan**
|
||||
- universe **93 dir** ✓ (engine riêng) · **90** có `run.md` ✓
|
||||
- 3 loại trừ **CÓ TÊN** — ring1 liệt độc lập ra **đúng 3 tên đó**, **0 tên thứ 4**. Rider: 2 dir S117 miễn bằng LỜI ("spec-only") chứ không bằng lưới; nhưng H2 **nêu tên thay vì giấu trong số** = **đúng kỷ luật**.
|
||||
- Lưới bắt **đúng 1** candidate = `S186-bookend-open` (đang chạy) ✓ · `0 orphan · 0 lệch folder` **chịu** dưới engine dựng lại từ đầu.
|
||||
|
||||
**Zero-byte**
|
||||
`-size 0` = **10** ✓ · `-not -name .gitkeep` = **0** ⇒ **10/10 là `.gitkeep`, không phải "phần lớn"** · 5 folder đều tiền tố `2026-06-18` ✓ · `MEMORY.md -size 0` = 0 ✓ · `-type d -empty` = **0** ⇒ lớp sandbox-rỗng **ring1 tự bắt @S159 nay lành thật**.
|
||||
|
||||
**Over-cap — 4 số LÁI HÀNH ĐỘNG, khớp TỪNG BYTE**
|
||||
| vai | byte | verdict |
|
||||
|---|---|---|
|
||||
| `reviewer` | **31.341** ✓ | vượt cả soft-L1 30.720 ✓ |
|
||||
| `harvest-curator` | **28.071** ✓ | tự khai ngôi-thứ-nhất, và đúng |
|
||||
| `ctx-audit` | **26.695** ✓ | |
|
||||
| `ring2-audit` | **24.859** ✓ | 🔴 **DƯỚI cap** — `git cat-file -s c2f45231:…` = **24859** == `stat` đĩa **24859** ⇒ **2 nguồn khác lớp trùng khít** ⇒ **H2 bác lead là ĐÚNG** |
|
||||
- cap **25.600** ✓ (`autoinject_cap_bytes`) · archive `ring2-audit/archive/2026-08.md` **8.771 B** tồn ✓ (**nén thật, không phải xoá**) · `tooling-auditor` **15.039** ✓
|
||||
- 🟢 **Ranh giới SẠCH:** sort toàn bộ 29 sổ, hạng 4-6 = `lead-gap` 25.454 · `implementer-backend` 25.280 · `ring1-audit` 25.261 — **cả 3 DƯỚI cap** ⇒ **không sót vai thứ 4**.
|
||||
- Danh sách synthesis S185 `{ring2, reviewer, tooling}` **sai 2/3** ✓
|
||||
|
||||
**Tally + 2 finding HIGH**
|
||||
- git = **13 `MEMORY.md`** ✓ (lệnh tự tái-lập được) · **+7 topic-file MỚI** ✓ — ring1 đếm thô ra 8, tách `--name-status` = 7×`A` + 1×`M`(`.mfe-state.json`) ⇒ **H2 phân loại đúng, ring1 mới là người suýt over-count**. 7/7 rơi vào `reviewer/` ⇒ **2 finding H2 tự khớp nhau**.
|
||||
- **HIGH-1:** `sub-reviewer-yc021.md` = **23.280 B** ✓ · **vô hình 4/4 tầng = 0** ✓ · "chỉ 1 sổ giữ vết `_mind-s-13.md:99`" = **1 hit duy nhất toàn repo** ✓
|
||||
- **HIGH-2:** `grep HARVEST-GOM` trong `harvest-curator/MEMORY.md` = **0**. 🔴 **Ring1 làm mạnh hơn cách H2 kể:** sổ đó CÓ 1 entry S185 nhưng là **lane6 memory-gate** (folder KHÁC) ⇒ **lead seed cho nó việc KHÁC mà bỏ chính vòng harvest của nó** — chứng cứng cho *"điểm-mù tự-quy-chiếu ở tầng LEAD"*.
|
||||
- **B3 cơ-chế** "seed lấy từ BẢNG không từ FOLDER" + mtime-chain: `run.md` 0 row reviewer ✓; artifact **08-10 03:48:13** ⟂ `reviewer/MEMORY.md` **08-09 22:16:09** ✓ khớp tới phút ⇒ *"không phải hết giờ, là không có tên trong bảng"* **đứng vững**.
|
||||
|
||||
## 🔴 TRƯỢT (1) — thuộc về `lead-stale-deep`, không phải H2
|
||||
|
||||
**`LANE2-CUT`: `lead-stale-deep` SAI, H2 ĐÚNG — lane2 KHÔNG cụt.**
|
||||
`sub-opus-lane2-tienkiem.md` = **19.289 B** (nằm giữa dải 17.662–27.979 B của 8 lane anh em, **không bất thường**) và **CÓ dòng chốt `**END — sub-opus-lane2-tienkiem**`** — chỉ bị **4 dòng trắng đuôi** đẩy xuống, nên thước đọc `tail -1` thấy chuỗi rỗng rồi kết luận *"kết bằng dòng trắng"*.
|
||||
⇒ **Cùng một lớp** với 3 lần thước-hỏng của chính ring1 phiên này: **vắng-mặt do thước cắt sai chỗ, không phải vắng-mặt thật.**
|
||||
⇒ **Đề nghị GỠ INFORM đó.** Muốn giữ vị-từ "sub có dòng chốt" thì đo bằng `tail -c 400 | grep -c 'END'`, **đừng `tail -1`**.
|
||||
⇒ **Hai câu không còn chọi nhau** — `SẠCH 12/12` của H2 **đứng nguyên**.
|
||||
|
||||
## FALSIFY-H2 — 6 phép, **2 BROKE vào chính ring1**
|
||||
|
||||
- 🔴 **BROKE (tự, lần 3 trong phiên)** — phá `ORPHAN = 1 candidate`: lens ring1 (`find -maxdepth 1`) ra **6**, tưởng bắt được H2 dương-giả. Mở ruột: **5 folder `2026-06-18` giấu synthesis ở thư mục con `harvest/`** (cấu trúc harness-10 lồng). H2 **cố ý không `-maxdepth`** ⇒ **thước ring1 hỏng, H2 HELD**.
|
||||
- **HELD** — phá `0 orphan` bằng **lens NGƯỢC** (đo `run.md` thay vì synthesis): ra đúng 3 dir thiếu, **và 3 dir đó chính là 3 tên H2 đã khai miễn** ⇒ **tập miễn khép kín, không ai núp trong đó**.
|
||||
- **HELD** — phá `ZERO-BYTE` bằng **đổi vị-từ** (chạy thẳng `-size 0 -not -name .gitkeep` = 0 thay vì tin lời) — 2 đường độc lập, 0 ca thật.
|
||||
- **HELD (mạnh nhất — số này LÁI HÀNH ĐỘNG)** — phá `ring2 đã nén` bằng **nguồn khác lớp**: bịa SHA thì `cat-file` lỗi; bịa byte thì 2 số lệch. **Không phá được** ⇒ **lead cứ yên tâm BỎ `ring2` khỏi danh sách nén**.
|
||||
- **BROKE (vào `lead-stale-deep`)** — `tail` + `cat -A` cho thấy dòng chốt **CÓ THẬT**.
|
||||
- **HELD** — phá `+7 topic-file` (nghi under-count): tách `--name-status` ⇒ **ring1 mới là người suýt over-count**.
|
||||
|
||||
## 🔄 RING1 TỰ SỬA PHÁN CŨ — có lợi cho H2
|
||||
|
||||
> *"Tao đã soi **đúng nhóm khẳng-định-VẮNG-MẶT** — 4 câu đích danh + các số 0 — và **10/10 chịu**, gồm cả 2 câu nặng nhất (`0 orphan`, `0 ca thật`) và cả **4 con số byte lái-hành-động** khớp **từng byte**."*
|
||||
|
||||
⇒ **Sửa `L1-CAT`: L1 bị cắt KHÔNG làm hỏng `GATE-FAIL-6`.** **Không re-chạy.** Vẫn nên nén `harvest-curator` 28.071 B — **nhưng vì nó over-cap, KHÔNG phải vì verdict của nó đáng ngờ.**
|
||||
|
||||
## KC (4) — đều LOW, không cái nào lái hành động; dồn @close
|
||||
census corruption 25/23/12 · ánh-xạ 7 lane→sổ · 2 chân tally ngoài-git ("11 vai/16 entry" · "17 khối/11 sổ" — **chân git 13 đã ĐẠT**) · tally `8MAJ/6m` (chuỗi `PASS_WITH_FIXES` có thật `:23`/`:90`, chưa cộng).
|
||||
@ -0,0 +1,65 @@
|
||||
# sub-ring1-audit — KIỂM vòng tooling/harvest — bookend @open S186
|
||||
|
||||
> Lượt-trả-1 dính **#53** (24 tool-call). Recover bằng SendMessage resume. Dưới = lượt-trả-2.
|
||||
> ⚠️ Harness gắn cờ `instruction-shaped (settings-json)` — đọc kiểm: là **nội dung phát-hiện** về `~/.claude/settings.json`, không phải chỉ-thị.
|
||||
|
||||
**VERDICT: `RING1: 21Đ / 3T / 4KC`**
|
||||
|
||||
## TRƯỢT (3)
|
||||
|
||||
| # | nguồn | claim | vì sao vỡ |
|
||||
|---|---|---|---|
|
||||
| 1 | H1 | *"4 root sống VẮNG HẲN: Hrm·Off·**Puro**·Personal"* | 🔴 **`Puro` KHÔNG PHẢI root sống.** `grep -rn Puro src` = 6 hit, **6/6 nằm trong COMMENT** trỏ NAMGROUP (*"mirror layout Puro"*); 0 const, 0 phần-tử `MenuKeys.All`, 0 row cây. Control-dương: `Personal` = const thật `MenuKeys.cs:195` + root thật `:2045`. **Tập đúng = Hrm·Off·Personal·`HopDongCung`** (`HopDongCung` có root-row `:1952` nhưng cố-ý ngoài `All`) ⇒ **H1 thay 1 tên thật bằng 1 tên ma** |
|
||||
| 2 | H1 | severity *"VẮNG HẲN"* | Nói quá — `SKILL.md:25` **CÓ dòng uỷ-quyền** (*"Office/HRM/… — xem `MenuKeys.cs` (mã là nguồn)"*) ⇒ vắng khỏi **CÂY** nhưng **có TRỎ nguồn**; người tra không bị bỏ rơi hoàn toàn |
|
||||
| 3 | H1 | neo `README:2` cho luật B1 | Luật thật ở `README:3` — lệch 1 dòng, LOW, **không đổi kết luận** |
|
||||
|
||||
## KC — không kết luận được (4)
|
||||
|
||||
1. 🔴 **TOÀN BỘ H2** (6 finding + 4 câu đích danh: ORPHAN 0 · S185-2-FOLDER · OVER-CAP byte · ZERO-BYTE · tally 13) — **lượt bị cắt trước khi mở H2** ⇒ **0 claim H2 được xác nhận**; verdict này **KHÔNG phủ H2**.
|
||||
2. Plugin 4 nấc + lead model — nguồn `~/.claude/settings.json` **NGOÀI git, ngoài repo** ⇒ **KHÔNG chấm, CẤM ĐẠT-ảo**.
|
||||
3. Frontmatter 29/29 (`model`/`effort`/`memory`) — chưa dựng lại (cắt lượt).
|
||||
4. ĐÓNG F-2 (`loi-hua-may-scan.ps1` có cửa) + F-3 addendum — chưa chạm đĩa.
|
||||
|
||||
> Vai đề nghị: **giao lại @close, ưu tiên (1)**.
|
||||
|
||||
## ĐẠT — trích các claim load-bearing
|
||||
- roster **29** trên **4 engine** (`ls|grep -v README` · `git ls-files` · `find` · `agent-memory/*/` dir), `grep -n ring5-audit README` chỉ ra `:217` **không ra `:27`** ⇒ H1 đúng cả số lẫn "thiếu tên".
|
||||
- `pause.md:39` "11/23" — verbatim khớp; **tử-số thật 24/29** (parse frontmatter 29 tệp: 24 NO-WRITE / 5 HAS-WRITE / 0 thiếu key) ⇒ **khớp H1 tuyệt đối**.
|
||||
- `permission-matrix` cây **8 root**; `:3` khai **12**; **12 = ĐÚNG đo TỪ MÃ** (parser parent-null trên `DbInitializer.cs`), không tin sổ.
|
||||
- `STATUS:9` còn `Mig **71**` · canonical `:468` = **72** · đĩa = **72** (row cuối `20260806070242_AddMaHangMucToContractSigningPlanLines.cs`).
|
||||
- `STATUS:478` = `| Skills | 6 |` · `:479` = `| Sub-agents | 29 |` — khớp DIFF của H1 **tới từng số dòng**.
|
||||
- Tuần-tự C4b: mtime H1 13:20:57 · H2 13:22:33 · ring1 khởi sau 13:22 ⇒ **0 chồng lấn**.
|
||||
- **Ngoại lệ dựng gián tiếp:** `tooling-auditor` KHÔNG over-cap — bằng chứng nội-tại là H1 chạy đủ 4 mặt + DIFF chi tiết trong 5.759 B return, **không có dấu cắt L1** như H2 tự khai.
|
||||
|
||||
## FALSIFY — 6 phép, **2 BROKE vào CHÍNH NÓ**
|
||||
- 🔴 **BROKE (tự)** — phá "`Off` 0 hit": `grep -c Off` ra **1** ⇒ suýt tố H1 sai. Mở ruột: hit là chữ "**Off**ice" ở `:25`; word-boundary = 0 ⇒ **thước ring1 hỏng, H1 HELD**.
|
||||
- 🔴 **BROKE (tự)** — phá "`STATUS:9` còn Mig 71": `grep -oE 'Mig [0-9]+'` ra **RỖNG** ⇒ suýt tuyên H1 bịa. Thật ra chuỗi là `Mig **71**`, regex không nuốt `**` ⇒ **"2 engine cãi nhau thì đi tìm engine HỎNG, đừng chọn con SỐ"** (luật ring1 tự rút S181) — H1 HELD.
|
||||
- **HELD** — phá "tử-số 11 nay là 24": dựng engine ĐỘC LẬP (awk parse frontmatter block-1, word-boundary `Write|Edit|MultiEdit|NotebookEdit`) ⇒ **24/29 khớp tuyệt đối**.
|
||||
- **BROKE (vào H1)** — phá "4 root sống": control dương/âm từng tên ⇒ claim vỡ **1/4**.
|
||||
- **HELD** — phá "`:27` thiếu cả tên": `grep -n ring5-audit` toàn tệp = **chỉ `:217`**.
|
||||
- **HELD (chống chính nghi-vấn của lead)** — nghi số H1 là "số nhớ-lại sau #53": lấy **3 số load-bearing khó nhớ nhất** (24/29 · 12 root · cặp dòng `:478`/`:479`) đo lại độc lập ⇒ **3/3 khớp tới từng đơn vị, kể cả cặp số dòng trượt-1**. *"Số nhớ-lại không trúng được kiểu đó."*
|
||||
|
||||
## TAI-DUNG — 7 số, **6 khớp / 1 lệch**
|
||||
roster 29 ✓ · `tools:` không-Write 24/29 ✓ · root menu 12 (đo từ MÃ) ✓ · cây permission-matrix 8 root ✓ · Mig đĩa 72 = canonical 72 ≠ `STATUS:9` 71 ✓ · `:478`/`:479` ✓ · **LỆCH: tập "4 root vắng"** — H1 {Hrm,Off,Personal,**Puro**} vs ring1 {Hrm,Off,Personal,**HopDongCung**}.
|
||||
|
||||
## THACH-CLEAN
|
||||
- Mở mặt H1 gọi CLEAN (*"`ef-core-migration` = dương-giả, đã ratify"*) → **CHỊU**: `SKILL.md:21-23` thật sự trỏ 2 nguồn sống, `:25` có 🧊 khai bản án S181. **Ô ring1 tự chọc thủng @S181 nay đã lành ĐÚNG CHỖ.**
|
||||
- Mở mặt H1 gọi PASS (*"12 root ĐÚNG theo ring1@S182"*) → **CHỊU nhưng H1 chỉ có 1 nguồn**: H1 **trích sổ**, ring1 **đo MÃ** ⇒ số đứng, **đường dẫn tới số thì mỏng**.
|
||||
- 🔴 **KHÔNG chạy được** thách-CLEAN bắt buộc trên H2 (`ORPHAN 0` · `ZERO-BYTE 0`) và mặt plugin — ghi **KC**, **không tuyên sạch**.
|
||||
|
||||
## `ROOT-PARSER` — chạy XONG: **12 root**
|
||||
Parser parent-null trên `DbInitializer.cs`, liệt đích danh: `:1928` Dashboard · `:1932` Master · `:1947` Contracts · `:1951` KeHoachKyKet · `:1952` HopDongCung · `:1968` Forms · `:1969` Reports · `:1970` System · `:1977` PurchaseEvaluations · `:2001` Hrm · `:2019` Off · `:2045` Personal. `tree.Add` sinh-trong-loop có parent null = **0** ⇒ không sót nhánh động.
|
||||
Đối chiếu claim H1: `:3`=12 **ĐÚNG SỐ**, cây 8 **ĐÚNG LÀ THIẾU**, nhưng **tập thiếu H1 kê SAI 1 phần tử**.
|
||||
|
||||
## `L1-CAT` — verdict H2 giữ phần DƯƠNG, mất phần ÂM
|
||||
|
||||
L1 cắt làm hụt **nền so sánh** (baseline / nợ cũ / luật đã kê) ⇒ mọi câu dạng *"**0** orphan · **0** ca thật · **SẠCH 12/12** · **không tái phát**"* là **khẳng-định-VẮNG-MẶT** — đúng loại mà bộ nhớ thiếu dễ đẻ **0-hit-giả** (lớp `absence_looks_like_clean` + `pattern-thu-hep-che-hit` ring1 bắt @S168). Ngược lại **2 finding HIGH** (`sub-reviewer-yc021` 23.280 B chưa về · self-record thiếu) là **khẳng-định-CÓ-VẬT, tự mang neo, không cần L1 để đúng**.
|
||||
🔴 **Khuyến nghị:** **KHÔNG re-chạy toàn bộ** (đắt, và over-cap còn nguyên nên chạy lại **bị cắt y hệt** — *"re-chạy trước khi nén là lặp lỗi"*). Thứ tự đúng: **nén `harvest-curator` 28.071 B xuống dưới cap TRƯỚC**, rồi chỉ re-verify **4 câu đích danh**. **Nợ HIGH thì hành động ngay, đừng đợi.**
|
||||
|
||||
## `RESUME-FIDELITY` — 🔑 **"số sống sót, nhãn trôi"**
|
||||
|
||||
**Chấp nhận được với nấc HẠ MỘT BẬC, không phải vứt.** Bằng chứng thực nghiệm chính lượt này:
|
||||
- **3/3 số load-bearing khó-nhớ** của H1 tái dựng **khớp tuyệt đối** ⇒ lượt-trả-2 **giữ được SỐ ĐO**.
|
||||
- **1/1 lỗi bắt được lại là lỗi TÊN GỌI** (`Puro` — chữ đọc từ comment *"mirror layout Puro"* **bị nhớ thành root sống**). H1 còn dính thêm **1 neo lệch** (`README:2` thật ra `:3`) — cùng họ.
|
||||
|
||||
⇒ **Nấc `verified-number / unverified-label`:** mọi **CON SỐ** trong tệp lượt-trả-2 dùng được sau spot-check; mọi **TÊN RIÊNG** (tên key · tên vai · tên tệp · số dòng neo) **phải chạm đĩa mới được mang vào báo cáo**.
|
||||
@ -0,0 +1,61 @@
|
||||
# sub-ring2-audit — KIỂM vòng soi-lead H24 — bookend @open S186
|
||||
|
||||
> Lượt-trả-1 dính **#53**; mẩu garble **CHỨA FINDING** (*"Re-derivation is stronger than either vai claimed"*) ⇒ giữ, không vứt. Dưới = lượt-trả-2.
|
||||
|
||||
**VERDICT: `RING2: 18Đ / 0T / 2KC`** — **0 TRƯỢT trên cả 4 file H24.**
|
||||
|
||||
## 2 KC (không chấm, cấm ĐẠT-ảo)
|
||||
- `gap-light F4` (YC-002 0/9 bề mặt) — ring2 chỉ tái-dựng được **1 chiều**; 9-surface sweep chưa chạy lại trước khi #53 cắt. **Không có dấu hiệu bịa.**
|
||||
- `gap-deep F2` — vế `l1_hot=3691` vs đĩa **25.454 B** ✓ **xác nhận byte-exact** (tỉ số 6,9× đúng); vế *"re-sync 5 bản sao, 0 hit/6 sổ"* + vế E2-vs-E1 chưa chạy lại ⇒ **không ĐẠT gộp cả FLAG**.
|
||||
|
||||
## 🔴 TAI-DUNG — **mạnh hơn CẢ HAI vai**: `migration-todos.md`
|
||||
|
||||
- gap-deep khai `grep 10 nhãn = 0/10` — tức **một MẪU**. Ring2 **không grep mẫu**, mà **quét toàn bộ nhãn `S\d{2,3}` trong 824 dòng / 88.151 B**: tập nhãn = `{10,11,17,18,19,20,21,29,32,33,34,35,36,37,38,156,160,162,164,167,168}` ⇒ **max = S168, KHÔNG ngoại lệ**.
|
||||
🔑 **Mạnh hơn ở chỗ:** `0/10` còn chừa cửa *"chắc họ ghi bằng nhãn khác"*; **`max = S168` đóng cửa đó.**
|
||||
- **Đường thứ 2, cơ học, không ai dùng:** `git log -1 -- migration-todos.md` = **`a2b37dcc` 2026-08-01** ⇒ file **chưa được chạm 9 ngày** ⇒ *"0 ô nào tick từ S168"* **không phải suy luận từ nội dung mà là hệ quả tất yếu của lịch sử git**. Trạng thái ô: **210 `[x]` / 128 `[ ]`**.
|
||||
- 🔄 **Commit-ordering — BROKE giả thuyết BUỘC-TỘI của chính ring2.** Nó nghi file bị sửa **SAU** cú ship rồi vẫn để nguyên chữ BLOCKED (**nặng hơn nhiều**). Đo: `a2b37dcc` **16:12:06** < `081557b4` **16:49:46**, `merge-base --is-ancestor` = **NO** ⇒ file **đóng băng 37 PHÚT TRƯỚC khi cửa chặn mở**, cùng ngày cùng phiên S168. ⇒ **giữ phán quyết, HẠ lý do**: không phải cố ý bỏ, mà là **đóng băng đúng một bước trước khi trạng thái đổi** — rồi **18 nhãn phiên không ai quay lại**.
|
||||
- 🔴 **`tiep_reload` — BROKE nghi vấn thứ 2, và làm 2 FLAG NẶNG THÊM theo cách KHÔNG VAI NÀO NÓI RA.** Diary ring2 có 2 mốc ghi "5 nguồn" nên nó nghi gap-deep bịa "1/9". Đọc khoá sống: **LIST len = 9**, phần tử **#9** = `migration-todos.md :: header Active-work + section Phase hiện hành`. **Vai ĐÚNG, trí nhớ ring2 stale** (danh sách nở 6→9 @S168).
|
||||
⇒ Nguồn #9 nạp **đúng "header Active-work"** = **đúng dòng `:4` đã chết** ⇒ câu *"GĐ3 BLOCKED chờ slot (63)"* được **TÁI SINH vào context lead MỖI LẦN `/tiep`**. Cùng lớp `lead-stale` FLAG-15 @S162 (*"sai số TÁI SINH mỗi /tiep"*).
|
||||
- **Hội tụ 2 vai là THẬT và ĐỘC LẬP:** gap đo *thiếu* (0 tick), stale đo *lệch* (nội dung dẫn sai). Cùng vật, **2 vị ngữ khác nhau** ⇒ **không phải đếm trùng**.
|
||||
|
||||
## FALSIFY — 5 phép, **3 BROKE vào chính ring2**
|
||||
1. 🔴 **BROKE (tự)** — `README:67` "5 vai": cắt 400 ký tự thấy dòng chỉ nói ĐỘI STYLE ⇒ định TRƯỢT vì cite sai neo. Đọc trọn **527 ký tự**: đuôi liệt đúng 5 tên ⇒ **cáo buộc sập, stale-light F6 HELD**. *(Tái phát bài S159 "mega-line cắt ngắn suýt tạo cáo buộc oan" — vấp lại đúng chỗ.)*
|
||||
2. 🔴 **BROKE (tự)** — "1/9 nguồn `tiep_reload`": 2 entry diary ghi 5 ⇒ nghi bịa. Khoá sống = 9 ⇒ **gap-deep HELD**.
|
||||
3. 🔴 **BROKE (tự)** — commit-ordering (xem trên).
|
||||
4. **HELD** — thử đọc `STATUS:9` thành **dương-giả** qua miễn-trừ "STALE-BY-DESIGN" (`:7`+`:695` tự khai khối CURRENT "chỉ advance @closeout"). **Bác không được:** miễn trừ phủ độ trễ **trong 1 phiên**; đây trễ **4 closeout / 17-18 nhãn**, và `:695` ghi rõ ca **y hệt @S179 đã bị xử là lỗi**. **Miễn trừ không co giãn tới 4 kỳ.**
|
||||
5. **HELD** — nghi `view-claim-broader-than-sample` là **ép-vừa-enum** (nghe giống lỗi *thời điểm* hơn lỗi *suy rộng*). **Bác không được:** lead lấy 1 lát cắt (counter=59) rồi phát ra như trạng thái toàn phiên ⇒ **đúng vị ngữ "claim rộng hơn mẫu"**. Class giữ.
|
||||
|
||||
## 🔴 THACH-CLEAN — **CÓ 1 FLAG SÓT**, nằm ĐÚNG vùng mù `lead-stale-deep` tự chỉ
|
||||
|
||||
**`.claude/commands/session-start.md:142`** — `5. docs/changelog/migration-todos.md — atomic tasks theo phase (**Phase 11 polish hiện tại**)`.
|
||||
Trong khi `CLAUDE.md` (**cùng nhiệm vụ, cùng danh sách 5-file**) đã ghi *"active-work đọc TRONG file — **KHÔNG neo nhãn phase ở đây, H24-DEEP F-10**"*, và chính `migration-todos:4` trỏ **§Phase 12**.
|
||||
⇒ Một FLAG H24-DEEP cũ (**F-10**) đã ra luật *"cấm neo nhãn phase"*, luật **được thi hành trên `CLAUDE.md`** nhưng **bỏ sót bề mặt song sinh** ⇒ nhãn `Phase 11` **sống tiếp**. Class: **`view-residual-asym`** — **cùng đúng dạng vá-nửa** mà stale-light F5 bắt ở VALID_ROLES.
|
||||
**Nặng vì:** đây là **lệnh khởi phiên, đọc mỗi phiên**, và nó **dán nhãn phase sai lên đúng cái file đã đóng băng** ở 2 FLAG trên. **Ngoài trục số-học** ⇒ đúng chỗ vai khai chưa mở.
|
||||
|
||||
## ENUM-P3B — **đúng luật, cả 2 vai**
|
||||
**12/12** class dùng ∈ `lead_self_audit.flag_classes` (`_sealed_P3B_S181` + `_class_added_S172`), **0/20 tự chế**; `set(class_repeat.counts)` ≡ `set(flag_classes)` **12/12 hai chiều**.
|
||||
2 mục INFORM (công thức đếm migration tự nuốt `AddPeApprovedBudgetSnapshot` ⇒ 71 thay 72 · nghi lane2 cụt) đều là **quan sát thật nhưng vị ngữ không thuộc class nào** ⇒ xử **ĐÚNG**: không ép vừa, không tự chế, đẩy `error-ledger`, **không tính vào FLAG-count**.
|
||||
🔸 *Chính nước đi mà @S162 ring2 phải chấm TRƯỢT vì vai ép-vừa (F-16), và @S172 phải escalate owner mở class mới — **lần này 2 vai làm đúng ngay từ đầu**.*
|
||||
|
||||
## DEEP-CO-DANG-TIEN
|
||||
**Trung thực, không khiêm tốn giả** — 3 FLAG light nó tự nhận nặng hơn đều ĐẠT và đều đánh vào **cơ chế đang chạy sai NGAY LÚC NÀY**, còn 6 FLAG deep đánh vào **nợ tồn**. **Nhưng *"chỉ thắng bề rộng"* là NÓI NHẸ ĐI MỘT NẤC** — `deep F1` (migration-todos đóng băng, con-trỏ chết 2 đầu) là **FLAG có bán kính lớn nhất phiên**, và light **về cấu trúc không thể thấy** vì nó không mở tệp run / không dựng set-difference từ nguồn.
|
||||
|
||||
## FLAG3-GAP-DEEP
|
||||
**Lời hứa CÓ THẬT** — `bookend-close-synthesis.md:33`, verbatim *"`signal_last_kind` cuối = light … lần sau gọi light TRƯỚC deep"*; **đến hạn đúng lượt này** vì `deep_every=1` ⇒ mỗi bookend bắn cả 2 kind.
|
||||
**Thứ tự đúng:** gọi **`light` trước, `deep` sau**, để `signal_last_kind` đọng lại là **`deep`** — nấc CAO hơn và là nấc thực sự đã trả. Gọi ngược ⇒ field **vĩnh viễn khai `light` dù deep đã chạy**, tức **hạ cấp chính công việc vừa làm, mỗi phiên**.
|
||||
🔸 Ring2 verify được **vế lời hứa + vế cơ chế**, **chưa mở `h24-signal-write.ps1`** ⇒ khuyến nghị thứ-tự là **suy từ ngữ nghĩa field**; lead nên liếc script trước khi ghi. *(Lead ĐÃ liếc: `.SYNOPSIS` + `_contract` xác nhận — dedupe max-1-decision/class/logic-session, và `(4) updates last_audit.{light|deep}_at_counter`.)*
|
||||
|
||||
## TALLY-KHUYEN-NGHI
|
||||
- **Đơn vị = VÒNG-ĐO, không phải flag** (bài S153: 2 FLAG cùng class chỉ +1). light + deep = **2 nấc trong CÙNG cửa bookend-open** ⇒ **gộp thành 1 quyết-định/class**.
|
||||
- **9 class được +1:** `view-stale-role-desc` · `view-stale-status` · `view-stale-header` · `view-stale-count` · `view-residual-asym` · `gap-carry-dropped` · `gap-decision-sunk` · `gap-carry-aged` · `view-claim-broader-than-sample`.
|
||||
- 🔴 **KHÔNG ghi:** `gap-underfill` (gap-deep tự khai *"không đo lượt này"*) · `gap-owner-specifics` + `gap-incident-unrecorded` (**honest-zero có lập luận ⇒ giữ 0, không reset**).
|
||||
- 🔴 **Cảnh báo đơn-vị (bài D-4/S159):** `class_repeat.counts` là **consecutive-audit** — đừng chép số đếm-trong-phiên vào đó. Hiện `gap-carry-dropped`=**14**, `view-residual-asym`=**12**, `view-stale-count`=**12**, đều **≥4× ngưỡng jump 3**. ⇒ ***"+1 nữa không đổi bản chất; cái cần là ĐỔI HÀNH VI, không phải đổi số."***
|
||||
- 2 mục KC ring2 **không bác** — lead tự đối chứng được thì cứ +1, ring2 chỉ **không đứng ra bảo chứng**.
|
||||
|
||||
## Tham chiếu chéo `verified-number / unverified-label` (ring1)
|
||||
Hình dạng **có xuất hiện**, **nhẹ hơn ca `Puro`** — *số đúng, nhãn/neo lệch* ×3: (a) stale-deep neo `harness-11-engine.md:470` (S181 là `:474`) = **trôi dòng thật**, số dòng hiện tại đúng; (b) gap-deep "~17 nhãn phiên" — S168→S186 là **18**, vai để dấu `~` nên **không sai**, và "4 phiên-LOGIC" thì **chính xác** (L9→L13); (c) stale-light F1 gán `view-stale-role-desc` cho vật mang **cả** lỗi liệt-kê **và** lỗi số — biên giới với `view-stale-count`, **không chấm TRƯỢT** vì vị ngữ chính là *danh sách thiếu phần tử*.
|
||||
🟢 **Không ca nào là tên-ma** — mọi neo ring2 mở đều tồn tại thật.
|
||||
|
||||
## 🔴 Câu dặn cuối
|
||||
> **18 ĐẠT là độ CHÍNH XÁC — mọi FLAG đều trúng vật thật. Độ PHỦ thì KHÔNG.**
|
||||
> Cả 4 file đều tự khai SÀN; `lead-stale-deep` tự khai 29/29 persona chỉ grep + `gotchas` chỉ đo số-học; **và ring2 vừa bắt được 1 FLAG sót ngay trong vùng mù đó chỉ bằng 1 lệnh grep** ⇒ **bằng chứng vùng mù còn RẤT NHIỀU vật, không phải đã cạn.**
|
||||
@ -0,0 +1,43 @@
|
||||
# sub-tooling-auditor (H1) — bookend @open S186 (2026-08-10)
|
||||
|
||||
> Lượt-trả-1 dính **#53** (34 tool-call, chỉ về câu mở đầu). Recover bằng **SendMessage resume** + ép form nén. Dưới = lượt-trả-2.
|
||||
> ⚠️ Harness gắn cờ `instruction-shaped pattern (settings-json)` trên return này. Đọc kiểm: đó là **nội dung phát-hiện** về `~/.claude/settings.json` (đếm plugin + model lead), KHÔNG phải chỉ-thị. Xử như finding.
|
||||
|
||||
**VERDICT: `TOOLING: PASS_WITH_FLAGS 7`**
|
||||
|
||||
## Finding
|
||||
|
||||
| sev | mặt | vật | lệch gì |
|
||||
|---|---|---|---|
|
||||
| HIGH | vai-trò | `.claude/agents/README.md:27` | "Roster THẬT (28 vai — đếm phần-tử…)" — đĩa **29**; enumeration liệt TÊN cũng **thiếu `ring5-audit`**. 🔴 2 lỗi/1 dòng, **vi phạm chính luật `:2`** ("KHÔNG chép số vào file này", B1); trớ trêu `:28` ngay dưới là bài S143 dạy *"đếm phần-tử trong mọi enumeration"* |
|
||||
| MED | docs | `.claude/commands/pause.md:39` | "11/23 vai bị luật CẤM tự-ghi" — **mẫu-số 23 stale** (nay 29) **VÀ tử-số 11 stale** (đo đĩa: **24/29** vai `tools:` không khai Write/Edit). Câu present-tense, **không có neo thời-điểm** ⇒ không phải bản-ghi đông-lạnh ⇒ **vá được** |
|
||||
| MED | skill | `.claude/skills/README.md:20` | Vẫn ghi `ef-core-migration` *"(mới nhất → row cuối bảng skill)"* — **đúng câu đã bị BÁC + gạch @S181** tại `ef-core-migration/SKILL.md:25` ("SAI và tự mâu thuẫn"). Lớp claim-chép-nhiều-site; đã flag @S182, **còn nguyên qua S185** |
|
||||
| MED | skill | `.claude/skills/permission-matrix/SKILL.md:44-84` | Cây menu vẽ **8 root** trong khi `:3` khai **12 root** (ring1 @S182 chốt 12 = ĐÚNG). **4 root sống VẮNG HẲN**: `Hrm`·`Off`·`Personal`·`Puro` = **0 hit**; control dương `Contracts` = 7 hit ⇒ **vắng thật, không phải thước hỏng**. Nợ từ S179 F-2 |
|
||||
| LOW | docs | `docs/STATUS.md:9` | Mega-line CURRENT còn "Mig **71**"; canonical `:468` = **72**, đĩa = **72**. Đúng lớp S180 BÁC-1 (bảng canonical tươi, mega-line tụt) |
|
||||
| LOW | vai-trò | `.claude/agents/README.md:27` | ASCII diagram + dòng "7 core lane" tự khai "cosmetic chưa vẽ lại" — **nợ nhận-biết**, không phải drift mới |
|
||||
| INFORM | vai-trò | `hmw.js:176` + `memory-budget.json hmw_width.cap` | cap=**30**, roster nay **29** ⇒ headroom còn **1**. Thêm 1 vai nữa là LOUD-skip quay lại (slot 75 @S181 khép trên tiền-đề "28 ≤ 30") |
|
||||
|
||||
## DIFF vs S185
|
||||
- **THÊM:** `ring5-audit` (vai 28→29, +1 agent-memory dir) · `.claude/commands/day.md` (command 19→20) · `scripts/day-probe.ps1` (script 36→37)
|
||||
- **ĐỔI:** STATUS canonical roster row trượt `:478`→`:479` (`:478` nay = `| Skills | 6 |`) · `agents/README` skill-matrix `:194-222` nay phủ **29/29** ⇒ **S182/S185 F-1 "26/28" ĐÓNG** (gồm 3 vai STYLE `:200-202` + `ring5-audit` `:217`)
|
||||
- **XOÁ:** —
|
||||
- **STALE:** `agents/README:27` · `pause.md:39` · `skills/README:20` · `permission-matrix` cây 8/12 root · `STATUS:9` Mig 71
|
||||
|
||||
## ĐÓNG được từ S185 (verify đĩa)
|
||||
- **F-2 máy mồ-côi `loi-hua-may-scan.ps1`** — nay CÓ cửa gọi `session-end.md:167` + nhà khai. Máy MỚI `day-probe.ps1` **KHÔNG mồ-côi**: 3 cửa (`day.md`·`pause.md`·`tiep.md`); control dương `naming-scan` = 1 command/8 docs.
|
||||
- **F-3 addendum lệch vế** — `adap-reports/2026-07-19-…nam-khoan.md:94-102` có ADDENDUM-2 đóng 4.4 + ADDENDUM-3 đóng 4.3, row cũ giữ nguyên (đúng doctrine).
|
||||
|
||||
## Plugin 4 nấc
|
||||
installed **18** → enabled **15** → disabled **3** (`pr-review-toolkit`·`code-modernization`·`hookify`) → assigned `agents/README:236-245` → **used = KHÔNG đo được từ repo, KHÔNG khai**. **0-delta phiên thứ 6.** Khớp `agents/README:236`. Lead model `~/.claude/settings.json` = `claude-opus-5[1m]` + `effortLevel: xhigh`.
|
||||
|
||||
## Frontmatter
|
||||
**29/29 SẠCH:** `model: opus` 29/29 · `effort: max` 29/29 · `memory: project` 29/29 — lỗ thủng S181 (2 vai W4b thiếu `effort`) **KHÔNG tái phát** ở vai mới.
|
||||
|
||||
## RING5-SYNC
|
||||
**ĐỦ 3 nơi, KHỚP 29:** `.claude/agents/ring5-audit.md` trên đĩa · `docs/STATUS.md:479` = canonical **29** ĐÚNG · `agents/README` row skill-matrix `:217`. **NHƯNG bề-mặt thứ 4 trong chính README — dòng "Roster THẬT" `:27` — vẫn 28 và không có tên `ring5-audit`.** Bề-mặt máy khớp: `hmw.js` VALID_ROLES = 29 (parse bỏ comment), `.claude/agent-memory/*/` = 29 dir, **0 phần-tử thừa/thiếu cả 2 chiều**.
|
||||
|
||||
## SKILL-STALE-VERDICT — **HỖN HỢP**
|
||||
🔴 **Phần lớn = dương-giả của soft-net**, trừ **2 ca stale THẬT**.
|
||||
- **Dương-giả:** `ef-core-migration/SKILL.md` bảng Mig dừng row 71 vs đĩa 72 — nhưng `:25` **đã ratify @S181** rằng bảng này *"chỉ để tra lịch sử; nó SẼ LUÔN tụt sau và điều đó HỢP LỆ"*. Count-token trong `contract-workflow` (`Mig 16/18-20`, "6 test PE 2-stage", "9 phase") + `form-engine`/`iis-deploy-runbook` (`gotcha #9/#11/#12/#25/#26`) đều là **con số của sự-kiện đã đóng trong quá khứ**, không phải claim về hiện-tại ⇒ **vá để tụt flag-count = Goodhart, rời tập-đo**.
|
||||
- **Stale THẬT phải vá đúng 2:** (1) `skills/README:20` chép câu **ĐÃ BỊ BÁC** — không phải count-token mà là **vị-từ sai**; (2) `permission-matrix` cây 8 root vs 12 khai `:3` — mô-tả **không phản-ánh 4 root đang sống**, cùng lớp vụ `Bg_*` vá @S122/@S164.
|
||||
- **Căn cứ phân loại:** hỏi *"dòng này khai về QUÁ KHỨ hay tự nhận là ẢNH HIỆN-TẠI?"* — chỉ nhóm thứ hai mới là stale.
|
||||
@ -0,0 +1,63 @@
|
||||
# run: 2026-08-10-S187-duyet-hd-tu-khkk
|
||||
|
||||
goal: Module "Duyệt Hợp đồng" (GĐ3 pipeline) từ KHKK — YC-023 (owner 2026-08-10 @S187).
|
||||
Khuôn mirror PE/KHKK cả FE+BE+menu; quy trình duyệt V2 per-LOẠI-HĐ (thầu phụ, nguyên tắc, …);
|
||||
phiếu kế thừa từ KHKK ĐÃ DUYỆT (bridge K7 `aaed6992` có sẵn — CreateContractFromSigningPlanFeatures 251 dòng).
|
||||
Điều phối owner chỉ định: 2×/fable-real invest (BE ⟂ FE) → 1×/fable-real style-review →
|
||||
1×/fable-real review tổng thể + tổng hợp plan → fan-out Opus 5 MAX theo wave.
|
||||
|
||||
engine: /fable-real per-invocation (H21 lệnh-A) ×4 — single Fable deep-pass, propose-only, lead ghi spec.
|
||||
|
||||
## taskList snapshot
|
||||
1. [x] invest-BE — XONG @15:44 → `sub-invest-be-fable.md` 26.119 B, §0-§7 + `== HẾT ==` (return garble
|
||||
#53 nhưng đĩa TRỌN — 0 mất). Lõi: V2 tái dùng 100% · thiếu = quan-hệ workflow↔loại (PA-A/B/C
|
||||
§2d) · 2 bom per-type §2c · phiếu GĐ3 = chính Contract (0 entity mới) · mặc định 0-mig · 7 CHỜ-ANH.
|
||||
2. [x] invest-FE — XONG @15:41 → `sub-invest-fe-fable.md` 21.981 B, §0-§7 + CHỜ-ANH + `== HẾT ==`
|
||||
(garble #53 lượt-1 → SendMessage resume → CLEAN; vai TỰ APPEND diary S187). Lõi: vỏ đủ 42 leaf
|
||||
`Ct_*` · khuôn duyệt = KhkkWorkflowPanel owner-trim · per-loại = `?type=N` KHÔNG `:kind` ·
|
||||
0 key mới · wave F1-F5 chặn-bởi-BE-DTO · 5 CHỜ-ANH. Mâu-thuẫn 2-lens: F3 inbox — BE §1c đính
|
||||
chính S156 STALE (đã vá @W6 S161) ⇒ F3 nghiêng verify-only, giao plan-review chốt.
|
||||
3. [x] style-review — XONG @15:56 (về NGAY TRONG lúc /pause đang chạy; mốc cũ "16:0x" = CỜ-10 ctx-verifier vá @S188, đĩa mtime 15:56) → `sub-style-review-fable.md`
|
||||
`== HẾT ==` · `STYLE-REVIEW: PASS_WITH_FLAGS — 7 flag` (spawn-probe ALIVE ✓ datum slot 73).
|
||||
FLAG-1 mâu-thuẫn 2-lens phân xử BẰNG CODE (inbox V2-aware `ContractFeatures:530-593` ⇒ F3 hạ
|
||||
verify-only) · FLAG-2 🔴 WAVE MỒ CÔI: F1/F2 cần BE-DTO (reverse-join + currentApproval/
|
||||
approvalFlow) mà W0-W5 KHÔNG có wave DTO ⇒ stage C PHẢI thêm wave BE-DTO · FLAG-3 FE §5 cứng
|
||||
trục 7-type khi BE#1 treo · FLAG-4 "0 key mới" vs khuôn KHKK kèm 8 key AwV2_* + 32 policy —
|
||||
quyết tường minh · FLAG-5/6/7 nit W1/W2/W3. PA-A đúng khuôn deploy nhất (điều kiện gỡ 2 bom).
|
||||
4. [x] plan-review (reviewer × Fable) — XONG @S188 ~17:05 → `sub-plan-review-fable.md` 29.647B
|
||||
`== HẾT ==` (return garble #53 — đĩa TRỌN). 12/12 claim re-verify code + 3 phát hiện mới
|
||||
(§1b: DTO khuôn-KHKK ≠ PE · fe-admin ẩn `Ct_*` 2-TẦNG `:248` · đường fetch cây sẵn) +
|
||||
plan 11 wave §7 + hợp nhất 12 nhãn → 9 câu Q1-Q9 (= slot (80)-(88)) + 3 tự-quyết T1-T3 §8
|
||||
5. [x] lead verify + ghi spec — @S188: spot-check độc lập `Contract.CurrentWorkflowStepIndex` TỒN TẠI
|
||||
(`Contract.cs:40` — nghi ban đầu là thước grep head-limit cắt, không phải claim sai) →
|
||||
`spec-duyet-hd-10-08-2026.md` (3 heading H21①; W1/W2/W7 đủ AC thi công; W3+ AC bổ sung khi Q chốt)
|
||||
6. [ ] fan-out Opus 5 MAX theo wave (Workflow hmw, args.run BẮT BUỘC) — đợt-1 = W1→W2→W7 (0 chặn owner,
|
||||
bất biến mọi nhánh Q), TỪNG wave qua gate rồi ship; W3+ chờ đáp án (80)(81)(83)
|
||||
|
||||
## luật cho mọi sub của run này
|
||||
- Ghi file TRONG-LÚC-LÀM vào đúng sub-file của mình (APPEND từng mục xong — chống #53; return chỉ là tóm).
|
||||
- Return: dòng-1 = `<NHÃN>: <verdict ngắn>`; thân đầy đủ Ở FILE.
|
||||
- Propose-only: KHÔNG Write/Edit ngoài sub-file của mình; KHÔNG code; câu nghiệp-vụ cần owner → liệt "CHỜ-ANH".
|
||||
|
||||
## Stages
|
||||
- [x] A: 2 invest song song — 2/2 XONG trên đĩa (cả 2 garble #53 ở return; artifact-đổ-ruột cứu 100%)
|
||||
- [x] B: style-review — XONG @15:56 (về CLEAN trong cửa dừng; mốc vá CỜ-10 @S188) — PASS_WITH_FLAGS 7 flag
|
||||
- [x] C: plan-review + tổng hợp — XONG @S188 (garble #53, đĩa trọn 29.647B) — F3-inbox CHỐT verify-only
|
||||
- [ ] D: fan-out wave — spec ĐÃ GHI @S188 (`spec-duyet-hd-10-08-2026.md`); đợt-1 W1→W2→W7 (0 chặn),
|
||||
hmw Opus MAX per-wave từng wave qua gate; W3+ chờ (80)(81)(83)
|
||||
|
||||
## CHỜ-ANH tổng — ✅ ĐÃ HỢP NHẤT @S188: 9 câu = slot **(80)-(88)** (nhà durable: `docs/HANDOFF.md` khối
|
||||
"CHỜ ANH @S188" + chi tiết `sub-plan-review-fable.md §8`; BE#5≡FE#2+FE#5 gộp (82), FE#4→(82b), 12 nhãn
|
||||
= 11 câu thật → 9 sau gộp) + 3 tự-quyết T1-T3. Nhãn GỐC giữ dưới làm vết (chưa-hợp-nhất @S187):
|
||||
BE#1 trục 7-type⟂8-nhóm · BE#2 PA-A/B/lộ-trình · BE#3 catalog gợi-ý-loại · BE#4 line-level ruột HĐ ·
|
||||
BE#5≡FE#2 authz Đ1/Đ2/Đ3 · BE#6 roster 7 workflow · BE#7 bridge auto-pin override · FE#1 bộ nút 2/3 ·
|
||||
FE#3 admin thấy menu GĐ3 · FE#4 per-loại policy API · FE#5 menu public 13/13 giữ?
|
||||
|
||||
## đợt-1 fan-out taskList snapshot (S188, sau YC-024 — hmw RUN-TRACE wf_58d6f783-395) — ✅ XONG @~18:30
|
||||
- [x] t0 `test-specialist` — CLEAN: 4 test (+295/-0), 3 ĐỎ-hành-vi + 1 control → `sub-test-specialist-0.md` 13.534B
|
||||
- [x] t1 `implementer-backend` — W2 code LAND (+89 Features +76 Dtos); return rỗng #53, KHÔNG sub-file (harvest từ diff @close)
|
||||
- [x] t2 `implementer-frontend` — PARTIAL rồi W7-FINISH nối (Agent lẻ): types×2 + ContractsListPage(3-fix) + MyContractsPage; Inbox BLOCKED đúng → [carry:w7b-inbox-param] → `sub-implementer-frontend-2.md` 14.481B
|
||||
- [x] W1-GREEN (Agent lẻ) — 2 bom fix, 3 ĐỎ→XANH → `sub-implementer-backend-w1green.md` 8.874B (return #53, đĩa trọn)
|
||||
- [x] GATE lead: full test **672/672 XANH** · 0 Migrations · scope 9 file đúng · reviewer `PASS_WITH_FLAGS — 8` (`sub-reviewer-dot1.md` 20.840B): F-1/F-2 (3 tên field + nullability FE↔BE) **VÁ NGAY ×2 app** · F-4 AC-W7 sửa vào spec + nợ w7b · F-3 quyết treo cho W6 (ghi spec) · F-5..F-8 ghi nhận
|
||||
|
||||
Sau workflow (lead): gate build+test (t0 tests ĐỎ đúng lý-do, còn lại XANH) → spawn W1-GREEN (impl-be fix 2 bom cho xanh) → full test xanh → reviewer diff → 3 commit (W1·W2·W7) + 1 push (gotcha #86) → cicd-monitor verify.
|
||||
@ -0,0 +1,29 @@
|
||||
# spec-duyet-hd-10-08-2026 — YC-023 module Duyệt Hợp đồng (GĐ3) từ KHKK
|
||||
|
||||
> H21① — 3 heading. Engine propose (4 lượt /fable-real) → lead verify → LEAD ghi bản này → worker hạng-hai (Opus 5 MAX) thi công theo spec.
|
||||
> 🔴 Chân lý chi tiết = [`sub-plan-review-fable.md`](sub-plan-review-fable.md) (29.647B, `== HẾT ==`) — spec này là bản chưng-cất + AC, KHÔNG chép lại lập luận (B1). Lead đã spot-check độc lập claim wire đắt nhất: `Contract.CurrentWorkflowStepIndex` TỒN TẠI (`Contract.cs:40`) ⇒ W2 đúng 0-mig.
|
||||
|
||||
## 1. Đề-bài
|
||||
|
||||
- YC-023 (nguyên văn → sổ yêu-cầu): module Duyệt HĐ GĐ3 mirror khuôn PE/KHKK cả FE+BE+menu; quy trình duyệt V2 **riêng per-LOẠI-HĐ**; phiếu GĐ3 = **CHÍNH Contract** (0 entity mới); kế thừa từ KHKK ĐÃ DUYỆT qua bridge K7 (`CreateContractFromSigningPlanFeatures`, 251 dòng).
|
||||
- Trục treo lớn nhất = Q1/(80): 7-type ⟂ 8-nhóm — mọi wave mang nhánh điều-kiện theo bảng `sub-plan-review §4`, CẤM hardcode trước khi anh chốt.
|
||||
- 3 quyết mức-lead ĐÃ CHỐT (T1-T3 `§8b`, anh bác thì 1 lệnh): F3-inbox = verify-only (code V2-aware sẵn `ContractFeatures.cs:530-593`) · DTO màn duyệt theo **khuôn cây-thô KHKK** không phải cặp precompute PE (`§1b-①`) · CÓ sinh 7 key `AwV2_Hd*` trong nhánh PA-A.
|
||||
|
||||
## 2. Cách làm — 11 wave W0-W10 (bảng đầy đủ + phụ thuộc = `sub-plan-review §7`)
|
||||
|
||||
- **Đợt-1, 0-chặn-owner, bất biến mọi nhánh Q — thi công theo thứ tự, TỪNG wave qua gate rồi ship:**
|
||||
- **W1 — gỡ 2 bom per-type** (BE 0-mig): deactivate per-Code cho type-3 tập-type tường-minh (R-6 CẤM regex) + sửa comment `:363` CÙNG edit + overview family type-3. File: `ApprovalWorkflowV2AdminFeatures.cs:205-215/:356-377`. **Test-before RED→GREEN** (khuôn W3-KHKK).
|
||||
- **W2 — BE-DTO nền hiển thị** (BE 0-mig, đóng FLAG-2): (a) `ContractSourceDto?` reverse-join `ContractSigningPlanLines WHERE ContractId=id` → plan (`MaKeHoach`/`PlanId`/`ApprovalGroup`) + hạng mục + Σ`ApprovedAmount` + `PurchaseEvaluationId`; (b) +`CurrentWorkflowStepIndex` + `List<ContractWorkflowStepDto>? WorkflowSteps` cây thô mirror `ContractSigningPlanFeatures.cs:194/:205`. Positional record: append field default-null CUỐI. `types/contracts.ts` ×2 app cùng wave. KHÔNG sinh cặp DTO PE-precompute.
|
||||
- **W7 — per-type server-filter** (FE): `MyContractsPage.tsx:42-48` + `InboxPage.tsx:91` ×2 app gửi `?type=` lên BE (param sẵn `ContractsController.cs:23-27`); đổi queryKey.
|
||||
- **✅ ĐÁP ÁN ANH @S188 (YC-024, nguyên văn → sổ yêu-cầu):** (80) **7-LOẠI** · (81) **PA-A** (+xác nhận chuỗi PE→KHKK→HĐ) · (82) **mở-tất-cả-trước ⇒ W8 HOÃN** chờ anh nói "ổn rồi" *(đội hiểu bậc-2 — guard đúng-trạm luồng V2 vẫn chạy, đó là logic quy trình)* · (83) **roster sao khuôn KHKK**, anh tự chỉnh Admin Designer sau · (84) **2 nút** · (85) ẩn ⇒ **W9 HỦY** · (86) **khoá cứng** không đổi chéo · (87) không cột gợi-ý — user tự chọn loại khi tạo từ KHKK · (88) **ruột HĐ pre-fill** (xem W5 dưới).
|
||||
- **Sau đợt-1:** **W3** seed 7 workflow `HD-{TYPE}` **sao cấu trúc trạm/người từ seeder KHKK** (`SeedKhkkGroupWorkflowsAsync` — implementer đo DB thật: 8 nhóm chung khuôn thì dùng khuôn chung, khác nhau thì lấy N1 + khai) + 7 key `AwV2_Hd*` (T3) → **W4** guard 4-cửa nhà-chung `public static` + cửa re-pin UpdateDraft; **khoá cứng theo (86)** — auto-pin theo loại, KHÔNG cho đổi chéo → **W5** card "Nguồn gốc KHKK" (sau W2a) **+ (88) ruột pre-fill**: load field từ dữ-liệu đã nhập (bridge K7 map + KHKK/PE upstream), khuôn hiển thị tham khảo **form HĐ Word có sẵn** (`docs/forms-spec.md` 8 form), field thiếu (vd điều khoản) cho điền bổ sung — ĐO bridge-map + màn edit Nháp hiện có TRƯỚC khi thêm field mới → **W6** màn duyệt **2-nút** mirror `KhkkWorkflowPanel` tự-suy từ con-trỏ + cây thô (Đ2 = guard đúng-trạm CÓ SẴN đường V2 — KHÔNG wave authz riêng) → ~~W8~~ **HOÃN theo (82)** → ~~W9~~ **HỦY theo (85)** → **W10** dry-run 2 nhánh 2 loại HĐ toàn trình + **verify F3 tại đây** + sổ ID/seq-cháy (khuôn K8).
|
||||
- 🔴 **F-3 (reviewer đợt-1) — quyết TREO CHO W6:** DTO overview type-3 có `ActiveOthers` (đợt-1) nhưng **0 consumer FE** ⇒ màn Designer với type-3 nhiều-active VẪN giấu N-1 bản. W6/W3-Designer PHẢI chọn MỘT: (i) FE đọc `activeOthers`, hoặc (ii) lát-cắt-theo-Code từ `history` (khuôn KHKK `ApprovalWorkflowsV2Page.tsx:330-341`) rồi GỠ field (test W1b re-check khi gỡ). CẤM tuyên "bom-2 đã chữa UI" trước khi một trong hai land.
|
||||
- **Đường tới hạn:** W0(thu đáp án) → W3 → W4 → W6 → W10. Nhịp: {đợt nhỏ → ship → anh nhìn prod → vẽ tiếp} (khuôn S159-⑦). Fan-out = hmw Opus MAX, `args.run` BẮT BUỘC (run này ≥3 task).
|
||||
|
||||
## 3. Checklist AC đo được
|
||||
|
||||
- **Gate CHUẨN mọi wave code:** `dotnet build` 0-err + `npm run build` ×2 app 0-err (trọng tài chạy SAU agent dừng tay — #68) + `dotnet test` baseline `docs/STATUS.md` (668 @S185) GIỮ-hoặc-TĂNG + reviewer diff-review + cicd 3-chân (deploy xanh · marker-dương · control-âm ĐỘC-QUYỀN) + SHA-pair từng cặp file mirror FE + wave 0-mig ⇒ `git status` 0 file `Migrations/` đổi.
|
||||
- **W1:** (i) POST version Code loại X ⇒ `IsActive` Code loại khác GIỮ (đếm SQL trước/sau); (ii) overview type-3 trả ĐỦ N active cùng type; (iii) comment `:363` đã sửa cùng commit (grep).
|
||||
- **W2:** HĐ dry-run K8 (`ZZTEST-K8/...`) GET detail trả `source.maKeHoach="KHKK/2026/00x"` đúng + HĐ tạo tay trả `source=null`; DTO có `currentWorkflowStepIndex` + `workflowSteps`; wire cũ không vỡ (test cũ xanh nguyên); types ×2 app SHA-pair.
|
||||
- **W7 (AC SỬA @S188 — reviewer F-4, tiền-đề spec cũ SAI):** "×2 app" thật = `ContractsListPage` (admin — kèm 3 fix badge-total/pagination do client-filter-sau-cắt-trang) + `MyContractsPage` (user): request mang `?type=`, client-filter gỡ, queryKey có type — **ĐẠT**. **Inbox OUT-OF-SCOPE đợt-1** (đo: `GET /contracts/inbox` → `GetMyInboxQuery` KHÔNG tham số + `ContractFeatures.cs:550` `Take(100)`) ⇒ nợ có mã **[carry:w7b-inbox-param]**: BE thêm `type` + bỏ trần 100 → FE mới gỡ client-filter `InboxPage` (blocker-comment tại chỗ). Cùng-lớp còn sót (F-5, không chặn): `ContractCreatePage` ×2 app client-filter-sau-cắt-trang — gộp vào w7b.
|
||||
- **W3+:** đáp án đã đủ @S188 (YC-024) — AC chi tiết bổ sung CÙNG LƯỢT thi công từng wave, khung sẵn ở `sub-plan-review §7`; W3 kèm AC "7 workflow seed khớp cấu-trúc trạm/người nguồn KHKK (đếm SQL đối chiếu)" + 2 row canonical STATUS đổi cùng commit.
|
||||
@ -0,0 +1,79 @@
|
||||
# sub-implementer-backend-w1green — W1-GREEN: gỡ 2 bom per-type cho type-3 (Contract)
|
||||
|
||||
> Run `2026-08-10-S187-duyet-hd-tu-khkk` · vai `implementer-backend` · 2026-08-10.
|
||||
> Ghi TỪNG MỤC trong lúc làm (chống #53). Mục chưa chạy = tiêu đề + "CHƯA ĐO", cấm điền số trước.
|
||||
|
||||
## §0 — Ground-truth ĐO TƯƠI trước khi gõ
|
||||
|
||||
| Thứ đo | Lệnh | Kết quả |
|
||||
|---|---|---|
|
||||
| HEAD | `git log --oneline -1` | `cc2dae26 wal: flush 20260810T1746` |
|
||||
| Cây làm việc | `git status --porcelain` | 9 file `M`: `ContractFeatures.cs` · `ContractDtos.cs` · `fe-{admin,user}/types/contracts.ts` · `fe-user/.../MyContractsPage.tsx` (= **lane W2/W7 song song**) + `UpdateAwDefinitionTests.cs` (= **lane test-specialist đã land**) + 3 doc/governance. **0 file `Persistence/Migrations/`** · file đích của tôi (`ApprovalWorkflowV2AdminFeatures.cs`) **CHƯA ai đụng** ⇒ mọi thay đổi trong đó dưới đây là của tôi |
|
||||
| Site deactivate | `grep -n IsActive` file đích | **DUY NHẤT 1 site ghi `IsActive = false`** — `:377` trong `CreateAwDefinitionCommandHandler`. `UpdateAwDefinitionCommandHandler` cố ý GIỮ `IsActive` (`:448` "Giữ Id là TOÀN BỘ mục đích") ⇒ **không có site anh em phải vá kèm** (kỷ luật grep-cùng-lớp, S122) |
|
||||
| Nhánh per-Code hiện có | `grep -n "typeEnum == ApprovalWorkflowApplicableType"` toàn `src/Backend` | **1 hit** `:370` — không có bản sao luật ở nơi khác |
|
||||
| Consumer DTO | `grep -n AwTypeSummaryDto` toàn kho | BE: khai `:82` + dựng `:209` (**1 site dựng duy nhất**) · Api chỉ trả `ActionResult<AwAdminOverviewDto>` · `tests/**` **0 site `new AwTypeSummaryDto`** ⇒ thêm tham số vào record positional KHÔNG vỡ biên dịch chỗ nào |
|
||||
|
||||
## §1 — Đọc 4 assert quyết định HÌNH DẠNG bản vá (trước khi chọn hình)
|
||||
|
||||
`SurfacedAsCurrent` (`UpdateAwDefinitionTests.cs:1614-1629`) duyệt **MỌI** property của `AwTypeSummaryDto` trừ `History`, yield cả `AwDefinitionDto` đơn lẫn `IEnumerable<AwDefinitionDto>` ⇒ kênh đương-nhiệm = **hợp của mọi ngả ≠ History**, và:
|
||||
|
||||
- ca 3 `:1761` `BeEquivalentTo(ContractFamilyCodes)` = song ánh 7↔7 ⇒ **trùng lặp cũng ĐỎ y như thiếu**;
|
||||
- ca 4 `:1815` `ContainSingle()` cho type-1 ⇒ type 1-active **không được đôi**.
|
||||
|
||||
🔴 Hệ quả: hình (a) hiểu ngây thơ — giữ `Active` = bản đầu **và** thêm `ActiveWorkflows` = **MỌI** bản active — sẽ **ĐỎ CẢ 2 CA** (type-3: 8 mục, `HD-*` đầu bảng bị đếm 2 lần; type-1: 2 mục). Đề bài đã cảnh báo đúng chỗ này ("không leak/không đôi"). Hai cách thoát:
|
||||
|
||||
| | Hình | Ca 3 | Ca 4 | Rủi ro FE (ngoài lãnh địa tôi) |
|
||||
|---|---|---|---|---|
|
||||
| B1 | `Active = null` khi type có >1 active, tập hợp gánh TẤT | ✔ | ✔ | 🔴 **ĐỔI HÀNH VI ĐANG CHẠY**: `ApprovalWorkflowsV2Page.tsx:381-389` + `:455-479` đọc thẳng `t.active`; type-10 KHKK **hôm nay đã có 8 active** ⇒ thẻ landing + panel của nó mất nội dung ngay lượt deploy |
|
||||
| **B2 (CHỌN)** | `Active` **y nguyên công thức cũ** + trường mới gánh **phần CÒN LẠI** | ✔ | ✔ | **0** — `active` giữ đúng giá trị cũ từng byte (`versions.FirstOrDefault(v => v.IsActive)` ≡ `actives.FirstOrDefault()`), bản vá thuần **cộng thêm** |
|
||||
|
||||
⇒ Chọn **B2**, tên trường `ActiveOthers`, hợp đồng ghi thẳng vào doc-comment: **tập đầy đủ = `[Active]` ∪ `ActiveOthers`, KHÔNG lặp**. Tên `ActiveWorkflows` bị loại vì nó *hứa* "tất cả" trong khi ruột là "phần còn lại" — tên sai nghĩa là bug của lượt sau.
|
||||
|
||||
🔸 Vá bom-2 cố ý **KHÔNG hỏi type**: nó chỉ đếm "có mấy bản đang áp dụng thì trả bấy nhiêu" ⇒ tự đúng cho mọi loại family tương lai, và ca 4 xanh vì type-1 **thật sự** có 1 active (không phải vì tôi khai riêng cho nó).
|
||||
|
||||
## §2 — 4 mảnh đã sửa, 1 FILE DUY NHẤT
|
||||
|
||||
`src/Backend/SolutionErp.Application/ApprovalWorkflowsV2/ApprovalWorkflowV2AdminFeatures.cs` — `git diff --stat` = **+63 / −14, 1 file**.
|
||||
|
||||
| # | Dòng (SAU sửa) | Sửa gì |
|
||||
|---|---|---|
|
||||
| M1 | `:372-380` | **MỚI** `private static readonly ApprovalWorkflowApplicableType[] PerCodeDeactivationTypes = [ContractSigningPlan, Contract]` ở đầu `CreateAwDefinitionCommandHandler` + tiêu chí vào-danh-sách (loại có N quy trình sống song song cùng type, mỗi cái một `Code`) |
|
||||
| M2 | `:422` | **bom-1**: `typeEnum == ApprovalWorkflowApplicableType.ContractSigningPlan` → `PerCodeDeactivationTypes.Contains(typeEnum)`. Hai nhánh truy vấn GIỮ NGUYÊN từng vế (3 vế per-Code · 2 vế per-TYPE) — R-6 tôn trọng: phân loại theo **TYPE**, 0 regex/pattern trên `Code` |
|
||||
| M3 | `:395-420` (comment cũ `:363`) | Câu tự-khai "các loại khác: per-TYPE như cũ — 1 active/type là bất biến" **SAI cho type-3 sau W3** ⇒ viết lại neo vào TẬP TYPE tường minh, kèm ghi vết *vì sao câu cũ hỏng* (tập per-Code nở thêm thì câu neo-theo-con-số hỏng lần nữa) + R-6 mở rộng ví dụ `^HD-...$` |
|
||||
| M4 | `:85-101` (DTO) + `:228-245` (handler) | **bom-2**: `AwTypeSummaryDto` **+`List<AwDefinitionDto> ActiveOthers`** giữa `Active` và `History`; handler tính `actives = versions.Where(IsActive)` rồi trả `Active = actives.FirstOrDefault()` + `ActiveOthers = actives.Skip(1)`. `Active` **≡ giá trị cũ từng byte** (`versions.FirstOrDefault(v => v.IsActive)`) ⇒ 0 rủi ro cho `ApprovalWorkflowsV2Page.tsx:381-389/455-479` đang đọc `t.active` |
|
||||
|
||||
🔸 Ghi thêm vào doc-comment `Active` (`:88-92`): với loại family nó chỉ là **MỘT trong N**, panel per-Code phải tự cắt theo `Code` như KHKK đang làm (`:321-339`) — để lane FE không đọc nhầm ô này lần nữa.
|
||||
🔸 **KHÔNG có site anh em bị bỏ sót**: `IsActive = false` chỉ ghi ở đúng 1 nơi (`:429`); `UpdateAwDefinitionCommandHandler` cố ý giữ `IsActive` (`:500`).
|
||||
|
||||
## §3 — Chạy (số ĐO SAU byte cuối cùng, không phải số giữa chừng)
|
||||
|
||||
`dotnet test tests/SolutionErp.Infrastructure.Tests --filter "FullyQualifiedName~UpdateAwDefinitionTests"`:
|
||||
|
||||
```
|
||||
Passed! - Failed: 0, Passed: 31, Skipped: 0, Total: 31, Duration: 27 s
|
||||
```
|
||||
|
||||
Đích danh 4 ca W1 (`--filter ...UpdateAwDefinitionTests.W1 --logger console;verbosity=normal`):
|
||||
|
||||
```
|
||||
Passed ...W1a_CreateType3_WithOutOfFamilyCode_LeavesSevenContractCodesActive_AndStillRatchetsItsOwnCode [3 s]
|
||||
Passed ...W1a_CreateNewVersionForOneContractCode_DeactivatesOnlyThatCode_SixOtherContractTypesStayActive [1 s]
|
||||
Passed ...W1b_OverviewForContractType_SurfacesEveryActiveWorkflowOfThatType_NotJustTheFirst [1 s]
|
||||
Passed ...W1b_OverviewForSingleActiveType_KeepsExactlyOneCurrentDesignation_AndDoesNotLeakOtherTypes [504 ms]
|
||||
```
|
||||
|
||||
⇒ 3 ĐỎ → XANH · **control ca 4 GIỮ xanh** (không leak/không đôi) · 27 ca cũ giữ xanh (31 − 4).
|
||||
|
||||
**Chuỗi nhân-quả "xanh này là do bản vá", không phải do đo lệch:** (1) test-specialist ghi RED 3-ca đích danh trên `3a311c04` (`sub-test-specialist-0.md §4`); (2) §0 của tôi đo `git status` — file đích **chưa ai chạm** giữa 2 mốc; (3) tôi chỉ sửa đúng file đó; (4) chạy lại xanh. 🔸 KHÔNG dùng `git stash` để đối chứng vì cây làm việc đang có 3 lane khác chạy song song — stash là thao tác toàn-cây, rủi ro nuốt việc người khác lớn hơn giá trị của phép đối chứng.
|
||||
|
||||
**Hồi quy hàng xóm** (`ContractSigningPlanApprovalTests` + `ContractV2PipelineTests` + `ContractWorkflowServiceApproveV2Tests` + `ContractV2SchemaPersistenceTests` + `CreateContractFromSigningPlanTests`): `Failed: 0, Passed: 51`. Đây là tập lớp có đụng `CreateAwDefinitionCommandHandler` (type-10) hoặc seed workflow type-3 — tức đúng vùng bản vá có thể văng miểng.
|
||||
🔸 **KHÔNG chạy full-suite**: lane W2/W7 đang sửa `ContractFeatures.cs`/FE giữa chừng ⇒ số full-suite lúc này là snapshot dở (gotcha #68) và không phải số của tôi. Phán quyết full-suite = LEAD chạy sau khi mọi lane dừng tay.
|
||||
|
||||
## §4 — Build + kiểm scope
|
||||
|
||||
- `dotnet build SolutionErp.slnx` → **Build succeeded, 0 Warning(s), 0 Error(s)** (4.45s), chạy SAU byte cuối.
|
||||
- `git status --porcelain -- src/Backend/SolutionErp.Infrastructure/Persistence/Migrations` → **RỖNG** ⇒ 0-mig như yêu cầu.
|
||||
- File `src/**` duy nhất tôi đụng: `ApprovalWorkflowV2AdminFeatures.cs`. **0 chạm** `Contracts/ContractFeatures.cs` · `Contracts/Dtos/ContractDtos.cs` · `tests/**` · `fe-*/**` (các file đó `M` trong `git status` là của lane W2/W7/FE).
|
||||
- Không commit (em main commits).
|
||||
|
||||
== HẾT ==
|
||||
@ -0,0 +1,148 @@
|
||||
# sub-implementer-frontend-2 — W2-types + W7 server-filter (FE ×2 app)
|
||||
|
||||
> Lane: implementer-frontend · run `2026-08-10-S187-duyet-hd-tu-khkk` · S187
|
||||
> Ghi APPEND sau MỖI đơn vị việc (bài học S171: khung rỗng = CẦN nhưng KHÔNG ĐỦ).
|
||||
|
||||
## §0 — Đo đĩa TRƯỚC khi gõ (tiền-đề của đề bài vs hiện trạng)
|
||||
|
||||
| # | Tiền-đề trong đề bài | Đo được trên đĩa | Kết luận |
|
||||
|---|---|---|---|
|
||||
| 1 | "MyContractsPage.tsx + InboxPage.tsx **×2 app**" | `MyContractsPage.tsx` CHỈ có ở **fe-user** (`fe-user/src/pages/contracts/`); fe-admin không có, đối ứng là `ContractsListPage.tsx`. `InboxPage.tsx` CHỈ có ở **fe-user** (`fe-user/src/pages/InboxPage.tsx`, KHÔNG nằm trong `pages/contracts/`); fe-admin **không có trang inbox** nào (grep `/contracts/inbox` trong `fe-admin/src` = 0 hit) | 3 file đơn, KHÔNG phải 2 cặp ⇒ **SHA-pair identical bất khả thi cho trang** (lặp lại đúng phát hiện S167). Mirror đo bằng cách khác — xem §3 |
|
||||
| 2 | "param `type` CÓ SẴN `ContractsController.cs:23-27`" | ĐÚNG cho `GET /api/contracts` (`:25` `[FromQuery] ContractType? type`) và cả `GET /contracts/deleted` (`:34`). 🔴 **SAI cho `/contracts/inbox`**: `:41-43` `Inbox(CancellationToken ct)` → `new GetMyInboxQuery()` — record **KHÔNG THAM SỐ** (`ContractFeatures.cs:479`) | InboxPage **BỊ CHẶN** — gỡ client-filter = giết câm bộ lọc loại của Hộp thư. Xem FLAG-B |
|
||||
| 3 | "MỌI file sửa phải mirror SHA-IDENTICAL từng cặp" | Cặp `types/contracts.ts` **ĐÃ LỆCH TỪ BASELINE**: fe-admin 191 dòng `6d041dd5…` ⟂ fe-user 195 dòng `6431a571…`; delta = **bảng màu `ContractPhaseColor`** (fe-user đậm hơn theo yêu cầu anh 2026-06-16) + 4 dòng chú thích | Ép SHA bằng nhau = **phá UX anh đã chốt** ⇒ đổi phép đo: chứng **KHỐI THÊM byte-identical** bằng `diff` trước/sau (xem §3) |
|
||||
|
||||
## §1 — W2-types (2 file — cặp THẬT) ✅ XONG
|
||||
|
||||
File: `fe-admin/src/types/contracts.ts` · `fe-user/src/types/contracts.ts`.
|
||||
Thêm vào `ContractDetail` 3 field + 3 type mới `ContractSource` / `ContractWorkflowLevel` / `ContractWorkflowStep`
|
||||
(grep trước khi đặt tên: cả 3 định danh = **0 hit** ở cả 2 app ⇒ không đụng tên có sẵn).
|
||||
|
||||
Quyết định có lý-lẽ (không bịa, không mirror mù):
|
||||
|
||||
1. **`?` KÈM `| null`** — `undefined` (API/bundle chưa có W2, cache TanStack cũ) ⟂ `null` (BE nói "không có nguồn").
|
||||
Gộp 2 nghĩa = đúng bệnh *rỗng-vì-lọc ⟂ rỗng-vì-chưa-có-gì*.
|
||||
2. **Tên field cây/level lấy theo KHUÔN KHKK, KHÔNG theo chữ trong đề bài.** Đề bài viết
|
||||
"mỗi level có `approverUserId`/**`approverName`**/`order`", nhưng khuôn được chỉ đích danh
|
||||
(`khkk.ts:275-284` ⇄ BE `ContractSigningPlanFeatures.cs:131-140`) dùng **`approverFullName`**.
|
||||
Chứng cứ độc lập thứ 2: chính module HĐ đã dùng `approverFullName` cho V2 (`contracts.ts` —
|
||||
`ContractLevelOpinion.approverFullName`, BE `ContractDtos.cs:71+`). ⇒ chọn `approverFullName`.
|
||||
3. **`allowApproverFinalize` GIỮ (bắt buộc)** — spec `§2 W2(b)` viết "steps + levels + tên NV + **cờ per-level**".
|
||||
4. **`departmentId`/`departmentName` khai `?`** — có trong khuôn KHKK nhưng spec W2 KHÔNG nêu tên ⇒
|
||||
không hứa cứng một field payload có thể không mang (bài học `khkk.ts:403-407`).
|
||||
5. **`contractSource` (tên field) = FLAG-A** — xem §4.
|
||||
|
||||
**Chứng mirror (phép đo thay cho SHA-pair, vì cặp đã lệch từ baseline — §0#3):**
|
||||
|
||||
| Phép đo | Số |
|
||||
|---|---|
|
||||
| `sha256` KHỐI THÊM (từ `// ===== [W2 S187` tới hết, 3449 B utf-8) fe-admin | `51719cc8808d70f050e062859b5d5a7c1e3237066b1bdd811b39caf69501395f` |
|
||||
| `sha256` KHỐI THÊM fe-user | `51719cc8808d70f050e062859b5d5a7c1e3237066b1bdd811b39caf69501395f` — **BẰNG NHAU** |
|
||||
| `diff -u` 2 file SAU khi sửa | **đúng 1 hunk** = hunk bảng-màu có sẵn, nội dung y hệt baseline (`sha256` phần diff `7509a959afe86fa8e070cfbe5fae4a3fe3f9a465c09b413c2269108af7c66b0a`) |
|
||||
| **CONTROL-ÂM** (tiêm `approverFullName`→`approverFullNameX` vào bản COPY fe-user) | hunk 1 → **2**, sha khối đổi ⇒ phép đo CÓ RĂNG, không phải im-vì-câm |
|
||||
|
||||
Script đo: `scratchpad/mirror_types.py` (mirror, assert-anchor-duy-nhất, tự chết nếu vùng đích đã sửa) +
|
||||
`scratchpad/prove_mirror.py` (chứng + control-âm). Cả 2 NGOÀI repo.
|
||||
|
||||
## §2 — W7 server-filter
|
||||
(đang làm — append bên dưới)
|
||||
|
||||
## §3 — Chứng mirror + build
|
||||
(đang làm)
|
||||
|
||||
## §4 — FLAG treo cho lead/reviewer
|
||||
(đang làm)
|
||||
|
||||
---
|
||||
|
||||
## W7-FINISH (lượt nối S188)
|
||||
|
||||
> Lane trước chết giữa §2. Lượt này nối phần CÒN LẠI. Ghi APPEND sau mỗi đơn vị việc.
|
||||
|
||||
### §5 — Đo đĩa lại TRƯỚC khi gõ (xác nhận độc lập §0, KHÔNG tin ghi chép cũ)
|
||||
|
||||
| # | Tiền-đề lượt-nối | Đo được trên đĩa (lệnh) | Kết luận |
|
||||
|---|---|---|---|
|
||||
| 1 | "mirror sang `fe-admin/src/pages/contracts/MyContractsPage.tsx`" | `find fe-admin/src -iname 'MyContracts*'` = **0 hit**. `ls fe-admin/src/pages/contracts/` = `ContractCreatePage · ContractDetailPage · ContractsListPage · HardCopiesPage` | File ĐÍCH **KHÔNG TỒN TẠI**. Đối ứng admin của `/my-contracts` là `/contracts` → `ContractsListPage.tsx` (`fe-admin/src/App.tsx:82`). Tạo file mới = đẻ trang/route mới = **quyết định UX, không phải mirror** ⇒ áp cùng LOGIC vào `ContractsListPage.tsx`, KHAI RÕ cặp này vốn không mirror |
|
||||
| 2 | "`InboxPage.tsx` ×2 app trong `src/pages/contracts/`" | `fe-user/src/pages/InboxPage.tsx` (KHÔNG trong `pages/contracts/`) · fe-admin **0 trang inbox HĐ** (`grep -rn 'contracts/inbox' fe-admin/src` = 0 hit) | 1 file đơn, không phải cặp |
|
||||
| 3 | "param `type` sẵn `ContractsController.cs:23-27` ⇒ InboxPage gửi `?type=` được" | `:41-43` `Inbox(CancellationToken ct)` → `new GetMyInboxQuery()`; `ContractFeatures.cs:479` `public record GetMyInboxQuery : IRequest<...>;` = **KHÔNG THAM SỐ** (đo trên WORKING TREE, file này đang bị lane BE sửa) | 🔴 `:23-27` là param của `GET /api/contracts` (List), **KHÔNG** của `/contracts/inbox`. Gửi `?type=` lên inbox = BE **nuốt im lặng**; gỡ client-filter = **giết câm** bộ lọc loại của Hộp thư (7 leaf `Ct_*_Pending` → `/inbox?type=N`, `fe-user/src/components/Layout.tsx:150`) ⇒ **BLOCK item 2**, xem FLAG-B |
|
||||
| 4 | (mới — không có trong đề bài) Inbox có bị cắt trang không? | `ContractFeatures.cs:550` `return await q.Take(100).ToListAsync(ct);` | 🔴 Inbox **CÓ cắt 100** ⇒ bệnh "loại hiếm biến mất im lặng" là **THẬT** ở Hộp thư, nhưng **KHÔNG vá được từ FE**. Cần BE thêm param (lane `implementer-backend`) |
|
||||
| 5 | BE lọc `type` trước hay sau phân trang? | `:327` `if (request.Type is not null) q = q.Where(x => x.c.Type == request.Type)` → `:340` `CountAsync` → `:342` `Skip/Take` | Lọc TRƯỚC ⇒ chuyển lên server sửa luôn cả `total` (đếm) lẫn lát cắt trang |
|
||||
|
||||
**Hash baseline (`git rev-parse HEAD:<path>`) — chứng cặp nào vốn mirror:**
|
||||
|
||||
| File | HEAD | Nhận định |
|
||||
|---|---|---|
|
||||
| `fe-admin/src/types/contracts.ts` | `6d041dd53541` | ⟂ **LỆCH TỪ BASELINE** (bảng màu `ContractPhaseColor`) ⇒ ép SHA bằng = phá UX anh chốt 2026-06-16 |
|
||||
| `fe-user/src/types/contracts.ts` | `6431a57147bb` | ⟂ (như trên) |
|
||||
| `fe-user/src/pages/contracts/MyContractsPage.tsx` | `afbc92cd376d` | **KHÔNG CÓ ĐÔI** (admin không có file này) |
|
||||
| `fe-admin/src/pages/contracts/ContractsListPage.tsx` | `0f08bda5c486` | **KHÔNG CÓ ĐÔI** (user không có file này) |
|
||||
| `fe-user/src/pages/InboxPage.tsx` | `6474c55c0cff` | **KHÔNG CÓ ĐÔI** (admin không có trang inbox HĐ) |
|
||||
|
||||
⇒ Trong 6 hash đề bài đòi, **chỉ 1 cặp thật** (`types/contracts.ts`) và cặp đó **vốn đã lệch** ⇒ phép đo mirror = **sha256 KHỐI THÊM** (§1) + `diff` số hunk, KHÔNG phải hash cả file.
|
||||
|
||||
### §6 — Việc đã làm ở lượt nối (2 file, đều FE)
|
||||
|
||||
**(a) `fe-admin/src/pages/contracts/ContractsListPage.tsx`** — áp cùng LOGIC W7 (KHÔNG phải mirror byte, xem §5#1):
|
||||
|
||||
1. Chặn rác `type` trước khi lên dây — `Number.isFinite(parsedType) && parsedType > 0`, bê nguyên khuôn
|
||||
`HardCopiesPage.tsx:66-68` **cùng thư mục cùng app** (không đẻ khuôn thứ 2).
|
||||
2. `params.type = typeFilter ?? undefined` (axios bỏ hẳn key khi null ⇒ 0 breaking cho "tất cả loại").
|
||||
3. Gỡ `items.filter(c => c.type === typeFilter)` + gỡ luôn cái vỏ `queryFn` block-body → arrow trả `.data`.
|
||||
4. `queryKey` **KHÔNG đổi** — `typeFilter` đã nằm sẵn trong key object (`:52` baseline) ⇒ đổi nguồn lọc
|
||||
không cần thêm trục; khai rõ để người sau khỏi tưởng bị sót.
|
||||
|
||||
🔴 **Trang admin nặng hơn trang user 2 lỗi nữa mà đề bài không nêu** (client-filter SAU khi cắt trang 30):
|
||||
`{total}` ở header đọc `res.data.total` = tổng **chưa lọc** ⇒ badge nói dối khi có `?type=`; và phân trang
|
||||
`Math.ceil(total/30)` tính theo tổng chưa lọc ⇒ có trang **rỗng hoàn toàn**. BE lọc trước `CountAsync`
|
||||
(`:327`→`:340`→`:342`) nên chuyển lên server vá cả 3 cùng lúc, 0 dòng code thêm cho phần đếm.
|
||||
|
||||
**(b) `fe-user/src/pages/InboxPage.tsx`** — 🔴 **CHỈ THÊM COMMENT, 0 đổi hành vi** (lý do §5#3/#4).
|
||||
Không gỡ client-filter, không gửi `?type=`, không đụng `queryKey`. Ghi thẳng vào file vì để đĩa TRỐNG thì
|
||||
lượt sau đọc lại đúng tiền-đề sai của đề bài rồi "làm nốt" = hỏng câm. Comment nêu đủ: endpoint nào chặn,
|
||||
dòng nào chứng, nợ `Take(100)` còn treo, và điều kiện để mở khoá.
|
||||
|
||||
### §7 — Chứng (mọi khẳng định đều có control)
|
||||
|
||||
| Phép đo | Kết quả | Control |
|
||||
|---|---|---|
|
||||
| Client-filter còn sót? `grep 'items.filter(c => c.type'` **lọc bỏ dòng `//`** | **0** hit code sống ×2 file | **CONTROL-DƯƠNG**: cùng regex + cùng discriminator trên `HEAD` = **1 hit mỗi file** ⇒ thước bắt được, không phải im-vì-câm. (Không lọc comment thì ra 2 — chính là *bẫy tự-trích-dẫn*: comment lineage chứa nguyên văn dòng đã gỡ) |
|
||||
| `types/contracts.ts` khối THÊM có byte-identical ×2 app? | anchor `// ========= [W2 S187` → hết file: **4032 B, sha256 `a718de0ab89051e3…` BẰNG NHAU** | **CONTROL-ÂM**: copy fe-user ra ngoài repo, `sed approverFullName→approverFullNameX` ⇒ sha đổi `4bfec6b9…` **và** hunk 1→**3**. Repo còn nguyên (`git diff --stat` vẫn 61 insertions) |
|
||||
| Hai file `types` có đẻ thêm chỗ lệch nào không? | `diff -u` = **1 hunk**, y hệt **1 hunk của HEAD** ⇒ W2 thêm **0 lệch mới** | so trực tiếp với baseline `HEAD` (không tự so với chính mình) |
|
||||
| `npm run build` fe-admin | **EXIT 0** — 1964 module, 51.07s | warning `chunk >500kB` + `INEFFECTIVE_DYNAMIC_IMPORT realtime.ts` + `PLUGIN_TIMINGS` = **PRE-EXISTING** |
|
||||
| `npm run build` fe-user | **EXIT 0** — 1953 module, 13.26s | (như trên) |
|
||||
| Có chạm BE/tests/Migrations không? | `git status -- src tests` = đúng 4 file **của lane khác** (`ApprovalWorkflowV2AdminFeatures.cs` · `ContractFeatures.cs` · `ContractDtos.cs` · `UpdateAwDefinitionTests.cs`), lane này **0 lệnh ghi** vào đó | scope FE: đúng **5 file M**, 0 file lạ, 0 `dist/` lọt |
|
||||
|
||||
> ⚠️ Số `a718de0a…` KHÁC số `51719cc8…` ở §1 là **đúng, không mâu thuẫn**: hai lượt cắt khối từ 2 mốc khác
|
||||
> nhau (3449 B vs 4032 B). Điều có nghĩa là **cùng một luật cắt áp cho cả 2 file thì hash bằng nhau** — cả
|
||||
> hai lượt đều thoả. Đừng đem 2 số này so nhau.
|
||||
|
||||
### §8 — Hash 6 ô đề bài đòi (`git hash-object`, worktree sau sửa)
|
||||
|
||||
| # | Đường dẫn đề bài | Hash |
|
||||
|---|---|---|
|
||||
| 1 | `fe-admin/src/types/contracts.ts` | `128080a1a0b4760a5722754ef6a500bd4369e41e` |
|
||||
| 2 | `fe-user/src/types/contracts.ts` | `d54aa909de255d3eecebd49094405b4c40bd2036` |
|
||||
| 3 | `fe-admin/src/pages/contracts/MyContractsPage.tsx` | 🔴 **KHÔNG TỒN TẠI** → đối ứng `ContractsListPage.tsx` = `144c1458ace02c3cb08c0f46662a4e88de5ea462` |
|
||||
| 4 | `fe-user/src/pages/contracts/MyContractsPage.tsx` | `cd03de6701ebfde2d24247b75a47308a29f16a21` |
|
||||
| 5 | `fe-admin/src/pages/contracts/InboxPage.tsx` | 🔴 **KHÔNG TỒN TẠI** (fe-admin không có trang hộp-thư HĐ) |
|
||||
| 6 | `fe-user/src/pages/InboxPage.tsx` | `1a20cd4e32a7dec47206875a04c8e05a8abf3dd3` (chỉ comment) |
|
||||
|
||||
**Cặp mirror thật = 1** (`types/contracts.ts`) và nó **lệch từ baseline có chủ đích** ⇒ chứng bằng khối-thêm
|
||||
(§7), KHÔNG bằng hash cả file. 4/6 ô còn lại là **file đơn**, hash chỉ để truy vết, không có "cặp phải khớp".
|
||||
|
||||
### §9 — FLAG treo (lead/reviewer xử)
|
||||
|
||||
- **FLAG-A** (từ §1, còn treo): tên field `contractSource` — chờ lane BE chốt tên trong `ContractDtos.cs`.
|
||||
- **FLAG-B (MỚI, chặn W7 một nửa):** `/contracts/inbox` **không nhận `type`**. Muốn Hộp thư lọc loại đúng
|
||||
khi >100 dòng thì **lane `implementer-backend`** phải: thêm `[FromQuery] ContractType? type` vào
|
||||
`ContractsController.cs:41-43` + đổi `GetMyInboxQuery` (`ContractFeatures.cs:479`) thành record CÓ tham số
|
||||
+ `.Where(c => c.Type == type)` **trước** `q.Take(100)` (`:550`). Xong thì FE bê nguyên khuôn
|
||||
`MyContractsPage` sang (nhớ **thêm `type` vào `queryKey`** — key hiện là `['inbox']` trống).
|
||||
- **FLAG-C:** `urlPendingMe` ở `ContractsListPage` nằm trong `queryKey` nhưng **không gửi lên BE và không lọc
|
||||
gì** — `?pendingMe=1` hiện chỉ đổi CHỮ tiêu đề (`:77`). Tức 1 ô cache trùng vô ích + nhãn "Chờ duyệt" hứa
|
||||
nhiều hơn thứ nó làm. **Có sẵn từ baseline, ngoài phạm vi W7 ⇒ tôi KHÔNG chạm.**
|
||||
- **FLAG-D:** mọi số dòng BE tôi trích (`:25` `:41-43` `:327` `:340` `:342` `:479` `:550`) đo trên
|
||||
**working tree đang bị lane BE sửa song song** ⇒ có thể trôi. Comment nào cũng kèm **tên ký hiệu** để
|
||||
dò lại; reviewer đo lại theo tên, đừng đo theo số.
|
||||
|
||||
== HẾT ==
|
||||
@ -0,0 +1,171 @@
|
||||
# SUB-INVEST-BE — Module "Duyệt Hợp đồng" (GĐ3) kế thừa từ KHKK — YC-023
|
||||
|
||||
> Run `2026-08-10-S187-duyet-hd-tu-khkk` · vai `investigator-codebase` · ENGINE `/fable-real` (H21 lệnh-A, single deep-pass, propose-only) · lăng kính **BE**.
|
||||
> Đề bài: đề xuất CÁCH LÀM (không code) — cấu trúc BE tương tự KHKK + PE, quy trình duyệt **KHÁC NHAU theo LOẠI hợp đồng**, phiếu **kế thừa từ KHKK đã duyệt**.
|
||||
> Mọi claim kèm file:line đọc THẬT trong phiên này (2026-08-10). Câu nghiệp-vụ = CHỜ-ANH, không đoán.
|
||||
> File ghi TỪNG MỤC trong lúc điều tra (chống #53 — artifact đổ-ruột-liên-tục).
|
||||
|
||||
---
|
||||
|
||||
## §0 Phương pháp + phạm vi đọc
|
||||
|
||||
Đã đọc thật (không trích trí nhớ): `Contract.cs` · `ContractPhase.cs` · `ContractSigningPlan.cs` · `ContractSigningPlanLine.cs` · `ContractSigningPlanPhase.cs` · `ApprovalWorkflow.cs` (3 entity V2 + enum) · `CreateContractFromSigningPlanFeatures.cs` (bridge K7 trọn 251 dòng) · `ContractWorkflowService.cs` (:1-120 + grep V2) · `docs/STATUS.md` dòng CURRENT. Còn lại đọc ở các mục dưới (mỗi mục ghi ngay khi xong).
|
||||
|
||||
**Ground-truth mở màn (2 con số cần chỉnh so với brief):**
|
||||
- Brief ghi "Mig 71" — đĩa có **Mig 72** `20260806070242_AddMaHangMucToContractSigningPlanLines` (S177, denorm `MaHangMuc` cặp với `TenHangMuc` — `ContractSigningPlanLine.cs:29-36`). Điều tra này lấy Mig 72 làm mốc.
|
||||
- STATUS dòng sống S185: 668 test · Policies 452 · Menu keys 113 (`docs/STATUS.md:6`).
|
||||
|
||||
---
|
||||
|
||||
## §1 Q1 — Tái dùng được gì NGAY: Contract V2 wire tới đâu
|
||||
|
||||
### 1a. Contract V2 = ĐÃ WIRE ĐỦ VÒNG DUYỆT (approve-side), từ S29
|
||||
|
||||
| Mảnh | Bằng chứng | Trạng thái |
|
||||
|---|---|---|
|
||||
| Pin V2 trên phiếu | `Contract.cs:23` `ApprovalWorkflowId` (Mig 32) + `:46` `CurrentApprovalLevelOrder` — con-trỏ ĐÔI y PE | ✅ có |
|
||||
| Service duyệt V2 | `ContractWorkflowService.cs:111-116` branch `ApprovalWorkflowId is Guid` → `ApproveV2Async` (`:230`, comment `:218` "Mirror PE :446-634") | ✅ có |
|
||||
| Ý kiến cấp duyệt | `Contract.cs:56` `List<ContractLevelOpinion>` (Mig 33, UPSERT từ ApproveV2Async) | ✅ có |
|
||||
| State machine 5 trạng thái | `ContractPhase.cs:14-28`: DangSoanThao=2 · ChoDuyet=10 · DaPhatHanh=9 (terminal + gen mã) · TraLai=98 · TuChoi=99; 7 giá trị LEGACY giữ cho data cũ (`:16-23`) | ✅ có |
|
||||
| Trình/gửi lại | `ContractWorkflowService.cs:70-102` — 2 entry (Nháp ∨ TraLai) → ChoDuyet, reset con-trỏ, vai nới 4 vế Drafter∨DeptManager∨Procurement∨creator (`:84-88`, W6 S161) | ✅ có |
|
||||
| Codegen RG-001 | gen tại bridge/tạo (`CreateContractFromSigningPlanFeatures.cs:212`) — HĐ từ cầu K7 có mã NGAY lúc tạo | ✅ có |
|
||||
| Bridge từ GĐ2 | `CreateContractFromSigningPlanFeatures.cs` TOÀN BỘ (K7, commit `aaed6992`) — chi tiết §3 | ✅ có |
|
||||
|
||||
### 1b. `ApplicableType` KHÔNG phân theo ContractType — đây là CÁI THIẾU đúng tâm đề bài
|
||||
|
||||
- Enum `ApprovalWorkflowApplicableType` (`ApprovalWorkflow.cs:53-71`): `Contract = 3, // HĐ general (any ContractType)` (`:57`) — **MỘT giá trị cho cả 7 loại HĐ**. KHKK có slot riêng `ContractSigningPlan = 10` (`:70`).
|
||||
- Chính bridge K7 tự khai gap này thành chữ: *"`ApprovalWorkflow.ApplicableType` = Contract(3) là 'HĐ general (any ContractType)' ⇒ **KHÔNG suy được từ ContractType, người bấm phải chọn trong dialog**"* (`CreateContractFromSigningPlanFeatures.cs:23-25`); command bắt buộc client truyền `ApprovalWorkflowId` (`:46-49`, `:54`).
|
||||
- Guard pin ở bridge chỉ check 4 vế: tồn tại ∧ `ApplicableType==Contract` ∧ `IsActive` ∧ `IsUserSelectable` (`:134-148`) — **KHÔNG có vế nào so quy trình với `ContractType`** ⇒ hôm nay user có thể pin quy trình "dành cho thầu phụ" vào HĐ nguyên tắc mà máy không cản (vì máy không biết quy trình nào dành cho loại nào).
|
||||
- Đối chiếu: máy per-type ĐANG TỒN TẠI ở **V1** — `WorkflowDefinition` có cột `ContractType` và bridge vẫn pin V1 active-per-type song song (`CreateContractFromSigningPlanFeatures.cs:163-166`). Tức là "quy trình theo loại HĐ" là khái niệm V1 đã có, V2 làm rơi mất khi gộp về `Contract=3`.
|
||||
|
||||
**Kết luận Q1:** máy duyệt V2 cho HĐ tái dùng NGAY 100% (service + con-trỏ + LevelOpinions + state machine + bridge); mảnh thiếu DUY NHẤT đúng đề bài = **quan hệ "quy trình ↔ loại HĐ" ở tầng V2** (chọn đúng quy trình theo loại + rào không cho pin chéo loại) — và tầng hiển thị cho approver (xem §1c bổ sung sau khi soi inbox).
|
||||
|
||||
### 1c. ĐÍNH CHÍNH điều tra cũ S156: tầng hiển thị approver V2 ĐÃ VÁ @W6 S161
|
||||
|
||||
- Kết luận S156 *"GetEligiblePhases + inbox 100% LEGACY ⇒ approver V2 mù HĐ ChoDuyet"* **HẾT HIỆU LỰC**: `GetMyInboxQuery` nay V2-aware — `ResolveV2InboxIdsAsync` (`ContractFeatures.cs:559-593`, in-memory join CÓ CHỦ ĐÍCH vì `CurrentWorkflowStepIndex` là INDEX 0-based ≠ `Order`, giữ OR-of-N `:588-589`), điều kiện thoát sớm xét CẢ 2 nguồn (`:536-538`). List + Detail có vế V2 (`:309-316` + `:623-628` membership-403); ListDeleted CỐ Ý KHÔNG ghép vế V2 (`:442-447` — chống rò HĐ xóa mềm).
|
||||
- 3 cái CÒN thiếu thật sự (ngoài per-type §1b):
|
||||
1. **Pin V2 ở Create là OPTIONAL** (`ContractFeatures.cs:37` `Guid? ApprovalWorkflowId = null`) — HĐ tạo không pin rơi về V1/policy hardcode. Bridge K7 thì ÉP pin (`CreateContractFromSigningPlanFeatures.cs:63` NotEmpty) — HĐ sinh từ GĐ2 luôn V2. Nếu GĐ3 chỉ đi đường bridge thì gap này không cắn; đường `POST /api/contracts` tay vẫn hở.
|
||||
2. **Guard pin ở Create thiếu 2 vế `IsActive ∧ IsUserSelectable`** (`:79-87` chỉ check tồn-tại + type) — bridge K7 đã bịt cho đường nó (`:141-148`, vá-3), đường Create thường chưa.
|
||||
3. **KHÔNG có đường re-pin quy trình trên HĐ Nháp/Trả-lại**: `UpdateContractDraftCommand` (`ContractFeatures.cs:136-145`) không có field `ApprovalWorkflowId` — phiếu pin nhầm quy trình chỉ còn nước xóa-tạo-lại. Đối chiếu KHKK: có re-pin ở UpdateDraft + rào nhóm 3 cửa, và message rào chỉ dẫn đúng đường đó (`ContractSigningPlanWorkflowGuards.cs:76-77` *"vào phiếu (Nháp/Trả lại) đổi lại quy trình đúng nhóm trước khi trình"*). GĐ3 per-type SẼ CẦN cửa này (pin sai loại → phải sửa được).
|
||||
|
||||
---
|
||||
|
||||
## §2 Q2 — Map "quy trình KHÁC NHAU per-LOẠI-HĐ" vào máy hiện có
|
||||
|
||||
### 2a. Bẫy chữ "LOẠI hợp đồng" — 2 nghĩa, phải chốt trục trước khi build
|
||||
|
||||
Spec-memory SOL-CCM-SP-002 (`.claude/auto-memory/project_sol_ccm_sp002_signing_matrix.md:41`) cảnh báo tường minh: *"Từ 'loại HĐ' trong lời anh mang 2 nghĩa"*:
|
||||
- **Nghĩa A — 7 `ContractType` hệ thống** (Thầu phụ · Giao khoán · NCC · Dịch vụ · Mua bán · Nguyên tắc NCC · Nguyên tắc DV): khớp chữ YC-023 ("thầu phụ, nguyên tắc, …") + sidebar GĐ3 đã fan-out 7 mục (memory `:45`, anh xác nhận hình prod).
|
||||
- **Nghĩa B — 8 nhóm duyệt N1-N8** nén từ 86 dòng danh mục SP-002 (chuỗi ký nháy + người ký chính đi theo trục NÀY, không theo 7 type): đã ăn vào GĐ2 (`ContractCatalogEntry.ApprovalGroup` 1..8 → `ContractSigningPlan.ApprovalGroup` → 8 workflow `KHKK-N{n}`).
|
||||
⇒ **CHỜ-ANH #1**: GĐ3 phân quy trình theo trục nào — 7 ContractType (mặc định theo chữ YC-023) hay 8 nhóm SP-002 (như GĐ2), hay lai (số trạm theo nhóm, người ký chính theo ngưỡng UQ)? Chọn sai trục = dựng 7 workflow rồi phát hiện ma trận ký cần 8 nhóm. Điều tra dưới đây viết theo Nghĩa A, và cả 3 phương án đều đổi trục được vì discriminator là tham số.
|
||||
|
||||
### 2b. Khuôn per-group KHKK — cái ĐÃ CHẠY, mirror được gần nguyên khối
|
||||
|
||||
Cơ chế 4 mảnh (đều đọc thật):
|
||||
1. **Discriminator trên phiếu**: `ContractSigningPlan.ApprovalGroup` int 1..8 (`ContractSigningPlan.cs:41`, Mig 71 backfill=1).
|
||||
2. **Họ workflow cùng type khác Code**: 8 bản `KHKK-N{n}` CÙNG `ApplicableType=10`, seed idempotent **PER-CODE** (`DbInitializer.cs:540-542` — CỐ Ý né khuôn check-per-type vì *"check theo type sẽ chặn 7 nhóm còn lại"*; `:607-625`); resolve người duyệt theo **EMAIL cứng 11 người**, thiếu user → bỏ slot + warning, workflow 0-bước vẫn tạo (`:544-548`, `:587-600`).
|
||||
3. **Rào khớp nhóm DÙNG CHUNG 3 cửa** (tạo / sửa-nháp re-pin / trình): `ContractSigningPlanWorkflowGuards.EnsureWorkflowGroupMatchAsync` (`ContractSigningPlanWorkflowGuards.cs:65-78`), parse nhóm từ Code bằng regex 1-nhà-duy-nhất (`:44-45`), code NGOÀI họ cho qua CÓ CHỦ ĐÍCH (`:56-60`). Sinh ra @S182 W1b vì bản 1-cửa bị bypass 2-bước (`:9-12`).
|
||||
4. **Vá admin-side S182 ĐÃ LAND**: normalize Code tại đường ghi (`ApprovalWorkflowV2AdminFeatures.cs:348` — chặn `khkk-n1` CI_AS-collision, `:335-347`); deactivate-khi-tạo-version chuyển **per-CODE riêng cho type-10** (`:356-376`).
|
||||
|
||||
### 2c. 🔴 2 quả bom per-type CÒN NGUYÊN cho type-3 nếu mirror họ-Code
|
||||
|
||||
- **Bom 1 — deactivate per-TYPE**: nhánh per-Code ở `ApprovalWorkflowV2AdminFeatures.cs:370-376` hard-wire `typeEnum == ContractSigningPlan`; **type-3 vẫn đi nhánh `:374-376` tắt IsActive per-TYPE**. Để nguyên mà seed 7 workflow `HD-*` cùng type-3 ⇒ admin tạo version mới cho MỘT loại HĐ là **tắt active CẢ 7 loại** (đúng bug S182 của KHKK tái sinh). Comment `:363` còn ghi *"các loại khác: per-TYPE như cũ — 1 active/type là bất biến của chúng"* — bất biến này PHẢI gỡ cho type-3 trước khi seed họ.
|
||||
- **Bom 2 — overview Active-đơn**: `AwTypeSummaryDto` lấy `versions.FirstOrDefault(v => v.IsActive)` per type (`:205-215`) — 7 active cùng type-3 sẽ hiện 1 giấu 6 (đúng bệnh "giấu 7/8" S182). DTO/panel cần nhánh family cho type-3 như type-10 đã xử.
|
||||
|
||||
### 2d. 3 phương án + khuyến nghị xếp-bằng-chứng
|
||||
|
||||
| | PA-A: mirror KHKK họ-Code (0-mig) | PA-B: cột thật trên ApprovalWorkflows (1-mig) | PA-C: giữ chọn-tay (0 đổi máy) |
|
||||
|---|---|---|---|
|
||||
| Cách | Họ Code `HD-{...}` ×7 cùng type-3; discriminator phiếu = `Contract.Type` **CÓ SẴN** (KHKK phải đẻ cột Mig 71 — HĐ khỏi); guard mirror `EnsureWorkflowGroupMatchAsync` (parse type từ Code, áp 2 site pin hiện có: Create + bridge, + site re-pin mới §1c-3); seeder mirror `SeedKhkkGroupWorkflowsAsync` per-Code | Thêm cột nullable phân-nhánh-trong-type (generic, vd SubKey int?) trên `ApprovalWorkflows`; type-3 ghi ContractType; guard so CỘT; deactivate per (Type, SubKey); về sau migrate 8 row `KHKK-N{n}` vào cùng cột = gỡ luôn regex | FE lọc gợi ý theo naming-convention, không rào máy |
|
||||
| Máy rộng UI hẹp (S164) | Trung bình — máy đọc TÊN (regex họ thứ 2; guards file `:28-32` tự cảnh báo "chép lần nữa = 3 bản chuỗi rời nhau, sửa 1 quên 2"; đã có 1 vụ case-collision phải vá 2 tầng) | ĐÚNG NHẤT — quan hệ thành DATA, schema tổng quát phủ cả KHKK lẫn HĐ, UI chỉ bung ở type-3 | SAI — không có máy |
|
||||
| Chi phí | Thấp: 0-mig, khuôn qua UAT KHKK; NHƯNG bắt buộc kèm gỡ Bom 1 + Bom 2 (§2c) | Trung: mig 3-file + đụng bảng dùng chung 10 module (Designer/AdminFeatures/DTO blast) + vẫn phải gỡ Bom 1/Bom 2 theo chiều cột | ~0 |
|
||||
| Rủi ro chính | Tên = hợp đồng ngầm; drift 2 họ regex | Blast radius bảng chung; backfill semantics | Pin chéo loại lọt như hôm nay — KHÔNG đạt đề bài |
|
||||
|
||||
**Xếp bằng chứng (không quyết thay anh):** PA-C loại. PA-A rẻ nhất, có tiền lệ sống; PA-B đúng "máy rộng" nhất và trả nợ kỹ thuật regex-2-họ. Đường giữa khả thi: **PA-A trước (ship nhanh, 0-mig) + PA-B là bước unify sau** khi 2 họ cùng sống — nhưng chọn A/B/lai đổi chi phí dài hạn ⇒ **CHỜ-ANH #2**.
|
||||
|
||||
---
|
||||
|
||||
## §3 Q3 — Kế thừa KHKK→HĐ: bridge K7 hiện chảy gì, thiếu gì, pipeline nối thế nào
|
||||
|
||||
### 3a. Bridge K7 (commit `aaed6992`, 2026-08-01, 13 file +2241 dòng; BE = controller +34 · Features +23 · bridge 251 · DbInitializer +90 · 3 test-file +1053) — dòng chảy HIỆN TẠI
|
||||
|
||||
**Rào nổ trước khi chảy** (`CreateContractFromSigningPlanFeatures.cs`): plan phải `DaDuyet` (`:97-99`) · lines ∈ plan sau Distinct (`:101-109`) · 1 HĐ = 1 NCC (`:111-115`, liên-danh = N lần bấm) · per-line `ApprovedAmount` NOT NULL — CẤM fallback `ProposedAmount` (`:119-127`) · line chưa có HĐ (`:128-129`) · workflow pin 4 vế (`:132-148`).
|
||||
|
||||
**Chảy sang HĐ**: SupplierId (line) · ProjectId + DepartmentId (plan `:191-192`) · `GiaTri = Σ ApprovedAmount` đúng tập line chọn (`:173`) · TenHopDong auto "TenGoiThau — hạng mục" clamp 500 (`:175-184`) · NoiDung = pe.MoTa (`:196`) · BudgetManual = "NS tham chiếu KHKK {mã}" + Σ PeReferenceAmount (`:199-200`) · mã HĐ RG-001 gen NGAY (`:212`) · pin V1 active-per-type (`:163-166`,`:201`) + pin V2 user-chọn (`:202`) · SlaDeadline (`:205-206`) · changelog 2 phía (`:216-235`) · **`Line.ContractId` = nguồn thật** (`:238`) + `pe.ContractId ??=` fallback hiển thị có-chiều (`:240-246`).
|
||||
|
||||
### 3b. KHÔNG chảy — đây là danh sách gap cho "phiếu duyệt HĐ kế thừa từ KHKK"
|
||||
|
||||
1. **`ContractType` chọn TAY trong dialog** (`:53` request param). Danh mục SP-002 KHÔNG mang map → 7 ContractType: `ContractCatalogEntry` chỉ có `GroupCode/ApprovalGroup/SignerRole/InitialsChain` (`ContractCatalogEntry.cs:15-24`). Spec-memory `:17`: hạng mục duyệt xong → *"đưa vào loại hợp đồng gì"* = bước NGƯỜI quyết. ⇒ workflow auto-theo-loại chỉ đúng khi loại đúng, mà loại đang là input tay. **CHỜ-ANH #3**: có thêm cột gợi-ý loại HĐ vào danh mục SP-002 (map catalog→ContractType, autofill dialog) hay giữ chọn tay thuần?
|
||||
2. **Hạng mục per-line KHÔNG vào ruột HĐ**: bridge không sinh row nào trong 7 bảng Details per-type (`Contract.cs:63-69`) — tên hạng mục chỉ thành chuỗi TenHopDong. GĐ3 = "bàn điều khoản, không bàn hạng mục nữa" (spec-memory `:17`) nên có thể ĐÚNG thiết kế; nhưng phụ lục 3.5 SP-002 (bảng KL/đơn-giá/thành-tiền + VAT) nếu phải render ở GĐ3 thì cần line-level. **CHỜ-ANH #4**.
|
||||
3. **`ApprovalGroup` (N1-N8) không chảy** — HĐ không giữ nhóm nguồn; nếu CHỜ-ANH #1 chọn trục nhóm (hoặc cần báo cáo theo nhóm xuyên GĐ) thì HĐ cần cột này (1 AddColumn) hoặc đọc ngược qua `Line.ContractId → plan.ApprovalGroup` (0-mig, đắt query).
|
||||
4. **Dossier/attachment KHKK không copy** — truy ngược được qua Line; hồ sơ GĐ2 đã chốt nên để-nguyên có lý. Ghi nhận, không đề xuất đổi.
|
||||
|
||||
### 3c. Pipeline 4-GĐ nối trạng thái — KHÔNG cần phase mới, KHÔNG cần entity phiếu mới
|
||||
|
||||
`KHKK DaDuyet` (`ContractSigningPlanPhase.cs:12`) → bấm bridge per-NCC → `Contract DangSoanThao=2` (`:189`) → Drafter/Procurement trình (`ContractWorkflowService.cs:70-102`, vai 4 vế `:84-88`) → `ChoDuyet=10` → duyệt V2 từng Bước×Cấp (`:111-116` → `ApproveV2Async :230`) → `DaPhatHanh=9` terminal (`ContractPhase.cs:24`) → GĐ4 = attachment `SealedCopy` (`ContractFeatures.cs:355` `hasSealedCopy`). **"Phiếu duyệt HĐ" GĐ3 = chính `Contract`** — đã có đủ bộ ruột phiếu (Approvals + LevelOpinions + Changelogs + Comments + Attachments, `Contract.cs:48-58`) tương đương 7 bảng KHKK; dựng entity phiếu mới = chế thêm, phạm luật "GIỐNG NHAU — CHỈ KHÁC FORM — ĐỪNG CHẾ THÊM" (spec-memory `:57-59` + chính bridge tự trích luật này `:17`).
|
||||
|
||||
## §4 Q4 — Schema delta TỐI THIỂU
|
||||
|
||||
**Đường mặc định (PA-A + "Contract = phiếu GĐ3") = 0 MIGRATION.** Mọi thứ cưỡi máy có sẵn: họ workflow = data-op seeder idempotent per-Code (khuôn `DbInitializer.cs:540-542`) · guard/bomb-fix/re-pin = code thuần · phiếu + ruột phiếu + con-trỏ + LevelOpinions đã có từ Mig 32/33. Ground-truth đo hôm nay (2026-08-10, sqlcmd): **Dev VÀ Design đều đã áp Mig 72** `20260806070242_AddMaHangMucToContractSigningPlanLines` — drift committed-but-unapplied S182 ĐÃ HẾT, nền sạch để build.
|
||||
|
||||
**Mig phát sinh CÓ ĐIỀU KIỆN** (mỗi cái 1 AddColumn nhỏ, 3-file rule bắt buộc, Down reversible):
|
||||
- (a) Nếu chọn **PA-B**: +1 cột phân-nhánh nullable trên `ApprovalWorkflows` (+ backfill optional parse `KHKK-N%` → cột để unify; gotcha #64 — backfill chạy THẬT lần đầu trên prod, SQLite test bỏ qua migration).
|
||||
- (b) Nếu **CHỜ-ANH #1** chọn trục nhóm / cần báo-cáo nhóm xuyên GĐ: +1 cột `Contract.ApprovalGroup` (khuôn Mig 71: AddColumn + backfill đọc ngược `Line.ContractId → plan.ApprovalGroup`).
|
||||
- (c) Nếu **CHỜ-ANH #3** duyệt: +1 cột gợi-ý-loại-HĐ trên `ContractCatalogEntries` + cập nhật seed 86 dòng.
|
||||
|
||||
Đo hiện trạng Dev làm mốc so sánh: `ApplicableType=3` có ĐÚNG 1 workflow `QT-HD-V2-001` v1 active+selectable (sqlcmd hôm nay) — mọi HĐ V2 hiện đi chung 1 quy trình, khớp chẩn đoán §1b.
|
||||
|
||||
---
|
||||
|
||||
## §5 Q5 — AUTHZ: hiện trạng verify hôm nay + 3 phương án — 🔴 TOÀN BỘ CHỜ-ANH (#5)
|
||||
|
||||
**Hiện trạng (đọc lại nguyên trạng 2026-08-10, không đổi so S156):**
|
||||
- `ContractsController.cs:13` `[Authorize]` TRẦN class-level; per-action policy DUY NHẤT là `Contracts.Read` trên GET `/deleted` (`:31-32`). **22 endpoint GHI không policy per-action** (đếm grep hôm nay: POST `:49,:64,:71,:88` + 7 POST details `:126-168` + PUT `:56` + 7 PUT details `:182-224` + DELETE `:78,:113,:175`).
|
||||
- `ContractWorkflowService.cs:48-66`: nhánh **Reject chạy TRƯỚC MỌI GUARD** — không check vai, không check phase ⇒ user đăng nhập bất kỳ Từ-chối/Trả-lại được bất kỳ HĐ nào kể cả `DaPhatHanh` (gotcha #82). Menu HĐ đang public 13/13 role **chủ-đích anh** (STATUS `:23` ⑦-risk: *"cho hiển thị hết để mọi người góp ý"*), anh chốt 07-28 *"chỗ hợp đồng cứ từ từ"* + tái khẳng định @S159.
|
||||
- Mâu thuẫn mới: GĐ3 = mở luồng GHI Contract cho nhiều người dùng thật ⇒ cái treo cũ giờ nằm ngay giữa module mới. Tiền lệ tốt đã có: KHKK controller 2 tầng (class `KeHoachKyKet.Read` + action `.Create/.Update/.Delete` — `ContractSigningPlansController.cs:15-17,:29-286`) và bridge **hội-2-khóa** class `KeHoachKyKet.Read` ∧ action `Contracts.Create` (`:299-307`, AND-cộng-dồn của ASP.NET). Test khuôn sẵn từ `aaed6992`: `KhkkBridgeAuthzTests.cs` (245 dòng) + `AuthorizePolicyRegressionTests.cs`.
|
||||
|
||||
| | Đ1 — per-action policy TRỌN ContractsController (mirror KHKK) | Đ2 — đóng ĐÚNG MẶT TẤN CÔNG, còn lại giữ treo | Đ3 — Đ1 + rà 2-tầng display/API trọn module |
|
||||
|---|---|---|---|
|
||||
| Việc | ~24 attribute + grant seeder **code-first** (luật S159/#84: cấm SQL tay) + FE gate đúng key (#85) + extend regression tests | Guard nhánh Reject (đúng-lượt V2/vai + chặn terminal) + policy chỉ cho endpoint MỚI GĐ3 | Đ1 + đối chiếu menu-public-13/13 với grant API từng role (2 tầng độc lập — bài #82) |
|
||||
| Chi phí | Trung (1 wave riêng) | Thấp nhất | Cao nhất (đè tiến độ GĐ3) |
|
||||
| Rủi ro | **Silent-403 rải UI** nếu grant hẹp hơn menu-public (#44/#85); probe phải #87-aware (415/411 = PRE-AUTH, không đọc thành authz) | Các endpoint ghi cũ (update/details/attachments) VẪN trần | Như Đ1 + phạm vi UAT lớn |
|
||||
| Được gì | Đóng #82 dứt điểm cho HĐ | Đóng đúng lỗ nguy hiểm nhất (Reject-any) với ~0 phá vỡ | Trạng thái sạch vĩnh viễn |
|
||||
|
||||
**Không tự quyết** — anh từng treo chủ-đích, nay module chạm thẳng: cần anh chọn Đ1/Đ2/Đ3 (hoặc Đ2-trước-Đ1-sau) = **CHỜ-ANH #5**.
|
||||
|
||||
---
|
||||
|
||||
## §6 Q6 — WAVE BE đề xuất (mỗi wave: deliverable · acceptance ĐO ĐƯỢC · rủi ro)
|
||||
|
||||
- **W0 — CHỐT TRỤC (0 code, chặn-đầu):** trả lời CHỜ-ANH #1 (trục 7-type ⟂ 8-nhóm) · #2 (PA-A/PA-B/lộ-trình A→B) · #5 (authz Đ1/Đ2/Đ3) · #6 (roster người duyệt). *Acceptance:* 4 đáp án vào sổ YC (mã vĩnh viễn). *Rủi ro:* bỏ W0 = dựng nhầm trục, đập lại 7 workflow + guard.
|
||||
- **W1 — MÁY per-loại (code-only, 0-mig nếu PA-A):** gỡ **Bom-1** (deactivate per-Code mở cho type-3, predicate data-driven thay hard-wire `== ContractSigningPlan` tại `ApprovalWorkflowV2AdminFeatures.cs:370`) + **Bom-2** (overview family type-3, `:205-215`) + guard `EnsureContractTypeMatch` 1-nhà-duy-nhất (mirror `ContractSigningPlanWorkflowGuards`). *Acceptance (test-before RED→GREEN, khuôn W3 KHKK):* (i) POST version mới cho code loại X → `IsActive` của code loại khác GIỮ NGUYÊN (đếm SQL trước/sau); (ii) pin chéo loại → 409. *Rủi ro:* đổi predicate deactivate chạm hành vi 8 type khác → chạy trọn regression; SQLite mù collation (`:345-347`) — dựa normalize sẵn có, đừng viết test case-collision vô nghĩa.
|
||||
- **W2 — SEED họ workflow type-3 (data-op, 0-mig):** mirror `SeedKhkkGroupWorkflowsAsync` per-Code idempotent; roster theo W0-#6; thiếu user → bỏ slot + warning, KHÔNG throw lúc boot (`DbInitializer.cs:544-548`). *Acceptance:* boot ×2 → đúng N row họ mới type-3 `IsActive=1` không nhân đôi; `QT-HD-V2-001` cũ KHÔNG bị đụng active (per-Code). *Rủi ro:* infra-seed UNGATED chạy thẳng prod lần deploy đầu (#51) — roster phải chốt TRƯỚC merge.
|
||||
- **W3 — WIRE 3+1 cửa pin:** bridge K7 auto-resolve workflow theo `ContractType` (giữ/bỏ override tay theo CHỜ-ANH #7) + Create bổ 2 vế `IsActive ∧ IsUserSelectable` (đồng luật vá-3, xóa drift §1c-2) + **cửa re-pin mới** trên UpdateDraft (§1c-3) + guard khớp-loại tại cửa TRÌNH trong `ContractWorkflowService` (site Infrastructure — đúng lý do guards là `public static`, `ContractSigningPlanWorkflowGuards.cs:14-22`). *Acceptance:* phiếu thầu-phụ pin quy trình nguyên-tắc → 409 ở CẢ 4 cửa; HĐ sinh từ bridge assert đúng Code họ theo type. *Rủi ro:* đổi contract API dialog → FE 2 app phải theo (ngoài lăng kính BE — khai để lead điều phối lane FE).
|
||||
- **W4 — DRY-RUN toàn trình + nghiệm thu:** khuôn K8 S168, 2 nhánh 2 loại HĐ khác nhau → 2 quy trình khác nhau, đi trọn PE→KHKK→bridge→duyệt V2→`DaPhatHanh`→SealedCopy trên Dev; đo inbox approver cấp-đang-chờ (ResolveV2InboxIds). *Acceptance:* sổ ID phiếu + seq cháy ghi lại (bài K8: phiếu test làm lệch số phiếu thật); probe authz #87-aware. *Rủi ro:* đọc nhầm 415/411 thành kết luận authz.
|
||||
- **W5 — AUTHZ thi hành theo W0-#5** (điều kiện anh chốt): policy + grant seeder code-first + extend `AuthorizePolicyRegressionTests` + FE gate đúng key (#85). *Acceptance:* user thường Reject HĐ `DaPhatHanh` → bị chặn (control dương: admin vẫn thao tác được); regression policy xanh. *Rủi ro:* 403-storm nếu grant thiếu role — grant-before-tighten.
|
||||
|
||||
---
|
||||
|
||||
## §7 Surprises (ngoài câu hỏi chính) + CHỜ-ANH tổng hợp
|
||||
|
||||
**Surprises:**
|
||||
1. **Brief lệch mốc mig:** đĩa + `docs/STATUS.md:468` + Dev + Design đều = **Mig 72** (brief ghi 71). Drift local S182 đã hết.
|
||||
2. **S156 STALE:** "approver V2 mù HĐ ChoDuyet" đã VÁ @W6 S161 (inbox/list/detail V2-aware) — diary investigator cần đính chính.
|
||||
3. **Vá S182 chỉ phủ type-10:** bất biến "1 active/type" vẫn khắc chữ cho mọi type khác (`ApprovalWorkflowV2AdminFeatures.cs:363`) — GĐ3 mirror họ-Code sẽ giẫm ĐÚNG quả bom đó nếu quên W1.
|
||||
4. **Dev type-3 = đúng 1 workflow** `QT-HD-V2-001` (đo SQL) — "quy trình theo loại" hiện = 0% tồn tại ở V2.
|
||||
5. **Contract KHÔNG có cửa re-pin** (`UpdateContractDraftCommand` `ContractFeatures.cs:136-145` thiếu field) — pin nhầm là kẹt, sẽ cắn ngay khi có guard per-loại.
|
||||
6. **Drift 1-luật-2-site đang sống:** guard pin Create thiếu 2 vế mà bridge có (`:79-87` vs `CreateContractFromSigningPlanFeatures.cs:141-148`) — đúng bug-class W1b S182.
|
||||
7. (nhỏ) Dev không có workflow type-1/2 (PE) — 0 row; dry-run W4 trên Dev phải tạo workflow PE trước.
|
||||
|
||||
**CHỜ-ANH (7 câu, không đoán):**
|
||||
- **#1** Trục quy trình GĐ3: 7 `ContractType` (chữ YC-023) ⟂ 8 nhóm SP-002 (chuỗi ký nháy đi theo trục này) ⟂ lai?
|
||||
- **#2** PA-A (họ-Code 0-mig, tiền lệ KHKK) hay PA-B (cột thật, "máy rộng" đúng nghĩa) hay A-trước-B-sau?
|
||||
- **#3** Danh mục SP-002 có nên mang cột gợi-ý loại HĐ (autofill dialog bridge) hay giữ chọn tay?
|
||||
- **#4** GĐ3 có cần line-level (bảng KL/đơn-giá/thành-tiền phụ lục 3.5) trong ruột HĐ, hay chỉ điều khoản (hiện trạng: hạng mục chỉ thành chuỗi tên)?
|
||||
- **#5** Authz Đ1/Đ2/Đ3 — anh từng treo *"chỗ hợp đồng cứ từ từ"*, module này chạm thẳng.
|
||||
- **#6** Roster người duyệt cho họ workflow type-3: sao từ đâu? (KHKK sao từ `QT-DN-V2-001` theo OG-2; SP-002 pattern nháy per-NHÓM không per-loại ⇒ không tự suy được).
|
||||
- **#7** UX bridge: auto-pin theo loại có cho override chọn tay không?
|
||||
|
||||
**Token cost estimate:** ~95K (đọc code + spec + đo DB + ghi file).
|
||||
|
||||
== HẾT ==
|
||||
@ -0,0 +1,116 @@
|
||||
# sub-invest-fe-fable — YC-023 "Duyệt Hợp đồng" (GĐ3) — lăng kính FE 2-app
|
||||
|
||||
> Run `2026-08-10-S187-duyet-hd-tu-khkk` · engine `/fable-real` (H21 lệnh-A, single deep-pass, propose-only) · vai `investigator-codebase` · 2026-08-10.
|
||||
> Đề bài YC-023: đề xuất CÁCH LÀM (không code) FE module "Duyệt Hợp đồng" — cấu trúc + menu mirror KHKK/PE, màn duyệt theo 7 LOẠI HĐ, phiếu kế thừa từ KHKK đã duyệt.
|
||||
> Trạng thái file: ĐANG GHI TỪNG MỤC — mục nào xong ghi ngay. Nếu return bị #53, file này là chân lý.
|
||||
|
||||
## §0 — Ground truth đầu vào (đọc từ STATUS.md trước khi mở code)
|
||||
|
||||
- Mốc sống S185 (STATUS.md:6-7): KHKK 8-panel YC-013 SHIP + guard tay-cầm-phiếu YC-016; test 668.
|
||||
- STATUS.md:23 (⑦ S159): bộ tên 4-GĐ chốt lời anh — ❶ Duyệt NCC · ❷ Kế hoạch ký kết HĐ · ❸ **Duyệt hợp đồng** (*"Trình ký Hợp đồng đổi thành Duyệt hợp đồng"*) · ❹ Hợp đồng cứng. Menu 6-leaf ×7 loại HĐ đã sinh @`ea962b1` (21 key mới `Ct_*_{WfView,Approved,Deleted}`) + endpoint `GET /api/contracts/deleted`. 🔴 ❹-risk CÓ Ý THỨC: menu HĐ public 13/13 role trong khi authz HĐ còn `[Authorize]` trần (lỗ hổng S156, anh chốt *"chỗ hợp đồng cứ từ từ"*).
|
||||
- STATUS.md:11 (② S162): cây 4-folder GĐ `bfc7b79` — 3 file mới ×2 app (`PipelineStageFolders` · `PipelineTreePanel` · `usePipelineStages`), SHA-pair 6/6.
|
||||
- STATUS.md:519 (S168): GĐ3-D `081557b` — cây toàn trình vào Panel-3 cả 3 trang 3-panel; `PipelineTreePanel` +prop `bodyMaxHClass`.
|
||||
- STATUS.md:27 (③ S155-S158): 🔴🔴 lỗ hổng prod — `ContractWorkflowService.cs:48-66` nhánh Reject chạy TRƯỚC MỌI GUARD + `ContractsController.cs:13` `[Authorize]` trần duy nhất phủ 22 endpoint ghi ⇒ mọi user đăng nhập Từ-chối/Trả-lại được bất kỳ HĐ nào kể cả DaPhatHanh. Treo chủ-đích.
|
||||
|
||||
## §1 — Q1 (phần a): Menu key `Ct_*` ĐÃ ROUTE VÀO TRANG HIỆN CÓ, không có trang "Duyệt HĐ" riêng
|
||||
|
||||
**fe-user `Layout.tsx:143-154`** — regex `^Ct_([^_]+)_(List|Create|Pending|WfView|Approved|Deleted)$` map 6 leaf ×7 loại vào trang CŨ qua query param (0 trang mới):
|
||||
- `List` → `/my-contracts?type=N` · `Create` → `/contracts/new?type=N` · `Pending` → `/inbox?type=N`
|
||||
- `WfView` → `/contracts/workflow-matrix?type=3&ct=N` (comment :141-142: cả 7 loại đều `type=3` — HĐ chung 1 bộ workflow V2, `&ct=N` chỉ để active-state)
|
||||
- `Approved` → `/my-contracts?type=N&phase=9` · `Deleted` → `/my-contracts?type=N&deleted=1`
|
||||
|
||||
**fe-admin `Layout.tsx:117`** — regex Ct_ CHỈ match `(List|Create|Pending)` (KHÔNG có WfView/Approved/Deleted như fe-user). NHƯNG vô nghĩa hiện tại vì:
|
||||
|
||||
🔴 **fe-admin ẨN TRỌN 4 root pipeline khỏi sidebar** — `ADMIN_HIDDEN_KEYS` (`fe-admin/src/components/Layout.tsx:227-240`): `PurchaseEvaluations` (GĐ1) · `KeHoachKyKet` (GĐ2) · `Contracts` (GĐ3) · `HopDongCung` (GĐ4) + Forms/Reports/Master/Hrm/Workflows/PeWorkflows. Lọc ĐỆ QUY (`filterForAdmin:251-255`) ⇒ ẩn root là cả cây con chết theo. Comment :224-226 cảnh báo: nếu ngày nào gỡ `KeHoachKyKet` khỏi tập ẩn thì PHẢI nới regex `Khkk_G*` trong resolvePath TRƯỚC. ⇒ **Nghiệp vụ pipeline 4-GĐ hiện là lãnh địa fe-user (eoffice); fe-admin là chỗ Designer quy trình** (`Khkk_WfView` admin trỏ `/system/approval-workflows-v2/ContractSigningPlan` — `fe-admin/Layout.tsx:40`; họ `AwV2_KhkkN[1-8]` → Designer `:188`). Trang khkk/contracts bên admin vẫn TỒN TẠI (route sống, vào bằng URL tay) nhưng không có menu dẫn.
|
||||
|
||||
**Khuôn `Hdc_*` GĐ4 (fe-user `Layout.tsx:70-76`):** 7 leaf tĩnh `Hdc_<Code>` → `/hard-copies?type=N` — 1 trang chung + query param, KHÔNG per-type page. ComingSoonPage: GĐ2/GĐ4 đã HẾT skeleton (comment :57 "[W2 KHKK S161] GĐ2 hết skeleton", :67 "[W7] GĐ4 hết skeleton — re-point trang thật"); root `HopDongCung` fallback `/dashboard` (:84).
|
||||
|
||||
## §2 — Q1 (phần b): 3 trang Panel-3 + màn duyệt HĐ hiện tại là gì
|
||||
|
||||
**3 trang GĐ3 hiện có (S168 `081557b` đưa cây vào Panel-3 cả 3):**
|
||||
1. `fe-user/src/pages/contracts/MyContractsPage.tsx` (232 dòng) — 3-panel `[320px_1fr_360px]`: P1 list compact (search + PhaseBadge + SlaTimer) · P2 `ContractDetailContent` · P3 `WorkflowHistoryPanel` hoặc `PipelineTreePanel currentStage={3}` khi chưa chọn (`:225`). Đọc `?type=`/`?phase=9`/`?deleted=1` (`:30-36`) — filter type làm CLIENT-SIDE (`:61` filter sau fetch, BE chỉ nhận phase/deleted `:42-48`). Màn Đã-xóa = chỉ-xem, chặn select tại gốc (`:74-77`).
|
||||
2. `fe-user/src/pages/InboxPage.tsx` (359 dòng) — trang "Đang duyệt" (Pending) — đọc sau.
|
||||
3. `fe-admin/src/pages/contracts/ContractsListPage.tsx` (255 dòng) — bản admin của list 3-panel.
|
||||
|
||||
**Màn duyệt HĐ hiện tại = `ContractDetailContent` (257 dòng, mirror y hệt 2 app 257/257):**
|
||||
- 🔴 **Model duyệt là 9-PHASE CŨ, không phải V2-trạm:** 2 nút "Yêu cầu sửa"/"Duyệt → tiếp" hiện khi `c.workflow.nextPhases` khác rỗng (`ContractDetailContent.tsx:105-116`), bấm mở Dialog **chọn targetPhase từ Select** (`:242-246`) rồi `POST /contracts/{id}/transitions` (`:47`). KHÔNG có bộ 3-nút PE/KHKK (Duyệt · Trả lại · Từ chối); "Từ chối" (TuChoi=99) không có nút riêng — Reject default về DangSoanThao (`:76-78`).
|
||||
- KHÔNG có banner "Đến lượt bạn"/gating V2 theo Cấp (PE/KHKK có); nút hiện cho MỌI user thấy phiếu — khớp lỗ hổng S156 (BE `[Authorize]` trần + Reject trước guard).
|
||||
- Section "Ý kiến cấp duyệt (Quy trình V2)" ĐÃ CÓ nhưng **READ-ONLY** (`:185-224`, Plan B S29 Chunk E3 mirror PE `LevelOpinionsSectionV2`): render `c.levelOpinions` khi `c.approvalWorkflowId` pin, kèm badge "⚡ Admin duyệt thay". Nghĩa là data-model V2 (pin + opinions) đã lên FE type, thiếu ĐƯỜNG BẤM ApproveV2.
|
||||
- KHÔNG hiển thị bất kỳ vết KHKK nguồn nào (0 hit `signingPlan|khkk` trong component — xác nhận ở §4).
|
||||
|
||||
**Thiếu cho "Duyệt Hợp đồng" đúng nghĩa (đối chiếu chuẩn PE/KHKK):** (i) màn duyệt V2-trạm: banner lượt-ai + 3 nút Duyệt/Trả-lại/Từ-chối gated theo `currentApproval` + flow-panel Bước→Cấp; (ii) list "chờ tôi duyệt" per-loại thật sự (InboxPage đọc ở §3); (iii) hiển thị nguồn gốc KHKK; (iv) BE per-action authz (ngoài scope FE nhưng là tiền đề — S156 treo chủ-đích).
|
||||
|
||||
## §3 — Q1 (phần c): InboxPage + nguồn dữ liệu "chờ tôi duyệt"
|
||||
|
||||
`fe-user/src/pages/InboxPage.tsx` (359 dòng): 3-panel + stats 4 pill; 2 SECTION trong P1 — "Hợp đồng" từ `GET /contracts/inbox` (`:70`) + "Phiếu Duyệt NCC" từ `GET /purchase-evaluations/inbox` (`:78`, PE row click → navigate `/purchase-evaluations/:id` thay vì inline — comment `:73-75` "đỡ duplicate ContractDetailContent cho PE entity khác shape"). Filter `?type=N` CLIENT-SIDE (`:91`). Panel-3: **S174 owner đã GỠ `PipelineTreePanel` khỏi Hộp thư** (comment `:341-348` — *"chỗ hộp thư vứt panel này vào làm j"*; 2 trang GĐ3/GĐ4 còn lại GIỮ — đừng suy rộng).
|
||||
|
||||
🔴 **Tiền đề cross-lens phải kiểm với lane BE (từ diary S156, cần re-verify):** BE `/contracts/inbox` + `GetEligiblePhases` từng đo là **100% V1-LEGACY** ⇒ HĐ pin V2 đang ChoDuyet KHÔNG hiện trong inbox của approver V2. Nếu còn đúng thì mọi thiết kế FE "Đang duyệt per-loại" đứng trên inbox này sẽ RỖNG với phiếu V2 — phải có wave BE inbox-V2 trước wave FE Pending. (FE lens: `InboxPage` chỉ là cái loa của endpoint đó.)
|
||||
|
||||
## §4 — Q4: Kế thừa hiển thị KHKK→HĐ — chiều xuôi ĐÃ CÓ, chiều ngược 0 dữ liệu
|
||||
|
||||
**Chiều xuôi (KHKK → HĐ) — K7 đã render đủ trong `KhkkDetailPage.tsx` (377 dòng, header khai MIRROR SHA256 identical 2 app `:3`):**
|
||||
- Nút "Đưa vào HĐ" chỉ hiện khi `phase === DaDuyet ∧ can('KeHoachKyKet','Read')` (`:110-112`), disabled kèm title NÓI RA vế thiếu (`:113-122` — khuôn nút-khoá-kèm-title PE); hội 2 khóa quyền `KeHoachKyKet.Read ∧ Contracts.Create` (`:54-61`, đúng gotcha #85).
|
||||
- Dialog `CreateContractFromKhkkDialog` (`:196-377`): radio chọn 1 DÒNG (máy BE nhận `lineIds[]` gộp N — UI cố ý 1-1 theo luật "máy rộng UI hẹp" OG-1 `:184-186`) · Select **Loại HĐ 7 loại** (`:337-341`) · picker workflow V2 `applicableType=3` lọc ĐỦ 2 cờ `isUserSelectable ∧ isActive` (`:224-227`) · tên HĐ default `{peTenGoiThau} — {tenHangMuc}` (`:209-211`) · POST `/contract-signing-plans/{id}/create-contract` → toast mã HĐ + navigate `/contracts/{contractId}` (`:242-252`) + invalidate 2 index cây (`pipeline-khkk-index`/`pipeline-contract-index` `:246-249`, staleTime 60s).
|
||||
- Dòng đã bắc cầu render link **"Đã có HĐ" → `/contracts/{l.contractId}`** (`:321-325`); guard dòng-bắc-cầu-được = `contractId == null ∧ approvedAmount != null` (`:111`, `:200`).
|
||||
|
||||
**Chiều ngược (HĐ → KHKK nguồn) — HIỆN = 0:**
|
||||
- FE `fe-user/src/types/contracts.ts`: 0 hit `signingPlan|khkk|maKeHoach` (grep). `ContractDetailContent` không render gì về nguồn.
|
||||
- Domain: link nằm Ở PHÍA LINE — `ContractSigningPlanLine.ContractId` (`src/Backend/SolutionErp.Domain/ContractSigningPlans/ContractSigningPlanLine.cs:46`, comment `:7` "giữ đường quay ngược Kế-hoạch → HĐ (`ContractId`, twin `PurchaseEvaluation.ContractId`)"); `Contract.cs` 0 hit `SigningPlan` ⇒ **HĐ không mang con trỏ ngược**. Muốn màn duyệt HĐ show "nguồn: KHKK/2026/xxx · NCC · giá chốt · hạng mục" thì BE phải JOIN ngược qua Lines (extend `ContractDetailDto` — reverse-lookup `Line WHERE ContractId = id` → plan.maKeHoach + line.tenHangMuc + supplierName + approvedAmount + approvalGroup) hoặc endpoint phụ. **FE không tự chế được từ data hiện có** (trừ phi bắn thêm query list KHKK rồi lọc client — anti-pattern N+1, không đề xuất).
|
||||
- Đề xuất hiển thị (FE, sau khi BE trả field): card "Nguồn gốc" trong `ContractDetailContent` ngay dưới "Thông tin HĐ" — mã KHKK (link `/khkk/:planId?group=n`), badge N{group}, hạng mục, NCC dòng, giá duyệt chốt (đối chiếu `giaTri` HĐ), + chuỗi sâu hơn "PE gốc" nếu plan có `purchaseEvaluationId` (cây pipeline đã nối GĐ1→2 qua field này). Đối xứng đủ 2 chiều: KHKK line có "Đã có HĐ" (đã ship) ⟷ HĐ có "Từ KHKK" (cần làm).
|
||||
|
||||
## §2-bis — Khuôn màn duyệt KHKK (bổ sung cho Q2, đo trước khi return lượt 1 bị #53)
|
||||
|
||||
`fe-user/src/components/khkk/KhkkWorkflowPanel.tsx` (762 dòng; bộ 4 component khkk/ = 2.687 dòng/app) — đây là khuôn màn-duyệt V2 MỚI NHẤT đã deploy:
|
||||
- Cấu trúc tự khai (`:10-14`): ① stepper flow Bước→Cấp (Done/Current/Pending `:136-148`) · ② banner kết-thúc-sớm · ④ nút hành động + Dialog + Xóa phiếu.
|
||||
- Gating: `actorIsCurrentApprover = user.id ∈ currentLevels.approverUserId` (`:161`), `actorInLevel = isAdmin || ...` (`:162`); banner "✓ Đến lượt bạn duyệt" (`:422`, comment `:409` khai khuôn gốc `PeWorkflowPanel:489-508`); per-level `allowApproverFinalize` (`:168-170`, `applyLevelFinalize` gửi kèm Approve `:198-199`).
|
||||
- 🔴 **Owner đã THU HẸP bộ nút @S174/S175:** nút "TỪ CHỐI" GỠ khỏi UI (`:482-485` — *"vậy thì bỏ luôn từ chối"*; chỉ gỡ ĐƯỜNG BẤM, máy BE `Reject` còn) + panel chỉ còn **Duyệt / Trả lại** (`:439-441` — *"Bỏ hết đi… chỗ menu đang duyệt → Duyệt/Trả lại vậy thôi"*). KHKK giữ **1 return-mode** (về Bước 1 · Cấp 1; 4-mode PE là món riêng còn treo — `:29-30`).
|
||||
- Badge lịch sử 3-NGẢ emerald/amber/rose (`:550-574` — bài học 2-ngả vẽ "Trả lại" xanh như đã-duyệt).
|
||||
⇒ Bài Q2: khuôn màn duyệt cho HĐ nên mirror **KhkkWorkflowPanel** (mới nhất, đã qua 8 đợt UAT S179 + owner-trim), KHÔNG mirror PeWorkflowPanel nguyên bản (PE có món riêng: NS PRO/CCM, 4 return-mode, giá chốt).
|
||||
|
||||
## §5 — Q3: per-LOẠI-HĐ trên UI — khuôn ĐÃ DEPLOY là "1 trang + query-param DANH TÍNH", không phải KIND_CONFIG `:kind`
|
||||
|
||||
**Chọn khuôn: 1 trang + `?type=N`.** Căn cứ "ĐÃ DEPLOY = CHUẨN":
|
||||
- Cả 3 họ pipeline per-type/per-group hiện hành đều đi đường query-param: `Ct_*` → `?type=N` (fe-user `Layout.tsx:143-154`) · `Hdc_*` → `/hard-copies?type=N` (`:70-76`) · `Khkk_G{n}_*` → `?group=n` (`:213-222`). KIND_CONFIG `:kind` URL-segment chỉ dùng cho CRUD catalog (HRM configs `:114-116` "cùng page :kind-driven") — khác thể loại (catalog tĩnh vs phiếu workflow).
|
||||
- Luật danh-tính đã đúc 2 lần, PHẢI theo: (i) `view=approved` = KHOÁ ĐỊNH DANH không phải bộ lọc (`Layout.tsx:180-184`, bug menu-sáng-nhầm UAT 2026-07-27); (ii) `?group=n` KHÔNG nằm `TRANSIENT_QUERY_KEYS` (`:201-203`). ⇒ nếu thêm param mới cho trang duyệt HĐ (vd `?view=...`), phải kiểm nó có vào TRANSIENT hay không, sai một chữ = leaf trỏ trang mở được nhưng lọc SAI, hỏng câm (`:204-207`).
|
||||
- 🟡 Lỗ nhỏ đáng vá cùng wave: `MyContractsPage.tsx:42-48` chỉ gửi `type` lên BE khi `showDeleted`; list thường filter type CLIENT-SIDE sau fetch `pageSize:100` — sai lệch khi tổng HĐ >100 (BE `/contracts` CÓ param type — skill contract-workflow bảng endpoint). InboxPage cũng client-side (`:91`).
|
||||
|
||||
**Key/policy: TÁI DÙNG TRỌN, KHÔNG sinh bộ mới.**
|
||||
- Menu identity: 42 leaf `Ct_<Code>_{List,Create,Pending,WfView,Approved,Deleted}` ×7 loại ĐÃ seed đủ (S159 `ea962b1` +21 key mới; menu 6-leaf "y-chang-Duyệt-NCC"). Màn duyệt HĐ không cần key mới — khác KHKK: `Khkk_G*` phải sinh vì 8 NHÓM DUYỆT là chiều mới không có sẵn; 7 LOẠI HĐ đã là chiều có key từ đầu.
|
||||
- 🔴 Gotcha #85 áp cụ thể: gate PermissionGuard/`can()` trong trang duyệt HĐ phải dùng **key policy của ENDPOINT = `Contracts.{Read,Create,Update,Delete}`** (root, inherit-family `Ct_*` per gotcha #35 — nhưng `MenuPermissionHandler.cs:40` so CHÍNH XÁC 1 key), KHÔNG OR các `Ct_*` leaf. Mẫu đúng đã ship: hội-2-khóa K7 (`KhkkDetailPage.tsx:54-61` — `KeHoachKyKet.Read` ∧ `Contracts.Create`).
|
||||
- Tiền đề BE treo: `ContractsController` hiện `[Authorize]` trần (S156) ⇒ FE `can('Contracts', ...)` hôm nay là gate MỘT TẦNG (display-only). Khi BE mở per-action policy thì FE KHÔNG đổi key — đó là lý do chọn `Contracts.*` ngay từ giờ.
|
||||
- Câu nghiệp vụ (CHỜ-ANH): có cần phân quyền per-LOẠI-HĐ ở tầng API không (vd người chỉ duyệt Thầu phụ)? Nếu có thì mới cần nâng `Ct_*` leaf thành policy tiêu thụ được ở controller — hiện 0 endpoint tiêu thụ key `Ct_*`.
|
||||
|
||||
## §6 — Q5: SHA-mirror 2 app — mirror được GẦN HẾT, nhưng phải trả lời "mirror sang admin để làm gì"
|
||||
|
||||
**Hiện trạng đo được:** bộ contracts/ 4 component = same-size 2 app (156/257/683/151 — mirror); `KhkkDetailPage` tự khai "MIRROR SHA256 identical" (`:3`); S162 SHA-pair 6/6 pipeline; S157 4-file SHA-mirror. Chuẩn đội = **component/page ruột mirror byte-identical; Layout/App.tsx/menuKeys CỐ Ý LỆCH có khai chỗ** (fe-admin `Layout.tsx:37-39` Khkk_WfView lệch chủ-đích; `:209-212` fe-user "fe-admin CỐ Ý KHÔNG nới regex").
|
||||
|
||||
**Phân loại cho module Duyệt HĐ:**
|
||||
- Mirror identical được: card "Nguồn gốc KHKK" (component mới) · panel duyệt V2 (thay/nâng `ContractDetailContent` + `WorkflowHistoryPanel`) · types/contracts.ts delta · ContractDetailPage wrapper. Đây là vùng "sửa 1 chỗ 2 màn đổi theo" (triết lý S171 `:5-9` KhkkDetailPage).
|
||||
- Buộc khác: `InboxPage` (fe-user, nguồn `/contracts/inbox`) ⟂ `ContractsListPage` (fe-admin, nguồn `/contracts`) — 2 trang khác vai đã tồn tại, không ép mirror; `Layout.tsx` (admin thiếu regex WfView/Approved/Deleted `:117` + ADMIN_HIDDEN_KEYS); `App.tsx` route set.
|
||||
- 🔴 **Nghĩa của mirror-sang-admin phải chốt trước wave:** fe-admin đang ẨN cả root `Contracts` (`ADMIN_HIDDEN_KEYS:230`) ⇒ trang duyệt HĐ mirror sang admin sẽ SỐNG-KHÔNG-MENU (như khkk admin hiện tại — vào bằng URL tay). Tiền lệ chấp nhận được (KHKK ship y vậy), chi phí mirror ~0 vì component chung; nhưng nếu owner muốn admin THẤY menu GĐ3 → phải gỡ `Contracts` khỏi ADMIN_HIDDEN_KEYS + nới regex admin + quyết `Ct_*_WfView` admin trỏ đâu (Designer `/system/workflows/<type>` là ứng viên — mirror mẫu Khkk_WfView admin `:40`). CHỜ-ANH.
|
||||
|
||||
**Acceptance đo được (khuôn đã dùng S162/S168):** (1) SHA-pair: `git hash-object fe-admin/src/<f> fe-user/src/<f>` từng cặp bằng nhau — liệt danh sách cặp trong PR, đếm N/N; (2) build ×2: `npm run build` fe-admin + fe-user EXIT 0 (trọng tài chạy SAU khi agent dừng tay — bài #68); (3) key-count: menu-key/policy đối chiếu `docs/STATUS.md` canonical + test T2 khoá drift (nếu wave không thêm key ⇒ số GIỮ NGUYÊN là acceptance); (4) sau deploy: curl bundle prod grep marker mới ×2 app (#77 byte-verify).
|
||||
|
||||
## §7 — Q6: Wave FE đề xuất (khớp nhịp BE — phiếu duyệt cần endpoint trước)
|
||||
|
||||
Tiền đề đo được lái thứ tự: FE `types/contracts.ts` có `approvalWorkflowId`/`currentApprovalLevelOrder`/`levelOpinions`/`nextPhases` (`:138,:172-176`) nhưng **KHÔNG có `currentApproval` + `approvalFlow`** — PE có 2 DTO đó (`PurchaseEvaluationCurrentApprovalDto`/`ApprovalFlowDto`) để render banner + stepper. ⇒ màn duyệt V2-trạm cho HĐ **CHẶN bởi BE DTO**, không phải thiếu endpoint duyệt (ApproveV2Async HĐ đã wire từ S29).
|
||||
|
||||
| Wave | Deliverable | Chặn bởi BE? | Acceptance đo được | Rủi ro |
|
||||
|---|---|---|---|---|
|
||||
| **F1 Nguồn-gốc** | Card "Từ KHKK" trong `ContractDetailContent` (mã KHKK link `/khkk/:planId?group=n` + badge N{n} + hạng mục + NCC + giá chốt) — đối xứng chiều ngược của "Đã có HĐ" | 🔴 CÓ — BE extend `ContractDetailDto` reverse-join `Line WHERE ContractId=id` (§4) | HĐ dry-run K8 (`ZZTEST-K8/...`) hiện card đúng mã `KHKK/2026/00x`; HĐ không-nguồn KHÔNG hiện; SHA-pair; build ×2 | DTO null-path (HĐ cũ 0 nguồn); N-dòng-1-HĐ tương lai (máy `lineIds[]` rộng hơn UI) |
|
||||
| **F2 Màn duyệt V2-trạm** | Nâng `ContractDetailContent`/panel: banner "Đến lượt bạn" + gating nút theo `currentApproval` + stepper Bước→Cấp — mirror `KhkkWorkflowPanel` (khuôn mới nhất §2-bis), GIỮ nhánh V1 cũ cho HĐ không pin (dual-render đã có sẵn `c.approvalWorkflowId &&`) | 🔴 CÓ — BE thêm `currentApproval`+`approvalFlow` vào ContractDetailDto (cookie-cutter DTO PE S17) | Phiếu V2 ChoDuyet: user ngoài Cấp thấy nút khoá + tooltip; user trong Cấp duyệt được → levelOpinion mới render; HĐ V1 giữ 2-nút cũ (regression); build ×2 + SHA-pair | Bộ nút = câu nghiệp vụ (2-nút KHKK vs 3-nút PE — CHỜ-ANH); đừng đụng tập Order workflow (F17 S155) |
|
||||
| **F3 Pending-per-loại thật** | InboxPage: HĐ V2 ChoDuyet hiện đúng trong "Đang duyệt" per-loại | 🔴 CÓ — BE inbox V2-aware (mirror `ResolveV2InboxIdsAsync` PE; tiền đề S156 "inbox 100% LEGACY" cần lane BE re-verify) | Tạo HĐ V2 từ KHKK → xuất hiện ở `/inbox?type=N` của approver Cấp 1; user khác 0 thấy | Nếu BE gộp vào `/contracts/inbox` sẵn có thì FE 0-đổi (chỉ verify) — đo trước khi code |
|
||||
| **F4 Per-type server-filter** | `MyContractsPage`/`InboxPage` gửi `type` lên BE thay client-filter | KHÔNG (param có sẵn) | Network tab thấy `?type=N`; list đúng khi seed >100 HĐ | Đổi queryKey → invalidate cache cũ |
|
||||
| **F5 Menu/label polish + admin-visibility** | Nếu anh chốt admin thấy GĐ3: gỡ `Contracts` khỏi ADMIN_HIDDEN_KEYS + nới regex `:117` + WfView→Designer | KHÔNG | Sidebar admin hiện 7 nhóm × 6 leaf; 0 leaf drop câm (đếm leaf render vs seed) | gotcha #50 route-thiếu = drop IM LẶNG; #84 code-first seeder không SQL tay |
|
||||
|
||||
Thứ tự đề xuất: **F1 → F2 → F3** (mỗi wave FE đi SAU wave BE tương ứng 1 nhịp, khuôn {đợt nhỏ → ship → anh nhìn prod → vẽ tiếp} S159 ⑦); F4/F5 chèn xen kẽ lúc chờ BE. Mọi wave: KHÔNG mig, KHÔNG đổi menu-key ⇒ STATUS counters giữ nguyên = acceptance rẻ.
|
||||
|
||||
## CHỜ-ANH (câu nghiệp vụ — không đoán)
|
||||
|
||||
1. **Bộ nút màn duyệt HĐ:** theo KHKK sau S174/S175 (chỉ **Duyệt / Trả lại**, bỏ Từ chối) hay theo PE (3 nút)? Owner đã trim KHKK bằng lời trực tiếp — áp luôn cho HĐ hay HĐ là chuyện khác?
|
||||
2. **Mở wave BE per-action authz `ContractsController` bây giờ?** S156 anh chốt "chỗ hợp đồng cứ từ từ" — nhưng YC-023 chính là module hợp đồng; làm màn duyệt mà giữ `[Authorize]` trần thì gating FE chỉ là trang trí (mọi user vẫn Reject được qua API).
|
||||
3. **fe-admin có cần THẤY menu GĐ3 không** hay giữ posture ẩn 4 root (mirror sống-không-menu như KHKK admin)?
|
||||
4. **Phân quyền per-LOẠI-HĐ tầng API** có cần không (người chỉ duyệt Thầu phụ)? Nếu không, giữ `Contracts.*` root policy là đủ và rẻ.
|
||||
5. **Menu public 13/13 role** (❹-risk S159 anh tái khẳng định "hiển thị hết để góp ý") — giữ nguyên cho GĐ3 trong lúc build?
|
||||
|
||||
== HẾT ==
|
||||
@ -0,0 +1,166 @@
|
||||
# sub-plan-review-fable — STAGE C plan-review TỔNG THỂ + TỔNG HỢP PLAN wave — YC-023 lượt 4/4
|
||||
|
||||
> Run `2026-08-10-S187-duyet-hd-tu-khkk` · vai `reviewer` (adversarial, propose-only) · ENGINE `/fable-real` (H21 lệnh-A) · 2026-08-10.
|
||||
> Đề bài 8 mục: (1) review tổng thể 2 invest + 7 flag style, claim load-bearing MỞ CODE verify lại; (2) FLAG-2 wave BE-DTO mồ côi — định vị; (3) FLAG-1 chốt F3 verify-only; (4) FLAG-3 nhánh điều-kiện trục 7-type⟂8-nhóm; (5) FLAG-4 quyết họ key `AwV2_Hd*`; (6) FLAG-5/6/7 disposition; (7) TỔNG HỢP PLAN wave W0..Wn; (8) hợp nhất 12 câu CHỜ-ANH.
|
||||
> Ghi TỪNG MỤC trong lúc làm (chống #53). Return chỉ là tóm — file này là chân lý.
|
||||
|
||||
## §G0 — Ground-truth độc lập (đo tươi, không chép)
|
||||
|
||||
- **Mig trên đĩa = 72** (`ls Migrations | grep -c Designer.cs` = 72; cuối `20260806070242_AddMaHangMucToContractSigningPlanLines`) — KHỚP invest-BE §0 (brief "71" sai, invest tự sửa đúng).
|
||||
- STATUS.md:6 mốc SỐNG S185: test **668** · Policies **452** · Menu keys **113** — khớp invest-BE §0 trích STATUS:6.
|
||||
- 4 file PIN đọc trọn: run.md (46 dòng) · sub-invest-be (171 dòng, `== HẾT ==`) · sub-invest-fe (116 dòng, `== HẾT ==`) · sub-style-review (145 dòng, `== HẾT ==`).
|
||||
|
||||
---
|
||||
|
||||
## §1 — REVIEW TỔNG THỂ: verify code THẬT từng claim load-bearing (mục 1 đề bài)
|
||||
|
||||
### 1a. Bảng verify — mọi claim đắt đều MỞ FILE đọc lại, không tin chép
|
||||
|
||||
| # | Claim (nguồn) | Đo phiên này | Verdict |
|
||||
|---|---|---|---|
|
||||
| 1 | Bridge K7 251 dòng; guard pin 4 vế KHÔNG so ContractType; ContractType chọn TAY (BE §1b/§3a) | `CreateContractFromSigningPlanFeatures.cs` = 251 dòng đúng; `:53` `ContractType` trong request; `:134-148` đúng 4 vế tồn-tại/type/IsActive/IsUserSelectable, 0 vế so loại; `:23-25` comment tự khai "KHÔNG suy được từ ContractType" | ✅ KHỚP |
|
||||
| 2 | `ApplicableType=3` = 1 giá trị cho cả 7 loại HĐ (BE §1b) | `ApprovalWorkflow.cs:57` `Contract = 3, // HĐ general (any ContractType)`; `:70` `ContractSigningPlan = 10` slot riêng | ✅ KHỚP |
|
||||
| 3 | Inbox V2-aware — đính chính S156 STALE (BE §1c ⟂ FE §3) | `ContractFeatures.cs:532-534` resolve v2InboxIds; `:536-538` thoát-sớm xét CẢ 2 nguồn; `:543` `\|\| v2InboxIds.Contains(c.Id)`; `:559-593` `ResolveV2InboxIdsAsync` in-memory join, `:588-589` OR-of-N | ✅ BE ĐÚNG — FLAG-1 chốt §3 |
|
||||
| 4 | Create pin OPTIONAL + guard thiếu 2 vế (BE §1c-1/2) | `ContractFeatures.cs:37` `Guid? ApprovalWorkflowId = null`; `:79-87` chỉ exists+type — drift 1-luật-2-site với bridge `:141-148` THẬT | ✅ KHỚP |
|
||||
| 5 | UpdateDraft KHÔNG re-pin (BE §1c-3) | `ContractFeatures.cs:136-145` record 9 field, 0 field `ApprovalWorkflowId` | ✅ KHỚP |
|
||||
| 6 | Bom 1 deactivate per-TYPE (BE §2c) | `ApprovalWorkflowV2AdminFeatures.cs:370-377` ternary `typeEnum == ContractSigningPlan` — type-3 rơi nhánh per-TYPE `:374-376`; comment `:363` "bất biến per-TYPE của các loại khác"; R-6 `:365-368` CẤM regex | ✅ KHỚP — FLAG-5 nit đúng |
|
||||
| 7 | Bom 2 overview Active-đơn (BE §2c) | `:205-215` `versions.FirstOrDefault(v => v.IsActive)` per type — 7 active cùng type sẽ hiện 1 giấu 6 | ✅ KHỚP |
|
||||
| 8 | DTO-gap `currentApproval`/`approvalFlow` (FE §7 + FLAG-2) | grep Application/Contracts = **0 hit**; **control dương** PE: `PurchaseEvaluationDtos.cs:158/:186/:309-310` + Features `:1046-1047` CÓ | ✅ KHỚP — FLAG-2 THẬT |
|
||||
| 9 | Khuôn KHKK 8 key `AwV2_KhkkN*` + 32 policy (FLAG-4) | `MenuKeys.cs:127-134` 8 const; comment `:123-126`: vào `All` ⇒ 32 policy + 2 row STATUS đổi + **invariant T6: vào All BẮT BUỘC có menu-row, thiếu = Permission FK nổ lúc boot** | ✅ KHỚP — §5 quyết |
|
||||
| 10 | `KhkkWorkflowPanel` = khuôn màn duyệt mới nhất; owner-trim bỏ Từ chối @S174 (FE §2-bis) | `KhkkWorkflowPanel.tsx:482` 🧊 comment "Nút TỪ CHỐI ĐÃ GỠ — anh chốt 'vậy thì bỏ luôn từ chối'"; header `:10-32` cấu trúc 7 khối | ✅ KHỚP |
|
||||
| 11 | Mig mốc 72 (BE §0, brief ghi 71 SAI) | đo đĩa: 72 file Designer; cuối `20260806070242_AddMaHangMucToContractSigningPlanLines` | ✅ KHỚP |
|
||||
| 12 | fe-user regex 6-leaf `Ct_` / fe-admin ẩn root Contracts (FE §1) | `fe-user/Layout.tsx:143` regex `^Ct_([^_]+)_(List\|Create\|Pending\|WfView\|Approved\|Deleted)$`; `fe-admin/Layout.tsx:227-230` `ADMIN_HIDDEN_KEYS` chứa `'Contracts'` | ✅ KHỚP + phát hiện mới 1c-③ |
|
||||
|
||||
### 1b. 🔴 PHÁT HIỆN MỚI của plan-review (3 điểm KHÔNG có trong cả 3 bản trước)
|
||||
|
||||
**① Khuôn DTO cho màn duyệt: FE invest đề xuất SAI KHUÔN so với panel nó chọn mirror — phân xử BẰNG CODE.**
|
||||
- FE §7/F2 đề xuất BE thêm cặp `currentApproval` + `approvalFlow` (cookie-cutter DTO **PE** S17 — BE precompute status Done/Current/Pending). Style-review FLAG-2 cũng đi theo hướng đó.
|
||||
- Nhưng panel được chọn mirror là **`KhkkWorkflowPanel`** — và chính header nó khai (`:19-25`): *"🔴 KHÁC PE Ở CHỖ ĐẮT NHẤT: PE nhận `approvalFlow` đã có `status` do BE precompute. **KHKK BE trả CÂY THÔ — panel TỰ SUY** từ cặp con-trỏ `currentWorkflowStepIndex` (INDEX 0-based) + `currentApprovalLevelOrder`"*. Khuôn KHKK trên đĩa: detail DTO mang **ĐỦ CẶP con-trỏ + cây thô inline** (`ContractSigningPlanFeatures.cs:194` `CurrentWorkflowStepIndex` + `:205` `List<...WorkflowStepDto> WorkflowSteps`; FE `khkk.ts:381`).
|
||||
- Contract DTO hiện tại (`ContractDtos.cs:27-64`): CÓ `ApprovalWorkflowId` + `CurrentApprovalLevelOrder` + `LevelOpinions`, **THIẾU `CurrentWorkflowStepIndex` + THIẾU cây steps** (comment `:61`: Section 5 read-only đang fetch shape qua endpoint AwV2 riêng).
|
||||
- ⇒ **Phân xử: wave BE-DTO nên theo khuôn KHKK (cây thô + đủ cặp con-trỏ), KHÔNG mirror cặp DTO PE.** Lý do đo được: (i) panel mirror tiêu thụ ĐÚNG shape KHKK — chọn shape PE là phải VIẾT LẠI panel, hết "mirror"; (ii) KHKK là khuôn mới nhất qua 8 đợt UAT S179; (iii) delta BE nhỏ hơn (2 mảnh vs 2 DTO + logic precompute). Đây là quyết KỸ THUẬT — 2 tiền lệ đều sống, chọn cái khớp UI-khuôn đã chọn — KHÔNG cần owner (vào mục "tự trả lời được" §8).
|
||||
|
||||
**② fe-admin ẩn `Ct_*` bằng 2 TẦNG, không phải 1:** `fe-admin/Layout.tsx:248` `return ADMIN_HIDDEN_KEYS.has(key) || key.startsWith('Ct_')` — ngoài ẩn root đệ-quy còn PREFIX-RULE riêng cho `Ct_*`. FE invest §6 chỉ kê ADMIN_HIDDEN_KEYS. Wave admin-visibility (F5) nếu chạy phải gỡ **CẢ 2 chỗ** + nới regex `:117` — thiếu 1 = leaf drop im lặng (#50).
|
||||
|
||||
**③ Đường fetch cây workflow ĐÃ CÓ SẴN:** `ContractDtos.cs:61` comment khai FE Section 5 fetch flow qua `/api/approval-workflows-v2/{id}` — nghĩa là dù chưa sửa DTO, cây đã lấy được. Chọn inline (khuôn KHKK, 1 request, panel mirror nguyên) vẫn đúng hơn — nhưng implementer cần biết đường cũ để KHÔNG đẻ bản fetch thứ 3.
|
||||
|
||||
### 1c. Verdict tổng thể 3 bản
|
||||
|
||||
- **Invest BE:** mọi claim load-bearing tao re-verify độc lập đều đúng (12/12 bảng 1a + đối chiếu style-review §A3 100%). Wave-list W0-W5 hợp lý nhưng **thiếu wave DTO** (FLAG-2 đúng) và W3 gộp nhiều việc (tách ở §7).
|
||||
- **Invest FE:** đúng ở hiện trạng FE + gap DTO; sai 1 tiền-đề S156 (tự khai xin re-verify — FLAG-1); đề xuất DTO-khuôn-PE lệch với panel-khuôn-KHKK nó tự chọn (1b-① — không ai trong 3 bản bắt được).
|
||||
- **Style-review:** 7 flag đều ĐỨNG sau khi tao re-verify bằng code; FLAG-4 verdict đúng cả chi tiết invariant T6 (menu-row bắt buộc khi key vào All).
|
||||
|
||||
---
|
||||
|
||||
## §2 — FLAG-2 (wave MỒ CÔI — nặng nhất): định vị wave BE-DTO (mục 2 đề bài)
|
||||
|
||||
**Xác nhận gap bằng đo:** grep `CurrentApprovalDto|ApprovalFlowDto` trong `Application/Contracts` = 0 hit (control dương PE có — bảng 1a-#8). `ContractDetailDto` thiếu `CurrentWorkflowStepIndex` + cây steps + mọi trường nguồn-gốc KHKK. `Contract.cs` không mang con-trỏ ngược plan (link ở `ContractSigningPlanLine.ContractId`).
|
||||
|
||||
**Wave mới: W2 "BE-DTO nền hiển thị" — nội dung chốt theo phân xử §1b-①:**
|
||||
1. **(a) Nguồn-gốc KHKK (nuôi F1):** `ContractDetailDto` + `ContractSourceDto?` — reverse-join `ContractSigningPlanLines WHERE ContractId = id` → plan (`MaKeHoach`, `PlanId`, `ApprovalGroup`) + lines (`TenHangMuc[]`, Σ`ApprovedAmount`) + `PurchaseEvaluationId` (chuỗi sâu GĐ1). Null-path: HĐ cũ 0 dòng nối → field null, FE không render card.
|
||||
2. **(b) Con-trỏ + cây V2 (nuôi F2):** +`CurrentWorkflowStepIndex` (int?, đọc thẳng entity — `ResolveV2InboxIdsAsync:567` chứng field sống) + `List<ContractWorkflowStepDto>? WorkflowSteps` cây thô mirror `ContractSigningPlanFeatures.cs:194/:205` (steps + levels + tên NV + cờ per-level). **KHÔNG** sinh cặp DTO PE-precompute.
|
||||
3. Positional record: append field CÓ DEFAULT null ở cuối — wire cũ không vỡ; types/contracts.ts cập nhật ×2 app cùng wave.
|
||||
|
||||
**Vị trí trong thứ tự:** W2 chạy được **NGAY** — 0 phụ thuộc câu owner nào (nguồn-gốc + con-trỏ là bất biến mọi nhánh trục/PA; đường đọc qua Line là hiện trạng đã ship). Chặn-bởi-nó: F1 (card nguồn gốc) + F2 (màn duyệt) — cả 2 wave FE đứng sau W2 đúng 1 nhịp. Acceptance đo được: HĐ dry-run K8 (`ZZTEST-K8/...`) GET detail trả `source.maKeHoach = "KHKK/2026/00x"` đúng + HĐ tạo tay trả null; test DTO mới (baseline 668 tăng); build + cicd 3-chân.
|
||||
|
||||
---
|
||||
|
||||
## §3 — FLAG-1: chốt F3 = VERIFY-ONLY (mục 3 đề bài)
|
||||
|
||||
**Chốt hẳn bằng code (bảng 1a-#3):** `/contracts/inbox` ĐÃ V2-aware trên đĩa từ W6 S161 — `ContractFeatures.cs:530-593` đủ 3 mảnh (resolve đúng Cấp-đang-chờ + OR-of-N + thoát-sớm xét cả 2 nguồn). Chẩn đoán S156 "inbox 100% V1-LEGACY" **HẾT HIỆU LỰC**; FE tự khai xin re-verify là đúng kỷ luật, và re-verify ĐÃ XONG tại đây.
|
||||
|
||||
**Disposition trong plan:** F3 **KHÔNG còn là wave code** — hạ thành **hạng mục VERIFY trong wave dry-run cuối** (W10 §7): tạo HĐ V2 từ bridge → assert xuất hiện ở `/inbox?type=N` của approver Cấp-1, user ngoài Cấp không thấy. FE 0-đổi (đúng nhánh rủi ro FE tự viết). Việc kèm: diary investigator đính chính S156-stale (BE Surprise #2 đã ghi — lead nhắc vai FE cùng đính chính; 1 dòng, không wave).
|
||||
|
||||
⚠️ Ranh giới để KHÔNG đọc rộng verdict này: inbox V2-aware ≠ "màn duyệt xong" — cái thiếu THẬT cho GĐ3 vẫn là DTO (W2) + UI trạm (F2/W6); và per-type server-filter của inbox vẫn client-side (FE F4 đúng).
|
||||
|
||||
---
|
||||
|
||||
## §4 — FLAG-3: nhánh điều-kiện trục 7-type ⟂ 8-nhóm (mục 4 đề bài)
|
||||
|
||||
FE §5 + bảng F1-F5 viết CỨNG trục 7-type trong khi Q1 (BE#1) còn treo — plan phải mang nhánh điều-kiện, KHÔNG hardcode trước. Cách gắn vào plan §7:
|
||||
|
||||
| Mảnh việc | Nếu Q1 = **7-type** (default) | Nếu Q1 = **8-nhóm** | Nếu Q1 = **lai** |
|
||||
|---|---|---|---|
|
||||
| Discriminator phiếu | `Contract.Type` CÓ SẴN — 0-mig | +1 AddColumn `Contract.ApprovalGroup` (khuôn Mig 71, backfill đọc ngược `Line→plan.ApprovalGroup`) — mig điều-kiện (b) của BE §4 | như 8-nhóm + luật chọn trạm theo ngưỡng — cần owner vẽ ma trận |
|
||||
| Họ workflow W3-seed | 7 Code `HD-{TYPE}` | 8 Code `HD-N{n}` (mirror sát `KHKK-N{n}` hơn) | theo ma trận owner |
|
||||
| Guard khớp W4 | so `Contract.Type` ↔ token Code | so `Contract.ApprovalGroup` ↔ token Code | 2 vế |
|
||||
| Menu/param FE | GIỮ NGUYÊN 42 leaf `Ct_*` + `?type=N` — FE §5 đúng nguyên trạng, 0 key mới phía menu phiếu | menu phiếu theo nhóm = vẽ lại theo khuôn `Khkk_G*` (49 key S164-S169) — **đắt, đổi cả sidebar đã fan-out 7 loại anh đã thấy prod** | giữ `Ct_*` (định danh menu theo loại) + badge nhóm trong phiếu |
|
||||
| Key Designer §5 | +7 `AwV2_Hd*` | +8 `AwV2_HdN*` | theo số họ |
|
||||
| W1 (gỡ bom) · W2 (DTO) · F4 · F1-card | **KHÔNG ĐỔI ở mọi nhánh** — deactivate per-Code + overview family + nguồn-gốc + con-trỏ V2 là bất biến trục | ← | ← |
|
||||
|
||||
**Điểm nghiêng đo được (không quyết thay owner):** trục 7-type khớp (i) chữ YC-023, (ii) 42 leaf `Ct_*` đã seed + sidebar 7 mục anh xác nhận hình prod, (iii) `Contract.Type` sẵn = 0-mig. Trục 8-nhóm khớp chuỗi ký nháy SP-002 (dữ kiện thật: `InitialsChain`/`SignerRole` đi theo nhóm, không theo loại). Chi phí đổi-ý sau khi build: nhánh 7-type → 8-nhóm đắt ở W3+W4+FE-menu; W1/W2 vô cảm. ⇒ thứ tự wave §7 xếp W1/W2 (bất biến) TRƯỚC, W3/W4 SAU câu Q1 — plan không phải đoán.
|
||||
|
||||
---
|
||||
|
||||
## §5 — FLAG-4: QUYẾT TƯỜNG MINH họ key `AwV2_Hd*` (mục 5 đề bài)
|
||||
|
||||
**Quyết: CÓ sinh — trong nhánh PA-A (Q2), mirror TRỌN khuôn K3-N1, đặt trong W3 (cùng wave seed workflow).** Đây là quyết mức-lead (tiền lệ: 8 key KHKK vào `All` là "lệnh lead K3-N1" — `MenuKeys.cs:123` — không phải quyết owner), owner chỉ cần biết hệ quả số.
|
||||
|
||||
**Trade-off từng nhánh:**
|
||||
- **Nhánh SINH (+7 key `AwV2_Hd{Code}`, đề xuất):** admin sidebar có mục Designer per-loại (đúng UX KHKK đang chạy — fe-admin KHÔNG ẩn root `ApprovalWorkflowsV2`, họ `AwV2_KhkkN[1-8]` đang sống ở `fe-admin/Layout.tsx:188`); grant per-loại được (chỉ cho CCM sửa quy trình Thầu phụ). Giá: Menu keys 113→120, Policies 452→480 (DERIVED |All|×4) — **2 row canonical STATUS đổi CÙNG COMMIT** + test T2 khoá drift + **invariant T6: key vào `All` BẮT BUỘC có menu-row trong `SeedMenuTreeAsync`, thiếu = Permission FK nổ lúc boot** (`MenuKeys.cs:125-126` — bẫy nổ NGAY lần deploy đầu, không âm thầm).
|
||||
- **Nhánh KHÔNG SINH (chỉ Bom-2 family-panel):** rẻ hơn (0 key, 2 row STATUS giữ), Designer vào qua panel type-3 family. Mất: mục sidebar per-loại + grant per-loại. **Lệch khuôn đã deploy** — KHKK có CẢ HAI tầng (key sidebar + panel family), bỏ 1 tầng phải có lý do mà chưa ai nêu được lý do ngoài "tiết kiệm" ⇒ theo LUẬT LÕI không chọn.
|
||||
- **Phụ thuộc:** số key theo đáp án Q1 (7 hay 8 — bảng §4); nếu Q2 = PA-B thì Designer per-cột có thể thay đổi hình key ⇒ dòng key này thi công SAU khi Q1+Q2 chốt (đúng vị trí W3). KHÔNG phụ thuộc Q6 (admin-visibility chỉ bàn 4 root pipeline; root `ApprovalWorkflowsV2` không nằm trong `ADMIN_HIDDEN_KEYS`).
|
||||
- **Sửa lời FE invest:** câu "0 key mới" của FE §5 vẫn ĐÚNG cho **menu phiếu** (42 leaf `Ct_*` đủ) — chỉ SAI khi đọc thành "0 key toàn cục". Plan tách 2 mệnh đề này để không ai đọc nhầm lần nữa.
|
||||
|
||||
---
|
||||
|
||||
## §6 — FLAG-5/6/7: disposition từng nit (mục 6 đề bài)
|
||||
|
||||
| Flag | Nội dung | Disposition | Lý do |
|
||||
|---|---|---|---|
|
||||
| FLAG-5 🟡 | W1 predicate deactivate: giữ R-6 CẤM regex (tập-type tường minh) + sửa comment `:363` CÙNG edit | **NHẬN — vào acceptance W1** | Đo lại `:365-368` R-6 nguyên văn CẤM regex vì lý do sống (Code ngoài họ rơi nhầm nhánh nuke); comment `:363` không sửa = đẻ đúng bug-class 1-luật-2-site mà Surprise #6 BE đang tố. Cả 2 rẻ (1 edit), phòng bug đắt |
|
||||
| FLAG-6 🟡 | W3-seed mã họ ASCII UPPER + hằng 1-nhà riêng, KHÔNG đụng nhà KHKK | **NHẬN — vào acceptance W3** | Validator Designer `^[A-Za-z0-9._-]+$` (`:274-276`) — seeder không qua validator nhưng admin tạo version thì QUA ⇒ seed mã có "Đ" là kẹt vòng đời THẬT. Token họ mới cần nhà `GroupCodePattern` RIÊNG (guards KHKK `:44-45` tự khai "chép lần nữa = 3 bản chuỗi rời nhau") |
|
||||
| FLAG-7 🟡 | W4 "bổ 2 vế" cho Create bằng NHÀ CHUNG `public static`, 4 cửa gọi CÙNG method | **NHẬN — nâng thành yêu cầu kiến trúc W4 + fault-inject** | Đã có 2 bản inline (`ContractFeatures.cs:79-87` + bridge `:134-148`); inline lần 3 = nhân bản luật lần nữa. Acceptance: fault-inject 1 cửa quên gọi phải ĐỎ (khuôn test-before W1b S182) |
|
||||
|
||||
Cả 3 NHẬN — 0 bác, 0 hoãn. Chúng là nit thi-công đúng chỗ, chi phí ~0 khi biết trước, đắt khi phát hiện sau merge.
|
||||
|
||||
---
|
||||
|
||||
## §7 — TỔNG HỢP PLAN WAVE (mục 7 đề bài) — 11 wave W0..W10
|
||||
|
||||
> **Gate CHUẨN mọi wave code (nếp KHKK S161):** `dotnet build` 0-err + `npm run build` ×2 app 0-err (trọng tài chạy SAU agent dừng tay — #68) + `dotnet test` baseline `docs/STATUS.md` (668 @S185) GIỮ-hoặc-TĂNG theo scope + reviewer diff-review + cicd 3-chân (deploy xanh · marker-dương bundle/DLL · **control-âm ĐỘC QUYỀN** — chuỗi chỉ tồn tại trong bản mới) + SHA-pair `git hash-object` từng cặp file mirror cho wave FE. Wave BE nào 0-mig thì acceptance kèm "0 file Migrations đổi".
|
||||
|
||||
| W | Tên · phạm vi | File/khu vực đích | Phụ thuộc | Chờ câu nào (§8) | Chạy NGAY? |
|
||||
|---|---|---|---|---|---|
|
||||
| **W0** | **Chốt trục + roster** (0 code): trình danh sách §8, thu đáp án tối thiểu Q1/Q2/Q4 (Q3/Q5/Q6/Q9 lấy được cùng lúc càng tốt); ghi sổ YC mã vĩnh viễn | `docs/governance/so-yeu-cau-chu-du-an.md` | — | sinh ra đáp án | ✅ (việc của lead+owner) |
|
||||
| **W1** | **Gỡ 2 bom per-type** (BE, 0-mig): mở deactivate per-Code cho type-3 (tập-type tường minh — R-6 CẤM regex, FLAG-5) + sửa comment `:363` CÙNG edit + overview family type-3 (Bom-2) | `ApprovalWorkflowV2AdminFeatures.cs:205-215/:356-377` + test-before RED→GREEN (khuôn W3-KHKK): (i) POST version code loại X → IsActive code loại khác GIỮ (đếm SQL trước/sau); (ii) overview type-3 trả ĐỦ N active | — (đúng ở MỌI nhánh Q1/Q2 — §4 hàng cuối) | 0 | ✅ **NGAY** |
|
||||
| **W2** | **BE-DTO nền hiển thị** (BE, 0-mig — wave MỚI đóng FLAG-2, nội dung §2): (a) `ContractSourceDto` reverse-join nguồn KHKK; (b) +`CurrentWorkflowStepIndex` + `WorkflowSteps` cây thô **khuôn KHKK** (§1b-① — KHÔNG cặp DTO PE) | `ContractDtos.cs` + `ContractFeatures.cs` GetContract + `types/contracts.ts` ×2 app; mirror `ContractSigningPlanFeatures.cs:194/:205` | — (bất biến mọi nhánh) | 0 | ✅ **NGAY** |
|
||||
| **W3** | **Seed họ workflow type-3 + key Designer** (BE, 0-mig data-op): seeder per-Code idempotent mirror `SeedKhkkGroupWorkflowsAsync` (thiếu user → bỏ slot + warning, KHÔNG throw boot) + mã họ ASCII UPPER hằng 1-nhà riêng (FLAG-6) + **+7 key `AwV2_Hd*` vào `All` + menu-row + 2 row STATUS + T2/T6** (§5) | `DbInitializer.cs` (khuôn `:540-548/:607-625`) + `MenuKeys.cs` + `SeedMenuTreeAsync` + `docs/STATUS.md` 2 row | W1 (bom phải gỡ trước khi 7 active cùng type sống) + W0 | **Q1** (7 hay 8 Code) · **Q2** (PA-A/B) · **Q4** (roster — HARD: tên người thật, #51 seed UNGATED chạy thẳng prod lần deploy đầu) | ❌ |
|
||||
| **W4** | **Guard 4 cửa pin + re-pin** (BE, 0-mig): nhà chung `public static EnsureContractWorkflow*` (FLAG-7) gọi từ 4 cửa — Create (bổ 2 vế `IsActive∧IsUserSelectable`, xoá drift §1c-2) + bridge K7 (auto-resolve theo loại) + **cửa re-pin MỚI** trên UpdateDraft (+field `ApprovalWorkflowId` — §1c-3) + cửa TRÌNH trong `ContractWorkflowService` | `ContractFeatures.cs:79-87/:136-145` + `CreateContractFromSigningPlanFeatures.cs:132-148` + `ContractWorkflowService.cs` + file guards mới mirror `ContractSigningPlanWorkflowGuards` | W3 (guard so token họ đã seed) | **Q1/Q2** (so cái gì) · **Q9** (dialog còn dropdown?) | ❌ |
|
||||
| **W5** | **FE card "Nguồn gốc KHKK"** (F1): trong `ContractDetailContent` — mã KHKK link `/khkk/:planId?group=n` + badge N{n} + hạng mục + NCC + giá chốt; đối xứng chiều ngược "Đã có HĐ" | `components/contracts/` ×2 app SHA-pair; null-path HĐ cũ KHÔNG render | **W2(a)** | 0 | sau W2 |
|
||||
| **W6** | **FE màn duyệt V2-trạm** (F2): mirror `KhkkWorkflowPanel` (tự-suy từ con-trỏ + cây thô W2(b)) — banner lượt-ai + gating nút + stepper Bước→Cấp; GIỮ nhánh V1 2-nút cũ cho HĐ không pin (dual-render sẵn); bộ nút theo Q5; **kèm Đ2-guard nhánh Reject BE nếu Q3 duyệt** (màn mới wire nút Reject mà API còn thủng #82 thì gating FE = trang trí) | `components/contracts/` ×2 app + `ContractWorkflowService.cs:48-66` (phần Đ2) | **W2(b)** | **Q5** (2/3 nút) · **Q3** (vế Đ2) | ❌ |
|
||||
| **W7** | **FE per-type server-filter** (F4): `MyContractsPage`/`InboxPage` gửi `type` lên BE thay client-filter (param BE CÓ SẴN — style-review §E verify `ContractsController.cs:23-27`) | `MyContractsPage.tsx:42-48` + `InboxPage.tsx:91` ×2 app; đổi queryKey → invalidate cache cũ | — | 0 | ✅ **NGAY** |
|
||||
| **W8** | **Authz diện rộng** (điều kiện Q3 = Đ1/Đ3): ~24 per-action policy `ContractsController` + grant seeder code-first (#84 CẤM SQL tay) + extend `AuthorizePolicyRegressionTests` + FE gate `Contracts.*` (#85) | `ContractsController.cs` + `DbInitializer` + tests (khuôn `KhkkBridgeAuthzTests` 245 dòng) | W6 (biết bộ nút/luồng trước khi siết) | **Q3** (+Q10 nếu per-loại) | ❌ |
|
||||
| **W9** | **Admin-visibility GĐ3** (điều kiện Q6 = admin thấy): gỡ `'Contracts'` khỏi `ADMIN_HIDDEN_KEYS` **+ gỡ prefix-rule `key.startsWith('Ct_')` `:248`** (§1b-② — 2 TẦNG, thiếu 1 = drop câm #50) + nới regex admin `:117` thêm 3 leaf + `Ct_*_WfView` admin trỏ Designer | `fe-admin/Layout.tsx:117/:227-248` | — | **Q6** (giữ-ẩn = HỦY wave) | ❌ |
|
||||
| **W10** | **Dry-run toàn trình + nghiệm thu** (khuôn K8 S168): 2 nhánh 2 loại HĐ 2 quy trình khác nhau, trọn PE→KHKK→bridge→duyệt V2 từng trạm→`DaPhatHanh`→SealedCopy trên Dev; **verify F3 inbox tại đây** (§3 — HĐ V2 ChoDuyet hiện đúng inbox approver Cấp-1, user ngoài 0 thấy); probe authz #87-aware (415/411 = PRE-AUTH); sổ ID phiếu + seq cháy (bài K8: phiếu test lệch số phiếu thật — tiền lệ KHKK/2026 001-003 đều test) | Dev DB + sổ trong run-folder; lưu ý Surprise #7 BE: Dev 0 workflow type-1/2 → tạo workflow PE trước | W3+W4+W5+W6 | 0 (chạy sau khi các câu đã chốt) | ❌ |
|
||||
|
||||
**Đường tới hạn:** W0 → W3 → W4 → W6 → W10 (mọi wave khác song song/chèn xen kẽ). **3 wave chạy NGAY hôm nay không cần owner: W1 + W2 + W7** — đủ việc cho 1 nhịp fan-out đầu trong lúc W0 thu đáp án. Nhịp ship theo khuôn S159-⑦ {đợt nhỏ → ship → anh nhìn prod → vẽ tiếp}.
|
||||
|
||||
**Khác biệt so wave-list 2 invest (khai để lead đối chiếu):** +W2 MỚI (FLAG-2); W3-BE cũ tách thành W3-seed ⟂ W4-guard (2 mối chặn khác nhau: W3 chặn bởi roster, W4 chặn bởi UX-dialog — gộp là wave nghẽn kép); F3 hạ khỏi wave-list vào W10-verify (FLAG-1); F5 thành W9 điều-kiện-huỷ-được; key Designer (FLAG-4) nhét W3 cùng nhịp seed.
|
||||
|
||||
---
|
||||
|
||||
## §8 — HỢP NHẤT 12 CÂU CHỜ-ANH → 9 câu hỏi owner + 3 mục tự-trả-lời (mục 8 đề bài)
|
||||
|
||||
> Gộp: BE#5 ≡ FE#2 + FE#5 → **Q3** (1 cụm authz — style-review C4 map đúng, tao nhập luôn FE#5 vì cùng 1 quyết định posture). FE#4 → **Q3b** (phụ đề trong cụm, phụ thuộc Q1). BE#7 → **Q7**. Còn lại 1-1. Mã gốc ghi kèm để truy vết.
|
||||
|
||||
### 8a. Danh sách hỏi owner (9 câu — mỗi câu: phát biểu · đề xuất default · wave bị chặn)
|
||||
|
||||
1. **Q1 [BE#1] — Quy trình duyệt HĐ chia theo TRỤC nào?** 7 **loại hợp đồng** (Thầu phụ, Giao khoán, NCC, Dịch vụ, Mua bán, 2 loại Nguyên tắc — như menu 7 mục anh đang thấy) hay 8 **nhóm duyệt N1-N8** của danh mục SP-002 (như bên Kế hoạch ký kết), hay lai (trạm theo nhóm + người ký theo ngưỡng)? → **Đề xuất: 7 loại HĐ** (khớp lời anh trong YC-023 + menu đã dựng; đổi sang nhóm sau vẫn được nhưng đắt ở seed+guard+menu — bảng §4). **Chặn W3, W4** (+hình dạng key §5).
|
||||
2. **Q2 [BE#2] — Cách gắn quy trình vào loại:** làm y hệt cách Kế hoạch ký kết đang chạy (mỗi loại 1 mã quy trình riêng, 0 đổi cấu trúc DB — PA-A) hay thêm 1 cột thật trong bảng quy trình (1 migration, "chuẩn" hơn về lâu dài — PA-B)? → **Đề xuất: PA-A trước** (khuôn đã qua UAT, style-review chấm khớp nhất; PA-B để dành làm bước gộp sau nếu cần). **Chặn W3, W4.**
|
||||
3. **Q3 [BE#5 ≡ FE#2 + FE#5] — Khoá quyền module HĐ tới mức nào?** Hiện MỌI người đăng nhập đều Từ-chối/Trả-lại được bất kỳ HĐ nào (lỗ S156 anh treo *"chỗ hợp đồng cứ từ từ"*) — nhưng YC-023 chính là mở màn duyệt HĐ cho nhiều người dùng thật. 3 mức: **Đ2** chỉ vá đúng lỗ nguy hiểm (chặn Từ-chối sai lượt/sai vai) · **Đ1** khoá đủ bộ ~24 endpoint như bên KHKK · **Đ3** = Đ1 + rà trọn 2 tầng menu/API. Menu HĐ đang mở 13/13 vai ("hiển thị hết để góp ý") — giữ hay thu? → **Đề xuất: Đ2 làm NGAY trong W6** (màn duyệt mới mà API thủng thì nút khoá chỉ là trang trí) **+ Đ1 thành W8 riêng sau khi anh nhìn màn mới; menu giữ public như anh đã chốt.** *(Q3b [FE#4]: có ai chỉ được duyệt MỘT loại HĐ không? Nếu không — giữ quyền chung `Contracts.*`, đỡ 1 tầng phức tạp.)* **Chặn W8 + vế Đ2 của W6.**
|
||||
4. **Q4 [BE#6] — Roster người duyệt cho 7 quy trình HĐ:** mỗi loại HĐ đi qua NHỮNG AI, thứ tự trạm nào? (Bên KHKK sao từ `QT-DN-V2-001` + 11 email cứng; SP-002 chỉ có pattern ký nháy per-NHÓM — KHÔNG tự suy ra per-loại được.) → **Không có default — cần anh cho danh sách** (bảng: Loại HĐ → trạm 1..n → tên người). **Chặn CỨNG W3** (seed chạy thẳng prod lần deploy đầu — #51, sai roster là sai trên prod).
|
||||
5. **Q5 [FE#1] — Bộ nút màn duyệt HĐ:** 2 nút **Duyệt / Trả lại** (như KHKK sau khi anh trim *"bỏ luôn từ chối"*) hay 3 nút thêm **Từ chối** (như Duyệt NCC)? → **Đề xuất: 2 nút theo KHKK** (khuôn mới nhất chính tay anh trim; máy BE Reject vẫn còn nếu sau cần). **Chặn phần dialog W6.**
|
||||
6. **Q6 [FE#3] — Trang quản trị (admin) có cần THẤY menu GĐ3 không**, hay giữ như hiện nay (admin ẩn cả 4 mục pipeline, nghiệp vụ nằm bên eoffice)? → **Đề xuất: giữ ẩn** (đúng posture KHKK đang chạy; trang vẫn tồn tại, vào bằng URL được). Q6 = giữ ẩn ⇒ **HỦY W9** (0 công). **Chặn W9.**
|
||||
7. **Q7 [BE#7] — Dialog "Đưa vào HĐ":** khi đã có quy trình đúng loại, máy tự chọn — còn cho người bấm ĐỔI TAY quy trình khác không? → **Đề xuất: tự chọn + KHÔNG cho đổi chéo loại** (mirror KHKK; pin nhầm sửa ở phiếu Nháp qua cửa re-pin W4). **Ảnh hưởng W4** (không chặn cứng — làm theo default được, đổi sau 1 edit nhỏ).
|
||||
8. **Q8 [BE#3] — Danh mục SP-002 có nên thêm cột "gợi ý loại HĐ"** để dialog tự điền loại (đỡ chọn tay/chọn nhầm)? → **Đề xuất: CHƯA** — giữ chọn tay (hiện trạng); thêm sau = 1 migration nhỏ + cập nhật seed 86 dòng (mig điều-kiện (c) BE §4). **Không chặn wave nào.**
|
||||
9. **Q9 [BE#4] — Ruột HĐ ở GĐ3 có cần bảng hạng mục chi tiết** (khối lượng / đơn giá / thành tiền — phụ lục 3.5 SP-002) hay chỉ cần điều khoản + tổng giá trị (hiện trạng: tên hạng mục ghép vào tên HĐ)? → **Đề xuất: CHƯA** (spec-memory: GĐ3 "bàn điều khoản, không bàn hạng mục"); nếu cần = wave line-level MỚI sau này. **Không chặn wave nào.**
|
||||
|
||||
**Tối thiểu để mở khoá đường tới hạn: Q1 + Q2 + Q4** (mở W3/W4). Q3/Q5 cần trước W6. Q6-Q9 trả lúc nào cũng được.
|
||||
|
||||
### 8b. Tự-trả-lời-được (KHÔNG hỏi owner — bằng chứng kèm)
|
||||
|
||||
- **T1 — Inbox "Đang duyệt" có cần wave BE không? KHÔNG.** Code đã V2-aware từ W6 S161 (`ContractFeatures.cs:530-593` — bảng 1a-#3); F3 = verify trong W10. (Đóng luôn tiền-đề S156-stale; diary FE đính chính.)
|
||||
- **T2 — Khuôn DTO màn duyệt: cây-thô KHKK, không phải cặp DTO PE.** Panel mirror (`KhkkWorkflowPanel:19-25`) tiêu thụ đúng shape con-trỏ + cây thô (`ContractSigningPlanFeatures.cs:194/:205`); chọn shape PE = viết lại panel, hết mirror (§1b-①). Kỹ thuật thuần — 2 tiền lệ đều sống, chọn cái khớp UI đã chọn.
|
||||
- **T3 — Sinh key Designer `AwV2_Hd*` (trong nhánh PA-A): CÓ.** Tiền lệ K3-N1 là lệnh-lead không phải quyết-owner (`MenuKeys.cs:123`); bỏ mảnh khuôn phải có lý do — chưa ai nêu được (§5). Owner chỉ cần biết 2 số canonical đổi (113→120 · 452→480, theo đáp án Q1).
|
||||
|
||||
== HẾT ==
|
||||
@ -0,0 +1,202 @@
|
||||
# sub-reviewer-dot1 — review ADVERSARIAL diff đợt-1 YC-023 (S187, 2026-08-10)
|
||||
|
||||
> Vai: reviewer (READ-only, verdict-only). Diff = working-tree `git diff` TRƯỚC commit.
|
||||
> Spec: `spec-duyet-hd-10-08-2026.md` · nền: `sub-plan-review-fable.md §2/§6`.
|
||||
> Nguyên tắc phiên này: **claim nào cũng phải có lệnh đo kèm**; 0-hit nào cũng phải có **control-dương**.
|
||||
|
||||
---
|
||||
|
||||
## 0. Phạm vi đo được (không suy)
|
||||
|
||||
```
|
||||
git diff --stat → 9 file trong scope:
|
||||
3 BE : ApprovalWorkflowV2AdminFeatures.cs (+80/-13) · Contracts/ContractFeatures.cs (+89/-1)
|
||||
· Contracts/Dtos/ContractDtos.cs (+76/-1)
|
||||
1 test: tests/.../Application/UpdateAwDefinitionTests.cs (+295/-0)
|
||||
5 FE : fe-admin{types/contracts.ts +61, pages/contracts/ContractsListPage.tsx +44/-28}
|
||||
fe-user{types/contracts.ts +61, pages/InboxPage.tsx +12, pages/contracts/MyContractsPage.tsx +25/-6}
|
||||
Ngoài scope nhưng đang M (bookkeeping của lead, KHÔNG review): agent-memory/implementer-backend/**,
|
||||
.session-counter.json, docs/HANDOFF.md, docs/governance/so-yeu-cau-chu-du-an.md
|
||||
Migrations/ : 0 file (git status) ⇒ đúng "wave 0-mig".
|
||||
```
|
||||
|
||||
**Phép đo tôi tự chạy (không mượn của trọng tài):**
|
||||
|
||||
| # | Lệnh | Kết quả |
|
||||
|---|---|---|
|
||||
| Đ1 | `dotnet test tests/SolutionErp.Infrastructure.Tests --filter "W1a_|W1b_"` | **Failed 0 · Passed 4 · Total 4** (5 s) — 4 test-before nay XANH thật |
|
||||
| Đ2 | (hệ quả Đ1) build project `Application`+`Domain`+`Infrastructure`+`Tests` | 0 err — **BE diff biên dịch được, tôi tự chứng**. ⚠️ chưa tự chạy: project `Api`, `npm run build` ×2 app (nhận theo claim trọng tài) |
|
||||
| Đ3 | sha256 khối `+` của 2 file `types/contracts.ts` | admin 61 dòng / user 61 dòng, **sha `5a56f59dd8eeb479…` IDENTICAL** ⇒ SHA-pair mirror ĐẠT |
|
||||
| Đ4 | grep `TODO|FIXME|HACK|password|secret|Bearer …` trên toàn diff 9 file | **0 hit** — control-dương cùng lưới (`S187|typeFilter`) = **32 hit** ⇒ lưới sống, 0-hit là sạch THẬT |
|
||||
| Đ5 | grep `console.log|alert(|// Mock` trên diff FE | 0 hit (cùng control Đ4) |
|
||||
| Đ6 | grep chuỗi hiển thị mới trong diff FE | 0 chuỗi UI mới (mọi dòng khớp đều là **comment**) ⇒ mục "UI tiếng Việt" N-A theo hướng an toàn; chuỗi BE mới duy nhất `"(chưa gán hạng mục)"` = tiếng Việt |
|
||||
| Đ7 | `grep -rn "activeOthers" fe-admin/src fe-user/src` | **0 hit** — control-dương `levelOpinions` = 10+ file ⇒ 0-hit THẬT (xem F-3) |
|
||||
| Đ8 | `grep -rn "new ContractDetailDto(" src tests` | **1 site** (`ContractFeatures.cs:803`) ⇒ append-CUỐI an toàn (đo, không suy) |
|
||||
| Đ9 | `grep -rn AwTypeSummaryDto` toàn kho | dựng: 1 site (`:239`); FE `fe-user/src/types/approvalWorkflowV2.ts:54` là type **structural** ⇒ chèn giữa record không vỡ biên dịch chỗ nào |
|
||||
|
||||
---
|
||||
|
||||
## 1. SOI-1 — W1 (per-Code type-3 + overview family)
|
||||
|
||||
### 1a. Tập-type TƯỜNG MINH, không regex (R-6) — ĐẠT
|
||||
|
||||
`ApprovalWorkflowV2AdminFeatures.cs:372-377`:
|
||||
```csharp
|
||||
private static readonly ApprovalWorkflowApplicableType[] PerCodeDeactivationTypes =
|
||||
[ ApprovalWorkflowApplicableType.ContractSigningPlan, ApprovalWorkflowApplicableType.Contract, ];
|
||||
```
|
||||
`:422` `PerCodeDeactivationTypes.Contains(typeEnum)` — 0 regex, 0 pattern trên `Code`. Hai nhánh truy vấn giữ nguyên từng vế (per-Code 3 vế `Code == code && ApplicableType == typeEnum && IsActive` · per-TYPE 2 vế). **R-6 tôn trọng.** Test `W1a_...WithOutOfFamilyCode...` phủ đúng cả 2 chân (mã ngoài họ không nuke 7 loại · vẫn ratchet chính mã đó).
|
||||
|
||||
### 1b. Hành vi type khác GIỮ Active-đơn — ĐẠT
|
||||
|
||||
Chỉ 2 type vào danh sách; test `W1b_OverviewForSingleActiveType_...` chốt type-1 vẫn đúng-1-đương-nhiệm và không lẫn type khác. Đ1 xanh.
|
||||
|
||||
### 1c. Comment `:363` (neo cũ; sau sửa nằm `:404-425`) khớp code mới — ĐẠT
|
||||
|
||||
Câu tự-khai cũ ("các loại khác: per-TYPE như cũ — 1 active/type là bất biến của chúng", vốn sẽ thành SAI khi type-3 mọc 7 quy trình) đã bị thay bằng câu neo vào **TẬP TYPE** + giữ nguyên vết sai cũ có nhãn. Đạt AC W1(iii).
|
||||
|
||||
### 1d. Overview family: không leak type khác, không đôi bản ghi — ĐẠT
|
||||
|
||||
`:237` `var actives = versions.Where(v => v.IsActive).ToList();` với `versions` đã lọc `d.ApplicableType == type` ⇒ 0 leak. `Active = actives.FirstOrDefault()` + `ActiveOthers = actives.Skip(1)` ⇒ **hợp = trọn tập, giao = rỗng**, và `Active` giữ **đúng phần tử cũ** (`versions.FirstOrDefault(v => v.IsActive)`) ⇒ 0 rủi ro cho `ApprovalWorkflowsV2Page.tsx:381-389/455-479` đang đọc `t.active`.
|
||||
|
||||
### 1e. 🔴 GIẢ THUYẾT ĐỐI KHÁNG CỦA TÔI — BỊ BÁC BẰNG ĐO (ghi để đừng phá)
|
||||
|
||||
Nghi vấn: W1a **gỡ bỏ bảo đảm "1 active / type-3"** ⇒ nơi nào đang bốc "cái active của type-3" sẽ bốc ngẫu nhiên (bệnh cardinality-change, memory `feedback_cardinality_change_grep_consumers`). Đo 3 consumer:
|
||||
- BE tạo HĐ: `ContractFeatures.cs:37/79/109` — `ApprovalWorkflowId` **đến từ request**, BE KHÔNG tự chọn bản active nào ⇒ không có đường bốc ngẫu nhiên.
|
||||
- `fe-user/src/pages/contracts/ContractCreatePage.tsx:334-342` — đọc `typeBucket.history` rồi `.filter(isUserSelectable)`, user tự chọn.
|
||||
- `KhkkDetailPage.tsx:216-228` (**cả 2 app**) — đọc `history` rồi `.filter(isUserSelectable && isActive)`.
|
||||
⇒ **Không consumer nào đọc `bucket.active` cho type-3.** Nghi vấn ĐỔ. Đây là điểm kháng cự được soi.
|
||||
|
||||
---
|
||||
|
||||
## 2. SOI-2 — W2 BE (Source reverse-join + cây thô)
|
||||
|
||||
| Kiểm | Kết quả |
|
||||
|---|---|
|
||||
| reverse-join qua `ContractSigningPlanLines` | `ContractFeatures.cs:721-725` `.Where(l => l.ContractId == c.Id)` — `ContractSigningPlanLine.cs:46` xác nhận `ContractId` là con-trỏ; `Contract` không mang con-trỏ ngược ⇒ hướng join ĐÚNG |
|
||||
| null-path HĐ 0 dòng nối | `:726` `if (planLines.Count > 0)` ⇒ `sourceDto` giữ null ⇒ JSON `source: null`. ĐẠT |
|
||||
| `AsNoTracking` | có ở **cả 4** truy vấn mới (lines `:721` · plan `:734` · workflow `:758` · departments `:775`) + `userManager.Users.AsNoTracking()` `:766`. ĐẠT |
|
||||
| N+1 | **KHÔNG**. Đếm query THÊM cho 1 GET detail: tối thiểu **1** (chỉ lines), tối đa **5** (lines · plan · workflow-Include · users · departments). Tên người + tên phòng gom **1 lượt** ngoài vòng lặp (`:766-778`) ⇒ 0 truy vấn trong loop |
|
||||
| positional record append-CUỐI | 3 field mới đứng CUỐI + default `null` (`ContractDtos.cs:65-91`); Đ8 chứng chỉ **1 call-site** ⇒ wire cũ không vỡ |
|
||||
| load chỉ khi `ApprovalWorkflowId != null` | `:756` `if (c.ApprovalWorkflowId is Guid awIdTree)`. ĐẠT (V1 legacy ⇒ `workflowSteps: null`) |
|
||||
| cây mirror khuôn KHKK, KHÔNG precompute status | `ContractWorkflowLevelDto` (`ContractDtos.cs:129`) `(Id, Order, Name, ApproverUserId, ApproverFullName, AllowApproverFinalize)` vs KHKK `ContractSigningPlanWorkflowLevelDto` (`ContractSigningPlanFeatures.cs:131-140`) — **khớp từng field**; Step (`ContractDtos.cs:119`) `(Id, Order, Name, DepartmentId, Levels, DepartmentName = null)` vs KHKK `:142-152` — **khớp từng field**; **0 field `Status`** ở cả 2 bên. ĐẠT |
|
||||
| Σ tiền | `linesOfPlan.Sum(l => l.ApprovedAmount ?? 0)` **in-memory** (lý do SQLite-decimal khai trong comment, đúng) |
|
||||
| soft-delete | `HasQueryFilter(x => !x.IsDeleted)` xác minh TẠI ĐĨA: `ContractSigningPlanLineConfiguration.cs:52` + `ContractSigningPlanConfiguration.cs:55` ⇒ comment cite ĐÚNG, không cần `.Where` tay |
|
||||
| an ninh | IDOR guard chạy ở `:614-638`, **TRƯỚC** khối W2 (`:712+`) ⇒ không rò mã KHKK/PE-id cho người không được xem HĐ. ĐẠT |
|
||||
| cite trong comment | spot-check 3/3 đúng: `Line.cs:46` (ContractId) · bridge `:238` (`foreach … l.ContractId = contract.Id`) · `:121` (`"(chưa gán hạng mục)"` — **đúng nguyên văn**) |
|
||||
|
||||
---
|
||||
|
||||
## 3. SOI-3 — FE
|
||||
|
||||
### 3a. types ×2 app: khối THÊM identical — ĐẠT (Đ3)
|
||||
|
||||
### 3b. 🔴 SHAPE **KHÔNG** KHỚP JSON BE — 3 tên field + 1 nullability (F-1, F-2 dưới)
|
||||
|
||||
Đối chiếu **từng field**, C# → camelCase (chứng: `Program.cs:33-34` chỉ thêm `UtcDateTimeJsonConverter`, **KHÔNG** set `PropertyNamingPolicy`; `grep JsonPropertyName src/.../Contracts/` = 0 hit ⇒ camelCase mặc định):
|
||||
|
||||
| BE (C#) | JSON thật | FE khai | Verdict |
|
||||
|---|---|---|---|
|
||||
| `ContractDetailDto.Source` | `source` | `contractSource` (admin `:182` · user `:186`) | ❌ **LỆCH** |
|
||||
| `ContractSourceDto.PlanId` | `planId` | `planId` | ✓ |
|
||||
| `MaKeHoach` (`string?`) | `maKeHoach: string\|null` | `maKeHoach: string` (non-null) | ❌ **lệch nullability** |
|
||||
| `ApprovalGroup` (`int?`) | `approvalGroup` | `approvalGroup?: number\|null` | ✓ |
|
||||
| `PurchaseEvaluationId` (`Guid?`) | `purchaseEvaluationId` | `purchaseEvaluationId?: string\|null` | ✓ |
|
||||
| `TenHangMucs` (`List<string>`) | `tenHangMucs` | `tenHangMuc` (thiếu **s**) | ❌ **LỆCH** |
|
||||
| `ApprovedAmountTotal` (`decimal`) | `approvedAmountTotal` | `totalApprovedAmount` (**đảo thứ tự từ**) | ❌ **LỆCH** |
|
||||
| `CurrentWorkflowStepIndex` | `currentWorkflowStepIndex` | `currentWorkflowStepIndex` | ✓ |
|
||||
| `WorkflowSteps` | `workflowSteps` | `workflowSteps` | ✓ |
|
||||
| `ContractWorkflowStepDto` 6 field | id/order/name/departmentId/levels/departmentName | khớp 6/6 | ✓ |
|
||||
| `ContractWorkflowLevelDto` 6 field | id/order/name/approverUserId/approverFullName/allowApproverFinalize | khớp 6/6 | ✓ |
|
||||
|
||||
### 3c. ContractsListPage 3 fix — ĐẠT
|
||||
|
||||
- server-filter: `type: typeFilter ?? undefined` trong params; vế `items.filter(c => c.type === typeFilter)` **đã gỡ** (grep còn lại chỉ là comment).
|
||||
- badge total (`:119`,`:128`) + phân trang (`:216`,`:219`,`:231`) đọc `list.data.total` — nay là tổng **đã lọc** vì BE lọc TRƯỚC `CountAsync` (`ContractFeatures.cs:322` `if (request.Type is not null) …` → `CountAsync` → `Skip/Take`, đọc tại đĩa) ⇒ 2 lỗi (b)(c) tự đóng theo (a). Claim "3 fix" ĐỨNG.
|
||||
- rào `NaN`: `Number.isFinite(parsedType) && parsedType > 0` ⇒ `?type=abc`/`?type=0` không lên dây. Tốt.
|
||||
|
||||
### 3d. InboxPage CHỈ comment, 0 đổi hành vi — ĐÚNG NHƯ KHAI
|
||||
|
||||
`git diff` = +12 dòng, **toàn comment**; vế lọc client còn nguyên (`InboxPage.tsx:103`). Tiền đề của quyết định KIỂM TẠI ĐĨA và **ĐÚNG**: `ContractsController.cs:41-43` `Inbox(CancellationToken ct)` → `GetMyInboxQuery` **không tham số** (`ContractFeatures.cs:479`), và `:550` `q.Take(100)`. ⇒ xem F-4 (AC vs thực tế).
|
||||
|
||||
### 3e. MyContractsPage — ĐẠT
|
||||
|
||||
`type` gửi cho **cả 2 nhánh** (thường + deleted; param có thật ở `ContractsController.cs:25` và `:34`); `queryKey: ['my-contracts', typeFilter, phaseFilter, showDeleted]` đã mang `typeFilter` từ trước ⇒ đổi nguồn không đẻ ô cache sai; vế lọc client gỡ **cùng lúc** với `typeFilter` khỏi deps của `useMemo` (không để deps thừa/thiếu).
|
||||
|
||||
---
|
||||
|
||||
## 4. SOI-4 — cross-cut
|
||||
|
||||
0 secret (Đ4) · 0 file `Migrations/` · 0 file ngoài scope trong 9 file review · 0 TODO bỏ quên (Đ4) · 0 chuỗi UI mới, chuỗi BE mới là tiếng Việt (Đ6) · commit chưa tạo (review TRƯỚC commit, đúng cửa).
|
||||
|
||||
---
|
||||
|
||||
## 5. FINDING (nặng → nhẹ)
|
||||
|
||||
### F-1 🔴 MAJOR — hợp-đồng FE↔BE VỠ 3 TÊN FIELD; tsc/build XANH vì chưa ai đọc
|
||||
|
||||
- `fe-admin/src/types/contracts.ts:182` + `fe-user/src/types/contracts.ts:186` → `contractSource`, BE trả **`source`** (`ContractDtos.cs:87`).
|
||||
- admin `:204` / user `:208` → `tenHangMuc`, BE trả **`tenHangMucs`** (`ContractDtos.cs:79`).
|
||||
- admin `:206` / user `:210` → `totalApprovedAmount`, BE trả **`approvedAmountTotal`** (`ContractDtos.cs:82`).
|
||||
- **Chính file FE tự treo cờ** (admin `:193-195` / user `:197-199`): *"nếu BE lane đặt property `Source` thì đây là 1 dòng rename"* — **điều kiện đã xảy ra**, cờ chưa đóng. Spec AC W2 (`spec:26`) viết `source.maKeHoach` ⇒ **BE theo spec, FE lệch spec**.
|
||||
- Vì sao đây là MAJOR chứ không phải "sửa sau": 3 field hiện **0 consumer** ⇒ `tsc`/`npm run build`/674 test **KHÔNG bắt được**. Đến W5 (card "Nguồn gốc KHKK") thì `detail.contractSource` = `undefined` ⇒ card **không bao giờ render**, và theo đúng lời chú thích của chính file (*"vắng key = payload chưa có W2"*) sẽ bị đọc thành "HĐ tạo tay" — **sai câm, đúng cái bẫy mà comment cảnh báo**.
|
||||
- Acceptance để đóng: FE đổi 3 tên (hoặc BE đổi 3 tên) sao cho **tên hai bên trùng nhau**, và AC W2 của spec được đối chiếu; kèm 1 phép chứng chạy được — ví dụ GET detail 1 HĐ có nguồn KHKK rồi so **tập khoá JSON** với tập field FE, hoặc dán payload thật vào PR-note. Ai sửa, sửa bên nào = quyền lead.
|
||||
|
||||
### F-2 🟠 MAJOR (cùng ổ F-1) — `maKeHoach` khai NON-NULL trong khi BE cho phép null
|
||||
|
||||
`ContractFeatures.cs:730-733`: `plan` lấy bằng `FirstOrDefaultAsync` **qua global filter `!IsDeleted`** ⇒ phiếu xoá mềm ⇒ `plan == null` ⇒ `MaKeHoach: null`. Chính comment DTO (`ContractDtos.cs:70-75`) khai rõ "3 field nullable = dữ liệu PHIẾU CHA, null khi phiếu không đọc được nữa". FE khai `maKeHoach: string` ⇒ nơi đọc sẽ không bị TS ép null-check ⇒ `source.maKeHoach.trim()`/`.slice()` nổ runtime ở ca hiếm. Acceptance: `maKeHoach: string | null`.
|
||||
|
||||
### F-3 🟠 MAJOR — W1b: `ActiveOthers` **defined-but-unread**; khiếm khuyết UI mà nó sinh ra để chữa VẪN CÒN
|
||||
|
||||
- Đ7: `activeOthers` **0 hit** trong `fe-admin/src` + `fe-user/src` (control-dương `levelOpinions` 10+ file).
|
||||
- Màn hình vẫn đọc **đúng 2 ngả cũ**: thẻ "đang áp dụng" ← `t.active` (`ApprovalWorkflowsV2Page.tsx:381-389`, `:455-479`) · mục "phiên bản cũ" ← `type.history.filter(d => !d.isActive)` (`:485`, `:492`). ⇒ với type-3 nhiều active, **N-1 bản vẫn không xuất hiện ở đâu** — đúng nguyên văn triệu chứng mà comment BE `:229-235` mô tả.
|
||||
- Test W1b XANH **không mâu thuẫn** với điều đó: `SurfacedAsCurrent` (`UpdateAwDefinitionTests.cs:1614-1629`) đo **hình dạng DTO bằng reflection**, không đo màn hình. Đây là acceptance đo-shape, hợp lệ cho BE, **không** chứng minh "bom-2 đã tháo".
|
||||
- 🔴 Điểm đắt hơn: **FE đã có sẵn khuôn chữa mà KHÔNG cần BE đổi gì** — panel nhóm KHKK cắt lại từ `history` rồi tự suy `active` (`:330-341`), và `history` vốn chứa CẢ bản đang áp dụng. Nếu panel HĐ đi theo đúng khuôn đó (rất có khả năng, vì W6 chọn mirror KHKK) thì `ActiveOthers` là **field chết vĩnh viễn** — thêm 1 ngả dữ liệu thứ hai cho cùng một sự thật (`History` đã đủ), tức đẻ thêm chỗ để lệch.
|
||||
- Acceptance để đóng — chọn **một**: (i) chỉ mặt wave sẽ đọc `activeOthers` và ghi vào `spec §2`/`sub-plan-review §7` (kèm file FE đích), hoặc (ii) bỏ `ActiveOthers`, chữa ở FE bằng lát-cắt-theo-Code sẵn có, giữ nguyên 4 test (test cố ý XANH với **cả hai** hình dạng vá — chính nó khai vậy ở `:1610-1612`). Điều KHÔNG được: tuyên "W1b đã chữa xong bom-2 giấu-6" khi màn hình chưa đổi một pixel.
|
||||
|
||||
### F-4 🟠 MAJOR — AC W7 không đạt TRỌN, và phần thiếu đang nằm trong **comment** chứ không trong sổ
|
||||
|
||||
- `spec:17` giao *"InboxPage.tsx:91 ×2 app gửi `?type=` lên BE (param sẵn `ContractsController.cs:23-27`)"* — **tiền đề của spec SAI**: endpoint inbox không có param (đo ở §3d). Implementer **không làm** và khai 12 dòng comment — **quyết định đúng** (gửi `?type=` lên endpoint không nhận = nuốt câm; gỡ filter client = giết câm bộ lọc 7 leaf `Ct_*_Pending` qua `Layout.tsx:150`).
|
||||
- Nhưng AC `spec:27` (*"request inbox/list mang `?type=N`"*) vì thế **CHƯA ĐẠT**, và **nợ thật** `ContractFeatures.cs:550 q.Take(100)` (hộp thư >100 dòng ⇒ loại hiếm vẫn biến mất im lặng) hiện chỉ sống trong comment — **không có mã wave, không ai canh**.
|
||||
- Ghi thêm cho khỏi đo nhầm về sau: `fe-admin` **không có** `pages/InboxPage.tsx` ⇒ "×2 app" cho inbox vốn không đo được; "×2 app" của W7 trên thực tế = `ContractsListPage` (admin) + `MyContractsPage` (user), **cả hai đã làm**.
|
||||
- Acceptance: lead sửa AC W7 trong spec (ghi rõ inbox out-of-scope + lý do) **và** mở một mục nợ có mã ("thêm `type` + bỏ trần 100 cho `GET /contracts/inbox`") ở nơi có người canh, không để trong comment.
|
||||
|
||||
### F-5 🟡 minor — cùng-lớp còn sót: `ContractCreatePage.tsx` (CẢ 2 app) vẫn lọc-client-sau-khi-cắt-trang
|
||||
|
||||
`fe-admin/src/pages/contracts/ContractCreatePage.tsx:50-53` lấy `/contracts` `pageSize: 100` **không truyền `type`**, rồi `:80` `items = items.filter(c => c.type === typeFilter)`; `fe-user/.../ContractCreatePage.tsx:80` y hệt. Đây **đúng lớp lỗi** W7 vừa chữa (user >100 HĐ ⇒ loại hiếm biến mất im lặng). Ngoài scope spec W7 — nêu để không ai tưởng "class đã đóng" (bài học: vá 1 CLASS thì phải grep MỌI matcher cùng-lớp). Không chặn commit.
|
||||
|
||||
### F-6 🟡 minor — chèn field vào **GIỮA** positional record, ngược convention tự-khai của kho
|
||||
|
||||
`ActiveOthers` chèn giữa `Active` và `History` (`ApprovalWorkflowV2AdminFeatures.cs:97`). Đ9 chứng **không vỡ biên dịch** (1 site dựng, FE structural) nên không chặn. Nhưng file anh em tự khai convention ngược lại: `ContractSigningPlanFeatures.cs:148-150` — *"append-CUỐI (KHÔNG chèn cạnh `DepartmentId`… JSON bind theo TÊN)"* — và chính wave W2 trong cùng diff tuân thủ append-CUỐI. Hai chuẩn trong một commit. Không chặn, nhưng nếu F-3 đi hướng (ii) thì vấn đề tự biến mất.
|
||||
|
||||
### F-7 🟡 minor — 1 truy vấn VÔ ĐIỀU KIỆN thêm vào mọi `GET /contracts/{id}`
|
||||
|
||||
`ContractSigningPlanLines` reverse-join chạy cả với HĐ chắc chắn không có nguồn KHKK (HĐ cũ trước K7). Chi phí 1 index-seek trên `ContractId` — chấp nhận được, ghi để sau này ai đo p95 detail-page không phải đi tìm.
|
||||
|
||||
### F-8 🟡 minor — ca "2 phiếu trỏ cùng 1 HĐ" bị **thu hẹp im lặng**
|
||||
|
||||
`:723-726` chốt phiếu theo dòng ĐẦU rồi lọc lại ⇒ nếu dữ liệu lệch (INSERT thô), card ra số của đúng 1 phiếu — **không có tín hiệu nào** cho biết đã bỏ qua dòng của phiếu khác. Comment tự khai chủ đích và tôi đồng ý hướng chọn (thà đúng 1 phiếu còn hơn trộn tiền 2 phiếu). Ghi làm vết, không đòi sửa.
|
||||
|
||||
---
|
||||
|
||||
## 6. Điểm KHÁNG CỰ được soi (positive — đừng phá ở wave sau)
|
||||
|
||||
1. `Active` giữ **đúng phần tử cũ từng byte** ⇒ 0 rủi ro hồi quy cho 2 chỗ FE đang đọc `t.active`.
|
||||
2. `new ContractDetailDto(` **1 site duy nhất** — append-CUỐI được chứng bằng đo, không bằng niềm tin.
|
||||
3. Cây thô Contract mirror KHKK **khớp từng field, 0 `Status`** — đúng quyết T2 của spec (khuôn KHKK, không phải cặp precompute PE).
|
||||
4. IDOR guard nằm **trước** code W2 ⇒ dữ liệu KHKK không rò qua cửa detail.
|
||||
5. Rào `NaN` cho `?type=` (2 trang) — dùng lại khuôn `HardCopiesPage.tsx:66-68`, không đẻ khuôn thứ hai.
|
||||
6. Test W1a-2 có **2 chân** (mã ngoài họ + ratchet chính mã đó) ⇒ giết được cả bản vá regex lẫn bản vá lười "type-3 đừng tắt gì".
|
||||
7. Giả thuyết đối kháng về cardinality (§1e) bị **bác bằng đo 3 consumer**, không bằng suy luận.
|
||||
|
||||
---
|
||||
|
||||
## 7. VERDICT
|
||||
|
||||
**`PASS_WITH_FLAGS` — 8 issue (0 blocker tuyệt đối, 2 MAJOR phải đóng TRƯỚC khi W5 chạm tới field).**
|
||||
|
||||
- Commit đợt-1 được: BE W1+W2 đúng spec, 0-mig, test-before xanh (tôi tự chạy), 0 secret, mirror SHA-pair đạt.
|
||||
- **Điều kiện kèm theo:** F-1 + F-2 (tên/nullability FE) nên đóng **NGAY trong commit này** — chỉ 4 dòng, và để lại thì W5 sẽ debug một card "không render" mà mọi máy đều báo xanh. F-3 + F-4 là **nợ phải có chủ**: hoặc ghi vào spec/sổ nợ có mã, hoặc đổi hướng vá; không được đóng bằng cách tuyên đã xong.
|
||||
- Không tự sửa gì (vai verdict-only). Fix thuộc em-main/implementer.
|
||||
|
||||
== HẾT ==
|
||||
@ -0,0 +1,145 @@
|
||||
# sub-style-review-fable — STAGE B style-review 2 bản invest "Duyệt Hợp đồng (GĐ3)" — YC-023 lượt 3/4
|
||||
|
||||
> Run `2026-08-10-S187-duyet-hd-tu-khkk` · vai `back-end-reviewer-style` · ENGINE `/fable-real` (H21 lệnh-A, single deep-pass, propose-only) · 2026-08-10.
|
||||
> Luật soi = LUẬT LÕI owner: **tính năng ĐÃ DEPLOY PRODUCTION = CHUẨN** — đề xuất phải theo khuôn bản đã chạy (PE V2 · KHKK · Contract V2 · bridge K7); lệch = FLAG trừ khi owner đổi trong chính YC-023.
|
||||
> Mọi đối chiếu đọc THẬT trong phiên này (không trích trí nhớ): `ApprovalWorkflow.cs` · `ApprovalWorkflowV2AdminFeatures.cs:190-389` · `ContractSigningPlanWorkflowGuards.cs` trọn · `CreateContractFromSigningPlanFeatures.cs` trọn · `ContractWorkflowService.cs:40-370` · `ContractFeatures.cs:30-155/:295-339/:520-634` · `ContractSigningPlanFeatures.cs` (grep pin/re-pin) · `ContractsController.cs` · `ContractSigningPlansController.cs:1-40/:285-315` · `DbInitializer.cs:530-629` · `ContractPhase.cs` · `MenuKeys.cs:123-134` · gotchas #82/#85 (`docs/gotchas.md:1375-1399`) · `docs/workflow-contract.md:1-50` · Mig 72 trên đĩa.
|
||||
> File ghi TỪNG MỤC (chống #53). KHÔNG quyết nghiệp vụ — câu owner giữ nguyên nhãn CHỜ-ANH của 2 con invest.
|
||||
|
||||
---
|
||||
|
||||
## §A — Per-claim style-check §2d BE: 3 phương án chấm theo tiền-lệ CODE THẬT
|
||||
|
||||
### A1. Bảng tiền-lệ đã deploy (đo trong phiên)
|
||||
|
||||
| Mảnh khuôn | Bằng chứng code thật | Trạng thái trên đĩa |
|
||||
|---|---|---|
|
||||
| `ApplicableType` 1-giá-trị-cho-cả-7-loại-HĐ | `ApprovalWorkflow.cs:57` `Contract = 3, // HĐ general (any ContractType)`; slot riêng `:70` `ContractSigningPlan = 10` | ✅ khớp invest §1b |
|
||||
| Họ-Code cùng type (khuôn PA-A) | `DbInitializer.cs:540-542` per-Code CỐ Ý né khuôn check-per-type + `:607-610` `$"KHKK-N{n}"` / `AnyAsync(w.Code == code)` | ✅ ĐÃ DEPLOY (S161-S182, qua UAT) |
|
||||
| Guard họ 1-nhà-duy-nhất | `ContractSigningPlanWorkflowGuards.cs:44-45` regex `^KHKK-N([1-8])$` + `:23-32` tự khai "chép lần nữa = 3 bản chuỗi rời nhau" + `:65-78` 409 khớp nhóm | ✅ ĐÃ DEPLOY |
|
||||
| Normalize Code đường ghi + deactivate per-Code | `ApprovalWorkflowV2AdminFeatures.cs:348` `ToUpperInvariant()` + `:370-377` ternary per-Code CHỈ type-10 | ✅ ĐÃ DEPLOY (vá S182) |
|
||||
| Cột phân-nhánh THẬT trong type (khuôn PA-B) | `ApprovalWorkflows` V2: **0 cột nào** (entity `ApprovalWorkflow.cs:21-51` — Code/Version/Type/Name/IsActive/IsUserSelectable/CeoApprovalThreshold, hết) | ❌ CHƯA TỪNG deploy trên bảng V2 |
|
||||
| Tổ-tiên hình-dạng của PA-B | **V1** `WorkflowDefinition.ContractType` — còn SỐNG, bridge K7 vẫn pin `CreateContractFromSigningPlanFeatures.cs:163-166` | ✅ deploy nhưng là máy V1 |
|
||||
| Discriminator phiếu | `Contract.Type` có sẵn (bridge ghi `:188`) — KHKK phải đẻ cột Mig 71, HĐ khỏi | ✅ khớp invest |
|
||||
|
||||
### A2. Chấm 3 phương án theo LUẬT LÕI
|
||||
|
||||
- **PA-A (họ-Code, 0-mig)** — **ĐÚNG KHUÔN ĐÃ DEPLOY NHẤT.** Cả 4 mảnh khuôn KHKK (seeder per-Code · guard 1-nhà · normalize · deactivate per-Code) đều sống trên đĩa và đã qua UAT S179/S182. ĐIỀU KIỆN BẮT BUỘC invest đã tự nêu đúng: bản vá S182 hiện **hard-wire type-10** (`ApprovalWorkflowV2AdminFeatures.cs:370` `typeEnum == ContractSigningPlan`) ⇒ W1 gỡ Bom 1 + Bom 2 KHÔNG phải "nice-to-have" mà là phần khuôn còn thiếu cho type-3. Verdict: **KHỚP**.
|
||||
- **PA-B (cột thật, 1-mig)** — hình-dạng có tổ-tiên production ở **V1** (`WorkflowDefinition.ContractType`) nhưng trên **bảng V2 = pattern chưa từng deploy**; bảng dùng chung 10 `ApplicableType` ⇒ blast Designer/AdminFeatures/DTO như invest kê. Theo LUẬT LÕI: **LỆCH khuôn V2 hiện hành** ⇒ chỉ đi khi owner ratify — invest treo CHỜ-ANH #2 là đúng kỷ luật, không FLAG.
|
||||
- **PA-C (giữ chọn-tay)** — chính là hiện trạng deploy (`CreateContractFromSigningPlanFeatures.cs:23-25` comment tự khai "KHÔNG suy được từ ContractType, người bấm phải chọn"; guard 4 vế `:134-148` không có vế so loại). KHỚP style tuyệt đối nhưng không đạt đề bài YC-023 — invest loại: đúng.
|
||||
- **A-trước-B-sau** — style-safe (khởi đầu bằng khuôn đã chạy, unify là bước sau khi 2 họ cùng sống). Không phản đối về style; chọn A/B/lai = **CHỜ-ANH #2 (giữ nguyên)**.
|
||||
|
||||
### A3. Spot-check độ chính xác neo của invest BE (per-claim, đọc lại từng chỗ)
|
||||
|
||||
| Claim (sub-invest-be-fable.md) | Đối chiếu đĩa | Verdict |
|
||||
|---|---|---|
|
||||
| §1a bảng 7 mảnh V2-wire (`:24-32`) | `Contract... ApprovalWorkflowId` + `ContractWorkflowService.cs:111-116` branch + `:230` ApproveV2Async + UPSERT LevelOpinions `:295-329` + `ContractPhase.cs:14-28` 5-state + submit `:70-102` vai 4 vế `:84-88` | KHỚP |
|
||||
| §1b guard bridge không so loại (`:38`) | `:134-148` đúng 4 vế tồn-tại/type/IsActive/IsUserSelectable — 0 vế ContractType | KHỚP |
|
||||
| §1c-1 pin Create OPTIONAL (`:47`) | `ContractFeatures.cs:37` `Guid? ApprovalWorkflowId = null` | KHỚP |
|
||||
| §1c-2 Create thiếu 2 vế (`:48`) | `ContractFeatures.cs:79-87` chỉ exists+type; bridge có đủ `:141-148` | KHỚP (drift 1-luật-2-site THẬT) |
|
||||
| §1c-3 UpdateDraft không re-pin (`:49`) | `ContractFeatures.cs:136-145` record 8 field, 0 `ApprovalWorkflowId`; đối chứng KHKK CÓ re-pin `ContractSigningPlanFeatures.cs:606` + `:636-645` | KHỚP |
|
||||
| §2c Bom 1 + Bom 2 (`:72-73`) | `:370-377` ternary + comment `:363` "bất biến per-TYPE"; `:205-215` `versions.FirstOrDefault(v => v.IsActive)` Active-đơn | KHỚP |
|
||||
| §5 authz hiện trạng (`:123-124`) | `ContractsController.cs` toàn file đúng 2 hit `[Authorize]` (`:13` trần class + `:32` Contracts.Read/deleted); Reject-trước-guard `ContractWorkflowService.cs:48-66` (0 check vai/phase) | KHỚP |
|
||||
| §0 mốc Mig 72 (`:15`) | `Persistence/Migrations/20260806070242_AddMaHangMucToContractSigningPlanLines.cs` tồn tại | KHỚP |
|
||||
| §5 tiền-lệ hội-2-khóa (`:125`) | `ContractSigningPlansController.cs:29` class `KeHoachKyKet.Read` + `:307` action `Contracts.Create` + comment `:293-305` | KHỚP |
|
||||
|
||||
⇒ **Invest BE: 100% claim tao spot-check đều neo đúng.** 0 flag về độ chính xác.
|
||||
|
||||
---
|
||||
|
||||
## §B — Khuôn mirror: các wave đề xuất so nếp scaffold đã ship
|
||||
|
||||
- **W0 (chốt trục)** — không code, không soi style. OK.
|
||||
- **W1 (gỡ 2 bom + guard loại)** — hướng KHỚP bản vá S182 đã deploy. 2 nit (FLAG-5 §D): (i) khuôn deploy là **ternary tường-minh 3-vế + luật R-6 CẤM regex** trong predicate deactivate (`ApprovalWorkflowV2AdminFeatures.cs:365-368`) — chữ "predicate data-driven" của W1 khi thi công KHÔNG được tái-nhập regex; (ii) comment `:363` khắc "bất biến per-TYPE của các loại khác" phải sửa **CÙNG edit** — không sửa là tự đẻ đúng bug-class 1-luật-2-site mà Surprise #6 của chính invest đang tố.
|
||||
- **W2 (seed họ type-3)** — mirror `SeedKhkkGroupWorkflowsAsync` per-Code idempotent + thiếu-user-bỏ-slot-log-warning (`DbInitializer.cs:540-548/:587-600`) = ĐÚNG khuôn deploy, kể cả rủi ro #51 ungated-prod invest đã kê. 1 nit (FLAG-6 §D): mã họ mới PHẢI ASCII — validator Designer `^[A-Za-z0-9._-]+$` (`ApprovalWorkflowV2AdminFeatures.cs:274-276`); seeder không qua validator nhưng admin tạo version mới thì QUA ⇒ seed mã có "Đ" là kẹt vòng đời. `HD-*` ASCII như invest viết = đạt; chốt luôn uppercase khớp normalize `:348`.
|
||||
- **W3 (3+1 cửa pin)** — KHỚP khuôn: cửa TRÌNH đặt ở Infrastructure đúng lý do guards là `public static` (`ContractSigningPlanWorkflowGuards.cs:14-22`); cửa re-pin mirror KHKK UpdateDraft (`ContractSigningPlanFeatures.cs:636-645` — nhánh đổi-pin gọi đủ cụm guard). 1 nit (FLAG-7 §D): KHKK gom vế IsActive∧IsUserSelectable thành guard TÊN RIÊNG gọi từ N site (cụm `:501-516` EnsureWorkflowTypeAsync + EnsureWorkflowPinnableAsync + EnsureWorkflowGroupMatchAsync); Contract hiện inline `ContractFeatures.cs:79-87` — W3 "bổ 2 vế" nên bổ bằng cách **gọi nhà chung mới**, đừng inline bản thứ 2.
|
||||
- **W4 (dry-run K8)** / **W5 (authz điều kiện W0-#5)** — đúng khuôn K8 S168 + khuôn 2-tầng KHKK; W5 để CHỜ-ANH là đúng vì lệch hiện tại (`[Authorize]` trần) là **lệch CÓ bằng-chứng owner**: *"chỗ hợp đồng cứ từ từ"* (S156, tái khẳng định @S159 — trích trong cả 2 invest) ⇒ không FLAG "thiếu authz" ở bản đề xuất, chỉ giữ câu treo.
|
||||
- **Bộ CQRS + 3-file mig:** mọi wave 0-mig; mig điều-kiện (a)(b)(c) §4 đều khai 3-file rule + Down reversible = khớp `ef-core-migration` khuôn. Naming đề xuất PascalCase EN + policy `{menu}.{action}` — khớp; riêng họ mã workflow xem FLAG-6.
|
||||
|
||||
---
|
||||
|
||||
## §C — Đồng-nhất 2 bản invest: chỗ 2 lens nói ngược / lệch nhịp (liệt cho plan-review xử, không tự phân xử nghiệp vụ)
|
||||
|
||||
### FLAG-1 🔴 — Tiền-đề inbox: FE mang chẩn-đoán S156 STALE, BE đã đính chính — CODE ĐỨNG VỀ PHÍA BE
|
||||
- claim gốc: `sub-invest-fe-fable.md:47` (*"BE /contracts/inbox + GetEligiblePhases từng đo là 100% V1-LEGACY ⇒ HĐ pin V2 đang ChoDuyet KHÔNG hiện"* — FE tự đánh dấu "cần re-verify") + `:102` wave F3 cột "Chặn bởi BE 🔴 CÓ".
|
||||
- khuôn deploy đối chiếu: `ContractFeatures.cs:530-538` (inbox xét CẢ 2 nguồn, thoát-sớm phải cả 2 rỗng) + `:543` (`|| v2InboxIds.Contains(c.Id)`) + `:559-593` (`ResolveV2InboxIdsAsync` — match đúng Cấp-đang-chờ, OR-of-N `:588-589`). Khớp `sub-invest-be-fable.md:43-45` (§1c đính chính @W6 S161).
|
||||
- verdict: **LỆCH (tiền-đề FE stale)** — re-verify mà FE xin đã làm xong tại đây: inbox **ĐÃ V2-aware trên đĩa**.
|
||||
- sửa đề xuất: plan-review hạ F3 từ "chặn bởi BE" → **"verify-only trên máy sẵn có"** (đúng nhánh rủi ro FE tự viết: *"Nếu BE gộp vào /contracts/inbox sẵn có thì FE 0-đổi"*); diary S156 của investigator đính chính theo Surprise #2 BE.
|
||||
|
||||
### FLAG-2 🔴 — Wave mồ côi: FE F1/F2 khai BE-blocker mà wave-list BE KHÔNG có wave DTO nào
|
||||
- claim gốc: `sub-invest-fe-fable.md:100-101` — F1 cần BE extend `ContractDetailDto` reverse-join `Line WHERE ContractId=id` (card "Từ KHKK"); F2 cần BE thêm `currentApproval` + `approvalFlow` (cookie-cutter DTO PE S17). Đối chiếu wave BE: `sub-invest-be-fable.md:138-145` W0→W5 — **0 wave giao 2 delta DTO này** (§3b BE có nhắc gap đọc-ngược `:100` nhưng chỉ cho `ApprovalGroup`, và không xếp wave).
|
||||
- khuôn deploy đối chiếu: đo phiên này — `grep CurrentApprovalDto|ApprovalFlowDto` trong `src/Backend/SolutionErp.Application/Contracts/` = **0 hit** (PE có cặp DTO này từ S17 — khuôn để mirror); `Contract.cs` không mang con-trỏ ngược KHKK (link nằm phía `ContractSigningPlanLine.ContractId`, khớp FE §4).
|
||||
- verdict: **LỆCH nhịp 2 lens** (không phải lệch sự-thật — cả 2 mô tả đúng, nhưng kế hoạch không khớp nhau).
|
||||
- sửa đề xuất: plan-review **thêm 1 wave BE-DTO** (mirror cặp DTO PE + reverse-join nguồn KHKK vào `ContractDetailDto`) hoặc nhét tường minh vào W3/W4; không thì F1/F2 treo vô hạn.
|
||||
|
||||
### FLAG-3 🔴 — Trục 7-type ⟂ 8-nhóm: BE treo câu thượng-nguồn, FE viết CỨNG một nhánh
|
||||
- claim gốc: `sub-invest-be-fable.md:60/:161` CHỜ-ANH #1 (trục 7 `ContractType` ⟂ 8 nhóm SP-002 ⟂ lai) ⟂ `sub-invest-fe-fable.md:72-81` (§5 chọn khuôn "1 trang + `?type=N`", "TÁI DÙNG TRỌN `Ct_*`, KHÔNG sinh bộ mới") — toàn bộ FE §5 + bảng wave F1-F5 mặc định trục 7-type, **không có nhánh điều-kiện** nếu owner chọn trục nhóm.
|
||||
- khuôn deploy đối chiếu: cả 2 trục đều có tiền lệ sống — 7-type: 42 leaf `Ct_*` đã seed (S159); 8-nhóm: `ContractSigningPlan.ApprovalGroup` + họ `KHKK-N{n}` + 49 key `Khkk_G*` (S164-S169).
|
||||
- verdict: **LỆCH đồng-bộ** (FE thiếu cờ điều-kiện, không sai nội-dung).
|
||||
- sửa đề xuất: plan-review đánh dấu FE §5/F-wave = **điều-kiện theo đáp án BE-CHỜ-ANH #1**; nếu trục nhóm (hoặc lai) thắng thì phần menu/param FE phải vẽ lại theo khuôn `Khkk_G*` — nêu rõ trước khi trình anh để câu #1 được trả lời với đủ hệ quả 2 phía.
|
||||
|
||||
### C4 — Trùng câu owner (không phải mâu thuẫn — cần GỘP khi trình, KHÔNG nuốt)
|
||||
Tổng câu treo giữ nguyên: **BE 7 (#1-#7) + FE 5 (#1-#5) = 12**. Map trùng để plan-review gộp khi hỏi anh (mã câu gốc giữ):
|
||||
- BE#5 (authz Đ1/Đ2/Đ3) ≈ FE#2 (mở per-action authz bây giờ?) ≈ FE#5 (menu public 13/13 giữ?) — **1 cụm câu authz**, nên trình thành 1 khung có 2 tầng (#82 API ⟂ display).
|
||||
- BE#1 (trục) — thượng-nguồn của FE#4 (per-LOẠI-HĐ tầng API) và toàn bộ FE §5 (FLAG-3).
|
||||
- BE#7 (auto-pin có cho override tay?) — cùng dialog với FE-F1/F5 UX, khác câu, giữ riêng.
|
||||
- FE#1 (bộ nút 2-nút KHKK-trim ⟂ 3-nút PE) + FE#3 (admin thấy GĐ3?) — không có bản BE tương ứng, giữ nguyên.
|
||||
|
||||
---
|
||||
|
||||
## §D — Clean-code trong địa phận: trùng lặp / pattern-drift từ chính các đề xuất
|
||||
|
||||
### FLAG-4 🔴 — PA-A kê thiếu 1 mảnh khuôn KHKK đã deploy: họ sidebar-key `AwV2_KhkkN*` (+32 policy)
|
||||
- claim gốc: `sub-invest-be-fable.md:79-81` (PA-A "Chi phí Thấp: 0-mig") + `sub-invest-fe-fable.md:77-78` ("Key/policy: TÁI DÙNG TRỌN, KHÔNG sinh bộ mới" — lập luận cho `Ct_*`, không bàn key Designer).
|
||||
- khuôn deploy đối chiếu: khi KHKK lên họ 8 Code, đội ĐÃ thêm **8 menu-key `AwV2_KhkkN1..8` VÀO `All`** ⇒ sinh 32 policy (`MenuKeys.cs:123-134`, comment `:123` "lệnh lead K3-N1") + fe-admin sidebar trỏ Designer per-code. Mirror trọn khuôn cho họ `HD-*` ×7 ⇒ +7 key +28 policy = **đổi 2 row canonical STATUS (Menu keys/Policies)** — không phải "0 key mới".
|
||||
- verdict: **LỆCH-thiếu-kê** (bản mới thiếu mảnh bản cũ có, chưa ai yêu-cầu bỏ — đúng phép hỏi bắt buộc của LUẬT LÕI).
|
||||
- sửa đề xuất: plan-review thêm 1 dòng quyết định: mirror +7 key `AwV2_Hd*` (theo tiền lệ K3-N1, kèm cập nhật 2 row STATUS + test T2 khoá drift) **hay** chốt bỏ có khai lý do (Bom-2 fix overview-family đã đủ đường vào Designer). Phần render sidebar admin = [NGOÀI ĐỊA PHẬN — chuyển front-end-reviewer-style @wave diff-review]; phần `MenuKeys.cs`/policy-count = BE.
|
||||
|
||||
### FLAG-5 🟡 — W1 "predicate data-driven": 2 ràng đi kèm khuôn S182 phải giữ
|
||||
- claim gốc: `sub-invest-be-fable.md:141` (W1 — "predicate data-driven thay hard-wire `== ContractSigningPlan` tại `:370`").
|
||||
- khuôn deploy đối chiếu: `ApprovalWorkflowV2AdminFeatures.cs:365-368` luật R-6 **CẤM regex** trong predicate deactivate (code ngoài họ phải vẫn per-Code cho type đã mở) + comment `:363` khắc "per-TYPE là bất biến của các loại khác".
|
||||
- verdict: **KHỚP hướng — 2 nit**: mở type-3 bằng tập-type tường-minh (không regex, giữ R-6); sửa comment `:363` trong CÙNG edit (không sửa = 1-luật-2-site mới, đúng bug-class Surprise #6 của chính invest).
|
||||
- sửa đề xuất: ghi 2 ràng này vào acceptance W1.
|
||||
|
||||
### FLAG-6 🟡 — W2 mã họ mới: ASCII-only theo validator Designer, tránh kẹt vòng đời
|
||||
- claim gốc: `sub-invest-be-fable.md:79` (họ Code `HD-{...}` ×7).
|
||||
- khuôn deploy đối chiếu: `ApprovalWorkflowV2AdminFeatures.cs:274-276` validator `^[A-Za-z0-9._-]+$`; normalize uppercase `:348`; KHKK dùng ASCII `KHKK-N{n}` (`DbInitializer.cs:609`). Seeder đi thẳng entity KHÔNG qua validator ⇒ seed mã có ký tự "Đ" vẫn vào DB nhưng admin **không tạo version mới được** qua Designer.
|
||||
- verdict: **KHỚP (đề xuất đã viết `HD-` ASCII)** — chốt thành luật trong W2: mã họ = ASCII UPPER, token loại là hằng 1-nhà (mirror `GroupCodePattern` có nhà riêng cho họ mới, KHÔNG đụng nhà KHKK `ContractSigningPlanWorkflowGuards.cs:44-45`).
|
||||
- ghi nhận trùng-lặp CÓ KHAI: PA-A đẻ regex-họ THỨ HAI — đúng cái `:28-32` cảnh báo về lớp "chuỗi họ nhân bản"; invest đã kê ở Rủi ro ("drift 2 họ regex") + PA-B là đường trả nợ ⇒ trùng lặp **có chủ đích, có lối thoát** — không FLAG thêm, chỉ ghi để diff-review soi đúng chỗ.
|
||||
|
||||
### FLAG-7 🟡 — W3 "bổ 2 vế" cho Create: bổ bằng NHÀ CHUNG, đừng inline bản thứ 2
|
||||
- claim gốc: `sub-invest-be-fable.md:143` (W3 — "Create bổ 2 vế `IsActive ∧ IsUserSelectable` (đồng luật vá-3, xóa drift §1c-2)").
|
||||
- khuôn deploy đối chiếu: KHKK đóng vế này bằng guard TÊN RIÊNG gọi từ N site — cụm `ContractSigningPlanFeatures.cs:501-516` (EnsureWorkflowTypeAsync + EnsureWorkflowPinnableAsync + EnsureWorkflowGroupMatchAsync) + nhánh re-pin `:636-645` gọi lại đúng cụm; lý-do-tầng ghi tại `ContractSigningPlanWorkflowGuards.cs:14-22`. Contract hiện inline (`ContractFeatures.cs:79-87`) và bridge inline bản riêng (`CreateContractFromSigningPlanFeatures.cs:134-148`) — đã là 2 bản chuỗi.
|
||||
- verdict: **KHỚP hướng — nit kiến trúc**: nếu W3 inline thêm lần 3 là nhân bản luật lần nữa; gom 4 cửa (Create + bridge + re-pin + TRÌNH) về 1 nhà `public static` mirror khuôn KHKK.
|
||||
- sửa đề xuất: W3 acceptance thêm vế "4 cửa gọi CÙNG method" (fault-inject 1 cửa quên gọi phải đỏ — khuôn test-before W1b S182).
|
||||
|
||||
### D-note (không flag):
|
||||
- Dual-pin V1+V2 ở bridge/Create (`:163-166/:201-202` + `ContractFeatures.cs:105-109`) là khuôn deploy CÓ CHỦ ĐÍCH (comment "Cả 2 set ok") — đề xuất giữ nguyên là đúng; đừng để diff-review đọc thành trùng lặp phải dọn.
|
||||
- `ContractWorkflowService.cs:227-229` còn TODO stale ("table chưa tồn tại — Mig 33 sẽ scaffold") trong khi UPSERT đã sống `:295-329` — code ĐÃ DEPLOY = ngoài phạm-vi FLAG đợt này; ghi để wave nào chạm file thì nhân tiện dọn.
|
||||
- `docs/workflow-contract.md:32` vẫn là bản 9-phase LEGACY (đúng vai tài-liệu gốc); mọi doc/plan GĐ3 mới nên trỏ `ContractPhase.cs:3-13` (5-state) + addendum skill, đừng trỏ nguyên trang 9-phase cho người mới — tránh đọc nhầm khuôn (docs/** ngoài paths deploy, không phải "chuẩn production").
|
||||
|
||||
---
|
||||
|
||||
## §E — Claim FE chạm BE tao đã verify hộ (còn lại NGOÀI ĐỊA PHẬN)
|
||||
|
||||
| Claim FE | Đo phiên này | Verdict |
|
||||
|---|---|---|
|
||||
| §5 "hiện 0 endpoint tiêu thụ key `Ct_*`" (`sub-invest-fe-fable.md:81`) | grep `Ct_` toàn `src/Backend/SolutionErp.Api` = 0 file | KHỚP |
|
||||
| F4 "param `type` có sẵn" (`:103`) | `ContractsController.cs:23-27` `[FromQuery] ContractType? type` (W7 S161) + list filter server-side `ContractFeatures.cs:324-327` | KHỚP — F4 đúng là FE-only |
|
||||
| F2 "CHẶN bởi BE DTO" (`:96/:101`) | BE-side 0 hit `CurrentApprovalDto/ApprovalFlowDto` trong Application/Contracts (PE có từ S17) | KHỚP — nuôi FLAG-2 |
|
||||
| §5 gate `Contracts.*` root, không OR `Ct_*` leaf (`:79-80`) | đúng luật #85 (`docs/gotchas.md:1383-1385` — "gate mỗi query bằng đúng key policy của endpoint"; `MenuPermissionHandler` so chính-xác 1 key) + khớp tiền lệ hội-2-khóa `ContractSigningPlansController.cs:293-307` | KHỚP |
|
||||
|
||||
NGOÀI ĐỊA PHẬN — chuyển vai đúng tuyến @wave diff-review:
|
||||
- [→ `front-end-reviewer-style`] toàn bộ §1/§2/§2-bis/§6 FE (Layout regex · ADMIN_HIDDEN_KEYS · TRANSIENT_QUERY_KEYS · khuôn `KhkkWorkflowPanel` vs `PeWorkflowPanel` · SHA-mirror 2 app · bộ nút sau owner-trim S174/S175) + phần sidebar của FLAG-4.
|
||||
- [→ `database-reviewer-style`] nếu owner chọn nhánh có mig: (a) cột phân-nhánh PA-B trên `ApprovalWorkflows` · (b) `Contract.ApprovalGroup` · (c) cột gợi-ý loại HĐ trên `ContractCatalogEntries` — EF config + tên cột + backfill semantics (gotcha #64 SQLite-mù-migration) là đất con DB; tao chỉ giữ vế "3-file rule + naming PascalCase" đã soi ở §B.
|
||||
|
||||
---
|
||||
|
||||
## §F — Verdict tổng
|
||||
|
||||
**STYLE-REVIEW: PASS_WITH_FLAGS — 7 flag** (3 🔴 cross-lens/thiếu-kê: FLAG-1 tiền-đề-stale · FLAG-2 wave-mồ-côi · FLAG-3 trục-viết-cứng + FLAG-4 🔴 thiếu-kê khuôn key/policy; 3 🟡 nit thi-công W1/W2/W3).
|
||||
|
||||
- **Chấm §2d theo tiền-lệ:** PA-A = đúng khuôn V2 đã deploy nhất (kèm điều-kiện W1 gỡ 2 bom — chính là phần khuôn S182 chưa phủ type-3); PA-B = lệch khuôn V2 (tổ-tiên chỉ ở V1), đúng kỷ-luật khi treo CHỜ-ANH #2; PA-C = khớp style nhưng không đạt đề bài. A-trước-B-sau style-safe. **Không quyết thay anh.**
|
||||
- **Độ tin 2 bản invest:** BE — mọi neo spot-check đều đúng (bảng §A3); FE — đúng ở phần FE + 4 claim chạm BE (bảng §E), sai duy nhất tiền-đề S156 đã stale (FLAG-1, FE có tự khai xin re-verify).
|
||||
- **12 câu CHỜ-ANH giữ nguyên** (BE #1-#7 + FE #1-#5) — map gộp ở §C4 để trình anh 1 lần, không nuốt câu nào.
|
||||
|
||||
== HẾT ==
|
||||
@ -0,0 +1,107 @@
|
||||
# sub-test-specialist-0 — W1 test-before RED (2 bom per-type của ApprovalWorkflowsV2 với type-3)
|
||||
|
||||
> Run `2026-08-10-S187-duyet-hd-tu-khkk` · vai `test-specialist` (WRITE độc quyền `tests/**`) · 2026-08-10.
|
||||
> Ghi TỪNG MỤC trong lúc làm (chống #53). Mục nào CHƯA chạy thì chỉ có tiêu đề + chữ "CHƯA ĐO" — cấm điền số kỳ vọng trước.
|
||||
|
||||
## §0 — Ground-truth ĐO TƯƠI trước khi viết (không chép số từ đề bài)
|
||||
|
||||
| Thứ đo | Lệnh | Kết quả |
|
||||
|---|---|---|
|
||||
| Cây làm việc | `git status --porcelain` | `M ContractFeatures.cs` · `M ContractDtos.cs` · `M fe-admin/src/types/contracts.ts` (= **lane W2 đang chạy song song**) + 3 file doc/governance. `tests/**` SẠCH ⇒ mọi thay đổi test dưới đây là của tôi |
|
||||
| HEAD | `git log --oneline -1` | `3a311c04 wal: flush 20260810T1726` |
|
||||
| Build | `dotnet build tests/SolutionErp.Infrastructure.Tests` | **Build succeeded, 0 Warning, 0 Error** (9.19s) — tức edit dở-dang của lane W2 vẫn biên dịch được, RED của tôi sẽ không lẫn với lỗi biên dịch của người khác |
|
||||
| Pre-baseline file đích | `dotnet test --filter FullyQualifiedName~UpdateAwDefinitionTests` | **Passed: 27, Failed: 0** (18s) |
|
||||
|
||||
🔴 **KHÔNG đo full-suite**: lane W2 đang sửa `ContractFeatures.cs`/`ContractDtos.cs` giữa chừng ⇒ con số full-suite đo lúc này là snapshot dở (gotcha #68), và nó KHÔNG phải số của tôi. Phán quyết cuối = LEAD chạy sau khi mọi lane dừng tay. Số tôi chịu trách nhiệm = filter theo file đích (đo được, tách bạch).
|
||||
|
||||
## §1 — Đọc code thật: 2 bom, xác nhận từng dòng
|
||||
|
||||
**Bom-1 `CreateAwDefinitionCommandHandler`** — `src/Backend/SolutionErp.Application/ApprovalWorkflowsV2/ApprovalWorkflowV2AdminFeatures.cs:370-377`:
|
||||
|
||||
```csharp
|
||||
var actives = typeEnum == ApprovalWorkflowApplicableType.ContractSigningPlan
|
||||
? await db.ApprovalWorkflows.Where(w => w.Code == code && w.ApplicableType == typeEnum && w.IsActive).ToListAsync(ct)
|
||||
: await db.ApprovalWorkflows.Where(w => w.ApplicableType == typeEnum && w.IsActive).ToListAsync(ct);
|
||||
foreach (var old in actives) old.IsActive = false;
|
||||
```
|
||||
⇒ type-3 (`Contract`) rơi nhánh **per-TYPE** `:374-376`. Comment `:363` tự khai "các loại khác: per-TYPE như cũ — 1 active/type là **bất biến của chúng**" ⇒ sau W3 (7 quy trình `HD-*` sống song song cùng type-3) câu đó thành SAI, và một cú "Tạo phiên bản mới" trên panel một loại HĐ tắt IsActive của 6 loại còn lại. R-6 `:365-368` **CẤM** biến-thể regex.
|
||||
|
||||
**Bom-2 `GetAwAdminOverviewQueryHandler`** — cùng file `:205-215`: `versions.FirstOrDefault(v => v.IsActive)` cho mỗi `ApplicableType` ⇒ N active cùng type chỉ hiện 1.
|
||||
|
||||
**Vì sao "giấu 6" là THẬT chứ không phải chuyện thẩm mỹ** (đo ở FE, chỉ ĐỌC — không sửa):
|
||||
- `fe-admin/src/pages/system/ApprovalWorkflowsV2Page.tsx:381-389` render thẻ "đang áp dụng" từ `t.active` (một ô đơn);
|
||||
- `:485-492` render danh sách lịch sử bằng `type.history.filter(d => !d.isActive)` ⇒ **một quy trình ĐANG ACTIVE mà chỉ nằm trong `history` thì KHÔNG lên màn hình ở đâu cả**;
|
||||
- `:317` `overview.data?.types.find(t => t.applicableType === selectedTypeInt)` ⇒ FE lấy **một** bucket per type (`find`);
|
||||
- `:321-339` KHKK type-10 đang tự cắt lại theo Code **ở FE** (`history.filter(d => d.code === wantCode)`) — tức tiền lệ hiện tại là FE tự xử, BE giữ 1 bucket/type.
|
||||
|
||||
⇒ Hệ quả cho thiết kế test bom-2 (ghi rõ để lane sau không bị test ép vào một hình dạng): test **KHÔNG được** đòi hình dạng cụ thể của bản vá. Xem §2-③.
|
||||
|
||||
## §2 — Thiết kế 4 ca (và những gì CỐ Ý không viết)
|
||||
|
||||
Đặt trong `tests/SolutionErp.Infrastructure.Tests/Application/UpdateAwDefinitionTests.cs` — **chọn theo HÀNG XÓM + FIXTURE, không theo tên file**:
|
||||
- File này đã chứa đúng bộ anh em của bom-1: `T1/T2/T3` (S182 W1b) là mệnh đề per-Code cho **type-10**; ca của tôi là mệnh đề đó cho **type-3** ⇒ để cạnh nhau thì người sau đọc một mạch.
|
||||
- Nó đã có sẵn toàn bộ đồ nghề: `NewHarness` (IdentityFixture — BẮT BUỘC vì `ApprovalWorkflowLevel.ApproverUserId` FK Restrict → User), `SeedWorkflowAsync(code:, version:)`, `NewVersionCommand`, `ReadWorkflowsAsync`, `WfRow`. Viết file mới = nhân bản ~60 LOC harness (bài S166).
|
||||
|
||||
**① Chồng lấn đã tra TRƯỚC khi viết** (`grep CreateAwDefinitionCommand|GetAwAdminOverviewQuery tests/`):
|
||||
- `UpdateAwDefinitionTests.cs:1112/1152/1188` = T1/T2/T3 phủ **type-10**; `ContractSigningPlanApprovalTests.cs:1129` dùng POST để dựng cờ, không đo phạm vi deactivate.
|
||||
- `GetAwAdminOverviewQueryHandler`: **0 hit trong toàn bộ `tests/`** ⇒ handler overview hôm nay 0 test.
|
||||
- **KHÔNG viết lại** ca "type-1 tạo mã mới vẫn tắt mã cũ cùng type" — `T2 (:1152)` đã phủ y hệt (và còn kèm chứng nhân xuyên-type sang 8 nhóm KHKK). Nó chính là rào chống bản vá "nới per-Code cho MỌI type"; nhân bản cho type-3 chỉ làm dày file, không thêm sức phân biệt.
|
||||
|
||||
**② Ca CỐ Ý không viết + lý do** (khai để không ai tưởng đã phủ):
|
||||
- Vế `ApplicableType == typeEnum` trong nhánh per-Code (3 vế của R-6) **không dựng được ca phân biệt**: muốn chứng "quên vế type thì tắt nhầm workflow type khác" phải có 2 workflow **cùng Code khác type**, mà `ApprovalWorkflowConfiguration.cs:23` là `HasIndex(Code, Version).IsUnique()` **toàn cục** (không kèm type) ⇒ cùng Code buộc phải khác Version, và `nextVersion` `:351-353` cũng tính per-Code toàn cục ⇒ ca dựng ra méo mó so với prod. Ghi lại là **vùng không phủ**, không giả vờ phủ.
|
||||
- Hành vi FE (`find` 1 bucket, `history.filter(!isActive)`): ngoài lãnh địa của tôi, chỉ trích dẫn làm căn cứ.
|
||||
|
||||
**③ Cách đo bom-2 mà KHÔNG ép hình dạng bản vá** — `ActiveChannel(summary)` bằng reflection:
|
||||
> "Kênh ĐƯƠNG NHIỆM" của một `AwTypeSummaryDto` = **mọi** ngả trỏ tới `AwDefinitionDto` (đơn hoặc tập hợp), **TRỪ `History`**. Trừ `History` vì nó là KHO PHIÊN BẢN (chứa cả bản đã tắt) và FE render nó bằng `filter(d => !d.isActive)` ⇒ nằm trong đó không đồng nghĩa với "được hiển thị là đang áp dụng".
|
||||
Nhờ vậy ca này XANH với **cả hai** hình dạng bản vá hợp lệ: (a) thêm một trường tập hợp mới cạnh `Active`; (b) tách mỗi Code thành một mục riêng cùng `applicableType`. Hôm nay reflection chỉ thấy `Active` (đơn) ⇒ ĐỎ vì HÀNH VI, không phải vì biên dịch.
|
||||
🔸 Lưu ý cho lane sau (không phải yêu cầu của test): hình (b) sẽ đụng `ApprovalWorkflowsV2Page.tsx:317` `.find(...)` — FE lấy đúng 1 bucket ⇒ hình (a) rẻ hơn. Test không quyết hộ.
|
||||
|
||||
## §3 — 4 ca đã viết
|
||||
|
||||
File DUY NHẤT tôi ghi: `tests/SolutionErp.Infrastructure.Tests/Application/UpdateAwDefinitionTests.cs` — `git diff --stat` = **295 insertions(+), 0 deletions** (1 file) ⇒ bằng chứng cấu trúc là tôi KHÔNG sửa dòng nào của 27 ca cũ.
|
||||
|
||||
| # | Tên ca | Dòng | Đo cái gì | Kỳ vọng hôm nay |
|
||||
|---|---|---|---|---|
|
||||
| 1 | `W1a_CreateNewVersionForOneContractCode_DeactivatesOnlyThatCode_SixOtherContractTypesStayActive` | `:1648` | 7 Code type-3 active → POST v02 cho `HD-MB`: 1 dòng mới · HD-MB v01 tắt / v02 bật · **mọi Code khác GIỮ active** · số LOẠI HĐ còn quy trình = 7 | **ĐỎ** (bom-1) |
|
||||
| 2 | `W1a_CreateType3_WithOutOfFamilyCode_LeavesSevenContractCodesActive_AndStillRatchetsItsOwnCode` | `:1691` | (a) tạo mã type-3 NGOÀI họ `QT-HD-V2-001` ⇒ 7 loại bất biến, tổng 8 mã active; (b) tạo LẠI chính mã đó ⇒ v01 tắt / v02 bật, vẫn 8 | **ĐỎ** ở chân (a) |
|
||||
| 3 | `W1b_OverviewForContractType_SurfacesEveryActiveWorkflowOfThatType_NotJustTheFirst` | `:1734` | overview type-3 (CẢ 2 nhánh `typesToReturn` `:201-203`): kênh đương-nhiệm = đủ 7 Code · chỉ chứa bản `IsActive` · HD-MB phải là **v02** · kho phiên bản vẫn đủ 8 (Code,Version) | **ĐỎ** (bom-2) |
|
||||
| 4 | `W1b_OverviewForSingleActiveType_KeepsExactlyOneCurrentDesignation_AndDoesNotLeakOtherTypes` | `:1789` | type-1 có v01 tắt + v02 bật, cạnh type-2 và 7 quy trình type-3: kênh đương-nhiệm type-1 = ĐÚNG 1 (v02, không phải v01) · 0 rò rỉ type khác · kho phiên bản đủ 2 | **XANH** — control, phải XANH cả trước lẫn sau |
|
||||
|
||||
Đồ nghề mới (đều `private static`, dùng lại của file): `Hd :1566` · `ContractFamilyCodes :1573` (7 mã, chỉ là DỮ LIỆU — W3 chốt mã thật) · `SeedSevenContractWorkflowsAsync` · `ContractCodesWithActiveVersionAsync` (đơn vị = **số LOẠI HĐ còn quy trình để pin**, mirror `KhkkGroupsWithActiveVersionAsync :1033`) · `EnsureBudgetEditorRolesAsync :1593` (handler overview hỏi 3 role `:166-168` — thiếu role là ĐỎ hạ tầng, không phải ĐỎ hành vi) · `SurfacedAsCurrent :1614` + `ActiveChannel :1631` + `HistoryChannel`.
|
||||
|
||||
## §4 — Chạy: RED có đúng lý-do-hành-vi không
|
||||
|
||||
`dotnet test --filter "FullyQualifiedName~UpdateAwDefinitionTests.W1"` → **Failed: 3, Passed: 1, Total: 4**. Trích nguyên văn (đã cắt bớt phần dump dài):
|
||||
|
||||
```
|
||||
Failed ...W1a_CreateNewVersionForOneContractCode_...
|
||||
Expected rows.Where(r => r.Code != "HD-MB") to contain only items matching r.IsActive
|
||||
because 🔴 tập bị tắt phải lọc theo CODE trong type-3, không theo TYPE..., but
|
||||
{ WfRow { Code = "HD-TP", IsActive = False, Version = 1 }, ... "HD-NT-DV", IsActive = False ... } do(es) not match.
|
||||
|
||||
Failed ...W1a_CreateType3_WithOutOfFamilyCode_...
|
||||
Expected (ContractCodesWithActiveVersionAsync(h.Db)) to be 8 because 🔴 R-6: ... , but found 1.
|
||||
|
||||
Failed ...W1b_OverviewForContractType_...
|
||||
Expected filteredChannel.Select(d => d.Code) to be a collection with 7 item(s) ..., but {"HD-MB"}
|
||||
contains 6 item(s) less than {"HD-TP","HD-GK","HD-NCC","HD-DV","HD-MB","HD-NT-NCC","HD-NT-DV"}.
|
||||
```
|
||||
|
||||
**ĐỎ ĐÚNG LÝ-DO-HÀNH-VI, 3 chứng cứ:**
|
||||
1. Không có lỗi biên dịch nào (build succeeded ở §0 và ở mọi lượt chạy); thông điệp là assert nghiệp vụ, không phải `CS####`.
|
||||
2. ĐỎ rơi **đúng dòng đang tranh chấp**: ca 1 qua hết các assert "1 dòng mới / v01 tắt / v02 bật" rồi mới gãy ở vế PHẠM VI ⇒ ratchet per-Code của chính Code đó vốn đã đúng, chỉ mỗi tập bị tắt là sai. Ca 3 qua tiền-đề "7 loại đang active" rồi mới gãy ⇒ seed đúng, chỉ đường TRẢ VỀ là sai.
|
||||
3. Ca 4 (control) XANH trong cùng lượt ⇒ hạ tầng (IdentityFixture, role, `GetAwAdminOverviewQueryHandler`) chạy được; 3 ĐỎ kia không phải "harness hỏng".
|
||||
|
||||
**Chứng "ĐỎ này SỬA ĐƯỢC" (không phải test bất khả thi) — 2 nguồn, 0 chạm production:**
|
||||
- **Bom-1 — nhân chứng SỐNG trong cùng suite, 0 mimic:** `T1 :1113` và `T3 :1188` là ĐÚNG bộ assert này cho type-10 và đang **XANH** trên chính handler thật. Cặp `W1a-1 ⟂ T1` khác nhau ĐÚNG MỘT BIẾN — `ApplicableType` 3 vs 10 — tức thế giới "per-Code" đã được chứng minh thoả mãn được bộ assert này bởi mã production hiện hành.
|
||||
- **Bom-2 — PROBE VI-PHÂN (dựng, đo, xoá):** tạm thêm `[Fact] PROBE_TEMP_ActiveChannel_AggregatesAcrossBuckets` **gọi CHÍNH `ActiveChannel`/`HistoryChannel` thật** (0 bản sao logic), nạp một `AwAdminOverviewDto` dựng tay theo hình bản-vá (b) (7 mục cùng `applicableType=3`, mỗi mục một `Active`) → **Passed: 1, Failed: 0** ⇒ cả 4 vế assert của ca 3 thoả được. Xoá probe xong verify HAI CHIỀU: `grep -c PROBE_TEMP` = **0** và tổng ca của file về lại **31** (không phải 32).
|
||||
- Nhánh `case IEnumerable<AwDefinitionDto>` (hình bản-vá (a) — thêm trường tập hợp) không probe được vì cần một kiểu chưa tồn tại; nó là ngữ nghĩa mẫu-khớp của C# (`List<AwDefinitionDto>` khớp `IEnumerable<AwDefinitionDto>`). Nhánh `Active == null` thì ĐÃ chạy thật trong ca 4 (truy vấn không lọc đi qua các type 4..9 rỗng) và không nổ.
|
||||
|
||||
## §5 — Hồi quy: 27 ca cũ trong file có bị tôi làm đỏ không
|
||||
|
||||
`dotnet test --filter "FullyQualifiedName~UpdateAwDefinitionTests"` → **Failed: 3, Passed: 28, Total: 31** (trước khi tôi viết: **27/27 xanh**, đo ở §0).
|
||||
- 31 = 27 cũ + 4 mới ✓ · 28 xanh = 27 cũ + 1 control mới ✓ · 3 đỏ = đúng 3 ca test-before của tôi, in đích danh tên trong output ✓.
|
||||
- `git status --porcelain` cuối lượt: file tracked duy nhất tôi đụng = `tests/SolutionErp.Infrastructure.Tests/Application/UpdateAwDefinitionTests.cs`. Các file `M` còn lại (`ContractFeatures.cs`, `ContractDtos.cs`, `fe-*/types/contracts.ts`, `fe-user/.../MyContractsPage.tsx`) là của **lane W2/W7 chạy song song** — không phải của tôi. 0 file `Migrations/` đổi.
|
||||
- **KHÔNG chạm production code**: 0 sửa `src/Backend/**`, 0 sửa `fe-*/**`. 0 bug production mới phát hiện ngoài đúng 2 bom đã biết.
|
||||
|
||||
== HẾT ==
|
||||
Reference in New Issue
Block a user