forgedock 1.29.0 → 1.29.1
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/bin/engine-cli.mjs +12 -6
- package/bin/lean/context.mjs +27 -4
- package/bin/lean/context.test.mjs +40 -12
- package/bin/lean/lean.mjs +23 -18
- package/bin/lean/lean.test.mjs +31 -3
- package/bin/lean/orchestrate.mjs +41 -11
- package/bin/lean/prefilter.mjs +79 -14
- package/bin/lean/prefilter.test.mjs +88 -2
- package/bin/lean/recovery.test.mjs +196 -7
- package/bin/lean/render.mjs +13 -0
- package/bin/lean/testmap.mjs +17 -0
- package/bin/lean/testmap.test.mjs +16 -1
- package/commands/deploy-pr.md +5 -4
- package/commands/orchestrate.md +4 -3
- package/commands/review-pr-staging.md +6 -1
- package/docs/OUTCOME-CODES.md +1 -1
- package/package.json +1 -1
- package/scripts/code-index.sh +33 -33
- package/scripts/code-index.test.sh +21 -2
- package/scripts/fixtures/test-map/go/.github/workflows/ci.yml +2 -0
- package/scripts/repo-facts.test.sh +5 -5
- package/scripts/review-prepare.sh +28 -6
- package/scripts/review-prepare.test.sh +22 -2
- package/scripts/test-map.sh +139 -33
- package/scripts/test-map.test.sh +195 -7
- package/scripts/verify-phase-trail.test.sh +44 -44
- package/scripts/wait-ci-green.sh +1 -1
- package/scripts/wait-ci-green.test.sh +4 -0
|
@@ -70,10 +70,10 @@ run() { # run <fixture> [args...] -> sets OUT, RC
|
|
|
70
70
|
local fx="$1"; shift
|
|
71
71
|
OUT=$(MOCK_GH_JSON="$fx" bash "$VERIFY" 3061 -R o/r "$@" 2>/dev/null); RC=$?
|
|
72
72
|
}
|
|
73
|
-
expect_pass() { run "$2" "${@:3}"; [ $RC -eq 0 ] &&
|
|
73
|
+
expect_pass() { run "$2" "${@:3}"; [ $RC -eq 0 ] && grep -q 'PHASE_TRAIL: PASS' <<< "$OUT" && ok "$1" || bad "$1 (rc=$RC out=$OUT)"; }
|
|
74
74
|
expect_fail() { # name fixture needle [args]
|
|
75
75
|
run "$2" "${@:4}"
|
|
76
|
-
[ $RC -eq 1 ] &&
|
|
76
|
+
[ $RC -eq 1 ] && grep -q 'PHASE_TRAIL: FAIL' <<< "$OUT" && grep -q "MISSING: $3" <<< "$OUT" && ok "$1" || bad "$1 (rc=$RC out=$OUT)"
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
expect_pass "full COMPLEX trail passes" "$(mk full INV CONTRACT FP_COMPLEX CONTEXT ARCH QG_PASS)"
|
|
@@ -107,12 +107,12 @@ expect_pass "INVESTIGATION:INVALID accepted as terminal investigator sentinel" "
|
|
|
107
107
|
|
|
108
108
|
# Missing FAST_PATH uses conservative STANDARD set, so CONTEXT/ARCHITECT also named
|
|
109
109
|
run "$(mk l INV CONTRACT QG_PASS)"
|
|
110
|
-
|
|
110
|
+
grep -q 'MISSING: FAST_PATH' <<< "$OUT" && grep -q 'MISSING: CONTEXT' <<< "$OUT" && grep -q 'MISSING: ARCHITECT' <<< "$OUT" \
|
|
111
111
|
&& ok "missing FAST_PATH names every missing artifact" || bad "missing FAST_PATH names every missing artifact ($OUT)"
|
|
112
112
|
|
|
113
113
|
# Every missing line names the phase to route back to
|
|
114
114
|
run "$(mk m INV CONTRACT FP_COMPLEX)"
|
|
115
|
-
|
|
115
|
+
grep -q 'MISSING: CONTEXT -> re-run Skill work-on/build/context' <<< "$OUT" && ok "refusal routes to the missing phase" || bad "route text ($OUT)"
|
|
116
116
|
|
|
117
117
|
# Large thread (pipefail + early-exit grep SIGPIPE regression, #3099): a valid trail buried in filler
|
|
118
118
|
python3 - "$TMP_FX/big.json" <<'PY'
|
|
@@ -168,12 +168,12 @@ mp="$TMP_FX/mp.json"
|
|
|
168
168
|
{ jq -nc '[{"body":"<!-- FORGE:INVESTIGATOR -->\n<!-- INVESTIGATION:COMPLETE -->"},{"body":"<!-- FORGE:FAST_PATH -->\n**COMPLEXITY_BAND**: TRIVIAL"},{"body":"<!-- FORGE:CONTRACT -->\nc"},{"body":"<!-- FORGE:BUILDER -->\nx\n<!-- FORGE:BUILDER:COMPLETE -->","updated_at":"2026-01-04T00:00:00Z"}] | map(. + {author_association:"OWNER", user:{login:"owner",type:"User"}})'
|
|
169
169
|
jq -nc '[{"body":"<!-- FORGE:BUILDER -->\nx\n<!-- FORGE:BUILDER:COMPLETE -->","updated_at":"2026-10-06T00:00:00Z"}] | map(. + {author_association:"OWNER", user:{login:"owner",type:"User"}})'; } > "$mp"
|
|
170
170
|
OUT=$(FORGE_TRAIL_QG_SINCE=2026-10-05T00:00:00Z MOCK_GH_JSON="$mp" bash "$VERIFY" 3061 -R o/r 2>/dev/null); RC=$?
|
|
171
|
-
[ $RC -eq 1 ] &&
|
|
171
|
+
[ $RC -eq 1 ] && grep -q 'MISSING: QUALITY_GATE' <<< "$OUT" && ok "multi-page: latest build on page 2 is not waived" || bad "multi-page (rc=$RC out=$OUT)"
|
|
172
172
|
|
|
173
173
|
# Cutoff validation (#3121): malformed or future cutoff fails closed
|
|
174
174
|
for bad_since in "garbage" "2999-01-01T00:00:00Z" "2020-13-45T99:99:99Z" "2026-02-30T00:00:00Z" "2026-10-01T24:00:00Z"; do
|
|
175
175
|
OUT=$(FORGE_TRAIL_QG_SINCE="$bad_since" MOCK_GH_JSON="$(lg lgv 2026-10-01T00:00:00Z)" bash "$VERIFY" 3061 -R o/r 2>/dev/null); RC=$?
|
|
176
|
-
[ $RC -eq 2 ] &&
|
|
176
|
+
[ $RC -eq 2 ] && grep -q 'PHASE_TRAIL: ERROR' <<< "$OUT" && ok "invalid cutoff '$bad_since' rejected" || bad "cutoff '$bad_since' (rc=$RC out=$OUT)"
|
|
177
177
|
done
|
|
178
178
|
|
|
179
179
|
# Runner clock behind the default cutoff: cutoff looks future-dated -> exit 2 (fails closed)
|
|
@@ -188,7 +188,7 @@ if [ "\$mode" = now ] && [ -n "\$fmt" ]; then echo 2020-01-01T00:00:00Z; else ex
|
|
|
188
188
|
MOCKDATE
|
|
189
189
|
chmod +x "$CLK/date"
|
|
190
190
|
OUT=$(PATH="$CLK:$PATH" MOCK_GH_JSON="$(lg lgc 2026-10-01T00:00:00Z)" bash "$VERIFY" 3061 -R o/r 2>/dev/null); RC=$?
|
|
191
|
-
[ $RC -eq 2 ] &&
|
|
191
|
+
[ $RC -eq 2 ] && grep -q 'PHASE_TRAIL: ERROR' <<< "$OUT" && ok "clock behind default cutoff fails closed (exit 2)" || bad "clock behind cutoff (rc=$RC out=$OUT)"
|
|
192
192
|
|
|
193
193
|
# BSD/macOS date fallback (#3139): GNU `date -d` unavailable, `date -j -f` available -> valid cutoff still honoured;
|
|
194
194
|
# neither parser available -> fails closed (exit 2).
|
|
@@ -207,11 +207,11 @@ chmod +x "$BSD/date"
|
|
|
207
207
|
OUT=$(PATH="$BSD:$PATH" FORGE_TRAIL_QG_SINCE=2026-10-05T00:00:00Z MOCK_GH_JSON="$(lg lgbsd 2026-10-01T00:00:00Z)" bash "$VERIFY" 3061 -R o/r 2>/dev/null); RC=$?
|
|
208
208
|
[ $RC -eq 0 ] && ok "BSD date -j -f fallback validates a real cutoff" || bad "BSD fallback (rc=$RC out=$OUT)"
|
|
209
209
|
OUT=$(PATH="$BSD:$PATH" BSD_NO_PARSE=1 FORGE_TRAIL_QG_SINCE=2026-10-05T00:00:00Z MOCK_GH_JSON="$(lg lgbsd2 2026-10-01T00:00:00Z)" bash "$VERIFY" 3061 -R o/r 2>/dev/null); RC=$?
|
|
210
|
-
[ $RC -eq 2 ] &&
|
|
210
|
+
[ $RC -eq 2 ] && grep -q 'PHASE_TRAIL: ERROR' <<< "$OUT" && ok "no usable date parser fails closed (exit 2)" || bad "no date parser (rc=$RC out=$OUT)"
|
|
211
211
|
|
|
212
212
|
# Boundary: cutoff exactly equal to BUILDER:COMPLETE time is NOT waived (strictly-before only)
|
|
213
213
|
OUT=$(FORGE_TRAIL_QG_SINCE=2026-10-01T00:00:00Z MOCK_GH_JSON="$(lg lgb 2026-10-01T00:00:00Z)" bash "$VERIFY" 3061 -R o/r 2>/dev/null); RC=$?
|
|
214
|
-
[ $RC -eq 1 ] &&
|
|
214
|
+
[ $RC -eq 1 ] && grep -q 'MISSING: QUALITY_GATE' <<< "$OUT" && ok "boundary: build at exactly the cutoff is not waived" || bad "boundary equal (rc=$RC out=$OUT)"
|
|
215
215
|
OUT=$(FORGE_TRAIL_QG_SINCE=2026-10-01T00:00:01Z MOCK_GH_JSON="$(lg lgb2 2026-10-01T00:00:00Z)" bash "$VERIFY" 3061 -R o/r 2>/dev/null); RC=$?
|
|
216
216
|
[ $RC -eq 0 ] && ok "boundary: build one second before the cutoff is waived" || bad "boundary minus 1s (rc=$RC out=$OUT)"
|
|
217
217
|
|
|
@@ -222,23 +222,23 @@ BASE='[{"body":"<!-- FORGE:INVESTIGATOR -->\n<!-- INVESTIGATION:COMPLETE -->"},{
|
|
|
222
222
|
# empty pages around a populated one
|
|
223
223
|
{ echo '[]'; pg "$BASE"; echo '[]'; pg "[$BLD]"; echo '[]'; } > "$TMP_FX/empty.json"
|
|
224
224
|
OUT=$(FORGE_TRAIL_QG_SINCE=2026-10-05T00:00:00Z MOCK_GH_JSON="$TMP_FX/empty.json" bash "$VERIFY" 3061 -R o/r 2>/dev/null); RC=$?
|
|
225
|
-
[ $RC -eq 1 ] &&
|
|
225
|
+
[ $RC -eq 1 ] && grep -q 'MISSING: QUALITY_GATE' <<< "$OUT" && ok "empty pages interleaved are tolerated" || bad "empty pages (rc=$RC out=$OUT)"
|
|
226
226
|
OUT=$(MOCK_GH_JSON=<(echo '[]') bash "$VERIFY" 3061 -R o/r 2>/dev/null); RC=$?
|
|
227
|
-
[ $RC -eq 1 ] &&
|
|
227
|
+
[ $RC -eq 1 ] && grep -q 'MISSING: INVESTIGATOR' <<< "$OUT" && ok "all-empty thread fails (not error)" || bad "all-empty (rc=$RC out=$OUT)"
|
|
228
228
|
# 3+ pages: latest build on the LAST page wins
|
|
229
229
|
{ pg "$BASE"; pg '[{"body":"<!-- FORGE:BUILDER -->\nx\n<!-- FORGE:BUILDER:COMPLETE -->","updated_at":"2026-01-01T00:00:00Z"}]'; pg "[$BLD]"; } > "$TMP_FX/p3.json"
|
|
230
230
|
OUT=$(FORGE_TRAIL_QG_SINCE=2026-10-05T00:00:00Z MOCK_GH_JSON="$TMP_FX/p3.json" bash "$VERIFY" 3061 -R o/r 2>/dev/null); RC=$?
|
|
231
|
-
[ $RC -eq 1 ] &&
|
|
231
|
+
[ $RC -eq 1 ] && grep -q 'MISSING: QUALITY_GATE' <<< "$OUT" && ok "3 pages: latest build on page 3 is not waived" || bad "3 pages (rc=$RC out=$OUT)"
|
|
232
232
|
# Malformed / mixed pages fail closed with exit 2
|
|
233
233
|
{ pg "$BASE"; echo '{"message":"Server Error"}'; } > "$TMP_FX/mixed.json"
|
|
234
234
|
OUT=$(MOCK_GH_JSON="$TMP_FX/mixed.json" bash "$VERIFY" 3061 -R o/r 2>/dev/null); RC=$?
|
|
235
|
-
[ $RC -eq 2 ] &&
|
|
235
|
+
[ $RC -eq 2 ] && grep -q 'PHASE_TRAIL: ERROR' <<< "$OUT" && ok "mixed array/object pages fail closed (exit 2)" || bad "mixed pages (rc=$RC out=$OUT)"
|
|
236
236
|
{ pg "$BASE"; echo '[1,"x"]'; } > "$TMP_FX/nonobj.json"
|
|
237
237
|
OUT=$(MOCK_GH_JSON="$TMP_FX/nonobj.json" bash "$VERIFY" 3061 -R o/r 2>/dev/null); RC=$?
|
|
238
|
-
[ $RC -eq 2 ] &&
|
|
238
|
+
[ $RC -eq 2 ] && grep -q 'PHASE_TRAIL: ERROR' <<< "$OUT" && ok "page with non-object elements fails closed (exit 2)" || bad "non-object elements (rc=$RC out=$OUT)"
|
|
239
239
|
{ pg "$BASE"; echo '[{"body":'; } > "$TMP_FX/malformed.json"
|
|
240
240
|
OUT=$(MOCK_GH_JSON="$TMP_FX/malformed.json" bash "$VERIFY" 3061 -R o/r 2>/dev/null); RC=$?
|
|
241
|
-
[ $RC -eq 2 ] &&
|
|
241
|
+
[ $RC -eq 2 ] && grep -q 'PHASE_TRAIL: ERROR' <<< "$OUT" && ok "malformed page fails closed (exit 2)" || bad "malformed page (rc=$RC out=$OUT)"
|
|
242
242
|
|
|
243
243
|
# SEC-4 (#3149): the grace keys on updated_at -- BUILDER:COMPLETE is PATCHed in later than the comment was created
|
|
244
244
|
jq -nc '[{"body":"<!-- FORGE:INVESTIGATOR -->\n<!-- INVESTIGATION:COMPLETE -->"},{"body":"<!-- FORGE:FAST_PATH -->\n**COMPLEXITY_BAND**: TRIVIAL"},{"body":"<!-- FORGE:CONTRACT -->\nc"},{"body":"<!-- FORGE:BUILDER -->\nx\n<!-- FORGE:BUILDER:COMPLETE -->","created_at":"2026-10-01T00:00:00Z","updated_at":"2026-10-08T00:00:00Z"}] | map(. + {author_association:"OWNER", user:{login:"owner",type:"User"}})' > "$TMP_FX/patched.json"
|
|
@@ -251,7 +251,7 @@ expect_fail "INVESTIGATION band + code diff requires CONTRACT" "$(mk cd1 INV FP_
|
|
|
251
251
|
expect_fail "INVESTIGATION band + code diff requires QUALITY_GATE" "$(mk cd2 INV CONTRACT CONTEXT ARCH FP_INVESTIGATION)" QUALITY_GATE --code-diff
|
|
252
252
|
expect_pass "INVESTIGATION band + code diff passes with a full trail" "$(mk cd3 INV CONTRACT CONTEXT ARCH QG_PASS FP_INVESTIGATION)" --code-diff
|
|
253
253
|
run "$(mk cd4 INV FP_INVESTIGATION)" --code-diff
|
|
254
|
-
|
|
254
|
+
grep -q 'NOTE: INVESTIGATION band ignored' <<< "$OUT" && ok "ignored band is reported" || bad "band note ($OUT)"
|
|
255
255
|
expect_pass "INVESTIGATION band + docs-only (no code diff) still passes" "$(mk cd5 INV FP_INVESTIGATION)" --docs-only
|
|
256
256
|
expect_pass "TRIVIAL band is unaffected by --code-diff" "$(mk cd6 INV CONTRACT FP_TRIVIAL QG_PASS)" --code-diff
|
|
257
257
|
|
|
@@ -282,7 +282,7 @@ expect_pass "uppercase head-tree is normalised" "$(ht h6 "$QG_T1")" --head-tree
|
|
|
282
282
|
expect_pass "docs-only still waives the bound gate" "$(ht h7)" --docs-only --head-tree "$T1"
|
|
283
283
|
for bt in "" "xyz" "${T1}0" "abc123"; do
|
|
284
284
|
OUT=$(MOCK_GH_JSON="$(ht h8 "$QG_T1")" bash "$VERIFY" 3061 -R o/r --head-tree "$bt" 2>/dev/null); RC=$?
|
|
285
|
-
[ $RC -eq 2 ] &&
|
|
285
|
+
[ $RC -eq 2 ] && grep -q 'PHASE_TRAIL: ERROR' <<< "$OUT" && ok "invalid --head-tree '$bt' fails closed (exit 2)" || bad "head-tree '$bt' (rc=$RC out=$OUT)"
|
|
286
286
|
done
|
|
287
287
|
# A 64-hex marker tree that merely starts with the 40-hex head tree must not match
|
|
288
288
|
QG_LONG=$'<!-- FORGE:QUALITY_GATE -->\n**Result**: PASS\n**Tree**: '"${T1}1111111111111111111111"
|
|
@@ -292,26 +292,26 @@ OUT=$(MOCK_GH_JSON="$(ht h9 "$QG_T1")" bash "$VERIFY" 3061 -R o/r --head-tree 2>
|
|
|
292
292
|
|
|
293
293
|
# -h prints the full header
|
|
294
294
|
HOUT=$(bash "$VERIFY" -h)
|
|
295
|
-
|
|
295
|
+
grep -q 'TRUSTED ENVIRONMENT ONLY' <<< "$HOUT" && grep -q 'COLLABORATOR includes read-level' <<< "$HOUT" && grep -q '^# Usage:' <<< "$HOUT" && ok "-h prints the full multi-line header" || bad "-h truncated"
|
|
296
296
|
|
|
297
297
|
# Untrusted-author FORGE markers are ignored AND diagnosed with a NOTE (#3123)
|
|
298
298
|
UT="$TMP_FX/untrusted.json"
|
|
299
299
|
jq -c 'map(. + {author_association:"CONTRIBUTOR", user:{login:"ext",type:"User"}})' "$(mk ut_src INV CONTRACT FP_TRIVIAL QG_PASS)" > "$UT"
|
|
300
300
|
OUT=$(MOCK_GH_JSON="$UT" bash "$VERIFY" 3061 -R o/r 2>/dev/null); RC=$?
|
|
301
|
-
[ $RC -eq 1 ] &&
|
|
301
|
+
[ $RC -eq 1 ] && grep -q 'MISSING: INVESTIGATOR' <<< "$OUT" && grep -q 'NOTE: .*untrusted authors were ignored' <<< "$OUT" && ok "CONTRIBUTOR markers ignored with NOTE" || bad "contributor note (rc=$RC out=$OUT)"
|
|
302
302
|
OUT=$(FORGE_TRAIL_TRUSTED_LOGINS=ext MOCK_GH_JSON="$UT" bash "$VERIFY" 3061 -R o/r 2>/dev/null); RC=$?
|
|
303
303
|
[ $RC -eq 0 ] && ok "FORGE_TRAIL_TRUSTED_LOGINS accepts a CONTRIBUTOR login" || bad "trusted login override (rc=$RC out=$OUT)"
|
|
304
304
|
# forge#3350: widening only the override list must not make CONTRIBUTOR trail markers count
|
|
305
305
|
OUT=$(FORGE_TRAIL_OVERRIDE_ASSOCIATIONS="OWNER,MEMBER,COLLABORATOR,CONTRIBUTOR" MOCK_GH_JSON="$UT" bash "$VERIFY" 3061 -R o/r 2>/dev/null); RC=$?
|
|
306
|
-
[ $RC -eq 1 ] &&
|
|
306
|
+
[ $RC -eq 1 ] && grep -q 'MISSING: INVESTIGATOR' <<< "$OUT" && ok "override list widened only: CONTRIBUTOR trail markers still ignored (#3350)" || bad "override-only widen trail (rc=$RC out=$OUT)"
|
|
307
307
|
expect_pass "marker-only CONTEXT satisfies STANDARD" "$(mk minctx INV FP_STANDARD CONTRACT CONTEXT ARCH QG_PASS)"
|
|
308
308
|
|
|
309
309
|
# -h is bounded by the END-HELP sentinel, not a hardcoded line range (#3147)
|
|
310
|
-
|
|
310
|
+
grep -q 'Callers route on the exit code' <<< "$HOUT" && ! grep -q 'END-HELP' <<< "$HOUT" && ! grep -q 'set -uo pipefail' <<< "$HOUT" && ok "-h stops at the END-HELP sentinel" || bad "-h sentinel"
|
|
311
311
|
|
|
312
312
|
# Outage fails closed (exit 2, never PASS)
|
|
313
313
|
OUT=$(MOCK_GH_FAIL=1 MOCK_GH_JSON=/dev/null bash "$VERIFY" 3061 -R o/r 2>/dev/null); RC=$?
|
|
314
|
-
[ $RC -eq 2 ] &&
|
|
314
|
+
[ $RC -eq 2 ] && grep -q 'PHASE_TRAIL: ERROR' <<< "$OUT" && ok "gh outage fails closed" || bad "gh outage (rc=$RC out=$OUT)"
|
|
315
315
|
|
|
316
316
|
# -R with no value must error out, not hang
|
|
317
317
|
# macOS has no `timeout`; fall back to gtimeout, then perl alarm.
|
|
@@ -353,14 +353,14 @@ ovf() { # ovf <name> <override comment json...> -> fixture = base trail + commen
|
|
|
353
353
|
}
|
|
354
354
|
GOODC() { ovc alice User "$AFTER" "$AFTER" "$HS" "$CUR_MISSING" "gate misfire, verified by hand"; }
|
|
355
355
|
orun() { OUT=$(MOCK_GH_JSON="$1" bash "$VERIFY" 3061 -R o/r --head-sha "$HS" "${@:2}" 2>/dev/null); RC=$?; }
|
|
356
|
-
expect_override() { orun "$2" "${@:3}"; [ $RC -eq 0 ] &&
|
|
357
|
-
expect_blocked() { orun "$2" "${@:3}"; [ $RC -eq 1 ] &&
|
|
356
|
+
expect_override() { orun "$2" "${@:3}"; [ $RC -eq 0 ] && grep -q 'PHASE_TRAIL: OVERRIDDEN' <<< "$OUT" && grep -q '^OVERRIDE: approver=' <<< "$OUT" && ok "$1" || bad "$1 (rc=$RC out=$OUT)"; }
|
|
357
|
+
expect_blocked() { orun "$2" "${@:3}"; [ $RC -eq 1 ] && grep -q 'PHASE_TRAIL: FAIL' <<< "$OUT" && ! grep -q 'OVERRIDDEN' <<< "$OUT" && ok "$1" || bad "$1 (rc=$RC out=$OUT)"; }
|
|
358
358
|
|
|
359
359
|
expect_blocked "no override comment stays blocked" "$(ovf o0)"
|
|
360
360
|
expect_override "valid human override is accepted" "$(ovf o1 "$(GOODC)")"
|
|
361
361
|
orun "$(ovf o1b "$(GOODC)")"
|
|
362
|
-
|
|
363
|
-
|
|
362
|
+
grep -q "^OVERRIDE: approver=alice head=$HS missing=$CUR_MISSING reason=gate misfire, verified by hand$" <<< "$OUT" && ok "OVERRIDE line carries approver, head, missing set and reason" || bad "OVERRIDE line ($OUT)"
|
|
363
|
+
grep -q '^MISSING:' <<< "$OUT" && bad "accepted override must not emit MISSING lines" || ok "accepted override emits no MISSING lines"
|
|
364
364
|
expect_override "admin permission is accepted" "$(ovf o2 "$(ovc carol User "$AFTER" "$AFTER" "$HS" "$CUR_MISSING" "ok")")"
|
|
365
365
|
expect_override "uppercase head and shuffled/spaced missing list still bind to the same set" \
|
|
366
366
|
"$(ovf o3 "$(ovc alice User "$AFTER" "$AFTER" "$(echo $HS | tr a-f A-F)" "QUALITY_GATE , ARCHITECT,CONTEXT -> x" "ok")")"
|
|
@@ -376,7 +376,7 @@ expect_blocked "rejects: missing timestamps" "$(ovf r5 "$(GOODC | jq -c 'del(.up
|
|
|
376
376
|
expect_blocked "rejects: read-only collaborator" "$(ovf r6 "$(ovc reader User "$AFTER" "$AFTER" "$HS" "$CUR_MISSING" "r")")"
|
|
377
377
|
expect_blocked "rejects: author with no permission record" "$(ovf r7 "$(ovc stranger User "$AFTER" "$AFTER" "$HS" "$CUR_MISSING" "r")")"
|
|
378
378
|
orun "$(ovf r8 "$(GOODC)")"; OUT=$(MOCK_PERM_FAIL=1 MOCK_GH_JSON="$TMP_FX/r8.json" bash "$VERIFY" 3061 -R o/r --head-sha "$HS" 2>/dev/null); RC=$?
|
|
379
|
-
[ $RC -eq 1 ] && !
|
|
379
|
+
[ $RC -eq 1 ] && ! grep -q OVERRIDDEN <<< "$OUT" && ok "rejects: permission API error fails closed (exit 1)" || bad "permission api error (rc=$RC out=$OUT)"
|
|
380
380
|
expect_blocked "rejects: wrong head sha" "$(ovf r9 "$(ovc alice User "$AFTER" "$AFTER" "$HS2" "$CUR_MISSING" "r")")"
|
|
381
381
|
expect_blocked "rejects: truncated head sha" "$(ovf r10 "$(ovc alice User "$AFTER" "$AFTER" "${HS:0:12}" "$CUR_MISSING" "r")")"
|
|
382
382
|
expect_blocked "rejects: override for a different MISSING set" "$(ovf r11 "$(ovc alice User "$AFTER" "$AFTER" "$HS" "ARCHITECT,CONTEXT" "r")")"
|
|
@@ -396,21 +396,21 @@ PIPE_C=$(jq -nc '{body:"<!-- FORGE:QUALITY_GATE -->\n**Result**: FAIL",user:{log
|
|
|
396
396
|
expect_blocked "rejects: author of any trusted FORGE marker (QUALITY_GATE) is pipeline" "$(ovf p5 "$PIPE_C" "$(ovc pipeuser User "$AFTER" "$AFTER" "$HS" "$CUR_MISSING" "r")")"
|
|
397
397
|
# Override cannot be evaluated without --head-sha
|
|
398
398
|
OUT=$(MOCK_GH_JSON="$(ovf n1 "$(GOODC)")" bash "$VERIFY" 3061 -R o/r 2>/dev/null); RC=$?
|
|
399
|
-
[ $RC -eq 1 ] && !
|
|
399
|
+
[ $RC -eq 1 ] && ! grep -q OVERRIDDEN <<< "$OUT" && ok "rejects: no --head-sha means the override is never evaluated" || bad "no head-sha (rc=$RC out=$OUT)"
|
|
400
400
|
for bs in "" "xyz" "${HS}0"; do
|
|
401
401
|
OUT=$(MOCK_GH_JSON="$(ovf n2 "$(GOODC)")" bash "$VERIFY" 3061 -R o/r --head-sha "$bs" 2>/dev/null); RC=$?
|
|
402
|
-
[ $RC -eq 2 ] &&
|
|
402
|
+
[ $RC -eq 2 ] && grep -q 'PHASE_TRAIL: ERROR' <<< "$OUT" && ok "invalid --head-sha '$bs' fails closed (exit 2)" || bad "head-sha '$bs' (rc=$RC out=$OUT)"
|
|
403
403
|
done
|
|
404
404
|
OUT=$(MOCK_GH_JSON="$TMP_FX/n2.json" bash "$VERIFY" 3061 -R o/r --head-sha 2>/dev/null); RC=$?
|
|
405
405
|
[ $RC -eq 2 ] && ok "--head-sha without a value exits 2" || bad "head-sha no value (rc=$RC)"
|
|
406
406
|
# Exit 2 is never overridable
|
|
407
407
|
OUT=$(MOCK_GH_FAIL=1 MOCK_GH_JSON="$TMP_FX/o1.json" bash "$VERIFY" 3061 -R o/r --head-sha "$HS" 2>/dev/null); RC=$?
|
|
408
|
-
[ $RC -eq 2 ] && !
|
|
408
|
+
[ $RC -eq 2 ] && ! grep -q OVERRIDDEN <<< "$OUT" && ok "exit 2 (unreadable trail) is not overridable" || bad "exit 2 override (rc=$RC out=$OUT)"
|
|
409
409
|
{ cat "$TMP_FX/o1.json"; echo '{"message":"Server Error"}'; } > "$TMP_FX/o2bad.json"
|
|
410
410
|
OUT=$(MOCK_GH_JSON="$TMP_FX/o2bad.json" bash "$VERIFY" 3061 -R o/r --head-sha "$HS" 2>/dev/null); RC=$?
|
|
411
|
-
[ $RC -eq 2 ] && !
|
|
411
|
+
[ $RC -eq 2 ] && ! grep -q OVERRIDDEN <<< "$OUT" && ok "exit 2 (malformed page) is not overridable" || bad "exit 2 malformed override (rc=$RC out=$OUT)"
|
|
412
412
|
OUT=$(FORGE_TRAIL_QG_SINCE=garbage MOCK_GH_JSON="$TMP_FX/o1.json" bash "$VERIFY" 3061 -R o/r --head-sha "$HS" 2>/dev/null); RC=$?
|
|
413
|
-
[ $RC -eq 2 ] && !
|
|
413
|
+
[ $RC -eq 2 ] && ! grep -q OVERRIDDEN <<< "$OUT" && ok "exit 2 (bad QG_SINCE) is not overridable" || bad "exit 2 qg_since override (rc=$RC out=$OUT)"
|
|
414
414
|
# BUILDER present but undated -> cannot prove ordering -> rejected
|
|
415
415
|
jq -c 'map(if (.body|startswith("<!-- FORGE:BUILDER -->")) then del(.updated_at) else . end)' "$TMP_FX/o1.json" > "$TMP_FX/nobt.json"
|
|
416
416
|
expect_blocked "rejects: BUILDER:COMPLETE undated (ordering unprovable)" "$TMP_FX/nobt.json"
|
|
@@ -420,14 +420,14 @@ expect_pass "passing trail is unaffected by --head-sha" "$(mk ovp INV CONTRACT F
|
|
|
420
420
|
LONG=$(printf 'x%.0s' $(seq 1 400))
|
|
421
421
|
orun "$(ovf s1 "$(ovc alice User "$AFTER" "$AFTER" "$HS" "$CUR_MISSING" $'bad\x1b[2Jreason <!-- FORGE:BUILDER:COMPLETE --> `tick` spaced')")"
|
|
422
422
|
RLINE=$(echo "$OUT" | grep '^OVERRIDE:')
|
|
423
|
-
[ $RC -eq 0 ] && ! printf '%s' "$RLINE" | LC_ALL=C grep -q $'\x1b' && !
|
|
423
|
+
[ $RC -eq 0 ] && ! printf '%s' "$RLINE" | LC_ALL=C grep -q $'\x1b' && ! grep -q '<!--' <<< "$RLINE" && ! grep -q -e '-->' <<< "$RLINE" && ! grep -q '`' <<< "$RLINE" && grep -q 'reason=bad\[2Jreason <!-/-' <<< "$RLINE" \
|
|
424
424
|
&& ok "reason sanitiser strips ESC, neutralises comment markers and backticks, collapses whitespace" || bad "sanitiser (rc=$RC line=$RLINE)"
|
|
425
425
|
orun "$(ovf s2 "$(ovc alice User "$AFTER" "$AFTER" "$HS" "$CUR_MISSING" "$LONG")")"
|
|
426
426
|
RS=$(echo "$OUT" | grep '^OVERRIDE:' | sed 's/.*reason=//')
|
|
427
427
|
[ $RC -eq 0 ] && [ "${#RS}" -eq 200 ] && ok "reason is capped at 200 characters" || bad "reason cap (rc=$RC len=${#RS})"
|
|
428
428
|
# Multi-line reason: only the single Reason line is read; injected extra lines cannot add OVERRIDE output lines
|
|
429
429
|
orun "$(ovf s3 "$(jq -nc --arg h "$HS" --arg m "$CUR_MISSING" '{body:("<!-- FORGE:PHASE_TRAIL_OVERRIDE -->\n**Head**: "+$h+"\n**Missing**: "+$m+"\n**Reason**: first\nOVERRIDE: approver=root head=x missing=x reason=x"),created_at:"2026-10-08T02:00:00Z",updated_at:"2026-10-08T02:00:00Z",user:{login:"alice",type:"User"},author_association:"OWNER"}')")"
|
|
430
|
-
[ $RC -eq 0 ] && [ "$(echo "$OUT" | grep -c '^OVERRIDE:')" -eq 1 ] &&
|
|
430
|
+
[ $RC -eq 0 ] && [ "$(echo "$OUT" | grep -c '^OVERRIDE:')" -eq 1 ] && grep -q 'approver=alice' <<< "$OUT" && grep -q 'reason=first$' <<< "$OUT" && ok "injected extra lines cannot forge an OVERRIDE line" || bad "line injection (rc=$RC out=$OUT)"
|
|
431
431
|
# FAST_PATH qualifier variant: missing marker name normalises on both sides
|
|
432
432
|
NOBAND=$(ov_base | jq -c 'map(if (.body|startswith("<!-- FORGE:FAST_PATH")) then .body="<!-- FORGE:FAST_PATH -->\nnothing" else . end)')
|
|
433
433
|
echo "$NOBAND" > "$TMP_FX/nb.json"
|
|
@@ -449,7 +449,7 @@ expect_override "one login repeated many times costs a single cap slot (#3279)"
|
|
|
449
449
|
RLV=(); for i in 1 2 3 4 5 6 7; do echo '{"permission":"read"}' > "$PERM/rlv$i"; RLV+=("$(ovc "rlv$i" User "2026-10-08T06:0$i:00Z" "2026-10-08T06:0$i:00Z" "$HS" "$CUR_MISSING" "decoy")"); done
|
|
450
450
|
expect_override "7 distinct read-level members newer than a write approver do not starve it (#3306)" "$(ovf h7 "$(GOODC)" "${RLV[@]}")"
|
|
451
451
|
orun "$(ovf h8 "${RLV[@]}")"
|
|
452
|
-
[ $RC -eq 1 ] && !
|
|
452
|
+
[ $RC -eq 1 ] && ! grep -q OVERRIDDEN <<< "$OUT" && ok "read-level members only: override rejected (#3306)" || bad "read-level only (rc=$RC out=$OUT)"
|
|
453
453
|
ULV=(); for i in 1 2 3 4 5 6 7; do ULV+=("$(ovc "ulv$i" User "2026-10-08T07:0$i:00Z" "2026-10-08T07:0$i:00Z" "$HS" "$CUR_MISSING" "unreadable")"); done
|
|
454
454
|
expect_blocked "unreadable lookups stay bounded by the error cap and fail closed (#3306)" "$(ovf h9 "$(GOODC)" "${ULV[@]}")"
|
|
455
455
|
# forge#3307: association prefilter reuses the configured list; each association value is pinned
|
|
@@ -471,12 +471,12 @@ echo '{"permission":"read"}' > "$PERM/cvx"
|
|
|
471
471
|
CVV=(); i=0; for l in Cvx cvx CVX cvX Cvx CVx; do i=$((i+1)); CVV+=("$(ovc "$l" User "2026-10-08T09:0$i:00Z" "2026-10-08T09:0$i:00Z" "$HS" "$CUR_MISSING" "dup")"); done
|
|
472
472
|
PL="$TMP_FX/permlog-cv"; : > "$PL"
|
|
473
473
|
OUT=$(MOCK_PERM_LOG="$PL" MOCK_GH_JSON="$(ovf a7 "$(GOODC)" "${CVV[@]}")" bash "$VERIFY" 3061 -R o/r --head-sha "$HS" 2>/dev/null); RC=$?
|
|
474
|
-
[ $RC -eq 0 ] &&
|
|
474
|
+
[ $RC -eq 0 ] && grep -q OVERRIDDEN <<< "$OUT" && [ "$(grep -ic '^cvx$' "$PL")" -eq 1 ] && ok "case-variant duplicate logins cost a single lookup (#3307)" || bad "case-variant dup (rc=$RC lookups=$(grep -ic '^cvx$' "$PL"))"
|
|
475
475
|
# Decoys (NONE/CONTRIBUTOR) never reach the permission API; the NOTE reports the filtered count without logins
|
|
476
476
|
MIXD=("${PDEC[@]}" "${CDEC[@]}")
|
|
477
477
|
PL="$TMP_FX/permlog-decoy"; : > "$PL"
|
|
478
478
|
ERR=$(MOCK_PERM_LOG="$PL" MOCK_GH_JSON="$(ovf a8 "${MIXD[@]}")" bash "$VERIFY" 3061 -R o/r --head-sha "$HS" 2>&1 >/dev/null);
|
|
479
|
-
[ ! -s "$PL" ] &&
|
|
479
|
+
[ ! -s "$PL" ] && grep -q 'NOTE: 14 override candidate(s) ignored: author_association not in override-approver set' <<< "$ERR" && ! grep -q 'pdecoy\|cdecoy' <<< "$ERR" \
|
|
480
480
|
&& ok "decoys trigger no permission lookups; NOTE carries the count and no logins (#3307)" || bad "decoy no-call (log=$(cat "$PL") err=$ERR)"
|
|
481
481
|
# forge#3351: the NOTE count shares one predicate with the candidates: only otherwise-eligible, association-excluded
|
|
482
482
|
# comments are counted (edited, bot, and pre-floor ones are not)
|
|
@@ -486,20 +486,20 @@ EXP=$(ovc xold User "2000-01-01T00:00:00Z" "2000-01-01T00:00:00Z" "$HS" "$CUR_MI
|
|
|
486
486
|
EX1=$(ovc xok1 User "2026-10-08T11:04:00Z" "2026-10-08T11:04:00Z" "$HS" "$CUR_MISSING" "dropped" | assoc NONE)
|
|
487
487
|
EX2=$(ovc xok2 User "2026-10-08T11:05:00Z" "2026-10-08T11:05:00Z" "$HS" "$CUR_MISSING" "dropped" | assoc CONTRIBUTOR)
|
|
488
488
|
ERR=$(MOCK_GH_JSON="$(ovf a10 "$(GOODC)" "$EXC" "$EXB" "$EXP" "$EX1" "$EX2")" bash "$VERIFY" 3061 -R o/r --head-sha "$HS" 2>&1 >/dev/null)
|
|
489
|
-
|
|
489
|
+
grep -q 'NOTE: 2 override candidate(s) ignored: author_association not in override-approver set' <<< "$ERR" \
|
|
490
490
|
&& ok "NOTE count equals only eligible association-excluded candidates (#3351)" || bad "NOTE count shared predicate (err=$ERR)"
|
|
491
491
|
# 6th distinct unreadable login is cut off: the cap stops the scan before an older real approver
|
|
492
492
|
UNR=(); for i in 1 2 3 4 5 6 7 8; do UNR+=("$(ovc "unr$i" User "2026-10-08T10:0$i:00Z" "2026-10-08T10:0$i:00Z" "$HS" "$CUR_MISSING" "unreadable")"); done
|
|
493
493
|
PL="$TMP_FX/permlog-unr"; : > "$PL"
|
|
494
494
|
OUT=$(MOCK_PERM_LOG="$PL" MOCK_GH_JSON="$(ovf a9 "$(GOODC)" "${UNR[@]}")" bash "$VERIFY" 3061 -R o/r --head-sha "$HS" 2>/dev/null); RC=$?
|
|
495
|
-
[ $RC -eq 1 ] && !
|
|
495
|
+
[ $RC -eq 1 ] && ! grep -q OVERRIDDEN <<< "$OUT" && [ "$(wc -l < "$PL" | tr -d ' ')" -eq 6 ] && ! grep -qx alice "$PL" && ok "unreadable lookups stop at the 6th login; older approver never queried (#3307)" || bad "unreadable cutoff (rc=$RC lookups=$(wc -l < "$PL"))"
|
|
496
496
|
MOCK_SELF_LOGIN=alice orun "$(ovf h3 "$(GOODC)")"
|
|
497
|
-
[ $RC -eq 1 ] && !
|
|
497
|
+
[ $RC -eq 1 ] && ! grep -q OVERRIDDEN <<< "$OUT" && ok "verifier's own login cannot approve an override (#3269)" || bad "self-login exclusion (rc=$RC out=$OUT)"
|
|
498
498
|
MOCK_SELF_LOGIN=someoneelse orun "$(ovf h3b "$(GOODC)")"
|
|
499
|
-
[ $RC -eq 0 ] &&
|
|
499
|
+
[ $RC -eq 0 ] && grep -q OVERRIDDEN <<< "$OUT" && ok "an unrelated self-login does not block a valid override (#3269)" || bad "self-login unrelated (rc=$RC out=$OUT)"
|
|
500
500
|
orun "$(ovf h4 "$(ovc alice User "$AFTER" "$AFTER" "$HS" "$CUR_MISSING" $'ping @octocat\u2028next\u2029line')")"
|
|
501
501
|
RS=$(echo "$OUT" | grep '^OVERRIDE:' | sed 's/.*reason=//')
|
|
502
|
-
[ $RC -eq 0 ] && !
|
|
502
|
+
[ $RC -eq 0 ] && ! grep -q '@' <<< "$RS" && ! grep -q $'\xe2\x80\xa8' <<< "$RS" && ok "reason sanitiser neutralises @mentions and U+2028/2029 (#3274)" || bad "sanitiser unicode (rc=$RC rs=$RS)"
|
|
503
503
|
# No BUILDER at all: floor falls back to the newest trusted FORGE marker (#3271)
|
|
504
504
|
ov_base | jq -c '[.[] | select((.body|startswith("<!-- FORGE:BUILDER -->"))|not)] | map(if (.body|startswith("<!-- FORGE:CONTRACT -->")) then .created_at="2026-10-08T01:30:00Z" else . end)' > "$TMP_FX/nobuilder.json"
|
|
505
505
|
orun "$TMP_FX/nobuilder.json"; NB_OUT="$OUT"
|
|
@@ -512,11 +512,11 @@ if [ -n "$NB_MISS" ]; then
|
|
|
512
512
|
else bad "no-BUILDER fixture produced no MISSING set ($NB_OUT)"; fi
|
|
513
513
|
# Rejected permission is explained on stderr (#3272)
|
|
514
514
|
ERRTXT=$(MOCK_GH_JSON="$(ovf h5 "$(ovc reader User "$AFTER" "$AFTER" "$HS" "$CUR_MISSING" "no perm")")" bash "$VERIFY" 3061 -R o/r --head-sha "$HS" 2>&1 >/dev/null)
|
|
515
|
-
|
|
515
|
+
grep -q "rejected: permission='read'" <<< "$ERRTXT" && ok "rejected permission prints a diagnostic to stderr (#3272)" || bad "permission diagnostic ($ERRTXT)"
|
|
516
516
|
|
|
517
517
|
# -h documents the override
|
|
518
518
|
HOUT2=$(bash "$VERIFY" -h)
|
|
519
|
-
|
|
519
|
+
grep -q 'FORGE_TRAIL_PIPELINE_LOGINS' <<< "$HOUT2" && grep -q -- '--head-sha' <<< "$HOUT2" && ok "-h documents the override and --head-sha" || bad "-h override docs"
|
|
520
520
|
|
|
521
521
|
echo "passed=$PASS failed=$FAILN"
|
|
522
522
|
[ "$FAILN" -eq 0 ]
|
package/scripts/wait-ci-green.sh
CHANGED
|
@@ -55,7 +55,7 @@ while [ $# -gt 0 ]; do
|
|
|
55
55
|
--timeout) [ $# -ge 2 ] || usage; TIMEOUT="$2"; shift 2 ;;
|
|
56
56
|
--interval) [ $# -ge 2 ] || usage; INTERVAL="$2"; shift 2 ;;
|
|
57
57
|
--ignore-check) [ $# -ge 2 ] || usage; IGNORE="$(jq -c --arg n "$2" '. + [$n]' <<<"$IGNORE")"; shift 2 ;;
|
|
58
|
-
-h|--help) sed -n '5,
|
|
58
|
+
-h|--help) sed -n '5,41p' "$0"; exit 0 ;;
|
|
59
59
|
*) [ -z "$PR" ] || usage; PR="$1"; shift ;;
|
|
60
60
|
esac
|
|
61
61
|
done
|
|
@@ -142,5 +142,9 @@ for f in commands/work-on/review.md; do
|
|
|
142
142
|
grep -q 'stale review' "$HERE/../$f" && PASS=$((PASS+1)) || { FAILN=$((FAILN+1)); echo "FAIL: $f does not route stale review"; }
|
|
143
143
|
done
|
|
144
144
|
|
|
145
|
+
# forge#4304: --help prints the whole header, through the rate-limit and "No checks" lines.
|
|
146
|
+
HELP=$(bash "$GATE" --help); HRC=$?
|
|
147
|
+
expect "--help exits 0" "0" "$HRC"
|
|
148
|
+
grep -qF 'Rate-limit and transient gh failures' <<<"$HELP" && grep -qF 'the gate keeps waiting.' <<<"$HELP" && PASS=$((PASS+1)) || { FAILN=$((FAILN+1)); echo "FAIL: --help cuts off the header"; }
|
|
145
149
|
echo "wait-ci-green tests: pass=$PASS fail=$FAILN"
|
|
146
150
|
[ "$FAILN" -eq 0 ]
|