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
38 changes: 35 additions & 3 deletions .github/workflows/release-on-upstream.yml
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,36 @@ jobs:
*) echo "::error::dry-run: ${TAG} is not a valid semver tag"; exit 1 ;;
esac

# Re-pinning .busbar-ref moves the SIBLING ../busbarAI checkout that every busbar-* entry in
# Cargo.toml is a path dependency on, and that move can change the resolved dependency graph
# (busbar 1.5.2 -> 1.5.3, for instance, pulled `valuable` in behind `tracing`). Cargo.lock is
# committed in this repo and release.yml builds with `--locked`, so a lock left describing the
# OLD graph makes `cargo build --locked` refuse to update it and every release target fails.
#
# So the lock is refreshed HERE, against the exact commit being pinned, and committed in the
# SAME commit as .busbar-ref. `--locked` in release.yml is deliberately kept: it is what
# guarantees a release builds exactly the graph resolved and reviewed at pin time, and dropping
# it would turn this loud failure into a silent drift.
- name: Refresh Cargo.lock against the busbar ref being pinned
if: steps.guard.outputs.exists == 'no' && steps.resolve.outputs.in_sha != '' && github.event.inputs.dry_run != 'true'
env:
IN_SHA: ${{ steps.resolve.outputs.in_sha }}
run: |
set -euo pipefail
# Cargo.toml's path deps point at ../busbarAI, a SIBLING of this checkout. actions/checkout
# cannot write outside the workspace, and GetBusbar/busbar is public, so clone it directly.
sib="$(cd .. && pwd)/busbarAI"
rm -rf "$sib"
git clone --quiet --no-checkout https://github.com/GetBusbar/busbar.git "$sib"
git -C "$sib" checkout --quiet "${IN_SHA}"
# Plain cargo (NO --locked) performs the MINIMAL resolution: every existing registry pin is
# preserved and only what the new busbar graph actually requires is added or moved.
cargo metadata --format-version 1 >/dev/null
# Prove the refreshed lock satisfies the exact flag release.yml will build under, BEFORE we
# commit and tag. If this fails, no tag is pushed and no phantom release can be created.
cargo metadata --format-version 1 --locked >/dev/null
echo "::notice::Cargo.lock is fresh against busbar ${IN_SHA} and satisfies --locked"

- name: Record the new busbar ref in .busbar-ref
if: steps.guard.outputs.exists == 'no' && steps.resolve.outputs.in_sha != '' && github.event.inputs.dry_run != 'true'
env:
Expand All @@ -165,10 +195,12 @@ jobs:
run: |
set -euo pipefail
printf '%s %s\n' "${IN_SHA}" "${IN_VER}" > .busbar-ref
if git diff --quiet .busbar-ref; then
echo "::notice::.busbar-ref already at ${IN_SHA} ${IN_VER}, no commit needed"
# Cargo.lock rides along with the ref it was resolved against: the two must never be
# committed apart, or release.yml's --locked build sees a lock for a different busbar.
if git diff --quiet -- .busbar-ref Cargo.lock; then
echo "::notice::.busbar-ref already at ${IN_SHA} ${IN_VER} and Cargo.lock already fresh, no commit needed"
else
git add .busbar-ref
git add .busbar-ref Cargo.lock
git commit -m ".busbar-ref: record busbar ${IN_VER} (${IN_SHA}) for the ${TAG} release"
git push origin HEAD:main
fi
Expand Down
46 changes: 30 additions & 16 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,18 +31,25 @@ permissions:
jobs:
# Create the Release first so the parallel per-target upload jobs have something to attach to
# (uploading from a matrix without a pre-existing release races -> "release not found").
#
# It is created as a DRAFT. A draft is addressable by tag to `gh release upload` / `gh release
# view` with this token, but it is NOT returned by the public `releases/latest` or
# `releases/tags/<tag>` endpoints -- so while the build matrix is still running, and for good if it
# fails, nothing following `releases/latest` can observe an assetless release. `verify-assets`
# publishes it only once assets are actually attached, which is what makes a release here either
# complete or absent, never an empty shell.
create-release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- name: Create GitHub Release
- name: Create GitHub Release (draft)
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh release create "${GITHUB_REF_NAME}" \
--repo "${GITHUB_REPOSITORY}" \
--title "busbar-webrequest ${GITHUB_REF_NAME}" \
--verify-tag --generate-notes \
--draft --verify-tag --generate-notes \
|| gh release view "${GITHUB_REF_NAME}" --repo "${GITHUB_REPOSITORY}"

# One signed .tar.gz per target: {cdylib + manifest.json}, packed by busbar-plugin-pack and
Expand Down Expand Up @@ -153,19 +160,20 @@ jobs:
with:
subject-path: "plugin-dist/*.tar.gz"

