@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.
@@ -95,8 +95,11 @@ judge: mechanical
95
95
  verdict_binding: VIOLATION ARGS HARNESS_ERROR OUT_OF_SCOPE PEER_ABSENT
96
96
  #
97
97
  # known-pair — **합성 레포**에서 실행시점에 세운다. peer 히스토리를 안 쓰는 이유 셋:
98
- # ① peer 의 이 진입점은 **수리 중**이다(qasp PR #174, 브랜치
99
- # `fix/anchor-scan-out-of-scope-repairs` 쌍을 고른 시점의 체크아웃 상태다).
98
+ # ① peer 의 이 진입점은 **움직인다**. 2026-08-16 이 줄을 쓸 때는 qasp PR #174 가 열려
99
+ # 있었고(브랜치 `fix/anchor-scan-out-of-scope-repairs`), PR **2026-08-17 머지됐다**
100
+ # (qasp-dev main `cfd0e77`, 브랜치 삭제). 즉 이유는 살아 있고 사례만 닫혔다 —
101
+ # 그 브랜치를 고정 SHA 로 박았으면 squash 로 커밋이 orphan 이 되어 쌍이 지금 죽어 있다.
102
+ # ★머지 후 known-pair 재실행으로 확인: `--known-positive` rc=1 · `--known-negative` rc=0.
100
103
  # 고정 SHA 를 박으면 그 브랜치가 squash 머지되는 순간 커밋이 orphan 이 되어 쌍이 죽는다.
101
104
  # ② 상대 ref(`HEAD~N`)는 커밋이 쌓이면 가리키는 구간이 조용히 움직인다 — 같은 선언이
102
105
  # 다른 것을 재게 된다.
