forgedock 1.32.0 → 1.33.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 (69) hide show
  1. package/bin/engine/outcome-codes.mjs +7 -3
  2. package/bin/engine/phases.mjs +29 -8
  3. package/bin/engine-cli.mjs +84 -24
  4. package/bin/forgedock.mjs +21 -1
  5. package/bin/lean/basesync.mjs +98 -0
  6. package/bin/lean/basesync.test.mjs +151 -0
  7. package/bin/lean/batch.mjs +9 -3
  8. package/bin/lean/batch.test.mjs +134 -3
  9. package/bin/lean/budget.mjs +7 -1
  10. package/bin/lean/budget.test.mjs +9 -1
  11. package/bin/lean/conformance.mjs +20 -7
  12. package/bin/lean/conformance.test.mjs +29 -2
  13. package/bin/lean/depcache.mjs +23 -3
  14. package/bin/lean/depcache.test.mjs +42 -1
  15. package/bin/lean/effects.mjs +29 -3
  16. package/bin/lean/execution.mjs +106 -14
  17. package/bin/lean/execution.test.mjs +101 -1
  18. package/bin/lean/hostkill.mjs +8 -0
  19. package/bin/lean/lean.mjs +65 -24
  20. package/bin/lean/lean.test.mjs +221 -10
  21. package/bin/lean/lightlane.mjs +38 -13
  22. package/bin/lean/lightlane.test.mjs +35 -1
  23. package/bin/lean/orchestrate.mjs +93 -26
  24. package/bin/lean/prompts.mjs +17 -7
  25. package/bin/lean/recovery.test.mjs +26 -0
  26. package/bin/lean/retry.mjs +15 -1
  27. package/bin/lean/sandbox-provider.contract.test.mjs +74 -1
  28. package/bin/lean/sandbox-remote.mjs +40 -7
  29. package/bin/lean/schedd.mjs +50 -14
  30. package/bin/lean/schedd.test.mjs +133 -21
  31. package/bin/lean/seqlease.mjs +49 -2
  32. package/bin/lean/seqlease.test.mjs +14 -0
  33. package/bin/lean/sequence.mjs +54 -6
  34. package/bin/lean/sequence.test.mjs +83 -1
  35. package/bin/pool/agent.mjs +435 -0
  36. package/bin/pool/cli.mjs +110 -0
  37. package/bin/pool/docker.mjs +56 -0
  38. package/bin/pool/http.mjs +55 -0
  39. package/bin/pool/router.mjs +299 -0
  40. package/bin/pool/store.mjs +25 -0
  41. package/bin/pool/version.mjs +44 -0
  42. package/bin/runner.mjs +1 -0
  43. package/bin/stats/aggregate.mjs +6 -0
  44. package/bin/stats/index.mjs +14 -2
  45. package/bin/stats/local-source.mjs +1 -1
  46. package/bin/stats/pool.mjs +51 -0
  47. package/bin/stats/pool.test.mjs +39 -0
  48. package/bin/stats/query.mjs +2 -2
  49. package/bin/stats/stats.test.mjs +15 -1
  50. package/commands/orchestrate.md +3 -3
  51. package/docs/CONFIG.md +23 -9
  52. package/docs/OUTCOME-CODES.md +5 -3
  53. package/docs/STATS.md +7 -0
  54. package/package.json +2 -1
  55. package/pool/README.md +13 -0
  56. package/pool/image/Dockerfile +46 -0
  57. package/pool/image/claude-shim +8 -0
  58. package/pool/image/cs-cred +12 -0
  59. package/pool/image/entrypoint.sh +61 -0
  60. package/pool/image/gh-shim +6 -0
  61. package/pool/install/install-agent-linux.sh +53 -0
  62. package/pool/install/install-agent.ps1 +225 -0
  63. package/pool/install/install-agent.sh +101 -0
  64. package/pool/install/install-router.sh +67 -0
  65. package/pool/install/keepalive.ps1 +36 -0
  66. package/pool/install/uninstall-agent.ps1 +20 -0
  67. package/scripts/hotspots.sh +33 -21
  68. package/scripts/hotspots.test.sh +22 -3
  69. package/scripts/refresh-bot-token.test.sh +56 -0
@@ -7,14 +7,17 @@
7
7
  #
8
8
  # Usage: hotspots.sh [--base <ref>] [--] <path>...
9
9
  # Config: forge.yaml maintenance.hotspot {enabled, file_lines, growth_lines, exclude_globs}; defaults true/2000/150/[]
