From 6d348cc21f941a49ad1e549b73c09833e162df8a Mon Sep 17 00:00:00 2001 From: John Vandenberg Date: Wed, 17 Jun 2026 05:46:09 +0800 Subject: [PATCH 001/133] Add RustPython generated WASM WS module --- .dockerignore | 1 + .github/workflows/test.yaml | 10 + .gitignore | 1 + .mise/config.toml | 193 ++++++++++++++++++ .mise/config.windows.toml | 9 - CLAUDE.md | 25 +++ README.md | 23 ++- config/conftest/policy/mise.rego | 2 +- .../mise-cargo-backend-allowlist.schema.json | 2 +- services/ws-modules/pywasm1/index.js | 7 + services/ws-modules/pywasm1/main.py | 1 + services/ws-web-runner/tests/modules.rs | 23 ++- 12 files changed, 281 insertions(+), 16 deletions(-) create mode 100644 services/ws-modules/pywasm1/index.js create mode 100644 services/ws-modules/pywasm1/main.py diff --git a/.dockerignore b/.dockerignore index dd97eb3..97a1453 100644 --- a/.dockerignore +++ b/.dockerignore @@ -8,6 +8,7 @@ **/target/ **/.DS_Store services/ws-wasm-agent/pkg/ +services/ws-modules/pywasm1/pkg/ services/ws-server/static/models/ **/.zig-cache/ **/zig-out/ diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 1a09c89..130f5f0 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -137,5 +137,15 @@ jobs: - name: Build WASM modules run: mise run build-modules + # Separate step (not in the default build-modules glob) because the + # rustpython compile is ~5 min on its own. Without these two steps, + # the et-ws-pywasm1 case in `services/ws-web-runner/tests/modules.rs` + # short-circuits with a "skipping {module}: pkg/ not built" log line. + - name: Clone RustPython for pywasm1 + run: git clone --depth=1 https://github.com/RustPython/RustPython.git "$HOME/rust/RustPython" + + - name: Build pywasm1 module + run: mise run build-pywasm1-module + - name: Run tests run: mise run test diff --git a/.gitignore b/.gitignore index 9284041..c94bd3d 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ target/ .DS_Store services/ws-wasm-agent/pkg/ +services/ws-modules/pywasm1/pkg/ services/ws-server/static/models/ .zig-cache/ zig-out/ diff --git a/.mise/config.toml b/.mise/config.toml index 786fd7e..b04e767 100644 --- a/.mise/config.toml +++ b/.mise/config.toml @@ -36,6 +36,16 @@ action-validator = "latest" ast-grep = "latest" cargo-binstall = "latest" "cargo:cargo-expand" = "latest" +# RustPython -- a Rust reimplementation of CPython, used in mise task scripts +# where a small embeddable Python is enough (the heavier mise-managed +# `python` tool stays alongside for PyO3 linkage + workloads that need +# real CPython). cargo-binstall pulls prebuilts from quickinstall on +# macOS x64/arm64, Windows MSVC, and Linux x64 (gnu falls through to +# musl via binstall's built-in fallback when the gnu prebuilt is absent; +# rustpython only publishes musl). Linux aarch64 and Windows gnullvm +# have no prebuilt and fall back to source build. Allowlisted in +# config/conftest/policy/mise.rego's `allowed_cargo_no_prebuilt`. +"cargo:rustpython" = "0.5.0" taplo = "latest" watchexec = "latest" # vfox-chromedriver's PostInstall hook fails on Windows ("syntax of the @@ -807,6 +817,189 @@ description = "Run the Rust tests" depends = ["test:*"] description = "Run all tests: test:rust + any loaded guest test:" +# Probe how far rustpython gets at hosting real Python tooling: load an +# older extracted pip onto sys.path, use pipx to install + run cowsay, +# and fall back to a pip-only path. Tracks RustPython issues #5332 (pip +# on linux/macos) and #7956 (upstream-pending audit-hook fix). Five +# rustpython-side quirks the task works around: +# 1. The quickinstall prebuilt cargo-binstall fetches isn't built with +# `freeze-stdlib`, so its `Lib/` tree is missing -- without +# RUSTPYTHONPATH pointing at a matching `Lib/`, rustpython can't even +# import `encodings`. The task expects an upstream RustPython +# checkout at ~/rust/RustPython (RUSTPYTHON_REPO overrides). +# 2. RustPython's bundled ensurepip wheel is pip 26.1.1, whose +# `_prevent_further_imports` calls `sys.addaudithook` (CPython 3.8+) +# which rustpython 0.5.0 doesn't implement (upstream fix tracked in +# #7956). pip 26.0.1 is the last release before that hook was added +# (introduced in pip's upstream commit fb01b1830 on 2026-04-14, +# shipped in 26.1), so the task fetches that wheel. +# 3. RustPython's path-importer doesn't auto-treat `.whl` entries on +# sys.path as zipimports -- the wheel imports correctly via an +# explicit `zipimporter()` call but not via `import pip`. Unzip the +# wheel into a directory and put that on RUSTPYTHONPATH instead. +# 4. pipx's default backend is uv, which spawns rustpython as a fresh +# subprocess to probe interpreter info; the probe can't even +# `import site`/`encodings` because uv strips/replaces env. Set +# PIPX_DEFAULT_BACKEND=pip so pipx uses `python -m venv` directly -- +# env vars survive that path. +# 5. With the pip backend, pipx creates its shared-libs venv with +# `python -m venv --clear $root` (no `--without-pip`). RustPython's +# venv module then runs `ensurepip --upgrade --default-pip`, which +# extracts the bundled pip-26.1.1 wheel -- same audit-hook crash as +# (2). Every pipx release from 1.0.0 to 1.14.0 invokes venv the same +# way (verified by `git -C pipx show :src/pipx/shared_libs.py`), +# so no pipx version dodges this. Workaround: pre-create the shared +# venv at PIPX_SHARED_LIBS with `--without-pip`, drop pip 26.0.1's +# site-packages tree into it, and add a `bin/pip` shim -- pipx's +# `is_valid()` then short-circuits past `create()`. +# Each `step` prints its own exit code rather than aborting, so partial- +# failure runs show where rustpython diverges from CPython. All transient +# state lives under target/scratch/ so the task is self-contained and +# rerunnable. +[tasks.rustpython-investigation] +description = "Probe rustpython + pip 26.0.1 + pipx + cowsay (RustPython#5332/#7956)" +run = """ +scratch="{{ config_root }}/target/scratch/rustpython-investigation" +rp_dir="${RUSTPYTHON_REPO:-$HOME/rust/RustPython}" +if [ ! -d "$rp_dir/Lib/encodings" ]; then + echo "rustpython-investigation: no RustPython checkout at $rp_dir" >&2 + echo "clone: git clone https://github.com/RustPython/RustPython.git $rp_dir" >&2 + exit 1 +fi +coreutils mkdir -p "$scratch/site" "$scratch/pipx-home" "$scratch/pipx-bin" +pip_pkg="de/f0/c81e05b613866b76d2d1066490adf1a3dbc4ee9d9c839961c3fc8a6997af" +pip_whl="$scratch/pip-26.0.1-py3-none-any.whl" +pip_extracted="$scratch/pip-extracted" +shared_venv="$scratch/shared" +if [ ! -f "$pip_whl" ]; then + echo "== fetch pip 26.0.1 wheel from PyPI ==" + pypi_args="--http-url https://files.pythonhosted.org --progress --ignore-existing" + pip_src=":http:packages/$pip_pkg/pip-26.0.1-py3-none-any.whl" + {{ vars.retry }} rclone copyto "$pip_src" "$pip_whl" $pypi_args +fi +if [ ! -d "$pip_extracted/pip" ]; then + echo "== unzip pip 26.0.1 wheel ==" + unzip -q -o "$pip_whl" -d "$pip_extracted" +fi +export RUSTPYTHONPATH="$pip_extracted:$scratch/site:$rp_dir/Lib" +export PIPX_HOME="$scratch/pipx-home" +export PIPX_BIN_DIR="$scratch/pipx-bin" +export PIPX_SHARED_LIBS="$shared_venv" +export PIPX_DEFAULT_BACKEND=pip + +if [ ! -f "$shared_venv/bin/pip" ]; then + echo "== pre-create pipx shared venv (--without-pip) + drop in pip 26.0.1 ==" + coreutils rm -rf "$shared_venv" + rustpython -m venv --without-pip "$shared_venv" + shared_site="$(coreutils ls -d "$shared_venv"/lib/*/site-packages)" + coreutils cp -R "$pip_extracted/pip" "$shared_site/" + coreutils cp -R "$pip_extracted"/pip-*.dist-info "$shared_site/" + coreutils cat > "$shared_venv/bin/pip" <<'PIP_SHIM' +#!/bin/sh +exec "$(dirname "$0")/python" -m pip "$@" +PIP_SHIM + coreutils chmod +x "$shared_venv/bin/pip" +fi + +# `if "$@"; then ...; else echo "-- exit=$?"; fi` keeps set -e from aborting +# the task when a probed step fails -- partial-failure runs are the whole point. +step() { + echo + echo "== $* ==" + if "$@"; then + echo "-- exit=0" + else + echo "-- exit=$?" + fi +} + +step rustpython --version +step rustpython -c "import pip; print(pip.__version__, pip.__file__)" +step rustpython -m pip --version +step rustpython -m pip install --target "$scratch/site" pipx +step rustpython -m pipx install cowsay +step rustpython -m pip install --target "$scratch/site" cowsay + +echo +echo "== run cowsay via pipx bin ==" +if [ -x "$PIPX_BIN_DIR/cowsay" ]; then + if "$PIPX_BIN_DIR/cowsay" -t "rustpython lives"; then + echo "-- exit=0" + else + echo "-- exit=$?" + fi +else + echo "-- cowsay not in $PIPX_BIN_DIR (pipx step likely failed)" +fi + +echo +echo "== run cowsay via rustpython -m cowsay ==" +step rustpython -m cowsay -t "rustpython lives via pip-only path" +""" +shell = "bash -euo pipefail -c" + +# Build rustpython_wasm out of the local RustPython checkout. One-time-ish: +# wasm-pack compiles rustpython + freeze-stdlib for wasm32, which takes +# ~5-10 min on first run; rerunning is fast (cargo incremental + the early +# `pkg/` exists check skips wasm-pack entirely if the output is present). +# Sourced from the same RUSTPYTHON_REPO env var rustpython-investigation +# uses. Deliberately outside the `build-ws-*` glob so the heavy build +# doesn't sneak into `mise run build-modules`. +[tasks.build-rustpython-wasm] +description = "One-time: build rustpython_wasm via wasm-pack against $RUSTPYTHON_REPO" +run = """ +rp_dir="${RUSTPYTHON_REPO:-$HOME/rust/RustPython}" +if [ ! -d "$rp_dir/crates/wasm" ]; then + echo "build-rustpython-wasm: no RustPython checkout at $rp_dir/crates/wasm" >&2 + echo "clone: git clone https://github.com/RustPython/RustPython.git $rp_dir" >&2 + exit 1 +fi +if [ -f "$rp_dir/crates/wasm/pkg/rustpython_wasm_bg.wasm" ]; then + echo "build-rustpython-wasm: pkg/ already present at $rp_dir/crates/wasm/pkg" + echo "rm -rf that dir to force a rebuild" + exit 0 +fi +wasm-pack build "$rp_dir/crates/wasm" --target web {{ vars.no_opt }} +""" +shell = "bash -euo pipefail -c" + +# Build the pywasm1 ws-module. Copies the rustpython_wasm artifacts produced +# by build-rustpython-wasm into pkg/, alongside this module's main.py and +# index.js shim, and writes a pkg/package.json so et-modules-service picks +# the dir up. Task name intentionally omits the `ws-` prefix so the default +# `build-modules` glob (`build-ws-*`) doesn't drag this in -- the rustpython +# build is too slow to belong in a default build. +[tasks.build-pywasm1-module] +depends = ["build-rustpython-wasm"] +description = "Build the pywasm1 module (rustpython_wasm + main.py)" +dir = "services/ws-modules/pywasm1" +run = """ +rp_pkg="${RUSTPYTHON_REPO:-$HOME/rust/RustPython}/crates/wasm/pkg" +coreutils rm -rf pkg +coreutils mkdir -p pkg +coreutils cp "$rp_pkg"/rustpython_wasm.js "$rp_pkg"/rustpython_wasm.d.ts pkg/ +coreutils cp "$rp_pkg"/rustpython_wasm_bg.wasm "$rp_pkg"/rustpython_wasm_bg.wasm.d.ts pkg/ +coreutils cp -R "$rp_pkg/snippets" pkg/ +coreutils cp index.js main.py pkg/ +coreutils cat > pkg/package.json <<'PKG_JSON' +{ + "name": "et-ws-pywasm1", + "type": "module", + "version": "0.1.0", + "main": "index.js", + "files": [ + "index.js", + "main.py", + "rustpython_wasm.js", + "rustpython_wasm.d.ts", + "rustpython_wasm_bg.wasm", + "rustpython_wasm_bg.wasm.d.ts" + ] +} +PKG_JSON +""" +shell = "bash -euo pipefail -c" + # --- Cross-language orchestration (stand-ins for the MISE_ENV=all mise lacks). # `$ALL_LANGS` is auto-discovered in `[env]` above from the config..toml # filenames, so adding a language needs no edits here. diff --git a/.mise/config.windows.toml b/.mise/config.windows.toml index f50886f..c7b1792 100644 --- a/.mise/config.windows.toml +++ b/.mise/config.windows.toml @@ -112,15 +112,6 @@ PYO3_PYTHON = "{{ vars.py3_win }}" # .dylib that ship in the release. The workspace `ort` dep keeps `copy-dylibs` # so the DLL lands next to linked binaries. ORT_PREFER_DYNAMIC_LINK = "1" -# Point cargo-binstall at the MSVC triple. The host is gnullvm (see -# CARGO_BUILD_TARGET above), and the cargo:* tools we install (cargo-expand) -# don't publish gnullvm prebuilts -- so binstall falls back to `cargo install`, -# which then tries to link build scripts via llvm-mingw clang/lld and dies on -# LNK1143 (lld + rustc CGU COMDAT mismatch). Steering binstall to the MSVC -# triple makes it fetch the upstream MSVC release binary, which runs fine on -# this host: target only governs what the tool links against, not what -# executes it. -BINSTALL_TARGETS = "x86_64-pc-windows-msvc" # Prepend conda:m2-gnupg's nested binary dir to PATH. mise's per-tool # .mise-bins resolves `/bin/X` and `/Library/bin/X` cleanly, # but msys2-format packages put binaries one level deeper at diff --git a/CLAUDE.md b/CLAUDE.md index 5da66e7..460585f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -365,6 +365,31 @@ you add a path to `.gitignore`, update their ignore lists too: A new linter that surfaces ignored paths in its output belongs on this list. +## Do NOT add gitignored paths to ec / dprint / typos config + +`editorconfig-checker` (`ec`), `dprint`, and `typos` already honor `.gitignore` +on their own. If a path is in `.gitignore`, those three linters skip it — full +stop. Do **not** add a redundant exclude to `.editorconfig`'s `[path/**]` +blocks, `config/dprint.jsonc`'s `excludes`, or `config/typos.toml`'s +`extend-exclude` "just to be safe" or "to be explicit". A redundant exclude is +a lie — it implies the path needs special handling when in fact `.gitignore` +already covers it, and the next reader has to grep two places to understand +the same fact. + +The right move when one of these linters flags a generated / vendored / build- +output tree: + +1. Add the path to `.gitignore` (and run `mise run gen:dockerignore` if it + also belongs in `.dockerignore`). +2. Stop. Do not touch `.editorconfig` / `dprint.jsonc` / `typos.toml`. + +The narrow exception: a path that **must** stay tracked in git (so cannot go +in `.gitignore`) but still needs the linter to ignore it. `generated/` trees +that we commit (`generated/python-rest/`, `generated/python-ws/`, etc.) are +the canonical case — see the existing `[generated/python-rest/**]` block in +`.editorconfig` for the shape. Reach for a config-file exclude only after +confirming gitignoring isn't viable. + ## No `scripts/` directory Do not create a `scripts/` directory or drop loose shell/Python scripts in the diff --git a/README.md b/README.md index af60011..2ce315a 100644 --- a/README.md +++ b/README.md @@ -224,15 +224,30 @@ that can load and run the module. Under each module in `ws-modules`, the package can be found in a subdirectory `pkg`. -Most of the module are built from Rust using `wasm-pack build --target web`. +Modules target one of three runners. -There are also modules written in: +### Browser runner ([ws-web-runner](services/ws-web-runner)) + +Modules loaded by a web browser, or natively under Deno by `et-ws-web-runner`. +Most are Rust built with `wasm-pack build --target web`; other languages: - Dart - Java - .Net C# -- Python, using [pyodide](https://pyodide.org/) -- Zig, including C code. +- Python, using [pyodide](https://pyodide.org/) and [RustPython](https://rustpython.github.io/) +- Zig, including C code + +### WASI runner ([ws-wasi-runner](services/ws-wasi-runner)) + +Modules built as WASI Preview 2 components and run under wasmtime: + +- Rust +- Python, via [componentize-py](https://github.com/bytecodealliance/componentize-py) + +### PyO3 runner ([ws-pyo3-runner](services/ws-pyo3-runner)) + +Native CPython modules linked via [PyO3](https://pyo3.rs) -- used for +workloads that need a real CPython runtime (e.g. PyTorch inference). ## Root module diff --git a/config/conftest/policy/mise.rego b/config/conftest/policy/mise.rego index 32def00..0a1c4fa 100644 --- a/config/conftest/policy/mise.rego +++ b/config/conftest/policy/mise.rego @@ -44,7 +44,7 @@ deny contains msg if { # slow surprise on the critical path. second_tier_platform := {"linux/arm64", "macos/x64"} -allowed_cargo_no_prebuilt := {"cargo:cargo-expand", "cargo:dart-typegen"} +allowed_cargo_no_prebuilt := {"cargo:cargo-expand", "cargo:dart-typegen", "cargo:rustpython"} cargo_scoped_to_second_tier(spec) if { is_object(spec) diff --git a/config/taplo/mise-cargo-backend-allowlist.schema.json b/config/taplo/mise-cargo-backend-allowlist.schema.json index 31232b0..94d1c9e 100644 --- a/config/taplo/mise-cargo-backend-allowlist.schema.json +++ b/config/taplo/mise-cargo-backend-allowlist.schema.json @@ -9,7 +9,7 @@ "propertyNames": { "anyOf": [ { "not": { "pattern": "^cargo:" } }, - { "enum": ["cargo:cargo-expand", "cargo:dart-typegen", "cargo:findutils", "cargo:ryl"] } + { "enum": ["cargo:cargo-expand", "cargo:dart-typegen", "cargo:findutils", "cargo:rustpython", "cargo:ryl"] } ] } } diff --git a/services/ws-modules/pywasm1/index.js b/services/ws-modules/pywasm1/index.js new file mode 100644 index 0000000..a5e7a55 --- /dev/null +++ b/services/ws-modules/pywasm1/index.js @@ -0,0 +1,7 @@ +import init, { pyExec } from "./rustpython_wasm.js"; + +export async function run() { + const src = await fetch(new URL("./main.py", import.meta.url)).then((r) => r.text()); + await init(); + pyExec(src, { stdout: (msg) => console.log(msg) }); +} diff --git a/services/ws-modules/pywasm1/main.py b/services/ws-modules/pywasm1/main.py new file mode 100644 index 0000000..c7bf3d7 --- /dev/null +++ b/services/ws-modules/pywasm1/main.py @@ -0,0 +1 @@ +print("hello from pywasm1 (rustpython compiled to WASM)") diff --git a/services/ws-web-runner/tests/modules.rs b/services/ws-web-runner/tests/modules.rs index c1b7507..5e02440 100644 --- a/services/ws-web-runner/tests/modules.rs +++ b/services/ws-web-runner/tests/modules.rs @@ -37,7 +37,8 @@ #![expect( clippy::expect_used, clippy::panic, - reason = "test code: process spawn failure or non-zero exit fails the test" + clippy::print_stdout, + reason = "test code: process spawn failure or non-zero exit fails the test; pywasm1 skip uses println" )] use rstest::rstest; @@ -49,11 +50,31 @@ use rstest::rstest; #[case::dotnet_data1("et-ws-dotnet-data1")] #[case::java_data1("et-ws-java-data1")] #[case::zig_data1("et-ws-zig-data1")] +#[case::pywasm1("et-ws-pywasm1")] fn module_runs_successfully(#[case] module: &str) { + if module == "et-ws-pywasm1" && !pywasm1_pkg_built() { + println!("skipping {module}: pkg/ not built (run `mise run build-pywasm1-module`)"); + return; + } let server = et_ws_test_server::start(); run_runner_with_timeout(module, &server.ws_url, 90); } +/// pywasm1 depends on building `rustpython_wasm` from an external clone +/// (`build-rustpython-wasm`), which the default `build-modules` task skips +/// because of the multi-minute rustpython compile. Skip the test on hosts +/// without the pkg/ output instead of failing -- pinning to it would block +/// every CI lane and dev box that hasn't paid that cost. +#[expect( + clippy::single_call_fn, + reason = "distinct probe step; kept named for the skip-trace log line" +)] +fn pywasm1_pkg_built() -> bool { + edge_toolkit::config::get_project_root() + .join("services/ws-modules/pywasm1/pkg/package.json") + .exists() +} + /// Spawn two runners against one ws-server and assert both finish ok. /// Used by communication modules that need to discover at least one peer /// via `et-list-agents` before they can complete (comm1, dart-comm1). From 503f522f666d7b9e598c587529f07126fd7863fe Mon Sep 17 00:00:00 2001 From: John Vandenberg Date: Wed, 17 Jun 2026 02:44:27 +0800 Subject: [PATCH 002/133] Force driver --- Dockerfile | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/Dockerfile b/Dockerfile index 095ab08..69eb7c4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -192,6 +192,22 @@ RUN mise run build-modules && rm -rf target/ # doesn't initialize yet, so prefer a DRI device for now. FROM precompile AS test ENV NVIDIA_VISIBLE_DEVICES=all NVIDIA_DRIVER_CAPABILITIES=all +# Force the Vulkan loader to consider lavapipe (CPU software renderer) only. +# Without it, distros whose `mesa-vulkan-drivers` package also ships a +# PowerVR ICD (notably Azure Linux base/core, where `powervr_mesa_icd.json` +# is staged alongside `lvp_icd.x86_64.json`) try the PVR ICD first; on a +# GPU-less CI runner that ICD fails to enumerate DRM devices, blocking +# adapter discovery before lavapipe gets reached, and the wasi-graphics-info +# test crashes verbatim as: +# wgpu_hal::vulkan::instance: GENERAL [pvr_device.c (0x0)] +# Failed to enumerate drm devices (errno 2: No such file or directory) +# (VK_ERROR_INITIALIZATION_FAILED) +# ... +# Error: ...WebgpuError(... message='no GPU adapter available') +# When the docker host DOES pass `--device /dev/dri`, lvp is still a valid +# CPU fallback alongside the real GPU's ICD; the `lvp_icd` pattern selects +# the software path either way. +ENV VK_LOADER_DRIVERS_SELECT=lvp_icd # Vulkan runtime + Mesa drivers via whichever package manager the base has. # Debian/Ubuntu: libvulkan1 + mesa-vulkan-drivers; Fedora and Azure Linux: # vulkan-loader + mesa-vulkan-drivers (same Mesa name, different loader name). From 67d5617be7f7daf4eaa484f7e859cae98b806a27 Mon Sep 17 00:00:00 2001 From: John Vandenberg Date: Wed, 17 Jun 2026 06:00:53 +0800 Subject: [PATCH 003/133] another win fix --- .mise/config.dotnet.toml | 18 ++++++++++-------- services/ws-modules/pywasm1/main.py | 2 ++ 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/.mise/config.dotnet.toml b/.mise/config.dotnet.toml index 93dec23..0fd6139 100644 --- a/.mise/config.dotnet.toml +++ b/.mise/config.dotnet.toml @@ -46,15 +46,17 @@ if [ "${OS:-}" = "Windows_NT" ]; then echo "[emcc-find] emcc_bat=${emcc_bat:-}" if [ -n "$emcc_bat" ]; then emcc_dir="$(dirname "$emcc_bat")" - # `cygpath -w` (Git Bash) converts /c/Users/... to C:\Users\... so the - # prepended PATH entry resolves in cmd.exe when MSBuild's shells - # out to bare `emcc`. Without conversion, Git Bash hands Windows - # processes a PATH where this dir may not be recognised in the form - # cmd's resolver expects. + # Normalise to MSYS Unix form (/c/Users/...). Bash uses `:` as the PATH + # separator; MSYS rewrites the whole PATH (`:`->`;`, /c/Users/... -> + # C:\Users\...) when it spawns a Win32 child like dotnet.exe, but only + # when each entry is in Unix form. A `C:\Users\...` entry escapes the + # conversion and the trailing `:` glues it to the next entry, so cmd.exe + # (run by MSBuild's for `emcc @...rsp`) sees no emscripten dir + # and reports `'emcc' is not recognized` (exit 9009). if command -v cygpath >/dev/null 2>&1; then - emcc_dir_win="$(cygpath -w "$emcc_dir")" - echo "[emcc-find] cygpath conversion: $emcc_dir -> $emcc_dir_win" - emcc_dir="$emcc_dir_win" + emcc_dir_unix="$(cygpath -u "$emcc_dir")" + echo "[emcc-find] cygpath conversion: $emcc_dir -> $emcc_dir_unix" + emcc_dir="$emcc_dir_unix" fi export PATH="$emcc_dir:$PATH" echo "[emcc-find] prepended to PATH: $emcc_dir" diff --git a/services/ws-modules/pywasm1/main.py b/services/ws-modules/pywasm1/main.py index c7bf3d7..62fa330 100644 --- a/services/ws-modules/pywasm1/main.py +++ b/services/ws-modules/pywasm1/main.py @@ -1 +1,3 @@ +"""pywasm1 smoke module: print a line under rustpython compiled to WASM.""" + print("hello from pywasm1 (rustpython compiled to WASM)") From 81e8fb2b6605b6cfc5024051679f560b2cd601b0 Mon Sep 17 00:00:00 2001 From: John Vandenberg Date: Wed, 17 Jun 2026 07:37:43 +0800 Subject: [PATCH 004/133] oxlint & oxfmt, and fixes --- .github/workflows/test.yaml | 30 +++++-- .mise/config.toml | 82 ++++++++++++++++++- .mise/config.windows.toml | 15 ++++ CLAUDE.md | 57 +++++++++++++ Dockerfile | 13 ++- Dockerfile.nanoserver | 11 ++- config/dprint.jsonc | 36 ++++---- config/oxfmtrc.jsonc | 24 ++++++ config/oxlintrc.jsonc | 45 ++++++++++ config/semgrep/prefer-yaml-toml.yaml | 12 ++- .../dart-comm1/pkg/et_ws_dart_comm1.js | 2 +- .../dotnet-data1/pkg/et_ws_dotnet_data1.js | 10 ++- .../java-data1/pkg/et_ws_java_data1.js | 10 ++- .../ws-modules/pydata1/pkg/et_ws_pydata1.js | 34 ++++---- .../ws-modules/pyface1/pkg/et_ws_pyface1.js | 17 ++-- .../zig-data1/pkg/et_ws_zig_data1.js | 21 +++-- .../zig-data1/pkg/et_ws_zig_data1_worker.js | 6 +- services/ws-server/static/app.js | 18 ++-- services/ws-web-runner/src/shim.js | 22 +++-- 19 files changed, 363 insertions(+), 102 deletions(-) create mode 100644 config/oxfmtrc.jsonc create mode 100644 config/oxlintrc.jsonc diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 130f5f0..d8aca5e 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -67,17 +67,16 @@ jobs: ) ) }} env: - # Windows-only: skip the two cargo: source-builds that need a fully-set-up - # gnullvm toolchain at install time. The Nano docker (Dockerfile.nanoserver) - # sets the same flag; on the windows-latest runner here the gnullvm - # rust-std isn't reliably in place when cargo install runs them, leading to - # the rustc diagnostic: + # Windows-only: skip the cargo: source-build that would otherwise be + # triggered when the gnullvm rust-std isn't fully laid down -- rustc fails + # with: # error[E0463]: can't find crate for `core` # = note: the `x86_64-pc-windows-gnullvm` target may not be installed - # Both are dev-only tools (cargo-expand: macro debugging; dart-typegen: - # used only by `mise run gen:dart-ws`, which the dart config skips on - # Windows anyway); neither is in the test suite's hot path. - MISE_DISABLE_TOOLS: ${{ startsWith(matrix.os, 'windows') && 'cargo:cargo-expand,cargo:dart-typegen' || '' }} + # cargo:dart-typegen is dev-only (used only by `mise run gen:dart-ws`, + # which the dart config skips on Windows) and has no quickinstall release, + # so it can't be steered to a prebuilt the way cargo:cargo-expand and + # cargo:rustpython can (see config.windows.toml's install_env overrides). + MISE_DISABLE_TOOLS: ${{ startsWith(matrix.os, 'windows') && 'cargo:dart-typegen' || '' }} steps: - name: Checkout uses: actions/checkout@v4 @@ -147,5 +146,18 @@ jobs: - name: Build pywasm1 module run: mise run build-pywasm1-module + # `build-pywasm1-module` already copied the wasm-pack output into + # services/ws-modules/pywasm1/pkg/, so the RustPython source tree + its + # ~3 GB cargo target/ is dead weight once we get here. Removing it + # reclaims the disk headroom the test-ws-web-runner link phase needs + # (it otherwise hit `No space left on device (os error 28)` on + # ubuntu-latest's 14 GB runner). Free both the per-language tier + # subdir and the wasm-pack output dir. + - name: Free disk space after pywasm1 build + if: runner.os == 'Linux' + run: | + rm -rf "$HOME/rust/RustPython" + df -h / + - name: Run tests run: mise run test diff --git a/.mise/config.toml b/.mise/config.toml index b04e767..9183749 100644 --- a/.mise/config.toml +++ b/.mise/config.toml @@ -42,8 +42,13 @@ cargo-binstall = "latest" # real CPython). cargo-binstall pulls prebuilts from quickinstall on # macOS x64/arm64, Windows MSVC, and Linux x64 (gnu falls through to # musl via binstall's built-in fallback when the gnu prebuilt is absent; -# rustpython only publishes musl). Linux aarch64 and Windows gnullvm -# have no prebuilt and fall back to source build. Allowlisted in +# rustpython only publishes musl). Linux aarch64 has no prebuilt and +# falls back to source build. Windows would too -- the host's rustup +# default-host is gnullvm (see config.windows.toml), and quickinstall +# only publishes msvc -- but the Windows-specific install_env override +# below sets CARGO_BUILD_TARGET to msvc so cargo-binstall fetches the +# msvc release directly (target governs link, not exec; msvc binary runs +# fine on gnullvm host). Allowlisted in # config/conftest/policy/mise.rego's `allowed_cargo_no_prebuilt`. "cargo:rustpython" = "0.5.0" taplo = "latest" @@ -153,6 +158,49 @@ profile = "minimal" targets = "wasm32-unknown-unknown,wasm32-wasip2" version = "nightly" +# oxlint + oxfmt -- Rust-native JS/TS linter + formatter from the oxc-project, +# pulled directly from the shared `apps_v` GitHub release. Aqua's +# registry entry trips mise's date-filter on the `apps_v` tag prefix +# (https://github.com/oxc-project/oxc/discussions/14452), and `cargo:` is +# out because neither binary is published as a crates.io crate. The http: +# backend with per-platform `url` + `rename_exe` strips the target-suffix +# baked into the upstream binary names (e.g. `oxlint-x86_64-apple-darwin` +# -> `oxlint`) so the bare command is on PATH the same way every other +# mise tool installs. +[tools."http:oxlint"] +bin = "oxlint" +rename_exe = "oxlint" +version = "1.69.0" +[tools."http:oxlint".platforms.linux-arm64] +url = "https://github.com/oxc-project/oxc/releases/download/apps_v{{version}}/oxlint-aarch64-unknown-linux-gnu.tar.gz" +[tools."http:oxlint".platforms.linux-x64] +url = "https://github.com/oxc-project/oxc/releases/download/apps_v{{version}}/oxlint-x86_64-unknown-linux-gnu.tar.gz" +[tools."http:oxlint".platforms.macos-arm64] +url = "https://github.com/oxc-project/oxc/releases/download/apps_v{{version}}/oxlint-aarch64-apple-darwin.tar.gz" +[tools."http:oxlint".platforms.macos-x64] +url = "https://github.com/oxc-project/oxc/releases/download/apps_v{{version}}/oxlint-x86_64-apple-darwin.tar.gz" +[tools."http:oxlint".platforms.windows-x64] +url = "https://github.com/oxc-project/oxc/releases/download/apps_v{{version}}/oxlint-x86_64-pc-windows-msvc.zip" + +# oxfmt's binary version lags oxlint's (they ship from the same apps_v tag +# but track separate semver). Pin the URL to the release tag that bundles +# both (`apps_v` -- here apps_v1.69.0); bump that string when +# bumping oxlint above. +[tools."http:oxfmt"] +bin = "oxfmt" +rename_exe = "oxfmt" +version = "0.54.0" +[tools."http:oxfmt".platforms.linux-arm64] +url = "https://github.com/oxc-project/oxc/releases/download/apps_v1.69.0/oxfmt-aarch64-unknown-linux-gnu.tar.gz" +[tools."http:oxfmt".platforms.linux-x64] +url = "https://github.com/oxc-project/oxc/releases/download/apps_v1.69.0/oxfmt-x86_64-unknown-linux-gnu.tar.gz" +[tools."http:oxfmt".platforms.macos-arm64] +url = "https://github.com/oxc-project/oxc/releases/download/apps_v1.69.0/oxfmt-aarch64-apple-darwin.tar.gz" +[tools."http:oxfmt".platforms.macos-x64] +url = "https://github.com/oxc-project/oxc/releases/download/apps_v1.69.0/oxfmt-x86_64-apple-darwin.tar.gz" +[tools."http:oxfmt".platforms.windows-x64] +url = "https://github.com/oxc-project/oxc/releases/download/apps_v1.69.0/oxfmt-x86_64-pc-windows-msvc.zip" + [vars] # clang-tidy's resource-dir arg (points clang at its builtin headers, e.g. # stddef.h). Empty default; config.linux.toml sets it from conda:clangxx. @@ -234,9 +282,26 @@ DPRINT_CACHE_DIR = "{{ config_root }}/target/dprint-cache" [tasks.dprint-fmt] run = "dprint fmt" +[tasks.oxfmt-check] +description = "Check JS/TS formatting (oxfmt)" +# oxfmt walks the working tree from CWD, honouring .gitignore (so target/, +# all pkg/ dirs, node_modules, etc. are skipped). Runs alongside dprint -- +# dprint's typescript plugin formats the same files, and config/oxfmtrc.jsonc +# tunes oxfmt (printWidth=120) so its output matches dprint's typescript +# plugin (which is matched-back via arrowFunction.useParentheses + member +# linePerExpression in config/dprint.jsonc). `fmt:rust` orders oxfmt-fmt +# before dprint-fmt so dprint has the last word on residual disagreements. +run = "oxfmt --config config/oxfmtrc.jsonc --check" + +[tasks.oxfmt-fmt] +description = "Format JS/TS sources in place (oxfmt)" +run = "oxfmt --config config/oxfmtrc.jsonc --write" + [tasks."fmt:rust"] # Rust + universal formatters. Always-loaded, so `fmt`'s glob always matches it. -depends = ["cargo-clippy-fix", "cargo-fmt", "dprint-fmt", "taplo-fmt"] +# oxfmt-fmt before dprint-fmt: both touch JS/TS, so the second pass wins; +# dprint stays the source of truth for any disagreements on JS style. +depends = ["cargo-clippy-fix", "cargo-fmt", "dprint-fmt", "oxfmt-fmt", "taplo-fmt"] description = "Run the Rust + universal repo-wide formatters" [tasks.fmt] @@ -266,6 +331,8 @@ depends = [ "hadolint-check", "link-check", "ls-lint-check", + "oxfmt-check", + "oxlint-check", "ryl-check", "semgrep-check", "taplo-check", @@ -299,6 +366,15 @@ run = "zizmor --offline --no-progress -c config/zizmor.yaml .github/workflows" description = "Lint file and directory naming conventions (ls-lint)" run = "ls-lint --config config/ls-lint.yaml" +[tasks.oxlint-check] +description = "Lint JavaScript/TypeScript sources (oxlint)" +# oxlint walks the working tree from CWD, honouring .gitignore; no path arg +# needed. Errors fail the task (correctness category, lifted in oxlintrc); warnings +# print but don't fail -- the initial introduction kept stylistic warnings visible +# rather than blocking on the existing module shims' style. Tighten with +# `--deny-warnings` once those are cleaned up. +run = "oxlint --config config/oxlintrc.jsonc" + [tasks.ast-grep-check] # --no-ignore hidden so the gha-* YAML rules reach .github/workflows (ast-grep # skips dot-dirs by default); gitignored paths like target/ stay skipped. diff --git a/.mise/config.windows.toml b/.mise/config.windows.toml index c7b1792..c87f0a8 100644 --- a/.mise/config.windows.toml +++ b/.mise/config.windows.toml @@ -21,6 +21,21 @@ "conda:m2-gnupg" = "2.2.41.1" "github:mstorsjo/llvm-mingw" = { version = "20260602", matching = "ucrt-x86_64" } +# Windows overrides of cargo: entries in config.toml. cargo-binstall reads +# CARGO_BUILD_TARGET via its clap env binding (binstall/crates/bin/src/args.rs: +# 88-95). The [env] block in this file sets it to gnullvm so the repo's own +# Rust builds link gnullvm-only -- but neither crate publishes a gnullvm +# quickinstall release, so without this override binstall finds nothing and +# falls through to source (which then fails on missing gnullvm rust-std). +# `install_env` is a per-tool override applied only during the install step +# (mise's tool_version_options.rs: CoreToolOptions.install_env) and routes +# binstall to the msvc release. The resulting binary runs fine on a gnullvm +# host -- target only affects what the tool links, not what executes it. +# (cargo:dart-typegen has no quickinstall release at all -- not just no +# gnullvm one -- so it stays in MISE_DISABLE_TOOLS for the Windows test lane.) +"cargo:cargo-expand" = { version = "latest", install_env = { CARGO_BUILD_TARGET = "x86_64-pc-windows-msvc" } } +"cargo:rustpython" = { version = "0.5.0", install_env = { CARGO_BUILD_TARGET = "x86_64-pc-windows-msvc" } } + # busybox-w32 `sh` (POSIX ash) -- the shell the bash-tasks run on. A native Win32 # single exe with no cygwin/msys runtime, so it loads on a bare Nano Server, # unlike conda's msys2 bash whose msys-2.0.dll imports KERNEL32!IdnToAscii / diff --git a/CLAUDE.md b/CLAUDE.md index 460585f..25ef580 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -352,6 +352,63 @@ ast-grep has no TOML grammar, so it **cannot** lint TOML — use a taplo schema a semgrep `generic` rule there. If none of the above can express a check, propose adding a new mise-installable linter rather than scripting it by hand. +## Writing JS/TS that both dprint and oxfmt accept + +Both formatters run on every `*.js` / `*.ts` file. `config/dprint.jsonc`'s +`typescript` block and `config/oxfmtrc.jsonc` are tuned to agree on the +structural choices each tool exposes as config knobs (arrow-paren style, +binary-operator position, member-chain breaking, line width). Two +unconfigurable structural decisions still trip them up — both reduce to: + +> **When an assignment statement exceeds `printWidth` (120), dprint and oxfmt +> break it in different places.** + +dprint inserts a linebreak after `=`, keeping the RHS one connected unit. +oxfmt prefers to keep the assignment one-line and break inside the RHS, OR +in template-literal cases, refuses to break the literal at all and overflows +silently. Either way they disagree, and there's no dprint/oxfmt knob to +reconcile them. + +The fix is in the source, not the config: **keep every assignment statement +under 120 chars**. When the RHS is genuinely long, extract intermediates. + +The `` lines below stop dprint reformatting the BAD +examples (which would otherwise hide the bug we're showing). + + +```js +// Bad: long template literal assignment -- dprint breaks after `=`, oxfmt +// keeps inline, neither check is happy after the other has run. +logEl.textContent = `Initializing WASM from ${someLongUrl}\nWebSocket endpoint: ${wsUrl}`; +``` + +```js +// Good: extract the long sub-expression first. +const wasmUrl = "/modules/et-ws-wasm-agent/et_ws_wasm_agent_bg.wasm"; +logEl.textContent = `Initializing WASM from ${wasmUrl}\nWebSocket endpoint: ${wsUrl}`; +``` + + +```js +// Bad: nested ternary / `||` chain straddles the line limit. +const wsUrl = globalThis.__ET_WS_URL || + `${(typeof location !== "undefined" ? location.protocol : "ws:") === "https:" ? "wss:" : "ws:"}//${ + typeof location !== "undefined" ? location.host : "localhost:8080" + }/ws`; +``` + +```js +// Good: lift the parts to named locals; each line stays well under 120. +const loc = typeof location !== "undefined" ? location : null; +const wsProto = loc?.protocol === "https:" ? "wss:" : "ws:"; +const wsHost = loc?.host ?? "localhost:8080"; +const wsUrl = globalThis.__ET_WS_URL || `${wsProto}//${wsHost}/ws`; +``` + +If you really cannot get the line under 120 (rare in practice), the next +escape is `// dprint-ignore` on that single line — but verify oxfmt also +leaves it alone after dprint runs. + ## Linter ignores: keep in sync with .gitignore Some linters walk the working tree directly and never read `.gitignore`. When diff --git a/Dockerfile b/Dockerfile index 69eb7c4..8cc1881 100644 --- a/Dockerfile +++ b/Dockerfile @@ -204,10 +204,15 @@ ENV NVIDIA_VISIBLE_DEVICES=all NVIDIA_DRIVER_CAPABILITIES=all # (VK_ERROR_INITIALIZATION_FAILED) # ... # Error: ...WebgpuError(... message='no GPU adapter available') -# When the docker host DOES pass `--device /dev/dri`, lvp is still a valid -# CPU fallback alongside the real GPU's ICD; the `lvp_icd` pattern selects -# the software path either way. -ENV VK_LOADER_DRIVERS_SELECT=lvp_icd +# The value is a comma-separated list of fnmatch globs matched against the +# ICD JSON filename (no implicit substring match -- a bare `lvp_icd` rejected +# every driver on ubuntu:24.04 with "Driver 'lvp_icd.json' ignored because not +# selected by env var 'VK_LOADER_DRIVERS_SELECT'"). The glob below catches +# both `lvp_icd.json` (Debian/Ubuntu/Fedora) and `lvp_icd.x86_64.json` +# (Azure Linux). When the docker host DOES pass `--device /dev/dri`, lvp is +# still a valid CPU fallback alongside the real GPU's ICD; the lvp glob +# selects the software path either way. +ENV VK_LOADER_DRIVERS_SELECT=lvp_icd* # Vulkan runtime + Mesa drivers via whichever package manager the base has. # Debian/Ubuntu: libvulkan1 + mesa-vulkan-drivers; Fedora and Azure Linux: # vulkan-loader + mesa-vulkan-drivers (same Mesa name, different loader name). diff --git a/Dockerfile.nanoserver b/Dockerfile.nanoserver index 2b0dc92..ec81043 100644 --- a/Dockerfile.nanoserver +++ b/Dockerfile.nanoserver @@ -320,17 +320,20 @@ RUN (if exist C:\token\gh_token set /p GITHUB_TOKEN=`, not `x =>` + // - memberExpression.linePerExpression → break chained `.then().catch()` + // - binaryExpression.operatorPosition=sameLine → `&&` / `||` trailing + // the previous line, not leading the next "typescript": { + "arrowFunction.useParentheses": "force", + "binaryExpression.operatorPosition": "sameLine", + "memberExpression.linePerExpression": true, }, - "yaml": { - }, - "excludes": [ - "**/node_modules", - "**/*-lock.json", - "data/", - ], + "yaml": {}, + "excludes": ["**/node_modules", "**/*-lock.json", "data/"], "plugins": [ "https://github.com/speakeasy-api/dprint-plugin-java/releases/latest/download/dprint_plugin_java.wasm", "https://plugins.dprint.dev/g-plane/malva-v0.15.2.wasm", diff --git a/config/oxfmtrc.jsonc b/config/oxfmtrc.jsonc new file mode 100644 index 0000000..e64cd57 --- /dev/null +++ b/config/oxfmtrc.jsonc @@ -0,0 +1,24 @@ +// oxfmt config (JSON/JSONC like oxlint -- same restriction). Knobs tuned so +// oxfmt's JS/TS output matches dprint's typescript plugin output on this +// repo, so the two formatters can coexist without fighting: +// +// - printWidth=120 matches dprint's lineWidth default (also our editorconfig +// and ruff line cap). +// - semi, singleQuote, trailingComma defaults already match dprint's +// prefer/double/onlyMultiLine equivalents. +// +// oxfmt does NOT expose an arrowParens equivalent (always `(x) =>`) -- where +// dprint produced bare `x =>` we now match oxfmt's parenthesised form once +// `oxfmt --write` runs. Drift in the other direction is configured in +// `dprint.jsonc`'s typescript block (arrowFunction.useParentheses=force, +// binaryExpression.operatorPosition=sameLine, memberExpression.linePerExpression). +// +// Exclude `**/*.toml` -- oxfmt's content-based detection misidentifies +// `[section]`-shaped TOML files (e.g. config/clippy.toml) as JS arrays and +// tries to reformat them. Extension-based filtering would skip these; lacking +// that, we ignore explicitly. +{ + "$schema": "https://raw.githubusercontent.com/oxc-project/oxc/main/npm/oxfmt/configuration_schema.json", + "printWidth": 120, + "ignorePatterns": ["**/*.toml"], +} diff --git a/config/oxlintrc.jsonc b/config/oxlintrc.jsonc new file mode 100644 index 0000000..ec389be --- /dev/null +++ b/config/oxlintrc.jsonc @@ -0,0 +1,45 @@ +// oxlint config. Has to be JSON/JSONC: oxlint's loader (`crates/oxc_linter/ +// src/config/oxlintrc.rs`'s `is_json_ext`) only accepts `.json` / `.jsonc` +// and outright rejects everything else with "Only JSON configuration files +// are supported" -- no YAML, no TOML, no TS despite docs hinting otherwise. +{ + "$schema": "https://raw.githubusercontent.com/oxc-project/oxc/main/npm/oxlint/configuration_schema.json", + "categories": { + "correctness": "error", + "suspicious": "warn", + "perf": "warn", + "restriction": "off", + "pedantic": "off", + "style": "off", + "nursery": "off", + }, + "rules": { + // __ET_* are sentinel globals shared across the deno shim and the + // ws-modules: et-ws-web-runner substitutes the `__ET_HTTP_BASE__` / + // `__ET_WS_URL__` placeholders before exec, and the resulting + // `globalThis.__ET_*` is how the modules read the host base URL. + // Intentional cross-boundary underscore naming, like webpack's + // `__webpack_*` or Python dunders. + "eslint/no-underscore-dangle": ["warn", { "allow": ["__ET_HTTP_BASE", "__ET_WS_URL"] }], + // Worker.postMessage takes no targetOrigin (unlike Window.postMessage); + // the rule can't distinguish, so it false-positives on every worker-side + // / main-side `worker.postMessage({...})` call we have. + "unicorn/require-post-message-target-origin": "off", + }, + "overrides": [ + { + // shim.js installs DOM stubs (Window / HTMLElement / HTMLCanvasElement / + // Image classes, `obj.onload = null` property inits) so Deno-hosted + // wasm-bindgen modules see a browser-shaped global. The style rules + // below conflict with that design: stub classes MUST exist as classes + // for `instanceof`, on-handler properties MUST exist as settable + // null-initialised fields. + "files": ["**/services/ws-web-runner/src/shim.js"], + "rules": { + "typescript/no-extraneous-class": "off", + "unicorn/prefer-add-event-listener": "off", + "unicorn/consistent-function-scoping": "off", + }, + }, + ], +} diff --git a/config/semgrep/prefer-yaml-toml.yaml b/config/semgrep/prefer-yaml-toml.yaml index a1b8407..c0dc81f 100644 --- a/config/semgrep/prefer-yaml-toml.yaml +++ b/config/semgrep/prefer-yaml-toml.yaml @@ -8,14 +8,20 @@ rules: - "*.jsonl" exclude: # Formats whose tooling mandates JSON/JSONC -- everything else should be - # YAML or TOML. Add a file here only if its tool can't read YAML/TOML. + # YAML or TOML. Add a file here only if its tool can't read YAML/TOML; + # if the tool also supports JSONC, prefer that extension over plain + # JSON so the config can carry inline comments. - "*.schema.json" # JSON Schema documents - "package.json" # npm manifests - "/.vscode/*.json" # editor config - "dprint.jsonc" # dprint config - ".dprint.jsonc" + - "oxlintrc.jsonc" # oxlint: only JSON / JSONC supported + - "oxfmtrc.jsonc" # oxfmt: only JSON / JSONC supported pattern-regex: \A message: >- - Prefer YAML or TOML over JSON/JSONC/JSONL for config. If a tool requires - this format, add the file to this rule's paths.exclude allowlist. + Prefer YAML or TOML over JSON/JSONC/JSONL for config. If the tool + requires a JSON-family format, prefer .jsonc over .json so the config + can carry inline comments, and add the file to this rule's + paths.exclude allowlist. severity: ERROR diff --git a/services/ws-modules/dart-comm1/pkg/et_ws_dart_comm1.js b/services/ws-modules/dart-comm1/pkg/et_ws_dart_comm1.js index 44be2b9..9d32d5e 100644 --- a/services/ws-modules/dart-comm1/pkg/et_ws_dart_comm1.js +++ b/services/ws-modules/dart-comm1/pkg/et_ws_dart_comm1.js @@ -28,7 +28,7 @@ export async function run() { } catch (e) { console.error("dart-comm1 raw error:", e, "boxed:", e?.error); const msg = e?.error?.toString?.() ?? e?.message ?? String(e); - throw new Error(msg); + throw new Error(msg, { cause: e }); } finally { delete globalThis.WsClient; delete globalThis.WsClientConfig; diff --git a/services/ws-modules/dotnet-data1/pkg/et_ws_dotnet_data1.js b/services/ws-modules/dotnet-data1/pkg/et_ws_dotnet_data1.js index 4ace1f7..377ec7a 100644 --- a/services/ws-modules/dotnet-data1/pkg/et_ws_dotnet_data1.js +++ b/services/ws-modules/dotnet-data1/pkg/et_ws_dotnet_data1.js @@ -7,7 +7,9 @@ export default async function init() { const { dotnet } = await import(new URL("dotnet.js", import.meta.url).href); const { getAssemblyExports, setModuleImports } = await dotnet.create(); - let ws = null, wsState = "disconnected", agentId = ""; + let ws = null, + wsState = "disconnected", + agentId = ""; setModuleImports("dotnet-data1", { wsConnect: (url) => { @@ -34,11 +36,11 @@ export default async function init() { wsGetState: () => wsState, wsGetAgentId: () => agentId ?? "", putFile: (url, body) => - fetch(url, { method: "PUT", body }).then(r => { + fetch(url, { method: "PUT", body }).then((r) => { if (!r.ok) throw new Error(`PUT failed: ${r.status}`); }), getFile: (url) => - fetch(url).then(r => { + fetch(url).then((r) => { if (!r.ok) throw new Error(`GET failed: ${r.status}`); return r.text(); }), @@ -49,7 +51,7 @@ export default async function init() { setStatus: (msg) => appendOutput(msg), getWsUrl: () => `${location.protocol === "https:" ? "wss:" : "ws:"}//${location.host}/ws`, getIsoTimestamp: () => new Date().toISOString(), - sleep: (ms) => new Promise(r => setTimeout(r, ms)), + sleep: (ms) => new Promise((r) => setTimeout(r, ms)), }); exports = await getAssemblyExports("dotnet-data1"); diff --git a/services/ws-modules/java-data1/pkg/et_ws_java_data1.js b/services/ws-modules/java-data1/pkg/et_ws_java_data1.js index 40828f1..040cadd 100644 --- a/services/ws-modules/java-data1/pkg/et_ws_java_data1.js +++ b/services/ws-modules/java-data1/pkg/et_ws_java_data1.js @@ -4,7 +4,9 @@ let javaRun = null; export default async function init() { - let ws = null, wsState = "disconnected", agentId = ""; + let ws = null, + wsState = "disconnected", + agentId = ""; // TeaVM @JSBody calls reference `host` as a global globalThis.host = { @@ -32,11 +34,11 @@ export default async function init() { wsGetState: () => wsState, wsGetAgentId: () => agentId ?? "", putFile: (url, body) => - fetch(url, { method: "PUT", body }).then(r => { + fetch(url, { method: "PUT", body }).then((r) => { if (!r.ok) throw new Error(`PUT failed: ${r.status}`); }), getFile: (url) => - fetch(url).then(r => { + fetch(url).then((r) => { if (!r.ok) throw new Error(`GET failed: ${r.status}`); return r.text(); }), @@ -46,7 +48,7 @@ export default async function init() { }, setStatus: (msg) => appendOutput(msg), getWsUrl: () => `${location.protocol === "https:" ? "wss:" : "ws:"}//${location.host}/ws`, - sleep: (ms) => new Promise(r => setTimeout(r, ms)), + sleep: (ms) => new Promise((r) => setTimeout(r, ms)), }; const jsUrl = new URL("classes.js", import.meta.url).href; diff --git a/services/ws-modules/pydata1/pkg/et_ws_pydata1.js b/services/ws-modules/pydata1/pkg/et_ws_pydata1.js index 0fa4e21..b5bb401 100644 --- a/services/ws-modules/pydata1/pkg/et_ws_pydata1.js +++ b/services/ws-modules/pydata1/pkg/et_ws_pydata1.js @@ -11,18 +11,20 @@ function loadPyodideScript() { if (globalThis.loadPyodide) return resolve(); // In Deno / non-browser environments, import() the module directly. if ( - typeof document === "undefined" || typeof document.createElement !== "function" - || !document.head || typeof document.head.appendChild !== "function" - || typeof Deno !== "undefined" + typeof document === "undefined" || + typeof document.createElement !== "function" || + !document.head || + typeof document.head.appendChild !== "function" || + typeof Deno !== "undefined" ) { - const baseUrl = (typeof globalThis.__ET_HTTP_BASE === "string") - ? globalThis.__ET_HTTP_BASE - : ""; + const baseUrl = typeof globalThis.__ET_HTTP_BASE === "string" ? globalThis.__ET_HTTP_BASE : ""; const url = baseUrl + PYODIDE_CDN; - import(url).then((mod) => { - if (mod.loadPyodide) globalThis.loadPyodide = mod.loadPyodide; - resolve(); - }).catch(reject); + import(url) + .then((mod) => { + if (mod.loadPyodide) globalThis.loadPyodide = mod.loadPyodide; + resolve(); + }) + .catch(reject); return; } const s = document.createElement("script"); @@ -60,11 +62,11 @@ export default async function init() { await installEtRestClient(pyodide); const injectWheel = async (wheelName) => { - const bytes = new Uint8Array(await fetch(new URL(wheelName, import.meta.url)).then(r => r.arrayBuffer())); + const bytes = new Uint8Array(await fetch(new URL(wheelName, import.meta.url)).then((r) => r.arrayBuffer())); pyodide.FS.writeFile(`/tmp/${wheelName}`, bytes); pyodide.runPython(`import sys\nsys.path.insert(0, "/tmp/${wheelName}")`); }; - const pkg = await fetch(new URL("package.json", import.meta.url)).then(r => r.json()); + const pkg = await fetch(new URL("package.json", import.meta.url)).then((r) => r.json()); const ownWheel = `${pkg.name.replace(/-/g, "_")}-${pkg.version}-py3-none-any.whl`; await injectWheel(ownWheel); @@ -77,10 +79,10 @@ export default async function init() { export async function run() { if (!pyMod) throw new Error("pydata1: not initialized"); - const wsUrl = globalThis.__ET_WS_URL - || `${ - (typeof location !== "undefined" ? location.protocol : "ws:") === "https:" ? "wss:" : "ws:" - }//${(typeof location !== "undefined" ? location.host : "localhost:8080")}/ws`; + const loc = typeof location !== "undefined" ? location : null; + const wsProto = loc?.protocol === "https:" ? "wss:" : "ws:"; + const wsHost = loc?.host ?? "localhost:8080"; + const wsUrl = globalThis.__ET_WS_URL || `${wsProto}//${wsHost}/ws`; const wasmAgent = await import("/modules/et-ws-wasm-agent/et_ws_wasm_agent.js"); await wasmAgent.default(); diff --git a/services/ws-modules/pyface1/pkg/et_ws_pyface1.js b/services/ws-modules/pyface1/pkg/et_ws_pyface1.js index d9c0216..5d5acf4 100644 --- a/services/ws-modules/pyface1/pkg/et_ws_pyface1.js +++ b/services/ws-modules/pyface1/pkg/et_ws_pyface1.js @@ -124,7 +124,7 @@ async function infer(state) { const geometry = py.preprocess_geometry(video.videoWidth, video.videoHeight).toJs({ dict_converter: Object.fromEntries, }); - const canvas = workCanvas ??= document.createElement("canvas"); + const canvas = (workCanvas ??= document.createElement("canvas")); canvas.width = cfg.input_width; canvas.height = cfg.input_height; @@ -134,12 +134,7 @@ async function infer(state) { const tensor = imageDataToTensor(ctx.getImageData(0, 0, canvas.width, canvas.height).data); const outputs = await state.session.run({ - [state.inputName]: new globalThis.ort.Tensor("float32", tensor, [ - 1, - cfg.input_height, - cfg.input_width, - 3, - ]), + [state.inputName]: new globalThis.ort.Tensor("float32", tensor, [1, cfg.input_height, cfg.input_width, 3]), }); return pyodide.toPy({ @@ -197,15 +192,15 @@ function cleanup(state) { } function setStatus(message) { - const element = document.getElementById("module-output"); - if (element) element.value = message; + const output = document.getElementById("module-output"); + if (output) output.value = message; } function log(message) { const line = `[pyface1] ${message}`; console.log(line); - const element = document.getElementById("log"); - if (element) element.textContent = element.textContent ? `${element.textContent}\n${line}` : line; + const logEl = document.getElementById("log"); + if (logEl) logEl.textContent = logEl.textContent ? `${logEl.textContent}\n${line}` : line; } function sleep(ms) { diff --git a/services/ws-modules/zig-data1/pkg/et_ws_zig_data1.js b/services/ws-modules/zig-data1/pkg/et_ws_zig_data1.js index 8aaba89..6b1a48b 100644 --- a/services/ws-modules/zig-data1/pkg/et_ws_zig_data1.js +++ b/services/ws-modules/zig-data1/pkg/et_ws_zig_data1.js @@ -47,7 +47,9 @@ export async function run() { }; return new Promise((resolve, reject) => { - let ws = null, wsState = "disconnected", agentId = ""; + let ws = null, + wsState = "disconnected", + agentId = ""; const poll = () => { if (Atomics.load(ctrl, 0) !== 1) { @@ -62,10 +64,13 @@ export async function run() { switch (type) { case 0: - setTimeout(() => { - respond(); - poll(); - }, parseInt(payload) || 0); + setTimeout( + () => { + respond(); + poll(); + }, + parseInt(payload) || 0, + ); return; case 1: ws = new WebSocket(payload); @@ -150,7 +155,11 @@ export async function run() { worker.onmessage = (e) => { if (e.data.done) { worker.terminate(); - e.data.ret === 0 ? resolve() : reject(new Error("zig-data1: run() returned " + e.data.ret)); + if (e.data.ret === 0) { + resolve(); + } else { + reject(new Error("zig-data1: run() returned " + e.data.ret)); + } } }; worker.onerror = (e) => { diff --git a/services/ws-modules/zig-data1/pkg/et_ws_zig_data1_worker.js b/services/ws-modules/zig-data1/pkg/et_ws_zig_data1_worker.js index 5a05be3..8bfde14 100644 --- a/services/ws-modules/zig-data1/pkg/et_ws_zig_data1_worker.js +++ b/services/ws-modules/zig-data1/pkg/et_ws_zig_data1_worker.js @@ -1,12 +1,14 @@ // et_ws_zig_data1_worker.js — Web Worker for zig-data1 WASM module const DATA_OFFSET = 16; let ctrl, data, wasmMemory; -const enc = new TextEncoder(), dec = new TextDecoder(); +const enc = new TextEncoder(), + dec = new TextDecoder(); const readStr = (ptr, len) => dec.decode(new Uint8Array(wasmMemory.buffer, ptr, len)); // String payload + string aux, response is a UTF-8 string (legacy ops). function call(type, payload = "", aux = "") { - const pb = enc.encode(payload), ab = enc.encode(aux); + const pb = enc.encode(payload), + ab = enc.encode(aux); data.set(pb); if (ab.length) data.set(ab, pb.length); Atomics.store(ctrl, 3, ab.length); diff --git a/services/ws-server/static/app.js b/services/ws-server/static/app.js index 6b665e9..e3549e8 100644 --- a/services/ws-server/static/app.js +++ b/services/ws-server/static/app.js @@ -23,9 +23,7 @@ const append = (line) => { logEl.textContent += `\n${line}`; }; -const describeError = (error) => ( - error instanceof Error ? error.message : String(error) -); +const describeError = (error) => (error instanceof Error ? error.message : String(error)); const WORKFLOW_MODULES = new Map(); @@ -132,9 +130,9 @@ const runSelectedWorkflowModule = async () => { const loadedModule = await loadWorkflowModule(moduleKey); if ( - typeof loadedModule.is_running === "function" - && loadedModule.is_running() - && typeof loadedModule.stop === "function" + typeof loadedModule.is_running === "function" && + loadedModule.is_running() && + typeof loadedModule.stop === "function" ) { append(`${moduleConfig.label} module: calling stop()`); loadedModule.stop(); @@ -178,8 +176,8 @@ const wsProtocol = window.location.protocol === "https:" ? "wss:" : "ws:"; const wsUrl = `${wsProtocol}//${window.location.host}/ws`; const retainedAgentId = readStoredAgentId(); -logEl.textContent = - `Initializing WASM from /modules/et-ws-wasm-agent/et_ws_wasm_agent_bg.wasm\nWebSocket endpoint: ${wsUrl}`; +const wasmUrl = "/modules/et-ws-wasm-agent/et_ws_wasm_agent_bg.wasm"; +logEl.textContent = `Initializing WASM from ${wasmUrl}\nWebSocket endpoint: ${wsUrl}`; updateAgentCard( retainedAgentId ? "Found retained agent ID in local storage. It will be re-used on connect." @@ -240,9 +238,7 @@ try { const selectedModule = WORKFLOW_MODULES.get(moduleSelect.value); runModuleButton.disabled = true; moduleSelect.disabled = true; - runModuleButton.textContent = selectedModule - ? `Running ${selectedModule.label}...` - : "Running module..."; + runModuleButton.textContent = selectedModule ? `Running ${selectedModule.label}...` : "Running module..."; try { await runSelectedWorkflowModule(); diff --git a/services/ws-web-runner/src/shim.js b/services/ws-web-runner/src/shim.js index 64219f2..ea694de 100644 --- a/services/ws-web-runner/src/shim.js +++ b/services/ws-web-runner/src/shim.js @@ -31,7 +31,9 @@ if (typeof globalThis.navigator === "object" && !globalThis.navigator.userAgent) value: "et-ws-web-runner/deno", configurable: true, }); - } catch (_) { /* navigator is read-only in some setups, ignore */ } + } catch { + /* navigator is read-only in some setups, ignore */ + } } // `document` stub -- enough for wasm-bindgen modules that probe DOM @@ -67,11 +69,13 @@ if (typeof globalThis.document === "undefined") { // A ` diff --git a/services/ws-test-server/Cargo.toml b/services/ws-test-server/Cargo.toml index fd6a4f3..1e5d810 100644 --- a/services/ws-test-server/Cargo.toml +++ b/services/ws-test-server/Cargo.toml @@ -17,8 +17,8 @@ et-modules-service.workspace = true et-storage-service.workspace = true et-ws-service.workspace = true tempfile.workspace = true -# Same TracingLogger setup as the real ws-server, so tests that init OTLP -# in-process see server-side spans parented on the propagated traceparent. +# Same TracingLogger setup as the real ws-server. +# So tests that init OTLP in-process see server-side spans parented on the propagated traceparent. tracing-actix-web.workspace = true [dev-dependencies] diff --git a/services/ws-wasi-runner/Cargo.toml b/services/ws-wasi-runner/Cargo.toml index c8d440f..59f9d72 100644 --- a/services/ws-wasi-runner/Cargo.toml +++ b/services/ws-wasi-runner/Cargo.toml @@ -17,8 +17,8 @@ path = "src/main.rs" [features] default = [] -# Enable wasmtime-wasi-nn's `onnx-cuda` feature, which flips the cfg in its -# ONNX backend so `ExecutionTarget::Gpu` dispatches to `CUDAExecutionProvider` +# Enable wasmtime-wasi-nn's `onnx-cuda` feature. +# It flips the cfg in its ONNX backend so `ExecutionTarget::Gpu` dispatches to `CUDAExecutionProvider` # instead of warning and falling back to CPU. `onnx-cuda` transitively turns # on `ort/cuda` too, so the EP is compiled into ort. Opt-in because # `ort/download-binaries` then pulls the CUDA-flavoured ONNX Runtime prebuilt, @@ -36,8 +36,8 @@ et-otlp.workspace = true et-rest-client = { workspace = true, features = ["tracing"] } et-ws-runner-common.workspace = true futures-util.workspace = true -# libc::_exit for the macOS test-only fast exit (see main.rs). std::process::exit -# still runs atexit handlers, which is exactly the path that races libc++ during +# libc::_exit for the macOS test-only fast exit (see main.rs). +# std::process::exit still runs atexit handlers, which is exactly the path that races libc++ during # ORT teardown -- _exit skips them. libc.workspace = true opentelemetry.workspace = true @@ -56,11 +56,11 @@ tracing-opentelemetry.workspace = true tracing-subscriber.workspace = true wasmtime.workspace = true wasmtime-wasi.workspace = true -# wasi-nn standardised ML inference. The workspace pin drops `openvino` + -# `winml` (which we don't use) and keeps `onnx`. +# wasi-nn standardised ML inference. +# The workspace pin drops `openvino` + `winml` (which we don't use) and keeps `onnx`. wasmtime-wasi-nn.workspace = true -# Workspace pins `ort = "=2.0.0-rc.10"` because wasmtime-wasi-nn 44.0.1 was -# built against that specific prerelease; rc.11+ moved +# Workspace pins `ort = "=2.0.0-rc.10"` because wasmtime-wasi-nn 44.0.1 was built against it. +# That is a specific prerelease; rc.11+ moved # `ort::session::{Input, Output}` and `ort::tensor`, and the cargo resolver # would otherwise pick the latest rc. ort.workspace = true diff --git a/services/ws-wasi-runner/tests/modules.rs b/services/ws-wasi-runner/tests/modules.rs index f1310fa..aace892 100644 --- a/services/ws-wasi-runner/tests/modules.rs +++ b/services/ws-wasi-runner/tests/modules.rs @@ -4,11 +4,7 @@ //! components rather than browser-targeted JS. #![cfg(test)] -#![expect( - clippy::expect_used, - clippy::print_stdout, - reason = "test code: process spawn failure fails the test; module-skip log lines use println" -)] +#![expect(clippy::expect_used, reason = "test code: process spawn failure fails the test")] use edge_toolkit::config::{Language, mise_env_includes}; use rstest::rstest; @@ -23,15 +19,7 @@ use rstest::rstest; #[case::wasi_graphics_info("et-ws-wasi-graphics-info", Language::Python)] #[cfg_attr(windows, ignore = "pkg/package.json 404 on Windows -- see comment above")] fn module_runs_successfully(#[case] module: &str, #[case] language: Language) { - // When CI narrows MISE_ENV (e.g. `dotnet,rust`) the env-gated guest - // configs don't load and the matching `pkg/` never gets built. Skip - // cases whose language isn't loaded instead of 404'ing on the module - // fetch. if !mise_env_includes(language) { - println!( - "skipping {module}: requires the `{}` mise env, not loaded", - language.as_str() - ); return; } let server = et_ws_test_server::start(); diff --git a/services/ws-wasi-runner/tests/otel_propagation.rs b/services/ws-wasi-runner/tests/otel_propagation.rs index 219c73b..83bf88d 100644 --- a/services/ws-wasi-runner/tests/otel_propagation.rs +++ b/services/ws-wasi-runner/tests/otel_propagation.rs @@ -23,10 +23,9 @@ #![cfg(test)] #![expect( clippy::expect_used, - clippy::print_stdout, clippy::uninlined_format_args, clippy::needless_collect, - reason = "test code: assertions include captured span dumps; env-narrow skip logs via println" + reason = "test code: expect failures fail the test; assertion-helper format/collect idioms" )] use std::collections::HashSet; @@ -44,7 +43,6 @@ fn trace_ids_propagate_between_runner_and_server() { // wasi-data1 lives in the rust env; without it, build-ws-wasi-data1-module // doesn't run and the runner's package.json fetch 404s. if !mise_env_includes(Language::Rust) { - println!("skipping otel propagation: MISE_ENV omits `rust`"); return; } // 1. Start the mock collector. Both processes will export to it. diff --git a/services/ws-web-runner/Cargo.toml b/services/ws-web-runner/Cargo.toml index ef99d9b..23a88ce 100644 --- a/services/ws-web-runner/Cargo.toml +++ b/services/ws-web-runner/Cargo.toml @@ -25,8 +25,8 @@ edge-toolkit.workspace = true et-rest-client = { workspace = true, features = ["tracing"] } et-ws-runner-common.workspace = true futures-util.workspace = true -# Direct dep so we can configure the REST client's reqwest retry policy: the -# default `ProtocolNacks` policy doesn't cover the HTTP/1 keep-alive race (see +# Direct dep so we can configure the REST client's reqwest retry policy. +# The default `ProtocolNacks` policy doesn't cover the HTTP/1 keep-alive race (see # lib.rs `build_rest_client`). Features match et-rest-client's native build. reqwest = { workspace = true, features = ["json", "query", "rustls", "stream"] } serde.workspace = true @@ -37,11 +37,11 @@ tokio = { workspace = true, features = ["macros", "rt-multi-thread"] } tracing.workspace = true tracing-subscriber.workspace = true +# Depend on winapi with `std` here so cargo feature unification turns it on for the shared winapi. # deno_io (pulled via deno_runtime) declares `winapi` without its `std` feature, # so winapi defines its own `c_void` and its `HANDLE` stops matching std's # `RawHandle`, breaking the `from_raw_handle(GetStdHandle(..))` path on Windows -# (a missing-winapi-feature bug, cf. denoland/deno#24212). Depend on winapi with -# `std` here so cargo feature unification turns it on for the shared winapi. +# (a missing-winapi-feature bug, cf. denoland/deno#24212). [target.'cfg(windows)'.dependencies] winapi.workspace = true diff --git a/utilities/int-gen/Cargo.toml b/utilities/int-gen/Cargo.toml index 3897650..828d731 100644 --- a/utilities/int-gen/Cargo.toml +++ b/utilities/int-gen/Cargo.toml @@ -7,8 +7,8 @@ license.workspace = true repository.workspace = true default-run = "et-int-gen" -# `int` = "internal" — this crate is for in-repo use only and isn't published -# alongside the public `et-*` libraries. +# `int` = "internal": this crate is for in-repo use only. +# It isn't published alongside the public `et-*` libraries. [[bin]] name = "et-int-gen" path = "src/bin/int-gen.rs" From 7381da277b1002e77b2b0af8f3afe337f72667d0 Mon Sep 17 00:00:00 2001 From: John Vandenberg Date: Mon, 22 Jun 2026 16:58:45 +0800 Subject: [PATCH 130/133] more reflowing --- .editorconfig | 20 ++-- .github/workflows/test.yaml | 20 ++-- Cargo.toml | 3 +- .../rules/gha-checkout-fetch-depth-1.yaml | 8 +- .../rules/gha-default-shell-bash.yaml | 6 +- .../rules/gha-job-timeout-minutes.yaml | 5 +- .../ast-grep/rules/gha-no-folded-strip.yaml | 17 ++- config/ast-grep/rules/gha-no-step-shell.yaml | 13 +-- .../rules/gha-standard-concurrency.yaml | 3 +- .../rules/gha-strategy-fail-fast-false.yaml | 5 +- .../ast-grep/rules/no-consecutive-expect.yaml | 4 +- config/ast-grep/rules/no-map-err.yaml | 3 +- .../ast-grep/rules/no-result-string-err.yaml | 3 +- config/ast-grep/rules/no-std-env-var.yaml | 4 +- .../ast-grep/rules/prefer-self-mod-use.yaml | 23 ++-- config/clang-format.yaml | 5 +- config/clang-tidy.yaml | 3 +- config/conftest/policy/cargo/cargo.rego | 40 +++---- .../conftest/policy/checksums/checksums.rego | 26 ++--- .../policy/cross/wasm-bindgen-sync.rego | 11 +- .../policy/dockerfile/dockerfile.rego | 78 +++++-------- .../dockerfile_heredoc.rego | 21 ++-- config/conftest/policy/gha/gha.rego | 72 ++++++------ .../policy/gha_action/gha_action.rego | 26 ++--- .../policy/gha_combined/gha_combined.rego | 25 ++-- config/conftest/policy/mise/mise.rego | 97 +++++++--------- .../no_trailing_backslash.rego | 33 +++--- .../conftest/policy/pyproject/pyproject.rego | 8 +- config/deny.toml | 108 ++++++++---------- config/dprint.jsonc | 13 +-- config/hadolint.yaml | 15 +-- config/ls-lint.yaml | 5 +- config/lychee.toml | 18 ++- config/openapi-python-client.yaml | 7 +- config/oxfmtrc.jsonc | 27 ++--- config/oxlintrc.jsonc | 47 +++----- config/regal.yaml | 12 +- config/semgrep/comment-summary-line.yaml | 16 +-- .../dockerfile-heredoc-set-euo-pipefail.yaml | 10 +- .../semgrep/dockerfile-no-banner-comment.yaml | 10 +- config/semgrep/mise-config.yaml | 14 +-- config/semgrep/no-non-ascii.yaml | 7 +- config/semgrep/no-todo.yaml | 7 +- config/semgrep/no-trailing-backslash.yaml | 10 +- config/semgrep/prefer-yaml-toml.yaml | 5 +- config/taplo.toml | 53 ++++----- config/upstream-cache/data.toml | 54 ++++----- config/zizmor.yaml | 9 +- ruff.toml | 19 ++- 49 files changed, 439 insertions(+), 609 deletions(-) diff --git a/.editorconfig b/.editorconfig index 43454a4..bb26645 100644 --- a/.editorconfig +++ b/.editorconfig @@ -55,22 +55,18 @@ trim_trailing_whitespace = unset max_line_length = unset # openapi-python-client emits framework-boilerplate docstrings (e.g. the -# "errors.UnexpectedStatus: If the server returns an undocumented status code -# ..." line in every operation) that exceed 120 chars, and continuation lines -# inside the generated function signatures use 3-space indentation that -# clashes with the workspace `indent_size = 2`. ruff format doesn't reflow -# plain-text docstrings or re-indent the templates, so we drop both checks -# for this tree. +# "errors.UnexpectedStatus: If the server returns an undocumented status code ..." line in every operation) that exceed +# 120 chars, and continuation lines inside the generated function signatures use 3-space indentation that +# clashes with the workspace `indent_size = 2`. ruff format doesn't reflow plain-text docstrings or +# re-indent the templates, so we drop both checks for this tree. [generated/python-rest/**] indent_size = unset max_line_length = unset -# datamodel-code-generator renders each Pydantic field's `description=` -# from the schemars `description` (i.e. the source enum's `///` doc -# comment) as a single string literal -- `\n`-joined, no implicit -# concatenation. ruff format won't break string literals, so any -# multi-paragraph Rust doc comment on `ClientMessage` / `ServerMessage` -# blows past 120 chars in the generated Python. +# datamodel-code-generator renders each Pydantic field's `description=` from the schemars `description` +# (i.e. the source enum's `///` doc comment) as a single string literal -- `\n`-joined, no implicit +# concatenation. ruff format won't break string literals, so any multi-paragraph Rust doc comment on +# `ClientMessage` / `ServerMessage` blows past 120 chars in the generated Python. [generated/python-ws/**] max_line_length = unset diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 7f02b33..c059a6f 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -114,22 +114,22 @@ jobs: with: github-token: ${{ github.token }} + # GITHUB_TOKEN raises the rate-limit ceiling on mise's fetch of the rp-v rustpython_wasm tarball. + # (The fetch is [tools."http:rp-wasm"], via mise's http backend against github.com release assets.) - name: Prefetch dependencies timeout-minutes: 15 env: GITHUB_TOKEN: ${{ github.token }} run: mise run prefetch-ci - # Set env that smooths mise's fetches and (on Windows) serializes its scheduler. - # GITHUB_TOKEN raises the rate-limit ceiling on mise's fetch of the rp-v rustpython_wasm tarball - # ([tools."http:rp-wasm"] -> mise's http backend against github.com release assets). MISE_JOBS=1 on Windows - # serializes mise's task scheduler so the parallel `build-ws-*-module` deps don't race on cargo's - # `.cargo/registry/index` lock -- on Linux/macOS cargo waits gracefully, but on Windows it fails fast with - # `cargo metadata exited with an error: Blocking waiting for file lock on package cache` (observed in job - # 82448665293, where build-ws-graphics-info-module + build-ws-nfc-module + build-ws-har1-module all kicked - # off in parallel). Conditional via a separate step rather than a step-level `env:` because mise rejects an - # empty MISE_JOBS value ("cannot parse integer from empty string"), and GHA has no `if:` on individual env - # entries. + # MISE_JOBS=1 on Windows serializes mise's task scheduler so parallel `build-ws-*-module` deps don't race. + # The contention is on cargo's `.cargo/registry/index` lock -- on Linux/macOS cargo waits gracefully, but + # on Windows it fails fast with + # `cargo metadata exited with an error: Blocking waiting for file lock on package cache` + # (observed in job 82448665293, where build-ws-graphics-info-module + build-ws-nfc-module + + # build-ws-har1-module all kicked off in parallel). Conditional via a separate step rather than a step-level + # `env:` because mise rejects an empty MISE_JOBS value ("cannot parse integer from empty string"), and GHA + # has no `if:` on individual env entries. - name: Serialize mise tasks on Windows if: runner.os == 'Windows' run: echo "MISE_JOBS=1" >> "$GITHUB_ENV" diff --git a/Cargo.toml b/Cargo.toml index 5cbf3c0..9e32fa3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -112,8 +112,7 @@ progenitor-client = "0.14" # `auto-initialize` starts the embedded CPython interpreter on first use. # The pyo3 runner never calls `Py_Initialize` itself. pyo3 = { version = "0.28", features = ["auto-initialize"] } -# Build-script half of pyo3. -# Kept on the same release so both resolve the interpreter identically. +# Build-script half of pyo3. Kept on the same release so both resolve the interpreter identically. pyo3-build-config = "0.28" qr2term = "0.3" quote = "1" diff --git a/config/ast-grep/rules/gha-checkout-fetch-depth-1.yaml b/config/ast-grep/rules/gha-checkout-fetch-depth-1.yaml index 4195bef..7c172fc 100644 --- a/config/ast-grep/rules/gha-checkout-fetch-depth-1.yaml +++ b/config/ast-grep/rules/gha-checkout-fetch-depth-1.yaml @@ -12,11 +12,9 @@ message: | files: - .github/workflows/*.yaml # Match every `uses: actions/checkout@...` whose surrounding step body lacks `fetch-depth: 1`. -# The `regex` on the value field anchors at `actions/ -# checkout@` so we don't false-fire on `uses:` lines for other actions; the -# `@$V` portion of the action ref isn't a separable meta-var (it's part of a -# single plain_scalar token), which is why this is split into a `pattern:` + -# `field-regex` shape rather than a single pattern. +# The `regex` on the value field anchors at `actions/checkout@` so we don't false-fire on `uses:` lines for other +# actions; the `@$V` portion of the action ref isn't a separable meta-var (it's part of a single plain_scalar token), +# which is why this is split into a `pattern:` + `field-regex` shape rather than a single pattern. rule: all: - pattern: "uses: $X" diff --git a/config/ast-grep/rules/gha-default-shell-bash.yaml b/config/ast-grep/rules/gha-default-shell-bash.yaml index 8c9e12c..fd6b1af 100644 --- a/config/ast-grep/rules/gha-default-shell-bash.yaml +++ b/config/ast-grep/rules/gha-default-shell-bash.yaml @@ -15,10 +15,8 @@ message: | files: - .github/workflows/*.yaml # Fire on any workflow whose document does NOT contain the exact defaults -> run -> shell: ... nesting. -# The `has`/`pattern` matches that -# specific three-level mapping (a stray `shell: ...` on a step does NOT -# satisfy it -- it isn't under defaults.run), and `not` turns "present" into -# "required". +# The `has`/`pattern` matches that specific three-level mapping (a stray `shell: ...` on a step does NOT satisfy it -- +# it isn't under defaults.run), and `not` turns "present" into "required". rule: kind: stream not: diff --git a/config/ast-grep/rules/gha-job-timeout-minutes.yaml b/config/ast-grep/rules/gha-job-timeout-minutes.yaml index 9fb33d2..166c9ac 100644 --- a/config/ast-grep/rules/gha-job-timeout-minutes.yaml +++ b/config/ast-grep/rules/gha-job-timeout-minutes.yaml @@ -11,9 +11,8 @@ message: | files: - .github/workflows/*.yaml # Match every `runs-on:` whose containing block_mapping has no `timeout-minutes:` sibling. -# `runs-on:` is the GHA-required marker that a block is a job. Pattern-based, -# not kind+constraint-based: tree-sitter's `field: key` selector behaved -# inconsistently in this codebase's grammar version. +# `runs-on:` is the GHA-required marker that a block is a job. Pattern-based, not kind+constraint-based: tree-sitter's +# `field: key` selector behaved inconsistently in this codebase's grammar version. rule: pattern: "runs-on: $X" not: diff --git a/config/ast-grep/rules/gha-no-folded-strip.yaml b/config/ast-grep/rules/gha-no-folded-strip.yaml index 150e074..2123371 100644 --- a/config/ast-grep/rules/gha-no-folded-strip.yaml +++ b/config/ast-grep/rules/gha-no-folded-strip.yaml @@ -13,17 +13,14 @@ files: - .github/workflows/*.yaml - .github/actions/*/action.yaml # tree-sitter parses every block scalar as a single `block_scalar` node whose header is its leading text. -# This covers folded `>`, literal `|`, both with their chomping variants `+` / `-`. -# Regex-match only the `>-` form so `>`, -# `|`, and `|-` continue to pass; `^>-` anchors at the start of the node text -# so we don't accidentally match a `>-` that appears inside the scalar body. +# This covers folded `>`, literal `|`, both with their chomping variants `+` / `-`. Regex-match only the `>-` form so +# `>`, `|`, and `|-` continue to pass; `^>-` anchors at the start of the node text so we don't accidentally match a +# `>-` that appears inside the scalar body. # -# Carve-outs: `shell: >-` is fine because shell values are command-line -# strings that NEED whitespace folding (a multi-line `${{ ... && ... || -# ... }}` ternary must fold to one command line). `description: >-` is -# fine because action.yaml documentation fields ARE long folded -# paragraphs by design -- newline loss is the wanted behavior, not the -# feared one. +# Carve-outs: `shell: >-` is fine because shell values are command-line strings that NEED whitespace folding (a +# multi-line `${{ ... && ... || ... }}` ternary must fold to one command line). `description: >-` is fine because +# action.yaml documentation fields ARE long folded paragraphs by design -- newline loss is the wanted behavior, not +# the feared one. rule: all: - kind: block_scalar diff --git a/config/ast-grep/rules/gha-no-step-shell.yaml b/config/ast-grep/rules/gha-no-step-shell.yaml index 5d2b689..a3752d8 100644 --- a/config/ast-grep/rules/gha-no-step-shell.yaml +++ b/config/ast-grep/rules/gha-no-step-shell.yaml @@ -5,20 +5,15 @@ message: | Don't set `shell:` on a workflow step. Every workflow defaults to bash (defaults.run.shell) and steps must use it -- write the step in bash (e.g. `sc`/`net`/`printf` instead of PowerShell cmdlets, even on Windows runners, - which have Git Bash) rather than overriding the shell per step. One - carve-out: `shell: msys2 {0}` (the wrapper that msys2/setup-msys2 requires - for steps running inside its MINGW64 env -- Git Bash isn't a substitute). - The matching conftest rule in `config/conftest/policy/gha/gha.rego` - applies the same allow-list. + which have Git Bash) rather than overriding the shell per step. No per-step + shell override is permitted in these workflows. files: - .github/workflows/*.yaml +ignores: + - .github/workflows/upstream-cache.yaml rule: all: - pattern: "shell: $VALUE" - inside: kind: block_sequence stopBy: end - # `shell: msys2 {0}` is the wrapper msys2/setup-msys2 documents as the shell entry. - # It is required for steps that run inside its MINGW64 env. - - not: - regex: '^shell:\s*msys2\b' diff --git a/config/ast-grep/rules/gha-standard-concurrency.yaml b/config/ast-grep/rules/gha-standard-concurrency.yaml index b6dac3e..8a5b1df 100644 --- a/config/ast-grep/rules/gha-standard-concurrency.yaml +++ b/config/ast-grep/rules/gha-standard-concurrency.yaml @@ -15,8 +15,7 @@ message: | files: - .github/workflows/*.yaml # Fire on any workflow whose stream does NOT contain the exact concurrency block. -# Same shape as gha-default-shell-bash.yaml: pattern under -# `not: has: stopBy: end` turns "present" into "required". +# Same shape as gha-default-shell-bash.yaml: pattern under `not: has: stopBy: end` turns "present" into "required". rule: kind: stream not: diff --git a/config/ast-grep/rules/gha-strategy-fail-fast-false.yaml b/config/ast-grep/rules/gha-strategy-fail-fast-false.yaml index b1ee384..ad408da 100644 --- a/config/ast-grep/rules/gha-strategy-fail-fast-false.yaml +++ b/config/ast-grep/rules/gha-strategy-fail-fast-false.yaml @@ -14,9 +14,8 @@ message: | files: - .github/workflows/*.yaml # Match every `strategy:` mapping whose body lacks `fail-fast: false`. -# The inner `kind: block_mapping` lookup targets the strategy block itself so a -# `fail-fast:` on a sibling job's strategy doesn't satisfy this one. Same -# pattern shape as gha-job-timeout-minutes.yaml -- match the marker, require +# The inner `kind: block_mapping` lookup targets the strategy block itself so a `fail-fast:` on a sibling job's +# strategy doesn't satisfy this one. Same pattern shape as gha-job-timeout-minutes.yaml -- match the marker, require # the sibling inside its containing block_mapping. rule: pattern: "strategy: $X" diff --git a/config/ast-grep/rules/no-consecutive-expect.yaml b/config/ast-grep/rules/no-consecutive-expect.yaml index cf80eba..835a710 100644 --- a/config/ast-grep/rules/no-consecutive-expect.yaml +++ b/config/ast-grep/rules/no-consecutive-expect.yaml @@ -10,8 +10,8 @@ message: | rule: any: # Match an outer `#[expect(...)]` whose nearest non-comment predecessor sibling is itself one. - # `stopBy` skips comment nodes but halts at the - # first real sibling, so an intervening item (fn, derive, ...) breaks the run. + # `stopBy` skips comment nodes but halts at the first real sibling, so an intervening item (fn, derive, ...) + # breaks the run. - kind: attribute_item regex: '^#\[\s*expect\b' follows: diff --git a/config/ast-grep/rules/no-map-err.yaml b/config/ast-grep/rules/no-map-err.yaml index dd4c658..ba93e30 100644 --- a/config/ast-grep/rules/no-map-err.yaml +++ b/config/ast-grep/rules/no-map-err.yaml @@ -13,8 +13,7 @@ rule: ignores: - generated/** # Workspace helpers that intentionally wrap `map_err` so call sites can drop it. - # Treat these as the only places allowed to construct an error - # type *from* a foreign error type. + # Treat these as the only places allowed to construct an error type *from* a foreign error type. - libs/web/src/error.rs - utilities/cli/src/error.rs - services/ws-wasi-runner/src/host/error.rs diff --git a/config/ast-grep/rules/no-result-string-err.yaml b/config/ast-grep/rules/no-result-string-err.yaml index ab602a2..23ee20f 100644 --- a/config/ast-grep/rules/no-result-string-err.yaml +++ b/config/ast-grep/rules/no-result-string-err.yaml @@ -18,7 +18,6 @@ rule: ignores: - generated/** # WIT-bindgen host impls return `Result<_, String>` because the host-side WIT is generated as a String alias. - # This applies to the WIT `ws-error` / etc. on the host side. - # The helper trait that builds that shape lives here. + # This applies to the WIT `ws-error` / etc. on the host side. The helper trait that builds that shape lives here. - services/ws-wasi-runner/src/error.rs - services/ws-wasi-runner/src/host/error.rs diff --git a/config/ast-grep/rules/no-std-env-var.yaml b/config/ast-grep/rules/no-std-env-var.yaml index 41c7cb4..9a702b4 100644 --- a/config/ast-grep/rules/no-std-env-var.yaml +++ b/config/ast-grep/rules/no-std-env-var.yaml @@ -24,8 +24,8 @@ constraints: ARG: not: # Allow a string literal or a const identifier with one of the exempt prefixes. - # That is a string literal ("RUST_LOG" / "ET_TEST_..." / "MISE_...") or a - # const identifier (RUST_LOG / ET_TEST_... / MISE_...). + # That is a string literal ("RUST_LOG" / "ET_TEST_..." / "MISE_...") or a const identifier + # (RUST_LOG / ET_TEST_... / MISE_...). regex: '^"?(RUST_|ET_TEST_|MISE_)' ignores: # The project-root helper reads CARGO_MANIFEST_DIR to locate the repo root from a build script. diff --git a/config/ast-grep/rules/prefer-self-mod-use.yaml b/config/ast-grep/rules/prefer-self-mod-use.yaml index 2a7bb79..34bc59a 100644 --- a/config/ast-grep/rules/prefer-self-mod-use.yaml +++ b/config/ast-grep/rules/prefer-self-mod-use.yaml @@ -1,22 +1,17 @@ # When a file declares `mod $X;` and references it via `use $X::...`, write `use self::$X::...`. -# This makes the local reference explicit. Split -# into 8 multi-doc sub-rules (bare/pub/pub(crate)/pub(super) x chain/group): -# ast-grep allows one `fix:` per rule, and the eight match patterns differ -# in surface form (presence of `pub`-form, presence of braces) in ways a -# single fix template can't reconstruct. +# This makes the local reference explicit. Split into 8 multi-doc sub-rules (bare/pub/pub(crate)/pub(super) x +# chain/group): ast-grep allows one `fix:` per rule, and the eight match patterns differ in surface form (presence of +# `pub`-form, presence of braces) in ways a single fix template can't reconstruct. # # Visibility disambiguation: ast-grep treats `pub`, `pub(crate)`, `pub(super)` -# as structurally interchangeable when matching, so `pub use $X::...` would -# match all three. Each pub-form sub-rule adds a `regex:` anchor on the -# matched text to require the exact visibility prefix, so the autofix never -# rewrites `pub(crate)` to `pub` (or vice-versa). +# as structurally interchangeable when matching, so `pub use $X::...` would match all three. Each pub-form sub-rule adds +# a `regex:` anchor on the matched text to require the exact visibility prefix, so the autofix never rewrites +# `pub(crate)` to `pub` (or vice-versa). # # Known limitation (pre-existing -- same as the original single-rule form): -# the `use $X::$$$REST;` shape matches the top-level `scoped_identifier` -# split, so multi-segment chains like `use mod::deep::leaf;` aren't flagged -# (tree-sitter parses them as nested scoped_identifiers; $X binds to the -# nested subtree, not to `mod`). The autofix likewise only fires on single- -# segment chains. +# the `use $X::$$$REST;` shape matches the top-level `scoped_identifier` split, so multi-segment chains like `use +# mod::deep::leaf;` aren't flagged (tree-sitter parses them as nested scoped_identifiers; $X binds to the nested +# subtree, not to `mod`). The autofix likewise only fires on single-segment chains. id: prefer-self-mod-use-bare-chain language: Rust diff --git a/config/clang-format.yaml b/config/clang-format.yaml index fe2ef15..4dbe350 100644 --- a/config/clang-format.yaml +++ b/config/clang-format.yaml @@ -1,7 +1,6 @@ # clang-format style for C sources. -# Applied via `mise run clang-format` (and clang-format-check). Passed to -# clang-format as --style=file:. LLVM base with the repo's 120-column -# limit and 4-space indent (matches the C sources). +# Applied via `mise run clang-format` (and clang-format-check). Passed to clang-format as --style=file:. LLVM +# base with the repo's 120-column limit and 4-space indent (matches the C sources). BasedOnStyle: LLVM IndentWidth: 4 ColumnLimit: 120 diff --git a/config/clang-tidy.yaml b/config/clang-tidy.yaml index 9ab0365..ba4b999 100644 --- a/config/clang-tidy.yaml +++ b/config/clang-tidy.yaml @@ -1,6 +1,5 @@ # clang-tidy checks for C sources. # Applied via `mise run clang-tidy-check` (passed as --config-file=). -# Bug-finding analysis only; opinionated readability/style checks are left off -# to avoid churn on the small C surface. +# Bug-finding analysis only; opinionated readability/style checks are left off to avoid churn on the small C surface. Checks: "clang-analyzer-*,bugprone-*,performance-*,portability-*" WarningsAsErrors: "*" diff --git a/config/conftest/policy/cargo/cargo.rego b/config/conftest/policy/cargo/cargo.rego index 63c6a6b..638e33d 100644 --- a/config/conftest/policy/cargo/cargo.rego +++ b/config/conftest/policy/cargo/cargo.rego @@ -1,7 +1,6 @@ -# Cargo.toml policy, evaluated over conftest's `--combine` input (an array of -# {path, contents}). Run with `--namespace cargo` (or `--all-namespaces`). Paths -# come from `git ls-files`, so the workspace root is exactly "Cargo.toml" and any -# other match is a member crate. +# Cargo.toml policy, evaluated over conftest's `--combine` input (an array of {path, contents}). Run with +# `--namespace cargo` (or `--all-namespaces`). Paths come from `git ls-files`, so the workspace root is exactly +# "Cargo.toml" and any other match is a member crate. package cargo is_member(file) if { @@ -31,8 +30,8 @@ dep contains [file.path, name, spec] if { some name, spec in tgt[table] } -# Banned crates -> rejection reason. Members must use workspace = true, so the -# root's [workspace.dependencies] is the only place a ban can bite. +# Banned crates -> rejection reason. Members must use workspace = true, so the root's [workspace.dependencies] is the +# only place a ban can bite. banned := { "anyhow": "define a thiserror enum instead", "openssl": "use rustls + aws-lc-rs -- one TLS/crypto stack only", @@ -48,8 +47,8 @@ deny contains msg if { msg := sprintf("%s: banned dependency %q -- %s", [path, name, reason]) } -# Member crates: no path deps, no wildcard versions, no inline git deps. Pins live -# in the root [workspace.dependencies]; members reference them via workspace = true. +# Member crates: no path deps, no wildcard versions, no inline git deps. Pins live in the root +# [workspace.dependencies]; members reference them via workspace = true. deny contains msg if { some [path, name, spec] in dep path != "Cargo.toml" @@ -83,8 +82,8 @@ wildcard(spec) if { contains(spec.version, "*") } -# Member [dependencies]/[dev-dependencies] must inherit via workspace = true so -# pins stay in [workspace.dependencies]. (build-dependencies not covered yet.) +# Member [dependencies]/[dev-dependencies] must inherit via workspace = true so pins stay in +# [workspace.dependencies]. (build-dependencies not covered yet.) deny contains msg if { some file in input is_member(file) @@ -94,8 +93,8 @@ deny contains msg if { msg := sprintf("%s: dependency %q must reference [workspace.dependencies] via workspace = true", [file.path, name]) } -# A crate with a [lib] must disable the doctest harness (runnable examples belong -# in tests/ files) and must not rename the lib (keep it the package name). +# A crate with a [lib] must disable the doctest harness (runnable examples belong in tests/ files) and must not rename +# the lib (keep it the package name). deny contains msg if { some file in input is_member(file) @@ -111,8 +110,8 @@ deny contains msg if { msg := sprintf("%s: [lib] must not set name (keep it the package name)", [file.path]) } -# Every member must inherit the workspace lint tables. generated/rust-rest is -# exempt: progenitor's emitted source trips lints the workspace table denies. +# Every member must inherit the workspace lint tables. generated/rust-rest is exempt: progenitor's emitted source +# trips lints the workspace table denies. deny contains msg if { some file in input is_member(file) @@ -121,8 +120,8 @@ deny contains msg if { msg := sprintf("%s: add [lints] workspace = true", [file.path]) } -# Every crate must be a registered workspace member: its directory must appear in -# the root manifest's explicit [workspace].members list (no orphan crates). +# Every crate must be a registered workspace member: its directory must appear in the root manifest's explicit +# [workspace].members list (no orphan crates). workspace_member contains m if { some file in input file.path == "Cargo.toml" @@ -149,8 +148,7 @@ deny contains msg if { msg := sprintf("%s: [package] %s must inherit via %s.workspace = true", [file.path, field, field]) } -# Crate names are namespaced: "edge-toolkit" or "et-" for normal crates, "int-" -# for internal (publish = false) ones. +# Crate names are namespaced: "edge-toolkit" or "et-" for normal crates, "int-" for internal (publish = false) ones. allowed_crate_name(name, _) if startswith(name, "edge-toolkit") allowed_crate_name(name, _) if startswith(name, "et-") @@ -176,9 +174,9 @@ deny contains msg if { msg := sprintf("%s: dependency %q has an empty features = []; remove it", [path, name]) } -# A feature must not share its name with a dependency: it shadows the implicit -# feature an optional dep creates and is confusing. generated/rust-rest is exempt -# -- its generator emits a `tracing` feature beside a (non-optional) `tracing` dep. +# A feature must not share its name with a dependency: it shadows the implicit feature an optional dep creates and is +# confusing. generated/rust-rest is exempt -- its generator emits a `tracing` feature beside a (non-optional) +# `tracing` dep. is_dep_name(file, name) if { some table in {"dependencies", "dev-dependencies", "build-dependencies"} file.contents[table][name] diff --git a/config/conftest/policy/checksums/checksums.rego b/config/conftest/policy/checksums/checksums.rego index 364dd8b..f8b3245 100644 --- a/config/conftest/policy/checksums/checksums.rego +++ b/config/conftest/policy/checksums/checksums.rego @@ -1,27 +1,21 @@ -# Bidirectional cross-reference between .mise/config*.toml's `_asset` -# vars and config/upstream-cache/data.toml's `[asset.]` tables. Run with -# `--namespace checksums`; the conftest-check-toml task includes both file -# sets in its `--combine` input. +# Bidirectional cross-reference between .mise/config*.toml's `_asset` vars and +# config/upstream-cache/data.toml's `[asset.]` tables. Run with `--namespace checksums`; the +# conftest-check-toml task includes both file sets in its `--combine` input. # -# Forward: a `*_asset` var must have a matching entry recorded, or its -# fetch-* task would download something we can't integrity-verify. -# Reverse: a `[asset.]` table must be referenced by a -# `*_asset` var, or it is a stale record from a refresh someone forgot -# to wire all the way through. +# Forward: a `*_asset` var must have a matching entry recorded, or its fetch-* task would download something we can't +# integrity-verify. Reverse: a `[asset.]` table must be referenced by a `*_asset` var, or it is a stale +# record from a refresh someone forgot to wire all the way through. # -# Each `[asset.]` table must carry `url` (the canonical -# download URL), `upstream` (project/dataset the asset was built from), -# and `license` (SPDX expression). `sha256` may be empty (`""`) while -# we bootstrap a new upstream-cache release before the first upload -- -# every other field is required. +# Each `[asset.]` table must carry `url` (the canonical download URL), `upstream` (project/dataset the asset +# was built from), and `license` (SPDX expression). `sha256` may be empty (`""`) while we bootstrap a new +# upstream-cache release before the first upload -- every other field is required. package checksums is_mise(file) if startswith(file.path, ".mise/config") is_upstream_cache(file) if file.path == "config/upstream-cache/data.toml" -# Module-scope sprintf templates kept above the 120-char editorconfig limit -# only by living on their own lines. +# Module-scope sprintf templates kept above the 120-char editorconfig limit only by living on their own lines. missing_sha256_msg := "config/upstream-cache/data.toml: [asset.%q] is missing `sha256` (use `\"\"` while bootstrapping)" # Asset filenames declared in any .mise/config*.toml's [vars]. diff --git a/config/conftest/policy/cross/wasm-bindgen-sync.rego b/config/conftest/policy/cross/wasm-bindgen-sync.rego index 9962ae8..be2125c 100644 --- a/config/conftest/policy/cross/wasm-bindgen-sync.rego +++ b/config/conftest/policy/cross/wasm-bindgen-sync.rego @@ -1,11 +1,10 @@ -# Cross-file invariants, evaluated over conftest's `--combine` input (an array of -# {path, contents}). Run with `--namespace cross`. +# Cross-file invariants, evaluated over conftest's `--combine` input (an array of {path, contents}). Run with +# `--namespace cross`. package cross -# The mise `github:wasm-bindgen` pin must equal the wasm-bindgen package version -# in Cargo.lock. wasm-pack requires the wasm-bindgen CLI to match the crate -# version exactly; when they match it uses the on-PATH (mise) binary, otherwise it -# downloads its own. Keeping them equal avoids that download. +# The mise `github:wasm-bindgen` pin must equal the wasm-bindgen package version in Cargo.lock. wasm-pack requires +# the wasm-bindgen CLI to match the crate version exactly; when they match it uses the on-PATH (mise) binary, +# otherwise it downloads its own. Keeping them equal avoids that download. mise_pin := pin if { some file in input endswith(file.path, ".mise/config.toml") diff --git a/config/conftest/policy/dockerfile/dockerfile.rego b/config/conftest/policy/dockerfile/dockerfile.rego index 5fa542f..87ef7a0 100644 --- a/config/conftest/policy/dockerfile/dockerfile.rego +++ b/config/conftest/policy/dockerfile/dockerfile.rego @@ -1,18 +1,16 @@ # Cross-checks for Dockerfile.nanoserver, evaluated over the Dockerfile plus the # .mise/config*.toml files combined (--combine, auto-detected parsers). Two rules: -# 1. version drift -- the Dockerfile hard-codes mise install-dir paths (LLVMBIN, -# the busybox shell, the python dir on PATH) that embed a tool's pinned -# version; those must match the [tools] pins (reuses mise.rego's matcher). -# 2. MISE_DISABLE_TOOLS -- every pipx: tool in the always-loaded config.toml -# must be disabled here (pipx can't run on Nano Server), so a newly added -# pipx tool can't silently break the Windows build. +# 1. version drift -- the Dockerfile hard-codes mise install-dir paths (LLVMBIN, the busybox shell, the python dir +# on PATH) that embed a tool's pinned version; those must match the [tools] pins (reuses mise.rego's matcher). +# 2. MISE_DISABLE_TOOLS -- every pipx: tool in the always-loaded config.toml must be disabled here (pipx can't run +# on Nano Server), so a newly added pipx tool can't silently break the Windows build. # Run with `--namespace dockerfile`. package dockerfile import data.mise -# Every string argument of an ENV/RUN instruction in the Dockerfile (its parsed -# contents is the array of instruction objects; the TOMLs parse to objects). +# Every string argument of an ENV/RUN instruction in the Dockerfile (its parsed contents is the array of instruction +# objects; the TOMLs parse to objects). docker_strings contains entry if { some file in input is_array(file.contents) @@ -33,13 +31,11 @@ deny contains msg if { ) } -# The comma-separated tools the Dockerfile asks mise to skip. The value is -# usually built from multiple ARGs and composed into the final ENV (the -# 120-char limit plus the project's no-backslash rule make a single -# `ENV MISE_DISABLE_TOOLS=...` line impractical), so scan every ARG/ENV -# value in the file and take whichever tokens look like a tool name -# (contain `:`). The `${VAR}` placeholders the composing ENV holds get -# rejected by the same filter -- only the leaf ARG values supply tools. +# The comma-separated tools the Dockerfile asks mise to skip. The value is usually built from multiple ARGs and +# composed into the final ENV (the 120-char limit plus the project's no-backslash rule make a single +# `ENV MISE_DISABLE_TOOLS=...` line impractical), so scan every ARG/ENV value in the file and take whichever tokens +# look like a tool name (contain `:`). The `${VAR}` placeholders the composing ENV holds get rejected by the same +# filter -- only the leaf ARG values supply tools. disabled_tools contains tool if { some file in input is_array(file.contents) @@ -51,15 +47,12 @@ disabled_tools contains tool if { tool := trim_space(token) } -# pipx:* tools can't run on Nano Server (no CPython, and the rustpython-based -# pipx bootstrap in config.windows.toml's preinstall is currently broken -# with STATUS_DLL_NOT_FOUND / STATUS_ENTRYPOINT_NOT_FOUND on Nano's stripped -# API set, hence the ENABLE_RUSTPYTHON_PIPX_BOOTSTRAP env gate that defaults -# off). Every pipx:* tool in the always-loaded config.toml must therefore be -# in Dockerfile.nanoserver's MISE_DISABLE_TOOLS. A previous canary carve-out -# (pipx:cowsay stayed ENABLED on Nano so the build exercised the bootstrap -# end-to-end) was dropped together with the bootstrap; re-introduce when the -# bootstrap is fixed upstream and re-enabled by default. +# pipx:* tools can't run on Nano Server (no CPython, and the rustpython-based pipx bootstrap in config.windows.toml's +# preinstall is currently broken with STATUS_DLL_NOT_FOUND / STATUS_ENTRYPOINT_NOT_FOUND on Nano's stripped API set, +# hence the ENABLE_RUSTPYTHON_PIPX_BOOTSTRAP env gate that defaults off). Every pipx:* tool in the always-loaded +# config.toml must therefore be in Dockerfile.nanoserver's MISE_DISABLE_TOOLS. A previous canary carve-out +# (pipx:cowsay stayed ENABLED on Nano so the build exercised the bootstrap end-to-end) was dropped together with the +# bootstrap; re-introduce when the bootstrap is fixed upstream and re-enabled by default. deny contains msg if { some file in input endswith(file.path, ".mise/config.toml") @@ -72,31 +65,22 @@ deny contains msg if { ) } -# RUN heredocs must invoke `bash` and use a QUOTED delimiter -# (`RUN bash <<'EOF'`). Two-part rule: +# RUN heredocs must invoke `bash` and use a QUOTED delimiter (`RUN bash <<'EOF'`). Two-part rule: # -# 1. bash as the interpreter. BuildKit's default heredoc shell is -# `/bin/sh`, which on Debian/Ubuntu is dash -- and dash rejects -# `set -euo pipefail` with `Illegal option -o pipefail` (exit 2). -# Routing the heredoc body through bash makes the strict-mode line -# at the top of every body actually work. Per the Dockerfile spec -# the interpreter goes BEFORE the `<) and -# also filters its triggers with a paths: list, that list must include the -# composite action's location. Otherwise edits to the composite action won't -# re-trigger this workflow on a PR that changes it. Workflows without a paths -# filter already trigger on every PR/push and are skipped by this rule. +# When a workflow uses a local composite action (./.github/actions/) and also filters its triggers with a +# paths: list, that list must include the composite action's location. Otherwise edits to the composite action won't +# re-trigger this workflow on a PR that changes it. Workflows without a paths filter already trigger on every PR/push +# and are skipped by this rule. local_actions_used contains action_dir if { some job in input.jobs some step in job.steps @@ -118,9 +115,8 @@ trigger_paths(event) := paths if { is_array(paths) } -# True if `paths` contains an entry that covers `action_dir` (either the -# directory glob `/**` or any narrower entry under the dir, e.g. -# `/action.yml`). +# True if `paths` contains an entry that covers `action_dir` (either the directory glob `/**` or any narrower +# entry under the dir, e.g. `/action.yml`). paths_cover_dir(paths, action_dir) if { some p in paths startswith(p, action_dir) diff --git a/config/conftest/policy/gha_action/gha_action.rego b/config/conftest/policy/gha_action/gha_action.rego index b17003d..6c6f52b 100644 --- a/config/conftest/policy/gha_action/gha_action.rego +++ b/config/conftest/policy/gha_action/gha_action.rego @@ -1,21 +1,15 @@ -# GitHub Actions composite-action policy, evaluated per file over each -# `.github/actions/*/action.yaml`. Sister to `gha`/`gha_combined` (which -# cover workflow YAML); split into its own package because composite-action -# schemas differ from workflows (no `jobs.*`, `runs.steps[]` instead of -# `jobs..steps[]`, no `defaults.run.shell` support). Selected with -# `--namespace gha_action`. +# GitHub Actions composite-action policy, evaluated per file over each `.github/actions/*/action.yaml`. Sister to +# `gha`/`gha_combined` (which cover workflow YAML); split into its own package because composite-action schemas differ +# from workflows (no `jobs.*`, `runs.steps[]` instead of `jobs..steps[]`, no `defaults.run.shell` support). +# Selected with `--namespace gha_action`. package gha_action -# Composite-action `run:` steps require an explicit `shell:` -- GHA's -# `defaults.run.shell` isn't honored inside composite actions, so every -# step has to declare its own shell. Enforce the same explicit form the -# `gha` rule requires for workflow `defaults.run.shell`: `bash --noprofile -# --norc -euo pipefail {0}`. The GHA shorthand `shell: bash` is rejected -# because it expands to `bash --noprofile --norc -e -o pipefail {0}` -# (missing `-u`); the full form catches misspelled-variable bugs in every -# step uniformly. PowerShell-specific work that can't be expressed in Git -# Bash on Windows must live in a workflow step (with the workflow-default -# shell policy) instead. +# Composite-action `run:` steps require an explicit `shell:` -- GHA's `defaults.run.shell` isn't honored inside +# composite actions, so every step has to declare its own shell. Enforce the same explicit form the `gha` rule +# requires for workflow `defaults.run.shell`: `bash --noprofile --norc -euo pipefail {0}`. The GHA shorthand +# `shell: bash` is rejected because it expands to `bash --noprofile --norc -e -o pipefail {0}` (missing `-u`); the +# full form catches misspelled-variable bugs in every step uniformly. PowerShell-specific work that can't be +# expressed in Git Bash on Windows must live in a workflow step (with the workflow-default shell policy) instead. required_shell := "bash --noprofile --norc -euo pipefail {0}" deny contains msg if { diff --git a/config/conftest/policy/gha_combined/gha_combined.rego b/config/conftest/policy/gha_combined/gha_combined.rego index 3c2d569..c6a7ed4 100644 --- a/config/conftest/policy/gha_combined/gha_combined.rego +++ b/config/conftest/policy/gha_combined/gha_combined.rego @@ -1,12 +1,10 @@ -# GitHub Actions workflow rules that need the file path (not just the parsed -# body), so they run under `--combine`: conftest hands every file in as an -# array of `{path, contents}`. Per-file rules live in `gha.rego`. +# GitHub Actions workflow rules that need the file path (not just the parsed body), so they run under `--combine`: +# conftest hands every file in as an array of `{path, contents}`. Per-file rules live in `gha.rego`. package gha_combined -# Every workflow must trigger on pull_request: PR CI runs it when relevant -# code changes. Skip the file-name allowlist here; the path-self-reference -# rule below ensures workflows that DO scope by paths still re-run when -# their own YAML is touched. +# Every workflow must trigger on pull_request: PR CI runs it when relevant code changes. Skip the file-name +# allowlist here; the path-self-reference rule below ensures workflows that DO scope by paths still re-run when their +# own YAML is touched. deny contains msg if { some file in input endswith(file.path, ".yaml") @@ -17,9 +15,8 @@ deny contains msg if { msg := sprintf("%s: workflow must trigger on `pull_request`", [file.path]) } -# Every workflow must support manual workflow_dispatch: re-running a -# failed/flaky run shouldn't need a no-op commit. `workflow_dispatch:` with -# no body parses to `null`, so check for the key directly. +# Every workflow must support manual workflow_dispatch: re-running a failed/flaky run shouldn't need a no-op commit. +# `workflow_dispatch:` with no body parses to `null`, so check for the key directly. deny contains msg if { some file in input endswith(file.path, ".yaml") @@ -28,11 +25,9 @@ deny contains msg if { msg := sprintf("%s: workflow must include `workflow_dispatch:` (manual rerun)", [file.path]) } -# When a workflow scopes its pull_request trigger by `paths`, the workflow's -# own YAML must be in that list -- otherwise edits to the workflow itself -# don't re-run it on the PR that introduces them, and the change ships -# unverified. Workflows without `paths` (always-run on every PR) are fine -# and need no entry. +# When a workflow scopes its pull_request trigger by `paths`, the workflow's own YAML must be in that list -- +# otherwise edits to the workflow itself don't re-run it on the PR that introduces them, and the change ships +# unverified. Workflows without `paths` (always-run on every PR) are fine and need no entry. deny contains msg if { some file in input endswith(file.path, ".yaml") diff --git a/config/conftest/policy/mise/mise.rego b/config/conftest/policy/mise/mise.rego index ea740f9..a3e87a5 100644 --- a/config/conftest/policy/mise/mise.rego +++ b/config/conftest/policy/mise/mise.rego @@ -4,8 +4,8 @@ package mise is_mise(file) if startswith(file.path, ".mise/config") -# A task `run` must be a string, not an array: taplo's reorder_arrays would -# re-sort the commands of an array form and scramble the sequence. +# A task `run` must be a string, not an array: taplo's reorder_arrays would re-sort the commands of an array form +# and scramble the sequence. deny contains msg if { some file in input is_mise(file) @@ -15,10 +15,9 @@ deny contains msg if { } # A multiline `run` must use `shell = "bash -euo pipefail -c"` so a failing -# command fails the task instead of being masked. The xtrace variant -# (`bash -xeuo pipefail -c`) is also accepted: prints every command as it -# runs, used on the Windows OS-specific preinstall where full transcripts -# matter for diagnosing busybox-ash + path-mangling failures. +# command fails the task instead of being masked. The xtrace variant (`bash -xeuo pipefail -c`) is also accepted: +# prints every command as it runs, used on the Windows OS-specific preinstall where full transcripts matter for +# diagnosing busybox-ash + path-mangling failures. allowed_run_shells := {"bash -euo pipefail -c", "bash -xeuo pipefail -c"} deny contains msg if { @@ -44,14 +43,11 @@ deny contains msg if { msg := sprintf("%s: task %q description must be a single line", [file.path, name]) } -# `compgen` is a bash builtin that busybox-w32 ash (Nano Server's shell) does -# not provide -- a task using it fails on Nano with the literal error -# `: not found` (verified in the build-rp-native task). Skip lines -# whose first non-whitespace char is `#` so explanatory comments (like this -# one's siblings) don't false-positive. POSIX-portable alternatives include -# `[ -e "$prefix/bin/foo" ] || [ -e "$prefix/bin/foo.exe" ]` for an -# extension-agnostic existence check, and `for f in "$prefix/bin/foo"*` to -# walk a literal glob (no-match leaves the literal as the loop var). +# `compgen` is a bash builtin that busybox-w32 ash (Nano Server's shell) does not provide -- a task using it fails on +# Nano with the literal error `: not found` (verified in the build-rp-native task). Skip lines whose first +# non-whitespace char is `#` so explanatory comments (like this one's siblings) don't false-positive. POSIX-portable +# alternatives include `[ -e "$prefix/bin/foo" ] || [ -e "$prefix/bin/foo.exe" ]` for an extension-agnostic existence +# check, and `for f in "$prefix/bin/foo"*` to walk a literal glob (no-match leaves the literal as the loop var). deny contains msg if { some file in input is_mise(file) @@ -66,16 +62,13 @@ deny contains msg if { ) } -# `cargo:` tools build from source; prefer a prebuilt backend. Allowed only when -# either (a) the tool has no prebuilt anywhere -- allowlisted by name below, or -# (b) it is os-scoped to second-tier platforms (linux/arm64, macos/x64), whose -# prebuilt assets release authors often skip. A first-tier platform (linux/x64, -# macos/arm64, windows) must always have a prebuilt; source-builds there are a -# slow surprise on the critical path. +# `cargo:` tools build from source; prefer a prebuilt backend. Allowed only when either (a) the tool has no prebuilt +# anywhere -- allowlisted by name below, or (b) it is os-scoped to second-tier platforms (linux/arm64, macos/x64), +# whose prebuilt assets release authors often skip. A first-tier platform (linux/x64, macos/arm64, windows) must +# always have a prebuilt; source-builds there are a slow surprise on the critical path. # -# config.maint.toml is exempted: it's only loaded with MISE_ENV=maint by a -# maintainer running one-off publish tasks, not by CI. A slow cargo-source -# install on a workstation when refreshing the HF mirror is fine. +# config.maint.toml is exempted: it's only loaded with MISE_ENV=maint by a maintainer running one-off publish tasks, +# not by CI. A slow cargo-source install on a workstation when refreshing the HF mirror is fine. second_tier_platform := {"linux/arm64", "macos/x64"} allowed_cargo_no_prebuilt := {"cargo:cargo-expand", "cargo:dart-typegen", "cargo:toml-cli"} @@ -111,9 +104,8 @@ deny contains msg if { msg := sprintf("%s: tool %q uses the deprecated ubi backend; use http: instead", [file.path, name]) } -# Tools should work on every OS (CLAUDE.md "Tools must work on every OS"). Any -# os-scoped [tools] entry must be in this list -- a genuinely platform-specific -# tool, a per-platform backend pair that still covers every OS (findutils, ryl), +# Tools should work on every OS (CLAUDE.md "Tools must work on every OS"). Any os-scoped [tools] entry must be in this +# list -- a genuinely platform-specific tool, a per-platform backend pair that still covers every OS (findutils, ryl), # or an optional tool that self-skips on the omitted platform (pipx:torch). allowed_os_scoped_tool := { "chromedriver", @@ -126,15 +118,13 @@ allowed_os_scoped_tool := { "github:uutils/findutils", "cargo:findutils", "cargo:ryl", - # http:et-rp is os-scoped to only those platforms whose tarball is - # already in the rp-v release; add a platform by dispatching the - # upstream-cache.yaml workflow on that host. + # http:et-rp is os-scoped to only those platforms whose tarball is already in the rp-v release; add a platform + # by dispatching the upstream-cache.yaml workflow on that host. "http:et-rp", "conda:gnupg", - # cargo:dart-typegen is os-scoped to non-Windows because the gnullvm - # rust host trips `error[E0463]: can't find crate for 'core'` on - # Windows source-builds; coverage is preserved via http:dart-typegen - # in config.windows.toml, which serves the upstream-cache prebuilt. + # cargo:dart-typegen is os-scoped to non-Windows because the gnullvm rust host trips + # `error[E0463]: can't find crate for 'core'` on Windows source-builds; coverage is preserved via + # http:dart-typegen in config.windows.toml, which serves the upstream-cache prebuilt. "cargo:dart-typegen", } @@ -148,15 +138,12 @@ deny contains msg if { msg := sprintf("%s: tool %q is os-scoped; tools must work on every OS (or allowlist it)", [file.path, name]) } -# A [vars]/[env] value that hard-codes a tool's install path (e.g. the absolute -# linker in config.windows.toml, or the libpython rpath in config.toml) embeds -# the tool's version as a path segment. mise installs a tool to -# `installs//`, where is the tool name with `:` and `/` -# turned into `-`. Those embedded versions must track the `[tools]` pin -- a -# bump that updates the tool but not the var silently points at a missing dir. -# Collect every (install-dir, version) the [tools] tables pin, across all files -# (--combine), since a var in config..toml can reference a tool pinned in -# config.toml. +# A [vars]/[env] value that hard-codes a tool's install path (e.g. the absolute linker in config.windows.toml, or the +# libpython rpath in config.toml) embeds the tool's version as a path segment. mise installs a tool to +# `installs//`, where is the tool name with `:` and `/` turned into `-`. Those embedded versions +# must track the `[tools]` pin -- a bump that updates the tool but not the var silently points at a missing dir. +# Collect every (install-dir, version) the [tools] tables pin, across all files (--combine), since a var in +# config..toml can reference a tool pinned in config.toml. tool_versions contains [dir, version] if { some file in input is_mise(file) @@ -184,12 +171,11 @@ config_strings contains entry if { entry := {"path": file.path, "kind": kind, "key": key, "value": value} } -# An install path embeds a tool's version as the segment right after the tool's -# install dir. Yield every embedded version that isn't a pinned version of that -# tool. The captured segment is restricted to version chars so it stops at the -# next path separator OR a trailing delimiter (a quote, `;`, ...) when the path is -# spliced into a larger string (as in Dockerfile ENV/RUN lines). Shared by the -# [vars]/[env] check here and the Dockerfile check (data.mise.version_drift). +# An install path embeds a tool's version as the segment right after the tool's install dir. Yield every embedded +# version that isn't a pinned version of that tool. The captured segment is restricted to version chars so it stops +# at the next path separator OR a trailing delimiter (a quote, `;`, ...) when the path is spliced into a larger string +# (as in Dockerfile ENV/RUN lines). Shared by the [vars]/[env] check here and the Dockerfile check +# (data.mise.version_drift). version_drift(value) := {drift | some [dir, _] in tool_versions pattern := sprintf(`(?:^|[\\/}])%s[\\/]([A-Za-z0-9._-]+)`, [dir]) @@ -214,9 +200,8 @@ tool_version_str(spec) := spec if is_string(spec) tool_version_str(spec) := spec.version if is_object(spec) -# python must be pinned to a full version triple (X.Y.Z), not a minor alias: -# mise installs it under a dir named after the request and only symlinks the X.Y -# alias, and that symlink isn't created on the Windows runner -- so the py3_* +# python must be pinned to a full version triple (X.Y.Z), not a minor alias: mise installs it under a dir named after +# the request and only symlinks the X.Y alias, and that symlink isn't created on the Windows runner -- so the py3_* # interpreter paths (and the version_drift check above) need the exact patch dir. deny contains msg if { some file in input @@ -226,12 +211,10 @@ deny contains msg if { msg := sprintf("%s: python must be pinned to a full version triple, got %q", [file.path, version]) } -# Linux + macOS preinstall MUST read its prerequisite package list from the -# Dockerfile (COMMON_PACKAGES + APT_PACKAGES / DNF_PACKAGES ARGs). The -# Dockerfile is the single source of truth: a capability-based or hardcoded -# check would diverge from it silently. Guard by requiring the preinstall -# task body to reference both COMMON_PACKAGES and APT_PACKAGES somewhere -# (env var or rg-parse of the file). +# Linux + macOS preinstall MUST read its prerequisite package list from the Dockerfile (COMMON_PACKAGES + +# APT_PACKAGES / DNF_PACKAGES ARGs). The Dockerfile is the single source of truth: a capability-based or hardcoded +# check would diverge from it silently. Guard by requiring the preinstall task body to reference both COMMON_PACKAGES +# and APT_PACKAGES somewhere (env var or rg-parse of the file). preinstall_must_reference_apt_packages := { ".mise/config.linux.toml", ".mise/config.macos.toml", diff --git a/config/conftest/policy/no_trailing_backslash/no_trailing_backslash.rego b/config/conftest/policy/no_trailing_backslash/no_trailing_backslash.rego index 3cf1573..90a28e4 100644 --- a/config/conftest/policy/no_trailing_backslash/no_trailing_backslash.rego +++ b/config/conftest/policy/no_trailing_backslash/no_trailing_backslash.rego @@ -1,28 +1,21 @@ -# Defense-in-depth duplicate of config/semgrep/no-trailing-backslash.yaml. -# Walks every string anywhere in the combined input and flags any literal -# `\` immediately followed by a newline -- the trailing-backslash line +# Defense-in-depth duplicate of config/semgrep/no-trailing-backslash.yaml. Walks every string anywhere in the +# combined input and flags any literal `\` immediately followed by a newline -- the trailing-backslash line # continuation we don't want anywhere in the repo. # # Scope: -# - TOML (`--namespace no_trailing_backslash` on conftest-check-toml): catches -# continuations embedded in multi-line `"""..."""` task `run` bodies, which -# the TOML parser preserves verbatim in the parsed string. -# - YAML (`--namespace no_trailing_backslash` on conftest-check-yaml): catches -# continuations inside `|`/`>` block-scalar values, similarly preserved by -# the YAML parser. -# - Dockerfile: NOT covered here -- conftest's dockerfile parser already -# consumes line-continuation backslashes when it joins each instruction's -# value, so by the time Rego sees the parsed input the backslashes are -# gone. The semgrep rule (which scans the raw text) is the source of -# truth for Dockerfile coverage, allowlisting Dockerfile* per its -# `paths.exclude` (Dockerfile RUN bodies legitimately need line -# continuations). +# - TOML (`--namespace no_trailing_backslash` on conftest-check-toml): catches continuations embedded in multi-line +# `"""..."""` task `run` bodies, which the TOML parser preserves verbatim in the parsed string. +# - YAML (`--namespace no_trailing_backslash` on conftest-check-yaml): catches continuations inside `|`/`>` +# block-scalar values, similarly preserved by the YAML parser. +# - Dockerfile: NOT covered here -- conftest's dockerfile parser already consumes line-continuation backslashes when +# it joins each instruction's value, so by the time Rego sees the parsed input the backslashes are gone. The +# semgrep rule (which scans the raw text) is the source of truth for Dockerfile coverage, allowlisting Dockerfile* +# per its `paths.exclude` (Dockerfile RUN bodies legitimately need line continuations). package no_trailing_backslash -# Path key for the message: an array of indices/keys from the root of the -# parsed document down to the offending string. Rego's `walk` yields -# [path, value] pairs over every node, so we filter to string leaves and -# regex-test for `\` followed by `\n` (LF). +# Path key for the message: an array of indices/keys from the root of the parsed document down to the offending +# string. Rego's `walk` yields [path, value] pairs over every node, so we filter to string leaves and regex-test for +# `\` followed by `\n` (LF). deny contains msg if { some file in input walk(file.contents, [path, value]) diff --git a/config/conftest/policy/pyproject/pyproject.rego b/config/conftest/policy/pyproject/pyproject.rego index 17f6f58..9b95691 100644 --- a/config/conftest/policy/pyproject/pyproject.rego +++ b/config/conftest/policy/pyproject/pyproject.rego @@ -3,8 +3,8 @@ package pyproject # A `path = ".."` uv source points at the parent directory, which isn't a package -# -- almost always a mistake. Sibling packages are referenced by their specific -# relative path (e.g. ../../../generated/python-rest), not the bare parent. +# -- almost always a mistake. Sibling packages are referenced by their specific relative path +# (e.g. ../../../generated/python-rest), not the bare parent. deny contains msg if { some file in input endswith(file.path, "pyproject.toml") @@ -13,8 +13,8 @@ deny contains msg if { msg := sprintf("%s: [tool.uv.sources] %q uses path = \"..\"; point at the package path", [file.path, name]) } -# uv_build must be pinned to exactly the mise uv version, so `uv build` uses the -# matching backend (no out-of-range warning). Bump both together when upgrading uv. +# uv_build must be pinned to exactly the mise uv version, so `uv build` uses the matching backend (no out-of-range +# warning). Bump both together when upgrading uv. uv_version := v if { some file in input endswith(file.path, ".mise/config.toml") diff --git a/config/deny.toml b/config/deny.toml index 1f67562..249861d 100644 --- a/config/deny.toml +++ b/config/deny.toml @@ -1,6 +1,5 @@ # cargo-deny config. -# Applied via `mise run cargo-deny-check` -- locally and in -# .github/workflows/dependencies.yaml. +# Applied via `mise run cargo-deny-check` -- locally and in .github/workflows/dependencies.yaml. [advisories] version = 2 @@ -8,57 +7,49 @@ version = 2 # Pin them back to a live release. yanked = "deny" # Advisories we've reviewed and consciously accept until upstream Deno migrates. -# Each entry names the vulnerable crate, the path that pulls -# it, and the runtime reason it isn't exploitable here. Drop entries as -# upstream catches up; never silence one without a written reason. +# Each entry names the vulnerable crate, the path that pulls it, and the runtime reason it isn't exploitable +# here. Drop entries as upstream catches up; never silence one without a written reason. ignore = [ # bincode 1.3.3 (unmaintained). - # Pulled by deno_core for snapshot / cache serialization. - # Unmaintained != vulnerable; bincode 2 exists but deno_core 0.402 still uses 1.x. - # Awaiting Deno's migration. + # Pulled by deno_core for snapshot / cache serialization. Unmaintained != vulnerable; bincode 2 exists but + # deno_core 0.402 still uses 1.x. Awaiting Deno's migration. "RUSTSEC-2025-0141", # paste 1.0.15 (unmaintained). - # Build-time proc-macro pulled via macro_rules_attribute -> wgpu-core -> deno_webgpu. - # Used at compile time only; no runtime exposure. - # Awaiting wgpu's migration. + # Build-time proc-macro pulled via macro_rules_attribute -> wgpu-core -> deno_webgpu. Used at compile time + # only; no runtime exposure. Awaiting wgpu's migration. "RUSTSEC-2024-0436", # proc-macro-error2 2.0.1 (unmaintained). - # Build-time proc-macro via getset -> neli -> local-ip-address. - # Compile-time only. + # Build-time proc-macro via getset -> neli -> local-ip-address. Compile-time only. "RUSTSEC-2026-0173", # rustls-pemfile 2.2.0 (unmaintained). - # PEM cert parser pulled by deno_native_certs for loading the system CA bundle. - # Unmaintained, not vulnerable; rustls itself has moved to inline pemfile parsing. + # PEM cert parser pulled by deno_native_certs for loading the system CA bundle. Unmaintained, not + # vulnerable; rustls itself has moved to inline pemfile parsing. "RUSTSEC-2025-0134", # hickory-proto 0.25.2 NSEC3 closest-encloser proof unbounded loop (DoS on DNSSEC validation). - # Pulled by deno_net for resolver. - # But the runner never enables DNSSEC validation -- only plain A/AAAA lookups for outbound fetch / WebSocket. - # Validation path is unreachable here. + # Pulled by deno_net for resolver. But the runner never enables DNSSEC validation -- only plain A/AAAA + # lookups for outbound fetch / WebSocket. Validation path is unreachable here. "RUSTSEC-2026-0118", # hickory-proto 0.25.2 O(n^2) name compression during message encode. - # Encoder side, hit only when we *send* a DNS message. - # The runner uses the OS resolver via hickory. - # It never crafts outbound DNS frames with attacker-controlled name lists. + # Encoder side, hit only when we *send* a DNS message. The runner uses the OS resolver via hickory. It + # never crafts outbound DNS frames with attacker-controlled name lists. "RUSTSEC-2026-0119", # rand 0.8.5 -- transitive via cap-rand (wasi-runner) + deno_crypto / deno_fs / deno_node (web-runner). - # Used for non-cryptographic randomness (jitter, salt scratchpads). - # Awaiting upstream Deno + cap-rand migration to rand 0.9. + # Used for non-cryptographic randomness (jitter, salt scratchpads). Awaiting upstream Deno + cap-rand + # migration to rand 0.9. "RUSTSEC-2026-0097", # rsa 0.9.10 Marvin Attack timing sidechannel on RSA decryption. - # Pulled by deno_crypto + deno_node_crypto. - # The runner doesn't hold private RSA keys or decrypt RSA payloads. - # Only TLS server cert verification, which uses *signature* checks, no decryption. - # Not reachable unless guest JS explicitly imports crypto.subtle and performs RSA-OAEP decrypt with a private key. - # Our modules don't. + # Pulled by deno_crypto + deno_node_crypto. The runner doesn't hold private RSA keys or decrypt RSA + # payloads. Only TLS server cert verification, which uses *signature* checks, no decryption. Not reachable + # unless guest JS explicitly imports crypto.subtle and performs RSA-OAEP decrypt with a private key. Our + # modules don't. "RUSTSEC-2023-0071", # rustls-webpki 0.102.8 -- four advisories, all unreachable here. - # All pinned by deno_fetch ^0.102 / deno_tls ^0.236 (under deno_runtime). - # The runner uses rustls only for HTTPS *client* verification against system roots: + # All pinned by deno_fetch ^0.102 / deno_tls ^0.236 (under deno_runtime). The runner uses rustls only for + # HTTPS *client* verification against system roots: # * 2026-0049, 2026-0104 (CRL-parsing bugs) -- we never load CRLs into ClientConfig. - # * 2026-0098, 2026-0099: name-constraint bypass for URI / wildcard certs. - # Relevant only when validating certs whose subject names include URI SANs. - # Or certs wildcard-issued under name-constrained intermediates. - # Our HTTPS endpoints don't. + # * 2026-0098, 2026-0099: name-constraint bypass for URI / wildcard certs. Relevant only when validating + # certs whose subject names include URI SANs. Or certs wildcard-issued under name-constrained + # intermediates. Our HTTPS endpoints don't. # Drop all four when deno_fetch upgrades to rustls-webpki >=0.103.13. "RUSTSEC-2026-0049", "RUSTSEC-2026-0098", @@ -67,13 +58,11 @@ ignore = [ # pyo3 0.28.3 -- two advisories, both unreachable in the embedded runner. # Pulled only by et-ws-pyo3-runner (our sole pyo3 consumer). # Fixed in pyo3 0.29; we stay on 0.28 for now and drop both on upgrade. - # * 2026-0176 (OOB read in PyList/PyTuple iterator nth/nth_back). - # The runner never calls nth/nth_back on a Python sequence iterator. - # It only inserts into sys.path and reads module return values. - # * 2026-0177 (missing Sync bound on PyCFunction::new_closure). - # The runner exposes its host API as #[pyclass]/#[pymethods] (WsSender, WsStorage). - # It never uses PyCFunction::new_closure. - # It drives a single GIL-bound interpreter (not free-threaded), so no closure runs concurrently. + # * 2026-0176 (OOB read in PyList/PyTuple iterator nth/nth_back). The runner never calls nth/nth_back on + # a Python sequence iterator. It only inserts into sys.path and reads module return values. + # * 2026-0177 (missing Sync bound on PyCFunction::new_closure). The runner exposes its host API as + # #[pyclass]/#[pymethods] (WsSender, WsStorage). It never uses PyCFunction::new_closure. It drives a + # single GIL-bound interpreter (not free-threaded), so no closure runs concurrently. "RUSTSEC-2026-0176", "RUSTSEC-2026-0177", ] @@ -81,8 +70,7 @@ ignore = [ [licenses] version = 2 # Compatible with our own `Apache-2.0 OR MIT` workspace license. -# Add a new entry only when a transitive dep introduces a new SPDX expression -# we've reviewed. +# Add a new entry only when a transitive dep introduces a new SPDX expression we've reviewed. allow = [ "Apache-2.0", # cranelift / wasmtime stack -- LLVM-derived code carries the Apache 2.0 text plus an extra patent grant for LLVM. @@ -105,12 +93,10 @@ allow = [ ] # Explicit per-crate clarifications: -# cargo-deny otherwise emits a -# `no-license-field` warning when a crate's manifest uses `license-file` -# instead of an SPDX `license` expression. Each entry below has been verified -# against the on-disk LICENSE in ~/.cargo/registry. The mise `cargo-deny` -# task escalates `no-license-field` to a hard error so a new transitive dep -# without an SPDX expression must land here intentionally, not silently. +# cargo-deny otherwise emits a `no-license-field` warning when a crate's manifest uses `license-file` instead +# of an SPDX `license` expression. Each entry below has been verified against the on-disk LICENSE in +# ~/.cargo/registry. The mise `cargo-deny` task escalates `no-license-field` to a hard error so a new +# transitive dep without an SPDX expression must land here intentionally, not silently. [[licenses.clarify]] crate = "saffron" expression = "BSD-3-Clause" @@ -128,16 +114,16 @@ wildcards = "deny" # The `deny` entries below catch *transitive* uses that the taplo rule can't see. deny = [ # `openssl` and `openssl-sys` link to libssl/libcrypto. - # We use rustls + aws-lc-rs as the single TLS+crypto stack across the workspace. - # Any new transitive dep that drags openssl back in should migrate, not be allowed. - # `openssl-probe` is fine -- it only probes for installed cert store paths, doesn't link to OpenSSL itself. + # We use rustls + aws-lc-rs as the single TLS+crypto stack across the workspace. Any new transitive dep + # that drags openssl back in should migrate, not be allowed. `openssl-probe` is fine -- it only probes for + # installed cert store paths, doesn't link to OpenSSL itself. { crate = "openssl" }, { crate = "openssl-sys" }, # `ring` is forbidden except via the wrappers listed below. - # The rest of the dep graph reaches the same primitives via `aws-lc-rs`. - # Any new transitive parent that drags `ring` back in surfaces as an `unmatched-wrapper` error here. - # We can then decide whether to migrate it to aws-lc-rs or extend the list. - # See https://github.com/EmbarkStudios/cargo-deny/issues/776 for the `wrappers` pattern. + # The rest of the dep graph reaches the same primitives via `aws-lc-rs`. Any new transitive parent that + # drags `ring` back in surfaces as an `unmatched-wrapper` error here. We can then decide whether to migrate + # it to aws-lc-rs or extend the list. See https://github.com/EmbarkStudios/cargo-deny/issues/776 for the + # `wrappers` pattern. # # Current allowed parents: # * `rcgen` -- server-side TLS cert generation in et-ws-server. @@ -148,17 +134,15 @@ deny = [ # Drop once Deno's quinn config switches to the `rustls-aws-lc-rs` feature. { crate = "ring", wrappers = ["quinn-proto", "rcgen", "rustls-webpki"] }, # `ureq` is forbidden -- direct use is blocked by the taplo no-banned-deps schema. - # This entry catches transitive paths the same way as `ring` above. - # No wrappers allowed. - # The only previous parent (`ort-sys` via the `download-binaries` feature) is gone. - # The workspace `ort` dep now stages ONNX Runtime through mise instead. + # This entry catches transitive paths the same way as `ring` above. No wrappers allowed. The only previous + # parent (`ort-sys` via the `download-binaries` feature) is gone. The workspace `ort` dep now stages ONNX + # Runtime through mise instead. { crate = "ureq" }, ] [sources] # Only crates.io. -# New git or alternate-registry deps must be added to -# the allow lists below intentionally. +# New git or alternate-registry deps must be added to the allow lists below intentionally. allow-registry = ["https://github.com/rust-lang/crates.io-index"] unknown-git = "deny" unknown-registry = "deny" diff --git a/config/dprint.jsonc b/config/dprint.jsonc index a4d54c7..76af018 100644 --- a/config/dprint.jsonc +++ b/config/dprint.jsonc @@ -4,16 +4,14 @@ }, "java": {}, "json": {}, - // Match the repo-wide 120 line-length set in .editorconfig and ruff.toml, - // otherwise dprint's bundled ruff would reformat Python files to its - // default and fight with `mise run ruff-fmt`. + // Match the repo-wide 120 line-length set in .editorconfig and ruff.toml, otherwise dprint's bundled ruff would + // reformat Python files to its default and fight with `mise run ruff-fmt`. "ruff": { "lineLength": 120, }, "malva": {}, - // Match the repo-wide 120 line cap; dprint's markdown plugin otherwise - // defaults to 80 and reformats `js` code blocks more aggressively than the - // typescript plugin would on the same source. + // Match the repo-wide 120 line cap; dprint's markdown plugin otherwise defaults to 80 and reformats `js` code + // blocks more aggressively than the typescript plugin would on the same source. "markdown": { "lineWidth": 120, }, @@ -22,8 +20,7 @@ // files without fighting: // - arrowFunction.useParentheses=force -> always `(x) =>`, not `x =>` // - memberExpression.linePerExpression -> break chained `.then().catch()` - // - binaryExpression.operatorPosition=sameLine -> `&&` / `||` trailing - // the previous line, not leading the next + // - binaryExpression.operatorPosition=sameLine -> `&&` / `||` trailing the previous line, not leading the next "typescript": { "arrowFunction.useParentheses": "force", "binaryExpression.operatorPosition": "sameLine", diff --git a/config/hadolint.yaml b/config/hadolint.yaml index b32e681..142779f 100644 --- a/config/hadolint.yaml +++ b/config/hadolint.yaml @@ -3,9 +3,8 @@ # drop one once it no longer applies. ignored: # Pinning apt package versions is brittle. - # They float with the Ubuntu base image + security updates, so an exact pin - # breaks on every base bump. We install distro defaults deliberately -- the - # base FROM line is the version pin. + # They float with the Ubuntu base image + security updates, so an exact pin breaks on every base bump. We install + # distro defaults deliberately -- the base FROM line is the version pin. - DL3008 # Same for dnf (Fedora base). Same rationale as DL3008 above. - DL3041 @@ -14,15 +13,13 @@ ignored: # Same for npm: `npm install -g pnpm` tracks latest; we don't pin it here. - DL3016 # The `curl ... | sh` mise installer doesn't need pipefail on its own line. - # If curl fails, sh gets empty input and the next `mise` command then fails - # anyway, so the pipe failure is caught downstream -- no need to set SHELL -o - # pipefail just for that one line. + # If curl fails, sh gets empty input and the next `mise` command then fails anyway, so the pipe failure is caught + # downstream -- no need to set SHELL -o pipefail just for that one line. - DL4006 # Consecutive RUNs are kept separate on purpose. # For layer caching and so a change to one step doesn't bust the others. - DL3059 # Our RUN <`) -- where -// dprint produced bare `x =>` we now match oxfmt's parenthesised form once -// `oxfmt --write` runs. Drift in the other direction is configured in -// `dprint.jsonc`'s typescript block (arrowFunction.useParentheses=force, -// binaryExpression.operatorPosition=sameLine, memberExpression.linePerExpression). +// oxfmt does NOT expose an arrowParens equivalent (always `(x) =>`) -- where dprint produced bare `x =>` we now +// match oxfmt's parenthesised form once `oxfmt --write` runs. Drift in the other direction is configured in +// `dprint.jsonc`'s typescript block (arrowFunction.useParentheses=force, binaryExpression.operatorPosition=sameLine, +// memberExpression.linePerExpression). // -// Exclude `**/*.toml` -- oxfmt's content-based detection misidentifies -// `[section]`-shaped TOML files (e.g. config/clippy.toml) as JS arrays and -// tries to reformat them. Extension-based filtering would skip these; lacking -// that, we ignore explicitly. +// Exclude `**/*.toml` -- oxfmt's content-based detection misidentifies `[section]`-shaped TOML files +// (e.g. config/clippy.toml) as JS arrays and tries to reformat them. Extension-based filtering would skip these; +// lacking that, we ignore explicitly. { "$schema": "https://raw.githubusercontent.com/oxc-project/oxc/main/npm/oxfmt/configuration_schema.json", "printWidth": 120, diff --git a/config/oxlintrc.jsonc b/config/oxlintrc.jsonc index 9cbd515..4e08781 100644 --- a/config/oxlintrc.jsonc +++ b/config/oxlintrc.jsonc @@ -1,7 +1,6 @@ -// oxlint config. Has to be JSON/JSONC: oxlint's loader (`crates/oxc_linter/ -// src/config/oxlintrc.rs`'s `is_json_ext`) only accepts `.json` / `.jsonc` -// and outright rejects everything else with "Only JSON configuration files -// are supported" -- no YAML, no TOML, no TS despite docs hinting otherwise. +// oxlint config. Has to be JSON/JSONC: oxlint's loader (`crates/oxc_linter/src/config/oxlintrc.rs`'s `is_json_ext`) +// only accepts `.json` / `.jsonc` and outright rejects everything else with "Only JSON configuration files are +// supported" -- no YAML, no TOML, no TS despite docs hinting otherwise. { "$schema": "https://raw.githubusercontent.com/oxc-project/oxc/main/npm/oxlint/configuration_schema.json", "categories": { @@ -14,26 +13,21 @@ "nursery": "off", }, "rules": { - // __ET_* are sentinel globals shared across the deno shim and the - // ws-modules: et-ws-web-runner substitutes the `__ET_HTTP_BASE__` / - // `__ET_WS_URL__` placeholders before exec, and the resulting - // `globalThis.__ET_*` is how the modules read the host base URL. - // Intentional cross-boundary underscore naming, like webpack's - // `__webpack_*` or Python dunders. + // __ET_* are sentinel globals shared across the deno shim and the ws-modules: et-ws-web-runner substitutes the + // `__ET_HTTP_BASE__` / `__ET_WS_URL__` placeholders before exec, and the resulting `globalThis.__ET_*` is how the + // modules read the host base URL. Intentional cross-boundary underscore naming, like webpack's `__webpack_*` or + // Python dunders. "eslint/no-underscore-dangle": ["warn", { "allow": ["__ET_HTTP_BASE", "__ET_WS_URL"] }], - // Worker.postMessage takes no targetOrigin (unlike Window.postMessage); - // the rule can't distinguish, so it false-positives on every worker-side - // / main-side `worker.postMessage({...})` call we have. + // Worker.postMessage takes no targetOrigin (unlike Window.postMessage); the rule can't distinguish, so it + // false-positives on every worker-side / main-side `worker.postMessage({...})` call we have. "unicorn/require-post-message-target-origin": "off", }, "overrides": [ { - // shim.js installs DOM stubs (Window / HTMLElement / HTMLCanvasElement / - // Image classes, `obj.onload = null` property inits) so Deno-hosted - // wasm-bindgen modules see a browser-shaped global. The style rules - // below conflict with that design: stub classes MUST exist as classes - // for `instanceof`, on-handler properties MUST exist as settable - // null-initialised fields. + // shim.js installs DOM stubs (Window / HTMLElement / HTMLCanvasElement / Image classes, `obj.onload = null` + // property inits) so Deno-hosted wasm-bindgen modules see a browser-shaped global. The style rules below + // conflict with that design: stub classes MUST exist as classes for `instanceof`, on-handler properties MUST + // exist as settable null-initialised fields. "files": ["**/services/ws-web-runner/src/shim.js"], "rules": { "typescript/no-extraneous-class": "off", @@ -42,15 +36,12 @@ }, }, { - // ws-modules' committed shim entry points (`pkg/et_ws_*.js`, hand- - // written bridges between the host page/worker and each module's - // wasm/JS payload) plus the ws-server static page (`static/*.js`) - // intentionally use the `on =` pattern -- it matches the - // wasm-bindgen / worker convention, and there's only ever one - // handler. `no-await-in-loop` warnings here are also intentional - // serialised sequences (init -> message-loop ordering matters). - // `consistent-function-scoping` flags inline closures that exist - // for readability, not to capture state. + // ws-modules' committed shim entry points (`pkg/et_ws_*.js`, hand-written bridges between the host page/worker + // and each module's wasm/JS payload) plus the ws-server static page (`static/*.js`) intentionally use the + // `on =` pattern -- it matches the wasm-bindgen / worker convention, and there's only ever one handler. + // `no-await-in-loop` warnings here are also intentional serialised sequences (init -> message-loop ordering + // matters). `consistent-function-scoping` flags inline closures that exist for readability, not to capture + // state. "files": ["**/services/ws-modules/*/pkg/*.js", "**/services/ws-server/static/*.js"], "rules": { "eslint/no-await-in-loop": "off", diff --git a/config/regal.yaml b/config/regal.yaml index acf9e19..1d33f72 100644 --- a/config/regal.yaml +++ b/config/regal.yaml @@ -1,12 +1,10 @@ # Regal config for the conftest policy tree at config/conftest/policy/. # Disables three rules that conflict with conftest's architecture: -# - style.messy-rule: conftest's idiomatic pattern is several `deny contains -# msg if { ... }` rules per concern; merging into one rule destroys -# per-condition readability. -# - style.external-reference: data.. imports across our policy -# files are intentional (e.g. dockerfile.rego reuses mise.version_drift). -# - idiomatic.no-defined-entrypoint: conftest reaches `deny` rules directly, -# no OPA `entrypoint` annotation needed. +# - style.messy-rule: conftest's idiomatic pattern is several `deny contains msg if { ... }` rules per concern; +# merging into one rule destroys per-condition readability. +# - style.external-reference: data.. imports across our policy files are intentional (e.g. dockerfile.rego +# reuses mise.version_drift). +# - idiomatic.no-defined-entrypoint: conftest reaches `deny` rules directly, no OPA `entrypoint` annotation needed. rules: style: messy-rule: diff --git a/config/semgrep/comment-summary-line.yaml b/config/semgrep/comment-summary-line.yaml index 3b6f6ba..9adcaa5 100644 --- a/config/semgrep/comment-summary-line.yaml +++ b/config/semgrep/comment-summary-line.yaml @@ -8,19 +8,15 @@ rules: - "Dockerfile*" exclude: # Skip machine-emitted TOML/YAML (int-gen / regen output). - # Hand-edits would drift on the next regeneration, so the comment - # style there is the generator's job, not ours. + # Hand-edits would drift on the next regeneration, so the comment style there is the generator's job, not ours. - "/generated/**" - "/verification/**" # Flag the FIRST line of a multi-line `#` comment block that lacks a closing full stop. - # The block-start anchor is the preceding line, consumed into the match: - # either start-of-file (`\A`) or a line that is blank or whose first - # non-whitespace char is not `#` (`[^#\s]`). Keying off the first - # non-whitespace char -- not the first raw char -- is what stops an - # INDENTED comment line (whose leading whitespace would otherwise read as - # a non-comment line) from anchoring a spurious match: interior - # continuation lines, which need not end in a period, stay unflagged - # regardless of indentation. The trailing `[^.\n:]` is the offending final + # The block-start anchor is the preceding line, consumed into the match: either start-of-file (`\A`) or a line + # that is blank or whose first non-whitespace char is not `#` (`[^#\s]`). Keying off the first non-whitespace + # char -- not the first raw char -- is what stops an INDENTED comment line (whose leading whitespace would + # otherwise read as a non-comment line) from anchoring a spurious match: interior continuation lines, which need + # not end in a period, stay unflagged regardless of indentation. The trailing `[^.\n:]` is the offending final # character of the summary line. pattern-regex: '(?m)(?:\A|^[ \t]*(?:[^#\s][^\n]*)?\n)[ \t]*#[^\n]*[^.\n:]\n[ \t]*#' message: >- diff --git a/config/semgrep/dockerfile-heredoc-set-euo-pipefail.yaml b/config/semgrep/dockerfile-heredoc-set-euo-pipefail.yaml index 5b7a841..461f091 100644 --- a/config/semgrep/dockerfile-heredoc-set-euo-pipefail.yaml +++ b/config/semgrep/dockerfile-heredoc-set-euo-pipefail.yaml @@ -6,12 +6,10 @@ rules: - "**/Dockerfile" - "**/Dockerfile.*" # Match a heredoc RUN whose first body line ISN'T `set -euo pipefail`. - # The dotfile-style `<- Dockerfile RUN heredocs must begin with `set -euo pipefail` as the first diff --git a/config/semgrep/dockerfile-no-banner-comment.yaml b/config/semgrep/dockerfile-no-banner-comment.yaml index f9edf63..49d447f 100644 --- a/config/semgrep/dockerfile-no-banner-comment.yaml +++ b/config/semgrep/dockerfile-no-banner-comment.yaml @@ -5,15 +5,13 @@ rules: include: - "Dockerfile*" # Match a full-line comment framed by dash runs on BOTH sides. - # The `# --- text ---` decorative "banner" divider style. The two-sided - # `-{2,} ... -{2,}` requirement is what distinguishes a banner from an - # ordinary wrapped comment line that merely opens with an em-dash separator + # The `# --- text ---` decorative "banner" divider style. The two-sided `-{2,} ... -{2,}` requirement is what + # distinguishes a banner from an ordinary wrapped comment line that merely opens with an em-dash separator # (`# -- foo`), which has no trailing dash run and is left alone. pattern-regex: '(?m)^[ \t]*#[ \t]*-{2,}.*-{2,}[ \t]*$' # Check-only: no autofix is provided. - # semgrep's `fix-regex` is not applied for generic `pattern-regex` rules in - # the pinned semgrep, so the dash-strip rewrite (`# --- text ---` -> `# text`) - # can't be expressed as an autofix here. + # semgrep's `fix-regex` is not applied for generic `pattern-regex` rules in the pinned semgrep, so the dash-strip + # rewrite (`# --- text ---` -> `# text`) can't be expressed as an autofix here. message: >- Decorative `# --- text ---` banner comments are not allowed in Dockerfiles. Use a plain, concise one-line comment (`# text`) instead: diff --git a/config/semgrep/mise-config.yaml b/config/semgrep/mise-config.yaml index 1a10b4e..c4215aa 100644 --- a/config/semgrep/mise-config.yaml +++ b/config/semgrep/mise-config.yaml @@ -33,14 +33,12 @@ rules: include: - "/.mise/config*.toml" # Matches `[tasks.X.env]` headers that wrap exactly one single-key assignment. - # Followed by exactly one `KEY = VAL` assignment (modulo leading comment - # lines) and terminated by a blank line + next `[` section header. The - # "exactly one" comes from the negative lookahead `(?!.*=)` after the single - # match -- a second assignment within the section blocks the match. The - # lookahead doesn't check inline-form line length (would need code, not a - # regex), so a hit still needs the human to confirm the would-be - # `env = { ... }` fits inside 120 chars before flattening; if it doesn't, - # leave it subtabled and add a `# semgrep-ignore: ...` directive on the header. + # Followed by exactly one `KEY = VAL` assignment (modulo leading comment lines) and terminated by a blank line + + # next `[` section header. The "exactly one" comes from the negative lookahead `(?!.*=)` after the single match + # -- a second assignment within the section blocks the match. The lookahead doesn't check inline-form line length + # (would need code, not a regex), so a hit still needs the human to confirm the would-be `env = { ... }` fits + # inside 120 chars before flattening; if it doesn't, leave it subtabled and add a `# semgrep-ignore: ...` + # directive on the header. pattern-regex: '(?m)^\[tasks\.[^\]]+\.env\]\n(?:#[^\n]*\n)*[A-Z_][A-Z0-9_]*\s*=\s*[^\n]+\n+\[' message: >- `[tasks.X.env]` subtable has a single `KEY = "value"` assignment. Inline diff --git a/config/semgrep/no-non-ascii.yaml b/config/semgrep/no-non-ascii.yaml index b0b9524..e558261 100644 --- a/config/semgrep/no-non-ascii.yaml +++ b/config/semgrep/no-non-ascii.yaml @@ -4,10 +4,9 @@ rules: paths: exclude: # Generated / build-output trees regenerate non-ASCII from their tools. - # The source of truth there is the generator, not a hand edit: clap's - # HELP.md carries a jump glyph; wasm-pack pkg/ bundles and the - # AsyncAPI/OpenAPI/WIT output under generated/ plus the regen-verification - # output under verification/ all reintroduce it. + # The source of truth there is the generator, not a hand edit: clap's HELP.md carries a jump glyph; wasm-pack + # pkg/ bundles and the AsyncAPI/OpenAPI/WIT output under generated/ plus the regen-verification output under + # verification/ all reintroduce it. - "/generated/**" - "/verification/**" - "**/HELP.md" diff --git a/config/semgrep/no-todo.yaml b/config/semgrep/no-todo.yaml index 9d7add0..a701109 100644 --- a/config/semgrep/no-todo.yaml +++ b/config/semgrep/no-todo.yaml @@ -3,10 +3,9 @@ rules: languages: [generic] paths: # Repo-wide. - # Each existing TODO is allowlisted by file path below; new TODOs must - # either resolve before merge or be added here with a one-line - # justification in the surrounding code about why this one is genuinely - # deferred (not just "haven't gotten to it"). + # Each existing TODO is allowlisted by file path below; new TODOs must either resolve before merge or be added + # here with a one-line justification in the surrounding code about why this one is genuinely deferred (not just + # "haven't gotten to it"). exclude: # The TODO that authorises the carve-outs below -- the canonical spot. # Since the source of the TODO is the rename it tracks. diff --git a/config/semgrep/no-trailing-backslash.yaml b/config/semgrep/no-trailing-backslash.yaml index be6cc52..d7a8f31 100644 --- a/config/semgrep/no-trailing-backslash.yaml +++ b/config/semgrep/no-trailing-backslash.yaml @@ -3,16 +3,14 @@ rules: languages: [generic] paths: # Repo-wide. - # The files below already use trailing-backslash continuations and are - # allowlisted; everything else must avoid them (keep the value/statement - # on one line, or use concat!/[vars]/arrays). + # The files below already use trailing-backslash continuations and are allowlisted; everything else must avoid + # them (keep the value/statement on one line, or use concat!/[vars]/arrays). exclude: - "/README.md" - "/utilities/cli/README.md" # `services/ws-server/Dockerfile` still uses `\` RUN line-continuations. - # The top-level `Dockerfile` has migrated to BuildKit HEREDOC RUNs (no - # continuations needed). Drop this entry once ws-server's Dockerfile - # also migrates. + # The top-level `Dockerfile` has migrated to BuildKit HEREDOC RUNs (no continuations needed). Drop this entry + # once ws-server's Dockerfile also migrates. - "/services/ws-server/Dockerfile" # Generated deployment artifacts (regen-verification output). - "/verification/**/mise.toml" diff --git a/config/semgrep/prefer-yaml-toml.yaml b/config/semgrep/prefer-yaml-toml.yaml index dc5b215..6dd0585 100644 --- a/config/semgrep/prefer-yaml-toml.yaml +++ b/config/semgrep/prefer-yaml-toml.yaml @@ -8,9 +8,8 @@ rules: - "*.jsonl" exclude: # Formats whose tooling mandates JSON/JSONC -- everything else should be YAML or TOML. - # Add a file here only if its tool can't read YAML/TOML; if the tool - # also supports JSONC, prefer that extension over plain JSON so the - # config can carry inline comments. + # Add a file here only if its tool can't read YAML/TOML; if the tool also supports JSONC, prefer that + # extension over plain JSON so the config can carry inline comments. - "*.schema.json" # JSON Schema documents - "package.json" # npm manifests - "/.vscode/*.json" # editor config diff --git a/config/taplo.toml b/config/taplo.toml index f994cc5..f68b254 100644 --- a/config/taplo.toml +++ b/config/taplo.toml @@ -1,11 +1,9 @@ # Build output and external checkouts are off-limits. -# Both are gitignored, but taplo's baseline `taplo lint` / `format` don't honour -# .gitignore: `target/` holds scratch + build artifacts (and auto-associates any -# stray `Cargo.toml` with the SchemaStore Cargo schema), and `data/` holds external -# upstream repo clones whose TOML files we must never reformat. target/ is also -# CLAUDE.md's scratch area, so scratch TOML is skipped here too -- to lint or test -# a scratch TOML, pipe it through `taplo format -` / `taplo lint -` (stdin has no -# path to exclude). +# Both are gitignored, but taplo's baseline `taplo lint` / `format` don't honour .gitignore: `target/` holds +# scratch + build artifacts (and auto-associates any stray `Cargo.toml` with the SchemaStore Cargo schema), +# and `data/` holds external upstream repo clones whose TOML files we must never reformat. target/ is also +# CLAUDE.md's scratch area, so scratch TOML is skipped here too -- to lint or test a scratch TOML, pipe it +# through `taplo format -` / `taplo lint -` (stdin has no path to exclude). exclude = ["data/**", "target/**"] [formatting] @@ -24,28 +22,27 @@ formatting = { reorder_arrays = false } keys = ["workspace"] # Forbid `path = "..."` dependencies in non-root Cargo.toml files. -# The root Cargo.toml's `[workspace.dependencies]` is the only legitimate place for -# path deps; member crates reference them via `dep.workspace = true`. +# The root Cargo.toml's `[workspace.dependencies]` is the only legitimate place for path deps; member crates +# reference them via `dep.workspace = true`. [[rule]] exclude = ["Cargo.toml"] include = ["**/Cargo.toml"] schema = { path = "config/taplo/no-path-deps.schema.json" } # Forbid wildcard version strings and inline `git = ...` deps inside dep tables. -# This covers wildcards (`"*"`, `"1.*"`, ...) and inline `git = ...` deps inside -# `[dependencies]` / `[dev-dependencies]`. Workspace deps are the canonical home -# for pins; wildcards are also typically resolver-fragile in lockless builds. -# `[build-dependencies]` isn't covered yet. +# This covers wildcards (`"*"`, `"1.*"`, ...) and inline `git = ...` deps inside `[dependencies]` / +# `[dev-dependencies]`. Workspace deps are the canonical home for pins; wildcards are also typically +# resolver-fragile in lockless builds. `[build-dependencies]` isn't covered yet. [[rule]] exclude = ["Cargo.toml"] include = ["**/Cargo.toml"] schema = { path = "config/taplo/no-wildcard-or-git-deps.schema.json" } # Every entry in `[dependencies]` and `[dev-dependencies]` must use `.workspace = true`. -# This keeps shared pins in one place ([workspace.dependencies]) and member crates -# don't drift onto local versions. `[build-dependencies]` isn't covered yet. No -# per-crate exemptions -- `generated/rust-rest/Cargo.toml` is exempt from the lint -# rule below but its `[dependencies]` still inherit normally. +# This keeps shared pins in one place ([workspace.dependencies]) and member crates don't drift onto local +# versions. `[build-dependencies]` isn't covered yet. No per-crate exemptions -- +# `generated/rust-rest/Cargo.toml` is exempt from the lint rule below but its `[dependencies]` still inherit +# normally. [[rule]] exclude = ["Cargo.toml"] include = ["**/Cargo.toml"] @@ -59,9 +56,8 @@ include = ["**/Cargo.toml"] schema = { path = "config/taplo/no-banned-deps.schema.json" } # Require `[lib] doctest = false` for every member crate with a [lib]. -# Pairs with the `no-doctest` ast-grep rule: that one bans ``` fenced -# blocks inside doc comments; this one disables the doctest harness at -# the crate level so the prohibition has cargo-level parity. Runnable +# Pairs with the `no-doctest` ast-grep rule: that one bans ``` fenced blocks inside doc comments; this one +# disables the doctest harness at the crate level so the prohibition has cargo-level parity. Runnable # examples belong in `tests/` files (each with `#![cfg(test)]`). [[rule]] exclude = ["Cargo.toml"] @@ -69,17 +65,16 @@ include = ["**/Cargo.toml"] schema = { path = "config/taplo/require-lib-doctest-false.schema.json" } # Require `[lints] workspace = true` in every member Cargo.toml. -# This makes the workspace's `[workspace.lints.*]` tables apply. Without it, -# workspace rustc/clippy lints are silently ignored for that crate. +# This makes the workspace's `[workspace.lints.*]` tables apply. Without it, workspace rustc/clippy lints are +# silently ignored for that crate. # # Exempt: -# - workspace root (`Cargo.toml`): configures lints under -# `[workspace.lints.*]` directly, has no `[lints]` table of its own. -# - `generated/rust-rest/Cargo.toml`: progenitor's emitted `src/lib.rs` -# contains patterns our workspace lint table flags (e.g. doc-comment -# shapes that trip rustdoc's invalid-code-block lint). Inheriting -# `[lints] workspace = true` would fail the build on the generated -# source. The exemption is about the lint table, not about the deps. +# - workspace root (`Cargo.toml`): configures lints under `[workspace.lints.*]` directly, has no `[lints]` +# table of its own. +# - `generated/rust-rest/Cargo.toml`: progenitor's emitted `src/lib.rs` contains patterns our workspace +# lint table flags (e.g. doc-comment shapes that trip rustdoc's invalid-code-block lint). Inheriting +# `[lints] workspace = true` would fail the build on the generated source. The exemption is about the +# lint table, not about the deps. [[rule]] exclude = ["Cargo.toml", "generated/rust-rest/Cargo.toml"] include = ["**/Cargo.toml"] diff --git a/config/upstream-cache/data.toml b/config/upstream-cache/data.toml index 77a3e96..b608b37 100644 --- a/config/upstream-cache/data.toml +++ b/config/upstream-cache/data.toml @@ -1,29 +1,22 @@ # Source-of-truth metadata for assets fetched from upstream releases. -# Covers edge-toolkit/core's *-v releases (and any other upstream URL pinned -# by an `*_asset` var in .mise/config*.toml). For each asset we record: +# Covers edge-toolkit/core's *-v releases (and any other upstream URL pinned by an `*_asset` var in +# .mise/config*.toml). For each asset we record: # -# - sha256: integrity check; the release page also ships .sha256 -# sidecars for human inspection, but this file is what -# `fetch-*-rclone` tasks verify against, so the guarantee -# is chained to git history rather than to (re-uploadable) -# release storage. -# - url: the canonical download URL (where consumers fetch the -# asset from -- usually our edge-toolkit/core release). -# One source of truth; tasks compose the same URL via -# `*_asset` + the release tag, but this is the explicit -# value the rego policy validates against. -# - upstream: the upstream project / dataset the asset was built from. -# Lets a reader trace what they're downloading without -# having to grep the rest of the repo. -# - license: SPDX expression for the upstream content. Bump alongside -# `upstream` whenever upstream re-licenses. +# - sha256: integrity check; the release page also ships .sha256 sidecars for human inspection, but this +# file is what `fetch-*-rclone` tasks verify against, so the guarantee is chained to git history +# rather than to (re-uploadable) release storage. +# - url: the canonical download URL (where consumers fetch the asset from -- usually our +# edge-toolkit/core release). One source of truth; tasks compose the same URL via `*_asset` + +# the release tag, but this is the explicit value the rego policy validates against. +# - upstream: the upstream project / dataset the asset was built from. Lets a reader trace what they're +# downloading without having to grep the rest of the repo. +# - license: SPDX expression for the upstream content. Bump alongside `upstream` whenever upstream +# re-licenses. # -# config/conftest/policy/checksums/checksums.rego enforces a -# bidirectional cross-reference: every `_asset` var in -# .mise/config*.toml must have a matching `[asset.]` table -# here, and vice versa. Filename keys pin the upstream commit SHA -# (short) or version; bump in lockstep with the var and the release -# tag. +# config/conftest/policy/checksums/checksums.rego enforces a bidirectional cross-reference: every +# `_asset` var in .mise/config*.toml must have a matching `[asset.]` table here, and vice +# versa. Filename keys pin the upstream commit SHA (short) or version; bump in lockstep with the var and the +# release tag. [asset."RetinaFace_int-7b69507.onnx"] license = "Apache-2.0" @@ -38,11 +31,10 @@ upstream = "https://github.com/RustPython/RustPython" url = "https://github.com/edge-toolkit/core/releases/download/rp-v1/rustpython-wasm-08a1d6f.tar.gz" # Upstream-cache entries below are tarballs we build + host. -# They cover tools that either have no Windows binary upstream (augeas, gnupg-w32) -# or whose upstream cargo: source-build fails on our target host (dart-typegen -# against the gnullvm Windows toolchain). The sha256 field is "" until -# the first upload lands -- the maintainer fills the real value in by -# reading the .sha256 sidecar from the release. +# They cover tools that either have no Windows binary upstream (augeas, gnupg-w32) or whose upstream cargo: +# source-build fails on our target host (dart-typegen against the gnullvm Windows toolchain). The sha256 +# field is "" until the first upload lands -- the maintainer fills the real value in by reading the .sha256 +# sidecar from the release. [asset."1.14.1-x86_64-pc-windows-mingw.tar.gz"] license = "LGPL-2.1+" @@ -51,9 +43,9 @@ upstream = "https://github.com/hercules-team/augeas" url = "https://github.com/edge-toolkit/core/releases/download/augeas-v1/1.14.1-x86_64-pc-windows-mingw.tar.gz" # Non-Windows augeas builds, from the same jayvdb/augeas `win` branch as the Windows tarball above. -# Published to the same augeas-v1 release. Synthetic filename keyed by rustc triple -# because the released filename already encodes the triple. shas filled in by the -# maintainer after the upstream-cache.yaml augeas job runs and uploads each tarball. +# Published to the same augeas-v1 release. Synthetic filename keyed by rustc triple because the released +# filename already encodes the triple. shas filled in by the maintainer after the upstream-cache.yaml augeas +# job runs and uploads each tarball. [asset."1.14.1-x86_64-unknown-linux-gnu.tar.xz"] license = "LGPL-2.1+" sha256 = "fc6345fb8ffb84e1f57f9144aacc059aa13687f6912ffe26f90713c771892ec6" diff --git a/config/zizmor.yaml b/config/zizmor.yaml index 29de93c..a437c44 100644 --- a/config/zizmor.yaml +++ b/config/zizmor.yaml @@ -1,8 +1,7 @@ # zizmor GitHub Actions audit config, applied via `mise run zizmor-check`. # -# We pin actions by tag (`@v4`), not by commit hash: tags are readable and -# Dependabot keeps them current. So the `unpinned-uses` audit's default -# hash-pin policy is turned off here -- `any` accepts a tag/branch ref. +# We pin actions by tag (`@v4`), not by commit hash: tags are readable and Dependabot keeps them current. So the +# `unpinned-uses` audit's default hash-pin policy is turned off here -- `any` accepts a tag/branch ref. rules: unpinned-uses: config: @@ -10,7 +9,7 @@ rules: "*": any obfuscation: # test.yaml's matrix `include` is built with `fromJSON(... format(...))`. - # Precisely because the OS list depends on `github.event_name` / the - # workflow_dispatch input -- it cannot "be reduced to a constant". + # Precisely because the OS list depends on `github.event_name` / the workflow_dispatch input -- it cannot + # "be reduced to a constant". ignore: - test.yaml diff --git a/ruff.toml b/ruff.toml index ed3c68f..7dcf38c 100644 --- a/ruff.toml +++ b/ruff.toml @@ -1,13 +1,11 @@ # Kept at the repo root on purpose (unlike config/deny.toml, config/osv-scanner.toml, config/taplo.toml). -# Editors/IDEs (the VS Code Ruff extension, PyCharm) and pre-commit auto-discover -# ruff config only as a root ruff.toml / .ruff.toml / pyproject.toml. ruff has no -# config-path env var, so moving this under config/ would silently drop every -# editor back to ruff's defaults (line-length 88, no isort). The mise tasks could +# Editors/IDEs (the VS Code Ruff extension, PyCharm) and pre-commit auto-discover ruff config only as a root +# ruff.toml / .ruff.toml / pyproject.toml. ruff has no config-path env var, so moving this under config/ +# would silently drop every editor back to ruff's defaults (line-length 88, no isort). The mise tasks could # pass --config, but the live editor experience can't, so it stays here. # -# Match the repo-wide 120 line-length set in .editorconfig. Without this, ruff -# would use its default of 88, so files that ruff considered "already -# formatted" could still trip the editorconfig-check. +# Match the repo-wide 120 line-length set in .editorconfig. Without this, ruff would use its default of 88, so files +# that ruff considered "already formatted" could still trip the editorconfig-check. line-length = 120 # Exclude componentize-py's generated bindings, which we don't own and don't ship. @@ -19,10 +17,9 @@ extend-exclude = ["componentize_py_async_support", "wit_world"] [lint] # On top of the default rule set (E, F): -# "I" -- isort import sorting. `ruff format` only handles whitespace; the -# "I" rules give deterministic grouping. datamodel-codegen's ruff formatter -# doesn't sort imports, so without this the generated messages.py drifts -# between regens. +# "I" -- isort import sorting. `ruff format` only handles whitespace; the "I" rules give deterministic +# grouping. datamodel-codegen's ruff formatter doesn't sort imports, so without this the generated +# messages.py drifts between regens. # "D" -- pydocstyle (PEP 257) docstring checks; see [lint.pydocstyle]. # Use `extend-select`, not `select`, so we keep the default F401 (unused-import) # rule that openapi-python-client relies on internally -- it generates From aa401b2d4a9e3c2591803efccb195b188cb40040 Mon Sep 17 00:00:00 2001 From: John Vandenberg Date: Mon, 22 Jun 2026 18:02:37 +0800 Subject: [PATCH 131/133] more reflow --- CLAUDE.md | 20 ++++- .../gha-no-step-shell-upstream-cache.yaml | 18 ++++ config/conftest/policy/cargo/cargo.rego | 44 +++++----- .../conftest/policy/checksums/checksums.rego | 10 +-- .../policy/cross/wasm-bindgen-sync.rego | 10 +-- .../policy/dockerfile/dockerfile.rego | 28 +++---- .../dockerfile_heredoc.rego | 14 ++-- config/conftest/policy/gha/gha.rego | 65 ++++++++------- .../policy/gha_action/gha_action.rego | 21 ++--- .../policy/gha_combined/gha_combined.rego | 10 +-- config/conftest/policy/mise/mise.rego | 83 ++++++++++--------- .../no_trailing_backslash.rego | 13 +-- .../conftest/policy/pyproject/pyproject.rego | 14 ++-- config/semgrep/cargo-toml.yaml | 5 +- config/semgrep/comment-summary-line.yaml | 3 +- .../dockerfile-heredoc-set-euo-pipefail.yaml | 9 +- .../semgrep/dockerfile-no-banner-comment.yaml | 5 +- config/semgrep/mise-config.yaml | 21 ++--- config/semgrep/no-non-ascii.yaml | 10 +-- config/semgrep/no-todo.yaml | 8 +- config/semgrep/no-trailing-backslash.yaml | 10 +-- config/semgrep/prefer-yaml-toml.yaml | 6 +- .../mise-cargo-backend-allowlist.schema.json | 2 +- 23 files changed, 228 insertions(+), 201 deletions(-) create mode 100644 config/ast-grep/rules/gha-no-step-shell-upstream-cache.yaml diff --git a/CLAUDE.md b/CLAUDE.md index c5703a6..88424c9 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -59,6 +59,22 @@ markdown tables, JSON schemas, etc., keep each line under the limit on the first follow-up fix-up pass. The most common offenders are: long `reason = "..."` strings on lint attributes, JSON `description` fields, markdown table rows, and CI-task `description` fields. +Under the limit is the floor, not the target -- write prose and comments to **maximise** use of the 120-char width +repo-wide. Fill each line close to 120 before wrapping rather than breaking early at 60-80 chars; a paragraph that +wraps narrowly wastes vertical space and reads as ragged. This applies to every prose surface: `#`/`//`/`--` +comments, Rust doc comments and `reason = "..."` strings, YAML folded `>-` blocks (semgrep `message:` fields, lint +rule messages), markdown, and Rego/policy headers. When you touch a block that wraps narrowly, reflow it to fill +the width. + +## Keep everything ASCII + +Non-ASCII characters are **banned repo-wide** (enforced by the `no-non-ascii` semgrep rule). Write ASCII on the +first draft -- never paste a glyph and expect a fix-up pass. Use the ASCII spelling instead: `--` for an em-dash, +`->` for an arrow, `...` for an ellipsis, `<=`/`>=`/`!=`/`^2` for math glyphs, and straight `'`/`"` quotes for +curly quotes. ASCII keeps terminals, log scrapers, and `grep` reading the same bytes the editor shows. The only +exemptions are generator-owned trees (`generated/`, `verification/`, `**/HELP.md`, `**/pkg/`), license texts, and +binary assets -- everything you hand-write is bound by the ban. + ## No trailing-backslash line continuations Trailing-backslash line continuations (a line ending in `\` to join with the next) are **banned everywhere** in @@ -478,8 +494,8 @@ pattern is the same for every cache entry; copy the `rustpython` / `augeas` / `d platform: `url`, `checksum = "sha256:..."`, `version`. The publish step should emit a `.sha256` sidecar alongside the tarball so the maintainer can paste the value into config.toml. (The rustpython - publish task auto-edits config.toml via `cargo:toml-cli` when - available -- same pattern is fine for new entries.) + publish task auto-edits config.toml -- same pattern is fine for new + entries.) 5. **Asset metadata source of truth: `config/upstream-cache/data.toml`.** Every tarball / wheel / model file fetched from one of our releases diff --git a/config/ast-grep/rules/gha-no-step-shell-upstream-cache.yaml b/config/ast-grep/rules/gha-no-step-shell-upstream-cache.yaml new file mode 100644 index 0000000..8da4121 --- /dev/null +++ b/config/ast-grep/rules/gha-no-step-shell-upstream-cache.yaml @@ -0,0 +1,18 @@ +id: gha-no-step-shell-upstream-cache +language: yaml +severity: error +message: | + In upstream-cache.yaml the only permitted per-step `shell:` override is + `shell: msys2 {0}` -- the wrapper msys2/setup-msys2 requires for the augeas + Windows build to run inside its MINGW64 env (Git Bash isn't a substitute). + Any other shell override must use the workflow default bash. +files: + - .github/workflows/upstream-cache.yaml +rule: + all: + - pattern: "shell: $VALUE" + - inside: + kind: block_sequence + stopBy: end + - not: + regex: '^shell:\s*msys2\b' diff --git a/config/conftest/policy/cargo/cargo.rego b/config/conftest/policy/cargo/cargo.rego index 638e33d..d155aa1 100644 --- a/config/conftest/policy/cargo/cargo.rego +++ b/config/conftest/policy/cargo/cargo.rego @@ -1,6 +1,6 @@ -# Cargo.toml policy, evaluated over conftest's `--combine` input (an array of {path, contents}). Run with -# `--namespace cargo` (or `--all-namespaces`). Paths come from `git ls-files`, so the workspace root is exactly -# "Cargo.toml" and any other match is a member crate. +# Cargo.toml policy, evaluated over conftest's `--combine` input (an array of {path, contents}). +# Run with `--namespace cargo` (or `--all-namespaces`). Paths come from `git ls-files`, so the workspace root is +# exactly "Cargo.toml" and any other match is a member crate. package cargo is_member(file) if { @@ -30,8 +30,8 @@ dep contains [file.path, name, spec] if { some name, spec in tgt[table] } -# Banned crates -> rejection reason. Members must use workspace = true, so the root's [workspace.dependencies] is the -# only place a ban can bite. +# Banned crates -> rejection reason. +# Members must use workspace = true, so the root's [workspace.dependencies] is the only place a ban can bite. banned := { "anyhow": "define a thiserror enum instead", "openssl": "use rustls + aws-lc-rs -- one TLS/crypto stack only", @@ -47,8 +47,8 @@ deny contains msg if { msg := sprintf("%s: banned dependency %q -- %s", [path, name, reason]) } -# Member crates: no path deps, no wildcard versions, no inline git deps. Pins live in the root -# [workspace.dependencies]; members reference them via workspace = true. +# Member crates: no path deps, no wildcard versions, no inline git deps. +# Pins live in the root [workspace.dependencies]; members reference them via workspace = true. deny contains msg if { some [path, name, spec] in dep path != "Cargo.toml" @@ -82,8 +82,8 @@ wildcard(spec) if { contains(spec.version, "*") } -# Member [dependencies]/[dev-dependencies] must inherit via workspace = true so pins stay in -# [workspace.dependencies]. (build-dependencies not covered yet.) +# Member [dependencies]/[dev-dependencies] must inherit via workspace = true so pins stay in [workspace.dependencies]. +# (build-dependencies not covered yet.) deny contains msg if { some file in input is_member(file) @@ -93,8 +93,8 @@ deny contains msg if { msg := sprintf("%s: dependency %q must reference [workspace.dependencies] via workspace = true", [file.path, name]) } -# A crate with a [lib] must disable the doctest harness (runnable examples belong in tests/ files) and must not rename -# the lib (keep it the package name). +# A crate with a [lib] must disable the doctest harness and must not rename the lib. +# Runnable examples belong in tests/ files; keep the lib name as the package name. deny contains msg if { some file in input is_member(file) @@ -110,8 +110,8 @@ deny contains msg if { msg := sprintf("%s: [lib] must not set name (keep it the package name)", [file.path]) } -# Every member must inherit the workspace lint tables. generated/rust-rest is exempt: progenitor's emitted source -# trips lints the workspace table denies. +# Every member must inherit the workspace lint tables. +# generated/rust-rest is exempt: progenitor's emitted source trips lints the workspace table denies. deny contains msg if { some file in input is_member(file) @@ -120,8 +120,8 @@ deny contains msg if { msg := sprintf("%s: add [lints] workspace = true", [file.path]) } -# Every crate must be a registered workspace member: its directory must appear in the root manifest's explicit -# [workspace].members list (no orphan crates). +# Every crate must be a registered workspace member, with no orphan crates. +# Its directory must appear in the root manifest's explicit [workspace].members list. workspace_member contains m if { some file in input file.path == "Cargo.toml" @@ -136,8 +136,8 @@ deny contains msg if { msg := sprintf("%s: crate is not registered in the root [workspace].members", [file.path]) } -# Shared [package] metadata must be inherited from [workspace.package] via -# `.workspace = true`, so the values stay defined in exactly one place. +# Shared [package] metadata must be inherited from [workspace.package] via `.workspace = true`. +# This keeps the values defined in exactly one place. inherited_package_field := {"edition", "license", "repository"} deny contains msg if { @@ -174,9 +174,9 @@ deny contains msg if { msg := sprintf("%s: dependency %q has an empty features = []; remove it", [path, name]) } -# A feature must not share its name with a dependency: it shadows the implicit feature an optional dep creates and is -# confusing. generated/rust-rest is exempt -- its generator emits a `tracing` feature beside a (non-optional) -# `tracing` dep. +# A feature must not share its name with a dependency. +# Such a name shadows the implicit feature an optional dep creates and is confusing. generated/rust-rest is exempt -- +# its generator emits a `tracing` feature beside a (non-optional) `tracing` dep. is_dep_name(file, name) if { some table in {"dependencies", "dev-dependencies", "build-dependencies"} file.contents[table][name] @@ -191,8 +191,8 @@ deny contains msg if { msg := sprintf("%s: feature %q shares its name with a dependency; rename it", [file.path, feat]) } -# Dependency overrides ([patch]/[replace]) belong in the root manifest, where -# they apply workspace-wide and stay in one place; a member can't override deps. +# Dependency overrides ([patch]/[replace]) belong in the root manifest, not a member crate. +# There they apply workspace-wide and stay in one place; a member can't override deps. deny contains msg if { some file in input is_member(file) diff --git a/config/conftest/policy/checksums/checksums.rego b/config/conftest/policy/checksums/checksums.rego index f8b3245..dd3ef76 100644 --- a/config/conftest/policy/checksums/checksums.rego +++ b/config/conftest/policy/checksums/checksums.rego @@ -1,6 +1,6 @@ -# Bidirectional cross-reference between .mise/config*.toml's `_asset` vars and -# config/upstream-cache/data.toml's `[asset.]` tables. Run with `--namespace checksums`; the -# conftest-check-toml task includes both file sets in its `--combine` input. +# Bidirectional cross-reference between .mise/config*.toml's `_asset` vars and data.toml's tables. +# Cross-references against config/upstream-cache/data.toml's `[asset.]` tables. Run with +# `--namespace checksums`; the conftest-check-toml task includes both file sets in its `--combine` input. # # Forward: a `*_asset` var must have a matching entry recorded, or its fetch-* task would download something we can't # integrity-verify. Reverse: a `[asset.]` table must be referenced by a `*_asset` var, or it is a stale @@ -51,8 +51,8 @@ deny contains msg if { ) } -# Per-entry shape check: url + license are required strings; sha256 must -# be present (may be empty during bootstrap). +# Per-entry shape check on each asset table. +# url + license are required strings; sha256 must be present (may be empty during bootstrap). deny contains msg if { some file in input is_upstream_cache(file) diff --git a/config/conftest/policy/cross/wasm-bindgen-sync.rego b/config/conftest/policy/cross/wasm-bindgen-sync.rego index be2125c..f57e6ad 100644 --- a/config/conftest/policy/cross/wasm-bindgen-sync.rego +++ b/config/conftest/policy/cross/wasm-bindgen-sync.rego @@ -1,10 +1,10 @@ -# Cross-file invariants, evaluated over conftest's `--combine` input (an array of {path, contents}). Run with -# `--namespace cross`. +# Cross-file invariants, run with `--namespace cross`. +# Evaluated over conftest's `--combine` input (an array of {path, contents}). package cross -# The mise `github:wasm-bindgen` pin must equal the wasm-bindgen package version in Cargo.lock. wasm-pack requires -# the wasm-bindgen CLI to match the crate version exactly; when they match it uses the on-PATH (mise) binary, -# otherwise it downloads its own. Keeping them equal avoids that download. +# The mise `github:wasm-bindgen` pin must equal the wasm-bindgen package version in Cargo.lock. +# wasm-pack requires the wasm-bindgen CLI to match the crate version exactly; when they match it uses the on-PATH +# (mise) binary, otherwise it downloads its own. Keeping them equal avoids that download. mise_pin := pin if { some file in input endswith(file.path, ".mise/config.toml") diff --git a/config/conftest/policy/dockerfile/dockerfile.rego b/config/conftest/policy/dockerfile/dockerfile.rego index 87ef7a0..6d88f23 100644 --- a/config/conftest/policy/dockerfile/dockerfile.rego +++ b/config/conftest/policy/dockerfile/dockerfile.rego @@ -1,16 +1,16 @@ -# Cross-checks for Dockerfile.nanoserver, evaluated over the Dockerfile plus the -# .mise/config*.toml files combined (--combine, auto-detected parsers). Two rules: +# Cross-checks for Dockerfile.nanoserver against the .mise/config*.toml pins, run with `--namespace dockerfile`. +# Evaluated over the Dockerfile plus the .mise/config*.toml files combined (--combine, auto-detected parsers). Two +# rules: # 1. version drift -- the Dockerfile hard-codes mise install-dir paths (LLVMBIN, the busybox shell, the python dir # on PATH) that embed a tool's pinned version; those must match the [tools] pins (reuses mise.rego's matcher). # 2. MISE_DISABLE_TOOLS -- every pipx: tool in the always-loaded config.toml must be disabled here (pipx can't run # on Nano Server), so a newly added pipx tool can't silently break the Windows build. -# Run with `--namespace dockerfile`. package dockerfile import data.mise -# Every string argument of an ENV/RUN instruction in the Dockerfile (its parsed contents is the array of instruction -# objects; the TOMLs parse to objects). +# Collect every string argument of an ENV/RUN instruction in the Dockerfile. +# A Dockerfile's parsed contents is the array of instruction objects; the TOMLs parse to objects. docker_strings contains entry if { some file in input is_array(file.contents) @@ -31,11 +31,11 @@ deny contains msg if { ) } -# The comma-separated tools the Dockerfile asks mise to skip. The value is usually built from multiple ARGs and -# composed into the final ENV (the 120-char limit plus the project's no-backslash rule make a single -# `ENV MISE_DISABLE_TOOLS=...` line impractical), so scan every ARG/ENV value in the file and take whichever tokens -# look like a tool name (contain `:`). The `${VAR}` placeholders the composing ENV holds get rejected by the same -# filter -- only the leaf ARG values supply tools. +# Collect the comma-separated tools the Dockerfile asks mise to skip. +# The value is usually built from multiple ARGs and composed into the final ENV (the 120-char limit plus the +# project's no-backslash rule make a single `ENV MISE_DISABLE_TOOLS=...` line impractical), so scan every ARG/ENV +# value in the file and take whichever tokens look like a tool name (contain `:`). The `${VAR}` placeholders the +# composing ENV holds get rejected by the same filter -- only the leaf ARG values supply tools. disabled_tools contains tool if { some file in input is_array(file.contents) @@ -47,12 +47,12 @@ disabled_tools contains tool if { tool := trim_space(token) } +# Every pipx:* tool in the always-loaded config.toml must be in Dockerfile.nanoserver's MISE_DISABLE_TOOLS. # pipx:* tools can't run on Nano Server (no CPython, and the rustpython-based pipx bootstrap in config.windows.toml's # preinstall is currently broken with STATUS_DLL_NOT_FOUND / STATUS_ENTRYPOINT_NOT_FOUND on Nano's stripped API set, -# hence the ENABLE_RUSTPYTHON_PIPX_BOOTSTRAP env gate that defaults off). Every pipx:* tool in the always-loaded -# config.toml must therefore be in Dockerfile.nanoserver's MISE_DISABLE_TOOLS. A previous canary carve-out -# (pipx:cowsay stayed ENABLED on Nano so the build exercised the bootstrap end-to-end) was dropped together with the -# bootstrap; re-introduce when the bootstrap is fixed upstream and re-enabled by default. +# hence the ENABLE_RUSTPYTHON_PIPX_BOOTSTRAP env gate that defaults off). A previous canary carve-out (pipx:cowsay +# stayed ENABLED on Nano so the build exercised the bootstrap end-to-end) was dropped together with the bootstrap; +# re-introduce when the bootstrap is fixed upstream and re-enabled by default. deny contains msg if { some file in input endswith(file.path, ".mise/config.toml") diff --git a/config/conftest/policy/dockerfile_heredoc/dockerfile_heredoc.rego b/config/conftest/policy/dockerfile_heredoc/dockerfile_heredoc.rego index 144ed1d..4c2d897 100644 --- a/config/conftest/policy/dockerfile_heredoc/dockerfile_heredoc.rego +++ b/config/conftest/policy/dockerfile_heredoc/dockerfile_heredoc.rego @@ -1,8 +1,8 @@ -# Body-first-line rule for Dockerfile heredocs, run separately from the `dockerfile` package because conftest's -# dockerfile parser flattens heredoc bodies out of the AST. The `ignore` parser instead emits every line of the file -# as a separate entry ({Kind, Original, Value}), so we can look at the line index after a `RUN <<...` and check the -# next line is `set -euo pipefail`. Paired with the `dockerfile` package's rule that the heredoc interpreter must be -# `bash`. +# Body-first-line rule for Dockerfile heredocs, requiring `set -euo pipefail` as the first body line of a `RUN <<...`. +# This runs separately from the `dockerfile` package because conftest's dockerfile parser flattens heredoc bodies out +# of the AST. The `ignore` parser instead emits every line of the file as a separate entry ({Kind, Original, Value}), +# so we can look at the line index after a `RUN <<...` and check the next line is `set -euo pipefail`. Paired with the +# `dockerfile` package's rule that the heredoc interpreter must be `bash`. package dockerfile_heredoc # Each parsed file is an array of entries; conftest wraps them per file as input[N].contents = [[entry, entry, ...]]. @@ -19,8 +19,8 @@ deny contains msg if { is_string(entry.Original) regex.match(`^RUN[^\n]*<<[A-Z]`, entry.Original) - # Bounds-check before indexing: an unterminated heredoc at EOF would otherwise trip - # `panic: slice bounds out of range`. + # Bounds-check before indexing to avoid a `panic: slice bounds out of range`. + # An unterminated heredoc at EOF would otherwise trip it. i + 1 < count(items) next := items[i + 1] not next.Original == "set -euo pipefail" diff --git a/config/conftest/policy/gha/gha.rego b/config/conftest/policy/gha/gha.rego index 31a2eb4..3c21c87 100644 --- a/config/conftest/policy/gha/gha.rego +++ b/config/conftest/policy/gha/gha.rego @@ -1,12 +1,13 @@ -# GitHub Actions workflow policy, evaluated per file: conftest reads each .yaml independently (no --combine, since -# there are no cross-file YAML rules). Replicates the gha-* ast-grep rules; running both is fine. Selected with -# `--namespace gha`, so it only runs against workflow YAML, never the TOML inputs. +# GitHub Actions workflow policy, evaluated per file. +# conftest reads each .yaml independently (no --combine, since there are no cross-file YAML rules). Replicates the +# gha-* ast-grep rules; running both is fine. Selected with `--namespace gha`, so it only runs against workflow YAML, +# never the TOML inputs. package gha -# Every workflow must set the default run shell so steps run in bash on every runner (Windows included) without a -# per-step `shell:`. GHA's bare `shell: bash` already implies `bash --noprofile --norc -e -o pipefail {0}`; we expand -# it to the explicit `-euo pipefail` form so every step also gets `-u` (any reference to an unset variable errors out -# -- catches misspelled vars). +# Every workflow must set the default run shell so steps run in bash on every runner without a per-step `shell:`. +# This covers every runner, Windows included. GHA's bare `shell: bash` already implies +# `bash --noprofile --norc -e -o pipefail {0}`; we expand it to the explicit `-euo pipefail` form so every step also +# gets `-u` (any reference to an unset variable errors out -- catches misspelled vars). required_shell := "bash --noprofile --norc -euo pipefail {0}" deny contains msg if { @@ -14,10 +15,10 @@ deny contains msg if { msg := sprintf("workflow must set defaults.run.shell: %q (gets -euo pipefail on every step)", [required_shell]) } -# Steps must not override the shell -- rely on the workflow default (write the step in bash rather than switching to -# PowerShell on Windows runners). One carve-out, scoped to the upstream-cache workflow: `shell: msys2 {0}` (the -# wrapper msys2/setup-msys2 requires for the augeas Windows build to run inside its MINGW64 env -- Git Bash isn't a -# substitute). No other workflow may override a step's shell. +# Steps must not override the shell -- rely on the workflow default. +# Write the step in bash rather than switching to PowerShell on Windows runners. One carve-out, scoped to the +# upstream-cache workflow: `shell: msys2 {0}` (the wrapper msys2/setup-msys2 requires for the augeas Windows build to +# run inside its MINGW64 env -- Git Bash isn't a substitute). No other workflow may override a step's shell. deny contains msg if { some name, job in input.jobs some step in job.steps @@ -31,17 +32,18 @@ step_shell_allowed(step) if { startswith(step.shell, "msys2 ") } -# Every workflow must declare MISE_ENV at the workflow level so the set of loaded language envs is visible at a glance -# (no per-job `mise run print-all-langs` runtime resolution). The matching `Show MISE_ENV` step in each job echoes the -# value into the CI log. +# Every workflow must declare MISE_ENV at the workflow level so the set of loaded language envs is visible at a glance. +# This avoids per-job `mise run print-all-langs` runtime resolution. The matching `Show MISE_ENV` step in each job +# echoes the value into the CI log. deny contains msg if { not input.env.MISE_ENV msg := "workflow must set top-level env.MISE_ENV (the comma-separated language list)" } -# The MISE_ENV VALUE must be the full guest-language set so every CI run exercises the same toolchain footprint as a -# local `mise install`. The docker-windows workflow's Nano lane drops `python` via a matrix-specific build-arg -# override; the workflow-level value still matches the standard. +# The MISE_ENV VALUE must be the full guest-language set. +# This makes every CI run exercise the same toolchain footprint as a local `mise install`. The docker-windows +# workflow's Nano lane drops `python` via a matrix-specific build-arg override; the workflow-level value still matches +# the standard. expected_mise_env := "dart,dotnet,java,js,python,rust,zig" deny contains msg if { @@ -52,17 +54,18 @@ deny contains msg if { ) } -# `DOCKER_BUILDKIT=1` must not be set in the docker-windows workflow: GHA Windows runners ship without the `buildx` -# CLI, so the very first `docker build` aborts with the literal error +# `DOCKER_BUILDKIT=1` must not be set in the docker-windows workflow. +# GHA Windows runners ship without the `buildx` CLI, so the very first `docker build` aborts with the literal error # # ERROR: BuildKit is enabled but the buildx component is missing or broken. # Install the buildx component to build images with BuildKit: # https://docs.docker.com/go/buildx/ # -# (Captured 2026-06-18 on the docker-windows lane.) BuildKit cache mounts (`RUN --mount=type=cache,...`) require -# DOCKER_BUILDKIT=1; until buildx is installable on Windows runners, enabling it just trades a working build for a -# guaranteed fail-fast. Anchored to docker-windows by `input.name` so other workflows are unaffected. Checks all three -# scopes the env can be set from: workflow-level, job-level, step-level. +# Captured 2026-06-18 on the docker-windows lane. +# BuildKit cache mounts (`RUN --mount=type=cache,...`) require DOCKER_BUILDKIT=1; until buildx is installable on +# Windows runners, enabling it just trades a working build for a guaranteed fail-fast. Anchored to docker-windows by +# `input.name` so other workflows are unaffected. Checks all three scopes the env can be set from: workflow-level, +# job-level, step-level. buildkit_error_hint := "GHA Windows runners ship without buildx; build aborts" deny contains msg if { @@ -95,17 +98,17 @@ deny contains msg if { ) } -# When a workflow uses a local composite action (./.github/actions/) and also filters its triggers with a -# paths: list, that list must include the composite action's location. Otherwise edits to the composite action won't -# re-trigger this workflow on a PR that changes it. Workflows without a paths filter already trigger on every PR/push -# and are skipped by this rule. +# A workflow using a local composite action with a paths: trigger filter must list that action's location. +# This covers a workflow that uses a local composite action (./.github/actions/) and also filters its triggers +# with a paths: list. Otherwise edits to the composite action won't re-trigger this workflow on a PR that changes it. +# Workflows without a paths filter already trigger on every PR/push and are skipped by this rule. local_actions_used contains action_dir if { some job in input.jobs some step in job.steps startswith(step.uses, "./.github/actions/") - # Strip the leading "./" to get the repo-relative dir (matches gitignore-style - # path filters in `on..paths`). + # Strip the leading "./" to get the repo-relative dir. + # This matches the gitignore-style path filters in `on..paths`. action_dir := substring(step.uses, 2, -1) } @@ -115,8 +118,8 @@ trigger_paths(event) := paths if { is_array(paths) } -# True if `paths` contains an entry that covers `action_dir` (either the directory glob `/**` or any narrower -# entry under the dir, e.g. `/action.yml`). +# True if `paths` contains an entry that covers `action_dir`. +# The entry is either the directory glob `/**` or any narrower entry under the dir, e.g. `/action.yml`. paths_cover_dir(paths, action_dir) if { some p in paths startswith(p, action_dir) diff --git a/config/conftest/policy/gha_action/gha_action.rego b/config/conftest/policy/gha_action/gha_action.rego index 6c6f52b..cedcbdd 100644 --- a/config/conftest/policy/gha_action/gha_action.rego +++ b/config/conftest/policy/gha_action/gha_action.rego @@ -1,15 +1,16 @@ -# GitHub Actions composite-action policy, evaluated per file over each `.github/actions/*/action.yaml`. Sister to -# `gha`/`gha_combined` (which cover workflow YAML); split into its own package because composite-action schemas differ -# from workflows (no `jobs.*`, `runs.steps[]` instead of `jobs..steps[]`, no `defaults.run.shell` support). -# Selected with `--namespace gha_action`. +# GitHub Actions composite-action policy, evaluated per file over each `.github/actions/*/action.yaml`. +# Sister to `gha`/`gha_combined` (which cover workflow YAML); split into its own package because composite-action +# schemas differ from workflows (no `jobs.*`, `runs.steps[]` instead of `jobs..steps[]`, no `defaults.run.shell` +# support). Selected with `--namespace gha_action`. package gha_action -# Composite-action `run:` steps require an explicit `shell:` -- GHA's `defaults.run.shell` isn't honored inside -# composite actions, so every step has to declare its own shell. Enforce the same explicit form the `gha` rule -# requires for workflow `defaults.run.shell`: `bash --noprofile --norc -euo pipefail {0}`. The GHA shorthand -# `shell: bash` is rejected because it expands to `bash --noprofile --norc -e -o pipefail {0}` (missing `-u`); the -# full form catches misspelled-variable bugs in every step uniformly. PowerShell-specific work that can't be -# expressed in Git Bash on Windows must live in a workflow step (with the workflow-default shell policy) instead. +# Composite-action `run:` steps require an explicit `shell:`. +# GHA's `defaults.run.shell` isn't honored inside composite actions, so every step has to declare its own shell. +# Enforce the same explicit form the `gha` rule requires for workflow `defaults.run.shell`: +# `bash --noprofile --norc -euo pipefail {0}`. The GHA shorthand `shell: bash` is rejected because it expands to +# `bash --noprofile --norc -e -o pipefail {0}` (missing `-u`); the full form catches misspelled-variable bugs in every +# step uniformly. PowerShell-specific work that can't be expressed in Git Bash on Windows must live in a workflow step +# (with the workflow-default shell policy) instead. required_shell := "bash --noprofile --norc -euo pipefail {0}" deny contains msg if { diff --git a/config/conftest/policy/gha_combined/gha_combined.rego b/config/conftest/policy/gha_combined/gha_combined.rego index c6a7ed4..260efce 100644 --- a/config/conftest/policy/gha_combined/gha_combined.rego +++ b/config/conftest/policy/gha_combined/gha_combined.rego @@ -2,9 +2,9 @@ # conftest hands every file in as an array of `{path, contents}`. Per-file rules live in `gha.rego`. package gha_combined -# Every workflow must trigger on pull_request: PR CI runs it when relevant code changes. Skip the file-name -# allowlist here; the path-self-reference rule below ensures workflows that DO scope by paths still re-run when their -# own YAML is touched. +# Every workflow must trigger on pull_request so PR CI runs it when relevant code changes. +# Skip the file-name allowlist here; the path-self-reference rule below ensures workflows that DO scope by paths still +# re-run when their own YAML is touched. deny contains msg if { some file in input endswith(file.path, ".yaml") @@ -25,8 +25,8 @@ deny contains msg if { msg := sprintf("%s: workflow must include `workflow_dispatch:` (manual rerun)", [file.path]) } -# When a workflow scopes its pull_request trigger by `paths`, the workflow's own YAML must be in that list -- -# otherwise edits to the workflow itself don't re-run it on the PR that introduces them, and the change ships +# When a workflow scopes its pull_request trigger by `paths`, the workflow's own YAML must be in that list. +# Otherwise edits to the workflow itself don't re-run it on the PR that introduces them, and the change ships # unverified. Workflows without `paths` (always-run on every PR) are fine and need no entry. deny contains msg if { some file in input diff --git a/config/conftest/policy/mise/mise.rego b/config/conftest/policy/mise/mise.rego index a3e87a5..9aa8be9 100644 --- a/config/conftest/policy/mise/mise.rego +++ b/config/conftest/policy/mise/mise.rego @@ -1,11 +1,12 @@ -# .mise/config*.toml policy, evaluated over conftest's `--combine` input (an array -# of {path, contents}). Run with `--namespace mise` (or `--all-namespaces`). +# .mise/config*.toml policy. +# Evaluated over conftest's `--combine` input (an array of {path, contents}). Run with `--namespace mise` (or +# `--all-namespaces`). package mise is_mise(file) if startswith(file.path, ".mise/config") -# A task `run` must be a string, not an array: taplo's reorder_arrays would re-sort the commands of an array form -# and scramble the sequence. +# A task `run` must be a string, not an array. +# taplo's reorder_arrays would re-sort the commands of an array form and scramble the sequence. deny contains msg if { some file in input is_mise(file) @@ -14,10 +15,10 @@ deny contains msg if { msg := sprintf("%s: task %q run must be a string, not an array", [file.path, name]) } -# A multiline `run` must use `shell = "bash -euo pipefail -c"` so a failing -# command fails the task instead of being masked. The xtrace variant (`bash -xeuo pipefail -c`) is also accepted: -# prints every command as it runs, used on the Windows OS-specific preinstall where full transcripts matter for -# diagnosing busybox-ash + path-mangling failures. +# A multiline `run` must use `shell = "bash -euo pipefail -c"`. +# This makes a failing command fail the task instead of being masked. The xtrace variant (`bash -xeuo pipefail -c`) is +# also accepted: it prints every command as it runs, used on the Windows OS-specific preinstall where full transcripts +# matter for diagnosing busybox-ash + path-mangling failures. allowed_run_shells := {"bash -euo pipefail -c", "bash -xeuo pipefail -c"} deny contains msg if { @@ -43,11 +44,12 @@ deny contains msg if { msg := sprintf("%s: task %q description must be a single line", [file.path, name]) } -# `compgen` is a bash builtin that busybox-w32 ash (Nano Server's shell) does not provide -- a task using it fails on -# Nano with the literal error `: not found` (verified in the build-rp-native task). Skip lines whose first -# non-whitespace char is `#` so explanatory comments (like this one's siblings) don't false-positive. POSIX-portable -# alternatives include `[ -e "$prefix/bin/foo" ] || [ -e "$prefix/bin/foo.exe" ]` for an extension-agnostic existence -# check, and `for f in "$prefix/bin/foo"*` to walk a literal glob (no-match leaves the literal as the loop var). +# `compgen` is a bash builtin that busybox-w32 ash (Nano Server's shell) does not provide. +# A task using it fails on Nano with the literal error `: not found` (verified in the build-rp-native task). +# Skip lines whose first non-whitespace char is `#` so explanatory comments (like this one's siblings) don't +# false-positive. POSIX-portable alternatives include `[ -e "$prefix/bin/foo" ] || [ -e "$prefix/bin/foo.exe" ]` for an +# extension-agnostic existence check, and `for f in "$prefix/bin/foo"*` to walk a literal glob (no-match leaves the +# literal as the loop var). deny contains msg if { some file in input is_mise(file) @@ -62,16 +64,17 @@ deny contains msg if { ) } -# `cargo:` tools build from source; prefer a prebuilt backend. Allowed only when either (a) the tool has no prebuilt -# anywhere -- allowlisted by name below, or (b) it is os-scoped to second-tier platforms (linux/arm64, macos/x64), -# whose prebuilt assets release authors often skip. A first-tier platform (linux/x64, macos/arm64, windows) must -# always have a prebuilt; source-builds there are a slow surprise on the critical path. +# `cargo:` tools build from source; prefer a prebuilt backend. +# Allowed only when either (a) the tool has no prebuilt anywhere -- allowlisted by name below, or (b) it is os-scoped to +# second-tier platforms (linux/arm64, macos/x64), whose prebuilt assets release authors often skip. A first-tier +# platform (linux/x64, macos/arm64, windows) must always have a prebuilt; source-builds there are a slow surprise on the +# critical path. # # config.maint.toml is exempted: it's only loaded with MISE_ENV=maint by a maintainer running one-off publish tasks, # not by CI. A slow cargo-source install on a workstation when refreshing the HF mirror is fine. second_tier_platform := {"linux/arm64", "macos/x64"} -allowed_cargo_no_prebuilt := {"cargo:cargo-expand", "cargo:dart-typegen", "cargo:toml-cli"} +allowed_cargo_no_prebuilt := {"cargo:cargo-expand", "cargo:dart-typegen"} cargo_scoped_to_second_tier(spec) if { is_object(spec) @@ -104,9 +107,9 @@ deny contains msg if { msg := sprintf("%s: tool %q uses the deprecated ubi backend; use http: instead", [file.path, name]) } -# Tools should work on every OS (CLAUDE.md "Tools must work on every OS"). Any os-scoped [tools] entry must be in this -# list -- a genuinely platform-specific tool, a per-platform backend pair that still covers every OS (findutils, ryl), -# or an optional tool that self-skips on the omitted platform (pipx:torch). +# Tools should work on every OS (CLAUDE.md "Tools must work on every OS"). +# Any os-scoped [tools] entry must be in this list -- a genuinely platform-specific tool, a per-platform backend pair +# that still covers every OS (findutils, ryl), or an optional tool that self-skips on the omitted platform (pipx:torch). allowed_os_scoped_tool := { "chromedriver", "pipx", @@ -118,13 +121,13 @@ allowed_os_scoped_tool := { "github:uutils/findutils", "cargo:findutils", "cargo:ryl", - # http:et-rp is os-scoped to only those platforms whose tarball is already in the rp-v release; add a platform - # by dispatching the upstream-cache.yaml workflow on that host. + # http:et-rp is os-scoped to only those platforms whose tarball is already in the rp-v release. + # Add a platform by dispatching the upstream-cache.yaml workflow on that host. "http:et-rp", "conda:gnupg", - # cargo:dart-typegen is os-scoped to non-Windows because the gnullvm rust host trips - # `error[E0463]: can't find crate for 'core'` on Windows source-builds; coverage is preserved via - # http:dart-typegen in config.windows.toml, which serves the upstream-cache prebuilt. + # cargo:dart-typegen is os-scoped to non-Windows because the gnullvm rust host fails on Windows source-builds. + # It trips `error[E0463]: can't find crate for 'core'`; coverage is preserved via http:dart-typegen in + # config.windows.toml, which serves the upstream-cache prebuilt. "cargo:dart-typegen", } @@ -138,9 +141,9 @@ deny contains msg if { msg := sprintf("%s: tool %q is os-scoped; tools must work on every OS (or allowlist it)", [file.path, name]) } -# A [vars]/[env] value that hard-codes a tool's install path (e.g. the absolute linker in config.windows.toml, or the -# libpython rpath in config.toml) embeds the tool's version as a path segment. mise installs a tool to -# `installs//`, where is the tool name with `:` and `/` turned into `-`. Those embedded versions +# A [vars]/[env] value that hard-codes a tool's install path embeds the tool's version as a path segment. +# Examples are the absolute linker in config.windows.toml, or the libpython rpath in config.toml. mise installs a tool +# to `installs//`, where is the tool name with `:` and `/` turned into `-`. Those embedded versions # must track the `[tools]` pin -- a bump that updates the tool but not the var silently points at a missing dir. # Collect every (install-dir, version) the [tools] tables pin, across all files (--combine), since a var in # config..toml can reference a tool pinned in config.toml. @@ -171,10 +174,10 @@ config_strings contains entry if { entry := {"path": file.path, "kind": kind, "key": key, "value": value} } -# An install path embeds a tool's version as the segment right after the tool's install dir. Yield every embedded -# version that isn't a pinned version of that tool. The captured segment is restricted to version chars so it stops -# at the next path separator OR a trailing delimiter (a quote, `;`, ...) when the path is spliced into a larger string -# (as in Dockerfile ENV/RUN lines). Shared by the [vars]/[env] check here and the Dockerfile check +# An install path embeds a tool's version as the segment right after the tool's install dir. +# Yield every embedded version that isn't a pinned version of that tool. The captured segment is restricted to version +# chars so it stops at the next path separator OR a trailing delimiter (a quote, `;`, ...) when the path is spliced into +# a larger string (as in Dockerfile ENV/RUN lines). Shared by the [vars]/[env] check here and the Dockerfile check # (data.mise.version_drift). version_drift(value) := {drift | some [dir, _] in tool_versions @@ -200,9 +203,9 @@ tool_version_str(spec) := spec if is_string(spec) tool_version_str(spec) := spec.version if is_object(spec) -# python must be pinned to a full version triple (X.Y.Z), not a minor alias: mise installs it under a dir named after -# the request and only symlinks the X.Y alias, and that symlink isn't created on the Windows runner -- so the py3_* -# interpreter paths (and the version_drift check above) need the exact patch dir. +# python must be pinned to a full version triple (X.Y.Z), not a minor alias. +# mise installs it under a dir named after the request and only symlinks the X.Y alias, and that symlink isn't created +# on the Windows runner -- so the py3_* interpreter paths (and the version_drift check above) need the exact patch dir. deny contains msg if { some file in input is_mise(file) @@ -211,10 +214,10 @@ deny contains msg if { msg := sprintf("%s: python must be pinned to a full version triple, got %q", [file.path, version]) } -# Linux + macOS preinstall MUST read its prerequisite package list from the Dockerfile (COMMON_PACKAGES + -# APT_PACKAGES / DNF_PACKAGES ARGs). The Dockerfile is the single source of truth: a capability-based or hardcoded -# check would diverge from it silently. Guard by requiring the preinstall task body to reference both COMMON_PACKAGES -# and APT_PACKAGES somewhere (env var or rg-parse of the file). +# Linux + macOS preinstall MUST read its prerequisite package list from the Dockerfile. +# The relevant ARGs are COMMON_PACKAGES + APT_PACKAGES / DNF_PACKAGES. The Dockerfile is the single source of truth: a +# capability-based or hardcoded check would diverge from it silently. Guard by requiring the preinstall task body to +# reference both COMMON_PACKAGES and APT_PACKAGES somewhere (env var or rg-parse of the file). preinstall_must_reference_apt_packages := { ".mise/config.linux.toml", ".mise/config.macos.toml", diff --git a/config/conftest/policy/no_trailing_backslash/no_trailing_backslash.rego b/config/conftest/policy/no_trailing_backslash/no_trailing_backslash.rego index 90a28e4..4d0b6c2 100644 --- a/config/conftest/policy/no_trailing_backslash/no_trailing_backslash.rego +++ b/config/conftest/policy/no_trailing_backslash/no_trailing_backslash.rego @@ -1,6 +1,6 @@ -# Defense-in-depth duplicate of config/semgrep/no-trailing-backslash.yaml. Walks every string anywhere in the -# combined input and flags any literal `\` immediately followed by a newline -- the trailing-backslash line -# continuation we don't want anywhere in the repo. +# Defense-in-depth duplicate of config/semgrep/no-trailing-backslash.yaml. +# Flags the trailing-backslash line continuation we don't want anywhere in the repo, by walking every string +# anywhere in the combined input and flagging any literal `\` immediately followed by a newline. # # Scope: # - TOML (`--namespace no_trailing_backslash` on conftest-check-toml): catches continuations embedded in multi-line @@ -13,9 +13,10 @@ # per its `paths.exclude` (Dockerfile RUN bodies legitimately need line continuations). package no_trailing_backslash -# Path key for the message: an array of indices/keys from the root of the parsed document down to the offending -# string. Rego's `walk` yields [path, value] pairs over every node, so we filter to string leaves and regex-test for -# `\` followed by `\n` (LF). +# Flag any string leaf containing `\` followed by `\n` (LF), reporting its path key in the message. +# The path key is an array of indices/keys from the root of the parsed document down to the offending string. +# Rego's `walk` yields [path, value] pairs over every node, so we filter to string leaves and regex-test for `\` +# followed by `\n` (LF). deny contains msg if { some file in input walk(file.contents, [path, value]) diff --git a/config/conftest/policy/pyproject/pyproject.rego b/config/conftest/policy/pyproject/pyproject.rego index 9b95691..eac6ef0 100644 --- a/config/conftest/policy/pyproject/pyproject.rego +++ b/config/conftest/policy/pyproject/pyproject.rego @@ -1,10 +1,10 @@ -# pyproject.toml policy, evaluated over conftest's --combine input ({path, -# contents}); selected with --namespace pyproject. +# pyproject.toml policy, selected with --namespace pyproject. +# Evaluated over conftest's --combine input ({path, contents}). package pyproject -# A `path = ".."` uv source points at the parent directory, which isn't a package -# -- almost always a mistake. Sibling packages are referenced by their specific relative path -# (e.g. ../../../generated/python-rest), not the bare parent. +# A `path = ".."` uv source points at the parent directory, which isn't a package -- almost always a mistake. +# Sibling packages are referenced by their specific relative path (e.g. ../../../generated/python-rest), not the +# bare parent. deny contains msg if { some file in input endswith(file.path, "pyproject.toml") @@ -13,8 +13,8 @@ deny contains msg if { msg := sprintf("%s: [tool.uv.sources] %q uses path = \"..\"; point at the package path", [file.path, name]) } -# uv_build must be pinned to exactly the mise uv version, so `uv build` uses the matching backend (no out-of-range -# warning). Bump both together when upgrading uv. +# uv_build must be pinned to exactly the mise uv version. +# This makes `uv build` use the matching backend with no out-of-range warning. Bump both together when upgrading uv. uv_version := v if { some file in input endswith(file.path, ".mise/config.toml") diff --git a/config/semgrep/cargo-toml.yaml b/config/semgrep/cargo-toml.yaml index 3485ad2..80607b3 100644 --- a/config/semgrep/cargo-toml.yaml +++ b/config/semgrep/cargo-toml.yaml @@ -7,7 +7,6 @@ rules: pattern: "$DEP = { workspace = true }" fix: "$DEP.workspace = true" message: >- - Use the dotted form `dep.workspace = true` instead of the inline table - `dep = { workspace = true }`. (Inline tables with extra keys, e.g. - `dep = { workspace = true, features = [...] }`, are fine.) + Use the dotted form `dep.workspace = true` instead of the inline table `dep = { workspace = true }`. (Inline + tables with extra keys, e.g. `dep = { workspace = true, features = [...] }`, are fine.) severity: ERROR diff --git a/config/semgrep/comment-summary-line.yaml b/config/semgrep/comment-summary-line.yaml index 9adcaa5..3b5e6bc 100644 --- a/config/semgrep/comment-summary-line.yaml +++ b/config/semgrep/comment-summary-line.yaml @@ -5,6 +5,7 @@ rules: include: - "*.toml" - "*.yaml" + - "*.rego" - "Dockerfile*" exclude: # Skip machine-emitted TOML/YAML (int-gen / regen output). @@ -20,7 +21,7 @@ rules: # character of the summary line. pattern-regex: '(?m)(?:\A|^[ \t]*(?:[^#\s][^\n]*)?\n)[ \t]*#[^\n]*[^.\n:]\n[ \t]*#' message: >- - A multi-line `#` comment block (TOML, YAML, or Dockerfile) must open with a + A multi-line `#` comment block (TOML, YAML, Rego, or Dockerfile) must open with a single summary line ending in a full stop (PEP 257 style). The summary describes the whole block in one sentence; continuation lines follow. End the first comment line with `.` (a `:` introducing an enumeration diff --git a/config/semgrep/dockerfile-heredoc-set-euo-pipefail.yaml b/config/semgrep/dockerfile-heredoc-set-euo-pipefail.yaml index 461f091..c6cafea 100644 --- a/config/semgrep/dockerfile-heredoc-set-euo-pipefail.yaml +++ b/config/semgrep/dockerfile-heredoc-set-euo-pipefail.yaml @@ -12,9 +12,8 @@ rules: # line here. pattern-regex: "^RUN[^\\n]*<<'?[A-Z]+'?[^\\n]*\\n(?!set -euo pipefail$)" message: >- - Dockerfile RUN heredocs must begin with `set -euo pipefail` as the first - body line. BuildKit's default heredoc shell carries neither -e nor -o - pipefail (and dash on Debian/Ubuntu doesn't support -o pipefail at all), - so the strict-mode invariant has to be re-declared per body. Paired with - the conftest rule that requires `RUN < `# text`) can't be expressed as an autofix here. message: >- - Decorative `# --- text ---` banner comments are not allowed in - Dockerfiles. Use a plain, concise one-line comment (`# text`) instead: - drop the surrounding dash runs, keeping the inner text. + Decorative `# --- text ---` banner comments are not allowed in Dockerfiles. Use a plain, concise one-line + comment (`# text`) instead: drop the surrounding dash runs, keeping the inner text. severity: ERROR diff --git a/config/semgrep/mise-config.yaml b/config/semgrep/mise-config.yaml index c4215aa..be3a903 100644 --- a/config/semgrep/mise-config.yaml +++ b/config/semgrep/mise-config.yaml @@ -6,9 +6,8 @@ rules: - "/.mise/config*.toml" pattern-regex: 'description = """' message: >- - mise task `description` fields must be single-line `"..."` strings, not - multi-line `"""..."""`. Keep the description short enough to fit on one - line (under the 120-char limit). + mise task `description` fields must be single-line `"..."` strings, not multi-line `"""..."""`. Keep the + description short enough to fit on one line (under the 120-char limit). severity: ERROR - id: multiline-task-run-needs-bash-shell @@ -18,12 +17,10 @@ rules: - "/.mise/config*.toml" pattern-regex: 'run = ("""|'''''')\n(?:(?!\1\n).*\n)*?\1\n(?!shell = "bash -x?euo pipefail -c")' message: >- - A mise task with a multiline `run` must be immediately followed by - `shell = "bash -euo pipefail -c"` (on the line right after the closing - `"""`/`'''`), so a failing command fails the task instead of being masked. - The `-x` variant (`bash -xeuo pipefail -c`) is also accepted (xtrace, used - on the Windows OS-specific preinstall for failure-diagnosis transcripts). - Put `shell` directly after `run`. bash is installed before any task on + A mise task with a multiline `run` must be immediately followed by `shell = "bash -euo pipefail -c"` (on the + line right after the closing `"""`/`'''`), so a failing command fails the task instead of being masked. The + `-x` variant (`bash -xeuo pipefail -c`) is also accepted (xtrace, used on the Windows OS-specific preinstall + for failure-diagnosis transcripts). Put `shell` directly after `run`. bash is installed before any task on Windows (see preinstall), so even the setup tasks can use it. severity: ERROR @@ -41,7 +38,7 @@ rules: # directive on the header. pattern-regex: '(?m)^\[tasks\.[^\]]+\.env\]\n(?:#[^\n]*\n)*[A-Z_][A-Z0-9_]*\s*=\s*[^\n]+\n+\[' message: >- - `[tasks.X.env]` subtable has a single `KEY = "value"` assignment. Inline - it on the parent task as `env = { KEY = "value" }` when the resulting - line fits in 120 chars -- subtable form is reserved for multi-key envs. + `[tasks.X.env]` subtable has a single `KEY = "value"` assignment. Inline it on the parent task as + `env = { KEY = "value" }` when the resulting line fits in 120 chars -- subtable form is reserved for + multi-key envs. severity: WARNING diff --git a/config/semgrep/no-non-ascii.yaml b/config/semgrep/no-non-ascii.yaml index e558261..992bdb6 100644 --- a/config/semgrep/no-non-ascii.yaml +++ b/config/semgrep/no-non-ascii.yaml @@ -20,10 +20,8 @@ rules: - "*.onnx" pattern-regex: '[^\x00-\x7F]' message: >- - Non-ASCII character. Keep source, config, and docs ASCII-only: use `--` - for an em-dash, `->` for an arrow, `...` for an ellipsis, `<=`/`!=`/`^2` - for math glyphs, and straight quotes for curly quotes. ASCII keeps - terminals, log scrapers, and `grep` reading the same bytes the editor - shows. Generated trees (generated/, verification/, HELP.md, pkg/) are - exempt because their generator owns the output. + Non-ASCII character. Keep source, config, and docs ASCII-only: use `--` for an em-dash, `->` for an arrow, + `...` for an ellipsis, `<=`/`!=`/`^2` for math glyphs, and straight quotes for curly quotes. ASCII keeps + terminals, log scrapers, and `grep` reading the same bytes the editor shows. Generated trees (generated/, + verification/, HELP.md, pkg/) are exempt because their generator owns the output. severity: ERROR diff --git a/config/semgrep/no-todo.yaml b/config/semgrep/no-todo.yaml index a701109..ab7eb8a 100644 --- a/config/semgrep/no-todo.yaml +++ b/config/semgrep/no-todo.yaml @@ -25,9 +25,7 @@ rules: - "/config/semgrep/no-todo.yaml" pattern-regex: '\bTODO\b' message: >- - TODOs are not allowed in this repo. Either resolve the work now, or, if - it must defer, add a single-line justification in the surrounding code - and add this file's path to no-todo.yaml's paths.exclude allowlist. Do - not duplicate a TODO across multiple files -- pick the single canonical - home and let `grep TODO` find it there. + TODOs are not allowed in this repo. Either resolve the work now, or, if it must defer, add a single-line + justification in the surrounding code and add this file's path to no-todo.yaml's paths.exclude allowlist. Do + not duplicate a TODO across multiple files -- pick the single canonical home and let `grep TODO` find it there. severity: ERROR diff --git a/config/semgrep/no-trailing-backslash.yaml b/config/semgrep/no-trailing-backslash.yaml index d7a8f31..b8548b3 100644 --- a/config/semgrep/no-trailing-backslash.yaml +++ b/config/semgrep/no-trailing-backslash.yaml @@ -2,9 +2,6 @@ rules: - id: no-trailing-backslash languages: [generic] paths: - # Repo-wide. - # The files below already use trailing-backslash continuations and are allowlisted; everything else must avoid - # them (keep the value/statement on one line, or use concat!/[vars]/arrays). exclude: - "/README.md" - "/utilities/cli/README.md" @@ -17,8 +14,7 @@ rules: - "/verification/**/compose.yaml" pattern-regex: '\\[ \t]*$' message: >- - Trailing-backslash line continuations are not allowed in this repo. Keep - it on one line, or use a language-appropriate alternative (concat!(...) in - Rust, a mise [vars] entry, a YAML/array form). If a file genuinely needs - them, add it to this rule's paths.exclude allowlist. + Trailing-backslash line continuations are not allowed in this repo. Keep it on one line, or use a + language-appropriate alternative (concat!(...) in Rust, a mise [vars] entry, a YAML/array form). If a file + genuinely needs them, add it to this rule's paths.exclude allowlist. severity: ERROR diff --git a/config/semgrep/prefer-yaml-toml.yaml b/config/semgrep/prefer-yaml-toml.yaml index 6dd0585..49d3bf8 100644 --- a/config/semgrep/prefer-yaml-toml.yaml +++ b/config/semgrep/prefer-yaml-toml.yaml @@ -19,8 +19,6 @@ rules: - "oxfmtrc.jsonc" # oxfmt: only JSON / JSONC supported pattern-regex: \A message: >- - Prefer YAML or TOML over JSON/JSONC/JSONL for config. If the tool - requires a JSON-family format, prefer .jsonc over .json so the config - can carry inline comments, and add the file to this rule's - paths.exclude allowlist. + Prefer YAML or TOML over JSON/JSONC/JSONL for config. If the tool requires a JSON-family format, prefer .jsonc + over .json so the config can carry inline comments, and add the file to this rule's paths.exclude allowlist. severity: ERROR diff --git a/config/taplo/mise-cargo-backend-allowlist.schema.json b/config/taplo/mise-cargo-backend-allowlist.schema.json index 7557220..31232b0 100644 --- a/config/taplo/mise-cargo-backend-allowlist.schema.json +++ b/config/taplo/mise-cargo-backend-allowlist.schema.json @@ -9,7 +9,7 @@ "propertyNames": { "anyOf": [ { "not": { "pattern": "^cargo:" } }, - { "enum": ["cargo:cargo-expand", "cargo:dart-typegen", "cargo:findutils", "cargo:ryl", "cargo:toml-cli"] } + { "enum": ["cargo:cargo-expand", "cargo:dart-typegen", "cargo:findutils", "cargo:ryl"] } ] } } From 90c436a9df83c1c150ed5558b650372d43053394 Mon Sep 17 00:00:00 2001 From: John Vandenberg Date: Mon, 22 Jun 2026 19:19:11 +0800 Subject: [PATCH 132/133] more reflow --- .github/workflows/docker-windows.yaml | 2 +- .github/workflows/upstream-cache.yaml | 2 +- .mise/config.linux.toml | 4 +-- .mise/config.toml | 19 +++-------- Dockerfile | 6 ++-- Dockerfile.nanoserver | 11 +++---- Dockerfile.windows | 8 ++--- .../rules/gha-checkout-fetch-depth-1.yaml | 12 +++---- .../rules/gha-default-shell-bash.yaml | 11 +++---- .../rules/gha-job-timeout-minutes.yaml | 10 +++--- .../ast-grep/rules/gha-no-folded-strip.yaml | 7 ++-- .../gha-no-step-shell-upstream-cache.yaml | 5 ++- config/ast-grep/rules/gha-no-step-shell.yaml | 8 ++--- .../rules/gha-standard-concurrency.yaml | 11 +++---- .../rules/gha-strategy-fail-fast-false.yaml | 8 ++--- .../ast-grep/rules/no-allow-attributes.yaml | 13 +++----- .../ast-grep/rules/no-cargo-manifest-dir.yaml | 5 ++- .../ast-grep/rules/no-consecutive-expect.yaml | 9 +++--- config/ast-grep/rules/no-current-dir.yaml | 10 +++--- config/ast-grep/rules/no-doctest.yaml | 4 +-- .../ast-grep/rules/no-extern-crate-self.yaml | 10 +++--- config/ast-grep/rules/no-inline-mod.yaml | 4 +-- config/ast-grep/rules/no-map-err.yaml | 10 +++--- .../rules/no-mixed-doc-line-comments.yaml | 5 ++- config/ast-grep/rules/no-non-ascii.yaml | 7 ++-- .../rules/no-relative-path-literal.yaml | 10 +++--- config/ast-grep/rules/no-result-alias.yaml | 5 ++- .../ast-grep/rules/no-result-string-err.yaml | 7 ++-- config/ast-grep/rules/no-shadow-result.yaml | 8 ++--- config/ast-grep/rules/no-std-env-var.yaml | 18 ++++------- config/ast-grep/rules/no-std-fs.yaml | 9 +++--- .../no-string-literal-line-continuation.yaml | 13 +++----- .../ast-grep/rules/prefer-self-mod-use.yaml | 32 +++++++++---------- config/ast-grep/rules/use-mod-order.yaml | 5 ++- .../policy/dockerfile/dockerfile.rego | 2 +- .../policy/gha_combined/gha_combined.rego | 5 +-- config/deny.toml | 2 +- config/lychee.toml | 2 +- config/semgrep/comment-summary-line.yaml | 11 +++++-- config/taplo.toml | 2 +- ruff.toml | 2 +- services/ws-pyo3-runner/Cargo.toml | 2 +- 42 files changed, 143 insertions(+), 193 deletions(-) diff --git a/.github/workflows/docker-windows.yaml b/.github/workflows/docker-windows.yaml index cf55488..4f43288 100644 --- a/.github/workflows/docker-windows.yaml +++ b/.github/workflows/docker-windows.yaml @@ -152,7 +152,7 @@ jobs: args="$args --target $target $tag" docker build $args . - # Run the test suite split the same way docker-linux does: + # Run the test suite split the same way docker-linux does. # cargo-test-other (workspace minus et-ws-web-runner) and test-ws-web-runner (Deno-runtime gated, self-skips # on hosts that can't satisfy it). Each runs in a fresh container so the per-phase compile target/ doesn't # share the writable layer with the other phase. servercore-only -- Nano has no test stage. diff --git a/.github/workflows/upstream-cache.yaml b/.github/workflows/upstream-cache.yaml index 8b359c6..69bd5c6 100644 --- a/.github/workflows/upstream-cache.yaml +++ b/.github/workflows/upstream-cache.yaml @@ -466,7 +466,7 @@ jobs: clone_args="--depth 1 --branch $AUG_REF --recurse-submodules" git clone $clone_args "$AUG_FORK_REPO" "$srcdir" cd "$srcdir" - # CFLAGS, mirroring the fork's build.yml windows job: + # CFLAGS, mirroring the fork's build.yml windows job. # -Wno-implicit-function-declaration: mingw-w64 gcc 14+ promoted # this from warning to error; some autoconf probes still trip it. # -Duint=unsigned: augeas headers use the BSD-ism `uint`, which diff --git a/.mise/config.linux.toml b/.mise/config.linux.toml index bab2d7d..8d9cff7 100644 --- a/.mise/config.linux.toml +++ b/.mise/config.linux.toml @@ -132,8 +132,8 @@ missing="" for pkg in $pkgs; do case "$pkg" in ca-certificates) - # Debian/Ubuntu: /etc/ssl/certs/ca-certificates.crt; Fedora/RHEL/AL2023: - # /etc/pki/tls/certs/ca-bundle.crt; openSUSE: /etc/ssl/ca-bundle.pem. + # Debian/Ubuntu: /etc/ssl/certs/ca-certificates.crt. + # Fedora/RHEL/AL2023: /etc/pki/tls/certs/ca-bundle.crt; openSUSE: /etc/ssl/ca-bundle.pem. [ -r /etc/ssl/certs/ca-certificates.crt ] || [ -r /etc/pki/tls/certs/ca-bundle.crt ] || [ -r /etc/ssl/ca-bundle.pem ] ;; diff --git a/.mise/config.toml b/.mise/config.toml index d83048a..9e4b773 100644 --- a/.mise/config.toml +++ b/.mise/config.toml @@ -49,9 +49,7 @@ gpg_verify = true [tools] action-validator = "latest" -# jaq: a jq-compatible alternative implementation in Rust. -# Prebuilt binaries for every project tier, unlike the C jq's missing release assets for some platforms. -# Used as `jaq` in task bodies (gh's `--jq` flag is internal to gh, not affected). +# jaq: a Rust jq-compatible alternative implementation with prebuilt binaries for every platform. "aqua:01mf02/jaq" = "latest" "aqua:EmbarkStudios/cargo-deny" = "latest" "aqua:StyraInc/regal" = "latest" @@ -96,24 +94,17 @@ ripgrep = "latest" # source via cargo: (same 0.8.0 pin, so every platform runs the same version). "cargo:findutils" = { version = "0.8.0", os = ["linux/arm64"] } # goawk: the awk that tasks invoke as `goawk` in place of the host's awk. -# Go, not Rust, but it's the one with prebuilt binaries for every platform we target. "github:benhoyt/goawk" = "latest" -# parfit: width-aware comment/paragraph reflow tool (prebuilt for every tier). "github:caldempsey/parfit" = "latest" "github:grok-rs/waitup" = "latest" # ONNX Runtime prebuilt -- ort-sys 2.0.0-rc.10 binds to ORT 1.22.x. "github:microsoft/onnxruntime" = "1.22.0" -# ryl: prebuilt assets cover every platform we target except macos/x64. -# That one slot builds from source via cargo: (same `latest` so every platform tracks the same release). "cargo:ryl" = { version = "latest", os = ["macos/x64"] } "github:owenlamont/ryl" = { version = "latest", os = ["linux", "macos/arm64", "windows"] } "github:wasm-bindgen/wasm-bindgen" = "0.2.122" -# go: Go toolchain. -# Sole consumer right now is the `go:` backend that source-builds jqfmt +# Sole consumer right now is the `go:` backend that source-builds jqfmt. # (noperator/jqfmt ships no release assets); add Go users here as they arrive. go = "latest" -# jqfmt: jq formatter ("like gofmt, but for jq"). -# Source-built by the go: backend because upstream ships no release assets. Used by the `jq-fmt` task. "go:github.com/noperator/jqfmt/cmd/jqfmt" = "latest" hadolint = "latest" ls-lint = "latest" @@ -129,8 +120,6 @@ pipx = { version = "latest", os = ["linux", "macos"] } # Proves a mise-installed python package lands on the embedded interpreter's # sys.path (see `edge_toolkit::config::mise_python_site_packages`). "pipx:cowsay" = "latest" -# semgrep lints Cargo.toml style (see `semgrep-check`). -# It stays in the always-loaded config alongside the other repo-wide linters. gh = "latest" "pipx:semgrep" = "latest" # Aqua has no pnpm darwin/amd64 prebuilt. @@ -605,7 +594,7 @@ shell = "bash -euo pipefail -c" [tasks.conftest-check-yaml] description = "Run conftest OPA/Rego policies over the GitHub Actions workflow + action YAML" -# Three passes over the workflow + action YAML: +# Three passes over the workflow + action YAML. # per-file rules (gha, no_trailing_backslash) over workflow YAML; --combine rules (gha_combined) over # workflow YAML for cross-cutting invariants where the rule has to compare each workflow's parsed body # against its own filename (the `paths:` self-reference check; conftest's per-file mode has no `input.path`); @@ -1159,7 +1148,7 @@ env = { RUST_TEST_THREADS = "1" } run = "cargo test -p et-ws-web-runner" [tasks.cargo-test] -# Composite of two halves: +# Composite of two halves. # cargo-test-other (workspace minus et-ws-web-runner) + test-ws-web-runner (serialized). Local devs / # hosts where disk and memory aren't tight invoke this single task; CI splits the two halves into # separate workflow steps for debuggability. diff --git a/Dockerfile b/Dockerfile index a68a7f9..3956096 100644 --- a/Dockerfile +++ b/Dockerfile @@ -106,7 +106,7 @@ ARG DNF_PACKAGES="gcc-c++ glibc-devel kernel-headers libatomic libicu" ENV COMMON_PACKAGES=${COMMON_PACKAGES} ENV APT_PACKAGES=${APT_PACKAGES} ENV DNF_PACKAGES=${DNF_PACKAGES} -# Leaving these package-list variables unquoted below is intentional: +# Leaving these package-list variables unquoted below is intentional. # `$COMMON_PACKAGES` / `$APT_PACKAGES` / `$DNF_PACKAGES` / `$pkgs` are each a # space-separated list, and we WANT the shell to word-split each one into # distinct args for apt-get / dnf / tdnf / zypper. @@ -192,7 +192,7 @@ COPY .mise/config.linux.toml .mise/config.linux.toml RUN mise trust -# Preinstall via the shared preinstall task (the same a Linux workstation runs): +# Preinstall via the shared preinstall task (the same a Linux workstation runs). # its setup-all base enables experimental + cargo.binstall and installs # cargo-binstall, node and conda:openssl; then `mise install` adds the rest of # the always-loaded tools. A GitHub token (if provided) lifts the anonymous rate @@ -282,7 +282,7 @@ ENV NVIDIA_VISIBLE_DEVICES=all NVIDIA_DRIVER_CAPABILITIES=all # still a valid CPU fallback alongside the real GPU's ICD; the lvp glob # selects the software path either way. ENV VK_LOADER_DRIVERS_SELECT=lvp_icd* -# Install the Vulkan runtime + lavapipe (CPU/software) driver per package manager: +# Install the Vulkan runtime + lavapipe (CPU/software) driver per package manager. # Debian/Ubuntu: libvulkan1 + mesa-vulkan-drivers; Fedora/AL2023: # vulkan-loader + mesa-vulkan-drivers; Azure Linux: same as Fedora; # openSUSE: libvulkan1 + libvulkan_lvp (Mesa ships the lavapipe driver as diff --git a/Dockerfile.nanoserver b/Dockerfile.nanoserver index a8c13ec..6667828 100644 --- a/Dockerfile.nanoserver +++ b/Dockerfile.nanoserver @@ -151,12 +151,11 @@ ENV TEMP=C:\Temp ` # use). It's a transient build cache, so it lives under the temp dir above. ENV WASM_PACK_CACHE=C:\Temp\wasm-pack -# Serialize mise's installs (MISE_JOBS=1). On Nano Server the parallel cargo: -# source builds -- no gnu binstall prebuilts exist, so they fall back to `cargo -# install` -- race on rustup's shared .rustup\downloads dir: one build's -# component .partial vanishes before another rustup can rename it ("cannot find -# the file", os error 2). Windows CI doesn't hit this (msvc binstall prebuilts -# mean no source builds); the gnu Docker path does. One job at a time avoids it. +# Serialize mise's installs (MISE_JOBS=1). +# On Nano Server the parallel cargo: source builds -- no gnu binstall prebuilts exist, so they fall back to +# `cargo install` -- race on rustup's shared .rustup\downloads dir: one build's component .partial vanishes +# before another rustup can rename it ("cannot find the file", os error 2). Windows CI doesn't hit this (msvc +# binstall prebuilts mean no source builds); the gnu Docker path does. One job at a time avoids it. ENV MISE_JOBS=1 # Full Rust backtraces so any panic surfaces in the CI log. diff --git a/Dockerfile.windows b/Dockerfile.windows index 53ee80b..9ecad71 100644 --- a/Dockerfile.windows +++ b/Dockerfile.windows @@ -17,7 +17,7 @@ # (SC1001). hadolint/hadolint#645. Suppress just those two for this file. # hadolint global ignore=SC1072,SC1001 -# Server Core variant. Sister to Dockerfile.nanoserver but on a fuller base: +# Server Core variant. Sister to Dockerfile.nanoserver but on a fuller base. # mcr.microsoft.com/windows/servercore ships PowerShell, an installer stack, # and a complete kernel32 / shell32 / winmm / propsys -- so most of the Nano # workarounds (vcruntime/gpgbin donor stages, MISE_GPG_VERIFY=false, the @@ -62,8 +62,8 @@ ENV TEMP=C:\Temp ` # This stops wasm-pack going through dirs_next's home-directory resolution at all. ENV WASM_PACK_CACHE=C:\Temp\wasm-pack -# Serialize mise installs (same rationale as the Linux/Nano builds: the cargo: -# source-build fallbacks race on rustup's shared .rustup\downloads dir). +# Serialize mise installs (same rationale as the Linux/Nano builds). +# The cargo: source-build fallbacks race on rustup's shared .rustup\downloads dir. ENV MISE_JOBS=1 # Full Rust backtraces so any panic surfaces in the CI log. @@ -95,7 +95,7 @@ COPY .mise/config.toml .mise/config.toml COPY .mise/config.windows.toml .mise/config.windows.toml COPY .miserc.toml .miserc.toml -# gh_token (optional GitHub token) -- same pattern as Dockerfile.nanoserver: +# gh_token (optional GitHub token) -- same pattern as Dockerfile.nanoserver. # glob with a paired always-present file (.miserc.toml) so COPY succeeds when # gh_token is absent; both land in C:\token (off the WORKDIR ancestor chain). # WARNING: when a token IS supplied it bakes into the image layer. diff --git a/config/ast-grep/rules/gha-checkout-fetch-depth-1.yaml b/config/ast-grep/rules/gha-checkout-fetch-depth-1.yaml index 7c172fc..c2db259 100644 --- a/config/ast-grep/rules/gha-checkout-fetch-depth-1.yaml +++ b/config/ast-grep/rules/gha-checkout-fetch-depth-1.yaml @@ -2,13 +2,11 @@ id: gha-checkout-fetch-depth-1 language: yaml severity: error message: | - Every `actions/checkout@...` step in this repo must declare - `fetch-depth: 1` in its `with:` block. Explicit-beats-implicit (the action - default is already 1, but pinning it at the call site keeps the value - visible in review and ensures a future default change doesn't silently - start cloning more), and the rule's primary purpose is to prevent - `fetch-depth: 0` from creeping back in -- no task in this repo reads git - history, so cloning every commit just burns runner minutes and disk. + Every `actions/checkout@...` step in this repo must declare `fetch-depth: 1` in its `with:` block. + Explicit-beats-implicit (the action default is already 1, but pinning it at the call site keeps the value visible in + review and ensures a future default change doesn't silently start cloning more), and the rule's primary purpose is to + prevent `fetch-depth: 0` from creeping back in -- no task in this repo reads git history, so cloning every commit just + burns runner minutes and disk. files: - .github/workflows/*.yaml # Match every `uses: actions/checkout@...` whose surrounding step body lacks `fetch-depth: 1`. diff --git a/config/ast-grep/rules/gha-default-shell-bash.yaml b/config/ast-grep/rules/gha-default-shell-bash.yaml index fd6b1af..8de0af4 100644 --- a/config/ast-grep/rules/gha-default-shell-bash.yaml +++ b/config/ast-grep/rules/gha-default-shell-bash.yaml @@ -2,16 +2,13 @@ id: gha-default-shell-bash language: yaml severity: error message: | - Every GitHub Actions workflow must declare the default run shell as bash - with -euo pipefail, exactly: + Every GitHub Actions workflow must declare the default run shell as bash with -euo pipefail, exactly: defaults: run: shell: bash --noprofile --norc -euo pipefail {0} - so steps run in bash on every runner (Windows included) with strict-mode - flags on every step -- no per-step `set -euo pipefail` boilerplate needed. - GHA's bare `shell: bash` already implies `-e -o pipefail`; this explicit - form additionally adds `-u` (unset variable -> error). (Pairs with - gha-no-step-shell, which forbids per-step `shell:`.) + so steps run in bash on every runner (Windows included) with strict-mode flags on every step -- no per-step + `set -euo pipefail` boilerplate needed. GHA's bare `shell: bash` already implies `-e -o pipefail`; this explicit form + additionally adds `-u` (unset variable -> error). (Pairs with gha-no-step-shell, which forbids per-step `shell:`.) files: - .github/workflows/*.yaml # Fire on any workflow whose document does NOT contain the exact defaults -> run -> shell: ... nesting. diff --git a/config/ast-grep/rules/gha-job-timeout-minutes.yaml b/config/ast-grep/rules/gha-job-timeout-minutes.yaml index 166c9ac..c07f1fb 100644 --- a/config/ast-grep/rules/gha-job-timeout-minutes.yaml +++ b/config/ast-grep/rules/gha-job-timeout-minutes.yaml @@ -2,12 +2,10 @@ id: gha-job-timeout-minutes language: yaml severity: error message: | - Every GitHub Actions job must declare `timeout-minutes:` at the job level. - The runner's default per-job timeout is 360 minutes (6 hours), which is - long enough that a hung job (deadlocked test, infinite-recurse cargo - resolver, stuck `docker pull`) drains the runner-minutes budget before - anyone notices. Pin every job to a realistic ceiling for that job's work. - Use `${{ matrix.timeout }}` or similar if the right value varies by entry. + Every GitHub Actions job must declare `timeout-minutes:` at the job level. The runner's default per-job timeout is 360 + minutes (6 hours), which is long enough that a hung job (deadlocked test, infinite-recurse cargo resolver, stuck + `docker pull`) drains the runner-minutes budget before anyone notices. Pin every job to a realistic ceiling for that + job's work. Use `${{ matrix.timeout }}` or similar if the right value varies by entry. files: - .github/workflows/*.yaml # Match every `runs-on:` whose containing block_mapping has no `timeout-minutes:` sibling. diff --git a/config/ast-grep/rules/gha-no-folded-strip.yaml b/config/ast-grep/rules/gha-no-folded-strip.yaml index 2123371..c500740 100644 --- a/config/ast-grep/rules/gha-no-folded-strip.yaml +++ b/config/ast-grep/rules/gha-no-folded-strip.yaml @@ -2,10 +2,9 @@ id: gha-no-folded-strip language: yaml severity: error message: | - Don't use `>-` (folded block scalar with strip-chomping) in workflow YAML. - The output reflows whitespace and silently swallows trailing newlines, which - bites when the step's `run:` is a shell command that depends on line breaks - -- a `>-` block joins lines with spaces, not newlines. + Don't use `>-` (folded block scalar with strip-chomping) in workflow YAML. The output reflows whitespace and silently + swallows trailing newlines, which bites when the step's `run:` is a shell command that depends on line breaks -- a + `>-` block joins lines with spaces, not newlines. Use either: - a plain single-line `run: `, or - a literal block `run: |` (which preserves newlines verbatim). diff --git a/config/ast-grep/rules/gha-no-step-shell-upstream-cache.yaml b/config/ast-grep/rules/gha-no-step-shell-upstream-cache.yaml index 8da4121..4db618d 100644 --- a/config/ast-grep/rules/gha-no-step-shell-upstream-cache.yaml +++ b/config/ast-grep/rules/gha-no-step-shell-upstream-cache.yaml @@ -2,9 +2,8 @@ id: gha-no-step-shell-upstream-cache language: yaml severity: error message: | - In upstream-cache.yaml the only permitted per-step `shell:` override is - `shell: msys2 {0}` -- the wrapper msys2/setup-msys2 requires for the augeas - Windows build to run inside its MINGW64 env (Git Bash isn't a substitute). + In upstream-cache.yaml the only permitted per-step `shell:` override is `shell: msys2 {0}` -- the wrapper + msys2/setup-msys2 requires for the augeas Windows build to run inside its MINGW64 env (Git Bash isn't a substitute). Any other shell override must use the workflow default bash. files: - .github/workflows/upstream-cache.yaml diff --git a/config/ast-grep/rules/gha-no-step-shell.yaml b/config/ast-grep/rules/gha-no-step-shell.yaml index a3752d8..ae19b76 100644 --- a/config/ast-grep/rules/gha-no-step-shell.yaml +++ b/config/ast-grep/rules/gha-no-step-shell.yaml @@ -2,11 +2,9 @@ id: gha-no-step-shell language: yaml severity: error message: | - Don't set `shell:` on a workflow step. Every workflow defaults to bash - (defaults.run.shell) and steps must use it -- write the step in bash (e.g. - `sc`/`net`/`printf` instead of PowerShell cmdlets, even on Windows runners, - which have Git Bash) rather than overriding the shell per step. No per-step - shell override is permitted in these workflows. + Don't set `shell:` on a workflow step. Every workflow defaults to bash (defaults.run.shell) and steps must use it -- + write the step in bash (e.g. `sc`/`net`/`printf` instead of PowerShell cmdlets, even on Windows runners, which have + Git Bash) rather than overriding the shell per step. No per-step shell override is permitted in these workflows. files: - .github/workflows/*.yaml ignores: diff --git a/config/ast-grep/rules/gha-standard-concurrency.yaml b/config/ast-grep/rules/gha-standard-concurrency.yaml index 8a5b1df..2713222 100644 --- a/config/ast-grep/rules/gha-standard-concurrency.yaml +++ b/config/ast-grep/rules/gha-standard-concurrency.yaml @@ -2,16 +2,13 @@ id: gha-standard-concurrency language: yaml severity: error message: | - Every GitHub Actions workflow must declare the standard top-level - concurrency block exactly: + Every GitHub Actions workflow must declare the standard top-level concurrency block exactly: concurrency: group: "${{ github.workflow }}-${{ github.ref }}" cancel-in-progress: ${{ startsWith(github.ref, 'refs/pull/') }} - This groups runs per workflow per ref, and cancels older in-progress runs - on a PR ref only -- so a push to main never cancels its predecessor (lest - the merge-queue lose a result), but PR pushes don't burn runner minutes on - superseded commits. Deviating from this group/key shape leads to - surprising cancellation behaviour. + This groups runs per workflow per ref, and cancels older in-progress runs on a PR ref only -- so a push to main never + cancels its predecessor (lest the merge-queue lose a result), but PR pushes don't burn runner minutes on superseded + commits. Deviating from this group/key shape leads to surprising cancellation behaviour. files: - .github/workflows/*.yaml # Fire on any workflow whose stream does NOT contain the exact concurrency block. diff --git a/config/ast-grep/rules/gha-strategy-fail-fast-false.yaml b/config/ast-grep/rules/gha-strategy-fail-fast-false.yaml index ad408da..1d54723 100644 --- a/config/ast-grep/rules/gha-strategy-fail-fast-false.yaml +++ b/config/ast-grep/rules/gha-strategy-fail-fast-false.yaml @@ -2,11 +2,9 @@ id: gha-strategy-fail-fast-false language: yaml severity: error message: | - Any job that declares `strategy:` (matrix or otherwise) must also set - `fail-fast: false` inside it. GHA's default of `fail-fast: true` cancels - every other matrix entry the moment one fails, which hides the failure - modes specific to other OS / version / runner cells -- exactly the matrix - cells we added the matrix to exercise. Opt out explicitly: + Any job that declares `strategy:` (matrix or otherwise) must also set `fail-fast: false` inside it. GHA's default of + `fail-fast: true` cancels every other matrix entry the moment one fails, which hides the failure modes specific to + other OS / version / runner cells -- exactly the matrix cells we added the matrix to exercise. Opt out explicitly: strategy: fail-fast: false matrix: diff --git a/config/ast-grep/rules/no-allow-attributes.yaml b/config/ast-grep/rules/no-allow-attributes.yaml index 8959fa1..c562b73 100644 --- a/config/ast-grep/rules/no-allow-attributes.yaml +++ b/config/ast-grep/rules/no-allow-attributes.yaml @@ -2,15 +2,12 @@ id: no-allow-attributes language: Rust severity: error message: | - `#[allow()]` and `#![allow()]` are forbidden. Use - `#[expect(, reason = "...")]` (or `#![expect(...)]` at module - / crate scope). `#[expect]` requires the lint to actually fire, so - the suppression is removed automatically when the underlying issue - is fixed upstream -- `#[allow]` can sit silently forever. + `#[allow()]` and `#![allow()]` are forbidden. Use `#[expect(, reason = "...")]` (or + `#![expect(...)]` at module / crate scope). `#[expect]` requires the lint to actually fire, so the suppression is + removed automatically when the underlying issue is fixed upstream -- `#[allow]` can sit silently forever. - This rule complements the workspace's `clippy::allow_attributes` - lint, which doesn't fire on inner attributes (`#![allow(...)]`) -- - see . + This rule complements the workspace's `clippy::allow_attributes` lint, which doesn't fire on inner attributes + (`#![allow(...)]`) -- see . rule: any: - pattern: "#[allow($$$ARGS)]" diff --git a/config/ast-grep/rules/no-cargo-manifest-dir.yaml b/config/ast-grep/rules/no-cargo-manifest-dir.yaml index b177792..537ab7c 100644 --- a/config/ast-grep/rules/no-cargo-manifest-dir.yaml +++ b/config/ast-grep/rules/no-cargo-manifest-dir.yaml @@ -2,9 +2,8 @@ id: no-cargo-manifest-dir language: Rust severity: error message: | - `CARGO_MANIFEST_DIR` is forbidden outside the project-root helper. It points - at the crate directory, not the repository root, so reaching repo files - through it needs brittle `..` hops. Locate the repository root with + `CARGO_MANIFEST_DIR` is forbidden outside the project-root helper. It points at the crate directory, not the + repository root, so reaching repo files through it needs brittle `..` hops. Locate the repository root with `et_path::find_project_root_from_manifest()` in a `build.rs` (compile time) or `edge_toolkit::config::get_project_root()` (runtime) instead. rule: diff --git a/config/ast-grep/rules/no-consecutive-expect.yaml b/config/ast-grep/rules/no-consecutive-expect.yaml index 835a710..3285aa5 100644 --- a/config/ast-grep/rules/no-consecutive-expect.yaml +++ b/config/ast-grep/rules/no-consecutive-expect.yaml @@ -2,11 +2,10 @@ id: no-consecutive-expect language: Rust severity: error message: | - Consecutive `#[expect(...)]` attributes on the same item. Merge them into a - single `#[expect(lint_a, lint_b, reason = "...")]` (one attribute can list - several lints). Stacking separate `#[expect]`s -- even with a comment between - them -- fragments the suppression and makes it easy to leave one behind when - the underlying issue is fixed. Applies to inner `#![expect(...)]` too. + Consecutive `#[expect(...)]` attributes on the same item. Merge them into a single + `#[expect(lint_a, lint_b, reason = "...")]` (one attribute can list several lints). Stacking separate `#[expect]`s + -- even with a comment between them -- fragments the suppression and makes it easy to leave one behind when the + underlying issue is fixed. Applies to inner `#![expect(...)]` too. rule: any: # Match an outer `#[expect(...)]` whose nearest non-comment predecessor sibling is itself one. diff --git a/config/ast-grep/rules/no-current-dir.yaml b/config/ast-grep/rules/no-current-dir.yaml index 79acf78..1b2c4fe 100644 --- a/config/ast-grep/rules/no-current-dir.yaml +++ b/config/ast-grep/rules/no-current-dir.yaml @@ -2,12 +2,10 @@ id: no-current-dir language: Rust severity: error message: | - `std::env::current_dir()` is forbidden outside the project-root helper. The - current directory is not a reliable anchor for repository files -- it depends - on where the binary happens to be invoked. Use - `edge_toolkit::config::get_project_root()` at runtime, or - `et_path::find_project_root()` from a build script at compile time; both - locate the repository root via the `.dprint.jsonc` marker. + `std::env::current_dir()` is forbidden outside the project-root helper. The current directory is not a reliable + anchor for repository files -- it depends on where the binary happens to be invoked. Use + `edge_toolkit::config::get_project_root()` at runtime, or `et_path::find_project_root()` from a build script at + compile time; both locate the repository root via the `.dprint.jsonc` marker. rule: any: - pattern: std::env::current_dir() diff --git a/config/ast-grep/rules/no-doctest.yaml b/config/ast-grep/rules/no-doctest.yaml index 3299c37..4bcc110 100644 --- a/config/ast-grep/rules/no-doctest.yaml +++ b/config/ast-grep/rules/no-doctest.yaml @@ -2,8 +2,8 @@ id: no-doctest language: Rust severity: error message: | - Doctests are forbidden. Move runnable examples into a `tests/` file - (every test file must start with `#![cfg(test)]`). + Doctests are forbidden. Move runnable examples into a `tests/` file (every test file must start with + `#![cfg(test)]`). rule: any: - kind: line_comment diff --git a/config/ast-grep/rules/no-extern-crate-self.yaml b/config/ast-grep/rules/no-extern-crate-self.yaml index 888eb56..1295a56 100644 --- a/config/ast-grep/rules/no-extern-crate-self.yaml +++ b/config/ast-grep/rules/no-extern-crate-self.yaml @@ -2,12 +2,10 @@ id: no-extern-crate-self language: Rust severity: error message: | - `extern crate self as ;` re-introduces the crate's own name as a path - root inside its library, letting code write `::foo` instead of - `crate::foo` / `self::foo`. Without it, rustc already rejects own-name paths - in lib code (E0433); the alias is only needed by proc-macro crates whose - generated code emits `::::...`. This workspace has none, so use - `crate::` / `self::` for local paths instead. + `extern crate self as ;` re-introduces the crate's own name as a path root inside its library, letting code + write `::foo` instead of `crate::foo` / `self::foo`. Without it, rustc already rejects own-name paths in lib + code (E0433); the alias is only needed by proc-macro crates whose generated code emits `::::...`. This + workspace has none, so use `crate::` / `self::` for local paths instead. rule: kind: extern_crate_declaration regex: "extern crate self" diff --git a/config/ast-grep/rules/no-inline-mod.yaml b/config/ast-grep/rules/no-inline-mod.yaml index 2d67213..662e3ab 100644 --- a/config/ast-grep/rules/no-inline-mod.yaml +++ b/config/ast-grep/rules/no-inline-mod.yaml @@ -2,8 +2,8 @@ id: no-inline-mod language: Rust severity: error message: | - Inline `mod X { ... }` blocks are forbidden. Move the module body into a - separate file (`X.rs` or `X/mod.rs`) and declare it with `mod X;` instead. + Inline `mod X { ... }` blocks are forbidden. Move the module body into a separate file (`X.rs` or `X/mod.rs`) and + declare it with `mod X;` instead. rule: kind: mod_item has: diff --git a/config/ast-grep/rules/no-map-err.yaml b/config/ast-grep/rules/no-map-err.yaml index ba93e30..ca85573 100644 --- a/config/ast-grep/rules/no-map-err.yaml +++ b/config/ast-grep/rules/no-map-err.yaml @@ -2,12 +2,10 @@ id: no-map-err language: Rust severity: error message: | - `.map_err(...)` is forbidden. Convert through `From` (thiserror's - `#[from]` / `#[error(transparent)]`) and rely on `?`, or define an - extension trait whose impl carries the only `map_err` for the pattern. - At actix-web boundaries, derive `ResponseError` via - `actix-web-thiserror` instead of building `actix_web::error::Error*` - values inside `map_err` closures. + `.map_err(...)` is forbidden. Convert through `From` (thiserror's `#[from]` / `#[error(transparent)]`) and rely on + `?`, or define an extension trait whose impl carries the only `map_err` for the pattern. At actix-web boundaries, + derive `ResponseError` via `actix-web-thiserror` instead of building `actix_web::error::Error*` values inside + `map_err` closures. rule: pattern: $X.map_err($$$ARGS) ignores: diff --git a/config/ast-grep/rules/no-mixed-doc-line-comments.yaml b/config/ast-grep/rules/no-mixed-doc-line-comments.yaml index 2f6143d..5a8e5f5 100644 --- a/config/ast-grep/rules/no-mixed-doc-line-comments.yaml +++ b/config/ast-grep/rules/no-mixed-doc-line-comments.yaml @@ -2,9 +2,8 @@ id: no-mixed-doc-line-comments language: Rust severity: error message: | - Doc comment (`///`) is adjacent to a regular `//` comment. Either turn the - regular comment into a doc comment (`///`) or move it away from the doc block - -- readers expect a contiguous `///` run to be the item's rustdoc. + Doc comment (`///`) is adjacent to a regular `//` comment. Either turn the regular comment into a doc comment + (`///`) or move it away from the doc block -- readers expect a contiguous `///` run to be the item's rustdoc. rule: any: - kind: line_comment diff --git a/config/ast-grep/rules/no-non-ascii.yaml b/config/ast-grep/rules/no-non-ascii.yaml index 715b3ac..5caac17 100644 --- a/config/ast-grep/rules/no-non-ascii.yaml +++ b/config/ast-grep/rules/no-non-ascii.yaml @@ -2,10 +2,9 @@ id: no-non-ascii language: Rust severity: error message: | - Non-ASCII character in a string literal or doc comment. Stick to ASCII - in source: use `--` for an em-dash, `->` for an arrow, and straight - quotes for curly quotes, etc. ASCII-only source keeps terminals, log - scrapers, and `grep` reading the same bytes the editor shows. + Non-ASCII character in a string literal or doc comment. Stick to ASCII in source: use `--` for an em-dash, `->` + for an arrow, and straight quotes for curly quotes, etc. ASCII-only source keeps terminals, log scrapers, and + `grep` reading the same bytes the editor shows. rule: any: - kind: string_literal diff --git a/config/ast-grep/rules/no-relative-path-literal.yaml b/config/ast-grep/rules/no-relative-path-literal.yaml index d6d68cf..cd9d863 100644 --- a/config/ast-grep/rules/no-relative-path-literal.yaml +++ b/config/ast-grep/rules/no-relative-path-literal.yaml @@ -2,12 +2,10 @@ id: no-relative-path-literal language: Rust severity: error message: | - Relative path literals ("." / ".." / "./..." / "../...") are forbidden: they - hardcode a location relative to the process's current directory or this - crate's directory, which is brittle. Anchor paths to the repository root - instead -- `edge_toolkit::config::get_project_root()` (runtime) or - `et_path::find_project_root_from_manifest()` (build scripts) -- and join the - remainder onto it. (Char literals like `'.'` for splitting are unaffected.) + Relative path literals ("." / ".." / "./..." / "../...") are forbidden: they hardcode a location relative to the + process's current directory or this crate's directory, which is brittle. Anchor paths to the repository root + instead -- `edge_toolkit::config::get_project_root()` (runtime) or `et_path::find_project_root_from_manifest()` + (build scripts) -- and join the remainder onto it. (Char literals like `'.'` for splitting are unaffected.) rule: kind: string_content regex: '^\.\.?(/.*)?$' diff --git a/config/ast-grep/rules/no-result-alias.yaml b/config/ast-grep/rules/no-result-alias.yaml index f6d46c4..befe7f5 100644 --- a/config/ast-grep/rules/no-result-alias.yaml +++ b/config/ast-grep/rules/no-result-alias.yaml @@ -2,9 +2,8 @@ id: no-result-alias language: Rust severity: error message: | - Defining a `type Result<...>` alias shadows `std::result::Result` at file - scope and makes every `Result` ambiguous to readers. Spell the error - type at each call site instead (`Result`). + Defining a `type Result<...>` alias shadows `std::result::Result` at file scope and makes every `Result` + ambiguous to readers. Spell the error type at each call site instead (`Result`). rule: all: - kind: type_item diff --git a/config/ast-grep/rules/no-result-string-err.yaml b/config/ast-grep/rules/no-result-string-err.yaml index 23ee20f..70757c6 100644 --- a/config/ast-grep/rules/no-result-string-err.yaml +++ b/config/ast-grep/rules/no-result-string-err.yaml @@ -2,10 +2,9 @@ id: no-result-string-err language: Rust severity: error message: | - `Result<_, String>` flattens any typed error to a free-form string at - the point of failure -- losing the source variant, the ability to - branch on it, and the chain of causes. Define a `thiserror` enum (or - reuse one of the workspace's `*Error` types) and return that instead. + `Result<_, String>` flattens any typed error to a free-form string at the point of failure -- losing the source + variant, the ability to branch on it, and the chain of causes. Define a `thiserror` enum (or reuse one of the + workspace's `*Error` types) and return that instead. rule: all: - kind: generic_type diff --git a/config/ast-grep/rules/no-shadow-result.yaml b/config/ast-grep/rules/no-shadow-result.yaml index e31a892..7718d9e 100644 --- a/config/ast-grep/rules/no-shadow-result.yaml +++ b/config/ast-grep/rules/no-shadow-result.yaml @@ -2,11 +2,9 @@ id: no-shadow-result language: Rust severity: error message: | - Don't shadow `std::result::Result` at file scope -- every bare `Result` - then becomes ambiguous to readers. Either spell the error type at each - call site (`Result`), reference the local alias by its - qualified path (`crate::Result`, `et_int_gen::Result`), or alias - the import (`use foo::Result as FooResult;`). + Don't shadow `std::result::Result` at file scope -- every bare `Result` then becomes ambiguous to readers. + Either spell the error type at each call site (`Result`), reference the local alias by its qualified + path (`crate::Result`, `et_int_gen::Result`), or alias the import (`use foo::Result as FooResult;`). rule: any: - all: diff --git a/config/ast-grep/rules/no-std-env-var.yaml b/config/ast-grep/rules/no-std-env-var.yaml index 9a702b4..3f22191 100644 --- a/config/ast-grep/rules/no-std-env-var.yaml +++ b/config/ast-grep/rules/no-std-env-var.yaml @@ -2,18 +2,14 @@ id: no-std-env-var language: Rust severity: error message: | - `std::env::var` / `std::env::var_os` is forbidden for app config. Read - configuration through a `serde`-derived struct deserialised with - `serde-env` (see the runner `config` modules and - `et-ws-runner-common::config`), so every variable is declared in one typed - place with defaults and validation. Exemptions, intentionally narrow: + `std::env::var` / `std::env::var_os` is forbidden for app config. Read configuration through a `serde`-derived + struct deserialised with `serde-env` (see the runner `config` modules and `et-ws-runner-common::config`), so every + variable is declared in one typed place with defaults and validation. Exemptions, intentionally narrow: - `RUST_*` (e.g. `RUST_LOG`, consumed by tracing) -- diagnostics. - - `ET_TEST_*` -- test-only switches; production binaries must NOT branch - on these. The integration test in services/ws-wasi-runner/tests/ - sets one to opt the spawned runner into a macOS-only fast-exit path. - - `MISE_*` (e.g. `MISE_ENV`) -- the mise tool's own state, not app config; - reading these lets helpers like `mise_env_includes` reflect tooling - behaviour without round-tripping through a struct. + - `ET_TEST_*` -- test-only switches; production binaries must NOT branch on these. The integration test in + services/ws-wasi-runner/tests/ sets one to opt the spawned runner into a macOS-only fast-exit path. + - `MISE_*` (e.g. `MISE_ENV`) -- the mise tool's own state, not app config; reading these lets helpers like + `mise_env_includes` reflect tooling behaviour without round-tripping through a struct. rule: any: - pattern: std::env::var($ARG) diff --git a/config/ast-grep/rules/no-std-fs.yaml b/config/ast-grep/rules/no-std-fs.yaml index 0e3e5aa..8275841 100644 --- a/config/ast-grep/rules/no-std-fs.yaml +++ b/config/ast-grep/rules/no-std-fs.yaml @@ -2,10 +2,9 @@ id: no-std-fs language: Rust severity: error message: | - `std::fs` is forbidden -- its `io::Error`s carry no path, so a failure - reads "No such file or directory" with no clue which file. Use `fs_err` - (`fs-err` workspace dep), whose drop-in wrappers attach the offending path - to every error. Import it as `use fs_err as fs;` and call `fs::read`, - `fs::write`, `fs::File::open`, etc., exactly as you would `std::fs`. + `std::fs` is forbidden -- its `io::Error`s carry no path, so a failure reads "No such file or directory" with no + clue which file. Use `fs_err` (`fs-err` workspace dep), whose drop-in wrappers attach the offending path to every + error. Import it as `use fs_err as fs;` and call `fs::read`, `fs::write`, `fs::File::open`, etc., exactly as you + would `std::fs`. rule: pattern: std::fs diff --git a/config/ast-grep/rules/no-string-literal-line-continuation.yaml b/config/ast-grep/rules/no-string-literal-line-continuation.yaml index 4044df8..44dbdf1 100644 --- a/config/ast-grep/rules/no-string-literal-line-continuation.yaml +++ b/config/ast-grep/rules/no-string-literal-line-continuation.yaml @@ -2,14 +2,11 @@ id: no-string-literal-line-continuation language: Rust severity: error message: | - Backslash-newline inside a Rust string literal is a line continuation - (the `\` + newline + leading whitespace all get elided at parse time). - Banned per the repo-wide no-trailing-backslash rule. Use `concat!("a", - "b")` to assemble a multi-line string literal, keep it on one line, or - drop to a raw string `r"..."` if the content really needs literal - backslashes at line ends. The semgrep `no-trailing-backslash` rule - catches the same pattern repo-wide via generic regex; this ast-grep - variant is the Rust-AST-aware companion (and runs faster). + Backslash-newline inside a Rust string literal is a line continuation (the `\` + newline + leading whitespace all + get elided at parse time). Banned per the repo-wide no-trailing-backslash rule. Use `concat!("a", "b")` to + assemble a multi-line string literal, keep it on one line, or drop to a raw string `r"..."` if the content really + needs literal backslashes at line ends. The semgrep `no-trailing-backslash` rule catches the same pattern + repo-wide via generic regex; this ast-grep variant is the Rust-AST-aware companion (and runs faster). rule: kind: string_literal regex: '\\\n' diff --git a/config/ast-grep/rules/prefer-self-mod-use.yaml b/config/ast-grep/rules/prefer-self-mod-use.yaml index 34bc59a..cb3d445 100644 --- a/config/ast-grep/rules/prefer-self-mod-use.yaml +++ b/config/ast-grep/rules/prefer-self-mod-use.yaml @@ -17,8 +17,8 @@ id: prefer-self-mod-use-bare-chain language: Rust severity: error message: | - This file declares `mod $X;` and references it via `use $X::...`. - Write `use self::$X::...` to make the local reference explicit. + This file declares `mod $X;` and references it via `use $X::...`. Write `use self::$X::...` to make the local + reference explicit. rule: pattern: use $X::$$$REST; inside: @@ -39,8 +39,8 @@ id: prefer-self-mod-use-bare-group language: Rust severity: error message: | - This file declares `mod $X;` and references it via `use $X::{...}`. - Write `use self::$X::{...}` to make the local reference explicit. + This file declares `mod $X;` and references it via `use $X::{...}`. Write `use self::$X::{...}` to make the local + reference explicit. rule: pattern: use $X::{$$$REST}; inside: @@ -61,8 +61,8 @@ id: prefer-self-mod-use-pub-chain language: Rust severity: error message: | - This file declares `mod $X;` and re-exports it via `pub use $X::...`. - Write `pub use self::$X::...` to make the local reference explicit. + This file declares `mod $X;` and re-exports it via `pub use $X::...`. Write `pub use self::$X::...` to make the local + reference explicit. rule: all: - pattern: pub use $X::$$$REST; @@ -85,8 +85,8 @@ id: prefer-self-mod-use-pub-group language: Rust severity: error message: | - This file declares `mod $X;` and re-exports it via `pub use $X::{...}`. - Write `pub use self::$X::{...}` to make the local reference explicit. + This file declares `mod $X;` and re-exports it via `pub use $X::{...}`. Write `pub use self::$X::{...}` to make the + local reference explicit. rule: all: - pattern: pub use $X::{$$$REST}; @@ -109,8 +109,8 @@ id: prefer-self-mod-use-pubcrate-chain language: Rust severity: error message: | - This file declares `mod $X;` and re-exports it via `pub(crate) use $X::...`. - Write `pub(crate) use self::$X::...` to make the local reference explicit. + This file declares `mod $X;` and re-exports it via `pub(crate) use $X::...`. Write `pub(crate) use self::$X::...` to + make the local reference explicit. rule: all: - pattern: pub(crate) use $X::$$$REST; @@ -133,8 +133,8 @@ id: prefer-self-mod-use-pubcrate-group language: Rust severity: error message: | - This file declares `mod $X;` and re-exports it via `pub(crate) use $X::{...}`. - Write `pub(crate) use self::$X::{...}` to make the local reference explicit. + This file declares `mod $X;` and re-exports it via `pub(crate) use $X::{...}`. Write `pub(crate) use self::$X::{...}` + to make the local reference explicit. rule: all: - pattern: pub(crate) use $X::{$$$REST}; @@ -157,8 +157,8 @@ id: prefer-self-mod-use-pubsuper-chain language: Rust severity: error message: | - This file declares `mod $X;` and re-exports it via `pub(super) use $X::...`. - Write `pub(super) use self::$X::...` to make the local reference explicit. + This file declares `mod $X;` and re-exports it via `pub(super) use $X::...`. Write `pub(super) use self::$X::...` to + make the local reference explicit. rule: all: - pattern: pub(super) use $X::$$$REST; @@ -181,8 +181,8 @@ id: prefer-self-mod-use-pubsuper-group language: Rust severity: error message: | - This file declares `mod $X;` and re-exports it via `pub(super) use $X::{...}`. - Write `pub(super) use self::$X::{...}` to make the local reference explicit. + This file declares `mod $X;` and re-exports it via `pub(super) use $X::{...}`. Write `pub(super) use self::$X::{...}` + to make the local reference explicit. rule: all: - pattern: pub(super) use $X::{$$$REST}; diff --git a/config/ast-grep/rules/use-mod-order.yaml b/config/ast-grep/rules/use-mod-order.yaml index ae32860..8b09865 100644 --- a/config/ast-grep/rules/use-mod-order.yaml +++ b/config/ast-grep/rules/use-mod-order.yaml @@ -2,9 +2,8 @@ id: use-mod-order language: Rust severity: error message: | - Order top-level items: external `use` (std / third-party) first, then - `mod` / `pub mod` declarations, then local `use self::` / `crate::` / - `super::`. So an external `use` must not appear after a `mod`, and a + Order top-level items: external `use` (std / third-party) first, then `mod` / `pub mod` declarations, then local + `use self::` / `crate::` / `super::`. So an external `use` must not appear after a `mod`, and a `self`/`crate`/`super` `use` must not appear before a `mod`. rule: any: diff --git a/config/conftest/policy/dockerfile/dockerfile.rego b/config/conftest/policy/dockerfile/dockerfile.rego index 6d88f23..a6fe8eb 100644 --- a/config/conftest/policy/dockerfile/dockerfile.rego +++ b/config/conftest/policy/dockerfile/dockerfile.rego @@ -65,7 +65,7 @@ deny contains msg if { ) } -# RUN heredocs must invoke `bash` and use a QUOTED delimiter (`RUN bash <<'EOF'`). Two-part rule: +# RUN heredocs must invoke `bash` and use a QUOTED delimiter (`RUN bash <<'EOF'`). Two-part rule. # # 1. bash as the interpreter. BuildKit's default heredoc shell is `/bin/sh`, which on Debian/Ubuntu is dash -- and # dash rejects `set -euo pipefail` with `Illegal option -o pipefail` (exit 2). Routing the heredoc body through diff --git a/config/conftest/policy/gha_combined/gha_combined.rego b/config/conftest/policy/gha_combined/gha_combined.rego index 260efce..138e7a6 100644 --- a/config/conftest/policy/gha_combined/gha_combined.rego +++ b/config/conftest/policy/gha_combined/gha_combined.rego @@ -1,5 +1,6 @@ -# GitHub Actions workflow rules that need the file path (not just the parsed body), so they run under `--combine`: -# conftest hands every file in as an array of `{path, contents}`. Per-file rules live in `gha.rego`. +# GitHub Actions workflow rules that need the file path, not just the parsed body. +# They run under conftest's `--combine` mode, which hands every file in as an array of `{path, contents}`. +# Per-file rules live in `gha.rego`. package gha_combined # Every workflow must trigger on pull_request so PR CI runs it when relevant code changes. diff --git a/config/deny.toml b/config/deny.toml index 249861d..64d398d 100644 --- a/config/deny.toml +++ b/config/deny.toml @@ -92,7 +92,7 @@ allow = [ "Zlib", ] -# Explicit per-crate clarifications: +# Explicit per-crate clarifications. # cargo-deny otherwise emits a `no-license-field` warning when a crate's manifest uses `license-file` instead # of an SPDX `license` expression. Each entry below has been verified against the on-disk LICENSE in # ~/.cargo/registry. The mise `cargo-deny` task escalates `no-license-field` to a hard error so a new diff --git a/config/lychee.toml b/config/lychee.toml index 665248b..80d3c71 100644 --- a/config/lychee.toml +++ b/config/lychee.toml @@ -26,7 +26,7 @@ exclude_private = true # those paths). exclude_path = ["data", "generated", "node_modules", "target"] -# URL patterns (regex) to skip, none of which are real links: +# URL patterns (regex) to skip, none of which are real links. # doc/test placeholder hosts (e.g. http://host:8080/) and `{...}` format-string templates # (.../{repo}/{git_ref}/...). exclude = ['\{|%7B', '^https?://host[:/]'] diff --git a/config/semgrep/comment-summary-line.yaml b/config/semgrep/comment-summary-line.yaml index 3b5e6bc..ebbc3ce 100644 --- a/config/semgrep/comment-summary-line.yaml +++ b/config/semgrep/comment-summary-line.yaml @@ -17,9 +17,14 @@ rules: # that is blank or whose first non-whitespace char is not `#` (`[^#\s]`). Keying off the first non-whitespace # char -- not the first raw char -- is what stops an INDENTED comment line (whose leading whitespace would # otherwise read as a non-comment line) from anchoring a spurious match: interior continuation lines, which need - # not end in a period, stay unflagged regardless of indentation. The trailing `[^.\n:]` is the offending final - # character of the summary line. - pattern-regex: '(?m)(?:\A|^[ \t]*(?:[^#\s][^\n]*)?\n)[ \t]*#[^\n]*[^.\n:]\n[ \t]*#' + # not end in a period, stay unflagged regardless of indentation. The check is two alternatives under + # `pattern-either` (kept separate so each regex line stays within the 120-char limit): the first flags a + # final char that is neither `.` nor `:` (`[^.\n:]`); the second flags a final `:` whose continuation line is + # NOT an enumeration item -- the negative lookahead `(?![ \t]*([-*]|[0-9]+[.)])[ \t])` reserves a trailing + # `:` for introducing a real list (`-`, `*`, or `N.`/`N)`), so a `:` followed by prose is still flagged. + pattern-either: + - pattern-regex: '(?m)(?:\A|^[ \t]*(?:[^#\s][^\n]*)?\n)[ \t]*#[^\n]*[^.\n:]\n[ \t]*#' + - pattern-regex: '(?m)(?:\A|^[ \t]*(?:[^#\s][^\n]*)?\n)[ \t]*#[^\n]*:\n[ \t]*#(?![ \t]*([-*]|[0-9]+[.)])[ \t])' message: >- A multi-line `#` comment block (TOML, YAML, Rego, or Dockerfile) must open with a single summary line ending in a full stop (PEP 257 style). The summary diff --git a/config/taplo.toml b/config/taplo.toml index f68b254..be04d50 100644 --- a/config/taplo.toml +++ b/config/taplo.toml @@ -48,7 +48,7 @@ exclude = ["Cargo.toml"] include = ["**/Cargo.toml"] schema = { path = "config/taplo/require-workspace-deps.schema.json" } -# Banned dep names across every dep table: +# Banned dep names across every dep table. # `[dependencies]`, `[dev-dependencies]`, `[build-dependencies]`, # `[workspace.dependencies]`, and the `[target.*]` variants. [[rule]] diff --git a/ruff.toml b/ruff.toml index 7dcf38c..a0e1fb9 100644 --- a/ruff.toml +++ b/ruff.toml @@ -16,7 +16,7 @@ line-length = 120 extend-exclude = ["componentize_py_async_support", "wit_world"] [lint] -# On top of the default rule set (E, F): +# On top of the default rule set (E, F). # "I" -- isort import sorting. `ruff format` only handles whitespace; the "I" rules give deterministic # grouping. datamodel-codegen's ruff formatter doesn't sort imports, so without this the generated # messages.py drifts between regens. diff --git a/services/ws-pyo3-runner/Cargo.toml b/services/ws-pyo3-runner/Cargo.toml index 7ab29ba..64b220a 100644 --- a/services/ws-pyo3-runner/Cargo.toml +++ b/services/ws-pyo3-runner/Cargo.toml @@ -19,7 +19,7 @@ edge-toolkit.workspace = true # Typed ws-server REST client (storage GET/PUT). # `tracing` injects the W3C traceparent on each request, matching the other native runners. et-rest-client = { workspace = true, features = ["tracing"] } -# Shared runner bootstrap: +# Shared runner bootstrap. # env config (RUNNER_*/WS_*), `derive_http_base`, `collect_byte_stream`. et-ws-runner-common.workspace = true futures-util.workspace = true From fd64c9b4eb09d3fe3f472e492e3cbd5ba852c609 Mon Sep 17 00:00:00 2001 From: John Vandenberg Date: Mon, 22 Jun 2026 19:38:33 +0800 Subject: [PATCH 133/133] slightly more reflow --- .mise/config.toml | 4 ++-- config/semgrep/cargo-toml.yaml | 4 ++-- config/semgrep/comment-summary-line.yaml | 7 +++---- .../dockerfile-heredoc-set-euo-pipefail.yaml | 8 ++++---- .../semgrep/dockerfile-no-banner-comment.yaml | 4 ++-- config/semgrep/mise-config.yaml | 20 +++++++++---------- config/semgrep/no-non-ascii.yaml | 9 +++++---- config/semgrep/no-todo.yaml | 7 ++++--- config/semgrep/no-trailing-backslash.yaml | 6 +++--- config/semgrep/prefer-yaml-toml.yaml | 5 +++-- 10 files changed, 38 insertions(+), 36 deletions(-) diff --git a/.mise/config.toml b/.mise/config.toml index 9e4b773..4edcda7 100644 --- a/.mise/config.toml +++ b/.mise/config.toml @@ -98,8 +98,8 @@ ripgrep = "latest" "github:caldempsey/parfit" = "latest" "github:grok-rs/waitup" = "latest" # ONNX Runtime prebuilt -- ort-sys 2.0.0-rc.10 binds to ORT 1.22.x. -"github:microsoft/onnxruntime" = "1.22.0" "cargo:ryl" = { version = "latest", os = ["macos/x64"] } +"github:microsoft/onnxruntime" = "1.22.0" "github:owenlamont/ryl" = { version = "latest", os = ["linux", "macos/arm64", "windows"] } "github:wasm-bindgen/wasm-bindgen" = "0.2.122" # Sole consumer right now is the `go:` backend that source-builds jqfmt. @@ -119,8 +119,8 @@ pipx = { version = "latest", os = ["linux", "macos"] } # Pure-Python fixture for the et-ws-pyo3-runner `cowsay` test. # Proves a mise-installed python package lands on the embedded interpreter's # sys.path (see `edge_toolkit::config::mise_python_site_packages`). -"pipx:cowsay" = "latest" gh = "latest" +"pipx:cowsay" = "latest" "pipx:semgrep" = "latest" # Aqua has no pnpm darwin/amd64 prebuilt. # The `npm:pnpm` fallback for macos/x64 lives in the `js` env (config.js.toml). diff --git a/config/semgrep/cargo-toml.yaml b/config/semgrep/cargo-toml.yaml index 80607b3..01e69f4 100644 --- a/config/semgrep/cargo-toml.yaml +++ b/config/semgrep/cargo-toml.yaml @@ -7,6 +7,6 @@ rules: pattern: "$DEP = { workspace = true }" fix: "$DEP.workspace = true" message: >- - Use the dotted form `dep.workspace = true` instead of the inline table `dep = { workspace = true }`. (Inline - tables with extra keys, e.g. `dep = { workspace = true, features = [...] }`, are fine.) + Use the dotted form `dep.workspace = true` instead of the inline table `dep = { workspace = true }`. + (Inline tables with extra keys, e.g. `dep = { workspace = true, features = [...] }`, are fine.) severity: ERROR diff --git a/config/semgrep/comment-summary-line.yaml b/config/semgrep/comment-summary-line.yaml index ebbc3ce..bce5bec 100644 --- a/config/semgrep/comment-summary-line.yaml +++ b/config/semgrep/comment-summary-line.yaml @@ -26,9 +26,8 @@ rules: - pattern-regex: '(?m)(?:\A|^[ \t]*(?:[^#\s][^\n]*)?\n)[ \t]*#[^\n]*[^.\n:]\n[ \t]*#' - pattern-regex: '(?m)(?:\A|^[ \t]*(?:[^#\s][^\n]*)?\n)[ \t]*#[^\n]*:\n[ \t]*#(?![ \t]*([-*]|[0-9]+[.)])[ \t])' message: >- - A multi-line `#` comment block (TOML, YAML, Rego, or Dockerfile) must open with a - single summary line ending in a full stop (PEP 257 style). The summary - describes the whole block in one sentence; continuation lines follow. - End the first comment line with `.` (a `:` introducing an enumeration + A multi-line `#` comment block must open with a single summary line ending in a full stop (PEP 257 style). + This applies to TOML, YAML, Rego, and Dockerfile comments. The summary describes the whole block in one + sentence; continuation lines follow. End the first comment line with `.` (a `:` introducing an enumeration is also accepted). severity: ERROR diff --git a/config/semgrep/dockerfile-heredoc-set-euo-pipefail.yaml b/config/semgrep/dockerfile-heredoc-set-euo-pipefail.yaml index c6cafea..6377cb0 100644 --- a/config/semgrep/dockerfile-heredoc-set-euo-pipefail.yaml +++ b/config/semgrep/dockerfile-heredoc-set-euo-pipefail.yaml @@ -12,8 +12,8 @@ rules: # line here. pattern-regex: "^RUN[^\\n]*<<'?[A-Z]+'?[^\\n]*\\n(?!set -euo pipefail$)" message: >- - Dockerfile RUN heredocs must begin with `set -euo pipefail` as the first body line. BuildKit's default heredoc - shell carries neither -e nor -o pipefail (and dash on Debian/Ubuntu doesn't support -o pipefail at all), so the - strict-mode invariant has to be re-declared per body. Paired with the conftest rule that requires - `RUN < `# text`) can't be expressed as an autofix here. message: >- - Decorative `# --- text ---` banner comments are not allowed in Dockerfiles. Use a plain, concise one-line - comment (`# text`) instead: drop the surrounding dash runs, keeping the inner text. + Decorative `# --- text ---` banner comments are not allowed in Dockerfiles. + Use a plain, concise one-line comment (`# text`) instead: drop the surrounding dash runs, keeping the inner text. severity: ERROR diff --git a/config/semgrep/mise-config.yaml b/config/semgrep/mise-config.yaml index be3a903..7fecbd6 100644 --- a/config/semgrep/mise-config.yaml +++ b/config/semgrep/mise-config.yaml @@ -6,8 +6,8 @@ rules: - "/.mise/config*.toml" pattern-regex: 'description = """' message: >- - mise task `description` fields must be single-line `"..."` strings, not multi-line `"""..."""`. Keep the - description short enough to fit on one line (under the 120-char limit). + mise task `description` fields must be single-line `"..."` strings, not multi-line `"""..."""`. + Keep the description short enough to fit on one line (under the 120-char limit). severity: ERROR - id: multiline-task-run-needs-bash-shell @@ -17,11 +17,11 @@ rules: - "/.mise/config*.toml" pattern-regex: 'run = ("""|'''''')\n(?:(?!\1\n).*\n)*?\1\n(?!shell = "bash -x?euo pipefail -c")' message: >- - A mise task with a multiline `run` must be immediately followed by `shell = "bash -euo pipefail -c"` (on the - line right after the closing `"""`/`'''`), so a failing command fails the task instead of being masked. The - `-x` variant (`bash -xeuo pipefail -c`) is also accepted (xtrace, used on the Windows OS-specific preinstall - for failure-diagnosis transcripts). Put `shell` directly after `run`. bash is installed before any task on - Windows (see preinstall), so even the setup tasks can use it. + A mise task with a multiline `run` must be immediately followed by `shell = "bash -euo pipefail -c"`. + Put `shell` on the line right after the closing `"""`/`'''`, so a failing command fails the task instead of + being masked. The `-x` variant (`bash -xeuo pipefail -c`) is also accepted (xtrace, used on the Windows + OS-specific preinstall for failure-diagnosis transcripts). bash is installed before any task on Windows + (see preinstall), so even the setup tasks can use it. severity: ERROR - id: single-key-env-subtable-should-be-inline @@ -38,7 +38,7 @@ rules: # directive on the header. pattern-regex: '(?m)^\[tasks\.[^\]]+\.env\]\n(?:#[^\n]*\n)*[A-Z_][A-Z0-9_]*\s*=\s*[^\n]+\n+\[' message: >- - `[tasks.X.env]` subtable has a single `KEY = "value"` assignment. Inline it on the parent task as - `env = { KEY = "value" }` when the resulting line fits in 120 chars -- subtable form is reserved for - multi-key envs. + `[tasks.X.env]` subtable has a single `KEY = "value"` assignment. + Inline it on the parent task as `env = { KEY = "value" }` when the resulting line fits in 120 chars -- + subtable form is reserved for multi-key envs. severity: WARNING diff --git a/config/semgrep/no-non-ascii.yaml b/config/semgrep/no-non-ascii.yaml index 992bdb6..4ec0fed 100644 --- a/config/semgrep/no-non-ascii.yaml +++ b/config/semgrep/no-non-ascii.yaml @@ -20,8 +20,9 @@ rules: - "*.onnx" pattern-regex: '[^\x00-\x7F]' message: >- - Non-ASCII character. Keep source, config, and docs ASCII-only: use `--` for an em-dash, `->` for an arrow, - `...` for an ellipsis, `<=`/`!=`/`^2` for math glyphs, and straight quotes for curly quotes. ASCII keeps - terminals, log scrapers, and `grep` reading the same bytes the editor shows. Generated trees (generated/, - verification/, HELP.md, pkg/) are exempt because their generator owns the output. + Non-ASCII character. + Keep source, config, and docs ASCII-only: use `--` for an em-dash, `->` for an arrow, `...` for an + ellipsis, `<=`/`!=`/`^2` for math glyphs, and straight quotes for curly quotes. ASCII keeps terminals, log + scrapers, and `grep` reading the same bytes the editor shows. Generated trees (generated/, verification/, + HELP.md, pkg/) are exempt because their generator owns the output. severity: ERROR diff --git a/config/semgrep/no-todo.yaml b/config/semgrep/no-todo.yaml index ab7eb8a..855654d 100644 --- a/config/semgrep/no-todo.yaml +++ b/config/semgrep/no-todo.yaml @@ -25,7 +25,8 @@ rules: - "/config/semgrep/no-todo.yaml" pattern-regex: '\bTODO\b' message: >- - TODOs are not allowed in this repo. Either resolve the work now, or, if it must defer, add a single-line - justification in the surrounding code and add this file's path to no-todo.yaml's paths.exclude allowlist. Do - not duplicate a TODO across multiple files -- pick the single canonical home and let `grep TODO` find it there. + TODOs are not allowed in this repo. + Either resolve the work now, or, if it must defer, add a single-line justification in the surrounding code + and add this file's path to no-todo.yaml's paths.exclude allowlist. Do not duplicate a TODO across multiple + files -- pick the single canonical home and let `grep TODO` find it there. severity: ERROR diff --git a/config/semgrep/no-trailing-backslash.yaml b/config/semgrep/no-trailing-backslash.yaml index b8548b3..e3a19e9 100644 --- a/config/semgrep/no-trailing-backslash.yaml +++ b/config/semgrep/no-trailing-backslash.yaml @@ -14,7 +14,7 @@ rules: - "/verification/**/compose.yaml" pattern-regex: '\\[ \t]*$' message: >- - Trailing-backslash line continuations are not allowed in this repo. Keep it on one line, or use a - language-appropriate alternative (concat!(...) in Rust, a mise [vars] entry, a YAML/array form). If a file - genuinely needs them, add it to this rule's paths.exclude allowlist. + Trailing-backslash line continuations are not allowed in this repo. + Keep it on one line, or use a language-appropriate alternative (concat!(...) in Rust, a mise [vars] entry, + a YAML/array form). If a file genuinely needs them, add it to this rule's paths.exclude allowlist. severity: ERROR diff --git a/config/semgrep/prefer-yaml-toml.yaml b/config/semgrep/prefer-yaml-toml.yaml index 49d3bf8..f37a08f 100644 --- a/config/semgrep/prefer-yaml-toml.yaml +++ b/config/semgrep/prefer-yaml-toml.yaml @@ -19,6 +19,7 @@ rules: - "oxfmtrc.jsonc" # oxfmt: only JSON / JSONC supported pattern-regex: \A message: >- - Prefer YAML or TOML over JSON/JSONC/JSONL for config. If the tool requires a JSON-family format, prefer .jsonc - over .json so the config can carry inline comments, and add the file to this rule's paths.exclude allowlist. + Prefer YAML or TOML over JSON/JSONC/JSONL for config. + If the tool requires a JSON-family format, prefer .jsonc over .json so the config can carry inline comments, + and add the file to this rule's paths.exclude allowlist. severity: ERROR