Files
yvgude--lean-ctx/.github/workflows/ci.yml
T
wehub-resource-sync 26382a7ac6
CI / Test (macos-latest) (push) Waiting to run
CI / Test (windows-latest) (push) Waiting to run
CI / Build (no embeddings / no ORT) (push) Waiting to run
CI / Format (push) Waiting to run
CI / Cookbook (Node) (push) Waiting to run
CI / Pi Extension (Node) (push) Waiting to run
CI / Rust SDK (lean-ctx-client) (push) Waiting to run
CI / Embed SDK (lean-ctx-sdk) (push) Waiting to run
CI / Python SDK (leanctx) (push) Waiting to run
CI / Hermes Plugin (Python) (push) Waiting to run
CI / SDK Conformance Matrix (push) Waiting to run
CI / Coverage (push) Waiting to run
CI / cargo-deny (push) Waiting to run
CI / Adversarial Safety (push) Waiting to run
CodeQL / Analyze (javascript-typescript) (push) Waiting to run
JetBrains Plugin / Actionlint (push) Waiting to run
CI / Benchmarks (push) Waiting to run
CI / Output-Quality Gate (eval A/B) (push) Waiting to run
CI / Documentation (push) Waiting to run
CI / CI Green (push) Blocked by required conditions
CodeQL / Analyze (actions) (push) Waiting to run
CodeQL / Analyze (rust) (push) Waiting to run
JetBrains Plugin / Validation (push) Waiting to run
JetBrains Plugin / Build (push) Waiting to run
JetBrains Plugin / Test (push) Blocked by required conditions
Security Check / Security Scan (push) Waiting to run
CI / Test (ubuntu-latest) (push) Has started running
CI / Clippy (push) Has started running
chore: import upstream snapshot with attribution
2026-07-13 12:35:30 +08:00

