From 48b6b097a2498d55a8a3af9da8fdb39c8ecd0ffc Mon Sep 17 00:00:00 2001 From: John Vandenberg Date: Mon, 3 Aug 2026 09:33:16 +0800 Subject: [PATCH 1/9] Require using aube --- .dockerignore | 1 - .editorconfig | 4 + .../actions/install-mise-tools/action.yaml | 10 +- .github/workflows/dependencies.yaml | 9 ++ .gitignore | 1 - .mise/config.js.toml | 70 ++++++++ .mise/config.toml | 59 ++++--- config/conftest/policy/mise/mise.rego | 4 + config/dprint.jsonc | 4 +- config/ls-lint.yaml | 4 +- .../mise-cargo-backend-allowlist.schema.json | 1 + config/typos.toml | 5 + package.json | 7 + pnpm-lock.yaml | 149 ++++++++++++++++++ pnpm-workspace.yaml | 9 ++ 15 files changed, 305 insertions(+), 32 deletions(-) create mode 100644 package.json create mode 100644 pnpm-lock.yaml create mode 100644 pnpm-workspace.yaml diff --git a/.dockerignore b/.dockerignore index d4e6650b..a3737975 100644 --- a/.dockerignore +++ b/.dockerignore @@ -23,7 +23,6 @@ services/ws-server/static/models/ **/.pytest_cache/ **/.python-version **/node_modules/ -**/pnpm-lock.yaml **/.venv/ # .NET build output. # `obj/` is safe globally (nothing tracked is named obj/), but `bin/` is scoped to the module so it never diff --git a/.editorconfig b/.editorconfig index e4a835c5..93215c55 100644 --- a/.editorconfig +++ b/.editorconfig @@ -83,3 +83,7 @@ max_line_length = unset [config/upstream-cache/data.toml] max_line_length = 200 + +# pnpm writes each package's `resolution: {integrity: sha512-...}` on one line, which exceeds the line length. +[**/pnpm-lock.yaml] +max_line_length = unset diff --git a/.github/actions/install-mise-tools/action.yaml b/.github/actions/install-mise-tools/action.yaml index 81ad4ae2..b2e2aba1 100644 --- a/.github/actions/install-mise-tools/action.yaml +++ b/.github/actions/install-mise-tools/action.yaml @@ -17,9 +17,8 @@ inputs: same step as mise. Forwarded to install-mise's `extra-tools` input. NOT used for `aube` -- install-action's manifest expects an `aubr` binary that recent aube releases don't ship, so the install resolves - to a cargo source-build that flakes on crates.io SSL. The aube path - below uses the original `mise run setup-aube` (npm-backed, allowed - to fail) which has been the reliable install method. + to a cargo source-build that flakes on crates.io SSL. aube installs + via `mise run setup-aube` below, off its `[tools]` pin. required: false default: "" @@ -38,13 +37,12 @@ runs: with: install-action-tools: ${{ inputs.install-action-tools }} - # Optional npm backend, installed before the main `mise install`. + # The npm: backend, installed before the main `mise install`. # Only useful when js env is loaded (it's the backend for npm:* tools, all of which live in config.js.toml); skip # otherwise to avoid the install cost on workflows that don't need any npm: install. See [tasks.setup-aube] in # .mise/config.toml for the full rationale. - - name: Install aube (optional npm backend, allowed to fail) + - name: Install aube npm backend if: contains(env.MISE_ENV, 'js') - continue-on-error: true shell: bash --noprofile --norc -euo pipefail {0} env: GITHUB_TOKEN: ${{ inputs.github-token }} diff --git a/.github/workflows/dependencies.yaml b/.github/workflows/dependencies.yaml index c6201fe6..eba76f17 100644 --- a/.github/workflows/dependencies.yaml +++ b/.github/workflows/dependencies.yaml @@ -9,6 +9,10 @@ name: dependencies - "**/Cargo.toml" - config/deny.toml - config/osv-scanner.toml + - package.json + - pnpm-lock.yaml + - pnpm-workspace.yaml + - services/ws-server/static/package.json - .github/workflows/dependencies.yaml - .github/actions/install-mise/** workflow_dispatch: @@ -57,6 +61,11 @@ jobs: - name: osv-scanner run: mise run osv-scanner + # Scans the npm-backend tool install trees, which no committed lockfile covers. + # The task installs any missing npm: tool itself, so this job needs no full `mise install`. + - name: osv-scanner npm tools + run: mise run osv-scanner-npm + # Restore cached `cargo unmaintained` lookups to keep reruns fast. # `cargo unmaintained` persists per-repository archival/last-commit lookups under # `$XDG_CACHE_HOME/cargo-unmaintained` (default `~/.cache/cargo-unmaintained`, with a versioned `v2/` subdir). diff --git a/.gitignore b/.gitignore index 9275c687..28020409 100644 --- a/.gitignore +++ b/.gitignore @@ -19,7 +19,6 @@ __pycache__/ .pytest_cache/ .python-version node_modules/ -pnpm-lock.yaml .venv/ # .NET build output. # `obj/` is safe globally (nothing tracked is named obj/), but `bin/` is scoped to the module so it never diff --git a/.mise/config.js.toml b/.mise/config.js.toml index c5b0f6a6..758fe464 100644 --- a/.mise/config.js.toml +++ b/.mise/config.js.toml @@ -65,6 +65,17 @@ url = "https://github.com/oxc-project/oxc/releases/download/apps_v1.69.0/oxfmt-x [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" +[env] +# Exempt stats-gl from aube's add-time package-reputation gates. +# `aube add` refuses (non-interactively) any package whose weekly npm downloads fall under +# `lowDownloadThreshold`, whose name is within a short edit distance of a top-100,000 package, or that was +# registered very recently -- a typosquat defence. stats-gl trips the similar-name arm and fails the install with +# `ERR_AUBE_LOW_DOWNLOAD_PACKAGE` ("... edit distance 2)? Pass --allow-low-downloads after verifying the package +# name."); mise's npm backend builds the aube command line itself, so the per-invocation flag isn't reachable. +# This lists the one exact name affected rather than resetting the threshold or globbing, and aube's OSV `MAL-*` +# malicious-package check is explicitly unaffected by the exemption, so the hard malware gate still applies. +AUBE_ALLOWED_UNPOPULAR_PACKAGES = "stats-gl" + [tasks.build-ws-har1-module] depends = ["build-et-cli", "build-wasm-cov-wrapper"] description = "Build the har1 workflow WASM module" @@ -116,6 +127,65 @@ run = "oxfmt --config config/oxfmtrc.jsonc --check . '!.mise/config*.toml'" description = "Format JS/TS sources in place (oxfmt)" run = "oxfmt --config config/oxfmtrc.jsonc --write . '!.mise/config*.toml'" +[tasks.osv-scanner-npm] +depends = ["setup-aube"] +description = "Scan the npm: tools' aube lockfiles for known vulnerabilities" +# Collect each npm: tool's aube lockfile under one root, then scan that root. +# osv-scanner has no `aube-lock.yaml` extractor -- an explicit `--lockfile ` on one is rejected with +# `could not determine extractor suitable to this file`, and a directory scan of the install tree reports +# `No package sources found`. Its extractors are keyed on filename, so each lockfile is copied in as +# `pnpm-lock.yaml`: aube emits pnpm-lockfile-v9 (same `lockfileVersion`, `importers` and `packages` shape, plus +# a `time:` block the pnpm extractor ignores), so the native pnpm extractor reads it as-is. +# +# Every declared npm: tool must yield a lockfile or the task fails. That check is load-bearing: osv-scanner +# exits 0 on `No package sources found`, so a tool that silently installed via a lockfile-less backend would +# otherwise read as scanned-and-clean. `--no-ignore` is required because the collection root is under gitignored +# target/. +# +# `mise ls --json` maps each tool to an ARRAY of installed versions, so the declared-tool test has to look for a +# `requested_version` on ANY element, not on `.value[0]`. Indexing element 0 silently dropped onnxruntime-web from +# the scan here, because a superseded 1.26.0 install still sat ahead of the active 1.27.0 entry and carried no +# `requested_version` -- the task then scanned three tools instead of four and exited 0, the precise +# reads-as-clean failure the lockfile check above is meant to rule out. +# +# A tool is installed only when entirely absent, which keeps the scan from mutating what it measures. An +# unconditional `mise install` re-resolves a `latest` pin and upgrades the tool mid-scan: doing that here once +# moved onnxruntime-web 1.26.0 -> 1.27.0, whose tree carries a patched protobufjs, so two real advisories against +# the installed 1.26.0 tree (GHSA-f38q-mgvj-vph7, GHSA-j3f2-48v5-ccww) vanished from the report instead of being +# reported. Installing only what is missing still lets a cold CI job run this without a full `mise install` of +# every language toolchain, while a warm machine gets scanned exactly as it stands. Deriving the tool list from +# `mise ls` rather than restating it means it cannot drift from the [tools] table above. +run = """ +dest=target/osv-npm-locks +coreutils rm -rf "$dest" +coreutils mkdir -p "$dest" +filter='to_entries[] | select(.key | startswith("npm:")) | select(any(.value[]; .requested_version)) | .key' +tools=$(mise ls --json | jaq -r "$filter") +if [ -z "$tools" ]; then + echo "osv-scanner-npm: no declared npm: tools found; is MISE_ENV=js loaded?" >&2 + exit 1 +fi +for tool in $tools; do + root=$(mise where "$tool" 2>/dev/null || true) + if [ -z "$root" ]; then + mise install "$tool" + root=$(mise where "$tool") + fi + lock=$(find "$root" -name aube-lock.yaml -type f | coreutils head -1) + if [ -z "$lock" ]; then + echo "osv-scanner-npm: $tool has no aube-lock.yaml under $root" >&2 + echo "osv-scanner-npm: it did not install via aube; check [settings] npm.package_manager" >&2 + exit 1 + fi + slug=$(coreutils basename "$(coreutils dirname "$root")") + coreutils mkdir -p "$dest/$slug" + coreutils cp "$lock" "$dest/$slug/pnpm-lock.yaml" + echo "osv-scanner-npm: collected $tool -> $dest/$slug/pnpm-lock.yaml" +done +osv-scanner --recursive --no-ignore "$dest" --config config/osv-scanner.toml +""" +shell = "bash -euo pipefail -c" + # Namespaced aggregators picked up by the default config's globbed `check`/`fmt`/`fix`. [tasks."check:js"] depends = ["oxfmt-check", "oxlint-check"] diff --git a/.mise/config.toml b/.mise/config.toml index 3c6ca6c0..1fd73766 100644 --- a/.mise/config.toml +++ b/.mise/config.toml @@ -39,6 +39,14 @@ http_timeout = "2m" # This disables idiomatic-version-file detection for every tool. We declare versions in `.mise/config*.toml`; # no need to parse `.tool-versions`/`.node-version`/etc. on top of that. idiomatic_version_file_enable_tools = [] +# Pin the npm: backend to aube rather than letting mise pick one. +# The default "auto" uses aube when it happens to be installed and silently falls back to plain npm otherwise, +# which produces two different install layouts: aube writes `global-aube//aube-lock.yaml` (a full +# transitive lockfile), plain npm writes a bare `lib/node_modules` tree with no lockfile at all. osv-scanner +# cannot extract anything from a lockfile-less tree, so under "auto" a tool's vulnerability coverage depended on +# whether aube was present the day it was installed. Pinning to aube makes every npm: tool emit a lockfile, which +# is what osv-scanner-npm collects and scans. +npm.package_manager = "aube" task.run_auto_install = false # Hard-verify tool signatures via gpg on every platform. # Propagates to node.gpg_verify and swift.gpg_verify per mise/src/config/settings.rs. mise's node plugin @@ -61,7 +69,12 @@ action-validator = { version = "latest", os = ["linux", "macos"] } "aqua:rustwasm/wasm-pack" = "latest" "aqua:vectordotdev/vector" = "0.56.0" ast-grep = "latest" +# aube is the npm: backend, and mandatory because npm.package_manager names it. +# aqua ships linux/macos-arm64/windows; macos/x64 has no aqua asset, so it takes the cargo backend -- allowed +# there because macos/x64 is second-tier. +aube = { version = "latest", os = ["linux", "macos/arm64", "windows"] } cargo-binstall = "latest" +"cargo:aube" = { version = "latest", os = ["macos/x64"] } "cargo:cargo-expand" = { version = "latest", os = ["linux", "macos"] } "cargo:open" = "latest" "cargo:wasm-opt" = { version = "latest", os = ["linux", "macos"] } @@ -314,10 +327,14 @@ augeas_windows_x64_asset = "1.14.1-x86_64-pc-windows-mingw.tar.gz" # Used to compose absolute paths into a tool's install dir from [env] (e.g. AUGEAS_LENS_LIB). Honours # MISE_DATA_DIR if set; otherwise mise's default of $XDG_DATA_HOME (~/.local/share/mise on Linux/macOS). mise_data_dir = '{{ env?.MISE_DATA_DIR or (vars.a_home ~ "/.local/share/mise") }}' -# The committed lockfiles osv-scanner scans, one per ecosystem (Rust/Python/Dart/Java). -# The gitignored pnpm-lock.yaml is intentionally excluded: it is regenerated per install (onnxruntime-web is -# unpinned), so there is no stable committed file to scan. -osv_locks = "--lockfile Cargo.lock --lockfile uv.lock --lockfile pubspec.lock --lockfile pom.xml" +# The Rust/Python/Dart/Java half of the lockfile set osv-scanner scans, one file per ecosystem. +# Split from the npm half (and composed into osv_locks below) so no single line exceeds the editorconfig line +# length. The `a_` prefix puts both halves ahead of osv_locks in mise's alphabetical [vars] render order, so +# they are defined before osv_locks interpolates them. +a_osv_locks_base = "--lockfile Cargo.lock --lockfile uv.lock --lockfile pubspec.lock --lockfile pom.xml" +# The npm half: the repo-root pnpm workspace lockfile, covering every member listed in pnpm-workspace.yaml. +a_osv_locks_node = "--lockfile pnpm-lock.yaml" +osv_locks = "{{ vars.a_osv_locks_base }} {{ vars.a_osv_locks_node }}" # 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. clang_resource_arg = "" @@ -915,22 +932,20 @@ description = "Lint YAML with ryl (a yamllint-compatible Rust linter)" run = "ryl -c config/ryl.yaml .github config" [tasks.setup-aube] -# `aube` is an OPTIONAL faster npm backend, kept out of the mandatory `[tools]` list. -# It is omitted because its install is flaky on some platforms and edge-toolkit falls back to the -# classical `lib/node_modules` layout without it (see `find_npm_modules_path_in`). +# Install the npm: backend ahead of the main `mise install`. +# aube is a mandatory `[tools]` entry, so `mise install` would fetch it anyway; this task exists purely for +# ordering. mise resolves tools concurrently and gives no guarantee that aube lands before the `npm:*` tools that +# invoke it as their package manager, and a tool whose backend binary isn't there yet fails outright. CI runs this +# first so the ordering is explicit rather than incidental. # -# CI (check.yaml / test.yaml) runs this task in its own allowed-to-fail step, placed BEFORE the main -# `mise install` so the `npm:onnxruntime-web` install can pick up aube as its backend. -# -# Platform split: aqua ships aube for linux/macos-arm64/windows; macos/x64 has -# no aqua asset, so use the cargo backend there. The branch is rendered by -# mise's tera templating to pick the right backend per platform. -description = "Install the optional aube npm backend (platform-specific, best-effort)" +# The tera branch picks the platform's backend: aqua ships aube for linux/macos-arm64/windows, macos/x64 has no +# aqua asset and takes cargo:aube. Versions come from the `[tools]` pins, so no `@version` here. +description = "Install the aube npm backend ahead of the npm: tools" run = """ {% if os() == "macos" and arch() == "x64" %} -mise install cargo:aube@latest +mise install cargo:aube {% else %} -mise install aube@latest +mise install aube {% endif %} """ shell = "bash -euo pipefail -c" @@ -966,8 +981,8 @@ shell = "bash -euo pipefail -c" [tasks.osv-scanner] # Scans every committed lockfile across languages for known vulnerabilities. -# Covers Cargo.lock, uv.lock, pubspec.lock, and pom.xml (one per ecosystem), with no build dependency, so the same -# task serves both ad-hoc local use and the dependencies workflow. The `--lockfile` args live in vars.osv_locks. +# The scanned set is vars.osv_locks. No build dependency, so the same task serves both ad-hoc local use and the +# dependencies workflow. run = "osv-scanner {{ vars.osv_locks }} --config config/osv-scanner.toml" [tasks."gen:osv-scanner"] @@ -1109,13 +1124,15 @@ shell = "bash -euo pipefail -c" [tasks."prefetch:node"] # Needs `build-ws-wasm-agent` first. -# The static workspace `link:`s its `pkg/`, so the link target must exist before `pnpm install`. +# The static member `link:`s its `pkg/`, so the link target must exist before `pnpm install`. depends = ["build-ws-wasm-agent"] -description = "Prefetch the Node (pnpm) static workspace" +description = "Prefetch the Node (pnpm) workspace" # pnpm refuses non-TTY destructive node_modules cleanup unless CI=true. # That cleanup fires when an earlier install left a different layout. Treat as a CI batch. env = { CI = "true" } -run = "pnpm install --dir services/ws-server/static --ignore-scripts --config.ignoredBuiltDependencies=protobufjs" +# Installs the whole workspace from the repo root, so no --dir. +# The members come from pnpm-workspace.yaml and the resolution lands in the single root pnpm-lock.yaml. +run = "pnpm install --ignore-scripts --config.ignoredBuiltDependencies=protobufjs" # `shell = "bash"` on Windows for the same base-config-task PATH dropout. # Same dropout as `prefetch:rust` and `build-et-cli` -- cmd subprocess can't find pnpm. Captured on # 3ac0d60b windows test job 82282265424 (after the prefetch:rust fix, the same failure surfaced one step diff --git a/config/conftest/policy/mise/mise.rego b/config/conftest/policy/mise/mise.rego index 94a065a6..4c59633a 100644 --- a/config/conftest/policy/mise/mise.rego +++ b/config/conftest/policy/mise/mise.rego @@ -148,6 +148,10 @@ allowed_os_scoped_tool := { # action-validator (aqua) has no Windows build, so it is os-scoped off Windows. # config.windows.toml installs cargo:action-validator there instead (cargo-quickinstall msvc prebuilt). "action-validator", + # aube is the mandatory npm: backend; aqua ships no darwin/amd64 asset, so macos/x64 takes cargo:aube instead. + # Between the two entries every platform is covered. + "aube", + "cargo:aube", "http:chromedriver", "pipx", "pipx:torch", diff --git a/config/dprint.jsonc b/config/dprint.jsonc index fefcf009..22fa777d 100644 --- a/config/dprint.jsonc +++ b/config/dprint.jsonc @@ -28,7 +28,9 @@ "memberExpression.linePerExpression": true, }, "yaml": {}, - "excludes": ["**/node_modules", "**/*-lock.json", "data/"], + // Package-manager lockfiles are excluded because their generator owns the formatting. + // Any reformatting dprint applied would be rewritten wholesale by the next `install` and surface as drift. + "excludes": ["**/node_modules", "**/*-lock.json", "**/*-lock.yaml", "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/ls-lint.yaml b/config/ls-lint.yaml index 0ed58be2..f43b88a4 100644 --- a/config/ls-lint.yaml +++ b/config/ls-lint.yaml @@ -26,8 +26,8 @@ ls: ignore: - .git - target - # `**/` because pnpm puts `node_modules` nested under each package dir. - # E.g. `services/ws-server/static/node_modules`, not at the repo root. + # `**/` because a pnpm workspace puts `node_modules` at the root AND under each member package dir. + # E.g. both `node_modules` and `services/ws-server/static/node_modules`. - "**/node_modules" - generated # Bundled data/model assets -- names are not hand-chosen. diff --git a/config/taplo/mise-cargo-backend-allowlist.schema.json b/config/taplo/mise-cargo-backend-allowlist.schema.json index ab1ac332..3504874a 100644 --- a/config/taplo/mise-cargo-backend-allowlist.schema.json +++ b/config/taplo/mise-cargo-backend-allowlist.schema.json @@ -12,6 +12,7 @@ { "enum": [ "cargo:action-validator", + "cargo:aube", "cargo:cargo-expand", "cargo:dart-typegen", "cargo:findutils", diff --git a/config/typos.toml b/config/typos.toml index daca7a5e..9c2b56b8 100644 --- a/config/typos.toml +++ b/config/typos.toml @@ -3,3 +3,8 @@ # is not ours to police. [files] extend-exclude = ["data/"] + +[default] +# "MIS Quarterly" is a journal title (Management Information Systems), not a typo of MISS/MIST. +# Scoped to the exact two-word title so bare "MIS" elsewhere still gets flagged. +extend-ignore-re = ["MIS Quarterly"] diff --git a/package.json b/package.json new file mode 100644 index 00000000..d6fdd93f --- /dev/null +++ b/package.json @@ -0,0 +1,7 @@ +{ + "name": "edge-toolkit-core", + "version": "0.1.0", + "description": "pnpm workspace root for the edge-toolkit JS packages", + "private": true, + "type": "module" +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 00000000..8c954183 --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,149 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: {} + + services/ws-server/static: + dependencies: + et-ws-wasm-agent: + specifier: link:../../ws-wasm-agent/pkg + version: link:../../ws-wasm-agent/pkg + onnxruntime-web: + specifier: '*' + version: 1.27.0 + stats-gl: + specifier: '*' + version: 4.2.3 + +packages: + + '@protobufjs/aspromise@1.1.2': + resolution: {integrity: sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==} + + '@protobufjs/base64@1.1.2': + resolution: {integrity: sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==} + + '@protobufjs/codegen@2.0.5': + resolution: {integrity: sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==} + + '@protobufjs/eventemitter@1.1.1': + resolution: {integrity: sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg==} + + '@protobufjs/fetch@1.1.1': + resolution: {integrity: sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw==} + + '@protobufjs/float@1.0.2': + resolution: {integrity: sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==} + + '@protobufjs/path@1.1.2': + resolution: {integrity: sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==} + + '@protobufjs/pool@1.1.0': + resolution: {integrity: sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==} + + '@protobufjs/utf8@1.1.2': + resolution: {integrity: sha512-b1UQwcEZ4yCnMCD8DAL1VlbvBJE9/IX4FTIp7BG1xYpf29SLazLSrqUkj4w7Y5y7cCVP6E5tcqqcI0xemPkHug==} + + '@types/node@26.1.2': + resolution: {integrity: sha512-Vu4a5UFA9rIIFJ7rB/Vaafh9lrCQszopTCx6KjFboXTGQbPNasehVR5TEiithSDGyd1DEiUByggTZsg8jukeIg==} + + flatbuffers@25.9.23: + resolution: {integrity: sha512-MI1qs7Lo4Syw0EOzUl0xjs2lsoeqFku44KpngfIduHBYvzm8h2+7K8YMQh1JtVVVrUvhLpNwqVi4DERegUJhPQ==} + + guid-typescript@1.0.9: + resolution: {integrity: sha512-Y8T4vYhEfwJOTbouREvG+3XDsjr8E3kIr7uf+JZ0BYloFsttiHU0WfvANVsR7TxNUJa/WpCnw/Ino/p+DeBhBQ==} + + long@5.3.2: + resolution: {integrity: sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==} + + onnxruntime-common@1.27.0: + resolution: {integrity: sha512-3KxL5wIVqa8Ex08jxSzncm9CMgw8CjOFyOQ7SxvG9o0cVLlhTNKXyIQuTbtX4tGPJEf73OER2xrjt4HJSBL4ow==} + + onnxruntime-web@1.27.0: + resolution: {integrity: sha512-ogDLsqIozHZwifPuN37OproAo0byX6t43/bP8GzeZWBWD6MOGExswFAx3up4NS/vvWBOg2u2PXomDt3rMmdQSg==} + + platform@1.3.6: + resolution: {integrity: sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg==} + + protobufjs@7.6.5: + resolution: {integrity: sha512-/FPD0nUc9jH6rfFjji9IBqOz4pcSE3CsT1m7Ep6Mdb0LxSUMj8hgl6GomOvZzpNpAqqGaXA0P3VSrZLFzIhQrw==} + engines: {node: '>=12.0.0'} + + stats-gl@4.2.3: + resolution: {integrity: sha512-XxDFKl4kpB9siOTQ5szzT095uvJjj6Q3KLd7AbaceH8lJzRTnFARhBt4AnqKOpdY37iJGK+2H8W9bntMIUghYw==} + peerDependencies: + three: '*' + peerDependenciesMeta: + three: + optional: true + + undici-types@8.3.0: + resolution: {integrity: sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==} + +snapshots: + + '@protobufjs/aspromise@1.1.2': {} + + '@protobufjs/base64@1.1.2': {} + + '@protobufjs/codegen@2.0.5': {} + + '@protobufjs/eventemitter@1.1.1': {} + + '@protobufjs/fetch@1.1.1': + dependencies: + '@protobufjs/aspromise': 1.1.2 + + '@protobufjs/float@1.0.2': {} + + '@protobufjs/path@1.1.2': {} + + '@protobufjs/pool@1.1.0': {} + + '@protobufjs/utf8@1.1.2': {} + + '@types/node@26.1.2': + dependencies: + undici-types: 8.3.0 + + flatbuffers@25.9.23: {} + + guid-typescript@1.0.9: {} + + long@5.3.2: {} + + onnxruntime-common@1.27.0: {} + + onnxruntime-web@1.27.0: + dependencies: + flatbuffers: 25.9.23 + guid-typescript: 1.0.9 + long: 5.3.2 + onnxruntime-common: 1.27.0 + platform: 1.3.6 + protobufjs: 7.6.5 + + platform@1.3.6: {} + + protobufjs@7.6.5: + dependencies: + '@protobufjs/aspromise': 1.1.2 + '@protobufjs/base64': 1.1.2 + '@protobufjs/codegen': 2.0.5 + '@protobufjs/eventemitter': 1.1.1 + '@protobufjs/fetch': 1.1.1 + '@protobufjs/float': 1.0.2 + '@protobufjs/path': 1.1.2 + '@protobufjs/pool': 1.1.0 + '@protobufjs/utf8': 1.1.2 + '@types/node': 26.1.2 + long: 5.3.2 + + stats-gl@4.2.3: {} + + undici-types@8.3.0: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 00000000..cd334f8e --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,9 @@ +# pnpm workspace members, so a single repo-root pnpm-lock.yaml covers every JS package. +# One lockfile at the root is what osv-scanner scans for npm advisories, which means a dependency added to any +# member lands in the scanned set automatically instead of needing its own --lockfile argument. +# +# The generated `pkg/` directories under services/ws-modules/ each carry a package.json, but their `dependencies` +# name modules the ws-server serves (et-ws, et-model-face1, ...) rather than published npm packages. +# Some of their deps are loaded via mise installed tools, which are checked separately. +packages: + - services/ws-server/static From f54c81893cdb52db38a2934b1913065d3c96e7d8 Mon Sep 17 00:00:00 2001 From: John Vandenberg Date: Mon, 3 Aug 2026 10:20:08 +0800 Subject: [PATCH 2/9] Fix Rust CVE and ruff errors --- .github/workflows/dependencies.yaml | 2 +- .mise/config.toml | 3 +- CLAUDE.md | 2 +- Cargo.lock | 428 +++++++----------- Cargo.toml | 11 +- config/deny.toml | 7 - config/osv-scanner.toml | 1 - ruff.toml | 10 +- .../ws-modules/pydata1/pydata1/__init__.py | 4 +- .../ws-modules/pyeye1/pyeye1/eye_detection.py | 14 +- .../pyface1/pyface1/face_detection.py | 8 +- services/ws-pyo3-runner/python/fanout.py | 4 +- .../ws-pyo3-runner/python/storage_pingpong.py | 4 +- services/ws-wasi-runner/Cargo.toml | 2 +- 14 files changed, 212 insertions(+), 288 deletions(-) diff --git a/.github/workflows/dependencies.yaml b/.github/workflows/dependencies.yaml index eba76f17..9d051192 100644 --- a/.github/workflows/dependencies.yaml +++ b/.github/workflows/dependencies.yaml @@ -59,7 +59,7 @@ jobs: run: mise run cargo-deny-check - name: osv-scanner - run: mise run osv-scanner + run: mise run osv-scanner-check # Scans the npm-backend tool install trees, which no committed lockfile covers. # The task installs any missing npm: tool itself, so this job needs no full `mise install`. diff --git a/.mise/config.toml b/.mise/config.toml index 1fd73766..cb954b8d 100644 --- a/.mise/config.toml +++ b/.mise/config.toml @@ -979,10 +979,11 @@ mise install rclone """ shell = "bash -euo pipefail -c" -[tasks.osv-scanner] +[tasks.osv-scanner-check] # Scans every committed lockfile across languages for known vulnerabilities. # The scanned set is vars.osv_locks. No build dependency, so the same task serves both ad-hoc local use and the # dependencies workflow. +alias = "osv-scanner" run = "osv-scanner {{ vars.osv_locks }} --config config/osv-scanner.toml" [tasks."gen:osv-scanner"] diff --git a/CLAUDE.md b/CLAUDE.md index e1254cff..dedaf05d 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -361,7 +361,7 @@ Plus, attached to the same Linker but defined by external WIT packages: WIT plus its matching host crate once it ships. - `wasi:nn/{tensor, graph, inference, errors}` -- standardised ML inference. The host wires `wasmtime-wasi-nn` with the ONNX Runtime backend (`ort` 2.0.0-rc.10, pinned because rc.11+ - moved API surface that wasmtime-wasi-nn 44 still uses). Guests load model bytes via + moved API surface that wasmtime-wasi-nn 47 still uses). Guests load model bytes via `graph.load`, build `Tensor`s, and call `compute` -- the same shape of calls Spin / wasmCloud / Fermyon production wasi-nn workloads use. CUDA dispatch is opt-in via the runner's `cuda` cargo feature (`cargo build -p et-ws-wasi-runner --features cuda` or diff --git a/Cargo.lock b/Cargo.lock index 9f105065..270023a4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1164,82 +1164,46 @@ dependencies = [ [[package]] name = "cap-fs-ext" -version = "3.4.5" +version = "4.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5528f85b1e134ae811704e41ef80930f56e795923f866813255bc342cc20654" +checksum = "d78e5a3368ae89b7cb68186411452b4b9fac8b41be9c19bf3f47c2d2c8e36e6b" dependencies = [ "cap-primitives", "cap-std", - "io-lifetimes", - "windows-sys 0.59.0", -] - -[[package]] -name = "cap-net-ext" -version = "3.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20a158160765c6a7d0d8c072a53d772e4cb243f38b04bfcf6b4939cfbe7482e7" -dependencies = [ - "cap-primitives", - "cap-std", - "rustix 1.1.4", - "smallvec 1.15.2", + "io-lifetimes 3.0.1", + "windows-sys 0.61.2", ] [[package]] name = "cap-primitives" -version = "3.4.5" +version = "4.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6cf3aea8a5081171859ef57bc1606b1df6999df4f1110f8eef68b30098d1d3a" +checksum = "cdadbd7c002d3a484b35243669abdae85a0ebaded5a61117169dc3400f9a7ff0" dependencies = [ "ambient-authority", "fs-set-times", "io-extras", - "io-lifetimes", + "io-lifetimes 3.0.1", "ipnet", "maybe-owned", "rustix 1.1.4", "rustix-linux-procfs", - "windows-sys 0.59.0", + "windows-sys 0.61.2", "winx", ] -[[package]] -name = "cap-rand" -version = "3.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8144c22e24bbcf26ade86cb6501a0916c46b7e4787abdb0045a467eb1645a1d" -dependencies = [ - "ambient-authority", - "rand 0.8.5", -] - [[package]] name = "cap-std" -version = "3.4.5" +version = "4.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6dc3090992a735d23219de5c204927163d922f42f575a0189b005c62d37549a" +checksum = "7281235d6e96d3544ca18bba9049be92f4190f8d923e3caef1b5f66cfa752608" dependencies = [ "cap-primitives", "io-extras", - "io-lifetimes", + "io-lifetimes 3.0.1", "rustix 1.1.4", ] -[[package]] -name = "cap-time-ext" -version = "3.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "def102506ce40c11710a9b16e614af0cde8e76ae51b1f48c04b8d79f4b671a80" -dependencies = [ - "ambient-authority", - "cap-primitives", - "iana-time-zone", - "once_cell", - "rustix 1.1.4", - "winx", -] - [[package]] name = "capacity_builder" version = "0.5.0" @@ -1645,9 +1609,9 @@ checksum = "7704b5fdd17b18ae31c4c1da5a2e0305a2bf17b5249300a9ee9ed7b72114c636" [[package]] name = "cpp_demangle" -version = "0.4.5" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2bb79cb74d735044c972aae58ed0aaa9a837e85b01106a54c39e42e97f62253" +checksum = "0667304c32ea56cb4cd6d2d7c0cfe9a2f8041229db8c033af7f8d69492429def" dependencies = [ "cfg-if", ] @@ -1698,11 +1662,11 @@ dependencies = [ [[package]] name = "cranelift-assembler-x64" -version = "0.131.3" +version = "0.134.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3867f7a56768640a79fc660d2f60298251dc6d65b5d1c907706cd1afff024957" +checksum = "d552bd33b7a56dc70aeb1e1c960e51a218fa0db50f23873b500a310379450b2d" dependencies = [ - "cranelift-assembler-x64-meta 0.131.3", + "cranelift-assembler-x64-meta 0.134.3", ] [[package]] @@ -1713,9 +1677,9 @@ checksum = "c7496a6e92b5cee48c5d772b0443df58816dee30fed6ba19b2a28e78037ecedf" [[package]] name = "cranelift-assembler-x64-meta" -version = "0.131.3" +version = "0.134.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0661d63dcf8fc4a6538c1ee4d523917c5b27e9fce7a4114cdf9e2b30b4043cf" +checksum = "078e80e4c222279e3330f6aa1a256ca77ddf156d4453166a9f09defedc4594dd" dependencies = [ "cranelift-srcgen", ] @@ -1731,11 +1695,11 @@ dependencies = [ [[package]] name = "cranelift-bforest" -version = "0.131.3" +version = "0.134.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8d535b489159ea63e3c40dfbe8d0e12bfb71f2a14845ef2407353e06c5a697c" +checksum = "820ce15d4ad3562d613c31a67b6d0434d403e7091a68d1349903842f7d31737e" dependencies = [ - "cranelift-entity 0.131.3", + "cranelift-entity 0.134.3", "wasmtime-internal-core", ] @@ -1747,9 +1711,9 @@ checksum = "573c641174c40ef31021ae4a5a3ad78974e280633502d0dfc6e362385e0c100f" [[package]] name = "cranelift-bitset" -version = "0.131.3" +version = "0.134.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3af4f7d421b2354deb01d714266022f38fcdbebc9f5f1ec6d310d3c27286d9e" +checksum = "61bca563d4b86d285928d9e27f97f27039bb33a0fc524fa130d7d0c106bf8ab3" dependencies = [ "serde", "serde_derive", @@ -1783,27 +1747,30 @@ dependencies = [ [[package]] name = "cranelift-codegen" -version = "0.131.3" +version = "0.134.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09fe4c289e67e0221d1705734a57f95e25c289ed0ead7728743ea21285fc4cf1" +checksum = "709f4b7c0fb57d952658d5b5c07fbdc4149acd7b7f0de9678ae754b9c981949a" dependencies = [ "bumpalo", - "cranelift-assembler-x64 0.131.3", - "cranelift-bforest 0.131.3", - "cranelift-bitset 0.131.3", - "cranelift-codegen-meta 0.131.3", - "cranelift-codegen-shared 0.131.3", - "cranelift-control 0.131.3", - "cranelift-entity 0.131.3", - "cranelift-isle 0.131.3", + "cranelift-assembler-x64 0.134.3", + "cranelift-bforest 0.134.3", + "cranelift-bitset 0.134.3", + "cranelift-codegen-meta 0.134.3", + "cranelift-codegen-shared 0.134.3", + "cranelift-control 0.134.3", + "cranelift-entity 0.134.3", + "cranelift-isle 0.134.3", "gimli 0.33.0", - "hashbrown 0.16.1", + "hashbrown 0.17.1", "libm", "log", + "postcard", "pulley-interpreter", "regalloc2 0.15.1", "rustc-hash 2.1.3", "serde", + "serde_derive", + "sha2", "smallvec 1.15.2", "target-lexicon", "wasmtime-internal-core", @@ -1821,12 +1788,12 @@ dependencies = [ [[package]] name = "cranelift-codegen-meta" -version = "0.131.3" +version = "0.134.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3063e5363dc5ee6ee8edd930314582c08eb91c209b9564da1cd667f6424b9b3" +checksum = "903dc8915af62aad1d9d0f39a5968d33fa80b9aa899ed6f56e47f40ca4512e1e" dependencies = [ - "cranelift-assembler-x64-meta 0.131.3", - "cranelift-codegen-shared 0.131.3", + "cranelift-assembler-x64-meta 0.134.3", + "cranelift-codegen-shared 0.134.3", "cranelift-srcgen", "heck", "pulley-interpreter", @@ -1840,9 +1807,9 @@ checksum = "0f4ff8d2e1235f2d6e7fc3c6738be6954ba972cd295f09079ebffeca2f864e22" [[package]] name = "cranelift-codegen-shared" -version = "0.131.3" +version = "0.134.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c34b9c8dbc9edf37744918e56898d4979ef1e764e8e4bbe8b4d50250838ddfe8" +checksum = "0522d74c227e49f3fd49ab055311486ae4f09083262b66705bed676952491469" [[package]] name = "cranelift-control" @@ -1855,9 +1822,9 @@ dependencies = [ [[package]] name = "cranelift-control" -version = "0.131.3" +version = "0.134.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4eed9dc54204dc99aad19669bca50142659ed583396d9a99a2aef34d7c136ef4" +checksum = "66560ea1c5cef72e170b18e46d263dba2d3169c9d39e8cafdab2173c6362cc1a" dependencies = [ "arbitrary", ] @@ -1873,11 +1840,11 @@ dependencies = [ [[package]] name = "cranelift-entity" -version = "0.131.3" +version = "0.134.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aa2846b239a046217ecf95cfed0e31be4e86843785d07438ad33f456871e888" +checksum = "b62ef5b17cc814d27e96b66a5b46da0e4ce2b8ac55a6d478048bb99d04b05526" dependencies = [ - "cranelift-bitset 0.131.3", + "cranelift-bitset 0.134.3", "serde", "serde_derive", "wasmtime-internal-core", @@ -1897,11 +1864,12 @@ dependencies = [ [[package]] name = "cranelift-frontend" -version = "0.131.3" +version = "0.134.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "144f70fa9cd07efb83497c12dc8fb73f360a690cd990c44e8ceebc293d8c13b5" +checksum = "a87e0aaa39dbf70693b348a221e45904111704ee8f9fef140498471005f9842d" dependencies = [ - "cranelift-codegen 0.131.3", + "cranelift-codegen 0.134.3", + "hashbrown 0.17.1", "log", "smallvec 1.15.2", "target-lexicon", @@ -1915,9 +1883,9 @@ checksum = "f900e0a3847d51eed0321f0777947fb852ccfce0da7fb070100357f69a2f37fc" [[package]] name = "cranelift-isle" -version = "0.131.3" +version = "0.134.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0733ca5b2aaa5f6d5d6a1439e3c44280d34730d4d5c262ca08c6775c8d83f191" +checksum = "cf79003ebfa1eed5e87f3b84446ad5236f540268289960e14f387dc9b28e40b7" [[package]] name = "cranelift-module" @@ -1943,20 +1911,20 @@ dependencies = [ [[package]] name = "cranelift-native" -version = "0.131.3" +version = "0.134.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75b1290d6193b171172d5fe9a6e42326edf487a79f211fbf1e76f912a4aed035" +checksum = "05bf4f235743c81e67ee4db617c5a4a0b65d58d3f0cfc575ee0f1a4e0cd58273" dependencies = [ - "cranelift-codegen 0.131.3", + "cranelift-codegen 0.134.3", "libc", "target-lexicon", ] [[package]] name = "cranelift-srcgen" -version = "0.131.3" +version = "0.134.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce0d5c2b4d719566816a0f1c9a9712d35d61e27df0ffd6c72a9afec9048db6c0" +checksum = "f6977c2a71ab1e0d1e62f966b411a498aa04c4dce47d93d52f8a360a06058922" [[package]] name = "crc32fast" @@ -5143,7 +5111,7 @@ version = "0.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94e7099f6313ecacbe1256e8ff9d617b75d1bcb16a6fddef94866d225a01a14a" dependencies = [ - "io-lifetimes", + "io-lifetimes 2.0.4", "rustix 1.1.4", "windows-sys 0.59.0", ] @@ -5616,6 +5584,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" dependencies = [ "foldhash 0.2.0", + "serde", + "serde_core", ] [[package]] @@ -6284,12 +6254,12 @@ dependencies = [ [[package]] name = "io-extras" -version = "0.18.4" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2285ddfe3054097ef4b2fe909ef8c3bcd1ea52a8f0d274416caebeef39f04a65" +checksum = "20fd6de4ccfcc187e38bc21cfa543cb5a302cb86a8b114eb7f0bf0dc9f8ac00f" dependencies = [ - "io-lifetimes", - "windows-sys 0.59.0", + "io-lifetimes 3.0.1", + "windows-sys 0.60.2", ] [[package]] @@ -6298,6 +6268,12 @@ version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06432fb54d3be7964ecd3649233cddf80db2832f47fec34c01f65b3d9d774983" +[[package]] +name = "io-lifetimes" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0fb0570afe1fed943c5c3d4102d5358592d8625fda6a0007fdbe65a92fba96" + [[package]] name = "ipconfig" version = "0.3.4" @@ -6828,12 +6804,9 @@ checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" [[package]] name = "mach2" -version = "0.4.3" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d640282b302c0bb0a2a8e0233ead9035e3bed871f0b7e81fe4a1ec829765db44" -dependencies = [ - "libc", -] +checksum = "dae608c151f68243f2b000364e1f7b186d9c29845f7d2d85bd31b9ad77ad552b" [[package]] name = "macro-string" @@ -8425,11 +8398,11 @@ dependencies = [ [[package]] name = "pulley-interpreter" -version = "44.0.3" +version = "47.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34dff5fd3d9ac4845939fcb4597cd413cb244bc530448ed4766d11b1725e53d0" +checksum = "bc5c8c21ea032e4efbdf2d067dc45171779dbe0c8ecf20ef4a57efa7474f2b0a" dependencies = [ - "cranelift-bitset 0.131.3", + "cranelift-bitset 0.134.3", "log", "pulley-macros", "wasmtime-internal-core", @@ -8437,9 +8410,9 @@ dependencies = [ [[package]] name = "pulley-macros" -version = "44.0.3" +version = "47.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c60fb1c885bdb1efd7c50e8e973714de558b75a65f20c3e9a41398c652aa44b" +checksum = "9f10925455d5dde962e3604eade797ba5488644c8ee14a44191e2f2b58980574" dependencies = [ "proc-macro2", "quote", @@ -8873,6 +8846,7 @@ dependencies = [ "hashbrown 0.17.1", "log", "rustc-hash 2.1.3", + "serde", "smallvec 1.15.2", ] @@ -10731,22 +10705,6 @@ dependencies = [ "syn 2.0.118", ] -[[package]] -name = "system-interface" -version = "0.27.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc4592f674ce18521c2a81483873a49596655b179f71c5e05d10c1fe66c78745" -dependencies = [ - "bitflags", - "cap-fs-ext", - "cap-std", - "fd-lock", - "io-lifetimes", - "rustix 0.38.44", - "windows-sys 0.59.0", - "winx", -] - [[package]] name = "tagptr" version = "0.2.0" @@ -11913,9 +11871,9 @@ checksum = "c31d56021e873866c968588ed85ccdf56db5c426e44afdb4618c39895104b920" [[package]] name = "wasm-compose" -version = "0.246.2" +version = "0.252.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f05a2b3bad87cc1ce45b63425ec09a854cc4cb369231c9fed1fee31538103efb" +checksum = "d59b710751a35d54732a63851cdfacbfb2266b7160ce174faf269d3f4e84e31b" dependencies = [ "anyhow", "heck", @@ -11923,29 +11881,29 @@ dependencies = [ "log", "petgraph 0.6.5", "smallvec 1.15.2", - "wasm-encoder 0.246.2", - "wasmparser 0.246.2", + "wasm-encoder 0.252.0", + "wasmparser 0.252.0", "wat", ] [[package]] name = "wasm-encoder" -version = "0.246.2" +version = "0.247.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61fb705ce81adde29d2a8e99d87995e39a6e927358c91398f374474746070ef7" +checksum = "30b6733b8b91d010a6ac5b0fb237dc46a19650bc4c67db66857e2e787d437204" dependencies = [ "leb128fmt", - "wasmparser 0.246.2", + "wasmparser 0.247.0", ] [[package]] name = "wasm-encoder" -version = "0.247.0" +version = "0.252.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30b6733b8b91d010a6ac5b0fb237dc46a19650bc4c67db66857e2e787d437204" +checksum = "8185ae345fa5687c054626ff9a50e7089797a343d9904d1dc9820eb4c4d3196f" dependencies = [ "leb128fmt", - "wasmparser 0.247.0", + "wasmparser 0.252.0", ] [[package]] @@ -11995,38 +11953,38 @@ dependencies = [ [[package]] name = "wasmparser" -version = "0.246.2" +version = "0.247.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71cde4757396defafd25417cfb36aa3161027d06d865b0c24baaae229aac005d" +checksum = "8e6fb4c2bee46c5ea4d40f8cdb5c131725cd976718ec56f1c8e82fbde5fa2a80" dependencies = [ "bitflags", - "hashbrown 0.16.1", + "hashbrown 0.17.1", "indexmap", "semver", - "serde", ] [[package]] name = "wasmparser" -version = "0.247.0" +version = "0.249.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e6fb4c2bee46c5ea4d40f8cdb5c131725cd976718ec56f1c8e82fbde5fa2a80" +checksum = "30538cae9a794215f490b532df01c557e2e2bfac92569482554acd0992a102ea" dependencies = [ "bitflags", - "hashbrown 0.17.1", "indexmap", "semver", ] [[package]] name = "wasmparser" -version = "0.249.0" +version = "0.252.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30538cae9a794215f490b532df01c557e2e2bfac92569482554acd0992a102ea" +checksum = "d3eb099dcadcde5be9eef55e3a337128efd4e44b4c93122487e4d2e4e1c6627c" dependencies = [ "bitflags", + "hashbrown 0.17.1", "indexmap", "semver", + "serde", ] [[package]] @@ -12042,20 +12000,20 @@ dependencies = [ [[package]] name = "wasmprinter" -version = "0.246.2" +version = "0.252.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e41f7493ba994b8a779430a4c25ff550fd5a40d291693af43a6ef48688f00e3" +checksum = "7142797de29b35ab8dbf15c00f55fda75d409da4c423a8ab8bd6b667a785824b" dependencies = [ "anyhow", "termcolor", - "wasmparser 0.246.2", + "wasmparser 0.252.0", ] [[package]] name = "wasmtime" -version = "44.0.3" +version = "47.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d807f646bfecc1dbb4990d8c864beebccbdb3f2cd1b39b2b82957b4e294c6058" +checksum = "c80ca6098e0d4d06886d91d7f2cc3cb6623eb583c4c0ab3c89cbfb6098c8586c" dependencies = [ "addr2line", "async-trait", @@ -12086,8 +12044,8 @@ dependencies = [ "target-lexicon", "tempfile", "wasm-compose", - "wasm-encoder 0.246.2", - "wasmparser 0.246.2", + "wasm-encoder 0.252.0", + "wasmparser 0.252.0", "wasmtime-environ", "wasmtime-internal-cache", "wasmtime-internal-component-macro", @@ -12099,24 +12057,24 @@ dependencies = [ "wasmtime-internal-jit-icache-coherence", "wasmtime-internal-unwinder", "wasmtime-internal-versioned-export-macros", - "wasmtime-internal-winch", "wat", "windows-sys 0.61.2", + "wit-parser 0.252.0", ] [[package]] name = "wasmtime-environ" -version = "44.0.3" +version = "47.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48b945309908f22473ebcd585ac2993948044228491cd96941d367aa81a49c3f" +checksum = "134f9d136d29c76f6c1b4c9b468e97a2efc38c7d49fd188e39b64870b2fea701" dependencies = [ "anyhow", "cpp_demangle", - "cranelift-bforest 0.131.3", - "cranelift-bitset 0.131.3", - "cranelift-entity 0.131.3", + "cranelift-bforest 0.134.3", + "cranelift-bitset 0.134.3", + "cranelift-entity 0.134.3", "gimli 0.33.0", - "hashbrown 0.16.1", + "hashbrown 0.17.1", "indexmap", "log", "object 0.39.1", @@ -12128,8 +12086,8 @@ dependencies = [ "sha2", "smallvec 1.15.2", "target-lexicon", - "wasm-encoder 0.246.2", - "wasmparser 0.246.2", + "wasm-encoder 0.252.0", + "wasmparser 0.252.0", "wasmprinter", "wasmtime-internal-component-util", "wasmtime-internal-core", @@ -12137,9 +12095,9 @@ dependencies = [ [[package]] name = "wasmtime-internal-cache" -version = "44.0.3" +version = "47.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f14b8b93c2137c88ed84114d9a09cb11cb8bf9394aba4856e48f5304a4f99eec" +checksum = "65db2eb1bfc5371a3b4107dbf539d0b93d05016fc29625b1648146b47db02071" dependencies = [ "base64 0.22.1", "directories-next", @@ -12157,9 +12115,9 @@ dependencies = [ [[package]] name = "wasmtime-internal-component-macro" -version = "44.0.3" +version = "47.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7307dec6251a18ffa9df03120d2945ac2476ce150b5b099f39b199e8f81464dc" +checksum = "2bf7b91fed3fc34781d57f9c6654ea2513bf0a99f7b0b0523c00de1e6efebe1c" dependencies = [ "anyhow", "proc-macro2", @@ -12167,39 +12125,39 @@ dependencies = [ "syn 2.0.118", "wasmtime-internal-component-util", "wasmtime-internal-wit-bindgen", - "wit-parser 0.246.2", + "wit-parser 0.252.0", ] [[package]] name = "wasmtime-internal-component-util" -version = "44.0.3" +version = "47.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a3d899b0270bcf04852f141bd9538cb162a436e7f56b2c6e0f4e57ecc70743a" +checksum = "fc8a678149885cae00289f806fbe74c7863084cf74cace0b8dc73602279400e1" [[package]] name = "wasmtime-internal-core" -version = "44.0.3" +version = "47.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aedd3947487d0afdd37accb981466fcd60571e898004c8955111f88686581dfc" +checksum = "8a0092c4b9d070ac5e278b6d0db10f5e71214190f0347ca57502b4692f796321" dependencies = [ "anyhow", - "hashbrown 0.16.1", + "hashbrown 0.17.1", "libm", "serde", ] [[package]] name = "wasmtime-internal-cranelift" -version = "44.0.3" +version = "47.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "512fd846630c064bfc42909eeba90a7d26703b6ded09ad4778ff6afcbdc868dd" +checksum = "6851ebc9e03cab23d9821d2b2505d380bdfe38f35ccec945247b05274d85c98b" dependencies = [ "cfg-if", - "cranelift-codegen 0.131.3", - "cranelift-control 0.131.3", - "cranelift-entity 0.131.3", - "cranelift-frontend 0.131.3", - "cranelift-native 0.131.3", + "cranelift-codegen 0.134.3", + "cranelift-control 0.134.3", + "cranelift-entity 0.134.3", + "cranelift-frontend 0.134.3", + "cranelift-native 0.134.3", "gimli 0.33.0", "itertools 0.14.0", "log", @@ -12208,7 +12166,7 @@ dependencies = [ "smallvec 1.15.2", "target-lexicon", "thiserror 2.0.18", - "wasmparser 0.246.2", + "wasmparser 0.252.0", "wasmtime-environ", "wasmtime-internal-core", "wasmtime-internal-unwinder", @@ -12217,9 +12175,9 @@ dependencies = [ [[package]] name = "wasmtime-internal-fiber" -version = "44.0.3" +version = "47.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15629ea71394be5812a52cb8fbc6cd039484ab1dd48fce5e1ef58ae89606289a" +checksum = "9b26da6d5f60d4c438da70bba3553fe810a840533a64156be287dca2081c6991" dependencies = [ "cc", "cfg-if", @@ -12232,9 +12190,9 @@ dependencies = [ [[package]] name = "wasmtime-internal-jit-debug" -version = "44.0.3" +version = "47.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5fce5fedc1c952a64cdf3c87e4632af072d2aca0bc2c460d53296fb2654757d" +checksum = "ed621ba25d7bf78b7edd7b4749abbb27c2e5cdba836c9504a424ee74b6c23149" dependencies = [ "cc", "object 0.39.1", @@ -12244,9 +12202,9 @@ dependencies = [ [[package]] name = "wasmtime-internal-jit-icache-coherence" -version = "44.0.3" +version = "47.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10005b038e662775ac002f233e429447a58892e89918580fa67ce8cdd9192d0a" +checksum = "5684ba160951baad06a725696f3c590e2fb0e8067c2aebee27bf7f9259058e85" dependencies = [ "cfg-if", "libc", @@ -12256,12 +12214,12 @@ dependencies = [ [[package]] name = "wasmtime-internal-unwinder" -version = "44.0.3" +version = "47.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03f3e0f474281b405a3e9d97239f83f643b572324d292e1ef9dc5e3e0ab04c68" +checksum = "112eead527bffa8ff0646a11fb4339a9d52ddd1da2b9a6fce4aff84c815dd94f" dependencies = [ "cfg-if", - "cranelift-codegen 0.131.3", + "cranelift-codegen 0.134.3", "log", "object 0.39.1", "wasmtime-environ", @@ -12269,65 +12227,44 @@ dependencies = [ [[package]] name = "wasmtime-internal-versioned-export-macros" -version = "44.0.3" +version = "47.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "910e5393af4aca456113581a5913b8d499cd2189013e983f8764d06ebc42b2ed" +checksum = "153592e0bed824fc13c6696203fa1bd7bd20eb355316475e39a55f081ef80eca" dependencies = [ "proc-macro2", "quote", "syn 2.0.118", ] -[[package]] -name = "wasmtime-internal-winch" -version = "44.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55481651bea5b8336fb200fa49914ccf802f5e7617ba9ab0b4691f16d4f97ff8" -dependencies = [ - "cranelift-codegen 0.131.3", - "gimli 0.33.0", - "log", - "object 0.39.1", - "target-lexicon", - "wasmparser 0.246.2", - "wasmtime-environ", - "wasmtime-internal-cranelift", - "winch-codegen", -] - [[package]] name = "wasmtime-internal-wit-bindgen" -version = "44.0.3" +version = "47.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d890c3804d0e46000fa901c86ac1a9fdedf684e72dfd64e582b56bb4a78a6746" +checksum = "c456ad6e81e0f46abfeca43687d18ea15e289c395b262ea6e0023e2682e088be" dependencies = [ "anyhow", "bitflags", "heck", "indexmap", - "wit-parser 0.246.2", + "wit-parser 0.252.0", ] [[package]] name = "wasmtime-wasi" -version = "44.0.3" +version = "47.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aa7f927779d92863a1447c1d88192b2242080608cb91ece69c177321488948b" +checksum = "4c1cf60cd6a565213af7074b4aad7bb5e110e3c6c6aae54425aa0500a0e15e86" dependencies = [ "async-trait", "bitflags", "bytes", "cap-fs-ext", - "cap-net-ext", - "cap-rand", "cap-std", - "cap-time-ext", - "fs-set-times", + "cfg-if", "futures", - "io-extras", - "io-lifetimes", + "io-lifetimes 3.0.1", + "rand 0.10.2", "rustix 1.1.4", - "system-interface", "thiserror 2.0.18", "tokio", "tracing", @@ -12340,9 +12277,9 @@ dependencies = [ [[package]] name = "wasmtime-wasi-io" -version = "44.0.3" +version = "47.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6fd8b702c2aa82bb4907da5ad44120b4385048f6a20731908a6e2485ea7567e" +checksum = "7fb08e1d7755aaa467ce14080b7b01e33c914a920f6000696f1e42e40ea6387e" dependencies = [ "async-trait", "bytes", @@ -12353,9 +12290,9 @@ dependencies = [ [[package]] name = "wasmtime-wasi-nn" -version = "44.0.3" +version = "47.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "844ddb79a727bfcd7db4d311bdbb3a9410f41d006075d47301dfcb1d850bd2a6" +checksum = "f0e100838e98b70716f7321896f74ce27ff29967a1775dbf9abc23dc875c1b79" dependencies = [ "ort", "thiserror 2.0.18", @@ -12695,9 +12632,9 @@ checksum = "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471" [[package]] name = "wiggle" -version = "44.0.3" +version = "47.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "165512f7870210d0fd45911990b832782b5fb82d40f4ce28cf86b40aaecd453c" +checksum = "89091642df051b84a7e00bdff75e985e507f2298384e477cc9310b0ddd79f004" dependencies = [ "bitflags", "thiserror 2.0.18", @@ -12709,9 +12646,9 @@ dependencies = [ [[package]] name = "wiggle-generate" -version = "44.0.3" +version = "47.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7f63d06fdf2e9a133407b318574574f66ea6590ea7a42c4a7554c029824454a" +checksum = "12247f46f31bb5e7593947f9bba07317d0c1978e0d02250b979dd08f56f46e9f" dependencies = [ "heck", "proc-macro2", @@ -12723,9 +12660,9 @@ dependencies = [ [[package]] name = "wiggle-macro" -version = "44.0.3" +version = "47.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b976b7285ca32a637e21721021442691a2d7938ab4650cb99472a672e9def6c" +checksum = "e556a0880c9506fdcc662b1e226c7ced692f4977fed5b1ed40a3f3f0a48356f2" dependencies = [ "proc-macro2", "quote", @@ -12764,25 +12701,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -[[package]] -name = "winch-codegen" -version = "44.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "436a7fa4109b13b0e555d01ec615ab8b928b7834639aca7b2fdc1f55d70a1f0c" -dependencies = [ - "cranelift-assembler-x64 0.131.3", - "cranelift-codegen 0.131.3", - "gimli 0.33.0", - "regalloc2 0.15.1", - "smallvec 1.15.2", - "target-lexicon", - "thiserror 2.0.18", - "wasmparser 0.246.2", - "wasmtime-environ", - "wasmtime-internal-core", - "wasmtime-internal-cranelift", -] - [[package]] name = "windows" version = "0.61.3" @@ -13288,12 +13206,12 @@ dependencies = [ [[package]] name = "wit-parser" -version = "0.246.2" +version = "0.247.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd979042b5ff288607ccf3b314145435453f20fc67173195f91062d2289b204d" +checksum = "8ffe4064318cdf3c08cb99343b44c039fcefe61ccdf58aa9975285f13d74d1fc" dependencies = [ "anyhow", - "hashbrown 0.16.1", + "hashbrown 0.17.1", "id-arena", "indexmap", "log", @@ -13302,14 +13220,14 @@ dependencies = [ "serde_derive", "serde_json", "unicode-xid", - "wasmparser 0.246.2", + "wasmparser 0.247.0", ] [[package]] name = "wit-parser" -version = "0.247.0" +version = "0.249.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ffe4064318cdf3c08cb99343b44c039fcefe61ccdf58aa9975285f13d74d1fc" +checksum = "50840f2e2cf170d910858089d7dfb3e97c6f9a0d6ec7bff7f7cc28f5aeacc15f" dependencies = [ "anyhow", "hashbrown 0.17.1", @@ -13321,14 +13239,14 @@ dependencies = [ "serde_derive", "serde_json", "unicode-xid", - "wasmparser 0.247.0", + "wasmparser 0.249.0", ] [[package]] name = "wit-parser" -version = "0.249.0" +version = "0.252.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50840f2e2cf170d910858089d7dfb3e97c6f9a0d6ec7bff7f7cc28f5aeacc15f" +checksum = "4266bea110371c620ccf3201c5023676046bc4556e5c7cfb5d500bda5ebc162d" dependencies = [ "anyhow", "hashbrown 0.17.1", @@ -13339,8 +13257,8 @@ dependencies = [ "serde", "serde_derive", "serde_json", - "unicode-xid", - "wasmparser 0.249.0", + "unicode-ident", + "wasmparser 0.252.0", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index d0586637..2a1d9181 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -188,10 +188,13 @@ wasm-bindgen = "0.2" wasm-bindgen-futures = "0.4" wasm-bindgen-test = "0.3" # 44.0.3 is the floor for the RUSTSEC-2026-0182 fix (fd_renumber leak in WASIp1). -# Stay on 44.x for ABI compat with wasmtime-wasi-nn 44.x. -wasmtime = { version = "44.0.3", features = ["async", "component-model"] } -wasmtime-wasi = "44.0.3" -wasmtime-wasi-nn = { version = "44", default-features = false, features = ["onnx"] } +# Keep all three on the same major for ABI compat -- wasmtime-wasi-nn tracks wasmtime's version exactly. +# 47.0.3 is the floor, not a preference: RUSTSEC-2026-0222 (GHSA-hgjw-h833-99q9) is fixed only in +# >=24.0.12 <25, >=36.0.13 <37, >=46.0.2 <47, or >=47.0.3, so the 44.x line this used to pin has no patched +# release at all and 47.0.2 is still affected. +wasmtime = { version = "47.0.3", features = ["async", "component-model"] } +wasmtime-wasi = "47.0.3" +wasmtime-wasi-nn = { version = "47", default-features = false, features = ["onnx"] } web-sys = "0.3" wgpu = { version = "29", default-features = false, features = ["dx12", "metal", "vulkan", "wgsl"] } # Windows-only feature-unification shim: forces `winapi/std` on for deno_io. diff --git a/config/deny.toml b/config/deny.toml index 63c94200..b6999284 100644 --- a/config/deny.toml +++ b/config/deny.toml @@ -43,13 +43,6 @@ ignore = [ # unless guest JS explicitly imports crypto.subtle and performs RSA-OAEP decrypt with a private key. Our # modules don't. "RUSTSEC-2023-0071", - # wasmtime-wasi 44.0.3 -- WASI hard links / renames bypass FilePerms on the rename/link *destination*. - # Pinned to 44.x for ABI compat with wasmtime-wasi-nn 44 (see [workspace.dependencies] wasmtime note); - # the fix is in 45.0.3 / 46.0.1. Unreachable here: et-ws-wasi-runner builds its WasiCtx with only - # inherit_stdio + inherit_env and grants the guest NO preopened directories, so there is no filesystem - # for a hard-link/rename to bypass FilePerms against. Guest storage is proxied through the host `storage` - # import (reqwest to ws-server), not WASI fs. Drop when wasmtime + wasmtime-wasi-nn bump to >=45 together. - "RUSTSEC-2026-0188", # opentelemetry_sdk <=0.32.0 -- unbounded W3C baggage-header parse before size limits, a DoS (fixed 0.32.1). # BaggagePropagator::extract_with_context parses inbound entries it then discards. Only our own copy (0.31.0) # is still affected -- the Deno-stack copy reached 0.32.1 in the deno_runtime 0.262 bump. Unreachable anyway: diff --git a/config/osv-scanner.toml b/config/osv-scanner.toml index 1c32d5af..61f393b8 100644 --- a/config/osv-scanner.toml +++ b/config/osv-scanner.toml @@ -10,5 +10,4 @@ IgnoredVulns = [ { id = "RUSTSEC-2026-0118" }, { id = "RUSTSEC-2026-0119" }, { id = "RUSTSEC-2026-0173" }, - { id = "RUSTSEC-2026-0188" }, ] diff --git a/ruff.toml b/ruff.toml index 187e5122..3b7d3d85 100644 --- a/ruff.toml +++ b/ruff.toml @@ -51,10 +51,12 @@ convention = "pep257" [lint.per-file-ignores] # Generated clients are exempt from the hand-authoring quality families. # Their code is machine-emitted by openapi-python-client / datamodel-codegen and regenerated wholesale, so naming, -# modernization, and refactor findings can't be hand-fixed and would break CI on the next regen. The E/F baseline -# stays active, as does "I" (import sorting keeps messages.py stable across regens); "D" was already dropped -# because the docstrings come verbatim from the OpenAPI/JSON-schema spec. -"generated/**" = ["ARG", "B", "C4", "D", "N", "PIE", "PLC", "PLE", "PLW", "RUF", "SIM", "UP"] +# modernization, refactor, and annotation-style findings can't be hand-fixed and would break CI on the next regen. +# The E/F baseline stays active, as does "I" (import sorting keeps messages.py stable across regens); "D" was +# already dropped because the docstrings come verbatim from the OpenAPI/JSON-schema spec. "PYI" covers the +# generated client's `__enter__`/`__exit__` annotations (return `Client` rather than `Self`, star-args typed as +# `Any`), which come from openapi-python-client's templates. +"generated/**" = ["ARG", "B", "C4", "D", "N", "PIE", "PLC", "PLE", "PLW", "PYI", "RUF", "SIM", "UP"] # The et-ws-pyo3-runner example modules keep per-process state in module-level globals by design. # The runner instantiates one copy per process and the contract is deliberately class-free (see echo.py's module # docstring), so PLW0603's "don't use the global statement" advice doesn't apply. diff --git a/services/ws-modules/pydata1/pydata1/__init__.py b/services/ws-modules/pydata1/pydata1/__init__.py index 9c2e3699..27cb5ea5 100644 --- a/services/ws-modules/pydata1/pydata1/__init__.py +++ b/services/ws-modules/pydata1/pydata1/__init__.py @@ -66,7 +66,9 @@ async def run(agent_id, base_url, sleep_ms, log, set_status, upload_consent) -> body=File(payload=consent_content.encode("utf-8")), ) log(f"pydata1: consent-gated upload succeeded: {consent_filename}") - except Exception as exc: + # Broad on purpose: this is the diagnostic half of the workflow, exercising the consent-gated upload + # path. Any failure is information to log, and must not fail the verification the test above just made. + except Exception as exc: # noqa: BLE001 log(f"pydata1: consent-gated upload failed: {exc}") else: log("pydata1: upload consent not granted, skipping consent-gated upload") diff --git a/services/ws-modules/pyeye1/pyeye1/eye_detection.py b/services/ws-modules/pyeye1/pyeye1/eye_detection.py index 9b5f0419..c64d5ba5 100644 --- a/services/ws-modules/pyeye1/pyeye1/eye_detection.py +++ b/services/ws-modules/pyeye1/pyeye1/eye_detection.py @@ -16,7 +16,7 @@ import time from collections import deque from collections.abc import Iterable, Sequence -from datetime import datetime +from datetime import datetime, timezone from statistics import fmean from typing import Any, TypedDict @@ -169,7 +169,9 @@ async def attempt_eye_capture(platform) -> None: """ try: filename = await platform.save_eye_capture() - except Exception as exc: + # Broad by contract: the capture crosses into JS (canvas encode, fetch upload), which surfaces arbitrary + # exception types through Pyodide, and this function's whole purpose is to report any of them without raising. + except Exception as exc: # noqa: BLE001 platform.log(f"eye capture failed: {exc}") platform.send_event(eye_capture_error_event_json(str(exc))) return @@ -243,7 +245,9 @@ async def sample_loop(platform) -> None: if platform.upload_consent(): await attempt_eye_capture(platform) platform.render(results_json(results, analysis, crop)) - except Exception as exc: + # Broad by design: MediaPipe/wasm inference and the JS canvas calls raise arbitrary types, and a + # long-running sample loop must surface the error and keep sampling rather than die on one bad frame. + except Exception as exc: # noqa: BLE001 message = f"pyeye1 eye movement screening: inference error\n{exc}" platform.set_status(message) platform.log(f"inference error: {exc}") @@ -426,7 +430,7 @@ def status_text(results: Sequence[FaceEyes], analysis: WindowAnalysis | None) -> f"eyes: {eye_count}", *analysis_lines(analysis), "screening heuristics only -- not a medical diagnosis", - f"processed at: {datetime.now().strftime('%X')}", + f"processed at: {datetime.now(timezone.utc).strftime('%X')}", ] return "\n".join(lines) @@ -473,7 +477,7 @@ def event_payload( "eyes": eye_count, "results": list(results), "analysis": analysis, - "processed_at": datetime.now().strftime("%X"), + "processed_at": datetime.now(timezone.utc).strftime("%X"), "model_path": EYE_MODEL_PATH, "source_resolution": {"width": float(width), "height": float(height)}, } diff --git a/services/ws-modules/pyface1/pyface1/face_detection.py b/services/ws-modules/pyface1/pyface1/face_detection.py index da667d3f..a273b922 100644 --- a/services/ws-modules/pyface1/pyface1/face_detection.py +++ b/services/ws-modules/pyface1/pyface1/face_detection.py @@ -6,7 +6,7 @@ import math import time from collections.abc import Iterable, Sequence -from datetime import datetime +from datetime import datetime, timezone from functools import lru_cache from typing import Any, TypedDict @@ -117,7 +117,9 @@ async def run( ) ) ) - except Exception as exc: + # Broad by design: ONNX/wasm inference and the JS canvas calls raise arbitrary types, and a long-running + # detection loop must surface the error and keep detecting rather than die on one bad frame. + except Exception as exc: # noqa: BLE001 message = f"pyface1 face detection: inference error\n{exc}" set_status(message) log(f"inference error: {exc}") @@ -281,7 +283,7 @@ def decode_outputs( return { "detections": detections, "confidence": float(confidence), - "processed_at": datetime.now().strftime("%X"), + "processed_at": datetime.now(timezone.utc).strftime("%X"), } diff --git a/services/ws-pyo3-runner/python/fanout.py b/services/ws-pyo3-runner/python/fanout.py index fa3c42bf..a01eb9c4 100644 --- a/services/ws-pyo3-runner/python/fanout.py +++ b/services/ws-pyo3-runner/python/fanout.py @@ -28,8 +28,8 @@ def init(send, _storage) -> None: def on_binary_frame(frame: bytes) -> None: """Push one binary frame per unit of the count in the first byte.""" if not frame: - return None + return count = frame[0] for i in range(count): _send.binary(bytes([i])) - return None + return diff --git a/services/ws-pyo3-runner/python/storage_pingpong.py b/services/ws-pyo3-runner/python/storage_pingpong.py index bd4d6951..9de9bc2b 100644 --- a/services/ws-pyo3-runner/python/storage_pingpong.py +++ b/services/ws-pyo3-runner/python/storage_pingpong.py @@ -42,7 +42,7 @@ def on_binary_frame(frame: bytes) -> None: key = key_bytes.decode("utf-8") _storage.put(key, value) _logger.info("stored %d bytes at key=%s", len(value), key) - return None + return key = frame.decode("utf-8") value = _storage.get(_agent_id, key) @@ -51,4 +51,4 @@ def on_binary_frame(frame: bytes) -> None: else: _send.binary(value) _logger.info("fetched key=%s (%d bytes)", key, 0 if value is None else len(value)) - return None + return diff --git a/services/ws-wasi-runner/Cargo.toml b/services/ws-wasi-runner/Cargo.toml index a85307b7..08e41d87 100644 --- a/services/ws-wasi-runner/Cargo.toml +++ b/services/ws-wasi-runner/Cargo.toml @@ -65,7 +65,7 @@ wasmtime-wasi.workspace = true # 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 it. +# Workspace pins `ort = "=2.0.0-rc.10"` because wasmtime-wasi-nn 47.0.3 is still 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. From 9440a53b3f632307d5a41b654ff526981021759f Mon Sep 17 00:00:00 2001 From: John Vandenberg Date: Mon, 3 Aug 2026 11:40:39 +0800 Subject: [PATCH 3/9] lints and fixes --- .github/workflows/dependencies.yaml | 2 +- Dockerfile.nanoserver | 7 +++++++ Dockerfile.windows | 3 +++ config/deny.toml | 10 ++++------ config/osv-scanner.toml | 1 - services/ws-server/Dockerfile | 3 ++- 6 files changed, 17 insertions(+), 9 deletions(-) diff --git a/.github/workflows/dependencies.yaml b/.github/workflows/dependencies.yaml index 9d051192..d2482c1f 100644 --- a/.github/workflows/dependencies.yaml +++ b/.github/workflows/dependencies.yaml @@ -50,7 +50,7 @@ jobs: - name: Install mise + dependency-scan tools uses: ./.github/actions/install-mise with: - install-action-tools: cargo-deny,cargo-unmaintained,coreutils,osv-scanner,ripgrep + install-action-tools: cargo-deny,cargo-unmaintained,coreutils,jaq,osv-scanner,ripgrep - name: Generate config/osv-scanner.toml from config/deny.toml run: mise run gen:osv-scanner diff --git a/Dockerfile.nanoserver b/Dockerfile.nanoserver index d8066d8e..665398ff 100644 --- a/Dockerfile.nanoserver +++ b/Dockerfile.nanoserver @@ -105,6 +105,9 @@ COPY --from=gpgbin C:\gnupg C:\gnupg # Run as cmd (the only shell Nano Server has) and as ContainerAdministrator. # ContainerAdministrator is so we can write under C:\ (the backtick line continuations # below rely on the escape directive documented at the top of this file). +# DL3066 wants a numeric user-id, which Windows containers have no equivalent of -- identities are SIDs behind +# well-known account names, and ContainerAdministrator is the canonical one. There is nothing numeric to use. +# hadolint ignore=DL3066 USER ContainerAdministrator SHELL ["cmd", "/S", "/C"] @@ -273,6 +276,10 @@ RUN (if exist C:\token\gh_token set /p GITHUB_TOKEN=nul diff --git a/Dockerfile.windows b/Dockerfile.windows index b55ed560..ec53599c 100644 --- a/Dockerfile.windows +++ b/Dockerfile.windows @@ -33,6 +33,9 @@ ARG WINDOWS_VERSION=ltsc2022 # config, and runs `mise install` against the always-loaded `[tools]` set. FROM mcr.microsoft.com/windows/servercore:${WINDOWS_VERSION} AS build-minimal SHELL ["cmd", "/S", "/C"] +# DL3066 wants a numeric user-id, which Windows containers have no equivalent of. +# Identities are SIDs behind well-known account names; ContainerAdministrator is the canonical one. +# hadolint ignore=DL3066 USER ContainerAdministrator # Install the VC++ runtime needed by msvc-built executables. diff --git a/config/deny.toml b/config/deny.toml index b6999284..31fa0948 100644 --- a/config/deny.toml +++ b/config/deny.toml @@ -18,9 +18,6 @@ ignore = [ # 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. - "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. @@ -33,9 +30,10 @@ ignore = [ # 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. + # rand 0.8.5 -- transitive via deno_crypto / deno_fs / deno_node (web-runner). + # Used for non-cryptographic randomness (jitter, salt scratchpads). Awaiting upstream Deno's migration to + # rand 0.9; the wasi-runner's own cap-rand path went away with the wasmtime 47 bump. cargo-deny reports this + # unmatched while osv-scanner still filters it, so the entry is live despite the advisory-not-detected warning. "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 diff --git a/config/osv-scanner.toml b/config/osv-scanner.toml index 61f393b8..f9b85e3f 100644 --- a/config/osv-scanner.toml +++ b/config/osv-scanner.toml @@ -9,5 +9,4 @@ IgnoredVulns = [ { id = "RUSTSEC-2026-0097" }, { id = "RUSTSEC-2026-0118" }, { id = "RUSTSEC-2026-0119" }, - { id = "RUSTSEC-2026-0173" }, ] diff --git a/services/ws-server/Dockerfile b/services/ws-server/Dockerfile index 0eb9d5ad..a3024cdb 100644 --- a/services/ws-server/Dockerfile +++ b/services/ws-server/Dockerfile @@ -48,7 +48,8 @@ COPY --from=builder /workspace/runtime-deps/node_modules/stats-gl ./node_modules RUN mkdir -p /app/storage \ && chown -R app:app /app -USER app +# Numeric uid, matching the useradd above, so a host inspecting the image resolves it without our passwd file. +USER 10001 EXPOSE 8080 8443 From b63888492c53cd403168ea24483cea53bde60899 Mon Sep 17 00:00:00 2001 From: John Vandenberg Date: Mon, 3 Aug 2026 13:13:30 +0800 Subject: [PATCH 4/9] fixes --- .mise/config.coverage.toml | 6 +++++ .mise/config.toml | 37 ++++++++++++++++++++++----- .mise/config.windows.toml | 16 ++++++++++++ config/conftest/policy/mise/mise.rego | 6 +++-- 4 files changed, 56 insertions(+), 9 deletions(-) diff --git a/.mise/config.coverage.toml b/.mise/config.coverage.toml index 88ddeb0a..bf62c5e1 100644 --- a/.mise/config.coverage.toml +++ b/.mise/config.coverage.toml @@ -57,6 +57,12 @@ coreutils rm -rf target/debug target/release # `--sh` is the current flag (the old `--export-prefix` alias is deprecated). This sets the rustc wrapper + # LLVM_PROFILE_FILE + profile dir so nextest and the regens instrument into one profile set that `report` merges. eval "$(cargo llvm-cov show-env --sh)" +# Pre-create the dirs ort-sys symlinks its dylibs into, for the same nightly-cargo reason as cargo-doc-check. +# It has to come after the eval above, which is what points CARGO_TARGET_DIR at the instrumented target dir -- +# a `depends` task would run in its own process and never see it. This whole env builds on nightly +# (RUSTUP_TOOLCHAIN above), so every cargo invocation below hits the same build-script layout. +ort_build_dir="${CARGO_TARGET_DIR:-target}${CARGO_BUILD_TARGET:+/$CARGO_BUILD_TARGET}/debug/build" +coreutils mkdir -p "$ort_build_dir/examples" "$ort_build_dir/deps" # nextest SIGKILLs the spawned et-ws-pyo3-runner (services/ws-pyo3-runner/tests/modules.rs) after each exchange. # LLVM flushes a process's counters only at exit, so a SIGKILLed process contributes nothing: the runner's async # run/drive/worker paths reported 0% while its in-process initialize() was covered. Continuous mode (%c) memory-maps diff --git a/.mise/config.toml b/.mise/config.toml index cb954b8d..4d769a91 100644 --- a/.mise/config.toml +++ b/.mise/config.toml @@ -70,9 +70,10 @@ action-validator = { version = "latest", os = ["linux", "macos"] } "aqua:vectordotdev/vector" = "0.56.0" ast-grep = "latest" # aube is the npm: backend, and mandatory because npm.package_manager names it. -# aqua ships linux/macos-arm64/windows; macos/x64 has no aqua asset, so it takes the cargo backend -- allowed -# there because macos/x64 is second-tier. -aube = { version = "latest", os = ["linux", "macos/arm64", "windows"] } +# aqua covers linux + macos/arm64. macos/x64 has no aqua asset and takes the cargo backend, allowed there because +# macos/x64 is second-tier. Windows has an aqua asset but its shim cannot resolve the extracted exe, so +# config.windows.toml supplies http:aube instead; between the three entries every platform is covered. +aube = { version = "latest", os = ["linux", "macos/arm64"] } cargo-binstall = "latest" "cargo:aube" = { version = "latest", os = ["macos/x64"] } "cargo:cargo-expand" = { version = "latest", os = ["linux", "macos"] } @@ -704,7 +705,26 @@ description = "Type-check rustdoc with -D warnings to fail on any doc issues" # `cargo +nightly fmt`. Drop `-Z unstable-options --check` and switch back to plain `cargo doc` once # rustdoc stabilises `--check`. env = { RUSTDOCFLAGS = "-Z unstable-options --check -D warnings" } -run = "cargo +nightly doc --keep-going --workspace --no-deps --document-private-items {{ vars.cargo_ws_excludes }}" +# The mkdir works around ort-sys assuming a fixed OUT_DIR depth; it must precede any nightly cargo build. +# Nightly cargo (1.99.0-nightly, 7c83d4cc0, 2026-07-29) writes build-script output to `build///out` +# where stable still uses `build/-/out`. ort-sys 2.0.0-rc.10's `copy_libraries` locates the profile +# dir via `out_dir.ancestors().nth(3)`, so that extra path segment lands it on `/debug/build` instead of +# `/debug`. It then symlinks the ONNX Runtime dylibs into that dir plus `examples/` and `deps/` beneath +# it, and the unwrapped symlink aborts on the first of those that does not exist: +# thread 'main' panicked at .../ort-sys-2.0.0-rc.10/build.rs:129:66: +# called `Result::unwrap()` on an `Err` value: Os { code: 2, kind: NotFound, message: "No such file or directory" } +# Seen on commit 9440a53b3f632307d5a41b654ff526981021759f in the macos + ubuntu check lanes, e.g. +# https://github.com/edge-toolkit/core/actions/runs/30782416445/job/91589563319 -- and reproducible locally. +# The feature cannot simply be switched off: wasmtime-wasi-nn declares `features = ["copy-dylibs"]` on its own +# ort dependency, so feature unification keeps it enabled regardless of this workspace's ort entry. Creating the +# dirs is inert under the classic layout. Drop this once ort stops hardcoding the depth -- 2.0.0-rc.13 still +# does, at build/dynamic_link.rs:18. +run = """ +ort_build_dir="${CARGO_TARGET_DIR:-target}${CARGO_BUILD_TARGET:+/$CARGO_BUILD_TARGET}/debug/build" +coreutils mkdir -p "$ort_build_dir/examples" "$ort_build_dir/deps" +cargo +nightly doc --keep-going --workspace --no-deps --document-private-items {{ vars.cargo_ws_excludes }} +""" +shell = "bash -euo pipefail -c" [tasks.cargo-clippy-fix] run = "cargo clippy --fix --allow-dirty --allow-staged --keep-going --workspace --tests {{ vars.cargo_ws_excludes }}" @@ -938,11 +958,14 @@ run = "ryl -c config/ryl.yaml .github config" # invoke it as their package manager, and a tool whose backend binary isn't there yet fails outright. CI runs this # first so the ordering is explicit rather than incidental. # -# The tera branch picks the platform's backend: aqua ships aube for linux/macos-arm64/windows, macos/x64 has no -# aqua asset and takes cargo:aube. Versions come from the `[tools]` pins, so no `@version` here. +# The tera branch picks the platform's backend, matching the three `[tools]` entries: aqua `aube` on +# linux/macos-arm64, `cargo:aube` on macos/x64, `http:aube` on Windows. Versions come from those pins, so no +# `@version` here. description = "Install the aube npm backend ahead of the npm: tools" run = """ -{% if os() == "macos" and arch() == "x64" %} +{% if os() == "windows" %} +mise install http:aube +{% elif os() == "macos" and arch() == "x64" %} mise install cargo:aube {% else %} mise install aube diff --git a/.mise/config.windows.toml b/.mise/config.windows.toml index 987a9b1e..0110be9e 100644 --- a/.mise/config.windows.toml +++ b/.mise/config.windows.toml @@ -47,6 +47,22 @@ # rewrite the task PATH into msys form, which native busybox-w32 can't resolve. Checksum-pinned so a silent # upstream rebuild fails loudly; `.exe` because mise writes the bin name verbatim and Windows launches by # extension. +# Prebuilt aube for Windows, replacing the aqua entry that config.toml scopes off this OS. +# The aqua entry installs fine here (`mise aube@1.37.0 installed`) but its shim cannot resolve the binary: the +# msvc zip holds aube.exe/aubr.exe/aubx.exe at the archive root with no bin/ dir, so every npm: install dies with +# mise ERROR cannot find binary path +# mise ERROR C:\Users\ContainerAdministrator\AppData\Local\mise\shims\aube.exe failed +# then `npm:: ...\shims\aube.exe exited with non-zero status: exit code 1`. Seen on commit +# 9440a53b3f632307d5a41b654ff526981021759f across all four Windows build lanes, e.g. +# https://github.com/edge-toolkit/core/actions/runs/30782416387/job/91589563131. Naming the executable via `bin` +# resolves it; `.exe` because mise writes the bin name verbatim and Windows launches by extension. Checksum-pinned +# so a silent upstream re-release fails loudly. Bump url + version + checksum together. +[tools."http:aube"] +bin = "aube.exe" +checksum = "sha256:7a264819a7c05d107ac72916f82209a183bfd26842b9528f1ca281bb32596c5e" +url = "https://github.com/jdx/aube/releases/download/v1.37.0/aube-v1.37.0-x86_64-pc-windows-msvc.zip" +version = "1.37.0" + [tools."http:busybox"] bin = "ash.exe" checksum = "sha256:6e263d154d8548d1eb936f65d1d8312c80df31c45974e48d6335e4dcc0f4f34c" diff --git a/config/conftest/policy/mise/mise.rego b/config/conftest/policy/mise/mise.rego index 4c59633a..4c5174b8 100644 --- a/config/conftest/policy/mise/mise.rego +++ b/config/conftest/policy/mise/mise.rego @@ -148,10 +148,12 @@ allowed_os_scoped_tool := { # action-validator (aqua) has no Windows build, so it is os-scoped off Windows. # config.windows.toml installs cargo:action-validator there instead (cargo-quickinstall msvc prebuilt). "action-validator", - # aube is the mandatory npm: backend; aqua ships no darwin/amd64 asset, so macos/x64 takes cargo:aube instead. - # Between the two entries every platform is covered. + # aube is the mandatory npm: backend, split across three backends because no single one covers every platform. + # aqua for linux + macos/arm64, cargo:aube for macos/x64 (no aqua asset), http:aube for Windows (aqua installs + # but its shim cannot resolve the extracted exe). Between the three entries every platform is covered. "aube", "cargo:aube", + "http:aube", "http:chromedriver", "pipx", "pipx:torch", From 3fe9839887dbab4cffbbc55998058f617fa0c745 Mon Sep 17 00:00:00 2001 From: John Vandenberg Date: Mon, 3 Aug 2026 14:19:02 +0800 Subject: [PATCH 5/9] more fixes --- .mise/config.coverage.toml | 15 ++ .mise/config.windows.toml | 33 +-- generated/zig-rest/src/et_rest_client.zig | 235 +++++++++++----------- 3 files changed, 151 insertions(+), 132 deletions(-) diff --git a/.mise/config.coverage.toml b/.mise/config.coverage.toml index bf62c5e1..734eb15c 100644 --- a/.mise/config.coverage.toml +++ b/.mise/config.coverage.toml @@ -28,6 +28,21 @@ # the minicov feature come from the wasm_cov / *_cov_feat vars so uninstrumented builds stay clean. [env] RUSTUP_TOOLCHAIN = "nightly" +# Resolve libonnxruntime at runtime from the mise install rather than from ort-sys's dylib copies. +# ort-sys symlinks the ONNX Runtime dylibs next to the built binaries, but it derives that directory from +# `OUT_DIR/../../..`, which nightly's `build///out` layout shifts down to `/debug/build`. +# The copies therefore land in `/debug/build{,/examples,/deps}` and never beside the test binaries, so +# et-ws-wasi-runner starts and immediately dies with no output -- surfacing in the wasi-runner tests as +# called `Result::unwrap()` on an `Err` value: Output(OutputError { program: ".../target/debug/et-ws-wasi-runner", +# status: ExitStatus(unix_wait_status(32512)), stdout_utf8: "", stderr_utf8: "", user_error: None }) +# (32512 >> 8 == 127, the loader's "cannot start" code). Seen on commit +# b63888492c53cd403168ea24483cea53bde60899 at +# https://github.com/edge-toolkit/core/actions/runs/30786563725/job/91601098246, in all four wasi-runner tests. +# Pointing the loader straight at the install dir is layout-independent, so it holds however cargo lays the +# build dir out. Scoped to this env because only the coverage lane runs binaries under nightly. Drop it together +# with the cargo-doc-check mkdir once ort stops hardcoding the OUT_DIR depth. +DYLD_LIBRARY_PATH = "{{ vars.ort_loc_unix }}/lib" +LD_LIBRARY_PATH = "{{ vars.ort_loc_unix }}/lib" _.path = "{{ env.HOME }}/.local/share/mise/installs/conda-clang/latest/bin" [tasks.cargo-llvm-cov] diff --git a/.mise/config.windows.toml b/.mise/config.windows.toml index 0110be9e..591d55b5 100644 --- a/.mise/config.windows.toml +++ b/.mise/config.windows.toml @@ -39,30 +39,35 @@ # Failing due to latest mise. # "cargo:action-validator" = { version = "latest", 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 / IdnToUnicode that Nano's stripped kernel32 -# forwarder doesn't re-export (-> 0xC0000139; unfixable -- the system file + registry are ACL-locked). -# Renamed `ash.exe` so mise -- whose POSIX-shell list is bash/sh/zsh/fish/ksh/dash, NOT ash -- doesn't -# rewrite the task PATH into msys form, which native busybox-w32 can't resolve. Checksum-pinned so a silent -# upstream rebuild fails loudly; `.exe` because mise writes the bin name verbatim and Windows launches by -# extension. # Prebuilt aube for Windows, replacing the aqua entry that config.toml scopes off this OS. # The aqua entry installs fine here (`mise aube@1.37.0 installed`) but its shim cannot resolve the binary: the # msvc zip holds aube.exe/aubr.exe/aubx.exe at the archive root with no bin/ dir, so every npm: install dies with # mise ERROR cannot find binary path # mise ERROR C:\Users\ContainerAdministrator\AppData\Local\mise\shims\aube.exe failed -# then `npm:: ...\shims\aube.exe exited with non-zero status: exit code 1`. Seen on commit -# 9440a53b3f632307d5a41b654ff526981021759f across all four Windows build lanes, e.g. -# https://github.com/edge-toolkit/core/actions/runs/30782416387/job/91589563131. Naming the executable via `bin` -# resolves it; `.exe` because mise writes the bin name verbatim and Windows launches by extension. Checksum-pinned -# so a silent upstream re-release fails loudly. Bump url + version + checksum together. +# then `npm:: ...\shims\aube.exe exited with non-zero status: exit code 1` -- mise's npm backend invokes the +# shim rather than the tool directly. Seen on commit 9440a53b3f632307d5a41b654ff526981021759f across all four +# Windows build lanes, e.g. https://github.com/edge-toolkit/core/actions/runs/30782416387/job/91589563131. +# +# `bin` is the in-archive name and deliberately carries NO `.exe`, unlike http:busybox below. That entry +# downloads a bare .exe rather than an archive, so mise writes the downloaded file using the bin name verbatim +# and needs the extension; for an archive mise resolves `/` and supplies the platform's +# executable suffix itself. Spelling it `aube.exe` here reproduced the identical `cannot find binary path` on +# commit b63888492c53cd403168ea24483cea53bde60899. Checksum-pinned so a silent upstream re-release fails +# loudly. Bump url + version + checksum together. [tools."http:aube"] -bin = "aube.exe" +bin = "aube" checksum = "sha256:7a264819a7c05d107ac72916f82209a183bfd26842b9528f1ca281bb32596c5e" url = "https://github.com/jdx/aube/releases/download/v1.37.0/aube-v1.37.0-x86_64-pc-windows-msvc.zip" version = "1.37.0" +# 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 / IdnToUnicode that Nano's stripped kernel32 +# forwarder doesn't re-export (-> 0xC0000139; unfixable -- the system file + registry are ACL-locked). +# Renamed `ash.exe` so mise -- whose POSIX-shell list is bash/sh/zsh/fish/ksh/dash, NOT ash -- doesn't +# rewrite the task PATH into msys form, which native busybox-w32 can't resolve. Checksum-pinned so a silent +# upstream rebuild fails loudly; `.exe` because mise writes the bin name verbatim and Windows launches by +# extension. [tools."http:busybox"] bin = "ash.exe" checksum = "sha256:6e263d154d8548d1eb936f65d1d8312c80df31c45974e48d6335e4dcc0f4f34c" diff --git a/generated/zig-rest/src/et_rest_client.zig b/generated/zig-rest/src/et_rest_client.zig index f50bcfcc..6bac7eb2 100644 --- a/generated/zig-rest/src/et_rest_client.zig +++ b/generated/zig-rest/src/et_rest_client.zig @@ -1,7 +1,8 @@ // -// this code was generated by openapi2zig 0.4.0 (1766c90) -// changes to this file may cause incorrect behavior and will be lost if the code is regenerated +// This code was generated by openapi2zig 0.4.2 (ac9e5bf) +// Changes to this file may cause incorrect behavior and will be lost if the code is regenerated // + const std = @import("std"); /////////////////////////////////////////// @@ -210,6 +211,118 @@ pub fn getJsonResult(comptime T: type, client: *Client, path: []const u8) !ApiRe pub fn postJsonResult(comptime T: type, client: *Client, path: []const u8, payload: anytype) !ApiResult(T) { return parseRawResponse(T, try postJsonRaw(client, path, payload)); } +fn stringifyStreamRequest(allocator: std.mem.Allocator, requestBody: anytype) ![]u8 { + var buf: std.Io.Writer.Allocating = .init(allocator); + defer buf.deinit(); + try std.json.Stringify.value(requestBody, .{ .emit_null_optional_fields = false }, &buf.writer); + + var parsed = try std.json.parseFromSlice(std.json.Value, allocator, buf.written(), .{ .ignore_unknown_fields = true }); + defer parsed.deinit(); + + if (parsed.value == .object) { + try parsed.value.object.put(parsed.arena.allocator(), "stream", .{ .bool = true }); + } + + var out: std.Io.Writer.Allocating = .init(allocator); + errdefer out.deinit(); + try std.json.Stringify.value(parsed.value, .{ .emit_null_optional_fields = false }, &out.writer); + return try out.toOwnedSlice(); +} + +fn streamJsonTyped(comptime T: type, client: *Client, path: []const u8, requestBody: anytype, callback: anytype, cancellation_token: ?*CancellationToken) !void { + const Callback = @TypeOf(callback.*); + var typed_callback: TypedSseCallback(T, Callback) = .{ .allocator = client.allocator, .callback = callback }; + try streamJson(client, path, requestBody, &typed_callback, cancellation_token); +} + +fn streamJson(client: *Client, path: []const u8, requestBody: anytype, callback: anytype, cancellation_token: ?*CancellationToken) !void { + const allocator = client.allocator; + const payload = try stringifyStreamRequest(allocator, requestBody); + defer allocator.free(payload); + + var headers = std.ArrayList(std.http.Header).empty; + defer headers.deinit(allocator); + const auth_header = try appendClientHeaders(allocator, &headers, client, "application/json", "text/event-stream"); + defer if (auth_header) |value| allocator.free(value); + + const url = try std.fmt.allocPrint(allocator, "{s}{s}", .{ client.base_url, path }); + defer allocator.free(url); + + if (client.http_observer) |obs| { + if (obs.onRequest) |cb| cb(obs.ctx, .POST, url, headers.items, payload); + } + + const uri = try std.Uri.parse(url); + try checkCancellation(cancellation_token); + + const start = std.Io.Clock.awake.now(client.io); + var req = client.http.request(.POST, uri, .{ + .redirect_behavior = .unhandled, + .headers = .{ .accept_encoding = .{ .override = "identity" } }, + .extra_headers = headers.items, + }) catch |err| { + if (client.http_observer) |obs| { + if (obs.onError) |cb| cb(obs.ctx, .POST, url, @errorName(err)); + } + return err; + }; + defer req.deinit(); + + req.transfer_encoding = .{ .content_length = payload.len }; + var request_body = try req.sendBodyUnflushed(&.{}); + try request_body.writer.writeAll(payload); + try request_body.end(); + try req.connection.?.flush(); + try checkCancellation(cancellation_token); + + var response = req.receiveHead(&.{}) catch |err| { + if (client.http_observer) |obs| { + if (obs.onError) |cb| cb(obs.ctx, .POST, url, @errorName(err)); + } + return err; + }; + const elapsed_ns = @as(u64, @intCast(start.untilNow(client.io, .awake).nanoseconds)); + if (response.head.status.class() != .success) { + if (client.http_observer) |obs| { + if (obs.onResponse) |cb| cb(obs.ctx, .POST, url, response.head.status, &.{}, "", elapsed_ns); + } + return error.ResponseError; + } + + if (client.http_observer) |obs| { + if (obs.onResponse) |cb| cb(obs.ctx, .POST, url, response.head.status, &.{}, "", elapsed_ns); + } + + var transfer_buffer: [8 * 1024]u8 = undefined; + const reader = response.reader(&transfer_buffer); + parseSseReader(allocator, reader, callback, cancellation_token) catch |err| switch (err) { + error.ReadFailed => return response.bodyErr() orelse err, + else => return err, + }; +} + +fn appendClientHeaders(allocator: std.mem.Allocator, headers: *std.ArrayList(std.http.Header), client: *Client, content_type: ?[]const u8, accept: []const u8) !?[]u8 { + if (content_type) |ct| { + try headers.append(allocator, .{ .name = "Content-Type", .value = ct }); + } + try headers.append(allocator, .{ .name = "Accept", .value = accept }); + + var auth_header: ?[]u8 = null; + if (client.api_key.len > 0) { + auth_header = try std.fmt.allocPrint(allocator, "Bearer {s}", .{client.api_key}); + try headers.append(allocator, .{ .name = "Authorization", .value = auth_header.? }); + } + if (client.organization) |organization| { + try headers.append(allocator, .{ .name = "OpenAI-Organization", .value = organization }); + } + if (client.project) |project| { + try headers.append(allocator, .{ .name = "OpenAI-Project", .value = project }); + } + for (client.default_headers) |header| { + try headers.append(allocator, header); + } + return auth_header; +} pub const CancellationToken = struct { cancelled: std.atomic.Value(bool), @@ -233,9 +346,6 @@ fn checkCancellation(token: ?*CancellationToken) !void { } } -const max_sse_line_size = 256 * 1024; -const max_sse_event_size = 1024 * 1024; - pub fn parseSseBytes(allocator: std.mem.Allocator, bytes: []const u8, callback: anytype, cancellation_token: ?*CancellationToken) !void { var reader: std.Io.Reader = .fixed(bytes); try parseSseReader(allocator, &reader, callback, cancellation_token); @@ -329,119 +439,8 @@ pub fn parseSseReaderTyped(comptime T: type, allocator: std.mem.Allocator, reade var typed_callback: TypedSseCallback(T, Callback) = .{ .allocator = allocator, .callback = callback }; try parseSseReader(allocator, reader, &typed_callback, cancellation_token); } - -fn stringifyStreamRequest(allocator: std.mem.Allocator, requestBody: anytype) ![]u8 { - var str: std.Io.Writer.Allocating = .init(allocator); - defer str.deinit(); - try std.json.Stringify.value(requestBody, .{ .emit_null_optional_fields = false }, &str.writer); - - var parsed = try std.json.parseFromSlice(std.json.Value, allocator, str.written(), .{ .ignore_unknown_fields = true }); - defer parsed.deinit(); - - if (parsed.value == .object) { - try parsed.value.object.put(parsed.arena.allocator(), "stream", .{ .bool = true }); - } - - var out: std.Io.Writer.Allocating = .init(allocator); - errdefer out.deinit(); - try std.json.Stringify.value(parsed.value, .{ .emit_null_optional_fields = false }, &out.writer); - return try out.toOwnedSlice(); -} - -fn streamJsonTyped(comptime T: type, client: *Client, path: []const u8, requestBody: anytype, callback: anytype, cancellation_token: ?*CancellationToken) !void { - const Callback = @TypeOf(callback.*); - var typed_callback: TypedSseCallback(T, Callback) = .{ .allocator = client.allocator, .callback = callback }; - try streamJson(client, path, requestBody, &typed_callback, cancellation_token); -} - -fn streamJson(client: *Client, path: []const u8, requestBody: anytype, callback: anytype, cancellation_token: ?*CancellationToken) !void { - const allocator = client.allocator; - const payload = try stringifyStreamRequest(allocator, requestBody); - defer allocator.free(payload); - - var headers = std.ArrayList(std.http.Header).empty; - defer headers.deinit(allocator); - const auth_header = try appendClientHeaders(allocator, &headers, client, "application/json", "text/event-stream"); - defer if (auth_header) |value| allocator.free(value); - - const url = try std.fmt.allocPrint(allocator, "{s}{s}", .{ client.base_url, path }); - defer allocator.free(url); - - if (client.http_observer) |obs| { - if (obs.onRequest) |cb| cb(obs.ctx, .POST, url, headers.items, payload); - } - - const uri = try std.Uri.parse(url); - try checkCancellation(cancellation_token); - - const start = std.Io.Clock.awake.now(client.io); - var req = client.http.request(.POST, uri, .{ - .redirect_behavior = .unhandled, - .headers = .{ .accept_encoding = .{ .override = "identity" } }, - .extra_headers = headers.items, - }) catch |err| { - if (client.http_observer) |obs| { - if (obs.onError) |cb| cb(obs.ctx, .POST, url, @errorName(err)); - } - return err; - }; - defer req.deinit(); - - req.transfer_encoding = .{ .content_length = payload.len }; - var request_body = try req.sendBodyUnflushed(&.{}); - try request_body.writer.writeAll(payload); - try request_body.end(); - try req.connection.?.flush(); - try checkCancellation(cancellation_token); - - var response = req.receiveHead(&.{}) catch |err| { - if (client.http_observer) |obs| { - if (obs.onError) |cb| cb(obs.ctx, .POST, url, @errorName(err)); - } - return err; - }; - const elapsed_ns = @as(u64, @intCast(start.untilNow(client.io, .awake).nanoseconds)); - if (response.head.status.class() != .success) { - if (client.http_observer) |obs| { - if (obs.onResponse) |cb| cb(obs.ctx, .POST, url, response.head.status, &.{}, "", elapsed_ns); - } - return error.ResponseError; - } - - if (client.http_observer) |obs| { - if (obs.onResponse) |cb| cb(obs.ctx, .POST, url, response.head.status, &.{}, "", elapsed_ns); - } - - var transfer_buffer: [8 * 1024]u8 = undefined; - const reader = response.reader(&transfer_buffer); - parseSseReader(allocator, reader, callback, cancellation_token) catch |err| switch (err) { - error.ReadFailed => return response.bodyErr() orelse err, - else => return err, - }; -} - -fn appendClientHeaders(allocator: std.mem.Allocator, headers: *std.ArrayList(std.http.Header), client: *Client, content_type: ?[]const u8, accept: []const u8) !?[]u8 { - if (content_type) |ct| { - try headers.append(allocator, .{ .name = "Content-Type", .value = ct }); - } - try headers.append(allocator, .{ .name = "Accept", .value = accept }); - - var auth_header: ?[]u8 = null; - if (client.api_key.len > 0) { - auth_header = try std.fmt.allocPrint(allocator, "Bearer {s}", .{client.api_key}); - try headers.append(allocator, .{ .name = "Authorization", .value = auth_header.? }); - } - if (client.organization) |organization| { - try headers.append(allocator, .{ .name = "OpenAI-Organization", .value = organization }); - } - if (client.project) |project| { - try headers.append(allocator, .{ .name = "OpenAI-Project", .value = project }); - } - for (client.default_headers) |header| { - try headers.append(allocator, header); - } - return auth_header; -} +const max_sse_line_size = 256 * 1024; +const max_sse_event_size = 1024 * 1024; ///////////////// // Summary: From 12bfe41934f07417c249f78f071481ffce3af75a Mon Sep 17 00:00:00 2001 From: John Vandenberg Date: Mon, 3 Aug 2026 16:30:39 +0800 Subject: [PATCH 6/9] force update of mise --- .../actions/install-mise-tools/action.yaml | 15 ---- .github/actions/install-mise/action.yaml | 2 +- .github/workflows/docker-windows.yaml | 2 +- .mise/config.coverage.toml | 11 ++- .mise/config.js.toml | 16 +--- .mise/config.toml | 47 +--------- .mise/config.windows.toml | 36 ++++---- Dockerfile | 3 +- README.md | 2 +- .../rules/doc-summary-ends-with-period.yaml | 2 + config/conftest/policy/mise/mise.rego | 6 -- .../mise-cargo-backend-allowlist.schema.json | 1 - libs/edge-toolkit/src/config.rs | 10 ++- libs/edge-toolkit/tests/npm_mod.rs | 44 ++++++++++ services/modules/tests/symlinks.rs | 87 ++++++++++++------- 15 files changed, 143 insertions(+), 141 deletions(-) diff --git a/.github/actions/install-mise-tools/action.yaml b/.github/actions/install-mise-tools/action.yaml index b2e2aba1..c524c162 100644 --- a/.github/actions/install-mise-tools/action.yaml +++ b/.github/actions/install-mise-tools/action.yaml @@ -15,10 +15,6 @@ inputs: description: >- Comma-separated tools to co-install via taiki-e/install-action in the same step as mise. Forwarded to install-mise's `extra-tools` input. - NOT used for `aube` -- install-action's manifest expects an `aubr` - binary that recent aube releases don't ship, so the install resolves - to a cargo source-build that flakes on crates.io SSL. aube installs - via `mise run setup-aube` below, off its `[tools]` pin. required: false default: "" @@ -37,17 +33,6 @@ runs: with: install-action-tools: ${{ inputs.install-action-tools }} - # The npm: backend, installed before the main `mise install`. - # Only useful when js env is loaded (it's the backend for npm:* tools, all of which live in config.js.toml); skip - # otherwise to avoid the install cost on workflows that don't need any npm: install. See [tasks.setup-aube] in - # .mise/config.toml for the full rationale. - - name: Install aube npm backend - if: contains(env.MISE_ENV, 'js') - shell: bash --noprofile --norc -euo pipefail {0} - env: - GITHUB_TOKEN: ${{ inputs.github-token }} - run: mise run setup-aube - - name: Install mise tools id: install-mise-tools continue-on-error: true diff --git a/.github/actions/install-mise/action.yaml b/.github/actions/install-mise/action.yaml index ecbccd53..a0ae782a 100644 --- a/.github/actions/install-mise/action.yaml +++ b/.github/actions/install-mise/action.yaml @@ -22,7 +22,7 @@ runs: - name: Install mise uses: taiki-e/install-action@v2 with: - tool: cargo-binstall,mise@2026.7.1 + tool: cargo-binstall,mise@2026.8.0 - name: Install extra tools via install-action if: inputs.install-action-tools != '' diff --git a/.github/workflows/docker-windows.yaml b/.github/workflows/docker-windows.yaml index f6b738ed..3ba77928 100644 --- a/.github/workflows/docker-windows.yaml +++ b/.github/workflows/docker-windows.yaml @@ -50,7 +50,7 @@ jobs: # The classic Windows builder on windows-2022 can't substitute build-args into the Dockerfile's RUN, and # mise's prebuilt "latest" zip is stale (2026.3.0, too old for the config). 2026.7.1 is required: the config # templates use Tera v2 syntax, which mise switched to in 2026.7.1 (older mise fails to parse them). - MISE_VERSION: "2026.7.1" + MISE_VERSION: "2026.8.0" steps: - name: Checkout uses: actions/checkout@v4 diff --git a/.mise/config.coverage.toml b/.mise/config.coverage.toml index 734eb15c..6b6fde76 100644 --- a/.mise/config.coverage.toml +++ b/.mise/config.coverage.toml @@ -148,7 +148,16 @@ AWK : > "$covdir/wasi.lcov" for profraw in "$covdir"/*.profraw; do name="$(coreutils basename "$profraw" .profraw)" - ll="$(find target -path '*/release/deps/*' -name "$name*.ll" 2>/dev/null | coreutils head -n 1)" + # Look for the .ll in nightly's new build-dir layout first, then the classic one. + # `-Z build-dir-new-layout` (now the default on nightly, which this env pins) moved per-crate intermediates + # from `/deps/` to `/build///out/`, so the deps-only glob this used to run found + # nothing and every module aborted the task with `wasi-cov: no .ll found for et_ws_audio1`. Observed on commit + # 3fe98398 at https://github.com/edge-toolkit/core/actions/runs/30789818011/job/91610793550. Both globs stay so + # the task works whichever layout the toolchain produces; drop the deps fallback once no supported cargo emits it. + ll="$(find target -path '*/release/build/*' -name "$name*.ll" 2>/dev/null | coreutils head -n 1)" + if [ -z "$ll" ]; then + ll="$(find target -path '*/release/deps/*' -name "$name*.ll" 2>/dev/null | coreutils head -n 1)" + fi if [ -z "$ll" ]; then echo "wasi-cov: no .ll found for $name"; exit 1; fi pd="$covdir/$name.profdata" obj="$covdir/$name.o" diff --git a/.mise/config.js.toml b/.mise/config.js.toml index 758fe464..b2fb4d59 100644 --- a/.mise/config.js.toml +++ b/.mise/config.js.toml @@ -65,17 +65,6 @@ url = "https://github.com/oxc-project/oxc/releases/download/apps_v1.69.0/oxfmt-x [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" -[env] -# Exempt stats-gl from aube's add-time package-reputation gates. -# `aube add` refuses (non-interactively) any package whose weekly npm downloads fall under -# `lowDownloadThreshold`, whose name is within a short edit distance of a top-100,000 package, or that was -# registered very recently -- a typosquat defence. stats-gl trips the similar-name arm and fails the install with -# `ERR_AUBE_LOW_DOWNLOAD_PACKAGE` ("... edit distance 2)? Pass --allow-low-downloads after verifying the package -# name."); mise's npm backend builds the aube command line itself, so the per-invocation flag isn't reachable. -# This lists the one exact name affected rather than resetting the threshold or globbing, and aube's OSV `MAL-*` -# malicious-package check is explicitly unaffected by the exemption, so the hard malware gate still applies. -AUBE_ALLOWED_UNPOPULAR_PACKAGES = "stats-gl" - [tasks.build-ws-har1-module] depends = ["build-et-cli", "build-wasm-cov-wrapper"] description = "Build the har1 workflow WASM module" @@ -128,9 +117,10 @@ description = "Format JS/TS sources in place (oxfmt)" run = "oxfmt --config config/oxfmtrc.jsonc --write . '!.mise/config*.toml'" [tasks.osv-scanner-npm] -depends = ["setup-aube"] description = "Scan the npm: tools' aube lockfiles for known vulnerabilities" # Collect each npm: tool's aube lockfile under one root, then scan that root. +# The lockfile comes from mise's own embedded aube, which its default `npm.package_manager = "auto"` uses for +# every `npm:` install -- no external package manager is involved, so nothing needs installing for this to work. # osv-scanner has no `aube-lock.yaml` extractor -- an explicit `--lockfile ` on one is rejected with # `could not determine extractor suitable to this file`, and a directory scan of the install tree reports # `No package sources found`. Its extractors are keyed on filename, so each lockfile is copied in as @@ -174,7 +164,7 @@ for tool in $tools; do lock=$(find "$root" -name aube-lock.yaml -type f | coreutils head -1) if [ -z "$lock" ]; then echo "osv-scanner-npm: $tool has no aube-lock.yaml under $root" >&2 - echo "osv-scanner-npm: it did not install via aube; check [settings] npm.package_manager" >&2 + echo "osv-scanner-npm: reinstall it so mise's embedded aube writes one" >&2 exit 1 fi slug=$(coreutils basename "$(coreutils dirname "$root")") diff --git a/.mise/config.toml b/.mise/config.toml index 4d769a91..23005276 100644 --- a/.mise/config.toml +++ b/.mise/config.toml @@ -10,20 +10,13 @@ # Run a check across everything: mise run check-all (or install-all) # Make a selection sticky: export MISE_ENV=dart -# Require release that introduced auto_env enabled in .miserc.toml -min_version = "2026.7.1" +# Track the latest mise release; the floor still covers auto_env enabled in .miserc.toml. +min_version = "2026.8.0" [settings] # Disable mise's two auto-install paths so tasks fail fast on a missing tool. # exec_auto_install covers `mise exec`, task.run_auto_install covers `mise run`. cargo.binstall = true -# Make every inline task body run under bash by default (Windows-only effect). -# bash is ash via MISE_BASH_PATH = busybox-w32 in config.windows.toml. Without this, mise spawns `cmd /c ""` -# and any task in the always-loaded base config hits the cmd-subprocess PATH dropout (cargo/pnpm/wasm-pack -# /etc. unresolvable) we worked around piecemeal with `shell = "bash -euo pipefail -c"` on prefetch:rust, -# prefetch:node, and build-et-cli. Setting the global flips every other task at once. Tasks that explicitly -# need cmd (e.g. dotnet-data1's `dotnet publish` chain) still set `shell` on the task to override. -windows_default_inline_shell_args = "bash -euo pipefail -c" # Disable mise backends we never use. # `asdf` is kept enabled because several of our bare-name tools (conftest, dprint, hadolint, pipx, rclone, # ruff, ...) list `asdf:` as the registry fallback after `aqua:`. `vfox` was the original target -- killed @@ -39,14 +32,6 @@ http_timeout = "2m" # This disables idiomatic-version-file detection for every tool. We declare versions in `.mise/config*.toml`; # no need to parse `.tool-versions`/`.node-version`/etc. on top of that. idiomatic_version_file_enable_tools = [] -# Pin the npm: backend to aube rather than letting mise pick one. -# The default "auto" uses aube when it happens to be installed and silently falls back to plain npm otherwise, -# which produces two different install layouts: aube writes `global-aube//aube-lock.yaml` (a full -# transitive lockfile), plain npm writes a bare `lib/node_modules` tree with no lockfile at all. osv-scanner -# cannot extract anything from a lockfile-less tree, so under "auto" a tool's vulnerability coverage depended on -# whether aube was present the day it was installed. Pinning to aube makes every npm: tool emit a lockfile, which -# is what osv-scanner-npm collects and scans. -npm.package_manager = "aube" task.run_auto_install = false # Hard-verify tool signatures via gpg on every platform. # Propagates to node.gpg_verify and swift.gpg_verify per mise/src/config/settings.rs. mise's node plugin @@ -69,13 +54,7 @@ action-validator = { version = "latest", os = ["linux", "macos"] } "aqua:rustwasm/wasm-pack" = "latest" "aqua:vectordotdev/vector" = "0.56.0" ast-grep = "latest" -# aube is the npm: backend, and mandatory because npm.package_manager names it. -# aqua covers linux + macos/arm64. macos/x64 has no aqua asset and takes the cargo backend, allowed there because -# macos/x64 is second-tier. Windows has an aqua asset but its shim cannot resolve the extracted exe, so -# config.windows.toml supplies http:aube instead; between the three entries every platform is covered. -aube = { version = "latest", os = ["linux", "macos/arm64"] } cargo-binstall = "latest" -"cargo:aube" = { version = "latest", os = ["macos/x64"] } "cargo:cargo-expand" = { version = "latest", os = ["linux", "macos"] } "cargo:open" = "latest" "cargo:wasm-opt" = { version = "latest", os = ["linux", "macos"] } @@ -951,28 +930,6 @@ run = "lychee --config config/lychee.toml '**/*.md' 'libs/**/*.rs' 'services/**/ description = "Lint YAML with ryl (a yamllint-compatible Rust linter)" run = "ryl -c config/ryl.yaml .github config" -[tasks.setup-aube] -# Install the npm: backend ahead of the main `mise install`. -# aube is a mandatory `[tools]` entry, so `mise install` would fetch it anyway; this task exists purely for -# ordering. mise resolves tools concurrently and gives no guarantee that aube lands before the `npm:*` tools that -# invoke it as their package manager, and a tool whose backend binary isn't there yet fails outright. CI runs this -# first so the ordering is explicit rather than incidental. -# -# The tera branch picks the platform's backend, matching the three `[tools]` entries: aqua `aube` on -# linux/macos-arm64, `cargo:aube` on macos/x64, `http:aube` on Windows. Versions come from those pins, so no -# `@version` here. -description = "Install the aube npm backend ahead of the npm: tools" -run = """ -{% if os() == "windows" %} -mise install http:aube -{% elif os() == "macos" and arch() == "x64" %} -mise install cargo:aube -{% else %} -mise install aube -{% endif %} -""" -shell = "bash -euo pipefail -c" - [tasks._setup_all] description = "Private shared cross-platform preinstall" hide = true diff --git a/.mise/config.windows.toml b/.mise/config.windows.toml index 591d55b5..a9109e62 100644 --- a/.mise/config.windows.toml +++ b/.mise/config.windows.toml @@ -39,27 +39,6 @@ # Failing due to latest mise. # "cargo:action-validator" = { version = "latest", install_env = { CARGO_BUILD_TARGET = "x86_64-pc-windows-msvc" } } -# Prebuilt aube for Windows, replacing the aqua entry that config.toml scopes off this OS. -# The aqua entry installs fine here (`mise aube@1.37.0 installed`) but its shim cannot resolve the binary: the -# msvc zip holds aube.exe/aubr.exe/aubx.exe at the archive root with no bin/ dir, so every npm: install dies with -# mise ERROR cannot find binary path -# mise ERROR C:\Users\ContainerAdministrator\AppData\Local\mise\shims\aube.exe failed -# then `npm:: ...\shims\aube.exe exited with non-zero status: exit code 1` -- mise's npm backend invokes the -# shim rather than the tool directly. Seen on commit 9440a53b3f632307d5a41b654ff526981021759f across all four -# Windows build lanes, e.g. https://github.com/edge-toolkit/core/actions/runs/30782416387/job/91589563131. -# -# `bin` is the in-archive name and deliberately carries NO `.exe`, unlike http:busybox below. That entry -# downloads a bare .exe rather than an archive, so mise writes the downloaded file using the bin name verbatim -# and needs the extension; for an archive mise resolves `/` and supplies the platform's -# executable suffix itself. Spelling it `aube.exe` here reproduced the identical `cannot find binary path` on -# commit b63888492c53cd403168ea24483cea53bde60899. Checksum-pinned so a silent upstream re-release fails -# loudly. Bump url + version + checksum together. -[tools."http:aube"] -bin = "aube" -checksum = "sha256:7a264819a7c05d107ac72916f82209a183bfd26842b9528f1ca281bb32596c5e" -url = "https://github.com/jdx/aube/releases/download/v1.37.0/aube-v1.37.0-x86_64-pc-windows-msvc.zip" -version = "1.37.0" - # 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 / IdnToUnicode that Nano's stripped kernel32 @@ -283,6 +262,21 @@ if ! echo "" | sha256sum >/dev/null 2>&1; then fi echo "[sha256sum-check] OK" +# Seed the inline-shell default into the GLOBAL mise config, the only place mise still honours it. +# Current mise (see min_version in config.toml) strips every `global_only` setting from a non-global config +# file -- strip_local_only_settings in its src/config/settings.rs -- and warns +# mise WARN windows_default_inline_shell_args in non-global config /.mise/config.toml is ignored +# for security reasons +# so this repo can no longer set it in .mise/config.toml. Without it, mise falls back to its `cmd /c` default +# for every single-line task `run` on Windows -- 78 of them -- where cargo/pnpm/wasm-pack are unresolvable +# (the cmd-subprocess PATH dropout). Multi-line bodies are unaffected: our semgrep +# multiline-task-run-needs-bash-shell rule already forces an explicit `shell` on those, which is also why +# THIS task still runs under bash and can bootstrap the setting. +# +# `mise settings set` writes the user's global config, so this covers workstations as well as CI -- an env var +# exported by the workflows would only ever fix CI. Idempotent, and scoped to this one Windows-only key. +mise settings set windows_default_inline_shell_args "bash -euo pipefail -c" + mise install conda:m2-git conda:m2-gnupg github:mstorsjo/llvm-mingw conda:m2-make rust # rustup's default-host still resolves bare channel names to msvc on Windows. diff --git a/Dockerfile b/Dockerfile index d08ec773..fb775f78 100644 --- a/Dockerfile +++ b/Dockerfile @@ -170,8 +170,9 @@ EOF # In a non-interactive build that's the equivalent of the shell integration -- # every `mise` / `mise run` below then resolves the workspace tools. # Pin the mise version: the config templates need Tera v2 (mise >= 2026.7.1), and mise.run honours MISE_VERSION. +# Keep this in lockstep with min_version in .mise/config.toml. # skipcq: DOK-DL4006 -RUN curl -fsSL https://mise.run | MISE_VERSION=v2026.7.1 sh +RUN curl -fsSL https://mise.run | MISE_VERSION=v2026.8.0 sh # Declare HOME explicitly rather than depending on the base image's ENV. # ubuntu/debian/fedora all set HOME=/root for the root user, but pinning it # here means the PATH expansion below doesn't silently break against a future diff --git a/README.md b/README.md index 7f94cf5b..be2af061 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ device or your own network, never sent to an external cloud service. ## mise -Please install [`mise`](https://mise.jdx.dev/) (2026.7.1 or later), including the shell integration. It is needed +Please install [`mise`](https://mise.jdx.dev/) (2026.8.0 or later), including the shell integration. It is needed for all use of this repository. The `mise` configuration lives under [`.mise/`](.mise/): the always-loaded [`.mise/config.toml`](.mise/config.toml) diff --git a/config/ast-grep/rules/doc-summary-ends-with-period.yaml b/config/ast-grep/rules/doc-summary-ends-with-period.yaml index 912c5578..c9673deb 100644 --- a/config/ast-grep/rules/doc-summary-ends-with-period.yaml +++ b/config/ast-grep/rules/doc-summary-ends-with-period.yaml @@ -26,11 +26,13 @@ rule: files: - libs/edge-toolkit/src/config.rs - libs/edge-toolkit/tests/no_mise.rs + - libs/edge-toolkit/tests/npm_mod.rs - libs/test-helpers/src/lib.rs - libs/ws-runner-common/tests/config.rs - services/modules/tests/api_modules.rs - services/modules/tests/configure_missing_root.rs - services/modules/tests/get_module_file.rs + - services/modules/tests/symlinks.rs - services/storage/src/lib.rs - services/storage/src/routes.rs - services/storage/src/tty_image.rs diff --git a/config/conftest/policy/mise/mise.rego b/config/conftest/policy/mise/mise.rego index 4c5174b8..94a065a6 100644 --- a/config/conftest/policy/mise/mise.rego +++ b/config/conftest/policy/mise/mise.rego @@ -148,12 +148,6 @@ allowed_os_scoped_tool := { # action-validator (aqua) has no Windows build, so it is os-scoped off Windows. # config.windows.toml installs cargo:action-validator there instead (cargo-quickinstall msvc prebuilt). "action-validator", - # aube is the mandatory npm: backend, split across three backends because no single one covers every platform. - # aqua for linux + macos/arm64, cargo:aube for macos/x64 (no aqua asset), http:aube for Windows (aqua installs - # but its shim cannot resolve the extracted exe). Between the three entries every platform is covered. - "aube", - "cargo:aube", - "http:aube", "http:chromedriver", "pipx", "pipx:torch", diff --git a/config/taplo/mise-cargo-backend-allowlist.schema.json b/config/taplo/mise-cargo-backend-allowlist.schema.json index 3504874a..ab1ac332 100644 --- a/config/taplo/mise-cargo-backend-allowlist.schema.json +++ b/config/taplo/mise-cargo-backend-allowlist.schema.json @@ -12,7 +12,6 @@ { "enum": [ "cargo:action-validator", - "cargo:aube", "cargo:cargo-expand", "cargo:dart-typegen", "cargo:findutils", diff --git a/libs/edge-toolkit/src/config.rs b/libs/edge-toolkit/src/config.rs index 69603b9e..09bde9a6 100644 --- a/libs/edge-toolkit/src/config.rs +++ b/libs/edge-toolkit/src/config.rs @@ -318,9 +318,13 @@ pub fn mise_npm_package_path(package: &str) -> Option { /// ``. Supports the mise npm backends: /// /// 1. Classical npm/mise (Unix): `/lib/node_modules/` -/// 2. npm on Windows: `/node_modules/` (no `lib/` segment -- -/// npm's global prefix layout differs by platform) -/// 3. aube backend: `/global-aube//node_modules/.aube/node_modules/` +/// 2. `/node_modules/`, covering both npm on Windows (whose global prefix omits the `lib/` +/// segment) and current mise, whose embedded aube writes that entry as a symlink to +/// `node_modules/.mise/@/node_modules/` -- itself a symlink into the shared +/// aube virtual store. The `is_dir` probe below follows both hops, so one check covers the whole chain. +/// 3. Legacy standalone-aube backend: `/global-aube//node_modules/.aube/node_modules/`. +/// Superseded by the `node_modules/.mise/` farm in 2, and kept only so trees an older mise already +/// installed keep resolving; nothing produces this layout any more. /// /// Tried in that order; returns `None` if no layout has the package. #[must_use] diff --git a/libs/edge-toolkit/tests/npm_mod.rs b/libs/edge-toolkit/tests/npm_mod.rs index 406ab00e..e2e2fd68 100644 --- a/libs/edge-toolkit/tests/npm_mod.rs +++ b/libs/edge-toolkit/tests/npm_mod.rs @@ -56,6 +56,50 @@ fn resolves_aube_backend_layout() { assert_eq!(found.as_deref(), Some(modules.as_path())); } +/// Current mise reaches the package through two symlink hops, and the resolver must follow both. +/// +/// `/node_modules/` is a *relative* symlink to +/// `.mise/@/node_modules/`, and `.mise/@` is itself a symlink into the shared +/// aube virtual store. The resolver's single `is_dir` probe is what traverses the chain, so a regression to a +/// non-following check (`symlink_metadata`, `read_dir` entry filtering) would resolve nothing and every +/// `/modules//...` request would 404. The real layouts these hops mirror are recorded on +/// `find_npm_modules_path_in`. +/// +/// Unix-only because it has to *create* symlinks: Windows symlink creation needs Developer Mode or elevation, +/// which isn't dependable on a runner. `services/modules/tests/symlinks.rs` is `#![cfg(unix)]` for the same +/// reason. The resolver code under test is platform-independent. +#[cfg(unix)] +#[test] +fn resolves_mise_two_hop_symlink_layout() { + use std::os::unix::fs::symlink; + + let install = TempDir::new().unwrap(); + let store = TempDir::new().unwrap(); + let modules = install.path().join("node_modules"); + let farm = modules.join(".mise"); + fs::create_dir_all(&farm).unwrap(); + + // Hop 2 target: the virtual-store entry holding the real package. + let store_entry = store.path().join("onnxruntime-web@1.27.0-ac0bad64e3fabd3b"); + let real_pkg = store_entry.join("node_modules/onnxruntime-web"); + fs::create_dir_all(&real_pkg).unwrap(); + fs::write(real_pkg.join("package.json"), "{}").unwrap(); + + symlink(&store_entry, farm.join("onnxruntime-web@1.27.0")).unwrap(); + symlink( + std::path::Path::new(".mise/onnxruntime-web@1.27.0/node_modules/onnxruntime-web"), + modules.join("onnxruntime-web"), + ) + .unwrap(); + + let found = find_npm_modules_path_in(install.path(), "onnxruntime-web"); + assert_eq!(found.as_deref(), Some(modules.as_path())); + assert!( + found.unwrap().join("onnxruntime-web/package.json").is_file(), + "package.json must be readable through both symlink hops", + ); +} + #[test] fn resolves_scoped_package_layout() { // A scoped npm package (e.g. @mediapipe/tasks-vision) lives at diff --git a/services/modules/tests/symlinks.rs b/services/modules/tests/symlinks.rs index e536989a..2ed1fa5f 100644 --- a/services/modules/tests/symlinks.rs +++ b/services/modules/tests/symlinks.rs @@ -1,13 +1,15 @@ //! Symlink-aware module discovery + serving. //! -//! mise's `aube` npm backend lays out `node_modules/.aube/node_modules/` -//! as *symlinks* to a content-addressed store. The modules service has to -//! follow those symlinks both when scanning (`list_modules`) and when -//! actix-files serves files out of the discovered package dir. The tests -//! here cover the full chain on a tempdir fixture that mirrors the aube -//! layout. Regressing either half manifests as a 404 on -//! `/modules/onnxruntime-web/dist/ort.min.js` (and similar) which is what -//! these tests pin down. +//! mise's embedded aube npm backend reaches a package through *two* symlink +//! hops: `node_modules/` points at +//! `node_modules/.mise/@/node_modules/`, and that +//! `.mise/@` entry points in turn at the content-addressed aube +//! virtual store. The modules service has to follow the whole chain both when +//! scanning (`list_modules`) and when actix-files serves files out of the +//! discovered package dir. The fixture below mirrors both hops so a regression +//! in multi-hop resolution is caught, not just single-hop. Regressing either +//! half manifests as a 404 on `/modules/onnxruntime-web/dist/ort.min.js` (and +//! similar) which is what these tests pin down. #![cfg(test)] #![cfg(unix)] @@ -28,36 +30,57 @@ use tempfile::TempDir; const ORT_BUNDLE: &[u8] = b"// pretend ort.min.js bundle"; -/// Build a fixture with `/onnxruntime-web-real/` as the real -/// package, and `/onnxruntime-web` as a symlink pointing at it. -/// `` is what we hand to `ModulesConfig::paths`. Returns both -/// tempdirs (kept alive by the caller) plus the modules config. -fn aube_layout_fixture() -> (TempDir, TempDir, ModulesConfig) { +/// Build a fixture mirroring mise's two-hop npm layout. +/// +/// `/@-/` holds the real package, standing in for the aube virtual store. +/// `` plays the install's `node_modules` and is what we hand to `ModulesConfig::paths`; within it +/// `` symlinks to `.mise/@/node_modules/`, and `.mise/@` symlinks to +/// the store entry -- so resolving a package traverses both hops exactly as it does against a real install. +/// Returns both tempdirs (kept alive by the caller) plus the modules config. +fn mise_layout_fixture() -> (TempDir, TempDir, ModulesConfig) { let store = TempDir::new().unwrap(); - let real_pkg = store.path().join("onnxruntime-web-real"); - fs::create_dir_all(real_pkg.join("dist")).unwrap(); + let scan = TempDir::new().unwrap(); + let farm = scan.path().join(".mise"); + fs::create_dir_all(&farm).unwrap(); + + // Second hop target: the store entry, named as aube names its virtual-store dirs. + let real_pkg = store.path().join("onnxruntime-web@1.27.0-ac0bad64e3fabd3b"); + fs::create_dir_all(real_pkg.join("node_modules/onnxruntime-web/dist")).unwrap(); + let pkg_inner = real_pkg.join("node_modules/onnxruntime-web"); fs::write( - real_pkg.join("package.json"), - r#"{"name":"onnxruntime-web","version":"1.26.0"}"#, + pkg_inner.join("package.json"), + r#"{"name":"onnxruntime-web","version":"1.27.0"}"#, ) .unwrap(); - fs::write(real_pkg.join("dist/ort.min.js"), ORT_BUNDLE).unwrap(); + fs::write(pkg_inner.join("dist/ort.min.js"), ORT_BUNDLE).unwrap(); - // Also drop an et-ws-server-static stub next to it -- `configure` - // panics if the configured `root` module can't be found, so we - // satisfy that requirement here too. - let static_root = store.path().join("et-ws-server-static"); - fs::create_dir_all(&static_root).unwrap(); + // Also drop an et-ws-server-static stub -- `configure` panics if the + // configured `root` module can't be found, so we satisfy that here too. + let static_store = store.path().join("et-ws-server-static@0.0.0-0000000000000000"); + let static_inner = static_store.join("node_modules/et-ws-server-static"); + fs::create_dir_all(&static_inner).unwrap(); fs::write( - static_root.join("package.json"), + static_inner.join("package.json"), r#"{"name":"et-ws-server-static","version":"0.0.0"}"#, ) .unwrap(); - fs::write(static_root.join("index.html"), b"").unwrap(); + fs::write(static_inner.join("index.html"), b"").unwrap(); - let scan = TempDir::new().unwrap(); - symlink(&real_pkg, scan.path().join("onnxruntime-web")).unwrap(); - symlink(&static_root, scan.path().join("et-ws-server-static")).unwrap(); + // Hop 2: `.mise/@` -> the store entry. + symlink(&real_pkg, farm.join("onnxruntime-web@1.27.0")).unwrap(); + symlink(&static_store, farm.join("et-ws-server-static@0.0.0")).unwrap(); + + // Hop 1: `` -> `.mise/@/node_modules/`, relative just as mise writes it. + symlink( + PathBuf::from(".mise/onnxruntime-web@1.27.0/node_modules/onnxruntime-web"), + scan.path().join("onnxruntime-web"), + ) + .unwrap(); + symlink( + PathBuf::from(".mise/et-ws-server-static@0.0.0/node_modules/et-ws-server-static"), + scan.path().join("et-ws-server-static"), + ) + .unwrap(); let config = ModulesConfig::new(vec![scan.path().to_path_buf()], "et-ws-server-static".to_string()); (store, scan, config) @@ -68,7 +91,7 @@ fn aube_layout_fixture() -> (TempDir, TempDir, ModulesConfig) { // synchronous; the async wrapper is harmless. #[actix_rt::test] async fn list_modules_follows_symlinks_to_package_dirs() { - let (_store, _scan, config) = aube_layout_fixture(); + let (_store, _scan, config) = mise_layout_fixture(); let found: Vec<(String, PathBuf)> = list_modules(&config); @@ -89,7 +112,7 @@ async fn list_modules_follows_symlinks_to_package_dirs() { #[actix_rt::test] async fn lists_symlinked_module_in_modules_api() { - let (_store, _scan, config) = aube_layout_fixture(); + let (_store, _scan, config) = mise_layout_fixture(); let app = test::init_service( App::new() .app_data(web::Data::new(AgentRegistry::<()>::default())) @@ -109,7 +132,7 @@ async fn lists_symlinked_module_in_modules_api() { #[actix_rt::test] async fn serves_file_under_symlinked_module() { - let (_store, _scan, config) = aube_layout_fixture(); + let (_store, _scan, config) = mise_layout_fixture(); let app = test::init_service( App::new() .app_data(web::Data::new(AgentRegistry::<()>::default())) @@ -135,7 +158,7 @@ async fn serves_file_under_symlinked_module() { #[actix_rt::test] async fn returns_404_for_missing_file_under_symlinked_module() { - let (_store, _scan, config) = aube_layout_fixture(); + let (_store, _scan, config) = mise_layout_fixture(); let app = test::init_service( App::new() .app_data(web::Data::new(AgentRegistry::<()>::default())) From 103d76f1355d771cfd88d53f9f29b14380a48b09 Mon Sep 17 00:00:00 2001 From: John Vandenberg Date: Mon, 3 Aug 2026 18:09:16 +0800 Subject: [PATCH 7/9] fixes --- .mise/config.coverage.toml | 37 +++++++++++++++++++++++++++++++++---- .mise/config.toml | 9 ++++++++- .mise/config.windows.toml | 19 ++++++++++++++----- 3 files changed, 55 insertions(+), 10 deletions(-) diff --git a/.mise/config.coverage.toml b/.mise/config.coverage.toml index 6b6fde76..43e2d9b3 100644 --- a/.mise/config.coverage.toml +++ b/.mise/config.coverage.toml @@ -291,12 +291,29 @@ skip { next } AWK "$llbin/llvm-profdata" merge -sparse -o "$covdir/agent.profdata" "$covdir"/wasm-agent-*.profraw objs=() -for ll in target/wasm32-unknown-unknown/debug/deps/*.ll; do +# Collect the instrumented .ll from whichever build-dir layout the toolchain produced. +# A plain `deps/*.ll` glob broke when nightly made `-Z build-dir-new-layout` the default: per-crate +# intermediates moved to `/build///out/`, the glob matched nothing, and the unexpanded +# pattern reached goawk as a literal filename -- `file "target/wasm32-unknown-unknown/debug/deps/*.ll" not +# found` on commit 12bfe419 at https://github.com/edge-toolkit/core/actions/runs/30797697402/job/91634929015. +# `find` also avoids the silent-nullglob trap the literal-pattern failure exposed. +lls="$(find target/wasm32-unknown-unknown/debug -path '*/build/*' -name '*.ll' 2>/dev/null)" +if [ -z "$lls" ]; then + lls="$(find target/wasm32-unknown-unknown/debug/deps -name '*.ll' 2>/dev/null)" +fi +if [ -z "$lls" ]; then + echo "wasm-agent-cov: no instrumented .ll under target/wasm32-unknown-unknown/debug" >&2 + exit 1 +fi +while IFS= read -r ll; do + [ -n "$ll" ] || continue name="$(coreutils basename "$ll" .ll)" goawk -f "$gut" "$ll" > "$covdir/$name.g.ll" "$llbin/llc" -filetype=obj -mtriple=x86_64-unknown-linux-gnu -o "$covdir/$name.o" "$covdir/$name.g.ll" objs+=("-object" "$covdir/$name.o") -done +done < "$covdir/all.lcov" keep="$covdir/keep.awk" coreutils cat > "$keep" <<'AWK' @@ -367,12 +384,24 @@ skip { next } AWK "$llbin/llvm-profdata" merge -sparse -o "$covdir/pic-viewer.profdata" "$covdir"/pic-viewer-*.profraw objs=() -for ll in target/wasm32-unknown-unknown/debug/deps/*.ll; do +# Same build-dir-layout handling as wasm-agent-cov above; this task shares the failure mode. +lls="$(find target/wasm32-unknown-unknown/debug -path '*/build/*' -name '*.ll' 2>/dev/null)" +if [ -z "$lls" ]; then + lls="$(find target/wasm32-unknown-unknown/debug/deps -name '*.ll' 2>/dev/null)" +fi +if [ -z "$lls" ]; then + echo "pic-viewer-cov: no instrumented .ll under target/wasm32-unknown-unknown/debug" >&2 + exit 1 +fi +while IFS= read -r ll; do + [ -n "$ll" ] || continue name="$(coreutils basename "$ll" .ll)" goawk -f "$gut" "$ll" > "$covdir/$name.g.ll" "$llbin/llc" -filetype=obj -mtriple=x86_64-unknown-linux-gnu -o "$covdir/$name.o" "$covdir/$name.g.ll" objs+=("-object" "$covdir/$name.o") -done +done < "$covdir/all.lcov" keep="$covdir/keep.awk" coreutils cat > "$keep" <<'AWK' diff --git a/.mise/config.toml b/.mise/config.toml index 23005276..29ab7f8e 100644 --- a/.mise/config.toml +++ b/.mise/config.toml @@ -105,7 +105,14 @@ ripgrep = "latest" # 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" -"go:github.com/noperator/jqfmt/cmd/jqfmt" = "latest" +# Version-pinned rather than `latest` so installing it needs no Go-proxy version lookup. +# Resolving `latest` queries the proxy for the tag list, and that request is bound by +# fetch_remote_versions_timeout (20s), which the Windows lanes' third-party egress overran: +# mise ERROR Failed to install go:github.com/noperator/jqfmt/cmd/jqfmt@latest: timed out after 20.00s +# on commit 12bfe419 at https://github.com/edge-toolkit/core/actions/runs/30797697389/job/91634929057. +# Pinning removes the lookup entirely instead of widening the timeout. upstream has exactly one release, so +# this is also what `latest` already resolved to -- revisit only when noperator/jqfmt tags a second version. +"go:github.com/noperator/jqfmt/cmd/jqfmt" = "0.1.0" hadolint = "latest" ls-lint = "latest" lychee = "latest" diff --git a/.mise/config.windows.toml b/.mise/config.windows.toml index a9109e62..5d0a2c40 100644 --- a/.mise/config.windows.toml +++ b/.mise/config.windows.toml @@ -142,10 +142,19 @@ maven_bin = '{{ vars.mise_installs }}\maven\3.9.16\apache-maven-3.9.16\bin' # conda:libclang's binary dir, for the LIBCLANG_PATH below. # Pinned to the [tools] version above -- bump both together (conftest's version_drift enforces). win_libclang = '{{ vars.mise_installs }}\conda-libclang\22.1.8\Library\bin' -# npm:oxlint-tsgolint's package dir inside its mise install, for the OXLINT_TSGOLINT_PATH entry below. -# npm global installs land at `\node_modules\` on Windows (vs `lib/node_modules` on Unix). Pinned -# to the config.js.toml [tools] version -- bump both together (conftest's version_drift enforces). -tsgolint_pkg = '{{ vars.mise_installs }}\npm-oxlint-tsgolint\0.24.0\node_modules\oxlint-tsgolint' +# The flat dependency farm inside npm:oxlint-tsgolint's mise install, for the OXLINT_TSGOLINT_PATH entry below. +# mise's embedded aube hoists a tool's dependencies rather than nesting them: `\node_modules\` +# is a symlink to `.mise\@\node_modules\`, and that package dir holds only its own files +# (bin, package.json, ...) with NO nested `node_modules`. Its `@oxlint-tsgolint\` platform dep lives in +# the flat farm instead, so the pre-aube +# `...\node_modules\oxlint-tsgolint\node_modules\@oxlint-tsgolint\` path this used to build no longer +# resolves and oxlint aborted with +# Failed to find tsgolint executable: OXLINT_TSGOLINT_PATH points to '...' which does not exist +# on commit 12bfe419 at https://github.com/edge-toolkit/core/actions/runs/30797697407/job/91634929181. +# The flat farm is preferred over the equivalent `.mise\oxlint-tsgolint@\node_modules\...` path +# because it carries no second copy of the version to keep in sync. Pinned to the config.js.toml [tools] +# version -- bump both together (conftest's version_drift enforces). +tsgolint_pkg = '{{ vars.mise_installs }}\npm-oxlint-tsgolint\0.24.0\node_modules\.mise\node_modules' # wasm-pack's wasm-opt (binaryen) can't execute on Nano Server (os error 3). # So the module + wasm-agent builds pass --no-opt; they reference {{ vars.no_opt }}. no_opt = "--no-opt" @@ -185,7 +194,7 @@ MISE_BASH_PATH = "{{ vars.winsh }}" # per-platform exe nested in the npm package sidesteps node and cmd.exe entirely. Windows-only (this file) # both because only cmd.exe has the limit and because oxlint hard-fails on a set-but-invalid value, which an # off-Windows `{% if %}` empty-string render would be. Inert when js isn't in MISE_ENV; just an unused path. -OXLINT_TSGOLINT_PATH = '{{ vars.tsgolint_pkg }}\node_modules\@oxlint-tsgolint\win32-x64\tsgolint.exe' +OXLINT_TSGOLINT_PATH = '{{ vars.tsgolint_pkg }}\@oxlint-tsgolint\win32-x64\tsgolint.exe' # Build for the LLVM mingw target. # gnullvm, not gnu: llvm-mingw ships compiler-rt + libunwind, not the GCC runtime (libgcc/libgcc_eh) the gnu # target's link line demands. The linker is llvm-mingw's clang and the raw-dylib import tool is its From 5a1467d1fac2703021f47ba1d5850ba4e63a8e8f Mon Sep 17 00:00:00 2001 From: John Vandenberg Date: Tue, 4 Aug 2026 10:47:39 +0800 Subject: [PATCH 8/9] fix docker disk space --- .../free-disk-space-windows/action.yaml | 70 ++++++++++++++++++- .github/workflows/docker-linux.yaml | 6 +- .github/workflows/docker-windows.yaml | 28 ++++---- .../pyeye1/tests/test_run_workflow.py | 27 +++++++ .../pyface1/tests/test_face_detection.py | 49 +++++++++++++ 5 files changed, 164 insertions(+), 16 deletions(-) diff --git a/.github/actions/free-disk-space-windows/action.yaml b/.github/actions/free-disk-space-windows/action.yaml index b48e292e..df6c1d11 100644 --- a/.github/actions/free-disk-space-windows/action.yaml +++ b/.github/actions/free-disk-space-windows/action.yaml @@ -7,7 +7,9 @@ description: >- jlumbroso/free-disk-space on Linux, which doesn't have a Windows path. Logs disk-free before + after so the reclaimed space is visible. - Enabling all makes approximately 10Gb free. + The default-on removals free roughly 17 GB. The two opt-in ones + (visual-studio, windows-kits) add up to another ~23 GB, but only a + caller that builds entirely inside a container may enable them. Caller is responsible for the `if: runner.os == 'Windows'` guard -- every step here uses Git Bash with `C:\` mounted at `/c`; running this @@ -42,6 +44,36 @@ inputs: Strawberry Perl) -- ~1 GB combined. required: false default: "true" + hostedtoolcache-unused-runtimes: + description: >- + Remove the `hostedtoolcache` Ruby, Go and CodeQL trees (~4 GB + combined). Nothing here uses Ruby; Go comes from mise's own `go` + pin rather than setup-go's copy; CodeQL runs only in codeql.yaml, + which does not call this action. + required: false + default: "true" + cloud-clis: + description: >- + Remove the preinstalled Azure, AWS and Google Cloud SDK trees + (~3 GB combined). No lane in this repo talks to a cloud provider. + required: false + default: "true" + visual-studio: + description: >- + Remove the preinstalled Visual Studio tree (~10-20 GB, the single + largest reclaim available). OFF by default because it is only safe + when the caller builds entirely inside a container: test.yaml's + `override (msvc)` lane needs MSVC's link.exe on the runner itself. + docker-windows.yaml opts in. + required: false + default: "false" + windows-kits: + description: >- + Remove the preinstalled Windows SDKs under `Windows Kits` (~3 GB). + OFF by default for the same reason as visual-studio -- a native + msvc link needs these headers and import libraries. + required: false + default: "false" keep-image: description: >- Docker image (repo:tag) to preserve during the docker prune. @@ -104,6 +136,42 @@ runs: [ -e "$p" ] && { echo "removing $p"; rm -rf -- "$p"; } || true done + - name: Remove hostedtoolcache unused runtimes + if: inputs.hostedtoolcache-unused-runtimes == 'true' + shell: bash --noprofile --norc -euo pipefail {0} + run: | + for p in /c/hostedtoolcache/{Ruby,go,CodeQL}; do + [ -e "$p" ] && { echo "removing $p"; rm -rf -- "$p"; } || true + done + + - name: Remove cloud CLIs + if: inputs.cloud-clis == 'true' + shell: bash --noprofile --norc -euo pipefail {0} + run: | + pf="/c/Program Files" + pf86="/c/Program Files (x86)" + paths=("$pf/Microsoft SDKs/Azure" "$pf86/Microsoft SDKs/Azure") + paths+=("$pf/Amazon" "$pf86/AWSCLIV2" "$pf86/Google/Cloud SDK") + for p in "${paths[@]}"; do + [ -e "$p" ] && { echo "removing $p"; rm -rf -- "$p"; } || true + done + + - name: Remove Visual Studio + if: inputs.visual-studio == 'true' + shell: bash --noprofile --norc -euo pipefail {0} + run: | + for p in "/c/Program Files/Microsoft Visual Studio" "/c/Program Files (x86)/Microsoft Visual Studio"; do + [ -e "$p" ] && { echo "removing $p"; rm -rf -- "$p"; } || true + done + + - name: Remove Windows Kits + if: inputs.windows-kits == 'true' + shell: bash --noprofile --norc -euo pipefail {0} + run: | + for p in "/c/Program Files (x86)/Windows Kits" "/c/Program Files/Windows Kits"; do + [ -e "$p" ] && { echo "removing $p"; rm -rf -- "$p"; } || true + done + - name: Prune docker images and builder cache shell: bash --noprofile --norc -euo pipefail {0} env: diff --git a/.github/workflows/docker-linux.yaml b/.github/workflows/docker-linux.yaml index 1b386fcc..1b3495e9 100644 --- a/.github/workflows/docker-linux.yaml +++ b/.github/workflows/docker-linux.yaml @@ -146,8 +146,12 @@ jobs: docker run --rm et-test sh -c "find / -xdev -type f -size +50M -exec du -h {} + | sort -h | tail -50" echo "::endgroup::" + # CARGO_INCREMENTAL=0 is passed into the container rather than set on the job. + # The compile happens inside `docker run`, so a job-level env would never reach cargo. Incremental caches + # buy nothing on a single-shot CI compile and cost disk, which these lanes are tight on -- amazonlinux and + # opensuse have both died mid-`cargo-test` with the runner losing its volume. - name: Run cargo-test - run: docker run --rm et-test mise run cargo-test + run: docker run --rm -e CARGO_INCREMENTAL=0 et-test mise run cargo-test - name: Run mise check run: docker run --rm et-check diff --git a/.github/workflows/docker-windows.yaml b/.github/workflows/docker-windows.yaml index 3ba77928..5f5c7ac2 100644 --- a/.github/workflows/docker-windows.yaml +++ b/.github/workflows/docker-windows.yaml @@ -70,10 +70,16 @@ jobs: sc query docker | grep -q RUNNING || net start docker docker version + # Opt into the aggressive reclaims: this lane builds entirely inside the container. + # Visual Studio and the Windows Kits are only touched by a NATIVE msvc link, which this workflow never + # does -- it only runs `docker build` and `docker run`. check.yaml and test.yaml share this action and + # leave both off, because test.yaml's `override (msvc)` lane does link natively against them. - name: Free disk space on Windows runner uses: ./.github/actions/free-disk-space-windows with: keep-image: mcr.microsoft.com/windows/${{ matrix.base }}:${{ matrix.windows_version }} + visual-studio: "true" + windows-kits: "true" - name: Prepare mise and Github token for the build context run: | @@ -132,31 +138,25 @@ jobs: docker run --rm "$IMAGE_TAG" cmd /c "mise exec -- rustpython -c \"print('et-rp ok')\"" echo "::endgroup::" - # The build target depends on the base image. - # nanoserver stops at `precompile` (no python/dotnet/test-runtime; the test stage doesn't exist there), - # servercore goes all the way through `test` so the next two steps can `docker run` the suite. Both targets - # compose with the precompile-stage layer cache from the build-minimal step above. - - name: Build stage precompile (nanoserver) / test (servercore) + - name: Build stage test env: DOCKERFILE: ${{ steps.dockerfile.outputs.dockerfile }} run: | - if [ "${{ matrix.base }}" = "servercore" ]; then - target="test" - tag="-t et-windows-test" - else - target="precompile" - tag="" - fi args="-f $DOCKERFILE --build-arg MISE_ENV" if [ "${{ matrix.runner }}" != "windows-2022" ]; then args="$args --build-arg WINDOWS_VERSION=${{ matrix.windows_version }}" fi - args="$args --target $target $tag" + args="$args --target test -t et-windows-test" # $args is a word-split flag list by design; do not quote it. # shellcheck disable=SC2086 docker build $args . # Run the Rust tests, excluding et-ws-web-runner on the gnullvm target. + # CARGO_INCREMENTAL=0 has to be passed into the container, not just set on the job: this compiles from + # scratch inside `docker run` (Dockerfile.windows's precompile stage deletes `target` after building the + # modules), so the incremental caches land on the runner's C: drive. They are pure waste on a + # single-shot CI compile and this lane has no disk to spare -- servercore ran out mid-link with + # `There is not enough space on the disk. (os error 112)` even after the disk-free step left 43 GB. - name: Run cargo-test if: matrix.base == 'servercore' - run: docker run --rm et-windows-test mise run cargo-test + run: docker run --rm -e CARGO_INCREMENTAL=0 et-windows-test mise run cargo-test diff --git a/services/ws-modules/pyeye1/tests/test_run_workflow.py b/services/ws-modules/pyeye1/tests/test_run_workflow.py index 79ee2b9e..a3a6b5c4 100644 --- a/services/ws-modules/pyeye1/tests/test_run_workflow.py +++ b/services/ws-modules/pyeye1/tests/test_run_workflow.py @@ -162,6 +162,14 @@ async def save_eye_capture(self) -> None: raise RuntimeError("upload failed") +class FailingInferencePlatform(FakePlatform): + """Inference itself raises, exercising `sample_loop`'s broad handler rather than the capture one.""" + + async def infer(self) -> str: + self.infer_calls += 1 + raise RuntimeError("inference exploded") + + class RunWorkflowTests(unittest.IsolatedAsyncioTestCase): async def test_happy_path_drives_the_full_workflow(self) -> None: platform = FakePlatform(stop_after=5) @@ -261,6 +269,25 @@ async def test_eye_capture_fires_on_first_rising_edge_after_consent_granted_mid_ await run(platform) self.assertEqual(platform.capture_calls, 1) + async def test_inference_failure_is_reported_and_the_sample_loop_keeps_going(self) -> None: + # `sample_loop` wraps each iteration in a broad handler precisely so one bad frame cannot end the + # session. Raising from `infer()` drives that path: the loop must surface the error via status + log + # and still run every remaining iteration rather than propagating out of `run`. + platform = FailingInferencePlatform(stop_after=3) + with patch.object(eye_detection, "ANALYSIS_INTERVAL_MS", 0.0): + await run(platform) + + self.assertEqual(platform.infer_calls, 3, "every iteration must still run after a failing one") + self.assertTrue( + any("inference error" in line and "inference exploded" in line for line in platform.logs), + f"expected the raised error logged, got {platform.logs}", + ) + self.assertTrue( + any("inference error" in status for status in platform.statuses), + f"expected the error surfaced as status, got {platform.statuses}", + ) + self.assertTrue(platform.cleaned, "cleanup must still run after inference failures") + async def test_eye_capture_failure_is_logged_and_does_not_abort_the_run(self) -> None: platform = FailingCapturePlatform(stop_after=5) platform.consent = True diff --git a/services/ws-modules/pyface1/tests/test_face_detection.py b/services/ws-modules/pyface1/tests/test_face_detection.py index 058d58cd..c0ed3bf0 100644 --- a/services/ws-modules/pyface1/tests/test_face_detection.py +++ b/services/ws-modules/pyface1/tests/test_face_detection.py @@ -1,6 +1,8 @@ import math import unittest +import unittest.mock +from pyface1 import face_detection from pyface1.face_detection import ( FACE_INPUT_HEIGHT, FACE_INPUT_WIDTH, @@ -11,6 +13,7 @@ decode_outputs, output_values, preprocess_geometry, + run, softmax, ) @@ -108,5 +111,51 @@ def test_preprocess_geometry_preserves_source_aspect_ratio(self) -> None: self.assertEqual(tall["resized_height"], 608.0) +class InferenceFailureTests(unittest.IsolatedAsyncioTestCase): + """`run`'s broad handler must report a failing inference and keep the detection loop alive.""" + + async def test_inference_failure_is_reported_and_the_loop_keeps_going(self) -> None: + # The handler is broad by design so one bad frame cannot end a long-running session. Raising from + # infer_once drives it: every iteration must still run, with the error surfaced via log + status + # rather than propagating out of `run`. + iterations = 3 + calls = {"infer": 0} + logs: list[str] = [] + statuses: list[str] = [] + + async def infer_once(): + calls["infer"] += 1 + raise RuntimeError("onnx exploded") + + async def sleep_ms(_ms) -> None: + pass + + def should_stop() -> bool: + return calls["infer"] >= iterations + + with unittest.mock.patch.object(face_detection, "FACE_INFERENCE_INTERVAL_MS", 0.0): + await run( + "input", + ["out0"], + infer_once, + lambda _message: None, + lambda _results: None, + sleep_ms, + logs.append, + statuses.append, + should_stop, + ) + + self.assertEqual(calls["infer"], iterations, "every iteration must still run after a failing one") + self.assertTrue( + any("inference error" in line and "onnx exploded" in line for line in logs), + f"expected the raised error logged, got {logs}", + ) + self.assertTrue( + any("inference error" in status for status in statuses), + f"expected the error surfaced as status, got {statuses}", + ) + + if __name__ == "__main__": unittest.main() From 77cf4d001bb387cd37b48ce5283264ee2617146d Mon Sep 17 00:00:00 2001 From: John Vandenberg Date: Tue, 4 Aug 2026 16:57:23 +0800 Subject: [PATCH 9/9] try to fix opensuse --- .github/workflows/docker-linux.yaml | 33 +++++++++++++++++++++++------ 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/.github/workflows/docker-linux.yaml b/.github/workflows/docker-linux.yaml index 1b3495e9..ed8229dd 100644 --- a/.github/workflows/docker-linux.yaml +++ b/.github/workflows/docker-linux.yaml @@ -25,7 +25,11 @@ env: jobs: build: runs-on: ubuntu-latest - timeout-minutes: 120 + # Raised from 120 for the opensuse lane's CARGO_BUILD_JOBS=2 cap (see the cargo-test step). + # Capping concurrent jobs trades wall-clock for peak memory, and opensuse was already the slowest lane at + # 47m51s when green, so the old ceiling left too little margin. The other lanes finish in 39-44m and are + # unaffected; this only stops a slower-but-succeeding build from being cut off. + timeout-minutes: 150 strategy: fail-fast: false matrix: @@ -146,12 +150,29 @@ jobs: docker run --rm et-test sh -c "find / -xdev -type f -size +50M -exec du -h {} + | sort -h | tail -50" echo "::endgroup::" - # CARGO_INCREMENTAL=0 is passed into the container rather than set on the job. - # The compile happens inside `docker run`, so a job-level env would never reach cargo. Incremental caches - # buy nothing on a single-shot CI compile and cost disk, which these lanes are tight on -- amazonlinux and - # opensuse have both died mid-`cargo-test` with the runner losing its volume. + # Every cargo knob here is passed INTO the container, not set on the job. + # The compile happens inside `docker run`, so a job-level env would never reach cargo. + # + # CARGO_INCREMENTAL=0: incremental caches buy nothing on a single-shot CI compile and cost disk. + # + # CARGO_PROFILE_DEV_DEBUG=0: debug info is the bulk of what the linker reads and writes, so dropping it + # cuts peak linker memory and disk while making the build faster -- unlike a parallelism cap, it costs no + # wall-clock. The trade is shallower backtraces if a test panics on these lanes. Applied to every lane + # because two of them have already died mid-`cargo-test`: amazonlinux once, and opensuse on all three + # attempts of run 30872860977, each time with the log never published and the step frozen `in_progress` + # at ~1h13m against a 120m limit -- the shape of the runner being killed rather than a build error. + # + # CARGO_BUILD_JOBS=2 on opensuse only: it is the slowest lane even when green (47m51s vs 39-44m) and the + # only one still failing, so it links under the most memory pressure. Capping concurrent codegen/link jobs + # is the direct lever, kept off the other lanes because it does cost wall-clock -- which is why + # timeout-minutes went to 150 alongside it. - name: Run cargo-test - run: docker run --rm -e CARGO_INCREMENTAL=0 et-test mise run cargo-test + env: + LANE_ARGS: ${{ matrix.base == 'opensuse/leap:15.6' && '-e CARGO_BUILD_JOBS=2' || '' }} + run: | + # $LANE_ARGS is a word-split flag list by design; do not quote it. + # shellcheck disable=SC2086 + docker run --rm -e CARGO_INCREMENTAL=0 -e CARGO_PROFILE_DEV_DEBUG=0 $LANE_ARGS et-test mise run cargo-test - name: Run mise check run: docker run --rm et-check