diff --git a/.ci/default.yaml b/.ci/default.yaml
index c6b7702fc3..d6165464b4 100644
--- a/.ci/default.yaml
+++ b/.ci/default.yaml
@@ -3,6 +3,11 @@
to: predev
on_green: merge
deadline: 6h
+# The PR battery, named explicitly so a check declared below but NOT listed here is on-demand only
+# (`modernci call measure {repo, from, check}`), never run on every move.
+on:
+ move: [preflight, gates, clippy, compile, test, doctest, deletion-matrix, deny]
+ manual: [oracle, test-gated]
env:
RUSTFLAGS: "-D warnings"
CARGO_TERM_COLOR: never
@@ -44,6 +49,13 @@ checks:
run: |
meta=$(cargo metadata --format-version 1 --locked)
cargo build --locked $(echo "$meta" | jq -r '.workspace_members as $m | .packages[] | select(.id as $i | $m | index($i)) | select(any(.targets[]; .kind | index("cdylib"))) | "-p " + .name')
+ # The example cdylibs the tests dlopen, built BY NAME and always: the loader's (the neutral frame
+ # door and the other door fixtures, which busbar's and the connector's tests load too) and
+ # busbar's test plane. A run scoped to `busbar` alone builds no other package's examples, and a
+ # test whose fixture is absent fails rather than skips; no variable tells this step the
+ # affected set, so the build does not wait on one.
+ cargo build --locked -p busbar-plugin-loader --examples
+ cargo build --locked -p busbar --examples
plugin() {
man=$(echo "$meta" | jq -r --arg p "$1" 'first(.packages[] | select(.name == $p) | .manifest_path) // empty')
if [ -z "$man" ]; then [ "$3" = optional ] && return 0; echo "busbar resolves no $1"; return 1; fi
@@ -71,6 +83,44 @@ checks:
# The selftests run the gates over the real tree, merge-base included.
requires: [git-history]
needs: [gates]
+ test-gated:
+ # The feature-gated suites the `test` check never builds (row 164): `test` runs default features
+ # only. A, the busbar/kernel/core-admin gates at once (test-harness, llm-on-driver,
+ # transport-webrtc, openapi-schema, timing), filtered to the gated binaries and modules so the
+ # default suites do not run twice; B, the plugin-pack CLI (pack); C, the loom models in release.
+ run: |
+ meta=$(cargo metadata --format-version 1 --locked)
+ cargo build --locked $(echo "$meta" | jq -r '.workspace_members as $m | .packages[] | select(.id as $i | $m | index($i)) | select(any(.targets[]; .kind | index("cdylib"))) | "-p " + .name')
+ # The example cdylibs the tests dlopen, built BY NAME and always: the loader's (the neutral frame
+ # door and the other door fixtures, which busbar's and the connector's tests load too) and
+ # busbar's test plane. A run scoped to `busbar` alone builds no other package's examples, and a
+ # test whose fixture is absent fails rather than skips; no variable tells this step the
+ # affected set, so the build does not wait on one.
+ cargo build --locked -p busbar-plugin-loader --examples
+ cargo build --locked -p busbar --examples
+ plugin() {
+ man=$(echo "$meta" | jq -r --arg p "$1" 'first(.packages[] | select(.name == $p) | .manifest_path) // empty')
+ if [ -z "$man" ]; then [ "$3" = optional ] && return 0; echo "busbar resolves no $1"; return 1; fi
+ ws="$(dirname "$man")/Cargo.toml"
+ pkgs=$(cargo metadata --format-version 1 --no-deps --manifest-path "$ws" | jq -r '.packages[].name')
+ if ! printf '%s\n' $pkgs | grep -qx "$2"; then [ "$3" = optional ] && return 0; echo "$1 has no $2"; return 1; fi
+ if [ "$3" = dropped-in ] && cargo metadata --format-version 1 --no-deps --manifest-path "$ws" | jq -e --arg p "$2" '.packages[] | select(.name == $p) | .features | has("dropped-in")' >/dev/null; then
+ cargo build --locked --manifest-path "$ws" -p "$2" --features dropped-in --target-dir "$PWD/target"
+ else
+ cargo build --locked --manifest-path "$ws" -p "$2" --target-dir "$PWD/target"
+ fi
+ }
+ plugin busbar-transport-tcp-plugin busbar-transport-tcp-plugin dropped-in
+ plugin busbar-transport-http busbar-transport-http-plugin dropped-in
+ plugin busbar-secret-env busbar-secret-env-plugin optional
+ plugin busbar-plane-decisions busbar-plane-decisions-plugin optional
+ cargo build --workspace --locked
+ cargo nextest run --locked --no-fail-fast -p busbar -p busbar-kernel -p busbar-core-admin --features busbar/test-harness,busbar/llm-on-driver,busbar/transport-webrtc,busbar/openapi-schema,busbar-kernel/timing -E 'binary(mcp_stdio_serve) | binary(hook_parity_driver) | binary(admin_cross_plane) | binary(timing_smoke) | package(busbar-core-admin) | (package(busbar-kernel) & test(/timing::/)) | (package(busbar) & test(/(serve_tests|secret_source|connector|boot)::/))'
+ cargo nextest run --locked --no-fail-fast -p busbar-plugin-loader --features pack -E 'binary(/busbar-plugin-pack/)'
+ timeout 3000 cargo test --release --locked -p busbar-kernel --features loom-model txn_loom
+ baseline: target
+ requires: [git-history]
+ needs: [gates]
doctest:
run: cargo test --workspace --doc --locked --no-fail-fast
scope: affected
@@ -88,3 +138,24 @@ checks:
baseline: target
requires: [cargo-deny]
needs: [preflight]
+ # ON-DEMAND ONLY (not in on.move): the predev->dev oracle, measurable on a PR head that changes the
+ # served LLM path (e.g. a plane default flip), so claim C3 is proven before landing, not only at the move.
+ oracle:
+ # busbar's own check tool, the pinned busbar-oracle (the backend's busbar-oracle tool sets
+ # BUSBAR_ORACLE_RUST_BIN): the judge is the one dev's oracle-rust.pin names, run only when its
+ # sha256 is that pin's for this host (a candidate never picks its own judge); the 1.5.5 golden is
+ # add-only from dev to this commit; then the candidate's release binary is recorded over the
+ # corpus and replayed against the committed 1.5.5 golden, strict: green is every cell byte-green
+ # or forgiven by an owner-signed accepted difference.
+ run: |
+ pin=$(git show "$MODERNCI_TO_SHA:testing/shadow-oracle/oracle-rust.pin")
+ want=$(printf '%s\n' "$pin" | sed -n "s/^sha256_linux_$(uname -m)=//p")
+ [[ "$want" =~ ^[0-9a-f]{64}$ ]] || { echo "ORACLE-PIN: oracle-rust.pin at $MODERNCI_TO_SHA pins no linux $(uname -m) judge"; exit 1; }
+ echo "$want $BUSBAR_ORACLE_RUST_BIN" | sha256sum -c -
+ "$BUSBAR_ORACLE_RUST_BIN" golden-guard --repo . --base "$MODERNCI_TO_SHA" --head HEAD
+ cargo build --locked --release -p busbar --bin busbar
+ ./bin/oracle fetch-golden
+ rm -rf target/oracle/recordings/candidate target/oracle/reports/dev
+ ./bin/oracle record --bin target/release/busbar --plane all --out target/oracle/recordings/candidate
+ ./bin/oracle replay --golden testing/shadow-oracle/golden/1.5.5 --candidate target/oracle/recordings/candidate --out target/oracle/reports/dev --strict
+ needs: [gates]
diff --git a/.ci/predev.yaml b/.ci/predev.yaml
index 966fa5be98..8dea412464 100644
--- a/.ci/predev.yaml
+++ b/.ci/predev.yaml
@@ -1,42 +1,52 @@
-# What predev must pass to move into dev: busbar-release's `dev` rung (crates/busbar-release-cli/
-# ladder.json: gates inherit, oracle, conformance; verdict absolute), the 1.6.0 done-oracle, and the
-# Q128 architecture audit (`cargo xtask q128 audit`). ModernCI names a branch file for the branch
-# being moved, so the move predev -> dev reads this file. Shadow while busbar-release still decides.
+# What predev must pass to move into dev: the `dev` rung (gates, oracle, conformance), the 1.6.0
+# done-oracle, and the Q128 architecture audit (`cargo xtask q128 audit`). ModernCI names a branch
+# file for the branch being moved, so the move predev -> dev reads this file, and ModernCI alone
+# judges it: promote.yml, the busbar-release hop that judged it before, is deleted (OWNER
+# 2026-10-12, "ModernCI only"; docs/design/BUSBAR-1.6.0.md Part 6, "BUSBAR-RELEASE RETIRED FROM
+# BUSBAR"). Its green landing is DEV-GREEN.
to: dev
on_green: merge
# The owner's approval on the pull request holds the merge too; q128-audit gates the move on the
# audit's record for the commit (no open BLOCK).
approve: true
-deadline: 6h
+# 8h: plane-delete --all (40-90 min), two LTO release builds and the workspace tests do not fit in 6h.
+deadline: 8h
env:
RUSTFLAGS: "-D warnings"
CARGO_TERM_COLOR: never
CARGO_PROFILE_DEV_DEBUG: "0"
CARGO_INCREMENTAL: "0"
on:
- move: [gates, oracle, conformance, verify-done, q128-audit]
+ move: [gates, oracle, conformance, verify-done, q128-audit, test-gated]
checks:
gates:
- # The rung inherits predev's boarded gates; here they are re-run on the commit, no worse than
- # dev, as the predev file runs them.
+ # The rung re-runs every registered gate on the commit, ABSOLUTE (no `baseline`: every row
+ # green, not "no worse than dev"; dev holds no xtask and no ledgers to be measured against).
+ # The base-relative rules (new edges, minted rows) take predev's own last landed commit as their
+ # base: every lane move already judged its delta against predev, so dev is never the base.
run: |
- export XTASK_CEILING_BASE="$MODERNCI_TO_SHA" XTASK_SHIP_TARGET="$MODERNCI_TO"
+ export XTASK_CEILING_BASE="$(git rev-parse "$MODERNCI_HEAD~1")" XTASK_SHIP_TARGET="$MODERNCI_TO"
cargo xtask gate --all --format=tsv
- baseline: target
- rules: ["qa/*.toml"]
+ # The base-relative rules read history: only a backend with the repo's history runs this check.
+ requires: [git-history]
oracle:
- # busbar's own check tool, the pinned busbar-oracle (the backend's busbar-oracle tool sets
- # BUSBAR_ORACLE_RUST_BIN): the judge is the one dev's oracle-rust.pin names, run only when its
- # sha256 is that pin's for this host (a candidate never picks its own judge); the 1.5.5 golden is
- # add-only from dev to this commit; then the candidate's release binary is recorded over the
- # corpus and replayed against the committed 1.5.5 golden, strict: green is every cell byte-green
- # or forgiven by an owner-signed accepted difference.
+ # busbar's own judge, obtained by bin/oracle itself (OWNER 2026-10-13: no hand-installed tools;
+ # #911): on Linux it fetches busbar-release's published busbar-oracle for the BASE pin's
+ # release_ref ($MODERNCI_TO_SHA's testing/shadow-oracle/oracle-rust.pin, or this tree's when dev
+ # holds none: the first move) and runs it only at that pin's sha256 for this architecture, its
+ # .sig verified against busbar-release's release key; no line for this architecture is no judge.
+ # BUSBAR_RELEASE_TOKEN (a secret ModernCI gives this check) reads the private release. The 1.5.5
+ # golden is add-only from dev to this commit wherever dev holds it (none before the first move);
+ # then the candidate's release binary is recorded over the corpus and replayed against the
+ # committed 1.5.5 golden, strict: green is every cell byte-green or forgiven by an owner-signed
+ # accepted difference.
run: |
- pin=$(git show "$MODERNCI_TO_SHA:testing/shadow-oracle/oracle-rust.pin")
- want=$(printf '%s\n' "$pin" | sed -n "s/^sha256_linux_$(uname -m)=//p")
- [[ "$want" =~ ^[0-9a-f]{64}$ ]] || { echo "ORACLE-PIN: oracle-rust.pin at $MODERNCI_TO_SHA pins no linux $(uname -m) judge"; exit 1; }
- echo "$want $BUSBAR_ORACLE_RUST_BIN" | sha256sum -c -
- "$BUSBAR_ORACLE_RUST_BIN" golden-guard --repo . --base "$MODERNCI_TO_SHA" --head HEAD
+ ./bin/oracle tool-dir >/dev/null
+ if git cat-file -e "$MODERNCI_TO_SHA:testing/shadow-oracle/golden/1.5.5" 2>/dev/null; then
+ ./bin/oracle golden-guard --repo . --base "$MODERNCI_TO_SHA" --head HEAD
+ else
+ echo "golden-guard: dev ($MODERNCI_TO_SHA) holds no golden/1.5.5; nothing to hold add-only"
+ fi
cargo build --locked --release -p busbar --bin busbar
./bin/oracle fetch-golden
rm -rf target/oracle/recordings/candidate target/oracle/reports/dev
@@ -87,9 +97,12 @@ checks:
# backend probed to have all of these, and says which one is missing otherwise.
requires: [docker, docker-compose, python3-venv, curl, git]
verify-done:
- # The done-oracle, full run: exit 0 only (3 is a --fast, provisional answer).
+ # The done-oracle, full run: exit 0 only (3 is a --fast, provisional answer). Its PARITY group
+ # obtains the judge through bin/oracle as the oracle check does (BUSBAR_RELEASE_TOKEN).
run: bash scripts/verify-1.6.0-done.sh
needs: [gates]
+ # It reads merge-bases and tags (the config-schema baseline is tag v1.5.3): full history and tags.
+ requires: [git-history]
q128-audit:
# DEV-GREEN item 5 (docs/design/BUSBAR-1.6.0.md l.2910-2912; 1.6.0-QUESTIONS.md Q128, option
# A): one fresh, independent architecture audit on this commit with no open BLOCK, and a clean
@@ -99,3 +112,44 @@ checks:
# tree, or none, is a BLOCK.
run: cargo xtask q128 audit
needs: [gates]
+ # The audit is judged against this commit's history: full history and tags.
+ requires: [git-history]
+ test-gated:
+ # The feature-gated suites the `test` check never builds (row 164): `test` runs default features
+ # only. A, the busbar/kernel/core-admin gates at once (test-harness, llm-on-driver,
+ # transport-webrtc, openapi-schema, timing), filtered to the gated binaries and modules so the
+ # default suites do not run twice; B, the plugin-pack CLI (pack); C, the loom models in release.
+ run: |
+ meta=$(cargo metadata --format-version 1 --locked)
+ cargo build --locked $(echo "$meta" | jq -r '.workspace_members as $m | .packages[] | select(.id as $i | $m | index($i)) | select(any(.targets[]; .kind | index("cdylib"))) | "-p " + .name')
+ # The example cdylibs the tests dlopen, built BY NAME and always: the loader's (the neutral frame
+ # door and the other door fixtures, which busbar's and the connector's tests load too) and
+ # busbar's test plane. A run scoped to `busbar` alone builds no other package's examples, and a
+ # test whose fixture is absent fails rather than skips; no variable tells this step the
+ # affected set, so the build does not wait on one.
+ cargo build --locked -p busbar-plugin-loader --examples
+ cargo build --locked -p busbar --examples
+ plugin() {
+ man=$(echo "$meta" | jq -r --arg p "$1" 'first(.packages[] | select(.name == $p) | .manifest_path) // empty')
+ if [ -z "$man" ]; then [ "$3" = optional ] && return 0; echo "busbar resolves no $1"; return 1; fi
+ ws="$(dirname "$man")/Cargo.toml"
+ pkgs=$(cargo metadata --format-version 1 --no-deps --manifest-path "$ws" | jq -r '.packages[].name')
+ if ! printf '%s\n' $pkgs | grep -qx "$2"; then [ "$3" = optional ] && return 0; echo "$1 has no $2"; return 1; fi
+ if [ "$3" = dropped-in ] && cargo metadata --format-version 1 --no-deps --manifest-path "$ws" | jq -e --arg p "$2" '.packages[] | select(.name == $p) | .features | has("dropped-in")' >/dev/null; then
+ cargo build --locked --manifest-path "$ws" -p "$2" --features dropped-in --target-dir "$PWD/target"
+ else
+ cargo build --locked --manifest-path "$ws" -p "$2" --target-dir "$PWD/target"
+ fi
+ }
+ plugin busbar-transport-tcp-plugin busbar-transport-tcp-plugin dropped-in
+ plugin busbar-transport-http busbar-transport-http-plugin dropped-in
+ plugin busbar-secret-env busbar-secret-env-plugin optional
+ plugin busbar-plane-decisions busbar-plane-decisions-plugin optional
+ cargo build --workspace --locked
+ cargo nextest run --locked --no-fail-fast -p busbar -p busbar-kernel -p busbar-core-admin --features busbar/test-harness,busbar/llm-on-driver,busbar/transport-webrtc,busbar/openapi-schema,busbar-kernel/timing -E 'binary(mcp_stdio_serve) | binary(hook_parity_driver) | binary(admin_cross_plane) | binary(timing_smoke) | package(busbar-core-admin) | (package(busbar-kernel) & test(/timing::/)) | (package(busbar) & test(/(serve_tests|secret_source|connector|boot)::/))'
+ cargo nextest run --locked --no-fail-fast -p busbar-plugin-loader --features pack -E 'binary(/busbar-plugin-pack/)'
+ timeout 3000 cargo test --release --locked -p busbar-kernel --features loom-model txn_loom
+ # Absolute at this rung, as every check of this move (no `baseline`: dev holds no suites to be
+ # measured against).
+ requires: [git-history]
+ needs: [gates]
diff --git a/.github/required-status-checks.md b/.github/required-status-checks.md
index ffea47b689..530e300a00 100644
--- a/.github/required-status-checks.md
+++ b/.github/required-status-checks.md
@@ -5,22 +5,18 @@ Branch protection lives in repository rulesets. No script writes it.
| Branch | Ruleset | Required |
| --- | --- | --- |
| `predev` | 24945926 | ModernCI's `ci` check. ModernCI judges the PR (`.ci/default.yaml`) and lands it. |
-| `dev` | 24340358 | `preflight` and `hop`, both from `.github/workflows/promote.yml` (the predev to dev promotion). |
+| `dev` | 24340358 | ModernCI's `ci` check on the predev to dev move (`.ci/predev.yaml`). The ruleset still names `preflight` and `hop`, the jobs of the deleted `promote.yml`: the owner replaces them with `ci`. |
| `qa`, `main` | 24334334 | Owner-only; no status check stands in for the owner. |
-Use the exact **check-run name** (the job's `name:`), not the workflow name. In `promote.yml`, `hop`
-is the job id with no `name:`, so its context reads `hop`.
-
-* `preflight`: formatting, `Cargo.lock` current, the generated C header current, on a free runner.
-* `hop`: the pinned busbar-release engine plans the hop and admits the candidate (every registered
- `cargo xtask gate`, plus the oracle and conformance where the rung asks for them).
+Use the exact **check name** the judge posts. ModernCI posts one check, `ci`, per pull request head.
## Reusable workflows (plugin repos call these)
`plugin-ci.yml`, `plugin-consumer-verify.yml`, `plugin-repin.yml` and `plugin-release.yml` are
`workflow_call` only, called by the plugin repos' rendered callers (busbar-release `template/`,
`busbar-release plugin sync`). Their status bubbles up into the **caller's** job, so require the
-caller's job in the plugin repo, never these directly.
+caller's job in the plugin repo, never these directly. They stay until each plugin repo is
+registered with ModernCI and judged by its own branch files.
## Branch hygiene
diff --git a/.github/workflows/promote.yml b/.github/workflows/promote.yml
deleted file mode 100644
index aa1c9b0ae5..0000000000
--- a/.github/workflows/promote.yml
+++ /dev/null
@@ -1,474 +0,0 @@
-name: Promote
-# This workflow serves the predev->dev promotion pull request and the dev->qa->main ladder above it. The
-# hop is GetBusbar/busbar-release's `busbar-release ci hop|verdict|publish|fallback` at RELEASE_REF
-# (private, so no `uses:`); these jobs only check out both repos and run it. No job here runs BOARD
-# (`ci train promote` needs the train's write token, and the job that runs the candidate's code holds
-# none): a PR lands by merge.
-# Pull requests into predev are not judged here: ModernCI judges them (required check `ci`) and lands
-# them with its own merge train, so predev has no merge queue and this workflow runs for no pull request
-# into predev or into a lane branch (a stacked lane PR), nor for `merge_group`. The `merge_group` handling in the jobs below
-# is left in place, dormant: it is job logic, and the concurrency and env lines the preflight holds to busbar-release's compiled copy read it.
-on: { pull_request: { branches: [dev, qa, main] } }
-# R5 (busbar-release gate.rs, OWNER 2026-10-08): no run is ever cancelled in progress. A pull request
-# has at most one run running and one pending, and each newer push replaces the pending one, so the
-# pending run is always the newest head. A merge-queue run is its own group (a cancelled queue run
-# re-runs whole, owner money 2026-10-03). The preflight holds this line to the one compiled into
-# busbar-release: any drift is RED.
-concurrency: { group: "promote-${{ github.event.pull_request.number || github.event.merge_group.head_ref }}", cancel-in-progress: false }
-permissions: { contents: read }
-# THE ENGINE PIN IS NOT IN THIS FILE (OWNER 2026-10-10): the org variables ENGINE_REF (a full
-# busbar-release commit) and ENGINE_SHA256 (the sha256 of that commit's dev-bin ci asset), set by
-# hand with `busbar-release engine pin `, so an engine update never waits in predev's merge
-# queue. The preflight reads them ONCE and hands them to every later job as outputs: one run is one
-# engine even if the variables move mid-run. Unset or malformed is a hard FAIL (ENGINE-PIN), never
-# an older pin; the bytes run only when their sha256 is ENGINE_SHA256 (workflow-rules R16).
-env:
- # The engine contract (busbar-release contract.rs): 2 = the hop holds only the read-only R2 keys and `cache-publish` writes. An engine refuses a contract it does not support, and the auto-pin moves only to engines that support this number.
- BUSBAR_RELEASE_CONTRACT: "2"
- SHA: ${{ github.event.pull_request.head.sha || github.event.merge_group.head_sha }}
- TO: ${{ github.base_ref || github.event.merge_group.base_ref }}
- BASE_SHA: ${{ github.event.pull_request.base.sha || github.event.merge_group.base_sha }}
-jobs:
- preflight:
- if: github.event_name == 'merge_group' || github.event.pull_request.head.repo.full_name == github.repository
- runs-on: ubuntu-latest
- timeout-minutes: 15
- # THE RUN GATE reads GitHub (open pull requests, the merge queue, this head's promote runs) with
- # this job's token. This job runs the candidate's `cargo xtask`, so the token only READS
- # (busbar-release 941028a): the gate's writes (ORPHANS, R7 REPEAT) are recorded in the act
- # artifact before any candidate code runs, and `preflight-act` below makes them.
- # OLD-ENGINE SHIM (remove once ENGINE_REF is at or past busbar-release 941028a): an older engine's
- # R6 field `jobs.preflight.permissions (actions: read)` reads this line's text, comment included;
- # this job's token stays contents read only.
- permissions: { contents: read } # actions: read is public on this repo; the writes are preflight-act's
- # RUNNERS: the runner gateway's table (vars.RUNNERS), from which the preflight sizes `runner=`.
- env: { RELEASE_REF: "${{ vars.ENGINE_REF }}", ENGINE_SHA256: "${{ vars.ENGINE_SHA256 }}", RUNNERS: "${{ vars.RUNNERS }}" }
- outputs: { engine_ref: "${{ steps.engine.outputs.engine_ref }}", engine_sha256: "${{ steps.engine.outputs.engine_sha256 }}", runner: "${{ steps.pre.outputs.runner }}", soak: "${{ steps.pre.outputs.soak }}", one_runner: "${{ steps.pre.outputs.one_runner }}", needs_release_artifact: "${{ steps.pre.outputs.needs_release_artifact }}", run: "${{ steps.pre.outputs.run }}", reason: "${{ steps.pre.outputs.reason }}" }
- steps:
- # Full history: the pre-flight reads the pull request's diff against BASE_SHA to choose one runner or the matrix.
- - { uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1, with: { ref: "${{ env.SHA }}", path: busbar, fetch-depth: 0 } } # v7
- # THE ENGINE, downloaded, never compiled: CI's --no-default-features binary of RELEASE_REF from
- # busbar-release's dev-bin release (dev-bin.yml), run only when its sha256 is ENGINE_SHA256.
- # First the pin's shape: a full 40-hex commit and a 64-hex digest, or FAIL.
- - name: Engine (busbar-release at RELEASE_REF, sha256-pinned)
- id: engine
- env: { GH_TOKEN: "${{ secrets.TURNSTILE_DISPATCH_TOKEN }}" }
- run: |
- set -euo pipefail
- [[ "$RELEASE_REF" =~ ^[0-9a-f]{40}$ ]] || { echo "::error title=ENGINE-PIN FAIL::vars.ENGINE_REF is unset or not a full 40-hex busbar-release commit (busbar-release engine pin )"; exit 1; }
- [[ "$ENGINE_SHA256" =~ ^[0-9a-f]{64}$ ]] || { echo "::error title=ENGINE-PIN FAIL::vars.ENGINE_SHA256 is unset or not a 64-hex sha256 (busbar-release engine pin )"; exit 1; }
- api=https://api.github.com/repos/GetBusbar/busbar-release/releases
- name="busbar-release-ci-linux-x86_64-$RELEASE_REF"
- id=$(curl -fsSL -H "Authorization: Bearer $GH_TOKEN" "$api/tags/dev-bin" | jq -r --arg n "$name" '.assets[] | select(.name == $n) | .id')
- [ -n "$id" ] || { echo "::error::dev-bin has no $name"; exit 1; }
- curl -fsSL -H "Authorization: Bearer $GH_TOKEN" -H "Accept: application/octet-stream" -o "$RUNNER_TEMP/busbar-release" "$api/assets/$id"
- echo "$ENGINE_SHA256 $RUNNER_TEMP/busbar-release" | sha256sum -c -
- chmod +x "$RUNNER_TEMP/busbar-release"
- { echo "engine_ref=$RELEASE_REF"; echo "engine_sha256=$ENGINE_SHA256"; } >> "$GITHUB_OUTPUT"
- - { uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1, with: { repository: GetBusbar/busbar-release, ref: "${{ env.RELEASE_REF }}", token: "${{ secrets.TURNSTILE_DISPATCH_TOKEN }}", path: busbar-release, persist-credentials: false } } # v7
- # The pre-flight is busbar-release's Rust CLI (preflight.json is the one list): it sizes the hop
- # (runner= to $GITHUB_OUTPUT), chooses how a pull request into predev runs (one_runner=: ONE runner
- # when the crates its diff affects are estimated within 20 min, else the shard matrix; OWNER-LOCKED
- # 2026-10-06), whether this run reads the release artifact (needs_release_artifact=: the rung runs
- # the oracle or conformance; predev runs neither), and runs every pre-flight check against the
- # candidate. Built with no default features, as the CLI documents for this job. FIRST it is THE
- # RUN GATE (OWNER 2026-10-08): run=1|0 and reason= from facts only (R1 STACK, R2 queued, R3
- # superseded, R4 duplicate head, R5/R6 this file's wiring); run=0 means nothing in this run
- # builds, and a RED rule fails here. The gate's writes are recorded (`--act-out act/act.json`,
- # before any candidate code runs) for preflight-act; this job makes none.
- # OLD-ENGINE SHIM (remove once ENGINE_REF is at or past 941028a): an engine without --act-out
- # runs without it.
- - id: pre
- working-directory: busbar-release
- env: { GITHUB_TOKEN: "${{ github.token }}", RUNNERS: "${{ vars.RUNNERS }}" }
- run: |
- act=()
- if "$RUNNER_TEMP/busbar-release" ci preflight --help | grep -q -- --act-out; then act=(--act-out "$GITHUB_WORKSPACE/act/act.json"); fi
- "$RUNNER_TEMP/busbar-release" ci preflight "${act[@]}"
- - { if: always(), uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a, with: { name: "preflight-act-${{ env.SHA }}", path: "act/", retention-days: 1, if-no-files-found: ignore, overwrite: true } } # v7.0.1
- # THE RUN GATE'S WRITES (busbar-release 941028a), in a job that runs NO candidate code: it checks
- # out nothing, downloads the engine (sha256-pinned) and the preflight's act artifact, and runs
- # `ci preflight-act`, which re-reads GitHub itself: ORPHANS cancels the merge-group runs a queue
- # rebuild left running (actions: write), and R7 REPEAT turns off the auto-merge of a PR whose head
- # already failed the queue as its own red (pull-requests: write), only the PR both the artifact
- # and the re-read name. Merge groups only: a pull request has nothing to act on. It runs whatever
- # the preflight concluded (R7 is a RED preflight), and never decides the verdict.
- preflight-act:
- needs: preflight
- if: ${{ always() && github.event_name == 'merge_group' && needs.preflight.outputs.engine_ref != '' }}
- runs-on: ubuntu-latest
- timeout-minutes: 10
- permissions: { contents: read, actions: write, pull-requests: write }
- env: { RELEASE_REF: "${{ needs.preflight.outputs.engine_ref }}", ENGINE_SHA256: "${{ needs.preflight.outputs.engine_sha256 }}" }
- steps:
- # THE ENGINE, downloaded, never compiled: CI's --no-default-features binary of RELEASE_REF from
- # busbar-release's dev-bin release (dev-bin.yml), run only when its sha256 is ENGINE_SHA256.
- # First the pin's shape: a full 40-hex commit and a 64-hex digest, or FAIL.
- - name: Engine (busbar-release at RELEASE_REF, sha256-pinned)
- env: { GH_TOKEN: "${{ secrets.TURNSTILE_DISPATCH_TOKEN }}" }
- run: |
- set -euo pipefail
- [[ "$RELEASE_REF" =~ ^[0-9a-f]{40}$ ]] || { echo "::error title=ENGINE-PIN FAIL::vars.ENGINE_REF is unset or not a full 40-hex busbar-release commit (busbar-release engine pin )"; exit 1; }
- [[ "$ENGINE_SHA256" =~ ^[0-9a-f]{64}$ ]] || { echo "::error title=ENGINE-PIN FAIL::vars.ENGINE_SHA256 is unset or not a 64-hex sha256 (busbar-release engine pin )"; exit 1; }
- api=https://api.github.com/repos/GetBusbar/busbar-release/releases
- name="busbar-release-ci-linux-x86_64-$RELEASE_REF"
- id=$(curl -fsSL -H "Authorization: Bearer $GH_TOKEN" "$api/tags/dev-bin" | jq -r --arg n "$name" '.assets[] | select(.name == $n) | .id')
- [ -n "$id" ] || { echo "::error::dev-bin has no $name"; exit 1; }
- curl -fsSL -H "Authorization: Bearer $GH_TOKEN" -H "Accept: application/octet-stream" -o "$RUNNER_TEMP/busbar-release" "$api/assets/$id"
- echo "$ENGINE_SHA256 $RUNNER_TEMP/busbar-release" | sha256sum -c -
- chmod +x "$RUNNER_TEMP/busbar-release"
- - { continue-on-error: true, uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c, with: { name: "preflight-act-${{ env.SHA }}", path: act } } # v8.0.1
- # OLD-ENGINE SHIM (remove once ENGINE_REF is at or past 941028a): an engine without the verb
- # made these writes in the preflight itself.
- - name: The run gate's writes (ORPHANS, R7 REPEAT)
- env: { GITHUB_TOKEN: "${{ github.token }}" }
- run: |
- if ! "$RUNNER_TEMP/busbar-release" ci --help | grep -q preflight-act; then echo "engine $RELEASE_REF has no preflight-act: nothing to act on here"; exit 0; fi
- "$RUNNER_TEMP/busbar-release" ci preflight-act --act "$GITHUB_WORKSPACE/act/act.json"
- # The release artifact the oracle records and the slsa-verifier conformance rig judges. It runs in
- # every merge group (the fold requires it: a check on the release build), and in every run whose
- # rung reads the artifact (`needs_release_artifact=true`: the rung runs the oracle or conformance,
- # i.e. the predev->dev pull request and up). dev moves by pull request now, not by the ladder
- # fast-forwarding a merge group's sha (ARCHITECT 2026-10-02, superseded), so the run that judges a
- # promotion builds the artifact of exactly the commit it promotes: its shards download
- # `slsa-artifact-` from THIS run, which a merge-group-only build never produced (#740 failed
- # every run at that download). workflow-rules R18 holds this wiring. Its SLSA provenance is minted
- # from `base64-subjects` by the pinned slsa-github-generator once the owner rules on R14 (the
- # generator must be referenced by tag); until then the suite is not-run, a standing red here.
- slsa-build:
- if: needs.preflight.outputs.run == '1' && (github.event_name == 'merge_group' || needs.preflight.outputs.needs_release_artifact == 'true')
- needs: preflight
- permissions: { contents: read }
- runs-on: ${{ needs.preflight.outputs.runner }}
- timeout-minutes: 90
- outputs: { base64-subjects: "${{ steps.digest.outputs.base64-subjects }}" }
- steps:
- - { uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1, with: { ref: "${{ env.SHA }}", path: busbar, persist-credentials: false } } # v7
- - name: Build the release artifact
- working-directory: busbar
- run: |
- cargo build --locked --release -p busbar --bin busbar
- mkdir -p ../slsa && cp target/release/busbar ../slsa/busbar
- - id: digest
- name: Its sha256 (the provenance subject)
- working-directory: slsa
- run: |
- sha256sum busbar > busbar.sha256
- echo "base64-subjects=$(base64 -w0 busbar.sha256)" >> "$GITHUB_OUTPUT"
- - { uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a, with: { name: "slsa-artifact-${{ env.SHA }}", path: "slsa/", retention-days: 7, if-no-files-found: error } } # v7.0.1
- # THE QA SOAK (OWNER 2026-10-10; busbar #734): the soak stays, as a 1-hour stability gate on each
- # qa promotion. `busbar-release soak ` (profile.rs's full profile: 3600 s, 50 rps,
- # 16 concurrent) soaks THIS candidate's release binary on the runner the preflight chose, wherever
- # the ladder's entry asks for it (`soak=true`: qa). The hop folds this job's result like
- # slsa-build's (`ci turnstile merge --needs soak=...`): a failed soak DENIES the promotion. CI=false
- # selects the full hour (the CI context would pick the two-minute smoke). It runs the candidate, so
- # it holds no write token. The candidate binary is this run's slsa-build artifact (a rung with the
- # soak reads the artifact, so slsa-build runs); a run without it builds it here. The engine is the
- # downloaded ci binary (built with `--features soak`, dev-bin.yml), sha256-pinned like every other
- # job's.
- soak:
- needs: [preflight, slsa-build]
- if: ${{ !cancelled() && needs.preflight.result == 'success' && needs.preflight.outputs.run == '1' && needs.preflight.outputs.soak == 'true' }}
- permissions: { contents: read }
- runs-on: ${{ needs.preflight.outputs.runner }}
- timeout-minutes: 180
- env: { RELEASE_REF: "${{ needs.preflight.outputs.engine_ref }}", ENGINE_SHA256: "${{ needs.preflight.outputs.engine_sha256 }}" }
- steps:
- - { uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1, with: { ref: "${{ env.SHA }}", path: busbar, persist-credentials: false } } # v7
- # THE ENGINE, downloaded, never compiled: CI's --no-default-features binary of RELEASE_REF from
- # busbar-release's dev-bin release (dev-bin.yml), run only when its sha256 is ENGINE_SHA256.
- # First the pin's shape: a full 40-hex commit and a 64-hex digest, or FAIL.
- - name: Engine (busbar-release at RELEASE_REF, sha256-pinned)
- env: { GH_TOKEN: "${{ secrets.TURNSTILE_DISPATCH_TOKEN }}" }
- run: |
- set -euo pipefail
- [[ "$RELEASE_REF" =~ ^[0-9a-f]{40}$ ]] || { echo "::error title=ENGINE-PIN FAIL::vars.ENGINE_REF is unset or not a full 40-hex busbar-release commit (busbar-release engine pin )"; exit 1; }
- [[ "$ENGINE_SHA256" =~ ^[0-9a-f]{64}$ ]] || { echo "::error title=ENGINE-PIN FAIL::vars.ENGINE_SHA256 is unset or not a 64-hex sha256 (busbar-release engine pin )"; exit 1; }
- api=https://api.github.com/repos/GetBusbar/busbar-release/releases
- name="busbar-release-ci-linux-x86_64-$RELEASE_REF"
- id=$(curl -fsSL -H "Authorization: Bearer $GH_TOKEN" "$api/tags/dev-bin" | jq -r --arg n "$name" '.assets[] | select(.name == $n) | .id')
- [ -n "$id" ] || { echo "::error::dev-bin has no $name"; exit 1; }
- curl -fsSL -H "Authorization: Bearer $GH_TOKEN" -H "Accept: application/octet-stream" -o "$RUNNER_TEMP/busbar-release" "$api/assets/$id"
- echo "$ENGINE_SHA256 $RUNNER_TEMP/busbar-release" | sha256sum -c -
- chmod +x "$RUNNER_TEMP/busbar-release"
- - { uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1, with: { repository: GetBusbar/busbar-release, ref: "${{ env.RELEASE_REF }}", token: "${{ secrets.TURNSTILE_DISPATCH_TOKEN }}", path: busbar-release, persist-credentials: false } } # v7
- - if: needs.slsa-build.result == 'success'
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
- with: { name: "slsa-artifact-${{ env.SHA }}", path: slsa }
- - if: needs.slsa-build.result != 'success'
- working-directory: busbar
- run: |
- cargo build --locked --release -p busbar --bin busbar
- mkdir -p ../slsa && cp target/release/busbar ../slsa/busbar
- - name: Soak the candidate (the full profile)
- working-directory: busbar-release
- env: { CI: "false", GH_TOKEN: "${{ github.token }}", TMPDIR: "${{ runner.temp }}" }
- run: |
- chmod +x "$GITHUB_WORKSPACE/slsa/busbar"
- "$RUNNER_TEMP/busbar-release" soak "$GITHUB_WORKSPACE/slsa/busbar"
- - { if: always(), uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a, with: { name: "soak-${{ env.SHA }}", path: "${{ runner.temp }}/busbar-soak-*/", retention-days: 30, if-no-files-found: warn } } # v7.0.1
- # The merge group's hop, and the predev->dev pull request's, is SHARDED (ARCHITECT 2026-10-02): four
- # runners each judge one part (`--shard K/4`: test:workspace's hash partition K, plus the unsplit steps
- # dealt to K; the gates in shard 1, the oracle and conformance in shard 4). Each builds and runs the
- # candidate's code, so it holds NO write token (contents and actions read only, no persisted
- # credentials); its report leaves as an artifact. It starts beside slsa-build, not after it (B3-P,
- # 2026-10-08), and no shard waits for it: the fold requires slsa-build's result (`needs:`,
- # `--needs`, `--must-run`), and nothing a shard runs reads the release artifact (the slsa-verifier
- # leg is not-run until `conformance record` is handed `--slsa-artifact`). The predev->dev shards
- # sat ~7 min each in `await-job` for a download nobody read (2026-10-11). Every shard runs to
- # the bottom (FULLY SEEN, busbar-release #152). A pull request into predev runs the shards only when the
- # pre-flight chose the matrix (one_runner=false: every crate affected, or over 20 min on one runner);
- # otherwise its hop is ONE runner, in the `hop` job below.
- hop-shard:
- name: "hop shard ${{ matrix.shard }}"
- strategy: { fail-fast: false, matrix: { shard: [1, 2, 3, 4] } }
- needs: [preflight]
- if: ${{ !cancelled() && needs.preflight.result == 'success' && needs.preflight.outputs.run == '1' && (github.event_name == 'merge_group' || github.base_ref == 'dev' || needs.preflight.outputs.one_runner != 'true') }}
- permissions: { contents: read, actions: read }
- runs-on: ${{ needs.preflight.outputs.runner }}
- timeout-minutes: 330
- env: { RELEASE_REF: "${{ needs.preflight.outputs.engine_ref }}", ENGINE_SHA256: "${{ needs.preflight.outputs.engine_sha256 }}", RUNNERS: "${{ vars.RUNNERS }}" }
- steps:
- - { uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1, with: { ref: "${{ env.SHA }}", path: busbar, fetch-depth: 0, persist-credentials: false } } # v7
- # THE ENGINE, downloaded, never compiled: CI's --no-default-features binary of RELEASE_REF from
- # busbar-release's dev-bin release (dev-bin.yml), run only when its sha256 is ENGINE_SHA256.
- # First the pin's shape: a full 40-hex commit and a 64-hex digest, or FAIL.
- - name: Engine (busbar-release at RELEASE_REF, sha256-pinned)
- env: { GH_TOKEN: "${{ secrets.TURNSTILE_DISPATCH_TOKEN }}" }
- run: |
- set -euo pipefail
- [[ "$RELEASE_REF" =~ ^[0-9a-f]{40}$ ]] || { echo "::error title=ENGINE-PIN FAIL::vars.ENGINE_REF is unset or not a full 40-hex busbar-release commit (busbar-release engine pin )"; exit 1; }
- [[ "$ENGINE_SHA256" =~ ^[0-9a-f]{64}$ ]] || { echo "::error title=ENGINE-PIN FAIL::vars.ENGINE_SHA256 is unset or not a 64-hex sha256 (busbar-release engine pin )"; exit 1; }
- api=https://api.github.com/repos/GetBusbar/busbar-release/releases
- name="busbar-release-ci-linux-x86_64-$RELEASE_REF"
- id=$(curl -fsSL -H "Authorization: Bearer $GH_TOKEN" "$api/tags/dev-bin" | jq -r --arg n "$name" '.assets[] | select(.name == $n) | .id')
- [ -n "$id" ] || { echo "::error::dev-bin has no $name"; exit 1; }
- curl -fsSL -H "Authorization: Bearer $GH_TOKEN" -H "Accept: application/octet-stream" -o "$RUNNER_TEMP/busbar-release" "$api/assets/$id"
- echo "$ENGINE_SHA256 $RUNNER_TEMP/busbar-release" | sha256sum -c -
- chmod +x "$RUNNER_TEMP/busbar-release"
- # THE JUDGE, pinned in busbar (OWNER 2026-10-10): the busbar-oracle testing/shadow-oracle/oracle-rust.pin
- # names, read at the BASE (a candidate never picks its own judge), downloaded from busbar-release's
- # dev-bin release and run only when its sha256 is the pin's sha256_linux_x86_64. The engine pin
- # never moves it. Only the last shard runs the oracle; `ci hop` and bin/oracle both take the binary
- # from BUSBAR_ORACLE_RUST_BIN, so nothing builds a judge.
- - name: Oracle (pinned at the base, sha256-checked)
- if: matrix.shard == 4
- env: { GH_TOKEN: "${{ secrets.TURNSTILE_DISPATCH_TOKEN }}" }
- run: |
- set -euo pipefail
- pin="$(git -C busbar show "$BASE_SHA:testing/shadow-oracle/oracle-rust.pin" 2>/dev/null || true)"
- # A base from before the pinned binary (no sha256 line): the oracle is built as before.
- grep -q '^sha256_linux_x86_64=' <<<"$pin" || { echo "::notice::oracle-rust.pin at $BASE_SHA pins no binary: bin/oracle builds the judge"; exit 0; }
- ref="$(printf '%s\n' "$pin" | sed -n 's/^release_ref=//p')"
- ORACLE_SHA256="$(printf '%s\n' "$pin" | sed -n 's/^sha256_linux_x86_64=//p')"
- [[ "$ref" =~ ^[0-9a-f]{40}$ && "$ORACLE_SHA256" =~ ^[0-9a-f]{64}$ ]] || { echo "::error title=ORACLE-PIN FAIL::oracle-rust.pin at $BASE_SHA needs a 40-hex release_ref and a 64-hex sha256_linux_x86_64"; exit 1; }
- api=https://api.github.com/repos/GetBusbar/busbar-release/releases
- name="busbar-oracle-linux-x86_64-$ref"
- id=$(curl -fsSL -H "Authorization: Bearer $GH_TOKEN" "$api/tags/dev-bin" | jq -r --arg n "$name" '.assets[] | select(.name == $n) | .id')
- [ -n "$id" ] || { echo "::error title=ORACLE-PIN FAIL::dev-bin has no $name"; exit 1; }
- curl -fsSL -H "Authorization: Bearer $GH_TOKEN" -H "Accept: application/octet-stream" -o "$RUNNER_TEMP/busbar-oracle" "$api/assets/$id"
- echo "$ORACLE_SHA256 $RUNNER_TEMP/busbar-oracle" | sha256sum -c -
- chmod +x "$RUNNER_TEMP/busbar-oracle"
- echo "BUSBAR_ORACLE_RUST_BIN=$RUNNER_TEMP/busbar-oracle" >> "$GITHUB_ENV"
- - { uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1, with: { repository: GetBusbar/busbar-release, ref: "${{ env.RELEASE_REF }}", token: "${{ secrets.TURNSTILE_DISPATCH_TOKEN }}", path: busbar-release, persist-credentials: false } } # v7
- # The conformance rigs `conformance record` runs in the full plan need node 22 and pnpm 10 (the mcp
- # suite's official runner, and rolldown's native binding built under it); the Latchkey large image
- # carries node 20, so mcp read not-run (CONFORMANCE-RIGS, 2026-10-02). Only the last shard of a
- # rung that runs conformance or the oracle (needs_release_artifact) records them; the others
- # skip the ~15 s install.
- - { if: "matrix.shard == 4 && needs.preflight.outputs.needs_release_artifact == 'true'", uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413, with: { version: 10 } } # v6.1.0
- - { if: "matrix.shard == 4 && needs.preflight.outputs.needs_release_artifact == 'true'", uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020, with: { node-version: 22 } } # v7.0.0
- # No debuginfo in the dev/test builds: build_test's all-features, feature-matrix and test builds plus
- # the oracle's release build filled the runner's ~41 GiB free disk with debuginfo (ENOSPC, no log).
- # Base and candidate build alike, so the no-worse-than-base verdict is unchanged.
- # GITHUB_TOKEN (contents read) is for `ci hop`'s R3 re-check alone (is this merge group still a
- # current queue entry's head?), which runs before any candidate code; the judge that runs the
- # candidate never holds it. A superseded group's shard builds nothing.
- # THE R2 KEYS HERE ONLY READ (busbar-release 069f032): this job runs candidate code, and the hop
- # refuses to start (INFRA) with the write keys in its environment. Unset read-only keys fail
- # open (no cache). The judge's cache puts land in r2-outbox/, uploaded below and published by
- # `cache-publish`, the one job that holds the write keys (workflow-rules R19).
- - working-directory: busbar-release
- run: $RUNNER_TEMP/busbar-release ci hop
- env: { GITHUB_TOKEN: "${{ github.token }}", R2_RO_KEY_ID: "${{ secrets.R2_CACHE_RO_ACCESS_KEY_ID }}", R2_RO_KEY_SECRET: "${{ secrets.R2_CACHE_RO_SECRET_ACCESS_KEY }}", CARGO_PROFILE_DEV_DEBUG: "0", SHARD: "${{ matrix.shard }}/4" }
- - { if: always(), uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a, with: { name: "turnstile-report-${{ env.SHA }}-shard${{ matrix.shard }}", path: "artifacts/", if-no-files-found: warn } } # v7.0.1
- - { if: always(), uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a, with: { name: "r2-outbox-${{ env.SHA }}-shard${{ matrix.shard }}", path: "r2-outbox/", retention-days: 1, if-no-files-found: ignore, overwrite: true } } # v7.0.1
- # `hop` (the required check) is the hop's ONE verdict. In a merge group and on the predev->dev pull
- # request it folds the shards: BOARD only when every shard reported and boarded and slsa-build is
- # green (or skipped: a pull request whose rung reads no release artifact): a red slsa-build denies
- # it; a shard that is missing, timed out or INFRA makes it INFRA, never green. A pull request
- # INTO PREDEV whose pre-flight
- # chose one runner (OWNER-LOCKED 2026-10-06, BUSBAR-1.6.0 Part 6 "Verification runs once per input")
- # runs it as ONE runner: no shard, no slsa-build; the ladder's predev `enter_pr`, the merge group's
- # gates and steps scoped to the crates the PR changes plus their reverse dependencies, on the PR merged
- # onto the base tip, judged no worse than the base the merge group landed in its 4 shards
- # (BASE_SHARDS). It runs the candidate's code, so it holds no write token either.
- hop:
- needs: [preflight, slsa-build, hop-shard, soak]
- if: ${{ !cancelled() && needs.preflight.result == 'success' }}
- permissions: { contents: read, actions: read }
- runs-on: ${{ (github.event_name == 'pull_request' && github.base_ref != 'dev' && needs.preflight.outputs.one_runner == 'true') && needs.preflight.outputs.runner || 'ubuntu-latest' }}
- timeout-minutes: ${{ (github.event_name == 'pull_request' && github.base_ref != 'dev' && needs.preflight.outputs.one_runner == 'true') && 330 || 30 }}
- env: { ONE_RUNNER: "${{ github.event_name == 'pull_request' && github.base_ref != 'dev' && needs.preflight.outputs.one_runner == 'true' }}", RELEASE_REF: "${{ needs.preflight.outputs.engine_ref }}", ENGINE_SHA256: "${{ needs.preflight.outputs.engine_sha256 }}", RUNNERS: "${{ vars.RUNNERS }}" }
- steps:
- # THE RUN GATE said run=0 (neutral: the merge queue, or another run of this head, judges it).
- # Nothing was built, so this run does NOT JUDGE the head: the required check fails here, never
- # passes by skipping (a skipped required check counts as success and would land an unjudged
- # merge group). The preflight writes one_runner=false on run=0, so nothing below builds either.
- - if: needs.preflight.outputs.run != '1'
- env: { REASON: "${{ needs.preflight.outputs.reason }}" }
- run: |
- echo "::error title=NOT JUDGED::not judged by this run (run=0): $REASON. Not a verdict on the code."
- exit 1
- - if: env.ONE_RUNNER == 'true'
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- with: { ref: "${{ env.SHA }}", path: busbar, fetch-depth: 0, persist-credentials: false }
- # THE ENGINE, downloaded, never compiled: CI's --no-default-features binary of RELEASE_REF from
- # busbar-release's dev-bin release (dev-bin.yml), run only when its sha256 is ENGINE_SHA256.
- # First the pin's shape: a full 40-hex commit and a 64-hex digest, or FAIL.
- - name: Engine (busbar-release at RELEASE_REF, sha256-pinned)
- env: { GH_TOKEN: "${{ secrets.TURNSTILE_DISPATCH_TOKEN }}" }
- run: |
- set -euo pipefail
- [[ "$RELEASE_REF" =~ ^[0-9a-f]{40}$ ]] || { echo "::error title=ENGINE-PIN FAIL::vars.ENGINE_REF is unset or not a full 40-hex busbar-release commit (busbar-release engine pin )"; exit 1; }
- [[ "$ENGINE_SHA256" =~ ^[0-9a-f]{64}$ ]] || { echo "::error title=ENGINE-PIN FAIL::vars.ENGINE_SHA256 is unset or not a 64-hex sha256 (busbar-release engine pin )"; exit 1; }
- api=https://api.github.com/repos/GetBusbar/busbar-release/releases
- name="busbar-release-ci-linux-x86_64-$RELEASE_REF"
- id=$(curl -fsSL -H "Authorization: Bearer $GH_TOKEN" "$api/tags/dev-bin" | jq -r --arg n "$name" '.assets[] | select(.name == $n) | .id')
- [ -n "$id" ] || { echo "::error::dev-bin has no $name"; exit 1; }
- curl -fsSL -H "Authorization: Bearer $GH_TOKEN" -H "Accept: application/octet-stream" -o "$RUNNER_TEMP/busbar-release" "$api/assets/$id"
- echo "$ENGINE_SHA256 $RUNNER_TEMP/busbar-release" | sha256sum -c -
- chmod +x "$RUNNER_TEMP/busbar-release"
- - { uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1, with: { repository: GetBusbar/busbar-release, ref: "${{ env.RELEASE_REF }}", token: "${{ secrets.TURNSTILE_DISPATCH_TOKEN }}", path: busbar-release, persist-credentials: false } } # v7
- # No debuginfo in the dev/test builds (as in the shards: the runner's disk). Read-only R2 keys,
- # the puts to r2-outbox/ for `cache-publish` (as in the shards: the judge runs candidate code).
- - if: env.ONE_RUNNER == 'true'
- working-directory: busbar-release
- run: $RUNNER_TEMP/busbar-release ci hop
- env: { R2_RO_KEY_ID: "${{ secrets.R2_CACHE_RO_ACCESS_KEY_ID }}", R2_RO_KEY_SECRET: "${{ secrets.R2_CACHE_RO_SECRET_ACCESS_KEY }}", CARGO_PROFILE_DEV_DEBUG: "0", BASE_SHARDS: "4" }
- - if: always() && env.ONE_RUNNER == 'true'
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
- with: { name: "turnstile-report-${{ env.SHA }}", path: "artifacts/", if-no-files-found: warn }
- - if: always() && env.ONE_RUNNER == 'true'
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
- with: { name: "r2-outbox-${{ env.SHA }}-one", path: "r2-outbox/", retention-days: 1, if-no-files-found: ignore, overwrite: true }
- - if: env.ONE_RUNNER != 'true'
- continue-on-error: true
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
- with: { pattern: "turnstile-report-${{ env.SHA }}-shard*", path: shards }
- - name: Fold the shards
- if: env.ONE_RUNNER != 'true'
- env: { GH_TOKEN: "${{ github.token }}", SLSA_BUILD: "${{ needs.slsa-build.result }}", SOAK: "${{ needs.soak.result }}", SLSA_READ: "${{ github.event_name == 'merge_group' || needs.preflight.outputs.needs_release_artifact == 'true' }}", SOAK_READ: "${{ needs.preflight.outputs.soak }}" }
- run: |
- mkdir -p merge artifacts
- # A SKIPPED needed job DENIES (busbar-release 03cb349) unless the rung does not read it:
- # slsa-build is read in a merge group and wherever needs_release_artifact, the soak where
- # the ladder asks for it (soak=true). OLD-ENGINE SHIM (remove once ENGINE_REF is at or past
- # 03cb349): an engine without --must-run takes a skip as neutral itself.
- must=()
- if "$RUNNER_TEMP/busbar-release" ci turnstile merge --help | grep -q -- --must-run; then
- must=(--must-run "slsa-build=$SLSA_READ" --must-run "soak=$SOAK_READ")
- fi
- # The run's own job list: a shard with no report is INFRA:platform when its job was never
- # created (GitHub dropped it, as in the 2026-10-07 16:52-17:01Z Actions outage), else
- # INFRA:ours. A list that cannot be read leaves plain INFRA; the hop is never green on it.
- gh api "repos/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID/attempts/$GITHUB_RUN_ATTEMPT/jobs?per_page=100" \
- >"$GITHUB_WORKSPACE/shard-jobs.json" || true
- for k in 1 2 3 4; do
- d="shards/turnstile-report-$SHA-shard$k"
- mkdir -p "merge/shard-$k" "artifacts/shard-$k"
- [ -f "$d/turnstile-report.json" ] && cp "$d/turnstile-report.json" "merge/shard-$k/"
- [ -d "$d" ] && cp -r "$d/." "artifacts/shard-$k/"
- done
- cd busbar-release
- "$RUNNER_TEMP/busbar-release" \
- ci turnstile merge --sha "$SHA" --shards 4 --dir "$GITHUB_WORKSPACE/merge" --out "$GITHUB_WORKSPACE/artifacts/turnstile-report.json" \
- --shard-jobs "$GITHUB_WORKSPACE/shard-jobs.json" --needs "slsa-build=$SLSA_BUILD" --needs "soak=$SOAK" "${must[@]}"
- # The durable verdict (verdict/), from the folded report and every shard's step logs: the artifact publish reads.
- - if: always() && needs.preflight.outputs.run == '1'
- working-directory: busbar-release
- run: $RUNNER_TEMP/busbar-release ci verdict
- - { if: "always() && needs.preflight.outputs.run == '1'", uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a, with: { name: "verdict-${{ env.SHA }}", path: "verdict/", retention-days: 90, if-no-files-found: warn } } # v7.0.1
- # THE R2 CACHE'S ONE WRITER (busbar-release 069f032). The hop jobs judge with READ-ONLY keys and
- # leave their cache puts (base reports, step PASSes) in r2-outbox/ artifacts; this job uploads them
- # with the WRITE keys. It runs NO candidate code: it checks out nothing, downloads the engine
- # (sha256-pinned) and the outboxes, and runs `ci cache-publish`, which uploads only objects under
- # the cache's own prefixes. It is no part of the verdict: nothing needs it, it is no required
- # check, and it fails open (no keys, no outbox, a failed upload: a later cache miss).
- # workflow-rules R19: no job that runs candidate code names the write keys.
- cache-publish:
- needs: [preflight, hop-shard, hop]
- if: ${{ always() && needs.preflight.result == 'success' && needs.preflight.outputs.run == '1' }}
- runs-on: ubuntu-latest
- timeout-minutes: 15
- continue-on-error: true
- permissions: { contents: read }
- env: { RELEASE_REF: "${{ needs.preflight.outputs.engine_ref }}", ENGINE_SHA256: "${{ needs.preflight.outputs.engine_sha256 }}" }
- steps:
- # THE ENGINE, downloaded, never compiled: CI's --no-default-features binary of RELEASE_REF from
- # busbar-release's dev-bin release (dev-bin.yml), run only when its sha256 is ENGINE_SHA256.
- # First the pin's shape: a full 40-hex commit and a 64-hex digest, or FAIL.
- - name: Engine (busbar-release at RELEASE_REF, sha256-pinned)
- env: { GH_TOKEN: "${{ secrets.TURNSTILE_DISPATCH_TOKEN }}" }
- run: |
- set -euo pipefail
- [[ "$RELEASE_REF" =~ ^[0-9a-f]{40}$ ]] || { echo "::error title=ENGINE-PIN FAIL::vars.ENGINE_REF is unset or not a full 40-hex busbar-release commit (busbar-release engine pin )"; exit 1; }
- [[ "$ENGINE_SHA256" =~ ^[0-9a-f]{64}$ ]] || { echo "::error title=ENGINE-PIN FAIL::vars.ENGINE_SHA256 is unset or not a 64-hex sha256 (busbar-release engine pin )"; exit 1; }
- api=https://api.github.com/repos/GetBusbar/busbar-release/releases
- name="busbar-release-ci-linux-x86_64-$RELEASE_REF"
- id=$(curl -fsSL -H "Authorization: Bearer $GH_TOKEN" "$api/tags/dev-bin" | jq -r --arg n "$name" '.assets[] | select(.name == $n) | .id')
- [ -n "$id" ] || { echo "::error::dev-bin has no $name"; exit 1; }
- curl -fsSL -H "Authorization: Bearer $GH_TOKEN" -H "Accept: application/octet-stream" -o "$RUNNER_TEMP/busbar-release" "$api/assets/$id"
- echo "$ENGINE_SHA256 $RUNNER_TEMP/busbar-release" | sha256sum -c -
- chmod +x "$RUNNER_TEMP/busbar-release"
- - { continue-on-error: true, uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c, with: { pattern: "r2-outbox-${{ env.SHA }}-*", path: r2-outbox, merge-multiple: true } } # v8.0.1
- # OLD-ENGINE SHIM (remove once ENGINE_REF is at or past 069f032): an engine without the verb
- # wrote the cache from the hop itself.
- - name: Publish the hop's cache puts (the R2 write keys, in this job only)
- env: { R2_KEY_ID: "${{ secrets.R2_CACHE_ACCESS_KEY_ID }}", R2_KEY_SECRET: "${{ secrets.R2_CACHE_SECRET_ACCESS_KEY }}" }
- run: |
- if ! "$RUNNER_TEMP/busbar-release" ci --help | grep -q cache-publish; then echo "engine $RELEASE_REF has no cache-publish: nothing published"; exit 0; fi
- "$RUNNER_TEMP/busbar-release" ci cache-publish --outbox "$GITHUB_WORKSPACE/r2-outbox"
- # A job with a write token (as is preflight-act). It never checks out the candidate: busbar-release at RELEASE_REF,
- # and the hop's verdict artifact. The sticky PR comment and the branch health issue (`ci publish`); a
- # hop whose runner was lost or timed out wrote no verdict, so `ci fallback` records the job and step
- # conclusions the API keeps after the logs are purged, and the run gate's reason when it refused the
- # run (STACK, wiring drift). A run the gate held neutral (run=0, preflight green) posts nothing: the
- # queue or another run judges that head.
- publish:
- needs: [preflight, hop]
- if: always() && needs.preflight.result != 'skipped' && (needs.preflight.result != 'success' || needs.preflight.outputs.run == '1')
- runs-on: ubuntu-latest
- timeout-minutes: 10
- # statuses: write: a merge group DENIED as its PR's own red (first in the group, or behind a green
- # group) marks that PR's head red
- # ("merge queue" status) beside its sticky verdict, so the watcher's fix agent starts at failure.
- permissions: { contents: read, actions: read, checks: read, pull-requests: write, statuses: write }
- env: { RELEASE_REF: "${{ needs.preflight.outputs.engine_ref }}", ENGINE_SHA256: "${{ needs.preflight.outputs.engine_sha256 }}" }
- steps:
- # THE ENGINE, downloaded, never compiled: CI's --no-default-features binary of RELEASE_REF from
- # busbar-release's dev-bin release (dev-bin.yml), run only when its sha256 is ENGINE_SHA256.
- # First the pin's shape: a full 40-hex commit and a 64-hex digest, or FAIL.
- - name: Engine (busbar-release at RELEASE_REF, sha256-pinned)
- env: { GH_TOKEN: "${{ secrets.TURNSTILE_DISPATCH_TOKEN }}" }
- run: |
- set -euo pipefail
- [[ "$RELEASE_REF" =~ ^[0-9a-f]{40}$ ]] || { echo "::error title=ENGINE-PIN FAIL::vars.ENGINE_REF is unset or not a full 40-hex busbar-release commit (busbar-release engine pin )"; exit 1; }
- [[ "$ENGINE_SHA256" =~ ^[0-9a-f]{64}$ ]] || { echo "::error title=ENGINE-PIN FAIL::vars.ENGINE_SHA256 is unset or not a 64-hex sha256 (busbar-release engine pin )"; exit 1; }
- api=https://api.github.com/repos/GetBusbar/busbar-release/releases
- name="busbar-release-ci-linux-x86_64-$RELEASE_REF"
- id=$(curl -fsSL -H "Authorization: Bearer $GH_TOKEN" "$api/tags/dev-bin" | jq -r --arg n "$name" '.assets[] | select(.name == $n) | .id')
- [ -n "$id" ] || { echo "::error::dev-bin has no $name"; exit 1; }
- curl -fsSL -H "Authorization: Bearer $GH_TOKEN" -H "Accept: application/octet-stream" -o "$RUNNER_TEMP/busbar-release" "$api/assets/$id"
- echo "$ENGINE_SHA256 $RUNNER_TEMP/busbar-release" | sha256sum -c -
- chmod +x "$RUNNER_TEMP/busbar-release"
- - { uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1, with: { repository: GetBusbar/busbar-release, ref: "${{ env.RELEASE_REF }}", token: "${{ secrets.TURNSTILE_DISPATCH_TOKEN }}", path: busbar-release, persist-credentials: false } } # v7
- - { id: verdict, continue-on-error: true, uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c, with: { name: "verdict-${{ env.SHA }}", path: verdict } } # v8.0.1
- - if: steps.verdict.outcome == 'success'
- working-directory: busbar-release
- run: $RUNNER_TEMP/busbar-release ci publish
- env: { GH_TOKEN: "${{ github.token }}", HEALTH_BRANCHES: none }
- - if: needs.preflight.result != 'success' || needs.hop.result != 'success'
- working-directory: busbar-release
- run: $RUNNER_TEMP/busbar-release ci fallback
- env: { GH_TOKEN: "${{ github.token }}", PR_NUMBER: "${{ github.event.pull_request.number }}", PREFLIGHT_RESULT: "${{ needs.preflight.result }}", PREFLIGHT_REASON: "${{ needs.preflight.outputs.reason }}", HOP_RESULT: "${{ needs.hop.result }}", HEALTH_BRANCHES: none }
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7f12b2eac8..75af599f25 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -950,6 +950,16 @@ each dialect translates, field by field, is listed in the generated
ToolUse input and ToolResult content); 1.5.5 showed only text blocks.
- A Bedrock response served from an Anthropic upstream carries `usage.cacheDetails`, the per-TTL
split of the cache-write tokens (additive: billed counts are unchanged).
+- A translated OpenAI Chat answer carries a model refusal in `message.refusal` / `delta.refusal`
+ and the serving `service_tier`, an image answer omits `created` when the upstream sent none, and
+ a translated request keeps a `developer` system prompt in the `developer` role.
+- A translated OpenAI Responses request keeps a `developer` system prompt as a `developer` input
+ item and sends a JSON tool result as the `function_call_output` text, and a translated Responses
+ stream ends at `response.failed` and names the serving `service_tier`.
+- A translated Gemini request or answer carries `functionCall.id` / `functionResponse.id`, a failed
+ tool result as `functionResponse.response.error`, a `fileData.mimeType`, the caller's
+ transcription prompt and temperature, and reads Vertex `createTime`, a `functionResponse.id` as
+ the call id, and the image content-policy stops as safety stops.
- A buffered OpenAI chat response carries its citations as nested `url_citation` annotations, the
shape the published Chat Completions schema defines.
- On a same-dialect route the caller receives every upstream response header except hop-by-hop
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 8aea311471..5e4db11e43 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -26,7 +26,7 @@ This page is the short version. Where the two differ, Part 6 wins.
both sides moved (theirs + ours − base; anything else stays a conflict):
`git config merge.ledger-count.driver "cargo xtask ledger-merge %O %A %B"`.
- Builds, tests, clippy, the gates and the oracle run in CI (ModernCI for `predev` PRs; `.github/workflows/promote.yml` for the promotion).
+ Builds, tests, clippy, the gates and the oracle run in CI: ModernCI, for `predev` PRs and for every promotion (`.ci/*.yaml`).
3. Push the lane and open a PR into `predev`. ModernCI (required check `ci`) judges and lands it.
Then stop; CI is the proof and a red comes back as a task.
4. The PR body says what changed, why, and the ruling or issue it implements. No AI attribution.
diff --git a/Cargo.lock b/Cargo.lock
index ca11475887..fe900ce5d4 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -579,6 +579,7 @@ dependencies = [
"axum",
"busbar-contract",
"busbar-kernel",
+ "busbar-kernel-egress",
"busbar-plugin-loader",
"busbar-transport-tcp-plugin",
"bytes",
@@ -733,7 +734,6 @@ dependencies = [
"busbar-kernel-scope",
"busbar-kernel-wal",
"busbar-plugin-loader",
- "busbar-store-memory",
"bytes",
"crc32fast",
"criterion",
diff --git a/Cargo.toml b/Cargo.toml
index eb15df80ac..289dea8d45 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -108,10 +108,10 @@ busbar-auth-sigv4 = { git = "https://github.com/GetBusbar/busbar-auth-sigv4", re
busbar-hook-ranking = { git = "https://github.com/GetBusbar/busbar-hook-ranking", rev = "7e1285e2e3293627723ab2754b9ddbfddeba9c46" }
# THE MEMORY STORE (kind: store), pulled at a pinned rev: the logic crate the root links on its store
# axis in every build (`[package.metadata.busbar.linked] store-memory`) and the plugin loader's `store`
-# both-ways fixture. The same package at the same rev, under the role the kernel's test build gives it
-# (`fixture-store`: a kernel source names no plugin). A rev bump moves both lines.
+# both-ways fixture. The kernel names it under no name: its test build links the in-crate R-FIX3
+# double (`busbar_kernel::test_support::store_double`), and the root's `linked_store_double` tests
+# prove the double states what this door states.
busbar-store-memory = { git = "https://github.com/GetBusbar/busbar-store-memory", rev = "848851e63ac7a454eb3d5daf9e11d739c48d738b" }
-fixture-store = { package = "busbar-store-memory", git = "https://github.com/GetBusbar/busbar-store-memory", rev = "848851e63ac7a454eb3d5daf9e11d739c48d738b" }
# ── A2A, from the a2aproject's OWN crates ──────────────────────────────────────────────────────
# THE OBVIOUS NAME IS THE WRONG ONE. `a2a-rs` on crates.io is `emillindfors/a2a-rs` (MIT, v0.5.0) —
# a different author entirely. The a2aproject family is the `a2a-*-lf` / `a2a-pb` set, all
diff --git a/README.md b/README.md
index fb5f0810aa..aba9701729 100644
--- a/README.md
+++ b/README.md
@@ -13,7 +13,6 @@
-
diff --git a/RELEASE.md b/RELEASE.md
index d13efc6719..8680b85a0c 100644
--- a/RELEASE.md
+++ b/RELEASE.md
@@ -1,33 +1,21 @@
# Releasing Busbar
-The branch ladder is **predev → dev → qa → main**. Pull requests into `predev` are judged and landed by
-ModernCI (required check `ci`, configured by [`.ci/default.yaml`](.ci/default.yaml)). The promotion
-from `predev` to `dev` runs [`.github/workflows/promote.yml`](.github/workflows/promote.yml). The release engine lives in the private `GetBusbar/busbar-release` repository
-(pinned outside this repo by the org variables `ENGINE_REF` and `ENGINE_SHA256`, set with
-`busbar-release engine pin `; `workflow-rules` R16 holds promote.yml to a 40-hex check and a
-sha256 verify of the engine at run time); busbar-release's `crates/busbar-release-train/ladder.json`
-says what entering each rung needs.
-
-| Rung entered (the PR's base) | What the hop runs | Consent |
-|---|---|---|
-| **`predev`** (from a `lane-*` branch) | every registered gate (`cargo xtask gate --list`, each by name) | none |
-| **`dev`** | the gates, the oracle against the 1.5.5 golden, conformance | none |
-| **`qa`** | the same, plus build + test, then the release binaries and the container | the owner (the org ruleset keeps `qa` locked) |
-| **`main`** | the same, then publish | the owner (the org ruleset keeps `main` locked) |
-
-A hop has two jobs, both in `promote.yml`:
-
-* **`preflight`**, on a free runner: `cargo fmt --all -- --check`, `cargo metadata --locked`, and the
- generated C header is current (`cargo xtask gate abi-header --write` leaves no diff).
-* **`hop`**, on a Latchkey runner sized by the rung: the pinned engine plans the hop and admits the
- candidate sha, and the verdict is uploaded as an artifact. When
- the rung needs no consent and the repository variable `PROMOTE_BOARD` is `true`, the engine
- fast-forwards the base branch to the exact sha it tested. Both promotions are **fast-forward
- pushes**: linear history is what makes "main's HEAD *is* the qa commit" true.
-
-> The entry builds a plan names (`release-binaries`, `container`, `publish`) are not yet ported into
-> the engine: `promote.yml` fails a hop that plans one with that message rather than skipping it.
-> Nothing is built or published until they land in busbar-release.
+The branch ladder is **predev → dev → qa → main**, and ModernCI judges and moves every rung from
+this repository's branch files (OWNER 2026-10-12, "ModernCI only"; the ruling is
+[`docs/design/BUSBAR-1.6.0.md`](docs/design/BUSBAR-1.6.0.md) Part 6, "BUSBAR-RELEASE RETIRED FROM
+BUSBAR"). A branch file is named for the branch being moved and says what the move must pass.
+
+| Move | Branch file | What it runs | Consent |
+|---|---|---|---|
+| a `lane-*` branch into **`predev`** | [`.ci/default.yaml`](.ci/default.yaml) | preflight, every registered gate, clippy, compile, test, doctest, deletion matrix, `cargo deny`; no worse than predev (required check `ci`) | none |
+| **`predev`** into **`dev`** | [`.ci/predev.yaml`](.ci/predev.yaml) | the gates, the oracle against the 1.5.5 golden, conformance, `scripts/verify-1.6.0-done.sh`, the Q128 audit | the owner's approval |
+| **`dev`** into **`qa`** | `.ci/dev.yaml` | the same, plus build and test, the fuzz smoke and the signing gate, the soak, the release binaries and the container | the owner (the org ruleset keeps `qa` locked) |
+| **`qa`** into **`main`** | `.ci/qa.yaml` | the same; on landing, the release is signed and published from the build that was judged, never rebuilt | the owner (the org ruleset keeps `main` locked) |
+
+`.ci/dev.yaml` and `.ci/qa.yaml` are not written yet: until they land, nothing is built or published
+from `qa` or `main`. The busbar-release hop (`promote.yml`, the engine pin) is deleted. busbar-release
+remains the home of the oracle (`busbar-oracle`, pinned by `testing/shadow-oracle/oracle-rust.pin`),
+the soak and the plugin fleet generator.
**The tag is an OUTPUT of a green release, never an input.** Nobody tags by hand; no workflow is
triggered by a `v*` tag (`cargo xtask gate workflow-rules`, rule R1). Docker Hub tag immutability
@@ -49,19 +37,17 @@ python3 .github/scripts/roll_changelog.py 1.6.0 CHANGELOG.md
```
That bumps `crates/busbar/Cargo.toml` + `Cargo.lock`, regenerates the committed OpenAPI schema, and
-rolls `[Unreleased]` to `[1.6.0]` with today's date. It does **not** tag. Open the result as a pull
-request against `dev` and let the hop run.
+rolls `[Unreleased]` to `[1.6.0]` with today's date. It does **not** tag. Land it on `predev` like
+any other change; it reaches `dev` with the next promotion.
## Moving a version up the ladder
-Open a pull request whose base is the next rung (`dev` into `qa`, `qa` into `main`; a lane branch into `predev`). `promote.yml`
-runs the hop on the PR; the run's check names are `preflight` and `hop`. A red hop is fixed
-forward on the lower branch and the pull request re-run: every push re-runs the hop on the new sha.
+ModernCI opens and judges each promotion (`modernci call moves` shows how each stands). A red move
+is fixed forward on the lower branch: every push re-judges it on the new head.
## Branch protection this assumes
-`qa` and `main` require the two contexts `preflight` and `hop` (plus the org ruleset's owner
-consent).
+`predev` requires ModernCI's check `ci`; `qa` and `main` take the owner's consent (the org ruleset).
Branch protection lives in repository rulesets, not in a script (see `.github/required-status-checks.md`).
Keep `enforce_admins: true`, `required_linear_history: true`, `allow_force_pushes: false` and
diff --git a/bin/oracle b/bin/oracle
index 8512b08b6c..4f6765c6ae 100755
--- a/bin/oracle
+++ b/bin/oracle
@@ -267,11 +267,14 @@ shim_dir="$root/tool"
mkdir -p "$root"
# ── obtain the Rust engine ──────────────────────────────────────────────────────────────────────
-# Precedence (soundest first, per the Phase C ruling "prefer `cargo build --release` from the pinned
-# busbar-release checkout"):
-# 1. BUSBAR_ORACLE_RUST_BIN= a prebuilt binary (CI builds it once, passes it here).
-# 2. BUSBAR_RELEASE_CHECKOUT= a busbar-release checkout to `cargo build --release` from.
-# 3. else git clone busbar-release at the pinned ref, then build.
+# Precedence:
+# 1. BUSBAR_ORACLE_RUST_BIN= a prebuilt binary chosen by the operator (verify-1.6.0-done.sh
+# refuses a DONE run with it set).
+# 2. on Linux: THE FETCHED JUDGE busbar-release's published `busbar-oracle-linux--`,
+# run only when its sha256 is the BASE pin's line for this
+# architecture (fetch_judge, below). No line: no judge, refused.
+# 3. elsewhere (a laptop): BUSBAR_RELEASE_CHECKOUT=, a busbar-release checkout to
+# `cargo build --release` from, else git clone busbar-release at the pinned ref, then build.
# ── the engine clone: a token is USED, never STORED (Q128 WARN, ARCHITECT 2026-10-05) ──────────────
# The token (the first of BUSBAR_RELEASE_TOKEN / GH_TOKEN / GITHUB_TOKEN that is set) reaches git as a
# one-shot `http..extraheader` through git's environment config (GIT_CONFIG_COUNT/KEY/VALUE):
@@ -352,11 +355,176 @@ if [ "${1:-}" = "token-selftest" ]; then
[ "$fail" = 0 ] && { echo "oracle token-selftest: the engine clone stores no credential"; exit 0; }
echo "oracle token-selftest: FAILED"; exit 1
fi
+# ── the pinned judge, FETCHED (OWNER 2026-10-13: no hand-installed tools; ModernCI installs no judge) ──
+# On Linux the judge is the binary busbar-release's dev-bin.yml built and published from the pinned
+# commit: `busbar-oracle-linux--` on the `dev-bin` release, run only when its
+# sha256 is the pin's `sha256_linux_` line. The pin read is the BASE's: $MODERNCI_TO_SHA's
+# testing/shadow-oracle/oracle-rust.pin when ModernCI names a base that holds one (a candidate never
+# picks its own judge; a pin bump judges from the commit after it lands), else this tree's (the first
+# predev -> dev move: dev holds no pin; .ci/predev.yaml says so). A pin with no line for this host's
+# architecture has NO JUDGE (red, never another architecture's, never a source build). A `.sig` beside
+# the asset is verified against busbar-release's release key (Ed25519 over "\n\n",
+# busbar-release-cli update.rs RELEASE_KEY_SPKI) before the bytes are ever made executable; a
+# `.sha256` beside it must agree with the pin. The verified binary is cached by its sha256 and
+# re-hashed on every use, so a cache cannot substitute a judge. The token (BUSBAR_RELEASE_TOKEN, else
+# GH_TOKEN, else GITHUB_TOKEN) reaches curl on stdin as a config line: never on an argv, never on disk.
+ORACLE_RELEASE_API="https://api.github.com/repos/GetBusbar/busbar-release"
+ORACLE_RELEASE_KEY_PEM='-----BEGIN PUBLIC KEY-----
+MCowBQYDK2VwAyEAT8gl0GhaEpxYXdRqfeNZ6KU8KgmI+8qbtP8UZPNPJiU=
+-----END PUBLIC KEY-----'
+_pin_in() { awk -F= -v k="$1" '$1==k{sub(/^[^=]*=/,"");print;exit}'; }
+sha256_of() { # sha256_of — the hex digest, or nothing
+ if command -v sha256sum >/dev/null 2>&1; then sha256sum -- "$1" | cut -c1-64
+ else shasum -a 256 -- "$1" | cut -c1-64; fi
+}
+judge_arch() { local a; a="$(uname -m)"; [ "$a" = arm64 ] && a=aarch64; printf '%s' "$a"; }
+judge_pin_text() { # judge_pin_text — the BASE pin's text (see above)
+ local repo="$1" rel="testing/shadow-oracle/oracle-rust.pin"
+ if [ -n "${MODERNCI_TO_SHA:-}" ] && git -C "$repo" cat-file -e "$MODERNCI_TO_SHA:$rel" 2>/dev/null; then
+ git -C "$repo" show "$MODERNCI_TO_SHA:$rel"
+ else
+ cat "$repo/$rel"
+ fi
+}
+release_api() { # release_api [ []] — an authenticated GET; the token never on an argv
+ local tok="${BUSBAR_RELEASE_TOKEN:-${GH_TOKEN:-${GITHUB_TOKEN:-}}}"
+ [ -n "$tok" ] || { echo "oracle: no token to read busbar-release's dev-bin release (BUSBAR_RELEASE_TOKEN, GH_TOKEN or GITHUB_TOKEN)" >&2; return 1; }
+ local accept="${2:-application/vnd.github+json}"
+ if [ -n "${3:-}" ]; then
+ printf 'header = "Authorization: Bearer %s"\n' "$tok" | curl -fsSL --retry 3 -K - -H "Accept: $accept" -o "$3" "$1"
+ else
+ printf 'header = "Authorization: Bearer %s"\n' "$tok" | curl -fsSL --retry 3 -K - -H "Accept: $accept" "$1"
+ fi
+}
+judge_assets() { # judge_assets — every dev-bin asset as `\t`
+ local out="$1" rid page n
+ rid="$(release_api "$ORACLE_RELEASE_API/releases/tags/dev-bin" | jq -r '.id')" || return 1
+ [[ "$rid" =~ ^[0-9]+$ ]] || { echo "oracle: busbar-release has no dev-bin release" >&2; return 1; }
+ : >"$out"
+ for page in $(seq 1 20); do
+ n="$(release_api "$ORACLE_RELEASE_API/releases/$rid/assets?per_page=100&page=$page" \
+ | jq -r '.[] | "\(.id)\t\(.name)"' | tee -a "$out" | wc -l)" || return 1
+ [ "$n" -lt 100 ] && break
+ done
+ return 0
+}
+fetch_asset() { # fetch_asset — : a directory (the selftest) or an asset list
+ local src="$1" name="$2" out="$3" id
+ if [ -d "$src" ]; then [ -f "$src/$name" ] && cp "$src/$name" "$out"; return; fi
+ id="$(awk -F'\t' -v n="$name" '$2==n{print $1; exit}' "$src")"
+ [ -n "$id" ] || return 1
+ release_api "$ORACLE_RELEASE_API/releases/assets/$id" application/octet-stream "$out"
+}
+verify_judge_sig() { # verify_judge_sig
+ local pem="$1" name="$2" hex="$3" sig="$4" d rc=0
+ command -v openssl >/dev/null 2>&1 || { echo "oracle: refusing the judge: $name has a .sig and there is no openssl to verify it" >&2; return 1; }
+ d="$(mktemp -d)"
+ printf '%s\n' "$pem" >"$d/key.pem"
+ printf '%s\n%s\n' "$name" "$hex" >"$d/msg"
+ if ! tr -d '\n\r ' <"$sig" | base64 -d >"$d/sig" 2>/dev/null; then rc=1
+ elif ! openssl pkeyutl -verify -pubin -inkey "$d/key.pem" -rawin -in "$d/msg" -sigfile "$d/sig" >/dev/null 2>&1; then rc=1; fi
+ rm -rf "$d"
+ [ "$rc" = 0 ] || echo "oracle: refusing the judge: $name's .sig is not busbar-release's release key's signature of it" >&2
+ return "$rc"
+}
+fetch_judge() { # fetch_judge [] — prints the verified judge
+ local text="$1" cache="$2" src="$3" pem="${4:-$ORACLE_RELEASE_KEY_PEM}" arch ref want name bin tmp got side
+ arch="$(judge_arch)"
+ ref="$(printf '%s\n' "$text" | _pin_in release_ref)"
+ want="$(printf '%s\n' "$text" | _pin_in "sha256_linux_$arch")"
+ [[ "$ref" =~ ^[0-9a-f]{40}$ ]] || { echo "oracle: no judge: the pin names no full 40-hex release_ref" >&2; return 1; }
+ [[ "$want" =~ ^[0-9a-f]{64}$ ]] || { echo "oracle: no judge: the pin has no sha256_linux_$arch line, and a host whose architecture the pin does not name has no judge" >&2; return 1; }
+ name="busbar-oracle-linux-$arch-$ref"
+ bin="$cache/$want/busbar-oracle"
+ if [ -x "$bin" ] && [ "$(sha256_of "$bin")" = "$want" ]; then printf '%s\n' "$bin"; return 0; fi
+ tmp="$(mktemp -d)"
+ if [ "$src" = api ]; then judge_assets "$tmp/assets.tsv" || { rm -rf "$tmp"; return 1; }; src="$tmp/assets.tsv"; fi
+ fetch_asset "$src" "$name" "$tmp/$name" || { echo "oracle: no judge: $name is not on busbar-release's dev-bin release" >&2; rm -rf "$tmp"; return 1; }
+ got="$(sha256_of "$tmp/$name")"
+ [ "$got" = "$want" ] || { echo "oracle: refusing the judge: $name has sha256 $got, the pin says $want" >&2; rm -rf "$tmp"; return 1; }
+ if fetch_asset "$src" "$name.sha256" "$tmp/$name.sha256" 2>/dev/null; then
+ side="$(cut -c1-64 <"$tmp/$name.sha256")"
+ [ "$side" = "$want" ] || { echo "oracle: refusing the judge: its published .sha256 ($side) is not the pin's $want" >&2; rm -rf "$tmp"; return 1; }
+ fi
+ if fetch_asset "$src" "$name.sig" "$tmp/$name.sig" 2>/dev/null; then
+ verify_judge_sig "$pem" "$name" "$got" "$tmp/$name.sig" || { rm -rf "$tmp"; return 1; }
+ fi
+ mkdir -p "$cache/$want"
+ mv "$tmp/$name" "$bin.part" && chmod +x "$bin.part" && mv -f "$bin.part" "$bin"
+ rm -rf "$tmp"
+ printf '%s\n' "$bin"
+}
+if [ "${1:-}" = "fetch-selftest" ]; then
+ # RED-before-green for the fetched judge, offline: a fixture "release" directory stands in for the
+ # dev-bin API (fetch_asset's directory source), and a throwaway Ed25519 key for busbar-release's.
+ st="$(mktemp -d)"; trap 'rm -rf "$st"' EXIT
+ fail=0 arch="$(judge_arch)" ref="0123456789abcdef0123456789abcdef01234567"
+ name="busbar-oracle-linux-$arch-$ref"
+ mkdir -p "$st/rel" "$st/cache"
+ printf '#!/bin/sh\necho planted-judge\n' >"$st/rel/$name"
+ good="$(sha256_of "$st/rel/$name")"
+ pin_ok="release_ref=$ref
+sha256_linux_$arch=$good"
+ if p="$(fetch_judge "$pin_ok" "$st/cache" "$st/rel" 2>/dev/null)" && [ "$("$p")" = planted-judge ]; then
+ echo " [ok] the pinned digest's judge is fetched, cached and runs"
+ else echo " [FAILED] the pinned digest's judge was not fetched"; fail=1; fi
+ rm -f "$st/rel/$name"
+ if p="$(fetch_judge "$pin_ok" "$st/cache" "$st/rel" 2>/dev/null)" && [ "$("$p")" = planted-judge ]; then
+ echo " [ok] a cached judge is reused by its digest, with no source"
+ else echo " [FAILED] the cached judge was not reused"; fail=1; fi
+ printf '#!/bin/sh\necho planted-judge\n' >"$st/rel/$name"
+ pin_wrong="release_ref=$ref
+sha256_linux_$arch=$(printf 'f%.0s' $(seq 1 64))"
+ if fetch_judge "$pin_wrong" "$st/cache2" "$st/rel" >/dev/null 2>"$st/err"; then
+ echo " [FAILED] a judge whose sha256 is not the pin's ran"; fail=1
+ elif grep -q "refusing the judge" "$st/err"; then echo " [ok] a wrong sha256 refuses the judge"
+ else echo " [FAILED] the wrong-sha refusal did not say so: $(cat "$st/err")"; fail=1; fi
+ if fetch_judge "release_ref=$ref" "$st/cache3" "$st/rel" >/dev/null 2>"$st/err"; then
+ echo " [FAILED] a pin with no line for this architecture produced a judge"; fail=1
+ elif grep -q "no judge" "$st/err"; then echo " [ok] a pin with no sha256_linux_$arch line has no judge"
+ else echo " [FAILED] the missing-line refusal did not say \"no judge\": $(cat "$st/err")"; fail=1; fi
+ printf 'e%.0s' $(seq 1 64) >"$st/rel/$name.sha256"
+ if fetch_judge "$pin_ok" "$st/cache4" "$st/rel" >/dev/null 2>/dev/null; then
+ echo " [FAILED] a published .sha256 disagreeing with the pin was accepted"; fail=1
+ else echo " [ok] a published .sha256 disagreeing with the pin refuses"; fi
+ printf '%s %s\n' "$good" "$name" >"$st/rel/$name.sha256"
+ if command -v openssl >/dev/null 2>&1 && openssl genpkey -algorithm ed25519 -out "$st/k.pem" 2>/dev/null \
+ && openssl pkey -in "$st/k.pem" -pubout -out "$st/pub.pem" 2>/dev/null; then
+ printf '%s\n%s\n' "$name" "$good" >"$st/msg"
+ openssl pkeyutl -sign -inkey "$st/k.pem" -rawin -in "$st/msg" -out "$st/sig.bin" 2>/dev/null
+ base64 <"$st/sig.bin" | tr -d '\n' >"$st/rel/$name.sig"
+ if fetch_judge "$pin_ok" "$st/cache5" "$st/rel" "$(cat "$st/pub.pem")" >/dev/null 2>&1; then
+ echo " [ok] a .sig made by the release key is accepted"
+ else echo " [FAILED] a good .sig was refused"; fail=1; fi
+ if fetch_judge "$pin_ok" "$st/cache6" "$st/rel" >/dev/null 2>"$st/err"; then
+ echo " [FAILED] a .sig made by another key was accepted under busbar-release's key"; fail=1
+ elif grep -q "release key" "$st/err"; then echo " [ok] a .sig made by another key refuses"
+ else echo " [FAILED] the bad-.sig refusal did not say so: $(cat "$st/err")"; fail=1; fi
+ else echo " [FAILED] no openssl with Ed25519 here: the .sig check cannot be proven"; fail=1; fi
+ # THE BASE PIN wins over the tree's when ModernCI names a base that holds one.
+ git init -q "$st/repo" && mkdir -p "$st/repo/testing/shadow-oracle" \
+ && printf 'release_ref=base\n' >"$st/repo/testing/shadow-oracle/oracle-rust.pin" \
+ && git -C "$st/repo" add -A && git -C "$st/repo" -c user.name=t -c user.email=t@t.invalid \
+ -c commit.gpgsign=false -c core.hooksPath=/dev/null commit -q -m pin \
+ && printf 'release_ref=head\n' >"$st/repo/testing/shadow-oracle/oracle-rust.pin" \
+ || { echo " [FAILED] could not build the base-pin fixture"; fail=1; }
+ b="$(git -C "$st/repo" rev-parse HEAD 2>/dev/null)"
+ if [ "$(MODERNCI_TO_SHA="$b" judge_pin_text "$st/repo")" = "release_ref=base" ] \
+ && [ "$(MODERNCI_TO_SHA= judge_pin_text "$st/repo")" = "release_ref=head" ]; then
+ echo " [ok] the base's pin is read when ModernCI names a base, the tree's otherwise"
+ else echo " [FAILED] the judge's pin was not read from the base"; fail=1; fi
+ [ "$fail" = 0 ] && { echo "oracle fetch-selftest: the judge is the pinned digest's, or there is none"; exit 0; }
+ echo "oracle fetch-selftest: FAILED"; exit 1
+fi
resolve_bin() {
if [ -n "${BUSBAR_ORACLE_RUST_BIN:-}" ]; then
[ -x "$BUSBAR_ORACLE_RUST_BIN" ] || { echo "oracle: BUSBAR_ORACLE_RUST_BIN is not executable: $BUSBAR_ORACLE_RUST_BIN" >&2; exit 1; }
printf '%s\n' "$BUSBAR_ORACLE_RUST_BIN"; return 0
fi
+ if [ "$(uname -s)" = Linux ]; then
+ fetch_judge "$(judge_pin_text "$here")" "${XDG_CACHE_HOME:-$HOME/.cache}/busbar-oracle-judge" api || exit 1
+ return 0
+ fi
local built="$root/checkout/target/release/$BINNAME"
if [ -x "$built" ]; then printf '%s\n' "$built"; return 0; fi
local checkout
diff --git a/conformance/manifest.json b/conformance/manifest.json
index a154e21d4f..a7f59508cf 100644
--- a/conformance/manifest.json
+++ b/conformance/manifest.json
@@ -37,7 +37,7 @@
},
{
"claim": null,
- "plan": "plane battery + served POST /v1/systemone on a booted busbar (systemone only; GET /v1/models keeps its 1.5.5 bytes)",
+ "plan": "plane battery + served POST /v1/systemone (one model; two models with upstream_model) and POST /api/evaluate (nanojev) on booted busbars; GET /v1/models keeps its 1.5.5 bytes",
"public": true,
"reason": "not served: POST /v1/systemone answers 404, the absence code on this boot; the decisions plane is registered and configured but no route serves its operation (the kernel plane driver serves it)",
"standard": "jev decision plane",
diff --git a/conformance/registry.toml b/conformance/registry.toml
index 5b5bdc7d9e..e15f07d5de 100644
--- a/conformance/registry.toml
+++ b/conformance/registry.toml
@@ -149,7 +149,7 @@ not_run_reason = "planned 1.6.0 — h2spec leg not yet green (MATRIX-RULING B-ti
[[suite]]
id = "jev"
standard = "jev decision plane"
-plan = "plane battery + served POST /v1/systemone on a booted busbar (systemone only; GET /v1/models keeps its 1.5.5 bytes)"
+plan = "plane battery + served POST /v1/systemone (one model; two models with upstream_model) and POST /api/evaluate (nanojev) on booted busbars; GET /v1/models keeps its 1.5.5 bytes"
tier = "spec-conformant"
label = "jev"
verdict = "conformance-verdict-jev"
diff --git a/crates/busbar-a2a/src/a2a/config.rs b/crates/busbar-a2a/src/a2a/config.rs
index 0076bcdaa5..5fb69036f7 100644
--- a/crates/busbar-a2a/src/a2a/config.rs
+++ b/crates/busbar-a2a/src/a2a/config.rs
@@ -299,7 +299,7 @@ pub struct AgentDefCfg {
///
/// WHAT IT NEVER PERMITS, and this is the half that makes the knob safe to have: a
/// CLOUD-METADATA endpoint. `169.254.169.254` and its family are refused whether this is set or
- /// not — see [`busbar_kernel::net_guard::ip_is_cloud_metadata`], whose own doc gives the reason: an
+ /// not — see [`busbar_contract::net::ip_is_cloud_metadata`], whose own doc gives the reason: an
/// `allow_private` that reached IMDS would be a configuration flag that hands out cloud
/// credentials.
///
diff --git a/crates/busbar-a2a/src/a2a/fetch.rs b/crates/busbar-a2a/src/a2a/fetch.rs
index f48ceaa689..b1984bed9f 100644
--- a/crates/busbar-a2a/src/a2a/fetch.rs
+++ b/crates/busbar-a2a/src/a2a/fetch.rs
@@ -2,7 +2,7 @@
// Copyright (C) 2026 Busbar Inc and contributors
//! THE AGENT CARD FETCH: the card-shaped part of a guarded fetch, over the guard in
-//! [`busbar_kernel::net_guard`].
+//! [`busbar_kernel::proxy::egress_unit::trust::net`].
//!
//! Everything else on this plane decides. This is the one module that REACHES OUT, at a URL an
//! operator wrote and following redirects a stranger controls, and it therefore carries the whole
@@ -11,7 +11,7 @@
//! ## THE GUARD IS NOT HERE, and that is the point
//!
//! Resolve-then-pin, the address judgement, the metadata arm that sits ahead of `allow_private`, the
-//! hop bound and the body cap all live in [`busbar_kernel::net_guard`]. They were written twice — once here
+//! hop bound and the body cap all live in [`busbar_kernel::proxy::egress_unit::trust::net`]. They were written twice — once here
//! and once for the MCP dispatch path — and the doc comments in each copy had already started
//! citing the other's function names as the reason an ordering was correct. A security control that
//! has to cite its twin to explain itself has two implementations and one of them will be the stale
@@ -52,7 +52,9 @@
use std::net::IpAddr;
-use busbar_kernel::net_guard::{self, AddressRefusal, GuardPolicy, PinnedTarget};
+use busbar_kernel::proxy::egress_unit::trust::net::{
+ self, AddressRefusal, GuardPolicy, PinnedTarget,
+};
use super::card::{WELL_KNOWN_CARD_PATH, WELL_KNOWN_CARD_PATH_LEGACY};
@@ -61,7 +63,7 @@ use super::card::{WELL_KNOWN_CARD_PATH, WELL_KNOWN_CARD_PATH_LEGACY};
/// Re-exported rather than redeclared: a second trait with the same shape would let a transport be
/// written against one and a guard against the other, which is how two implementations of one
/// control start.
-pub(crate) use busbar_kernel::net_guard::Resolver;
+pub(crate) use busbar_kernel::proxy::egress_unit::trust::net::Resolver;
/// The operator's fetch policy. Config, therefore intent.
#[derive(Clone, Debug, PartialEq, Eq)]
@@ -88,7 +90,7 @@ pub(crate) struct FetchPolicy {
/// concept and two spellings of it would be two things to learn and two things to get wrong.
/// It relaxes the loopback/private ARMS of the guard and NOTHING else: a cloud-metadata name
/// and a cloud-metadata address are refused with this set, because
- /// [`busbar_kernel::net_guard::judge_address`] tests the metadata arm BEFORE it reads this flag, and
+ /// [`busbar_kernel::proxy::egress_unit::trust::net::judge_address`] tests the metadata arm BEFORE it reads this flag, and
/// the alternate-IPv4-encoding arm is likewise unconditional. An `allow_private` that reached
/// IMDS would be a config flag that hands out cloud credentials.
pub(crate) allow_private: bool,
@@ -320,7 +322,7 @@ impl Pin {
}
}
-/// GUARD ONE HOP AND PIN IT: the card fetch's door onto [`busbar_kernel::net_guard::resolve_and_pin`].
+/// GUARD ONE HOP AND PIN IT: the card fetch's door onto [`busbar_kernel::proxy::egress_unit::trust::net::resolve_and_pin`].
///
/// Returns the parsed URL BESIDE the pin, because the two are needed together and for different
/// things: the socket goes to [`PinnedTarget::addr`], and the request carries the URL — its host in
@@ -332,10 +334,10 @@ impl Pin {
///
/// The one difference between the two callers of the guard that could NOT be parameterised away.
/// The MCP dispatch path wants a strict recogniser on an attacker-influenced string, and gets
-/// [`busbar_kernel::net_guard::split_url`]. This path must FOLLOW redirects, and following one means
+/// [`busbar_kernel::proxy::egress_unit::trust::net::split_url`]. This path must FOLLOW redirects, and following one means
/// joining a relative `Location` against the hop that sent it exactly as a client would — which
/// needs a real URL type and its resolution rules, not a splitter. Both then bring the host they
-/// parsed through the SAME [`busbar_kernel::net_guard::judge_host_name`] and the same resolve-then-pin, so
+/// parsed through the SAME [`busbar_kernel::proxy::egress_unit::trust::net::judge_host_name`] and the same resolve-then-pin, so
/// what differs is the recognition and never the judgement.
pub(crate) fn guard_hop(
url: &str,
@@ -364,7 +366,7 @@ pub(crate) fn guard_hop(
// `http://127.0.0.1` has made ONE decision; making them write two flags would teach that the
// second one is harmless. `allow_plaintext` stays its own field because a plaintext fetch of a
// PUBLIC host is a different, worse thing than a plaintext fetch of loopback.
- if net_guard::judge_scheme(parsed.as_str(), https, guard).is_err() {
+ if net::judge_scheme(parsed.as_str(), https, guard).is_err() {
return Err(FetchRefusal::NotHttps {
url: parsed.to_string(),
scheme,
@@ -382,12 +384,12 @@ pub(crate) fn guard_hop(
let host = host.strip_suffix('.').unwrap_or(host);
let port = parsed
.port_or_known_default()
- .unwrap_or_else(|| net_guard::default_port(https));
+ .unwrap_or_else(|| net::default_port(https));
// ── THE GUARD. Structural name refusals, then EXACTLY ONE resolution, then EVERY answered
// address, then the pin. All of it core's, including the ordering that keeps the
// cloud-metadata arm ahead of `allow_private`.
- let target = net_guard::resolve_and_pin(host, port, https, resolver, guard)?;
+ let target = net::resolve_and_pin(host, port, https, resolver, guard)?;
Ok((parsed, target))
}
@@ -466,7 +468,7 @@ pub(crate) fn fetch_card(
if (300..400).contains(&resp.status) {
// THE HOP BOUND IS CORE'S. A guard applied correctly to an unbounded chain is a way to
// spend the process, and the bound is the same one every guarded fetch gets.
- net_guard::refuse_hop_overflow(hops, url.as_str(), policy.guard())?;
+ net::refuse_hop_overflow(hops, url.as_str(), policy.guard())?;
let location = resp
.location
.as_deref()
@@ -499,7 +501,7 @@ pub(crate) fn fetch_card(
}
// THE BODY CAP IS CORE'S, and it is applied BEFORE the bytes are parsed.
- net_guard::refuse_oversized_body(url.as_str(), resp.body.len(), policy.guard())?;
+ net::refuse_oversized_body(url.as_str(), resp.body.len(), policy.guard())?;
let document: serde_json::Value =
serde_json::from_slice(&resp.body).map_err(|e| FetchRefusal::NotACard {
url: url.to_string(),
diff --git a/crates/busbar-a2a/src/a2a/mod.rs b/crates/busbar-a2a/src/a2a/mod.rs
index 757c445312..8f31aac98f 100644
--- a/crates/busbar-a2a/src/a2a/mod.rs
+++ b/crates/busbar-a2a/src/a2a/mod.rs
@@ -33,7 +33,7 @@
//
// AND THE ROUTER NOW RELAYS. [`relay`] is the hop `ingress::invoke` makes to the registered backend
// agent: it guards and pins the target through the SAME `fetch::guard_hop` — and therefore the
-// same `busbar_kernel::net_guard` resolve-then-pin — the card fetch
+// same the egress unit's guard (`busbar_kernel::proxy::egress_unit::trust::net`) resolve-then-pin — the card fetch
// uses, RE-ASKS the trust question against the live registry immediately before the socket so a
// mid-flight demotion is not something an in-flight request escapes, presents BUSBAR'S OWN leased
// credential or none, and turns every way the hop can fail into a busbar-attributed error rather
diff --git a/crates/busbar-a2a/src/a2a/pushnotify.rs b/crates/busbar-a2a/src/a2a/pushnotify.rs
index d85826ad7f..8be429f3bf 100644
--- a/crates/busbar-a2a/src/a2a/pushnotify.rs
+++ b/crates/busbar-a2a/src/a2a/pushnotify.rs
@@ -31,7 +31,7 @@
//!
//! ## The address predicates are NOT re-implemented here
//!
-//! The CGNAT, unique-local, link-local and alternate-encoding atoms live in [`busbar_kernel::net_guard`] and
+//! The CGNAT, unique-local, link-local and alternate-encoding atoms live in [`busbar_kernel::proxy::egress_unit::trust::net`] and
//! are used from there. Duplicated security logic is the one kind of duplication that cannot be made
//! safe by documenting it: somebody hardens one copy against a new obfuscation and never learns the
//! other exists.
@@ -131,10 +131,10 @@ pub(crate) struct PinnedCallback {
///
/// The tear-out went the other way too — the three ranges this copy had and the shared predicate
/// did not (`0.0.0.0/8`, `192.0.0.0/24`, `198.18.0.0/15`) moved INTO
-/// [`busbar_kernel::net_guard::ipv4_is_internal`] first, so no guard lost coverage in the unification. That
+/// [`busbar_contract::net::ipv4_is_internal`] first, so no guard lost coverage in the unification. That
/// ordering is the whole discipline: widen the shared predicate to the union, then delete the copy.
pub(crate) fn is_internal_addr(ip: &IpAddr) -> bool {
- busbar_kernel::net_guard::ip_is_internal(ip)
+ busbar_contract::net::ip_is_internal(ip)
}
/// Split a URL into `(scheme, host)`, the host read by the one shared URL reader.
@@ -203,7 +203,7 @@ fn structural_check(url: &str) -> Result {
if scheme != "https" {
return Err(PushNotifyError::Scheme(scheme));
}
- if busbar_kernel::net_guard::is_alternate_ipv4_encoding(&host) {
+ if busbar_contract::net::is_alternate_ipv4_encoding(&host) {
return Err(PushNotifyError::ObfuscatedHost(host));
}
// A canonical IP LITERAL is checked directly and is not subject to the resolver's answer at all
diff --git a/crates/busbar-a2a/src/a2a/relay.rs b/crates/busbar-a2a/src/a2a/relay.rs
index a5c04d7c8f..1c35de6af3 100644
--- a/crates/busbar-a2a/src/a2a/relay.rs
+++ b/crates/busbar-a2a/src/a2a/relay.rs
@@ -34,7 +34,7 @@
//! ## 2. THE NAME IS RESOLVED ONCE, BY THE GUARD, AND THE JUDGED ADDRESS IS WHAT CONNECTS
//!
//! This module does not open its own client. It reuses [`super::fetch::guard_hop`], and through it
-//! [`busbar_kernel::net_guard::resolve_and_pin`] — the same
+//! [`busbar_kernel::proxy::egress_unit::trust::net::resolve_and_pin`] — the same
//! guard the card fetch goes through — and hands the surviving address to the transport, which pins
//! it. A relay that handed the URL to `reqwest` and let the client resolve the host would reinstate
//! the second lookup, which is the whole of DNS rebinding, and would pass every test that does not
@@ -90,9 +90,8 @@
use super::creds::{Lease, LeaseError};
use super::fetch::{FetchPolicy, FetchRefusal, HttpResponse, Resolver};
use super::task::TaskState;
-use busbar_kernel::{
- breaker::normalize_raw_error, net_guard::PinnedTarget, plane_host::breaker as host_breaker,
-};
+use busbar_kernel::proxy::egress_unit::trust::net::PinnedTarget;
+use busbar_kernel::{breaker::normalize_raw_error, plane_host::breaker as host_breaker};
/// The HTTP round trip the relay makes, as a seam.
///
@@ -1610,7 +1609,7 @@ fn prepare<'a>(
admit_id: &mut busbar_contract::abi::hot::AdmissionId,
) -> Result<(url::Url, PinnedTarget, OutboundRelayRequest), RelayRefusal> {
// ── THE GUARD. One resolution, every answered address judged, one pinned address out. It is
- // `busbar_kernel::net_guard`'s, reached through the card fetch's hop door, so a relayed submission
+ // the egress unit's guard (`busbar_kernel::proxy::egress_unit::trust::net`)'s, reached through the card fetch's hop door, so a relayed submission
// and a card fetch cannot be guarded to two different standards.
// `call.policy`, NOT `seam.policy()`. The seam answers with the plane's fail-closed default and
// knows nothing about any registration; the call carries the one the operator's `allow_private:`
diff --git a/crates/busbar-a2a/src/a2a/tests/fetch_tests.rs b/crates/busbar-a2a/src/a2a/tests/fetch_tests.rs
index b5be265c55..7deaf7015c 100644
--- a/crates/busbar-a2a/src/a2a/tests/fetch_tests.rs
+++ b/crates/busbar-a2a/src/a2a/tests/fetch_tests.rs
@@ -830,7 +830,7 @@ fn allow_private_never_opens_a_cloud_metadata_target() {
}
/// `allow_private` ADMITS PLAINTEXT, because that is what the word means on the `tools:` plane
-/// (both planes ask `busbar_kernel::net_guard::judge_scheme`, which refuses `http://` with exactly
+/// (both planes ask `busbar_kernel::proxy::egress_unit::trust::net::judge_scheme`, which refuses `http://` with exactly
/// `!https && !plaintext_admissible()`), and a
/// loopback agent that could only be reached over TLS is a knob that does not do its job.
#[test]
diff --git a/crates/busbar-a2a/src/a2a/tests/pushnotify_tests.rs b/crates/busbar-a2a/src/a2a/tests/pushnotify_tests.rs
index 281da258ab..0054a54fed 100644
--- a/crates/busbar-a2a/src/a2a/tests/pushnotify_tests.rs
+++ b/crates/busbar-a2a/src/a2a/tests/pushnotify_tests.rs
@@ -49,7 +49,7 @@ fn every_internal_range_is_refused() {
("IETF protocol 192.0.0/24", v4(192, 0, 0, 8)),
("benchmarking 198.18/15", v4(198, 19, 0, 1)),
// AZURE WIRESERVER AND THE DOCUMENTATION BLOCK were missing from this plane's private copy
- // of the predicate while the shared one in `net_guard` had them. `168.63.129.16` is the
+ // of the predicate while the shared one in `busbar_contract::net` had them. `168.63.129.16` is the
// whole point: it is a PUBLIC address, so every range check in the copy missed it, and a
// caller could register it as a push callback and have busbar fetch Azure's platform
// metadata on its behalf. The copy's own doc claimed to cover "a cloud metadata address".
diff --git a/crates/busbar-a2a/src/a2a/words.rs b/crates/busbar-a2a/src/a2a/words.rs
index 54641ddb4c..14dd6836d5 100644
--- a/crates/busbar-a2a/src/a2a/words.rs
+++ b/crates/busbar-a2a/src/a2a/words.rs
@@ -10,7 +10,7 @@
//! shape is a body the official TCK rejects by schema.
//!
//! That split — **a caller keeps its refusal VOCABULARY, not its DECISION** — is
-//! `busbar_kernel::net_guard`'s rule, stated for a second concern. It is why `mcp/envelope.rs` and this
+//! the egress unit's guard (`busbar_kernel::proxy::egress_unit::trust::net`)'s rule, stated for a second concern. It is why `mcp/envelope.rs` and this
//! file can say completely different things about the same refusal without either of them deciding
//! when it happens.
diff --git a/crates/busbar-contract/include/busbar_plugin.h b/crates/busbar-contract/include/busbar_plugin.h
index 0d9082e9cf..8d07166e82 100644
--- a/crates/busbar-contract/include/busbar_plugin.h
+++ b/crates/busbar-contract/include/busbar_plugin.h
@@ -374,6 +374,10 @@ extern "C" {
#define BB_PLANE_UNITS_ESTIMATED UINT32_C(0) /* [`UnitCount::source`]: the plane's own estimate; never billed. */
#define BB_PLANE_UNITS_REPORTED UINT32_C(1) /* [`UnitCount::source`]: the far end reported it; it bills. */
#define BB_PLANE_UNITS_FLOOR UINT32_C(2) /* [`UnitCount::source`]: the plane's floor for a delivered reply whose far-end usage could not be */
+#define BB_PLANE_TAP_FAULT_NONE UINT8_C(0) /* [`OnPieceOut::tap_fault`]: no usage-tap fault. */
+#define BB_PLANE_TAP_FAULT_DECODE UINT8_C(1) /* [`OnPieceOut::tap_fault`]: the cell's own reader refused the body (`reason="decode"`). */
+#define BB_PLANE_TAP_FAULT_BAD_JSON UINT8_C(2) /* [`OnPieceOut::tap_fault`]: the body is not JSON (`reason="bad_json"`). */
+#define BB_PLANE_TAP_FAULT_UNKNOWN_PROTOCOL UINT8_C(3) /* [`OnPieceOut::tap_fault`]: the caller's dialect is not one the tap reads */
#define BB_PLANE_PRINCIPAL_NONE UINT32_C(0) /* [`ArriveOut::principal_need`]: no principal. */
#define BB_PLANE_PRINCIPAL_REQUIRED UINT32_C(1) /* [`ArriveOut::principal_need`]: the kernel must verify a principal before the first piece. */
#define BB_PLANE_PRINCIPAL_OPTIONAL UINT32_C(2) /* [`ArriveOut::principal_need`]: verify one if the caller presents it. */
@@ -2793,7 +2797,8 @@ struct bb_plane_OnPieceOut {
bb_mech_Span target;
uint32_t need;
uint8_t fault;
- uint8_t _fault_reserved[3];
+ uint8_t tap_fault;
+ uint8_t _fault_reserved[2];
bb_mech_Span lane;
uint32_t final_status;
uint32_t _final_reserved;
@@ -5292,7 +5297,8 @@ BB_ASSERT(offsetof(bb_plane_OnPieceOut, verb) == 160, "bb_plane_OnPieceOut.verb:
BB_ASSERT(offsetof(bb_plane_OnPieceOut, target) == 168, "bb_plane_OnPieceOut.target: offset");
BB_ASSERT(offsetof(bb_plane_OnPieceOut, need) == 176, "bb_plane_OnPieceOut.need: offset");
BB_ASSERT(offsetof(bb_plane_OnPieceOut, fault) == 180, "bb_plane_OnPieceOut.fault: offset");
-BB_ASSERT(offsetof(bb_plane_OnPieceOut, _fault_reserved) == 181, "bb_plane_OnPieceOut._fault_reserved: offset");
+BB_ASSERT(offsetof(bb_plane_OnPieceOut, tap_fault) == 181, "bb_plane_OnPieceOut.tap_fault: offset");
+BB_ASSERT(offsetof(bb_plane_OnPieceOut, _fault_reserved) == 182, "bb_plane_OnPieceOut._fault_reserved: offset");
BB_ASSERT(offsetof(bb_plane_OnPieceOut, lane) == 184, "bb_plane_OnPieceOut.lane: offset");
BB_ASSERT(offsetof(bb_plane_OnPieceOut, final_status) == 192, "bb_plane_OnPieceOut.final_status: offset");
BB_ASSERT(offsetof(bb_plane_OnPieceOut, _final_reserved) == 196, "bb_plane_OnPieceOut._final_reserved: offset");
diff --git a/crates/busbar-contract/src/abi/hot/host.rs b/crates/busbar-contract/src/abi/hot/host.rs
index 75712da13f..7e9f198f78 100644
--- a/crates/busbar-contract/src/abi/hot/host.rs
+++ b/crates/busbar-contract/src/abi/hot/host.rs
@@ -535,12 +535,13 @@ pub type CounterAddFn = extern "C-unwind" fn(
// ── THE HOST SERVICES (minor 30; ARCHITECT SD-3 queue (6), DEC-SERVE G2). The contract ports a
// codec reaches — `busbar_contract::codec::{fill_entropy, wall_clock_now,
-// usage_tap_fault_should_warn, max_translate_body_bytes}` — are process-wide `OnceLock`s the host
-// arms in ITS image. A plane dropped in as a `cdylib` carries its own copy of the contract, whose
-// ports nothing armed, so it read the failure path of every one. These four slots carry the
-// host's own services across the seam; the dropped-in door arms the plane image's ports over
-// them (`services::arm`) when the host opens the door. Process-wide, so `host` may be
-// `HostCtx::NULL`. ──
+// max_translate_body_bytes}` — are process-wide `OnceLock`s the host arms in ITS image. A plane
+// dropped in as a `cdylib` carries its own copy of the contract, whose ports nothing armed, so it
+// read the failure path of every one. These slots carry the host's own services across the seam;
+// the dropped-in door arms the plane image's ports over them (`services::arm`) when the host
+// opens the door. The usage-tap fault latch is RETIRED (row 155): the fault crosses as data on
+// the plane's answer (`abi::plane::OnPieceOut::tap_fault`), so no host serves its slot and no
+// plane calls it. Process-wide, so `host` may be `HostCtx::NULL`. ──
/// Fill `out_len` bytes at `out` from the host's entropy source (its CSPRNG). [`StatusClass::Ok`]
/// when every byte was written; [`StatusClass::Refused`] when the host has no entropy to give or
/// `out` is NULL with a non-zero length (the bytes are then unspecified); [`StatusClass::Fault`] on
@@ -550,9 +551,9 @@ pub type EntropyFillFn =
/// The host's wall clock in whole seconds since the Unix epoch; `0` when the host holds no clock
/// (the fail-closed reading, never a real second).
pub type WallClockFn = extern "C-unwind" fn(host: HostCtx) -> u64;
-/// Count one usage-tap fault of `reason` for `protocol` (two borrowed UTF-8 ranges, live for the
-/// call) and answer `true` only the FIRST time the host sees that pair, so the caller warns once.
-/// `false` for a range that is not UTF-8 or a caught panic.
+/// RETIRED (row 155): the slot's shape, kept so the table's layout holds. A usage-tap fault is data
+/// on the plane's answer (`abi::plane::OnPieceOut::tap_fault`), counted by the kernel; no host
+/// serves this slot and no plane calls it.
pub type TapFaultLatchFn = extern "C-unwind" fn(
host: HostCtx,
protocol_ptr: *const u8,
@@ -714,7 +715,7 @@ pub struct PlaneHostVtable {
pub entropy_fill: Option,
/// Read the host's wall clock (whole Unix seconds).
pub wall_clock: Option,
- /// Count a usage-tap fault and answer whether it is the first of its kind.
+ /// RETIRED (row 155): always `None`; the usage-tap fault is data on the plane's answer.
pub tap_fault_latch: Option,
/// Read the operator's live translation cap.
pub translate_cap: Option,
@@ -929,13 +930,12 @@ impl PlaneHostVtable {
translate_cap: None,
};
- /// [`EMPTY`](Self::EMPTY) plus the four HOST SERVICES, each served from THIS image's own armed
+ /// [`EMPTY`](Self::EMPTY) plus the three HOST SERVICES, each served from THIS image's own armed
/// contract ports ([`super::services`]). A host grants them by building its table over this
/// (`..PlaneHostVtable::SERVICES`): the services are the contract's, so the host adds no code.
pub const SERVICES: PlaneHostVtable = PlaneHostVtable {
entropy_fill: Some(super::services::entropy_fill),
wall_clock: Some(super::services::wall_clock),
- tap_fault_latch: Some(super::services::tap_fault_latch),
translate_cap: Some(super::services::translate_cap),
..Self::EMPTY
};
diff --git a/crates/busbar-contract/src/abi/hot/services.rs b/crates/busbar-contract/src/abi/hot/services.rs
index 2a0eab4681..c1957aefc4 100644
--- a/crates/busbar-contract/src/abi/hot/services.rs
+++ b/crates/busbar-contract/src/abi/hot/services.rs
@@ -4,19 +4,20 @@
//! THE HOST SERVICES ACROSS THE HOT SEAM (minor 30; ARCHITECT SD-3 queue (6), DEC-SERVE G2).
//!
//! The contract ports a codec reaches — [`fill_entropy`](crate::codec::fill_entropy),
-//! [`wall_clock_now`](crate::codec::wall_clock_now),
-//! [`usage_tap_fault_should_warn`](crate::codec::usage_tap_fault_should_warn) and
+//! [`wall_clock_now`](crate::codec::wall_clock_now) and
//! [`max_translate_body_bytes`](crate::codec::max_translate_body_bytes) — are process-wide
//! `OnceLock`s the host arms in ITS image. A plane LINKED into the host shares that image, so it
//! reads the host's services. A plane DROPPED IN as a `cdylib` carries its own copy of this crate,
//! whose ports nothing armed: it read the failure path of every one (a synthesized id with no
-//! entropy, an unstamped time, a latch that never warns, the default cap instead of the operator's).
+//! entropy, an unstamped time, the default cap instead of the operator's). A usage-tap fault is no
+//! port: the plane returns it as data on its answer (`abi::plane::OnPieceOut::tap_fault`), and the
+//! `tap_fault_latch` slot is served and armed by no one.
//!
//! Two halves, one file, because they are one contract:
//!
-//! * **HOST side** — `entropy_fill`, `wall_clock`, `tap_fault_latch`, `translate_cap`: the
-//! four slots of [`PlaneHostVtable::SERVICES`], each answering from THIS image's own ports, which
-//! the host armed. A host that builds its table over `SERVICES` adds no code of its own.
+//! * **HOST side** — `entropy_fill`, `wall_clock`, `translate_cap`: the three service slots of
+//! [`PlaneHostVtable::SERVICES`], each answering from THIS image's own ports, which the host
+//! armed. A host that builds its table over `SERVICES` adds no code of its own.
//! * **PLANE side** — [`arm`]: run in a dropped-in plane's image (the door's `busbar_plane_arm`
//! symbol, which the host calls when it opens the door), it installs that image's ports over the
//! host's slots, so a codec in the plane reads the host's services exactly as a linked codec does.
@@ -26,7 +27,7 @@ use super::pod::StatusClass;
use std::panic::catch_unwind;
use std::sync::atomic::{AtomicPtr, AtomicU32, Ordering};
-// ── HOST SIDE: the four slots, served from this image's armed contract ports ────────────────────────
+// ── HOST SIDE: the three slots, served from this image's armed contract ports ───────────────────────
/// `entropy_fill` — fill `out_len` bytes at `out` through [`crate::codec::fill_entropy`].
pub(crate) extern "C-unwind" fn entropy_fill(
@@ -59,78 +60,12 @@ pub(crate) extern "C-unwind" fn wall_clock(_host: HostCtx) -> u64 {
catch_unwind(|| crate::codec::wall_clock_now().unwrap_or(0)).unwrap_or(0)
}
-/// `tap_fault_latch` — [`crate::codec::usage_tap_fault_should_warn`] over the two borrowed ranges.
-/// The port takes the reason as `&'static str` (a codec names its reasons as constants), so a
-/// reason crossing from a plane image is kept once per distinct spelling, up to [`REASON_CAP`];
-/// past the cap every new spelling counts as [`UNLISTED_REASON`], so a plane cannot grow the host's
-/// memory or its label set without bound.
-pub(crate) extern "C-unwind" fn tap_fault_latch(
- _host: HostCtx,
- protocol_ptr: *const u8,
- protocol_len: usize,
- reason_ptr: *const u8,
- reason_len: usize,
-) -> bool {
- catch_unwind(|| {
- // SAFETY (both): each range is live and initialized for the call (the slot's discipline).
- let (Some(protocol), Some(reason)) =
- (unsafe { utf8(protocol_ptr, protocol_len) }, unsafe {
- utf8(reason_ptr, reason_len)
- })
- else {
- return false;
- };
- crate::codec::usage_tap_fault_should_warn(protocol, kept(reason))
- })
- .unwrap_or(false)
-}
-
/// `translate_cap` — [`crate::codec::max_translate_body_bytes`].
pub(crate) extern "C-unwind" fn translate_cap(_host: HostCtx) -> u64 {
catch_unwind(|| crate::codec::max_translate_body_bytes() as u64)
.unwrap_or(crate::codec::TRANSLATE_BODY_MAX_BYTES_DEFAULT as u64)
}
-/// How many distinct usage-tap reasons the host keeps for planes (a codec declares a handful).
-pub const REASON_CAP: usize = 64;
-
-/// The reason every spelling past [`REASON_CAP`] counts as.
-pub const UNLISTED_REASON: &str = "unlisted";
-
-/// `reason`, kept for the life of the process (once per spelling; see [`tap_fault_latch`]), in a
-/// fixed table of [`REASON_CAP`] process-lifetime slots — nothing is leaked, and the table cannot grow.
-fn kept(reason: &str) -> &'static str {
- static KEPT: [std::sync::OnceLock; REASON_CAP] =
- [const { std::sync::OnceLock::new() }; REASON_CAP];
- static FILLING: std::sync::Mutex<()> = std::sync::Mutex::new(());
- let _filling = FILLING
- .lock()
- .unwrap_or_else(std::sync::PoisonError::into_inner);
- for slot in &KEPT {
- match slot.get() {
- Some(kept) if kept == reason => return kept.as_str(),
- Some(_) => {}
- None => return slot.get_or_init(|| reason.to_owned()).as_str(),
- }
- }
- UNLISTED_REASON
-}
-
-/// A borrowed range as UTF-8; `None` for a NULL range with a length, or bytes that are not UTF-8.
-///
-/// # Safety
-/// A non-null `ptr` must address `len` live, initialized bytes for `'a`.
-unsafe fn utf8<'a>(ptr: *const u8, len: usize) -> Option<&'a str> {
- if len == 0 {
- return Some("");
- }
- if ptr.is_null() {
- return None;
- }
- // SAFETY: the caller's obligation.
- std::str::from_utf8(unsafe { core::slice::from_raw_parts(ptr, len) }).ok()
-}
-
// ── PLANE SIDE: arm this image's ports over the host's slots ────────────────────────────────────────
/// The host table [`arm`] was handed, and the size [`PlaneHostVtable::check`] honoured for it.
@@ -168,16 +103,13 @@ pub unsafe fn arm(host: *const PlaneHostVtable) -> StatusClass {
}
ARMED_SIZE.store(size, Ordering::Release);
ARMED.store(host.cast_mut(), Ordering::Release);
- // SAFETY (all four): `host`/`size` are what `check` just validated.
+ // SAFETY (all three): `host`/`size` are what `check` just validated.
if crate::host_slot!(host, size, entropy_fill).is_some() {
crate::codec::install_entropy_source(armed_entropy);
}
if crate::host_slot!(host, size, wall_clock).is_some() {
crate::codec::install_wall_clock(armed_wall_clock);
}
- if crate::host_slot!(host, size, tap_fault_latch).is_some() {
- crate::codec::install_usage_tap_fault_latch(armed_tap_fault_latch);
- }
if crate::host_slot!(host, size, translate_cap).is_some() {
crate::codec::install_translate_cap_reader(armed_translate_cap);
}
@@ -210,22 +142,6 @@ fn armed_wall_clock() -> u64 {
.map_or(0, |now| now(HostCtx::NULL))
}
-/// The usage-tap latch port, forwarded through the host's `tap_fault_latch`.
-fn armed_tap_fault_latch(protocol: &str, reason: &'static str) -> bool {
- // SAFETY: as `armed_entropy`.
- armed()
- .and_then(|(host, size)| crate::host_slot!(host, size, tap_fault_latch))
- .is_some_and(|latch| {
- latch(
- HostCtx::NULL,
- protocol.as_ptr(),
- protocol.len(),
- reason.as_ptr(),
- reason.len(),
- )
- })
-}
-
/// The translate-cap port, forwarded through the host's `translate_cap`.
fn armed_translate_cap() -> usize {
// SAFETY: as `armed_entropy`.
diff --git a/crates/busbar-contract/src/abi/hot/tests/services_tests.rs b/crates/busbar-contract/src/abi/hot/tests/services_tests.rs
index 358b532142..844181262b 100644
--- a/crates/busbar-contract/src/abi/hot/tests/services_tests.rs
+++ b/crates/busbar-contract/src/abi/hot/tests/services_tests.rs
@@ -14,15 +14,6 @@ extern "C-unwind" fn host_entropy(_host: HostCtx, out: *mut u8, len: usize) -> S
extern "C-unwind" fn host_clock(_host: HostCtx) -> u64 {
1_700_000_000
}
-extern "C-unwind" fn host_latch(
- _host: HostCtx,
- _protocol_ptr: *const u8,
- protocol_len: usize,
- _reason_ptr: *const u8,
- reason_len: usize,
-) -> bool {
- protocol_len == 5 && reason_len == 6
-}
extern "C-unwind" fn host_cap(_host: HostCtx) -> u64 {
4_096
}
@@ -57,7 +48,6 @@ fn an_armed_image_reads_the_hosts_services_through_every_port() {
static HOST: PlaneHostVtable = PlaneHostVtable {
entropy_fill: Some(host_entropy),
wall_clock: Some(host_clock),
- tap_fault_latch: Some(host_latch),
translate_cap: Some(host_cap),
..PlaneHostVtable::EMPTY
};
@@ -66,11 +56,6 @@ fn an_armed_image_reads_the_hosts_services_through_every_port() {
assert!(crate::codec::fill_entropy(&mut drawn));
assert_eq!(drawn, [0xA5; 4], "the draw is the host's");
assert_eq!(crate::codec::wall_clock_now(), Some(1_700_000_000));
- assert!(crate::codec::usage_tap_fault_should_warn("proto", "reason"));
- assert!(!crate::codec::usage_tap_fault_should_warn(
- "proto",
- "other-reason"
- ));
assert_eq!(crate::codec::max_translate_body_bytes(), 4_096);
// The HOST side of the same seam: `SERVICES` answers from this image's (now armed) ports.
@@ -93,31 +78,6 @@ fn an_armed_image_reads_the_hosts_services_through_every_port() {
(PlaneHostVtable::SERVICES.translate_cap.unwrap())(HostCtx::NULL),
4_096
);
- let latch = PlaneHostVtable::SERVICES.tap_fault_latch.unwrap();
- let (p, r) = ("proto", "reason");
- assert!(latch(
- HostCtx::NULL,
- p.as_ptr(),
- p.len(),
- r.as_ptr(),
- r.len()
- ));
- let invalid = [0xFFu8];
- assert!(
- !latch(HostCtx::NULL, p.as_ptr(), p.len(), invalid.as_ptr(), 1),
- "not UTF-8"
- );
-
- // Past `REASON_CAP` distinct spellings, every new reason is kept as `UNLISTED_REASON`, so a
- // plane cannot grow the host's memory or label set without bound. (Here, after every latch
- // assertion above: the kept set is process-wide.)
- for i in 0..REASON_CAP {
- let _ = kept(&format!("reason-{i}"));
- }
- assert_eq!(kept("one-past-the-cap"), UNLISTED_REASON);
- assert_eq!(
- kept("reason-0"),
- "reason-0",
- "a kept spelling is still found"
- );
+ // The usage-tap latch slot is RETIRED (row 155): `SERVICES` serves it no longer.
+ assert!(PlaneHostVtable::SERVICES.tap_fault_latch.is_none());
}
diff --git a/crates/busbar-contract/src/abi/plane/check.rs b/crates/busbar-contract/src/abi/plane/check.rs
index 3a27cc3279..e2b94ed41a 100644
--- a/crates/busbar-contract/src/abi/plane/check.rs
+++ b/crates/busbar-contract/src/abi/plane/check.rs
@@ -33,7 +33,7 @@ use super::{
MECHANISM_PEER_KEY, MECHANISM_ROOT, PIECE_OUT_TEXT, PIN_FINGERPRINT, PRINCIPAL_OPTIONAL,
RECORD_AUDIT, RECORD_PUT, REFUSAL_ANY_DIALECT, ROUTE_COUNTED, ROUTE_DIRECT, ROUTE_LOCAL,
ROUTE_ONCE, ROUTE_POOL, ROUTE_PUBLIC, ROUTE_SCOPE, ROUTE_SESSION, ROUTE_STREAM,
- SHAPE_PIECEWISE, SHAPE_WHOLE, TAIL_FALLBACK, TAIL_HOOKS_GATED, TAIL_PROBES,
+ SHAPE_PIECEWISE, SHAPE_WHOLE, TAIL_FALLBACK, TAIL_HOOKS_GATED, TAIL_PROBES, TAP_FAULT_NONE,
TRUST_ITEM_APPROVALS, TRUST_PIN, TRUST_PRIVATE_REACH, UNITS_ESTIMATED, VERDICT_HARD,
};
use crate::abi::hook::{
@@ -469,7 +469,9 @@ fn verdict(outcome: Outcome, v: u32) -> Result<(), Fault> {
}
/// `on_piece`'s breaker fault reading: a known `FAULT_*` (the transport kind's one vocabulary), none
-/// on an answer that is not READY, and its padding zero.
+/// on an answer that is not READY, and its padding zero. Its usage-tap fault rides only a READY
+/// answer; any value is admitted there, because the kernel counts a value outside the `TAP_FAULT_*`
+/// vocabulary as `unknown` rather than refusing the answer it rides.
fn fault_reading(outcome: Outcome, out: &OnPieceOut) -> Result<(), Fault> {
use crate::abi::transport::{FAULT_HARD, FAULT_NONE};
code(
@@ -481,7 +483,10 @@ fn fault_reading(outcome: Outcome, out: &OnPieceOut) -> Result<(), Fault> {
if out.fault != FAULT_NONE && outcome != Outcome::Ready {
return Err(fault(Rule::Contradiction, "on_piece.fault_not_ready"));
}
- if out._fault_reserved != [0; 3] {
+ if out.tap_fault != TAP_FAULT_NONE && outcome != Outcome::Ready {
+ return Err(fault(Rule::Contradiction, "on_piece.tap_fault_not_ready"));
+ }
+ if out._fault_reserved != [0; 2] {
return Err(fault(Rule::Contradiction, "on_piece.fault_reserved"));
}
Ok(())
diff --git a/crates/busbar-contract/src/abi/plane/mod.rs b/crates/busbar-contract/src/abi/plane/mod.rs
index 325fefc8a5..2ce74f5e5a 100644
--- a/crates/busbar-contract/src/abi/plane/mod.rs
+++ b/crates/busbar-contract/src/abi/plane/mod.rs
@@ -373,6 +373,16 @@ pub const UNITS_REPORTED: u32 = 1;
/// [`UNITS_REPORTED`]; the plane decides when it applies, the kernel adds no floor of its own.
pub const UNITS_FLOOR: u32 = 2;
+/// [`OnPieceOut::tap_fault`]: no usage-tap fault.
+pub const TAP_FAULT_NONE: u8 = 0;
+/// [`OnPieceOut::tap_fault`]: the cell's own reader refused the body (`reason="decode"`).
+pub const TAP_FAULT_DECODE: u8 = 1;
+/// [`OnPieceOut::tap_fault`]: the body is not JSON (`reason="bad_json"`).
+pub const TAP_FAULT_BAD_JSON: u8 = 2;
+/// [`OnPieceOut::tap_fault`]: the caller's dialect is not one the tap reads
+/// (`reason="unknown_protocol"`).
+pub const TAP_FAULT_UNKNOWN_PROTOCOL: u8 = 3;
+
/// THE ONE BILLING RULE ON A COUNT'S SOURCE: [`UNITS_REPORTED`] and [`UNITS_FLOOR`] bill;
/// [`UNITS_ESTIMATED`] (and any other value) never does. Every kernel site that ledgers, meters or
/// reads a fee unit from a plane's counts asks this, never a source compare of its own.
@@ -1795,8 +1805,17 @@ pub struct OnPieceOut {
/// may still be the destination's. `FAULT_NONE` = no reading. Laid in the padding after
/// `need` (pre-tag v1).
pub fault: u8,
+ /// THE UNIT'S USAGE-TAP FAULT (`TAP_FAULT_*`), beside the units this answer reports: why the
+ /// plane's usage tap could not read a same-protocol 2xx body, so the request billed 0 tokens
+ /// ([`crate::codec::UsageTapFault`]). The kernel counts it, once per answer that carries it, on
+ /// `busbar_billing_tap_decode_fail_total{protocol,reason}` under the unit's dialect, and warns
+ /// once per `(protocol, reason)`: the fault crosses as data, never as a host call. A CLOSED
+ /// enum: [`TAP_FAULT_NONE`] = none, then the named reasons; any other value is counted under
+ /// `reason="unknown"` (and warned once), never ignored and never read as none. Laid in the
+ /// padding after `fault` (pre-release, at the unchanged plane ABI version).
+ pub tap_fault: u8,
/// Alignment padding.
- pub _fault_reserved: [u8; 3],
+ pub _fault_reserved: [u8; 2],
/// THE UNIT'S LEDGER LANE, in the arena; a zero length = none named. The billing identity the
/// unit's units are priced, ledgered and metered under, which is not the route entry the walk
/// picked (a call of one tool on a pooled server is the tool's lane, not the server's). The
diff --git a/crates/busbar-contract/src/abi/tests/plane_check_tests.rs b/crates/busbar-contract/src/abi/tests/plane_check_tests.rs
index 60d328245f..8598675bf0 100644
--- a/crates/busbar-contract/src/abi/tests/plane_check_tests.rs
+++ b/crates/busbar-contract/src/abi/tests/plane_check_tests.rs
@@ -1675,13 +1675,44 @@ fn a_fault_reading_is_the_transports_vocabulary_on_a_ready_answer() {
f(Rule::Contradiction, "on_piece.fault_not_ready")
);
o.fault = FAULT_NONE;
- o._fault_reserved = [0, 1, 0];
+ o._fault_reserved = [0, 1];
assert_eq!(
piece(&o, &[], &[], &[]),
f(Rule::Contradiction, "on_piece.fault_reserved")
);
}
+/// THE USAGE-TAP FAULT rides the answer as data (row 155), beside the breaker reading and separate
+/// from it, on a READY answer only. A value past the vocabulary is ADMITTED: the kernel counts it
+/// as `unknown` rather than refusing the answer it rides. RED arm: a fault on a non-READY answer.
+#[test]
+fn a_usage_tap_fault_rides_only_a_ready_answer() {
+ use crate::abi::plane::{
+ TAP_FAULT_BAD_JSON, TAP_FAULT_DECODE, TAP_FAULT_NONE, TAP_FAULT_UNKNOWN_PROTOCOL,
+ };
+ let mut o: OnPieceOut = z();
+ for code in [
+ TAP_FAULT_NONE,
+ TAP_FAULT_DECODE,
+ TAP_FAULT_BAD_JSON,
+ TAP_FAULT_UNKNOWN_PROTOCOL,
+ ] {
+ o.tap_fault = code;
+ assert_eq!(piece(&o, &[], &[], &[]), Ok(()), "{code}");
+ }
+ o.tap_fault = TAP_FAULT_UNKNOWN_PROTOCOL + 1;
+ assert_eq!(
+ piece(&o, &[], &[], &[]),
+ Ok(()),
+ "counted as unknown, never refused"
+ );
+ o.tap_fault = TAP_FAULT_DECODE;
+ assert_eq!(
+ check_on_piece(Pending, &o, (&[], &[], &[]), &caps(), &bounds()),
+ f(Rule::Contradiction, "on_piece.tap_fault_not_ready")
+ );
+}
+
#[test]
fn a_verdict_rides_only_a_ready_answer() {
let mut o: OnPieceOut = z();
diff --git a/crates/busbar-contract/src/codec.rs b/crates/busbar-contract/src/codec.rs
index ebdc32cdc5..c33376b05e 100644
--- a/crates/busbar-contract/src/codec.rs
+++ b/crates/busbar-contract/src/codec.rs
@@ -123,53 +123,84 @@ pub enum CodecError {
Malformed(String),
}
-/// The host's USAGE-TAP FAULT REPORTER: told when a cell's own reader refused a same-protocol 2xx body
-/// in [`OperationHandler::extract_usage`]'s default, so the request billed 0 tokens. The host counts
-/// it (a per-request volume signal) and warns once per `(protocol, reason)`; the contract carries
-/// only the shape of the call, because counting and logging are the host's, not the cell's.
-pub type UsageTapFaultReporter = fn(ingress_protocol: &str, error: &CodecError);
-
-static USAGE_TAP_FAULT_REPORTER: std::sync::OnceLock =
- std::sync::OnceLock::new();
-
-/// Install the host's [`UsageTapFaultReporter`]. The first install wins and later ones are ignored:
-/// one process has one host. The host installs it where it installs the protocols whose cells report
-/// through it, so no cell is reachable before it is armed.
-pub fn install_usage_tap_fault_reporter(reporter: UsageTapFaultReporter) {
- let _ = USAGE_TAP_FAULT_REPORTER.set(reporter);
+/// A USAGE-TAP FAULT: why a cell's usage tap could not read a same-protocol 2xx body, so the request
+/// billed 0 tokens. It is DATA the tap returns ([`OperationHandler::extract_usage`]'s `Err`): the
+/// plane carries it out on its door's answer beside the units it reports
+/// ([`crate::abi::plane::OnPieceOut::tap_fault`]), and the kernel counts it (one increment of
+/// `busbar_billing_tap_decode_fail_total{protocol,reason}`) and warns once per `(protocol, reason)`.
+/// The contract carries only the shape: no port, no latch and no static, so a codec reaches no host
+/// to report it and holds nothing that counts it.
+///
+/// ```compile_fail
+/// // There is no host port to report through: the fault is returned, never reported.
+/// busbar_contract::codec::report_usage_tap_decode_failure(
+/// "proto",
+/// &busbar_contract::codec::CodecError::Malformed(String::new()),
+/// );
+/// ```
+///
+/// ```compile_fail
+/// // And no latch a codec asks whether to warn: the kernel's count is the only one.
+/// let _ = busbar_contract::codec::usage_tap_fault_should_warn("proto", "decode");
+/// ```
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum UsageTapFault {
+ /// The cell's own reader refused the body (`decode`).
+ Decode,
+ /// The body is not JSON (`bad_json`).
+ BadJson,
+ /// The caller's dialect is not one the tap reads (`unknown_protocol`).
+ UnknownProtocol,
}
-/// Report a usage-tap decode failure to the installed host reporter; a no-op in a process whose host
-/// installed none (a bare codec test).
-pub fn report_usage_tap_decode_failure(ingress_protocol: &str, error: &CodecError) {
- if let Some(report) = USAGE_TAP_FAULT_REPORTER.get() {
- report(ingress_protocol, error);
+impl UsageTapFault {
+ /// The `reason` label the kernel counts the fault under.
+ #[must_use]
+ pub const fn reason(self) -> &'static str {
+ match self {
+ UsageTapFault::Decode => "decode",
+ UsageTapFault::BadJson => "bad_json",
+ UsageTapFault::UnknownProtocol => "unknown_protocol",
+ }
}
-}
-/// The host's USAGE-TAP FAULT LATCH: counts one usage-tap fault of `reason` for `ingress_protocol`
-/// (a per-request volume signal) and answers `true` only the FIRST time the host sees that
-/// `(protocol, reason)`, so a codec that raises its own coded diagnostic for the fault warns once and
-/// logs at debug after. [`UsageTapFaultReporter`] is this latch plus the one decode-failure line a
-/// cell's default tap prints; a codec whose tap reads a body in more than one way reports each way's
-/// reason through the latch and prints its own line. Counting is the host's, not the codec's.
-pub type UsageTapFaultLatch = fn(ingress_protocol: &str, reason: &'static str) -> bool;
-
-static USAGE_TAP_FAULT_LATCH: std::sync::OnceLock = std::sync::OnceLock::new();
+ /// The fault's code on the door's answer ([`crate::abi::plane::TAP_FAULT_DECODE`] and on).
+ #[must_use]
+ pub const fn code(self) -> u8 {
+ match self {
+ UsageTapFault::Decode => crate::abi::plane::TAP_FAULT_DECODE,
+ UsageTapFault::BadJson => crate::abi::plane::TAP_FAULT_BAD_JSON,
+ UsageTapFault::UnknownProtocol => crate::abi::plane::TAP_FAULT_UNKNOWN_PROTOCOL,
+ }
+ }
-/// Install the host's [`UsageTapFaultLatch`]. First install wins, exactly as
-/// [`install_usage_tap_fault_reporter`], and the host installs both in the same place.
-pub fn install_usage_tap_fault_latch(latch: UsageTapFaultLatch) {
- let _ = USAGE_TAP_FAULT_LATCH.set(latch);
-}
+ /// The reason the kernel counts a door's answer code under, the byte read as the CLOSED enum
+ /// it is: `None` only for [`crate::abi::plane::TAP_FAULT_NONE`]; a named code's
+ /// [`Self::reason`]; and every other value [`Self::UNKNOWN_REASON`]. A byte outside the
+ /// vocabulary is never ignored and never read as none: it is counted, and warned once.
+ #[must_use]
+ pub const fn reason_of(code: u8) -> Option<&'static str> {
+ match Self::of_code(code) {
+ Some(fault) => Some(fault.reason()),
+ None if code == crate::abi::plane::TAP_FAULT_NONE => None,
+ None => Some(Self::UNKNOWN_REASON),
+ }
+ }
-/// Count one usage-tap fault through the installed host latch and answer whether this is the first
-/// of its `(protocol, reason)`. In a process whose host installed none (a bare codec test) nothing is
-/// counted and the answer is `false`, so the caller logs at debug.
-pub fn usage_tap_fault_should_warn(ingress_protocol: &str, reason: &'static str) -> bool {
- USAGE_TAP_FAULT_LATCH
- .get()
- .is_some_and(|latch| latch(ingress_protocol, reason))
+ /// The `reason` a tap-fault byte outside the vocabulary is counted under.
+ pub const UNKNOWN_REASON: &'static str = "unknown";
+
+ /// The fault a door's answer code names; `None` for [`crate::abi::plane::TAP_FAULT_NONE`] and
+ /// for a code outside the vocabulary (which [`Self::reason_of`] counts as `unknown`).
+ #[must_use]
+ pub const fn of_code(code: u8) -> Option {
+ match code {
+ crate::abi::plane::TAP_FAULT_DECODE => Some(UsageTapFault::Decode),
+ crate::abi::plane::TAP_FAULT_BAD_JSON => Some(UsageTapFault::BadJson),
+ crate::abi::plane::TAP_FAULT_UNKNOWN_PROTOCOL => Some(UsageTapFault::UnknownProtocol),
+ _ => None,
+ }
+ }
}
/// The host's TRANSLATE-BODY CAP READER: the operator's per-response translation cap, in bytes, as
@@ -329,16 +360,19 @@ pub trait OperationHandler: Send + Sync {
/// end, only when [`Self::taps_usage`] is true). Default: run THIS operation's own reader over the
/// body and project its token usage — so a token-metered non-chat op (embeddings) bills the same
/// as the cross-protocol path. Chat overrides this to run the egress protocol's chat reader.
- fn extract_usage(&self, ingress_protocol: &str, body: &[u8]) -> Option {
+ ///
+ /// `Err` is the [`UsageTapFault`] that kept the tap from reading the body: the request bills 0
+ /// tokens, and the fault is returned as data for the kernel to count, never reported from here.
+ fn extract_usage(
+ &self,
+ _ingress_protocol: &str,
+ body: &[u8],
+ ) -> Result