# 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 | **** |' 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+ (\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:]`** (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 `) | 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 ` **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).