@chrono-meta/fh-gate 2.15.1 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/.claude/rules/fh_4axis_gate.md +38 -0
  2. package/.claude-plugin/marketplace.json +2 -2
  3. package/CLAUDE.md +1 -1
  4. package/README.md +6 -1
  5. package/knowledge/shared/harness-core/fh_three_layer_canon.md +47 -0
  6. package/knowledge/shared/harness-core/field_verdict_crossfamily_gate.md +10 -0
  7. package/knowledge/shared/harness-core/measurement-integrity-checklist.md +15 -1
  8. package/knowledge/shared/harness-core/ship_readiness_gate.md +19 -2
  9. package/knowledge/shared/learnings/subagent_invocations_log.yaml +49 -0
  10. package/package.json +9 -1
  11. package/plugins/fh-commons/.claude-plugin/plugin.json +1 -1
  12. package/plugins/fh-meta/.claude-plugin/plugin.json +1 -1
  13. package/plugins/fh-meta/CHANGELOG.md +25 -0
  14. package/scripts/backtick_guard.sh +194 -0
  15. package/scripts/context_continuity_score.sh +49 -7
  16. package/scripts/fh-gate.sh +3 -3
  17. package/scripts/files_manifest_shipping_check.sh +19 -0
  18. package/scripts/gate_pathspec_check.sh +1 -1
  19. package/scripts/package_coverage_check.sh +24 -2
  20. package/scripts/proposal_hook.sh +89 -0
  21. package/scripts/public_surface_scan_files.sh +11 -2
  22. package/scripts/revert_probe.sh +250 -0
  23. package/scripts/selfcheck.sh +65 -2
  24. package/scripts/sim_isolated_run.sh +97 -7
  25. package/scripts/test_backtick_guard_lanes.sh +115 -0
  26. package/scripts/test_degrade_scan_shell_probes.sh +7 -7
  27. package/scripts/test_files_manifest_shipping_lanes.sh +5 -5
  28. package/scripts/test_heavy_classifier_lanes.sh +1 -1
  29. package/scripts/test_lane_runner_lanes.sh +59 -33
  30. package/scripts/test_mapped_tracks_lanes.sh +1 -1
  31. package/scripts/test_marker_soul_check_lanes.sh +24 -0
  32. package/scripts/test_node_check_lanes.sh +34 -34
  33. package/scripts/test_package_coverage_lanes.sh +53 -27
  34. package/scripts/test_pipe_verdict_guard_lanes.sh +5 -5
  35. package/scripts/test_precommit_pointer_index_lanes.sh +33 -0
  36. package/scripts/test_preprep_drift_anchor.sh +13 -4
  37. package/scripts/test_preprep_drift_anchor_lanes.sh +23 -0
  38. package/scripts/test_proposal_hook_lanes.sh +36 -0
  39. package/scripts/test_revert_probe_lanes.sh +146 -0
  40. package/scripts/test_session_close_lanes.sh +3 -5
  41. package/scripts/test_sim_isolated_run_lanes.sh +17 -0
  42. package/scripts/utterance_landing_check.sh +2 -2
  43. package/templates/.git-hooks/pre-commit +27 -4
  44. package/templates/settings.PreToolUse.snippet.json +37 -1
  45. package/plugins/fh-commons/README.md +0 -38
@@ -36,7 +36,9 @@ trap 'rm -rf "$TMP"' EXIT
36
36
 
37
37
  pass=0; fail=0; skipped=0
38
38
  ok() { printf ' ✅ %s\n' "$1"; pass=$((pass+1)); }
39
- bad() { printf ' ❌ %s\n' "$1"; fail=$((fail+1)); }
39
+ # $2 (optional) = the actual stdout/stderr/rc this case caught — printed so a red lane in CI is
40
+ # diagnosable without re-running it by hand. Truncated/flattened like the node-check lanes' bad().
41
+ bad() { printf ' ❌ %s\n' "$1"; [ -n "${2:-}" ] && printf ' got: %s\n' "$(printf '%s' "$2" | tr '\n' '|' | cut -c1-220)"; fail=$((fail+1)); }
40
42
  # A skip is COUNTED and reported. An uncounted skip is how "could not run" becomes indistinguishable
41
43
  # from "ran and passed" in the summary line — measured on this very suite in review round 2, where a
42
44
  # broken git produced "7 passed, 0 failed" and exit 0 while the real-worktree claim went untested.
@@ -70,23 +72,30 @@ make_tree() { # make_tree <dir> <git_shape> <cover_target:yes|no> [omit_manifest
70
72
  }
71
73
 
72
74
  run_tree() { bash "$1/scripts/package_coverage_check.sh" 2>&1; }
73
- rc_tree() { bash "$1/scripts/package_coverage_check.sh" >/dev/null 2>&1; echo $?; }
75
+ # 🟥 no more rc_tree(): it used to re-run the subject a SECOND time just to get $? (`out=$(run_tree
76
+ # ...); rc=$(rc_tree ...)` — two executions of the same script, so a nondeterministic subject could
77
+ # report an (out, rc) pair that never co-occurred in either real run). Every call site below now
78
+ # does `out=$(run_tree ...); rc=$?` — one execution, $? read off THAT command substitution.
74
79
 
75
80
  # Assert a POSITIVE outcome, never merely the absence of the SKIP line. Cross-family review caught
76
81
  # the first draft here: it checked only that `SKIP package-coverage` was missing, so a checker that
77
82
  # exited 1 on every worktree — the opposite defect, equally broken — would have passed the lane that
78
83
  # exists to prove the worktree path works. "Did not say the wrong thing" is not "did the right
79
84
  # thing"; a lane phrased as a negative can only ever fail one way.
85
+ # Both functions below stash their single execution's output/rc in _LAST_OUT/_LAST_RC (not `local`,
86
+ # deliberately — the caller reads them straight off the one run instead of re-invoking the subject a
87
+ # second time just to build a diagnostic string, which would be the same two-executions-for-one-
88
+ # verdict shape the L3–L7 dual-execution fix below removes).
80
89
  ran_clean() { # ran_clean <dir> -> 0 iff the check actually ran AND reported a clean scan
81
- local d="$1" o rc
82
- o=$(bash "$d/scripts/package_coverage_check.sh" 2>&1); rc=$?
83
- [ "$rc" -eq 0 ] && printf '%s' "$o" | grep -q 'PASS package-coverage' \
84
- && ! printf '%s' "$o" | grep -q 'SKIP package-coverage'
90
+ local d="$1"
91
+ _LAST_OUT=$(bash "$d/scripts/package_coverage_check.sh" 2>&1); _LAST_RC=$?
92
+ [ "$_LAST_RC" -eq 0 ] && printf '%s' "$_LAST_OUT" | grep -q 'PASS package-coverage' \
93
+ && ! printf '%s' "$_LAST_OUT" | grep -q 'SKIP package-coverage'
85
94
  }
86
95
  caught_defect() { # caught_defect <dir> -> 0 iff the check FOUND the planted omission
87
- local d="$1" o rc
88
- o=$(bash "$d/scripts/package_coverage_check.sh" 2>&1); rc=$?
89
- [ "$rc" -eq 1 ] && printf '%s' "$o" | grep -q 'scripts/helper.sh'
96
+ local d="$1"
97
+ _LAST_OUT=$(bash "$d/scripts/package_coverage_check.sh" 2>&1); _LAST_RC=$?
98
+ [ "$_LAST_RC" -eq 1 ] && printf '%s' "$_LAST_OUT" | grep -q 'scripts/helper.sh'
90
99
  }