@@ -0,0 +1,130 @@
1
+ # ═════════════════════════════════════════════════════════════════════════════
2
+ # 어댑터 선언 — FH 가 **남의 하네스 능력을 자기 안에서** 선언한다
3
+ # ═════════════════════════════════════════════════════════════════════════════
4
+ # peer 하네스 `qasp`(로컬 별칭 해석: `qasp-dev`)의 `scripts/web_rules_pr_scan.sh` —
5
+ # 1.5막 **웹 셀렉터-안정성 rule 세트** — 를 FH 가 호출 가능하게 감싼 것이다.
6
+ # 어댑터임은 **id 네임스페이스**(`adapter/…`)와 이 헤더로 표시한다 — 🟥 **새 스키마 키를
7
+ # 만들지 않는다.** `.cap` 파서가 이 클러스터에 네 벌 있고(relay · registry · cluster_scan ·
8
+ # effect-probe), 키를 늘리면 네 곳을 동시에 고쳐야 하며 그게 이 레포의 재발 결함이다.
9
+ #
10
+ # 왜 여기 사나 (운영자 결정 2026-08-16): 어댑터가 **기본 경로**다. 남의 레포 안 `.cap`
11
+ # (소유 선언)은 그 하네스를 소유하고 동시에 노출을 결정한 특권적 경우이지 일반 경로가 아니다.
12
+ # ★이 노드는 그 결정의 **직접 산물**이다 — qasp-dev PR #173 이 대상 레포 안에 `.cap` 을
13
+ # 두려다 그 원칙에 걸려 CLOSED 됐고(2026-08-16), 그 자리를 이 파일이 대신한다.
14
+ #
15
+ # ── 무엇을 재나 ──────────────────────────────────────────────────────────────
16
+ # **웹 E2E 테스트 diff 에 로케이터/대기 규율 위반이 들어왔는가.** rule 5종
17
+ # (`web-coordinate-click` P1 · `web-wait-for-timeout` P2 등 — LOCATOR_DISCIPLINE ·
18
+ # ASSERTION_STRENGTH 렌즈). peer 헤더의 기원 실측: rule 테이블은 2026-07-24 부터 있었으나
19
+ # **프로덕션 호출자가 0건**이었다(2026-07-27 실측 — tests/ 와 자기 자신뿐).
20
+ # *"호출자가 없는 검출기는 산문이지 게이트가 아니다."*
21
+ #
22
+ # ── 왜 net-new 인가 ──────────────────────────────────────────────────────────
23
+ # FH 에 같은 계기가 **없다.** 인접 자산은 전부 다른 질문을 한다:
24
+ # · `adapter/qasp-new-code-anchor` — **신규 실행 코드에 앵커가 있나**(대상=diff 의 추가 파일)
25
+ # · `degrade_direction_scan.sh` — **판정이 어느 방향으로 무너지나**(대상=verdict 표면)
26
+ # · `adapter/mate-agent-boundary` — 모바일 에이전트 경계
27
+ # 이 능력은 **테스트 코드 자체의 로케이터/대기 규율**을 본다 — 대상이 «게이트» 도 «신규 파일»
28
+ # 도 아니고 **E2E 테스트의 작성 품질**이다. 그 렌즈는 FH 에 없다.
29
+ #
30
+ # ── 왜 진입점을 그대로 못 쓰나 ───────────────────────────────────────────────
31
+ # ⓐ peer 는 자기 부재를 뜻하는 값이 없다 — 없으면 「qasp 를 안 깔았다」가 ENGINE_ERROR
32
+ # (= qasp 가 깨졌다)로 렌더된다. 어댑터가 `PEER_ABSENT(20)` 을 더한다.
33
+ # ⓑ 🟥 **peer 의 문서화된 계약이 실제보다 좁다.** 셸 헤더는 `0/1/3` 만 적는데, 하위 python
34
+ # CLI 가 `parser.error()` 를 쓰고 argparse 가 **exit 2** 를 내며 셸이 그대로 전파한다
35
+ # (실측 2026-08-17). 어댑터는 **실제 계약**을 선언한다 — 안 그러면 인자 오류가
36
+ # 「계기가 깨졌다」로 렌더된다. peer 쪽 헤더 갱신은 별건으로 남긴다.
37
+ id: forge-harness:adapter/qasp-web-rules
38
+ summary: adapter to qasp web selector-stability rules — locator/wait discipline violations in web E2E test diffs
39
+ tags: adapter qasp web e2e playwright locator selector wait flaky static-review rules diff
40
+ entry: bash scripts/adapters/qasp_web_rules.sh
41
+ requires_cwd: SELF
42
+ verdict_channel: exit
43
+ #
44
+ # 🟥 「안 쟀다」가 **세 값**으로 갈라져 있다 — 전부 다른 명제다:
45
+ # ARGS(2) 인자가 틀렸다 (호출에 대한 진술)
46
+ # ENGINE_ERROR(3) 하네스는 있는데 못 쟀다 (계기에 대한 진술)
47
+ # 도달 경로(peer 실측): python 인터프리터 부재 · diff-json 읽기 실패 ·
48
+ # `gh api` 실패 · **스캔 대상 0개**(peer 가 fail-closed 로 못 박았다)
49
+ # HARNESS_ERROR(10) 계기 자신이 전제를 잃었다 (어댑터에 대한 진술)
50
+ # 도달 경로: `peer_resolve.sh` 부재 · peer 진입점 부재 ·
51
+ # peer 별칭 다중 히트(모호 — 고르지 않는다) · 캘리브레이션 픽스처 부재 ·
52
+ # peer 가 자기 계약 {0,1,2,3} 밖의 값 반환
53
+ # PEER_ABSENT(20) 그 노드가 이 머신에 없다 (클러스터에 대한 진술)
54
+ #
55
+ # 🟥 **`10` 은 초판 선언에서 빠져 있었다 — 스크립트는 내던 값인데 선언이 좁았다.**
56
+ # 등록 바(`capability_registry_check.sh` M2+)가 잡았다: *"enum 에 「안 돌았다」를 뜻하는
57
+ # 값이 없다"*. 자기 레인(W6)이 `rc=10` 을 이미 단언하고 있었는데도 선언에는 없었다 —
58
+ # **[[feedback_unedited_parts_of_my_own_file]]** 의 형태다(내 편집 범위 안, 자력 적발 0).
59
+ # ★peer 자신의 규율: *"exit 3 을 0 으로 뭉개지 않는다 — 못 잰 것을 깨끗하다고 보고하는
60
+ # 순간 게이트가 장식이 된다."* 어댑터는 하위 exit 을 전파하지 않고 **명시 case 로 재매핑**한다.
61
+ # ★등록 바는 아래 두 arm 만 돌린다 — ARGS·ENGINE_ERROR·PEER_ABSENT 는 선언돼 있으나
62
+ # 그 판정에서 **관측되지 않는다**. 셋은 `scripts/test_adapter_lanes.sh` 가 덮는다.
63
+ verdict_enum: 0=CLEAN 1=FINDINGS 2=ARGS 3=ENGINE_ERROR 10=HARNESS_ERROR 20=PEER_ABSENT
64
+ approval: auto
65
+ #
66
+ # `reversibility: reversible` — 이 계기는 **읽고 보고할 뿐** 아무것도 되돌릴 수 없게 만들지
67
+ # 않는다. peer 표면 자신도 advisory 다(PR 코멘트/요약 — 차단하지 않는다).
68
+ reversibility: reversible
69
+ #
70
+ # 🟥 `residency: company` — peer 레포가 company 다. `--diff-json` 모드에서 이 어댑터는
71
+ # **호출자가 준 diff 의 파일 경로와 코드 라인**을 출력에 그대로 싣는다. 회사 코드를
72
+ # 스캔하면 그 경로·라인이 조직 자산을 나른다.
73
+ # ⇒ 이 능력의 **출력**은 로컬에 남기고 외부 계열 디스패치에 그대로 넘기지 않는다.
74
+ # ⚠️ 캘리브레이션 arm 은 **우리가 지은 합성 diff** 만 만지므로 그 경로에 한해 조직 내용이
75
+ # 없다. 그러나 residency 는 **가장 넓게 관측된 값**으로 선언한다.
76
+ residency: company
77
+ degrade: fail-closed
78
+ tier_floor: none
79
+ #
80
+ # ═════════════════════════════════════════════════════════════════════════════
81
+ # 🟥 `writes: write-local` — `read-only` 가 **아니다.** cold 실측이 근거다
82
+ # ═════════════════════════════════════════════════════════════════════════════
83
+ # 진입점이 `python -m src.static_review.web_rules` 를 부르고, 그 import 폐포가
84
+ # `src/config/settings.py` 의 **import 시점 `ensure_dirs()`** 에 닿는다.
85
+ #
86
+ # warm(운영자 실 트리) : 트리 변화 **없음** ← 🟥 이것을 근거로 쓰면 안 된다
87
+ # cold(격리 클론) : 신규 엔트리 **25개**
88
+ # `__pycache__` 디렉토리 5 + `.pyc` 파일 18
89
+ # **빈 디렉토리 `./tmp` · `./uploads`** ← `mkdir(exist_ok=True)` 의 산물
90
+ #
91
+ # warm 이 깨끗해 보인 것은 그 디렉토리들이 **이미 있어서** mkdir 이 무음 no-op 이었기
92
+ # 때문이다 — **warm cache 아티팩트지 read-only 의 증거가 아니다.**
93
+ #
94
+ # 🟥 **이 값은 손 실측으로만 선다 — M6 는 이 노드의 선언을 판별하지 못한다.**
95
+ # known-pair 로 확인했다(2026-08-17): 같은 파일을 `writes: read-only` 로 **거짓 선언**해도
96
+ # 프로브는 `✅ VERIFIED · 관측=no-write` 를 낸다. 즉 이 축에서 **컨트롤이 죽어 있다.**
97
+ #
98
+ # 기전: 프로브의 감시면은 ⓐ 샌드박스(= **FH** 레포의 격리 클론) ⓑ 임시 카나리아 하나
99
+ # ⓒ `$HOME` 엔트리 목록이다. 그런데 **어댑터는 정의상 peer 트리에서 돈다** —
100
+ # `cd "$PEER_ROOT"` 가 이 파일의 전제다. 쓰기는 전부 감시면 **밖**에 떨어지고, 밖이되
101
+ # ⓑⓒ 어디도 아니라서 «샌드박스 밖이 바뀌었다» 분기에도 안 걸린다.
102
+ #
103
+ # ⚠️ **초판이 여기에 «이제 기계로 검증·반증된다» 고 적었는데 그건 거짓이었다.**
104
+ # FH PR #422 가 «빈 디렉토리» 축을 넣은 것은 사실이고 일반 명제로는 맞지만, **그 축은
105
+ # 샌드박스 안을 볼 때만 작동하므로 이 노드에는 닿지 않는다.** 참인 일반 명제로 거짓인
106
+ # 개별 주장을 덮은 형태다 — 대조군이 없었으면 그대로 실렸다.
107
+ #
108
+ # ⇒ 이 선언의 근거는 **위 cold 손 실측 하나뿐**이고, 재검증하려면 손으로 다시 재야 한다
109
+ # (격리 클론 → 진입점 1회 → `find` 순증 계수). 자동 앵커 없음.
110
+ # ⇒ **어댑터 클래스 전체의 성질**이다. 자매 노드 `adapter/qasp-new-code-anchor` 의
111
+ # `writes: read-only` 도 같은 이유로 M6 가 판별하지 못한다 — 그쪽은 결론이 맞지만
112
+ # (순수 bash, import 없음) **맞는 이유가 프로브 덕은 아니다.**
113
+ writes: write-local
114
+ judge: mechanical
115
+ # CLEAN 만 clearing. FINDINGS 는 위반, 나머지 셋은 「안 쟀다」의 서로 다른 얼굴 — 전부 막는다.
116
+ verdict_binding: FINDINGS ARGS ENGINE_ERROR HARNESS_ERROR PEER_ABSENT
117
+ #
118
+ # known-pair — **FH 소유 픽스처**(`scripts/adapters/fixtures/`), 오프라인·결정적.
119
+ # 양성: `page.wait_for_timeout(3000)` → `web-wait-for-timeout` 발화 → 실측 rc=1
120
+ # 음성: `get_by_role("button", name=…).click()` → 위반 0 → 실측 rc=0
121
+ # ★왜 peer 픽스처를 안 쓰나: peer 의 `tests/` 는 그 레포 사정으로 움직인다. **남의 트리에
122
+ # 있는 픽스처에 의존하는 계기는 별도 계기가 아니라 그 레포의 일부다.**
123
+ # ★왜 PR 번호 모드를 안 쓰나: `gh api` 가 필요해 네트워크·인증에 의존하고, 그러면 M4 가
124
+ # 「계기가 판별하는가」가 아니라 **「오늘 gh 가 되는가」**를 재게 된다.
125
+ # ★왜 고정 SHA 를 안 박나: 자매 노드(`adapter/qasp-new-code-anchor`)가 그 대가를 오늘 실증했다
126
+ # — 그 쌍이 가리키던 브랜치가 squash 머지되며 커밋이 orphan 이 됐다. 합성 픽스처는 안 움직인다.
127
+ calibration_positive_args: --known-positive
128
+ calibration_positive_expect: FINDINGS
129
+ calibration_negative_args: --known-negative
130
+ calibration_negative_expect: CLEAN
@@ -194,6 +194,36 @@ controls: n/a — no measurement in this delta (<reason>)
194
194
  — zero hook lines, no fixture suite. Recorded here so the gap is visible from the rules side rather
195
195
  than only from the hook's absence.
196
196
 
