@chrono-meta/fh-gate 2.15.1 → 3.1.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 (134) hide show
  1. package/.claude/regression/probes_live.yaml +137 -0
  2. package/.claude/rules/.residency-patterns.defaults +7 -0
  3. package/.claude/rules/fh_4axis_gate.md +88 -1
  4. package/.claude-plugin/marketplace.json +8 -2
  5. package/AGENTS.md +27 -0
  6. package/CATALOG.md +17 -0
  7. package/CLAUDE.md +13 -3
  8. package/README.ja.md +51 -7
  9. package/README.ko.md +48 -7
  10. package/README.md +43 -6
  11. package/README.zh.md +45 -8
  12. package/docs/STANDARDS_ALIGNMENT.md +61 -0
  13. package/docs/USER_GUIDE.md +3 -0
  14. package/docs/USE_CASES.md +50 -0
  15. package/docs/model_tier_expectations.md +60 -0
  16. package/knowledge/shared/harness-core/fh_three_layer_canon.md +77 -10
  17. package/knowledge/shared/harness-core/field_verdict_crossfamily_gate.md +18 -0
  18. package/knowledge/shared/harness-core/harness_incubator_doctrine.md +9 -0
  19. package/knowledge/shared/harness-core/iso_ai_standards_crosswalk.md +139 -0
  20. package/knowledge/shared/harness-core/measurement-integrity-checklist.md +35 -1
  21. package/knowledge/shared/harness-core/ship_readiness_gate.md +19 -2
  22. package/knowledge/shared/learnings/subagent_invocations_log.yaml +489 -3
  23. package/package.json +48 -2
  24. package/plugins/fh-commons/.claude-plugin/plugin.json +1 -1
  25. package/plugins/fh-commons/skills/preprep/README.md +4 -1
  26. package/plugins/fh-commons/skills/preprep/SKILL.md +99 -3
  27. package/plugins/fh-commons/skills/preprep/diagram_from_json.py +154 -0
  28. package/plugins/fh-commons/skills/preprep/fixtures/fixture_R3_negative.pptx +0 -0
  29. package/plugins/fh-commons/skills/preprep/fixtures/fixture_R3_positive.pptx +0 -0
  30. package/plugins/fh-commons/skills/preprep/fixtures/mk_slide_fixtures.py +179 -0
  31. package/plugins/fh-commons/skills/preprep/interslide_deps.py +98 -9
  32. package/plugins/fh-commons/skills/preprep/lane_adjacent_dup.py +4 -1
  33. package/plugins/fh-commons/skills/preprep/lane_diagram.py +95 -0
  34. package/plugins/fh-commons/skills/preprep/lane_geometry.py +181 -0
  35. package/plugins/fh-commons/skills/preprep/lane_promise.py +4 -1
  36. package/plugins/fh-commons/skills/preprep/lane_slide_refs.py +134 -0
  37. package/plugins/fh-commons/skills/preprep/lane_slide_relations.py +300 -0
  38. package/plugins/fh-commons/skills/preprep/preprep.py +129 -8
  39. package/plugins/fh-commons/skills/preprep/preprep_wire.py +210 -0
  40. package/plugins/fh-commons/skills/preprep/presentation_checklist.md +4 -0
  41. package/plugins/fh-commons/skills/preprep/surfaces.example.yaml +18 -0
  42. package/plugins/fh-commons/skills/preprep/test_preprep_lanes_rp.py +184 -0
  43. package/plugins/fh-meta/.claude-plugin/plugin.json +1 -1
  44. package/plugins/fh-meta/CHANGELOG.md +67 -0
  45. package/plugins/fh-meta/skills/auto-decorrelation/SKILL.md +87 -2
  46. package/plugins/fh-meta/skills/frontier-digest/SKILL.md +2 -2
  47. package/plugins/fh-meta/skills/frontier-digest/SKILL_detail.md +25 -5
  48. package/plugins/fh-meta/skills/hub-cc-pr-reviewer/SKILL.md +11 -0
  49. package/plugins/fh-meta/skills/hub-cc-pr-reviewer/SKILL_detail.md +3 -0
  50. package/plugins/fh-qp/.claude-plugin/plugin.json +22 -0
  51. package/plugins/fh-qp/README.md +71 -0
  52. package/plugins/fh-qp/fixtures/evidence_known_clean.txt +3 -0
  53. package/plugins/fh-qp/fixtures/evidence_known_dirty.txt +6 -0
  54. package/plugins/fh-qp/fixtures/reach_known_partial.tsv +4 -0
  55. package/plugins/fh-qp/fixtures/reach_known_reached.tsv +4 -0
  56. package/plugins/fh-qp/fixtures/reach_known_wall.tsv +3 -0
  57. package/plugins/fh-qp/fixtures/verdicts_known_bad_branch.tsv +2 -0
  58. package/plugins/fh-qp/fixtures/verdicts_known_bad_vacuous_machine.tsv +2 -0
  59. package/plugins/fh-qp/fixtures/verdicts_known_good.tsv +4 -0
  60. package/plugins/fh-qp/fixtures/verdicts_verify_only.tsv +3 -0
  61. package/plugins/fh-qp/qp_profile.example.yaml +29 -0
  62. package/plugins/fh-qp/scripts/qp_tools.sh +216 -0
  63. package/plugins/fh-qp/skills/qp/SKILL.md +65 -0
  64. package/plugins/fh-qp/skills/qp-plan/SKILL.md +42 -0
  65. package/plugins/fh-qp/skills/qp-regress/SKILL.md +45 -0
  66. package/plugins/fh-qp/skills/qp-run/SKILL.md +49 -0
  67. package/scripts/backtick_guard.sh +194 -0
  68. package/scripts/chamber_run.sh +14 -5
  69. package/scripts/com.forge-harness.live-eval.plist +84 -0
  70. package/scripts/compaction_probe.sh +9 -35
  71. package/scripts/context_continuity_score.sh +49 -7
  72. package/scripts/directional_diff_gate.sh +14 -2
  73. package/scripts/fh-gate.sh +3 -3
  74. package/scripts/files_manifest_shipping_check.sh +19 -0
  75. package/scripts/frontier_digest_autopilot.sh +4 -1
  76. package/scripts/gate_pathspec_check.sh +1 -1
  77. package/scripts/map_postprocess.py +90 -0
  78. package/scripts/outbound_query_guard.sh +131 -0
  79. package/scripts/outbound_query_hook.sh +373 -0
  80. package/scripts/package_coverage_check.sh +79 -18
  81. package/scripts/pipe_verdict_guard.sh +41 -1
  82. package/scripts/probe_live_eval.sh +240 -0
  83. package/scripts/probe_live_eval_lib.py +579 -0
  84. package/scripts/proposal_hook.sh +192 -0
  85. package/scripts/public_surface_scan_files.sh +11 -2
  86. package/scripts/push_zone_check.sh +78 -0
  87. package/scripts/residency_closure_scan.py +252 -0
  88. package/scripts/revert_probe.sh +250 -0
  89. package/scripts/selfcheck.sh +106 -3
  90. package/scripts/session_close_check.sh +100 -0
  91. package/scripts/sim_isolated_run.sh +195 -9
  92. package/scripts/test_action_yml_lanes.sh +97 -0
  93. package/scripts/test_backtick_guard_lanes.sh +115 -0
  94. package/scripts/test_degrade_scan_shell_probes.sh +7 -7
  95. package/scripts/test_fh_qp_lanes.sh +105 -0
  96. package/scripts/test_files_manifest_shipping_lanes.sh +5 -5
  97. package/scripts/test_gate_two_verdicts_lanes.sh +139 -0
  98. package/scripts/test_heavy_classifier_lanes.sh +1 -1
  99. package/scripts/test_lane_runner_lanes.sh +59 -33
  100. package/scripts/test_map_postprocess_lanes.sh +143 -0
  101. package/scripts/test_mapped_tracks_lanes.sh +1 -1
  102. package/scripts/test_marker_affected_lanes.sh +93 -0
  103. package/scripts/test_marker_crossfamily_lanes.sh +90 -6
  104. package/scripts/test_marker_oracle_lanes.sh +136 -0
  105. package/scripts/test_marker_soul_check_lanes.sh +24 -0
  106. package/scripts/test_node_check_lanes.sh +34 -34
  107. package/scripts/test_outbound_query_hook_lanes.sh +433 -0
  108. package/scripts/test_outbound_query_lanes.sh +87 -0
  109. package/scripts/test_package_coverage_lanes.sh +53 -27
  110. package/scripts/test_pipe_verdict_guard_lanes.sh +31 -5
  111. package/scripts/test_precommit_pointer_index_lanes.sh +33 -0
  112. package/scripts/test_preprep_diagram_lanes.sh +87 -0
  113. package/scripts/test_preprep_drift_anchor.sh +16 -7
  114. package/scripts/test_preprep_drift_anchor_lanes.sh +23 -0
  115. package/scripts/test_preprep_slide_refs_lanes.sh +169 -0
  116. package/scripts/test_probe_live_eval_lanes.sh +437 -0
  117. package/scripts/test_proposal_hook_lanes.sh +57 -0
  118. package/scripts/test_push_zone_lanes.sh +304 -0
  119. package/scripts/test_residency_closure_lanes.sh +70 -0
  120. package/scripts/test_revert_probe_lanes.sh +146 -0
  121. package/scripts/test_session_close_lanes.sh +3 -5
  122. package/scripts/test_sim_isolated_run_lanes.sh +136 -0
  123. package/scripts/test_utterance_intake_lanes.sh +414 -0
  124. package/scripts/test_worktree_reclaim_lanes.sh +70 -0
  125. package/scripts/transcript_utterances.py +222 -0
  126. package/scripts/utterance_intake.sh +424 -0
  127. package/scripts/utterance_landing_check.sh +2 -2
  128. package/scripts/validate_yaml.sh +27 -0
  129. package/scripts/worktree_reclaim.sh +95 -0
  130. package/templates/.git-hooks/pre-commit +380 -5
  131. package/templates/.git-hooks/pre-push +91 -0
  132. package/templates/RED_TEAM_REPORT.md +49 -0
  133. package/templates/settings.PreToolUse.snippet.json +101 -1
  134. package/plugins/fh-commons/README.md +0 -38
