diff --git a/.github/workflows/review-swarm.yml b/.github/workflows/review-swarm.yml new file mode 100644 index 000000000..d2e0eaece --- /dev/null +++ b/.github/workflows/review-swarm.yml @@ -0,0 +1,96 @@ +name: Review swarm + +on: + pull_request: + types: [opened, synchronize, reopened, ready_for_review] + +permissions: + contents: read + pull-requests: write + +concurrency: + group: review-swarm-${{ github.event.pull_request.number }} + cancel-in-progress: true + +jobs: + review: + if: github.event.pull_request.draft == false + runs-on: ubuntu-latest + # Ordering invariant: job 75m > poll 3900s (65m) > swarm 3600000ms (60m). + timeout-minutes: 75 + steps: + - name: Check out immutable gate from main + uses: actions/checkout@v4 + with: + ref: main + path: gate + + - name: Check out PR head + uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.sha }} + path: target + + - name: Validate workspace secret + env: + RELAY_WORKSPACE_KEY: ${{ secrets.RELAY_WORKSPACE_KEY }} + run: | + if [ -z "$RELAY_WORKSPACE_KEY" ]; then + echo "RELAY_WORKSPACE_KEY secret not configured; see README ยง Review-swarm secret" >&2 + exit 1 + fi + + - name: Install Agent Relay CLI + run: npm install --global agent-relay + + - name: Fetch PR evidence on runner + env: + GH_TOKEN: ${{ github.token }} + working-directory: target + run: ../gate/.github/workflows/scripts/swarm-prepare.sh '${{ github.event.pull_request.number }}' ../gate + + - name: Launch cloud swarm + id: launch + env: + RELAY_API_KEY: ${{ secrets.RELAY_WORKSPACE_KEY }} + working-directory: target + run: | + result=$(agent-relay cloud run --json ../gate/workflows/review-swarm.yaml) + run_id=$(printf '%s' "$result" | jq -er '.runId // .id') + echo "run_id=$run_id" >> "$GITHUB_OUTPUT" + + - name: Wait for cloud swarm + id: wait + if: steps.launch.outputs.run_id != '' + env: + RELAY_API_KEY: ${{ secrets.RELAY_WORKSPACE_KEY }} + run: | + deadline=$((SECONDS + 3900)) + swarm_status=timed_out + while [ "$SECONDS" -lt "$deadline" ]; do + status_json=$(agent-relay cloud status --json '${{ steps.launch.outputs.run_id }}') || { + swarm_status=status_error + break + } + swarm_status=$(printf '%s' "$status_json" | jq -r '.status // "unknown"') + case "$swarm_status" in + completed|failed|cancelled) break ;; + esac + sleep 15 + done + echo "swarm_status=$swarm_status" >> "$GITHUB_OUTPUT" + # Deliberately exit zero: post must publish evidence for rejected runs. + exit 0 + + - name: Sync and post review + if: always() && steps.launch.outputs.run_id != '' + env: + GH_TOKEN: ${{ github.token }} + RELAY_API_KEY: ${{ secrets.RELAY_WORKSPACE_KEY }} + run: gate/.github/workflows/scripts/swarm-post.sh '${{ steps.launch.outputs.run_id }}' '${{ github.event.pull_request.number }}' '${{ github.repository }}' "$GITHUB_WORKSPACE/target" + + - name: Enforce terminal success + if: steps.wait.outputs.swarm_status != 'completed' + run: | + echo "Review swarm did not complete: ${{ steps.wait.outputs.swarm_status }}" >&2 + exit 1 diff --git a/.github/workflows/scripts/swarm-post.sh b/.github/workflows/scripts/swarm-post.sh new file mode 100644 index 000000000..4560604b1 --- /dev/null +++ b/.github/workflows/scripts/swarm-post.sh @@ -0,0 +1,51 @@ +#!/bin/sh +set -eu + +run_id=$1 +pr=$2 +repo=$3 +worktree=$4 +sync_started=$(date +%s) + +agent-relay cloud sync "$run_id" --dir "$worktree" +cd "$worktree" +. .review-target/swarm-verdict.sh + +post_sticky() { + anchor=$1 + body_file=$2 + comment_id=$(gh api --paginate "repos/$repo/issues/$pr/comments" \ + --jq ".[] | select(.body | contains(\"$anchor\")) | .id" | head -n 1) + if [ -n "$comment_id" ]; then + gh api --method PATCH "repos/$repo/issues/comments/$comment_id" \ + --raw-field "body=$(cat "$body_file")" >/dev/null + else + gh pr comment "$pr" --repo "$repo" --body-file "$body_file" >/dev/null + fi +} + +failed=0 +for lens in $SWARM_LENSES; do + transcript=$(swarm_latest_transcript "$pr" "$lens" ops/reviews) + if [ -z "$transcript" ] || [ "$(stat -c %Y "$transcript")" -lt "$sync_started" ]; then + echo "POST_FAILED: $lens transcript is missing or stale" >&2 + failed=1 + continue + fi + body=$(mktemp) + printf '\n' "$lens" > "$body" + cat "$transcript" >> "$body" + post_sticky "" "$body" + rm -f "$body" +done + +marker=$(mktemp) +if [ "$failed" -eq 0 ] && swarm_evaluate "$pr" ops/reviews; then + printf '%s\n%s\n' '' '๐ŸŽฏ review-swarm: PASSED' > "$marker" +else + failed=1 + printf '%s\n%s\n' '' '๐Ÿ›‘ review-swarm: FAILED' > "$marker" +fi +post_sticky '' "$marker" +rm -f "$marker" +[ "$failed" -eq 0 ] diff --git a/.github/workflows/scripts/swarm-prepare.sh b/.github/workflows/scripts/swarm-prepare.sh new file mode 100644 index 000000000..20a770945 --- /dev/null +++ b/.github/workflows/scripts/swarm-prepare.sh @@ -0,0 +1,25 @@ +#!/bin/sh +set -eu + +pr=$1 +gate_root=$2 +target_dir=.review-target + +case "$pr" in *[!0-9]*|'') echo "PREPARE_FAILED: invalid PR number" >&2; exit 64;; esac +[ -f "$gate_root/.github/workflows/scripts/swarm-verdict.sh" ] || { + echo "PREPARE_FAILED: trusted verdict helper missing" >&2; exit 66; +} + +mkdir -p "$target_dir" +printf '%s\n' "$pr" > "$target_dir/pr-number" +gh pr diff "$pr" > "$target_dir/pr.diff" +gh pr view "$pr" --json headRefName,headRefOid,title,url > "$target_dir/pr.json" +cp "$gate_root/.github/workflows/scripts/swarm-verdict.sh" \ + "$target_dir/swarm-verdict.sh" + +for file in pr-number pr.diff pr.json swarm-verdict.sh; do + [ -s "$target_dir/$file" ] || { + echo "PREPARE_FAILED: $target_dir/$file is empty" >&2; exit 65; + } +done +git add -f "$target_dir" diff --git a/.github/workflows/scripts/swarm-verdict.sh b/.github/workflows/scripts/swarm-verdict.sh new file mode 100644 index 000000000..a682128d4 --- /dev/null +++ b/.github/workflows/scripts/swarm-verdict.sh @@ -0,0 +1,47 @@ +#!/bin/sh + +SWARM_LENSES="maintainability history structure" + +swarm_latest_transcript() { + pr=$1 + lens=$2 + reviews_dir=$3 + find "$reviews_dir" -maxdepth 1 -type f -name "*-pr${pr}-${lens}.md" \ + -print 2>/dev/null | LC_ALL=C sort | tail -n 1 +} + +swarm_transcript_verdict() { + awk 'NF { line=$0 } END { + count=split(line, words, /[[:space:]]+/) + print count ? words[count] : "" + }' "$1" +} + +swarm_evaluate() { + pr=$1 + reviews_dir=$2 + failed=0 + + for lens in $SWARM_LENSES; do + transcript=$(swarm_latest_transcript "$pr" "$lens" "$reviews_dir") + if [ -z "$transcript" ]; then + echo "SWARM_FAILED: $lens transcript MISSING" + failed=1 + continue + fi + + verdict=$(swarm_transcript_verdict "$transcript") + if [ "$verdict" = REVIEW_PASSED ]; then + echo "SWARM_LENS: $lens PASSED $transcript" + elif [ "$verdict" = REVIEW_FAILED ]; then + echo "SWARM_FAILED: $lens FAILED $transcript" + failed=1 + else + echo "SWARM_FAILED: $lens UNCLEAR $transcript" + failed=1 + fi + done + + [ "$failed" -eq 0 ] && { echo SWARM_PASSED; return 0; } + return 1 +} diff --git a/.gitignore b/.gitignore index a4eca9004..554689b42 100644 --- a/.gitignore +++ b/.gitignore @@ -7,8 +7,6 @@ dist/ .env .agentworkforce/ .cargo-home/ -.review-target - # Toolchains materialize inside the workspace in a cloud sandbox and must never # be committed or delivered. Run f18ec684's patch carried .rustup-home/ files; # ops/deliver-run.sh scrubs them too, but ignoring them is the durable fix. diff --git a/README.md b/README.md index f88e21d30..0bec57fbf 100644 --- a/README.md +++ b/README.md @@ -31,3 +31,17 @@ Nine gates, in `docs/RFC-0001` ยง3. Gate 1 first: a relayflow can run โ€” the he ladder survives `kill -9` at every boundary. Private while we build. YC 2026-09-15 runs on this base. + +## Review-swarm secret + +The `Review swarm` GitHub Actions workflow requires a repository Actions secret +named `RELAY_WORKSPACE_KEY`. Obtain the unmasked key for the canonical workspace +with: + +```bash +agent-relay workspace key --reveal-secrets +``` + +Add that value under **Settings โ†’ Secrets and variables โ†’ Actions โ†’ New +repository secret**. The workflow fails during preflight when the secret is +missing; it never falls back to interactive login. diff --git a/ops/NEXT.md b/ops/NEXT.md index 86ababf65..e52b75365 100644 --- a/ops/NEXT.md +++ b/ops/NEXT.md @@ -1,82 +1,105 @@ -# NEXT โ€” work package for this tick +# NEXT โ€” Work package for this tick -**Scope:** Make CI run the suites it already has. CI task, `.github/` only. +**Gate:** 3 (Cloud review-swarm redesign) -This run is pinned to **the CI coverage gap** and must not work on any other -gate. It is a small change with an outsized effect, and it is the reason six of -eight independent signoffs on 2026-09-03 found P0s in PRs that were green. +**Scope (quoted from TARGET.md):** + +> Track D: Cloud review-swarm redesign โ€” build `.github/workflows/review-swarm.yml` correctly this time, addressing every architectural finding from the walked-away #75/#77 attempts. Parallel to Track A (hn-monitor); different territory (`.github/` + `workflows/` โ€” no overlap with `sdk/` work). ## Objective -`.github/workflows/cloud-runtime-artifact.yml` is the repository's ONLY -workflow. Verified on 2026-09-03: - -- The only cargo invocation is `cargo build --locked --release -p relayflowd`. - **`cargo test` appears nowhere.** The entire kernel suite โ€” 130 tests โ€” never - runs in CI. -- Vitest runs exactly **four** files: - `typed-output`, `validate`, `spec-parity`, `deterministic-llm`. The other ~22 - SDK test files never run. - -Every kernel-side defect found on 2026-09-03 was invisible to CI by -construction: an exactly-once double-fire where one effect fired twice; a -`$ref` cycle that aborted the daemon and re-ran the effect on every resume -(4 executions of one logical step); and two tests in the tree that encoded -**opposite** contracts and both passed, because neither ran. - -## What to do - -Add the missing coverage to `.github/workflows/cloud-runtime-artifact.yml`. -The job already installs a Rust toolchain and builds the kernel, so the -marginal cost of testing it is the test run itself. - -1. Run the kernel suite: `cargo test --workspace` from `kernel/`, using - `ops/cargo.sh` the way the repo does elsewhere. -2. Run the whole SDK suite rather than four named files. Note `npm test` does - `test:prep && typecheck && build` first โ€” a bare `vitest run` fails ~6 files - because `sdk/dist` does not exist. Use the repo's own script rather than - inventing an invocation. -3. Keep the existing artifact build, verify and smoke steps working. Do not - restructure the workflow; add coverage. - -## Constraints - -- **`.github/` only.** Do not fix any test this newly exposes. If enabling the - suites turns CI red, that is the correct and expected outcome โ€” report - exactly which tests fail and stop. A red CI that tells the truth is the - deliverable; a green CI that runs nothing is what we have. -- Do not touch `kernel/`, `sdk/`, or `testdata/`. -- Do not add a second workflow file. +Build the cloud-based GitHub Actions review-swarm workflow that enforces RFC-0001 ยง2 rule 7: every PR gets reviewed by our own swarm. The local `~/AgentWorkforce/review-swarm-loop.sh` exists but lives on the chief's laptop; the cloud version must exist for gate 3+ work to be trustworthy. + +This addresses all 9 architectural findings from rejected PRs #75 and #77. + +## Files in scope + +- `.github/workflows/review-swarm.yml` โ€” new GHA trigger file +- `.github/workflows/scripts/swarm-prepare.sh` โ€” new launcher-side PR fetcher +- `.github/workflows/scripts/swarm-post.sh` โ€” new sync + verdict + post script +- `.github/workflows/scripts/swarm-verdict.sh` โ€” new shared verdict logic (single source of truth) +- `workflows/review-swarm.yaml` โ€” refactor aggregate step to use shared verdict logic +- `.gitignore` โ€” drop `.review-target` mask (line 10) +- `README.md` โ€” document `RELAY_WORKSPACE_KEY` secret setup + +## Requirements (the 9 architectural findings) + +### 1. Immutable gate +The reviewed PR must NOT control its own judge. `.github/workflows/review-swarm.yml` must checkout `main`'s copy of `workflows/review-swarm.yaml` and `.github/workflows/scripts/*.sh` SEPARATELY from the PR head. Use two `actions/checkout@v4` steps with different `path:` values. + +### 2. Unified verdict-extraction logic +One source of truth for verdict logic. Create `scripts/swarm-verdict.sh` that both the aggregate step and `swarm-post.sh` source. Rules: +- Transcript selection sorts by FILENAME (`YYYYMMDD-HHMM` prefix), not mtime +- Verdict is the LAST non-empty line's token, not whole-file grep +- `overall = ALL lenses PASSED, else FAILED` โ€” fail-closed on MISSING/UNCLEAR/FAILED + +### 3. Auth secret validation fail-fast +Preflight step validates `RELAY_WORKSPACE_KEY` is set and non-empty BEFORE launching cloud run. If missing, fail with clear message. Do NOT proceed to interactive fallback. + +### 4. Sticky marker + sticky transcripts +Use HTML anchors to edit-in-place across pushes. A PR with 5 pushes should end with 1 marker + 3 transcripts (edited to latest), NOT 5 markers + 15 transcripts. Use `` anchors. + +### 5. Every PR gets reviewed +NO author whitelist. All PRs are reviewed. + +### 6. Cloud sandbox has no `gh` auth +GHA runner has `gh` auth, cloud sandbox does not. The workflow must fetch PR diff + metadata on the GHA runner via `gh pr diff/view`, stage into `.review-target/{pr-number,pr.diff,pr.json}`, `git add -f` (drop the `.gitignore` mask on `.review-target`), then `agent-relay cloud run` uploads the working tree. + +### 7. Job timeout > poll deadline > swarm timeoutMs +Documented invariant: +- `workflows/review-swarm.yaml` `timeoutMs: 3600000` (60 min) +- Wait step poll deadline: 3900s (65 min) +- Job `timeout-minutes: 75` (65 + 10 for install/checkout/post) +Add comments naming this ordering invariant. + +### 8. Wait step records terminal status; post step runs on always() +Structure: +```yaml +wait step: records $swarm_status output, always exits 0 +post step: if: always() && steps.launch.outputs.run_id != '' +fail step: if: steps.wait.outputs.swarm_status != 'completed' +``` +A rejecting swarm's transcripts + marker MUST reach the PR. + +### 9. Transcript-to-run-id binding +Require ALL THREE transcripts newly-produced in THIS sync. If any transcript's file mtime is older than when the sync started, reject as stale. ## Definition of done -ALL of the following must hold: - -1. `.github/workflows/cloud-runtime-artifact.yml` runs `cargo test --workspace` - and the full SDK suite. -2. You have run both suites LOCALLY and pasted the literal commands and their - output tails with test counts, so the change is grounded in what actually - passes rather than in what you expect CI to do. - - `cd kernel && PATH="$HOME/.cargo/bin:$PATH" RUSTUP_TOOLCHAIN=stable sh ../ops/cargo.sh test --workspace` - - `cd sdk && ./node_modules/.bin/vitest run` (after a build; `npx` hangs on - some hosts, use `./node_modules/.bin/`) -3. If either suite is red locally, you STOP and report which tests fail with - their literal output. Do not fix them. Do not weaken the workflow to go - green. -4. `sdk/tests/live-kernel.test.ts` needs a built `relayflowd`; if it cannot - collect in your sandbox, say so explicitly rather than reporting a pass that - excluded it. -5. As your LAST action, run `git status --porcelain` and paste it. - -## Why this and not a product change - -A sandbox cannot deliver โ€” no git remote, no GitHub token โ€” so its output is a -patch a human applies. That makes a small, self-contained, high-leverage -change the right shape for a tick. This one is three lines of intent, needs no -product knowledge to review, and every future tick benefits from it. - -The previous contents of this file described building `sdk/src/worker.ts`. That -file exists and gate-2 workloads run against it; the package was complete and -the file had not been updated. A tick that assesses against a finished work -package burns a whole cycle, so treat a stale NEXT.md as a defect in its own -right and say so in your assess step if you find one. +ALL of the following must be true: + +1. All files parse correctly: +```bash +python3 -c "import yaml; yaml.safe_load(open('.github/workflows/review-swarm.yml'))" +bash -n .github/workflows/scripts/swarm-prepare.sh +bash -n .github/workflows/scripts/swarm-post.sh +bash -n .github/workflows/scripts/swarm-verdict.sh +``` + +2. Aggregate verdict logic exists in ONE file (`scripts/swarm-verdict.sh`), both callers use it + +3. Author whitelist absent (no `if: github.event.pull_request.user.login == ...`) + +4. Immutable gate: two checkout steps with different paths in `.github/workflows/review-swarm.yml` + +5. PR body explicitly documents each of the 9 requirements above and shows where each is satisfied + +6. SDK tests green: +```bash +cd sdk && npm test +``` + +7. Final verification: +```bash +git status --porcelain +``` + +## Out of scope + +- `sdk/` (Track A owns that) +- `kernel/` (gate 1 done, no changes) +- `ops/*` (chief owns briefs and state) +- Any GHA workflow other than review-swarm.yml +- Actually TESTING the workflow in CI (requires `RELAY_WORKSPACE_KEY` secret set, which is a human step) + +The definition of done is the workflow being CORRECT, not proven live. diff --git a/workflows/review-swarm.yaml b/workflows/review-swarm.yaml index 6bd1a73cc..f35b73be7 100644 --- a/workflows/review-swarm.yaml +++ b/workflows/review-swarm.yaml @@ -14,6 +14,7 @@ description: > swarm: pattern: dag channel: flows-review + # Ordering invariant: job 75m > poll 3900s (65m) > swarm 3600000ms (60m). timeoutMs: 3600000 maxConcurrency: 3 @@ -39,18 +40,16 @@ workflows: - name: fetch type: deterministic command: | - # Deterministic steps do not inherit the launching shell's env, so the - # target is read from a file the operator writes before the run: - # echo 8 > .review-target - set -u - if [ ! -f .review-target ]; then - echo "FETCH_FAILED: .review-target missing โ€” write the PR number to it first"; exit 1 - fi - PR=$(tr -dc '0-9' < .review-target) - [ -n "$PR" ] || { echo "FETCH_FAILED: .review-target holds no PR number"; exit 1; } - gh pr view "$PR" --json headRefName,title,url > /tmp/pr-$PR.json - gh pr diff "$PR" > /tmp/pr-$PR.diff - echo "target PR #$PR, $(wc -l < /tmp/pr-$PR.diff) diff lines" + set -eu + target=.review-target + for file in pr-number pr.diff pr.json swarm-verdict.sh; do + [ -s "$target/$file" ] || { + echo "FETCH_FAILED: $target/$file missing or empty"; exit 1; + } + done + PR=$(tr -dc '0-9' < "$target/pr-number") + [ -n "$PR" ] || { echo "FETCH_FAILED: invalid PR number"; exit 1; } + echo "target PR #$PR, $(wc -l < "$target/pr.diff") diff lines" echo FETCHED - name: lens-maintainability @@ -58,15 +57,15 @@ workflows: agent: maintainability dependsOn: [fetch] task: | - Review the PR whose number is in .review-target (diff at - /tmp/pr-.diff, metadata at /tmp/pr-.json) through ONE lens: maintainability. + Review the PR whose number is in .review-target/pr-number (diff at + .review-target/pr.diff, metadata at .review-target/pr.json) through ONE lens: maintainability. Ask: could a stranger read this in six months and change it safely? Name unclear boundaries, implicit contracts, missing failure handling, comments that assert what the code does not do, and tests that would not fail if the behavior broke. Read AGENTS.md and docs/RFC-0001-everything-is-a-relayflow.md first. Write your complete review to - ops/reviews/$(date +%Y%m%d-%H%M)-pr$(cat .review-target)-maintainability.md + ops/reviews/$(date +%Y%m%d-%H%M)-pr$(cat .review-target/pr-number)-maintainability.md and `git add` it. End your output with REVIEW_PASSED or REVIEW_FAILED. verification: type: output_contains @@ -79,7 +78,7 @@ workflows: agent: history dependsOn: [fetch] task: | - Review the PR whose number is in .review-target (diff at /tmp/pr-.diff) through ONE + Review the PR whose number is in .review-target/pr-number (diff at .review-target/pr.diff) through ONE lens: does this change fit the story of the code? Run `git log --oneline -40` and read ops/DRIVE-LOG.md, ops/NEXT.md and ops/DIRECTIVES.md if present. Ask: does it repeat a mistake the log @@ -87,7 +86,7 @@ workflows: Does it reintroduce something a previous commit deliberately removed? Does the commit message tell the truth about the diff? Write your complete review to - ops/reviews/$(date +%Y%m%d-%H%M)-pr$(cat .review-target)-history.md and + ops/reviews/$(date +%Y%m%d-%H%M)-pr$(cat .review-target/pr-number)-history.md and `git add` it. End your output with REVIEW_PASSED or REVIEW_FAILED. verification: type: output_contains @@ -100,14 +99,14 @@ workflows: agent: structure dependsOn: [fetch] task: | - Review the PR whose number is in .review-target (diff at /tmp/pr-.diff) through ONE + Review the PR whose number is in .review-target/pr-number (diff at .review-target/pr.diff) through ONE lens: structure. Boundaries, coupling, file size and single purpose, whether the shape matches RFC-0001 (closed kernel vocabulary, helpers over primitives, fail-closed, completionReason discipline) and AGENTS.md. Name anything that puts product logic in the kernel, adds a primitive instead of a helper, or grows a file past its purpose. Write your complete review to - ops/reviews/$(date +%Y%m%d-%H%M)-pr$(cat .review-target)-structure.md and + ops/reviews/$(date +%Y%m%d-%H%M)-pr$(cat .review-target/pr-number)-structure.md and `git add` it. End your output with REVIEW_PASSED or REVIEW_FAILED. verification: type: output_contains @@ -123,7 +122,7 @@ workflows: # exactly the review files the lenses staged before any later reset # can destroy them. set -u - PR=$(tr -dc '0-9' < .review-target 2>/dev/null) + PR=$(tr -dc '0-9' < .review-target/pr-number 2>/dev/null) if ! git diff --cached --quiet -- ops/reviews/; then git commit -m "ops(review): persist PR #${PR} swarm transcripts" -- ops/reviews/ fi @@ -132,23 +131,7 @@ workflows: type: deterministic dependsOn: [persist-transcripts] command: | - # Any single honest refusal blocks the merge. A missing transcript is - # a refusal too: an unpersisted verdict is not evidence. - set -u - PR=$(tr -dc '0-9' < .review-target 2>/dev/null) - fail=0 - for lens in maintainability history structure; do - f=$(ls -t ops/reviews/*-pr${PR}-${lens}.md 2>/dev/null | head -1) - if [ -z "$f" ]; then - echo "SWARM_FAILED: $lens produced no transcript"; fail=1; continue - fi - if grep -q "REVIEW_FAILED" "$f"; then - echo "SWARM_FAILED: $lens rejected โ€” see $f"; fail=1 - elif grep -q "REVIEW_PASSED" "$f"; then - echo "ok: $lens passed ($f)" - else - echo "SWARM_FAILED: $lens transcript carries no verdict ($f)"; fail=1 - fi - done - [ $fail -eq 0 ] && echo SWARM_PASSED || exit 1 + set -eu + . .review-target/swarm-verdict.sh + swarm_evaluate "$(tr -dc '0-9' < .review-target/pr-number)" ops/reviews timeoutMs: 120000