# sub-impl-M1-scaffold — S149 hoi-tu bookend (implementer/hmw)

> Vai: **implementer (hmw)** · Deliverable: **M1** (checklist **K1**).
> Con-trỏ spec: `spec-hoi-tu-bookend-24-07-2026.md` §② M1 (dòng 32-33) + §③ K1 (dòng 72).
> Khung hub tham chiếu (TAILOR, không copy): `AI_INFRA/scripts/session_scaffold.py` (169 dòng) + `AI_INFRA/.claude/templates/session-context-template.md`.
> Status: **DONE — 4/4 K1 PASS + 6/6 boundary PASS.**

---

## 0. Phạm-vi + 2 deliverable

1. `scripts/session-scaffold.ps1` — 2 mode `-New` / `-Ensure` (**FILE MỚI**).
2. `.claude/templates/session-context-template.md` — template tách file. **ĐÃ tồn tại** dạng draft (generator=`session_scaffold.py`, placeholder `{{TS_SOURCE}}`) → **reconcile 2 sửa surgical** về `session-scaffold.ps1` + `{{HEAD}}`.

ISOLATION: chỉ đụng 2 file trên + sub-MD này. Git-status xác nhận footprint = đúng 3 file (mọi thay-đổi khác trong tree là của em-main/sub song song M2-M5, KHÔNG phải tôi).

---

## 1. Quyết-định thiết-kế

**D1 — 2 mode tách-đôi (khớp S146):**
- `-New`: cấp session MỚI = `max+1` (session-start ONLY). `-N <k>` tùy chọn pin id tường minh; nếu `session-<k>` đã tồn tại (folder HOẶC `_context`) → **COLLISION exit 4, ghi KHÔNG gì**.
- `-Ensure`: heal idempotent session HIỆN TẠI (pause/tiep). `_context` thiếu → tạo; `_context` có → **exit 0 NO-OP** (mtime giữ nguyên). `-N` tùy chọn; mặc định = `max`.
- Phát hiện `-N` tường-minh qua `$PSBoundParameters.ContainsKey('N')` (không dùng sentinel bẩn).

**D2 — ts-mốc = git-committer, CẤM wall-clock (spec M1:33):** `git -C <RepoRoot> log -1 --format=%cI` cho ts + `--format=%h` cho HEAD-sha. Chạy qua helper `Invoke-Git` (EAP=Continue cục bộ → cây-non-git exit 128 KHÔNG throw dưới `$ErrorActionPreference='Stop'` toàn cục).
- 🔸 **DEVIATION-vs-hub tường minh:** hub `head_ts()` (py :49-71) fallback `datetime.now(UTC)` gắn tag `FALLBACK`. SE **từ chối fallback** → git-down = **exit 3**. Lý do: ts nuôi phép đo **C10 LATENESS** (so ts-scaffold vs ts-đầu-phiên) — wall-clock phá tính-xác-định. Chứng răng ở BC6.

**D3 — CẤM đè `_context` (mọi mode):** hard-guard trong `Write-Context` — `Test-Path` trước khi ghi, tồn-tại → `Fail exit 5`. Backstop độc-lập với logic mode (New chặn @collision, Ensure chặn @no-op); guard là tầng phòng-thủ cuối.

**D4 — encoding:** đọc template `[IO.File]::ReadAllText(path, UTF8)` (decoder .NET tự strip BOM), render bằng `String.Replace` literal (KHÔNG regex → `<N>`/`<k>` trong ENTRY-SCHEMA sống sót, khớp lý-do hub py:74-76), ghi `[IO.File]::WriteAllText` + `UTF8Encoding($false)` no-BOM + normalize CRLF→LF. Render thay đúng 3 placeholder: `{{N}}` `{{TS}}` `{{HEAD}}`.

