@chrono-meta/fh-gate 2.2.0 → 2.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/.claude/capabilities/adapters/qasp-new-code-anchor.cap +5 -2
  2. package/.claude/capabilities/adapters/qasp-web-rules.cap +130 -0
  3. package/.claude/rules/fh_4axis_gate.md +55 -0
  4. package/.claude-plugin/marketplace.json +2 -2
  5. package/CLAUDE.md +51 -6
  6. package/README.ja.md +14 -2
  7. package/README.ko.md +14 -1
  8. package/README.md +16 -2
  9. package/README.zh.md +12 -1
  10. package/knowledge/shared/harness-core/field_verdict_crossfamily_gate.md +231 -7
  11. package/knowledge/shared/harness-core/harness_incubator_doctrine.md +215 -1
  12. package/knowledge/shared/harness-core/ship_readiness_gate.md +168 -1
  13. package/knowledge/shared/learnings/subagent_invocations_log.yaml +24 -0
  14. package/package.json +9 -1
  15. package/plugins/fh-commons/.claude-plugin/plugin.json +1 -1
  16. package/plugins/fh-meta/.claude-plugin/plugin.json +1 -1
  17. package/plugins/fh-meta/CHANGELOG.md +110 -0
  18. package/plugins/fh-meta/skills/auto-decorrelation/SKILL.md +20 -0
  19. package/plugins/fh-meta/skills/steel-quench/SKILL.md +25 -2
  20. package/scripts/adapters/fixtures/qasp_web_rules_known_negative.json +7 -0
  21. package/scripts/adapters/fixtures/qasp_web_rules_known_positive.json +7 -0
  22. package/scripts/adapters/mate_agent_boundary.sh +27 -7
  23. package/scripts/adapters/qasp_web_rules.sh +171 -0
  24. package/scripts/capability_effect_probe.sh +104 -0
  25. package/scripts/capability_registry_check.sh +69 -3
  26. package/scripts/relay_channel.sh +151 -6
  27. package/scripts/selfcheck.sh +18 -0
  28. package/scripts/target_freeze.sh +239 -0
  29. package/scripts/test_adapter_lanes.sh +54 -1
  30. package/scripts/test_marker_axes_run_lanes.sh +19 -0
  31. package/scripts/test_marker_standpoint_lanes.sh +83 -0
  32. package/scripts/test_marker_thirdparty_lanes.sh +73 -0
  33. package/scripts/test_regression_guard_ci_lanes.sh +170 -0
  34. package/scripts/test_relay_channel_lanes.sh +146 -17
  35. package/scripts/test_target_freeze_lanes.sh +203 -0
  36. package/templates/.git-hooks/pre-commit +262 -0
  37. package/templates/PRE-PUBLISH-CHECKLIST.md +22 -0
  38. package/templates/regression_guard.sh +32 -4
@@ -157,8 +157,12 @@ engine under the other — and the two candidates for "④" are **context-contin
157
157
  external-grounding**, which currently hold different grades, so the ambiguity is not cosmetic.
158
158
  The collision is worse than one file's internal disagreement: **the identities are numbered ①–⑤ and the
159
159
  engines are not numbered at all**, so a bare ① in any record is undecidable without its sentence. This
160
- file shows it — a paragraph enumerating engines sits four lines above `③⑤ are 🟢, ①②④ are 🔵 RC`,
160
+ file shows it — a paragraph enumerating engines sits four lines above `①③⑤ are 🟢, ②④ are 🔵 RC`,
161
161
  where those numerals mean *identities*.
162
+ (🟥 That example string was itself stale until 2026-08-17: it still read `③⑤ are 🟢, ①②④ are 🔵 RC`,
163
+ which 632 had already superseded — so the illustration pointed at text no longer in the file.
164
+ Same **in-file half-fix propagation** this document names elsewhere; fixed here, and the grade
165
+ values in an *example* must be re-checked whenever the table moves.)
162
166
 
163
167
  > **Legacy decode (do not delete — session records already use numbers).** Records written before
164
168
  > 2026-08-13 say 엔진 ①~④. They decode by the **engine table order above**:
