13 KiB
sub-task-2 — LANE W1-③ — scripts/wal-recovery-test.ps1 (FIX #3-bis → test CHẠY LẠI ĐƯỢC)
VERDICT: PASS — 4/4 ca chọn đúng nhánh · 30 assert · 0 fail · exit 0. Fault-inject 2 vector chứng RĂNG: control=0 → injected=1 → reverted=0. Non-ASCII = 0.
- File TẠO (duy nhất):
scripts/wal-recovery-test.ps1(535 dòng, NEW) - Sub-MD:
.claude/workflows/runs/2026-07-15-S119-adap-6-broadcast/sub-task-2.md - Containment:
git status --porcelain→ mục duy nhất của em =?? scripts/wal-recovery-test.ps1. 4 mục còn lại (M scripts/governance-detectors.ps1,?? scripts/spawn-model-audit.ps1,?? scripts/agent-frontmatter-eol-check.ps1,?? .../sub-task-1.md) = lane khác, em KHÔNG chạm. 0 git add/commit/push.
1. Luật được encode (SUT) — :109-116
function Select-WalFoldBranch {
param([int]$K, [string]$Bottom) # <<< KHÔNG có $Nonwal — cố ý
if ($K -eq 0) { return 'noop' }
if ($Bottom -match '^wal:') { return 'reset-soft' }
return 'rebase-fixup'
}
🔴 NONWAL không phải biến rẽ nhánh — được neo BẰNG CẤU TRÚC, không bằng grep: SUT không nhận $Nonwal làm param, nên nó không thể rẽ theo NONWAL. Assert runtime qua reflection (Get-Command Select-WalFoldBranch).Parameters.Keys (:317-321).
Vì sao KHÔNG grep: bài học W0.4 (TỰ-THAM-CHIẾU). Gate grep chuỗi chạy trên chính file nó kiểm → khối comment mô tả luật cũ (
:29-35) sẽ khiến grep bắt trúng câu trích → FAIL trên bản SỬA ĐÚNG. Reflection soi đối tượng function sống, miễn nhiễm với text.
Select-WalFoldBranchLegacy (:118-122) giữ lại CHỈ làm oracle đối chứng — rẽ theo NONWAL đúng như spec v2 :92.
2. Chống-tautology: oracle ĐO, không phát-biểu-lại luật
Bẫy hiển nhiên: assert SUT(ca2)=='reset-soft' khi SUT là hàm 3 dòng cùng file = phát-biểu-lại luật = vô nghĩa. Cách đóng:
Mỗi ca chạy CẢ HAI nhánh ứng-viên trên cây độc lập (Test-Candidate :263), rồi ĐO hậu-trạng n(wal: trong origin/main..HEAD):
| ca | cf reset-soft | cf rebase-fixup | oracle | expected lấy từ đâu |
|---|---|---|---|---|
| ca0 | CLEAN (n=0) | CLEAN (n=0) | INCONCLUSIVE | spec v3-PATCH — DEFINITIONAL, khai thật |
| ca1 | CLEAN (n=0) | DEADLOCK (n=1) | EMPIRICAL | ĐO — chỉ 1 nhánh sống |
| ca2 | CLEAN (n=0) | DEADLOCK (n=1) | EMPIRICAL | ĐO — chỉ 1 nhánh sống |
| ca3 | CLEAN (n=0) | CLEAN (n=0) | INCONCLUSIVE | spec v3-PATCH — DEFINITIONAL, khai thật |
🔸 KHAI THẬT: ca0/ca3 expected là DEFINITIONAL (lấy từ spec, KHÔNG đo được) vì cả 2 nhánh tương-đương trên thước n(wal:). Ở ca3 reset --soft cũng ra range sạch — spec chọn rebase, không phải vì nhánh kia chết. Script in thẳng nhãn DEFINITIONAL ra bảng, không giấu. Chỉ ca1/ca2 là EMPIRICAL.
3. REGRESSION-GUARD (:440-473) — giá trị thật của lane
Không phải phát-biểu-lại luật; cả 2 vế đều ĐO trong chính lượt chạy:
Replay of W0.3 4.2 - legacy branch (rebase+fixup) executed on a ca2 tree:
rebase-exit = 0 (0 = it SUCCEEDS, which is exactly the hazard)
rebase-state left = False
after: wal: flush 1
n(wal: in range) = 1
re-squash (2nd fold) -> n(wal:)=1 [rebase-exit=0] -> fixed point = PERMANENT STOP
Khớp từng chi tiết bằng chứng lead @W0.3 4.2 (W0-evidence-15-07-2026.md:69-75): exit 0, không để lại .git/rebase-merge, commit gộp THỪA KẾ message của commit ĐÁY (wal: flush 1 — lead ghi c2c39dc wal: flush 1).
🔴 Điểm mấu chốt: lỗi này THÀNH-CÔNG MỘT CÁCH SAI → test CẤM chỉ assert exit == 0 (nếu thế nó sẽ PASS trên chính con bug). Nên assert neo vào hậu-trạng đo được n(wal:), không vào exit code.
+ Thêm ngoài bằng-chứng lead — fixed-point probe (:453-460): fold LẦN 2 trên kết quả deadlock → n(wal:) vẫn = 1. Biến "STOP VĨNH VIỄN" từ tường-thuật → số đo: đây là điểm bất-động, re-squash không bao giờ thoát.
4 assert guard:
legacy-branch-deadlocks(:462) — ĐO: legacy exit 0 màn(wal:)=1legacy-picks-the-bad-one(:468) — legacy(NONWAL=1) =rebase-fixup= đúng nhánh vừa đo là chếtsut-rejects-legacy-branch(:470) — SUT KHÔNG được chọn nhánh vừa đo là chết ⇒ ai đổi luật về NONWAL → FAILrules-diverge-at-ca2(:472) — 2 luật phải LỆCH ở ca2, nếu không guard thành vô-nghĩa
4. Assert an-toàn (v2 :260-261)
| Assert | Vị trí | Phủ |
|---|---|---|
rebase-exit-never-nonzero |
:398 |
main tree + cây counterfactual (ca3 in [0,0]) |
no-rebase-state-left |
:388 |
.git/rebase-merge và .git/rebase-apply ABSENT sau mọi bước |
cf-no-rebase-state |
:390 |
ABSENT trên cả 2 cây counterfactual |
headK-eq-origin-pre-reset |
:402 |
HEAD~K == origin/main PASS TRƯỚC khi reset (:227-230 abort nếu lệch) |
ca2-never-rebases |
:415 |
v3-PATCH: ca2 KHÔNG được chạm git rebase |
git-tree-identical |
:409 |
ca0: ref-state (for-each-ref + HEAD) byte-identical trước/sau |
noop-touched-nothing |
:407 |
ca0: 0 reset, 0 rebase |
5. FAULT-INJECT — 2 vector (bắt buộc, stdout thật ở §7)
Vector 1 — -InjectLegacyRule (:69, built-in, chạy-lại-được): swap SUT sang legacy → cases 2/4 PASS | asserts 30 run, 7 failed → exit 1.
Vector 2 — sửa MÃ NGUỒN thật trên cây tạm + -RepoRoot <cây-tạm> (đúng dạng spec đòi): copy script sang $env:TEMP/wal-fi-*/scripts/, revert Select-WalFoldBranch về luật NONWAL (4 phép thay, mỗi phép xác nhận occurrences = 1 trước khi thay) → chạy → 8 assert FAIL, exit 1 → gỡ lỗi → 4/4 PASS, exit 0.
Vector 2 mạnh hơn: chứng rằng một lần revert THẬT trong tương lai bị bắt, không phải chỉ một cờ em tự dựng cho mình.
Anti-Goodhart: gate này KHÔNG xanh-sẵn — script là file MỚI, không có "đã xanh trước khi làm gì". Teeth chứng bằng injected=1 ở cả 2 vector, không chỉ happy-path.
6. 🔴 2 BUG THẬT script tự bắt (không phải happy-path)
(a) Pre/post-state bug — lượt chạy ĐẦU FAIL thật. Guard tính $m2 = Measure-Range (ca2-main) SAU khi cây đã fold → đọc nhầm hậu-trạng (K=1, bottom = [CLAUDE] fold re-commit) → hỏi luật một câu KHÁC câu đang test → 2 assert FAIL giả. Fix: $script:CaseMeasure chốt số đo TRƯỚC fold (:76-78, :345, :447-451).
🔸 Đáng chú ý: bảng in cases 4/4 PASS trong khi verdict = FAIL — vì $exitCode TÍNH TỪ counter (:523-524), không phải nhãn dán. Đúng bài học META-COUNT @W0.4: nhãn không bao giờ đè số đo.
(b) Flaky assert repo-untouched — tự bắt lúc audit containment. Bản đầu assert HEAD + porcelain bất-biến trước/sau. Nhưng ĐO: porcelain repo trôi 2 → 5 giữa các lượt chạy vì lane song song đang ghi, và Stop-hook wal-flush.ps1 commit được → HEAD trôi. ⇒ assert sẽ FAIL vì lý-do NGOẠI-SINH. Hạ xuống INFO (:497-509); containment giữ HARD ở workdir-outside-repo (cấu-trúc: mọi cây đều treo dưới $root, chứng $root ngoài $RepoRoot là đủ).
Chứng bằng ĐO, không bằng lời: inject concurrency — background job ghi intruder.txt vào cây -RepoRoot giữa lượt chạy → porcelain 0 -> 1 ; unchanged=False → VERDICT PASS, exit 0. Bản cũ sẽ FAIL ở đúng ca này.
7. STDOUT THẬT
Default (-File scripts/wal-recovery-test.ps1):
ca0 base 0 0 noop reset-soft noop DEFINITIONAL PASS
ca1 base|wal:|wal: 2 0 reset-soft reset-soft reset-soft EMPIRICAL PASS
ca2 base|wal:|[CLAUDE] 2 1 reset-soft rebase-fixup reset-soft EMPIRICAL PASS
ca3 base|[CLAUDE]|wal: 2 1 rebase-fixup rebase-fixup rebase-fixup DEFINITIONAL PASS
cases 4/4 PASS | asserts 30 run, 0 failed
VERDICT: PASS DEFAULT-EXIT = 0
Fault-inject vector 2 (cây tạm, -RepoRoot $fi):
occurrences of [param([int]$K, [string]$Bottom)] = 1 (must be 1)
occurrences of [if ($K -eq 0) { return 'noop' }] = 1 (must be 1)
occurrences of [if ($Bottom -match '^wal:') { return 'reset-soft' }] = 1 (must be 1)
occurrences of [return (Select-WalFoldBranch -K $K -Bottom $Bottom)] = 1 (must be 1)
--- (c) RUN INJECTED ---
[FAIL] env | sut-has-no-NONWAL-param | Select-WalFoldBranch params = [K, Bottom, Nonwal]
[FAIL] ca0 | branch-selection | SUT=reset-soft expected=noop (DEFINITIONAL oracle)
[FAIL] ca2 | branch-selection | SUT=rebase-fixup expected=reset-soft (EMPIRICAL oracle)
[FAIL] ca2 | ca2-never-rebases | git rebase NOT invoked on ca2
[FAIL] ca2 | post-state-clean | n(wal: in origin/main..HEAD) after SUT branch = 1
[FAIL] grd | sut-rejects-legacy-branch | SUT = rebase-fixup != the measured-deadlock branch
[FAIL] grd | rules-diverge-at-ca2 | SUT=rebase-fixup vs legacy=rebase-fixup
cases 2/4 PASS | asserts 30 run, 8 failed
VERDICT: FAIL INJECTED-EXIT = 1
--- (d) REMOVE FAULT ---
cases 4/4 PASS | asserts 30 run, 0 failed REVERTED-EXIT = 0
FAULT-INJECT SUMMARY: control=0 injected=1 reverted=0
[FAIL] ca2 | post-state-clean | n(wal:)=1= mạnh nhất: luật injected thật sự rebase cây ca2-main và để lạiwal:trong range — deadlock đo ngay trên cây SUT.
Concurrency boundary:
[info] RepoRoot HEAD f7ab8c0 -> f7ab8c0 ; porcelain 0 -> 1 ; unchanged=False
[info] delta above is NOT a failure: containment is proven by workdir-outside-repo.
cases 4/4 PASS | asserts 30 run, 0 failed
VERDICT: PASS CONCURRENT-EXIT = 0
8. Quy-ước .ps1 (gotcha #30/#37)
| Luật | Trạng thái | Đo |
|---|---|---|
| 1. Thân ASCII-only | ✅ | NON-ASCII byte count = 0 (quét 24071 byte); no BOM (first 3 bytes = 60,35,10) |
2. Token VN qua code-point (U) |
n/a — KHÔNG cần | Script chỉ match ^wal: + ^\[CLAUDE\] = thuần ASCII. Rule 2 chỉ áp khi "cần token tiếng Việt để MATCH nội dung file đích". KHÔNG thêm hàm U rỗng để làm-màu |
3. -Encoding UTF8 đọc file đích |
n/a | Không đọc file đích nào; cây git tự tổng-hợp từ đầu |
| 4. PS 5.1 only | ✅ | Quét &&=0 ||=0 ??=0 -AsHashtable=0; PSParser::Tokenize → 0 parse error; chạy thật trên 5.1.26100.8737 |
5. param($RepoRoot = ...) + path derive |
✅ | :65-69 đúng dạng bắt buộc; 0 đường-dẫn tuyệt-đối hardcode; chạy thật với -RepoRoot <cây-tạm> |
| 6. Đọc file mẫu trước | ✅ | Đọc trọn governance-detectors.ps1 (455 dòng); mượn Write-Section:55, param:32-34, Write-Flag→Assert-That |
| EOL | ✅ | CRLF=0; git check-attr → text: auto eol: lf (khớp .gitattributes W0) |
9. Khác detector — CỐ Ý
governance-detectors.ps1:455 = exit 0 luôn (DETECT-only, mandate). File này là TEST ⇒ exit 1 khi fail (:523-524, :535), exit 0 khi 4/4. Nhiệm-vụ lane ghi rõ "đây LÀ test, khác detector".
10. 🔸 Khai chặt — giới-hạn
- ca0/ca3 = DEFINITIONAL, không phải đo (§2). Chỉ ca1/ca2 empirical.
- Test phủ luật rẽ nhánh FIX #3-bis. KHÔNG phủ: interlock hook
wal-flush.ps1(W0.3 4.3 = fix #8b, ngoài task),wal:count-guard §5.2, hành-vi trên repo thật. - Cây tạm dùng
refs/remotes/origin/mainlocal giả — không remote thật, không network. Đúng shape lead đo, nhưng repo thật có thể có yếu-tố khác (worktree bẩn, hook, submodule) chưa phủ. - 1 build git:
git version 2.45.1.windows.1, win32. Chưa phủ git khác/OS khác. -InjectLegacyRulelà cửa sau có chủ-đích (chứng teeth chạy-lại-được). Nó không đổi luật ở đường mặc-định (Get-FoldBranch :124-128), nhưng vẫn là param công-khai — nếu ai chạy CI kèm cờ này thì gate sẽ đỏ. Khai để không ai tưởng là bug.