Merge remote-tracking branch 'origin/main' into micn/buzz-agent-goose-core

Co-authored-by: Michael Neale <michael.neale@gmail.com>
Signed-off-by: Michael Neale <michael.neale@gmail.com>
This commit is contained in:
Michael Neale
2026-08-25 15:19:09 +10:00
247 changed files with 27426 additions and 2788 deletions

View File

@@ -0,0 +1,161 @@
#!/usr/bin/env bash
set -euo pipefail
root=$(cd "$(dirname "$0")/.." && pwd)
known_good='e18b497796c12c097a38f9edb9d0641fb99eee32'
tmp=$(mktemp -d)
trap 'rm -rf "$tmp"' EXIT
mkdir -p "$tmp/.github/workflows"
cp "$root"/.github/workflows/*.yml "$tmp/.github/workflows/"
for workflow in "$root"/.github/workflows/*.yaml; do
[[ -e "$workflow" ]] || continue
cp "$workflow" "$tmp/.github/workflows/"
done
cp "$root/renovate.json" "$tmp/renovate.json"
run_contract() {
BUZZ_RUST_CACHE_CONTRACT_ROOT="$tmp" "$root/scripts/test-rust-cache-contract.sh"
}
expect_failure() {
local expected=$1
local output
if output=$(run_contract 2>&1); then
echo "expected rust cache contract failure containing: $expected" >&2
exit 1
fi
if [[ "$output" != *"$expected"* ]]; then
printf 'unexpected rust cache contract error:\n%s\n' "$output" >&2
exit 1
fi
}
write_cache_workflow() {
cat > "$tmp/.github/workflows/new-cache-user.yaml"
}
run_contract >/dev/null
write_cache_workflow <<'YAML'
name: Bad digest
on: workflow_dispatch
jobs:
cache:
runs-on: ubuntu-latest
steps:
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6
YAML
expect_failure 'restored rust-cache v2.9.2'
write_cache_workflow <<'YAML'
name: Mutable tag
on: workflow_dispatch
jobs:
cache:
runs-on: ubuntu-latest
steps:
- uses: Swatinem/rust-cache@v2
YAML
expect_failure 'rust-cache must stay on the v2.9.1 digest'
write_cache_workflow <<'YAML'
name: Expression ref
on: workflow_dispatch
jobs:
cache:
runs-on: ubuntu-latest
steps:
- uses: Swatinem/rust-cache@${{ matrix.cache-ref }}
YAML
expect_failure 'rust-cache must stay on the v2.9.1 digest'
write_cache_workflow <<'YAML'
name: Spaced key
on: workflow_dispatch
jobs:
cache:
runs-on: ubuntu-latest
steps:
- uses : Swatinem/rust-cache@v2
YAML
expect_failure 'rust-cache must stay on the v2.9.1 digest'
write_cache_workflow <<'YAML'
name: Flow mapping
on: workflow_dispatch
jobs:
cache:
runs-on: ubuntu-latest
steps:
- { uses: Swatinem/rust-cache@v2 }
YAML
expect_failure 'rust-cache must stay on the v2.9.1 digest'
write_cache_workflow <<'YAML'
name: Quoted key
on: workflow_dispatch
jobs:
cache:
runs-on: ubuntu-latest
steps:
- "uses": Swatinem/rust-cache@v2
YAML
expect_failure 'rust-cache must stay on the v2.9.1 digest'
rm "$tmp/.github/workflows/new-cache-user.yaml"
mkdir -p "$tmp/.github/actions/cache"
cat > "$tmp/.github/actions/cache/action.yml" <<'YAML'
name: Unsafe cache wrapper
runs:
using: composite
steps:
- { uses: Swatinem/rust-cache@v2 }
YAML
write_cache_workflow <<'YAML'
name: Local cache wrapper
on: workflow_dispatch
jobs:
cache:
runs-on: ubuntu-latest
steps:
- uses: ./.github/actions/cache
YAML
expect_failure 'rust-cache must stay on the v2.9.1 digest'
cat > "$tmp/.github/actions/cache/action.yml" <<YAML
name: Safe preferred cache wrapper
runs:
using: composite
steps:
- uses: Swatinem/rust-cache@$known_good
YAML
cat > "$tmp/.github/actions/cache/action.yaml" <<'YAML'
name: Unreachable stale cache wrapper
runs:
using: composite
steps:
- uses: Swatinem/rust-cache@v2
YAML
run_contract >/dev/null
rm -rf "$tmp/.github/actions" "$tmp/.github/workflows/new-cache-user.yaml"
python3 - "$tmp/.github/workflows/ci.yml" <<'PY'
import pathlib
import sys
path = pathlib.Path(sys.argv[1])
workflow = path.read_text()
needle = " key: sherpa-cache-v1\n save-if:"
replacement = " save-if:"
if workflow.count(needle) != 1:
raise SystemExit("expected one Unit Tests cache generation key")
workflow = workflow.replace(needle, replacement)
job = " unit-tests:\n"
if workflow.count(job) != 1:
raise SystemExit("expected one Unit Tests job")
workflow = workflow.replace(job, job + " env:\n key: sherpa-cache-v1\n", 1)
path.write_text(workflow)
PY
expect_failure 'rust-cache action must keep with.key set to sherpa-cache-v1'
echo "rust cache contract regressions passed"

View File

@@ -0,0 +1,122 @@
#!/usr/bin/env bash
set -euo pipefail
root=${BUZZ_RUST_CACHE_CONTRACT_ROOT:-$(cd "$(dirname "$0")/.." && pwd)}
renovate="$root/renovate.json"
known_good='e18b497796c12c097a38f9edb9d0641fb99eee32'
known_bad='6323deb102c322ba6fcbdcafc7e3dddab59af2b6'
ruby - "$root" "$renovate" "$known_good" "$known_bad" <<'RUBY'
require "json"
require "pathname"
require "yaml"
root_name, renovate_name, known_good, known_bad = ARGV
root = Pathname(root_name)
workflow_paths = (root / ".github" / "workflows").children
.select { |path| path.file? && [".yml", ".yaml"].include?(path.extname) }
.sort
abort "no GitHub Actions workflows found" if workflow_paths.empty?
load_workflow = lambda do |path|
workflow = YAML.safe_load(path.binread.force_encoding("UTF-8"), aliases: true)
abort "#{path}: workflow must be a YAML mapping" unless workflow.is_a?(Hash)
workflow
rescue Psych::Exception => error
abort "#{path}: invalid workflow YAML: #{error.message}"
end
uses_values = []
walk = nil
walk = lambda do |value, path, location|
case value
when Hash
value.each do |key, child|
child_location = "#{location}.#{key}"
uses_values << [path, child_location, child] if key.to_s == "uses"
walk.call(child, path, child_location)
end
when Array
value.each_with_index do |child, index|
walk.call(child, path, "#{location}[#{index}]")
end
end
end
workflows = workflow_paths.to_h do |path|
workflow = load_workflow.call(path)
walk.call(workflow, path, "$")
[path, workflow]
end
loaded_paths = workflow_paths.to_h { |path| [path.cleanpath, true] }
uses_index = 0
while uses_index < uses_values.length
coordinate = uses_values[uses_index][2]
uses_index += 1
next unless coordinate.is_a?(String) && coordinate.start_with?("./")
clean_root = root.realpath
target = (clean_root / coordinate.delete_prefix("./")).cleanpath
resolved_target = target.exist? ? target.realpath : target
root_prefix = "#{clean_root}#{File::SEPARATOR}"
inside_root = resolved_target == clean_root || resolved_target.to_s.start_with?(root_prefix)
abort "local action path escapes contract root: #{coordinate}" unless inside_root
next unless target.directory?
manifest = [target / "action.yml", target / "action.yaml"].find(&:file?)
abort "local action manifest missing: #{coordinate}" unless manifest
clean_manifest = manifest.cleanpath
next if loaded_paths[clean_manifest]
loaded_paths[clean_manifest] = true
action = load_workflow.call(clean_manifest)
walk.call(action, clean_manifest, "$")
end
cache_uses = uses_values.each_with_object([]) do |(path, location, coordinate), found|
next unless coordinate.is_a?(String)
match = /\ASwatinem\/rust-cache@(.+)\z/i.match(coordinate)
found << [path, location, match[1]] if match
end
abort "no Swatinem/rust-cache actions found" if cache_uses.empty?
cache_uses.each do |path, location, ref|
if ref == known_bad
abort "#{path}:#{location}: restored rust-cache v2.9.2, which poisons sherpa caches"
end
unless ref == known_good
abort "#{path}:#{location}: rust-cache must stay on the v2.9.1 digest"
end
end
ci_path = root / ".github" / "workflows" / "ci.yml"
ci = workflows.fetch(ci_path) { load_workflow.call(ci_path) }
jobs = ci["jobs"]
unit_tests = jobs.is_a?(Hash) ? jobs["unit-tests"] : nil
abort "unit-tests job missing" unless unit_tests.is_a?(Hash)
steps = unit_tests["steps"]
abort "Unit Tests steps missing" unless steps.is_a?(Array)
cache_steps = steps.select do |step|
step.is_a?(Hash) && step["uses"].is_a?(String) &&
step["uses"].match?(/\ASwatinem\/rust-cache@/i)
end
abort "Unit Tests must contain exactly one rust-cache action step" unless cache_steps.length == 1
cache_with = cache_steps.first["with"]
unless cache_with.is_a?(Hash) && cache_with["key"] == "sherpa-cache-v1"
abort "Unit Tests rust-cache action must keep with.key set to sherpa-cache-v1"
end
renovate = JSON.parse(Pathname(renovate_name).binread.force_encoding("UTF-8"))
rules = renovate.fetch("packageRules", []).select do |rule|
rule["matchManagers"] == ["github-actions"] &&
rule["matchPackageNames"] == ["Swatinem/rust-cache"]
end
unless rules.length == 1 && rules.first["allowedVersions"] == "<=2.9.1"
abort "Renovate must keep Swatinem/rust-cache at v2.9.1 or older"
end
RUBY
echo "rust cache contract passed"