forgedock 1.8.4 → 1.9.0

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.
Files changed (38) hide show
  1. package/bin/engine/admission.mjs +23 -4
  2. package/bin/engine/admission.test.mjs +37 -0
  3. package/commands/orchestrate/phase-1-resolve.md +40 -24
  4. package/commands/orchestrate/phase-3-dependency.md +56 -11
  5. package/commands/orchestrate/phase-4-execution.md +349 -111
  6. package/commands/orchestrate/phase-5-cleanup.md +17 -0
  7. package/commands/orchestrate/phase-6-report.md +1 -1
  8. package/commands/quality-gate.md +51 -0
  9. package/commands/recover-orphans.md +3 -3
  10. package/commands/review-pr.md +87 -16
  11. package/commands/work-on/build/context.md +25 -4
  12. package/commands/work-on/build/validate.md +1 -0
  13. package/commands/work-on/build.md +26 -9
  14. package/commands/work-on/close.md +104 -40
  15. package/commands/work-on/decompose.md +13 -7
  16. package/commands/work-on/investigate.md +13 -7
  17. package/commands/work-on/remediate.md +15 -4
  18. package/commands/work-on/review.md +48 -19
  19. package/commands/work-on.md +62 -9
  20. package/docs/CODEX.md +12 -0
  21. package/docs/CONFIG.md +49 -1
  22. package/docs/RELEASE-NOTES.md +52 -0
  23. package/package.json +1 -1
  24. package/scripts/amplification-breaker.sh +89 -0
  25. package/scripts/amplification-breaker.test.sh +39 -0
  26. package/scripts/check-admission-parity.mjs +74 -1
  27. package/scripts/check-spec-markers.sh +67 -3
  28. package/scripts/classify-finding.sh +120 -0
  29. package/scripts/classify-finding.test.sh +74 -0
  30. package/scripts/close-dossier-no-main-commit.test.sh +139 -0
  31. package/scripts/forge-root.test.sh +84 -27
  32. package/scripts/lint-build-validate-files.sh +29 -0
  33. package/scripts/lint-build-validate-files.test.sh +25 -0
  34. package/scripts/lint-dispatch-prompt.test.sh +2 -1
  35. package/scripts/recovery-claim-live.sh +73 -0
  36. package/scripts/recovery-claim-live.test.sh +66 -0
  37. package/scripts/verify-phase-trail.sh +183 -4
  38. package/scripts/verify-phase-trail.test.sh +211 -1
@@ -16,7 +16,21 @@ trap 'rm -rf "$TMP_BIN" "$TMP_FX"' EXIT
16
16
  cat > "$TMP_BIN/gh" <<'MOCK'
17
17
  #!/usr/bin/env bash
18
18
  if [ "${MOCK_GH_FAIL:-}" = "1" ]; then echo "mock gh: outage" >&2; exit 1; fi
19
- cat "$MOCK_GH_JSON"
19
+ # Route by endpoint (#3152): unknown endpoints fail, so a test can never pass by accident on the wrong fixture.
20
+ for a in "$@"; do
21
+ case "$a" in
22
+ repos/*/issues/*/comments) cat "$MOCK_GH_JSON"; exit 0 ;;
23
+ user) [ -n "${MOCK_SELF_LOGIN:-}" ] && { echo "$MOCK_SELF_LOGIN"; exit 0; }; exit 1 ;;
24
+ repos/*/collaborators/*/permission)
25
+ [ "${MOCK_PERM_FAIL:-}" = "1" ] && { echo "mock gh: permission api error" >&2; exit 1; }
26
+ u="${a#repos/*/collaborators/}"; u="${u%/permission}"
27
+ [ -n "${MOCK_PERM_LOG:-}" ] && echo "$u" >> "$MOCK_PERM_LOG"
28
+ f="${MOCK_PERM_DIR:-/nonexistent}/$u"
29
+ [ -f "$f" ] && { cat "$f"; exit 0; }
30
+ echo "mock gh: 404" >&2; exit 1 ;;
31
+ esac
32
+ done
33
+ echo "mock gh: unexpected endpoint: $*" >&2; exit 1
20
34
  MOCK
21
35
  chmod +x "$TMP_BIN/gh"
22
36
  export PATH="$TMP_BIN:$PATH"