**D5 — pure-ASCII code (gotcha #30/#37):** `.ps1` = 6785 byte, 0 byte>127, no-BOM (đo byte-level). Tiếng Việt CHỈ nằm trong template `.md` (render đúng, chứng ở §2). Message trong `.ps1` = English/ASCII.

**D6 — `-RepoRoot` fault-inject seam** (default `$PSScriptRoot\..`, khớp house-style `distill-shard-probe.ps1:13`) → chạy trên cây tạm chứng RĂNG.

**Exit codes:** 0 ok/no-op · 2 usage · 3 git-unavailable · 4 collision · 5 defensive-overwrite-block.

**Template reconcile (2 sửa):**
```
- nguon-ts={{TS_SOURCE}} | generator=scripts/session_scaffold.py
+ nguon-ts=git log -1 --format=%cI @ HEAD {{HEAD}} | generator=scripts/session-scaffold.ps1
```
(+ dòng ts-mốc `(nguồn: {{TS_SOURCE}})` → `(nguồn: git log -1 --format=%cI @ HEAD {{HEAD}})`). Giữ nguyên toàn-bộ 3-block SE-tailored sẵn có (STOCK-map rows SE: STATUS/HANDOFF/.session-counter/broadcasts).

---

## 2. Fault-inject K1 — 4 ca (chạy THẬT trên cây tạm scratchpad `fi-scaffold`, git-init + commit)

Harness: `scratchpad/fi-run.ps1`. Gọi script qua **child `powershell.exe -File`** (bắt buộc — `exit N` in-process sẽ giết harness; child-process cho `$LASTEXITCODE` thật + test dưới PS 5.1 thật).

| Ca | Kỳ vọng | Kết quả đo | Verdict |
|---|---|---|---|
| (a) `-Ensure` ×2 | lần 2 exit 0 + mtime `_context` KHÔNG đổi | run1.exit=0 (create) · run2.exit=0 · mtime_eq=True · hash_eq=True (sleep 1.2s giữa 2 lần → nếu ghi-đè mtime PHẢI đổi) | **PASS** |
| (b) `-New` khi max=3 | tạo `session-4` | exit=0 · session-4 tạo · noLeftover-`{{`=True · sID=4 · gen=ps1 · ts=git-cI khớp · head=%h khớp · only4=True · noBOM · noCRLF | **PASS** |
| (c) `-New -N 3` collision (folder) | exit≠0 + 0 ghi | exit=4 · `_context-s-3.md` KHÔNG được tạo | **PASS** |
| (d) `_context` tồn tại + `-New -N 3` | exit≠0 KHÔNG đè | seed.exit=0 · collide.exit=4 · hash_eq=True · mtime_eq=True (sleep 1.2s giữa) | **PASS** |

**Evidence render (session-4 `_context`):**
```
# _context-s-4 — SESSION-MODEL narrative durable
<!-- SCAFFOLD-META: sID=4 | ts-moc=2026-07-24T23:56:20+07:00 | nguon-ts=git log -1 --format=%cI @ HEAD 010b4bf | generator=scripts/session-scaffold.ps1 -->
> ts-mốc khởi-tạo = 2026-07-24T23:56:20+07:00 (nguồn: git log -1 ... @ HEAD 010b4bf) — mốc git-committer, KHÔNG phải wall-clock
```
(Tiếng Việt render đúng, no mojibake; ts = committer-ts của HEAD cây-tạm, KHÔNG wall-clock.)

### Boundary checks bổ sung (anti-Goodhart — chứng create-path FIRE, không chỉ refuse):
| BC | Kỳ vọng | Đo |
|---|---|---|
| BC1 `-New` cây FRESH (max=0) | tạo session-1 | exit=0 created=True **PASS** |
| BC2 `-New -Ensure` cùng lúc | usage exit 2 | exit=2 **PASS** |
| BC3 không mode | usage exit 2 | exit=2 **PASS** |
| BC4 `-Ensure` default=max (session có) | no-op exit 0 | exit=0 **PASS** |
| BC5 `-Ensure` cây rỗng (0 session) | exit 2 (không gì để heal) | exit=2 **PASS** |
| BC6 git-unavailable (cây non-git) | exit 3 (từ chối wall-clock) | exit=3 **PASS** |

BC6 = chứng RĂNG của invariant "CẤM wall-clock" (D2); BC1 = chứng script THẬT tạo được (không Goodhart-refuse-all).

---

## 3. Verdict

**M1 DONE — 4/4 K1 PASS + 6/6 boundary PASS.** 2 deliverable:
- `scripts/session-scaffold.ps1` (mới, pure-ASCII, PS 5.1 parse-clean)
- `.claude/templates/session-context-template.md` (2 sửa surgical → ps1-generator + `{{HEAD}}`)

**CÒN LẠI cho em-main (NGOÀI write-scope của tôi — isolation):**
- 🔴 **K1 "vết-chữ-owner"**: "quyết định owner liên quan phải có ≥1 hit trên bề-mặt bền (MARKS/ledger)". Đây là ghi vào `ACTIVE-MARKS.md`/ledger — **thuộc em-main**, tôi KHÔNG đụng (isolation). Nhãn quyết-định hình-B = `OWNER-DELEGATED → lead-decided` (spec dòng 29). Em-main cần verify ≥1 hit trên MARKS/ledger.
- **Wire call-site**: script chưa được gọi từ `session-start`/`tiep` (đó là **M6/K6 = em-main**, land SAU M4 theo thứ-tự spec dòng 64-65). M1 chỉ giao *bộ máy CLI*.