91
100
  # A CLEAN LANE ALONE PROVES NOTHING ABOUT THE WORKTREE PATH. Cross-family review round 2 demonstrated
92
101
  # this by EXECUTION: it replaced the subject with a mutant that printed `PASS package-coverage`
@@ -99,11 +108,11 @@ wt_pair() { # wt_pair <label> <dir> <git_shape>
99
108
  local label="$1" d="$2" shape="$3"
100
109
  make_tree "$d" "$shape" yes
101
110
  if ! ran_clean "$d"; then
102
- bad "$label — clean leg: did not run to a PASS ($(run_tree "$d" | head -1))"; return
111
+ bad "$label — clean leg: did not run to a PASS (rc=$_LAST_RC)" "$_LAST_OUT"; return
103
112
  fi
104
113
  make_tree "$d" "$shape" no # identical tree, files[] no longer covers the referenced path
105
114
  if ! caught_defect "$d"; then
106
- bad "$label — DEFECT leg: planted omission not caught, so the clean PASS proved nothing"; return
115
+ bad "$label — DEFECT leg: planted omission not caught, so the clean PASS proved nothing (rc=$_LAST_RC)" "$_LAST_OUT"; return
107
116
  fi
108
117
  ok "$label"
109
118
  }
@@ -171,15 +180,20 @@ else
171
180
  esac
172
181
  }
173
182
  mkdir -p "$TMP/emptytpl"
174
- if git "${GIT_ISO[@]}" -C "$TMP/realrepo" init -q --template="$TMP/emptytpl" . >/dev/null 2>&1 \
183
+ # The whole fixture-build chain runs as ONE group so its combined stdout+stderr lands in one
184
+ # variable instead of /dev/null — a failed build used to report only "could not be built", with
185
+ # no way to tell which of the four steps broke or why.
186
+ _l1b_ok=1
187
+ _l1b_out="$( { git "${GIT_ISO[@]}" -C "$TMP/realrepo" init -q --template="$TMP/emptytpl" . \
175
188
  && _gitdir_ok \
176
- && git "${GIT_ISO[@]}" -C "$TMP/realrepo" commit -q --allow-empty -m init >/dev/null 2>&1 \
177
- && git "${GIT_ISO[@]}" -C "$TMP/realrepo" worktree add -q --detach "$TMP/realwt" >/dev/null 2>&1 \
178
- && [ -f "$TMP/realwt/.git" ]; then
189
+ && git "${GIT_ISO[@]}" -C "$TMP/realrepo" commit -q --allow-empty -m init \
190
+ && git "${GIT_ISO[@]}" -C "$TMP/realrepo" worktree add -q --detach "$TMP/realwt" \
191
+ && [ -f "$TMP/realwt/.git" ]; } 2>&1 )" || _l1b_ok=0
192
+ if [ "$_l1b_ok" -eq 1 ]; then
179
193
  wt_pair "L1-b real \`git worktree add\` (.git is a genuine gitdir pointer): scans for real" \
180
194
  "$TMP/realwt" none # the REAL .git file is already in place; do not overwrite it
181
195
  else
182
- bad "L1-b git IS installed but the real-worktree fixture could not be built — the claim was testable and was not tested"
196
+ bad "L1-b git IS installed but the real-worktree fixture could not be built — the claim was testable and was not tested" "$_l1b_out"
183
197
  fi
184
198
  fi
185
199
 
@@ -190,11 +204,11 @@ wt_pair "L2 ordinary checkout (.git is a DIR): scans for real — both legs" "$T
190
204
  # The widening from -d to -e must not cost the legitimate skip. An installed npm package has no
191
205
  # .git of either kind; making it fail there would fire on every consumer running `npm test`.
192
206
  make_tree "$TMP/l3" none yes
193
- out=$(run_tree "$TMP/l3"); rc=$(rc_tree "$TMP/l3")
207
+ out=$(run_tree "$TMP/l3"); rc=$?
194
208
  if printf '%s' "$out" | grep -q 'SKIP package-coverage' && [ "$rc" -eq 0 ]; then
195
209
  ok "L3 package mode (no .git): still skips, exit 0"
196
210
  else
197
- bad "L3 package mode (no .git): expected SKIP+0, got rc=$rc / $(printf '%s' "$out" | head -1)"
211
+ bad "L3 package mode (no .git): expected SKIP+0, got rc=$rc" "$out"
198
212
  fi
199
213
 
200
214
  # ── L4 · a checkout with no manifest is UNMEASURED, not clean ────────────────────────
@@ -203,48 +217,60 @@ fi
203
217
  # file list cannot be read, which is "cannot measure", not "nothing to measure". An anchor that
204
218
  # pins the wrong direction is worse than no anchor — it makes the hole look deliberate.
205
219
  make_tree "$TMP/l4" dir yes omit
206
- out=$(run_tree "$TMP/l4"); rc=$(rc_tree "$TMP/l4")
220
+ out=$(run_tree "$TMP/l4"); rc=$?
207
221
  if [ "$rc" -eq 1 ] && printf '%s' "$out" | grep -q 'UNMEASURED, not clean'; then
208
222
  ok "L4 .git present but no package.json: FAILS as unmeasured, not a clean skip"
209
223
  else
210
- bad "L4 no package.json: expected exit 1 (unmeasured), got rc=$rc / $(printf '%s' "$out" | head -1)"
224
+ bad "L4 no package.json: expected exit 1 (unmeasured), got rc=$rc" "$out"
211
225
  fi
212
226
 
213
227
  # ── L5/L6 · KNOWN PAIR ───────────────────────────────────────────────────────────────
214
228
  # Same tree twice; the ONLY difference is whether files[] covers the referenced path.
215
229
  # L5 known-POSITIVE: referenced, exists, not shipped -> must FAIL.
216
230
  make_tree "$TMP/l5" dir no
217
- out=$(run_tree "$TMP/l5"); rc=$(rc_tree "$TMP/l5")
231
+ out=$(run_tree "$TMP/l5"); rc=$?
218
232
  if [ "$rc" -eq 1 ] && printf '%s' "$out" | grep -q 'scripts/helper.sh'; then
219
233
  ok "L5 known-positive (referenced ∧ exists ∧ ¬shipped): FAIL, names the path"
220
234
  else
221
- bad "L5 known-positive: expected exit 1 naming scripts/helper.sh, got rc=$rc"
235
+ bad "L5 known-positive: expected exit 1 naming scripts/helper.sh, got rc=$rc" "$out"
222
236
  fi
223
237
 
224
238
  # L6 known-NEGATIVE: identical, but the path is in files[] -> must PASS.
225
239
  make_tree "$TMP/l6" dir yes
226
- out=$(run_tree "$TMP/l6"); rc=$(rc_tree "$TMP/l6")
240
+ out=$(run_tree "$TMP/l6"); rc=$?
227
241
  if [ "$rc" -eq 0 ] && printf '%s' "$out" | grep -q 'PASS package-coverage'; then
228
242
  ok "L6 known-negative (same tree, path shipped): PASS"
229
243
  else
230
- bad "L6 known-negative: expected exit 0 PASS, got rc=$rc"
244
+ bad "L6 known-negative: expected exit 0 PASS, got rc=$rc" "$out"
231
245
  fi
