Files
ironclaw/.github/workflows/code_style.yml
firat.sertgoz 9dd228a62a fix(ci): clear inherited main check failures (#7425)
* fix(ci): scope POSIX trace test import to Unix

* fix(ci): run Windows WebUI setup with Bash

* test(ci): pin Bash for Windows WebUI setup

* fix(ci): clear remaining main check failures

---------

Co-authored-by: italic-jinxin <106428113+italic-jinxin@users.noreply.github.com>
2026-08-10 08:17:31 +00:00

652 lines
33 KiB
YAML

name: Code Style
on:
pull_request:
merge_group:
branches:
- main
types:
- checks_requested
# Pushes to main refresh the rust-cache entries that PR and merge-group
# jobs restore from. Non-push runs stay restore-only.
push:
branches:
- main
permissions:
contents: read
concurrency:
group: code-style-${{ github.head_ref || github.ref }}
cancel-in-progress: true
env:
# Tests must never touch the real OS keychain (macOS Keychain auth dialog /
# Linux Secret Service). Guarded by
# crates/substrates/ironclaw_secrets/src/keychain.rs::os_keychain_suppressed:
# cfg!(test) covers unit tests; this covers integration/e2e that link the
# non-cfg(test) library.
IRONCLAW_DISABLE_OS_KEYCHAIN: "1"
# Pin wall-clock rendering inputs so a test cannot pass or fail on the
# runner's locale. Reborn renders model-visible time slices, and date/number
# formatting differs by locale, so an unpinned runner turns a formatting
# regression into an unreproducible flake. UTC also keeps recorded fixtures
# comparable with what a developer sees locally.
TZ: "UTC"
LANG: "C.UTF-8"
jobs:
changes:
name: Detect code changes
runs-on: ubuntu-latest
outputs:
has_code: ${{ steps.non_pr.outputs.has_code || steps.diff.outputs.has_code }}
has_guidance: ${{ steps.non_pr.outputs.has_guidance || steps.diff.outputs.has_guidance }}
has_docs: ${{ steps.non_pr.outputs.has_docs || steps.diff.outputs.has_docs }}
has_reborn_cli: ${{ steps.non_pr.outputs.has_reborn_cli || steps.diff.outputs.has_reborn_cli }}
has_clippy: ${{ steps.non_pr.outputs.has_clippy || steps.diff.outputs.has_clippy }}
clippy_packages: ${{ steps.non_pr.outputs.clippy_packages || steps.diff.outputs.clippy_packages }}
clippy_matrix: ${{ steps.non_pr.outputs.clippy_matrix || steps.diff.outputs.clippy_matrix }}
steps:
- id: non_pr
if: github.event_name == 'push'
run: |
echo "has_code=true" >> "$GITHUB_OUTPUT"
echo "has_guidance=true" >> "$GITHUB_OUTPUT"
echo "has_docs=true" >> "$GITHUB_OUTPUT"
echo "has_reborn_cli=true" >> "$GITHUB_OUTPUT"
echo "has_clippy=true" >> "$GITHUB_OUTPUT"
echo 'clippy_packages=[]' >> "$GITHUB_OUTPUT"
echo 'clippy_matrix=[{"name":"all-features","flags":"--all-features"},{"name":"default","flags":""}]' >> "$GITHUB_OUTPUT"
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v6
if: github.event_name == 'pull_request' || github.event_name == 'merge_group'
with:
fetch-depth: 0
persist-credentials: false
- id: diff
if: github.event_name == 'pull_request' || github.event_name == 'merge_group'
env:
BASE_SHA: ${{ github.event_name == 'pull_request' && github.event.pull_request.base.sha || github.event.merge_group.base_sha }}
HEAD_SHA: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
run: |
CHANGED_FILES="$(git diff --name-only "$BASE_SHA"..."$HEAD_SHA")"
printf '%s\n' "$CHANGED_FILES" > "${RUNNER_TEMP}/changed-files.txt"
clippy_packages="$(
python3 scripts/ci/changed_workspace_packages.py \
--changed-files "${RUNNER_TEMP}/changed-files.txt"
)"
echo "clippy_packages=${clippy_packages}" >> "$GITHUB_OUTPUT"
if jq -e 'length > 0' <<< "${clippy_packages}" > /dev/null; then
echo "has_clippy=true" >> "$GITHUB_OUTPUT"
else
echo "has_clippy=false" >> "$GITHUB_OUTPUT"
fi
if printf '%s\n' "$CHANGED_FILES" | grep -Eq '^(crates/|tests/|migrations/|Cargo\.toml$|Cargo\.lock$|Dockerfile|\.gitignore$|scripts/ci/|\.githooks/|docker/reborn/entrypoint\.sh$|scripts/reborn_webui_v2_live_qa/|scripts/live-canary/|\.github/workflows/(live-canary|reborn-e2e)\.yml$|scripts/check_no_panics\.py$|scripts/no_panics_reborn_baseline\.txt$|\.github/scripts/(pr-labeler|test-pr-labeler)\.sh$|\.github/workflows/(code_style|main-ci-slack-alerts)\.yml$)'; then
echo "has_code=true" >> "$GITHUB_OUTPUT"
else
echo "has_code=false" >> "$GITHUB_OUTPUT"
echo "No code changes — style checks will be skipped"
fi
# The guidance gate (scripts/ci/check-guidance.py, run inside
# fast-checks) scans surfaces the code filter above deliberately
# leaves out: `.claude/` rules and skills (whose frontmatter
# `paths:` triggers it verifies), the root AGENTS.md/CLAUDE.md pair
# (reference seed + alias site), and `docs/` (the reference-target
# space guidance most often cites — a rename there must dangle
# references loudly, not merge green). Without this output a PR
# editing only `.claude/rules/` would skip the exact
# never-firing-trigger class the gate exists to catch (#7306
# review). `has_code` keeps its meaning — ws12_workflow_contracts.py
# pins docs/ and openwiki/ OUT of it — so this OR-s into
# fast-checks' condition only; the clippy and JS lanes stay
# code-scoped. openwiki/ stays out here too: it is generated by its
# own workflow and is not a check-guidance scan surface.
if printf '%s\n' "$CHANGED_FILES" | grep -Eq '^(\.claude/|AGENTS\.md$|CLAUDE\.md$|docs/)'; then
echo "has_guidance=true" >> "$GITHUB_OUTPUT"
else
echo "has_guidance=false" >> "$GITHUB_OUTPUT"
fi
# docs/ is deliberately NOT in the has_code scope above, so a
# docs-only PR skips every Rust lane — the publication-boundary gate
# needs its own trigger. The gate's own files are in scope so the
# guardrail runs when it changes (review-discipline.md: "Guardrails
# are code").
if printf '%s\n' "$CHANGED_FILES" | grep -Eq '^(docs/|scripts/ci/docs_publication_boundary\.py$|scripts/ci/test_docs_publication_boundary\.py$|\.github/workflows/code_style\.yml$)'; then
echo "has_docs=true" >> "$GITHUB_OUTPUT"
else
echo "has_docs=false" >> "$GITHUB_OUTPUT"
fi
# Crate scopes are matched by NAME at any depth: `crates/([^/]+/)*<name>/`
# keeps matching once crates move into family directories
# (crates/<family>/ironclaw_*, PROPOSAL §5). A flat `crates/<name>/`
# prefix stops matching there, `has_reborn_cli` goes false, and the
# dist-build lane silently skips — the WS10 failure mode (#6963).
# scripts/ci/ws12_workflow_contracts.py pins this regex against the
# real crate inventory, so a renamed or moved crate fails loudly here
# instead of quietly falling out of scope.
# `ironclaw_loop_host` joined the list with the WS3 runner sheds: the
# model gateway and the tool-disclosure decorator moved there out of
# `ironclaw_turn_runner`, so without it a PR touching only the new home
# would no longer light this lane — the move would have narrowed CI
# scope as a side effect.
if printf '%s\n' "$CHANGED_FILES" | grep -Eq '^(crates/([^/]+/)*ironclaw_turn_runner/|crates/([^/]+/)*ironclaw_loop_host/|crates/([^/]+/)*ironclaw_cli/|crates/([^/]+/)*ironclaw_config/|crates/([^/]+/)*ironclaw_architecture_tests/tests/reborn_dependency_boundaries\.rs$|scripts/ci/smoke-release-binary\.py$|tests/test_smoke_release_binary\.py$|Cargo\.toml$|Cargo\.lock$|\.github/dist-build-setup\.yml$|\.github/workflows/(code_style|ironclaw-release|docker|reborn-release-compile)\.yml$)'; then
echo "has_reborn_cli=true" >> "$GITHUB_OUTPUT"
else
echo "has_reborn_cli=false" >> "$GITHUB_OUTPUT"
fi
if [ "${{ github.event_name }}" = "pull_request" ]; then
echo 'clippy_matrix=[{"name":"all-features","flags":"--all-features"}]' >> "$GITHUB_OUTPUT"
else
echo 'clippy_matrix=[{"name":"all-features","flags":"--all-features"},{"name":"default","flags":""}]' >> "$GITHUB_OUTPUT"
fi
fast-checks:
name: Fast deterministic checks
needs: changes
# `has_guidance` OR-s in the guidance surfaces (.claude/, root
# AGENTS.md/CLAUDE.md, docs/) so check-guidance.py runs for the files it
# governs; every other lane keys on `has_code` alone.
if: needs.changes.outputs.has_code == 'true' || needs.changes.outputs.has_guidance == 'true'
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v6
with:
fetch-depth: 0
persist-credentials: false
- name: Install Rust
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable
with:
components: rustfmt
- name: Install Python
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.12"
- name: Check formatting
run: cargo fmt --all -- --check
- name: Run cargo deny
uses: EmbarkStudios/cargo-deny-action@3c6349835b2b7b196a839186cb8b78e02f7b5f25 # v2
- name: Reject tracked files that match .gitignore
run: |
tracked_ignored="$(git ls-files -ci --exclude-standard)"
if [ -n "$tracked_ignored" ]; then
echo "Tracked files match .gitignore; remove them from source control or update .gitignore:"
printf '%s\n' "$tracked_ignored"
exit 1
fi
# Guards the #5603 Docker-outage class: every include_str! target must
# exist and be present in each Dockerfile build context.
- name: include_str! paths + Docker COPY coverage
run: scripts/ci/check-include-str-paths.sh
# The full checkout already contains the merge base needed by this
# diff-aware guard, so consolidating the jobs avoids another fetch.
- name: Hermetic env mutation guard
run: scripts/ci/check-hermetic-env.sh
- name: Static-check self-tests
run: |
scripts/ci/test-check-include-str-paths.sh
scripts/ci/test-check-hermetic-env.sh
scripts/ci/test-ci-comm-locale-pin.sh
scripts/ci/test-classify-test-scope.sh
scripts/ci/test-build-wasm-extensions.sh
scripts/ci/test-check-wasm-artifact-freshness.sh
scripts/ci/test-package-feature-flags.sh
scripts/ci/test-quality-gate-runner.sh
python3 scripts/ci/test_changed_workspace_packages.py
scripts/ci/test-reborn-crate-test-buckets.sh
scripts/ci/test-main-ci-slack-alerts.sh
python3 scripts/ci/test_ws12_suite_shards.py
python3 scripts/ci/test_ws12_workflow_contracts.py
python3 scripts/ci/test-check-target-tree.py
python3 scripts/ci/test-check-guidance.py
scripts/ci/test-hermetic-test-process.sh
scripts/ci/test-reborn-docker-entrypoint.sh
# #7144: this 204-test module had never been run by any lane, so five
# of its assertions had silently drifted out of sync with the code they
# gate — including the live-canary shard roster, which pinned a
# `reborn-e2e.yml` string that no longer existed.
python3 -m unittest scripts.reborn_webui_v2_live_qa.test_run_live_qa
bash .github/scripts/test-pr-labeler.sh
- name: Self-test panic checker
if: github.event_name != 'push'
run: python3 scripts/check_no_panics.py --self-test
- name: Check Reborn production panic baseline
if: github.event_name != 'push'
run: python3 scripts/check_no_panics.py --reborn-baseline
- name: Check changed production code for panic-style calls
if: github.event_name != 'push'
env:
BASE_SHA: ${{ github.event_name == 'pull_request' && github.event.pull_request.base.sha || github.event.merge_group.base_sha }}
run: python3 scripts/check_no_panics.py --base "$BASE_SHA" --head HEAD
# A committed `.wasm` whose `wasm-src/` moved on without it ships stale
# behaviour: the rebuild job overwrites the artifact in the working tree
# before testing it, so it never compares against what is committed.
- name: Check committed WASM artifacts are not stale
run: python3 scripts/ci/check-wasm-artifact-freshness.py
# Every other path gate answers "where is crate X?" by discovery, which
# is what lets them survive a family move — and is exactly why none of
# them can say the move landed where PROPOSAL §5 draws it. This one
# compares `cargo metadata` against the documented tree itself.
- name: Check the crate tree matches PROPOSAL §5
run: python3 scripts/ci/check-target-tree.py
# The guidance half of the same claim: every repo path the agent docs
# reference must be tracked, every `.claude/rules` `paths:` trigger must
# match at least one tracked file (a glob that matches nothing is a rule
# that never fires), and every crate must appear in its family's
# AGENTS.md crate table with a README.md (guidance-conventions.md).
- name: Check guidance references the tracked tree
run: python3 scripts/ci/check-guidance.py
- name: Check composition mass budget
run: bash scripts/ci/check-composition-budget.sh
- name: Self-test the composition budget gate
run: bash scripts/ci/test-check-composition-budget.sh
- name: Unit-test the dev-metrics tool
run: python3 scripts/test_dev_metrics.py
- name: Validate changed-coverage policy
run: >-
python3 scripts/ci/reborn_changed_coverage.py
--manifest tests/integration/changed-coverage-exemptions.toml
--validate-manifest-only
- name: Self-test exact release-binary smoke gate
run: python3 -m unittest tests/test_smoke_release_binary.py
webui-v2-js-lint:
name: WebUI v2 JS lint (no-undef)
needs: changes
if: needs.changes.outputs.has_code == 'true'
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v6
with:
persist-credentials: false
# `working-directory:` is a YAML key, not a shell command — it cannot glob
# or resolve a path at runtime, so the family move (crates/<family>/
# ironclaw_webui, PROPOSAL §5) is resolved once here through the shared
# crate inventory (scripts/ci/crate-dir.sh -> scripts/ci/lib/crate_tree.py)
# rather than left as a literal every step below would need updating.
# `env` populated via $GITHUB_ENV is available to a later step's
# `working-directory:` (docs.github.com/actions: context availability for
# jobs.<job_id>.steps.working-directory includes `env`), so every
# subsequent step in THIS job can read it back through ${{ env.* }}.
- name: Resolve WebUI frontend directory
run: |
set -euo pipefail
webui_dir="$(bash scripts/ci/crate-dir.sh ironclaw_webui)"
echo "WEBUI_FRONTEND_DIR=${webui_dir}/frontend" >> "$GITHUB_ENV"
- name: Enable pnpm
run: corepack enable pnpm
- name: Install Node.js
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v4
with:
node-version: "22"
cache: "pnpm"
# actions/setup-node hashes this list to build the cache key
# (verified against the pinned commit's bundled dist/setup/index.js:
# `hashFiles` walks one globber built from every newline-separated
# pattern and only throws when the COMBINED walk finds nothing), so
# the exact line stays live for today's tree (WS7 put the crate in
# `crates/product/`) and the deeper glob picks up any later re-homing
# without either one needing to be conditional. The two must not
# overlap: `ws12_workflow_contracts.py` rejects a spare that already
# matches the real location, because a pattern matching everything is
# broad rather than depth-tolerant.
cache-dependency-path: |
crates/product/ironclaw_webui/frontend/pnpm-lock.yaml
crates/*/*/ironclaw_webui/frontend/pnpm-lock.yaml
# `no-undef` over WebUI v2 JavaScript catches production modules that reference a
# symbol they never imported. The VM-based component suites stub
# every collaborator through a `vm` context, so that class of bug (e.g. an
# htm `${Component}` used without an import) passes the unit tests and only
# surfaces at runtime — this is the gate the eval harness cannot provide.
- name: Install WebUI v2 frontend deps
working-directory: ${{ env.WEBUI_FRONTEND_DIR }}
run: pnpm install --frozen-lockfile
- name: Lint WebUI v2 JS
working-directory: ${{ env.WEBUI_FRONTEND_DIR }}
run: pnpm lint
# The full vitest suite (chat, extensions, telegram/slack panels, gate
# routing, channels tab, configure modal — the vm-tsx harness) previously
# never ran in CI: only the settings-scoped subset was wired elsewhere,
# so whole component suites could rot silently (the PR #5362 gap). The
# vm-tsx setup file is declared in vite.config.ts setupFiles; no extra
# CI configuration beyond node + pnpm is needed.
- name: WebUI v2 frontend tests (vitest)
working-directory: ${{ env.WEBUI_FRONTEND_DIR }}
run: pnpm test
# The SPA build is embedded into the serve binary at compile time through
# Cargo's OUT_DIR (see ironclaw_webui/build.rs), so a frontend that lints
# and tests but does not build still breaks downstream binary builds.
# Keep the build in the same job so the three gates travel together.
- name: Build WebUI v2 frontend
working-directory: ${{ env.WEBUI_FRONTEND_DIR }}
run: pnpm build
clippy:
name: Clippy (${{ matrix.name }})
needs: changes
if: >-
needs.changes.outputs.has_code == 'true' &&
(github.event_name != 'pull_request' || needs.changes.outputs.has_clippy == 'true')
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include: ${{ fromJSON(needs.changes.outputs.clippy_matrix) }}
steps:
- name: Checkout repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v6
with:
persist-credentials: false
- name: Install Rust
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable
with:
components: clippy
- name: Enable pnpm for setup-node cache
if: contains(matrix.flags, '--all-features')
run: corepack enable pnpm
- name: Install Node.js for WebUI bundle builds
if: contains(matrix.flags, '--all-features')
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v4
with:
node-version: "22"
cache: "pnpm"
# See the webui-v2-js-lint job above for why this is a depth-tolerant
# glob list rather than a single literal.
cache-dependency-path: |
crates/product/ironclaw_webui/frontend/pnpm-lock.yaml
crates/*/*/ironclaw_webui/frontend/pnpm-lock.yaml
- name: Enable pnpm
if: contains(matrix.flags, '--all-features')
run: corepack enable pnpm
- name: Install WebUI frontend dependencies
if: contains(matrix.flags, '--all-features')
run: |
set -euo pipefail
webui_dir="$(bash scripts/ci/crate-dir.sh ironclaw_webui)"
cd "${webui_dir}/frontend"
pnpm install --frozen-lockfile
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
with:
shared-key: clippy
# Keep saves to protected-branch and merge_group runs so the ~10 GB
# repo cache LRU is seeded by shared states, not arbitrary PR branches.
save-if: ${{ matrix.name == 'all-features' && ((github.event_name == 'push' && github.ref == 'refs/heads/main') || github.event_name == 'merge_group') }}
- name: Setup OVH sccache
uses: ./.github/actions/setup-sccache-dist
with:
scheduler-url: ${{ vars.SCCACHE_DIST_SCHEDULER_URL }}
auth-token: ${{ secrets.SCCACHE_DIST_AUTH_TOKEN }}
cache-ssh-host: ${{ vars.SCCACHE_CACHE_SSH_HOST }}
cache-ssh-user: ${{ vars.SCCACHE_CACHE_SSH_USER }}
cache-ssh-port: ${{ vars.SCCACHE_CACHE_SSH_PORT }}
cache-ssh-private-key: ${{ secrets.SCCACHE_CACHE_SSH_PRIVATE_KEY }}
cache-ssh-known-hosts: ${{ secrets.SCCACHE_CACHE_SSH_KNOWN_HOSTS }}
redis-password: ${{ secrets.SCCACHE_REDIS_PASSWORD }}
- name: Check production-target lints
if: github.event_name == 'pull_request'
env:
CLIPPY_PACKAGES: ${{ needs.changes.outputs.clippy_packages }}
run: |
package_args=()
while IFS= read -r package; do
package_args+=(-p "${package}")
done < <(jq -r '.[]' <<< "${CLIPPY_PACKAGES}")
# No explicit target filter. `--lib` is a hard error on a bin-only
# package ("no library targets found in package `ironclaw`"), which
# is exactly what a PR touching only crates/app/ironclaw_cli
# selects, and `--bins` alone degrades to "no targets matched; this
# is a no-op" on a lib-only package — the lane would report green
# having linted nothing. Cargo's default target set for a package is
# already lib + bins, with tests, examples, and benches excluded, so
# omitting both keeps this the production-target lane for every
# package shape. scripts/ci/ws12_workflow_contracts.py pins this.
cargo clippy "${package_args[@]}" \
${{ matrix.flags }} -- -D warnings
- name: Check all-target lints
if: github.event_name != 'pull_request'
run: cargo clippy --all --tests --examples ${{ matrix.flags }} -- -D warnings
# #7119. The PR lane above lints only the *changed* packages, so a crate
# that is merely a dependency of that set compiles with whatever features
# the set happens to enable. `--lib --bins` builds no dev-dependencies, and
# in this workspace `test-support` is turned on exclusively through
# dev-dependency edges — so it is OFF here and ON in every `--tests` lane.
# Code behind `cfg(any(test, feature = "test-support"))` then vanishes and
# a `use` that only it names becomes an unused import, which no
# whole-workspace lane could see: `--all --tests` builds the dev-deps that
# unify the feature back on. This step is that missing shape, workspace-
# wide, so the failure lands at merge time instead of waiting for the one
# PR whose diff happens to produce the tripping package set.
#
# Deliberately the `default` flavor: `--all-features` re-enables
# `test-support` on every selected package and masks the exact class this
# step exists to catch.
- name: Check production-target lints (workspace, no dev-dependency features)
if: github.event_name != 'pull_request' && matrix.name == 'default'
run: cargo clippy --all --lib --bins -- -D warnings
clippy-windows:
name: Clippy Windows (${{ matrix.name }})
needs: changes
if: needs.changes.outputs.has_code == 'true' && github.event_name == 'push'
runs-on: windows-latest
strategy:
fail-fast: false
matrix:
include: ${{ fromJSON(needs.changes.outputs.clippy_matrix) }}
steps:
- name: Checkout repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v6
with:
persist-credentials: false
- name: Install Rust
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable
with:
components: clippy
- name: Enable pnpm for setup-node cache
if: contains(matrix.flags, '--all-features')
run: corepack enable pnpm
- name: Install Node.js for WebUI bundle builds
if: contains(matrix.flags, '--all-features')
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v4
with:
node-version: "22"
cache: "pnpm"
# See the webui-v2-js-lint job above for why this is a depth-tolerant
# glob list rather than a single literal.
cache-dependency-path: |
crates/product/ironclaw_webui/frontend/pnpm-lock.yaml
crates/*/*/ironclaw_webui/frontend/pnpm-lock.yaml
- name: Enable pnpm
if: contains(matrix.flags, '--all-features')
run: corepack enable pnpm
- name: Install WebUI frontend dependencies
if: contains(matrix.flags, '--all-features')
shell: bash
run: |
set -euo pipefail
webui_dir="$(bash scripts/ci/crate-dir.sh ironclaw_webui)"
cd "${webui_dir}/frontend"
pnpm install --frozen-lockfile
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
with:
key: windows-${{ matrix.name }}
save-if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}
- name: Check lints
run: cargo clippy --all --tests --examples ${{ matrix.flags }} -- -D warnings
reborn-cli-smoke:
name: Reborn CLI smoke tests
needs: changes
if: needs.changes.outputs.has_reborn_cli == 'true' && github.event_name != 'pull_request'
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v6
with:
persist-credentials: false
- name: Install Rust
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.12"
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
with:
shared-key: reborn-cli-smoke
# Keep saves to protected-branch and merge_group runs so the ~10 GB
# repo cache LRU is seeded by shared states, not arbitrary PR branches.
save-if: ${{ (github.event_name == 'push' && github.ref == 'refs/heads/main') || github.event_name == 'merge_group' }}
- name: Setup OVH sccache
uses: ./.github/actions/setup-sccache-dist
with:
# Keep this Wasmtime-heavy smoke target cache-only. sccache-dist can
# corrupt generated Wasmtime/Wiggle inputs and produce bogus type
# resolution failures.
cache-ssh-host: ${{ vars.SCCACHE_CACHE_SSH_HOST }}
cache-ssh-user: ${{ vars.SCCACHE_CACHE_SSH_USER }}
cache-ssh-port: ${{ vars.SCCACHE_CACHE_SSH_PORT }}
cache-ssh-private-key: ${{ secrets.SCCACHE_CACHE_SSH_PRIVATE_KEY }}
cache-ssh-known-hosts: ${{ secrets.SCCACHE_CACHE_SSH_KNOWN_HOSTS }}
redis-password: ${{ secrets.SCCACHE_REDIS_PASSWORD }}
- name: Test Reborn boot config crate
run: cargo test -p ironclaw_config
- name: Test Reborn CLI binary crate
run: cargo test -p ironclaw
- name: Test Reborn libSQL restart integration
run: |
cargo test -p ironclaw_host_runtime \
--test reborn_durable_restart_integration \
-- --nocapture
- name: Test Reborn architecture boundaries
run: cargo test -p ironclaw_architecture_tests reborn
# docs/ mixes the public Mintlify site with internal engineering docs.
# Omission from docs.json navigation is NOT a publication boundary — a page
# left out of navigation is still deployed, reachable by URL, and indexable.
# This gate fails when a .md/.mdx under docs/ is neither in navigation nor
# fenced by docs/.mintignore (nor explicitly marked `hidden: true`), when
# navigation references a page with no source file, and when .mintignore
# grows a new entry — the fence list is frozen; new internal docs belong
# under docs/internal/.
docs-publication-boundary:
name: Docs publication boundary
needs: changes
if: needs.changes.outputs.has_docs == 'true'
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v6
with:
persist-credentials: false
- name: Install Python
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.12"
- name: Self-test the publication boundary check
run: python3 scripts/ci/test_docs_publication_boundary.py
- name: Check every docs/ page is published or fenced
run: python3 scripts/ci/docs_publication_boundary.py
code-style:
name: Code Style (fmt + clippy)
runs-on: ubuntu-latest
if: always()
needs:
- changes
- fast-checks
- clippy
- clippy-windows
- reborn-cli-smoke
- webui-v2-js-lint
- docs-publication-boundary
steps:
- name: Checkout repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v6
with:
persist-credentials: false
- run: |
# A docs-only PR has has_code=false, so the boundary gate must be
# checked before the early exit below or it could never block.
if [[ "${{ needs.changes.outputs.has_docs }}" == "true" && "${{ needs.docs-publication-boundary.result }}" != "success" ]]; then
echo "Docs publication boundary failed: ${{ needs.docs-publication-boundary.result }}"
exit 1
fi
# Same reason as the docs gate above: a guidance-only PR has
# has_code=false, so fast-checks (which carries check-guidance.py)
# must be judged before the early exit or the gate would run and
# never be able to block — the inert-guard shape this whole change
# exists to remove.
if [[ "${{ needs.changes.outputs.has_guidance }}" == "true" && "${{ needs.fast-checks.result }}" != "success" ]]; then
echo "fast-checks failed: ${{ needs.fast-checks.result }}"
exit 1
fi
if [[ "${{ needs.changes.outputs.has_code }}" == "false" ]]; then
if [[ "${{ needs.changes.outputs.has_reborn_cli }}" == "true" && "${{ needs.reborn-cli-smoke.result }}" != "success" ]]; then
echo "Reborn CLI smoke failed: ${{ needs.reborn-cli-smoke.result }}"
exit 1
fi
echo "No code changes — style checks skipped correctly"
exit 0
fi
# shellcheck disable=SC1091
if ! source .github/scripts/ci-job-result-ok.sh; then
echo "Unable to source .github/scripts/ci-job-result-ok.sh"
exit 1
fi
for job_result in \
"fast-checks=${{ needs.fast-checks.result }}" \
"webui-v2-js-lint=${{ needs.webui-v2-js-lint.result }}"; do
name="${job_result%%=*}"
result="${job_result##*=}"
if ! job_result_ok "$name" "$result" false "allow"; then
echo "$name failed: $result"
exit 1
fi
done
clippy_may_skip=false
if [[ "${{ github.event_name }}" == "pull_request" && \
"${{ needs.changes.outputs.has_clippy }}" != "true" ]]; then
clippy_may_skip=true
fi
if ! job_result_ok \
"clippy" "${{ needs.clippy.result }}" "${clippy_may_skip}" "allow"; then
echo "clippy failed: ${{ needs.clippy.result }}"
exit 1
fi
for job_result in \
"reborn-cli-smoke=${{ needs.reborn-cli-smoke.result }}" \
"clippy-windows=${{ needs.clippy-windows.result }}"; do
name="${job_result%%=*}"
result="${job_result##*=}"
if ! job_result_ok "$name" "$result" true "allow"; then
echo "$name failed: $result"
exit 1
fi
done
- name: Assert the full clippy matrix ran (merge queue + push)
if: needs.changes.outputs.has_code == 'true' && github.event_name != 'pull_request'
env:
MATRIX_JSON: ${{ needs.changes.outputs.clippy_matrix }}
run: |
# This roll-up is a required check. A green result must mean the full
# feature matrix was linted, so fail loudly if the matrix config ever
# regresses to the slim PR lane on a merge-queue or push run.
for lane in all-features default; do
if ! printf '%s' "$MATRIX_JSON" | jq -e --arg lane "$lane" 'any(.[]; .name == $lane)' > /dev/null; then
echo "Required clippy lane missing from matrix on ${{ github.event_name }}: $lane"
echo "Matrix was: $MATRIX_JSON"
exit 1
fi
done