@@ -780,8 +780,32 @@ validate_defense_leg() { # $1 = marker path — fires ONLY at the floor tiers (s
780
780
  return 0
781
781
  }
782
782
 
783
+ # ── residency= token inside crossfamily grounds (2026-09-05, operator-approved) ──────────────
784
+ # WHY: `scripts/residency_closure_scan.py` screens a payload for organizational identifiers
785
+ # before it leaves this family boundary — but until now "was it screened" was a claim with no
786
+ # channel: auto-decorrelation's own Step 5 note ("Residency still governs … sanitize before any
787
+ # external-family dispatch") was prose with nothing downstream reading it back
788
+ # ([[feedback_half_externalization_slot_without_consumer]] shape — a slot with no consumer). This
789
+ # wires the scan's own exit code into the SAME typed field the crossfamily lane already validates,
790
+ # rather than opening a new marker line — one channel, not two.
791
+ #
792
+ # GRAMMAR (inside crossfamily's own grounds, after the em-dash):
793
+ # crossfamily: panel(codex) — residency=CLEAN(files=7) · R1..R2, 4 findings
794
+ # crossfamily: DEGRADED_SINGLE_FAMILY — residency=TAINTED(2 files, stripped=no) → not sent · …
795
+ # crossfamily: DEGRADED_SINGLE_FAMILY — residency=NOT_SCANNED(scanner absent) → not sent · …
796
+ #
797
+ # SCOPE — same Mechanization Boundary as the rest of this function: a channel check on the
798
+ # RECORD (present when a panel claim is made, closed-enum shaped whenever it appears at all),
799
+ # never a truth check. Whether the CLEAN verdict was ACCURATE is not this lane's question — that
800
+ # stays with the reader (cross-family reads the marker), identical ceiling to `standpoint:`.
801
+ #
802
+ # 🟥 NO RETROACTIVITY. Same pattern as `DEFEATER_GRACE_DATE` below: a marker dated before this
803
+ # constant is validated exactly as it was before this change existed. Forcing it retroactively
804
+ # would block every in-flight branch and teach `--no-verify`, which disarms the Destructive-Op
805
+ # gate sharing this hook.
806
+ RESIDENCY_TOKEN_GRACE_DATE="2026-09-05"
783
807
  validate_crossfamily_leg() { # $1 = marker path
784
- local m="$1" line val reason fams
808
+ local m="$1" line val reason fams mdate _res_active _res_tokens _res_ntok _res_wellformed _res_kind
785
809
  if [ ! -f "$m" ] || ! grep -qE '^[[:space:]]*crossfamily:[[:space:]]*[^[:space:]]' "$m"; then
786
810
  echo " ❌ FAIL — load-bearing file staged with no 'crossfamily:' line in the Axes 2-3 marker."
787
811
  echo " Verdict/gate/irreversible-surface code shares the author's blind spot with a"
@@ -817,6 +841,33 @@ validate_crossfamily_leg() { # $1 = marker path
817
841
  reason=$(printf '%s' "$line" | sed -E 's/^[^—-]*(—|--)[[:space:]]*//')
818
842
  [ "$reason" = "$line" ] && reason=""
819
843
 
844
+ # residency= token — computed once, consumed by whichever branch below needs it (REQUIRED for
845
+ # panel(...), format-only-if-present for declined/DEGRADED_*/UNKNOWN). See the comment above
846
+ # RESIDENCY_TOKEN_GRACE_DATE for the grammar and why this rides inside crossfamily's own grounds
847
+ # rather than opening a new marker line.
848
+ mdate=$(printf '%s' "$m" | sed -nE 's/.*_([0-9]{4}-[0-9]{2}-[0-9]{2})\.marker$/\1/p')
849
+ _res_active=1
850
+ # Same "can't-read-date → NOT exempt" rule as validate_defeater_leg, same reason: the hook
851
+ # stamps TODAY's date into every marker filename it creates, so an unparseable date is an
852
+ # unexpected shape, not evidence of age ([[feedback_not_found_is_not_zero_family]]).
853
+ if [ -n "$mdate" ] && [ "$mdate" \< "$RESIDENCY_TOKEN_GRACE_DATE" ]; then _res_active=0; fi
854
+ # Presence counts EVERY `residency=` occurrence (any shape); well-formedness is a SEPARATE grep for
855
+ # the full token `residency=<KIND>(...)` with its closing paren. The first draft grepped the prefix
856
+ # `residency=<KIND>(` only, so a malformed token could hide by FAILING the shape grep — cross-family
857
+ # (codex gpt-5.5, 2026-09-05) showed `residency=CLEAN (files=2)` (space) and `residency=CLEAN(files=1`
858
+ # (no close paren) passing the optional-token path as if absent. Presence-first closes that: a
859
+ # `residency=` that is not one well-formed token is malformed, never invisible.
860
+ _res_tokens=$(printf '%s' "$reason" | grep -oE 'residency=[^[:space:]]*' 2>/dev/null || true)
861
+ _res_ntok=0
862
+ [ -n "$_res_tokens" ] && _res_ntok=$(printf '%s\n' "$_res_tokens" | grep -c .)
863
+ _res_full=$(printf '%s' "$reason" | grep -oE 'residency=(CLEAN|TAINTED|NOT_SCANNED)\([^)]*\)' 2>/dev/null || true)
864
+ _res_nfull=0
865
+ [ -n "$_res_full" ] && _res_nfull=$(printf '%s\n' "$_res_full" | grep -c .)
866
+ _res_wellformed=0
867
+ [ "$_res_ntok" -eq 1 ] && [ "$_res_nfull" -eq 1 ] && _res_wellformed=1
868
+ _res_kind=""
869
+ [ "$_res_wellformed" -eq 1 ] && _res_kind=$(printf '%s' "$_res_full" | sed -E 's/^residency=([A-Za-z_]*)\(.*$/\1/')
870
+
820
871
  case "$val" in
821
872
  declined)
822
873
  # `declined` means ONE thing: the OPERATOR declined sidecars (UAP), a chosen floor. It was
@@ -872,6 +923,22 @@ validate_crossfamily_leg() { # $1 = marker path
872
923
  [ "${#reason}" -ge 20 ] && echo " (grounds were substantive but named no resolvable path)"
873
924
  return 1
874
925
  fi
926
+ if [ "$_res_active" -eq 1 ] && [ "$_res_ntok" -gt 0 ]; then
927
+ if [ "$_res_ntok" -gt 1 ]; then
928
+ echo " ❌ FAIL — grounds carries MORE THAN ONE 'residency=' token."
929
+ printf '%s\n' "$_res_tokens" | sed 's/^/ /'
930
+ echo " Same trap as two crossfamily: lines — the reader takes the first, so an"
931
+ echo " appended correction is silently shadowed. Leave exactly one."
932
+ return 1
933
+ fi
934
+ if [ "$_res_wellformed" -ne 1 ]; then
935
+ echo " ❌ FAIL — 'residency=' token present but not one of CLEAN|TAINTED|NOT_SCANNED:"
936
+ printf '%s\n' "$_res_tokens" | sed 's/^/ /'
937
+ echo " Closed set, same reason crossfamily's own value is one — free prose here is"
938
+ echo " read as measured by a later session."
939
+ return 1
940
+ fi
941
+ fi
875
942
  echo " ✅ cross-family leg: declined (operator-chosen floor, first-class — not a degrade)"
876
943
  echo " $(printf '%s' "$reason" | cut -c1-80)" ;;
877
944
  panel*)
@@ -922,6 +989,38 @@ validate_crossfamily_leg() { # $1 = marker path
922
989
  echo " pattern here in the same commit that first uses it."
923
990
  return 1
924
991
  fi
992
+ if [ "$_res_active" -eq 1 ]; then
993
+ if [ "$_res_ntok" -gt 1 ]; then
994
+ echo " ❌ FAIL — grounds carries MORE THAN ONE 'residency=' token."
995
+ printf '%s\n' "$_res_tokens" | sed 's/^/ /'
996
+ echo " Same trap as two crossfamily: lines — the reader takes the first. Leave"
997
+ echo " exactly one."
998
+ return 1
999
+ fi
1000
+ if [ "$_res_ntok" -eq 0 ]; then
1001
+ echo " ❌ FAIL — panel(...) with no 'residency=CLEAN(' token in grounds."
1002
+ echo " A panel claim means a payload left this family boundary. Screen it first"
1003
+ echo " (scripts/residency_closure_scan.py --files <payload>) and record the result:"
1004
+ echo " crossfamily: panel($fams) — residency=CLEAN(files=7) · R1..R3, ..."
1005
+ echo " A TAINTED or NOT_SCANNED payload must not have been sent — if that is what"
1006
+ echo " actually happened, the honest crossfamily value is DEGRADED_*, not panel(...)."
1007
+ return 1
1008
+ fi
1009
+ if [ "$_res_wellformed" -ne 1 ]; then
1010
+ echo " ❌ FAIL — 'residency=' token present but not one of CLEAN|TAINTED|NOT_SCANNED:"
1011
+ printf '%s\n' "$_res_tokens" | sed 's/^/ /'
1012
+ echo " Closed set, same reason crossfamily's own value is one — free prose here is"
1013
+ echo " read as measured by a later session."
1014
+ return 1
1015
+ fi
1016
+ if [ "$_res_kind" != "CLEAN" ]; then
1017
+ echo " ❌ FAIL — panel(...) (payload WAS sent) with residency=$_res_kind(...) (NOT clean"
1018
+ echo " / not screened) on the SAME line — a contradiction in the record."
1019
+ echo " A sent payload must carry residency=CLEAN(...). If it was actually TAINTED or"
1020
+ echo " NOT_SCANNED, it should not have shipped — the honest value is DEGRADED_*."
1021
+ return 1
1022
+ fi
1023
+ fi
925
1024
  echo " ✅ cross-family leg: $val${reason:+ — $(printf '%s' "$reason" | cut -c1-60)}" ;;
926
1025
  DEGRADED_SINGLE_FAMILY|DEGRADED_PANEL_UNUSED|UNKNOWN)
927
1026
  # A degrade is allowed; being silent about WHICH degrade, or about its grounds, is not.
@@ -940,6 +1039,18 @@ validate_crossfamily_leg() { # $1 = marker path
940
1039
  echo " crossfamily: $val — probed codex/agy/gemini, <what came back>"
941
1040
  return 1
942
1041
  fi
1042
+ if [ "$_res_active" -eq 1 ] && [ "$_res_ntok" -gt 0 ]; then
1043
+ if [ "$_res_ntok" -gt 1 ]; then
1044
+ echo " ❌ FAIL — grounds carries MORE THAN ONE 'residency=' token."
1045
+ printf '%s\n' "$_res_tokens" | sed 's/^/ /'
1046
+ return 1
1047
+ fi
1048
+ if [ "$_res_wellformed" -ne 1 ]; then
1049
+ echo " ❌ FAIL — 'residency=' token present but not one of CLEAN|TAINTED|NOT_SCANNED:"
1050
+ printf '%s\n' "$_res_tokens" | sed 's/^/ /'
1051
+ return 1
1052
+ fi
1053
+ fi
943
1054
  echo " ⚠️ cross-family leg: $val — recorded, not silent (weekly audit re-queues these)"
944
1055
  echo " $(printf '%s' "$reason" | cut -c1-80)"
945
1056
  # 🟥 수락 경로에도 «안 짚은 채널»을 띄운다. 실패 메시지에만 채널 목록이 있으면,
@@ -1360,6 +1471,206 @@ validate_defeater_leg() { # $1 = marker path
1360
1471
  }
1361
1472
 
1362
1473
 
1474
+ # ── ⑤ affected: — 「이 변경이 건드리는 것 + 열린 질문」 (2026-09-04, frontier absorption) ──
1475
+ # tenet: FH-T02 (기록의 속성) · FH-T07 (행위자가 읽는 자리)
1476
+ #
1477
+ # WHY: Anthropic AI-Native SDLC playbook `intent.md` 는 «Affected users and systems» ·
1478
+ # «Open questions» 두 칸을 싣는다. FH 는 두 칸으로 쪼개지 않는다 — 별도 필드마다 빈 칸이 하나씩
1479
+ # 늘어나는 것이 이 절 전체(soul-check·tenets·thirdparty)가 이미 피하는 모양이다. 한 줄 자유
1480
+ # 산문 안에 「건드리는 것」과 「열린 질문 = …」 관례를 같이 담는다.
1481
+ #
1482
+ # SCOPE — **채널 검사만**. `defeater:` 와 같은 형태(단일 줄·중복 차단·비공허)이지만 한 가지
1483
+ # 다르다: `defeater:` 는 「없음」을 정직한 선언으로 인정한다(반증 조건이 실제로 없을 수 있다).
1484
+ # `affected:` 는 다르다 — 어떤 변경이든 반드시 무언가를 건드린다(제로 영향은 명제상 없다). 그래서
1485
+ # 「없음/TBD/-」류 자리표시자만 있는 값은 정직한 부재가 아니라 **안 채운 것**이고, 차단한다.
1486
+ #
1487
+ # 옵셔널 필드다 — **없으면 통과**(soul-check:/tenets: 와 같은 패턴. 채택은 점진적이다).
1488
+ validate_affected_leg() { # $1 = marker path
1489
+ local m="$1" n line body words
1490
+ n=$(grep -cE '^[[:space:]]*affected:' "$m" 2>/dev/null); n=${n:-0}
1491
+ if [ "$n" -eq 0 ]; then
1492
+ # near-miss: 키 오타는 부재가 아니라 오타다 (defeater: 와 같은 이유 — 저자는 적었다고
1493
+ # 믿는데 게이트는 못 읽는 것이 가장 조용한 실패다).
1494
+ if grep -qE '^[[:space:]]*(affects:|affected[[:space:]]+:|affeted:|affcted:|effected:)' "$m" 2>/dev/null; then
1495
+ echo " ❌ FAIL — affected 키처럼 보이지만 정확히 'affected:' 가 아닌 줄이 있다."
1496
+ grep -nE '^[[:space:]]*(affects:|affected[[:space:]]+:|affeted:|affcted:|effected:)' "$m" | sed 's/^/ /'
1497
+ echo " 읽는 쪽은 정확히 'affected:' 만 읽는다."
1498
+ return 1
1499
+ fi
1500
+ return 0 # 진짜 부재 — 옵셔널 필드, 채택은 점진적이다(soul-check:/tenets: 와 같은 형태)
1501
+ fi
1502
+ if [ "$n" -gt 1 ]; then
1503
+ echo " ❌ FAIL — 마커에 'affected:' 줄이 둘 이상이다."
1504
+ grep -nE '^[[:space:]]*affected:' "$m" | sed 's/^/ /'
1505
+ echo " 읽는 쪽은 첫 줄을 취하므로 나중에 붙인 정정이 조용히 가려진다."
1506
+ return 1
1507
+ fi
1508
+ line=$(grep -m1 -E '^[[:space:]]*affected:' "$m" | sed -E 's/^[[:space:]]*affected:[[:space:]]*//')
1509
+ body=$(printf '%s' "$line" | sed -E 's/^[[:space:]]+//; s/[[:space:]]+$//')
1510
+ # 양끝 구두점/공백만 벗긴다 — 클래스 파싱(`tr -d '[:punct:]'` 류)은 한글 바이트를 지우는
1511
+ # 이식성 결함을 낸 전례가 있다(defeater 의 2026-08-30 CI 리트로). 파라미터 확장만 쓴다.
1512
+ _av="$body"
1513
+ while :; do
1514
+ case "$_av" in
1515
+ ' '*|' '*) _av="${_av# }"; _av="${_av# }" ;;
1516
+ *' '|*' ') _av="${_av% }"; _av="${_av% }" ;;
1517
+ *'.'|*'·'|*'—'|*'-'|*']') _av="${_av%?}" ;;
1518
+ *) break ;;
1519
+ esac
1520
+ done
1521
+ case "$(printf '%s' "$_av" | tr 'A-Z' 'a-z')" in
1522
+ 없음|none|n/a|na|tbd|todo|-|해당없음|'')
1523
+ echo " ❌ FAIL — affected 가 자리표시자뿐이다 ('$body')."
1524
+ echo " 이 변경이 건드리는 것(사람·하네스·표면)과 아직 열린 질문을 한 줄로 적어라."
1525
+ echo " affected: 소비자 install 의 pre-commit 사용자(마커 형식) · 열린 질문 = 필드 강제 시점"
1526
+ echo " Append to: $m"
1527
+ return 1 ;;
1528
+ esac
1529
+ # 🟥 헬퍼 부재를 «잔여 없음»으로 렌더하지 않는다 — 레인 스위트가 이 함수만 추출하면 헬퍼가
1530
+ # 안 따라오고, 정의되지 않은 명령은 거짓이 되어 fail-open 이 된다(defeater 가 이미 겪었다).
1531
+ if ! declare -f _marker_template_residue >/dev/null 2>&1; then
1532
+ echo " ❌ HARNESS-ERROR — _marker_template_residue 가 정의돼 있지 않다."
1533
+ echo " 격리 실행이면 그 헬퍼도 같이 추출해야 한다. 부재는 «통과»가 아니다."
1534
+ return 1
1535
+ fi
1536
+ if _marker_template_residue "$body"; then
1537
+ echo " ❌ FAIL — affected 가 힌트의 자리표시자 그대로다 (기록이 아직 안 쓰였다)."
1538
+ return 1
1539
+ fi
1540
+ words=$(printf '%s' "$body" | wc -w | tr -d ' '); words=${words:-0}
1541
+ if [ "$words" -lt 3 ]; then
1542
+ echo " ❌ FAIL — affected 가 공허하다 ($words 낱말). 건드리는 것 · 열린 질문을 적어라."
1543
+ return 1
1544
+ fi
1545
+ echo " ✅ affected 기록됨 ($words 낱말)"
1546
+ return 0
1547
+ }
1548
+
1549
+
1550
+ # ── ⑥ oracle: — 「기대값을 무엇으로 정했나」 오라클 유형 (2026-09-05, ISO/IEC TR 29119-11 정렬 · crosswalk §4 M1) ──
1551
+ # tenet: FH-T02 (기록의 속성) · FH-T07 (행위자가 읽는 자리)
1552
+ #
1553
+ # WHY: TR 29119-11 의 핵심이 «테스트 오라클 문제»(기대값을 정할 수 없을 때 무엇으로 판정하나)다.
1554
+ # FH 는 known-pair · 결과 전 채점기 · «미측정 ≠ 0» 으로 그 문제를 다루지만, 마커는 «컨트롤이
1555
+ # 살아 있다» 까지만 적고 **어떤 종류의 오라클**로 판정했는지는 안 적었다 — 외부 독자가 첫 번째로
1556
+ # 묻는 칸이 비어 있었다(knowledge/shared/harness-core/iso_ai_standards_crosswalk.md §4 M1,
1557
+ # 운영자 승인 2026-09-05 «오라클 채널을 넣는 게 좋아 보인다면 마다할 이유가 없지»).
1558
+ #
1559
+ # SCOPE — **채널 검사만** (§Mechanization Boundary): 닫힌 enum(6) · 비공허 근거 · 단일 줄 · 근사키
1560
+ # 차단. 값의 진위(그 오라클이 정말 그 종류였나)는 안 본다. `none` 은 합법이되 **사유가 필수**다 —
1561
+ # «오라클 없이 판정했다» 는 기록이고, 사유 없는 none 은 «안 봤다» 와 구분이 안 된다.
1562
+ #
1563
+ # enum (형식: `oracle: <kind> — <근거>` · kind 뒤는 공백 · — · : · · · ( · . 중 하나, 또는 줄 끝)
1564
+ # known-pair 양성·음성 컨트롤이 같은 실행에 있다 (계기 보정)
1565
+ # metamorphic 입력 변환 → 기대 출력 변화 «관계»로 판정 (기대값 없이)
1566
+ # back-to-back 다른 구현/계열이 같은 입력에 낸 출력을 대조 (cross-family 가 같은 diff 를 읽는 것)
1567
+ # a-b 한 변수 ARM/CTRL 비교 (reps≥3). `A/B` 는 허용 표기
1568
+ # human 사람이 기대값을 판정 (운영자 눈검증 · HITL)
1569
+ # none 오라클 없음 — 사유 필수 (문서만 · 측정 없음 …)
1570
+ #
1571
+ # 근사키 규칙(한 문장, 여섯 문서면이 같은 문장을 쓴다): **키가 `oracle` 로 시작하는데 정확히 `oracle:`
1572
+ # 이 아닌 줄 전부**(`Oracle:` `oracles:` `oracle :` `oracle :` `oracle_type:` `oracle-type:` 키만 있는
1573
+ # `oracle`) **+ 오타 `orcale:` `oralce:` `오라클:`** — 정상 `oracle:` 줄이 있어도 같이 있으면 차단한다
1574
+ # (cross-family codex #2: 정상 줄 뒤의 `Oracle: …` 정정이 조용히 가려지는 형태).
1575
+ #
1576
+ # 옵셔널 필드다 — **없으면 통과**(affected:/soul-check:/tenets: 와 같은 패턴. 채택은 점진적이다).
1577
+ # 힌트 템플릿에는 넣지 않는다 — 옵셔널 필드마다 빈 칸이 하나씩 느는 모양을 이 절이 피한다.
1578
+ # 소비처(정직하게): 오늘은 마커 grep 감사와 표준 정렬 증거뿐, 기계가 읽는 곳은 없다.
1579
+ # 명명된 잔여: 근거에 힌트 자리표시자 리터럴(`<...>`)이 있으면 차단된다(affected/defeater 와 같은 경계 —
1580
+ # 다른 표기로 적어라) · 전각 공백으로 «시작하는» 줄은 [[:space:]] 밖이라 못 본다.
1581
+ validate_oracle_leg() { # $1 = marker path
1582
+ local m="$1" n nm line body kind k rest words
1583
+ # near-miss — n 과 무관하게 항상 본다. `-n` 접두(N:) 뒤에서 정확한 소문자 `oracle:` 줄만 제외한다.
1584
+ nm=$(grep -niE '^[[:space:]]*(oracle:|oracle([^:]|$)|orcale:|oralce:|오라클:)' "$m" 2>/dev/null | grep -vE '^[0-9]+:[[:space:]]*oracle:')
1585
+ if [ -n "$nm" ]; then
1586
+ echo " ❌ FAIL — oracle 키처럼 보이지만 정확히 'oracle:' 가 아닌 줄이 있다."
1587
+ printf '%s\n' "$nm" | sed 's/^/ /'
1588
+ echo " 읽는 쪽은 정확히 'oracle:'(소문자, 콜론 바로 뒤) 만 읽는다 — 저자는 적었다고 믿는데 게이트는 못 읽는 것이 가장 조용한 실패다."
1589
+ return 1
1590
+ fi
1591
+ n=$(grep -cE '^[[:space:]]*oracle:' "$m" 2>/dev/null); n=${n:-0}
1592
+ if [ "$n" -eq 0 ]; then
1593
+ return 0 # 진짜 부재 — 옵셔널 필드, 채택은 점진적이다
1594
+ fi
1595
+ if [ "$n" -gt 1 ]; then
1596
+ echo " ❌ FAIL — 마커에 'oracle:' 줄이 둘 이상이다."
1597
+ grep -nE '^[[:space:]]*oracle:' "$m" | sed 's/^/ /'
1598
+ echo " 읽는 쪽은 첫 줄을 취하므로 나중에 붙인 정정이 조용히 가려진다."
1599
+ return 1
1600
+ fi
1601
+ line=$(grep -m1 -E '^[[:space:]]*oracle:' "$m" | sed -E 's/^[[:space:]]*oracle:[[:space:]]*//')
1602
+ body=$(printf '%s' "$line" | sed -E 's/^[[:space:]]+//; s/[[:space:]]+$//')
1603
+ if [ -z "$body" ]; then
1604
+ echo " ❌ FAIL — oracle 값이 비어 있다."
1605
+ echo " 형식: oracle: <kind> — <근거> kind ∈ known-pair · metamorphic · back-to-back · a-b · human · none"
1606
+ return 1
1607
+ fi
1608
+ # kind = 앞쪽 [A-Za-z/-] 연속 — 하이픈은 kind 안에 있다(known-pair · back-to-back · a-b).
1609
+ # 매치가 없으면 sed 가 body 를 그대로 돌려주고 enum 검사가 잡는다.
1610
+ kind=$(printf '%s' "$body" | sed -E 's/^([A-Za-z/-]+).*$/\1/')
1611
+ case "$kind" in "$body") rest="" ;; *) rest="${body#"$kind"}" ;; esac
1612
+ # kind 바로 뒤는 구분자이거나 줄 끝이어야 한다 — enum 멤버가 «접두어»로만 맞는 값(known-pair2 ·
1613
+ # human_review)이 통과하던 구멍(cross-family codex #1·#3).
1614
+ case "$rest" in
1615
+ ''|' '*|' '*|'—'*|':'*|'·'*|'('*|'.'*) ;;
1616
+ *)
1617
+ echo " ❌ FAIL — oracle kind 뒤에 구분자 없이 다른 문자가 붙어 있다 ('$body')."
1618
+ echo " 형식: oracle: <kind> — <근거> kind 뒤는 공백 · — · : · · · ( 중 하나 (예: known-pair2 · human_review 는 enum 밖)"
1619
+ return 1 ;;
1620
+ esac
1621
+ k=$(printf '%s' "$kind" | tr 'A-Z' 'a-z'); [ "$k" = "a/b" ] && k="a-b"
1622
+ case "$k" in
1623
+ known-pair|metamorphic|back-to-back|a-b|human|none) ;;
1624
+ *)
1625
+ echo " ❌ FAIL — oracle kind '$kind' 는 enum 밖이다."
1626
+ echo " kind ∈ known-pair · metamorphic · back-to-back · a-b · human · none (형식: oracle: <kind> — <근거>)"
1627
+ return 1 ;;
1628
+ esac
1629
+ # 근거 = kind 뒤. 양끝 구분자·공백만 벗긴다 — 리터럴 접두/접미 제거만 쓴다(`${x#?}` 는 C 로케일에서
1630
+ # 다바이트 구분자의 한 바이트만 깎고, 클래스 파싱은 한글 바이트를 지운 전례가 있다 — affected 참조).
1631
+ while :; do
1632
+ case "$rest" in
1633
+ ' '*) rest="${rest# }" ;; ' '*) rest="${rest# }" ;;
1634
+ '—'*) rest="${rest#—}" ;; '-'*) rest="${rest#-}" ;; ':'*) rest="${rest#:}" ;;
1635
+ '·'*) rest="${rest#·}" ;; '('*) rest="${rest#(}" ;; '.'*) rest="${rest#.}" ;;
1636
+ *' ') rest="${rest% }" ;; *' ') rest="${rest% }" ;;
1637
+ *')') rest="${rest%)}" ;; *'.') rest="${rest%.}" ;;
1638
+ *) break ;;
1639
+ esac
1640
+ done
1641
+ case "$(printf '%s' "$rest" | tr 'A-Z' 'a-z')" in
1642
+ 없음|none|n/a|na|tbd|todo|-|해당없음|'')
1643
+ if [ "$k" = none ]; then
1644
+ echo " ❌ FAIL — oracle: none 은 사유가 필수다 (지금: '$body')."
1645
+ echo " 예: oracle: none — 문서만 변경, 측정 없음"
1646
+ else
1647
+ echo " ❌ FAIL — oracle 근거가 자리표시자뿐이다 ('$body')."
1648
+ echo " 예: oracle: known-pair — 레인 PASS/BLOCK 픽스처 양쪽 + 되돌림"
1649
+ fi
1650
+ echo " Append to: $m"
1651
+ return 1 ;;
1652
+ esac
1653
+ # 🟥 헬퍼 부재를 «잔여 없음»으로 렌더하지 않는다 — 격리 추출이 헬퍼를 안 가져오면 정의되지 않은
1654
+ # 명령은 거짓이 되어 fail-open 이 된다(defeater·affected 가 이미 겪었다). 레인 o31 이 이 가지를 잰다.
1655
+ if ! declare -f _marker_template_residue >/dev/null 2>&1; then
1656
+ echo " ❌ HARNESS-ERROR — _marker_template_residue 가 정의돼 있지 않다."
1657
+ echo " 격리 실행이면 그 헬퍼도 같이 추출해야 한다. 부재는 «통과»가 아니다."
1658
+ return 1
1659
+ fi
1660
+ if _marker_template_residue "$rest"; then
1661
+ echo " ❌ FAIL — oracle 근거가 힌트의 자리표시자 그대로다 (기록이 아직 안 쓰였다)."
1662
+ return 1
1663
+ fi
1664
+ words=$(printf '%s' "$rest" | wc -w | tr -d ' '); words=${words:-0}
1665
+ if [ "$words" -lt 2 ]; then
1666
+ echo " ❌ FAIL — oracle 근거가 공허하다 ($words 낱말). 무엇을 무엇과 대조했는지 적어라."
1667
+ return 1
1668
+ fi
1669
+ echo " ✅ oracle 기록됨 ($k · 근거 $words 낱말)"
1670
+ return 0
1671
+ }
1672
+
1673
+
1363
1674
  SOUL_PRESENT_GRACE_DATE="2026-08-21"
