@chrono-meta/fh-gate 3.0.0 → 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 (111) 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 +50 -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 +12 -2
  8. package/README.ja.md +51 -7
  9. package/README.ko.md +48 -7
  10. package/README.md +37 -5
  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 +30 -10
  17. package/knowledge/shared/harness-core/field_verdict_crossfamily_gate.md +8 -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 +20 -0
  21. package/knowledge/shared/learnings/subagent_invocations_log.yaml +441 -4
  22. package/package.json +40 -2
  23. package/plugins/fh-commons/.claude-plugin/plugin.json +1 -1
  24. package/plugins/fh-commons/skills/preprep/README.md +4 -1
  25. package/plugins/fh-commons/skills/preprep/SKILL.md +99 -3
  26. package/plugins/fh-commons/skills/preprep/diagram_from_json.py +154 -0
  27. package/plugins/fh-commons/skills/preprep/fixtures/fixture_R3_negative.pptx +0 -0
  28. package/plugins/fh-commons/skills/preprep/fixtures/fixture_R3_positive.pptx +0 -0
  29. package/plugins/fh-commons/skills/preprep/fixtures/mk_slide_fixtures.py +179 -0
  30. package/plugins/fh-commons/skills/preprep/interslide_deps.py +98 -9
  31. package/plugins/fh-commons/skills/preprep/lane_adjacent_dup.py +4 -1
  32. package/plugins/fh-commons/skills/preprep/lane_diagram.py +95 -0
  33. package/plugins/fh-commons/skills/preprep/lane_geometry.py +181 -0
  34. package/plugins/fh-commons/skills/preprep/lane_promise.py +4 -1
  35. package/plugins/fh-commons/skills/preprep/lane_slide_refs.py +134 -0
  36. package/plugins/fh-commons/skills/preprep/lane_slide_relations.py +300 -0
  37. package/plugins/fh-commons/skills/preprep/preprep.py +129 -8
  38. package/plugins/fh-commons/skills/preprep/preprep_wire.py +210 -0
  39. package/plugins/fh-commons/skills/preprep/presentation_checklist.md +4 -0
  40. package/plugins/fh-commons/skills/preprep/surfaces.example.yaml +18 -0
  41. package/plugins/fh-commons/skills/preprep/test_preprep_lanes_rp.py +184 -0
  42. package/plugins/fh-meta/.claude-plugin/plugin.json +1 -1
  43. package/plugins/fh-meta/CHANGELOG.md +43 -1
  44. package/plugins/fh-meta/skills/auto-decorrelation/SKILL.md +87 -2
  45. package/plugins/fh-meta/skills/frontier-digest/SKILL.md +2 -2
  46. package/plugins/fh-meta/skills/frontier-digest/SKILL_detail.md +25 -5
  47. package/plugins/fh-meta/skills/hub-cc-pr-reviewer/SKILL.md +11 -0
  48. package/plugins/fh-meta/skills/hub-cc-pr-reviewer/SKILL_detail.md +3 -0
  49. package/plugins/fh-qp/.claude-plugin/plugin.json +22 -0
  50. package/plugins/fh-qp/README.md +71 -0
  51. package/plugins/fh-qp/fixtures/evidence_known_clean.txt +3 -0
  52. package/plugins/fh-qp/fixtures/evidence_known_dirty.txt +6 -0
  53. package/plugins/fh-qp/fixtures/reach_known_partial.tsv +4 -0
  54. package/plugins/fh-qp/fixtures/reach_known_reached.tsv +4 -0
  55. package/plugins/fh-qp/fixtures/reach_known_wall.tsv +3 -0
  56. package/plugins/fh-qp/fixtures/verdicts_known_bad_branch.tsv +2 -0
  57. package/plugins/fh-qp/fixtures/verdicts_known_bad_vacuous_machine.tsv +2 -0
  58. package/plugins/fh-qp/fixtures/verdicts_known_good.tsv +4 -0
  59. package/plugins/fh-qp/fixtures/verdicts_verify_only.tsv +3 -0
  60. package/plugins/fh-qp/qp_profile.example.yaml +29 -0
  61. package/plugins/fh-qp/scripts/qp_tools.sh +216 -0
  62. package/plugins/fh-qp/skills/qp/SKILL.md +65 -0
  63. package/plugins/fh-qp/skills/qp-plan/SKILL.md +42 -0
  64. package/plugins/fh-qp/skills/qp-regress/SKILL.md +45 -0
  65. package/plugins/fh-qp/skills/qp-run/SKILL.md +49 -0
  66. package/scripts/chamber_run.sh +14 -5
  67. package/scripts/com.forge-harness.live-eval.plist +84 -0
  68. package/scripts/compaction_probe.sh +9 -35
  69. package/scripts/directional_diff_gate.sh +14 -2
  70. package/scripts/frontier_digest_autopilot.sh +4 -1
  71. package/scripts/map_postprocess.py +90 -0
  72. package/scripts/outbound_query_guard.sh +131 -0
  73. package/scripts/outbound_query_hook.sh +373 -0
  74. package/scripts/package_coverage_check.sh +55 -16
  75. package/scripts/pipe_verdict_guard.sh +41 -1
  76. package/scripts/probe_live_eval.sh +240 -0
  77. package/scripts/probe_live_eval_lib.py +579 -0
  78. package/scripts/proposal_hook.sh +120 -17
  79. package/scripts/push_zone_check.sh +78 -0
  80. package/scripts/residency_closure_scan.py +252 -0
  81. package/scripts/selfcheck.sh +41 -1
  82. package/scripts/session_close_check.sh +100 -0
  83. package/scripts/sim_isolated_run.sh +98 -2
  84. package/scripts/test_action_yml_lanes.sh +97 -0
  85. package/scripts/test_fh_qp_lanes.sh +105 -0
  86. package/scripts/test_gate_two_verdicts_lanes.sh +139 -0
  87. package/scripts/test_map_postprocess_lanes.sh +143 -0
  88. package/scripts/test_marker_affected_lanes.sh +93 -0
  89. package/scripts/test_marker_crossfamily_lanes.sh +90 -6
  90. package/scripts/test_marker_oracle_lanes.sh +136 -0
  91. package/scripts/test_outbound_query_hook_lanes.sh +433 -0
  92. package/scripts/test_outbound_query_lanes.sh +87 -0
  93. package/scripts/test_pipe_verdict_guard_lanes.sh +26 -0
  94. package/scripts/test_preprep_diagram_lanes.sh +87 -0
  95. package/scripts/test_preprep_drift_anchor.sh +3 -3
  96. package/scripts/test_preprep_slide_refs_lanes.sh +169 -0
  97. package/scripts/test_probe_live_eval_lanes.sh +437 -0
  98. package/scripts/test_proposal_hook_lanes.sh +22 -1
  99. package/scripts/test_push_zone_lanes.sh +304 -0
  100. package/scripts/test_residency_closure_lanes.sh +70 -0
  101. package/scripts/test_sim_isolated_run_lanes.sh +119 -0
  102. package/scripts/test_utterance_intake_lanes.sh +414 -0
  103. package/scripts/test_worktree_reclaim_lanes.sh +70 -0
  104. package/scripts/transcript_utterances.py +222 -0
  105. package/scripts/utterance_intake.sh +424 -0
  106. package/scripts/validate_yaml.sh +27 -0
  107. package/scripts/worktree_reclaim.sh +95 -0
  108. package/templates/.git-hooks/pre-commit +353 -1
  109. package/templates/.git-hooks/pre-push +91 -0
  110. package/templates/RED_TEAM_REPORT.md +49 -0
  111. package/templates/settings.PreToolUse.snippet.json +65 -1
