@chrono-meta/fh-gate 2.4.0 → 2.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +2 -2
- package/AGENTS.md +11 -0
- package/CLAUDE.md +56 -1
- package/knowledge/shared/harness-core/fh_three_layer_canon.md +18 -6
- package/knowledge/shared/harness-core/field_verdict_crossfamily_gate.md +31 -0
- package/knowledge/shared/harness-core/sonnet_floor_doctrine.md +16 -0
- package/knowledge/shared/learnings/subagent_invocations_log.yaml +126 -0
- package/knowledge/shared/rules/multi_session_close_protocol.md +124 -2
- package/package.json +3 -1
- package/plugins/fh-commons/.claude-plugin/plugin.json +1 -1
- package/plugins/fh-meta/.claude-plugin/plugin.json +1 -1
- package/plugins/fh-meta/CHANGELOG.md +60 -0
- package/scripts/capability_effect_probe.sh +173 -5
- package/scripts/digest_landing_check.sh +168 -13
- package/scripts/fh_session_load.sh +71 -0
- package/scripts/selfcheck.sh +29 -0
- package/scripts/test_evidence_root_psa_lanes.sh +78 -0
- package/scripts/test_satellite_publish_gate_lanes.sh +339 -0
- package/templates/.git-hooks/pre-commit +64 -6
|
@@ -18,6 +18,42 @@
|
|
|
18
18
|
set -uo pipefail
|
|
19
19
|
|
|
20
20
|
REPO_ROOT=$(git rev-parse --show-toplevel)
|
|
21
|
+
# ── EVIDENCE_ROOT — 증거(tracks/)가 사는 곳. **소스 경로와 다르다.** ──────────────
|
|
22
|
+
# 🟥 WHY: 워크트리에서 `--show-toplevel` 은 **그 워크트리**를 준다. `tracks/` 는 gitignored 라
|
|
23
|
+
# 워크트리에 안 따라오므로, 마커와 매니페스트를 거기서 찾으면 **항상 부재**다. 그래서 게이트가
|
|
24
|
+
# 스스로 권하는 «세션당 worktree» 를 게이트 자신이 봉쇄하고 있었다(2026-08-18 실측: 워크트리
|
|
25
|
+
# FH-자산 커밋 → Axis 2+3 「NOT CONFIRMED」 + Axis 4 「edit_manifest.yaml not found」).
|
|
26
|
+
# 「구조적으로 불가능」이 아니라 **경로 해석 한 줄**이었다.
|
|
27
|
+
# `--git-common-dir` 은 워크트리에서도 **메인 트리의 .git** 을 준다. 메인 트리에서는 `.git` 이라
|
|
28
|
+
# 같은 값이 나온다 ⇒ **하위호환**(3트리 대조 실측: 메인/워크트리/FH 전부 기대대로).
|
|
29
|
+
# ⚠️ 상대 경로로 나올 수 있으므로 절대화한다. 실패하면 REPO_ROOT 로 **degrade** — 증거를 못 찾는
|
|
30
|
+
# 쪽이지 통과시키는 쪽이 아니다(fail-closed 방향 유지).
|
|
31
|
+
#
|
|
32
|
+
# 🟥 **명시된 잔여 — 이 수리가 «두 번째 위험»을 처음 도달 가능하게 만든다** (2026-08-18,
|
|
33
|
+
# 타계열 리뷰 지적, 실측으로 확인):
|
|
34
|
+
# ⓐ 증거 부재 = 이 블록이 닫는 것
|
|
35
|
+
# ⓑ 훅 자기무력화 = **안 닫힌다.** `core.hooksPath` 가 **상대 경로**(`templates/.git-hooks`
|
|
36
|
+
# — FH 문서가 지시하는 바로 그 형태)면 워크트리는 **자기 사본**의 훅을 돌린다.
|
|
37
|
+
# 그 사본을 워크트리 안에서 고치면 그 워크트리의 게이트가 죽고,
|
|
38
|
+
# 메인 트리의 `git status` 에는 안 보인다.
|
|
39
|
+
# 종전엔 ⓐ 가 **사실상의 장벽**이라 ⓑ 가 도달 불가였다. ⓐ 를 없애면 ⓑ 가 **처음으로 실재**한다.
|
|
40
|
+
# ⇒ 워크트리를 쓸 거면 `core.hooksPath` 를 **절대 경로**로 설정해라:
|
|
41
|
+
# git config core.hooksPath "$(git rev-parse --show-toplevel)/templates/.git-hooks"
|
|
42
|
+
# 그러면 모든 워크트리가 **메인 트리의 훅 한 벌**을 돈다.
|
|
43
|
+
# 🟥 정정 (2026-08-18) — 초판은 여기에 «그러면 자기무력화가 **구조적으로 막힌다**» 라고
|
|
44
|
+
# 적었고 그건 **과대주장**이다. 절대 경로가 막는 것은 **훅 파일 자체의 교체**뿐이다:
|
|
45
|
+
# 이 훅은 `$REPO_ROOT/scripts/*` 를 10곳 넘게 **source** 하므로(psa_scan_lib ·
|
|
46
|
+
# branch_claim · regression_guard …), 워크트리에서 그 헬퍼 하나를 고치면 게이트는
|
|
47
|
+
# 그대로 초록으로 통과한다. 「막힌다」가 아니라 **「한 겹 좁아진다」**가 정확하다.
|
|
48
|
+
# 🟥 이 훅은 그것을 **강제하지 않는다** — 자기 자신의 설치 형태를 자기가 검사하는 것은
|
|
49
|
+
# 이미 무력화된 뒤엔 안 돌기 때문이다(검사기가 검사 대상). 문서·설치 안내가 그 층이다.
|
|
50
|
+
_gcd=$(git rev-parse --git-common-dir 2>/dev/null || echo "")
|
|
51
|
+
case "$_gcd" in
|
|
52
|
+
"") EVIDENCE_ROOT="$REPO_ROOT" ;;
|
|
53
|
+
/*) EVIDENCE_ROOT=$(dirname "$_gcd") ;;
|
|
54
|
+
*) EVIDENCE_ROOT=$(dirname "$REPO_ROOT/$_gcd") ;;
|
|
55
|
+
esac
|
|
56
|
+
[ -d "$EVIDENCE_ROOT" ] || EVIDENCE_ROOT="$REPO_ROOT"
|
|
21
57
|
BRANCH=$(git rev-parse --abbrev-ref HEAD)
|
|
22
58
|
TODAY=$(date +%Y-%m-%d)
|
|
23
59
|
BRANCH_SLUG="${BRANCH//\//_}"
|
|
@@ -281,8 +317,17 @@ if [ ! -r "$PSA_LIB" ]; then
|
|
|
281
317
|
FAILED=1
|
|
282
318
|
else
|
|
283
319
|
. "$PSA_LIB"
|
|
320
|
+
# ── R4 (2026-08-18) · 오버라이드는 **EVIDENCE_ROOT** 에서 찾는다 ──────────────
|
|
321
|
+
# `tracks/` 는 `--git-common-dir` 로 옮겼는데 **똑같이 gitignored 인** 이 패턴 파일은
|
|
322
|
+
# `$REPO_ROOT` 에 남아 있었다. 그래서 워크트리 커밋이 매번 **defaults-only** 로 기밀성
|
|
323
|
+
# 스캔을 돌았다(회사명·실명 클래스가 통째로 UNSCANNED, 게다가 비차단 경고라 조용하다).
|
|
324
|
+
# 반쪽-픽스의 전형이다([[feedback_half_fix_propagation_boundary]]).
|
|
325
|
+
# 🟥 `defaults` 는 **tracked** 라 `REPO_ROOT` 가 맞다 — 같이 옮기면 안 된다. 옮기는 것은
|
|
326
|
+
# gitignored 인 오버라이드 쪽 하나뿐이고, 없으면 REPO_ROOT 로 폴백한다.
|
|
327
|
+
PSA_OVR="$EVIDENCE_ROOT/.claude/rules/.public-surface-patterns"
|
|
328
|
+
[ -r "$PSA_OVR" ] || PSA_OVR="$REPO_ROOT/.claude/rules/.public-surface-patterns"
|
|
284
329
|
psa_load "$REPO_ROOT/.claude/rules/.public-surface-patterns.defaults" \
|
|
285
|
-
"${PSA_PATTERNS:-$
|
|
330
|
+
"${PSA_PATTERNS:-$PSA_OVR}"
|
|
286
331
|
if [ "$PSA_OVERRIDE_PRESENT" -eq 0 ]; then
|
|
287
332
|
# 2026-08-16 weekly-audit residual — operator decision: LOUDER WARNING, not fail-closed.
|
|
288
333
|
# A hard block here would fail every fresh clone's / CI's / worktree's first commit (the
|
|
@@ -305,7 +350,7 @@ else
|
|
|
305
350
|
if [ "${PUBLIC_SURFACE_OK:-0}" = "1" ]; then
|
|
306
351
|
echo " ⚠️ proceeding with an incomplete gate by PUBLIC_SURFACE_OK=1 (conscious)"
|
|
307
352
|
echo "$(date +%Y-%m-%dT%H:%M:%S) PUBLIC_SURFACE_OK override — branch $BRANCH — gate inactive/incomplete" \
|
|
308
|
-
>> "$
|
|
353
|
+
>> "$EVIDENCE_ROOT/tracks/_meta/.psa_override_log" 2>/dev/null || true
|
|
309
354
|
else
|
|
310
355
|
FAILED=1
|
|
311
356
|
fi
|
|
@@ -342,7 +387,7 @@ else
|
|
|
342
387
|
if [ "${PUBLIC_SURFACE_OK:-0}" = "1" ]; then
|
|
343
388
|
echo " ⚠️ public-surface hit(s) allowed by PUBLIC_SURFACE_OK=1 (conscious, reviewed intent)"
|
|
344
389
|
echo "$(date +%Y-%m-%dT%H:%M:%S) PUBLIC_SURFACE_OK override — branch $BRANCH — review suppressed hit(s) above" \
|
|
345
|
-
>> "$
|
|
390
|
+
>> "$EVIDENCE_ROOT/tracks/_meta/.psa_override_log" 2>/dev/null || true
|
|
346
391
|
else
|
|
347
392
|
echo " Operator-private token reached the public surface. Generalize it (companion-store name"
|
|
348
393
|
echo " → 'a private companion store'; corp-context → 'restricted/corp env'; absolute home path"
|
|
@@ -1243,7 +1288,7 @@ validate_marker_floor() {
|
|
|
1243
1288
|
# ── Axes 2+3 — steel-quench + phantom-quench (full gate only) ─────────
|
|
1244
1289
|
if [ "$GATE_MODE" = "full" ]; then
|
|
1245
1290
|
echo "[Axis 2+3] Adversarial + Source-Grounding..."
|
|
1246
|
-
MARKER_DIR="$
|
|
1291
|
+
MARKER_DIR="$EVIDENCE_ROOT/tracks/_meta"
|
|
1247
1292
|
MARKER="$MARKER_DIR/.axes_23_passed_${BRANCH_SLUG}_${TODAY}.marker"
|
|
1248
1293
|
|
|
1249
1294
|
if [ -f "$MARKER" ]; then
|
|
@@ -1270,7 +1315,7 @@ fi
|
|
|
1270
1315
|
|
|
1271
1316
|
# ── Axis 4 — Edit Manifest entry (always required) ────────────────────────────
|
|
1272
1317
|
echo "[Axis 4] Edit Manifest..."
|
|
1273
|
-
MANIFEST="$
|
|
1318
|
+
MANIFEST="$EVIDENCE_ROOT/tracks/_meta/edit_manifest.yaml"
|
|
1274
1319
|
if [ ! -f "$MANIFEST" ]; then
|
|
1275
1320
|
echo " ❌ FAIL — tracks/_meta/edit_manifest.yaml not found"
|
|
1276
1321
|
echo " Run /edit-manifest RECORD or create the file manually."
|
|
@@ -1311,6 +1356,19 @@ for i, entry in enumerate(doc):
|
|
|
1311
1356
|
if not isinstance(entry, dict):
|
|
1312
1357
|
print("entry #%d is a %s, not a mapping — a list item may have drifted outside its parent"
|
|
1313
1358
|
% (i, type(entry).__name__)); sys.exit(1)
|
|
1359
|
+
# 🟥 인터리브 지문 — 동시 append 는 **YAML 을 깨지 않고** 내용을 뒤섞는다.
|
|
1360
|
+
# 2026-08-18 실측(적대적 재현): 워커 4개가 한 엔트리를 여러 write 로 쪼개 append 하면
|
|
1361
|
+
# 파싱은 OK 인데 필드가 서로 섞이고(w3 의 branch 에 w2 의 impact) 나머지는 `date` 만 남는다.
|
|
1362
|
+
# ⇒ **형식 검사가 통과시키는 무음 손상.** 이 축이 없애려는 병을 이 축 자신이 통과시키고 있었다.
|
|
1363
|
+
# 지문은 스키마가 아니다 — 실물 407 엔트리의 키 분포가 느슨해서(date 100% · branch 38%)
|
|
1364
|
+
# 필수키를 걸면 과거를 과차단한다. known-pair 로 고른 지문은 **«date 만 있는 엔트리»**:
|
|
1365
|
+
# known-negative 실물 407건 → 0건 · known-positive 재현본 → 3/5.
|
|
1366
|
+
# 워크트리 병렬 커밋이 열리기 전에는 이 조건 자체가 안 생겼다 — #448 이 문을 여니 같이 닫는다.
|
|
1367
|
+
orphan = [i for i, e in enumerate(doc) if set(e.keys()) == {"date"}]
|
|
1368
|
+
if orphan:
|
|
1369
|
+
print("entry #%s has only `date` — concurrent-append interleaving signature "
|
|
1370
|
+
"(fields lost/mixed). Re-check the last appended entries by hand." % orphan[0])
|
|
1371
|
+
sys.exit(1)
|
|
1314
1372
|
def norm(v):
|
|
1315
1373
|
return v.isoformat() if hasattr(v, "isoformat") else str(v).strip()
|
|
1316
1374
|
if not any(norm(e.get("date")) == sys.argv[2] for e in doc):
|
|
@@ -1512,7 +1570,7 @@ if [ -n "$LOADBEARING" ]; then
|
|
|
1512
1570
|
else
|
|
1513
1571
|
echo " ⚠️ degrade lint unavailable (scripts/degrade_direction_scan.sh missing) — advisory leg skipped"
|
|
1514
1572
|
fi
|
|
1515
|
-
MARKER_LB="$
|
|
1573
|
+
MARKER_LB="$EVIDENCE_ROOT/tracks/_meta/.axes_23_passed_${BRANCH_SLUG}_${TODAY}.marker"
|
|
1516
1574
|
if validate_crossfamily_leg "$MARKER_LB"; then :; else FAILED=1; fi
|
|
1517
1575
|
if validate_standpoint_leg "$MARKER_LB"; then :; else FAILED=1; fi
|
|
1518
1576
|
if validate_thirdparty_leg "$MARKER_LB"; then :; else FAILED=1; fi
|