Files
solution-erp/.claude/workflows/runs/2026-07-15-S119-adap-6-broadcast/sub-reviewer-R3.md
pqhuy1987 4727d16178 [CLAUDE] Docs: S119 adap 6 broadcast AI_INFRA - dieu tra chieu rong + spec v3 (dung truoc wave)
check-email 6 broadcast moi (H23 model-at-spawn / H24 lead-self-audit / H22 wal-defect-fix
5-san / H22 push-guard / EOL-CRLF agent-registry / owner-sign) - hash 7/7 + tamper 4/4 MATCH.

Pipeline H21: fable-clone invest 5-lane (wf_1f6bd5e2-478, 5/5 clean) -> spec v1 ->
fable-clone reviewer 4-lane (wf_78a84f9b-03e: R2 FAIL 4C+8M, R3 FAIL 5C+6M, R1/R4 chet #53)
-> v2 -> fable-real reviewer cong-cuoi (wf_cb964f83-331: GO-WITH-FIXES + 8 fix + 2 honesty-C)
-> v3 (STILL-BROKEN = 0). Anh chot B: dung tai spec, wave chay phien sau qua /tiep.

Do that: 57 wal: da lot origin/main (hub do duoc 1 -> SE = 57x); nguyen nhan KHONG phai
turn-seam ma la session-end.md:113 SE tu viet "kep sau -> GIU NGUYEN". K troi 3->10/buoi.
v1 SE GAY MAT VIEC (R3 fault-inject: NONWAL=0 -> 0 commit de fixup -> detached HEAD ->
hook fail-open -> abort -> mat worktree = E-029 bang cua sau).

Owner 6 chot: nguyen-tac moi (vuot-khung = request anh + AI_INFRA) / PA-2 = PA-2a hang-so +
PA-2b audit / 6-15-3 / [carry:slug] / KHONG don 57 / fix#8 (a)(b).

0 prod-code, 0 migration, 0 FE. Test 509 giu nguyen (45D + 464I).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 11:20:29 +07:00

159 lines
27 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Reviewer lane R3 — safety-wave
> RETURN-only /fable-clone reviewer ensemble wf_78a84f9b-03e. Lead ghi (B3). agentId=a196f3052f681bacf
---
VERDICT: FAIL — W0.3's fixup mechanism provably breaks on this repo's exact state (detached HEAD → hook commits onto it → `--abort` destroys work, fault-injected end-to-end), W0.2's "dirty=0 / cửa-sổ sạch" premise is already false, and the push-guard deadlocks on first use because the squash and the count live in different turns.
Measured at review time (all read-only, real repo unless marked temp-tree):
---
## C — CRITICAL
**C1. W0.3's primary mechanism (`GIT_SEQUENCE_EDITOR` → all `pick <sha> wal:` → `fixup`) HARD-FAILS today, and the failure state destroys uncommitted work.**
- **what · where:** spec §2.0 W0.3 + §3.8 row "`rebase -i` bị chặn harness → `GIT_SEQUENCE_EDITOR` + **fixup** (KHÔNG drop)".
- **WHY sai:** `git merge-base --is-ancestor 3fde8af origin/main`**YES**; `git rev-parse origin/main` = `3fde8af` exactly. So `origin/main..HEAD` = **5 commits, 5/5 `wal:`** — there is **no non-`wal:` commit in the range to fixup INTO**. `git rebase -i origin/main` only exposes commits *above* origin/main, so every todo line becomes `fixup`.
Fault-injected in a temp tree (1 pushed base + 5 `wal:`, identical shape):
```
error: cannot 'fixup' without a previous commit ×5
REBASE-EXIT=1
.git/rebase-merge → REBASE-STATE-LEFT-BEHIND
git rev-parse --abbrev-ref HEAD → HEAD (DETACHED)
```
Then, still in that state, I replicated `.claude/hooks/wal-flush.ps1:7-17` verbatim (it is unconditional and fail-open — `exit 0`, `2>$null`, **no rebase-state guard**):
```
hook commit landed at: f6e05f9 on HEAD ← detached
git rebase --abort → branch main
grep -c PRECIOUS... wal.md → 0 ← WORKTREE: *** GONE ***
merge-base --is-ancestor f6e05f9 HEAD → *** ORPHANED (reflog only) ***
```
This is `E-029` reached by a route W0.2 does not guard. The spec's only mention of this case is a parenthetical "**Biên:** wal: ở đáy range … ⇒ giữ nhánh `reset --soft HEAD~K`" — it is stated as an *edge*, not as **the branch that fires today**, and no runtime discriminator is written. A worker that gets the spec via args and does not Read `session-end.md` will run the documented `fixup` path.
- **FIX:** W0.3 must specify an explicit runtime branch **before** any rebase, plus a hook interlock:
1. `RANGE=$(git rev-list origin/main..HEAD)`; `NONWAL=$(git log --format='%s' origin/main..HEAD | grep -vc '^wal:' || true)`
2. `NONWAL == 0` → **`reset --soft HEAD~K` ONLY. Never invoke rebase.**
3. `NONWAL > 0` → rebase+fixup, and the script must leave the **first** todo line as `pick` (or `reword`) — never emit a leading `fixup`.
4. Any rebase path must be wrapped: `trap 'git rebase --abort' ERR` + a hard rule "if `.git/rebase-merge` or `.git/rebase-apply` exists → STOP, do not let a turn end". Optionally add a rebase-state guard to `wal-flush.ps1` (`if (Test-Path .git/rebase-merge) { exit 0 }`) — otherwise the hook writes to a detached HEAD.
- **VERIFY:** re-run the temp-tree injection above → expect `reset --soft` branch chosen, `REBASE-EXIT` never non-zero, and `ls .git/rebase-merge` → absent after the step.
**C2. `K`/`n` is stale and still moving — three different values in three artifacts within one morning. A literal `K` either deadlocks or force-pushes over a pushed commit.**
- **what · where:** spec §1.1 "n=4 chưa-đẩy"; §2.0 W0.3 "ca **n=4=K** hôm nay"; §3.9.
- **WHY sai:** `.claude/WAL.md:7` says **n=3**; spec says **n=4**; `git rev-list --count origin/main..HEAD` **now = 5** (`110ade2 wal: flush 20260715T0940` landed after the spec was written — it committed `lead-evidence-hub-inspect.md`). The Stop-hook fires at **every** turn boundary, so K grows monotonically until the squash runs. Two failure modes:
- `K` too small (4 when 5) → 1 `wal:` survives → push-guard `n=0` fails → **deadlock**.
- `K` too large → `HEAD~K` lands **below** `origin/main` → branch behind remote → `git push` rejected non-FF → the tempting "fix" is `--force`, which **rewrites `3fde8af` on the shared remote** = the exact N.4 floor §3.5 spends three paragraphs defending. The off-by-one is not hypothetical — it is already present in the spec.
- **FIX:** delete every literal `n=4`/`K` from spec and §5.0 text. Mandate: `K=$(git rev-list --count origin/main..HEAD)`, then two asserts before touching anything — (i) `[ "$(git log --format='%s' origin/main..HEAD | grep -vc '^wal:' || true)" -eq 0 ]`, (ii) `[ "$(git rev-parse HEAD~$K)" = "$(git rev-parse origin/main)" ]`. Abort loudly if either fails.
- **VERIFY:** `git rev-list --count origin/main..HEAD` immediately before and after the squash; assert `HEAD~K == origin/main` pre-reset.
**C3. Push-guard deadlocks on first use: the count is moved into the push turn, the squash is not.**
- **what · where:** spec §2.1 row `push-guard`: "Chuyển **phép đếm** vào chính khối §5.2, chained ngay trước `git push origin main`"; acceptance "(ii) đếm **ngay trước** push = 0 **trong CÙNG transcript-turn** với `git push`".
- **WHY sai:** the Stop hook (`.claude/settings.json` → `wal-flush.ps1`) commits `.claude/WAL.md`, `.claude/agent-memory/`, `.claude/workflows/runs/` at **every turn boundary**. The spec mandates count+push share a turn but says nothing about **§5.0 (squash) sharing that turn**. Between §5.0 and §5.2 the closeout provably dirties hook paths: Sàn-2 writes `closing:` into WAL at "session-end BƯỚC 0" (§2.1 Sàn-2), W4 spawns a reviewer (agent-memory write), W4/W5 write into `runs/<id>/`. Each such turn boundary → one new `wal:` → `n≥1` at §5.2 → **guard blocks push → session cannot close**. The predictable human response is to bypass the guard, i.e. the gate deletes itself.
Historical confirmation that the current design only survives because §5.0..§5.2 happen to be one turn: `3fde8af` (S118 closeout, pushed) @16:29:55, then `6de7db6 wal: flush 20260714T1631` fired ~1 min later — exactly the §6.4 note at `session-end.md:145`.
- **FIX:** §5.2's block must be **squash → count → push, all three chained in ONE turn**, i.e. re-run the §5.0 computation inside the §5.2 block rather than trusting the earlier §5.0 result. State it as: "§5.0 is a *pre-pass*; §5.2 re-executes it. If §5.2's recount is >0 → re-squash once, recount, then push; if still >0 → STOP + report." Add the acceptance "no turn boundary between the final squash and `git push`".
- **VERIFY:** dry-run a closeout that deliberately dirties WAL between §5.0 and §5.2 → the block must still reach `git push` without manual intervention; `git log --format='%s' origin/main -20 | grep -c '^wal:'` unchanged (57).
**C4. W0.2's "worktree đang sạch / dirty = 0" is FALSE right now, and `git add --renormalize .` silently stages dirty content while the acceptance is structurally blind to it.**
- **what · where:** spec §2.0 header "(a) worktree **đang sạch** = cửa-sổ an-toàn DUY-NHẤT"; W0.2 "Hiện dirty = 0 (chỉ 7 broadcast untracked) ⇒ **cửa-sổ sạch**"; acceptance "`git diff --stat` sau renormalize = rỗng".
- **WHY sai:** `git status --short` **now** → `M .claude/WAL.md` (plus 7 untracked broadcasts + 6 untracked run-folder files). The premise is stale, and the spec is bơm qua args — the worker will not re-measure unless told to. Fault-injected in a temp tree (`core.autocrlf=true`, one `i/lf w/crlf` file, one dirty file with uncommitted content):
```
git add --renormalize .
git diff --stat → (empty) ← spec acceptance PASSES
git diff --cached --stat → .gitattributes | 1 +
WAL.md | 1 + ← dirty file SILENTLY STAGED
```
`--renormalize` is `git add`: unstaged diff is empty **because** everything moved to the index. The acceptance cannot distinguish "no-op" from "staged a pile", so "diff RỖNG ⇒ 0 commit ⇒ 0 CI" is unsound whenever the tree is dirty — which it is.
- **FIX:** (i) W0.2 step 0 = **re-measure**, not trust the spec: `git status --porcelain` must be captured and every entry classified before any git-index or worktree op; abort if unclassified. (ii) Replace the acceptance with `git diff --cached --stat` = empty (the staged view), and pre-check with `git add --renormalize --dry-run .` (verified working: exit 0, lists files) instead of the live `git add`. (iii) Scope the renormalize to the gate paths rather than `.`.
- **VERIFY:** `git status --porcelain` → non-empty today (proves the premise stale); `git add --renormalize --dry-run .` → lists exactly the intended set and nothing dirty.
**C5. §2.0 W0.2's acceptance (toàn-repo) contradicts §2.2 SCOPE + §3.9 (scoped) — and the scoped gate is ALREADY GREEN, so the toàn-repo variant is 188 files of pure risk for zero gate benefit.**
- **what · where:** §2.0 W0.2 acceptance: `git ls-files --eol | grep -c 'i/lf.*w/crlf'` = **0** (unscoped) **vs** §2.2 🔴 SCOPE + §3.9:262: `git ls-files --eol .claude/agents .claude/commands .claude/skills | grep -c …` = **0** (scoped).
- **WHY sai:** measured via `git ls-files --eol` (the authority the trục mandates):
| metric | measured | spec |
|---|---|---|
| tracked total | 1312 | — |
| `i/lf w/crlf` | **188** | 188 ✓ |
| …of which `Migrations/` | **131** | 131 ✓ |
| **gate scope** (`agents`+`commands`+`skills`) | **0** | 0 |
The scoped gate **passes today with zero work**. The unscoped acceptance demands touching all 188 — and since `git add --renormalize` does **not** rewrite the worktree (proven: file stays `i/lf w/crlf` after renormalize; only `rm` + `git checkout --` yields `w/lf`), satisfying it requires `rm`+`checkout` on **188 files incl. 131 EF migrations**, plus the 6 files the spec itself names as live: I confirmed all 6 are in the 188 (`docs/STATUS.md`, `docs/HANDOFF.md`, `docs/CLAUDE.md`, `.claude/agent-memory/{implementer-backend,reviewer}/MEMORY.md`, `.claude/agent-memory/.mfe-state.json`). Worse, it is **un-holdable**: W1/W2 spawn agents that rewrite `agent-memory/*/MEMORY.md` → back to `w/crlf` → the gate re-breaks after it was "passed".
- **FIX:** delete the unscoped acceptance from §2.0 W0.2. Keep only §2.2/§3.9's scoped gate; declare the remaining 188 **INFORM-only** (as §2.2 already argues) and explicitly forbid the mass `rm`+`checkout`. If W0.1 is landed, new/rewritten files normalize on `git add` anyway — no sweep needed.
- **VERIFY:** `git ls-files --eol .claude/agents .claude/commands .claude/skills | grep -c 'i/lf.*w/crlf'` → **0 today**, and still 0 after W2 writes the 2 new agents.
---
## M — MAJOR
**M1. `ls .claude/agents/*.md = 14` fails by construction — the real number after +2 is 15.**
`ls -1 .claude/agents/*.md | wc -l` = **13** = 12 roster + **`README.md`** (which is also a `.md` in that dir). +2 new agents = **15**, not 14. Spec §2.4(1)(a) and §3.9:271 both assert 14. The gate can only be made green by *not* creating one agent, or by deleting `agents/README.md` — both catastrophic-by-Goodhart.
**FIX:** state the invariant on the roster, not the glob: `VALID_ROLES.length == 14` **AND** `(ls .claude/agents/*.md | grep -v README.md | wc -l) == 14`. **VERIFY:** both counts equal 14 post-W2.
**M2. W1① `governance-detectors.ps1` is NOT "🟢 1 agent owner, disjoint" — it is coupled to `docs/STATUS.md`, which §3.2's conflict table omits entirely.**
`Get-GovernanceMd:118-127` and `Write-Flag:43-53` / `Write-Section:55` anchors are exact (verified). But C2/B3 resolves canonicals from STATUS.md's CURRENT-STATE table via `Get-StatusValue` (`:154-157`, pattern `^\|\s*<label>\s*\|\s*\*\*(\d+)`). Measured rows: `Migrations|SQL tables|Master data|API endpoints|FE pages|Menu keys|Tests|Gotchas|User memory|Skills|Sub-agents`.
- `| Menu keys | **54** |` **exists** → the `menu` token works (and disk truth `grep -c "public const string" MenuKeys.cs` = **54**, matching §1.5 exactly).
- **There is NO `Policies` row** → `Get-StatusValue 'Policies'` = `$null` → the `policy` half of GAP-3 cannot fire without first adding `| Policies | **216** |` to STATUS.md. Not mentioned anywhere in the spec.
- `| Sub-agents | **12** |` is a **canonical row**. W2 takes the roster to 14. If STATUS.md is not updated in the same landing, C2/B3 will flag every derived doc that says "14" as stale-against-canonical-12 → the detector **storms on H24's own change**, and §2.4(2)(c)'s "TOTAL ≥ 46" gets met by garbage.
**FIX:** add `docs/STATUS.md` to §3.2 as a **SERIAL em-main** file; land `Policies` row + `Sub-agents 12→14` in the same commit as W2; make W1①'s task explicitly depend on it. **VERIFY:** `sed -n '10,24p' docs/STATUS.md | cut -c1-40` shows both rows; detector output has no `Sub-agents` stale flags.
**M3. §3.9's `TOTAL FLAGS ≥ 46 VÀ có dòng permission-matrix/SKILL.md` is unsatisfiable as a CLOSE gate — W4's entire purpose is to make that line disappear.**
Measured baseline by running the script: **`TOTAL FLAGS: 42`** (§1.5's "42 flag baseline" ✓ confirmed; `grep -c memory-budget` = 0 and `grep -c ConvertFrom-Json` = 0 ✓ confirmed). §2.4(4) requires "(4) chạy lại ⇒ dòng **BIẾN MẤT**, TOTAL giảm đúng số đã vá". So at close time the §3.9 line is FALSE by design. A worker running the final checklist sees FAIL and is incentivised to revert the patch to go green.
**FIX:** split into two time-anchored gates — pre-W4: "TOTAL ≥ 46 **AND** line present, evidence = detector stdout captured to `runs/<id>/w4-precontrol.txt`, reviewer-witnessed"; post-W4: "line **absent** AND `TOTAL == 46k` where k = flags patched". The pre-gate must reference the saved artifact, never a re-run.
**M4. Sàn-3 ① drops the dual-accept clause → 5 guaranteed false positives; and the spec cites a blank line as its authority.**
`README.md:20` = **a blank line** (the code fence closes at :19). The real orphan definition is **`README.md:83` (L2)**, which reads: "orphan = folder có `run.md` mà KHÔNG có `<stage>-synthesis.md` … **run cũ S71 dual-accept `harvest/<stage>-synthesis.md`**" — mirrored at `session-start.md:97` and `README.md:123` (C8 "close-gate ACCEPT BOTH"). Spec §2.1 Sàn-3 ① states only "có `run.md` mà `*-synthesis.md` vắng/0-byte" — **no dual-accept**. Measured: a naive flat scan flags **13** folders; with dual-accept it flags **8** (the 5 `h10`/`h910` folders each have `harvest/<stage>-synthesis.md`, verified by `ls`). So the spec's headline "8/27 orphan, 7 tồn-đọng" is **correct** — but only under a rule the spec omits. Since Sàn-3's canonical text gets **re-authored** into `tiep.md` §0 (W3) and the worker does not Read `README.md`, the clause is lost → h10 ×3 + h910 ×2 storm — which W0.5's "close 7 folders" does **not** cover, because those 5 were never in the 7.
**FIX:** carry the dual-accept clause verbatim into Sàn-3 ①'s canonical text; fix the citation to `README.md:83`. **VERIFY:** run the new scan → exactly `{2026-07-15-S119-adap-6-broadcast}` (+ nothing else) after W0.5.
*(Side note, not spec error: `2026-07-14-S117-pe-approve-minmax-range` and `-pe-luyke-finalize` have **no `run.md` at all** → invisible to a `run.md`-anchored scan. C2 fragile-point, worth an INFORM bậc.)*
**M5. Sàn-1's tripwire breaks documented live paths and its wrapper list is wrong.**
`hmw.js` anchors verified exact: `checkpointApproved` :83-85 ✓, `VALID_ROLES` :22 (12 entries) ✓, STOP-HARD :104 ✓, `resolveModel` :43-51 ✓, H8 comment :32-42 ✓. But the proposed `if (A.taskList.length >= 3 && !(A.run?.dir && A.run?.name)) throw`:
- (a) **kills the documented legacy alias.** `hmw.js:116`: `const wave = (A.run && A.run.dir) ? A.run : ((A.wave && A.wave.dir) ? A.wave : null)` — `args.wave` is a supported entry point (`:19`, `:115`). A ≥3-task call using `wave:` throws even though RUN-TRACE is fully satisfied.
- (b) **contract mismatch:** the tripwire demands `.dir` **and** `.name`; hmw.js's own gate requires only `.dir`. A call with `run:{dir}` works today, throws after.
- (c) **wrapper list wrong.** Measured: `git grep -c "args\.run" -- .claude/commands/` → **exit 1, 0 hits**. `.claude/commands/` contains **no `hmw.md`** — the spec's third wrapper "`/hmw`" does not exist as a file. And **`fable-real.md` is absent from the list** although it launches ensembles ≥3 lanes (`fable-real.md:19` "Vai BẮT BUỘC ∈ roster 12"). First `/fable-real` ≥3-lane run after this lands → **throw** = "vỡ đường đang chạy", precisely what the spec's own §2.1 warns about.
**FIX:** tripwire condition → `const w = (A.run&&A.run.dir)?A.run:((A.wave&&A.wave.dir)?A.wave:null); if (A.taskList.length >= 3 && !w) throw` (mirror :116 exactly, drop the `.name` requirement or add `.name` to :116's contract in the same edit). Enumerate wrappers by measurement, not memory: `fable-clone.md`, `fable-real.md`, `ultra-on.md` (+ wherever `/hmw` is actually defined — locate it first). **VERIFY:** `git grep -c "args\.run" -- .claude/commands/` ≥ 3 **and** a ≥3-task `wave:`-style call still succeeds.
**M6. §2.4(5)(c)'s sweep grep is the wrong pattern and "5 chỗ" undercounts by ~3×.**
Grep tool (not Bash — per the trục's false-negative warning) for `12-agent|12 sub` → **~14 hits in 6 active files**: `session-start.md` :2/:66/:73/:74/:202 · `session-end.md` :2/:83 · `agents/README.md` :1/:3/:31/:35 · `workflows/README.md:36` · `ultra-on.md:24` · `fable-real.md:36`. The pattern **misses** the other live phrasing `roster 12`/`12 vai`/`10→12`: `fable-clone.md` :3/:18/:21/:34 · `fable-real.md` :3/:19 · `fable-real-runbook.md` :74/:94/:148/:191/:314/:444/:450/:501/:503/:509/:520 · `harness-11-engine.md` :332/:345 · `ACTIVE-MARKS.md:19`. It also misses an **already-stale** one: `hmw.js:9` meta.description says "fan-out **9-agent** roster". So the acceptance can return 0-hit while the drift is live = false-PASS. Note `ultra-on.md:24` literally heads a VALID_ROLES list — leave it at 12 and a lead reading it concludes `lead-view-auditor` is not a valid role → never spawns it → **H24 dead on arrival**.
**FIX:** sweep pattern `(12|9)[- ](agent|sub)|roster 12|12 vai|VALID_ROLES[^0-9]*12`; replace "5 chỗ" with the measured list; fix `hmw.js:9` in the same edit.
---
## m — minor
- **m1.** Spec cites "`.claude/WAL.md:15` `verify:`" twice (§2.1 push-guard (a), §3.8). The `grep -c '^wal:'` idiom is at **`:16`**; `:15` is `next:`. The idiom claim itself is **correct** (`grep -c` exits 1 on 0 matches). WAL is auto-rewritten, so pin by content, not line.
- **m2.** Spec's `1267 i/lf` → measured **1268**. `110ade2` added one file after the spec was written. Harmless in itself; it is the same staleness class as C2/C4 and a cheap tell.
- **m3.** Spec asserts `git rebase -i` "**BỊ CHẶN** trong harness". Empirically it **ran** in my Bash tool with `GIT_SEQUENCE_EDITOR` set (see C1 transcript). The premise is wrong; the chosen workaround is still fine, so this is cosmetic — but "blocked" shouldn't be sold as measured.
- **m4.** §2.4(1)(b) mandates FLAG format `^[A-Z0-9-]+ \| <class> \|`. The existing convention (`governance-detectors.ps1:24` doc + `:51` `Write-Flag`) is `[DETECTOR] {SEV,-4} | {file:line} | {desc} | resolve: {…}` — a `[DETECTOR] ` literal prefix. Two flag grammars in one governance surface = the vocab-fork class C3 already flags. Reuse the existing shape.
- **m5.** W0.5 alternative B ("scope scan `mtime > closeout cuối`") is **not durable**: the repo is `D:\Dropbox\…`; a Dropbox re-sync rewrites mtime → all 27 folders look new → total storm. Also "closeout cuối" is undefined as a measurable anchor. Use `git log -1 --format=%cI -- <folder>` (git-native, sync-proof); the reference point would be `3fde8af` @ `2026-07-14T16:29:55+07:00`.
- **m6.** W0.5 alternative A ("Đóng — thêm `*-synthesis.md`") invites fabrication: for 12-day-old runs (S112/S113/S114) the transcript is gone, so any "synthesis" written now is invented — poisoning the audit trail to green a gate. `README.md:100` already has the honest form: "**Đánh-dấu aborted** … verdict = `⚠️ ABORTED — <lý-do>`, ghi rõ phần nào hoàn-thành (recover qua git/disk/prod truth, **KHÔNG tin agent return-message**)". Say that explicitly, or the Goodhart path is the default.
---
## Trục-mandated answers
**Mọi chỗ spec có thể huỷ uncommitted work:**
1. **C1** — rebase-fixup → detached HEAD → hook commits onto it → `git rebase --abort` discards worktree + orphans the commit. **Proven**, highest severity.
2. **C2** — `reset --soft HEAD~K` with K too large → branch behind `origin/main` → non-FF push → `--force` temptation → rewrites `3fde8af` on the shared remote (Gitea runner + CI #388/#389 cite it).
3. **C5** — the unscoped `rm` + `git checkout --` sweep over **188** files, on a **stale "dirty = 0"** premise (C4). At-risk-and-live: `agent-memory/{implementer-backend,reviewer}/MEMORY.md`, `agent-memory/.mfe-state.json`, `docs/{STATUS,HANDOFF,CLAUDE}.md` — all 6 confirmed in the 188. *(Correction to a plausible worry: `.claude/WAL.md` is `i/lf w/lf` — **not** in the 188, so the checkout sweep does not reach it. The `renormalize .` staging in C4 does.)*
4. **Hook × index.lock** — `git rebase` or a mass `checkout` while `wal-flush.ps1` runs `git add`/`git commit` (fail-open, `2>$null`) on a **Dropbox-synced** `.git` → hook silently no-ops → the H22 durability guarantee is off exactly during the riskiest step. §3.8/N.7(e) name `index.lock` but only for the "dọn 57" scenario, not for W0.
**Rollback:** the spec has **no rollback/abort path for W0** — §3.8 lists risks and preventions only. **FIX:** W0 step 0 = `git tag w0-safepoint HEAD` + record `git rev-parse HEAD` + `git status --porcelain > runs/<id>/w0-pre.txt`; documented undo = `git rebase --abort` (if rebase state) → `git reset --mixed w0-safepoint` (unstage renormalize) → `git checkout w0-safepoint -- .gitattributes`; and a stated reflog escape (`git reflog | head`). Without this, C1's failure mode has no written exit.
**W1 disjointness:** file-level yes (`governance-detectors.ps1` / `scripts/agent-frontmatter-eol-check.ps1` / `scripts/wal-recovery-test.ps1` — all exist-or-new, no overlap). Semantically **no** — see M2 (STATUS.md coupling). Plus: the 3 lanes run as agents → each writes `agent-memory/*/MEMORY.md` → the Stop hook auto-commits that path each turn → wal: accumulation during W1 feeds C2/C3. And W1② ("PASS trên **12** agent thật") vs W2 (creates agents 1314): the acceptance's hardcoded 12 **excludes from validation the two files most likely to carry the defect** — brand-new agent `.md` written on Windows. Use a glob, not a count.
**W2 vs W1 on `hmw.js`:** no collision — W1 does not touch `hmw.js`; W2 owns it in "1 EDIT" per §3.2. That part holds.
**Push "cùng lượt":** your reading is right — guard+push in one tool-call means the hook has not fired between them. The gap is that the **squash** is not in that lượt (C3). §3.3 W6 says "push-guard **CÙNG LƯỢT** với push" and §2.1 acceptance (ii) says count+push same transcript-turn; **neither binds §5.0**. That is the deadlock.
---
## REFUTATION LOG — claims I attacked and could NOT break
1. **"188 file `i/lf w/crlf`, 131 = `Migrations/`"** — attacked with `git ls-files --eol` (authority, per your CR-count warning): **188 ✓, 131 ✓**. Also `i/none` **25** ✓, `i/-text` **19** ✓, `i/crlf` **0** ✓. Only `i/lf` differs (1268 vs 1267) — one commit of drift, not an error.
2. **"`git add --renormalize .` = diff RỖNG cho 188"** — I expected index churn. Temp-tree test: the `i/lf w/crlf` file produced **zero** cached diff (clean filter → identical blob). **The core claim holds.** Only the *dirty-file side effect* and the *acceptance metric* are broken (C4).
3. **W0.1 premise "`* text=auto eol=lf` beats `core.autocrlf=true`@system"** — `core.autocrlf` confirmed `true` at `C:/Program Files/Git/etc/gitconfig`; `check-attr` on `reviewer.md` today = `text: unspecified, eol: unspecified` (matches spec). Temp-tree: after `* text=auto eol=lf` + `rm` + `checkout` → `attr/text=auto eol=lf`, `w/lf`, `od -c` shows `\n`. **Holds.**
4. **W0.1's binary-class safety** — tried to find a file the `* text=auto` rule would corrupt. The 19 `i/-text` = 8+5 `.docx` / 6 `.png` / 1 `.xlsx` — all already auto-detected binary and all covered by the proposed list. The 25 `i/none` = 23 `.gitkeep` (empty) + 2 `.svg` (no EOL) — unaffected. `.bat`/`.cmd`: **0 tracked**. All 20+ `.ps1`: already `w/lf` → rule is a no-op. **W0.1 is safe; the ⚠️ it raises is genuinely N/A.**
5. **"8/27 orphan, 7 tồn-đọng (S112 ×4, S113, S114, pe-negative-quote) + 1 bay"** — my naive scan said 13, so I went hunting for an error. `ls */harvest/` proved all 5 `h10`/`h910` carry `harvest/<stage>-synthesis.md`, and `README.md:83` grants dual-accept. **The spec's number is right.** The defect is the *omitted clause + wrong citation* (M4), not the count.
6. **§1.5 positive-control numbers** — `grep -c "public const string" MenuKeys.cs` = **54** ✓ (skill says "~60"). Baseline `TOTAL FLAGS: 42` ✓ by running the script. `grep memory-budget|ConvertFrom-Json` in the .ps1 = **0/0** ✓. **All verified.**
7. **"Detector can't see `.claude/skills/`" — my hypothesis, REFUTED.** `Get-GovernanceMd:119` scans `docs` + `.claude` recursively with `-Filter *.md`; `SKILL.md` matches; the 6-path exclusion list (`governance-detectors.ps1`, `harness-11-engine.md`, `vocab-alias-map.md`, `broadcasts/inbox`, `broadcasts/outbox`, `.claude/workflows/runs`) does **not** include skills. The positive control **can** fire. Spec correct.
8. **"`Menu keys` has no canonical row" — my hypothesis, REFUTED.** `| Menu keys | **54** |` exists in STATUS.md's CURRENT-STATE table. Only `Policies` is missing (M2).
9. **AS-6 CI claims**`.gitea/workflows/*.yml:20-27` `paths-ignore` = `docs/**`, `**/*.md`, `.claude/skills/**`, `.claude/agent-memory/**`, `.claude/workflows/runs/**`, `.gitignore`, `scripts/**.md`. So `.gitattributes` **triggers** ✓, `scripts/*.ps1` **triggers** ✓, `.json` **triggers** ✓, `agent-memory/**` + `runs/**` **do not** ✓. **Spec §3.8 correct** (it merely omits that `hmw.js` also triggers, which is harmless).
10. **All `hmw.js` line anchors** (:22 / :83-85 / :104 / :43-51 / :32-42) and **all `governance-detectors.ps1` anchors** (:43-53 / :55 / :118-127 / :154-157) — **exact**. `session-start.md:51` ("chain rỗng" → "WAL sạch") ✓, `:54` ("CHỈ LÀ CON-TRỎ") ✓, `:97` (orphan scan) ✓, `:98` (skippable) ✓, `session-end.md:112-113` (§5.0 counts LIÊN-TỤC from HEAD, ":113 GIỮ NGUYÊN, chấp-nhận noise") ✓, `:145` (§6.4 note) ✓, `fable-clone.md:15` ("ENSEMBLE tier-2") ✓ with **0** `tier:'opus'` (GAP #6 real) ✓, `fable-clone.md:19` roster-12 = VALID_ROLES-12 ✓. **§1.2 L1's leak-channel diagnosis is independently confirmed** by the S118 shape: `e88e75d → b56659b(wal) → c2135ba → b7f7b56(wal) → 1491d29(wal) → c808b9a → 3fde8af` — sandwiched wal: rode to origin because §5.0 only counts trailing. The spec's headline analysis is sound; it is the **execution mechanics** that fail.
11. **`WAL.md:16` `grep -c` idiom "dính exit-1-khi-0"** — confirmed real (only the line number is off by one, m1).