@chrono-meta/fh-gate 2.2.0 โ†’ 2.4.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 (38) hide show
  1. package/.claude/capabilities/adapters/qasp-new-code-anchor.cap +5 -2
  2. package/.claude/capabilities/adapters/qasp-web-rules.cap +130 -0
  3. package/.claude/rules/fh_4axis_gate.md +55 -0
  4. package/.claude-plugin/marketplace.json +2 -2
  5. package/CLAUDE.md +51 -6
  6. package/README.ja.md +14 -2
  7. package/README.ko.md +14 -1
  8. package/README.md +16 -2
  9. package/README.zh.md +12 -1
  10. package/knowledge/shared/harness-core/field_verdict_crossfamily_gate.md +231 -7
  11. package/knowledge/shared/harness-core/harness_incubator_doctrine.md +215 -1
  12. package/knowledge/shared/harness-core/ship_readiness_gate.md +168 -1
  13. package/knowledge/shared/learnings/subagent_invocations_log.yaml +24 -0
  14. package/package.json +9 -1
  15. package/plugins/fh-commons/.claude-plugin/plugin.json +1 -1
  16. package/plugins/fh-meta/.claude-plugin/plugin.json +1 -1
  17. package/plugins/fh-meta/CHANGELOG.md +110 -0
  18. package/plugins/fh-meta/skills/auto-decorrelation/SKILL.md +20 -0
  19. package/plugins/fh-meta/skills/steel-quench/SKILL.md +25 -2
  20. package/scripts/adapters/fixtures/qasp_web_rules_known_negative.json +7 -0
  21. package/scripts/adapters/fixtures/qasp_web_rules_known_positive.json +7 -0
  22. package/scripts/adapters/mate_agent_boundary.sh +27 -7
  23. package/scripts/adapters/qasp_web_rules.sh +171 -0
  24. package/scripts/capability_effect_probe.sh +104 -0
  25. package/scripts/capability_registry_check.sh +69 -3
  26. package/scripts/relay_channel.sh +151 -6
  27. package/scripts/selfcheck.sh +18 -0
  28. package/scripts/target_freeze.sh +239 -0
  29. package/scripts/test_adapter_lanes.sh +54 -1
  30. package/scripts/test_marker_axes_run_lanes.sh +19 -0
  31. package/scripts/test_marker_standpoint_lanes.sh +83 -0
  32. package/scripts/test_marker_thirdparty_lanes.sh +73 -0
  33. package/scripts/test_regression_guard_ci_lanes.sh +170 -0
  34. package/scripts/test_relay_channel_lanes.sh +146 -17
  35. package/scripts/test_target_freeze_lanes.sh +203 -0
  36. package/templates/.git-hooks/pre-commit +262 -0
  37. package/templates/PRE-PUBLISH-CHECKLIST.md +22 -0
  38. package/templates/regression_guard.sh +32 -4