197
+ **`thirdparty:` — ⓓ3자 대면의 자기 필드 (2026-08-17 신설).** ⓑ가 `standpoint:` 를 갖는 것과
198
+ 같은 형태다: `axes-run` 에는 포인터(`ⓓ=→thirdparty`)만 두고 값은 이 필드가 나른다.
199
+
200
+ ```
201
+ thirdparty: checked(<무엇을 검색했고 무엇을 찾았나>)
202
+ thirdparty: none-found(<무엇을 검색했나>) ← 찾았는데 선행 없음. «안 봤다»와 다른 명제다
203
+ thirdparty: DEGRADED_NO_ACCESS(<사유>) ← 못 봤다 (검색 수단 부재)
204
+ thirdparty: DEGRADED_NOT_RUN(<사유>) ← 안 돌렸다
205
+ thirdparty: UNKNOWN ← 안 봤다
206
+ thirdparty: not-applicable(<사유>)
207
+ ```
208
+
209
+ 🟥 **왜 `axes-run` 한 칸으로 부족한가.** 그 칸은 **토큰**을 담는데, 이 축이 실제로 내는 것은
210
+ «무엇을 검색해서 무엇을 찾았나» 라 한 토큰에 안 들어간다. **자리가 없으면 안 적히고, 안 적히면
211
+ 다음 세션이 «이 축을 돌렸나»를 물을 수도 확인할 수도 없다.**
212
+ 실측 근거(2026-08-17): 이 축을 처음 제대로 돌린 세션에서 **발표 주장 6건이 선행자산에 걸렸다**
213
+ (mutation testing · Anthropic skill-creator 블라인드 · Claude Code auto mode · Self-Harness ·
214
+ 공식 cross-family 플러그인 · constrained decoding). 그 전까지 이 레포 전체 마크다운에서
215
+ `promptfoo|DeepEval|Inspect|garak|PyRIT|mutation testing` grep 히트는 **1줄**이었다 —
216
+ 축은 정본화돼 있었는데 **기록면이 없어서 습관이 안 생겼다.**
217
+
218
+ **degrade 3값 분리는 `crossfamily:` 를 그대로 상속한다** — 못 봤다 / 안 돌렸다 / 안 봤다는
219
+ 서로 다른 명제이고, 자유 산문은 그 셋을 접는다. 접히면 «안 돌린 축»이 «선행 없음»으로 읽힌다.
220
+
221
+ **검증 범위(오늘 기준)**: 훅은 ⓑ와 동일하게 **포인터가 가리키는 필드의 존재**만 본다.
222
+ enum 값 검증은 안 한다 — `standpoint:` 와 같은 유보이고, 같은 이유다(첫 거짓 값이 기록되면
223
+ 그때 기계화한다). ⚠️ ⓑ가 겪은 **화살표 fail-open 도 같이 상속해 미리 막았다**: 화살표를
224
+ 필수로 보지 않고 «ⓓ 값이 thirdparty 를 참조하는가»로 본다. 같은 명시 잔여(참조어가 다른
225
+ 토큰에 있으면 미검출)도 그대로 남는다.
226
+
197
227
  **Scope, unchanged**: form + non-vacuity + auditability, **never provenance**. A marker claiming
198
228
  `ⓐ=codex` when codex never ran passes — deliberately. Catching that is cross-family review *reading*
199
229
  the marker, which is not this hook's job.
