Skip to content

ADR-002: Keep the domain provider-neutral and expose adapter ports

Status

Accepted

Context

The platform supports AWS and Azure as alternative choices for an installation. The portal, API, CLI, and policy behavior should remain equivalent regardless of that choice. If common request models import cloud SDKs or contain provider-native resource details, provider selection leaks into every consumer and a request can accidentally target the wrong cloud.

Milestone 2 also needs a useful simulation path before credentials, accounts, subscriptions, Terraform, or cloud APIs are introduced.

Decision

The domain layer contains only provider-neutral Pydantic contracts. A raw environment request has no provider. The provider is resolved from the selected installation profile and becomes part of the normalized request identity.

The domain exposes a runtime-checkable base ProviderAdapter port and explicit statically narrowed AWSProviderAdapter and AzureProviderAdapter protocols. Only the base port is runtime-checkable: structurally identical marker protocols cannot be distinguished at runtime. Adapters validate the selected installation, validate the normalized request, and return neutral validation violations and deterministic simulation resources. The orchestrator validates common policies first, routes only to the selected provider, then performs those two adapter validation stages before simulation.

Syntactically valid public and IPv6 networks are accepted by the request model so the common policy layer can return the stable network-must-be-private-ipv4 explanation. Policy and validation contracts are internally consistent, violations are sorted by code, field, and message with exact duplicates removed, and simulation resources are sorted deterministically.

Provider SDKs, Terraform modules, credentials, account/subscription IDs, and network calls belong in separate provider-specific layers. They must not be required to import or test the common domain.

Consequences

Positive consequences:

  • Every interface can share one validated request and one explanation of policy failures.
  • Provider lock and request fingerprints make installation ownership explicit.
  • Both provider routes can be tested credential-free with fake adapters.
  • Provider implementations can evolve independently behind stable ports.
  • Simulation remains safe and reproducible for local evaluation.

Trade-offs:

  • Provider-specific capability differences need a separate adapter validation result rather than being added to common models.
  • The neutral model cannot express every provider feature; provider-native configuration stays behind the implemented Terraform/workflow boundary.
  • Adapter registration needs explicit mismatch and missing-adapter checks.

Rejected alternatives

Put AWS and Azure fields in one request model

Rejected because it creates optional-field combinations, leaks provider details into every interface, and makes provider mismatch easy to introduce.

Let each interface choose its own provider

Rejected because provider ownership belongs to the installation and must remain immutable after activation.

Import cloud SDKs in the common domain

Rejected because it increases dependency and credential surface, prevents credential-free tests, and couples policy contracts to external APIs.