1364
1675
  validate_soul_present_leg() { # $1 = marker path
1365
1676
  # ── ①영혼 존재 검사 (2026-08-21) ──────────────────────────────────────────────
@@ -1470,6 +1781,18 @@ validate_soul_present_leg() { # $1 = marker path
1470
1781
  echo " «이 기록이 템플릿 자신인가»만 본다 — 채널 검사다."
1471
1782
  return 1
1472
1783
  fi
1784
+ # ── ①영혼 2칸 존재 검사 (advisory only, 2026-09-04 — six_axis_review 판정안 4) ─────
1785
+ # «성공 정의»와 «절대 안 함» 두 낱말(변형 포함)이 둘 다 있는지만 본다. Mechanization
1786
+ # Boundary: 존재만 — 두 칸이 다 있어도 내용이 옳다는 보장은 없고, 하나가 없어도 커밋을
1787
+ # 막지 않는다(위 non-vacuity 검사가 이미 최소 6낱말을 요구·차단한다 — 이건 그 위에 얹는
1788
+ # 별도 관측이지 새 차단이 아니다). 「없음」 선언 분기는 위에서 이미 return 했으므로 여기
1789
+ # 도달하지 않는다.
1790
+ _soul_missing=""
1791
+ printf '%s' "$body" | grep -qE '성공 ?정의' || _soul_missing="성공 정의"
1792
+ printf '%s' "$body" | grep -qE '절대 ?안 ?함' || _soul_missing="${_soul_missing:+$_soul_missing · }절대 안 함"
1793
+ if [ -n "$_soul_missing" ]; then
1794
+ echo " ⚠️ ①영혼 2칸 미달 — 빠진 낱말: $_soul_missing (advisory, 커밋은 안 막는다)"
1795
+ fi
1473
1796
  echo " ✅ ①영혼 present: $words word(s)"
1474
1797
  return 0
1475
1798
  }
@@ -2045,6 +2368,8 @@ if [ "$GATE_MODE" = "full" ]; then
2045
2368
  # ([[feedback_built_but_not_wired]] — 오늘만 다섯 번째 얼굴).
2046
2369
  validate_soul_tenet_refs "$MARKER" || _SOUL_OK=0
2047
2370
  validate_defeater_leg "$MARKER" || _SOUL_OK=0
2371
+ validate_affected_leg "$MARKER" || _SOUL_OK=0
2372
+ validate_oracle_leg "$MARKER" || _SOUL_OK=0
2048
2373
  if [ "$_DEFENSE_OK" -eq 1 ] && [ "$_SOUL_OK" -eq 1 ] && validate_marker_floor "$MARKER" && validate_marker_axes_run "$MARKER" && validate_first_use_leg "$MARKER"; then
2049
2374
  echo " ✅ PASS (marker confirmed + floor/axes fields valid:"
2050
2375
  echo " .axes_23_passed_${BRANCH_SLUG}_${TODAY}.marker)"
@@ -2658,14 +2983,25 @@ if [ -n "$MD_STAGED" ]; then
2658
2983
  echo "[Pointers] Detail-pointer resolution (staged markdown)..."
2659
2984
  PTR_FAIL=0; PTR_CHECKED=0
2660
2985
  for f in $MD_STAGED; do
2661
- [ -f "$REPO_ROOT/$f" ] || continue
2662
- fdir="$(cd "$REPO_ROOT/$(dirname "$f")" && pwd)"
2663
- blocks="$(awk '
2986
+ # Read the STAGED blob, not the working-tree file. diff-filter=ACMR already guarantees $f
2987
+ # exists in the index, but a file can be staged and then deleted or further-edited on disk
2988
+ # before commit — `git commit` commits the INDEX, so `[ -f "$REPO_ROOT/$f" ] || continue`
2989
+ # here silently dropped the check on a committed broken pointer (deleted-on-disk case) and,
2990
+ # for a further-edited case, would have checked the wrong bytes entirely. Reproduced 2026-09-03:
2991
+ # stage a .md with a broken `**Detail**: See §X` pointer, `rm` it from disk (index untouched),
2992
+ # commit — PTR_CHECKED stayed 0 and the broken pointer landed silently.
2993
+ _md_blob="$(git -C "$REPO_ROOT" show ":$f" 2>/dev/null)" || {
2994
+ echo " ❌ $f: staged (diff-filter=ACMR) but unreadable from the index — cannot verify its Detail pointers"
2995
+ PTR_FAIL=1; continue
2996
+ }
2997
+ fdir="$(cd "$REPO_ROOT/$(dirname "$f")" 2>/dev/null && pwd)"
2998
+ [ -n "$fdir" ] || fdir="$REPO_ROOT/$(dirname "$f")"
2999
+ blocks="$(printf '%s\n' "$_md_blob" | awk '
2664
3000
  inblk && /^>/ { blk=blk" "$0; next }
2665
3001
  inblk { print blk; inblk=0; blk="" }
2666
3002
  /\*\*Detail\*\*: See/ { blk=$0; inblk=1; next }
2667
3003
  END { if(inblk) print blk }
2668
- ' "$REPO_ROOT/$f")"
3004
+ ')"
2669
3005
  [ -z "$blocks" ] && continue
2670
3006
  while IFS= read -r block; do
2671
3007
  [ -z "$block" ] && continue
@@ -2716,6 +3052,41 @@ else
2716
3052
  echo " ⚠️ 상주 유입 게이트 미설치 (scripts/residency_admission_check.sh) — skipped, not passed"
2717
3053
  fi
2718
3054
 
3055
+ # ── 수용-근거 한 줄 (보고 전용 — 절대 안 막는다) ────────────────────────────
3056
+ # WHY (2026-09-06, digest 답습 · arXiv:2609.04167 SWE-Gate): 그 논문은 «기능 테스트 통과»와
3057
+ # «리뷰 제약 충족»을 **따로** 채점하라고 처방한다 — 실측에서 기능 테스트를 통과한 수리 644 건 중
3058
+ # **221 건(34%)** 이 실제 PR 리뷰 코멘트에서 뽑은 제약을 어겼다. 한 숫자로 합치면 그 34% 가 안 보인다.
3059
+ # FH 도 같은 형태였다: 이 훅은 Axis 1(회귀)·레인·매니페스트가 통과하면 «ALL AXES PASSED» 라는
3060
+ # **합성 판정 하나**를 찍었고, 그동안 마커의 `crossfamily: DEGRADED_PANEL_UNUSED` 같은 값은
3061
+ # 출력 어디에도 안 나왔다. 통과는 통과인데 «무엇이 기록됐나»는 안 보이는 것이다.
3062
+ # 그래서 판정을 둘로 찍는다 — 위는 «초록인가», 아래는 «무엇이 기록됐나».
3063
+ # 🟥 §Mechanization Boundary: 이건 **채널**이다. 기록의 속성(어떤 값이 적혔나)만 찍고,
3064
+ # 그 값이 참인지는 판정하지 않으며 아무것도 막지 않는다. 차단은 기존 다리들이 그대로 한다.
3065
+ # Anchor: scripts/test_gate_two_verdicts_lanes.sh
3066
+ _acceptance_evidence_line() { # $1 = marker path (비었거나 없으면 n/a)
3067
+ local m="${1:-}" f v out=""
3068
+ if [ -z "$m" ] || [ ! -f "$m" ]; then
3069
+ printf 'acceptance-evidence: n/a (경량 게이트 — 이 경로는 마커를 읽지 않는다)\n'
3070
+ return 0
3071
+ fi
3072
+ for f in crossfamily standpoint thirdparty oracle; do
3073
+ v=$(sed -n "s/^[[:space:]]*${f}:[[:space:]]*\([^[:space:]][^[:space:]]*\).*/\1/p" "$m" | head -1)
3074
+ # 🟥 첫 실사용(이 훅의 첫 두-판정 커밋)이 즉시 잡은 것: 값이 `checked(확인한 것 = …)` 처럼
3075
+ # **괄호 안에 공백**을 담으면 첫 토큰이 `checked(확인한` 으로 잘려, 값도 아니고 문법도
3076
+ # 깨져 보이는 문자열이 찍힌다. 반면 `panel(codex,gemini)` · `tier2(qasp)` 는 괄호가 값의
3077
+ # 일부라 살려야 한다. 판별자는 **괄호가 닫혔는가** 하나뿐이다 — 안 닫혔으면 여는 괄호
3078
+ # 앞까지가 값이다. (마커 defeater 에 «관측될 것»으로 적어 둔 그 줄이 그대로 발화했다.)
3079
+ case "$v" in
3080
+ *'('*')'*) : ;; # panel(codex,gemini) — 괄호가 값의 일부
3081
+ *'('*) v="${v%%(*}" ;; # checked(확인한 … — 잘린 괄호는 값이 아니다
3082
+ esac
3083
+ [ -n "$v" ] || v='(없음)'
3084
+ out="${out}${out:+ · }${f}=${v}"
3085
+ done
3086
+ printf 'acceptance-evidence: %s\n' "$out"
3087
+ }
3088
+
3089
+
2719
3090
  # ── Verdict ───────────────────────────────────────────────────────────────────
