Wire request signing to RuntimeServices store primitives (PR 9)#609
Wire request signing to RuntimeServices store primitives (PR 9)#609prk-Jr wants to merge 15 commits intofeature/edgezero-pr8-content-rewritingfrom
Conversation
…gezero-pr9-wire-signing-to-store-primitives
…te methods via management API Replace FastlyApiClient with FastlyManagementApiClient in the put/delete methods of FastlyPlatformConfigStore and the create/delete methods of FastlyPlatformSecretStore. Remove the now-unused FastlyApiClient import.
Thread RuntimeServices into AuctionContext so auction providers can access platform stores directly. Update PrebidAuctionProvider to use RequestSigner::from_services(context.services) instead of the now- removed from_config() shim. All construction sites and test helpers updated accordingly. This satisfies the final acceptance criterion of #490: no FastlyConfigStore/FastlySecretStore construction remains in the request_signing/ modules.
aram356
left a comment
There was a problem hiding this comment.
Summary
Solid architectural move — management API writes are cleanly extracted from trusted-server-core into the Fastly adapter, and request signing is now fully platform-agnostic via RuntimeServices. Tests migrated well with spy/stub stores exercising real crypto.
Two issues with HTTP status code handling in FastlyManagementApiClient that could cause silent failures in production.
Blocking
🔧 wrench
create_secretrejects HTTP 201 Created: Fastly secret store API returns 201 on creation, but only 200 is accepted — new secrets will be treated as failures (management_api.rs:221)update_config_itemmay reject HTTP 201: Config store PUT may return 201 for new items — same risk as above (management_api.rs:139)
❓ question
Box::leakin test helpers: Intentional memory leak inorchestrator.rs:683andprebid.rs:1290— wouldLazyLock<RuntimeServices>be preferred?
Non-blocking
♻️ refactor
- Duplicate
JWKS_STORE_NAMELazyLock: Identical definition in bothsigning.rs:17androtation.rs:20— could live inrequest_signing/mod.rs
🤔 thinking
api_keystored asVec<u8>but used as string: Could beStringdirectly, or a zeroizing type for security (management_api.rs:41)
⛏ nitpick
- Response body read but discarded:
bufis read in every method but never included in error messages, unlike the deletedFastlyApiClientwhich used it for debugging
👍 praise
KeyRotationManager::new()becoming infallible is a clean simplification- Spy store test pattern with
Mutex<Vec<...>>is excellent — real crypto with in-memory stores
CI Status
- integration tests: PASS
- browser integration tests: PASS
- prepare integration artifacts: PASS
ChristianPavilonis
left a comment
There was a problem hiding this comment.
Review Summary
This PR does an excellent job of decoupling request signing from Fastly-specific storage primitives, moving to platform-agnostic RuntimeServices throughout. The layering is clean and the test infrastructure is solid.
However, there are two P0 issues in the Fastly management API client that could cause failures on key re-rotation, and several opportunities to reduce test code duplication and improve documentation coverage.
Findings by severity
| Severity | Count |
|---|---|
| P0 (critical) | 2 |
| P1 (high) | 4 |
| P2 (medium) | 9 |
Findings not attached inline
(These findings reference lines outside the diff or files not modified in this PR.)
🤔 P2 — parse_ed25519_signing_key heuristic is fragile (crates/trusted-server-core/src/request_signing/signing.rs, line 32)
Using len > 32 to distinguish raw vs Base64-encoded keys works by current convention but creates an implicit contract between the writer (rotation.rs) and reader (this function). A 32-byte raw key that happens to be valid UTF-8 could theoretically be misinterpreted. Consider adding a doc comment explaining the encoding contract explicitly.
🤔 P2 — RequestSigner struct + kid field lack doc comments (crates/trusted-server-core/src/request_signing/signing.rs, line 51)
Per project documentation standards (CLAUDE.md), public structs and their fields should have doc comments. RequestSigner and its kid field are undocumented.
🤔 P2 — Public request/response endpoint types undocumented (crates/trusted-server-core/src/request_signing/endpoints.rs, lines 56-246)
VerifySignatureRequest, VerifySignatureResponse, RotateKeyRequest, RotateKeyResponse, DeactivateKeyRequest, DeactivateKeyResponse all lack doc comments. These are the public API surface — adding even one-line descriptions helps consumers understand the expected shapes.
👏 Praise — Excellent StoreName/StoreId newtype separation (crates/trusted-server-core/src/platform/types.rs)
Having distinct types for edge-visible runtime names vs. management API identifiers prevents subtle bugs at the type level. The compiler catches misuse that would otherwise be a runtime surprise.
See inline comments for all other findings.
ChristianPavilonis
left a comment
There was a problem hiding this comment.
Overall this is a well-architected PR — the core crate is now genuinely platform-agnostic for signing, and the management API isolation to the adapter is the right layering. A few correctness and structural items below, none blocking.
Note: One finding could not be placed as an inline comment because the relevant line is not part of the diff:
🤔 Module doc still references Fastly-specific store semantics (crates/trusted-server-core/src/request_signing/mod.rs, line 8)
The module doc still says "Fastly stores have two identifiers" and references ConfigStore::open / SecretStore::open. Since the whole point of this PR is platform-agnostic signing, this doc should use platform-neutral language (e.g. "Platform stores have two identifiers" and reference the trait methods).
aram356
left a comment
There was a problem hiding this comment.
Summary
This PR cleanly separates the Fastly management API client from trusted-server-core into the adapter layer, replacing direct FastlyConfigStore/FastlySecretStore usage with RuntimeServices platform traits. The architecture improvement is solid. Two items need clarification before merge.
Blocking
🔧 wrench
- Secret storage format change: the new
FastlyManagementApiClient::create_secretbase64-encodes thesecretfield at the transport layer (and uses PUT), while the oldFastlyApiClient::create_secretsent it as raw text (POST). Sincerotation.rs::store_private_keyalready base64-encodes the raw key, the effective payload format has changed. Need confirmation on Fastly API contract and backward compatibility with existing keys. (management_api.rs:68)
❓ question
- Broadened status check: old code checked for specific status codes (
OK,NO_CONTENT), new code usesis_success()(any 2xx). Was this intentional? (management_api.rs:90)
Non-blocking
🤔 thinking
- Per-call client construction: each write call constructs a new
FastlyManagementApiClient(4x duringrotate_key). SDK caching mitigates this, but worth considering caching at a higher level. (platform.rs:69) parse_active_kidsuntested directly: edge cases like",",""," kid-a , , kid-b "are not covered by focused unit tests. (mod.rs:56)
♻️ refactor
signing_store_idsreturns unnamed tuple: fine for two values, but a named struct would improve readability if more settings are added. (endpoints.rs:168)
⛏ nitpick
JWKS_STORE_NAME/SIGNING_STORE_NAMEvisibility:pubbut only used within the crate —pub(crate)would be more precise. (mod.rs:49)- PR checklist says "tracing": actual code and CLAUDE.md use
logmacros.
CI Status
- integration tests: PASS
- browser integration tests: PASS
- prepare integration artifacts: PASS
Summary
FastlyConfigStore/FastlySecretStoreconstruction inrequest_signing/withRuntimeServicesplatform traits, making the signing subsystem platform-agnostictrusted-server-coreinto an adapter-onlyFastlyManagementApiClient, enforcing the EdgeZero layering rule that only the adapter may call Fastly-specific APIsRuntimeServicesintoAuctionContextsoPrebidAuctionProvider(and future providers) can callRequestSigner::from_serviceswithout a deprecatedFastlyConfigStoreshimChanges
crates/trusted-server-adapter-fastly/src/management_api.rsFastlyManagementApiClientwithupdate_config_item,delete_config_item,create_secret,delete_secretvia Fastly management APIcrates/trusted-server-adapter-fastly/src/platform.rsFastlyPlatformConfigStore::put/deleteandFastlyPlatformSecretStore::create/deletedelegate toFastlyManagementApiClientcrates/trusted-server-adapter-fastly/src/main.rsservicesinto/verify-signaturehandler; addmod management_apicrates/trusted-server-core/src/request_signing/signing.rsRequestSigner::from_services(&RuntimeServices)replacesfrom_config();verify_signatureaccepts&RuntimeServices; deprecated shim removedcrates/trusted-server-core/src/request_signing/rotation.rsKeyRotationManager::new()now infallible; all methods acceptservices: &RuntimeServicescrates/trusted-server-core/src/request_signing/endpoints.rsservices: &RuntimeServices; tests use in-memory stub storescrates/trusted-server-core/src/auction/types.rsAuctionContextgainspub services: &'a RuntimeServicescrates/trusted-server-core/src/auction/orchestrator.rsservicesthrough derivedAuctionContextconstruction sitescrates/trusted-server-core/src/auction/endpoints.rsserviceswhen constructingAuctionContextcrates/trusted-server-core/src/integrations/prebid.rsRequestSigner::from_services(context.services)— no more#[allow(deprecated)]crates/trusted-server-core/src/platform/test_support.rsbuild_services_with_config_and_secrettest helpercrates/trusted-server-core/src/storage/api_client.rsFastlyApiClientmoved to adapter-onlymanagement_api.rscrates/trusted-server-core/src/storage/mod.rsapi_clientmodule andFastlyApiClientre-exportdocs/superpowers/plans/2026-03-31-pr9-wire-signing-to-store-primitives.mdCloses
Closes #490
Test plan
cargo test --workspacecargo clippy --workspace --all-targets --all-features -- -D warningscargo fmt --all -- --checkcd crates/js/lib && npx vitest runcd crates/js/lib && npm run formatcd docs && npm run formatcargo build --package trusted-server-adapter-fastly --release --target wasm32-wasip1fastly compute serveChecklist
unwrap()in production code — useexpect("should ...")tracingmacros (notprintln!)