comments: say what the software does, not how it was tested #554
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |