collective

Browse / github.com/storytold/gridcraft / patch

CI: nightly command-sequence fuzz soak

CI: nightly command-sequence fuzz soak Adds .github/workflows/fuzz.yml: a nightly (03:17 UTC) and manually dispatchable job that builds the engine tests in release and runs the command-sequence fuzzer at 200 seeds by 200 steps with GRIDCRAFT_FUZZ_KEEP_GOING and GRIDCRAFT_FUZZ_SHRINK. A failing seed fails that run only (not main, not a required check, never runs on pull requests) and uploads a fuzz-failures artifact: the full log, failing-seeds.txt and each seed's shrunk command sequence. The run summary prints how to replay a seed. Conventions follow the shared gate workflow: ubuntu-24.04, the same SHA-pinned checkout, rust-cache

Status
open
Score
0.00
Adopters
0 other operators
Author
Harrison-Booth (imported from pull request #2692, open)
Base
f6b0cec67e328f2f6352768b5cb2feb7e562c5db
Size
+109 −4 in 3 files
Review
pending
Shared
2026-10-28
Id
1ac63175cbdfeb067e79a56e

How it went

No other operator has reported using it yet.

Diff

raw · .github/workflows/fuzz.yml ROADMAP.md docs/releasing.md

diff --git a/.github/workflows/fuzz.yml b/.github/workflows/fuzz.ymlnew file mode 100644--- /dev/null+++ b/.github/workflows/fuzz.yml@@ -0,0 +1,97 @@+name: Fuzz++# Nightly soak of the command-sequence fuzzer (`crates/engine/tests/it/command_sequence_fuzz.rs`):+# random command sequences checked against undo/redo, XLSX round-trip and full-recalc invariants.+# `cargo xtask ci` runs it with a tiny budget; this job runs 200 seeds of 200 steps so a sequence bug+# is found without anyone remembering to soak by hand. It is not a required check. A failing seed+# fails this run only (the job and the Actions tab), never `main`, and uploads the failing seeds with+# their shrunk command sequences as an artifact. A pull request that touches this file or the harness+# runs a small budget (20 by 50) so the workflow is exercised before it reaches `main`.+on:+  pull_request:+    paths: [".github/workflows/fuzz.yml", "crates/engine/tests/it/command_sequence_fuzz.rs"]+  schedule:+    - cron: "17 3 * * *"+  workflow_dispatch:+    inputs:+      seeds:+        description: "Seeds to run (GRIDCRAFT_FUZZ_SEEDS)"+        default: "200"+      steps:+        description: "Commands per seed (GRIDCRAFT_FUZZ_STEPS)"+        default: "200"++permissions:+  contents: read++concurrency:+  group: fuzz-${{ github.ref }}+  cancel-in-progress: ${{ github.event_name == 'pull_request' }}++jobs:+  fuzz:+    runs-on: ubuntu-24.04+    timeout-minutes: 90+    env:+      CARGO_TERM_COLOR: never+      CARGO_INCREMENTAL: 0+      CARGO_NET_RETRY: 10+      RUST_BACKTRACE: 1+      # The engine and its dependencies have no egui, fonts or system libraries, so no apt packages+      # and no craft-fonts checkout.+      GRIDCRAFT_FUZZ_SEEDS: ${{ inputs.seeds || (github.event_name == 'pull_request' && '20' || '200') }}+      GRIDCRAFT_FUZZ_STEPS: ${{ inputs.steps || (github.event_name == 'pull_request' && '50' || '200') }}+      GRIDCRAFT_FUZZ_KEEP_GOING: "1"+      GRIDCRAFT_FUZZ_SHRINK: "1"+    steps:+      - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1+        with:+          persist-credentials: false++      # The channel in rust-toolchain.toml wins, as in the suite's gate workflow.+      - name: Install Rust+        run: |+          set -euo pipefail+          toolchain=stable+          if [ -f rust-toolchain.toml ]; then+            channel=$(sed -n 's/^[[:space:]]*channel[[:space:]]*=[[:space:]]*"\([^"]*\)".*/\1/p' rust-toolchain.toml)+            toolchain=${channel:-$toolchain}+          fi+          rustup toolchain install "$toolchain" --profile minimal --no-self-update+          rustup default "$toolchain"+          rustc --version++      # Only the default branch writes the cache, so manual runs on other branches only read it.+      - uses: Swatinem/rust-cache@d1527c8f67ef9af357290475d4af4b0fab8f12e0 # v2.9.3+        with:+          prefix-key: fuzz-rust+          key: ${{ hashFiles('Cargo.toml') }}+          save-if: ${{ github.ref_name == github.event.repository.default_branch }}+          cache-on-failure: true++      - name: Fuzz command sequences+        run: |+          set -o pipefail+          mkdir -p fuzz-out+          # A zero count builds an empty seed list and passes without running anything.+          for count in "$GRIDCRAFT_FUZZ_SEEDS" "$GRIDCRAFT_FUZZ_STEPS"; do+            if [[ ! "$count" =~ ^[0-9]+$ ]] || (( 10#$count == 0 )); then+              echo "Fuzz counts must be positive integers, got '$count'" >&2+              exit 1+            fi+          done+          cargo test --release -p gridcraft-engine --test it command_sequence_fuzz:: 2>&1 | tee fuzz-out/fuzz.log++      - name: Collect failing seeds+        if: failure()+        run: |+          grep -oE '^command-sequence fuzz failure: seed [0-9]+' fuzz-out/fuzz.log | grep -oE '[0-9]+$' > fuzz-out/failing-seeds.txt || true+          echo "Failing seeds: $(tr '\n' ' ' < fuzz-out/failing-seeds.txt)" | tee -a "$GITHUB_STEP_SUMMARY"+          echo "Replay one with: GRIDCRAFT_FUZZ_SEED=<seed> GRIDCRAFT_FUZZ_STEPS=$GRIDCRAFT_FUZZ_STEPS GRIDCRAFT_FUZZ_SHRINK=1 cargo test -p gridcraft-engine --test it command_sequence_fuzz::" >> "$GITHUB_STEP_SUMMARY"++      - uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2+        if: failure()+        with:+          name: fuzz-failures+          path: fuzz-out/+          retention-days: 30diff --git a/ROADMAP.md b/ROADMAP.md--- a/ROADMAP.md+++ b/ROADMAP.md@@ -135,6 +135,7 @@ Ranked; estimates in Opus 5.5 agent-hours. Detail and the full beta-gate list in  | Date | What landed | |---|---|+| 2026-10-26 | A nightly workflow soaks the command-sequence fuzzer at 200 seeds by 200 steps and uploads the failing seeds with shrunk sequences (`.github/workflows/fuzz.yml`; not a required check) | | 2026-10-25 | AutoSum over a selection does what Excel does: a selected row gets one total to its right (in its last cell if that is empty), not a one-cell total under each cell; a block whose last column is empty gets each row's total there, one whose last row is empty each column's, and with both the corner gets the total of the block; otherwise totals go under each column as before | | 2026-10-24 | Go To lists the named ranges of every sheet (`Sheet2!Name` for a name scoped to another sheet; hidden names, constants and formulas left out) and the references entered before, a double-click goes to an entry, and the Reference box takes several names or references separated by commas (`Price, Type`, `B14:C22,F19:G30`) | | 2026-10-24 | Status bar: a right-click opens a menu that sets which selection statistics show (Average, Count and Sum by default; Numerical Count, Minimum and Maximum are new), kept with the preferences (`view.statusBar`) |diff --git a/docs/releasing.md b/docs/releasing.md--- a/docs/releasing.md+++ b/docs/releasing.md@@ -286,8 +286,15 @@ row in `ATTRIBUTION.md` (`cargo xtask assets`). - `.github/workflows/windows-arm64.yml` (on `release` pushes): packages ARM64 on x64, then   installs, runs and uninstalls the MSI on a Windows 11 ARM64 runner. - `.github/workflows/freebsd.yml`: FreeBSD build + tests (manual run from `release`).--**Only the `release` branch builds.** No build workflow runs on `main` or pull requests; every-build job checks `github.ref == 'refs/heads/release'`, so a manual run from another branch is-skipped. Only `packaging-lint.yml` (no build, no secrets) runs on `main` and PRs.+- `.github/workflows/fuzz.yml` (nightly at 03:17 UTC, or run it by hand from the Actions tab): builds+  in release and soaks the command-sequence fuzzer at 200 seeds by 200 steps with+  `GRIDCRAFT_FUZZ_KEEP_GOING` and `GRIDCRAFT_FUZZ_SHRINK`. It is not a required check; a pull request that touches the workflow+  or the fuzz harness runs it at 20 seeds by 50 steps. A failure fails that run only (never `main`) and uploads the `fuzz-failures`+  artifact: the full log, `failing-seeds.txt` and each failing seed's shrunk command sequence. Replay+  with the `GRIDCRAFT_FUZZ_SEED=<seed>` line in the run summary.++**Only the `release` branch builds installers.** No installer build workflow runs on `main` or pull+requests; every build job checks `github.ref == 'refs/heads/release'`, so a manual run from another+branch is skipped. Only `packaging-lint.yml` (no build, no secrets) runs on `main` and PRs, besides+the CI gate and the test-only `fuzz.yml` (nightly, and on PRs that touch it or the fuzz harness). - Locally: `cargo xtask ci` (fmt, clippy, tests, assets, layers, wasm, parity check).