@@ -672,6 +676,169 @@ Each remedy is a run that leaves an artifact, tracked in `tracks/_meta/identity_
672
676
  > An identity whose broad path *runs* is not 🔴 ideal-only. Both are 🟡: running, not yet closed. Lesson:
673
677
  > do not score an identity by its hardest sub-mechanism — that reads a live-but-incomplete path as zero.
674
678
 
679
+ ### ④ promotion criteria — 이 절이 **없었다는 것** 자체가 첫 발견 (2026-08-17)
680
+
681
+ ②(438줄)와 Ⓑ(421줄)는 전용 승급 기준 절을 갖고, ①은 행 안에 인라인으로 갖는다.
682
+ **④만 없었다.** 그리고 이 파일이 ② 절 첫머리에 그 절이 왜 필요한지 이미 적어놨다 —
683
+ *"매 라운드가 바닥부터 기준을 재도출하지 않도록"*. **그 처방이 ④에만 적용되지 않았다.**
684
+
685
+ #### 1. 🟥 유일하게 명시돼 있던 조건은 **다른 명제를 잰다**
686
+
687
+ 지금까지 ④에 대해 조건처럼 읽힌 문장은 하나뿐이었다:
688
+ > *"④ file-change ≠ token-introduction — the instrument is a screener, not an adjudicator"*
689
+
690
+ 그런데 그 계기(`scripts/digest_landing_check.sh`)가 **자기 헤더에서 스코프를 자백한다**:
691
+ > *"착지 대상 = 공개 FH 자산 + `tracks/`. 비공개 companion store 는 **보지 않는다** …
692
+ > 따라서 **이 계기가 재는 것은 «조직 전체 전파»가 아니라 «허브 내부 착지»다**"*
693
+
694
+ 반면 ④가 주장하는 명제는 `fh_three_layer_canon.md` — *"바깥에서 온 것이 **조직 안까지** 착지한다"*.
695
+ ⇒ **그 조건을 100% 닫아도 ④의 명제는 한 글자도 안 재진다.** ①이 dominance 를 두 번 측정하고
696
+ «겨냥이 틀렸다»로 판정한 것과 같은 형태이고, ④는 그 판정을 **한 번도 받은 적이 없다**.
697
+
698
+ #### 2. 🟥 그리고 구조적으로 도달 불가다 — P1 과 같은 형태 (**이 파일 안에서는** 지목된 적 없다)
699
+
700
+ 이 파일의 residency 규율(§Ⓑ promotion criteria 3)이 못 박는다:
701
+ > *"조직 환경 실적은 **증언으로 남고 측정으로 세지 않는다.** residency 때문에 이 파일에서
702
+ > 검증할 수 없고, 검증 못 하는 것을 숫자에 넣으면 그 숫자 전체가 못 쓰게 된다"*
703
+
704
+ 그런데 **④는 대상이 조직인 유일한 행**이다. ⇒ 🟢가 요구하는 «구체적 실적 아티팩트»를
705
+ **이 파일 안에서는 원리적으로 만들 수 없다.** ②의 P1이 «mtime은 위조 가능 → 어떤 런도 만족
706
+ 불가»였던 것과 정확히 같은 구조이며, P1 은 지목되어 수리됐고 **이 파일 안에서는** ④ 에 대한
707
+ 같은 지목이 없다. ⚠️ **범위 제한이다** — 이 파일을 읽어서 내린 판단이고, 전 코퍼스 부재 스캔
708
+ (검색어·컨트롤 동반)은 **안 돌렸다**. 「아무도 지목한 적 없다」로 넓혀 읽지 마라.
709
+
710
+ #### 3. 이미 충족돼 있을 가능성 — 못 읽는 지점이 한 단어로 특정된다
711
+
712
+ 조직 착지는 **운영자 증언으로 존재한다 — 이 세션이 검증한 것이 아니고, §2 대로 이 파일에서는
713
+ 검증 불가다**(전부 residency 경계 안이라 종류만 적는다): 정기 조직 보고 채널로의 상시 착지 ·
714
+ 외부 발표 채택과 그 산출이 다시 FH 기계의 근거로 인용됨(**증언 — 「관통」으로 단정하지 않는다**) ·
715
+ 조직 내 정식 공개 1건.
716
+
717
+ 🟥 **라벨을 반드시 붙인다.** Ⓑ 행이 같은 처리를 한다 — *"실적 열거는 **운영자 증언** + 실측
718
+ 2건이고 … **증언으로 표기하고 측정으로 세지 않는다**"*. 증인이 없는 관통을 「관통」으로 적으면
719
+ 다음 라운드가 이 절을 «④는 사실상 충족» 의 근거로 인용한다. 그게 이 파일이 반복해 자책한
720
+ 인용 훼손 경로다.
721
+
722
+ > **못 읽는 지점 = «as ONE pipeline».** 조건이 「착지했나」가 아니라
723
+ > **「하나의 계기로 관통을 증인할 수 있나」**를 요구하는데, 그 증인 계기가 스스로 조직을
724
+ > 스코프 밖으로 선언했다. 계기 헤더도 절반 자백해뒀다 — *"«안 닫힌다»가 아니라
725
+ > **«닫히는데 증인이 없다»**"*, *"**파이프라인은 그날 실제로 관통했다**"*.
726
+
727
+ #### 4. 제안하는 기준 — 막힌 것은 측정이 아니라 **측정의 위치**다
728
+
729
+ ```
730
+ 지금 조직 데이터를 공개 레포로 가져와 재려 한다 → residency 가 막는다 (정당하게)
731
+ 대안 계기를 데이터가 있는 쪽으로 옮긴다 → 조직 밖으로 나오는 것을 최소화한다
732
+ ```
733
+
734
+ 🟥 **초판이 여기서 두 번 틀렸고, 적대검증이 커밋 전에 잡았다. 그 정정을 남긴다.**
735
+
736
+ **틀린 것 ①** — 초판은 *"**판정만** 넘어오므로 residency 를 안 깬다"* 고 적었다. 그런데 §2 가
737
+ 도달 불가의 **근거로** 인용한 것이 Ⓑ-3 의 *"조직 실적은 … 이 파일에서 **검증할 수 없다**"* 다.
738
+ **조직에서 나온 판정을 이 파일 계열의 공개 기록에 넣는 것**이 곧 그 규칙이 금지한 행위다 —
739
+ 블로커로 인용한 규칙을 처방이 우회했다. 순환이 아니라 **자기무효화**다.
740
+
741
+ **틀린 것 ②** — 초판은 챔버 순서 증인(*"해시만 적는다, 본문 유출 0"*)을 유비로 들었다.
742
+ **성립하지 않는다**:
743
+ ```
744
+ 챔버 증인이 넘기는 것 해시 → 원문 N 개 중 무엇인지에 대해 **0 비트**
745
+ 초판의 P4-1 이 넘기는 것 후보별 PASS/FAIL → 후보 N 개에 대해 **N 비트**
746
+ ```
747
+ 「조직이 이 프런티어 항목을 도입했다/안 했다」는 **그 자체가 조직 정보**다.
748
+ *"원본은 한 바이트도 안 나온다"* 는 **바이트 수를 정보량과 혼동한 문장**이었다. 유비를 철회한다.
749
+
750
+ **그래서 좁힌다.** 조직 밖으로 나오는 것은 **집계된 단일 verdict 하나**뿐이다:
751
+
752
+ **P4-1 (mandatory-pass)** — 조직 환경 클론에서 착지 계기가 실행되고, 공개 쪽으로는
753
+ **집계된 단일 verdict(PASS/FAIL 하나)** 만 기록된다.
754
+ 🟥 **후보별 판정·건수·비율을 공개 쪽에 쓰는 것은 금지한다** — N 비트가 새는 경로다.
755
+ 🟥 그리고 residency 예외 절차를 그대로 탄다(`CLAUDE.md §Field-Harness Diagnostic`):
756
+ **명시적 운영자 승인 + gitignored 감사노트**. *"«이게 충분히 sanitize 됐나»는 세션이 혼자
757
+ 내리는 판단이 아니다"* — 이 조건이 없으면 P4-1 은 미충족이다.
758
+
759
+ **P4-1 의 선행조건 — 지금 둘 다 미충족이고, 그걸 적는 것이 이 절의 값이다**
760
+ 1. 🟥 **잴 대상이 조직 클론에 존재해야 한다.** 계기 인터페이스는
761
+ `digest_landing_check.sh <digest.md> [target ...]` 인데, digest 산출물은
762
+ `tracks/_meta/frontier_digest_<날짜>.md` 이고 **`tracks/**` 는 gitignored**, 생성은
763
+ **이 머신의 launchd 매일 09:00** 이다. ⇒ **조직 클론에는 잴 digest 가 구조적으로 없다.**
764
+ 조직 쪽 생성 경로가 생기거나 공개 digest 를 반입할 수 있어야 한다.
765
+ 2. 🟥 **실행 주체를 이름으로 적어야 한다.** 없으면 P4-1 은 **「미도달」로 표기하고 승급 기준으로
766
+ 세지 않는다** — 이름 없는 실행 주체는 P1 이 «어떤 런도 만족 불가» 였던 것과 같은 형태다.
767
+
768
+ **P4-2 (measured)**: 그 verdict 가 known-pair 를 가른다 — 착지한 건과 착지 안 한 건을 실제로
769
+ 구분. 컨트롤 없는 PASS 는 이 조건을 만족시키지 않는다.
770
+
771
+ #### 5. 🟥 남는 잔여 — 닫는 게 아니라 한 칸 올리는 것이다
772
+
773
+ 그 판정은 **비저자가 볼 수 없는 환경에서 나온 자기신고**다. 챔버 증인이 «저자를 신뢰하지 않는
774
+ 기록»을 요구한 것과 **같은 문제가 조직 경계에서 재발한다**. 그래서 이 기준은 ④를 «완전히
775
+ 닫는다»가 아니라 **«증언 → 측정으로 한 칸 올린다»**이고, 그 한계를 조건에 같이 적는다.
776
+ 비저자 실증이 가능해지는 시점(조직 내 다른 운영자가 그 계기를 돌리는 것)이 다음 칸이다.
777
+
778
+ **강등 조건 — 이 절이 승급만 적고 강등을 안 적으면 Ⓑ 절의 목적을 못 따른다**
779
+ (Ⓑ 절 자기 규정: *"«어떻게 올라가나»가 아니라 «**무엇이 이 행을 내릴 수 있나**»"*):
780
+ - **D4-1**: P4-1 의 verdict 가 **known-pair 를 못 가르면**(착지한 건과 안 한 건이 같은 값을 받으면)
781
+ ④는 **🔵 로 되돌린다.** 판별력 없는 PASS 는 측정이 아니다
782
+ - **D4-2**: 선행조건 1·2 중 하나라도 **다시 미충족이 되면**(조직 쪽 digest 경로가 사라지거나
783
+ 실행 주체가 없어지면) ④는 **「미도달」로 되돌아간다** — 한 번 통과한 것으로 고정되지 않는다
784
+ - **D4-3**: residency 예외 절차(운영자 승인 + 감사노트) 없이 조직 판정이 공개 쪽에 기록되면
785
+ 그 기록은 **무효이고 ④는 강등된다.** 이건 등급 문제가 아니라 규율 위반이다
786
+
787
+ #### 6. 부수 — 이 절을 쓰면서 확인된 stale 2건
788
+
789
+ - 🟥 **207·238줄의 «zero callers» 처방은 stale 이 아니다 — 초판이 그렇게 적었고 틀렸다.**
790
+ `scripts/selfcheck.sh:617` 앵커 루프에 들어간 것은 **`--self-test` 레인뿐**이고, 레포 전체에서
791
+ `digest_landing_check.sh <digest.md>` 를 **인자와 함께 부르는 프로덕션 호출부는 0개**다.
792
+ selfcheck 주석 자신이 그렇게 말한다 — *"lanes existed and ran only when a human typed the
793
+ command"*. ⇒ **「배선됐다」가 다른 배선이었다**: 계기의 self-test 는 도는데 계기 자체는 아무도
794
+ 안 부른다. **그리고 이 0 은 §4 P4-1 의 미충족 선행조건이다** — 공개 레포에서조차 실행 경로가
795
+ 없는 계기를 조직 클론에서 돌린다고 쓸 수 없다. (나머지 절반 «네 분기 앵커링»은 **미확인**.)
796
+ - **160줄**이 옛 등급(`③⑤ are 🟢, ①②④ are 🔵`)을 **현재형 예시**로 인용한다. 632줄이 이미
797
+ 갱신했다. 이 파일이 스스로 이름 붙인 **파일 내부 half-fix 전파 결함**의 재발이다.
798
+
799
+ ### §Gate-consequence 의 일관성 — 자백은 적혀 있고 처분은 안 됐다 (2026-08-17)
800
+
801
+ 607–614줄이 **스스로** 이렇게 적어놨다:
802
+ > *"⑤ 는 🟢 on `intent-routing probe 94%` — **a self-measurement, not a head-to-head** …
803
+ > The inconsistency is real and it is **the gate's, not ②'s** … Resolving that is a **separate
804
+ > change** to the status definitions — flagged here, **not silently settled**."*
805
+
806
+ **그 «separate change» 가 오지 않았다.** 실측하면 **6행 중 4행이 비일관**이다:
807
+ ```
808
+ Ⓑ 일관 적용 만족 — 282줄이 "§Gate consequence 를 **면제 없이 그대로 만족**한다" 고 명시
809
+ ③ 일관 적용 보유 (moat 3–4 family blind · HITL 8/8)
810
+ ─────────────────────────────────────────────────
811
+ ① 비일관 예외를 정식 취득 (92-99줄 — 사전등록 2회 측정 후 미성립. 절차는 밟았다)
812
+ ② 비일관 **미결** — 607-614 자백이 "②를 면제할지 ⑤를 재측정할지" 를 안 정했다
813
+ ⑤ 비일관 미보유인 채 🟢 ← 자백의 대상
814
+ ④ 비일관 언급조차 없음
815
+ ```
816
+ 🟥 **초판이 여기서 «6행 중 일관 적용된 행이 없다» 고 적었는데, 바로 아래 자기 표가 그걸
817
+ 반증했다**(Ⓑ 만족 · ③ 보유). 적대검증이 잡았고 정정한다. 그리고 초판은 ②를 **「면제」로 확정
818
+ 표기**했는데, 자백 원문은 **미결**이다 — 절의 결론(«아무것도 정하지 않는다»)과 표가 어긋나 있었다.
819
+ 🟥 **이건 오늘 이 저장소가 반복해 만난 «미측정을 0으로 접었다»와 다른 형태다** — 접힘이 아니라
820
+ **적용 누락**이다. 그리고 자백이 문서에 적혀 있는데 안 고쳐진 것은 「몰랐다」가 아니라
821
+ **「알고 미뤘다」**라서, 다음 라운드가 그 자백을 「이미 인지된 사항」으로 읽고 또 넘길 위험이 크다.
822
+
823
+ **처분(운영자 결정 필요, 여기서 확정하지 않음)** — 둘 중 하나여야 하고 셋째는 없다:
824
+ - **(A) §Gate-consequence 가 모든 🟢 를 구속한다** ⇒ ⑤ 는 과대 채점이고 재측정 대상이다
825
+ - **(B) advisory 다** ⇒ ②·④ 를 거기에 묶어서는 안 되고, ① 이 받은 «예외»는 애초에 필요 없었다
826
+
827
+ ⚠️ 이 절은 **자백을 결정으로 바꾸지 않는다.** 지금 하는 것은 「미뤄졌다는 사실 자체를 기록해서
828
+ 다음 라운드가 그것을 인지된 사항으로 소비하지 못하게」 하는 것뿐이다.
829
+
830
+ 🟥 **그런데 그것만으로는 이 절이 자기가 진단한 실패를 반복한다 — 적대검증 지적, 수용한다.**
831
+ 607-614줄의 자백도 **이 파일의 산문**이었고, 그 매체로 **이미 한 번 실패했다**(그래서 이 절이
832
+ 있다). 같은 매체에 한 번 더 적으면서 앵커·기한·소유자를 안 붙이면 결과가 같다.
833
+ `CLAUDE.md §Mechanization Boundary` 가 **채널(«기록이 있는가»)은 기계화 대상**이라고 정한다.
834
+
835
+ ⇒ **파일 밖 앵커를 건다**: 세션 카드(`tracks/_meta/reference_next_session_starter.md`)의
836
+ 🧵 블록에 **«⑤ dominance 일관성 — 운영자 결정 대기 (제기 2026-08-17)»** 를 등재한다.
837
+ 카드는 매 세션 시작에 읽히고 이 파일은 안 읽힌다 — 그게 차이다.
838
+ 🟥 **이 등재가 없으면 이 절은 자백의 재생산일 뿐이다.**
839
+
840
+ ---
841
+
675
842
  ## For a field harness (e.g. pmh, qasp)
676
843
  Same gate, its own identities. A field harness ships to its team when its identity checklist is all-green,
677
844
  certified by a **실증상세 (demonstration-detail) doc in that harness's own repo** — the QA certificate
@@ -2182,3 +2182,27 @@
2182
2182
  outcome: accepted
2183
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
2184
  cost: 135k tokens
2185
+ - date: 2026-08-18
2186
+ agent: Explore ×4 (isolated) — AX-레디 착수 전 정찰(FH 대상동결 선행자산 · qasp 해석기 어휘 · D2 스캐너 배선 · D3ⓑ 용어 채널 · B5 상태 어휘 · 호출부 0 좌표)
2187
+ purpose: "재발명 방지 + 좌표 확정. 착수 전 «이미 있나» 를 기계로 먼저 묻는다(오늘 이 레포에서 재발명이 한 번 났었다)"
2188
+ outcome: partial
2189
+ evidence: "6축 정찰을 4발주로 묶음(각 대상 지문 `target_freeze.sh verify` 로 시작). **정확했던 것**: 대상동결 선행자산 스캔이 «기계 없음 + 실패 2종 문서화» 를 짚어 3번째 시도의 설계를 결정 · D2 가 `bug_report.py` 호출부 0을 컨트롤 동반으로 확정 · B5 가 `rer_from_act2` 를 «어휘 매핑 어댑터 레퍼런스» 로 지목(재발명 회피) · 근거표 A3 의 팬텀 인용 `PEER_ABSENT` 발견. 🟥 **틀린 것 2건, 둘 다 내가 직접 재현해서 걸렀다**: ⓐ 「`_CAUSE_PLAIN` 커버리지 게이트가 FAIL 레인 4종을 놓친다」 → 그 4종은 `StepEvidence.triage` 가 아니라 이슈-후보 dict 로 가고 소스 주석이 이미 그렇게 갈라놨다(오탐) ⓑ 「`env_cols_source` 가 존재하지 않는다」 → 7히트 실재(주석이 말한 건 *다른 모듈*). ⇒ 정찰 산출은 소스로 닫히기 전엔 좌표가 아니다"
2190
+ cost: ~309k tokens (4건 합)
2191
+ - date: 2026-08-18
2192
+ agent: general-purpose (isolated) — qasp 호출부 0/불일치 스캐너 제작
2193
+ purpose: "«완성된 층 + 호출자 없음» 이 하루 4회 났다 — stdlib ast 기반 계기를 known-pair 로 세우고 전수 1회"
2194
+ outcome: accepted
2195
+ evidence: "**known-pair 양성 2/2 · 음성 5/5**(음성은 호출자 *경로까지* 대조). 전수 1024심볼 · ⓐ162(엄격 32)·ⓑ207·UNDECIDABLE 73. 🟥 **자기 오탐 2계열을 스스로 잡아 고쳤다**: `same_module_refs` 가 import 링크를 요구해 구조적으로 항상 0(→엄격 ⓐ 162→32) · `DeviceAdapter` 덕타이핑 미검출. 진양성 손검증 1건(`RuleAutoExpansionGate` — 외부 참조가 주석 2줄, 그 이름은 **존재하지 않는 철자**). ★ **`target_freeze.sh` 의 첫 실사용이 여기서 났다** — 회수 시 `WRONG-TARGET`(발주 중 내가 커밋 3건 투입), 위임분이 stale 판정을 안 들고 오고 현재 트리에서 전량 재실행. 미해결 자백: 손검증 1건뿐 · 미검출 미측정 · CI 미배선"
2196
+ cost: 167k tokens
2197
+ - date: 2026-08-18
2198
+ agent: codex/gpt-5.5 (cross-family sidecar) — FH `target_freeze.sh` 게이트 적대검증
2199
+ purpose: "감사 대상 동결 게이트의 fail-open / 오탐 / 셸 이식성 — 재현 명령 필수"
2200
+ outcome: accepted
2201
+ evidence: "**6건, 전부 재현 명령 동반, 내가 직접 재현해 6/6 확인.** S급 3건이 **fail-open**(`_sha` 가 `|awk` 로 rc 소실 · `ls-files -o` 실패가 파이프에 먹힘 · 라벨 문자치환 충돌로 **핀한 적 없는 라벨이 MATCH**) + A급 2(서브모듈·심볼릭링크 미검출) + 과탐 1(빈 커밋). 🟥 **레인 11/11 초록인 상태에서 전부 뚫렸다** — 컨트롤 있음 ≠ 판별력 있음. 전부 수리 + 레인 고정"
2202
+ cost: ~40k tokens
2203
+ - date: 2026-08-18
2204
+ agent: agy/gemini (cross-family sidecar, 2번째 계열) — 같은 게이트, **다른 축** 요구
2205
+ purpose: "codex 지적 목록을 먼저 주고 «그 밖의 축»(동시성·env·거짓 안심·인코딩)을 요구 — 한 계열에 몰면 같은 사각이 남는다"
2206
+ outcome: accepted
2207
+ evidence: "**6건 · codex 와 겹친 지적 0.** 최상위는 `GIT_DIR`/`GIT_WORK_TREE` 가 `git -C` 를 이긴다 — **엉뚱한 레포를 재고도 «동결했다» 고 보고**한다(계기가 대상을 틀리는 형태). 그 외 untracked **실행 비트** 미검출(이 레포가 이미 데인 축) · TOCTOU «유령 지문» · 미초기화 서브모듈은 `foreach` 가 건너뜀 · `---` 구분자 경계 · `foreach` 의 `;` rc 소실. 전부 수리, 레인 11→28. ⇒ **계열 분리가 값을 냈다** — 이 6건은 codex 를 한 번 더 돌려서는 안 나온다"
2208
+ cost: ~35k tokens
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chrono-meta/fh-gate",
3
- "version": "2.2.0",
3
+ "version": "2.4.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": [
@@ -84,6 +84,9 @@
84
84
  "scripts/adapters/gstack_content_safety.sh",
85
85
  "scripts/adapters/mate_agent_boundary.sh",
86
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",
87
90
  "scripts/test_adapter_lanes.sh",
88
91
  "scripts/test_fh_gate_regressions.sh",
89
92
  "templates/local_fh_context.md",
@@ -111,6 +114,8 @@
111
114
  "scripts/tier_census_grep.sh",
112
115
  "scripts/test_marker_floor_lanes.sh",
113
116
  "scripts/test_marker_crossfamily_lanes.sh",
117
+ "scripts/test_marker_standpoint_lanes.sh",
118
+ "scripts/test_marker_thirdparty_lanes.sh",
114
119
  "scripts/test_marker_axes_run_lanes.sh",
115
120
  "scripts/test_env_purity_lanes.sh",
116
121
  "scripts/.env_purity_tokens.defaults",
@@ -127,6 +132,8 @@
127
132
  "scripts/test_relay_channel_lanes.sh",
128
133
  "scripts/fh_session_load.sh",
129
134
  "scripts/branch_claim.sh",
135
+ "scripts/target_freeze.sh",
136
+ "scripts/test_target_freeze_lanes.sh",
130
137
  "scripts/test_branch_claim_lanes.sh",
131
138
  "scripts/hook_source_lib.sh",
132
139
  "scripts/test_hook_source_gate_lanes.sh",
@@ -135,6 +142,7 @@
135
142
  "templates/degrade_direction_scan.sh",
136
143
  "templates/.git-hooks",
137
144
  "templates/regression_guard.sh",
145
+ "scripts/test_regression_guard_ci_lanes.sh",
138
146
  "templates/predelete_check.sh",
139
147
  "templates/PRE-PUBLISH-CHECKLIST.md",
140
148
  "scripts/degrade_direction_scan.sh",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fh-commons",
3
- "version": "2.2.0",
3
+ "version": "2.4.0",
4
4
  "engines": {
5
5
  "claudeCode": ">=1.0.0"
6
6
  },
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fh-meta",
3
- "version": "2.2.0",
3
+ "version": "2.4.0",
4
4
  "engines": {
5
5
  "claudeCode": ">=1.0.0"
6
6
  },
@@ -10,6 +10,116 @@ Format: [Keep a Changelog](https://keepachangelog.com/en/1.1.0/)
10
10
 
11
11
  ## Plugin Level
12
12
 
13
+ ### [2.4.0] — 2026-08-18
14
+
15
+ 🟥 **BREAKING (gate) ①**: the Axes 2–3 marker's **`standpoint:` and `thirdparty:` fields are now
16
+ validated by value**, not merely by presence (`validate_standpoint_leg` / `validate_thirdparty_leg`
17
+ in `templates/.git-hooks/pre-commit`). A marker carrying free prose where the closed enum is
18
+ required, a degrade value with **no substantive grounds on the same line**, or **more than one**
19
+ line of either field, now **blocks the commit**.
20
+ - **Remedy**: use the enum. `standpoint: tier1b(<harness>) — <what you read>` ·
21
+ `standpoint: not-applicable — <what you checked to conclude no target exists>` ·
22
+ `thirdparty: checked(<what prior art you searched>)`. The hook prints the accepted forms.
23
+ - **Why**: presence-only checking catches silence but not a **confident wrong answer** — measured on
24
+ the marker corpus, 4 markers used the enum correctly while **2 wrote free prose about peer-session
25
+ contact** and matched no enum member at all (the field-canon failure this repo already names:
26
+ normalizing a harness term into a general concept).
27
+
28
+ 🟥 **BREAKING (gate) ②**: **Axis 1 (`regression-guard.yml`) had never actually run** — an
29
+ instrument error was rendering **green**. It now runs, so a repo whose Axis 1 check was
30
+ "passing" may go **red on its first real execution**.
31
+ - **Remedy**: read the actual guard output; the failures it now reports were always there.
32
+ - **Why**: the sample was 5/5 instrument-errors-as-green. A check that cannot fail is not a check.
33
+
34
+ 🟥 **BREAKING (gate) ③**: `scripts/selfcheck.sh` now executes **`test_target_freeze_lanes.sh`**
35
+ (28 lanes) as part of the standard run. A consumer whose `selfcheck` was green may see new output,
36
+ and a genuinely broken environment (no `shasum`/`sha256sum`) now surfaces there instead of silently.
37
+ - **Remedy**: the lanes are self-contained (`mktemp` fixtures, no network). A missing hash tool is a
38
+ real finding, not a lane defect — the gate it anchors fails closed without one.
39
+ - **Why**: `lane-runner` flagged the suite as having **no runner** — *"a suite nothing executes is
40
+ prose."* The author of the lanes had not wired them.
41
+
42
+ **Added**
43
+ - `scripts/target_freeze.sh` + `scripts/test_target_freeze_lanes.sh` — **audit target freeze**.
44
+ Pin a target repo's content-addressed fingerprint before dispatching an audit; verify on return;
45
+ `WRONG-TARGET` (rc 1) invalidates that round's verdict wholesale. Third mechanization attempt,
46
+ deliberately routed **around** the two failures recorded in `steel-quench/SKILL.md` (a hook
47
+ advisory that fired on 100% of markers; a `base-SHA + diff-line-count` fingerprint invariant under
48
+ in-place edits and blind to untracked files).
49
+ 🟥 **Cross-family review found 3 fail-open paths while its lanes were 11/11 green** — `_sha`
50
+ losing `shasum`'s rc through `| awk`, `git ls-files -o` failure swallowed by a pipeline, and a
51
+ label-sanitizing collision that returned `MATCH` for a label **never pinned**. All reproduced by
52
+ hand, all fixed, lanes 11 → 28.
53
+ - `④ promotion-criteria` section in the identity gate (its absence was itself the first finding).
54
+ - Incubator routing: `net-new` failure now routes to **`CURATED`** (hand the maker the prior-art
55
+ list and the delta it does not cover) instead of `KILL`; judgment-shaped candidates route to
56
+ `NOT-APPLICABLE`. KILL survives for measured precision-shortfall, hub-state dependence, and
57
+ inability-to-run.
58
+
59
+ **Fixed**
60
+ - `relay_channel` registration-time checker had a **0-line call site** — the gate existed as 42KB
61
+ of code that nothing invoked.
62
+ - `standpoint: §7` closed its `not-applicable` definition (`Q0` target-class resolution, which can
63
+ return **more than one** target, each owing its own tier).
64
+ - `mate-agent-boundary` adapter: its **calibration pair was dead** — the peer entry point hardcodes
65
+ its target and ignores `$1`, so the positive and negative arms were measuring **the same file**.
66
+ The negative arm's green was not discrimination; the live target simply happened to be failing.
67
+ - `package.json files[]` omitted two scripts that shipped documentation instructs consumers to run
68
+ (a phantom pointer inside a gate instruction).
69
+
70
+ ### [2.3.0] — 2026-08-17
71
+
72
+ 🟥 **BREAKING (gate) ①**: the capability effect-probe (`M6`) now watches **directory existence and
73
+ non-regular nodes**, not files only. A `.cap` declaring `writes: read-only` whose entry point
74
+ creates an **empty directory**, a **symlink**, or a **fifo/socket/device node** now returns
75
+ `VIOLATION` where it previously returned `✅ VERIFIED`. Registration of such a capability
76
+ **blocks**.
77
+ - **Remedy**: declare the real value (`write-local` / `write-remote`), or stop creating the node.
78
+ The probe prints which surface changed.
79
+ - **Why**: `_snapshot()` used `find -type f`, so **empty directories were invisible** — git does not
80
+ track them, so they are absent from the isolated-clone sandbox too. Measured with a 3-arm
81
+ known-pair: write-file-to-new-path → 1, write-file-to-existing-dir → 1, **empty-dirs-only → 0**
82
+ (the false green). A cross-family review then found the same class still open one layer down:
83
+ `ln -sf /etc/passwd link` and `mkfifo pipe` **also passed** after the directory axis was added.
84
+ Both are now caught; symlinks are compared **by target**, since existence alone makes
85
+ `ln -sf other link` look like no change.
86
+ - ⚠️ **Named residual, unchanged**: metadata-only writes (`chmod`, xattr, hardlink topology) and
87
+ empty directories **under `.git`** remain invisible. And the probe **still cannot discriminate an
88
+ adapter-class capability** — its outside-watch is a temp canary plus `$HOME`, while an adapter by
89
+ definition runs in the peer tree. That value stands on hand measurement, not on the probe.
90
+
91
+ 🟥 **BREAKING (gate) ②**: writing `ⓓ=→thirdparty` in the Axes 2–3 marker now **requires a non-empty
92
+ `thirdparty:` line**. A marker with the pointer but no target **blocks the commit**.
93
+ - **Remedy**: the hook prints the accepted forms —
94
+ `checked(<what was surveyed>)` · `none-found(<what was searched>)` · `UNKNOWN` · `not-applicable`.
95
+ - **Why**: a pointer at nothing is not a record. Same failure the `ⓑ=→standpoint` guard closed.
96
+
97
+ **Added**
98
+ - `adapter/qasp-web-rules` — a cluster node wrapping a peer harness's web selector-stability rule
99
+ set. Enum `0=CLEAN 1=FINDINGS 2=ARGS 3=ENGINE_ERROR 10=HARNESS_ERROR 20=PEER_ABSENT`, calibration
100
+ pair from **FH-owned fixtures** (offline, deterministic), `writes: write-local` established by
101
+ cold-clone measurement. Registration bar M1–M6: **REGISTRABLE**.
102
+ 🟥 **Consumer 0** — declared and callable, nothing calls it yet. Do not round that to "wired".
103
+ 🟥 The declared enum is **wider than the peer's documented contract** (`0/1/3`): the underlying
104
+ CLI uses `parser.error()`, argparse exits **2**, and the shell wrapper propagates it verbatim.
105
+ The adapter declares the **real** contract, or an argument mistake renders as "the instrument broke".
106
+
107
+ **Fixed**
108
+ - The adapter's argument validation now runs **before** peer resolution. Previously the **same bad
109
+ call returned different values on different machines** — `ARGS(2)` where the peer was installed,
110
+ `HARNESS_ERROR(10)` where it was not — i.e. the check measured **what is installed on this box**
111
+ rather than **how it was called**. `ARGS` is a statement about the call; `PEER_ABSENT` is a
112
+ statement about the cluster; the two are independent facts.
113
+ - Capability entry points and their calibration fixtures are now in `files[]`. Consumers previously
114
+ received the **declaration without the instrument** — a `.cap` that passes the registration bar
115
+ and then returns `HARNESS_ERROR` inside the tarball.
116
+
117
+ **Note for consumers of the effect-probe**
118
+ If you hold `.cap` files, re-run the probe **before** upgrading: this release changes verdicts for
119
+ the three shapes above. A green result under 2.2.0 is not evidence under 2.3.0.
120
+
121
+ ---
122
+
13
123
  ### [2.2.0] — 2026-08-17
14
124
 
15
125
  🟥 **BREAKING (gate)**: chamber step 6 now reads `ACTUAL.md`, **not `BUDGET.md`**. An in-flight
@@ -202,6 +202,26 @@ re-checks** it against the artifact (phantom-quench back-trace) before accepting
202
202
  source-grounded is **dropped, not judged**. No sidecar-only verdict (no weak-local-judge regression).
203
203
  Local 4090 = **canary tier** (evidence-of, never terminal verdict).
204
204
 
205
+ **Target freeze — a prior drop reason, checked before any of the above (2026-08-17).** Grounding a
206
+ finding against *today's* tree proves nothing if the sidecar reviewed *yesterday's*. Pin before
207
+ dispatch and verify on return:
208
+
209
+ ```bash
210
+ bash scripts/target_freeze.sh pin "$REPO" "$ROUND_LABEL" # 발주 직전
211
+ bash scripts/target_freeze.sh verify "$REPO" "$ROUND_LABEL" # 회수 직후
212
+ ```
213
+
214
+ `rc 1` = **WRONG-TARGET → drop the whole round's findings**, not individual ones — the round read a
215
+ tree that moved under it, so which findings survive is not decidable from the output. `rc 10` =
216
+ UNKNOWN (could not measure) — that is **not** a pass; re-pin and re-dispatch. Measured trigger: three
217
+ occurrences in one day, one of which was sending a sidecar a **pre-repair diff** and then grounding
218
+ its findings against the repaired tree — every finding "failed to ground", and the instrument, not
219
+ the reviewer, was wrong.
220
+
221
+ 🟥 **What this does NOT cover**: §L191's inlined-file pattern. When the prompt carries file contents
222
+ on stdin (the robust form for sidecars that have no file tools), the tree freeze says nothing about
223
+ **what bytes you actually sent**. That binding is still owed and is not claimed here.
224
+
205
225
  ## Step 6 — Degrade ladder (the intelligent scale-down)
206
226
 
207
227
  **Consent branch first — declined ≠ degraded** (`[[capability_escalation_consent]]`): if the UAP has
@@ -425,7 +425,8 @@ over **unchanged** code:
425
425
  `WRONG-TARGET` and stop on mismatch:
426
426
 
427
427
  ```bash
428
- git rev-parse --short HEAD; git diff HEAD | wc -l; git status --porcelain | grep -c '^??'
428
+ bash scripts/target_freeze.sh pin "$REPO" "$WAVE_LABEL" # 발주 직전 토큰을 프롬프트에 박는다
429
+ bash scripts/target_freeze.sh verify "$REPO" "$WAVE_LABEL" # 회수 직후 — rc 1 이면 WRONG-TARGET
429
430
  ```
430
431
 
431
432
  An audit that did not pin its target **cannot be counted as a terminating round** — it may have
@@ -435,7 +436,29 @@ over **unchanged** code:
435
436
  active — gate-locality on the very fix that was closing a gate-locality gap, caught by the round
436
437
  that audited it.
437
438
 
438
- > **Named residual — the freeze claim is currently SELF-ATTESTED, and deliberately not
439
+ > **MECHANIZED 2026-08-17 — the residual below is the history, not the current state.**
440
+ > The recurrence condition this paragraph set ("mechanize on the first recurrence") fired **three
441
+ > times in one day**: a cross-family review was sent a **pre-repair** diff · a review's output was
442
+ > truncated with `tail`, losing findings and the pin itself · a scan was followed by an edit that
443
+ > shifted every line number. All three were "did I send / read the tree that is there now?", and
444
+ > the prose rule above was already in place and caught **none** of them (operator-approved as the
445
+ > next session's 1순위, 2026-08-17).
446
+ >
447
+ > `scripts/target_freeze.sh` (lanes: `scripts/test_target_freeze_lanes.sh`, **28/28**) takes the
448
+ > third attempt deliberately **through** the two failures recorded below: it content-addresses
449
+ > `git diff HEAD` (the **patch**, not its line count — so a same-line-count in-place edit is
450
+ > caught, lane A) and hashes untracked files separately (lane B), and it is **not a hook** —
451
+ > two explicit commands the dispatcher runs, so it cannot become the 100%-firing background noise
452
+ > of attempt ①. It refuses to answer at all when `assume-unchanged`/`skip-worktree` is set
453
+ > (exit 10 UNKNOWN, never MATCH), and a **restore-to-original lane returns MATCH** — that arm is
454
+ > the point: without it, "always reports WRONG" would pass every other lane.
455
+ > 🟥 **Scope, stated narrowly**: it freezes the *target tree*, **not the bytes you inlined into a
456
+ > sidecar prompt**. Where files are pasted into the prompt (`auto-decorrelation` §L191 — sidecars
457
+ > often cannot read a tree at all), the inlined content needs its own binding; this script does
458
+ > not provide it.
459
+ >
460
+ > **Named residual (HISTORICAL — superseded by the block above) — the freeze claim was
461
+ > SELF-ATTESTED, and deliberately not
439
462
  > mechanized.** Criterion 1 turns on "unmodified since the previous audit", and nothing checks
440
463
  > that. Two attempts to mechanize it on the day the rule was written were both wrong: a hook
441
464
  > advisory that fired on 100% of markers (noise, and its convergence detector could not match the
@@ -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
+ ]
@@ -100,14 +100,34 @@ if [ -n "$TARGET" ]; then
100
100
  [ -r "$TARGET" ] || _harness_error "대상 경로를 읽을 수 없다: $TARGET"
101
101
  fi
102
102
 
103
- # peer 트리를 **변이하지 않는다** — cwd 만 peer 로 두고 읽기만 한다.
104
- # (기본 대상이 peer 레포 상대경로라 cwd 가 peer 여야 한다.)
103
+ # peer 트리를 **변이하지 않는다** — cwd 만 두고 읽기만 한다.
105
104
  # 판정은 파이프를 통과시키지 않는다(PIPE-VERDICT).
106
- if [ -n "$TARGET" ]; then
107
- out=$(cd "$PEER_ROOT" && bash "$ENTRY" "$TARGET" 2>&1); rc=$?
108
- else
109
- out=$(cd "$PEER_ROOT" && bash "$ENTRY" 2>&1); rc=$?
110
- fi
105
+ #
106
+ # 🟥 **초판은 대상을 인자로 넘겼고, peer 는 그 인자를 안 읽는다** (2026-08-18 실측).
107
+ # peer 진입점은 `TARGET=".claude/agents/mate-failure-patcher.md"` 를 **하드코딩**하고
108
+ # `$1` 은 `pass()`/`fail()` 헬퍼 안에서만 쓴다. 그래서 `--known-positive` / `--known-negative`
109
+ # 두 arm 이 **같은 파일**(peer 실물 명세서)을 재고 있었다 ⇒ **캘리브레이션 쌍이 죽어 있었다.**
110
+ # 음성 arm 이 초록이던 것은 판별력이 아니라 **실물이 마침 실패 상태**여서다.
111
+ # 이 픽스처 파일의 헤더가 스스로 경고한 그 상태다 — *"실물을 양성 arm 으로 쓰면 계기의
112
+ # 판정이 남의 편집에 따라 조용히 뒤집힌다"*.
113
+ # ⇒ 대상이 명시되면 **peer 가 기대하는 모양의 임시 트리**를 만들어 거기서 돌린다.
114
+ # peer 코드를 고치지 않고(남의 레포다) peer 의 하드코딩된 상대경로를 만족시키는 방법이다.
115
+ _run_peer() {
116
+ if [ -z "$TARGET" ]; then
117
+ ( cd "$PEER_ROOT" && bash "$ENTRY" ) 2>&1
118
+ return $?
119
+ fi
120
+ local stage
121
+ stage=$(mktemp -d) || return 10
122
+ mkdir -p "$stage/.claude/agents" || { rm -rf "$stage"; return 10; }
123
+ cp "$TARGET" "$stage/.claude/agents/mate-failure-patcher.md" || { rm -rf "$stage"; return 10; }
124
+ local o r
125
+ o=$( cd "$stage" && bash "$ENTRY" 2>&1 ); r=$?
126
+ rm -rf "$stage"
127
+ printf '%s\n' "$o"
128
+ return $r
129
+ }
130
+ out=$(_run_peer); rc=$?
111
131
  printf '%s\n' "$out"
112
132
 
113
133
  # ── 명시 재매핑 (같은 숫자, 다른 사건 — 전파가 아니다) ───────────────────────