@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.
- package/.claude/regression/probes_live.yaml +137 -0
- package/.claude/rules/.residency-patterns.defaults +7 -0
- package/.claude/rules/fh_4axis_gate.md +50 -1
- package/.claude-plugin/marketplace.json +8 -2
- package/AGENTS.md +27 -0
- package/CATALOG.md +17 -0
- package/CLAUDE.md +12 -2
- package/README.ja.md +51 -7
- package/README.ko.md +48 -7
- package/README.md +37 -5
- package/README.zh.md +45 -8
- package/docs/STANDARDS_ALIGNMENT.md +61 -0
- package/docs/USER_GUIDE.md +3 -0
- package/docs/USE_CASES.md +50 -0
- package/docs/model_tier_expectations.md +60 -0
- package/knowledge/shared/harness-core/fh_three_layer_canon.md +30 -10
- package/knowledge/shared/harness-core/field_verdict_crossfamily_gate.md +8 -0
- package/knowledge/shared/harness-core/harness_incubator_doctrine.md +9 -0
- package/knowledge/shared/harness-core/iso_ai_standards_crosswalk.md +139 -0
- package/knowledge/shared/harness-core/measurement-integrity-checklist.md +20 -0
- package/knowledge/shared/learnings/subagent_invocations_log.yaml +441 -4
- package/package.json +40 -2
- package/plugins/fh-commons/.claude-plugin/plugin.json +1 -1
- package/plugins/fh-commons/skills/preprep/README.md +4 -1
- package/plugins/fh-commons/skills/preprep/SKILL.md +99 -3
- package/plugins/fh-commons/skills/preprep/diagram_from_json.py +154 -0
- package/plugins/fh-commons/skills/preprep/fixtures/fixture_R3_negative.pptx +0 -0
- package/plugins/fh-commons/skills/preprep/fixtures/fixture_R3_positive.pptx +0 -0
- package/plugins/fh-commons/skills/preprep/fixtures/mk_slide_fixtures.py +179 -0
- package/plugins/fh-commons/skills/preprep/interslide_deps.py +98 -9
- package/plugins/fh-commons/skills/preprep/lane_adjacent_dup.py +4 -1
- package/plugins/fh-commons/skills/preprep/lane_diagram.py +95 -0
- package/plugins/fh-commons/skills/preprep/lane_geometry.py +181 -0
- package/plugins/fh-commons/skills/preprep/lane_promise.py +4 -1
- package/plugins/fh-commons/skills/preprep/lane_slide_refs.py +134 -0
- package/plugins/fh-commons/skills/preprep/lane_slide_relations.py +300 -0
- package/plugins/fh-commons/skills/preprep/preprep.py +129 -8
- package/plugins/fh-commons/skills/preprep/preprep_wire.py +210 -0
- package/plugins/fh-commons/skills/preprep/presentation_checklist.md +4 -0
- package/plugins/fh-commons/skills/preprep/surfaces.example.yaml +18 -0
- package/plugins/fh-commons/skills/preprep/test_preprep_lanes_rp.py +184 -0
- package/plugins/fh-meta/.claude-plugin/plugin.json +1 -1
- package/plugins/fh-meta/CHANGELOG.md +43 -1
- package/plugins/fh-meta/skills/auto-decorrelation/SKILL.md +87 -2
- package/plugins/fh-meta/skills/frontier-digest/SKILL.md +2 -2
- package/plugins/fh-meta/skills/frontier-digest/SKILL_detail.md +25 -5
- package/plugins/fh-meta/skills/hub-cc-pr-reviewer/SKILL.md +11 -0
- package/plugins/fh-meta/skills/hub-cc-pr-reviewer/SKILL_detail.md +3 -0
- package/plugins/fh-qp/.claude-plugin/plugin.json +22 -0
- package/plugins/fh-qp/README.md +71 -0
- package/plugins/fh-qp/fixtures/evidence_known_clean.txt +3 -0
- package/plugins/fh-qp/fixtures/evidence_known_dirty.txt +6 -0
- package/plugins/fh-qp/fixtures/reach_known_partial.tsv +4 -0
- package/plugins/fh-qp/fixtures/reach_known_reached.tsv +4 -0
- package/plugins/fh-qp/fixtures/reach_known_wall.tsv +3 -0
- package/plugins/fh-qp/fixtures/verdicts_known_bad_branch.tsv +2 -0
- package/plugins/fh-qp/fixtures/verdicts_known_bad_vacuous_machine.tsv +2 -0
- package/plugins/fh-qp/fixtures/verdicts_known_good.tsv +4 -0
- package/plugins/fh-qp/fixtures/verdicts_verify_only.tsv +3 -0
- package/plugins/fh-qp/qp_profile.example.yaml +29 -0
- package/plugins/fh-qp/scripts/qp_tools.sh +216 -0
- package/plugins/fh-qp/skills/qp/SKILL.md +65 -0
- package/plugins/fh-qp/skills/qp-plan/SKILL.md +42 -0
- package/plugins/fh-qp/skills/qp-regress/SKILL.md +45 -0
- package/plugins/fh-qp/skills/qp-run/SKILL.md +49 -0
- package/scripts/chamber_run.sh +14 -5
- package/scripts/com.forge-harness.live-eval.plist +84 -0
- package/scripts/compaction_probe.sh +9 -35
- package/scripts/directional_diff_gate.sh +14 -2
- package/scripts/frontier_digest_autopilot.sh +4 -1
- package/scripts/map_postprocess.py +90 -0
- package/scripts/outbound_query_guard.sh +131 -0
- package/scripts/outbound_query_hook.sh +373 -0
- package/scripts/package_coverage_check.sh +55 -16
- package/scripts/pipe_verdict_guard.sh +41 -1
- package/scripts/probe_live_eval.sh +240 -0
- package/scripts/probe_live_eval_lib.py +579 -0
- package/scripts/proposal_hook.sh +120 -17
- package/scripts/push_zone_check.sh +78 -0
- package/scripts/residency_closure_scan.py +252 -0
- package/scripts/selfcheck.sh +41 -1
- package/scripts/session_close_check.sh +100 -0
- package/scripts/sim_isolated_run.sh +98 -2
- package/scripts/test_action_yml_lanes.sh +97 -0
- package/scripts/test_fh_qp_lanes.sh +105 -0
- package/scripts/test_gate_two_verdicts_lanes.sh +139 -0
- package/scripts/test_map_postprocess_lanes.sh +143 -0
- package/scripts/test_marker_affected_lanes.sh +93 -0
- package/scripts/test_marker_crossfamily_lanes.sh +90 -6
- package/scripts/test_marker_oracle_lanes.sh +136 -0
- package/scripts/test_outbound_query_hook_lanes.sh +433 -0
- package/scripts/test_outbound_query_lanes.sh +87 -0
- package/scripts/test_pipe_verdict_guard_lanes.sh +26 -0
- package/scripts/test_preprep_diagram_lanes.sh +87 -0
- package/scripts/test_preprep_drift_anchor.sh +3 -3
- package/scripts/test_preprep_slide_refs_lanes.sh +169 -0
- package/scripts/test_probe_live_eval_lanes.sh +437 -0
- package/scripts/test_proposal_hook_lanes.sh +22 -1
- package/scripts/test_push_zone_lanes.sh +304 -0
- package/scripts/test_residency_closure_lanes.sh +70 -0
- package/scripts/test_sim_isolated_run_lanes.sh +119 -0
- package/scripts/test_utterance_intake_lanes.sh +414 -0
- package/scripts/test_worktree_reclaim_lanes.sh +70 -0
- package/scripts/transcript_utterances.py +222 -0
- package/scripts/utterance_intake.sh +424 -0
- package/scripts/validate_yaml.sh +27 -0
- package/scripts/worktree_reclaim.sh +95 -0
- package/templates/.git-hooks/pre-commit +353 -1
- package/templates/.git-hooks/pre-push +91 -0
- package/templates/RED_TEAM_REPORT.md +49 -0
- package/templates/settings.PreToolUse.snippet.json +65 -1
package/scripts/validate_yaml.sh
CHANGED
|
@@ -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
|