@biffo/cli 0.228.7 → 0.228.9
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.
|
@@ -78,6 +78,162 @@ set -u
|
|
|
78
78
|
LIST=""
|
|
79
79
|
[ "${1:-}" = "--list" ] && LIST=1
|
|
80
80
|
|
|
81
|
+
# Checkout health -- is the tree a command is about to trust stale or dirty in
|
|
82
|
+
# the ONE place that matters: the PRIMARY checkout. (#1196)
|
|
83
|
+
#
|
|
84
|
+
# AGENTS.md SS1/SS2 already say the primary must stay on the integration
|
|
85
|
+
# branch, no more than a `git fetch` behind, and that real work happens in
|
|
86
|
+
# worktrees instead -- but nothing checked it, and it happened again. An agent
|
|
87
|
+
# asked to migrate tabsii-crm's `auth.ts` read an 80-line file exporting five
|
|
88
|
+
# extra functions and built a whole, internally consistent, WRONG analysis on
|
|
89
|
+
# it: `origin/dev` held 36 lines and one export. The primary checkout it read
|
|
90
|
+
# was 16 commits behind, 1 ahead, dirty. The agent's reasoning was sound; its
|
|
91
|
+
# input was not, and nothing said so.
|
|
92
|
+
#
|
|
93
|
+
# This does not naturally belong to a PRE-PUSH file -- the incident above
|
|
94
|
+
# never touched git at all, it was a pure read, so a gate that only runs at
|
|
95
|
+
# push time could never have caught it by running later. It is here anyway,
|
|
96
|
+
# not in `scripts/hook-audit.sh` as the issue itself proposed, for a
|
|
97
|
+
# mechanical reason recorded rather than argued away: #1194 (gitleaks scope)
|
|
98
|
+
# and this were required to land in the same PR, in this file. Two things
|
|
99
|
+
# follow from that:
|
|
100
|
+
#
|
|
101
|
+
# `--checkout-health` is the part meant to answer #1196's actual question --
|
|
102
|
+
# run it BEFORE trusting a read, standalone, any time:
|
|
103
|
+
# sh scripts/verify.sh --checkout-health
|
|
104
|
+
#
|
|
105
|
+
# The WARN folded into the ordinary run further down is defence in depth for
|
|
106
|
+
# the one case an ordinary PUSH-TIME run can still see: a push attempted
|
|
107
|
+
# FROM the primary, which SS1 forbids outright regardless of staleness.
|
|
108
|
+
# It does not, and cannot, cover the read-only case the issue was filed for.
|
|
109
|
+
#
|
|
110
|
+
# Scope is deliberately narrow. A WORKTREE being behind `origin/dev` is normal
|
|
111
|
+
# mid-work -- that is what branching for a unit of work looks like -- so this
|
|
112
|
+
# never evaluates one. It only evaluates the ONE checkout AGENTS.md says must
|
|
113
|
+
# always mirror the integration branch: a non-worktree working tree whose
|
|
114
|
+
# CURRENT branch IS that branch. A worktree, a detached HEAD, or some other
|
|
115
|
+
# branch checked out at a repo root are all out of scope, and reported as
|
|
116
|
+
# such (`n/a`) rather than silently read as healthy for a question they were
|
|
117
|
+
# never asked.
|
|
118
|
+
CHECKOUT_HEALTH_BRANCH="${BIFFO_INTEGRATION_BRANCH:-dev}"
|
|
119
|
+
|
|
120
|
+
# A linked worktree's git-dir lives under the primary's `.git/worktrees/`; the
|
|
121
|
+
# primary's own git-dir does not. That is the one property distinguishing them
|
|
122
|
+
# regardless of where either happens to be checked out on disk.
|
|
123
|
+
_is_linked_worktree() {
|
|
124
|
+
case "$(git rev-parse --git-dir 2>/dev/null)" in
|
|
125
|
+
*/worktrees/*) return 0 ;;
|
|
126
|
+
*) return 1 ;;
|
|
127
|
+
esac
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
# Verdict in CHECKOUT_HEALTH_VERDICT: healthy | stale | n/a | detached | unknown.
|
|
131
|
+
# Detail (the "why", for stale/unknown) in CHECKOUT_HEALTH_DETAIL.
|
|
132
|
+
#
|
|
133
|
+
# Sets globals rather than echoing the verdict for `$(...)` capture on
|
|
134
|
+
# purpose: `_v=$(checkout_health)` runs the function in a SUBSHELL, and any
|
|
135
|
+
# variable it assigns -- CHECKOUT_HEALTH_DETAIL included -- dies with that
|
|
136
|
+
# subshell. The first version did exactly that and every non-trivial verdict
|
|
137
|
+
# crashed the caller under `set -u` reading a detail that command substitution
|
|
138
|
+
# had already thrown away. Caught by the fail-first rehearsal below, not by
|
|
139
|
+
# the happy-path `healthy` case, which never touches DETAIL and looked fine.
|
|
140
|
+
checkout_health() {
|
|
141
|
+
CHECKOUT_HEALTH_VERDICT=""
|
|
142
|
+
CHECKOUT_HEALTH_DETAIL=""
|
|
143
|
+
if _is_linked_worktree; then
|
|
144
|
+
CHECKOUT_HEALTH_VERDICT="n/a"
|
|
145
|
+
return 0
|
|
146
|
+
fi
|
|
147
|
+
_ch_branch=$(git symbolic-ref --short -q HEAD) || {
|
|
148
|
+
CHECKOUT_HEALTH_VERDICT="detached"
|
|
149
|
+
return 0
|
|
150
|
+
}
|
|
151
|
+
if [ "$_ch_branch" != "$CHECKOUT_HEALTH_BRANCH" ]; then
|
|
152
|
+
CHECKOUT_HEALTH_VERDICT="n/a"
|
|
153
|
+
return 0
|
|
154
|
+
fi
|
|
155
|
+
_ch_dirty=""
|
|
156
|
+
[ -n "$(git status --porcelain 2>/dev/null)" ] && _ch_dirty=1
|
|
157
|
+
|
|
158
|
+
if ! git remote get-url origin >/dev/null 2>&1; then
|
|
159
|
+
CHECKOUT_HEALTH_VERDICT="unknown"
|
|
160
|
+
CHECKOUT_HEALTH_DETAIL="no 'origin' remote configured"
|
|
161
|
+
return 0
|
|
162
|
+
fi
|
|
163
|
+
|
|
164
|
+
# A fetch may be the only way to know staleness -- do not hard-fail an
|
|
165
|
+
# offline machine over a question it structurally cannot answer. Bounded so
|
|
166
|
+
# a dead network cannot hang the gate the way an unbounded fetch could; a
|
|
167
|
+
# stale-but-cached remote-tracking ref is still evidence, just older.
|
|
168
|
+
_ch_fetched=1
|
|
169
|
+
timeout 10 git fetch --quiet origin "$CHECKOUT_HEALTH_BRANCH" 2>/dev/null || _ch_fetched=""
|
|
170
|
+
if [ -z "$_ch_fetched" ] && ! git rev-parse -q --verify "origin/$CHECKOUT_HEALTH_BRANCH" >/dev/null 2>&1; then
|
|
171
|
+
CHECKOUT_HEALTH_VERDICT="unknown"
|
|
172
|
+
CHECKOUT_HEALTH_DETAIL="could not reach origin, and no cached origin/$CHECKOUT_HEALTH_BRANCH to fall back on"
|
|
173
|
+
return 0
|
|
174
|
+
fi
|
|
175
|
+
|
|
176
|
+
_ch_behind=$(git rev-list --count "HEAD..origin/$CHECKOUT_HEALTH_BRANCH" 2>/dev/null || echo 0)
|
|
177
|
+
_ch_ahead=$(git rev-list --count "origin/$CHECKOUT_HEALTH_BRANCH..HEAD" 2>/dev/null || echo 0)
|
|
178
|
+
|
|
179
|
+
if [ -n "$_ch_dirty" ] || [ "${_ch_behind:-0}" -gt 0 ] || [ "${_ch_ahead:-0}" -gt 0 ]; then
|
|
180
|
+
CHECKOUT_HEALTH_VERDICT="stale"
|
|
181
|
+
_ch_bits=""
|
|
182
|
+
[ "${_ch_behind:-0}" -gt 0 ] && _ch_bits="$_ch_bits ${_ch_behind} behind"
|
|
183
|
+
[ "${_ch_ahead:-0}" -gt 0 ] && _ch_bits="$_ch_bits ${_ch_ahead} ahead"
|
|
184
|
+
[ -n "$_ch_dirty" ] && _ch_bits="$_ch_bits dirty"
|
|
185
|
+
CHECKOUT_HEALTH_DETAIL="${_ch_bits# }"
|
|
186
|
+
return 0
|
|
187
|
+
fi
|
|
188
|
+
|
|
189
|
+
CHECKOUT_HEALTH_VERDICT="healthy"
|
|
190
|
+
return 0
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
# Standalone entry point. Exits before any of the slower checks below run --
|
|
194
|
+
# answering "can I trust this tree" is the whole job, not a side effect of a
|
|
195
|
+
# full gate run. FAILS CLOSED on a confirmed-stale tree (exit 1) and on a tree
|
|
196
|
+
# it could not evaluate (exit 2) -- "cannot tell" is never a pass, the same
|
|
197
|
+
# convention `wait-for-checks.sh` and `branch-health.sh` already use for
|
|
198
|
+
# exactly this reason. It does not fail merely for BEING the primary: a clean,
|
|
199
|
+
# up-to-date primary on the integration branch is the correct, expected state,
|
|
200
|
+
# not a violation -- only staleness and dirt are.
|
|
201
|
+
if [ "${1:-}" = "--checkout-health" ]; then
|
|
202
|
+
checkout_health
|
|
203
|
+
case "$CHECKOUT_HEALTH_VERDICT" in
|
|
204
|
+
healthy)
|
|
205
|
+
printf 'checkout-health: OK - on %s, clean, matches origin/%s\n' \
|
|
206
|
+
"$CHECKOUT_HEALTH_BRANCH" "$CHECKOUT_HEALTH_BRANCH"
|
|
207
|
+
exit 0
|
|
208
|
+
;;
|
|
209
|
+
n/a)
|
|
210
|
+
printf 'checkout-health: n/a - not the primary checkout parked on %s (worktree, detached HEAD, or another branch)\n' \
|
|
211
|
+
"$CHECKOUT_HEALTH_BRANCH"
|
|
212
|
+
exit 0
|
|
213
|
+
;;
|
|
214
|
+
detached)
|
|
215
|
+
printf 'checkout-health: n/a - detached HEAD, not evaluated\n'
|
|
216
|
+
exit 0
|
|
217
|
+
;;
|
|
218
|
+
stale)
|
|
219
|
+
printf 'checkout-health: STALE - this checkout is %s\n' "$CHECKOUT_HEALTH_DETAIL"
|
|
220
|
+
printf 'AGENTS.md SS1/SS2: keep the primary on %s, no more than a git fetch\n' "$CHECKOUT_HEALTH_BRANCH"
|
|
221
|
+
printf 'behind, and do real work in a worktree instead. Do not trust anything\n'
|
|
222
|
+
printf 'read from this tree until: git fetch origin && git status\n'
|
|
223
|
+
exit 1
|
|
224
|
+
;;
|
|
225
|
+
unknown)
|
|
226
|
+
printf 'checkout-health: CANNOT TELL - %s\n' "$CHECKOUT_HEALTH_DETAIL"
|
|
227
|
+
printf 'Not a pass -- reconnect and re-run before trusting this tree.\n'
|
|
228
|
+
exit 2
|
|
229
|
+
;;
|
|
230
|
+
*)
|
|
231
|
+
printf 'checkout-health: CANNOT TELL - unexpected verdict "%s"\n' "$CHECKOUT_HEALTH_VERDICT"
|
|
232
|
+
exit 2
|
|
233
|
+
;;
|
|
234
|
+
esac
|
|
235
|
+
fi
|
|
236
|
+
|
|
81
237
|
FAILED=""
|
|
82
238
|
PASSED=""
|
|
83
239
|
SKIPPED=""
|
|
@@ -339,6 +495,25 @@ if [ -z "$LIST" ]; then
|
|
|
339
495
|
_stamp=""
|
|
340
496
|
[ -f .biffo-shared-version ] && _stamp=" (template $(cat .biffo-shared-version))"
|
|
341
497
|
printf '\nverify - the checks CI runs, before the push%s\n\n' "$_stamp"
|
|
498
|
+
|
|
499
|
+
# Defence in depth (#1196): the ordinary run only reaches the ONE case it
|
|
500
|
+
# can, a push attempted straight from the primary checkout -- see the long
|
|
501
|
+
# comment above `checkout_health` for why this is a WARN, not a FAIL, and
|
|
502
|
+
# why it does not (cannot) cover the read-only case the issue was filed for.
|
|
503
|
+
# A worktree -- the normal place this script runs -- returns `n/a` here
|
|
504
|
+
# before any git command runs, so this costs nothing on the common path.
|
|
505
|
+
checkout_health
|
|
506
|
+
case "$CHECKOUT_HEALTH_VERDICT" in
|
|
507
|
+
stale)
|
|
508
|
+
printf ' \033[33mWARN\033[0m %-16s this is the primary checkout on %s and it is %s\n' \
|
|
509
|
+
"checkout-health" "$CHECKOUT_HEALTH_BRANCH" "$CHECKOUT_HEALTH_DETAIL"
|
|
510
|
+
printf ' \033[33mAGENTS.md SS1/SS2: never edit or push from the primary -- do the work in a worktree instead.\033[0m\n'
|
|
511
|
+
NOT_RUN="$NOT_RUN checkout-health"
|
|
512
|
+
;;
|
|
513
|
+
unknown)
|
|
514
|
+
printf ' \033[33mWARN\033[0m %-16s could not verify - %s\n' "checkout-health" "$CHECKOUT_HEALTH_DETAIL"
|
|
515
|
+
;;
|
|
516
|
+
esac
|
|
342
517
|
fi
|
|
343
518
|
|
|
344
519
|
# Python first: ruff is near-instant, so the cheapest feedback on the largest
|
|
@@ -640,6 +815,59 @@ fi
|
|
|
640
815
|
# does nothing and lets the gate print `verify passed` is the precise failure this
|
|
641
816
|
# whole file exists to prevent, and it would be worse here than elsewhere: the
|
|
642
817
|
# thing not being checked is credentials.
|
|
818
|
+
#
|
|
819
|
+
# Scoped to TRACKED files only (#1194). `--no-git` walks the filesystem, not the
|
|
820
|
+
# index, and does not honour `.gitignore` -- so anything a build leaves behind
|
|
821
|
+
# gets scanned too. An agent in tabsii-crm ran `pnpm run build`, then hit this
|
|
822
|
+
# gate scanning 218MB of `.next/`/`out/` and got 30 phantom leaks, none in a
|
|
823
|
+
# tracked file, none of them committable. A scanner that cries wolf is worse
|
|
824
|
+
# than a slow one: the second time 30 leaks turn out to be bundle noise, people
|
|
825
|
+
# stop reading gitleaks output, which is exactly the day a real one hides in it.
|
|
826
|
+
#
|
|
827
|
+
# The fix is not a broader `.gitleaks.toml` allowlist -- that has to be kept in
|
|
828
|
+
# step with `.gitignore` by hand, in every repo this file runs in, forever, and
|
|
829
|
+
# it is the wrong shape besides: AGENTS.md SS7 says never fix a scan failure by
|
|
830
|
+
# editing the allowlist, and a path-list that grows to cover every build tool's
|
|
831
|
+
# output directory is that same fix wearing a different hat. It is narrowed to
|
|
832
|
+
# what the gate is actually FOR instead: nothing untracked can reach the remote
|
|
833
|
+
# a push sends to, so nothing untracked needs to be able to fail a push.
|
|
834
|
+
# `gitleaks_tracked_only` (below) mirrors `git ls-files` into a scratch
|
|
835
|
+
# directory -- current on-disk content, not HEAD, so a secret staged into an
|
|
836
|
+
# already-tracked file is still caught before the commit that would push it --
|
|
837
|
+
# and scans that copy. Relative paths inside the copy match the repo, so the
|
|
838
|
+
# existing path-based `.gitleaks.toml` allowlist entries keep working unchanged.
|
|
839
|
+
gitleaks_tracked_only() {
|
|
840
|
+
_gl_dir=$(mktemp -d "${TMPDIR:-/tmp}/biffo-gitleaks.XXXXXX") || return 1
|
|
841
|
+
_gl_root=$(git rev-parse --show-toplevel) || {
|
|
842
|
+
rm -rf "$_gl_dir"
|
|
843
|
+
return 1
|
|
844
|
+
}
|
|
845
|
+
# Newline-delimited, not `git ls-files -z` + `read -d ''`: `-d` is a bashism
|
|
846
|
+
# dash does not implement, and this file runs under `sh`
|
|
847
|
+
# (shell-portability.test.ts enforces it). The `plan_artefacts` loop above
|
|
848
|
+
# already made this exact call for the same reason.
|
|
849
|
+
( cd "$_gl_root" && git ls-files ) | while IFS= read -r _f; do
|
|
850
|
+
[ -f "$_gl_root/$_f" ] || continue
|
|
851
|
+
mkdir -p "$_gl_dir/$(dirname "$_f")"
|
|
852
|
+
cp -p "$_gl_root/$_f" "$_gl_dir/$_f" 2>/dev/null
|
|
853
|
+
done
|
|
854
|
+
# No explicit `--config`, deliberately. gitleaks' own default resolution
|
|
855
|
+
# looks for `.gitleaks.toml` at "(target path)", which is `--source` -- i.e.
|
|
856
|
+
# the mirrored copy, where the file already sits at its usual relative path
|
|
857
|
+
# if this repo tracks one. A first cut passed `--config "$_gl_root/.gitleaks.toml"`
|
|
858
|
+
# to remove any doubt about that resolution, and it was a regression:
|
|
859
|
+
# against a repo with NO `.gitleaks.toml` at all -- a real, valid state,
|
|
860
|
+
# since gitleaks otherwise falls back to its built-in default ruleset --
|
|
861
|
+
# a literal `--config` path that does not exist is FATAL ("unable to load
|
|
862
|
+
# gitleaks config"), where the flagless form degrades gracefully to
|
|
863
|
+
# defaults, identical to what this repo ran before #1194. Verified by
|
|
864
|
+
# running both forms against a `--source` with no config file present.
|
|
865
|
+
gitleaks detect --no-git --redact --exit-code=2 --source "$_gl_dir"
|
|
866
|
+
_gl_status=$?
|
|
867
|
+
rm -rf "$_gl_dir"
|
|
868
|
+
return $_gl_status
|
|
869
|
+
}
|
|
870
|
+
|
|
643
871
|
if ci_has "gitleaks"; then
|
|
644
872
|
# The installation check gates EXECUTION only, never `--list`.
|
|
645
873
|
#
|
|
@@ -653,7 +881,7 @@ if ci_has "gitleaks"; then
|
|
|
653
881
|
# `terraform-fmt` above has the same shape and is only unexposed because
|
|
654
882
|
# terraform happens to be installed here. Recorded, not fixed in this change.
|
|
655
883
|
if [ -n "$LIST" ] || command -v gitleaks >/dev/null 2>&1; then
|
|
656
|
-
run_check gitleaks
|
|
884
|
+
run_check gitleaks gitleaks_tracked_only
|
|
657
885
|
else
|
|
658
886
|
# Say how to close it, pinned to the version ci.yml installs. A skip that
|
|
659
887
|
# only reports its own absence stays skipped: this one sat `n/a` long
|
|
@@ -78,6 +78,162 @@ set -u
|
|
|
78
78
|
LIST=""
|
|
79
79
|
[ "${1:-}" = "--list" ] && LIST=1
|
|
80
80
|
|
|
81
|
+
# Checkout health -- is the tree a command is about to trust stale or dirty in
|
|
82
|
+
# the ONE place that matters: the PRIMARY checkout. (#1196)
|
|
83
|
+
#
|
|
84
|
+
# AGENTS.md SS1/SS2 already say the primary must stay on the integration
|
|
85
|
+
# branch, no more than a `git fetch` behind, and that real work happens in
|
|
86
|
+
# worktrees instead -- but nothing checked it, and it happened again. An agent
|
|
87
|
+
# asked to migrate tabsii-crm's `auth.ts` read an 80-line file exporting five
|
|
88
|
+
# extra functions and built a whole, internally consistent, WRONG analysis on
|
|
89
|
+
# it: `origin/dev` held 36 lines and one export. The primary checkout it read
|
|
90
|
+
# was 16 commits behind, 1 ahead, dirty. The agent's reasoning was sound; its
|
|
91
|
+
# input was not, and nothing said so.
|
|
92
|
+
#
|
|
93
|
+
# This does not naturally belong to a PRE-PUSH file -- the incident above
|
|
94
|
+
# never touched git at all, it was a pure read, so a gate that only runs at
|
|
95
|
+
# push time could never have caught it by running later. It is here anyway,
|
|
96
|
+
# not in `scripts/hook-audit.sh` as the issue itself proposed, for a
|
|
97
|
+
# mechanical reason recorded rather than argued away: #1194 (gitleaks scope)
|
|
98
|
+
# and this were required to land in the same PR, in this file. Two things
|
|
99
|
+
# follow from that:
|
|
100
|
+
#
|
|
101
|
+
# `--checkout-health` is the part meant to answer #1196's actual question --
|
|
102
|
+
# run it BEFORE trusting a read, standalone, any time:
|
|
103
|
+
# sh scripts/verify.sh --checkout-health
|
|
104
|
+
#
|
|
105
|
+
# The WARN folded into the ordinary run further down is defence in depth for
|
|
106
|
+
# the one case an ordinary PUSH-TIME run can still see: a push attempted
|
|
107
|
+
# FROM the primary, which SS1 forbids outright regardless of staleness.
|
|
108
|
+
# It does not, and cannot, cover the read-only case the issue was filed for.
|
|
109
|
+
#
|
|
110
|
+
# Scope is deliberately narrow. A WORKTREE being behind `origin/dev` is normal
|
|
111
|
+
# mid-work -- that is what branching for a unit of work looks like -- so this
|
|
112
|
+
# never evaluates one. It only evaluates the ONE checkout AGENTS.md says must
|
|
113
|
+
# always mirror the integration branch: a non-worktree working tree whose
|
|
114
|
+
# CURRENT branch IS that branch. A worktree, a detached HEAD, or some other
|
|
115
|
+
# branch checked out at a repo root are all out of scope, and reported as
|
|
116
|
+
# such (`n/a`) rather than silently read as healthy for a question they were
|
|
117
|
+
# never asked.
|
|
118
|
+
CHECKOUT_HEALTH_BRANCH="${BIFFO_INTEGRATION_BRANCH:-dev}"
|
|
119
|
+
|
|
120
|
+
# A linked worktree's git-dir lives under the primary's `.git/worktrees/`; the
|
|
121
|
+
# primary's own git-dir does not. That is the one property distinguishing them
|
|
122
|
+
# regardless of where either happens to be checked out on disk.
|
|
123
|
+
_is_linked_worktree() {
|
|
124
|
+
case "$(git rev-parse --git-dir 2>/dev/null)" in
|
|
125
|
+
*/worktrees/*) return 0 ;;
|
|
126
|
+
*) return 1 ;;
|
|
127
|
+
esac
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
# Verdict in CHECKOUT_HEALTH_VERDICT: healthy | stale | n/a | detached | unknown.
|
|
131
|
+
# Detail (the "why", for stale/unknown) in CHECKOUT_HEALTH_DETAIL.
|
|
132
|
+
#
|
|
133
|
+
# Sets globals rather than echoing the verdict for `$(...)` capture on
|
|
134
|
+
# purpose: `_v=$(checkout_health)` runs the function in a SUBSHELL, and any
|
|
135
|
+
# variable it assigns -- CHECKOUT_HEALTH_DETAIL included -- dies with that
|
|
136
|
+
# subshell. The first version did exactly that and every non-trivial verdict
|
|
137
|
+
# crashed the caller under `set -u` reading a detail that command substitution
|
|
138
|
+
# had already thrown away. Caught by the fail-first rehearsal below, not by
|
|
139
|
+
# the happy-path `healthy` case, which never touches DETAIL and looked fine.
|
|
140
|
+
checkout_health() {
|
|
141
|
+
CHECKOUT_HEALTH_VERDICT=""
|
|
142
|
+
CHECKOUT_HEALTH_DETAIL=""
|
|
143
|
+
if _is_linked_worktree; then
|
|
144
|
+
CHECKOUT_HEALTH_VERDICT="n/a"
|
|
145
|
+
return 0
|
|
146
|
+
fi
|
|
147
|
+
_ch_branch=$(git symbolic-ref --short -q HEAD) || {
|
|
148
|
+
CHECKOUT_HEALTH_VERDICT="detached"
|
|
149
|
+
return 0
|
|
150
|
+
}
|
|
151
|
+
if [ "$_ch_branch" != "$CHECKOUT_HEALTH_BRANCH" ]; then
|
|
152
|
+
CHECKOUT_HEALTH_VERDICT="n/a"
|
|
153
|
+
return 0
|
|
154
|
+
fi
|
|
155
|
+
_ch_dirty=""
|
|
156
|
+
[ -n "$(git status --porcelain 2>/dev/null)" ] && _ch_dirty=1
|
|
157
|
+
|
|
158
|
+
if ! git remote get-url origin >/dev/null 2>&1; then
|
|
159
|
+
CHECKOUT_HEALTH_VERDICT="unknown"
|
|
160
|
+
CHECKOUT_HEALTH_DETAIL="no 'origin' remote configured"
|
|
161
|
+
return 0
|
|
162
|
+
fi
|
|
163
|
+
|
|
164
|
+
# A fetch may be the only way to know staleness -- do not hard-fail an
|
|
165
|
+
# offline machine over a question it structurally cannot answer. Bounded so
|
|
166
|
+
# a dead network cannot hang the gate the way an unbounded fetch could; a
|
|
167
|
+
# stale-but-cached remote-tracking ref is still evidence, just older.
|
|
168
|
+
_ch_fetched=1
|
|
169
|
+
timeout 10 git fetch --quiet origin "$CHECKOUT_HEALTH_BRANCH" 2>/dev/null || _ch_fetched=""
|
|
170
|
+
if [ -z "$_ch_fetched" ] && ! git rev-parse -q --verify "origin/$CHECKOUT_HEALTH_BRANCH" >/dev/null 2>&1; then
|
|
171
|
+
CHECKOUT_HEALTH_VERDICT="unknown"
|
|
172
|
+
CHECKOUT_HEALTH_DETAIL="could not reach origin, and no cached origin/$CHECKOUT_HEALTH_BRANCH to fall back on"
|
|
173
|
+
return 0
|
|
174
|
+
fi
|
|
175
|
+
|
|
176
|
+
_ch_behind=$(git rev-list --count "HEAD..origin/$CHECKOUT_HEALTH_BRANCH" 2>/dev/null || echo 0)
|
|
177
|
+
_ch_ahead=$(git rev-list --count "origin/$CHECKOUT_HEALTH_BRANCH..HEAD" 2>/dev/null || echo 0)
|
|
178
|
+
|
|
179
|
+
if [ -n "$_ch_dirty" ] || [ "${_ch_behind:-0}" -gt 0 ] || [ "${_ch_ahead:-0}" -gt 0 ]; then
|
|
180
|
+
CHECKOUT_HEALTH_VERDICT="stale"
|
|
181
|
+
_ch_bits=""
|
|
182
|
+
[ "${_ch_behind:-0}" -gt 0 ] && _ch_bits="$_ch_bits ${_ch_behind} behind"
|
|
183
|
+
[ "${_ch_ahead:-0}" -gt 0 ] && _ch_bits="$_ch_bits ${_ch_ahead} ahead"
|
|
184
|
+
[ -n "$_ch_dirty" ] && _ch_bits="$_ch_bits dirty"
|
|
185
|
+
CHECKOUT_HEALTH_DETAIL="${_ch_bits# }"
|
|
186
|
+
return 0
|
|
187
|
+
fi
|
|
188
|
+
|
|
189
|
+
CHECKOUT_HEALTH_VERDICT="healthy"
|
|
190
|
+
return 0
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
# Standalone entry point. Exits before any of the slower checks below run --
|
|
194
|
+
# answering "can I trust this tree" is the whole job, not a side effect of a
|
|
195
|
+
# full gate run. FAILS CLOSED on a confirmed-stale tree (exit 1) and on a tree
|
|
196
|
+
# it could not evaluate (exit 2) -- "cannot tell" is never a pass, the same
|
|
197
|
+
# convention `wait-for-checks.sh` and `branch-health.sh` already use for
|
|
198
|
+
# exactly this reason. It does not fail merely for BEING the primary: a clean,
|
|
199
|
+
# up-to-date primary on the integration branch is the correct, expected state,
|
|
200
|
+
# not a violation -- only staleness and dirt are.
|
|
201
|
+
if [ "${1:-}" = "--checkout-health" ]; then
|
|
202
|
+
checkout_health
|
|
203
|
+
case "$CHECKOUT_HEALTH_VERDICT" in
|
|
204
|
+
healthy)
|
|
205
|
+
printf 'checkout-health: OK - on %s, clean, matches origin/%s\n' \
|
|
206
|
+
"$CHECKOUT_HEALTH_BRANCH" "$CHECKOUT_HEALTH_BRANCH"
|
|
207
|
+
exit 0
|
|
208
|
+
;;
|
|
209
|
+
n/a)
|
|
210
|
+
printf 'checkout-health: n/a - not the primary checkout parked on %s (worktree, detached HEAD, or another branch)\n' \
|
|
211
|
+
"$CHECKOUT_HEALTH_BRANCH"
|
|
212
|
+
exit 0
|
|
213
|
+
;;
|
|
214
|
+
detached)
|
|
215
|
+
printf 'checkout-health: n/a - detached HEAD, not evaluated\n'
|
|
216
|
+
exit 0
|
|
217
|
+
;;
|
|
218
|
+
stale)
|
|
219
|
+
printf 'checkout-health: STALE - this checkout is %s\n' "$CHECKOUT_HEALTH_DETAIL"
|
|
220
|
+
printf 'AGENTS.md SS1/SS2: keep the primary on %s, no more than a git fetch\n' "$CHECKOUT_HEALTH_BRANCH"
|
|
221
|
+
printf 'behind, and do real work in a worktree instead. Do not trust anything\n'
|
|
222
|
+
printf 'read from this tree until: git fetch origin && git status\n'
|
|
223
|
+
exit 1
|
|
224
|
+
;;
|
|
225
|
+
unknown)
|
|
226
|
+
printf 'checkout-health: CANNOT TELL - %s\n' "$CHECKOUT_HEALTH_DETAIL"
|
|
227
|
+
printf 'Not a pass -- reconnect and re-run before trusting this tree.\n'
|
|
228
|
+
exit 2
|
|
229
|
+
;;
|
|
230
|
+
*)
|
|
231
|
+
printf 'checkout-health: CANNOT TELL - unexpected verdict "%s"\n' "$CHECKOUT_HEALTH_VERDICT"
|
|
232
|
+
exit 2
|
|
233
|
+
;;
|
|
234
|
+
esac
|
|
235
|
+
fi
|
|
236
|
+
|
|
81
237
|
FAILED=""
|
|
82
238
|
PASSED=""
|
|
83
239
|
SKIPPED=""
|
|
@@ -339,6 +495,25 @@ if [ -z "$LIST" ]; then
|
|
|
339
495
|
_stamp=""
|
|
340
496
|
[ -f .biffo-shared-version ] && _stamp=" (template $(cat .biffo-shared-version))"
|
|
341
497
|
printf '\nverify - the checks CI runs, before the push%s\n\n' "$_stamp"
|
|
498
|
+
|
|
499
|
+
# Defence in depth (#1196): the ordinary run only reaches the ONE case it
|
|
500
|
+
# can, a push attempted straight from the primary checkout -- see the long
|
|
501
|
+
# comment above `checkout_health` for why this is a WARN, not a FAIL, and
|
|
502
|
+
# why it does not (cannot) cover the read-only case the issue was filed for.
|
|
503
|
+
# A worktree -- the normal place this script runs -- returns `n/a` here
|
|
504
|
+
# before any git command runs, so this costs nothing on the common path.
|
|
505
|
+
checkout_health
|
|
506
|
+
case "$CHECKOUT_HEALTH_VERDICT" in
|
|
507
|
+
stale)
|
|
508
|
+
printf ' \033[33mWARN\033[0m %-16s this is the primary checkout on %s and it is %s\n' \
|
|
509
|
+
"checkout-health" "$CHECKOUT_HEALTH_BRANCH" "$CHECKOUT_HEALTH_DETAIL"
|
|
510
|
+
printf ' \033[33mAGENTS.md SS1/SS2: never edit or push from the primary -- do the work in a worktree instead.\033[0m\n'
|
|
511
|
+
NOT_RUN="$NOT_RUN checkout-health"
|
|
512
|
+
;;
|
|
513
|
+
unknown)
|
|
514
|
+
printf ' \033[33mWARN\033[0m %-16s could not verify - %s\n' "checkout-health" "$CHECKOUT_HEALTH_DETAIL"
|
|
515
|
+
;;
|
|
516
|
+
esac
|
|
342
517
|
fi
|
|
343
518
|
|
|
344
519
|
# Python first: ruff is near-instant, so the cheapest feedback on the largest
|
|
@@ -640,6 +815,59 @@ fi
|
|
|
640
815
|
# does nothing and lets the gate print `verify passed` is the precise failure this
|
|
641
816
|
# whole file exists to prevent, and it would be worse here than elsewhere: the
|
|
642
817
|
# thing not being checked is credentials.
|
|
818
|
+
#
|
|
819
|
+
# Scoped to TRACKED files only (#1194). `--no-git` walks the filesystem, not the
|
|
820
|
+
# index, and does not honour `.gitignore` -- so anything a build leaves behind
|
|
821
|
+
# gets scanned too. An agent in tabsii-crm ran `pnpm run build`, then hit this
|
|
822
|
+
# gate scanning 218MB of `.next/`/`out/` and got 30 phantom leaks, none in a
|
|
823
|
+
# tracked file, none of them committable. A scanner that cries wolf is worse
|
|
824
|
+
# than a slow one: the second time 30 leaks turn out to be bundle noise, people
|
|
825
|
+
# stop reading gitleaks output, which is exactly the day a real one hides in it.
|
|
826
|
+
#
|
|
827
|
+
# The fix is not a broader `.gitleaks.toml` allowlist -- that has to be kept in
|
|
828
|
+
# step with `.gitignore` by hand, in every repo this file runs in, forever, and
|
|
829
|
+
# it is the wrong shape besides: AGENTS.md SS7 says never fix a scan failure by
|
|
830
|
+
# editing the allowlist, and a path-list that grows to cover every build tool's
|
|
831
|
+
# output directory is that same fix wearing a different hat. It is narrowed to
|
|
832
|
+
# what the gate is actually FOR instead: nothing untracked can reach the remote
|
|
833
|
+
# a push sends to, so nothing untracked needs to be able to fail a push.
|
|
834
|
+
# `gitleaks_tracked_only` (below) mirrors `git ls-files` into a scratch
|
|
835
|
+
# directory -- current on-disk content, not HEAD, so a secret staged into an
|
|
836
|
+
# already-tracked file is still caught before the commit that would push it --
|
|
837
|
+
# and scans that copy. Relative paths inside the copy match the repo, so the
|
|
838
|
+
# existing path-based `.gitleaks.toml` allowlist entries keep working unchanged.
|
|
839
|
+
gitleaks_tracked_only() {
|
|
840
|
+
_gl_dir=$(mktemp -d "${TMPDIR:-/tmp}/biffo-gitleaks.XXXXXX") || return 1
|
|
841
|
+
_gl_root=$(git rev-parse --show-toplevel) || {
|
|
842
|
+
rm -rf "$_gl_dir"
|
|
843
|
+
return 1
|
|
844
|
+
}
|
|
845
|
+
# Newline-delimited, not `git ls-files -z` + `read -d ''`: `-d` is a bashism
|
|
846
|
+
# dash does not implement, and this file runs under `sh`
|
|
847
|
+
# (shell-portability.test.ts enforces it). The `plan_artefacts` loop above
|
|
848
|
+
# already made this exact call for the same reason.
|
|
849
|
+
( cd "$_gl_root" && git ls-files ) | while IFS= read -r _f; do
|
|
850
|
+
[ -f "$_gl_root/$_f" ] || continue
|
|
851
|
+
mkdir -p "$_gl_dir/$(dirname "$_f")"
|
|
852
|
+
cp -p "$_gl_root/$_f" "$_gl_dir/$_f" 2>/dev/null
|
|
853
|
+
done
|
|
854
|
+
# No explicit `--config`, deliberately. gitleaks' own default resolution
|
|
855
|
+
# looks for `.gitleaks.toml` at "(target path)", which is `--source` -- i.e.
|
|
856
|
+
# the mirrored copy, where the file already sits at its usual relative path
|
|
857
|
+
# if this repo tracks one. A first cut passed `--config "$_gl_root/.gitleaks.toml"`
|
|
858
|
+
# to remove any doubt about that resolution, and it was a regression:
|
|
859
|
+
# against a repo with NO `.gitleaks.toml` at all -- a real, valid state,
|
|
860
|
+
# since gitleaks otherwise falls back to its built-in default ruleset --
|
|
861
|
+
# a literal `--config` path that does not exist is FATAL ("unable to load
|
|
862
|
+
# gitleaks config"), where the flagless form degrades gracefully to
|
|
863
|
+
# defaults, identical to what this repo ran before #1194. Verified by
|
|
864
|
+
# running both forms against a `--source` with no config file present.
|
|
865
|
+
gitleaks detect --no-git --redact --exit-code=2 --source "$_gl_dir"
|
|
866
|
+
_gl_status=$?
|
|
867
|
+
rm -rf "$_gl_dir"
|
|
868
|
+
return $_gl_status
|
|
869
|
+
}
|
|
870
|
+
|
|
643
871
|
if ci_has "gitleaks"; then
|
|
644
872
|
# The installation check gates EXECUTION only, never `--list`.
|
|
645
873
|
#
|
|
@@ -653,7 +881,7 @@ if ci_has "gitleaks"; then
|
|
|
653
881
|
# `terraform-fmt` above has the same shape and is only unexposed because
|
|
654
882
|
# terraform happens to be installed here. Recorded, not fixed in this change.
|
|
655
883
|
if [ -n "$LIST" ] || command -v gitleaks >/dev/null 2>&1; then
|
|
656
|
-
run_check gitleaks
|
|
884
|
+
run_check gitleaks gitleaks_tracked_only
|
|
657
885
|
else
|
|
658
886
|
# Say how to close it, pinned to the version ci.yml installs. A skip that
|
|
659
887
|
# only reports its own absence stays skipped: this one sat `n/a` long
|