ADR-012: SecureHttpClient API and transports
Table of contents
Status
Accepted
Date
2026-01-12
Context
Multiple extensions need to call external HTTP services with centralized credentials, policies, and audit. We need:
- a stable, minimal public PHP API,
- a clean boundary between "product logic" (registry/policy/audit) and "transport engine".
We also want optional Rust (FFI or later sidecar) without forcing it on everyone.
Decision
We define a public SecureHttpClientInterface and a transport abstraction:
SecureHttpClient (product logic)
- Resolves service by
serviceId - Loads credential set
- Enforces policy (deny-by-default)
- Executes request via a configured transport backend
- Records audit metadata
- Returns a response wrapper
TransportInterface (engine)
interface TransportInterface
{
public function send(RequestSpec $request): ResponseSpec;
}
Copied!
Backends:
PhpTransport(default; PSR-18 or Symfony HttpClient)RustFfiTransport(optional)- (future)
SidecarTransport
Consumers never talk to transports directly; they only use SecureHttpClientInterface.
Consequences
Positive
- Decouples governance logic from transport implementation
- Allows fallback and progressive rollout of Rust
- Keeps API surface small and stable for consumers
- Avoids "typed DTO in Rust" trap: response is raw/json in PHP
Negative
- Slight abstraction overhead
- Requires careful policy enforcement placement (must not be bypassable)
Alternatives considered
Let consumers pick HTTP client directly
Allow consumers to use PSR-18 clients directly.
Rejected: loses central policy enforcement and audit guarantees.
Make Rust mandatory
Require Rust transport for all installations.
Rejected: adoption killer; too many environments can't/won't run native code.