Upgrade to Rust 1.98.1 and refresh dependencies - #366
Open
aram356 wants to merge 9 commits into
Open
Conversation
Move the pinned toolchain from 1.95.0 to 1.98.1 (current stable) and take every semver-compatible dependency update. Toolchain pins move together so a generated app builds on the same release this repo does: `.tool-versions`, the demo's Fastly `rust-toolchain.toml`, and the CLI scaffold generator. A new test asserts the generator's pin matches `.tool-versions` -- previously only a doc comment asked the two to be bumped in step, and they had already drifted. Three releases of new clippy `restriction` lints land with the bump: - `inline_trait_bounds` (~42 sites) -- generic bounds moved to `where` clauses. - `duration_suboptimal_units` -- `Duration::from_mins` is const-stable as of 1.98, so `MIN_TTL` uses it and its `#[expect]` is dropped. - `inline_modules` -- allowed workspace-wide, in the demo, and in the scaffold template. It flags every `#[cfg(test)] mod tests` block; colocated tests are the mainstream Rust idiom and what CLAUDE.md mandates. - `missing_trait_methods` now wants `Drop::pin_drop` on three RAII guards. That method is behind the unstable `pin_ergonomics` feature and the compiler rejects implementing it alongside `drop`, so no stable code satisfies the lint; each site gets an `#[expect]` that will fail once the method stabilises. redb 4.2.0 added an assertion that read references not outlive their transaction, which caught a latent bug in the persistent KV store: `get_bytes` held an `AccessGuard` borrowed from the table across the `drop(table); drop(read_txn)` in its lazy-expiry path. The value is now copied out before the transaction ends, so the update goes in rather than being pinned back. The demo workspace moves to edition 2024, matching the main workspace and the scaffold templates. `cargo fix --edition` wrapped the config test's `env::set_var`/`remove_var` in `unsafe` blocks with FIXMEs; the demo denies `unsafe_code`, so the test uses the existing `edgezero_core::test_env::EnvOverride` guard instead, which also restores the variable when an assertion panics. Edition 2024 let-chains collapse a nested `if let` in the demo handlers. All five CI gates pass on 1.98.1, and the three wasm targets (wasip1/wasip2/unknown-unknown) compile.
The `inline_trait_bounds` sweep ran on the host target, so it never linted code behind `target_arch = "wasm32"` or the adapters' wasm-only `tests/contract.rs`. CI's per-target clippy matrix caught seven remaining sites across the three adapters; all move to `where` clauses. Toolchain references outside `.tool-versions` now read 1.98.1: the CLAUDE.md toolchain table, the illustrative version in the two toolchain-file parsers, and the deploy-core test harness defaults that stand in for a real deploy. Two literals in `deploy-core/tests/run.sh` stay at 1.95.0 on purpose -- they pair with a 1.60.0 fixture to assert that an app's own `.tool-versions` wins over the deployer's, so the values are arbitrary and only their precedence matters. The `pub_with_shorthand` note claimed 6 offending items, verified on clippy 1.95. Re-running the check on 1.98 with the allow removed reports 44, so the comment now says 44 rather than carrying a stale count forward. `docs/superpowers/**` keeps its 1.95 mentions: those are dated design records describing the stack as it was when each was written, and the VitePress config excludes them from the published site.
Moves the tools `.tool-versions` pins to current releases, each verified against the workload CI actually runs it for: - Fastly CLI 15.1.0 -> 16.0.0. The major carries no breaking changes (a starter-kit bounds fix and dependency bumps); every subcommand the adapter shells out to -- `compute serve`, `config-store list --json`, `config-store-entry list`, `service`, `service-version`, `resource-link` -- still exists with the same flags. - Viceroy 0.17.0 -> 0.21.0. Runs the Fastly wasip1 suites: 6 contract tests and 88 runtime unit tests pass. - wasmtime 44.0.1 -> 48.0.1. Runs the Spin wasip2 contract suite: 12 tests, same result as on 44.0.1. - Node.js 24.12.0 -> 24.20.0, staying on the Krypton LTS line rather than jumping to 26.x. The docs site lints and builds on it. `deploy-fastly/versions.json` moves in lockstep with a real checksum for the v16.0.0 linux-amd64 archive, cross-checked against the `fastly_v16.0.0_SHA256SUMS` release asset. `install-fastly.sh` fails closed when versions.json and `.tool-versions` disagree, and the `real-install` job downloads and verifies the archive for real, so a wrong digest here breaks CI rather than shipping. The scaffold generator pins all four tools, so generated apps get the same set. Its drift test now covers nodejs, fastly and viceroy alongside rust -- confirmed to fail, with the offending tool named, when a pin is changed in only one of the two files. Two 15.1.0/1.95.0 literals in the deploy-core tests stay put: they are self-contained fixtures asserting resolution precedence and checksum-mismatch rejection, where only the relationship between the values matters.
sha2 0.11 returns `hybrid_array::Array` from `finalize()`/`digest()`.
Unlike 0.10's `GenericArray` it does not implement `LowerHex`, so the
two `format!("{:x}", ..)` call sites no longer compile.
Hand-rolling the hex is the wrong answer here: every version of it
trips a different `restriction` lint in turn (`indexing_slicing` for a
lookup table, `cast_possible_truncation` for `as`, then
`arithmetic_side_effects` for the nibble maths). `base16ct` is
RustCrypto's own encoder -- same org as sha2, `no_std`, and with no
dependencies of its own, so it stays WASM-safe -- and
`lower::encode_string` restores both call sites to a one-liner.
Taking 0.11 on our direct dependency drops sha2 0.10 from the tree
entirely; the workspace now resolves a single sha2.
Output is unchanged: `canonical_form_pin_v1` still pins
903a0e4a..., and the 51 `chunked_config` tests -- including the
writer/resolver round trip that reads back an envelope by its embedded
digest -- pass under Viceroy on wasm32-wasip1.
`cargo update` only moves within the ranges the manifests allow, so seven crates were still held a major behind. Six of them upgrade: - brotli 8 -> 9, similar 2 -> 3, validator 0.20 -> 0.21 (both workspaces, since the demo pins it separately) -- no source changes. - fastly and log-fastly 0.12 -> 0.13. The Fastly wasip1 suites pass under Viceroy: 88 runtime unit tests and 6 contract tests. - rusqlite 0.32 -> 0.40. `prepare_cached` is no longer on `Transaction`; the one call site in the Spin KV writer uses `prepare` instead, which prepares the statement once and reuses it across the batch exactly as before. The push_sqlite suite still passes, including the round trip through Spin's vendored schema. spin-sdk stays at ~6.0. 7.0 compiles clean on wasm32-wasip2 and its 12 contract tests pass under wasmtime, but it imports `wasi:http/types@0.3.0`, which Spin 4.0.2 does not provide -- the component fails to link at `spin up`, which neither `cargo check` nor the wasmtime-hosted tests can see. The pin comment now records that error verbatim so the next attempt starts from the evidence. Reverting restores `smoke_test_kv.sh spin` to 8/8. The scaffold generator seeds `validator` and `fastly` for generated apps, and its own comment requires the validator major to match this workspace or a generated crate fails to compile against `edgezero-core`'s re-derived `Validate`. Both seeds move with the workspace, and a new test asserts the shared seeds track the root manifest -- confirmed to fail, naming both versions, when they drift.
The six deploy smoke jobs failed on the validator 0.20 -> 0.21 bump: error[E0277]: the trait bound `FixtureAppConfig: validator::traits::Validate` is not satisfied `make-smoke-fixture.sh` generates an app-owned CLI that takes `edgezero-core` by path but pinned `validator` itself. Once the workspace moved to 0.21 the fixture still asked for 0.20, so the derive expanded against one `Validate` while the crate imported another. Confirmed by rebuilding the fixture locally: 0.20 reproduces the CI error exactly, 0.21 builds. That made three copies of the same pin found one failure at a time -- the workspace, the demo, the scaffold seeds, and now the fixture -- so `check_shared_dep_pins.sh` compares all of them against the root manifest and runs beside the existing gates in test.yml. Verified it reports each location, by name and version, when that pin is changed in only one file. Cargo cannot catch this itself: the demo workspace is excluded, the scaffold seed is a string literal, and the fixture manifest does not exist until CI generates it.
Self-review of the earlier commits. `get_bytes` copied the value bytes on every path, including the expired one where they are dropped two statements later in favour of `Ok(None)`. It also asked `is_expired` twice about the same value and left `Ok(Some(value))` depending on an invariant the reader has to reconstruct -- that the expired branch always returns first, so `value` cannot be `None` there. Expiry is now decided once, while the `AccessGuard` is still alive, and the bytes are copied only when they will be returned; the delete path falls through instead of nesting. The app-demo env test's comment still described mutating process env in place and leaned on a sibling test's `env_overlay: false` for safety. Both stopped being true when it moved to `env_lock` + `EnvOverride`; the comment now names the lock and the restore-on-drop. `check_shared_dep_pins.sh` matched only the table form of the pin. It fails closed on the bare form (`validator = "0.21"`) rather than passing silently, but that turns a harmless reformat into a red build, so `pin_in` now accepts either spelling.
The `base16ct` pin carried five lines explaining that sha2 0.11's `finalize()` stopped implementing `LowerHex`. That is why the change was made, not something a later reader has to know to avoid breaking anything, and the commit that introduced it already says so. The pin now reads like every other entry in the list. The redb comment spent its second half restating what the two following statements plainly do. What is worth keeping is the part a reader cannot see: redb asserts that no read reference outlives its transaction, so the borrow has to end before the drops. Without that the code looks needlessly roundabout and the obvious simplification reintroduces the panic. The `inline_modules` and `spin-sdk` comments stay. Every other entry in the clippy allow-list carries its reason, so a bare allow would be the odd one out and an easy deletion; and the spin-sdk note is what stops the next attempt rediscovering that 7.0 compiles, passes the contract tests, and only then fails to link at `spin up`.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
cargo updatealone cannot reach.restrictionlints in code where a fix exists, and migratesexamples/app-demoto edition 2024 so it matches the main workspace and the scaffold templates.Changes
.tool-versions,examples/app-demo/.../rust-toolchain.toml1.95.0→1.98.1crates/edgezero-cli/src/generator.rs1.98.1and the current tool/crate versions, so new apps match this repo; drift tests assert both against the real filesCargo.toml,Cargo.lockinline_modulesallowcrates/edgezero-adapter-axum/src/key_value_store.rsget_bytescopies the value out before dropping the read txncrates/edgezero-core/src/key_value_store.rsMIN_TTLusesDuration::from_mins(1); stale#[expect]removed-core/src/canonical_form.rs,-adapter-fastly/src/chunked_config.rsbase16ct::lower::encode_stringedgezero-core,-adapter,-adapter-fastly,-adapter-axum,-cli,-macroswhereclauses (48 sites in total, incl. the wasm-only ones below)crates/edgezero-core/src/{app_config,test_env}.rs,-adapter-fastly/src/cli.rs#[expect]for the unsatisfiablepin_droplintcrates/edgezero-cli/src/templates/root/Cargo.toml.hbsinline_modulesallow tooexamples/app-demo/**EnvOverridereplacesunsafeenv access; let-chain collapse-adapter-{cloudflare,spin,fastly}wasm paths +tests/contract.rsCLAUDE.md, deploy-core scripts/tests.tool-versions,deploy-fastly/versions.jsonNew clippy lints
Three releases' worth arrive at once, because the workspace enables the entire
restrictiongroup atdeny.Fixed in code:
inline_trait_bounds(48 sites) — bounds moved towhereclauses.duration_suboptimal_units—Duration::from_minsis const-stable as of 1.98, soMIN_TTLuses it and its#[expect]is dropped.Allowed, with reasons:
inline_modulesflags every#[cfg(test)] mod testsblock — 77 of them. Complying means splitting each into a sibling file, which contradicts CLAUDE.md's "colocate tests with implementation modules". Colocation is also what std, tokio and serde do. Allowed in the workspace, the demo, and the scaffold template so generated apps don't inherit the problem.missing_trait_methodsnow wantsDrop::pin_dropon three RAII guards. That method sits behind the unstablepin_ergonomicsfeature, and the compiler rejects implementing it alongsidedrop(conflicting implementations of Drop::drop and Drop::pin_drop) — no stable code satisfies the lint. Each site gets an#[expect], which will itself fail once the method stabilises, prompting a revisit.redb 4.2.0 caught a real bug
redb 4.2.0 initially failed
ttl_expires_entryandcontract_ttl_expireswith a panic pointing into redb's own page manager:This reads like an upstream regression but isn't. redb added an assertion that read references must not outlive their transaction, and it caught
get_bytesholding anAccessGuard(borrowed from the table) across thedrop(table); drop(read_txn)in its lazy-expiry path — silently wrong on 4.1.x. The value is now copied out before the transaction ends, so the update goes in rather than being pinned back. Otherdrop(table)sites in that file are write transactions with no live read guard.CLI tooling
Each pin was verified against the workload CI runs it for, not just installed:
deploy-fastly/versions.jsonmoves in lockstep with a real SHA-256 for the v16.0.0 linux-amd64 archive, cross-checked against thefastly_v16.0.0_SHA256SUMSrelease asset. This matters:install-fastly.shfails closed when versions.json and.tool-versionsdisagree, and thereal-installjob downloads and verifies the archive for real — a wrong digest breaks CI rather than shipping.The scaffold generator pins all four, so generated apps get the same set, and its drift test covers nodejs/fastly/viceroy alongside rust. I confirmed it fails, naming the offending tool, when a pin is changed in only one of the two files.
Host clippy does not cover the wasm targets
cargo clippy --fixruns against the host target, so it never linted code behindtarget_arch = "wasm32"or the adapters' wasm-onlytests/contract.rs. CI's per-target clippy matrix caught 7 survivinginline_trait_boundssites there. Worth knowing for future lint sweeps: a cleancargo clippy --workspace --all-featureson the host does not imply the wasm targets are clean.Dependency majors
cargo updateonly moves within the ranges the manifests allow, so seven crates sat a major behind. Six upgrade:brotlisimilarvalidatorfastly,log-fastlyrusqliteprepare_cachedis no longer onTransaction; the one call site usesprepare, which still prepares once and reuses across the batchredbAccessGuardfix abovespin-sdkstays at~6.0. 7.0 compiles clean onwasm32-wasip2and its 12 contract tests pass under wasmtime — but it importswasi:http/types@0.3.0, which Spin 4.0.2 does not provide, so the component fails to link atspin up:Neither
cargo checknor the wasmtime-hosted tests can see this — only the real runtime. The pin comment now records the error verbatim so the next attempt starts from evidence rather than repeating the experiment. Reverting restoressmoke_test_kv.sh spinto 8/8.The scaffold generator seeds
validatorandfastly, and its own comment requires the validator major to match this workspace or a generated crate fails againstedgezero-core's re-derivedValidate. Both seeds move, and a new test asserts the shared seeds track the root manifest — confirmed to fail, naming both versions, when they drift.sha2 0.10 → 0.11
finalize()/digest()now returnhybrid_array::Array, which — unlike 0.10'sGenericArray— does not implementLowerHex, so bothformat!("{:x}", ..)call sites stop compiling.Hand-rolling the hex is a trap: each version trips a different
restrictionlint in turn (indexing_slicingfor a lookup table, thenas-truncation, thenarithmetic_side_effectsfor nibble maths).base16ctis RustCrypto's own encoder — same org as sha2,no_std, zero dependencies of its own — andlower::encode_stringrestores both sites to a one-liner.Taking 0.11 on our direct dependency drops sha2 0.10 from the tree. One older copy remains —
fastly0.13 pulls sha2 0.9 transitively — so this goes from three copies to two, not to one.Output is unchanged, which matters because these digests are persisted in config envelopes:
canonical_form_pin_v1still pins903a0e4a…, and the 51chunked_configtests — including the writer/resolver round trip that reads an envelope back by its embedded digest — pass under Viceroy onwasm32-wasip1.A fourth validator pin, and a gate for it
The first push of the
validator0.20 → 0.21 bump turned six deploy smoke jobs red:make-smoke-fixture.shgenerates an app-owned CLI at CI time that takesedgezero-coreby path but pinsvalidatoritself, so the derive expanded against oneValidatewhile the crate imported another. Rebuilding the fixture locally reproduces the error at 0.20 and builds clean at 0.21.That was the pin turning up in a fourth place, one failure at a time — this workspace, the excluded demo workspace, the scaffold seeds, and a manifest that does not exist until CI generates it.
scripts/check_shared_dep_pins.shnow compares all four against the root manifest and runs beside the existing gates intest.yml; it is verified to report each location, by name and version, when only one is changed. Cargo cannot do this itself for any of the three satellites.Toolchain references
Updated to 1.98.1 in
CLAUDE.md, the two toolchain-file parsers' illustrative versions, and the deploy-core test-harness defaults. Two deliberate exclusions:docs/superpowers/**keeps its 1.95 mentions — dated design records describing the stack as it was when each was written, andsrcExcluded from the published site.deploy-core/tests/run.shpair with a1.60.0fixture to assert an app's.tool-versionsbeats the deployer's; the values are arbitrary, only precedence matters.The
pub_with_shorthandcomment claimed 6 offending items (verified on 1.95). Re-running the check on 1.98 reports 44, so the note was corrected rather than renumbered.Note for reviewers
Rust here is managed by asdf, not rustup. After pulling this branch, run
asdf install rust 1.98.1followed byrustup target add wasm32-wasip1 wasm32-wasip2 wasm32-unknown-unknown. Without the asdf install, cargo fails withNo version is set for command cargo— and a clippy run can exit 0 on that message alone, which looks like a pass.Closes
Closes #365
Test plan
Verified on
rustc 1.98.1 (48a229cea 2026-09-01):cargo test --workspace --all-targets— 1424 passed, 0 failedcargo clippy --workspace --all-targets --all-features -- -D warningscargo fmt --all -- --checkcargo check --workspace --all-targets --features "fastly cloudflare spin"wasm32-wasip1(Fastly) /wasm32-wasip2(Spin) /wasm32-unknown-unknown(Cloudflare).github/actions/deploy-core/tests/run.sh— 260 passed, 0 failedexamples/app-demoworkspace: 33 passed, 0 failed, plus clippy clean on edition 2024npm ci && npm run lint && npm run buildon Node 24.20.0edgezero serve --adapter axum— not run; covered by the axum adapter's test suitecanonical_form_pin_v1passes, confirming config-envelope hashes are unchangedscripts/smoke_test_kv.sh spin— 8/8 against a realspin up(this is what caught the spin-sdk 7 blocker)Checklist
{id}syntax (not:id)edgezero_core(nothttpcrate)KvRegistry/ConfigRegistry/SecretRegistry(not the legacy single-handle setters)check_shared_dep_pins.shwired into CI