forgedock 1.16.2 → 1.17.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 (95) hide show
  1. package/bin/engine/outcome-codes.mjs +92 -0
  2. package/bin/engine/phases.mjs +86 -26
  3. package/bin/engine/projector.mjs +48 -1
  4. package/bin/engine/runlog.mjs +13 -5
  5. package/bin/engine/telemetry-seam.mjs +62 -0
  6. package/bin/engine/telemetry-seam.test.mjs +60 -0
  7. package/bin/engine/telemetry.mjs +148 -0
  8. package/bin/engine/telemetry.test.mjs +120 -0
  9. package/bin/engine-cli.mjs +12 -1
  10. package/bin/engine.mjs +145 -40
  11. package/bin/forgedock.mjs +7 -1
  12. package/bin/hooks/interactive-engine.mjs +10 -0
  13. package/bin/hooks/pre-tool-use.mjs +187 -0
  14. package/bin/runner.mjs +7 -0
  15. package/bin/stats/aggregate.mjs +231 -0
  16. package/bin/stats/cache.mjs +40 -0
  17. package/bin/stats/github-source.mjs +140 -0
  18. package/bin/stats/index.mjs +154 -0
  19. package/bin/stats/local-source.mjs +79 -0
  20. package/bin/stats/package.json +6 -0
  21. package/bin/stats/render.mjs +82 -0
  22. package/bin/stats/stats.test.mjs +224 -0
  23. package/commands/cleanup.md +83 -2
  24. package/commands/deploy-info.md +8 -4
  25. package/commands/orchestrate/phase-1-resolve.md +4 -1
  26. package/commands/orchestrate/phase-4-execution.md +17 -2
  27. package/commands/orchestrate/phase-5-cleanup.md +18 -2
  28. package/commands/pipeline-health/phase-1-context.md +2 -0
  29. package/commands/pipeline-health/phase-2-metrics.md +12 -5
  30. package/commands/pipeline-status.md +21 -1
  31. package/commands/review-pr-agents/protocols.md +1 -0
  32. package/commands/review-pr-agents.md +10 -2
  33. package/commands/review-pr-staging.md +102 -25
  34. package/commands/review-pr.md +119 -103
  35. package/commands/work-on/build/implement.md +26 -5
  36. package/commands/work-on/build/validate.md +3 -2
  37. package/commands/work-on/build.md +95 -64
  38. package/commands/work-on/close.md +17 -6
  39. package/commands/work-on/decompose.md +45 -4
  40. package/commands/work-on/investigate.md +6 -2
  41. package/commands/work-on/remediate.md +22 -6
  42. package/commands/work-on/review.md +2 -2
  43. package/docs/CONFIG.md +33 -0
  44. package/docs/OUTCOME-CODES.md +48 -0
  45. package/docs/RELEASE-NOTES.md +12 -0
  46. package/docs/STATS.md +62 -0
  47. package/docs/TELEMETRY.md +65 -0
  48. package/docs/WORK-ON-RUNTIME.md +13 -0
  49. package/docs/site/command-reference.md +1 -1
  50. package/docs/spec/label-state-machine.md +1 -1
  51. package/package.json +3 -2
  52. package/scripts/builder-resume-match.test.sh +58 -0
  53. package/scripts/cap-findings.sh +110 -0
  54. package/scripts/cap-findings.test.sh +130 -0
  55. package/scripts/check-spec-markers.sh +2 -0
  56. package/scripts/classify-finding.sh +65 -16
  57. package/scripts/classify-finding.test.sh +43 -1
  58. package/scripts/classify-grep-exit.sh +20 -0
  59. package/scripts/classify-grep-exit.test.sh +46 -0
  60. package/scripts/close-parent-c3.test.sh +54 -0
  61. package/scripts/cost-report.sh +146 -0
  62. package/scripts/cost-report.test.sh +80 -0
  63. package/scripts/diff-size.sh +5 -0
  64. package/scripts/doctor-pipeline-state.sh +43 -0
  65. package/scripts/file-deferred-followups.sh +159 -0
  66. package/scripts/file-deferred-followups.test.sh +106 -0
  67. package/scripts/fixtures/cost-report/a-merged1.jsonl +6 -0
  68. package/scripts/fixtures/cost-report/a-merged2-remediated.jsonl +7 -0
  69. package/scripts/fixtures/cost-report/a-needs-human.jsonl +4 -0
  70. package/scripts/fixtures/cost-report/b-merged.jsonl +3 -0
  71. package/scripts/fixtures/cost-report/c-merged-attempt-usage.jsonl +8 -0
  72. package/scripts/fixtures/cost-report/hydrated-no-usage.jsonl +3 -0
  73. package/scripts/fixtures/cost-report/old-log-no-version.jsonl +3 -0
  74. package/scripts/fixtures/cost-report/unfinished-no-terminal.jsonl +2 -0
  75. package/scripts/fixtures/cost-report/unfinished-reopened.jsonl +5 -0
  76. package/scripts/forge-root.test.sh +8 -3
  77. package/scripts/lib/fence-bootstrap.sh +4 -3
  78. package/scripts/lib/pr-issue-number.sh +18 -0
  79. package/scripts/lib/review-template-resolve.sh +41 -0
  80. package/scripts/lib/telemetry.sh +168 -0
  81. package/scripts/remediate-liveness.sh +42 -6
  82. package/scripts/review-acceptance-gate.sh +16 -7
  83. package/scripts/review-acceptance-gate.test.sh +13 -0
  84. package/scripts/review-pr-domain-map.test.sh +8 -3
  85. package/scripts/review-pr-gate2.test.sh +15 -0
  86. package/scripts/review-pr-staging-finding-filter.test.sh +44 -0
  87. package/scripts/review-pr-staging-scope.test.sh +65 -5
  88. package/scripts/review-prepare.sh +517 -0
  89. package/scripts/review-prepare.test.sh +270 -0
  90. package/scripts/review-roster.sh +172 -0
  91. package/scripts/review-roster.test.sh +163 -0
  92. package/scripts/run-shell-tests.sh +3 -0
  93. package/scripts/telemetry.test.sh +106 -0
  94. package/scripts/wait-ci-green.sh +8 -1
  95. package/templates/forge.yaml.minimal +9 -0
