Skip to content

comments: say what the software does, not how it was tested #554

comments: say what the software does, not how it was tested

comments: say what the software does, not how it was tested #554

Workflow file for this run

name: CI
on:
push:
branches: [main, dev, qa]
pull_request:
# Without this, every push queues a fully independent run that competes for the same runners
# instead of cancelling the one it superseded -- qa-gate.yml already has this; ci.yml never did,
# so a burst of rapid pushes (e.g. iterating on a CI fix) pays for N full runs instead of 1.
concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true
env:
CARGO_TERM_COLOR: always
RUSTFLAGS: "-D warnings"
jobs:
# LAYOUT GATE — its OWN job, deliberately. It used to be the first step of `check`, ahead of
# clippy/build/test, which made it a MASK: any layout violation aborted the job before a single
# test ran, and the `Test` step is the only place BUSBAR_TEST_POSTGRES_URL / VALKEY_URL are set
# against the service containers, so the two 1.5.0 store backends' live-DB coverage — the coverage
# the comment on `services:` calls out as load-bearing, hardened to HARD-FAIL rather than skip —
# silently stopped executing on every push and PR. A layout debt and a broken test are independent
# facts about a commit; ordering them in one job means the second is unobservable until the first
# is paid off. Both jobs are required checks, and neither can now hide the other.
structure-lint:
name: structure lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
# The lint's `#[cfg(test)]` scope scanner decides which lines are EXEMPT from every
# choke-point bypass rule, so a scanner that can be lied to reports "no bypass" while the
# bypass sits in production. Its self-test runs FIRST: never trust the lint's verdict before
# proving the lint still works.
- name: Structure lint self-test
run: scripts/structure-lint.sh --selftest
- name: Structure lint
run: scripts/structure-lint.sh
# RELEASE-SCRIPT lint — durable guard against the 1.5.2 gate's 2h31m hang: a backgrounded
# server (serve_forever) whose stdout was NOT redirected, captured via `$(...)`, held the
# substitution's pipe open until the job timeout. Self-test runs FIRST (never trust the lint's
# verdict before proving the scanner still catches the real antipattern), then it scans
# scripts/release-check*.sh and verifies the 1.5.2 watchdog is intact.
- name: Release-script lint self-test
run: scripts/release-script-lint.sh --selftest
- name: Release-script lint
run: scripts/release-script-lint.sh
# RESPONSE-HEADER lint: every busbar-INJECTED response header
# (`Server-Timing: busbar;dur=`, `x-busbar-route-policy`/`-target`) must be emitted from its ONE
# sanctioned, config-gated site — never a hand-rolled second emission that bypasses the
# `advanced.response_headers` opt-in. Self-test runs FIRST (never trust the lint's verdict before
# proving the scanner still catches a real bypass), then it scans crates/busbar/src.
- name: Response-header lint self-test
run: scripts/response-header-lint.sh --selftest
- name: Response-header lint
run: scripts/response-header-lint.sh
# TRACING-SEAM lint: every `#[tracing::instrument]` must carry an explicit
# `level =` so a hot-path span can never again silently default to INFO (always-on). Self-test
# runs FIRST (never trust the lint's verdict before proving the scanner still catches a real
# bypass), then it scans crates/**/*.rs.
- name: Tracing lint self-test
run: scripts/tracing-lint.sh --selftest
- name: Tracing lint
run: scripts/tracing-lint.sh
# SETTINGS-LEAK lint: an admin-facing projection may serve an opaque `settings:` bag's KEY
# NAMES (`settings_keys` / `service::redact_settings_bags`) but NEVER its values — that bag is
# where an operator's credentials legitimately live and the reads are READ-ONLY scope. Added
# after the SAME defect was found in FOUR independent projections (see the script header).
# Self-test runs FIRST (never trust the lint's verdict before proving the scanner still catches
# a real leak), then it scans crates/busbar/src/admin.
- name: Settings-leak lint self-test
run: scripts/settings-leak-lint.sh --selftest
- name: Settings-leak lint
run: scripts/settings-leak-lint.sh
# BLOCKING-FFI lint (1.5.3): a synchronous call into a dlopened PLUGIN (`transport_call`, and
# the `dlopen` + constructor before it) must never run inline in an `async fn` — one such call
# parks a Tokio worker for the plugin's full network timeout, and N concurrent callers stop the
# runtime polling anything, `/healthz` included. Added after the SAME defect was found in five
# independent places, the last of them on the ANONYMOUSLY-reachable `/auth/token` (see the
# script header). Self-test runs FIRST (never trust the lint's verdict before proving the
# scanner still catches a real inline call), then it scans crates/busbar/src.
- name: Blocking-FFI lint self-test
run: scripts/blocking-ffi-lint.sh --selftest
- name: Blocking-FFI lint
run: scripts/blocking-ffi-lint.sh
# THE REGISTRY GATE (plugins.yaml is the single source of truth for first-party plugins):
# red when a registered plugin lacks coverage anywhere — no qa-gate checkout, no
# release-check phase, no published release (or a phantom release with zero assets) — or
# when a plugin-shaped org repo exists unregistered. Found necessary during the 1.5.0 ship:
# the plugin list was duplicated across 5+ places and the "full plugin gate" silently ran
# 6 of 8. Adding a plugin = one plugins.yaml entry; this gate stays red until every
# consumer actually covers it.
- name: Plugin registry gate
env:
GH_TOKEN: ${{ github.token }}
run: scripts/plugin-registry-check.sh
# QA-GATE SEGMENTATION self-test. qa/segments.toml is the umbrella's single
# source of truth (registry-driven fan-out, mirroring plugins.yaml). This self-test proves the
# manifest's SHAPE before any segment is trusted to run: it lists both active and reserved
# entries, the preserved core-data-plane/plugins coverage is present (union ⊇ today's gate),
# every reserved slot is defined-but-inert (PASS/SKIP), and every segment names a run command.
# Same "prove the gate before you trust its verdict" discipline as the lints above.
- name: qa-gate segmentation self-test
run: scripts/qa-segments.sh --selftest
check:
name: fmt · clippy · build · test
runs-on: ubuntu-latest
# Live-DB service containers for the store-postgres / store-valkey roundtrip tests. Without these
# the roundtrip tests skip (their URLs are unset) and the two 1.5.0 store backends ship with ZERO
# CI coverage of the delete_key cascade + credential cleanup. The tests read BUSBAR_TEST_POSTGRES_URL
# / VALKEY_URL (set on the Test step below) and, because `CI` is set in Actions, HARD-FAIL rather
# than silently skip if a service is misconfigured - so this coverage cannot vanish unnoticed.
services:
postgres:
image: postgres:16
env:
POSTGRES_USER: busbar
POSTGRES_PASSWORD: busbar
POSTGRES_DB: busbar_test
ports:
- 5432:5432
# Wait until the DB accepts connections before the job's steps run.
options: >-
--health-cmd "pg_isready -U busbar"
--health-interval 10s
--health-timeout 5s
--health-retries 5
valkey:
image: valkey/valkey:8
ports:
- 6379:6379
options: >-
--health-cmd "valkey-cli ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5
# NOTE: there used to be a `vault:` service here for busbar-secret-vault /
# busbar-secret-vault-plugin's live-Vault tests. That coverage moved with the crates to
# GetBusbar/hashicorp-vault (its own ci.yml boots the same real hashicorp/vault dev-mode
# container) — see docs/plugins.md and scripts/release-check.sh's Vault phase for how the
# monorepo now gates on that repo's own test suite via a sibling checkout instead.
steps:
- uses: actions/checkout@v7
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt, clippy
- name: Cache cargo
uses: Swatinem/rust-cache@v2
- name: Format check
run: cargo fmt --all -- --check
- name: Clippy
run: cargo clippy --workspace --all-targets --locked -- -D warnings
- name: Build
run: cargo build --workspace --locked --verbose
- name: Test
# Point the store-postgres / store-valkey roundtrip tests at the service containers above so
# they RUN (not skip) in CI. The services publish on localhost via the mapped ports.
env:
BUSBAR_TEST_POSTGRES_URL: postgres://busbar:busbar@localhost:5432/busbar_test
VALKEY_URL: redis://localhost:6379
run: cargo test --workspace --locked --verbose
# OpenAPI schema gate (CI-ONLY `openapi-schema` feature). `openapi_doc()` derives typed response
# schemas via schemars — a dependency deliberately kept OUT of the shipped binary (the `check` job
# above builds/tests WITHOUT the feature, proving that). This job compiles the feature, lints it,
# and runs the openapi tests: the DRIFT GUARD (`openapi_json_matches_committed_file`) fails the PR
# if the committed `openapi.json` — the file the runtime serves via `include_str!` — no longer
# matches what the code generates, and the COVERAGE LOCK proves every operation has a typed body.
# Regenerate a stale file with:
# UPDATE_OPENAPI=1 cargo test -p busbar --features openapi-schema openapi_json_matches_committed_file
openapi-schema:
name: openapi-schema clippy · drift · coverage
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: dtolnay/rust-toolchain@stable
with:
components: clippy
- uses: Swatinem/rust-cache@v2
- name: Clippy (--features openapi-schema)
run: cargo clippy -p busbar --all-targets --features openapi-schema --locked -- -D warnings
- name: OpenAPI tests (drift guard + coverage lock)
run: cargo test -p busbar --features openapi-schema --locked openapi -- --nocapture
# CONFIG-STABILITY gate. 1.5.3 is the LAST config-breaking release; after
# it the config grammar is FROZEN and every future feature may add only NEW OPTIONAL keys/sections/
# enum-variants. This job ENFORCES that per-PR, exactly like the openapi drift guard above but with
# the mechanic that guard lacks: an ADDITIVE-ONLY classifier. The self-test runs FIRST (never trust
# the gate's verdict before proving its RED/GREEN discipline still holds — like every sibling lint),
# then the gate: (1) DRIFT — the committed config-schema.snapshot.json must byte-match a fresh render
# of the config surface (a config-type change that forgot to regen fails loud with UPDATE_CONFIG_SCHEMA=1);
# (2) ADDITIVE-ONLY — the committed baseline (read from a git ref, NEVER the working tree, so a
# snapshot refresh cannot launder a break) vs the fresh render, classified: new optional field / new
# section / enum-append = OK; field removed/retyped, newly-required, enum-drop = FAIL naming the field.
config-stability:
name: config-stability gate (frozen grammar · additive-only)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
with:
# Need history so the additive check can diff against the PR base (not just the tip).
fetch-depth: 0
# On a PR, the anti-launder baseline is the BASE branch's snapshot (a break committed alongside
# a refreshed snapshot on the PR tip is still caught against the base). On a push, HEAD is the
# baseline (no-op delta). schemars is NOT needed — the gate is a stdlib-python fingerprint, so
# this job needs no Rust toolchain and stays cheap.
- name: Config-stability gate self-test (prove RED/GREEN before trusting the verdict)
run: scripts/config-stability-gate.sh --selftest
- name: Config-stability gate (drift + additive-only)
env:
CONFIG_SCHEMA_BASELINE_REF: ${{ github.base_ref && format('origin/{0}', github.base_ref) || 'HEAD' }}
run: scripts/config-stability-gate.sh --check
# PUBLIC-HYGIENE gate. busbar is sold to enterprises, and this repo, the docs site and the
# generated openapi.json are all public. A customer who finds an internal tracking id, a developer's
# home directory, test-process narration or an authorship trailer in a shipped file does not
# conclude "untidy" — they conclude the product was assembled by a process they were not shown.
# That is a commercial fact about the product, so it gets a control rather than a sweep.
#
# Each of these is a TEXT class. A manual sweep removes instances; this removes the class, and
# keeps it removed.
#
# Twelve rules, each derived from text really found in this repo's history, each carrying its own
# RED fixture AND a GREEN twin. The false-positive controls are as load-bearing as the rules — the
# vendor names this gateway routes to (Anthropic, OpenAI, Claude, Gemini), a genuine human
# `Co-authored-by:` trailer, technical invariants like `fail closed`, and identifiers that merely
# contain a flagged word are all explicit GREEN controls — because a gate that cries wolf gets
# switched off, and then it protects nothing.
#
# No toolchain and no build: it is pure stdlib python over the file list `git ls-files` reports,
# which is the exact definition of "what the public gets". Self-test FIRST, and a scan that
# discovers ZERO files is a HARD FAILURE, so neither a broken rule table nor a mistyped path can
# read as "clean". The same script runs over every PLUGIN repo via the plugin-ci reusable workflow.
public-hygiene:
name: public-hygiene gate (nothing public may describe how it was built)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- name: Public-hygiene self-test (every rule proven RED, every twin proven GREEN)
run: python3 scripts/public-hygiene-lint.py --selftest
- name: Public-hygiene gate
run: python3 scripts/public-hygiene-lint.py --root .
# EXECUTABLE-CONFIG gate. Every config-grammar guard that existed before this one was scoped to
# DOCS — `crates/busbar/tests/docs_examples.rs` reads `docs/**`, marketing's check-config-blocks.mjs
# reads what it PUBLISHES. Nothing ever looked at the configs a MACHINE runs: the `cat > config.yaml
# <<EOF` heredocs in CI workflows and shell scripts, the config strings inside Rust integration
# tests, the yaml under examples/ and docker/. Those are exactly the ones that rot, because a docs
# example gets read by a human every release and an e2e heredoc gets read by nobody until an engine
# upgrade refuses to boot it — which is how the 1.5.3 retired-auth-grammar defect reached a long
# list of plugin repos, core's own plugin-ci.yml, signing-gate.sh, release-check.sh, the shipped
# docker/config.yaml and a shipped example, all at once and all invisible.
#
# It judges with the REAL binary (`busbar --validate`, the same mechanism docs_examples.rs uses), so
# it can never drift from `detect_legacy_markers`. Its own job because it needs a compiled busbar;
# only the one binary is built, not the workspace. Self-test FIRST — a scanner that has quietly
# stopped extracting anything would otherwise pass vacuously, which is worse than no gate at all.
# The same script runs over every PLUGIN repo via the plugin-ci reusable workflow, so the fleet
# inherits it with no per-repo work.
executable-config-lint:
name: executable-config gate (heredocs · test literals · shipped yaml)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
- name: Cache cargo
uses: Swatinem/rust-cache@v2
- name: Build busbar (the validator this gate judges with)
run: cargo build --locked --bin busbar
- name: Executable-config gate self-test (RED/GREEN before the verdict is trusted)
run: |
python3 -c "import yaml" 2>/dev/null || pip install --quiet pyyaml
python3 scripts/executable-config-lint.py --busbar target/debug/busbar --selftest
- name: Executable-config gate
run: python3 scripts/executable-config-lint.py --busbar target/debug/busbar --root .
# Compliance-by-compilation gate: busbar must build + lint clean with the built-in auth plugin
# COMPILED OUT (`--no-default-features`), so a regulated deployment can ship a binary that provably
# contains no such auth code. Build, clippy, AND the test suite all stay green on the featureless
# binary — the feature-dependent behavior tests (admin-token auth, native ranking policies) are
# `#[cfg(feature = ...)]`-gated, so what remains still passes.
#
# SCOPE, STATED HONESTLY: this job proves the featureless binary COMPILES, LINTS, and passes the
# unit tests that remain compiled into it. It does NOT boot the binary and does NOT serve a request,
# so it cannot see a core path that compiles fine and then fails at runtime because the module it
# reaches for is absent. Proving the featureless binary WORKS is the `no-plugins-gate` job below,
# which boots it and drives real HTTP through it.
no-default-features:
name: no-default-features build · clippy · test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: dtolnay/rust-toolchain@stable
with:
components: clippy
- uses: Swatinem/rust-cache@v2
- name: Clippy (no-default-features)
run: cargo clippy --no-default-features --locked -- -D warnings
- name: Build (no-default-features)
run: cargo build --no-default-features --locked --verbose
- name: Test (no-default-features)
run: cargo test --no-default-features --locked --verbose
# THE MECHANICAL DEFINITION OF "PLUGIN". Anything busbar calls a plugin must be 100% a plugin: make
# it downloadable-only tomorrow, ship it uninstalled, and CORE MUST STILL WORK. If core stops
# working, core assumed it always had that module — and that module is not a plugin, it is part of
# the engine wearing a plugin's name.
#
# This job RUNS the binary; `no-default-features` above only compiles it. Two axes, because a plugin
# can be wrongly assumed two independent ways and neither axis can see the other's failures:
# COMPILED OUT (`--no-default-features` — the built-in plugin features are not in the binary) and
# NOT INSTALLED (default features, but `plugins.dir` holds zero artifacts — catches core assuming a
# `dlopen`ed store/auth/hook/secret plugin is on disk). Against a config that references ZERO
# plugins, each axis must boot, serve `/healthz`, ANSWER ITS ADMIN PLANE (real reads and a real
# write — not just the unauthenticated liveness route), and proxy a real request end-to-end to a
# real mock upstream, asserted on the upstream's unique marker.
#
# It has its own job for the reason txn-guards states: a gate nothing runs is not a gate. The
# self-test runs FIRST and is a hard prerequisite — it drives the featureless binary against
# fixtures that genuinely depend on a compiled-out plugin, plus stub servers that pass every earlier
# assertion and break exactly one later one, so a gate that had rotted into passing vacuously fails
# here instead of manufacturing false confidence.
no-plugins-gate:
name: no-plugins gate (compiled out · not installed)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- name: No-plugins gate SELF-TEST (prove RED/GREEN before trusting the verdict)
run: scripts/no-plugins-gate.sh --selftest
- name: No-plugins gate (axis 1 compiled out · axis 2 not installed)
run: scripts/no-plugins-gate.sh --check
# CONCURRENCY GATES for the config-mutation transaction (choke point C). Neither of these can run
# inside `cargo test --workspace`: the compile fence must FAIL to build (it is a negative test
# behind the `txn-fence-red` feature) and the loom model explores interleavings exhaustively behind
# `loom-model`, far too slowly for the default suite. A gate nothing runs is not a gate, so they
# get their own job — otherwise the transaction guard could be dismantled with the tree still green.
txn-guards:
name: txn compile fence · loom model
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- name: Transaction compile fence (must fail to compile)
run: scripts/txn-fence.sh
- name: Loom model of the swap invariant
run: scripts/loom.sh
# TIMING GATE: the ignored hot-path latency test, in release mode. Its bounds are deliberately
# generous (25ms p50 / 250ms p99 through the full in-process router+mock round trip) so runner
# noise can never trip it — it exists to catch GROSS hot-path regressions (sync I/O, stray
# sleeps) the instant they land. Fine-grained overhead numbers are bench/latency/'s job.
timing:
name: timing gate (release)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- name: Hot-path timing gate
run: cargo test --release --locked timing_gate -- --ignored
# Portability gate: busbar must build + pass tests on Windows too (no OS-specific code).
windows:
name: windows build · test
runs-on: windows-latest
steps:
- uses: actions/checkout@v7
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- name: Build
run: cargo build --workspace --verbose
- name: Test
run: cargo test --workspace --verbose