Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 47 additions & 15 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,26 +28,34 @@ updates:
prefix: "chore(ci)"

# --- Python --------------------------------------------------------------
# Root: the CI dependency set for the required green-set job.
- package-ecosystem: pip
directory: "/"
schedule:
interval: monthly
open-pull-requests-limit: 3
groups:
ci-python:
patterns: ["*"]
commit-message:
prefix: "chore(deps)"

# ARF: the application dependency set, including the ML stack.
# ONE entry covering every requirements location, not one per directory.
#
# Two entries (`/` and `/ARF`) produced two PRs that both edited
# ARF/requirements.txt -- #51 and #49 -- so they were guaranteed to conflict,
# and whichever merged second would need a manual rebase for no reason. The
# root entry already reaches ARF/requirements.txt on its own; `directories`
# adds the pwnies file that only the /ARF entry was picking up.
#
# The point of one entry is that ONE config owns all three paths, so no two
# entries can propose conflicting edits to the same file again. How Dependabot
# then shapes the PRs across `directories` -- a single grouped PR, or one per
# directory -- I have not verified, and the honest note is that it does not
# matter here: either shape is conflict-free, because there is no second entry
# to conflict with.
#
# Review suggested adding `group-by: dependency-name`. Declined: by its own
# description that produces one PR PER DEPENDENCY, which is further from the
# single-PR-per-cycle intent than the current setting, not closer.
- package-ecosystem: pip
directory: "/ARF"
directories:
- "/"
- "/ARF"
- "/ARF/pwnies"
schedule:
interval: monthly
open-pull-requests-limit: 3
groups:
arf-python:
python:
patterns: ["*"]
Comment thread
kalisam marked this conversation as resolved.
commit-message:
prefix: "chore(deps)"
Expand Down Expand Up @@ -102,5 +110,29 @@ updates:
- dependency-name: "hdi"
- dependency-name: "hdk"
- dependency-name: "holochain_serialized_bytes"
# getrandom is NOT an ordinary dependency of the zomes. Each zome declares
# `getrandom = { version = "0.3", features = ["wasm_js"] }` purely to turn
# on the wasm backend of the getrandom that hdi/hdk already pull in. It is
# a feature-enabling shim, and it only works while it resolves to the SAME
# version hdi requires.
#
# PR #46 bumped it to 0.4 on its own. hdi 0.7.1 still needs 0.3.4, so the
# two now coexisted as separate crates, `wasm_js` applied only to 0.4.3,
# and 0.3.4 was left with no wasm backend:
#
# error[E0425]: cannot find function `inner_u32` in module `backends`
# --> getrandom-0.3.4/src/lib.rs
#
# Verified by `cargo check --workspace --target wasm32-unknown-unknown`:
# clean on origin/main, broken on that PR. Native `cargo check` passes on
# both, which is why it looks harmless.
#
# Scoped to the MAJOR jump, not the whole crate. A blanket
# `dependency-name: getrandom` would also suppress security updates, and
# 0.3.x patch releases are exactly what should still flow: they unify with
# what hdi already requires, so they carry no risk of the split above.
# Only 0.4+ does.
- dependency-name: "getrandom"
Comment thread
coderabbitai[bot] marked this conversation as resolved.
versions: [">=0.4"]
commit-message:
prefix: "chore(deps)"
161 changes: 154 additions & 7 deletions .github/workflows/rust-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,20 +24,36 @@ name: Rust CI
on:
push:
branches: [ "main" ]
# Must list every file that can change the BUILD, not just sources, and must
# stay in step with the `changes` job's predicate below. A push of only
# ARF/.cargo/config.toml -- the file that selects the wasm getrandom backend
# -- matched nothing here, so main could take that change with no Rust job
# running at all.
paths:
- "**/*.rs"
- "**/Cargo.toml"
- "**/Cargo.lock"
- "rustfmt.toml"
- "**/rustfmt.toml"
- "**/.rustfmt.toml"
- "**/rust-toolchain"
- "**/rust-toolchain.toml"
- "**/.cargo/config"
- "**/.cargo/config.toml"
- ".github/workflows/rust-ci.yml"
# Deliberately NOT path-filtered, for the same reason python-ci.yml is not --
# see the comment on its `pull_request` trigger.
#
# GitHub leaves a required check PENDING, not passing, when its workflow is
# skipped by path filtering. `fmt` and `wasm-check` are meant to be required,
# so a filter here would hang every PR that happens to touch no Rust. Six of
# the seven PRs open when this was written match zero Rust paths, so that is
# measured, not hypothetical.
#
# I made this exact mistake here after documenting it one file over. The jobs
# below therefore always RUN and always report; what they skip is the
# expensive work, decided per-PR by the `changes` job.
pull_request:
branches: [ "main" ]
paths:
- "**/*.rs"
- "**/Cargo.toml"
- "**/Cargo.lock"
- "rustfmt.toml"
- ".github/workflows/rust-ci.yml"
merge_group:
types: [checks_requested]
schedule:
Expand Down Expand Up @@ -68,22 +84,153 @@ env:
CARGO_WORKSPACE: ARF

