@chrono-meta/fh-gate 2.0.1 → 2.2.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.
- package/.claude/rules/fh_4axis_gate.md +77 -1
- package/.claude-plugin/marketplace.json +3 -3
- package/AGENTS.md +26 -3
- package/CLAUDE.md +240 -4
- package/knowledge/shared/harness-core/capability_composition_contract.md +68 -0
- package/knowledge/shared/harness-core/fh_three_layer_canon.md +38 -12
- package/knowledge/shared/harness-core/field_verdict_crossfamily_gate.md +115 -3
- package/knowledge/shared/harness-core/harness_incubator_doctrine.md +82 -0
- package/knowledge/shared/harness-core/harness_terminal_correlation_and_recommendations.md +85 -10
- package/knowledge/shared/harness-core/ship_readiness_gate.md +241 -1
- package/knowledge/shared/learnings/subagent_invocations_log.yaml +102 -0
- package/knowledge/shared/rules/sister_asset_protocol.md +12 -0
- package/package.json +7 -2
- package/plugins/fh-commons/.claude-plugin/plugin.json +1 -1
- package/plugins/fh-commons/agents/quench-challenger.md +6 -1
- package/plugins/fh-commons/skills/ko-tech-writer/fixtures/known_negative.md +19 -0
- package/plugins/fh-commons/skills/ko-tech-writer/fixtures/known_positive.md +20 -0
- package/plugins/fh-meta/.claude-plugin/plugin.json +1 -1
- package/plugins/fh-meta/CHANGELOG.md +88 -0
- package/plugins/fh-meta/skills/auto-decorrelation/SKILL.md +14 -2
- package/plugins/fh-meta/skills/steel-quench/SKILL_detail.md +13 -0
- package/plugins/fh-meta/skills/verify-bidirectional/SKILL.md +34 -1
- package/scripts/capability_registry_check.sh +75 -12
- package/scripts/chamber_run.sh +39 -5
- package/scripts/chamber_witness.sh +25 -0
- package/scripts/fh-gate.sh +16 -1
- package/scripts/ko_tech_writer_calibrate.py +127 -0
- package/scripts/package_coverage_check.sh +10 -0
- package/scripts/prepublish_scope_note.sh +139 -0
- package/scripts/publish_freshness_check.sh +21 -2
- package/scripts/selfcheck.sh +32 -6
- package/scripts/test_fh_gate_regressions.sh +21 -9
- package/scripts/test_ko_tech_writer_lanes.sh +61 -0
- package/scripts/test_marker_axes_run_lanes.sh +87 -3
- package/scripts/test_marker_crossfamily_lanes.sh +31 -1
- package/templates/.git-hooks/pre-commit +225 -10
- package/templates/subagent-tally-hook.json +2 -2
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# prepublish_scope_note.sh — the publish gate must test the ARTIFACT, not this machine.
|
|
3
|
+
#
|
|
4
|
+
# ── 왜 이 파일이 있나 (package.json 은 주석을 못 단다) ────────────────────────────
|
|
5
|
+
# 2026-08-17 실측: `prepublishOnly` 체인이 `scripts/selfcheck.sh` 를 통과 조건으로 삼고
|
|
6
|
+
# 있었고, 그 안의 **peer 하네스 어댑터 레인**은 «이 머신에 어떤 레포가 깔려 있는가» 에 따라
|
|
7
|
+
# 판정이 갈린다. 결과:
|
|
8
|
+
#
|
|
9
|
+
# CI (peer 레포 0개) selfcheck 초록 — PEER_ABSENT 로 SKIP → 출하 가능
|
|
10
|
+
# 클러스터를 쓰는 노드 selfcheck 적색 — 어댑터 없는 peer 계기 → 출하 차단
|
|
11
|
+
#
|
|
12
|
+
# **peer 를 안 깐 머신은 출하할 수 있고, 실제로 클러스터를 쓰는 머신은 출하할 수 없다.**
|
|
13
|
+
# 방향이 거꾸로다. 그리고 이 클래스는 같은 날 반대 방향으로도 밟혔다(픽스처가 gitignored
|
|
14
|
+
# 파일을 인용 → 로컬 초록·CI 적색). 뿌리는 하나다:
|
|
15
|
+
#
|
|
16
|
+
# > 검정이 «저장소에 무엇이 있는가» 가 아니라 «이 머신에 무엇이 있는가» 를 재고 있었다.
|
|
17
|
+
#
|
|
18
|
+
# ── 처방 (`fh_signal_2026-08-17_publish-depends-on-local-env.md` ⓑ) ─────────────
|
|
19
|
+
# `prepublishOnly` 는 **출하물의 성질만** 본다:
|
|
20
|
+
# publish_freshness_check · version_lockstep_check · package_coverage --vs-tarball
|
|
21
|
+
# · public_surface_scan_files
|
|
22
|
+
# peer 클러스터 상태는 출하물의 성질이 아니다. 그래서 selfcheck 는 이 체인에서 빠졌다.
|
|
23
|
+
#
|
|
24
|
+
# 🟥 **빠진 것이 «커버리지 상실» 이 되지 않게 하는 것이 이 스크립트의 실제 일이다.**
|
|
25
|
+
# selfcheck 는 여전히 `npm test` 이고, **CI `validate` 잡이 매 푸시마다 돌린다** — 그리고
|
|
26
|
+
# `validate` 는 main 의 **required check** 다(2026-08-12 실측: legacy
|
|
27
|
+
# `required_status_checks.contexts = ["validate"]`). 즉 출하되는 커밋은 이미 selfcheck 를
|
|
28
|
+
# 통과한 커밋이다. 그 전제가 참인지를 **여기서 기계로 확인한다** — 워크플로에서
|
|
29
|
+
# selfcheck 호출이 사라지면 이 스크립트가 출하를 막는다. 산문 약속이 아니라 채널 검사다.
|
|
30
|
+
#
|
|
31
|
+
# ⚠️ **명시 잔여 — 축소해서 읽지 마라. 아래 넷은 cross-family 실측으로 확정된 구멍이다.**
|
|
32
|
+
# ⓐ 이 검사는 «워크플로가 selfcheck 를 호출한다» 는 **존재**를 볼 뿐, 그 잡이 이 커밋에서
|
|
33
|
+
# 실제로 **초록이었는지**는 안 본다. 그건 ⓒ(CI 태그 출하 배선)의 일이고 미구축이다.
|
|
34
|
+
# ⓐ-2 🟥 **`if: false` 로 꺼진 잡은 못 잡는다**(codex 지적 #2, S). 호출 문자열은 살아 있고
|
|
35
|
+
# 잡은 절대 안 돈다 — 이 게이트는 «커버리지 있음» 을 낸다. YAML 파서 없이는 조건식을
|
|
36
|
+
# 평가할 수 없고, 파서를 여기 넣는 것은 이 스크립트의 일이 아니다. **미봉이라 적는다.**
|
|
37
|
+
# ⓐ-3 🟥 **어느 잡인지 안 본다**(codex 지적 #4, A). 위 주석은 `validate` 잡을 말하는데
|
|
38
|
+
# 술어는 **파일 안 아무 잡이나** 받는다. required check 는 `validate` 하나뿐이므로
|
|
39
|
+
# 다른 잡에 옮겨진 selfcheck 는 필수가 아니게 되지만 이 검사는 통과시킨다.
|
|
40
|
+
# ⓐ-4 🟥 **트리거를 안 본다**(codex 지적 #3, A). `on: pull_request` 만 있어도 통과한다 —
|
|
41
|
+
# 그러면 출하되는 main 커밋 자체는 selfcheck 를 한 번도 안 탄다.
|
|
42
|
+
# ⇒ ⓐ-2~4 를 닫는 정직한 형태는 문자열 검사가 아니라 **`gh api` 로 실제 required check 와
|
|
43
|
+
# 그 커밋의 conclusion 을 읽는 것**이다. 그건 네트워크 의존이라 출하 경로에 넣을지가
|
|
44
|
+
# 별도 결정이고(오프라인 fail-closed 는 또 다른 과차단), 여기서 정하지 않는다.
|
|
45
|
+
# ⓑ `validate` 의 `strict: false` 는 그대로다 — 초록인 체크가 지금의 main 을 본 적이
|
|
46
|
+
# 없을 수 있다(CLAUDE.md §4축 게이트 기계층에 이미 기록된 잔여).
|
|
47
|
+
# ⓒ 어댑터 레인 자체의 수리(레인 분리, 계기 부재 → SKIP)는 **qasp AX-ready 세션으로
|
|
48
|
+
# 라우팅**됐다(`fh_signal_2026-08-17_qasp-adapter-phantom-air.md`). 여기서 손대지 않는다.
|
|
49
|
+
#
|
|
50
|
+
# 종료코드: 0 통과 · 1 커버리지 전제 파손(출하 차단) · 3 계기가 측정 불가(fail-closed)
|
|
51
|
+
|
|
52
|
+
set -u
|
|
53
|
+
|
|
54
|
+
WF=".github/workflows/validate.yml"
|
|
55
|
+
SUBJ="scripts/selfcheck.sh"
|
|
56
|
+
|
|
57
|
+
_note() {
|
|
58
|
+
echo "── prepublish scope ─────────────────────────────────────────────"
|
|
59
|
+
echo " 이 체인은 **출하물의 성질만** 검사한다 (freshness · lockstep ·"
|
|
60
|
+
echo " tarball coverage · public surface). peer 클러스터 상태는 출하물의"
|
|
61
|
+
echo " 성질이 아니므로 selfcheck 는 여기 없다 — CI 'validate' 가 돌린다."
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
check() {
|
|
65
|
+
if [ ! -f "$WF" ]; then
|
|
66
|
+
echo "❌ INSTRUMENT ERROR $WF 가 없다 — selfcheck 의 CI 커버리지를 확인할 수 없다."
|
|
67
|
+
echo " 측정 불가는 통과가 아니다(미측정 ≠ 0). 출하를 막는다."
|
|
68
|
+
return 3
|
|
69
|
+
fi
|
|
70
|
+
# 🟥 주석을 먼저 벗긴다. cross-family(codex/gpt-5.5) 실측 지적 #1(S): 초판은 `grep -qE
|
|
71
|
+
# "selfcheck\.sh"` 였고 **주석 처리된 호출에도 매칭**됐다 — 즉 `# - run: bash
|
|
72
|
+
# scripts/selfcheck.sh` 로 레인을 꺼놓아도 이 게이트는 «커버리지 있음» 을 냈다.
|
|
73
|
+
# 그리고 **언급이 아니라 호출**이어야 한다 — `echo "… scripts/selfcheck.sh …"` 는 실행이
|
|
74
|
+
# 아니다(실물 워크플로에 그런 줄이 실제로 있다). 그래서 줄의 **첫 토큰이 실행자**인 것만 센다.
|
|
75
|
+
# 🟥 첫 수리는 `run:` 을 같은 줄에서 요구했고 **실물에서 즉시 적색**이었다 — 실물은
|
|
76
|
+
# `run: |` 블록 스칼라 안에서 호출한다(`validate.yml:150`). self-test 6/6 초록인데
|
|
77
|
+
# 실물이 빨간 상태였고, 그게 «픽스처가 실물을 대표하지 못한다» 의 교과서 형태다.
|
|
78
|
+
# 그래서 아래 known-pair 는 **실물 형태(블록 스칼라)를 그대로 뜬다.**
|
|
79
|
+
if ! sed 's/#.*//' "$WF" \
|
|
80
|
+
| grep -qE '^[[:space:]]*(-[[:space:]]+run:[[:space:]]*)?(bash|sh|source|\.)[[:space:]]+[^[:space:]]*selfcheck\.sh'; then
|
|
81
|
+
echo "❌ PUBLISH BLOCKED $WF 가 더 이상 $SUBJ 를 호출하지 않는다."
|
|
82
|
+
echo ""
|
|
83
|
+
echo " prepublishOnly 에서 selfcheck 를 뺀 근거는 «CI 가 대신 돌린다» 였다."
|
|
84
|
+
echo " 그 전제가 깨졌으므로 지금 출하하면 selfcheck 커버리지가 **어디에도 없다**."
|
|
85
|
+
echo ""
|
|
86
|
+
echo " 고르는 길 둘:"
|
|
87
|
+
echo " ① $WF 에 selfcheck 호출을 복구한다 (원래 자리)"
|
|
88
|
+
echo " ② selfcheck 를 prepublishOnly 로 되돌린다 — 단 그러면 2026-08-17 의"
|
|
89
|
+
echo " «출하 가능 여부 = 이 머신의 디렉터리 구성» 결함이 함께 돌아온다"
|
|
90
|
+
return 1
|
|
91
|
+
fi
|
|
92
|
+
_note
|
|
93
|
+
echo " ✅ CI 커버리지 확인: $WF 가 $SUBJ 를 호출한다"
|
|
94
|
+
return 0
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
# ── known-pair self-test — 컨트롤이 살아 있는지 보여주는 실행 출력 ────────────────
|
|
98
|
+
if [ "${1:-}" = "--self-test" ]; then
|
|
99
|
+
t=$(mktemp -d) || exit 3
|
|
100
|
+
trap 'rm -rf "$t"' EXIT
|
|
101
|
+
pass=0; fail=0
|
|
102
|
+
_lane() { # $1 = 이름, $2 = 기대 rc, $3 = 워크플로 내용(빈 문자열이면 파일 없음)
|
|
103
|
+
local name="$1" want="$2" body="$3" got
|
|
104
|
+
( cd "$t" && rm -rf .github && if [ -n "$body" ]; then
|
|
105
|
+
mkdir -p .github/workflows && printf '%s\n' "$body" > "$WF"
|
|
106
|
+
fi
|
|
107
|
+
WF="$WF" SUBJ="$SUBJ"; check >/dev/null 2>&1 ); got=$?
|
|
108
|
+
if [ "$got" = "$want" ]; then pass=$((pass+1)); echo " PASS $name (rc=$got)"
|
|
109
|
+
else fail=$((fail+1)); echo " FAIL $name — 기대 rc=$want, 실제 rc=$got"; fi
|
|
110
|
+
}
|
|
111
|
+
echo "prepublish_scope_note.sh --self-test"
|
|
112
|
+
_lane "known-positive: 워크플로가 selfcheck 호출" 0 " - run: bash scripts/selfcheck.sh"
|
|
113
|
+
_lane "known-negative: 호출이 사라짐" 1 " - run: echo hi"
|
|
114
|
+
_lane "계기 부재: 워크플로 파일 자체가 없음" 3 ""
|
|
115
|
+
# ★ cross-family 지적 #1(S) 회귀 앵커 — 주석 처리된 호출은 커버리지가 아니다.
|
|
116
|
+
_lane "★주석 처리된 호출은 커버리지 아님" 1 " # - run: bash scripts/selfcheck.sh
|
|
117
|
+
- run: echo no selfcheck"
|
|
118
|
+
# ★ 과차단 컨트롤 — 주석을 벗기는 수리가 **정상 호출까지** 죽이지 않았는지. 이게 없으면
|
|
119
|
+
# 위 레인은 «전부 차단하는 계기» 로도 초록이 된다(컨트롤 있음 ≠ 판별력 있음).
|
|
120
|
+
_lane "★컨트롤: 주석과 실호출이 같이 있으면 통과" 0 " # - run: bash scripts/selfcheck.sh (구버전)
|
|
121
|
+
- run: bash scripts/selfcheck.sh"
|
|
122
|
+
# ★ 실물 형태 — `run: |` 블록 스칼라 안에서의 호출. 이게 실제 validate.yml 의 모양이고,
|
|
123
|
+
# 초판 정규식은 여기서 죽었다(self-test 는 초록인데 실물이 적색이었다).
|
|
124
|
+
_lane "★실물 형태: run: | 블록 안 호출" 0 " - name: Selfcheck
|
|
125
|
+
run: |
|
|
126
|
+
echo \"=== scripts/selfcheck.sh ===\"
|
|
127
|
+
bash scripts/selfcheck.sh"
|
|
128
|
+
# ★ 언급 ≠ 실행. 같은 블록 안에 echo 로만 이름이 있으면 커버리지가 아니다.
|
|
129
|
+
_lane "★echo 언급만 있으면 커버리지 아님" 1 " - name: Selfcheck
|
|
130
|
+
run: |
|
|
131
|
+
echo \"=== scripts/selfcheck.sh 는 로컬에서 돌린다 ===\"
|
|
132
|
+
echo done"
|
|
133
|
+
echo " ── $pass pass / $fail fail"
|
|
134
|
+
[ "$fail" -eq 0 ] || exit 1
|
|
135
|
+
exit 0
|
|
136
|
+
fi
|
|
137
|
+
|
|
138
|
+
check
|
|
139
|
+
exit $?
|
|
@@ -46,8 +46,27 @@ run_checks() {
|
|
|
46
46
|
# ── ① 워킹트리가 깨끗한가 ────────────────────────────────────────────────
|
|
47
47
|
# `--porcelain` 은 추적 파일의 수정·스테이징·미추적을 전부 낸다. 미추적까지 세는 것은
|
|
48
48
|
# 의도적이다 — 2026-08-13 사고는 **남의 미커밋 파일**이 팩된 것이었다.
|
|
49
|
-
|
|
50
|
-
|
|
49
|
+
# 🟥 cross-family(codex/gpt-5.5, 2026-08-17) 가 이 한 줄에서 S급 3건을 냈고 S1 은 재현됐다.
|
|
50
|
+
# S1 초판 `git status --porcelain 2>/dev/null` 는 stderr 를 버리고 rc 를 안 봐서,
|
|
51
|
+
# **git 이 실패하면 빈 문자열이 「깨끗함」으로 읽혔다.** 비가역 게이트의 fail-open.
|
|
52
|
+
# 재현: printf x >/tmp/badindex; GIT_INDEX_FILE=/tmp/badindex bash <이 파일>
|
|
53
|
+
# S3 `status.showUntrackedFiles=no` 로 untracked 이 은폐된다 → -c 로 덮고 -uall 강제
|
|
54
|
+
# A4 `assume-unchanged`/`skip-worktree` 비트로 tracked 변경이 은폐된다 → ls-files -v 로 검사
|
|
55
|
+
local dirty status_out status_rc
|
|
56
|
+
status_out=$(git -c status.showUntrackedFiles=normal status --porcelain --untracked-files=all 2>&1)
|
|
57
|
+
status_rc=$?
|
|
58
|
+
if [ "$status_rc" -ne 0 ]; then
|
|
59
|
+
bad "git status 가 rc=$status_rc 로 실패했다 — **미측정은 통과가 아니다**:"
|
|
60
|
+
printf '%s\n' "$status_out" | head -5 | sed 's/^/ /'
|
|
61
|
+
return 1
|
|
62
|
+
fi
|
|
63
|
+
local hidden
|
|
64
|
+
hidden=$(git ls-files -v 2>/dev/null | grep -E '^[hS]' || true)
|
|
65
|
+
if [ -n "$hidden" ]; then
|
|
66
|
+
bad "assume-unchanged / skip-worktree 비트가 걸린 tracked 파일 — status 엔 안 보이지만 npm 은 팩한다:"
|
|
67
|
+
printf '%s\n' "$hidden" | head -5 | sed 's/^/ /'
|
|
68
|
+
fi
|
|
69
|
+
dirty=$(printf '%s\n' "$status_out" | grep -vE '^\?\? tracks/' || true)
|
|
51
70
|
if [ -n "$dirty" ]; then
|
|
52
71
|
bad "워킹트리가 깨끗하지 않다 — publish 는 커밋이 아니라 이 트리를 팩한다:"
|
|
53
72
|
printf '%s\n' "$dirty" | head -10 | sed 's/^/ /'
|
package/scripts/selfcheck.sh
CHANGED
|
@@ -308,7 +308,11 @@ fi
|
|
|
308
308
|
# a consumer who simply has no hooks. That is the "unmeasured → clean" move this file legislates
|
|
309
309
|
# against — and worse, the --self-test loop 200 lines below already routes rc=10 to `fail=1` saying
|
|
310
310
|
# "it could not measure, so its verdicts prove nothing". Same file, same code, opposite verdict.
|
|
311
|
-
# (Adversarial review 2026-08-16 found this
|
|
311
|
+
# (Adversarial review 2026-08-16 found this. ⚠️ An earlier draft of this comment cited `:524` for
|
|
312
|
+
# it; the standpoint review measured the real sites — the loop dispatches at :528 and routes
|
|
313
|
+
# rc=10 to fail=1 at :562, and the quoted sentence lives at :429/:475. `:524` is none of them, so
|
|
314
|
+
# a reviewer following it lands on prose. A stale line number in the comment that argues against
|
|
315
|
+
# stale instruments is this commit's own defect class, recorded rather than quietly corrected.)
|
|
312
316
|
#
|
|
313
317
|
# The discriminator is mechanical, not a guess: the three harness-defect paths use plain `echo
|
|
314
318
|
# "❌ harness-error: …"`, while the NOT-INSTALLED path goes through `say()` which `--quiet` suppresses.
|
|
@@ -415,11 +419,30 @@ fi
|
|
|
415
419
|
if [ ! -f scripts/package_coverage_check.sh ]; then
|
|
416
420
|
_absent_subject_verdict "test_package_coverage_lanes.sh" "scripts/package_coverage_check.sh" || fail=1
|
|
417
421
|
elif [ -f scripts/test_package_coverage_lanes.sh ]; then
|
|
418
|
-
|
|
422
|
+
if ! bash scripts/test_package_coverage_lanes.sh; then
|
|
419
423
|
fail=1
|
|
420
424
|
fi
|
|
421
|
-
|
|
422
|
-
|
|
425
|
+
# The CHECKER's semantics are deliberately fail-closed and must not be softened: given a git
|
|
426
|
+
# checkout with no package.json it reports UNMEASURED, never "clean", and its own lane L4 pins
|
|
427
|
+
# exactly that. Applicability is the CALLER's judgment, and it is made mechanically here.
|
|
428
|
+
#
|
|
429
|
+
# A repo that ships no npm surface at all is not "unmeasured npm coverage" — it has no npm
|
|
430
|
+
# coverage to measure. Measured 2026-08-16 in a sibling harness that carries this suite verbatim
|
|
431
|
+
# and is not an npm package: this was a PERMANENT red, on every run forever. A gate that can
|
|
432
|
+
# never go green is not strict, it is a gate people learn to ignore — and this suite is the
|
|
433
|
+
# mandatory-pass surface, so that habit is expensive.
|
|
434
|
+
#
|
|
435
|
+
# Note the asymmetry, because it is the whole point: this repo HAS an npm surface, so if its
|
|
436
|
+
# package.json ever went missing the checker would fire and SHOULD — that is a real defect here.
|
|
437
|
+
# The guard below distinguishes "the surface is absent by nature" from "the surface is missing",
|
|
438
|
+
# which is the distinction `not found ≠ 0` exists to protect.
|
|
439
|
+
if [ -f package.json ] || [ -d bin ]; then
|
|
440
|
+
if ! bash scripts/package_coverage_check.sh; then
|
|
441
|
+
fail=1
|
|
442
|
+
fi
|
|
443
|
+
else
|
|
444
|
+
echo "SKIP package-coverage — NOT APPLICABLE: this repo ships no npm surface"
|
|
445
|
+
echo " (checked mechanically: package.json absent, bin/ absent — not a claim, a test)"
|
|
423
446
|
fi
|
|
424
447
|
else
|
|
425
448
|
echo "FAIL test_package_coverage_lanes.sh: package_coverage_check.sh present but its anchor is missing"
|
|
@@ -507,7 +530,8 @@ for _pair in \
|
|
|
507
530
|
"scripts/residency_closure_scan.py|scripts/test_residency_closure_lanes.sh" \
|
|
508
531
|
"scripts/reviewer_capability_corpus.tsv|scripts/test_reviewer_capability_conformance.sh" \
|
|
509
532
|
"scripts/field_canon_preload.sh|scripts/test_field_canon_lanes.sh" \
|
|
510
|
-
"scripts/stale_clone_guard.sh|scripts/test_stale_clone_guard_lanes.sh"
|
|
533
|
+
"scripts/stale_clone_guard.sh|scripts/test_stale_clone_guard_lanes.sh" \
|
|
534
|
+
"plugins/fh-commons/skills/ko-tech-writer/SKILL.md|scripts/test_ko_tech_writer_lanes.sh"
|
|
511
535
|
do
|
|
512
536
|
_subj="${_pair%%|*}"; _anc="${_pair##*|}"; _lbl="${_anc##*/}"
|
|
513
537
|
if [ ! -f "$_subj" ]; then
|
|
@@ -889,7 +913,9 @@ else
|
|
|
889
913
|
fail=1
|
|
890
914
|
fi
|
|
891
915
|
|
|
892
|
-
# marker axes-run lanes — 훅의
|
|
916
|
+
# marker axes-run lanes — 훅의 축 자기대조 형식 검사(§CLAUDE.md 3층 자기 대조)의 앵커.
|
|
917
|
+
# ⚠️ 「4축」이라고 적혀 있었는데 2026-08-17 부로 **6축 분기가 생겼다**(마커 날짜 >= 그 날이면
|
|
918
|
+
# 기호 키 ⓐ~ⓕ 요구, 미만이면 옛 ASCII 넷). 산문 층이라 레인은 안 깨지고 조용히 stale 이었다.
|
|
893
919
|
# subject 부재를 FAIL 로 두는 이유는 branch-claim 블록과 같다: 이 subject 는 pre-commit 훅
|
|
894
920
|
# 자체이고 files[] 에 있으므로 «정당한 부재» 가 없다. 부재 = 삭제다.
|
|
895
921
|
if [ ! -f templates/.git-hooks/pre-commit ]; then
|
|
@@ -21,6 +21,18 @@ pass=0; fail=0
|
|
|
21
21
|
TMPROOT=$(mktemp -d "${TMPDIR:-/tmp}/fh_gate_test_XXXXXX")
|
|
22
22
|
trap 'rm -rf "$TMPROOT"' EXIT
|
|
23
23
|
|
|
24
|
+
# Review SUBJECT — a fixture this suite creates, NOT a file it hopes the repo has.
|
|
25
|
+
# Every gate invocation below used the literal `package.json` as its subject. That is an npm-package
|
|
26
|
+
# assumption smuggled into a test: this suite passed here only because this repo happens to ship one.
|
|
27
|
+
# MEASURED 2026-08-16 in a sibling harness that carries this file verbatim and ships no package.json:
|
|
28
|
+
# every lane that actually invokes the gate on a file returned 10 ("0 of N targets resolved →
|
|
29
|
+
# harness error") — 12 failures from this one hardcoded fixture, on top of 18 more from the same
|
|
30
|
+
# missing file killing the gate's version read outright. The suite was measuring the fixture's
|
|
31
|
+
# existence, not the gate's behaviour. A test that only runs in the repo it was written in is not a
|
|
32
|
+
# regression anchor; it is a local habit.
|
|
33
|
+
SUBJECT="$TMPROOT/subject_under_review.txt"
|
|
34
|
+
printf 'fixture file for gate regression lanes — content is irrelevant, existence is not\n' > "$SUBJECT"
|
|
35
|
+
|
|
24
36
|
# --- fake codex backend: writes $FAKE_PAYLOAD to the -o path, exits 0 ---
|
|
25
37
|
# Doubles as the live demonstration of the PATH-trusting residual documented in fh-gate.sh.
|
|
26
38
|
FAKEBIN="$TMPROOT/bin"; mkdir -p "$FAKEBIN"
|
|
@@ -68,16 +80,16 @@ check() {
|
|
|
68
80
|
fi
|
|
69
81
|
}
|
|
70
82
|
|
|
71
|
-
run_gate() { env "$@" bash "$GATE" "
|
|
83
|
+
run_gate() { env "$@" bash "$GATE" "$SUBJECT" quick test; }
|
|
72
84
|
run_fake() {
|
|
73
85
|
local payload="$1"; shift
|
|
74
86
|
env PATH="$FAKEBIN:$PATH" FH_BACKEND=codex FH_MODEL=fake FAKE_PAYLOAD="$payload" \
|
|
75
|
-
bash "$GATE" "
|
|
87
|
+
bash "$GATE" "$SUBJECT" quick test
|
|
76
88
|
}
|
|
77
89
|
run_fake_claude() {
|
|
78
90
|
local payload="$1"; shift
|
|
79
91
|
env PATH="$FAKEBIN:$PATH" FH_BACKEND=claude FH_MODEL=fake FAKE_PAYLOAD="$payload" \
|
|
80
|
-
bash "$GATE" "
|
|
92
|
+
bash "$GATE" "$SUBJECT" quick test
|
|
81
93
|
}
|
|
82
94
|
|
|
83
95
|
echo "── argument / env validation ──"
|
|
@@ -89,7 +101,7 @@ check "FH_TIMEOUT non-integer rejected" 11 run_gate FH_TIMEOUT="abc" FH_DRY_RUN=
|
|
|
89
101
|
check "FH_TIMEOUT integer accepted (no regression)" 12 run_gate FH_TIMEOUT=120 FH_DRY_RUN=1
|
|
90
102
|
# Newline in FH_CALLER forges an extra column-0 FH_GATE_VERDICT line in the legacy contract.
|
|
91
103
|
check "FH_CALLER newline injection rejected" 11 \
|
|
92
|
-
env FH_CALLER=$'ci\nFH_GATE_VERDICT: PASS' FH_DRY_RUN=1 bash "$GATE" "
|
|
104
|
+
env FH_CALLER=$'ci\nFH_GATE_VERDICT: PASS' FH_DRY_RUN=1 bash "$GATE" "$SUBJECT" quick
|
|
93
105
|
|
|
94
106
|
echo
|
|
95
107
|
echo "── dry-run must not be readable as PASS ──"
|
|
@@ -129,7 +141,7 @@ check "claude path: clean PASS → exit 0" 0 run_fake_claude \
|
|
|
129
141
|
check "claude path: is_error envelope → fails closed" 10 \
|
|
130
142
|
env PATH="$FAKEBIN:$PATH" FH_BACKEND=claude FH_MODEL=fake \
|
|
131
143
|
FAKE_ENVELOPE='{"is_error":true,"subtype":"error","structured_output":{"status":"SUCCESS","verdict":"PASS","findings_count":0,"findings_a":0,"findings_b":0,"findings":[]}}' \
|
|
132
|
-
bash "$GATE" "
|
|
144
|
+
bash "$GATE" "$SUBJECT" quick test
|
|
133
145
|
|
|
134
146
|
echo
|
|
135
147
|
echo "── legitimate verdicts still work (no over-blocking regression) ──"
|
|
@@ -199,7 +211,7 @@ NOENT="$TMPROOT/noentropy"; mkdir -p "$NOENT"
|
|
|
199
211
|
printf '#!/usr/bin/env bash\nexit 1\n' > "$NOENT/openssl"; chmod +x "$NOENT/openssl"
|
|
200
212
|
printf '#!/usr/bin/env bash\nexit 1\n' > "$NOENT/od"; chmod +x "$NOENT/od"
|
|
201
213
|
check "no CSPRNG (openssl+od stubbed to fail) → fails closed" 10 \
|
|
202
|
-
env PATH="$NOENT:$PATH" FH_DRY_RUN=1 bash "$GATE" "
|
|
214
|
+
env PATH="$NOENT:$PATH" FH_DRY_RUN=1 bash "$GATE" "$SUBJECT" quick test
|
|
203
215
|
|
|
204
216
|
echo
|
|
205
217
|
echo "── FH_BACKEND=cross (decorrelated review) ──"
|
|
@@ -225,7 +237,7 @@ check_out() { # <name> <expected-exit> <grep-ere that MUST appear> -- <cmd...>
|
|
|
225
237
|
run_cross() { # <claude-payload> <codex-payload>
|
|
226
238
|
env PATH="$FAKEBIN:$PATH" FH_BACKEND=cross FH_MODEL=fake \
|
|
227
239
|
FAKE_PAYLOAD_CLAUDE="$1" FAKE_PAYLOAD_CODEX="$2" FAKE_PAYLOAD="$1" \
|
|
228
|
-
bash "$GATE" "
|
|
240
|
+
bash "$GATE" "$SUBJECT" quick test
|
|
229
241
|
}
|
|
230
242
|
|
|
231
243
|
check_out "cross: both PASS → PASS, decorrelated" 0 'FH_GATE_DECORRELATED: yes' \
|
|
@@ -241,9 +253,9 @@ ONELEG="$TMPROOT/oneleg"; mkdir -p "$ONELEG"; cp "$FAKEBIN/claude" "$ONELEG/clau
|
|
|
241
253
|
check_out "cross: codex absent → single leg, DECORRELATED: no" 0 'FH_GATE_DECORRELATED: no' \
|
|
242
254
|
env PATH="$ONELEG:/usr/bin:/bin" FH_BACKEND=cross FH_MODEL=fake \
|
|
243
255
|
FAKE_PAYLOAD_CLAUDE="$CROSS_PASS" FAKE_PAYLOAD="$CROSS_PASS" \
|
|
244
|
-
bash "$GATE" "
|
|
256
|
+
bash "$GATE" "$SUBJECT" quick test
|
|
245
257
|
check "cross: no family available → fails closed" 10 \
|
|
246
|
-
env PATH="/usr/bin:/bin" FH_BACKEND=cross bash "$GATE" "
|
|
258
|
+
env PATH="/usr/bin:/bin" FH_BACKEND=cross bash "$GATE" "$SUBJECT" quick test
|
|
247
259
|
|
|
248
260
|
echo
|
|
249
261
|
echo "────────────────────────────────────────────────────────────────────"
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# test_ko_tech_writer_lanes.sh — `ko-tech-writer` Step 2 / Step 4-b 판별력 known-pair (얇은 래퍼).
|
|
3
|
+
#
|
|
4
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
5
|
+
# 왜 필요한가 — 이 레포가 자기 스캔을 UNCALIBRATED 로 강등해 놨다
|
|
6
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
7
|
+
# `knowledge/shared/harness-core/harness_terminal_correlation_and_recommendations.md`:
|
|
8
|
+
# Step 2 "잔여 0건(양성 컨트롤 동반)" 주장 -> UNCALIBRATED: 컨트롤·재현커맨드·출력이
|
|
9
|
+
# 하나도 없다. 재현 불가한 0은 0의 증거가 아니다
|
|
10
|
+
# Step 4 "전칭 단정 잔여 0건" 주장 -> UNCALIBRATED, 자기반증(그 시점에 3건 생존)
|
|
11
|
+
#
|
|
12
|
+
# 챔버 런 #12(2026-08-17, prosody-lens KILL)가 이 부채를 배출 판정의 결정적 근거로
|
|
13
|
+
# 인용했다 — "같은 계열 계기가 미보정인데 하나 더 짓는 것은 미보정 계기의 증식".
|
|
14
|
+
# 운영자 결정: 새 계기보다 이 부채가 먼저. 이 파일이 그 부채를 갚는다.
|
|
15
|
+
#
|
|
16
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
17
|
+
# 왜 래퍼인가 — 패턴이 셸 밖에 있어야 하는 실측 이유가 둘 있다
|
|
18
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
19
|
+
# 초판은 패턴을 이 파일 안에 두고 엔진을 ripgrep 으로 고정했다. CI 가 둘 다 반증했다
|
|
20
|
+
# (2026-08-17, ubuntu-latest):
|
|
21
|
+
# 1) ripgrep 부재 -> 스위트가 rc=10 으로 죽었다(HARNESS ERROR). 설계는 옳게 작동했다 —
|
|
22
|
+
# SKIP 이 아니라 시끄럽게 죽었다 — 그러나 CI 에서 못 도는 앵커는 앵커가 아니다.
|
|
23
|
+
# 2) 이식성 린트 적중 — 셸 파일 안의 한글 문자 범위는 GNU grep C 로케일에서 exit 2 로
|
|
24
|
+
# 죽고 그 결과가 "무매치"로 읽힌다. 즉 fail-open.
|
|
25
|
+
# 두 결함이 같은 처방으로 닫힌다: **패턴을 Python 으로 옮긴다.** SKILL.md 가 허용 엔진으로
|
|
26
|
+
# Python `re` 를 이미 명시했으므로 격하가 아니라 등가 엔진으로의 이동이고, python3 는
|
|
27
|
+
# 세 허용 엔진 중 어디에나 있는 유일한 것이다.
|
|
28
|
+
#
|
|
29
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
30
|
+
# 무엇이 재어지는가 — Step 2 다섯 클래스 + Step 4-b 두 패턴
|
|
31
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
32
|
+
# Step 2 C1 줄표 이어붙임 · C2 용어-머리 · C3 콜론 나열투 · C4 조각문 · C5 소유 직역
|
|
33
|
+
# Step 4-b C6 전칭 어휘 · C6b 부정형 전칭
|
|
34
|
+
# 각 레인의 PASS 조건은 **양성 >= 1 그리고 음성 == 0** 이다. 존재 확인이 아니라 판별
|
|
35
|
+
# 확인이라는 점이 핵심 — known-negative 가 없으면 "오탐이 있나" 조차 못 재고, 그러면
|
|
36
|
+
# PASS 는 "늘 잡는 계기" 와 구분되지 않는다.
|
|
37
|
+
#
|
|
38
|
+
# 🟥 첫 캘리브레이션이 정본의 과장을 드러냈다: SKILL.md 는 Step 2 의 "앞 다섯 줄은 기계
|
|
39
|
+
# 검출 가능" 이라 적었는데, 실제로 grep 을 싣고 있는 것은 C1·C5 둘뿐이고 C2·C3·C4 의
|
|
40
|
+
# 검출 힌트 칸은 산문 서술이다. 특히 C4 는 패턴이 없어서, 내가 임의로 지은 패턴이
|
|
41
|
+
# 양성을 0건으로 놓쳤다 — 계기를 세우자마자 대상의 결함이 나온 형태다.
|
|
42
|
+
#
|
|
43
|
+
# 🟥 이 스위트가 PASS 를 내도 주장하면 안 되는 것: "Step 2 잔여 0건" · "Step 4 잔여 0건".
|
|
44
|
+
# 판별력과 잔여는 다른 명제이고, 후자는 문서마다 따로 잰다. 그 혼동이 애초에 두 행을
|
|
45
|
+
# UNCALIBRATED 로 강등시킨 사유다.
|
|
46
|
+
#
|
|
47
|
+
# 사용: bash scripts/test_ko_tech_writer_lanes.sh
|
|
48
|
+
# exit: 0 판별 확인(전 레인 PASS) · 1 판별 실패 · 10 harness error(python3/픽스처 부재)
|
|
49
|
+
|
|
50
|
+
set -uo pipefail
|
|
51
|
+
|
|
52
|
+
SRC="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
53
|
+
PY="$SRC/ko_tech_writer_calibrate.py"
|
|
54
|
+
|
|
55
|
+
command -v python3 >/dev/null 2>&1 || {
|
|
56
|
+
echo "harness error: python3 absent -- cannot measure. Not a pass." >&2; exit 10; }
|
|
57
|
+
[ -f "$PY" ] || { echo "harness error: missing $PY" >&2; exit 10; }
|
|
58
|
+
|
|
59
|
+
echo "-- ko-tech-writer Step2/4-b discrimination known-pair --"
|
|
60
|
+
python3 "$PY"
|
|
61
|
+
exit $?
|
|
@@ -2,9 +2,17 @@
|
|
|
2
2
|
# test_marker_axes_run_lanes.sh — regression fixtures for pre-commit validate_marker_axes_run.
|
|
3
3
|
#
|
|
4
4
|
# 대상: CLAUDE.md §3층 자기 대조가 요구하는 마커 3줄 중 **기계로 볼 수 있는 두 줄** —
|
|
5
|
-
# axes-run:
|
|
5
|
+
# axes-run: 각 축의 실행 여부. 마커 날짜에 따라 **배열이 둘**이다:
|
|
6
|
+
# >= 2026-08-17 ⓐ계열 · ⓑ입장 · ⓒ격리그라운딩 · ⓓ3자대면 · ⓔ첫실사용 · ⓕ되돌림
|
|
7
|
+
# < 2026-08-17 a=계열 · b=첫실사용 · c=기록그라운딩 · d=되돌림 (옛 ASCII 4축)
|
|
6
8
|
# controls: 각 축 컨트롤의 **생사**
|
|
7
9
|
#
|
|
10
|
+
# 🟥 이 헤더 자신이 2026-08-17 까지 그 충돌을 저지르고 있었다 — «네 축(ⓐ다른계열 ⓑ첫실사용
|
|
11
|
+
# ⓒ기록그라운딩 ⓓ되돌림)» 이라고, **기호 표기로 옛 4축 의미**를 가르쳤다. 훅 본문이
|
|
12
|
+
# 「옛 b=첫실사용은 지금 ⓔ」 라고 경고하는 파일의 짝인데 정작 짝이 반대를 가르쳤고,
|
|
13
|
+
# 같은 세션이 이 파일을 11→25레인으로 늘리면서 **헤더는 안 봤다**. 잡은 것은 pmh-dev
|
|
14
|
+
# 입장리뷰(tier2)이고, 이 파일은 전파 자산이라 그 오류가 하류로 배송되던 중이었다.
|
|
15
|
+
#
|
|
8
16
|
# 🟥 이 스위트가 증명하지 않는 것 (계약을 픽스처로 못박는다):
|
|
9
17
|
# 「a=codex」 라고 적혀 있을 때 codex 가 **실제로 돌았는지**는 검사하지 않는다.
|
|
10
18
|
# 마커의 계약은 form + non-vacuity + auditability 이지 provenance 가 아니다 —
|
|
@@ -25,12 +33,19 @@ T=$(mktemp -d); trap 'rm -rf "$T"' EXIT
|
|
|
25
33
|
# 훅에서 함수 + 유예 상수를 그대로 뽑아 쓴다. 복제하면 드리프트하므로 **소스가 하나**다.
|
|
26
34
|
{
|
|
27
35
|
grep -E '^AXES_RUN_GRACE_DATE=' "$HOOK"
|
|
36
|
+
grep -E '^SIX_AXES_GRACE_DATE=' "$HOOK"
|
|
28
37
|
sed -n '/^validate_marker_axes_run()/,/^}/p' "$HOOK"
|
|
29
38
|
} > "$T/fn.sh"
|
|
30
39
|
|
|
31
40
|
# 계기 자체가 살아있는지 먼저 본다 — 추출이 실패하면 모든 레인이 «통과»로 초록이 된다.
|
|
32
|
-
|
|
33
|
-
|
|
41
|
+
# 🟥 상수를 **각각** 확인한다. 하나로 뭉뚱그리면 새 상수가 추출에서 빠져도 초록이고,
|
|
42
|
+
# 그 경우 함수는 unbound 로 죽으며 모든 레인이 BLOCK 으로 몰려 «6축이 잘 막는다» 로
|
|
43
|
+
# 오독된다 — 계기 고장이 판정으로 렌더되는 형태다.
|
|
44
|
+
for _c in AXES_RUN_GRACE_DATE SIX_AXES_GRACE_DATE; do
|
|
45
|
+
grep -q "^${_c}=" "$T/fn.sh" || { echo "❌ 계기 사망: 훅에서 ${_c} 추출 실패 — 레인 결과를 신뢰하지 마라"; exit 1; }
|
|
46
|
+
done
|
|
47
|
+
if ! grep -q 'validate_marker_axes_run()' "$T/fn.sh"; then
|
|
48
|
+
echo "❌ 계기 사망: 훅에서 함수 추출 실패 — 레인 결과를 신뢰하지 마라"
|
|
34
49
|
exit 1
|
|
35
50
|
fi
|
|
36
51
|
|
|
@@ -90,6 +105,75 @@ check "$G2" PASS "파일명에 날짜가 없으면 판정 근거가 없다 →
|
|
|
90
105
|
B6=$(mk 2026-08-10 'axis2-evidence: PASS')
|
|
91
106
|
check "$B6" BLOCK "★유예일 **당일**은 요구한다 (경계가 < 이지 <= 가 아님을 고정)"
|
|
92
107
|
|
|
108
|
+
# ── 6축 확장 (2026-08-17) ───────────────────────────────────────────────────
|
|
109
|
+
# 표기법이 배열을 선언한다: ASCII(a=…d=)=옛 4축 · 기호(ⓐ=…ⓕ=)=현 6축.
|
|
110
|
+
# 그래서 여기서 고정할 것이 셋이다 — ⓐ 새 날짜는 기호 여섯을 요구하나 ⓑ 옛 날짜는
|
|
111
|
+
# 건드리지 않나(소급 차단 금지) ⓒ **혼용이 막히나**(혼용을 허용하면 판별 가능성이
|
|
112
|
+
# 통째로 사라지고, 옛 b=/d= 는 새 배열에서 다른 축이라 무음 오독이 된다).
|
|
113
|
+
D_SIX=2026-08-18 # 6축 유예일 이후
|
|
114
|
+
SIX_OK='axes-run: ⓐ=codex(4건) ⓑ=→standpoint ⓒ=none ⓓ=none ⓔ=CI배선 ⓕ=되돌림(국소성)
|
|
115
|
+
controls: alive — known-positive 3히트 · known-negative 0
|
|
116
|
+
standpoint: not-applicable'
|
|
117
|
+
|
|
118
|
+
S1=$(mk "$D_SIX" "$SIX_OK")
|
|
119
|
+
check "$S1" PASS "6축: 기호 여섯 + 컨트롤 + standpoint → 통과"
|
|
120
|
+
|
|
121
|
+
S2=$(mk "$D_SIX" 'axes-run: ⓐ=codex ⓑ=→standpoint ⓒ=none ⓓ=none ⓕ=되돌림
|
|
122
|
+
controls: alive — ok
|
|
123
|
+
standpoint: tier1')
|
|
124
|
+
check "$S2" BLOCK "6축: ⓔ 가 빠짐 → 차단"
|
|
125
|
+
|
|
126
|
+
S3=$(mk "$D_SIX" 'axes-run: ⓐ=codex b=CI배선 ⓒ=none ⓓ=none ⓔ=실사용 ⓕ=되돌림
|
|
127
|
+
controls: alive — ok')
|
|
128
|
+
check "$S3" BLOCK "★6축: ASCII 키가 기호 키를 대신함 → 차단 (ⓑ 가 빠졌다는 뜻이므로 missing-key 가 잡는다)"
|
|
129
|
+
|
|
130
|
+
# ★ 오탐 컨트롤 — 전용 혼용 가드를 지었다가 **실측 오탐 때문에 지웠다**. 값이 자유 산문이라
|
|
131
|
+
# `f=0.9` 나 `a=제어 b=처리` 가 값 안에 정당하게 들어온다. 과차단은 `--no-verify` 를 훈련시키고
|
|
132
|
+
# 그건 같은 훅의 Destructive-Op 게이트까지 무장해제한다. 이 두 레인이 그 삭제를 고정한다 —
|
|
133
|
+
# 누가 «안전하게» 혼용 가드를 되살리면 여기서 빨개진다.
|
|
134
|
+
S3b=$(mk "$D_SIX" 'axes-run: ⓐ=codex ⓑ=→standpoint ⓒ=none ⓓ=none ⓔ=실사용 ⓕ=되돌림 f=0.9 상승
|
|
135
|
+
controls: alive — ok
|
|
136
|
+
standpoint: tier1')
|
|
137
|
+
check "$S3b" PASS "★오탐 컨트롤: 값 안의 f=0.9 는 혼용이 아니다 → 통과"
|
|
138
|
+
|
|
139
|
+
S3c=$(mk "$D_SIX" 'axes-run: ⓐ=codex ⓑ=→standpoint ⓒ=none ⓓ=none ⓔ=arm a=제어 b=처리 비교 ⓕ=되돌림
|
|
140
|
+
controls: alive — ok
|
|
141
|
+
standpoint: tier1')
|
|
142
|
+
check "$S3c" PASS "★오탐 컨트롤: 값 안의 a=제어 b=처리 는 혼용이 아니다 → 통과"
|
|
143
|
+
|
|
144
|
+
S4=$(mk "$D_SIX" 'axes-run: a=codex b=CI배선 c=none d=되돌림
|
|
145
|
+
controls: alive — ok')
|
|
146
|
+
check "$S4" BLOCK "★6축 유예 후 옛 ASCII 넷만 → 차단 (같은 글자가 다른 축을 가리키므로 그대로 두면 무음 재해석)"
|
|
147
|
+
|
|
148
|
+
S5=$(mk "$D_NEW" 'axes-run: a=codex b=CI배선 c=none d=되돌림
|
|
149
|
+
controls: alive — ok')
|
|
150
|
+
check "$S5" PASS "★컨트롤: 6축 유예 **이전** 마커는 옛 ASCII 넷으로 그대로 통과 (소급 차단 없음)"
|
|
151
|
+
|
|
152
|
+
S6=$(mk 2026-08-17 'axes-run: a=codex b=CI c=none d=되돌림
|
|
153
|
+
controls: alive — ok')
|
|
154
|
+
check "$S6" BLOCK "★6축 유예일 **당일**은 요구한다 (경계가 < 임을 고정 — 상수를 바꾸면 빨개진다)"
|
|
155
|
+
|
|
156
|
+
S7=$(mk "$D_SIX" 'axes-run: ⓐ=none ⓑ=→standpoint ⓒ=none ⓓ=none ⓔ=none ⓕ=none
|
|
157
|
+
controls: n/a — 이번 델타에 측정이 없다')
|
|
158
|
+
check "$S7" BLOCK "★ⓑ=→standpoint 인데 standpoint: 줄이 없음 → 차단 (죽은 포인터)"
|
|
159
|
+
|
|
160
|
+
# ★ fail-open 컨트롤 — 초판은 화살표(→)를 필수로 봤고, 자기 프로브가 `ⓑ=standpoint`(화살표 없음)이
|
|
161
|
+
# 검사를 통째로 비껴가는 것을 실측했다. 죽은 포인터가 표기 하나로 통과하면 그건 fail-open 이다.
|
|
162
|
+
S7b=$(mk "$D_SIX" 'axes-run: ⓐ=none ⓑ=standpoint ⓒ=none ⓓ=none ⓔ=none ⓕ=none
|
|
163
|
+
controls: n/a — 측정 없음')
|
|
164
|
+
check "$S7b" BLOCK "★화살표 없는 ⓑ=standpoint 도 차단 (표기 하나로 검사를 비껴가면 fail-open)"
|
|
165
|
+
|
|
166
|
+
S7c=$(mk "$D_SIX" 'axes-run: ⓐ=none ⓑ=→standpoint(qasp) ⓒ=none ⓓ=none ⓔ=none ⓕ=none
|
|
167
|
+
controls: n/a — 측정 없음
|
|
168
|
+
standpoint: tier1b(qasp)')
|
|
169
|
+
check "$S7c" PASS "★컨트롤: 포인터에 대상이 붙어도 standpoint: 줄이 있으면 통과 (과차단 방지)"
|
|
170
|
+
|
|
171
|
+
S8=$(mk "$D_SIX" 'axes-run: ⓐ=codex ⓑ=→standpoint ⓒ=none ⓓ=none ⓔ=실사용 ⓕ=되돌림
|
|
172
|
+
axes-run: ⓐ=거짓말 ⓑ=x ⓒ=x ⓓ=x ⓔ=x ⓕ=x
|
|
173
|
+
controls: alive — ok
|
|
174
|
+
standpoint: tier1')
|
|
175
|
+
check "$S8" BLOCK "★axes-run 줄이 둘 → 차단 (첫 줄만 읽히므로 둘째 줄은 «없다»가 아니라 «안 보인다»)"
|
|
176
|
+
|
|
93
177
|
# ── ★ 배선 판별자 — 위 레인들은 **함수만** 본다 ──────────────────────────────
|
|
94
178
|
# 실측 2026-08-09: 훅의 호출부에서 `&& validate_marker_axes_run "$MARKER"` 를 떼고
|
|
95
179
|
# 위 11 레인을 돌렸더니 **전부 초록**이었다. 함수는 멀쩡하고 아무도 안 부르는 상태를
|
|
@@ -55,8 +55,38 @@ mk 'crossfamily: panel(codex,gemini) — R1..R3, 9 findings, 8 fixed 1 refuted,
|
|
|
55
55
|
check k1 PASS "panel() with family list + verdict"
|
|
56
56
|
mk 'crossfamily: panel(codex, gemini) — spaced list, the S-grade over-block\n' k1b
|
|
57
57
|
check k1b PASS "panel(codex, gemini) — SPACE after comma (was truncated to 'panel(codex,')"
|
|
58
|
+
# ⚠️ CONTRACT CHANGE 2026-08-16 — `declined` now requires grounds. It used to pass BARE, which is
|
|
59
|
+
# what the old k3 asserted. That was the enum's soft spot: `declined` means the OPERATOR declined
|
|
60
|
+
# sidecars (UAP), a chosen floor — a factual, attributable claim — and it was the ONLY value with no
|
|
61
|
+
# grounds requirement, so an author who had merely judged decorrelation unnecessary reached for the
|
|
62
|
+
# nearest permissive token and passed clean. Measured in this repo the same day, by a session that
|
|
63
|
+
# had corrected a different marker's crossfamily value hours earlier and had the correct semantics
|
|
64
|
+
# printed to it in the hook's own error text. A closed enum stops prose from collapsing distinct
|
|
65
|
+
# states; it does not stop a WRONG token from being a VALID one — that is what these three lanes are.
|
|
66
|
+
# The old bare-declined expectation is not deleted silently; it is inverted here with its reason.
|
|
58
67
|
mk 'crossfamily: declined\n' k3
|
|
59
|
-
check k3
|
|
68
|
+
check k3 BLOCK "declined BARE — no grounds (was PASS until 2026-08-16; an operator decision has a record)"
|
|
69
|
+
mk 'crossfamily: declined — operator declined sidecars, chosen floor, per knowledge/shared/rules/operational_adaptation.md\n' k3b
|
|
70
|
+
check k3b PASS "declined + a record path that RESOLVES (the documented form)"
|
|
71
|
+
# The real defect, verbatim-shaped: a genuine authorial judgment, plausibly worded, wrong token.
|
|
72
|
+
# This is the known-POSITIVE that is not synthetic — it is the sentence that actually shipped.
|
|
73
|
+
mk 'crossfamily: declined — this is a small, mechanically-verifiable hook-wiring fix, not a verdict-enum change; the standpoint axis is the decorrelation applied here\n' k3c
|
|
74
|
+
check k3c BLOCK "declined + AUTHOR-judgment grounds — a choice with a panel reachable is DEGRADED_PANEL_UNUSED"
|
|
75
|
+
# ── the three a cross-family review (codex/gpt-5.6-terra) found against the FIRST version of this
|
|
76
|
+
# lane, which grepped for vocabulary (operator|uap|consent|…). All three reproduced on the spot,
|
|
77
|
+
# which is why the check is now a RESOLVABLE-RECORD test rather than a word list.
|
|
78
|
+
mk 'crossfamily: declined — declined because I judged it unnecessary myself\n' k3d
|
|
79
|
+
check k3d BLOCK "SELF-VALIDATING: echoing the value satisfied the old word list ('declin')"
|
|
80
|
+
mk 'crossfamily: declined — operator documentation says authors may choose freely\n' k3e
|
|
81
|
+
check k3e BLOCK "VACUOUS: carries 'operator' but describes no declination at all"
|
|
82
|
+
mk 'crossfamily: declined — operator declined sidecars, see tracks/_meta/nonexistent_record.md\n' k3f
|
|
83
|
+
check k3f BLOCK "FABRICATED PATH: cites a record that does not exist on disk"
|
|
84
|
+
# ⚠️ INSTRUMENT NOTE, recorded because it nearly produced a false verdict on this very lane:
|
|
85
|
+
# the first attempt to known-pair these ran the extracted function under **zsh** (this operator's
|
|
86
|
+
# interactive shell) while the hook runs under **bash**. zsh does not word-split an unquoted
|
|
87
|
+
# parameter expansion, so the `for _tok in $reason` scan saw ONE token and the legitimate
|
|
88
|
+
# path-citing case scored a false BLOCK — the code was correct and the harness was wrong.
|
|
89
|
+
# Re-run under bash: 7/7. If you hand-test this function, invoke it with `bash`, not the login shell.
|
|
60
90
|
mk 'crossfamily: DEGRADED_SINGLE_FAMILY — probed codex/agy/4090, 0 capable reachable\n' k4
|
|
61
91
|
check k4 PASS "DEGRADED_SINGLE_FAMILY + substantive reason"
|
|
62
92
|
mk 'crossfamily: UNKNOWN — consent unset, panel not probed this round\n' k5
|