@@ -0,0 +1,203 @@
1
+ #!/usr/bin/env bash
2
+ # test_target_freeze_lanes.sh โ€” known pairs for scripts/target_freeze.sh
3
+ #
4
+ # ๐ŸŸฅ ์ด ๋ ˆ์ธ์ด ์‹ค์ œ๋กœ ์žฌ๋Š” ๊ฒƒ: ใ€Œ๋Œ€์ƒ์ด ์›€์ง์˜€์Œ์„ **๊ฐ€๋ฅด๋Š”๊ฐ€**ใ€. ์–‘์„ฑ๋งŒ ์žฌ๋ฉด ยซํ•ญ์ƒ ๋‹ค๋ฅด๋‹ค๊ณ 
5
+ # ํ•˜๋Š” ๊ณ„๊ธฐยป ์™€ ๊ตฌ๋ถ„๋˜์ง€ ์•Š๊ณ , ๊ทธ ๊ณ„๊ธฐ๋Š” ๋งค ๋ผ์šด๋“œ ํŒ์ •์„ ๋ฌดํšจํ™”ํ•ด ์•„๋ฌด๋„ ์•ˆ ์“ฐ๊ฒŒ ๋œ๋‹ค
6
+ # (= ์„ ํ–‰ ์‹คํŒจ โ‘ ์˜ ๋ฐฐ๊ฒฝ-์†Œ์Œ ๊ฒฝ๋กœ). ๊ทธ๋ž˜์„œ **๋ณต์› ํ›„ MATCH** ๋ ˆ์ธ์ด ์ด ํŒŒ์ผ์˜ ํ•ต์‹ฌ์ด๋‹ค.
7
+ #
8
+ # ๐ŸŸฅ ๊ทธ๋ฆฌ๊ณ  ์ด ์Šคํฌ๋ฆฝํŠธ๊ฐ€ ์กด์žฌํ•˜๋Š” ์ด์œ ์ธ **์„ ํ–‰ ์‹คํŒจ 2์ข…**์„ ๊ฐ๊ฐ ํ•œ ๋ ˆ์ธ์œผ๋กœ ๊ณ ์ •ํ•œ๋‹ค:
9
+ # A ์ค„ ์ˆ˜๊ฐ€ ๊ฐ™์€ in-place ํŽธ์ง‘ โ€” `base-SHA + diff ์ค„ ์ˆ˜` ์ง€๋ฌธ์ด ๋ชป ์žก๋˜ ๊ฒƒ
10
+ # B untracked ํŒŒ์ผ ์ถ”๊ฐ€ โ€” ๊ฐ™์€ ์ง€๋ฌธ์ด ๊ตฌ์กฐ์ ์œผ๋กœ ๋งน๋ชฉ์ด๋˜ ๊ฒƒ
11
+ # ์ด ๋‘˜์ด ๋น ์ง€๋ฉด 3๋ฒˆ์งธ ์‹œ๋„๋„ ๊ฐ™์€ ์ž๋ฆฌ์—์„œ ๋ฌด๋„ˆ์ง„๋‹ค.
12
+
13
+ set -u
14
+ T="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/target_freeze.sh"
15
+ pass=0; fail=0
16
+ WORK=$(mktemp -d)
17
+ trap 'rm -rf "$WORK"' EXIT
18
+ R="$WORK/repo"
19
+
20
+ # run <label> <expect MATCH|WRONG|UNKNOWN> <cmd...>
21
+ run() {
22
+ local label="$1" want="$2"; shift 2
23
+ local out rc got
24
+ out=$("$@" 2>&1); rc=$?
25
+ case "$rc" in
26
+ 0) got=MATCH ;;
27
+ 1) got=WRONG ;;
28
+ 10) got=UNKNOWN ;;
29
+ *) got="rc$rc" ;;
30
+ esac
31
+ if [ "$got" = "$want" ]; then
32
+ printf ' โœ… %-52s %s (expected %s)\n' "$label" "$got" "$want"; pass=$((pass+1))
33
+ else
34
+ printf ' โŒ %-52s %s (expected %s)\n out: %s\n' "$label" "$got" "$want" "$out"; fail=$((fail+1))
35
+ fi
36
+ }
37
+
38
+ echo "[target-freeze] fixture build"
39
+ mkdir -p "$R" && git init -q "$R"
40
+ git -C "$R" config user.email t@t; git -C "$R" config user.name t
41
+ printf 'a\n' >"$R/a.txt"
42
+ git -C "$R" add -A && git -C "$R" commit -qm init
43
+
44
+ echo "[target-freeze] lanes"
45
+ run "pin (์ตœ์ดˆ)" MATCH bash "$T" pin "$R" demo
46
+
47
+ # โ”€โ”€ A. ์„ ํ–‰ ์‹คํŒจ โ‘ก-1: ์ค„ ์ˆ˜ ๋ถˆ๋ณ€ in-place ํŽธ์ง‘ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
48
+ printf 'b\n' >"$R/a.txt"
49
+ run "A ์ค„ ์ˆ˜ ๊ฐ™์€ in-place ํŽธ์ง‘ โ†’ WRONG" WRONG bash "$T" verify "$R" demo
50
+ printf 'a\n' >"$R/a.txt"
51
+
52
+ # โ”€โ”€ C. ํŒ๋ณ„๋ ฅ ์ปจํŠธ๋กค โ€” ๋˜๋Œ๋ฆฌ๋ฉด ๋‹ค์‹œ MATCH (ยซํ•ญ์ƒ ๋‹ค๋ฅด๋‹คยป ๊ณ„๊ธฐ๊ฐ€ ์•„๋‹˜) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
53
+ run "C ๋ณต์› ํ›„ โ†’ MATCH (ํŒ๋ณ„๋ ฅ ์ปจํŠธ๋กค)" MATCH bash "$T" verify "$R" demo
54
+
55
+ # โ”€โ”€ B. ์„ ํ–‰ ์‹คํŒจ โ‘ก-2: untracked ์ถ”๊ฐ€ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
56
+ printf 'new\n' >"$R/untracked.txt"
57
+ run "B untracked ์ถ”๊ฐ€ โ†’ WRONG" WRONG bash "$T" verify "$R" demo
58
+ rm -f "$R/untracked.txt"
59
+ run "B ์ œ๊ฑฐ ํ›„ โ†’ MATCH" MATCH bash "$T" verify "$R" demo
60
+
61
+ # โ”€โ”€ gitignore ๋œ ํŒŒ์ผ์€ ๋Œ€์ƒ์ด ์•„๋‹ˆ๋‹ค (์˜คํƒ ๊ฒฝ๋กœ โ€” ์‚ฐ์ถœ๋ฌผ ๋””๋ ‰ํ† ๋ฆฌ๊ฐ€ ๋งค ๋Ÿฐ ๋ฐ”๋€๋‹ค) โ”€โ”€
62
+ printf 'out/\n' >"$R/.gitignore"
63
+ git -C "$R" add .gitignore && git -C "$R" commit -qm ignore
64
+ run "pin (ignore ์ปค๋ฐ‹ ํ›„ ์žฌํ•€)" MATCH bash "$T" pin "$R" demo
65
+ mkdir -p "$R/out" && printf 'noise\n' >"$R/out/run.log"
66
+ run "ignored ์‚ฐ์ถœ๋ฌผ ์ƒ์„ฑ โ†’ MATCH (์˜คํƒ ์—†์Œ)" MATCH bash "$T" verify "$R" demo
67
+
68
+ # โ”€โ”€ โ“‘ ์€ํ ๋น„ํŠธ: ์žด ์ˆ˜ ์—†์œผ๋ฉด ยซ์ผ์น˜ยป ๋ฅผ ๋‚ผ ์ž๊ฒฉ์ด ์—†๋‹ค โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
69
+ git -C "$R" update-index --assume-unchanged a.txt
70
+ printf 'HIDDEN\n' >"$R/a.txt"
71
+ run "assume-unchanged โ†’ UNKNOWN (ํ†ต๊ณผ ์•„๋‹˜)" UNKNOWN bash "$T" verify "$R" demo
72
+ git -C "$R" update-index --no-assume-unchanged a.txt
73
+ printf 'a\n' >"$R/a.txt"
74
+
75
+ # โ”€โ”€ โ“’ ๋ฏธ์ธก์ •์„ ํ†ต๊ณผ๋กœ ์ ‘์ง€ ์•Š๋Š”๋‹ค โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
76
+ run "ํ•€ ์—†๋Š” ๋ผ๋ฒจ โ†’ UNKNOWN" UNKNOWN bash "$T" verify "$R" nolabel
77
+ run "git ๋ ˆํฌ ์•„๋‹˜ โ†’ UNKNOWN" UNKNOWN bash "$T" verify "$WORK" demo
78
+
79
+ # โ”€โ”€ ์Šคํ…Œ์ด์ง•๋งŒ ํ•ด๋„ ๋Œ€์ƒ์€ ์›€์ง์ธ ๊ฒƒ์ด๋‹ค โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
80
+ printf 'staged\n' >"$R/a.txt"; git -C "$R" add a.txt
81
+ run "staged ๋ณ€๊ฒฝ โ†’ WRONG" WRONG bash "$T" verify "$R" demo
82
+
83
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
84
+ # cross-family(codex, 2026-08-18)๊ฐ€ **๋šซ์€** ๊ฒฝ๋กœ๋“ค โ€” ์ „๋ถ€ ์žฌํ˜„ ํ™•์ธ ํ›„ ์—ฌ๊ธฐ ๊ณ ์ •ํ•œ๋‹ค.
85
+ # ๐ŸŸฅ ์œ„ 11 ๋ ˆ์ธ์ด ์ดˆ๋ก์ธ ์ฑ„๋กœ ์•„๋ž˜๊ฐ€ ์ „๋ถ€ ํ†ต๊ณผํ–ˆ๋‹ค: ์ปจํŠธ๋กค์ด ์žˆ๋‹ค๋Š” ๊ฒƒ๊ณผ ํŒ๋ณ„๋ ฅ์ด ์žˆ๋‹ค๋Š”
86
+ # ๊ฒƒ์€ ๋‹ค๋ฅด๋‹ค. ์ด ๋ธ”๋ก์ด ์—†์œผ๋ฉด ๋‹ค์Œ ์ˆ˜๋ฆฌ๊ฐ€ ๊ฐ™์€ ์ž๋ฆฌ๋ฅผ ์กฐ์šฉํžˆ ๋‹ค์‹œ ์—ฐ๋‹ค.
87
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
88
+ echo
89
+ echo "[target-freeze] cross-family ๊ฐ€ ๋šซ์€ ๊ฒฝ๋กœ (fail-open 3 ยท ์˜คํƒ 1)"
90
+
91
+ # โ”€โ”€ S: ๋ผ๋ฒจ ๋ณ„์นญ โ€” `a/b` ์™€ `a_b` ๊ฐ€ ๊ฐ™์€ ํ•€ ํŒŒ์ผ์ด๋ฉด ยซํ•€ํ•œ ์  ์—†๋Š” ๋ผ๋ฒจยป ์ด MATCH โ”€โ”€
92
+ bash "$T" pin "$R" a_b >/dev/null 2>&1
93
+ run "S ๋ผ๋ฒจ ๋ณ„์นญ a/b (ํ•€ ์—†์Œ) โ†’ UNKNOWN" UNKNOWN bash "$T" verify "$R" a/b
94
+
95
+ # โ”€โ”€ S: `ls-files -o` ์‹คํŒจ๊ฐ€ ํŒŒ์ดํ”„์— ๋จนํ˜€ ยซuntracked ์—†์Œยป ์œผ๋กœ ์ ‘ํžˆ๋˜ ๊ฒƒ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
96
+ FAKEBIN="$WORK/fakebin"; mkdir -p "$FAKEBIN"
97
+ REALGIT=$(command -v git)
98
+ printf '#!/usr/bin/env bash
99
+ if [ "$1" = "-C" ] && [ "$3" = "ls-files" ] && [ "$4" = "-o" ]; then exit 42; fi
100
+ exec %q "$@"
101
+ ' "$REALGIT" >"$FAKEBIN/git"
102
+ chmod +x "$FAKEBIN/git"
103
+ bash "$T" pin "$R" lsfail >/dev/null
104
+ printf 'new
105
+ ' >"$R/u2.txt"
106
+ run "S ls-files -o ๊ณ ์žฅ โ†’ UNKNOWN (MATCH ์•„๋‹˜)" UNKNOWN env PATH="$FAKEBIN:$PATH" bash "$T" verify "$R" lsfail
107
+ rm -f "$R/u2.txt"
108
+
109
+ # โ”€โ”€ S: untracked ์ค‘์ฒฉ git repo โ€” ์•ˆ์˜ ๋‚ด์šฉ ๋ณ€๊ฒฝ์„ ๋ชป ๋ณด๋ฏ€๋กœ **fail-closed** โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
110
+ mkdir -p "$R/nested" && git init -q "$R/nested"
111
+ git -C "$R/nested" config user.email t@t; git -C "$R/nested" config user.name t
112
+ printf 'one
113
+ ' >"$R/nested/x.txt"
114
+ run "S untracked ๋””๋ ‰ํ† ๋ฆฌ โ†’ UNKNOWN (๋ชป ์žฐ๋‹ค๊ณ  ๋งํ•œ๋‹ค)" UNKNOWN bash "$T" pin "$R" nested
115
+ rm -rf "$R/nested"
116
+
117
+ # โ”€โ”€ A: ๊นจ์ง„ ์‹ฌ๋ณผ๋ฆญ ๋งํฌ ์žฌ์ง€์ • โ€” ๋งํฌ๊ฐ€ ๊ฐ€๋ฆฌํ‚ค๋Š” ๋ฌธ์ž์—ด์ด ๋‚ด์šฉ์ด๋‹ค โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
118
+ ln -s missing-one "$R/link"
119
+ run "A ์‹ฌ๋ณผ๋ฆญ ๋งํฌ pin" MATCH bash "$T" pin "$R" symlink
120
+ ln -sfn missing-two "$R/link"
121
+ run "A ๋งํฌ ์žฌ์ง€์ • โ†’ WRONG" WRONG bash "$T" verify "$R" symlink
122
+ ln -sfn missing-one "$R/link"
123
+ run "A ๋งํฌ ๋ณต์› โ†’ MATCH (ํŒ๋ณ„๋ ฅ ์ปจํŠธ๋กค)" MATCH bash "$T" verify "$R" symlink
124
+ rm -f "$R/link"
125
+
126
+ # โ”€โ”€ S: ์„œ๋ธŒ๋ชจ๋“ˆ **์›Œํ‚นํŠธ๋ฆฌ** ๋‚ด์šฉ ๋ณ€๊ฒฝ (์ปค๋ฐ‹ ์—†์ด) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
127
+ # ๐ŸŸฅ `submodule status` ๋งŒ์œผ๋กœ๋Š” ๋ชป ์žก๋Š”๋‹ค โ€” ๊ทธ ์ค„์€ ์„œ๋ธŒ๋ชจ๋“ˆ์˜ HEAD ๋ฅผ ๋งํ•˜๋ฏ€๋กœ,
128
+ # ์ปค๋ฐ‹ ์—†์ด ํŒŒ์ผ๋งŒ ๊ณ ์น˜๋ฉด SHA ๊ฐ€ ๊ทธ๋Œ€๋กœ๋‹ค. ์ˆ˜๋ฆฌ 1์ฐจ๋ณธ์ด ์ •ํ™•ํžˆ ์—ฌ๊ธฐ์„œ ๋šซ๋ ธ๋‹ค.
129
+ SUB="$WORK/sub"; mkdir -p "$SUB" && git init -q "$SUB"
130
+ git -C "$SUB" config user.email t@t; git -C "$SUB" config user.name t
131
+ printf 'v1\n' >"$SUB/s.txt"; git -C "$SUB" add -A; git -C "$SUB" commit -qm sub
132
+ SR="$WORK/subhost"; mkdir -p "$SR" && git init -q "$SR"
133
+ git -C "$SR" config user.email t@t; git -C "$SR" config user.name t
134
+ printf 'a\n' >"$SR/a.txt"; git -C "$SR" add -A; git -C "$SR" commit -qm init
135
+ if git -C "$SR" -c protocol.file.allow=always submodule add -q "$SUB" sub 2>/dev/null; then
136
+ git -C "$SR" commit -qm addsub
137
+ printf 'v2\n' >"$SR/sub/s.txt"
138
+ run "S ์„œ๋ธŒ๋ชจ๋“ˆ pin" MATCH bash "$T" pin "$SR" sm
139
+ printf 'v3\n' >"$SR/sub/s.txt"
140
+ run "S ์„œ๋ธŒ๋ชจ๋“ˆ ์›Œํ‚นํŠธ๋ฆฌ ๋ณ€๊ฒฝ โ†’ WRONG" WRONG bash "$T" verify "$SR" sm
141
+ printf 'v2\n' >"$SR/sub/s.txt"
142
+ run "S ์„œ๋ธŒ๋ชจ๋“ˆ ๋ณต์› โ†’ MATCH (ํŒ๋ณ„๋ ฅ ์ปจํŠธ๋กค)" MATCH bash "$T" verify "$SR" sm
143
+ else
144
+ printf ' โš ๏ธ ์„œ๋ธŒ๋ชจ๋“ˆ ๋ ˆ์ธ skip โ€” ์ด git ์ด file:// ์„œ๋ธŒ๋ชจ๋“ˆ์„ ๊ฑฐ๋ถ€ํ•œ๋‹ค(๋ฏธ์ธก์ •, ํ†ต๊ณผ ์•„๋‹˜)\n'
145
+ fi
146
+
147
+ # โ”€โ”€ A: ์˜คํƒ โ€” ๋นˆ ์ปค๋ฐ‹์€ **๋‚ด์šฉ์„ ์•ˆ ๋ฐ”๊พผ๋‹ค**. ํŠธ๋ฆฌ ํ•ด์‹œ๋ฅผ ์“ฐ๋ฏ€๋กœ MATCH ์—ฌ์•ผ ํ•œ๋‹ค โ”€โ”€โ”€โ”€
148
+ # โ˜…์•ž ๋ ˆ์ธ์ด ์Šคํ…Œ์ด์ง•์„ ๋‚จ๊ฒจ ๋’€๋‹ค. ๊ทธ๋Œ€๋กœ `--allow-empty` ํ•˜๋ฉด **๊ทธ ์Šคํ…Œ์ด์ง•์ด ์ปค๋ฐ‹๋˜์–ด**
149
+ # ํŠธ๋ฆฌ๊ฐ€ ์‹ค์ œ๋กœ ๋ฐ”๋€๋‹ค โ€” ์ดˆํŒ์—์„œ ์ด ๋ ˆ์ธ์ด ๋นจ๊ฐฐ๋˜ ์ด์œ ๋Š” ์Šคํฌ๋ฆฝํŠธ๊ฐ€ ์•„๋‹ˆ๋ผ ํ”ฝ์Šค์ฒ˜์˜€๋‹ค.
150
+ git -C "$R" reset -q --hard HEAD
151
+ bash "$T" pin "$R" emptycommit >/dev/null
152
+ git -C "$R" commit --allow-empty -qm noop
153
+ run "A ๋นˆ ์ปค๋ฐ‹(๊ฐ™์€ tree) โ†’ MATCH (์˜คํƒ ์•„๋‹˜)" MATCH bash "$T" verify "$R" emptycommit
154
+
155
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
156
+ # 2๋ฒˆ์งธ ๊ณ„์—ด(agy, 2026-08-18)์ด **๋‹ค๋ฅธ ์ถ•**์—์„œ ๋šซ์€ ๊ฒƒ โ€” codex ์ง€์  ๋ชฉ๋ก์„ ์ฃผ๊ณ  ยซ๊ทธ ๋ฐ–ยป์„
157
+ # ์š”๊ตฌํ–ˆ๋‹ค. ๊ณ„์—ด์„ ๋‚˜๋ˆˆ ๊ฒƒ์ด ๊ฐ’์„ ๋ƒˆ๋‹ค(๊ฐ™์€ ๊ณ„์—ด ๋‘˜์ด๋ฉด ๊ฐ™์€ ์‚ฌ๊ฐ์ด ๋‚จ๋Š”๋‹ค).
158
+ # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
159
+ echo
160
+ echo "[target-freeze] 2๋ฒˆ์งธ ๊ณ„์—ด์ด ๋šซ์€ ์ถ•"
161
+
162
+ # โ”€โ”€ S: `GIT_DIR` ์ด `git -C` ๋ฅผ ์ด๊ธด๋‹ค โ€” ์—‰๋šฑํ•œ ๋ ˆํฌ๋ฅผ ์žฌ๊ณ  ยซ๋™๊ฒฐํ–ˆ๋‹คยป ๊ณ  ๋ณด๊ณ ํ•˜๋˜ ๊ฒƒ โ”€โ”€
163
+ OTHER="$WORK/other"; mkdir -p "$OTHER" && git init -q "$OTHER"
164
+ git -C "$OTHER" config user.email t@t; git -C "$OTHER" config user.name t
165
+ printf 'other\n' >"$OTHER/o.txt"; git -C "$OTHER" add -A; git -C "$OTHER" commit -qm other
166
+ git -C "$R" reset -q --hard HEAD
167
+ bash "$T" pin "$R" gitdir >/dev/null
168
+ printf 'moved\n' >"$R/a.txt"
169
+ # ํ™˜๊ฒฝ๋ณ€์ˆ˜๊ฐ€ ์‚ด์•„ ์žˆ์œผ๋ฉด $R ์ด ์•„๋‹ˆ๋ผ $OTHER ๋ฅผ ์žฌ์„œ ยซ๋ณ€ํ™” ์—†์Œยป(MATCH)์ด ๋‚˜์™”๋‹ค.
170
+ run "S GIT_DIR ์˜ค์—ผ ์ƒํƒœ์—์„œ๋„ ๋Œ€์ƒ์€ R โ†’ WRONG" WRONG \
171
+ env GIT_DIR="$OTHER/.git" GIT_WORK_TREE="$OTHER" bash "$T" verify "$R" gitdir
172
+ git -C "$R" checkout -q -- a.txt
173
+ run "S ์˜ค์—ผ ์ œ๊ฑฐ ํ›„ ๋ณต์› โ†’ MATCH (ํŒ๋ณ„๋ ฅ ์ปจํŠธ๋กค)" MATCH bash "$T" verify "$R" gitdir
174
+
175
+ # โ”€โ”€ S: `_sha` ๊ฐ€ `| awk` ๋กœ rc ๋ฅผ ์žƒ๋˜ ๊ฒƒ โ€” ํ•ด์‹œ ์‹คํŒจ๊ฐ€ ยซ๋นˆ ์ง€๋ฌธยป ์œผ๋กœ ์ ‘ํžˆ๋ฉด MATCH โ”€โ”€โ”€โ”€โ”€โ”€
176
+ # ๐ŸŸฅ ์ด ๋ ˆ์ธ์€ **๋˜๋Œ๋ฆผ ํ”„๋กœ๋ธŒ๊ฐ€ ๋งŒ๋“ค์—ˆ๋‹ค**: `_sha` ์˜ rc ๊ฒ€์‚ฌ๋ฅผ ๋˜๋Œ๋ ธ๋Š”๋ฐ 26 ๋ ˆ์ธ์ด
177
+ # ์ „๋ถ€ ์ดˆ๋ก์ด์—ˆ๋‹ค = ๊ทธ ์ˆ˜๋ฆฌ๋ฅผ ๋ฎ๋Š” ์•ต์ปค๊ฐ€ ํ•˜๋‚˜๋„ ์—†์—ˆ๋‹ค(์žฅ์‹ ์•ต์ปค). ๊ณ„๊ธฐ๊ฐ€ ์Šค์Šค๋กœ๋ฅผ
178
+ # ๋ชป ์žฌ๋Š” ์ž๋ฆฌ๋ฅผ ๋˜๋Œ๋ฆผ์ด ์งš์–ด๋‚ธ ๊ฒƒ์ด๋‹ค.
179
+ SHABIN="$WORK/shabin"; mkdir -p "$SHABIN"
180
+ REALSHA=$(command -v shasum || true)
181
+ if [ -n "$REALSHA" ]; then
182
+ printf '#!/usr/bin/env bash\ninput=$(cat)\ncase "$input" in boom*) exit 7 ;; esac\nprintf %%s "$input" | %q "$@"\n' "$REALSHA" >"$SHABIN/shasum"
183
+ chmod +x "$SHABIN/shasum"
184
+ git -C "$R" reset -q --hard HEAD
185
+ printf 'boom1\n' >"$R/u4.txt"
186
+ run "S ํ•ด์‹œ ๋„๊ตฌ ์‹คํŒจ โ†’ UNKNOWN (pin)" UNKNOWN env PATH="$SHABIN:$PATH" bash "$T" pin "$R" shafail
187
+ rm -f "$R/u4.txt"
188
+ run "S ์ •์ƒ ๋„๊ตฌ๋กœ๋Š” pin ๋จ (ํŒ๋ณ„๋ ฅ ์ปจํŠธ๋กค)" MATCH bash "$T" pin "$R" shafail
189
+ else
190
+ printf ' โš ๏ธ _sha ๋ ˆ์ธ skip โ€” shasum ๋ถ€์žฌ(๋ฏธ์ธก์ •, ํ†ต๊ณผ ์•„๋‹˜)\n'
191
+ fi
192
+
193
+ # โ”€โ”€ B: untracked ํŒŒ์ผ์˜ ์‹คํ–‰ ๋น„ํŠธ โ€” ์ด ๋ ˆํฌ๊ฐ€ ์ด๋ฏธ ๋ฐ์ธ ์ถ• โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
194
+ printf '#!/bin/sh\necho hi\n' >"$R/u3.sh"; chmod 644 "$R/u3.sh"
195
+ run "B ์‹คํ–‰๋น„ํŠธ pin" MATCH bash "$T" pin "$R" execbit
196
+ chmod +x "$R/u3.sh"
197
+ run "B chmod +x โ†’ WRONG (๋‚ด์šฉ์€ ๊ทธ๋Œ€๋กœ๋‹ค)" WRONG bash "$T" verify "$R" execbit
198
+ chmod 644 "$R/u3.sh"
199
+ run "B ๋น„ํŠธ ๋ณต์› โ†’ MATCH (ํŒ๋ณ„๋ ฅ ์ปจํŠธ๋กค)" MATCH bash "$T" verify "$R" execbit
200
+ rm -f "$R/u3.sh"
201
+
202
+ printf '\n[target-freeze] pass=%d fail=%d\n' "$pass" "$fail"
203
+ [ "$fail" -eq 0 ]
@@ -717,6 +717,217 @@ validate_crossfamily_leg() { # $1 = marker path
717
717
  return 0
718
718
  }