@@ -273,6 +287,9 @@ OUT=$(MOCK_GH_JSON="$UT" bash "$VERIFY" 3061 -R o/r 2>/dev/null); RC=$?
273
287
  [ $RC -eq 1 ] && echo "$OUT" | grep -q 'MISSING: INVESTIGATOR' && echo "$OUT" | grep -q 'NOTE: .*untrusted authors were ignored' && ok "CONTRIBUTOR markers ignored with NOTE" || bad "contributor note (rc=$RC out=$OUT)"
274
288
  OUT=$(FORGE_TRAIL_TRUSTED_LOGINS=ext MOCK_GH_JSON="$UT" bash "$VERIFY" 3061 -R o/r 2>/dev/null); RC=$?
275
289
  [ $RC -eq 0 ] && ok "FORGE_TRAIL_TRUSTED_LOGINS accepts a CONTRIBUTOR login" || bad "trusted login override (rc=$RC out=$OUT)"
290
+ # forge#3350: widening only the override list must not make CONTRIBUTOR trail markers count
291
+ OUT=$(FORGE_TRAIL_OVERRIDE_ASSOCIATIONS="OWNER,MEMBER,COLLABORATOR,CONTRIBUTOR" MOCK_GH_JSON="$UT" bash "$VERIFY" 3061 -R o/r 2>/dev/null); RC=$?
292
+ [ $RC -eq 1 ] && echo "$OUT" | grep -q 'MISSING: INVESTIGATOR' && ok "override list widened only: CONTRIBUTOR trail markers still ignored (#3350)" || bad "override-only widen trail (rc=$RC out=$OUT)"
276
293
  expect_pass "marker-only CONTEXT satisfies STANDARD" "$(mk minctx INV FP_STANDARD CONTRACT CONTEXT ARCH QG_PASS)"
277
294
 
278
295
  # -h is bounded by the END-HELP sentinel, not a hardcoded line range (#3147)
@@ -294,5 +311,198 @@ OUT=$("${TMO[@]}" bash "$VERIFY" 5 -R 2>/dev/null); RC=$?
294
311
  OUT=$(bash "$VERIFY" 2>/dev/null); RC=$?
295
312
  [ $RC -eq 2 ] && ok "no args is a usage error" || bad "usage error (rc=$RC)"
296
313
 
