EdgeZero CLI Extensions: extensible CLI, multi-store manifest, auth/provision/config#269
EdgeZero CLI Extensions: extensible CLI, multi-store manifest, auth/provision/config#269aram356 wants to merge 241 commits into
Conversation
Turns on `pedantic` (warn) and `restriction` (deny) workspace-wide and adds
`[lints] workspace = true` to every crate so the policy actually applies.
Captures a baseline allow-list in `Cargo.toml`, organized by category
(Documentation, Style/formatting, Defensive coding, API design, Imports/paths,
Output/diagnostics, Tests, Attributes) with per-lint counts and rationales —
each entry is a TODO unless explicitly marked intentional.
Defensive-coding pass:
- New `clippy.toml` with `allow-{unwrap,expect,panic,indexing-slicing}-in-tests`
so test code keeps its conventional idioms; production code is denied.
- Production unwraps factored out: `current_dir()`/`init_logger()` now
propagate via `?`; `writeln!` to a `String` rewritten as `push_str(&format!)`
so there's no `Result` to discard; bundled-template registration and other
genuine compile-time invariants use `.expect("...")` as documented assertions.
- Other small wins: `inefficient_to_string` fixed, `match_same_arms` collapsed,
`manual_assert` swapped, `cast_lossless`+truncation replaced with bound-checked
`u16::try_from` in adapter-axum CLI, `unreachable!()` in `#[action]` macro
replaced with a proper `syn::Error::compile_error`.
Lints kept allowed in the workspace are annotated with `(intentional)` where
they conflict with idiomatic Rust (`implicit_return`, `question_mark_used`,
`pattern_type_mismatch`, `default_numeric_fallback`, `arithmetic_side_effects`,
`as_conversions`, `string_slice`) or have no per-test config option
(`assertions_on_result_states`).
`cargo clippy --workspace --all-targets --all-features -- -D warnings`,
`cargo fmt`, and `cargo test --workspace --all-targets` all pass.
Drives the API-design lint group from 18 allows down to 8 (kept as intentional
with rationale comments in `Cargo.toml`).
Factored out:
- `return_self_not_must_use` (18): added `#[must_use]` to all `RouterBuilder`
builder methods. Catches "I forgot to call `.build()`" bugs.
- `impl_trait_in_params` (26): converted `fn f(x: impl Into<String>)` →
explicit generics on `EdgeError::*`, `ConfigStoreError::*`, `RouteInfo::new`,
`InMemorySecretStore::new`, `AxumConfigStore::{new,from_env,from_lookup}`.
Makes turbofish callable.
- `rc_buffer` (4): `Arc<Vec<RouteInfo>>` → `Arc<[RouteInfo]>` in `RouterInner`
and the builder. Saves an indirection.
- `unnecessary_wraps` (4): `build_fastly_request` and `convert_response` no
longer wrap an always-Ok value in `Result`. Cleaner call sites.
- `mutex_atomic` (1): `Arc<Mutex<bool>>` → `Arc<AtomicBool>` in the
`middleware_fn` test.
- `ref_patterns` (11): `if let Some(ref x) = ...` → `if let Some(x) = &...`
across env-override `Drop` impls, router builder, response builder, body
matchers.
- `wildcard_enum_match_arm` (7): `args.rs` tests now use `let-else` instead of
catch-all wildcard match arms; `EdgeError::source` now lists each non-Internal
variant explicitly; `cli/build.rs` switched to `if let Value::Table(_) = ...`;
the one site that genuinely matches an external enum (`fastly::config_store::
LookupError`) keeps a localized `#[allow(..., reason = "external enum")]`.
- `clone_on_ref_ptr` (1): `store.clone()` → `Arc::clone(&store)` in the axum
service test (with explicit `Arc<dyn KvStore>` annotation so `Arc::clone`
picks the right type).
- `renamed_function_params` (4): renamed `request: Request` → `req: Request`
in `Service::call` impls to match the trait signature.
- `same_name_method` (2): `EdgeError::source` deliberately shadows
`std::error::Error::source` (typed `&AnyError` vs trait-object `&dyn Error`).
Documented at the call site with a `#[allow(..., reason = "...")]`.
Kept allowed (with `(intentional: ...)` comments in `Cargo.toml`):
- `exhaustive_structs` (108) and `exhaustive_enums` (18): blanket
`#[non_exhaustive]` would break user pattern matching and field-syntax
construction. Apply per-type only when genuinely planned.
- `must_use_candidate` (117): most flagged sites are getters returning
`&str`/`&Path` — ignoring is impossible, the lint adds noise.
- `missing_trait_methods` (20): relying on default trait methods is fine.
- `needless_pass_by_value` (16): most flagged sites are deliberate ownership
transfers — error transformers, proc-macro signatures, builders.
- `field_scoped_visibility_modifiers`, `partial_pub_fields`,
`trivially_copy_pass_by_ref`: deliberate API design choices.
Final clippy + workspace tests pass.
Following pushback that the prior passes were papering over lints rather
than addressing them, this commit revisits each lint that was previously
allowed with hand-wavy reasoning and either (a) factors it out for real,
(b) applies it selectively where the fix matters, or (c) replaces the
rationale with a per-site audit finding.
Real fixes:
- `Body::as_bytes` and `Body::into_bytes` no longer panic on streaming
bodies — they return `Option`. This eliminates two production panic
sites the previous pass left as `panic = "allow"`. The internal
`into_bytes_bounded` site is correctly gated by `is_stream()`; all
other callers are tests that *intentionally* assert the body is
buffered, now with `.expect("buffered")`.
- `assertions_on_result_states` is no longer allowed. All 13 sites
converted from `assert!(r.is_ok())` / `assert!(r.is_err())` to
`r.expect("...")` / `r.expect_err("...")` — these print the value or
error on failure instead of just `assertion failed: false`.
- `#[non_exhaustive]` applied to all 4 error enums (`EdgeError`,
`KvError`, `SecretError`, `ConfigStoreError`) and the 3 manifest
enums (`HttpMethod`, `BodyMode`, `LogLevel`) — this is the idiomatic
Rust pattern for error/config enums (see `std::io::ErrorKind`,
`serde::de::Error`). Also applied to 19 deserialize-only manifest
structs (`Manifest*`, `ResolvedEnvironment*`-where-not-constructed-
externally).
- `needless_pass_by_value` real fix in `run_app_with_stores`:
`FastlyLogging` and `StoreRequirements` are now passed by reference
since the function only reads from them.
Lints kept allowed but with audited per-site rationales (replacing the
previous one-line hand-waves):
- `pattern_type_mismatch`: every flagged site uses Rust 2018
match-ergonomics. The "fix" reverts to manual `ref` patterns or
explicit `&Variant(...)` arms, both worse.
- `arithmetic_side_effects`: every site is bounded by domain invariants
(TTL+now, path component counts, byte offsets after `len()` checks).
- `as_conversions`: dominated by trait-object coercions (`Arc::new(x)
as BoxMiddleware`) which cannot be expressed as `From`/`Into` in
stable Rust.
- `string_slice`: every flagged site indexes ASCII-only data (env var
names, header names, `matchit` path components).
- `expect_used`: 62 production sites audited — bundled-template
registration, AsyncRead-contract slice access, lock-poisoning
unrecoverable, build-script panics. None benefit from `?`
propagation.
- `panic`: route-registration `unwrap_or_else(|err| panic!(...))` and
proc-macro expansion failures. Both build/setup-time programmer
errors, not runtime conditions.
- `cast_possible_truncation` / `cast_sign_loss`: narrowing/sign casts
always preceded by range checks.
- `exhaustive_structs` / `exhaustive_enums`: applied selectively above;
remaining sites are tuple-struct extractors users *destructure*,
unit structs, externally-constructed scaffold blueprints, request-
context types used in integration tests, and small enums (`Body`,
`AdapterAction`) where adding `#[non_exhaustive]` would force 12+
adapter sites to add never-firing wildcard arms.
Workspace clippy + tests still pass with `-D warnings`.
Removes 22 mechanical-fix allow entries from `Cargo.toml` after fixing the
underlying call sites:
Auto-fixed (`cargo clippy --fix` + manual cleanup):
- `uninlined_format_args` (180), `redundant_closure_for_method_calls` (25),
`map_unwrap_or` (29), `explicit_iter_loop` (14),
`unseparated_literal_suffix` (24, separated form chosen),
`implicit_clone` (2), `pathbuf_init_then_push` (3), `string_add` (3),
`unreadable_literal` (4), `manual_let_else` (2), `else_if_without_else`
(2 — the Fastly-vs-other-adapter logging branch refactored to a
pre-computed `Option<endpoint>`), `return_and_then` (2), `ip_constant`
(2), `manual_string_new` (1), `redundant_type_annotations` (1),
`needless_raw_strings` (1), `needless_raw_string_hashes` (1),
`elidable_lifetime_names` (2), `redundant_test_prefix` (1),
`if_then_some_else_none` (6), `deref_by_slicing` (5), `shadow_same` (4),
`match_wildcard_for_single_variants` (5), `pub_with_shorthand` (30),
`decimal_literal_representation` (1).
Real fixes (manual):
- `key_value_store.rs`: replaced bare scoping blocks `{ ...?; }` with
explicit `drop(table)` so neither `semicolon_inside_block` nor
`semicolon_outside_block` fires (the lint pair is mutually exclusive
and one always fires). Same treatment for `decompress.rs` and
`proxy.rs` brotli-test compressor scopes.
- `middleware.rs`: collapsed the `Mutex` lock+await pattern into a
single `self.log.lock().unwrap().push(...)` statement so the lock
guard drops immediately (was previously triggering
`await_holding_lock` after I removed the scoping block).
- `dev_server.rs`: `let service = service` (shadow_same) refactored
into a `let service = { mut service = ...; ...; service }` block
expression that yields the configured value.
- `response.rs`: dropped redundant `let stream = stream` shadow.
- `request.rs`: renamed `test_is_json_content_type` →
`json_content_type_detection` (the redundant `test_` prefix).
- `proxy.rs` test panics: `_ => panic!(...)` → `Body::Stream(_) =>
panic!(...)` so the match stays exhaustive when `Body` grows.
- `cli.rs`: `0xFFFF` instead of `65535` for the u16-MAX boundary.
- `dev_server.rs::stable_store_name_hash`: split FNV-1a magic numbers
with `_` separators.
The Style section in `Cargo.toml` is rewritten as a tight allow-list
(no narrative, no historical commit log inside the manifest). Each
remaining entry has a one-line rationale grouped by category:
- Idiomatic Rust (8 lints): `implicit_return`, `min_ident_chars`,
`single_call_fn`, `single_char_lifetime_names`, `pub_use`,
`str_to_string`, `question_mark_used` (was duplicated; consolidated
in Defensive section).
- Mutually-exclusive pairs we picked one side of: `separated_literal_suffix`,
`pub_with_shorthand`.
- Held-by-choice (5 lints): `format_push_string`, `shadow_reuse`,
`shadow_unrelated`, `similar_names`, `non_ascii_literal`,
`too_many_lines`, `arbitrary_source_item_ordering`,
`module_name_repetitions`.
Allow-list went from ~80 entries to 57 across all categories.
`cargo clippy --workspace --all-targets --all-features -- -D warnings`
and `cargo test --workspace --all-targets` both pass.
`#[action]` requires the user-written fn to be `async fn` because the generated outer fn `.await`s it. When a handler body has no awaits of its own, `clippy::unused_async` fires on the user's source — but the user has no choice; the macro forces `async`. Inject the allow into the inner fn's attribute list inside the macro expansion so handler authors don't have to know about the lint.
Imports/paths track:
- `non_std_lazy_statics` (6 sites): `once_cell::Lazy` → `std::sync::LazyLock`
in `crates/edgezero-adapter/src/{registry,scaffold}.rs`. Drops `once_cell`
from `crates/edgezero-adapter/Cargo.toml`. (Workspace dep stays — example
app still uses it.)
- `unused_trait_names` (37 sites): `use Foo;` → `use Foo as _;` for traits
imported only for their methods (`StreamExt`, `Write`, `Read`, `Hooks`,
`IntoHandler`, `Spanned`, etc.) across both library and proc-macro crates.
- `iter_over_hash_type` (1 site): the only flagged production iteration is
in `RouterInner::dispatch` (collecting allowed methods for a 405 response).
Refactored from a `for ... { allowed.insert(...) }` loop into
`.iter().filter().map().collect::<HashSet<_>>()`. The result is a `HashSet`
whose order doesn't matter (`EdgeError::method_not_allowed` sorts on render).
Attributes track:
- `allow_attributes` (3 sites): `#[allow(...)]` → `#[expect(..., reason)]` on
the genuine deliberate-shadowing/wildcard-match-arm sites in
`error.rs::EdgeError::source` and `config_store.rs::map_lookup_error`. The
CLI build script (`build.rs`) now emits `#[expect(unused_imports, reason)]`
on every generated `pub(crate) use` re-export.
- `allow_attributes_without_reason` (5 sites): every existing `#[allow(...)]`
now has a `, reason = "..."` and (where stable-`expect` applies) is migrated
to `#[expect(...)]`. Sites: `cli_support.rs` and `decompress.rs` top-of-file
`#![expect(dead_code, ...)]`; the four test-only `Deserialize` field structs
in `context.rs` and `params.rs`; the macro's `manifest_definitions` shim;
the two fastly `deprecated` re-exports.
Also kept allowed (real audits in `Cargo.toml` rationales):
- `absolute_paths` (200+ sites): one-shot `std::env::var()` / `std::fmt::Display`
uses; adding `use` statements wouldn't improve readability for single-use.
- `std_instead_of_alloc` / `std_instead_of_core`: not targeting `no_std`.
- `tests_outside_test_module`: lint matches plain `#[cfg(test)] mod tests`
only — doesn't recognize `#[cfg(all(test, feature = "..."))]` or
integration-test files in `tests/`.
- `print_stderr` / `print_stdout`: kept in CLI top-level error reporters and
status output (`[edgezero] creating project at ...`).
Allow-list now at 51 entries.
…c / doc_markdown / missing_fields_in_debug Adds public-API docs across every flagged site: - `missing_panics_doc` (28 sites): added `# Panics` sections describing each panic condition. Most are documented invariants (lock poisoning, AsyncRead-contract slice access, builder pre-validated headers); a few are caller-controlled (`enable_route_listing_at` asserts on path shape, `RouterBuilder::build` panics on duplicate route, `load_from_str` panics on invalid embedded TOML — the docs note safer alternatives). - `missing_errors_doc` (62 unique pub fns, 124 lints with re-exports): added `# Errors` sections describing the concrete error variants returned. Dispatched via batch script with per-fn descriptions covering every site (KV / secret / config-store / manifest / proxy / extractor / body / responder / middleware / adapter dispatch APIs). - `missing_fields_in_debug` (2 unique sites — 4 with re-exports): `ProxyRequest`/`ProxyResponse` `Debug` impls now use `finish_non_exhaustive()` to acknowledge the deliberately-skipped `body` and `extensions` fields. - `doc_markdown` (17 sites): backticked `EdgeZero`, `SystemTime`, `Axum`, `SecretStore`, etc. in doc comments. Lints kept allowed (with rationale comments in `Cargo.toml`): - `missing_docs_in_private_items` (275 sites): private docs aren't load-bearing for users — industry-standard "kept allowed". - `missing_inline_in_public_items`: `#[inline]` is a perf hint; rustc/LLVM make better decisions than blanket-marking every cross-crate public item. Allow-list: 51 → 47 entries.
…t_stdout allows
The CLI binary now initializes a `simple_logger` with no timestamps and no
level prefixes (so the user-facing UX is unchanged: `[edgezero] creating
project at ...` still prints exactly that), and all `println!` /
`eprintln!` sites are converted to `log::info!` / `log::error!` /
`log::warn!`.
Sites converted (24 total):
- `crates/edgezero-cli/src/main.rs`: top-level error reporters (`new`,
`build`, `deploy`, `serve`, `dev`) + status output for store-binding
warnings.
- `crates/edgezero-cli/src/generator.rs`: 9 status messages and 2 git
warnings now go through the logger.
- `crates/edgezero-cli/src/dev_server.rs`, `adapter.rs`: dev manifest /
command-failure reporting.
- `crates/edgezero-adapter-{axum,cloudflare,fastly,spin}/src/cli.rs`:
one build-artifact-path message each.
Allow-list: 47 → 45 entries (`print_stderr` + `print_stdout` removed).
Real renames + restructuring (no inline allow attrs):
- `non_ascii_literal` (3 sites): replaced the Japanese KV-key test literal
with `\u{...}` escapes (same runtime bytes, ASCII source) instead of
`#[expect]`-ing the lint. Replaced `→` arrow in a CLI test message with
`->`.
- `similar_names` (2 sites): renamed `decoded` → `output` in
`crates/edgezero-adapter-spin/src/decompress.rs` to break the
`decoded`/`decoder` prefix-share that the lint flags.
- `too_many_lines` (1 site): split `collect_adapter_data` in
`crates/edgezero-cli/src/generator.rs` into three helpers
(`blueprint_data_entries`, `render_manifest_section`,
`append_readme_entries`).
- `shadow_unrelated` (~14 sites): renamed every flagged inner binding
to be specific to its purpose:
- `serve_with_stores`: `let router = Router::new()...` →
`axum_router`; `let server = server.with_graceful_shutdown(...)` →
`graceful_server`; `let shutdown = ...` → `shutdown_signal`.
- `store_name_slug`: `Some(ch)` → `Some(lower_ch)` (was shadowing
outer `ch`).
- dev_server tests: `let url = ...` reused per-step → `write_url`,
`read_url`, `check_url`, `delete_url`, `save_url`, `load_url`;
`let resp = ...` → `write_response`/`read_response`/`save_resp`/
`load_resp`/`exists_before`/`exists_after`.
- `axum::key_value_store::get_bytes`: inner write-txn `table` →
`write_table`, `entry` → `fresh_entry`.
- `list_keys_page` cursor match: inner `Some(cursor)` → `Some(scan_from)`.
- `data_persists_across_reopens` test: second `let store = ...` →
`reopened`.
- `axum::response::into_axum_response` error path: `body` →
`error_body`, `response` → `error_response`. Test: `stream` →
`body_stream`.
- `fastly::key_value_store::list_keys_page`: inner `cursor` →
`next_cursor`.
- `fastly::proxy` test: collapsed two pairs of `body`/`collected` reuse
into named bindings (`plain_body`, `gzip_body`).
- `spin::decompress` test: `let result = ...` reused per-encoding →
`none_encoding`, `identity_encoding`.
- `core::body::from_stream_maps_errors` test: `stream` →
`source`/`chunks`.
- `core::key_value_store` tests: `let val = ...` reused → `after_first`/
`after_second`/`int_val`/`str_val`/`single_dot_err`/`double_dot_err`.
- `axum::cli::read_axum_project`: `Some(value)` → `Some(port_value)`
(was shadowing outer `value` from `toml::from_str`).
Allow-list: 45 → 41 entries.
…quest path
Real fixes (not just docs) for every production-code .expect() that could
fire under upstream contract change or misconfigured input:
- `IntoResponse::into_response` now returns `Result<Response, EdgeError>`
workspace-wide (breaking change). Cascades through `Responder`,
`EdgeError::into_response`, `RouterService::oneshot`, the handler future
in `core/handler.rs`, and the route-listing builder.
- `ProxyResponse::into_response` and `core::response::response_with_body`
now return `Result<Response, EdgeError>` and propagate `http::Builder`
failures via `map_err(EdgeError::internal)?` instead of `.expect()`.
- `core::body::Body::into_bytes_bounded` rewritten as a `match self {
Once | Stream }` so the unreachable `is_stream()`-guarded `.expect()`
pair is gone — the compiler proves exhaustiveness.
- `core/compression.rs` decoder slice access now propagates as
`io::Error::other(...)` instead of `.expect("AsyncRead contract")`,
so a malicious or buggy upstream stream fails the request rather than
crashing the worker.
- `axum/response.rs::into_axum_response` error path no longer uses
`Response::builder().expect(...)`; constructs the 500 response
directly via `Response::new` + `status_mut` + `headers_mut().insert`,
every step infallible by `http`-crate contract.
- `axum/proxy.rs` replaced `Default` (which panicked on TLS init) with
fallible `AxumProxyClient::try_new() -> Result<_, reqwest::Error>`.
Production caller in `request.rs::into_core_request` propagates as a
`String` error (matches the fn's existing return type).
- `fastly/logger.rs::init_logger` now returns
`Result<(), InitLoggerError>` (a typed enum wrapping the underlying
build error and `log::SetLoggerError`) instead of `.expect("non-empty
Fastly logger endpoint")`. `lib.rs::init_logger` re-exports the wider
return type.
- `cli/generator.rs::render_templates` propagates the previously-
`.expect("adapter context dir has a file name")` invariant as
`io::Error::other` since the surrounding fn already returns
`io::Result<()>`.
`axum/service.rs::call` (the tower `Service` impl) bridges the new
`Result<Response, EdgeError>` from `RouterService::oneshot` into a
`Response<AxumBody>` by mapping the error to a hard-coded 500 with a
plain-text body — `Service::call` returns `Result<Response, Infallible>`
so we cannot propagate further up the stack here.
`adapter-fastly` adds `thiserror` as a direct dependency for
`InitLoggerError`. All 557 workspace tests still pass.
Replaces the previous \`std::io::Result<()>\` / \`io::Error::other(format!(...))\`
shape across the \`edgezero new\` code path with two domain-specific error
types:
- \`crate::scaffold::ScaffoldError\` (variants \`Io { path, source }\` and
\`Render { name, message }\`) wraps every Handlebars failure and every
filesystem op inside template rendering with the offending path/template
name attached.
- \`crate::generator::GeneratorError\` (variants \`OutputDirExists\`,
\`AdapterDirMissingFileName\`, \`Io { path, source }\`, and
\`Scaffold(#[from] ScaffoldError)\`) replaces the workspace-construction
io::Error stringification.
\`generate_new\`, \`ProjectLayout::new\`, \`collect_adapter_data\`, and
\`render_templates\` all return \`Result<_, GeneratorError>\`.
\`adapter-cli\` and \`scaffold\` now depend on \`thiserror\` directly. All
557 workspace tests still pass.
The `IntoResponse::into_response` change in 1506738 turned the trait into `-> Result<Response, EdgeError>` workspace-wide. The demo app (`examples/app-demo/`) is excluded from the main `Cargo.toml` workspace, so it didn't get rebuilt by the workspace clippy/test gate and silently broke. This propagates the same fix to the demo: - Every `block_on(handler(ctx)).expect("handler ok").into_response()` in `crates/app-demo-core/src/handlers.rs` test code now appends `.expect("response")` to unwrap the response result. - Every `into_body().into_bytes()` test path now appends `.expect("buffered")` since `Body::into_bytes()` returns `Option<Bytes>` (changed in the defensive-coding pass). `cd examples/app-demo && cargo test --workspace --all-targets` passes all 21 demo handler tests; `cargo clippy --workspace -- -D warnings` also clean.
Inherit pedantic+restriction lints in the demo workspace and each demo crate. Fix the lints that flagged real issues in the demo handlers (`as _` trait imports, inlined format args, fast-path `to_string`, renamed shadowed bindings, separated literal suffix). The demo's allow-list is intentionally narrower than the library's — only entries the demo actually trips. New allows can be added lazily as future failures surface.
Add a clippy.toml mirroring the parent (allow expect/unwrap/panic/ indexing-slicing in tests). Then refactor away the workspace allows that were genuine wins: - shadow_reuse: rename `chunk` and `cursor` shadows - absolute_paths: import std::env, std::time::Duration, std::process, and use already-imported Arc instead of std::sync::Arc - default_numeric_fallback: add type suffixes (1_u64, 0_i32..3_i32, 1_i64) - pattern_type_mismatch: implicitly fixed by str_to_owned changes - missing_trait_methods: implement KvStore::exists on the test MockKv - expect_used in production code: stream() now propagates the response builder error via EdgeError::internal The remaining allow-list keeps only entries the demo actually trips that match main's philosophical stance — std (not core/alloc) for binaries, idiomatic `?` over match, terse closure idents, and the single exhaustive_structs site that comes from the `app!` macro.
- str_to_string (21 sites): `.to_string()` → `.to_owned()` on `&str` - arithmetic_side_effects: counter `n + 1` → `n.wrapping_add(1)` - min_ident_chars + pattern_type_mismatch: rename closure destructures `|(k, v)|` → `|&(name, value)|`/`|&(key, value)|` - pub_with_shorthand + field_scoped_visibility_modifiers: drop `pub(crate)` shorthand on the demo's DTOs and handlers — the `mod handlers;` declaration is already private, so plain `pub` is crate-private at the boundary - print_stderr: axum main returns `anyhow::Result<()>` and lets the Termination impl render errors; fastly/cloudflare host stubs keep `eprintln!` behind a localized `#[expect]` with reason since they only run on the wrong target Workspace allow-list now keeps only the entries that match main's philosophical stance (idiomatic `?`, `pub` shorthand handled per-call site, etc.) plus the single `exhaustive_structs` site from the `app!` macro.
Drop the `arbitrary_source_item_ordering` allow in favor of the canonical clippy-restriction layout: - Top of `handlers.rs`: consts (alphabetical), then structs (alphabetical: ConfigParams, EchoBody, EchoParams, NoteIdPath, ProxyPath), then handler fns - Test mod: uses, then structs (alphabetical), then impls grouped with their self-types, then helper + test fns interleaved in alphabetical order - `impl KvStore for MockKv` methods alphabetical (delete, exists, get_bytes, list_keys_page, put_bytes, put_bytes_with_ttl) - Hoisted the late `use edgezero_core::secret_store::...` up to the test mod's use block No behavior changes — pure reordering. Demo workspace allow-list drops to 8 entries.
The `edgezero new` generator now scaffolds the same lint policy EdgeZero itself uses: - Root `Cargo.toml` carries `[workspace.lints.clippy]` (pedantic warn + restriction deny) with the same demo-tested allow-list - Root `clippy.toml` exempts tests from `unwrap`/`expect`/`panic`/ indexing-slicing restriction lints - Each generated crate's Cargo.toml inherits via `[lints] workspace = true` Generated projects are clippy-clean against the strict gate out of the box.
Both adapters were calling `from_core_response` directly on the router's return value, but `oneshot` now yields `Result<Response, EdgeError>` since the response builder errors propagate through the router. Extract the response with `?` first so the wasm32 builds (`--target wasm32-unknown-unknown` for cloudflare, `--target wasm32-wasip1` for spin) compile again.
… per-site Real fixes (allows now justified by audit, not laziness): - build.rs returns `Result<(), Box<dyn Error>>` instead of expect-panicking - adapter registry / blueprint registry recover from poisoned RwLocks via `unwrap_or_else(PoisonError::into_inner)` rather than expect-panicking - ManifestLoader gains `try_load_from_str` returning `io::Result`; adapter `run_app` paths propagate via `?`. The non-fallible `load_from_str` keeps its panic-on-bad-input contract for compile-time-embedded manifests, with a documented per-fn `#[expect(clippy::panic, reason = ...)]` - `expand_app` macro emits `compile_error!()` instead of panicking on bad `edgezero.toml` (rustc surfaces a clean build error) - `parse_handler_path` keeps a panic with a clear reason — proc-macro expansion errors *are* build failures - `partial_pub_fields` on `Manifest`: privatized `root` and `logging_resolved`, kept the deserialized fields `pub` for the public API. Localized `#[expect]` documents the deliberate split - `must_use_candidate` fixed on cli_support helpers via `#[must_use]` - `missing_inline` fixed on adapter/scaffold registry functions - `pub_use`, `format_push_string`, `arithmetic_side_effects`, `default_numeric_fallback`, `pattern_type_mismatch`, `min_ident_chars`, `str_to_string`, `absolute_paths`, `module_name_repetitions`, `shadow_reuse`: all kept as workspace allows but with concise rationales replacing the prior verbose audit notes Each remaining workspace allow now has a one-line reason. The list is shorter than before but explicitly accepts the lints whose "fix" would universally make the code worse (match-ergonomics destructures, std-only binary entrypoints, idiomatic `?`/return).
…space-wide 54 sites across 23 files. Fixed places where my bulk replace had wrongly converted Display::to_string() calls (anyhow::Error, io::Error, i32 etc.) back to .to_string(). The lint allow is dropped from the workspace.
23 sites across extractor.rs, key_value_store.rs, middleware.rs, proxy.rs, adapter-axum dev_server/key_value_store, adapter-spin decompress. Validator length(min=N) gets _u64; range(min=N, max=N) gets matching type suffix; loop-bound and assertion literals get explicit i32.
Core crate: replaced 60+ `std::collections::HashMap`, `std::sync::Arc`, `std::ops::Deref/DerefMut`, `crate::error::EdgeError`, `futures::executor::block_on`, `std::task::*`, `std::string::String::*` absolute paths with explicit `use` statements. Axum proxy.rs: imported the various `axum::http::*` and `axum::routing::*` types used in test functions. The lint stays allowed at the workspace level for adapter test modules where one-shot uses of framework types like `axum::http::HeaderMap` and `fastly::kv_store::KVStore` are clearer inline.
Real fixes (workspace allows dropped, code refactored): - AdapterAction marked #[non_exhaustive] with wildcard arms in adapter cli match sites — drops a workspace exhaustive_enums concession - Adapter crate exposes `pub mod registry` instead of pub-using items at the crate root — drops the workspace pub_use concession - expand_action_impl made private (no longer pub(crate)) — drops the workspace pub_with_shorthand concession on this site - ManifestLoader, Manifest, ManifestApp/HttpTrigger/Environment/Binding/ ResolvedEnvironment*, ManifestAdapterBuild/Commands, ManifestConfigStoreConfig, ManifestLoggingConfig, ResolvedLoggingConfig, ManifestKvConfig, ManifestSecretsConfig, HttpMethod, LogLevel — all reordered to match canonical clippy item ordering (consts first, then structs, impls, fns; alphabetical within each group) - Manifest impl methods sorted alphabetically; Manifest fields sorted - match-ergonomics destructures rewritten as let-else for clarity - HttpMethod gained Copy; LogLevel/HttpMethod take `self` (drops trivially_copy_pass_by_ref) - partial_pub_fields fixed via consistent pub on Stores in fastly request - needless_pass_by_value: run_app_with_config / run_app_with_logging take `&FastlyLogging`; map_edge_error / map_lookup_error take by ref; build_fastly_request takes `&HeaderMap`; generate_new takes `&NewArgs` - expect_used localized on register_templates with rationale - ManifestLoader::load_from_str / parse_handler_path keep panic-on-bad- build-input contract documented per-fn - Router: route-listing duplicate-path panic + add_route panic both documented per-fn (build-time programmer error) - spin contract test uses #[allow] for expect/tests-outside per file - separate manifest_definitions.rs in macros crate (drops mod-after-use) Workspace allows that survived (most match audited rationales): implicit_return, question_mark_used, single_call_fn, separated_literal_suffix, pub_with_shorthand (rustfmt-enforced), pub_use, min_ident_chars, single_char_lifetime_names, shadow_reuse, module_name_repetitions, format_push_string, pattern_type_mismatch, arithmetic_side_effects, float_arithmetic, as_conversions, exhaustive_structs, exhaustive_enums, missing_trait_methods, absolute_paths, std_instead_of_alloc/core, missing_inline_in_public_items, tests_outside_test_module, arbitrary_source_item_ordering (core-crate files outside manifest.rs). Tests pass, strict clippy clean across workspace + demo.
Override KvStore::exists in 4 production impls (axum/fastly/cloudflare + NoopKvStore) and the in-test MockStore. Override configure/name/ config_store/build_app in the two Hooks test impls. Update the #[app] macro to emit configure, build_app, and a None-returning config_store when [stores.config] is absent so generated user apps still pass clippy. Add explicit clone_from to RouteEntry's Clone impl.
Delete config_store, key_value_store, and secret_store crate-root
re-exports — items remain reachable via the `pub mod` paths. Update the
two short-path callers (axum service.rs / secret_store.rs) to use full
module paths. Keep `pub use edgezero_macros::{action, app}` and the
`http` facade re-exports — these are the only surviving sites and the
lint is module-scoped so it cannot be silenced per-item. Workspace
allow rationale updated to point to those two patterns.
The previous comment framed `push_str(&format!(...))` as a stylistic preference. It is actually the only call-site form that satisfies the full restriction-deny gate: `write!(s, ...)` returns a `Result` which trips `let_underscore_must_use` under `let _ =`, `unwrap_used` under `.unwrap()`, and `expect_used` under `.expect()`.
Switch generator.rs from `push_str(&format!(...))` to `writeln!(...)?` which writes directly into the buffer (no temp String allocation) and propagates `std::fmt::Error` rather than silencing it. Add `GeneratorError::Format(#[from] std::fmt::Error)` and bubble the result through `render_manifest_section` and `append_readme_entries`. Drop the workspace allow.
Rename 'a → 'mw on Next, 'a → 'route on RouteMatch, 'a → 'manifest on manifest_command, and 'a → 'blueprint on AdapterContext. Drop the workspace allow.
Eliminate let-rebinding shadows across core, fastly, axum, and cli
crates. The recurring patterns:
- `while let Some(chunk) = stream.next().await { let chunk = chunk?; }`
→ rename outer to `result`, keep inner `chunk`
- `if let Some(cursor) = cursor.filter(...)` → rename outer/inner to
distinct names
- `let path = path.into()` (Into-paramter idiom) → rename to
destination-specific name
- closure params shadowing outer captures → rename closure param
All renames preserve semantics; tests + workspace clippy + wasm
target checks all pass.
Split `#[cfg(all(test, feature = "..."))]` on test modules into two separate cfg attributes (`#[cfg(test)] #[cfg(feature = "...")]`) which the lint recognizes correctly. Affects edgezero-adapter-fastly lib.rs and edgezero-cli main.rs.
Addresses the post-f1179df PR #257 review. 1. Medium — Spin Cloud pushes are non-atomic across chunks (one `spin cloud key-value set` shellout per ≤96 KiB argv chunk), but the pre-fix error only named the failing chunk's size + exit status. If chunk 1 committed and chunk 2 failed, the operator was left with partially-updated live cloud state and no resume boundary. Fastly already produces a committed / failed / not-attempted diagnostic (cli.rs::push_entries_with_committer). Mirror Fastly's shape in `push_cloud::write_batch`: track a cursor through `entries` as committed-chunks accumulate, and on failure emit a structured error naming - committed keys (already on Fermyon Cloud, safe to skip), - failed-chunk keys (the cloud API is atomic per shellout, so a non-zero exit means none of these landed), - not-attempted keys (subsequent chunks, fully re-push needed), - a resume hint pointing out that `set` is idempotent so re-running with the full set is also safe. Regression covered by `write_batch_partial_failure_reports_committed_failed_and_not_attempted_keys`, which stands up a fake `spin` that succeeds on invocation 1 and fails on invocation 2, feeds 7 × ~30 KiB entries (>=3 chunks), and asserts the diagnostic names all three buckets plus the upstream stderr. 2. Medium — `docs/guide/manifest-store-migration.md`'s capability table still listed spin's Config as `Single (flat variables)`, but the Spin runtime is KV-backed and Multi for Config (one `spin_sdk::key_value::Store` per declared `[stores.config].id`). This page is linked from the loader's hard-error message when it encounters a pre-rewrite manifest, so the stale entry can actively mislead migrations. Changed to `Multi (KV label)`. 3. Low — Axum config-push docs claimed it was "future Stage 7 work" and told users to populate `.edgezero/local-config-<id>.json` directly. `config push --adapter axum` ships and writes the same file the runtime reads. Updated: - `docs/guide/configuration.md` — bullet now points operators at `edgezero config push --adapter axum`. - `docs/guide/adapters/axum.md` — same bullet under the Config Store section; also calls out the typed flow from a downstream `<your-cli>` for `#[secret]` stripping. Verified (all gates green): - cargo fmt --all -- --check - cargo clippy --workspace --all-targets --all-features -- -D warnings - cargo test --workspace --all-targets - cargo clippy -p edgezero-adapter-spin --target wasm32-wasip2 --features spin --all-targets -- -D warnings - cd examples/app-demo && cargo test --workspace --all-targets - (cd docs && npx prettier --check guide/manifest-store-migration.md guide/configuration.md guide/adapters/axum.md)
The June-07 review caught a clippy regression in the freshly-added `write_batch` partial-failure diagnostic: under `cargo clippy --workspace --all-targets --all-features -- -D warnings` the direct slice indexing (`entries[..cursor]`, `entries[cursor..chunk_end]`, `entries[chunk_end..]`) tripped `clippy::indexing_slicing`, and the single-letter destructured binding `(k, _)` tripped `clippy::min_ident_chars`. Both are part of this repo's strict-clippy restriction set (Stage 8 chore/strict-clippy merge), so the previous form failed CI on `cargo clippy` even though `cargo test` and `cargo fmt` were clean. Refactor: replace the three slice indices with `.get(..)` calls that fall through to `&[]` if the in-bounds invariant is ever violated (it can't be — `cursor` and `chunk_end` are produced from `entries.len()` and `chunk_entries`'s structure) — and rename the destructured binding to `(key, _value)`. Pure refactor: the partial-failure error string and the `write_batch_partial_failure_reports_committed_failed_and_not_attempted_keys` regression test are unchanged. All 16 push_cloud tests still pass. Verified (clean): - cargo fmt --all -- --check - cargo clippy --workspace --all-targets --all-features -- -D warnings - cargo test -p edgezero-adapter-spin --features cli push_cloud
The June-07 review caught `npx prettier --check .` failing on `docs/guide/cli-walkthrough.md` lines 190 / 202-204. Prettier doesn't preserve indentation on continuation lines inside backtick-wrapped inline-code spans (`KEY=VALUE`, `SET <key> <value>`, `azure_cosmos`), so the de-indent it wants is purely a source-format change — the rendered VitePress output is unchanged because the surrounding backticks group the whole multi-line span as one inline code block. Pure formatting; no content edits. Verified: `(cd docs && npx prettier --check .)` is clean.
ChristianPavilonis
left a comment
There was a problem hiding this comment.
PR Review
Requesting changes for four blocking issues found in the extensible CLI/store work. Inline comments cover the diff-addressable findings; the second location for the secret-store issue is listed below because it is not commentable on the current PR diff.
Body-level finding
- 🔧
with_secrets()issue also affects Fastly (crates/edgezero-adapter-fastly/src/request.rs:483):resolve_secret_handle(false)returnsNone, so.with_secrets()without.require_secrets()does not inject a FastlySecretRegistry. Apply the same fix as the Cloudflare comment: construct the secret handle wheneverSecretSource::Onis selected, and add a regression test for.with_secrets()without.require_secrets().
CI Status
- fmt: PASS
- clippy: NOT VERIFIED locally — local
rustc 1.91.1is below the repo-pinned1.95.0/spin-sdk 6.0.0requirement - tests: PASS
Five reviewer follow-ups from the chore/strict-clippy PR: - fastly/dispatch_with_kv: # Errors doc now distinguishes the kv_required=true (errors) and kv_required=false (logs + skips) paths. - core/KvPage: doc clarifies termination is cursor.is_none(), not keys.is_empty(); empty keys with Some(cursor) is a valid intermediate page when the scan-cap skips a run of expired entries. - core/EdgeError: rename intrinsic source() to inner() and drop the same_name_method expect — the name conflict with Error::source is gone. - macros/#[app]: comment the emitted Hooks impl to flag drift risk for configure/build_app vs the trait defaults under missing_trait_methods. - core/tests: collapse DefaultHooks/TestHooks Hooks impls to the methods they actually override; add a targeted #[expect(missing_trait_methods)] on each stub with the rationale.
Pulls in c5652d3 — chore/strict-clippy's second round of PR #257 review-feedback fixes — on top of f374351. Conflicts resolved in three files; the other two touched files auto-merged cleanly: - crates/edgezero-core/src/error.rs Upstream renamed the typed `EdgeError::source(&self) -> Option<&AnyError>` helper to `inner()` to drop the same-name shadow of `Error::source` (and dropped the `#[expect(clippy::same_name_method)]` it needed). Git auto-merged the new `inner()` insertion near the top of the impl, but couldn't tell HEAD's old `source()` block (further down) was the same logic — that landed as a conflict. Resolution: keep upstream's `inner()` rename, drop the duplicate `source()` block, and add `EdgeError::NotImplemented` to `inner()`'s None-arm match (that variant exists only on this branch — upstream hadn't seen it, so its match was missing the arm). - crates/edgezero-core/src/app.rs Upstream collapsed the `DefaultHooks` / `TestHooks` test stubs to only the methods they actually override (the rest now use trait defaults under `#[expect(missing_trait_methods)]`). Conflict 1 (DefaultHooks): HEAD still spelled out `build_app`, `configure`, `name` overrides that just delegated to the trait default. Kept upstream's slim form (only `routes` + `stores`). Conflict 2 (TestHooks): HEAD had a `build_app` override that delegated to the trait default; upstream had a `config_store()` override targeting a method removed from the `Hooks` trait on this branch (post-rewrite `stores()` carries config metadata). Kept only the methods that still override real trait behavior: `configure`, `name`, `routes`, `stores`. - crates/edgezero-adapter-fastly/src/request.rs Upstream improved the `# Errors` doc on a `pub fn dispatch_with_kv` that no longer exists on this branch — the post-rewrite signature is `pub(crate) fn dispatch_with_registries(..., config_meta, kv_meta, secret_meta, env)` (three-store, not single-kv). The `kv_required` distinction upstream documented now lives inside `resolve_kv_handle`. Resolution: kept HEAD's signature + brief doc, with a pointer to `resolve_kv_handle` for the `kv_required=true/false` escalation semantics upstream wanted documented. No `# Errors` section needed since the function is `pub(crate)`, outside the `missing_errors_doc` lint's scope. Auto-merged (no conflict): - crates/edgezero-core/src/key_value_store.rs (upstream `KvPage` doc clarification on termination = cursor.is_none()) - crates/edgezero-macros/src/app.rs (upstream comment on the emitted Hooks impl to flag drift risk for configure/build_app vs trait defaults under missing_trait_methods) Verified on this branch (all gates green): - cargo fmt --all -- --check - cargo clippy --workspace --all-targets --all-features -- -D warnings - cargo test --workspace --all-targets - cargo clippy -p edgezero-adapter-spin --target wasm32-wasip2 --features spin --all-targets -- -D warnings - cargo clippy -p edgezero-adapter-fastly --target wasm32-wasip1 --features fastly --all-targets -- -D warnings - cargo clippy -p edgezero-adapter-cloudflare --target wasm32-unknown-unknown --features cloudflare --all-targets -- -D warnings - cd examples/app-demo && cargo test --workspace --all-targets
… leak, tighten store-id validation Addresses the PR #269 review (5 actionable findings — 1 body-level, 4 inline). 1. **`.with_secrets()` silently dropped the secret store** (cloudflare + fastly — `request.rs`) `SecretSource::On { required }` calls `resolve_secret_handle(.., required)`, and that helper short-circuited to `None` whenever `!required`. `.with_secrets()` sets `required: false`, so the documented `.with_secrets().dispatch(...)` path ran handlers without any `SecretRegistry`. Users who never called `.require_secrets()` saw `ctx.secret_store_default()` return `None` even though they'd opted in. Fix: always construct the handle when `SecretSource::On` is selected — the per-platform constructors (`CloudflareSecretStore:: from_env`, `FastlySecretStore`) can't fail at construction time, so there's nothing for `required` to gate at the handle-building step. The bool stays in the signature for symmetry with `resolve_kv_handle` and as a hook for future per-secret-lookup availability policy. Regression: two new `synthesis_tests` in fastly assert `resolve_secret_handle(false).is_some()` AND `resolve_secret_handle(true).is_some()`. The cloudflare path takes a `worker::Env` (wasm-only, not constructible on host), so the fix is documented in-source at the `dispatch` call site and on the function itself; the shared logic is covered by fastly's host regression. 2. **Cloudflare provision could orphan a namespace** (`adapter-cloudflare/src/cli.rs`) `read_namespace_id` accepts both `[[kv_namespaces]]` (array-of- tables) AND `kv_namespaces = [{ binding, id }]` (inline-array) forms. An inline-array entry with no `id` returned `None` ("not yet provisioned"), so provision shelled `wrangler kv namespace create` successfully, then `upsert_kv_namespace`'s `as_array_of_tables_mut()` rejected the inline shape and the upsert errored — leaving the freshly-created namespace orphaned on Cloudflare with no local entry to track it. Fix: new `check_kv_namespaces_writeback_shape` pre-flight runs BEFORE `create_kv_namespace`. Missing or array-of-tables form is OK; inline-array form fails fast with an actionable message pointing at the conversion AND naming the orphan hazard so the operator knows the risk. Four regression tests in cloudflare's `cli.rs::tests` cover missing-file, no-kv_namespaces, valid array-of-tables, and the inline-array rejection. 3. **Fastly config push leaked values through argv** (`adapter-fastly/src/cli.rs::create_config_store_entry`) `--value=<value>` exposed every entry's bytes in `ps`/ `/proc/<pid>/cmdline` AND was bounded by the host's `ARG_MAX` (4 KiB–256 KiB depending on platform — easy to trip with a JSON blob). Fix: switch to `--stdin`, pipe the value through `child.stdin`. Verified `fastly config-store-entry create --help` on the pinned Fastly CLI 15.x lists `--stdin` as an "OPTIONAL FLAGS" entry that, when set, makes `--value` ignored. Value bytes no longer appear in argv; effective size cap lifts to whatever the OS pipe buffer + CLI accept (megabytes in practice). 4. **Store logical ids were under-validated** (`edgezero-core/src/manifest.rs::validate_store_declaration`) Pre-fix only blanks, control characters, exact duplicates, and default-not-in-ids were rejected. Values like `foo/bar` (escapes the local-config dir at `.edgezero/local-config-<id>.json`), `foo__bar` (collides with the `EDGEZERO__STORES__<KIND>__<ID>__ NAME` env-var segment separator), and case-only duplicates (`foo`/`FOO` collide under upper-case env-var lookup AND lower-cased segment paths) made it through and broke at runtime. Fix: two new validators — - `store_id_format`: only `[A-Za-z0-9_-]` allowed, `__` reserved as the env-var separator; - `store_id_case_duplicate`: ASCII case-insensitive dedup so `foo` and `FOO` no longer alias at runtime. Four regression tests cover the new error paths (path-separator rejected, double-underscore rejected, case-only dup rejected) plus a positive case asserting the canonical scaffold shapes (`app_config`, `feature-flags`, `appConfig`, `v1`+`v2`) still validate cleanly. Architectural review feedback (shell-out vs. tight API integration) is acknowledged but left for follow-up — that's a scope-level tradeoff, not a blocker on this PR. Documented as such in the inline reply. Verified (all gates green): - cargo fmt --all -- --check - cargo clippy --workspace --all-targets --all-features -- -D warnings - cargo test --workspace --all-targets - cargo clippy -p edgezero-adapter-spin --target wasm32-wasip2 --features spin --all-targets -- -D warnings - cargo clippy -p edgezero-adapter-fastly --target wasm32-wasip1 --features fastly --all-targets -- -D warnings - cargo clippy -p edgezero-adapter-cloudflare --target wasm32-unknown-unknown --features cloudflare --all-targets -- -D warnings - cd examples/app-demo && cargo test --workspace --all-targets
Review-feedback summary — all 5 findings addressed in e1d7753Replies posted on each inline thread; this is a summary so the body-level Fastly secrets mirror is also visible.
Gates verified clean on the merged tree (host fmt + clippy + workspace tests + 3 wasm clippy targets + app-demo workspace tests). |
ChristianPavilonis
left a comment
There was a problem hiding this comment.
🔧 Requesting changes for several correctness issues in the new provision/config-push paths.
😃 The overall direction looks solid: portable logical stores, env-resolved platform names, adapter-owned provision/push, and typed config metadata all feel like the right seams.
I kept the inline comments focused on high-confidence cases where a command can mutate the wrong platform state, report success without making the runtime able to open the resource, or leave generated/local flows broken. These should be addressed before this lands.
Folded here because GitHub would not resolve these as inline comments in the current PR diff:
- 🔧 Generated Cloudflare commands still use
wrangler build, which current Wrangler docs list as removed. Because manifest commands take precedence over the adapter's built-in build implementation, generated projects/app-demo get a brokenedgezero build --adapter cloudflare. Please switch scaffold/docs to a supported build flow and keep project-context handling consistent with the other Wrangler paths. - 🔧
ManifestStoresshould reject unknown[stores.*]subtables. Withoutdeny_unknown_fieldsor a flatten catch-all, typos like[stores.configs]/[stores.secret]are silently ignored. With the hard-cutoff schema posture, these should be schema errors rather than 'no store declared'.
|
🌱 Architecture follow-up, non-blocking for the correctness review I just submitted: two design questions from my architecture pass that I intentionally did not include as request-changes inline findings.
|
Two real fixes from PR review feedback (round 3): - cloudflare scaffold + demo: replace wildcard `use worker::*` with explicit imports, add `# Errors` doc and `#[inline]`. Generated Cloudflare projects now pass strict wasm32 clippy. - docs/guide/handlers.md: update the IntoResponse example to return `Result<Response, EdgeError>` (the trait signature changed in this PR); the old `-> Response` example would fail to compile. The reviewer's matching suggestions for the fastly and spin scaffolds required suppressions on macro-expanded code (`#[fastly::main]`, `#[http_component]`) we do not own. Those are deliberately declined; see the PR thread replies for the rationale.
…lision, fastly --upsert/--stdin + resource-link + valid setup tables, axum project-root discovery Addresses the 7 inline findings from the June-10 23:10 review round on PR #269. 1. CF non-local push needs --remote + correct cwd (cloudflare/cli.rs) Wrangler v4 defaults `kv bulk put` to LOCAL Miniflare storage when the command supports both, so the old `wrangler kv bulk put <file> --namespace-id=<id>` invocation could silently populate `.wrangler/state` while reporting success and leaving the live Cloudflare namespace empty. Add explicit `--remote`. Also set `current_dir(wrangler_path.parent())` so wrangler picks up the project's `account_id` / `--env` / persistence settings the same way `wrangler dev` / `wrangler deploy` do. 2. CF --local should target the binding, not a remote namespace id (cloudflare/cli.rs) The scaffold ships with `local-dev-placeholder` ids, so the old `find_namespace_id(...)?` call would block `--local` push until the operator had also run `edgezero provision` against the live Cloudflare account. Switch to wrangler's documented local KV shape: `wrangler kv bulk put <file> --binding <BINDING> --local` (binding-keyed, no remote id resolution). `current_dir` is set to the wrangler project so the binding resolves under the same `wrangler.toml` `wrangler dev --local` uses. 3. CF kv/config merged-id collision check (cloudflare/cli.rs) Implement `merged_id_kinds() -> &["kv", "config"]` (mirrors the Spin adapter's same return). Both `[stores.kv]` and `[stores.config]` resolve to the same `[[kv_namespaces]] binding = <platform-name>` entry in wrangler.toml. Declaring the same logical id under both kinds (e.g. `kv.ids = ["x"]` AND `config.ids = ["x"]`) would resolve to ONE KV namespace at runtime with silent kv/config write collisions, and provision would skip the second binding as "already provisioned". `config validate` now catches this before any wrangler shell-out. 4. Fastly remote push is now repeatable (fastly/cli.rs) Switched `create_config_store_entry` from `config-store-entry create --value=...` to `config-store-entry update --upsert --stdin`. `update --upsert` matches the convergent semantic the other adapters' config-push paths already have (axum overwrites JSON, cloudflare `kv bulk put` overwrites, spin `cloud key-value set` overwrites). The prior `create` form erred on every existing key, making the first push succeed but every follow-up push fail at the first unchanged key. 5. Fastly provision warns when service is already deployed (fastly/cli.rs) Fastly's `[setup.<kind>_stores.<name>]` is consumed ONLY when `fastly compute deploy` is creating a new service. If `service_id` is already present in fastly.toml the service has been deployed at least once and subsequent deploys skip `[setup]` entirely — the store gets created in the account but no resource link ties it to a service version, so the running Compute service can't open it. Provision now reads `service_id` via the new `read_fastly_service_id` helper and, when an existing service is detected, EMITS the exact `fastly resource-link create --service-id=<id> --resource-id=<STORE-ID> --version=latest --autoclone --name=<name>` one-shot the operator needs to run (plus the `fastly <kind>-store list --json` lookup for `<STORE-ID>`). We deliberately don't auto-run the link command because `--autoclone` mutates the active service version and silently doing that is surprising; the instruction lets the operator audit before committing. 6. Fastly [local_server.*_stores.<id>] no longer written by provision (fastly/cli.rs) Pre-fix `append_fastly_setup` wrote an empty `[local_server.<kind>_stores.<id>]` alongside `[setup.<kind>_stores.<id>]`. The empty stanza doesn't satisfy fastly's local-server schema: config-stores need `format = "inline-toml"` plus a contents table, kv/secret stores need a JSON `file = "..."` or `{key, data}` array. The empty form made `fastly compute serve` either error at startup or silently skip the declared store. `provision` now writes ONLY the `[setup]` half — local-server seeding is `edgezero config push --adapter fastly --local`'s job (already producing the valid `format` + `contents` shape for config-stores; kv/secret stores stay operator-managed for local-server until equivalent writers land). `setup_block_present` matches: only checks `[setup.*]`. Updated 4 existing tests + added a regression for the post-fix empty-table avoidance. 7. Axum push/runtime cwd mismatch (axum/config_store.rs) Push wrote to `manifest_root/.edgezero/local-config-<id>.json`, but the runtime's `from_local_file` resolved the same name against the process cwd. Cargo `cargo run -p my-app-adapter-axum` from a crate dir made the runtime read `crates/<adapter>/.edgezero/...` while push wrote to `<project>/.edgezero/...` — empty store, no warning. `local_path` now walks up from cwd looking for an ancestor containing `edgezero.toml` (the same convention cargo uses for `Cargo.toml`). Found → rooted under the ancestor; absent → falls back to cwd-relative (preserves the deployment shape where the binary ships without `edgezero.toml` alongside it). 3 regression tests in `config_store::tests` (no-match, single-match, nearest-of-multiple). Verified (all gates green): - cargo fmt --all -- --check - cargo clippy --workspace --all-targets --all-features -- -D warnings - cargo test --workspace --all-targets - cargo clippy -p edgezero-adapter-spin --target wasm32-wasip2 --features spin --all-targets -- -D warnings - cargo clippy -p edgezero-adapter-fastly --target wasm32-wasip1 --features fastly --all-targets -- -D warnings - cargo clippy -p edgezero-adapter-cloudflare --target wasm32-unknown-unknown --features cloudflare --all-targets -- -D warnings - cd examples/app-demo && cargo test --workspace --all-targets
Round-2 review summary — all 7 findings addressed in 4079cde
Gates verified clean on the merged tree (host fmt + clippy + workspace tests + 3 wasm clippy targets + app-demo workspace tests). |
…i + strict-clippy fixes Pulls in 453a44b — chore/strict-clippy's third review round — on top of 4079cde. Two of the three files (the cloudflare scaffold and the app-demo cloudflare adapter) auto-merged cleanly: git took upstream's explicit `use worker::{event, Context, Env, Request, Response, Result}` import + `# Errors` doc + `#[inline]`, while keeping the post-fix 3-arg `run_app::<App>(req, env, ctx)` call this branch shipped in f1179df. `docs/guide/handlers.md` had no conflict (single-hunk doc fix on a section this branch didn't touch). Strict-clippy passes on the merged tree EXCEPT for five lints already pending in the codebase pre-merge that strict-clippy hit on a clean build. Fixed in this commit: - `clippy::arbitrary_source_item_ordering` on `axum/config_store.rs`: my F7 fix in 4079cde inserted `find_project_root_dir{,_from}` free functions BETWEEN `impl AxumConfigStore` and `impl ConfigStore for AxumConfigStore`, splitting two impl blocks across a free-function definition. Reordered so both impls sit together first, then the free functions. - `clippy::absolute_paths` on `axum/config_store.rs`: `&std::env::current_dir().ok()?` → `use std::env;` + `&env::...`. - `clippy::min_ident_chars` on `fastly/cli.rs:504`: `.filter(|s| !s.is_empty())` → `.filter(|svc_id| !svc_id.is_empty())`. - `clippy::unused_trait_names` on `fastly/cli.rs:3`: `use std::io:: {ErrorKind, Write};` → `Write as _` since `Write` is only used anonymously (via `stdin.write_all`). - `clippy::semicolon_outside_block` on `fastly/cli.rs:743`: the bare scoping block around the `stdin.write_all` was there to drop the `&mut` borrow before `child.wait_with_output()` consumes child. Refactored to `child.stdin.take()` + `drop(stdin)` which gives the same drop-before-consume semantics without the bare block. - `clippy::unnecessary_wraps` on `fastly/request.rs:484` AND `cloudflare/request.rs:357`: post-fix `resolve_secret_handle` unconditionally builds `Some(handle)`. Returning `SecretHandle` directly (and wrapping `Some(...)` at the single `SecretSource::On` call site) drops the redundant `Option`. Updated the two host-runnable Fastly regression tests to match. Auto-merged (no conflicts): - crates/edgezero-adapter-cloudflare/src/templates/src/lib.rs.hbs - examples/app-demo/crates/app-demo-adapter-cloudflare/src/lib.rs - docs/guide/handlers.md Verified (all gates green): - cargo fmt --all -- --check - cargo clippy --workspace --all-targets --all-features -- -D warnings - cargo test --workspace --all-targets - cargo clippy -p edgezero-adapter-spin --target wasm32-wasip2 --features spin --all-targets -- -D warnings - cargo clippy -p edgezero-adapter-fastly --target wasm32-wasip1 --features fastly --all-targets -- -D warnings - cargo clippy -p edgezero-adapter-cloudflare --target wasm32-unknown-unknown --features cloudflare --all-targets -- -D warnings - cd examples/app-demo && cargo test --workspace --all-targets
Newly scaffolded EdgeZero projects now get a `.tool-versions` file at the project root, so `asdf install` picks up matching toolchain pins automatically. The file's contents adapt to the adapter set the operator selected at `edgezero new` time: - `rust 1.95.0` — always (every generated project is a Rust workspace). - `nodejs 24.12.0` — added when the cloudflare adapter is in the project (wrangler is a Node binary). - `fastly 15.1.0` + `viceroy 0.17.0` — added when the fastly adapter is in the project (the CLI we shell out to for provision/config push, and what `fastly compute serve` uses for local emulation). - `spin` — no asdf pin; instead a trailing header comment points the operator at https://spinframework.dev/install. The Spin CLI is install-flow-managed across our toolchain (no consistent asdf plugin we pin to), so writing a brittle pin we can't honour would mislead more than it would help. - `axum` — no extra pin (uses the host Rust toolchain only). Version numbers track the repo's own `.tool-versions`. When we bump those, we bump these. Implementation: - New `templates/root/tool-versions.hbs` is a one-liner (`{{{tool_versions_contents}}}`) — the file body is computed in Rust so the per-adapter conditional is type-checkable instead of living in handlebars templating. - New `build_tool_versions(adapter_ids)` in `generator.rs` returns the file body. Pin lines are sorted + deduped so the output is stable regardless of declaration order. - Registered alongside the other root templates in `scaffold.rs::register_templates`, written via `write_tmpl` in `generator.rs`. To stay under the project's clippy `too_many_lines` cap on `render_templates`, the six root files are now written through a new `write_root_files` helper. Regression coverage: - `register_templates_registers_all_known_templates` extended to cover `root_tool_versions`. - `assert_scaffold_files` (used by both end-to-end scaffold tests) asserts the file exists in the generated project. - Six new unit tests on `build_tool_versions`: - rust-only fallback with no adapters, - nodejs added only for cloudflare, - fastly+viceroy added only for fastly, - axum-only matches the no-adapter shape, - spin gets the install-hint comment instead of an asdf pin, - the all-four composite case (no duplicates, sorted output). Verified (all gates green): - cargo fmt --all -- --check - cargo clippy --workspace --all-targets --all-features -- -D warnings - cargo test --workspace --all-targets - cargo test -p edgezero-cli --test generated_project_builds -- --ignored - cargo clippy -p edgezero-adapter-spin --target wasm32-wasip2 --features spin --all-targets -- -D warnings - cargo clippy -p edgezero-adapter-fastly --target wasm32-wasip1 --features fastly --all-targets -- -D warnings - cargo clippy -p edgezero-adapter-cloudflare --target wasm32-unknown-unknown --features cloudflare --all-targets -- -D warnings - cd examples/app-demo && cargo test --workspace --all-targets
# Conflicts: # Cargo.lock # Cargo.toml
…ync) into feature/extensible-cli Pulls in two new commits on chore/strict-clippy since the last sync at 453a44b: - dbb7fa3 Bump redb 4.0 → 4.1.0 (#255) — minor bump within the existing 4.x major. No API or on-disk format changes for the axum adapter's KV store; the workspace lock already resolved HEAD's `redb = "4.1"` to 4.1.0, so the merged Cargo.lock is unchanged in practice. - c666132 Merge remote-tracking branch 'origin/main' into chore/strict-clippy — the main-into-branch sync carrying #255. Conflicts resolved in two files: - Cargo.toml: HEAD's `redb = "4.1"` vs. upstream's `redb = "4.1.0"` are semver-equivalent — kept upstream's more specific pin to track the explicit dependency intent from #255. HEAD's reqwest feature set (`["rustls", "blocking", "json"]`) is kept verbatim: upstream's set (`["rustls"]`) is from before this branch added the `blocking` + `json` features that `edgezero-adapter-fastly`'s CLI shell-out path depends on. HEAD's `rusqlite` block (introduced for the Spin local KV writer's vendored schema) is preserved — upstream didn't have it because the redb bump landed before that work merged. - Cargo.lock: 15 conflict regions. Took HEAD's lock entirely (`git checkout --ours`) — HEAD's lock already pins `redb = "4.1.0"` because `4.1` resolves to the same version, so no manual conflict surgery was needed. Verified via `cargo update -p redb` (zero packages updated, no drift). Verified on the merged tree (all gates green): - cargo fmt --all -- --check - cargo clippy --workspace --all-targets --all-features -- -D warnings - cargo test --workspace --all-targets - cd examples/app-demo && cargo test --workspace --all-targets
ChristianPavilonis
left a comment
There was a problem hiding this comment.
😃 Approving — I did one more correctness-focused pass and did not find any merge-blocking regressions. The main paths I checked are covered by passing workspace/app-demo/generated-project validation, and the remaining items look appropriate as non-blocking follow-ups.
📌 I left a handful of follow-up notes inline using the review emoji guide.
📝 One extra follow-up I did not inline: cargo test -p edgezero-cli --no-default-features --features cli --lib currently fails because adapter-dependent tests still run when no adapter features are linked. That does not appear to be part of the current CI gate, but it is worth cleaning up separately if the slim cli feature combo is intended to be testable.
Three real fixes from PR review feedback:
- .github/workflows/test.yml: switch wasmtime version verification from
`grep -qF "wasmtime $version"` (substring match — accepts `44.0.10`
when pinned to `44.0.1`) to exact comparison via `awk '{print $2}'`.
- crates/edgezero-cli/src/main.rs: replace SimpleLogger with a tiny
custom log::Log impl that prints only `record.args()` — `info`/`debug`
/`trace` to stdout, `warn`/`error` to stderr. SimpleLogger always
emitted `INFO [edgezero_cli::xxx] ...` prefixes even with
`without_timestamps()`, regressing the user-facing CLI UX the
surrounding comment promised.
- docs/guide/handlers.md: simplify the IntoResponse example to
`.map_err(EdgeError::internal)` — drops the undeclared `anyhow`
dependency the previous snippet required.
The reviewer's fourth comment (body.rs as_bytes/into_bytes signature
changes — compat shims or migration docs) is intentionally declined.
The crate is pre-1.0; the project rejects backward-compat shims, and
the new `Option<&[u8]>` / `Option<Bytes>` signatures are the safer ones.
…ields on [stores], reject `__` in app-config keys, args Default fix, Spin smoke runtime-config Addresses the 5 non-blocking follow-ups from the June-11 17:09Z review round on PR #269. 1. Case-insensitive `[adapters.<name>]` lookup + load-time dup rejection (`core/manifest.rs`, `cli/{adapter,config,lib,provision}.rs`) Pre-fix, the manifest stored `[adapters.fastly]` and CLI lookups used exact-case `adapters.get(name)` / `contains_key(name)`. The adapter registry treats names case-insensitively, so `--adapter Fastly` against `[adapters.fastly]` failed at the CLI's pre-check before reaching the registry. New `Manifest::adapter_entry(name)` performs the case-insensitive lookup and returns the manifest's canonical spelling so error messages surface the operator's exact text. `validate_manifest_adapter_keys_case_unique` rejects case-fold duplicate keys (e.g. both `[adapters.fastly]` AND `[adapters.Fastly]`) at manifest load so the lookup is never ambiguous. All five CLI lookup sites switched to `adapter_entry`. 2. `deny_unknown_fields` on `ManifestStores` (`core/manifest.rs`) `[stores.secret]` (typo for `secrets`) was silently ignored — validation passed with no secrets metadata wired. Adding `#[serde(deny_unknown_fields)]` on the parent table catches the typo at parse time, mirroring the rejection rules `StoreDeclaration` already has for legacy fields below it. 3. Reject `__` in app-config keys (`core/app_config.rs`) `foo__bar` (scalar) and `[foo] bar` (nested) both built env path `<APP>__FOO__BAR` — a single env var would ambiguously override both. The walker now rejects keys containing `__` at every level with an actionable error explaining the collision. Mirrors the manifest-side `store_id_format` rule shipped in PR #269 round 1. 4. Manual `Default` impls on the three args structs (`cli/args.rs`) `ProvisionArgs`, `ConfigPushArgs`, `ConfigValidateArgs` all derive `clap::Args` with `#[arg(default_value = "edgezero.toml")]` on `manifest`. Pre-fix they also derived `Default`, which set `manifest = PathBuf::new()` — library callers using `..Default::default()` hit a confusing "no such file" error when `ManifestLoader::from_path("")` ran. Manual `Default` impls now set `manifest = PathBuf::from("edgezero.toml")` matching what clap's CLI parser writes; the value is centralised in a `default_manifest_path()` helper so the clap literal and the `Default` impls stay in sync. 5. Spin smoke scripts pass `--runtime-config-file` + seed (`scripts/smoke_test_{config,kv,secrets}.sh`) The demo's `spin.toml` declares non-`default` KV labels (`app_config`, `sessions`, `cache`) that Spin's runtime only honours when `--runtime-config-file runtime-config.toml` is passed to `spin up`. Pre-fix, all three Spin smoke paths started without the flag and could fail with `unknown key_value_stores label <name>` before readiness. The config smoke ALSO seeds the local Spin KV via `app-demo-cli config push --adapter spin --local --no-env` before `spin up` so the per-key checks observe seeded values, not defaults. Regression coverage (synthetic adapter names — `xenon`/`krypton` — keep these manifest-level tests independent of any specific adapter crate's identity): - `manifest::tests::manifest_rejects_case_fold_duplicate_adapter_keys` - `manifest::tests::manifest_adapter_entry_matches_case_insensitively_returning_canonical_key` - `manifest::tests::manifest_stores_rejects_unknown_kind_at_parse_time` - `app_config::tests::env_overlay_rejects_scalar_key_containing_double_underscore` - `app_config::tests::env_overlay_rejects_double_underscore_in_nested_table_key_too` - `args::tests::provision_args_default_manifest_matches_clap_default` - `args::tests::config_push_args_default_manifest_matches_clap_default` - `args::tests::config_validate_args_default_manifest_matches_clap_default` - `args::tests::default_manifest_path_matches_clap_literal` - Updated `args::tests::config_validate_args_defaults` to expect the post-fix `"edgezero.toml"` instead of the pre-fix `PathBuf::new()`. Verified (all gates green): - cargo fmt --all -- --check - cargo clippy --workspace --all-targets --all-features -- -D warnings - cargo test --workspace --all-targets - cargo clippy -p edgezero-adapter-spin --target wasm32-wasip2 --features spin --all-targets -- -D warnings - cargo clippy -p edgezero-adapter-fastly --target wasm32-wasip1 --features fastly --all-targets -- -D warnings - cargo clippy -p edgezero-adapter-cloudflare --target wasm32-unknown-unknown --features cloudflare --all-targets -- -D warnings - cd examples/app-demo && cargo test --workspace --all-targets
Round-4 review summary — all 5 non-blocking follow-ups addressed in a9ae306
Regression coverage: 9 new tests, plus 1 updated to expect the new Gates clean (fmt + host clippy + workspace tests + 3 wasm clippy targets + app-demo workspace tests). |
…ndlers.md) into feature/extensible-cli Pulls in dfb00b3 — the third strict-clippy round-of-review-fixes commit since the last sync at c666132: - .github/workflows/test.yml: switch wasmtime version verification from `grep -qF "wasmtime $version"` (substring match — accepts `44.0.10` when pinned to `44.0.1`) to exact comparison via `awk '{print $2}'`. Auto-merged cleanly. - docs/guide/handlers.md: simplify the IntoResponse example to `.map_err(EdgeError::internal)` — drops the undeclared `anyhow` dependency the previous snippet required. Auto-merged cleanly. - crates/edgezero-cli/src/main.rs: upstream introduced a custom `CliLogger` (`log::Log` impl that prints `record.args()` verbatim with no `INFO [edgezero_cli::xxx]` prefixes) to replace the previous `SimpleLogger`-based init. The fix is real: `SimpleLogger::without_timestamps()` still emits the `INFO [module]` prefix, regressing the user-facing CLI UX the surrounding doc promised. Upstream put the logger code in the binary (`main.rs`). On this branch, the logger lives in the library (`lib.rs::init_cli_logger`), called by the binary, so a downstream CLI built on `edgezero-cli` can reuse it. Conflict resolution: kept HEAD's thin `main.rs` (no logger code in the binary) AND ported the upstream `CliLogger` improvement INTO `lib.rs`, replacing the `SimpleLogger`-based body of `init_cli_logger` with the routing rules upstream wrote (info/debug/trace → stdout, warn/error → stderr). That gives us the actual UX fix on this branch's architecture. `simple_logger` stays in the dep graph for the non-`cli`-feature `main()` branch (still uses it to print the "rebuild with --features cli" hint). Verified on the merged tree (all gates green): - cargo fmt --all -- --check - cargo clippy --workspace --all-targets --all-features -- -D warnings - cargo test --workspace --all-targets - cd examples/app-demo && cargo test --workspace --all-targets
… feature/extensible-cli PR #257 (chore/strict-clippy) was squash-merged into main as 7ec2ad1, so the same content this branch had been pulling in piecewise via repeated `origin/chore/strict-clippy` merges (c666132, 1b7342f, 3082391, a04814d, 9175cff, e526e6b) now arrives as a single tree-level diff against main. Tree-level reconciliation produced 54 conflicts because the squash commit ships the pre-rewrite/pre-PR-#269 shape of each affected file (old `Command::{ Build {…}, Deploy {…}, Serve {…}, Dev }` enum variants, `handle_build/deploy/serve` routing in `main.rs`, old `SimpleLogger` init, missing `Auth`/`Config`/`Provision` subcommands, old store/manifest types, etc.), while HEAD has the fully-rewritten state that PR #269 has been delivering and reviewing for the last several rounds. Resolution: take HEAD for all 54 conflicts. The squash commit adds nothing this branch doesn't already have via the prior piecewise merges — every conflict region's upstream side is content this branch explicitly rewrote. Confirmed by walking the conflict markers and matching each upstream hunk to a corresponding rewrite already on HEAD (case-insensitive adapter lookup, manual `Default` impls, `[stores]` `deny_unknown_fields`, `__` rejection in app-config keys, F1-F7 fastly/cloudflare/spin fixes, `.tool-versions` scaffold template, `CliLogger` in lib.rs, etc.). One additional new-file delete: `crates/edgezero-cli/src/dev_server.rs` (added by the squash) is the pre-rewrite location of the dev server; HEAD has moved it to `crates/edgezero-adapter-axum/src/dev_server.rs`. Removed the stray upstream-added file. Plus one real round-5 review-feedback fix carried over from upstream's `75a01ec strict-clippy: round-5 review feedback (cli args + logger)`. That commit had two fixes: 1. `args.rs`: drop the unused `--local-core` flag from `NewArgs`. This branch already doesn't have it (we dropped it as part of earlier rewrites), so it's already in the right state. 2. `lib.rs::CliLogger`: filter `debug`/`trace` explicitly rather than routing them to stdout alongside `info`. Pre-fix the match arm wrote `info|debug|trace => println!(...)` and the doc claimed all three went to stdout, but `enabled()` already capped at `<= Info` and `LevelFilter::Info` was set globally so debug/trace never reached `log()` — the comment promised behaviour the impl couldn't deliver. Split the match arm so `info => println!`, `debug | trace => {}`, and the doc now says "info to stdout, warn/error to stderr; debug/trace filtered out (no verbosity flag yet)". Applied here on lib.rs since this branch already moved `CliLogger` out of `main.rs` (where upstream put it) into the library so downstream CLIs built on `edgezero-cli` reuse the same prefix-less output. Verified on the merged tree (all gates green): - cargo fmt --all -- --check - cargo clippy --workspace --all-targets --all-features -- -D warnings - cargo test --workspace --all-targets - cargo clippy -p edgezero-adapter-spin --target wasm32-wasip2 --features spin --all-targets -- -D warnings - cargo clippy -p edgezero-adapter-fastly --target wasm32-wasip1 --features fastly --all-targets -- -D warnings - cargo clippy -p edgezero-adapter-cloudflare --target wasm32-unknown-unknown --features cloudflare --all-targets -- -D warnings - cd examples/app-demo && cargo test --workspace --all-targets
Epic: #268
Summary
Turns
edgezero-cliinto an extensible library, rewrites the manifest store schema and runtime to a multi-store model, addsauth/provision/config validate/config pushcommands, replaces thedevsubcommand with a contributor-onlydemo, and updatesapp-demoto exercise everything across axum / cloudflare / fastly / spin. Plus two integrations completed in-branch: Spin 6.0 (WASI Preview 2) andchore/strict-clippy(PR #257).Delivered as one PR, eight sequential stages (one per sub-issue of #268), with additional hardening from multi-round self-review and two integration cycles on top.
[stores.*]fields become hard load errors.chore/strict-clippy(PR Enable strict clippy with documented allow-list and defensive-coding pass #257) —origin/chore/strict-clippyhas been merged into this branch (merge commit611064e) so the diff againstchore/strict-clippyis the actual feature delta. Base retargets tomainwhen Enable strict clippy with documented allow-list and defensive-coding pass #257 merges.Stages (sub-issues of #268)
config validatecommandauthcommand +CommandRunnerinfrastructureprovisioncommandconfig pushcommandapp-demointegration polish + docs auditIn-branch integrations on top of the eight stages
Spin 6.0 (WASI Preview 2) —
487ac5fspin-sdk = "6"workspace + app-demo + generator defaultwasm32-wasip1→wasm32-wasip2for the Spin adapter only; Fastly stays on wasip1. CI matrix entry + CLAUDE.md + docs + scripts + scaffold template + app-demo manifests all updated.IncomingRequest→Request;req.into_parts()+IncomingBodyExt::bytes(); deleted the 10-armMethodenum match (Methodis now a re-export ofhttp::Method);Request::builder()…build()→.body(FullBody::new(Bytes::from(...)))?; asyncStore::open/get/set/delete/exists/get_keys; asyncvariables::get;#[http_component]→#[http_service].Response<FullBody<Bytes>>; contract tests usehttp_body_util::BodyExt::collect().chore/strict-clippyintegration —611064eorigin/chore/strict-clippyso the workspace-wide strict-clippy gate (pedantic warn + restriction deny, 13-item allow-list) sits underneath everything in this PR.adapter-wasm-clippyCI matrix added in Enable strict clippy with documented allow-list and defensive-coding pass #257; switched its spin entry from wasip1 → wasip2 to match the Spin 6 migration.Strict-clippy pass on wasm32 adapter targets —
910739dadapter-wasm-clippymatrix to green: cloudflare (~44 errors), fastly (~14), spin (~115).#[allow]sprinkles):pub use mod::Foo→pub mod foo;absolute_paths→useimports;min_ident_chars→ renames (|e|→|err|,|c|→char::is_control);#[inline]+# Errorson every public fn;arbitrary_source_item_orderingreordering;tests_outside_test_module+expect_usedin tests fixed by wrapping in#[cfg(test)] mod tests { ... };used_underscore_items→const _: fn() = assert_provider_impl::<T>;;let_underscore_must_use→drop(init_logger()); type aliasSpinFullResponse = Response<FullBody<Bytes>>for the recurring complex type;HeaderValue::from_static("spin")to killexpect_usedinproxy.rs;.saturating_add()forarithmetic_side_effects.Self-review followups closed in this PR
Two close-out commits address findings from the post-merge self-reviews:
16898f8— Store id validation (rejects blank / whitespace / control / duplicate logical ids withstore_id_blank/store_id_duplicateerrors); docs ESLint ignore for.vitepress/.tempso the docs lint gate is build-state-independent; removed the misleading singularStoreDeclaration::config_store_name(&self, _adapter)helper.4928cdc— Spin wasm contract CI fix (tests insert*Registry::single_id(...)instead of bare handles so they match the dispatch boundary'ssynthesise_store_registries);generated_project_buildsnow invokes the generated typed CLI'sconfig validate --strict(catchesAppConfigdrift the raw validator can't); docs build fix (multi-line inline backticks incli-walkthrough.mdpush bullets converted to fenced shell blocks — VitePress was parsing the leaked<tempfile>/<id>as unterminated HTML tags); provision + push docs now describe the<platform-name>resolution (EDGEZERO__STORES__<KIND>__<ID>__NAME→ logical id) for cloudflare/fastly instead of the wrong "matched by<store_id>" wording; Spin docs corrected on secret translation (config keys translate.→__;#[secret]values are only lowercased — matchesSpinSecretStore::get_bytes+ the cli validator exactly); stale plan doc references updated (app-demo IS in CI; Spin target IS wasip2); axum secret store rustdoc no longer references the removedcargo edgezero devcommand.Design + plan
Committed under
docs/superpowers/(force-added — that path is currently in.gitignore; maintainers can decide whether to formally track it):docs/superpowers/specs/2026-05-19-cli-extensions-design.mddocs/superpowers/plans/2026-05-20-cli-extensions.mdTest plan
cargo fmt --all -- --checkcargo clippy --workspace --all-targets --all-features -- -D warnings(host strict-clippy)cargo test --workspace --all-targets(1057+ tests pass)cargo check --workspace --all-targets --features "fastly cloudflare spin"cargo check -p edgezero-adapter-spin --target wasm32-wasip2 --features spincargo check -p edgezero-adapter-fastly --target wasm32-wasip1 --features fastlycargo check -p edgezero-adapter-cloudflare --target wasm32-unknown-unknown --features cloudflarecargo clippy -p edgezero-adapter-cloudflare --features cloudflare --target wasm32-unknown-unknown --all-targets -- -D warningscargo clippy -p edgezero-adapter-fastly --features fastly --target wasm32-wasip1 --all-targets -- -D warningscargo clippy -p edgezero-adapter-spin --features spin --target wasm32-wasip2 --all-targets -- -D warningscd examples/app-demo && cargo test --workspace --all-targetscd examples/app-demo && cargo fmt --check && cargo clippy --workspace --all-targets --all-features -- -D warningscargo check -p app-demo-adapter-spin --target wasm32-wasip2cargo test -p edgezero-cli --test generated_project_builds -- --ignored(now also runs the generated typed CLI'sconfig validate --strict)cd docs && npm run lint && npm run format && npm run buildwasmtime run(12/12 — reviewer-verified locally; CI matrix runs the same)