2720
3091
  echo ""
2721
3092
  echo "══════════════════════════════════════════════"
@@ -2728,6 +3099,10 @@ if [ "$FAILED" -eq 1 ]; then
2728
3099
  fi
2729
3100
 
2730
3101
  echo " ✅ ALL AXES PASSED — commit allowed"
3102
+ echo " functional : PASS (Axis 1 회귀 · 레인 · 매니페스트)"
3103
+ echo " $(_acceptance_evidence_line "${MARKER:-}")"
3104
+ echo " 🟥 둘은 한 판정이 아니다 — 위는 «초록인가», 아래는 «무엇이 기록됐나»다."
3105
+ echo " 형식이 통과했다는 뜻이지 그 값이 참이라는 뜻이 아니다 (arXiv:2609.04167)."
2731
3106
  echo "══════════════════════════════════════════════"
2732
3107
  echo ""
2733
3108
  exit 0
@@ -42,6 +42,97 @@ REPO_ROOT=$(git rev-parse --show-toplevel 2>/dev/null || true)
42
42
  [ -z "$REPO_ROOT" ] && { echo " ❌ pre-push: not in a git repo — fail-closed (block)"; exit 1; }
43
43
  ZERO="0000000000000000000000000000000000000000"
44
44
  PUSH_REMOTE="${1:-origin}" # git passes the remote NAME as $1; ranges are scoped to it (see below)
