Repository navigation
Description
Status: implemented in #306 (stacked on #300)
Four independent upstream primitives for the trusted-server -> EdgeZero migration. Delivered in #306; base is the #300 branch and auto-retargets to main when #300 merges.
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 under docs/superpowers/plans/.
Workstream A -- State<T> extractor
State<T>extractor (FromRequestby type from request extensions;Deref/into_inner;500when unregistered); composes in#[action]with no macro change.RouterBuilder::with_state<T>stores state in a singlestate_extensions: Extensionsbag; dispatchextends it into every request. ShippedState<T>+with_stateonly (noExtensionalias, noctx.state()accessor).
Workstream B -- nested / array #[secret]
- Owned path-qualified
SecretField { kind, path: Vec<SecretPathSegment>, optional };AppConfigMeta::secret_fields() -> Vec<SecretField>(const -> fn). - Derive recurses into
#[app_config(nested)]object +Vec<_>fields; acceptsOption<String>; path/renameguards;AppConfigRootassertion. - Runtime
secret_walkpath navigator; nested/list-awarevalidate_excluding_secretspruning; CLI path-aware TOML collector for validate/push/diff; inverted nested-AppConfig CI guard.
P0-C -- Fastly run_app dispatch fidelity
- C1 multi-value
Set-Cookie(response + proxy response/request); C2 platform-neutralHooks::owns_logging()gated across all four adapters +app!(owns_logging = <bool>); C3run_app_with_request_extensions, a generic pre-dispatch hook for raw-request signals. - Note: C3 is only the Fastly-side mechanism. A standard cross-adapter JA4 API (Expose JA4 through a standard request interface #283) is NOT delivered here and remains open.
P0-D -- app! app-state injection
app!(state = <expr>)makes the macro-generated router call.with_state(...), reusing A's dispatch injection. Macro-only; app-demo example + e2e test.
Open questions -- resolved during implementation
- A-1 / A-2: ship
State<T>+with_stateonly; noExtensionalias, noctx.state()accessor. - B-1 (arrays): implemented
ArrayEachfrom day one (spec's own inventory haspartners[*].api_key). - B-2 (recursion): explicit
#[app_config(nested)]opt-in (type-heuristic rejected). - B-3 (const -> fn):
AppConfigMeta::secret_fields() -> Vec<SecretField>-- cross-crate const concat was not expressible, so thefnform was taken; every impl (incl. test impls) flipped. - Router uses the
crate::http::Extensionsfacade; nolib.rsre-export;validate_excluding_secretsgot true nested/list-aware pruning; a purpose-builtKeyInNamedStorefixture was added.
Acceptance criteria
- All 5 CI gates green (fmt, clippy, workspace tests, feature check, spin wasm32) -- plus the nested-AppConfig audit, app-demo (own workspace) tests + clippy, and Fastly wasm32-wasip1 + Viceroy tests.
- Unit + trybuild UI + integration tests per spec 3.4 / 4.7.
-
app-demobuilds/serves on all four adapters; top-level#[secret] api_tokenstill resolves. -
edgezero-cli config validate/push/diffwork over nested + array secrets. - Rustdoc + guide updates (
docs/guide/handlers.md,docs/guide/configuration.md).
Metadata
Metadata
Assignees
Labels
No labels
Activity
aram356 commentedon Jul 2, 2026
Second-pass review — additional blockers (verified against
origin/main@42843b1)Folded into spec §8. Recommendation: split into two plans. Workstream A can start now; Workstream B waits on the items below.
Workstream B — must fix before it is plan-ready
crates/edgezero-cli/src/bin/check_no_nested_app_config.rs(run in CI as "Nested AppConfig audit",.github/workflows/test.yml:58) rejects anyAppConfigstruct nested inside another (throughOption/Vec/Box/…). The#[app_config(nested)]design violates it today. Must become "nestedAppConfigallowed iff field has#[app_config(nested)]" — changes the audit binary + its tests.SecretFieldhas only{kind, path}; addoptional: boolso the walk/CLI can tell "required missing → error" from "optional absent → skip".&'static [SecretPathSegment]contradicts B-3's owned/Cowconclusion. Usesecret_fields() -> Vec<SecretField>with ownedpath.edgezero-macros/src/lib.rs:20attributes(secret)→attributes(secret, app_config).TypedSecretEntry.field_name(edgezero-adapter/src/registry.rs:178) is borrowed&str; dotted/array paths are computed strings → make it owned/Cowand thread through the Spin collision check.rename_all: the guard atedgezero-macros/src/app_config.rs:75only fires when direct#[secret]fields exist; a parent with only#[app_config(nested)]children escapes it. Extend to fire on any nested field.partners[*].api_key; don't ship object-only unless the trusted-serverSettingsaudit confirms no array secret leaves — else includeArrayEachfrom the start.Workstream A — plan-ready
No blockers. Router insertion before
RequestContext::new(router.rs:267) and the generic#[action]FromRequestcall (action.rs:87) fit as designed.[-]Implement State<T> extractor + nested/array #[secret] support (edgezero-core/macros)[/-][+]Implement State<T> + nested/array #[secret] + Fastly dispatch fidelity (P0-C) + app! app-state (P0-D)[/+]