@chrono-meta/fh-gate 1.4.99 โ†’ 2.0.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.
Files changed (41) hide show
  1. package/.claude/capabilities/adapters/gstack-content-safety.cap +110 -0
  2. package/.claude/capabilities/adapters/mate-agent-boundary.cap +110 -0
  3. package/.claude/capabilities/adapters/qasp-new-code-anchor.cap +116 -0
  4. package/.claude/capabilities/degrade-direction-scan.cap +24 -0
  5. package/.claude/capabilities/public-surface-scan.cap +24 -0
  6. package/.claude-plugin/marketplace.json +3 -3
  7. package/AGENTS.md +9 -3
  8. package/CHEATSHEET.md +17 -1
  9. package/CLAUDE.md +2 -2
  10. package/README.ja.md +55 -20
  11. package/README.ko.md +86 -20
  12. package/README.md +57 -20
  13. package/README.zh.md +49 -19
  14. package/knowledge/shared/harness-core/fh_three_layer_canon.md +96 -5
  15. package/knowledge/shared/harness-core/field_verdict_crossfamily_gate.md +36 -0
  16. package/knowledge/shared/harness-core/harness_incubator_doctrine.md +1 -1
  17. package/knowledge/shared/harness-core/harness_terminal_correlation_and_recommendations.md +214 -0
  18. package/knowledge/shared/harness-core/harness_verification_core_extended.md +1 -1
  19. package/knowledge/shared/harness-core/ship_readiness_gate.md +29 -10
  20. package/knowledge/shared/learnings/subagent_invocations_log.yaml +73 -0
  21. package/package.json +13 -3
  22. package/plugins/fh-commons/.claude-plugin/plugin.json +1 -1
  23. package/plugins/fh-meta/.claude-plugin/plugin.json +2 -2
  24. package/plugins/fh-meta/CHANGELOG.md +45 -0
  25. package/scripts/adapters/gstack_content_safety.sh +186 -0
  26. package/scripts/adapters/mate_agent_boundary.sh +120 -0
  27. package/scripts/adapters/peer_resolve.sh +99 -0
  28. package/scripts/adapters/qasp_new_code_anchor.sh +159 -0
  29. package/scripts/branch_claim.sh +14 -0
  30. package/scripts/capability_effect_probe.sh +701 -0
  31. package/scripts/capability_registry_check.sh +313 -16
  32. package/scripts/cluster_capability_scan.sh +629 -0
  33. package/scripts/fh_session_load.sh +70 -0
  34. package/scripts/portability_lint.sh +257 -0
  35. package/scripts/publish_freshness_check.sh +204 -0
  36. package/scripts/relay_channel.sh +70 -7
  37. package/scripts/selfcheck.sh +166 -1
  38. package/scripts/test_adapter_lanes.sh +296 -0
  39. package/scripts/test_capability_entrypoint_shipping.sh +83 -6
  40. package/scripts/test_relay_channel_lanes.sh +76 -0
  41. package/templates/.git-hooks/pre-commit +14 -2
