diff --git a/.github/workflows/k3s.yaml b/.github/workflows/k3s.yaml index 27ff6ffd..bd3cd585 100644 --- a/.github/workflows/k3s.yaml +++ b/.github/workflows/k3s.yaml @@ -23,8 +23,14 @@ name: k3s - services/ws-web-runner/Dockerfile - services/ws-test-server/src/bin/verify-math1.rs - utilities/cli/src/deployment_types/k3s.rs + # The published job's generator and the deployment it runs, for the same reason. + - utilities/cli/src/deployment_types/mise.rs - verification/local/output/math1/Dockerfile - verification/local/output/math1/k3s.yaml + - verification/published/output/math1/mise.toml + # What the published-k3s job applies and builds, for the same reason as the local pair above. + - verification/published/output/math1/Dockerfile + - verification/published/output/math1/k3s.yaml workflow_dispatch: permissions: @@ -105,3 +111,99 @@ jobs: env: CARGO_NET_RETRY: "5" run: mise run k3s-verify + + # Whether what is on crates.io and GitHub Packages actually runs, which no other job asks. + # Every sibling check reads this working tree; this one installs the released hub, runner and module packages + # and runs the math1 exchange on them, so it is the only signal that a release is usable. + # + # Allowed to fail, deliberately. What it exercises is whatever the registries currently hold, so between a + # change landing here and the release that carries it this job reports the old artifacts -- correctly, and + # with nothing the branch can do about it. A red result is a question about outstanding releases, not a + # broken tree, so it must never gate a merge. + published: + runs-on: ubuntu-latest + continue-on-error: true + # `packages: read` is what the scenario's npm installs need. + # GitHub Packages rejects an unauthenticated read even for a public package, so the module packages are + # unreachable without it. + permissions: + contents: read + packages: read + # No image builds, unlike the job above: this installs released binaries rather than building any. + timeout-minutes: 60 + steps: + - name: Checkout + uses: actions/checkout@v7 + with: + fetch-depth: 1 + persist-credentials: false + + - name: Install mise + tools + uses: ./.github/actions/install-mise-tools + timeout-minutes: 60 + with: + github-token: ${{ github.token }} + + - name: Run the published math1 scenario + timeout-minutes: 30 + env: + CARGO_NET_RETRY: "5" + GITHUB_TOKEN: ${{ github.token }} + run: mise run published-verify + + # The same cluster check as `verify`, against released images rather than ones this branch built. + # `verify` proves the manifests describe a working deployment when every image comes from this tree. This + # proves the released images still satisfy them, which is a different question: the manifests move with the + # branch while the images move only when someone publishes, so the two drift apart silently in between. + # + # Allowed to fail for the same reason the mise job above is. What it pulls is whatever ghcr currently holds, + # so between a change landing here and the images being republished this job reports the old ones -- + # correctly, and with nothing the branch can do about it. Kept a separate job rather than folded into + # `published` so a red result says which half is stale: the crates or the images. + published-k3s: + runs-on: ubuntu-latest + continue-on-error: true + # `packages: read` is what pulling the released hub and runner images needs. + permissions: + contents: read + packages: read + # No hub or runner build, unlike `verify`, because both are pulled. + # Only the scenario image is built, since no release can carry a module set particular to one deployment. + timeout-minutes: 60 + steps: + - name: Checkout + uses: actions/checkout@v7 + with: + fetch-depth: 1 + persist-credentials: false + + - name: Install mise + tools + uses: ./.github/actions/install-mise-tools + timeout-minutes: 60 + with: + github-token: ${{ github.token }} + + # The scenario image stages each module's built `pkg/`, which is gitignored. + # A fresh checkout has neither, so the image build fails on its own COPY. Only this scenario's two + # modules are built, not the whole glob. + - name: Build the math1 modules + timeout-minutes: 30 + env: + GITHUB_TOKEN: ${{ github.token }} + run: | + mise run build-ws-math1-module + mise run build-ws-math1-sender-module + + # The hub comes from the registry as a named build context rather than being built. + # That is the whole point of this job, and a plain build of the scenario Dockerfile fails on `FROM hub`. + - name: Build the math1 scenario image on the released hub + run: | + dockerfile=verification/published/output/math1/Dockerfile + hub=docker-image://ghcr.io/edge-toolkit/core/et-ws-server:latest + docker build --build-context "hub=$hub" -t et-ws-server-math1:latest -f "$dockerfile" . + + - name: Apply the published manifests and verify the stored model + timeout-minutes: 20 + env: + CARGO_NET_RETRY: "5" + run: mise run k3s-verify published diff --git a/.mise/config.maint.toml b/.mise/config.maint.toml index 96fdbf52..0e69e417 100644 --- a/.mise/config.maint.toml +++ b/.mise/config.maint.toml @@ -841,12 +841,13 @@ description = "Publish one batch of five workspace crates to crates.io (dry run # numbers stay stable for a part-published release, and re-check with the same pairs afterwards. run = """ crates=( - et-path edge-toolkit et-test-helpers et-otlp et-test-otlp - et-web et-rest-client et-ws-runner-common et-ws-wasm-agent et-wasi-guest - et-ws-comm1 et-ws-data1 et-ws-math1 et-ws-math1-sender et-ws-wasi-comm1 - et-ws-wasi-data1 et-ws-wasi-math1 et-ws-wasi-math1-sender et-modules-service et-storage-service - et-websockify-service et-ws-service et-ws-test-server et-ws-pyo3-runner et-ws-server - et-ws-wasi-runner et-ws-web-runner et-cli et-onnx et-repo-check + et-org et-path edge-toolkit et-test-helpers et-otlp + et-test-otlp et-web et-rest-client et-ws-runner-common et-ws-wasm-agent + et-wasi-guest et-ws-comm1 et-ws-data1 et-ws-math1 et-ws-math1-sender + et-ws-wasi-comm1 et-ws-wasi-data1 et-ws-wasi-math1 et-ws-wasi-math1-sender et-modules-service + et-storage-service et-websockify-service et-ws-service et-ws-test-server et-ws-pyo3-runner + et-ws-server et-ws-wasi-runner et-ws-web-runner et-cli et-onnx + et-repo-check ) per_batch=5 # `set --` then `$#` counts the array without the `${` + `#` pair, which mise's Tera pass reads as a comment. diff --git a/.mise/config.toml b/.mise/config.toml index db929a84..8984b423 100644 --- a/.mise/config.toml +++ b/.mise/config.toml @@ -139,6 +139,11 @@ ripgrep = "latest" # Its 0.8.0 release has no asset, so it builds from # source via cargo: (same 0.8.0 pin, so every platform runs the same version). "cargo:findutils" = { version = "0.8.0", os = ["linux/arm64"] } +# agent-lint: the linter behind agent-lint-check, over this repository's Claude configuration. +# Scoped to the three targets upstream publishes a binary for -- macOS arm64 and Linux x86_64/aarch64, all +# three musl or native -- because there is no Windows or macOS x86_64 asset in the release to install. The +# task skips where the tool cannot be installed rather than failing on a missing binary. +"github:zhupanov/agent-lint" = { version = "4.0.3", os = ["linux", "macos/arm64"] } # goawk: the awk that tasks invoke as `goawk` in place of the host's awk. "github:benhoyt/goawk" = "latest" "github:caldempsey/parfit" = "latest" @@ -797,6 +802,7 @@ description = "Run all formatters: fmt:rust + any loaded guest fmt:" depends = [ "action-validator-check", "actionlint-check", + "agent-lint-check", "ast-grep-check", "branch-up-to-date-check", "cargo-check", @@ -860,6 +866,22 @@ description = "Apply all lint-fix passes: fix:rust + any loaded guest fix: description = "Validate GitHub Actions workflow + composite-action YAML" run = "action-validator .github/workflows/*.yaml .github/actions/*/action.yaml" +[tasks.agent-lint-check] +description = "Lint this repository's Claude configuration (CLAUDE.md, .claude/) with agent-lint" +# Skips where upstream ships no binary rather than failing on a missing one. +# That is the one case this repo treats a missing tool as expected: the `[tools]` entry is os-scoped to the +# targets the release actually carries, so on any other platform there is nothing to install and nothing this +# could have run. macOS x86_64 is the case the os scope alone cannot express, since it scopes by OS and arch +# while a task body is the only place the two can be read together on a host that does have mise. +run = """ +if [ "$(coreutils uname -s)" = "Darwin" ] && [ "$(coreutils uname -m)" != "arm64" ]; then + echo "agent-lint-check: upstream publishes no macOS x86_64 binary; nothing to check on this host" + exit 0 +fi +agent-lint . +""" +shell = "{{ vars.task_shell }}" + [tasks.actionlint-check] description = "Lint GitHub Actions workflows (actionlint): local-action input validation, expression syntax, shellcheck" # Lint every workflow with actionlint's native checks plus its shellcheck integration over each `run:` script. @@ -1421,6 +1443,94 @@ find verification -name k3s.yaml -print0 | """ shell = "{{ vars.task_shell }}" +# End-to-end check that a published deployment runs against released artifacts and nothing local. +# The sibling scenario checks all read this working tree: the mise and compose ones build the runners out of the +# workspace, and k3s-verify imports images the caller built. None of them can tell whether what was published is +# usable, which is a different question and one only a release can answer. This installs the hub, the runner and +# the module packages from crates.io and GitHub Packages, runs the math1 exchange on them, and asserts the twin +# stored the model the other deployments are held to. +# +# Expected to fail for a stretch after any change to a published crate, and that is not a defect in this task. +# What it runs is whatever the registries currently hold, so between a change landing here and the release that +# carries it, this reports the old artifacts -- correctly. It is therefore deliberately absent from the `check` +# aggregate and marked continue-on-error in CI: a red result asks "is a release outstanding?", not "is the tree +# broken?". Run it after publishing, not before. +# +# Needs a GITHUB_TOKEN because GitHub Packages requires auth even to read a public package, and Docker for the +# collector the scenario starts alongside the hub. +[tasks.published-verify] +description = "Run the published math1 scenario against released crates and module packages, end to end" +run = """ +scenario=math1 +out="verification/published/output/$scenario" +if [ -z "${GITHUB_TOKEN:-}" ]; then + echo "published-verify: GITHUB_TOKEN is unset; GitHub Packages rejects an unauthenticated read" >&2 + exit 1 +fi + +# Storage is redirected so the run is self-contained. +# Unset, the hub resolves its default against the repository root it finds by walking up, so two runs and any +# stale bucket from ordinary development share one directory and the poll below can read a model this run never +# produced. +storage=$(coreutils mktemp -d) +scenario_pid="" +cleanup() { + status=$? + if [ -n "$scenario_pid" ]; then kill "$scenario_pid" 2>/dev/null || true; fi + # The collector outlives the task that started it. + # mise's child is the docker client, and killing a client leaves the container running, to be adopted by the + # next run as a name collision. + docker rm -f openobserve >/dev/null 2>&1 || true + coreutils rm -rf "$storage" + exit "$status" +} +trap cleanup EXIT + +mise trust "$out/mise.toml" >/dev/null + +# The lockfile is removed rather than honoured, which is the opposite of what a deployment usually wants. +# `lockfile = true` is set repository-wide, so a scenario directory acquires one on its first install and from +# then on pins whatever was resolved that day -- `specifiers = ["latest"]` alongside a fixed version, so a +# newer release is not consulted at all. That is precisely the release this task exists to exercise, and the +# pin is silent: the install succeeds, and only the running binary's version gives it away. The file is +# gitignored, so nothing downstream depends on it surviving. +coreutils rm -f "$out/mise.lock" + +# The npm registry config is exported here rather than left to the deployment's own `[env]`. +# mise computes that entry but does not apply it to its own tool resolution, so the embedded npm client reads +# no `NPM_CONFIG_USERCONFIG`, falls back to registry.npmjs.org, and answers `package not found` for a scoped +# package that only GitHub Packages holds. Exported into the process, the same file resolves against +# npm.pkg.github.com and installs. Measured both ways on one cold cache: the deployment's `[env]` alone +# fetched https://registry.npmjs.org/@edge-toolkit%2Fet-ws-math1 and installed nothing. +npmrc="$PWD/$out/npmrc" +(cd "$out" && NPM_CONFIG_USERCONFIG="$npmrc" mise install) +(cd "$out" && STORAGE_URL="file://$storage" mise run generated-scenario) & +scenario_pid=$! + +# The model is what says the exchange ran, rather than the processes being up. +# The runners retry until the hub answers, so both being alive proves only that neither has given up yet. +stored="" +for _ in $(coreutils seq 1 60); do + for bucket in "$storage"/*/math1-output.json; do + if [ -f "$bucket" ]; then + stored=$(coreutils cat "$bucket") + break + fi + done + if [ -n "$stored" ]; then break; fi + coreutils sleep 5 +done + +if [ -z "$stored" ]; then + echo "published-verify: no math1-output.json appeared under $storage" >&2 + echo "published-verify: check whether the crates and module packages this scenario names are published yet" >&2 + exit 1 +fi + +printf '%s' "$stored" | cargo run -q -p et-ws-test-server --bin verify-math1 +""" +shell = "{{ vars.task_shell }}" + # End-to-end check that the generated k3s manifests actually run the math1 scenario on a real cluster. # `kubeconform-check` proves the manifests are valid and `verification-check` proves they are stable, and # neither applies them -- a manifest that is both can still describe a deployment that does not work. This @@ -1435,13 +1545,25 @@ shell = "{{ vars.task_shell }}" description = "Apply the generated math1 k3s manifests to a throwaway cluster and verify the stored model" run = """ scenario=math1 +source="${usage_source:-local}" ns="et-$scenario" cluster="et-$scenario-verify" -out="verification/local/output/$scenario" +out="verification/$source/output/$scenario" hub_image="et-ws-server-$scenario:latest" -runner_image="et-ws-web-runner:latest" manifest_wait=300s +# The runner is the one image that differs between the two sources. +# A published deployment names the released one and so has it pulled here rather than built; the scenario +# image is built either way, because no release can carry a module set particular to one deployment. Pulling +# rather than leaving it to the cluster is what keeps the guard below meaningful: `k3d image import` reads the +# local daemon, so an image only the registry has would fail there instead of at the check. +if [ "$source" = "published" ]; then + runner_image="ghcr.io/edge-toolkit/core/et-ws-web-runner:latest" + docker pull "$runner_image" +else + runner_image="et-ws-web-runner:latest" +fi + for image in "$hub_image" "$runner_image"; do if ! docker image inspect "$image" >/dev/null 2>&1; then echo "k3s-verify: $image is not built; $out/README.md has the build and import steps" >&2 @@ -1476,7 +1598,7 @@ trap 'status=$?; k3d cluster delete "$cluster" >/dev/null 2>&1 || true coreutils rm -rf "$secrets_dir"' EXIT -input="verification/local/input/$scenario.yaml" +input="verification/$source/input/$scenario.yaml" cargo run -q -p et-cli -- generate-deployment --input-file "$input" --output-dir "$secrets_dir" k3d cluster delete "$cluster" >/dev/null 2>&1 || true k3d cluster create "$cluster" --wait @@ -1515,6 +1637,9 @@ fi printf '%s' "$stored" | cargo run -q -p et-ws-test-server --bin verify-math1 """ shell = "{{ vars.task_shell }}" +usage = """ +arg "[source]" help="Which verification tree to apply: local (default) or published" +""" # Hardening checks over the generated Kubernetes manifests, mirroring what Codacy reports on a pushed branch. # Codacy's three findings are trivy's KSV-0001 (privilege escalation), KSV-0014 (writable root filesystem) and diff --git a/.mise/config.windows.toml b/.mise/config.windows.toml index 16eb1bb1..6ed05641 100644 --- a/.mise/config.windows.toml +++ b/.mise/config.windows.toml @@ -441,3 +441,9 @@ shell = "{{ vars.task_shell_trace }}" [tasks.action-validator-check] # Broken by latest mise. run = "true" + +[tasks.agent-lint-check] +# Upstream publishes no Windows asset in the v4.0.3 release. +# The tool is therefore os-scoped away from this platform and there is nothing here to run, so this is +# overridden rather than left to fail on a binary that was never installed. +run = "true" diff --git a/.mise/mise.lock b/.mise/mise.lock index 015a3788..6ded6e21 100644 --- a/.mise/mise.lock +++ b/.mise/mise.lock @@ -1231,6 +1231,25 @@ url = "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/wasm-bindgen/wasm-bindgen/releases/download/0.2.128/wa url_api = "https://api.github.com/repos/wasm-bindgen/wasm-bindgen/releases/assets/545035796" provenance = "github-attestations" +[[tools."github:zhupanov/agent-lint"]] +version = "4.0.3" +backend = "github:zhupanov/agent-lint" + +[tools."github:zhupanov/agent-lint"."platforms.linux-arm64"] +checksum = "sha256:23db5f1a08ccabd5df6cb58d8c0aa7f1949c3b4246e4b90608bea1a369275633" +url = "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/zhupanov/agent-lint/releases/download/v4.0.3/agent-lint-v4.0.3-aarch64-unknown-linux-musl.tar.gz" +url_api = "https://api.github.com/repos/zhupanov/agent-lint/releases/assets/487601241" + +[tools."github:zhupanov/agent-lint"."platforms.linux-x64"] +checksum = "sha256:0be0fdea2b90a167bdf9f477289863b69f4df63890507289abdfd72fd2d9fab0" +url = "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/zhupanov/agent-lint/releases/download/v4.0.3/agent-lint-v4.0.3-x86_64-unknown-linux-musl.tar.gz" +url_api = "https://api.github.com/repos/zhupanov/agent-lint/releases/assets/487601238" + +[tools."github:zhupanov/agent-lint"."platforms.macos-arm64"] +checksum = "sha256:ba994b6a64fce048df6b90da5d1ed769771238fd3f85c0ab14d1bb6d58987f7a" +url = "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/zhupanov/agent-lint/releases/download/v4.0.3/agent-lint-v4.0.3-aarch64-apple-darwin.tar.gz" +url_api = "https://api.github.com/repos/zhupanov/agent-lint/releases/assets/487601239" + [[tools.go]] version = "1.26.4" backend = "core:go" diff --git a/CLAUDE.md b/CLAUDE.md index 1b640630..2972ac38 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -355,6 +355,12 @@ All tasks run through `mise run `. The aggregates below act on Rust + the **Build a single module:** `MISE_ENV= mise run build-ws--module` (e.g., `mise run build-ws-face-detection-module` for the Rust modules, or `MISE_ENV=zig mise run build-ws-zig-data1-module`). +**Maintainer tasks need `-E maint`:** the tasks in `.mise/config.maint.toml` -- `release-rust-crates`, +`publish-module-packages`, the `bootstrap-*-release` ones -- live in a config that is not loaded by default, so +the flag goes on `mise` itself and before `run`: `mise -E maint run release-rust-crates 1 --execute`. Without it +mise answers `no task release-rust-crates found` and prints the tasks it can see, which reads as the task having +been deleted rather than as an env that was never loaded. + ## Formatters & checks by file type **Verify the change actually works before running the lint/format battery.** Functional verification comes first: diff --git a/Cargo.lock b/Cargo.lock index 5a6f8a35..45d0d540 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2291,6 +2291,15 @@ dependencies = [ "uuid", ] +[[package]] +name = "defmt" +version = "0.3.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0963443817029b2024136fc4dd07a5107eb8f977eaf18fcd1fdeb11306b64ad" +dependencies = [ + "defmt 1.1.1", +] + [[package]] name = "defmt" version = "1.1.1" @@ -4155,7 +4164,7 @@ dependencies = [ [[package]] name = "edge-toolkit" -version = "0.2.0" +version = "0.3.0" dependencies = [ "asyncapi-rust", "base64 0.23.1", @@ -4366,7 +4375,7 @@ dependencies = [ [[package]] name = "et-modules-service" -version = "0.1.0" +version = "0.2.0" dependencies = [ "actix-files", "actix-rt", @@ -4397,7 +4406,7 @@ version = "0.1.0" [[package]] name = "et-otlp" -version = "0.1.0" +version = "0.2.0" dependencies = [ "edge-toolkit", "fs-err", @@ -4418,7 +4427,7 @@ dependencies = [ [[package]] name = "et-path" -version = "0.1.0" +version = "0.2.0" dependencies = [ "fs-err", "tempfile", @@ -4522,7 +4531,7 @@ dependencies = [ [[package]] name = "et-web" -version = "0.1.0" +version = "0.2.0" dependencies = [ "js-sys", "minicov", @@ -4801,7 +4810,7 @@ dependencies = [ [[package]] name = "et-ws-pyo3-runner" -version = "0.1.0" +version = "0.2.0" dependencies = [ "backon", "base64 0.23.1", @@ -4827,7 +4836,7 @@ dependencies = [ [[package]] name = "et-ws-runner-common" -version = "0.1.0" +version = "0.2.0" dependencies = [ "edge-toolkit", "et-otlp", @@ -4869,7 +4878,7 @@ dependencies = [ [[package]] name = "et-ws-server" -version = "0.1.0" +version = "0.2.0" dependencies = [ "actix-rt", "actix-web", @@ -5027,7 +5036,7 @@ dependencies = [ [[package]] name = "et-ws-wasi-runner" -version = "0.1.0" +version = "0.2.0" dependencies = [ "async-trait", "bytemuck", @@ -5071,7 +5080,7 @@ dependencies = [ [[package]] name = "et-ws-wasm-agent" -version = "0.1.0" +version = "0.2.0" dependencies = [ "chrono", "edge-toolkit", @@ -5089,7 +5098,7 @@ dependencies = [ [[package]] name = "et-ws-web-runner" -version = "0.1.0" +version = "0.2.0" dependencies = [ "cc", "command-error", @@ -5186,10 +5195,12 @@ dependencies = [ [[package]] name = "faster-hex" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7223ae2d2f179b803433d9c830478527e92b8117eab39460edae7f1614d9fb73" +checksum = "04839bdf9d8c10f66806fad16b852fc72aab80873aebc3cb69d85b4fa41543ed" dependencies = [ + "autocfg", + "defmt 0.3.100", "heapless", "serde", ] @@ -6672,7 +6683,7 @@ version = "0.2.37" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ab1baf72f08796de0260609515130699b890ac25f30e610ad894bc5856cafdb" dependencies = [ - "defmt", + "defmt 1.1.1", "jiff-core", "jiff-static", "portable-atomic", @@ -6686,7 +6697,7 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e52fe76043ccecc9005d2305ebaadf7d7fc0cc89ca6baa10a94d6bc68c7128c" dependencies = [ - "defmt", + "defmt 1.1.1", "log", ] diff --git a/Cargo.toml b/Cargo.toml index a2d742d2..6a2e6935 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -87,23 +87,23 @@ deno_resolver = "0.85" # instead of panicking when no SnapshotOptions struct is in OpState -- without # baking our own startup snapshot we'd otherwise hit the panic on first run. deno_runtime = { version = "0.262", features = ["transpile", "hmr"] } -edge-toolkit = { path = "libs/edge-toolkit", version = "0.2.0" } -et-modules-service = { path = "services/modules", version = "0.1.0" } +edge-toolkit = { path = "libs/edge-toolkit", version = "0.3.0" } +et-modules-service = { path = "services/modules", version = "0.2.0" } et-org = { path = "libs/org", version = "0.1.0" } -et-otlp = { path = "libs/et-otlp", version = "0.1.0" } -et-path = { path = "libs/path", version = "0.1.0" } +et-otlp = { path = "libs/et-otlp", version = "0.2.0" } +et-path = { path = "libs/path", version = "0.2.0" } et-rest-client = { path = "generated/rust-rest", version = "0.1.0", default-features = false } et-storage-service = { path = "services/storage", version = "0.1.0" } et-test-helpers = { path = "libs/test-helpers", version = "0.1.0" } et-test-otlp = { path = "libs/test-otlp", version = "0.1.0" } et-wasi-guest = { path = "libs/wasi-guest", version = "0.1.0" } -et-web = { path = "libs/web", version = "0.1.0" } +et-web = { path = "libs/web", version = "0.2.0" } et-websockify-service = { path = "services/websockify", version = "0.1.0" } -et-ws-runner-common = { path = "libs/ws-runner-common", version = "0.1.0" } -et-ws-server = { path = "services/ws-server", version = "0.1.0" } +et-ws-runner-common = { path = "libs/ws-runner-common", version = "0.2.0" } +et-ws-server = { path = "services/ws-server", version = "0.2.0" } et-ws-service = { path = "services/ws", version = "0.1.0" } et-ws-test-server = { path = "services/ws-test-server", version = "0.1.0" } -et-ws-wasm-agent = { path = "services/ws-wasm-agent", version = "0.1.0" } +et-ws-wasm-agent = { path = "services/ws-wasm-agent", version = "0.2.0" } fake = "5" fs-err = "3" futures-core = "0.3" diff --git a/agent-lint.toml b/agent-lint.toml new file mode 120000 index 00000000..c00dca6e --- /dev/null +++ b/agent-lint.toml @@ -0,0 +1 @@ +config/agent-lint.toml \ No newline at end of file diff --git a/config/agent-lint.toml b/config/agent-lint.toml new file mode 100644 index 00000000..3f85a6fe --- /dev/null +++ b/config/agent-lint.toml @@ -0,0 +1,13 @@ +# Rules this repository holds at warning rather than error. +# +# Q002 (prompt-negative-only) wants every operative prohibition paired with a positive imperative within three +# source lines. A good part of CLAUDE.md is deliberately absolute -- "NEVER delete a lint rule without explicit +# user permission", "never turn a lint off to make code pass" -- and those earn their keep by admitting no +# exception. Restating each as a positive imperative would soften instructions whose whole value is that they +# do not bend, so the rule is held at warning here rather than obeyed. +# +# Downgraded, not suppressed: `warn` keeps every finding printed while letting the check exit 0, so a site where +# a positive form genuinely reads better is still visible and can be rewritten. `suppress` would hide these and +# every future one, which is the wholesale silencing this repository's own linting rules forbid. +[lint] +warn = ["Q002"] diff --git a/config/conftest/policy/mise/mise.rego b/config/conftest/policy/mise/mise.rego index 2538393d..62b86254 100644 --- a/config/conftest/policy/mise/mise.rego +++ b/config/conftest/policy/mise/mise.rego @@ -215,6 +215,9 @@ allowed_os_scoped_tool := { "github:christianhelle/openapi2zig", "github:owenlamont/ryl", "github:uutils/findutils", + # agent-lint publishes three targets and no more: macOS arm64 and Linux x86_64/aarch64. + # There is no Windows or macOS x86_64 asset to install, so agent-lint-check skips on those two. + "github:zhupanov/agent-lint", # macmon is an Apple Silicon monitor with no Linux/Windows build, so the o2-macmon task scopes it to macOS. "github:vladkens/macmon", # nvidia_gpu_exporter is the Linux NVIDIA GPU path for o2-nvidia; Windows uses windows_exporter, macOS macmon. diff --git a/config/jscpd-baseline.json b/config/jscpd-baseline.json index 24cecbcd..a7afb077 100644 --- a/config/jscpd-baseline.json +++ b/config/jscpd-baseline.json @@ -25,10 +25,12 @@ "12bdba43f82ce591": 1, "136c57b4180f7238": 1, "151943582f4d3a1c": 1, + "1562424f270b9928": 1, "15f330210837288e": 1, "161f873e1c1bf887": 1, "162b7eccbfa52a74": 1, "16acb47f836cc1f8": 1, + "16cc2ae0719020a6": 1, "16ff0dbe3f3bf18c": 1, "1a07ddf368451c0a": 1, "1a1867cca4e1d4e8": 1, @@ -181,7 +183,6 @@ "98dab08a54b05abf": 1, "98e1331244999da4": 1, "98f11864904716c0": 1, - "99e862abc592b065": 1, "9a8dea5abd17445a": 1, "9b6b5ce6c7b4646c": 1, "9e835c94361d6917": 1, @@ -225,6 +226,7 @@ "ba8145a95d4bf884": 1, "bab02c8fb19dca60": 1, "bbce73eb21524ef5": 1, + "bbda9d8d86954db8": 1, "c01708e08c252d4a": 1, "c06c37bfc8b0d9c9": 1, "c09857b2be5aa4e1": 1, diff --git a/libs/edge-toolkit/Cargo.toml b/libs/edge-toolkit/Cargo.toml index 51c1e2a3..565393ba 100644 --- a/libs/edge-toolkit/Cargo.toml +++ b/libs/edge-toolkit/Cargo.toml @@ -2,7 +2,7 @@ name = "edge-toolkit" publish = true description = "A collection of utilities and common code for Edge Toolkit services" -version = "0.2.0" +version = "0.3.0" edition.workspace = true license.workspace = true repository.workspace = true diff --git a/libs/et-otlp/Cargo.toml b/libs/et-otlp/Cargo.toml index 69244cc2..afd35e52 100644 --- a/libs/et-otlp/Cargo.toml +++ b/libs/et-otlp/Cargo.toml @@ -2,7 +2,7 @@ name = "et-otlp" publish = true description = "Shared OTLP tracing setup for edge-toolkit services" -version = "0.1.0" +version = "0.2.0" edition.workspace = true license.workspace = true repository.workspace = true diff --git a/libs/path/Cargo.toml b/libs/path/Cargo.toml index 15fa5309..13b419f1 100644 --- a/libs/path/Cargo.toml +++ b/libs/path/Cargo.toml @@ -2,7 +2,7 @@ name = "et-path" publish = true description = "Path utilities" -version = "0.1.0" +version = "0.2.0" edition.workspace = true license.workspace = true repository.workspace = true diff --git a/libs/web/Cargo.toml b/libs/web/Cargo.toml index c4e64014..1c7473cd 100644 --- a/libs/web/Cargo.toml +++ b/libs/web/Cargo.toml @@ -2,7 +2,7 @@ name = "et-web" publish = true description = "Web helpers for WASM modules" -version = "0.1.0" +version = "0.2.0" edition.workspace = true license.workspace = true repository.workspace = true diff --git a/libs/ws-runner-common/Cargo.toml b/libs/ws-runner-common/Cargo.toml index e56ed487..27d34268 100644 --- a/libs/ws-runner-common/Cargo.toml +++ b/libs/ws-runner-common/Cargo.toml @@ -2,7 +2,7 @@ name = "et-ws-runner-common" publish = true description = "Shared helpers + constants for the native ws-server agent runners" -version = "0.1.0" +version = "0.2.0" edition.workspace = true license.workspace = true repository.workspace = true diff --git a/services/modules/Cargo.toml b/services/modules/Cargo.toml index b1f556bd..bc7d8fe3 100644 --- a/services/modules/Cargo.toml +++ b/services/modules/Cargo.toml @@ -2,7 +2,7 @@ name = "et-modules-service" publish = true description = "Discovers ws-module packages on disk and serves their files statically" -version = "0.1.0" +version = "0.2.0" edition.workspace = true license.workspace = true repository.workspace = true diff --git a/services/ws-pyo3-runner/Cargo.toml b/services/ws-pyo3-runner/Cargo.toml index b53ae853..0f59750f 100644 --- a/services/ws-pyo3-runner/Cargo.toml +++ b/services/ws-pyo3-runner/Cargo.toml @@ -2,7 +2,7 @@ name = "et-ws-pyo3-runner" publish = true description = "Generic edge-toolkit agent runtime that hosts a user-supplied Python module via PyO3" -version = "0.1.0" +version = "0.2.0" edition.workspace = true license.workspace = true repository.workspace = true diff --git a/services/ws-server/Cargo.toml b/services/ws-server/Cargo.toml index 668f7c61..41f4918b 100644 --- a/services/ws-server/Cargo.toml +++ b/services/ws-server/Cargo.toml @@ -2,7 +2,7 @@ name = "et-ws-server" publish = true description = "Actix-web entry point wiring the ws hub, storage and modules services together" -version = "0.1.0" +version = "0.2.0" edition.workspace = true license.workspace = true repository.workspace = true diff --git a/services/ws-wasi-runner/Cargo.toml b/services/ws-wasi-runner/Cargo.toml index 70bc2457..a1a26577 100644 --- a/services/ws-wasi-runner/Cargo.toml +++ b/services/ws-wasi-runner/Cargo.toml @@ -2,7 +2,7 @@ name = "et-ws-wasi-runner" publish = true description = "Native runner that fetches a ws-module WASI component from the server and executes it via wasmtime" -version = "0.1.0" +version = "0.2.0" edition.workspace = true license.workspace = true repository.workspace = true diff --git a/services/ws-wasm-agent/Cargo.toml b/services/ws-wasm-agent/Cargo.toml index ab09783a..dc22133b 100644 --- a/services/ws-wasm-agent/Cargo.toml +++ b/services/ws-wasm-agent/Cargo.toml @@ -2,7 +2,7 @@ name = "et-ws-wasm-agent" publish = true description = "WebSocket agent" -version = "0.1.0" +version = "0.2.0" edition.workspace = true license.workspace = true repository.workspace = true diff --git a/services/ws-web-runner/Cargo.toml b/services/ws-web-runner/Cargo.toml index 734bc5dc..b73a99ac 100644 --- a/services/ws-web-runner/Cargo.toml +++ b/services/ws-web-runner/Cargo.toml @@ -2,7 +2,7 @@ name = "et-ws-web-runner" publish = true description = "Runner that fetches a ws-module from the server and executes its JS via embedded Deno" -version = "0.1.0" +version = "0.2.0" edition.workspace = true license.workspace = true repository.workspace = true diff --git a/utilities/cli/src/deployment_types/mise.rs b/utilities/cli/src/deployment_types/mise.rs index 96974c3b..afc01e33 100644 --- a/utilities/cli/src/deployment_types/mise.rs +++ b/utilities/cli/src/deployment_types/mise.rs @@ -467,6 +467,16 @@ fn module_tool_value(tool: &str, latest: &Value) -> Value { if !tool.starts_with(&format!("npm:{MODULE_SCOPE}")) { return latest.clone(); } + waived(latest) +} + +/// The same waiver, for a tool named outright rather than resolved from a scenario's module list. +/// +/// The hub and the runners are this project's own binaries, published by the release that produced the +/// deployment, so the reasoning above applies to them unchanged: a deployment generated beside a fresh publish +/// would otherwise install yesterday's binary for a day and give no sign of it, since resolving `latest` to an +/// older release is what mise does rather than an error. `cargo:open` is somebody else's and keeps the delay. +fn waived(latest: &Value) -> Value { let mut options = Table::new(); let _previous: Option = options.insert("version".to_string(), latest.clone()); let _previous: Option = options.insert("minimum_release_age".to_string(), Value::String("0".to_string())); @@ -478,10 +488,10 @@ fn mise_tools(runners: &[RunnerInstance], artifacts: ArtifactSource, staged: &[S let _previous: Option = tools.insert("cargo:open".to_string(), Value::String(LATEST.to_string())); if matches!(artifacts, ArtifactSource::Published) { let latest = Value::String(LATEST.to_string()); - let _previous: Option = tools.insert(format!("cargo:{HUB_CRATE}"), latest.clone()); + let _previous: Option = tools.insert(format!("cargo:{HUB_CRATE}"), waived(&latest)); for runner in runners { let crate_name = runner_crate(&runner.runner); - let _previous: Option = tools.insert(format!("cargo:{crate_name}"), latest.clone()); + let _previous: Option = tools.insert(format!("cargo:{crate_name}"), waived(&latest)); } for tool in staged { let value = module_tool_value(tool, &latest); diff --git a/utilities/cli/src/lib.rs b/utilities/cli/src/lib.rs index 6f5148aa..9f408139 100644 --- a/utilities/cli/src/lib.rs +++ b/utilities/cli/src/lib.rs @@ -628,12 +628,21 @@ fn runner_kinds(cluster: &ClusterInput) -> Vec { /// published one declares its binaries as `cargo:` tools, and `task.run_auto_install` is off, so without this /// step the first task dies on a command it cannot find rather than fetching it. Why the binaries are released /// rather than built is said once, above the run sections, because it is true of every mode. +/// +/// `NPM_CONFIG_USERCONFIG` is exported on the command line rather than left to the `[env]` beside it, which +/// carries the same value. mise computes that entry but does not apply it to its own tool resolution, so the +/// npm client it embeds reads no user config, falls back to registry.npmjs.org, and reports the scoped module +/// packages as `package not found` -- they exist only on GitHub Packages. Exported into the process, the same +/// file resolves against the right registry. The `[env]` entry stays because the tasks do get it. const fn mise_install_note(artifacts: ArtifactSource) -> &'static str { if matches!(artifacts, ArtifactSource::Published) { return concat!( - "Fetch the binaries the tasks below name before the first run:\n\n", + "Fetch the binaries and module packages the tasks below name before the first run.\n", + "`GITHUB_TOKEN` has to be set: GitHub Packages rejects an unauthenticated read even for a public\n", + "package. The registry configuration is exported rather than relied on from `mise.toml`, because\n", + "mise does not apply its own `[env]` to the resolution this command performs:\n\n", "```bash\n", - "mise install\n", + "NPM_CONFIG_USERCONFIG=\"$PWD/npmrc\" mise install\n", "```\n\n" ); } diff --git a/utilities/cli/tests/scenario_generation.rs b/utilities/cli/tests/scenario_generation.rs index d61d18d2..9914246c 100644 --- a/utilities/cli/tests/scenario_generation.rs +++ b/utilities/cli/tests/scenario_generation.rs @@ -547,9 +547,12 @@ fn the_artifact_source_decides_whether_the_mise_deployment_builds_what_it_runs() "a published deployment runs the released runner: {published}" ); assert!(!published.contains("cargo run"), "and builds nothing: {published}"); - // Each released binary is declared as a tool, which is what puts it on `PATH` for the task above. + // Each released binary is declared as a tool, which is what puts it on `PATH` for the task above, and + // each waives the release age. Without the waiver mise hides a release younger than a day and installs + // the one before it -- so a deployment generated beside the publish it was made for runs the previous + // binary, and says nothing: resolving `latest` to an older release is ordinary behaviour, not an error. for crate_name in ["et-ws-server", "et-ws-wasi-runner"] { - let declared = format!("\"cargo:{crate_name}\" = \"latest\""); + let declared = format!("[tools.\"cargo:{crate_name}\"]\nminimum_release_age = \"0\"\nversion = \"latest\""); assert!(published.contains(&declared), "expected {declared} in: {published}"); } } diff --git a/verification/published/output/default/README.md b/verification/published/output/default/README.md index 3fdc9a72..1acfbc3f 100644 --- a/verification/published/output/default/README.md +++ b/verification/published/output/default/README.md @@ -17,10 +17,13 @@ covering it, so its credential is not committed by whatever repository it lands ## Run With Mise -Fetch the binaries the tasks below name before the first run: +Fetch the binaries and module packages the tasks below name before the first run. +`GITHUB_TOKEN` has to be set: GitHub Packages rejects an unauthenticated read even for a public +package. The registry configuration is exported rather than relied on from `mise.toml`, because +mise does not apply its own `[env]` to the resolution this command performs: ```bash -mise install +NPM_CONFIG_USERCONFIG="$PWD/npmrc" mise install ``` From this directory, start the scenario with: diff --git a/verification/published/output/default/mise.toml b/verification/published/output/default/mise.toml index 48a4684a..dc87a1fe 100644 --- a/verification/published/output/default/mise.toml +++ b/verification/published/output/default/mise.toml @@ -32,11 +32,14 @@ et-ws-server MODULES_ROOT = "@edge-toolkit/et-ws-server-static" [tools] -"cargo:et-ws-server" = "latest" "cargo:open" = "latest" "npm:onnxruntime-web" = "latest" "npm:stats-gl" = "latest" +[tools."cargo:et-ws-server"] +minimum_release_age = "0" +version = "latest" + [tools."npm:@edge-toolkit/et-ws-server-static"] minimum_release_age = "0" version = "latest" diff --git a/verification/published/output/math1/README.md b/verification/published/output/math1/README.md index 8a548a7f..10ad9b92 100644 --- a/verification/published/output/math1/README.md +++ b/verification/published/output/math1/README.md @@ -17,10 +17,13 @@ covering it, so its credential is not committed by whatever repository it lands ## Run With Mise -Fetch the binaries the tasks below name before the first run: +Fetch the binaries and module packages the tasks below name before the first run. +`GITHUB_TOKEN` has to be set: GitHub Packages rejects an unauthenticated read even for a public +package. The registry configuration is exported rather than relied on from `mise.toml`, because +mise does not apply its own `[env]` to the resolution this command performs: ```bash -mise install +NPM_CONFIG_USERCONFIG="$PWD/npmrc" mise install ``` From this directory, start the scenario with: diff --git a/verification/published/output/math1/mise.toml b/verification/published/output/math1/mise.toml index d7cf4f30..936426e1 100644 --- a/verification/published/output/math1/mise.toml +++ b/verification/published/output/math1/mise.toml @@ -51,10 +51,16 @@ et-ws-server [tasks.ws-server.env] [tools] -"cargo:et-ws-server" = "latest" -"cargo:et-ws-web-runner" = "latest" "cargo:open" = "latest" +[tools."cargo:et-ws-server"] +minimum_release_age = "0" +version = "latest" + +[tools."cargo:et-ws-web-runner"] +minimum_release_age = "0" +version = "latest" + [tools."npm:@edge-toolkit/et-ws-math1"] minimum_release_age = "0" version = "latest" diff --git a/verification/published/output/pyo3-math1/README.md b/verification/published/output/pyo3-math1/README.md index bfd43e22..64e7e448 100644 --- a/verification/published/output/pyo3-math1/README.md +++ b/verification/published/output/pyo3-math1/README.md @@ -17,10 +17,13 @@ covering it, so its credential is not committed by whatever repository it lands ## Run With Mise -Fetch the binaries the tasks below name before the first run: +Fetch the binaries and module packages the tasks below name before the first run. +`GITHUB_TOKEN` has to be set: GitHub Packages rejects an unauthenticated read even for a public +package. The registry configuration is exported rather than relied on from `mise.toml`, because +mise does not apply its own `[env]` to the resolution this command performs: ```bash -mise install +NPM_CONFIG_USERCONFIG="$PWD/npmrc" mise install ``` From this directory, start the scenario with: diff --git a/verification/published/output/pyo3-math1/mise.toml b/verification/published/output/pyo3-math1/mise.toml index 5016ccda..277c0771 100644 --- a/verification/published/output/pyo3-math1/mise.toml +++ b/verification/published/output/pyo3-math1/mise.toml @@ -51,11 +51,20 @@ et-ws-server [tasks.ws-server.env] [tools] -"cargo:et-ws-pyo3-runner" = "latest" -"cargo:et-ws-server" = "latest" -"cargo:et-ws-wasi-runner" = "latest" "cargo:open" = "latest" +[tools."cargo:et-ws-pyo3-runner"] +minimum_release_age = "0" +version = "latest" + +[tools."cargo:et-ws-server"] +minimum_release_age = "0" +version = "latest" + +[tools."cargo:et-ws-wasi-runner"] +minimum_release_age = "0" +version = "latest" + [tools."npm:@edge-toolkit/et-ws-pyo3-math1"] minimum_release_age = "0" version = "latest" diff --git a/verification/published/output/wasi-math1/README.md b/verification/published/output/wasi-math1/README.md index 9aaee78e..19405959 100644 --- a/verification/published/output/wasi-math1/README.md +++ b/verification/published/output/wasi-math1/README.md @@ -17,10 +17,13 @@ covering it, so its credential is not committed by whatever repository it lands ## Run With Mise -Fetch the binaries the tasks below name before the first run: +Fetch the binaries and module packages the tasks below name before the first run. +`GITHUB_TOKEN` has to be set: GitHub Packages rejects an unauthenticated read even for a public +package. The registry configuration is exported rather than relied on from `mise.toml`, because +mise does not apply its own `[env]` to the resolution this command performs: ```bash -mise install +NPM_CONFIG_USERCONFIG="$PWD/npmrc" mise install ``` From this directory, start the scenario with: diff --git a/verification/published/output/wasi-math1/mise.toml b/verification/published/output/wasi-math1/mise.toml index 8c1035b0..dc4d4af8 100644 --- a/verification/published/output/wasi-math1/mise.toml +++ b/verification/published/output/wasi-math1/mise.toml @@ -51,10 +51,16 @@ et-ws-server [tasks.ws-server.env] [tools] -"cargo:et-ws-server" = "latest" -"cargo:et-ws-wasi-runner" = "latest" "cargo:open" = "latest" +[tools."cargo:et-ws-server"] +minimum_release_age = "0" +version = "latest" + +[tools."cargo:et-ws-wasi-runner"] +minimum_release_age = "0" +version = "latest" + [tools."npm:@edge-toolkit/et-ws-wasi-math1"] minimum_release_age = "0" version = "latest"