forgedock 1.18.0 → 1.19.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 (116) hide show
  1. package/bin/engine/outcome-codes.mjs +13 -0
  2. package/bin/engine/phases.mjs +54 -4
  3. package/bin/engine/runlog.mjs +10 -2
  4. package/bin/engine/telemetry-seam.mjs +3 -0
  5. package/bin/engine/telemetry.test.mjs +3 -2
  6. package/bin/engine-cli.mjs +32 -2
  7. package/bin/engine.mjs +139 -7
  8. package/bin/runner.mjs +69 -9
  9. package/bin/stats/aggregate.mjs +7 -3
  10. package/bin/stats/cache.mjs +7 -3
  11. package/bin/stats/github-source.mjs +136 -28
  12. package/bin/stats/index.mjs +46 -9
  13. package/bin/stats/render.mjs +1 -1
  14. package/bin/stats/stats.test.mjs +201 -40
  15. package/commands/review-pr-staging.md +4 -4
  16. package/commands/review-pr.md +25 -15
  17. package/commands/work-on/build/architect.md +53 -0
  18. package/commands/work-on/build/implement.md +19 -0
  19. package/commands/work-on/build/validate.md +320 -28
  20. package/commands/work-on/build.md +66 -9
  21. package/commands/work-on/investigate.md +7 -0
  22. package/commands/work-on/remediate.md +32 -13
  23. package/commands/work-on/review.md +32 -19
  24. package/docs/CONFIG.md +89 -2
  25. package/docs/OUTCOME-CODES.md +5 -1
  26. package/docs/STATS.md +11 -4
  27. package/docs/TELEMETRY.md +5 -2
  28. package/docs/WORK-ON-RUNTIME.md +18 -0
  29. package/package.json +2 -2
  30. package/packages/protocol/src/types.js +1 -1
  31. package/scripts/acceptance-red-green.sh +90 -0
  32. package/scripts/acceptance-red-green.test.sh +74 -0
  33. package/scripts/cap-findings.test.sh +2 -0
  34. package/scripts/check-contract-scope.test.sh +2 -0
  35. package/scripts/check-edge-cases.sh +143 -0
  36. package/scripts/check-edge-cases.test.sh +175 -0
  37. package/scripts/check-spec-markers.sh +2 -0
  38. package/scripts/check-test-tmp-cleanup.sh +56 -0
  39. package/scripts/check-test-tmp-cleanup.test.sh +69 -0
  40. package/scripts/ci-local/adapters/azure-pipelines.mjs +113 -0
  41. package/scripts/ci-local/adapters/bitbucket.mjs +64 -0
  42. package/scripts/ci-local/adapters/buildkite.mjs +58 -0
  43. package/scripts/ci-local/adapters/circleci.mjs +101 -0
  44. package/scripts/ci-local/adapters/common.mjs +115 -0
  45. package/scripts/ci-local/adapters/explicit.mjs +21 -0
  46. package/scripts/ci-local/adapters/github-actions.mjs +349 -0
  47. package/scripts/ci-local/adapters/gitlab-ci.mjs +184 -0
  48. package/scripts/ci-local/adapters/portability.mjs +104 -0
  49. package/scripts/ci-local/adapters/task-runners.mjs +67 -0
  50. package/scripts/ci-local/cli.mjs +50 -0
  51. package/scripts/ci-local/lib.mjs +371 -0
  52. package/scripts/ci-local/plan.mjs +186 -0
  53. package/scripts/ci-local/run.mjs +345 -0
  54. package/scripts/ci-local/scoping.mjs +508 -0
  55. package/scripts/ci-local.sh +97 -0
  56. package/scripts/ci-local.test.sh +919 -0
  57. package/scripts/classify-finding.sh +32 -6
  58. package/scripts/classify-finding.test.sh +23 -0
  59. package/scripts/classify-test-run.sh +69 -0
  60. package/scripts/classify-test-run.test.sh +90 -0
  61. package/scripts/close-parent-c3.test.sh +2 -0
  62. package/scripts/consumer-sweep.sh +569 -0
  63. package/scripts/consumer-sweep.test.sh +357 -0
  64. package/scripts/detect-append-registries.sh +106 -0
  65. package/scripts/detect-append-registries.test.sh +64 -0
  66. package/scripts/fixtures/ci-local/azure/azure-pipelines.yml +11 -0
  67. package/scripts/fixtures/ci-local/bitbucket/bitbucket-pipelines.yml +12 -0
  68. package/scripts/fixtures/ci-local/buildkite/.buildkite/pipeline.yml +10 -0
  69. package/scripts/fixtures/ci-local/circleci/.circleci/config.yml +29 -0
  70. package/scripts/fixtures/ci-local/gha-basic/.github/workflows/ci.yml +59 -0
  71. package/scripts/fixtures/ci-local/gha-basic/.github/workflows/docs.yml +10 -0
  72. package/scripts/fixtures/ci-local/gha-basic/.github/workflows/nightly.yml +10 -0
  73. package/scripts/fixtures/ci-local/gha-basic/docs/index.md +1 -0
  74. package/scripts/fixtures/ci-local/gha-basic/src/greet.sh +2 -0
  75. package/scripts/fixtures/ci-local/gha-basic/tests/greet.test.sh +3 -0
  76. package/scripts/fixtures/ci-local/gha-dorny/.github/workflows/ci.yml +51 -0
  77. package/scripts/fixtures/ci-local/gha-dorny/backend/app.py +1 -0
  78. package/scripts/fixtures/ci-local/gha-dorny/docs/a.md +1 -0
  79. package/scripts/fixtures/ci-local/gha-dorny/frontend/app.js +1 -0
  80. package/scripts/fixtures/ci-local/gha-run/.github/workflows/ci.yml +39 -0
  81. package/scripts/fixtures/ci-local/gha-run/src/x.txt +1 -0
  82. package/scripts/fixtures/ci-local/gitlab/.gitlab-ci.yml +43 -0
  83. package/scripts/fixtures/ci-local/gitlab/docs/d.md +1 -0
  84. package/scripts/fixtures/ci-local/gitlab/src/a.txt +1 -0
  85. package/scripts/fixtures/ci-local/monorepo/.github/workflows/ci.yml +13 -0
  86. package/scripts/fixtures/ci-local/monorepo/package-lock.json +1 -0
  87. package/scripts/fixtures/ci-local/monorepo/packages/a/src/a.sh +2 -0
  88. package/scripts/fixtures/ci-local/monorepo/packages/a/tests/a.test.sh +3 -0
  89. package/scripts/fixtures/ci-local/monorepo/packages/b/src/b.sh +2 -0
  90. package/scripts/fixtures/ci-local/monorepo/packages/b/tests/b.test.sh +3 -0
  91. package/scripts/fixtures/ci-local/no-ci/Makefile +6 -0
  92. package/scripts/fixtures/ci-local/no-ci/package.json +1 -0
  93. package/scripts/fixtures/ci-local/no-ci/src/a.js +1 -0
  94. package/scripts/fixtures/ci-local/no-ci/src/b.js +1 -0
  95. package/scripts/fixtures/ci-local/no-ci/tests/a.test.js +2 -0
  96. package/scripts/fixtures/ci-local/no-ci/tests/b.test.js +2 -0
  97. package/scripts/forge-root.test.sh +1 -1
  98. package/scripts/lib/telemetry.sh +1 -0
  99. package/scripts/remediate-approved-head.test.sh +2 -0
  100. package/scripts/remediate-basesync.test.sh +1 -1
  101. package/scripts/review-acceptance-gate.sh +2 -2
  102. package/scripts/review-acceptance-gate.test.sh +20 -0
  103. package/scripts/review-delta.sh +8 -1
  104. package/scripts/review-delta.test.sh +19 -0
  105. package/scripts/review-finalize.sh +193 -41
  106. package/scripts/review-finalize.test.sh +211 -4
  107. package/scripts/review-pr-delta-wiring.test.sh +13 -0
  108. package/scripts/review-prepare.sh +5 -3
  109. package/scripts/review-prepare.test.sh +18 -2
  110. package/scripts/run-shell-tests.sh +5 -0
  111. package/scripts/telemetry.test.sh +2 -0
  112. package/scripts/trusted-comments.test.sh +2 -1
  113. package/scripts/verify-phase-trail.test.sh +8 -0
  114. package/scripts/wait-ci-green.sh +61 -9
  115. package/scripts/wait-ci-green.test.sh +41 -3
  116. package/scripts/with-run-tmpdir.sh +20 -0
