# W4 — detector-fire evidence (positive-control) · 2026-07-15 S122

> 🔴 **CAPTURE TRƯỚC KHI VÁ.** Đây là bằng-chứng **time-anchored** thay cho *"reviewer chứng-kiến"* làm neo duy-nhất (spec v3 **fix #9(c)**).
> **Luật W4 (v2 §3.3 + §2.4(4)):** `fire → chứng-kiến → RỒI MỚI vá → hết fire`. 🔴 **CẤM vá `permission-matrix` TRƯỚC** — vá trước thì detector chạy xong xanh lè và ta **mất chính bằng-chứng** rằng nó có răng.
> **Lệnh:** `powershell.exe -ExecutionPolicy Bypass -File scripts/governance-detectors.ps1` · **exit = 0** (DETECT-only, advisory, không fail build).

---

## 1. 🔴 POSITIVE-CONTROL FIRE — `permission-matrix/SKILL.md` (chạy TRƯỚC mọi sửa đổi)

```
[DETECTOR] MED  | .claude/skills/permission-matrix/SKILL.md:3  | derived-stale: writes 60 menu but canonical=54    | resolve: update to 54 OR replace with pointer '-> docs/STATUS.md'
[DETECTOR] MED  | .claude/skills/permission-matrix/SKILL.md:3  | derived-stale: writes 240 policy but canonical=216  | resolve: update to 216 OR replace with pointer '-> docs/STATUS.md'
[DETECTOR] MED  | .claude/skills/permission-matrix/SKILL.md:16 | derived-stale: writes 60 menu but canonical=54     | resolve: update to 54 OR replace with pointer '-> docs/STATUS.md'
[DETECTOR] LOW  | .claude/skills/permission-matrix/SKILL.md:81 | derived-stale: writes 48 policy but canonical=216  | resolve: update to 216 OR replace with pointer '-> docs/STATUS.md'
[DETECTOR] LOW  | .claude/skills/permission-matrix/SKILL.md:16 | title-stale: anchor says 2026-04-30 but newest governance milestone is 2026-07-15 (76d behind)
```

**= 5 FLAG** (2 MED + 3 LOW).
🔸 **Đính-chính con-số của LEAD:** WAL ghi *"permission-matrix **6 dòng**"* — đếm thật = **5 flag**. Dòng thứ 6 (`:70` `anchor 2026-04-30 … SKILL.md:16`) là **dòng context trong khối tóm-tắt anchor**, KHÔNG phải flag. *(Lỗi đếm-của-lead, bắt được TRƯỚC khi thành claim gửi hub — nhưng vẫn là lỗi, → adap-report.)*

## 2. ✅ TOTAL = 50 — khớp CHÍNH XÁC dự-báo W2

```
TOTAL FLAGS: 50
NOTE: DETECT-only lowering net. Exit 0 always (never fails build). FLAGs are advisory.
```
WAL:17 (W2) dự-báo: *"TOTAL 49→**50** (−1 cadence, +2 policy)"*. **Đo ra đúng 50.** ⇒ hợp-đồng chéo W1↔W2 khép **cả hai chiều**, đo bằng **chính script W1**.

## 3. ✅ Chuỗi nhân-quả W1↔W2 — 3 detector đổi trạng-thái ĐÚNG như thiết-kế

| Detector | TRƯỚC W2 | SAU W2 | Ý-nghĩa |
|---|---|---|---|
| `h24_cadence` | `missing — W2 chưa land ⇒ measuring NOTHING` | `M = light_every = 6 (read from config)` | fail-loud **tắt đúng lúc** key land — **không** giả-định default |
| `spawn-model-audit` | `expected constant missing` | `[OK] claude-opus-4-8 = owner-ratified` | PA-2a hằng-số nhận diện được |
| GAP-3 policy | `policy=MISSING` | `216` **+ BẮT NGAY** `permission-matrix:3 writes 240 but canonical=216` | 🔴 **row STATUS `Policies` land ⇒ detector LẬP TỨC có canonical để so ⇒ lộ stale ẩn 76 ngày** |

🔴 **Dòng cuối là điểm mấu-chốt:** trước W2, nửa `policy` của GAP-3 **KHÔNG THỂ fire** vì `docs/STATUS.md` **không có row `Policies`** ⇒ detector không có gì để so ⇒ **im lặng**, và sự im-lặng đó trông y hệt "sạch". Thêm canonical ⇒ 2 flag mới hiện ra **ngay**. Bài học: **detector không có nguồn chuẩn = detector đo NOTHING, và nó không tự nói cho biết.**

## 4. 🔴 PHÁT-HIỆN NGOÀI SCOPE W4 — vai H24 bị chính detector bắt

```
[DETECTOR] LOW  | .claude/agents/lead-view-auditor.md:45 | title-stale: anchor says 2026-04-30 but newest governance milestone is 2026-07-15 (76d behind)
```

**Đây là DƯƠNG-GIẢ.** `lead-view-auditor.md:45` chứa `**Status (post Session 6 — 2026-04-30):**` làm **VÍ-DỤ minh-hoạ** cho class `view-stale-header` trong bảng flag-class của chính nó. Detector đọc ngày trong **ví-dụ** và tưởng là **anchor thật của file**.

🔴 **Cùng CƠ-CHẾ với lỗi đã làm acceptance FIX#6 bất-khả-thi** (`lead-view-auditor.md:47` chứa *"agents/README ghi 12 sub khi roster = 14"* làm ví-dụ ⇒ sweep-pattern bắt). **Hai detector ĐỘC-LẬP, cùng một file, cùng một lý-do:**
> **Ví-dụ về stale trông y hệt stale.** File **định-nghĩa** anti-pattern tất-yếu **chứa mẫu** anti-pattern ⇒ mọi detector khớp-mẫu sẽ bắt nó. Đây **KHÔNG** phải lỗi của vai, cũng **KHÔNG** phải lỗi của detector — là **hệ-quả cấu-trúc** của việc dò bằng khớp-mẫu.

**Vì sao KHÔNG "sửa" bằng cách đổi ví-dụ:** ví-dụ mất tính cụ-thể thì bảng flag-class mất giá-trị dạy-nghề (vai đọc "con-số lệch thực-tế" mà không có mẫu thật thì tự chế class — đúng thứ enum-ĐÓNG sinh ra để chặn).
**Xử @S122:** giữ ví-dụ, **khai vào đây + adap-report**. Đề-xuất hướng (KHÔNG tự làm — đổi hành-vi detector = quyền anh / W5): detector cần **cơ-chế miễn-trừ tường-minh** cho khối ví-dụ (vd fenced-block hoặc allowlist path), thay vì bắt lead nhớ né.

🔸 **2 flag title-stale KHÁC là THẬT, ngoài scope W4** (không đụng): `form-engine/SKILL.md:15` (anchor 2026-04-30) · `docs/rag-setup-plan.md:4` (anchor 2026-05-12).
🔸 **`ef-core-migration/SKILL.md:167/:184` `writes 1 migration but canonical=66` = DƯƠNG-GIẢ** (cụm "1 migration" trong câu văn) — chính script tự khai: *"count-token grep is a soft net — module-local phrases … can false-positive; H18-A ratio-band demotes far-from-canonical counts to LOW = review-not-fail"*. LOW = đọc-bằng-phán-đoán, không auto-fix.

## 5. Nấc trung-thực của chính W4

- **Detector = DÒ + NÊU CỜ, KHÔNG tự sửa** (tầng D6). `exit 0` **luôn luôn** — advisory, không chặn build.
- **Fire ≠ chứng minh detector tốt** — chỉ chứng nó **đọc được** và **có canonical để so**. Chất-lượng thật đo ở tỉ-lệ dương-giả (mục 4 = 1 dương-giả cấu-trúc đã lộ).
- **Bước kế:** vá `permission-matrix` → chạy lại → **5 flag đó PHẢI biến mất** (và TOTAL tụt tương-ứng). Nếu vá xong mà **vẫn fire** ⇒ vá sai chỗ. Nếu TOTAL **không tụt đúng số** ⇒ có gì đó khác đã đổi ⇒ điều-tra, **đừng làm tròn**.

---

## 6. ✅ SAU KHI VÁ — chu-trình khép, số cộng KHỚP

```
permission-matrix : 5 FLAG  →  0 FLAG
TOTAL             : 50      →  45        (tụt ĐÚNG 5)
Flag MỚI xuất-hiện: 0
```

**Diff đo bằng `comm` (không đếm bằng mắt):** đúng **5 dòng** biến mất — `:3`×2 · `:16`×2 · `:81`×1 — **0 dòng thêm**. ⇒ bản vá **không đụng gì ngoài phạm-vi**; TOTAL tụt **đúng bằng** số flag đóng, không phải trùng-hợp làm-tròn.

**Cách vá = B1 (bỏ số, trỏ canonical), KHÔNG phải đổi số:**
| Dòng | Trước | Sau |
|---|---|---|
| `:3` description | `~60 menu key … ~240 policy` | bỏ số → *"số menu-key · policy canonical → `docs/STATUS.md`"* |
| `:16` Status | `(post Session 6 — 2026-04-30)` + `~60 menu key total` | anchor `2026-07-15 — S122 W4` + trỏ canonical + **chỉ ghi HÌNH-DẠNG nhóm, không ghi số** |
| `:81` | `register 48 policy` | `register policy {menu}.{action}` + khai **DERIVED** = `\|MenuKeys.All\| × \|Actions\|` |

🔴 **Đổi số thì lần sau lại stale — bỏ số thì hết stale vĩnh-viễn.** Đây cũng đúng lời khuyên detector tự in: *"resolve: update to 54 **OR replace with pointer '-> docs/STATUS.md'**"* — chọn vế sau.

## 7. 🔴 STALE NẶNG HƠN CON-SỐ (tìm được lúc vá, ngoài 5 flag)

`SKILL.md:20` liệt `Budgets root + 3 Bg_* (List/Create/Pending)` + `:23` *"Budgets KHÔNG inherit (Bg_* phải grant tay)"* — **như menu ĐANG SỐNG**.
**Đo đĩa:** module Budget **XOÁ từ S61 (Mig 50)**. Hai hit `Bg_` còn lại trong mã = **comment bia-mộ**: `MenuKeys.cs:70` *"[S61 Mig 50] Module Ngân sách cũ (Budgets + Bg_List/Bg_Create/Bg_Pending)"* · `fe-{admin,user}/src/lib/menuKeys.ts:29` *"… XÓA — thay bằng bảng"*. Code **sạch**; chỉ skill còn mô-tả.
🔴 **Detector count-token KHÔNG bắt được cái này** — nó so **con-số**, mà đây là **thực-thể không tồn tại**. Skill mô-tả sai suốt **S61 → S122**. ⇒ **giới-hạn của detector đếm-số**: nó thấy *"60 ≠ 54"* nhưng không thấy *"4 trong 60 cái đó đã bị xoá 60 phiên trước"*. Vá @S122 W4 (🧊-mark, giữ bia-mộ để tra-cứu).

## 8. Lead tự-bắt trong W4 (→ adap-report)

1. **WAL ghi "permission-matrix 6 dòng" — thật 5.** Dòng thứ 6 (`:70`) là context trong khối tóm-tắt anchor, không phải flag. *(Bắt được TRƯỚC khi thành claim gửi hub — nhưng vẫn là lỗi đếm, lần thứ 3 trong phiên.)*
2. **Suýt claim ẩu về canonical:** lead grep thô `MenuKeys.cs` ra **64** chuỗi rồi định dùng làm số. **SAI** — grep đếm **mọi chuỗi trong file**, không phải phần-tử mảng `All`. W2 ground đúng: `|MenuKeys.All|` = **54** (`MenuKeys.cs:147`) × `|Actions|` 4 = **216**. ⇒ **Lead bỏ số của mình, dùng số W2.** Bài học: *đo bằng công-cụ sai còn tệ hơn không đo — vì nó ra một con-số trông có vẻ đo được.*

---
*Capture @S122 `/tiep` W4 · mục 1-5 ghi TRƯỚC mọi sửa đổi (TOTAL 50) · mục 6-8 ghi SAU (TOTAL 45) · exit 0 cả 2 lần*
