162 lines
13 KiB
Markdown
162 lines
13 KiB
Markdown
# 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`
|
|
|
|
```powershell
|
|
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:)=1`
|
|
- `legacy-picks-the-bad-one` (`:468`) — legacy(NONWAL=1) = `rebase-fixup` = đúng nhánh vừa đo là chết
|
|
- `sut-rejects-legacy-branch` (`:470`) — **SUT KHÔNG được chọn nhánh vừa đo là chết** ⇒ ai đổi luật về NONWAL → **FAIL**
|
|
- `rules-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ại `wal:` 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/main` **local 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.
|
|
- `-InjectLegacyRule` là **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.
|