@chrono-meta/fh-gate 1.4.86 → 1.4.88
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/rules/fh_4axis_gate.md +1 -1
- package/.claude-plugin/marketplace.json +2 -2
- package/AGENTS.md +8 -0
- package/CLAUDE.md +30 -6
- package/knowledge/shared/harness-core/capability_composition_contract.md +416 -0
- package/knowledge/shared/harness-core/fh_detail_protocols.md +4 -0
- package/knowledge/shared/learnings/subagent_invocations_log.yaml +128 -0
- package/knowledge/shared/rules/auto_project_mapping.md +64 -1
- package/knowledge/shared/rules/operations.md +26 -0
- package/package.json +6 -2
- package/plugins/fh-commons/.claude-plugin/plugin.json +1 -1
- package/plugins/fh-meta/.claude-plugin/plugin.json +1 -1
- package/plugins/fh-meta/skills/prompt-regression/SKILL.md +1 -1
- package/scripts/degrade_direction_scan.sh +34 -2
- package/scripts/gate_pathspec_check.sh +91 -1
- package/scripts/package_coverage_check.sh +27 -0
- package/scripts/selfcheck.sh +112 -1
- package/scripts/session_close_check.sh +18 -4
- package/scripts/test_degrade_scan_shell_probes.sh +11 -2
- package/scripts/test_dispatch_log_lanes.sh +41 -1
- package/scripts/test_node_infra_delta_lanes.sh +211 -0
- package/scripts/test_session_close_chain_lanes.sh +349 -0
- package/scripts/test_wizard_snippet_merge_lanes.sh +202 -0
- package/templates/.claude/rules/session.md +28 -5
- package/templates/.git-hooks/pre-commit +9 -2
- package/templates/degrade_direction_scan.sh +34 -2
- package/templates/regression_guard.sh +25 -0
|
@@ -39,6 +39,7 @@ if command -v gh >/dev/null 2>&1; then
|
|
|
39
39
|
# `[{"number":227},{"number":226},{"number":225}]` → old 1, new 3; `[]` → 0 both ways — which is
|
|
40
40
|
# why an environment with zero open PRs can never surface this, and why the 0-case alone is not a
|
|
41
41
|
# calibration. Observed live the same day: 2 open PRs reported as 1.
|
|
42
|
+
# Anchored by scripts/test_session_close_chain_lanes.sh lane ①-b-P3.
|
|
42
43
|
PRS=$(gh pr list --author "@me" --state open --json number 2>/dev/null | grep -o '"number"' | wc -l | tr -d ' ' || true)
|
|
43
44
|
[ "${PRS:-0}" -gt 0 ] && echo "⚠️ ①-b $PRS open PR(s) by you — classify: self-mergeable vs awaiting-external"
|
|
44
45
|
fi
|
|
@@ -72,11 +73,16 @@ if [ "${FH_CHANGED:-0}" -gt 0 ]; then
|
|
|
72
73
|
fi
|
|
73
74
|
fi
|
|
74
75
|
|
|
75
|
-
#
|
|
76
|
+
# ④-log real-time completion log — required whenever any commit landed today.
|
|
77
|
+
# NAMING: this block does NOT implement CLAUDE.md's ④ (memory hygiene). It implements the
|
|
78
|
+
# "Real-time completion tracking" paragraph that sits above the chain. Labelling it ④ meant a green
|
|
79
|
+
# "④" told a reader memory hygiene had been verified when nothing had checked it — false coverage,
|
|
80
|
+
# the same class as a summary line that prints PASS while the exit code refuses. The label now says
|
|
81
|
+
# what it checks. (Numbering mismatch found 2026-08-02 by the lane-writing pass.)
|
|
76
82
|
COMMITS_TODAY=$(git -C "$FH" log --since="today 00:00" --oneline 2>/dev/null | wc -l | tr -d ' ')
|
|
77
83
|
FC="$FH/tracks/_meta/fh_completed_${TODAY}.md"
|
|
78
84
|
if [ "$COMMITS_TODAY" -gt 0 ] && [ ! -f "$FC" ]; then
|
|
79
|
-
echo "❌
|
|
85
|
+
echo "❌ ④-log commits landed today but tracks/_meta/fh_completed_${TODAY}.md is missing"
|
|
80
86
|
FAIL=1
|
|
81
87
|
fi
|
|
82
88
|
|
|
@@ -150,12 +156,20 @@ if [ "$HOOK_OK" -eq 0 ]; then
|
|
|
150
156
|
echo " (that file is gitignored, so a fresh clone has none). An unmeasured dispatch count is"
|
|
151
157
|
echo " NOT a count of zero. Install: templates/subagent-tally-hook.json → .claude/settings.json"
|
|
152
158
|
fi
|
|
153
|
-
|
|
159
|
+
# `grep -c` PRINTS 0 and EXITS 1 when the count is zero. Under `set -o pipefail` (line 16) that
|
|
160
|
+
# makes the pipeline fail, `|| echo 0` appends a SECOND line, and the value becomes "0\n0" — which
|
|
161
|
+
# `[ -eq ]` rejects as a bash error, so the branch it guards is skipped. The guard was therefore
|
|
162
|
+
# disarmed in EXACTLY the case it exists for (zero log entries). Measured 2026-08-04 during a close:
|
|
163
|
+
# the check printed ✅ while `[: 0\n0: integer expression expected` went to stderr.
|
|
164
|
+
# Fix = no `|| echo`, plus integer sanitation. This is the documented prescription for this class
|
|
165
|
+
# ([[feedback_pipefail_fallback_disarms_guard]]) applied to the checker that cited it.
|
|
166
|
+
_int() { case "${1:-}" in (''|*[!0-9]*) echo 0 ;; (*) echo "$1" ;; esac; }
|
|
167
|
+
DISPATCHED=$(_int "$(grep -c "^$TODAY$" "$TALLY" 2>/dev/null | tr -d ' ' || true)")
|
|
154
168
|
# Both quotings, because the file carries both: hand-written entries use `- date: 2026-08-02`
|
|
155
169
|
# while anything appended via yaml.dump renders `- date: '"'"'2026-08-02'"'"'`. Matching one form counted
|
|
156
170
|
# half the entries as absent — a divergent-normalizer miss inside the check that exists to catch
|
|
157
171
|
# missing records. Known-pair calibrated below in test_dispatch_log_lanes.sh.
|
|
158
|
-
LOGGED=$(grep -cE "^- date: *'?$TODAY'?" "$LOG" 2>/dev/null | tr -d ' ' ||
|
|
172
|
+
LOGGED=$(_int "$(grep -cE "^- date: *'?$TODAY'?" "$LOG" 2>/dev/null | tr -d ' ' || true)")
|
|
159
173
|
if [ "${DISPATCHED:-0}" -gt 0 ] && [ "${LOGGED:-0}" -eq 0 ]; then
|
|
160
174
|
echo "❌ ④-e $DISPATCHED sub-agent dispatch(es) today and ZERO invocation-log entries — the 60/40"
|
|
161
175
|
echo " promotion gate and the UAP loop both read that file; an unlogged session is invisible to"
|
|
@@ -163,6 +163,15 @@ cat > "$TMP/s5_positive.sh" <<'EOF'
|
|
|
163
163
|
set -uo pipefail
|
|
164
164
|
N=$(find /nope . -maxdepth 1 2>/dev/null | grep -c . || echo 0)
|
|
165
165
|
M=$(git log --oneline 2>/dev/null | wc -l || echo 0)
|
|
166
|
+
# WIDENED 2026-08-04. Every line below was INVISIBLE to the narrowed rule, and each was verified to
|
|
167
|
+
# actually produce "0\n0" before being pinned here (line count measured, not assumed):
|
|
168
|
+
P=$(cat /etc/hosts | grep -c . | tr -d ' ' || echo 0) # transparent filter after the counter
|
|
169
|
+
Q=$(grep -c "^nosuchline$" /etc/hosts 2>/dev/null | tr -d ' ' || echo 0) # the PR #251 shape
|
|
170
|
+
R=$(grep -Ec "^nosuchline$" /etc/hosts || echo 0) # combined flag cluster -Ec
|
|
171
|
+
S=$(grep --count "^nosuchline$" /etc/hosts || echo 0) # long option
|
|
172
|
+
T=$(grep -Fcx "nosuchline" /etc/hosts || echo 0) # -Fcx
|
|
173
|
+
U=$(false | grep -c . | cat || echo 0) # trailing stage that always emits
|
|
174
|
+
V=$(false | grep -c . | grep -v nosuch || echo 0) # trailing grep whose pattern misses the "0"
|
|
166
175
|
EOF
|
|
167
176
|
cat > "$TMP/s5_negative.sh" <<'EOF'
|
|
168
177
|
#!/usr/bin/env bash
|
|
@@ -174,8 +183,8 @@ J=$(printf '%s' "$x" | jq -r '.a // 0' 2>/dev/null || echo 0)
|
|
|
174
183
|
# A comment describing the defect must not be scored as the defect: cmd | grep -c . || echo 0
|
|
175
184
|
EOF
|
|
176
185
|
n=$(s_hits "$TMP/s5_positive.sh")
|
|
177
|
-
[ "$n" -eq
|
|
178
|
-
|
|
186
|
+
[ "$n" -eq 9 ] && ok "S5 known-positive: 9/9 — incl. -Ec/--count/-Fcx flag clusters, trailing \`| cat\`/\`| grep -v\`, and the no-upstream-pipe form" \
|
|
187
|
+
|| bad "S5 known-positive: expected 9 S-hits, got $n — 2 = the pre-2026-08-04 rule (pipe-presence anchor); 4 = the first widening, which still missed every combined flag cluster (\`-Ec\`, \`--count\`, \`-Fcx\`) and any trailing stage outside a hardcoded name list"
|
|
179
188
|
|
|
180
189
|
n=$(s_hits "$TMP/s5_negative.sh")
|
|
181
190
|
[ "$n" -eq 0 ] && ok "S5 known-negative: \`||\` chains, empty-on-failure pipelines and comments stay silent" \
|
|
@@ -75,7 +75,14 @@ PY
|
|
|
75
75
|
[ "$ok" -eq 0 ] ; chk $? "hook ends in exit 0 — a non-zero hook exit discards its stdout SILENTLY"
|
|
76
76
|
# run it against a scratch HUB and confirm it appends today's date
|
|
77
77
|
CLAUDE_PROJECT_DIR="$T/hub" bash -c "$cmd" >/dev/null 2>&1
|
|
78
|
-
|
|
78
|
+
# Split + sanitize, not `grep -c … || echo 0`: on no-match `grep -c` PRINTS "0" and exits 1, so
|
|
79
|
+
# the fallback appends a SECOND line and `[ -ge ]` dies with "integer expression expected".
|
|
80
|
+
# Here that error happens to land on the FAIL branch — honest scope: this was never a live
|
|
81
|
+
# fail-open, it was a verdict reached by a bash error instead of a comparison, one refactor
|
|
82
|
+
# away from flipping. Found by the S5 sweep after the rule was widened (2026-08-04).
|
|
83
|
+
_tally_n=$(grep -c "$(date +%Y-%m-%d)" "$T/hub/tracks/_meta/.subagent_dispatch_tally" 2>/dev/null); _tally_n=${_tally_n:-0}
|
|
84
|
+
case "$_tally_n" in (*[!0-9]*|'') _tally_n=0 ;; esac
|
|
85
|
+
[ "$_tally_n" -ge 1 ]
|
|
79
86
|
chk $? "hook actually appends a dated line when run (not merely present)"
|
|
80
87
|
fi
|
|
81
88
|
else
|
|
@@ -93,6 +100,39 @@ hookless_verdict() { # $1=hook_configured -> MEASURED | NOT-MEASURED
|
|
|
93
100
|
grep -q "NOT MEASURED" "$CHECK" ; chk $? "the subject actually carries that branch (not just this lane)"
|
|
94
101
|
[ -f "$SCRIPT_DIR/../templates/subagent-tally-hook.json" ] ; chk $? "installable snippet exists for a clone that has no settings.json"
|
|
95
102
|
|
|
103
|
+
echo "── pipefail disarm (the guard was dead in EXACTLY its target case) ──"
|
|
104
|
+
# `grep -c` PRINTS 0 and EXITS 1 on a zero count. Under `set -o pipefail` the old
|
|
105
|
+
# `grep -c … | tr -d ' ' || echo 0` therefore produced "0\n0", `[ -eq ]` threw, and the ❌ branch
|
|
106
|
+
# was skipped — so ④-e reported ✅ whenever the log had ZERO entries, which is the one situation it
|
|
107
|
+
# exists to block. Measured live 2026-08-04 during a session close.
|
|
108
|
+
# Lane 1: reproduce the OLD form and assert it is broken (a control — if this ever passes, the
|
|
109
|
+
# premise changed and the fix below is measuring nothing).
|
|
110
|
+
old_form=$(bash -c 'set -uo pipefail; grep -c "^NOSUCHDATE$" '"$CHECK"' 2>/dev/null | tr -d " " || echo 0')
|
|
111
|
+
[ "$(printf %s "$old_form" | grep -c "")" -eq 2 ] ; chk $? "control: the old \`|| echo 0\` form really does yield TWO lines under pipefail"
|
|
112
|
+
# Lane 2: the shipped form yields a single sanitised integer.
|
|
113
|
+
new_form=$(bash -c 'set -uo pipefail; _int() { case "${1:-}" in (""|*[!0-9]*) echo 0 ;; (*) echo "$1" ;; esac; }; _int "$(grep -c "^NOSUCHDATE$" '"$CHECK"' 2>/dev/null | tr -d " " || true)"')
|
|
114
|
+
[ "$new_form" = "0" ] ; chk $? "shipped form yields a single sanitised 0"
|
|
115
|
+
( set -uo pipefail; [ "$new_form" -eq 0 ] ) 2>/dev/null ; chk $? "and \`[ -eq ]\` accepts it (the old form threw here)"
|
|
116
|
+
# Lane 3-5: BEHAVIOUR, not spelling. The first draft of these lanes string-matched the old form and
|
|
117
|
+
# went GREEN on a revert — the needle did not match `tr -d ' '` (quotes), so the anchor was
|
|
118
|
+
# decorative in the way this repo already named ([[feedback_anchor_can_be_decorative]], cause:
|
|
119
|
+
# "needle matching unrelated text"). Extract the subject's OWN assignment lines and evaluate them.
|
|
120
|
+
_eval_subject_assign() { # $1 = variable name; echoes what the SUBJECT computes for a zero match
|
|
121
|
+
local var="$1" line
|
|
122
|
+
line=$(grep -E "^${var}=" "$CHECK" | head -1)
|
|
123
|
+
[ -n "$line" ] || { echo "NOLINE"; return; }
|
|
124
|
+
bash -c "set -uo pipefail
|
|
125
|
+
_int() { case \"\${1:-}\" in (''|*[!0-9]*) echo 0 ;; (*) echo \"\$1\" ;; esac; }
|
|
126
|
+
TODAY=NOSUCHDATE; TALLY='$CHECK'; LOG='$CHECK'
|
|
127
|
+
$line
|
|
128
|
+
printf %s \"\$$var\"" 2>/dev/null
|
|
129
|
+
}
|
|
130
|
+
for _v in DISPATCHED LOGGED; do
|
|
131
|
+
_got=$(_eval_subject_assign "$_v")
|
|
132
|
+
[ "$(printf %s "$_got" | grep -c '')" -le 1 ] ; chk $? "subject's \$$_v is ONE line on a zero match (was two: the disarm)"
|
|
133
|
+
( set -uo pipefail; [ "${_got:-x}" -eq 0 ] ) 2>/dev/null ; chk $? "subject's \$$_v survives \`[ -eq 0 ]\` (the disarm threw here)"
|
|
134
|
+
done
|
|
135
|
+
|
|
96
136
|
echo ""
|
|
97
137
|
if [ "$FAILED" -ne 0 ]; then echo "DISPATCH-LOG LANES: FAIL"; exit 1; fi
|
|
98
138
|
echo "DISPATCH-LOG LANES: PASS ($PASS/$PASS)"
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# test_node_infra_delta_lanes.sh — lanes for the INFRA-DELTA branch of scripts/fh_node_check.sh.
|
|
3
|
+
#
|
|
4
|
+
# WHY A SEPARATE FILE FROM test_node_check_lanes.sh: that suite covers the floor probes (git hooks,
|
|
5
|
+
# Mode D companion detection, emission model) and never enters the infra-delta branch — every one of
|
|
6
|
+
# its fixtures runs against a state file whose PREV_HEAD is either empty (first session) or equal to
|
|
7
|
+
# HEAD. The branch that reacts to `git pull` moving install-relevant files was unexercised, which is
|
|
8
|
+
# how the four items below survived. Keeping it separate also keeps the two suites' fixture families
|
|
9
|
+
# from colliding: this one must CHANGE HEAD between runs, which breaks the "event once then silent"
|
|
10
|
+
# lane in the other file.
|
|
11
|
+
#
|
|
12
|
+
# CALIBRATION RULE OBSERVED THROUGHOUT: every lane asserting an ABSENCE names its known-positive in
|
|
13
|
+
# the lane title and builds it from the SAME fixture family in the SAME run. Exit codes are read
|
|
14
|
+
# DIRECTLY off the subject, never through a pipe.
|
|
15
|
+
#
|
|
16
|
+
# ⓘ GAP lanes pin behaviour believed WRONG without failing the suite; if the behaviour changes they
|
|
17
|
+
# announce "GAP CLOSED" so the lane gets promoted instead of silently rotting.
|
|
18
|
+
#
|
|
19
|
+
# Exit 0 = every asserting lane calibrated · 1 = the instrument is wrong.
|
|
20
|
+
|
|
21
|
+
set -uo pipefail
|
|
22
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
23
|
+
CHECK="$SCRIPT_DIR/fh_node_check.sh"
|
|
24
|
+
[ -f "$CHECK" ] || { echo "FAIL subject missing: $CHECK"; exit 1; }
|
|
25
|
+
|
|
26
|
+
TMPROOT=$(mktemp -d "${TMPDIR:-/tmp}/fh_infra_lanes.XXXXXX")
|
|
27
|
+
trap 'rm -rf "$TMPROOT"' EXIT
|
|
28
|
+
FAILED=0; PASSED=0; GAPS=0
|
|
29
|
+
|
|
30
|
+
_pass() { echo "✅ $1"; PASSED=$((PASSED+1)); }
|
|
31
|
+
_fail() { echo "❌ $1"; FAILED=1; }
|
|
32
|
+
_line() { # $1=name $2=pattern $3=expect(0/1) $4=output
|
|
33
|
+
local hit=0
|
|
34
|
+
printf '%s\n' "$4" | grep -q -- "$2" && hit=1
|
|
35
|
+
if [ "$hit" = "$3" ]; then _pass "$1 (hit=$hit, expected=$3)"
|
|
36
|
+
else _fail "$1 — hit=$hit, expected=$3"; printf '%s\n' "$4" | sed 's/^/ │ /'; fi
|
|
37
|
+
}
|
|
38
|
+
_rc() { if [ "$2" = "$3" ]; then _pass "$1 (exit=$2, expected=$3)"; else _fail "$1 — exit=$2, expected=$3"; fi; }
|
|
39
|
+
_gap() { # $1=name $2=observed(1=still open) $3=note
|
|
40
|
+
if [ "$2" = "1" ]; then echo "ⓘ GAP (still open) $1"; echo " ↳ $3"; GAPS=$((GAPS+1))
|
|
41
|
+
else echo "🎉 GAP CLOSED — $1 : behaviour changed, promote this lane to an asserting one"; fi
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
# ── fixtures ────────────────────────────────────────────────────────────────────
|
|
45
|
+
# A hub with FH's own gate sentinel in both hooks, so MISS is empty and any output is attributable
|
|
46
|
+
# to the infra-delta branch alone (instrument-discrimination: a lane that cannot tell which leg
|
|
47
|
+
# fired is not measuring that leg).
|
|
48
|
+
_hub() { # $1=name → echoes path
|
|
49
|
+
local d="$TMPROOT/$1"
|
|
50
|
+
mkdir -p "$d/.git" || true
|
|
51
|
+
git -C "$d" init -q 2>/dev/null || { mkdir -p "$d"; git -C "$d" init -q; }
|
|
52
|
+
git -C "$d" config user.email anchor@local; git -C "$d" config user.name anchor
|
|
53
|
+
mkdir -p "$d/.git/hooks"
|
|
54
|
+
printf '#!/bin/sh\n# FH 4-Axis Gate\nexit 0\n' > "$d/.git/hooks/pre-commit"
|
|
55
|
+
cp "$d/.git/hooks/pre-commit" "$d/.git/hooks/pre-push"
|
|
56
|
+
chmod +x "$d/.git/hooks/pre-commit" "$d/.git/hooks/pre-push"
|
|
57
|
+
echo seed > "$d/seed.txt"
|
|
58
|
+
git -C "$d" add -A >/dev/null 2>&1
|
|
59
|
+
git -C "$d" -c commit.gpgsign=false commit -qm seed >/dev/null 2>&1
|
|
60
|
+
printf '%s' "$d"
|
|
61
|
+
}
|
|
62
|
+
_commit() { # $1=hub $2..=paths to create
|
|
63
|
+
local d="$1" p; shift
|
|
64
|
+
for p in "$@"; do mkdir -p "$d/$(dirname "$p")"; echo changed >> "$d/$p"; done
|
|
65
|
+
git -C "$d" add -A >/dev/null 2>&1
|
|
66
|
+
git -C "$d" -c commit.gpgsign=false commit -qm change >/dev/null 2>&1
|
|
67
|
+
}
|
|
68
|
+
_run() { # $1=hub $2=statefile ; sets OUT/RC (RC read directly, no pipe)
|
|
69
|
+
OUT=$(HUB_DIR="$1" FH_NODE_STATE="$2" FH_MACHINE_ID=lanebox bash "$CHECK" 2>&1); RC=$?
|
|
70
|
+
}
|
|
71
|
+
# seed the state so PREV_HEAD is the pre-change commit (this is the ONLY way into the branch)
|
|
72
|
+
_seed_state() { _run "$1" "$2" >/dev/null 2>&1; }
|
|
73
|
+
|
|
74
|
+
NEW='🆕 Install-relevant assets changed'
|
|
75
|
+
HDR='🖥️ \[node\]'
|
|
76
|
+
|
|
77
|
+
echo "══ infra delta: fires on an install-relevant change, silent otherwise ══"
|
|
78
|
+
H=$(_hub p_pos); _seed_state "$H" "$TMPROOT/st_pos"
|
|
79
|
+
_commit "$H" scripts/fh_env_delta_scan.sh
|
|
80
|
+
_run "$H" "$TMPROOT/st_pos"
|
|
81
|
+
_line "ID-P install-relevant file changed → 🆕 block fires" "$NEW" 1 "$OUT"
|
|
82
|
+
_line "ID-P → names the changed path" 'scripts/fh_env_delta_scan.sh' 1 "$OUT"
|
|
83
|
+
_line "ID-P → routes to the wizard" 'Re-run /install-wizard' 1 "$OUT"
|
|
84
|
+
_rc "ID-P detector never gates (exit 0)" "$RC" 0
|
|
85
|
+
|
|
86
|
+
# CONTROL for ID-P, same fixture family, same run-shape: HEAD advances by exactly one commit, but
|
|
87
|
+
# the changed path is outside the install surface. Silence here is only meaningful BECAUSE ID-P
|
|
88
|
+
# above spoke on an otherwise identical fixture.
|
|
89
|
+
H=$(_hub p_ctl); _seed_state "$H" "$TMPROOT/st_ctl"
|
|
90
|
+
_commit "$H" README.md
|
|
91
|
+
_run "$H" "$TMPROOT/st_ctl"
|
|
92
|
+
_line "ID-C non-install path changed → no 🆕 block (over-fire ctl for ID-P)" "$NEW" 0 "$OUT"
|
|
93
|
+
_rc "ID-C → exit 0" "$RC" 0
|
|
94
|
+
|
|
95
|
+
echo
|
|
96
|
+
echo "══ one fixture PER regex alternative (an untested alternative is an untested branch) ══"
|
|
97
|
+
# Deliberately one path per alternative: a single fixture touching several at once would let any
|
|
98
|
+
# alternative be deleted with the suite staying green — the lane4c lesson from test_node_check_lanes.
|
|
99
|
+
i=0
|
|
100
|
+
for p in "templates/.git-hooks/pre-push" \
|
|
101
|
+
"templates/settings.SessionStart.snippet.json" \
|
|
102
|
+
"scripts/fh_session_load.sh" \
|
|
103
|
+
"plugins/fh-meta/skills/install-wizard/SKILL.md" \
|
|
104
|
+
"plugins/fh-meta/skills/install-doctor/SKILL.md"; do
|
|
105
|
+
i=$((i+1))
|
|
106
|
+
H=$(_hub "alt$i"); _seed_state "$H" "$TMPROOT/st_alt$i"
|
|
107
|
+
_commit "$H" "$p"
|
|
108
|
+
_run "$H" "$TMPROOT/st_alt$i"
|
|
109
|
+
_line "ID-R$i alternative covered: $p" "$NEW" 1 "$OUT"
|
|
110
|
+
done
|
|
111
|
+
|
|
112
|
+
# NEGATIVE for the regex: install-adjacent-LOOKING paths that the pattern deliberately does not
|
|
113
|
+
# cover. Without this leg "the regex matches everything" would also pass ID-R1..5.
|
|
114
|
+
H=$(_hub alt_neg); _seed_state "$H" "$TMPROOT/st_altneg"
|
|
115
|
+
_commit "$H" "plugins/fh-meta/skills/harvest-loop/SKILL.md"
|
|
116
|
+
_run "$H" "$TMPROOT/st_altneg"
|
|
117
|
+
_line "ID-Rn non-install skill path → not treated as infra (paired with ID-R4/5)" "$NEW" 0 "$OUT"
|
|
118
|
+
|
|
119
|
+
echo
|
|
120
|
+
echo "══ unreachable PREV_HEAD: UNMEASURED, not silence ══"
|
|
121
|
+
H=$(_hub p_unreach); _commit "$H" scripts/fh_env_delta_scan.sh
|
|
122
|
+
printf 'lanebox|1700000000|deadbee' > "$TMPROOT/st_unreach"
|
|
123
|
+
_run "$H" "$TMPROOT/st_unreach"
|
|
124
|
+
_line "ID-U gc/rebase/shallow → UNMEASURED is stated" 'UNMEASURED' 1 "$OUT"
|
|
125
|
+
_line "ID-U → does NOT claim the 🆕 delta (not found ≠ 0)" "$NEW" 0 "$OUT"
|
|
126
|
+
_rc "ID-U → exit 0" "$RC" 0
|
|
127
|
+
|
|
128
|
+
echo
|
|
129
|
+
echo "══ co-reporting: a missing floor does not suppress the infra delta ══"
|
|
130
|
+
H=$(_hub p_both); _seed_state "$H" "$TMPROOT/st_both"
|
|
131
|
+
rm -f "$H/.git/hooks/pre-commit"
|
|
132
|
+
_commit "$H" scripts/fh_node_check.sh
|
|
133
|
+
_run "$H" "$TMPROOT/st_both"
|
|
134
|
+
_line "ID-B missing floor reported" 'Missing mechanical floor' 1 "$OUT"
|
|
135
|
+
_line "ID-B AND infra delta reported (paired)" "$NEW" 1 "$OUT"
|
|
136
|
+
|
|
137
|
+
echo
|
|
138
|
+
echo "══ ⓘ GAP lanes — believed-wrong behaviour, pinned not asserted ══"
|
|
139
|
+
|
|
140
|
+
# GAP 1 — attribution. Every other emission path prints the 🖥️ [node] header; the infra-only and
|
|
141
|
+
# UNMEASURED-only paths print 4-space-indented CONTINUATION lines under a header that was never
|
|
142
|
+
# printed. This is not cosmetic in situ: SessionStart hooks on one matcher have their outputs
|
|
143
|
+
# delivered as separate, unordered, unlabelled blocks (measured in
|
|
144
|
+
# scripts/test_sessionstart_multihook_lanes.sh), so an unattributed indented block genuinely cannot
|
|
145
|
+
# be traced to the hook that emitted it.
|
|
146
|
+
H=$(_hub g_hdr); _seed_state "$H" "$TMPROOT/st_hdr"
|
|
147
|
+
_commit "$H" scripts/fh_env_delta_scan.sh
|
|
148
|
+
_run "$H" "$TMPROOT/st_hdr"
|
|
149
|
+
_g=0
|
|
150
|
+
{ printf '%s\n' "$OUT" | grep -q "$NEW"; } && ! printf '%s\n' "$OUT" | grep -q "$HDR" && _g=1
|
|
151
|
+
_gap "infra-only emission has NO 🖥️ [node] header" "$_g" \
|
|
152
|
+
"Output is 4-space-indented continuation lines with no parent line. MISS and IDENTITY both print a \
|
|
153
|
+
header; the infra and UNMEASURED paths do not, because they hang off an if/elif that both fail."
|
|
154
|
+
|
|
155
|
+
# GAP 2 — silent truncation. `head -6` with no count. The script's own header argues 'not found ≠ 0'
|
|
156
|
+
# and 'distinguish no-change from could-not-measure'; an omitted-3-of-9 list is the same class of
|
|
157
|
+
# hidden omission, one layer in.
|
|
158
|
+
H=$(_hub g_trunc); _seed_state "$H" "$TMPROOT/st_trunc"
|
|
159
|
+
_commit "$H" scripts/fh_a.sh scripts/fh_b.sh scripts/fh_c.sh scripts/fh_d.sh \
|
|
160
|
+
scripts/fh_e.sh scripts/fh_f.sh scripts/fh_g.sh scripts/fh_h.sh scripts/fh_i.sh
|
|
161
|
+
_run "$H" "$TMPROOT/st_trunc"
|
|
162
|
+
LISTED=$(printf '%s\n' "$OUT" | grep -c '^ - ')
|
|
163
|
+
_g=0; { [ "$LISTED" = "6" ] && ! printf '%s\n' "$OUT" | grep -qiE 'more|[0-9]+ of [0-9]+|truncat'; } && _g=1
|
|
164
|
+
_gap "9 install-relevant files changed → 6 listed, 3 dropped with no count" "$_g" \
|
|
165
|
+
"listed=$LISTED, and no '…and N more'. A big pull (exactly when re-running the wizard matters most) \
|
|
166
|
+
silently hides the tail. Cheap fix: append the residual count."
|
|
167
|
+
|
|
168
|
+
# GAP 3 — the one-shot consumes an UNRESOLVED unknown. State is written UNCONDITIONALLY to HEAD_NOW,
|
|
169
|
+
# including on the run that could not compute the delta. Next session PREV_HEAD == HEAD, so the
|
|
170
|
+
# delta across that pull is never attempted again and never mentioned again. The script's header
|
|
171
|
+
# says events report once and CONDITIONS report until they stop being true — an un-computed delta is
|
|
172
|
+
# an unresolved condition, and it is being treated as a discharged event.
|
|
173
|
+
H=$(_hub g_once); _commit "$H" scripts/fh_env_delta_scan.sh
|
|
174
|
+
printf 'lanebox|1700000000|deadbee' > "$TMPROOT/st_once"
|
|
175
|
+
SPOKE=0
|
|
176
|
+
for _i in 1 2 3; do
|
|
177
|
+
_run "$H" "$TMPROOT/st_once"
|
|
178
|
+
printf '%s\n' "$OUT" | grep -q 'UNMEASURED' && SPOKE=$((SPOKE+1))
|
|
179
|
+
done
|
|
180
|
+
_g=0; [ "$SPOKE" = "1" ] && _g=1
|
|
181
|
+
_gap "UNMEASURED spoken once, then permanently forgotten (state advanced anyway)" "$_g" \
|
|
182
|
+
"spoke on $SPOKE of 3 consecutive runs. Same shape as the failure the state-based design was \
|
|
183
|
+
introduced to fix ('a broken machine reported once then went quiet forever') — the fix reached MISS \
|
|
184
|
+
but not the infra branch. CODE-vs-DOC: fh_node_check.sh's own header claims it distinguishes \
|
|
185
|
+
'no change' from 'could not measure', yet after run 1 the two are indistinguishable forever."
|
|
186
|
+
|
|
187
|
+
# GAP 4 — the event is consumed even when nobody saw it. State advances before/independently of
|
|
188
|
+
# whether the emission reached anyone. Pairs directly with the F11 measurement: a SessionStart hook
|
|
189
|
+
# whose sibling ordering, delivery, or exit handling drops its output loses the notice permanently.
|
|
190
|
+
H=$(_hub g_unseen); _seed_state "$H" "$TMPROOT/st_unseen"
|
|
191
|
+
_commit "$H" scripts/fh_env_delta_scan.sh
|
|
192
|
+
HUB_DIR="$H" FH_NODE_STATE="$TMPROOT/st_unseen" FH_MACHINE_ID=lanebox bash "$CHECK" >/dev/null 2>&1
|
|
193
|
+
_run "$H" "$TMPROOT/st_unseen"
|
|
194
|
+
_g=0; printf '%s\n' "$OUT" | grep -q "$NEW" || _g=1
|
|
195
|
+
_gap "infra event is consumed by a run whose output nobody received" "$_g" \
|
|
196
|
+
"One discarded-stdout run and the notice is gone for good. The write is deliberately unconditional \
|
|
197
|
+
(so the timestamp does not mean 'last time it spoke'), but that argument covers the TIMESTAMP, not \
|
|
198
|
+
the HEAD field. Recording HEAD only when the delta was successfully computed AND emitted would keep \
|
|
199
|
+
the timestamp honest and stop discharging unseen events."
|
|
200
|
+
|
|
201
|
+
echo
|
|
202
|
+
echo "──────────────────────────────────────────────"
|
|
203
|
+
if [ "$FAILED" -ne 0 ]; then
|
|
204
|
+
echo "NODE INFRA-DELTA LANES: FAIL — instrument miscalibrated (do not trust its verdict)"
|
|
205
|
+
echo " asserting lanes passed: $PASSED · open gaps: $GAPS"
|
|
206
|
+
exit 1
|
|
207
|
+
fi
|
|
208
|
+
echo "NODE INFRA-DELTA LANES: PASS ($PASSED asserting lanes) · $GAPS KNOWN GAP(S) open (see ⓘ above)"
|
|
209
|
+
echo " Green covers: fires/does-not-fire, per-alternative regex coverage, UNMEASURED, co-reporting."
|
|
210
|
+
echo " Green does NOT cover the four gaps above."
|
|
211
|
+
exit 0
|