10
- # (also when forge.yaml or yq is missing).
10
+ # (also when forge.yaml or yq is missing); maintenance.generated_paths (forge#4519): shell globs of generated,
11
+ # bot-owned files, default [].
11
12
  # Output (stdout, one JSON object):
12
- # {"enabled","file_lines","growth_lines","files":[{path,lines,commits_90d,hotspot,over,excluded,base_lines,growth}]}
13
+ # {"enabled","file_lines","growth_lines","generated_paths",
14
+ # "files":[{path,lines,commits_90d,hotspot,over,generated,excluded,base_lines,growth}]}
13
15
  # lines = `wc -l` of the working-tree file (0 when missing); commits_90d = `git rev-list --count --since=90.days HEAD`;
14
- # hotspot = lines × commits_90d; over = lines > file_lines; excluded = linguist-generated / linguist-vendored set by
15
- # .gitattributes, or a match of an exclude_glob (shell pattern, `*` crosses `/`). Without --base no blob is read and
16
+ # hotspot = lines × commits_90d; over = lines > file_lines; generated = linguist-generated set by .gitattributes, a
17
+ # match of a generated_paths glob, or `@generated` / `DO NOT EDIT` in the first 5 lines of the working-tree file;
18
+ # excluded = generated, linguist-vendored, or a match of an exclude_glob (shell pattern, `*` crosses `/`). Without --base no blob is read and
16
19
  # base_lines/growth are null; with --base they are the line count at git merge-base(<ref>, HEAD) (0 when absent there)
17
- # and lines - base_lines. enabled:false prints files:[] and does no per-file work.
20
+ # and lines - base_lines. enabled:false prints files:[] (generated_paths still set) and does no per-file work.
18
21
  # Exit: 0 ok | 2 usage or bad ref. bash 3.2 portable.
19
22
  set -uo pipefail
20
23
 