719
719
 
720
+ # โ”€โ”€ standpoint: typed value lane (2026-08-17) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
721
+ # Grammar: standpoint: <VALUE>[ โ€” <reason>]
722
+ #
723
+ # WHY NOW. This field shipped 2026-08-14 with its value DELIBERATELY unvalidated โ€” the doctrine
724
+ # said "mechanize on the first recorded false value, not before". That value is now on record:
725
+ # a `release_2.3.0` marker wrote `not-applicable` on a delta whose OWN grounds line concedes
726
+ # "์†Œ๋น„์ž install ์˜ ๊ฒŒ์ดํŠธ ์ˆ˜์šฉ์€ ๋ฐ”๋€๋‹ค (BREAKING 2๊ฑด)", and two 2026-08-14 deltas that changed
727
+ # shipped gate scripts / a shipped SKILL.md carried no line at all. The threshold this field set
728
+ # for itself is met.
729
+ #
730
+ # SCOPE โ€” channel, not judgment (CLAUDE.md ยงMechanization Boundary). This asserts properties of the
731
+ # RECORD: present ยท single ยท a member of the closed enum ยท non-vacuous ยท not wearing the OTHER
732
+ # axis's tokens. It never asserts the value is CORRECT โ€” that stays with the reader, by design.
733
+ #
734
+ # ๐ŸŸฅ NAMED RESIDUAL โ€” the EXECUTION claim warns, it does not block. tier2/tier2b/tier3 assert that
735
+ # something was RUN in the target, which ยง7 calls "the load-bearing half". Blocking on that needs a
736
+ # vocabulary grep, and on first contact with the real corpus that grep over-blocked a legitimate
737
+ # marker whose grounds read "๊ทธ ๋ ˆํฌ์—์„œ ์‹ค์ œ๋กœ ํ˜ธ์ถœํ•ด ์–‘ยท์Œ arm ์„ ํ™•์ธํ–ˆ๋‹ค" โ€” it did not know ใ€Œํ˜ธ์ถœใ€.
738
+ # Over-blocking trains `--no-verify`, which would disarm the Destructive-Op gate in this same hook.
739
+ # So the cost is stated instead of hidden: a fabricated tier2 with a fluent reason PASSES this lane.
740
+ # Closing that is ยง4-b's job (cross-family reads the marker), not this lane's.
741
+ #
742
+ # ๐ŸŸฅ DOGFOOD RESULT, STATED SO NOBODY READS THIS AS MORE THAN IT IS โ€” the very marker whose false
743
+ # value MET this field's mechanization threshold (`release_2.3.0`, which wrote `not-applicable` on a
744
+ # release delta) **PASSES this lane**, because its grounds sentence is long and specific. That is not
745
+ # a bug in the lane; it is the Mechanization Boundary holding: the channel is checked, the judgment
746
+ # is not. Anyone reporting that this wiring "closed the self-report axis" is wrong. What closed is
747
+ # the shape of the record; what remains open is whether the recorded answer is true.
748
+ #
749
+ # Calibration before wiring (2026-08-17): known-pair 12/12 both directions; run against all 24
750
+ # corpus markers carrying the field โ†’ 7 blocked, every one a real defect (6 bare `not-applicable`,
751
+ # independently matching a hand count, + 1 marker carrying TWO contradictory standpoint lines),
752
+ # and 0 over-blocks. Anchor: scripts/test_marker_standpoint_lanes.sh
753
+ validate_standpoint_leg() { # $1 = marker path
754
+ local m="$1" line val reason n quoted=""
755
+ n=$(grep -cE '^[[:space:]]*standpoint:' "$m" 2>/dev/null); n=${n:-0}
756
+ if [ "$n" -eq 0 ]; then
757
+ echo " โŒ FAIL โ€” load-bearing file staged with no 'standpoint:' line in the Axes 2-3 marker."
758
+ echo " State the answer, whatever it is โ€” the gate blocks silence, not a modest value."
759
+ echo " Settle the TARGET CLASS first (ยง7 Q0), then the tier:"
760
+ echo " standpoint: not-applicable โ€” <what you checked to conclude no target exists>"
761
+ echo " standpoint: tier1b(pmh-dev) โ€” read the target's own files, executed nothing"
762
+ echo " standpoint: tier2(qasp-dev) โ€” ran <command> there, saw <output>"
763
+ echo " standpoint: DEGRADED_NOT_RUN โ€” target reachable, not run (<why>)"
764
+ echo " Append one line to: $m"
765
+ return 1
766
+ fi
767
+ # Duplicate guard โ€” same defect class the crossfamily lane closes: a marker is append-only in
768
+ # practice and readers take the FIRST match, so an appended correction is silently shadowed.
769
+ # Measured in this corpus: one marker carries `tier1` at :32 and `not-applicable` at :89.
770
+ if [ "$n" -gt 1 ]; then
771
+ echo " โŒ FAIL โ€” marker carries MORE THAN ONE 'standpoint:' line."
772
+ grep -nE '^[[:space:]]*standpoint:' "$m" | sed 's/^/ /'
773
+ echo " A later line does not supersede an earlier one โ€” the reader takes the first."
774
+ echo " Delete the stale line and leave exactly one."
775
+ return 1
776
+ fi
777
+ line=$(grep -m1 -E '^[[:space:]]*standpoint:' "$m" | sed -E 's/^[[:space:]]*standpoint:[[:space:]]*//')
778
+ case "$line" in '"'*|"'"*) quoted=" [format: value wrapped in quotes โ€” an auditor's grep for
779
+ 'standpoint: tier2' misses it; drop the quotes next time]";; esac
780
+ line=$(printf '%s' "$line" | sed -E 's/^["'"'"']//; s/["'"'"']$//')
781
+ val=$(printf '%s' "$line" | sed -E 's/[[:space:]]*(โ€”|--).*$//' | sed -E 's/[[:space:]]+$//')
782
+ reason=$(printf '%s' "$line" | sed -E 's/^[^โ€”-]*((โ€”|--)[[:space:]]*)?//')
783
+ # Cross-axis contamination. `crossfamily:` and `standpoint:` each carry a three-way degrade
784
+ # triad and the doctrine says the literals are DISTINCT ON PURPOSE. Borrowing the other axis's
785
+ # token records the wrong axis while looking well-formed.
786
+ case "$val" in
787
+ DEGRADED_SINGLE_FAMILY*|DEGRADED_PANEL_UNUSED*|panel\(*|declined)
788
+ echo " โŒ FAIL โ€” '$val' belongs to the crossfamily: axis, not standpoint:."
789
+ echo " crossfamily = WHICH MODEL FAMILY reviewed. standpoint = WHOSE REPO was ground truth."
790
+ echo " standpoint's own triad: DEGRADED_NO_TARGET_ACCESS (could not) /"
791
+ echo " DEGRADED_NOT_RUN (did not) / UNKNOWN (did not look)."
792
+ return 1 ;;
793
+ esac
794
+ case "$val" in
795
+ tier1)
796
+ echo " โœ… standpoint leg: tier1 (content-only โ€” a scoping fact, not a failure)$quoted" ;;
797
+ tier1b\(*\)|tier2\(*\)|tier2b\(*\)|tier3\(*\))
798
+ case "$val" in
799
+ tier2\(*\)|tier2b\(*\)|tier3\(*\))
800
+ if [ "${#reason}" -lt 20 ] || ! printf '%s' "$reason" \
801
+ | grep -qiE 'ran |run |์‹คํ–‰|๋Œ๋ ธ|ํ˜ธ์ถœ|invok|call|rc=|exit |output|์ถœ๋ ฅ|suite|์Šค์œ„ํŠธ|test|bash |grep |npm |pytest|command|์ปค๋งจ๋“œ|arm'; then
802
+ echo " โš ๏ธ standpoint leg: $val โ€” recorded, but the grounds do not NAME an execution."
803
+ echo " ${val%%(*} asserts code RAN in the target. The discriminator is mechanical:"
804
+ echo " name the command you ran and the output you saw. If you only read files,"
805
+ echo " the honest rung is tier1b โ€” weaker on purpose, so the owed arm stays visible."
806
+ echo " Advisory by design (see the named residual above this function)."
807
+ else
808
+ echo " โœ… standpoint leg: $val$quoted"
809
+ fi ;;
810
+ *) echo " โœ… standpoint leg: $val$quoted" ;;
811
+ esac ;;
812
+ not-applicable)
813
+ # ยง7: asserting non-applicability without naming what was checked is indistinguishable from
814
+ # UNKNOWN wearing a permissive label. Measured: 6 bare instances in this corpus.
815
+ if [ "${#reason}" -lt 20 ]; then
816
+ echo " โŒ FAIL โ€” bare 'not-applicable' with no grounds on the same line."
817
+ echo " That is UNKNOWN wearing a permissive label. Name what you checked:"
818
+ echo " no named peer carries this surface AND it changes no consumer-visible behavior."
819
+ return 1
820
+ fi
821
+ echo " โœ… standpoint leg: not-applicable$quoted" ;;
822
+ DEGRADED_NO_TARGET_ACCESS*|DEGRADED_NOT_RUN*|UNKNOWN*)
823
+ if [ "${#reason}" -lt 20 ]; then
824
+ echo " โŒ FAIL โ€” $val without substantive grounds on the same line."
825
+ echo " could-not / did-not / did-not-look are separate values on purpose; collapsing"
826
+ echo " them renders an unrun probe as a zero finding. Name what you probed."
827
+ return 1
828
+ fi
829
+ echo " โš ๏ธ standpoint leg: $val โ€” recorded, not silent" ;;
830
+ *)
831
+ echo " โŒ FAIL โ€” '$val' is not a member of the standpoint: enum."
832
+ echo " Closed enum: tier1 ยท tier1b(<h>) ยท tier2(<h>) ยท tier2b(<h>) ยท tier3(<h>) ยท"
833
+ echo " not-applicable ยท DEGRADED_NO_TARGET_ACCESS ยท DEGRADED_NOT_RUN ยท UNKNOWN"
834
+ echo " Default-deny: an unrecognised value is UNDECIDABLE, never assumed benign."
835
+ return 1 ;;
836
+ esac
837
+ return 0
838
+ }
839
+
840
+ # โ”€โ”€ thirdparty: typed value lane (2026-08-17) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
841
+ # Grammar: thirdparty: <VALUE>(<grounds>) ยท UNKNOWN takes no parenthetical
842
+ #
843
+ # WHAT THIS AXIS IS โ€” and it has TWO halves, which is what made getting it wrong so easy.
844
+ # The 6-axis canon (fh_three_layer_canon.md:243) defines โ““ by what it RECEIVES: ยซ๋ฌธ์ œ + ๋‚จ์˜
845
+ # ์ฝ”๋“œ๋ฒ ์ด์Šคยป, and it asks two questions:
846
+ # โ‘  prior art โ€” has this claimed-new thing already been solved outside? checked/none-found
847
+ # โ‘ก harness-level โ€” how does my change look from ANOTHER HARNESS's repo and peer-review
848
+ # adversarial persona? (operator: put Sonnet in gstack, wear the gstack
849
+ # persona, review the qasp change โ€” a cross-FAMILY, harness-scoped
850
+ # adversarial review. FH is the GOVERNOR: it creates the situation,
851
+ # observes, and judges. What the 4-axis gate verifies is the OPINION
852
+ # that third harness produced, judged LOCALLY โ€” it does not put a gate
853
+ # on someone else's repo.)
854
+ # It is NOT "I talked to another session". The canonical spec
855
+ # (`.claude/rules/fh_4axis_gate.md`) has said so since the field shipped, and the evidence that
856
+ # created it was 6 presentation claims that turned out to have prior art (mutation testing,
857
+ # promptfoo/DeepEval, an official cross-family plugin, โ€ฆ).
858
+ #
859
+ # WHY NOW โ€” the first recorded false values exist, and they are the author's own. Measured across
860
+ # the marker corpus 2026-08-17: 4 markers used the enum correctly; **2 wrote free prose about
861
+ # peer-session contact** and matched no enum member at all. That is the field-canon failure this
862
+ # repo already names โ€” normalizing a harness term into a general concept ("third party" โ†’ "another
863
+ # session"). Presence was checked; the value was not; nothing caught it.
864
+ #
865
+ # SCOPE โ€” channel, not judgment (CLAUDE.md ยงMechanization Boundary). This asserts the RECORD is a
866
+ # member of the closed enum and names non-vacuous grounds. It never asserts the prior-art search
867
+ # was thorough, nor that what a third party told you is TRUE โ€” ๐ŸŸฅ that second one is deliberate and
868
+ # load-bearing: the axis exists to surface information you could not predict, and the truth of what
869
+ # comes back is adjudicated by the OTHER axes (adversarial ยท grounding ยท first-real-use), which is
870
+ # what the 4-axis gate is for. A wrong answer from a third party is that party's defect, possibly
871
+ # one to go fix; it is not something this field should be asked to certify.
872
+ #
873
+ # TRIGGER โ€” validate IF PRESENT. Presence stays pointer-driven (`โ““=โ†’thirdparty`), deliberately:
874
+ # requiring the field on every load-bearing change would force a prior-art search per commit, and
875
+ # over-blocking trains the override that disarms the Destructive-Op gate in this same hook.
876
+ #
877
+ # Calibration before wiring: the corpus supplied BOTH arms โ€” peer-authored markers 4/4 PASS,
878
+ # author's own malformed 2/2 BLOCK, plus 6 synthetic shapes, 0 over-blocks. An earlier draft DID
879
+ # over-block 3 legitimate markers by looking for the closing paren on the first line when the
880
+ # grounds wrap across lines; the corpus caught it. Anchor: scripts/test_marker_thirdparty_lanes.sh
881
+ validate_thirdparty_leg() { # $1 = marker path
882
+ local m="$1" line val reason n
883
+ n=$(grep -cE '^[[:space:]]*thirdparty:' "$m" 2>/dev/null); n=${n:-0}
884
+ [ "$n" -eq 0 ] && return 0 # absent is governed by the โ““ pointer check above, not here
885
+ if [ "$n" -gt 1 ]; then
886
+ echo " โŒ FAIL โ€” marker carries MORE THAN ONE 'thirdparty:' line."
887
+ grep -nE '^[[:space:]]*thirdparty:' "$m" | sed 's/^/ /'
888
+ echo " Readers take the first, so an appended correction is silently shadowed."
889
+ return 1
890
+ fi
891
+ line=$(grep -m1 -E '^[[:space:]]*thirdparty:' "$m" | sed -E 's/^[[:space:]]*thirdparty:[[:space:]]*//')
892
+ val=$(printf '%s' "$line" | sed -E 's/\(.*$//' | sed -E 's/[[:space:]]*(โ€”|--).*$//' | sed -E 's/[[:space:]]+$//')
893
+ # Cross-axis contamination โ€” standpoint's tiers and crossfamily's tokens are different axes.
894
+ case "$val" in
895
+ tier1|tier1b|tier2|tier2b|tier3|panel|declined|DEGRADED_SINGLE_FAMILY|DEGRADED_PANEL_UNUSED|DEGRADED_NO_TARGET_ACCESS)
896
+ echo " โŒ FAIL โ€” '$val' belongs to another axis, not thirdparty:."
897
+ echo " standpoint = whose repo was ground truth ยท crossfamily = which model family reviewed"
898
+ echo " thirdparty = was this claimed-new thing already done OUTSIDE this repo."
899
+ return 1 ;;
900
+ esac
901
+ case "$val" in
902
+ UNKNOWN)
903
+ echo " โš ๏ธ thirdparty leg: UNKNOWN โ€” ์•ˆ ๋ดค๋‹ค. Recorded, not silent." ;;
904
+ peer-review|checked|none-found|not-applicable|DEGRADED_NO_ACCESS|DEGRADED_NOT_RUN)
905
+ # The parenthetical may wrap across lines; only require that it OPENS with content after it.
906
+ case "$line" in *\(*) ;; *)
907
+ echo " โŒ FAIL โ€” '$val' without a (<grounds>) parenthetical."
908
+ echo " none-found without naming WHAT you searched is indistinguishable from not looking."
909
+ return 1 ;;
910
+ esac
911
+ reason=$(printf '%s' "$line" | sed -E 's/^[^(]*\(//')
912
+ if [ "${#reason}" -lt 12 ]; then
913
+ echo " โŒ FAIL โ€” '$val(...)' grounds are vacuous. Name what was searched and what came back."
914
+ return 1
915
+ fi
916
+ echo " โœ… thirdparty leg: $val" ;;
917
+ *)
918
+ echo " โŒ FAIL โ€” '$val' is not a member of the thirdparty: enum."
919
+ echo " Closed enum: peer-review(<harness>/<persona> -> <target>, <what came back>) ยท"
920
+ echo " checked(<searched/found>) ยท none-found(<searched>) ยท"
921
+ echo " DEGRADED_NO_ACCESS(<why>) ยท DEGRADED_NOT_RUN(<why>) ยท UNKNOWN ยท not-applicable(<why>)"
922
+ echo " ๐ŸŸฅ โ““3์ž๋Œ€๋ฉด = third-party PRIOR ART, not 'I talked to another session'. The two"
923
+ echo " markers that made this lane necessary both made exactly that substitution."
924
+ return 1 ;;
925
+ esac
926
+ return 0
927
+ }
928
+
929
+
930
+
720
931
  # โ”€โ”€ 4์ถ• ์ž๊ธฐ ๋Œ€์กฐ โ€” ยซ์–ด๋А ์ถ•์„ ๋Œ๋ ธ๊ณ  ์–ด๋А ์ถ•์„ ์•ˆ ๋Œ๋ ธ๋‚˜ยป ํ˜•์‹ ๊ฒ€์‚ฌ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
