forgedock 1.36.0 → 1.37.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.
- package/bin/bot-token-status.mjs +26 -0
- package/bin/engine/events.mjs +10 -1
- package/bin/engine/events.test.mjs +15 -1
- package/bin/engine/outcome-codes.mjs +39 -1
- package/bin/engine/phases.mjs +65 -5
- package/bin/engine/telemetry-seam.mjs +1 -1
- package/bin/engine/telemetry-seam.test.mjs +4 -0
- package/bin/engine-cli.mjs +268 -37
- package/bin/engine.mjs +3 -1
- package/bin/forgedock.mjs +108 -5
- package/bin/labels.json +50 -0
- package/bin/lean/batch.mjs +10 -4
- package/bin/lean/batch.test.mjs +75 -1
- package/bin/lean/conformance.e2e.test.mjs +1 -1
- package/bin/lean/conformance.mjs +125 -11
- package/bin/lean/conformance.test.mjs +122 -1
- package/bin/lean/decisions.mjs +181 -0
- package/bin/lean/decisions.test.mjs +71 -0
- package/bin/lean/effects.mjs +29 -14
- package/bin/lean/execution.mjs +60 -16
- package/bin/lean/execution.test.mjs +59 -2
- package/bin/lean/gatewatch.mjs +31 -0
- package/bin/lean/gatewatch.test.mjs +59 -0
- package/bin/lean/guardgate.mjs +170 -0
- package/bin/lean/guardgate.test.mjs +153 -0
- package/bin/lean/guards.mjs +243 -0
- package/bin/lean/guards.test.mjs +177 -0
- package/bin/lean/heavy.mjs +54 -8
- package/bin/lean/heavy.test.mjs +74 -1
- package/bin/lean/hostkill.mjs +1 -1
- package/bin/lean/integration.mjs +276 -0
- package/bin/lean/integration.test.mjs +189 -0
- package/bin/lean/lean.mjs +134 -28
- package/bin/lean/lean.test.mjs +239 -22
- package/bin/lean/lifecycle.mjs +129 -0
- package/bin/lean/lifecycle.test.mjs +118 -0
- package/bin/lean/light.mjs +1 -1
- package/bin/lean/milestonesync.mjs +130 -0
- package/bin/lean/milestonesync.test.mjs +133 -0
- package/bin/lean/orchestrate.mjs +83 -15
- package/bin/lean/parked-sweep.mjs +45 -0
- package/bin/lean/parked-sweep.test.mjs +71 -0
- package/bin/lean/postdeploy-check.mjs +38 -6
- package/bin/lean/postdeploy-check.test.mjs +62 -2
- package/bin/lean/postdeploy-monitor.mjs +12 -2
- package/bin/lean/postdeploy-monitor.test.mjs +27 -1
- package/bin/lean/prefilter.mjs +83 -23
- package/bin/lean/prefilter.test.mjs +73 -1
- package/bin/lean/probe.mjs +2 -2
- package/bin/lean/probe.test.mjs +1 -1
- package/bin/lean/prompts.mjs +15 -9
- package/bin/lean/resume.test.mjs +1 -1
- package/bin/lean/retry.mjs +28 -0
- package/bin/lean/sandbox-provider.contract.test.mjs +8 -3
- package/bin/lean/sandbox-remote.mjs +4 -4
- package/bin/lean/schedd.mjs +53 -9
- package/bin/lean/schedd.test.mjs +77 -5
- package/bin/lean/sequence.mjs +24 -0
- package/bin/lean/sequence.test.mjs +31 -1
- package/bin/lean/signoff.mjs +66 -0
- package/bin/lean/signoff.test.mjs +102 -0
- package/bin/lean/train.mjs +177 -0
- package/bin/lean/train.test.mjs +113 -0
- package/bin/pool/cli.mjs +2 -0
- package/bin/pool/health.mjs +30 -0
- package/bin/review-context.mjs +298 -0
- package/bin/runner.mjs +26 -3
- package/bin/stats/compounding.mjs +157 -0
- package/bin/stats/compounding.test.mjs +74 -0
- package/bin/stats/index.mjs +21 -2
- package/commands/bootstrap.md +10 -8
- package/commands/deploy-info.md +3 -0
- package/commands/issue.md +2 -0
- package/commands/milestone.md +43 -54
- package/commands/orchestrate/phase-4-execution.md +2 -0
- package/commands/orchestrate.md +8 -1
- package/commands/pipeline-status.md +20 -0
- package/commands/review-pr.md +1 -0
- package/commands/work-on/build/implement.md +1 -1
- package/commands/work-on/build.md +4 -2
- package/commands/work-on/investigate.md +3 -1
- package/commands/work-on/remediate.md +1 -1
- package/docs/CONFIG.md +71 -2
- package/docs/EVENTS.md +1 -4
- package/docs/OUTCOME-CODES.md +9 -1
- package/docs/STATS.md +20 -0
- package/docs/site/start-a-new-project.md +1 -1
- package/docs/spec/label-state-machine.md +6 -0
- package/package.json +1 -1
- package/pool/install/install-agent.sh +4 -2
- package/pool/install/install-router.sh +4 -2
- package/pool/install/install-schedd.sh +28 -0
- package/pool/install/unit-hardening.sh +34 -0
- package/scripts/acceptance-red-green.sh +19 -2
- package/scripts/acceptance-red-green.test.sh +2 -0
- package/scripts/bootstrap-create.mjs +3 -2
- package/scripts/bootstrap-validate.mjs +31 -4
- package/scripts/file-deferred-followups.sh +285 -77
- package/scripts/file-deferred-followups.test.sh +111 -4
- package/scripts/hotspots.sh +23 -2
- package/scripts/hotspots.test.sh +9 -0
- package/scripts/plan-validate-live.mjs +111 -0
- package/scripts/readiness.mjs +141 -0
- package/scripts/review-prepare.sh +17 -1
- package/scripts/review-prepare.test.sh +17 -0
- package/scripts/settle-scope.mjs +74 -0
- package/scripts/systemd/forgedock-bot-token-refresh.service +7 -0
- package/scripts/systemd/forgedock-bot-token-refresh.timer +11 -0
- package/scripts/wait-ci-green.sh +14 -3
- package/scripts/wait-ci-green.test.sh +3 -1
- package/templates/bootstrap/needs-human-issue.md +4 -1
- package/templates/github-actions/migration-journal-guard.test.mjs +26 -0
package/scripts/wait-ci-green.sh
CHANGED
|
@@ -71,11 +71,13 @@ jitter() { awk -v s="$1" -v p="$JITTER_PCT" -v r="$RANDOM" 'BEGIN{ v=s*(1-p/100+
|
|
|
71
71
|
# classify_gh <text> -> prints one class, or nothing when the text is unrecognised:
|
|
72
72
|
# limited real rate-limit evidence (x-ratelimit-remaining: 0, HTTP 429, "API rate limit exceeded" or GraphQL "API rate limit already exceeded", secondary rate limit,
|
|
73
73
|
# abuse detection, too many requests): a wait, bounded by the whole window
|
|
74
|
-
#
|
|
74
|
+
# unauth a bare 401 (expired token, forge#4662): the refresh timer may fix it, so a small bounded retry, then an explicit auth error
|
|
75
|
+
# auth a bare 403 (missing scope): permanent, never waited on
|
|
75
76
|
# transient 5xx / gateway / network failures: a small bounded retry (FORGE_CI_TRANSIENT_RETRIES), never the long wait
|
|
76
77
|
classify_gh() {
|
|
77
78
|
if printf '%s' "$1" | grep -Eqi 'api rate limit (already )?exceeded|secondary (rate )?limit|abuse detection|x-ratelimit-remaining: *0|HTTP 429|status 429|too many requests'; then echo limited
|
|
78
|
-
elif printf '%s' "$1" | grep -Eqi 'HTTP
|
|
79
|
+
elif printf '%s' "$1" | grep -Eqi 'HTTP 401|status 401|bad credentials|requires authentication'; then echo unauth
|
|
80
|
+
elif printf '%s' "$1" | grep -Eqi 'HTTP 403|status 403'; then echo auth
|
|
79
81
|
elif printf '%s' "$1" | grep -Eqi 'HTTP 5[0-9][0-9]|status 5[0-9][0-9]|bad gateway|service unavailable|gateway time-?out|timed? ?out|i/o timeout|connection (reset|refused)|could not resolve host|unexpected EOF|network is unreachable|TLS handshake'; then echo transient
|
|
80
82
|
fi
|
|
81
83
|
}
|
|
@@ -94,6 +96,10 @@ while :; do
|
|
|
94
96
|
START_SHA=""; _hc="$(classify_gh "$(cat "$_herr" 2>/dev/null)")"
|
|
95
97
|
case "$_hc" in
|
|
96
98
|
auth) emit ERROR; echo "cannot read PR #$PR head in $REPO (HTTP 401/403 without rate-limit evidence)"; exit 2 ;;
|
|
99
|
+
unauth)
|
|
100
|
+
_hfail=$((_hfail + 1))
|
|
101
|
+
if [ "$_hfail" -gt 2 ]; then emit ERROR; echo "cannot read PR #$PR head in $REPO: HTTP 401 (bot token expired or revoked?) ${_hfail} times in a row; run scripts/refresh-bot-token.sh"; exit 2; fi
|
|
102
|
+
sleep "$(jitter "$INTERVAL")" ;;
|
|
97
103
|
limited)
|
|
98
104
|
_hlim=$((_hlim + 1))
|
|
99
105
|
if [ $(( $(date +%s) - _hstart )) -ge "$TIMEOUT" ]; then emit RATE_LIMITED; echo "PR #$PR: GitHub rate-limited for the whole ${TIMEOUT}s window reading the PR head; retry later"; exit 4; fi
|
|
@@ -117,8 +123,13 @@ while :; do
|
|
|
117
123
|
NC_OUT="$(gh pr checks "$PR" -R "$REPO" 2>&1 || true)"
|
|
118
124
|
if printf '%s' "$NC_OUT" | grep -qi 'no checks reported'; then JSON='[]'
|
|
119
125
|
elif GH_CLASS="$(classify_gh "$NC_OUT")"; [ "$GH_CLASS" = auth ]; then
|
|
120
|
-
# A bare
|
|
126
|
+
# A bare 403 is an auth fault that never clears: fail fast instead of waiting out the window (forge#4037).
|
|
121
127
|
emit ERROR; echo "gh pr checks refused (HTTP 401/403 without rate-limit evidence); fix the token or its scopes"; exit 2
|
|
128
|
+
elif [ "$GH_CLASS" = unauth ]; then
|
|
129
|
+
# forge#4662: an expired token is unknown state, not "no checks": back off, then fail with an explicit auth message.
|
|
130
|
+
transient=$((transient + 1))
|
|
131
|
+
if [ "$transient" -gt "$TRANSIENT_MAX" ]; then emit ERROR; echo "gh pr checks refused (HTTP 401) ${transient} times in a row; the bot token is expired or revoked, run scripts/refresh-bot-token.sh"; exit 2; fi
|
|
132
|
+
sleep "$(jitter "$INTERVAL")"; continue
|
|
122
133
|
elif [ "$GH_CLASS" = transient ]; then
|
|
123
134
|
# Network timeout / 5xx: a small bounded retry, never the long rate-limit wait.
|
|
124
135
|
transient=$((transient + 1))
|
|
@@ -74,7 +74,9 @@ expect "rate limited for the whole window is RATE_LIMITED rc 4" "4|CI_GATE: RATE
|
|
|
74
74
|
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)"
|
|
75
75
|
# forge#4037: only real rate-limit evidence is RATE_LIMITED; a bare 401/403 fails fast; timeouts/5xx are a small bounded retry.
|
|
76
76
|
expect "bare 403 is a permanent error rc 2" "2|CI_GATE: ERROR" "$(run "$(mk a403 A403 "$P")" --timeout 60)"
|
|
77
|
-
|
|
77
|
+
# forge#4662: a 401 is a refreshable token, so it is retried a bounded number of times before it is an error.
|
|
78
|
+
expect "401 then pass retries (token refreshed meanwhile)" "0|CI_GATE: PASS" "$(run "$(mk a401 A401 "$P")" --timeout 60)"
|
|
79
|
+
expect "persistent 401 is rc 2 after the bounded retries" "2|CI_GATE: ERROR" "$(run "$(mk a401all A401)" --timeout 60)"
|
|
78
80
|
expect "HTTP 429 is RATE_LIMITED rc 4" "4|CI_GATE: RATE_LIMITED" "$(run "$(mk r429 R429)" --timeout 2)"
|
|
79
81
|
expect "x-ratelimit-remaining: 0 on a 403 is RATE_LIMITED rc 4" "4|CI_GATE: RATE_LIMITED" "$(run "$(mk rhdr RHDR)" --timeout 2)"
|
|
80
82
|
expect "network timeout then pass retries" "0|CI_GATE: PASS" "$(run "$(mk tmo TMO "$P")")"
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
<!-- SPDX-FileCopyrightText: Copyright (c) RapierCraft Studios -->
|
|
2
2
|
<!-- SPDX-License-Identifier: AGPL-3.0-or-later -->
|
|
3
3
|
<!-- /bootstrap needs-human issue: one per credential, account or external input (OAuth client, cloud or DNS account,
|
|
4
|
-
payment keys, API keys, legal review). Label it needs-human and set "needsHuman": true in the plan. Code issues
|
|
4
|
+
payment keys, API keys, legal review). Label it needs-human and needs-human:external (the script adds the latter), give it a `gates:` line (preview|staging-deploy|prod-deploy|feature-flag:<name>) and an `owner:` line, and set "needsHuman": true in the plan. Code issues
|
|
5
5
|
reference it (#{{KEY}}) but never list it under Dependencies; their mocks keep dev and CI green. Copy from "## Problem" down. -->
|
|
6
6
|
## Problem
|
|
7
7
|
|
|
8
|
+
gates: preview
|
|
9
|
+
owner: maintainer
|
|
10
|
+
|
|
8
11
|
{which credential, account or external input is needed, and which issues use it}
|
|
9
12
|
|
|
10
13
|
### Setup steps
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// Guard test template for repos with a drizzle-style migration journal (#4661). Copy it next to your tests and set JOURNAL.
|
|
2
|
+
// Newest-only migrators (drizzle `migrate()`) skip an entry whose `when` is older than one already applied. CI migrates a
|
|
3
|
+
// fresh database, so it never notices; parallel milestone branches interleave stamps and hit this on persistent databases.
|
|
4
|
+
// Run in CI with the base ref fetched: BASE_REF=origin/main node --test migration-journal-guard.test.mjs
|
|
5
|
+
//
|
|
6
|
+
// Optional upgrade test (repo-specific, not provided): migrate a database to the base's head, apply the branch's
|
|
7
|
+
// migrations, then assert that every migration the branch added is recorded as applied.
|
|
8
|
+
import { test } from "node:test";
|
|
9
|
+
import assert from "node:assert/strict";
|
|
10
|
+
import { execFileSync } from "node:child_process";
|
|
11
|
+
import { readFileSync } from "node:fs";
|
|
12
|
+
|
|
13
|
+
const JOURNAL = process.env.JOURNAL ?? "drizzle/meta/_journal.json";
|
|
14
|
+
const BASE_REF = process.env.BASE_REF ?? "origin/main";
|
|
15
|
+
const entries = JSON.parse(readFileSync(JOURNAL, "utf8")).entries;
|
|
16
|
+
|
|
17
|
+
test("journal `when` strictly increases", () => {
|
|
18
|
+
for (let i = 1; i < entries.length; i++) assert.ok(entries[i].when > entries[i - 1].when, `${entries[i].tag} when ${entries[i].when} is not after ${entries[i - 1].tag}`);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test("no new entry is older than the base max `when`", (t) => {
|
|
22
|
+
let base;
|
|
23
|
+
try { base = JSON.parse(execFileSync("git", ["show", `${BASE_REF}:${JOURNAL}`], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] })).entries; } catch { return t.skip(`no ${JOURNAL} at ${BASE_REF}`); }
|
|
24
|
+
const known = new Set(base.map((e) => e.tag)), max = Math.max(...base.map((e) => e.when));
|
|
25
|
+
for (const e of entries.filter((x) => !known.has(x.tag))) assert.ok(e.when > max, `${e.tag} when ${e.when} is not above the base max ${max}: re-stamp it with the generator`);
|
|
26
|
+
});
|