Pre-publish smoke matrix for every tagged release. All items must pass before pushing a version tag.
These run on every push and PR (.github/workflows/ci.yml,
.github/workflows/interop.yml):
-
cargo fmt --check -
python3 scripts/check-clippy-reasons.py -
python3 scripts/check-v1-stable-surface.py— stable config fields and object shape, native gRPC signatures/messages, CLI paths/JSON contracts, role classifications, compatibility floor, and the consecutive-release upgrade receipt still match their machine sources. -
cargo clippy --workspace --all-targets -- -D warnings -
cargo test --workspace -
cargo doc --workspace --lib --no-deps(warning denial comes from.cargo/config.tomlbuild.rustdocflags— don't set a differentRUSTDOCFLAGSenv, it forks the doc-cache fingerprint and forces a full workspace re-doc on the next differently-flagged run) - MSRV gate —
cargo check --workspace --all-targetsat the declaredrust-version(kept in lockstep with the Dockerfile builder version) - Wire crate README freshness gate — if
crates/wire/Cargo.tomlversion changed in the diff,crates/wire/README.mdmust also be touched - Gate 8b BUM-filter kernel primitive
(
evpn_bum_filter_kerneljob) — runs the netns harness under--cap-add=NET_ADMIN --cap-add=SYS_ADMIN --security-opt apparmor=unconfinedagainstubuntu-latest's 6.x kernel - Interop tier — the PR-gated foundation interop jobs in
.github/workflows/interop.yml(against FRR 10.3.1 via containerlab) are green; that workflow is the authoritative job set, so check it rather than re-listing M-numbers here.
Before tagging, skim the release and tracking docs for the low-conflict
convention in CONTRIBUTING.md:
-
CHANGELOG.md[Unreleased]entries are in the right subsections and read as compact per-PR entries rather than broad rewrites of older shipped text. -
ROADMAP.mdhas one row or checkbox per remaining concern; shipped slices say what landed and what remains. - Hot tracking docs such as
docs/evpn-alpha-soak.mdanddocs/evpn-enablement.mdupdate exact gates/rows instead of rewriting unrelated summary prose. - New config knobs follow
docs/config-knob-contributor-guide.md: schema, validation, reload matrix, runtime consumption, persistence, docs, and tests all move together. - Process-only documentation changes intentionally omit CHANGELOG entries unless they affect users or operators.
The project remains public alpha outside the explicit
rustbgpd-rs-rr-v1 inventory. Before tagging:
- Review every change to
docs/v1-stable-surface.jsonas a product compatibility decision, not a checksum refresh. No shipped alpha or experimental feature becomes stable merely because it exists. - Stable protobuf changes are additive; removed numbers/names remain reserved. Any changed stable service-signature digest has an explicit compatibility review.
- Stable config changes preserve names, types, effective defaults, required-field membership, and unknown-field handling, or carry the documented deprecation and migration window. Additive optional siblings remain permitted.
- rpol golden decision compatibility, stable metric names/types/label meanings, and stable event/JSON field meanings remain intact.
- The transaction path remains canonical for live compound mutation. SIGHUP wording remains file-driven reconcile with reload-matrix behavior, not an atomic compound-mutation guarantee.
- At least one consecutive-release upgrade fixture is accepted by the new
release. The latest receipt must end at the workspace release-line anchor
(
vMAJOR.MINOR.0), whilebaseline_releasemust match the exact workspace version. Receipts form one contiguous chain of adjacent minor release lines from the canonicalv0.50.0history origin, with an explicit transition into a new major; immutable files must still match their source git tag. The current workspace target is proven by its parser test before tagging; both tags are required once an exercise is historical. Add a new receipt for a real migration; do not overwrite older evidence. - Inventoried CLI commands preserve their path and command name. Flags, positional arguments, defaults, exit behavior, and human output are not covered unless separately inventoried.
The crates/api/src/authz.rs METHODS matrix is the code-level source
of truth for per-method risk tiers (ADR-0064). The matrix tests prove
the matrix and the proto stay in sync and that the counts match the
published inventory — but they cannot prove a tier assignment is
correct, only that one exists. Tier correctness is a release-review
judgment. Before tagging, for every method added since the last
release:
-
cargo test -p rustbgpd-api authzis green — this enforces: matrix ↔proto/rustbgpd.protomembership (every RPC covered exactly once),docs/grpc-method-inventory.jsonmatches the matrix, and the per-tier counts match. -
docs/grpc-method-inventory.mdwas regenerated by hand to match the.json(the.jsonis test-enforced; the.mdis not, so it drifts silently if you forget). -
No method sits at
readunless it is pure liveness with zero topology / route / policy / state disclosure. The tier is currently empty by design — anything read-only that exposes the network belongs atsensitive_read. A new method landing atreadis the most likely under-tiering mistake; scrutinize it. -
Every event / explain / route-listing surface is
sensitive_read+ —WatchEvents,SubscribeFromEvent,List*Events,Explain*,List*Routes, gNMISubscribe. These disclose routing, topology, or decision detail and must never beread. -
Anything that injects into the dataplane/network, mutates process-wide or daemon-wide state, or is otherwise high-blast is
operator_only— route/FlowSpec/EVPN injection,SetGlobal*, global policy chains,Shutdown,SetGracefulShutdown. -
Dataplane-programming guardrail. Any new RPC that programs the kernel dataplane (FIB / VXLAN / FDB / L3VNI / nexthop groups) must have its tier explicitly justified in review — it must not land in
mutatingby inattention. Three methods deliberately sit atMutatingdespite touching the kernel dataplane, each guarded by a tier-pin test incrates/api/src/authz.rs: -EvpnService/ApplyEvpnRuntime— ADR-0063 v1 is a single validated, additive L2VNI/IP-VRF apply (the EVPN sibling ofAddNeighbor). If its scope widens past single-add (issue #210), re-evaluate its tier via an ADR update — not the pin. -RibService/SetFibTableandRibService/DeleteFibTable— validated, SIGHUP-serialized, reconciler-acked, persisted config-surface mutations that back-fill already-learned routes (the unicast sibling of dynamic-neighbor CRUD), not operator-authored injection. ADR-0074 records this decision and explicitly considered and rejected a dedicateddataplane_mutatingtier (no ADR-0064 role maps to it;operator_onlywould over-grant FIB automation). If their scope widens past back-filling learned routes, re-evaluate via an ADR-0074 update — not the pin.For any *fourth* such method, start from ADR-0074's split: a validated, persisted config surface that directs already-learned routes may still fit `mutating`; operator-authored injection is `operator_only`. Reach for a dedicated `dataplane_mutating` tier (captured in an ADR) only if a real case falls cleanly between those two. -
Tier ↔ role boundary is intentional — recall
Observer ≤ sensitive_read,Automation ≤ mutating,Operator ≤ operator_only. Ask: should the Automation role be able to call this new method unattended? If not, it is notmutating.
Run these from a clean build (cargo build --workspace --release) before
tagging. The --workspace flag is required to build both rustbgpd and
rbgp.
# Build both binaries
cargo build --workspace --release
# Start daemon with minimal config
./target/release/rustbgpd examples/minimal/config.toml &
DAEMON_PID=$!
sleep 2
# Verify CLI commands parse and connect
export RUSTBGPD_ADDR=unix:///tmp/rustbgpd/grpc.sock
./target/release/rbgp health
./target/release/rbgp global
./target/release/rbgp neighbor
./target/release/rbgp rib
./target/release/rbgp metrics
kill $DAEMON_PID# Each built-in profile emits a starter config that loads cleanly.
for p in lab edge; do
./target/release/rustbgpd --init-config "$p" --stdout > "/tmp/init-$p.toml"
./target/release/rustbgpd --check "/tmp/init-$p.toml" # expect: config OK
done
# Guards must exit non-zero:
./target/release/rustbgpd --stdout # --stdout without --init-config
./target/release/rustbgpd --init-config lab --stdout --check x # --init-config combined with --check/--diffWalk the exact README quickstart from a clean tree and confirm:
- the minimal config validates with
--check - the daemon creates the UDS socket under
/tmp/rustbgpd rbgp health,global, andneighborsucceed withRUSTBGPD_ADDR- no undocumented prerequisite or manual workaround is needed
Verify the daemon creates the gRPC socket at the configured
runtime_state_dir:
ls -la /tmp/rustbgpd/grpc.sock # should exist after daemon startOn a Linux host with iproute2, ping, and namespace privileges
(CAP_NET_ADMIN + CAP_SYS_ADMIN), run the privileged EVPN Linux
bundle:
sudo -E env "PATH=$PATH" bash scripts/test-evpn-linux-netns.shThis wrapper runs the gated BUM, single-dst VTEP FDB, L3, FDB-NHG,
and raw nexthop_raw netns tests sequentially with
EVPN_LINUX_NETNS=1. If prerequisites are missing, it exits before
cargo with the failing ip netns add diagnostic.
echo "test-token-value" > /tmp/rustbgpd-token
cat > /tmp/rustbgpd-auth-test.toml <<'EOF'
[global]
asn = 65001
router_id = "10.0.0.1"
listen_port = 1179
runtime_state_dir = "/tmp/rustbgpd-auth"
[global.telemetry]
prometheus_addr = "127.0.0.1:19179"
log_format = "json"
[global.telemetry.grpc_uds]
path = "/tmp/rustbgpd-auth/grpc.sock"
token_file = "/tmp/rustbgpd-token"
# This smoke isolates bearer-token authn; legacy enforcement keeps it
# independent of tier role config (tier additionally needs grpc_uds.principal
# plus a matching [security.grpc.roles] entry).
[security.grpc]
enforcement = "legacy"
EOF
./target/release/rustbgpd /tmp/rustbgpd-auth-test.toml &
DAEMON_PID=$!
sleep 2
# Without token — should fail
./target/release/rbgp -s unix:///tmp/rustbgpd-auth/grpc.sock health 2>&1 | grep -i "error\|unauthenticated"
# With token — should succeed
./target/release/rbgp -s unix:///tmp/rustbgpd-auth/grpc.sock --token-file /tmp/rustbgpd-token health
kill $DAEMON_PID
rm -rf /tmp/rustbgpd-auth /tmp/rustbgpd-token /tmp/rustbgpd-auth-test.tomlWhen ADR-0064 changes land, capture one request against a test listener and confirm both audit surfaces move:
# With the token-auth daemon above still running:
./target/release/rbgp -s unix:///tmp/rustbgpd-auth/grpc.sock \
--token-file /tmp/rustbgpd-token health
# Logs should include target="grpc_authz" with path, tier, result, authn,
# access_mode, and principal fields.
# Metrics should expose bounded-label decision counters, with no method path or
# principal labels:
curl -fsS http://127.0.0.1:19179/metrics | grep bgp_grpc_authz_decisions_totalFor external audit prep, verify the current method inventory and threat model:
cargo test -p rustbgpd-api authz
test -s docs/grpc-method-inventory.md
test -s docs/adr/0064-threat-model.mdRun at least one from each category:
docker build --target dev -t rustbgpd:dev .
# Basic eBGP + RIB
containerlab deploy -t tests/interop/m4-frr.clab.yml
bash tests/interop/scripts/test-m4-frr.sh
containerlab destroy -t tests/interop/m4-frr.clab.yml
# Route server + policy
containerlab deploy -t tests/interop/m13-policy-frr.clab.yml
bash tests/interop/scripts/test-m13-policy-frr.sh
containerlab destroy -t tests/interop/m13-policy-frr.clab.yml
# Graceful Restart
containerlab deploy -t tests/interop/m11-gr-frr.clab.yml
bash tests/interop/scripts/test-m11-gr-frr.sh
containerlab destroy -t tests/interop/m11-gr-frr.clab.yml
# Extended next-hop / IPv6 export path
containerlab deploy -t tests/interop/m18-extnexthop-frr.clab.yml
bash tests/interop/scripts/test-m18-extnexthop-frr.sh
containerlab destroy -t tests/interop/m18-extnexthop-frr.clab.ymlIf the release includes recent LLGR changes, also run:
containerlab deploy -t tests/interop/m16-llgr-frr.clab.yml
bash tests/interop/scripts/test-m16-llgr-frr.sh
containerlab destroy -t tests/interop/m16-llgr-frr.clab.ymlIf the release includes EVPN changes (any commit touching
crates/wire/src/evpn.rs, crates/wire/src/pmsi.rs, EVPN paths in
crates/rib/src/, the EVPN gRPC surface, crates/evpn-linux/src/,
crates/evpn/src/origination.rs, src/evpn_dataplane.rs,
src/evpn_originator.rs, or src/evpn_imet.rs), run at least one of
M29 (capability sanity) or M30 (real Type 2 reflection). Run M33
(scale) before any release that claims new performance numbers:
# Capability sanity — fastest EVPN smoke
containerlab deploy -t tests/interop/m29-evpn-rr-frr.clab.yml
bash tests/interop/scripts/test-m29-evpn-rr-frr.sh
containerlab destroy -t tests/interop/m29-evpn-rr-frr.clab.yml
# Real Type 2 MAC reflection through kernel VXLAN — full RR confidence
containerlab deploy -t tests/interop/m30-evpn-type2-frr.clab.yml
bash tests/interop/scripts/test-m30-evpn-type2-frr.sh
containerlab destroy -t tests/interop/m30-evpn-type2-frr.clab.yml
# Scale + churn (50k Type 2 routes, in-tree load generator)
containerlab deploy -t tests/interop/m33-evpn-scale.clab.yml
bash tests/interop/scripts/test-m33-evpn-scale.sh
containerlab destroy -t tests/interop/m33-evpn-scale.clab.yml --cleanupIf the release touches the VTEP dataplane (crates/evpn-linux/)
or local-MAC origination (crates/evpn/src/origination.rs,
src/evpn_originator.rs, src/evpn_imet.rs,
crates/wire/src/pmsi.rs), additionally run M36 (downward, Gate 7b)
and M37 (upward, Gate 7b+1) before tagging. If it touches the
ADR-0079 adoption/reap sweep (crates/evpn-linux/src/reconcile.rs),
M60 (kill-and-restart FDB adoption sweep) and M61 (kill-and-restart
L3 adoption sweep) run in the hosted Kernel Dataplane workflow and
can be reproduced manually the same way. M36 and M37 run in the hosted
Kernel Dataplane workflow; manual reproduction still requires
CAP_NET_ADMIN or a privileged runner:
# Build the daemon image (bidirectional VTEP needs CAP_NET_ADMIN)
docker build --target dev -t rustbgpd:dev .
# M36 — Gate 7b downward path: rustbgpd-as-VTEP, FRR-as-originator
containerlab deploy -t tests/interop/m36-evpn-vtep-smoke.clab.yml
bash tests/interop/scripts/test-m36-evpn-vtep-smoke.sh
containerlab destroy -t tests/interop/m36-evpn-vtep-smoke.clab.yml
# M37 — Gate 7b+1 upward path: rustbgpd-as-originator, FRR-as-consumer
# Validates ADR-0055 §1-§6 end-to-end (RTNLGRP_NEIGH classifier,
# Type 2 origination per RFC 7432 §15.1, Type 3 IMET with PMSI Tunnel
# label = raw 24-bit VNI per RFC 8365 §5.1.3, shutdown drain ordering)
containerlab deploy -t tests/interop/m37-evpn-local-origination.clab.yml
bash tests/interop/scripts/test-m37-evpn-local-origination.sh
containerlab destroy -t tests/interop/m37-evpn-local-origination.clab.ymlIf the release touches Gate 7b+2 (MAC-with-IP Type 2 via ARP/ND
suppression — crates/evpn/src/origination_macip.rs), also run
M37+IP. If the release touches Gate 8 / 8b (Type 1/4 origination
in crates/evpn/src/origination_es.rs, DF election in
crates/evpn/src/df_election.rs, ESI Label / ES-Import RT extcomms,
aliasing in crates/evpn/src/aliasing.rs, mass-withdraw in
crates/evpn/src/mass_withdraw.rs, or BUM-port enforcement), run M38
to validate DF election + Type 1/4 origination against a peer running
the same code. If the release touches Gate 9 / ADR-0059 / ADR-0087 /
ADR-0090 (IP-VRF, Type 5, L3 FIB programming, overlay-index
recursion/origination, aliasing ECMP, or FDB nexthop groups), run the hosted
Kernel Dataplane workflow for M39, M40, M68, M71, and future M72 as
appropriate. If it touches
ADR-0089 VLAN-aware bridge or SVD programming, also require M70 plus
the dataplane_vlan_fdb and svd_fdb_vni netns selectors. They can still be
reproduced manually with:
# M37+IP — Gate 7b+2 MAC-with-IP Type 2 via ARP/ND suppression
containerlab deploy -t tests/interop/m37-evpn-mac-ip-origination.clab.yml
bash tests/interop/scripts/test-m37-evpn-mac-ip-origination.sh
containerlab destroy -t tests/interop/m37-evpn-mac-ip-origination.clab.yml
# M38 — Gate 8 observable DF election with two VTEPs sharing an ESI
containerlab deploy -t tests/interop/m38-evpn-df-election.clab.yml
bash tests/interop/scripts/test-m38-evpn-df-election.sh
containerlab destroy -t tests/interop/m38-evpn-df-election.clab.yml
# M39 — Gate 9 symmetric Interface-less IRB Type 5 datapath
containerlab deploy -t tests/interop/m39-evpn-type5-symmetric-irb.clab.yml
bash tests/interop/scripts/test-m39-evpn-type5-symmetric-irb.sh
containerlab destroy -t tests/interop/m39-evpn-type5-symmetric-irb.clab.yml
# M40 — ADR-0059 aliasing ECMP via FDB nexthop groups
containerlab deploy -t tests/interop/m40-evpn-aliasing-ecmp-frr.clab.yml
bash tests/interop/scripts/test-m40-evpn-aliasing-ecmp-frr.sh
containerlab destroy -t tests/interop/m40-evpn-aliasing-ecmp-frr.clab.yml
# M68 — ADR-0087 GW-IP overlay-index Type 5 consumed by FRR
containerlab deploy -t tests/interop/m68-evpn-type5-gwip-overlay-index-frr.clab.yml
bash tests/interop/scripts/test-m68-evpn-type5-gwip-overlay-index-frr.sh
containerlab destroy -t tests/interop/m68-evpn-type5-gwip-overlay-index-frr.clab.yml
# M70 — ADR-0089 VLAN-aware bridge FDB attribution consumed from FRR
containerlab deploy -t tests/interop/m70-evpn-vlan-aware-bridge-frr.clab.yml
bash tests/interop/scripts/test-m70-evpn-vlan-aware-bridge-frr.sh
containerlab destroy -t tests/interop/m70-evpn-vlan-aware-bridge-frr.clab.yml --cleanup
# M71 — RFC 9136 §4.3 ESI overlay-index Type 5 single-active receive
containerlab deploy -t tests/interop/m71-evpn-esi-overlay-type5-receive-gobgp.clab.yml
bash tests/interop/scripts/test-m71-evpn-esi-overlay-type5-receive-gobgp.sh
containerlab destroy -t tests/interop/m71-evpn-esi-overlay-type5-receive-gobgp.clab.yml --cleanupIf the release touches ADR-0061 / ADR-0066 / ADR-0068 general unicast FIB
(src/fib.rs, src/fib_runtime.rs, [[fib_tables]], ListFibRoutes,
rbgp rib fib, ECMP caps, multipath_relax, or weighted multipath), run
the hosted Kernel Dataplane workflow for the relevant FIB suites: M42 for
base configured-table install, M50 for ECMP, and M52 for multipath-relax. Manual
reproduction:
# M42 — ADR-0061 configured-table unicast Linux FIB runtime
docker build --target dev -t rustbgpd:dev .
containerlab deploy -t tests/interop/m42-fib-runtime-frr.clab.yml
bash tests/interop/scripts/test-m42-fib-runtime-frr.sh
containerlab destroy -t tests/interop/m42-fib-runtime-frr.clab.yml
# M50 — ADR-0066 ECMP FIB install
containerlab deploy -t tests/interop/m50-fib-ecmp-frr.clab.yml
bash tests/interop/scripts/test-m50-fib-ecmp-frr.sh
containerlab destroy -t tests/interop/m50-fib-ecmp-frr.clab.yml
# M52 — ADR-0066 multipath-relax
containerlab deploy -t tests/interop/m52-fib-ecmp-relax-frr.clab.yml
bash tests/interop/scripts/test-m52-fib-ecmp-relax-frr.sh
containerlab destroy -t tests/interop/m52-fib-ecmp-relax-frr.clab.ymlIf the release touches ADR-0067 BFD (crates/bfd, src/bfd_runtime.rs,
[[bfd_profiles]], [neighbors.bfd], BfdService, rbgp bfd, or BFD
events / coupling), run the hosted Kernel Dataplane workflow for M51.
Manual reproduction:
# M51 — single-hop BFD + RFC 5882 coupling against FRR bfdd
docker build --target dev -t rustbgpd:dev .
containerlab deploy -t tests/interop/m51-bfd-frr.clab.yml
bash tests/interop/scripts/test-m51-bfd-frr.sh
containerlab destroy -t tests/interop/m51-bfd-frr.clab.ymlAlso smoke the controller-injection path against a live RR (M30 container is fine):
# Inject a Type 2 route via gRPC, list it back, withdraw it
RR_ADDR=$(docker inspect clab-m30-evpn-type2-frr-rustbgpd \
--format '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}'):50051
./target/release/rbgp -s "$RR_ADDR" evpn add-mac-ip \
--rd 65000:100 --mac 02:00:00:aa:bb:cc \
--label 100 --next-hop 10.0.0.1
./target/release/rbgp -s "$RR_ADDR" evpn --route-type 2
./target/release/rbgp -s "$RR_ADDR" evpn delete-mac-ip \
--rd 65000:100 --mac 02:00:00:aa:bb:ccIf the release includes RPKI/RTR, FlowSpec, or best-path explain changes:
# RPKI/RTR cache interop (requires rpki/stayrtr:latest)
containerlab deploy -t tests/interop/m21-rpki-frr.clab.yml
bash tests/interop/scripts/test-m21-rpki-frr.sh
containerlab destroy -t tests/interop/m21-rpki-frr.clab.yml
# FlowSpec injection + distribution
containerlab deploy -t tests/interop/m22-flowspec-frr.clab.yml
bash tests/interop/scripts/test-m22-flowspec-frr.sh
containerlab destroy -t tests/interop/m22-flowspec-frr.clab.yml
# GoBGP peer (requires gobgp:interop image)
docker build -t gobgp:interop -f tests/interop/Dockerfile.gobgp tests/interop/
containerlab deploy -t tests/interop/m23-gobgp.clab.yml
bash tests/interop/scripts/test-m23-gobgp.sh
containerlab destroy -t tests/interop/m23-gobgp.clab.ymldocker build --target dev -t rustbgpd:dev .
# Verify shipped binaries are present
docker run --rm --entrypoint sh rustbgpd:dev -c \
"ls /usr/local/bin/rustbgpd /usr/local/bin/rbgp"
# Verify rbgp parses subcommands
docker run --rm --entrypoint rbgp rustbgpd:dev --help- Update
CHANGELOG.mdwith the new version section - Verify changelog completeness: run
git log <prev-tag>..HEAD --onelineand confirm every user-visible change (features, fixes, interop suites) is listed under the new version — not misattributed to a prior release. Check that stale counts (test totals, interop suite counts, script counts) are updated in CHANGELOG.md, README.md, and ROADMAP.md. Also sweep(post-vX.Y.Z)annotations in ROADMAP.md and the Maturity row in README.md. - Bump versions:
- Root
Cargo.toml:[workspace.package] versionplus every internalrustbgpd-*pin in[workspace.dependencies]. Library crates that publish independently, such asrustbgpd-wire, may carry bothpathandversionso downstream publish dry-runs resolve from crates.io; do not remove those version pins during the workspace bump. crates/wire/Cargo.toml: bump only ifcrates/wire/src/changed since the last wire publish (see semver rules in the next section). Land the wire bump in its own commit before the workspace bump so the wire publish is reproducible from the commit alone.
- Root
- Run the full checklist above (fmt, clippy
-D warnings, test, doc-D warnings, release build) - Commit (workspace):
release: prep vX.Y.Z — bump workspace, roll CHANGELOG - Annotated tag (lightweight tags break the release-history convention
here):
git tag -a vX.Y.Z -m "vX.Y.Z — <one-line headline>" - Push:
git push origin main && git push origin vX.Y.Z - Verify CI passes on the tag (build matrix x86_64 + aarch64, doc, GHCR
push, release.yml binary build + GitHub Release creation). Both
publication workflows are fail-closed:
release.ymlandcontainer.ymleach gate publication on averify-tag-versionjob (tagvX.Y.Zmust equal the workspace[workspace.package]version) and atestjob (cargo test --workspaceon the tagged commit). If you tagged a commit without the version bump, the tag build fails and publishes nothing — fix the bump, delete the tag, and re-tag. - Verify container image published to GHCR via
docker/metadata-action's semver pattern:ghcr.io/lance0/rustbgpd:X.Y.Z(exact, immutable)ghcr.io/lance0/rustbgpd:X.Y(rolls forward within the minor)ghcr.io/lance0/rustbgpd:latest(auto-published for non-prerelease tags via the action's defaultlatest=autoflavor) These container-image tags are emitted without thevprefix —0.45.0, notv0.45.0(docker/metadata-actionstrips it). The git tag staysvX.Y.Z(step 6); only the image tag drops thev.
- Verify release tarballs under
GitHub Releases — each
tag should publish version-less
rustbgpd-linux-amd64.tar.gzandrustbgpd-linux-arm64.tar.gzplus per-archchecksums-<arch>.txt. Each tarball containsrustbgpd,rbgp,LICENSE-MIT, andLICENSE-APACHE(license presence is asserted by the workflow; the runtime image likewise ships both licenses at/). The version-less filenames are what powers the staticreleases/latest/download/URLs indocs/deployment.md; if the filenames drift, deployment.md silently breaks for new operators. - Verify GitHub release notes: check that the release created by CI has
accurate notes. The
releaseworkflow extracts the matching## [X.Y.Z]block out ofCHANGELOG.mdvia awk and fails the tag build if the section is missing; if GitHub shows the auto-generated commit list anyway, fix the CHANGELOG heading and either re-tag or edit the release body.
The wire crate has its own version in crates/wire/Cargo.toml, decoupled
from the daemon workspace version. Only publish when the wire crate itself
changed.
- Did
crates/wire/change since the last wire publish?- If no: skip. Do not publish a no-op release.
- If yes: continue.
- Decide semver bump (see below)
- Update
versionincrates/wire/Cargo.toml - Add a
rustbgpd-wireentry inCHANGELOG.md cargo publish -p rustbgpd-wire --dry-runcargo publish -p rustbgpd-wire
Wire crate semver:
- Patch: bug fixes, stricter validation, docs/test improvements
- Minor: new message types, attributes, helper methods, additive API changes
- Major: breaking API changes, changed method signatures, enum shape changes
The FSM crate has its own version in crates/fsm/Cargo.toml, decoupled from
the daemon workspace version. Only publish when the pure FSM API or docs change;
do not force an FSM release for every daemon tag.
- Did
crates/fsm/or its public examples/docs change since the lastrustbgpd-fsmpublish?- If no: skip. Do not publish a no-op release.
- If yes: continue.
- Decide semver bump:
- Patch: bug fixes, docs/test improvements, no public API expansion.
- Minor: additive events/actions/helpers or new non-breaking negotiation surfaces.
- Major: changed method signatures, removed variants, or enum/struct
shape changes not protected by
#[non_exhaustive].
- Update
versionincrates/fsm/Cargo.toml - Add a
rustbgpd-fsmentry inCHANGELOG.md cargo publish -p rustbgpd-fsm --dry-runcargo publish -p rustbgpd-fsm