# sub-impl-M5-sentinel — closeout-sentinel.ps1 (M5 / K5)

> Role: implementer (hmw) · Task-4 · run `2026-07-24-S149-hoi-tu-bookend`
> Deliverable: `scripts/closeout-sentinel.ps1` (1 file mới) + positive-control 5 ca lịch-sử THẬT
> Trần lượt ~30 · VERDICT: **DONE — 5/5 positive-control khớp; predicate = OBS-PAIR (ground-truth), lệch tên-vai/counter so task-wording đã ghi rõ + chứng minh bằng đo, KHÔNG nới**

## 0. Trạng thái
- [x] Đọc spec M5 + K5 + VIPIX `:153` (`--nghi-thuc`) reference (đọc TRỌN handler)
- [x] Đo file-list 5 commit positive-control (git diff-tree)
- [x] Viết `scripts/closeout-sentinel.ps1` (ASCII-only body, PS 5.1, advisory exit-0)
- [x] Chạy positive-control 5 ca → 5/5 khớp
- [x] Fault-injection 5 test (teeth) — auto-detect · SKIP · role-set decisive · counter-gate · bad-commit
- [x] Deviation-vs-VIPIX + deviation-vs-task-wording ghi comment đầu file (D1-D5, T1-T2)

## 1. Đề-bài (M5 / K5)
`scripts/closeout-sentinel.ps1 -Commit <sha>`: với 1 commit closeout, KHI phiên đó CÓ chạy audit H24
(`-AuditRan` HOẶC tự dò run-folder H24 cùng phiên) → kiểm closeout CÓ ghi tín-hiệu vòng-audit lên đĩa chưa.
Thiếu → FLAG `audit do roi nhu chua do`. Positive-control lịch-sử THẬT: S140/S143 PASS · S146/S147/S148 FLAG.
K5 (predicate ĐÃ SỬA theo W2): **predicate = delta BẤT KỲ dưới `agent-memory/<role>/**`** (không MEMORY.md-only).

## 2. Phát hiện then chốt — predicate thực-tế = OBS-PAIR, không phải "lead-stale/gap-auditor"
Task-wording (dòng deliverable) ghi role = `lead-stale-auditor` HOẶC `lead-gap-auditor` + counter-AND.
NHƯNG đo đĩa 5 commit chứng minh predicate 5/5 = **obs-pair {harvest-curator, tooling-auditor}**, KHÔNG phải lead-audit pair:

- **invest sub-MD `:116`** (bản thiết-kế M5 gốc): *"kiểm ... delta CẢ `tooling-auditor/MEMORY.md` LẪN `harvest-curator/MEMORY.md`"* — obs-pair.
- **VIPIX `:153`** (nguồn tailor): kiểm `tooling-auditor/MEMORY.md` AND `harvest-curator/MEMORY.md` — obs-pair.
- **git file-list**: S140/S143 chạm obs-pair (→PASS); S146/S147/S148 KHÔNG chạm obs-pair (→FLAG).
- 🔴 **Chốt hạ (ca S148):** commit `932d607` chạm `lead-view-auditor/MEMORY.md` + `lead-omission-auditor/MEMORY.md`
  (TÊN CŨ của cặp soi-lead) **VÀ** `.session-counter.json`. Nếu predicate = lead-audit-pair thì S148 → **PASS**,
  MÂU THUẪN kỳ-vọng FLAG. Đã ĐO trực-tiếp (test T-C): `-Roles lead-view-auditor,lead-omission-auditor` trên S148 → **PASS**.
  ⇒ tập-role positive-control = obs-pair ĐỘC-QUYỀN (loại lead-audit pair ra khỏi tập).

Kết-luận: default `-Roles = harvest-curator,tooling-auditor`; **configurable** (M6 bookend-hoá xong thì trỏ
`-Roles lead-stale-auditor,lead-gap-auditor`). Đây là **hiệu-chỉnh tập-role theo ground-truth, KHÔNG nới** —
obs-pair AND-cả-hai chặt HƠN lead-pair OR-một-trong-hai.