721
932
  # CLAUDE.md ยง3์ธต ์ž๊ธฐ ๋Œ€์กฐ๊ฐ€ ๋งˆ์ปค์— 3์ค„์„ ์š”๊ตฌํ•œ๋‹ค. ๊ทธ์ค‘ **๊ธฐ๊ณ„๋กœ ๋ณผ ์ˆ˜ ์žˆ๋Š” ๋‘ ์ค„**๋งŒ ์—ฌ๊ธฐ์„œ
722
933
  # ๊ฐ•์ œํ•œ๋‹ค. ๋‚˜๋จธ์ง€(โ‘ ์˜ํ˜ผ์„ ยซ์„ค๊ณ„ ์ „์—ยป ์ผ๋Š”๊ฐ€)๋Š” **์›๋ฆฌ์ ์œผ๋กœ ํ™•์ธ ๋ถˆ๊ฐ€**๋ผ ์•ˆ ๋„ฃ๋Š”๋‹ค โ€”
@@ -866,6 +1077,26 @@ validate_marker_axes_run() { # $1 = marker path
866
1077
  echo " standpoint: not-applicable"
867
1078
  return 1
868
1079
  fi
1080
+
1081
+ # โ““3์ž๋Œ€๋ฉด๋„ ๊ฐ™์€ ํ˜•ํƒœ โ€” ๊ฐ’์€ `thirdparty:` ์ž๊ธฐ ํ•„๋“œ๊ฐ€ ๋‚˜๋ฅธ๋‹ค (2026-08-17 ์‹ ์„ค).
1082
+ # ๐ŸŸฅ ์™œ ์ž๊ธฐ ํ•„๋“œ๊ฐ€ ํ•„์š”ํ•œ๊ฐ€: `axes-run` ํ•œ ์นธ์€ **ํ† ํฐ**๋งŒ ๋‹ด๋Š”๋‹ค. โ““๊ฐ€ ์‹ค์ œ๋กœ ๋‚ด๋Š” ๊ฒƒ์€
1083
+ # ยซ๋ฌด์—‡์„ ๊ฒ€์ƒ‰ํ•ด์„œ ๋ฌด์—‡์„ ์ฐพ์•˜๋‚˜ยป ๋ผ ํ•œ ํ† ํฐ์— ์•ˆ ๋“ค์–ด๊ฐ„๋‹ค. ์ž๋ฆฌ๊ฐ€ ์—†์œผ๋ฉด ์•ˆ ์ ํžˆ๊ณ ,
1084
+ # ์•ˆ ์ ํžˆ๋ฉด ๋‹ค์Œ ์„ธ์…˜์ด ยซ์ด ์ถ•์„ ๋Œ๋ ธ๋‚˜ยป๋ฅผ ๋ฌผ์„ ์ˆ˜๋„ ํ™•์ธํ•  ์ˆ˜๋„ ์—†๋‹ค.
1085
+ # ์‹ค์ธก(2026-08-17): ์ด ์ถ•์„ ์ฒ˜์Œ ์ œ๋Œ€๋กœ ๋Œ๋ฆฐ ์„ธ์…˜์—์„œ ๋ฐœํ‘œ ์ฃผ์žฅ **6๊ฑด**์ด ์„ ํ–‰์ž์‚ฐ์—
1086
+ # ๊ฑธ๋ ธ๋Š”๋ฐ, ๊ทธ ๊ฒฐ๊ณผ๋ฅผ ๊ธฐ๋กํ•  ํ•„๋“œ๊ฐ€ ์—†์—ˆ๋‹ค.
1087
+ # โš ๏ธ โ“‘ ์™€ **๋˜‘๊ฐ™์€ fail-open ์„ ๋ฐ˜๋ณตํ•˜์ง€ ์•Š๋Š”๋‹ค** โ€” ํ™”์‚ดํ‘œ๋ฅผ ํ•„์ˆ˜๋กœ ๋ณด์ง€ ์•Š๊ณ 
1088
+ # ยซโ““ ๊ฐ’์ด thirdparty ๋ฅผ ์ฐธ์กฐํ•˜๋Š”๊ฐ€ยป ๋กœ ๋„“ํžŒ๋‹ค(โ“‘๊ฐ€ ๊ทธ๋ ‡๊ฒŒ ๋šซ๋ ธ๋˜ ์‹ค์ธก์ด ๋ฐ”๋กœ ์œ„์— ์žˆ๋‹ค).
1089
+ # ๊ฐ™์€ ๋ช…์‹œ ์ž”์—ฌ๋„ ๊ทธ๋Œ€๋กœ ์ƒ์†ํ•œ๋‹ค: ์ฐธ์กฐ์–ด๊ฐ€ ๋‹ค๋ฅธ ํ† ํฐ์— ์žˆ์œผ๋ฉด ์•ˆ ์žกํžŒ๋‹ค.
1090
+ if echo "$line" | grep -qE "(^|[[:space:]])โ““=[^[:space:]]*thirdparty" \
1091
+ && ! grep -qE '^[[:space:]]*thirdparty:[[:space:]]*[^[:space:]]' "$m" 2>/dev/null; then
1092
+ echo " โŒ MARKER โ€” 'โ““=โ†’thirdparty' ๋ผ๊ณ  ์ ์—ˆ๋Š”๋ฐ 'thirdparty:' ์ค„์ด ์—†๋‹ค"
1093
+ echo " ํฌ์ธํ„ฐ๊ฐ€ ๊ฐ€๋ฆฌํ‚ค๋Š” ๊ณณ์ด ๋น„์–ด ์žˆ์œผ๋ฉด ๊ทธ๊ฑด ๊ธฐ๋ก์ด ์•„๋‹ˆ๋ผ ์ฃฝ์€ ํฌ์ธํ„ฐ๋‹ค."
1094
+ echo " thirdparty: checked(promptfooยทDeepEval ํ›‘์Œ โ€” ๊ฒน์นจ ์—†์Œ)"
1095
+ echo " thirdparty: none-found(<๋ฌด์—‡์„ ๊ฒ€์ƒ‰ํ–ˆ๋‚˜>) โ† ์ฐพ์•˜๋Š”๋ฐ ์—†์Œ. ยซ์•ˆ ๋ดค๋‹คยป์™€ ๋‹ค๋ฅด๋‹ค"
1096
+ echo " thirdparty: UNKNOWN โ† ์•ˆ ๋ดค๋‹ค"
1097
+ echo " thirdparty: not-applicable"
1098
+ return 1
1099
+ fi
869
1100
  fi
870
1101
 
871
1102
  line=$(grep -m1 -E '^[[:space:]]*controls:' "$m" 2>/dev/null \
@@ -1283,6 +1514,8 @@ if [ -n "$LOADBEARING" ]; then
1283
1514
  fi
1284
1515
  MARKER_LB="$REPO_ROOT/tracks/_meta/.axes_23_passed_${BRANCH_SLUG}_${TODAY}.marker"
1285
1516
  if validate_crossfamily_leg "$MARKER_LB"; then :; else FAILED=1; fi
1517
+ if validate_standpoint_leg "$MARKER_LB"; then :; else FAILED=1; fi
1518
+ if validate_thirdparty_leg "$MARKER_LB"; then :; else FAILED=1; fi
1286
1519
  fi
1287
1520
 
1288
1521
  # โ”€โ”€ staged-blob materializer (advisory linters) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
@@ -1391,6 +1624,35 @@ if [ -f "$PL_LINT" ]; then
1391
1624
  fi
1392
1625
  fi
1393
1626
 
1627
+ # โ”€โ”€ Pre-ship read reminder (advisory) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
1628
+ # WHY (2026-08-17, โ““3์ž๋Œ€๋ฉด ์ฒซ ์‹ค์‚ฌ์šฉ์˜ ๋‹ต์Šต๋ถ„). A third harness (gstack), wearing its own
1629
+ # `/plan-devex-review` persona, reviewed a peer harness's change and asked a question neither FH
1630
+ # nor that harness asks: **who opens this file, and what do they see in the first eight lines?**
1631
+ # Its finding: a human-facing report had been shipped to three surfaces where the body was 3 lines
1632
+ # under 21 lines of fixed template, the answer to "did it pass?" was buried in the 4th bullet, and
1633
+ # the last impression was "readers may skip this". The delta that shipped it was correct code.
1634
+ #
1635
+ # FH reached the same place independently the same day from a different axis โ€” "readability is
1636
+ # measured only by rendering it or by a person; a static scan catches what is ABSENT, never what is
1637
+ # UNREADABLE", with 12 findings and 0 caught by static scanners. **Two independent arrivals** is
1638
+ # why this was the one item the internalization filter accepted out of four
1639
+ # (`harness_incubator_doctrine.md` ยง4-c).
1640
+ #
1641
+ # ๐ŸŸฅ WHAT THIS IS NOT. It does not judge readability, and it never blocks. Judging "is this
1642
+ # readable" is exactly the frozen-judgment class ยงMechanization Boundary reserves for people, and a
1643
+ # vocabulary test for "human-facing document" over-blocked a legitimate case the same day it was
1644
+ # first tried. The trigger is therefore a PATH SET (what actually ships to readers), not a
1645
+ # vocabulary guess, and its whole output is one line of salience.
1646
+ _PRESHIP=$(printf '%s\n' "$STAGED" | grep -E '^(README([._a-z]*)?\.md|CHEATSHEET\.md|CATALOG\.md|docs/[^/]*\.md|templates/[^/]*\.md)$' || true)
1647
+ if [ -n "$_PRESHIP" ]; then
1648
+ echo "[Gate] reader-facing surface staged โ€” pre-ship read reminder (advisory)..."
1649
+ printf '%s\n' "$_PRESHIP" | sed 's/^/ /'
1650
+ echo " โš ๏ธ ์ด ํŒŒ์ผ๋“ค์€ ยซ์‚ฌ๋žŒ์ด ์—ฐ๋‹คยป. ๋‚˜๊ฐ€๊ธฐ ์ „์— ํ•œ ๋ฒˆ ยซ๋…์ž๋กœ์„œยป ์ฝ์—ˆ๋‚˜?"
1651
+ echo " ์ฒซ 8์ค„์— ๊ฒฐ๋ก ์ด ์žˆ๋‚˜ ยท ๋ณธ๋ฌธ์ด ํ…œํ”Œ๋ฆฟ์— ์•ˆ ๋ฎ์ด๋‚˜ ยท ๋งˆ์ง€๋ง‰ ์ธ์ƒ์ด ๋ฌด์—‡์ธ๊ฐ€"
1652
+ echo " ๋ Œ์ฆˆ๊ฐ€ ์ด๋ฏธ ์žˆ๋‹ค: /sim-conductor A-1 (beginner cold-read) ๋˜๋Š” ์ง์ ‘ ๋ Œ๋”ํ•ด์„œ ์ฝ๊ธฐ"
1653
+ echo " (advisory โ€” ๋ง‰์ง€ ์•Š๋Š”๋‹ค. ใ€Œ์ฝ์—ˆ๋Š”๊ฐ€ใ€๋Š” ์ด ํ›…์ด ๋ณด์ฆํ•  ์ˆ˜ ์—†๊ณ  ๋ณด์ฆํ•˜๋ ค ๋“ค์ง€๋„ ์•Š๋Š”๋‹ค.)"
1654
+ fi
1655
+
1394
1656
  # โ”€โ”€ Novelty-claim ledger (advisory) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
1395
1657
  # "Ask the world before asserting novelty" is an INTENT trigger โ€” un-hookable, like the
1396
1658
  # repo-go-public surface. So this does not force the asking; it makes NOT having asked visible
@@ -83,3 +83,25 @@ A pre-publish catch is a process win worth logging:
83
83
 
84
84
  **Done when**: Step 0 pre-flags clean (or escalated to full scan) ยท Step 1 both gates non-LEAK ยท
85
85
  publish executed *after* the gates passed, never before.
86
+
87
+ ## Read it as a reader, once, before it goes out
88
+
89
+ Not a correctness check โ€” the code can be right and this still fails. Open the thing a **reader**
90
+ opens and look at what they see:
91
+
92
+ - [ ] Is the **conclusion in the first ~8 lines**, or is it buried under banners, metadata and
93
+ "how to read this table"?
94
+ - [ ] Does the **body survive the template**? (measured case: 3 lines of content under 21 lines of
95
+ fixed boilerplate)
96
+ - [ ] What is the **last impression**? (measured case: the final line told the reader they could
97
+ skip it)
98
+ - [ ] Does it say **what the reader should do next**?
99
+
100
+ A lens already exists โ€” `/sim-conductor` A-1 (`beginner` cold-read) โ€” or just render it and read it.
101
+
102
+ ๐ŸŸฅ **Why this is a human step and not a scan**: a static check catches what is ABSENT, never what is
103
+ UNREADABLE. Two independent measurements arrived here the same day (2026-08-17): one harness axis
104
+ found 12 readability defects with **0** caught by static scanners; a third-harness review
105
+ (`harness_incubator_doctrine.md` ยง4-c) found a shipped report whose answer to "did it pass?" sat in
106
+ the fourth bullet. The pre-commit hook prints an advisory reminder when a reader-facing surface is
107
+ staged โ€” it reminds, it does not certify.
@@ -64,13 +64,41 @@ if [ "${1:-}" = "--pr" ]; then
64
64
  fi
65
65
  PR_BRANCH="$2"
66
66
  BASE_BRANCH="${3:-main}"
67
- BASE_REF=$(git merge-base "$BASE_BRANCH" "$PR_BRANCH" 2>/dev/null)
67
+ # Resolve each side to a ref that actually EXISTS in this checkout.
68
+ #
69
+ # WHY (measured 2026-08-17, 5 of 5 SAMPLED CI runs โ€” not an exhaustive audit): a bare branch
70
+ # NAME does not resolve in a GitHub Actions PR checkout. actions/checkout lands on a DETACHED
71
+ # HEAD and creates refs/remotes/origin/*, not local branches โ€” so `git merge-base main <branch>`
72
+ # found neither side, returned empty, and this block exited 3. `fetch-depth: 0` was already set
73
+ # and is NOT the cause; the history was present, the NAMES were not. The workflow then rendered
74
+ # that instrument error as a green PASS (fixed in the same commit).
75
+ #
76
+ # ๐ŸŸฅ THE FIRST FIX FOR THIS INTRODUCED A WORSE HOLE, caught by cross-family review before it
77
+ # shipped. It resolved `ref -> origin/ref -> refs/remotes/origin/ref` and fell back to HEAD.
78
+ # `github.head_ref` is only a branch NAME, not owner-qualified, so a **fork PR whose branch is
79
+ # named `main`** resolved the PR side to the BASE repo's `main` โ€” merge-base(main, main) = main,
80
+ # empty diff, SKIP, green. A guard silently comparing a branch to itself is worse than one that
81
+ # errors. So: NO name-guessing and NO silent HEAD fallback. Callers pass something
82
+ # unambiguous (a SHA, or an explicit `origin/<ref>`); anything that does not resolve EXACTLY
83
+ # is an instrument error, and the workflow now fails closed on that.
84
+ _rg_resolve_ref() { # $1 = ref-ish; echoes it iff it resolves EXACTLY as given (rc=1 otherwise)
85
+ git rev-parse --verify --quiet "${1}^{commit}" >/dev/null 2>&1 && printf '%s' "$1"
86
+ }
87
+ _BASE_RESOLVED=$(_rg_resolve_ref "$BASE_BRANCH") || _BASE_RESOLVED=""
88
+ _HEAD_RESOLVED=$(_rg_resolve_ref "$PR_BRANCH") || _HEAD_RESOLVED=""
89
+ if [ -z "$_BASE_RESOLVED" ] || [ -z "$_HEAD_RESOLVED" ]; then
90
+ echo "ERROR: ref does not resolve โ€” base='$BASE_BRANCH'->'${_BASE_RESOLVED:-<none>}' head='$PR_BRANCH'->'${_HEAD_RESOLVED:-<none>}'" >&2
91
+ echo " Pass an unambiguous ref (a SHA, or origin/<branch>). Guessing is how a fork PR" >&2
92
+ echo " branch named 'main' silently compared the base repo's main to itself." >&2
93
+ exit 3
94
+ fi
95
+ BASE_REF=$(git merge-base "$_BASE_RESOLVED" "$_HEAD_RESOLVED" 2>/dev/null)
68
96
  if [ -z "$BASE_REF" ]; then
69
- echo "ERROR: cannot compute merge-base for $PR_BRANCH vs $BASE_BRANCH" >&2
97
+ echo "ERROR: cannot compute merge-base for $_HEAD_RESOLVED vs $_BASE_RESOLVED" >&2
70
98
  exit 3
71
99
  fi
72
- HEAD_REF="$PR_BRANCH"
73
- echo "PR MODE: merge-base=$(git rev-parse --short "$BASE_REF") branch=$PR_BRANCH"
100
+ HEAD_REF="$_HEAD_RESOLVED"
101
+ echo "PR MODE: merge-base=$(git rev-parse --short "$BASE_REF") base=$_BASE_RESOLVED head=$_HEAD_RESOLVED"
74
102
  elif [ "${1:-}" = "--staged" ]; then
75
103
  # Pre-commit context: evaluate the staged index against HEAD. On a direct-to-main
76
104
  # workflow, --pr's merge-base(main,main)=HEAD yields an empty diff, so staged changes