Merge pull request #167 from AreevAI/release/v1.7.2 #362
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: CI | |
| on: | |
| push: | |
| branches: [main] | |
| pull_request: | |
| workflow_dispatch: | |
| concurrency: | |
| group: ci-${{ github.ref }} | |
| cancel-in-progress: true | |
| env: | |
| CARGO_TERM_COLOR: always | |
| RUST_BACKTRACE: 1 | |
| # `cargo test --workspace` links ~113 separate test binaries, and each one | |
| # emits full debug info. On Windows that is a large share of both link time | |
| # and disk I/O (measured: ~6 min between the last `Compiling` line and the | |
| # first test). Line tables keep the file:line that RUST_BACKTRACE=1 above | |
| # exists to produce, at a fraction of the size. CI-only, set here rather | |
| # than in Cargo.toml so local `cargo test` debugging is untouched. | |
| CARGO_PROFILE_TEST_DEBUG: line-tables-only | |
| CARGO_PROFILE_DEV_DEBUG: line-tables-only | |
| jobs: | |
| test: | |
| name: test (${{ matrix.os }}) | |
| runs-on: ${{ matrix.os }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| os: [ubuntu-latest, macos-latest, windows-latest] | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: dtolnay/rust-toolchain@stable | |
| - uses: actions/setup-python@v7 | |
| with: | |
| python-version: "3.11" | |
| # Defender scans every file cargo writes, and this job writes a lot of | |
| # them. Best-effort: a future runner image that refuses the exclusion | |
| # must slow the build down, not fail it. | |
| - name: Exclude the build tree from Defender (windows) | |
| if: runner.os == 'Windows' | |
| continue-on-error: true | |
| shell: pwsh | |
| run: | | |
| Add-MpPreference -ExclusionPath "${{ github.workspace }}" | |
| Add-MpPreference -ExclusionPath "$env:USERPROFILE\.cargo" | |
| foreach ($p in 'rustc.exe','cargo.exe','link.exe') { | |
| Add-MpPreference -ExclusionProcess $p | |
| } | |
| - uses: Swatinem/rust-cache@v2 | |
| - uses: taiki-e/install-action@nextest | |
| # `cargo test` runs test BINARIES one at a time, parallelising only | |
| # within each; with ~113 binaries, most of them small, the cores sit | |
| # idle. Measured on windows-latest: 11.1 min of test execution out of a | |
| # 22 min step. nextest schedules across binaries instead (locally: | |
| # 55s -> 30s for the same already-built suite, 2,535 tests, same result). | |
| # | |
| # Sharding this job was considered and rejected on the same numbers: | |
| # only ~11 of the 22 min is test execution, so every shard would repeat | |
| # the ~11 min of compile+link to split the other half — near-zero | |
| # wall-clock gain for 3x the runner minutes. It would also have to keep | |
| # reporting under the exact name `test (windows-latest)`, which the | |
| # protect-main ruleset requires. | |
| - name: Test workspace | |
| run: cargo nextest run --workspace | |
| # nextest deliberately does not run doctests, so they stay on cargo. | |
| - name: Doctests | |
| run: cargo test --workspace --doc | |
| postgres: | |
| name: postgres backend | |
| runs-on: ubuntu-latest | |
| services: | |
| postgres: | |
| image: pgvector/pgvector:pg16 | |
| env: | |
| POSTGRES_PASSWORD: postgres | |
| POSTGRES_DB: areev_test | |
| ports: ["5432:5432"] | |
| # Mandatory: the service starts concurrently with the job and the | |
| # first connection races it without a health gate. | |
| options: >- | |
| --health-cmd "pg_isready -U postgres" | |
| --health-interval 5s | |
| --health-timeout 5s | |
| --health-retries 10 | |
| env: | |
| DATABASE_URL: postgres://postgres:postgres@127.0.0.1:5432/areev_test | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: dtolnay/rust-toolchain@stable | |
| with: | |
| components: clippy | |
| - uses: actions/setup-python@v7 | |
| with: | |
| python-version: "3.11" | |
| - uses: actions/setup-node@v4 | |
| with: | |
| node-version: 22 | |
| - uses: Swatinem/rust-cache@v2 | |
| # The conformance runner hard-fails (rather than skipping) when | |
| # CI=true and DATABASE_URL is missing, so a broken service can never | |
| # masquerade as a skipped suite. | |
| - name: Conformance + CAL smoke (postgres) | |
| run: cargo test -p areev-conformance --features postgres | |
| - name: Store unit tests with the postgres feature | |
| run: cargo test -p areev-store --features postgres --lib | |
| # `postgres-tls` is a SEPARATE build, not a superset in test terms: the | |
| # refusal path (STO-E003, "asks for encryption, gets none") only exists | |
| # in the build above, and the verifiers only exist in this one. Its | |
| # handshake tests need no database — they stand up a throwaway CA and a | |
| # fake Postgres on loopback — so they belong wherever the feature | |
| # compiles. | |
| - name: Store unit tests with the postgres-tls feature | |
| run: cargo test -p areev-store --features postgres-tls --lib | |
| # The workspace clippy job lints default features only; without this | |
| # the feature-gated backend would accumulate warnings invisibly (and | |
| # docs.rs builds all-features, so it must at least compile clean). | |
| - name: Clippy with the postgres feature | |
| run: cargo clippy -p areev-store -p areev-conformance --features postgres --all-targets -- -D warnings | |
| - name: Clippy with the postgres-tls feature | |
| run: cargo clippy -p areev-store -p areev-conformance --features postgres-tls --all-targets -- -D warnings | |
| # The bindings against a REAL postgres — the deployment surface the | |
| # server tier exists for. (The python/node jobs have no database, so | |
| # their postgres tests skip there; this is where they run.) | |
| - name: Python bindings against postgres | |
| run: | | |
| python -m venv .venv | |
| .venv/bin/pip install maturin pytest | |
| VIRTUAL_ENV=$PWD/.venv .venv/bin/maturin develop -m crates/areev-py/Cargo.toml | |
| .venv/bin/pytest crates/areev-py/tests/ -q | |
| - name: Node bindings against postgres | |
| working-directory: crates/areev-js | |
| run: | | |
| npm install | |
| npx napi build --platform | |
| node --test __test__/smoke.mjs __test__/pg_smoke.mjs | |
| clippy: | |
| name: clippy | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: dtolnay/rust-toolchain@stable | |
| with: | |
| components: clippy | |
| - uses: actions/setup-python@v7 | |
| with: | |
| python-version: "3.11" | |
| - uses: Swatinem/rust-cache@v2 | |
| - name: Clippy | |
| run: cargo clippy --workspace --all-targets -- -D warnings | |
| # The scheduler core's purity is enforced mechanically (governed-agents | |
| # §4): its dependency tree may not contain a clock, randomness, or IO | |
| # crate — one SystemTime::now() silently invalidates deterministic | |
| # replay. `cargo deny` cannot ban std, so: (a) the dep tree is grepped | |
| # for the crates that would smuggle ambient authority in, and (b) the | |
| # crate-local clippy.toml disallowing SystemTime::now/Instant::now may | |
| # never be silenced with an allow(). | |
| - name: areev-run-core purity (no clock/rand/net in the dep tree) | |
| run: | | |
| set -e | |
| BAD=$(cargo tree -p areev-run-core -e normal --prefix none \ | |
| | awk '{print $1}' | sort -u \ | |
| | grep -E '^(rand|rand_core|getrandom|chrono|time|tokio|ureq|reqwest|hyper|mio)$' || true) | |
| if [ -n "$BAD" ]; then | |
| echo "areev-run-core dependency tree contains ambient-authority crates:"; echo "$BAD"; exit 1 | |
| fi | |
| - name: areev-run-core purity (disallowed_methods never silenced) | |
| run: | | |
| if grep -rn "allow(clippy::disallowed_methods)" crates/areev-run-core/src; then | |
| echo "the purity lint may not be silenced in the scheduler core"; exit 1 | |
| fi | |
| # NOTE: `cargo fmt --check` is intentionally NOT gated — the tree is not | |
| # uniformly rustfmt-formatted by design (see CONTRIBUTING.md). Contributors | |
| # format only the lines they touch. | |
| msrv: | |
| name: msrv | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: dtolnay/rust-toolchain@1.90.0 | |
| - uses: actions/setup-python@v7 | |
| with: | |
| python-version: "3.11" | |
| - uses: Swatinem/rust-cache@v2 | |
| - name: Build on minimum supported Rust version | |
| run: cargo build --workspace --locked | |
| docs: | |
| name: doc | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: dtolnay/rust-toolchain@stable | |
| - uses: actions/setup-python@v7 | |
| with: | |
| python-version: "3.11" | |
| - uses: Swatinem/rust-cache@v2 | |
| - name: Build API docs | |
| run: cargo doc --workspace --no-deps | |
| coverage: | |
| name: coverage | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: dtolnay/rust-toolchain@stable | |
| with: | |
| components: llvm-tools-preview | |
| - uses: actions/setup-python@v7 | |
| with: | |
| python-version: "3.11" | |
| - uses: Swatinem/rust-cache@v2 | |
| - uses: taiki-e/install-action@cargo-llvm-cov | |
| - name: Measure coverage | |
| run: cargo llvm-cov --workspace --lcov --output-path lcov.info | |
| # Three gates: the committed figure has not drifted (the README quotes | |
| # it), the scored set is above its global floor, and no individual crate | |
| # has slipped below its own floor. Per-crate is the point — one workspace | |
| # number lets a regression in one crate hide behind a gain in another. | |
| - name: Coverage figure is current and every crate is above its floor | |
| run: python3 scripts/coverage.py --lcov lcov.info --check | |
| - name: Upload coverage report | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: coverage-lcov | |
| path: lcov.info | |
| if-no-files-found: error | |
| python: | |
| name: python bindings | |
| 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: actions/setup-python@v7 | |
| with: | |
| python-version: "3.11" | |
| - uses: Swatinem/rust-cache@v2 | |
| - name: Build the extension module + run pytest | |
| run: | | |
| python -m venv .venv | |
| . .venv/bin/activate | |
| python -m pip install --upgrade pip maturin pytest | |
| maturin develop -m crates/areev-py/Cargo.toml | |
| pytest crates/areev-py/tests/ -v | |
| # The Hermes MemoryProvider plugin needs exactly what this job already | |
| # built — a maturin `areev` in .venv — and nothing else. It used to | |
| # ride the `adapters` job's venv for that reason; that job left with | |
| # the adapters (AreevAI/areev-adapters), the plugin did not. | |
| - name: Hermes provider smoke | |
| run: | | |
| . .venv/bin/activate | |
| python examples/hermes/test_provider.py | |
| agent-example: | |
| # The keyless floor, enforced. `examples/agents/` promises every agent | |
| # example runs end to end with no credential, no network and no model | |
| # key — both acts, smoke then improve — and that where an agent ships | |
| # more than one language stack, all of them mint the identical | |
| # content-addressed plan (created_at is pinned; run-smokes.sh compares | |
| # the hashes). `invoice-to-accounting` ships Python + TypeScript + Rust; | |
| # the other agents are Python, and run-smokes.sh skips a stack an agent | |
| # does not have before it ever consults REQUIRE, so adding a language is | |
| # a wrapper plus an agent file and needs no edit here. | |
| # An untested example in the flagship repo is wrong at exactly the | |
| # moment somebody is evaluating, so the promise is a job, not a README | |
| # claim — the SAME entry point a developer runs locally before pushing | |
| # (docs: examples/agents/docs/testing.md). | |
| name: agent examples (keyless, every stack) | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: dtolnay/rust-toolchain@stable | |
| - uses: actions/setup-python@v7 | |
| with: | |
| python-version: "3.11" | |
| - uses: actions/setup-node@v7 | |
| with: | |
| node-version: "22" | |
| - uses: Swatinem/rust-cache@v2 | |
| - name: Build the Python binding into a venv (maturin) | |
| run: | | |
| python -m venv .venv | |
| . .venv/bin/activate | |
| python -m pip install --upgrade pip maturin | |
| maturin develop --release -m crates/areev-py/Cargo.toml | |
| - name: Build the Node binding (napi) | |
| run: cd crates/areev-js && npm ci && npm run build | |
| # Both acts of every agent, in all three stacks, plus the | |
| # cross-language plan-hash comparison. CARGO_TARGET_DIR points the | |
| # detached Rust stack at the cached workspace target so its path deps | |
| # do not recompile from scratch. | |
| - name: Every agent, every language — smoke + improve | |
| run: | | |
| . .venv/bin/activate | |
| CARGO_TARGET_DIR="$PWD/target" REQUIRE="python typescript rust" \ | |
| examples/agents/run-smokes.sh | |
| tls: | |
| # The non-default native-TLS feature (the recorded rustls exception): | |
| # round-trip + no-plaintext-downgrade, plus a clean feature build. | |
| name: tls feature | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: dtolnay/rust-toolchain@stable | |
| - uses: Swatinem/rust-cache@v2 | |
| - run: cargo test -p areev-server --features tls | |
| - run: cargo check -p areev --features tls | |
| node: | |
| name: node binding (${{ matrix.os }}) | |
| 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: actions/setup-node@v7 | |
| with: | |
| node-version: "22" | |
| - uses: Swatinem/rust-cache@v2 | |
| # areev-js is a DETACHED cargo workspace with its own Cargo.lock, so a | |
| # dependency added to any workspace crate it depends on (areev-run, | |
| # areev-store, …) does not reach this lockfile — and Dependabot's cargo | |
| # entry for "/" does not either. It drifts silently, and since | |
| # release-npm.yml builds `--locked`, the drift surfaces as a failed | |
| # RELEASE rather than a failed build. Assert it here instead. | |
| # Fix: `cd crates/areev-js && cargo metadata >/dev/null` and commit. | |
| - name: Assert the areev-js lockfile is current | |
| working-directory: crates/areev-js | |
| run: cargo metadata --locked --format-version 1 > /dev/null | |
| - name: Build the native addon + run smoke test | |
| working-directory: crates/areev-js | |
| run: | | |
| npm ci | |
| npx napi build --platform --release -- --locked | |
| node --test __test__/smoke.mjs __test__/pg_smoke.mjs | |
| # `index.js` / `index.d.ts` are generated by `napi build`, but they are | |
| # ALSO the files npm ships (package.json `main`/`types`/`files`) — and | |
| # release-npm.yml's publish job never regenerates them: it publishes them | |
| # straight from the checkout, since the build legs upload only `*.node`. | |
| # A stale committed copy therefore ships a loader that does not match the | |
| # toolchain that built the binaries, and nothing else would catch it. | |
| # (This is why they are tracked rather than gitignored — gitignoring them | |
| # would publish a package whose `main` does not exist.) | |
| # Regenerate with `npx napi build --platform --release` and commit. | |
| # napi's output is host-independent, so this holds on every matrix leg. | |
| - name: Assert the generated binding loader is committed | |
| working-directory: crates/areev-js | |
| run: git diff --exit-code -- index.js index.d.ts | |
| # areev-sandbox is a standalone package, deliberately outside the workspace | |
| # (it carries wasmi). `cargo test --workspace` never touches it, so it needs | |
| # its own job or a change here can break it silently — the same reason | |
| # areev-js has one. | |
| sandbox: | |
| name: sandbox (wasm tier C) | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: dtolnay/rust-toolchain@stable | |
| - name: build | |
| run: cargo build --manifest-path areev-sandbox/Cargo.toml | |
| - name: test | |
| run: cargo test --manifest-path areev-sandbox/Cargo.toml | |
| - name: clippy | |
| run: cargo clippy --manifest-path areev-sandbox/Cargo.toml --all-targets -- -D warnings | |
| # The version lives in five places and only one of them is inherited | |
| # (`version.workspace`). maturin reads pyproject.toml, npm reads | |
| # package.json, and the GENERATED areev-js/index.js hard-codes it in every | |
| # platform arm. Both drift modes have shipped: a workspace-only bump makes | |
| # the publish workflows skip-existing over the released version (a green run | |
| # that ships nothing), and a package.json bump without regenerating index.js | |
| # breaks require() for anyone with NAPI_RS_ENFORCE_VERSION_CHECK set. Catch | |
| # it on the PR that introduces it, not on release day. | |
| # The self-improvement bench's keyless floor. `--mock` swaps the live model | |
| # for a deterministic agent, so this asserts the PLUMBING of the A/B/A/B | |
| # causal claim — lessons applied raise held-out success, rollback returns it | |
| # to baseline, re-apply recovers it, and rollback really does empty the | |
| # LESSONS prompt section. It is never a learning claim (no model runs); the | |
| # live numbers come from `--agent-cmd` and are published with transcripts. | |
| # `--arms` extends the same floor to the passive-memory providers: keyless | |
| # throughout (m-llm summarizes with the mock summarizer, no API key), and | |
| # PLUMBING-ONLY by construction — the deterministic agent complies with any | |
| # context it is handed, so these arms prove a provider reached the prompt | |
| # and can never rank curation against retrieval. `--workers 4` is the | |
| # parallel path; it must emit byte-identical output to `--workers 1`, which | |
| # `cargo test -p areev-bench` pins separately. | |
| selfimprove: | |
| name: selfimprove A/B/A/B + passive arms (mock) | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: dtolnay/rust-toolchain@stable | |
| - uses: Swatinem/rust-cache@v2 | |
| - name: A/B/A/B shape holds under the deterministic agent | |
| run: | | |
| cargo run -p areev-bench --bin selfimprove_aba -- \ | |
| --workdir "$RUNNER_TEMP/aba" --seed 1 --mock --assert-shape \ | |
| --arms m-steel,m-all,m-llm --workers 4 | |
| # The governed-only invocation is what SELFIMPROVE.md's "Live pilot" | |
| # block tells a reader to run, and it is a different code path from the | |
| # one above (no providers, no arm bookkeeping). Exercised keylessly here | |
| # so the documented command cannot rot. | |
| - name: The governed-only path still runs standalone | |
| run: | | |
| cargo run -p areev-bench --bin selfimprove_aba -- \ | |
| --workdir "$RUNNER_TEMP/aba-governed" --seed 1 --mock --assert-shape | |
| # The loop+LLM arm: the canned loop-LLM authors a lesson, the scripted | |
| # review applies it, rollback removes it, re-apply restores it — the | |
| # authored-lesson lifecycle asserted keylessly (the shape floor, never | |
| # a learning claim; the canned lesson deliberately cannot move the | |
| # mock agent's rates). | |
| - name: The loop+LLM arm applies and restores an authored lesson | |
| run: | | |
| cargo run -p areev-bench --bin selfimprove_aba -- \ | |
| --workdir "$RUNNER_TEMP/aba-llm" --seed 1 --mock --mock-llm \ | |
| --llm-lessons --assert-shape | |
| # The committed runs are evidence, not decoration: every number in | |
| # RESULTS.md is recomputed from the transcripts that shipped with it, | |
| # and MANIFEST.md's checksums are re-derived, so a published file that | |
| # is renamed or overwritten fails the build instead of review. | |
| - name: Published runs match their own transcripts | |
| run: | | |
| python3 crates/areev-bench/scripts/verify_run.py \ | |
| crates/areev-bench/results/selfimprove-3seed-qwen3-30b-2026-08-26 | |
| python3 crates/areev-bench/scripts/verify_run.py \ | |
| crates/areev-bench/results/selfimprove-llmarm-3seed-qwen3-30b-2026-08-30 | |
| python3 crates/areev-bench/scripts/verify_run.py \ | |
| crates/areev-bench/results/selfimprove-2x2-qwen3-30b-2026-08-30 | |
| # The cross-configuration comparison computes published numbers too, so | |
| # its pairing and discordant-count logic is checked on synthetic runs — | |
| # a silent break there would corrupt a claim, not fail a build. | |
| - name: The cross-configuration statistics tool agrees with itself | |
| run: python3 crates/areev-bench/scripts/aba_arm_stats.py --selftest | |
| versions: | |
| name: versions agree | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - run: python3 scripts/check_versions.py | |
| # README.md quotes repository quality numbers (test vs source lines, test | |
| # count, error codes) from committed artifacts. Regenerate and diff, so the | |
| # published figures cannot quietly drift away from the tree. | |
| stats: | |
| name: repo stats current | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Verify the committed stats match the tree | |
| run: python3 scripts/repo_stats.py --check | |
| - uses: actions/upload-artifact@v4 | |
| with: | |
| name: repo-stats | |
| path: | | |
| docs/repo-stats.html | |
| docs/repo-stats.json | |
| docs/assets/repo-stats-*.svg |