@chrono-meta/fh-gate 3.0.0 → 3.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 (111) hide show
  1. package/.claude/regression/probes_live.yaml +137 -0
  2. package/.claude/rules/.residency-patterns.defaults +7 -0
  3. package/.claude/rules/fh_4axis_gate.md +50 -1
  4. package/.claude-plugin/marketplace.json +8 -2
  5. package/AGENTS.md +27 -0
  6. package/CATALOG.md +17 -0
  7. package/CLAUDE.md +12 -2
  8. package/README.ja.md +51 -7
  9. package/README.ko.md +48 -7
  10. package/README.md +37 -5
  11. package/README.zh.md +45 -8
  12. package/docs/STANDARDS_ALIGNMENT.md +61 -0
  13. package/docs/USER_GUIDE.md +3 -0
  14. package/docs/USE_CASES.md +50 -0
  15. package/docs/model_tier_expectations.md +60 -0
  16. package/knowledge/shared/harness-core/fh_three_layer_canon.md +30 -10
  17. package/knowledge/shared/harness-core/field_verdict_crossfamily_gate.md +8 -0
  18. package/knowledge/shared/harness-core/harness_incubator_doctrine.md +9 -0
  19. package/knowledge/shared/harness-core/iso_ai_standards_crosswalk.md +139 -0
  20. package/knowledge/shared/harness-core/measurement-integrity-checklist.md +20 -0
  21. package/knowledge/shared/learnings/subagent_invocations_log.yaml +441 -4
  22. package/package.json +40 -2
  23. package/plugins/fh-commons/.claude-plugin/plugin.json +1 -1
  24. package/plugins/fh-commons/skills/preprep/README.md +4 -1
  25. package/plugins/fh-commons/skills/preprep/SKILL.md +99 -3
  26. package/plugins/fh-commons/skills/preprep/diagram_from_json.py +154 -0
  27. package/plugins/fh-commons/skills/preprep/fixtures/fixture_R3_negative.pptx +0 -0
  28. package/plugins/fh-commons/skills/preprep/fixtures/fixture_R3_positive.pptx +0 -0
  29. package/plugins/fh-commons/skills/preprep/fixtures/mk_slide_fixtures.py +179 -0
  30. package/plugins/fh-commons/skills/preprep/interslide_deps.py +98 -9
  31. package/plugins/fh-commons/skills/preprep/lane_adjacent_dup.py +4 -1
  32. package/plugins/fh-commons/skills/preprep/lane_diagram.py +95 -0
  33. package/plugins/fh-commons/skills/preprep/lane_geometry.py +181 -0
  34. package/plugins/fh-commons/skills/preprep/lane_promise.py +4 -1
  35. package/plugins/fh-commons/skills/preprep/lane_slide_refs.py +134 -0
  36. package/plugins/fh-commons/skills/preprep/lane_slide_relations.py +300 -0
  37. package/plugins/fh-commons/skills/preprep/preprep.py +129 -8
  38. package/plugins/fh-commons/skills/preprep/preprep_wire.py +210 -0
  39. package/plugins/fh-commons/skills/preprep/presentation_checklist.md +4 -0
  40. package/plugins/fh-commons/skills/preprep/surfaces.example.yaml +18 -0
  41. package/plugins/fh-commons/skills/preprep/test_preprep_lanes_rp.py +184 -0
  42. package/plugins/fh-meta/.claude-plugin/plugin.json +1 -1
  43. package/plugins/fh-meta/CHANGELOG.md +43 -1
  44. package/plugins/fh-meta/skills/auto-decorrelation/SKILL.md +87 -2
  45. package/plugins/fh-meta/skills/frontier-digest/SKILL.md +2 -2
  46. package/plugins/fh-meta/skills/frontier-digest/SKILL_detail.md +25 -5
  47. package/plugins/fh-meta/skills/hub-cc-pr-reviewer/SKILL.md +11 -0
  48. package/plugins/fh-meta/skills/hub-cc-pr-reviewer/SKILL_detail.md +3 -0
  49. package/plugins/fh-qp/.claude-plugin/plugin.json +22 -0
  50. package/plugins/fh-qp/README.md +71 -0
  51. package/plugins/fh-qp/fixtures/evidence_known_clean.txt +3 -0
  52. package/plugins/fh-qp/fixtures/evidence_known_dirty.txt +6 -0
  53. package/plugins/fh-qp/fixtures/reach_known_partial.tsv +4 -0
  54. package/plugins/fh-qp/fixtures/reach_known_reached.tsv +4 -0
  55. package/plugins/fh-qp/fixtures/reach_known_wall.tsv +3 -0
  56. package/plugins/fh-qp/fixtures/verdicts_known_bad_branch.tsv +2 -0
  57. package/plugins/fh-qp/fixtures/verdicts_known_bad_vacuous_machine.tsv +2 -0
  58. package/plugins/fh-qp/fixtures/verdicts_known_good.tsv +4 -0
  59. package/plugins/fh-qp/fixtures/verdicts_verify_only.tsv +3 -0
  60. package/plugins/fh-qp/qp_profile.example.yaml +29 -0
  61. package/plugins/fh-qp/scripts/qp_tools.sh +216 -0
  62. package/plugins/fh-qp/skills/qp/SKILL.md +65 -0
  63. package/plugins/fh-qp/skills/qp-plan/SKILL.md +42 -0
  64. package/plugins/fh-qp/skills/qp-regress/SKILL.md +45 -0
  65. package/plugins/fh-qp/skills/qp-run/SKILL.md +49 -0
  66. package/scripts/chamber_run.sh +14 -5
  67. package/scripts/com.forge-harness.live-eval.plist +84 -0
  68. package/scripts/compaction_probe.sh +9 -35
  69. package/scripts/directional_diff_gate.sh +14 -2
  70. package/scripts/frontier_digest_autopilot.sh +4 -1
  71. package/scripts/map_postprocess.py +90 -0
  72. package/scripts/outbound_query_guard.sh +131 -0
  73. package/scripts/outbound_query_hook.sh +373 -0
  74. package/scripts/package_coverage_check.sh +55 -16
  75. package/scripts/pipe_verdict_guard.sh +41 -1
  76. package/scripts/probe_live_eval.sh +240 -0
  77. package/scripts/probe_live_eval_lib.py +579 -0
  78. package/scripts/proposal_hook.sh +120 -17
  79. package/scripts/push_zone_check.sh +78 -0
  80. package/scripts/residency_closure_scan.py +252 -0
  81. package/scripts/selfcheck.sh +41 -1
  82. package/scripts/session_close_check.sh +100 -0
  83. package/scripts/sim_isolated_run.sh +98 -2
  84. package/scripts/test_action_yml_lanes.sh +97 -0
  85. package/scripts/test_fh_qp_lanes.sh +105 -0
  86. package/scripts/test_gate_two_verdicts_lanes.sh +139 -0
  87. package/scripts/test_map_postprocess_lanes.sh +143 -0
  88. package/scripts/test_marker_affected_lanes.sh +93 -0
  89. package/scripts/test_marker_crossfamily_lanes.sh +90 -6
  90. package/scripts/test_marker_oracle_lanes.sh +136 -0
  91. package/scripts/test_outbound_query_hook_lanes.sh +433 -0
  92. package/scripts/test_outbound_query_lanes.sh +87 -0
  93. package/scripts/test_pipe_verdict_guard_lanes.sh +26 -0
  94. package/scripts/test_preprep_diagram_lanes.sh +87 -0
  95. package/scripts/test_preprep_drift_anchor.sh +3 -3
  96. package/scripts/test_preprep_slide_refs_lanes.sh +169 -0
  97. package/scripts/test_probe_live_eval_lanes.sh +437 -0
  98. package/scripts/test_proposal_hook_lanes.sh +22 -1
  99. package/scripts/test_push_zone_lanes.sh +304 -0
  100. package/scripts/test_residency_closure_lanes.sh +70 -0
  101. package/scripts/test_sim_isolated_run_lanes.sh +119 -0
  102. package/scripts/test_utterance_intake_lanes.sh +414 -0
  103. package/scripts/test_worktree_reclaim_lanes.sh +70 -0
  104. package/scripts/transcript_utterances.py +222 -0
  105. package/scripts/utterance_intake.sh +424 -0
  106. package/scripts/validate_yaml.sh +27 -0
  107. package/scripts/worktree_reclaim.sh +95 -0
  108. package/templates/.git-hooks/pre-commit +353 -1
  109. package/templates/.git-hooks/pre-push +91 -0
  110. package/templates/RED_TEAM_REPORT.md +49 -0
  111. package/templates/settings.PreToolUse.snippet.json +65 -1