## 3. Bảng đo file-list 5 commit (git diff-tree --name-status)
| SID | commit | obs-pair delta (path) | .session-counter.json | lead-audit pair | Kỳ vọng | Verdict |
|---|---|---|---|---|---|---|
| S140 | b146e2b | ✓ harvest+tooling **archive/2026-07.gist.md** | ✗ | ✗ | PASS | **PASS** ✓ |
| S143 | a1e2416 | ✓ harvest+tooling **MEMORY.md** | ✓ | ✗ | PASS | **PASS** ✓ |
| S146 | 8b6df0a | ✗ (chỉ reviewer/*) | ✓ | ✗ | FLAG | **FLAG** ✓ |
| S147 | 5f6c3be | ✗ (cicd/investigator/reviewer) | ✗ | ✗ | FLAG | **FLAG** ✓ |
| S148 | 932d607 | ✗ | ✓ | ✓ lead-view+lead-omission MEMORY.md | FLAG | **FLAG** ✓ |

🔴 Cột "archive/gist" ca S140 = chính điểm W2: predicate MEMORY.md-only sẽ FLAG OAN S140 (nó chỉ chạm gist,
không MEMORY.md) → K5 mở predicate thành "delta BẤT KỲ dưới role-dir" (script khớp mọi path: `MEMORY.md`, `archive/*`).

🔴 Ma-trận này chứng: KHÔNG combo nào có counter-AND cho S140=PASS (S140 counter=✗). Chi tiết mục 6/T2.

## 4. Thiết kế script (`scripts/closeout-sentinel.ps1`)
- Param: `-Commit <sha>` (bắt buộc) · `-AuditRan` (switch) · `-Roles <csv>` (default obs-pair) ·
  `-AnyRole` (OR thay AND) · `-RequireCounter` (nâng counter thành gate) · `-RepoRoot`.
- Luồng: resolve+validate commit → lấy file-list (`diff-tree --root`) → xác định audit-ran
  (`-AuditRan` HOẶC auto-detect run-folder chứa `h24|sub-lead|lead-(view|omission|stale|gap)`) →
  nếu KHÔNG-RUN ⇒ **SKIP** (không có gì để ghi, tránh false-flag) → nếu RAN ⇒ kiểm role-dir-delta
  (predicate-1, quyết verdict) + counter-touch (predicate-2, AUXILIARY/WARN, non-gating mặc-định).
- Role-delta match: file StartsWith `.claude/agent-memory/<role>/` (khớp cả `MEMORY.md` lẫn `archive/*`).
- AND semantics mặc-định (mọi role phải có delta — trung-thành VIPIX); `-AnyRole` nới thành OR.
- Advisory: **exit 0 LUÔN**; đọc dòng `verdict: PASS|FLAG|SKIP|ERROR`.
- ASCII-only body (gotcha #30 — verified 12976 bytes 0 non-ASCII); FLAG string transliterate ASCII
  `audit do roi nhu chua do` (bản gốc dấu tiếng-Việt nằm trong comment giải-thích, KHÔNG trong output-runtime của PS 5.1).
- Robustness fix trong lúc test: `[string[]]$Roles` khi gọi từ shell NGOÀI dạng `-Roles a,b` bị bind thành
  1 element `"a,b"` → thêm chuẩn-hoá split-on-comma (bắt được nhờ test T-C, KHÔNG suy-luận).
- Bug fix trong lúc test: `$PSScriptRoot` RỖNG khi `-File` bằng path TƯƠNG-ĐỐI (PS 5.1) → fallback chain
  `$PSScriptRoot → $PSCommandPath → $MyInvocation → cwd`.

## 5. Kết quả positive-control 5 ca (chạy trên repo THẬT)
```
b146e2b (S140) expected=PASS got=PASS  OK   [obs-pair delta = archive/2026-07.gist.md ×2, counter=n WARN]
a1e2416 (S143) expected=PASS got=PASS  OK   [obs-pair delta = MEMORY.md × 2, counter=y]
8b6df0a (S146) expected=FLAG got=FLAG  OK   [obs-pair 0/2, reviewer-only]
5f6c3be (S147) expected=FLAG got=FLAG  OK   [obs-pair 0/2]
932d607 (S148) expected=FLAG got=FLAG  OK   [obs-pair 0/2 dù có lead-view/omission+counter]
```
**5/5 KHỚP.** Không ca nào lệch → không phải nới predicate cho vừa (predicate obs-pair là ground-truth từ invest+VIPIX+đo đĩa).

Fault-injection (chứng có RĂNG, không chỉ happy-path):
- **T-A auto-detect:** S148 KHÔNG `-AuditRan` → tự dò `2026-07-24-S148-h24-force-fire` → RAN → FLAG. ✓
- **T-B SKIP:** S147 KHÔNG `-AuditRan` (phiên S147 không tạo run-folder H24) → NOT-RUN → SKIP (= vì sao positive-control phải assert `-AuditRan`). ✓
- **T-C role-set decisive:** `-Roles lead-view-auditor,lead-omission-auditor` trên S148 → **PASS** (delta OK cả 2 MEMORY.md)
  ⇒ chứng lead-audit-pair predicate sẽ vỡ S148=FLAG ⇒ obs-pair là tập DUY-NHẤT cho 5/5. ✓
- **T-D counter-gate boundary:** S140 `-RequireCounter` → flip **FLAG** (S140 counter=✗) ⇒ chứng counter-as-hard-gate
  vỡ S140=PASS ⇒ vì sao counter non-gating mặc-định. ✓
- **T-E bad commit:** `deadbeef...` → `verdict: ERROR resolve-commit`, exit 0 (graceful). ✓

## 6. Deviation ghi nhận (đầy đủ trong comment đầu file .ps1)
**vs VIPIX `:153` (`--nghi-thuc`):**
- D1 SCOPE: VIPIX soi 3-closeout gần nhất (rule-of-3) + auto-detect qua MARKERS (STATUS+HANDOFF+CRYSTALLIZED);
  SE soi ĐÚNG 1 `-Commit` (giống VIPIX `--commit` forced) vì K5 per-commit.
- D2 PATH: VIPIX pin `*/MEMORY.md`; SE mở "delta BẤT KỲ dưới role-dir" (W2-fix cho S140 archive-gist).
- D3 GATE: VIPIX không cổng "audit-ran" (obs-pair chạy vô-điều-kiện, vắng-mặt = tín-hiệu); SE cổng `-AuditRan`/auto-detect
  để phiên pre-bookend genuinely-skip → SKIP không false-flag.
- D4 COUNTER: VIPIX không kiểm counter; SE surface counter như WARN auxiliary.
- D5 RUNTIME: SE PS 5.1 ASCII-only (gotcha #30); VIPIX Node giữ dấu tiếng-Việt.

**vs task-wording (ghi rõ, KHÔNG nới — K5 "CẤM nới predicate cho vừa"):**
- T1 ROLE-DEFAULT: task ghi lead-stale/gap-auditor; ground-truth (invest:116 + VIPIX:153 + đo 5-commit + T-C) = obs-pair.
  Default obs-pair, configurable `-Roles`. Hiệu-chỉnh theo sự-thật, KHÔNG nới (obs-pair AND chặt hơn lead-pair OR).
- T2 COUNTER-AS-GATE: task ghi counter = hard-AND ("Thiếu → FLAG"). BẤT-KHẢ toán-học với 5/5:
  S140 counter=✗ (git-verified) mà PHẢI PASS ⇒ mọi `[X AND counter]` cho S140=FLAG. Spec K5 (authoritative)
  KHÔNG liệt counter làm gate. Giải: counter = REPORT/WARN non-gating; `-RequireCounter` nâng thành gate cho tương-lai
  (sau M2/M3 cơ-khí-hoá counter-tick). Đã chứng T-D.

## 7. Đường dẫn
- Deliverable: `D:\Dropbox\CONG_VIEC\SOLUTION\SOLUTION_ERP\scripts\closeout-sentinel.ps1`
- Sub-MD (file này): `.claude/workflows/runs/2026-07-24-S149-hoi-tu-bookend/sub-impl-M5-sentinel.md`
