@chrono-meta/fh-gate 2.0.0 → 2.1.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 (56) hide show
  1. package/.claude/capabilities/adapters/gstack-content-safety.cap +110 -0
  2. package/.claude/capabilities/adapters/mate-agent-boundary.cap +110 -0
  3. package/.claude/capabilities/adapters/qasp-new-code-anchor.cap +116 -0
  4. package/.claude/capabilities/degrade-direction-scan.cap +24 -0
  5. package/.claude/capabilities/public-surface-scan.cap +24 -0
  6. package/.claude/rules/fh_4axis_gate.md +77 -1
  7. package/.claude-plugin/marketplace.json +3 -3
  8. package/AGENTS.md +25 -3
  9. package/CHEATSHEET.md +17 -1
  10. package/CLAUDE.md +241 -5
  11. package/README.ja.md +55 -20
  12. package/README.ko.md +86 -20
  13. package/README.md +57 -20
  14. package/README.zh.md +49 -19
  15. package/knowledge/shared/harness-core/capability_composition_contract.md +68 -0
  16. package/knowledge/shared/harness-core/fh_three_layer_canon.md +122 -5
  17. package/knowledge/shared/harness-core/field_verdict_crossfamily_gate.md +115 -3
  18. package/knowledge/shared/harness-core/harness_incubator_doctrine.md +83 -1
  19. package/knowledge/shared/harness-core/harness_terminal_correlation_and_recommendations.md +255 -0
  20. package/knowledge/shared/harness-core/harness_verification_core_extended.md +1 -1
  21. package/knowledge/shared/harness-core/ship_readiness_gate.md +234 -11
  22. package/knowledge/shared/learnings/subagent_invocations_log.yaml +128 -0
  23. package/knowledge/shared/rules/sister_asset_protocol.md +12 -0
  24. package/package.json +12 -3
  25. package/plugins/fh-commons/.claude-plugin/plugin.json +1 -1
  26. package/plugins/fh-commons/agents/quench-challenger.md +6 -1
  27. package/plugins/fh-meta/.claude-plugin/plugin.json +2 -2
  28. package/plugins/fh-meta/CHANGELOG.md +60 -0
  29. package/plugins/fh-meta/skills/auto-decorrelation/SKILL.md +14 -2
  30. package/plugins/fh-meta/skills/steel-quench/SKILL_detail.md +13 -0
  31. package/plugins/fh-meta/skills/verify-bidirectional/SKILL.md +34 -1
  32. package/scripts/adapters/gstack_content_safety.sh +186 -0
  33. package/scripts/adapters/mate_agent_boundary.sh +120 -0
  34. package/scripts/adapters/peer_resolve.sh +99 -0
  35. package/scripts/adapters/qasp_new_code_anchor.sh +159 -0
  36. package/scripts/branch_claim.sh +14 -0
  37. package/scripts/capability_effect_probe.sh +340 -20
  38. package/scripts/capability_registry_check.sh +271 -20
  39. package/scripts/chamber_run.sh +24 -3
  40. package/scripts/chamber_witness.sh +25 -0
  41. package/scripts/cluster_capability_scan.sh +629 -0
  42. package/scripts/fh-gate.sh +16 -1
  43. package/scripts/fh_session_load.sh +70 -0
  44. package/scripts/package_coverage_check.sh +10 -0
  45. package/scripts/portability_lint.sh +257 -0
  46. package/scripts/prepublish_scope_note.sh +139 -0
  47. package/scripts/relay_channel.sh +70 -7
  48. package/scripts/selfcheck.sh +167 -5
  49. package/scripts/test_adapter_lanes.sh +296 -0
  50. package/scripts/test_capability_entrypoint_shipping.sh +8 -1
  51. package/scripts/test_fh_gate_regressions.sh +21 -9
  52. package/scripts/test_marker_axes_run_lanes.sh +87 -3
  53. package/scripts/test_marker_crossfamily_lanes.sh +31 -1
  54. package/scripts/test_relay_channel_lanes.sh +76 -0
  55. package/templates/.git-hooks/pre-commit +239 -12
  56. package/templates/subagent-tally-hook.json +2 -2