jobs:
# Decides whether the Rust work is worth doing on this ref, without deciding
# whether the CHECKS report -- those always do.
changes:
name: Detect Rust changes
runs-on: ubuntu-latest
timeout-minutes: 5
outputs:
rust: ${{ steps.detect.outputs.rust }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Detect
id: detect
run: |
set -euo pipefail
if [ "${{ github.event_name }}" != "pull_request" ]; then
# push, schedule, merge_group, manual: always do the work.
echo "rust=true" >> "$GITHUB_OUTPUT"
Comment thread
kalisam marked this conversation as resolved.
exit 0
fi
# The predicate must cover everything that can change the BUILD, not
# just sources. ARF/.cargo/config.toml carries
# [target.wasm32-unknown-unknown]
# rustflags = ['--cfg', 'getrandom_backend="wasm_js"']
# which is the flag that makes the wasm build work at all -- its own
# comment records the failure without it. A PR editing only that file
# was reported rust=false, so both required checks took their no-op
# success path and the gate went green on the single change most
# certain to break it. Same for rust-toolchain.toml, the legacy
# `.cargo/config`, and `.rustfmt.toml`.
# The checkout above uses fetch-depth: 0, which fetches all history for
# all branches, so origin/<base> is already present. No extra fetch --
# and deliberately no `|| true` anywhere in here: every failure mode of
# this step must fail the job. A masked failure produces rust=false,
# which is the answer that SKIPS the required checks.
base="origin/${{ github.base_ref }}"
git rev-parse --verify --quiet "$base^{commit}" >/dev/null \
|| { echo "::error::base ref $base not fetched; cannot decide"; exit 1; }
changed=$(git diff --name-only "$base"...HEAD)
echo "changed files:"
sed 's/^/ /' <<< "$changed"
# Not `... | grep -q ...`: grep -q exits at the first match, the writer
# upstream of it takes SIGPIPE, and under `set -o pipefail` the whole
# pipeline reports non-zero -- so a large diff that DOES contain Rust
# changes could select rust=false and skip the required checks. A
# here-string has no pipeline and no such race.
if grep -Eq '\.rs$|(^|/)Cargo\.(toml|lock)$|(^|/)\.?rustfmt\.toml$|(^|/)rust-toolchain(\.toml)?$|(^|/)\.cargo/config(\.toml)?$|\.github/workflows/rust-ci\.yml$' <<< "$changed"; then
echo "rust=true" >> "$GITHUB_OUTPUT"
else
echo "rust=false" >> "$GITHUB_OUTPUT"
fi

# Both required jobs below run with `!cancelled()` and fail closed on a
# detector that did not succeed. Reason: GitHub reports a job skipped by a
# conditional -- including one skipped because a job it `needs` FAILED -- with
# a conclusion of Success, and a required check in that state does not block
# the merge. (This is the opposite of a workflow skipped by path filtering,
# which stays Pending and blocks; the two are easy to conflate.) So without
# this, any failure in `changes` -- checkout, the base-ref guard, the diff --
# would take both required checks down with it and report both as passing, on
# exactly the PRs where detection could not decide.
fmt:
name: Format (cargo fmt --check)
runs-on: ubuntu-latest
timeout-minutes: 10
needs: changes
if: ${{ !cancelled() }}
steps:
- name: Detector must have succeeded
if: needs.changes.result != 'success'
run: |
echo "::error::Detect Rust changes concluded '${{ needs.changes.result }}'. Nothing proves this ref leaves Rust untouched, so this gate fails closed."
exit 1

- uses: actions/checkout@v4

- name: Set up Rust toolchain (stable + rustfmt)
if: needs.changes.outputs.rust == 'true'
uses: dtolnay/rust-toolchain@4360b52568e2003a75bf9bc1d59f33a8e3fc893c # `stable` branch head, 2026-08-21
with:
components: rustfmt

- name: Cargo fmt (check only)
if: needs.changes.outputs.rust == 'true'
working-directory: ${{ env.CARGO_WORKSPACE }}
run: cargo fmt --all -- --check

- name: No Rust changes
if: needs.changes.outputs.rust != 'true'
run: echo "No Rust files changed; nothing to format-check. Reporting success."

wasm-check:
name: Compile check (wasm32)
Comment on lines +180 to +181

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Run the required WASM check for every pull request

When Compile check (wasm32) is registered as a required check—which is necessary for this job to act as the advertised merge gate—the workflow's pull_request trigger still filters to Rust-related paths at lines 33–40. GitHub will not create the required check for npm-, Python-, or documentation-only PRs, leaving those PRs pending and unable to merge; this exact failure mode is already documented in python-ci.yml lines 34–42. Run the workflow on every pull request and conditionally skip the expensive work while still producing the required status.

Useful? React with 👍 / 👎.

runs-on: ubuntu-latest
timeout-minutes: 30
needs: changes

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Propagate change-detector failures to the WASM gate

When changes fails—for example because its explicit base-ref verification, checkout, or diff fails—GitHub Actions skips jobs that need the failed job unless they use an always() condition. Consequently, wasm-check never reaches its fallback step and is reported as skipped; skipped required checks do not block merging, so a Rust PR can bypass the advertised compile gate precisely when detection cannot decide. Run the required job with always() and fail it when needs.changes.result is not success (and apply the same protection to fmt), or make the detector itself a required check.

Useful? React with 👍 / 👎.

# See the note on `fmt`: skipped required check == Success == merges.
if: ${{ !cancelled() }}
steps:
- name: Detector must have succeeded
if: needs.changes.result != 'success'
run: |
echo "::error::Detect Rust changes concluded '${{ needs.changes.result }}'. Nothing proves this ref leaves Rust untouched, so this gate fails closed."
exit 1

- uses: actions/checkout@v4

- name: Cache cargo registry + build
if: needs.changes.outputs.rust == 'true'
uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
ARF/target
key: ${{ runner.os }}-cargo-wasm-${{ hashFiles('ARF/Cargo.lock') }}
restore-keys: ${{ runner.os }}-cargo-wasm-

- name: Set up Rust toolchain (stable + wasm32)
if: needs.changes.outputs.rust == 'true'
uses: dtolnay/rust-toolchain@4360b52568e2003a75bf9bc1d59f33a8e3fc893c # `stable` branch head, 2026-08-21
with:
targets: wasm32-unknown-unknown

# NOT part of the `clippy`/`test` hold. Those are held because the full
# Holochain build and test surface is not expected to pass yet. This is a
# type-check of the zomes against the target they actually ship to, and it
# is the only thing that can catch a dependency bump breaking the wasm
# backend.
#
# It exists because it was needed: dependabot PR #46 bumped getrandom
# 0.3 -> 0.4 and every check on that PR passed -- green set, CodeQL,
# semgrep, cargo fmt -- while `cargo check --target wasm32-unknown-unknown`
# failed outright. `cargo fmt` does not compile, and clippy and test are
# held, so nothing in CI could see it. Native `cargo check` passes too, so
# the target matters.
- name: Cargo check (wasm32)
if: needs.changes.outputs.rust == 'true'
working-directory: ${{ env.CARGO_WORKSPACE }}
run: cargo check --workspace --target wasm32-unknown-unknown

- name: No Rust changes
if: needs.changes.outputs.rust != 'true'
run: echo "No Rust files changed; nothing to compile-check. Reporting success."

clippy:
name: Lint (cargo clippy) [HELD -- manual only]
if: github.event_name == 'workflow_dispatch'
Expand Down
Loading