mirror of
https://github.com/block/buzz.git
synced 2026-10-28 13:01:10 +00:00
Merge origin/main into micn/buzz-agent-goose-core
Adopts main's tool-call permission gate (#5712) onto the goose loop. The conflicts were semantic, not textual: #5712 landed +2494/-299 inside `crates/buzz-agent`, the crate this branch rewrote, so five of main's files (`llm.rs`, `mcp.rs`, `fake_llm.rs`, `regressions.rs`, and main's `agent.rs` loop) no longer exist here and their share of the feature had to be re-expressed against goose. Taken from main unchanged: `permission.rs` (the broker), `wire.rs`'s `session/request_permission` shapes, `Inbound::Response`, `send_checked`, and `write_frames`. Re-expressed: * The gate moved from main's `RunCtx::execute_parallel` to `tools::run_one`, which is where this branch dispatches model-issued calls. It runs after the `tool_call` announcement and before `Agent::dispatch_tool_call`, so a denied call still reaches a terminal wire state. * The broker takes goose's `CancellationToken` instead of a `watch::Receiver<bool>`, and an `AskSubject` instead of buzz's deleted `ToolCall` type. * Argument-shape validation is dropped: it guarded main's `mcp.rs`, and goose's `CallToolRequestParams::arguments` is already `Option<Map>`, so a non-object cannot be represented. * `max_pending_permissions` / `permission_timeout` follow this crate's `env_parse`-with-default style rather than main's validated `parse_env`. Two behaviour notes worth review: * `load_skill` is now **gated**, where main exempted it as an in-process built-in. Under goose it is a real tool on the skills platform extension, dispatched through the same path as any MCP tool. Exempting it would need an allowlist keyed on tool *name* — which an untrusted MCP server controls, and could register its own `load_skill` to inherit. Test renamed accordingly. * Connection teardown already cancels every session (`serve`'s shutdown), so main's `cancel_all_sessions` plus writer-death select arm had no separate work to do here; the broker's wire-closed path covers the rest. Test harness changes: * `tests/common/mod.rs`'s fake LLM now answers SSE. goose's openai-compatible provider requests `stream: true` and parses `chat.completion.chunk`; against main's plain `chat.completion` body every turn ended `end_turn` with no tool call and no ask, which reads as a broken gate rather than a broken fixture. Its `/models` lookup is also answered without popping the canned queue. * `tests/approve/mod.rs` auto-approves for the nine suites whose subject is not the boundary, selecting by option `kind` rather than a hardcoded `optionId`. * `cancel.rs` now answers the ask and waits for `FAKE_MCP_CALL_RECEIVED` before cancelling. Without that the cancel resolved the *permission wait*, the tool was never dispatched, and `cancel_propagates_notifications_cancelled_to_mcp` failed — a test named "cancel mid tool call" was exercising "cancel before tool call". Verified at this tree: `buzz-agent` full suite 111 unit + 36 integration (including all 9 boundary tests) pass, `clippy --all-targets -D warnings` clean, `cargo fmt --check` clean, `cargo build --workspace` clean, and both `Cargo.lock` files resolve `--locked` (8 goose deps in root, 0 in desktop). Co-authored-by: Michael Neale <michael.neale@gmail.com> Signed-off-by: Michael Neale <michael.neale@gmail.com>
This commit is contained in:
26
scripts/create-deployment-eligibility-predicate.jq
Normal file
26
scripts/create-deployment-eligibility-predicate.jq
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
predicate_version: 1,
|
||||
eligible: true,
|
||||
source: {
|
||||
repository: $source_repository,
|
||||
ref: $source_ref,
|
||||
sha: $source_sha
|
||||
},
|
||||
build: {
|
||||
workflow: $build_workflow,
|
||||
run_id: $build_run_id,
|
||||
run_attempt: $build_run_attempt,
|
||||
run_url: $build_run_url
|
||||
},
|
||||
qualification: {
|
||||
workflow: $qualification_workflow,
|
||||
run_id: $qualification_run_id,
|
||||
run_attempt: $qualification_run_attempt,
|
||||
run_url: $qualification_run_url,
|
||||
conclusion: "success"
|
||||
},
|
||||
helm_chart: {
|
||||
name: "buzz",
|
||||
compatible_version: $chart_version
|
||||
}
|
||||
}
|
||||
@@ -257,12 +257,15 @@ def validate(args: argparse.Namespace) -> None:
|
||||
bad = [str(path.relative_to(ROOT)) for path, value in manifests.items() if value != version]
|
||||
if bad:
|
||||
raise SystemExit(f"version mismatch in: {', '.join(bad)}")
|
||||
author = git("show", "-s", "--format=%an <%ae>", candidate)
|
||||
name = git("show", "-s", "--format=%an", candidate)
|
||||
email = git("show", "-s", "--format=%ae", candidate)
|
||||
if not name or not email:
|
||||
raise SystemExit("candidate has no author identity")
|
||||
author = f"{name} <{email}>"
|
||||
body = git("show", "-s", "--format=%B", candidate)
|
||||
if author != "Wes <wesbillman@users.noreply.github.com>":
|
||||
raise SystemExit(f"unexpected candidate author: {author}")
|
||||
if "Signed-off-by: Wes <wesbillman@users.noreply.github.com>" not in body:
|
||||
raise SystemExit("candidate is missing Wes Signed-off-by trailer")
|
||||
signoffs = re.findall(rf"(?m)^Signed-off-by: {re.escape(author)}$", body)
|
||||
if len(signoffs) != 1:
|
||||
raise SystemExit(f"candidate must carry exactly one Signed-off-by trailer matching its author {author}")
|
||||
if not re.search(r"(?m)^Co-authored-by: .+ <.+>$", body):
|
||||
raise SystemExit("candidate is missing automation Co-authored-by trailer")
|
||||
print(f"validated immutable desktop candidate {candidate} for desktop-v{version}")
|
||||
|
||||
@@ -336,6 +336,7 @@ export BUZZ_GIT_HOOK_HMAC_SECRET="${HMAC_SECRET}"
|
||||
export BUZZ_BIND_ADDR="${RELAY_HOST}:${RELAY_PORT}"
|
||||
export RELAY_URL="${RELAY_WS}"
|
||||
export RUST_LOG="buzz_relay=warn"
|
||||
export BUZZ_RELAY_PRIVATE_KEY="${BUZZ_RELAY_PRIVATE_KEY:-$(openssl rand -hex 32)}"
|
||||
export BUZZ_REQUIRE_AUTH_TOKEN=false
|
||||
|
||||
# Clean repos dir (isolated test state)
|
||||
|
||||
66
scripts/ensure-local-relay-key.sh
Executable file
66
scripts/ensure-local-relay-key.sh
Executable file
@@ -0,0 +1,66 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ENV_FILE="${1:-.env}"
|
||||
|
||||
if [[ ! -f "${ENV_FILE}" ]]; then
|
||||
echo "error: ${ENV_FILE} does not exist" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
existing_key="$({
|
||||
unset BUZZ_RELAY_PRIVATE_KEY
|
||||
set +u
|
||||
# shellcheck disable=SC1090
|
||||
source "${ENV_FILE}" || exit 1
|
||||
printf '%s' "${BUZZ_RELAY_PRIVATE_KEY:-}"
|
||||
})"
|
||||
|
||||
if [[ -n "${existing_key}" ]]; then
|
||||
chmod 600 "${ENV_FILE}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
relay_key="$(node <<'NODE'
|
||||
const { randomBytes } = require("node:crypto");
|
||||
const curveOrder = BigInt(
|
||||
"0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141",
|
||||
);
|
||||
|
||||
let bytes;
|
||||
let scalar;
|
||||
do {
|
||||
bytes = randomBytes(32);
|
||||
scalar = BigInt(`0x${bytes.toString("hex")}`);
|
||||
} while (scalar === 0n || scalar >= curveOrder);
|
||||
|
||||
process.stdout.write(bytes.toString("hex"));
|
||||
NODE
|
||||
)"
|
||||
|
||||
temp_file="$(mktemp "${ENV_FILE}.tmp.XXXXXX")"
|
||||
trap 'rm -f "${temp_file}"' EXIT
|
||||
|
||||
awk -v key="${relay_key}" '
|
||||
BEGIN { replaced = 0 }
|
||||
/^[[:space:]]*(export[[:space:]]+)?BUZZ_RELAY_PRIVATE_KEY=/ {
|
||||
if (!replaced) {
|
||||
print "BUZZ_RELAY_PRIVATE_KEY=" key
|
||||
replaced = 1
|
||||
}
|
||||
next
|
||||
}
|
||||
{ print }
|
||||
END {
|
||||
if (!replaced) {
|
||||
if (NR > 0) print ""
|
||||
print "BUZZ_RELAY_PRIVATE_KEY=" key
|
||||
}
|
||||
}
|
||||
' "${ENV_FILE}" > "${temp_file}"
|
||||
|
||||
chmod 600 "${temp_file}"
|
||||
mv "${temp_file}" "${ENV_FILE}"
|
||||
trap - EXIT
|
||||
|
||||
echo "Generated BUZZ_RELAY_PRIVATE_KEY in ${ENV_FILE}."
|
||||
@@ -42,8 +42,7 @@ chore(release): release Buzz Desktop version $version
|
||||
|
||||
Co-authored-by: $agent_name <$agent_email>
|
||||
EOF
|
||||
git -c user.name='Wes' -c user.email='wesbillman@users.noreply.github.com' \
|
||||
commit -s -F "$msg"
|
||||
git commit -s -F "$msg"
|
||||
scripts/desktop_release.py validate --candidate HEAD --version "$version" --repo block/buzz
|
||||
|
||||
candidate_sha="$(git rev-parse HEAD)"
|
||||
|
||||
@@ -103,6 +103,7 @@ else
|
||||
RELAY_BIN="${ROOT}/target/ci/buzz-relay"
|
||||
fi
|
||||
log "starting relay at ${RELAY_HTTP_URL}"
|
||||
RELAY_PRIVATE_KEY="$(openssl rand -hex 32)"
|
||||
env \
|
||||
DATABASE_URL="postgres://buzz:buzz_dev@localhost:5432/${DB_NAME}" \
|
||||
REDIS_URL="redis://localhost:6379/${REDIS_DB}" \
|
||||
@@ -110,6 +111,7 @@ env \
|
||||
BUZZ_BIND_ADDR="127.0.0.1:${RELAY_PORT}" \
|
||||
BUZZ_HEALTH_PORT="${HEALTH_PORT}" \
|
||||
BUZZ_METRICS_PORT="${METRICS_PORT}" \
|
||||
BUZZ_RELAY_PRIVATE_KEY="${RELAY_PRIVATE_KEY}" \
|
||||
BUZZ_REQUIRE_AUTH_TOKEN=false \
|
||||
BUZZ_RECONCILE_CHANNELS=true \
|
||||
BUZZ_RATE_LIMIT_HUMAN_MESSAGES_PER_MIN=1000000 \
|
||||
|
||||
9
scripts/select-qualified-ci-run.jq
Normal file
9
scripts/select-qualified-ci-run.jq
Normal file
@@ -0,0 +1,9 @@
|
||||
[
|
||||
.workflow_runs[]
|
||||
| select(.head_sha == $source_sha)
|
||||
| select(.event == "push")
|
||||
| select(.head_branch == "main" or .head_branch == "release")
|
||||
]
|
||||
| sort_by([.id, .run_attempt])
|
||||
| last // empty
|
||||
| select(.conclusion == "success")
|
||||
@@ -133,6 +133,7 @@ ok "Relay built"
|
||||
# survives (same pattern the perf stack uses). Logs to ${RELAY_LOG}.
|
||||
RELAY_LOG="${RELAY_LOG:-/tmp/dawn-relay-run.log}"
|
||||
TMUX_SESSION="${TMUX_SESSION:-dawn-relay}"
|
||||
RELAY_PRIVATE_KEY="$(openssl rand -hex 32)"
|
||||
tmux kill-session -t "${TMUX_SESSION}" 2>/dev/null || true
|
||||
if command -v lsof >/dev/null 2>&1 && lsof -nP -iTCP:"${RELAY_MAIN}" -sTCP:LISTEN >/dev/null 2>&1; then
|
||||
err "Port ${RELAY_MAIN} is already in use; refusing to report a stale relay as this harness."
|
||||
@@ -151,6 +152,7 @@ tmux new-session -d -s "${TMUX_SESSION}" "cd '${REPO_ROOT}' && env \
|
||||
BUZZ_S3_ACCESS_KEY=buzz_dev \
|
||||
BUZZ_S3_SECRET_KEY=buzz_dev_secret \
|
||||
BUZZ_S3_BUCKET=buzz-media \
|
||||
BUZZ_RELAY_PRIVATE_KEY=${RELAY_PRIVATE_KEY} \
|
||||
BUZZ_REQUIRE_AUTH_TOKEN=false \
|
||||
BUZZ_RECONCILE_CHANNELS=true \
|
||||
'./target/${CARGO_TARGET_PROFILE}/buzz-relay' > '${RELAY_LOG}' 2>&1"
|
||||
|
||||
@@ -152,6 +152,8 @@ fi
|
||||
|
||||
log "Starting relay..."
|
||||
|
||||
TEST_RELAY_PRIVATE_KEY="${BUZZ_RELAY_PRIVATE_KEY:-$(openssl rand -hex 32)}"
|
||||
|
||||
# Optional NIP-43 membership gating: exported by callers that need a
|
||||
# membership-gated relay (e.g. the mesh lifecycle smoke). All three must be
|
||||
# set together — the relay fails fast otherwise.
|
||||
@@ -160,8 +162,8 @@ if [[ "${BUZZ_REQUIRE_RELAY_MEMBERSHIP:-}" == "true" ]]; then
|
||||
MEMBERSHIP_ENV+=(
|
||||
BUZZ_REQUIRE_RELAY_MEMBERSHIP=true
|
||||
RELAY_OWNER_PUBKEY="${RELAY_OWNER_PUBKEY:?RELAY_OWNER_PUBKEY required with BUZZ_REQUIRE_RELAY_MEMBERSHIP=true}"
|
||||
BUZZ_RELAY_PRIVATE_KEY="${BUZZ_RELAY_PRIVATE_KEY:?BUZZ_RELAY_PRIVATE_KEY required with BUZZ_REQUIRE_RELAY_MEMBERSHIP=true}"
|
||||
)
|
||||
: "${BUZZ_RELAY_PRIVATE_KEY:?BUZZ_RELAY_PRIVATE_KEY required with BUZZ_REQUIRE_RELAY_MEMBERSHIP=true}"
|
||||
log "Membership gating enabled (NIP-43)"
|
||||
fi
|
||||
|
||||
@@ -170,6 +172,7 @@ nohup env \
|
||||
REDIS_URL=redis://localhost:6379 \
|
||||
RELAY_URL=ws://localhost:3000 \
|
||||
BUZZ_BIND_ADDR=0.0.0.0:3000 \
|
||||
BUZZ_RELAY_PRIVATE_KEY="${TEST_RELAY_PRIVATE_KEY}" \
|
||||
BUZZ_REQUIRE_AUTH_TOKEN=false \
|
||||
BUZZ_RECONCILE_CHANNELS=true \
|
||||
BUZZ_GIT_PROBE_WRITERS=8 \
|
||||
|
||||
@@ -64,8 +64,42 @@ for path in ('desktop/package.json', 'desktop/src-tauri/tauri.conf.json'):
|
||||
open('desktop/src-tauri/Cargo.toml','w').write('[package]\nversion = "1.0.1"\n')
|
||||
PY
|
||||
git add .
|
||||
git -c user.name=Wes -c user.email=wesbillman@users.noreply.github.com commit -q -s -m 'chore(release): release Buzz Desktop version 1.0.1' -m 'Co-authored-by: Test Automation <test@example.com>'
|
||||
git commit -q -s -m 'chore(release): release Buzz Desktop version 1.0.1' -m 'Co-authored-by: Test Automation <test@example.com>'
|
||||
PATH="$mock_bin:$PATH" scripts/desktop_release.py validate --version 1.0.1 --repo block/buzz
|
||||
good_candidate=$(git rev-parse HEAD)
|
||||
|
||||
# A candidate whose Signed-off-by does not match its author is a dishonest
|
||||
# DCO sign-off and must be rejected. Rewrite the author while keeping the
|
||||
# original trailer body, then restore the honest candidate.
|
||||
git -c user.name=Impostor -c user.email=impostor@example.com commit -q --amend --no-edit --reset-author
|
||||
if PATH="$mock_bin:$PATH" scripts/desktop_release.py validate --version 1.0.1 --repo block/buzz >/dev/null 2>&1; then
|
||||
echo "validator accepted a candidate whose sign-off does not match its author" >&2; exit 1
|
||||
fi
|
||||
git reset -q --hard "$good_candidate"
|
||||
|
||||
# The trailer must be a complete anchored line, not substring-matched. A prose
|
||||
# line that merely contains the sign-off text, or a real trailer with trailing
|
||||
# garbage, must be rejected. Both were accepted before the anchored parse.
|
||||
for bogus in \
|
||||
'not-a-trailer Signed-off-by: test <test@example.com>' \
|
||||
'Signed-off-by: test <test@example.com> trailing-garbage'; do
|
||||
git commit -q --amend -m 'chore(release): release Buzz Desktop version 1.0.1' \
|
||||
-m 'Co-authored-by: Test Automation <test@example.com>' -m "$bogus"
|
||||
if PATH="$mock_bin:$PATH" scripts/desktop_release.py validate --version 1.0.1 --repo block/buzz >/dev/null 2>&1; then
|
||||
echo "validator accepted a malformed sign-off: $bogus" >&2; exit 1
|
||||
fi
|
||||
git reset -q --hard "$good_candidate"
|
||||
done
|
||||
|
||||
# Two matching sign-offs are also invalid: the contract is exactly one.
|
||||
git commit -q --amend -m 'chore(release): release Buzz Desktop version 1.0.1' \
|
||||
-m 'Co-authored-by: Test Automation <test@example.com>' \
|
||||
-m 'Signed-off-by: test <test@example.com>' -m 'Signed-off-by: test <test@example.com>'
|
||||
if PATH="$mock_bin:$PATH" scripts/desktop_release.py validate --version 1.0.1 --repo block/buzz >/dev/null 2>&1; then
|
||||
echo "validator accepted duplicate Signed-off-by trailers" >&2; exit 1
|
||||
fi
|
||||
git reset -q --hard "$good_candidate"
|
||||
|
||||
grep -Fq "$unrelated_before" CHANGELOG.md
|
||||
grep -Fq "$unrelated_after" CHANGELOG.md
|
||||
! grep -Fq "$prior_merge" CHANGELOG.md
|
||||
|
||||
33
scripts/test-ensure-local-relay-key.sh
Executable file
33
scripts/test-ensure-local-relay-key.sh
Executable file
@@ -0,0 +1,33 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
||||
TEST_DIR="$(mktemp -d "${TMPDIR:-/tmp}/buzz-relay-key-test.XXXXXX")"
|
||||
trap 'rm -rf "${TEST_DIR}"' EXIT
|
||||
|
||||
ENV_FILE="${TEST_DIR}/.env"
|
||||
cp "${REPO_ROOT}/.env.example" "${ENV_FILE}"
|
||||
|
||||
"${SCRIPT_DIR}/ensure-local-relay-key.sh" "${ENV_FILE}" >/dev/null
|
||||
first_key="$(sed -n 's/^BUZZ_RELAY_PRIVATE_KEY=//p' "${ENV_FILE}")"
|
||||
|
||||
if [[ ! "${first_key}" =~ ^[0-9a-f]{64}$ ]]; then
|
||||
echo "FAIL: bootstrap did not generate a valid 32-byte hex relay key" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
"${SCRIPT_DIR}/ensure-local-relay-key.sh" "${ENV_FILE}" >/dev/null
|
||||
second_key="$(sed -n 's/^BUZZ_RELAY_PRIVATE_KEY=//p' "${ENV_FILE}")"
|
||||
|
||||
if [[ "${first_key}" != "${second_key}" ]]; then
|
||||
echo "FAIL: bootstrap replaced the existing relay key" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "$(grep -c '^BUZZ_RELAY_PRIVATE_KEY=' "${ENV_FILE}")" -ne 1 ]]; then
|
||||
echo "FAIL: bootstrap wrote more than one relay key" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "PASS: bootstrap generates one relay key and reuses it"
|
||||
117
scripts/test-relay-image-eligibility-workflow.sh
Executable file
117
scripts/test-relay-image-eligibility-workflow.sh
Executable file
@@ -0,0 +1,117 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
repo_root=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
||||
workflow="$repo_root/.github/workflows/docker.yml"
|
||||
selector="$repo_root/scripts/select-qualified-ci-run.jq"
|
||||
predicate_builder="$repo_root/scripts/create-deployment-eligibility-predicate.jq"
|
||||
|
||||
require_literal() {
|
||||
local needle=$1
|
||||
grep -Fq -- "$needle" "$workflow" || {
|
||||
echo "relay image workflow is missing required delivery contract: $needle" >&2
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
require_literal " qualify:"
|
||||
require_literal "actions: read"
|
||||
require_literal "actions/workflows/ci.yml/runs"
|
||||
require_literal 'select-qualified-ci-run.jq'
|
||||
require_literal "needs: [build, qualify]"
|
||||
require_literal "https://buzz.block.xyz/attestations/deployment-eligibility/v1"
|
||||
require_literal "actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6"
|
||||
require_literal "if: matrix.variant == 'release'"
|
||||
require_literal "BUZZ_SOURCE_SHA"
|
||||
require_literal "BUZZ_BUILD_ID"
|
||||
require_literal "BUZZ_BUILD_URL"
|
||||
require_literal '- "deploy/charts/buzz/Chart.yaml"'
|
||||
require_literal '- "scripts/create-deployment-eligibility-predicate.jq"'
|
||||
require_literal '- "scripts/select-qualified-ci-run.jq"'
|
||||
require_literal '- "scripts/test-relay-image-eligibility-workflow.sh"'
|
||||
|
||||
if grep -Fq "buzz-staging-dev" "$workflow"; then
|
||||
echo "canonical relay image workflow references the preview-only image package" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# The merge job reads scripts/create-deployment-eligibility-predicate.jq from the
|
||||
# workspace, so it must check out the source first. Guard against the checkout being
|
||||
# dropped from that job (the workspace is otherwise empty and jq exits non-zero).
|
||||
merge_job=$(awk '/^ merge:/{f=1} f&&/^ [a-z][a-z_-]*:$/&&!/^ merge:/{exit} f' "$workflow")
|
||||
grep -Fq "actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10" <<<"$merge_job" || {
|
||||
echo "merge job must check out the source before building the eligibility predicate" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
select_run() {
|
||||
jq -r --arg source_sha aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa -f "$selector" | jq -r '.id // empty'
|
||||
}
|
||||
|
||||
selected=$(select_run <<'JSON'
|
||||
{"workflow_runs":[
|
||||
{"id":100,"run_attempt":1,"head_sha":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","head_branch":"main","event":"push","status":"completed","conclusion":"failure"},
|
||||
{"id":101,"run_attempt":1,"head_sha":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","head_branch":"main","event":"push","status":"completed","conclusion":"success"}
|
||||
]}
|
||||
JSON
|
||||
)
|
||||
[[ "$selected" == "101" ]] || {
|
||||
echo "latest successful same-SHA main run was not selected" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
selected=$(select_run <<'JSON'
|
||||
{"workflow_runs":[
|
||||
{"id":100,"run_attempt":1,"head_sha":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","head_branch":"main","event":"push","status":"completed","conclusion":"success"},
|
||||
{"id":101,"run_attempt":1,"head_sha":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","head_branch":"main","event":"push","status":"completed","conclusion":"failure"}
|
||||
]}
|
||||
JSON
|
||||
)
|
||||
[[ -z "$selected" ]] || {
|
||||
echo "stale successful run remained eligible after a newer failure" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
selected=$(select_run <<'JSON'
|
||||
{"workflow_runs":[
|
||||
{"id":102,"run_attempt":1,"head_sha":"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb","head_branch":"main","event":"push","status":"completed","conclusion":"success"},
|
||||
{"id":103,"run_attempt":1,"head_sha":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","head_branch":"feature","event":"pull_request","status":"completed","conclusion":"success"}
|
||||
]}
|
||||
JSON
|
||||
)
|
||||
[[ -z "$selected" ]] || {
|
||||
echo "wrong-SHA or pull-request CI run was accepted" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
predicate=$(jq -n \
|
||||
--arg source_repository "block/buzz" \
|
||||
--arg source_ref "refs/heads/main" \
|
||||
--arg source_sha "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" \
|
||||
--arg build_workflow ".github/workflows/docker.yml" \
|
||||
--argjson build_run_id 200 \
|
||||
--argjson build_run_attempt 2 \
|
||||
--arg build_run_url "https://github.com/block/buzz/actions/runs/200/attempts/2" \
|
||||
--arg qualification_workflow ".github/workflows/ci.yml" \
|
||||
--argjson qualification_run_id 201 \
|
||||
--argjson qualification_run_attempt 1 \
|
||||
--arg qualification_run_url "https://github.com/block/buzz/actions/runs/201" \
|
||||
--arg chart_version "0.1.8" \
|
||||
-f "$predicate_builder")
|
||||
|
||||
jq -e '
|
||||
.predicate_version == 1 and
|
||||
.eligible == true and
|
||||
.source.sha == "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" and
|
||||
.build.run_id == 200 and
|
||||
.qualification.run_id == 201 and
|
||||
.qualification.conclusion == "success" and
|
||||
.helm_chart == {"name":"buzz","compatible_version":"0.1.8"} and
|
||||
(has("schema") | not) and
|
||||
(.helm_chart | has("schema_compatibility") | not)
|
||||
' <<<"$predicate" >/dev/null || {
|
||||
echo "deployment eligibility predicate has an invalid contract" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
echo "relay image eligibility workflow contract passed"
|
||||
Reference in New Issue
Block a user