@@ -0,0 +1,296 @@
1
+ #!/usr/bin/env bash
2
+ # test_adapter_lanes.sh — `scripts/adapters/*` (크로스하네스 **어댑터**)의 회귀 레인.
3
+ #
4
+ # ─────────────────────────────────────────────────────────────────────────────
5
+ # 무엇을 고정하나
6
+ # ─────────────────────────────────────────────────────────────────────────────
7
+ # 등록 바(`capability_registry_check.sh`)는 **선언된 캘리브레이션 arm 두 개만** 돌리고,
8
+ # 자기 출력에 «나머지 enum 값은 관측하지 않았다» 고 적는다. 어댑터에서 그 «나머지» 는
9
+ # 하필 가장 중요한 값이다 — **`PEER_ABSENT`**. 「그 하네스를 이 머신에 안 깔았다」가
10
+ # `CLEAN` 으로 접히면 미측정이 통과로 렌더되고, `HARNESS_ERROR` 로 접히면 멀쩡한 이웃이
11
+ # 고장으로 렌더된다. 이 레인 묶음이 그 값과 peer 해석 규칙을 고정한다.
12
+ #
13
+ # 레인 클래스 (되돌림 프로브가 이 태그로 «어느 레인이 빨개져야 하는가» 를 사전등록한다)
14
+ # resolve-absent peer 가 안 보일 때 **전용 값**이 나오는가
15
+ # resolve-alias 별칭 규칙(`<n>-dev` · `_`→`-`)이 실제로 도는가
16
+ # resolve-guard 모호/부분일치를 **고르지 않는가**
17
+ # control peer 가 보이면 **실제 판정**이 나오는가
18
+ # (「늘 PEER_ABSENT 를 찍는 계기」와 구분하는 자리)
19
+ # enum M4 가 관측하지 않는 나머지 enum 값들
20
+ #
21
+ # 사용법
22
+ # bash scripts/test_adapter_lanes.sh # 레인 전부
23
+ # bash scripts/test_adapter_lanes.sh --revert-probe # 레인 + 되돌림 프로브 2종
24
+ # exit: 0 = 전부 기대대로 · 1 = 회귀 · 10 = 하네스 오류(전제 파손 — 판정 아님)
25
+
26
+ set -uo pipefail
27
+
28
+ REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
29
+ # 되돌림 프로브가 **중화된 사본**을 가리키게 하려고 파라미터화한다. 기본값은 실물이다.
30
+ ADAPTER_DIR="${FH_ADAPTER_DIR:-$REPO_ROOT/scripts/adapters}"
31
+
32
+ T="$(mktemp -d)" || { echo "❌ HARNESS-ERROR — mktemp 실패"; exit 10; }
33
+ trap 'rm -rf "$T"' EXIT
34
+
35
+ FAIL=0; N=0
36
+ RED_LANES="" # 이번 실행에서 빨개진 레인 ID (되돌림 프로브가 읽는다)
37
+
38
+ # ── peer 가 이 머신에 **있어야만** 의미가 있는 레인 ──────────────────────────
39
+ # 🟥 실측(2026-08-16): 로컬 23/23 초록인데 **CI 는 9레인 적색**이었다(G2 G3 G6 G9 M2 M3
40
+ # Q2 Q3 Q6). 이 레인들이 루트 오버라이드 없이 어댑터를 불러 `$HOME/projects` 를 보는데,
41
+ # CI 체크아웃엔 peer 레포가 없어 전부 `PEER_ABSENT(20)` 이 나왔다. 즉 레인의 앵커가
42
+ # **운영자 머신의 레이아웃**에 걸려 있었다 — 같은 날 `L13c` 가 gitignored 데이터에 앵커를
43
+ # 걸어 CI 만 빨갛던 것과 **같은 클래스**다([[feedback_ci_measures_state_not_transition]]).
44
+ #
45
+ # 🟥 그리고 이 스킵은 **조용하면 안 된다.** peer 가 없는 환경에서 컨트롤이 전부 빠지면
46
+ # 남는 것은 PEER_ABSENT 레인뿐이고, 그건 「늘 PEER_ABSENT 를 내는 계기」와 **구분되지 않는다**
47
+ # — 컨트롤이 막으려던 바로 그 상태다. 그래서 스킵 수를 세어 **종결 줄에 반드시 찍는다.**
48
+ SKIPPED=0
49
+ _lane_peer() { # $1=peer_root $2=id $3=class $4=설명 $5=expected $6=actual
50
+ if [ -n "$1" ]; then
51
+ _lane "$2" "$3" "$4" "$5" "$6"
52
+ else
53
+ SKIPPED=$((SKIPPED + 1))
54
+ printf ' ⏭ %-6s [%-14s] %s — peer 부재로 SKIP (**통과 아님**)\n' "$2" "$3" "$4"
55
+ fi
56
+ }
57
+
58
+ _lane() { # $1=id $2=class $3=설명 $4=expected_rc $5=actual_rc
59
+ N=$((N + 1))
60
+ if [ "$4" = "$5" ]; then
61
+ printf ' ✅ %-6s [%-14s] %s (rc=%s)\n' "$1" "$2" "$3" "$5"
62
+ else
63
+ printf ' ❌ %-6s [%-14s] %s — expect rc=%s, got rc=%s\n' "$1" "$2" "$3" "$4" "$5"
64
+ FAIL=1; RED_LANES="$RED_LANES $1"
65
+ fi
66
+ }
67
+
68
+ # 계기 전제 — 어댑터 3종이 **실제로 거기 있는지** 먼저 본다. 없으면 이 레인 묶음은
69
+ # «전부 초록» 이 아니라 하네스 오류다(빈 대상에 대고 초록을 내는 것이 여기서 막으려는 형태).
70
+ for a in gstack_content_safety mate_agent_boundary qasp_new_code_anchor peer_resolve; do
71
+ [ -r "$ADAPTER_DIR/$a.sh" ] || { echo "❌ HARNESS-ERROR — $ADAPTER_DIR/$a.sh 부재. 빈 대상에 초록을 내지 않는다"; exit 10; }
72
+ done
73
+
74
+ G="$ADAPTER_DIR/gstack_content_safety.sh"
75
+ M="$ADAPTER_DIR/mate_agent_boundary.sh"
76
+ Q="$ADAPTER_DIR/qasp_new_code_anchor.sh"
77
+
78
+ # 「peer 가 안 보이는 세계」 = 잘 형성된 루트 목록인데 그 안에 이 peer 가 없는 것.
79
+ # (빈 **문자열**이 아니다 — 그건 「어디를 볼지 자체가 없다」는 다른 명제이고 전제 파손이다.)
80
+ EMPTY_WORLD="$T/empty_world"; mkdir -p "$EMPTY_WORLD"
81
+
82
+ _rc() { "$@" >/dev/null 2>&1; printf '%s' "$?"; } # 판정은 파이프를 통과시키지 않는다
83
+
84
+ # 실물 peer 루트 — 있으면 alias/control 레인을 돌리고, 없으면 그 레인은 SKIP 으로 **말한다**.
85
+ PEER_HOME="${FH_PROJECTS_HOME:-$HOME/projects}"
86
+ _peer_root() { # $1=이름 → 실물 루트 or 빈 문자열 (별칭 순서는 어댑터와 같다)
87
+ local n="$1" c
88
+ for c in "$n" "$n-dev" "$(printf '%s' "$n" | tr '_' '-')"; do
89
+ [ -d "$PEER_HOME/$c" ] && { printf '%s' "$PEER_HOME/$c"; return; }
90
+ done
91
+ printf ''
92
+ }
93
+ GS_ROOT="$(_peer_root gstack)"; MT_ROOT="$(_peer_root mate)"; QS_ROOT="$(_peer_root qasp)"
94
+
95
+ echo "== 어댑터 레인 (대상: $ADAPTER_DIR) =="
96
+ echo
97
+
98
+ # ═══════════════════════════════════════════════════════════════════════════
99
+ # ① gstack-content-safety
100
+ # ═══════════════════════════════════════════════════════════════════════════
101
+ echo "── gstack-content-safety (enum 0=CLEAN 1=BLOCK_HIGH 2=REVIEW_MEDIUM 3=NO_TARGET 10=HARNESS_ERROR 20=PEER_ABSENT)"
102
+ _lane G1 resolve-absent "peer 불가시 → 전용값 PEER_ABSENT(CLEAN 도 HARNESS_ERROR 도 아니다)" 20 \
103
+ "$(FH_CLUSTER_ROOTS="$EMPTY_WORLD" _rc bash "$G" --known-positive)"
104
+ _lane_peer "$GS_ROOT" G2 control "peer 가시 → 실제 판정 BLOCK_HIGH («늘 PEER_ABSENT» 와 구분)" 1 \
105
+ "$(_rc bash "$G" --known-positive)"
106
+ _lane_peer "$GS_ROOT" G3 control "peer 가시 → 음성 arm 은 CLEAN (계기가 «가른다» 는 증거)" 0 \
107
+ "$(_rc bash "$G" --known-negative)"
108
+ if [ -n "$GS_ROOT" ]; then
109
+ _lane G4 resolve-alias "명시 루트 목록(FH_CLUSTER_ROOTS)으로 해석된다" 0 \
110
+ "$(FH_CLUSTER_ROOTS="$GS_ROOT" _rc bash "$G" --known-negative)"
111
+ else
112
+ echo " ⏭ G4 [resolve-alias ] gstack 실물 루트 없음 — SKIP (통과 아님)"
113
+ fi
114
+ # 부분일치로 남의 레포를 집지 않는다. basename 이 **정확히** 별칭 후보여야 한다.
115
+ mkdir -p "$T/world_prefix/gstackx"
116
+ _lane G5 resolve-guard "basename 부분일치는 peer 가 아니다(gstackx ≠ gstack)" 20 \
117
+ "$(FH_CLUSTER_ROOTS="$T/world_prefix/gstackx" _rc bash "$G" --known-positive)"
118
+ mkdir -p "$T/empty_target"
119
+ _lane_peer "$GS_ROOT" G6 enum "빈 디렉토리 → NO_TARGET(3). CLEAN 이 아니다 — 아무것도 안 쟀다" 3 \
120
+ "$(_rc bash "$G" --target "$T/empty_target")"
121
+ _lane_peer "$GS_ROOT" G7 enum "없는 대상 → HARNESS_ERROR(10). 부재 대상은 빈 대상이 아니다" 10 \
122
+ "$(_rc bash "$G" --target "$T/does_not_exist")"
123
+ _lane_peer "$GS_ROOT" G8 enum "인자 없음 → HARNESS_ERROR(10). 안 돈 실행을 판정으로 렌더하지 않는다" 10 \
124
+ "$(_rc bash "$G")"
125
+ # MEDIUM 만 — 자동 차단이 아니라 사람 판단. 리터럴은 이 소스가 자기 스캐너에 물리지 않게 쪼갠다.
126
+ printf 'payroll note\ncontact ssn %s-%s-%s for the record\n' '123' '45' '6789' > "$T/medium_only.txt"
127
+ _lane_peer "$GS_ROOT" G9 enum "MEDIUM 만 → REVIEW_MEDIUM(2). CLEAN 과도 BLOCK 과도 다른 값이다" 2 \
128
+ "$(_rc bash "$G" --target "$T/medium_only.txt")"
129
+ echo
130
+
131
+ # ═══════════════════════════════════════════════════════════════════════════
132
+ # ② mate-agent-boundary
133
+ # ═══════════════════════════════════════════════════════════════════════════
134
+ echo "── mate-agent-boundary (enum 0=PASS 1=FAIL 10=HARNESS_ERROR 20=PEER_ABSENT)"
135
+ _lane M1 resolve-absent "peer 불가시 → 전용값 PEER_ABSENT(PASS 로도 HARNESS_ERROR 로도 안 접힌다)" 20 \
136
+ "$(FH_CLUSTER_ROOTS="$EMPTY_WORLD" _rc bash "$M" --known-positive)"
137
+ _lane_peer "$MT_ROOT" M2 control "peer 가시 → 실제 판정 PASS" 0 "$(_rc bash "$M" --known-positive)"
138
+ _lane_peer "$MT_ROOT" M3 control "peer 가시 → 음성 arm 은 FAIL (판별력의 증거)" 1 "$(_rc bash "$M" --known-negative)"
139
+ if [ -n "$MT_ROOT" ]; then
140
+ # 🟥 별칭 레인의 핵심: 이름은 `mate` 인데 실물 디렉토리는 `mate-dev` 다.
141
+ # 별칭 후보에서 `<n>-dev` 가 빠지면 이 레인이 즉시 PEER_ABSENT 로 빨개진다.
142
+ _lane M4 resolve-alias "이름 mate → 실물 $(basename "$MT_ROOT") 로 별칭 해석된다" 0 \
143
+ "$(FH_CLUSTER_ROOTS="$MT_ROOT" _rc bash "$M" --known-positive)"
144
+ else
145
+ echo " ⏭ M4 [resolve-alias ] mate 실물 루트 없음 — SKIP (통과 아님)"
146
+ fi
147
+ _lane_peer "$MT_ROOT" M5 enum "없는 대상 → HARNESS_ERROR(10). peer 헤더가 «위반 0 이 아니다» 라고 못 박은 값" 10 \
148
+ "$(_rc bash "$M" --target "$T/does_not_exist.md")"
149
+ _lane_peer "$MT_ROOT" M6 enum "알 수 없는 모드 → HARNESS_ERROR(10)" 10 "$(_rc bash "$M" --bogus-mode)"
150
+ echo
151
+
152
+ # ═══════════════════════════════════════════════════════════════════════════
153
+ # ③ qasp-new-code-anchor
154
+ # ═══════════════════════════════════════════════════════════════════════════
155
+ echo "── qasp-new-code-anchor (enum 0=PASS 1=VIOLATION 2=ARGS 3=HARNESS_ERROR 4=OUT_OF_SCOPE 20=PEER_ABSENT)"
156
+ _lane Q1 resolve-absent "peer 불가시 → 전용값 PEER_ABSENT(PASS 로도 HARNESS_ERROR 로도 안 접힌다)" 20 \
157
+ "$(FH_CLUSTER_ROOTS="$EMPTY_WORLD" _rc bash "$Q" --known-positive)"
158
+ _lane_peer "$QS_ROOT" Q2 control "peer 가시 → 실제 판정 VIOLATION" 1 "$(_rc bash "$Q" --known-positive)"
159
+ _lane_peer "$QS_ROOT" Q3 control "peer 가시 → 음성 arm 은 PASS (판별력의 증거)" 0 "$(_rc bash "$Q" --known-negative)"
160
+ if [ -n "$QS_ROOT" ]; then
161
+ _lane Q4 resolve-alias "이름 qasp → 실물 $(basename "$QS_ROOT") 로 별칭 해석된다" 0 \
162
+ "$(FH_CLUSTER_ROOTS="$QS_ROOT" _rc bash "$Q" --known-negative)"
163
+ # 🟥 모호 가드 — 한 이름에 레포 둘. **고르지 않는다.** 조용히 하나를 고르면 «어느
164
+ # 하네스를 쟀는지» 아무도 모르게 된다. 판정 불가는 PASS 가 아니므로 HARNESS_ERROR.
165
+ mkdir -p "$T/ambig/qasp" "$T/ambig/qasp-dev"
166
+ _lane Q5 resolve-guard "한 이름에 레포 둘 → 고르지 않고 HARNESS_ERROR(3)" 3 \
167
+ "$(FH_CLUSTER_ROOTS="$T/ambig/qasp:$T/ambig/qasp-dev" _rc bash "$Q" --known-positive)"
168
+ else
169
+ echo " ⏭ Q4/Q5 [resolve-* ] qasp 실물 루트 없음 — SKIP (통과 아님)"
170
+ fi
171
+ _lane_peer "$QS_ROOT" Q6 enum "--range 에 base 없음 → ARGS(2). 판정이 아니다" 2 "$(_rc bash "$Q" --range)"
172
+
173
+ # ── 합성 peer — peer 히스토리를 만지지 않고 나머지 enum 을 덮는다 ────────────
174
+ # 🟥 왜 합성 peer 인가: peer 는 `residency: company` 다. 실물 히스토리를 레인으로 쓰면
175
+ # 레인 출력에 조직 경로가 실린다. 그리고 실물 히스토리는 남의 커밋에 따라 조용히
176
+ # 다른 것을 재게 된다(감사 대상은 얼려야 한다). 합성 peer 는 **peer 의 실제 진입점
177
+ # 스크립트를 복사해** 쓰므로, 재는 것은 여전히 «어댑터가 peer 의 exit 을 옳게
178
+ # 재매핑하는가» 다 — 어댑터의 매핑이지 peer 의 히스토리가 아니다.
179
+ if [ -n "$QS_ROOT" ] && [ -r "$QS_ROOT/scripts/new_code_anchor_scan.sh" ]; then
180
+ SYN="$T/synworld/qasp-dev"; mkdir -p "$SYN/scripts" "$SYN/src" "$SYN/tests"
181
+ cp "$QS_ROOT/scripts/new_code_anchor_scan.sh" "$SYN/scripts/" || { echo "❌ HARNESS-ERROR — peer 진입점 복사 실패"; exit 10; }
182
+ GIT=(git -c user.email=lane@localhost -c user.name=lane -c commit.gpgsign=false)
183
+ ( cd "$SYN" && "${GIT[@]}" init -q . && printf 'base\n' > README.md \
184
+ && "${GIT[@]}" add -A && "${GIT[@]}" commit -qm base ) >/dev/null 2>&1 \
185
+ || { echo "❌ HARNESS-ERROR — 합성 peer 레포 생성 실패"; exit 10; }
186
+ SYN_BASE="$(cd "$SYN" && git rev-parse HEAD)"
187
+ # 범위 밖 언어의 신규 실행 코드만 넣는다 → 「검사되지 않았다」 ≠ 「깨끗하다」
188
+ ( cd "$SYN" && printf 'export const f = (a) => a + 1;\n' > src/newthing.ts \
189
+ && "${GIT[@]}" add -A && "${GIT[@]}" commit -qm oos ) >/dev/null 2>&1
190
+ _lane Q7 enum "범위 밖 언어의 신규 실행 코드 → OUT_OF_SCOPE(4). PASS 로 접히지 않는다" 4 \
191
+ "$(FH_CLUSTER_ROOTS="$SYN" _rc bash "$Q" --range "$SYN_BASE" HEAD)"
192
+ _lane Q8 enum "해석 불가 ref → HARNESS_ERROR(3). 판정이 아니다" 3 \
193
+ "$(FH_CLUSTER_ROOTS="$SYN" _rc bash "$Q" --range no-such-ref-xyz HEAD)"
194
+ else
195
+ echo " ⏭ Q7/Q8 [enum ] qasp 진입점 도달 불가 — SKIP (통과 아님)"
196
+ fi
197
+ echo
198
+
199
+ # ═══════════════════════════════════════════════════════════════════════════
200
+ # 되돌림 프로브 — 앵커가 장식이 아님을 «되돌려 보고» 잰다 (적용확인 → 실행 → 복원)
201
+ # ═══════════════════════════════════════════════════════════════════════════
202
+ # 레인이 초록인 것과 레인에 **판별력이 있는 것**은 다른 명제다. 여기서는 peer 해석부를
203
+ # 두 방향으로 중화하고, **사전등록한 레인 집합만** 빨개지는지 본다.
204
+ # R1 «부재 분기» 중화 → 부재를 부재로 못 낸다 ⇒ resolve-absent + resolve-guard(부분일치) 빨강
205
+ # R2 «별칭 목록» 중화 → `-dev` 를 못 찾는다 ⇒ mate/qasp 의 alias·control·enum 레인만 빨강
206
+ #
207
+ # 🟥 **초판 사전등록이 두 군데 틀렸고, 프로브가 그걸 잡았다**(2026-08-16). 적어 두는 이유는
208
+ # 이게 프로브에 판별력이 있다는 증거이기 때문이다 — 사전등록을 관측에 맞춰 조용히 고치면
209
+ # 그 증거가 사라진다. 두 차이는 **레인 결함이 아니라 사전등록 오류**였고, 기계적으로 설명된다:
210
+ # · R1 에 **G5** 가 추가로 빨강: G5 는 `gstackx` 루트를 준다. 중화판은 부재 분기 대신
211
+ # `$PROJECTS_HOME/gstack` 를 내는데 **그 경로는 실재한다** → 어댑터가 실제로 돌아
212
+ # BLOCK_HIGH(1) 을 낸다. G5 도 부재 분기에 결박된 레인이므로 빨강이 맞다.
213
+ # · R2 에서 **Q5 는 초록**: Q5 는 `qasp` 와 `qasp-dev` 둘을 주는 모호 레인이다. 별칭
214
+ # 목록이 `$1` 하나로 줄면 `qasp` 만 맞아 **모호가 아니게 되고**, 어댑터는 진입점
215
+ # 부재로 HARNESS_ERROR(3) 를 낸다 — 기대값과 같은 값이다. 즉 R2 는 Q5 를 원리적으로
216
+ # 구분하지 못한다. 그건 Q5 의 결함이 아니라 **이 프로브 축의 사각지대**이고,
217
+ # Q5 는 R1 이 아니라 별도 축(모호 판정 자체를 지우는 중화)으로만 흔들 수 있다.
218
+ # ★중화는 **실물 파일이 아니라 사본**에 건다(FH_ADAPTER_DIR). 실물 트리를 건드리지 않고,
219
+ # 사본이 실물과 «중화한 줄 말고는 동일» 함을 적용확인 단계에서 대조한다.
220
+ _revert_probe() {
221
+ local name="$1" sedexpr="$2" applied_marker="$3" expect_red="$4" desc="$5"
222
+ local C="$T/neutralized_$name"
223
+ rm -rf "$C"; mkdir -p "$C"
224
+ cp -R "$REPO_ROOT/scripts/adapters/." "$C/" || { echo "❌ HARNESS-ERROR — 사본 생성 실패"; return 10; }
225
+ # ── 1단 적용확인: 중화가 **실제로 들어갔는가**. 안 들어간 채 «초록» 을 보고
226
+ # «레인이 튼튼하다» 고 읽는 것이 이 프로브의 최대 실패모드다.
227
+ sed -i.bak "$sedexpr" "$C/peer_resolve.sh" && rm -f "$C/peer_resolve.sh.bak"
228
+ if ! grep -q "$applied_marker" "$C/peer_resolve.sh"; then
229
+ echo " ❌ $name — 중화가 적용되지 않았다(마커 «$applied_marker» 부재). 프로브를 신뢰할 수 없다"
230
+ return 1
231
+ fi
232
+ # 중화한 파일 말고는 사본이 실물과 동일해야 한다(다른 변수를 안 건드렸다는 확인).
233
+ local other_diff
234
+ other_diff="$(diff -rq "$REPO_ROOT/scripts/adapters" "$C" 2>/dev/null | grep -v 'peer_resolve.sh' | wc -l | tr -d ' ')"
235
+ if [ "$other_diff" != "0" ]; then
236
+ echo " ❌ $name — 사본이 peer_resolve.sh 외에도 다르다($other_diff건). 프로브 격리 실패"
237
+ return 1
238
+ fi
239
+ # ── 2단 실행: 사본을 대상으로 레인을 통째로 다시 돌린다.
240
+ local out red
241
+ out="$(FH_ADAPTER_DIR="$C" bash "${BASH_SOURCE[0]}" --lanes-only 2>&1)"
242
+ red="$(printf '%s\n' "$out" | sed -n 's/^ ❌ \([A-Z][0-9]*\) .*/\1/p' | sort | tr '\n' ' ' | sed 's/ $//')"
243
+ # ── 3단 복원: 사본을 지운다. 실물은 애초에 안 건드렸다.
244
+ rm -rf "$C"
245
+ printf ' %s — %s\n' "$name" "$desc"
246
+ printf ' 사전등록 빨강: [%s]\n' "$expect_red"
247
+ printf ' 실측 빨강: [%s]\n' "$red"
248
+ if [ "$red" = "$expect_red" ]; then
249
+ printf ' ✅ 정확히 그 레인만 빨개졌다 — 이 레인들은 peer 해석부에 실제로 결박돼 있다\n'
250
+ return 0
251
+ fi
252
+ printf ' ❌ 사전등록과 다르다 — 레인이 장식이거나 사전등록이 틀렸다. 둘 다 결함이다\n'
253
+ return 1
254
+ }
255
+
256
+ case "${1:-}" in
257
+ --lanes-only)
258
+ # 되돌림 프로브가 자기를 재귀 호출할 때 쓰는 모드. 프로브를 다시 돌리지 않는다.
259
+ ;;
260
+ --revert-probe)
261
+ echo "== 되돌림 프로브 (적용확인 → 실행 → 복원) =="
262
+ RP=0
263
+ # R1: 「없다」를 못 내게 한다 — 부재 분기를 순진한 추측 경로로 바꾼다.
264
+ _revert_probe R1 \
265
+ 's|^ printf .peer-resolve: peer| printf "%s" "${FH_PROJECTS_HOME:-$HOME/projects}/$n"; return 0 # NEUTRALIZED_R1\n printf '"'"'peer-resolve: peer|' \
266
+ 'NEUTRALIZED_R1' \
267
+ 'G1 G5 M1 Q1' \
268
+ '«부재» 분기 중화 → 부재 분기에 결박된 레인만 빨개져야 한다' || RP=1
269
+ echo
270
+ # R2: 별칭 후보를 이름 자신 하나로 줄인다 — `-dev` 를 못 찾게 된다.
271
+ _revert_probe R2 \
272
+ 's|^ printf .%s\\n. "\$1" "\$1-dev".*$| printf "%s\\n" "$1" # NEUTRALIZED_R2|' \
273
+ 'NEUTRALIZED_R2' \
274
+ 'M2 M3 M4 M5 M6 Q2 Q3 Q4 Q6 Q7 Q8' \
275
+ '별칭 목록 중화 → mate/qasp 쪽 레인만 빨개져야 한다(gstack 은 별칭이 필요 없다)' || RP=1
276
+ echo
277
+ [ "$RP" -eq 0 ] || FAIL=1
278
+ ;;
279
+ '') ;;
280
+ *) echo "usage: $(basename "$0") [--revert-probe]" >&2; exit 10 ;;
281
+ esac
282
+
283
+ echo
284
+ if [ "$FAIL" -eq 0 ]; then
285
+ # ★`${N}` 로 감싼다 — 한글이 바로 뒤에 오면 bash 가 `$N개` 를 **변수명 `N개`** 로 읽어
286
+ # `unbound variable` 로 죽는다(실측 2026-08-16: 레인 23개 전부 초록인데 마지막 요약
287
+ # 줄에서 rc=1). 계기가 자기 보고 줄에서 죽으면 초록이 빨강으로 렌더된다.
288
+ echo "✅ 레인 ${N}개 전부 기대대로"
289
+ if [ "$SKIPPED" -gt 0 ]; then
290
+ echo "⏭ 단, peer 부재로 **${SKIPPED}개 레인이 안 돌았다** — 그중 컨트롤이 빠지면"
291
+ echo " 남은 초록은 「늘 PEER_ABSENT 를 내는 계기」와 구분되지 않는다. 초록을 그렇게 읽지 마라."
292
+ fi
293
+ else
294
+ echo "❌ 회귀 — 빨강:$RED_LANES (레인 ${N}개 실행 · peer 부재 SKIP ${SKIPPED}개)"
295
+ fi
296
+ exit "$FAIL"
@@ -41,7 +41,14 @@ command -v node >/dev/null 2>&1 || {
41
41
  # Discover entry points by convention. A DISCOVERY of zero is an instrument failure, not a clean
42
42
  # result: this check exists precisely because the surface it guards is invisible to reference
43
43
  # walking, so "found nothing to check" must never render as "everything ships".
44
- ENTRIES=$(find scripts -maxdepth 1 -type f -name '*_capability.sh' 2>/dev/null | sort)
44
+ # 🟥 2026-08-16: 발견 규약이 **한 벌뿐**이었다(`scripts/*_capability.sh`, maxdepth 1). 같은 날
45
+ # 도입된 **어댑터 진입점**(`scripts/adapters/*.sh` — 남의 하네스 능력을 FH 안에서 부르는 기본
46
+ # 경로)은 이름도 위치도 그 규약 밖이라 **이 검사에 구조적으로 안 보였다.** 즉 어댑터 스크립트가
47
+ # `files[]` 에서 빠져도 이 검사는 초록이었다 — 검사기가 지키려던 바로 그 결함이 검사기 자신의
48
+ # 사각에서 재현될 수 있었다. 두 규약을 **합집합**으로 본다(빼지 말고 더한다).
49
+ ENTRIES=$( { find scripts -maxdepth 1 -type f -name '*_capability.sh' 2>/dev/null
50
+ find scripts/adapters -maxdepth 1 -type f -name '*.sh' 2>/dev/null
51
+ } | LC_ALL=C sort -u )
45
52
  n=$(printf '%s\n' "$ENTRIES" | grep -c . || true)
46
53
  n=$(( ${n:-0} + 0 ))
47
54
  if [ "$n" -eq 0 ]; then
@@ -21,6 +21,18 @@ pass=0; fail=0
21
21
  TMPROOT=$(mktemp -d "${TMPDIR:-/tmp}/fh_gate_test_XXXXXX")
22
22
  trap 'rm -rf "$TMPROOT"' EXIT
23
23
 
24
+ # Review SUBJECT — a fixture this suite creates, NOT a file it hopes the repo has.
25
+ # Every gate invocation below used the literal `package.json` as its subject. That is an npm-package
26
+ # assumption smuggled into a test: this suite passed here only because this repo happens to ship one.
27
+ # MEASURED 2026-08-16 in a sibling harness that carries this file verbatim and ships no package.json:
28
+ # every lane that actually invokes the gate on a file returned 10 ("0 of N targets resolved →
29
+ # harness error") — 12 failures from this one hardcoded fixture, on top of 18 more from the same
30
+ # missing file killing the gate's version read outright. The suite was measuring the fixture's
31
+ # existence, not the gate's behaviour. A test that only runs in the repo it was written in is not a
32
+ # regression anchor; it is a local habit.
33
+ SUBJECT="$TMPROOT/subject_under_review.txt"
34
+ printf 'fixture file for gate regression lanes — content is irrelevant, existence is not\n' > "$SUBJECT"
35
+
24
36
  # --- fake codex backend: writes $FAKE_PAYLOAD to the -o path, exits 0 ---
25
37
  # Doubles as the live demonstration of the PATH-trusting residual documented in fh-gate.sh.
26
38
  FAKEBIN="$TMPROOT/bin"; mkdir -p "$FAKEBIN"
@@ -68,16 +80,16 @@ check() {
68
80
  fi
69
81
  }
70
82
 
71
- run_gate() { env "$@" bash "$GATE" "package.json" quick test; }
83
+ run_gate() { env "$@" bash "$GATE" "$SUBJECT" quick test; }
72
84
  run_fake() {
73
85
  local payload="$1"; shift
74
86
  env PATH="$FAKEBIN:$PATH" FH_BACKEND=codex FH_MODEL=fake FAKE_PAYLOAD="$payload" \
75
- bash "$GATE" "package.json" quick test
87
+ bash "$GATE" "$SUBJECT" quick test
76
88
  }
77
89
  run_fake_claude() {
78
90
  local payload="$1"; shift
79
91
  env PATH="$FAKEBIN:$PATH" FH_BACKEND=claude FH_MODEL=fake FAKE_PAYLOAD="$payload" \
80
- bash "$GATE" "package.json" quick test
92
+ bash "$GATE" "$SUBJECT" quick test
81
93
  }
82
94
 
83
95
  echo "── argument / env validation ──"
@@ -89,7 +101,7 @@ check "FH_TIMEOUT non-integer rejected" 11 run_gate FH_TIMEOUT="abc" FH_DRY_RUN=
89
101
  check "FH_TIMEOUT integer accepted (no regression)" 12 run_gate FH_TIMEOUT=120 FH_DRY_RUN=1
90
102
  # Newline in FH_CALLER forges an extra column-0 FH_GATE_VERDICT line in the legacy contract.
91
103
  check "FH_CALLER newline injection rejected" 11 \
92
- env FH_CALLER=$'ci\nFH_GATE_VERDICT: PASS' FH_DRY_RUN=1 bash "$GATE" "package.json" quick
104
+ env FH_CALLER=$'ci\nFH_GATE_VERDICT: PASS' FH_DRY_RUN=1 bash "$GATE" "$SUBJECT" quick
93
105
 
94
106
  echo
95
107
  echo "── dry-run must not be readable as PASS ──"
@@ -129,7 +141,7 @@ check "claude path: clean PASS → exit 0" 0 run_fake_claude \
129
141
  check "claude path: is_error envelope → fails closed" 10 \
130
142
  env PATH="$FAKEBIN:$PATH" FH_BACKEND=claude FH_MODEL=fake \
131
143
  FAKE_ENVELOPE='{"is_error":true,"subtype":"error","structured_output":{"status":"SUCCESS","verdict":"PASS","findings_count":0,"findings_a":0,"findings_b":0,"findings":[]}}' \
132
- bash "$GATE" "package.json" quick test
144
+ bash "$GATE" "$SUBJECT" quick test
133
145
 
134
146
  echo
135
147
  echo "── legitimate verdicts still work (no over-blocking regression) ──"
@@ -199,7 +211,7 @@ NOENT="$TMPROOT/noentropy"; mkdir -p "$NOENT"
199
211
  printf '#!/usr/bin/env bash\nexit 1\n' > "$NOENT/openssl"; chmod +x "$NOENT/openssl"
200
212
  printf '#!/usr/bin/env bash\nexit 1\n' > "$NOENT/od"; chmod +x "$NOENT/od"
201
213
  check "no CSPRNG (openssl+od stubbed to fail) → fails closed" 10 \
202
- env PATH="$NOENT:$PATH" FH_DRY_RUN=1 bash "$GATE" "package.json" quick test
214
+ env PATH="$NOENT:$PATH" FH_DRY_RUN=1 bash "$GATE" "$SUBJECT" quick test
203
215
 
204
216
  echo
205
217
  echo "── FH_BACKEND=cross (decorrelated review) ──"
@@ -225,7 +237,7 @@ check_out() { # <name> <expected-exit> <grep-ere that MUST appear> -- <cmd...>
225
237
  run_cross() { # <claude-payload> <codex-payload>
226
238
  env PATH="$FAKEBIN:$PATH" FH_BACKEND=cross FH_MODEL=fake \
227
239
  FAKE_PAYLOAD_CLAUDE="$1" FAKE_PAYLOAD_CODEX="$2" FAKE_PAYLOAD="$1" \
228
- bash "$GATE" "package.json" quick test
240
+ bash "$GATE" "$SUBJECT" quick test
229
241
  }
230
242
 
231
243
  check_out "cross: both PASS → PASS, decorrelated" 0 'FH_GATE_DECORRELATED: yes' \
@@ -241,9 +253,9 @@ ONELEG="$TMPROOT/oneleg"; mkdir -p "$ONELEG"; cp "$FAKEBIN/claude" "$ONELEG/clau
241
253
  check_out "cross: codex absent → single leg, DECORRELATED: no" 0 'FH_GATE_DECORRELATED: no' \
242
254
  env PATH="$ONELEG:/usr/bin:/bin" FH_BACKEND=cross FH_MODEL=fake \
243
255
  FAKE_PAYLOAD_CLAUDE="$CROSS_PASS" FAKE_PAYLOAD="$CROSS_PASS" \
244
- bash "$GATE" "package.json" quick test
256
+ bash "$GATE" "$SUBJECT" quick test
245
257
  check "cross: no family available → fails closed" 10 \
246
- env PATH="/usr/bin:/bin" FH_BACKEND=cross bash "$GATE" "package.json" quick test
258
+ env PATH="/usr/bin:/bin" FH_BACKEND=cross bash "$GATE" "$SUBJECT" quick test
247
259
 
248
260
  echo
249
261
  echo "────────────────────────────────────────────────────────────────────"
@@ -2,9 +2,17 @@
2
2
  # test_marker_axes_run_lanes.sh — regression fixtures for pre-commit validate_marker_axes_run.
3
3
  #
4
4
  # 대상: CLAUDE.md §3층 자기 대조가 요구하는 마커 3줄 중 **기계로 볼 수 있는 두 줄** —
5
- # axes-run: 축(ⓐ다른계열 ⓑ첫실사용 ⓒ기록그라운딩 ⓓ되돌림) 각각의 실행 여부
5
+ # axes-run: 축의 실행 여부. 마커 날짜에 따라 **배열이 둘**이다:
6
+ # >= 2026-08-17 ⓐ계열 · ⓑ입장 · ⓒ격리그라운딩 · ⓓ3자대면 · ⓔ첫실사용 · ⓕ되돌림
7
+ # < 2026-08-17 a=계열 · b=첫실사용 · c=기록그라운딩 · d=되돌림 (옛 ASCII 4축)
6
8
  # controls: 각 축 컨트롤의 **생사**
7
9
  #
10
+ # 🟥 이 헤더 자신이 2026-08-17 까지 그 충돌을 저지르고 있었다 — «네 축(ⓐ다른계열 ⓑ첫실사용
11
+ # ⓒ기록그라운딩 ⓓ되돌림)» 이라고, **기호 표기로 옛 4축 의미**를 가르쳤다. 훅 본문이
12
+ # 「옛 b=첫실사용은 지금 ⓔ」 라고 경고하는 파일의 짝인데 정작 짝이 반대를 가르쳤고,
13
+ # 같은 세션이 이 파일을 11→25레인으로 늘리면서 **헤더는 안 봤다**. 잡은 것은 pmh-dev
14
+ # 입장리뷰(tier2)이고, 이 파일은 전파 자산이라 그 오류가 하류로 배송되던 중이었다.
15
+ #
8
16
  # 🟥 이 스위트가 증명하지 않는 것 (계약을 픽스처로 못박는다):
9
17
  # 「a=codex」 라고 적혀 있을 때 codex 가 **실제로 돌았는지**는 검사하지 않는다.
10
18
  # 마커의 계약은 form + non-vacuity + auditability 이지 provenance 가 아니다 —
@@ -25,12 +33,19 @@ T=$(mktemp -d); trap 'rm -rf "$T"' EXIT
25
33
  # 훅에서 함수 + 유예 상수를 그대로 뽑아 쓴다. 복제하면 드리프트하므로 **소스가 하나**다.
26
34
  {
27
35
  grep -E '^AXES_RUN_GRACE_DATE=' "$HOOK"
36
+ grep -E '^SIX_AXES_GRACE_DATE=' "$HOOK"
28
37
  sed -n '/^validate_marker_axes_run()/,/^}/p' "$HOOK"
29
38
  } > "$T/fn.sh"
30
39
 
31
40
  # 계기 자체가 살아있는지 먼저 본다 — 추출이 실패하면 모든 레인이 «통과»로 초록이 된다.
32
- if ! grep -q 'validate_marker_axes_run()' "$T/fn.sh" || ! grep -q '^AXES_RUN_GRACE_DATE=' "$T/fn.sh"; then
33
- echo "❌ 계기 사망: 훅에서 함수/상수 추출 실패 레인 결과를 신뢰하지 마라"
41
+ # 🟥 상수를 **각각** 확인한다. 하나로 뭉뚱그리면 상수가 추출에서 빠져도 초록이고,
42
+ # 그 경우 함수는 unbound 죽으며 모든 레인이 BLOCK 으로 몰려 «6축이 잘 막는다» 로
43
+ # 오독된다 — 계기 고장이 판정으로 렌더되는 형태다.
44
+ for _c in AXES_RUN_GRACE_DATE SIX_AXES_GRACE_DATE; do
45
+ grep -q "^${_c}=" "$T/fn.sh" || { echo "❌ 계기 사망: 훅에서 ${_c} 추출 실패 — 레인 결과를 신뢰하지 마라"; exit 1; }
46
+ done
47
+ if ! grep -q 'validate_marker_axes_run()' "$T/fn.sh"; then
48
+ echo "❌ 계기 사망: 훅에서 함수 추출 실패 — 레인 결과를 신뢰하지 마라"
34
49
  exit 1
35
50
  fi
36
51
 
@@ -90,6 +105,75 @@ check "$G2" PASS "파일명에 날짜가 없으면 판정 근거가 없다 →
90
105
  B6=$(mk 2026-08-10 'axis2-evidence: PASS')
91
106
  check "$B6" BLOCK "★유예일 **당일**은 요구한다 (경계가 < 이지 <= 가 아님을 고정)"
92
107
 
108
+ # ── 6축 확장 (2026-08-17) ───────────────────────────────────────────────────
109
+ # 표기법이 배열을 선언한다: ASCII(a=…d=)=옛 4축 · 기호(ⓐ=…ⓕ=)=현 6축.
110
+ # 그래서 여기서 고정할 것이 셋이다 — ⓐ 새 날짜는 기호 여섯을 요구하나 ⓑ 옛 날짜는
111
+ # 건드리지 않나(소급 차단 금지) ⓒ **혼용이 막히나**(혼용을 허용하면 판별 가능성이
112
+ # 통째로 사라지고, 옛 b=/d= 는 새 배열에서 다른 축이라 무음 오독이 된다).
113
+ D_SIX=2026-08-18 # 6축 유예일 이후
114
+ SIX_OK='axes-run: ⓐ=codex(4건) ⓑ=→standpoint ⓒ=none ⓓ=none ⓔ=CI배선 ⓕ=되돌림(국소성)
115
+ controls: alive — known-positive 3히트 · known-negative 0
116
+ standpoint: not-applicable'
117
+
118
+ S1=$(mk "$D_SIX" "$SIX_OK")
119
+ check "$S1" PASS "6축: 기호 여섯 + 컨트롤 + standpoint → 통과"
120
+
121
+ S2=$(mk "$D_SIX" 'axes-run: ⓐ=codex ⓑ=→standpoint ⓒ=none ⓓ=none ⓕ=되돌림
122
+ controls: alive — ok
123
+ standpoint: tier1')
124
+ check "$S2" BLOCK "6축: ⓔ 가 빠짐 → 차단"
125
+
126
+ S3=$(mk "$D_SIX" 'axes-run: ⓐ=codex b=CI배선 ⓒ=none ⓓ=none ⓔ=실사용 ⓕ=되돌림
127
+ controls: alive — ok')
128
+ check "$S3" BLOCK "★6축: ASCII 키가 기호 키를 대신함 → 차단 (ⓑ 가 빠졌다는 뜻이므로 missing-key 가 잡는다)"
129
+
130
+ # ★ 오탐 컨트롤 — 전용 혼용 가드를 지었다가 **실측 오탐 때문에 지웠다**. 값이 자유 산문이라
131
+ # `f=0.9` 나 `a=제어 b=처리` 가 값 안에 정당하게 들어온다. 과차단은 `--no-verify` 를 훈련시키고
132
+ # 그건 같은 훅의 Destructive-Op 게이트까지 무장해제한다. 이 두 레인이 그 삭제를 고정한다 —
133
+ # 누가 «안전하게» 혼용 가드를 되살리면 여기서 빨개진다.
134
+ S3b=$(mk "$D_SIX" 'axes-run: ⓐ=codex ⓑ=→standpoint ⓒ=none ⓓ=none ⓔ=실사용 ⓕ=되돌림 f=0.9 상승
135
+ controls: alive — ok
136
+ standpoint: tier1')
137
+ check "$S3b" PASS "★오탐 컨트롤: 값 안의 f=0.9 는 혼용이 아니다 → 통과"
138
+
139
+ S3c=$(mk "$D_SIX" 'axes-run: ⓐ=codex ⓑ=→standpoint ⓒ=none ⓓ=none ⓔ=arm a=제어 b=처리 비교 ⓕ=되돌림
140
+ controls: alive — ok
141
+ standpoint: tier1')
142
+ check "$S3c" PASS "★오탐 컨트롤: 값 안의 a=제어 b=처리 는 혼용이 아니다 → 통과"
143
+
144
+ S4=$(mk "$D_SIX" 'axes-run: a=codex b=CI배선 c=none d=되돌림
145
+ controls: alive — ok')
146
+ check "$S4" BLOCK "★6축 유예 후 옛 ASCII 넷만 → 차단 (같은 글자가 다른 축을 가리키므로 그대로 두면 무음 재해석)"
147
+
148
+ S5=$(mk "$D_NEW" 'axes-run: a=codex b=CI배선 c=none d=되돌림
149
+ controls: alive — ok')
150
+ check "$S5" PASS "★컨트롤: 6축 유예 **이전** 마커는 옛 ASCII 넷으로 그대로 통과 (소급 차단 없음)"
151
+
152
+ S6=$(mk 2026-08-17 'axes-run: a=codex b=CI c=none d=되돌림
153
+ controls: alive — ok')
154
+ check "$S6" BLOCK "★6축 유예일 **당일**은 요구한다 (경계가 < 임을 고정 — 상수를 바꾸면 빨개진다)"
155
+
156
+ S7=$(mk "$D_SIX" 'axes-run: ⓐ=none ⓑ=→standpoint ⓒ=none ⓓ=none ⓔ=none ⓕ=none
157
+ controls: n/a — 이번 델타에 측정이 없다')
158
+ check "$S7" BLOCK "★ⓑ=→standpoint 인데 standpoint: 줄이 없음 → 차단 (죽은 포인터)"
159
+
160
+ # ★ fail-open 컨트롤 — 초판은 화살표(→)를 필수로 봤고, 자기 프로브가 `ⓑ=standpoint`(화살표 없음)이
161
+ # 검사를 통째로 비껴가는 것을 실측했다. 죽은 포인터가 표기 하나로 통과하면 그건 fail-open 이다.
162
+ S7b=$(mk "$D_SIX" 'axes-run: ⓐ=none ⓑ=standpoint ⓒ=none ⓓ=none ⓔ=none ⓕ=none
163
+ controls: n/a — 측정 없음')
164
+ check "$S7b" BLOCK "★화살표 없는 ⓑ=standpoint 도 차단 (표기 하나로 검사를 비껴가면 fail-open)"
165
+
166
+ S7c=$(mk "$D_SIX" 'axes-run: ⓐ=none ⓑ=→standpoint(qasp) ⓒ=none ⓓ=none ⓔ=none ⓕ=none
167
+ controls: n/a — 측정 없음
168
+ standpoint: tier1b(qasp)')
169
+ check "$S7c" PASS "★컨트롤: 포인터에 대상이 붙어도 standpoint: 줄이 있으면 통과 (과차단 방지)"
170
+
171
+ S8=$(mk "$D_SIX" 'axes-run: ⓐ=codex ⓑ=→standpoint ⓒ=none ⓓ=none ⓔ=실사용 ⓕ=되돌림
172
+ axes-run: ⓐ=거짓말 ⓑ=x ⓒ=x ⓓ=x ⓔ=x ⓕ=x
173
+ controls: alive — ok
174
+ standpoint: tier1')
175
+ check "$S8" BLOCK "★axes-run 줄이 둘 → 차단 (첫 줄만 읽히므로 둘째 줄은 «없다»가 아니라 «안 보인다»)"
176
+
93
177
  # ── ★ 배선 판별자 — 위 레인들은 **함수만** 본다 ──────────────────────────────
94
178
  # 실측 2026-08-09: 훅의 호출부에서 `&& validate_marker_axes_run "$MARKER"` 를 떼고
95
179
  # 위 11 레인을 돌렸더니 **전부 초록**이었다. 함수는 멀쩡하고 아무도 안 부르는 상태를
@@ -55,8 +55,38 @@ mk 'crossfamily: panel(codex,gemini) — R1..R3, 9 findings, 8 fixed 1 refuted,
55
55
  check k1 PASS "panel() with family list + verdict"
56
56
  mk 'crossfamily: panel(codex, gemini) — spaced list, the S-grade over-block\n' k1b
57
57
  check k1b PASS "panel(codex, gemini) — SPACE after comma (was truncated to 'panel(codex,')"
58
+ # ⚠️ CONTRACT CHANGE 2026-08-16 — `declined` now requires grounds. It used to pass BARE, which is
59
+ # what the old k3 asserted. That was the enum's soft spot: `declined` means the OPERATOR declined
60
+ # sidecars (UAP), a chosen floor — a factual, attributable claim — and it was the ONLY value with no
61
+ # grounds requirement, so an author who had merely judged decorrelation unnecessary reached for the
62
+ # nearest permissive token and passed clean. Measured in this repo the same day, by a session that
63
+ # had corrected a different marker's crossfamily value hours earlier and had the correct semantics
64
+ # printed to it in the hook's own error text. A closed enum stops prose from collapsing distinct
65
+ # states; it does not stop a WRONG token from being a VALID one — that is what these three lanes are.
66
+ # The old bare-declined expectation is not deleted silently; it is inverted here with its reason.
58
67
  mk 'crossfamily: declined\n' k3
59
- check k3 PASS "declined (operator chose this floor not a degrade)"
68
+ check k3 BLOCK "declined BARE — no grounds (was PASS until 2026-08-16; an operator decision has a record)"
69
+ mk 'crossfamily: declined — operator declined sidecars, chosen floor, per knowledge/shared/rules/operational_adaptation.md\n' k3b
70
+ check k3b PASS "declined + a record path that RESOLVES (the documented form)"
71
+ # The real defect, verbatim-shaped: a genuine authorial judgment, plausibly worded, wrong token.
72
+ # This is the known-POSITIVE that is not synthetic — it is the sentence that actually shipped.
73
+ mk 'crossfamily: declined — this is a small, mechanically-verifiable hook-wiring fix, not a verdict-enum change; the standpoint axis is the decorrelation applied here\n' k3c
74
+ check k3c BLOCK "declined + AUTHOR-judgment grounds — a choice with a panel reachable is DEGRADED_PANEL_UNUSED"
75
+ # ── the three a cross-family review (codex/gpt-5.6-terra) found against the FIRST version of this
76
+ # lane, which grepped for vocabulary (operator|uap|consent|…). All three reproduced on the spot,
77
+ # which is why the check is now a RESOLVABLE-RECORD test rather than a word list.
78
+ mk 'crossfamily: declined — declined because I judged it unnecessary myself\n' k3d
79
+ check k3d BLOCK "SELF-VALIDATING: echoing the value satisfied the old word list ('declin')"
80
+ mk 'crossfamily: declined — operator documentation says authors may choose freely\n' k3e
81
+ check k3e BLOCK "VACUOUS: carries 'operator' but describes no declination at all"
82
+ mk 'crossfamily: declined — operator declined sidecars, see tracks/_meta/nonexistent_record.md\n' k3f
83
+ check k3f BLOCK "FABRICATED PATH: cites a record that does not exist on disk"
84
+ # ⚠️ INSTRUMENT NOTE, recorded because it nearly produced a false verdict on this very lane:
85
+ # the first attempt to known-pair these ran the extracted function under **zsh** (this operator's
86
+ # interactive shell) while the hook runs under **bash**. zsh does not word-split an unquoted
87
+ # parameter expansion, so the `for _tok in $reason` scan saw ONE token and the legitimate
88
+ # path-citing case scored a false BLOCK — the code was correct and the harness was wrong.
89
+ # Re-run under bash: 7/7. If you hand-test this function, invoke it with `bash`, not the login shell.
60
90
  mk 'crossfamily: DEGRADED_SINGLE_FAMILY — probed codex/agy/4090, 0 capable reachable\n' k4
61
91
  check k4 PASS "DEGRADED_SINGLE_FAMILY + substantive reason"
62
92
  mk 'crossfamily: UNKNOWN — consent unset, panel not probed this round\n' k5