From 6aa4ec9ccf2cd6abb991fdc55f5d5eac53ca43d2 Mon Sep 17 00:00:00 2001 From: Alex Mikheev Date: Fri, 11 Sep 2026 23:48:47 +0100 Subject: [PATCH 1/3] ci: V-model pipeline for native-ci and GitHub CI, kept in sync (Refs #254) Same change as 799f6e3 on the Gitea lineage, applied on top of the GitHub main lineage so the public CI matrix can run it. See docs/ci-gap-analysis.md. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01WjGks4DGYdDw3QLgu32S96 --- .config/nextest.toml | 28 +++++++++ .gitea/workflows/native-ci.yml | 102 +++++++++++++++++++++---------- .github/workflows/ci.yml | 99 +++++++++++++++++++++++++++++- deny.toml | 101 ++++++++++++++++++++++++++++++ docs/ci-gap-analysis.md | 108 +++++++++++++++++++++++++++++++++ rust-toolchain.toml | 8 +++ 6 files changed, 412 insertions(+), 34 deletions(-) create mode 100644 .config/nextest.toml create mode 100644 deny.toml create mode 100644 docs/ci-gap-analysis.md create mode 100644 rust-toolchain.toml diff --git a/.config/nextest.toml b/.config/nextest.toml new file mode 100644 index 00000000..eb82a9d8 --- /dev/null +++ b/.config/nextest.toml @@ -0,0 +1,28 @@ +# cargo-nextest configuration (Refs #254). +# +# `default` is what developers get locally. `ci` is selected in the workflows +# with `--profile ci`: no fail-fast so one red target does not hide the rest, +# JUnit output for the runner to archive, and bounded retries only for tests +# that are known to depend on a live terraphim_server process. + +[profile.default] +retries = 0 +fail-fast = true +slow-timeout = { period = "60s", terminate-after = 3 } + +[profile.ci] +retries = 0 +fail-fast = false +slow-timeout = { period = "60s", terminate-after = 3 } + +[profile.ci.junit] +path = "junit.xml" + +# The integration binaries that shell out to a real terraphim_server +# (TERRAPHIM_SERVER_BIN) are the only ones allowed a retry and a longer +# slow-timeout, and only in CI: they wait on a live HTTP service. Everything +# else must be deterministic; a flaky test is a bug, not a retry candidate. +[[profile.ci.overrides]] +filter = 'binary(/^(server_mode_tests|cross_mode_consistency_test|integration_tests|kg_ranking_integration_test)$/)' +retries = 1 +slow-timeout = { period = "120s", terminate-after = 3 } diff --git a/.gitea/workflows/native-ci.yml b/.gitea/workflows/native-ci.yml index d975ddfd..805269c6 100644 --- a/.gitea/workflows/native-ci.yml +++ b/.gitea/workflows/native-ci.yml @@ -1,25 +1,45 @@ name: native-ci +# V-model gate order (Refs #254): fast checks -> tests -> coverage/security -> +# nightly UB gates. Kept in sync with .github/workflows/ci.yml; see +# docs/ci-gap-analysis.md "Sync rule". Every step's first token must be on the +# terraphim-gitea-runner allowlist (cargo, rustup, bash, test, ...); curl and +# python are denied, so tools are provisioned with `cargo install`. on: push: workflow_dispatch: jobs: - build: + # Stage 1: fast checks (fmt, clippy, deny). Fails in seconds, not minutes. + check: runs-on: terraphim-native steps: + - run: rustup show active-toolchain - run: cargo fmt --all -- --check - run: cargo clippy --workspace --all-targets -- -D warnings + # #2171: enrichment feature is not covered by default-features clippy. + - run: cargo clippy -p terraphim_sessions --features enrichment -- -D warnings + # Supply chain: advisories, licences, bans, sources per deny.toml. + # cargo-deny is present on runner-5 only, so install idempotently. + - run: cargo install cargo-deny --locked --version 0.20.2 + - run: cargo deny check + # Stage 2: build + full test gate against a real terraphim_server. + test: + needs: check + runs-on: terraphim-native + steps: + # #106: the terraphim_update signed-archive tests shell out to the + # `zipsign` binary on the host. It is installed at /usr/local/bin/zipsign + # on bigbox (see "Host Tooling" in gitea-infrastructure HANDOVER.md); + # runners carry their own CARGO_HOME and do not all have ~/.cargo/bin on + # PATH, so a user-local install is invisible to them. Fail fast with a + # pointer to the fix rather than 21 failing test targets. + - name: Check host tooling (zipsign) + # Note: the terraphim-gitea-runner command policy inspects the + # literal first token; `if`/`then`/`fi` shell keywords get rejected. + # Use `test` (the only conditional primitive on the allowlist) and + # `||` chaining instead. Refs #106. + run: | + test -x /usr/local/bin/zipsign && /usr/local/bin/zipsign --version || { echo "::error::zipsign not found on PATH. Install on the runner host: sudo install -m 0755 ~/.cargo/bin/zipsign /usr/local/bin/zipsign (see gitea-infrastructure HANDOVER.md, 'Host Tooling'). Refs #106"; exit 1; } - run: cargo build --workspace - # #84: broaden the workspace gate so integration tests, binary - # smoke tests, and example doctests participate in the gate. The - # narrower `--lib` gate silently skipped crates/terraphim_mcp_server - # tests/test_tools_list.rs and tests/test_all_mcp_tools.rs, which - # exercised real stdio/JSON-RPC round-trips against the - # terraphim_mcp_server binary. `--tests --bins --examples` adds - # those targets; `--lib` is kept so the existing crate-internal - # coverage is still exercised. `--no-fail-fast` makes all targets - # run even when one fails, so a single broken test does not hide - # the rest of the failures behind an early abort. - - run: cargo test --workspace --tests --bins --examples --lib --no-fail-fast # #113: build terraphim_server from terraphim-ai so the # server-binary-dependent integration tests have a real binary. # terraphim_server is not a workspace member here -- it lives in @@ -41,22 +61,42 @@ jobs: # requirements (1.20.2) match both 1.20.2 and 1.21.0 in the registry # and cargo aborts with "patch resolved to more than one candidate". - run: cargo install --locked --git https://git.terraphim.cloud/terraphim/terraphim-ai --tag v1.21.3 --root /tmp/terraphim_server_install --config 'registries.terraphim.index="sparse+https://git.terraphim.cloud/api/packages/terraphim/cargo/"' --config 'registry.global-credential-providers=["cargo:token"]' --bin terraphim_server terraphim_server - # #113: run the integration tests that require a real - # terraphim_server binary. ensure_server_binary() (in - # cross_mode_consistency_test.rs / kg_ranking_integration_test.rs) - # and server_binary_path() (in integration_tests.rs) both resolve - # TERRAPHIM_SERVER_BIN first, so pointing the env var at the - # install root is enough. - - run: TERRAPHIM_SERVER_BIN=/tmp/terraphim_server_install/bin/terraphim_server cargo test -p terraphim_agent --test cross_mode_consistency_test -- --nocapture - - run: TERRAPHIM_SERVER_BIN=/tmp/terraphim_server_install/bin/terraphim_server cargo test -p terraphim_agent --test integration_tests -- --nocapture - - run: TERRAPHIM_SERVER_BIN=/tmp/terraphim_server_install/bin/terraphim_server cargo test -p terraphim_agent --test kg_ranking_integration_test -- --nocapture - # #2171: enrichment feature clippy + test invocations. - - run: cargo clippy -p terraphim_sessions --features enrichment -- -D warnings - - run: cargo test -p terraphim_sessions --features enrichment --lib --no-fail-fast - # #95: isolated packaged install-graph regression. - - run: cargo test -p terraphim_agent --test packaged_install_graph_regression -- --nocapture - # #118: repo guards -- duplicate-crate detection and the publish gate's own - # tests. Rust tests, not shell steps: the runner allowlist rejects any - # program that is not cargo ("policy rejected command: ... not on the - # allowlist"), which is what took CI down from #112 until now. - - run: cargo test -p terraphim_agent --test ci_guards -- --nocapture + # nextest: one binary per target, parallel scheduling, JUnit output + # (.config/nextest.toml profile ci). --all-targets keeps the + # terraphim-agents#91 coverage: binaries, examples and tests/*.rs. + - run: cargo install cargo-nextest --locked + - run: TERRAPHIM_SERVER_BIN=/tmp/terraphim_server_install/bin/terraphim_server cargo nextest run --workspace --all-targets --profile ci + # nextest does not run doctests. + - run: cargo test --workspace --doc --no-fail-fast + # #113: focused re-runs of the server-backed integration tests (also in + # the run above; kept for fast failure attribution). + - run: TERRAPHIM_SERVER_BIN=/tmp/terraphim_server_install/bin/terraphim_server cargo nextest run -p terraphim_agent --test cross_mode_consistency_test --test integration_tests --test kg_ranking_integration_test --profile ci + # #2171 / terraphim-clients#150: feature lanes invisible to default features. + - run: cargo nextest run -p terraphim_sessions --features enrichment --lib --profile ci + - run: cargo nextest run -p terraphim_sessions --all-features --profile ci + # #95 / #118: focused regression and repo guards (Rust tests, not shell + # steps: the runner allowlist rejects non-cargo programs). + - run: cargo nextest run -p terraphim_agent --test packaged_install_graph_regression --test ci_guards --profile ci + # Stage 3: coverage gate. Threshold only ever goes up (docs/ci-gap-analysis.md). + coverage: + needs: check + runs-on: terraphim-native + steps: + - run: rustup component add llvm-tools + - run: cargo install cargo-llvm-cov --locked + - run: cargo llvm-cov --workspace --lib --lcov --output-path lcov.info + - run: cargo llvm-cov report --fail-under-lines 65 + # Stage 4: UB gates on nightly (from the #252 UB_RUNBOOK). Miri covers the + # pure-computation crates only; tokio/reqwest paths are unsupported by Miri. + # terraphim_hooks' discovery tests spawn a process (posix_spawn), which Miri + # cannot emulate, and validation::tests::test_validate_latency runs 1000 + # timed iterations that are meaningless (and very slow) under Miri, hence + # --skip discovery --skip latency. + ub-gates: + needs: check + runs-on: terraphim-native + steps: + - run: rustup toolchain install nightly --profile minimal -c miri -c rust-src + - run: cargo +nightly miri setup + - run: MIRIFLAGS="-Zmiri-disable-isolation" cargo +nightly miri test -p terraphim_negative_contribution -p terraphim_command_runtime -p terraphim_hooks --lib -- --skip discovery --skip latency + - run: MIRIFLAGS="-Zmiri-disable-isolation -Zmiri-tree-borrows" cargo +nightly miri test -p terraphim_negative_contribution -p terraphim_command_runtime -p terraphim_hooks --lib -- --skip discovery --skip latency diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3e2e6263..6fed846c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,4 +1,10 @@ name: CI +# Public mirror of .gitea/workflows/native-ci.yml (Refs #254). Same gate +# order and the same cargo invocations; differences are limited to +# provisioning (GitHub actions instead of cargo install on a self-hosted +# runner), the multi-platform matrix, and the absence of the terraphim_server +# backed integration lane, which needs the private registry. See +# docs/ci-gap-analysis.md "Sync rule". on: push: branches: [main] @@ -9,9 +15,13 @@ on: env: CARGO_TERM_COLOR: always RUST_BACKTRACE: 1 + # The 1.21.x terraphim family resolves from the private Gitea registry + # (see [patch.crates-io] in Cargo.toml). Same secret release-binaries uses. + CARGO_REGISTRIES_TERRAPHIM_TOKEN: ${{ secrets.CARGO_REGISTRIES_TERRAPHIM_TOKEN }} jobs: - build: + # Stage 1: fast checks + check: runs-on: ubuntu-latest # The workspace pins twelve private-registry deps (Cargo.toml lines # 82-85 and others). Without this token the hosted runner cannot @@ -24,8 +34,9 @@ jobs: CARGO_REGISTRIES_TERRAPHIM_TOKEN: ${{ secrets.CARGO_REGISTRIES_TERRAPHIM_TOKEN }} steps: - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@stable + - uses: dtolnay/rust-toolchain@master with: + toolchain: 1.97.1 components: rustfmt, clippy - uses: Swatinem/rust-cache@v2 - name: Install release signature verifier @@ -41,8 +52,36 @@ jobs: python3 -m unittest tests.test_manifest_builder_compatibility -v - run: cargo fmt --all -- --check - run: cargo clippy --workspace --all-targets -- -D warnings + - run: cargo clippy -p terraphim_sessions --features enrichment -- -D warnings + - uses: taiki-e/install-action@cargo-deny + - run: cargo deny check + + # Stage 2: tests on every release platform + test: + needs: check + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: 1.97.1 + - uses: Swatinem/rust-cache@v2 + - uses: taiki-e/install-action@nextest - run: cargo build --workspace - - run: cargo test --workspace --lib --no-fail-fast + # Lib and doc tests on all platforms. Integration tests that need a + # terraphim_server binary run only on native-ci. + - run: cargo nextest run --workspace --lib --profile ci + - run: cargo test --workspace --doc --no-fail-fast + # #2171: enrichment-feature test invocation. + - run: cargo nextest run -p terraphim_sessions --features enrichment --lib --profile ci + # #4325: zero-chunk smoke for terraphim_grep default features. + - run: cargo nextest run -p terraphim_grep --test default_feature_smoke --profile ci + # #95: isolated packaged install-graph regression. + - run: cargo nextest run -p terraphim_agent --test packaged_install_graph_regression --profile ci # GitHub-side home for the updater integration contracts (managed-mode # receipt/refusal, strict v2 manifest readers, R2 checksum fail-closed). # On Gitea these lanes live in .gitea/workflows/native-ci.yml, which is @@ -138,3 +177,57 @@ jobs: bash "$t" echo "::endgroup::" done + + # Stage 3: coverage gate (Linux only; threshold only ever increases) + coverage: + needs: check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: 1.97.1 + components: llvm-tools + - uses: Swatinem/rust-cache@v2 + - uses: taiki-e/install-action@cargo-llvm-cov + - run: cargo llvm-cov --workspace --lib --lcov --output-path lcov.info + - run: cargo llvm-cov report --fail-under-lines 65 + - uses: actions/upload-artifact@v4 + with: + name: lcov + path: lcov.info + + # Stage 4: UB gates on nightly (from the #252 UB_RUNBOOK) + ub-gates: + needs: check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@nightly + with: + components: miri, rust-src + - uses: Swatinem/rust-cache@v2 + - run: cargo +nightly miri setup + - run: MIRIFLAGS="-Zmiri-disable-isolation" cargo +nightly miri test -p terraphim_negative_contribution -p terraphim_command_runtime -p terraphim_hooks --lib -- --skip discovery --skip latency + - run: MIRIFLAGS="-Zmiri-disable-isolation -Zmiri-tree-borrows" cargo +nightly miri test -p terraphim_negative_contribution -p terraphim_command_runtime -p terraphim_hooks --lib -- --skip discovery --skip latency + + # Stage 5: benchmark regression, informational on PRs (baselines from #253) + benchmarks: + if: github.event_name == 'pull_request' + needs: test + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: 1.97.1 + - uses: Swatinem/rust-cache@v2 + - run: cargo bench -p terraphim_grep --features code-search --bench hybrid_search -- --output-format bencher | tee bench-output.txt + - uses: benchmark-action/github-action-benchmark@v1 + with: + tool: cargo + output-file-path: bench-output.txt + alert-threshold: "120%" + comment-on-alert: true + fail-on-alert: false + auto-push: false diff --git a/deny.toml b/deny.toml new file mode 100644 index 00000000..14e65619 --- /dev/null +++ b/deny.toml @@ -0,0 +1,101 @@ +# cargo-deny configuration (Refs #254). +# +# Policy: advisories and yanked crates block the pipeline; unmaintained and +# notice-level advisories warn. Every `ignore` entry below carries the reason +# and the issue that owns its removal. Ignores are temporary by construction: +# remove the entry as soon as the named fix lands. + +[graph] +all-features = true +targets = [ + "x86_64-unknown-linux-gnu", + "aarch64-unknown-linux-gnu", + "aarch64-apple-darwin", + "x86_64-apple-darwin", + "x86_64-pc-windows-msvc", +] + +[advisories] +version = 2 +db-urls = ["https://github.com/rustsec/advisory-db"] +# TEMPORARY: warn until the Cargo.lock bump lands (spin 0.9.8 is yanked and is +# fixed by `cargo update -p spin`). Flip back to "deny" in that commit. +yanked = "warn" +ignore = [ + # --- Lockfile bumps pending (semver-compatible, `cargo update -p `). + # Held back until the UB audit #252 finishes its Phase 3 dynamic runs, so + # Miri/TSan results are attributed to one dependency set. Remove all six + # entries in the same commit as the Cargo.lock bump. + { id = "RUSTSEC-2026-0204", reason = "crossbeam-epoch 0.9.18 -> 0.9.21 via cargo update; blocked on #252 Phase 3" }, + { id = "RUSTSEC-2026-0258", reason = "h2 0.4.15 -> 0.4.19 via cargo update; blocked on #252 Phase 3" }, + { id = "RUSTSEC-2026-0190", reason = "anyhow 1.0.102 -> 1.0.104 via cargo update; blocked on #252 Phase 3" }, + # The same Cargo.lock bump also clears the yanked spin 0.9.8 (cargo update -p spin). + # Unsound-class advisories cargo-audit reports for event-listener, lru, memmap2 and + # git2 (RUSTSEC-2026-0221/0253/0186/0183/0184) are informational in cargo-deny and + # are not encountered by this config; the first three are fixed by the same bump, + # git2 needs fff-search to move to a patched release (child issue of #254). + # --- quick-xml: two lockfile copies, both below the fixed 0.41. + { id = "RUSTSEC-2026-0195", reason = "quick-xml 0.37 (self_update via terraphim_update 1.20.2 registry) and 0.38 (opendal 0.54); fix is upstream in self_update and opendal. Child issue of #254" }, + { id = "RUSTSEC-2026-0194", reason = "same crate and chain as RUSTSEC-2026-0195" }, + # --- Requires an upstream or major-version change. + { id = "RUSTSEC-2026-0189", reason = "rmcp 0.9.1 -> >=1.4.0 is a major upgrade of terraphim_mcp_server's transport; DNS-rebinding affects the streamable HTTP server only. Tracked as a child of #254" }, + # --- Unmaintained (warn-level; listed so the reason is recorded). + { id = "RUSTSEC-2025-0141", reason = "bincode 1.x via transitive deps; no maintained drop-in" }, + { id = "RUSTSEC-2025-0119", reason = "number_prefix via indicatif" }, + { id = "RUSTSEC-2024-0436", reason = "paste proc-macro; compile-time only" }, + { id = "RUSTSEC-2024-0370", reason = "proc-macro-error; compile-time only" }, +] + +[licenses] +version = 2 +confidence-threshold = 0.9 +allow = [ + "MIT", + "MIT-0", + "Apache-2.0", + "Apache-2.0 WITH LLVM-exception", + "BSD-2-Clause", + "BSD-3-Clause", + "ISC", + "Zlib", + "0BSD", + "Unlicense", + "CC0-1.0", + "BSL-1.0", + "Unicode-3.0", + "CDLA-Permissive-2.0", + "MPL-2.0", + "bzip2-1.0.6", +] +# LGPL appears only as one option of a tri-licensed crate (r-efi: MIT OR +# Apache-2.0 OR LGPL-2.1-or-later); cargo-deny picks an allowed option, so +# no exception is needed. + +[[licenses.exceptions]] +# html2md is GPL-3.0+ and reaches terraphim_agent and terraphim-cli +# (both Apache-2.0) through terraphim_middleware (terraphim-ai registry). +# This is an upstream licence-compatibility problem, not a local one: the +# fix is for terraphim_middleware to replace html2md or gate it behind a +# non-default feature. Recorded here so the gate stays green while the +# upstream issue is open; remove when terraphim_middleware drops html2md. +allow = ["GPL-3.0-or-later", "GPL-3.0"] +name = "html2md" + +[bans] +multiple-versions = "warn" +wildcards = "deny" +highlight = "all" +deny = [ + { name = "openssl-sys", reason = "workspace policy is rustls everywhere; native TLS breaks the static release-binaries matrix" }, +] + +[sources] +unknown-registry = "deny" +unknown-git = "deny" +allow-registry = [ + "https://github.com/rust-lang/crates.io-index", + "sparse+https://git.terraphim.cloud/api/packages/terraphim/cargo/", +] +allow-git = [ + "https://github.com/rustls/webpki", +] diff --git a/docs/ci-gap-analysis.md b/docs/ci-gap-analysis.md new file mode 100644 index 00000000..823a2ce4 --- /dev/null +++ b/docs/ci-gap-analysis.md @@ -0,0 +1,108 @@ +# CI/CD gap analysis and staged pipeline + +Refs terraphim/terraphim-clients#254. Written 2026-09-11 against commit 1120180. + +## Pipelines in play + +| Pipeline | File | Runner | Role | +|---|---|---|---| +| native-ci | `.gitea/workflows/native-ci.yml` | `terraphim-native` (bigbox, 24 cores) | Source of truth. Full workspace gate including the terraphim_server-backed integration lane. | +| CI | `.github/workflows/ci.yml` | GitHub hosted | Public mirror for the crates.io / GitHub build of terraphim-agent. Multi-platform matrix. | +| release-binaries, publish-crates, publish-registry, r2-manifest-health | `.github/workflows/*`, `.gitea/workflows/publish-registry.yml` | mixed | Release and distribution. Out of scope here except where gates feed them. | + +## Gates before and after + +| V-model stage | Gate | Before | After (this change) | +|---|---|---|---| +| Implementation | fmt, clippy `-D warnings` | present, in one monolithic job | own `check` job, runs first, fails in seconds | +| Implementation | pinned toolchain | none | `rust-toolchain.toml` = 1.97.1; GitHub pins the same version | +| Verification | unit and integration tests | `cargo test --all-targets` | `cargo nextest run --all-targets --profile ci` with JUnit output; doctests via `cargo test --doc` | +| Verification | coverage threshold | none | `cargo llvm-cov --fail-under-lines N` (N set from the measured baseline, see below) | +| Verification | supply chain | none in CI; `cargo deny` failed locally | `deny.toml` with every advisory triaged; `cargo deny check` in `check` | +| Verification | Miri on unsafe or pure crates | none | `ub-gates` job on nightly, default and tree-borrows axes, crates chosen by the #252 runbook | +| Verification | benchmark regression | none | GitHub `benchmarks` job on PRs, informational, using the #253 baselines | +| Validation | multi-platform | Linux only | GitHub `test` matrix: ubuntu, macos, windows | +| Validation | release artefacts | mature | unchanged | + +## Runner constraints that shaped the design + +The terraphim-gitea-runner enforces a command policy on the literal first token of each step. Allowed: `cargo`, `rustup`, `bash`, `sh`, `test`, `git`, `make`, `rch` and a few coreutils. Denied: `curl`, `wget`, `docker`, `python`. Consequences: + +- Tools are provisioned with `cargo install --locked` inside the job. Runners have separate `CARGO_HOME`s and uneven tool sets (nextest and miri on some, cargo-deny on runner-5 only, cargo-llvm-cov nowhere), so provisioning is idempotent and repeated per job. +- `cargo build`, `check`, `clippy` and `doc` are routed to the rch compile farm by policy; `cargo nextest`, `cargo llvm-cov`, `cargo deny` and `cargo +nightly miri` run on the host. +- Repository scripts must be invoked as `bash ./scripts/x.sh`; `./scripts/x.sh` is rejected. +- No `uses:` actions on the native runner; checkout is implicit. +- Gitea dispatches one job per task as a "SingleWorkflow" payload, so `needs:` ordering and job fan-out are handled by Gitea itself; the runner only ever sees a single job. The four-job split therefore works on native-ci and the `check` job really does fail fast. +- The runner reports a commit status per job as `native-ci / (push)`. Branch protection on `main` currently lists `native-ci / build (push)` (status checks disabled at the time of writing). Because this change renames `build` to `check` and `test`, the protection rule must be updated when status checks are re-enabled. + +## Advisory triage (deny.toml) + +| Advisory | Crate and chain | Action | +|---|---|---| +| RUSTSEC-2026-0204 crossbeam-epoch | fff-search, ignore | `cargo update -p crossbeam-epoch` (0.9.21) | +| RUSTSEC-2026-0258 h2 | hyper, axum, rmcp, reqwest | `cargo update -p h2` (0.4.19) | +| RUSTSEC-2026-0190 anyhow (unsound downcast_mut) | opendal, terraphim_config | `cargo update -p anyhow` (1.0.104) | +| RUSTSEC-2026-0221 event-listener (!Send crossing) | sqlx via opendal | `cargo update -p event-listener` (5.4.2) | +| RUSTSEC-2026-0253 lru (UAF on panic in pop) | transitive | `cargo update -p lru` (0.18.4) | +| RUSTSEC-2026-0186 memmap2 | transitive | `cargo update -p memmap2` (0.9.11) | +| yanked spin 0.9.8 | transitive | `cargo update -p spin` | +| RUSTSEC-2026-0189 rmcp DNS rebinding | terraphim_mcp_server direct dep, 0.9.1 | major upgrade to rmcp 1.4; child issue | +| RUSTSEC-2026-0183 / 0184 git2 UB | fff-search | needs fff-search to adopt a patched git2; child issue | +| RUSTSEC-2026-0194 / 0195 quick-xml | self_update (via terraphim_update) and opendal 0.54 | upstream; child issue | +| RUSTSEC-2023-0071 rsa Marvin, quinn-proto 0.11.14 | in Cargo.lock but not in the resolved all-features graph | not reachable; cargo-audit reports them from the lockfile only | +| five unmaintained notices | bincode, instant, number_prefix, paste, proc-macro-error | recorded, warn level | + +The seven lockfile bumps are one commit. They are held until the UB audit (#252) finishes its Phase 3 dynamic runs so Miri and TSan results are attributed to a single dependency set. Until then `deny.toml` carries dated ignore entries and `yanked = "warn"`; that commit removes the entries and restores `yanked = "deny"`. + +### Licence finding + +`html2md` (GPL-3.0+) reaches `terraphim_agent` and `terraphim-cli` (Apache-2.0) through `terraphim_middleware` from the terraphim registry. This is an upstream licence-compatibility problem in terraphim-ai; `deny.toml` carries a named exception so the gate is green, and the exception must go when terraphim_middleware drops or feature-gates html2md. + +## Why GitHub CI has been red since 2026-09-01 + +Every run fails within twenty seconds at `cargo fmt --check`. Locally the tree is rustfmt-clean on 1.97.1. The GitHub workflow used `dtolnay/rust-toolchain@stable`, which resolves to the newest stable and its newer rustfmt, whose output differs from 1.97.1 on this tree. Pinning the toolchain in the workflow (and `rust-toolchain.toml`) is the fix; no source formatting change is needed. + +## A test that only fails under workspace feature unification + +`terraphim-session-analyzer::connectors::codex::tests::test_parse_response_item` passes when run with `-p terraphim-session-analyzer` and fails under `cargo test --workspace --lib` (the GitHub CI command) because feature unification across the workspace changes the `ResponseItem` payload parsing. This is a pre-existing red, independent of this change; it is tracked in its own issue and the coverage baseline was measured with `--ignore-run-fail`. + +## Coverage threshold + +The threshold in both workflows is set from the first measured baseline, rounded down to the nearest 5. It only ever increases. + +Baseline measured 2026-09-11 on commit 1120180, macOS aarch64, `cargo llvm-cov --workspace --lib --ignore-run-fail`: + +| Metric | Covered | Total | Percent | +|---|---|---|---| +| Lines | 23753 | 35183 | 67.51 | +| Functions | 2425 | 3615 | 67.08 | +| Regions | 37948 | 54538 | 69.58 | + +Gate: `--fail-under-lines 65`. Per-crate targets from the skill's table (80 percent for library crates, 70 for binaries, 90 for any module that carries `unsafe`) are the direction of travel, not the gate today. + +## Miri crate selection + +Miri cannot execute tokio, mio, reqwest or process spawning. The `ub-gates` job therefore runs the pure-computation crates only: terraphim_negative_contribution, terraphim_command_runtime and terraphim_hooks. The hooks `discovery` tests spawn a subprocess (`posix_spawnattr_init` is unsupported by Miri) and are skipped with `-- --skip discovery`, and the 1000-iteration latency test in the same crate is skipped with `--skip latency` because wall-clock assertions are meaningless under Miri. The #252 runbook widens this list as Phase 3 establishes which other test modules are Miri-clean (session-analyzer parsing and sessions redaction are the next candidates). + +## Sync rule + +`native-ci.yml` is the source of truth. Any change to a `cargo` invocation in a gate is made there first and mirrored into `ci.yml` in the same commit. Allowed divergence between the two: + +1. Provisioning: `cargo install` on the native runner versus `taiki-e/install-action` on GitHub. +2. The terraphim_server-backed integration lane exists only on native-ci (private registry). +3. The multi-platform matrix and the benchmark job exist only on GitHub. + +A PR that changes one file and not the other is rejected in review unless the diff falls into one of those three categories. + +## Staged rollout + +1. Land `rust-toolchain.toml`, `deny.toml`, `.config/nextest.toml` and the restructured workflows (this branch). Coverage threshold at the measured floor. +2. After #252 Phase 3: the lockfile bump commit; remove the six ignores; `yanked = "deny"`. +3. After #252 Phase 12: extend the Miri crate list from the runbook; add the TSan lane if the runner can build-std. +4. After #251 lands feature flags: add `--features otel,prometheus` to the clippy and test lanes. +5. After #253 commits baselines: point the benchmark job at `docs/perf/` baselines and raise the alert threshold from informational to blocking on main. + +## Open items + +- `cargo-llvm-cov` on the native runner needs `rustup component add llvm-tools` for the pinned 1.97.1 toolchain; the job does this each run. +- The Windows leg of the GitHub matrix has never run this workspace's tests; expect path or `zipsign` related failures on first run and gate them with `if: runner.os != 'Windows'` only with a recorded reason. diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 00000000..7d406388 --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,8 @@ +# Pinned toolchain for reproducible CI and local builds (Refs #254). +# Matches the active stable on the terraphim-native runner host and on +# developer machines as of 2026-09-11. Bump deliberately, in its own commit, +# and re-run the full native-ci gate before merging. +[toolchain] +channel = "1.97.1" +components = ["rustfmt", "clippy"] +profile = "minimal" From 17e45c3abb2da9d4ae04361e2a30dac93bb85af0 Mon Sep 17 00:00:00 2001 From: Alex Mikheev Date: Fri, 11 Sep 2026 23:55:26 +0100 Subject: [PATCH 2/3] ci: coverage job tolerates test failures; record first-run findings (Refs #254) --ignore-run-fail on cargo llvm-cov so the coverage gate reports the number even when the test gate is red (#264). Document the GitHub and Gitea main lineage divergence, the private-registry block on the GitHub mirror, and the Gitea-side job skips seen on the first runs. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01WjGks4DGYdDw3QLgu32S96 --- .gitea/workflows/native-ci.yml | 4 +++- .github/workflows/ci.yml | 5 +++-- docs/ci-gap-analysis.md | 14 ++++++++++++++ 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/.gitea/workflows/native-ci.yml b/.gitea/workflows/native-ci.yml index 805269c6..f4d90f0e 100644 --- a/.gitea/workflows/native-ci.yml +++ b/.gitea/workflows/native-ci.yml @@ -78,13 +78,15 @@ jobs: # steps: the runner allowlist rejects non-cargo programs). - run: cargo nextest run -p terraphim_agent --test packaged_install_graph_regression --test ci_guards --profile ci # Stage 3: coverage gate. Threshold only ever goes up (docs/ci-gap-analysis.md). + # --ignore-run-fail: the test job owns test failures; this job owns the + # percentage, so a red test must not hide the coverage number. coverage: needs: check runs-on: terraphim-native steps: - run: rustup component add llvm-tools - run: cargo install cargo-llvm-cov --locked - - run: cargo llvm-cov --workspace --lib --lcov --output-path lcov.info + - run: cargo llvm-cov --workspace --lib --lcov --output-path lcov.info --ignore-run-fail - run: cargo llvm-cov report --fail-under-lines 65 # Stage 4: UB gates on nightly (from the #252 UB_RUNBOOK). Miri covers the # pure-computation crates only; tokio/reqwest paths are unsupported by Miri. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6fed846c..85a0e03a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -178,7 +178,8 @@ jobs: echo "::endgroup::" done - # Stage 3: coverage gate (Linux only; threshold only ever increases) + # Stage 3: coverage gate (Linux only; threshold only ever increases). + # --ignore-run-fail: the test job owns test failures; this job owns the number. coverage: needs: check runs-on: ubuntu-latest @@ -190,7 +191,7 @@ jobs: components: llvm-tools - uses: Swatinem/rust-cache@v2 - uses: taiki-e/install-action@cargo-llvm-cov - - run: cargo llvm-cov --workspace --lib --lcov --output-path lcov.info + - run: cargo llvm-cov --workspace --lib --lcov --output-path lcov.info --ignore-run-fail - run: cargo llvm-cov report --fail-under-lines 65 - uses: actions/upload-artifact@v4 with: diff --git a/docs/ci-gap-analysis.md b/docs/ci-gap-analysis.md index 823a2ce4..5aef1797 100644 --- a/docs/ci-gap-analysis.md +++ b/docs/ci-gap-analysis.md @@ -84,6 +84,20 @@ Gate: `--fail-under-lines 65`. Per-crate targets from the skill's table (80 perc Miri cannot execute tokio, mio, reqwest or process spawning. The `ub-gates` job therefore runs the pure-computation crates only: terraphim_negative_contribution, terraphim_command_runtime and terraphim_hooks. The hooks `discovery` tests spawn a subprocess (`posix_spawnattr_init` is unsupported by Miri) and are skipped with `-- --skip discovery`, and the 1000-iteration latency test in the same crate is skipped with `--skip latency` because wall-clock assertions are meaningless under Miri. The #252 runbook widens this list as Phase 3 establishes which other test modules are Miri-clean (session-analyzer parsing and sessions redaction are the next candidates). +## Two lineages, not one repository + +GitHub `main` is not a mirror of Gitea `main`. At the time of writing GitHub is 17 commits ahead of and 253 behind the Gitea lineage, and the two do not merge cleanly. Consequences: + +1. A branch cut from Gitea `main` cannot be a GitHub pull request (it conflicts, and GitHub Actions skips conflicting PRs). The CI change therefore exists twice: `task/254-ci-pipeline` on the Gitea lineage and `task/254-ci-pipeline-gh` on the GitHub lineage, with identical CI files. +2. The GitHub lineage carried unformatted code in terraphim-session-analyzer (43 rustfmt diffs under 1.97.1); a whitespace-only commit fixes it there. +3. The GitHub mirror cannot resolve the private registry: the first real run failed with `failed to load source for dependency terraphim_service` because `https://git.terraphim.cloud/api/packages/terraphim/cargo/config.json` answered "Not available" to the GitHub runner. Until the 1.21.x family is on crates.io (#210) or the registry token secret is confirmed to work in pull-request runs, the GitHub `test`, `coverage` and `ub-gates` jobs cannot go green. This is a pre-existing condition that the old single-job workflow never reached because it failed at rustfmt first. + +## First-run observations on native-ci + +- `check` (fmt, clippy, cargo-deny including its install) took about 2.5 minutes; cargo-deny's `cargo install` alone was 84 seconds. A host-level install would remove that. +- `coverage` failed on the first run because `cargo llvm-cov` aborts when a test fails (#264); `--ignore-run-fail` now separates the two concerns. +- Gitea marked the `test` job of run 449 and the `check` job of run 451 as skipped at creation, with no runner having fetched the task (verified in the runner journal). No per-job rerun API exists on Gitea 1.26 (`jobs/{id}/rerun` and `runs/{id}/rerun` both 404), so a skipped job costs a full `workflow_dispatch`. Cause unknown; tracked with #244 and terraphim-ai#3375. + ## Sync rule `native-ci.yml` is the source of truth. Any change to a `cargo` invocation in a gate is made there first and mirrored into `ci.yml` in the same commit. Allowed divergence between the two: From 93eaeb8029525374fbc1ff09849a0d5b0d31354d Mon Sep 17 00:00:00 2001 From: Alex Date: Sun, 4 Oct 2026 10:51:19 +0100 Subject: [PATCH 3/3] fix(ci): keep the canonical build job name for the release CI contract --- .github/workflows/ci.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 85a0e03a..26dac6f3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,7 +21,7 @@ env: jobs: # Stage 1: fast checks - check: + build: runs-on: ubuntu-latest # The workspace pins twelve private-registry deps (Cargo.toml lines # 82-85 and others). Without this token the hosted runner cannot @@ -58,7 +58,7 @@ jobs: # Stage 2: tests on every release platform test: - needs: check + needs: build strategy: fail-fast: false matrix: @@ -181,7 +181,7 @@ jobs: # Stage 3: coverage gate (Linux only; threshold only ever increases). # --ignore-run-fail: the test job owns test failures; this job owns the number. coverage: - needs: check + needs: build runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -200,7 +200,7 @@ jobs: # Stage 4: UB gates on nightly (from the #252 UB_RUNBOOK) ub-gates: - needs: check + needs: build runs-on: ubuntu-latest steps: - uses: actions/checkout@v4