@chrono-meta/fh-gate 3.0.0 → 3.1.1
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.
- package/.claude/regression/probes_live.yaml +137 -0
- package/.claude/rules/.residency-patterns.defaults +7 -0
- package/.claude/rules/fh_4axis_gate.md +50 -1
- package/.claude-plugin/marketplace.json +8 -2
- package/AGENTS.md +27 -0
- package/CATALOG.md +17 -0
- package/CLAUDE.md +12 -2
- package/README.ja.md +51 -7
- package/README.ko.md +48 -7
- package/README.md +37 -5
- package/README.zh.md +45 -8
- package/docs/STANDARDS_ALIGNMENT.md +61 -0
- package/docs/USER_GUIDE.md +3 -0
- package/docs/USE_CASES.md +50 -0
- package/docs/model_tier_expectations.md +60 -0
- package/knowledge/shared/harness-core/fh_three_layer_canon.md +30 -10
- package/knowledge/shared/harness-core/field_verdict_crossfamily_gate.md +8 -0
- package/knowledge/shared/harness-core/harness_incubator_doctrine.md +9 -0
- package/knowledge/shared/harness-core/iso_ai_standards_crosswalk.md +139 -0
- package/knowledge/shared/harness-core/measurement-integrity-checklist.md +20 -0
- package/knowledge/shared/learnings/subagent_invocations_log.yaml +451 -4
- package/package.json +40 -2
- package/plugins/fh-commons/.claude-plugin/plugin.json +1 -1
- package/plugins/fh-commons/skills/preprep/README.md +4 -1
- package/plugins/fh-commons/skills/preprep/SKILL.md +99 -3
- package/plugins/fh-commons/skills/preprep/diagram_from_json.py +154 -0
- package/plugins/fh-commons/skills/preprep/fixtures/fixture_R3_negative.pptx +0 -0
- package/plugins/fh-commons/skills/preprep/fixtures/fixture_R3_positive.pptx +0 -0
- package/plugins/fh-commons/skills/preprep/fixtures/mk_slide_fixtures.py +179 -0
- package/plugins/fh-commons/skills/preprep/interslide_deps.py +98 -9
- package/plugins/fh-commons/skills/preprep/lane_adjacent_dup.py +4 -1
- package/plugins/fh-commons/skills/preprep/lane_diagram.py +95 -0
- package/plugins/fh-commons/skills/preprep/lane_geometry.py +181 -0
- package/plugins/fh-commons/skills/preprep/lane_promise.py +4 -1
- package/plugins/fh-commons/skills/preprep/lane_slide_refs.py +134 -0
- package/plugins/fh-commons/skills/preprep/lane_slide_relations.py +300 -0
- package/plugins/fh-commons/skills/preprep/preprep.py +129 -8
- package/plugins/fh-commons/skills/preprep/preprep_wire.py +210 -0
- package/plugins/fh-commons/skills/preprep/presentation_checklist.md +4 -0
- package/plugins/fh-commons/skills/preprep/surfaces.example.yaml +18 -0
- package/plugins/fh-commons/skills/preprep/test_preprep_lanes_rp.py +184 -0
- package/plugins/fh-meta/.claude-plugin/plugin.json +1 -1
- package/plugins/fh-meta/CHANGELOG.md +76 -1
- package/plugins/fh-meta/skills/auto-decorrelation/SKILL.md +87 -2
- package/plugins/fh-meta/skills/frontier-digest/SKILL.md +2 -2
- package/plugins/fh-meta/skills/frontier-digest/SKILL_detail.md +25 -5
- package/plugins/fh-meta/skills/hub-cc-pr-reviewer/SKILL.md +11 -0
- package/plugins/fh-meta/skills/hub-cc-pr-reviewer/SKILL_detail.md +3 -0
- package/plugins/fh-qp/.claude-plugin/plugin.json +22 -0
- package/plugins/fh-qp/README.md +71 -0
- package/plugins/fh-qp/fixtures/evidence_known_clean.txt +3 -0
- package/plugins/fh-qp/fixtures/evidence_known_dirty.txt +6 -0
- package/plugins/fh-qp/fixtures/reach_known_partial.tsv +4 -0
- package/plugins/fh-qp/fixtures/reach_known_reached.tsv +4 -0
- package/plugins/fh-qp/fixtures/reach_known_wall.tsv +3 -0
- package/plugins/fh-qp/fixtures/verdicts_known_bad_branch.tsv +2 -0
- package/plugins/fh-qp/fixtures/verdicts_known_bad_vacuous_machine.tsv +2 -0
- package/plugins/fh-qp/fixtures/verdicts_known_good.tsv +4 -0
- package/plugins/fh-qp/fixtures/verdicts_verify_only.tsv +3 -0
- package/plugins/fh-qp/qp_profile.example.yaml +29 -0
- package/plugins/fh-qp/scripts/qp_tools.sh +216 -0
- package/plugins/fh-qp/skills/qp/SKILL.md +65 -0
- package/plugins/fh-qp/skills/qp-plan/SKILL.md +42 -0
- package/plugins/fh-qp/skills/qp-regress/SKILL.md +45 -0
- package/plugins/fh-qp/skills/qp-run/SKILL.md +49 -0
- package/scripts/chamber_run.sh +14 -5
- package/scripts/com.forge-harness.live-eval.plist +84 -0
- package/scripts/compaction_probe.sh +9 -35
- package/scripts/directional_diff_gate.sh +14 -2
- package/scripts/frontier_digest_autopilot.sh +4 -1
- package/scripts/map_postprocess.py +90 -0
- package/scripts/outbound_query_guard.sh +131 -0
- package/scripts/outbound_query_hook.sh +373 -0
- package/scripts/package_coverage_check.sh +55 -16
- package/scripts/pipe_verdict_guard.sh +41 -1
- package/scripts/probe_live_eval.sh +240 -0
- package/scripts/probe_live_eval_lib.py +579 -0
- package/scripts/proposal_hook.sh +120 -17
- package/scripts/push_zone_check.sh +78 -0
- package/scripts/residency_closure_scan.py +252 -0
- package/scripts/selfcheck.sh +41 -1
- package/scripts/session_close_check.sh +100 -0
- package/scripts/sim_isolated_run.sh +98 -2
- package/scripts/test_action_yml_lanes.sh +160 -0
- package/scripts/test_fh_qp_lanes.sh +105 -0
- package/scripts/test_gate_two_verdicts_lanes.sh +139 -0
- package/scripts/test_map_postprocess_lanes.sh +143 -0
- package/scripts/test_marker_affected_lanes.sh +93 -0
- package/scripts/test_marker_crossfamily_lanes.sh +90 -6
- package/scripts/test_marker_oracle_lanes.sh +136 -0
- package/scripts/test_outbound_query_hook_lanes.sh +433 -0
- package/scripts/test_outbound_query_lanes.sh +87 -0
- package/scripts/test_pipe_verdict_guard_lanes.sh +26 -0
- package/scripts/test_preprep_diagram_lanes.sh +87 -0
- package/scripts/test_preprep_drift_anchor.sh +3 -3
- package/scripts/test_preprep_slide_refs_lanes.sh +169 -0
- package/scripts/test_probe_live_eval_lanes.sh +437 -0
- package/scripts/test_proposal_hook_lanes.sh +22 -1
- package/scripts/test_push_zone_lanes.sh +304 -0
- package/scripts/test_residency_closure_lanes.sh +70 -0
- package/scripts/test_sim_isolated_run_lanes.sh +119 -0
- package/scripts/test_utterance_intake_lanes.sh +414 -0
- package/scripts/test_worktree_reclaim_lanes.sh +70 -0
- package/scripts/transcript_utterances.py +222 -0
- package/scripts/utterance_intake.sh +424 -0
- package/scripts/validate_yaml.sh +27 -0
- package/scripts/worktree_reclaim.sh +95 -0
- package/templates/.git-hooks/pre-commit +353 -1
- package/templates/.git-hooks/pre-push +91 -0
- package/templates/RED_TEAM_REPORT.md +49 -0
- package/templates/settings.PreToolUse.snippet.json +65 -1
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# test_gate_two_verdicts_lanes.sh — pre-commit 의 `_acceptance_evidence_line` 계약을 고정한다.
|
|
3
|
+
#
|
|
4
|
+
# WHY (2026-09-06, frontier-digest 답습 · arXiv:2609.04167 «SWE-Gate»): 그 논문의 실측은
|
|
5
|
+
# 기능 테스트를 통과한 수리 644 건 중 **221 건(34%)** 이 실제 PR 리뷰 코멘트에서 뽑은 제약을
|
|
6
|
+
# 어겼다는 것이고, 처방은 «기능 정확성과 리뷰 제약 충족을 따로 채점하라» 다. 이 훅도 같은
|
|
7
|
+
# 형태였다 — 축이 다 통과하면 «ALL AXES PASSED» 라는 **합성 판정 하나**만 찍혔고, 마커의
|
|
8
|
+
# `crossfamily: DEGRADED_PANEL_UNUSED` 같은 값은 출력 어디에도 안 나왔다.
|
|
9
|
+
#
|
|
10
|
+
# SCOPE — 채널이지 판정이 아니다(§Mechanization Boundary): 이 함수는 «어떤 값이 적혔나」만
|
|
11
|
+
# 찍는다. 값의 진위를 판정하지 않고, 어떤 경우에도 종료코드를 바꾸지 않는다. 그래서 레인도
|
|
12
|
+
# «막나」가 아니라 «가려지나」를 잰다 — 특히 DEGRADED/UNKNOWN 이 조용히 사라지지 않는지.
|
|
13
|
+
#
|
|
14
|
+
# 종료코드: 0 = 계약대로 · 1 = 회귀
|
|
15
|
+
set -uo pipefail
|
|
16
|
+
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
17
|
+
HOOK="$REPO_ROOT/templates/.git-hooks/pre-commit"
|
|
18
|
+
T=$(mktemp -d); trap 'rm -rf "$T"' EXIT
|
|
19
|
+
[ -f "$HOOK" ] || { echo "ⓘ $HOOK absent — subject missing when we looked (NOT a pass)"; exit 2; }
|
|
20
|
+
|
|
21
|
+
sed -n '/^_acceptance_evidence_line()/,/^}/p' "$HOOK" > "$T/fn.sh"
|
|
22
|
+
# 계기 보정: 추출이 비면 아래 픽스처가 «아무것도 아닌 것」을 재고 전부 통과한다.
|
|
23
|
+
if ! grep -q '_acceptance_evidence_line' "$T/fn.sh"; then
|
|
24
|
+
echo "❌ HARNESS-ERROR — _acceptance_evidence_line 이 $HOOK 에서 추출되지 않았다."
|
|
25
|
+
echo " 픽스처가 빈 함수를 잴 참이었다. 초록을 보고하지 않고 중단한다."
|
|
26
|
+
exit 1
|
|
27
|
+
fi
|
|
28
|
+
|
|
29
|
+
FAIL=0
|
|
30
|
+
run() { bash -c 'set -uo pipefail; . "$1"; _acceptance_evidence_line "$2"' _ "$T/fn.sh" "${1:-}" 2>&1; }
|
|
31
|
+
lane() { # $1=id $2=marker body ('' = 마커 없음) $3..=출력에 반드시 있어야 하는 조각들
|
|
32
|
+
local id="$1" body="$2"; shift 2
|
|
33
|
+
local path="" out n rc
|
|
34
|
+
if [ -n "$body" ]; then path="$T/m.marker"; printf '%s\n' "$body" > "$path"; fi
|
|
35
|
+
out=$(run "$path"); rc=$?
|
|
36
|
+
if [ $rc -ne 0 ]; then
|
|
37
|
+
printf ' ❌ %-34s 함수가 rc=%s — 보고 전용인데 실패했다\n %s\n' "$id" "$rc" "$out"; FAIL=1; return
|
|
38
|
+
fi
|
|
39
|
+
for n in "$@"; do
|
|
40
|
+
if ! printf '%s' "$out" | grep -qF -- "$n"; then
|
|
41
|
+
printf ' ❌ %-34s «%s» 가 출력에 없다\n %s\n' "$id" "$n" "$out"; FAIL=1; return
|
|
42
|
+
fi
|
|
43
|
+
done
|
|
44
|
+
printf ' ✅ %-34s %s\n' "$id" "$(printf '%s' "$out" | head -1)"
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
echo "== 수용-근거 줄: 값이 그대로 드러나는가 =="
|
|
48
|
+
|
|
49
|
+
# L1 known-positive — 네 필드가 다 있으면 네 값이 다 나온다
|
|
50
|
+
lane L1-all-four "crossfamily: panel(codex,gemini) — 2라운드
|
|
51
|
+
standpoint: tier2(qasp) — 명령과 출력을 댔다
|
|
52
|
+
thirdparty: checked — 상류 소스 독해
|
|
53
|
+
oracle: known-pair — 양성/음성 픽스처" \
|
|
54
|
+
"crossfamily=panel(codex,gemini)" "standpoint=tier2(qasp)" "thirdparty=checked" "oracle=known-pair"
|
|
55
|
+
|
|
56
|
+
# L2 🟥 하중 레인 — DEGRADED 가 «통과» 뒤에 숨지 않는다 (이 레인 스위트의 존재 이유)
|
|
57
|
+
lane L2-degraded-visible "crossfamily: DEGRADED_PANEL_UNUSED — 패널 미사용
|
|
58
|
+
standpoint: not-applicable — Q0 ⓒ
|
|
59
|
+
oracle: none — 잴 값이 없다" \
|
|
60
|
+
"crossfamily=DEGRADED_PANEL_UNUSED" "standpoint=not-applicable"
|
|
61
|
+
|
|
62
|
+
# L3 UNKNOWN 도 마찬가지 — «안 봤다»가 «봤는데 괜찮다»로 안 접힌다
|
|
63
|
+
lane L3-unknown-visible "crossfamily: UNKNOWN — 안 봤다
|
|
64
|
+
standpoint: UNKNOWN — 안 봤다" \
|
|
65
|
+
"crossfamily=UNKNOWN" "standpoint=UNKNOWN"
|
|
66
|
+
|
|
67
|
+
# L4 부재는 «(없음)» 으로 — 빈 문자열이면 필드가 있었는지조차 안 보인다 (미측정≠0)
|
|
68
|
+
lane L4-absent-is-typed "axis2-model: opus" \
|
|
69
|
+
"crossfamily=(없음)" "standpoint=(없음)" "thirdparty=(없음)" "oracle=(없음)"
|
|
70
|
+
|
|
71
|
+
# L5 마커 자체가 없는 경로(경량 게이트) — 근거가 «있다»고 주장하지 않는다
|
|
72
|
+
lane L5-no-marker "" "n/a" "경량 게이트"
|
|
73
|
+
|
|
74
|
+
# L6 들여쓴 필드도 읽는다(마커는 손으로 쓰는 파일이다)
|
|
75
|
+
lane L6-indented " crossfamily: declined — 근거 있음" "crossfamily=declined"
|
|
76
|
+
|
|
77
|
+
# L7 첫 줄만 — 같은 키가 둘이면 앞의 것을 찍고 조용히 합치지 않는다
|
|
78
|
+
lane L7-first-wins "crossfamily: UNKNOWN — 안 봤다
|
|
79
|
+
crossfamily: panel(codex) — 나중 줄" "crossfamily=UNKNOWN"
|
|
80
|
+
|
|
81
|
+
# L8 🟥 첫 실사용이 잡은 결함 — 괄호 «안»에 공백이 있으면 값이 잘려 문법도 깨진 문자열이 찍혔다
|
|
82
|
+
# (`thirdparty: checked(확인한 것 = …)` → `checked(확인한`). 판별자는 «괄호가 닫혔는가» 하나.
|
|
83
|
+
lane L8-open-paren-trimmed "thirdparty: checked(확인한 것 = 초록 원문 대조)" "thirdparty=checked"
|
|
84
|
+
# L9 반대편 — 닫힌 괄호는 값의 일부라 그대로 살아야 한다(L8 의 수리가 이걸 깨면 안 된다)
|
|
85
|
+
lane L9-closed-paren-kept "crossfamily: panel(codex,gemini) — 2라운드" "crossfamily=panel(codex,gemini)"
|
|
86
|
+
lane L9b-tier-paren-kept "standpoint: tier2(qasp) — 명령과 출력" "standpoint=tier2(qasp)"
|
|
87
|
+
|
|
88
|
+
# L8b 잘린 값이 «그대로» 새어나오지 않는지 직접 확인 (needle 부재를 단언)
|
|
89
|
+
printf 'thirdparty: checked(확인한 것 = x)\n' > "$T/p.marker"
|
|
90
|
+
if run "$T/p.marker" | grep -qF 'checked(확인한'; then
|
|
91
|
+
echo " ❌ L8b 잘린 괄호 문자열이 아직 출력에 있다"; FAIL=1
|
|
92
|
+
else
|
|
93
|
+
echo " ✅ L8b 잘린 괄호 문자열은 출력에 없다"
|
|
94
|
+
fi
|
|
95
|
+
|
|
96
|
+
# ── 컨트롤 A (판별력): 값이 다르면 출력도 달라야 한다 ─────────────────────
|
|
97
|
+
printf 'crossfamily: panel(codex) — x\n' > "$T/a.marker"
|
|
98
|
+
printf 'crossfamily: DEGRADED_SINGLE_FAMILY — y\n' > "$T/b.marker"
|
|
99
|
+
OA=$(run "$T/a.marker"); OB=$(run "$T/b.marker")
|
|
100
|
+
if [ "$OA" != "$OB" ] && printf '%s' "$OB" | grep -qF 'DEGRADED_SINGLE_FAMILY'; then
|
|
101
|
+
echo " ✅ CTRL-A 판별력: 서로 다른 값이 서로 다른 줄을 낸다"
|
|
102
|
+
else
|
|
103
|
+
echo " ❌ CTRL-A 판별력 없음 — 두 마커가 같은 줄을 냈다(계기가 값을 안 읽는다)"; FAIL=1
|
|
104
|
+
fi
|
|
105
|
+
|
|
106
|
+
# ── 컨트롤 B (되돌림): 값 추출을 죽인 뮤턴트에서 L2 가 빨개지는가 ──────────
|
|
107
|
+
# 이게 없으면 «필드를 안 읽는 함수»도 이 스위트를 통과한다.
|
|
108
|
+
sed 's/^ for f in crossfamily.*/ for f in ; do :; done; out="";/' "$T/fn.sh" > "$T/mut.sh"
|
|
109
|
+
if ! grep -q 'out=""' "$T/mut.sh"; then
|
|
110
|
+
echo " ❌ CTRL-B 뮤턴트가 적용되지 않았다 — 되돌림 프로브가 장식이다"; FAIL=1
|
|
111
|
+
else
|
|
112
|
+
MOUT=$(bash -c 'set -uo pipefail; . "$1"; _acceptance_evidence_line "$2"' _ "$T/mut.sh" "$T/b.marker" 2>&1 || true)
|
|
113
|
+
if printf '%s' "$MOUT" | grep -qF 'DEGRADED_SINGLE_FAMILY'; then
|
|
114
|
+
echo " ❌ CTRL-B 뮤턴트인데도 값이 나온다 — 레인이 실물을 안 재고 있다"; FAIL=1
|
|
115
|
+
else
|
|
116
|
+
echo " ✅ CTRL-B 되돌림: 추출을 죽이면 값이 사라진다(레인이 실패할 수 있음을 증명)"
|
|
117
|
+
fi
|
|
118
|
+
fi
|
|
119
|
+
|
|
120
|
+
# ── 컨트롤 C (배선): 훅의 PASS 배너가 이 함수를 실제로 부르는가 ───────────
|
|
121
|
+
# 함수만 있고 호출부가 없으면 산문이다([[feedback_built_but_not_wired]]).
|
|
122
|
+
if grep -q '_acceptance_evidence_line "\${MARKER:-}"' "$HOOK" \
|
|
123
|
+
&& grep -q 'ALL AXES PASSED' "$HOOK"; then
|
|
124
|
+
echo " ✅ CTRL-C 배선: PASS 배너가 이 함수를 부른다"
|
|
125
|
+
else
|
|
126
|
+
echo " ❌ CTRL-C 배선 없음 — 정의만 있고 PASS 배너가 안 부른다"; FAIL=1
|
|
127
|
+
fi
|
|
128
|
+
|
|
129
|
+
# ── 컨트롤 D (무해): 이 함수는 종료코드를 바꾸지 않는다 (채널이지 게이트가 아니다) ──
|
|
130
|
+
run "" >/dev/null 2>&1; RC_NONE=$?
|
|
131
|
+
run "$T/b.marker" >/dev/null 2>&1; RC_DEG=$?
|
|
132
|
+
if [ "$RC_NONE" -eq 0 ] && [ "$RC_DEG" -eq 0 ]; then
|
|
133
|
+
echo " ✅ CTRL-D 채널: 마커 부재도 DEGRADED 도 rc=0 (막지 않는다)"
|
|
134
|
+
else
|
|
135
|
+
echo " ❌ CTRL-D 이 함수가 종료코드를 바꾼다 (none=$RC_NONE degraded=$RC_DEG) — 채널이 아니라 게이트가 됐다"; FAIL=1
|
|
136
|
+
fi
|
|
137
|
+
|
|
138
|
+
echo "── gate-two-verdicts lanes: $([ $FAIL -eq 0 ] && echo 'all green' || echo 'FAILED')"
|
|
139
|
+
exit "$FAIL"
|
|
@@ -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
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
-
|
|
44
|
-
|
|
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 "$
|
|
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
|