45
+
46
+ # ── Push zone: a non-owner github.com remote is not a plain-push channel ─────────────────────
47
+ # CLAUDE.local.md §REST API push 계정규칙: a push under a non-owner account goes through the REST
48
+ # Contents API (new files only, never overwrite/delete) — plain `git push` is not that channel.
49
+ # That rule was correctly applied to ONE org repo and missed on ANOTHER org repo in the SAME round
50
+ # (2026-07-26) — not from ignorance, but because it was applied at the point of discovery instead
51
+ # of enumerated across every remote in play (the half-fix / propagation-boundary class).
52
+ # `scripts/push_zone_check.sh` closes the ENUMERATE half by hand, before a push round, and it
53
+ # explicitly does not block. This block closes the other half mechanically: it cannot see the whole
54
+ # round, but it CAN refuse the one push in front of it when THIS push's own remote sits outside the
55
+ # owner list — exactly the "forgot on this one" shape of the 2026-07-26 miss.
56
+ #
57
+ # Degrade direction (Surface-Class Degrade Invariant): applicability is mechanical, never guessed.
58
+ # • no owner-account list → UNCALIBRATED, plain push allowed (a guessed verdict could
59
+ # authorise the wrong channel, which is worse than no verdict at all)
60
+ # • remote host is not github.com → out of scope for this axis (the account rule is a github.com
61
+ # personal-account axis; a GHE / other host remote says so and is allowed, not silently risky)
62
+ # • remote URL does not parse into a known shape → UNMEASURED, allowed (a harness gap, not a
63
+ # policy decision — NOT the destructive-branch UNCLASSIFIABLE case below, which fails closed
64
+ # because a delete is irreversible; blocking every unfamiliar remote-URL shape here would just
65
+ # train --no-verify on this same hook's real, irreversible guards)
66
+ # • owner ∈ list → silent (the common, expected case)
67
+ # • owner ∉ list → BLOCK. Never prints the owner list itself — only the single non-matching
68
+ # owner already public in the URL being pushed to.
69
+ #
70
+ # Override (explicit, logged — same shape as DESTRUCTIVE_OP_OK / PUBLIC_SURFACE_OK):
71
+ # PUSH_ZONE_OK=1 git push …
72
+ PUSH_ZONE_SRC="${PUSH_ZONE_OWNERS:-$REPO_ROOT/.claude/rules/.push-zone-owners}"
73
+ if [ ! -f "$PUSH_ZONE_SRC" ]; then
74
+ echo " ⏭️ push-zone: UNCALIBRATED — no owner-account list at $PUSH_ZONE_SRC, plain push allowed"
75
+ else
76
+ # One owner per line. Inline comments (`owner # note`), CR line endings, surrounding whitespace and a
77
+ # trailing slash are stripped BEFORE tokenizing — cross-family (codex gpt-5.5, 2026-09-05) showed an
78
+ # inline-comment word being read as an ALLOWED owner (fail-open) and a CRLF list blocking its own owner.
79
+ _pz_owners="$(sed -e 's/#.*$//' -e 's/\r$//' -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//' -e 's#/*$##' "$PUSH_ZONE_SRC" 2>/dev/null | grep -v '^$' | tr '[:upper:]' '[:lower:]')"
80
+ _pz_url="${2:-}"
81
+ # git passes the destination's URL as $2. A bare remote NAME (no ':' and no '/') means the URL was
82
+ # not resolved for us (alias / insteadOf edge) — resolve it here rather than reading a name as a host
83
+ # and quietly landing in the "not github.com" allow branch (codex #5).
84
+ case "$_pz_url" in
85
+ *:*|*/*|"") ;;
86
+ *) _pz_url="$(git remote get-url --push "$_pz_url" 2>/dev/null || printf '%s' "$_pz_url")" ;;
87
+ esac
88
+ if [ -z "$_pz_url" ]; then
89
+ echo " ⏭️ push-zone: UNMEASURED — remote URL not supplied to the hook (\$2 empty), plain push allowed"
90
+ else
91
+ # Normalise before parsing: lowercase, trim, drop the scheme, drop userinfo (`user@`). The HOST is then
92
+ # everything up to the first ':' or '/'. This accepts every github.com shape git itself accepts —
93
+ # `https://user@github.com/o/r`, `https://github.com:443/o/r`, `git@github.com:o/r`, `git@github.com:/o/r`,
94
+ # `ssh://git@github.com:22/o/r` — instead of an allowlist of three literal prefixes that read the rest as
95
+ # "unrecognized → allow" (codex #1–#4: four real github.com shapes were failing OPEN).
96
+ _pz_url_lc="$(printf '%s' "$_pz_url" | tr '[:upper:]' '[:lower:]' | sed -e 's/[[:space:]]*$//' -e 's/^[[:space:]]*//')"
97
+ _pz_norm="$(printf '%s' "$_pz_url_lc" | sed -E 's#^[a-z][a-z0-9+.-]*://##; s#^[^@/]*@##')"
98
+ _pz_host="$(printf '%s' "$_pz_norm" | sed -E 's#[:/].*$##')"
99
+ if [ "$_pz_host" = "github.com" ]; then
100
+ _pz_path="$(printf '%s' "$_pz_norm" | sed -E 's#^github\.com(:[0-9]+)?[:/]+##')"
101
+ _pz_owner="${_pz_path%%/*}"
102
+ if [ -z "$_pz_owner" ] || [ "$_pz_path" = "$_pz_norm" ]; then
103
+ echo " ⏭️ push-zone: UNMEASURED — github.com remote in an unrecognized URL shape, plain push allowed"
104
+ else
105
+ _pz_hit=0
106
+ for _pz_o in $_pz_owners; do
107
+ [ "$_pz_owner" = "$_pz_o" ] && { _pz_hit=1; break; }
108
+ done
109
+ if [ "$_pz_hit" -eq 0 ]; then
110
+ echo ""
111
+ echo "══════════════════════════════════════════════"
112
+ echo " ⛔ FH Push-Zone Gate — remote is outside the owner-account list"
113
+ echo "══════════════════════════════════════════════"
114
+ echo " remote '$PUSH_REMOTE' → github.com/$_pz_owner"
115
+ echo " Plain git push is not this remote's channel — CLAUDE.local.md §REST API push"
116
+ echo " 계정규칙 (a non-owner account pushes via the REST Contents API instead)."
117
+ echo " Deliberate exception (explicit, logged): PUSH_ZONE_OK=1 git push …"
118
+ echo "══════════════════════════════════════════════"
119
+ if [ "${PUSH_ZONE_OK:-0}" = "1" ]; then
120
+ echo " ⚠️ FH Push-Zone: allowed by PUSH_ZONE_OK=1 (conscious, gated intent)"
121
+ mkdir -p "$REPO_ROOT/tracks/_meta" 2>/dev/null || true
122
+ printf '%s PUSH_ZONE_OK override — remote:%s owner:%s\n' \
123
+ "$(date +%Y-%m-%dT%H:%M:%S)" "$PUSH_REMOTE" "$_pz_owner" \
124
+ >> "$REPO_ROOT/tracks/_meta/.push_zone_override_log" 2>/dev/null || true
125
+ else
126
+ exit 1
127
+ fi
128
+ fi
129
+ fi
130
+ else
131
+ echo " ⏭️ push-zone: remote host is not github.com — outside this axis's scope, plain push allowed"
132
+ fi
133
+ fi
134
+ fi
135
+
45
136
  BASE="${FH_DESTRUCTIVE_BASE:-origin/main}"