@@ -0,0 +1,106 @@
1
+ #!/usr/bin/env bash
2
+ # SPDX-FileCopyrightText: Copyright (c) RapierCraft Studios
3
+ # SPDX-License-Identifier: AGPL-3.0-or-later
4
+ # Tests for scripts/lib/telemetry.sh (forge_emit): record shape per event, upsert, rollover, buffer/flush,
5
+ # kill switch, never-fails guarantee. No network: gh is replaced via FORGE_TELEMETRY_GH.
6
+ set -u
7
+ HERE=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
8
+ LIB="$HERE/lib/telemetry.sh"
9
+ PASS=0; FAIL=0
10
+ ok() { PASS=$((PASS+1)); }
11
+ bad() { FAIL=$((FAIL+1)); echo "FAIL: $1" >&2; }
12
+ check() { if [ "$2" = "$3" ]; then ok; else bad "$1 (got '$2', want '$3')"; fi; }
13
+
14
+ TD=$(mktemp -d); trap 'rm -rf "$TD"' EXIT
15
+ export TD FORGE_REPO=o/r FORGE_ISSUE=5 FORGE_TELEMETRY_BUFFER_DIR="$TD/buf"
16
+ unset FORGE_TELEMETRY FORGE_PR FORGE_RUN FORGE_PHASE FORGE_TELEMETRY_MAX_CHARS
17
+ cat > "$TD/gh" <<'STUB'
18
+ #!/usr/bin/env bash
19
+ echo "$*" >> "$TD/calls"
20
+ case "$*" in
21
+ "api --paginate "*) if [ -f "$TD/comments.json" ]; then cat "$TD/comments.json"; else echo '[]'; fi ;;
22
+ "api -X PATCH"*) b="${@: -1}"; printf '%s' "${b#body=}" > "$TD/patched" ;;
23
+ "api -X POST"*) b="${@: -1}"; printf '%s' "${b#body=}" > "$TD/posted" ;;
24
+ esac
25
+ STUB
26
+ cat > "$TD/gh-fail" <<'STUB'
27
+ #!/usr/bin/env bash
28
+ exit 1
29
+ STUB
30
+ chmod +x "$TD/gh" "$TD/gh-fail"
31
+ export FORGE_TELEMETRY_GH="$TD/gh"
32
+ set_comments() { jq -n --arg b "$1" --arg t "${2:-Bot}" '[{id:77,body:$b,user:{type:$t},author_association:"NONE"}]' > "$TD/comments.json"; }
33
+ nrec() { printf '%s\n' "$1" | grep -c '^{'; }
34
+ reset() { rm -rf "$TD/calls" "$TD/posted" "$TD/patched" "$TD/comments.json" "$TD/buf"; }
35
+
36
+ . "$LIB"
37
+
38
+ # 1. One fixture per event shape: record carries the shared keys plus the payload, typed.
39
+ reset
40
+ forge_emit diff_size lines=10 excluded=2 threshold=0 result=OK
41
+ REC=$(grep '^{' "$TD/posted")
42
+ check "post has marker" "$(head -n 1 "$TD/posted")" "<!-- FORGE:TELEMETRY v1 -->"
43
+ check "shared keys" "$(printf '%s' "$REC" | jq -c 'keys_unsorted[:9]')" '["v","ts","forge_version","repo","issue","pr","run","phase","event"]'
44
+ check "issue number" "$(printf '%s' "$REC" | jq -r '.issue')" "5"
45
+ check "pr null" "$(printf '%s' "$REC" | jq -r '.pr')" "null"
46
+ check "numeric payload" "$(printf '%s' "$REC" | jq -r '.lines + .excluded')" "12"
47
+ check "string payload" "$(printf '%s' "$REC" | jq -r '.result')" "OK"
48
+ for fx in "acceptance_gate phase=review verified=3 failed=0 malformed=1 deferred=0:phase" \
49
+ "finding_disposition severity=HIGH confidence=CONFIRMED agent=auth disposition=ISSUE reason=severity-HIGH:disposition" \
50
+ "review_panel agents_selected=4 agents_completed=4 mode=FULL:mode" \
51
+ "ci_gate result=PASS rc=0 checks=7:checks" \
52
+ "liveness decision=DEFER why=lease:decision"; do
53
+ reset; FORGE_PR=9 forge_emit ${fx%%:*}
54
+ key=${fx##*:}; ev=${fx%% *}
55
+ check "fixture $ev event" "$(grep '^{' "$TD/posted" | jq -r '.event')" "$ev"
56
+ check "fixture $ev key $key" "$(grep '^{' "$TD/posted" | jq -r --arg k "$key" 'has($k)')" "true"
57
+ done
58
+ # review-side events go to the PR, others to the issue
59
+ reset; FORGE_PR=9 forge_emit review_panel mode=FULL; grep -q 'issues/9/comments' "$TD/calls" && ok || bad "review_panel targets PR"
60
+ reset; FORGE_PR=9 forge_emit diff_size lines=1; grep -q 'issues/5/comments' "$TD/calls" && ok || bad "diff_size targets issue"
61
+
62
+ # 2. Sanitising: reserved keys, bad keys, backticks and newlines never reach the record.
63
+ reset
64
+ forge_emit x repo=evil 'a b=1' ok='a`b
65
+ c'
66
+ REC=$(grep '^{' "$TD/posted")
67
+ check "reserved key ignored" "$(printf '%s' "$REC" | jq -r '.repo')" "o/r"
68
+ check "value sanitised" "$(printf '%s' "$REC" | jq -r '.ok')" "a_b c"
69
+ check "bad key dropped" "$(printf '%s' "$REC" | jq 'has("a b")')" "false"
70
+
71
+ # 3. Upsert edits the single trusted comment in place; an untrusted one is ignored.
72
+ reset; forge_emit ci_gate result=PASS; set_comments "$(cat "$TD/posted")"
73
+ forge_emit liveness decision=PROCEED
74
+ check "upsert patches" "$(nrec "$(cat "$TD/patched")")" "2"
75
+ check "no second post" "$(grep -c 'POST' "$TD/calls")" "1"
76
+ reset; forge_emit ci_gate result=PASS; set_comments "$(cat "$TD/posted")" User; rm -f "$TD/posted"
77
+ forge_emit liveness decision=PROCEED
78
+ [ -f "$TD/posted" ] && [ ! -f "$TD/patched" ] && ok || bad "untrusted comment must not be patched"
79
+
80
+ # 4. Rollover to part=N near the size limit.
81
+ reset; forge_emit ci_gate result=PASS; set_comments "$(cat "$TD/posted")"; rm -f "$TD/posted"
82
+ FORGE_TELEMETRY_MAX_CHARS=200 forge_emit liveness decision=PROCEED
83
+ check "rollover header" "$(head -n 1 "$TD/posted")" "<!-- FORGE:TELEMETRY v1 part=2 -->"
84
+
85
+ # 5. Buffer on failure, flush on next emit, nothing lost.
86
+ reset
87
+ FORGE_TELEMETRY_GH="$TD/gh-fail" forge_emit diff_size lines=1
88
+ check "buffered" "$(ls "$TD/buf")" "o_r__5.jsonl"
89
+ forge_emit diff_size lines=2
90
+ check "flushed both records" "$(nrec "$(cat "$TD/posted")")" "2"
91
+ [ ! -e "$TD/buf/o_r__5.jsonl" ] && ok || bad "buffer removed after flush"
92
+
93
+ # 6. Never fails the caller: unwritable buffer dir, no gh, kill switch, bad event name, caller options intact.
94
+ reset; : > "$TD/afile"
95
+ FORGE_TELEMETRY_GH=/nonexistent FORGE_TELEMETRY_BUFFER_DIR="$TD/afile/buf" forge_emit diff_size lines=1; check "unwritable dir rc" "$?" "0"
96
+ OUT=$(set -e; forge_emit ci_gate result=PASS; echo still-running); check "caller set -e survives" "$OUT" "still-running"
97
+ check "stdout silent" "$(forge_emit ci_gate result=PASS 2>&1)" ""
98
+ reset; FORGE_TELEMETRY=off forge_emit ci_gate result=PASS; [ ! -e "$TD/calls" ] && ok || bad "FORGE_TELEMETRY=off must not call gh"
99
+ reset; forge_emit 'bad event!' a=1; [ ! -e "$TD/calls" ] && ok || bad "bad event name dropped"
100
+ reset; (unset FORGE_ISSUE; forge_emit ci_gate result=PASS); [ ! -e "$TD/calls" ] && ok || bad "no target dropped"
101
+
102
+ # 7. Exec'd CLI form used by spec call sites.
103
+ reset; bash "$LIB" ci_gate result=PASS; check "cli rc" "$?" "0"; [ -f "$TD/posted" ] && ok || bad "cli form emits"
104
+
105
+ echo "telemetry.test.sh: $PASS passed, $FAIL failed"
106
+ [ "$FAIL" -eq 0 ]
@@ -30,6 +30,9 @@
30
30
  # "No checks" passes only when GitHub Actions also reports no workflow runs for the head commit;
31
31
  # if runs exist but their checks have not appeared yet, the gate keeps waiting.
32
32
  set -uo pipefail
33
+ # Telemetry (forge#3834): best-effort decision record; never changes stdout, stderr or the exit code.
34
+ _TEL="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/lib/telemetry.sh"; [ -f "$_TEL" ] && . "$_TEL" 2>/dev/null
35
+ type forge_emit >/dev/null 2>&1 || forge_emit() { :; }
33
36
 
34
37
  PR=""; REPO=""
35
38
  TIMEOUT="${FORGE_CI_TIMEOUT:-540}"; INTERVAL="${FORGE_CI_INTERVAL:-20}"; SETTLE="${FORGE_CI_SETTLE:-45}"
@@ -50,7 +53,11 @@ case "$REPO" in */*) ;; *) usage ;; esac
50
53
  for v in "$TIMEOUT" "$INTERVAL" "$GRACE" "$SETTLE"; do case "$v" in ''|*[!0-9]*) usage ;; esac; done
51
54
  [ "$INTERVAL" -ge 1 ] || INTERVAL=1
52
55
 
53
- emit() { echo "CI_GATE: $1"; echo "CI_GATE_HEAD: ${START_SHA:-unknown}"; }
56
+ emit() {
57
+ case "$1" in PASS) _rc=0 ;; FAIL) _rc=1 ;; TIMEOUT) _rc=3 ;; *) _rc=2 ;; esac
58
+ FORGE_REPO="${FORGE_REPO:-$REPO}" FORGE_PR="${FORGE_PR:-$PR}" forge_emit ci_gate result="$1" rc="$_rc" checks="${total:-0}"
59
+ echo "CI_GATE: $1"; echo "CI_GATE_HEAD: ${START_SHA:-unknown}"
60
+ }
54
61
  head_sha() { gh pr view "$PR" -R "$REPO" --json headRefOid --jq '.headRefOid' 2>/dev/null; }
55
62
  START_SHA="$(head_sha)"
56
63
  if [ -z "$START_SHA" ]; then START_SHA=""; emit ERROR; echo "cannot read PR #$PR head in $REPO"; exit 2; fi
@@ -42,3 +42,12 @@ branches:
42
42
  default: "main"
43
43
  staging: "staging"
44
44
  feature_pattern: "milestone/{slug}"
45
+
46
+ # =============================================================================
47
+ # DECOMPOSITION (OPTIONAL)
48
+ # Set close_parent: true to close a decomposed parent issue at decomposition
49
+ # (default false: it stays open until its last child closes).
50
+ # =============================================================================
51
+
52
+ # decomposition:
53
+ # close_parent: false