@chrono-meta/fh-gate 2.1.0 → 2.3.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/capabilities/adapters/qasp-new-code-anchor.cap +5 -2
- package/.claude/capabilities/adapters/qasp-web-rules.cap +130 -0
- package/.claude/rules/fh_4axis_gate.md +30 -0
- package/.claude-plugin/marketplace.json +3 -3
- package/AGENTS.md +10 -3
- package/knowledge/shared/harness-core/harness_terminal_correlation_and_recommendations.md +36 -2
- package/knowledge/shared/harness-core/ship_readiness_gate.md +36 -0
- package/knowledge/shared/learnings/subagent_invocations_log.yaml +36 -0
- package/package.json +8 -1
- package/plugins/fh-commons/.claude-plugin/plugin.json +1 -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 +98 -0
- package/scripts/adapters/fixtures/qasp_web_rules_known_negative.json +7 -0
- package/scripts/adapters/fixtures/qasp_web_rules_known_positive.json +7 -0
- package/scripts/adapters/qasp_web_rules.sh +171 -0
- package/scripts/capability_effect_probe.sh +104 -0
- package/scripts/chamber_run.sh +15 -2
- package/scripts/ko_tech_writer_calibrate.py +127 -0
- package/scripts/publish_freshness_check.sh +21 -2
- package/scripts/selfcheck.sh +2 -1
- package/scripts/test_adapter_lanes.sh +37 -1
- package/scripts/test_ko_tech_writer_lanes.sh +61 -0
- package/scripts/test_marker_axes_run_lanes.sh +19 -0
- package/templates/.git-hooks/pre-commit +20 -0
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# qasp_web_rules.sh — FH 소유 **어댑터**. peer `qasp` 의
|
|
3
|
+
# `scripts/web_rules_pr_scan.sh`(1.5막 웹 셀렉터-안정성 rule 세트)를 클러스터 호출 가능한
|
|
4
|
+
# typed capability 로 감싼다.
|
|
5
|
+
#
|
|
6
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
7
|
+
# 무엇을 재나
|
|
8
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
9
|
+
# **웹 E2E 테스트 diff 에 로케이터/대기 규율 위반이 들어왔는가.** rule 5종
|
|
10
|
+
# (`web-coordinate-click` · `web-wait-for-timeout` 등 — LOCATOR_DISCIPLINE ·
|
|
11
|
+
# ASSERTION_STRENGTH 렌즈). peer 헤더의 기원 실측: 이 rule 테이블은 2026-07-24 부터
|
|
12
|
+
# 있었지만 **부르는 사람이 없었다**(2026-07-27 실측: 프로덕션 호출자 0건, tests/ 와
|
|
13
|
+
# 자기 자신뿐). *"호출자가 없는 검출기는 산문이지 게이트가 아니다."*
|
|
14
|
+
# 진입점은 **peer 소유 코드**이고 이 어댑터가 만든 것이 아니다.
|
|
15
|
+
#
|
|
16
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
17
|
+
# VERDICT ENUM (선언과 정확히 일치해야 한다)
|
|
18
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
19
|
+
# 0 CLEAN 위반 0건
|
|
20
|
+
# 1 FINDINGS 위반 존재
|
|
21
|
+
# 2 ARGS 인자 오류 — 판정이 아니다
|
|
22
|
+
# 3 ENGINE_ERROR 스캔이 못 돌았다(python 부재 · 입력 불가 · 대상 0개) — 판정이 아니다
|
|
23
|
+
# 20 PEER_ABSENT **peer 하네스가 이 머신에 없다**
|
|
24
|
+
#
|
|
25
|
+
# 🟥 **`2` 는 peer 셸 헤더에 안 적혀 있다** — 그 헤더는 `0/1/3` 만 적는다. 그러나 하위
|
|
26
|
+
# python CLI(`src/static_review/web_rules.py`)가 `parser.error()` 를 쓰고 argparse 는
|
|
27
|
+
# **exit 2** 를 내며, 셸 래퍼는 `exit "$RC"` 로 **그대로 전파**한다(실측 2026-08-17).
|
|
28
|
+
# 즉 문서화된 계약보다 실제 계약이 한 값 넓다. 어댑터는 **실제 계약**을 선언한다 —
|
|
29
|
+
# 안 그러면 argparse 오류가 `*)` 로 떨어져 HARNESS_ERROR 가 되고, 「인자를 틀렸다」가
|
|
30
|
+
# 「계기가 깨졌다」로 렌더된다.
|
|
31
|
+
#
|
|
32
|
+
# 🟥 값 셋이 「안 쟀다」의 서로 다른 얼굴이다:
|
|
33
|
+
# ARGS(2) 인자가 틀렸다 (호출에 대한 진술)
|
|
34
|
+
# ENGINE_ERROR(3) 하네스는 있는데 못 쟀다 (계기에 대한 진술)
|
|
35
|
+
# PEER_ABSENT(20) 그 노드가 이 머신에 없다 (클러스터에 대한 진술)
|
|
36
|
+
# peer 가 자기 규율로 이미 적어 뒀다 — *"exit 3 을 0 으로 뭉개지 않는다. 못 잰 것을
|
|
37
|
+
# 깨끗하다고 보고하는 순간 게이트가 장식이 된다."* 어댑터는 그 위에 PEER_ABSENT 만 더한다.
|
|
38
|
+
#
|
|
39
|
+
# ⚠️ 하위 exit 을 **전파하지 않는다** — 아래 case 가 명시 재매핑이다. 같은 숫자가 이
|
|
40
|
+
# 클러스터 안에서 다른 사건을 뜻한다(`new_code_anchor_scan.sh` 의 `4=OUT_OF_SCOPE` ·
|
|
41
|
+
# `judge_rer.sh` 의 `4=BLOCKED` · `pull_cli.py` 의 `4=NO_CORPUS`).
|
|
42
|
+
#
|
|
43
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
44
|
+
# 🟥 `writes: write-local` — read-only 가 **아니다.** cold 실측이 근거다
|
|
45
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
46
|
+
# 진입점이 `python -m src.static_review.web_rules` 를 부르고, 그 import 폐포가
|
|
47
|
+
# `src/config/settings.py` 의 **import 시점 `ensure_dirs()`** 에 닿는다.
|
|
48
|
+
#
|
|
49
|
+
# warm(운영자 실 트리) 실측 : 트리 변화 **없음** ← 🟥 이것을 근거로 쓰면 안 된다
|
|
50
|
+
# cold(격리 클론) 실측 : 신규 엔트리 **25개**
|
|
51
|
+
# · `__pycache__` 디렉토리 5 + `.pyc` 파일 18
|
|
52
|
+
# · **빈 디렉토리 `./tmp` · `./uploads`** ← `mkdir(exist_ok=True)` 의 산물
|
|
53
|
+
#
|
|
54
|
+
# warm 이 깨끗해 보인 이유는 그 디렉토리들이 **이미 있어서** mkdir 이 무음 no-op 이었기
|
|
55
|
+
# 때문이다 — **warm cache 아티팩트지 read-only 의 증거가 아니다.**
|
|
56
|
+
# ★그리고 저 «빈 디렉토리» 는 2026-08-17 이전의 M6 이펙트 프로브가 **구조적으로 못 보던**
|
|
57
|
+
# 형태다(`_snapshot` 이 `-type f` 라 파일만 셌다). FH PR #422 로 그 축을 넣었고,
|
|
58
|
+
# 그래서 이 선언은 **이제 기계로 검증·반증된다.** 그 전이었으면 거짓 `read-only` 로
|
|
59
|
+
# 선언해도 VERIFIED 를 받았다.
|
|
60
|
+
#
|
|
61
|
+
# ⚠️ **명시 잔여 — M6 는 peer 트리를 안 본다.** 프로브의 «샌드박스 밖» 감시면은 임시
|
|
62
|
+
# 카나리아 하나와 `$HOME` 엔트리 목록뿐이다. 어댑터는 **정의상 peer 트리에서 돌므로**,
|
|
63
|
+
# 이 capability 의 가장 실질적인 쓰기 표면이 프로브의 감시 밖에 있다. 위 cold 수치는
|
|
64
|
+
# 프로브 판정이 아니라 **손 실측**이다. 이 갭은 이 파일이 만든 게 아니라 어댑터 클래스
|
|
65
|
+
# 전체의 성질이고, 여기 이름으로 남긴다.
|
|
66
|
+
#
|
|
67
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
68
|
+
# 캘리브레이션 쌍 — **FH 소유 픽스처**, 오프라인·결정적
|
|
69
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
70
|
+
# 양성: `fixtures/qasp_web_rules_known_positive.json` — `page.wait_for_timeout(3000)`
|
|
71
|
+
# → `web-wait-for-timeout` 발화 → FINDINGS
|
|
72
|
+
# 음성: `fixtures/qasp_web_rules_known_negative.json` — `get_by_role(...).click()`
|
|
73
|
+
# → 위반 0 → CLEAN
|
|
74
|
+
# ★왜 peer 픽스처를 안 쓰나: peer 의 `tests/` 는 그 레포 사정으로 움직인다. 남의 트리에
|
|
75
|
+
# 있는 픽스처에 의존하는 계기는 **별도 계기가 아니라 그 레포의 일부**다.
|
|
76
|
+
# ★왜 PR 번호 모드를 안 쓰나: `gh api` 가 필요해 **네트워크·인증에 의존**하고, 그러면
|
|
77
|
+
# 등록 바의 M4 가 「계기가 판별하는가」가 아니라 「오늘 gh 가 되는가」를 재게 된다.
|
|
78
|
+
# ★residency: peer 는 company 다. 이 두 픽스처는 **우리가 지은 합성 diff** 라 조직
|
|
79
|
+
# 식별자가 없다 — 캘리브레이션 출력에 조직 경로가 실리지 않는다.
|
|
80
|
+
#
|
|
81
|
+
# 사용법
|
|
82
|
+
# qasp_web_rules.sh --diff-json <path> # peer 레포에서 실행, 실제 diff 스캔
|
|
83
|
+
# qasp_web_rules.sh --known-positive # expect FINDINGS
|
|
84
|
+
# qasp_web_rules.sh --known-negative # expect CLEAN
|
|
85
|
+
set -o pipefail
|
|
86
|
+
|
|
87
|
+
PEER_NAME="qasp"
|
|
88
|
+
RC_CLEAN=0; RC_FINDINGS=1; RC_ARGS=2; RC_ENGINE=3; RC_HARNESS=10; RC_PEER_ABSENT=20
|
|
89
|
+
|
|
90
|
+
_harness_error() { printf 'qasp-web-rules: HARNESS_ERROR — %s\n' "$1" >&2; exit "$RC_HARNESS"; }
|
|
91
|
+
_args_error() { printf 'qasp-web-rules: ARGS — %s\n' "$1" >&2; exit "$RC_ARGS"; }
|
|
92
|
+
_peer_absent() { printf 'qasp-web-rules: PEER_ABSENT — %s\n' "$1" >&2; exit "$RC_PEER_ABSENT"; }
|
|
93
|
+
|
|
94
|
+
SELF_DIR="$(cd -P "$(dirname "${BASH_SOURCE[0]:-$0}")" && pwd)"
|
|
95
|
+
_LIB="$SELF_DIR/peer_resolve.sh"
|
|
96
|
+
[ -r "$_LIB" ] || _harness_error "peer 해석 라이브러리 부재: $_LIB"
|
|
97
|
+
# shellcheck source=/dev/null
|
|
98
|
+
. "$_LIB"
|
|
99
|
+
|
|
100
|
+
# ── ① 인자 먼저 검문한다 — peer 해석보다 **앞**이다 ─────────────────────────
|
|
101
|
+
# 🟥 순서가 뒤집혀 있었다(2026-08-17 CI 실측). 초판은 peer 를 먼저 풀었고, 그래서
|
|
102
|
+
# **같은 잘못된 호출이 머신마다 다른 값**을 냈다:
|
|
103
|
+
# peer 있는 머신(내 로컬) : `qasp_web_rules.sh` (모드 없음) → ARGS(2)
|
|
104
|
+
# peer 없는 머신(CI) : 같은 호출 → HARNESS_ERROR(10)
|
|
105
|
+
# 즉 검정이 «어떻게 불렀나» 가 아니라 **«이 머신에 뭐가 깔렸나»** 를 재고 있었다.
|
|
106
|
+
# 이 레포가 오늘 출하 레인에서 이미 데인 클래스와 같다.
|
|
107
|
+
#
|
|
108
|
+
# ★원리: 이 파일 헤더가 스스로 갈라 놓은 대로 **ARGS 는 «호출에 대한 진술»** 이고
|
|
109
|
+
# **PEER_ABSENT 는 «클러스터에 대한 진술»** 이다. 두 사실은 독립이고, 호출이 틀렸다는
|
|
110
|
+
# 것은 클러스터를 보지 않고도 안다. 오타를 낸 사람에게 «qasp 를 깔아라» 라고 말하면
|
|
111
|
+
# 진짜 문제에서 멀어진다.
|
|
112
|
+
# ⚠️ 자매 어댑터(`qasp_new_code_anchor.sh`)는 아직 **peer-first** 다 — 그쪽 enum 레인이
|
|
113
|
+
# `_lane_peer` 로 가려져 있어서 이 어긋남이 안 보였다. 여기서만 고쳤고, 그쪽 정렬은
|
|
114
|
+
# 별건으로 남긴다(고치면 그쪽 레인의 SKIP 가드도 같이 걷어야 한다).
|
|
115
|
+
#
|
|
116
|
+
# ★경계는 «호출 + FH 로컬 파일» 이다. peer 를 봐야만 알 수 있는 것만 ② 로 내린다.
|
|
117
|
+
# 픽스처도 여기다 — `$SELF_DIR/fixtures/` 는 **우리 소유**라 peer 유무와 무관하다.
|
|
118
|
+
MODE="${1:-}"
|
|
119
|
+
DIFF_JSON=""
|
|
120
|
+
case "$MODE" in
|
|
121
|
+
--known-positive) DIFF_JSON="$SELF_DIR/fixtures/qasp_web_rules_known_positive.json" ;;
|
|
122
|
+
--known-negative) DIFF_JSON="$SELF_DIR/fixtures/qasp_web_rules_known_negative.json" ;;
|
|
123
|
+
--diff-json)
|
|
124
|
+
DIFF_JSON="${2:-}"
|
|
125
|
+
[ -n "$DIFF_JSON" ] || _args_error "--diff-json 뒤에 경로가 필요하다"
|
|
126
|
+
# 읽을 수 없는 입력은 **호출 오류**다 — peer 가 있든 없든 같은 값이어야 한다.
|
|
127
|
+
[ -r "$DIFF_JSON" ] || _args_error "읽을 수 없는 diff-json: $DIFF_JSON" ;;
|
|
128
|
+
'') _args_error "모드 미지정 — 돌지 않은 실행을 판정으로 렌더하지 않는다" ;;
|
|
129
|
+
*) _args_error "알 수 없는 모드 '$MODE'" ;;
|
|
130
|
+
esac
|
|
131
|
+
|
|
132
|
+
# 픽스처 부재는 **ARGS 가 아니라 HARNESS_ERROR** 다 — 캘리브레이션 쌍이 사라진 것은
|
|
133
|
+
# 「호출을 잘못했다」가 아니라 **「이 계기가 자기 판별력을 증명할 수 없다」**는 뜻이다.
|
|
134
|
+
case "$MODE" in
|
|
135
|
+
--known-positive|--known-negative)
|
|
136
|
+
[ -r "$DIFF_JSON" ] || _harness_error "캘리브레이션 픽스처 부재: $DIFF_JSON" ;;
|
|
137
|
+
esac
|
|
138
|
+
|
|
139
|
+
# ── ② peer 해석 ─────────────────────────────────────────────────────────────
|
|
140
|
+
PEER_ROOT="$(fh_peer_resolve "$PEER_NAME")"; _rrc=$?
|
|
141
|
+
case "$_rrc" in
|
|
142
|
+
0) ;;
|
|
143
|
+
1) _peer_absent "peer «$PEER_NAME» 가 이 머신에 없다 — 안 쟀다(통과가 아니다)" ;;
|
|
144
|
+
2) _harness_error "peer «$PEER_NAME» 해석이 모호하다 — 어느 트리를 쟀는지 말할 수 없으므로 판정하지 않는다" ;;
|
|
145
|
+
*) _harness_error "peer 루트 목록 자체가 없다 — 전제 파손" ;;
|
|
146
|
+
esac
|
|
147
|
+
|
|
148
|
+
ENTRY="$PEER_ROOT/scripts/web_rules_pr_scan.sh"
|
|
149
|
+
[ -r "$ENTRY" ] || _harness_error "peer 진입점 부재: $ENTRY (하네스는 있으나 계기가 없다)"
|
|
150
|
+
|
|
151
|
+
# peer 의 진입점은 `git rev-parse --show-toplevel` 로 자기 루트를 잡으므로 **cwd 가 peer 여야**
|
|
152
|
+
# 한다. 판정은 파이프를 통과시키지 않는다(PIPE-VERDICT).
|
|
153
|
+
# `PYTHON` 을 명시로 넘긴다 — peer 의 기본값이 `$REPO_ROOT/.venv/bin/python` 이라 이미 맞지만,
|
|
154
|
+
# 그 폴백(`command -v python3`)이 발동하면 **의존성 없는 인터프리터**로 떨어져 ENGINE_ERROR 가
|
|
155
|
+
# 나고, 그건 「이 머신에 뭐가 깔렸나」를 재는 것이 된다. 명시하면 그 갈림이 안 생긴다.
|
|
156
|
+
_PY="$PEER_ROOT/.venv/bin/python"
|
|
157
|
+
if [ -x "$_PY" ]; then
|
|
158
|
+
out=$(cd "$PEER_ROOT" && PYTHON="$_PY" bash "$ENTRY" --files "$DIFF_JSON" 2>&1); rc=$?
|
|
159
|
+
else
|
|
160
|
+
out=$(cd "$PEER_ROOT" && bash "$ENTRY" --files "$DIFF_JSON" 2>&1); rc=$?
|
|
161
|
+
fi
|
|
162
|
+
printf '%s\n' "$out"
|
|
163
|
+
|
|
164
|
+
# ── 명시 재매핑 (같은 숫자, 다른 사건 — 전파가 아니다) ───────────────────────
|
|
165
|
+
case "$rc" in
|
|
166
|
+
0) printf 'qasp-web-rules: CLEAN — 웹 로케이터/대기 규율 위반 0건\n'; exit "$RC_CLEAN" ;;
|
|
167
|
+
1) printf 'qasp-web-rules: FINDINGS — 위반이 있다\n'; exit "$RC_FINDINGS" ;;
|
|
168
|
+
2) printf 'qasp-web-rules: ARGS — peer 계기가 인자를 거부했다(판정이 아니다)\n' >&2; exit "$RC_ARGS" ;;
|
|
169
|
+
3) printf 'qasp-web-rules: ENGINE_ERROR — 스캔이 돌지 못했다. «위반 0» 이 아니다\n' >&2; exit "$RC_ENGINE" ;;
|
|
170
|
+
*) _harness_error "peer 진입점이 자기 계약 {0,1,2,3} 밖의 $rc 를 반환했다" ;;
|
|
171
|
+
esac
|
|
@@ -105,6 +105,33 @@ _snapshot() { # $1=dir → "sha path" 목록 (정렬)
|
|
|
105
105
|
| tr '\n' '\0' \
|
|
106
106
|
| xargs -0 -n 400 shasum -a 256 2>/dev/null \
|
|
107
107
|
| LC_ALL=C sort
|
|
108
|
+
# 🟥 **디렉토리 존재 축** (2026-08-17). 위 블록은 `-type f` 라 **파일만** 센다 — 빈
|
|
109
|
+
# 디렉토리는 관측면에 아예 없었고, `mkdir -p uploads outputs tmp` 만 하는 capability 가
|
|
110
|
+
# `writes: read-only` 선언으로 **✅ VERIFIED** 를 받았다(known-pair 3-arm 실측).
|
|
111
|
+
# git 은 빈 디렉토리를 추적하지 않으므로 격리 클론 샌드박스에도 없다 — 즉 이건 warm
|
|
112
|
+
# cache 가 아니라 **관측면의 구멍**이다(원정 §2 의 진단을 이 실측이 정정했다).
|
|
113
|
+
# ★존재만 센다, mtime 은 안 센다 — 안쪽 파일이 바뀔 때마다 디렉토리 mtime 이 움직여
|
|
114
|
+
# 읽기전용 capability 가 거짓 VIOLATION 을 받는다.
|
|
115
|
+
# ★`.git` 제외 경계는 위 파일 축과 **같다**. 갈라지면 한쪽만 고쳐지는 구조가 된다.
|
|
116
|
+
find "$1" -type d -not -path '*/.git/*' -not -path '*/.git' 2>/dev/null \
|
|
117
|
+
| LC_ALL=C sort | sed 's/^/dir /'
|
|
118
|
+
# 🟥 **비정규 노드 축** (2026-08-17, cross-family codex/gpt-5.5 지목 → 실측 확인).
|
|
119
|
+
# `-type f` 도 `-type d` 도 symlink·fifo·소켓·디바이스 노드를 안 센다. 실측:
|
|
120
|
+
# entry `ln -sf /etc/passwd leaked_link` + writes: read-only → ✅ VERIFIED
|
|
121
|
+
# entry `mkfifo backdoor_pipe` + writes: read-only → ✅ VERIFIED
|
|
122
|
+
# 위 디렉토리 축을 더한 **뒤에도** 둘 다 통과했다 — 같은 결함 클래스가 남아 있었다.
|
|
123
|
+
# ★symlink 는 **대상까지** 잰다. 존재만 세면 같은 경로를 다른 곳으로 다시 거는
|
|
124
|
+
# `ln -sf other link` 가 무변화로 보인다 — 그게 이 축에서 가장 위험한 형태다.
|
|
125
|
+
# ★`stat -f`/`-printf` 를 안 쓴다 — 전자는 BSD 전용, 후자는 GNU 전용이라 한쪽 CI 에서
|
|
126
|
+
# 조용히 죽는다([[feedback_wiring_surfaces_hidden_failures]] 의 실측 클래스).
|
|
127
|
+
# `-type` 술어와 `readlink` 만 쓰면 양쪽에서 돈다.
|
|
128
|
+
find "$1" -type l -not -path '*/.git/*' 2>/dev/null | LC_ALL=C sort \
|
|
129
|
+
| while IFS= read -r _l; do
|
|
130
|
+
printf 'link %s -> %s\n' "$_l" "$(readlink "$_l" 2>/dev/null)"
|
|
131
|
+
done
|
|
132
|
+
find "$1" -not -path '*/.git/*' -not -path '*/.git' \
|
|
133
|
+
! -type f ! -type d ! -type l 2>/dev/null \
|
|
134
|
+
| LC_ALL=C sort | sed 's/^/node /'
|
|
108
135
|
# ⚠️ 명명된 한계: 개행이 든 파일명은 `find` 출력에서 쪼개진다. 이 프로브가 도입될 때부터
|
|
109
136
|
# 있던 성질이고 이번에 안 고쳤다 — 고치려면 `find -print0` 로 전 구간을 널 구분해야 한다.
|
|
110
137
|
}
|
|
@@ -683,6 +710,83 @@ EOF
|
|
|
683
710
|
fails=$((fails+1)); echo "❌ L15b rc=$rc (0 기대) — 정당한 비영 verdict 를 막는다"
|
|
684
711
|
fi
|
|
685
712
|
|
|
713
|
+
# ── L16/L16b 빈 디렉토리 생성은 «쓰기» 다 ────────────────────────────────────
|
|
714
|
+
# 2026-08-17 known-pair 3-arm 실측: `_snapshot` 이 `-type f` 라 **파일만** 셌고,
|
|
715
|
+
# `mkdir -p uploads outputs tmp` 만 하는 capability 가 `writes: read-only` 선언으로
|
|
716
|
+
# ✅ VERIFIED 를 받았다. git 이 빈 디렉토리를 추적하지 않으니 격리 클론에도 없어서
|
|
717
|
+
# before/after 어느 쪽에도 안 나타난다 — warm cache 가 아니라 관측면의 구멍이다.
|
|
718
|
+
# 과녁이 실물인 이유: qasp 의 static-review 진입점이 정확히 이 모양이다
|
|
719
|
+
# (`settings.py` 가 import 시점에 `ensure_dirs()` 를 무조건 실행).
|
|
720
|
+
cat > "$d/emptydirs.cap" <<EOF
|
|
721
|
+
id: t:emptydirs
|
|
722
|
+
entry: bash -c 'mkdir -p uploads outputs tmp'
|
|
723
|
+
requires_cwd: $_rs
|
|
724
|
+
writes: read-only
|
|
725
|
+
EOF
|
|
726
|
+
out=$(probe_one "$d/emptydirs.cap" 2>&1); rc=$?
|
|
727
|
+
if [ "$rc" = "1" ]; then
|
|
728
|
+
pass=$((pass+1)); echo "✅ L16 빈 디렉토리 생성은 read-only 선언을 위반한다"
|
|
729
|
+
else
|
|
730
|
+
fails=$((fails+1)); echo "❌ L16 rc=$rc (1 기대) — 빈 디렉토리 생성이 «no-write» 로 렌더된다"
|
|
731
|
+
fi
|
|
732
|
+
# L16b 컨트롤(과차단 방지): 재는 것은 **상태**지 활동이 아니다. 만들었다가 지워
|
|
733
|
+
# 순증이 0 이면 위반이 아니다 — 여기서 빨개지면 mkdir 자체를 벌하는 것이고,
|
|
734
|
+
# 그러면 임시 작업공간을 쓰는 정당한 read-only capability 가 전부 막힌다.
|
|
735
|
+
cat > "$d/dir_net_zero.cap" <<EOF
|
|
736
|
+
id: t:dirnetzero
|
|
737
|
+
entry: bash -c 'mkdir -p scratch_tmp && rmdir scratch_tmp'
|
|
738
|
+
requires_cwd: $_rs
|
|
739
|
+
writes: read-only
|
|
740
|
+
EOF
|
|
741
|
+
out=$(probe_one "$d/dir_net_zero.cap" 2>&1); rc=$?
|
|
742
|
+
if [ "$rc" = "0" ]; then
|
|
743
|
+
pass=$((pass+1)); echo "✅ L16b 컨트롤 — 순증 0 인 디렉토리 왕복은 위반이 아니다(상태를 잰다)"
|
|
744
|
+
else
|
|
745
|
+
fails=$((fails+1)); echo "❌ L16b rc=$rc (0 기대) — 활동을 벌한다(상태가 아니라)"
|
|
746
|
+
fi
|
|
747
|
+
|
|
748
|
+
# ── L17/L17b/L18 비정규 노드도 «쓰기» 다 ────────────────────────────────────
|
|
749
|
+
# L16 을 넣은 **뒤에도** 통과하던 형태다(실측): `-type f`·`-type d` 어느 쪽도 안 센다.
|
|
750
|
+
cat > "$d/symlink.cap" <<EOF
|
|
751
|
+
id: t:symlink
|
|
752
|
+
entry: bash -c 'ln -sf /etc/passwd leaked_link'
|
|
753
|
+
requires_cwd: $_rs
|
|
754
|
+
writes: read-only
|
|
755
|
+
EOF
|
|
756
|
+
out=$(probe_one "$d/symlink.cap" 2>&1); rc=$?
|
|
757
|
+
if [ "$rc" = "1" ]; then
|
|
758
|
+
pass=$((pass+1)); echo "✅ L17 symlink 생성은 read-only 선언을 위반한다"
|
|
759
|
+
else
|
|
760
|
+
fails=$((fails+1)); echo "❌ L17 rc=$rc (1 기대) — symlink 가 «no-write» 로 렌더된다"
|
|
761
|
+
fi
|
|
762
|
+
# L17b — **대상 재지정**. 존재만 세면 이게 무변화로 보인다: 경로도 타입도 그대로다.
|
|
763
|
+
# 이 축에서 가장 조용한 형태라 별도 레인으로 둔다.
|
|
764
|
+
mkdir -p "$_rs" 2>/dev/null; ln -sf /dev/null "$_rs/preexisting_link" 2>/dev/null
|
|
765
|
+
cat > "$d/relink.cap" <<EOF
|
|
766
|
+
id: t:relink
|
|
767
|
+
entry: bash -c 'ln -sf /etc/passwd preexisting_link'
|
|
768
|
+
requires_cwd: $_rs
|
|
769
|
+
writes: read-only
|
|
770
|
+
EOF
|
|
771
|
+
out=$(probe_one "$d/relink.cap" 2>&1); rc=$?
|
|
772
|
+
if [ "$rc" = "1" ]; then
|
|
773
|
+
pass=$((pass+1)); echo "✅ L17b 기존 symlink 의 **대상 재지정**도 위반이다(존재만 세지 않는다)"
|
|
774
|
+
else
|
|
775
|
+
fails=$((fails+1)); echo "❌ L17b rc=$rc (1 기대) — 대상을 안 재고 존재만 센다"
|
|
776
|
+
fi
|
|
777
|
+
cat > "$d/fifo.cap" <<EOF
|
|
778
|
+
id: t:fifo
|
|
779
|
+
entry: bash -c 'mkfifo backdoor_pipe'
|
|
780
|
+
requires_cwd: $_rs
|
|
781
|
+
writes: read-only
|
|
782
|
+
EOF
|
|
783
|
+
out=$(probe_one "$d/fifo.cap" 2>&1); rc=$?
|
|
784
|
+
if [ "$rc" = "1" ]; then
|
|
785
|
+
pass=$((pass+1)); echo "✅ L18 fifo/비정규 노드 생성도 위반이다"
|
|
786
|
+
else
|
|
787
|
+
fails=$((fails+1)); echo "❌ L18 rc=$rc (1 기대) — 비정규 노드가 감시면 밖이다"
|
|
788
|
+
fi
|
|
789
|
+
|
|
686
790
|
rm -rf "$d"
|
|
687
791
|
echo "── capability_effect_probe lanes: $pass PASS / $fails FAIL ──"
|
|
688
792
|
[ "$fails" = "0" ]
|
package/scripts/chamber_run.sh
CHANGED
|
@@ -56,7 +56,15 @@ _witness_record() { # $1 = artifact path
|
|
|
56
56
|
return 0
|
|
57
57
|
fi
|
|
58
58
|
if bash "$_WITNESS" record "$SLUG" "$1" >/dev/null 2>&1; then
|
|
59
|
-
|
|
59
|
+
# 🟥 «커밋해야 된다» 로만 적으면 **틀린 처방을 가르친다**(2026-08-17 런 #12 실측:
|
|
60
|
+
# 네 해시를 한 커밋에 배치했더니 브랜치에서부터 UNORDERED). verify 는
|
|
61
|
+
# `[ verdict_ts -le pre_max_ts ]` 로 판정하므로 **같은 커밋 = 같은 초 = 실패**다.
|
|
62
|
+
if [ "$(basename "$1")" = "EMISSION_VERDICT.md" ]; then
|
|
63
|
+
echo " ↳ 순서 증인: EMISSION_VERDICT.md 해시 기록됨"
|
|
64
|
+
echo " 🟥 이 해시는 **게이트 해시들과 다른 커밋**으로 올려야 한다. 같이 커밋하면 UNORDERED 다."
|
|
65
|
+
else
|
|
66
|
+
echo " ↳ 순서 증인: $(basename "$1") 해시 기록됨 — **verdict 보다 먼저, 별도 커밋으로** 올려라"
|
|
67
|
+
fi
|
|
60
68
|
else
|
|
61
69
|
echo " ⚠️ 순서 증인 기록 실패: $(basename "$1") (증인 없음 — 미측정이지 0이 아니다)"
|
|
62
70
|
fi
|
|
@@ -197,7 +205,12 @@ if [ -f "$_WITNESS" ]; then
|
|
|
197
205
|
echo " ✅ 이 EMIT 은 순서 증인을 가진다 — identity ② 승격 근거로 사용 가능"
|
|
198
206
|
else
|
|
199
207
|
echo " 🟥 이 EMIT 에는 순서 증인이 없다(rc=${_wrc:-?}) — identity ② 승격 근거로 쓰지 마라."
|
|
200
|
-
echo "
|
|
208
|
+
echo " 처방 — 커밋을 **합치지 마라**. 세 경로가 각각 증인을 죽인다(2026-08-17 실측):"
|
|
209
|
+
echo " ① 한 커밋에 배치 → 같은 초라 UNORDERED (런 #12 에서 재현)"
|
|
210
|
+
echo " ② --squash 머지 → 게이트+verdict 가 main 에서 한 커밋으로 접힌다 (런 #11)"
|
|
211
|
+
echo " ③ --delete-branch → 순서를 담은 커밋이 도달 불가가 된다"
|
|
212
|
+
echo " 올바른 형태: 게이트 해시 커밋 → (별도) verdict 해시 커밋, **두 PR 로 분리해 각각 머지**."
|
|
213
|
+
echo " 그러면 squash 해도 main 에 2커밋이 남아 순서가 보존된다."
|
|
201
214
|
# ★ 출력만 하고 exit 0 으로 끝내면, stdout 을 안 읽는 호출자에게는 증인 없는 EMIT 도
|
|
202
215
|
# **성공**이다(cross-family F8). EMIT 은 승격 주장이므로 기계적으로 구분되어야 한다.
|
|
203
216
|
WITNESS_EMIT_FAIL=1
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""ko_tech_writer_calibrate.py — `ko-tech-writer` Step 2 / Step 4-b 스캔의 **판별력** known-pair.
|
|
4
|
+
|
|
5
|
+
왜 파이썬인가 (엔진 선택은 취향이 아니라 실측 결과다)
|
|
6
|
+
─────────────────────────────────────────────────────────
|
|
7
|
+
`SKILL.md` Step 4 가 허용 엔진을 명시한다: *"유니코드 인식 엔진(ripgrep · GNU grep UTF-8
|
|
8
|
+
로케일 · Python `re` — **`grep -P` 는 예외**)"*. 초판은 `rg` 로 고정했고 **CI 에서 죽었다** —
|
|
9
|
+
ubuntu-latest 에 ripgrep 이 없다(2026-08-17 실측, `HARNESS ERROR ... exited 10`).
|
|
10
|
+
같은 실행에서 이식성 린트도 걸렸다: 셸 파일 안의 `[가-힣]` 문자 범위는
|
|
11
|
+
**GNU grep C 로케일에서 exit 2 로 죽고 그 결과가 «무매치» 로 읽힌다** — 즉 fail-open.
|
|
12
|
+
|
|
13
|
+
Python `re` 는 ⓐ 세 허용 엔진 중 **어디에나 있는 유일한 것**이고 ⓑ 유니코드를 기본으로
|
|
14
|
+
인식하며 ⓒ 패턴이 셸 밖으로 나가 로케일 문제를 구조적으로 없앤다. **격하가 아니라
|
|
15
|
+
정본이 이미 허용한 등가 엔진으로의 이동**이다.
|
|
16
|
+
|
|
17
|
+
무엇을 주고 무엇을 안 주는가 — 섞지 마라
|
|
18
|
+
─────────────────────────────────────────────────────────
|
|
19
|
+
준다 각 스캔이 **양성을 잡고 음성을 안 잡는가**(=판별력). 재현 커맨드와 실행 출력.
|
|
20
|
+
안 준다 그 스캔이 **임의 문서에서 잔여 0건인가**. 그건 문서마다 따로 재는 것이다.
|
|
21
|
+
⇒ 이걸 통과했다고 «Step 2 잔여 0» 을 주장하면
|
|
22
|
+
`harness_terminal_correlation_and_recommendations.md` 의 강등 사유가 그대로 재발한다.
|
|
23
|
+
|
|
24
|
+
exit: 0 전 레인 판별 · 1 판별 실패 · 10 harness error(픽스처 부재)
|
|
25
|
+
"""
|
|
26
|
+
import os
|
|
27
|
+
import re
|
|
28
|
+
import sys
|
|
29
|
+
|
|
30
|
+
HERE = os.path.dirname(os.path.abspath(__file__))
|
|
31
|
+
FIX = os.path.join(HERE, "..", "plugins", "fh-commons", "skills", "ko-tech-writer", "fixtures")
|
|
32
|
+
POS = os.path.join(FIX, "known_positive.md")
|
|
33
|
+
NEG = os.path.join(FIX, "known_negative.md")
|
|
34
|
+
|
|
35
|
+
# (라벨, 패턴) — Step 2 의 «기계 검출 가능» 5클래스 + Step 4-b 의 2패턴.
|
|
36
|
+
#
|
|
37
|
+
# 🟥 정본의 분류가 과장이라는 것이 첫 캘리브레이션에서 드러났다. SKILL.md 는
|
|
38
|
+
# "앞 다섯 줄은 기계 검출 가능" 이라 적었는데, **실제로 grep 을 싣고 있는 것은
|
|
39
|
+
# C1(줄표)·C5(소유 직역) 둘뿐**이고 C2·C3·C4 의 «검출 힌트» 칸은 산문 서술이다.
|
|
40
|
+
# 특히 C4 는 «서술어 없는 마침» 이라는 서술뿐 패턴이 없다.
|
|
41
|
+
LANES = [
|
|
42
|
+
("C1 줄표 이어붙임", r"(다|것|음) — "),
|
|
43
|
+
("C2 용어-머리", r"^\s*[-*]\s+\*\*[^*]+\*\* — "),
|
|
44
|
+
("C3 콜론 나열투", r"[가-힣]은: "),
|
|
45
|
+
# C4: SKILL.md 에 패턴이 없다. 아래는 **후보 검출**이지 판정이 아니다
|
|
46
|
+
# (SKILL.md 자신의 규율: "grep은 후보를 표시할 뿐, 남길지 판정은 문맥으로").
|
|
47
|
+
# 닫힌 명사 어휘라 **recall 이 낮다** — 낮은 쪽이 과차단보다 안전한 방향이고,
|
|
48
|
+
# 넓히려면 어휘를 늘리는 게 아니라 형태소 분석이 필요하다.
|
|
49
|
+
("C4 조각문(후보·닫힌 어휘)", r"(층|것|점|축|건|뿐|바|셈)\.\s*(<!--|$)"),
|
|
50
|
+
("C5 소유 직역", r"(을|를) (갖|가지)"),
|
|
51
|
+
("C6 전칭 어휘", r"전부|모두|하나도|전혀|일절|예외 없이"),
|
|
52
|
+
("C6b 부정형 전칭", r"(안|못) ?(했|만들|나오|잡히)|지 않았(다|습니다)"),
|
|
53
|
+
]
|
|
54
|
+
|
|
55
|
+
# 절대 잡히면 안 되는 토큰 — 「늘 잡는다」를 내는 죽은 계기 배제용
|
|
56
|
+
NEVER = r"ZZZ_absent_token_ZZZ"
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def count(path, pattern):
|
|
60
|
+
"""패턴에 매칭되는 **줄 수**. 파일 부재는 예외로 올린다(무음 0 금지)."""
|
|
61
|
+
rx = re.compile(pattern, re.MULTILINE)
|
|
62
|
+
n = 0
|
|
63
|
+
with open(path, encoding="utf-8") as fh:
|
|
64
|
+
for line in fh:
|
|
65
|
+
if rx.search(line.rstrip("\n")):
|
|
66
|
+
n += 1
|
|
67
|
+
return n
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def main():
|
|
71
|
+
for p in (POS, NEG):
|
|
72
|
+
if not os.path.isfile(p):
|
|
73
|
+
sys.stderr.write("harness error: fixture missing %s\n" % p)
|
|
74
|
+
return 10
|
|
75
|
+
|
|
76
|
+
print("engine: python%d.%d re (unicode-aware)"
|
|
77
|
+
% (sys.version_info[0], sys.version_info[1]))
|
|
78
|
+
print("known-positive: %s" % os.path.relpath(POS, os.path.join(HERE, "..")))
|
|
79
|
+
print("known-negative: %s" % os.path.relpath(NEG, os.path.join(HERE, "..")))
|
|
80
|
+
print("")
|
|
81
|
+
|
|
82
|
+
npass = nfail = 0
|
|
83
|
+
for label, pat in LANES:
|
|
84
|
+
p = count(POS, pat)
|
|
85
|
+
n = count(NEG, pat)
|
|
86
|
+
if p >= 1 and n == 0:
|
|
87
|
+
print("OK %s -- positive %d / negative 0 (discriminates)" % (label, p))
|
|
88
|
+
npass += 1
|
|
89
|
+
elif p < 1:
|
|
90
|
+
print("FAIL %s -- MISSES the positive (positive %d). A blind instrument." % (label, p))
|
|
91
|
+
nfail += 1
|
|
92
|
+
else:
|
|
93
|
+
print("FAIL %s -- HITS the negative (negative %d). Over-blocks; no discrimination."
|
|
94
|
+
% (label, n))
|
|
95
|
+
nfail += 1
|
|
96
|
+
|
|
97
|
+
# META 컨트롤 1 — 없는 토큰이 양쪽 다 0인가 (계기가 «늘 잡는다» 를 내지 않는다)
|
|
98
|
+
if count(POS, NEVER) == 0 and count(NEG, NEVER) == 0:
|
|
99
|
+
print("OK META control -- an absent token matches nothing in either fixture")
|
|
100
|
+
npass += 1
|
|
101
|
+
else:
|
|
102
|
+
print("FAIL META control -- an absent token matched. Do not trust this suite.")
|
|
103
|
+
nfail += 1
|
|
104
|
+
|
|
105
|
+
# META 컨트롤 2 — 두 픽스처가 실제로 다른 파일인가
|
|
106
|
+
with open(POS, encoding="utf-8") as a, open(NEG, encoding="utf-8") as b:
|
|
107
|
+
if a.read() != b.read():
|
|
108
|
+
print("OK META control -- positive/negative fixtures differ")
|
|
109
|
+
npass += 1
|
|
110
|
+
else:
|
|
111
|
+
print("FAIL META control -- the two fixtures are identical; results are meaningless")
|
|
112
|
+
nfail += 1
|
|
113
|
+
|
|
114
|
+
print("")
|
|
115
|
+
if nfail == 0:
|
|
116
|
+
print("CALIBRATED (%d lanes) -- Step 2 five classes + Step 4-b two patterns discriminate."
|
|
117
|
+
% npass)
|
|
118
|
+
print(" NOT proven: 'zero residue' in any real document. That is measured per document.")
|
|
119
|
+
print(" Citing this suite as evidence of 'zero residue' reproduces the very downgrade")
|
|
120
|
+
print(" recorded in harness_terminal_correlation_and_recommendations.md.")
|
|
121
|
+
return 0
|
|
122
|
+
print("NOT CALIBRATED (%d/%d failed)" % (nfail, npass + nfail))
|
|
123
|
+
return 1
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
if __name__ == "__main__":
|
|
127
|
+
sys.exit(main())
|
|
@@ -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
|
@@ -530,7 +530,8 @@ for _pair in \
|
|
|
530
530
|
"scripts/residency_closure_scan.py|scripts/test_residency_closure_lanes.sh" \
|
|
531
531
|
"scripts/reviewer_capability_corpus.tsv|scripts/test_reviewer_capability_conformance.sh" \
|
|
532
532
|
"scripts/field_canon_preload.sh|scripts/test_field_canon_lanes.sh" \
|
|
533
|
-
"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"
|
|
534
535
|
do
|
|
535
536
|
_subj="${_pair%%|*}"; _anc="${_pair##*|}"; _lbl="${_anc##*/}"
|
|
536
537
|
if [ ! -f "$_subj" ]; then
|
|
@@ -67,13 +67,14 @@ _lane() { # $1=id $2=class $3=설명 $4=expected_rc $5=actual_rc
|
|
|
67
67
|
|
|
68
68
|
# 계기 전제 — 어댑터 3종이 **실제로 거기 있는지** 먼저 본다. 없으면 이 레인 묶음은
|
|
69
69
|
# «전부 초록» 이 아니라 하네스 오류다(빈 대상에 대고 초록을 내는 것이 여기서 막으려는 형태).
|
|
70
|
-
for a in gstack_content_safety mate_agent_boundary qasp_new_code_anchor peer_resolve; do
|
|
70
|
+
for a in gstack_content_safety mate_agent_boundary qasp_new_code_anchor qasp_web_rules peer_resolve; do
|
|
71
71
|
[ -r "$ADAPTER_DIR/$a.sh" ] || { echo "❌ HARNESS-ERROR — $ADAPTER_DIR/$a.sh 부재. 빈 대상에 초록을 내지 않는다"; exit 10; }
|
|
72
72
|
done
|
|
73
73
|
|
|
74
74
|
G="$ADAPTER_DIR/gstack_content_safety.sh"
|
|
75
75
|
M="$ADAPTER_DIR/mate_agent_boundary.sh"
|
|
76
76
|
Q="$ADAPTER_DIR/qasp_new_code_anchor.sh"
|
|
77
|
+
W="$ADAPTER_DIR/qasp_web_rules.sh"
|
|
77
78
|
|
|
78
79
|
# 「peer 가 안 보이는 세계」 = 잘 형성된 루트 목록인데 그 안에 이 peer 가 없는 것.
|
|
79
80
|
# (빈 **문자열**이 아니다 — 그건 「어디를 볼지 자체가 없다」는 다른 명제이고 전제 파손이다.)
|
|
@@ -196,6 +197,41 @@ else
|
|
|
196
197
|
fi
|
|
197
198
|
echo
|
|
198
199
|
|
|
200
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
201
|
+
# ④ qasp-web-rules (자매 노드 — 같은 peer, 다른 능력)
|
|
202
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
203
|
+
# 🟥 ③ 과 **같은 peer 인데 다른 enum 이다**. 값을 옮겨 쓰면 안 된다 —
|
|
204
|
+
# ③ 의 `3=HARNESS_ERROR`·`4=OUT_OF_SCOPE` 와 여기의 `3=ENGINE_ERROR` 는 다른 사건이고,
|
|
205
|
+
# 여기엔 `4` 가 아예 없다. 두 노드를 한 눈에 보는 이 파일이 그 혼동의 1순위 발생지다.
|
|
206
|
+
echo "── qasp-web-rules (enum 0=CLEAN 1=FINDINGS 2=ARGS 3=ENGINE_ERROR 20=PEER_ABSENT)"
|
|
207
|
+
_lane W1 resolve-absent "peer 불가시 → 전용값 PEER_ABSENT(CLEAN 으로도 ENGINE_ERROR 로도 안 접힌다)" 20 \
|
|
208
|
+
"$(FH_CLUSTER_ROOTS="$EMPTY_WORLD" _rc bash "$W" --known-positive)"
|
|
209
|
+
_lane_peer "$QS_ROOT" W2 control "peer 가시 → 양성 arm 은 FINDINGS" 1 "$(_rc bash "$W" --known-positive)"
|
|
210
|
+
_lane_peer "$QS_ROOT" W3 control "peer 가시 → 음성 arm 은 CLEAN (판별력의 증거)" 0 "$(_rc bash "$W" --known-negative)"
|
|
211
|
+
_lane W4 enum "모드 미지정 → ARGS(2). 안 돈 실행을 판정으로 렌더하지 않는다" 2 "$(_rc bash "$W")"
|
|
212
|
+
_lane W5 enum "없는 diff-json → ARGS(2). 호출 오류지 계기 고장이 아니다" 2 \
|
|
213
|
+
"$(_rc bash "$W" --diff-json /nonexistent/nope.json)"
|
|
214
|
+
# 🟥 W6 — 픽스처 부재는 **ARGS 가 아니라 HARNESS_ERROR** 다. 캘리브레이션 쌍이 사라진 것은
|
|
215
|
+
# 「호출을 잘못했다」가 아니라 **「이 계기가 자기 판별력을 증명할 수 없다」**는 뜻이고,
|
|
216
|
+
# 그 둘을 같은 값으로 접으면 «쌍이 없는 계기»가 «인자 실수»로 보고된다.
|
|
217
|
+
if [ -n "$QS_ROOT" ]; then
|
|
218
|
+
FIXBK="$T/fixbk"; mkdir -p "$FIXBK"
|
|
219
|
+
FIXPOS="$ADAPTER_DIR/fixtures/qasp_web_rules_known_positive.json"
|
|
220
|
+
if [ -r "$FIXPOS" ]; then
|
|
221
|
+
mv "$FIXPOS" "$FIXBK/pos.json"
|
|
222
|
+
_lane W6 enum "캘리브레이션 픽스처 부재 → HARNESS_ERROR(10), ARGS 아님" 10 \
|
|
223
|
+
"$(_rc bash "$W" --known-positive)"
|
|
224
|
+
mv "$FIXBK/pos.json" "$FIXPOS"
|
|
225
|
+
# 복원 확인 — 레인이 자기가 옮긴 것을 되돌렸는지 **잰다**(안 하면 다음 레인이 거짓 적색)
|
|
226
|
+
[ -r "$FIXPOS" ] || { echo "❌ HARNESS-ERROR — W6 가 픽스처를 복원하지 못했다"; exit 10; }
|
|
227
|
+
else
|
|
228
|
+
echo " ⏭ W6 [enum ] 양성 픽스처 부재 — SKIP (통과 아님)"
|
|
229
|
+
fi
|
|
230
|
+
else
|
|
231
|
+
echo " ⏭ W6 [enum ] qasp 실물 루트 없음 — SKIP (통과 아님)"
|
|
232
|
+
fi
|
|
233
|
+
echo
|
|
234
|
+
|
|
199
235
|
# ═══════════════════════════════════════════════════════════════════════════
|
|
200
236
|
# 되돌림 프로브 — 앵커가 장식이 아님을 «되돌려 보고» 잰다 (적용확인 → 실행 → 복원)
|
|
201
237
|
# ═══════════════════════════════════════════════════════════════════════════
|
|
@@ -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 $?
|