46
137
  # All ancestry/reachability checks below must ignore local `git replace`/graft objects — a graft can
47
138
  # falsify merge-base/rev-list/ls-tree to make a divergent force look fast-forward or an unmerged branch
@@ -0,0 +1,49 @@
1
+ # Red-team report — ISO/IEC 42119-7 형 (템플릿, 2026-09-05)
2
+
3
+ > **쓰는 자리**: cross-family 적대 리뷰(`auto-decorrelation` Step 4~6) · `/steel-quench` Wave-1 · challenger 디스패치의 **산출을 담는 형식**.
4
+ > 새 검사가 아니라 **이미 하는 것의 보고 형식**이다 — 개발 중인 ISO/IEC AWI TS 42119-7(AI 레드티밍)의 **공개 스코프가 다루는 주제**
5
+ > (용어 · 위험 식별 · 적용 범위 · 목표/공격 벡터 · 계획/실행 방법 · 문서/보고 · 수명주기 통합 — 🟥 규범 «요구사항」은 미공개, 이건 «다루는 주제」다)
6
+ > 를 우리 산출이 이미 갖고 있으니 **이름표만 맞춘다**. §4 의 «처분」은 이 레포의 로컬 프로세스이지 표준 스코프 항목이 아니다.
7
+ > 정본 crosswalk: `knowledge/shared/harness-core/iso_ai_standards_crosswalk.md §2` (42119-7 행).
8
+ >
9
+ > 🟥 이 형식은 «잘 했다」를 증명하지 않는다. 채널이다 — 칸이 비어 있으면 안 돌린 것이고, 채워져 있어도 진위는 마커/PR 의 그라운딩이 진다.
10
+
11
+ ```
12
+ # RED-TEAM REPORT — <대상 한 줄> date: YYYY-MM-DD trigger: <4축 마커 트리거 / load-bearing gate / chamber / 요청>
13
+
14
+ ## 0. 적용 범위와 용어 (applicability · terms)
15
+ 대상 : <어느 AI 시스템/하네스의 어느 표면 — 코드·프롬프트·게이트·설정>
16
+ 적용 범위 : <이 평가가 «보는」 것과 «안 보는」 것 한 줄씩>
17
+ 용어 : <이 보고에서 S/A/B 가 뜻하는 것 · «fail-open」 등 국소 용어의 정의>
18
+
19
+ ## 1. 위험 식별 (what could go wrong — 42119-7 "identification of risks")
20
+ 표면 : <가역 / 비가역(publish·delete·history) / 소비자 가시>
21
+ 실패 모드 : <이 변경이 틀렸다면 관측될 것 — 마커 defeater: 와 같은 문장>
22
+ AI 특성 : <비결정성 · 데이터 의존 · 적응성 · 불투명성 · 자율성 중 해당 — 없으면 «없음»>
23
+
24
+ ## 2. 목표와 공격 벡터 (objectives & attack vectors)
25
+ 목표 : S = <fail-open / 유출 / 차단 회피 … 이 대상에서 S 의 정의> A = <과차단 / 오탐 / …> B = 유지보수
26
+ 공격 벡터 : <레지스트리 인용 — steel-quench Wave-1 각도 #n · 또는 프롬프트의 "attack specifically (1)…(n)" 목록 그대로>
27
+
28
+ ## 3. 방법 (methodology — 29119 프로세스 정렬)
29
+ 리뷰어 : <계열(codex/gemini/…) · 입장(cold / target-repo tier1b·tier2·tier2b·tier3) · 받은 것(diff만 / diff+주장 / 실행 출력)>
30
+ 격리 : <리뷰어가 저자 추론을 봤나 — 안 봤다 / 봤다(사유)>
31
+ 실행 : <리뷰어가 무엇을 실행했나 — 명령과 출력, 없으면 «정적»>
32
+ residency : <residency=CLEAN(files=n, stripped=m) — 스트립한 파일은 리뷰어에게 이름으로 알렸나>
33
+
34
+ ## 4. 발견 (findings — 재현 입력을 반드시 함께) + 처분(로컬 프로세스)
35
+ | # | 심각도 | 위치(file:line) | 재현 입력 | 왜 | 처분(수리 / 반박 / 잔여) | 앵커(레인 id · fail-before) |
36
+ |---|---|---|---|---|---|---|
37
+
38
+ 수리 n · 반박 n(각각 근거 한 줄) · 잔여 n(이름으로)
39
+
40
+ ## 5. 수명주기 통합 (integration into the AI system life cycle)
41
+ 어느 게이트에서 돌았나 : <pre-commit 4축 / Field-Harness Load-Bearing Change Gate / chamber SCREEN / 릴리스 전>
42
+ 재실행 조건 : <이 표면이 다시 바뀌면 / 모델·계열이 바뀌면 / N 세션 뒤>
43
+ 기록 위치 : <마커 crossfamily: · PR 본문 · fh_signal_*>
44
+ ```
45
+
46
+ **채우는 규칙**
47
+ - §4 의 «재현 입력」 이 빈 발견은 발견이 아니라 의견이다 — 그 행은 처분 «반박」으로 가거나 지운다.
48
+ - «반박」은 근거 한 줄이 있어야 한다(실측이면 실측을, 계약이면 계약 문장을).
49
+ - 이 보고는 **마커를 대체하지 않는다**. 마커 `crossfamily:` 한 줄이 이 보고의 요약이고, 보고는 그 줄의 전개다.