232
246
 
233
247
  # ── L7 · the impossible-zero guard is not reachable by an empty files[] ──────────────
234
248
  # A manifest with no shipped docs must report the extractor as broken, not print a pass.
235
249
  mkdir -p "$TMP/l7/scripts"; cp "$SUBJECT" "$TMP/l7/scripts/"; mkdir -p "$TMP/l7/.git"
236
250
  printf '{"files":[]}\n' > "$TMP/l7/package.json"
237
- out=$(run_tree "$TMP/l7"); rc=$(rc_tree "$TMP/l7")
251
+ out=$(run_tree "$TMP/l7"); rc=$?
238
252
  if [ "$rc" -eq 1 ] && printf '%s' "$out" | grep -q 'the check broke, it did not pass'; then
239
253
  ok "L7 zero shipped docs: reported as broken extractor, not as a pass"
240
254
  else
241
- bad "L7 zero shipped docs: expected exit 1 'check broke', got rc=$rc"
255
+ bad "L7 zero shipped docs: expected exit 1 'check broke', got rc=$rc" "$out"
242
256
  fi
243
257
 
244
258
  echo
245
259
  if [ "$skipped" -gt 0 ]; then
246
260
  echo "package-coverage lanes: ${pass} passed, ${fail} failed, ${skipped} UNCALIBRATED (not verified here)"
247
261
  else
248
- echo "package-coverage lanes: ${pass} passed, ${fail} failed"
262
+
263
+ # ── lane 7: tarball oracle JSON without files[] → text-listing fallback (CI 2026-09-04, v3.0.0) ──
264
+ # Known-pair: stub npm returns JSON lacking files[] but delegates the text `pack --dry-run` to the
265
+ # real npm → PASS (7a). Stub returns the same JSON and an EMPTY text listing → ORACLE_UNAVAILABLE rc=2 (7b).
266
+ cd "$REPO_ROOT" || exit 10; _REAL_NPM=$(command -v npm); _ST=$(mktemp -d) # earlier lanes cd into fixtures — the subject reads .git/package.json from cwd
267
+ printf '#!/bin/bash\nif [ "$*" = "pack --dry-run --json" ]; then echo "[{\\"id\\":\\"x\\"}]"; else exec %s "$@"; fi\n' "$_REAL_NPM" > "$_ST/npm"; chmod +x "$_ST/npm"
268
+ o=$(PATH="$_ST:$PATH" bash "$SUBJECT" --vs-tarball 2>&1); rc=$?
269
+ if [ "$rc" = 0 ] && printf '%s' "$o" | grep -q "^PASS package-coverage"; then echo " ✅ lane 7a: JSON without files[] → text listing fallback PASSes"; pass=$((pass+1)); else echo " ❌ lane 7a: fallback did not PASS (rc=$rc)"; printf "%s\n" "$o" | grep -E "UNAVAILABLE|head:" | cut -c1-220; fail=$((fail+1)); fi
270
+ printf '#!/bin/bash\nif [ "$*" = "pack --dry-run --json" ]; then echo "[{\\"id\\":\\"x\\"}]"; elif [ "$*" = "pack --dry-run" ]; then exit 0; else exec %s "$@"; fi\n' "$_REAL_NPM" > "$_ST/npm"
271
+ o=$(PATH="$_ST:$PATH" bash "$SUBJECT" --vs-tarball 2>&1); rc=$?
272
+ if [ "$rc" = 2 ] && printf '%s' "$o" | grep -q "UNAVAILABLE"; then echo " ✅ lane 7b: JSON without files[] AND empty text listing → UNAVAILABLE rc=2 (fail-closed)"; pass=$((pass+1)); else echo " ❌ lane 7b: expected rc=2 UNAVAILABLE, got rc=$rc"; fail=$((fail+1)); fi
273
+ rm -rf "$_ST"
274
+ echo "package-coverage lanes: ${pass} passed, ${fail} failed"
249
275
  fi
250
276
  [ "$fail" -eq 0 ] || exit 1
