Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 52 additions & 1 deletion .github/workflows/review-swarm.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,18 @@ jobs:
- name: Check out immutable gate from main
uses: actions/checkout@v4
with:
ref: main
# The base SHA is the immutable definition this PR is judged by. A
# moving `main` ref could change the judge while this run is live.
ref: ${{ github.event.pull_request.base.sha }}
path: gate-files
sparse-checkout: |
workflows/review-swarm.yaml
.github/workflows/scripts/swarm-post.sh
.github/workflows/scripts/swarm-prepare.sh
.github/workflows/scripts/swarm-verdict.sh
.github/workflows/scripts/swarm-gate.test.sh
.github/workflows/scripts/swarm-definition.sh
.github/workflows/scripts/swarm-definition.test.sh

# The gate decides whether code merges, so before it judges anything it
# proves it can still say no. `swarm-verdict.sh` is a handful of lines of
Expand All @@ -70,6 +74,8 @@ jobs:
env:
REVIEW_PR_NUMBER: ${{ github.event.pull_request.number }}
run: |
ruby --version
ruby -ryaml -e 'abort "Psych YAML parser unavailable" unless defined?(Psych)'
test_script=gate-files/.github/workflows/scripts/swarm-gate.test.sh
if [ ! -f "$test_script" ]; then
# Only the introducing PR may bootstrap before its test is on main.
Expand All @@ -82,6 +88,51 @@ jobs:
exit 1
fi
bash "$test_script"
definition_test=gate-files/.github/workflows/scripts/swarm-definition.test.sh
if [ ! -f "$definition_test" ]; then
# The introducing PR cannot run a helper that is not on its base
# yet. Once this PR lands, absence is a deletion or checkout bug
# and must fail closed like the verdict self-test above.
if [ "$REVIEW_PR_NUMBER" = 265 ]; then
echo "::notice::PR #265 bootstrap: candidate validator is not on main yet."
else
echo "::error::main-owned candidate validator is missing" >&2
exit 1
fi
else
bash "$definition_test"
fi

# A PR that changes the relayflow definition cannot be judged by that
# definition without violating the immutable-gate rule. Parse and check
# the candidate's load-bearing policy against the trusted base, but keep
# the actual review run below on the base definition. This is a candidate
# contract check, not a candidate verdict.
- name: Validate candidate review definition
env:
REVIEW_PR_NUMBER: ${{ github.event.pull_request.number }}
REVIEW_BASE_SHA: ${{ github.event.pull_request.base.sha }}
REVIEW_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
working-directory: pr-head
run: |
# Compare the PR patch (merge-base...head), not the two trees. A
# branch can be stale on this file without the PR changing it.
if git diff --quiet "$REVIEW_BASE_SHA...$REVIEW_HEAD_SHA" -- workflows/review-swarm.yaml; then
echo "Candidate review definition unchanged; trusted base definition is the effective file."
exit 0
Comment thread
cursor[bot] marked this conversation as resolved.
fi
validator=../gate-files/.github/workflows/scripts/swarm-definition.sh
if [ ! -f "$validator" ]; then
if [ "$REVIEW_PR_NUMBER" = 265 ]; then
echo "::notice::PR #265 bootstrap: candidate validator is not on main yet."
else
echo "::error::main-owned candidate validator is missing" >&2
exit 1
fi
else
"$validator" workflows/review-swarm.yaml \
../gate-files/workflows/review-swarm.yaml
fi

# Fail here, in seconds, rather than in `Launch cloud swarm` ten minutes
# later. WorkflowApiKeyClient.fromEnv requires CLOUD_API_URL and
Expand Down
47 changes: 47 additions & 0 deletions .github/workflows/scripts/swarm-definition.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
#!/usr/bin/env bash
set -euo pipefail

candidate=${1:?usage: swarm-definition.sh CANDIDATE TRUSTED}
trusted=${2:?usage: swarm-definition.sh CANDIDATE TRUSTED}

test -s "$candidate" || { echo "candidate workflow is missing: $candidate" >&2; exit 1; }
test -s "$trusted" || { echo "trusted workflow is missing: $trusted" >&2; exit 1; }

