@chrono-meta/fh-gate 1.4.85 → 1.4.87

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.
@@ -93,6 +93,39 @@ hookless_verdict() { # $1=hook_configured -> MEASURED | NOT-MEASURED
93
93
  grep -q "NOT MEASURED" "$CHECK" ; chk $? "the subject actually carries that branch (not just this lane)"
94
94
  [ -f "$SCRIPT_DIR/../templates/subagent-tally-hook.json" ] ; chk $? "installable snippet exists for a clone that has no settings.json"
95
95
 
96
+ echo "── pipefail disarm (the guard was dead in EXACTLY its target case) ──"
97
+ # `grep -c` PRINTS 0 and EXITS 1 on a zero count. Under `set -o pipefail` the old
98
+ # `grep -c … | tr -d ' ' || echo 0` therefore produced "0\n0", `[ -eq ]` threw, and the ❌ branch
99
+ # was skipped — so ④-e reported ✅ whenever the log had ZERO entries, which is the one situation it
100
+ # exists to block. Measured live 2026-08-04 during a session close.
101
+ # Lane 1: reproduce the OLD form and assert it is broken (a control — if this ever passes, the
102
+ # premise changed and the fix below is measuring nothing).
103
+ old_form=$(bash -c 'set -uo pipefail; grep -c "^NOSUCHDATE$" '"$CHECK"' 2>/dev/null | tr -d " " || echo 0')
104
+ [ "$(printf %s "$old_form" | grep -c "")" -eq 2 ] ; chk $? "control: the old \`|| echo 0\` form really does yield TWO lines under pipefail"
105
+ # Lane 2: the shipped form yields a single sanitised integer.
106
+ 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)"')
107
+ [ "$new_form" = "0" ] ; chk $? "shipped form yields a single sanitised 0"
108
+ ( set -uo pipefail; [ "$new_form" -eq 0 ] ) 2>/dev/null ; chk $? "and \`[ -eq ]\` accepts it (the old form threw here)"
109
+ # Lane 3-5: BEHAVIOUR, not spelling. The first draft of these lanes string-matched the old form and
110
+ # went GREEN on a revert — the needle did not match `tr -d ' '` (quotes), so the anchor was
111
+ # decorative in the way this repo already named ([[feedback_anchor_can_be_decorative]], cause:
112
+ # "needle matching unrelated text"). Extract the subject's OWN assignment lines and evaluate them.
113
+ _eval_subject_assign() { # $1 = variable name; echoes what the SUBJECT computes for a zero match
114
+ local var="$1" line
115
+ line=$(grep -E "^${var}=" "$CHECK" | head -1)
116
+ [ -n "$line" ] || { echo "NOLINE"; return; }
117
+ bash -c "set -uo pipefail
118
+ _int() { case \"\${1:-}\" in (''|*[!0-9]*) echo 0 ;; (*) echo \"\$1\" ;; esac; }
119
+ TODAY=NOSUCHDATE; TALLY='$CHECK'; LOG='$CHECK'
120
+ $line
121
+ printf %s \"\$$var\"" 2>/dev/null
122
+ }
123
+ for _v in DISPATCHED LOGGED; do
124
+ _got=$(_eval_subject_assign "$_v")
125
+ [ "$(printf %s "$_got" | grep -c '')" -le 1 ] ; chk $? "subject's \$$_v is ONE line on a zero match (was two: the disarm)"
126
+ ( set -uo pipefail; [ "${_got:-x}" -eq 0 ] ) 2>/dev/null ; chk $? "subject's \$$_v survives \`[ -eq 0 ]\` (the disarm threw here)"
127
+ done
128
+
96
129
  echo ""
97
130
  if [ "$FAILED" -ne 0 ]; then echo "DISPATCH-LOG LANES: FAIL"; exit 1; fi
98
131
  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
@@ -0,0 +1,349 @@
1
+ #!/usr/bin/env bash
2
+ # test_session_close_chain_lanes.sh — known-pair anchors for the close-chain steps that had NO
3
+ # mechanical lane: ① (status snapshot) · ①-b (open-PR sweep) · ④-log (real-time completion log, the
4
+ # only exit-1 FAIL path besides ⑤) · ④-b (npm freshness + BIDIRECTIONAL entry-point drift) ·
5
+ # and the pre-push SURFACE MATCHING (ordinary push advises · FH_SESSION_CLOSE=1 push blocks).
6
+ #
7
+ # Already anchored elsewhere, deliberately NOT duplicated here:
8
+ # ②, ⑤ card-last → scripts/test_session_close_lanes.sh
9
+ # ⑤-b card-drift probe → scripts/test_card_drift_probe.sh (incl. the locale-divergence leg)
10
+ # pre-push stdin/ordering → scripts/test_prepush_stdin_integrity.sh
11
+ #
12
+ # CALIBRATION RULE OBSERVED THROUGHOUT: every lane that asserts an ABSENCE is paired with a
13
+ # known-POSITIVE built from the SAME fixture family, so "passed because the guard worked" is
14
+ # distinguishable from "passed because nothing ran". Exit codes are read DIRECTLY off the
15
+ # subject (never through a pipe — `cmd | tail; echo $?` reads tail's status and has produced
16
+ # false green in this repo four times).
17
+ #
18
+ # ⓘ GAP lanes: places where the subject's CURRENT behaviour is believed WRONG (fail-open, or a
19
+ # spec/code disagreement). They pin the observed behaviour but never fail the suite; if the
20
+ # behaviour changes they announce "GAP CLOSED" so the lane gets promoted instead of silently
21
+ # rotting. They are counted and printed separately — a green summary here does NOT mean the
22
+ # chain is fully guarded.
23
+ #
24
+ # Exit 0 = every asserting lane calibrated · exit 1 = the gate's instrument is wrong.
25
+
26
+ set -uo pipefail
27
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
28
+ ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
29
+ CHECK="$SCRIPT_DIR/session_close_check.sh"
30
+ HOOK="$ROOT/templates/.git-hooks/pre-push"
31
+ TODAY=$(date +%Y-%m-%d)
32
+ FAILED=0
33
+ PASSED=0
34
+ GAPS=0
35
+
36
+ [ -f "$CHECK" ] || { echo "FAIL subject missing: $CHECK"; exit 1; }
37
+ [ -f "$HOOK" ] || { echo "FAIL subject missing: $HOOK"; exit 1; }
38
+
39
+ TMPROOT=$(mktemp -d "${TMPDIR:-/tmp}/fh_close_lanes.XXXXXX")
40
+ trap 'rm -rf "$TMPROOT"' EXIT
41
+
42
+ _pass() { echo "✅ $1"; PASSED=$((PASSED+1)); }
43
+ _fail() { echo "❌ $1"; FAILED=1; }
44
+
45
+ # assert a line IS / IS NOT present in captured output
46
+ _line() { # $1=name $2=pattern $3=expect(0/1) $4=output
47
+ local hit=0
48
+ printf '%s\n' "$4" | grep -q -- "$2" && hit=1
49
+ if [ "$hit" = "$3" ]; then _pass "$1 (hit=$hit, expected=$3)"
50
+ else _fail "$1 — hit=$hit, expected=$3"; printf '%s\n' "$4" | sed 's/^/ │ /'; fi
51
+ }
52
+
53
+ _rc() { # $1=name $2=actual $3=expected
54
+ if [ "$2" = "$3" ]; then _pass "$1 (exit=$2, expected=$3)"
55
+ else _fail "$1 — exit=$2, expected=$3"; fi
56
+ }
57
+
58
+ # GAP lane: pins believed-wrong behaviour without failing the suite.
59
+ _gap() { # $1=name $2=observed-condition-result(0/1 as evaluated by caller) $3=note
60
+ if [ "$2" = "1" ]; then
61
+ echo "ⓘ GAP (still open) $1"
62
+ echo " ↳ $3"
63
+ GAPS=$((GAPS+1))
64
+ else
65
+ echo "🎉 GAP CLOSED — $1 : behaviour changed, promote this lane to an asserting one"
66
+ fi
67
+ }
68
+
69
+ # ── fixture builders ─────────────────────────────────────────────────────────────
70
+ _repo() { # $1=dirname ; makes a git repo with one commit dated $2 (default now)
71
+ local T="$TMPROOT/$1" when="${2:-}"
72
+ mkdir -p "$T"
73
+ (
74
+ cd "$T" || exit 1
75
+ git init -q . 2>/dev/null
76
+ git config user.email anchor@local
77
+ git config user.name anchor
78
+ echo seed > unrelated.txt
79
+ # tracks/ is gitignored in the real repo too — without this the fixture's own card+log show up
80
+ # as untracked paths and ①'s "clean tree" lane can never be built (self-inflicted dirt).
81
+ printf 'tracks/\nbin/\n' > .gitignore
82
+ git add -A
83
+ if [ -n "$when" ]; then
84
+ GIT_AUTHOR_DATE="$when" GIT_COMMITTER_DATE="$when" git commit -qm seed
85
+ else
86
+ git commit -qm seed
87
+ fi
88
+ ) >/dev/null 2>&1
89
+ mkdir -p "$T/tracks/_meta"
90
+ printf '%s' "$T"
91
+ }
92
+
93
+ _card() { printf '# card\n' > "$1/tracks/_meta/reference_next_session_starter.md"; }
94
+ # ④ + ⑤ satisfied: completion log first, card LAST (card-last ordering) — so any exit 1 in the
95
+ # ① / ①-b lanes is attributable to the leg under test, not to ambient ④/⑤ noise.
96
+ _artifacts() { printf -- '- ✅ x\n' > "$1/tracks/_meta/fh_completed_${TODAY}.md"; _card "$1"; }
97
+
98
+ _run() { # $1=repo ; sets OUT and RC (RC read directly, no pipe)
99
+ # HERMETIC: stub `gh` to "no open PRs" for every lane that is not specifically testing ①-b.
100
+ # Without this the ambient environment leaked in — the ①-P lane passed only while this operator
101
+ # happened to have zero open PRs, and went red the moment a PR was opened mid-session. A lane
102
+ # that depends on the day's PR count is measuring the environment, not the code.
103
+ mkdir -p "$1/bin"
104
+ { printf '#!/usr/bin/env bash\necho "[]"\nexit 0\n'; } > "$1/bin/gh"
105
+ chmod +x "$1/bin/gh"
106
+ OUT=$(PATH="$1/bin:$PATH" bash "$CHECK" "$1" 2>/dev/null); RC=$?
107
+ }
108
+
109
+ echo "══ ① status snapshot ══"
110
+ T=$(_repo one_clean); _artifacts "$T"
111
+ _run "$T"
112
+ _line "①-P clean tree → ✅ clean line" '✅ ① working tree clean' 1 "$OUT"
113
+ # Pattern must not match `⚠️ ①-b` — a loose `⚠️ ①` conflated two different steps and made
114
+ # this absence assertion answer a question about the OPEN-PR sweep instead of the status snapshot.
115
+ _line "①-P clean tree → no ⚠️ ① line (paired)" '⚠️ ① ' 0 "$OUT"
116
+ _rc "①-P clean tree → exit 0" "$RC" 0
117
+
118
+ T=$(_repo one_dirty); _artifacts "$T"; echo scratch > "$T/uncommitted.txt"
119
+ _run "$T"
120
+ _line "①-N uncommitted path → ⚠️ fires" 'uncommitted path' 1 "$OUT"
121
+ _line "①-N uncommitted path → clean line absent" '✅ ① working tree clean' 0 "$OUT"
122
+ _rc "①-N uncommitted is ADVISORY, not blocking" "$RC" 0
123
+
124
+ # unpushed: needs a real upstream, so build a bare remote
125
+ T=$(_repo one_unpushed); _artifacts "$T"
126
+ (
127
+ cd "$T" || exit 1
128
+ git init -q --bare "$TMPROOT/one_unpushed.git" 2>/dev/null
129
+ git remote add origin "$TMPROOT/one_unpushed.git"
130
+ git push -q -u origin HEAD 2>/dev/null
131
+ echo more > second.txt && git add -A && git commit -qm second
132
+ ) >/dev/null 2>&1
133
+ _run "$T"
134
+ _line "①-N unpushed commit → ⚠️ fires" 'unpushed commit' 1 "$OUT"
135
+ _line "①-N unpushed → clean line absent (paired)" '✅ ① working tree clean' 0 "$OUT"
136
+
137
+ T="$TMPROOT/one_notrepo"; mkdir -p "$T/tracks/_meta"; _artifacts "$T"
138
+ _run "$T"
139
+ _g=0; printf '%s\n' "$OUT" | grep -q '✅ ① working tree clean' && _g=1
140
+ _gap "① non-repo reports CLEAN" "$_g" \
141
+ "git is unavailable/not a repo → DIRTY=0, UNPUSHED=0 → the check reports '✅ working tree clean'. \
142
+ An instrument that could not look is not a clean result (not found ≠ 0). Should say UNSCANNED."
143
+
144
+ echo
145
+ echo "══ ①-b open-PR sweep ══"
146
+ _ghstub() { # $1=repo $2=stdout $3=exit
147
+ mkdir -p "$1/bin"
148
+ { printf '#!/usr/bin/env bash\ncat <<'"'"'GHEOF'"'"'\n%s\nGHEOF\nexit %s\n' "$2" "$3"; } > "$1/bin/gh"
149
+ chmod +x "$1/bin/gh"
150
+ }
151
+ _run_with_gh() { # $1=repo
152
+ OUT=$(PATH="$1/bin:$PATH" bash "$CHECK" "$1" 2>/dev/null); RC=$?
153
+ }
154
+
155
+ T=$(_repo b_zero); _artifacts "$T"; _ghstub "$T" '[]' 0
156
+ _run_with_gh "$T"
157
+ _line "①-b-C no open PRs → no ①-b line (over-fire control)" '①-b' 0 "$OUT"
158
+
159
+ T=$(_repo b_one); _artifacts "$T"; _ghstub "$T" '[{"number":227}]' 0
160
+ _run_with_gh "$T"
161
+ _line "①-b-P1 one open PR → sweep line fires" '①-b 1 open PR' 1 "$OUT"
162
+
163
+ # COUNT CONSISTENCY. gh emits compact single-line JSON when piped (measured 2026-08-02:
164
+ # `[{"number":227},{"number":226},{"number":225}]` on ONE line), so a line-counting `grep -c`
165
+ # reports 1 for any non-zero number of PRs. CLAUDE.md ①-b's own origin note pairs this step with
166
+ # count-consistency, so a sweep that says "1 open PR" while three are open is a real defect, not
167
+ # a cosmetic one — the operator classifies what the sweep names.
168
+ T=$(_repo b_three); _artifacts "$T"
169
+ _ghstub "$T" '[{"number":227},{"number":226},{"number":225}]' 0
170
+ _run_with_gh "$T"
171
+ _line "①-b-P3 three open PRs → sweep says 3" '①-b 3 open PR' 1 "$OUT"
172
+
173
+ T=$(_repo b_err); _artifacts "$T"; _ghstub "$T" '' 4
174
+ _run_with_gh "$T"
175
+ _g=0; printf '%s\n' "$OUT" | grep -q '①-b' || _g=1
176
+ _gap "①-b gh ERROR is silent (indistinguishable from zero PRs)" "$_g" \
177
+ "gh present but failing (auth/offline, exit 4) → stderr discarded, count 0, NO line printed. \
178
+ The sweep 'not run' and the sweep 'found nothing' look identical. Advisory surface, so not \
179
+ fail-closed — but it should print 'sweep UNAVAILABLE' rather than nothing."
180
+
181
+ echo
182
+ echo "══ ④-log real-time completion log (the exit-1 FAIL path) ══"
183
+ # NOTE the label: this is NOT CLAUDE.md's ④ (memory hygiene, deliberately unmechanized — see
184
+ # CLAUDE.md §Session Wrap-up). The block was renamed 2026-08-02 because a green "④" implied memory
185
+ # hygiene had been verified when nothing checked it. These lanes follow the renamed label; if they
186
+ # ever go green against the OLD string again, the grep has stopped matching and the pair below is
187
+ # passing vacuously.
188
+ # The card is present and NEWEST in both ④ lanes on purpose: otherwise an exit 1 could be ⑤'s,
189
+ # and the lane would not discriminate which invariant fired (instrument-discrimination rule).
190
+ T=$(_repo four_missing); _card "$T" # commit today, fh_completed absent
191
+ _run "$T"
192
+ _line "④-N commits today + no fh_completed → ❌ fires" "❌ ④-log commits landed today" 1 "$OUT"
193
+ _line "④-N ⑤ still holds (failure attributable to ④)" '✅ ⑤ card is the newest' 1 "$OUT"
194
+ _rc "④-N → exit 1 (BLOCKS a close push)" "$RC" 1
195
+
196
+ T=$(_repo four_present)
197
+ printf -- '- ✅ something — done\n' > "$T/tracks/_meta/fh_completed_${TODAY}.md"
198
+ _card "$T" # card written LAST → card-last holds
199
+ _run "$T"
200
+ _line "④-P fh_completed present → no ❌ ④-log" '❌ ④-log' 0 "$OUT"
201
+ _line "④-P ⑤ ✅ present (known-positive)" '✅ ⑤ card is the newest' 1 "$OUT"
202
+ _rc "④-P → exit 0" "$RC" 0
203
+
204
+ T=$(_repo four_old "3 days ago"); _card "$T" # no commits today, no fh_completed
205
+ _run "$T"
206
+ _line "④-C no commits today → no ④ line at all (over-fire control)" '④-log' 0 "$OUT"
207
+ _rc "④-C → exit 0" "$RC" 0
208
+
209
+ T="$TMPROOT/four_notrepo"; mkdir -p "$T/tracks/_meta"; _card "$T"
210
+ _run "$T"
211
+ _g=0; { printf '%s\n' "$OUT" | grep -q '❌ ④' || true; } ; printf '%s\n' "$OUT" | grep -q '❌ ④' || _g=1
212
+ _gap "④ instrument-down degrades to PASS on a BLOCKING leg" "$_g" \
213
+ "No git → COMMITS_TODAY=0 → the ④ requirement silently evaporates and the check exits 0. \
214
+ ④ is one of only two legs that can block a close push; its trigger failing open means the block \
215
+ is only as reliable as git being readable. Should distinguish 'no commits' from 'could not count'."
216
+
217
+ echo
218
+ echo "══ ④-b npm freshness + BIDIRECTIONAL entry-point drift ══"
219
+ _tagged() { # $1=name $2..=paths to change AFTER the tag
220
+ local name="$1"; shift
221
+ local T="$TMPROOT/$name" p
222
+ mkdir -p "$T"
223
+ (
224
+ cd "$T" || exit 1
225
+ git init -q . 2>/dev/null
226
+ git config user.email anchor@local && git config user.name anchor
227
+ echo '{"name":"fx","version":"1.0.0"}' > package.json
228
+ echo base > CLAUDE.md; echo base > AGENTS.md
229
+ mkdir -p docs knowledge/shared/harness-core knowledge/shared/learnings
230
+ echo base > docs/codex-compat.md
231
+ echo base > knowledge/shared/harness-core/x.md
232
+ echo base > knowledge/shared/learnings/log.yaml
233
+ git add -A && git commit -qm base && git tag v1.0.0
234
+ for p in "$@"; do mkdir -p "$(dirname "$p")"; echo changed >> "$p"; done
235
+ git add -A && git commit -qm change
236
+ ) >/dev/null 2>&1
237
+ mkdir -p "$T/tracks/_meta"
238
+ printf -- '- ✅ x\n' > "$T/tracks/_meta/fh_completed_${TODAY}.md"
239
+ _card "$T"
240
+ printf '%s' "$T"
241
+ }
242
+ SHIP='④-b npm-shipped assets changed'
243
+ DRIFT_CC='drift candidate (CC→Codex)'
244
+ DRIFT_CX='drift candidate (Codex→CC)'
245
+
246
+ T=$(_tagged bb_cc CLAUDE.md); _run "$T"
247
+ _line "④-b-1 CLAUDE.md changed → republish reminder" "$SHIP" 1 "$OUT"
248
+ _line "④-b-1 → CC→Codex drift fires" "$DRIFT_CC" 1 "$OUT"
249
+ _line "④-b-1 → Codex→CC does NOT fire (paired)" "$DRIFT_CX" 0 "$OUT"
250
+ _rc "④-b-1 drift is ADVISORY (exit 0)" "$RC" 0
251
+
252
+ T=$(_tagged bb_cx AGENTS.md); _run "$T"
253
+ _line "④-b-2 AGENTS.md changed → republish reminder" "$SHIP" 1 "$OUT"
254
+ _line "④-b-2 → Codex→CC drift fires (the 07-19 miss)" "$DRIFT_CX" 1 "$OUT"
255
+ _line "④-b-2 → CC→Codex does NOT fire (paired)" "$DRIFT_CC" 0 "$OUT"
256
+
257
+ T=$(_tagged bb_cx2 docs/codex-compat.md); _run "$T"
258
+ _line "④-b-3 docs/codex-compat.md → Codex→CC fires" "$DRIFT_CX" 1 "$OUT"
259
+ _line "④-b-3 → CC→Codex does NOT fire (paired)" "$DRIFT_CC" 0 "$OUT"
260
+
261
+ T=$(_tagged bb_kn knowledge/shared/harness-core/x.md); _run "$T"
262
+ _line "④-b-4 shipped knowledge/ counts as a CC entry point" "$DRIFT_CC" 1 "$OUT"
263
+
264
+ T=$(_tagged bb_both CLAUDE.md AGENTS.md); _run "$T"
265
+ _line "④-b-5 both sides changed → republish reminder still" "$SHIP" 1 "$OUT"
266
+ _line "④-b-5 both sides → CC→Codex silent (over-fire ctrl)" "$DRIFT_CC" 0 "$OUT"
267
+ _line "④-b-5 both sides → Codex→CC silent (over-fire ctrl)" "$DRIFT_CX" 0 "$OUT"
268
+
269
+ T=$(_tagged bb_unshipped knowledge/shared/learnings/log.yaml); _run "$T"
270
+ _line "④-b-6 UNshipped path → no republish reminder" "$SHIP" 0 "$OUT"
271
+ _line "④-b-6 UNshipped path → no drift candidate" "$DRIFT_CC" 0 "$OUT"
272
+ _line "④-b-6 UNshipped path → ④-b is not silent-broken (control below)" '④-b' 0 "$OUT"
273
+
274
+ # no-tag fixture: identical CLAUDE.md change, but no version tag exists
275
+ T="$TMPROOT/bb_notag"; mkdir -p "$T"
276
+ (
277
+ cd "$T" || exit 1
278
+ git init -q . 2>/dev/null
279
+ git config user.email anchor@local && git config user.name anchor
280
+ echo '{"name":"fx","version":"1.0.0"}' > package.json
281
+ echo base > CLAUDE.md && git add -A && git commit -qm base
282
+ echo changed >> CLAUDE.md && git add -A && git commit -qm change
283
+ ) >/dev/null 2>&1
284
+ mkdir -p "$T/tracks/_meta"; printf -- '- ✅ x\n' > "$T/tracks/_meta/fh_completed_${TODAY}.md"; _card "$T"
285
+ _run "$T"
286
+ _g=0; printf '%s\n' "$OUT" | grep -q '④-b' || _g=1
287
+ _gap "④-b whole step vanishes when no version tag is reachable" "$_g" \
288
+ "LAST_TAG empty → the republish reminder AND both drift candidates are skipped silently. \
289
+ CLAUDE.md ④-b conditions the drift check on 'npm-shipped assets changed', not on a tag existing; \
290
+ a shallow/tagless clone or a pre-first-release state therefore runs a close with the entry-point \
291
+ drift check simply absent, and nothing says so."
292
+
293
+ echo
294
+ echo "══ pre-push SURFACE MATCHING (advise vs block) ══"
295
+ # A stubbed session_close_check lets these lanes control _SC_RC exactly, which is the variable
296
+ # under test. The real script's verdict logic is anchored above; what is anchored HERE is that
297
+ # the hook routes verdict → advisory or block by surface, and nothing else.
298
+ _hookfx() { # $1=name $2=stub exit code → echoes repo path
299
+ local T="$TMPROOT/$1"
300
+ mkdir -p "$T/scripts"
301
+ (
302
+ cd "$T" || exit 1
303
+ git init -q . 2>/dev/null
304
+ git config user.email anchor@local && git config user.name anchor
305
+ printf '#!/usr/bin/env bash\necho "❌ ⑤ card-last violated — synthetic"\nexit %s\n' "$2" \
306
+ > scripts/session_close_check.sh
307
+ chmod +x scripts/session_close_check.sh
308
+ git add -A && git commit -qm seed
309
+ ) >/dev/null 2>&1
310
+ cp "$HOOK" "$T/pre-push-under-test"
311
+ printf '%s' "$T"
312
+ }
313
+ _hookrun() { # $1=repo $2=FH_SESSION_CLOSE value ("" = unset)
314
+ local T="$1" v="$2" sha
315
+ sha=$(git -C "$T" rev-parse HEAD)
316
+ # ordinary NEW-BRANCH push: local_sha real, remote_sha zero → not a delete, not a force,
317
+ # not the integration branch → the hook must fall through to exit 0 unless a leg blocks.
318
+ OUT=$(cd "$T" && printf 'refs/heads/feat/x %s refs/heads/feat/x %s\n' "$sha" "0000000000000000000000000000000000000000" \
319
+ | env ${v:+FH_SESSION_CLOSE=$v} bash ./pre-push-under-test origin https://example.invalid/x.git 2>&1)
320
+ RC=$? # command substitution: this is the pipeline's status under pipefail, i.e. the HOOK's
321
+ }
322
+
323
+ T=$(_hookfx hook_adv 1); _hookrun "$T" ""
324
+ _rc "PP-1 ordinary push + violation → exit 0 (ADVISORY)" "$RC" 0
325
+ _line "PP-1 → the ❌ line is surfaced, not swallowed" '❌ ⑤ card-last violated' 1 "$OUT"
326
+ _line "PP-1 → says it is advisory + names the enforcing form" 'FH_SESSION_CLOSE=1' 1 "$OUT"
327
+
328
+ T=$(_hookfx hook_block 1); _hookrun "$T" 1
329
+ _rc "PP-2 close push + violation → exit 1 (BLOCKS)" "$RC" 1
330
+ _line "PP-2 → prints the enforcing banner" '⛔ FH Session-Close Check' 1 "$OUT"
331
+
332
+ T=$(_hookfx hook_ok 0); _hookrun "$T" 1
333
+ _rc "PP-3 close push + CLEAN → exit 0 (blocks on the VERDICT, not on the flag)" "$RC" 0
334
+ _line "PP-3 → prints the consistent line" '✅ FH Session-Close Check' 1 "$OUT"
335
+
336
+ T=$(_hookfx hook_ok2 0); _hookrun "$T" ""
337
+ _rc "PP-4 ordinary push + CLEAN → exit 0" "$RC" 0
338
+ _line "PP-4 → no advisory noise on a healthy push" 'close invariant(s) violated' 0 "$OUT"
339
+
340
+ echo
341
+ echo "──────────────────────────────────────────────"
342
+ if [ "$FAILED" -ne 0 ]; then
343
+ echo "CLOSE-CHAIN LANES: FAIL — the close-chain instrument is miscalibrated (do not trust its verdict)"
344
+ echo " asserting lanes passed: $PASSED · open gaps: $GAPS"
345
+ exit 1
346
+ fi
347
+ echo "CLOSE-CHAIN LANES: PASS ($PASSED asserting lanes) · $GAPS KNOWN GAP(S) still open (see ⓘ above)"
348
+ echo " A green line here covers ① ①-b ④ ④-b and the pre-push advise/block split — NOT the gaps."
349
+ exit 0