forked from Transia-RnD/XrplCSharp
-
Notifications
You must be signed in to change notification settings - Fork 1
205 lines (181 loc) · 10.3 KB
/
Copy pathprotocol-watch.yml
File metadata and controls
205 lines (181 loc) · 10.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
name: Protocol Watch
# Weekly diff of rippled develop's protocol definition files against the
# baseline recorded in the tracking issue. When a watched file changes, a
# comment lands on the issue with links to the diffs — the manual "did the
# protocol move?" ritual becomes a notification.
#
# Coverage is an explicit list of full repo-relative paths (WATCH), so files
# from any directory can be tracked: the five protocol .macro definition files
# plus three headers the SDK mirrors but which live outside the macros —
# TxFlags.h (transaction flags), TER.h (result codes) and LedgerFormats.h
# (lsf ledger-object flags, vendored as a test fixture and diffed by
# TestULedgerFlagsConformance, which compares against a pinned copy and so
# cannot notice upstream moving on its own).
#
# State lives in the tracking issue body (labeled protocol-watch), not in the
# repo and not in actions cache: no commits through branch protection, no
# cache eviction. The baseline JSON is keyed by full path.
on:
schedule:
- cron: '0 6 * * 1' # Mondays 06:00 UTC
workflow_dispatch:
permissions:
issues: write
# Only one run may read/update the tracking-issue state at a time
concurrency:
group: protocol-watch
cancel-in-progress: false
env:
UPSTREAM: XRPLF/rippled
UPSTREAM_REF: develop
# Explicit watch list — full repo-relative paths, one per line
WATCH: |
include/xrpl/protocol/detail/features.macro
include/xrpl/protocol/detail/ledger_entries.macro
include/xrpl/protocol/detail/permissions.macro
include/xrpl/protocol/detail/sfields.macro
include/xrpl/protocol/detail/transactions.macro
include/xrpl/protocol/TxFlags.h
include/xrpl/protocol/TER.h
include/xrpl/protocol/LedgerFormats.h
ISSUE_TITLE: 'Protocol watch: rippled develop'
ISSUE_LABEL: protocol-watch
jobs:
watch:
runs-on: ubuntu-latest
steps:
- name: Compare protocol files against tracked baseline
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
REPO: ${{ github.repository }}
run: |
set -euo pipefail
# Resolve the upstream head first, then pin the contents lookups to it
# so the recorded shas and the diff links refer to the same commit
head_sha=$(gh api "repos/$UPSTREAM/commits/$UPSTREAM_REF" --jq .sha)
# Build the current path->blob-sha map from the explicit watch list.
# Fail closed: only a genuine HTTP 404 means "removed upstream" (omit it
# so the union diff flags it as removed); any other failure (401/403/429/
# 5xx, network) aborts the run so a transient error can never drop an
# entry and overwrite the baseline.
current='{}'
while IFS= read -r path; do
[ -z "$path" ] && continue
if resp=$(gh api "repos/$UPSTREAM/contents/$path?ref=$head_sha" 2>/tmp/gh_err); then
# A file returns an object with a string sha; a directory returns an array
sha=$(printf '%s' "$resp" | jq -r 'select(type == "object" and .type == "file") | .sha // empty')
if [ -z "$sha" ]; then
echo "::error::watched path is not a file with a sha: $path"
exit 1
fi
current=$(jq -n --argjson cur "$current" --arg p "$path" --arg s "$sha" '$cur + {($p): $s}')
elif grep -q 'HTTP 404' /tmp/gh_err; then
echo "::warning::watched path not found upstream (404): $path"
else
echo "::error::lookup failed for $path:"
cat /tmp/gh_err
exit 1
fi
done <<< "$WATCH"
echo "current: $current"
# Find the tracking issue
issue=$(gh issue list --repo "$REPO" --label "$ISSUE_LABEL" --state open \
--json number,body --jq '.[0] // empty')
if [ -z "$issue" ]; then
gh label create "$ISSUE_LABEL" --repo "$REPO" \
--description "rippled develop protocol tracking" --color 5319e7 2>/dev/null || true
body=$(printf 'Automated baseline for the weekly protocol watch (.github/workflows/protocol-watch.yml). Do not edit the JSON block by hand.\n\nLast checked upstream commit: %s\n\n```json\n%s\n```\n' "$head_sha" "$current")
gh issue create --repo "$REPO" --title "$ISSUE_TITLE" --label "$ISSUE_LABEL" --body "$body"
echo "Baseline issue created."
exit 0
fi
number=$(echo "$issue" | jq -r .number)
# Fail closed on a corrupted baseline: an empty-object fallback would
# report every file as changed and overwrite the evidence
# Strip a trailing CR first: a CRLF-encoded issue body (e.g. from a
# hand-edit on Windows) would leave `\r` before the `$` anchor and the
# fence never matches, tripping the fail-closed guard below
block=$(echo "$issue" | jq -r .body | awk '{sub(/\r$/,"")} /^```json$/{f=1;next} /^```$/{f=0} f')
if [ -z "$block" ]; then
echo "::error::Tracking issue #$number has no JSON baseline block. Fix the issue body or close the issue to bootstrap a fresh baseline."
exit 1
fi
if ! stored=$(echo "$block" | jq -ce 'select(type == "object" and (to_entries | all(.value | type == "string")))'); then
echo "::error::Tracking issue #$number JSON baseline is malformed. Fix the issue body or close the issue to bootstrap a fresh baseline."
exit 1
fi
echo "stored: $stored"
# Union of keys so upstream file removals/renames are detected too
changed=$(jq -n --argjson cur "$current" --argjson old "$stored" \
'[(($cur | keys) + ($old | keys) | unique)[] | select($cur[.] != $old[.])]')
echo "changed: $changed"
if [ "$(echo "$changed" | jq length)" -eq 0 ]; then
echo "No protocol changes."
exit 0
fi
# Build a comment with per-file history links and the actual diff; a path
# missing from the current snapshot was removed/renamed upstream - do not
# link a 404 blob.
#
# The diff is the point of this comment. Naming the files that moved still
# leaves the operator to fetch two blobs and compare them by hand, which is
# the work the watch exists to save. Both blob shas are already known here -
# the baseline holds the old one, the snapshot the new - so the content can
# be fetched by sha and diffed directly.
#
# Rendering a diff is best-effort while the notification is not: a blob
# fetch that fails leaves the file listed with its links and a note, rather
# than aborting a run whose job is to tell somebody that something changed.
DIFF_LINES_PER_FILE=80
fetch_blob() {
gh api "repos/$UPSTREAM/git/blobs/$1" --jq '.content' 2>/dev/null | base64 -d 2>/dev/null
}
files=$(echo "$changed" | jq -r '.[]')
lines=""
for f in $files; do
if [ "$(echo "$current" | jq --arg f "$f" 'has($f)')" != "true" ]; then
lines="$lines- \`$f\` — **removed or renamed upstream** · [history](https://github.com/$UPSTREAM/commits/$UPSTREAM_REF/$f)"$'\n'
continue
fi
lines="$lines- \`$f\` — [history](https://github.com/$UPSTREAM/commits/$UPSTREAM_REF/$f) · [current](https://github.com/$UPSTREAM/blob/$head_sha/$f)"$'\n'
new_sha=$(echo "$current" | jq -r --arg f "$f" '.[$f]')
old_sha=$(echo "$stored" | jq -r --arg f "$f" '.[$f] // empty')
if [ -z "$old_sha" ]; then
lines="$lines"' <br>**new file upstream** — nothing to diff against.'$'\n'
continue
fi
if ! fetch_blob "$old_sha" > /tmp/pw_old 2>/dev/null || ! fetch_blob "$new_sha" > /tmp/pw_new 2>/dev/null; then
lines="$lines"' <br>*(could not fetch both blobs to render a diff — follow the links above)*'$'\n'
continue
fi
body=$(diff -U2 /tmp/pw_old /tmp/pw_new || true)
# Drop diff's own ---/+++ header: the file is already named above.
body=$(printf '%s\n' "$body" | tail -n +3)
total=$(printf '%s\n' "$body" | wc -l)
if [ "$total" -gt "$DIFF_LINES_PER_FILE" ]; then
body=$(printf '%s\n' "$body" | head -n "$DIFF_LINES_PER_FILE")
body="$body"$'\n'"... truncated at $DIFF_LINES_PER_FILE of $total lines — see the links above"
fi
lines="$lines"$'\n'" <details><summary>diff</summary>"$'\n\n'" \`\`\`diff"$'\n'
while IFS= read -r dl; do
lines="$lines $dl"$'\n'
done <<< "$body"
lines="$lines"' ```'$'\n\n'" </details>"$'\n\n'
done
# Idempotency: if the notification for this head_sha is already posted
# (a previous run died between comment and baseline update), skip the
# duplicate comment but still update the baseline below
marker="<!-- protocol-watch:$head_sha -->"
# Capture first: an API failure must abort the run (set -e), and a
# grep -q early-exit must not SIGPIPE the gh call under pipefail
existing_comments=$(gh api "repos/$REPO/issues/$number/comments" --paginate --jq '.[].body')
if grep -qF "$marker" <<< "$existing_comments"; then
echo "Notification for $head_sha already posted; updating the baseline only."
else
comment=$(printf '%s\nProtocol definitions changed on `%s` `%s` (checked at upstream commit %s):\n\n%s\nFollow-ups to consider: sync `definitions.json` (`server_definitions` diff), new amendments for the nightly stand config, new transaction/ledger-object fields, flags or result codes for the models.\n' "$marker" "$UPSTREAM" "$UPSTREAM_REF" "$head_sha" "$lines")
gh issue comment "$number" --repo "$REPO" --body "$comment"
fi
# Update the baseline in the issue body
body=$(printf 'Automated baseline for the weekly protocol watch (.github/workflows/protocol-watch.yml). Do not edit the JSON block by hand.\n\nLast checked upstream commit: %s\n\n```json\n%s\n```\n' "$head_sha" "$current")
gh issue edit "$number" --repo "$REPO" --body "$body"
echo "Comment posted and baseline updated."