@@ -11,13 +11,13 @@
11
11
  "plugins": [
12
12
  {
13
13
  "name": "fh-meta",
14
- "version": "2.1.0",
15
- "description": "New in 2.1.0: BREAKING (gate): `crossfamily: declined` in an Axes 2-3 marker now requires grounds naming a record path that RESOLVES on disk — bare `declined`, and `declined` justified by author judgment, are blocked at commit. Remedy: cite where the operator decision lives (e.g. `.. — operator declined sidecars, per knowledge/shared/rules/operational_adaptation.md`), or use `DEGRADED_PANEL_UNUSED` if a panel was reachable and you chose not to recruit it — which is what author judgment actually is. `declined` was the only enum value with no grounds requirement; a cross-family review then broke the first (vocabulary-grep) fix three ways — self-validating on the value's own token, vacuous keyword passes, and over-blocking real declinations in natural prose — so the check asserts a resolvable record instead of words. Also: standpoint axis gains `tier1b` (a STATIC read of a target repo, executed nothing) plus a decide-in-order procedure, after blind floor-tier sims graded pure cold-reads as `tier2` three rounds running; steel-quench Wave 1's sixth angle (gate-locality) gains the output-template row it never had, so a mandatory angle stops being structurally unreportable; verify-bidirectional gains category 5 (prescriptive doctrine statement); Sister Asset Protocol gains an active-adoption trigger; new resident doctrine — Mechanization Boundary, Local Execution First, Skeleton-not-Muscle, Expedition track, and this package's versioning policy. Hub meta-operations toolkit — 35 skills + 7 agents. New in 2.0.1: harness-doctor cadence hook, portability lint wired into pre-commit, branch_claim.sh claim-count-vs-tree-count warning, louder confidentiality-scan fail-open notice, fh-gate.sh missing-package.json survival, identity ① reclassified 🟢 (cross-harness adapters + relay argument channel). New in 1.4.53: `fh-codex-doctor` (npm bin) — Codex adapter drift scanner; reads the documented M1/M2/M3 skill tier map + skill/agent source and reports codex-native/adapter-required/claude-native/unclassified per unit, wired into `npm test`/`prepublishOnly` (fail-closed on unclassified Claude-native primitives). New in 1.4.49: steel-quench gains Step 0.6 Verdict-Invariance Probe (groundedness axis — a load-bearing judged gate's verdict must track behavior, not rubric phrasing; measured flip-count over cross-family paraphrases; arXiv:2605.06161 Policy Invariance anchor); multi_model_sidecar_strategy §Vendor-native harness (a model is strongest in its own vendor CLI — Claude/CC, GPT/codex, Gemini/Antigravity; a universal router degrades all of them, so it stays an autocomplete/QA sidecar, never orchestration); predelete_check.sh fail-closed rewrite; memory-hygiene A-TMA anchor. New in 1.4.48: phantom-quench + steel-quench gain external frontier anchors (arXiv:2607.02052 package-hallucination; arXiv:2607.02057 prompt-coverage-adequacy); README model-flat claim reframed from a per-release point-curve to structural invariants (operation flattens across tiers; depth tier-order fixed within a generation). New in 1.4.47: onboarding step ① surfaces the Mode D companion-store session-start load in the auto-read salience anchor (previously only in the local binding + rules, so a greeting could skip the load). New in 1.4.46: context-doctor command-output axis (route to rtk/proxy for verbose CLI stdout, complementing .claudeignore; risk-gated to token-scarce envs). New in 1.4.41: context-doctor 2026 trigger vocab (context engineering/rot/collapse) + phantom-citation hardening; hub measurement-integrity-checklist (cross-model measurement pre-flight: display-name pin/reps≥3/discriminating probe). New in 1.4.40: install-wizard queryable-wiki scaffold (INDEX + session-start read + R/W/C ingest). New in 1.4.39: auto-decorrelation (cross-family verifier sidecar recruitment) + video-ingest (capability-routed video ingestion). New in 1.4.x: verify-axis check-class taxonomy (mandatory-pass/measured/judged), no-reinvention Tier-0 inventory, 7-class failure taxonomy, Destructive-Op Gate, Wave-T (Temper), tier-floor governance, Mode D Model Notice, FC consent lane, default-Sonnet guidance. New in 1.3.0: public-surface-audit, field-harvest Mode B auto-trigger, 4-axis gate scope ext. Validated cross-CLI: Claude Code, Codex, Gemini.",
14
+ "version": "2.3.0",
15
+ "description": "New in 2.2.0: BREAKING (gate): chamber step 6 now reads ACTUAL.md, not BUDGET.md — an in-flight chamber run whose actual cost sits in BUDGET.md blocks until the ACTUAL: line moves to tracks/_chamber/<slug>/ACTUAL.md (the runner prints the path). Why: BUDGET.md's pre-verdict hash IS the ordering witness, and step 6 hard-blocked until that same file changed, so every run that reached COMPLETE necessarily mutated a witnessed artifact and verify returned TAMPERED — the chamber's promotion condition was unsatisfiable by construction, not by strictness. Two roles (immutable witness / post-verdict calibration sink) had collided in one file; each was correct alone, so neither side's code showed the conflict. Also: ko-tech-writer Step 2/4-b scans are now calibration-backed (known-pair fixtures + reproducible command, shipped) — discrimination is proven, 'zero residue' is explicitly NOT; chamber lane suite 12 -> 33 including the runner x witness seam no test covered; chamber_run.sh now teaches the two-commit discipline (gate hashes and verdict hash must land in separate commits/PRs — it previously advised the opposite). New in 2.1.0: BREAKING (gate): `crossfamily: declined` in an Axes 2-3 marker now requires grounds naming a record path that RESOLVES on disk — bare `declined`, and `declined` justified by author judgment, are blocked at commit. Remedy: cite where the operator decision lives (e.g. `.. — operator declined sidecars, per knowledge/shared/rules/operational_adaptation.md`), or use `DEGRADED_PANEL_UNUSED` if a panel was reachable and you chose not to recruit it — which is what author judgment actually is. `declined` was the only enum value with no grounds requirement; a cross-family review then broke the first (vocabulary-grep) fix three ways — self-validating on the value's own token, vacuous keyword passes, and over-blocking real declinations in natural prose — so the check asserts a resolvable record instead of words. Also: standpoint axis gains `tier1b` (a STATIC read of a target repo, executed nothing) plus a decide-in-order procedure, after blind floor-tier sims graded pure cold-reads as `tier2` three rounds running; steel-quench Wave 1's sixth angle (gate-locality) gains the output-template row it never had, so a mandatory angle stops being structurally unreportable; verify-bidirectional gains category 5 (prescriptive doctrine statement); Sister Asset Protocol gains an active-adoption trigger; new resident doctrine — Mechanization Boundary, Local Execution First, Skeleton-not-Muscle, Expedition track, and this package's versioning policy. Hub meta-operations toolkit — 35 skills + 7 agents. New in 2.0.1: harness-doctor cadence hook, portability lint wired into pre-commit, branch_claim.sh claim-count-vs-tree-count warning, louder confidentiality-scan fail-open notice, fh-gate.sh missing-package.json survival, identity ① reclassified 🟢 (cross-harness adapters + relay argument channel). New in 1.4.53: `fh-codex-doctor` (npm bin) — Codex adapter drift scanner; reads the documented M1/M2/M3 skill tier map + skill/agent source and reports codex-native/adapter-required/claude-native/unclassified per unit, wired into `npm test`/`prepublishOnly` (fail-closed on unclassified Claude-native primitives). New in 1.4.49: steel-quench gains Step 0.6 Verdict-Invariance Probe (groundedness axis — a load-bearing judged gate's verdict must track behavior, not rubric phrasing; measured flip-count over cross-family paraphrases; arXiv:2605.06161 Policy Invariance anchor); multi_model_sidecar_strategy §Vendor-native harness (a model is strongest in its own vendor CLI — Claude/CC, GPT/codex, Gemini/Antigravity; a universal router degrades all of them, so it stays an autocomplete/QA sidecar, never orchestration); predelete_check.sh fail-closed rewrite; memory-hygiene A-TMA anchor. New in 1.4.48: phantom-quench + steel-quench gain external frontier anchors (arXiv:2607.02052 package-hallucination; arXiv:2607.02057 prompt-coverage-adequacy); README model-flat claim reframed from a per-release point-curve to structural invariants (operation flattens across tiers; depth tier-order fixed within a generation). New in 1.4.47: onboarding step ① surfaces the Mode D companion-store session-start load in the auto-read salience anchor (previously only in the local binding + rules, so a greeting could skip the load). New in 1.4.46: context-doctor command-output axis (route to rtk/proxy for verbose CLI stdout, complementing .claudeignore; risk-gated to token-scarce envs). New in 1.4.41: context-doctor 2026 trigger vocab (context engineering/rot/collapse) + phantom-citation hardening; hub measurement-integrity-checklist (cross-model measurement pre-flight: display-name pin/reps≥3/discriminating probe). New in 1.4.40: install-wizard queryable-wiki scaffold (INDEX + session-start read + R/W/C ingest). New in 1.4.39: auto-decorrelation (cross-family verifier sidecar recruitment) + video-ingest (capability-routed video ingestion). New in 1.4.x: verify-axis check-class taxonomy (mandatory-pass/measured/judged), no-reinvention Tier-0 inventory, 7-class failure taxonomy, Destructive-Op Gate, Wave-T (Temper), tier-floor governance, Mode D Model Notice, FC consent lane, default-Sonnet guidance. New in 1.3.0: public-surface-audit, field-harvest Mode B auto-trigger, 4-axis gate scope ext. Validated cross-CLI: Claude Code, Codex, Gemini.",
16
16
  "source": "./plugins/fh-meta"
17
17
  },
18
18
  {
19
19
  "name": "fh-commons",
20
- "version": "2.1.0",
20
+ "version": "2.3.0",
21
21
  "description": "Project-agnostic utility skills — 5 skills (convergence-loop · deliberation · mcp-circuit-breaker · token-budget-gate · ko-tech-writer) + 1 agent (quench-challenger). Domain-independent utilities transplantable into any project.",
22
22
  "source": "./plugins/fh-commons"
23
23
  }
package/AGENTS.md CHANGED
@@ -121,15 +121,22 @@ Because non-Claude runtimes do not auto-load Claude path rules, apply these rule
121
121
  `ⓐ=… ⓑ=→standpoint ⓒ=… ⓓ=… ⓔ=… ⓕ=…`; markers dated earlier keep the old **ASCII four**
122
122
  (`a b c d`) and are not retroactively blocked. 🟥 **The two arrays are not the same letters —
123
123
  old `b` (first real use) is now `ⓔ`, old `d` (revert probe) is now `ⓕ`.** Copying an old line
124
- forward silently swaps two axes and raises no error, which is why the *notation itself* declares
125
- which array a marker used. `standpoint:` remains the canonical field for (the `axes-run` entry
124
+ forward silently swaps two axes and raises no error. **Which array a marker used is decided by
125
+ the date in its filename** (`< 2026-08-17` = old four). ⚠️ The notation is NOT the discriminator
126
+ — that claim stood in this file for part of 2026-08-17 and a hand-count of the corpus refuted it:
127
+ 2 of the 4 circled-key markers on disk are dated 2026-08-10 and carry the OLD meanings. Aligning
128
+ the notation still helps going forward; it does not work backwards.
129
+ `standpoint:` remains the canonical field for ⓑ (the `axes-run` entry
126
130
  is only a pointer to it, and a pointer at an empty field is blocked); **its value enum is still
127
131
  validated by nothing** — that is the one remaining gap, and it is not the same thing as the axis
128
132
  being unmechanized. Format spec: `.claude/rules/fh_4axis_gate.md §Marker axis fields`.
129
133
  (Two drift corrections landed here on 2026-08-17: first this sentence said "four" while its own
130
134
  next clause described the +1 — caught by the session-close ④-b CC↔Codex parity check — and then
131
135
  the machine layer moved to six the same day.)
132
- **ⓓ has no field at all** — record it in prose and do not read a silent ⓓ as a run one.
136
+ ⚠️ Until 2026-08-17 this entry point added "**ⓓ has no field at all** — record it in prose".
137
+ That is now **false**: `ⓓ=` is a required key like the rest. The retraction is kept visible
138
+ rather than deleted, because a Codex-side reader who memorised the old line would otherwise
139
+ keep writing markers without ⓓ and see them blocked with no idea why.
133
140
  🟥 ⓑ **standpoint is itself split** (2026-08-17): a STATIC read of the target's own files is
134
141
  `tier1b` and **executes nothing**; `tier2`+ asserts that something was RUN — the discriminator is
135
142
  mechanical, *name the command you ran and the output you saw*. Measured on one delta: the static
@@ -232,13 +232,47 @@ residency 가 걸린 환경(조직 내부·규제·고객 데이터)에서는 **
232
232
  |---|---|---|
233
233
  | **Step 0 레지스터** | 독자용 아키텍처 기술문서 (문어 존댓말/명료체) | mandatory-pass |
234
234
  | **Step 1 캘리브레이션** | FH Knowledge Core 정본 표본(`knowledge/shared/harness-core/fh_ecosystem_positioning.md`) 서식 및 톤 대조 완료 | mandatory-pass |
235
- | **Step 2 문체 규율** | 번역투·조각문 5종 스캔 «잔여 0건 (양성 컨트롤 동반)» 주장 | 🟥 `UNCALIBRATED` 컨트롤이 무엇이었는지·재현 커맨드·출력이 **하나도 없다**. 재현 불가한 0 0 증거가 아니다 |
235
+ | **Step 2 문체 규율** | 번역투·조각문 5종 스캔 «잔여 0건 (양성 컨트롤 동반)» 주장 | 🟡 **부분 해소 (2026-08-17)** — 계기의 **판별력**은 이제 재현 가능하다: `bash scripts/test_ko_tech_writer_lanes.sh`(9레인, 픽스처 `plugins/fh-commons/skills/ko-tech-writer/fixtures/known_{positive,negative}.md`). 5클래스 전부 «양성 ≥1 · 음성 0» 으로 갈린다. 🟥 **그러나 이 행의 원 주장(«이 문서에서 잔여 0건»)은 여전히 미검증**이다 — 판별력과 잔여는 다른 명제이고, 후자는 문서마다 따로 재야 한다. 아래 §Step2-calibration 참조 |
236
236
  | **Step 3 정직 수위** | 저자 내부 집계 규율 서술 제거 및 독자 의사결정 기반 정보 보존 | judged |
237
- | **Step 4 수치·주장 게이트** | 수치 전수 추출 + «전칭 단정 스캔 잔여 0건» 주장 | 🟥 `UNCALIBRATED — 자기반증`: 그 «0건» 시점에 본문에 전칭 단정이 **3건 살아 있었다**(«완벽 보호» ×2, «가장 뛰어난 하네스») — 임포트 심사가 손으로 잡아 정정했다. 계기는 초록인데 대상을 안 쟀다 |
237
+ | **Step 4 수치·주장 게이트** | 수치 전수 추출 + «전칭 단정 스캔 잔여 0건» 주장 | 🟡 **부분 해소 (2026-08-17)** — 전칭 단정 후보 검출 2패턴(어휘형·부정형)의 판별력이 같은 스위트로 재현된다(양성 2·2건 / 음성 0·0건). 🟥 **자기반증 사실 자체는 그대로 남는다** — 아래 원 판정 유지. 그리고 «수치 전수 추출» 쪽은 **미보정**이다(이 스위트가 안 다룬다). ↓ 원 판정: 🟥 `UNCALIBRATED — 자기반증`: 그 «0건» 시점에 본문에 전칭 단정이 **3건 살아 있었다**(«완벽 보호» ×2, «가장 뛰어난 하네스») — 임포트 심사가 손으로 잡아 정정했다. 계기는 초록인데 대상을 안 쟀다 |
238
238
  | **Step 5 지각 QA** | Mermaid 다이어그램 노드 레이블 및 Decision Tree ASCII 렌더링 시각 확인 완료 | judged |
239
239
  | **글로벌 인프라 심사** | 논리 격리 vs 보안 샌드박싱 분리, 자원 경합 및 Context Budgeting 피드백 반영 | 🟥 `LOCAL-ONLY ATTESTATION — UNVERIFIED`: 저자 런타임(Antigravity) 측 심사이고, 짝으로 적혀 있던 `research` 서브에이전트는 **이 레포에 존재하지 않는다**(등록 에이전트 8종 중 없음). FH 안에서 재현 불가 |
240
240
  | **적대적 공격 심사** | 직교적 3계층 모델 재정립, $N_{human}$ vs $M_{subagent}$ 인지 분리, 샌드박싱 조건 개고 | 🟥 `LOCAL-ONLY ATTESTATION — UNVERIFIED`: `challenger` 는 실재하는 FH 에이전트지만(`plugins/fh-meta/agents/challenger.md`), 이 행이 가리키는 실행의 마커·로그가 없다. **이름의 실재는 실행의 증거가 아니다** |
241
241
 
242
+ <a name="step2-calibration"></a>
243
+ ### §Step2-calibration — 2026-08-17, 무엇이 해소됐고 무엇이 안 됐나
244
+
245
+ **기원**: 챔버 런 #12(`prosody-lens`, KILL)가 이 두 행을 **배출 판정의 결정적 근거**로 인용했다 —
246
+ *"같은 계열 계기가 미보정인데 하나 더 짓는 것은 재발명이자 미보정 계기의 증식이다."*
247
+ 운영자 결정(2026-08-17): **새 계기보다 이 부채가 먼저.**
248
+
249
+ ```
250
+ 재현 커맨드 bash scripts/test_ko_tech_writer_lanes.sh
251
+ 픽스처 plugins/fh-commons/skills/ko-tech-writer/fixtures/known_positive.md
252
+ plugins/fh-commons/skills/ko-tech-writer/fixtures/known_negative.md
253
+ 결과 9 레인 전건 통과 — 5클래스 + 전칭 2패턴 + META 컨트롤 2
254
+ 엔진 ripgrep 15.1.0 (SKILL.md 가 rg 로 고정한 그 엔진)
255
+ ```
256
+
257
+ **해소된 것**: 「컨트롤이 무엇이었는지·재현 커맨드·출력이 하나도 없다」 — 셋 다 생겼다.
258
+ 각 클래스가 **양성 ≥1 · 음성 0** 으로 갈린다(존재 확인이 아니라 판별 확인).
259
+
260
+ 🟥 **해소되지 않은 것 — 축소하지 않는다**
261
+ 1. **원 주장(«이 문서에서 잔여 0건»)은 여전히 미검증.** 판별력과 잔여는 다른 명제다.
262
+ 이 스위트를 근거로 «잔여 0» 을 주장하면 강등 사유가 그대로 재발한다 — 스위트 자신이
263
+ 출력 말미에 그렇게 인쇄한다.
264
+ 2. **Step 4 의 자기반증 사실은 그대로 남는다**(«0건» 시점에 전칭 단정 3건 생존).
265
+ 3. **«수치 전수 추출»은 이 스위트가 안 다룬다** — Step 4 의 절반은 여전히 미보정.
266
+ 4. 🟥 **부수 발견 — 정본의 분류가 과장이다.** SKILL.md 는 *"앞 다섯 줄은 기계 검출 가능"*
267
+ 이라 적었는데, **실제로 grep 을 싣고 있는 것은 C1(줄표)·C5(소유 직역) 둘뿐**이고
268
+ C2·C3·C4 는 산문 힌트다. 특히 **C4 조각문은 패턴이 없다**(«서술어 없는 마침»).
269
+ 첫 캘리브레이션이 그걸 드러냈다 — 내가 임의로 지은 C4 패턴이 **양성을 0건으로 놓쳤다.**
270
+ 지금 실린 C4 는 **닫힌 명사 어휘 목록**이라 recall 이 낮고, 넓히려면 어휘 추가가 아니라
271
+ 형태소 분석이 필요하다. **낮다는 사실을 스크립트 주석에 적었다.**
272
+ 5. 이 스위트는 `rg` 부재 시 **SKIP 이 아니라 rc=10** 으로 끝난다(미측정을 통과로 렌더 금지).
273
+ ⚠️ 실측 계기: 이 개발 머신의 `grep` 은 **ugrep 7.5.0**(GNU 아님)이라 한글 word 경계가
274
+ 갈릴 수 있다 — SKILL.md 가 엔진을 `rg` 로 못 박은 이유가 여기서 실증된다.
275
+
242
276
  > 🟥 **이 부록 전체의 지위**: 저자 런타임의 **자기신고**이며, 위 «잔여 0건»·«PASS» 는 아티팩트로
243
277
  > 뒷받침되지 않는다. FH 자기 규율상 이것은 증거가 아니라 저자의 주장이다
244
278
  > (`fh_4axis_gate.md §Reviewer-visible evidence` 의 degrade 라벨을 그대로 적용).
@@ -515,6 +515,42 @@ thing being counted"* 라고 경고한 그 형태가, **그 경고를 적은 채
515
515
  잴 것은 파일이 아니라 **지시**였다. 되돌림 실측: `git show HEAD:` 판으로 되돌리면 L13-b 가
516
516
  적색(`step6 이 증인 아티팩트를 고치라고 시킨다`), 컨트롤은 초록 유지.
517
517
 
518
+ ### 🟥 §P1-2026-08-17-b — 그리고 그 수리로도 아직 부족했다. **커밋을 합치는 모든 경로가 증인을 죽인다**
519
+
520
+ 런 #12(`prosody-lens`, KILL)가 수리 후 첫 런이었고, TAMPERED 는 사라졌으나 **`UNORDERED`** 가 나왔다.
521
+ 판정 코드가 이유를 명시한다 — `chamber_witness.sh do_verify`:
522
+
523
+ ```bash
524
+ # 주석 원문: "같은 커밋(또는 같은 초)에 들어온 verdict 는 «먼저» 를 증명하지 못한다"
525
+ if [ "$verdict_ts" -le "$pre_max_ts" ]; then # ← -le. 같은 «초» 도 실패
526
+ echo "UNORDERED — verdict 가 pre-verdict 아티팩트보다 먼저이거나 같은 시점에 커밋됐다."
527
+ return 1
528
+ fi
529
+ ```
530
+ 이건 의도된 설계다(초판이 `-lt` 라 same-second 역순을 통과시켰고 cross-family 가 잡았다).
531
+ 문제는 **그 조건을 만드는 경로가 이 레포의 표준 절차 안에 셋이나 있다**는 것이다:
532
+
533
+ | 경로 | 실측 |
534
+ |---|---|
535
+ | **P-a `--squash` 머지** | 런 #11 은 브랜치에서 게이트/verdict 를 **따로** 커밋해 순서가 성립했다(`e86f796`→`f79cc05`→`852064b`). PR #414 를 squash 하자 **main 에서 원장을 건드린 커밋 = 1개** — 게이트 4 + verdict 가 전부 그 하나에 접혔다 |
536
+ | **P-b 저자가 한 커밋에 배치** | 런 #12 에서 4해시를 한 번에 커밋(`50ef761`) → 브랜치에서부터 UNORDERED. 실측 `verdict_commit=1786935355 latest_pre=1786935355` |
537
+ | **P-c `--delete-branch`** | 순서를 담은 커밋이 도달 불가가 된다(런 #12 의 첫 증인 커밋이 그렇게 됐다) |
538
+
539
+ 🟥 **정정**: 위 §P1-2026-08-17 을 *"P1 이 만족 가능해졌다"* 로 읽지 마라. 정확히는
540
+ **피처 브랜치에서 만족 가능하고, 이 레포가 의무화한 squash 머지가 main 에서 그것을 파괴한다.**
541
+
542
+ **채택된 처방 ⓐ (운영자 결정 2026-08-17) — 정책 변경 0**:
543
+ > **게이트 해시와 verdict 해시를 두 PR 로 분리한다.** 각각 squash 해도 main 에 **2커밋**이 남아
544
+ > 순서가 보존된다. 저자 규율이 따라온다 — **게이트 커밋과 verdict 커밋을 절대 합치지 않는다**(P-b 차단).
545
+
546
+ **배선**(산문으로 안 남긴다): `chamber_run.sh` 가 **행위자가 읽는 자리에서** 이 규율을 인쇄한다 —
547
+ 게이트 기록 시 «verdict 보다 먼저, 별도 커밋으로», verdict 기록 시 «게이트 해시들과 **다른 커밋**»,
548
+ 그리고 UNORDERED 일 때 세 경로를 이름으로 열거. 앵커는 `test_chamber_run_lanes.sh` **L15/L15b/L15c**
549
+ (33레인) — 🟥 **그 레인이 생긴 이유는 러너가 틀린 처방을 가르치고 있었기 때문이다**:
550
+ *"원장 해시를 커밋한 뒤 재실행하면 증인이 성립한다"* — 한 커밋에 넣으면 성립하지 않는다.
551
+ **게이트 문구는 «막는가»만이 아니라 «옳은 것을 가르치는가»도 재야 한다**
552
+ (`[[feedback_gate_prescription_is_unverified]]`, 2026-08-17 3번째 사례).
553
+
518
554
  ⚠️ **런 #11 자신은 TAMPERED 로 남는다. 소급 수리하지 않는다** — 과거 9런을 back-fill 하지 않는 것과
519
555
  같은 이유다(사후에 쓴 기록은 증명하지 않는다). **P1 은 이 수리 이후의 EMIT 런에서 처음 만족 가능하다.**
520
556
  🟥 그리고 이 수리는 **P1 을 만족시키지 않는다** — 만족을 *가능하게* 만들 뿐이다. ②는 🔵 유지.
@@ -2146,3 +2146,39 @@
2146
2146
  outcome: accepted
2147
2147
  evidence: "8건 지적(S2·A3·B3). 그중 2건(혼용 오탐 · 화살표 fail-open)은 내가 투입 전 자력 수리했고 codex 가 독립으로 같은 지점을 지목해 수렴 확인 — 그 둘은 「둘 다 봤다」. **나머지 5건 자력 적발 0**: grep 이 주석 처리된 호출에 매칭(S) · if:false 로 꺼진 잡(S) · 트리거 미검사(A) · 잡 신원 미검사(A) · 마커 파일명 날짜 추출 fail-open(B, 선재). 각 지적에 깨뜨리는 입력을 구체적으로 제시해 손 재현이 즉시 가능했다"
2148
2148
  cost: 85k tokens
2149
+ - date: 2026-08-17
2150
+ agent: fh-meta:beginner (isolated) — 챔버 런 #12 step-4 블라인드 1/3 (낭독 렌즈)
2151
+ purpose: "「소리 내어 읽히는가」 계기 후보의 냉담 첫 접촉 — 정의가 서는가, 첫 실패 지점, ko-tech-writer 와의 겹침"
2152
+ outcome: accepted
2153
+ evidence: "tool_uses 3(실독). HARD 4/SOFT 2 · **첫 산출물 도달 실패**. 최대 소득 = *「소리 내어 읽힌다」의 예시 셋이 서로 다른 층이라 정의를 역추정하는 것조차 안 된다* — 번역투=어법층(소리 무관) · 호흡=글자수 · 운율만 소리. 🟥 **그 결함은 후보가 아니라 내 한 줄 요약이 갖고 있었다**(정본은 A~F 로 층을 갈라놨다). ko-tech-writer 대조로 Step 2(번역투 7클래스)·Step 2-b(리듬 수치 대조)를 줄번호로 짚음"
2154
+ cost: 112k tokens
2155
+ - date: 2026-08-17
2156
+ agent: fh-meta:main-player (isolated) — 챔버 런 #12 step-4 블라인드 2/3
2157
+ purpose: "실사용자 일상 가치 — 매일 쓸 층이 있는가, 별도 계기인가 한 스텝인가"
2158
+ outcome: accepted
2159
+ evidence: "tool_uses 4. 판정 **NO** — 장면 3/3 대체, 증명된 사용자 **n=1·월 3회**. 🟥 결정적 한 줄: *「후보의 known-positive 가 이미 남의 집에 있다」*(ko-tech-writer:84-90 의 124자 문장·어간 4연속 실측) — **픽스처가 남의 집에 있는 계기는 별도 계기가 아니다.** 그리고 한국어 기술문서 인구를 *「못 잡는다 — UNMEASURED 이지 크다도 작다도 아니다」* 로 **스스로 자백**해 판정 신뢰도를 올렸다. 진짜 구멍 지목: 낭독 대본 레지스터의 Step 5 N/A"
2160
+ cost: 106k tokens
2161
+ - date: 2026-08-17
2162
+ agent: fh-meta:challenger (isolated) — 챔버 런 #12 step-4 블라인드 3/3
2163
+ purpose: "배출 후보 적대 심사 — 술어 성립성·재발명·정답라벨·보이지 않는 것"
2164
+ outcome: partial
2165
+ evidence: "tool_uses 12, S 4건. **VALID**: S3(기계화 가능분은 이미 있는 스텝) · S4(표면지표 환원 시 **양방향 오탐**, known-negative 3건을 손으로 제작해 시연) · A3(정답 라벨 주체 부재, SKILL.md:235 «생성자=평가자 금지»). 🟥 **S2 는 거버너가 반증**: *「산출물 회수 불가, rg 낭독 tracks/ → no matches」* 의 repro 가 **재현 안 된다**(실측 12파일, 정본 신호 272줄 실재). S1(«양식 미도달»)은 과장 — 음절수·동음이의는 텍스트에서 계산되는 소리 속성. ⇒ **사이드카 발견은 소스로 닫히기 전엔 판정이 아니다**(오늘 2번째). 본인이 *「나도 스캐너를 안 돌렸고 나 역시 소리를 못 낸다」* 로 등급을 스스로 깎은 것은 신뢰도를 올렸다"
2166
+ cost: 131k tokens
2167
+ - date: 2026-08-17
2168
+ agent: general-purpose (isolated) — 챔버 런 #12 조건 1(net-new) measured 스캔
2169
+ purpose: "외부 생태계(한국어권 1순위) + 로컬 전수 스캔으로 재발명 여부 측정. 런 #11 의 자백된 사각(영어 전량)을 INTENT 가 필수 조건으로 박았다"
2170
+ outcome: accepted
2171
+ evidence: "tool_uses 27(WebSearch 12 중 **한국어 7** · GitHub API 15 · WebFetch 4 · 로컬 정독). 이 런의 KILL 을 가른 계기이고 **거버너의 프레이밍 오류 영향을 가장 덜 받았다**. ⓑ번역투 **포화** — 2026-06~08 3개월간 한국어 번역투 제거 스킬 **10+ 신설**(humanizer-ko 35패턴 등) · ⓒ처방형식은 이 분야 **기본값** · ⓐ운율만 외부 공백(GitHub 전체 `낭독` SKILL.md **1건=우리 미러**). 🟥 **결정적 근거를 찾았다** — harness_terminal_correlation:235-236 이 ko-tech-writer Step 2·4 를 이미 `UNCALIBRATED` 로 강등(Step 4 는 자기반증까지). 미탐 자백 7종을 스스로 열거(GitHub API 403 4쿼리 미실행 · 국립국어원 규칙목록 미개봉 · description 기준 판정 등)"
2172
+ cost: 152k tokens
2173
+ - date: 2026-08-17
2174
+ agent: codex/gpt-5.5 (cross-family sidecar) — qasp-dev #174 load-bearing 게이트 리뷰
2175
+ purpose: "게이트 exit enum·면제 경로 변경의 degrade-direction / bypass / 테스트 판별력 적대검증"
2176
+ outcome: partial
2177
+ evidence: "5건 지목, 전부 diff 인용 동반. **실행으로 갈랐다 — 2 반증 · 3 확인.** 🟥 반증 둘(깊은 `src/**/*.py`·중첩 `scripts/ci/*.sh` 가 샌다)의 근거 오류가 같다: **bash `case` 의 `*` 는 `/` 를 먹는다**(경로 글롭 semantics 로 읽음) — 실측 rc=1 로 둘 다 정상 차단. 확인 셋 중 **이 PR 이 새로 만든 것은 S1-c 하나**(sandbox 제외가 침묵, 종전 rc=4→rc=0 무흔적)이고 그것만 수리. 나머지 둘은 기존 결함·재발 0건이라 미착수. 테스트 판별력에서 되돌림 에이전트와 **갈렸고 코덱스가 옳았다**(파일명 substring 은 그 이름이 위반 목록에 실려도 참 → 보고 형태로 조임). ⇒ 사이드카 발견은 소스로 닫히기 전엔 판정이 아니다 — 이번엔 40% 가 틀렸다"
2178
+ cost: 68k tokens
2179
+ - date: 2026-08-17
2180
+ agent: general-purpose (isolated, sonnet) — qasp-dev #174 되돌림 프로브 ⓕ축
2181
+ purpose: "PR 이 추가한 테스트가 장식인지 실측 — 수리 4건 개별 되돌림 3단(적용확인→실행→복원) + substring 8행 판별력"
2182
+ outcome: accepted
2183
+ evidence: "tool_uses 25(계기 생존 — 카드가 경고한 `tool_uses: 0` 아님). **4/4 앵커 생존**, 매번 대응 레인만 적색(S1→2건·S2→1건·S3→2건·B1→known-pair 2건), 무관 25~28개는 통과 → 과결합 0. 복원 후 29 passed 재확인, 트리 clean. 🟥 substring 8행은 **전부 ✅ 로 판정했는데 코덱스와 갈렸고 이쪽이 졌다** — 「뚫리는 입력을 제시 못 함」을 판별력 있음으로 읽었으나, 그 단언이 *주장*하는 것(«범위 밖 보고가 났다»)은 파일명만으로 증명되지 않는다. **없음을 증명 못 함 ≠ 판별력 있음**"
2184
+ cost: 135k tokens
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chrono-meta/fh-gate",
3
- "version": "2.1.0",
3
+ "version": "2.3.0",
4
4
  "description": "FH runtime adapters — run FH governance, skills, and agents via Claude or Codex with machine-parseable gates.",
5
5
  "license": "MIT",
6
6
  "keywords": [
@@ -72,6 +72,10 @@
72
72
  "scripts/test_lane_runner_lanes.sh",
73
73
  "scripts/test_version_lockstep_lanes.sh",
74
74
  "scripts/package_coverage_check.sh",
75
+ "scripts/test_ko_tech_writer_lanes.sh",
76
+ "scripts/ko_tech_writer_calibrate.py",
77
+ "plugins/fh-commons/skills/ko-tech-writer/fixtures/known_positive.md",
78
+ "plugins/fh-commons/skills/ko-tech-writer/fixtures/known_negative.md",
75
79
  "scripts/publish_freshness_check.sh",
76
80
  "scripts/prepublish_scope_note.sh",
77
81
  "scripts/lane_runner_check.sh",
@@ -80,6 +84,9 @@
80
84
  "scripts/adapters/gstack_content_safety.sh",
81
85
  "scripts/adapters/mate_agent_boundary.sh",
82
86
  "scripts/adapters/qasp_new_code_anchor.sh",
87
+ "scripts/adapters/qasp_web_rules.sh",
88
+ "scripts/adapters/fixtures/qasp_web_rules_known_positive.json",
89
+ "scripts/adapters/fixtures/qasp_web_rules_known_negative.json",
83
90
  "scripts/test_adapter_lanes.sh",
84
91
  "scripts/test_fh_gate_regressions.sh",
85
92
  "templates/local_fh_context.md",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fh-commons",
3
- "version": "2.1.0",
3
+ "version": "2.3.0",
4
4
  "engines": {
5
5
  "claudeCode": ">=1.0.0"
6
6
  },
@@ -0,0 +1,19 @@
1
+ <!-- KNOWN-NEGATIVE 픽스처 — Step 2/4-b 스캔이 «잡으면 안 되는» 표본.
2
+ 같은 주제·비슷한 길이의 정상 산문. 히트가 나오면 그 스캔은 과차단이다. -->
3
+
4
+ # 표본 — 같은 내용을 정상 문체로
5
+
6
+ 파이프라인은 세 단계로 나뉘고, 각 단계가 서로 다른 결함을 잡습니다.
7
+
8
+ - **격리**: 에이전트를 별도 컨텍스트에서 돌리는 방식입니다.
9
+
10
+ 관측된 분업은 검출을 기계가 맡고 판정을 사람이 맡는 형태입니다.
11
+
12
+ 여기서 한 층이 걸립니다.
13
+
14
+ 이 스캐너에는 이전 상태가 없습니다.
15
+ 그 판정에는 재현 경로가 없습니다.
16
+
17
+ 이 방식에서는 오탐이 세 건 나왔고, 그 셋을 손으로 확인했습니다.
18
+ 후보 목록에서 두 건이 빠졌으며, 남은 항목은 개별로 판정했습니다.
19
+ 그 경로는 두 차례 관측됐습니다.
@@ -0,0 +1,20 @@
1
+ <!-- KNOWN-POSITIVE 픽스처 — Step 2/4-b 스캔이 «잡아야 하는» 표본.
2
+ 🟥 이 파일은 일부러 번역투를 담고 있다. 문체 교정 대상이 아니다.
3
+ 각 줄 끝 주석이 어느 클래스를 심었는지 밝힌다. 클래스당 최소 1건. -->
4
+
5
+ # 표본 — 일부러 심은 번역투
6
+
7
+ 파이프라인은 세 단계로 나뉩니다 — 그리고 각 단계가 서로 다른 결함을 잡습니다. <!-- C1 줄표 이어붙임 -->
8
+
9
+ - **격리** — 에이전트를 별도 컨텍스트에서 돌리는 방식입니다. <!-- C2 용어-머리 -->
10
+
11
+ 관측된 분업은: 검출은 기계가, 판정은 사람이. <!-- C3 콜론 나열투 -->
12
+
13
+ 여기서 걸리는 층. <!-- C4 조각문 -->
14
+
15
+ 이 스캐너는 이전 상태를 갖지 않습니다. <!-- C5 소유 직역 -->
16
+ 그 판정은 재현 경로를 가지고 있지 않습니다. <!-- C5 소유 직역(변형) -->
17
+
18
+ 이 방식으로는 오탐이 전혀 나오지 않았습니다. <!-- C6 전칭 단정(Step 4-b) -->
19
+ 후보 목록에서 하나도 빠지지 않았고, 예외 없이 모두 통과했습니다. <!-- C6 전칭 단정 -->
20
+ 그 경로는 한 번도 잡히지 않았다. <!-- C6 부정형 전칭 -->
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fh-meta",
3
- "version": "2.1.0",
3
+ "version": "2.3.0",
4
4
  "engines": {
5
5
  "claudeCode": ">=1.0.0"
6
6
  },
@@ -10,6 +10,104 @@ Format: [Keep a Changelog](https://keepachangelog.com/en/1.1.0/)
10
10
 
11
11
  ## Plugin Level
12
12
 
13
+ ### [2.3.0] — 2026-08-17
14
+
15
+ 🟥 **BREAKING (gate) ①**: the capability effect-probe (`M6`) now watches **directory existence and
16
+ non-regular nodes**, not files only. A `.cap` declaring `writes: read-only` whose entry point
17
+ creates an **empty directory**, a **symlink**, or a **fifo/socket/device node** now returns
18
+ `VIOLATION` where it previously returned `✅ VERIFIED`. Registration of such a capability
19
+ **blocks**.
20
+ - **Remedy**: declare the real value (`write-local` / `write-remote`), or stop creating the node.
21
+ The probe prints which surface changed.
22
+ - **Why**: `_snapshot()` used `find -type f`, so **empty directories were invisible** — git does not
23
+ track them, so they are absent from the isolated-clone sandbox too. Measured with a 3-arm
24
+ known-pair: write-file-to-new-path → 1, write-file-to-existing-dir → 1, **empty-dirs-only → 0**
25
+ (the false green). A cross-family review then found the same class still open one layer down:
26
+ `ln -sf /etc/passwd link` and `mkfifo pipe` **also passed** after the directory axis was added.
27
+ Both are now caught; symlinks are compared **by target**, since existence alone makes
28
+ `ln -sf other link` look like no change.
29
+ - ⚠️ **Named residual, unchanged**: metadata-only writes (`chmod`, xattr, hardlink topology) and
30
+ empty directories **under `.git`** remain invisible. And the probe **still cannot discriminate an
31
+ adapter-class capability** — its outside-watch is a temp canary plus `$HOME`, while an adapter by
32
+ definition runs in the peer tree. That value stands on hand measurement, not on the probe.
33
+
34
+ 🟥 **BREAKING (gate) ②**: writing `ⓓ=→thirdparty` in the Axes 2–3 marker now **requires a non-empty
35
+ `thirdparty:` line**. A marker with the pointer but no target **blocks the commit**.
36
+ - **Remedy**: the hook prints the accepted forms —
37
+ `checked(<what was surveyed>)` · `none-found(<what was searched>)` · `UNKNOWN` · `not-applicable`.
38
+ - **Why**: a pointer at nothing is not a record. Same failure the `ⓑ=→standpoint` guard closed.
39
+
40
+ **Added**
41
+ - `adapter/qasp-web-rules` — a cluster node wrapping a peer harness's web selector-stability rule
42
+ set. Enum `0=CLEAN 1=FINDINGS 2=ARGS 3=ENGINE_ERROR 10=HARNESS_ERROR 20=PEER_ABSENT`, calibration
43
+ pair from **FH-owned fixtures** (offline, deterministic), `writes: write-local` established by
44
+ cold-clone measurement. Registration bar M1–M6: **REGISTRABLE**.
45
+ 🟥 **Consumer 0** — declared and callable, nothing calls it yet. Do not round that to "wired".
46
+ 🟥 The declared enum is **wider than the peer's documented contract** (`0/1/3`): the underlying
47
+ CLI uses `parser.error()`, argparse exits **2**, and the shell wrapper propagates it verbatim.
48
+ The adapter declares the **real** contract, or an argument mistake renders as "the instrument broke".
49
+
50
+ **Fixed**
51
+ - The adapter's argument validation now runs **before** peer resolution. Previously the **same bad
52
+ call returned different values on different machines** — `ARGS(2)` where the peer was installed,
53
+ `HARNESS_ERROR(10)` where it was not — i.e. the check measured **what is installed on this box**
54
+ rather than **how it was called**. `ARGS` is a statement about the call; `PEER_ABSENT` is a
55
+ statement about the cluster; the two are independent facts.
56
+ - Capability entry points and their calibration fixtures are now in `files[]`. Consumers previously
57
+ received the **declaration without the instrument** — a `.cap` that passes the registration bar
58
+ and then returns `HARNESS_ERROR` inside the tarball.
59
+
60
+ **Note for consumers of the effect-probe**
61
+ If you hold `.cap` files, re-run the probe **before** upgrading: this release changes verdicts for
62
+ the three shapes above. A green result under 2.2.0 is not evidence under 2.3.0.
63
+
64
+ ---
65
+
66
+ ### [2.2.0] — 2026-08-17
67
+
68
+ 🟥 **BREAKING (gate)**: chamber step 6 now reads `ACTUAL.md`, **not `BUDGET.md`**. An in-flight
69
+ chamber run whose actual cost was written into `BUDGET.md` will **block at step 6** until the value
70
+ moves to a new `ACTUAL.md` in the same workspace.
71
+ - **Remedy**: the runner prints the exact path when it blocks — move the `ACTUAL:` line to
72
+ `tracks/_chamber/<slug>/ACTUAL.md`. `BUDGET.md` keeps `ESTIMATE:` only.
73
+ - **Why**: `BUDGET.md`'s pre-verdict hash IS the ordering witness (`ship_readiness_gate §② P1`), and
74
+ step 6 was hard-blocking until that same file changed. So **every run that reached COMPLETE
75
+ necessarily mutated a witnessed artifact** and `verify` returned `TAMPERED` — identity ②'s only
76
+ promotion condition was unsatisfiable by construction, not by strictness. Measured on chamber run
77
+ #11, the first run ever taken through step 7. Two roles (immutable witness / post-verdict
78
+ calibration sink) had collided in one file; each was correct alone, so neither side's code showed
79
+ the conflict.
80
+
81
+ **Added**
82
+ - `scripts/ko_tech_writer_calibrate.py` + `scripts/test_ko_tech_writer_lanes.sh` + two known-pair
83
+ fixtures — the discrimination of `ko-tech-writer` Step 2 (five translationese classes) and
84
+ Step 4-b (universal-claim candidates) is now **reproducible**: positive ≥1 / negative 0 per class,
85
+ plus two META controls. Wired into `selfcheck.sh` and shipped in `files[]`.
86
+ 🟥 **What this does NOT prove**: "zero residue" in any real document. Discrimination and residue
87
+ are different propositions; the suite prints that warning itself.
88
+ - Chamber lane suite **12 → 33 lanes**, including the **runner × witness seam** that no test covered
89
+ (the runner's lanes excluded the witness by design; the witness self-test ran it standalone).
90
+
91
+ **Changed**
92
+ - `chamber_run.sh` now teaches the **two-commit discipline** where the actor reads it: gate hashes
93
+ and the verdict hash must land in **separate commits** (and, for a squash-merge repo, **separate
94
+ PRs**). Committing them together yields `UNORDERED` — the runner previously advised the opposite.
95
+ - `chamber_witness.sh do_record` skips a byte-identical re-record of the same
96
+ `(run, artifact, sha)` triple. A **changed** artifact still appends — that is the tamper evidence.
97
+
98
+ **Fixed**
99
+ - `harness_terminal_correlation_and_recommendations.md` Appendix: Step 2 / Step 4 rows move from
100
+ 🟥 `UNCALIBRATED` to 🟡 **partially resolved**, with the reproduction command recorded. The
101
+ original claims ("zero residue" in that document; Step 4's numeric extraction half) remain
102
+ **unverified and are labelled as such**.
103
+
104
+ **Known residual (not closed)**
105
+ - Identity ② stays **RC**. No run has yet been `WITNESSED` on `main`; the two-PR prescription is
106
+ reasoned from the verify logic, and its efficacy is provable only by the next EMIT run.
107
+ - `--delete-branch` still orphans ordering evidence — warned in prose, not blocked.
108
+ - Of Step 2's five "machine-detectable" classes, only two ship an actual grep in `SKILL.md`; the
109
+ calibration surfaced that the doc over-claims. C4's pattern is a closed noun list with **low recall**.
110
+
13
111
  ### [2.1.0] — 2026-08-17
14
112
 
15
113
  🟥 **BREAKING (gate)**: `crossfamily: declined` in an Axes 2-3 marker now requires grounds naming a
@@ -0,0 +1,7 @@
1
+ [
2
+ {
3
+ "filename": "tests/e2e/test_checkout_web.py",
4
+ "status": "modified",
5
+ "patch": "@@ -1,3 +1,5 @@\n import pytest\n+\n+def test_pay(page):\n+ page.get_by_role(\"button\", name=\"결제\").click()\n"
6
+ }
7
+ ]
@@ -0,0 +1,7 @@
1
+ [
2
+ {
3
+ "filename": "tests/e2e/test_checkout_web.py",
4
+ "status": "modified",
5
+ "patch": "@@ -1,3 +1,6 @@\n import pytest\n+\n+def test_pay(page):\n+ page.wait_for_timeout(3000)\n+ page.click(\"text=결제\")\n"
6
+ }
7
+ ]