# sub-task-0 — LANE W1-① : `scripts/governance-detectors.ps1`

**Role:** implementer · **Run:** 2026-07-15-S119-adap-6-broadcast · **Lane:** W1-①
**File EDITed (only):** `scripts/governance-detectors.ps1` (455 → 715 lines, +267/−7)
**VERDICT:** 3/3 việc DONE · fault-inject PASS 2 chiều · **TOTAL FLAGS 42 → 49** · exit 0 · non-ASCII 0

---

## 0. 🔴 4 tiền-đề trong TASK-BRIEF SAI so với đĩa (re-verify từ disk, memory `feedback_resume_premise_reverify`)

| # | Brief nói | Đĩa thật | Bằng chứng | Xử lý |
|---|---|---|---|---|
| P1 | config = `.claude/memory-budget.json` | **KHÔNG tồn tại**. Thật = `.claude/agent-memory/memory-budget.json` | `find . -iname '*memory-budget*'` → `./.claude/agent-memory/memory-budget.json` | Probe **2 path**, agent-memory/ TRƯỚC; in path đã resolve |
| P2 | STATUS.md:6 ⇒ in `2026-07-14` | **`2026-07-15`** | `git show 3fde8af:docs/STATUS.md` = 07-14 → commit **`4727d16`** (hôm nay 11:20:29, S119 doc-update) đổi thành 07-15 | KHÔNG sửa STATUS (cấm + = bịa). Detector in **07-15** = ĐÚNG |
| P3 | HANDOFF **chưa** backfill `[carry:*]` ⇒ 0-fire | **ĐÃ backfill — 7 khoá** | `grep -o '\[carry:[^]]*\]' docs/HANDOFF.md \| wc -l` = 8 (7 thật + 1 literal format-spec) | Vacuous **vẫn đúng** nhưng **lý-do KHÁC** (§4) |
| P4 | HANDOFF:5 ≈ 46 735 ký-tự | 46 493 ký-tự (drift nhẹ) | python `len()` | Không ảnh hưởng |

> ⚠️ `grep -c` đếm **DÒNG** không đếm **occurrence** → trên mega-line trả `1` ⇒ dễ kết luận nhầm "chưa backfill". Phải `grep -o … | wc -l`.

---

## 1. (A) GAP-3 — +2 token canonical

`Get-StatusValue` cũ resolve 4 token (:154-157). Thêm `menu` + `policy` qua map `$canonRows` (`:170-198`).

**Tên row ĐỌC TỪ ĐĨA, không đoán:**
- `menu` → `| Menu keys | **54** |` = `docs/STATUS.md:19` ✅ tồn tại
- `policy` → `| Policies | **216** |` ❌ **CHƯA có** (W2 mới thêm — spec v2:197)

**Row thiếu ⇒ FAIL-LOUD + SKIP** (không crash, không im-lặng-xanh):
```
[DETECTOR] MED | docs/STATUS.md | canonical row missing: no '| Policies | **<n>** |' row in
CURRENT STATE -> token 'policy' UNRESOLVED and SKIPPED (scanning nothing, NOT green)
```
Làm generic cho cả 6 token ⇒ row nào mất cũng kêu (0 flag mới cho 4 token cũ vì row đủ).

**stdout thật:**
```
STATUS.md canonical: mig=66 test=509 gotcha=82 table=89 menu=54 policy=MISSING
```
`menu` fire 2 flag thật: `permission-matrix/SKILL.md:3` + `:16` — “~60 menu key” vs canonical 54.

---

## 2. (B) Detector title-freshness — `H24-1`

- `anchor_patterns[]` = **MẢNG 2 DẠNG** (fix C2 quan trọng nhất của lane):
  1. `\*\*Last updated:\*\*\s*(\d{4}-\d{2}-\d{2})`
  2. `\*\*Status \(post Session \d+ <EM-DASH> (\d{4}-\d{2}-\d{2})\)\:\*\*`
