EdgeZero full-migration umbrella design spec#839
Draft
aram356 wants to merge 20 commits into
Draft
Conversation
Defines the end-state, current-state gap analysis, and an ordered set of phases (stores, config injection, secret externalization, extractors, legacy-path removal) for moving Trusted Server completely onto EdgeZero. Phase 0 (State<T> extractor + nested #[secret]) is an upstream edgezero prerequisite tracked separately.
6 tasks
Clarify that platform/mod.rs and types.rs are edited (KV re-export and a shrinking RuntimeServices remain), not deleted, in Phase 1.
…tore id - P0-C: Fastly bypasses run_app (multi-value Set-Cookie, logger reinit, JA4/H2 capture) - add EdgeZero dispatch prereq or documented exception - P-BOOT: specify boot-time config/secret store access for Cloudflare/Spin (build_state runs before request context; registry is per-request) - D5: unify the split app-config store id (app_config vs trusted_server_config) - Promote the secret inventory to a spec artifact; array + optional secrets confirmed present, so edgezero #305 must ship ArrayEach + Option<String> - Phase 4 acceptance: per-adapter route parity (EC routes are Fastly-only) - Phase 5: expand deletion ledger (route_tests, viceroy config, fastly.toml, runbook) - Scope the include_str! ban to adapter/runtime app-config only
…iation (D5) - D6: EdgeZero stores are read-only, but KeyRotationManager writes/deletes config+secrets at runtime for /_ts/admin/keys/*. management_api.rs cannot be deleted in Phase 1 unconditionally; gate on a keep/move-to-ops/upstream decision (R10) - D5 expanded: reconcile ALL runtime store ids (app_config, secrets, JWKS, DataDome ts_secrets, S3, fixtures) with edgezero.toml or strict lookup fails - Fastly needs explicit registry injection into its custom oneshot path - Phase 1 plan starts with a store-capability inventory, not deletions
Task 1 is a decision gate (store-id inventory + D5/D6) per review, not deletions. Read-path migration and Fastly custom-dispatch registry injection are fully specified; management_api.rs deletion is gated on the D6 decision.
- Lock plan to D6-a; D6-b/c stop after Task 1 (separate plan) - Kind-aware store-id reconciliation (kv/config/secrets), incl. ec_store as KV - Composite read/write store bridge with a write-delegation test - New task: migrate Fastly/Axum BOOT config read to EdgeZero before deleting impls - Local Fastly registry builders (EdgeZero builders are pub(crate)); R11 - Concrete named tests, files, routes, fixtures (no <test_name> placeholders) - Sync spec D5 (kind-aware), Phase 1 boot-read, R11
…e tests - Task 3: composite holds the whole ConfigRegistry/SecretRegistry and resolves named(store_name) per read (multi-store); strict unknown-id error; test uses 2 ids - Task 1/spec: correct KV ids to ec_store + consent_store (creative/counter/opid are fastly.toml platform stores, not Settings logical ids) - Task 4: core-level loader test with InMemoryConfigStore + ConfigStoreHandle::new - Task 5: add non-default config-id (JWKS) + non-default secret-id (DataDome, S3) tests - Task 6: local EnvConfig runtime-dictionary reader (EdgeZero helper private); R12 - Task 8: tests build registries with >=2 ids and assert unknown id errors strictly - Spec D5/Phase 1: kind-aware incl [stores.kv]; R9 mentions ec_identity_store; R12
- D7: runtime app-config is config-store-only; NO runtime env vars. ts config push reads env at push time and bakes resolved values into the blob. Stores open by logical id (name == id); no runtime EDGEZERO__STORES__*__NAME read. - Task 6: drop local_env_config entirely (D7) - open stores by logical id; resolves the fastly::ConfigStore-has-no-iter and private-helper findings (R12) - creative_store IS a Settings KV id (deprecated); include it, exclude counter_store/opid_store (Fastly-adapter constants) - Task 2/spec: tighten kind-aware KV inventory (ec_store, consent_store, creative_store) - Task 4: run the actual core test name; Task 5: one filter per cargo test invocation
Switch the six edgezero git deps from branch main to worktree-state-nested-secrets-spec-review (stackpop/edgezero#306, stacked on #300) to pick up the Phase 0 State<T> extractor work. cargo check-axum passes.
- DataDome IP-CIDR config store (datadome-ip-bypass) added to store inventory - Explicit app-config decision: store id trusted_server_config, key app_config; rename DEFAULT_CONFIG_STORE_ID + repoint request_signing.config_store_id - Task 3: ConfigRegistry::named returns ConfigStoreBinding -> use binding.handle.get; map EdgeZero Ok(None)/Err to PlatformError - Task 6: exact builder signatures (KV Result<Option<..>,FastlyError>, config/secret Option<..>) + missing-store/open-failure policy - Task 5: DataDome secret read tested on a protected NON-integration route - StoreName reconciled to logical read id (D7); doc + call-site audit step - Task 4: Axum boot reads .edgezero/local-config-trusted_server_config.json, no env override
Blockers: - Hooks::stores() is not overridden on any adapter (empty StoresMetadata); add Task 2 Step 5 to wire stores() from edgezero.toml on all four adapters - Axum uses routes()+AxumDevServer, not run_app; Task 5 Step 0 switches Axum to edgezero_adapter_axum::run_app::<TrustedServerApp>() - request-signing reads use jwks_store/signing_keys but writes use config_store_id/secret_store_id; fix example/fixtures to jwks_store/signing_keys (NOT app_config/secrets); only the app-config store renames to trusted_server_config - PlatformConfigStore/SecretStore mix read+write; Task 3 Step 0 splits write-only PlatformConfigWriter/PlatformSecretWriter so Task 8 can drop reads and compile High/Medium: - D7 softened: EdgeZero builders read EnvConfig but fall back to logical id; we set none - Task 2 Step 6: declare stores in fastly.toml/wrangler.toml/spin.toml/Axum local files - Task 2 test parameterized over example + fixture + all-store-refs config - CI gate adds cargo check-cloudflare + check-spin (wasm surfaces) - Fix stale spec D5/R9 wording
- Axum: exact path = keep AxumDevServer::with_config + .with_{config,kv,secret}_registry
(dev_server::run_app drops custom PORT/axum.toml); add adapter-axum registry builders
- Task 5: extract whole registry via ctx.request().extensions().get::<ConfigRegistry>()
(RequestContext has no whole-registry getter; per-id accessors would wire only default)
- Task 2: enumerate exact file paths; replace brace-glob git add that would hit
non-existent adapter manifest paths
- Spec: state requirement that Fastly management id == runtime logical id for
jwks_store/signing_keys (or supply a mapping, out of Phase 1 scope)
- Task 3: composite writer test asserts (StoreId, key, value) forwarding (D6-a risk)
- Minor: expect("should ...") convention; Task 7 local verify adds check-cloudflare/spin
- Fastly build_per_request_services still read FastlyPlatformConfigStore directly, so injected registries were unused; add Task 6 Step 4b to build RuntimeServices from the composite via registries in extensions - App-config key == store id (trusted_server_config): default_config_key falls back to id and D7 forbids the __KEY env; set CONFIG_BLOB_KEY=trusted_server_config, retire the app_config key (no --key/env needed) - Task 5: rename heading to 'preserve AxumDevServer::with_config + add registries'; list adapter-axum/src/registries.rs (Step 0 already kept with_config) - Task 2 Step 6: exact per-adapter manifest mappings (CF config=KV binding, secrets=flat Worker secrets via wrangler secret put; Spin config/kv=KV labels) - Name S3 secret store id s3-auth explicitly in D5/Task 2 - Fix all-store-refs.toml include_str path (testdata/, not ../testdata/) - Writer traits Send+Sync; expect_err 'should ...' convention
…y file) - Add config_payload.rs (CONFIG_BLOB_KEY) to Task 2 files + git add (was missing) - Task 2 Step 6b: rename app_config in generate-viceroy-config.rs (+ its test), tests/common/config.rs, tests/environments/axum.rs env var (they run in adapter suites and break under the store/key rename) - Task 2 Step 6: Cloudflare/Spin secrets are FLAT (ignore store_name) -> provision the concrete secret KEYS the code reads (signing KIDs, DataDome key name, S3 access_key_id/secret_access_key/session token), not the store id - Task 6: add app.rs to files; test passes only after Step 4b (not 3-4) - Task 1 output table: drop EDGEZERO__STORES__*__NAME column (D7 -> platform resource per adapter, no env mapping)
The prior review's Critical 'APIs do not exist' findings were against a stale cargo-cache checkout (ce6bcf7, edgezero #253 'Add store support for Spin') that predates the registry refactor. Our actual pin is 6ebc29a5 (PR #306), which HAS StoreRegistry/StoresMetadata/Hooks::stores()/dispatch_with_registries/ with_*_registry/[stores.*] ids+default/CF-KV-namespace/Axum-local-file. Added a 'Pinned dependency (verified)' note recording this to prevent re-litigation. Valid fixes: - Cloudflare app.rs settings_from_cloudflare_config_json reads literal value.get("app_config") from the side-channel; change to CONFIG_BLOB_KEY so the key rename doesn't break Cloudflare boot (Phase 2 does the store migration) - Viceroy generator already emits a trusted_server_config store for rollout flags; the app_config->trusted_server_config rename must MERGE the blob into that table, not emit a duplicate table
User chose full convergence on the canonical app-demo wiring. Reshape the
umbrella spec:
- End-state: every adapter is one-line run_app::<App>; App from app! macro;
handlers #[action] + State<Arc<AppState>>; no TS-local registry/dispatch wiring
- Two NEW required upstream edgezero prerequisites (verified gaps in pinned 6ebc29a):
- P0-C: header-preserving Fastly run_app dispatch + pre-dispatch extension hook
(Set-Cookie/JA4/logger) - permanent-exception fallback removed
- P0-D: macro/run_app app-state injection (with_state is builder-only; app!
router + run_app never call it) - a Hooks state hook run_app inserts per request
- D1 retained (load-once Settings) but injected via P0-D state hook, not with_state
- Phase 4 rewritten as full convergence (app! macro, run_app, extractors, catch-all
fallback for integration/publisher dispatch)
- Sequencing note (R14): Phase 1 adapter-registry builders (Tasks 5-6) are throwaway
under run_app; recommend landing P0-C/P0-D early to skip them
- Risks R7 (P0-C required), R13 (P0-D), R14 (sequencing)
…ate) Implementation-ready spec to hand to the edgezero dev, grounded in pinned 6ebc29a: - P0-C: C1 append_header for multi-value Set-Cookie (response.rs:29 set_header bug), C2 Hooks::owns_logging() opt-out, C3 run_app_with_request_extensions pre-dispatch hook for JA4/H2/client_info from the raw fastly::Request - P0-D: Hooks::app_state() injected per request (symmetric with registries) + app! macro state= argument; note P0-D is avoidable via hand-built routes()+with_state
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
Adds the umbrella design spec for moving Trusted Server completely onto EdgeZero primitives: config push, KV, secret store, config injection without an embedded
trusted-server.toml, extractor-based handlers, and deletion of every pre-EdgeZero workaround.This is a docs-only PR (one spec file). It defines the end-state, a verified current-state gap analysis, and an ordered set of phases — each of which will get its own implementation plan/PR.
Spec:
docs/superpowers/specs/2026-07-02-edgezero-full-migration-design.mdPhase map (foundation-first)
State<T>extractor + nested/array#[secret]). Upstream edgezero repo, tracked separately by its own PR.StoreRegistry(delete bespokePlatformConfigStore/PlatformSecretStore,RuntimeServices,management_api.rs,settings_data.rschunk resolver). No upstream dependency — startable immediately.include_str!), dropfrom_toml_and_env+ theconfigcrate.#[secret]onSettings, move inline secrets to the secret store, deleteRedacted<T>. Depends on Phase 0 nested#[secret].#[action]extractors using the upstreamedState<T>; delete per-adapter handler shims.legacy_main/compat.rs/rollout flags). Gated on 100% EdgeZero rollout (Fastly entry point switch (dual-path with flag) #495).Key decisions
Arc<Settings>viaState<Settings>rather than the per-requestAppConfig<C>extractor (avoids re-parsing the wholeSettingson every request).TrustedServerAppConfigwrapper ontoSettings.Status
Draft — for review of scope, phase ordering, and D1 before per-phase implementation plans begin.