@@ -41,7 +41,14 @@ command -v node >/dev/null 2>&1 || {
41
41
  # Discover entry points by convention. A DISCOVERY of zero is an instrument failure, not a clean
42
42
  # result: this check exists precisely because the surface it guards is invisible to reference
43
43
  # walking, so "found nothing to check" must never render as "everything ships".
44
- ENTRIES=$(find scripts -maxdepth 1 -type f -name '*_capability.sh' 2>/dev/null | sort)
44
+ # ๐ŸŸฅ 2026-08-16: ๋ฐœ๊ฒฌ ๊ทœ์•ฝ์ด **ํ•œ ๋ฒŒ๋ฟ**์ด์—ˆ๋‹ค(`scripts/*_capability.sh`, maxdepth 1). ๊ฐ™์€ ๋‚ 
45
+ # ๋„์ž…๋œ **์–ด๋Œ‘ํ„ฐ ์ง„์ž…์ **(`scripts/adapters/*.sh` โ€” ๋‚จ์˜ ํ•˜๋„ค์Šค ๋Šฅ๋ ฅ์„ FH ์•ˆ์—์„œ ๋ถ€๋ฅด๋Š” ๊ธฐ๋ณธ
46
+ # ๊ฒฝ๋กœ)์€ ์ด๋ฆ„๋„ ์œ„์น˜๋„ ๊ทธ ๊ทœ์•ฝ ๋ฐ–์ด๋ผ **์ด ๊ฒ€์‚ฌ์— ๊ตฌ์กฐ์ ์œผ๋กœ ์•ˆ ๋ณด์˜€๋‹ค.** ์ฆ‰ ์–ด๋Œ‘ํ„ฐ ์Šคํฌ๋ฆฝํŠธ๊ฐ€
47
+ # `files[]` ์—์„œ ๋น ์ ธ๋„ ์ด ๊ฒ€์‚ฌ๋Š” ์ดˆ๋ก์ด์—ˆ๋‹ค โ€” ๊ฒ€์‚ฌ๊ธฐ๊ฐ€ ์ง€ํ‚ค๋ ค๋˜ ๋ฐ”๋กœ ๊ทธ ๊ฒฐํ•จ์ด ๊ฒ€์‚ฌ๊ธฐ ์ž์‹ ์˜
48
+ # ์‚ฌ๊ฐ์—์„œ ์žฌํ˜„๋  ์ˆ˜ ์žˆ์—ˆ๋‹ค. ๋‘ ๊ทœ์•ฝ์„ **ํ•ฉ์ง‘ํ•ฉ**์œผ๋กœ ๋ณธ๋‹ค(๋นผ์ง€ ๋ง๊ณ  ๋”ํ•œ๋‹ค).
49
+ ENTRIES=$( { find scripts -maxdepth 1 -type f -name '*_capability.sh' 2>/dev/null
50
+ find scripts/adapters -maxdepth 1 -type f -name '*.sh' 2>/dev/null
51
+ } | LC_ALL=C sort -u )
45
52
  n=$(printf '%s\n' "$ENTRIES" | grep -c . || true)
46
53
  n=$(( ${n:-0} + 0 ))
47
54
  if [ "$n" -eq 0 ]; then
@@ -53,6 +60,16 @@ fi
53
60
  FILES_JSON=$(node -e 'process.stdout.write(JSON.stringify(require("./package.json").files||[]))' 2>/dev/null) || {
54
61
  echo " INSTRUMENT ERROR: could not read package.json files[]"; exit 1; }
55
62
 
63
+ # โ˜…`mktemp` โ€” ์ดˆํŒ์€ `${TMPDIR:-/tmp}/cap_entry_$$.txt` ์˜€๋‹ค. TMPDIR ์—†๋Š” ํ™˜๊ฒฝ(๋ฆฌ๋ˆ…์Šค CIยท
64
+ # ์ปจํ…Œ์ด๋„ˆ๊ฐ€ ํ”ํ•˜๋‹ค)์—์„œ ๊ณต์šฉ `/tmp` + PID ๊ธฐ๋ฐ˜ **์˜ˆ์ธก ๊ฐ€๋Šฅํ•œ ์ด๋ฆ„**์ด๋ผ, ๋ฏธ๋ฆฌ ์‹ฌ์–ด๋‘” ์‹ฌ๋งํฌ๋ฅผ
65
+ # `>` ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ๊ฐ€ ๋”ฐ๋ผ๊ฐ€ ๋Œ€์ƒ ํŒŒ์ผ์„ ๋ฎ์–ด์“ด๋‹ค(CWE-377, ๋ณด์•ˆ ํŒจ์Šค [B]). ์ด ํŒŒ์ผ์€ selfcheck ์—
66
+ # ๋ฐฐ์„ ๋ผ **์†Œ๋น„์ž ๋จธ์‹ ์—์„œ `npm test` ๋กœ ๋ˆ๋‹ค**.
67
+ # โš ๏ธ `mktemp -t <prefix>` ๋Š” **BSD ์ „์šฉ**์ด๋‹ค โ€” GNU ๋Š” ํ…œํ”Œ๋ฆฟ์— X ๋ฅผ ์š”๊ตฌํ•˜๊ณ 
68
+ # `mktemp: too few X's in template` ๋กœ ์ฃฝ๋Š”๋‹ค(2026-08-16 CI ์‹ค์ธก: ๋กœ์ปฌ macOS ์ดˆ๋ก ยท ubuntu ์ ์ƒ‰).
69
+ # ์ด ๋ ˆํฌ๊ฐ€ ์ด๋ฏธ ๊ธฐ๋กํ•œ `stat -f` BSD-first ์™€ ๊ฐ™์€ ์–ผ๊ตด์ด๋‹ค. ์–‘์ชฝ์—์„œ ๋„๋Š” ํ˜•ํƒœ๋Š”
70
+ # **๋ช…์‹œ ํ…œํ”Œ๋ฆฟ + X ๋‹ค์ˆ˜**๋ฟ์ด๋‹ค. (๊ณ„๊ธฐ๊ฐ€ fail-closed ๋กœ ๋ง‰์•„ ์ค€ ๋•์— ์กฐ์šฉํžˆ ์•ˆ ์ง€๋‚˜๊ฐ”๋‹ค.)
71
+ _CAP_TMP=$(mktemp "${TMPDIR:-/tmp}/cap_entry.XXXXXXXX") || { echo " INSTRUMENT ERROR: mktemp ์‹คํŒจ"; exit 1; }
72
+ trap 'rm -f "$_CAP_TMP"' EXIT INT TERM
56
73
  printf '%s\n' "$ENTRIES" | while IFS= read -r e; do
57
74
  [ -n "$e" ] || continue
58
75
  if node -e 'const f=JSON.parse(process.argv[1]);process.exit(f.includes(process.argv[2])?0:1)' "$FILES_JSON" "$e"; then
@@ -60,14 +77,14 @@ printf '%s\n' "$ENTRIES" | while IFS= read -r e; do
60
77
  else
61
78
  echo " MISSING $e"
62
79
  fi
63
- done > "${TMPDIR:-/tmp}/cap_entry_$$.txt"
80
+ done > "$_CAP_TMP"
64
81
 
65
- missing=$(grep -c '^ MISSING ' "${TMPDIR:-/tmp}/cap_entry_$$.txt" || true)
82
+ missing=$(grep -c '^ MISSING ' "$_CAP_TMP" || true)
66
83
  missing=$(( ${missing:-0} + 0 ))
67
- shipped=$(grep -c '^ ok ' "${TMPDIR:-/tmp}/cap_entry_$$.txt" || true)
84
+ shipped=$(grep -c '^ ok ' "$_CAP_TMP" || true)
68
85
  shipped=$(( ${shipped:-0} + 0 ))
69
- cat "${TMPDIR:-/tmp}/cap_entry_$$.txt"
70
- rm -f "${TMPDIR:-/tmp}/cap_entry_$$.txt"
86
+ cat "$_CAP_TMP"
87
+ rm -f "$_CAP_TMP"
71
88
 
72
89
  if [ "$missing" -eq 0 ]; then
73
90
  ok "all $shipped capability entry point(s) are in package.json files[]"
@@ -118,6 +135,66 @@ else
118
135
  bad "$dangling declared capability entry point(s) missing from disk โ€” npm would ship a broken files[] and the disk-side check alone stays green"
119
136
  fi
120
137
 
138
+ # SIBLING DEPENDENCIES (added 2026-08-16 โ€” occurrence #4 of the class this file's header names).
139
+ # The two directions above both key on the ENTRY-POINT convention (`*_capability.sh`). That is
140
+ # structurally blind to the failure that actually happened this time: a shipped script called a
141
+ # NON-entry-point sibling that did not ship. `capability_registry_check.sh` shipped, its new M6 axis
142
+ # ran `${0%/*}/capability_effect_probe.sh`, and that probe was absent from files[] โ€” so on a consumer
143
+ # install M6 would hit its fail-closed branch and reject every registration, blaming a "missing probe"
144
+ # that exists in the repo.
145
+ #
146
+ # WHY `package_coverage_check.sh` IS BLIND TO IT โ€” and why the fix belongs here, not there: that
147
+ # checker's extraction regex requires a reference to START with a shipped top-level directory
148
+ # (`scripts|templates|bin|docs|knowledge|plugins|.claude`). A script naming its own sibling writes
149
+ # `${0%/*}/name.sh` or `$(dirname "$0")/name.sh` โ€” the path is BUILT AT RUNTIME and no literal
150
+ # `scripts/` appears. The reference-follower cannot see a path that does not textually exist.
151
+ #
152
+ # Scope kept narrow on purpose: only SELF-DIRECTORY forms. `$REPO_ROOT/scripts/x.sh` already contains
153
+ # the literal `scripts/x.sh` and is caught upstream; widening past that would duplicate that checker.
154
+ SIB_PAT='(\$\{0%/\*\}|\$\(dirname [^)]*\)|\$\{BASH_SOURCE[^}]*%/\*\})/[A-Za-z0-9_.-]+\.(sh|py|js)'
155
+ SHIPPED_SH=$(node -e '
156
+ const f=JSON.parse(process.argv[1]);
157
+ process.stdout.write(f.filter(p=>/^scripts\/.*\.sh$/.test(p)).join("\n"));
158
+ ' "$FILES_JSON")
159
+
160
+ sib_refs=0; sib_missing=0
161
+ if [ -n "$SHIPPED_SH" ]; then
162
+ while IFS= read -r s; do
163
+ [ -n "$s" ] && [ -f "$s" ] || continue
164
+ # `grep -o` yields the whole matched expression; the sibling NAME is its last path segment.
165
+ # COMMENT LINES ARE STRIPPED FIRST, and that is not cosmetic: the finding's text asserts the
166
+ # callee is "called at runtime". A file whose PROSE names a sibling (this file's own header does)
167
+ # would be reported under a claim that is false about it โ€” the right defect attributed to the
168
+ # wrong caller. Caught during this lane's own known-pair calibration, where the positive arm
169
+ # named two callers and only one of them actually calls anything.
170
+ for ref in $(sed 's/^[[:space:]]*#.*//' "$s" | grep -oE "$SIB_PAT" 2>/dev/null | sed 's|.*/||' | sort -u); do
171
+ sib_refs=$((sib_refs+1))
172
+ # Only assert on siblings that EXIST in the repo. A reference to a name that is absent from
173
+ # disk too is a different defect (a dead call), and it belongs to the dangling check above โ€”
174
+ # claiming it here would report one defect as two.
175
+ [ -f "scripts/$ref" ] || continue
176
+ if node -e 'const f=JSON.parse(process.argv[1]);process.exit(f.includes(process.argv[2])?0:1)' \
177
+ "$FILES_JSON" "scripts/$ref"; then :; else
178
+ echo " SIBLING-MISSING scripts/$ref (called by $s at runtime, absent from files[])"
179
+ sib_missing=$((sib_missing+1))
180
+ fi
181
+ done
182
+ done <<EOF
183
+ $SHIPPED_SH
184
+ EOF
185
+ fi
186
+
187
+ # Discovery-zero is an instrument failure here for the same reason as above: this repo is KNOWN to
188
+ # contain at least one self-directory sibling call (that is why this block exists). Zero means the
189
+ # pattern stopped matching โ€” a silent scanner, not a clean repo.
190
+ if [ "$sib_refs" -eq 0 ]; then
191
+ bad "INSTRUMENT DEAD โ€” zero self-directory sibling references found across $(printf '%s\n' "$SHIPPED_SH" | grep -c .) shipped scripts; the pattern is no longer matching anything"
192
+ elif [ "$sib_missing" -eq 0 ]; then
193
+ ok "all $sib_refs runtime sibling dependenc(ies) of shipped scripts are themselves in files[]"
194
+ else
195
+ bad "$sib_missing runtime sibling dependenc(ies) absent from files[] โ€” the consumer gets a caller whose callee is missing"
196
+ fi
197
+
121
198
  # CONTROL โ€” the check must be able to say NO. A checker that only ever prints ok is indistinguishable
122
199
  # from a checker that is not looking; assert the negative arm on a name that cannot be in files[].
123
200
  if node -e 'const f=JSON.parse(process.argv[1]);process.exit(f.includes(process.argv[2])?0:1)' \
@@ -578,6 +578,82 @@ A_N10=$(cap a_n10.cap "id: demo:a" "entry: bash $T/n_key_pass.sh" "verdict_chann
578
578
  rc=0; bash "$RELAY" run --cap "$A_N10" --cap "$B" --task t > "$T/on10" 2>&1 || rc=$?
579
579
  _t "N10 both ์ฑ„๋„์˜ **์ผ์น˜** ์ •์ƒ ๊ฒฝ๋กœ๊ฐ€ ์‹ค์ œ๋กœ ๋ˆ๋‹ค(๊ณผ์ฐจ๋‹จ ์•„๋‹˜)" 0 "$rc"
580
580
 
581
+ # โ”€โ”€ N11โ€“N15 ๋…ธ๋“œ๋ณ„ ํ˜ธ์ถœ ์ธ์ž ์ฑ„๋„ (`--cap-args`) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
582
+ # ๐ŸŸฅ ์ด ์ฑ„๋„์ด ์—†๋˜ ๋™์•ˆ **ํ˜ธ์ถœ ์‹œ์  ์ธ์ž๋ฅผ ์š”๊ตฌํ•˜๋Š” ๋Šฅ๋ ฅ์€ relay ๋ฅผ ํ†ต๊ณผํ•  ์ˆ˜ ์—†์—ˆ๋‹ค**
583
+ # (์‹ค์ธก 2026-08-16, pmh-dev:merge-noop-check โ†’ ํ•ญ์ƒ ARGSโ†’HARNESS_ERRORโ†’BLOCKED).
584
+ # fail-closed ๋ผ ๋ฐฉํ–ฅ์€ ์˜ณ์•˜์ง€๋งŒ ์‹ ํ˜ธ๊ฐ€ 0 ์ด์—ˆ๊ณ , ๊ทธ๊ฑด ยซํ˜ธ์ถœ ๊ฐ€๋Šฅยป ์„ ๊ตฌ์กฐ์ ์œผ๋กœ ๋ชป ๋งŒ๋“ ๋‹ค.
585
+ # ๋ ˆ์ธ์€ ๋„ค ๊ฐ€์ง€๋ฅผ ๋”ฐ๋กœ ๋‹จ์–ธํ•œ๋‹ค โ€” ๋„๋‹ฌ ยท ๊ฒฐ๋ฐ• ๋Œ€์ƒ ยท fail-closed ยท **๋น„์ฃผ์ž… ์ปจํŠธ๋กค**.
586
+ mk n_echo_args.sh '#!/usr/bin/env bash
587
+ # ๋ฐ›์€ ์œ„์น˜ ์ธ์ž๋ฅผ ๊ทธ๋Œ€๋กœ ์ฐ๊ณ , ์ฒซ ์ธ์ž๊ฐ€ "GO" ์ผ ๋•Œ๋งŒ ํ†ต๊ณผํ•œ๋‹ค.
588
+ echo "ARGS=[$*]"
589
+ [ "${1:-}" = "GO" ] && exit 0
590
+ exit 1'
591
+ ARGSCAP() { cap "$1" "id: demo:$2" "entry: bash $T/n_echo_args.sh" "verdict_channel: exit" \
592
+ "verdict_enum: 0=PASS 1=FAIL" "approval: auto" "reversibility: reversible" "residency: public" \
593
+ "degrade: fail-closed" "tier_floor: none" "writes: read-only" "judge: mechanical" \
594
+ "verdict_binding: FAIL"; }
595
+
596
+ # N11 ์ธ์ž๊ฐ€ ์‹ค์ œ๋กœ ์ง„์ž…์ ์— **๋„๋‹ฌํ•œ๋‹ค** โ€” ์—†์œผ๋ฉด FAIL ์ด์–ด์•ผ ํ•  ๋…ธ๋“œ๊ฐ€ ํ†ต๊ณผํ•œ๋‹ค
597
+ A_N11=$(ARGSCAP a_n11.cap a)
598
+ rc=0; bash "$RELAY" run --cap "$A_N11" --cap-args "GO" --cap "$B" --task t \
599
+ --record "$T/rec11" > "$T/on11" 2>&1 || rc=$?
600
+ _t "N11 --cap-args ๊ฐ€ ์ง„์ž…์ ์— ์‹ค์ œ๋กœ ๋„๋‹ฌํ•œ๋‹ค" 0 "$rc"
601
+ # โ˜… ๋‹จ์–ธ ๋Œ€์ƒ์€ relay ์˜ stdout ์ด ์•„๋‹ˆ๋ผ **๋…ธ๋“œ ์ž์‹ ์˜ ์ถœ๋ ฅ**์ด๋‹ค. relay ๋Š” ๋…ธ๋“œ stdout ์„
602
+ # ๊ธฐ๋ก ๋””๋ ‰ํ„ฐ๋ฆฌ๋กœ ๋ณด๋‚ด๋ฏ€๋กœ, relay ์ถœ๋ ฅ์— ๋Œ€๊ณ  grep ํ•˜๋ฉด ๋Š˜ ยซ์—†์Œยป ์ด ๋‚˜์™€ ๊ฑฐ์ง“ ์ ์ƒ‰์ด ๋œ๋‹ค
603
+ # (์ดˆํŒ์ด ๊ทธ๋ ‡๊ฒŒ ํ‹€๋ ธ๋‹ค โ€” ๊ณ„๊ธฐ๊ฐ€ ์•„๋‹ˆ๋ผ ๋‹จ์–ธ ์œ„์น˜๊ฐ€ ํ‹€๋ฆฐ ๊ฒฝ์šฐ).
604
+ _tout "N11b ๋ฐ›์€ ์ธ์ž๋ฅผ ์ง„์ž…์ ์ด ๊ทธ๋Œ€๋กœ ๋ณธ๋‹ค" "ARGS=\[GO\]" "$T/rec11/node1.out"
605
+
606
+ # N12 ๐ŸŸฅ **๋น„์ฃผ์ž… ์ปจํŠธ๋กค** โ€” ๊ฐ™์€ capfile ์— ์ธ์ž๋งŒ ๋นผ๋ฉด FAIL ์ด์–ด์•ผ ํ•œ๋‹ค.
607
+ # ์ด๊ฒŒ ์—†์œผ๋ฉด N11 ์€ ยซ์ธ์ž์™€ ๋ฌด๊ด€ํ•˜๊ฒŒ ๋Š˜ ํ†ต๊ณผํ•˜๋Š” ๊ณ„๊ธฐยป ์™€ ๊ตฌ๋ถ„๋˜์ง€ ์•Š๋Š”๋‹ค.
608
+ rc=0; bash "$RELAY" run --cap "$A_N11" --cap "$B" --task t \
609
+ --record "$T/rec12" > "$T/on12" 2>&1 || rc=$?
610
+ _t "N12 ์ธ์ž๋ฅผ ์•ˆ ์ฃผ๋ฉด ๊ฐ™์€ ๋…ธ๋“œ๊ฐ€ ๋ง‰ํžŒ๋‹ค(์ปจํŠธ๋กค โ€” ์ฑ„๋„์ด ์‹ค์žฌํ•œ๋‹ค๋Š” ์ฆ๊ฑฐ)" 2 "$rc"
611
+ _tout "N12b ์ธ์ž ์—†์ด ๋ถ€๋ฅด๋ฉด ์ง„์ž…์ ์ด ๋นˆ ์ธ์ž๋ฅผ ๋ณธ๋‹ค" "ARGS=\[\]" "$T/rec12/node1.out"
612
+
613
+ # N13 ๊ฒฐ๋ฐ• ๋Œ€์ƒ์€ **๊ฐ€์žฅ ์ตœ๊ทผ --cap** ์ด๋‹ค โ€” ์œ„์น˜ ์ธ๋ฑ์Šค๋กœ ์ง์ง€์œผ๋ฉด ์กฐ์šฉํžˆ ๋‹ค๋ฅธ ๋…ธ๋“œ์— ๋ถ™๋Š”๋‹ค
614
+ A_N13=$(ARGSCAP a_n13.cap a)
615
+ rc=0; bash "$RELAY" run --cap "$B" --cap "$A_N13" --cap-args "GO" --task t > "$T/on13" 2>&1 || rc=$?
616
+ _t "N13 ์ธ์ž๊ฐ€ ยซ๊ฐ€์žฅ ์ตœ๊ทผ --capยป ์— ๊ฒฐ๋ฐ•๋œ๋‹ค(์ฒซ ๋…ธ๋“œ๋กœ ์ƒˆ์ง€ ์•Š๋Š”๋‹ค)" 0 "$rc"
617
+
618
+ # N14 `--cap` ์—†์ด ๋จผ์ € ์˜ค๋ฉด fail-closed โ€” ์–ด๋”” ๋ถ™์ผ์ง€ ๋ชจ๋ฅด๋Š” ์ธ์ž๋ฅผ ์กฐ์šฉํžˆ ๋ฒ„๋ฆฌ์ง€ ์•Š๋Š”๋‹ค
619
+ rc=0; bash "$RELAY" run --cap-args "GO" --cap "$A_N11" --cap "$B" --task t > "$T/on14" 2>&1 || rc=$?
620
+ _t "N14 --cap ์•ž์˜ --cap-args ๋Š” fail-closed(๋ฌด์Œ ํ๊ธฐ ์•„๋‹˜)" 10 "$rc"
621
+
622
+ # N15 ์…ธ ๋ฉ”ํƒ€๋ฌธ์ž ๊ฑฐ๋ถ€ โ€” ์ง„์ž…์ ์ด ๋‹จ์–ด๋ถ„ํ•ด๋กœ ์‹คํ–‰๋˜๋ฏ€๋กœ ํ”„๋กœ๋ธŒ์™€ ๊ฐ™์€ ๊ทœ์น™์„ ์“ด๋‹ค
623
+ rc=0; bash "$RELAY" run --cap "$A_N11" --cap-args 'GO; rm -rf /tmp/x' --cap "$B" --task t > "$T/on15" 2>&1 || rc=$?
624
+ _t "N15 --cap-args ์˜ ์…ธ ๋ฉ”ํƒ€๋ฌธ์ž๋Š” ๊ฑฐ๋ถ€๋œ๋‹ค" 10 "$rc"
625
+
626
+ # โ”€โ”€ N16โ€“N18 cross-family 3๊ฑด(2026-08-16) โ€” ์ „๋ถ€ ์† ์žฌํ˜„ ํ›„ ์•ต์ปค โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
627
+ # ์…‹ ๋‹ค ใ€Œ์กฐ์šฉํžˆ ์ž˜๋ชป๋˜๋Š”ใ€ ํ˜•ํƒœ๋‹ค: ๊ฐ’์ด ์‚ฌ๋ผ์ง€๊ฑฐ๋‚˜, ๊ฒฐ๋ฐ•์ด ์ฃฝ๊ฑฐ๋‚˜, ์ธ์ž ๊ฒฝ๊ณ„๊ฐ€ ๋ฐ”๋€๋‹ค.
628
+ A_N16=$(ARGSCAP a_n16.cap a)
629
+ rc=0; bash "$RELAY" run --cap "$A_N16" --cap-args "FIRST" --cap-args "GO" --cap "$B" --task t > "$T/on16" 2>&1 || rc=$?
630
+ _t "N16 ๊ฐ™์€ --cap ์— --cap-args ๋‘ ๋ฒˆ์€ fail-closed(์•ž ๊ฐ’ ๋ฌด์Œ ํ๊ธฐ ๊ธˆ์ง€)" 10 "$rc"
631
+
632
+ # N17 ๊ฒฐ๋ฐ• ํ”Œ๋ž˜๊ทธ์™€์˜ ์ถฉ๋Œ โ€” `--cap-args --` ๋กœ `--upstream-verdict` ๋ฅผ ์œ„์น˜์ธ์ž๋กœ ๋ฐ€์–ด๋‚ผ ์ˆ˜ ์žˆ์—ˆ๋‹ค
633
+ A_N17=$(cap a_n17.cap "id: demo:a" "entry: bash $T/n_echo_args.sh" "upstream_argv: yes" \
634
+ "verdict_channel: exit" "verdict_enum: 0=PASS 1=FAIL" \
635
+ "approval: auto" "reversibility: reversible" "residency: public" "degrade: fail-closed" \
636
+ "tier_floor: none" "writes: read-only" "judge: mechanical" "verdict_binding: FAIL")
637
+ rc=0; bash "$RELAY" run --cap "$A_N17" --cap-args "GO" --cap "$B" --task t > "$T/on17" 2>&1 || rc=$?
638
+ _t "N17 upstream_argv ๋…ธ๋“œ์— --cap-args ๋ฅผ ๋ถ™์ด๋ฉด fail-closed(๊ฒฐ๋ฐ•์ด ์กฐ์šฉํžˆ ์ฃฝ์ง€ ์•Š๋Š”๋‹ค)" 10 "$rc"
639
+ # N17b ์ปจํŠธ๋กค โ€” upstream_argv ๋ฅผ **์„ ์–ธํ•˜์ง€ ์•Š์€** ๋…ธ๋“œ๋Š” ์ข…์ „๋Œ€๋กœ ํ†ต๊ณผํ•ด์•ผ ํ•œ๋‹ค(๊ณผ์ฐจ๋‹จ ๋ฐฉ์ง€)
640
+ rc=0; bash "$RELAY" run --cap "$A_N16" --cap-args "GO" --cap "$B" --task t > "$T/on17b" 2>&1 || rc=$?
641
+ _t "N17b ์ปจํŠธ๋กค โ€” upstream_argv ๋ฏธ์„ ์–ธ ๋…ธ๋“œ๋Š” --cap-args ๋ฅผ ์ •์ƒ ์ˆ˜์šฉ" 0 "$rc"
642
+
643
+ # N18 ๊ฐœํ–‰ โ€” ์ดˆํŒ ๊ฐ€๋“œ๋ฅผ ํ†ต๊ณผํ•ด **ํ•œ ์ธ์ž๊ฐ€ ๋‘ ์ธ์ž๋กœ ์ชผ๊ฐœ์กŒ๋‹ค**($'GO\nSECOND' โ†’ ARGC=2)
644
+ rc=0; bash "$RELAY" run --cap "$A_N16" --cap-args "$(printf 'GO\nSECOND')" --cap "$B" --task t > "$T/on18" 2>&1 || rc=$?
645
+ _t "N18 --cap-args ์˜ ๊ฐœํ–‰์€ ๊ฑฐ๋ถ€๋œ๋‹ค(์ธ์ž ๊ฒฝ๊ณ„ ๋ฌด์Œ ๋ณ€ํ˜• ์ฐจ๋‹จ)" 10 "$rc"
646
+ # N18b ์ปจํŠธ๋กค โ€” **์ •์ƒ ๋‹ค์ค‘ ์ธ์ž**(๊ณต๋ฐฑ ๊ตฌ๋ถ„)๋Š” ๊ณ„์† ํ†ต๊ณผํ•ด์•ผ ํ•œ๋‹ค. ์•ˆ ๊ทธ๋Ÿฌ๋ฉด N18 ์€
647
+ # ใ€Œ์ธ์ž๋ฅผ ์—ฌ๋Ÿฌ ๊ฐœ ๋ชป ์ฃผ๋Š” ๊ณ„๊ธฐใ€์™€ ๊ตฌ๋ถ„๋˜์ง€ ์•Š๋Š”๋‹ค.
648
+ A_N18=$(cap a_n18.cap "id: demo:a" "entry: bash $T/n_echo_args.sh" "verdict_channel: exit" \
649
+ "verdict_enum: 0=PASS 1=FAIL" "approval: auto" "reversibility: reversible" "residency: public" \
650
+ "degrade: fail-closed" "tier_floor: none" "writes: read-only" "judge: mechanical" \
651
+ "verdict_binding: FAIL")
652
+ rc=0; bash "$RELAY" run --cap "$A_N18" --cap-args "GO SECOND THIRD" --cap "$B" --task t \
653
+ --record "$T/rec18" > "$T/on18b" 2>&1 || rc=$?
654
+ _t "N18b ์ปจํŠธ๋กค โ€” ๊ณต๋ฐฑ ๊ตฌ๋ถ„ ๋‹ค์ค‘ ์ธ์ž๋Š” ์ •์ƒ ํ†ต๊ณผ" 0 "$rc"
655
+ _tout "N18b2 ์„ธ ์ธ์ž๊ฐ€ ๊ทธ๋Œ€๋กœ ๋„๋‹ฌํ•œ๋‹ค" "ARGS=\[GO SECOND THIRD\]" "$T/rec18/node1.out"
656
+
581
657
  printf '\nโ”€โ”€ relay_channel lanes: %d PASS / %d FAIL โ”€โ”€\n' "$pass" "$fail"
582
658
  [ "$fail" -eq 0 ] || exit 1
583
659
  exit 0
@@ -284,8 +284,20 @@ else
284
284
  psa_load "$REPO_ROOT/.claude/rules/.public-surface-patterns.defaults" \
285
285
  "${PSA_PATTERNS:-$REPO_ROOT/.claude/rules/.public-surface-patterns}"
286
286
  if [ "$PSA_OVERRIDE_PRESENT" -eq 0 ]; then
287
- echo " โš ๏ธ operator pattern override absent โ€” only committed defaults active (home paths)."
288
- echo " Populate .claude/rules/.public-surface-patterns (gitignored) for company-specific tokens."
287
+ # 2026-08-16 weekly-audit residual โ€” operator decision: LOUDER WARNING, not fail-closed.
288
+ # A hard block here would fail every fresh clone's / CI's / worktree's first commit (the
289
+ # override is gitignored by construction), training PUBLIC_SURFACE_OK into a reflex โ€” the same
290
+ # override-habituation this repo has measured and warned against repeatedly. So the degrade
291
+ # direction stays PASS; what changed is that the old two-line notice was easy to scroll past in
292
+ # a green hook run. This is now impossible to miss, still non-blocking.
293
+ echo ""
294
+ echo " ๐ŸŸง๐ŸŸง๐ŸŸง CONFIDENTIALITY COVERAGE REDUCED โ€” READ BEFORE TRUSTING THIS PASS ๐ŸŸง๐ŸŸง๐ŸŸง"
295
+ echo " โš ๏ธ operator pattern override ABSENT โ€” only committed defaults are active."
296
+ echo " Company/employer-name and real-username token classes are UNSCANNED this run."
297
+ echo " A clean PASS below reflects the defaults-only pattern set, not the full one."
298
+ echo " Fix: populate .claude/rules/.public-surface-patterns (gitignored, one severity<TAB>regex per line)."
299
+ echo " ๐ŸŸง๐ŸŸง๐ŸŸง โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ ๐ŸŸง๐ŸŸง๐ŸŸง"
300
+ echo ""
289
301
  fi
290
302
  if [ "$PSA_DEFAULTS_OK" -eq 0 ] || [ "$PSA_BAD_ROWS" -gt 0 ] \
291
303
  || [ -z "$(printf '%s' "$PSA_STREAM" | grep -vE '^[[:space:]]*(#|$)' || true)" ]; then