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`.
Three issues from review. The demo lockfile still resolved validator_derive 0.20.0, which pulls proc-macro-error2 2.0.1 -- the crate behind the "rejected by a future version of Rust" warning that has been printing on every demo build. The root lockfile had already moved to 0.20.1 and proc-macro-error3; the demo now matches, and the warning is gone. brotli goes back to 8. `async-compression` 0.4.44 resolves brotli 8 through compression-codecs 0.4.39 (the current release), so taking 9 on the direct dependency never replaced that copy -- it added a second complete allocator/decompressor stack that every adapter graph compiles. Measured on the demo Spin release build: 7,169,574 bytes with both, 7,129,410 with one, so ~40 KB of wasm bought nothing. We call only `Decompressor` and `CompressorWriter`, which are unchanged across the two majors. This is the same duplicate-stack test that kept sha2 honest; it should have been applied to every major, not just that one. `check_shared_dep_pins.sh` read the pin with an unanchored match and took the first hit, so a commented-out `# validator = "0.19"` above the real line became the expected version and the gate reported three violations against three correct files. The match is now anchored to an assignment at line start (optionally inside a Rust string literal, which is how the scaffold seeds it), and a miss normalises to empty via `|| true` so the missing-pin diagnostics run instead of `set -e` killing the script at the command substitution.
spin-sdk moves to `~7.0`. The earlier hold was accurate about the symptom -- 7.0 imports `wasi:http/types@0.3.0` and the component fails to link -- but wrong to treat it as a property of the SDK. It is a property of the runtime that was installed: Spin 4.0.2 does not carry that interface and Spin 4.1.0 does. Verified end to end rather than by compiling: `smoke_test_kv.sh spin` is 8/8 against a real `spin up` on 4.1.0. The demo workspace and the scaffold seed pin spin-sdk separately and move with it; `.tool-versions` now names Spin 4.1 as the floor instead of the stale 3.7. `redb` was declared `4.1.0` while resolving 4.2.0. The caret made that work, but 4.2 is the release whose read-reference assertion caught the `AccessGuard` bug fixed earlier in this branch, so the declared floor now matches the version whose behaviour the code depends on. `check_shared_dep_pins.sh` had a second, unanchored parser for the generator seed that the earlier fix missed. A commented-out seed above the real assignment shadowed it, which both failed a correct tree and passed an incorrect one; a missing seed printed nothing at all, because `set -e` aborted at the command substitution. It now calls `pin_in` like the other paths and reports a missing seed by name. Also refreshes both lockfiles (20 packages in the root, a larger backlog in the demo, which had never taken the earlier sweep) and moves the demo off anyhow 1.0.102, which carries an unsoundness advisory. brotli stays at 8: compression-codecs 0.4.39 is current and still resolves brotli 8, so taking 9 adds a second allocator and decompressor rather than replacing the first.
`npm update` inside the declared ranges; `package.json` is unchanged. eslint 10.9.1 -> 10.10.0, @types/node 24.12.4 -> 24.13.3, and brace-expansion 5.0.6 -> 5.0.9 transitively, which is what Dependabot #353 proposes. @types/node stays on the 24 line on purpose: it describes the Node runtime, and `.tool-versions` pins nodejs 24.20.0 (the active LTS). Taking the 26 line that Dependabot #368 proposes would type against APIs the pinned runtime does not have. vitepress, prettier, @eslint/js and typescript-eslint were already at their latest releases. `npm audit` still reports three advisories, all the same root: vitepress 1.6.4 -> vite 5.4.21 -> esbuild 0.21.5, with no fix available. 1.6.4 is the newest vitepress (2.x is alpha only), and the advisory is a dev-server issue in a dev-only dependency, so it does not reach `vitepress build` or the published site.
`docs/guide/adapters/spin.md` still documented the `~6.0` pin in its schema-coupling note. It now names `~7.0`, matching the workspace. The prerequisites also listed "Spin CLI" with no floor. spin-sdk 7 imports `wasi:http/types@0.3.0`, which Spin 4.0.x does not provide, and the failure mode is unhelpful: the component builds, the contract tests pass under wasmtime, and only `spin up` reports a linker error naming an interface the reader never wrote. The requirement and that error are now stated where someone would look before hitting it. Checked the rest of the published guide for claims this branch invalidated: the remaining crate mentions are version-agnostic, and the `sha256` sections describe the envelope format, which the sha2 0.11 move left byte-identical.
ChristianPavilonis
left a comment
There was a problem hiding this comment.
Summary
Reviewed 40c52e3f68aeef925d395688d34d601fc269ee4f against 593fc9282a1c56e12bae15f91eef2162f4b6a1b7. The dependency and toolchain refresh is well covered by CI and focused local checks. Approving with two medium compatibility and operational follow-ups noted inline.
Review follow-ups on #366. The Spin 4.1 requirement was documented in the adapter guide but never reached the code that enforces it, so both directions were wrong: `smoke_test_config_key_override.sh` accepted anything from 3.7 up, which let Spin 4.0 through the pre-check and into the opaque wasm linker error the check exists to prevent; and `VERIFIED_SPIN_MAJOR_RANGE` excluded major 4, so an operator on the required runtime was told it was unverified. The threshold is now 4.1 and the range includes 4. Major 4 is included on evidence, not convenience: the `spin_key_value` statement at the v4.1.0 tag is byte-identical to the vendored copy (checked 2026-09-07). Two tests pin the behaviour -- one asserts 4 is verified and 5 still warns, one parses a real 4.1.0 version string -- and the warning text no longer claims the schema came from 3.x. Generated `.tool-versions` linked to the Spin installer without naming a version. It now states the 4.1 floor and why the failure is easy to miss. Existing generated applications pin `fastly`, `spin-sdk` and `validator` themselves, and the adapters hand provider-owned types straight through, so updating EdgeZero alone resolves two majors of the same crate and fails with a trait error naming only the application's own type. `docs/guide/dependency-majors-migration.md` lists what to move together, the Spin runtime requirement, and how to verify -- the CI fixture hit exactly this and the error named nothing useful.
prk-Jr
left a comment
There was a problem hiding this comment.
PR Review
Summary
A three-minor toolchain jump, seven dependency majors, two lockfiles and an edition migration, and every non-mechanical decision in it is justified at the site rather than in the description alone — the brotli hold with a byte count, the redb floor with the assertion it preserves, the inline_modules allow with the CLAUDE.md rule it conflicts with. The AccessGuard bug the redb bump surfaced is a real latent defect, correctly diagnosed as an upstream assertion rather than an upstream regression, and correctly fixed. All 23 checks pass on 9ba4eae.
Two things to resolve before merge, both about the Spin 4.1 floor and its gates rather than about the upgrade itself.
😃 Praise
- The
spin-sdk6 → 7 write-up is the most useful part of this PR for whoever hits this next:cargo checkand the wasmtime contract suite both pass on a component that cannot boot, and only the smoke test catches it. That is a non-obvious property of the toolchain worth having written down. - Verifying each CLI pin against the workload CI actually runs it for — Viceroy against the wasip1 suites, wasmtime against the wasip2 contract suite — rather than against
--version. - Checking
cargo tree -ibefore takingbrotli9 and then not taking it, with the 40 KB measured. A major that does not consolidate is a cost with no benefit, and the reasoning is now pinned where the next person will read it.
Findings
Blocking
- 🔧
scripts/smoke_test_kv.shhas no Spin 4.1 guard, and its install URL is stale (scripts/smoke_test_kv.sh:62-67) — see below. - ❓
spin-sdkis the one shared pin with no drift gate (crates/edgezero-cli/src/generator.rs:886) — inline.
Non-blocking
- ♻️ Redundant drift test (
crates/edgezero-cli/src/generator.rs:939) — subsumed by the one at line 916; inline. - 🤔
pin_inandextract_versionaccept different grammars (scripts/check_shared_dep_pins.sh:33) — fails closed, but misdiagnoses a reformatted pin as a missing one; inline. - ⛏ The deploy smoke fixture is the last edition-2021 island (
.github/actions/deploy-core/tests/make-smoke-fixture.sh:41) — see below. - 📝
simple_logger4 → 5 is a seventh major (examples/app-demo/Cargo.toml:44) — inline.
🔧 scripts/smoke_test_kv.sh:62-67 — the 4.1 guard is missing from the script the docs now point at
The Spin 4.1 pre-check landed in scripts/smoke_test_config_key_override.sh:451-467, and it is correct — printf "%d%02d" orders 4.0.9 (400) below 4.1.0 (401) and handles 4.10 and 10.1 properly, and the empty-version fall-through is guarded by the -n test.
But this PR also rewrote .tool-versions:9-10 to name a different script as the one operators should run:
Install matching CLI manually (https://spinframework.dev/install) before running
smoke_test_kv.sh spin; otherwise setSKIP_SPIN=1.
and the test plan cites scripts/smoke_test_kv.sh spin — 8/8 — as the end-to-end verification for the SDK bump. smoke_test_kv.sh only checks command -v spin, so an operator on Spin 4.0 following that line gets:
Error: component imports instance `wasi:http/types@0.3.0`, but a
matching implementation was not found in the linker
which is exactly the error the guard exists to prevent, from the exact path the guard was documented against. The PR's own framing — "only the smoke test catches this class" — is what makes the gap matter: the smoke test that catches it is the other one.
Its install URL is also now wrong in a way that points operators at the problem:
echo "Spin CLI is required. Install from https://developer.fermyon.com/spin/v3/install" >&2A v3 install page, for a 4.1 floor.
Fix: factor the pre-check into a helper both scripts source (the guard is ~12 lines and is going to drift if copied), and update the URL to https://spinframework.dev/install to match .tool-versions. smoke_test_config.sh and smoke_test_secrets.sh are worth a look for the same pattern.
⛏ .github/actions/deploy-core/tests/make-smoke-fixture.sh:41 — still edition = "2021"
The PR moves examples/app-demo to 2024 so the reference app stops being the odd one out against the workspace and the scaffold templates. This generated fixture is then the remaining 2021 island, and it takes edgezero-core and edgezero-cli by path — so it is compiled against 2024 crates from a 2021 crate. That works, and nothing here depends on the edition, so this is not a correctness point. But the fixture already turned up once in this PR as the fourth place a pin hides; leaving it on the old edition keeps it slightly out of step with everything it links against.
CI Status
- fmt: PASS
- clippy (host + all 4 per-target matrix combinations): PASS
- tests: PASS
- 23/23 checks green on
9ba4eae
Not run locally: this repo needs 1.98.1 via asdf and my box has rustup at 1.95, so I reviewed the diff statically rather than reproducing the gates. The note in the PR description about a clippy run exiting 0 on No version is set for command cargo is a good catch and is why I did not report a local run as a pass.
Verified against the tree
Every dependency claim in the description checks out against the lockfiles:
| Claim | Result |
|---|---|
base16ct 1.0.0, no transitive deps |
✅ lockfile entry has no dependencies block |
brotli consolidated to one copy |
✅ single brotli 8.0.4 |
sha2 three copies → two |
✅ 0.9.9 (via fastly) and 0.11.0 only |
demo anyhow → 1.0.104 |
✅ |
demo on proc-macro-error3 |
✅ proc-macro-error3 / proc-macro-error-attr3 3.1.1 |
prepare still hoisted out of the batch loop |
✅ push_sqlite.rs:251 — prepared once, reused across entries |
check_shared_dep_pins.sh passes and anchors correctly |
✅ ran it: all validator pins agree (0.21); the contains("validator = { version =") line at generator.rs:1209 correctly does not shadow the real seed |
base16ct::lower::encode_string is output-identical to format!("{:x}", ..) here — both lowercase, both zero-padded, and the input is a fixed 32-byte digest, so there is no width or truncation difference. The canonical_form_pin_v1 pinned-hash test passing is the load-bearing evidence, since these digests are persisted in config envelopes.
Review follow-ups. The 4.1 pre-check landed in `smoke_test_config_key_override.sh`, but `.tool-versions` and the test plan both name `smoke_test_kv.sh` as the script to run, and that one only checked `command -v spin` and linked a v3 install page. An operator following those instructions on Spin 4.0 reached the WIT linker error the check exists to prevent, from the path it was documented against. `smoke_test_config.sh` and `smoke_test_secrets.sh` had the same gap. The check is now `scripts/spin_version_guard.sh`, sourced by all four rather than copied into each. Verified with stub CLIs across the boundaries the encoding has to get right: 3.7.1, 4.0.2 and 4.0.9 are refused, 4.1.0, 4.10.0 and 10.1.0 pass. `smoke_test_kv.sh spin` refuses 4.0.2 with an actionable message and still reports 8/8 on 4.1.0. `spin-sdk` was the one shared pin with no drift gate. It is spelled `~7.0` in the workspace and `7` in both the demo and the scaffold seed, so an exact comparison would have failed on spelling; comparing the major closes it. That is worth having: a `validator` mismatch fails at compile time, while a `spin-sdk` mismatch compiles, passes the wasmtime contract suite, and dies at `spin up`. Also from review: `pin_in` required `version` to be the first key and exactly single spaces, so a reordered or differently spaced pin was reported as missing rather than as itself -- it now accepts the same grammar as `extract_version`, which is what the comment above it already claimed. The `rust` drift test is deleted as subsumed by the one that resolves every pinned tool from `.tool-versions`. The deploy smoke fixture moves to edition 2024, leaving no 2021 crate linking against 2024 ones.
|
Thanks — both blocking findings were real gaps, and the first one is the more embarrassing of the two. 🔧
|
| CLI | result |
|---|---|
| 3.7.1 | refused |
| 4.0.2 | refused |
| 4.0.9 | refused |
| 4.1.0 | allowed |
| 4.10.0 | allowed |
| 10.1.0 | allowed |
Then end to end on the real script: smoke_test_kv.sh spin on 4.0.2 now prints Spin CLI 4.0.2 is too old: spin-sdk 7 needs >= 4.1 for wasi:http/types@0.3.0 and exits, and on 4.1.0 still reports 8/8. No developer.fermyon.com/spin/v3 URLs remain, and command -v spin now appears only inside the helper.
⛏ Edition-2021 fixture
Also taken. make-smoke-fixture.sh emits edition = "2024", and I regenerated the fixture and compiled it to confirm — no 2021 crate linking against 2024 ones.
Not changed
The simple_logger note needs no code change but the majors table should say seven; updating the PR body.
All seven local gates pass on cfcc6cd5: fmt, clippy (host + wasm), workspace tests, demo tests, the pin gate, and the deploy-action suite.
Also — your note about not reporting a local run as a pass because of the asdf/No version is set trap is the right instinct. That false pass cost me real time earlier in this branch.
prk-Jr
left a comment
There was a problem hiding this comment.
PR Review
Summary
Approving. A large but unusually well-argued upgrade: Rust 1.95.0 → 1.98.1, six dependency majors taken and one (brotli) deliberately held with a measured reason, plus a latent AccessGuard bug in the axum KV store that redb 4.2's new assertion exposed. The dependency graph comes out cleaner than it went in — hashbrown, windows-targets, ahash, zerocopy and the RC wasip3 worlds all de-duplicate — and the new check_shared_dep_pins.sh gate plus the two generator drift tests turn a previously deploy-time failure mode into a lint.
Nothing here blocks. Three items below are worth a follow-up rather than a re-review: two are small fixes, and one is a question I'd like answered in writing because it's the single failure mode this PR cannot self-detect.
😃 Praise
- The redb 4.2 finding is the highlight.
get_byteswas holding anAccessGuardborrowed from the table acrossdrop(table); drop(read_txn)in the lazy-expiry path — silently wrong on 4.1.x. The obvious read of the new panic (assertion failed: !self.read_page_ref_counts…) is "upstream regression, pin back to 4.1"; instead the floor was raised to 4.2.0 so the assertion can't be resolved away, with the reason recorded at the pin (Cargo.toml:69-73). - brotli is the discipline highlight. Held at 8 with a measured justification — 7,169,574 vs 7,129,410 bytes on the demo Spin release build — rather than bumped for tidiness, with both the mechanism and the exit condition recorded at
Cargo.toml:40-45. check_shared_dep_pins.sh+ the two generator drift tests convert a failure that previously only surfaced asE0277: the trait bound MyAppConfig: validator::traits::Validate is not satisfiedin a deploy smoke job into a named lint. The comment explaining why cargo cannot do this — four pins, three in workspaces that share no lockfile, one in a manifest that does not exist until CI generates it — is at exactly the right altitude. The anchored^[[:space:]]*(\\?")?validator[[:space:]]*=match correctly skips"validator".to_owned(),and catches the escaped-literal seed form.- "
cargo checkand the wasmtime contract suite both pass on a build that cannot boot." That sentence, plus "a clean hostcargo clippy --all-featuresdoes not imply the wasm targets are clean," are the two things a reader most needs from this PR, and both are in the description. Same for the asdf note about clippy exiting 0 onNo version is set for command cargo. - Edition 2024's
env::set_varunsoundness was handled by centralising, not by spreadingunsafe.app-demo-core/src/config.rs:188-189now takesenv_lock()+EnvOverride, routing through the workspace's single#[expect(unsafe_code)]module, and the guard restores state even when an assertion panics — strictly better than theenv::remove_varbefore the asserts it replaced.
Findings
Worth fixing before or shortly after merge
-
🔧 Stale
~6.0SDK pin in the module that documents the pin guard —crates/edgezero-adapter-spin/src/cli/push_sqlite.rs:26and:37.Cargo.toml:99is nowspin-sdk = "~7.0". This module's whole purpose is documenting the three-layer guard around Spin's internal SQLite schema, and layer 2 now names a pin that no longer exists — as does the "what the guards CANNOT catch" paragraph. The reference sweep updated the equivalent line indocs/guide/adapters/spin.md:205, so this reads as a miss rather than a policy.~6.0→~7.0in both lines. (Commented in the body rather than inline because both lines are outside the diff.) -
🔧
spin_meets_floorfails open on an unparseable version —scripts/spin_version_guard.sh:36-43, inline below. Dev-only blast radius, two-line fix. -
❓ Fastly CLI 15.1.0 → 16.0.0: flags were verified, but were the
--jsonpayload schemas? The adapter does not just shell out —edgezero-adapter-fastly/src/cli.rsparses CLI JSON output shapes in roughly eight places, and its own diagnostics name this as the expected failure mode: "output is neither a bare array nor anitemsenvelope …; fastly CLI may have changed its schema" (:3526), "entry has no stringitem_key/item_valuefields" (:3546), and "The fastly CLI may have changed its JSON schema in a recent version … Workaround: pin to a known-compatible fastly CLI version" (:4057,:3641,:5218).The description's verification line is "Every subcommand the adapter shells out to still exists with the same flags." That covers the command and flag surface, not payload shape — and payload shape is what those parsers and error strings are about. Nothing in CI exercises a real
fastlyCLI (correctly, per the no-credentials rule), sofastly-installer-check.ymlproves only that the pinned archive installs and that the two pin sources agree.Concretely: were
config-store list --json,config-store-entry list --json,config-store-entry describe --jsonandservice-version list --jsoncompared between 15.1.0 and 16.0.0, even manually against a scratch service? Unlike Spin, there's noVERIFIED_FASTLY_MAJOR_RANGEanalogue here, so a schema change surfaces first as a red deploy in a consumer's repo. A one-line note in the description recording that comparison would close it.
Non-blocking
- 🤔
VERIFIED_SPIN_MAJOR_RANGE=[2,3,4]still blesses majors that cannot boot a spin-sdk 7 component, while four other files now state a hard 4.1 floor (push_sqlite.rs:60, inline). - 🤔
prepare_cached→prepareis explained in the description but not at the call site (push_sqlite.rs:252, inline). - ♻️ Migration guide's Toolchain section mentions only Rust, but a scaffolded app's
.tool-versionsalso pins fastly/viceroy/nodejs andinstall-fastly.shfails closed on a mismatch (docs/guide/dependency-majors-migration.md:55-59, inline). - ⛏ One-line
whereinsideconfig_store_contract_tests!, where the two sibling macros are wrapped (crates/edgezero-core/src/config_store.rs:45, inline). - ⛏ List continuation un-indented in the new Spin prerequisites bullet (
docs/guide/adapters/spin.md:13-15, inline). - ⛏
# shellcheck source=resolves relative to the sourcing file, so it looks forscripts/scripts/…(four smoke scripts, line 5 — grouped inline on one). - 🌱 Seed drift test covers 4 of 12 seeds —
simple_loggeris the one this PR actually had to move (crates/edgezero-cli/src/generator.rs:886, inline). - 🌱
include_str!reaches outside the package in two tests; harmless forcargo packagesince both arecfg(test), but would breakcargo teston a vendored copy (generator.rs:884,:954, inline). - 🤔 48-site
inline_trait_boundsrewrite rides along with the dependency change, whileinline_modules(77 sites) andpub_with_shorthand(44 sites) were allowed with reasons instead. The rationale for each choice is sound and documented; the observation is only that a large mechanical churn now shares a diff with the toolchain and dependency work. Noting that-D warningson the new clippy meant something had to happen in the same PR, so this is a preference, not a defect.
Dependency review
Both lockfiles diffed at package level (root: 7 added / 7 removed / ~150 changed; demo: 9 / 9 / ~145). Nothing yanked or unvetted.
- New direct dep
base16ct1.0.0 (unconditional inedgezero-coreandedgezero-adapter-fastly) — right call:sha20.11'sArraydroppedLowerHex, soformat!("{:x}", …)no longer compiles, andbase16ctis RustCrypto's own encoder —no_std, no dependencies of its own, WASM-clean. Unconditional placement matchessha2, also unconditional in both. Digest output verified unchanged by the retainedcanonical_form_pin_v1pinned-hash test, which is what actually matters since these digests are persisted in config envelopes. - RustCrypto 0.11 line pulled in wholesale —
digest0.10→0.11.3,crypto-common0.1→0.2.2,block-buffer0.10→0.12.1, newhybrid-array0.4.14 andconst-oid0.10.2. Allno_std, all WASM-safe. libsqlite3-sys0.30.1 → 0.38.2 (via rusqlite 0.40) is the largest single jump, but host-only and CLI-only:dep:rusqlitesits behind the spin adapter'sclifeature (Cargo.toml:16,:44,optional = true), so no WASM target ever sees bundled SQLite. Written-file compatibility is covered by the byte-equality schema test pluswrite_batch_round_trips_through_spin_schema.- New transitives, all benign —
bstr1.13.1,zlib-rs0.6.7 (pure Rust),proc-macro-error33.1.1 replacingproc-macro-error2(which is the fix for the "rejected by a future version of Rust" warning),rand_pcg0.10.2 (demo only). - Net de-duplication —
ahash,hashlink,lazy_static,zerocopygone entirely;hashbrown3 copies → 1;windows-targets/windows_*collapse to a single 0.52.6;getrandom0.3.4 dropped;wasm-encoder/wasmparser/wit-*each 2 → 1. wasip30.4.0/0.6.0-rc → 0.7.1+wasi-0.3.0 is consistent with the spin-sdk 7 story and the claimed Spin 4.1 floor — the RC WIT worlds are gone and the released 0.3.0 interface is what's imported.docs/package-lock.json—package.jsonunchanged, so a pure in-rangenpm update. Every addition isdev: true(cacheable/hashery/qifiedunder eslint'sflat-cache) plus oneoptional,os: ["linux"]rollup binary. Nothing reaches the built site.
📌 Out of scope
- No schema-shape regression fixture for the Fastly CLI's
--jsonpayloads, the wayvendored_schema_matches_upstream_byte_for_byteexists for Spin's SQLite schema. A recorded-fixture test over the four parsed payloads would turn the ❓ above into a CI gate for every future CLI bump — worth a separate issue. docs/superpowers/plans/**keeps ~15 references to Rust 1.95.0 / edition 2021 /spin-sdk ~6.0/ viceroy 0.17.0. The description declares these out of scope as dated design records and they'resrcExcluded from the published site — agreed, noted so a later grep sweep doesn't re-litigate it.
CI Status
Taken from the GitHub checks rather than re-run locally.
- fmt: PASS
- clippy: PASS
- tests: PASS
| # MAJOR.MINOR only; a pre-release suffix on the patch is ignored. | ||
| encoded=$(printf '%s' "$version" | awk -F'.' '{printf "%d%02d", $1, $2}') | ||
|
|
||
| if [ -n "$encoded" ] && [ "$encoded" -lt "$SPIN_MIN_ENCODED" ]; then |
There was a problem hiding this comment.
🔧 Fails open on an unparseable version.
If spin --version produces no parseable output — empty stdout, a non-zero exit swallowed by 2>/dev/null, or a future format change where the version is not field 2 — then version is empty, awk gets no records, the action block never runs, and encoded is "". The [ -n "$encoded" ] guard short-circuits and the function returns 0, reporting "present and new enough" for a CLI whose version is unknown. The smoke test then proceeds to spin up and dies with exactly the opaque wasi:http/types@0.3.0 … not found in the linker error this guard exists to pre-empt.
That contradicts the helper's own contract at lines 20-21 ("Returns 0 when the CLI is present and new enough, 1 otherwise"). Garbage-but-non-empty output does fail correctly (%d of unknown → 0 → below the floor); only the empty case escapes.
Blast radius is dev-only, but the logic was just extracted into a shared helper that four scripts now depend on, so this is the moment to close it:
if [ -z "$encoded" ]; then
echo "Could not parse a version from \`spin --version\` (got: '${version}'); need >= ${SPIN_MIN_DISPLAY}." >&2
return 1
fi
if [ "$encoded" -lt "$SPIN_MIN_ENCODED" ]; then| /// verification to be repeated and this constant updated. Until then, | ||
| /// an operator running a Spin CLI outside this range gets a | ||
| /// `log::warn!` on first SQLite-direct push. | ||
| const VERIFIED_SPIN_MAJOR_RANGE: &[u32] = &[2, 3, 4]; |
There was a problem hiding this comment.
🤔 This range still blesses majors that cannot boot the component.
.tool-versions, docs/guide/adapters/spin.md:10, scripts/spin_version_guard.sh:25 and the new migration guide all now state Spin 4.1+ as a hard floor. An operator on Spin 3.x gets no warning from verify_spin_runtime_compat, even though their runtime definitively cannot run a spin-sdk 7 component.
I follow the intent — the range is scoped to the SQLite schema, a CLI-side concern that still works on 3.x, and config push is legitimately usable there. But the two facts now read as contradictory within the same repo. Either narrow to &[4], or add a sentence here distinguishing "schema verified for" from "runtime supported".
verified_range_covers_the_spin_majors_the_sdk_supports asserts contains(&4) and !contains(&5) and says nothing about 2/3, so narrowing wouldn't break it.
| { | ||
| let mut statement = transaction | ||
| .prepare_cached(SPIN_KV_SET) | ||
| .prepare(SPIN_KV_SET) |
There was a problem hiding this comment.
🤔 The reason for prepare over prepare_cached lives only in the PR body.
The description explains it (rusqlite 0.40 dropped prepare_cached from Transaction, and prepare still prepares once and reuses across the batch — correct, the statement is hoisted outside the for loop). Every other constraint in this file is recorded inline, and this is the sort of line a future reader "optimises" back:
// rusqlite 0.40 dropped `prepare_cached` from `Transaction`; `prepare`
// still prepares once and reuses the statement across the batch below.| Install Spin 4.1 or newer ([install](https://spinframework.dev/install)) | ||
| before upgrading the SDK. `cargo check` cannot detect this. | ||
|
|
||
| ## Toolchain |
There was a problem hiding this comment.
♻️ The Toolchain section covers Rust but not the CLI pins.
A scaffolded app's .tool-versions (see generator.rs:657-666) also pins fastly, viceroy and nodejs, and this PR moves all three (16.0.0 / 0.21.0 / 24.20.0). For Fastly apps that's operational, not cosmetic: install-fastly.sh fails closed when .tool-versions and versions.json disagree, so an app that moves the Rust pin and not the Fastly pin gets a red deploy action. Worth a short second paragraph or a second table row.
| use $crate::config_store::ConfigStore; | ||
|
|
||
| fn run<Fut: ::std::future::Future>(future: Fut) -> Fut::Output { | ||
| fn run<Fut>(future: Fut) -> Fut::Output where Fut: ::std::future::Future { |
There was a problem hiding this comment.
⛏ This where clause is on one line, while the equivalents in key_value_store.rs:95 and secret_store.rs:50 are wrapped. rustfmt doesn't reach inside macro_rules! bodies, so it slipped through — it's the only site in the 48-site sweep that reads differently.
| - Spin CLI **4.1 or newer** ([install](https://spinframework.dev/install)). | ||
| The workspace's `spin-sdk` imports `wasi:http/types@0.3.0`, which Spin | ||
| 4.0.x does not provide — on an older runtime the component builds and | ||
| its tests pass, then `spin up` fails with `component imports instance |
There was a problem hiding this comment.
⛏ Lines 14-15 start at column 0 inside a bullet whose other lines are indented 2. It renders via lazy continuation and prettier accepts it, but it reads as a broken list in the source. Indent both by 2, or lift the error text into a fenced block below the bullet — the way the migration guide and spin_version_guard.sh both do.
| set -euo pipefail | ||
|
|
||
| # Spin CLI floor check, shared with the other smoke tests. | ||
| # shellcheck source=scripts/spin_version_guard.sh |
There was a problem hiding this comment.
⛏ # shellcheck source= path won't resolve (same line in smoke_test_config_key_override.sh, smoke_test_kv.sh and smoke_test_secrets.sh).
shellcheck resolves source= relative to the sourcing file's directory, so from inside scripts/ this looks for scripts/scripts/spin_version_guard.sh. Harmless today — the shellcheck job in deploy-action.yml only covers .github/actions/** — so it's dead weight rather than a break. Use # shellcheck source=./spin_version_guard.sh or # shellcheck source-path=SCRIPTDIR.
| // shared crates are checked against the root manifest here. | ||
| let root_manifest = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/../../Cargo.toml")); | ||
| let seeds = seed_workspace_dependencies(); | ||
| for crate_name in ["validator", "axum", "fastly", "worker"] { |
There was a problem hiding this comment.
🌱 Seed drift test covers 4 of 12 seeds.
["validator", "axum", "fastly", "worker"] plus the spin-sdk major. simple_logger, clap, tokio, serde, log, tracing and once_cell are unchecked. simple_logger is the interesting one — this PR moved the demo from 4 to 5 to catch up with the root workspace, i.e. exactly the drift class this test exists to prevent, and it's a one-word addition to the array.
| // resolves. If the two majors drift apart the generated app fails to | ||
| // compile with a confusing "trait bound not satisfied" error, so the | ||
| // shared crates are checked against the root manifest here. | ||
| let root_manifest = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/../../Cargo.toml")); |
There was a problem hiding this comment.
🌱 include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/../../Cargo.toml")) reaches outside the package (same pattern at :954 for .tool-versions).
Fine in-tree, and no problem for cargo package — both are inside #[cfg(test)], so the publish verify build never evaluates them. But cargo test on a vendored or published copy of edgezero-cli would fail to compile. Recording it rather than asking for a change: if this crate is ever published, these two need a #[cfg] escape or an env-var-driven skip.
|
|
||
| if let Some(entry) = table | ||
| // redb asserts that no read reference outlives its transaction, so the | ||
| // `AccessGuard` borrow has to end before the drops below. |
There was a problem hiding this comment.
😃 The best thing in this PR.
get_bytes was holding an AccessGuard borrowed from the table across drop(table); drop(read_txn) in the lazy-expiry path — silently wrong on redb 4.1.x. The obvious read of the new panic (assertion failed: !self.read_page_ref_counts…) is "upstream regression, pin back to 4.1"; instead the floor was raised to 4.2.0 specifically so the assertion can't be resolved away, with the reason recorded at the pin (Cargo.toml:69-73).
The rewrite here is also the right shape: copy the value out inside the match, then handle the expired path with the read txn already dropped — and the TOCTOU re-check inside the write txn is preserved verbatim.
Summary
cargo updatealone cannot reach. Onlybrotliis held back, with the reason and the measurement recorded at the pin.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:similarvalidatorfastly,log-fastlyrusqliteprepare_cachedis no longer onTransaction; the one call site usesprepare, which still prepares once and reuses across the batchsimple_loggerredbAccessGuardfix above. The declared floor was4.1.0while resolving 4.2.0; 4.2 is the release whose assertion caught that bug, so the floor now matches the behaviour the code relies onspin-sdkbrotlistays at 8 — the one major not taken.async-compression0.4.44 resolves brotli 8 throughcompression-codecs0.4.39 (still the current release), so taking 9 on the direct dependency does not replace that copy — it adds a second complete allocator/decompressor stack that every adapter graph compiles. Measured on the demo Spin release build: 7,169,574 bytes with both stacks, 7,129,410 with one — ~40 KB of wasm for no gain, since we call onlyDecompressorandCompressorWriter, unchanged across the two majors. Whenasync-compressionmoves to 9 the pin should follow and the copies collapse.spin-sdk6 → 7 needed a runtime bump, not a code change. 7.0 importswasi:http/types@0.3.0; Spin 4.0.x does not provide it, so the component compiles and passes its wasmtime-hosted contract tests and then fails to link atspin up:Spin 4.1.0 ships that interface. Verified end to end rather than by compiling —
scripts/smoke_test_kv.sh spinis 8/8 against a realspin up. The demo workspace and the scaffold seed pinspin-sdkseparately and move with it, and.tool-versionsnow names Spin 4.1 as the floor (it still said 3.7).Worth flagging for reviewers:
cargo checkand the wasmtime contract suite both pass on a build that cannot boot. Only the smoke test catches this class.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.Review follow-ups
Three issues found reviewing the branch, all fixed in the final commit:
validator_derive0.20.0 →proc-macro-error22.0.1 — the crate behind therejected by a future version of Rustwarning that printed on every demo build. The root lockfile had already moved to 0.20.1 →proc-macro-error3; the demo now matches and the warning is gone.anyhow1.0.102, which has an unsoundness advisory. Both lockfiles are now refreshed and the demo is on 1.0.104.check_shared_dep_pins.shread the pin with an unanchored match and took the first hit, so a commented-out# validator = "0.19"above the real line became the expected version and the gate reported three violations against three correct files. The match is now anchored to an assignment at line start, and a miss normalises to empty via|| trueso the missing-pin diagnostics run instead ofset -eaborting at the command substitution.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 upon Spin 4.1.0 with spin-sdk 7Checklist
{id}syntax (not:id)edgezero_core(nothttpcrate)KvRegistry/ConfigRegistry/SecretRegistry(not the legacy single-handle setters)check_shared_dep_pins.shwired into CI