- **Em-dash XÁC MINH BYTE, không đoán** — `grep … | xxd`:
  `6f 73 74 20 53 65 73 73 69 6f 6e 20 36 20 `**`e2 80 94`**` 20 32 30 32 36…` ⇒ **U+2014** ✔ (không phải hyphen)
  → build runtime `$EM_DASH = U @(0x2014)` (thân .ps1 ASCII-only, gotcha #30)
- `grep "Last updated" .claude/skills/` = **0 hit** ⇒ xác nhận: 1-regex + “không anchor ⇒ bỏ qua” **sẽ giết chính positive-control** ⇒ W4 bất-khả-thi.

### 🔴 PARSE NEO-PHẠM-VI — vì sao load-bearing (ĐO THẬT)
`docs/STATUS.md:6` = **69 870 ký-tự / 41 date-token**. Cả 3 chuỗi cấm đều **NẰM TRÊN LINE 6**:

| token | nguồn thật |
|---|---|
| `2026-08-01` | hạn audit tháng tới = **TƯƠNG-LAI** |
| `1987-03-07` | cắt từ mark-id `RC-pqhuy**1987-03-07**-2026-…` |
| `2026-20-42` | cắt từ mark-id `…-**2026-20-42**-…` — **tháng 20 ngày 42** |

`grep -o '[0-9]{4}-[0-9]{2}-[0-9]{2}' | sort | tail` → **`2026-20-42`**.
⇒ regex “ngày bất kỳ” lấy max ⇒ mốc-phải = rác/tương-lai ⇒ `trái < phải` **không bao giờ đúng** ⇒ **0 flag vĩnh viễn** = tự bóp chết positive-control mà vẫn xanh.
⇒ Fix: capture **chỉ ngày dính anchor** + `TryParseExact` (loại thẳng tháng-20/ngày-42).

- **mốc-phải** = MAX ngày-anchor hợp-lệ toàn corpus = `2026-07-15` @ `docs/STATUS.md:6`
- **`docs/_archive/` LOẠI KHỎI detector này** — snapshot đóng-băng có chủ-đích; “resolve = refresh ngày” sẽ **phá chính bản ghi lịch-sử** ⇒ flag không-thể-giải = nhiễu vĩnh-viễn.
  🔴 Loại **CỤC-BỘ** (`$TitleFreshSkip`), **KHÔNG** đụng `Test-Excluded` global → C1/C3 giữ nguyên baseline (nới global = đổi baseline lane khác = vượt quyền).
  Khai thật: giữ `_archive` sẽ +2 flag (47→49… thực ra 49→51). Loại đi **vẫn đạt ≥46** ⇒ không có mâu thuẫn số/đúng-đắn.

**stdout thật:**
```
anchors parsed: 5 doc(s) ; moc-phai (newest governance milestone) = 2026-07-15 from docs/STATUS.md:6
  anchor 2026-07-15  docs/STATUS.md:6
  anchor 2026-07-15  docs/HANDOFF.md:5
  anchor 2026-05-12  docs/rag-setup-plan.md:4
  anchor 2026-04-30  .claude/skills/permission-matrix/SKILL.md:16
  anchor 2026-04-30  .claude/skills/form-engine/SKILL.md:15
```
✅ `permission-matrix/SKILL.md` **FIRE** (76d behind). **KHÔNG sửa** file đó (W4 sở-hữu: fire → reviewer chứng-kiến → rồi mới vá).

> 📌 Cảnh báo cho W4: `docs/changelog/skill-audit-2026-05-late.md:43` có phán-quyết audit CŨ ghi đúng anchor này là *“section header timestamp historical, KHÔNG drift”*. H24/W4 lật lại là đổi-chính-sách có chủ-đích — nhưng nên biết là đang lật, không phải phát-hiện-mới.

---

## 3. (C) Detector carry-age — `H24-2` (INFORM-only)

- `M` = **`h24_cadence.light_every`** đọc từ `.claude/agent-memory/memory-budget.json`. **0 số nhịp hardcode.**
- Shape canonical (owner-decisions Q2): `h24_cadence: { light_every: 6, deep_every: 15, jump_on_class_repeat: 3 }`
- **dòng-carry = đoạn LOGIC**: đọc `-Raw`, split trên marker `NEXT anh|NEXT em` → **45 segment** (KHÔNG `Get-Content -TotalCount`: 12 dòng vật-lý, riêng :5 = 46 493 ký-tự).
- Chuỗi tính **chỉ trên segment CÓ carry**; phiên không phát carry **không phá chuỗi**; chỉ khoá vắng trên **dòng CÓ carry** mới reset.
- Charset khoá `[a-z0-9][a-z0-9._-]*` **loại `<`** ⇒ literal **`[carry:<slug>]`** (câu văn ĐỊNH-NGHĨA quy-ước trong HANDOFF) không bị đếm = tránh **TỰ-THAM-CHIẾU** (bài học lead W0.4 #1).
- Thiếu key ⇒ **FAIL-LOUD**, **không** giả-định default.

**stdout thật (repo thật, hôm nay):**
```
config resolved: .claude/agent-memory/memory-budget.json
[DETECTOR] MED | .claude/agent-memory/memory-budget.json | h24_cadence missing - W2 chua land
  => carry-age cadence UNRESOLVED, measuring NOTHING (no default is assumed ...)
HANDOFF logic-segments (NEXT anh/em) = 45 ; of those, carry-lines = 1
  [inform] carry 's119-wave' streak=1 carry-line(s) ; M unresolved -> NO aged/not-aged verdict   (×7 khoá)
```

### 🔴 KHAI THẬT — VACUOUS, nhưng lý-do KHÁC brief
Brief nói vacuous vì *“chưa backfill”*. **Sai.** Đã backfill 7 khoá. Vacuous vì **cấu-trúc**:
> **CHỈ CÓ ĐÚNG 1 dòng-carry** (seg#0 = `NEXT anh` của S119). Backfill chỉ đóng dấu **block hiện-tại**.
> ⇒ mọi streak = **1**. Với M ∈ {6,15,3} ⇒ **0 fire** — kể cả khi W2 land key.

**Hệ quả thiết-kế (cần lead quyết):** detector đo **streak ĐÓNG-DẤU**, không đo **tuổi thật**.
Bằng chứng cụ thể: `[carry:tra-bui-relogin]` ở seg#0 (S119); mà **seg#2 = `NEXT anh` của S118** nói đúng việc đó (*“tra.bui + 6 NV PRO đăng xuất → đăng nhập lại”*) nhưng **không được đóng dấu** ⇒ tuổi thật ≥2 phiên, detector đọc **1**.
⇒ Chỉ hết vacuous khi **mỗi phiên đóng dấu lại khoá còn mở vào block NEXT mới** (hoặc retro-stamp lịch-sử). Nếu không, streak **kẹt ở 1 vĩnh-viễn** — đây là “xanh từ commit đầu” đúng thứ H24 bảo phải nghi.

---

## 4. 🐞 BUG THẬT bắt được (nếu không fault-inject thì SHIP LỖI)

**PowerShell tên biến CASE-INSENSITIVE** ⇒ `$M` (cadence) **CHÍNH LÀ** `$m` (biến loop `foreach ($m in [regex]::Matches(...))`).
Match-object **ghi đè** cadence ⇒ `($null -eq $M)` thành **false** ⇒ rẽ nhầm sang `elseif ($n -ge $M)` ⇒
```
Could not compare "1" to "[carry:bvaau]".  (×7)
```
⇒ **nhánh FAIL-LOUD (= nhánh SỐNG hôm nay vì M unresolved) BỊ VỠ**, `[inform]` không in ra.
**Fix:** `$M` → `$CadenceM`, loop var `$m` → `$cm` (+ comment cảnh báo tại chỗ).
🔴 `exit 0` **che** lỗi này (detector luôn exit 0) ⇒ chỉ đọc exit-code là **không phát hiện**. Phải đọc stderr.

---

## 5. FAULT-INJECT (cây tạm `$env:TEMP\gd-fi`, chạy `-RepoRoot <cây-tạm>`)

| Ca | Inject | Kỳ vọng | stdout thật | Verdict |
|---|---|---|---|---|
| FI-1(c) | copy `permission-matrix/SKILL.md` anchor `2026-04-30` < mốc-phải | FLAG | `[DETECTOR] LOW \| .claude/skills/permission-matrix/SKILL.md:16 \| title-stale: anchor says 2026-04-30 but newest … 2026-07-15 (76d behind)` | ✅ |
| FI-1(d) | sửa anchor → `2026-07-15` | hết FLAG | `title-stale flags after repair = 0` ; `moc-phai … from .claude/skills/permission-matrix/SKILL.md:16` | ✅ |
| FI-2 | `light_every=1` | 7 FLAG | `M = h24_cadence.light_every = 1 (read from config, not hardcoded)` → `gap-carry-aged FLAGS = 7` | ✅ |
| FI-2 | `light_every=2` (**biên**) | 0 FLAG | `[ok] carry 's119-wave' streak=1 < M=2` → `gap-carry-aged FLAGS = 0` | ✅ |
| FI-2 | `light_every=6` | 0 FLAG | `[ok] … streak=1 < M=6` → `gap-carry-aged FLAGS = 0` | ✅ |
| FI-3 | `probe-garbage.md` anchor = `2026-20-42` | **không nhận** | `probe-garbage adopted as anchor? -> False` | ✅ |
| FI-3 | `probe-future.md` anchor `2026-05-01` + cùng dòng có `2026-08-01` + 2 mark-id | lấy `2026-05-01` | `anchor 2026-05-01 docs/probe-future.md:3` ; `2026-08-01 -> False` ; `2026-20-42 -> False` ; `1987-03-07 -> False` | ✅ |

**FI-2 = bằng chứng acceptance (a):** đổi số trong config ⇒ output đổi (7 flag ↔ 0 flag) ⇒ **thật sự đọc config**, không hardcode. Biên chính xác tại `M` (1 fire, 2 không).
**Anti-Goodhart:** gate **KHÔNG** xanh sẵn — baseline 42, cả 2 detector mới đều fire trên fault-inject và cùng tắt khi gỡ fault.

---

## 6. M3 SCOPE — số owner-đã-ký KHÔNG đụng

`git diff -U0` → dòng `-` **duy nhất** = 6 dòng canonical-resolve tôi tái-cấu-trúc + 1 dòng `countPatterns` (thêm dấu phẩy). **0 số ngoài-scope bị sửa:**

| Hằng-số | Dòng | Trạng thái |
|---|---|---|
| `$walLineCount -gt 40` (mark `RC-…12-07-2026-11-43-45`) | :459 | ✅ nguyên-văn |
| `$ratio -ge 0.5 -and $ratio -le 2.0` | :296 | ✅ nguyên-văn |
| `$maxGotcha + 50` | :335 | ✅ nguyên-văn |
| `$liveVariants.Count -ge 2` | :439 | ✅ nguyên-văn |

---

## 7. ACCEPTANCE (tính TỪ SỐ ĐO)

| # | Tiêu chí | Số đo | Verdict |
|---|---|---|---|
| (a) | config-driven; grep `memory-budget\|ConvertFrom-Json` 0 → >0 | **6** hit; FI-2 đổi 1→2 ⇒ 7 flag→0 flag | ✅ PASS |
| (b) | STATUS.md:6 ⇒ in ngày anchor, không ra 3 chuỗi cấm | in **`2026-07-15`**; garbage/future = False ×3 | ⚠️ **PASS-có-caveat** (literal brief `2026-07-14` STALE 1 commit — `4727d16`; §0 P2) |
| (c) | TOTAL ≥ 46 **và** có dòng `permission-matrix/SKILL.md` | **49** (42+7); có **3** dòng permission-matrix | ✅ PASS |
| (d) | exit luôn 0 | `EXIT CODE = 0` | ✅ PASS |
| (e) | non-ASCII = 0 | **0** byte >127; BOM-less | ✅ PASS |

**+7 flag (Compare-Object, không tự đếm tay):** policy-row-missing ×1 · menu ×2 · title-stale ×3 · h24_cadence-missing ×1.
**Dropped flags = NONE** ⇒ không phá detector cũ.

---

## 8. Cần lead xử (ngoài quyền lane W1-①)

1. 🔴 **Hợp-đồng chéo W2:** W1① đọc **`h24_cadence.light_every`** tại **`.claude/agent-memory/memory-budget.json`**. W2 phải land **đúng** path + sub-key đó, nếu không ① fail-loud vĩnh-viễn. (Spec chỉ ghi basename ⇒ mơ hồ.)
2. 🔴 **W2 nâng Sub-agents 12→14** ⇒ nay `menu`/`policy` đã sống, **row `Policies` phải land CÙNG** — nếu không, flag `canonical row missing` ở lại và mọi derived-doc ghi “14” sẽ bị flag stale-vs-12 (spec v2:176 cảnh báo “detector BÃO ⇒ ≥46 thoả bằng RÁC”). Hôm nay 49 **không** chứa rác loại đó.
3. ⚠️ **carry-age vacuous cấu-trúc** (§3) — cần quy-ước đóng-dấu lại mỗi phiên, nếu không detector vô-dụng dù W2 land.
4. ⚠️ **Bug tiền-tồn ngoài lane:** `:361` `$userMemDir = 'C:\Users\pqhuy\.claude\projects\…'` **hardcode tuyệt-đối**, KHÔNG derive `$RepoRoot` ⇒ chạy `-RepoRoot <cây-tạm>` **vẫn đọc user-memory thật** ⇒ fault-inject C1-wikilink không cô-lập được. Không sửa (ngoài 3 việc được giao + đổi baseline).
5. ⚠️ Acceptance (b) literal `2026-07-14` nên sửa thành **“in đúng ngày ở anchor STATUS.md:6”** (số tuyệt-đối tự-lão-hoá mỗi lần STATUS đổi ngày).