# PHANTOM-RELEASE GUARD: assert the published Release actually carries assets before we treat this
# as a real release. The per-target build/upload jobs run with fail-fast:false, and `create-release`
# always makes the (initially empty) Release up front — so a build/pack failure on EVERY target
# (e.g. a stale Cargo.lock tripping `--locked`, as happened on the first v1.0.3 cut) leaves a tag +
# Release with ZERO assets: a "phantom" that silently breaks busbar's plugin-registry-gate. This job
# fails the whole release run loud if assets == 0, so a phantom can never ship (or notify marketing)
# unnoticed again. It depends on the build matrix but does NOT inherit its fail-fast:false — one
# green target is enough to have assets, but zero across the board must hard-fail here.
# PHANTOM-RELEASE GATE: this is the only step that turns the draft into a published release, and it
# does so only after proving assets are attached. The per-target build/upload jobs run with
# fail-fast:false and `create-release` makes the (initially empty) Release up front, so a build/pack
# failure on EVERY target (e.g. a stale Cargo.lock tripping `--locked`) leaves a Release with ZERO
# assets. Previously that Release was published immediately and such a failure shipped a "phantom":
# a tag whose `releases/latest` carried nothing, silently breaking busbar's plugin-registry-gate.
# Now the empty Release is a draft nothing can resolve, and this job either publishes it or fails
# the run with the draft left unpublished. It depends on the build matrix but does NOT inherit its
# fail-fast:false: one green target is enough to have assets, zero across the board must hard-fail.
verify-assets:
needs: [webrequest-plugin]
runs-on: ubuntu-latest
steps:
- name: Assert the Release has at least one asset
- name: Assert the Release has at least one asset, then publish it
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
Expand All @@ -175,13 +183,19 @@ jobs:
--json assets --jq '.assets | length')"
echo "Release ${GITHUB_REF_NAME} has ${count} asset(s)."
if [ "${count}" -eq 0 ]; then
echo "::error::PHANTOM RELEASE: ${GITHUB_REF_NAME} was published with 0 assets." \
"Every build/pack target failed to upload a tarball. Failing the release run so this" \
"tag is not mistaken for a real release by busbar's plugin-registry-gate. Fix the" \
"build (check Cargo.lock freshness vs --locked and the plugin cdylib build step)," \
"delete this tag+release, and re-cut." >&2
echo "::error::PHANTOM RELEASE PREVENTED: ${GITHUB_REF_NAME} has 0 assets." \
"Every build/pack target failed to upload a tarball. The release is still a DRAFT," \
"so it was never visible to anything following releases/latest and there is no" \
"phantom to clean up -- just fix the build (check Cargo.lock freshness vs --locked" \
"and the plugin cdylib build step) and re-run. Failing the release run." >&2
exit 1
fi
# Only now, with assets provably attached, does this stop being a draft and become the
# release that `releases/latest` resolves to.
gh release edit "${GITHUB_REF_NAME}" \
--repo "${GITHUB_REPOSITORY}" \
--draft=false --latest
echo "::notice::Published ${GITHUB_REF_NAME} with ${count} asset(s)."

# Instant marketing-site rebuild the moment this plugin ships a real release -- marketing's
# deploy.yml listens for this exact repository_dispatch event type (plus its own daily-poll
Expand Down
14 changes: 12 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions tests/e2e.rs
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,11 @@ fn cfg(url: &str) -> String {

fn req_with_prompt(text: &str) -> RoutingRequest<'static> {
RoutingRequest {
// busbar 1.5.3 added the correlation id and the declared-signal bag to the hook projection.
// These fixtures pin a fixed id and an EMPTY bag: no test here declares a signal, and an
// empty bag serialises to nothing, so the forwarded envelope is unchanged by their presence.
request_id: 1,
signals: busbar_api::SignalBag::new(),
pool: "p",
ingress_protocol: "anthropic",
requested_model: None,
Expand Down Expand Up @@ -299,6 +304,8 @@ fn cand(idx: usize) -> Candidate<'static> {
available_concurrency: 1,
budget_remaining: None,
rate_headroom: None,
// Same contract as RoutingRequest::signals above: empty unless a consumer declares one.
signals: busbar_api::SignalBag::new(),
}
}

Expand Down
9 changes: 7 additions & 2 deletions tests/full_stack_e2e.rs
Original file line number Diff line number Diff line change
Expand Up @@ -327,15 +327,20 @@ mockup:
// `auth.chain: []` (open relay) on purpose: this test's whole point is the admin-install ->
// plugin-load -> hook-invocation -> webhook round trip, not the client auth chain (covered
// elsewhere in busbar's own suite) — narrowing scope here keeps the failure surface honest.
//
// `admin-tokens` is DEFINED once under `identity-providers:` and REFERENCED by bare name from
// `auth.admin_auth`. busbar 1.5.3 retired the inline-module form this used to use, and refuses
// to boot a config still carrying it, so the inline shape would fail this test at startup.
let config_yaml = format!(
r#"
listen: "127.0.0.1:{data_port}"
admin_listen: "127.0.0.1:{admin_port}"
identity-providers:
admin-tokens: {{ module: admin-tokens, token: {{ env: BUSBAR_E2E_ADMIN_TOKEN }} }}
auth:
chain: []
signing_key: {{ env: BUSBAR_SIGNING_KEY }}
admin_auth:
- admin-tokens: {{ token: {{ env: BUSBAR_E2E_ADMIN_TOKEN }} }}
admin_auth: [admin-tokens]
plugins:
enabled: true
dir: "{plugins_dir}"
Expand Down
Loading