@@ -133,6 +133,33 @@ if [ "$_skills" -eq 0 ] || [ "$_agents" -eq 0 ]; then
133
133
  fi
134
134
  echo " (scanned: $_skills skill(s), $_agents agent(s))"
135
135
 
136
+ # ── Whole-file YAML documents that machinery READS (not frontmatter) ─────────────────────────
137
+ # The dispatch ledger is parsed by session_close_check ④-e / activity_log / round/gatecheck_qset and
138
+ # by the 60/40 promotion gate. Measured 2026-09-04: PR #611 merged an entry whose unquoted scalar
139
+ # carried `: ` (`… API error: 529»`) and the file had been UNPARSEABLE at HEAD since — while its own
140
+ # marker cited "validate_yaml 레인" as the live control. That lane never read this file: it checks
141
+ # SKILL/agent FRONTMATTER only. This block closes that gap for the files listed here. Known pair at
142
+ # wiring time: the #611 HEAD ledger → ❌ (line 3090), the quoted fix → ✅.
143
+ _LEDGERS="knowledge/shared/learnings/subagent_invocations_log.yaml"
144
+ for f in $_LEDGERS; do
145
+ [ -f "$f" ] || { echo " ❌ ledger missing: $f (not found ≠ empty)"; ERRORS=$((ERRORS + 1)); continue; }
146
+ if [ -z "$PARSER" ]; then
147
+ echo " ⚠️ $f: UNCALIBRATED — no YAML parser, NOT verified"; UNCALIBRATED=$((UNCALIBRATED + 1)); continue
148
+ fi
149
+ out=$(python3 - "$f" <<'PY' 2>&1
150
+ import sys, yaml
151
+ try:
152
+ d = yaml.safe_load(open(sys.argv[1], encoding='utf-8'))
153
+ except yaml.YAMLError as e:
154
+ print('PARSE ' + str(e).replace('\n', ' ')[:160]); sys.exit(1)
155
+ if not isinstance(d, list) or not d:
156
+ print('NOTLIST-OR-EMPTY'); sys.exit(2)
157
+ print('OK %d entries' % len(d))
158
+ PY
159
+ ); rc=$?
160
+ if [ "$rc" -eq 0 ]; then echo " ✅ $f: $out"; else echo " ❌ $f: $out"; ERRORS=$((ERRORS + 1)); fi
161
+ done
162
+
136
163
  echo ""
