Skip to content

Wire request signing to RuntimeServices store primitives (PR 9)#609

Open
prk-Jr wants to merge 15 commits intofeature/edgezero-pr8-content-rewritingfrom
feature/edgezero-pr9-wire-signing-to-store-primitives
Open

Wire request signing to RuntimeServices store primitives (PR 9)#609
prk-Jr wants to merge 15 commits intofeature/edgezero-pr8-content-rewritingfrom
feature/edgezero-pr9-wire-signing-to-store-primitives

Conversation

@prk-Jr
Copy link
Copy Markdown
Collaborator

@prk-Jr prk-Jr commented Apr 2, 2026

Summary

  • Replaces direct FastlyConfigStore/FastlySecretStore construction in request_signing/ with RuntimeServices platform traits, making the signing subsystem platform-agnostic
  • Moves management API write operations (config item / secret CRUD) out of trusted-server-core into an adapter-only FastlyManagementApiClient, enforcing the EdgeZero layering rule that only the adapter may call Fastly-specific APIs
  • Threads RuntimeServices into AuctionContext so PrebidAuctionProvider (and future providers) can call RequestSigner::from_services without a deprecated FastlyConfigStore shim

Changes

File Change
crates/trusted-server-adapter-fastly/src/management_api.rs NewFastlyManagementApiClient with update_config_item, delete_config_item, create_secret, delete_secret via Fastly management API
crates/trusted-server-adapter-fastly/src/platform.rs FastlyPlatformConfigStore::put/delete and FastlyPlatformSecretStore::create/delete delegate to FastlyManagementApiClient
crates/trusted-server-adapter-fastly/src/main.rs Wire services into /verify-signature handler; add mod management_api
crates/trusted-server-core/src/request_signing/signing.rs Rewrite — RequestSigner::from_services(&RuntimeServices) replaces from_config(); verify_signature accepts &RuntimeServices; deprecated shim removed
crates/trusted-server-core/src/request_signing/rotation.rs Rewrite — KeyRotationManager::new() now infallible; all methods accept services: &RuntimeServices
crates/trusted-server-core/src/request_signing/endpoints.rs All handlers accept services: &RuntimeServices; tests use in-memory stub stores
crates/trusted-server-core/src/auction/types.rs AuctionContext gains pub services: &'a RuntimeServices
crates/trusted-server-core/src/auction/orchestrator.rs Pass services through derived AuctionContext construction sites
crates/trusted-server-core/src/auction/endpoints.rs Pass services when constructing AuctionContext
crates/trusted-server-core/src/integrations/prebid.rs Use RequestSigner::from_services(context.services) — no more #[allow(deprecated)]
crates/trusted-server-core/src/platform/test_support.rs Add build_services_with_config_and_secret test helper
crates/trusted-server-core/src/storage/api_client.rs DeletedFastlyApiClient moved to adapter-only management_api.rs
crates/trusted-server-core/src/storage/mod.rs Remove api_client module and FastlyApiClient re-export
docs/superpowers/plans/2026-03-31-pr9-wire-signing-to-store-primitives.md Implementation plan

Closes

Closes #490

Test plan

  • cargo test --workspace
  • cargo clippy --workspace --all-targets --all-features -- -D warnings
  • cargo fmt --all -- --check
  • JS tests: cd crates/js/lib && npx vitest run
  • JS format: cd crates/js/lib && npm run format
  • Docs format: cd docs && npm run format
  • WASM build: cargo build --package trusted-server-adapter-fastly --release --target wasm32-wasip1
  • Manual testing via fastly compute serve

Checklist

  • Changes follow CLAUDE.md conventions
  • No unwrap() in production code — use expect("should ...")
  • Uses tracing macros (not println!)
  • New code has tests
  • No secrets or credentials committed

prk-Jr added 11 commits March 31, 2026 18:47
…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.
@prk-Jr prk-Jr self-assigned this Apr 2, 2026
@prk-Jr prk-Jr changed the title (PR 9) Wire request signing to RuntimeServices store primitives Wire request signing to RuntimeServices store primitives (PR 9) Apr 2, 2026
@prk-Jr prk-Jr linked an issue Apr 2, 2026 that may be closed by this pull request
Copy link
Copy Markdown
Collaborator

@aram356 aram356 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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_secret rejects 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_item may reject HTTP 201: Config store PUT may return 201 for new items — same risk as above (management_api.rs:139)

❓ question

  • Box::leak in test helpers: Intentional memory leak in orchestrator.rs:683 and prebid.rs:1290 — would LazyLock<RuntimeServices> be preferred?

Non-blocking

♻️ refactor

  • Duplicate JWKS_STORE_NAME LazyLock: Identical definition in both signing.rs:17 and rotation.rs:20 — could live in request_signing/mod.rs

🤔 thinking

  • api_key stored as Vec<u8> but used as string: Could be String directly, or a zeroizing type for security (management_api.rs:41)

⛏ nitpick

  • Response body read but discarded: buf is read in every method but never included in error messages, unlike the deleted FastlyApiClient which 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

@prk-Jr prk-Jr requested a review from aram356 April 6, 2026 09:58
Copy link
Copy Markdown
Collaborator

@ChristianPavilonis ChristianPavilonis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Collaborator

@ChristianPavilonis ChristianPavilonis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Copy link
Copy Markdown
Collaborator

@aram356 aram356 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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_secret base64-encodes the secret field at the transport layer (and uses PUT), while the old FastlyApiClient::create_secret sent it as raw text (POST). Since rotation.rs::store_private_key already 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 uses is_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 during rotate_key). SDK caching mitigates this, but worth considering caching at a higher level. (platform.rs:69)
  • parse_active_kids untested directly: edge cases like ",", "", " kid-a , , kid-b " are not covered by focused unit tests. (mod.rs:56)

♻️ refactor

  • signing_store_ids returns 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_NAME visibility: pub but only used within the crate — pub(crate) would be more precise. (mod.rs:49)
  • PR checklist says "tracing": actual code and CLAUDE.md use log macros.

CI Status

  • integration tests: PASS
  • browser integration tests: PASS
  • prepare integration artifacts: PASS

@prk-Jr prk-Jr requested a review from aram356 April 10, 2026 08:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Wire signing to store write primitives

3 participants