Skip to content

Add declarative tool rules (allow/deny/hide/approval) #303

Add declarative tool rules (allow/deny/hide/approval)

Add declarative tool rules (allow/deny/hide/approval) #303

Workflow file for this run

name: CI
on:
push:
pull_request:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read
env:
# Lint levels live in `[workspace.lints]` in the root Cargo.toml so local and
# CI runs agree; don't add a blanket RUSTFLAGS here.
CARGO_TERM_COLOR: always
jobs:
rust:
name: Rust
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
with:
# This job executes repository code (cargo build/test); don't persist
# the token in git config.
persist-credentials: false
# No `submodules:` entry on purpose: this repository has no
# submodules. The template it came from vendored `vendor/tinybus` for
# the loadable-module half, which was removed along with that half —
# there is no `.gitmodules` and no gitlink in the tree. Adding one
# back would be a no-op that implies a dependency this crate does not
# have.
- uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt, clippy
- uses: taiki-e/install-action@v2
with:
tool: cargo-llvm-cov
- uses: Swatinem/rust-cache@v2
- name: Check formatting
run: cargo fmt --all -- --check
- name: Clippy
run: cargo clippy --all-targets --all-features -- -D warnings
- name: Build
run: cargo build --all-targets --all-features
- name: Test
run: cargo test --all-features
- name: Test default features
run: cargo test
# This crate is the vocabulary both an agent harness and a host
# application link against, so its dependency list is a promise to both.
# That promise is invisible in a diff, because a forbidden dependency
# arrives transitively through a feature someone enabled one crate away —
# so it is asserted rather than documented.
#
# `tinyagents` is on the list for a structural reason, not a size one: it
# depends on *this* crate. An edge back would be a cycle, and the
# `context` module's erasure trait exists precisely to make one
# unnecessary. Everything else on the list is weight a tool author should
# not have to compile to write a tool.
#
# The FORWARD form is required. `cargo tree -i <crate> -p tinytools`
# discards the `-p` scope, prints the whole-workspace inverse tree, and
# exits 0 looking clean even when this crate is the one at fault.
- name: Assert the vocabulary crate stays dependency-light
run: |
set -euo pipefail
# Compare crate NAMES only. `cargo tree` prints each package as
# `name vX.Y.Z (/path/to/checkout)`, and a consumer may vendor this
# repository *underneath* one of the forbidden crates — tinyagents
# does exactly that — so grepping the raw line matches the path and
# reports a dependency that is not there. Cut the version and path off
# first.
package_names="$(cargo tree -p tinytools --all-features -e normal,build --prefix none \
| awk '{print $1}' | sort -u)"
# An ALLOWLIST, not a blocklist: naming eight forbidden crates only
# catches those eight. Adding `surf`, `async-std`, an arbitrary
# `*-sys` native binding, or any other transport/runtime would pass
# silently under a blocklist. Every package this crate's forward tree
# is reviewed to actually contain is named here instead, so *any*
# newly introduced package — forbidden or merely unreviewed — fails
# the gate until this list is updated in the same commit.
allowed='
tinytools
anyhow
async-trait
serde
serde_core
serde_derive
serde_json
proc-macro2
quote
syn
unicode-ident
itoa
memchr
zmij
'
# Anything in package_names that is not a line of $allowed.
forbidden="$(comm -23 \
<(printf '%s\n' "$package_names") \
<(printf '%s\n' "$allowed" | sort -u))"
if [ -n "$forbidden" ]; then
echo "tinytools pulled in a dependency its manifest doesn't review for:" >&2
echo "$forbidden" >&2
echo >&2
echo "This crate is what a harness and a host both compile against." >&2
echo "It must stay free of agent harnesses, transports, async" >&2
echo "runtimes, HTTP clients and native libraries. If this package" >&2
echo "is a genuinely reviewed addition, add it to the allowlist in" >&2
echo "this step in the same commit that adds the dependency." >&2
exit 1
fi
- name: Require 90% line coverage in every source file
run: .github/scripts/check-file-coverage.sh 90 coverage.json
- name: Upload coverage report
if: ${{ always() }}
uses: actions/upload-artifact@v7
with:
name: coverage-json
path: coverage.json
if-no-files-found: ignore
docs:
name: Docs
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- name: Build documentation
env:
RUSTDOCFLAGS: -D warnings
run: cargo doc --no-deps --all-features
msrv:
name: Minimum supported Rust version
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
with:
persist-credentials: false
# `rust-version` is inherited from `[workspace.package]`, so every member
# reports the same value. Read it off the package the module ships as
# rather than off `packages[0]`, whose order cargo does not promise.
- name: Read rust-version from Cargo.toml
id: msrv
run: |
set -euo pipefail
msrv="$(cargo metadata --format-version 1 --no-deps \
| jq -r '.packages[] | select(.name == "tinytools") | .rust_version')"
if [[ -z "$msrv" || "$msrv" == "null" ]]; then
echo "workspace.package.rust-version is not set in Cargo.toml" >&2
exit 1
fi
echo "version=$msrv" >> "$GITHUB_OUTPUT"
- uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{ steps.msrv.outputs.version }}
- uses: Swatinem/rust-cache@v2
- name: Build with the declared MSRV
run: cargo build --all-targets --all-features
supply-chain:
name: Supply chain
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
with:
persist-credentials: false
- name: Check advisories, licenses, bans, and sources
uses: EmbarkStudios/cargo-deny-action@v2
with:
command: check all