314
+ # --- Break-glass override (#3152) ---
315
+ HS=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa; HS2=bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
316
+ PERM="$TMP_FX/perm"; mkdir -p "$PERM"
317
+ echo '{"permission":"write"}' > "$PERM/alice"; echo '{"permission":"admin"}' > "$PERM/carol"
318
+ echo '{"permission":"read"}' > "$PERM/reader"; echo '{"permission":"write"}' > "$PERM/pipeuser"
319
+ echo '{"permission":"write"}' > "$PERM/builduser"
320
+ export MOCK_PERM_DIR="$PERM"
321
+ BT=2026-10-08T01:00:00Z; AFTER=2026-10-08T02:00:00Z; BEFORE=2026-10-08T00:30:00Z
322
+ # Base trail: everything but CONTEXT/ARCHITECT/QUALITY_GATE on a STANDARD band, built (BUILDER:COMPLETE) at $BT by `builduser`.
323
+ ov_base() {
324
+ jq -nc --arg bt "$BT" '[
325
+ {body:"<!-- FORGE:INVESTIGATOR -->\n<!-- INVESTIGATION:COMPLETE -->",user:{login:"builduser",type:"User"},author_association:"OWNER"},
326
+ {body:"<!-- FORGE:FAST_PATH -->\n**COMPLEXITY_BAND**: STANDARD",user:{login:"builduser",type:"User"},author_association:"OWNER"},
327
+ {body:"<!-- FORGE:CONTRACT -->\nc",user:{login:"builduser",type:"User"},author_association:"OWNER"},
328
+ {body:"<!-- FORGE:BUILDER -->\nx\n<!-- FORGE:BUILDER:COMPLETE -->",created_at:$bt,updated_at:$bt,user:{login:"builduser",type:"User"},author_association:"OWNER"}]'
329
+ }
330
+ CUR_MISSING='ARCHITECT,CONTEXT,QUALITY_GATE'
331
+ # ovc <login> <type> <created> <updated> <head> <missing> <reason> -> one override comment (JSON)
332
+ ovc() {
333
+ jq -nc --arg l "$1" --arg t "$2" --arg c "$3" --arg u "$4" --arg h "$5" --arg m "$6" --arg r "$7" \
334
+ '{body:("<!-- FORGE:PHASE_TRAIL_OVERRIDE -->\n**Head**: "+$h+"\n**Missing**: "+$m+"\n**Reason**: "+$r),created_at:$c,updated_at:$u,author_association:"OWNER",user:{login:$l,type:$t}}'
335
+ }
336
+ ovf() { # ovf <name> <override comment json...> -> fixture = base trail + comments
337
+ local out="$TMP_FX/$1.json"; shift
338
+ { ov_base | jq -c '.[]'; for c in "$@"; do echo "$c"; done; } | jq -sc '.' > "$out"; echo "$out"
339
+ }
340
+ GOODC() { ovc alice User "$AFTER" "$AFTER" "$HS" "$CUR_MISSING" "gate misfire, verified by hand"; }
341
+ orun() { OUT=$(MOCK_GH_JSON="$1" bash "$VERIFY" 3061 -R o/r --head-sha "$HS" "${@:2}" 2>/dev/null); RC=$?; }
342
+ expect_override() { orun "$2" "${@:3}"; [ $RC -eq 0 ] && echo "$OUT" | grep -q 'PHASE_TRAIL: OVERRIDDEN' && echo "$OUT" | grep -q '^OVERRIDE: approver=' && ok "$1" || bad "$1 (rc=$RC out=$OUT)"; }
343
+ expect_blocked() { orun "$2" "${@:3}"; [ $RC -eq 1 ] && echo "$OUT" | grep -q 'PHASE_TRAIL: FAIL' && ! echo "$OUT" | grep -q 'OVERRIDDEN' && ok "$1" || bad "$1 (rc=$RC out=$OUT)"; }
344
+
345
+ expect_blocked "no override comment stays blocked" "$(ovf o0)"
346
+ expect_override "valid human override is accepted" "$(ovf o1 "$(GOODC)")"
347
+ orun "$(ovf o1b "$(GOODC)")"
348
+ echo "$OUT" | grep -q "^OVERRIDE: approver=alice head=$HS missing=$CUR_MISSING reason=gate misfire, verified by hand$" && ok "OVERRIDE line carries approver, head, missing set and reason" || bad "OVERRIDE line ($OUT)"
349
+ echo "$OUT" | grep -q '^MISSING:' && bad "accepted override must not emit MISSING lines" || ok "accepted override emits no MISSING lines"
350
+ expect_override "admin permission is accepted" "$(ovf o2 "$(ovc carol User "$AFTER" "$AFTER" "$HS" "$CUR_MISSING" "ok")")"
351
+ expect_override "uppercase head and shuffled/spaced missing list still bind to the same set" \
352
+ "$(ovf o3 "$(ovc alice User "$AFTER" "$AFTER" "$(echo $HS | tr a-f A-F)" "QUALITY_GATE , ARCHITECT,CONTEXT -> x" "ok")")"
353
+ expect_override "a later valid override wins over an earlier rejected one" \
354
+ "$(ovf o4 "$(ovc reader User "$BEFORE" "$BEFORE" "$HS" "$CUR_MISSING" "old")" "$(GOODC)")"
355
+
356
+ # One test per rejection rule
357
+ expect_blocked "rejects: Bot author" "$(ovf r1 "$(ovc alice Bot "$AFTER" "$AFTER" "$HS" "$CUR_MISSING" "r")")"
358
+ expect_blocked "rejects: null user" "$(ovf r2 "$(GOODC | jq -c '.user=null')")"
359
+ expect_blocked "rejects: missing user.type" "$(ovf r3 "$(GOODC | jq -c 'del(.user.type)')")"
360
+ expect_blocked "rejects: edited comment (updated_at != created_at)" "$(ovf r4 "$(ovc alice User "$AFTER" "2026-10-08T03:00:00Z" "$HS" "$CUR_MISSING" "r")")"
361
+ expect_blocked "rejects: missing timestamps" "$(ovf r5 "$(GOODC | jq -c 'del(.updated_at)')")"
362
+ expect_blocked "rejects: read-only collaborator" "$(ovf r6 "$(ovc reader User "$AFTER" "$AFTER" "$HS" "$CUR_MISSING" "r")")"
363
+ expect_blocked "rejects: author with no permission record" "$(ovf r7 "$(ovc stranger User "$AFTER" "$AFTER" "$HS" "$CUR_MISSING" "r")")"
364
+ 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=$?
365
+ [ $RC -eq 1 ] && ! echo "$OUT" | grep -q OVERRIDDEN && ok "rejects: permission API error fails closed (exit 1)" || bad "permission api error (rc=$RC out=$OUT)"
366
+ expect_blocked "rejects: wrong head sha" "$(ovf r9 "$(ovc alice User "$AFTER" "$AFTER" "$HS2" "$CUR_MISSING" "r")")"
367
+ expect_blocked "rejects: truncated head sha" "$(ovf r10 "$(ovc alice User "$AFTER" "$AFTER" "${HS:0:12}" "$CUR_MISSING" "r")")"
368
+ expect_blocked "rejects: override for a different MISSING set" "$(ovf r11 "$(ovc alice User "$AFTER" "$AFTER" "$HS" "ARCHITECT,CONTEXT" "r")")"
369
+ expect_blocked "rejects: override for a superset of MISSING" "$(ovf r12 "$(ovc alice User "$AFTER" "$AFTER" "$HS" "$CUR_MISSING,CONTRACT" "r")")"
370
+ expect_blocked "rejects: posted before BUILDER:COMPLETE" "$(ovf r13 "$(ovc alice User "$BEFORE" "$BEFORE" "$HS" "$CUR_MISSING" "r")")"
371
+ expect_blocked "rejects: posted exactly at BUILDER:COMPLETE time" "$(ovf r14 "$(ovc alice User "$BT" "$BT" "$HS" "$CUR_MISSING" "r")")"
372
+ expect_blocked "rejects: empty reason" "$(ovf r15 "$(ovc alice User "$AFTER" "$AFTER" "$HS" "$CUR_MISSING" " ")")"
373
+ expect_blocked "rejects: reason that is only control characters" "$(ovf r16 "$(ovc alice User "$AFTER" "$AFTER" "$HS" "$CUR_MISSING" $'\x1b\x07')")"
374
+ expect_blocked "rejects: login with path characters (no API path injection)" "$(ovf r17 "$(ovc 'alice/../x' User "$AFTER" "$AFTER" "$HS" "$CUR_MISSING" "r")")"
375
+ # Pipeline identity: configured login, and derived (author of the pipeline's own FORGE markers)
376
+ FORGE_TRAIL_PIPELINE_LOGINS="Alice" expect_blocked "rejects: configured pipeline login (case-insensitive)" "$(ovf p1 "$(GOODC)")"
377
+ FORGE_TRAIL_PIPELINE_LOGINS="x, alice ,y" expect_blocked "rejects: configured pipeline login in a list" "$(ovf p2 "$(GOODC)")"
378
+ expect_blocked "rejects: derived pipeline identity (author of FORGE:BUILDER/INVESTIGATOR/CONTRACT)" "$(ovf p3 "$(ovc builduser User "$AFTER" "$AFTER" "$HS" "$CUR_MISSING" "self approve")")"
379
+ expect_blocked "rejects: derived pipeline identity matches case-insensitively" "$(ovf p4 "$(ovc BuildUser User "$AFTER" "$AFTER" "$HS" "$CUR_MISSING" "self approve")")"
380
+ # A pipeline App identity that posts markers is also excluded even if typed User in the override
381
+ PIPE_C=$(jq -nc '{body:"<!-- FORGE:QUALITY_GATE -->\n**Result**: FAIL",user:{login:"pipeuser",type:"User"},author_association:"OWNER"}')
382
+ 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")")"
383
+ # Override cannot be evaluated without --head-sha
384
+ OUT=$(MOCK_GH_JSON="$(ovf n1 "$(GOODC)")" bash "$VERIFY" 3061 -R o/r 2>/dev/null); RC=$?
385
+ [ $RC -eq 1 ] && ! echo "$OUT" | grep -q OVERRIDDEN && ok "rejects: no --head-sha means the override is never evaluated" || bad "no head-sha (rc=$RC out=$OUT)"
386
+ for bs in "" "xyz" "${HS}0"; do
387
+ OUT=$(MOCK_GH_JSON="$(ovf n2 "$(GOODC)")" bash "$VERIFY" 3061 -R o/r --head-sha "$bs" 2>/dev/null); RC=$?
388
+ [ $RC -eq 2 ] && echo "$OUT" | grep -q 'PHASE_TRAIL: ERROR' && ok "invalid --head-sha '$bs' fails closed (exit 2)" || bad "head-sha '$bs' (rc=$RC out=$OUT)"
389
+ done
390
+ OUT=$(MOCK_GH_JSON="$TMP_FX/n2.json" bash "$VERIFY" 3061 -R o/r --head-sha 2>/dev/null); RC=$?
391
+ [ $RC -eq 2 ] && ok "--head-sha without a value exits 2" || bad "head-sha no value (rc=$RC)"
392
+ # Exit 2 is never overridable
393
+ 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=$?
394
+ [ $RC -eq 2 ] && ! echo "$OUT" | grep -q OVERRIDDEN && ok "exit 2 (unreadable trail) is not overridable" || bad "exit 2 override (rc=$RC out=$OUT)"
395
+ { cat "$TMP_FX/o1.json"; echo '{"message":"Server Error"}'; } > "$TMP_FX/o2bad.json"
396
+ OUT=$(MOCK_GH_JSON="$TMP_FX/o2bad.json" bash "$VERIFY" 3061 -R o/r --head-sha "$HS" 2>/dev/null); RC=$?
397
+ [ $RC -eq 2 ] && ! echo "$OUT" | grep -q OVERRIDDEN && ok "exit 2 (malformed page) is not overridable" || bad "exit 2 malformed override (rc=$RC out=$OUT)"
398
+ 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=$?
399
+ [ $RC -eq 2 ] && ! echo "$OUT" | grep -q OVERRIDDEN && ok "exit 2 (bad QG_SINCE) is not overridable" || bad "exit 2 qg_since override (rc=$RC out=$OUT)"
400
+ # BUILDER present but undated -> cannot prove ordering -> rejected
401
+ jq -c 'map(if (.body|startswith("<!-- FORGE:BUILDER -->")) then del(.updated_at) else . end)' "$TMP_FX/o1.json" > "$TMP_FX/nobt.json"
402
+ expect_blocked "rejects: BUILDER:COMPLETE undated (ordering unprovable)" "$TMP_FX/nobt.json"
403
+ # An already-passing trail never needs (or echoes) an override
404
+ expect_pass "passing trail is unaffected by --head-sha" "$(mk ovp INV CONTRACT FP_COMPLEX CONTEXT ARCH QG_PASS)" --head-sha "$HS"
405
+ # Sanitiser: ESC, marker injection, backticks, newlines, cap
406
+ LONG=$(printf 'x%.0s' $(seq 1 400))
407
+ orun "$(ovf s1 "$(ovc alice User "$AFTER" "$AFTER" "$HS" "$CUR_MISSING" $'bad\x1b[2Jreason <!-- FORGE:BUILDER:COMPLETE --> `tick` spaced')")"
408
+ RLINE=$(echo "$OUT" | grep '^OVERRIDE:')
409
+ [ $RC -eq 0 ] && ! printf '%s' "$RLINE" | LC_ALL=C grep -q $'\x1b' && ! echo "$RLINE" | grep -q '<!--' && ! echo "$RLINE" | grep -q -e '-->' && ! echo "$RLINE" | grep -q '`' && echo "$RLINE" | grep -q 'reason=bad\[2Jreason <!-/-' \
410
+ && ok "reason sanitiser strips ESC, neutralises comment markers and backticks, collapses whitespace" || bad "sanitiser (rc=$RC line=$RLINE)"
411
+ orun "$(ovf s2 "$(ovc alice User "$AFTER" "$AFTER" "$HS" "$CUR_MISSING" "$LONG")")"
412
+ RS=$(echo "$OUT" | grep '^OVERRIDE:' | sed 's/.*reason=//')
413
+ [ $RC -eq 0 ] && [ "${#RS}" -eq 200 ] && ok "reason is capped at 200 characters" || bad "reason cap (rc=$RC len=${#RS})"
414
+ # Multi-line reason: only the single Reason line is read; injected extra lines cannot add OVERRIDE output lines
415
+ 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"}')")"
416
+ [ $RC -eq 0 ] && [ "$(echo "$OUT" | grep -c '^OVERRIDE:')" -eq 1 ] && echo "$OUT" | grep -q 'approver=alice' && echo "$OUT" | grep -q 'reason=first$' && ok "injected extra lines cannot forge an OVERRIDE line" || bad "line injection (rc=$RC out=$OUT)"
417
+ # FAST_PATH qualifier variant: missing marker name normalises on both sides
418
+ NOBAND=$(ov_base | jq -c 'map(if (.body|startswith("<!-- FORGE:FAST_PATH")) then .body="<!-- FORGE:FAST_PATH -->\nnothing" else . end)')
419
+ echo "$NOBAND" > "$TMP_FX/nb.json"
420
+ CUR2='ARCHITECT,CONTEXT,FAST_PATH,QUALITY_GATE'
421
+ { jq -c '.[]' "$TMP_FX/nb.json"; ovc alice User "$AFTER" "$AFTER" "$HS" "$CUR2" "qualifier variant"; } | jq -sc . > "$TMP_FX/nb2.json"
422
+ expect_override "MISSING 'FAST_PATH (no COMPLEXITY_BAND value)' binds by marker name" "$TMP_FX/nb2.json"
423
+ # --- Review-finding hardening (#3268-#3274) ---
424
+ echo '{"permission":"write"}' > "$PERM/octo_acme"
425
+ expect_override "EMU login with underscore is accepted (#3270)" "$(ovf h1 "$(ovc octo_acme User "$AFTER" "$AFTER" "$HS" "$CUR_MISSING" "emu approver")")"
426
+ DECOYS=(); for i in 1 2 3 4 5 6; do DECOYS+=("$(ovc "decoy$i" User "2026-10-08T03:0$i:00Z" "2026-10-08T03:0$i:00Z" "$HS2" "$CUR_MISSING" "decoy")"); done
427
+ expect_override "5-candidate cap is not starved by decoys failing local checks (#3268)" "$(ovf h2 "$(GOODC)" "${DECOYS[@]}")"
428
+ # forge#3279: non-collaborator decoys that pass every local check must not consume the cap; repeat logins cost one slot
429
+ PDEC=(); for i in 1 2 3 4 5 6 7; do PDEC+=("$(ovc "pdecoy$i" User "2026-10-08T04:0$i:00Z" "2026-10-08T04:0$i:00Z" "$HS" "$CUR_MISSING" "decoy" | jq -c '.author_association="NONE"')"); done
430
+ expect_override "non-collaborator decoy flood (newer, all-local-checks-passing) does not starve a real override (#3279)" "$(ovf h5 "$(GOODC)" "${PDEC[@]}")"
431
+ echo '{"permission":"read"}' > "$PERM/rdr1"
432
+ RDEC=(); for i in 1 2 3 4 5 6; do RDEC+=("$(ovc rdr1 User "2026-10-08T05:0$i:00Z" "2026-10-08T05:0$i:00Z" "$HS" "$CUR_MISSING" "same login repeated")"); done
433
+ expect_override "one login repeated many times costs a single cap slot (#3279)" "$(ovf h6 "$(GOODC)" "${RDEC[@]}")"
434
+ # forge#3306: read-level members (pass the association prefilter) must not consume the cap ahead of an older write approver
435
+ 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
436
+ expect_override "7 distinct read-level members newer than a write approver do not starve it (#3306)" "$(ovf h7 "$(GOODC)" "${RLV[@]}")"
437
+ orun "$(ovf h8 "${RLV[@]}")"
438
+ [ $RC -eq 1 ] && ! echo "$OUT" | grep -q OVERRIDDEN && ok "read-level members only: override rejected (#3306)" || bad "read-level only (rc=$RC out=$OUT)"
439
+ 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
440
+ expect_blocked "unreadable lookups stay bounded by the error cap and fail closed (#3306)" "$(ovf h9 "$(GOODC)" "${ULV[@]}")"
441
+ # forge#3307: association prefilter reuses the configured list; each association value is pinned
442
+ echo '{"permission":"write"}' > "$PERM/memb"; echo '{"permission":"write"}' > "$PERM/collab"; echo '{"permission":"write"}' > "$PERM/concealed"
443
+ assoc() { jq -c --arg a "$1" '.author_association=$a'; }
444
+ expect_override "MEMBER write approver is accepted (#3307)" "$(ovf a1 "$(ovc memb User "$AFTER" "$AFTER" "$HS" "$CUR_MISSING" "r" | assoc MEMBER)")"
445
+ expect_override "COLLABORATOR write approver is accepted (#3307)" "$(ovf a2 "$(ovc collab User "$AFTER" "$AFTER" "$HS" "$CUR_MISSING" "r" | assoc COLLABORATOR)")"
446
+ CDEC=(); for i in 1 2 3 4 5 6 7; do CDEC+=("$(ovc "cdecoy$i" User "2026-10-08T08:0$i:00Z" "2026-10-08T08:0$i:00Z" "$HS" "$CUR_MISSING" "decoy" | assoc CONTRIBUTOR)"); done
447
+ expect_override "CONTRIBUTOR decoy flood does not starve a real override (#3307)" "$(ovf a3 "$(GOODC)" "${CDEC[@]}")"
448
+ CONC=$(ovc concealed User "$AFTER" "$AFTER" "$HS" "$CUR_MISSING" "concealed membership" | assoc CONTRIBUTOR)
449
+ expect_blocked "CONTRIBUTOR write approver is rejected under the default list (#3307)" "$(ovf a4 "$CONC")"
450
+ FORGE_TRAIL_OVERRIDE_ASSOCIATIONS="OWNER,MEMBER,COLLABORATOR,CONTRIBUTOR" expect_override "widened override list accepts a CONTRIBUTOR write approver (#3307, #3350)" "$(ovf a4 "$CONC")"
451
+ FORGE_TRAIL_TRUSTED_ASSOCIATIONS="OWNER,MEMBER,COLLABORATOR,CONTRIBUTOR" expect_override "trail list widened still widens override by default inheritance (#3350)" "$(ovf a4i "$CONC")"
452
+ FORGE_TRAIL_OVERRIDE_ASSOCIATIONS="" expect_blocked "empty override list yields no candidates with default trail list (#3350)" "$(ovf a4e "$(GOODC)")"
453
+ FORGE_TRAIL_TRUSTED_ASSOCIATIONS="OWNER" expect_blocked "narrowed list rejects a MEMBER approver (#3307)" "$(ovf a5 "$(ovc memb User "$AFTER" "$AFTER" "$HS" "$CUR_MISSING" "r" | assoc MEMBER)")"
454
+ FORGE_TRAIL_TRUSTED_ASSOCIATIONS="" expect_blocked "empty list yields no override candidates (fail closed) (#3307)" "$(ovf a6 "$(GOODC)")"
455
+ # Case-variant duplicate logins cost one lookup / one slot
456
+ echo '{"permission":"read"}' > "$PERM/cvx"
457
+ 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
458
+ PL="$TMP_FX/permlog-cv"; : > "$PL"
459
+ 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=$?
460
+ [ $RC -eq 0 ] && echo "$OUT" | grep -q OVERRIDDEN && [ "$(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"))"
461
+ # Decoys (NONE/CONTRIBUTOR) never reach the permission API; the NOTE reports the filtered count without logins
462
+ MIXD=("${PDEC[@]}" "${CDEC[@]}")
463
+ PL="$TMP_FX/permlog-decoy"; : > "$PL"
464
+ ERR=$(MOCK_PERM_LOG="$PL" MOCK_GH_JSON="$(ovf a8 "${MIXD[@]}")" bash "$VERIFY" 3061 -R o/r --head-sha "$HS" 2>&1 >/dev/null);
465
+ [ ! -s "$PL" ] && echo "$ERR" | grep -q 'NOTE: 14 override candidate(s) ignored: author_association not in override-approver set' && ! echo "$ERR" | grep -q 'pdecoy\|cdecoy' \
466
+ && ok "decoys trigger no permission lookups; NOTE carries the count and no logins (#3307)" || bad "decoy no-call (log=$(cat "$PL") err=$ERR)"
467
+ # forge#3351: the NOTE count shares one predicate with the candidates: only otherwise-eligible, association-excluded
468
+ # comments are counted (edited, bot, and pre-floor ones are not)
469
+ EXC=$(ovc xedit User "2026-10-08T11:01:00Z" "2026-10-08T11:02:00Z" "$HS" "$CUR_MISSING" "edited" | assoc NONE)
470
+ EXB=$(ovc xbot Bot "2026-10-08T11:03:00Z" "2026-10-08T11:03:00Z" "$HS" "$CUR_MISSING" "bot" | assoc NONE)
471
+ EXP=$(ovc xold User "2000-01-01T00:00:00Z" "2000-01-01T00:00:00Z" "$HS" "$CUR_MISSING" "pre-floor" | assoc NONE)
472
+ EX1=$(ovc xok1 User "2026-10-08T11:04:00Z" "2026-10-08T11:04:00Z" "$HS" "$CUR_MISSING" "dropped" | assoc NONE)
473
+ EX2=$(ovc xok2 User "2026-10-08T11:05:00Z" "2026-10-08T11:05:00Z" "$HS" "$CUR_MISSING" "dropped" | assoc CONTRIBUTOR)
474
+ 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)
475
+ echo "$ERR" | grep -q 'NOTE: 2 override candidate(s) ignored: author_association not in override-approver set' \
476
+ && ok "NOTE count equals only eligible association-excluded candidates (#3351)" || bad "NOTE count shared predicate (err=$ERR)"
477
+ # 6th distinct unreadable login is cut off: the cap stops the scan before an older real approver
478
+ 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
479
+ PL="$TMP_FX/permlog-unr"; : > "$PL"
480
+ 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=$?
481
+ [ $RC -eq 1 ] && ! echo "$OUT" | grep -q OVERRIDDEN && [ "$(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"))"
482
+ MOCK_SELF_LOGIN=alice orun "$(ovf h3 "$(GOODC)")"
483
+ [ $RC -eq 1 ] && ! echo "$OUT" | grep -q OVERRIDDEN && ok "verifier's own login cannot approve an override (#3269)" || bad "self-login exclusion (rc=$RC out=$OUT)"
484
+ MOCK_SELF_LOGIN=someoneelse orun "$(ovf h3b "$(GOODC)")"
485
+ [ $RC -eq 0 ] && echo "$OUT" | grep -q OVERRIDDEN && ok "an unrelated self-login does not block a valid override (#3269)" || bad "self-login unrelated (rc=$RC out=$OUT)"
486
+ orun "$(ovf h4 "$(ovc alice User "$AFTER" "$AFTER" "$HS" "$CUR_MISSING" $'ping @octocat\u2028next\u2029line')")"
487
+ RS=$(echo "$OUT" | grep '^OVERRIDE:' | sed 's/.*reason=//')
488
+ [ $RC -eq 0 ] && ! printf '%s' "$RS" | grep -q '@' && ! printf '%s' "$RS" | grep -q $'\xe2\x80\xa8' && ok "reason sanitiser neutralises @mentions and U+2028/2029 (#3274)" || bad "sanitiser unicode (rc=$RC rs=$RS)"
489
+ # No BUILDER at all: floor falls back to the newest trusted FORGE marker (#3271)
490
+ 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"
491
+ orun "$TMP_FX/nobuilder.json"; NB_OUT="$OUT"
492
+ NB_MISS=$(echo "$NB_OUT" | sed -n 's/^MISSING: //p' | sed -e 's/ -> .*$//' | paste -sd, -)
493
+ if [ -n "$NB_MISS" ]; then
494
+ { jq -c '.[]' "$TMP_FX/nobuilder.json"; ovc alice User "2026-10-08T01:10:00Z" "2026-10-08T01:10:00Z" "$HS" "$NB_MISS" "older than marker"; } | jq -sc . > "$TMP_FX/nbo1.json"
495
+ expect_blocked "override older than newest trusted marker is rejected when BUILDER is absent (#3271)" "$TMP_FX/nbo1.json"
496
+ { jq -c '.[]' "$TMP_FX/nobuilder.json"; ovc alice User "$AFTER" "$AFTER" "$HS" "$NB_MISS" "newer than marker"; } | jq -sc . > "$TMP_FX/nbo2.json"
497
+ expect_override "override newer than newest trusted marker is accepted when BUILDER is absent (#3271)" "$TMP_FX/nbo2.json"
498
+ else bad "no-BUILDER fixture produced no MISSING set ($NB_OUT)"; fi
499
+ # Rejected permission is explained on stderr (#3272)
500
+ 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)
501
+ echo "$ERRTXT" | grep -q "rejected: permission='read'" && ok "rejected permission prints a diagnostic to stderr (#3272)" || bad "permission diagnostic ($ERRTXT)"
502
+
503
+ # -h documents the override
504
+ HOUT2=$(bash "$VERIFY" -h)
505
+ echo "$HOUT2" | grep -q 'FORGE_TRAIL_PIPELINE_LOGINS' && echo "$HOUT2" | grep -q -- '--head-sha' && ok "-h documents the override and --head-sha" || bad "-h override docs"
506
+
297
507
  echo "passed=$PASS failed=$FAILN"
298
508
  [ "$FAILN" -eq 0 ]