Skip to content

CI

CI #1427

Workflow file for this run

name: CI
on:
pull_request:
branches: [main, staging]
push:
branches: [main, staging]
schedule:
- cron: "17 8 * * *"
workflow_dispatch:
concurrency:
group: ci-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
env:
CARGO_TERM_COLOR: always
RUST_BACKTRACE: 1
RUST_TOOLCHAIN: nightly-2026-02-08
jobs:
# ─────────────────────────────────────────────────────
# Gate 0: Verification contract (seconds)
# ─────────────────────────────────────────────────────
verification-contract:
name: Verification Contract (verification.v1)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install git hook wrappers
run: bash scripts/setup-hooks.sh
- name: Generate verification.v1 report
env:
VERIFICATION_AGENT_PROVIDER: github-actions
VERIFICATION_AGENT_MODEL: ci
run: |
bash scripts/verification-v1-report.sh \
--session "${{ github.run_id }}" \
--out verification.v1.json \
--pretty
- name: Validate verification.v1 contract
run: bash scripts/verification-v1-validate.sh verification.v1.json
- name: Enforce verification policy
run: |
jq -e '
.summary.blocking_failures == 0 and
.summary.checks_failed == 0
' verification.v1.json >/dev/null
jq '{summary: .summary}' verification.v1.json
- name: Upload verification artifact
if: always()
uses: actions/upload-artifact@v4
with:
name: verification-v1
path: verification.v1.json
# ─────────────────────────────────────────────────────
# Gate 1: Fast checks (seconds)
# ─────────────────────────────────────────────────────
check:
name: Compile & Lint
runs-on: ubuntu-latest
timeout-minutes: 45
steps:
- uses: actions/checkout@v4
- name: Install system dependencies
run: sudo apt-get update && sudo apt-get install -y libz3-dev
- name: Free disk space for full workspace tests
run: |
sudo rm -rf /usr/local/lib/android /usr/share/dotnet /opt/ghc /opt/hostedtoolcache/CodeQL || true
df -h
- name: Install pinned Rust toolchain
uses: dtolnay/rust-toolchain@nightly
with:
toolchain: ${{ env.RUST_TOOLCHAIN }}
components: clippy, rustfmt
- name: cargo fmt --check
run: cargo fmt --check
- name: Cache cargo registry
uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
target/
key: ${{ runner.os }}-cargo-check-${{ hashFiles('**/Cargo.lock') }}
restore-keys: ${{ runner.os }}-cargo-check-
- name: cargo check --workspace
run: cargo check --workspace
- name: cargo clippy --workspace --all-targets
run: cargo clippy --workspace --all-targets -- -D warnings
# ─────────────────────────────────────────────────────
# Gate 1b: Bench compile (non-PR events only)
# ─────────────────────────────────────────────────────
bench-build:
name: Bench Build
runs-on: ubuntu-latest
if: github.event_name != 'pull_request'
timeout-minutes: 45
steps:
- uses: actions/checkout@v4
- name: Install system dependencies
run: sudo apt-get update && sudo apt-get install -y libz3-dev
- name: Install pinned Rust toolchain
uses: dtolnay/rust-toolchain@nightly
with:
toolchain: ${{ env.RUST_TOOLCHAIN }}
- name: Cache cargo registry
uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
target/
key: ${{ runner.os }}-cargo-bench-${{ hashFiles('**/Cargo.lock') }}
restore-keys: ${{ runner.os }}-cargo-bench-
- name: cargo bench --workspace --no-run
run: cargo bench --workspace --no-run
# ─────────────────────────────────────────────────────
# Gate 2: Integrity checks (seconds)
# ─────────────────────────────────────────────────────
integrity:
name: Integrity & DST Patterns
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install pinned Rust toolchain
uses: dtolnay/rust-toolchain@nightly
with:
toolchain: ${{ env.RUST_TOOLCHAIN }}
- name: Cache cargo registry
uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
target/
key: ${{ runner.os }}-cargo-integrity-${{ hashFiles('**/Cargo.lock') }}
restore-keys: ${{ runner.os }}-cargo-integrity-
- name: No TODO/FIXME/HACK in production code
run: |
# Strip #[cfg(test)] blocks and proc macro crates before scanning
FOUND=false
for f in $(find crates -name '*.rs' -not -path '*/tests/*' -not -name '*_test.rs' -not -name '*_tests.rs' -not -name 'tests.rs' -not -path '*/temper-macros/*'); do
# Extract only production code (stop at #[cfg(test)])
PROD_CODE="$(awk '/^#\[cfg\(test\)\]/{exit} {print}' "$f")"
if echo "$PROD_CODE" | grep -E '(TODO|FIXME|XXX|HACK)\b' | grep -v '// ci-ok' | grep -qv '^[[:space:]]*//' 2>/dev/null; then
echo "FAIL: $f contains TODO/FIXME/HACK"
echo "$PROD_CODE" | grep -nE '(TODO|FIXME|XXX|HACK)\b' | grep -v '// ci-ok' | grep -v '^[[:space:]]*//'
FOUND=true
fi
done
if [ "$FOUND" = true ]; then exit 1; fi
echo "Integrity check: OK"
- name: No unwrap() in production code
run: |
# Strip #[cfg(test)] blocks and proc macro crates before scanning.
# Exclude safe unwrap patterns:
# - .read().unwrap() / .write().unwrap() / .lock().unwrap() — RwLock/Mutex
# (poisoned lock = prior panic, TigerStyle fail-fast is correct)
# - .with_ymd_and_hms(...).unwrap() — chrono infallible for valid dates
FOUND=false
for f in $(find crates -name '*.rs' -not -path '*/tests/*' -not -name '*_test.rs' -not -name '*_tests.rs' -not -name 'tests.rs' -not -path '*/temper-macros/*' -not -path '*/benches/*'); do
PROD_CODE="$(awk '/^#\[cfg\(test\)\]/{exit} {print}' "$f")"
FILTERED="$(echo "$PROD_CODE" | grep '\.unwrap()' | grep -v '// ci-ok' | grep -v '^[[:space:]]*//' | grep -v '\.read()\.unwrap()' | grep -v '\.write()\.unwrap()' | grep -v '\.lock()\.unwrap()' | grep -v 'with_ymd_and_hms.*\.unwrap()' || true)"
if [ -n "$FILTERED" ]; then
echo "FAIL: $f contains .unwrap()"
echo "$FILTERED"
FOUND=true
fi
done
if [ "$FOUND" = true ]; then exit 1; fi
echo "No unwrap: OK"
- name: Readability ratchet
run: bash scripts/readability-ratchet.sh check .ci/readability-baseline.env
- name: Storage dispatch boundary ratchet
run: bash scripts/check-storage-dispatch-boundary.sh
# DST pattern scan DISABLED (2026-02-17)
# Reason: Grep-based pattern matching is too brittle and caused 5+ consecutive
# CI failures from false positives. The DST reviewer agent (.claude/agents/dst-reviewer.md)
# performs semantic analysis which is strictly superior to regex scanning.
# The pattern scan will be re-evaluated once the DST architecture refactor
# (shared effect application) is complete.
# See: .progress/012_20260217_dst_architecture_fix.md
- name: Dependency isolation
run: |
# Check temper-jit has no production dep on temper-verify
if cargo tree --no-dev -p temper-jit 2>/dev/null | grep -q temper-verify; then
echo "FAIL: temper-jit has production dependency on temper-verify"
exit 1
fi
# Check no stateright/proptest in production crates
for crate in temper-jit temper-server temper-runtime; do
if cargo tree --no-dev -p "$crate" 2>/dev/null | grep -qE 'stateright|proptest'; then
echo "FAIL: $crate production binary includes stateright or proptest"
exit 1
fi
done
echo "Dependency isolation: OK"
# ─────────────────────────────────────────────────────
# Gate 3: Full test suite (minutes)
# ─────────────────────────────────────────────────────
test:
name: Tests
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Install system dependencies
run: sudo apt-get update && sudo apt-get install -y libz3-dev
- name: Install pinned Rust toolchain
uses: dtolnay/rust-toolchain@nightly
with:
toolchain: ${{ env.RUST_TOOLCHAIN }}
- name: Cache cargo registry
uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
key: ${{ runner.os }}-cargo-test-${{ hashFiles('**/Cargo.lock') }}
restore-keys: ${{ runner.os }}-cargo-test-
- name: Install wasm target for GEPA test modules
run: rustup target add wasm32-unknown-unknown
- name: Build GEPA test WASM modules
run: |
for module in gepa-replay gepa-reflective gepa-score gepa-pareto gepa-verify; do
(cd "wasm-modules/$module" && cargo build --target wasm32-unknown-unknown --release)
done
- name: cargo test --workspace -- --skip dst_
run: cargo test --workspace -- --skip dst_
# spec_validate_endpoint declares required-features = ["observe"]. The
# workspace run above usually still covers it via feature unification
# (temper-cli/temper-mcp enable temper-server's observe feature), but
# that coverage is incidental — it evaporates if those edges change.
# Run the observe-gated tests explicitly so coverage is guaranteed.
- name: cargo test observe-gated tests
run: |
cargo test -p temper-server --features observe --test spec_validate_endpoint
cargo test -p temper-server --features observe --lib observe::
# ─────────────────────────────────────────────────────
# Gate 3b: DST/platform coverage (matrixed)
# ─────────────────────────────────────────────────────
dst-platform-tests:
name: DST/Platform Tests (${{ matrix.suite }})
runs-on: ubuntu-latest
timeout-minutes: 90
strategy:
fail-fast: false
matrix:
include:
- suite: core
command: cargo test -p temper-server --test dst_concurrency_retry --test dst_hotswap --test dst_lifecycle --test dst_multi_tenant --test dst_persistence
- suite: platform-boot
command: cargo test -p temper-server --test dst_platform_boot
- suite: platform-consistency
command: cargo test -p temper-server --test dst_platform_cedar --test dst_platform_index --test dst_platform_rollback
- suite: platform-random
command: cargo test -p temper-server --test dst_platform_random
steps:
- uses: actions/checkout@v4
- name: Install system dependencies
run: sudo apt-get update && sudo apt-get install -y libz3-dev
- name: Install pinned Rust toolchain
uses: dtolnay/rust-toolchain@nightly
with:
toolchain: ${{ env.RUST_TOOLCHAIN }}
- name: Cache cargo registry
uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
target/
key: ${{ runner.os }}-cargo-dst-${{ matrix.suite }}-${{ hashFiles('**/Cargo.lock') }}
restore-keys: ${{ runner.os }}-cargo-dst-${{ matrix.suite }}-
- name: cargo test DST/platform suite
env:
TEMPER_DST_RANDOM_MODE: ${{ matrix.suite == 'platform-random' && github.event_name == 'pull_request' && 'smoke' || 'full' }}
run: ${{ matrix.command }}
# ─────────────────────────────────────────────────────
# Gate 4: Spec verification (minutes)
# ─────────────────────────────────────────────────────
verify-specs:
name: Spec Verification (L0-L3)
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
- name: Install system dependencies
run: sudo apt-get update && sudo apt-get install -y libz3-dev
- name: Install pinned Rust toolchain
uses: dtolnay/rust-toolchain@nightly
with:
toolchain: ${{ env.RUST_TOOLCHAIN }}
- name: Cache cargo registry
uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
target/
key: ${{ runner.os }}-cargo-verify-${{ hashFiles('**/Cargo.lock') }}
restore-keys: ${{ runner.os }}-cargo-verify-
- name: Build temper-cli
run: cargo build -p temper-cli
- name: Verify all spec directories
run: |
# Find all directories containing .ioa.toml files
SPEC_DIRS=$(find . -name '*.ioa.toml' -exec dirname {} \; | sort -u)
if [ -z "$SPEC_DIRS" ]; then
echo "No spec directories found — skipping verification"
exit 0
fi
ALL_PASSED=true
for dir in $SPEC_DIRS; do
# Only verify dirs that also have a CSDL model
if [ -f "$dir/model.csdl.xml" ]; then
echo "Verifying specs in $dir..."
if ! cargo run -p temper-cli -- verify --specs-dir "$dir"; then
echo "FAIL: Spec verification failed for $dir"
ALL_PASSED=false
fi
fi
done
if [ "$ALL_PASSED" = false ]; then exit 1; fi
echo "All specs verified: OK"
# ─────────────────────────────────────────────────────
# Gate N: Instrumentation hygiene (ADR-0052)
# Fails when a metric registration has no emission site.
# ─────────────────────────────────────────────────────
instrumentation-hygiene:
name: Instrumentation Hygiene (ADR-0052)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install rust toolchain
uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{ env.RUST_TOOLCHAIN }}
- uses: actions/cache@v4
with:
path: |
~/.cargo/bin
~/.cargo/registry
~/.cargo/git
target/
key: ${{ runner.os }}-cargo-instrumentation-${{ hashFiles('**/Cargo.lock') }}
restore-keys: ${{ runner.os }}-cargo-instrumentation-
- name: Run check_instrumentation
run: cargo run -q -p temper-server --bin check_instrumentation