@@ -0,0 +1,143 @@
1
+ #!/usr/bin/env bash
2
+ # test_map_postprocess_lanes.sh — scripts/map_postprocess.py 의 두 계약을 고정한다.
3
+ #
4
+ # WHY: 이 후처리는 발행되는 지도 HTML 의 «폭 하한»을 바꾸고 SVG 를 다시 만든다. 둘 다 조용히
5
+ # 실패하면 사람이 못 본다 — 패치가 안 붙어도 페이지는 뜨고(옛 폭), SVG 가 어긋나도 열리기는 한다.
6
+ # 그래서 ⓐ 리터럴 부재 = 드리프트 = 하드 정지(3), ⓑ SVG 는 «커밋된 두 장 바이트 동일 재현»을
7
+ # known-pair 로 잡는다. 컨트롤: 패치 문자열이 실제로 상수와 다르고 뷰포트를 참조하는가.
8
+ #
9
+ # 종료코드: 0 pass · 1 레인 실패 · 2 대상 부재 · 10 setup 실패
10
+ set -uo pipefail
11
+ cd "$(dirname "$0")/.." || exit 10
12
+ S=scripts/map_postprocess.py
13
+ PASS=0; FAIL=0
14
+ ok() { echo "✅ $1"; PASS=$((PASS+1)); }
15
+ ng() { echo "❌ $1"; FAIL=$((FAIL+1)); }
16
+
17
+ [ -f "$S" ] || { echo "ⓘ $S absent — subject missing when we looked (NOT a pass)"; exit 2; }
18
+ command -v python3 >/dev/null 2>&1 || { echo "ⓘ python3 absent — setup broke (NOT a pass)"; exit 10; }
19
+
20
+ TMP=$(mktemp -d) || exit 10
21
+ trap 'rm -rf "$TMP"' EXIT
22
+
23
+ mkfix() { # $1=out path, $2=reader-width literal line (may be empty), $3=extra style blocks
24
+ {
25
+ printf '<html><head><style>.a{color:red}</style>'
26
+ [ -n "${3:-}" ] && printf '<style>.b{color:blue}</style>'
27
+ printf '</head><body>'
28
+ printf '<svg viewBox="0 0 10 10"><rect/></svg>'
29
+ printf '<script>%s</script></body></html>' "$2"
30
+ } > "$1"
31
+ }
32
+
33
+ # ── L1 known-positive: 상수가 있으면 패치되고 SVG 가 나온다
34
+ mkfix "$TMP/pos.html" 'var MIN_READER_WIDTH = 960;' ''
35
+ python3 "$S" "$TMP/pos.html" >"$TMP/o1" 2>&1; RC=$?
36
+ if [ $RC -eq 0 ] && /usr/bin/grep -q 'window.innerWidth' "$TMP/pos.html" && [ -f "$TMP/pos.svg" ]; then
37
+ ok "L1 known-positive: patched + svg written (rc=0)"
38
+ else
39
+ ng "L1 known-positive failed (rc=$RC): $(head -2 "$TMP/o1" | tr '\n' ' ')"
40
+ fi
41
+
42
+ # ── L2 멱등: 두 번째 실행은 SKIP 이고 여전히 rc=0
43
+ python3 "$S" "$TMP/pos.html" >"$TMP/o2" 2>&1; RC=$?
44
+ if [ $RC -eq 0 ] && /usr/bin/grep -q 'SKIP' "$TMP/o2"; then
45
+ ok "L2 idempotent re-run reports SKIP (rc=0)"
46
+ else
47
+ ng "L2 idempotency broken (rc=$RC): $(head -2 "$TMP/o2" | tr '\n' ' ')"
48
+ fi
49
+
50
+ # ── L3 known-negative(드리프트): 리터럴이 아예 없으면 rc=3 이고 파일을 안 건드린다
51
+ mkfix "$TMP/drift.html" 'var SOMETHING_ELSE = 1;' ''
52
+ BEFORE=$(shasum "$TMP/drift.html" | awk '{print $1}')
53
+ python3 "$S" "$TMP/drift.html" >"$TMP/o3" 2>&1; RC=$?
54
+ AFTER=$(shasum "$TMP/drift.html" | awk '{print $1}')
55
+ if [ $RC -eq 3 ] && [ "$BEFORE" = "$AFTER" ] && [ ! -f "$TMP/drift.svg" ]; then
56
+ ok "L3 drift → rc=3, file untouched, no svg (fail-closed)"
57
+ else
58
+ ng "L3 drift not fail-closed (rc=$RC, sha changed=$([ "$BEFORE" = "$AFTER" ] && echo no || echo yes))"
59
+ fi
60
+
61
+ # ── L4 컨트롤: 패치 문자열이 상수와 실제로 다르고 뷰포트를 참조하는가
62
+ # (같은 값으로 «치환»하면 L1 도 통과한다 — 그 자멸을 막는 자리)
63
+ OLDLIT=$(python3 -c "import re;s=open('$S',encoding='utf-8').read();print(re.search(r\"^OLD = '(.*)'\",s,re.M).group(1))")
64
+ NEWLIT=$(python3 -c "
65
+ import re
66
+ s=open('$S',encoding='utf-8').read()
67
+ m=re.search(r\"NEW = \((.*?)\)\n\", s, re.S)
68
+ print(''.join(re.findall(r\"'([^']*)'\", m.group(1))))")
69
+ if [ -n "$OLDLIT" ] && [ -n "$NEWLIT" ] && [ "$OLDLIT" != "$NEWLIT" ] && \
70
+ printf '%s' "$NEWLIT" | /usr/bin/grep -q 'innerWidth'; then
71
+ ok "L4 control: replacement differs from the constant and reads the viewport"
72
+ else
73
+ ng "L4 control: replacement is vacuous (old='$OLDLIT' new='$NEWLIT')"
74
+ fi
75
+
76
+ # ── L5 known-pair: 커밋된 지도 두 장을 HTML 에서 바이트 동일하게 재현하는가
77
+ # (SVG 는 <style>+<svg> 만 담아 뷰어 JS 를 안 싣는다 — 그래서 폭 패치와 무관하게 안정적인 짝이다)
78
+ PAIR_OK=1; PAIR_RAN=0
79
+ for base in docs/map/fh_trust.dataflow docs/map/fh_assets.architecture; do
80
+ [ -f "$base.html" ] && [ -f "$base.svg" ] || continue
81
+ PAIR_RAN=$((PAIR_RAN+1))
82
+ python3 - "$S" "$base.html" "$base.svg" <<'PY' || PAIR_OK=0
83
+ import importlib.util, sys
84
+ spec = importlib.util.spec_from_file_location('mp', sys.argv[1])
85
+ mp = importlib.util.module_from_spec(spec); spec.loader.exec_module(mp)
86
+ built = mp.build_svg(open(sys.argv[2], encoding='utf-8').read())
87
+ cur = open(sys.argv[3], encoding='utf-8').read()
88
+ if built != cur:
89
+ print(f' ✗ {sys.argv[3]}: rebuilt {len(built)}B != committed {len(cur)}B')
90
+ sys.exit(1)
91
+ PY
92
+ done
93
+ if [ "$PAIR_RAN" -eq 0 ]; then
94
+ ng "L5 known-pair: 0 pairs found — instrument had nothing to measure (NOT a pass)"
95
+ elif [ "$PAIR_OK" -eq 1 ]; then
96
+ ok "L5 known-pair: $PAIR_RAN committed svg(s) reproduced byte-for-byte from html"
97
+ else
98
+ ng "L5 known-pair: rebuilt svg differs from the committed one"
99
+ fi
100
+
101
+ # ── L6 <style> 블록이 둘이면 추출기는 멈춘다(조용히 첫 장만 싣지 않는다)
102
+ mkfix "$TMP/two.html" 'var MIN_READER_WIDTH = 960;' 'extra'
103
+ python3 "$S" "$TMP/two.html" >"$TMP/o6" 2>&1; RC=$?
104
+ if [ $RC -eq 3 ] && [ ! -f "$TMP/two.svg" ]; then
105
+ ok "L6 two <style> blocks → rc=3, no svg written"
106
+ else
107
+ ng "L6 two <style> blocks not rejected (rc=$RC)"
108
+ fi
109
+
110
+ # ── L7 없는 파일 → rc=4 (인자 오류와 드리프트를 섞지 않는다)
111
+ python3 "$S" "$TMP/nope.html" >"$TMP/o7" 2>&1; RC=$?
112
+ [ $RC -eq 4 ] && ok "L7 missing file → rc=4 (distinct from drift rc=3)" || ng "L7 missing file rc=$RC (expected 4)"
113
+
114
+ # ── L8 --check 는 쓰지 않는다
115
+ mkfix "$TMP/chk.html" 'var MIN_READER_WIDTH = 960;' ''
116
+ B=$(shasum "$TMP/chk.html" | awk '{print $1}')
117
+ python3 "$S" --check "$TMP/chk.html" >"$TMP/o8" 2>&1; RC=$?
118
+ A=$(shasum "$TMP/chk.html" | awk '{print $1}')
119
+ if [ $RC -eq 0 ] && [ "$B" = "$A" ] && /usr/bin/grep -q 'UNPATCHED' "$TMP/o8" && [ ! -f "$TMP/chk.svg" ]; then
120
+ ok "L8 --check reports without writing"
121
+ else
122
+ ng "L8 --check wrote or misreported (rc=$RC)"
123
+ fi
124
+
125
+ # ── L9 발행본이 실제로 패치된 상태인가 (배선 확인 — 스크립트가 있는데 안 돌린 경우를 잡는다)
126
+ SHIPPED_OK=1; SHIPPED_N=0
127
+ for f in docs/map/*.html; do
128
+ [ -f "$f" ] || continue
129
+ case "$f" in *.visual-check.html) continue;; esac
130
+ SHIPPED_N=$((SHIPPED_N+1))
131
+ python3 "$S" --check "$f" 2>/dev/null | /usr/bin/grep -q '^PATCHED' || { SHIPPED_OK=0; echo " ✗ $f not patched"; }
132
+ done
133
+ if [ "$SHIPPED_N" -eq 0 ]; then
134
+ ng "L9 no docs/map/*.html found — nothing measured (NOT a pass)"
135
+ elif [ "$SHIPPED_OK" -eq 1 ]; then
136
+ ok "L9 all $SHIPPED_N shipped map html carry the patched reader-width floor"
137
+ else
138
+ ng "L9 a shipped map html is unpatched — re-run: python3 $S docs/map/*.html"
139
+ fi
140
+
141
+ echo "── map_postprocess lanes: PASS=$PASS FAIL=$FAIL"
142
+ [ "$FAIL" -eq 0 ] || exit 1
143
+ exit 0
@@ -0,0 +1,93 @@
1
+ #!/usr/bin/env bash
2
+ # test_marker_affected_lanes.sh — regression fixtures for pre-commit's
3
+ # validate_affected_leg (2026-09-04, frontier absorption of Anthropic AI-Native SDLC
4
+ # playbook intent.md's "Affected users and systems" / "Open questions" columns).
5
+ #
6
+ # WHY: FH does not split the two source columns into two marker fields — a second field is
7
+ # a second empty slot, the exact shape soul-check:/tenets:/thirdparty: already avoid. One
8
+ # free-prose line carries both, by convention ("건드리는 것 · 열린 질문 = …").
9
+ #
10
+ # SCOPE — channel, not judgment: single line · no duplicate · non-vacuous · not a bare
11
+ # placeholder. Unlike defeater:, "없음" is NOT a legal declared-absence here — every change
12
+ # touches something, so a placeholder-only value is an unfilled field, not an honest record
13
+ # of nothing. Absent (no `affected:` line at all) is legal — adoption is incremental, same
14
+ # shape as soul-check:/tenets:.
15
+ #
16
+ # Fixtures assert BOTH directions (known-pair): every intended shape is admitted, and every
17
+ # hole this lane closes still blocks.
18
+ #
19
+ # Usage: bash scripts/test_marker_affected_lanes.sh Exit: 0 = all behave; 1 = regression.
20
+
21
+ set -uo pipefail
22
+ REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
23
+ HOOK="$REPO_ROOT/templates/.git-hooks/pre-commit"
24
+ T=$(mktemp -d); trap 'rm -rf "$T"' EXIT
25
+
26
+ # 헬퍼도 같이 추출한다 — 안 하면 다리가 fail-open 이 되고 레인이 거짓 PASS 를 낸다
27
+ # (defeater/soul-check 가 이미 겪은 형태: [[feedback_broken_parser_reports_a_verdict]]).
28
+ sed -n '/^marker_recreate_hint()/,/^}/p' "$HOOK" > "$T/_helpers.sh"
29
+ sed -n '/^_marker_template_residue()/,/^}/p' "$HOOK" >> "$T/_helpers.sh"
30
+ sed -n '/^validate_affected_leg()/,/^}/p' "$HOOK" > "$T/fn0.sh"
31
+ cat "$T/_helpers.sh" "$T/fn0.sh" > "$T/fn.sh"
32
+
33
+ # Instrument calibration — an empty extraction would let every fixture "pass" against nothing.
34
+ if ! grep -q 'validate_affected_leg' "$T/fn.sh"; then
35
+ echo "❌ HARNESS-ERROR — validate_affected_leg did not extract from $HOOK."
36
+ echo " Fixtures below would measure an empty function. Aborting rather than reporting green."
37
+ exit 1
38
+ fi
39
+ grep -q '^_marker_template_residue()' "$T/fn.sh" || { echo "❌ HARNESS-ERROR — 헬퍼 미결합"; exit 1; }
40
+
41
+ FAIL=0
42
+ lane() { # $1 = id $2 = expect(BLOCK|PASS) $3 = marker body
43
+ local id="$1" expect="$2" body="$3" rc out
44
+ printf '%s\n' "$body" > "$T/m.marker"
45
+ out=$( bash -c 'set -uo pipefail; . "$1"; validate_affected_leg "$2"' _ "$T/fn.sh" "$T/m.marker" 2>&1 ); rc=$?
46
+ local got=PASS; [ $rc -ne 0 ] && got=BLOCK
47
+ if [ "$got" = "$expect" ]; then
48
+ printf ' ✅ %-40s %s\n' "$id" "$got"
49
+ else
50
+ printf ' ❌ %-40s expected %s, got %s\n %s\n' "$id" "$expect" "$got" "$(printf '%s' "$out" | head -3)"
51
+ FAIL=1
52
+ fi
53
+ }
54
+
55
+ echo "== affected: lanes — PASS side (absent / genuinely filled) =="
56
+ lane P1-absent-is-legal PASS "axis2-model: opus
57
+ axes-run: ⓐ=codex"
58
+ lane P2-real-content PASS "affected: 소비자 install 의 pre-commit 사용자(마커 형식) · 열린 질문 = 필드 강제 시점"
59
+ lane P3-real-content-no-openq PASS "affected: the private companion store 미러 사용자 · 인덱스 재생성 스크립트가 이 필드를 안 읽는다"
60
+ lane P4-korean-multiword PASS "affected: 마커를 grep 으로 감사하는 하류 세션 전부 · 열린 질문 = 강제 시점을 정할 것인가"
61
+
62
+ echo "== affected: lanes — BLOCK side (holes that must stay closed) =="
63
+ lane G1-placeholder-none-ko BLOCK "affected: 없음"
64
+ lane G2-placeholder-tbd BLOCK "affected: TBD"
65
+ lane G3-placeholder-tbd-lower BLOCK "affected: tbd"
66
+ lane G4-placeholder-dash BLOCK "affected: -"
67
+ lane G5-placeholder-na BLOCK "affected: n/a"
68
+ lane G6-empty-field BLOCK "affected:"
69
+ lane G7-duplicate-line BLOCK "affected: 소비자 install 사용자
70
+ affected: 정정 — 사실은 필드 하네스 사용자다"
71
+ lane G8-nearmiss-plural BLOCK "affects: 소비자 install 사용자"
72
+ lane G9-nearmiss-space BLOCK "affected : 소비자 install 사용자"
73
+ lane G10-nearmiss-typo BLOCK "affeted: 소비자 install 사용자"
74
+ lane G11-too-short BLOCK "affected: TBD 확인"
75
+ # 🟥 _marker_template_residue 는 힌트에서 파생한 자리표시자만 본다(v3, defeater 의 R1~R4 가
76
+ # 이미 겪은 형태). `affected:` 는 힌트에 아직 실려 있지 않아서 필드-고유 자리표시자는 못
77
+ # 잡는다 — 명명된 잔여(defeater 절 참고). 잡히는 것은 다른 필드와 **공유하는** `<...>` 뿐이다.
78
+ lane G13-shared-hint-placeholder BLOCK "affected: <...>"
79
+
80
+ echo
81
+ echo "== 실물 코퍼스 대조 (있으면) =="
82
+ # 실물 마커에 affected: 가 아직 없는 것이 정상이다 — 이 필드는 오늘 신설이다. 부재 마커도
83
+ # 여전히 PASS 여야 한다는 것을 실물로도 한 번 더 확인한다(계기가 실물 코퍼스에서도 안 죽는가).
84
+ _real="$REPO_ROOT/tracks/_meta/.axes_23_passed_fix_chamber-run-doctrine-vocabulary_2026-09-04.marker"
85
+ if [ -f "$_real" ]; then
86
+ lane R1-real-corpus-marker-still-passes PASS "$(cat "$_real")"
87
+ else
88
+ echo " ⚠️ 실물 마커 부재 — SKIPPED (아직 오늘자 the private companion store 미러가 없을 수 있다, 통과 아님)"
89
+ fi
90
+
91
+ echo
92
+ if [ $FAIL -eq 0 ]; then echo "AFFECTED LANES: PASS"; else echo "AFFECTED LANES: FAIL"; fi
93
+ exit $FAIL
@@ -34,19 +34,41 @@ sed -n '/^validate_crossfamily_leg()/,/^}/p' "$HOOK" > "$T/fn.sh"
34
34
 
35
35
  # Instrument calibration: an empty extraction would let every fixture "pass" against
36
36
  # nothing. Assert the function body actually arrived before measuring anything.
37
- if ! grep -q 'DEGRADED_PANEL_UNUSED' "$T/fn.sh"; then
38
- echo "❌ HARNESS-ERROR validate_crossfamily_leg did not extract from $HOOK."
39
- echo " Fixtures below would measure an empty function. Aborting rather than reporting green."
37
+ # '_res_active' is inside the residency= token block (2026-09-05) RESIDENCY_TOKEN_GRACE_DATE
38
+ # itself lives OUTSIDE the function (same placement as DEFEATER_GRACE_DATE next to
39
+ # validate_defeater_leg), so it is not part of this extraction and is supplied by the harness
40
+ # below, exactly like GRACE_RES is.
41
+ if ! grep -q 'DEGRADED_PANEL_UNUSED' "$T/fn.sh" || ! grep -q '_res_active' "$T/fn.sh"; then
42
+ echo "❌ HARNESS-ERROR — validate_crossfamily_leg did not extract from $HOOK (or the residency"
43
+ echo " token block is missing from it). Fixtures below would measure an empty/stale function."
40
44
  exit 1
41
45
  fi
42
46
 
43
- mk() { printf "$1" > "$T/$2"; } # $1 = marker body, $2 = fixture name
44
- run() { bash -c "source '$T/fn.sh'; validate_crossfamily_leg '$1'" >/dev/null 2>&1; }
47
+ GRACE_RES=$(grep -m1 '^RESIDENCY_TOKEN_GRACE_DATE=' "$HOOK" | sed -E 's/.*"(.*)"/\1/')
48
+ [ -n "$GRACE_RES" ] || { echo "❌ HARNESS-ERROR RESIDENCY_TOKEN_GRACE_DATE 못 읽었다"; exit 1; }
49
+ # Every fixture below this point (k*/b*/x*/c*) tests crossfamily's PRE-EXISTING shape rules —
50
+ # panel format, embedding/reranker filters, declined, degrade grounds — none of them are testing
51
+ # the residency-token addition. So mk()/run() stamp a date BEFORE RESIDENCY_TOKEN_GRACE_DATE into
52
+ # the marker filename (the same field validate_crossfamily_leg itself reads via `mdate`), earning
53
+ # exemption the same way a real old marker would — not special-cased in the check function for
54
+ # this suite's convenience. The dedicated residency-token section near the end of this file uses
55
+ # its OWN helper with an explicit, varying date to test the grace boundary on purpose.
56
+ PRE_RES="2026-08-01" # < RESIDENCY_TOKEN_GRACE_DATE (2026-09-05) by construction
57
+ # 🟥 the DATE must be the LAST underscore-segment before `.marker` — validate_crossfamily_leg's
58
+ # own `mdate` extraction is `.*_(YYYY-MM-DD)\.marker$`, which does not match if a fixture-name
59
+ # suffix follows the date (caught by this suite's own fail-before run: k1/k1b/k7 false-BLOCKed
60
+ # because ".._${PRE_RES}_$2.marker" put the name AFTER the date, so mdate came back empty and
61
+ # every pre-existing fixture was silently treated as un-exempt).
62
+ mk() { printf "$1" > "$T/.axes_23_passed_fix_x_$2_${PRE_RES}.marker"; } # $1=body $2=fixture name
63
+ run() {
64
+ bash -c "RESIDENCY_TOKEN_GRACE_DATE='$GRACE_RES'; source '$T/fn.sh'; validate_crossfamily_leg '$T/.axes_23_passed_fix_x_$1_${PRE_RES}.marker'" \
65
+ >/dev/null 2>&1
66
+ }
45
67
 
46
68
  FAIL=0; N=0
47
69
  check() { # $1=fixture $2=expected(PASS|BLOCK) $3=label
48
70
  N=$((N+1))
49
- if run "$T/$1"; then got=PASS; else got=BLOCK; fi
71
+ if run "$1"; then got=PASS; else got=BLOCK; fi
50
72
  if [ "$got" = "$2" ]; then echo "✅ $3 → $got"; else echo "❌ $3 → $got (expected $2)"; FAIL=1; fi
51
73
  }
52
74
 
@@ -166,6 +188,68 @@ check c4 BLOCK "glm-ocr — matches a valid family AND an incapable class"
166
188
  mk 'crossfamily: panel(qwen-embedding-8b) — qwen family\n' c5
167
189
  check c5 BLOCK "qwen-embedding — same overlap, other direction"
168
190
 
191
+ # ── residency= token inside crossfamily grounds (2026-09-05) ──────────────────────────────────
192
+ # Wires scripts/residency_closure_scan.py's own verdict into the SAME typed field this whole
193
+ # suite already calibrates, rather than opening a second marker line. All fixtures above this
194
+ # point are PRE-GRACE (via mk()'s PRE_RES stamp) and therefore exempt by construction — this
195
+ # section is the only place that varies the marker-filename date on purpose, mirroring
196
+ # test_marker_soul_tenet_lanes.sh's `dlane` / test_marker_soul_check_lanes.sh's `planep` pattern.
197
+ # Same ordering requirement as mk()/run() above — date LAST, immediately before `.marker`.
198
+ resfix() { printf "%s\n" "$1" > "$T/.axes_23_passed_fix_x_$3_$2.marker"; } # $1=body $2=date $3=fname
199
+ resrun() { # $1=date $2=fname → rc
200
+ bash -c "RESIDENCY_TOKEN_GRACE_DATE='$GRACE_RES'; source '$T/fn.sh'; validate_crossfamily_leg '$T/.axes_23_passed_fix_x_$2_$1.marker'" \
201
+ >/dev/null 2>&1
202
+ }
203
+ rescheck() { # $1=fname $2=date $3=body $4=expect(PASS|BLOCK) $5=label
204
+ N=$((N+1))
205
+ resfix "$3" "$2" "$1"
206
+ if resrun "$2" "$1"; then got=PASS; else got=BLOCK; fi
207
+ if [ "$got" = "$4" ]; then echo "✅ $5 → $got"; else echo "❌ $5 → $got (expected $4)"; FAIL=1; fi
208
+ }
209
+
210
+ echo
211
+ echo "── residency= token in crossfamily grounds (grace = $GRACE_RES) ──"
212
+ echo "· panel(...) REQUIRES a well-formed residency=CLEAN( token —"
213
+ rescheck r1 "$GRACE_RES" 'crossfamily: panel(codex) — residency=CLEAN(files=7) · R1..R2, 4 findings' \
214
+ PASS "r1 panel + residency=CLEAN( present — required token satisfied"
215
+ rescheck r2 "$GRACE_RES" 'crossfamily: panel(codex) — R1..R2, 4 findings, no residency token at all' \
216
+ BLOCK "r2 panel POST-GRACE with NO residency token → BLOCK (the hole this closes)"
217
+ rescheck r3 "2026-08-20" 'crossfamily: panel(codex) — R1..R2, 4 findings, no residency token at all' \
218
+ PASS "r3 SAME body as r2, PRE-GRACE filename date → exempt (no retroactivity)"
219
+ rescheck r4 "$GRACE_RES" 'crossfamily: panel(codex) — residency=TAINTED(2 files, stripped=no) · sent anyway' \
220
+ BLOCK "r4 panel + residency=TAINTED( co-present — contradiction (sent AND tainted)"
221
+ rescheck r5 "$GRACE_RES" 'crossfamily: panel(codex) — residency=NOT_SCANNED(scanner absent) · sent anyway' \
222
+ BLOCK "r5 panel + residency=NOT_SCANNED( co-present — contradiction (sent AND unscanned)"
223
+ rescheck r6 "$GRACE_RES" 'crossfamily: panel(codex) — residency=WEIRD(oops) · R1, 1 finding' \
224
+ BLOCK "r6 panel + malformed residency token (not one of CLEAN|TAINTED|NOT_SCANNED)"
225
+ rescheck r7 "$GRACE_RES" 'crossfamily: panel(codex) — residency=CLEAN(files=3) · residency=CLEAN(files=9) · dup' \
226
+ BLOCK "r7 panel + TWO residency tokens on one line (first-wins trap, same as dup crossfamily:)"
227
+
228
+ echo "· DEGRADED_*/UNKNOWN/declined — residency= is OPTIONAL, format-only when present —"
229
+ rescheck r8 "$GRACE_RES" 'crossfamily: DEGRADED_SINGLE_FAMILY — residency=TAINTED(2 files, stripped=no) not sent, probed codex/agy, 0 reachable' \
230
+ PASS "r8 DEGRADED + well-formed TAINTED token — optional token, present and valid"
231
+ rescheck r9 "$GRACE_RES" 'crossfamily: DEGRADED_SINGLE_FAMILY — probed codex/agy, 0 reachable' \
232
+ PASS "r9 DEGRADED with NO residency token at all — optional, absence is fine"
233
+ rescheck r10 "$GRACE_RES" 'crossfamily: DEGRADED_SINGLE_FAMILY — residency=BOGUS(oops) probed codex, 0 reachable' \
234
+ BLOCK "r10 DEGRADED + malformed residency token → BLOCK even though the token itself is optional"
235
+ rescheck r11 "$GRACE_RES" 'crossfamily: UNKNOWN — residency=NOT_SCANNED(scanner absent), panel not probed this round' \
236
+ PASS "r11 UNKNOWN + well-formed NOT_SCANNED token"
237
+ rescheck r12 "$GRACE_RES" 'crossfamily: declined — operator declined sidecars, chosen floor, per knowledge/shared/rules/operational_adaptation.md residency=CLEAN(files=2)' \
238
+ PASS "r12 declined + a well-formed CLEAN token appended after the resolvable-record citation"
239
+ rescheck r13 "$GRACE_RES" 'crossfamily: DEGRADED_PANEL_UNUSED — residency=CLEAN(files=1) · residency=TAINTED(files=1) codex+agy reachable, not recruited' \
240
+ BLOCK "r13 DEGRADED + TWO residency tokens — the duplicate guard applies here too, not just panel"
241
+ # r14–r17 (cross-family codex gpt-5.5, 2026-09-05): the first draft grepped the token PREFIX
242
+ # `residency=<KIND>(`, so a token that failed the prefix grep was treated as ABSENT — on the optional
243
+ # path that is a fail-open (malformed reads as "no token, fine"). Presence is now counted first.
244
+ rescheck r14 "$GRACE_RES" 'crossfamily: DEGRADED_SINGLE_FAMILY — residency=CLEAN (files=2) probed codex, 0 reachable' \
245
+ BLOCK "r14 DEGRADED + spaced token 'residency=CLEAN (files=2)' — malformed, must not read as absent"
246
+ rescheck r15 "$GRACE_RES" 'crossfamily: DEGRADED_SINGLE_FAMILY — residency=CLEAN(files=1 probed codex, 0 reachable' \
247
+ BLOCK "r15 DEGRADED + unterminated token 'residency=CLEAN(files=1' (no close paren) → malformed"
248
+ rescheck r16 "$GRACE_RES" 'crossfamily: panel(codex) — residency=CLEAN(files=3, stripped=2) · R1..R3, 8 findings' \
249
+ PASS "r16 panel + CLEAN token whose payload contains a space and a comma (legal inside the parens)"
250
+ rescheck r17 "$GRACE_RES" 'crossfamily: panel(codex) — residency=CLEAN(files=3, stripped=2) — stripped files were named to the reviewer' \
251
+ PASS "r17 control — a second em-dash in the grounds does not break the token parse"
252
+
169
253
  echo
170
254
  if [ "$FAIL" -eq 0 ]; then echo "✅ all $N fixtures behave"; else echo "❌ regression ($N fixtures run)"; fi
171
255
  exit "$FAIL"
@@ -0,0 +1,136 @@
1
+ #!/usr/bin/env bash
2
+ # test_marker_oracle_lanes.sh — regression fixtures for pre-commit's validate_oracle_leg
3
+ # (2026-09-05, ISO/IEC TR 29119-11 alignment — iso_ai_standards_crosswalk.md §4 M1, operator-approved).
4
+ #
5
+ # WHY: the test-oracle problem is the core of TR 29119-11 — «when no expected result exists, what
6
+ # did you decide against?». FH already carries the mechanism (known-pair controls, scorer fixed before
7
+ # results, «not found ≠ 0») but the marker recorded only «controls alive», never WHICH KIND of oracle.
8
+ # `oracle: <kind> — <grounds>` is that record. Closed enum of six:
9
+ # known-pair · metamorphic · back-to-back · a-b (A/B accepted) · human · none (reason REQUIRED).
10
+ #
11
+ # SCOPE — channel, not judgment: enum membership · the kind must be followed by a delimiter or end
12
+ # of line (an enum member as a mere PREFIX — `known-pair2`, `human_review` — is not a member) ·
13
+ # non-vacuous grounds (≥2 words, no placeholder) · `none` must carry its reason · single line ·
14
+ # near-miss keys block by ONE rule: any line whose key starts with `oracle` but is not exactly
15
+ # `oracle:` (`Oracle:` `oracles:` `oracle :` `oracle :` `oracle_type:` `oracle-type:` bare `oracle`)
16
+ # plus the typos `orcale:` `oralce:` `오라클:` — and they block EVEN WHEN a correct `oracle:` line is
17
+ # also present (a shadowed correction is the quietest failure). The hook never judges whether the
18
+ # named oracle was actually used. Absent = pass — adoption is incremental, same shape as
19
+ # affected:/soul-check:/tenets:.
20
+ #
21
+ # Fixtures assert BOTH directions (known-pair): every intended shape is admitted, and every hole
22
+ # this lane closes still blocks. Indentation is exercised on BOTH sides (o6 pass / o19 block) so a
23
+ # leading-space line can neither be skipped nor bypass validation. Where two holes would produce the
24
+ # same BLOCK, the lane also asserts the specific diagnostic (4th arg) so each branch is covered
25
+ # uniquely (cross-family codex #7). o31 runs the leg WITHOUT its helper and expects HARNESS-ERROR,
26
+ # so a fail-open on helper absence cannot hide behind a green suite.
27
+ #
28
+ # Usage: bash scripts/test_marker_oracle_lanes.sh Exit: 0 = all behave; 1 = regression.
29
+
30
+ set -uo pipefail
31
+ REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
32
+ HOOK="$REPO_ROOT/templates/.git-hooks/pre-commit"
33
+ T=$(mktemp -d); trap 'rm -rf "$T"' EXIT
34
+
35
+ # 헬퍼도 같이 추출한다 — 안 하면 다리가 fail-open 이 되고 레인이 거짓 PASS 를 낸다
36
+ # ([[feedback_broken_parser_reports_a_verdict]] — defeater/soul-check/affected 가 이미 겪은 형태).
37
+ sed -n '/^marker_recreate_hint()/,/^}/p' "$HOOK" > "$T/_helpers.sh"
38
+ sed -n '/^_marker_template_residue()/,/^}/p' "$HOOK" >> "$T/_helpers.sh"
39
+ sed -n '/^validate_oracle_leg()/,/^}/p' "$HOOK" > "$T/fn0.sh"
40
+ cat "$T/_helpers.sh" "$T/fn0.sh" > "$T/fn.sh"
41
+
42
+ # Instrument calibration — an empty extraction would let every fixture "pass" against nothing.
43
+ if ! grep -q 'validate_oracle_leg' "$T/fn.sh"; then
44
+ echo "❌ HARNESS-ERROR — validate_oracle_leg did not extract from $HOOK."
45
+ echo " Fixtures below would measure an empty function. Aborting rather than reporting green."
46
+ exit 1
47
+ fi
48
+ grep -q '^_marker_template_residue()' "$T/fn.sh" || { echo "❌ HARNESS-ERROR — 헬퍼 미결합"; exit 1; }
49
+
50
+ FAIL=0
51
+ lane() { # $1 = id $2 = expect(BLOCK|PASS) $3 = marker body [$4 = diagnostic substring that must appear]
52
+ local id="$1" expect="$2" body="$3" needle="${4:-}" rc out
53
+ printf '%s\n' "$body" > "$T/m.marker"
54
+ out=$( bash -c 'set -uo pipefail; . "$1"; validate_oracle_leg "$2"' _ "$T/fn.sh" "$T/m.marker" 2>&1 ); rc=$?
55
+ local got=PASS; [ $rc -ne 0 ] && got=BLOCK
56
+ if [ "$got" != "$expect" ]; then
57
+ printf ' ❌ %-40s expected %s, got %s\n %s\n' "$id" "$expect" "$got" "$(printf '%s' "$out" | head -3)"
58
+ FAIL=1; return
59
+ fi
60
+ if [ -n "$needle" ] && ! printf '%s' "$out" | grep -qF -- "$needle"; then
61
+ printf ' ❌ %-40s %s but the diagnostic «%s» is missing — a different branch fired\n %s\n' "$id" "$got" "$needle" "$(printf '%s' "$out" | head -2)"
62
+ FAIL=1; return
63
+ fi
64
+ printf ' ✅ %-40s %s\n' "$id" "$got"
65
+ }
66
+
67
+ echo "== oracle: lanes — PASS side (absent / every enum member / delimiter variants) =="
68
+ lane o1-absent-is-legal PASS "axis2-model: opus
69
+ axes-run: ⓐ=codex"
70
+ lane o2-known-pair-emdash PASS "oracle: known-pair — 레인 PASS/BLOCK 픽스처 양쪽 + W6 되돌림"
71
+ lane o3-ab-slash-alias PASS "oracle: A/B — ARM/CTRL 한 변수 reps=3"
72
+ lane o3b-ab-hyphen PASS "oracle: a-b — ARM/CTRL 한 변수 reps=3"
73
+ lane o4-none-with-reason PASS "oracle: none — 문서만 변경, 측정 없음"
74
+ lane o5-human-paren-form PASS "oracle: human(운영자 눈검증 2건)"
75
+ lane o6-indented-valid-still-pass PASS " oracle: metamorphic — 입력 변환 관계 (ARM 어형 → CTRL 어형)"
76
+ lane o7-mixed-case-kind PASS "oracle: Known-Pair — 양성 음성 컨트롤 동반"
77
+ lane o7b-back-to-back-no-space PASS "oracle: back-to-back—codex 가 같은 diff 를 읽음"
78
+ lane o32-colon-delimiter PASS "oracle: known-pair: 컨트롤 동반 실행"
79
+ lane o33-middle-dot-delimiter PASS "oracle: a-b · ARM/CTRL reps=3"
80
+ lane o34-value-mention-not-a-key PASS "affected: oracle 채널 사용자 · 열린 질문 = 없음
81
+ oracle: human — 운영자 눈검증 2건"
82
+ lane o35-period-delimiter PASS "oracle: known-pair. 컨트롤 동반 실행 양쪽"
83
+
84
+ echo "== oracle: lanes — BLOCK side (holes that must stay closed; 4th arg = the branch that must fire) =="
85
+ lane o8-enum-outside BLOCK "oracle: pseudo — 3-way 대조" 'enum 밖'
86
+ lane o9-none-without-reason BLOCK "oracle: none" '사유가 필수'
87
+ lane o10-none-placeholder-reason BLOCK "oracle: none — TBD" '사유가 필수'
88
+ lane o11-empty-value BLOCK "oracle:" '비어 있다'
89
+ lane o12-duplicate-line BLOCK "oracle: known-pair — 컨트롤 동반 실행
90
+ oracle: human — 정정, 사실은 눈검증" '둘 이상'
91
+ lane o13-nearmiss-qasp-field-name BLOCK "oracle_type: known-pair — 컨트롤 동반 실행" "정확히 'oracle:'"
92
+ lane o14-nearmiss-capital-key BLOCK "Oracle: known-pair — 컨트롤 동반 실행" "정확히 'oracle:'"
93
+ lane o15-nearmiss-space-colon BLOCK "oracle : known-pair — 컨트롤 동반 실행" "정확히 'oracle:'"
94
+ lane o16-kind-only-no-grounds BLOCK "oracle: known-pair" '자리표시자뿐'
95
+ lane o17-one-word-grounds BLOCK "oracle: human — 운영자" '공허하다'
96
+ lane o18-shared-hint-placeholder BLOCK "oracle: known-pair — <...>" '힌트의 자리표시자'
97
+ lane o19-indented-invalid-blocks BLOCK " oracle: pseudo — 3-way 대조 두 낱말" 'enum 밖'
98
+ lane o20-placeholder-grounds BLOCK "oracle: metamorphic — n/a" '자리표시자뿐'
99
+ # ── cross-family (codex, 2026-09-05) 가 연 구멍들 — 각각 fail-before 를 실측한 뒤 닫았다 ──
100
+ lane o21-enum-prefix-junk BLOCK "oracle: known-pair2 positive negative controls" '구분자 없이'
101
+ lane o22-underscore-after-kind BLOCK "oracle: human_review by Alice" '구분자 없이'
102
+ lane o23-case-variant-beside-exact BLOCK "oracle: known-pair — positive negative controls
103
+ Oracle: human — later correction" "정확히 'oracle:'"
104
+ lane o24-fullwidth-space-colon BLOCK "oracle : known-pair — 컨트롤 동반 실행" "정확히 'oracle:'"
105
+ lane o25-nearmiss-plural BLOCK "oracles: known-pair — 컨트롤 동반 실행" "정확히 'oracle:'"
106
+ lane o26-nearmiss-typo-orcale BLOCK "orcale: known-pair — 컨트롤 동반 실행" "정확히 'oracle:'"
107
+ lane o27-nearmiss-typo-oralce BLOCK "oralce: known-pair — 컨트롤 동반 실행" "정확히 'oracle:'"
108
+ lane o28-nearmiss-korean-key BLOCK "오라클: known-pair — 컨트롤 동반 실행" "정확히 'oracle:'"
109
+ lane o29-nearmiss-hyphen-type BLOCK "oracle-type: known-pair — 컨트롤 동반 실행" "정확히 'oracle:'"
110
+ lane o30-bare-key-no-colon BLOCK "oracle" "정확히 'oracle:'"
111
+ lane o36-unlisted-oracle-star-key BLOCK "oracle.evidence: positive negative controls" "정확히 'oracle:'"
112
+
113
+ # o31 — 헬퍼 없이 함수만 격리 실행하면 «통과»가 아니라 HARNESS-ERROR 여야 한다(fail-open 방지 가지).
114
+ printf '%s\n' "oracle: known-pair — 컨트롤 동반 실행 양쪽" > "$T/m31.marker"
115
+ _o31=$( bash -c 'set -uo pipefail; . "$1"; validate_oracle_leg "$2"' _ "$T/fn0.sh" "$T/m31.marker" 2>&1 ); _rc31=$?
116
+ if [ $_rc31 -ne 0 ] && printf '%s' "$_o31" | grep -qF 'HARNESS-ERROR'; then
117
+ printf ' ✅ %-40s BLOCK (HARNESS-ERROR, not a silent pass)\n' o31-helper-absent-is-not-pass
118
+ else
119
+ printf ' ❌ %-40s expected HARNESS-ERROR without the helper, got rc=%s: %s\n' o31-helper-absent-is-not-pass "$_rc31" "$(printf '%s' "$_o31" | head -1)"
120
+ FAIL=1
121
+ fi
122
+
123
+ echo
124
+ echo "== 실물 코퍼스 대조 (있으면) =="
125
+ # 실물 마커에 oracle: 가 아직 없는 것이 정상이다 — 이 필드는 오늘 신설이다. 부재 마커도 여전히
126
+ # PASS 여야 한다는 것을 실물로도 한 번 더 확인한다(계기가 실물 코퍼스에서도 안 죽는가).
127
+ _real=$(ls -t "$REPO_ROOT"/tracks/_meta/.axes_23_passed_*.marker 2>/dev/null | head -1)
128
+ if [ -n "$_real" ] && [ -f "$_real" ]; then
129
+ lane "R1-real-corpus-$(basename "$_real" | cut -c1-24)" PASS "$(cat "$_real")"
130
+ else
131
+ echo " ⚠️ 실물 마커 부재 — SKIPPED (gitignored tracks/ 가 없는 체크아웃일 수 있다, 통과 아님)"
132
+ fi
133
+
134
+ echo
135
+ if [ $FAIL -eq 0 ]; then echo "ORACLE LANES: PASS"; else echo "ORACLE LANES: FAIL"; fi
136
+ exit $FAIL