681 lines
27 KiB
YAML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
workflow_dispatch:
# Cancel a still-running CI for the same ref when a new push supersedes it —
# avoids burning runner-hours on stale commits during active PR iteration.
# Never cancel on main: a run there should always finish (e.g. for the
# scorecard/coverage artifacts it produces), even if another push lands.
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
permissions:
contents: read
env:
CARGO_TERM_COLOR: always
RUSTFLAGS: -Dwarnings
# CI must be hermetic: never let tests download the embedding model
# (~90 MB) in the background. Without this, the #551 auto-activation can
# load the engine mid-suite and slow tarpaulin past its timeout.
LEAN_CTX_EMBEDDINGS_AUTO_DOWNLOAD: "0"
jobs:
test:
name: Test
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.os }}
# --test-threads=1 (env-race legacy, v3.3.5) serializes ~70 binaries that
# now include heavy property/fuzz/benchmark suites (#291, #493, #551) —
# well beyond 90 min on hosted runners. Parallelization is tracked
# separately; until then the gate gets the time it actually needs.
#
# Tried switching to cargo-nextest (rust/.config/nextest.toml already
# exists and documents the same env-race for 3 known binaries via a
# `serial-state` test-group). Reverted: nextest's default parallelism
# (test-threads = -4) surfaced several more tests with hidden wall-clock
# timing assumptions (e.g. core::cache::tests::hebbian_eviction_bonus_is_wired
# depends on two calls landing in the same real-time 500ms window) that a
# `max-threads=1` test-group does NOT fix — that only serializes tests
# *within* the group against each other, it doesn't protect them from
# being scheduling-delayed by the many *other* tests still running in
# parallel elsewhere on the same runner. Revisit once those tests are
# made robust to scheduling jitter (or the burst window is made
# injectable for tests) rather than papering over it with test-groups.
timeout-minutes: 180
env:
# ~60 integration-test binaries each statically link the full lib
# (tree-sitter ×18, rten, aws-lc, resvg, …). With default full debug
# info that is 25-40 GB and the ubuntu runner dies with ENOSPC / the
# linker with SIGBUS. Line tables keep file:line in panic backtraces
# at a fraction of the size.
CARGO_PROFILE_DEV_DEBUG: line-tables-only
CARGO_PROFILE_TEST_DEBUG: line-tables-only
# property_compression alone took 8012s serialized on ubuntu (87% of
# the job) with proptest's default 256 cases — and pushed Windows past
# the 180-min timeout. 64 cases keep the properties exercised on every
# push at a quarter of the cost; local runs keep the full default.
PROPTEST_CASES: 64
steps:
- uses: actions/checkout@v4 # v4
with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable
- name: Ensure toolchain is active
shell: bash
run: |
CARGO_HOME="${CARGO_HOME:-$HOME/.cargo}"
export PATH="$CARGO_HOME/bin:$PATH"
rustup default stable
cargo --version
echo "$CARGO_HOME/bin" >> $GITHUB_PATH
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
with:
workspaces: rust -> target
# ---- Windows: MSYS2 + GNU target + jemalloc -------------------------
# The default MSVC toolchain can't build jemalloc (autotools mismatch).
# We switch to the GNU target with MSYS2/MinGW and pre-build jemalloc.
# See .github/actions/windows-jemalloc for the gory details.
- name: Setup MSYS2 and MinGW
if: runner.os == 'Windows'
uses: msys2/setup-msys2@e9898307ac31d1a803454791be09ab9973336e1c # v2
with:
msystem: MINGW64
update: true
install: base-devel mingw-w64-x86_64-toolchain
path-type: inherit
- name: Install Rust GNU target
if: runner.os == 'Windows'
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable
with:
targets: x86_64-pc-windows-gnu
- name: Build jemalloc + dummy liballoc.a (Windows only)
if: runner.os == 'Windows'
id: jemalloc
uses: ./.github/actions/windows-jemalloc
- name: Free disk space
if: runner.os == 'Linux'
shell: bash
run: |
sudo rm -rf /usr/share/dotnet /usr/local/lib/android /opt/ghc \
/opt/hostedtoolcache/CodeQL /usr/local/.ghcup /usr/share/swift \
/usr/local/share/powershell /usr/local/share/chromium \
/usr/local/share/boost /usr/lib/jvm /usr/local/julia* || true
sudo docker system prune -af --volumes >/dev/null 2>&1 || true
df -h /
- name: Run tests
working-directory: rust
shell: bash
run: |
if [[ "${{ runner.os }}" == "macOS" ]]; then
CARGO="${CARGO_HOME}/bin/cargo"
else
CARGO="cargo"
fi
if [[ "${{ runner.os }}" == "Windows" ]]; then
JEMALLOC_OVERRIDE="${{ steps.jemalloc.outputs.jemalloc-override }}" \
CARGO_TARGET_X86_64_PC_WINDOWS_GNU_RUSTFLAGS="-L ${{ steps.jemalloc.outputs.dummy-dir }}" \
"$CARGO" test --target x86_64-pc-windows-gnu --all-features -- --test-threads=1
else
"$CARGO" test --all-features -- --test-threads=1
fi
# #581: lock in the parallel BM25 (incremental) rebuild win against silent
# regression. Timing is only meaningful on a quiet, single-threaded runner,
# so the gate is env-opt-in (no-op in the matrix above) and runs once here.
- name: BM25 build-time regression gate (#581)
if: runner.os == 'Linux'
working-directory: rust
shell: bash
env:
LEAN_CTX_PERF_GATE: "1"
run: cargo test --all-features --lib -- --test-threads=1 parallel_incremental_rebuild_perf_gate --nocapture
clippy:
name: Clippy
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4 # v4
with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable
with:
components: clippy
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
with:
workspaces: rust -> target
- name: Run clippy
working-directory: rust
run: cargo clippy --all-features -- -D warnings
# Maintainability gate (#660): no Rust file grows past 1500 LOC
# (legacy files are frozen at 2000 via the script's allowlist).
- name: LOC gate (file size budget)
run: ./scripts/loc-gate.sh
# Guards the no-ORT build (#586). The `embeddings`/`neural` features pull the
# `ort` crate, whose `load-dynamic` dylib resolver has no fallback arm for
# non-tier-1 targets (FreeBSD, etc.), so community ports build with those
# features off. This job catches any code that references `ort`/embeddings
# without a feature gate. Dead code in the reduced build is expected, so
# warnings are not denied here (override the workflow-wide -Dwarnings).
build-minimal:
name: Build (no embeddings / no ORT)
runs-on: ubuntu-latest
env:
RUSTFLAGS: ""
steps:
- uses: actions/checkout@v4 # v4
with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
with:
workspaces: rust -> target
- name: Check build without ORT-backed features
working-directory: rust
run: cargo check --no-default-features --features "tree-sitter,http-server,team-server,secure-update,jemalloc"
# The no-tree-sitter combo is the slim-binary candidate (#663) and had
# rotted silently because only the no-ORT combo was checked.
- name: Check build without tree-sitter
working-directory: rust
run: cargo check --no-default-features --features "embeddings,http-server,team-server,secure-update,jemalloc"
fmt:
name: Format
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v4 # v4
with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable
with:
components: rustfmt
- name: Check formatting
working-directory: rust
run: cargo fmt --check
cookbook:
name: Cookbook (Node)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4 # v4
with:
persist-credentials: false
- uses: actions/setup-node@v4
with:
node-version: "20"
cache: "npm"
cache-dependency-path: cookbook/package-lock.json
- name: Install
working-directory: cookbook
run: npm ci
- name: Lint
working-directory: cookbook
run: npm run lint
- name: Typecheck
working-directory: cookbook
run: npm run typecheck
- name: Test
working-directory: cookbook
run: npm test
- name: Build
working-directory: cookbook
run: npm run build
pi-extension:
name: Pi Extension (Node)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4 # v4
with:
persist-credentials: false
# Fail fast if pi-lean-ctx / lean-ctx-bin drifted from the engine version,
# so the Pi extension can never quietly fall behind (npm skips the publish
# of a stale version). Runs on every push/PR — not just at release.
- name: Check npm package ↔ engine version coupling
run: python3 scripts/check-package-versions.py
- uses: actions/setup-node@v4
with:
node-version: "22"
cache: "npm"
cache-dependency-path: packages/pi-lean-ctx/package-lock.json
- name: Install
working-directory: packages/pi-lean-ctx
run: npm ci
- name: Typecheck
working-directory: packages/pi-lean-ctx
run: npm run typecheck
- name: Test
working-directory: packages/pi-lean-ctx
run: npm test
rust-sdk:
name: Rust SDK (lean-ctx-client)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4 # v4
with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable
with:
components: clippy, rustfmt
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
with:
workspaces: clients/rust/lean-ctx-client -> target
- name: Format
working-directory: clients/rust/lean-ctx-client
run: cargo fmt --check
- name: Clippy
working-directory: clients/rust/lean-ctx-client
run: cargo clippy --all-targets -- -D warnings
- name: Test
working-directory: clients/rust/lean-ctx-client
run: cargo test
- name: Docs
working-directory: clients/rust/lean-ctx-client
env:
RUSTDOCFLAGS: -D warnings
run: cargo doc --no-deps
embed-sdk:
# Track A — the in-process embedding SDK (rust/crates/lean-ctx-sdk). It is a
# workspace member but excluded from `default-members`, so the engine's own
# test/clippy/doc jobs never see it; this job is its dedicated gate
# (pedantic clippy, doctests, the engine-integration tests, example build).
name: Embed SDK (lean-ctx-sdk)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4 # v4
with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable
with:
components: clippy, rustfmt
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
with:
workspaces: rust -> target
- name: Format
working-directory: rust
run: cargo fmt -p lean-ctx-sdk --check
- name: Clippy (pedantic, 0 warnings)
working-directory: rust
run: cargo clippy -p lean-ctx-sdk --all-targets -- -D warnings
- name: Test (doctests + engine integration)
working-directory: rust
run: cargo test -p lean-ctx-sdk
- name: Example builds
working-directory: rust
run: cargo build -p lean-ctx-sdk --example embed
- name: Docs
working-directory: rust
env:
RUSTDOCFLAGS: -D warnings
run: cargo doc -p lean-ctx-sdk --no-deps
python-sdk:
name: Python SDK (leanctx)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4 # v4
with:
persist-credentials: false
- uses: actions/setup-python@v5 # v5
with:
python-version: "3.11"
- name: Install
working-directory: clients/python
run: pip install -e ".[dev]"
- name: Test
working-directory: clients/python
run: python -m pytest -q
hermes-plugin:
# The Hermes context-engine plugin (integrations/hermes-lean-ctx) replaces
# Hermes' built-in ContextCompressor with lean-ctx. Gate: pytest (ABC
# conformance + tool_call/tool_result invariant + daemon-down graceful) and
# a benchmark smoke that proves the harness runs offline (local fallback).
name: Hermes Plugin (Python)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4 # v4
with:
persist-credentials: false
- uses: actions/setup-python@v5 # v5
with:
python-version: "3.11"
- name: Install test deps
# Runtime dep (leanctx SDK) is wired onto sys.path by the plugin
# conftest from clients/python — no install needed. tiktoken makes the
# token count match the host model exactly; without it the plugin uses
# its char-based fallback (also tested).
run: pip install pytest tiktoken
- name: Run plugin tests
working-directory: integrations/hermes-lean-ctx
run: python -m pytest tests/ -q
- name: Benchmark smoke (offline, local fallback)
working-directory: integrations/hermes-lean-ctx
# Discard port → daemon unreachable → exercises deterministic local
# compaction end-to-end. Competitors are auto-skipped when absent.
run: python benchmarks/run.py --turns 40 --needles 6 --context-length 2000 --base-url http://127.0.0.1:9 --no-write
sdk-conformance:
# GL #395: every first-party SDK must prove the full /v1 contract against
# a real engine build. Red when an SDK misses a route, mistypes a shape,
# or the engine drifts from the frozen surface.
name: SDK Conformance Matrix
runs-on: ubuntu-latest
timeout-minutes: 45
steps:
- uses: actions/checkout@v4 # v4
with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
with:
workspaces: |
rust -> target
clients/rust/lean-ctx-client -> target
- uses: actions/setup-node@v4
with:
node-version: "20"
cache: "npm"
cache-dependency-path: cookbook/package-lock.json
- uses: actions/setup-python@v5 # v5
with:
python-version: "3.11"
- name: Install Python SDK
working-directory: clients/python
# langchain-core gives the adapter smoke a real framework round trip;
# LlamaIndex/CrewAI skip cleanly (heavy dep trees, covered by the
# framework-agnostic OpenAI adapter path).
run: pip install -e ".[dev]" langchain-core
- name: Run conformance kit (3 SDKs vs real server)
run: ./scripts/sdk-conformance.sh
- name: Upload conformance matrix
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: sdk-conformance-matrix
path: docs/reference/sdk-conformance-matrix.md
if-no-files-found: ignore
coverage:
name: Coverage
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4 # v4
with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable
with:
# tarpaulin's LLVM engine (below) needs llvm-profdata/llvm-cov from
# the rustup component — without it the engine silently falls back.
components: llvm-tools-preview
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
with:
workspaces: rust -> target
- name: Free disk space
run: |
sudo rm -rf /usr/share/dotnet /usr/local/lib/android /opt/ghc /opt/hostedtoolcache/CodeQL || true
df -h /
- uses: taiki-e/install-action@74e87cbfa15a59692b158178d8905a61bf6fca95 # v2
with:
tool: cargo-tarpaulin
- name: Generate coverage
working-directory: rust
env:
CARGO_BUILD_JOBS: "2"
# --engine llvm uses LLVM source-based coverage (instrument-coverage +
# llvm-profdata) instead of tarpaulin's default ptrace engine. ptrace
# is non-deterministically fragile on hosted runners: it corrupts the
# tracee's registers under load, surfacing as a bogus
# "memory allocation of 1407…bytes failed" abort followed by
# "ECHILD: No child processes" — a flake unrelated to the code (the
# Test matrix on the same SHA stays green). The LLVM engine never
# ptraces, so the whole class of flake disappears.
run: |
cargo tarpaulin --engine llvm --out xml --skip-clean --all-features --lib --timeout 180 \
--exclude-files 'src/cli/*' --exclude-files 'src/main.rs' \
-- --test-threads=1
pkill -f 'target.*lean-ctx' 2>/dev/null || true
- name: Upload to Codecov
uses: codecov/codecov-action@b9fd7d16f6d7d1b5d2bec1a2887e65ceed900238 # v4
with:
fail_ci_if_error: false
deny:
name: cargo-deny
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4 # v4
with:
persist-credentials: false
- uses: EmbarkStudios/cargo-deny-action@5bb39ff5d5a0e94dc9e2dc94eced0c6129743a57 # v2
with:
manifest-path: rust/Cargo.toml
adversarial:
name: Adversarial Safety
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4 # v4
with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
with:
workspaces: rust -> target
- name: Run adversarial compression tests
working-directory: rust
run: cargo test --test adversarial_compression -- --nocapture
bench:
name: Benchmarks
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
with:
workspaces: rust -> target
- name: Compile benchmarks
working-directory: rust
run: cargo bench --no-run
- name: Generate reproducible scorecard
working-directory: rust
run: cargo run --quiet -- benchmark scorecard --json --output ../scorecard.json
- name: Upload scorecard artifact
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: scorecard
path: scorecard.json
quality-gate:
name: Output-Quality Gate (eval A/B)
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v4 # v4
with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
with:
workspaces: rust -> target
- name: Build lean-ctx
working-directory: rust
run: cargo build --quiet
# Self-footprint gate (#578/#964): the fixed per-session cost lean-ctx
# itself injects (advertised tool schemas + MCP instructions + wakeup)
# must stay under budget. Runs in a scratch HOME + empty cwd so only OUR
# footprint is measured — no repo rules files, no developer state.
# Baseline after the diet: ~2147 tok (schemas 1685, instructions 462).
- name: Self-footprint gate (doctor overhead)
shell: bash
env:
LEAN_CTX_CONTEXT_BUDGET_TOKENS: "2600"
run: |
set -euo pipefail
BIN="$GITHUB_WORKSPACE/rust/target/debug/lean-ctx"
SCRATCH=$(mktemp -d)
mkdir -p "$SCRATCH/home" "$SCRATCH/data" "$SCRATCH/work"
cd "$SCRATCH/work"
HOME="$SCRATCH/home" LEAN_CTX_DATA_DIR="$SCRATCH/data" "$BIN" doctor overhead --gate
# Deterministic with/without output-quality proof. Three modes, in priority order:
# 1. A committed recording (rust/eval/recording.json) → byte-stable replay, no secrets,
# and the gate BLOCKS on a regression.
# 2. Otherwise, if LEAN_CTX_EVAL_* secrets are set → capture from the live model + gate.
# 3. Otherwise → skipped (the harness itself is still covered by the test job).
# Capture a recording once with: lean-ctx eval ab --suite … --record rust/eval/recording.json
- name: Run deterministic A/B output-quality gate
working-directory: rust
shell: bash
env:
LEAN_CTX_EVAL_MODEL_URL: ${{ secrets.LEAN_CTX_EVAL_MODEL_URL }}
LEAN_CTX_EVAL_MODEL: ${{ secrets.LEAN_CTX_EVAL_MODEL }}
LEAN_CTX_EVAL_MODEL_KEY: ${{ secrets.LEAN_CTX_EVAL_MODEL_KEY }}
run: |
set -euo pipefail
BIN=./target/debug/lean-ctx
REC=eval/recording.json
"$BIN" eval init eval-suite
if [ -f "$REC" ]; then
echo "Replaying committed recording (deterministic, no secrets)…"
"$BIN" eval ab --suite eval-suite/suite.ndjson --replay "$REC" --out ab-report.json --gate
elif [ -n "${LEAN_CTX_EVAL_MODEL_URL:-}" ]; then
echo "No committed recording; capturing from the configured live model…"
mkdir -p eval
"$BIN" eval ab --suite eval-suite/suite.ndjson --record "$REC" --out ab-report.json --gate
else
echo "::notice::Output-quality gate skipped — commit rust/eval/recording.json or set LEAN_CTX_EVAL_* secrets."
fi
- name: Verify artifact (signature + determinism digest)
working-directory: rust
shell: bash
run: |
if [ -f ab-report.json ]; then ./target/debug/lean-ctx eval verify ab-report.json; fi
- name: Upload signed A/B report
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: eval-ab-report
path: rust/ab-report.json
if-no-files-found: ignore
# Off-vs-on answer-quality testbench across pinned repos (#611). The committed
# local-fixture subset replays with a recorded model → byte-stable, no secrets,
# and BLOCKS on any per-repo regression. Refresh with:
# lean-ctx eval testbench --lock eval/testbench/testbench.lock.json --record eval/testbench/recording.json
- name: Run deterministic testbench gate (off vs on)
working-directory: rust
shell: bash
run: |
set -euo pipefail
BIN=./target/debug/lean-ctx
LOCK=eval/testbench/testbench.lock.json
REC=eval/testbench/recording.json
if [ -f "$REC" ]; then
echo "Replaying committed testbench subset (deterministic, no secrets)…"
"$BIN" eval testbench --lock "$LOCK" --replay "$REC" --out testbench-out --gate
else
echo "::notice::Testbench gate skipped — commit rust/eval/testbench/recording.json."
fi
- name: Upload testbench FINDINGS
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: testbench-findings
path: |
rust/testbench-out/FINDINGS.md
rust/testbench-out/regressions.json
if-no-files-found: ignore
doc:
name: Documentation
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4 # v4
with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
with:
workspaces: rust -> target
- name: Check docs compile
working-directory: rust
env:
RUSTDOCFLAGS: -D warnings
run: cargo doc --no-deps --all-features
- name: Check generated reference docs are current
working-directory: rust
run: cargo run --example gen_docs --features dev-tools -- --check
- name: Check bundled registry snapshots are canonical
working-directory: rust
run: cargo run --example gen_registry --features dev-tools -- --check
- name: Check committed testbench recording is current
working-directory: rust
run: cargo run --example gen_testbench_recording --features dev-tools -- --check
# Single aggregating gate. Point branch protection at THIS one job ("CI Green")
# instead of every matrix leg — it stays correct when jobs are added/removed and
# cannot be bypassed by a silently-missing required check. Runs always() so it can
# inspect results even when a dependency failed; fails only on failure/cancelled
# (a legitimately skipped job — e.g. the optional quality-gate — does not block).
ci-green:
name: CI Green
if: always()
needs:
- test
- clippy
- build-minimal
- fmt
- cookbook
- pi-extension
- rust-sdk
- embed-sdk
- python-sdk
- hermes-plugin
- sdk-conformance
- coverage
- deny
- adversarial
- bench
- quality-gate
- doc
runs-on: ubuntu-latest
steps:
- name: Verify no required job failed
shell: bash
env:
NEEDS_JSON: ${{ toJSON(needs) }}
run: |
echo "$NEEDS_JSON"
python3 - <<'PY'
import json, os, sys
needs = json.loads(os.environ["NEEDS_JSON"])
blocked = {k: v["result"] for k, v in needs.items()
if v["result"] in ("failure", "cancelled")}
if blocked:
print("Required CI jobs did not pass:", blocked)
sys.exit(1)
print("All required CI jobs passed (success or skipped).")
PY
# Security audit runs in security-check.yml workflow