State<T> + nested/array #[secret] + Fastly dispatch fidelity (P0-C) + app! app-state (P0-D)#306
Draft
aram356 wants to merge 73 commits into
Draft
State<T> + nested/array #[secret] + Fastly dispatch fidelity (P0-C) + app! app-state (P0-D)#306aram356 wants to merge 73 commits into
aram356 wants to merge 73 commits into
Conversation
…nfig/app-demo test coverage, workspace commands
…dy-mode rename, Internal->500, scaffold CI checks
… roadmap update, macro crate test
Match the edgezero-cli precedent and the workspace-dependency convention. Adds quote to [workspace.dependencies].
…veat - emit `include_bytes!` const in app! output so edits to edgezero.toml trigger a Cargo rebuild without requiring a .rs change - strengthen middleware_sees_introspection_data to assert manifest_json presence and non-empty route list, mirroring dispatch test - add content-type assertion to routes_lists_registered_routes - add security caveat to routing.md Introspection Routes section noting endpoints are unauthenticated and environment.variables values are emitted
… no edgezero.toml change)
… atomic #[action(manifest|routes)] TDD tasks
…lity inject); fix plan single-filter cmd + probe response
…ne; cargo test in Task 10a
…rrying handler struct
Design spec for two upstream edgezero-core/edgezero-macros primitives: - State<T> extractor + RouterBuilder::with_state for app-owned shared state - nested/array #[secret] support via path-qualified SecretField metadata Filed under docs/superpowers/specs/. Includes a maintainer-review appendix (§8) verifying every current-mechanics claim against origin/main @ 42843b1 and folding in the corrections found: http-facade use in the router plumbing, the inaccurate lib.rs re-export step, the omitted validate_excluding_secrets consumer (needs nested-ValidationErrors navigation, not a rename), the per-struct guard-enforcement rewording, and B-3 being forced to the secret_fields() fn lowering.
…T_FIELDS naming An empty #[app_config()] (or bare #[app_config]) previously returned Ok(false) from nested_optin, silently NOT recursing the field and dropping the child's #[secret] metadata. Now errors, matching the documented contract; adds an app_config_empty trybuild fixture. Also renames stale SECRET_FIELDS references in comments + the rename_all diagnostic to secret_fields(), and broadens that diagnostic to mention #[app_config(nested)] children.
… path-aware secret check Push: build_config_envelope_preserves_nested_and_array_secret_names asserts nested + array secret key names survive verbatim into envelope.data. Diff: diff_typed_rejects_empty_nested_secret proves the path-aware typed_secret_checks catches an empty nested secret (naming integrations. server_side_key) before the remote-read step.
… spec + test comments
…intainer-review revised Adds the P0-C (Fastly run_app dispatch fidelity: Set-Cookie multi-value, owns_logging opt-out, raw-request pre-dispatch hook) + P0-D (app! state= injection) design, revised across three review rounds against 47a112c: P0-D reduced to a macro-only change reusing the router Extensions bag; C3 Extensions path + before-conversion scratch-bag ordering; C1 proxy response fidelity; C2 cross-adapter owns_logging + missing_trait_methods emission; full app! argument grammar.
… (app! app-state)
…ogging macro-emission integration test
…rkspace); Task 3->4 fix
…Service, not dispatch_with_registries)
…oy from crate dir, not host cargo test)
…); trait method alphabetical ordering
…aw-request signals
…ts, crate-root app-demo state, real route TOML)
…)] opt-in; check off completed plan boxes
5 tasks
…sions; model cached app_state (OnceLock) Finding 1: app!(state = <expr>) runs the state expression each time build_router() is called (per request on Fastly), so the demo's app_state() now caches via OnceLock and the guide warns to build heavy state once + hand out Arc clones. Finding 2: handlers.md gains the app!(state = ...) macro path; fastly.md documents run_app_with_request_extensions (JA4/H2/client-IP hook) and owns_logging opt-out.
…tate - handlers.md: app! allows only one state=<expr> (macro rejects duplicates); use an aggregate app-state struct for multiple values. Fix stray '>' typo. - fastly.md: get_tls_ja4() returns Option<&str>; Ja4 wraps String -> .to_owned(). - p0d plan: model the cached OnceLock<Arc<_>> app_state (was fresh Arc per call).
… model - cli-reference.md / cli-walkthrough.md: bundled `edgezero config push` errors (no raw push); typed push writes ONE BlobEnvelope with every field verbatim, including #[secret] key NAMES — nothing is flattened or stripped. Correct the per-adapter mechanics to one (key, envelope_json) entry and fix the stale Fastly command (config-store-entry update --upsert --stdin). - blob-app-config-migration.md: C::SECRET_FIELDS -> C::secret_fields(). - fastly.md: client IP is carried via FastlyRequestContext (only JA4/H2 need the raw hook); fix import path to context::FastlyRequestContext.
- axum.md: the local file is { "<store_id>": "<BlobEnvelope JSON>" }, not a
flat per-leaf map; secret key names are preserved (never stripped/resolved).
Show the AppConfig<C> extractor as the reader; drop the flat store.get example.
- cli-walkthrough.md: Spin secret note + env-overlay example — push preserves
secret key NAMES and writes the nested blob, it does not strip secrets.
- axum config_store.rs: module + method doc comments describe the envelope
entry (outer map still string->string; typed push writes one envelope string).
- spin.md / manifest-store-migration.md: use <your-cli> config push (bundled edgezero config push errors by design; typed push runs from the app CLI). - axum.md + axum config_store.rs: the outer blob key is the *selected* config key — defaults to the logical store id, overridable with --key. - axum config_store.rs: add generated_at to the BlobEnvelope example (required field), matching the fuller shape in the public Axum docs.
… selector - cli-reference.md: add the shipped `config diff` command (typed-only; --format unified/json/structured, --exit-code, --key/--store/--local/--no-env/etc.), and document the config push flags that were missing: --key, --no-diff, --yes/-y. - manifest-store-migration.md: EDGEZERO__STORES__CONFIG__<ID>__KEY is a concrete runtime blob selector (staging/canary), not free-form tuning — dedicated table row + note; disambiguate the free-form row's metavariable to <SUFFIX>. - configuration.md: mention the __KEY selector next to __NAME so operators find it.
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.
Four independent upstream primitives for the trusted-server -> EdgeZero migration, stacked on #300 (introspection routes; base
worktree-feature+introspection-routes, auto-retargets tomainwhen #300 merges).Closes #304. Base depends on #300.
Specs:
docs/superpowers/specs/2026-07-02-edgezero-state-and-nested-secrets-design.md(A+B),docs/superpowers/specs/2026-07-03-edgezero-p0cd-fastly-dispatch-and-appstate-design.md(P0-C/P0-D). Task-by-task plans underdocs/superpowers/plans/(all boxes checked).Workstream A --
State<T>extractorState<T>extractor (FromRequestby type from request extensions;Deref/DerefMut/into_inner;500when unregistered).RouterBuilder::with_state<T>stores app state in a singleExtensionsbag; dispatch doesrequest.extensions_mut().extend(state.clone())after the introspection injects (last-write-wins byTypeId). Zero macro change --#[action]composesState<T>via the genericFromRequestpath.docs/guide/handlers.md"Sharing app state".Workstream B -- nested / array
#[secret]SecretField { kind, path: Vec<SecretPathSegment>, optional };AppConfigMeta::secret_fields() -> Vec<SecretField>(wasconst SECRET_FIELDS);dotted_path().#[app_config(nested)]opt-in recurses into sub-struct /Vec<_>fields; acceptsOption<String>; rejectsOption<String>on#[secret(store_ref)]; empty/bare#[app_config]hard-errors;AppConfigRootbound assertion; serderename/rename_allguards extended along the path.secret_walkis a path navigator (nested objects, arrays per-element, optional absent/nullskip,KeyInNamedStoresibling resolved in the innermost parent, dotted[n]errors).validate_excluding_secretspruning.config validate/push/diff;TypedSecretEntrycarries a dotted label.#[app_config(nested)].docs/guide/configuration.md"Nested and array secrets".P0-C -- Fastly
run_appdispatch fidelityset_header->append_headerinfrom_core_responseand the proxy response/request conversion (originSet-Cookieno longer collapses).Hooks::owns_logging()-- platform-neutral opt-out gated in all four adapters'run_app;app!(owns_logging = <bool>)macro argument.run_app_with_request_extensions-- a generic pre-dispatch hook: an app closure reads raw-fastly::Requestsignals (e.g. JA4 / H2 / client IP -- the app supplies the closure and its own type) into a scratchExtensionsbefore conversion, merged into the core request; visible to middleware and theState/extractor layer.P0-D --
app!app-state injectionapp!(state = <expr>)makes the macro-generatedbuild_router()call.with_state(<expr>), reusing Workstream A's dispatch injection. Macro-only -- no adapter orHookschange.app-demogains astate = crate::app_state()+State<Arc<DemoState>>handler with an end-to-end test.Tests / CI gates (all green locally)
cargo fmt,cargo clippy --workspace --all-targets --all-features -D warnings,cargo test --workspace --all-targets, feature check (fastly cloudflare spin), spinwasm32-wasip2, the nested-AppConfig audit, app-demo (own workspace) tests + clippy, and the Fastly adapter's wasm32-wasip1 + Viceroy tests (C1/C3).Notes
cargo test.#[expect]are documenteddead_codeon#[derive(AppConfig)]test fixtures.