feat: implement portable outbound HTTP and request lifecycle contracts - #275
Draft
aram356 wants to merge 305 commits into
Draft
feat: implement portable outbound HTTP and request lifecycle contracts#275aram356 wants to merge 305 commits into
aram356 wants to merge 305 commits into
Conversation
Previous commit only went to v5 for the three Node-deprecation actions.
Audit of all actions used across the four workflows shows five more
behind by one or two majors:
actions/checkout v5 → v6
actions/setup-node v5 → v6
actions/configure-pages v4 → v6
actions/deploy-pages v4 → v5
actions/upload-pages-artifact v3 → v5
All other pins are already current:
actions/cache v5 (latest)
actions-rust-lang/setup-rust-toolchain v1 (latest)
github/codeql-action/{init,analyze} v4 (latest)
# Conflicts: # .github/workflows/test.yml
CodeQL's `rust/cleartext-logging` rule (alert #7) taints any value returned by a function whose name contains "secret" — it can't tell configuration metadata (the binding identifier from edgezero.toml) from secret material. The previous rename `secret_store_name → secret_store_binding` did NOT defeat the heuristic because "secret" is still in the function name. Real fix: stop logging the binding name. Operators can read their own `edgezero.toml` to verify which store binding was configured. The presence message ("secrets enabled for axum") is still emitted, which is the only thing the log line was actually load-bearing for. Updated the affected unit test assertion to match the new wording.
Same heuristic as alert #7 — CodeQL taints any value returned by a function whose name contains "secret" and tracks it through to HTTP sinks. The test helper `start_test_server_with_secret_handle` was flagged because its return value's `base_url` flowed into `reqwest::Client::get(url)`. Rename the helper to `start_test_server_with_store_handle` and the return struct to `TestServerWithStore`. Functionally identical — the test just bootstraps a dev server with an optional handle. The remaining `with_secret_handle` builder method on `AxumDevServer` is unaffected because it returns `Self`, not a sink-bound value.
Three real coverage gaps from earlier commits were untested:
1. `KvStore::put_bytes_with_ttl` overflow error path
(axum/PersistentKvStore). Asserts `Duration::MAX` triggers
`SystemTime::checked_add` overflow and surfaces as
`KvError::Internal("ttl overflows system time")`.
2. `Manifest::try_load_from_str` Err path. Two cases: invalid TOML
bytes and a manifest that fails `validator` (empty config-store
name). Both should return `io::ErrorKind::InvalidData`.
3. `GeneratorError::Format` smoke test. The variant cannot fire in
practice (write-to-String is infallible), but it is part of the
public error surface and the `From<fmt::Error>` wiring must keep
working — assert construction + Display.
Existing coverage for the other behaviour-affecting changes was
already adequate: `KvStore::exists` is exercised by the
`contract_exists` macro across every impl plus 3 dedicated unit
tests, and `Hooks` default-method overrides are exercised by the
`TestHooks`/`DefaultHooks` tests already in app.rs.
ctor 1.0 requires explicit `#[ctor(unsafe)]` to acknowledge that
pre-main static-initialisation runs without the usual Rust safety
guarantees. The annotation is an attribute argument, not an
`unsafe { }` block, so the workspace `unsafe_code = "deny"` lint is
still satisfied. Updated the four adapter cli.rs files
(axum/cloudflare/fastly/spin).
spin-sdk 6.0 is NOT bumped: it raises the MSRV to rustc 1.93 but the
workspace ships rustc 1.91.1 (.tool-versions). Pin stays at 5.2 with
an explanatory comment until we bump the toolchain.
Bumps `.tool-versions`:
rust 1.91.1 → 1.95.0
viceroy 0.16.4 → 0.17.0
Both viceroy 0.17 and spin-sdk 6.0 raised their MSRV to rustc 1.93/1.95
respectively. We can now take viceroy 0.17 freely; spin-sdk 6.0 has
breaking API changes (Method variants → http::Method constants,
`IncomingRequest` removed, Builder::build() → .body()) and is left at
5.2 with a TODO until a focused migration PR.
New 1.95 clippy lints fixed in-place:
- `result_map_unwrap_or_default`: `.map(p).unwrap_or(false)` → `.is_ok_and(p)` (2 sites)
- `manual_map`: `.map(x).unwrap_or(default)` → `.map_or(default, x)` (1 site)
- `duration_suboptimal_units`: `Duration::from_secs(60)` → `from_mins(1)` in
non-const contexts. Two const items keep `from_secs(60 * 60 * 24 * 365)`
with a localized `#[expect(clippy::duration_suboptimal_units, reason =
"from_days/from_mins not stable in const context")]` because
`Duration::from_{mins,days}` const variants are still nightly-only.
- `to_string_in_format_args` / `inefficient_to_string`: replaced two
`ToString::to_string` / `str::to_string` with `str::to_owned`
- `missing_inline_in_public_items`: added `#[inline]` to two proc-macro
entrypoints in edgezero-macros, three EnvOverride methods + the
`env_guard` helper in axum/test_utils, and `From<Action>` for
AdapterAction in cli/adapter.rs
- `doc_paragraph_terminators`: added trailing punctuation to clap doc
comments on every variant/field of `Command`/`NewArgs` (cli/args.rs)
and the `KV_TABLE` doc in axum/key_value_store.rs
Docs:
- CLAUDE.md "Rust": 1.91.1 → 1.95.0
- CLAUDE.md "Fastly CLI": v13.0.0 → 15.1.0
- Fix typo `fasltly` → `fastly` in .tool-versions; remove dup line
- examples/app-demo/.../rust-toolchain.toml: 1.91.1 → 1.95.0
- test.yml: drop the now-stale "1.91 MSRV constraint" comment on the
viceroy install step
Both warnings sat behind `#[cfg]` gates that the `--all-features` build profile hid: 1. `fastly::init_logger` (no-features stub) needed `#[inline]` — `missing_inline_in_public_items` only fires when the stub branch is selected, i.e. when the `fastly` feature is off. 2. `cli::dev_server::EchoParams` (no-`dev-example` build) was defined after `default_router`/`build_dev_router`; the canonical item ordering wants structs before fns at module level. Moved `EchoParams` to the top of the module so the order is correct in either feature profile. Surfaces only via `cargo clippy --workspace --all-targets` (no `--all-features`); the existing CI runs `--all-features` so we did not catch this until now.
The `https://wasmtime.dev/install.sh` script broke as of 2026-05-19: its version-detection interpolation failed and it tried to download literal version `{`, causing the spin-wasm-tests CI job to fail ("Could not download Wasmtime version '{'"). Replace the install path with a direct GitHub-release tarball download, pinned to the version recorded in `.tool-versions` (same single-source-of-truth pattern already used for rust + viceroy). Adds `wasmtime 44.0.1` to `.tool-versions` and a `Resolve Wasmtime version` step in the workflow that greps it out.
1. `pub_with_shorthand` comment direction was reversed in the workspace `Cargo.toml`. Confirmed by removing the allow: 6 sites fire `usage of \`pub\` without \`in\`` (i.e. clippy flags `pub(crate)` and wants `pub(in crate)`). Restore the allow with wording that matches the actual lint direction and reflects the audited 6-site count. 2. Workspace `.cargo/config.toml` was hard-coding the `wasm32-wasip1` runner to Viceroy, which silently broke `cargo test -p edgezero-adapter-spin --target wasm32-wasip1` from the workspace root (used viceroy host ABI instead of wasmtime). Fix: remove the workspace-level runner entirely and add a per-package config for spin (`crates/edgezero-adapter-spin/ .cargo/config.toml`) that selects `wasmtime run`. Fastly already had its own per-package config. CI continues to override via `CARGO_TARGET_WASM32_WASIP1_RUNNER` env var, so workspace-root invocations work in CI without the global default. 3. Add a module-level doc comment at the top of `crates/edgezero-adapter-spin/tests/contract.rs` explaining that the tests cover internal router/dispatch logic, NOT the Spin host ABI (no `spin_sdk`/WIT imports). A breaking change in the Spin runtime's WIT would not be caught here.
`parse_handler_path` previously panicked on a syntactically-invalid handler path in `edgezero.toml`, which rustc surfaced as a confusing "proc-macro panicked" message. Refactor to return `Result<ExprPath, String>`; `build_middleware_tokens` and `build_route_tokens` propagate the error; `expand_app` returns `compile_error!()` with the message, matching the existing error path for manifest read/parse/validation failures. Two new tests: parse_handler_path_accepts_absolute_crate_path (happy path) and parse_handler_path_rejects_invalid_syntax_with_message (asserts the error message names the failure and echoes the offending input). Addresses the PR review comment on `crates/edgezero-macros/src/app.rs`.
PR reviewer claimed the lint warns *against* longhand and recommends
shorthand (i.e. our `pub(crate)` use should never fire it). Verified
empirically — removing the allow on clippy 1.95 produces 6 errors:
error: usage of `pub` without `in`
| pub(crate) fn decompress_body(...)
| ^^^^^^^^^^ help: add it: `pub(in crate)`
= help: ...index.html#pub_with_shorthand
So `pub_with_shorthand` flags `pub(crate)` and suggests `pub(in crate)`;
the reviewer's reading is 180° off. Quote the diagnostic in the comment
itself so future maintainers don't fall into the same trap.
Sub-project #1 of 7 in the CLI extensions roadmap. Turns edgezero-cli into lib + bin, exposes per-command Args structs and run_* functions for downstream projects to compose their own CLIs via clap subcommand flattening, and adds app-demo-cli as the canonical consumer. Force-added because docs/superpowers/ is gitignored project-wide for plans; this spec is shared design intent and meant to be reviewed in the repo.
Replaces the sub-project-#1-only spec with a single design document that covers the full effort: extensible edgezero-cli library, generator updates for <name>-cli and <name>.toml scaffolding, per-service typed app-config schema with validator integration, four new commands (auth, provision, config validate, config push), shell-out mocking via a private CommandRunner trait, and the app-demo overhaul that exercises everything end-to-end. Implementation still ships in 7 incremental PRs but the design decisions live in one place so reviewers see the whole picture. Force-added because docs/superpowers/ is gitignored project-wide.
High-severity fixes: - Add --manifest to ProvisionArgs and ConfigPushArgs (matches validate) - Update Wrangler invocations to 3.60+ syntax (space-form, --namespace-id) - Persist provisioned IDs in edgezero.toml [stores.*.adapters.<x>].id; cross-write to per-adapter manifests where deploys need them - Mermaid diagram in §3 replacing ASCII art Medium-severity fixes: - config push runs strict validation as pre-flight (no separate flag) - Move --adapter to each AuthSub variant so UX is `auth login --adapter X` - Constrain typed config push to serde_json::to_value(C) -> Object; document flatten / rename / skip / Option::None handling - Unify raw + typed serialization rules; raw drops Validate + secret skip - Replace CommandRunner positional args with CommandSpec struct (program, args, cwd, stdin, env) - "Backwards-compatible" language replacing "unchanged" for default bin - Move walkthrough doc to docs/guide/ with explicit sidebar update Low + open questions: - Document consumer-facing Cargo feature names and adapter opt-outs - Generator migration note: sub-project 1 outputs don't auto-migrate - Deprecate [stores.config.defaults] in favor of <name>.toml [config] - Mark Spin provision / config push as "not yet supported" with pointer to the in-flight Spin stores PR; clear error message until then Secret annotation: - New §6.6 documenting #[derive(AppConfig)] from edgezero-macros - #[secret] field attribute marks runtime-secret-store-backed fields - Toml value for those fields is the secret-store binding name - config validate (typed) cross-checks the binding appears in [stores.secrets] - config push (typed) skips SECRET_FIELDS entirely The implementation still ships in 7 incremental PRs.
…cope Manifest schema rewrite (new sub-projects #2 and #3): - [stores.<kind>].ids = [...] + default declare the logical stores the app uses (kv, secrets, config all multi-store) - [adapters.<X>.stores.<kind>.<id>].name = "..." maps each logical id to the platform-specific name on adapter X, with optional adapter-specific tuning fields stored as free-form extras - Provisioned platform resource IDs (Cloudflare namespace ID, Fastly store ID) live in each platform's native manifest (wrangler.toml, fastly.toml), not in edgezero.toml. provision writes them there; config push reads them back. - RequestContext store accessors become id-keyed: ctx.kv_store("id") / ctx.kv_store_default() (and similarly for config_store / secret_store). Each adapter builds a StoreRegistry<H> at request setup from [adapters.<self>.stores.*]. - Manifest validator enforces: ids non-empty; default in ids; every adapter has a name mapping for every id. Naming: - Field on the per-adapter block is `name` (matches the user's example), not `binding`. The Cloudflare wrangler.toml term `binding` is now called out as wrangler's terminology, not ours. Secret references (§6.7): - The string a #[secret] field holds is an app-defined reference; the spec documents both valid runtime patterns (logical store id or key within the default secret store). Validate just confirms the string is non-empty and that the app has a secret store available. config validate (§11) explicitly covers app-config validation: - TOML syntax, [config] table presence, type matching against C, serde-rejected unknown fields, validator business rules, non-empty secret references, and the manifest-side cross-checks. Sub-project count: 7 → 9 (added schema rewrite + RequestContext API rewrite as #2 and #3; existing app-config/validate/auth/provision/push/ polish become #4-#9). This is a breaking change to the on-disk manifest schema; the in-tree example/app-demo is migrated as part of the work, and a migration guide ships with sub-project #2.
…cret forms HIGH severity fixes: - Cloudflare config store rewritten from [vars] to KV (§6.9) so `config push` actually reaches the runtime without redeploying. Lands in sub-project #3 alongside the rest of the runtime work. - Sub-project #2 is now purely additive on the schema: no runtime changes, no removal of [stores.config.defaults]. The runtime bridge and the defaults removal move out of #2 (into #3 and #9 respectively). - Spin completeness: validator skips adapters without an [adapters.<X>.stores] section. App-demo's Spin adapter omits stores until the in-flight Spin stores PR lands. - Extractor design (§6.8): existing Kv / Secrets extractors keep working as default-store accessors; new KvNamed<const ID> / SecretsNamed<const ID> extractors give type-safe named access. No handler-facing break. - Hooks, ConfigStoreMetadata, and app! macro added to sub-project #3 scope; they all become id-keyed. Multi-store rewrite is now complete. MEDIUM severity fixes: - Validate bound is DeserializeOwned + Validate + AppConfigMeta (no Serialize). The serde_json::to_value object check is push-only; push adds Serialize. - Secret semantics: two explicit forms via attribute. #[secret] = key inside the default secret store. #[secret(store_ref)] = logical store id in [stores.secrets].ids. Validate cross-checks the latter. - AppConfigMeta::SECRET_FIELDS is now &'static [SecretField] carrying SecretKind so the CLI can apply the right validation per field. - #[secret] constrained to non-flattened, non-renamed scalar fields; combinations with #[serde(flatten)] / rename / skip produce compile errors. Macro tests cover the constraints. - Unknown-field rejection is no longer a validate guarantee; the generator template emits #[serde(deny_unknown_fields)] on the generated config struct so new projects opt in by default. - Every public *Args derives Default + #[non_exhaustive]; external construction documented as Default + field mutation. LOW severity fixes: - Macro example fixed: #[proc_macro_derive(AppConfig, attributes( secret))] in edgezero-macros/src/lib.rs directly. No bogus _impl re-export. - Cloudflare-invalid JS-identifier `name` values are errors (would break worker deploy), not warnings. Sub-project ordering and risk: - #2 risk dropped to L (purely additive). - #3 grows to absorb Cloudflare KV swap + Hooks/macro/extractor. - #9 now also drops [stores.config.defaults] and wires axum dev-server to seed from <name>.toml.
HIGH severity fixes: - ConfigStore::get becomes async (#[async_trait(?Send)]). Cloudflare config moves [vars] -> KV with real async reads. Cascade (trait, 3 adapter impls, Hooks, handlers, extractors) contained to #3. - Drop const-generic &'static str extractors (don't compile on stable 1.95). Kv / Secrets extractors refactored to yield a registry handle with default() / named(id) accessors. - Introduce BoundKvStore / BoundConfigStore / BoundSecretStore so runtime accessors return a handle bound to the resolved platform name; callers just .get(key).await. - Sub-project #2 models logical store declarations as Option<LogicalStoreConfig> so old-shape manifests (None) are distinguishable from new-but-incomplete ones (Some with empty ids). Keeps #2 genuinely additive. MEDIUM severity fixes: - Fastly native-manifest writeback: spec commits to a read/write-path- agreement contract; exact fastly.toml sections pinned in #7's plan. - Adapter store completeness uses an explicit STORES_SUPPORTED_ADAPTERS allowlist (axum, cloudflare, fastly). A supported adapter omitting [adapters.<X>.stores] is an error; only non-allowlisted adapters (spin) skip. - All "default store" prose uses the resolved default id (explicit default, else single ids[0]). - AuthArgs no longer derives Default (avoids a placeholder subcommand leaking into a real auth path). §6.11 documents which *Args get Default. - config push gains explicit "validate passes, push serialization fails" test scenarios (non-object typed config, compound shapes, skip_serializing_if, Option::None, flatten). LOW severity: - Ship-gate wording: existing commands stay backwards-compatible rather than "edgezero --help unchanged" (false once auth/provision/ config land). New requirement - environment-variable override resolution (§6.10): - load_app_config overlays env vars on the toml [config] table. - Env var format: <APP_NAME>__<SECTION>__..__<KEY>; __ separates every nesting level; APP_NAME is [app].name uppercased, hyphens to underscores. - Type coercion against the target TOML type; --no-env escape hatch on validate and push. app-demo (§15) now explicitly exercises every new capability: multi- store, async config, named-kv extractor, nested config section, env override, both secret forms, validate/push, auth/provision via mock.
…n, Fastly contract HIGH severity fixes: - Manifest old-vs-new discrimination corrected. Existing manifests already have [stores.kv/secrets/config] tables, so table-presence can't discriminate. Sub-project #2 now uses compatibility structs carrying legacy fields (name, legacy adapters) plus new logical fields (ids, default) side by side; the discriminator is ids.is_some(). The current app-demo edgezero.toml parses unchanged. - Hooks cannot return bound handles. Hooks / ConfigStoreMetadata are static compile-time app metadata; bound handles need per-request adapter state. Split: Hooks/app! emit store metadata registries; only RequestContext returns Bound*Store handles. Adapters consume the metadata at request setup to build the runtime registries. - Env overlay type coercion: with C: DeserializeOwned there is no pre-deserialization type reflection. Env vars now override existing keys only, coerced to the existing TOML value's type. Matches the current AxumConfigStore::from_env behavior. To make a key env-overridable it must appear in <name>.toml. - Axum config push and runtime read agreed: the axum config store is backed by .edgezero/local-config-<id>.json; config push --adapter axum writes that file; edgezero dev regenerates it at startup. No more disagreement between push target and dev-server source. MEDIUM severity fixes: - Fastly writeback contract made concrete from Fastly's docs: [setup.<kind>_stores.<name>] + [local_server.<kind>_stores.<name>] keyed by resource link name (== our `name`). provision creates the store and ensures both fastly.toml sections exist; config push resolves the store id on demand via `fastly config-store list --json` (Fastly has no stable persisted id slot). Read/write paths all key off [adapters.fastly.stores.<kind>.<id>].name. - Env key matching is deterministic and ambiguity-rejecting: keys transform to an env segment form (uppercase); two siblings mapping to the same segment is an AppConfigError. No case-insensitive fuzzy fallback. - Cloudflare KV eventual consistency: §6.9 no longer claims values are live "on the next request"; CI does not assert immediate global Cloudflare visibility. LOW severity: - BoundSecretStore keeps the existing bytes::Bytes API (get -> Option<Bytes>, require_str), not Vec<u8>.
…e-PR delivery Hard cutoff (per user directive — projects fully migrated, no compat): - Removed all old-vs-new manifest discrimination: no compat structs, no ids.is_some() check, no legacy-field parsing. The store schema is rewritten outright. Legacy fields (name, legacy adapters overrides, [stores.config.defaults]) are hard load errors pointing at the migration guide. Spin as a first-class store-capable adapter (PR #253 baseline): - Removed the "Spin deferred" non-goal. Spin participates fully. - New §6.7 Spin store semantics: KV is label-backed multi-store with a max_list_keys cap; config and secrets are both spin_sdk::variables — a single flat namespace, lowercase [a-z0-9_] keys, no dots. - Replaced the flat STORES_SUPPORTED_ADAPTERS allowlist with an adapter x kind capability matrix (Multi vs Single). Validation: if any target adapter is Single for a kind, [stores.<kind>].ids must have exactly one id (you cannot have two config stores if you also target Spin). - §6.4 config key model: nested config flattens to dotted keys; canonical handler form is dotted; Spin config store translates . -> __ internally; config push writes platform-native key form. - Spin wired into commit 2 (runtime registry, async ConfigStore now cascades across all FOUR adapters), commit 6 (provision: spin.toml writeback for key_value_stores / [variables] / [component.<name>.variables]), commit 7 (config push: Spin variables in spin.toml). - provision now has explicit axum (no-op, prints local-store note) and spin (manifest writeback, no CommandRunner) contracts; config push is split per adapter — no universal native-resource-ID assumption. Other review fixes: - Default resolution made strict: `default` required when ids.len() > 1. - Docs config path corrected to docs/.vitepress/config.mts (not .ts). Delivery: one PR with eight commits (one per sub-project), not eight PRs. CI gates the PR head; each commit should still build for bisectability. Sub-project count stays at 8 (manifest+runtime stay merged as the atomic commit 2).
Nine findings against the current (f0aed20) spec, all Spin-integration depth: - Spin provision cannot know config/secret variable keys (manifest has store ids, not field keys). Fix: Spin provision does KV-label spin.toml writeback ONLY. Config-variable declaration moves to config push (which loads <name>.toml). Secret-variable declaration is manual. - config push --adapter spin must write BOTH [variables] (declaration + default) and [component.<name>.variables] (binding) — a Spin variable is unreadable without the component binding. Errors rather than writing a half-configured manifest. - Spin component discovery specified: parse spin.toml; single component resolves implicitly; multi-component requires [adapters.spin.adapter].component; config validate --strict surfaces failures early. - Secret variables are not inferable (#[secret(store_ref)] runtime keys are code-local). Spin secret variables are declared manually by the developer; the CLI never writes them. - Config/secret namespace collision guarantee was wrong: #[secret] field VALUES (not Rust field names) are the secret keys. config validate now computes the effective Spin variable set ({flattened config keys} u {#[secret] values}) and errors on duplicates. - Spin KV TTL: BoundKvStore exposes put_*_with_ttl (verified in key_value_store.rs). On Spin these return a deterministic KvError::Unsupported, never silent store-without-expiry. - Spin KV listing-cap error variant flagged as an open reconciliation point with PR #253 (Validation -> a limit/server error); resolved in commit 2, not a blocker. - Single (adapter, kind) per-id mapping blocks are now FORBIDDEN (validation error), not "accepted but vestigial". Fixes the §1 vs §6.6 contradiction. - Spin variable naming rule pinned as Spin's own ^[a-z][a-z0-9_]*$ (cites spinframework.dev/manifest-reference), not an EdgeZero rule. app-demo (§15) updated: manually declares Spin secret variables, single-component spin.toml, asserts Spin provision writes only key_value_stores and config push writes both spin.toml tables.
Seventh-pass review fixes (against 27a6169): - KvError::Unsupported does not exist today — spec now states commit 2 adds the variant with a 5xx-class EdgeError mapping (Spin TTL writes). - Spin listing-cap error resolved in-spec, not left open: commit 2 adds KvError::LimitExceeded (5xx-class), and the Spin listing path returns it past max_list_keys, replacing PR #253's KvError::Validation. - run_dev() -> ! corrected: the dev server may return. Now run_demo() -> Result<(), String>; commit 1 adjusts the dev-server boundary (today it returns ()). - Commit 2 bisectability: added a config-seeding story — the axum config store's backing-file contract lands in commit 2, but commit-2 tests seed the .edgezero/local-config-<id>.json fixture directly; config push / demo-regeneration that produce the file land in commits 7/8. - Spin config/secret collision check clarified as typed-only (needs AppConfigMeta::SECRET_FIELDS); raw validation does the key-syntax and component-discovery checks but not the collision check, and says so in its diagnostics. - Spin variable-name rule kept pinned to spinframework.dev docs. dev → demo subcommand rename (per user): - The subcommand that runs the example app locally on axum is now `demo`; `dev` is reserved for a future dev-workflow command. - run_dev → run_demo, Command::Dev → Command::Demo, the CLI's dev_server module → demo_server. The edgezero-adapter-axum crate's own internal dev_server module is left as-is (not user-facing). Documentation update step (per user): - New §6.12 makes documentation part of every commit's definition-of-done, with a page→commit ownership table (cli-reference, configuration, kv, handlers, getting-started, adapters/cloudflare, adapters/overview, architecture). - Commit 8 ends with a documentation audit: grep docs/ for stale references (old manifest keys, dev subcommand, old store API), confirm none remain, confirm the .vitepress/config.mts sidebar is complete, docs CI green.
- Commit 2 bisectability vs AppDemoConfig: §8 now states commit 2's app-demo handler migration is store-accessor-only (ctx.kv_store(id), config_store, the refactored extractors). AppDemoConfig and any typed-app-config handler work are commit 3 — commit 2 never references a type that lands in commit 3. - #[secret(store_ref)] vs Single-secrets capability: §6.8 spells out that axum/cloudflare/spin are all Single for secrets, so any app including one of them has exactly one secrets id, and every #[secret(store_ref)] field must resolve to it. store_ref only buys multiple secret stores on a Fastly-only project. §15 / the walkthrough show this for the all-four-adapter app-demo. - Spin variable-name rule drift guard: commit 7 gets a golden-file test on the generated spin.toml — asserts every variable name matches ^[a-z][a-z0-9_]*$ and that the generated manifest parses (round-trips through the same parser the runtime uses), so the rule cannot drift from Spin's actual manifest behaviour. Reviewer confirms no blocking design issues remain.
- Spin manifest validation strength: the spin.toml golden test now specifies a strongest-first ladder — (1) the spin CLI's own manifest validation when present (the wasm32 spin CI job already installs it), (2) a spin_sdk validation entry point if exposed, (3) toml + regex as the weakest acceptable fallback. The regex is the floor, not the ceiling; real Spin validation is preferred wherever reachable. - Generated template vs app-demo example made explicit: `edgezero new` scaffolds the common case — greeting, nested service section, a single plain #[secret] — and deliberately does NOT include #[secret(store_ref)] (a commented line shows how to add it). store_ref only helps Fastly-only projects, so it should not be the default in every fresh scaffold. app-demo remains the full-capability showcase that exercises both secret forms. - Commit 2 flagged as the explicit review hotspot in §16: the atomic manifest+runtime rewrite warrants the most reviewer attention; its per-adapter contract tests are the primary mitigation and should be reviewed alongside the code. Reviewer confirms no blocking issues; spec is implementation-ready.
- Spin config-push --dry-run never mutates: plan Task 8.1 and spec §15
reworded — dry-run PRINTS the would-be both-table content and the
test asserts spin.toml is unchanged on disk. (The real push writing
both tables is covered by commit 7's non-dry-run tests.)
- Spin `component` field location: it belongs on the
[adapters.<x>.adapter] definition struct (with `crate`/`manifest`),
not the top-level ManifestAdapter — otherwise the accepted TOML
would wrongly be [adapters.spin] component = ...
- load_app_config API made consistent: AppConfigLoadOptions
{ env_overlay } struct; simple load_app_config / _raw apply the
overlay (default); load_app_config_with_options / _raw_with_options
take the struct; --no-env calls the _with_options form with
env_overlay: false. No hidden bool param. Updated spec §4 + §6.10
and plan Tasks 3.1 / 3.3.
- Axum multi-KV path rule: one redb file per logical id, file stem
from [adapters.axum.stores.kv.<id>].name -> .edgezero/kv-<name>.redb.
Prevents multi-store collapsing into one backing file.
- Generator manual check: stop assuming the project lands in CWD or
/tmp/throwaway; generate into an explicit mktemp dir via --dir.
- Removed references to a non-existent crates/edgezero-core/src/
hooks.rs — Hooks + ConfigStoreMetadata both live in app.rs.
- Macro compile-fail tests: Task 3.2 now adds `trybuild = "1"` to edgezero-macros [dev-dependencies] explicitly (only `tempfile` was there), with a tests/ui/*.rs fixture + .stderr golden per rejected case. - External-consumer test env guard: tests/lib_consumer.rs must restore EDGEZERO_MANIFEST via an RAII EnvOverride guard and stay a single #[test] (no in-binary parallelism); a shared Mutex guard is required if more env-touching tests are ever added. - WASM contract test commands pinned: Task 2.7 step 6 names the exact target / features / runner per adapter (cloudflare wasm32-unknown- unknown + wasm-bindgen; fastly wasm32-wasip1 + Viceroy; spin wasm32-wasip1 + Wasmtime), deferring to test.yml as source of truth. - app-demo e2e lifecycle: Task 8.1/8.2 now require an ephemeral port (no hard-coded 8787), a readiness poll (no bare sleep), and RAII teardown that kills the demo server even on assertion failure; the loop is preferably a Rust integration test, not shell-in-YAML.
…ign gate - Default `edgezero` binary wiring (High): commits 4-7 now have explicit steps to add Auth / Provision / Config(Validate|Push) to the default edgezero-cli `Command` enum and `main.rs` dispatch (raw run_* — the default binary has no app struct), with `edgezero --help` / parse tests. Previously only the original five commands and app-demo-cli were wired; the spec requires the new subcommands on the default binary too. New Task 4.2 covers `config`; Task 5.2/6.1/7.2 extended. - Generated `<name>-cli` template upgrade (Medium): new Task 8.2 updates templates/cli/src/main.rs.hbs to the full eight-command set once auth/provision/config exist, wiring the scaffold's config arm to the typed functions with the generated project's config struct. Generator test asserts it. - Full-gate alignment (Medium): added a canonical "## The full gate" section with the exact five CI commands from CLAUDE.md / the workflows (cargo check uses --features "fastly cloudflare spin", not --all-features). Every "run the full gate" step references it; fixed the commit-1 and commit-8 gate steps and the Codebase-facts CI line that had drifted to --all-features. Commit-8 tasks renumbered (8.2 CI wiring -> 8.3; walkthrough/audit -> 8.4).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Implements portable outbound HTTP across core and the Axum, Cloudflare, Fastly, and Spin adapters, and hardens the adjacent request-lifecycle contracts needed to use it safely.
app!(configure = ...)and aligns the checked-in demo and generated projects with route-aware admission, finite read deadlines, bounded fallback bodies, explicit outbound limits, and index-aligned fanout timing.Evidence boundaries
The capability matrices fail closed where the platform contract is not proven:
ResponseReturnedsignal is converter completion before returning to Hyper, not transport evidence.Validation
scripts/run_tests.shcargo fmt --all -- --checkcargo test --workspace --all-targetscargo clippy --workspace --all-targets --all-features -- -D warningscargo check --workspace --all-targets --features "fastly cloudflare spin"