ebman 0.31.0

k9s-style TUI for AWS Elastic Beanstalk
name: CI

on:
  push:
    branches: [main]
  pull_request:

env:
  CARGO_TERM_COLOR: always
  RUST_BACKTRACE: short

# Least privilege for every job below. Without an explicit block a job
# inherits the repository default, which can be write-all — CodeQL flags
# each one (`actions/missing-workflow-permissions`). Nothing in CI needs
# more than reading the checkout.
permissions:
  contents: read

jobs:
  build-and-test:
    name: build, test
    runs-on: ${{ matrix.os }}
    strategy:
      fail-fast: false
      matrix:
        os: [ubuntu-latest, macos-latest]
    steps:
      - uses: actions/checkout@v4
      - uses: dtolnay/rust-toolchain@stable
      - uses: Swatinem/rust-cache@v2
      - name: Build
        run: cargo build --locked --all-targets
      - name: Test
        run: cargo test --locked --all-targets

  fmt:
    name: rustfmt
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: dtolnay/rust-toolchain@stable
        with:
          components: rustfmt
      - run: cargo fmt --all -- --check

  clippy:
    name: clippy
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: dtolnay/rust-toolchain@stable
        with:
          components: clippy
      - uses: Swatinem/rust-cache@v2
      - run: cargo clippy --locked --all-targets -- -D warnings

  msrv:
    name: msrv (rust 1.94.1)
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: dtolnay/rust-toolchain@1.94.1
      - uses: Swatinem/rust-cache@v2
      - run: cargo build --locked

  deny:
    name: cargo-deny (advisories, licences, bans)
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      # 61 direct dependencies including the whole AWS SDK, and until
      # now nothing checked them against RUSTSEC at all.
      - uses: EmbarkStudios/cargo-deny-action@v2
        with:
          command: check advisories licenses bans sources

  semver:
    name: cargo-semver-checks
    runs-on: ubuntu-latest
    # Only on a PR: it diffs the public API against the last published
    # release, which is noise on a main-branch push between tags.
    if: github.event_name == 'pull_request'
    steps:
      - uses: actions/checkout@v4
      - uses: dtolnay/rust-toolchain@stable
      - uses: Swatinem/rust-cache@v2
      # ebman is lib + bin on crates.io, so a signature change in the
      # lib is a semver event. 0.30.2 shipped one that a human review
      # caught (`ui::series_anomaly_label` gained a parameter) — the
      # kind of thing that decides whether the next tag is 0.30.3 or
      # 0.31.0, and not something to notice by reading.
      - uses: obi1kenobi/cargo-semver-checks-action@v2
        with:
          feature-group: default-features

  candor:
    name: candor (effect/layer policy)
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: dtolnay/rust-toolchain@stable
      - uses: Swatinem/rust-cache@v2
      - name: Install dylint
        run: cargo install cargo-dylint dylint-link --locked
      - name: Build the candor lint
        run: |
          # Pinned, not HEAD. This gate can fail the release, so it must
          # not change underfoot: an unrelated push to candor-rust would
          # otherwise land in the next ebman CI run with no review here.
          # Bump deliberately.
          git clone --filter=blob:none https://github.com/tombaldwin/candor-rust ~/candor
          (cd ~/candor && git checkout --quiet a7f01138a1ceea6b2e05dc92995ee47ee9219201 && cargo build)
      # The DYLINT backend, not `candor-scan`. The syntactic scanner
      # reports `ui::draw` with no Exec/Ipc at all, while the
      # type-resolved lint finds both through the embedded shell pane —
      # so gating on a scan report would miss exactly the class this
      # policy exists to catch.
      - name: Enforce .candor/policy
        # Plain text, overriding the workflow-level `always`. The three
        # greps below parse this output; with colour on, cargo writes
        # "Checking\e[0m ebman" and the "did it actually run" guard
        # fails a clean tree. It did exactly that once.
        env:
          CARGO_TERM_COLOR: never
        run: |
          LIB=$(ls ~/candor/target/debug/libcandor@*.so)
          # Force a re-analysis. `rust-cache` restores the check
          # artefacts, and a cached `cargo dylint` prints "Finished"
          # without re-running the lint — so the gate would pass on
          # stale results from before the change under review.
          touch src/lib.rs src/main.rs
          # No `|| true` on the lint. Swallowing its exit code means a
          # crash, a missing lib or a compile error produces no AS-EFF
          # lines, the grep below finds nothing, and the gate passes —
          # a gate that reports success when it did not run.
          if ! out=$(CANDOR_POLICY=.candor/policy cargo dylint --lib-path "$LIB" 2>&1); then
            echo "$out"
            echo "::error::candor lint failed to RUN (not a policy violation)"
            exit 1
          fi
          echo "$out"
          # And prove it actually analysed this crate, for the same
          # reason: a lint that silently no-ops emits no findings.
          if ! echo "$out" | grep -qE 'Checking.*ebman'; then
            echo "::error::candor lint produced no analysis of ebman — gate did not run"
            exit 1
          fi
          if echo "$out" | grep -qE "AS-EFF-00[689]"; then
            echo "::error::effect/layer policy violation — see .candor/policy"
            exit 1
          fi