@@ -16,6 +16,7 @@ cat > "$T/bin/gh" <<'GH'
16
16
  #!/usr/bin/env bash
17
17
  D="$FAKE_DIR"
18
18
  if [ "$1 $2" = "pr view" ]; then
19
+ if [ -f "$D/headerr" ]; then hn=$(( $(cat "$D/headerr.n" 2>/dev/null || echo 0) + 1 )); echo "$hn" > "$D/headerr.n"; if [ "$hn" -le "$(cat "$D/headerr.max")" ]; then cat "$D/headerr" >&2; exit 1; fi; fi
19
20
  n=$(( $(cat "$D/sha.n" 2>/dev/null || echo 0) + 1 )); echo "$n" > "$D/sha.n"
20
21
  total=$(wc -l < "$D/sha"); [ "$n" -gt "$total" ] && n=$total; sed -n "${n}p" "$D/sha"; exit 0
21
22
  fi
@@ -24,10 +25,14 @@ if [ "$1" = "api" ]; then
24
25
  exit 1
25
26
  fi
26
27
  if [ "$1 $2" = "pr checks" ]; then
27
- case " $* " in *" --json "*) ;; *) [ -f "$D/nochecks" ] && { echo "no checks reported on the 'x' branch" >&2; exit 1; }; exit 1 ;; esac
28
+ case " $* " in *" --json "*) ;; *)
29
+ [ -f "$D/nochecks" ] && { echo "no checks reported on the 'x' branch" >&2; exit 1; }
30
+ cn=$(cat "$D/chk.n" 2>/dev/null || echo 1); while [ ! -f "$D/checks.$cn" ] && [ "$cn" -gt 1 ]; do cn=$((cn-1)); done; cc=$(cat "$D/checks.$cn" 2>/dev/null || true)
31
+ case "$cc" in RALR) echo "HTTP 403: API rate limit already exceeded for user ID 12345. (https://api.github.com/graphql)" >&2 ;; RATE) echo "HTTP 403: API rate limit exceeded for user (https://api.github.com/graphql)" >&2 ;; SECOND) echo "You have exceeded a secondary rate limit" >&2 ;; ERR) echo "HTTP 502: Bad Gateway" >&2 ;; ERRX) echo "boom: unexpected failure" >&2 ;; A403) echo "HTTP 403: Resource not accessible by personal access token" >&2 ;; A401) echo "HTTP 401: Bad credentials" >&2 ;; R429) echo "HTTP 429: Too Many Requests" >&2 ;; RHDR) printf "HTTP 403: Forbidden\nx-ratelimit-remaining: 0\n" >&2 ;; TMO) echo "dial tcp: i/o timeout" >&2 ;; esac
32
+ exit 1 ;; esac
28
33
  n=$(( $(cat "$D/chk.n" 2>/dev/null || echo 0) + 1 )); echo "$n" > "$D/chk.n"