@@ -31,19 +34,21 @@ while [ $# -gt 0 ]; do
31
34
  done
32
35
 
33
36
  # ---- config: maintenance.hotspot as JSON, {} when unreadable ----
34
- CFG='{}'
37
+ CFG='{}'; GEN_CFG='[]'
35
38
  if [ -f forge.yaml ] && command -v yq >/dev/null 2>&1; then
36
39
  c=$(yq -o=json '.maintenance.hotspot // {}' forge.yaml 2>/dev/null) || c=""
37
40
  printf '%s' "$c" | jq -e 'type == "object"' >/dev/null 2>&1 && CFG="$c"
41
+ g=$(yq -o=json '.maintenance.generated_paths // []' forge.yaml 2>/dev/null) || g=""
42
+ printf '%s' "$g" | jq -e 'type == "array"' >/dev/null 2>&1 && GEN_CFG="$g"
38
43
  fi
44
+ GEN_CFG=$(printf '%s' "$GEN_CFG" | jq -c '[.[] | strings | select(length > 0)]')
39
45
  CFG=$(printf '%s' "$CFG" | jq -c '
40
46
  def pos($d): if type == "number" and . > 0 then floor else $d end;
41
47
  { enabled: (.enabled != false), file_lines: (.file_lines | pos(2000)), growth_lines: (.growth_lines | pos(150)),
42
48
  exclude_globs: ([(.exclude_globs // [])[]? | strings]) }')
43
49
  ENABLED=$(printf '%s' "$CFG" | jq -r '.enabled')
44
50
  FILE_LINES=$(printf '%s' "$CFG" | jq -r '.file_lines')
45
- GROWTH_LINES=$(printf '%s' "$CFG" | jq -r '.growth_lines')
46
- head_json() { printf '%s' "$CFG" | jq -c --argjson files "$1" '{enabled, file_lines, growth_lines, files: $files}'; }
51
+ head_json() { printf '%s' "$CFG" | jq -c --argjson files "$1" --argjson gen "$GEN_CFG" '{enabled, file_lines, growth_lines, generated_paths: $gen, files: $files}'; }
47
52
  [ "$ENABLED" = true ] || { head_json '[]'; exit 0; }
48
53
 
49
54
  MB=""
@@ -53,6 +58,19 @@ if [ -n "$BASE" ]; then
53
58
  fi
54
59
  HAS_HEAD=0; git rev-parse --verify -q "HEAD^{commit}" >/dev/null && HAS_HEAD=1
55
60
  GLOBS=$(printf '%s' "$CFG" | jq -r '.exclude_globs[]')
61
+ GEN_GLOBS=$(printf '%s' "$GEN_CFG" | jq -r '.[]')
62
+ # 0 when $1 matches a newline-separated list of shell patterns in $2 (`*` crosses `/`)
63
+ glob_hit() {
64
+ [ -n "$2" ] || return 1
65
+ while IFS= read -r g; do
66
+ [ -n "$g" ] || continue
67
+ # shellcheck disable=SC2254
68
+ case "$1" in $g) return 0 ;; esac
69
+ done <<EOF
70
+ $2
71
+ EOF
72
+ return 1
73
+ }
56
74
 
57
75
  T=$(mktemp -d "${TMPDIR:-/tmp}/hotspots.XXXXXX") || die "mktemp failed"
58
76
  trap 'rm -rf "$T"' EXIT
@@ -72,29 +90,23 @@ for p in "$@"; do
72
90
  [ -n "$p" ] || continue
73
91
  lines=0; [ -f "$p" ] && lines=$(wc -l < "$p" | tr -d ' ')
74
92
  commits=$(cat "$T/c.$i" 2>/dev/null); case "$commits" in ''|*[!0-9]*) commits=0 ;; esac
75
- excluded=false
76
- if git check-attr linguist-generated linguist-vendored -- "$p" 2>/dev/null | grep -qE ': (set|true)$'; then excluded=true; fi
77
- if [ "$excluded" = false ] && [ -n "$GLOBS" ]; then
78
- while IFS= read -r g; do
79
- [ -n "$g" ] || continue
80
- # shellcheck disable=SC2254
81
- case "$p" in $g) excluded=true; break ;; esac
82
- done <<EOF
83
- $GLOBS
84
- EOF
85
- fi
93
+ generated=false; excluded=false
94
+ if git check-attr linguist-generated -- "$p" 2>/dev/null | grep -qE ': (set|true)$' || glob_hit "$p" "$GEN_GLOBS" \
95
+ || { [ -f "$p" ] && head -n 5 "$p" 2>/dev/null | grep -qE '@generated|DO NOT EDIT'; }; then generated=true; excluded=true; fi
96
+ if [ "$excluded" = false ] && git check-attr linguist-vendored -- "$p" 2>/dev/null | grep -qE ': (set|true)$'; then excluded=true; fi
97
+ if [ "$excluded" = false ] && glob_hit "$p" "$GLOBS"; then excluded=true; fi
86
98
  base_lines=null; growth=null
87
99
  if [ -n "$MB" ]; then
88
100
  base_lines=0
89
101
  git cat-file -e "$MB:$p" 2>/dev/null && base_lines=$(git show "$MB:$p" | wc -l | tr -d ' ')
90
102
  growth=$((lines - base_lines))
91
103
  fi
92
- printf '%s\t%s\t%s\t%s\t%s\t%s\n' "$p" "$lines" "$commits" "$excluded" "$base_lines" "$growth" >> "$T/rows"
104
+ printf '%s\t%s\t%s\t%s\t%s\t%s\t%s\n' "$p" "$lines" "$commits" "$excluded" "$base_lines" "$growth" "$generated" >> "$T/rows"
93
105
  done
94
106
 
95
107
  FILES=$(jq -R -s -c --argjson fl "$FILE_LINES" '
96
108
  [split("\n")[] | select(length > 0) | split("\t") |
97
109
  { path: .[0], lines: (.[1] | tonumber), commits_90d: (.[2] | tonumber) } as $r |
98
- $r + { hotspot: ($r.lines * $r.commits_90d), over: ($r.lines > $fl), excluded: (.[3] == "true"),
110
+ $r + { hotspot: ($r.lines * $r.commits_90d), over: ($r.lines > $fl), generated: (.[6] == "true"), excluded: (.[3] == "true"),
99
111
  base_lines: (.[4] | fromjson), growth: (.[5] | fromjson) }]' < "$T/rows")
100
112
  head_json "$FILES"
@@ -27,7 +27,7 @@ echo y >> big.py; lines small.py 10; commit c2
27
27
  OUT="$(bash "$S" big.py ./small.py missing/gone.py)"; rc=$?
28
28
  eq "rc" "$rc" 0
29
29
  eq "defaults" "$(q '[.enabled,.file_lines,.growth_lines]')" '[true,2000,150]'
30
- eq "big facts" "$(q '.files[0]')" '{"path":"big.py","lines":2102,"commits_90d":2,"hotspot":4204,"over":true,"excluded":false,"base_lines":null,"growth":null}'
30
+ eq "big facts" "$(q '.files[0]')" '{"path":"big.py","lines":2102,"commits_90d":2,"hotspot":4204,"over":true,"generated":false,"excluded":false,"base_lines":null,"growth":null}'
31
31
  eq "small facts (./ stripped)" "$(q '.files[1] | [.path,.lines,.commits_90d,.over]')" '["small.py",10,1,false]'
32
32
  eq "missing file" "$(q '.files[2] | [.path,.lines,.commits_90d,.over]')" '["missing/gone.py",0,0,false]'
33
33
  OUT="$(bash "$S")"; eq "no paths" "$(q '.files')" '[]'
@@ -49,6 +49,13 @@ printf 'gen/** linguist-generated\nvendor/** linguist-vendored=true\n' > .gitatt
49
49
  commit gen
50
50
  OUT="$(bash "$S" gen/api.py vendor/lib/x.js dist/bundle.js big.py)"
51
51
  eq "attr exclusions" "$(q '[.files[] | .excluded]')" '[true,true,false,false]'
52
+ eq "attr generated" "$(q '[.files[] | .generated]')" '[true,false,false,false]'
53
+ # forge#4519: a generated-file header in the first 5 lines marks the file generated
54
+ { echo '# @generated'; i=0; while [ $i -lt 2500 ]; do echo "l$i"; i=$((i+1)); done; } > hdr.py
55
+ { i=0; while [ $i -lt 2500 ]; do echo "l$i"; i=$((i+1)); done; echo '# DO NOT EDIT'; } > late.py
56
+ OUT="$(bash "$S" hdr.py late.py)"
57
+ rm -f hdr.py late.py
58
+ eq "header generated" "$(q '[.files[] | [.generated,.excluded]]')" '[[true,true],[false,false]]'
52
59
  if [ "$HAVE_YQ" = 1 ]; then
53
60
  printf 'maintenance:\n hotspot:\n file_lines: 100\n growth_lines: 5\n exclude_globs: ["dist/*", "*.lock"]\n' > forge.yaml
54
61
  OUT="$(bash "$S" dist/bundle.js small.py big.py)"
@@ -60,7 +67,19 @@ if [ "$HAVE_YQ" = 1 ]; then
60
67
  eq "opt-out" "$(q '[.enabled,.files]')" '[false,[]]'
61
68
  printf 'maintenance:\n hotspot:\n file_lines: nope\n' > forge.yaml
62
69
  OUT="$(bash "$S" big.py)"; eq "bad value falls back" "$(q '.file_lines')" 2000
63
- rm -f forge.yaml
70
+ # forge#4519: maintenance.generated_paths marks bot-owned files generated (and so excluded), even over the threshold
71
+ mkdir -p web/public/openapi-versions
72
+ lines web/public/openapi.json 2500; lines web/public/openapi-versions/3.29.2.json 2500
73
+ printf 'maintenance:\n generated_paths: ["web/public/openapi*.json", "web/public/openapi-versions/*"]\n' > forge.yaml
74
+ OUT="$(bash "$S" web/public/openapi.json web/public/openapi-versions/3.29.2.json big.py gen/api.py)"
75
+ eq "generated_paths reported" "$(q '.generated_paths')" '["web/public/openapi*.json","web/public/openapi-versions/*"]'
76
+ eq "generated_paths: generated" "$(q '[.files[] | .generated]')" '[true,true,false,true]'
77
+ eq "generated_paths: excluded" "$(q '[.files[] | .excluded]')" '[true,true,false,true]'
78
+ printf 'maintenance:\n generated_paths: ["*.json"]\n hotspot:\n enabled: false\n' > forge.yaml
79
+ OUT="$(bash "$S" big.py)"; eq "generated_paths with the gate off" "$(q '[.enabled,.generated_paths,.files]')" '[false,["*.json"],[]]'
80
+ printf 'maintenance:\n generated_paths: nope\n' > forge.yaml
81
+ OUT="$(bash "$S" big.py)"; eq "bad generated_paths falls back" "$(q '.generated_paths')" '[]'
82
+ rm -rf forge.yaml web
64
83
  else
65
84
  echo "SKIP: forge.yaml cases need mikefarah yq v4"
66
85
  fi
@@ -93,7 +112,7 @@ done
93
112
  eq "30k exact commits" "$(q '[.files[] | .commits_90d]')" '[300,60,60,60]'
94
113
  eq "30k exact lines" "$(q '[.files[] | .lines]')" '[1,1,1,1]'
95
114
  if [ "$best" -lt 1000 ]; then ok; else bad "30k-commit run took ${best} ms (limit 1000)"; fi
96
- grep -q '"rev-list"' "$TMP/trace.json" && ok || bad "git trace not captured"
115
+ if grep -q '"rev-list"' "$TMP/trace.json"; then ok; else bad "git trace not captured"; fi
97
116
  if grep -qE '"(cat-file|show)"' "$TMP/trace.json"; then bad "blob read without --base"; else ok; fi
98
117
  if [ -e "$TMP/net.log" ]; then bad "network tool invoked: $(tr '\n' ' ' < "$TMP/net.log")"; else ok; fi
99
118
 
@@ -0,0 +1,56 @@
1
+ #!/usr/bin/env bash
2
+ # SPDX-FileCopyrightText: Copyright (c) RapierCraft Studios
3
+ # SPDX-License-Identifier: AGPL-3.0-or-later
4
+ #
5
+ # Fixtures for scripts/refresh-bot-token.sh (#4507): hosts.yml is replaced by rename, never rewritten in place.
6
+ # Run: bash scripts/refresh-bot-token.test.sh (bash 3.2 compatible). Stubs `node` and `gh`; no network.
7
+
8
+ set -u
9
+ DIR=$(cd "$(dirname "$0")" && pwd)
10
+ S="$DIR/refresh-bot-token.sh"
11
+ T=$(mktemp -d "${TMPDIR:-/tmp}/refresh-bot-token-test.XXXXXX")
12
+ trap 'rm -rf "$T"' EXIT
13
+ pass=0; fail=0
14
+ ok() { pass=$((pass+1)); }
15
+ no() { fail=$((fail+1)); echo "FAIL: $1"; }
16
+
17
+ mkdir -p "$T/bin" "$T/cfg"
18
+ echo key > "$T/key.pem"
19
+ cat > "$T/bin/node" <<'STUB'
20
+ #!/usr/bin/env bash
21
+ printf 'tok-new'
22
+ STUB
23
+ cat > "$T/bin/gh" <<'STUB'
24
+ #!/usr/bin/env bash
25
+ echo "$GH_CONFIG_DIR $*" >> "$GH_LOG"
26
+ case "$1 $2" in
27
+ "auth login") [ -n "${GH_FAIL:-}" ] && exit 1; cat > /dev/null; printf 'github.com:\n oauth_token: tok-new\n' > "$GH_CONFIG_DIR/hosts.yml" ;;
28
+ "auth status") echo "Logged in to github.com account rapiercraft-forgedock[bot]" ;;
29
+ esac
30
+ exit 0
31
+ STUB
32
+ chmod +x "$T/bin/node" "$T/bin/gh"
33
+ inode() { ls -i "$1" | awk '{print $1}'; }
34
+
35
+ # (a) success: gh writes in a staging dir; hosts.yml is renamed into place
36
+ printf 'github.com:\n oauth_token: tok-old\n' > "$T/cfg/hosts.yml"
37
+ before=$(inode "$T/cfg/hosts.yml")
38
+ OUT=$(PATH="$T/bin:$PATH" GH_CONFIG_DIR="$T/cfg" GH_LOG="$T/log" bash "$S" --pem "$T/key.pem" 2>&1); RC=$?
39
+ [ "$RC" = 0 ] && ok || no "(a) exit $RC: $OUT"
40
+ grep -q 'tok-new' "$T/cfg/hosts.yml" && ok || no "(a) hosts.yml not updated"
41
+ [ "$(inode "$T/cfg/hosts.yml")" != "$before" ] && ok || no "(a) hosts.yml rewritten in place (same inode)"
42
+ if awk '{print $1}' "$T/log" | grep -qx "$T/cfg"; then no "(a) gh ran against the live config dir"; else ok; fi
43
+ grep -q "auth login" "$T/log" && ok || no "(a) gh auth login not run"
44
+ ls -a "$T/cfg" | grep -q '^\.hosts-stage\.' && no "(a) staging dir left behind" || ok
45
+ case "$OUT" in *"token refreshed"*) ok ;; *) no "(a) success message missing: $OUT" ;; esac
46
+
47
+ # (b) failed login: the live hosts.yml stays byte-identical, exit != 0
48
+ printf 'github.com:\n oauth_token: tok-keep\n' > "$T/cfg/hosts.yml"
49
+ cp "$T/cfg/hosts.yml" "$T/expect"
50
+ PATH="$T/bin:$PATH" GH_CONFIG_DIR="$T/cfg" GH_LOG="$T/log" GH_FAIL=1 bash "$S" --pem "$T/key.pem" >/dev/null 2>&1; RC=$?
51
+ [ "$RC" != 0 ] && ok || no "(b) failed login exited 0"
52
+ cmp -s "$T/expect" "$T/cfg/hosts.yml" && ok || no "(b) hosts.yml changed after a failed login"
53
+ ls -a "$T/cfg" | grep -q '^\.hosts-stage\.' && no "(b) staging dir left behind" || ok
54
+
55
+ echo "refresh-bot-token: $pass passed, $fail failed"
56
+ [ "$fail" = 0 ]