# The candidate is parsed for structural and policy invariants only. It is never
# used as the authority for the review result: the caller must still launch the
# trusted definition from the base commit. Ruby's stdlib YAML parser is present
# on the GitHub runner, so this check adds no floating dependency.
ruby -ryaml - "$candidate" "$trusted" <<'RUBY'
candidate_path, trusted_path = ARGV

def load_workflow(path, label)
abort "#{label} must not be a symbolic link" if File.lstat(path).symlink?
value = YAML.safe_load(File.read(path), aliases: false)
Comment thread
coderabbitai[bot] marked this conversation as resolved.
abort "#{label} is not a YAML mapping" unless value.is_a?(Hash)
value
rescue Psych::Exception => error
abort "#{label} is invalid YAML: #{error.message.lines.first.strip}"
end

candidate = load_workflow(candidate_path, "candidate workflow")
trusted = load_workflow(trusted_path, "trusted workflow")

swarm = candidate.fetch("swarm")
abort "candidate swarm must be a mapping" unless swarm.is_a?(Hash)
error_handling = candidate.fetch("errorHandling")
abort "candidate errorHandling must be a mapping" unless error_handling.is_a?(Hash)

strategy = error_handling.fetch("strategy")
abort "candidate errorHandling.strategy must be retry (got #{strategy.inspect})" unless strategy == "retry"

delay = error_handling.fetch("retryDelayMs")
abort "candidate retryDelayMs must be a positive integer" unless delay.is_a?(Integer) && delay.positive?
abort "candidate retryDelayMs must honor the platform's 60s backoff (got #{delay})" unless delay >= 60_000

trusted_timeout = trusted.fetch("swarm").fetch("timeoutMs")
candidate_timeout = swarm.fetch("timeoutMs")
abort "candidate timeoutMs must remain #{trusted_timeout} (got #{candidate_timeout.inspect})" unless candidate_timeout == trusted_timeout

puts "CANDIDATE_DEFINITION_OK"
puts "retry strategy=#{strategy} retryDelayMs=#{delay} timeoutMs=#{candidate_timeout}"
RUBY
49 changes: 49 additions & 0 deletions .github/workflows/scripts/swarm-definition.test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#!/usr/bin/env bash
set -euo pipefail

script_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
validator="$script_dir/swarm-definition.sh"
work=$(mktemp -d)
trap 'rm -rf "$work"' EXIT

cat > "$work/trusted.yaml" <<'YAML'
swarm:
timeoutMs: 3600000
YAML

cat > "$work/candidate.yaml" <<'YAML'
version: '1.0'
swarm:
timeoutMs: 3600000
errorHandling:
strategy: retry
retryDelayMs: 60000
YAML

expect_reject() {
local label=$1
shift
if "$@" >"$work/out" 2>&1; then
echo "FAIL: $label unexpectedly passed" >&2
cat "$work/out" >&2
exit 1
fi
echo "ok: $label"
}

"$validator" "$work/candidate.yaml" "$work/trusted.yaml" | grep -q '^CANDIDATE_DEFINITION_OK$'
echo "ok: valid candidate passes"

sed 's/retryDelayMs: 60000/retryDelayMs: 1000/' "$work/candidate.yaml" > "$work/short-delay.yaml"
expect_reject "short retry delay" "$validator" "$work/short-delay.yaml" "$work/trusted.yaml"

sed 's/timeoutMs: 3600000/timeoutMs: 1800000/' "$work/candidate.yaml" > "$work/short-timeout.yaml"
expect_reject "changed timeout" "$validator" "$work/short-timeout.yaml" "$work/trusted.yaml"

printf 'not: [valid\n' > "$work/broken.yaml"
expect_reject "invalid YAML" "$validator" "$work/broken.yaml" "$work/trusted.yaml"

ln -s "$work/candidate.yaml" "$work/candidate-link.yaml"
expect_reject "symbolic-link candidate" "$validator" "$work/candidate-link.yaml" "$work/trusted.yaml"

echo "swarm-definition: all tests passed"
Loading