All checks were successful
PR Checks / rust-gates (pull_request) Successful in 3m14s
The sidecar now files a frame by its `type` and never by its `kind`. That is the dumb-forwarder property made structural: `event` is appended to history, `snapshot` replaces the board of its kind, `reply` is routed by `reqId`, `control` is broadcast and kept nowhere. Ten new event kinds are no change here at all, which is the whole point when the thing that grows fastest is the catalogue. A frame whose `type` this build does not know is dropped and counted, never guessed at. Defaulting an absent one to `event` would file a BOARD as history — the presence board appended a few thousand times, which nothing reports. The count is on `/health` as `untyped_frames`, because the failure it diagnoses (a plugin and a sidecar on different protocol versions, which the game link has no handshake to catch) otherwise presents as a website showing nothing while the game is plainly up. It caught exactly that within three seconds of first running, against a protocol 1 plugin still live on a retired rig. `boards` generalises protocol 1's single `server_state` row, and a database made by protocol 1 is migrated in place: the two indexed columns are added by a guarded `ALTER`, and the old board is carried across. Without that carry-over an upgraded sidecar answers `204` until the game next connects, and the website reads that as "never heard from" — losing a server it has rendered for weeks at the exact moment somebody upgraded the bridge. `GET /feed` is the ingest cursor: oldest first, strictly after an id, with `lastId` and `more`. It is a separate route rather than a flag on `/events` because one route with two orderings serves the other one to every caller that forgets the parameter — and for the ingesting caller that means advancing its cursor past rows it never read. Omitting `since` asks where the END is; `since=0` is the other question entirely, and the two must not be separated by whether somebody typed a parameter. `[store].retain_days` (default 14) prunes events hourly. Boards are never pruned: history grows and the present does not, and a pruned board is a server that has never connected. The repository also had no CI. `pr-checks.yml` runs the fmt, clippy and test gates phases 1 and 3 have both been running by hand — a guard nothing invokes is a guard whose state nobody knows. 44 tests pass, clippy clean at `-D warnings`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016wDDVXWMDz82WqE1i969r4
100 lines
4.0 KiB
YAML
100 lines
4.0 KiB
YAML
# Gate every pull request into `main` on the checks this repository already had
|
|
# and nobody ran automatically.
|
|
#
|
|
# Phases 1 and 3 both wrote `cargo fmt`, `cargo clippy -D warnings` and a test
|
|
# suite, and both ran them BY HAND from a workstation. That is the whole gap
|
|
# this file closes: a guard nothing invokes is a guard whose state nobody knows,
|
|
# and the repository that gets released had nothing gating it at all.
|
|
#
|
|
# Adapted from RunicGateway/installer's pr-checks.yml, which is the other Rust
|
|
# crate in this project and already solved the toolchain-on-a-shared-runner
|
|
# problem. Two differences, both because of where the crate sits:
|
|
#
|
|
# • The crate is in `sidecar/`, not at the repo root, so every cargo step runs
|
|
# with that working directory and the cache key reads that lockfile.
|
|
# • There is no "does a crate exist yet" detection. The installer needed it
|
|
# because its CI landed before its code; here the code came first.
|
|
#
|
|
# Enforcement (one-time, in the Gitea UI):
|
|
# Repository Settings → Branches → Branch Protection (rule for `main`)
|
|
# • Enable Status Check
|
|
# • Status check patterns: PR Checks / *
|
|
# Gitea only lists a context in its dropdown after it has reported once, so let
|
|
# this run on one PR first; the glob matches without the dropdown and keeps
|
|
# matching as jobs are added.
|
|
#
|
|
# Scope note: `edge` is gated as well as `main` though this repo has no `edge`
|
|
# branch. Multi-phase work lands there first everywhere else in this project, and
|
|
# gating only the `main` hop would run these checks for the first time at the
|
|
# cutover — the one moment a red build is most expensive to find.
|
|
|
|
name: PR Checks
|
|
|
|
on:
|
|
pull_request:
|
|
branches: [main, edge]
|
|
|
|
concurrency:
|
|
group: pr-checks-${{ github.ref }}
|
|
cancel-in-progress: true
|
|
|
|
jobs:
|
|
rust-gates:
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 30
|
|
defaults:
|
|
run:
|
|
working-directory: sidecar
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
|
|
# One job runs all three gates on purpose: installing the toolchain costs
|
|
# far more than the checks do, so splitting fmt/clippy/test into parallel
|
|
# jobs would pay that cost three times for no wall-clock win.
|
|
- name: Install Rust toolchain (rustfmt + clippy)
|
|
run: |
|
|
set -euo pipefail
|
|
SUDO=""; [ "$(id -u)" -ne 0 ] && SUDO="sudo"
|
|
$SUDO apt-get update
|
|
$SUDO apt-get install -y --no-install-recommends \
|
|
build-essential curl ca-certificates git
|
|
|
|
if ! command -v cargo >/dev/null 2>&1; then
|
|
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs \
|
|
| sh -s -- -y --profile minimal --default-toolchain stable
|
|
fi
|
|
echo "${HOME}/.cargo/bin" >> "$GITHUB_PATH"
|
|
export PATH="${HOME}/.cargo/bin:${PATH}"
|
|
rustup component add rustfmt clippy
|
|
cargo --version && cargo fmt --version && cargo clippy --version
|
|
|
|
# Keyed on Cargo.lock: dependency builds are reused until a dep actually
|
|
# changes. A cache miss only makes the run slower, never wrong.
|
|
- name: Cache cargo registry and build dir
|
|
uses: actions/cache@v4
|
|
with:
|
|
path: |
|
|
~/.cargo/registry
|
|
~/.cargo/git
|
|
sidecar/target
|
|
key: ${{ runner.os }}-cargo-${{ hashFiles('sidecar/Cargo.lock') }}
|
|
restore-keys: |
|
|
${{ runner.os }}-cargo-
|
|
|
|
# Cheapest gate first — parses only, no compile, so a formatting slip fails
|
|
# in seconds instead of after a full build.
|
|
- name: cargo fmt --check
|
|
run: cargo fmt --check
|
|
|
|
# --all-targets covers the tests too, which is where most of this crate's
|
|
# interesting code is. -D warnings makes a lint a failure, so the crate
|
|
# starts clean at this bar and anything new is a regression from the PR.
|
|
- name: cargo clippy
|
|
run: cargo clippy --locked --all-targets -- -D warnings
|
|
|
|
# --locked also proves Cargo.lock is in sync with Cargo.toml rather than
|
|
# letting the build silently update it.
|
|
- name: cargo test
|
|
run: cargo test --locked
|
|
|