@chrono-meta/fh-gate 1.4.77 → 1.4.79
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 +63 -0
- package/.claude-plugin/marketplace.json +2 -2
- package/AGENTS.md +96 -260
- package/CLAUDE.md +2 -7
- package/docs/codex-compat.md +4 -1
- package/knowledge/shared/harness-core/agents_md_runtime_details.md +233 -0
- package/knowledge/shared/harness-core/loop_engineering.md +1 -1
- package/knowledge/shared/harness-core/multi_model_sidecar_strategy.md +1 -1
- package/knowledge/shared/learnings/subagent_invocations_log.yaml +14 -0
- package/knowledge/shared/rules/operational_adaptation.md +1 -130
- package/package.json +14 -5
- 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/install-doctor/SKILL.md +88 -0
- package/plugins/fh-meta/skills/install-wizard/SKILL.md +1 -1
- package/plugins/fh-meta/skills/install-wizard/SKILL_detail.md +117 -3
- package/scripts/fh_node_check.sh +184 -0
- package/scripts/fh_session_load.sh +101 -31
- package/scripts/halffix_propagation_scan.sh +126 -0
- package/scripts/package_coverage_check.sh +40 -1
- package/scripts/pipe_verdict_guard.sh +93 -0
- package/scripts/selfcheck.sh +114 -21
- package/scripts/session_close_check.sh +15 -1
- package/scripts/sidecar_calibrate.sh +275 -0
- package/scripts/test_card_drift_probe.sh +55 -0
- package/scripts/test_halffix_lanes.sh +170 -0
- package/scripts/test_node_check_lanes.sh +179 -0
- package/scripts/test_ollama_panel_lanes.sh +120 -0
- package/scripts/test_package_coverage_lanes.sh +250 -0
- package/scripts/test_pipe_verdict_guard_lanes.sh +96 -0
- package/scripts/test_sidecar_calibrate_lanes.sh +218 -0
- package/scripts/test_sidecar_wait_stdin.sh +13 -1
- package/templates/.git-hooks/pre-commit +9 -0
- package/templates/settings.PreToolUse.snippet.json +49 -0
- package/templates/settings.SessionStart.snippet.json +54 -0
- package/scripts/consent_registry_check.sh +0 -390
- package/scripts/test_consent_registry.sh +0 -255
- package/templates/consent_classes.yaml.example +0 -75
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# test_package_coverage_lanes.sh — regression anchor for scripts/package_coverage_check.sh.
|
|
3
|
+
#
|
|
4
|
+
# WHY THIS EXISTS (measured 2026-07-31):
|
|
5
|
+
# package_coverage_check.sh gated itself on `[ ! -d .git ]`. In a git WORKTREE `.git` is a FILE
|
|
6
|
+
# (a gitdir pointer), so every worktree fell into the "installed package" branch and the check
|
|
7
|
+
# printed `SKIP` and exited 0 without scanning anything. That is a FALSE CLEAN of the worst shape:
|
|
8
|
+
# a worktree is the standard way to approximate a fresh CI checkout, so the surface used to argue
|
|
9
|
+
# "CI would be green" was precisely the surface on which this check silently did not run.
|
|
10
|
+
# It was found by using the instrument, then asking whether the instrument had run at all —
|
|
11
|
+
# CLAUDE.md §Instrument-Calibration, applied to FH's own tooling.
|
|
12
|
+
#
|
|
13
|
+
# Second reason: selfcheck.sh enforces "subject present but anchor missing => FAIL" for eight
|
|
14
|
+
# other scripts, and package_coverage_check.sh was the one subject exempted from its own rule.
|
|
15
|
+
# An unanchored checker is one revert away from being decoration.
|
|
16
|
+
#
|
|
17
|
+
# The lanes pin, in order: the source-checkout predicate across all four tree shapes (worktree /
|
|
18
|
+
# ordinary checkout / package mode / no manifest), and a KNOWN PAIR — a tree that must FAIL and an
|
|
19
|
+
# otherwise identical tree that must PASS. A predicate that cannot separate that pair is not
|
|
20
|
+
# measuring coverage, it is emitting a verdict.
|
|
21
|
+
#
|
|
22
|
+
# Usage: bash scripts/test_package_coverage_lanes.sh
|
|
23
|
+
# Exit: 0 = no lane FAILED. 1 = a regression.
|
|
24
|
+
# NOT "all lanes passed": a lane whose precondition is absent (no git) is counted as
|
|
25
|
+
# UNCALIBRATED and printed in the summary, and exit stays 0. Stated precisely because the
|
|
26
|
+
# earlier wording promised more than the code delivers, and an exit contract that overstates
|
|
27
|
+
# is exactly the false-clean this suite exists to prevent (round-3 review, LOW).
|
|
28
|
+
set -uo pipefail
|
|
29
|
+
|
|
30
|
+
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
31
|
+
SUBJECT="$REPO_ROOT/scripts/package_coverage_check.sh"
|
|
32
|
+
[ -f "$SUBJECT" ] || { echo "FAIL: $SUBJECT not found"; exit 1; }
|
|
33
|
+
|
|
34
|
+
TMP="$(mktemp -d)"
|
|
35
|
+
trap 'rm -rf "$TMP"' EXIT
|
|
36
|
+
|
|
37
|
+
pass=0; fail=0; skipped=0
|
|
38
|
+
ok() { printf ' ✅ %s\n' "$1"; pass=$((pass+1)); }
|
|
39
|
+
bad() { printf ' ❌ %s\n' "$1"; fail=$((fail+1)); }
|
|
40
|
+
# A skip is COUNTED and reported. An uncounted skip is how "could not run" becomes indistinguishable
|
|
41
|
+
# from "ran and passed" in the summary line — measured on this very suite in review round 2, where a
|
|
42
|
+
# broken git produced "7 passed, 0 failed" and exit 0 while the real-worktree claim went untested.
|
|
43
|
+
# Degrade direction per CLAUDE.md §Instrument-Calibration: label it UNCALIBRATED, never a bare pass.
|
|
44
|
+
skip() { printf ' ⏭ UNCALIBRATED — %s\n' "$1"; skipped=$((skipped+1)); }
|
|
45
|
+
|
|
46
|
+
# Build a hermetic fake source tree. `git_shape` is one of: file | dir | none.
|
|
47
|
+
# `ship_the_doc` decides whether the referenced path is inside files[] (the known pair).
|
|
48
|
+
make_tree() { # make_tree <dir> <git_shape> <cover_target:yes|no> [omit_manifest]
|
|
49
|
+
local d="$1" git_shape="$2" cover="$3" omit="${4:-}"
|
|
50
|
+
mkdir -p "$d/scripts" "$d/docs"
|
|
51
|
+
cp "$SUBJECT" "$d/scripts/package_coverage_check.sh"
|
|
52
|
+
|
|
53
|
+
# A shipped document that points at scripts/helper.sh, and that file really exists here.
|
|
54
|
+
# "exists here but absent from files[]" is exactly the defect class this check owns.
|
|
55
|
+
printf 'Run `scripts/helper.sh` before the gate.\n' > "$d/docs/guide.md"
|
|
56
|
+
printf '#!/usr/bin/env bash\necho helper\n' > "$d/scripts/helper.sh"
|
|
57
|
+
|
|
58
|
+
if [ "$cover" = "yes" ]; then
|
|
59
|
+
printf '{"files":["docs/guide.md","scripts/helper.sh"]}\n' > "$d/package.json"
|
|
60
|
+
else
|
|
61
|
+
printf '{"files":["docs/guide.md"]}\n' > "$d/package.json"
|
|
62
|
+
fi
|
|
63
|
+
[ -n "$omit" ] && rm -f "$d/package.json"
|
|
64
|
+
|
|
65
|
+
case "$git_shape" in
|
|
66
|
+
dir) mkdir -p "$d/.git" ;;
|
|
67
|
+
file) printf 'gitdir: /somewhere/else/.git/worktrees/x\n' > "$d/.git" ;;
|
|
68
|
+
none) : ;;
|
|
69
|
+
esac
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
run_tree() { bash "$1/scripts/package_coverage_check.sh" 2>&1; }
|
|
73
|
+
rc_tree() { bash "$1/scripts/package_coverage_check.sh" >/dev/null 2>&1; echo $?; }
|
|
74
|
+
|
|
75
|
+
# Assert a POSITIVE outcome, never merely the absence of the SKIP line. Cross-family review caught
|
|
76
|
+
# the first draft here: it checked only that `SKIP package-coverage` was missing, so a checker that
|
|
77
|
+
# exited 1 on every worktree — the opposite defect, equally broken — would have passed the lane that
|
|
78
|
+
# exists to prove the worktree path works. "Did not say the wrong thing" is not "did the right
|
|
79
|
+
# thing"; a lane phrased as a negative can only ever fail one way.
|
|
80
|
+
ran_clean() { # ran_clean <dir> -> 0 iff the check actually ran AND reported a clean scan
|
|
81
|
+
local d="$1" o rc
|
|
82
|
+
o=$(bash "$d/scripts/package_coverage_check.sh" 2>&1); rc=$?
|
|
83
|
+
[ "$rc" -eq 0 ] && printf '%s' "$o" | grep -q 'PASS package-coverage' \
|
|
84
|
+
&& ! printf '%s' "$o" | grep -q 'SKIP package-coverage'
|
|
85
|
+
}
|
|
86
|
+
caught_defect() { # caught_defect <dir> -> 0 iff the check FOUND the planted omission
|
|
87
|
+
local d="$1" o rc
|
|
88
|
+
o=$(bash "$d/scripts/package_coverage_check.sh" 2>&1); rc=$?
|
|
89
|
+
[ "$rc" -eq 1 ] && printf '%s' "$o" | grep -q 'scripts/helper.sh'
|
|
90
|
+
}
|
|
91
|
+
# A CLEAN LANE ALONE PROVES NOTHING ABOUT THE WORKTREE PATH. Cross-family review round 2 demonstrated
|
|
92
|
+
# this by EXECUTION: it replaced the subject with a mutant that printed `PASS package-coverage`
|
|
93
|
+
# whenever `.git` was a file, without scanning anything — and all eight lanes passed, suite exit 0.
|
|
94
|
+
# The lanes proved "returned a PASS-shaped string", not "ran the coverage logic". The known-positive
|
|
95
|
+
# fixtures existed but only under the `.git`-is-a-DIRECTORY shape, so nothing exercised detection in
|
|
96
|
+
# the very shape the fix was about. Every worktree lane below is now a PAIR: the same tree must go
|
|
97
|
+
# clean->PASS and planted-omission->FAIL. A bypassing mutant fails the second half by construction.
|
|
98
|
+
wt_pair() { # wt_pair <label> <dir> <git_shape>
|
|
99
|
+
local label="$1" d="$2" shape="$3"
|
|
100
|
+
make_tree "$d" "$shape" yes
|
|
101
|
+
if ! ran_clean "$d"; then
|
|
102
|
+
bad "$label — clean leg: did not run to a PASS ($(run_tree "$d" | head -1))"; return
|
|
103
|
+
fi
|
|
104
|
+
make_tree "$d" "$shape" no # identical tree, files[] no longer covers the referenced path
|
|
105
|
+
if ! caught_defect "$d"; then
|
|
106
|
+
bad "$label — DEFECT leg: planted omission not caught, so the clean PASS proved nothing"; return
|
|
107
|
+
fi
|
|
108
|
+
ok "$label"
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
# ── L1 · THE REGRESSION ANCHOR ───────────────────────────────────────────────────────
|
|
112
|
+
wt_pair "L1 worktree (.git is a FILE): scans for real — clean PASSes, planted omission FAILs" \
|
|
113
|
+
"$TMP/l1" file
|
|
114
|
+
|
|
115
|
+
# ── L1-b · a REAL git worktree, not a hand-written pointer ───────────────────────────
|
|
116
|
+
# The synthetic fixture writes a gitdir pointer whose target does not exist, so it proves the
|
|
117
|
+
# predicate accepts "a file named .git" — not that it accepts a genuine worktree. This lane builds
|
|
118
|
+
# an actual repo and an actual `git worktree add`.
|
|
119
|
+
# ISOLATION (review round 2, LOW): the git commands run with signing off, hooks disabled, and both
|
|
120
|
+
# config files pointed at /dev/null, so a configured signing prompt or a global hook cannot hang the
|
|
121
|
+
# suite or write outside $TMP. GIT_TERMINAL_PROMPT=0 turns any credential prompt into an error.
|
|
122
|
+
# SKIP ACCOUNTING (review round 2, MED — confirmed by execution): the first draft printed a skip and
|
|
123
|
+
# incremented nothing, so a machine with a broken git reported "7 passed, 0 failed" and exited 0 —
|
|
124
|
+
# a lane that cannot run must not read as a lane that passed. Now: git ABSENT is a legitimate skip
|
|
125
|
+
# but is counted and surfaced as UNCALIBRATED in the summary; git PRESENT but setup failing is a
|
|
126
|
+
# FAILURE, because there the claim was testable and the test did not run.
|
|
127
|
+
GIT_ISO=(-c commit.gpgsign=false -c core.hooksPath=/dev/null -c user.email=a@b -c user.name=t)
|
|
128
|
+
# CONFIG isolation is not REPOSITORY-ROUTING isolation. Round-3 review reproduced this on Apple Git
|
|
129
|
+
# 2.50.1: with GIT_DIR pointing at an unrelated repo, this suite reported "8 passed, 0 failed" while
|
|
130
|
+
# that external repo's commit count went 0 -> 1. So the lane could pass by exercising — and WRITING
|
|
131
|
+
# TO — a repository that is not its fixture. A test that mutates state outside its own $TMP is not
|
|
132
|
+
# a test, and the green summary is the worst part: it reports calibration it did not perform.
|
|
133
|
+
# Every routing variable is cleared, and the resolved git-dir is then ASSERTED to live under $TMP
|
|
134
|
+
# rather than assumed — the unset list is a denylist and a denylist is never proof; the assertion is.
|
|
135
|
+
# SECOND LEAK CHANNEL, same class, found by the reviewer running the attack rather than reading for
|
|
136
|
+
# it: GIT_CONFIG_PARAMETERS injects config that GIT_CONFIG_GLOBAL/SYSTEM=/dev/null does NOT suppress.
|
|
137
|
+
# Reproduced here: with `init.templateDir` pointed at a directory whose `refs` is a symlink to an
|
|
138
|
+
# external dir, `git init` populated that external dir — and the suite still printed
|
|
139
|
+
# "8 passed, 0 failed". The git-dir assertion cannot see this one, because the git-dir DOES resolve
|
|
140
|
+
# inside $TMP; the escape is through the template, not the routing. So the assertion is necessary and
|
|
141
|
+
# not sufficient, and the config-injection channels have to be closed by name.
|
|
142
|
+
# `--template=` with an empty directory is the belt to that braces: even if a config channel is
|
|
143
|
+
# missed, init has an explicit, empty template to copy from.
|
|
144
|
+
unset GIT_DIR GIT_WORK_TREE GIT_INDEX_FILE GIT_COMMON_DIR GIT_OBJECT_DIRECTORY \
|
|
145
|
+
GIT_ALTERNATE_OBJECT_DIRECTORIES GIT_NAMESPACE GIT_CEILING_DIRECTORIES \
|
|
146
|
+
GIT_CONFIG_PARAMETERS GIT_CONFIG_COUNT GIT_TEMPLATE_DIR
|
|
147
|
+
export GIT_TERMINAL_PROMPT=0 GIT_CONFIG_GLOBAL=/dev/null GIT_CONFIG_SYSTEM=/dev/null
|
|
148
|
+
if ! command -v git >/dev/null 2>&1; then
|
|
149
|
+
skip "L1-b real git worktree (git not installed — the on-disk layout under test cannot exist here)"
|
|
150
|
+
else
|
|
151
|
+
mkdir -p "$TMP/realrepo"
|
|
152
|
+
# BOTH SIDES NORMALIZED. First version compared the raw `mktemp -d` path against
|
|
153
|
+
# `rev-parse --absolute-git-dir`, and on macOS that fails for a clean tree: mktemp hands back
|
|
154
|
+
# /var/folders/... while git resolves symlinks and returns /private/var/folders/... . The
|
|
155
|
+
# assertion then rejected its OWN correct fixture — a false positive on a safety check, which is
|
|
156
|
+
# the same defect class as a false negative and would have trained the next reader to delete it.
|
|
157
|
+
# `pwd -P` resolves the $TMP side; the other side is already physical because
|
|
158
|
+
# `rev-parse --absolute-git-dir` resolves symlinks itself. The raw "$TMP"/* branch is kept as a
|
|
159
|
+
# belt for platforms where it does not. (An earlier comment said "both sides normalized via
|
|
160
|
+
# pwd -P" — only one side uses it.)
|
|
161
|
+
_TMP_REAL="$(cd "$TMP" && pwd -P)"
|
|
162
|
+
# NAME THE ORDER HONESTLY: `git init` runs FIRST and this assertion runs immediately after, so it
|
|
163
|
+
# gates every subsequent write (commit, worktree add) — not the init itself. init is bounded
|
|
164
|
+
# separately by the unset list plus the explicit empty --template. An earlier comment claimed
|
|
165
|
+
# "before anything is written", which overstated it by one step.
|
|
166
|
+
_gitdir_ok() { # the fixture repo must resolve INSIDE $TMP before anything FURTHER is written
|
|
167
|
+
local r; r=$(git "${GIT_ISO[@]}" -C "$TMP/realrepo" rev-parse --absolute-git-dir 2>/dev/null) || return 1
|
|
168
|
+
case "$r" in
|
|
169
|
+
"$_TMP_REAL"/*|"$TMP"/*) return 0 ;;
|
|
170
|
+
*) echo " ↳ git-dir resolved OUTSIDE the fixture: $r" >&2; return 1 ;;
|
|
171
|
+
esac
|
|
172
|
+
}
|
|
173
|
+
mkdir -p "$TMP/emptytpl"
|
|
174
|
+
if git "${GIT_ISO[@]}" -C "$TMP/realrepo" init -q --template="$TMP/emptytpl" . >/dev/null 2>&1 \
|
|
175
|
+
&& _gitdir_ok \
|
|
176
|
+
&& git "${GIT_ISO[@]}" -C "$TMP/realrepo" commit -q --allow-empty -m init >/dev/null 2>&1 \
|
|
177
|
+
&& git "${GIT_ISO[@]}" -C "$TMP/realrepo" worktree add -q --detach "$TMP/realwt" >/dev/null 2>&1 \
|
|
178
|
+
&& [ -f "$TMP/realwt/.git" ]; then
|
|
179
|
+
wt_pair "L1-b real \`git worktree add\` (.git is a genuine gitdir pointer): scans for real" \
|
|
180
|
+
"$TMP/realwt" none # the REAL .git file is already in place; do not overwrite it
|
|
181
|
+
else
|
|
182
|
+
bad "L1-b git IS installed but the real-worktree fixture could not be built — the claim was testable and was not tested"
|
|
183
|
+
fi
|
|
184
|
+
fi
|
|
185
|
+
|
|
186
|
+
# ── L2 · ordinary checkout keeps working ─────────────────────────────────────────────
|
|
187
|
+
wt_pair "L2 ordinary checkout (.git is a DIR): scans for real — both legs" "$TMP/l2" dir
|
|
188
|
+
|
|
189
|
+
# ── L3 · package mode must STILL skip ────────────────────────────────────────────────
|
|
190
|
+
# The widening from -d to -e must not cost the legitimate skip. An installed npm package has no
|
|
191
|
+
# .git of either kind; making it fail there would fire on every consumer running `npm test`.
|
|
192
|
+
make_tree "$TMP/l3" none yes
|
|
193
|
+
out=$(run_tree "$TMP/l3"); rc=$(rc_tree "$TMP/l3")
|
|
194
|
+
if printf '%s' "$out" | grep -q 'SKIP package-coverage' && [ "$rc" -eq 0 ]; then
|
|
195
|
+
ok "L3 package mode (no .git): still skips, exit 0"
|
|
196
|
+
else
|
|
197
|
+
bad "L3 package mode (no .git): expected SKIP+0, got rc=$rc / $(printf '%s' "$out" | head -1)"
|
|
198
|
+
fi
|
|
199
|
+
|
|
200
|
+
# ── L4 · a checkout with no manifest is UNMEASURED, not clean ────────────────────────
|
|
201
|
+
# EXPECTATION CORRECTED by cross-family review, 2026-07-31. The first draft asserted SKIP+0 here
|
|
202
|
+
# and would have ANCHORED a fail-open: inside a checkout, a missing package.json means the shipped
|
|
203
|
+
# file list cannot be read, which is "cannot measure", not "nothing to measure". An anchor that
|
|
204
|
+
# pins the wrong direction is worse than no anchor — it makes the hole look deliberate.
|
|
205
|
+
make_tree "$TMP/l4" dir yes omit
|
|
206
|
+
out=$(run_tree "$TMP/l4"); rc=$(rc_tree "$TMP/l4")
|
|
207
|
+
if [ "$rc" -eq 1 ] && printf '%s' "$out" | grep -q 'UNMEASURED, not clean'; then
|
|
208
|
+
ok "L4 .git present but no package.json: FAILS as unmeasured, not a clean skip"
|
|
209
|
+
else
|
|
210
|
+
bad "L4 no package.json: expected exit 1 (unmeasured), got rc=$rc / $(printf '%s' "$out" | head -1)"
|
|
211
|
+
fi
|
|
212
|
+
|
|
213
|
+
# ── L5/L6 · KNOWN PAIR ───────────────────────────────────────────────────────────────
|
|
214
|
+
# Same tree twice; the ONLY difference is whether files[] covers the referenced path.
|
|
215
|
+
# L5 known-POSITIVE: referenced, exists, not shipped -> must FAIL.
|
|
216
|
+
make_tree "$TMP/l5" dir no
|
|
217
|
+
out=$(run_tree "$TMP/l5"); rc=$(rc_tree "$TMP/l5")
|
|
218
|
+
if [ "$rc" -eq 1 ] && printf '%s' "$out" | grep -q 'scripts/helper.sh'; then
|
|
219
|
+
ok "L5 known-positive (referenced ∧ exists ∧ ¬shipped): FAIL, names the path"
|
|
220
|
+
else
|
|
221
|
+
bad "L5 known-positive: expected exit 1 naming scripts/helper.sh, got rc=$rc"
|
|
222
|
+
fi
|
|
223
|
+
|
|
224
|
+
# L6 known-NEGATIVE: identical, but the path is in files[] -> must PASS.
|
|
225
|
+
make_tree "$TMP/l6" dir yes
|
|
226
|
+
out=$(run_tree "$TMP/l6"); rc=$(rc_tree "$TMP/l6")
|
|
227
|
+
if [ "$rc" -eq 0 ] && printf '%s' "$out" | grep -q 'PASS package-coverage'; then
|
|
228
|
+
ok "L6 known-negative (same tree, path shipped): PASS"
|
|
229
|
+
else
|
|
230
|
+
bad "L6 known-negative: expected exit 0 PASS, got rc=$rc"
|
|
231
|
+
fi
|
|
232
|
+
|
|
233
|
+
# ── L7 · the impossible-zero guard is not reachable by an empty files[] ──────────────
|
|
234
|
+
# A manifest with no shipped docs must report the extractor as broken, not print a pass.
|
|
235
|
+
mkdir -p "$TMP/l7/scripts"; cp "$SUBJECT" "$TMP/l7/scripts/"; mkdir -p "$TMP/l7/.git"
|
|
236
|
+
printf '{"files":[]}\n' > "$TMP/l7/package.json"
|
|
237
|
+
out=$(run_tree "$TMP/l7"); rc=$(rc_tree "$TMP/l7")
|
|
238
|
+
if [ "$rc" -eq 1 ] && printf '%s' "$out" | grep -q 'the check broke, it did not pass'; then
|
|
239
|
+
ok "L7 zero shipped docs: reported as broken extractor, not as a pass"
|
|
240
|
+
else
|
|
241
|
+
bad "L7 zero shipped docs: expected exit 1 'check broke', got rc=$rc"
|
|
242
|
+
fi
|
|
243
|
+
|
|
244
|
+
echo
|
|
245
|
+
if [ "$skipped" -gt 0 ]; then
|
|
246
|
+
echo "package-coverage lanes: ${pass} passed, ${fail} failed, ${skipped} UNCALIBRATED (not verified here)"
|
|
247
|
+
else
|
|
248
|
+
echo "package-coverage lanes: ${pass} passed, ${fail} failed"
|
|
249
|
+
fi
|
|
250
|
+
[ "$fail" -eq 0 ] || exit 1
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# test_pipe_verdict_guard_lanes.sh — known pairs for scripts/pipe_verdict_guard.sh
|
|
3
|
+
#
|
|
4
|
+
# WHY THIS FILE EXISTS, AND WHY IT IS WRITTEN BEFORE THE DETECTOR
|
|
5
|
+
# 2026-07-30 harvest #1: across a 5-round challenger run, three rounds contained a fix that
|
|
6
|
+
# reverted an earlier fix — and the rounds that wrote the lane BEFORE touching the code had
|
|
7
|
+
# zero such regressions. Convergence came from the ORDER, not the patch. So: lanes first.
|
|
8
|
+
#
|
|
9
|
+
# WHAT IS BEING GUARDED
|
|
10
|
+
# Reading a verdict from `$?`/`${PIPESTATUS[…]}` after a pipeline, which yields the status of
|
|
11
|
+
# the LAST stage. When the last stage is a display filter (`tail`, `head`, `cat`), that status
|
|
12
|
+
# is the filter's — a FAILING gate reads as exit 0. Degrade direction: toward PASS.
|
|
13
|
+
#
|
|
14
|
+
# R1 (deterministic, zero-FP): `${PIPESTATUS[...]}` under zsh. zsh spells it `$pipestatus[1]`
|
|
15
|
+
# and 1-indexes it; the bash array expands to the EMPTY STRING. Any verdict read from it is
|
|
16
|
+
# not merely wrong, it is absent. Measured 6× in this project's ad-hoc invocations.
|
|
17
|
+
# R2 (heuristic, narrowed): pipeline whose FINAL stage is a display filter, followed by a read
|
|
18
|
+
# of `$?`. Narrowed to display filters on purpose — see the FP lanes below.
|
|
19
|
+
#
|
|
20
|
+
# WHY A REPO-FILE LINTER IS THE WRONG SURFACE (measured 2026-07-31)
|
|
21
|
+
# The prescription on the session card was "add an S6 class to degrade_direction_scan.sh".
|
|
22
|
+
# Hand-verifying every `pipe + $?` hit in this repo's scripts returned 7 hits, 7 of them correct
|
|
23
|
+
# (the last stage WAS the command under test in all 7). True positives in shipped files: 0.
|
|
24
|
+
# All 6 measured recurrences were in interactively-composed commands, which no file scanner
|
|
25
|
+
# reads. Shipping S6 would have been a 0-true-positive probe — exactly the failure S5's own
|
|
26
|
+
# comment records ("100% FP trains dismissal of the one hit that will matter").
|
|
27
|
+
|
|
28
|
+
set -u
|
|
29
|
+
G="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/pipe_verdict_guard.sh"
|
|
30
|
+
pass=0; fail=0
|
|
31
|
+
|
|
32
|
+
# expect <label> <expected: HIT|CLEAN> <command-string>
|
|
33
|
+
expect() {
|
|
34
|
+
local label="$1" want="$2" cmd="$3" out got
|
|
35
|
+
out=$(printf '%s' "$cmd" | bash "$G" --stdin-raw 2>&1)
|
|
36
|
+
if printf '%s' "$out" | grep -q 'PIPE-VERDICT'; then got=HIT; else got=CLEAN; fi
|
|
37
|
+
if [ "$got" = "$want" ]; then
|
|
38
|
+
printf ' ✅ %-52s %s (expected %s)\n' "$label" "$got" "$want"; pass=$((pass+1))
|
|
39
|
+
else
|
|
40
|
+
printf ' ❌ %-52s %s (expected %s)\n' "$label" "$got" "$want"; fail=$((fail+1))
|
|
41
|
+
printf ' cmd: %s\n out: %s\n' "$cmd" "$out"
|
|
42
|
+
fi
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
echo "[pipe-verdict-guard] known pairs"
|
|
46
|
+
echo "-- R1: PIPESTATUS under zsh (deterministic) --"
|
|
47
|
+
# The exact shape emitted 6× in this project, including twice on 2026-07-31.
|
|
48
|
+
expect "R1 the measured shape" HIT 'bash x.sh | tail -5; echo "exit=${PIPESTATUS[0]}"'
|
|
49
|
+
expect "R1 any index" HIT 'a | b; rc=${PIPESTATUS[1]}'
|
|
50
|
+
expect "R1 inside a larger command" HIT 'cd /r && npm t | tail; E=${PIPESTATUS[0]}; echo $E'
|
|
51
|
+
# zsh's own spelling is correct here and must never be flagged.
|
|
52
|
+
expect "R1 zsh spelling is CLEAN" CLEAN 'a | b; rc=$pipestatus[1]'
|
|
53
|
+
|
|
54
|
+
echo "-- R2: display-filter final stage, then \$? --"
|
|
55
|
+
expect "R2 tail then \$?" HIT 'bash gate.sh | tail -20; echo "exit=$?"'
|
|
56
|
+
expect "R2 head then \$?" HIT 'make test | head -40; rc=$?'
|
|
57
|
+
expect "R2 cat then \$?" HIT 'run.sh | cat; if [ $? -ne 0 ]; then echo bad; fi'
|
|
58
|
+
|
|
59
|
+
echo "-- R2 false-positive lanes: the 7 shapes this repo actually ships --"
|
|
60
|
+
# Every one of these was hand-verified on 2026-07-31 as CORRECT: the final stage IS the command
|
|
61
|
+
# whose status is wanted. A detector that flags these is noise, and noise trains dismissal.
|
|
62
|
+
expect "FP grep -q is the test itself" CLEAN 'echo "$pos" | grep -qE "$re"; rc=$?'
|
|
63
|
+
expect "FP grep compiles the regex" CLEAN "printf '' | grep -E \"\$re\" >/dev/null 2>&1; rc=\$?"
|
|
64
|
+
# Path deliberately generic: naming a real unshipped script here would make this lane a shipped
|
|
65
|
+
# doc pointing at a file the package omits (caught by selfcheck's package-coverage rule).
|
|
66
|
+
expect "FP last stage is the script" CLEAN "printf '%s' \"\$S\" | bash some-filter.sh - ; echo EXIT:\$?"
|
|
67
|
+
expect "FP command substitution assign" CLEAN 'out=$(printf x | ( cd "$r" && bash hook 2>&1 )); rc=$?'
|
|
68
|
+
expect "FP no pipe at all" CLEAN 'bash gate.sh; echo "exit=$?"'
|
|
69
|
+
expect "FP || fallback, not a pipe" CLEAN 'stat -c %Y f || stat -f %m f || echo 0'
|
|
70
|
+
expect "FP pipefail set, explicit" CLEAN 'set -o pipefail; bash gate.sh | tail -5; rc=$?'
|
|
71
|
+
|
|
72
|
+
echo "-- adversarial (found by the Axis-2 pass on this guard, 2026-07-31) --"
|
|
73
|
+
# A: grep is line-oriented, so `.*` never spanned a newline and every MULTI-LINE command missed.
|
|
74
|
+
# This mattered more than the single-line case: the invocations that actually recur in this
|
|
75
|
+
# project are multi-line. Caught by attacking the guard, not by the happy-path lanes.
|
|
76
|
+
expect "A multi-line pipe then \$?" HIT 'bash gate.sh | tail -20
|
|
77
|
+
echo "exit=$?"'
|
|
78
|
+
expect "A multi-line, three statements" HIT 'cd /r
|
|
79
|
+
make test | head -40
|
|
80
|
+
rc=$?'
|
|
81
|
+
expect "A multi-line stays CLEAN if legit" CLEAN 'cd /r
|
|
82
|
+
echo x | grep -q y
|
|
83
|
+
rc=$?'
|
|
84
|
+
# B: zsh accepts `$PIPESTATUS[0]` without braces; the brace-anchored regex missed it.
|
|
85
|
+
expect "B PIPESTATUS without braces" HIT 'a | b; rc=$PIPESTATUS[0]'
|
|
86
|
+
expect "B braced form still caught" HIT 'a | b; rc=${PIPESTATUS[0]}'
|
|
87
|
+
|
|
88
|
+
echo "-- opt-out --"
|
|
89
|
+
expect "noqa suppresses" CLEAN 'bash g.sh | tail; rc=$? # noqa: pipe-verdict'
|
|
90
|
+
|
|
91
|
+
echo
|
|
92
|
+
if [ "$fail" -eq 0 ]; then
|
|
93
|
+
echo "[pipe-verdict-guard] ✅ all $pass known pairs hold"; exit 0
|
|
94
|
+
else
|
|
95
|
+
echo "[pipe-verdict-guard] ❌ $fail/$((pass+fail)) lanes failed"; exit 1
|
|
96
|
+
fi
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# test_sidecar_calibrate_lanes.sh — regression lanes for scripts/sidecar_calibrate.sh.
|
|
3
|
+
#
|
|
4
|
+
# WHY LANES FIRST: the calibrator's whole job is to distinguish states that LOOK the same from the
|
|
5
|
+
# outside — "the sidecar ran" vs "the model I asked for answered", "absent" vs "unmeasured". Those
|
|
6
|
+
# are negative legs, and negative legs are what three adversarial rounds on the node check showed
|
|
7
|
+
# nobody tests until a lane forces it. Each lane below drives the calibrator with a STUB CLI whose
|
|
8
|
+
# behaviour is known, so the calibrator's verdict can be checked against ground truth.
|
|
9
|
+
#
|
|
10
|
+
# No network, no API spend: every sidecar binary is replaced by a stub on PATH.
|
|
11
|
+
#
|
|
12
|
+
# Usage: bash scripts/test_sidecar_calibrate_lanes.sh
|
|
13
|
+
# Exit: 0 = all lanes pass; 1 = at least one failed.
|
|
14
|
+
|
|
15
|
+
set -uo pipefail
|
|
16
|
+
|
|
17
|
+
REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
18
|
+
CAL="$REPO/scripts/sidecar_calibrate.sh"
|
|
19
|
+
TMP="$(mktemp -d)"
|
|
20
|
+
trap 'rm -rf "$TMP"' EXIT
|
|
21
|
+
|
|
22
|
+
PASS=0; FAIL=0
|
|
23
|
+
ok() { PASS=$((PASS+1)); printf ' ✅ %s\n' "$1"; }
|
|
24
|
+
bad() { FAIL=$((FAIL+1)); printf ' ❌ %s\n got: %s\n' "$1" "$(printf '%s' "$2" | tr '\n' '|' | cut -c1-240)"; }
|
|
25
|
+
|
|
26
|
+
# stub <name> <behaviour> — write a fake sidecar CLI onto the lane PATH.
|
|
27
|
+
# honest : echoes back the model it was pinned to (a truthful runtime)
|
|
28
|
+
# silent-fallback: ALWAYS answers as one fixed model, whatever the pin (the agy trap)
|
|
29
|
+
# loud-reject : exits non-zero on an unknown pin (the codex behaviour)
|
|
30
|
+
# chatty : answers with agentic prose instead of the requested token (unparseable verdict)
|
|
31
|
+
# banner-echo : prints a session banner that REPEATS the pinned model, then answers as a
|
|
32
|
+
# different model — the real shape of `codex exec` stdout (measured 2026-07-30)
|
|
33
|
+
# listed-substitute: REJECTS an unknown name (so the bogus control says "rejects-bogus") yet
|
|
34
|
+
# silently serves a different model for a name that IS in its own catalogue —
|
|
35
|
+
# the measured agy shape (`gemini-3.1-pro-high` answered as 3.6 Flash, no error)
|
|
36
|
+
# alias-name : answers with the vendor's PRODUCT name carrying the right version but not every
|
|
37
|
+
# token of the pin slug — measured: pin `gpt-5.6-sol` → "GPT-5.6 Codex"
|
|
38
|
+
# absent : not created at all
|
|
39
|
+
mkstub() {
|
|
40
|
+
local name="$1" mode="$2" bin="$TMP/bin"
|
|
41
|
+
mkdir -p "$bin"
|
|
42
|
+
case "$mode" in
|
|
43
|
+
absent) rm -f "$bin/$name"; return ;;
|
|
44
|
+
esac
|
|
45
|
+
cat > "$bin/$name" <<STUB
|
|
46
|
+
#!/usr/bin/env bash
|
|
47
|
+
mode="$mode"
|
|
48
|
+
pin=""
|
|
49
|
+
prev=""
|
|
50
|
+
for a in "\$@"; do
|
|
51
|
+
case "\$prev" in -m|--model) pin="\$a" ;; esac
|
|
52
|
+
prev="\$a"
|
|
53
|
+
done
|
|
54
|
+
case "\$1" in --version) echo "stub 9.9.9"; exit 0 ;; esac
|
|
55
|
+
# Scan ALL arguments for the verdict prompt — it is NOT always last. codex puts the prompt at the
|
|
56
|
+
# end; agy puts it after -p with `--print-timeout 170s` trailing. A first draft read only the last
|
|
57
|
+
# argument, so the agy-shaped call never looked like a verdict request and lane5b failed against a
|
|
58
|
+
# correct script. An honest runtime ANSWERS THE QUESTION ASKED: a one-token request gets one token.
|
|
59
|
+
is_verdict=0
|
|
60
|
+
for a in "\$@"; do case "\$a" in *"PASS or FAIL"*) is_verdict=1 ;; esac; done
|
|
61
|
+
case "\$mode" in
|
|
62
|
+
loud-reject)
|
|
63
|
+
case "\$pin" in *nonexistent*|*bogus*) echo "ERROR: model '\$pin' is not supported" >&2; exit 1 ;; esac
|
|
64
|
+
if [ "\$is_verdict" -eq 1 ]; then echo "PASS"; else echo "I am \$pin, by StubCorp."; fi ;;
|
|
65
|
+
silent-fallback)
|
|
66
|
+
if [ "\$is_verdict" -eq 1 ]; then echo "PASS"; else echo "I am stub-flash-3.6, by StubCorp."; fi ;;
|
|
67
|
+
alias-name)
|
|
68
|
+
if [ "\$is_verdict" -eq 1 ]; then echo "PASS"; else echo "StubGPT-3.1 Coder"; fi ;;
|
|
69
|
+
listed-substitute)
|
|
70
|
+
case "\$pin" in *nonexistent*|*bogus*) echo "ERROR: model '\$pin' is not supported" >&2; exit 1 ;; esac
|
|
71
|
+
if [ "\$is_verdict" -eq 1 ]; then echo "PASS"; else echo "I am stub-flash-3.6, by StubCorp."; fi ;;
|
|
72
|
+
banner-echo)
|
|
73
|
+
echo "Reading additional input from stdin... CLI v0.0.0"
|
|
74
|
+
echo "--------"
|
|
75
|
+
echo "model: \$pin workdir: /tmp approval: never"
|
|
76
|
+
echo "--------"
|
|
77
|
+
if [ "\$is_verdict" -eq 1 ]; then echo "PASS"; else echo "I am stub-flash-3.6, by StubCorp."; fi ;;
|
|
78
|
+
chatty)
|
|
79
|
+
printf 'Summary of Work\n- considered the request\n- the answer is probably PASS\n' ;;
|
|
80
|
+
honest|*)
|
|
81
|
+
if [ "\$is_verdict" -eq 1 ]; then echo "PASS"; else echo "I am \$pin, by StubCorp."; fi ;;
|
|
82
|
+
esac
|
|
83
|
+
exit 0
|
|
84
|
+
STUB
|
|
85
|
+
chmod +x "$bin/$name"
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
# HERMETIC PATH — system paths only, plus the stub dir. Inheriting $PATH looked harmless and was
|
|
89
|
+
# not: "absent" is simulated by NOT creating a stub, so the real codex/agy further down $PATH were
|
|
90
|
+
# found instead and the lanes fired REAL, billed API calls (measured: a lane run hung past 120s
|
|
91
|
+
# against live runtimes). A test that can reach production is not a test.
|
|
92
|
+
run_cal() { PATH="$TMP/bin:/usr/bin:/bin:/usr/sbin:/sbin" bash "$CAL" --stub-model "stub-pro-3.1" "$@" 2>&1; }
|
|
93
|
+
|
|
94
|
+
echo "── sidecar-calibrate lanes ──"
|
|
95
|
+
|
|
96
|
+
# LANE 1 — absent runtime must read as ABSENT, never as a failure of the panel and never as "fine".
|
|
97
|
+
mkstub codex absent; mkstub agy absent
|
|
98
|
+
out="$(run_cal --only codex)"
|
|
99
|
+
case "$out" in
|
|
100
|
+
*"codex"*ABSENT*) ok "lane1 absent runtime reported ABSENT" ;;
|
|
101
|
+
*) bad "lane1 absent runtime not reported as ABSENT" "$out" ;;
|
|
102
|
+
esac
|
|
103
|
+
|
|
104
|
+
# LANE 2 — THE POINT OF THE WHOLE SCRIPT. A runtime that silently answers as a different model must
|
|
105
|
+
# be reported as UNTRUSTED-PIN, not as reachable. "The sidecar ran" and "the model I pinned answered"
|
|
106
|
+
# are different propositions; agy's slug fallback is the measured instance (2026-07-30).
|
|
107
|
+
mkstub agy silent-fallback
|
|
108
|
+
out="$(run_cal --only agy)"
|
|
109
|
+
case "$out" in
|
|
110
|
+
*UNTRUSTED-PIN*) ok "lane2 silent fallback caught (pinned model did not answer)" ;;
|
|
111
|
+
*) bad "lane2 silent fallback passed as a healthy sidecar" "$out" ;;
|
|
112
|
+
esac
|
|
113
|
+
|
|
114
|
+
# LANE 3 — an honest runtime that echoes its pin is PIN-OK.
|
|
115
|
+
mkstub agy honest
|
|
116
|
+
out="$(run_cal --only agy)"
|
|
117
|
+
case "$out" in
|
|
118
|
+
*PIN-OK*) ok "lane3 honest runtime reported PIN-OK" ;;
|
|
119
|
+
*) bad "lane3 honest runtime not reported PIN-OK" "$out" ;;
|
|
120
|
+
esac
|
|
121
|
+
|
|
122
|
+
# LANE 4 — the negative control must actually control. A runtime that ACCEPTS a bogus model pin has
|
|
123
|
+
# no server-side validation, so "it ran without error" proves nothing about which model answered;
|
|
124
|
+
# that must be visible in the report rather than inferred.
|
|
125
|
+
mkstub codex loud-reject
|
|
126
|
+
out="$(run_cal --only codex)"
|
|
127
|
+
case "$out" in
|
|
128
|
+
*"control: rejects-bogus"*) ok "lane4 bogus-pin control observed (runtime validates pins)" ;;
|
|
129
|
+
*) bad "lane4 bogus-pin control not reported" "$out" ;;
|
|
130
|
+
esac
|
|
131
|
+
mkstub codex honest # honest stub answers ANY pin, including a bogus one
|
|
132
|
+
out="$(run_cal --only codex)"
|
|
133
|
+
case "$out" in
|
|
134
|
+
*"control: accepts-bogus"*) ok "lane4b runtime accepting a bogus pin is flagged" ;;
|
|
135
|
+
*) bad "lane4b runtime accepting a bogus pin was not flagged" "$out" ;;
|
|
136
|
+
esac
|
|
137
|
+
|
|
138
|
+
# LANE 5 — verdict fitness is MEASURED, not assumed. A runtime that answers a one-token request with
|
|
139
|
+
# agentic prose cannot carry a machine-read verdict. The recorded 2026-07-04 finding said exactly
|
|
140
|
+
# this about agy at 1.0.14; the version has moved since, so the answer must be re-measured, never
|
|
141
|
+
# inherited.
|
|
142
|
+
mkstub agy chatty
|
|
143
|
+
out="$(run_cal --only agy)"
|
|
144
|
+
case "$out" in
|
|
145
|
+
*VERDICT-UNPARSEABLE*) ok "lane5 prose-answering runtime flagged VERDICT-UNPARSEABLE" ;;
|
|
146
|
+
*) bad "lane5 prose answer accepted as a usable verdict channel" "$out" ;;
|
|
147
|
+
esac
|
|
148
|
+
mkstub agy honest
|
|
149
|
+
out="$(run_cal --only agy)"
|
|
150
|
+
case "$out" in
|
|
151
|
+
*VERDICT-OK*) ok "lane5b token-answering runtime reported VERDICT-OK" ;;
|
|
152
|
+
*) bad "lane5b clean token answer not reported VERDICT-OK" "$out" ;;
|
|
153
|
+
esac
|
|
154
|
+
|
|
155
|
+
# LANE 8 (measured on the first real run) — BANNER ECHO MUST NOT SATISFY THE IDENTITY PROBE.
|
|
156
|
+
# Real CLIs print a session banner before the answer, and that banner repeats the pinned model name
|
|
157
|
+
# back (`codex exec` prints its version and config header). Matching against whole stdout therefore
|
|
158
|
+
# passes any runtime that merely echoes its own configuration — which is precisely the lie this
|
|
159
|
+
# script exists to catch, re-entering through the transport layer instead of the model.
|
|
160
|
+
mkstub agy banner-echo
|
|
161
|
+
out="$(run_cal --only agy)"
|
|
162
|
+
case "$out" in
|
|
163
|
+
*UNTRUSTED-PIN*) ok "lane8 banner echo rejected (config echo is not a self-report)" ;;
|
|
164
|
+
*) bad "lane8 banner echoing the pin passed the identity probe" "$out" ;;
|
|
165
|
+
esac
|
|
166
|
+
|
|
167
|
+
# LANE 10 (measured 2026-07-30) — a vendor product alias carrying the RIGHT VERSION is not a pin
|
|
168
|
+
# failure. Pinning `gpt-5.6-sol` returned "GPT-5.6 Codex": same family, same version, different
|
|
169
|
+
# product suffix. Requiring every token of the pin slug to appear made the calibrator report
|
|
170
|
+
# UNTRUSTED-PIN on a runtime whose pin had actually held — and that runtime also validates pins
|
|
171
|
+
# server-side, so the corroborating evidence was there to read. The version token is the
|
|
172
|
+
# discriminator that matters; the fallback cases (3.1 asked, 3.6 answered) differ exactly there.
|
|
173
|
+
mkstub agy alias-name
|
|
174
|
+
out="$(run_cal --only agy)"
|
|
175
|
+
case "$out" in
|
|
176
|
+
*PIN-OK*) ok "lane10 product alias with matching version accepted as PIN-OK" ;;
|
|
177
|
+
*) bad "lane10 matching version rejected because a suffix token was absent" "$out" ;;
|
|
178
|
+
esac
|
|
179
|
+
|
|
180
|
+
# LANE 9 (measured 2026-07-30) — "rejects unknown names" does NOT imply "serves known names
|
|
181
|
+
# faithfully", and the gap between them is where the real trap lives. agy rejects a nonsense model
|
|
182
|
+
# yet silently substituted Flash for `gemini-3.1-pro-high`, a slug from its OWN catalogue. A control
|
|
183
|
+
# that only probes nonsense therefore returns reassuring evidence about the wrong question: the
|
|
184
|
+
# identity probe must still decide, and the report must not let `rejects-bogus` read as "pin safe".
|
|
185
|
+
mkstub agy listed-substitute
|
|
186
|
+
out="$(run_cal --only agy)"
|
|
187
|
+
case "$out" in
|
|
188
|
+
*"control: rejects-bogus"*UNTRUSTED-PIN*|*UNTRUSTED-PIN*"control: rejects-bogus"*)
|
|
189
|
+
ok "lane9 validates unknown names yet substitutes a known one → still UNTRUSTED-PIN" ;;
|
|
190
|
+
*) bad "lane9 pin substitution masked by a passing bogus-control" "$out" ;;
|
|
191
|
+
esac
|
|
192
|
+
|
|
193
|
+
# LANE 6 — panel verdict. With no usable different-family sidecar the script must say so explicitly:
|
|
194
|
+
# this is the line a marker's `crossfamily:` leg quotes, and the 2026-07-30 defect was asserting
|
|
195
|
+
# "cross-family unavailable" without ever probing.
|
|
196
|
+
mkstub codex absent; mkstub agy absent
|
|
197
|
+
out="$(run_cal)"
|
|
198
|
+
case "$out" in
|
|
199
|
+
*"PANEL: none"*) ok "lane6 empty panel stated explicitly (not silence)" ;;
|
|
200
|
+
*) bad "lane6 empty panel not stated" "$out" ;;
|
|
201
|
+
esac
|
|
202
|
+
mkstub codex honest
|
|
203
|
+
out="$(run_cal)"
|
|
204
|
+
case "$out" in
|
|
205
|
+
*"PANEL: "*codex*) ok "lane6b populated panel names the usable runtime" ;;
|
|
206
|
+
*) bad "lane6b populated panel not named" "$out" ;;
|
|
207
|
+
esac
|
|
208
|
+
|
|
209
|
+
# LANE 7 — detector, not gate: always exit 0, so a calibration run can never block a caller. The
|
|
210
|
+
# caller reads the verdict; the script does not decide for it.
|
|
211
|
+
mkstub codex absent; mkstub agy absent
|
|
212
|
+
PATH="$TMP/bin:/usr/bin:/bin:/usr/sbin:/sbin" bash "$CAL" >/dev/null 2>&1
|
|
213
|
+
[ $? -eq 0 ] && ok "lane7 exits 0 even with an empty panel (detector, not gate)" \
|
|
214
|
+
|| bad "lane7 non-zero exit on empty panel" "exit=$?"
|
|
215
|
+
|
|
216
|
+
printf '\nsidecar-calibrate lanes: %d passed, %d failed\n' "$PASS" "$FAIL"
|
|
217
|
+
[ "$FAIL" -eq 0 ] || exit 1
|
|
218
|
+
exit 0
|
|
@@ -143,7 +143,19 @@ if command -v script >/dev/null 2>&1; then
|
|
|
143
143
|
# The inner shell writes its rc to a FILE. Parsing `script`'s own stdout fails: it emits ^D and
|
|
144
144
|
# CRLF, and the first version of this lane read rc as empty and reported a defect that did not
|
|
145
145
|
# exist — the target was fine, the instrument was not.
|
|
146
|
-
script
|
|
146
|
+
# `script(1)` HAS TWO INCOMPATIBLE CLIs and this lane only ever spoke one of them. BSD/macOS takes
|
|
147
|
+
# `script -q <file> <cmd> [args...]`; util-linux (every Linux runner) takes `script -q -c "<cmd>"
|
|
148
|
+
# <file>`. Given the BSD form, util-linux treats `bash` and `-c` as stray operands, never runs the
|
|
149
|
+
# inner command, and writes no rc file — so the lane read rc='<unread>' and reported a defect in
|
|
150
|
+
# sidecar_wait that did not exist. Measured on the first CI run, 2026-07-31; it had been invisible
|
|
151
|
+
# because this suite had only ever executed on macOS. Same shape as the Hangul-range collation bug
|
|
152
|
+
# found in the same run: a TOOL INTERFACE that differs by platform, silently.
|
|
153
|
+
_P9CMD="SIDECAR_POLL=1 timeout 12 bash '$SW' '$TD/p9.out' 3 -- cat >'$TD/p9.txt' 2>&1; echo \$? > '$TD/p9rc'"
|
|
154
|
+
if script --version 2>&1 | grep -qi util-linux; then
|
|
155
|
+
script -q -c "$_P9CMD" /dev/null >/dev/null 2>&1 < /dev/null
|
|
156
|
+
else
|
|
157
|
+
script -q /dev/null bash -c "$_P9CMD" >/dev/null 2>&1 < /dev/null
|
|
158
|
+
fi
|
|
147
159
|
rc9=$(tr -dc '0-9' < "$TD/p9rc" 2>/dev/null)
|
|
148
160
|
if [ -n "$rc9" ] && [ "$rc9" != "124" ] && grep -q 'SIDECAR_VERDICT=' "$TD/p9.txt" 2>/dev/null; then
|
|
149
161
|
ok "P9 the tty path runs and emits a verdict (UNCALIBRATED — see note; does NOT bind the branch)"
|
|
@@ -362,6 +362,15 @@ if [ -n "$EXEC_STAGED" ] && [ -z "$DOC_STAGED" ]; then
|
|
|
362
362
|
echo ""
|
|
363
363
|
fi
|
|
364
364
|
|
|
365
|
+
# ── Half-fix propagation (advisory — MARK, never block) ──────────────────────
|
|
366
|
+
# The operator's spec for this debt is explicit: 표시(차단 아님 — 정당한 복제도 있다).
|
|
367
|
+
# templates/ ships deliberate copies of scripts/, so blocking on duplication would block correct
|
|
368
|
+
# work; and a gate that fires when the author did the right thing is a gate that gets disabled.
|
|
369
|
+
# Failing to RUN is likewise never a finding: a missing/erroring scan is silent here, because this
|
|
370
|
+
# surface is a commit (reversible) — the fail-closed direction belongs to publish and delete.
|
|
371
|
+
HALFFIX="$REPO_ROOT/scripts/halffix_propagation_scan.sh"
|
|
372
|
+
[ -f "$HALFFIX" ] && bash "$HALFFIX" 2>&1 || true
|
|
373
|
+
|
|
365
374
|
# ── Axis 1 — Regression Guard (always required) ──────────────────────────────
|
|
366
375
|
echo "[Axis 1] Regression Guard..."
|
|
367
376
|
GUARD="$REPO_ROOT/templates/regression_guard.sh"
|