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

27 KiB
Raw Blame History

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/mainYES; 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 == 0reset --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.jsonwal-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 nowM .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 rowGet-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.mdrun 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*-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. C2reset --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/lfnot in the 188, so the checkout sweep does not reach it. The renormalize . staging in C4 does.)
  4. Hook × index.lockgit 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 + checkoutattr/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 numbersgrep -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).