@@ -45,9 +45,9 @@ expect() {
45
45
  echo "[pipe-verdict-guard] known pairs"
46
46
  echo "-- R1: PIPESTATUS under zsh (deterministic) --"
47
47
  # The exact shape emitted 6× in this project, including twice on 2026-07-31.
48
- expect "R1 the measured shape" HIT 'bash x.sh | tail -5; echo "exit=${PIPESTATUS[0]}"'
49
- expect "R1 any index" HIT 'a | b; rc=${PIPESTATUS[1]}'
50
- expect "R1 inside a larger command" HIT 'cd /r && npm t | tail; E=${PIPESTATUS[0]}; echo $E'
48
+ expect "R1 the measured shape" HIT 'bash x.sh | tail -5; echo "exit=${PIPESTATUS[0]}"' # portability-noqa: fixture string fed to pipe_verdict_guard.sh for static analysis, never executed by this shell
49
+ expect "R1 any index" HIT 'a | b; rc=${PIPESTATUS[1]}' # portability-noqa: same as above
50
+ expect "R1 inside a larger command" HIT 'cd /r && npm t | tail; E=${PIPESTATUS[0]}; echo $E' # portability-noqa: same as above
51
51
  # zsh's own spelling is correct here and must never be flagged.
52
52
  expect "R1 zsh spelling is CLEAN" CLEAN 'a | b; rc=$pipestatus[1]'
53
53
 
@@ -83,7 +83,7 @@ echo x | grep -q y
83
83
  rc=$?'
84
84
  # B: zsh accepts `$PIPESTATUS[0]` without braces; the brace-anchored regex missed it.
85
85
  expect "B PIPESTATUS without braces" HIT 'a | b; rc=$PIPESTATUS[0]'
86
- expect "B braced form still caught" HIT 'a | b; rc=${PIPESTATUS[0]}'
86
+ expect "B braced form still caught" HIT 'a | b; rc=${PIPESTATUS[0]}' # portability-noqa: fixture string fed to pipe_verdict_guard.sh for static analysis, never executed by this shell
87
87
 
88
88
  echo "-- D: statement-continuation flatten + wrapped filter (leg-C MED round, 2026-08-01) --"
89
89
  # The blanket newline→`;` rewrite broke the CONTINUATION shapes: a newline after `|`/`&&` or a
@@ -167,7 +167,7 @@ echo "-- C: delivery channel (closes N=8 — detection without delivery is decor
167
167
  # NAMED RESIDUAL (cross-family LOW, accepted): $(…) capture strips NUL bytes, so a mutant emitting
168
168
  # NUL+JSON would pass C1. The producer's hits text is static ASCII+⚠️ with no NUL source, so the
169
169
  # lane does not pay for a byte-exact harness; revisit only if the producer ever emits dynamic bytes.
170
- HIT_CMD='bash x.sh | tail -5; echo "exit=${PIPESTATUS[0]}"'
170
+ HIT_CMD='bash x.sh | tail -5; echo "exit=${PIPESTATUS[0]}"' # portability-noqa: fixture string fed to the guard for static analysis, never executed by this shell
171
171
  payload() { python3 -c 'import json,sys; print(json.dumps({"tool_name":"Bash","tool_input":{"command":sys.argv[1]}}))' "$1"; }
172
172
 
173
173
  # All C lanes measure ONE invocation: stdout, stderr, and exit code from the same run (a pair of
@@ -0,0 +1,33 @@
1
+ #!/usr/bin/env bash
2
+ # test_precommit_pointer_index_lanes.sh — known pair for templates/.git-hooks/pre-commit [Pointers]:
3
+ # the Detail-pointer gate must read the STAGED blob, not the working tree. Found 2026-09-03 (arm C
4
+ # wt2, A3 triage): `[ -f "$REPO_ROOT/$f" ] || continue` let a staged .md with a broken
5
+ # `**Detail**: See §X` pointer land silently when the file was rm'd from disk after staging
6
+ # (git commits the INDEX). Runs in a disposable shallow clone — never touches this checkout.
7
+ # P1 staged broken pointer, file rm'd from disk → commit BLOCKED (❌ pointer line printed)
8
+ # N1 staged VALID pointer, file rm'd from disk → [Pointers] block runs with no ❌ (control: index read works)
9
+ # N2 staged broken pointer, file still on disk → BLOCKED (the pre-fix path also caught this — control)
10
+ # Usage: bash scripts/test_precommit_pointer_index_lanes.sh [--hook <path>] (default = templates/.git-hooks/pre-commit)
11
+ set -u; ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"; HOOK="$ROOT/templates/.git-hooks/pre-commit"
12
+ [ "${1:-}" = "--hook" ] && HOOK="$2"
13
+ T=$(mktemp -d); trap 'rm -rf "$T"' EXIT; pass=0; fail=0
14
+ git clone -q --depth 1 "file://$ROOT" "$T/r" 2>/dev/null || { echo "❌ clone failed"; exit 10; }
15
+ cd "$T/r" && git config user.email t@t && git config user.name t && mkdir -p .git/hooks && cp "$HOOK" .git/hooks/pre-commit && chmod +x .git/hooks/pre-commit
16
+ mkdir -p docs/lanefix; printf '## §Alive\ntext\n' > docs/lanefix/target.md; git add docs/lanefix/target.md; git -c core.hooksPath=/dev/null commit -qm base 2>/dev/null
17
+ run_case(){ local label="$1" want="$2" ptr="$3" rm_after="$4"
18
+ printf '# probe\n\n> **Detail**: See `docs/lanefix/target.md §%s`\n' "$ptr" > docs/lanefix/probe.md
19
+ git add docs/lanefix/probe.md; [ "$rm_after" = 1 ] && rm -f docs/lanefix/probe.md
20
+ out=$(FH_SKIP_GATE_AXES=1 git commit -qm probe 2>&1); rc=$?
21
+ # Discriminate on the [Pointers] block itself, not on commit rc: the fixture clone carries no
22
+ # marker/manifest, so OTHER axes block every commit here — rc is not this lane's signal.
23
+ if printf '%s' "$out" | grep -q "Detail pointer §"; then got=BLOCK_PTR
24
+ elif printf '%s' "$out" | grep -q "unreadable from the index"; then got=BLOCK_INDEX
25
+ elif printf '%s' "$out" | grep -q "\[Pointers\]"; then got=PTR_OK
26
+ else got=PTR_NOT_RUN; fi
27
+ git reset -q --hard HEAD 2>/dev/null; # noqa: destructive-op — disposable clone under mktemp git rm -q --cached docs/lanefix/probe.md 2>/dev/null; rm -f docs/lanefix/probe.md
28
+ if [ "$got" = "$want" ]; then printf ' ✅ %-48s %s\n' "$label" "$got"; pass=$((pass+1)); else printf ' ❌ %-48s %s (expected %s)\n' "$label" "$got" "$want"; fail=$((fail+1)); printf '%s\n' "$out" | grep -E "Pointers|❌" | head -4 | sed 's/^/ /'; fi; }
29
+ echo "[precommit-pointer-index] hook=$HOOK"
30
+ run_case "P1 broken pointer, staged then rm'd from disk" BLOCK_PTR Ghost 1
31
+ run_case "N1 valid pointer, staged then rm'd (index read)" PTR_OK Alive 1
32
+ run_case "N2 broken pointer, still on disk (old path too)" BLOCK_PTR Ghost 0
33
+ echo "[precommit-pointer-index] $pass passed, $fail failed"; [ "$fail" -eq 0 ]
@@ -16,7 +16,16 @@ set -uo pipefail
16
16
  HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
17
17
  SRC="$HERE/plugins/fh-commons/skills/preprep"
18
18
  # standalone 배포 위치는 환경변수로 받는다. 기본값을 박으면 다른 머신에서 거짓 SKIP 이 된다.
19
- DIST="${PREPREP_STANDALONE_DIR:-}"
19
+ # 🟥 2026-09-03 — 그런데 «아무도 그 변수를 안 걸어서» D2 가 여태 SKIP 이었고, 그 사이 컴패니언
20
+ # 저장소의 fork(724줄)가 정본(789줄)과 갈라져 L9~L11 을 안 부르고 있었다 — 정본 주석이 이미
21
+ # 한 번 적어 둔 사고의 2회째(fh_signal_2026-09-03_preprep-standalone-anchor-skip.md). 슬롯은
22
+ # 있고 소비처가 0 인 형태. 처방: 명시 변수가 없으면 운영자 로컬 바인딩이 이미 export 하는
23
+ # FH_COMPANION_STORE 아래 preprep/ 을 «자동 후보»로 쓴다 — 기본값을 박는 게 아니라 이미
24
+ # 선언된 경로를 읽는 것이라 다른 머신에서 거짓 SKIP 을 만들지 않는다(없으면 여전히 SKIP).
25
+ DIST="${PREPREP_STANDALONE_DIR:-}"; DIST_SRC="PREPREP_STANDALONE_DIR"
26
+ if [ -z "$DIST" ] && [ -n "${FH_COMPANION_STORE:-}" ] && [ -d "${FH_COMPANION_STORE}/preprep" ]; then
27
+ DIST="${FH_COMPANION_STORE}/preprep"; DIST_SRC="FH_COMPANION_STORE/preprep (자동 후보)"
28
+ fi
20
29
  PASS=0; FAIL=0; SKIP=0
21
30
  ok(){ echo " ✅ $1"; PASS=$((PASS+1)); }
22
31
  ng(){ echo " ❌ $1"; FAIL=$((FAIL+1)); }
@@ -41,16 +50,16 @@ fi
41
50
 
42
51
  # D2 — standalone 대조
43
52
  if [ -z "$DIST" ]; then
44
- sk "D2 standalone 대조 — PREPREP_STANDALONE_DIR 미설정이라 배포본을 못 찾았다"
53
+ sk "D2 standalone 대조 — PREPREP_STANDALONE_DIR 미설정이고 FH_COMPANION_STORE/preprep 도 없어 배포본을 못 찾았다. UNCHECKED — 배포본이 있는 머신이면 둘 중 하나를 export 해라"
45
54
  elif [ ! -d "$DIST" ]; then
46
- ng "D2 PREPREP_STANDALONE_DIR 이 가리키는 곳이 없다: $DIST (설정됐는데 부재 = 드리프트 아니라 배선 결함)"
55
+ ng "D2 $DIST_SRC 이 가리키는 곳이 없다: $DIST (설정됐는데 부재 = 드리프트 아니라 배선 결함)"
47
56
  else
48
57
  drift=""
49
58
  for f in preprep.py interslide_deps.py lane_progression.py lane_adjacent_dup.py; do
50
59
  if [ ! -f "$DIST/$f" ]; then drift="$drift $f(부재)"
51
60
  elif ! cmp -s "$SRC/$f" "$DIST/$f"; then drift="$drift $f(갈림)"; fi
52
61
  done
53
- [ -z "$drift" ] && ok "D2 standalone 코드 5파일이 단일 소스와 바이트 동일" \
62
+ [ -z "$drift" ] && ok "D2 standalone 코드 5파일이 단일 소스와 바이트 동일 ($DIST_SRC)" \
54
63
  || ng "D2 드리프트:$drift ⇒ 사본이 둘이 됐다. 단일 소스에서 다시 뽑아라"
55
64
  fi
56
65
 
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env bash
2
+ # test_preprep_drift_anchor_lanes.sh — known pairs for the D2 leg of scripts/test_preprep_drift_anchor.sh
3
+ # WHY (2026-09-03): D2 compared the standalone copy only when PREPREP_STANDALONE_DIR was set. Nobody
4
+ # set it, so the leg SKIPPED for weeks while the companion-store fork drifted (724 vs 789 lines, L9-L11
5
+ # never called) — the second occurrence of the exact accident the canon's own comment records.
6
+ # Fix under test: with the env var unset, the anchor falls back to $FH_COMPANION_STORE/preprep when it
7
+ # exists. These lanes pin: fallback used · fallback discriminates (identical PASS / drifted FAIL) ·
8
+ # explicit var still wins · nothing set → SKIP (never a silent PASS).
9
+ set -u
10
+ HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"; A="$HERE/scripts/test_preprep_drift_anchor.sh"
11
+ SRC="$HERE/plugins/fh-commons/skills/preprep"; T=$(mktemp -d); pass=0; fail=0
12
+ chk(){ if [ "$1" = 0 ]; then echo " ✅ $2"; pass=$((pass+1)); else echo " ❌ $2"; fail=$((fail+1)); fi; }
13
+ mk(){ mkdir -p "$1"; for f in preprep.py interslide_deps.py lane_progression.py lane_adjacent_dup.py lane_promise.py; do cp "$SRC/$f" "$1/$f"; done; }
14
+ echo "[preprep-drift-anchor] D2 known pairs"
15
+ # L1 nothing set → D2 SKIP (skip != pass), rc 0 (D2 is not a FAIL)
16
+ out=$(env -u PREPREP_STANDALONE_DIR FH_COMPANION_STORE="$T/nostore" bash "$A" 2>&1); printf '%s' "$out" | grep -q "D2 .*SKIPPED"; chk $? "L1 var unset + no companion preprep → D2 SKIPPED (not PASS)"
17
+ # L2 companion copy identical → fallback used, D2 PASS
18
+ mk "$T/be/preprep"; out=$(env -u PREPREP_STANDALONE_DIR FH_COMPANION_STORE="$T/be" bash "$A" 2>&1); printf '%s' "$out" | grep -q "✅ D2 .*자동 후보"; chk $? "L2 fallback FH_COMPANION_STORE/preprep identical → D2 PASS via 자동 후보"
19
+ # L3 companion copy drifted → D2 FAIL, rc 1 (the accident class)
20
+ printf '\n# drift\n' >> "$T/be/preprep/preprep.py"; env -u PREPREP_STANDALONE_DIR FH_COMPANION_STORE="$T/be" bash "$A" >"$T/l3.out" 2>&1; rc=$?; [ "$rc" -ne 0 ] && grep -q "D2 드리프트.*preprep.py(갈림)" "$T/l3.out"; chk $? "L3 fallback copy drifted → D2 FAIL rc=$rc (known-positive)"
21
+ # L4 explicit var wins over companion
22
+ mk "$T/explicit"; out=$(PREPREP_STANDALONE_DIR="$T/explicit" FH_COMPANION_STORE="$T/be" bash "$A" 2>&1); printf '%s' "$out" | grep -q "✅ D2 .*(PREPREP_STANDALONE_DIR)"; chk $? "L4 explicit PREPREP_STANDALONE_DIR wins over drifted companion copy"
23
+ rm -rf "$T"; echo "[preprep-drift-anchor] $pass passed, $fail failed"; [ "$fail" -eq 0 ]
@@ -0,0 +1,36 @@
1
+ #!/usr/bin/env bash
2
+ # test_proposal_hook_lanes.sh — known pairs for scripts/proposal_hook.sh (written before shipping; r4 KP + Bash path)
3
+ set -u; HDIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"; T=$(mktemp -d); pass=0; fail=0
4
+ export CLAUDE_PROJECT_DIR="$T"
5
+ exp(){ local label="$1" want="$2" payload="$3" got; got=$(printf '%s' "$payload" | bash "$HDIR/proposal_hook.sh" 2>/dev/null | wc -c | tr -d ' '); [ "$got" -gt 0 ] && got=HIT || got=CLEAN
6
+ if [ "$got" = "$want" ]; then printf ' ✅ %-52s %s\n' "$label" "$got"; pass=$((pass+1)); else printf ' ❌ %-52s %s (expected %s)\n' "$label" "$got" "$want"; fail=$((fail+1)); fi; }
7
+ echo "[proposal-hook] known pairs"
8
+ exp "T1 Edit comm+LC_ALL (verdict compare)" HIT '{"tool_name":"Edit","tool_input":{"file_path":"/x/scripts/sim_isolated_run.sh","old_string":" comm -13 <(sort a) <(sort b) \\","new_string":" comm -13 <(LC_ALL=C sort a) <(LC_ALL=C sort b) \\"}}'
9
+ exp "T2 Edit adds fail-open guard" HIT '{"tool_name":"Edit","tool_input":{"file_path":"/x/scripts/capability_effect_probe.sh","old_string":" h=$(ls -A \"$HOME\" | shasum)","new_string":" h=$(ls -A \"$HOME\" | shasum) || { echo LS_FAILED; exit 10; }"}}'
10
+ exp "T3 Edit continue guard" HIT '{"tool_name":"Edit","tool_input":{"file_path":"/x/templates/regression_guard.sh","old_string":" [ -e \"$blk\" ] && [ -s \"$blk\" ] || continue","new_string":" [ -e \"$blk\" ] || { echo missing >&2; continue; }"}}'
11
+ exp "HARD Edit usage string only (r4 0/5)" CLEAN '{"tool_name":"Edit","tool_input":{"file_path":"/x/scripts/daily_report.sh","old_string":" *) echo \"usage: d.sh [run]\" >&2; exit 2 ;;","new_string":" *) echo \"usage: d.sh [run|--self-test] (DR_DATE)\" >&2; exit 2 ;;"}}'
12
+ exp "docs file with tokens" CLEAN '{"tool_name":"Edit","tool_input":{"file_path":"/x/docs/a.md","old_string":"a","new_string":"exit 1 || continue"}}'
13
+ exp "Write new scripts/*.sh with verdict" HIT '{"tool_name":"Write","tool_input":{"file_path":"/x/scripts/new_check.sh","content":"#!/bin/bash\n[ -f x ] || exit 1"}}'
14
+ exp "Bash sed -i on scripts/*.sh with token" HIT '{"tool_name":"Bash","tool_input":{"command":"sed -i \"\" \"s/comm -13/LC_ALL=C comm -13/\" scripts/sim_isolated_run.sh && [ -s out ] || exit 1"}}'
15
+ exp "Bash sed -i token ONLY inside quotes (a1)" HIT '{"tool_name":"Bash","tool_input":{"command":"sed -i \"\" \"s/exit 1/exit 2/\" scripts/target.sh"}}'
16
+ exp "Bash sed -i single-quoted token (a1b)" HIT '{"tool_name":"Bash","tool_input":{"command":"sed -i '"'"''"'"' '"'"'s/|| continue/|| { echo x; continue; }/'"'"' scripts/target.sh"}}'
17
+ exp "Bash sed -i no token anywhere (a3)" CLEAN '{"tool_name":"Bash","tool_input":{"command":"sed -i \"\" s/foo/bar/ scripts/target.sh"}}'
18
+ exp "Bash redirect into scripts/*.sh w/ token" HIT '{"tool_name":"Bash","tool_input":{"command":"printf \"%s\\n\" x >> scripts/x.sh; grep -q y scripts/x.sh || exit 3"}}'
19
+ exp "Bash redirect into docs (no)" CLEAN '{"tool_name":"Bash","tool_input":{"command":"echo \"exit 1\" >> docs/a.md || exit 1"}}'
20
+ exp "Bash ls only (no target)" CLEAN '{"tool_name":"Bash","tool_input":{"command":"ls scripts/ && [ -d scripts ] || exit 1"}}'
21
+ exp "G1 Edit templates/.git-hooks/pre-commit (no .sh)" HIT '{"tool_name":"Edit","tool_input":{"file_path":"/x/templates/.git-hooks/pre-commit","old_string":" [ -f x ] || continue","new_string":" [ -f x ] || { echo missing; PTR_FAIL=1; continue; }"}}'
22
+ exp "G1-ctrl Edit .git-hooks docs-ish no token" CLEAN '{"tool_name":"Edit","tool_input":{"file_path":"/x/templates/.git-hooks/pre-commit","old_string":"# note a","new_string":"# note b"}}'
23
+ exp "noqa exempts" CLEAN '{"tool_name":"Edit","tool_input":{"file_path":"/x/scripts/a.sh","old_string":"a","new_string":"exit 1 # noqa: proposal-hook"}}'
24
+ exp "unparseable payload silent" CLEAN 'not json'
25
+ msg(){ printf '%s' "$2" | bash "$HDIR/proposal_hook.sh" 2>/dev/null; }
26
+ fact(){ local label="$1" want="$2" payload="$3" got; got=$(msg x "$payload"); if printf '%s' "$got" | grep -q -- "$want"; then printf ' ✅ %-52s carries «%s»\n' "$label" "$want"; pass=$((pass+1)); else printf ' ❌ %-52s missing «%s»\n' "$label" "$want"; fail=$((fail+1)); fi; }
27
+ mkdir -p "$T/scripts"; : > "$T/scripts/test_has_lane_lanes.sh"; printf 'scan of scripts/covered.sh\nfindings: 0\n' > "$T/scripts/.degrade_scan_last_2026-09-03.txt"
28
+ E='{"tool_name":"Edit","tool_input":{"file_path":"/x/scripts/%s","old_string":"a","new_string":"[ -f x ] || exit 1"}}'
29
+ fact "F1 lane exists → fact line, no known-pair item" "이미 있다" "$(printf "$E" has_lane.sh)"
30
+ fact "F1b lane exists → scan item still proposed" "degrade_direction_scan.sh 로" "$(printf "$E" has_lane.sh)"
31
+ fact "F2 self-lane (test_*_lanes.sh) → fact line" "이 파일 자체가 레인" "$(printf "$E" test_has_lane_lanes.sh)"
32
+ fact "F3 scan covers file → fact line w/ findings" "findings: 0" "$(printf "$E" covered.sh)"
33
+ fact "F4 neither → both items proposed" "known-pair(고친 케이스 + 반대 케이스) 컨트롤 degrade_direction_scan.sh" "$(printf "$E" bare.sh)"
34
+ got=$(msg x "$(printf "$E" bare.sh)"); if printf '%s' "$got" | grep -q "사실:"; then echo " ❌ F4-ctrl bare file must carry NO fact line"; fail=$((fail+1)); else echo " ✅ F4-ctrl bare file carries no fact line (known-negative)"; pass=$((pass+1)); fi
35
+ [ -f "$T/.claude/.proposal_hook_events.tsv" ] && [ "$(grep -c FIRE "$T/.claude/.proposal_hook_events.tsv")" -ge 5 ]; r=$?; [ $r = 0 ] && { echo " ✅ evidence file carries a FIRE row per hit"; pass=$((pass+1)); } || { echo " ❌ evidence file missing/short"; fail=$((fail+1)); }
36
+ rm -rf "$T"; echo "[proposal-hook] $pass passed, $fail failed"; [ "$fail" -eq 0 ]
@@ -0,0 +1,146 @@
1
+ #!/usr/bin/env bash
2
+ # test_revert_probe_lanes.sh — known-pair calibration for scripts/revert_probe.sh
3
+ #
4
+ # WHY THESE LANES. revert_probe.sh exists to replace 15+ hand-written revert-and-observe scripts
5
+ # with one generic tool. The two failure modes that would make it worse than the hand-rolled
6
+ # scripts it replaces are exactly the two known-pair lanes below:
7
+ # L1 DECORATIVE anchor — a suite whose lanes never actually depend on the target file's content
8
+ # must be reported as decorative (exit 1), not silently scored as "passed".
9
+ # L2 REAL anchor — the mirror-image known-positive: a suite that DOES catch the revert
10
+ # must report the exact flipped lane and exit 0. Without this half, L1 proves nothing
11
+ # ([[feedback_control_presence_is_not_discrimination]]).
12
+ # L3 RESTORE GUARANTEED — the target file must come back byte-identical after the probe, in
13
+ # BOTH the normal-completion path and the harness-error path (suite crashes / produces no
14
+ # parseable output). A revert tool that leaves the baseline content sitting in the working
15
+ # tree on the failure path is strictly worse than not having the tool.
16
+ # L4+ usage guards, harness-error detection, and the git-show (not git-checkout) restore path.
17
+ #
18
+ # Fixtures are throwaway git repos + throwaway suite scripts, never this repo's own assets — a
19
+ # lane suite that references a repo-specific path fails on a fresh clone (portability_lint P9).
20
+ #
21
+ # Usage: bash scripts/test_revert_probe_lanes.sh
22
+
23
+ set -uo pipefail
24
+ ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
25
+ SUT="${FH_REVERT_PROBE_BIN:-$ROOT/scripts/revert_probe.sh}"
26
+ pass=0; fail=0
27
+ ok() { printf ' ✅ %s\n' "$1"; pass=$((pass+1)); }
28
+ no() { printf ' ❌ %s\n' "$1"; fail=$((fail+1)); }
29
+
30
+ if [ ! -f "$SUT" ]; then
31
+ echo "FAIL test_revert_probe_lanes.sh: subject absent ($SUT) — skipped is not passed"
32
+ exit 1
33
+ fi
34
+
35
+ WORKROOT="$(mktemp -d "${TMPDIR:-/tmp}/revertlane-XXXXXX")"
36
+ trap 'rm -rf "$WORKROOT"' EXIT
37
+
38
+ # ── throwaway repo: commit 1 = baseline (missing the fix) · commit 2/HEAD = current (has the fix)
39
+ SRC="$WORKROOT/src"; mkdir -p "$SRC"
40
+ (
41
+ cd "$SRC" && git init -q . && git config user.email l@l && git config user.name l
42
+ printf 'value = old_and_broken\n' > guard.txt
43
+ git add guard.txt && git commit -qm "baseline (no fix)"
44
+ printf 'value = MARKER_OK\n' > guard.txt
45
+ git add guard.txt && git commit -qm "current (has the fix)"
46
+ ) >/dev/null 2>&1
47
+ TARGET="$SRC/guard.txt"
48
+ TARGET_SHA_BEFORE() { shasum -a 256 "$TARGET" 2>/dev/null | awk '{print $1}'; }
49
+
50
+ # ── fixture suites ──────────────────────────────────────────────────────────────────────────
51
+ # REAL anchor: the lane's pass/fail genuinely depends on the target file's content.
52
+ REAL_SUITE="$WORKROOT/real_suite.sh"
53
+ cat > "$REAL_SUITE" <<'EOF'
54
+ #!/usr/bin/env bash
55
+ if grep -q MARKER_OK "$FIXTURE_TARGET" 2>/dev/null; then
56
+ printf ' %s %s\n' "✅" "marker present in guard.txt"
57
+ else
58
+ printf ' %s %s\n' "❌" "marker present in guard.txt"
59
+ fi
60
+ exit 0
61
+ EOF
62
+ chmod +x "$REAL_SUITE"
63
+
64
+ # DECORATIVE anchor: always ✅, never actually reads the target file.
65
+ DECO_SUITE="$WORKROOT/deco_suite.sh"
66
+ cat > "$DECO_SUITE" <<'EOF'
67
+ #!/usr/bin/env bash
68
+ printf ' %s %s\n' "✅" "unrelated check that never reads guard.txt"
69
+ exit 0
70
+ EOF
71
+ chmod +x "$DECO_SUITE"
72
+
73
+ # HARNESS-ERROR suite: crashes before printing any ✅/❌ line at all.
74
+ CRASH_SUITE="$WORKROOT/crash_suite.sh"
75
+ cat > "$CRASH_SUITE" <<'EOF'
76
+ #!/usr/bin/env bash
77
+ echo "no verdict lines here, just noise" >&2
78
+ exit 1
79
+ EOF
80
+ chmod +x "$CRASH_SUITE"
81
+
82
+ echo "── revert_probe known-pair lanes ──────────────────────────────────"
83
+
84
+ # L1 — DECORATIVE anchor → exit 1, 0 flipped lanes
85
+ BEFORE1="$(TARGET_SHA_BEFORE)"
86
+ OUT1=$(FIXTURE_TARGET="$TARGET" bash "$SUT" "$TARGET" "$DECO_SUITE" --baseline HEAD~1 2>&1); RC1=$?
87
+ [ "$RC1" -eq 1 ] && ok "L1 decorative anchor → exit 1" || no "L1 decorative anchor: rc=$RC1 (want 1)"
88
+ printf '%s' "$OUT1" | grep -q "되돌린 레인 (✅→❌, 앵커가 실제로 잡은 것): 0개" \
89
+ && ok "L1b report states 0 flipped lanes" || no "L1b report did not state 0 flipped lanes"
90
+ AFTER1="$(TARGET_SHA_BEFORE)"
91
+ [ "$BEFORE1" = "$AFTER1" ] && ok "L1c target file restored after decorative run" \
92
+ || no "L1c target file NOT restored (before=$BEFORE1 after=$AFTER1)"
93
+
94
+ # L2 — REAL anchor → exit 0, exactly 1 flipped lane, names it
95
+ BEFORE2="$(TARGET_SHA_BEFORE)"
96
+ OUT2=$(FIXTURE_TARGET="$TARGET" bash "$SUT" "$TARGET" "$REAL_SUITE" --baseline HEAD~1 2>&1); RC2=$?
97
+ [ "$RC2" -eq 0 ] && ok "L2 real anchor → exit 0" || no "L2 real anchor: rc=$RC2 (want 0)"
98
+ printf '%s' "$OUT2" | grep -q "되돌린 레인 (✅→❌, 앵커가 실제로 잡은 것): 1개" \
99
+ && ok "L2b report states exactly 1 flipped lane" || no "L2b report did not state 1 flipped lane"
100
+ printf '%s' "$OUT2" | grep -q "marker present in guard.txt" \
101
+ && ok "L2c report names the exact flipped label" || no "L2c flipped label not named in report"
102
+ AFTER2="$(TARGET_SHA_BEFORE)"
103
+ [ "$BEFORE2" = "$AFTER2" ] && ok "L2d target file restored after real-anchor run" \
104
+ || no "L2d target file NOT restored (before=$BEFORE2 after=$AFTER2)"
105
+
106
+ # L3 — frontier warning header always present (both suites above already exercised it; assert once)
107
+ printf '%s' "$OUT2" | grep -q "2607.22880" && ok "L3 frontier-warning citation present in output" \
108
+ || no "L3 frontier-warning citation missing"
109
+ printf '%s' "$OUT2" | grep -q "되돌린 파일(뮤턴트) 수: 1" \
110
+ && ok "L3b mutant-count line present (n=1 caveat)" || no "L3b mutant-count line missing"
111
+
112
+ # L4 — HARNESS ERROR: suite produces zero ✅/❌ lines → exit 10, and restore STILL happens.
113
+ # This is the lane that matters most: a crash on the baseline run is exactly the moment a naive
114
+ # implementation would leave the baseline content sitting in the working tree.
115
+ BEFORE4="$(TARGET_SHA_BEFORE)"
116
+ OUT4=$(FIXTURE_TARGET="$TARGET" bash "$SUT" "$TARGET" "$CRASH_SUITE" --baseline HEAD~1 2>&1); RC4=$?
117
+ [ "$RC4" -eq 10 ] && ok "L4 zero-label suite → exit 10 (harness error, not a silent pass)" \
118
+ || no "L4 zero-label suite: rc=$RC4 (want 10)"
119
+ AFTER4="$(TARGET_SHA_BEFORE)"
120
+ [ "$BEFORE4" = "$AFTER4" ] && ok "L4b target file restored even on the harness-error path" \
121
+ || no "L4b RESTORE FAILED ON HARNESS-ERROR PATH (before=$BEFORE4 after=$AFTER4) — this is the defect class this suite exists to catch"
122
+
123
+ # L5 — usage guards
124
+ OUT5=$(bash "$SUT" 2>&1); RC5=$?
125
+ [ "$RC5" -eq 2 ] && ok "L5 no args → exit 2" || no "L5 no args: rc=$RC5"
126
+ OUT5b=$(bash "$SUT" "$TARGET" 2>&1); RC5b=$?
127
+ [ "$RC5b" -eq 2 ] && ok "L5b missing suite arg → exit 2" || no "L5b missing suite arg: rc=$RC5b"
128
+ OUT5c=$(bash "$SUT" "$WORKROOT/does_not_exist.txt" "$REAL_SUITE" 2>&1); RC5c=$?
129
+ [ "$RC5c" -eq 2 ] && ok "L5c missing target file → exit 2" || no "L5c missing target file: rc=$RC5c"
130
+ OUT5d=$(bash "$SUT" "$TARGET" "$WORKROOT/does_not_exist.sh" 2>&1); RC5d=$?
131
+ [ "$RC5d" -eq 2 ] && ok "L5d missing suite file → exit 2" || no "L5d missing suite file: rc=$RC5d"
132
+ OUT5e=$(bash "$SUT" "$TARGET" "$REAL_SUITE" --baseline totally-bogus-ref 2>&1); RC5e=$?
133
+ [ "$RC5e" -eq 2 ] && ok "L5e bad --baseline ref → exit 2" || no "L5e bad baseline ref: rc=$RC5e"
134
+
135
+ # L6 — the restore path uses `git show`, never `git checkout <ref> -- <path>` (the latter STAGES
136
+ # the revert into the index — [[feedback_git_checkout_path_stages_the_revert]]). Assert the repo's
137
+ # index carries no staged change after a probe run, both directions.
138
+ ( cd "$SRC" && git diff --cached --quiet ) \
139
+ && ok "L6 no staged changes left in the index after any probe run" \
140
+ || no "L6 the index has staged changes — a checkout-based restore would do this"
141
+ ( cd "$SRC" && git status --porcelain ) | grep -q . \
142
+ && no "L6b working tree not clean after probes (git status is non-empty)" \
143
+ || ok "L6b working tree clean after all probes (file content == committed HEAD)"
144
+
145
+ echo "revert_probe lanes: $pass passed, $fail failed"
146
+ [ "$fail" -eq 0 ] || exit 1
@@ -138,9 +138,7 @@ PREMISE_FAILED=0
138
138
  # Human-readable on both platforms: GNU prints a date for %y, BSD's %Fm prints a raw epoch, so the
139
139
  # BSD branch asks for a formatted date instead — the whole point of this line is eyeballing.
140
140
  _mtime_h() {
141
- stat -c %y "$1" 2>/dev/null \
142
- || stat -f "%Sm" -t "%Y-%m-%d %H:%M:%S" "$1" 2>/dev/null \
143
- || echo "?"
141
+ stat -c %y "$1" 2>/dev/null || stat -f "%Sm" -t "%Y-%m-%d %H:%M:%S" "$1" 2>/dev/null || echo "?"
144
142
  }
145
143
 
146
144
  _assert_newer() { # $1=expected-newer $2=reference $3=lane label — returns 1 if premise fails
@@ -298,7 +296,7 @@ _carry_fixture() { # $1=오늘카드 본문 $2=fh_completed 본문(빈 문자
298
296
  printf '%s\n' "$card_body" > "$T/tracks/_meta/reference_next_session_starter.md"
299
297
  [ -n "$done_body" ] && printf '%s\n' "$done_body" > "$T/tracks/_meta/fh_completed_${TODAY}.md"
300
298
  # companion store: 어제 날짜로 커밋된 «이전 카드» 미러 하나
301
- FUT=$(date -v+2d '+%m-%d' 2>/dev/null || date -d '+2 days' '+%m-%d')
299
+ FUT=$(date -d '+2 days' '+%m-%d' 2>/dev/null || date -v+2d '+%m-%d')
302
300
  ( cd "$S" && git init -q . && git config user.email a@l && git config user.name a \
303
301
  && mkdir -p tracks-meta \
304
302
  && printf '기한 %s 미팅 안건 [B]\n' "$FUT" > tracks-meta/reference_next_session_starter.md \
@@ -307,7 +305,7 @@ _carry_fixture() { # $1=오늘카드 본문 $2=fh_completed 본문(빈 문자
307
305
  git commit -qm prior ) >/dev/null 2>&1
308
306
  printf '%s|%s' "$T" "$S"
309
307
  }
310
- _carry_future() { date -v+2d '+%m-%d' 2>/dev/null || date -d '+2 days' '+%m-%d'; }
308
+ _carry_future() { date -d '+2 days' '+%m-%d' 2>/dev/null || date -v+2d '+%m-%d'; }
311
309
 
312
310
  _carry_lane() { # $1=name $2=pattern $3=expect $4="REPO|STORE" $5=extra-env(옵션)
313
311
  local name="$1" pat="$2" expect="$3" pair="$4" env5="${5:-}" T S out hit
@@ -261,5 +261,22 @@ if [ -x "$ROOT/scripts/round/arm_blind_probe.sh" ]; then
261
261
  || no "L24 눈가림 실패 — 팔이 정답지를 읽을 수 있다"
262
262
  else no "L24 프로브 없음 — 검사 못 함(스킵 아님)"; fi
263
263
 
264
+ # ── L25 ⓒ 날짜 오염 통제 필드 (six_axis_review_2026-09-04 강화 #3) — 존재만 본다, 값은 안 본다.
265
+ # 이 러너가 판정을 안 낸다는 것이 헤더의 약속이라, 레인도 «필드가 있나»만 잰다.
266
+ OUTDIR="$WORKROOT/o25"
267
+ ( cd "$SRC" && PATH="$STUBBIN:$PATH" HOME="$FAKEHOME" FH_STUB_MODE=say \
268
+ bash "$SUT" --arm a --reps 1 --prompt p --out "$OUTDIR" ) >/dev/null 2>&1
269
+ META="$OUTDIR/a_r1.meta.tsv"
270
+ if [ -f "$META" ]; then
271
+ grep -q '^corpus_head_date ' "$META" && ok "L25a corpus_head_date field recorded" \
272
+ || no "L25a corpus_head_date field missing"
273
+ grep -q '^sim_model ' "$META" && ok "L25b sim_model field recorded" \
274
+ || no "L25b sim_model field missing"
275
+ grep -q '^sim_model_cutoff ' "$META" && ok "L25c sim_model_cutoff field recorded" \
276
+ || no "L25c sim_model_cutoff field missing"
277
+ else
278
+ no "L25 meta.tsv not written at all ($META)"
279
+ fi
280
+
264
281
  echo "sim_isolated_run lanes: $pass passed, $fail failed"
265
282
  [ "$fail" -eq 0 ] || exit 1
@@ -132,7 +132,7 @@ done
132
132
  _probe_hits() {
133
133
  local pat="$1"; shift
134
134
  grep -lE -- "$pat" "$@" 2>/dev/null
135
- return "${PIPESTATUS[0]:-$?}"
135
+ return "${PIPESTATUS[0]:-$?}" # portability-noqa: shebang (line 1) pins bash — PIPESTATUS is a real array there, unlike zsh
136
136
  }
137
137
 
138
138
  # ── 1단계: 컨트롤. 살아있음을 증명하기 전에는 타깃을 인쇄하지 않는다 ──
@@ -179,7 +179,7 @@ MISS=0; N=0
179
179
  while IFS=$'\t' read -r kind pat label; do
180
180
  [ "${kind:-}" = "TARGET" ] || continue
181
181
  N=$((N+1))
182
- hits=$(_probe_hits "$pat" "${FILES[@]}" | wc -l | tr -d ' '); _rc=${PIPESTATUS[0]}
182
+ hits=$(_probe_hits "$pat" "${FILES[@]}" | wc -l | tr -d ' '); _rc=${PIPESTATUS[0]} # portability-noqa: shebang (line 1) pins bash — PIPESTATUS is a real array there, unlike zsh
183
183
  if [ "$_rc" -ge 2 ]; then
184
184
  printf " 🟥 %-46s 프로브 오류\n" "${label:-$pat}"
185
185
  BAD_PAT="${BAD_PAT:+$BAD_PAT, }TARGET/${label:-$pat}"