29
34
  f="$D/checks.$n"; while [ ! -f "$f" ] && [ "$n" -gt 1 ]; do n=$((n-1)); f="$D/checks.$n"; done
30
- c=$(cat "$f"); [ "$c" = "ERR" ] && { echo "HTTP 502" >&2; exit 1; }
35
+ c=$(cat "$f"); case "$c" in ERR|ERRX|RALR|RATE|SECOND|A403|A401|R429|RHDR|TMO) exit 1 ;; esac
31
36
  [ "$c" = "NONE" ] && { echo "no checks reported on the 'x' branch" >&2; exit 1; }
32
37
  echo "$c"; exit 0
33
38
  fi
@@ -51,7 +56,40 @@ expect "pending then fail" "1|CI_GATE: FAIL" "$(run "$(mk pendfail '[{"name":"a"
51
56
  expect "pending past timeout" "3|CI_GATE: TIMEOUT" "$(run "$(mk slow '[{"name":"a","bucket":"pending"}]')" --timeout 2)"
52
57
  expect "fail wins over pending" "1|CI_GATE: FAIL" "$(run "$(mk mixed '[{"name":"a","bucket":"pending"},{"name":"b","bucket":"fail"}]')")"
53
58
  expect "transient outage then pass" "0|CI_GATE: PASS" "$(run "$(mk blip ERR "$P")")"
54
- expect "persistent outage" "2|CI_GATE: ERROR" "$(run "$(mk down ERR)" --timeout 60)"
59
+ expect "persistent unclassified failure is an ERROR" "2|CI_GATE: ERROR" "$(run "$(mk down ERRX)" --timeout 60)"
60
+ # Rate-limit / 5xx classification: retried, never an ERROR; a window that only saw them ends RATE_LIMITED (rc 4).
61
+ expect "rate limited then pass retries" "0|CI_GATE: PASS" "$(run "$(mk rl RATE "$P")")"
62
+ expect "secondary limit then pass retries" "0|CI_GATE: PASS" "$(run "$(mk rl2 SECOND "$P")")"
63
+ expect "transient 5xx then pass retries" "0|CI_GATE: PASS" "$(run "$(mk fivexx ERR "$P")")"
64
+ expect "rate limited for the whole window is RATE_LIMITED rc 4" "4|CI_GATE: RATE_LIMITED" "$(run "$(mk rlall RATE)" --timeout 2)"
65
+ expect "more than 5 rate-limit hits are not an ERROR" "0|CI_GATE: PASS" "$(run "$(mk rlmany RATE RATE RATE RATE RATE RATE RATE "$P")" --timeout 60)"
66
+ # forge#4037: only real rate-limit evidence is RATE_LIMITED; a bare 401/403 fails fast; timeouts/5xx are a small bounded retry.
67
+ expect "bare 403 is a permanent error rc 2" "2|CI_GATE: ERROR" "$(run "$(mk a403 A403 "$P")" --timeout 60)"
68
+ expect "bare 401 is a permanent error rc 2" "2|CI_GATE: ERROR" "$(run "$(mk a401 A401 "$P")" --timeout 60)"
69
+ expect "HTTP 429 is RATE_LIMITED rc 4" "4|CI_GATE: RATE_LIMITED" "$(run "$(mk r429 R429)" --timeout 2)"
70
+ expect "x-ratelimit-remaining: 0 on a 403 is RATE_LIMITED rc 4" "4|CI_GATE: RATE_LIMITED" "$(run "$(mk rhdr RHDR)" --timeout 2)"
71
+ expect "network timeout then pass retries" "0|CI_GATE: PASS" "$(run "$(mk tmo TMO "$P")")"
72
+ expect "persistent network timeout is a bounded ERROR, not the long wait" "2|CI_GATE: ERROR" "$(FORGE_CI_TRANSIENT_RETRIES=2 run "$(mk tmoall TMO)" --timeout 600)"
73
+ expect "persistent 5xx is a bounded ERROR, not the long wait" "2|CI_GATE: ERROR" "$(FORGE_CI_TRANSIENT_RETRIES=2 run "$(mk fivexxall ERR)" --timeout 600)"
74
+ # forge#4037: the initial head read uses the same classification.
75
+ d=$(mk headrl "$P"); echo "HTTP 403: API rate limit exceeded for user" > "$d/headerr"; echo 2 > "$d/headerr.max"
76
+ expect "rate-limited head read waits, then passes" "0|CI_GATE: PASS" "$(run "$d" --timeout 60)"
77
+ d=$(mk headrlall "$P"); echo "HTTP 429: Too Many Requests" > "$d/headerr"; echo 99999 > "$d/headerr.max"
78
+ expect "rate-limited head read for the whole window is rc 4" "4|CI_GATE: RATE_LIMITED" "$(run "$d" --timeout 2)"
79
+ d=$(mk head403 "$P"); echo "HTTP 403: Resource not accessible" > "$d/headerr"; echo 99999 > "$d/headerr.max"
80
+ # forge#4040: the GraphQL wording "API rate limit already exceeded (HTTP 403)" is a rate limit (rc 4), not the auth class (rc 2).
81
+ expect "GraphQL 'rate limit already exceeded' 403 is RATE_LIMITED rc 4" "4|CI_GATE: RATE_LIMITED" "$(run "$(mk ralr RALR)" --timeout 2)"
82
+ expect "rate limit already exceeded, then pass" "0|CI_GATE: PASS" "$(run "$(mk ralr2 RALR "$P")" --timeout 60)"
83
+ d=$(mk headralr "$P"); echo "HTTP 403: API rate limit already exceeded for user ID 12345." > "$d/headerr"; echo 99999 > "$d/headerr.max"
84
+ expect "GraphQL 'already exceeded' head read is RATE_LIMITED rc 4" "4|CI_GATE: RATE_LIMITED" "$(run "$d" --timeout 2)"
85
+ d=$(mk head403b "$P"); echo "HTTP 403: Resource not accessible" > "$d/headerr"; echo 99999 > "$d/headerr.max"
86
+ expect "bare 403 head read is rc 2" "2|CI_GATE: ERROR" "$(run "$d" --timeout 60)"
87
+ # Jitter: sleeps vary within +/-20% of the interval (fake sleep records its argument and returns at once).
88
+ mkdir -p "$T/bin2"; printf '#!/usr/bin/env bash\necho "$1" >> "$FAKE_DIR/sleeps"\n' > "$T/bin2/sleep"; chmod +x "$T/bin2/sleep"
89
+ d=$(mk jit '[{"name":"a","bucket":"pending"}]' '[{"name":"a","bucket":"pending"}]' '[{"name":"a","bucket":"pending"}]' '[{"name":"a","bucket":"pending"}]' '[{"name":"a","bucket":"pending"}]' '[{"name":"a","bucket":"pending"}]' '[{"name":"a","bucket":"pending"}]' '[{"name":"a","bucket":"pending"}]' "$P")
90
+ FAKE_DIR="$d" PATH="$T/bin2:$T/bin:$PATH" FORGE_CI_INTERVAL=100 FORGE_CI_SETTLE=0 bash "$GATE" 7 -R o/r >/dev/null 2>&1
91
+ expect "jittered sleeps stay within +/-20%" "0" "$(awk '$1<80||$1>120{bad++} END{print bad+0}' "$d/sleeps")"
92
+ expect "jitter is applied (not a fixed interval)" "1" "$(awk '{u[$1]=1} END{n=0; for(k in u) n++; print (n>1)?1:0}' "$d/sleeps")"
55
93
  d=$(mk none NONE); touch "$d/nochecks"
56
94
  expect "no checks after grace passes" "0|CI_GATE: PASS" "$(run "$d")"
57
95
  expect "no checks + REQUIRE fails closed" "2|CI_GATE: ERROR" "$(FORGE_CI_REQUIRE_CHECKS=1 run "$d")"
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env bash
2
+ # SPDX-FileCopyrightText: Copyright (c) RapierCraft Studios
3
+ # SPDX-License-Identifier: AGPL-3.0-or-later
4
+ #
5
+ # with-run-tmpdir.sh — run a command with a per-run TMPDIR that is removed when it exits.
6
+ #
7
+ # Usage: bash scripts/with-run-tmpdir.sh <command> [args...]
8
+ # Children inherit the exported TMPDIR (os.tmpdir() honours it), so temp dirs a test
9
+ # forgets to remove cannot outlive the run. The command's exit code is preserved.
10
+ # Bash 3.2 portable.
11
+
12
+ set -u
13
+
14
+ [ "$#" -gt 0 ] || { echo "with-run-tmpdir: no command given" >&2; exit 2; }
15
+
16
+ RUN_TMP=$(mktemp -d) || { echo "with-run-tmpdir: cannot create per-run TMPDIR" >&2; exit 1; }
17
+ trap 'rm -rf "$RUN_TMP"' EXIT
18
+ export TMPDIR="$RUN_TMP"
19
+
20
+ "$@"