137
164
  if [ "$UNCALIBRATED" -gt 0 ]; then
138
165
  echo " ⚠️ UNCALIBRATED — $UNCALIBRATED skill(s) unverified (no parser). Not a pass."
@@ -0,0 +1,95 @@
1
+ #!/usr/bin/env bash
2
+ # worktree_reclaim.sh — reclaim gitignored `tracks/**` artifacts from a git worktree BEFORE it is removed.
3
+ #
4
+ # WHY (N=2, so a script — 2026-09-02 signal + 2026-09-05 push-zone dispatch): the Destructive-Op gate
5
+ # enumerates COMMITS, and `tracks/**` is gitignored, so a worktree removal can delete a session's only
6
+ # copy of a signal file / governance log / dispatch report without any gate ever seeing it. Both
7
+ # incidents lost real files (51-line signal · a day's governance log). The manual recipe that replaced
8
+ # them — `diff -rq <wt>/tracks tracks | grep "^Only in <wt>"` → list to a FILE → copy → only then
9
+ # remove — is exactly what this script does, in that order, because after the copy «never existed»
10
+ # and «already moved» are indistinguishable (the list file is the evidence).
11
+ #
12
+ # USAGE
13
+ # bash scripts/worktree_reclaim.sh <worktree-path> # enumerate only (writes the list file)
14
+ # bash scripts/worktree_reclaim.sh <worktree-path> --apply # enumerate + copy + verify
15
+ #
16
+ # EXIT 0 = nothing to reclaim, or everything reclaimed and byte-verified
17
+ # 1 = files exist on BOTH sides with different content — a human must merge (listed, not copied)
18
+ # 2 = usage / not a worktree of a main checkout
19
+ # 10 = harness error (list file unwritable, copy failed verification)
20
+ #
21
+ # NEVER removes the worktree. Removal stays an explicit `git worktree remove` after this exits 0.
22
+ set -uo pipefail
23
+ # bash 3.2 + `set -u`: expanding an EMPTY array with "${a[@]}" is a fatal unbound-variable error, so every
24
+ # array loop below uses the ${a[@]+"${a[@]}"} idiom (measured on the first fixture run, 2026-09-05).
25
+
26
+ WT="${1:-}"; MODE="${2:-}"
27
+ [ -n "$WT" ] || { echo "usage: $0 <worktree-path> [--apply]" >&2; exit 2; }
28
+ [ -d "$WT" ] || { echo "🟥 not a directory: $WT" >&2; exit 2; }
29
+ case "$MODE" in ''|--apply) ;; *) echo "usage: $0 <worktree-path> [--apply]" >&2; exit 2 ;; esac
30
+
31
+ COMMON="$(git -C "$WT" rev-parse --git-common-dir 2>/dev/null)" || { echo "🟥 not a git worktree: $WT" >&2; exit 2; }
32
+ GITDIR="$(git -C "$WT" rev-parse --git-dir 2>/dev/null)"
33
+ case "$COMMON" in /*) ;; *) COMMON="$WT/$COMMON" ;; esac
34
+ case "$GITDIR" in /*) ;; *) GITDIR="$WT/$GITDIR" ;; esac
35
+ COMMON="$(cd "$COMMON" && pwd -P)"; GITDIR="$(cd "$GITDIR" && pwd -P)"
36
+ if [ "$COMMON" = "$GITDIR" ]; then
37
+ echo "🟥 $WT is the MAIN checkout (git-dir == git-common-dir), not a worktree — nothing to reclaim from itself" >&2; exit 2
38
+ fi
39
+ ROOT="$(dirname "$COMMON")" # main checkout root = parent of the shared .git
40
+ WT="$(cd "$WT" && pwd -P)"
41
+ SRC="$WT/tracks"; DST="$ROOT/tracks"
42
+ [ -d "$SRC" ] || { echo "ℹ️ no tracks/ in worktree — nothing to reclaim (rc=0)"; exit 0; }
43
+ mkdir -p "$DST/_meta/dispatch" 2>/dev/null || { echo "🟥 cannot create $DST/_meta/dispatch" >&2; exit 10; }
44
+
45
+ NAME="$(basename "$WT")"; TS="$(date +%Y%m%d-%H%M%S)"
46
+ LIST="$DST/_meta/dispatch/reclaim_${NAME}_${TS}.txt"
47
+
48
+ # 1. ENUMERATE — the list lands in a file FIRST (the evidence that survives the copy)
49
+ ONLY=(); DIFF=()
50
+ while IFS= read -r line; do
51
+ case "$line" in
52
+ "Only in $SRC"*)
53
+ d="${line#Only in }"; dir="${d%%: *}"; f="${d#*: }"
54
+ rel="${dir#$SRC}"; rel="${rel#/}"
55
+ p="${rel:+$rel/}$f"
56
+ if [ -d "$SRC/$p" ]; then
57
+ while IFS= read -r ff; do ONLY+=("${ff#$SRC/}"); done < <(find "$SRC/$p" -type f)
58
+ else
59
+ ONLY+=("$p")
60
+ fi ;;
61
+ "Files $SRC"*" differ") DIFF+=("$(printf '%s' "${line#Files }" | sed "s# and .*##; s#^$SRC/##")") ;;
62
+ esac
63
+ done < <(diff -rq "$SRC" "$DST" 2>/dev/null)
64
+
65
+ {
66
+ echo "# worktree_reclaim — $WT → $ROOT ($TS)"
67
+ echo "# only-in-worktree: ${#ONLY[@]} differ-both-sides: ${#DIFF[@]} mode: ${MODE:-enumerate}"
68
+ for p in ${ONLY[@]+"${ONLY[@]}"}; do echo "ONLY $p"; done
69
+ for p in ${DIFF[@]+"${DIFF[@]}"}; do echo "DIFF $p"; done
70
+ } > "$LIST" || { echo "🟥 cannot write list file $LIST" >&2; exit 10; }
71
+ echo "── worktree_reclaim: $NAME ──"
72
+ echo " only-in-worktree: ${#ONLY[@]} · differ-both-sides: ${#DIFF[@]} · list: ${LIST#$ROOT/}"
73
+ for p in ${ONLY[@]+"${ONLY[@]}"}; do echo " ONLY $p"; done
74
+ for p in ${DIFF[@]+"${DIFF[@]}"}; do echo " DIFF $p (both sides exist and differ — NOT copied, merge by hand)"; done
75
+
76
+ # 2. RECLAIM (--apply) — copy, then verify byte-identical; a copy that cannot be verified is a harness error
77
+ if [ "$MODE" = "--apply" ] && [ "${#ONLY[@]}" -gt 0 ]; then
78
+ bad=0
79
+ for p in ${ONLY[@]+"${ONLY[@]}"}; do
80
+ mkdir -p "$(dirname "$DST/$p")" && cp -p "$SRC/$p" "$DST/$p" && cmp -s "$SRC/$p" "$DST/$p" \
81
+ && echo " ✅ reclaimed $p" || { echo " 🟥 copy/verify FAILED $p"; bad=$((bad+1)); }
82
+ done
83
+ [ "$bad" -eq 0 ] || { echo "🟥 $bad file(s) not reclaimed — do NOT remove the worktree" >&2; exit 10; }
84
+ fi
85
+
86
+ if [ "${#DIFF[@]}" -gt 0 ]; then
87
+ echo "⚠️ ${#DIFF[@]} file(s) differ on both sides — reconcile by hand before removing the worktree (rc=1)"
88
+ exit 1
89
+ fi
90
+ if [ "${#ONLY[@]}" -gt 0 ] && [ "$MODE" != "--apply" ]; then
91
+ echo "ℹ️ ${#ONLY[@]} file(s) exist only in the worktree — re-run with --apply to copy them (rc=1 until reclaimed)"
92
+ exit 1
93
+ fi
94
+ echo "✅ nothing left only in the worktree — safe to: git worktree remove $WT"
95
+ exit 0
@@ -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) ──────────────────────────────────────────────
@@ -2057,6 +2368,8 @@ if [ "$GATE_MODE" = "full" ]; then
2057
2368
  # ([[feedback_built_but_not_wired]] — 오늘만 다섯 번째 얼굴).
2058
2369
  validate_soul_tenet_refs "$MARKER" || _SOUL_OK=0
2059
2370
  validate_defeater_leg "$MARKER" || _SOUL_OK=0
2371
+ validate_affected_leg "$MARKER" || _SOUL_OK=0
2372
+ validate_oracle_leg "$MARKER" || _SOUL_OK=0
2060
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
2061
2374
  echo " ✅ PASS (marker confirmed + floor/axes fields valid:"
2062
2375
  echo " .axes_23_passed_${BRANCH_SLUG}_${TODAY}.marker)"
@@ -2739,6 +3052,41 @@ else
2739
3052
  echo " ⚠️ 상주 유입 게이트 미설치 (scripts/residency_admission_check.sh) — skipped, not passed"
2740
3053
  fi
2741
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
+
2742
3090
  # ── Verdict ───────────────────────────────────────────────────────────────────
2743
3091
  echo ""
2744
3092
  echo "══════════════════════════════════════════════"
@@ -2751,6 +3099,10 @@ if [ "$FAILED" -eq 1 ]; then
2751
3099
  fi
2752
3100
 
2753
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)."
2754
3106
  echo "══════════════════════════════════════════════"
2755
3107
  echo ""
2756
3108
  exit 0