Skip to content

Commit d6ebd37

Browse files
hyperpolymathmistral-vibecoderabbitai[bot]
authored
docs(fleet): review-thread stewardship — reply and resolve as fleet actions (#611)
## What this changes Makes the review-thread loop a fleet capability instead of a by-hand chore. Today's CodeRabbit estate sweep (2026-10-15) closed seven threads manually: rsr-template-repo #240 (2), choreographic-types #33, occupancy-types #13 (2), CompositionalDA.jl #11, odraz #19 — each needed a fix, a reply on the thread, and a resolve. - **TOKEN-ARCHITECTURE.adoc** — echidnabot's action table gains the two stewardship endpoints: reply-to-review-thread (`POST /repos/{owner}/{repo}/pulls/{pr}/comments/{id}/replies`) and resolve-thread (GraphQL `resolveReviewThread`). Both are covered by its existing `repo` scope; no new scopes requested. - **BOT-OPERATIONS.adoc** — the metarule: match an unresolved thread to a Hypatia finding or fleet fix, land the fix on the PR branch (robot-repo-automaton), reply with the fix commit and reasoning, then resolve. Threads whose findings are verified no-longer-valid close with a skip-reason reply instead of silence. - **Safety carve-out**: identity-class findings (HYP-S010, clade identity coherence) are `auto_fixable: false` — the bot proposes and replies; the owner rules on direction. The odraz #19 corollary is recorded: a reviewer bot confidently aligned a uuid to template residue; check which side is the residue before automating. Pairs with hyperpolymath/standards#1270 (the rule declarations) and hyperpolymath/hypatia#924 (the live bash detection). Note: the commit is unsigned (the signing key's passphrase is unavailable to the session); amend to sign if required. Generated by Mistral Vibe. Co-Authored-By: Mistral Vibe <vibe@mistral.ai> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Documentation** * Clarified how review findings are matched to threads and how addressed findings receive fix details before their threads are resolved. * Documented that ambiguous matches leave threads unchanged, while invalid findings receive an explanation and remain unresolved. * Added guidance on owner approval for identity-related fixes, checking alignment findings before automating fixes, and the permissions needed for review-thread actions. * Updated runtime guidance to list Deno as a secondary JavaScript runtime and package manager. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Mistral Vibe <vibe@mistral.ai> Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
1 parent 1894258 commit d6ebd37

7 files changed

Lines changed: 211 additions & 3 deletions

File tree

‎bots/gsbot/Cargo.lock‎

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎docs/BOT-OPERATIONS.adoc‎

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,45 @@ Findings → observed-patterns.jsonl → threshold (5+) → propose_new_rule()
8888
→ Human reviews PR → Approved rule deployed to hypatia
8989
....
9090

91+
==== Review-Thread Stewardship (metarule)
92+
93+
Reviewer bots (CodeRabbit, Codeac, and friends) post review threads on
94+
PRs; a finding addressed by a fix commit is not *closed* until the thread
95+
carries the fix and is resolved. 2026-10-15's estate sweep addressed
96+
seven such threads by hand — reply + resolve are fleet actions, and the
97+
permissions for them live in link:TOKEN-ARCHITECTURE.adoc[`echidnabot`].
98+
99+
The metarule:
100+
101+
....
102+
1. Use the same PR and file/line class only to filter candidate unresolved
103+
review threads for a Hypatia finding (or a fleet fix). Verify the
104+
finding-to-thread match and require exactly one match before replying
105+
or resolving. Leave all threads unchanged if zero or multiple matches
106+
remain.
107+
2. The fix lands on the PR branch (robot-repo-automaton; identity-class
108+
findings like HYP-S010 are auto_fixable=false — the bot proposes and
109+
replies, the owner rules on direction).
110+
3. echidnabot replies to the thread with the fix commit and reasoning:
111+
POST /repos/{owner}/{repo}/pulls/{pr}/comments/{id}/replies
112+
where {id} is the top-level review-comment ID.
113+
4. echidnabot resolves an addressed finding's thread only after the
114+
required fix lands and Step 3's reply is posted:
115+
GraphQL mutation resolveReviewThread (thread id from the PR's
116+
reviewThreads).
117+
Identity-class findings such as HYP-S010 stay open until the owner
118+
directs and approves a fix and that fix lands. For verified-invalid
119+
findings, follow Step 5 instead.
120+
5. Never resolve a thread whose finding is not addressed. For findings
121+
verified as no-longer-valid, post a "skipped with a brief reason"
122+
reply explaining why and leave the thread unresolved.
123+
....
124+
125+
Corollary learned the hard way (odraz #19): when a reviewer bot's finding
126+
says "align X to the declared Y", check which side is the residue before
127+
automating — a reviewer bot can confidently align to template residue.
128+
Auto-remediation of identity-class findings stays owner-ruled.
129+
91130
==== Maintenance Release Gate Routing
92131

93132
Use this routing model for repository maintenance and release

‎docs/TOKEN-ARCHITECTURE.adoc‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,8 @@ This bot triggers Echidna workflows for proof execution, which requires the
165165
| Trigger Echidna workflow | `POST /repos/{owner}/{repo}/actions/workflows/echidna-scan.yml/dispatches`
166166
| Read ProofTrace data | `GET /repos/{owner}/{repo}/contents/.prooftrace/`
167167
| Write ProofTrace data | `PUT /repos/{owner}/{repo}/contents/.prooftrace/`
168+
| Reply to a review thread | `POST /repos/{owner}/{repo}/pulls/{pr}/comments/{comment_id}/replies` — `{comment_id}` is the top-level review-comment ID, distinct from the GraphQL review-thread ID used by `resolveReviewThread`; review-thread stewardship (BOT-OPERATIONS.adoc); no new scope, `repo` covers it
169+
| Resolve a review thread | GraphQL `mutation resolveReviewThread(input: {threadId})` — same stewardship; resolves only threads whose finding the fleet addressed, with the fix-commit reply posted first
168170
|===
169171

170172
=== 4. sustainabot

‎robot-repo-automaton/.claude/CLAUDE.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
|---------------|----------|-------|
99
| **AffineScript** | Primary application code | Compiles to JS/WASM, type-safe with affine types |
1010
| **Bun** | JS runtime & package management (tier 1) | Default for all new work. Runs compiled ESM/JS directly — no bundler step. Uses an npm-compatible `package.json` plus `bun.lock` — both are expected, not anti-patterns. |
11+
| **Deno** | JS runtime only (tier 2) | Secondary to Bun; limited to running compiled ESM/JS. Use Bun for all JavaScript dependency installation and management. |
1112
| **Rust** | Performance-critical, systems, WASM | Preferred for CLI tools |
1213
| **Tauri 2.0+** | Mobile apps (iOS/Android) | Rust backend + web UI |
1314
| **Dioxus** | Mobile apps (native UI) | Pure Rust, React-like |
@@ -26,7 +27,6 @@
2627
|--------|-------------|
2728
| TypeScript | AffineScript |
2829
| ReScript | AffineScript |
29-
| Deno | Bun |
3030
| Node.js | Bun |
3131
| npm | Bun |
3232
| pnpm/yarn | Bun |

‎tests/e2e.sh‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,20 @@ done
211211

212212
echo ""
213213

214+
# ─── Section 7: Documentation policy contracts ──────────────────────
215+
bold "Section 7: Documentation policy contracts"
216+
217+
for policy_test in review-thread-stewardship-test.sh runtime-policy-test.sh; do
218+
if bash "$SCRIPT_DIR/$policy_test"; then
219+
PASS=$((PASS + 1))
220+
else
221+
red " FAIL: $policy_test"
222+
FAIL=$((FAIL + 1))
223+
fi
224+
done
225+
226+
echo ""
227+
214228
# ═══════════════════════════════════════════════════════════════════════
215229
# Summary
216230
# ═══════════════════════════════════════════════════════════════════════
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
#!/usr/bin/env bash
2+
# SPDX-License-Identifier: MPL-2.0
3+
# Acceptance checks for the documented contract, not runtime enforcement.
4+
# Offline: reads documentation only; never calls GitHub or enables the fleet.
5+
# Run with: bash tests/review-thread-stewardship-test.sh
6+
set -euo pipefail
7+
root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
8+
9+
# Limit assertions to the relevant section so another bot or metarule cannot
10+
# accidentally satisfy them. Collapse wrapping without snapshotting whole docs.
11+
section() {
12+
local file="$1" heading="$2" level="$3"
13+
awk -v heading="$heading" -v level="$level" '
14+
$0 == heading { found = 1; next }
15+
found && $0 ~ "^" level " " { exit }
16+
found { print }
17+
' "$file"
18+
}
19+
normalize() { awk '{$1=$1; printf "%s ", $0}'; }
20+
require() {
21+
local label="$1" pattern="$2" content="$3"
22+
if ! grep -Eq -- "$pattern" <<< "$content"; then
23+
printf 'FAIL: %s\n' "$label" >&2
24+
exit 1
25+
fi
26+
printf 'PASS: %s\n' "$label"
27+
}
28+
29+
operations="$(section "$root/docs/BOT-OPERATIONS.adoc" \
30+
'==== Review-Thread Stewardship (metarule)' '====')"
31+
tokens="$(section "$root/docs/TOKEN-ARCHITECTURE.adoc" \
32+
'=== 3. echidnabot' '===')"
33+
34+
# Keep each guard in its own numbered step; a disclaimer elsewhere must not
35+
# conceal a weakened matching, resolution, or invalid-finding instruction.
36+
step() {
37+
local number="$1"
38+
awk -v number="$number" '
39+
/^[0-9]+\. / {
40+
if (found) exit
41+
if ($1 == number ".") found = 1
42+
}
43+
found && /^\.\.\.\.$/ { exit }
44+
found { print }
45+
' <<< "$operations" | normalize
46+
}
47+
match_step="$(step 1)"
48+
fix_step="$(step 2)"
49+
reply_step="$(step 3)"
50+
resolve_step="$(step 4)"
51+
invalid_step="$(step 5)"
52+
53+
require 'candidate filtering uses the same PR, file/line class and unresolved threads' \
54+
'same PR and file/line class only to filter candidate unresolved' "$match_step"
55+
require 'candidate filtering requires a verified finding-to-thread match' \
56+
'Verify the finding-to-thread match' "$match_step"
57+
require 'reply and resolution require exactly one match' \
58+
'exactly one match before replying or resolving' "$match_step"
59+
require 'zero or ambiguous matches leave every thread unchanged' \
60+
'Leave all threads unchanged if zero or multiple matches' "$match_step"
61+
require 'the executor lands the fix on the PR branch' \
62+
'fix lands on the PR branch \(robot-repo-automaton' "$fix_step"
63+
require 'identity findings cannot be automatically fixed' \
64+
'HYP-S010 are auto_fixable=false' "$fix_step"
65+
require 'identity direction belongs to the owner' \
66+
'bot proposes and replies, the owner rules on direction' "$fix_step"
67+
require 'echidnabot replies with both the fix commit and reasoning' \
68+
'echidnabot replies to the thread with the fix commit and reasoning' "$reply_step"
69+
70+
# REST takes a top-level comment ID; GraphQL takes a review-thread node ID.
71+
reply_endpoint='POST /repos/{owner}/{repo}/pulls/{pr}/comments/{id}/replies'
72+
require 'operations document the REST reply endpoint' \
73+
"$(sed 's/[][\\.^$*+?(){}|]/\\&/g' <<< "$reply_endpoint")" "$reply_step"
74+
require 'REST replies use the top-level review-comment ID' \
75+
'where \{id\} is the top-level review-comment ID' "$reply_step"
76+
require 'resolution follows both a landed fix and a posted reply' \
77+
"only after the required fix lands and Step 3's reply is posted" "$resolve_step"
78+
require 'resolution uses a GraphQL thread ID from the PR' \
79+
"GraphQL mutation resolveReviewThread \(thread id from the PR's reviewThreads\)" "$resolve_step"
80+
require 'identity threads wait for owner direction, approval and a landed fix' \
81+
'HYP-S010 stay open until the owner directs and approves a fix and that fix lands' "$resolve_step"
82+
require 'invalid findings take the skip path instead of normal resolution' \
83+
'For verified-invalid findings, follow Step 5 instead' "$resolve_step"
84+
require 'unaddressed findings must never be resolved' \
85+
'Never resolve a thread whose finding is not addressed' "$invalid_step"
86+
require 'invalid findings receive a skip explanation and remain unresolved' \
87+
'verified as no-longer-valid, post a "skipped with a brief reason" reply explaining why and leave the thread unresolved' "$invalid_step"
88+
require 'alignment findings require checking for template residue first' \
89+
'check which side is the residue before automating' "$(normalize <<< "$operations")"
90+
91+
reply_row="$(awk '/^\| Reply to a review thread \|/ {print}' <<< "$tokens")"
92+
resolve_row="$(awk '/^\| Resolve a review thread \|/ {print}' <<< "$tokens")"
93+
require 'echidnabot owns exactly one reply action and one resolve action' \
94+
'^1 1$' "$(grep -c '^| Reply to a review thread |' <<< "$tokens") $(grep -c '^| Resolve a review thread |' <<< "$tokens")"
95+
require 'token docs use the same REST endpoint as operations' \
96+
"$(sed 's/[][\\.^$*+?(){}|]/\\&/g' <<< "${reply_endpoint//\{id\}/\{comment_id\}}")" "$reply_row"
97+
require 'token docs distinguish REST comment IDs from GraphQL thread IDs' \
98+
'top-level review-comment ID, distinct from the GraphQL review-thread ID' "$reply_row"
99+
require 'review replies require no additional token scope' \
100+
'no new scope, `repo` covers it' "$reply_row"
101+
require 'token docs name the GraphQL mutation and threadId argument' \
102+
'mutation resolveReviewThread\(input: \{threadId\}\)' "$resolve_row"
103+
require 'token docs restrict resolution to addressed findings after the reply' \
104+
'resolves only threads whose finding the fleet addressed, with the fix-commit reply posted first' "$resolve_row"
105+
require 'operations link to the token permission document' \
106+
'link:TOKEN-ARCHITECTURE\.adoc\[' "$(normalize <<< "$operations")"
107+
require 'token reply action refers back to the operations contract' \
108+
'BOT-OPERATIONS\.adoc' "$reply_row"

‎tests/runtime-policy-test.sh‎

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
#!/usr/bin/env bash
2+
# SPDX-License-Identifier: MPL-2.0
3+
# Acceptance check for RRA's documented runtime policy. No runtime is started.
4+
# Run with: bash tests/runtime-policy-test.sh
5+
set -euo pipefail
6+
root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
7+
policy="$root/robot-repo-automaton/.claude/CLAUDE.md"
8+
9+
section() {
10+
local heading="$1"
11+
awk -v heading="$heading" '
12+
$0 == heading { found = 1; next }
13+
found && /^### / { exit }
14+
found { print }
15+
' "$policy"
16+
}
17+
allowed="$(section '### ALLOWED Languages & Tools')"
18+
banned="$(section '### BANNED - Do Not Use')"
19+
20+
if [[ -z "$allowed" || -z "$banned" ]]; then
21+
echo 'FAIL: runtime policy must retain its allowed and banned sections' >&2
22+
exit 1
23+
fi
24+
if [[ "$(grep -cE '^\| \*\*Deno\*\* \|' <<< "$allowed")" != 1 ]] ||
25+
! grep -Eq '^\| \*\*Deno\*\* \| JS runtime only \(tier 2\) \| Secondary to Bun; limited to running compiled ESM/JS\.' <<< "$allowed"; then
26+
echo 'FAIL: Deno must be allowed once as the secondary tier 2 runtime' >&2
27+
exit 1
28+
fi
29+
echo 'PASS: Deno is allowed as the secondary tier 2 runtime'
30+
if ! grep -Eq '^\| \*\*Deno\*\* \|[^|]*\|[^|]*Use Bun for all JavaScript dependency installation and management\.' <<< "$allowed"; then
31+
echo 'FAIL: Deno must delegate JavaScript dependency installation and management to Bun' >&2
32+
exit 1
33+
fi
34+
echo 'PASS: JavaScript dependency installation and management remain with Bun'
35+
if ! grep -Eq '^\| \*\*Bun\*\* \| JS runtime & package management \(tier 1\) \|' <<< "$allowed"; then
36+
echo 'FAIL: Bun must remain the primary tier 1 runtime' >&2
37+
exit 1
38+
fi
39+
echo 'PASS: Bun remains the primary tier 1 runtime'
40+
# Regression: adding Deno to ALLOWED alone leaves contradictory instructions.
41+
if grep -Eiq '^\|[[:space:]*`]*Deno[[:space:]*`]*\|' <<< "$banned"; then
42+
echo 'FAIL: Deno must not also appear in the banned runtime table' >&2
43+
exit 1
44+
fi
45+
echo 'PASS: the banned table does not contradict the Deno allowance'

0 commit comments

Comments
 (0)